Muse Low-level Commands Reference
Low-level commands are the machine-readable layer of the Muse CLI. They output JSON, stream bytes without size limits, use predictable exit codes, and compose cleanly in shell pipelines and agent scripts.
If you want to automate Muse — write a script, build an agent workflow, or
integrate Muse into another tool — these commands are the right entry point.
The higher-level porcelain commands (muse commit, muse merge, etc.) call
these internally.
These commands were previously namespaced under muse <cmd> and are
now top-level: muse <cmd>. No sub-namespace needed.
Quick Index
| Command | Purpose |
|---|---|
hash-object |
Compute SHA-256 of a file; optionally store it |
cat-object |
Stream raw bytes or metadata for a stored object |
verify-object |
Re-hash stored objects to detect corruption |
rev-parse |
Resolve branch name / HEAD / prefix → full commit ID |
read-commit |
Print full commit JSON record |
read-snapshot |
Print full snapshot JSON record |
ls-files |
List tracked files and their object IDs |
commit-tree |
Create a commit from an existing snapshot |
update-ref |
Move or delete a branch ref |
commit-graph |
BFS walk of the commit DAG |
merge-base |
Find the lowest common ancestor of two commits |
snapshot-diff |
Diff two snapshots: added / modified / deleted |
pack-objects |
Pack commits, snapshots, and objects into an MPack |
unpack-objects |
Apply an MPack to the local store |
verify-pack |
Three-tier integrity check for an MPack |
show-ref |
List all branch refs and their commit IDs |
symbolic-ref |
Read or write the HEAD symbolic reference |
for-each-ref |
Iterate refs with full commit metadata; sort and filter |
name-rev |
Map commit IDs to <branch>~N names |
check-ref-format |
Validate branch/ref names against naming rules |
check-ignore |
Test whether paths match .museignore rules |
check-attr |
Query .museattributes for merge strategies |
domain-info |
Inspect the active domain plugin and its schema |
ls-remote |
List refs on a remote without changing local state |
The Plumbing Contract
Every plumbing command follows the same rules:
| Property | Guarantee |
|---|---|
| Output | JSON to stdout, errors to stderr |
| Exit 0 | Success — output is valid and complete |
| Exit 1 | User error — bad input, ref not found, invalid ID |
| Exit 3 | Internal error — I/O failure, integrity check failed |
| Idempotent reads | Reading commands never modify state |
| Idempotent writes | Writing the same object twice is a no-op |
| Encoding | All text I/O is UTF-8 |
| Object IDs | Always 64 lowercase hex characters (SHA-256) |
| Short flags | Every flag has a -x short form |
JSON output is always printed to stdout. When an error occurs, the message
goes to stderr; some commands also write a machine-readable {"error": "..."}
object to stdout so scripts that parse stdout can detect the failure
without inspecting exit codes.
Command Reference
hash-object — compute a content ID
muse hash-object <file> [-w] [-f json|text]
Computes the SHA-256 content address of a file. Identical bytes always produce
the same ID; this is how Muse deduplicates storage. With --write (-w) the
object is also stored in .muse/objects/ so it can be referenced by future
snapshots and commits. The file is streamed at 64 KiB at a time — arbitrarily
large blobs never spike memory.
Flags
| Flag | Short | Default | Description |
|---|---|---|---|
--write |
-w |
off | Store the object after hashing |
--format |
-f |
json |
Output format: json or text |
Output — JSON (default)
{"object_id": "a3f2...c8d1", "stored": false}
stored is true only when --write is passed and the object was not already
in the store.
Output — --format text
a3f2...c8d1
Exit codes: 0 success · 1 path not found, is a directory, or bad --format · 3 I/O write error or integrity check failed
cat-object — read a stored object
muse cat-object <object-id> [-f raw|info]
Reads a content-addressed object from .muse/objects/. With --format raw
(the default) the raw bytes are streamed to stdout at 64 KiB at a time —
pipe to a file, another process, or a network socket without any size ceiling.
With --format info a JSON summary is printed instead of the content.
Flags
| Flag | Short | Default | Description |
|---|---|---|---|
--format |
-f |
raw |
raw (stream bytes) or info (JSON metadata) |
Output — --format info
{"object_id": "a3f2...c8d1", "present": true, "size_bytes": 4096}
When the object is absent and --format info is used, present is false
and size_bytes is 0 (exit 1). When --format raw is used and the object
is absent, the error goes to stderr (exit 1).
Exit codes: 0 found · 1 not found or invalid ID format · 3 I/O read error
rev-parse — resolve a ref to a commit ID
muse rev-parse <ref> [-f json|text]
Resolves a branch name, HEAD, or an abbreviated SHA prefix to the full
64-character commit ID. Use this to canonicalise any ref before passing it to
other commands.
Arguments
| Argument | Description |
|---|---|
<ref> |
Branch name, HEAD, full commit ID, or unique prefix |
Flags
| Flag | Short | Default | Description |
|---|---|---|---|
--format |
-f |
json |
json or text |
Output — JSON
{"ref": "main", "commit_id": "a3f2...c8d1"}
Ambiguous prefixes return an error object with a candidates list (exit 1).
Output — --format text
a3f2...c8d1
Exit codes: 0 resolved · 1 not found, ambiguous, or bad --format
ls-files — list files in a snapshot
muse ls-files [--commit <id>] [-f json|text]
Lists every file tracked in a commit's snapshot together with its content object ID. Defaults to the HEAD commit of the current branch.
Flags
| Flag | Short | Default | Description |
|---|---|---|---|
--commit |
-c |
HEAD | Commit ID to inspect |
--format |
-f |
json |
json or text |
Output — JSON
{
"commit_id": "a3f2...c8d1",
"snapshot_id": "b7e4...f912",
"file_count": 3,
"files": [
{"path": "tracks/bass.mid", "object_id": "c1d2...a3b4"},
{"path": "tracks/drums.mid", "object_id": "e5f6...b7c8"},
{"path": "tracks/piano.mid", "object_id": "09ab...cd10"}
]
}
Files are sorted by path.
Output — --format text (tab-separated, suitable for awk / cut)
c1d2...a3b4 tracks/bass.mid
e5f6...b7c8 tracks/drums.mid
09ab...cd10 tracks/piano.mid
Exit codes: 0 listed · 1 commit or snapshot not found, or bad --format
read-commit — print full commit metadata
muse read-commit <commit-id> [-f json|text]
Emits the complete JSON record for a commit. Accepts a full 64-character ID or a unique prefix.
Flags
| Flag | Short | Default | Description |
|---|---|---|---|
--format |
-f |
json |
json (full record) or text (compact one-liner) |
Output
{
"commit_id": "a3f2...c8d1",
"branch": "main",
"snapshot_id": "b7e4...f912",
"message": "Add verse melody",
"committed_at": "2026-03-18T12:00:00+00:00",
"parent_commit_id": "ff01...23ab",
"parent2_commit_id": null,
"author": "gabriel",
"agent_id": "",
"model_id": "",
"toolchain_id": "",
"prompt_hash": "",
"signature": "",
"signer_key_id": "",
"sem_ver_bump": "none",
"breaking_changes": [],
"reviewed_by": [],
"test_runs": 0,
"metadata": {}
}
Output — --format text
a3f2...c8d1 main gabriel 2026-03-21T12:00:00+00:00 Add verse melody
Error conditions always produce JSON on stdout so scripts can parse them
without inspecting stderr.
Exit codes: 0 found · 1 not found, ambiguous prefix, invalid ID format, or bad --format
read-snapshot — print full snapshot metadata
muse read-snapshot <snapshot-id> [-f json|text]
Emits the complete JSON record for a snapshot. Every commit references exactly
one snapshot. Use ls-files --commit <id> if you want to look up a snapshot
from a commit ID rather than the snapshot ID directly.
Flags
| Flag | Short | Default | Description |
|---|---|---|---|
--format |
-f |
json |
json (full manifest) or text (compact one-liner) |
Output
{
"snapshot_id": "b7e4...f912",
"created_at": "2026-03-18T12:00:00+00:00",
"file_count": 3,
"manifest": {
"tracks/bass.mid": "c1d2...a3b4",
"tracks/drums.mid": "e5f6...b7c8",
"tracks/piano.mid": "09ab...cd10"
}
}
Output — --format text
b7e4...f912 3 files 2026-03-21T12:00:00+00:00
Exit codes: 0 found · 1 not found, invalid ID format, or bad --format
commit-tree — create a commit from a snapshot ID
muse commit-tree -s <snapshot-id> [-p <parent-id>]... [-m <message>] [-a <author>] [-b <branch>] [-f json|text]
Low-level commit creation. The snapshot must already exist in the store. Both
the snapshot ID and any parent IDs are validated as proper 64-character SHA-256
hex strings before any I/O is attempted. Use --parent / -p once for a
linear commit and twice for a merge commit. The commit is written to
.muse/commits/ but no branch ref is updated — use update-ref to advance
a branch to the new commit.
Flags
| Flag | Short | Required | Description |
|---|---|---|---|
--snapshot |
-s |
✅ | SHA-256 snapshot ID |
--parent |
-p |
— | Parent commit ID (repeat for merges) |
--message |
-m |
— | Commit message |
--author |
-a |
— | Author name |
--branch |
-b |
— | Branch name (default: current branch) |
--format |
-f |
json |
json or text (bare commit ID) |
Output — JSON (default)
{"commit_id": "a3f2...c8d1"}
Output — --format text
a3f2...c8d1
The text form is ideal for shell pipelines where you want to capture the ID
directly without a jq call: NEW=$(muse commit-tree -s "$SNAP" -f text)
Exit codes: 0 commit written · 1 snapshot or parent not found, invalid ID format, or repo.json unreadable · 3 write failure
update-ref — move a branch to a commit
muse update-ref <branch> <commit-id> [--no-verify] [-f json|text]
muse update-ref <branch> --delete [-f json|text]
Directly writes (or deletes) a branch reference file under .muse/refs/heads/.
The branch name is validated with the same rules as check-ref-format before
any file is written — path-traversal via crafted branch names is not possible.
The commit ID format is always validated regardless of --no-verify, so a
malformed ID can never corrupt the ref file.
By default, the commit must already exist in the local store (--verify is on);
pass --no-verify to write the ref before the commit is stored — useful after
an unpack-objects pipeline where objects arrive in dependency order.
Flags
| Flag | Short | Default | Description |
|---|---|---|---|
--delete |
-d |
off | Delete the branch ref instead of updating it |
--verify/--no-verify |
— | --verify |
Require commit to exist in store |
--format |
-f |
json |
json or text (silent on success — exits 0) |
Output — JSON (default), update
{"branch": "main", "commit_id": "a3f2...c8d1", "previous": "ff01...23ab"}
previous is null when the branch had no prior commit.
Output — JSON, delete
{"branch": "feat/x", "deleted": true}
Output — --format text
Silent on success (exit 0). Mirrors the behaviour of git update-ref, making
it drop-in compatible with shell scripts that use exit code only.
Exit codes: 0 done · 1 commit not in store (with --verify), invalid branch or commit ID, or --delete on non-existent ref · 3 file write failure
commit-graph — emit the commit DAG
muse commit-graph [--tip <id>] [--stop-at <id>] [-n <max>] [-c] [-1] [-a] [-f json|text]
Performs a BFS walk from a tip commit (defaulting to HEAD), following both
parent_commit_id and parent2_commit_id pointers. Returns every reachable
commit as a JSON array. Useful for building visualisations, computing
reachability sets, or finding the commits on a branch since it diverged from
another.
Flags
| Flag | Short | Default | Description |
|---|---|---|---|
--tip |
— | HEAD | Commit to start from |
--stop-at |
— | — | Stop BFS at this commit (exclusive) |
--max |
-n |
10 000 | Maximum commits to traverse |
--count |
-c |
off | Emit only the integer count, not the full node list |
--first-parent |
-1 |
off | Follow only first-parent links — linear history, no merge parents |
--ancestry-path |
-a |
off | With --stop-at: restrict to commits on the direct path between tip and stop-at (capped at 100 000 visited commits to guard against unbounded BFS) |
--format |
-f |
json |
json or text (one ID per line) |
Output — JSON
{
"tip": "a3f2...c8d1",
"count": 42,
"truncated": false,
"commits": [
{
"commit_id": "a3f2...c8d1",
"parent_commit_id": "ff01...23ab",
"parent2_commit_id": null,
"message": "Add verse melody",
"branch": "main",
"committed_at": "2026-03-18T12:00:00+00:00",
"snapshot_id": "b7e4...f912",
"author": "gabriel"
}
]
}
truncated is true when the graph was cut off by --max.
Output — --count
{"tip": "a3f2...c8d1", "count": 42}
--count suppresses the commits array entirely, making it suitable for fast
cardinality checks without loading commit metadata.
Examples
Commits on a feature branch since it diverged from main:
BASE=$(muse merge-base feat/x main -f text)
muse commit-graph --tip feat/x --stop-at "$BASE" -f text
Count commits in a feature branch:
muse commit-graph \
--tip $(muse rev-parse feat/x -f text) \
--stop-at $(muse merge-base feat/x dev -f text) \
--count
Linear history only (skip merge parents):
muse commit-graph --first-parent -f text
Exit codes: 0 graph emitted · 1 tip commit not found, --ancestry-path without --stop-at, or bad --format
pack-objects — pack commits for transport
muse pack-objects <want>... [--have <id>...]
Collects a set of commits — and all their referenced snapshots and objects —
into a single MPack written to stdout. Pass --have to tell
the packer which commits the receiver already has; objects reachable only from
--have ancestors are excluded, minimising transfer size.
<want> may be a full commit ID or HEAD.
Flags
| Flag | Short | Description |
|---|---|---|
--have |
— | Commits the receiver already has (repeat for multiple) |
Output — an MPack (pipe to a file or unpack-objects)
{
"commits": [...],
"snapshots": [...],
"objects": [{"object_id": "...", "content": "<bytes>"}],
"branch_heads": {"main": "a3f2...c8d1"}
}
Objects are binary-safe; the mpack encoding handles framing without base64.
Exit codes: 0 pack written · 1 a wanted commit not found or HEAD has no commits · 3 I/O error reading from the local store
unpack-objects — apply an mpack to the local store
cat pack.muse | muse unpack-objects [-f json|text]
muse pack-objects HEAD | muse unpack-objects
Reads an MPack from stdin and writes its commits,
snapshots, and objects into .muse/. Idempotent: objects already present in
the store are silently skipped. Partial packs from interrupted transfers are
safe to re-apply.
Flags
| Flag | Short | Default | Description |
|---|---|---|---|
--format |
-f |
json |
json (machine-readable counts) or text (human summary) |
Output — JSON (default)
{
"commits_written": 12,
"snapshots_written": 12,
"objects_written": 47,
"objects_skipped": 3
}
Output — --format text
Wrote 12 commits, 12 snapshots, 47 objects (3 skipped).
Exit codes: 0 unpacked (all objects stored) · 1 invalid JSON from stdin or bad --format · 3 write failure
ls-remote — list refs on a remote
muse ls-remote [<remote-or-url>] [-f json|text]
Contacts a remote and lists every branch HEAD without altering local state.
The <remote-or-url> argument is either a remote name configured with
muse remote add (defaults to origin) or a full https:// URL.
Flags
| Flag | Short | Default | Description |
|---|---|---|---|
--format |
-f |
text |
text (tab-separated) or json (structured) |
Output — --format text (default)
One line per branch, tab-separated. The default branch is marked with *.
a3f2...c8d1 main *
b7e4...f912 feat/experiment
Output — --format json
{
"repo_id": "550e8400-e29b-41d4-a716-446655440000",
"domain": "midi",
"default_branch": "main",
"branches": {
"main": "a3f2...c8d1",
"feat/experiment": "b7e4...f912"
}
}
Exit codes: 0 remote contacted · 1 remote not configured, URL invalid, or bad --format · 3 transport error (network, HTTP error)
Composability Patterns
Export a history range
# All commits on feat/x that are not on main
BASE=$(muse rev-parse main -f text)
TIP=$(muse rev-parse feat/x -f text)
muse commit-graph --tip "$TIP" --stop-at "$BASE" -f text
Ship commits between two machines
# On the sender — pack everything the receiver doesn't have
HAVE=$(muse ls-remote origin --format text | awk '{print "--have " $1}' | tr '\n' ' ')
muse pack-objects HEAD $HAVE > bundle.json
# On the receiver
cat bundle.json | muse unpack-objects
muse update-ref main <commit-id>
Verify a stored object
ID=$(muse hash-object tracks/drums.mid -f text)
muse cat-object "$ID" -f info
Inspect what changed in the last commit
muse read-commit $(muse rev-parse HEAD -f text) | \
python3 -c "import sys, json; d=json.load(sys.stdin); print(d['message'])"
Script a bare commit (advanced)
# 1. Hash and store the files
OID=$(muse hash-object -w tracks/drums.mid -f text)
# 2. Build a snapshot manifest and write it (via muse commit is easier,
# but for full control use commit-tree after writing the snapshot)
SNAP=$(muse rev-parse HEAD -f text | \
xargs -I{} muse read-commit {} | \
python3 -c "import sys,json; print(json.load(sys.stdin)['snapshot_id'])")
# 3. Create a commit on top of HEAD
PARENT=$(muse rev-parse HEAD -f text)
NEW=$(muse commit-tree -s "$SNAP" -p "$PARENT" -m "scripted commit" | \
python3 -c "import sys,json; print(json.load(sys.stdin)['commit_id'])")
# 4. Advance the branch
muse update-ref main "$NEW"
merge-base — find the common ancestor of two commits
Find the lowest common ancestor of two commits — the point at which two branches diverged.
muse merge-base <commit-a> <commit-b> [-f json|text]
| Flag | Short | Default | Description |
|---|---|---|---|
--format |
-f |
json |
Output format: json or text |
Arguments accept full SHA-256 commit IDs, branch names, or HEAD.
JSON output:
{
"commit_a": "<sha256>",
"commit_b": "<sha256>",
"merge_base": "<sha256>"
}
When no common ancestor exists, merge_base is null and error is set.
| Exit | Meaning |
|---|---|
| 0 | Result computed (check merge_base for null vs. found) |
| 1 | Ref cannot be resolved; bad --format |
| 3 | DAG walk failed |
snapshot-diff — diff two snapshot manifests
Compare two snapshots and categorise every changed path as added, modified, or deleted.
muse snapshot-diff <ref-a> <ref-b> [-f json|text] [-s]
| Flag | Short | Default | Description |
|---|---|---|---|
--format |
-f |
json |
Output format: json or text |
--stat |
-s |
false | Append a summary line in text mode |
Arguments accept snapshot IDs, commit IDs, branch names, or HEAD.
JSON output:
{
"snapshot_a": "<sha256>",
"snapshot_b": "<sha256>",
"added": [{"path": "new.mid", "object_id": "<sha256>"}],
"modified": [{"path": "main.mid", "object_id_a": "<sha256>", "object_id_b": "<sha256>"}],
"deleted": [{"path": "old.mid", "object_id": "<sha256>"}],
"total_changes": 3
}
Text output:
A new.mid
M main.mid
D old.mid
| Exit | Meaning |
|---|---|
| 0 | Diff computed (zero changes is a valid result) |
| 1 | Ref cannot be resolved; bad --format |
| 3 | I/O error reading snapshot records |
domain-info — inspect the active domain plugin
Inspect the domain plugin active for this repository — its name, class, optional protocol capabilities, and full structural schema.
muse domain-info [-f json|text] [-a]
| Flag | Short | Default | Description |
|---|---|---|---|
--format |
-f |
json |
Output format: json or text |
--all-domains |
-a |
false | List every registered domain; no repo required |
JSON output:
{
"domain": "midi",
"plugin_class": "MidiPlugin",
"capabilities": {
"structured_merge": true,
"crdt": false
},
"schema": {
"domain": "midi", "merge_mode": "three_way",
"dimensions": [...], "top_level": {...}
},
"registered_domains": ["bitcoin", "code", "midi", "scaffold"]
}
| Exit | Meaning |
|---|---|
| 0 | Domain resolved and schema emitted |
| 1 | Domain not registered; bad --format |
| 3 | Plugin raised an error computing its schema |
show-ref — list all branch refs
List all branch refs and the commit IDs they point to.
muse show-ref [-f json|text] [-p PATTERN] [-H] [-v REF]
| Flag | Short | Default | Description |
|---|---|---|---|
--format |
-f |
json |
Output format: json or text |
--pattern |
-p |
"" |
fnmatch glob to filter ref names |
--head |
-H |
false | Print only HEAD ref and commit ID |
--verify |
-v |
"" |
Silent existence check — exit 0 if found, 1 if not |
JSON output:
{
"refs": [
{"ref": "refs/heads/dev", "commit_id": "<sha256>"},
{"ref": "refs/heads/main", "commit_id": "<sha256>"}
],
"head": {"ref": "refs/heads/main", "branch": "main", "commit_id": "<sha256>"},
"count": 2
}
Use --verify in shell conditionals:
muse show-ref --verify refs/heads/my-branch && echo "branch exists"
| Exit | Meaning |
|---|---|
| 0 | Refs enumerated (or --verify ref exists) |
| 1 | --verify ref absent; bad --format |
| 3 | I/O error reading refs directory |
check-ignore — test whether paths are excluded by .museignore
Test whether workspace paths are excluded by .museignore rules.
muse check-ignore <path>... [-f json|text] [-q] [-V]
| Flag | Short | Default | Description |
|---|---|---|---|
--format |
-f |
json |
Output format: json or text |
--quiet |
-q |
false | No output; exit 0 if all ignored, 1 otherwise |
--verbose |
-V |
false | Include matching pattern in text output |
JSON output:
{
"domain": "midi",
"patterns_loaded": 4,
"results": [
{"path": "build/out.bin", "ignored": true, "matching_pattern": "build/"},
{"path": "tracks/dr.mid", "ignored": false, "matching_pattern": null}
]
}
Last-match-wins: a negation rule (!important.mid) can un-ignore a path
matched by an earlier rule.
| Exit | Meaning |
|---|---|
| 0 | Results emitted (or --quiet with all ignored) |
| 1 | --quiet with any non-ignored path; missing args |
| 3 | TOML parse error in .museignore |
check-attr — query merge-strategy attributes for paths
Query merge-strategy attributes for workspace paths from .museattributes.
muse check-attr <path>... [-f json|text] [-d DIMENSION] [-A]
| Flag | Short | Default | Description |
|---|---|---|---|
--format |
-f |
json |
Output format: json or text |
--dimension |
-d |
* |
Domain axis to query (e.g. notes, tempo) |
--all-rules |
-A |
false | Return every matching rule, not just first-match |
JSON output (default: first-match):
{
"domain": "midi",
"rules_loaded": 3,
"dimension": "*",
"results": [
{
"path": "drums/kit.mid",
"dimension": "*",
"strategy": "ours",
"rule": {"path_pattern": "drums/*", "strategy": "ours", "priority": 10, ...}
}
]
}
When no rule matches, strategy is "auto" and rule is null.
| Exit | Meaning |
|---|---|
| 0 | Attributes resolved and emitted |
| 1 | Missing args; bad --format |
| 3 | TOML parse error in .museattributes |
verify-object — re-hash stored objects to detect corruption
Re-hash stored objects to detect silent data corruption.
muse verify-object <object-id>... [-f json|text] [-q]
| Flag | Short | Default | Description |
|---|---|---|---|
--format |
-f |
json |
Output format: json or text |
--quiet |
-q |
false | No output; exit 0 if all OK, 1 otherwise |
Objects are streamed in 64 KiB chunks — safe for very large blobs.
JSON output:
{
"results": [
{"object_id": "<sha256>", "ok": true, "size_bytes": 4096, "error": null},
{"object_id": "<sha256>", "ok": false, "size_bytes": null,
"error": "object not found in store"}
],
"all_ok": false,
"checked": 2,
"failed": 1
}
Compose with show-ref to verify every commit in a repo:
muse show-ref -f json \
| jq -r '.refs[].commit_id' \
| xargs muse verify-object
| Exit | Meaning |
|---|---|
| 0 | All objects verified successfully |
| 1 | One or more objects failed; object not found; bad args |
| 3 | Unexpected I/O error (disk read failure) |
symbolic-ref — read or write HEAD's symbolic reference
In Muse, HEAD is always a symbolic reference — it always points to a branch,
never directly to a commit. symbolic-ref reads which branch HEAD tracks or,
with --set, points HEAD at a different branch.
# Read mode
muse symbolic-ref HEAD [-f json|text] [--short]
# Write mode
muse symbolic-ref HEAD --set <branch> [-f json|text]
| Flag | Short | Default | Description |
|---|---|---|---|
--set |
-s |
"" |
Branch name to point HEAD at |
--short |
-S |
false | Emit branch name only (not the full refs/heads/… path) |
--format |
-f |
json |
Output format: json or text |
JSON output (read mode):
{
"ref": "HEAD",
"symbolic_target": "refs/heads/main",
"branch": "main",
"commit_id": "<sha256>"
}
commit_id is null when the branch has no commits yet.
Text output: refs/heads/main (or just main with --short)
| Exit | Meaning |
|---|---|
| 0 | Ref read or written |
| 1 | --set target branch does not exist; bad --format |
| 3 | I/O error reading or writing HEAD |
for-each-ref — iterate all refs with rich commit metadata
Enumerates every branch ref together with the full commit metadata it points to. Supports sorting by any commit field and glob-pattern filtering, making it ideal for agent pipelines that need to slice the ref list without post-processing.
muse for-each-ref [-p <pattern>] [-s <field>] [-d] [-n <count>] [-f json|text]
| Flag | Short | Default | Description |
|---|---|---|---|
--pattern |
-p |
"" |
fnmatch glob on the full ref name, e.g. refs/heads/feat/* |
--sort |
-s |
ref |
Sort field: ref, branch, commit_id, author, committed_at, message |
--desc |
-d |
false | Reverse sort order (descending) |
--count |
-n |
0 |
Limit to first N refs after sorting (0 = unlimited) |
--format |
-f |
json |
Output format: json or text |
JSON output:
{
"refs": [
{
"ref": "refs/heads/dev",
"branch": "dev",
"commit_id": "<sha256>",
"author": "gabriel",
"message": "Add verse melody",
"committed_at": "2026-01-01T00:00:00+00:00",
"snapshot_id": "<sha256>"
}
],
"count": 1
}
Text output: <commit_id> <ref> <committed_at> <author>
Example — three most recently committed branches:
muse for-each-ref --sort committed_at --desc --count 3
| Exit | Meaning |
|---|---|
| 0 | Refs emitted (list may be empty) |
| 1 | Bad --sort field; bad --format |
| 3 | I/O error reading refs or commit records |
name-rev — map commit IDs to branch-relative names
For each supplied commit ID, performs a single multi-source BFS from all branch
tips and reports the closest branch and hop distance. Results are expressed as
<branch>~N — where N is the number of parent hops from the tip. When N is 0
(the commit is the exact branch tip) the name is the bare branch name with no
~0 suffix.
muse name-rev <commit-id>... [-n] [-u <string>] [-f json|text]
| Flag | Short | Default | Description |
|---|---|---|---|
--name-only |
-n |
false | Emit only the name (or the undefined string), not the commit ID |
--undefined |
-u |
"undefined" |
String to emit for unreachable commits |
--format |
-f |
json |
Output format: json or text |
JSON output:
{
"results": [
{
"commit_id": "<sha256>",
"name": "main~3",
"branch": "main",
"distance": 3,
"undefined": false
},
{
"commit_id": "<sha256>",
"name": null,
"branch": null,
"distance": null,
"undefined": true
}
]
}
Text output: <sha256> main~3 (or main~3 with --name-only)
Performance: A single O(total-commits) BFS from all branch tips simultaneously. Every commit is visited at most once regardless of how many input IDs are supplied.
| Exit | Meaning |
|---|---|
| 0 | All results computed (some may be undefined) |
| 1 | Bad --format; no commit IDs supplied |
| 3 | I/O error reading commit records |
check-ref-format — validate branch and ref names
Tests one or more names against Muse's branch-naming rules — the same
validation used by muse branch and muse update-ref. Use in
scripts to pre-validate names before attempting to create a branch.
muse check-ref-format <name>... [-q] [-f json|text]
| Flag | Short | Default | Description |
|---|---|---|---|
--quiet |
-q |
false | No output — exit 0 if all valid, exit 1 otherwise |
--format |
-f |
json |
Output format: json or text |
Rules enforced: 1–255 chars; no backslash, null bytes, CR, LF, or tab;
no leading/trailing dot; no consecutive dots (..); no leading/trailing or
consecutive slashes.
JSON output:
{
"results": [
{"name": "feat/my-branch", "valid": true, "error": null},
{"name": "bad..name", "valid": false, "error": "..."}
],
"all_valid": false
}
Text output:
ok feat/my-branch
FAIL bad..name → Branch name 'bad..name' contains forbidden characters
Shell conditional:
muse check-ref-format -q "$BRANCH" && git checkout -b "$BRANCH"
| Exit | Meaning |
|---|---|
| 0 | All names are valid |
| 1 | One or more names are invalid; no names supplied |
verify-pack — verify MPack integrity
Reads an MPack from stdin or --file and performs three-tier integrity
checking:
- Object integrity — every object payload is base64-decoded and its SHA-256
is recomputed. The digest must match the declared
object_id. - Snapshot consistency — every snapshot's manifest entries reference objects present in the bundle or already in the local store.
- Commit consistency — every commit's
snapshot_idis present in the bundle or already in the local store.
muse pack-objects main | muse verify-pack
muse verify-pack --file bundle.json
| Flag | Short | Default | Description |
|---|---|---|---|
--file |
-i |
"" |
Path to bundle file (reads stdin when omitted) |
--quiet |
-q |
false | No output — exit 0 if clean, exit 1 on any failure |
--no-local |
-L |
false | Skip local store checks (verify bundle in isolation) |
--format |
-f |
json |
Output format: json or text |
JSON output:
{
"objects_checked": 42,
"snapshots_checked": 5,
"commits_checked": 5,
"all_ok": true,
"failures": []
}
With failures:
{
"all_ok": false,
"failures": [
{"kind": "object", "id": "<sha256>", "error": "hash mismatch"},
{"kind": "snapshot", "id": "<sha256>", "error": "missing object: ..."}
]
}
Validate before upload:
muse pack-objects main | muse verify-pack -q \
&& echo "mpack is clean — safe to push"
| Exit | Meaning |
|---|---|
| 0 | MPack is fully intact |
| 1 | One or more integrity failures; malformed msgpack; bad args |
| 3 | I/O error reading stdin or the mpack file |
Composability Patterns — Advanced
Name every commit reachable from a branch
# Get all commit IDs on feat/x since it diverged from dev
BASE=$(muse merge-base feat/x dev -f text)
muse commit-graph --tip feat/x --stop-at "$BASE" -f text \
| xargs muse name-rev --name-only
Audit all refs with full metadata and filter by recency
# List all branches modified in 2026, sorted newest-first
muse for-each-ref --sort committed_at --desc \
| jq '.refs[] | select(.committed_at | startswith("2026"))'
Validate a branch name before creating it
BRANCH="feat/my-feature"
muse check-ref-format -q "$BRANCH" \
&& echo "Name is valid — safe to branch" \
|| echo "Invalid branch name"
Verify a bundle before shipping
muse pack-objects main | tee bundle.json | muse verify-pack -q \
&& echo "bundle is clean — safe to push" \
|| echo "bundle has integrity failures — do not push"
Switch active branch via plumbing
# Check where HEAD is now
muse symbolic-ref HEAD -f text # → refs/heads/main
# Redirect HEAD to dev
muse symbolic-ref HEAD --set dev
muse rev-parse HEAD -f text # → tip of dev
Find stale branches (no commits in the last 30 days)
# Requires `date` and `jq`
CUTOFF=$(date -u -v-30d +%Y-%m-%dT%H:%M:%SZ 2>/dev/null \
|| date -u --date="30 days ago" +%Y-%m-%dT%H:%M:%SZ)
muse for-each-ref -f json \
| jq --arg c "$CUTOFF" '.refs[] | select(.committed_at < $c) | .branch'
Check which files changed between two branches
BASE=$(muse merge-base main feat/x -f text)
muse snapshot-diff "$BASE" feat/x --format text --stat
Object ID Quick Reference
All IDs in Muse are 64-character lowercase hex SHA-256 digests. There are three kinds:
| Kind | Computed from | Used by |
|---|---|---|
| Object ID | File bytes | hash-object, cat-object, snapshot manifests |
| Snapshot ID | Sorted path:object_id pairs |
read-snapshot, commit-tree |
| Commit ID | Parent IDs + snapshot ID + message + timestamp | read-commit, rev-parse, update-ref |
Every ID is deterministic and content-addressed. The same input always produces the same ID; two different inputs never produce the same ID in practice.
Exit Code Summary
| Code | Constant | Meaning |
|---|---|---|
| 0 | SUCCESS |
Command completed successfully |
| 1 | USER_ERROR |
Bad input, ref not found, invalid format |
| 3 | INTERNAL_ERROR |
I/O failure, integrity check, transport error |