gabriel / muse public
code-domain.md markdown
1134 lines 35.5 KB
8912a997 feat: code porcelain hardening — security, perf, JSON, docs Gabriel Cardona <gabriel@tellurstori.com> 2d ago
1 # Code Domain — Complete Reference
2
3 > **Engine:** `muse/plugins/code/` · **No external deps for core analysis**
4 > **Scope:** Every command, module, type, and protocol in the code domain plugin
5
6 ---
7
8 ## Overview
9
10 The code domain plugin treats a codebase as a **typed, content-addressed symbol graph** — not as a bag of text lines. Every function, class, method, variable, and import becomes a `SymbolRecord` with a stable content-addressed identity (SHA-256). This unlocks operations that are structurally impossible in Git:
11
12 - Track a function through renames and cross-file moves with perfect identity.
13 - Cherry-pick a single named function out of a historical commit.
14 - Detect exact and near-duplicate code across an entire snapshot in O(1).
15 - Predict merge conflicts before writing a single byte.
16 - Enforce architectural invariants as committed rules.
17 - Assign semantic version bumps automatically at commit time.
18 - Coordinate thousands of parallel agents without a central lock server.
19
20 ---
21
22 ## Contents
23
24 1. [Symbol Identity Model](#1-symbol-identity-model)
25 2. [Provenance & Topology Commands](#2-provenance--topology-commands)
26 3. [Query & Temporal Search](#3-query--temporal-search)
27 4. [Index Infrastructure](#4-index-infrastructure)
28 5. [Symbol Identity Detail](#5-symbol-identity-detail)
29 6. [Multi-Agent Coordination Layer](#6-multi-agent-coordination-layer)
30 7. [Merge Engine & Architectural Enforcement](#7-merge-engine--architectural-enforcement)
31 8. [Semantic Versioning](#8-semantic-versioning)
32 9. [Call-Graph Tier Commands](#9-call-graph-tier-commands)
33 10. [Architecture Internals](#10-architecture-internals)
34 11. [Type Reference](#11-type-reference)
35
36 ---
37
38 ## 1. Symbol Identity Model
39
40 Every symbol carries four content-addressed hashes and two stable keys:
41
42 | Field | Description |
43 |---|---|
44 | `content_id` | SHA-256 of the full normalized AST (signature + body + metadata). Two symbols are identical iff their `content_id` matches. |
45 | `body_hash` | SHA-256 of the function/class body only (excluding signature and decorators). Matches across renames and decorator changes. |
46 | `signature_id` | SHA-256 of the normalized parameter list and return annotation. Matches across implementation-only changes. |
47 | `metadata_id` *(v2)* | SHA-256 of decorator list + async flag + base classes. Matches when only the implementation or signature changed. |
48 | `canonical_key` *(v2)* | `{file}#{scope}#{kind}#{name}#{lineno}` — stable machine handle for agent-to-agent symbol handoff. |
49 | `qualified_name` | Dotted path within the file (e.g. `MyClass.my_method`). |
50
51 ### Exact Refactor Classification
52
53 Two symbols are classified by comparing their four hashes:
54
55 | Classification | Condition |
56 |---|---|
57 | `unchanged` | `content_id` matches |
58 | `rename` | `body_hash` matches, name differs, same file |
59 | `move` | `content_id` matches, different file, same name |
60 | `rename+move` | `body_hash` matches, different file, different name |
61 | `signature_only` | `body_hash` matches, `signature_id` differs |
62 | `impl_only` | `signature_id` matches, `body_hash` differs |
63 | `metadata_only` | `body_hash` + `signature_id` match, `metadata_id` differs |
64 | `full_rewrite` | Both signature and body changed |
65
66 ---
67
68 ## 2. Provenance & Topology Commands
69
70 ### `muse code lineage ADDRESS`
71
72 Full provenance chain of a named symbol from its first appearance to the present.
73
74 ```
75 muse code lineage src/billing.py::compute_total
76 muse code lineage src/billing.py::compute_total --commit HEAD~10
77 muse code lineage src/billing.py::compute_total --json
78 ```
79
80 **How it works:** Walks all commits in chronological order, scanning `InsertOp`/`DeleteOp`/`ReplaceOp` entries in each `structured_delta`. Rename detection uses `content_id` matching across Insert+Delete pairs within a single commit.
81
82 **Output events:** `created`, `modified`, `renamed_from`, `moved_from`, `deleted`.
83
84 **Flags:**
85 - `--commit REF` — stop history walk at this commit (default: HEAD)
86 - `--json` — emit a JSON array of event objects
87
88 **JSON schema:**
89 ```json
90 [
91 {
92 "event": "created",
93 "commit_id": "a1b2c3d4...",
94 "committed_at": "2026-01-01T00:00:00+00:00",
95 "message": "Initial commit",
96 "address": "src/billing.py::compute_total",
97 "content_id": "sha256..."
98 }
99 ]
100 ```
101
102 ---
103
104 ### `muse code api-surface`
105
106 Public API surface of a snapshot — every non-underscore function, class, and method.
107
108 ```
109 muse code api-surface
110 muse code api-surface --commit v1.0
111 muse code api-surface --diff v1.0
112 muse code api-surface --json
113 ```
114
115 **With `--diff REF`:** Shows three sections — **Added** (new public symbols), **Removed** (deleted public symbols), **Changed** (same address, different `content_id`).
116
117 **Public** means: `kind` in `{function, class, method, async_function}` and `name` not starting with `_`.
118
119 ---
120
121 ### `muse code codemap`
122
123 Semantic topology of the entire codebase at a snapshot.
124
125 ```
126 muse code codemap
127 muse code codemap --top 10
128 muse code codemap --commit HEAD~5
129 muse code codemap --json
130 ```
131
132 **What it shows:**
133 - **Modules by size** — ranked by symbol count
134 - **Import graph** — in-degree (how many modules import this one)
135 - **Cycles** — import cycles detected via DFS (a hard architectural smell)
136 - **High-centrality symbols** — functions called from many places (blast-radius risk)
137 - **Boundary files** — high fan-out (imports many), zero fan-in (nothing imports them)
138
139 **Flags:**
140 - `--top N` — show top N entries per section (default: 5)
141 - `--commit REF` — snapshot to analyse
142 - `--json` — structured output
143
144 ---
145
146 ### `muse code clones`
147
148 Find exact and near-duplicate symbol clusters across the snapshot.
149
150 ```
151 muse code clones
152 muse code clones --tier exact
153 muse code clones --tier near
154 muse code clones --tier both
155 muse code clones --commit HEAD~3 --json
156 ```
157
158 **Exact clones:** Same `body_hash` at different addresses. These are literal copy-paste duplicates — same implementation, possibly different name.
159
160 **Near-clones:** Same `signature_id`, different `body_hash`. Same public contract (parameters + return type), diverged implementation — a maintainability risk.
161
162 **Output:** Clusters, one per group, listing all member addresses.
163
164 ---
165
166 ### `muse code checkout-symbol ADDRESS --commit REF`
167
168 Restore a single named symbol from a historical commit into the current working tree. Only the target symbol's lines change; everything else is untouched.
169
170 ```
171 muse code checkout-symbol src/billing.py::compute_total --commit v1.0
172 muse code checkout-symbol src/billing.py::compute_total --commit abc123 --dry-run
173 muse code checkout-symbol src/billing.py::compute_total --commit v1.0 --json
174 ```
175
176 **Flags:**
177 - `--commit REF` *(required)* — source commit
178 - `--dry-run` — print the unified diff without writing
179 - `--json` — emit result as JSON for agent consumption
180
181 **JSON output:**
182
183 ```json
184 {
185 "address": "src/billing.py::compute_total",
186 "file": "src/billing.py",
187 "restored_from": "abc12345",
188 "dry_run": false
189 }
190 ```
191
192 **Safety:** Rejects the operation if the target file cannot be parsed (syntax error) or if the symbol no longer exists at the destination location and the file cannot be safely patched.
193
194 **Security:** The file path component of ADDRESS is validated via `contain_path()` before any disk access. Paths that escape the repo root (e.g. `../../etc/passwd::foo`) are rejected with exit 1.
195
196 ---
197
198 ### `muse code semantic-cherry-pick ADDRESS... --from REF`
199
200 Cherry-pick one or more named symbols from a historical commit. Applies each symbol patch to the working tree at the symbol's current location; appends at the end of the file if the symbol is not present in the current tree.
201
202 ```
203 muse code semantic-cherry-pick src/billing.py::compute_total --from v1.0
204 muse code semantic-cherry-pick src/billing.py::f1 src/billing.py::f2 --from abc123
205 muse code semantic-cherry-pick src/billing.py::compute_total --from v1.0 --dry-run --json
206 ```
207
208 **Flags:**
209 - `--from REF` *(required)* — source commit
210 - `--dry-run` — show what would change without writing
211 - `--json` — structured output with per-symbol patch results
212
213 **JSON output:**
214
215 ```json
216 {
217 "from_commit": "abc12345",
218 "dry_run": false,
219 "results": [
220 {"address": "src/billing.py::compute_total", "status": "applied",
221 "detail": "lines 10–25 → 12 lines", "old_lines": 16, "new_lines": 12}
222 ],
223 "applied": 1,
224 "failed": 0,
225 "already_current": 0
226 }
227 ```
228
229 **Security:** Every file path extracted from ADDRESS arguments is validated via `contain_path()` before any disk I/O or directory creation. Paths that escape the repo root are recorded as `not_found` and the remaining symbols continue to be processed.
230
231 ---
232
233 ## 3. Query & Temporal Search
234
235 ### `muse code query PREDICATE...`
236
237 Symbol graph predicate DSL — SQL for your codebase.
238
239 ```
240 muse code query kind=function language=Python
241 muse code query "(kind=function OR kind=method) name^=_"
242 muse code query "NOT kind=import file~=billing"
243 muse code query kind=function name~=validate --all-commits
244 muse code query hash=a3f2c9 --all-commits --first
245 muse code query --commit v1.0 kind=class
246 muse code query kind=function --json
247 ```
248
249 #### Predicate Grammar (v2)
250
251 ```
252 expr = or_expr
253 or_expr = and_expr ( "OR" and_expr )*
254 and_expr = not_expr ( and_expr )* # implicit AND
255 not_expr = "NOT" primary | primary
256 primary = "(" expr ")" | atom
257 atom = KEY OP VALUE
258 ```
259
260 #### Operators
261
262 | Operator | Meaning |
263 |---|---|
264 | `=` | Exact match (case-insensitive for strings) |
265 | `~=` | Contains substring |
266 | `^=` | Starts with |
267 | `$=` | Ends with |
268 | `!=` | Not equal |
269 | `>=` | Greater than or equal (lineno keys only) |
270 | `<=` | Less than or equal (lineno keys only) |
271
272 #### Keys
273
274 | Key | Type | Description |
275 |---|---|---|
276 | `kind` | string | `function`, `class`, `method`, `variable`, `import`, … |
277 | `language` | string | `Python`, `Go`, `Rust`, `TypeScript`, … |
278 | `name` | string | Bare symbol name |
279 | `qualified_name` | string | Dotted qualified name (e.g. `MyClass.save`) |
280 | `file` | string | File path (relative to repo root) |
281 | `hash` | string | `content_id` prefix (hex) |
282 | `body_hash` | string | `body_hash` prefix |
283 | `signature_id` | string | `signature_id` prefix |
284 | `lineno_gt` | integer | Symbol starts *after* this line number |
285 | `lineno_lt` | integer | Symbol starts *before* this line number |
286
287 #### Flags
288
289 | Flag | Description |
290 |---|---|
291 | `--commit REF` | Query a specific commit (mutually exclusive with `--all-commits`) |
292 | `--all-commits` | Walk all commits, deduplicate by `content_id`, annotate first-seen commit |
293 | `--first` | With `--all-commits`: keep only the first appearance of each unique body |
294 | `--json` | JSON output with `schema_version: 2` wrapper |
295
296 ---
297
298 ### `muse code query-history PREDICATE... [--from REF] [--to REF]`
299
300 Temporal symbol search — track matching symbols across a commit range.
301
302 ```
303 muse code query-history kind=function language=Python
304 muse code query-history name~=validate --from v1.0 --to HEAD
305 muse code query-history kind=class --json
306 ```
307
308 **Output:** For each matching symbol address, reports `first_seen`, `last_seen`, `commit_count` (how many commits touched it), and `change_count` (how many times its `content_id` changed).
309
310 **JSON schema:**
311 ```json
312 {
313 "schema_version": 2,
314 "query": "kind=function language=Python",
315 "from_ref": "v1.0",
316 "to_ref": "HEAD",
317 "results": [
318 {
319 "address": "src/billing.py::compute_total",
320 "first_seen": "commit_id...",
321 "last_seen": "commit_id...",
322 "commit_count": 12,
323 "change_count": 3
324 }
325 ]
326 }
327 ```
328
329 ---
330
331 ## 4. Index Infrastructure
332
333 ### `muse code index status`
334
335 Show present/absent/corrupt status and entry counts for all local indexes.
336
337 ```
338 muse code index status
339 muse code index status --json
340 ```
341
342 **Flags:**
343 - `--json` — emit status array as JSON
344
345 **JSON output:**
346
347 ```json
348 [
349 {"name": "symbol_history", "status": "present", "entries": 1024, "updated_at": "2026-03-21T12:00:00"},
350 {"name": "hash_occurrence", "status": "absent", "entries": 0, "updated_at": null}
351 ]
352 ```
353
354 ### `muse code index rebuild`
355
356 Rebuild one or all indexes by walking the full commit history.
357
358 ```
359 muse code index rebuild
360 muse code index rebuild --json
361 muse code index rebuild --index symbol_history
362 muse code index rebuild --index hash_occurrence --verbose
363 ```
364
365 **Flags:**
366 - `--index NAME` — rebuild only this index (default: all)
367 - `--verbose, -v` — show progress while building
368 - `--json` — emit rebuild summary as JSON
369
370 **JSON output:**
371
372 ```json
373 {
374 "rebuilt": ["symbol_history", "hash_occurrence"],
375 "symbol_history_addresses": 512,
376 "symbol_history_events": 2048,
377 "hash_occurrence_clusters": 31,
378 "hash_occurrence_addresses": 87
379 }
380 ```
381
382 ### Index Design
383
384 Indexes live under `.muse/indices/` and are:
385 - **Derived** — computed entirely from the commit history.
386 - **Optional** — no command requires them for correctness; they only provide speed.
387 - **Fully rebuildable** — `muse code index rebuild` reconstructs them from scratch in one pass.
388 - **Versioned** — `schema_version` field for forward compatibility.
389
390 #### `symbol_history` index
391
392 Maps `symbol_address → list[HistoryEntry]` (chronological). Enables O(1) lineage lookups instead of O(commits × files) scans.
393
394 #### `hash_occurrence` index
395
396 Maps `body_hash → list[symbol_address]`. Enables O(1) clone detection and `muse code find-symbol hash=` queries.
397
398 ---
399
400 ## 5. Symbol Identity Detail
401
402 ### New `SymbolRecord` fields
403
404 `SymbolRecord` gains two backward-compatible fields (empty string `""` for pre-v2 records):
405
406 **`metadata_id`**
407 : SHA-256 of the symbol's *metadata wrapper* — decorators + async flag for Python functions, decorator list + base classes for Python classes. Allows distinguishing a decorator change from a body change.
408
409 **`canonical_key`**
410 : `{file}#{scope}#{kind}#{name}#{lineno}` — a stable, unique machine handle for a symbol within a snapshot. Enables agent-to-agent symbol handoff without re-querying. Disambiguates overloaded names and nested scopes.
411
412 ### `muse code detect-refactor` (v2 output)
413
414 With `--json`, emits `schema_version: 2` with a richer classification:
415
416 ```json
417 {
418 "schema_version": 2,
419 "from_commit": "abc...",
420 "to_commit": "def...",
421 "total": 3,
422 "events": [
423 {
424 "old_address": "src/billing.py::compute_total",
425 "new_address": "src/billing.py::compute_invoice_total",
426 "old_kind": "function",
427 "new_kind": "function",
428 "exact_classification": "rename",
429 "inferred_refactor": "none",
430 "confidence": 1.0,
431 "evidence": ["body_hash matches a1b2c3d4"],
432 "old_content_id": "ab12cd34",
433 "new_content_id": "ef56gh78",
434 "old_body_hash": "a1b2c3d4",
435 "new_body_hash": "a1b2c3d4"
436 }
437 ]
438 }
439 ```
440
441 **`exact_classification`** values: `rename`, `move`, `rename+move`, `signature_only`, `impl_only`, `metadata_only`, `full_rewrite`, `unchanged`.
442
443 **`inferred_refactor`** values: `extract`, `inline`, `split`, `merge`, `none`.
444
445 ---
446
447 ## 6. Multi-Agent Coordination Layer
448
449 The coordination layer enables thousands of agents to work on the same codebase simultaneously without stepping on each other. It is **purely advisory** — the VCS engine never reads coordination data for correctness decisions. Agents that ignore it still produce correct commits.
450
451 ### Storage Layout
452
453 ```
454 .muse/coordination/
455 reservations/<uuid>.json advisory symbol lease
456 intents/<uuid>.json declared operation before edit
457 ```
458
459 All records are **write-once** (never mutated) and use TTL-based expiry. Expired records are kept for audit purposes but ignored by all commands.
460
461 ---
462
463 ### `muse coord reserve ADDRESS... [OPTIONS]`
464
465 Announce intent to edit one or more symbol addresses.
466
467 ```
468 muse coord reserve src/billing.py::compute_total
469 muse coord reserve src/billing.py::f1 src/billing.py::f2 --run-id agent-007 --ttl 7200
470 muse coord reserve src/billing.py::compute_total --op rename
471 muse coord reserve src/billing.py::compute_total --json
472 ```
473
474 **Flags:**
475 - `--run-id ID` — identifier for this agent/run (default: random UUID)
476 - `--ttl SECONDS` — reservation expiry in seconds (default: 3600)
477 - `--op OPERATION` — declared operation: `rename`, `move`, `extract`, `modify`, `delete`
478 - `--json` — JSON output
479
480 **Conflict detection:** Warns (but never blocks) if any of the requested addresses are already reserved by another active reservation.
481
482 **Reservation schema (v1):**
483 ```json
484 {
485 "schema_version": 1,
486 "reservation_id": "<uuid>",
487 "run_id": "<agent-supplied ID>",
488 "branch": "<current branch>",
489 "addresses": ["src/billing.py::compute_total"],
490 "created_at": "2026-03-18T12:00:00+00:00",
491 "expires_at": "2026-03-18T13:00:00+00:00",
492 "operation": "rename"
493 }
494 ```
495
496 ---
497
498 ### `muse coord intent ADDRESS... --op OPERATION [OPTIONS]`
499
500 Declare a specific operation before executing it. More precise than a reservation; enables `muse coord forecast` to produce accurate conflict predictions.
501
502 ```
503 muse coord intent src/billing.py::compute_total --op rename --detail "rename to compute_invoice_total"
504 muse coord intent src/billing.py::compute_total --op modify --reservation-id <uuid>
505 ```
506
507 **Flags:**
508 - `--op OPERATION` *(required)* — `rename`, `move`, `extract`, `modify`, `delete`, `refactor`
509 - `--detail TEXT` — free-text description of the planned change
510 - `--reservation-id UUID` — link to an existing reservation
511 - `--run-id ID` — agent identifier
512 - `--json` — JSON output
513
514 **Intent schema (v1):**
515 ```json
516 {
517 "schema_version": 1,
518 "intent_id": "<uuid>",
519 "reservation_id": "<uuid or empty>",
520 "run_id": "<agent ID>",
521 "branch": "<current branch>",
522 "addresses": ["src/billing.py::compute_total"],
523 "operation": "rename",
524 "created_at": "2026-03-18T12:00:00+00:00",
525 "detail": "rename to compute_invoice_total"
526 }
527 ```
528
529 ---
530
531 ### `muse coord forecast [OPTIONS]`
532
533 Predict merge conflicts from active reservations and intents — **before** writing any code.
534
535 ```
536 muse coord forecast
537 muse coord forecast --branch feature-x
538 muse coord forecast --json
539 ```
540
541 **Conflict types detected:**
542
543 | Type | Confidence | Condition |
544 |---|---|---|
545 | `address_overlap` | 1.0 | Two reservations on the same symbol address |
546 | `blast_radius_overlap` | 0.75 | Reservations on symbols that call each other (via call graph) |
547 | `operation_conflict` | 0.9 | Two reservations declare incompatible operations (e.g. both `rename`) |
548
549 **Flags:**
550 - `--branch BRANCH` — restrict to reservations on this branch
551 - `--json` — structured conflict list
552
553 ---
554
555 ### `muse coord plan-merge OURS THEIRS [OPTIONS]`
556
557 Dry-run semantic merge plan — classify all symbol conflicts without writing anything.
558
559 ```
560 muse coord plan-merge main feature-x
561 muse coord plan-merge HEAD~5 HEAD --json
562 ```
563
564 **Output:** Classifies each diverging symbol into one of:
565 - `no_conflict` — diverged in disjoint symbols
566 - `symbol_edit_overlap` — both sides modified the same symbol
567 - `rename_edit` — one side renamed, the other modified
568 - `delete_use` — one side deleted a symbol still used by the other
569
570 **Flags:**
571 - `--json` — structured output with full classification details
572
573 ---
574
575 ### `muse coord shard --agents N [OPTIONS]`
576
577 Partition the codebase into N low-coupling work zones for parallel agent assignment.
578
579 ```
580 muse coord shard --agents 4
581 muse coord shard --agents 8 --language Python
582 muse coord shard --agents 4 --json
583 ```
584
585 **Algorithm:** Builds the import graph, finds connected components, greedily merges small components into N balanced shards (by symbol count). Reports cross-shard edges as a coupling score (lower is better).
586
587 **Flags:**
588 - `--agents N` *(required)* — number of shards
589 - `--language LANG` — restrict to files of this language
590 - `--json` — shard assignments as JSON
591
592 ---
593
594 ### `muse coord reconcile [OPTIONS]`
595
596 Recommend merge ordering and integration strategy from the current coordination state.
597
598 ```
599 muse coord reconcile
600 muse coord reconcile --json
601 ```
602
603 **Output:** For each active branch with reservations, recommends:
604 - **Merge order** — branches with fewer predicted conflicts should merge first
605 - **Integration strategy** — `fast-forward`, `rebase`, or `manual` (when conflicts are predicted)
606 - **Conflict hotspots** — addresses that appear in the most reservations
607
608 ---
609
610 ## 7. Merge Engine & Architectural Enforcement
611
612 ### `ConflictRecord` — Structured Conflict Taxonomy
613
614 `MergeResult` now carries `conflict_records: list[ConflictRecord]` alongside the existing `conflicts: list[str]`. Each `ConflictRecord` provides structured metadata for programmatic conflict handling:
615
616 ```python
617 @dataclass
618 class ConflictRecord:
619 path: str
620 conflict_type: str = "file_level" # see taxonomy below
621 ours_summary: str = ""
622 theirs_summary: str = ""
623 addresses: list[str] = field(default_factory=list)
624 ```
625
626 **`conflict_type` taxonomy:**
627
628 | Value | Meaning |
629 |---|---|
630 | `symbol_edit_overlap` | Both branches modified the same symbol |
631 | `rename_edit` | One branch renamed, the other modified |
632 | `move_edit` | One branch moved, the other modified |
633 | `delete_use` | One branch deleted a symbol still used by the other |
634 | `dependency_conflict` | Conflicting changes to interdependent symbols |
635 | `file_level` | Legacy — no symbol-level information available |
636
637 ---
638
639 ### `muse code breakage`
640
641 Detect symbol-level structural breakage in the current working tree vs HEAD.
642
643 ```
644 muse code breakage
645 muse code breakage --language Python
646 muse code breakage --json
647 ```
648
649 **Checks performed:**
650
651 1. **`stale_import`** — a `from X import Y` where `Y` no longer exists in the committed version of `X` (detected via symbol graph, not execution).
652 2. **`missing_interface_method`** — a class body is missing a method that exists in the HEAD snapshot's version of that class.
653
654 **What it does NOT do:** Execute code, install packages, run mypy or a type checker, or access the network. Pure structural analysis.
655
656 **JSON output:**
657 ```json
658 {
659 "breakage_count": 2,
660 "issues": [
661 {
662 "issue_type": "stale_import",
663 "file": "src/billing.py",
664 "description": "imports compute_total from src/utils.py but compute_total was removed"
665 }
666 ]
667 }
668 ```
669
670 ---
671
672 ### `muse code invariants`
673
674 Enforce architectural rules declared in `.muse/invariants.toml`.
675
676 ```
677 muse code invariants
678 muse code invariants --commit HEAD~5
679 muse code invariants --json
680 ```
681
682 **Rule types:**
683
684 #### `no_cycles`
685 ```toml
686 [[rules]]
687 type = "no_cycles"
688 name = "no import cycles"
689 ```
690 The import graph must be a DAG. Reports every cycle as a violation.
691
692 #### `forbidden_dependency`
693 ```toml
694 [[rules]]
695 type = "forbidden_dependency"
696 name = "core must not import cli"
697 source_pattern = "muse/core/"
698 forbidden_pattern = "muse/cli/"
699 ```
700 Files matching `source_pattern` must not import from files matching `forbidden_pattern`.
701
702 #### `layer_boundary`
703 ```toml
704 [[rules]]
705 type = "layer_boundary"
706 name = "plugins must not import from cli"
707 lower = "muse/plugins/"
708 upper = "muse/cli/"
709 ```
710 Files in `lower` must not import from files in `upper` (enforces layered architecture).
711
712 #### `required_test`
713 ```toml
714 [[rules]]
715 type = "required_test"
716 name = "all billing functions must have tests"
717 source_pattern = "src/billing.py"
718 test_pattern = "tests/test_billing.py"
719 ```
720 Every public function in `source_pattern` must have a corresponding test function in `test_pattern` (matched by bare name).
721
722 **Bootstrapping:** If `.muse/invariants.toml` does not exist, `muse code invariants` creates it with a commented template and exits with a guided onboarding message.
723
724 ---
725
726 ## 8. Semantic Versioning
727
728 Muse automatically assigns semantic version bumps at commit time based on the `StructuredDelta`.
729
730 ### `SemVerBump`
731
732 ```python
733 SemVerBump = Literal["major", "minor", "patch", "none"]
734 ```
735
736 ### Inference rules (`infer_sem_ver_bump`)
737
738 | Change type | Bump | Breaking? |
739 |---|---|---|
740 | Delete a public symbol | `major` | yes — address added to `breaking_changes` |
741 | Rename a public symbol | `major` | yes — old address added to `breaking_changes` |
742 | `signature_only` change | `major` | yes — callers may break |
743 | Insert a new public symbol | `minor` | no |
744 | `impl_only` change (body only) | `patch` | no |
745 | `metadata_only` change | `none` | no |
746 | Formatting-only change | `none` | no |
747 | Non-public symbol changes | `patch` or `none` | no |
748
749 **Public** = name does not start with `_` and kind is `function`, `class`, `method`, or `async_function`.
750
751 ### Storage
752
753 Both `StructuredDelta` and `CommitRecord` carry:
754 - `sem_ver_bump: SemVerBump` (default `"none"`)
755 - `breaking_changes: list[str]` (default `[]`)
756
757 These fields are backward-compatible — pre-v2 commits read as `"none"` / `[]`.
758
759 ### `muse log` display
760
761 When a commit's `sem_ver_bump` is non-`none`, long-form `muse log` output appends:
762 ```
763 SemVer: MAJOR
764 Breaking: src/billing.py::compute_total, src/billing.py::Invoice (+2 more)
765 ```
766
767 ---
768
769 ## 9. Call-Graph Tier Commands
770
771 ### `muse code impact ADDRESS [OPTIONS]`
772
773 Transitive blast-radius analysis — what else breaks if this function changes?
774
775 ```
776 muse code impact src/billing.py::compute_total
777 muse code impact src/billing.py::compute_total --commit HEAD~5
778 muse code impact src/billing.py::compute_total --json
779 ```
780
781 **Algorithm:** BFS over the reverse call graph (Python only via `ast`). Traverses until the transitive closure is exhausted, annotating each affected symbol with its depth.
782
783 **Risk levels:** 🟢 (0–2 callers), 🟡 (3–9 callers), 🔴 (10+ callers).
784
785 ---
786
787 ### `muse code dead [OPTIONS]`
788
789 Dead code detection — symbols with no callers and no importers.
790
791 ```
792 muse code dead
793 muse code dead --kind function
794 muse code dead --exclude-tests
795 muse code dead --json
796 ```
797
798 **Detection logic:** A symbol is a dead-code candidate when:
799 1. Its bare name appears in no `ast.Call` node in the snapshot **and**
800 2. Its module is not imported anywhere in the snapshot.
801
802 **Distinction:** `definite_dead` (module never imported) vs `soft_dead` (module imported but function never called directly).
803
804 ---
805
806 ### `muse code coverage CLASS_ADDRESS [OPTIONS]`
807
808 Class interface call-coverage — which methods of a class are actually called?
809
810 ```
811 muse code coverage src/billing.py::Invoice
812 muse code coverage src/billing.py::Invoice --show-callers
813 muse code coverage src/billing.py::Invoice --json
814 ```
815
816 **Output:** Lists every method of the class, marks which ones appear in `ast.Call` nodes anywhere in the snapshot, and prints a coverage percentage. No test suite required.
817
818 ---
819
820 ### `muse code deps ADDRESS_OR_FILE [OPTIONS]`
821
822 Import graph + call-graph analysis.
823
824 ```
825 muse code deps src/billing.py
826 muse code deps src/billing.py --reverse
827 muse code deps src/billing.py::compute_total
828 muse code deps src/billing.py::compute_total --reverse
829 muse code deps src/billing.py --commit v1.0 --json
830 ```
831
832 **File mode:** Lists all `import`-kind symbols from the file (what does it import?). With `--reverse`: which other files import this one.
833
834 **Symbol mode** (`address` contains `::`): Python-only call extraction — which functions does this function call? With `--reverse`: which functions call this one.
835
836 ---
837
838 ### `muse code find-symbol [OPTIONS]`
839
840 Cross-commit, cross-branch symbol search by hash, name, or kind.
841
842 ```
843 muse code find-symbol --hash a3f2c9
844 muse code find-symbol --name compute_total
845 muse code find-symbol --name compute_* --kind function
846 muse code find-symbol --hash a3f2c9 --all-branches --first
847 muse code find-symbol --name validate --json
848 ```
849
850 **Flags:**
851 - `--hash HEX` — match `content_id` prefix (exact body match across history)
852 - `--name NAME` — exact name or prefix glob with `*`
853 - `--kind KIND` — restrict to symbol kind
854 - `--all-branches` — also scan all branch tips in `.muse/refs/heads/`
855 - `--first` — deduplicate on `content_id`, keeping only the first appearance
856 - `--json` — structured output
857
858 ---
859
860 ### `muse code patch ADDRESS SOURCE [OPTIONS]`
861
862 Surgical semantic patch — replace exactly one named symbol in the working tree.
863
864 ```
865 muse code patch src/billing.py::compute_total new_impl.py
866 echo "def compute_total(x): return x * 2" | muse code patch src/billing.py::compute_total -
867 muse code patch src/billing.py::compute_total new_impl.py --dry-run
868 muse code patch src/billing.py::compute_total new_impl.py --json
869 ```
870
871 **Syntax validation:** Before writing, validates the replacement source with:
872 - `ast.parse` for Python
873 - `tree-sitter` CST error-node check for all 11 supported languages
874
875 Rejects the patch and exits non-zero if the source has syntax errors.
876
877 **Flags:**
878 - `--body, -b FILE` *(required)* — file containing the replacement source (`-` for stdin)
879 - `--dry-run, -n` — print what would change without writing
880 - `--json` — emit result as JSON for agent consumption
881
882 **JSON output:**
883
884 ```json
885 {
886 "address": "src/billing.py::compute_total",
887 "file": "src/billing.py",
888 "lines_replaced": 12,
889 "new_lines": 9,
890 "dry_run": false
891 }
892 ```
893
894 **Security:** The file path component of ADDRESS is validated via `contain_path()` before any disk access. Paths that escape the repo root (e.g. `../../etc/passwd::foo`) are rejected with exit 1.
895
896 ---
897
898 ### `muse grep PATTERN [OPTIONS]`
899
900 Search the typed symbol graph by name — not file text. Every result is a real symbol declaration; no false positives from comments, string literals, or call sites.
901
902 ```
903 muse grep validate
904 muse grep "^handle" --regex
905 muse grep Invoice --kind class
906 muse grep compute --language Go
907 muse grep total --commit HEAD~5
908 muse grep validate --json
909 ```
910
911 **Flags:**
912
913 | Flag | Short | Description |
914 |---|---|---|
915 | `--regex, -e` | | Treat PATTERN as a Python regex (default: substring match) |
916 | `--kind KIND, -k` | | Restrict to symbols of this kind (function, class, method, …) |
917 | `--language LANG, -l` | | Restrict to files of this language (Python, Go, …) |
918 | `--commit REF, -c` | | Search a historical commit instead of HEAD |
919 | `--hashes` | | Include 8-char content-ID prefix in output |
920 | `--json` | | Emit results as JSON |
921
922 **JSON output:**
923
924 ```json
925 [
926 {
927 "address": "src/auth.py::validate_token",
928 "kind": "function",
929 "name": "validate_token",
930 "qualified_name": "validate_token",
931 "file": "src/auth.py",
932 "lineno": 14,
933 "language": "Python",
934 "content_id": "cb4afa1234567890..."
935 }
936 ]
937 ```
938
939 **Security:** Patterns are capped at 512 characters to prevent ReDoS. Invalid regex syntax is caught and reported as exit 1 rather than crashing.
940
941 ---
942
943 ### `muse code-check [COMMIT] [OPTIONS]`
944
945 Enforce semantic code invariants against a commit snapshot.
946
947 ```
948 muse code-check # check HEAD
949 muse code-check abc1234 # check specific commit
950 muse code-check --strict # exit 1 on any error-severity violation
951 muse code-check --json # machine-readable JSON output
952 muse code-check --rules my_rules.toml # custom rules file inside the repo
953 ```
954
955 **Flags:**
956
957 | Flag | Description |
958 |---|---|
959 | `COMMIT` | Commit ID to check (default: HEAD) |
960 | `--strict` | Exit 1 when any error-severity violation is found |
961 | `--json` | Emit machine-readable JSON |
962 | `--rules FILE` | Path to a TOML invariants file **inside the repo** (default: `.muse/code_invariants.toml`) |
963
964 **Security:** `--rules FILE` is validated via `contain_path()` — paths that escape the repo root are rejected with exit 1.
965
966 ---
967
968 ## 10. Architecture Internals
969
970 ### Module Map
971
972 ```
973 muse/
974 plugins/code/
975 plugin.py MidiPlugin → CodePlugin (MuseDomainPlugin + StructuredMergePlugin)
976 ast_parser.py Python AST → SymbolRecord; validate_syntax() for all 11 languages
977 symbol_diff.py diff_symbol_trees() — O(n) diffing, rename/move annotation
978 _query.py symbols_for_snapshot(), walk_commits(), language_of()
979 _predicate.py Predicate DSL parser — tokenise → recursive descent → Predicate callable
980 _callgraph.py ForwardGraph, ReverseGraph, build_*, transitive_callers BFS
981 _refactor_classify.py classify_exact(), classify_composite(), RefactorClassification
982 core/
983 coordination.py Reservation, Intent, create/load helpers, .muse/coordination/
984 indices.py SymbolHistoryIndex, HashOccurrenceIndex, save/load/rebuild
985 ```
986
987 ### Language Support
988
989 | Language | Extension(s) | Parser | Symbol types |
990 |---|---|---|---|
991 | Python | `.py` | `ast` (stdlib) | function, async_function, class, method, variable, import |
992 | JavaScript | `.js` `.jsx` `.mjs` `.cjs` | tree-sitter | function, class, method |
993 | TypeScript | `.ts` `.tsx` | tree-sitter | function, class, method, interface, type_alias, enum |
994 | Go | `.go` | tree-sitter | function (method qualified as `Type.Method`) |
995 | Rust | `.rs` | tree-sitter | function (impl method qualified as `Type.method`) |
996 | Java | `.java` | tree-sitter | class, interface, method, constructor, enum |
997 | C | `.c` `.h` | tree-sitter | function_definition |
998 | C++ | `.cpp` `.cc` `.cxx` `.hpp` | tree-sitter | function, class, struct |
999 | C# | `.cs` | tree-sitter | class, interface, struct, method, constructor, enum |
1000 | Ruby | `.rb` | tree-sitter | class, module, method, singleton_method |
1001 | Kotlin | `.kt` `.kts` | tree-sitter | function, class, method |
1002
1003 ### Layer Rules
1004
1005 - `muse/core/*` is domain-agnostic — never imports from `muse/plugins/*`
1006 - `muse/cli/commands/*` are thin — delegate all logic to `muse/core/*` or plugin helpers
1007 - `muse/plugins/code/*` is the only layer that imports domain-specific AST logic
1008 - `muse/core/coordination.py` and `muse/core/indices.py` are domain-agnostic helpers
1009
1010 ---
1011
1012 ## 11. Type Reference
1013
1014 ### `SymbolRecord` (TypedDict)
1015
1016 ```python
1017 class SymbolRecord(TypedDict):
1018 kind: str # function | class | method | variable | import | …
1019 name: str # bare name
1020 qualified_name: str # dotted path (e.g. MyClass.save)
1021 lineno: int
1022 end_lineno: int
1023 content_id: str # SHA-256 of full normalized AST
1024 body_hash: str # SHA-256 of body only
1025 signature_id: str # SHA-256 of signature only
1026 metadata_id: str # SHA-256 of decorators + async + bases (v2, "" for pre-v2)
1027 canonical_key: str # {file}#{scope}#{kind}#{name}#{lineno} (v2, "" for pre-v2)
1028 ```
1029
1030 ### `StructuredDelta`
1031
1032 ```python
1033 class StructuredDelta(TypedDict):
1034 domain: str
1035 ops: list[DomainOp]
1036 summary: str
1037 sem_ver_bump: SemVerBump # default "none"
1038 breaking_changes: list[str] # default []
1039 ```
1040
1041 ### `DomainOp` union
1042
1043 ```python
1044 DomainOp = InsertOp | DeleteOp | ReplaceOp | MoveOp | PatchOp
1045 ```
1046
1047 Each op is a `TypedDict` discriminated by a `Literal` `"op"` field.
1048
1049 ### `ConflictRecord` (dataclass)
1050
1051 ```python
1052 @dataclass
1053 class ConflictRecord:
1054 path: str
1055 conflict_type: str = "file_level"
1056 ours_summary: str = ""
1057 theirs_summary: str = ""
1058 addresses: list[str] = field(default_factory=list)
1059 ```
1060
1061 ### `Reservation`
1062
1063 ```python
1064 class Reservation:
1065 reservation_id: str
1066 run_id: str
1067 branch: str
1068 addresses: list[str]
1069 created_at: datetime
1070 expires_at: datetime
1071 operation: str | None
1072 def is_active(self) -> bool: ...
1073 def to_dict(self) -> dict[str, str | int | list[str] | None]: ...
1074 @classmethod
1075 def from_dict(cls, d) -> Reservation: ...
1076 ```
1077
1078 ### `Intent`
1079
1080 ```python
1081 class Intent:
1082 intent_id: str
1083 reservation_id: str
1084 run_id: str
1085 branch: str
1086 addresses: list[str]
1087 operation: str
1088 created_at: datetime
1089 detail: str
1090 def to_dict(self) -> dict[str, str | int | list[str]]: ...
1091 @classmethod
1092 def from_dict(cls, d) -> Intent: ...
1093 ```
1094
1095 ### `SemVerBump`
1096
1097 ```python
1098 SemVerBump = Literal["major", "minor", "patch", "none"]
1099 ```
1100
1101 ### `Predicate`
1102
1103 ```python
1104 Predicate = Callable[[str, SymbolRecord], bool]
1105 # first arg: file_path
1106 # second arg: SymbolRecord
1107 # returns: True if the symbol matches the predicate
1108 ```
1109
1110 ### `ExactClassification`
1111
1112 ```python
1113 ExactClassification = Literal[
1114 "rename", "move", "rename+move",
1115 "signature_only", "impl_only", "metadata_only",
1116 "full_rewrite", "unchanged",
1117 ]
1118 ```
1119
1120 ### `InferredRefactor`
1121
1122 ```python
1123 InferredRefactor = Literal["extract", "inline", "split", "merge", "none"]
1124 ```
1125
1126 ---
1127
1128 ## Further Reading
1129
1130 - [Plugin Authoring Guide](plugin-authoring-guide.md) — implementing `MuseDomainPlugin`
1131 - [Type Contracts](type-contracts.md) — strict typing rules and enforcement
1132 - [CRDT Reference](crdt-reference.md) — CRDT and OT merge primitives
1133 - [Demo — Code](../demo/demo-code.md) — full narrative walkthrough of all code commands
1134 - [Demo — MIDI](../demo/midi-demo.md) — MIDI domain demo walkthrough