@mu-crew/mu 1.6.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.
- package/AGENTS.md +611 -0
- package/README.md +328 -0
- package/dist/cli.js +22146 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +4853 -0
- package/dist/index.js +9740 -0
- package/dist/index.js.map +1 -0
- package/docs/.m-reflow-5f7exa_b.md +509 -0
- package/docs/.m-reflow-5v_034rw.md +545 -0
- package/docs/.m-reflow-7yaw8ubc.md +513 -0
- package/docs/ARCHITECTURE.md +997 -0
- package/docs/HANDOVER.md +486 -0
- package/docs/ROADMAP.md +277 -0
- package/docs/USAGE_GUIDE.md +2615 -0
- package/docs/VISION.md +487 -0
- package/docs/VOCABULARY.md +481 -0
- package/docs/img/tui-dashboard.png +0 -0
- package/package.json +87 -0
- package/skills/mu/REMOTE_WORKERS.md +604 -0
- package/skills/mu/SKILL.md +369 -0
package/AGENTS.md
ADDED
|
@@ -0,0 +1,611 @@
|
|
|
1
|
+
# Repo guide for AI coding agents
|
|
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
|
|
45
|
+
|
|
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; v10)
|
|
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
|
+
---
|
|
192
|
+
|
|
193
|
+
## Working conventions
|
|
194
|
+
|
|
195
|
+
### Build / test / lint
|
|
196
|
+
|
|
197
|
+
```bash
|
|
198
|
+
npm install
|
|
199
|
+
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
|
|
206
|
+
```
|
|
207
|
+
|
|
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
|
+
---
|
|
408
|
+
|
|
409
|
+
## Common tasks
|
|
410
|
+
|
|
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 **v10** (`CURRENT_SCHEMA_VERSION` in
|
|
452
|
+
`src/db.ts`): 10 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-v10 DB
|
|
455
|
+
with `SchemaTooOldError` (exit 4); there is no in-process
|
|
456
|
+
migration ladder.
|
|
457
|
+
2. Bump `CURRENT_SCHEMA_VERSION` in `src/db.ts` and mirror the new
|
|
458
|
+
shape in `CURRENT_SCHEMA`. Prefer script-free bumps: additive
|
|
459
|
+
CREATE-TABLE-IF-NOT-EXISTS, or an idempotent `DROP TABLE` block.
|
|
460
|
+
Reach for a one-shot migration script only when the change can't
|
|
461
|
+
be expressed that way. **If you add a table, classify it** in
|
|
462
|
+
`PORTABLE_TABLES` or `MACHINE_LOCAL_TABLES` —
|
|
463
|
+
`test/entities.test.ts` fails loudly otherwise.
|
|
464
|
+
3. Update tests that exercise the schema (`test/db.test.ts`).
|
|
465
|
+
4. Update [CHANGELOG.md](CHANGELOG.md) under the upcoming version's
|
|
466
|
+
`### Changed` section.
|
|
467
|
+
|
|
468
|
+
### "Add a new multiplexer operation"
|
|
469
|
+
|
|
470
|
+
mu drives **two** multiplexers, tmux and herdr, behind the
|
|
471
|
+
`MuxBackend` interface in `src/mux/types.ts`. Call sites resolve one
|
|
472
|
+
with `await activeMux()` and never name a backend.
|
|
473
|
+
|
|
474
|
+
1. **Add the method to `MuxBackend`** with a doc comment saying what
|
|
475
|
+
it means, not how tmux does it.
|
|
476
|
+
2. **Implement it in BOTH** `src/mux/tmux.ts` and `src/mux/herdr.ts`.
|
|
477
|
+
A backend with no equivalent no-ops (see
|
|
478
|
+
`enableMuPaneBorders*`, `selectLayout`) — it does not throw.
|
|
479
|
+
3. **If only one backend can do it**, make it an OPTIONAL method
|
|
480
|
+
(`paneStatus?()`, `startAgentInPane?()`) and have the caller
|
|
481
|
+
branch on the CAPABILITY being present, never on `mux.name`.
|
|
482
|
+
4. **Decide load-bearing vs best-effort.** Load-bearing calls (spawn,
|
|
483
|
+
send, kill) let `NoMultiplexerError` propagate to exit 5.
|
|
484
|
+
Best-effort calls (identity, decoration, orphan surfacing) wrap in
|
|
485
|
+
try/catch so a missing mux degrades instead of failing the verb —
|
|
486
|
+
`resolveWorkerIdentity` in `src/tasks/claim.ts` is the shape.
|
|
487
|
+
|
|
488
|
+
All tmux invocations go through `src/mux/tmux.ts` `tmux(args)`; all
|
|
489
|
+
herdr invocations through `src/mux/herdr.ts` `herdr(args)`. **No raw
|
|
490
|
+
`execa("tmux", …)` / `execa("herdr", …)` anywhere else.** Each wrapper
|
|
491
|
+
produces a typed error (`TmuxError` / `HerdrError`, both extending
|
|
492
|
+
`MuxError`) and each has an executor seam the tests mock — use
|
|
493
|
+
`installMux()` from `test/_mux.ts` rather than the raw setters.
|
|
494
|
+
|
|
495
|
+
For send-style operations the two backends differ sharply, and that
|
|
496
|
+
difference is the point: tmux needs the canonical bracketed-paste
|
|
497
|
+
sequence in `sendToPane` (naive `tmux send-keys "<text>"` is broken —
|
|
498
|
+
`/`, `?`, `f` get eaten by the agent's TUI), while herdr's
|
|
499
|
+
`agent prompt` is one atomic call. Do not port the tmux workaround
|
|
500
|
+
to herdr.
|
|
501
|
+
|
|
502
|
+
### "Fix a flaky integration test"
|
|
503
|
+
|
|
504
|
+
Integration tests against real tmux can be slow because tmux/sh
|
|
505
|
+
processes need time to settle. Use:
|
|
506
|
+
|
|
507
|
+
- Unique session names (`mu-test-<pid>-<ts>-<random>`) so parallel
|
|
508
|
+
runs never collide.
|
|
509
|
+
- Polling loops (50ms × 10 attempts) when waiting for state to
|
|
510
|
+
propagate, not fixed sleeps.
|
|
511
|
+
- `try { ... } catch {}` cleanup in `afterEach` for tmux session
|
|
512
|
+
kills and DB closes — a failure mid-test should never block the
|
|
513
|
+
next.
|
|
514
|
+
- `setSleepForTests(async () => {})` in unit tests so the real
|
|
515
|
+
`MU_SEND_DELAY_MS` doesn't slow them.
|
|
516
|
+
- `process.env.MU_SPAWN_LIVENESS_MS = "0"` in integration-test
|
|
517
|
+
`beforeEach` to skip the 1500ms post-spawn check (the unit-test
|
|
518
|
+
suite covers it).
|
|
519
|
+
|
|
520
|
+
---
|
|
521
|
+
|
|
522
|
+
## Skill files are context, not documentation
|
|
523
|
+
|
|
524
|
+
`skills/mu/SKILL.md` and `skills/mu/REMOTE_WORKERS.md` are loaded into an
|
|
525
|
+
orchestrator's context window, SKILL.md on every invocation, where every word
|
|
526
|
+
competes with the user's actual work for attention. **Keep them ruthlessly
|
|
527
|
+
terse.**
|
|
528
|
+
|
|
529
|
+
Read the `writing-for-agents` skill before editing either one. Work its levers
|
|
530
|
+
in this order:
|
|
531
|
+
|
|
532
|
+
- **Cache test.** `mu <verb> --help` is a one-command lookup that cannot go
|
|
533
|
+
stale. A skill that restates it is a cache that rots. Verb lists, flag
|
|
534
|
+
enumerations and option tables belong in `--help`; the skill carries only
|
|
535
|
+
what `--help` cannot say — the gotcha, the reason, the trap.
|
|
536
|
+
- **Single source of truth.** One meaning, one place. Scattered advice does not
|
|
537
|
+
get assembled by the reader: it gets missed. Prefer a consolidating edit that
|
|
538
|
+
deletes as much as it adds.
|
|
539
|
+
- **Progressive disclosure.** SKILL.md gets the trigger and the rule.
|
|
540
|
+
REMOTE_WORKERS.md gets the detail. Anything only some branches need goes
|
|
541
|
+
behind the pointer, never in the always-loaded file.
|
|
542
|
+
- **No-op hunt.** Delete any sentence the model already obeys by default. The
|
|
543
|
+
test is behavioural: does it change what an agent does?
|
|
544
|
+
- **Positive instruction.** State the target behaviour. A prohibition drags the
|
|
545
|
+
banned shape into context and makes it more available, so name an
|
|
546
|
+
anti-pattern only where a reader needs to recognise their own instinct.
|
|
547
|
+
|
|
548
|
+
Measure before and after. An edit that grows SKILL.md needs a reason in the
|
|
549
|
+
commit message, and "the new verb needed documenting" is not one.
|
|
550
|
+
|
|
551
|
+
Two failures already paid for this rule. A 910-word section titled "CLI
|
|
552
|
+
overview (only gotchas; use `--help` for full syntax)" was half verb list, and
|
|
553
|
+
a DO/DON'T section was the orchestrator loop written twice — 675 words removed
|
|
554
|
+
with nothing lost. And a warning about wedging a host existed in the file while
|
|
555
|
+
the agent that wedged the host never saw it, because it sat 350 lines from the
|
|
556
|
+
section it applied to. **A skill that sprawls stops being read**, which makes
|
|
557
|
+
sprawl a correctness problem, not a tidiness one.
|
|
558
|
+
|
|
559
|
+
---
|
|
560
|
+
|
|
561
|
+
## What NOT to do
|
|
562
|
+
|
|
563
|
+
- **Don't pad the skill files.** They are context, not docs, and
|
|
564
|
+
`--help` is the verb list. See § Skill files are context, not
|
|
565
|
+
documentation.
|
|
566
|
+
- **Don't add a config file.** mu is CLI flags + env vars.
|
|
567
|
+
- **Don't add a daemon, watcher, or background process.** Every
|
|
568
|
+
invocation is short-lived.
|
|
569
|
+
- **Don't add abstractions for hypothetical future flexibility.**
|
|
570
|
+
Two real impls today, or use a concrete type. (Cautionary tale: a
|
|
571
|
+
prior internal runtime's `RunContext` trait with zero implementors.)
|
|
572
|
+
- **Don't grow stream wrappers around stream wrappers.**
|
|
573
|
+
(`TextStream` / `TextState` / `StreamResult`.)
|
|
574
|
+
- **Don't generate JS strings as a "typed protocol."**
|
|
575
|
+
- **Don't put state-snapshot/handle layering on top of SQLite.**
|
|
576
|
+
SQLite is the canonical state. Read it directly; don't introduce
|
|
577
|
+
a `MuStateHandle` facade.
|
|
578
|
+
- **Don't add a template/discovery system for agent roles** until
|
|
579
|
+
pattern promotion criteria are met.
|
|
580
|
+
- **Don't bundle pi.** It's a peer dep, optional.
|
|
581
|
+
- **Don't write to files outside `~/.local/state/mu/` or the
|
|
582
|
+
project repo** without documenting why.
|
|
583
|
+
- **Don't promote a roadmap item to "shipped"** unless its
|
|
584
|
+
promotion criteria in [docs/ROADMAP.md](docs/ROADMAP.md) are met.
|
|
585
|
+
|
|
586
|
+
---
|
|
587
|
+
|
|
588
|
+
## When you're done
|
|
589
|
+
|
|
590
|
+
Before opening a PR or marking a task complete:
|
|
591
|
+
|
|
592
|
+
```bash
|
|
593
|
+
npm run typecheck && npm run lint && npm run test:fast && npm run test && npm run build
|
|
594
|
+
```
|
|
595
|
+
|
|
596
|
+
The acceptance test (`test/acceptance.integration.test.ts`) must
|
|
597
|
+
pass — it's the "end-to-end works" gate.
|
|
598
|
+
|
|
599
|
+
Checklist for any non-trivial change:
|
|
600
|
+
|
|
601
|
+
- [ ] If you added a typed verb, the corresponding `mu sql`
|
|
602
|
+
workaround row was removed from `docs/USAGE_GUIDE.md`.
|
|
603
|
+
- [ ] If you added vocabulary, `docs/VOCABULARY.md` has the new
|
|
604
|
+
entry.
|
|
605
|
+
- [ ] If you changed an architectural seam,
|
|
606
|
+
`docs/ARCHITECTURE.md` is updated.
|
|
607
|
+
- [ ] [CHANGELOG.md](CHANGELOG.md) has an entry under the upcoming
|
|
608
|
+
version.
|
|
609
|
+
|
|
610
|
+
That's it. Be small, be typed, follow the conventions, ship clean
|
|
611
|
+
green builds.
|