@mu-crew/mu 3.13.3 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. package/AGENTS.md +73 -35
  2. package/README.md +71 -57
  3. package/dist/chunk-5HECDBK3.js +4482 -0
  4. package/dist/chunk-5HECDBK3.js.map +1 -0
  5. package/dist/chunk-AW2NONYU.js +301 -0
  6. package/dist/chunk-AW2NONYU.js.map +1 -0
  7. package/dist/chunk-GQNHIPJN.js +4060 -0
  8. package/dist/chunk-GQNHIPJN.js.map +1 -0
  9. package/dist/{chunk-3BARPJLY.js → chunk-PZ5GOCWH.js} +16 -2
  10. package/dist/chunk-PZ5GOCWH.js.map +1 -0
  11. package/dist/chunk-S424D5EP.js +13766 -0
  12. package/dist/chunk-S424D5EP.js.map +1 -0
  13. package/dist/{cli-CKOMHBXV.js → cli-GB2CAVVW.js} +6776 -4350
  14. package/dist/cli-GB2CAVVW.js.map +1 -0
  15. package/dist/cli.js +2 -3
  16. package/dist/cli.js.map +1 -1
  17. package/dist/errors-KHOYKD7D.js +65 -0
  18. package/dist/extension/mu-pi.js +1617 -836
  19. package/dist/extension/mu-pi.js.map +1 -1
  20. package/dist/index.d.ts +4015 -2561
  21. package/dist/index.js +614 -297
  22. package/dist/index.js.map +1 -1
  23. package/dist/tui-ALPHYTA2.js +1773 -0
  24. package/dist/tui-ALPHYTA2.js.map +1 -0
  25. package/docs/ARCHITECTURE.md +117 -66
  26. package/docs/VOCABULARY.md +112 -49
  27. package/docs/guide/README.md +3 -3
  28. package/docs/guide/backends.md +8 -4
  29. package/docs/guide/cleanup.md +61 -33
  30. package/docs/guide/delegate.md +141 -66
  31. package/docs/guide/dispatch.md +148 -53
  32. package/docs/guide/getting-started.md +37 -44
  33. package/docs/guide/other-agent-clis.md +102 -0
  34. package/docs/guide/recovery.md +31 -50
  35. package/docs/guide/remote.md +115 -45
  36. package/docs/guide/sql.md +104 -24
  37. package/docs/guide/stop-a-worker.md +30 -37
  38. package/docs/guide/sync.md +11 -3
  39. package/docs/guide/upgrade.md +93 -43
  40. package/docs/reference/env.md +23 -32
  41. package/docs/reference/naming.md +12 -7
  42. package/docs/reference/surface-contract.md +153 -0
  43. package/package.json +33 -11
  44. package/prompts/mu-debug.md +3 -1
  45. package/prompts/mu-refute.md +2 -1
  46. package/prompts/mu-research.md +3 -1
  47. package/prompts/mu-review.md +2 -1
  48. package/prompts/mu-rules-audit.md +5 -4
  49. package/prompts/mu-sweep.md +2 -1
  50. package/prompts/mu-tournament.md +4 -2
  51. package/prompts/mu-until.md +3 -1
  52. package/prompts/ultrathink.md +1 -1
  53. package/skills/mu/SKILL.md +60 -291
  54. package/skills/mu/recipes/adversarial-review.md +12 -103
  55. package/skills/mu/recipes/brief.md +14 -61
  56. package/skills/mu/recipes/course-correct.md +23 -0
  57. package/skills/mu/recipes/findings.md +17 -111
  58. package/skills/mu/recipes/models.md +17 -92
  59. package/skills/mu/recipes/move.md +14 -0
  60. package/skills/mu/recipes/orchestrator-loop.md +20 -119
  61. package/skills/mu/recipes/recovery.md +8 -29
  62. package/skills/mu/recipes/remote-workers.md +16 -318
  63. package/skills/mu/recipes/tasks-or-calls.md +15 -78
  64. package/skills/mu/recipes/test-health.md +19 -77
  65. package/skills/mu/recipes/worker.md +17 -24
  66. package/dist/chunk-45KQLMEL.js +0 -2908
  67. package/dist/chunk-45KQLMEL.js.map +0 -1
  68. package/dist/chunk-G434JKV2.js +0 -641
  69. package/dist/chunk-G434JKV2.js.map +0 -1
  70. package/dist/chunk-KUGUFBWH.js +0 -12527
  71. package/dist/chunk-KUGUFBWH.js.map +0 -1
  72. package/dist/chunk-QITLNZEM.js +0 -87
  73. package/dist/chunk-QITLNZEM.js.map +0 -1
  74. package/dist/chunk-VIEYQMQA.js +0 -393
  75. package/dist/chunk-VIEYQMQA.js.map +0 -1
  76. package/dist/chunk-WI6AWCMK.js +0 -19
  77. package/dist/chunk-WI6AWCMK.js.map +0 -1
  78. package/dist/cli-CKOMHBXV.js.map +0 -1
  79. package/dist/devtools-LS4ZEPWM.js +0 -3772
  80. package/dist/devtools-LS4ZEPWM.js.map +0 -1
  81. package/dist/tui-LFJ5P4Y2.js +0 -40956
  82. package/dist/tui-LFJ5P4Y2.js.map +0 -1
  83. package/dist/tui-sync-worker.js +0 -17
  84. package/dist/tui-sync-worker.js.map +0 -1
  85. package/dist/yank-XDOONQSL.js +0 -13
  86. package/dist/yank-XDOONQSL.js.map +0 -1
  87. package/docs/ROADMAP.md +0 -123
  88. package/docs/VISION.md +0 -298
  89. package/docs/architecture/control-socket.md +0 -142
  90. package/docs/architecture/dag.md +0 -117
  91. package/docs/architecture/mux.md +0 -167
  92. package/docs/architecture/ops-log.md +0 -160
  93. package/docs/architecture/sdk.md +0 -187
  94. package/docs/architecture/sync.md +0 -159
  95. package/docs/architecture/tui.md +0 -165
  96. package/docs/guide/tui.md +0 -98
  97. package/docs/history/CHANGELOG-pre-3.md +0 -4866
  98. package/docs/img/tui-dashboard.png +0 -0
  99. package/docs/specs/2026-09-28-agent-state-from-murmur.md +0 -333
  100. package/docs/specs/2026-09-29-task-substates.md +0 -247
  101. package/docs/specs/2026-10-03-keep-driving-nudge.md +0 -163
  102. package/docs/specs/2026-10-21-fast-and-slow-gates.md +0 -229
  103. package/skills/mu/recipes/backlog-triage.md +0 -45
  104. package/skills/mu/recipes/codemode-driver.md +0 -118
  105. package/skills/mu/recipes/deep-research.md +0 -69
  106. package/skills/mu/recipes/drift-audit.md +0 -75
  107. package/skills/mu/recipes/fan-out.md +0 -42
  108. package/skills/mu/recipes/hypothesis-panel.md +0 -48
  109. package/skills/mu/recipes/long-run.md +0 -32
  110. package/skills/mu/recipes/loop-until-done.md +0 -45
  111. package/skills/mu/recipes/plan.md +0 -67
  112. package/skills/mu/recipes/refute.md +0 -55
  113. package/skills/mu/recipes/review-panel.md +0 -65
  114. package/skills/mu/recipes/rules-audit.md +0 -89
  115. package/skills/mu/recipes/tournament.md +0 -44
  116. package/skills/mu/recipes/ultrathink.md +0 -86
  117. package/skills/mu/recipes/watcher.md +0 -28
  118. package/skills/mu/recipes/waves.md +0 -21
  119. /package/dist/{chunk-3BARPJLY.js.map → errors-KHOYKD7D.js.map} +0 -0
@@ -3,7 +3,7 @@
3
3
  mu is one SQLite file, a crew of agents in multiplexer panes, a task
4
4
  DAG, and one append-only op log that everything else derives from.
5
5
  Callers sit on top, a shared TypeScript core in the middle, and SQLite,
6
- the multiplexer and the VCS at the base. The CLI verbs and the SDK are
6
+ the multiplexer and git at the base. The CLI verbs and the SDK are
7
7
  thin facades over the same core.
8
8
 
9
9
  Terms: [VOCABULARY.md](VOCABULARY.md) (source of truth). Principles:
@@ -18,11 +18,11 @@ Terms: [VOCABULARY.md](VOCABULARY.md) (source of truth). Principles:
18
18
  ▼ ▼
19
19
  ┌─────────────────────────────────────────────────────────────────┐
20
20
  │ mu core (shared TS modules) │
21
- │ agents/ mux/ ctl/ · tasks/ tracks · vcs/ workspace/ · ops log │
22
- │ (capture, apply, undo, sync, drift) │
21
+ │ agents/ mux/ ctl/ · tasks/ tracks · worktree · ops log │
22
+ │ (capture, apply, sync, drift) │
23
23
  └───────────────┬──────────────────┬──────────────────┬───────────┘
24
24
  ▼ ▼ ▼
25
- SQLite (~/.local/state/mu/mu.db) · tmux/herdr panes · jj/sl/git
25
+ SQLite (~/.local/state/mu-next/mu.db) · tmux/herdr panes · git worktrees
26
26
  pi agents: control socket per agent
27
27
  ```
28
28
 
@@ -31,7 +31,7 @@ Terms: [VOCABULARY.md](VOCABULARY.md) (source of truth). Principles:
31
31
  **The ops log.** Every write to a portable table (`workstreams`,
32
32
  `tasks`, `task_edges`, `task_notes`) is recorded as an op by a SQLite
33
33
  trigger in the same transaction. The tables are the materialized view
34
- reads hit; the log is the record. History (`mu log`), undo, sync,
34
+ reads hit; the log is the record. History (`mu log`), sync,
35
35
  rebuild and drift checks are all queries or replays over it. Ops carry
36
36
  only changed columns and an HLC, which makes per-field merge across
37
37
  machines free. Read this first:
@@ -56,142 +56,194 @@ herdr, and murmur for other CLIs on tmux:
56
56
  **The control socket.** mu's pi extension serves a unix socket inside
57
57
  each spawned pi. Send, `--fresh`, wait, abort and state for pi agents
58
58
  go through it, exactly, with no screen reading and no fallback to
59
- pasting. Remote agents use an ssh socket forward printed by
60
- `mu agent remote-env`. The tmux bracketed-paste path serves the
61
- [cases listed there](architecture/control-socket.md#no-silent-fallback).
59
+ pasting. Remote agents (`spawn --host`) use a socket forward their
60
+ host profile's shell template carries. Non-pi agents are sent to only on herdr
61
+ ([architecture/control-socket.md](architecture/control-socket.md#no-silent-fallback)).
62
62
 
63
63
  **Sync.** With `MU_SYNC_DIR` set, every `mu` invocation ingests peer
64
64
  JSONL segments before the verb and flushes its own ops after it,
65
65
  through `handle()`. There is no daemon and no network code:
66
66
  [architecture/sync.md](architecture/sync.md).
67
67
 
68
- **The TUI.** Bare `mu` on a TTY opens a read-only ink dashboard of 10
69
- cards with drill-down popups. It polls SQL every second and
70
- subprocesses every 10 seconds, yanks `mu` commands instead of running
71
- them, and is the only code that imports ink:
72
- [architecture/tui.md](architecture/tui.md).
68
+ **Dashboard.** The 3.x card TUI is removed. Bare `mu` on a TTY (not `--json`) opens the fresh read-only ink dashboard, a lazy chunk (`src/cli/tui/`) the SDK index never reaches; anywhere else it prints help. Projections run on a `readonly` connection; ambient sync runs on the launcher's separate writable connection, outside render. Five tabs (Overview, Tasks, DAG, Agents, Log) over a workstream row; Enter drills a row (notes, scrollback, one log op); `a` on an agent runs the mux's attach steps with Ink's terminal suspended, else opens the scrollback with the reason.
73
69
 
74
70
  **CLI and SDK surface.** Each operation is a typed SDK function plus a
75
71
  thin Commander wrapper. Public functions take operator-facing names and
76
72
  resolve them to surrogate ids exactly once. Typed errors map to exit
77
- codes 0-7. The same page covers testing layers and packaging:
73
+ codes 0-7; a wait or a stream adds 130/143 on a signal. The same page covers testing layers and packaging:
78
74
  [architecture/sdk.md](architecture/sdk.md).
79
75
 
80
76
  ## Module map
81
77
 
82
78
  `src/` is flat at the root, with one level of cohesive clusters.
83
79
  Cluster files import from neighbours and root modules, never from the
84
- hub that re-exports them. `src/tasks.ts`, `src/agents.ts`, `src/mux.ts`,
85
- `src/vcs.ts` and `src/workspace.ts` are re-export hubs.
80
+ hub that re-exports them. `src/tasks.ts`, `src/agents.ts` and `src/mux.ts`
81
+ are re-export hubs.
86
82
 
87
83
  ### Storage and the ops log
88
84
 
89
85
  | Module | Responsibility |
90
86
  | --- | --- |
91
- | `src/db.ts` | better-sqlite3 connection (WAL); schema v11 (12 tables, 3 views) in `applySchema`; installs capture; owns `SYNCED_ENTITIES`, `PORTABLE_TABLES`, `MACHINE_LOCAL_TABLES`; refuses older (`SchemaTooOldError`) or newer (`SchemaTooNewError`) DBs, exit 4 |
87
+ | `src/db.ts` | better-sqlite3 connection (WAL); schema 13: `upgradeSchema` brings a new or incomplete v13 file current in ONE transaction (DDL, `ADDED_COLUMNS`, schema stamp, capture triggers and capture stamp together; both stamps re-read under the lock); 26 tables (`schema_version`, `machine_identity`, `op_ctx`, `op_dying`, `ops`, `workstreams`, `agents`, `worktrees`, `tasks`, `task_edges`, `task_notes`, `task_substates`, `task_auto`, `sync_peers`, `sync_fingerprints`, and the machine-local `delegate_calls`, `wait_cursors`, `agent_settles`, `host_profiles`, `host_channels`, `spawn_receipts`, `human_needs`, `wait_registrations`, `wait_action_receipts`, `wait_resumes`, `remote_recovery`) and 4 views (`ready`, `blocked`, `blockers`, `goals`); owns `TASK_KINDS`; `SqliteTooOldError` below 3.42; owns `SYNCED_ENTITIES`, `PORTABLE_TABLES`, `MACHINE_LOCAL_TABLES`; refuses older (`SchemaTooOldError`) or newer (`SchemaTooNewError`) DBs, exit 4 |
92
88
  | `src/compare.ts` | code-unit string compare; replaces `localeCompare` so ICU is never initialised |
93
89
  | `src/hlc.ts` | hybrid logical clock, serialized as sortable TEXT |
94
- | `src/capture.ts` | TEMP triggers recording each portable write as an op, and refusing natural-key changes |
95
- | `src/op-context.ts` | `withOpContext` (intent, actor, group); `withCaptureSuppressed` echo guard |
96
- | `src/apply.ts` | apply one op: per-field LWW, tombstones, substate pair repair, `reprojectDeferredOps` |
97
- | `src/undo.ts` | inverse ops for one group; refuses a superseded group |
98
- | `src/rebuild.ts` | replay the whole log into a new DB file |
90
+ | `src/capture.ts` | Triggers stored in the DB file (versioned by `capture_version`) recording each portable write as an op, refusing natural-key changes and explicit ids <= 0 or above 2^53-1; `installCapture` (own transaction) and `installCaptureLocked` (inside the caller's, used by `upgradeSchema`); `captureDoctorCheck` |
91
+ | `src/op-context.ts` | `withOpContext` (intent, actor, group) and the `withCaptureSuppressed` echo guard; both own the transaction around the `op_ctx` row |
92
+ | `src/fold.ts` | the one definition of a portable row: pure per-key folds of the ops log (horizon, per-field LWW including `kind` and `wave`, substate, notes, edges) |
93
+ | `src/apply.ts` | apply one op: re-fold the keys it touches and make their rows equal the fold; `projectLog` folds every key |
94
+ | `src/human-needed.ts`, `src/cli/human-needed.ts` | `recordHumanNeeded` / `openHumanNeeds`: a `human` task (OPEN/parked, brief = reason) plus the machine-local `human_needs(task_id, caller)` row in one transaction; `mu human-needed <reason>`. `HumanTaskError` (tasks/errors.ts) refuses unpark, claim and the CLOSE block on such a task |
95
+ | `src/wait-actions.ts`, `src/wait-action-receipts.ts` | stall actions of a wait (`handleStall` implementations). `releaseStall` / `releaseStallHandler`: the guarded release, every guard rechecked in the SAME immediate transaction as `releaseTask(expectedOwnerId, ifRevision)` (no await inside; a refusal writes nothing). `wait_action_receipts` storage: `reserveActionReceipt` (INSERT OR IGNORE on task, owner, incarnation, action), `transitionActionReceipt` (compare-and-set along legal edges only). Pure SQL. `steerStall` / `steerStallHandler`: the crash-safe steer: plan and reserve in one immediate transaction (the msgId is saved with the row), then ctl hello and incarnation bind, then the transport's `beforeWrite` fence re-runs every guard and CASes `reserved -> sending` in one synchronous immediate transaction: only the CAS winner writes. A row past `reserved` is only probed (`status {msgId}`, same registry id, pane and incarnation), never resent, never turned into a release. `waitWithActions`: `waitForEvents` plus the handler `onStall` names (a composition, not a loop) |
96
+ | `src/wait-registration.ts` | `wait_registrations`: `registerOwnedWait` (typed refs that exist now, checked in its transaction; merged and deduped), `beginWait`/`endWait` (one active wait per caller: generation compare-and-set, delivered refs and their baselines only removed), per-agent-ref unconsumed baselines (`baselines_json`); `dropOwnedRefs`/`callersOwing` for the done-agent drop; every write one short transaction, never across an await; writes `wait_cursors` only to clear a DONE mark (new work armed, ref dropped), never a consumed token |
97
+ | `src/wait.ts` | `waitForEvents`: the ONE wait loop (tasks, agents, delegates, the caller's registration; with no ref it also discovers what the caller spawned, by `agents.spawner`, and registers it with a zero run baseline; `scope` narrows what is owned to one workstream; `onStall: warn` flags and goes on). Each iteration: `beforePoll`, bounded `ambientIngest`, DB reads, one batched agent read, CLOSE application, pick; then races an agent ctl watch, the poll sleep, abort and the deadline (every await raced, no transaction across one). first/all, urgent events, engine-owned stall detection with a raced `handleStall`, the callers SET (union of owned refs, delegates, human needs), typed cursor delivery in one transaction guarded by each caller's registration generation, flush on return. There is no other wait code: `mu task wait`, `mu agent wait` and `mu wait` all call it (`setWaitSleepForTests` is its sleep seam) |
98
+ | `src/wait-types.ts` | the types and errors of `waitForEvents`: `WaitRef`, `WaitEvent`, `WaitSnapshot`, `WaitResult`, `WaitOptions`, `AgentReading`, `AgentWatchHandle`, `WaitStallContext`, `StallActionResult`, `AgentBaseline`, `WaitNothingError`, `WaitRefError`, `WAIT_HEARTBEAT_CAP_MS` |
99
+ | `src/wait-callers.ts` | `currentCallers`: the managed `<ws>/<agent>` and `pi:<session>` of this process, from env, never a guess |
100
+ | `src/wait-cursor.ts` | `wait_cursors` access: typed keys (`<kind>:<ws>/<id>`, and `input|`, `dead|`, `stall|`, `open|` + key for urgent observations and blockers seen open), completion tokens, monotonic `recordCursors`, `isConsumed`, `logStallOnce` (one `agent.stall` op per task and owner (row, incarnation, runs): a `stall-log|` mark under a reserved caller) |
101
+ | `src/wait-item.ts` | the wait loop's `Item` (what one watched ref can deliver now) and the pure helpers that need none of the loop's state: `refKey`, `urgency`, `exitFor`, `inputToken`, `agentSnapshot`, `delegateItem`, `sameDeath` (a dead task and its dead owner are one event), `failedGate`, `snapshotsOf` |
102
+ | `src/wait-latch.ts` | the settled latch of a waited agent: `AgentTrack`, `supersededBy` (THE rule: a latch is valid only for the exact agentId + incarnation + runs it captured; another identity rewatches from run 0, a later run from the latched run), `rearmLatch` (the one way a latch is dropped) |
103
+ | `src/wait-db.ts` | the engine's loop-free DB helpers: `closeSettledRun` (applies a settled run's CLOSE, never throws), `agentExists`, `openBlockers` (uncapped transitive open blockers) |
104
+ | `src/wait-owed.ts` | when an owned agent ref is still owed a wait and when it is done: `agentOwed` (a reading + open/closed task counts + the run fence -> owed / done / unsure), the DONE mark (`setAgentDone`, `hasDoneMark`: a `done\|agent:<ws>/<name>` row in `wait_cursors`) that the engine keeps true each tick and `agent close` sets, `runInFlight`. A registered agent whose row is gone and which is marked done is dropped, never `dead` |
105
+ | `src/wait-ctl.ts` | `ctlAgentHooks`: the default `readAgents` (one `readAgentStates` pass) and `watchAgent` (`watchAgentRun`; counts a registered ref, explicit or owned, from max(fence, same-process cursor), else from the probed run count (`unknownBaseline`)) |
106
+ | `src/cli/wait.ts` | `mu wait` (`wireWaitCommand`, `cmdWait`) and the core `mu task wait` shares (`runWait`, `parseWaitRef`, `installWaitSignals`). SIGINT/SIGTERM listeners are installed before the DB opens and aborted into the engine's final snapshot; the result is emitted once, then the exit follows (`CliExitError`, drained by `handle`). Typed refs only (a bare word is refused); `--first` default, `--all`, `--until-text`, `--status`, `--stuck-after`, `--on-stall exit\|warn\|steer\|release` (`runWait` calls `waitWithActions`; the handler is wrapped to print one stderr line naming what was done), `--wave`, `--blockers-of`, `--timeout` (default and cap 1500 s). One stderr heartbeat a minute; stdout is exactly one envelope. `beforePoll` runs the one `reconcile` per workstream that has agents; a DB-only wait never opens a mux |
107
+ | `src/cli/wait-arm.ts` | producer arming: `armWait` (registers under `currentCallers()[0]`, never throws: a refusal is a warning), `armSend` (a delivered prompt: the agent ref fenced by the measured runs + incarnation + registry id; mux sends unfenced; a ctl send with no runs arms only the extra refs), `agentRef`, `taskRef`. Called by `cmdSpawn`, `cmdSpawnTask`, `cmdSend`, `cmdSendInterrupt`, `cmdClaim`; `send --wait` and `claim --and-wait` then call `runWait` with the receipt as `extraData` |
108
+ | `src/cli/wait-output.ts` | the wait's one JSON `data`: `firing` (= `event`), `exit`, `timedOut`, `interrupted`, `open`, `snapshots`, `tally` (+ `summary`), `cursor`, `resume` (the stored handle `mu wait --resume <id>`, or null with `resumeWhy`; `owedRefs` is what the handle must keep), bounded to 4096 FINAL bytes with `humanNeeds`/`warning` inside the bound: texts shrink first (tail kept, `lastTextCut`), then the needs, the tally's findings, and the snapshots (`pageRecords`, `snapshotsCut`, `retrieve`); firing, exit, resume and `open` never |
109
+ | `src/wait-resume.ts` | `wait_resumes` (machine-local): `saveResume` (one short transaction; rows older than 14 days go with the next save), `loadResume`, `WaitResumeNotFoundError`; the stored refs (run baseline and pi incarnation as last seen), mode, filters, deadline and callers behind `mu wait --resume <id>` |
110
+ | `src/wait-regex.ts` | the bounded `--until-text` regex: a grammar that refuses exponential shapes, an 8 KiB tail and a 50 ms `node:vm` budget; `WaitRegexError` |
111
+ | `src/rebuild.ts` | copy the whole log into a new DB file, then `projectLog` it |
99
112
  | `src/compact.ts` | `mu db compact` (blank redundant note-tombstone payloads) and `mu db forget` (drop a torn-down workstream's ops); this machine only |
100
- | `src/legacy-ops.ts` | classifier for historical log-only intents; rebuild copies them unprojected |
101
113
  | `src/drift.ts` | cheap invariant (every row has an op) and `--deep` rebuild-diff |
102
114
  | `src/logs.ts` | typed reader over `ops`; `appendLog` and `emitEvent`, the writes triggers cannot cover |
103
- | `src/log-render.ts` | the one op-to-prose formatter (`renderOp`), shared by CLI and TUI |
115
+ | `src/log-render.ts` | the one op-to-prose formatter (`renderOp`), shared by the CLI renderers; never clips free text; an unknown historical intent renders generically (`op put|del`, intent, kind, changed field names) |
104
116
 
105
117
  ### Sync and doctor checks
106
118
 
107
119
  | Module | Responsibility |
108
120
  | --- | --- |
109
121
  | `src/segments.ts` | JSONL segments: flush own ops, ingest peers from a watermark |
122
+ | `src/sync-dir-guard.ts` | `assertSyncDirV2`: refuses a `MU_SYNC_DIR` holding a segment that opens with a v1 line or a manifest with `v` < 2 (a 3.x peer); called inside `flushSegment`/`discoverPeers`/`ingestSegment` and in `handle()`; `SyncDirForeignError`, exit 4 |
123
+ | `src/segment-line.ts` | the segment line: per-line version rules (`SEGMENT_FORMAT_VERSION`, `SEGMENT_READ_VERSIONS`), crc framing, encode and decode |
110
124
  | `src/segment-manifest.ts` | segment `.manifest` sidecar: read, write, seal, and the self-consistency check the owner's flush runs before trusting it |
111
125
  | `src/segment-resume.ts` | byte offset + running sha256 saved with a peer's caught-up fingerprint, so ingest reads only appended bytes |
112
126
  | `src/sha256-resumable.ts` | SHA-256 with a serialisable running state, so the segment manifest hashes only appended bytes |
113
127
  | `src/sync.ts` | peer status, the ambient hook, `--from`, `--repair` |
114
- | `src/file-lock.ts` | cross-process advisory lock through atomic `fs.mkdir` |
115
- | `src/fleet-hazards.ts` | mixed-fleet checks: DB inside `MU_SYNC_DIR`, network mount, case collisions |
128
+ | `src/file-lock.ts` | cross-process advisory lock through atomic `fs.mkdir`; `strict` publishes whole by rename, reclaims a dead holder's lock under an O_EXCL claim, fails closed; release by token |
129
+ | `src/fleet-hazards.ts` | mixed-fleet checks: DB inside `MU_SYNC_DIR` (plus `DbInsideSyncDirError`, which `handle()` throws before `openDb`), network mount, case collisions, own segment written by a newer mu |
116
130
  | `src/dormant.ts` | doctor housekeeping: `finished` (all closed, idle 14 days) vs `abandoned` (idle 60 days, open tasks) workstreams; idle derived from `MAX(tasks.updated_at)` |
117
131
  | `src/disk-recon.ts` | report-only state-dir vs DB reconciliation (rows without dirs, dirs without rows, residue); `--disk` measures bytes |
118
- | `src/doctor-summary.ts` | cheap doctor slice for the TUI; ctl, extension and skill checks; remediation text |
132
+ | `src/cli/doctor.ts` | `mu doctor` (human and `--json`): environment, DB integrity, injected scope, per-workstream counts and report-only reconcile drift, disk reconciliation |
133
+ | `src/doctor-summary.ts` | `DoctorCheck` row shape; murmur, pi-extension and ctl-socket checks used by `mu doctor` |
119
134
 
120
135
  ### Agents, multiplexer and control socket
121
136
 
122
137
  | Module | Responsibility |
123
138
  | --- | --- |
124
- | `src/agents.ts` | CRUD, send, read, list, close, liveness, reaper; `composeAgentTitle` |
125
- | `src/agents/spawn.ts` | spawn: CLI resolution, pane create or reuse, liveness, readiness, ctl handshake, rollback |
139
+ | `src/agents.ts` | CRUD (incarnation CAS), send, read, list, liveness, reaper; hub re-exporting close |
140
+ | `src/agents/spawn.ts` | spawn: CLI resolution, `mux.spawnAgent`, liveness, readiness, ctl handshake, rollback |
141
+ | `src/agents/staging.ts` | dead `--worktree` stagings (a SIGKILLed spawn's placeholder row + unused worktree): `deadStagings` for doctor, `reclaimDeadStagings` at spawn |
126
142
  | `src/agents/spawn-lock.ts` | per-session lock around topology and row insert |
127
- | `src/agents/transport.ts` | send routing: ctl for pi agents, mux paste otherwise, no fallback; `expectsCtl` |
143
+ | `src/agents/lock.ts` | per-agent lock (`withAgentLock`): fail-closed and strict; taken by close and `--worktree` staging/rollback; `withSpawnAttemptLock`: the same strict lock per (task, attempt key) |
144
+ | `src/agents/reap.ts` | `reapAgentRow`: the one row-level reaper step, inside the caller's transaction and op group: record the remote worktree, DELETE the row, free its host channel, reopen what it owned with a `[reaper]` note. Used by `deleteAgent` and by teardown |
145
+ | `src/db-recovery.ts` | DDL only, spliced into `CURRENT_SCHEMA`: the `remote_recovery` table and its triggers (AFTER DELETE on `agents`, BEFORE DELETE on `workstreams` for the cascade): the database records a remote worktree whenever its row dies, however it is deleted |
146
+ | `src/remote-recovery.ts` | `remote_recovery` reader and guards: `listRemoteRecoveries`, `selectRemoteRecovery` (sole record, or `--recovery`; several refuse), `assertRecoveryUsable` / `assertRecoveryProfile` / `assertRecoveryUnchanged`, CAS `deleteRemoteRecovery` / `setRecoveryCollectedHead`, `RemoteRecoveryRefusedError`. One row per original registry incarnation, no FK, no op; `mu rebuild` reports it lost |
147
+ | `src/agents/gate.ts` | remote command gates: `gateWrapper` (the `{cmd}` that runs the command and writes `MU_GATE <token> <rc>` atomically on the host), `readGate` (pane proven exited via `mux.paneResult`, channel freed by id + pane, then one bounded `exec` read; running / exited / dead / unknown), `cleanGateResult` (close removes only its token's files) |
148
+ | `src/agents/receipts.ts` | `spawn_receipts` storage: reserve, patch, reset, read (pure SQL) |
149
+ | `src/agents/task-spawn.ts` | `spawnForTask`: `agent spawn --task` as one journaled attempt (plus `spawnAutoAttempt`, the executor's guarded `auto:<n>` replay: reservation required, a failed key is never reset, permission re-checked before the pane and before the send): reserve, spawn (identity journaled in the spawn lock), claim for that registry id, send with the reserved msgId, pin the brief; unsent failures roll back only their own resources, unknown delivery keeps pane and claim; replay never resends |
150
+ | `src/agents/close.ts` | `previewClose`, `closeAgent`: preview, `--if` fence, replacement check, incarnation CAS on the delete |
151
+ | `src/agents/close-recovery.ts` | close of a removed agent's remote worktree: `recoveryPreview` (read only), `closeRecovery` (original aside, CAS before the removal, delete only that record) |
152
+ | `src/agent-output.ts` | the agent `--json` boundary (pure): `agentToOutput`, `liveAgentToOutput`, `closePreviewToOutput`. SQL column names, `id` = the registry incarnation, real `workstream_id`; live facts snake_cased and only when read. The CLI maps; `AgentRow` stays camelCase |
153
+ | `src/agents/transport.ts` | send routing: ctl for pi agents; herdr `sendToPane` for others; tmux refuses (`AgentSendUnsupportedError`); no fallback; `expectsCtl`; the opt-in `ctlGuard` (signal, timeout, `beforeWrite`) a stall steer uses, refused on a route that cannot honour it |
128
154
  | `src/agents/abort.ts` | stop a pi turn through the control socket; abort + send (`--interrupt`) |
129
155
  | `src/agents/delegate.ts` | `delegateOutcome`: one wait result to `done` / `empty` / `error` / `died` / `timeout` / `pending` |
130
- | `src/agents/wait.ts` | block until task-less agents leave `busy` |
131
- | `src/agents/adopt.ts` | register an existing pane as an agent |
132
- | `src/agents/kick.ts` | `mu agent kick`: signal a wedged pane's foreground process group |
156
+ | `src/agents/observe.ts` | `AgentObservation`: the one typed read of an agent (registry id + pane, ctl incarnation, settled run count, `settled`, text + `lastTextTruncated`, pending, source/since, session file); `observeAgentStates` (one `readAgentStates` pass), `watchAgentRun` (the ctl probe + `wait` + drop fallback `mu agent wait` uses), `settledCloseInput` (the `applySettledClose` input, undefined unless ctl-settled). A seam, not a second poll loop |
133
157
  | `src/agents/errors.ts` | typed agent errors |
134
- | `src/exec-file.ts` | `execFileCapture`: child_process runner shared by the tmux, herdr, kick and murmur executors (no execa) |
158
+ | `src/exec-file.ts` | `execFileCapture` (+ `execGroupCapture`: own process group, reaped before it settles): child_process runner shared by the tmux, herdr and murmur executors (no execa) |
135
159
  | `src/agent-state.ts` | runtime state from ctl, herdr or murmur; `unknown` with a reason |
136
160
  | `src/reconcile.ts` | ghost prune and orphan surfacing |
137
161
  | `src/mux/index.ts` | cluster barrel that `src/mux.ts` re-exports |
138
- | `src/mux/types.ts` | `MuxBackend` interface; `MuxError`, `PaneNotFoundError`, `NoMultiplexerError` |
162
+ | `src/mux/types.ts` | `MuxBackend` interface (`ensureSession`, `spawnAgent`, `listPanes`, `paneAlive`, `capture`, `attachCommands`), `attachShellLine`; `MuxError`, `PaneNotFoundError`, `NoMultiplexerError` |
139
163
  | `src/mux/detect.ts` | `MU_MUX` → `HERDR_ENV` → `$TMUX` → `PATH` ladder; `activeMux()` |
140
- | `src/mux/tmux.ts` | tmux backend: the `tmux()` wrapper, bracketed-paste send, pane validation |
141
- | `src/mux/herdr.ts` | herdr backend: JSON socket API, atomic send, native pane status, `startAgentInPane` |
142
- | `src/mux/input-timing.ts` | when tmux paste input can be submitted; not a state source |
164
+ | `src/mux/tmux.ts` | tmux backend: the `tmux()` wrapper, one-step `spawnAgent`, pane validation; no send |
165
+ | `src/mux/herdr.ts` | herdr backend: JSON socket API, atomic send, native pane status, `spawnAgent` (bare pane, `agent start`) |
143
166
  | `src/tmux.ts` | tmux-only re-exports: `MU_TMUX_SOCKET` seam, `sleep` / `setSleepForTests` |
144
167
  | `src/ctl/path.ts` | `ctlSocketPath`, `remoteCtlSocketPath`, `MU_CTL_SOCK` |
145
168
  | `src/ctl/protocol.ts` | protocol v1 types, `CTL_OPS`, line framing; imports nothing from mu |
146
- | `src/ctl/client.ts` | `ctlRequest`, `ctlProbe`, version and unknown-op errors |
169
+ | `src/ctl/client.ts` | `ctlRequest` (opt-in synchronous `beforeWrite` fence: `CtlWriteVetoedError`, nothing written; an abort after the write of a side-effecting op is `CtlDeliveryUnknownError`), `ctlProbe`, version and unknown-op errors |
147
170
  | `extension/mu-pi.ts` | pi extension, child side: serves `$MU_CTL_SOCK`; built to `dist/extension/mu-pi.js` |
148
- | `extension/delegate.ts` | pi extension, parent side: `mu_delegate` / `mu_delegate_cancel`, which shell out to `mu` |
149
- | `extension/nudge.ts` | pi extension: the keep-driving nudge. Arms on a `mu` dispatch, checks `mu task list --status IN_PROGRESS --json` at `agent_before_settle`, injects the SKILL.md rule once per prompt, logs `--kind nudge`. Also the close nudge (worker settles owning a task) and the refute nudge (`task claim --for` on a task with no refute decision in `mu task notes --json`; one notice, no continuation). `parallelSettle` runs the three settle checks in parallel behind one `agent_before_settle` handler and merges their entries; `mu log` breadcrumbs are not awaited |
150
- | `src/link.ts` | `mu link pi`: extension shim, skill symlink, `inspectLinks` for doctor |
171
+ | `src/delegates.ts` | delegate calls as durable tasks: `registerDelegateCall` (kind=delegate task + brief + `delegate_calls` row, one transaction, idempotent per `call_id`), `markDelegate` (admission CAS, including the refused to queued reissue the live runtime makes), `finishDelegate` (the one recorder: verdict note, report, close, state in one op group; a failed record is `failed`, never `done`), `listDelegates`, `verdictTally`; typed `DelegateCallConflictError`, `DelegateStateError`, `DelegateNotFoundError` |
172
+ | `src/delegate-verdict.ts` | `parseVerdict`, `recordNote`, `formatElapsed`: a delegate answer as a verdict. Pure and dependency-free, shared with the standalone extension |
173
+ | `src/delegate-process.ts` | the mux-free delegate runner: `runDelegateProcess` runs one `queued`/`starting` call in a child `pi --mode rpc` (ctl socket, log, session and an attempt receipt derived from the call id; send once with a stable msgId and expected incarnation; `unknown` is never resent; `workspace` through `src/worktree.ts`) and finishes it with `finishDelegate`. No pane, no agents row |
174
+ | `src/cli/delegates.ts` | `mu delegates` (list, show, tally, register, mark, finish, run; `reissue` refuses and names the tool) |
175
+ | `extension/delegate.ts` | pi extension, parent side: `mu_delegate` / `mu_delegate_reissue` / `mu_delegate_cancel`: admission (running, queued, refused), spawn through `agent spawn --task` (on `NoMultiplexerError` only: `mu delegates run <call-id>`, a child pi with no pane), the wait, the record (`mu delegates finish` before the pane closes) and the **wake**: answers no settle showed (`DelegateHandle.delivered`) get one pointer message when pi is idle, resent once if pi's queue loses it, dropped with a note on Escape; all state changes go through `mu delegates` |
176
+ | `extension/delegate-calls.ts` | the extension's wrappers over `mu delegates register / mark / finish / show` |
177
+ | `extension/delegate-format.ts`, `delegate-process-format.ts`, `delegate-args.ts`, `delegate-trace.ts`, `mu-run.ts` | pure wording of a gap's report, the wake and the dropped-wake note; the mux-free delegate's tool result and failure wording; label and model arguments; the `MU_DELEGATE_TRACE` log; the `mu` runner seam (per-call env, opt-in SIGTERM drain on abort) and envelope readers |
178
+ | `extension/nudge.ts` | pi extension: the keep-driving **settle wait** and the close nudge. Every completed `agent_before_settle` reads the caller identities' open human needs (`mu state --me`, one per identity) and runs `mu wait --first --on-stall exit --json` as this session (managed identity + `PI_SESSION_ID` through the runner's per-call env); an event continues the turn with the wait's result under the SKILL.md rule; a `delegate` event adds that call's report (`mu task notes`, capped, with a pointer), and one of a wave keeps waiting while siblings run, then adds ONE **wave summary**. Esc, input, a dialog or shutdown aborts only that wait, which drains through SIGTERM. Holds (`input`, `wake`, `blocked`) skip a wait's start, never a consumed result's entries. Close nudge: a worker settles owning a task. `parallelSettle` runs the settle checks in parallel behind one handler and merges their entries (a consumed wait result survives a late hold); `mu log` breadcrumbs are not awaited |
179
+ | `src/pi-migrate.ts`, `src/pi-known-skills.ts`, `src/cli/pi.ts` | `mu pi migrate`: move the old link-pi shim and skill link aside, only when ownership is proven (byte-exact generated shim template, link into this install, released-skill digest). pi loads mu as a package: `package.json` `pi` manifest |
151
180
 
152
- ### Tasks, workspaces and workstreams
181
+ ### Tasks, worktrees and workstreams
153
182
 
154
183
  | Module | Responsibility |
155
184
  | --- | --- |
156
185
  | `src/tasks/core.ts`, `id.ts` | row shapes, id resolution, qualified ids |
157
- | `src/tasks/queries.ts`, `sort.ts` | list, next, owned-by; ROI, recency, age and id sorts |
186
+ | `src/tasks/queries.ts`, `sort.ts` | list, next, owned-by; `selectTaskNotes` (default notes view), `listNotes` (full history); ROI, recency, age and id sorts |
158
187
  | `src/tasks/edit.ts`, `edges.ts` | add and edit; block, unblock, reparent, delete with cycle check |
188
+ | `src/task-auto.ts` | `mu task auto`: `authorizeTasks` (one op-context transaction: local `task_auto` row + synced `dispatch_host`), `autoPermitted`, `authorisedReady`, `openBlockersClosure`; never DELETEs a `task_auto` row |
189
+ | `src/auto-attempt.ts` | the executor's attempt accounting: `planAttempt` (read-only, also the dry run), `reserveAttempt` (charge one run + `task_auto.last_spawn` + `reserved` receipt `auto:<n>` in one immediate transaction that re-plans first), `reserveRetry` (charge a run and reserve an IN-PLACE retry of a live worker), `endAttempt` (terminal `abandoned` / `needs_human`), `pendingAttempts` (un-ended stored attempts to resume before anything new); `last_spawn` stores the exact prompt |
190
+ | `src/task-brief.ts` | `buildTaskBrief`: first brief verbatim + latest report + one task/CLOSE footer, the executor stores its text so a replay never rebuilds; `TaskBriefMissingError` |
191
+ | `src/edit-distance.ts` | `editDistance`: Levenshtein, for the did-you-mean hints (unknown verb, wrong column, unknown workstream). Leaf module. |
192
+ | `src/messages.ts` | the keyed message table: `MESSAGES["<domain>.<what>"]` (a function of the facts it names) and `say(key, ...facts)`. Every error/hint line an agent reads lives here; classes, exit codes and structured fields stay at the throw site and never read the prose. Leaf module (imports nothing). Migrated: task, agent, db, mux, sync, host, remote, workstream, worktree, log, delegate. |
193
+ | `src/next-hints.ts` | `computeNext(db, {callers, workstream?, outcome?})`: the ONE rule set behind every `next` (`NextPlan`), read-only (never waits, registers, ingests or runs a move). Outcome moves: the exact wait a send owes, the receipt check of an unknown delivery (only while the same registry row stands; never a resend), a gate's wait, the brief a `claim --for` owes, a preview's apply with `--if <rev>`, the stored `mu wait --resume`. State moves (skipped while a caller has an open human need): `mu next -w <ws>` over authorised work, the bare wait over owned refs (only after a verb that armed one: `ctx.armed`). `renderArgv` (POSIX words), `renderPlan` (safe before destructive, unguarded destructive refused), `capPlan` (a seq is dropped whole). `courseCheckHint` / `computeNotices`: the one-line "course check due" notice (10 closed or 24 h since the last `COURSE CHECK` note). Wired by `src/cli/handle.ts` (`finalNext`) <!-- doc-cli-drift:skip --> |
194
+ | `src/next.ts` | `executeNext`: recover (slice B), resume stored attempts, then spawn authorised ready tasks through `spawnAutoAttempt`; finite effect cap (counts every effect: reaps, retries, a spawn rolled back by the guard); ends unreconcilable attempts once; dry run; this caller's human need is checked before each effect; `hold` waits (`waitWithActions`, bounded) and recovers deaths and API errors; `signal` stops between effects |
195
+ | `src/next-recover.ts` | the recovery scan: `observeWorkers` (a status read plus the ctl `wait` that carries `lastError`, bounded), `inFlightWorkers`, `scanRecovery` (dead local → reap, live API error → retry in place, else nothing or `attention`; remote never), `exhaustedAfterDeath`, `liveWorkers` (the worker-ceiling count) |
196
+ | `src/next-refute.ts` | `executeNext --refute` (slice C): `nextCheck` (CLOSED/done permitted attempts with no call, or this caller's unfinished call), `runCheck` (register `starting` → `spawnForTask` → `running`; or read once and `finishDelegate`), `checkerRefs`; the call id `refute:<ws>:<task>:<attempt>` is the once-key |
197
+ | `src/cli/next.ts` | `mu next`: signals before the DB, one bounded envelope (`nextData`), exit mapping (`nextExit`) |
198
+ | `src/close-block.ts` | the CLOSE block of a settled pi run: `parseCloseBlock` (one grammar: last column-0 `CLOSE:` line outside a fence, exact marker lines, escaped evidence) and `applySettledClose` (report note, close note and status flip in ONE op group, once per (agent, incarnation, runs) through `agent_settles`; refuses delegate and human tasks; malformed reads "no close (malformed)") |
159
199
  | `src/tasks/claim.ts` | atomic claim and release; `resolveWorkerIdentity` |
160
200
  | `src/tasks/lifecycle.ts`, `status.ts` | close, open and cascade; `TaskStatus` |
161
- | `src/tasks/wait.ts`, `errors.ts` | `waitForTasks`; typed task errors |
201
+ | `src/tasks/errors.ts` | typed task errors |
162
202
  | `src/tracks.ts` | union-find tracks with diamond merge |
163
- | `src/dag.ts` | `loadFullDag`, `renderForest`, `renderTaskTree` for `mu task tree` and the DAG popup |
164
- | `src/workstream.ts` | ensure, list, summarize, destroy |
165
- | `src/vcs/*.ts` | `VcsBackend` with git, jj, sl and none; detection `jj` → `sl` → `git` → none |
166
- | `src/workspace/*.ts` | per-agent workspace registry: CRUD, staleness and dirty decoration, orphans |
167
- | `src/staleness.ts` | `WORKSPACE_STALE_THRESHOLD` and `isWorkspaceStale` |
168
- | `src/project-root.ts` | `detectProjectRoot` for the TUI launch ladder |
169
- | `src/state.ts` | `mu state` seam: fast SQL tier, slow subprocess tier, merge; parses `REMOTE:` notes |
203
+ | `src/dag.ts` | `loadOpenTaskFrontier` (the bounded open DAG: open tasks expand, CLOSED are leaves, depth and node caps, one SQL read per level; `task tree` and the dashboard read the same query), `renderFrontierLines` |
204
+ | `src/workstream-scope.ts` | the one never-trimming validator for a workstream name typed as scope (`-w`, a ref prefix, a record's `workstream`); shared by the CLI and the pi extension |
205
+ | `src/revision.ts` | task revision (`taskRevision`: derived on read from the task row, owner, both edge directions and note count; no counter, no LIKE), `agentRevision` (derived from the agent incarnation, row, owned tasks and worktree rows), `RevisionMismatchError`, `withTaskFence` (one `--if` check around a whole invocation) |
206
+ | `src/workstream.ts` | ensure, list, summarize, revision, soft teardown, reuse of a torn-down name |
207
+ | `src/vcs/git.ts` | git primitives (plain functions, git only): toplevel, `worktree add --detach` / remove, dirty files, the upstream ref set, `isAncestor`, `createBranch` |
208
+ | `src/worktree.ts` | per-agent worktrees: `createWorktree`, `removeWorktree`, `worktreeState`, the machine-local `worktrees` registry (outlives the agent; the reaper never touches it), the one close rule (`releaseWorktree`: park at `<path>.mu-removing`, scan holders with `worktreeHolders` (/proc, else lsof): a writer or an unprovable scan refuses unless `--force`, a cwd or reader is a warning; re-judge, then pin a create-only branch and remove), name reuse (`reclaimWorktreePath`); `WorktreeDirtyError`, `WorktreeHeldError`, `WorktreeBranchExistsError`, `WorktreePathNotEmptyError`, `WorktreeVcsRequiredError` |
209
+ | `src/state.ts` | `stateSnapshot`: the canonical READ-ONLY state read (SQL column names; no reconcile, no reap, no write; async only for ctl probes) of tasks (ordered by raw `MAX(ops.seq)` activity), registry agents (host/cwd/base/collected_head), the caller's own delegate queue and waves (`verdictTally(..., caller)`), and its open human needs; `mu state`, `mu me` and the dashboard share it |
210
+ | `src/dashboard-data.ts` | `loadDashboard`: the dashboard's ONE read-only projection (live workstreams incl. scratch; every task by status with qualified keys and raw `MAX(ops.seq)` activity; open frontier; registry agents with owned task; canonical log items; the caller's own human needs and delegates, empty for a null caller). No reconcile, reap, ingest or write. `next` is `computeNext` over the caller set (`computed`; `unavailable` only when it throws), and an agent's `tool` is the ctl status's running tool with `elapsed_ms` at read (null = none, absent = old extension or no ctl). `attention` (dead/permission/input/stalled agents, OPEN human tasks of any recorder, next unavailable, sync warnings) reads every live workstream and ignores every filter. `scopeWorkstream` reads tasks, edges and frontier of the shown workstream only (agents, log, caller panels and attention stay machine-wide; human tasks and task owners come from one SQL pass each), so a tick costs the shown workstream, not the whole database |
170
211
 
171
212
  ### CLI, output and packaging
172
213
 
173
214
  | Module | Responsibility |
174
215
  | --- | --- |
175
216
  | `src/main.ts` | the `mu` bin (`dist/cli.js`): enables the compile cache, then `import()`s `src/cli.ts` |
176
- | `src/compile-cache.ts`, `src/state-dir.ts` | best-effort V8 compile cache under `<state>/compile-cache`; builtins-only state-dir resolution |
217
+ | `src/compile-cache.ts`, `src/state-dir.ts` | best-effort V8 compile cache under `<state>/compile-cache`; builtins-only state-dir resolution (default `<state home>/mu-next`) |
177
218
  | `src/cli.ts` | Commander wiring (`buildProgram`, `runCli`) |
219
+ | `src/cli/dashboard-launch.ts`, `src/cli/tui/*` | bare `mu`: `openDashboardOrHelp` (TTY and not `--json` -> `handle()` guards + writable open + lazy `import("./tui/index.js")`, else help). `tui/runtime.ts`: the RO projection connection (fast reads take agent states from the slow tier's cache: no socket or subprocess) plus single-flight, throttled, quiet `ambientIngest` on the writable one (its latest warnings are kept and passed to every read as `syncWarnings`, so a `sync` attention item lasts until an ingest comes back clean) and `close()` drains and closes it; `use-snapshot.ts`: the ONE poll loop (3.x tiers: fast SQL-only tick, 1 s, `+`/`-` live; slow tick, 10 s, re-probes agent states into the runtime's cache; flicker guard: a byte-equal snapshot key publishes nothing, `nonce` moves only on change and drills re-read on it, `slowNonce` re-captures scrollback), sequential and cancellable; `app.tsx`: the view, whose query is the shown workstream plus the Log tail (the Tasks status and text filters are client-side; CLOSED rows are read only while that filter asks for them) (workstream row, five tabs, attention strip, filters, framed full-height list, footer pinned to the last row, keys); `overview.tsx`: the Overview tab's card grid (`overviewCards` from the other tabs' rows, laid out by `grid.ts` in `frame.tsx` frames); `rows.ts`: the pure model (initial workstream, per-tab rows and their drill targets, filters, cursor by key, Next line); `text.ts`: clean/cut/pad by visual width (`fitLine` for styled lines); `theme.ts`: the one palette (3.x colours as named roles -> ink props; `statusRole`, `agentStateRole`; NO_COLOR drops colour, keeps bold/dim/inverse), so no view names a colour; `columns.ts`: protect/clip column widths over the caller's rows (the snapshot, never the filtered window); `frame.tsx`: rounded frames, title/count in the top border and `+N more` in the bottom, exactly width x height; `grid.ts`: Overview card geometry (1/2/3 columns at <120/120/180 cols, fixed placement table, min/max row budgets with spare rows to cards hiding data, `fitColumn`); `drill.tsx`: the read-only full-screen drills, exactly `rows` lines with the hint last (visual-width wrap, clamped scroll, guarded loads, OSC 52 yank), live on the loop's nonces (previous body kept during a reload, scroll kept for the same entity, follows at the end); `nav.ts`: the one list/body key map (j/k, Ctrl-d/u, PgUp/PgDn, g/gg, G) shared by the tabs and the drills; `commands.ts`: the only things the UI runs or copies (`planAttach` over `mux.attachCommands`, `runAttach` ordered required/optional steps, `yankSequence`); `index.ts`: `runDashboard`; `dashboard-shutdown.ts`: the one idempotent teardown (SIGINT/SIGTERM/SIGHUP, uncaught error, Ink exit, boot error) ending in the quiet final `ambientFlush`. Imports only from root files, never `cli.ts` |
178
220
  | `src/cli/*.ts` | one file per verb namespace, thin wrappers over the SDK; `format.ts` renders tables, `handle.ts` maps errors to exit codes and runs ambient sync |
179
- | `src/cli/tasks/*.ts` | the `mu task` namespace, including `mu me tasks` / `mu me next` |
180
- | `src/cli/dispatch-hints.ts` | dispatch-time `Next:` hints for pi agents (`--fresh`, `--interrupt`, abort) |
221
+ | `src/cli/tasks/*.ts` | the `mu task` namespace, including `mu me tasks` / `mu me next`. `records.ts` is the task read boundary: `taskToOutput` (SQL column names, `id` = `local_id`, real `workstream_id`/`owner_id`), `taskRecordCut`/`taskItemCut` (one escaped line <= 400 characters, `cut=N` for what it clipped), `taskPage`/`pageRecords`/`footerLine` (a result serialized and grown to <= 4 KB; the `more=N cut=N next=<same command> --all` footer is reserved first and never sliced); `list`, `next`, `owned-by`, `show` and `tree` all go through it. `mu state` and `mu me` keep the table formatter. `wire.ts` holds every `task.command(...)` definition |
222
+ | `src/cli/log.ts`, `src/cli/log-records.ts`, `src/cli/page-records.ts` | `mu log`: `log-records.ts` is the read boundary (one escaped record <= 400 characters, or a `LogItem` keyed by `ops` columns, bounded to 2 KB; `shrinkItem` bounds every item field; `logPage` pages the final bytes with min 0, `more`/`cut` name `mu log --seq A..B` / `--after S` reads, digits only; `--seq`/`--after` take no scope or filter, each record whole); `--tail` is always NDJSON (`cmdLogTail`: row written, then cursor moves; a failed write throws `TailStopped` carrying the cursor; SIGINT 130, SIGTERM 143). `page-records.ts` is the generic pager `mu workstream list [--torn-down]` shares (`--all` uncapped) |
223
+ | `src/cli/db-query.ts` | `mu db query` (alias `mu sql`) and `mu db --shell`. Reads by default: without `--write` a statement `stmt.readonly` calls a write (EXPLAIN excepted) is refused, and the connection runs `query_only`; one op-context scope per write; `guardPragma` allows only read-only PRAGMAs before any compile; one `EXPLAIN` per statement (`checkStatement`) refuses transaction control (`AutoCommit`/`Savepoint`), ATTACH/DETACH, all DDL (a write to any `sqlite_master` root page, TEMP included) and any write to `op_ctx`/`op_dying`/`ops`/`schema_version`/`machine_identity`/`sync_peers` outside a main `_cap_*` trigger, a statement's own write to `sqlite_sequence`, and an UPDATE of `id`/`rowid` on the four guarded tables (a rowid-changing `Delete` with no table name); `installScopedViews` (TEMP `ready`/`blocked`/`blockers`); `teachColumnError`; output is JSON by default (`--table` for prose; `isSqlVerbArgv` in `src/output.ts` makes parse-time errors JSON too) and a read is `{items,count,more,retrieve?}` paged by `pageRecords` (min 0) over the final document bytes (`sqlRowsPayload`), `--all` whole. `src/cli/db.ts` keeps `backup`, `compact`, `forget` |
224
+ | `src/cli/state.ts`, `src/cli/me-identity.ts` | `mu state` / `mu me` (one JSON document, no card): scope (variadic/comma/repeated `-w`, `--all`, `--me`), explicit reconcile for `-w` reads, bounding to 4096 final bytes (lists shrink, then workstreams page, min 0; a cut names `mu state ... --full`). `me-identity.ts` is the one identity path: the injected `<ws>/<name>` row, else `pi:<session>`, never `$TMUX_PANE` (`mu me tasks` / `next` use its managed-agent form) |
225
+ | `src/cli/sync.ts` | `mu sync`: peer status, `--from`, `--repair` over `src/sync.ts` |
181
226
  | `src/cli/stdin.ts` | `-` as a text argument: reads note / send text from stdin (heredoc prose) |
182
- | `src/cli/agents-remote.ts` | `mu agent remote-env`: prints the ssh forward and env, runs nothing |
183
- | `src/cli/tui-launch-focus.ts` | initial-tab focus ladder for bare `mu` and `mu state --tui` |
184
- | `src/cli/tui-load.ts` | lazy `import()` of the TUI with `NODE_ENV` defaulted to `production` (React prod build) |
185
- | `src/cli/tui/` | the ink TUI; the only place ink and react are imported |
186
- | `src/cli/tui/sync-worker.ts` | worker_thread (`dist/tui-sync-worker.js`) running the TUI's slow-tick sync pass off ink's thread |
187
- | `src/output.ts`, `src/shell-quote.ts` | `printNextSteps` / `errorNextSteps`; POSIX quoting for hints |
227
+ | `src/hosts.ts` | host profiles: the template grammar (`expandHostTemplate`, one pass, per-context quoting, whitelisted `{env}`, `{name:sh}` second layer), profile CRUD, `host_channels` admission (`reserveHostChannel` / `bindHostChannel` / `releaseHostChannel` by token, `withHostChannel`, transient vs attached), `admitHostChannel` (spawn reserves before its pane opens), `checkHost`. ssh-free: runs only the user's stored templates with `/bin/sh -c`, each as its own process group (`execGroupCapture`, reaped before its channel is released; the group id is recorded on the `host_channels` row so a dead caller cannot free a slot under a live transport) |
228
+ | `src/cli/host.ts` | the host verbs (add, list, show, rm, init, check) |
229
+ | `src/remote-agent.ts` | the remote half of `spawn --host`: `prepareRemote` (one `exec` makes a detached remote worktree or resolves the cwd, answer is a tagged line), `removeRemoteWorktree` (the spawn-rollback helper: clean and at its base only, `--force`), `closeRemoteWorktree` (the close rule: observe, apply, rename the tree aside, prove no process holds it via `/proc` or `lsof`, re-check HEAD and cleanliness, `worktree remove` without `--force`; `--keep-branch`), `remoteShellCommand` (the filled `shell` template as the local pane's command; five whitelisted identity vars), `localPaneAlive` (a retained exited pane is not alive: it holds no channel) |
230
+ | `src/remote-collect.ts` | `collectRemoteAgent`: the `fetch` template run in the LOCAL repo under one channel, the fetched commit resolved from FETCH_HEAD (cleared first) and pinned under `refs/mu/collect/<ws>/<agent>`, `agents.collected_head` set by compare-and-set on id, pane, host, cwd and base |
231
+ | `src/cli/agent-close.ts` | `mu agent close`, recovery-aware scope (`closeScope`: live agent or recovery record) |
232
+ | `src/cli/agent-collect.ts` | `mu agent collect` |
233
+ | `src/output-contract.ts` | the `--json` wire: `Envelope<T>` (`{ok,data,next}` / `{ok:false,error,message,next,exit}`), `NextPlan`, `encodeEnvelope`, `isEnvelope`. Pure. `cli.ts` `emitJson` wraps every payload once; `handle.ts` `emitError` prints the one failure document on stdout; both take `next` from `finalNext()` (the ONE finalisation: `computeNext` over the DB `handle()` holds, the verb's `setNextOutcome` and the scope `resolveWorkstream` records, fitted to the 4 KB result by `fitNext`; human text gets one dim `next:` line); `emitJsonRow` is the raw NDJSON row (`mu log --tail`, inert like an envelope). `extension/delegate.ts` `parseEnvelope` reads it |
234
+ | `src/output.ts`, `src/shell-quote.ts` | colour, `muTable`/`renderBoxTable`, JSON-mode and verb-argv probes (`isJsonMode`, `isSqlVerbArgv`, `isWaitVerbArgv`), `usageLines`; POSIX quoting for `next` commands |
188
235
  | `src/glyphs.ts` | the one glyph vocabulary (single-cell Nerd Font codepoints) |
189
236
  | `src/index.ts` | SDK entry point |
190
- | `skills/mu/` | the skill loaded into an agent's context; `mu --help` owns the verb reference |
191
- | `scripts/migrate.ts` | retained migration sidecar ([scripts/README.md](../scripts/README.md)) |
237
+ | `skills/mu/` | the skill loaded into an agent's context (`SKILL.md` ≤ 600 words, 12 recipes ≤ 250 each and ≤ 4,000 together, pinned by `test/prose-budget.test.ts`, with failure-backed markers); `mu --help` owns the verb reference; every dropped 3.x rule has a destination in [rule-destinations](history/mu-next-design/rule-destinations.md) |
192
238
  | `scripts/check-doc-links.mjs` | relative-link and anchor checker for every markdown file, run by `test/doc-links.test.ts` |
239
+ | `tools/screenshot/` | `dashboard.sh` seeds the dogfood `payments` scene on a private tmux socket, captures it at 200x40 and `ansi-to-png.py` renders the capture to `docs/img/tui-dashboard.png` (README); not shipped |
240
+ | `tools/eval/` | the eval harness: scripted scenarios and judges run against a built surface, the release matrix and the cold-guess probe; reports in [docs/eval/](eval/README.md); not shipped |
241
+ | `docs/history/` | records only: old changelogs and the [4.0 design history](history/mu-next-design/README.md) (design of record, rule destinations, release reviews, the TUI dogfood kit `dashboard.sh` runs); not shipped |
193
242
  | `test/` | vitest suites; `*.integration.test.ts` touch real tmux, herdr or VCS ([sdk.md § Testing layers](architecture/sdk.md#testing-layers)) |
194
243
 
244
+ The shipped contract is
245
+ [reference/surface-contract.md](reference/surface-contract.md).
246
+
195
247
  ## Key seams
196
248
 
197
249
  A new implementation of each is small.
@@ -199,8 +251,7 @@ A new implementation of each is small.
199
251
  | Seam | Add one by |
200
252
  | --- | --- |
201
253
  | `MuxBackend` | implementing the interface in `src/mux/`; a backend without an equivalent no-ops, and a backend-only feature is an optional method callers test for |
202
- | `VcsBackend` | implementing `detect`, `createWorkspace`, `freeWorkspace`, `isClean`, `commitsBehind`, `rebaseTo`, `commitsSinceBase`, `recentCommits`, `showCommit` (about 80-150 LOC) |
203
- | typed verb | an SDK function; a `cmd<Verb>` in `src/cli/<namespace>.ts`; one Commander block in `buildProgram()` wrapped in `handle()` and routed through `printNextSteps` |
204
- | schema change | bump `CURRENT_SCHEMA_VERSION` in `src/db.ts` and mirror `CURRENT_SCHEMA`; startup stays migration-free ([scripts/README.md](../scripts/README.md)) |
254
+ | typed verb | an SDK function; a `cmd<Verb>` in `src/cli/<namespace>.ts`; one Commander block in `buildProgram()` wrapped in `handle()`; the verb declares what it did (`setNextOutcome`) and `next` is computed for it |
255
+ | schema change | bump `CURRENT_SCHEMA_VERSION` in `src/db.ts` and mirror `CURRENT_SCHEMA`; startup stays migration-free |
205
256
  | syncable field | add the column to a portable table, extend the UPDATE trigger's changed-column comparison, confirm apply writes it; a full-row payload regresses merge to row-level LWW |
206
257
  | cross-machine sync | nothing: every invocation already flushes and ingests |