@bluewombat/runtime 0.10.0 → 0.11.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 +121 -90
- package/SPECS.md +102 -22
- package/dist/cli.js +32 -1
- package/dist/cli.js.map +1 -1
- package/dist/config/agent-shorthand.d.ts +3 -4
- package/dist/config/agent-shorthand.d.ts.map +1 -1
- package/dist/config/agent-shorthand.js +3 -4
- package/dist/config/agent-shorthand.js.map +1 -1
- package/dist/config/fixture-yaml.d.ts +8 -0
- package/dist/config/fixture-yaml.d.ts.map +1 -0
- package/dist/config/fixture-yaml.js +30 -0
- package/dist/config/fixture-yaml.js.map +1 -0
- package/dist/config/load-config.d.ts.map +1 -1
- package/dist/config/load-config.js +88 -101
- 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 +3 -4
- package/dist/config/parse-args.js.map +1 -1
- package/dist/config/types.d.ts +8 -16
- package/dist/config/types.d.ts.map +1 -1
- package/dist/config/types.js +1 -1
- package/dist/config/types.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/loop/features.d.ts +16 -0
- package/dist/loop/features.d.ts.map +1 -1
- package/dist/loop/features.js +124 -45
- package/dist/loop/features.js.map +1 -1
- package/dist/loop/open-host.d.ts +10 -0
- package/dist/loop/open-host.d.ts.map +1 -1
- package/dist/loop/open-host.js +8 -8
- package/dist/loop/open-host.js.map +1 -1
- package/dist/loop/streams.d.ts +3 -0
- package/dist/loop/streams.d.ts.map +1 -1
- package/dist/loop/streams.js +2 -1
- package/dist/loop/streams.js.map +1 -1
- package/dist/loop/tick.d.ts +3 -0
- package/dist/loop/tick.d.ts.map +1 -1
- package/dist/loop/tick.js.map +1 -1
- package/dist/loop/trace.d.ts +1 -0
- package/dist/loop/trace.d.ts.map +1 -1
- package/dist/loop/trace.js +1 -1
- package/dist/loop/trace.js.map +1 -1
- package/dist/loop/transformers.d.ts +4 -4
- package/dist/loop/transformers.d.ts.map +1 -1
- package/dist/loop/transformers.js +11 -22
- package/dist/loop/transformers.js.map +1 -1
- package/dist/operator/board.d.ts +43 -4
- package/dist/operator/board.d.ts.map +1 -1
- package/dist/operator/board.js +153 -10
- package/dist/operator/board.js.map +1 -1
- package/dist/operator/doctor.d.ts.map +1 -1
- package/dist/operator/doctor.js +5 -8
- package/dist/operator/doctor.js.map +1 -1
- package/dist/operator/explain.d.ts.map +1 -1
- package/dist/operator/explain.js +6 -11
- package/dist/operator/explain.js.map +1 -1
- package/dist/operator/incident-dossier.d.ts +154 -0
- package/dist/operator/incident-dossier.d.ts.map +1 -0
- package/dist/operator/incident-dossier.js +696 -0
- package/dist/operator/incident-dossier.js.map +1 -0
- package/dist/operator/incident-system.d.ts +48 -0
- package/dist/operator/incident-system.d.ts.map +1 -0
- package/dist/operator/incident-system.js +152 -0
- package/dist/operator/incident-system.js.map +1 -0
- package/dist/operator/incident.d.ts +27 -0
- package/dist/operator/incident.d.ts.map +1 -0
- package/dist/operator/incident.js +220 -0
- package/dist/operator/incident.js.map +1 -0
- package/dist/operator/init.js +24 -24
- package/dist/operator/init.js.map +1 -1
- package/dist/operator/live.js +4 -4
- package/dist/operator/live.js.map +1 -1
- package/dist/operator/log.d.ts +2 -0
- package/dist/operator/log.d.ts.map +1 -1
- package/dist/operator/log.js +2 -5
- package/dist/operator/log.js.map +1 -1
- package/dist/operator/restart.d.ts +20 -0
- package/dist/operator/restart.d.ts.map +1 -0
- package/dist/operator/restart.js +63 -0
- package/dist/operator/restart.js.map +1 -0
- package/package.json +14 -14
package/README.md
CHANGED
|
@@ -53,19 +53,21 @@ 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 status` | Snapshot the ledger as a Feature / Ticket tree, then exit. `--json` for scripts |
|
|
57
57
|
| `mason log` | Replay the journal, with the reason a phase stopped. `--problems` for just those |
|
|
58
58
|
| `mason init` | Set this directory up: asks, writes the config and the Builder stub, then runs `setup` |
|
|
59
59
|
| `mason setup` | Prepare the tracker. A plan by default; `--apply` writes. Exit 1 on a blocked step |
|
|
60
60
|
| `mason doctor` | Check the config, the slots, and the manager. Exit 1 on any failure |
|
|
61
|
+
| `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 |
|
|
61
62
|
| `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
63
|
| `mason cancel` | Abandon a Ticket by key, when the tracker cannot say it is gone |
|
|
64
|
+
| `mason restart` | Resume an escalated Feature with a fresh workspace, made again from its line. Offline: the tracker is not told |
|
|
63
65
|
| `mason release` | Drop a held unit without waiting for the bail clock |
|
|
64
66
|
|
|
65
67
|
### What an agent role starts with
|
|
66
68
|
|
|
67
69
|
```bash
|
|
68
|
-
mason explain
|
|
70
|
+
mason explain ticket.build.producer # one role; no role names all of them
|
|
69
71
|
mason explain --json # the same facts for a script
|
|
70
72
|
```
|
|
71
73
|
|
|
@@ -128,7 +130,9 @@ mason watch
|
|
|
128
130
|
That is the operator surface: the durable state of every Ticket, then the film
|
|
129
131
|
of the current step (`submitted` included). It does not talk to the tracker, and
|
|
130
132
|
it cannot `resume` or claim. `status` is the same snapshot without the
|
|
131
|
-
follow
|
|
133
|
+
follow: a header with the counts, each Feature with its Tickets under it
|
|
134
|
+
(escalated Features first, finished ones last, keys in numeric order), then the
|
|
135
|
+
Tickets of no Feature. `--json` prints that tree as JSON.
|
|
132
136
|
|
|
133
137
|
When a run only printed `unavailable:<key>`, the reason is already in the
|
|
134
138
|
journal and `log` is how you read it:
|
|
@@ -144,6 +148,24 @@ distinct failure. If a slot was started with `--transcript-dir`, the newest
|
|
|
144
148
|
transcript files are named underneath, with the agent exit and duration read
|
|
145
149
|
from the start of each file.
|
|
146
150
|
|
|
151
|
+
When a Ticket or a Feature is waiting for a person, `incident` gathers what the
|
|
152
|
+
run knows of it:
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
mason incident # what waits for a person
|
|
156
|
+
mason incident 106 # one stop: the key, or the tracker's number
|
|
157
|
+
mason incident 106 --out ./inc # incident.md and incident.json, to hand on
|
|
158
|
+
mason incident 106 --with-config # also the config file, secret-looking values blanked
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
The page says what the run was made of (versions, what the configuration switched
|
|
162
|
+
on), who refused the work and what they said, the Tickets of the
|
|
163
|
+
Feature with their pull requests, the Gate's configuration, the journal's events
|
|
164
|
+
of the last round and where the streams and transcripts are. What it could not
|
|
165
|
+
find is listed under "Not found". Streams exist only if the Project turned them
|
|
166
|
+
on (`--streams-dir`); without them the page has the Gate's report but not what
|
|
167
|
+
the Gate printed.
|
|
168
|
+
|
|
147
169
|
To abandon a Ticket the tracker can no longer mention (a deleted issue, a
|
|
148
170
|
manager with no `probe`), stop `run` if it holds the ledger lock, then:
|
|
149
171
|
|
|
@@ -162,6 +184,18 @@ mason release github:owner/name#19
|
|
|
162
184
|
|
|
163
185
|
`release` destroys the workspace and forgets it; the next `run` isolates a fresh one.
|
|
164
186
|
|
|
187
|
+
A Feature that escalated keeps its work. When a person then changes its line and resumes it,
|
|
188
|
+
the work takes in what the line gained, merged into it, before it is judged again; a conflict
|
|
189
|
+
escalates the Feature again, naming it. To throw the work away instead, stop `run` if it holds
|
|
190
|
+
the lock, then:
|
|
191
|
+
|
|
192
|
+
```bash
|
|
193
|
+
mason restart github:owner/name#106 # or just 106
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
The Feature must be escalated. Its workspace is removed and it is resumed; the next `run`
|
|
197
|
+
cuts the work again from the line. The tracker is not told.
|
|
198
|
+
|
|
165
199
|
The film is `<ledger>/events.jsonl`, next to `cursor`. It is not truth after a
|
|
166
200
|
crash — the ledger is. The file can grow; there is no rotation yet.
|
|
167
201
|
|
|
@@ -178,43 +212,48 @@ value that starts with `@`, like every `@bluewombat/...` package name, needs
|
|
|
178
212
|
quotes: `@` is a reserved YAML indicator at the start of a plain scalar.
|
|
179
213
|
|
|
180
214
|
```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
215
|
workLine:
|
|
192
216
|
stable: ./work-line-stable
|
|
193
217
|
isolation: "@bluewombat/isolation-copy"
|
|
194
218
|
warm:
|
|
195
219
|
cmd: [pnpm, install, --frozen-lockfile]
|
|
196
220
|
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
221
|
ticket:
|
|
222
|
+
tracker:
|
|
223
|
+
package: "@bluewombat/manager-fake"
|
|
224
|
+
options:
|
|
225
|
+
source: ./.mason/source
|
|
226
|
+
target: ./.mason/threads
|
|
227
|
+
build:
|
|
228
|
+
producer:
|
|
229
|
+
cmd: [node, ./mason-builder.mjs]
|
|
230
|
+
timeoutMs: 600000
|
|
231
|
+
repair:
|
|
232
|
+
cmd: [node, ./mason-repair.mjs]
|
|
233
|
+
timeoutMs: 600000
|
|
234
|
+
gates: { defaultTimeoutMs: 120000, gates: [] }
|
|
235
|
+
maxAttempts: 3
|
|
236
|
+
validate: { agent: false }
|
|
207
237
|
gates: { defaultTimeoutMs: 900000, gates: [] }
|
|
238
|
+
feature:
|
|
239
|
+
tracker:
|
|
240
|
+
package: "@bluewombat/manager-fake"
|
|
241
|
+
options:
|
|
242
|
+
source: ./.mason/source
|
|
243
|
+
target: ./.mason/threads
|
|
208
244
|
authority:
|
|
209
245
|
enabled: false
|
|
210
246
|
timeoutMs: 600000
|
|
211
247
|
pollIntervalMs: 10000
|
|
212
248
|
```
|
|
213
249
|
|
|
250
|
+
The file reads by level, and each level in the order it is used: `ticket` and `feature` both hold
|
|
251
|
+
`tracker`, then what makes the work (`build` for a Ticket, `test` for a Feature), `validate`, `gates`, `fix`.
|
|
252
|
+
|
|
214
253
|
| Field | Meaning |
|
|
215
254
|
| --------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
216
|
-
| `
|
|
217
|
-
| `
|
|
255
|
+
| `feature.tracker` | `package` (a name or path that exports `createManager`) and `options`: where Features are written and signalled |
|
|
256
|
+
| `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
257
|
| `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
258
|
| `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
259
|
| `workLine.isolation` | Package name or path exporting `strategy` — required. Host loads it like a manager; Isolator/Integrator do not sniff |
|
|
@@ -227,14 +266,13 @@ pollIntervalMs: 10000
|
|
|
227
266
|
| `ledger` | WorkLedger root (created). The Cursor is a file inside it. Default `<home>/ledger` |
|
|
228
267
|
| `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
268
|
| `agents` | Agents a pass names instead of writing `cmd` (§ Agent passes written short) |
|
|
230
|
-
| `
|
|
231
|
-
| `
|
|
232
|
-
| `
|
|
233
|
-
| `
|
|
234
|
-
| `
|
|
235
|
-
| `builder.maxAttempts` | Attempts one Task may start. Default 3 |
|
|
269
|
+
| `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 |
|
|
270
|
+
| `ticket.build.producer` | `cmd` / `timeoutMs`. Spawned in the workspace. Exit 0 runs `ticket.build.gates` |
|
|
271
|
+
| `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 |
|
|
272
|
+
| `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 |
|
|
273
|
+
| `ticket.build.maxAttempts` | Attempts one Task may start. Default 3 |
|
|
236
274
|
| `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 `
|
|
275
|
+
| `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
276
|
| `ticket.maxAttempts` | Attempts that one fix may start. Default 3 |
|
|
239
277
|
| `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
278
|
| `timeoutMs` | How long a child **outside** a Task may run: manager, isolations |
|
|
@@ -246,7 +284,7 @@ pollIntervalMs: 10000
|
|
|
246
284
|
|
|
247
285
|
Every `cmd` is named where it is used, and nothing falls back to a neighbour: a
|
|
248
286
|
`repair` with no command of its own is refused rather than quietly running the
|
|
249
|
-
first-pass agent again. `
|
|
287
|
+
first-pass agent again. `ticket.build` makes the unit of work. `ticket` judges the whole
|
|
250
288
|
and, when that judgement refuses it, runs `ticket.fix` — not a second first-pass.
|
|
251
289
|
`ticket.validate`, when declared, is a second, local judge of the whole: it
|
|
252
290
|
runs after align, before the Authority sees the work (or before the final
|
|
@@ -275,23 +313,23 @@ agents:
|
|
|
275
313
|
denyShell: [] # commands refused to the agent, e.g. [gh, vercel]; ssh and scp always are
|
|
276
314
|
transcripts: ./.mason/transcripts
|
|
277
315
|
pluginsOut: ./.mason/plugins # where deck profiles are built
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
316
|
+
ticket:
|
|
317
|
+
build:
|
|
318
|
+
producer:
|
|
319
|
+
agent: cursor
|
|
320
|
+
prompt: ./prompts/builder.md
|
|
321
|
+
homePlugins: [./ai-plugins/core/builder]
|
|
322
|
+
appDeckProfile: .ai/profiles/builder
|
|
323
|
+
args: [--mode, agent] # the vendor CLI's own flags
|
|
324
|
+
timeoutMs: 1200000
|
|
287
325
|
```
|
|
288
326
|
|
|
289
327
|
Host writes the `cmd` it stands for — `node <slots>/<role>.mjs --prompt-file …
|
|
290
328
|
-- node <slots>/agents/cursor.mjs --transcript-dir … --plugin-dir … --app-deck-profile
|
|
291
329
|
… --plugins-out … --agent-arg …` — with `<slots>` the
|
|
292
330
|
`node_modules/@bluewombat/slots` next to the config. Nothing past the config
|
|
293
|
-
knows a pass was written short. The roles: `
|
|
294
|
-
`
|
|
331
|
+
knows a pass was written short. The roles: `ticket.build.producer`,
|
|
332
|
+
`ticket.build.repair`, `ticket.fix`, `ticket.validate`, and `authority.describe`
|
|
295
333
|
(an argv, or `{ agent, prompt?, homePlugins?, appDeckProfile?, args? }`).
|
|
296
334
|
|
|
297
335
|
| Key | Meaning |
|
|
@@ -387,20 +425,20 @@ Examples ship in [`@bluewombat/slots`](../../plugins/slots/README.md), installed
|
|
|
387
425
|
the shipped roles and agents (Cursor CLI, Claude Code), the shipped Gates, and
|
|
388
426
|
their options. What follows is what Host guarantees them.
|
|
389
427
|
|
|
390
|
-
```
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
428
|
+
```yaml
|
|
429
|
+
ticket:
|
|
430
|
+
build:
|
|
431
|
+
producer:
|
|
432
|
+
cmd:
|
|
433
|
+
- node
|
|
434
|
+
- ./node_modules/@bluewombat/slots/builders/producer.mjs
|
|
435
|
+
- --
|
|
436
|
+
- node
|
|
437
|
+
- ./node_modules/@bluewombat/slots/agents/claude.mjs
|
|
438
|
+
timeoutMs: 600000
|
|
401
439
|
```
|
|
402
440
|
|
|
403
|
-
`
|
|
441
|
+
`ticket.build.repair` is required too, with its own `cmd` and `timeoutMs`. The
|
|
404
442
|
composition with `--` is the same.
|
|
405
443
|
|
|
406
444
|
### Builder
|
|
@@ -437,16 +475,16 @@ that completes cleanly but finds a problem is still a refusal.
|
|
|
437
475
|
|
|
438
476
|
### Gates
|
|
439
477
|
|
|
440
|
-
Four sequences: `
|
|
441
|
-
`
|
|
478
|
+
Four sequences: `ticket.build.gates`, shared by `ticket.build.producer` and
|
|
479
|
+
`ticket.build.repair` (a repair is judged by exactly what judged the first try),
|
|
442
480
|
then one per assembly role — `ticket.fix`, `ticket.validate` — plus
|
|
443
481
|
`ticket.gates` for the one judgement-only pass with no producer of its own
|
|
444
482
|
(re-checking the assembled ticket; `ci-green` reads the checks the work line
|
|
445
483
|
ran on what the Publisher placed, so it belongs there). The assembly lists are
|
|
446
484
|
not shared with each other or with the builder, and no dispatch chooses between
|
|
447
485
|
two buckets: a check right for one pass is not necessarily right for another,
|
|
448
|
-
even within the same stage. `gates` written on `
|
|
449
|
-
`
|
|
486
|
+
even within the same stage. `gates` written on `ticket.build.producer` or
|
|
487
|
+
`ticket.build.repair` is refused, and the message names `ticket.build.gates`.
|
|
450
488
|
|
|
451
489
|
Implementer's Gates are told `--stage unit` or `--stage ticket` alongside
|
|
452
490
|
`--id`, `--attempt`, `--gate-id` and `--intention`.
|
|
@@ -454,7 +492,7 @@ Most have no use for it. `workspace-changed` does: "nothing changed" means
|
|
|
454
492
|
nothing was done in a unit, and is the correct outcome for an assembly that
|
|
455
493
|
needed nothing.
|
|
456
494
|
|
|
457
|
-
`init` writes an empty list on `
|
|
495
|
+
`init` writes an empty list on `ticket.build.producer` and `ticket.build.repair`; add a
|
|
458
496
|
Gate when the Project has a check.
|
|
459
497
|
|
|
460
498
|
Every child carries its own ceiling and no other. A Gate entry may name a
|
|
@@ -464,7 +502,7 @@ from what another child left behind, so a slow producer cannot leave its
|
|
|
464
502
|
Gates a millisecond to answer in.
|
|
465
503
|
|
|
466
504
|
What bounds the unit is therefore what you wrote:
|
|
467
|
-
`
|
|
505
|
+
`ticket.build.maxAttempts × (ticket.build.producer.timeoutMs + Σ ticket.build.gates timeouts)`
|
|
468
506
|
for a first pass, and the same shape with `repair` in place of `producer` once
|
|
469
507
|
a Gate has refused one.
|
|
470
508
|
A repair and an assembly fix each carry their own `timeoutMs`. An assembly fix
|
|
@@ -484,53 +522,46 @@ is bounded the same way from `ticket.maxAttempts` and `ticket.fix`.
|
|
|
484
522
|
}
|
|
485
523
|
```
|
|
486
524
|
|
|
487
|
-
### The
|
|
525
|
+
### The feature-test acceptance check
|
|
488
526
|
|
|
489
|
-
A Project proves each Feature by
|
|
527
|
+
A Project proves each Feature by its own tests, run once on the assembled Feature
|
|
490
528
|
line, when its config says so, by a pair:
|
|
491
529
|
|
|
492
530
|
```yaml
|
|
493
|
-
|
|
494
|
-
|
|
531
|
+
feature:
|
|
532
|
+
test: # makes the Ticket that holds the suite (label `feature-test` on the tracker)
|
|
495
533
|
agent: claude
|
|
496
|
-
prompt: ./prompts/
|
|
534
|
+
prompt: ./prompts/feature-test.md # {{task}} is the scenarios; {{report}}, {{refused_by}}, {{refusal}} on a refused pass
|
|
497
535
|
timeoutMs: 1800000
|
|
498
536
|
gates: # form only: it compiles, one test per manifest id, no application file touched
|
|
499
537
|
gates:
|
|
500
|
-
- { id:
|
|
501
|
-
feature:
|
|
538
|
+
- { id: feature-tests-form, argv: [...], timeoutMs: 120000 }
|
|
502
539
|
validate:
|
|
503
540
|
agent: false
|
|
504
541
|
gates:
|
|
505
542
|
defaultTimeoutMs: 120000
|
|
506
543
|
gates:
|
|
507
|
-
- id:
|
|
508
|
-
skipWhen: skip-test
|
|
544
|
+
- id: feature-tests
|
|
545
|
+
skipWhen: skip-feature-test # Host does not run this Gate for a Feature that carries the label
|
|
509
546
|
timeoutMs: 720000
|
|
510
547
|
argv:
|
|
511
548
|
- node
|
|
512
|
-
- ./node_modules/@bluewombat/slots/gates/
|
|
513
|
-
- --up
|
|
514
|
-
- pnpm e2e:up
|
|
515
|
-
- --ready
|
|
516
|
-
- pnpm e2e:ready
|
|
549
|
+
- ./node_modules/@bluewombat/slots/gates/feature-tests.mjs
|
|
517
550
|
- --run
|
|
518
|
-
- pnpm
|
|
519
|
-
- --down
|
|
520
|
-
- pnpm e2e:down
|
|
551
|
+
- pnpm test --grep "{ids}" --reporter=junit --outputFile={junit}
|
|
521
552
|
```
|
|
522
553
|
|
|
523
554
|
`skipWhen` is the one condition Host reads for a Gate, on a Feature's Gates only
|
|
524
555
|
(`feature.validate`, `feature.gates`, `feature.fix`); its one value today is
|
|
525
|
-
`skip-test
|
|
526
|
-
passed: the journal gets `gate-skipped` and the trace says "`
|
|
527
|
-
skip-test
|
|
556
|
+
`skip-feature-test`, whatever the tracker calls that label. A Gate left out is not a Gate that
|
|
557
|
+
passed: the journal gets `gate-skipped` and the trace says "`feature-test` not run: the Feature carries
|
|
558
|
+
skip-feature-test". The Gate knows nothing of the skip and no environment variable carries it.
|
|
528
559
|
|
|
529
|
-
The tracker side: a Ticket carrying the
|
|
530
|
-
made by `
|
|
531
|
-
(`
|
|
560
|
+
The tracker side: a Ticket carrying the feature-test label (`featureTestLabel` of the TicketTracker manager) is
|
|
561
|
+
made by `feature.test`; a Feature carrying the skip label
|
|
562
|
+
(`skipFeatureTestLabel` of the FeatureTracker manager) has that Ticket cancelled, with a line in
|
|
532
563
|
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
|
|
564
|
+
check refuses a Feature that holds neither a feature-test Ticket nor the skip label, or whose feature-test
|
|
534
565
|
Ticket is not blocked by every other Ticket, as `invalid`, with the reason as a comment.
|
|
535
566
|
|
|
536
567
|
What the Gate proves is a red/green verdict; what the operator reads is its text in the
|
|
@@ -610,7 +641,7 @@ it each stop `mason run` at boot, and `mason doctor` reports the same offline
|
|
|
610
641
|
and is the operator's directory.
|
|
611
642
|
|
|
612
643
|
```
|
|
613
|
-
assembled → align → recheck (
|
|
644
|
+
assembled → align → recheck (ticket.build.gates again, on the aligned work)
|
|
614
645
|
conflict or failed → ticket.fix, then align again
|
|
615
646
|
→ ticket.validate (optional, local)
|
|
616
647
|
refused → ticket.fix, then align again, recheck, validate again
|
|
@@ -631,13 +662,13 @@ fast-forward — the work line moved where the Authority is, and the next
|
|
|
631
662
|
Isolation must start from that version, not from a copy left behind.
|
|
632
663
|
|
|
633
664
|
A refusal is not a second failure path: its report reaches `ticket.fix`
|
|
634
|
-
exactly as a Gate's report reaches `
|
|
665
|
+
exactly as a Gate's report reaches `ticket.build.repair`. After `ticket.maxRefusals`
|
|
635
666
|
refusals the ticket escalates with that report as its Trace, and `done` names
|
|
636
667
|
the Submission rather than a directory on the machine that ran.
|
|
637
668
|
|
|
638
669
|
## Write another manager
|
|
639
670
|
|
|
640
|
-
Host loads whatever `
|
|
671
|
+
Host loads whatever `feature.tracker.package` and `ticket.tracker.package` name, as long as it exports `createManager`. The
|
|
641
672
|
contract and a worked example are in
|
|
642
673
|
[`@bluewombat/manager-kit`](../manager-kit/README.md). Nothing in Host changes.
|
|
643
674
|
|
|
@@ -706,7 +737,7 @@ change that breaks it does not merge.
|
|
|
706
737
|
| `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
738
|
| `plugins/` | Load a package by name and check its shape: a manager, an isolation strategy | What to do with the loaded thing | `config` |
|
|
708
739
|
| `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 |
|
|
740
|
+
| `operator/` | Tools for a human: `init`, `setup`, `doctor`, `watch` / `status`, `log`, `incident`, `cancel`, `restart`, `release`, `board`, the terminal prompt | Anything the loop needs — the loop cannot see this layer | everything below |
|
|
710
741
|
| `index.ts`, `cli.ts` | The package surface and the dispatch. Nothing else sits at the top of `src/` | Logic | everything |
|
|
711
742
|
|
|
712
743
|
Host's code names no plugin. A manager, an isolation strategy, a persistence
|
|
@@ -761,6 +792,6 @@ src/
|
|
|
761
792
|
config/ argv, JSON config, config discovery → HostOptions; defaults: the plugin names Host ships with
|
|
762
793
|
plugins/ load: the one loader; manager, isolation, persist: one shape each; discover
|
|
763
794
|
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
|
|
795
|
+
operator/ run, init, setup, doctor, explain, live (watch / status), log, incident, film, cancel, release, board, prompt, git-remote
|
|
765
796
|
fixtures/ stand-in slots for the loop's tests
|
|
766
797
|
```
|
package/SPECS.md
CHANGED
|
@@ -71,7 +71,7 @@ See § 8.
|
|
|
71
71
|
|
|
72
72
|
## 4. Loading a manager
|
|
73
73
|
|
|
74
|
-
`
|
|
74
|
+
`feature.tracker.package` and `ticket.tracker.package` are each a package name or a path. The two sides may name the same package or two different ones: a package asked for a side it does not answer is refused by name. One package with the same options on both sides is built once. `doctor` and `setup` run each distinct manager once; the reference work line is named by the ticket tracker, which holds the Authority. Resolution imports the bare specifier
|
|
75
75
|
first — mason and its managers installed side by side in one project — then
|
|
76
76
|
walks up from the config file's directory, which covers a global `mason`
|
|
77
77
|
driving a locally installed manager. A module without `createManager` is
|
|
@@ -103,7 +103,7 @@ The FeatureTracker's `listen` delivers Features whose specification is final.
|
|
|
103
103
|
| `invalid` (new, or the intention changed) | emit `invalid` on the Feature, keyed by fingerprint |
|
|
104
104
|
| `invalid` (same fingerprint as recorded) | leave it; the tracker was told, and telling it is what delivered it again |
|
|
105
105
|
| `upsert` (absent or `waiting`) | `admitFeature`, emit `accepted`. Nothing is driven here: the sweep does it |
|
|
106
|
-
| `upsert` (`open` or `escalated`) whose `
|
|
106
|
+
| `upsert` (`open` or `escalated`) whose `skip_feature_test` differs from the ledger's | `setFeatureSkipFeatureTest`; journal `skip-feature-test`; no other change |
|
|
107
107
|
| `upsert` (any other state) | leave it; journal `skipped`. An echo of a Host report is this row |
|
|
108
108
|
| `upsert` + `signalsResume` while `escalated` | same as `resume` — a poll snapshot has no `labeled` edge |
|
|
109
109
|
| `resume` | resume the Feature, emit `resumed` |
|
|
@@ -140,20 +140,33 @@ advanced (`advanceProject`):
|
|
|
140
140
|
4. A Feature whose Tickets are all blocked escalates; one that cannot be
|
|
141
141
|
settled because the Authority lacks `settled` waits.
|
|
142
142
|
|
|
143
|
-
The
|
|
143
|
+
The Feature's own work (`.mason/feature-workspaces/<feature>`) is cut from its
|
|
144
|
+
line once, when it is closed, and a pass that sent it to the fix finds it where
|
|
145
|
+
it was left. A resume is the one time the line may have gained commits since
|
|
146
|
+
(a person's): `resumeFeature` marks the work stale (`workspace_stale`), and the
|
|
147
|
+
next pass, before judging, refreshes the line and runs the Integration with the
|
|
148
|
+
work as Parent and the line copy as Child (`<key>:resume`), so what the work
|
|
149
|
+
holds, a refused fix's commits included, stays. Once it is done the mark is
|
|
150
|
+
cleared (`markFeatureWorkspaceCurrent`); a refresh that fails or an interrupt
|
|
151
|
+
leaves it, and the next pass tries again. A conflict is not settled by Host: the
|
|
152
|
+
Feature escalates (`assembly`) with a reason naming the conflict, the work left
|
|
153
|
+
as it was, and the mark stays for the next resume. A work cut after the resume
|
|
154
|
+
is current by construction.
|
|
155
|
+
|
|
156
|
+
The feature-test acceptance check, when the Project declares it (`feature.test`):
|
|
144
157
|
|
|
145
158
|
- A Feature does not open (and is reported `invalid`, once per reason) unless it holds exactly
|
|
146
|
-
one
|
|
147
|
-
- A Ticket with `
|
|
148
|
-
Transformers makes it with `
|
|
159
|
+
one feature-test Ticket (`TicketEntry.featureTest`), blocked by every other live Ticket, or carries `skip_feature_test`.
|
|
160
|
+
- A Ticket with `feature-test` is admitted with `feature-test`, and
|
|
161
|
+
Transformers makes it with `feature.test` (its command, its Gates, for the first pass, a refused
|
|
149
162
|
pass and the recheck) instead of the producer and the repair.
|
|
150
|
-
- A Feature with `
|
|
151
|
-
`
|
|
163
|
+
- A Feature with `skip_feature_test` has its feature-test Ticket cancelled by the sweep (journal
|
|
164
|
+
`feature-test-ticket-cancelled`, a trace line), and Transformers leaves out, for that Feature, every
|
|
152
165
|
Gate of `feature.validate`, `feature.gates` and `feature.fix` that has `skipWhen:
|
|
153
|
-
skip-test
|
|
154
|
-
says "`<id>` not run: the Feature carries skip-test
|
|
166
|
+
skip-feature-test`: the journal gets `gate-skipped` with the Gate id and the reason, and the trace
|
|
167
|
+
says "`<id>` not run: the Feature carries skip-feature-test". The mark is read from the ledger as
|
|
155
168
|
each pass starts (`TransformerSlots.marks`).
|
|
156
|
-
- A Feature that is not marked and no longer holds a live
|
|
169
|
+
- A Feature that is not marked and no longer holds a live feature-test Ticket (it was cancelled while the
|
|
157
170
|
mark was on) escalates when it closes.
|
|
158
171
|
|
|
159
172
|
Host never drives an `escalated` unit: that state is frozen until a human `resume`.
|
|
@@ -221,7 +234,7 @@ the Ticket that was driven. The work line copy is refreshed onto the Feature's
|
|
|
221
234
|
line before a Ticket starts: starting on a copy that was not fast-forwarded
|
|
222
235
|
would build on a version that no longer exists.
|
|
223
236
|
|
|
224
|
-
## 8. init, setup, doctor, watch, status, log, cancel and release
|
|
237
|
+
## 8. init, setup, doctor, watch, status, log, incident, cancel and release
|
|
225
238
|
|
|
226
239
|
`init` writes a config that already runs: the Builder is a stub that says what to replace
|
|
227
240
|
(example Cursor CLI and Claude Code Builders live in `@bluewombat/slots/builders/`),
|
|
@@ -229,14 +242,14 @@ and the Gate sequence starts empty. Example Gates live in
|
|
|
229
242
|
`@bluewombat/slots/gates/` (`parent-clean`, `sensitive-path`,
|
|
230
243
|
`workspace-changed`, `ci-green`).
|
|
231
244
|
|
|
232
|
-
Four Gate sequences: `
|
|
233
|
-
`
|
|
245
|
+
Four Gate sequences: `ticket.build.gates`, shared by `ticket.build.producer` and
|
|
246
|
+
`ticket.build.repair`, then `ticket.fix.gates`, `ticket.validate.gates` and
|
|
234
247
|
`ticket.gates` for the one judgement-only pass with no producer of its own.
|
|
235
248
|
The assembly sequences are not shared and nothing is dispatched between buckets: a check that is right for one pass can be wrong
|
|
236
249
|
for another, even within the same stage — `workspace-changed` says an Attempt
|
|
237
|
-
that changed nothing did nothing, which is true of `
|
|
250
|
+
that changed nothing did nothing, which is true of `ticket.build.producer`/`repair`
|
|
238
251
|
and false of `ticket.gates`'s pass, whose correct outcome is that nothing
|
|
239
|
-
was left to do. A repair Attempt is judged by `
|
|
252
|
+
was left to do. A repair Attempt is judged by `ticket.build.gates`, the same list as the
|
|
240
253
|
producer's (Host hands Implementer that list as both `gates` and `repairGates`). The assembly's
|
|
241
254
|
judgement-only pass produces nothing unless something refused it first: it is
|
|
242
255
|
a judgement, and a refusal is `ticket.fix`, not a second first-pass. Two
|
|
@@ -353,9 +366,16 @@ poll. A `listen` that did not complete is never held and never hidden.
|
|
|
353
366
|
`watch` and `status` are the operator live view. They open the ledger the same
|
|
354
367
|
way `run` does (`openFilesystemPersist` then `openWorkLedger`) and they do not
|
|
355
368
|
open a manager, Conductor, or Host. `status` prints a snapshot of every known
|
|
356
|
-
Ticket and exits
|
|
369
|
+
Feature and Ticket and exits: a header with the counts, then one line per Feature
|
|
370
|
+
(key, state, title, the Submission or why it stopped) with its Tickets indented
|
|
371
|
+
under it, then the Tickets that belong to no Feature under `No Feature`. A Ticket
|
|
372
|
+
holding the feature tests is marked `[feature-test]`. Features that need a person
|
|
373
|
+
(`escalated`) come first, then those in progress, then `done` and `cancelled`.
|
|
374
|
+
Keys are ordered by tracker, then by number (`#18` before `#107`), the order
|
|
375
|
+
`incident` lists what waits for a person in too. Plain text, no colour. `watch` prints that snapshot, then follows new journal lines
|
|
357
376
|
until SIGINT (exit 0). A missing journal is not an error: the snapshot still
|
|
358
|
-
stands. `--json` prints the
|
|
377
|
+
stands. `--json` prints the same tree: `{ features: [{ …, tickets: […] }], tickets: […] }`,
|
|
378
|
+
the latter holding the Tickets of no Feature. Ctrl-C on watch leaves the worker
|
|
359
379
|
running. The file can grow without rotation. A followed line that records a
|
|
360
380
|
stop includes the child's last words (`detail`, `reason`, a non-zero exit, a
|
|
361
381
|
Gate report that did not pass). `run` prints that same note under the phase
|
|
@@ -372,6 +392,57 @@ slot was given `--transcript-dir` it names the newest transcript files with
|
|
|
372
392
|
the agent exit and duration read from the start of each file. A missing
|
|
373
393
|
journal is not an error. Exit 2 when the config or `--tail` cannot be read.
|
|
374
394
|
|
|
395
|
+
`incident [<key>]` gathers what is known about one stop into a single page, for a
|
|
396
|
+
person (or anything else) who has to understand it. Read-only: it opens the
|
|
397
|
+
ledger, the journal, the streams and the transcripts, and writes only where
|
|
398
|
+
`--out DIR` says (`incident.md` and `incident.json`). It does not open Host, a
|
|
399
|
+
manager or Conductor, so it works on a stopped run and on one still going. The
|
|
400
|
+
key is a full key or the tracker's number (`106`, `#106`) when one key ends so;
|
|
401
|
+
two matches name both and exit 2, no match exits 2. With no key it lists what
|
|
402
|
+
waits for a person: every escalated Feature and Ticket, one line each. `--json`
|
|
403
|
+
prints the page as JSON.
|
|
404
|
+
|
|
405
|
+
The page is the schema `bluewombat.incident/1`. It states, without judging:
|
|
406
|
+
the subject (kind, state, the first lines of what was asked, `blocked_by`); the
|
|
407
|
+
stop (layer, what the ledger says verbatim, who refused it, the Gate or agent
|
|
408
|
+
that ended the last round with its verdict, the Task and Attempt, the moment);
|
|
409
|
+
the stops of this subject in the journal and its resumes; the budgets spent;
|
|
410
|
+
the Tickets of the Feature, each with state, Attempts, pull request and the
|
|
411
|
+
moment it landed; the configuration of the refusing Gate; the system (bluewombat,
|
|
412
|
+
Node, the `@bluewombat/*` packages the Project installed, and what the
|
|
413
|
+
configuration switched on: trackers, ledger, Authority, tickets in parallel,
|
|
414
|
+
limits, which passes run an agent or only Gates, each Gate sequence, streams;
|
|
415
|
+
a command shows its program only, never its arguments, which may hold a key); the journal's events
|
|
416
|
+
of the last round; the stream files and the transcripts under the subject, with
|
|
417
|
+
the end of the refusing Gate's stream, each agent's exit, duration, closing
|
|
418
|
+
words and `MASON_VERDICT` / `MASON_BLOCKED` line; and `gaps`, what could not be
|
|
419
|
+
found. A reader must never read an absence as a "no": a missing journal, a
|
|
420
|
+
stream that was never filmed or is gone, a Gate no longer in the configuration
|
|
421
|
+
and a subject that is not stopped are each said in `gaps`. The last round is
|
|
422
|
+
what the journal holds between the previous stop of the subject (or its
|
|
423
|
+
beginning) and the last `status` of phase `escalated`; without that line the
|
|
424
|
+
page says so and takes the moment from the ledger. `--with-config` adds the
|
|
425
|
+
configuration file itself, with the value of any `key: value` line whose key
|
|
426
|
+
looks like a secret (token, secret, password, api key, credential) blanked. The
|
|
427
|
+
page quotes the ledger's
|
|
428
|
+
words with a fence longer than any backtick run in them.
|
|
429
|
+
|
|
430
|
+
An escalation records, beside its words, who refused it (`refused_by`: a Gate's
|
|
431
|
+
id or a role, when the refusal came with a name) and when (`at`, the ledger's
|
|
432
|
+
clock); a Feature's also records where its line stopped (`kind`: `assembly` for
|
|
433
|
+
the assembled line refused by a check, `submitted` for its offer). All are
|
|
434
|
+
optional: a record written before them stays readable, and the page then takes
|
|
435
|
+
the refuser from the last non-passing Gate whose words the ledger repeats. The
|
|
436
|
+
tracker is told the stage of a Feature's stop from that `kind` (`integrating`
|
|
437
|
+
for an assembled line, `submitting` otherwise).
|
|
438
|
+
|
|
439
|
+
`restart <feature-key>` resumes an escalated Feature with a fresh workspace: the
|
|
440
|
+
Feature's work is removed (the same `rm` as a cancel) and the ledger's `resumeFeature` is taken,
|
|
441
|
+
so the next pass cuts the work again from the line and judges it. Nothing is said to the tracker and
|
|
442
|
+
nothing is fetched. It takes the ledger lock — stop `run` first. The key is the full key or the
|
|
443
|
+
tracker number, as `incident` takes it. Exit 0 once restarted, 1 when the Feature is not
|
|
444
|
+
`escalated`, 2 for a missing, unknown or Ticket key. The journal gets `restarted`.
|
|
445
|
+
|
|
375
446
|
`cancel <key>` abandons one Ticket the same way a `cancel` delivery does. The
|
|
376
447
|
team still abandons in the TicketTracker; this is the hatch when the tracker
|
|
377
448
|
cannot say the intention is gone (a manager with no `probe`, or an operator
|
|
@@ -423,10 +494,10 @@ positional: `release github:owner/name#19`.
|
|
|
423
494
|
open, escalated or submitted. A Feature blocked by another does not open
|
|
424
495
|
until it is `done`. A Feature whose Tickets are not all `task-ready` does
|
|
425
496
|
not open.
|
|
426
|
-
16b. With the
|
|
427
|
-
is reported `invalid`; with `
|
|
428
|
-
the journal and the trace saying so; with
|
|
429
|
-
red goes to `feature.fix` and green lets the line be offered. A Project without `
|
|
497
|
+
16b. With the feature-test check declared: a Feature without a feature-test Ticket nor `skip_feature_test` does not open and
|
|
498
|
+
is reported `invalid`; with `skip_feature_test` its feature-test Ticket is cancelled and the feature-tests Gate is not run,
|
|
499
|
+
the journal and the trace saying so; with a feature-test Ticket the line is judged once by the suite,
|
|
500
|
+
red goes to `feature.fix` and green lets the line be offered. A Project without `feature.test`
|
|
430
501
|
is unchanged.
|
|
431
502
|
17. After an escalation, a `probe` of `gone` cancels that Ticket (or Feature)
|
|
432
503
|
and the sweep goes on. `unavailable` leaves the
|
|
@@ -441,4 +512,13 @@ positional: `release github:owner/name#19`.
|
|
|
441
512
|
themselves the failure. `--json` returns every kept line. It does
|
|
442
513
|
not import `openHost`.
|
|
443
514
|
|
|
515
|
+
20. `incident` on a Feature escalated by a Gate names that Gate, quotes the ledger
|
|
516
|
+
verbatim, lists the Feature's Tickets with their pull requests and says in
|
|
517
|
+
`gaps` what it could not read. On a Ticket that stopped on a Gate it names
|
|
518
|
+
the Gate and the Feature above it. With no key it lists what waits. It
|
|
519
|
+
opens no Host and writes nothing but `--out`.
|
|
520
|
+
21. A Feature's line refused by a check that cannot be repaired escalates with
|
|
521
|
+
`refused_by` set to that check and `kind` `assembly`, and the tracker is told
|
|
522
|
+
`integrating`.
|
|
523
|
+
|
|
444
524
|
How to install, configure and run: [README.md](./README.md).
|