@bluewombat/runtime 0.10.0 → 0.11.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 (160) hide show
  1. package/README.md +202 -94
  2. package/SPECS.md +151 -25
  3. package/dist/cli.js +95 -1
  4. package/dist/cli.js.map +1 -1
  5. package/dist/config/agent-shorthand.d.ts +3 -4
  6. package/dist/config/agent-shorthand.d.ts.map +1 -1
  7. package/dist/config/agent-shorthand.js +3 -4
  8. package/dist/config/agent-shorthand.js.map +1 -1
  9. package/dist/config/fixture-yaml.d.ts +8 -0
  10. package/dist/config/fixture-yaml.d.ts.map +1 -0
  11. package/dist/config/fixture-yaml.js +30 -0
  12. package/dist/config/fixture-yaml.js.map +1 -0
  13. package/dist/config/load-config.d.ts.map +1 -1
  14. package/dist/config/load-config.js +90 -102
  15. package/dist/config/load-config.js.map +1 -1
  16. package/dist/config/parse-args.d.ts.map +1 -1
  17. package/dist/config/parse-args.js +3 -4
  18. package/dist/config/parse-args.js.map +1 -1
  19. package/dist/config/types.d.ts +15 -16
  20. package/dist/config/types.d.ts.map +1 -1
  21. package/dist/config/types.js +1 -1
  22. package/dist/config/types.js.map +1 -1
  23. package/dist/index.d.ts +3 -1
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +1 -0
  26. package/dist/index.js.map +1 -1
  27. package/dist/loop/authority.d.ts +2 -0
  28. package/dist/loop/authority.d.ts.map +1 -1
  29. package/dist/loop/authority.js +11 -1
  30. package/dist/loop/authority.js.map +1 -1
  31. package/dist/loop/deliveries.d.ts.map +1 -1
  32. package/dist/loop/deliveries.js +19 -0
  33. package/dist/loop/deliveries.js.map +1 -1
  34. package/dist/loop/features.d.ts +30 -0
  35. package/dist/loop/features.d.ts.map +1 -1
  36. package/dist/loop/features.js +195 -73
  37. package/dist/loop/features.js.map +1 -1
  38. package/dist/loop/journal.d.ts +7 -0
  39. package/dist/loop/journal.d.ts.map +1 -1
  40. package/dist/loop/journal.js +23 -1
  41. package/dist/loop/journal.js.map +1 -1
  42. package/dist/loop/lock.d.ts +2 -0
  43. package/dist/loop/lock.d.ts.map +1 -1
  44. package/dist/loop/lock.js +10 -0
  45. package/dist/loop/lock.js.map +1 -1
  46. package/dist/loop/open-host.d.ts +10 -0
  47. package/dist/loop/open-host.d.ts.map +1 -1
  48. package/dist/loop/open-host.js +8 -8
  49. package/dist/loop/open-host.js.map +1 -1
  50. package/dist/loop/run-slot.d.ts +14 -0
  51. package/dist/loop/run-slot.d.ts.map +1 -1
  52. package/dist/loop/run-slot.js +53 -5
  53. package/dist/loop/run-slot.js.map +1 -1
  54. package/dist/loop/streams.d.ts +3 -0
  55. package/dist/loop/streams.d.ts.map +1 -1
  56. package/dist/loop/streams.js +4 -3
  57. package/dist/loop/streams.js.map +1 -1
  58. package/dist/loop/tick.d.ts +3 -0
  59. package/dist/loop/tick.d.ts.map +1 -1
  60. package/dist/loop/tick.js +2 -1
  61. package/dist/loop/tick.js.map +1 -1
  62. package/dist/loop/trace.d.ts +1 -0
  63. package/dist/loop/trace.d.ts.map +1 -1
  64. package/dist/loop/trace.js +1 -1
  65. package/dist/loop/trace.js.map +1 -1
  66. package/dist/loop/transformers.d.ts +4 -4
  67. package/dist/loop/transformers.d.ts.map +1 -1
  68. package/dist/loop/transformers.js +16 -25
  69. package/dist/loop/transformers.js.map +1 -1
  70. package/dist/operator/board.d.ts +43 -4
  71. package/dist/operator/board.d.ts.map +1 -1
  72. package/dist/operator/board.js +153 -10
  73. package/dist/operator/board.js.map +1 -1
  74. package/dist/operator/dash-model.d.ts +65 -0
  75. package/dist/operator/dash-model.d.ts.map +1 -0
  76. package/dist/operator/dash-model.js +209 -0
  77. package/dist/operator/dash-model.js.map +1 -0
  78. package/dist/operator/dash-view.d.ts +16 -0
  79. package/dist/operator/dash-view.d.ts.map +1 -0
  80. package/dist/operator/dash-view.js +226 -0
  81. package/dist/operator/dash-view.js.map +1 -0
  82. package/dist/operator/dash.d.ts +34 -0
  83. package/dist/operator/dash.d.ts.map +1 -0
  84. package/dist/operator/dash.js +141 -0
  85. package/dist/operator/dash.js.map +1 -0
  86. package/dist/operator/doctor.d.ts.map +1 -1
  87. package/dist/operator/doctor.js +48 -8
  88. package/dist/operator/doctor.js.map +1 -1
  89. package/dist/operator/explain.d.ts.map +1 -1
  90. package/dist/operator/explain.js +6 -11
  91. package/dist/operator/explain.js.map +1 -1
  92. package/dist/operator/incident-dossier.d.ts +154 -0
  93. package/dist/operator/incident-dossier.d.ts.map +1 -0
  94. package/dist/operator/incident-dossier.js +692 -0
  95. package/dist/operator/incident-dossier.js.map +1 -0
  96. package/dist/operator/incident-system.d.ts +57 -0
  97. package/dist/operator/incident-system.d.ts.map +1 -0
  98. package/dist/operator/incident-system.js +214 -0
  99. package/dist/operator/incident-system.js.map +1 -0
  100. package/dist/operator/incident.d.ts +27 -0
  101. package/dist/operator/incident.d.ts.map +1 -0
  102. package/dist/operator/incident.js +220 -0
  103. package/dist/operator/incident.js.map +1 -0
  104. package/dist/operator/init.js +24 -24
  105. package/dist/operator/init.js.map +1 -1
  106. package/dist/operator/install.d.ts +9 -0
  107. package/dist/operator/install.d.ts.map +1 -0
  108. package/dist/operator/install.js +14 -0
  109. package/dist/operator/install.js.map +1 -0
  110. package/dist/operator/live.js +4 -4
  111. package/dist/operator/live.js.map +1 -1
  112. package/dist/operator/log.d.ts +2 -0
  113. package/dist/operator/log.d.ts.map +1 -1
  114. package/dist/operator/log.js +2 -5
  115. package/dist/operator/log.js.map +1 -1
  116. package/dist/operator/restart.d.ts +20 -0
  117. package/dist/operator/restart.d.ts.map +1 -0
  118. package/dist/operator/restart.js +63 -0
  119. package/dist/operator/restart.js.map +1 -0
  120. package/dist/operator/supervise/act.d.ts +37 -0
  121. package/dist/operator/supervise/act.d.ts.map +1 -0
  122. package/dist/operator/supervise/act.js +354 -0
  123. package/dist/operator/supervise/act.js.map +1 -0
  124. package/dist/operator/supervise/config.d.ts +74 -0
  125. package/dist/operator/supervise/config.d.ts.map +1 -0
  126. package/dist/operator/supervise/config.js +175 -0
  127. package/dist/operator/supervise/config.js.map +1 -0
  128. package/dist/operator/supervise/council.d.ts +45 -0
  129. package/dist/operator/supervise/council.d.ts.map +1 -0
  130. package/dist/operator/supervise/council.js +97 -0
  131. package/dist/operator/supervise/council.js.map +1 -0
  132. package/dist/operator/supervise/decision.d.ts +46 -0
  133. package/dist/operator/supervise/decision.d.ts.map +1 -0
  134. package/dist/operator/supervise/decision.js +137 -0
  135. package/dist/operator/supervise/decision.js.map +1 -0
  136. package/dist/operator/supervise/exec.d.ts +28 -0
  137. package/dist/operator/supervise/exec.d.ts.map +1 -0
  138. package/dist/operator/supervise/exec.js +47 -0
  139. package/dist/operator/supervise/exec.js.map +1 -0
  140. package/dist/operator/supervise/memory.d.ts +33 -0
  141. package/dist/operator/supervise/memory.d.ts.map +1 -0
  142. package/dist/operator/supervise/memory.js +73 -0
  143. package/dist/operator/supervise/memory.js.map +1 -0
  144. package/dist/operator/supervise/observe.d.ts +47 -0
  145. package/dist/operator/supervise/observe.d.ts.map +1 -0
  146. package/dist/operator/supervise/observe.js +73 -0
  147. package/dist/operator/supervise/observe.js.map +1 -0
  148. package/dist/operator/supervise/prompts.d.ts +60 -0
  149. package/dist/operator/supervise/prompts.d.ts.map +1 -0
  150. package/dist/operator/supervise/prompts.js +338 -0
  151. package/dist/operator/supervise/prompts.js.map +1 -0
  152. package/dist/operator/supervise/review.d.ts +24 -0
  153. package/dist/operator/supervise/review.d.ts.map +1 -0
  154. package/dist/operator/supervise/review.js +51 -0
  155. package/dist/operator/supervise/review.js.map +1 -0
  156. package/dist/operator/supervise/supervise.d.ts +26 -0
  157. package/dist/operator/supervise/supervise.d.ts.map +1 -0
  158. package/dist/operator/supervise/supervise.js +484 -0
  159. package/dist/operator/supervise/supervise.js.map +1 -0
  160. package/package.json +14 -14
package/README.md CHANGED
@@ -53,19 +53,23 @@ package's README — Host does not grow a `--repo` flag for it.
53
53
  | --------------- | -------------------------------------------------------------------------------------- |
54
54
  | `mason run` | Drain the TicketTracker, run Conductor, report |
55
55
  | `mason watch` | Snapshot the ledger and follow the journal. Ctrl-C leaves the worker running |
56
- | `mason status` | Snapshot the ledger, then exit. `--json` for scripts |
56
+ | `mason dash` | A dashboard that redraws itself: version (npm or local copy), worker running or stopped, active / upcoming / past. `t` flips to a dependency tree, `q` quits; `--tree`, `--once`, `--interval-ms N`, `--no-color` |
57
+ | `mason status` | Snapshot the ledger as a Feature / Ticket tree, then exit. `--json` for scripts |
57
58
  | `mason log` | Replay the journal, with the reason a phase stopped. `--problems` for just those |
58
59
  | `mason init` | Set this directory up: asks, writes the config and the Builder stub, then runs `setup` |
59
60
  | `mason setup` | Prepare the tracker. A plan by default; `--apply` writes. Exit 1 on a blocked step |
60
61
  | `mason doctor` | Check the config, the slots, and the manager. Exit 1 on any failure |
62
+ | `mason incident` | Gather what is known about one stop (ledger, journal, streams, transcripts) into one page, for a person to read or hand on. Without a key, list what waits. `--json`, `--out DIR`, `--with-config`. Read-only |
63
+ | `mason supervise` | Watch a run from another process and do what a person would: find the cause of a stop with a council of agents, resume the run, and offer the change that stops it coming back. `--once`, `--dry-run`, `--supervisor-config FILE` |
61
64
  | `mason explain` | Say what an agent role starts with: prompt, plugins, skills, the project's profile. Read-only, offline. Exit 1 on a setup every attempt would fail on |
62
65
  | `mason cancel` | Abandon a Ticket by key, when the tracker cannot say it is gone |
66
+ | `mason restart` | Resume an escalated Feature with a fresh workspace, made again from its line. Offline: the tracker is not told |
63
67
  | `mason release` | Drop a held unit without waiting for the bail clock |
64
68
 
65
69
  ### What an agent role starts with
66
70
 
67
71
  ```bash
68
- mason explain builder.producer # one role; no role names all of them
72
+ mason explain ticket.build.producer # one role; no role names all of them
69
73
  mason explain --json # the same facts for a script
70
74
  ```
71
75
 
@@ -128,7 +132,9 @@ mason watch
128
132
  That is the operator surface: the durable state of every Ticket, then the film
129
133
  of the current step (`submitted` included). It does not talk to the tracker, and
130
134
  it cannot `resume` or claim. `status` is the same snapshot without the
131
- follow. `--json` prints that snapshot as JSON.
135
+ follow: a header with the counts, each Feature with its Tickets under it
136
+ (escalated Features first, finished ones last, keys in numeric order), then the
137
+ Tickets of no Feature. `--json` prints that tree as JSON.
132
138
 
133
139
  When a run only printed `unavailable:<key>`, the reason is already in the
134
140
  journal and `log` is how you read it:
@@ -144,6 +150,100 @@ distinct failure. If a slot was started with `--transcript-dir`, the newest
144
150
  transcript files are named underneath, with the agent exit and duration read
145
151
  from the start of each file.
146
152
 
153
+ When a Ticket or a Feature is waiting for a person, `incident` gathers what the
154
+ run knows of it:
155
+
156
+ ```bash
157
+ mason incident # what waits for a person
158
+ mason incident 106 # one stop: the key, or the tracker's number
159
+ mason incident 106 --out ./inc # incident.md and incident.json, to hand on
160
+ mason incident 106 --with-config # also the config file, parsed as YAML and blanked by path and by shape
161
+ ```
162
+
163
+ The page says what the run was made of (versions, what the configuration switched
164
+ on), who refused the work and what they said, the Tickets of the
165
+ Feature with their pull requests, the Gate's configuration, the journal's events
166
+ of the last round and where the streams and transcripts are. What it could not
167
+ find is listed under "Not found". Streams exist only if the Project turned them
168
+ on (`--streams-dir`); without them the page has the Gate's report but not what
169
+ the Gate printed.
170
+
171
+ ### Supervising a run
172
+
173
+ > **In test, and meant to leave mason.** Do not rely on it unattended. Its move out of Host is issue #55.
174
+
175
+ A run stops: a Gate blocks, two Tickets conflict, the disk fills, the worker dies. Each time, a person
176
+ reads the page above, finds the cause, resumes, and fixes whatever made it happen. `supervise` does that
177
+ by hand-work, in a loop, in a process of its own on the same ledger:
178
+
179
+ ```bash
180
+ mason supervise --dry-run # deliberate and write what it would do; do none of it
181
+ mason supervise # watch, decide, act
182
+ mason supervise --once # one pass, then exit
183
+ ```
184
+
185
+ It watches for four things: an escalated Ticket or Feature, a journal silent while work is active, a
186
+ worker gone, a disk running low. The last two it answers directly. For the first two it gathers the
187
+ `incident` page, then three agents read it from different seats and cannot see each other (the
188
+ *root cause*: the chain, with the code read; the *remedies*: every way to resume and to prevent; the
189
+ *skeptic*: how the obvious answer fails). An *arbiter* reads the three and the code and decides: a
190
+ cause with evidence, the moves that put the run back in motion, and the changes to make so it does not
191
+ return. A decision it is not sure of (`confidence: low`) is not acted on: it is asked again with that
192
+ on record. An arbiter answer refused for its shape is not rerun: a small agent (`agents.reformatter`,
193
+ Haiku by default) rewrites the answer's own text into the required form, without the reports or the code.
194
+ A stop only a person can settle is not resumed, but the changes that keep it from
195
+ returning are still made.
196
+
197
+ Every member reads the target's *rules* first (`targets.<name>.rules`, default `AGENTS.md`, which routes to
198
+ the decisions and the product): the code says what mason does, the rules say what it must be. The arbiter
199
+ states each fix as a result, its constraints and its proof, not as a design, and adds its *conformity* (the
200
+ rules and decisions it touches). A fix that cannot reach its result without revisiting an accepted decision
201
+ carries `revises`: it is not made and a person decides. The guard also reads the proposal before the fixer
202
+ writes anything, and a change whose same guard point is rejected twice in a row is stopped for a person.
203
+
204
+ The changes are made by a *fixer* agent in a worktree of its own, checked by the repository's own
205
+ commands, then read by a *guard* agent that is shown the change and the repository but not the stop (does it
206
+ fit the product, its decisions, its callers, its documents, and is it proved?) and refused without evidence.
207
+ A change the guard approves is offered as a pull request, and left for a person to fold unless the target has
208
+ `autoMerge: true`; nothing that waits on it moves meanwhile. Then whatever follows the fold
209
+ (a release, an upgrade) runs. A Project declares which repositories may be changed; with none, the
210
+ supervisor only moves the run. The moves are `resume`, `restart` (a Feature), `cancel`, `start-worker`,
211
+ `restart-worker` and `cleanup-disk`; a bound is never one of them.
212
+
213
+ It is configured in `mason.supervisor.yaml`, next to the main configuration. Nothing in it is required
214
+ but what the Project wants done:
215
+
216
+ ```yaml
217
+ pollIntervalMs: 15000
218
+ maxAttempts: 3 # per ticket, before a person is asked
219
+ cooldownMs: 300000 # after acting, the same finding is left this long to take effect
220
+ worker:
221
+ start: { cmd: [mason, run], timeoutMs: 30000 }
222
+ resume: # how a resume reaches the tracker: {key} and {number} are filled
223
+ cmd: [gh, issue, edit, "{number}", --repo, owner/name, --add-label, to-resume]
224
+ diskCleanup:
225
+ - cmd: [sh, -c, "rm -rf .mason/workspaces/*done*"]
226
+ notify: { cmd: [say, "mason needs you: {subject}"] } # optional; needs-human.md is written either way
227
+ agents: # defaults to Claude Code: plan mode for the council, a worktree for the fixer
228
+ arbiter: { cmd: [claude, -p, "{prompt}", --model, opus, --permission-mode, plan, "{addDirArgs}"] }
229
+ targets:
230
+ mason:
231
+ repo: ../bluewombat # a checkout; the supervisor never edits it, it adds worktrees
232
+ base: main
233
+ checks:
234
+ - { cmd: [npm, test] }
235
+ - { cmd: [npm, run, lint] }
236
+ deny: [.github/workflows/] # paths the fixer may not change
237
+ after: # once folded: cut and install the release, restart the worker
238
+ - { cmd: [sh, -c, "./scripts/ship-and-restart.sh"] }
239
+ ```
240
+
241
+ `open`, `merge` and `merged` default to `gh pr create`, `gh pr merge --squash --auto` and a `gh pr view`
242
+ state check; a repository that folds otherwise names its own. Everything the supervisor does is filed
243
+ under `<home>/supervisor/`: `events.jsonl` (the film), `memory.json` (attempts per subject),
244
+ `incidents/<stamp>-<key>/` (the dossier, each member's prompt and answer, `decision.json`,
245
+ `outcomes.json`) and `needs-human.md`, the one page a person needs to read: what only they can decide.
246
+
147
247
  To abandon a Ticket the tracker can no longer mention (a deleted issue, a
148
248
  manager with no `probe`), stop `run` if it holds the ledger lock, then:
149
249
 
@@ -162,6 +262,18 @@ mason release github:owner/name#19
162
262
 
163
263
  `release` destroys the workspace and forgets it; the next `run` isolates a fresh one.
164
264
 
265
+ A Feature that escalated keeps its work. When a person then changes its line and resumes it,
266
+ the work takes in what the line gained, merged into it, before it is judged again; a conflict
267
+ escalates the Feature again, naming it. To throw the work away instead, stop `run` if it holds
268
+ the lock, then:
269
+
270
+ ```bash
271
+ mason restart github:owner/name#106 # or just 106
272
+ ```
273
+
274
+ The Feature must be escalated. Its workspace is removed and it is resumed; the next `run`
275
+ cuts the work again from the line. The tracker is not told.
276
+
165
277
  The film is `<ledger>/events.jsonl`, next to `cursor`. It is not truth after a
166
278
  crash — the ledger is. The file can grow; there is no rotation yet.
167
279
 
@@ -178,43 +290,48 @@ value that starts with `@`, like every `@bluewombat/...` package name, needs
178
290
  quotes: `@` is a reserved YAML indicator at the start of a plain scalar.
179
291
 
180
292
  ```yaml
181
- featureTracker:
182
- package: "@bluewombat/manager-fake"
183
- options:
184
- source: ./.mason/source
185
- target: ./.mason/threads
186
- ticketTracker:
187
- package: "@bluewombat/manager-fake"
188
- options:
189
- source: ./.mason/source
190
- target: ./.mason/threads
191
293
  workLine:
192
294
  stable: ./work-line-stable
193
295
  isolation: "@bluewombat/isolation-copy"
194
296
  warm:
195
297
  cmd: [pnpm, install, --frozen-lockfile]
196
298
  timeoutMs: 600000
197
- builder:
198
- producer:
199
- cmd: [node, ./mason-builder.mjs]
200
- timeoutMs: 600000
201
- repair:
202
- cmd: [node, ./mason-repair.mjs]
203
- timeoutMs: 600000
204
- gates: { defaultTimeoutMs: 120000, gates: [] }
205
- maxAttempts: 3
206
299
  ticket:
300
+ tracker:
301
+ package: "@bluewombat/manager-fake"
302
+ options:
303
+ source: ./.mason/source
304
+ target: ./.mason/threads
305
+ build:
306
+ producer:
307
+ cmd: [node, ./mason-builder.mjs]
308
+ timeoutMs: 600000
309
+ repair:
310
+ cmd: [node, ./mason-repair.mjs]
311
+ timeoutMs: 600000
312
+ gates: { defaultTimeoutMs: 120000, gates: [] }
313
+ maxAttempts: 3
314
+ validate: { agent: false }
207
315
  gates: { defaultTimeoutMs: 900000, gates: [] }
316
+ feature:
317
+ tracker:
318
+ package: "@bluewombat/manager-fake"
319
+ options:
320
+ source: ./.mason/source
321
+ target: ./.mason/threads
208
322
  authority:
209
323
  enabled: false
210
324
  timeoutMs: 600000
211
325
  pollIntervalMs: 10000
212
326
  ```
213
327
 
328
+ The file reads by level, and each level in the order it is used: `ticket` and `feature` both hold
329
+ `tracker`, then what makes the work (`build` for a Ticket, `test` for a Feature), `validate`, `gates`, `fix`.
330
+
214
331
  | Field | Meaning |
215
332
  | --------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
216
- | `featureTracker` | `package` (a name or path that exports `createManager`) and `options`: where Features are written and signalled |
217
- | `ticketTracker` | Same shape: where the Tickets of a Feature are, and the Authority that holds the lines. May name the same package as `featureTracker`, or another |
333
+ | `feature.tracker` | `package` (a name or path that exports `createManager`) and `options`: where Features are written and signalled |
334
+ | `ticket.tracker` | Same shape: where the Tickets of a Feature are, and the Authority that holds the lines. May name the same package as `feature.tracker`, or another |
218
335
  | `workLine.stable` | Existing directory the work folds into. Optional when the manager names a reference work line: Host then keeps its own copy at `<home>/work-line` |
219
336
  | `workLine.targetStable` | A second copy of the work line, on the branch a Feature is folded into: what is merged into the Feature when it is brought up to date. Host keeps its own at `<home>/target-line` when the manager names a reference; required with an Authority otherwise |
220
337
  | `workLine.isolation` | Package name or path exporting `strategy` — required. Host loads it like a manager; Isolator/Integrator do not sniff |
@@ -227,26 +344,25 @@ pollIntervalMs: 10000
227
344
  | `ledger` | WorkLedger root (created). The Cursor is a file inside it. Default `<home>/ledger` |
228
345
  | `persist` | Package name or path exporting `openPersist({ ledgerRoot })`: how the ledger is stored under that root. Default `@bluewombat/persist-fs`; `@bluewombat/persist-sqlite` ships too. `watch` reads through the same backend |
229
346
  | `agents` | Agents a pass names instead of writing `cmd` (§ Agent passes written short) |
230
- | `builder.tests` | Required. The tests of the task, written before its code by an agent that is not the producer: `{ agent, prompt, homePlugins, appDeckProfile, timeoutMs, gates }`, or `{ agent: false }` for none. Its `gates` check them (`tests-red`: they must fail, for the right reason). What it wrote is committed as a checkpoint titled `Tests: …`, which `builder.gates` can keep intact (`tests-frozen`). A ticket the tracker labels as having no tests first (`no-tests` with the GitHub manager) runs no agent and no `tests-red` |
231
- | `builder.e2e` | Optional. Declares the e2e acceptance check (see § The e2e acceptance check): the pass that makes the Ticket holding the suite, `{ agent, prompt, homePlugins, appDeckProfile, timeoutMs, gates }` or `{ cmd, timeoutMs, gates }`. One command for the first pass and for a pass a Gate refused; its own `gates` (they check the form: compiles, one test per manifest id, no application file touched). Nothing is borrowed from `builder.producer`, `builder.repair` or `builder.gates`. No tests are written first for that Ticket. Needs a Feature Gate with `skipWhen: skip-test-e2e`, and the other way round |
232
- | `builder.producer` | `cmd` / `timeoutMs`. Spawned in the workspace. Exit 0 runs `builder.gates` |
233
- | `builder.repair` | The same, for a pass a Gate refused. **Required**, and never inherited: it names its own command. It is judged by the same `builder.gates` as the producer |
234
- | `builder.gates` | `defaultTimeoutMs` and `gates`: the one Gate list for `builder.producer` and `builder.repair`. `gates` written on either pass is refused, naming this key. Empty: the unit runs ungated |
235
- | `builder.maxAttempts` | Attempts one Task may start. Default 3 |
347
+ | `feature.test` | Optional. Declares the feature-test acceptance check (see § The feature-test acceptance check): the pass that makes the Ticket holding the suite, `{ agent, prompt, homePlugins, appDeckProfile, timeoutMs, gates }` or `{ cmd, timeoutMs, gates }`. One command for the first pass and for a pass a Gate refused; its own `gates` (they check the form: compiles, one test per manifest id, no application file touched). Nothing is borrowed from `ticket.build.producer`, `ticket.build.repair` or `ticket.build.gates`. Needs a Feature Gate with `skipWhen: skip-feature-test`, and the other way round |
348
+ | `ticket.build.producer` | `cmd` / `timeoutMs`. Spawned in the workspace. Exit 0 runs `ticket.build.gates` |
349
+ | `ticket.build.repair` | The same, for a pass a Gate refused. **Required**, and never inherited: it names its own command. It is judged by the same `ticket.build.gates` as the producer |
350
+ | `ticket.build.gates` | `defaultTimeoutMs` and `gates`: the one Gate list for `ticket.build.producer` and `ticket.build.repair`. `gates` written on either pass is refused, naming this key. Empty: the unit runs ungated |
351
+ | `ticket.build.maxAttempts` | Attempts one Task may start. Default 3 |
236
352
  | `ticket.fix` | `cmd` / `timeoutMs` / `gates`. Spawned when a judgement of the whole refused it — by an Authority, by `ticket.validate`, or by both. Judged by its own `gates` |
237
- | `ticket.validate` | `cmd` / `timeoutMs` / `gates`. A local, read-only judge of the assembled ticket, run after align, with or without an Authority. A refusal is repaired by `ticket.fix` the same as an Authority's. Judged by its own `gates`, never `builder`'s or `ticket.fix`'s. `validate` is required when a `ticket` block is written, and so is `feature.validate`: `{ agent: false, gates }` turns the agent off and keeps the gates, and `{ agent: false }` with no gates turns the review off — a bare `gates:` counts as none. |
353
+ | `ticket.validate` | `cmd` / `timeoutMs` / `gates`. A local, read-only judge of the assembled ticket, run after align, with or without an Authority. A refusal is repaired by `ticket.fix` the same as an Authority's. Judged by its own `gates`, never `ticket.build`'s or `ticket.fix`'s. `validate` is required when a `ticket` block is written, and so is `feature.validate`: `{ agent: false, gates }` turns the agent off and keeps the gates, and `{ agent: false }` with no gates turns the review off — a bare `gates:` counts as none. |
238
354
  | `ticket.maxAttempts` | Attempts that one fix may start. Default 3 |
239
355
  | `ticket.gates` | `defaultTimeoutMs` and `gates`: the produce:false judgement-only pass — no producer of its own, re-checking the assembled ticket. Empty: the Authority judges alone. Independent of `ticket.fix.gates` / `ticket.validate.gates` — never shared with either |
240
356
  | `timeoutMs` | How long a child **outside** a Task may run: manager, isolations |
241
357
  | `ticket.maxRefusals` | Times the work may be sent back before it escalates, by an Authority, by `ticket.validate`, or by both — one shared budget. Default 3 |
242
- | `ticket.maxParallel` | How many Tickets of a Project are worked on at once. Default 1: one after the other. Above 1 needs `authority.enabled`; a Ticket waiting on a check or a person holds a place, the others go on. Tickets that wait on another (*Blocked by*) still start only once it is `done` |
243
- | `feature` | Required with `authority.enabled`. How a Feature's line is delivered to the line it is folded into, by the same steps as a Ticket: the final line is merged into the Feature's work (`workLine.targetStable`, or Host's own copy of the reference), `validate` (same shape as `ticket.validate`) reviews the whole against the Feature's request and its `gates` (at least one) are where the project's checks on the whole line run, a refusal goes to `fix`, the line is published, and `gates` (at least one, the CI) judge it. `maxAttempts` and `maxRefusals` (default 3, same rule as a Ticket's) are its own. Nothing falls back to `ticket`. The merge itself is automatic only with `merge-when-ready`; the delivery to the Authority is for every Feature |
358
+ | `ticket.maxParallel` | How many Tickets of a Project are worked on at once. Default 1: one after the other. Above 1 needs `authority.enabled`; a Ticket waiting on a check or a person holds a place, the others go on. Tickets that wait on another (*Blocked by*) still start only once it is `done`. A resumed Ticket takes a place too: while all of them are held by Tickets being worked on, the resume waits and its signal is kept |
359
+ | `feature` | Required with `authority.enabled`. How a Feature's line is delivered to the line it is folded into, by the same steps as a Ticket: the final line is merged into the Feature's work (`workLine.targetStable`, or Host's own copy of the reference), `validate` (same shape as `ticket.validate`) reviews the whole against the Feature's request and its `gates` (at least one) are where the project's checks on the whole line run, a refusal goes to `fix`, the line is published, and `gates` (at least one, the CI) judge it. `maxAttempts` and `maxRefusals` (default 3, same rule as a Ticket's) are its own. `settledTimeoutMs` (default 14400000, 4 hours) is how long a submitted Feature waits while the manager cannot say whether its Submission was folded (no answer, or no `settled`) before it escalates; a Submission the tracker says is still open waits for its reviewer. Nothing falls back to `ticket`. The merge itself is automatic only with `merge-when-ready`; the delivery to the Authority is for every Feature |
244
360
  | `pollIntervalMs` | Set it and the process keeps draining until SIGINT. Absent: one tick. Pick it with § Choosing `pollIntervalMs` — 10000 is the floor worth having |
245
361
  | `observability.streams` | `enabled`, `dir`, `keep`. Films what every child says, as it says it — one file per child under `<dir>/<ticket>/`, named in the journal by a `stream-opened` line. `dir` resolves against the config file; default `<home>/streams`. `keep` is `stdout` / `stderr`, default both. Off unless `enabled` is true: a stream is the Project's own code and prompts in the clear |
246
362
 
247
363
  Every `cmd` is named where it is used, and nothing falls back to a neighbour: a
248
364
  `repair` with no command of its own is refused rather than quietly running the
249
- first-pass agent again. `builder` makes the unit of work. `ticket` judges the whole
365
+ first-pass agent again. `ticket.build` makes the unit of work. `ticket` judges the whole
250
366
  and, when that judgement refuses it, runs `ticket.fix` — not a second first-pass.
251
367
  `ticket.validate`, when declared, is a second, local judge of the whole: it
252
368
  runs after align, before the Authority sees the work (or before the final
@@ -275,23 +391,23 @@ agents:
275
391
  denyShell: [] # commands refused to the agent, e.g. [gh, vercel]; ssh and scp always are
276
392
  transcripts: ./.mason/transcripts
277
393
  pluginsOut: ./.mason/plugins # where deck profiles are built
278
- builder:
279
- producer:
280
- agent: cursor
281
- prompt: ./prompts/builder.md
282
- homePlugins: [./ai-plugins/core/builder]
283
- appDeckProfile: .ai/profiles/builder
284
- args: [--mode, agent] # the vendor CLI's own flags
285
- timeoutMs: 1200000
286
- gates: { defaultTimeoutMs: 120000, gates: [] }
394
+ ticket:
395
+ build:
396
+ producer:
397
+ agent: cursor
398
+ prompt: ./prompts/builder.md
399
+ homePlugins: [./ai-plugins/core/builder]
400
+ appDeckProfile: .ai/profiles/builder
401
+ args: [--mode, agent] # the vendor CLI's own flags
402
+ timeoutMs: 1200000
287
403
  ```
288
404
 
289
405
  Host writes the `cmd` it stands for — `node <slots>/<role>.mjs --prompt-file …
290
406
  -- node <slots>/agents/cursor.mjs --transcript-dir … --plugin-dir … --app-deck-profile
291
407
  … --plugins-out … --agent-arg …` — with `<slots>` the
292
408
  `node_modules/@bluewombat/slots` next to the config. Nothing past the config
293
- knows a pass was written short. The roles: `builder.producer`,
294
- `builder.repair`, `ticket.fix`, `ticket.validate`, and `authority.describe`
409
+ knows a pass was written short. The roles: `ticket.build.producer`,
410
+ `ticket.build.repair`, `ticket.fix`, `ticket.validate`, and `authority.describe`
295
411
  (an argv, or `{ agent, prompt?, homePlugins?, appDeckProfile?, args? }`).
296
412
 
297
413
  | Key | Meaning |
@@ -387,20 +503,20 @@ Examples ship in [`@bluewombat/slots`](../../plugins/slots/README.md), installed
387
503
  the shipped roles and agents (Cursor CLI, Claude Code), the shipped Gates, and
388
504
  their options. What follows is what Host guarantees them.
389
505
 
390
- ```json
391
- "builder": {
392
- "producer": {
393
- "cmd": [
394
- "node", "./node_modules/@bluewombat/slots/builders/producer.mjs",
395
- "--",
396
- "node", "./node_modules/@bluewombat/slots/agents/claude.mjs"
397
- ],
398
- "timeoutMs": 600000
399
- }
400
- }
506
+ ```yaml
507
+ ticket:
508
+ build:
509
+ producer:
510
+ cmd:
511
+ - node
512
+ - ./node_modules/@bluewombat/slots/builders/producer.mjs
513
+ - --
514
+ - node
515
+ - ./node_modules/@bluewombat/slots/agents/claude.mjs
516
+ timeoutMs: 600000
401
517
  ```
402
518
 
403
- `builder.repair` is required too, with its own `cmd` and `timeoutMs`. The
519
+ `ticket.build.repair` is required too, with its own `cmd` and `timeoutMs`. The
404
520
  composition with `--` is the same.
405
521
 
406
522
  ### Builder
@@ -437,16 +553,16 @@ that completes cleanly but finds a problem is still a refusal.
437
553
 
438
554
  ### Gates
439
555
 
440
- Four sequences: `builder.gates`, shared by `builder.producer` and
441
- `builder.repair` (a repair is judged by exactly what judged the first try),
556
+ Four sequences: `ticket.build.gates`, shared by `ticket.build.producer` and
557
+ `ticket.build.repair` (a repair is judged by exactly what judged the first try),
442
558
  then one per assembly role — `ticket.fix`, `ticket.validate` — plus
443
559
  `ticket.gates` for the one judgement-only pass with no producer of its own
444
560
  (re-checking the assembled ticket; `ci-green` reads the checks the work line
445
561
  ran on what the Publisher placed, so it belongs there). The assembly lists are
446
562
  not shared with each other or with the builder, and no dispatch chooses between
447
563
  two buckets: a check right for one pass is not necessarily right for another,
448
- even within the same stage. `gates` written on `builder.producer` or
449
- `builder.repair` is refused, and the message names `builder.gates`.
564
+ even within the same stage. `gates` written on `ticket.build.producer` or
565
+ `ticket.build.repair` is refused, and the message names `ticket.build.gates`.
450
566
 
451
567
  Implementer's Gates are told `--stage unit` or `--stage ticket` alongside
452
568
  `--id`, `--attempt`, `--gate-id` and `--intention`.
@@ -454,7 +570,7 @@ Most have no use for it. `workspace-changed` does: "nothing changed" means
454
570
  nothing was done in a unit, and is the correct outcome for an assembly that
455
571
  needed nothing.
456
572
 
457
- `init` writes an empty list on `builder.producer` and `builder.repair`; add a
573
+ `init` writes an empty list on `ticket.build.producer` and `ticket.build.repair`; add a
458
574
  Gate when the Project has a check.
459
575
 
460
576
  Every child carries its own ceiling and no other. A Gate entry may name a
@@ -464,7 +580,7 @@ from what another child left behind, so a slow producer cannot leave its
464
580
  Gates a millisecond to answer in.
465
581
 
466
582
  What bounds the unit is therefore what you wrote:
467
- `builder.maxAttempts × (builder.producer.timeoutMs + Σ builder.gates timeouts)`
583
+ `ticket.build.maxAttempts × (ticket.build.producer.timeoutMs + Σ ticket.build.gates timeouts)`
468
584
  for a first pass, and the same shape with `repair` in place of `producer` once
469
585
  a Gate has refused one.
470
586
  A repair and an assembly fix each carry their own `timeoutMs`. An assembly fix
@@ -484,59 +600,51 @@ is bounded the same way from `ticket.maxAttempts` and `ticket.fix`.
484
600
  }
485
601
  ```
486
602
 
487
- ### The e2e acceptance check
603
+ ### The feature-test acceptance check
488
604
 
489
- A Project proves each Feature by an acceptance suite, run once on the assembled Feature
605
+ A Project proves each Feature by its own tests, run once on the assembled Feature
490
606
  line, when its config says so, by a pair:
491
607
 
492
608
  ```yaml
493
- builder:
494
- e2e: # makes the Ticket that holds the suite (label `e2e` on the tracker)
609
+ feature:
610
+ test: # makes the Ticket that holds the suite (label `feature-test` on the tracker)
495
611
  agent: claude
496
- prompt: ./prompts/e2e.md # {{task}} is the scenarios; {{report}}, {{refused_by}}, {{refusal}} on a refused pass
612
+ prompt: ./prompts/feature-test.md # {{task}} is the scenarios; {{report}}, {{refused_by}}, {{refusal}} on a refused pass
497
613
  timeoutMs: 1800000
498
614
  gates: # form only: it compiles, one test per manifest id, no application file touched
499
615
  gates:
500
- - { id: e2e-form, argv: [...], timeoutMs: 120000 }
501
- feature:
616
+ - { id: feature-tests-form, argv: [...], timeoutMs: 120000 }
502
617
  validate:
503
618
  agent: false
504
619
  gates:
505
620
  defaultTimeoutMs: 120000
506
621
  gates:
507
- - id: e2e
508
- skipWhen: skip-test-e2e # Host does not run this Gate for a Feature that carries the label
622
+ - id: feature-tests
623
+ skipWhen: skip-feature-test # Host does not run this Gate for a Feature that carries the label
509
624
  timeoutMs: 720000
510
625
  argv:
511
626
  - node
512
- - ./node_modules/@bluewombat/slots/gates/e2e.mjs
513
- - --up
514
- - pnpm e2e:up
515
- - --ready
516
- - pnpm e2e:ready
627
+ - ./node_modules/@bluewombat/slots/gates/feature-tests.mjs
517
628
  - --run
518
- - pnpm e2e
519
- - --down
520
- - pnpm e2e:down
629
+ - pnpm test --grep "{ids}" --reporter=junit --outputFile={junit}
521
630
  ```
522
631
 
523
632
  `skipWhen` is the one condition Host reads for a Gate, on a Feature's Gates only
524
633
  (`feature.validate`, `feature.gates`, `feature.fix`); its one value today is
525
- `skip-test-e2e`, whatever the tracker calls that label. A Gate left out is not a Gate that
526
- passed: the journal gets `gate-skipped` and the trace says "`e2e` not run: the Feature carries
527
- skip-test-e2e". The Gate knows nothing of the skip and no environment variable carries it.
634
+ `skip-feature-test`, whatever the tracker calls that label. A Gate left out is not a Gate that
635
+ passed: the journal gets `gate-skipped` and the trace says "`feature-test` not run: the Feature carries
636
+ skip-feature-test". The Gate knows nothing of the skip and no environment variable carries it.
528
637
 
529
- The tracker side: a Ticket carrying the e2e label (`e2eLabel` of the TicketTracker manager) is
530
- made by `builder.e2e` and has no tests written first; a Feature carrying the skip label
531
- (`skipTestE2eLabel` of the FeatureTracker manager) has that Ticket cancelled, with a line in
638
+ The tracker side: a Ticket carrying the feature-test label (`featureTestLabel` of the TicketTracker manager) is
639
+ made by `feature.test`; a Feature carrying the skip label
640
+ (`skipFeatureTestLabel` of the FeatureTracker manager) has that Ticket cancelled, with a line in
532
641
  the trace. The label is read again until the Feature is submitted. A Project that declares the
533
- check refuses a Feature that holds neither an e2e Ticket nor the skip label, or whose e2e
642
+ check refuses a Feature that holds neither a feature-test Ticket nor the skip label, or whose feature-test
534
643
  Ticket is not blocked by every other Ticket, as `invalid`, with the reason as a comment.
535
644
 
536
645
  What the Gate proves is a red/green verdict; what the operator reads is its text in the
537
646
  journal (`gate-finished`) — a red suite is also in the reason of the refusal the fix is
538
- given and in the escalation. A passing suite's text is not shown on the pull request: see
539
- RESERVATIONS I17.
647
+ given and in the escalation. A passing suite's text is not shown on the pull request (issue #62).
540
648
 
541
649
  ### Describer
542
650
 
@@ -610,7 +718,7 @@ it each stop `mason run` at boot, and `mason doctor` reports the same offline
610
718
  and is the operator's directory.
611
719
 
612
720
  ```
613
- assembled → align → recheck (builder.gates again, on the aligned work)
721
+ assembled → align → recheck (ticket.build.gates again, on the aligned work)
614
722
  conflict or failed → ticket.fix, then align again
615
723
  → ticket.validate (optional, local)
616
724
  refused → ticket.fix, then align again, recheck, validate again
@@ -631,13 +739,13 @@ fast-forward — the work line moved where the Authority is, and the next
631
739
  Isolation must start from that version, not from a copy left behind.
632
740
 
633
741
  A refusal is not a second failure path: its report reaches `ticket.fix`
634
- exactly as a Gate's report reaches `builder.repair`. After `ticket.maxRefusals`
742
+ exactly as a Gate's report reaches `ticket.build.repair`. After `ticket.maxRefusals`
635
743
  refusals the ticket escalates with that report as its Trace, and `done` names
636
744
  the Submission rather than a directory on the machine that ran.
637
745
 
638
746
  ## Write another manager
639
747
 
640
- Host loads whatever `featureTracker.package` and `ticketTracker.package` name, as long as it exports `createManager`. The
748
+ Host loads whatever `feature.tracker.package` and `ticket.tracker.package` name, as long as it exports `createManager`. The
641
749
  contract and a worked example are in
642
750
  [`@bluewombat/manager-kit`](../manager-kit/README.md). Nothing in Host changes.
643
751
 
@@ -706,7 +814,7 @@ change that breaks it does not merge.
706
814
  | `config/` | What a Project wrote: argv, `mason.config.yaml`, discovery, `HostOptions`. Pure: YAML in, options out | A runtime value (a Journal, a Port, a process handle) | Node, `manager-kit` types |
707
815
  | `plugins/` | Load a package by name and check its shape: a manager, an isolation strategy | What to do with the loaded thing | `config` |
708
816
  | `loop/` | The process: listen → probe → adapt → admit → run → report; composition (`open-host`, `transformers`, Authority, work line, lock, Cursor, journal, trace) | A command a human types; a string a human reads about the config | `plugins`, `config`, the kernel, Transformers |
709
- | `operator/` | Tools for a human: `init`, `setup`, `doctor`, `watch` / `status`, `log`, `cancel`, `release`, `board`, the terminal prompt | Anything the loop needs — the loop cannot see this layer | everything below |
817
+ | `operator/` | Tools for a human: `init`, `setup`, `doctor`, `watch` / `status`, `log`, `incident`, `supervise/`, `cancel`, `restart`, `release`, `board`, the terminal prompt | Anything the loop needs — the loop cannot see this layer | everything below |
710
818
  | `index.ts`, `cli.ts` | The package surface and the dispatch. Nothing else sits at the top of `src/` | Logic | everything |
711
819
 
712
820
  Host's code names no plugin. A manager, an isolation strategy, a persistence
@@ -761,6 +869,6 @@ src/
761
869
  config/ argv, JSON config, config discovery → HostOptions; defaults: the plugin names Host ships with
762
870
  plugins/ load: the one loader; manager, isolation, persist: one shape each; discover
763
871
  loop/ open-host (composition), tick (the loop), deliveries, drive, probe, report, authority, work-line, cursor, lock, journal (the film), streams (what a child said), trace, transformers
764
- operator/ run, init, setup, doctor, explain, live (watch / status), log, film, cancel, release, board, prompt, git-remote
872
+ operator/ run, init, setup, doctor, explain, live (watch / status), log, incident, supervise/ (observe, council, act), film, cancel, release, board, prompt, git-remote
765
873
  fixtures/ stand-in slots for the loop's tests
766
874
  ```