@mu-crew/mu 3.1.0 → 3.2.1

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 (43) hide show
  1. package/AGENTS.md +203 -588
  2. package/README.md +80 -308
  3. package/dist/cli.js +6106 -4716
  4. package/dist/cli.js.map +1 -1
  5. package/dist/extension/mu-pi.js +786 -0
  6. package/dist/extension/mu-pi.js.map +1 -0
  7. package/dist/index.d.ts +738 -536
  8. package/dist/index.js +6617 -5785
  9. package/dist/index.js.map +1 -1
  10. package/docs/ARCHITECTURE.md +178 -1008
  11. package/docs/ROADMAP.md +88 -169
  12. package/docs/VISION.md +206 -407
  13. package/docs/VOCABULARY.md +202 -455
  14. package/docs/architecture/control-socket.md +119 -0
  15. package/docs/architecture/dag.md +113 -0
  16. package/docs/architecture/mux.md +163 -0
  17. package/docs/architecture/ops-log.md +140 -0
  18. package/docs/architecture/sdk.md +185 -0
  19. package/docs/architecture/sync.md +129 -0
  20. package/docs/architecture/tui.md +146 -0
  21. package/docs/guide/README.md +25 -0
  22. package/docs/guide/backends.md +61 -0
  23. package/docs/guide/cleanup.md +74 -0
  24. package/docs/guide/delegate.md +87 -0
  25. package/docs/guide/dispatch.md +137 -0
  26. package/docs/guide/getting-started.md +177 -0
  27. package/docs/guide/recovery.md +133 -0
  28. package/docs/guide/remote.md +74 -0
  29. package/docs/guide/sql.md +76 -0
  30. package/docs/guide/stop-a-worker.md +64 -0
  31. package/docs/guide/sync.md +78 -0
  32. package/docs/guide/tui.md +98 -0
  33. package/docs/guide/upgrade.md +76 -0
  34. package/docs/history/CHANGELOG-pre-3.md +4866 -0
  35. package/docs/reference/env.md +87 -0
  36. package/docs/reference/naming.md +82 -0
  37. package/docs/specs/2026-09-28-agent-state-from-murmur.md +1 -1
  38. package/docs/specs/2026-09-29-task-substates.md +1 -1
  39. package/package.json +4 -4
  40. package/skills/mu/REMOTE_WORKERS.md +186 -491
  41. package/skills/mu/SKILL.md +142 -217
  42. package/docs/HANDOVER.md +0 -486
  43. package/docs/USAGE_GUIDE.md +0 -2637
package/AGENTS.md CHANGED
@@ -1,612 +1,227 @@
1
1
  # Repo guide for AI coding agents
2
2
 
3
- You're an AI coding agent working on the `mu` repo, which builds
4
- **mu** — a CLI that manages a persistent crew of AI agents in
5
- multiplexer panes (tmux or herdr) coordinated through a built-in
6
- task DAG.
7
-
8
- Read the linked docs before you write code. Follow the conventions
9
- below.
10
-
11
- ---
12
-
13
- ## Read these first (in this order)
14
-
15
- 1. **[docs/USAGE_GUIDE.md](docs/USAGE_GUIDE.md)** — what mu does
16
- from a user's perspective. ~10 minutes.
17
- 2. **[CHANGELOG.md](CHANGELOG.md)** — the upcoming version's entry.
18
- Single source of truth for the verb list, schema, env vars.
19
- 3. **[docs/VISION.md](docs/VISION.md)** — the design principles you
20
- must not violate.
21
- 4. **[docs/ROADMAP.md](docs/ROADMAP.md)** — what's next, with
22
- promotion criteria. **Read the "Anti-feature pledges" section
23
- before adding any new dep, abstraction, or surface.**
24
- 5. **[docs/VOCABULARY.md](docs/VOCABULARY.md)** — canonical terms.
25
- **Source of truth for every word** in code, docs, and error
26
- messages. If you use a term not defined there, fix the docs first.
27
- 6. **[docs/ARCHITECTURE.md](docs/ARCHITECTURE.md)** — module layout,
28
- reconciliation algorithm, TUI architecture, key seams.
29
-
30
- Design rationale for rejected and unbuilt features lives in
31
- [docs/ROADMAP.md](docs/ROADMAP.md) per item, alongside its promotion
32
- criteria.
33
-
34
- > **If you are an orchestrator** — a coding agent driving a crew of
35
- > pi worker agents on this repo via `mu` — read
36
- > **[docs/HANDOVER.md](docs/HANDOVER.md)** instead of the order
37
- > above: onboarding steps, the 8-phase dispatch loop,
38
- > conflict-resolution playbook, known gotchas, end-of-session
39
- > checklist. AGENTS.md is for workers and humans editing the repo
40
- > directly.
41
-
42
- ---
43
-
44
- ## Repo layout
3
+ This repo builds **mu**: a CLI that runs AI agents in tmux or herdr
4
+ panes, coordinated through a task DAG.
45
5
 
46
- ```
47
- mu/
48
- ├── README.md # human-user entry point
49
- ├── AGENTS.md # this file
50
- ├── CHANGELOG.md # release notes
51
- ├── docs/ # everything else
52
- │ ├── USAGE_GUIDE.md # user-facing tour (§ 5b is the TUI reference)
53
- │ ├── HANDOVER.md # orchestrator goto reset doc (8-phase loop + gotchas)
54
- │ ├── ROADMAP.md # what's next; promotion criteria; anti-feature pledges
55
- │ ├── VISION.md # design pillars
56
- │ ├── VOCABULARY.md # canonical terms (single source of truth)
57
- │ └── ARCHITECTURE.md # module layout, TUI architecture, key seams
58
- ├── src/ # all source (root files: SDK + shared infra; one
59
- │ # level of subdirs OK for cohesive clusters — see
60
- │ # `src/cli/`, `src/agents/`, `src/tasks/`, `src/mux/`)
61
- │ ├── db.ts # SQLite schema + openDb (single CREATE-IF-NOT-EXISTS block; v11)
62
- │ │ # also owns SYNCED_ENTITIES / PORTABLE_TABLES / MACHINE_LOCAL_TABLES
63
- │ ├── mux.ts # multiplexer backend hub (re-exports src/mux/*)
64
- │ ├── mux/ # cohesive cluster: one file per multiplexer
65
- │ │ ├── index.ts # re-export hub consumed by src/mux.ts
66
- │ │ ├── types.ts # MuxBackend interface + MuxError / PaneNotFoundError / NoMultiplexerError
67
- │ │ ├── detect.ts # MU_MUX → HERDR_ENV → $TMUX → availability ladder; activeMux()
68
- │ │ ├── tmux.ts # tmux impl: wrapper, 6-step send protocol, pane validation
69
- │ │ └── herdr.ts # herdr impl: JSON socket API, atomic send, native pane status
70
- │ ├── tmux.ts # back-compat re-export of the tmux backend (new code imports mux.js)
71
- │ ├── detect.ts # pi status detector + Braille-spinner fallback; used only when
72
- │ │ # the backend has no paneStatus() of its own (i.e. tmux)
73
- │ ├── reconcile.ts # ghost prune + status detect + orphan surface
74
- │ ├── agents.ts # CRUD + send/read/list/close + liveness + reaper hub (re-exports src/agents/*)
75
- │ ├── agents/ # cohesive cluster of agent-lifecycle internals
76
- │ │ ├── spawn.ts # spawnAgent + resolveCliCommand / awaitSpawnLiveness / pane create-or-reuse / prestage / rollback
77
- │ │ ├── spawn-lock.ts # cross-process advisory lock around a spawn's topology critical section
78
- │ │ ├── kick.ts # reaper events + cleanup of dead agent rows
79
- │ │ ├── adopt.ts # adoptAgent: register an existing pane as a managed agent
80
- │ │ ├── wait.ts # waitForAgents: block on task-less agents finishing (busy → any other state)
81
- │ │ └── errors.ts # typed agent error classes (AgentNotFoundError, AgentDiedOnSpawnError, …)
82
- │ ├── tasks.ts # task SDK hub (re-exports src/tasks/*)
83
- │ ├── tasks/ # cohesive cluster of task-graph internals
84
- │ │ ├── status.ts # TaskStatus enum + helpers
85
- │ │ ├── core.ts # core SDK funcs reused across cluster files
86
- │ │ ├── id.ts # tryResolveTaskId / qualified-id helpers
87
- │ │ ├── queries.ts # listTasks / nextTasks / owned-by
88
- │ │ ├── edit.ts # addTask / setTaskTitle / etc (no edges)
89
- │ │ ├── edges.ts # block / unblock / reparent / delete + dedupe
90
- │ │ ├── claim.ts # claim / release + resolveActorIdentity (atomic CAS)
91
- │ │ ├── lifecycle.ts # setTaskStatus / closeTask / openTask + cascade
92
- │ │ ├── wait.ts # waitForTasks: block until tasks reach a target status
93
- │ │ ├── sort.ts # sortTasks (roi / recency / age / id)
94
- │ │ └── errors.ts # typed task error classes (TaskAlreadyOwnedError, CycleError, …)
95
- │ ├── tracks.ts # parallel-tracks union-find with diamond merge
96
- │ ├── workstream.ts # ensureWorkstream / list / summarize / destroy
97
- │ ├── hlc.ts # hybrid logical clock: the monotonic ordering key on every op
98
- │ ├── capture.ts # SQLite triggers recording every portable mutation as an op, in-transaction
99
- │ ├── op-context.ts # withOpContext (intent/actor/group) + withCaptureSuppressed echo guard
100
- │ ├── apply.ts # the apply path: per-field LWW merge + tombstones + deferred reprojection
101
- │ ├── undo.ts # inverse ops for one group (granular undo; supersede refusal)
102
- │ ├── rebuild.ts # replay the whole ops log into a NEW DB file (disaster recovery)
103
- │ ├── drift.ts # ops-log-vs-live-tables drift check (cheap default tier + --deep rebuild-diff)
104
- │ ├── segments.ts # JSONL segment transport: flush own ops / ingest peers from a watermark
105
- │ ├── sync.ts # peer status + the ambient flush/ingest hook + --from reader + --repair
106
- │ ├── fleet-hazards.ts # mixed-fleet doctor checks (DB inside MU_SYNC_DIR, network mount, case collisions)
107
- │ ├── file-lock.ts # generic cross-process advisory lock via atomic fs.mkdir
108
- │ ├── logs.ts # typed READER over `ops` + appendLog / emitEvent (the one write path triggers can't cover)
109
- │ ├── log-render.ts # the ONE op → prose formatter (renderOp); shared by CLI + TUI
110
- │ ├── vcs.ts # VcsBackend hub (re-exports src/vcs/*: jj/sl/git/none impls)
111
- │ ├── workspace.ts # per-agent VCS workspaces hub (re-exports src/workspace/*)
112
- │ ├── glyphs.ts # THE glyph vocabulary: agent status + state glyphs + card digits
113
- │ ├── shell-quote.ts # POSIX single-quote helper for copy-pasteable Next: hints
114
- │ ├── dag.ts # full-DAG forest builder (loadFullDag for `mu task tree` + DAG popup)
115
- │ ├── state.ts # SDK seam for `mu state` (fast SQL tier + slow subprocess tier + merge)
116
- │ ├── staleness.ts # WORKSPACE_STALE_THRESHOLD + isStaleWorkspace
117
- │ ├── project-root.ts # detectProjectRoot for the TUI launch cwd ladder
118
- │ ├── doctor-summary.ts # TUI-friendly slice of `mu doctor` checks + remediation helpers
119
- │ ├── output.ts # NextStep type + printNextSteps / errorNextSteps
120
- │ ├── cli.ts # commander wiring (buildProgram); re-exports format/handle for back-compat
121
- │ ├── cli/ # one file per verb-namespace; thin wrappers over the SDK
122
- │ │ ├── workstream.ts # workstream init / list / destroy
123
- │ │ ├── agents.ts # agent spawn / send / read / list / show / close / kick / wait / adopt
124
- │ │ ├── tasks.ts # `mu task` hub (re-exports wireTaskCommands / cmdMyNext / cmdMyTasks / unescapeNoteText)
125
- │ │ ├── tasks/ # sub-cluster of the `mu task` namespace
126
- │ │ │ ├── queries.ts # list / next / owned-by + cmdMyTasks / cmdMyNext (back `mu me tasks` / `mu me next`)
127
- │ │ │ ├── lifecycle.ts # close / open + cascade preview
128
- │ │ │ ├── edit.ts # add / show / notes / note / update + helpers
129
- │ │ │ ├── edges.ts # block / unblock / reparent / delete
130
- │ │ │ ├── claim.ts # claim / release / wait
131
- │ │ │ ├── tree.ts # tree rendering
132
- │ │ │ └── wire.ts # Commander glue
133
- │ │ ├── workspace.ts # workspace list / free / path / orphans / refresh / commits
134
- │ │ ├── log.ts # log read / write / tail
135
- │ │ ├── undo.ts # mu undo [group] (list / preview / --yes apply)
136
- │ │ ├── sync.ts # mu sync (peer status; --from / --repair)
137
- │ │ ├── rebuild.ts # mu rebuild <file>
138
- │ │ ├── state.ts # `mu state` (canonical state card); --tui dispatches to src/cli/tui/
139
- │ │ ├── staleness.ts # shared workspace-staleness CLI helpers + warn formatter
140
- │ │ ├── tui-launch-focus.ts # initial-tab focus ladder for bare `mu` and `mu state --tui`
141
- │ │ ├── tui/ # interactive ink-based TUI cluster; ONLY place ink/react are imported
142
- │ │ │ ├── index.ts # runTui entrypoint; alt-screen + mouse-mode lifecycle
143
- │ │ │ ├── escapes.ts # pure ANSI escape constants (ALT_SCREEN_*, mouse-mode bytes) — no ink imports
144
- │ │ │ ├── app.tsx # <App> root (popup state machine + global keymap + footer + tick + tabs)
145
- │ │ │ ├── state.ts # poll-loop hook (useDashboardSnapshot; fast/slow tick split)
146
- │ │ │ ├── keys.ts # pure dispatchGlobalKey + dispatchPopupKey + shouldSwallowGlobalKey
147
- │ │ │ ├── keymap-spec.ts # canonical keymap source-of-truth (drives help overlay + dispatch)
148
- │ │ │ ├── yank.ts # clipboard probe + write (pbcopy/wl-copy/xclip/xsel/clip.exe + OSC-52)
149
- │ │ │ ├── mouse.ts # vendored SGR mouse layer (parser + double-click + useMouse hook)
150
- │ │ │ ├── layout.ts # responsive multi-column dashboard + per-card row budgets
151
- │ │ │ ├── columns.ts # column-aligned row layout with protect/clip clipping
152
- │ │ │ ├── wrap-ansi.ts # ANSI-aware visual-width line wrapper + SGR close-on-end
153
- │ │ │ ├── format-helpers.ts # shared TUI formatters (relTime, sinceClaim, ROI, etc.)
154
- │ │ │ ├── titled-box.tsx # rounded border with section header inset into top border + bottomLabel
155
- │ │ │ ├── popup-shell.tsx # popup outer chrome (cyan TitledBox)
156
- │ │ │ ├── list-row.tsx # centralised non-selected row primitive (width pin + gutter + truncate)
157
- │ │ │ ├── padded-rows.tsx # per-card body padder
158
- │ │ │ ├── help.tsx # ?/F1 keymap overlay (scrollable on short panes)
159
- │ │ │ ├── status-bar.tsx # bottom status bar (mode + active ws + tick + footer flash)
160
- │ │ │ ├── tab-strip.tsx # multi-workstream tab switcher (N≥2)
161
- │ │ │ ├── tab-strip-layout.ts # pure window-around-active layout helper for the tab strip
162
- │ │ │ ├── tuicr.ts # `t` shortcut: alt-screen handoff to tuicr -r <sha>
163
- │ │ │ ├── use-popup-filter.tsx # shared '/' substring filter hook + applyFilter + FilterPrompt
164
- │ │ │ ├── use-status-filter.tsx # task-status toggles for task-list popups (o/i/c)
165
- │ │ │ ├── use-notes-drill.ts # shared notes-drill memo (5 task popups consume it)
166
- │ │ │ ├── use-popup-action-queue.ts # consume mouse PopupAction queue once per render
167
- │ │ │ ├── use-terminal-size.ts # shared reactive terminal-size hook (ink doesn't re-render on resize)
168
- │ │ │ ├── agent-display.ts # shared agent-row display helpers for cards/popups
169
- │ │ │ ├── lazygit.ts # `l` in the Commits popup: alt-screen handoff to lazygit
170
- │ │ │ ├── tmux-attach.ts # `a` in the Agents popup: alt-screen handoff to the agent's mux pane
171
- │ │ │ ├── cards/{agents,tracks,ready,log,workspaces,inprogress,blocked,recent,commits,doctor}.tsx + _placeholder.tsx
172
- │ │ │ └── popups/{agents,tracks,ready,log,workspaces,inprogress,blocked,recent,commits,doctor,dag,all-tasks}.tsx
173
- │ │ │ # plus drill.tsx (DrillScrollView), task-detail.tsx (TaskDetailDrill),
174
- │ │ │ # cursor-row.tsx, scroll.ts (applyCursor/applyScroll), viewport.ts,
175
- │ │ │ # show-loader.ts (shared subprocess-preserving loader)
176
- │ │ ├── db.ts # db backup (VACUUM INTO)
177
- │ │ ├── sql.ts # sql escape hatch
178
- │ │ ├── doctor.ts # doctor diagnostic
179
- │ │ ├── format.ts # pure rendering helpers (table renderers, status colourers, truncate/relTime)
180
- │ │ └── handle.ts # typed-error → exit-code map + handle() wrapper
181
- │ └── index.ts # SDK entrypoint (re-exports)
182
- ├── test/ # ~200 *.test.ts files / ~2900 tests (`npm run test`); many use real tmux/git/jj/sl
183
- ├── skills/mu/SKILL.md # what the LLM running inside an agent pane sees
184
- ├── package.json # bin: { mu: ./dist/cli.js }, type: module
185
- ├── tsconfig.json # strict + noUncheckedIndexedAccess + verbatimModuleSyntax
186
- ├── tsup.config.ts # bundles src/ → dist/ (cli + index entries)
187
- ├── biome.json # lint + format
188
- └── vitest.config.ts # tests
189
- ```
190
-
191
- ---
6
+ ## Read these first
192
7
 
193
- ## Working conventions
8
+ 1. [docs/guide/README.md](docs/guide/README.md) — what mu does for a user.
9
+ 2. [CHANGELOG.md](CHANGELOG.md) — the upcoming version's entry is the
10
+ source of truth for verbs, schema, and env vars.
11
+ 3. [docs/VISION.md](docs/VISION.md) — design pillars you must not violate.
12
+ 4. [docs/ROADMAP.md](docs/ROADMAP.md) — what's next and the promotion
13
+ criteria. Read [Anti-feature pledges](docs/ROADMAP.md#anti-feature-pledges)
14
+ before you add a dependency, an abstraction, or a surface.
15
+ 5. [docs/VOCABULARY.md](docs/VOCABULARY.md) — canonical terms for code,
16
+ docs, and error messages. [docs/reference/](docs/reference/) holds env vars and naming.
17
+ 6. [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) — the module map (what
18
+ lives where in `src/`), plus deep dives under [docs/architecture/](docs/architecture/).
194
19
 
195
- ### Build / test / lint
20
+ ## Build, test, lint
196
21
 
197
22
  ```bash
198
23
  npm install
199
24
  npm run build # tsup → dist/
200
- npm run typecheck # source + test TypeScript checks
201
- npm run lint # biome check src test
202
- npm run test:fast # fast unit/dev-loop tier (excludes *.integration.test.ts / *.smoke.test.ts)
203
- npm run test # full vitest suite, including integration tests
204
- npm run test:watch # vitest in watch mode
205
- npm run test:watch:fast # fast-tier watch mode
25
+ npm run typecheck
26
+ npm run lint
27
+ npm run test:fast # unit tier; safe to run concurrently
28
+ npm run test # full suite, including integration
29
+ npm run test:stress # pre-release stability gate
206
30
  ```
207
31
 
208
- #### Two TypeScript compilers, on purpose
209
-
210
- `tsc` is **TypeScript 7** (the Go-native compiler) and `tsc6` is the
211
- TypeScript 6 JS compiler. `package.json` aliases them:
212
-
213
- ```json
214
- "@typescript/native": "npm:typescript@^7.0.2",
215
- "typescript": "npm:@typescript/typescript6@^6.0.2"
216
- ```
217
-
218
- This looks backwards and is not. TS 7.0 ships **no programmatic
219
- compiler API** (`ts.createProgram` and friends return undefined; the
220
- package exports exactly two keys). It is deferred to 7.1. `tsup --dts`
221
- goes through `rollup-plugin-dts`, which needs that API, so a plain
222
- `typescript@7` install builds JS fine and then dies with
223
- `Cannot read properties of undefined (reading 'useCaseSensitiveFileNames')`.
224
-
225
- The alias resolves both needs at once: anything that `require`s
226
- `typescript` as a LIBRARY gets the 6.x JS API, while the `tsc` BINARY
227
- is the fast Go compiler (typecheck went ~9s → ~0.6s). The two bins do
228
- not collide, so no shim is needed.
229
-
230
- Revisit when 7.1 lands its new API and `rollup-plugin-dts` adopts it;
231
- at that point `typescript` can point at 7.x directly and
232
- `@typescript/native` disappears.
233
-
234
- Use `npm run test:fast` for the inner dev loop and concurrent worker
235
- checks; it is the concurrency-safe tier that avoids real tmux/VCS
236
- subprocess fixtures. The green gates still include `npm run test`
237
- before any commit. Real-tmux integration tests need `$TMUX` set and
238
- skip themselves otherwise; CI runs inside tmux.
239
-
240
- ### Commits
241
-
242
- - Conventional but not strict: prefix with the scope when helpful
243
- (e.g. `R4 + R9: ...`, `schema: ...`). One logical change per
244
- commit.
245
- - Body explains **what changed and why**, not just what. Reference
246
- the [VISION.md](docs/VISION.md) pillar / [ROADMAP.md](docs/ROADMAP.md)
247
- item / promotion criterion that motivated the change.
248
- - Verify typecheck + lint + tests + build clean before committing.
249
- Say so in the commit message.
250
-
251
- ### Code style
252
-
253
- - TypeScript, strict mode. `noUncheckedIndexedAccess` is on — don't
254
- trust array indices to be defined; use early returns.
255
- - ESM only (`"type": "module"`). NodeNext module resolution.
256
- - No `any`. Use `unknown` and narrow.
257
- - No non-null assertions (`!`). Use early returns or `if (!x) throw`.
258
- - Errors are typed classes (e.g. `AgentNotFoundError`,
259
- `TaskAlreadyOwnedError`, `CycleError`, `MuxError` and its per-backend
260
- subclasses `TmuxError` / `HerdrError`,
261
- `AgentDiedOnSpawnError`) so the CLI's `handle()` wrapper can map
262
- them to specific exit codes.
263
- - Imports stay sorted (Biome's `organizeImports` enforces this).
264
- - Run `npx biome check --write src test` to auto-fix sort + format.
265
- **Do not** run `--write --unsafe`: it rewrites `delete
266
- process.env.X` to `process.env.X = undefined`, which silently
267
- produces the literal string `"undefined"`. The env-deletion pattern
268
- here is `const key = "FOO"; delete process.env[key];`.
269
- - Hard cap: 1500 LOC per file. Refactor signal at 800.
270
- - **Layout: flat at the root; one level of subdirs is allowed when a
271
- cluster of files is naturally cohesive** (e.g. `src/cli/` for the
272
- thin commander wrappers, one file per verb-namespace). Each subdir
273
- cluster needs:
274
- (1) a clear theme (every file does the same kind of thing),
275
- (2) imports go from cluster-files → root-files (no upward imports),
276
- (3) ARCHITECTURE.md's module table has a row covering it.
277
-
278
- ### Tests
279
-
280
- - Unit tests: real SQLite (in-temp-dir), mocked tmux executor via
281
- `setTmuxExecutor()`. Fast, deterministic. For anything touching the
282
- multiplexer, use `installMux()` from `test/_mux.ts` rather than the
283
- raw setters — it installs the backend AND its executor together and
284
- hands back one `restore()`, which removes the ordering hazard of
285
- stubbing one backend while asserting against another.
286
- - TUI popup/card behaviour tests should follow `test/README.md`:
287
- prefer the `test/_ink-render.ts` CaptureStream seam over
288
- `readFileSync` source-greps except for narrow structural guards.
289
- - Fast tier: `npm run test:fast` runs `test/**/*.test.ts` while
290
- excluding `*.integration.test.ts` and `*.smoke.test.ts`. Keep this
291
- tier pure/in-process: mocked tmux/VCS, real SQLite only in per-test
292
- temp DBs, no real tmux/git/jj/sl subprocess fixtures, no
293
- filesystem-heavy export/import/snapshot paths, and no fixed sleeps
294
- above 50ms.
295
- - Integration tests: full-only tests use the `.integration.test.ts`
296
- suffix (e.g. `tmux.integration.test.ts`). They may touch real tmux
297
- or herdr servers, git/jj/sl fixture repos, subprocess-backed smoke
298
- paths, filesystem-heavy export/import/snapshot flows, or
299
- intentionally slower in-process CLI flows. Real-tmux tests are
300
- skipped when `$TMUX` is unset; real-herdr tests skip unless a
301
- running, protocol-compatible server is present. These tests typically opt out of the spawn
302
- liveness check via `process.env.MU_SPAWN_LIVENESS_MS = "0"` in
303
- `beforeEach`, since the sh subprocesses they spawn are intentionally
304
- long-lived.
305
- - Each test gets its own temp DB and (for integration) a unique
306
- tmux session like `mu-test-<pid>-<ts>-<rand>` to avoid colliding
307
- with the user's panes or with parallel test runs.
308
- - **herdr isolation is a guard, not a socket.** tmux gets a private
309
- server via `MU_TMUX_SOCKET`, so a stray test is contained
310
- structurally. herdr has no equivalent: its only isolation is a named
311
- session (`MU_HERDR_SESSION`), so `test/_mux.ts` enforces it at
312
- runtime — `assertHerdrIsolated()` refuses to run when the var is
313
- unset or names `default`, and the fatal verbs (`server stop`,
314
- `server restart`, `server kill`) are refused even inside a correct
315
- session, because a stopped server is shared-fate. **Never stop the
316
- herdr server or kill its main process from a test.** That destroys
317
- the user's real panes and their unsaved work, with no undo. Clean up
318
- per-entity (close what you created), never by nuking the server.
319
- - Dogfood reality: multiple pi worker agents often run `npm run test`
320
- concurrently on the same machine from different workspaces. Treat
321
- flakes that pass in isolation but fail under load as concurrency
322
- bugs first (shared `/tmp` cleanup, tmux socket/session collisions,
323
- leaked subprocesses, VCS background file activity). Use
324
- `npm run test:stress` for the pre-release/stability gate; it runs
325
- the suite repeatedly with a per-run timeout and can simulate
326
- parallel full-suite runs via
327
- `MU_TEST_STRESS_MODE=parallel MU_TEST_STRESS_PARALLEL=2`.
328
- - The acceptance test in `test/acceptance.integration.test.ts` is the
329
- "everything works" gate. Keep it passing.
330
- - **DB baseline**: `openDb()` refuses to open the user's REAL
331
- default DB (`<HOME or XDG_STATE_HOME>/mu/mu.db`) when
332
- `process.env.VITEST` is set or `NODE_ENV === "test"`. Tests
333
- MUST use a per-test temp DB — either via MU_DB_PATH (which
334
- `test/_runCli.ts` sets automatically) or an explicit `{ path }`
335
- argument to `openDb`. A regression that forgets either one
336
- fails loudly at the offending openDb() call site instead of
337
- silently writing to the dev box's live state. Production never
338
- sets VITEST, so the guard is a no-op outside the test runner.
339
- - **Env baseline**: `test/_setup.ts` (vitest `setupFiles`) clears
340
- every `MU_*` env var inherited from the parent shell at the start
341
- of each fork, so SDK-level overrides (`MU_PI_COMMAND`,
342
- `MU_IDLE_THRESHOLD_MS`, `MU_SEND_DELAY_MS`, …) can't silently
343
- change behaviour underneath tests. Allowlist: `MU_TMUX_SOCKET`
344
- (set by `_global-teardown.ts` at MODULE LOAD time — BEFORE vitest
345
- spawns the worker pool — for Layer-3 isolation; see round-3
346
- Part A in the file's header comment for why module-load not
347
- setup()). Tests that need a specific value opt IN per-test via
348
- `process.env.X = "..."` or `withEnv()` from `test/_env.ts`.
349
- - **Default-socket sweep philosophy**: `_global-teardown.ts` runs
350
- an ALLOWLIST sweep of `mu-*` sessions on the user's default tmux
351
- socket at suite setup AND teardown. The allowlist is DB-rooted:
352
- (1) `mu-<name>` for every workstream in the user's REAL DB
353
- (read-only via better-sqlite3, bypassing the `openDb()` test
354
- guard) and (2) `mu-$MU_SESSION` if the orchestrator runs the suite
355
- inside a tmux pane. Anything else is, by elimination, test residue
356
- and is killed. No session is grandfathered by being present at
357
- module load — that defeats the self-healing intent. Cost: an ad-hoc
358
- `tmux new-session -t mu-foo` with no DB row gets killed; run
359
- `mu workstream init foo` first. Tests can hardcode any workstream
360
- name — if they accidentally bypass the private socket, the sweep
361
- catches them.
362
-
363
- ### When you change behaviour, update VOCABULARY first
364
-
365
- Vocabulary is canonical. If you introduce a new concept, name, or
366
- verb, **add it to docs/VOCABULARY.md before the code lands**. If you
367
- rename something, update VOCABULARY.md in the same commit.
368
-
369
- ### Deferred features — don't smuggle them in
370
-
371
- [docs/ROADMAP.md](docs/ROADMAP.md) lists what's next, by version,
372
- with **promotion criteria**:
373
-
374
- > 1. A real user hits the missing feature in real workflows ≥2
375
- > times.
376
- > 2. The current substrate makes the addition straightforward (no
377
- > major pillar refactor).
378
- > 3. The addition fits in <300 LOC or has a clear smaller subset.
379
-
380
- If you find yourself adding something not on the roadmap and not
381
- meeting these criteria, **stop**. Add an entry to
382
- [docs/ROADMAP.md](docs/ROADMAP.md) (or open an issue) and move on.
383
-
384
- The "anti-feature pledges" in ROADMAP.md are firm:
385
-
386
- - No config file
387
- - No daemon / background process beyond what the multiplexer +
388
- SQLite give us
389
- - No anticipatory abstractions (no traits with zero implementors)
390
- - No wrappers around wrappers
391
- - No codegen / embedded JS engine / workflow DSL
392
- - No template/discovery system for agent roles (spawn flags + first
393
- message ARE the definition)
394
- - No render layer beyond `cli-table3` + `picocolors`, EXCEPT `ink`
395
- confined to `src/cli/tui/`. NO second TUI stack alongside `ink`
396
- (no `blessed` / `terminal-kit` etc.); if `ink` ever stops paying
397
- off, REPLACE it, don't stack stacks.
398
- - No plugin runtime, web UI, RPC, chat/docs integrations, memory
399
- system, workflow engine
400
- - Don't bundle pi (it's a peer dep)
401
-
402
- ### When in doubt: be small
403
-
404
- Ship the smallest thing that works, then layer on as real friction
405
- proves itself.
406
-
407
- ---
32
+ `tsc` is TypeScript 7 (fast typecheck). The `typescript` package is
33
+ aliased to 6.x on purpose: `tsup --dts` needs the programmatic API that
34
+ TS 7.0 lacks. Revisit at TS 7.1.
35
+
36
+ ## Code style
37
+
38
+ - Strict TypeScript with `noUncheckedIndexedAccess`, ESM only. Use
39
+ `unknown` instead of `any`, and early returns instead of `!`.
40
+ - Throw typed error classes (`AgentNotFoundError`, `CycleError`,
41
+ `MuxError` and its `TmuxError` / `HerdrError` subclasses, …) so
42
+ `handle()` maps each to an exit code.
43
+ - Auto-fix sort and format with `npx biome check --write src test`.
44
+ Never add `--unsafe`: it rewrites `delete process.env.X` into
45
+ `process.env.X = undefined`, which sets the string `"undefined"`.
46
+ Delete env vars as `const key = "FOO"; delete process.env[key];`.
47
+ - Files cap at 1500 LOC. Refactor at 800.
48
+ - Layout is flat at the `src/` root. One level of subdirectory is
49
+ allowed for a cohesive cluster: one theme, imports only from the
50
+ cluster to root files, and a row in the ARCHITECTURE module map.
51
+
52
+ ## Tests
53
+
54
+ - Unit tests use real SQLite in a temp dir and a mocked multiplexer.
55
+ Install mocks with `installMux()` from `test/_mux.ts`: it sets the
56
+ backend and its executor together and returns one `restore()`.
57
+ - TUI tests render through `test/_ink-render.ts` (see `test/README.md`).
58
+ - The fast tier (`*.test.ts`) stays in-process: mocked tmux and VCS,
59
+ per-test temp DBs, no real tmux/git/jj/sl, no sleeps over 50ms.
60
+ - Name full-only tests `*.integration.test.ts`. They may use real
61
+ tmux, herdr, VCS fixtures, or subprocesses. Real-tmux tests skip
62
+ without `$TMUX`. Set `process.env.MU_SPAWN_LIVENESS_MS = "0"` in
63
+ `beforeEach` when the spawned process is meant to live long.
64
+ - Integration tests use unique session names
65
+ (`mu-test-<pid>-<ts>-<rand>`), poll (50ms × 10) instead of sleeping,
66
+ and wrap `afterEach` cleanup in `try {} catch {}`. Unit tests call
67
+ `setSleepForTests(async () => {})`.
68
+ - **Never stop the herdr server or kill its process from a test.** It
69
+ destroys the user's real panes. herdr isolation is only a named
70
+ session (`MU_HERDR_SESSION`, enforced by `assertHerdrIsolated()`), so
71
+ close only what you created.
72
+ - `openDb()` refuses the real DB under vitest. Use `MU_DB_PATH` or
73
+ `{ path }`.
74
+ - `test/_setup.ts` clears inherited `MU_*` vars except
75
+ `MU_TMUX_SOCKET`. Opt in per test with `withEnv()` from `test/_env.ts`.
76
+ - `test/_global-teardown.ts` kills every `mu-*` tmux session on the
77
+ default socket that has no workstream in your real DB.
78
+ - Workers run `npm run test` concurrently. A test that fails
79
+ under load but passes alone is a test-infra concurrency bug (shared
80
+ `/tmp`, sockets, leaked subprocesses). One that also fails alone is a
81
+ real bug.
82
+ - `test/acceptance.integration.test.ts` is the end-to-end gate. Keep it
83
+ green.
84
+
85
+ ## Change rules
86
+
87
+ - **VOCABULARY first.** Add or rename a term in
88
+ [docs/VOCABULARY.md](docs/VOCABULARY.md) before or with the code.
89
+ - **Roadmap gate.** Build only what is on the roadmap or meets all
90
+ three promotion criteria: a real user hit the gap ≥2 times, the
91
+ current design makes it straightforward, and it fits in <300 LOC.
92
+ Otherwise add a ROADMAP entry and move on.
93
+ - **Commits:** one logical change each, scope prefix when useful
94
+ (`schema: …`). The body says what changed and why, cites the VISION
95
+ pillar or ROADMAP item, and states that the green gate passed. Add a
96
+ [CHANGELOG.md](CHANGELOG.md) entry under the upcoming version.
408
97
 
409
98
  ## Common tasks
410
99
 
411
- ### "Add a new CLI verb"
412
-
413
- 1. Find or write the programmatic function in `src/agents.ts`,
414
- `src/tasks.ts`, `src/workstream.ts`, etc. Test it with mocked
415
- tmux. Return a typed result object (`{ changed: boolean,
416
- previousStatus, ... }`) so callers can log lifecycle
417
- transitions.
418
- 2. Wire the verb in `src/cli.ts` using `commander`. Use
419
- `handle(...)` so typed errors map to exit codes. If the verb
420
- takes `--workstream`, use `command.optsWithGlobals()` (via
421
- `this`) so the top-level option doesn't swallow it (commander
422
- gotcha).
423
- 3. Update [docs/USAGE_GUIDE.md](docs/USAGE_GUIDE.md) with the new
424
- verb in the right section.
425
- 4. Update [docs/VOCABULARY.md](docs/VOCABULARY.md) operations
426
- table.
427
- 5. Update [skills/mu/SKILL.md](skills/mu/SKILL.md) **only if the verb has a
428
- gotcha `--help` cannot state.** The skill is not a verb list — see
429
- § Skill files are context, not documentation, below.
430
- 6. Update [CHANGELOG.md](CHANGELOG.md) under the upcoming version.
431
- 7. If this verb promotes a `mu sql` workaround, remove the
432
- workaround entry from the `docs/USAGE_GUIDE.md` gaps table.
433
- 8. Smoke-test: `MU_SYNC_DIR= MU_DB_PATH=/tmp/mu-smoke.db node
434
- dist/cli.js <verb> ...` to verify it works against real tmux. If
435
- the verb touches the agent layer, smoke it on herdr too —
436
- `MU_MUX=herdr` plus a private `MU_HERDR_SESSION` (never the default
437
- session).
438
-
439
- **Blank `MU_SYNC_DIR` as well as `MU_DB_PATH`.** Overriding the DB
440
- alone does not contain a smoke test: sync is ambient, so the first
441
- flush stamps the throwaway DB's fresh `machine_id` onto a segment
442
- in your REAL sync folder. Nothing prunes it, and absence of a
443
- segment is the only way a peer disappears, so `mu sync` then lists
444
- a phantom machine forever. Eight of them accumulated in `~/mu`
445
- before anyone noticed. `src/segments.ts` now refuses a non-temp
446
- sync dir under vitest, but a hand-run `node dist/cli.js` is not
447
- under vitest — blank the var yourself.
448
-
449
- ### "Update the schema"
450
-
451
- 1. Current schema version is **v11** (`CURRENT_SCHEMA_VERSION` in
452
- `src/db.ts`): 11 tables + 3 views. The schema is the
453
- `applySchema(db)` block — idempotent CREATE-IF-NOT-EXISTS plus
454
- targeted `DROP TABLE IF EXISTS`. `openDb` REFUSES any pre-v11 DB
455
- with `SchemaTooOldError` and any newer one with `SchemaTooNewError`
456
- (both exit 4); there is no in-process
457
- migration ladder.
458
- 2. Bump `CURRENT_SCHEMA_VERSION` in `src/db.ts` and mirror the new
459
- shape in `CURRENT_SCHEMA`. Prefer script-free bumps: additive
460
- CREATE-TABLE-IF-NOT-EXISTS, or an idempotent `DROP TABLE` block.
461
- Reach for a one-shot migration script only when the change can't
462
- be expressed that way. **If you add a table, classify it** in
463
- `PORTABLE_TABLES` or `MACHINE_LOCAL_TABLES` —
464
- `test/entities.test.ts` fails loudly otherwise.
465
- 3. Update tests that exercise the schema (`test/db.test.ts`).
466
- 4. Update [CHANGELOG.md](CHANGELOG.md) under the upcoming version's
467
- `### Changed` section.
468
-
469
- ### "Add a new multiplexer operation"
470
-
471
- mu drives **two** multiplexers, tmux and herdr, behind the
472
- `MuxBackend` interface in `src/mux/types.ts`. Call sites resolve one
473
- with `await activeMux()` and never name a backend.
474
-
475
- 1. **Add the method to `MuxBackend`** with a doc comment saying what
476
- it means, not how tmux does it.
477
- 2. **Implement it in BOTH** `src/mux/tmux.ts` and `src/mux/herdr.ts`.
478
- A backend with no equivalent no-ops (see
479
- `enableMuPaneBorders*`, `selectLayout`) — it does not throw.
480
- 3. **If only one backend can do it**, make it an OPTIONAL method
481
- (`paneStatus?()`, `startAgentInPane?()`) and have the caller
482
- branch on the CAPABILITY being present, never on `mux.name`.
483
- 4. **Decide load-bearing vs best-effort.** Load-bearing calls (spawn,
484
- send, kill) let `NoMultiplexerError` propagate to exit 5.
485
- Best-effort calls (identity, decoration, orphan surfacing) wrap in
486
- try/catch so a missing mux degrades instead of failing the verb —
487
- `resolveWorkerIdentity` in `src/tasks/claim.ts` is the shape.
488
-
489
- All tmux invocations go through `src/mux/tmux.ts` `tmux(args)`; all
490
- herdr invocations through `src/mux/herdr.ts` `herdr(args)`. **No raw
491
- `execa("tmux", …)` / `execa("herdr", …)` anywhere else.** Each wrapper
492
- produces a typed error (`TmuxError` / `HerdrError`, both extending
493
- `MuxError`) and each has an executor seam the tests mock — use
494
- `installMux()` from `test/_mux.ts` rather than the raw setters.
495
-
496
- For send-style operations the two backends differ sharply, and that
497
- difference is the point: tmux needs the canonical bracketed-paste
498
- sequence in `sendToPane` (naive `tmux send-keys "<text>"` is broken —
499
- `/`, `?`, `f` get eaten by the agent's TUI), while herdr's
500
- `agent prompt` is one atomic call. Do not port the tmux workaround
501
- to herdr.
502
-
503
- ### "Fix a flaky integration test"
504
-
505
- Integration tests against real tmux can be slow because tmux/sh
506
- processes need time to settle. Use:
507
-
508
- - Unique session names (`mu-test-<pid>-<ts>-<random>`) so parallel
509
- runs never collide.
510
- - Polling loops (50ms × 10 attempts) when waiting for state to
511
- propagate, not fixed sleeps.
512
- - `try { ... } catch {}` cleanup in `afterEach` for tmux session
513
- kills and DB closes — a failure mid-test should never block the
514
- next.
515
- - `setSleepForTests(async () => {})` in unit tests so the real
516
- `MU_SEND_DELAY_MS` doesn't slow them.
517
- - `process.env.MU_SPAWN_LIVENESS_MS = "0"` in integration-test
518
- `beforeEach` to skip the 1500ms post-spawn check (the unit-test
519
- suite covers it).
520
-
521
- ---
100
+ ### Add a CLI verb
101
+
102
+ 1. Write the SDK function (`src/agents.ts`, `src/tasks.ts`, …) with a
103
+ typed result (`{ changed, previousStatus, … }`). Test with mocks.
104
+ 2. Wire it in `src/cli.ts` with commander inside `handle(...)`. For
105
+ `--workstream`, read `this.optsWithGlobals()`; the top-level option
106
+ otherwise swallows it.
107
+ 3. Document it in the matching `docs/guide/` how-to, VOCABULARY if
108
+ it adds a term, and CHANGELOG. Remove any `mu sql` workaround it
109
+ replaces. Touch `skills/mu/SKILL.md` only for a gotcha `--help`
110
+ cannot state.
111
+ 4. Smoke-test against real tmux, and on herdr with `MU_MUX=herdr` plus
112
+ a private `MU_HERDR_SESSION` if it touches agents:
113
+
114
+ ```bash
115
+ MU_SYNC_DIR= MU_DB_PATH=/tmp/mu-smoke.db node dist/cli.js <verb> ...
116
+ ```
117
+
118
+ Blank `MU_SYNC_DIR` too. Otherwise the first flush writes the
119
+ throwaway DB's `machine_id` into your real sync folder, and `mu sync`
120
+ lists that phantom peer forever.
121
+
122
+ ### Update the schema
123
+
124
+ The schema is v11 (`CURRENT_SCHEMA_VERSION` in `src/db.ts`), applied by
125
+ `applySchema(db)` as idempotent CREATE-IF-NOT-EXISTS. `openDb` refuses
126
+ older DBs (`SchemaTooOldError`) and newer ones (`SchemaTooNewError`),
127
+ both exit 4. There is no migration ladder.
128
+
129
+ 1. Bump `CURRENT_SCHEMA_VERSION` and mirror the shape in
130
+ `CURRENT_SCHEMA`. Prefer additive or idempotent-drop changes. Write
131
+ a one-shot migration script only when neither works.
132
+ 2. Classify every new table in `PORTABLE_TABLES` or
133
+ `MACHINE_LOCAL_TABLES`. `test/entities.test.ts` fails otherwise.
134
+ 3. Update `test/db.test.ts` and CHANGELOG `### Changed`.
135
+
136
+ ### Add a multiplexer operation
137
+
138
+ Call sites use `await activeMux()` and never name a backend.
139
+
140
+ 1. Add the method to `MuxBackend` (`src/mux/types.ts`) with a comment
141
+ saying what it means.
142
+ 2. Implement it in `src/mux/tmux.ts` and `src/mux/herdr.ts`. A backend
143
+ with no equivalent no-ops instead of throwing.
144
+ 3. If only one backend can do it, make it optional and branch on the
145
+ method's presence, not on `mux.name`.
146
+ 4. Let `NoMultiplexerError` propagate (exit 5) for load-bearing calls
147
+ (spawn, send, kill). Wrap best-effort calls in try/catch, like
148
+ `resolveWorkerIdentity` in `src/tasks/claim.ts`.
149
+
150
+ Run tmux only through `tmux(args)` and herdr only through
151
+ `herdr(args)`. pi agents are driven through the control socket, not
152
+ the multiplexer. For non-pi agents and `--via mux`, tmux needs the
153
+ bracketed-paste sequence in `sendToPane`, while herdr's `agent prompt`
154
+ is one atomic call. Keep the tmux workaround out of herdr.
155
+
156
+ ## Known traps
157
+
158
+ - **Silent `node dist/cli.js --help`** with "Detected unsettled
159
+ top-level await" is an import cycle: a file in `src/cli/tui/`
160
+ imported `../../../cli.js`. Import from the real source module
161
+ instead (`rg 'from "\.\./\.\./\.\./cli\.js"' src/cli/tui/`).
162
+ - **ANSI in ink `<Text>`** breaks wrap math. Pre-wrap by visual width
163
+ with `src/cli/tui/wrap-ansi.ts` and pad lines to the box width.
164
+ - **A key or click lands in the wrong popup**: a stale mouse event was
165
+ replayed. Consume it once, as `src/cli/tui/use-popup-action-queue.ts`
166
+ does.
167
+ - **A hint for one popup** goes in its `hint` prop, not the global
168
+ `POPUP_DRILL_HINTS`.
169
+
170
+ ## Orchestrating on this repo
171
+
172
+ Follow the loop in [skills/mu/SKILL.md](skills/mu/SKILL.md). Repo specifics:
173
+
174
+ - Each workspace runs `npm install` into its own `node_modules`. Never
175
+ symlink main's.
176
+ - `mu task wait` may list commits already on main when a worker forked
177
+ from an old HEAD. Check `git log` in the workspace and cherry-pick
178
+ only the new shas.
179
+ - CHANGELOG.md conflicts are routine: keep both halves in order.
180
+ Resolve source conflicts by hand, never with `--ours` or `--theirs`.
181
+ - Fix a worker's bug in a separate commit after the cherry-pick. Do
182
+ not amend it.
183
+ - Gate the merged tree with the full gate below plus
184
+ `node dist/cli.js --help`.
185
+ - The installed `mu` may be older than the branch build. Confirm a
186
+ worker received its brief (`mu agent read <w> -n 3`).
522
187
 
523
188
  ## Skill files are context, not documentation
524
189
 
525
- `skills/mu/SKILL.md` and `skills/mu/REMOTE_WORKERS.md` are loaded into an
526
- orchestrator's context window, SKILL.md on every invocation, where every word
527
- competes with the user's actual work for attention. **Keep them ruthlessly
528
- terse.**
529
-
530
- Read the `writing-for-agents` skill before editing either one. Work its levers
531
- in this order:
532
-
533
- - **Cache test.** `mu <verb> --help` is a one-command lookup that cannot go
534
- stale. A skill that restates it is a cache that rots. Verb lists, flag
535
- enumerations and option tables belong in `--help`; the skill carries only
536
- what `--help` cannot say — the gotcha, the reason, the trap.
537
- - **Single source of truth.** One meaning, one place. Scattered advice does not
538
- get assembled by the reader: it gets missed. Prefer a consolidating edit that
539
- deletes as much as it adds.
540
- - **Progressive disclosure.** SKILL.md gets the trigger and the rule.
541
- REMOTE_WORKERS.md gets the detail. Anything only some branches need goes
542
- behind the pointer, never in the always-loaded file.
543
- - **No-op hunt.** Delete any sentence the model already obeys by default. The
544
- test is behavioural: does it change what an agent does?
545
- - **Positive instruction.** State the target behaviour. A prohibition drags the
546
- banned shape into context and makes it more available, so name an
547
- anti-pattern only where a reader needs to recognise their own instinct.
548
-
549
- Measure before and after. An edit that grows SKILL.md needs a reason in the
550
- commit message, and "the new verb needed documenting" is not one.
551
-
552
- Two failures already paid for this rule. A 910-word section titled "CLI
553
- overview (only gotchas; use `--help` for full syntax)" was half verb list, and
554
- a DO/DON'T section was the orchestrator loop written twice — 675 words removed
555
- with nothing lost. And a warning about wedging a host existed in the file while
556
- the agent that wedged the host never saw it, because it sat 350 lines from the
557
- section it applied to. **A skill that sprawls stops being read**, which makes
558
- sprawl a correctness problem, not a tidiness one.
559
-
560
- ---
561
-
562
- ## What NOT to do
563
-
564
- - **Don't pad the skill files.** They are context, not docs, and
565
- `--help` is the verb list. See § Skill files are context, not
566
- documentation.
567
- - **Don't add a config file.** mu is CLI flags + env vars.
568
- - **Don't add a daemon, watcher, or background process.** Every
569
- invocation is short-lived.
570
- - **Don't add abstractions for hypothetical future flexibility.**
571
- Two real impls today, or use a concrete type. (Cautionary tale: a
572
- prior internal runtime's `RunContext` trait with zero implementors.)
573
- - **Don't grow stream wrappers around stream wrappers.**
574
- (`TextStream` / `TextState` / `StreamResult`.)
575
- - **Don't generate JS strings as a "typed protocol."**
576
- - **Don't put state-snapshot/handle layering on top of SQLite.**
577
- SQLite is the canonical state. Read it directly; don't introduce
578
- a `MuStateHandle` facade.
579
- - **Don't add a template/discovery system for agent roles** until
580
- pattern promotion criteria are met.
581
- - **Don't bundle pi.** It's a peer dep, optional.
582
- - **Don't write to files outside `~/.local/state/mu/` or the
583
- project repo** without documenting why.
584
- - **Don't promote a roadmap item to "shipped"** unless its
585
- promotion criteria in [docs/ROADMAP.md](docs/ROADMAP.md) are met.
586
-
587
- ---
190
+ `skills/mu/SKILL.md` loads into every orchestrator's context, and
191
+ `skills/mu/REMOTE_WORKERS.md` on demand. Every word competes with the
192
+ user's work. Read the `writing-for-agents` skill before you edit
193
+ either one.
194
+
195
+ - Leave verb lists, flags, and option tables to `--help`. The skill
196
+ carries only what `--help` cannot: the gotcha, the reason, the trap.
197
+ - Say each thing once, next to where it applies.
198
+ - SKILL.md holds the trigger and the rule. Detail that only some
199
+ branches need goes in REMOTE_WORKERS.md.
200
+ - Delete sentences the model obeys by default. State the target
201
+ behaviour.
202
+
203
+ A commit that grows SKILL.md must say why. "The new verb needed
204
+ documenting" is not a reason.
205
+
206
+ ## What not to do
207
+
208
+ - No config file. mu is CLI flags plus env vars.
209
+ - No daemon, watcher, or background process. Every invocation is
210
+ short-lived.
211
+ - No abstraction without two real implementations today.
212
+ - No wrappers around wrappers, no JS-string codegen, no facade over
213
+ SQLite (it is the canonical state; read it directly).
214
+ - No template or discovery system for agent roles.
215
+ - No render layer beyond `cli-table3` + `picocolors`, except `ink`
216
+ inside `src/cli/tui/`. Never a second TUI stack.
217
+ - Don't bundle pi. It is an optional peer dependency.
218
+ - Write files only under `~/.local/state/mu/` or the repo.
588
219
 
589
220
  ## When you're done
590
221
 
591
- Before opening a PR or marking a task complete:
592
-
593
222
  ```bash
594
223
  npm run typecheck && npm run lint && npm run test:fast && npm run test && npm run build
595
224
  ```
596
225
 
597
- The acceptance test (`test/acceptance.integration.test.ts`) must
598
- pass — it's the "end-to-end works" gate.
599
-
600
- Checklist for any non-trivial change:
601
-
602
- - [ ] If you added a typed verb, the corresponding `mu sql`
603
- workaround row was removed from `docs/USAGE_GUIDE.md`.
604
- - [ ] If you added vocabulary, `docs/VOCABULARY.md` has the new
605
- entry.
606
- - [ ] If you changed an architectural seam,
607
- `docs/ARCHITECTURE.md` is updated.
608
- - [ ] [CHANGELOG.md](CHANGELOG.md) has an entry under the upcoming
609
- version.
610
-
611
- That's it. Be small, be typed, follow the conventions, ship clean
612
- green builds.
226
+ Then check that VOCABULARY, ARCHITECTURE, and CHANGELOG reflect the
227
+ change.