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