gabriel / muse public
_muse
603 lines 29.5 KB
9ef121d1 feat: add Oh My ZSH plugin for Muse (all six phases) Gabriel Cardona <gabriel@tellurstori.com> 3d ago
1 #compdef muse
2 # _muse — ZSH completion for the Muse version control CLI
3 # ─────────────────────────────────────────────────────────────────────────────
4 # Coverage:
5 # Top-level commands ~50 commands with descriptions
6 # Branch arguments checkout, merge, branch -d, cherry-pick
7 # Tag arguments tag create/delete
8 # Remote arguments push, pull, fetch, remote subcommands
9 # Short SHA arguments log, show, diff, reset, revert, blame, reflog
10 # Tracked file arguments midi/code subcommands that take file paths
11 # Config key paths config get/set
12 # Domain subcommands midi (25), code (28), coord (6), plumbing (12)
13 # Per-command flags log, diff, commit, branch, status, stash, …
14 #
15 # All branch/tag/remote lookups read directly from .muse/ — no subprocess.
16 # Tracked-file lookup uses muse plumbing ls-files (one subprocess, deferred).
17 # ─────────────────────────────────────────────────────────────────────────────
18
19 # ── Helper: walk up to find .muse/ root (pure ZSH) ────────────────────────────
20 function _muse_find_root_path() {
21 local dir="$PWD"
22 while [[ "$dir" != "/" ]]; do
23 [[ -d "$dir/.muse" ]] && { echo "$dir"; return 0; }
24 dir="${dir:h}"
25 done
26 return 1
27 }
28
29 # ── Branch names from .muse/refs/heads/ ───────────────────────────────────────
30 function _muse_branches() {
31 local root
32 root=$(_muse_find_root_path) || return 1
33 local refs_dir="$root/.muse/refs/heads"
34 [[ -d "$refs_dir" ]] || return 1
35 local -a branches
36 branches=(${(f)"$(ls "$refs_dir" 2>/dev/null)"})
37 (( ${#branches} )) && _describe 'branch' branches
38 }
39
40 # ── Tag names from .muse/tags/ ────────────────────────────────────────────────
41 function _muse_tags() {
42 local root
43 root=$(_muse_find_root_path) || return 1
44 local tags_dir="$root/.muse/tags"
45 [[ -d "$tags_dir" ]] || return 1
46 # Each tag file has a "tag" key — collect unique names via python3.
47 local -a tags
48 tags=(${(f)"$(MUSE_META_ROOT="$root" python3 <<'PYEOF' 2>/dev/null
49 import json, os, glob
50 root = os.environ['MUSE_META_ROOT']
51 seen = set()
52 for f in glob.glob(os.path.join(root, '.muse', 'tags', '*', '*.json')):
53 try:
54 name = json.load(open(f)).get('tag', '')
55 if name and name not in seen:
56 seen.add(name)
57 print(name)
58 except Exception:
59 pass
60 PYEOF
61 )"})
62 (( ${#tags} )) && _describe 'tag' tags
63 }
64
65 # ── Remote names from .muse/remotes/ ──────────────────────────────────────────
66 function _muse_remotes() {
67 local root
68 root=$(_muse_find_root_path) || return 1
69 local remotes_dir="$root/.muse/remotes"
70 [[ -d "$remotes_dir" ]] || return 1
71 local -a remotes
72 remotes=(${(f)"$(ls "$remotes_dir" 2>/dev/null)"})
73 (( ${#remotes} )) && _describe 'remote' remotes
74 }
75
76 # ── Short SHAs (first 8 chars) from .muse/commits/ ───────────────────────────
77 function _muse_short_shas() {
78 local root
79 root=$(_muse_find_root_path) || return 1
80 local commits_dir="$root/.muse/commits"
81 [[ -d "$commits_dir" ]] || return 1
82 local -a shas
83 shas=(${(f)"$(ls "$commits_dir" 2>/dev/null | sed 's/\.json$//' | cut -c1-8)"})
84 (( ${#shas} )) && _describe 'commit' shas
85 }
86
87 # ── Tracked files via muse plumbing ls-files (one subprocess, lazy) ───────────
88 function _muse_tracked_files() {
89 local root
90 root=$(_muse_find_root_path) || return 1
91 local -a files
92 files=(${(f)"$(cd "$root" && muse plumbing ls-files 2>/dev/null | awk '{print $1}')"})
93 (( ${#files} )) && _describe 'tracked file' files
94 }
95
96 # ── Known config key paths ────────────────────────────────────────────────────
97 function _muse_config_keys() {
98 local -a keys
99 keys=(
100 'user.name:Your display name (human or agent handle)'
101 'user.email:Your email address'
102 'user.type:Identity type — "human" or "agent"'
103 'hub.url:MuseHub fabric endpoint URL'
104 'domain.ticks_per_beat:MIDI ticks per beat (midi domain)'
105 'domain.default_channel:MIDI default channel (midi domain)'
106 )
107 _describe 'config key' keys
108 }
109
110 # ── Main completion function ───────────────────────────────────────────────────
111 function _muse() {
112 local context curcontext="$curcontext" state state_descr line
113 typeset -A opt_args
114
115 local -a cmds
116 cmds=(
117 # ── Core VCS ──────────────────────────────────────────────────────────────
118 'init:Initialise a new Muse repository in the current directory'
119 'status:Show working-tree status (dirty files, merge conflicts)'
120 'log:Show commit history with domain-aware metadata and graph'
121 'diff:Show changes between commits, branches, or the working tree'
122 'show:Show a commit, snapshot, or object in detail'
123 'commit:Record changes to the repository as a new commit'
124 'branch:List, create, or delete branches'
125 'checkout:Switch branches or restore working-tree files'
126 'merge:Merge two branches with domain-aware three-way merge'
127 'reset:Move HEAD (and optionally the working tree) to a commit'
128 'revert:Create new commits that undo one or more existing commits'
129 'cherry-pick:Apply changes from a specific commit to the current branch'
130 'stash:Save and restore working-tree changes without committing'
131 'tag:Create, list, or delete annotated tags'
132 'blame:Show which commit last modified each tracked file'
133 'reflog:Show a log of all HEAD movements and branch updates'
134 'bisect:Binary search through history to find a regression'
135 'gc:Garbage-collect loose objects and orphaned blobs'
136 'archive:Create a tarball of a named tree'
137 'worktree:Manage multiple working trees from one repository'
138 'workspace:Manage named multi-repository workspaces'
139 # ── Remotes ───────────────────────────────────────────────────────────────
140 'remote:Add, remove, rename, and inspect remote references'
141 'clone:Clone a remote Muse repository to a local directory'
142 'fetch:Download objects and refs from a remote without merging'
143 'pull:Fetch and integrate changes from a remote branch'
144 'push:Upload local commits to a remote branch'
145 # ── Identity & hub ────────────────────────────────────────────────────────
146 'auth:Manage authentication (login, logout, whoami)'
147 'hub:Connect and interact with the MuseHub fabric'
148 'config:Read and write repository or global configuration'
149 # ── Domain utilities ──────────────────────────────────────────────────────
150 'domains:List all installed and available domain plugins'
151 'check:Run domain invariant checks on the current snapshot'
152 'annotate:Annotate file lines with commit and author provenance'
153 'attributes:Manage per-path domain attributes (.museattributes)'
154 # ── Domain commands ───────────────────────────────────────────────────────
155 'midi:MIDI domain — analysis, transformation, and multi-agent commands'
156 'code:Code domain — symbol analysis, refactor detection, and semantic search'
157 'coord:Multi-agent coordination — reservation, intent, and reconciliation'
158 # ── Plumbing ──────────────────────────────────────────────────────────────
159 'plumbing:Low-level machine-readable plumbing commands (JSON output)'
160 )
161
162 _arguments -C \
163 '(-h --help)'{-h,--help}'[Show help and exit]' \
164 '(-v --version)'{-v,--version}'[Show version and exit]' \
165 '1: :->command' \
166 '*:: :->args'
167
168 case "$state" in
169 command)
170 _describe 'muse command' cmds
171 return
172 ;;
173
174 args)
175 case "${line[1]}" in
176
177 # ── Checkout ──────────────────────────────────────────────────────────
178 checkout)
179 _arguments \
180 '-b[Create a new branch and switch to it]:branch name:' \
181 '--detach[Detach HEAD at the target commit]' \
182 '*:branch or tracked file:_muse_branches'
183 ;;
184
185 # ── Merge ─────────────────────────────────────────────────────────────
186 merge)
187 _arguments \
188 '--no-ff[Always create a merge commit]' \
189 '--strategy=[Merge strategy (ours/theirs/auto/dimension-merge)]:strategy:(ours theirs auto dimension-merge manual union)' \
190 '*:branch to merge:_muse_branches'
191 ;;
192
193 # ── Branch ────────────────────────────────────────────────────────────
194 branch)
195 _arguments \
196 '(-d --delete)'{-d,--delete}'[Delete a branch]:branch:_muse_branches' \
197 '(-v --verbose)'{-v,--verbose}'[Show commit IDs alongside branch names]' \
198 '1:new branch name:' \
199 '2:start point:_muse_short_shas'
200 ;;
201
202 # ── Tag ───────────────────────────────────────────────────────────────
203 tag)
204 _arguments \
205 '(-d --delete)'{-d,--delete}'[Delete a tag]:tag:_muse_tags' \
206 '1:tag name:_muse_tags' \
207 '2:commit SHA:_muse_short_shas'
208 ;;
209
210 # ── Remote ────────────────────────────────────────────────────────────
211 remote)
212 local -a remote_cmds
213 remote_cmds=(
214 'add:Register a new remote reference'
215 'remove:Remove a remote and all tracking pointers'
216 'rename:Rename an existing remote'
217 'list:Print all configured remotes'
218 'show:Show URL and tracking info for a remote'
219 'set-url:Change the URL of an existing remote'
220 )
221 _arguments \
222 '1: :->remote_cmd' \
223 '*:: :->remote_args'
224 case "$state" in
225 remote_cmd) _describe 'remote subcommand' remote_cmds ;;
226 remote_args) _muse_remotes ;;
227 esac
228 ;;
229
230 # ── Push / pull / fetch ───────────────────────────────────────────────
231 push)
232 _arguments \
233 '--force[Force-push (overwrite remote history)]' \
234 '--dry-run[Simulate the push without sending data]' \
235 '1:remote:_muse_remotes' \
236 '2:branch:_muse_branches'
237 ;;
238 pull)
239 _arguments \
240 '--rebase[Rebase local commits on top of remote]' \
241 '1:remote:_muse_remotes' \
242 '2:branch:_muse_branches'
243 ;;
244 fetch)
245 _arguments \
246 '--all[Fetch all remotes]' \
247 '1:remote:_muse_remotes' \
248 '2:branch:_muse_branches'
249 ;;
250
251 # ── Ref-argument commands ─────────────────────────────────────────────
252 show|blame|reflog)
253 _arguments '*:ref or file:_muse_short_shas'
254 ;;
255 reset)
256 _arguments \
257 '--hard[Reset working tree and index to commit]' \
258 '--soft[Move HEAD only, keep index and working tree]' \
259 '*:commit:_muse_short_shas'
260 ;;
261 revert)
262 _arguments \
263 '--no-commit[Stage the revert without committing]' \
264 '*:commit to revert:_muse_short_shas'
265 ;;
266 cherry-pick)
267 _arguments '*:commit to apply:_muse_short_shas'
268 ;;
269
270 # ── Log ───────────────────────────────────────────────────────────────
271 log)
272 _arguments \
273 '--oneline[Compact one-commit-per-line format]' \
274 '--graph[Draw ASCII commit graph]' \
275 '--stat[Show file-change statistics per commit]' \
276 '--patch[Show full diff patch per commit]' \
277 '(-n --max-count)'{-n,--max-count}'[Maximum number of commits to show]:count:' \
278 '--since[Show commits after this date]:date:' \
279 '--until[Show commits before this date]:date:' \
280 '--author[Filter by commit author]:author:' \
281 '--section[Filter by section metadata]:section:' \
282 '--track[Filter by track metadata]:track:' \
283 '--emotion[Filter by emotion metadata]:emotion:'
284 ;;
285
286 # ── Diff ──────────────────────────────────────────────────────────────
287 diff)
288 _arguments \
289 '--stat[Show diffstat summary only]' \
290 '--patch[Show full structured patch]' \
291 '1:first ref:_muse_short_shas' \
292 '2:second ref:_muse_short_shas'
293 ;;
294
295 # ── Status ────────────────────────────────────────────────────────────
296 status)
297 _arguments \
298 '(-s --short)'{-s,--short}'[Compact output]' \
299 '--porcelain[Machine-readable output for scripts]' \
300 '(-b --branch)'{-b,--branch}'[Show branch information only]'
301 ;;
302
303 # ── Commit ────────────────────────────────────────────────────────────
304 commit)
305 _arguments \
306 '(-m --message)'{-m,--message}'[Commit message]:message:' \
307 '*--meta[Domain metadata key=value pair]:key=value:' \
308 '--agent-id[Agent identity string (set by muse-agent-session)]:agent_id:' \
309 '--model-id[AI model identifier]:model_id:' \
310 '--toolchain-id[Toolchain that produced this commit]:toolchain_id:'
311 ;;
312
313 # ── Stash ─────────────────────────────────────────────────────────────
314 stash)
315 local -a stash_cmds
316 stash_cmds=(
317 'push:Save working-tree changes to the stash stack'
318 'pop:Restore and remove the latest stash entry'
319 'drop:Delete a stash entry without restoring it'
320 'list:List all stash entries'
321 'show:Show the contents of a stash entry'
322 )
323 _arguments \
324 '1: :->stash_cmd' \
325 '*:: :->stash_args'
326 case "$state" in
327 stash_cmd) _describe 'stash subcommand' stash_cmds ;;
328 esac
329 ;;
330
331 # ── Config ────────────────────────────────────────────────────────────
332 config)
333 local -a config_cmds
334 config_cmds=(
335 'show:Print the full resolved configuration'
336 'get:Get the value of a config key'
337 'set:Set a config key to a value'
338 'edit:Open the config file in $EDITOR'
339 )
340 _arguments \
341 '1: :->config_cmd' \
342 '*:: :->config_args'
343 case "$state" in
344 config_cmd)
345 _describe 'config subcommand' config_cmds
346 ;;
347 config_args)
348 case "${line[2]}" in
349 get|set) _muse_config_keys ;;
350 esac
351 ;;
352 esac
353 ;;
354
355 # ── Auth ──────────────────────────────────────────────────────────────
356 auth)
357 local -a auth_cmds
358 auth_cmds=(
359 'login:Authenticate and store credentials for MuseHub'
360 'logout:Remove stored authentication credentials'
361 'whoami:Show current authenticated identity'
362 )
363 _describe 'auth subcommand' auth_cmds
364 ;;
365
366 # ── Hub ───────────────────────────────────────────────────────────────
367 hub)
368 local -a hub_cmds
369 hub_cmds=(
370 'connect:Connect this repository to MuseHub'
371 'status:Show MuseHub connection and fabric status'
372 'disconnect:Disconnect from MuseHub'
373 'ping:Test connectivity to the hub endpoint'
374 )
375 _describe 'hub subcommand' hub_cmds
376 ;;
377
378 # ── Worktree ──────────────────────────────────────────────────────────
379 worktree)
380 local -a wt_cmds
381 wt_cmds=(
382 'add:Add a new linked working tree'
383 'list:List all working trees'
384 'remove:Remove a linked working tree'
385 'prune:Remove stale working-tree administrative files'
386 )
387 _describe 'worktree subcommand' wt_cmds
388 ;;
389
390 # ── Workspace ─────────────────────────────────────────────────────────
391 workspace)
392 local -a ws_cmds
393 ws_cmds=(
394 'create:Create a new named workspace'
395 'list:List all workspaces'
396 'switch:Switch to a different workspace'
397 'delete:Delete a workspace (does not delete repositories)'
398 )
399 _describe 'workspace subcommand' ws_cmds
400 ;;
401
402 # ── Bisect ────────────────────────────────────────────────────────────
403 bisect)
404 local -a bisect_cmds
405 bisect_cmds=(
406 'start:Start a bisect session'
407 'good:Mark the current commit as good'
408 'bad:Mark the current commit as bad'
409 'reset:End the bisect session and return to original HEAD'
410 'log:Show the bisect log'
411 )
412 _describe 'bisect subcommand' bisect_cmds
413 ;;
414
415 # ── Plumbing ──────────────────────────────────────────────────────────
416 plumbing)
417 local -a plumbing_cmds
418 plumbing_cmds=(
419 'hash-object:Compute SHA-256 of a file; optionally write to object store'
420 'cat-object:Emit raw bytes of a content-addressed object to stdout'
421 'rev-parse:Resolve a branch, tag, or SHA prefix to a full commit ID'
422 'ls-files:List all tracked files and their object IDs in the HEAD snapshot'
423 'read-commit:Emit full CommitRecord as JSON'
424 'read-snapshot:Emit full SnapshotRecord manifest as JSON'
425 'commit-tree:Create a commit from an explicit snapshot_id'
426 'update-ref:Move a branch HEAD pointer to a commit ID'
427 'commit-graph:Emit the full commit DAG as a JSON node list'
428 'pack-objects:Build a PackBundle JSON from wanted commits (for transport)'
429 'unpack-objects:Unpack a PackBundle JSON from stdin into the local object store'
430 'ls-remote:List branch heads on a remote without modifying local state'
431 )
432 _arguments \
433 '1: :->plumbing_cmd' \
434 '*:: :->plumbing_args'
435 case "$state" in
436 plumbing_cmd) _describe 'plumbing subcommand' plumbing_cmds ;;
437 plumbing_args)
438 case "${line[2]}" in
439 rev-parse|read-commit|read-snapshot|commit-tree) _muse_short_shas ;;
440 ls-remote) _muse_remotes ;;
441 update-ref) _arguments '1:branch:_muse_branches' '2:commit:_muse_short_shas' ;;
442 hash-object|cat-object) _files ;;
443 esac
444 ;;
445 esac
446 ;;
447
448 # ── MIDI domain ───────────────────────────────────────────────────────
449 midi)
450 local -a midi_cmds
451 midi_cmds=(
452 # Analysis
453 'notes:List all NoteEvent entities in the current snapshot'
454 'note-log:Show note-level change history across commits'
455 'note-blame:Show which commit introduced each note'
456 'harmony:Analyse chord progressions and harmonic content'
457 'piano-roll:Render an ASCII piano roll of the snapshot'
458 'hotspots:Most-frequently changed notes or sections'
459 'velocity-profile:Statistical distribution of MIDI velocities'
460 'rhythm:Analyse rhythmic patterns and time signatures'
461 'scale:Detect probable key and scale from note content'
462 'contour:Show melodic contour (ascending/descending arc)'
463 'density:Note density per bar or time window'
464 'tension:Harmonic tension curve across the piece'
465 'cadence:Detect cadence points and resolution patterns'
466 'motif:Find and label recurring melodic motifs'
467 'voice-leading:Analyse voice-leading and part writing'
468 'instrumentation:Show instrument/channel assignment breakdown'
469 'tempo:Show tempo map and tempo-change events'
470 'compare:Compare two commits or branches by musical content'
471 # Transformation
472 'transpose:Transpose notes by a number of semitones'
473 'mix:Mix note content from multiple MIDI sources'
474 'quantize:Snap notes to a rhythmic grid'
475 'humanize:Add timing and velocity variation for expressiveness'
476 'invert:Invert melodic intervals around an axis pitch'
477 'retrograde:Reverse the note sequence in time'
478 'arpeggiate:Decompose chords into arpeggiated patterns'
479 'normalize:Normalise MIDI velocity range to a target window'
480 # Multi-agent
481 'shard:Partition snapshot into agent-assignable voice/channel shards'
482 'agent-map:Show current agent assignment map for the snapshot'
483 'find-phrase:Search for a melodic phrase across commit history'
484 # Structured query
485 'query:Run a MIDI query DSL expression against the snapshot'
486 'check:Verify all declared domain invariants pass'
487 )
488 _arguments \
489 '1: :->midi_cmd' \
490 '*:: :->midi_args'
491 case "$state" in
492 midi_cmd) _describe 'midi subcommand' midi_cmds ;;
493 midi_args) _muse_tracked_files ;;
494 esac
495 ;;
496
497 # ── Code domain ───────────────────────────────────────────────────────
498 code)
499 local -a code_cmds
500 code_cmds=(
501 'symbols:List all symbols (functions, classes, types) in the snapshot'
502 'symbol-log:Show symbol-level change history across commits'
503 'detect-refactor:Detect rename, move, split, and merge refactorings'
504 'grep:Search symbol names and body content by pattern'
505 'blame:Show per-symbol commit provenance'
506 'hotspots:Symbols that change most frequently across history'
507 'stable:Symbols unchanged for the last N commits'
508 'coupling:Symbols that tend to change together (co-change analysis)'
509 'compare:Compare two commits by symbol content and API surface'
510 'languages:Language breakdown of the current snapshot'
511 'patch:Apply a structured code delta to the working tree'
512 'query:Run a code query DSL expression against the snapshot'
513 'query-history:Run a query expression across all commits in history'
514 'deps:Show the dependency graph for a symbol or module'
515 'find-symbol:Search for a symbol by name or regex pattern'
516 'impact:Estimate change impact from a set of modified symbols'
517 'dead:Detect unreachable or dead code symbols'
518 'coverage:Show test coverage attributed to each symbol'
519 'lineage:Show full ancestry of a symbol across renames and moves'
520 'api-surface:Show public API symbols and their change history'
521 'codemap:Generate a high-level code structure map'
522 'clones:Detect duplicate or near-duplicate code blocks'
523 'checkout-symbol:Check out a specific symbol at a past commit'
524 'semantic-cherry-pick:Cherry-pick by symbol address rather than commit SHA'
525 'index:Rebuild the code symbol index from scratch'
526 'breakage:Detect breaking changes in the API surface between commits'
527 'invariants:Show declared code invariants for the snapshot'
528 'check:Verify all declared code invariants pass'
529 'code-query:Advanced code query with predicate and pattern DSL'
530 )
531 _arguments \
532 '1: :->code_cmd' \
533 '*:: :->code_args'
534 case "$state" in
535 code_cmd) _describe 'code subcommand' code_cmds ;;
536 code_args) _muse_tracked_files ;;
537 esac
538 ;;
539
540 # ── Coord domain ──────────────────────────────────────────────────────
541 coord)
542 local -a coord_cmds
543 coord_cmds=(
544 'reserve:Reserve a set of files or symbols for exclusive editing'
545 'intent:Declare editing intent for a path or symbol address'
546 'forecast:Predict merge conflicts from all declared intents'
547 'plan-merge:Generate a conflict-free merge plan from declared intents'
548 'shard:Partition the snapshot into agent-assignable shards'
549 'reconcile:Reconcile and apply parallel edits from multiple agents'
550 )
551 _arguments \
552 '1: :->coord_cmd' \
553 '*:: :->coord_args'
554 case "$state" in
555 coord_cmd) _describe 'coord subcommand' coord_cmds ;;
556 coord_args) _muse_tracked_files ;;
557 esac
558 ;;
559
560 # ── Clone ─────────────────────────────────────────────────────────────
561 clone)
562 _arguments \
563 '--branch=[Branch to clone]:branch:' \
564 '--depth=[Shallow clone depth]:depth:' \
565 '1:remote URL:' \
566 '2:local directory:_directories'
567 ;;
568
569 # ── Archive ───────────────────────────────────────────────────────────
570 archive)
571 _arguments \
572 '--format=[Archive format (tar/zip)]:format:(tar zip)' \
573 '--output=[Output file path]:output:_files' \
574 '1:tree-ish:_muse_short_shas'
575 ;;
576
577 # ── Annotate / attributes ─────────────────────────────────────────────
578 annotate)
579 _arguments '*:file:_muse_tracked_files'
580 ;;
581 attributes)
582 local -a attr_cmds
583 attr_cmds=(
584 'list:Show all path attribute rules'
585 'check:Show effective attributes for a path'
586 )
587 _arguments '1: :->attr_cmd' '*:: :->attr_args'
588 case "$state" in
589 attr_cmd) _describe 'attributes subcommand' attr_cmds ;;
590 attr_args) _muse_tracked_files ;;
591 esac
592 ;;
593
594 # ── Fallback: file completion ─────────────────────────────────────────
595 *)
596 _files
597 ;;
598 esac
599 ;;
600 esac
601 }
602
603 _muse "$@"