@bluewombat/runtime 0.1.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 (151) hide show
  1. package/README.md +473 -0
  2. package/SPECS.md +303 -0
  3. package/dist/cli.d.ts +3 -0
  4. package/dist/cli.d.ts.map +1 -0
  5. package/dist/cli.js +172 -0
  6. package/dist/cli.js.map +1 -0
  7. package/dist/config/defaults.d.ts +17 -0
  8. package/dist/config/defaults.d.ts.map +1 -0
  9. package/dist/config/defaults.js +17 -0
  10. package/dist/config/defaults.js.map +1 -0
  11. package/dist/config/find-config.d.ts +9 -0
  12. package/dist/config/find-config.d.ts.map +1 -0
  13. package/dist/config/find-config.js +25 -0
  14. package/dist/config/find-config.js.map +1 -0
  15. package/dist/config/home.d.ts +17 -0
  16. package/dist/config/home.d.ts.map +1 -0
  17. package/dist/config/home.js +23 -0
  18. package/dist/config/home.js.map +1 -0
  19. package/dist/config/load-config.d.ts +13 -0
  20. package/dist/config/load-config.d.ts.map +1 -0
  21. package/dist/config/load-config.js +537 -0
  22. package/dist/config/load-config.js.map +1 -0
  23. package/dist/config/parse-args.d.ts +25 -0
  24. package/dist/config/parse-args.d.ts.map +1 -0
  25. package/dist/config/parse-args.js +478 -0
  26. package/dist/config/parse-args.js.map +1 -0
  27. package/dist/config/types.d.ts +144 -0
  28. package/dist/config/types.d.ts.map +1 -0
  29. package/dist/config/types.js +2 -0
  30. package/dist/config/types.js.map +1 -0
  31. package/dist/index.d.ts +30 -0
  32. package/dist/index.d.ts.map +1 -0
  33. package/dist/index.js +16 -0
  34. package/dist/index.js.map +1 -0
  35. package/dist/loop/authority.d.ts +51 -0
  36. package/dist/loop/authority.d.ts.map +1 -0
  37. package/dist/loop/authority.js +166 -0
  38. package/dist/loop/authority.js.map +1 -0
  39. package/dist/loop/context.d.ts +44 -0
  40. package/dist/loop/context.d.ts.map +1 -0
  41. package/dist/loop/context.js +2 -0
  42. package/dist/loop/context.js.map +1 -0
  43. package/dist/loop/cursor.d.ts +4 -0
  44. package/dist/loop/cursor.d.ts.map +1 -0
  45. package/dist/loop/cursor.js +20 -0
  46. package/dist/loop/cursor.js.map +1 -0
  47. package/dist/loop/deliveries.d.ts +22 -0
  48. package/dist/loop/deliveries.d.ts.map +1 -0
  49. package/dist/loop/deliveries.js +240 -0
  50. package/dist/loop/deliveries.js.map +1 -0
  51. package/dist/loop/describe.d.ts +40 -0
  52. package/dist/loop/describe.d.ts.map +1 -0
  53. package/dist/loop/describe.js +48 -0
  54. package/dist/loop/describe.js.map +1 -0
  55. package/dist/loop/drive.d.ts +15 -0
  56. package/dist/loop/drive.d.ts.map +1 -0
  57. package/dist/loop/drive.js +42 -0
  58. package/dist/loop/drive.js.map +1 -0
  59. package/dist/loop/journal.d.ts +22 -0
  60. package/dist/loop/journal.d.ts.map +1 -0
  61. package/dist/loop/journal.js +49 -0
  62. package/dist/loop/journal.js.map +1 -0
  63. package/dist/loop/lock.d.ts +11 -0
  64. package/dist/loop/lock.d.ts.map +1 -0
  65. package/dist/loop/lock.js +60 -0
  66. package/dist/loop/lock.js.map +1 -0
  67. package/dist/loop/message.d.ts +34 -0
  68. package/dist/loop/message.d.ts.map +1 -0
  69. package/dist/loop/message.js +48 -0
  70. package/dist/loop/message.js.map +1 -0
  71. package/dist/loop/open-host.d.ts +13 -0
  72. package/dist/loop/open-host.d.ts.map +1 -0
  73. package/dist/loop/open-host.js +180 -0
  74. package/dist/loop/open-host.js.map +1 -0
  75. package/dist/loop/probe.d.ts +9 -0
  76. package/dist/loop/probe.d.ts.map +1 -0
  77. package/dist/loop/probe.js +57 -0
  78. package/dist/loop/probe.js.map +1 -0
  79. package/dist/loop/report.d.ts +26 -0
  80. package/dist/loop/report.d.ts.map +1 -0
  81. package/dist/loop/report.js +328 -0
  82. package/dist/loop/report.js.map +1 -0
  83. package/dist/loop/tick.d.ts +24 -0
  84. package/dist/loop/tick.d.ts.map +1 -0
  85. package/dist/loop/tick.js +169 -0
  86. package/dist/loop/tick.js.map +1 -0
  87. package/dist/loop/trace.d.ts +16 -0
  88. package/dist/loop/trace.d.ts.map +1 -0
  89. package/dist/loop/trace.js +49 -0
  90. package/dist/loop/trace.js.map +1 -0
  91. package/dist/loop/transformers.d.ts +27 -0
  92. package/dist/loop/transformers.d.ts.map +1 -0
  93. package/dist/loop/transformers.js +229 -0
  94. package/dist/loop/transformers.js.map +1 -0
  95. package/dist/loop/work-line.d.ts +73 -0
  96. package/dist/loop/work-line.d.ts.map +1 -0
  97. package/dist/loop/work-line.js +109 -0
  98. package/dist/loop/work-line.js.map +1 -0
  99. package/dist/operator/board.d.ts +31 -0
  100. package/dist/operator/board.d.ts.map +1 -0
  101. package/dist/operator/board.js +67 -0
  102. package/dist/operator/board.js.map +1 -0
  103. package/dist/operator/cancel.d.ts +19 -0
  104. package/dist/operator/cancel.d.ts.map +1 -0
  105. package/dist/operator/cancel.js +70 -0
  106. package/dist/operator/cancel.js.map +1 -0
  107. package/dist/operator/doctor.d.ts +15 -0
  108. package/dist/operator/doctor.d.ts.map +1 -0
  109. package/dist/operator/doctor.js +416 -0
  110. package/dist/operator/doctor.js.map +1 -0
  111. package/dist/operator/git-remote.d.ts +39 -0
  112. package/dist/operator/git-remote.d.ts.map +1 -0
  113. package/dist/operator/git-remote.js +78 -0
  114. package/dist/operator/git-remote.js.map +1 -0
  115. package/dist/operator/init.d.ts +28 -0
  116. package/dist/operator/init.d.ts.map +1 -0
  117. package/dist/operator/init.js +522 -0
  118. package/dist/operator/init.js.map +1 -0
  119. package/dist/operator/live.d.ts +19 -0
  120. package/dist/operator/live.d.ts.map +1 -0
  121. package/dist/operator/live.js +149 -0
  122. package/dist/operator/live.js.map +1 -0
  123. package/dist/operator/prompt.d.ts +35 -0
  124. package/dist/operator/prompt.d.ts.map +1 -0
  125. package/dist/operator/prompt.js +125 -0
  126. package/dist/operator/prompt.js.map +1 -0
  127. package/dist/operator/setup.d.ts +18 -0
  128. package/dist/operator/setup.d.ts.map +1 -0
  129. package/dist/operator/setup.js +62 -0
  130. package/dist/operator/setup.js.map +1 -0
  131. package/dist/plugins/discover.d.ts +13 -0
  132. package/dist/plugins/discover.d.ts.map +1 -0
  133. package/dist/plugins/discover.js +81 -0
  134. package/dist/plugins/discover.js.map +1 -0
  135. package/dist/plugins/isolation.d.ts +78 -0
  136. package/dist/plugins/isolation.d.ts.map +1 -0
  137. package/dist/plugins/isolation.js +64 -0
  138. package/dist/plugins/isolation.js.map +1 -0
  139. package/dist/plugins/load.d.ts +35 -0
  140. package/dist/plugins/load.d.ts.map +1 -0
  141. package/dist/plugins/load.js +109 -0
  142. package/dist/plugins/load.js.map +1 -0
  143. package/dist/plugins/manager.d.ts +43 -0
  144. package/dist/plugins/manager.d.ts.map +1 -0
  145. package/dist/plugins/manager.js +47 -0
  146. package/dist/plugins/manager.js.map +1 -0
  147. package/dist/plugins/persist.d.ts +33 -0
  148. package/dist/plugins/persist.d.ts.map +1 -0
  149. package/dist/plugins/persist.js +26 -0
  150. package/dist/plugins/persist.js.map +1 -0
  151. package/package.json +66 -0
package/SPECS.md ADDED
@@ -0,0 +1,303 @@
1
+ # Host
2
+
3
+ The composition root, not a Transformer. It is the process you run: Cursor, admission,
4
+ Conductor, report. It is not a FeatureStandard transformer and it is not a
5
+ sequencer — Conductor sequences; this package calls it.
6
+
7
+ Host does not know a tracker. It resolves one FeatureManager package, hands it
8
+ the options it never reads, and drives the `ManagerPort` that package returns.
9
+ Adding GitLab or Jira changes nothing here.
10
+
11
+ ## 1. Job
12
+
13
+ ```
14
+ boot Host, each tick others
15
+ ---- --------------- ------
16
+ paths + slots listen: drain the manager since the manager package
17
+ manager + its options the Cursor WorkLedger
18
+ the plugins (isolation, probe: abandon what the tracker Conductor
19
+ persist) no longer lists Publisher / Refresher slots
20
+ the work line copy refresh the work line copy
21
+ one Delivery at a time:
22
+ admit / ready / cancel,
23
+ reconcile, drive, report,
24
+ save Cursor
25
+ sweep: drive what is in flight
26
+ or received, report
27
+ ← idle | ran | stopped
28
+ ```
29
+
30
+ **In:** WorkLineStable, how to isolate and fold it (`workLine.isolation`),
31
+ workspace root, ledger root, a manager specifier and its opaque options,
32
+ Planner / Builder / Gate argv.
33
+ **Out:** a typed result. The CLI maps that to an exit code.
34
+
35
+ ## 2. Faces
36
+
37
+ | Face | Entry | Must not |
38
+ | ------ | ------------------------------------------------------------------- | --------------------------------------------- |
39
+ | Import | `openHost(options)` then `runOnce` / `run` | Live inside Conductor `src/run/` |
40
+ | CLI | `mason run \| watch \| status \| cancel \| init \| setup \| doctor` | Interpret a FeatureStandard; pick Child paths |
41
+
42
+ `src/loop/` is the process: `open-host.ts` opens packages (composition),
43
+ `tick.ts` runs the listen / probe / Cursor / sweep loop, `deliveries.ts` handles one
44
+ Delivery, `drive.ts` runs one Project and reports on the Feature that ran,
45
+ `report.ts` maps Conductor outcomes to FeatureManager Events,
46
+ `journal.ts` and `trace.ts` are its film and its stdout. `loop/` may import
47
+ Transformer packages: this package **is** the composition root for the
48
+ execution Transformers. Conductor still must not. Host must not import a
49
+ tracker or an isolation strategy by name: both reach it through `plugins/`.
50
+ § 1–7 describe `loop/`; § 8 describes `operator/`. The layers and the one
51
+ import rule between them are in [README.md](./README.md) § Layers.
52
+
53
+ ## 3. Configuration
54
+
55
+ `mason.config.json`, found by walking up from the working directory, or the
56
+ file `--config` names. Relative paths in the file resolve against the file;
57
+ relative paths in a flag resolve against the working directory. Flags override
58
+ the file. `--manager-option key=value` merges per key, so overriding one option
59
+ does not drop the others; a repeated key becomes an array.
60
+
61
+ Host validates only its own keys. `managerOptions` is copied through untouched:
62
+ a tracker's fields are that package's business, and its errors are that
63
+ package's words.
64
+
65
+ ## 4. Loading a manager
66
+
67
+ `manager` is a package name or a path. Resolution imports the bare specifier
68
+ first — mason and its managers installed side by side in one project — then
69
+ walks up from the config file's directory, which covers a global `mason`
70
+ driving a locally installed manager. A module without `createManager` is
71
+ refused by name, and a manager that cannot read its options returns a reason,
72
+ never a throw.
73
+
74
+ Secrets never pass through Host. The manager reads them from the environment
75
+ it is handed.
76
+
77
+ ## 5. Cursor
78
+
79
+ Path: `<ledgerRoot>/cursor`. Contents: the last committed Listener Cursor, or
80
+ absent. `runOnce` passes it as the manager's `since`. After each Delivery is
81
+ handled (including skipped / invalid), the Cursor is that Delivery's name.
82
+ `unavailable` / interrupt does **not** advance the Cursor: the Delivery is
83
+ retried on the next tick.
84
+
85
+ A crash may replay one Delivery. Handlers must tolerate a repeat: admit is
86
+ idempotent on `key`; a manager may append an Event twice — accepted trade for
87
+ not losing an Event.
88
+
89
+ ## 6. One Delivery
90
+
91
+ `adapt` first. Then:
92
+
93
+ | Adapter | Host |
94
+ | ------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
95
+ | `invalid` (new, or the intention changed) | `recordInvalid`, emit `invalid` |
96
+ | `invalid` (same fingerprint as recorded) | leave it; the tracker was told, and telling it is what delivered it again |
97
+ | `upsert` (new/invalid) | `admit`, emit `accepted`, then `runProject` when the Project is free; a Project frozen by an escalation queues the Feature (journal `queued`, one `progress` Event) and the sweep starts it once the Project is free. Post-run Events (`planned` / `done` / `escalated`) are reported on the Feature that was driven, not on the delivered key. `progress` (once per refusal) when the Authority sent the Submission back and a repair is on its way |
98
+ | `upsert` (in-flight / terminal) | leave it; do not restart. In-flight is `planning`, `running`, `escalated`, `integrating`, `submitted`; terminal is `merging`, `done`, `cancelled`. An echo of a Host report (Submitted, a progress comment) is this row: do not admit it |
99
+ | `upsert` (`received`) | same drive as a new upsert (no second `accepted`) |
100
+ | `upsert` + `signalsReady` while `escalated` | same as `ready` — a poll snapshot has no `labeled` edge |
101
+ | `ready` | `resumeReady`, emit `resumed`, `runProject`, then the same post-run Events |
102
+ | `cancel` | `conductor.cancel`. `cancelled`, or `reject_late` when the ledger says point of no return; nothing on `done` (the issue closing is the Submission's own doing) or `cancelled` |
103
+ | `unavailable` / interrupt | stop the tick |
104
+
105
+ `runProject` is always preceded by `reconcile` on that Project. Host never
106
+ drives an `escalated` Feature: that state is frozen until a human `ready`.
107
+
108
+ Every Event Host emits is a write to the tracker, and a tracker that lists by
109
+ "last updated" delivers the intention again on the next pass because of that
110
+ write. No row above may emit on that echo: `invalid` is keyed by fingerprint,
111
+ `accepted` by the ledger not knowing the key (or knowing it `invalid`), the
112
+ queued `progress` by `eventId`, the post-run Events by the Feature that was
113
+ driven, `resumed` by taking the signal back, `cancelled` by the ledger
114
+ refusing a second cancel, an in-flight Feature (including `submitted`) by
115
+ leaving the upsert.
116
+
117
+ On top of that, every Event that states a fact carries an `eventId`, so a
118
+ manager that already has it says nothing — after a restart as much as within
119
+ a pass. The id names the fact: `<key>:accepted:<fingerprint>` and
120
+ `<key>:invalid:<fingerprint>` (an edit is a new fact), `<key>:planned:<planned_at>`,
121
+ `<key>:integrated:<subtask>`, `<key>:submitted:<reference>`,
122
+ `<key>:refused:<n>`, `<key>:queued:<blocker>`, `<key>:resumed:<round>`,
123
+ `<key>:escalated:<round>:<kind>[:<subtask>]`, `<key>:done`,
124
+ `<key>:cancelled`, `<key>:reject_late:…`. The round is the ledger's count of
125
+ `ready` taken: a Plan refused, resumed and refused again has the same Attempt
126
+ count both times, and only the round tells the two escalations apart.
127
+ `escalation_reminder` alone would carry none, repetition being its point.
128
+
129
+ ## 7. Loop
130
+
131
+ `run`: `runOnce` then, if `pollIntervalMs` is set, sleep and repeat until
132
+ interrupt.
133
+
134
+ SIGINT / SIGTERM: set the interrupt flag, `conductor.pause()`, stop after the
135
+ current Delivery.
136
+
137
+ Each pass appends JSON lines to `<ledgerRoot>/events.jsonl` (listen, probed,
138
+ Transformer progress, admitted, reported, queued, idle, a heartbeat while
139
+ `submitted`). That file is a film, not truth: a crash may drop the last lines;
140
+ the ledger is still right. `mason run` still writes a human Trace on stdout.
141
+ The journal is how another process attaches.
142
+
143
+ After listen, if the manager declares `probe` and the listen reached the source
144
+ (`completed`, `listened` — not `source-lost` / `interrupted` / `invalid-invocation`),
145
+ Host asks `probe` of every Feature abandon is still legal for (`received`,
146
+ `planning`, `running`, `escalated`, `integrating`, `submitted`). `gone` is the
147
+ same abandon as a `cancel` delivery, so a deleted item a poll can never list
148
+ still frees the Project; `unavailable` is not abandon. Probe runs before the
149
+ work-line refresh and the deliveries, so a cancel in this pass can start the
150
+ `received` queue on a copy that was just fast-forwarded.
151
+
152
+ The sweep after the deliveries drives every Project that has work in flight
153
+ **or** `received`, once per tick. The result is reported on the Feature that
154
+ was driven. A `received` Feature behind an escalation is journaled `queued`
155
+ and left; the next sweep that finds the Project free claims it. The same set
156
+ (`in-flight ∪ received`) decides whether this pass refreshes the work line
157
+ copy: starting a queued Feature on a copy that was not fast-forwarded this
158
+ tick would build on a version that no longer exists.
159
+
160
+ ## 8. init, setup, doctor, watch, status and cancel
161
+
162
+ `init` writes a config that already runs: the Planner is the bootstrap one from
163
+ `@bluewombat/slots/planners/`, the Builder is a stub that says what to replace
164
+ (example Cursor CLI and Claude Code Builders live in `@bluewombat/slots/builders/`),
165
+ and the Gate sequence starts empty. Example Gates live in
166
+ `@bluewombat/slots/gates/` (`parent-clean`, `sensitive-path`,
167
+ `workspace-changed`, `ci-green`).
168
+
169
+ Two Gate sequences: `builder.gates` is judged on each Subtask, `assembly.gates`
170
+ on the feature they assemble into. A check that is right for one can be wrong for the
171
+ other — `workspace-changed` says an Attempt that changed nothing did nothing,
172
+ which is true of a Subtask and false of an assembly whose correct outcome is that
173
+ nothing was left to do. The assembly produces nothing unless something refused it
174
+ first: it is a judgement, and a refusal is `assembly.fix`, not a second first-pass.
175
+
176
+ Every child of a Task carries its own ceiling and no other. Every `cmd` names
177
+ its own `timeoutMs`, and none is inherited from a neighbour — nothing else
178
+ bounds a producer. A Gate takes its own `timeoutMs`,
179
+ or its sequence's `defaultTimeoutMs`, and a Gate that would end up with neither
180
+ is refused when the config loads. Nothing is derived from what another child
181
+ left behind, so what bounds a whole Task is arithmetic the Project can do:
182
+ the builder's `maxAttempts` times its producer's ceiling plus the sum of its Gates'.
183
+ An assembly fix is bounded the same way from `assembly.maxAttempts` and `assembly.fix`.
184
+ Root `timeoutMs` bounds what is outside a Task — the manager, the
185
+ isolations. Host has no flag named after a tracker (`--repo` is not a Host
186
+ flag): tracker fields come from that package.
187
+
188
+ At a terminal it asks rather than demands. Which manager comes from the packages
189
+ installed nearby that declare the `mason-manager` keyword — Host holds no list
190
+ of trackers, so a third-party one is offered the same way. A single one is named
191
+ and confirmed rather than assumed: picking it silently reads as though the tool
192
+ only speaks that tracker. None installed is an error naming the install
193
+ command. What that manager needs comes from its own `questionsManager`: it
194
+ declares the wording and the validation, Host reads the line. An option already
195
+ given on the command line is not asked for again.
196
+
197
+ WorkLineStable must exist before a run, so `init` inspects the path, names the
198
+ branch when it is a git tree, writes `workLine.isolation` to
199
+ `@bluewombat/isolation-git` or `@bluewombat/isolation-copy` from that inspection
200
+ (Isolator and Integrator do not choose a strategy; Host loads the package named
201
+ here), and offers to clone one —
202
+ after listing the
203
+ remote's branches and refusing a name that is not among them, because a
204
+ `git clone --branch` that misses complains about the repository rather than the
205
+ name that was wrong. `--clone REMOTE` (with an optional `--branch`, defaulting
206
+ to the remote's own HEAD) answers that question ahead of time, so a script
207
+ reaches the same place through the same code. An existing directory is never
208
+ re-cloned. `init` then runs `setup` and offers to apply it, so nothing has to be
209
+ re-run by hand.
210
+
211
+ A flag is read by the same question that would have asked for it, so
212
+ `--manager-option labels=mason` lands in the config in the shape a typed answer
213
+ would, and a malformed one is refused before anything is written. The setup plan
214
+ that follows never decides `init`'s exit code: it wrote its files, and
215
+ `mason init && mason setup --apply` must not stop because a token is not
216
+ exported yet.
217
+
218
+ `--manager` turns every question off, which is what a script wants;
219
+ `--interactive` forces them when stdin is a pipe. Overwriting an existing config
220
+ needs `--force`, or a yes to the question. Ctrl-C, Ctrl-D, or a pipe running dry
221
+ stops on the spot with `Cancelled. Nothing written.` and exit 130 — a read after
222
+ the input has ended raises rather than answering empty, so no question is ever
223
+ re-asked at a terminal nobody is at. A refused answer is re-asked at most three
224
+ times, each time saying what was wrong with it.
225
+
226
+ `setup` asks the manager to bring the tracker to the shape a run expects. Host
227
+ knows none of that shape: it resolves the manager, calls `setupManager`, and
228
+ prints the steps. Without `--apply` nothing is written — a plan says what is
229
+ `satisfied` and what is `missing`. With `--apply`, a step it fixed is `applied`.
230
+ A `blocked` step exits 1; a `missing` step in a plan does not. A manager with no
231
+ `setupManager` is reported as having nothing to do, and exits 0.
232
+
233
+ `doctor` reports one line per check and exits 1 on any `fail`: the Node
234
+ version, which config was found, whether the invocation is complete, whether
235
+ WorkLineStable exists (and whether it is a git tree), whether `workLine.isolation`
236
+ matches that tree and Authority, whether the created
237
+ directories have an existing ancestor, whether every slot command resolves,
238
+ whether the manager loads, and then the manager's own findings. It parses
239
+ without demanding that paths exist, so one missing directory does not hide
240
+ every other check, and it never uses the network.
241
+
242
+ `watch` and `status` are the operator live view. They open the ledger the same
243
+ way `run` does (`openFilesystemPersist` then `openWorkLedger`) and they do not
244
+ open a manager, Conductor, or Host. `status` prints a snapshot of every known
245
+ Feature and exits. `watch` prints that snapshot, then follows new journal lines
246
+ until SIGINT (exit 0). A missing journal is not an error: the snapshot still
247
+ stands. `--json` prints the snapshot as JSON. Ctrl-C on watch leaves the worker
248
+ running. The file can grow without rotation.
249
+
250
+ `cancel <key>` abandons one Feature the same way a `cancel` delivery does. The
251
+ team still abandons in the FeatureManager; this is the hatch when the tracker
252
+ cannot say the intention is gone (a manager with no `probe`, or an operator
253
+ who already knows) and the ledger would otherwise freeze the Project. It
254
+ takes the ledger lock — stop `run` first if it holds it. Exit 0 on cancel or
255
+ already cancelled, 1 when abandon is refused (`done`, point of no return), 2
256
+ when the key is missing or the ledger does not know it. The key is the first
257
+ positional: `cancel github:owner/name#19`.
258
+
259
+ ## 9. Acceptance
260
+
261
+ 1. Convertible upsert in an empty Source → WorkLineStable carries the Builder
262
+ marker, Thread `accepted`, `planned`, `done`, Cursor is that file name.
263
+ 2. Unconvertible upsert → Thread `invalid`, no Conductor work, Cursor advanced.
264
+ 3. Fail-blocking Gate, then a `ready` Event, then passing Gates → Thread
265
+ contains `escalated` then `resumed` then `done`.
266
+ 4. `cancel` while `escalated` → Thread `cancelled`; WorkLineStable seed-only.
267
+ 5. Re-running `runOnce` with the saved Cursor does not re-deliver the first Event.
268
+ 6. GitHub: convertible issue (mocked API) → WorkLineStable marker, comments
269
+ `accepted`, `planned`, `done`.
270
+ 7. GitHub: fail-blocking then the same issue with the ready label → `escalated`,
271
+ `resumed`, `done`.
272
+ 8. A manager named by path, and one named by package, both load; a module without
273
+ `createManager` is refused by name.
274
+ 9. `init --manager` then `doctor` in an empty directory reports exactly what is missing;
275
+ `init` without `--manager` and without a terminal is refused.
276
+ 10. `setup` without `--apply` writes nothing, and names the flag that would;
277
+ a manager without `setupManager` exits 0 saying so.
278
+ 11. The wizard names the only installed manager, repeats a question its manager
279
+ refused, and skips an option already passed as a flag.
280
+ 12. `--clone` takes the named branch, falls back to the remote's default,
281
+ refuses a branch the remote does not carry, and re-clones nothing.
282
+ 13. `list()` after admit of two keys returns two summaries; a refused command
283
+ does not appear; `listInFlight()` is the mid-flight subset.
284
+ 14. A Transformer `status` line and a `gate-finished` line during `runProject`
285
+ appear in `<ledgerRoot>/events.jsonl`.
286
+ 15. `watch` on a ledger with Features and no journal prints the snapshot,
287
+ including a `submitted` Feature's reference, and exits 0 on interrupt. It
288
+ does not import `openHost` or a manager package.
289
+ 16. Two Features on one Project, the first `escalated`: admitting the second
290
+ emits `accepted` and one `progress` (queued), not `escalated`; the second
291
+ stays `received`. Cancelling the first in a later pass starts the second.
292
+ A `submitted` Feature that reaches `done` while a second is delivered
293
+ reports `done` on the first.
294
+ 17. After an escalation, a `probe` of `gone` cancels that Feature and the sweep
295
+ starts the `received` queue in the same pass. `unavailable` leaves the
296
+ escalation. A listen that missed the source does not probe. GitHub: a 404
297
+ on `GET /issues/{n}` after a completed listen cancels, even when the
298
+ listing is empty. `cancel <key>` abandons an escalated Feature.
299
+ 18. A Feature `submitted`, then the same upsert delivered again, then the
300
+ Authority refuses: Host skips the echo, records no `pending_fingerprint`,
301
+ and the next pass repairs. It does not escalate as a plan refusal.
302
+
303
+ How to install, configure and run: [README.md](./README.md).
package/dist/cli.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
package/dist/cli.js ADDED
@@ -0,0 +1,172 @@
1
+ #!/usr/bin/env node
2
+ import { PRODUCT } from "@bluewombat/manager-kit";
3
+ import { CONFIG_FILENAME } from "./config/find-config.js";
4
+ import { DEFAULT_HOME } from "./config/home.js";
5
+ import { parseArgs } from "./config/parse-args.js";
6
+ import { openHost } from "./loop/open-host.js";
7
+ import { runCancel } from "./operator/cancel.js";
8
+ import { runDoctor } from "./operator/doctor.js";
9
+ import { runInit } from "./operator/init.js";
10
+ import { runLive } from "./operator/live.js";
11
+ import { isInteractive, terminalAsk } from "./operator/prompt.js";
12
+ import { runSetup } from "./operator/setup.js";
13
+ const USAGE = `Usage: ${PRODUCT} <command>
14
+
15
+ run drain the FeatureManager, run Conductor, report
16
+ watch snapshot the ledger and follow the journal
17
+ status snapshot the ledger, then exit
18
+ cancel abandon a Feature by key (when the tracker cannot say it is gone)
19
+ init set this directory up — asks, writes, then runs setup
20
+ doctor check the config, the slots, and the manager
21
+ setup prepare the tracker (--apply to write; a plan without it)
22
+
23
+ init flags: --manager PKG --manager-option KEY=VALUE --work-line-stable DIR
24
+ --clone REMOTE [--branch NAME] --force --interactive
25
+
26
+ Shared flags: --config FILE --manager NAME --manager-option KEY=VALUE --home DIR
27
+ --work-line-stable DIR --work-line-branch NAME --workspace-root DIR --ledger DIR
28
+ --planner -- CMD --planner-timeout-ms N
29
+ --builder -- CMD --builder-timeout-ms N --builder-max-attempts N
30
+ --builder-repair -- CMD --builder-repair-timeout-ms N
31
+ --builder-gate ID -- CMD --builder-gate-timeout-ms N
32
+ --assembly-fix -- CMD --assembly-fix-timeout-ms N --assembly-max-attempts N
33
+ --assembly-gate ID -- CMD --assembly-gate-timeout-ms N
34
+ --timeout-ms N --poll-interval-ms N
35
+
36
+ A gate's --*-gate-timeout-ms comes before the --*-gate it bounds.
37
+
38
+ watch / status also take --json (snapshot as JSON).
39
+
40
+ Without --config, the nearest ${CONFIG_FILENAME} is used. --home is where Host
41
+ keeps its own files (default ${DEFAULT_HOME}): the ledger, the workspaces, its copy of
42
+ the work line.
43
+ `;
44
+ function installSignalHandlers(state) {
45
+ const onSignal = () => {
46
+ state.interrupted = true;
47
+ };
48
+ process.on("SIGINT", onSignal);
49
+ process.on("SIGTERM", onSignal);
50
+ return () => {
51
+ process.off("SIGINT", onSignal);
52
+ process.off("SIGTERM", onSignal);
53
+ };
54
+ }
55
+ async function run(argv) {
56
+ const parsed = parseArgs(argv, { cwd: process.cwd() });
57
+ if (!parsed.ok) {
58
+ process.stderr.write(`${parsed.reason}\n`);
59
+ return 2;
60
+ }
61
+ const interruptFlag = { interrupted: false };
62
+ const options = { ...parsed.invocation, interruptFlag };
63
+ const detachSignals = installSignalHandlers(interruptFlag);
64
+ let host;
65
+ try {
66
+ host = await openHost(options);
67
+ const result = await host.run();
68
+ if (result.lastRun?.outcome === "refused") {
69
+ return 1;
70
+ }
71
+ return interruptFlag.interrupted ? 130 : 0;
72
+ }
73
+ catch (error) {
74
+ process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
75
+ return 2;
76
+ }
77
+ finally {
78
+ await host?.close();
79
+ detachSignals();
80
+ }
81
+ }
82
+ async function main(argv) {
83
+ const command = argv[0];
84
+ const rest = argv.slice(1);
85
+ if (command === "run") {
86
+ return await run(rest);
87
+ }
88
+ if (command === "watch" || command === "status") {
89
+ const interruptFlag = { interrupted: false };
90
+ const detachSignals = installSignalHandlers(interruptFlag);
91
+ try {
92
+ return await runLive({
93
+ cwd: process.cwd(),
94
+ argv: rest,
95
+ write: (line) => process.stdout.write(line),
96
+ interruptFlag,
97
+ follow: command === "watch",
98
+ });
99
+ }
100
+ finally {
101
+ detachSignals();
102
+ }
103
+ }
104
+ if (command === "init") {
105
+ // Asking only makes sense at a terminal; a script gets the flags-only path.
106
+ // `--interactive` forces the questions when stdin is a pipe.
107
+ const wizard = (isInteractive() || rest.includes("--interactive")) && !rest.includes("--manager");
108
+ const terminal = wizard ? terminalAsk() : undefined;
109
+ try {
110
+ const result = await runInit({
111
+ cwd: process.cwd(),
112
+ argv: rest,
113
+ env: process.env,
114
+ write: (line) => process.stdout.write(line),
115
+ ...(terminal === undefined ? {} : { ask: terminal.ask }),
116
+ });
117
+ if (!result.ok) {
118
+ process.stderr.write(`${result.reason}\n`);
119
+ return result.exitCode ?? 2;
120
+ }
121
+ return result.exitCode;
122
+ }
123
+ finally {
124
+ terminal?.close();
125
+ }
126
+ }
127
+ if (command === "setup") {
128
+ const interruptFlag = { interrupted: false };
129
+ const detachSignals = installSignalHandlers(interruptFlag);
130
+ try {
131
+ return await runSetup({
132
+ cwd: process.cwd(),
133
+ argv: rest,
134
+ env: process.env,
135
+ write: (line) => process.stdout.write(line),
136
+ interruptFlag,
137
+ });
138
+ }
139
+ finally {
140
+ detachSignals();
141
+ }
142
+ }
143
+ if (command === "doctor") {
144
+ return await runDoctor({
145
+ cwd: process.cwd(),
146
+ argv: rest,
147
+ env: process.env,
148
+ write: (line) => process.stdout.write(line),
149
+ });
150
+ }
151
+ if (command === "cancel") {
152
+ const interruptFlag = { interrupted: false };
153
+ const detachSignals = installSignalHandlers(interruptFlag);
154
+ try {
155
+ return await runCancel({
156
+ cwd: process.cwd(),
157
+ argv: rest,
158
+ env: process.env,
159
+ write: (line) => process.stdout.write(line),
160
+ interruptFlag,
161
+ });
162
+ }
163
+ finally {
164
+ detachSignals();
165
+ }
166
+ }
167
+ process.stderr.write(USAGE);
168
+ return 2;
169
+ }
170
+ const exitCode = await main(process.argv.slice(2));
171
+ process.exitCode = exitCode;
172
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * The plugins Host ships with, by name — the one place these names are
3
+ * written. Host's code never imports a plugin; it loads one by the name a
4
+ * config gives, and these are the names `mason init` writes and `persist`
5
+ * falls back to. Host's package.json declares exactly these, so a config
6
+ * `init` wrote runs with nothing else installed. A manager has no default:
7
+ * Host cannot pick a tracker.
8
+ */
9
+ /** Isolation by git worktree, for a work line that is a git tree. */
10
+ export declare const ISOLATION_GIT = "@bluewombat/isolation-git";
11
+ /** Isolation by directory copy, for a plain directory. */
12
+ export declare const ISOLATION_COPY = "@bluewombat/isolation-copy";
13
+ /** Where the ledger goes when a config says nothing: one JSON file per key. */
14
+ export declare const DEFAULT_PERSIST = "@bluewombat/persist-fs";
15
+ /** The Planner `init` points a new config at until the Project writes its own. */
16
+ export declare const BOOTSTRAP_PLANNER = "@bluewombat/slots/planners/one-subtask.mjs";
17
+ //# sourceMappingURL=defaults.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"defaults.d.ts","sourceRoot":"","sources":["../../src/config/defaults.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,qEAAqE;AACrE,eAAO,MAAM,aAAa,8BAA8B,CAAC;AACzD,0DAA0D;AAC1D,eAAO,MAAM,cAAc,+BAA+B,CAAC;AAC3D,+EAA+E;AAC/E,eAAO,MAAM,eAAe,2BAA2B,CAAC;AACxD,kFAAkF;AAClF,eAAO,MAAM,iBAAiB,+CAA+C,CAAC"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * The plugins Host ships with, by name — the one place these names are
3
+ * written. Host's code never imports a plugin; it loads one by the name a
4
+ * config gives, and these are the names `mason init` writes and `persist`
5
+ * falls back to. Host's package.json declares exactly these, so a config
6
+ * `init` wrote runs with nothing else installed. A manager has no default:
7
+ * Host cannot pick a tracker.
8
+ */
9
+ /** Isolation by git worktree, for a work line that is a git tree. */
10
+ export const ISOLATION_GIT = "@bluewombat/isolation-git";
11
+ /** Isolation by directory copy, for a plain directory. */
12
+ export const ISOLATION_COPY = "@bluewombat/isolation-copy";
13
+ /** Where the ledger goes when a config says nothing: one JSON file per key. */
14
+ export const DEFAULT_PERSIST = "@bluewombat/persist-fs";
15
+ /** The Planner `init` points a new config at until the Project writes its own. */
16
+ export const BOOTSTRAP_PLANNER = "@bluewombat/slots/planners/one-subtask.mjs";
17
+ //# sourceMappingURL=defaults.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"defaults.js","sourceRoot":"","sources":["../../src/config/defaults.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,qEAAqE;AACrE,MAAM,CAAC,MAAM,aAAa,GAAG,2BAA2B,CAAC;AACzD,0DAA0D;AAC1D,MAAM,CAAC,MAAM,cAAc,GAAG,4BAA4B,CAAC;AAC3D,+EAA+E;AAC/E,MAAM,CAAC,MAAM,eAAe,GAAG,wBAAwB,CAAC;AACxD,kFAAkF;AAClF,MAAM,CAAC,MAAM,iBAAiB,GAAG,4CAA4C,CAAC"}
@@ -0,0 +1,9 @@
1
+ export declare const CONFIG_FILENAME = "mason.config.json";
2
+ /**
3
+ * The nearest `mason.config.json`, walking up from `startDir`.
4
+ *
5
+ * Running from a subdirectory of the project is the common case, so the file is
6
+ * looked up the way a toolchain config is, not demanded on the command line.
7
+ */
8
+ export declare function findConfig(startDir: string): string | undefined;
9
+ //# sourceMappingURL=find-config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"find-config.d.ts","sourceRoot":"","sources":["../../src/config/find-config.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,eAAe,sBAA2B,CAAC;AAExD;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAa/D"}
@@ -0,0 +1,25 @@
1
+ import { existsSync } from "node:fs";
2
+ import { dirname, join, resolve } from "node:path";
3
+ import { PRODUCT } from "@bluewombat/manager-kit";
4
+ export const CONFIG_FILENAME = `${PRODUCT}.config.json`;
5
+ /**
6
+ * The nearest `mason.config.json`, walking up from `startDir`.
7
+ *
8
+ * Running from a subdirectory of the project is the common case, so the file is
9
+ * looked up the way a toolchain config is, not demanded on the command line.
10
+ */
11
+ export function findConfig(startDir) {
12
+ let dir = resolve(startDir);
13
+ for (;;) {
14
+ const candidate = join(dir, CONFIG_FILENAME);
15
+ if (existsSync(candidate)) {
16
+ return candidate;
17
+ }
18
+ const parent = dirname(dir);
19
+ if (parent === dir) {
20
+ return undefined;
21
+ }
22
+ dir = parent;
23
+ }
24
+ }
25
+ //# sourceMappingURL=find-config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"find-config.js","sourceRoot":"","sources":["../../src/config/find-config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACnD,OAAO,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAC;AAElD,MAAM,CAAC,MAAM,eAAe,GAAG,GAAG,OAAO,cAAc,CAAC;AAExD;;;;;GAKG;AACH,MAAM,UAAU,UAAU,CAAC,QAAgB;IACzC,IAAI,GAAG,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC5B,SAAS,CAAC;QACR,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,eAAe,CAAC,CAAC;QAC7C,IAAI,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;YAC1B,OAAO,SAAS,CAAC;QACnB,CAAC;QACD,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;QAC5B,IAAI,MAAM,KAAK,GAAG,EAAE,CAAC;YACnB,OAAO,SAAS,CAAC;QACnB,CAAC;QACD,GAAG,GAAG,MAAM,CAAC;IACf,CAAC;AACH,CAAC"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Where Host keeps what is its own — the ledger, the workspaces, its copy of
3
+ * the work line. One directory, so one line in a `.gitignore` covers it and
4
+ * nothing the system writes lands next to the operator's files.
5
+ *
6
+ * `workspaceRoot` and `ledger` in the config still move each part on its own;
7
+ * this is only where they go when nobody says.
8
+ */
9
+ export declare const DEFAULT_HOME = "./.mason";
10
+ export declare const HOME_LAYOUT: {
11
+ readonly workspaces: "workspaces";
12
+ readonly ledger: "ledger";
13
+ readonly workLine: "work-line";
14
+ };
15
+ export declare function homeOf(configDir: string, home: string | undefined): string;
16
+ export declare function inHome(home: string, part: keyof typeof HOME_LAYOUT): string;
17
+ //# sourceMappingURL=home.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"home.d.ts","sourceRoot":"","sources":["../../src/config/home.ts"],"names":[],"mappings":"AAGA;;;;;;;GAOG;AACH,eAAO,MAAM,YAAY,aAAkB,CAAC;AAE5C,eAAO,MAAM,WAAW;;;;CAId,CAAC;AAEX,wBAAgB,MAAM,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAE1E;AAED,wBAAgB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,OAAO,WAAW,GAAG,MAAM,CAE3E"}
@@ -0,0 +1,23 @@
1
+ import { join, resolve } from "node:path";
2
+ import { PRODUCT } from "@bluewombat/manager-kit";
3
+ /**
4
+ * Where Host keeps what is its own — the ledger, the workspaces, its copy of
5
+ * the work line. One directory, so one line in a `.gitignore` covers it and
6
+ * nothing the system writes lands next to the operator's files.
7
+ *
8
+ * `workspaceRoot` and `ledger` in the config still move each part on its own;
9
+ * this is only where they go when nobody says.
10
+ */
11
+ export const DEFAULT_HOME = `./.${PRODUCT}`;
12
+ export const HOME_LAYOUT = {
13
+ workspaces: "workspaces",
14
+ ledger: "ledger",
15
+ workLine: "work-line",
16
+ };
17
+ export function homeOf(configDir, home) {
18
+ return resolve(configDir, home ?? DEFAULT_HOME);
19
+ }
20
+ export function inHome(home, part) {
21
+ return join(home, HOME_LAYOUT[part]);
22
+ }
23
+ //# sourceMappingURL=home.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"home.js","sourceRoot":"","sources":["../../src/config/home.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAC;AAElD;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,MAAM,OAAO,EAAE,CAAC;AAE5C,MAAM,CAAC,MAAM,WAAW,GAAG;IACzB,UAAU,EAAE,YAAY;IACxB,MAAM,EAAE,QAAQ;IAChB,QAAQ,EAAE,WAAW;CACb,CAAC;AAEX,MAAM,UAAU,MAAM,CAAC,SAAiB,EAAE,IAAwB;IAChE,OAAO,OAAO,CAAC,SAAS,EAAE,IAAI,IAAI,YAAY,CAAC,CAAC;AAClD,CAAC;AAED,MAAM,UAAU,MAAM,CAAC,IAAY,EAAE,IAA8B;IACjE,OAAO,IAAI,CAAC,IAAI,EAAE,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC;AACvC,CAAC"}