@bluewombat/runtime 0.5.0 → 0.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/README.md +85 -57
- package/SPECS.md +35 -18
- package/dist/cli.js +18 -0
- package/dist/cli.js.map +1 -1
- package/dist/config/load-config.d.ts.map +1 -1
- package/dist/config/load-config.js +68 -16
- package/dist/config/load-config.js.map +1 -1
- package/dist/config/parse-args.d.ts.map +1 -1
- package/dist/config/parse-args.js +84 -24
- package/dist/config/parse-args.js.map +1 -1
- package/dist/config/types.d.ts +32 -12
- package/dist/config/types.d.ts.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/loop/deliveries.d.ts +16 -0
- package/dist/loop/deliveries.d.ts.map +1 -1
- package/dist/loop/deliveries.js +25 -0
- package/dist/loop/deliveries.js.map +1 -1
- package/dist/loop/drive.d.ts.map +1 -1
- package/dist/loop/drive.js +15 -1
- package/dist/loop/drive.js.map +1 -1
- package/dist/loop/open-host.d.ts +1 -1
- package/dist/loop/open-host.d.ts.map +1 -1
- package/dist/loop/open-host.js +13 -1
- package/dist/loop/open-host.js.map +1 -1
- package/dist/loop/report.d.ts.map +1 -1
- package/dist/loop/report.js +20 -0
- package/dist/loop/report.js.map +1 -1
- package/dist/loop/tick.d.ts +3 -1
- package/dist/loop/tick.d.ts.map +1 -1
- package/dist/loop/tick.js.map +1 -1
- package/dist/loop/transformers.d.ts +9 -2
- package/dist/loop/transformers.d.ts.map +1 -1
- package/dist/loop/transformers.js +31 -29
- package/dist/loop/transformers.js.map +1 -1
- package/dist/loop/warm.d.ts +21 -0
- package/dist/loop/warm.d.ts.map +1 -0
- package/dist/loop/warm.js +59 -0
- package/dist/loop/warm.js.map +1 -0
- package/dist/operator/doctor.d.ts.map +1 -1
- package/dist/operator/doctor.js +19 -20
- package/dist/operator/doctor.js.map +1 -1
- package/dist/operator/film.js +5 -0
- package/dist/operator/film.js.map +1 -1
- package/dist/operator/init.js +14 -5
- package/dist/operator/init.js.map +1 -1
- package/dist/operator/live.js +9 -7
- package/dist/operator/live.js.map +1 -1
- package/dist/operator/release.d.ts +18 -0
- package/dist/operator/release.d.ts.map +1 -0
- package/dist/operator/release.js +65 -0
- package/dist/operator/release.js.map +1 -0
- package/package.json +15 -15
package/README.md
CHANGED
|
@@ -50,16 +50,17 @@ package's README — Host does not grow a `--repo` flag for it.
|
|
|
50
50
|
|
|
51
51
|
## Commands
|
|
52
52
|
|
|
53
|
-
| Command
|
|
54
|
-
|
|
|
55
|
-
| `mason run`
|
|
56
|
-
| `mason watch`
|
|
57
|
-
| `mason status`
|
|
58
|
-
| `mason log`
|
|
59
|
-
| `mason init`
|
|
60
|
-
| `mason setup`
|
|
61
|
-
| `mason doctor`
|
|
62
|
-
| `mason cancel`
|
|
53
|
+
| Command | Job |
|
|
54
|
+
| --------------- | -------------------------------------------------------------------------------------- |
|
|
55
|
+
| `mason run` | Drain the FeatureManager, run Conductor, report |
|
|
56
|
+
| `mason watch` | Snapshot the ledger and follow the journal. Ctrl-C leaves the worker running |
|
|
57
|
+
| `mason status` | Snapshot the ledger, then exit. `--json` for scripts |
|
|
58
|
+
| `mason log` | Replay the journal, with the reason a phase stopped. `--problems` for just those |
|
|
59
|
+
| `mason init` | Set this directory up: asks, writes the config and the Builder stub, then runs `setup` |
|
|
60
|
+
| `mason setup` | Prepare the tracker. A plan by default; `--apply` writes. Exit 1 on a blocked step |
|
|
61
|
+
| `mason doctor` | Check the config, the slots, and the manager. Exit 1 on any failure |
|
|
62
|
+
| `mason cancel` | Abandon a Feature by key, when the tracker cannot say it is gone |
|
|
63
|
+
| `mason release` | Drop a held Subtask without waiting for the bail clock |
|
|
63
64
|
|
|
64
65
|
One `run` per ledger: `run` writes `<ledger>/lock` with its pid and a second
|
|
65
66
|
`run` on the same home refuses to start while that process lives (two runs
|
|
@@ -106,6 +107,13 @@ manager with no `probe`), stop `run` if it holds the ledger lock, then:
|
|
|
106
107
|
mason cancel github:owner/name#19
|
|
107
108
|
```
|
|
108
109
|
|
|
110
|
+
After a crash left a Subtask `running` with nobody on it, stop `run` if it
|
|
111
|
+
holds the lock, then:
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
mason release github:owner/name#19
|
|
115
|
+
```
|
|
116
|
+
|
|
109
117
|
The film is `<ledger>/events.jsonl`, next to `cursor`. It is not truth after a
|
|
110
118
|
crash — the ledger is. The file can grow; there is no rotation yet.
|
|
111
119
|
|
|
@@ -129,20 +137,25 @@ managerOptions:
|
|
|
129
137
|
workLine:
|
|
130
138
|
stable: ./work-line-stable
|
|
131
139
|
isolation: "@bluewombat/isolation-copy"
|
|
140
|
+
warm:
|
|
141
|
+
cmd: [pnpm, install, --frozen-lockfile]
|
|
142
|
+
timeoutMs: 600000
|
|
132
143
|
planner:
|
|
133
144
|
cmd:
|
|
134
145
|
- node
|
|
135
146
|
- ./node_modules/@bluewombat/slots/planners/one-subtask.mjs
|
|
136
147
|
timeoutMs: 600000
|
|
148
|
+
gates: { defaultTimeoutMs: 120000, gates: [] }
|
|
137
149
|
builder:
|
|
138
150
|
producer:
|
|
139
151
|
cmd: [node, ./mason-builder.mjs]
|
|
140
152
|
timeoutMs: 600000
|
|
153
|
+
gates: { defaultTimeoutMs: 120000, gates: [] }
|
|
141
154
|
repair:
|
|
142
155
|
cmd: [node, ./mason-repair.mjs]
|
|
143
156
|
timeoutMs: 600000
|
|
157
|
+
gates: { defaultTimeoutMs: 120000, gates: [] }
|
|
144
158
|
maxAttempts: 3
|
|
145
|
-
gates: { defaultTimeoutMs: 120000, gates: [] }
|
|
146
159
|
assembly:
|
|
147
160
|
gates: { defaultTimeoutMs: 900000, gates: [] }
|
|
148
161
|
authority:
|
|
@@ -151,31 +164,31 @@ timeoutMs: 600000
|
|
|
151
164
|
pollIntervalMs: 10000
|
|
152
165
|
```
|
|
153
166
|
|
|
154
|
-
| Field | Meaning
|
|
155
|
-
| --------------------------- |
|
|
156
|
-
| `manager` | Package name or path that exports `createManager`
|
|
157
|
-
| `managerOptions` | Opaque to Host. What it holds is the manager's own documentation
|
|
158
|
-
| `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`
|
|
159
|
-
| `workLine.isolation` | Package name or path exporting `strategy` — required. Host loads it like a manager; Isolator/Integrator do not sniff
|
|
160
|
-
| `workLine.isolationOptions` | What that strategy is told, unread by Host — like `managerOptions`. `isolation-git` reads `exclude`, the paths a Child never gets (default `.env`, `.env.*`). A strategy without `createStrategy` refuses any
|
|
161
|
-
| `workLine.
|
|
162
|
-
| `
|
|
163
|
-
| `
|
|
164
|
-
| `
|
|
165
|
-
| `
|
|
166
|
-
| `
|
|
167
|
-
| `
|
|
168
|
-
| `
|
|
169
|
-
| `builder.
|
|
170
|
-
| `builder.
|
|
171
|
-
| `builder.
|
|
172
|
-
| `assembly.fix` | `cmd` / `timeoutMs`. Spawned when a judgement of the whole refused it — by an Authority, by `assembly.validate`, or by both
|
|
173
|
-
| `assembly.validate` | `cmd` / `timeoutMs`. Optional. A local, read-only judge of the assembled feature, run after align, with or without an Authority. A refusal is repaired by `assembly.fix` the same as an Authority's
|
|
174
|
-
| `assembly.maxAttempts` | Attempts that one fix may start. Default 3
|
|
175
|
-
| `assembly.gates` |
|
|
176
|
-
| `timeoutMs` | How long a child **outside** a Task may run: manager, isolations
|
|
177
|
-
| `maxRefusals` | Times the work may be sent back before it escalates, by an Authority, by `assembly.validate`, or by both — one shared budget. Default 3
|
|
178
|
-
| `pollIntervalMs` | Set it and the process keeps draining until SIGINT. Absent: one tick. Pick it with § Choosing `pollIntervalMs` — 10000 is the floor worth having
|
|
167
|
+
| Field | Meaning |
|
|
168
|
+
| --------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
169
|
+
| `manager` | Package name or path that exports `createManager` |
|
|
170
|
+
| `managerOptions` | Opaque to Host. What it holds is the manager's own documentation |
|
|
171
|
+
| `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` |
|
|
172
|
+
| `workLine.isolation` | Package name or path exporting `strategy` — required. Host loads it like a manager; Isolator/Integrator do not sniff |
|
|
173
|
+
| `workLine.isolationOptions` | What that strategy is told, unread by Host — like `managerOptions`. `isolation-git` reads `exclude`, the paths a Child never gets (default `.env`, `.env.*`). A strategy without `createStrategy` refuses any |
|
|
174
|
+
| `workLine.warm` | Optional `cmd` / `timeoutMs`. Runs once in the Feature workspace after Isolation, before any Subtask is copied from it — typically `pnpm install --frozen-lockfile`. Absent: Subtasks inherit a cold Feature |
|
|
175
|
+
| `workLine.branch` | The work line a fold lands on. Without it, what the reference names, or else whatever is checked out |
|
|
176
|
+
| `authority` | `enabled`, the `publish` / `refresh` slots, and the optional `describe` slot (§ Describer). Absent: the fold is local |
|
|
177
|
+
| `home` | Where Host keeps what is its own — default `./.mason` |
|
|
178
|
+
| `workspaceRoot` | Isolated feature / Subtask directories (created). Default `<home>/workspaces` |
|
|
179
|
+
| `ledger` | WorkLedger root (created). The Cursor is a file inside it. Default `<home>/ledger` |
|
|
180
|
+
| `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 |
|
|
181
|
+
| `planner` | `cmd` / `timeoutMs` / `gates`. FeatureStandard in, a Plan out. Its own `gates` judges `workLineStable` once a Plan is otherwise accepted — not where the Planner agent itself reads from |
|
|
182
|
+
| `builder.producer` | `cmd` / `timeoutMs` / `gates`. Spawned in the Subtask workspace. Exit 0 runs its own `gates` |
|
|
183
|
+
| `builder.repair` | The same, for a pass a Gate refused. **Required**, and never inherited — including its `gates`: a repair with none of its own runs ungated, even when `builder.producer.gates` is not empty |
|
|
184
|
+
| `builder.maxAttempts` | Attempts one Task may start. Default 3 |
|
|
185
|
+
| `assembly.fix` | `cmd` / `timeoutMs` / `gates`. Spawned when a judgement of the whole refused it — by an Authority, by `assembly.validate`, or by both. Judged by its own `gates` |
|
|
186
|
+
| `assembly.validate` | `cmd` / `timeoutMs` / `gates`. Optional. A local, read-only judge of the assembled feature, run after align, with or without an Authority. A refusal is repaired by `assembly.fix` the same as an Authority's. Judged by its own `gates`, never `builder`'s or `assembly.fix`'s |
|
|
187
|
+
| `assembly.maxAttempts` | Attempts that one fix may start. Default 3 |
|
|
188
|
+
| `assembly.gates` | `defaultTimeoutMs` and `gates`: the produce:false judgement-only pass — no producer of its own, re-checking the assembled feature. Empty: the Authority judges alone. Independent of `assembly.fix.gates` / `assembly.validate.gates` — never shared with either |
|
|
189
|
+
| `timeoutMs` | How long a child **outside** a Task may run: manager, isolations |
|
|
190
|
+
| `maxRefusals` | Times the work may be sent back before it escalates, by an Authority, by `assembly.validate`, or by both — one shared budget. Default 3 |
|
|
191
|
+
| `pollIntervalMs` | Set it and the process keeps draining until SIGINT. Absent: one tick. Pick it with § Choosing `pollIntervalMs` — 10000 is the floor worth having |
|
|
179
192
|
| `observability.streams` | `enabled`, `dir`, `keep`. Films what every child says, as it says it — one file per child under `<dir>/<feature>/`, 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 |
|
|
180
193
|
|
|
181
194
|
Every `cmd` is named where it is used, and nothing falls back to a neighbour: a
|
|
@@ -187,6 +200,10 @@ runs after align, before the Authority sees the work (or before the final
|
|
|
187
200
|
fold, with none). Its refusal is repaired by `assembly.fix` exactly as an
|
|
188
201
|
Authority's is, and shares the same `maxRefusals` budget.
|
|
189
202
|
|
|
203
|
+
Paths a slot **writes** — `--transcript-dir` and `--agent-home` — always resolve
|
|
204
|
+
against the config file's directory, even before that directory exists. Left
|
|
205
|
+
relative they would land inside the Task worktree (published, then destroyed).
|
|
206
|
+
|
|
190
207
|
Every tracker field lives in `managerOptions`. Host does not read them.
|
|
191
208
|
|
|
192
209
|
### Choosing `pollIntervalMs`
|
|
@@ -208,11 +225,11 @@ touch it. It only sets how long `mason run` takes to notice:
|
|
|
208
225
|
a read whose answer did not change comes back 304, which does not count
|
|
209
226
|
against the token's budget.
|
|
210
227
|
|
|
211
|
-
| The tick
|
|
212
|
-
|
|
|
213
|
-
| Nothing to do
|
|
214
|
-
| A Feature in flight
|
|
215
|
-
| A Feature `submitted`
|
|
228
|
+
| The tick | GitHub REST calls | Counted when nothing moved | Besides |
|
|
229
|
+
| --------------------- | ------------------------------------------------------------------------------------------------ | ---------------------------------------------------------- | ---------------------------------------------- |
|
|
230
|
+
| Nothing to do | 1: the issues updated since the Cursor, 100 per page | 0 (304) | none: no fetch |
|
|
231
|
+
| A Feature in flight | + 1 per Feature that can still be abandoned: is its issue still there | 0 (304) | a `git fetch` of the work line copy (not REST) |
|
|
232
|
+
| A Feature `submitted` | + the judgement: `ci-green` asks for the pull request, its check runs and its statuses (about 3) | about 3: `ci-green` is its own process and keeps no `etag` | |
|
|
216
233
|
|
|
217
234
|
Reports to the tracker (a comment, a label move: 2 to 4 calls each) happen
|
|
218
235
|
when the work moves, not per tick, and each changes the listing: the read
|
|
@@ -318,18 +335,27 @@ that completes cleanly but finds a problem is still a refusal.
|
|
|
318
335
|
|
|
319
336
|
### Gates
|
|
320
337
|
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
`
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
338
|
+
Five independent sequences, one per role — `planner`, `builder.producer`,
|
|
339
|
+
`builder.repair`, `assembly.fix`, `assembly.validate` — plus `assembly.gates`
|
|
340
|
+
for the one judgement-only pass with no producer of its own (re-checking the
|
|
341
|
+
assembled feature; `ci-green` reads the checks the work line ran on what the
|
|
342
|
+
Publisher placed, so it belongs there). No sharing, no dispatch choosing
|
|
343
|
+
between two buckets: a check right for one pass is not necessarily right for
|
|
344
|
+
another, even within the same stage. `builder.repair` is the sharpest edge —
|
|
345
|
+
it does not inherit `builder.producer.gates`, so a repair Attempt with no
|
|
346
|
+
Gate of its own runs ungated.
|
|
347
|
+
|
|
348
|
+
Implementer's Gates are told `--stage unit` or `--stage assembly` alongside
|
|
349
|
+
`--id`, `--attempt`, `--gate-id`, `--intention` and `--definition-of-done`.
|
|
350
|
+
Most have no use for it. `workspace-changed` does: "nothing changed" means
|
|
351
|
+
nothing was done in a unit, and is the correct outcome for an assembly that
|
|
352
|
+
needed nothing. FeatureBreakdown's Gates (`planner.gates`) are told only
|
|
353
|
+
`--key` and `--gate-id` — no Attempt counter, no `--stage`, no
|
|
354
|
+
`--definition-of-done` — so a Gate written for Implementer's shape is not a
|
|
355
|
+
drop-in for `planner.gates`.
|
|
356
|
+
|
|
357
|
+
`init` writes an empty list on `builder.producer` and `builder.repair`; add a
|
|
358
|
+
Gate when the Project has a check.
|
|
333
359
|
|
|
334
360
|
Every child carries its own ceiling and no other. A Gate entry may name a
|
|
335
361
|
`timeoutMs`; without one it takes its sequence's `defaultTimeoutMs`, and a Gate
|
|
@@ -338,7 +364,9 @@ from what another child left behind, so a slow producer can no longer leave its
|
|
|
338
364
|
Gates a millisecond to answer in.
|
|
339
365
|
|
|
340
366
|
What bounds a Subtask is therefore what you wrote:
|
|
341
|
-
`builder.maxAttempts × (builder.producer.timeoutMs + Σ builder.gates timeouts)
|
|
367
|
+
`builder.maxAttempts × (builder.producer.timeoutMs + Σ builder.producer.gates timeouts)`
|
|
368
|
+
for a first pass, and the same shape with `repair` in place of `producer` once
|
|
369
|
+
a Gate has refused one.
|
|
342
370
|
A repair and an assembly fix each carry their own `timeoutMs`. An assembly fix
|
|
343
371
|
is bounded the same way from `assembly.maxAttempts` and `assembly.fix`.
|
|
344
372
|
|
|
@@ -525,7 +553,7 @@ change that breaks it does not merge.
|
|
|
525
553
|
| `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 |
|
|
526
554
|
| `plugins/` | Load a package by name and check its shape: a manager, an isolation strategy | What to do with the loaded thing | `config` |
|
|
527
555
|
| `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 |
|
|
528
|
-
| `operator/` | Tools for a human: `init`, `setup`, `doctor`, `watch` / `status`, `log`, `cancel`, `board`, the terminal prompt
|
|
556
|
+
| `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 |
|
|
529
557
|
| `index.ts`, `cli.ts` | The package surface and the dispatch. Nothing else sits at the top of `src/` | Logic | everything |
|
|
530
558
|
|
|
531
559
|
Host's code names no plugin. A manager, an isolation strategy, a persistence
|
|
@@ -576,10 +604,10 @@ SPECS.md behavioural contract
|
|
|
576
604
|
README.md how to install, configure and run; the layers above
|
|
577
605
|
src/
|
|
578
606
|
index.ts the package surface
|
|
579
|
-
cli.ts mason run | watch | status | log | cancel | init | setup | doctor
|
|
607
|
+
cli.ts mason run | watch | status | log | cancel | release | init | setup | doctor
|
|
580
608
|
config/ argv, JSON config, config discovery → HostOptions; defaults: the plugin names Host ships with
|
|
581
609
|
plugins/ load: the one loader; manager, isolation, persist: one shape each; discover
|
|
582
610
|
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
|
|
583
|
-
operator/ init, setup, doctor, live (watch / status), log, film, cancel, board, prompt, git-remote
|
|
611
|
+
operator/ init, setup, doctor, live (watch / status), log, film, cancel, release, board, prompt, git-remote
|
|
584
612
|
fixtures/ stand-in slots for the loop's tests
|
|
585
613
|
```
|
package/SPECS.md
CHANGED
|
@@ -34,10 +34,10 @@ Planner / Builder / Gate argv.
|
|
|
34
34
|
|
|
35
35
|
## 2. Faces
|
|
36
36
|
|
|
37
|
-
| Face | Entry
|
|
38
|
-
| ------ |
|
|
39
|
-
| Import | `openHost(options)` then `runOnce` / `run`
|
|
40
|
-
| CLI | `mason run \| watch \| status \| log \| cancel \| init \| setup \| doctor` | Interpret a FeatureStandard; pick Child paths |
|
|
37
|
+
| Face | Entry | Must not |
|
|
38
|
+
| ------ | ------------------------------------------------------------------------------------- | --------------------------------------------- |
|
|
39
|
+
| Import | `openHost(options)` then `runOnce` / `run` | Live inside Conductor `src/run/` |
|
|
40
|
+
| CLI | `mason run \| watch \| status \| log \| cancel \| release \| init \| setup \| doctor` | Interpret a FeatureStandard; pick Child paths |
|
|
41
41
|
|
|
42
42
|
`src/loop/` is the process: `open-host.ts` opens packages (composition),
|
|
43
43
|
`tick.ts` runs the listen / probe / Cursor / sweep loop, `deliveries.ts` handles one
|
|
@@ -147,9 +147,11 @@ than waited for; the Transformer then answers `interrupted`. `mason run` exits
|
|
|
147
147
|
|
|
148
148
|
Each pass appends JSON lines to `<ledgerRoot>/events.jsonl` (listen, probed,
|
|
149
149
|
Transformer progress, admitted, reported, queued, idle, a heartbeat while
|
|
150
|
-
`submitted`
|
|
151
|
-
|
|
152
|
-
|
|
150
|
+
`submitted`, one `held` while a Subtask waits on a bail that has not expired).
|
|
151
|
+
Every report to the tracker is filmed with how long it took (`ms`): `reported`
|
|
152
|
+
when the manager took it (including a Thread that already carried that eventId
|
|
153
|
+
— a duplicate is already said), `report-declined` when it failed. That file is
|
|
154
|
+
a film, not truth: a crash may drop the last lines;
|
|
153
155
|
the ledger is still right. `mason run` still writes a human Trace on stdout.
|
|
154
156
|
The journal is how another process attaches.
|
|
155
157
|
|
|
@@ -177,7 +179,7 @@ and left; the next sweep that finds the Project free claims it. The same set
|
|
|
177
179
|
copy: starting a queued Feature on a copy that was not fast-forwarded this
|
|
178
180
|
tick would build on a version that no longer exists.
|
|
179
181
|
|
|
180
|
-
## 8. init, setup, doctor, watch, status, log and
|
|
182
|
+
## 8. init, setup, doctor, watch, status, log, cancel and release
|
|
181
183
|
|
|
182
184
|
`init` writes a config that already runs: the Planner is the bootstrap one from
|
|
183
185
|
`@bluewombat/slots/planners/`, the Builder is a stub that says what to replace
|
|
@@ -186,16 +188,23 @@ and the Gate sequence starts empty. Example Gates live in
|
|
|
186
188
|
`@bluewombat/slots/gates/` (`parent-clean`, `sensitive-path`,
|
|
187
189
|
`workspace-changed`, `ci-green`).
|
|
188
190
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
191
|
+
Five independent Gate sequences, one per role — `planner.gates`,
|
|
192
|
+
`builder.producer.gates`, `builder.repair.gates`, `assembly.fix.gates`,
|
|
193
|
+
`assembly.validate.gates` — plus `assembly.gates` for the one judgement-only
|
|
194
|
+
pass with no producer of its own. No sequence is shared and nothing is
|
|
195
|
+
dispatched between buckets: a check that is right for one pass can be wrong
|
|
196
|
+
for another, even within the same stage — `workspace-changed` says an Attempt
|
|
197
|
+
that changed nothing did nothing, which is true of `builder.producer`/`repair`
|
|
198
|
+
and false of `assembly.gates`'s pass, whose correct outcome is that nothing
|
|
199
|
+
was left to do. `builder.repair` does not inherit `builder.producer.gates`: a
|
|
200
|
+
repair Attempt with no Gate of its own runs ungated. The assembly's
|
|
201
|
+
judgement-only pass produces nothing unless something refused it first: it is
|
|
202
|
+
a judgement, and a refusal is `assembly.fix`, not a second first-pass. Two
|
|
203
|
+
things may judge it: `assembly.gates` (and an Authority's own checks on what
|
|
204
|
+
was published) and, optionally, `assembly.validate` — a local, read-only pass
|
|
205
|
+
that runs before either, judged by its own `assembly.validate.gates`, not
|
|
206
|
+
`assembly.gates`. Both send a refusal to the same `fix` and spend the same
|
|
207
|
+
`maxRefusals` budget.
|
|
199
208
|
|
|
200
209
|
Every child of a Task carries its own ceiling and no other. Every `cmd` names
|
|
201
210
|
its own `timeoutMs`, and none is inherited from a neighbour — nothing else
|
|
@@ -334,6 +343,14 @@ already cancelled, 1 when abandon is refused (`done`, point of no return), 2
|
|
|
334
343
|
when the key is missing or the ledger does not know it. The key is the first
|
|
335
344
|
positional: `cancel github:owner/name#19`.
|
|
336
345
|
|
|
346
|
+
`release <key>` drops a held Subtask (or a Feature still in `planning`) without
|
|
347
|
+
waiting for the bail clock — the hatch after a crash left nobody on the work.
|
|
348
|
+
A clean stop releases itself. It takes the ledger lock — stop `run` first if
|
|
349
|
+
it holds it. Exit 0 when the Subtask was released or the Feature was already
|
|
350
|
+
free, 1 when the Feature is past `planning` / `running` (the state is named),
|
|
351
|
+
2 when the key is missing or the ledger does not know it. The key is the first
|
|
352
|
+
positional: `release github:owner/name#19`.
|
|
353
|
+
|
|
337
354
|
## 9. Acceptance
|
|
338
355
|
|
|
339
356
|
1. Convertible upsert in an empty Source → WorkLineStable carries the Builder
|
package/dist/cli.js
CHANGED
|
@@ -10,6 +10,7 @@ import { runInit } from "./operator/init.js";
|
|
|
10
10
|
import { runLive } from "./operator/live.js";
|
|
11
11
|
import { runLog } from "./operator/log.js";
|
|
12
12
|
import { isInteractive, terminalAsk } from "./operator/prompt.js";
|
|
13
|
+
import { runRelease } from "./operator/release.js";
|
|
13
14
|
import { runSetup } from "./operator/setup.js";
|
|
14
15
|
const USAGE = `Usage: ${PRODUCT} <command>
|
|
15
16
|
|
|
@@ -18,6 +19,7 @@ const USAGE = `Usage: ${PRODUCT} <command>
|
|
|
18
19
|
status snapshot the ledger, then exit
|
|
19
20
|
log replay the journal, including why a phase stopped
|
|
20
21
|
cancel abandon a Feature by key (when the tracker cannot say it is gone)
|
|
22
|
+
release drop a held Subtask without waiting for the bail clock
|
|
21
23
|
init set this directory up — asks, writes, then runs setup
|
|
22
24
|
doctor check the config, the slots, and the manager
|
|
23
25
|
setup prepare the tracker (--apply to write; a plan without it)
|
|
@@ -184,6 +186,22 @@ async function main(argv) {
|
|
|
184
186
|
detachSignals();
|
|
185
187
|
}
|
|
186
188
|
}
|
|
189
|
+
if (command === "release") {
|
|
190
|
+
const interruptFlag = { interrupted: false };
|
|
191
|
+
const detachSignals = installSignalHandlers(interruptFlag);
|
|
192
|
+
try {
|
|
193
|
+
return await runRelease({
|
|
194
|
+
cwd: process.cwd(),
|
|
195
|
+
argv: rest,
|
|
196
|
+
env: process.env,
|
|
197
|
+
write: (line) => process.stdout.write(line),
|
|
198
|
+
interruptFlag,
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
finally {
|
|
202
|
+
detachSignals();
|
|
203
|
+
}
|
|
204
|
+
}
|
|
187
205
|
process.stderr.write(USAGE);
|
|
188
206
|
return 2;
|
|
189
207
|
}
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAC;AAClD,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC1D,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAChD,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAEnD,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAC/C,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACjD,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACjD,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAC7C,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAC7C,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAC3C,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAClE,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAE/C,MAAM,KAAK,GAAG,UAAU,OAAO
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"load-config.d.ts","sourceRoot":"","sources":["../../src/config/load-config.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"load-config.d.ts","sourceRoot":"","sources":["../../src/config/load-config.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAIV,cAAc,EAIf,MAAM,YAAY,CAAC;AA+BpB,MAAM,MAAM,YAAY,GACpB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,UAAU,EAAE,OAAO,CAAC,cAAc,CAAC,CAAA;CAAE,GACjD;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAElC;;GAEG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,CAsKrD"}
|
|
@@ -18,9 +18,10 @@ const KNOWN_KEYS = new Set([
|
|
|
18
18
|
"maxRefusals",
|
|
19
19
|
"pollIntervalMs",
|
|
20
20
|
]);
|
|
21
|
-
const WORK_LINE_KEYS = new Set(["stable", "branch", "isolation", "isolationOptions"]);
|
|
21
|
+
const WORK_LINE_KEYS = new Set(["stable", "branch", "isolation", "isolationOptions", "warm"]);
|
|
22
22
|
const PASS_KEYS = new Set(["cmd", "timeoutMs"]);
|
|
23
|
-
const
|
|
23
|
+
const GATED_PASS_KEYS = new Set(["cmd", "timeoutMs", "gates"]);
|
|
24
|
+
const STAGE_KEYS = new Set(["producer", "repair", "maxAttempts"]);
|
|
24
25
|
const ASSEMBLY_KEYS = new Set(["fix", "validate", "maxAttempts", "gates"]);
|
|
25
26
|
const GATES_KEYS = new Set(["defaultTimeoutMs", "gates"]);
|
|
26
27
|
const AUTHORITY_KEYS = new Set(["enabled", "publish", "refresh", "describe"]);
|
|
@@ -97,6 +98,9 @@ export function loadConfig(path) {
|
|
|
97
98
|
if (workLine.isolationOptions !== undefined) {
|
|
98
99
|
invocation.workLineIsolationOptions = workLine.isolationOptions;
|
|
99
100
|
}
|
|
101
|
+
if (workLine.warm !== undefined) {
|
|
102
|
+
invocation.workLineWarm = workLine.warm;
|
|
103
|
+
}
|
|
100
104
|
}
|
|
101
105
|
const workspaceRoot = stringField(object, "workspaceRoot");
|
|
102
106
|
if (workspaceRoot.ok === false) {
|
|
@@ -120,7 +124,7 @@ export function loadConfig(path) {
|
|
|
120
124
|
invocation.persist = persist.value;
|
|
121
125
|
}
|
|
122
126
|
if (object.planner !== undefined) {
|
|
123
|
-
const planner =
|
|
127
|
+
const planner = readGatedPass(object.planner, configDir, "planner");
|
|
124
128
|
if (planner.ok === false) {
|
|
125
129
|
return planner;
|
|
126
130
|
}
|
|
@@ -243,6 +247,14 @@ function readWorkLine(value, configDir) {
|
|
|
243
247
|
Array.isArray(isolationOptions))) {
|
|
244
248
|
return { ok: false, reason: 'Config "workLine.isolationOptions" must be a YAML mapping.' };
|
|
245
249
|
}
|
|
250
|
+
let warm;
|
|
251
|
+
if (object.warm !== undefined) {
|
|
252
|
+
const read = readPass(object.warm, configDir, "workLine.warm");
|
|
253
|
+
if (read.ok === false) {
|
|
254
|
+
return read;
|
|
255
|
+
}
|
|
256
|
+
warm = read.value;
|
|
257
|
+
}
|
|
246
258
|
return {
|
|
247
259
|
ok: true,
|
|
248
260
|
...(stable.value === undefined ? {} : { stable: resolve(configDir, stable.value) }),
|
|
@@ -251,6 +263,7 @@ function readWorkLine(value, configDir) {
|
|
|
251
263
|
...(isolationOptions === undefined
|
|
252
264
|
? {}
|
|
253
265
|
: { isolationOptions: isolationOptions }),
|
|
266
|
+
...(warm === undefined ? {} : { warm }),
|
|
254
267
|
};
|
|
255
268
|
}
|
|
256
269
|
/**
|
|
@@ -483,6 +496,49 @@ function readPass(value, configDir, key) {
|
|
|
483
496
|
value: { cmd: resolveArgv(cmd.value, configDir), timeoutMs: timeoutMs.value },
|
|
484
497
|
};
|
|
485
498
|
}
|
|
499
|
+
/**
|
|
500
|
+
* A command, and its own Gate sequence — `planner`, `builder.producer`,
|
|
501
|
+
* `builder.repair`, `assembly.fix`, `assembly.validate`. Deliberately its own
|
|
502
|
+
* ~10 lines rather than a parameterized `readPass`: each reader's error
|
|
503
|
+
* messages and key set stay independently legible.
|
|
504
|
+
*/
|
|
505
|
+
function readGatedPass(value, configDir, key) {
|
|
506
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
507
|
+
return {
|
|
508
|
+
ok: false,
|
|
509
|
+
reason: `Config "${key}" must be a YAML mapping with "cmd" and "timeoutMs".`,
|
|
510
|
+
};
|
|
511
|
+
}
|
|
512
|
+
const object = value;
|
|
513
|
+
for (const name of Object.keys(object)) {
|
|
514
|
+
if (!GATED_PASS_KEYS.has(name)) {
|
|
515
|
+
return { ok: false, reason: `Config "${key}" has unknown key "${name}".` };
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
const cmd = stringArrayField(object, "cmd");
|
|
519
|
+
if (cmd.ok === false || cmd.value === undefined) {
|
|
520
|
+
return { ok: false, reason: `Config "${key}.cmd" must be a non-empty array of strings.` };
|
|
521
|
+
}
|
|
522
|
+
const timeoutMs = positiveIntField(object, "timeoutMs");
|
|
523
|
+
if (timeoutMs.ok === false || timeoutMs.value === undefined) {
|
|
524
|
+
return {
|
|
525
|
+
ok: false,
|
|
526
|
+
reason: `Config "${key}" needs a positive "timeoutMs": nothing else bounds a command.`,
|
|
527
|
+
};
|
|
528
|
+
}
|
|
529
|
+
let gates = [];
|
|
530
|
+
if (object.gates !== undefined) {
|
|
531
|
+
const read = readGates(object.gates, configDir, `${key}.gates`);
|
|
532
|
+
if (read.ok === false) {
|
|
533
|
+
return read;
|
|
534
|
+
}
|
|
535
|
+
gates = read.value;
|
|
536
|
+
}
|
|
537
|
+
return {
|
|
538
|
+
ok: true,
|
|
539
|
+
value: { cmd: resolveArgv(cmd.value, configDir), timeoutMs: timeoutMs.value, gates },
|
|
540
|
+
};
|
|
541
|
+
}
|
|
486
542
|
/**
|
|
487
543
|
* Making a Subtask: both passes named, neither borrowing the other's command.
|
|
488
544
|
*/
|
|
@@ -502,18 +558,17 @@ function readBuilder(value, configDir) {
|
|
|
502
558
|
if (object.repair === undefined) {
|
|
503
559
|
return { ok: false, reason: 'Config "builder" needs a "repair".' };
|
|
504
560
|
}
|
|
505
|
-
const readProducer =
|
|
561
|
+
const readProducer = readGatedPass(object.producer, configDir, "builder.producer");
|
|
506
562
|
if (readProducer.ok === false) {
|
|
507
563
|
return readProducer;
|
|
508
564
|
}
|
|
509
|
-
const readRepair =
|
|
565
|
+
const readRepair = readGatedPass(object.repair, configDir, "builder.repair");
|
|
510
566
|
if (readRepair.ok === false) {
|
|
511
567
|
return readRepair;
|
|
512
568
|
}
|
|
513
569
|
const spec = {
|
|
514
570
|
producer: readProducer.value,
|
|
515
571
|
repair: readRepair.value,
|
|
516
|
-
gates: [],
|
|
517
572
|
};
|
|
518
573
|
const maxAttempts = positiveIntField(object, "maxAttempts");
|
|
519
574
|
if (maxAttempts.ok === false) {
|
|
@@ -522,13 +577,6 @@ function readBuilder(value, configDir) {
|
|
|
522
577
|
if (maxAttempts.value !== undefined) {
|
|
523
578
|
spec.maxAttempts = maxAttempts.value;
|
|
524
579
|
}
|
|
525
|
-
if (object.gates !== undefined) {
|
|
526
|
-
const gates = readGates(object.gates, configDir, "builder.gates");
|
|
527
|
-
if (gates.ok === false) {
|
|
528
|
-
return gates;
|
|
529
|
-
}
|
|
530
|
-
spec.gates = gates.value;
|
|
531
|
-
}
|
|
532
580
|
return { ok: true, value: spec };
|
|
533
581
|
}
|
|
534
582
|
/**
|
|
@@ -554,14 +602,14 @@ function readAssembly(value, configDir) {
|
|
|
554
602
|
}
|
|
555
603
|
const spec = { gates: [] };
|
|
556
604
|
if (object.fix !== undefined) {
|
|
557
|
-
const read =
|
|
605
|
+
const read = readGatedPass(object.fix, configDir, "assembly.fix");
|
|
558
606
|
if (read.ok === false) {
|
|
559
607
|
return read;
|
|
560
608
|
}
|
|
561
609
|
spec.fix = read.value;
|
|
562
610
|
}
|
|
563
611
|
if (object.validate !== undefined) {
|
|
564
|
-
const read =
|
|
612
|
+
const read = readGatedPass(object.validate, configDir, "assembly.validate");
|
|
565
613
|
if (read.ok === false) {
|
|
566
614
|
return read;
|
|
567
615
|
}
|
|
@@ -592,8 +640,12 @@ function readAssembly(value, configDir) {
|
|
|
592
640
|
* resolved against the slot's working directory, which is the Task workspace.
|
|
593
641
|
* A workspace is a git worktree that is published and then destroyed: output
|
|
594
642
|
* left there is committed into the work first, and lost after.
|
|
643
|
+
*
|
|
644
|
+
* `--transcript-dir` and `--agent-home` are Host-owned write paths (films, and
|
|
645
|
+
* the vendor's isolated HOME). Both must resolve against the config directory
|
|
646
|
+
* even before the directory exists.
|
|
595
647
|
*/
|
|
596
|
-
const WRITTEN_PATH_FLAGS = new Set(["--transcript-dir"]);
|
|
648
|
+
const WRITTEN_PATH_FLAGS = new Set(["--transcript-dir", "--agent-home"]);
|
|
597
649
|
function resolveArgv(argv, configDir) {
|
|
598
650
|
return argv.map((token, at) => WRITTEN_PATH_FLAGS.has(argv[at - 1] ?? "")
|
|
599
651
|
? resolve(configDir, token)
|