gabriel / muse public
omzsh-plugin.md markdown
480 lines 15.7 KB
9ef121d1 feat: add Oh My ZSH plugin for Muse (all six phases) Gabriel Cardona <gabriel@tellurstori.com> 3d ago
1 # Muse Oh My ZSH Plugin — Full Reference
2
3 This document is the complete reference for the Muse Oh My ZSH plugin. For a quick-start guide see [`tools/omzsh-plugin/README.md`](../../tools/omzsh-plugin/README.md).
4
5 ---
6
7 ## Architecture
8
9 The plugin is divided into 14 sections (§0–§13) inside `tools/omzsh-plugin/muse.plugin.zsh`. The companion file `tools/omzsh-plugin/_muse` contains the ZSH completion function.
10
11 ### Performance model
12
13 The prompt segment must never block the shell. The plugin achieves this by:
14
15 1. **Zero-subprocess file reads** for branch (`.muse/HEAD`), merge state (`.muse/MERGE_STATE.json`), and domain (`.muse/repo.json`) — all raw file reads inside ZSH.
16 2. **One python3 subprocess** per full refresh for JSON/TOML parsing (domain, user type, merge state, SemVer). This is batched into a single call.
17 3. **One muse subprocess** for dirty detection (`muse status --porcelain`), guarded by `$MUSE_DIRTY_TIMEOUT`. Only runs after a muse command (`$_MUSE_CMD_RAN=1`) or on a cold cache.
18 4. **Cached state** in `$MUSE_*` env vars, invalidated by `chpwd` and `preexec`.
19
20 ```
21 chpwd ─────────────────────────────────► invalidate → _muse_refresh_fast
22 (head + meta only)
23 preexec (muse cmd) ─► _MUSE_CMD_RAN=1
24 precmd ─────────────────────────────────► _MUSE_CMD_RAN? → _muse_refresh
25 (full: + dirty + semver)
26 _MUSE_CACHE_VALID=0? → _muse_refresh
27 ```
28
29 ### Environment variables
30
31 All `$MUSE_*` variables are exported and visible to subprocesses. This is the machine-to-machine interface — an orchestrating AI agent can read any of these.
32
33 | Variable | Type | Content |
34 |---|---|---|
35 | `MUSE_REPO_ROOT` | string | Absolute path to the repo root, or `""` |
36 | `MUSE_DOMAIN` | string | Active domain plugin (`midi`, `code`, `bitcoin`, …) |
37 | `MUSE_BRANCH` | string | Current branch name, or 8-char SHA if detached |
38 | `MUSE_DETACHED` | 0/1 | 1 when HEAD is detached |
39 | `MUSE_DIRTY` | 0/1 | 1 when working tree has uncommitted changes |
40 | `MUSE_DIRTY_COUNT` | integer | Number of changed paths |
41 | `MUSE_DIRTY_STATE` | string | `"clean"` \| `"dirty"` \| `"?"` (timeout) |
42 | `MUSE_MERGING` | 0/1 | 1 when `MERGE_STATE.json` exists |
43 | `MUSE_MERGE_BRANCH` | string | The branch being merged in |
44 | `MUSE_CONFLICT_COUNT` | integer | Number of conflict paths |
45 | `MUSE_USER_TYPE` | string | `"human"` \| `"agent"` (from `config.toml`) |
46 | `MUSE_LAST_SEMVER` | string | `"major"` \| `"minor"` \| `"patch"` \| `""` |
47 | `MUSE_CONTEXT_JSON` | JSON string | Full compact context (see below) |
48 | `MUSE_SESSION_MODEL_ID` | string | Active agent session model |
49 | `MUSE_SESSION_AGENT_ID` | string | Active agent session ID |
50 | `MUSE_SESSION_START` | ISO-8601 | When the agent session started |
51 | `MUSE_SESSION_LOG_FILE` | path | Path to the active session `.jsonl` log |
52
53 ---
54
55 ## Prompt Functions
56
57 ### `muse_prompt_info()`
58
59 Primary left-prompt segment. Outputs nothing if not in a muse repo.
60
61 **Human mode** (default):
62 ```
63 ♪ midi:main ✓
64 ♪ midi:feat/x ✗ 3Δ
65 ♪ midi:main ⚡ ← feature/exp (2 conflicts)
66 ♪ midi:main ✓ [🤖 claude-4.6-sonnet]
67 ```
68
69 **Agent mode** (`MUSE_AGENT_MODE=1`):
70 ```
71 [midi|main|clean|no-merge]
72 [midi|main|dirty:3|merging:feature/exp:2|agent:coding-assistant]
73 ```
74
75 ### `muse_rprompt_info()`
76
77 Right-prompt SemVer indicator. Shows the `sem_ver_bump` field of the HEAD commit.
78
79 ```
80 [MINOR] (yellow)
81 [MAJOR] (red)
82 [PATCH] (green)
83 ```
84
85 ### `prompt_muse_vcs()` and `instant_prompt_muse_vcs()`
86
87 Powerlevel10k segment implementations. Use by adding `muse_vcs` to
88 `POWERLEVEL9K_LEFT_PROMPT_ELEMENTS` or `POWERLEVEL9K_RIGHT_PROMPT_ELEMENTS`.
89
90 ---
91
92 ## Workflow Functions
93
94 ### Branch creation
95
96 ```zsh
97 muse-new-feat <name> # creates and switches to feat/<name>
98 muse-new-fix <name> # creates and switches to fix/<name>
99 muse-new-refactor <name> # creates and switches to refactor/<name>
100 ```
101
102 ### Commits
103
104 ```zsh
105 muse-wip
106 ```
107 Commits with message `[WIP] 2026-03-20T14:30:00Z`. Useful for checkpointing
108 before switching context.
109
110 ```zsh
111 muse-quick-commit
112 ```
113 Interactive guided commit. Prompts for message and domain-specific metadata:
114 - **midi**: section (verse/chorus/bridge), track name, emotion
115 - **code**: module/package, breaking change flag
116
117 ```zsh
118 muse-agent-commit "message" [extra flags…]
119 ```
120 Wraps `muse commit` and auto-injects `--agent-id` and `--model-id` from the
121 active agent session (`$MUSE_SESSION_AGENT_ID`, `$MUSE_SESSION_MODEL_ID`).
122 Use this instead of bare `muse commit` when inside an agent session.
123
124 ### Sync & merge
125
126 ```zsh
127 muse-sync
128 ```
129 Runs `muse fetch && muse pull && muse status` in sequence.
130
131 ```zsh
132 muse-safe-merge <branch>
133 ```
134 Runs `muse merge`. If conflicts occur, prints a structured conflict list and
135 optionally opens conflict paths in `$EDITOR`.
136
137 ### Health & provenance
138
139 ```zsh
140 muse-health
141 ```
142 Shows a formatted health summary: dirty state, merge state, stash count, configured remotes, domain, user type, active agent session.
143
144 ```zsh
145 muse-who-last
146 ```
147 Shows authorship provenance of the HEAD commit. Distinguishes human vs agent authorship and shows model ID if present.
148
149 ```zsh
150 muse-agent-blame [N]
151 ```
152 Scans the last N commits (default 10) and prints a provenance table:
153
154 ```
155 Agent provenance — last 10 commits on main
156 SHA Date Type Author/Model Message
157 ────────── ────────── ────── ────────────────────────────── ──────────────
158 a1b2c3d4 2026-03-20 agent claude-4.6-sonnet Refactor notes
159 e5f6a7b8 2026-03-19 human gabriel Add bass track
160 ```
161
162 ```zsh
163 muse-overview
164 ```
165 Domain-specific live state overview. Calls `muse midi notes` for MIDI repos,
166 `muse code symbols` for code repos.
167
168 ---
169
170 ## Agent-Native Functions
171
172 ### `muse-context [--json|--toml|--oneline]`
173
174 Outputs a compact, token-efficient repo context block. Designed to be injected
175 into an AI agent's context window.
176
177 **Default (human)**:
178 ```
179 MUSE REPO CONTEXT ♪ midi:main
180 ──────────────────────────────────────────────────────
181 domain midi
182 branch main
183 commit a1b2c3d4 "Add verse melody" (2026-03-20)
184 last author gabriel [human]
185 semver MINOR
186 dirty yes — 3 changed
187 merging no
188 remotes origin
189 user human
190 ```
191
192 **`--json`**: pretty-printed JSON (uses `$MUSE_CONTEXT_JSON` as source)
193
194 **`--toml`**: TOML format (two sections: `[repo]` and `[commit]`)
195
196 **`--oneline`**: `midi:main dirty:3 commit:a1b2c3d4`
197
198 ### `muse-agent-session <model_id> [agent_id]`
199
200 Begins a named agent session:
201 - Exports `$MUSE_SESSION_MODEL_ID`, `$MUSE_SESSION_AGENT_ID`, `$MUSE_SESSION_START`
202 - Creates a `.jsonl` session log in `$MUSE_SESSION_LOG_DIR`
203 - Exports `$MUSE_SESSION_LOG_FILE` pointing to the log
204 - Updates the prompt to show `[🤖 <model_id>]`
205
206 ```zsh
207 muse-agent-session claude-4.6-sonnet coding-assistant
208 # model claude-4.6-sonnet
209 # agent coding-assistant
210 # log ~/.muse/sessions/20260320-143000-12345.jsonl
211 ```
212
213 ### `muse-agent-end`
214
215 Ends the current session, writes a `session_end` entry to the log, unsets all
216 `$MUSE_SESSION_*` variables, and refreshes the prompt.
217
218 ### `muse-sessions [file]`
219
220 Without arguments: lists the 20 most recent session `.jsonl` files with model ID, agent ID, and start time.
221
222 With a file argument: replays the session log, showing each command with its timestamp, exit code, and elapsed milliseconds:
223
224 ```
225 2026-03-20T14:23:11 SESSION START model=claude-4.6-sonnet agent=coding-assistant
226 2026-03-20T14:23:15 $ muse status
227 2026-03-20T14:23:16 EXIT 0 (312ms)
228 2026-03-20T14:23:20 $ muse commit -m "Refactor velocity normalisation"
229 2026-03-20T14:23:21 EXIT 0 (891ms)
230 2026-03-20T14:24:00 SESSION END
231 ```
232
233 ### Session log format
234
235 Each `.jsonl` file contains one JSON object per line:
236
237 ```jsonl
238 {"t":"2026-03-20T14:23:11Z","event":"session_start","model_id":"claude-4.6-sonnet","agent_id":"coding-assistant","domain":"midi","branch":"main","repo_root":"/proj","pid":12345}
239 {"t":"2026-03-20T14:23:15Z","cmd":"muse status","cwd":"/proj","domain":"midi","branch":"main","pid":12345}
240 {"t":"2026-03-20T14:23:16Z","event":"cmd_end","exit":0,"elapsed_ms":312}
241 {"t":"2026-03-20T14:24:00Z","event":"session_end","model_id":"claude-4.6-sonnet","agent_id":"coding-assistant","start":"2026-03-20T14:23:11Z"}
242 ```
243
244 The `cmd_end` entry always immediately follows the `cmd` entry it closes. All timestamps are UTC ISO-8601.
245
246 ---
247
248 ## Visual Tools
249
250 ### `muse-graph [log flags…]`
251
252 Wraps `muse log --graph --oneline` with Python-driven ANSI colorisation:
253
254 - Graph chrome (`*`, `|`, `/`, `\`) coloured with the domain's theme colour
255 - Commit SHAs highlighted in yellow
256 - `HEAD -> branch` highlighted in bold green
257 - `[MAJOR]` in red, `[MINOR]` in yellow, `[PATCH]` in green
258 - `[agent:id]` markers in cyan
259
260 Passes any extra flags through to `muse log`:
261
262 ```zsh
263 muse-graph -n 20
264 muse-graph --since "2026-01-01"
265 ```
266
267 ### `muse-timeline [N]`
268
269 Vertical timeline of the last N commits (default 20). Uses domain colours and Unicode box-drawing characters:
270
271 ```
272 ♪ TIMELINE — main (last 5 commits)
273 ────────────────────────────────────────────────────────────
274 ◉ a1b2c3d4 Add verse melody
275
276 ○ e5f6a7b8 Transpose chorus up 2 semitones
277
278 ○ c9d0e1f2 Add bass line
279
280 ○ a3b4c5d6 Initial commit
281
282 ```
283
284 ### `muse-diff-preview [ref…]`
285
286 Pipes `muse diff` output through `delta` (preferred) or `bat` for syntax
287 highlighting. Falls back to plain output if neither is installed.
288
289 ### `muse-commit-browser`
290
291 fzf-powered commit browser. Requires `fzf`.
292
293 - Left pane: `muse log --oneline`
294 - Right pane: `muse show <selected>` preview
295 - `↵` — checkout the selected commit
296 - `ctrl-d` — show full diff in `less`
297 - `ctrl-y` — copy commit SHA to clipboard
298 - `ctrl-s` — open `muse show` in `less`
299
300 ### `muse-branch-picker`
301
302 fzf-powered branch switcher. Also bound to `Ctrl+B`. Requires `fzf`.
303
304 - Right pane: last 8 commits on the highlighted branch
305 - `↵` — checkout the selected branch
306 - `ctrl-d` — delete the selected branch
307
308 ### `muse-stash-browser`
309
310 fzf-powered stash browser. Requires `fzf`.
311
312 - Right pane: `muse stash show` preview
313 - `↵` — pop the selected stash
314 - `ctrl-d` — drop the selected stash
315
316 ---
317
318 ## Completion Reference
319
320 ### Top-level commands
321
322 All ~50 top-level muse commands are listed with one-line descriptions. Descriptions are intentionally domain-aware (not generic VCS language).
323
324 ### Per-command argument completion
325
326 | Command | What completes |
327 |---|---|
328 | `checkout` | branch names + `-b` flag |
329 | `merge` | branch names + `--strategy` values |
330 | `branch` | branch names for `-d`, flags |
331 | `tag` | tag names, short SHAs |
332 | `push` | remote names, branch names, `--force` |
333 | `pull` | remote names, branch names, `--rebase` |
334 | `fetch` | remote names, branch names, `--all` |
335 | `remote` | `add/remove/rename/list/show/set-url` → remote names |
336 | `log` | all flags with values where applicable |
337 | `diff` | short SHAs + `--stat`/`--patch` |
338 | `show` | short SHAs |
339 | `reset` | short SHAs + `--hard`/`--soft` |
340 | `revert` | short SHAs + `--no-commit` |
341 | `cherry-pick` | short SHAs |
342 | `blame` | short SHAs |
343 | `commit` | `--meta key=val`, `--agent-id`, `--model-id`, `--toolchain-id` |
344 | `stash` | `push/pop/drop/list/show` |
345 | `config` | `show/get/set/edit` → config key paths |
346 | `auth` | `login/logout/whoami` |
347 | `hub` | `connect/status/disconnect/ping` |
348 | `worktree` | `add/list/remove/prune` |
349 | `workspace` | `create/list/switch/delete` |
350 | `bisect` | `start/good/bad/reset/log` |
351 | `plumbing` | all 12 plumbing subcommands + context-aware ref/remote/file args |
352 | `midi` | all 25 midi subcommands with MIDI-specific descriptions |
353 | `code` | all 28 code subcommands with code-analysis descriptions |
354 | `coord` | all 6 coordination subcommands |
355 | `clone` | `--branch`, `--depth`, remote URL, local directory |
356 | `archive` | `--format (tar/zip)`, `--output`, tree-ish |
357 | `annotate` | tracked file paths |
358 | `attributes` | `list/check` → tracked file paths |
359
360 ### Config key completions
361
362 | Key | Description |
363 |---|---|
364 | `user.name` | Display name (human or agent handle) |
365 | `user.email` | Email address |
366 | `user.type` | `"human"` or `"agent"` |
367 | `hub.url` | MuseHub fabric endpoint URL |
368 | `domain.ticks_per_beat` | MIDI ticks per beat |
369 | `domain.default_channel` | MIDI default channel |
370
371 ---
372
373 ## Hook System
374
375 Set these in `.zshrc` to run custom commands after muse operations:
376
377 ```zsh
378 # Desktop notification on commit (macOS)
379 MUSE_POST_COMMIT_CMD='osascript -e "display notification \"Committed\" with title \"Muse\""'
380
381 # Rebuild a local index after checkout
382 MUSE_POST_CHECKOUT_CMD='make index 2>/dev/null || true'
383
384 # Trigger CI after a clean merge
385 MUSE_POST_MERGE_CMD='curl -X POST https://ci.example.com/trigger'
386 ```
387
388 The `MUSE_POST_MERGE_CMD` fires only after a **clean** merge (no conflicts). During a conflicted merge, the hook is suppressed until you resolve and commit.
389
390 ---
391
392 ## Keybinding Reference
393
394 | Binding | Widget | Action |
395 |---|---|---|
396 | `Ctrl+B` | `_muse_widget_branch_picker` | Open fzf branch picker |
397 | `ESC-M` | `_muse_widget_commit_browser` | Open fzf commit browser |
398 | `ESC-H` | `_muse_widget_health` | Print repo health and reset prompt |
399
400 Set `MUSE_BIND_KEYS=0` to disable all keybindings.
401
402 ---
403
404 ## Agent Mode Reference
405
406 `MUSE_AGENT_MODE=1` is a master switch that transforms the plugin for agent
407 orchestration use:
408
409 | Feature | Normal mode | Agent mode |
410 |---|---|---|
411 | Prompt | `♪ midi:main ✗ 3Δ` | `[midi\|main\|dirty:3\|no-merge]` |
412 | `$MUSE_CONTEXT_JSON` | Set silently | Also emitted to stderr before each prompt |
413 | Emoji | Yes | No (ASCII only) |
414 | `muse_rprompt_info()` | Shows SemVer | Suppressed |
415
416 Typical use — start a subshell for an agent, set the flag, and have the
417 orchestrator read `$MUSE_CONTEXT_JSON` from stderr:
418
419 ```python
420 import subprocess, json
421
422 proc = subprocess.Popen(
423 ["zsh", "--interactive"],
424 env={**os.environ, "MUSE_AGENT_MODE": "1"},
425 stderr=subprocess.PIPE,
426 )
427 # Each prompt draw emits $MUSE_CONTEXT_JSON to stderr
428 context = json.loads(proc.stderr.readline())
429 print(context["domain"], context["branch"], context["dirty"])
430 ```
431
432 ---
433
434 ## Upgrading
435
436 Because the install script uses symlinks, `git pull` inside the muse repo
437 automatically updates the plugin. Run `source ~/.zshrc` to pick up changes
438 in the current shell session.
439
440 ---
441
442 ## Troubleshooting
443
444 **Prompt shows nothing in a muse repo**
445
446 1. Verify `muse` is in your `$PATH`: `which muse`
447 2. Verify you are inside a muse repo: `ls .muse/HEAD`
448 3. Check `_MUSE_CACHE_VALID` is being set: `echo $_MUSE_CACHE_VALID` (should be 1)
449 4. Run `_muse_refresh` manually and check for errors
450
451 **Dirty check shows `?`**
452
453 The `muse status --porcelain` call timed out. Increase `MUSE_DIRTY_TIMEOUT`:
454
455 ```zsh
456 MUSE_DIRTY_TIMEOUT=3 # allow 3 seconds before giving up
457 ```
458
459 **Completions not showing**
460
461 Ensure the `_muse` file is in your `$fpath`. The install script handles this
462 via symlinks. If you installed manually, add the plugin directory:
463
464 ```zsh
465 fpath=("$ZSH_CUSTOM/plugins/muse" $fpath)
466 autoload -Uz compinit && compinit
467 ```
468
469 **`muse-commit-browser` / `muse-branch-picker` not working**
470
471 Install [fzf](https://github.com/junegunn/fzf): `brew install fzf` (macOS) or `apt install fzf` (Debian/Ubuntu).
472
473 **Session logs not written**
474
475 Verify `$MUSE_SESSION_LOG_DIR` exists and is writable:
476
477 ```zsh
478 mkdir -p "$MUSE_SESSION_LOG_DIR"
479 ls -la "$MUSE_SESSION_LOG_DIR"
480 ```