gabriel / muse public
_muse
514 lines 20.3 KB
e74bbfd6 chore: ignore .hypothesis, .pytest_cache, .mypy_cache, .ruff_cache; add… gabriel 8h ago
1 #compdef muse
2 # Zsh completion for the Muse CLI.
3 #
4 # Installation (pick one):
5 # 1. Symlink into a directory already on $fpath:
6 # ln -sf /path/to/muse/completions/_muse ~/.zsh/completions/_muse
7 # 2. Oh-My-Zsh users — copy or symlink into the custom completions dir:
8 # ln -sf /path/to/muse/completions/_muse \
9 # ~/.oh-my-zsh/completions/_muse
10 # 3. Add the completions directory directly to $fpath in ~/.zshrc:
11 # fpath=(/path/to/muse/completions $fpath)
12 # autoload -Uz compinit && compinit
13 #
14 # After installing, reload completions:
15 # exec zsh OR autoload -Uz compinit && compinit -u
16
17 # ---------------------------------------------------------------------------
18 # Helpers — read repo state directly from the filesystem; no subprocess.
19 # ---------------------------------------------------------------------------
20
21 # Walk up from $PWD and return the .muse directory path, or "" if not found.
22 _muse_dot_muse() {
23 local dir="$PWD"
24 while [[ "$dir" != "/" ]]; do
25 if [[ -f "$dir/.muse/repo.json" ]]; then
26 print -r -- "$dir/.muse"
27 return 0
28 fi
29 dir="${dir:h}"
30 done
31 return 1
32 }
33
34 # Emit branch names by listing .muse/refs/heads/ — zero subprocesses.
35 _muse_branches() {
36 local dot_muse
37 dot_muse="$(_muse_dot_muse)" || return
38 local heads_dir="$dot_muse/refs/heads"
39 [[ -d "$heads_dir" ]] || return
40 local -a branches
41 # ${heads_dir}/**/*(N:t) recurses and strips directory prefix.
42 branches=(${(f)"$(find "$heads_dir" -type f 2>/dev/null | sed "s|$heads_dir/||")"})
43 (( ${#branches} )) && _describe 'branches' branches
44 }
45
46 # Emit release tag names by reading .muse/releases/<repo_id>/*.json — one python3.
47 _muse_release_tags() {
48 local dot_muse
49 dot_muse="$(_muse_dot_muse)" || return
50 local -a tags
51 tags=(${(f)"$(MUSE_DOT="$dot_muse" python3 <<'PYEOF' 2>/dev/null
52 import json, os, pathlib
53 dot = pathlib.Path(os.environ['MUSE_DOT'])
54 try:
55 repo_id = json.loads((dot / 'repo.json').read_text()).get('repo_id', '')
56 releases_dir = dot / 'releases' / repo_id
57 if releases_dir.is_dir():
58 for p in sorted(releases_dir.iterdir()):
59 if p.suffix == '.json':
60 try:
61 t = json.loads(p.read_text()).get('tag', '')
62 if t:
63 print(t)
64 except Exception:
65 pass
66 except Exception:
67 pass
68 PYEOF
69 )"})
70 (( ${#tags} )) && _describe 'release tags' tags
71 }
72
73 # Emit remote names from .muse/config.toml — zero subprocesses.
74 _muse_remotes() {
75 local dot_muse
76 dot_muse="$(_muse_dot_muse)" || { _values 'remote' 'origin' 'local'; return; }
77 local cfg="$dot_muse/config.toml"
78 local -a remotes
79 if [[ -f "$cfg" ]]; then
80 # Extract remote names from [remotes.<name>] section headers.
81 remotes=(${(f)"$(grep -E '^\[remotes\.' "$cfg" 2>/dev/null \
82 | sed -E 's/\[remotes\.([^]]+)\]/\1/')"})
83 fi
84 if (( ${#remotes} )); then
85 _describe 'remotes' remotes
86 else
87 _values 'remote' 'origin' 'local'
88 fi
89 }
90
91 # ---------------------------------------------------------------------------
92 # Command lists (kept in sync with muse/cli/app.py)
93 # ---------------------------------------------------------------------------
94
95 local -a _muse_top_cmds=(
96 'plumbing:Tier 1 machine-readable plumbing commands'
97 'init:Initialise a new Muse repository'
98 'status:Show working-tree drift against HEAD'
99 'log:Display commit history'
100 'commit:Record the current state as a new version'
101 'diff:Compare working tree against HEAD or two commits'
102 'show:Inspect a commit'
103 'branch:List, create, or delete branches'
104 'checkout:Switch branches or restore from a commit'
105 'merge:Three-way merge a branch into HEAD'
106 'reset:Move HEAD to a prior commit'
107 'revert:Undo a prior commit'
108 'cherry-pick:Apply a specific commit on top of HEAD'
109 'stash:Shelve uncommitted changes'
110 'tag:Attach and query semantic tags'
111 'release:Create, list, show, and push versioned releases'
112 'push:Upload commits and objects to a remote'
113 'pull:Fetch from a remote and merge'
114 'fetch:Download commits from a remote'
115 'clone:Create a local copy of a remote repository'
116 'remote:Manage remote connections'
117 'auth:Identity management'
118 'hub:MuseHub fabric connection'
119 'config:Local repository configuration'
120 'domains:Domain plugin dashboard'
121 'attributes:Display .museattributes rules'
122 'annotate:Attach CRDT annotations to a commit'
123 'blame:Show per-line commit provenance'
124 'reflog:History of HEAD and branch-ref movements'
125 'rerere:Reuse recorded conflict resolutions'
126 'gc:Remove unreachable objects'
127 'archive:Export a historical snapshot'
128 'bisect:Binary search commit history'
129 'worktree:Manage multiple branch checkouts'
130 'workspace:Compose multiple Muse repositories'
131 'rebase:Replay commits onto a new base'
132 'clean:Remove untracked files'
133 'describe:Label a commit by its nearest tag'
134 'shortlog:Summarise history by author or agent'
135 'verify:Check repository integrity'
136 'snapshot:Explicit snapshot management'
137 'bundle:Pack commits into a portable bundle'
138 'content-grep:Search tracked file content'
139 'whoami:Show current identity'
140 'check:Run domain invariant checks'
141 'cat:Print a single tracked symbol'
142 'midi:MIDI domain semantic commands'
143 'code:Code domain semantic commands'
144 'coord:Multi-agent coordination commands'
145 )
146
147 local -a _muse_plumbing_cmds=(
148 'hash-object:SHA-256 a file and optionally store it'
149 'cat-object:Emit raw bytes of a stored object'
150 'rev-parse:Resolve branch or HEAD to a commit ID'
151 'ls-files:List tracked files and object IDs'
152 'read-commit:Emit full commit JSON'
153 'read-snapshot:Emit full snapshot JSON'
154 'commit-tree:Create a commit from an explicit snapshot ID'
155 'update-ref:Move a branch HEAD to a specific commit'
156 'commit-graph:Emit the commit DAG as JSON'
157 'pack-objects:Build a PackBundle to stdout'
158 'unpack-objects:Read a PackBundle from stdin'
159 'ls-remote:List remote branch heads'
160 'merge-base:Find the lowest common ancestor of two commits'
161 'snapshot-diff:Diff two snapshot manifests'
162 'domain-info:Inspect the active domain plugin'
163 'show-ref:List all refs and their commit IDs'
164 'check-ignore:Test paths against .museignore rules'
165 'check-attr:Query merge-strategy attributes for paths'
166 'verify-object:Re-hash stored objects to detect corruption'
167 'symbolic-ref:Read or write HEAD symbolic reference'
168 'for-each-ref:Iterate all refs with rich commit metadata'
169 'name-rev:Map commit IDs to descriptive names'
170 'check-ref-format:Validate branch or ref names'
171 'verify-pack:Verify the integrity of a PackBundle'
172 )
173
174 local -a _muse_midi_cmds=(
175 'notes:List notes in a MIDI track'
176 'note-log:Show note-level history'
177 'note-blame:Show per-note commit provenance'
178 'harmony:Analyse harmonic content'
179 'piano-roll:Render a piano-roll view'
180 'note-hotspots:Find most-changed note regions'
181 'velocity-profile:Plot velocity distribution'
182 'transpose:Shift pitches by semitones'
183 'mix:Merge MIDI tracks'
184 'query:Query note events'
185 'check:Run MIDI domain checks'
186 'rhythm:Analyse rhythmic patterns'
187 'scale:Detect active scales'
188 'contour:Analyse melodic contour'
189 'density:Compute note density over time'
190 'tension:Compute harmonic tension'
191 'cadence:Detect cadence points'
192 'motif:Find recurring motifs'
193 'voice-leading:Analyse voice-leading quality'
194 'instrumentation:List instrumentation per track'
195 'tempo:Display tempo map'
196 'compare:Compare two MIDI commits'
197 'quantize:Snap notes to a rhythmic grid'
198 'humanize:Add expressive timing variation'
199 'invert:Invert intervals around an axis'
200 'retrograde:Reverse note sequence'
201 'arpeggiate:Spread chords into arpeggios'
202 'normalize:Normalise velocity levels'
203 'shard:Split a MIDI file into shards'
204 'agent-map:Show which agents modified which notes'
205 'find-phrase:Search for a melodic phrase'
206 )
207
208 local -a _muse_code_cmds=(
209 'cat:Print a symbol'\''s source'
210 'symbols:List symbols in the snapshot'
211 'symbol-log:Show per-symbol commit history'
212 'detect-refactor:Detect renames and extractions'
213 'grep:Search symbol names and bodies'
214 'blame:Show per-symbol commit provenance'
215 'hotspots:Find most-changed symbols'
216 'stable:Find stable (rarely changed) symbols'
217 'coupling:Show frequently co-changed symbol pairs'
218 'compare:Compare two code commits'
219 'languages:List languages in the snapshot'
220 'patch:Apply a delta patch to symbols'
221 'query:Query the symbol graph'
222 'query-history:Query across commit history'
223 'deps:Show dependency graph'
224 'find-symbol:Search for a symbol by name'
225 'impact:Estimate change impact'
226 'dead:Find unreachable symbols'
227 'coverage:Show test coverage mapping'
228 'lineage:Trace a symbol'\''s ancestry'
229 'api-surface:Enumerate public API symbols'
230 'codemap:Render the module dependency map'
231 'clones:Detect duplicate code'
232 'checkout-symbol:Restore a symbol to a prior version'
233 'semantic-cherry-pick:Apply a symbol-level change'
234 'index:Rebuild the symbol index'
235 'breakage:Detect breaking changes'
236 'invariants:Run symbol invariant checks'
237 'add:Stage files for commit'
238 'reset:Unstage files'
239 'code-check:Run code-domain checks'
240 'code-query:Run a structured code query'
241 )
242
243 local -a _muse_coord_cmds=(
244 'reserve:Reserve a symbol for exclusive editing'
245 'intent:Declare editing intent'
246 'forecast:Forecast merge conflicts'
247 'plan-merge:Plan a coordinated merge'
248 'shard:Partition work across agents'
249 'reconcile:Reconcile diverged agent branches'
250 )
251
252 # ---------------------------------------------------------------------------
253 # Main dispatcher
254 # ---------------------------------------------------------------------------
255
256 _muse() {
257 local curcontext="$curcontext" state line
258 typeset -A opt_args
259
260 _arguments -C \
261 '(-h --help)'{-h,--help}'[Show help]' \
262 '(-V --version)'{-V,--version}'[Show version]' \
263 '1: :->command' \
264 '*:: :->args' \
265 && return 0
266
267 case $state in
268 command)
269 _describe 'muse commands' _muse_top_cmds
270 ;;
271
272 args)
273 case $words[1] in
274
275 # --- sub-namespaces -----------------------------------------
276 plumbing)
277 _arguments '1: :->sub' '*:: :->plumbing_args'
278 case $state in
279 sub) _describe 'plumbing commands' _muse_plumbing_cmds ;;
280 esac
281 ;;
282
283 midi)
284 _arguments '1: :->sub'
285 case $state in
286 sub) _describe 'midi commands' _muse_midi_cmds ;;
287 esac
288 ;;
289
290 code)
291 _arguments '1: :->sub' '*:: :->code_args'
292 case $state in
293 sub) _describe 'code commands' _muse_code_cmds ;;
294 code_args)
295 case $words[1] in
296 add|reset) _files ;;
297 esac
298 ;;
299 esac
300 ;;
301
302 coord)
303 _arguments '1: :->sub'
304 case $state in
305 sub) _describe 'coord commands' _muse_coord_cmds ;;
306 esac
307 ;;
308
309 # --- branch-aware commands -----------------------------------
310 checkout)
311 _arguments \
312 '(-b --branch)'{-b,--branch}'[Create new branch]:branch:_muse_branches' \
313 '(-f --force)'{-f,--force}'[Force checkout]' \
314 '1: :_muse_branches'
315 ;;
316
317 merge)
318 _arguments \
319 '--no-commit[Do not auto-commit after merge]' \
320 '--squash[Squash into a single commit]' \
321 '--strategy[Merge strategy]:strategy:(ours theirs union)' \
322 '1: :_muse_branches'
323 ;;
324
325 branch)
326 _arguments \
327 '(-d --delete)'{-d,--delete}'[Delete a branch]:branch:_muse_branches' \
328 '(-D --force-delete)'{-D,--force-delete}'[Force-delete a branch]:branch:_muse_branches' \
329 '(-m --move)'{-m,--move}'[Rename a branch]:branch:_muse_branches' \
330 '1:: :_muse_branches'
331 ;;
332
333 cherry-pick)
334 _arguments \
335 '(-n --no-commit)'{-n,--no-commit}'[Stage without committing]' \
336 '1: :_muse_branches'
337 ;;
338
339 rebase)
340 _arguments \
341 '--onto[New base branch]:branch:_muse_branches' \
342 '1: :_muse_branches'
343 ;;
344
345 # --- remote-aware commands -----------------------------------
346 push)
347 _arguments \
348 '(-b --branch)'{-b,--branch}'[Branch to push]:branch:_muse_branches' \
349 '(-u --set-upstream)'{-u,--set-upstream}'[Set upstream tracking]' \
350 '--force[Force push even if remote diverged]' \
351 '1:: :_muse_remotes' \
352 '2:: :_muse_branches'
353 ;;
354
355 pull)
356 _arguments \
357 '(-b --branch)'{-b,--branch}'[Branch to pull into]:branch:_muse_branches' \
358 '--rebase[Rebase instead of merge]' \
359 '--no-commit[Do not auto-commit after merge]' \
360 '1:: :_muse_remotes' \
361 '2:: :_muse_branches'
362 ;;
363
364 fetch)
365 _arguments \
366 '(-b --branch)'{-b,--branch}'[Specific branch to fetch]:branch:_muse_branches' \
367 '--all[Fetch from all configured remotes]' \
368 '1:: :_muse_remotes'
369 ;;
370
371 clone)
372 _arguments \
373 '(-b --branch)'{-b,--branch}'[Branch to check out after clone]:branch' \
374 '--name[Override repo directory name]:name' \
375 '1: :_urls'
376 ;;
377
378 # --- simple flag commands ------------------------------------
379 commit)
380 _arguments \
381 '(-m --message)'{-m,--message}'[Commit message]:message' \
382 '--allow-empty[Allow a commit with no changes]' \
383 '--sign[Sign the commit]' \
384 '(-f --format)'{-f,--format}'[Output format]:fmt:(text json)'
385 ;;
386
387 status)
388 _arguments \
389 '(-s --short)'{-s,--short}'[Condensed output]' \
390 '--porcelain[Machine-readable output]' \
391 '(-b --branch)'{-b,--branch}'[Show branch only]' \
392 '(-f --format)'{-f,--format}'[Output format]:fmt:(text json)'
393 ;;
394
395 log)
396 _arguments \
397 '(-n --max-count)'{-n,--max-count}'[Limit number of commits]:count' \
398 '--oneline[Compact one-line output]' \
399 '--graph[Show commit graph]' \
400 '(-f --format)'{-f,--format}'[Output format]:fmt:(text json)'
401 ;;
402
403 diff)
404 _arguments \
405 '--stat[Show diffstat summary]' \
406 '(-f --format)'{-f,--format}'[Output format]:fmt:(text json)' \
407 '1:: :_muse_branches' \
408 '2:: :_muse_branches'
409 ;;
410
411 show)
412 _arguments \
413 '(-f --format)'{-f,--format}'[Output format]:fmt:(text json)' \
414 '1:: :_muse_branches'
415 ;;
416
417 reset)
418 _arguments \
419 '--hard[Discard working tree changes]' \
420 '--soft[Keep working tree changes staged]' \
421 '1:: :_muse_branches'
422 ;;
423
424 tag)
425 _arguments \
426 '(-d --delete)'{-d,--delete}'[Delete a tag]' \
427 '(-l --list)'{-l,--list}'[List tags]' \
428 '(-f --format)'{-f,--format}'[Output format]:fmt:(text json)'
429 ;;
430
431 release)
432 _arguments -C \
433 '1: :->sub' \
434 '*:: :->release_args'
435 case $state in
436 sub)
437 local -a _muse_release_subcmds=(
438 'add:Create a new release from a tag'
439 'list:List all releases'
440 'show:Display a release'
441 'push:Push a release to a remote'
442 'delete:Delete a draft release'
443 )
444 _describe 'release subcommands' _muse_release_subcmds
445 ;;
446 release_args)
447 case $words[1] in
448 add)
449 _arguments \
450 '--title[Release title]:title' \
451 '--body[Release notes]:body' \
452 '--channel[Release channel]:channel:(stable beta alpha nightly)' \
453 '--draft[Mark as draft]' \
454 '--remote[Push to remote after creating]:remote:_muse_remotes' \
455 '1: :_muse_release_tags'
456 ;;
457 show)
458 _arguments \
459 '(-f --format)'{-f,--format}'[Output format]:fmt:(text json)' \
460 '1: :_muse_release_tags'
461 ;;
462 push)
463 _arguments \
464 '--remote[Remote to push to]:remote:_muse_remotes' \
465 '1: :_muse_release_tags' \
466 '2:: :_muse_remotes'
467 ;;
468 delete)
469 _arguments \
470 '--remote[Also retract from this remote]:remote:_muse_remotes' \
471 '(-y --yes)'{-y,--yes}'[Skip confirmation]' \
472 '1: :_muse_release_tags'
473 ;;
474 list)
475 _arguments \
476 '--channel[Filter by channel]:channel:(stable beta alpha nightly)' \
477 '--remote[List releases on a remote]:remote:_muse_remotes' \
478 '(-f --format)'{-f,--format}'[Output format]:fmt:(text json)'
479 ;;
480 esac
481 ;;
482 esac
483 ;;
484
485 stash)
486 _arguments \
487 '1:: :(push pop list drop show apply)'
488 ;;
489
490 remote)
491 _arguments \
492 '1:: :(add remove list rename set-url show)'
493 ;;
494
495 auth)
496 _arguments \
497 '1:: :(login logout whoami)'
498 ;;
499
500 hub)
501 _arguments \
502 '1:: :(connect disconnect status ping)'
503 ;;
504
505 config)
506 _arguments \
507 '1:: :(show get set edit)'
508 ;;
509 esac
510 ;;
511 esac
512 }
513
514 _muse "$@"