# Muse Utility Reference & Anti-Pattern Inventory Every canonical ID, path, hash, and display operation has a utility function. **Never inline these patterns.** Use the function. This document is both a reference and a checkable inventory — every known violation is listed so they can be tracked to zero. --- ## Utility Functions ### `muse.core._types` | Function | Signature | Purpose | Replaces | |----------|-----------|---------|----------| | `blob_id` | `(data: bytes) → str` | `sha256:` ID from raw bytes | `"sha256:" + hashlib.sha256(data).hexdigest()` | | `content_hash` | `(obj: JsonValue) → str` | `sha256:` ID from JSON-serialisable object | manual `json.dumps` + `hashlib.sha256` | | `hash_file` | `(path: Path, *, chunk_size=65536) → str` | `sha256:` ID from file (streaming) | `blob_id(path.read_bytes())` for large files | | `long_id` | `(hex_digest: str, algo="sha256", *, strip=False) → str` | Add or strip `algo:` prefix (idempotent) | `f"sha256:{hex}"` · `"sha256:" + hex` · `.removeprefix("sha256:")` | | `short_id` | `(full_id: str, algo="sha256", *, strip=False) → str` | Canonical 12-char abbreviated ID | `full_id[:12]` · `full_id[7:19]` · manual slicing | | `split_id` | `(object_id: str) → tuple[str, str]` | Parse `(algo, hex)` from prefixed or bare ID | `partition(":")` inline · `.removeprefix("sha256:")` at storage boundary | | `fake_id` | `(seed: str) → str` | Deterministic `sha256:` ID for tests | `hashlib.sha256(seed.encode()).hexdigest()` in tests | | `now_utc_iso` | `() → str` | Current UTC time as ISO 8601 | `datetime.datetime.now(datetime.timezone.utc).isoformat()` | | `load_json_file` | `(path: Path) → Any \| None` | Read + parse JSON file, `None` on error | `json.loads(path.read_text(...))` in try/except | | `encode_sig` | `(algorithm: str, raw: bytes) → str` | Encode signature bytes as `algo:base64url` | manual `base64.urlsafe_b64encode` + f-string | | `encode_pubkey` | `(algorithm: str, raw: bytes) → str` | Encode public key bytes as `algo:base64url` | same as above | | `decode_sig` | `(value: str) → tuple[str, bytes]` | Decode prefixed signature string | manual `partition(":")` + `b64decode` | | `decode_pubkey` | `(value: str) → tuple[str, bytes]` | Decode prefixed public key string | same as above | | `sig_algo` | `(value: str) → str` | Extract algorithm prefix from crypto value | `value.partition(":")[0]` inline | | `split_sig` | `(value: str) → tuple[str, str]` | Parse `(algo, b64_str)` from prefixed signature string | `sig.removeprefix("ed25519:")` · manual `partition(":")` | | `split_pubkey` | `(value: str) → tuple[str, str]` | Parse `(algo, b64_str)` from prefixed public key string | `key.removeprefix("ed25519:")` · manual `partition(":")` | ### `muse.core._types` — Object store layout constants | Constant | Value | Purpose | Replaces | |----------|-------|---------|----------| | `OBJECTS_DIR` | `"objects"` | Objects subdirectory name inside `.muse/` | hardcoded `"objects"` string | | `DEFAULT_ALGO` | `"sha256"` | Default hash algorithm subdirectory | hardcoded `"sha256"` string in path construction | Import these instead of hardcoding the strings. `object_store.py` imports them here — no other module should redefine them. ### `muse.core.object_store` — Object path utilities | Function | Purpose | Replaces | |----------|---------|----------| | `object_path(root, object_id)` | `.muse/objects/sha256//` | `root / ".muse" / "objects" / "sha256" / hex[:2] / hex[2:]` | | `objects_algo_dir(root)` | `.muse/objects/sha256/` directory | `root / ".muse" / "objects" / "sha256"` · `root / ".muse" / "objects" / DEFAULT_ALGO` | `object_path` accepts a full `sha256:<64hex>` ID and handles shard-prefix length from config. **Never construct these paths by hand.** ### Focused modules — Path constructors | Function | Purpose | Replaces | |----------|---------|----------| | `commit_path(root, commit_id)` | `.muse/objects/sha256/<2>/<62>` (unified store) | `_commits_dir(root) / f"{id.removeprefix('sha256:')}.msgpack"` | | `snapshot_path(root, snapshot_id)` | `.muse/objects/sha256/<2>/<62>` (unified store) | inline path construction | | `tag_path(root, repo_id, tag_id)` | `.muse/tags/sha256//sha256/.msgpack` | inline construction | | `release_path(root, repo_id, release_id)` | `.muse/releases/sha256//sha256/.msgpack` | inline construction | ### `muse.core.validation` — Input guards | Function | Purpose | |----------|---------| | `validate_object_id(s)` | Assert `sha256:<64-hex>`; raise `ValueError` | | `sanitize_display(s)` | Strip control chars for terminal output | | `sanitize_provenance(s)` | Strip unsafe chars from agent/model IDs | | `contain_path(base, rel)` | Resolve and assert stays inside `base` | | `assert_write_inside_repo(root, path)` | Raise if path escapes repo root | --- ## ID Format Rules | Entity | Format | Notes | |--------|--------|-------| | Object, Commit, Snapshot, Tag, Release | `sha256:<64-hex>` | Always prefixed | | Repo genesis ID | `sha256:<64-hex>` | `content_hash({"created_at":…,"domain":…,"path":…})` | | Signature | `ed25519:` | No padding | | Public key | `ed25519:` | No padding | | Coordination IDs (reservation, intent) | `sha256:<64-hex>` | `content_hash({run_id, branch, addresses, …})` | | Task queue task IDs | `sha256:<64-hex>` | `content_hash({queue, title, context})` | | Hub server IDs (proposals, webhooks, etc.) | arbitrary string | External — format is musehub's to decide | | Test hub repo slugs | arbitrary string | External names — not Muse object IDs | **Content-addressed IDs are used everywhere in Muse.** `uuid.uuid4()` for `repo_id`, `commit_id`, `snapshot_id`, `object_id`, `tag_id`, `release_id`, `reservation_id`, `intent_id`, or `task_id` is a bug. --- ## The Storage-Boundary Rule **In-memory, content-addressed IDs are always prefixed (`sha256:<64hex>`). Always.** The prefix is stripped only at the storage boundary — when constructing a filesystem path that uses bare hex as a directory/filename component. It is added back only when reading an ID back from disk. Nowhere else. ``` in memory: sha256:abcdef... ← always full prefixed form ↓ write path on disk: .muse/objects/sha256/ab/cdef... ← algo dir, then bare hex sharded ↓ read path in memory: sha256:abcdef... ← reconstitute prefix immediately on read ``` **Legitimate uses of `split_id()` / `long_id(strip=True)`:** - Inside `object_path()`, `commit_path()`, `snapshot_path()`, `tag_path()`, `release_path()` — path construction only - GC traversal that walks raw filesystem paths and needs to reconstruct IDs - Prefix-scan globs where the filesystem requires bare hex **Not legitimate:** - Stripping prefix to pass an ID between in-memory functions - Stripping to do string comparison (compare full prefixed IDs) - Stripping to shorten for display (use `short_id()` instead — it handles the prefix) **`short_id()` applies to all `sha256:`-prefixed IDs**, including coordination IDs, task IDs, and hub content IDs. Hub server IDs (proposal IDs, delivery IDs, etc.) are external strings — `[:8]` on those for debug logs is fine, but do not apply `short_id()` to external strings that lack the `sha256:` prefix. --- ## Anti-Pattern Inventory ### Pattern 1 — Manual `sha256:` prefix construction **Fix:** `long_id(hex)` · `blob_id(data)` · `content_hash(obj)` | | File | Notes | |--|------|-------| | ✅ | `muse/core/gc.py` | Fixed — `long_id()` | | ✅ | `tests/test_implicit_edge_cache.py` | Fixed — `blob_id()` | | ✅ | `tests/test_guard_supercharge.py` | Fixed — `fake_id()` | | ✅ | `tests/test_symbol_log_supercharge.py` | Fixed — `fake_id()` | | ✅ | `tests/test_symbols_supercharge.py` | Fixed — `fake_id()` | | ✅ | `tests/test_algo_prefix_globs.py` | Fixed — `fake_id()` | | ✅ | `tests/test_verify_tag_supercharge.py` | Fixed — `fake_id()` | | ✅ | `tests/test_predict_supercharge.py` | Fixed — `fake_id()` | | ✅ | `tests/test_cmd_cherry_pick_hardening.py` | Fixed — `fake_id()` | | ✅ | `tests/test_cmd_cat_object.py` | Fixed — `fake_id()` | | ✅ | `tests/test_cmd_verify_object.py` | Fixed — `fake_id()` | | ✅ | `tests/test_merge_base_supercharge.py` | Fixed — `fake_id()` | | ✅ | `tests/test_cmd_merge_base.py` | Fixed — `fake_id()` | | ✅ | `tests/test_cmd_read_snapshot.py` | Fixed — `fake_id()` | | ✅ | `tests/test_read_snapshot_supercharge.py` | Fixed — `fake_id()` | | ✅ | `tests/test_cmd_read_commit.py` | Fixed — `fake_id()` | | ✅ | `tests/test_read_commit_supercharge.py` | Fixed — `fake_id()` | | ✅ | `tests/test_cmd_commit_tree.py` | Fixed — `fake_id()` | | ✅ | `tests/test_harmony_cli.py` | Fixed — `fake_id()` | | ✅ | `tests/test_harmony_phase1.py` | Fixed — `fake_id()` | | — | `tests/test_update_ref_supercharge.py:472` | Assertion string, not construction — intentional | | — | `tests/test_verify_supercharge.py:400,401` | Expected-value assertions for `long_id` — intentional | | — | `tests/test_core_types.py` | Unit tests for `long_id` itself — intentional | | — | `muse/core/snapshot.py` docstring | Example strings in module docstring — intentional | > All actionable Pattern 1 violations are resolved. --- ### Pattern 2 — Manual prefix stripping outside the storage boundary **Rule:** IDs in memory are always prefixed. Strip only inside path-construction functions. **Fix:** `split_id(x)` → `(algo, hex)` · or `long_id(x, strip=True)` for bare hex. Any `removeprefix("sha256:")` or `[7:]` outside of `object_path()`/`commit_path()`/`snapshot_path()`/`tag_path()`/`release_path()` or a GC filesystem walk is a violation. #### Production code | | File | Notes | |--|------|-------| | ✅ | `muse/core/gc.py` | Fixed — `long_id(id_str, strip=True)` | | ✅ | `muse/core/object_store.py` | Fixed — `split_id(object_id)` (×2) | | ✅ | `muse/core/commits.py` | Fixed — `long_id(safe_ref, strip=True)` | | ✅ | `muse/cli/commands/read.py` | Fixed — `long_id(ref, strip=True)` | | ✅ | `muse/cli/commands/log.py` | Fixed — `long_id(ref, strip=True)` (×2) | | ✅ | `muse/cli/commands/rev_parse.py` | Fixed — `long_id(ref, strip=True)` | | ✅ | `muse/cli/commands/read_commit.py` | Fixed — `long_id(commit_id, strip=True)` | | ✅ | `muse/cli/commands/code_check.py` | Fixed — `long_id(ref, strip=True)` | | ✅ | `muse/cli/commands/snapshot_cmd.py` | Fixed — `long_id(snapshot_id, strip=True)` | | ✅ | `muse/cli/commands/snapshot_diff.py` | Fixed — `long_id(prefix, strip=True)` | | ✅ | `muse/cli/commands/name_rev.py` | Fixed — `long_id(s, strip=True)` | #### Test code | | File | Notes | |--|------|-------| | ✅ | `tests/test_apply_pack_oserror_integrity.py` | Fixed — `commit_path()` | | ✅ | `tests/test_cmd_code_add.py` | Fixed — `split_id()` / startswith assertion | | ✅ | `tests/test_directories_feature.py` | Fixed — `split_id()` | | ✅ | `tests/test_cmd_reset_hardening.py` | Fixed — `split_id()` / `snapshot_path()` | | ✅ | `tests/test_cmd_snapshot_hardening.py` | Fixed — `split_id()` | | ✅ | `tests/test_harmony_phase4.py` | Fixed — `split_id()` | | ✅ | `tests/test_integrity_I5_commit_integrity.py` | Fixed — `long_id(strip=True)` | | ✅ | `tests/test_algo_prefix_globs.py` | Fixed — `long_id(strip=True)` | | ✅ | `tests/test_ls_tree_supercharge.py` | Fixed — `split_id()` | | ✅ | `tests/test_cmd_ls_files.py` | Fixed — `split_id(f["object_id"])` | | ✅ | `tests/test_cmd_revert_hardening.py` | Fixed — `short_id(cid, strip=True)` | > Note: `muse/core/snapshot.py` lines 474–475 strip before hashing — the hash computation intentionally uses bare hex as input. Leave as-is. --- ### Pattern 3 — Manual `hashlib.sha256` for object IDs **Fix:** `blob_id(data)` · `content_hash(obj)` · `hash_file(path)` Only flagged where result is used as a stored/returned ID, not streaming verification. | | File | Notes | |--|------|-------| | ✅ | `tests/test_implicit_edge_cache.py` | Already fixed — `blob_id(raw)` | | ✅ | `muse/plugins/code/manifest.py` | Fixed — `blob_id(…)` (×2); also storage-boundary fix with `long_id(strip=True)` | | ✅ | `muse/plugins/midi/manifest.py` | Fixed — `blob_id(file_path.encode())` | | ✅ | `muse/plugins/midi/entity.py` | Fixed — `split_id(blob_id(track_path.encode()))[1][:8]` | --- ### Pattern 4 — Manual short ID truncation **Fix:** `short_id(full_id)` (returns `sha256:abcdef123456`) · `short_id(full_id, strip=True)` (returns `abcdef123456`) #### Production code Applies to all `sha256:`-prefixed content-addressed IDs, including `reservation_id`, `intent_id`, and `task_id`. Hub server IDs (proposal IDs, delivery IDs) are external strings — use `[:8]` for those where needed. | | File | Notes | |--|------|-------| | ✅ | `muse/cli/commands/migrate.py` | Fixed — `short_id(sha, strip=True)` (×2) | | ✅ | `muse/plugins/midi/plugin.py` | Fixed — `short_id(cid, strip=True)` | | ✅ | `muse/cli/commands/tension.py` | Fixed — `short_id(commit.commit_id, strip=True)` | | ✅ | `muse/cli/commands/scale_detect.py` | Fixed — `short_id(commit.commit_id, strip=True)` | | ✅ | `muse/cli/commands/rhythm.py` | Fixed — `short_id(commit.commit_id, strip=True)` | | ✅ | `muse/cli/commands/velocity_profile.py` | Fixed — `short_id(commit.commit_id, strip=True)` | | ✅ | `muse/cli/commands/release.py` | Fixed — `short_id(release_id, strip=True)` (×2) | > `muse/core/dag.py:594`, `muse/core/task_queue.py:578,660,1068`, `muse/core/coordination.py:362,568,797,944`, `muse/cli/commands/hub/proposals.py:600,642` — hub server ID truncations (external strings, not sha256 IDs). #### Test code (representative — use `short_id` consistently) | | File | Notes | |--|------|-------| | ✅ | `tests/test_cmd_gc.py` | Fixed — `short_id(orphan_id, strip=True)` | | ✅ | `tests/test_cmd_check.py` | Fixed — `short_id(cids[-1], strip=True)` | | ✅ | `tests/test_cmd_tag.py` | Fixed — `short_id(commit_id)` | | ✅ | `tests/test_harmony_cli.py` | Fixed — `short_id(pid)` (×4) | | ✅ | `tests/test_cmd_show.py` | Fixed — `short_id(cid)` | | ✅ | `tests/test_cmd_read_snapshot.py` | Fixed — `short_id(sid)` | | ✅ | `tests/test_cmd_read_commit.py` | Fixed — `short_id(cid)` | | ✅ | `tests/test_cmd_cherry_pick_hardening.py` | Fixed — `short_id(cid)` (×2) | | ✅ | `tests/test_cmd_revert_hardening.py` | Fixed — `short_id(cid)` (×2) | | ✅ | `tests/test_cmd_bisect_hardening.py` | Fixed — `short_id(mid)` (×3) | | ✅ | `tests/test_cmd_snapshot_hardening.py` | Fixed — `short_id(snap_id)` (×7) | | ✅ | `tests/test_cmd_branch.py` | Fixed — `short_id(first_sha)` (×2) | | ✅ | `tests/test_cmd_archive_hardening.py` | Fixed — `short_id(commit_id)` (×2) | | — | `tests/test_algo_prefix_globs.py` | `bare_hex[:12]` — bare hex variable, not prefixed, exempt | | — | `tests/test_cmd_reflog_hardening.py` | `_SHA_B[:12]` — bare hex constant, not prefixed, exempt | --- ### Pattern 5 — Manual UTC timestamp **Fix:** `now_utc_iso()` | | File | Notes | |--|------|-------| | ✅ | `muse/core/bisect.py` | Fixed — `now_utc_iso()` | | ✅ | `tools/demo.py` | Fixed — `now_utc_iso()` (×3 including line 490) | | ✅ | `tests/test_directories_feature.py` | Fixed — `now_utc_iso()` (×3) | | ✅ | `tests/test_workdir_integrity.py` | Fixed — `now_utc_iso()` | | ✅ | `tests/test_cmd_coord_gc.py` | Fixed — `now_utc_iso()` (×2) | | ✅ | `tests/test_cmd_watch_coord.py` | Fixed — `now_utc_iso()` (×5) | | ✅ | `tests/test_release_analysis.py` | Fixed — `now_utc_iso()` | | ✅ | `tests/test_security_object_store_poisoning.py` | Fixed — `now_utc_iso()` (×2) | --- ### Pattern 6 — Manual JSON file loading **Fix:** `load_json_file(path)` — returns `None` on any error #### Production code | | File | Notes | |--|------|-------| | ✅ | `muse/cli/commands/status.py` | Fixed — `load_json_file(cfg_path)` | | — | `muse/plugins/code/stage.py` | Uses msgpack, not JSON file — not applicable | | ✅ | `tools/render_html.py` | Fixed — `load_json_file(json_path)` | | ✅ | `tools/demo.py` | Fixed — `load_json_file(f)` (×2) | > Note: `verify_tag.py:182` and `verify_commit.py:121` use `_json.loads(resp.read().decode())` on HTTP response bodies — not file paths, leave as-is. #### Test code (representative sample) | | File | Notes | |--|------|-------| | ✅ | `tests/test_cli_workflow.py` | Fixed — `load_json_file(...)` | | ✅ | `tests/test_cmd_reserve.py` | Fixed — `load_json_file(...)` (×3) | | ✅ | `tests/test_cmd_sparse_checkout.py` | Fixed — `load_json_file(...)` (×6) | --- ### Pattern 7 — `uuid.uuid4()` for content-addressed entity IDs **Fix:** `content_hash({…})` for genesis IDs · `fake_id(seed)` for deterministic test IDs · `secrets.token_hex(16)` for CRDT tokens and claim nonces **Everything below is a bug** — `repo_id`, `tag_id`, `release_id`, `commit_id`, `reservation_id`, `intent_id`, `task_id` must not use `uuid.uuid4()`. | | File | Notes | |--|------|-------| | ✅ | `tests/test_cmd_gc.py` | Fixed — `fake_id("repo")` | | ✅ | `tests/test_rebase_missing_snapshot_guard.py` | Fixed — `fake_id(...)` | | ✅ | `tests/test_security_ast_dos.py` | Fixed — `fake_id(...)` | | ✅ | `tests/test_merge_data_integrity.py` | Fixed — `fake_id(...)` | | ✅ | `tests/test_merge_supercharge.py` | Fixed — `fake_id(...)` | | ✅ | `tests/test_cmd_merge_dry_run.py` | Fixed — `fake_id(...)` | | ✅ | `tests/test_cmd_check.py` | Fixed — `fake_id(...)` | | ✅ | `tests/test_cmd_shelf.py` | Fixed — `fake_id(...)` | | ✅ | `tests/test_workdir_integrity.py` | Fixed — `fake_id(...)` | | ✅ | `tests/test_gc_full.py` | Fixed — `fake_id(...)` | | ✅ | `tests/test_cmd_merge.py` | Fixed — `fake_id(...)` | | ✅ | `tests/test_cmd_tag.py` | Fixed — `fake_id(...)` (×5) | | ✅ | `tests/test_pull_missing_snapshot_guard.py` | Fixed — `fake_id(...)` | | ✅ | `tests/test_cmd_reset_revert.py` | Fixed — `fake_id(...)` | | ✅ | `tests/test_cmd_config.py` | Fixed — `fake_id(...)` | | ✅ | `tests/test_cmd_bisect.py` | Fixed — `fake_id(...)` | | ✅ | `tests/test_cmd_archive_hardening.py` | Fixed — `fake_id(...)` | | ✅ | `tests/test_gc_supercharge.py` | Fixed — `fake_id(...)` | | ✅ | `tests/test_cmd_bisect_hardening.py` | Fixed — `fake_id(...)` (×2) | | ✅ | `tests/test_cmd_gc_hardening.py` | Fixed — `fake_id(...)` | | ✅ | `tests/test_security_code_porcelain.py` | Fixed — `fake_id(...)` | | ✅ | `tests/test_cmd_reflog.py` | Fixed — `fake_id(...)` | | ✅ | `tests/test_cmd_plan_merge.py` | Fixed — `fake_id(...)` (×2) | | ✅ | `tests/test_cmd_release_hardening.py` | Fixed — `fake_id(...)`; stale `import uuid` removed | --- ### Pattern 8 — Manual `fake_id` equivalent in tests **Fix:** `fake_id(seed)` — deterministic, prefixed, well-formed | | File | Notes | |--|------|-------| | ✅ | `tests/test_security_agent_impersonation.py` | Fixed — `fake_id(cid)` | | — | `tests/test_msign_dual_sig.py` | Nonces, not IDs — legitimate, leave as-is | | — | `tests/test_core_msign.py` | Signing verification hashes — legitimate, leave as-is | --- ### Pattern 9 — Manual object path construction **Fix:** `object_path(root, object_id)` from `muse.core.object_store` **Never inline** `root / ".muse" / "objects" / "sha256" / hex[:2] / hex[2:]`. | | File | Notes | |--|------|-------| | ✅ | `tests/test_cmd_invariants.py` | Already using `object_path()` | | ✅ | `tests/test_cmd_gc_hardening.py` | Fixed — `object_path(root, "sha256:" + sha)` (×2) | | ✅ | `tests/test_gc_supercharge.py` | Already using `object_path()` | | ✅ | `tests/test_porcelain_security.py` | Already using `object_path()` | | ✅ | `tests/test_core_gc.py` | Already using `object_path()` | | ✅ | `tests/test_cmd_code_add.py` | Already using `object_path()` | | ✅ | `tests/test_code_manifest.py` | Already using `object_path()` | | ✅ | `tests/test_merge_data_integrity.py` | Already using `object_path()` | --- ### Pattern 10 — Manual cryptographic prefix stripping / construction **Fix:** `split_sig(value)` → `(algo, b64_str)` · `split_pubkey(value)` → `(algo, b64_str)` · `encode_sig(algo, raw)` · `encode_pubkey(algo, raw)` Never use `removeprefix("ed25519:")`, `"ed25519:" + b64`, or `partition(":")` inline on signature/pubkey strings. | | File | Notes | |--|------|-------| | ✅ | `tests/test_core_provenance.py` | Fixed — `split_sig()` · `encode_sig()` / `decode_sig()` | --- ### Pattern 11 — `--format` flag instead of `--json` **Universal rule:** Every muse CLI command follows the same output convention: - **Default (no flag):** plain text, human-readable - **`--json` / `-j`:** machine-readable JSON for agents and scripts There is **no `--format` flag** on any muse command. `--format text`, `--format json`, and `--format xml` do not exist. Code or tests using `--format` are wrong. **In tests:** never pass `--format ` to any muse CLI invocation. **In production code:** never add a `--format` argument to a command's parser. **JSON parsing:** `cmd --json` output is always an envelope dict — for `list`-style commands the entries are under the `"entries"` key, never at the top level as a bare array. #### Known violations | | File | Location | Fix | |--|------|----------|-----| | ✅ | `tests/test_cmd_rev_parse.py` | `TestDefaultFormat`, `TestBranchResolution`, `TestAbbrevRef`, `TestEdgeCases`, `TestErrorJson` | Removed `--format text/xml`; rewrote to use plain-text default | | ✅ | `tests/test_cmd_shelf.py` | `TestSecurity::test_invalid_format_exits_1` | `assert r.exit_code != 0` (no `--format` flag exists) | | ✅ | `tests/test_cmd_shelf.py` | `TestListJsonSchema`, `TestReadJsonSchema`, `TestApplyJsonSchema`, `TestPopJsonSchema`, `TestDropJsonSchema`, `TestRoundTrips`, `TestStress` | `json.loads(r.output)` → `json.loads(r.output)["entries"]` | | ✅ | `muse/cli/commands/intent.py` | Fixed — replaced `--format/-f` with `--json/-j` bool flag; updated `test_cmd_intent.py` | #### Legitimate `--format` uses (not violations) | File | What `--format` controls | |------|--------------------------| | `snapshot_cmd.py` | Archive encoding: `tar.gz` or `zip` — file container format, not output mode | | `archive.py` | Same | | `narrative.py` | Narrative style: `timeline` or `prose` — content structure, not output mode | | `docs_cmd.py` | Documentation output type: `html`, `md`, `text`, `json` — multi-format docs renderer; `json` choice is a violation but `html`/`md` have no `--json` equivalent | > Grep target: `"--format"` in test files (no legitimate uses); `add_argument.*"--format".*choices.*text.*json` in command files.