gabriel / muse public
porcelain.md markdown
1414 lines 35.1 KB
51e65168 docs: comprehensive plumbing and porcelain reference update Gabriel Cardona <gabriel@tellurstori.com> 2d ago
1 # Muse Porcelain Commands
2
3 > **Layer guide:** Muse commands are organised into three tiers.
4 > This document covers **Tier 2 — Core Porcelain**: the high-level,
5 > human-friendly commands that build on the Tier 1 plumbing layer.
6 > Tier 3 commands (MIDI, Bitcoin, Code) live in their own reference docs.
7
8 All porcelain commands accept `--format json` where documented below. JSON is
9 printed to `stdout`; human text goes to `stdout` too; error messages always go
10 to `stderr`. Exit codes follow the same convention as the plumbing layer:
11 `0` success · `1` user error · `3` internal error.
12
13 ---
14
15 ## Quick Index
16
17 | Command | Description |
18 |---------|-------------|
19 | [`init`](#init) | Initialise a new Muse repository |
20 | [`commit`](#commit) | Record the working tree as a new version |
21 | [`status`](#status) | Show working-tree drift against HEAD |
22 | [`log`](#log) | Display commit history |
23 | [`diff`](#diff) | Compare working tree or two commits |
24 | [`show`](#show) | Inspect a commit — metadata, diff, files |
25 | [`branch`](#branch) | List, create, or delete branches |
26 | [`checkout`](#checkout) | Switch branches or restore a snapshot |
27 | [`merge`](#merge) | Three-way merge a branch into the current branch |
28 | [`rebase`](#rebase) | Replay commits onto a new base |
29 | [`reset`](#reset) | Move HEAD to a prior commit |
30 | [`revert`](#revert) | Undo a commit by creating a new one |
31 | [`cherry-pick`](#cherry-pick) | Apply a single commit's changes |
32 | [`stash`](#stash) | Shelve and restore uncommitted changes |
33 | [`tag`](#tag) | Attach and query semantic tags on commits |
34 | [`blame`](#blame) | Line-level attribution for any text file |
35 | [`reflog`](#reflog) | History of HEAD and branch-ref movements |
36 | [`rerere`](#rerere) | Reuse recorded conflict resolutions |
37 | [`gc`](#gc) | Garbage-collect unreachable objects |
38 | [`archive`](#archive) | Export a snapshot as tar.gz or zip |
39 | [`bisect`](#bisect) | Binary-search through history for a regression |
40 | [`worktree`](#worktree) | Multiple simultaneous branch checkouts |
41 | [`clean`](#clean) | Remove untracked files from the working tree |
42 | [`describe`](#describe) | Label a commit by its nearest tag |
43 | [`shortlog`](#shortlog) | Commit summary grouped by author or agent |
44 | [`verify`](#verify) | Whole-repository integrity check |
45 | [`snapshot`](#snapshot) | Explicit snapshot management |
46 | [`bundle`](#bundle) | Pack and unpack commits for offline transfer |
47 | [`content-grep`](#content-grep) | Full-text search across tracked file content |
48 | [`whoami`](#whoami) | Show the current identity |
49 | [`config`](#config) | Read and write repository configuration |
50
51 ---
52
53 ## Established Core Porcelain
54
55 ### `init` — initialise a repository
56
57 Create a fresh `.muse/` directory in the current folder.
58
59 ```
60 muse init # initialise in current dir
61 muse init --domain midi # set the active domain
62 muse init -d code # short flag
63 ```
64
65 **Flags**
66
67 | Flag | Short | Default | Description |
68 |------|-------|---------|-------------|
69 | `--domain` | `-d` | `midi` | Domain plugin to activate for this repo |
70
71 **Exit codes:** `0` success · `1` already initialised
72
73 ---
74
75 ### `commit` — record the working tree
76
77 Snapshot the working tree and write a commit pointing to it.
78
79 ```
80 muse commit -m "verse melody"
81 muse commit --message "Add chorus" --author "gabriel"
82 muse commit --allow-empty
83 muse commit --format json
84 ```
85
86 **Flags**
87
88 | Flag | Short | Default | Description |
89 |------|-------|---------|-------------|
90 | `--message` | `-m` | `""` | Commit message |
91 | `--author` | `-a` | config value | Override the author name |
92 | `--allow-empty` | `-e` | off | Commit even when nothing changed |
93 | `--format` | `-f` | `text` | `text` or `json` |
94
95 **JSON output**
96
97 ```json
98 {
99 "commit_id": "a3f2...c8d1",
100 "branch": "main",
101 "message": "Add verse melody",
102 "author": "gabriel",
103 "committed_at": "2026-03-21T12:00:00+00:00",
104 "snapshot_id": "b7e4...f912",
105 "sem_ver_bump": "minor"
106 }
107 ```
108
109 `sem_ver_bump` is `"none"`, `"patch"`, `"minor"`, or `"major"` depending on the
110 domain plugin's assessment of the change.
111
112 **Exit codes:** `0` committed · `1` nothing to commit (and `--allow-empty` not given)
113
114 ---
115
116 ### `status` — show drift against HEAD
117
118 ```
119 muse status
120 muse status --short
121 muse status --json # machine-readable
122 muse status -s -j
123 ```
124
125 **Flags**
126
127 | Flag | Short | Default | Description |
128 |------|-------|---------|-------------|
129 | `--short` | `-s` | off | Compact one-line-per-file output |
130 | `--json` | `-j` | off | JSON output (equivalent to `--format json`) |
131
132 **Text output:**
133
134 ```
135 On branch main
136
137 Modified:
138 tracks/bass.mid
139 Added:
140 tracks/lead.mid
141 ```
142
143 `clean` when working tree matches HEAD.
144
145 **JSON output**
146
147 ```json
148 {
149 "branch": "main",
150 "clean": false,
151 "modified": ["tracks/bass.mid"],
152 "added": ["tracks/lead.mid"],
153 "deleted": []
154 }
155 ```
156
157 **Exit codes:** `0` always (non-zero drift is shown, not signalled)
158
159 ---
160
161 ### `log` — display commit history
162
163 ```
164 muse log
165 muse log --limit 20
166 muse log --branch feat/audio
167 muse log --format json
168 ```
169
170 **Flags**
171
172 | Flag | Short | Default | Description |
173 |------|-------|---------|-------------|
174 | `--branch` | `-b` | current | Branch to walk |
175 | `--limit` | `-n` | 50 | Max commits to emit |
176 | `--format` | `-f` | `text` | `text` or `json` |
177
178 **JSON output** — an array of commit records, newest first:
179
180 ```json
181 [
182 {
183 "commit_id": "a3f2...c8d1",
184 "branch": "main",
185 "message": "Add verse melody",
186 "author": "gabriel",
187 "committed_at": "2026-03-21T12:00:00+00:00",
188 "snapshot_id": "b7e4...f912",
189 "parent_commit_id": "ff01...23ab",
190 "sem_ver_bump": "minor"
191 }
192 ]
193 ```
194
195 **Exit codes:** `0` always
196
197 ---
198
199 ### `diff` — compare working tree or two commits
200
201 ```
202 muse diff # working tree vs HEAD
203 muse diff --from HEAD~3
204 muse diff --from v1.0 --to v2.0
205 muse diff --format json
206 ```
207
208 **Flags**
209
210 | Flag | Short | Default | Description |
211 |------|-------|---------|-------------|
212 | `--from` | `-f` | HEAD | Ref or commit to diff from |
213 | `--to` | `-t` | working tree | Ref or commit to diff to |
214 | `--format` | — | `text` | `text` or `json` |
215
216 **JSON output**
217
218 ```json
219 {
220 "from": "ff01...23ab",
221 "to": "a3f2...c8d1",
222 "added": ["tracks/lead.mid"],
223 "removed": ["tracks/old.mid"],
224 "modified": ["tracks/bass.mid"],
225 "total_changes": 3
226 }
227 ```
228
229 > **Note:** The JSON field is `"total_changes"` (not `"ops"` or `"changes"`).
230
231 **Exit codes:** `0` always
232
233 ---
234
235 ### `show` — inspect a commit
236
237 ```
238 muse show HEAD
239 muse show abc123
240 muse show --format json
241 muse show --stat HEAD # files changed, not full diff
242 ```
243
244 **Flags**
245
246 | Flag | Short | Default | Description |
247 |------|-------|---------|-------------|
248 | `--ref` | `-r` | HEAD | Commit or branch to inspect |
249 | `--stat` | `-s` | off | Show file-level summary instead of raw diff |
250 | `--format` | `-f` | `text` | `text` or `json` |
251
252 **JSON output — full diff mode**
253
254 ```json
255 {
256 "commit_id": "a3f2...c8d1",
257 "branch": "main",
258 "message": "Add verse melody",
259 "author": "gabriel",
260 "committed_at": "2026-03-21T12:00:00+00:00",
261 "snapshot_id": "b7e4...f912",
262 "parent_commit_id": "ff01...23ab",
263 "delta": {
264 "added": ["tracks/lead.mid"],
265 "removed": [],
266 "modified": ["tracks/bass.mid"]
267 }
268 }
269 ```
270
271 **JSON output — `--stat` mode**
272
273 ```json
274 {
275 "commit_id": "a3f2...c8d1",
276 "branch": "main",
277 "message": "Add verse melody",
278 "author": "gabriel",
279 "committed_at": "2026-03-21T12:00:00+00:00",
280 "snapshot_id": "b7e4...f912",
281 "parent_commit_id": "ff01...23ab",
282 "files_added": 1,
283 "files_removed": 0,
284 "files_modified": 1
285 }
286 ```
287
288 **Exit codes:** `0` found · `1` commit not found
289
290 ---
291
292 ### `branch` — list, create, or delete branches
293
294 ```
295 muse branch # list all
296 muse branch feat/reverb # create
297 muse branch --delete feat/reverb
298 muse branch -d feat/reverb # short flag
299 muse branch --format json # structured list
300 ```
301
302 **Flags**
303
304 | Flag | Short | Default | Description |
305 |------|-------|---------|-------------|
306 | `--delete` | `-d` | off | Delete a branch |
307 | `--format` | `-f` | `text` | `text` or `json` |
308
309 **JSON output — list**
310
311 ```json
312 {
313 "current": "main",
314 "branches": [
315 {"name": "dev", "commit_id": "ff01...23ab", "is_current": false},
316 {"name": "main", "commit_id": "a3f2...c8d1", "is_current": true}
317 ]
318 }
319 ```
320
321 **JSON output — create / delete**
322
323 ```json
324 {"action": "created", "branch": "feat/reverb"}
325 {"action": "deleted", "branch": "feat/reverb"}
326 ```
327
328 **Exit codes:** `0` success · `1` branch already exists (create) or not found (delete)
329
330 ---
331
332 ### `checkout` — switch branches or restore snapshot
333
334 ```
335 muse checkout main
336 muse checkout feat/guitar
337 muse checkout --create feat/new-idea # create and switch
338 muse checkout -c feat/new-idea # short flag
339 muse checkout --format json # machine-readable result
340 ```
341
342 **Flags**
343
344 | Flag | Short | Default | Description |
345 |------|-------|---------|-------------|
346 | `--create` | `-c` | off | Create branch then switch |
347 | `--format` | `-f` | `text` | `text` or `json` |
348
349 **JSON output**
350
351 ```json
352 {
353 "action": "switched",
354 "branch": "feat/guitar",
355 "commit_id": "a3f2...c8d1"
356 }
357 ```
358
359 `"action"` is one of `"switched"`, `"created"`, or `"already_on"` (when you
360 check out the branch that is already active).
361
362 **Exit codes:** `0` success · `1` branch not found (and `--create` not given)
363
364 ---
365
366 ### `merge` — three-way merge
367
368 ```
369 muse merge feat/audio
370 muse merge --message "Merge audio feature"
371 muse merge --abort
372 muse merge --continue
373 muse merge --format json
374 ```
375
376 **Flags**
377
378 | Flag | Short | Default | Description |
379 |------|-------|---------|-------------|
380 | `--message` | `-m` | auto | Override merge commit message |
381 | `--abort` | `-a` | off | Abort an in-progress merge |
382 | `--continue` | `-c` | off | Resume after resolving conflicts |
383 | `--format` | `-f` | `text` | `text` or `json` |
384
385 **JSON output — clean merge**
386
387 ```json
388 {
389 "action": "merged",
390 "branch": "feat/audio",
391 "commit_id": "a3f2...c8d1",
392 "message": "Merge feat/audio into main"
393 }
394 ```
395
396 **JSON output — conflict**
397
398 ```json
399 {
400 "action": "conflict",
401 "branch": "feat/audio",
402 "conflicts": ["tracks/bass.mid"]
403 }
404 ```
405
406 **Conflict flow:**
407 1. `muse merge <branch>` → conflict reported, writes `MERGE_STATE.json`
408 2. Resolve files manually
409 3. `muse merge --continue` → commit the merge
410 4. Or `muse merge --abort` → restore original HEAD
411
412 **Exit codes:** `0` merged · `1` conflict or bad arguments
413
414 ---
415
416 ### `rebase` — replay commits onto a new base
417
418 Muse rebase replays a sequence of commits onto a new base using the same
419 three-way merge engine as `muse merge`. Because commits are content-addressed,
420 each replayed commit gets a **new ID** — the originals are untouched in the store.
421
422 ```
423 muse rebase main # replay current branch onto main
424 muse rebase --onto newbase upstream # replay onto a specific base
425 muse rebase --squash main # collapse all commits into one
426 muse rebase --squash -m "feat: all in" # squash with custom message
427 muse rebase --abort # restore original HEAD
428 muse rebase --continue # resume after conflict resolution
429 muse rebase --format json
430 ```
431
432 **Flags**
433
434 | Flag | Short | Description |
435 |------|-------|-------------|
436 | `--onto <ref>` | `-o` | New base commit |
437 | `--squash` | `-s` | Collapse all commits into one |
438 | `--message <msg>` | `-m` | Message for squash commit |
439 | `--abort` | `-a` | Abort and restore original HEAD |
440 | `--continue` | `-c` | Resume after resolving a conflict |
441 | `--format <fmt>` | `-f` | `text` or `json` |
442
443 **JSON output — squash rebase**
444
445 ```json
446 {
447 "action": "squash_rebase",
448 "onto": "main",
449 "new_commit_id": "a3f2...c8d1",
450 "commits_squashed": 4,
451 "branch": "feat/audio"
452 }
453 ```
454
455 **JSON output — normal rebase**
456
457 ```json
458 {
459 "action": "rebase",
460 "onto": "main",
461 "branch": "feat/audio",
462 "commits_replayed": 4,
463 "new_tip": "a3f2...c8d1"
464 }
465 ```
466
467 **Conflict flow:**
468 1. `muse rebase main` → conflict reported, writes `REBASE_STATE.json` and `MERGE_STATE.json`
469 2. Resolve files manually
470 3. `muse rebase --continue` → commit the resolved state and continue
471 4. Or `muse rebase --abort` → restore the original branch pointer
472
473 **State file:** `.muse/REBASE_STATE.json` — tracks remaining/completed commits
474 and the `onto` base. Cleared automatically on successful completion or `--abort`.
475
476 **Exit codes:** `0` clean · `1` conflict or bad arguments
477
478 ---
479
480 ### `reset` — move HEAD to a prior commit
481
482 ```
483 muse reset HEAD~1 # move back one commit
484 muse reset abc123 # move to specific commit
485 muse reset --hard # also reset working tree
486 muse reset --format json
487 ```
488
489 **Flags**
490
491 | Flag | Short | Default | Description |
492 |------|-------|---------|-------------|
493 | `--hard` | `-H` | off | Also update the working tree to match |
494 | `--format` | `-f` | `text` | `text` or `json` |
495
496 **JSON output**
497
498 ```json
499 {
500 "action": "reset",
501 "branch": "main",
502 "previous_commit": "a3f2...c8d1",
503 "new_commit": "ff01...23ab",
504 "hard": false
505 }
506 ```
507
508 **Exit codes:** `0` success · `1` commit not found
509
510 ---
511
512 ### `revert` — undo a commit by creating a new one
513
514 Non-destructive: the original commit remains in history. A new commit is
515 created whose effect is the inverse of the target commit.
516
517 ```
518 muse revert HEAD
519 muse revert abc123
520 muse revert --message "Undo broken change"
521 muse revert --format json
522 ```
523
524 **Flags**
525
526 | Flag | Short | Default | Description |
527 |------|-------|---------|-------------|
528 | `--message` | `-m` | auto | Override revert commit message |
529 | `--format` | `-f` | `text` | `text` or `json` |
530
531 **JSON output**
532
533 ```json
534 {
535 "action": "reverted",
536 "reverted_commit": "a3f2...c8d1",
537 "new_commit_id": "b7e4...f912",
538 "branch": "main",
539 "message": "Revert \"Add verse melody\""
540 }
541 ```
542
543 **Exit codes:** `0` success · `1` commit not found or nothing to revert
544
545 ---
546
547 ### `cherry-pick` — apply a single commit's changes
548
549 ```
550 muse cherry-pick abc123
551 muse cherry-pick abc123 --message "Cherry: verse fix"
552 muse cherry-pick abc123 --format json
553 ```
554
555 **Flags**
556
557 | Flag | Short | Default | Description |
558 |------|-------|---------|-------------|
559 | `--message` | `-m` | auto | Override the cherry-picked commit message |
560 | `--format` | `-f` | `text` | `text` or `json` |
561
562 **JSON output**
563
564 ```json
565 {
566 "action": "cherry_picked",
567 "source_commit": "abc1...2345",
568 "new_commit_id": "a3f2...c8d1",
569 "branch": "main",
570 "message": "Cherry: verse fix"
571 }
572 ```
573
574 **Exit codes:** `0` success · `1` commit not found or conflict
575
576 ---
577
578 ### `stash` — shelve and restore changes
579
580 ```
581 muse stash push -m "WIP: bridge section"
582 muse stash list
583 muse stash list --format json
584 muse stash pop
585 muse stash drop 0
586 ```
587
588 **Subcommands**
589
590 | Subcommand | Description |
591 |------------|-------------|
592 | `push` | Stash current working-tree changes |
593 | `list` | List saved stashes |
594 | `pop` | Restore the most recent stash and drop it |
595 | `apply <n>` | Restore stash N without dropping it |
596 | `drop <n>` | Delete stash N |
597 | `show <n>` | Show what a stash contains |
598
599 **Flags — `push`**
600
601 | Flag | Short | Description |
602 |------|-------|-------------|
603 | `--message` | `-m` | Label for the stash entry |
604
605 **Flags — `list` / `show`**
606
607 | Flag | Short | Description |
608 |------|-------|-------------|
609 | `--format` | `-f` | `text` or `json` |
610
611 **JSON output — `list`**
612
613 ```json
614 [
615 {
616 "index": 0,
617 "message": "WIP: bridge section",
618 "created_at": "2026-03-21T12:00:00+00:00",
619 "branch": "feat/audio"
620 }
621 ]
622 ```
623
624 **Exit codes:** `0` success · `1` stash index out of range or nothing to stash
625
626 ---
627
628 ### `tag` — semantic tags on commits
629
630 ```
631 muse tag v1.0.0
632 muse tag v1.0.0 --commit abc123
633 muse tag list
634 muse tag list --format json
635 muse tag delete v0.9.0
636 muse tag show v1.0.0 --format json
637 ```
638
639 **Subcommands**
640
641 | Subcommand | Description |
642 |------------|-------------|
643 | `<name>` | Create a tag on HEAD (or `--commit`) |
644 | `list` | List all tags |
645 | `show <name>` | Inspect a tag |
646 | `delete <name>` | Delete a tag |
647
648 **Flags — create**
649
650 | Flag | Short | Description |
651 |------|-------|-------------|
652 | `--commit` | `-c` | Attach tag to a specific commit ID |
653 | `--message` | `-m` | Optional annotation |
654 | `--format` | `-f` | `text` or `json` |
655
656 **JSON output — create**
657
658 ```json
659 {"action": "created", "name": "v1.0.0", "commit_id": "a3f2...c8d1"}
660 ```
661
662 **JSON output — `list`**
663
664 ```json
665 [
666 {"name": "v1.0.0", "commit_id": "a3f2...c8d1", "created_at": "2026-03-21T12:00:00+00:00"}
667 ]
668 ```
669
670 **Exit codes:** `0` success · `1` tag or commit not found
671
672 ---
673
674 ### `blame` — line-level attribution
675
676 ```
677 muse blame song.mid
678 muse blame --format json song.mid
679 ```
680
681 **Flags**
682
683 | Flag | Short | Default | Description |
684 |------|-------|---------|-------------|
685 | `--format` | `-f` | `text` | `text` or `json` |
686 | `--ref` | `-r` | HEAD | Branch or commit to blame against |
687
688 **JSON output**
689
690 ```json
691 [
692 {
693 "line": 1,
694 "content": "tempo: 120",
695 "commit_id": "a3f2...c8d1",
696 "author": "gabriel",
697 "committed_at": "2026-03-21T12:00:00+00:00",
698 "message": "Add verse melody"
699 }
700 ]
701 ```
702
703 **Exit codes:** `0` success · `1` file or ref not found
704
705 ---
706
707 ### `reflog` — HEAD and branch movement history
708
709 ```
710 muse reflog
711 muse reflog --branch feat/audio
712 muse reflog --limit 50
713 muse reflog --format json
714 ```
715
716 **Flags**
717
718 | Flag | Short | Default | Description |
719 |------|-------|---------|-------------|
720 | `--branch` | `-b` | HEAD | Branch to show reflog for |
721 | `--limit` | `-n` | 50 | Max entries to show |
722 | `--format` | `-f` | `text` | `text` or `json` |
723
724 The reflog is the "undo safety net" — every ref movement is recorded so
725 you can recover from accidental resets, force-pushes, or botched rebases.
726
727 **JSON output**
728
729 ```json
730 [
731 {
732 "index": 0,
733 "commit_id": "a3f2...c8d1",
734 "action": "commit",
735 "message": "Add verse melody",
736 "author": "gabriel",
737 "moved_at": "2026-03-21T12:00:00+00:00"
738 }
739 ]
740 ```
741
742 `"index"` is 0-based, with 0 being the most recent entry. `"action"` describes
743 what caused the ref movement: `"commit"`, `"merge"`, `"rebase"`, `"reset"`, `"checkout"`, etc.
744
745 **Exit codes:** `0` always
746
747 ---
748
749 ### `rerere` — reuse recorded resolutions
750
751 ```
752 muse rerere list # show cached resolutions
753 muse rerere apply # auto-apply cached fixes to current conflicts
754 muse rerere forget abc123 # remove a cached resolution
755 muse rerere status # show which conflicts have cached resolutions
756 ```
757
758 **How it works:** After a successful merge, Muse records the resolution in
759 `.muse/rerere/`. On future conflicts with the same "conflict fingerprint",
760 `rerere apply` replays the resolution automatically.
761
762 **Exit codes:** `0` resolution applied or listed · `1` no matching resolution
763
764 ---
765
766 ### `gc` — garbage collect
767
768 Removes objects that are not reachable from any branch or tag ref. Orphaned
769 commits (e.g. after a reset), dangling snapshots, and unreferenced blobs are
770 all eligible.
771
772 ```
773 muse gc # remove unreachable objects
774 muse gc --dry-run # preview what would be removed
775 muse gc -n # short flag for --dry-run
776 muse gc --format json
777 ```
778
779 **Flags**
780
781 | Flag | Short | Default | Description |
782 |------|-------|---------|-------------|
783 | `--dry-run` | `-n` | off | Preview without deleting |
784 | `--format` | `-f` | `text` | `text` or `json` |
785
786 **JSON output**
787
788 ```json
789 {
790 "commits_removed": 2,
791 "snapshots_removed": 2,
792 "objects_removed": 11,
793 "bytes_freed": 204800,
794 "dry_run": false
795 }
796 ```
797
798 **Exit codes:** `0` always
799
800 ---
801
802 ### `archive` — export a snapshot
803
804 ```
805 muse archive HEAD
806 muse archive HEAD --format zip --output release.zip
807 muse archive v1.0.0 --prefix project/
808 ```
809
810 **Flags**
811
812 | Flag | Short | Default | Description |
813 |------|-------|---------|-------------|
814 | `--format` | `-f` | `tar.gz` | Archive format: `tar.gz` or `zip` |
815 | `--output` | `-o` | `<commit>.tar.gz` | Output file path |
816 | `--prefix` | `-p` | `""` | Directory prefix inside the archive |
817
818 All archive entries use the `prefix/` directory. Tar-slip / zip-slip are
819 prevented: entry paths are validated to stay within the prefix.
820
821 **Exit codes:** `0` archive written · `1` ref not found · `3` I/O error
822
823 ---
824
825 ### `bisect` — binary-search for a regression
826
827 Muse bisect works on any domain — not just code. Use it to find which commit
828 introduced a melody change, a tuning drift, or a data regression.
829
830 ```
831 muse bisect start
832 muse bisect bad HEAD # mark HEAD as bad (broken)
833 muse bisect good v1.0.0 # mark v1.0.0 as good (working)
834 muse bisect bad # mark the currently-tested commit as bad
835 muse bisect good # mark the currently-tested commit as good
836 muse bisect skip # skip an untestable commit
837 muse bisect log # show the current session log
838 muse bisect reset # end the session and restore HEAD
839 muse bisect run pytest # automated bisect: run command, 0=good, 1=bad
840 ```
841
842 **Subcommands**
843
844 | Subcommand | Description |
845 |------------|-------------|
846 | `start` | Begin a new bisect session |
847 | `bad [<ref>]` | Mark a commit as bad; omit to mark current |
848 | `good [<ref>]` | Mark a commit as good; omit to mark current |
849 | `skip [<ref>]` | Skip a commit that cannot be tested |
850 | `log` | Print the current session state |
851 | `reset` | End the session; restore the original branch |
852 | `run <cmd>` | Automate: run command, exit 0 = good, exit 1 = bad |
853
854 Bisect narrows the search range using binary search. On each step, Muse
855 checks out the midpoint commit and waits for a verdict (`good`/`bad`/`skip`).
856 The search converges in O(log N) steps regardless of domain.
857
858 **Exit codes:** `0` session active or found · `1` bad arguments · `3` I/O error
859
860 ---
861
862 ### `worktree` — multiple simultaneous checkouts
863
864 ```
865 muse worktree add /path/to/dir feat/audio
866 muse worktree list
867 muse worktree list --format json
868 muse worktree remove feat/audio
869 muse worktree prune
870 ```
871
872 **Subcommands**
873
874 | Subcommand | Description |
875 |------------|-------------|
876 | `add <path> <branch>` | Check out `branch` into `path` |
877 | `list` | List registered worktrees |
878 | `remove <branch>` | Remove a linked worktree |
879 | `prune` | Remove entries for deleted directories |
880
881 **Flags — `list`**
882
883 | Flag | Short | Description |
884 |------|-------|-------------|
885 | `--format` | `-f` | `text` or `json` |
886
887 **JSON output — `list`**
888
889 ```json
890 [
891 {"path": "/home/g/muse-main", "branch": "main", "is_main": true},
892 {"path": "/home/g/muse-audio", "branch": "feat/audio", "is_main": false}
893 ]
894 ```
895
896 **Exit codes:** `0` success · `1` path or branch conflict
897
898 ---
899
900 ### `clean` — remove untracked files
901
902 Scans the working tree against the HEAD snapshot and removes files not tracked
903 in any commit. `--force` is required to actually delete files (safety guard).
904
905 ```
906 muse clean -n # dry-run: show what would be removed
907 muse clean -f # delete untracked files
908 muse clean -f -d # also delete empty directories
909 muse clean -f -x # also delete .museignore-excluded files
910 muse clean -f -d -x # everything untracked + ignored + empty dirs
911 ```
912
913 **Flags**
914
915 | Flag | Short | Description |
916 |------|-------|-------------|
917 | `--dry-run` | `-n` | Preview without deleting |
918 | `--force` | `-f` | Required to actually delete |
919 | `--directories` | `-d` | Remove empty untracked directories |
920 | `--include-ignored` | `-x` | Also remove .museignore-excluded files |
921
922 **Exit codes:** `0` clean or cleaned · `1` untracked exist but `--force` not given
923
924 ---
925
926 ### `describe` — label by nearest tag
927
928 Walks backward from a commit and finds the nearest tag. Returns `<tag>~N`
929 where N is the hop count. N=0 gives the bare tag name.
930
931 ```
932 muse describe # → v1.0.0~3
933 muse describe --ref feat/audio # describe the tip of a branch
934 muse describe --long # → v1.0.0-3-gabc123456789
935 muse describe --require-tag # exit 1 if no tags exist
936 muse describe --format json # machine-readable
937 ```
938
939 **Flags**
940
941 | Flag | Short | Description |
942 |------|-------|-------------|
943 | `--ref <ref>` | `-r` | Branch or commit to describe |
944 | `--long` | `-l` | Always show `<tag>-<dist>-g<sha>` |
945 | `--require-tag` | `-t` | Fail if no tag found |
946 | `--format <fmt>` | `-f` | `text` or `json` |
947
948 **JSON output schema:**
949
950 ```json
951 {
952 "commit_id": "string (full SHA-256)",
953 "tag": "string | null",
954 "distance": 0,
955 "short_sha": "string (12 chars)",
956 "name": "string (e.g. v1.0.0~3)"
957 }
958 ```
959
960 **Exit codes:** `0` description produced · `1` ref not found or `--require-tag` with no tags
961
962 ---
963
964 ### `shortlog` — commit summary by author or agent
965
966 Groups commits by `author` or `agent_id` and prints a count + message list.
967 Especially expressive in Muse because both human and agent contributions are
968 tracked with full metadata.
969
970 ```
971 muse shortlog # current branch
972 muse shortlog --all # all branches
973 muse shortlog --numbered # sort by commit count (most active first)
974 muse shortlog --email # include agent_id alongside author name
975 muse shortlog --limit 100 # cap commit walk at 100
976 muse shortlog --format json # JSON for agent consumption
977 ```
978
979 **Flags**
980
981 | Flag | Short | Description |
982 |------|-------|-------------|
983 | `--branch <br>` | `-b` | Branch to summarise |
984 | `--all` | `-a` | Summarise all branches |
985 | `--numbered` | `-n` | Sort by commit count |
986 | `--email` | `-e` | Include agent_id |
987 | `--limit <N>` | `-l` | Max commits to walk |
988 | `--format <fmt>` | `-f` | `text` or `json` |
989
990 **JSON output schema:**
991
992 ```json
993 [
994 {
995 "author": "string",
996 "count": 12,
997 "commits": [
998 {"commit_id": "...", "message": "...", "committed_at": "..."}
999 ]
1000 }
1001 ]
1002 ```
1003
1004 **Exit codes:** `0` always
1005
1006 ---
1007
1008 ### `verify` — whole-repository integrity check
1009
1010 Walks every reachable commit from every branch ref and performs a three-tier check:
1011
1012 1. Every branch ref points to an existing commit.
1013 2. Every commit's snapshot exists.
1014 3. Every object referenced by every snapshot exists, and (unless `--no-objects`)
1015 its SHA-256 is recomputed to detect silent data corruption.
1016
1017 This is Muse's equivalent of `git fsck`.
1018
1019 ```
1020 muse verify # full integrity check (re-hashes all objects)
1021 muse verify --no-objects # existence-only check (faster)
1022 muse verify --quiet # exit code only — no output
1023 muse verify -q && echo "healthy"
1024 muse verify --format json | jq '.failures'
1025 ```
1026
1027 **Flags**
1028
1029 | Flag | Short | Description |
1030 |------|-------|-------------|
1031 | `--quiet` | `-q` | No output; exit 0 = clean, 1 = failure |
1032 | `--no-objects` | `-O` | Skip SHA-256 re-hashing |
1033 | `--format <fmt>` | `-f` | `text` or `json` |
1034
1035 **JSON output schema:**
1036
1037 ```json
1038 {
1039 "refs_checked": 3,
1040 "commits_checked": 42,
1041 "snapshots_checked": 42,
1042 "objects_checked": 210,
1043 "all_ok": true,
1044 "failures": [
1045 {
1046 "kind": "object",
1047 "id": "abc123...",
1048 "error": "hash mismatch — data corruption detected"
1049 }
1050 ]
1051 }
1052 ```
1053
1054 **Failure kinds:** `ref` · `commit` · `snapshot` · `object`
1055
1056 **Exit codes:** `0` all checks passed · `1` one or more failures
1057
1058 ---
1059
1060 ### `snapshot` — explicit snapshot management
1061
1062 A snapshot is Muse's fundamental unit of state: an immutable, content-addressed
1063 record mapping workspace paths to their SHA-256 object IDs.
1064
1065 `muse snapshot` exposes snapshots as a first-class operation — capture, list,
1066 show, and export them independently of commits. Useful for mid-work checkpoints
1067 in agent pipelines.
1068
1069 #### `snapshot create`
1070
1071 ```
1072 muse snapshot create
1073 muse snapshot create -m "WIP: before refactor"
1074 muse snapshot create --format json # prints snapshot_id
1075 ```
1076
1077 **JSON output:**
1078
1079 ```json
1080 {
1081 "snapshot_id": "string",
1082 "file_count": 42,
1083 "note": "string",
1084 "created_at": "ISO8601"
1085 }
1086 ```
1087
1088 #### `snapshot list`
1089
1090 ```
1091 muse snapshot list
1092 muse snapshot list --limit 5
1093 muse snapshot list --format json
1094 ```
1095
1096 #### `snapshot show`
1097
1098 ```
1099 muse snapshot show <snapshot_id>
1100 muse snapshot show abc123 # prefix lookup
1101 muse snapshot show abc123 --format text
1102 ```
1103
1104 #### `snapshot export`
1105
1106 ```
1107 muse snapshot export <snapshot_id>
1108 muse snapshot export abc123 --format zip --output release.zip
1109 muse snapshot export abc123 --prefix project/
1110 ```
1111
1112 **Archive formats:** `tar.gz` (default) · `zip`
1113
1114 **Flags (export):**
1115
1116 | Flag | Short | Description |
1117 |------|-------|-------------|
1118 | `--format <fmt>` | `-f` | `tar.gz` or `zip` |
1119 | `--output <path>` | `-o` | Output file path |
1120 | `--prefix <str>` | | Directory prefix inside archive |
1121
1122 **Exit codes:** `0` success · `1` snapshot not found
1123
1124 ---
1125
1126 ### `bundle` — offline commit transfer
1127
1128 A bundle is a self-contained JSON file carrying commits, snapshots, and objects.
1129 Copy it over SSH, USB, or email — no network connection required.
1130
1131 The bundle format is identical to the plumbing `PackBundle` JSON and is
1132 human-inspectable.
1133
1134 #### `bundle create`
1135
1136 ```
1137 muse bundle create out.bundle # bundle from HEAD
1138 muse bundle create out.bundle feat/audio # bundle a specific branch
1139 muse bundle create out.bundle HEAD --have old-sha # delta bundle
1140 ```
1141
1142 #### `bundle unbundle`
1143
1144 ```
1145 muse bundle unbundle repo.bundle # apply and update branch refs
1146 muse bundle unbundle repo.bundle --no-update-refs # objects only
1147 ```
1148
1149 #### `bundle verify`
1150
1151 ```
1152 muse bundle verify repo.bundle
1153 muse bundle verify repo.bundle --quiet
1154 muse bundle verify repo.bundle --format json
1155 ```
1156
1157 #### `bundle list-heads`
1158
1159 ```
1160 muse bundle list-heads repo.bundle
1161 muse bundle list-heads repo.bundle --format json
1162 ```
1163
1164 **Bundle value-add over plumbing:** `unbundle` updates local branch refs from
1165 the bundle's `branch_heads` map, so the receiver's repo reflects the sender's
1166 branch state automatically.
1167
1168 **Exit codes:** `0` success · `1` file not found, corrupt, or bad args
1169
1170 ---
1171
1172 ### `content-grep` — full-text search across tracked files
1173
1174 Searches every file in the HEAD snapshot for a pattern. Files are read from
1175 the content-addressed object store. Binary files and non-UTF-8 files are
1176 silently skipped.
1177
1178 Muse-specific: the search target is the **immutable object store** — you're
1179 searching a specific point in history, not the working tree.
1180
1181 ```
1182 muse content-grep --pattern "Cm7"
1183 muse content-grep --pattern "TODO|FIXME" --files-only
1184 muse content-grep --pattern "verse" --ignore-case
1185 muse content-grep --pattern "tempo" --format json
1186 muse content-grep --pattern "chord" --ref feat/harmony
1187 muse content-grep --pattern "hit" --count
1188 ```
1189
1190 **Flags**
1191
1192 | Flag | Short | Description |
1193 |------|-------|-------------|
1194 | `--pattern <regex>` | `-p` | Python regex to search for |
1195 | `--ref <ref>` | `-r` | Branch or commit to search |
1196 | `--ignore-case` | `-i` | Case-insensitive matching |
1197 | `--files-only` | `-l` | Print only matching file paths |
1198 | `--count` | `-c` | Print match count per file |
1199 | `--format <fmt>` | `-f` | `text` or `json` |
1200
1201 **JSON output schema:**
1202
1203 ```json
1204 [
1205 {
1206 "path": "song.txt",
1207 "object_id": "abc123...",
1208 "match_count": 3,
1209 "matches": [
1210 {"line_number": 4, "text": "chord: Cm7"}
1211 ]
1212 }
1213 ]
1214 ```
1215
1216 **Exit codes:** `0` at least one match · `1` no matches
1217
1218 ---
1219
1220 ### `whoami` — show the current identity
1221
1222 A shortcut for `muse auth whoami`.
1223
1224 ```
1225 muse whoami
1226 muse whoami --json # JSON output for agent consumers
1227 muse whoami --all # show identities for all configured hubs
1228 ```
1229
1230 **Flags**
1231
1232 | Flag | Short | Description |
1233 |------|-------|-------------|
1234 | `--json` | `-j` | JSON output |
1235 | `--all` | `-a` | Show all hub identities |
1236
1237 **Text output:**
1238
1239 ```
1240 hub: app.musehub.ai
1241 type: agent
1242 name: mozart-agent-v2
1243 id: usr_abc123
1244 token: set
1245 ```
1246
1247 **JSON output:**
1248
1249 ```json
1250 {
1251 "hub": "app.musehub.ai",
1252 "type": "agent",
1253 "name": "mozart-agent-v2",
1254 "id": "usr_abc123",
1255 "token_set": true,
1256 "capabilities": ["push", "pull", "share"]
1257 }
1258 ```
1259
1260 `"token_set"` is a boolean. `"capabilities"` lists the operations the current
1261 token is authorised for on the configured hub.
1262
1263 **Exit codes:** `0` identity found · `1` no identity stored (not authenticated)
1264
1265 ---
1266
1267 ## Domain Auth & Config
1268
1269 ### `auth` — identity management
1270
1271 ```
1272 muse auth login --hub app.musehub.ai
1273 muse auth login --agent --agent-id mozart-v2 --model gpt-4.5
1274 muse auth whoami
1275 muse auth logout
1276 ```
1277
1278 ---
1279
1280 ### `config` — repository configuration
1281
1282 Read and write repository configuration stored in `.muse/config.toml`.
1283
1284 ```
1285 muse config show # full config as text
1286 muse config show --format json # machine-readable
1287 muse config get core.author # single value
1288 muse config set core.author "Gabriel" # write a value
1289 ```
1290
1291 **Subcommands**
1292
1293 | Subcommand | Description |
1294 |------------|-------------|
1295 | `show` | Print the entire config |
1296 | `get <key>` | Print a single key's value |
1297 | `set <key> <value>` | Write a key/value pair |
1298
1299 **Flags — `show`**
1300
1301 | Flag | Short | Description |
1302 |------|-------|-------------|
1303 | `--format` | `-f` | `text` or `json` |
1304
1305 **JSON output — `show`**
1306
1307 ```json
1308 {
1309 "user": {
1310 "author": "gabriel",
1311 "email": ""
1312 },
1313 "hub": {
1314 "url": "https://app.musehub.ai"
1315 },
1316 "remotes": {
1317 "origin": "https://app.musehub.ai/repos/my-repo"
1318 },
1319 "domain": {
1320 "my.key": "value"
1321 }
1322 }
1323 ```
1324
1325 Top-level sections:
1326 - `user` — local author identity
1327 - `hub` — MuseHub connection (HTTPS URLs only)
1328 - `remotes` — named remote URLs
1329 - `domain` — domain-specific key/value pairs (any `domain.*` key is permitted)
1330
1331 **Blocked namespaces:** `auth.*` and `remotes.*` keys cannot be written via
1332 `config set` — use dedicated commands (`muse auth login`, `muse remote add`).
1333
1334 **Exit codes:** `0` success · `1` key not found, blocked namespace, or bad format
1335
1336 ---
1337
1338 ### `hub` — MuseHub connection
1339
1340 ```
1341 muse hub connect https://app.musehub.ai
1342 muse hub status
1343 muse hub disconnect
1344 ```
1345
1346 ---
1347
1348 ## Composability Patterns
1349
1350 Muse porcelain commands are designed to compose cleanly in pipelines.
1351
1352 **Check integrity before every push:**
1353
1354 ```bash
1355 muse verify --quiet || { echo "repo corrupt!"; exit 1; }
1356 muse push
1357 ```
1358
1359 **Offline collaboration via bundle:**
1360
1361 ```bash
1362 # Sender:
1363 muse bundle create session.bundle
1364 scp session.bundle colleague:/tmp/
1365
1366 # Receiver:
1367 muse bundle verify /tmp/session.bundle --quiet
1368 muse bundle unbundle /tmp/session.bundle
1369 ```
1370
1371 **Generate a release label in CI:**
1372
1373 ```bash
1374 VERSION=$(muse describe --format json | jq -r .name)
1375 echo "Building $VERSION..."
1376 muse snapshot export HEAD --output "${VERSION}.tar.gz"
1377 ```
1378
1379 **Find which commits touched a melody line:**
1380
1381 ```bash
1382 muse content-grep --pattern "tempo: 120" --format json | jq '.[].path'
1383 ```
1384
1385 **Agent activity summary:**
1386
1387 ```bash
1388 muse shortlog --all --numbered --email --format json \
1389 | jq '.[] | select(.author | test("agent")) | {agent: .author, count: .count}'
1390 ```
1391
1392 **Checkpoint before a risky refactor:**
1393
1394 ```bash
1395 SNAP=$(muse snapshot create -m "pre-refactor" --format json | jq -r .snapshot_id)
1396 # ... do the work ...
1397 muse snapshot show "$SNAP" --format json | jq '.manifest | keys'
1398 ```
1399
1400 **Binary-search for the broken commit (automated):**
1401
1402 ```bash
1403 muse bisect start
1404 muse bisect bad HEAD
1405 muse bisect good v1.0.0
1406 muse bisect run pytest tests/regression.py
1407 # Muse prints the first bad commit and resets automatically.
1408 ```
1409
1410 **Audit all recent changes by agents:**
1411
1412 ```bash
1413 muse log --format json | jq '[.[] | select(.author | startswith("agent-"))]'
1414 ```