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