@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.
- package/README.md +473 -0
- package/SPECS.md +303 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +172 -0
- package/dist/cli.js.map +1 -0
- package/dist/config/defaults.d.ts +17 -0
- package/dist/config/defaults.d.ts.map +1 -0
- package/dist/config/defaults.js +17 -0
- package/dist/config/defaults.js.map +1 -0
- package/dist/config/find-config.d.ts +9 -0
- package/dist/config/find-config.d.ts.map +1 -0
- package/dist/config/find-config.js +25 -0
- package/dist/config/find-config.js.map +1 -0
- package/dist/config/home.d.ts +17 -0
- package/dist/config/home.d.ts.map +1 -0
- package/dist/config/home.js +23 -0
- package/dist/config/home.js.map +1 -0
- package/dist/config/load-config.d.ts +13 -0
- package/dist/config/load-config.d.ts.map +1 -0
- package/dist/config/load-config.js +537 -0
- package/dist/config/load-config.js.map +1 -0
- package/dist/config/parse-args.d.ts +25 -0
- package/dist/config/parse-args.d.ts.map +1 -0
- package/dist/config/parse-args.js +478 -0
- package/dist/config/parse-args.js.map +1 -0
- package/dist/config/types.d.ts +144 -0
- package/dist/config/types.d.ts.map +1 -0
- package/dist/config/types.js +2 -0
- package/dist/config/types.js.map +1 -0
- package/dist/index.d.ts +30 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +16 -0
- package/dist/index.js.map +1 -0
- package/dist/loop/authority.d.ts +51 -0
- package/dist/loop/authority.d.ts.map +1 -0
- package/dist/loop/authority.js +166 -0
- package/dist/loop/authority.js.map +1 -0
- package/dist/loop/context.d.ts +44 -0
- package/dist/loop/context.d.ts.map +1 -0
- package/dist/loop/context.js +2 -0
- package/dist/loop/context.js.map +1 -0
- package/dist/loop/cursor.d.ts +4 -0
- package/dist/loop/cursor.d.ts.map +1 -0
- package/dist/loop/cursor.js +20 -0
- package/dist/loop/cursor.js.map +1 -0
- package/dist/loop/deliveries.d.ts +22 -0
- package/dist/loop/deliveries.d.ts.map +1 -0
- package/dist/loop/deliveries.js +240 -0
- package/dist/loop/deliveries.js.map +1 -0
- package/dist/loop/describe.d.ts +40 -0
- package/dist/loop/describe.d.ts.map +1 -0
- package/dist/loop/describe.js +48 -0
- package/dist/loop/describe.js.map +1 -0
- package/dist/loop/drive.d.ts +15 -0
- package/dist/loop/drive.d.ts.map +1 -0
- package/dist/loop/drive.js +42 -0
- package/dist/loop/drive.js.map +1 -0
- package/dist/loop/journal.d.ts +22 -0
- package/dist/loop/journal.d.ts.map +1 -0
- package/dist/loop/journal.js +49 -0
- package/dist/loop/journal.js.map +1 -0
- package/dist/loop/lock.d.ts +11 -0
- package/dist/loop/lock.d.ts.map +1 -0
- package/dist/loop/lock.js +60 -0
- package/dist/loop/lock.js.map +1 -0
- package/dist/loop/message.d.ts +34 -0
- package/dist/loop/message.d.ts.map +1 -0
- package/dist/loop/message.js +48 -0
- package/dist/loop/message.js.map +1 -0
- package/dist/loop/open-host.d.ts +13 -0
- package/dist/loop/open-host.d.ts.map +1 -0
- package/dist/loop/open-host.js +180 -0
- package/dist/loop/open-host.js.map +1 -0
- package/dist/loop/probe.d.ts +9 -0
- package/dist/loop/probe.d.ts.map +1 -0
- package/dist/loop/probe.js +57 -0
- package/dist/loop/probe.js.map +1 -0
- package/dist/loop/report.d.ts +26 -0
- package/dist/loop/report.d.ts.map +1 -0
- package/dist/loop/report.js +328 -0
- package/dist/loop/report.js.map +1 -0
- package/dist/loop/tick.d.ts +24 -0
- package/dist/loop/tick.d.ts.map +1 -0
- package/dist/loop/tick.js +169 -0
- package/dist/loop/tick.js.map +1 -0
- package/dist/loop/trace.d.ts +16 -0
- package/dist/loop/trace.d.ts.map +1 -0
- package/dist/loop/trace.js +49 -0
- package/dist/loop/trace.js.map +1 -0
- package/dist/loop/transformers.d.ts +27 -0
- package/dist/loop/transformers.d.ts.map +1 -0
- package/dist/loop/transformers.js +229 -0
- package/dist/loop/transformers.js.map +1 -0
- package/dist/loop/work-line.d.ts +73 -0
- package/dist/loop/work-line.d.ts.map +1 -0
- package/dist/loop/work-line.js +109 -0
- package/dist/loop/work-line.js.map +1 -0
- package/dist/operator/board.d.ts +31 -0
- package/dist/operator/board.d.ts.map +1 -0
- package/dist/operator/board.js +67 -0
- package/dist/operator/board.js.map +1 -0
- package/dist/operator/cancel.d.ts +19 -0
- package/dist/operator/cancel.d.ts.map +1 -0
- package/dist/operator/cancel.js +70 -0
- package/dist/operator/cancel.js.map +1 -0
- package/dist/operator/doctor.d.ts +15 -0
- package/dist/operator/doctor.d.ts.map +1 -0
- package/dist/operator/doctor.js +416 -0
- package/dist/operator/doctor.js.map +1 -0
- package/dist/operator/git-remote.d.ts +39 -0
- package/dist/operator/git-remote.d.ts.map +1 -0
- package/dist/operator/git-remote.js +78 -0
- package/dist/operator/git-remote.js.map +1 -0
- package/dist/operator/init.d.ts +28 -0
- package/dist/operator/init.d.ts.map +1 -0
- package/dist/operator/init.js +522 -0
- package/dist/operator/init.js.map +1 -0
- package/dist/operator/live.d.ts +19 -0
- package/dist/operator/live.d.ts.map +1 -0
- package/dist/operator/live.js +149 -0
- package/dist/operator/live.js.map +1 -0
- package/dist/operator/prompt.d.ts +35 -0
- package/dist/operator/prompt.d.ts.map +1 -0
- package/dist/operator/prompt.js +125 -0
- package/dist/operator/prompt.js.map +1 -0
- package/dist/operator/setup.d.ts +18 -0
- package/dist/operator/setup.d.ts.map +1 -0
- package/dist/operator/setup.js +62 -0
- package/dist/operator/setup.js.map +1 -0
- package/dist/plugins/discover.d.ts +13 -0
- package/dist/plugins/discover.d.ts.map +1 -0
- package/dist/plugins/discover.js +81 -0
- package/dist/plugins/discover.js.map +1 -0
- package/dist/plugins/isolation.d.ts +78 -0
- package/dist/plugins/isolation.d.ts.map +1 -0
- package/dist/plugins/isolation.js +64 -0
- package/dist/plugins/isolation.js.map +1 -0
- package/dist/plugins/load.d.ts +35 -0
- package/dist/plugins/load.d.ts.map +1 -0
- package/dist/plugins/load.js +109 -0
- package/dist/plugins/load.js.map +1 -0
- package/dist/plugins/manager.d.ts +43 -0
- package/dist/plugins/manager.d.ts.map +1 -0
- package/dist/plugins/manager.js +47 -0
- package/dist/plugins/manager.js.map +1 -0
- package/dist/plugins/persist.d.ts +33 -0
- package/dist/plugins/persist.d.ts.map +1 -0
- package/dist/plugins/persist.js +26 -0
- package/dist/plugins/persist.js.map +1 -0
- package/package.json +66 -0
package/README.md
ADDED
|
@@ -0,0 +1,473 @@
|
|
|
1
|
+
# runtime
|
|
2
|
+
|
|
3
|
+
Host: the process you run, `@bluewombat/runtime`, binary `mason`. It listens,
|
|
4
|
+
admits, calls Conductor, and reports. It does not know a tracker: it loads one
|
|
5
|
+
FeatureManager package and hands it the options it never reads. Contract:
|
|
6
|
+
[SPECS.md](./SPECS.md).
|
|
7
|
+
|
|
8
|
+
## Install
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npm install @bluewombat/runtime @bluewombat/manager-github @bluewombat/slots
|
|
12
|
+
npx mason init
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
`init` at a terminal asks its way through: which manager (only when more than
|
|
16
|
+
one is installed), what that manager needs, and where WorkLineStable is — it can
|
|
17
|
+
clone it, and refuses a branch the remote does not have. Then it writes the
|
|
18
|
+
files and runs `setup` itself, offering to apply what the tracker is missing.
|
|
19
|
+
The Builder is left for you.
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npx mason doctor
|
|
23
|
+
npx mason run
|
|
24
|
+
npx mason watch
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
A script gets the same result with no questions — `--manager` turns them off:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
mason init --manager @bluewombat/manager-github \
|
|
31
|
+
--manager-option repo=owner/name \
|
|
32
|
+
--manager-option labels=mason \
|
|
33
|
+
--work-line-stable ./work-line-stable \
|
|
34
|
+
--clone git@github.com:owner/name.git --branch dev
|
|
35
|
+
mason setup --apply
|
|
36
|
+
mason doctor
|
|
37
|
+
mason run
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Each `--manager-option` is read by the question that would have asked for it, so
|
|
41
|
+
a malformed one stops `init` before it writes. `--clone` does what the wizard
|
|
42
|
+
offers: it checks the branch is really on the remote before cloning, falls back
|
|
43
|
+
to the remote's own default branch when `--branch` is left out, and leaves an
|
|
44
|
+
existing directory alone. `--interactive` forces the questions back on when
|
|
45
|
+
stdin is a pipe. `doctor` is the command whose exit code says whether a run will
|
|
46
|
+
work.
|
|
47
|
+
|
|
48
|
+
A GitHub Issues tracker is `@bluewombat/manager-github`, configured in that
|
|
49
|
+
package's README — Host does not grow a `--repo` flag for it.
|
|
50
|
+
|
|
51
|
+
## Commands
|
|
52
|
+
|
|
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 init` | Set this directory up: asks, writes the config and the Builder stub, then runs `setup` |
|
|
59
|
+
| `mason setup` | Prepare the tracker. A plan by default; `--apply` writes. Exit 1 on a blocked step |
|
|
60
|
+
| `mason doctor` | Check the config, the slots, and the manager. Exit 1 on any failure |
|
|
61
|
+
| `mason cancel` | Abandon a Feature by key, when the tracker cannot say it is gone |
|
|
62
|
+
|
|
63
|
+
One `run` per ledger: `run` writes `<ledger>/lock` with its pid and a second
|
|
64
|
+
`run` on the same home refuses to start while that process lives (two runs
|
|
65
|
+
both listen, both drive, and the tracker hears everything twice). A lock a
|
|
66
|
+
dead process left behind is taken over.
|
|
67
|
+
|
|
68
|
+
`setup` is the only command that changes anything outside this machine, and only
|
|
69
|
+
with `--apply`. What it prepares is the manager's business — Host prints the
|
|
70
|
+
steps it is handed. A manager without `setupManager` says it has nothing to do.
|
|
71
|
+
|
|
72
|
+
`init` takes `--manager NAME` (which turns the questions off), `--manager-option KEY=VALUE`,
|
|
73
|
+
`--work-line-stable DIR`, `--clone REMOTE`, `--branch NAME`, `--force` and `--interactive`.
|
|
74
|
+
|
|
75
|
+
Leave `mason run` going in one terminal. In another:
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
mason watch
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
That is the operator surface: the durable state of every Feature, then the film
|
|
82
|
+
of the current step (`submitted` included). It does not talk to the tracker, and
|
|
83
|
+
it cannot `ready` or claim. `status` is the same snapshot without the
|
|
84
|
+
follow. `--json` prints that snapshot as JSON.
|
|
85
|
+
|
|
86
|
+
To abandon a Feature the tracker can no longer mention (a deleted issue, a
|
|
87
|
+
manager with no `probe`), stop `run` if it holds the ledger lock, then:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
mason cancel github:owner/name#19
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
The film is `<ledger>/events.jsonl`, next to `cursor`. It is not truth after a
|
|
94
|
+
crash — the ledger is. The file can grow; there is no rotation yet.
|
|
95
|
+
|
|
96
|
+
## Config
|
|
97
|
+
|
|
98
|
+
`mason.config.json`, found by walking up from the working directory. `--config
|
|
99
|
+
FILE` names another one. Relative paths resolve against the file; relative
|
|
100
|
+
flag paths resolve against the working directory. Flags override the file, and
|
|
101
|
+
`--manager-option` overrides one manager option without dropping the others.
|
|
102
|
+
|
|
103
|
+
```json
|
|
104
|
+
{
|
|
105
|
+
"manager": "@bluewombat/manager-fake",
|
|
106
|
+
"managerOptions": { "source": "./.mason/source", "target": "./.mason/threads" },
|
|
107
|
+
"workLine": { "stable": "./work-line-stable", "isolation": "@bluewombat/isolation-copy" },
|
|
108
|
+
"planner": {
|
|
109
|
+
"cmd": ["node", "./node_modules/@bluewombat/slots/planners/one-subtask.mjs"],
|
|
110
|
+
"timeoutMs": 600000
|
|
111
|
+
},
|
|
112
|
+
"builder": {
|
|
113
|
+
"producer": { "cmd": ["node", "./mason-builder.mjs"], "timeoutMs": 600000 },
|
|
114
|
+
"repair": { "cmd": ["node", "./mason-repair.mjs"], "timeoutMs": 600000 },
|
|
115
|
+
"maxAttempts": 3,
|
|
116
|
+
"gates": { "defaultTimeoutMs": 120000, "gates": [] }
|
|
117
|
+
},
|
|
118
|
+
"assembly": { "gates": { "defaultTimeoutMs": 900000, "gates": [] } },
|
|
119
|
+
"authority": { "enabled": false },
|
|
120
|
+
"timeoutMs": 600000,
|
|
121
|
+
"pollIntervalMs": 30000
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
| Field | Meaning |
|
|
126
|
+
| --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
127
|
+
| `manager` | Package name or path that exports `createManager` |
|
|
128
|
+
| `managerOptions` | Opaque to Host. What it holds is the manager's own documentation |
|
|
129
|
+
| `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` |
|
|
130
|
+
| `workLine.isolation` | Package name or path exporting `strategy` — required. Host loads it like a manager; Isolator/Integrator do not sniff |
|
|
131
|
+
| `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 |
|
|
132
|
+
| `workLine.branch` | The work line a fold lands on. Without it, what the reference names, or else whatever is checked out |
|
|
133
|
+
| `authority` | `enabled`, the `publish` / `refresh` slots, and the optional `describe` slot (§ Describer). Absent: the fold is local |
|
|
134
|
+
| `home` | Where Host keeps what is its own — default `./.mason` |
|
|
135
|
+
| `workspaceRoot` | Isolated feature / Subtask directories (created). Default `<home>/workspaces` |
|
|
136
|
+
| `ledger` | WorkLedger root (created). The Cursor is a file inside it. Default `<home>/ledger` |
|
|
137
|
+
| `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 |
|
|
138
|
+
| `planner` | `cmd` / `timeoutMs`. FeatureStandard in, a Plan out |
|
|
139
|
+
| `builder.producer` | `cmd` / `timeoutMs`. Spawned in the Subtask workspace. Exit 0 lets Gates run |
|
|
140
|
+
| `builder.repair` | The same, for a pass a Gate refused. **Required**, and never inherited |
|
|
141
|
+
| `builder.maxAttempts` | Attempts one Task may start. Default 3 |
|
|
142
|
+
| `builder.gates` | `defaultTimeoutMs` and `gates`: ordered checks on each Subtask |
|
|
143
|
+
| `assembly.fix` | `cmd` / `timeoutMs`. Spawned when a judgement of the whole refused it. Needs `authority.enabled` |
|
|
144
|
+
| `assembly.maxAttempts` | Attempts that one fix may start. Default 3 |
|
|
145
|
+
| `assembly.gates` | Ordered checks on the assembled feature. Empty: the Authority judges alone |
|
|
146
|
+
| `timeoutMs` | How long a child **outside** a Task may run: manager, isolations |
|
|
147
|
+
| `maxRefusals` | Times an Authority may send a Submission back before it escalates. Default 3 |
|
|
148
|
+
| `pollIntervalMs` | Set it and the process keeps draining until SIGINT. Absent: one tick |
|
|
149
|
+
|
|
150
|
+
Every `cmd` is named where it is used, and nothing falls back to a neighbour: a
|
|
151
|
+
`repair` with no command of its own is refused rather than quietly running the
|
|
152
|
+
first-pass agent again. `builder` makes a Subtask. `assembly` judges the whole
|
|
153
|
+
and, when that judgement refuses it, runs `assembly.fix` — not a second first-pass.
|
|
154
|
+
|
|
155
|
+
Every tracker field lives in `managerOptions`. Host does not read them.
|
|
156
|
+
|
|
157
|
+
## Slots
|
|
158
|
+
|
|
159
|
+
Commands mason spawns and never imports: the Planner, the Builder, each Gate,
|
|
160
|
+
and — when an Authority is enabled — the Publisher and the Refresher. Every one
|
|
161
|
+
reads argv and answers with **one JSON object on stdout**; an exit code alone is
|
|
162
|
+
not the contract. mason does not pick a producer, and a slot may be written in
|
|
163
|
+
anything that can print a line.
|
|
164
|
+
|
|
165
|
+
Examples ship in [`@bluewombat/slots`](../../plugins/slots/README.md), installed with Host —
|
|
166
|
+
the shipped roles and agents (Cursor CLI, Claude Code), the shipped Gates, and
|
|
167
|
+
their options. What follows is what Host guarantees them.
|
|
168
|
+
|
|
169
|
+
```json
|
|
170
|
+
"planner": {
|
|
171
|
+
"cmd": ["node", "./node_modules/@bluewombat/slots/planners/one-subtask.mjs"],
|
|
172
|
+
"timeoutMs": 600000
|
|
173
|
+
},
|
|
174
|
+
"builder": {
|
|
175
|
+
"producer": {
|
|
176
|
+
"cmd": [
|
|
177
|
+
"node", "./node_modules/@bluewombat/slots/builders/producer.mjs",
|
|
178
|
+
"--",
|
|
179
|
+
"node", "./node_modules/@bluewombat/slots/agents/claude.mjs"
|
|
180
|
+
],
|
|
181
|
+
"timeoutMs": 600000
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
`builder.repair` is required too, with its own `cmd` and `timeoutMs`. The
|
|
187
|
+
composition with `--` is the same.
|
|
188
|
+
|
|
189
|
+
### Builder
|
|
190
|
+
|
|
191
|
+
Spawned in the Subtask workspace with `--intention`, `--definition-of-done`,
|
|
192
|
+
`--id`, `--attempt`, and `--context`. On a retry it also gets `--report` and
|
|
193
|
+
`--report-from`: what refused the last pass, and which Gate said it. Exit 0
|
|
194
|
+
means the Gates may run.
|
|
195
|
+
|
|
196
|
+
A Builder **leaves its work uncommitted in the workspace**. That is where
|
|
197
|
+
Integrator folds from, and it is what the Gates read: a commit moves `HEAD` and
|
|
198
|
+
hides the change from them.
|
|
199
|
+
|
|
200
|
+
The config always names a producer (and a repair). Implementer can still run a
|
|
201
|
+
Task as its Gate sequence alone — that is the first judgement of the assembled
|
|
202
|
+
feature, which makes nothing.
|
|
203
|
+
|
|
204
|
+
### Assembly fix
|
|
205
|
+
|
|
206
|
+
Spawned in the feature workspace when a judgement of the whole refused it
|
|
207
|
+
(`--report`, `--report-from`). Same argv as a Builder otherwise. Host hands
|
|
208
|
+
Implementer `assembly.fix` as that pass's only agent: there is no first-pass
|
|
209
|
+
producer at this moment.
|
|
210
|
+
|
|
211
|
+
### Gates
|
|
212
|
+
|
|
213
|
+
Two sequences, because a Subtask and the feature it assembles into are not the
|
|
214
|
+
same situation. `builder.gates` runs on each Subtask; `assembly.gates` on the
|
|
215
|
+
assembled feature. Put the Project's own checks in `builder.gates`, and fill
|
|
216
|
+
`assembly.gates` with what only makes sense on the whole — `ci-green` reads the
|
|
217
|
+
checks the work line ran on what the Publisher placed, so it belongs there.
|
|
218
|
+
|
|
219
|
+
Every Gate is told `--stage unit` or `--stage assembly` alongside `--id`,
|
|
220
|
+
`--attempt`, `--gate-id`, `--intention` and `--definition-of-done`. Most have no
|
|
221
|
+
use for it. `workspace-changed` does: "nothing changed" means nothing was done
|
|
222
|
+
in a unit, and is the correct outcome for an assembly that needed nothing.
|
|
223
|
+
|
|
224
|
+
`init` writes an empty list; add a Gate when the Project has a check.
|
|
225
|
+
|
|
226
|
+
Every child carries its own ceiling and no other. A Gate entry may name a
|
|
227
|
+
`timeoutMs`; without one it takes its sequence's `defaultTimeoutMs`, and a Gate
|
|
228
|
+
that ends up with neither is refused when the config loads. Nothing is derived
|
|
229
|
+
from what another child left behind, so a slow producer can no longer leave its
|
|
230
|
+
Gates a millisecond to answer in.
|
|
231
|
+
|
|
232
|
+
What bounds a Subtask is therefore what you wrote:
|
|
233
|
+
`builder.maxAttempts × (builder.producer.timeoutMs + Σ builder.gates timeouts)`.
|
|
234
|
+
A repair and an assembly fix each carry their own `timeoutMs`. An assembly fix
|
|
235
|
+
is bounded the same way from `assembly.maxAttempts` and `assembly.fix`.
|
|
236
|
+
|
|
237
|
+
```json
|
|
238
|
+
{
|
|
239
|
+
"id": "ci-green",
|
|
240
|
+
"argv": [
|
|
241
|
+
"node",
|
|
242
|
+
"./node_modules/@bluewombat/slots/gates/ci-green.mjs",
|
|
243
|
+
"--token-env",
|
|
244
|
+
"MASON_GITHUB_TOKEN",
|
|
245
|
+
"--require-checks"
|
|
246
|
+
],
|
|
247
|
+
"timeoutMs": 900000
|
|
248
|
+
}
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
### Planner
|
|
252
|
+
|
|
253
|
+
FeatureStandard in, a Plan out. It is given `--key`, `--intention`,
|
|
254
|
+
`--title` and `--max-units`. `init` writes the
|
|
255
|
+
bootstrap Planner — one Subtask that is the intention — from
|
|
256
|
+
`@bluewombat/slots/planners/one-subtask.mjs`.
|
|
257
|
+
|
|
258
|
+
### Describer
|
|
259
|
+
|
|
260
|
+
How a delivered feature describes itself — the words the manager puts on the
|
|
261
|
+
Submission and on the fold — is the Project's, not Host's. Three levels, one
|
|
262
|
+
key, `authority.describe`:
|
|
263
|
+
|
|
264
|
+
| `authority.describe` | The Submission's description | Cost |
|
|
265
|
+
| -------------------------------------- | --------------------------------------------------------------------------------------- | ------------------------- |
|
|
266
|
+
| not set | the manager's own default: the issue's title, its body as the human wrote it | none |
|
|
267
|
+
| `messages/conventional.mjs` | `<type>: <title>` (`--scope`, `--type`), the body as written | none |
|
|
268
|
+
| `messages/git-producer.mjs -- <agent>` | an agent reads the git diff and writes both under `--rules-file` — the team's guideline | one agent run per feature |
|
|
269
|
+
|
|
270
|
+
It is an argv like `publish` and `refresh`, bounded by the global `timeoutMs`.
|
|
271
|
+
Host spawns it once per feature, in the feature workspace, when the work is
|
|
272
|
+
offered — with `--id`, `--title`, `--intention` and `--target` (the work line
|
|
273
|
+
it lands on) — and takes one line back: `{ "subject": "…", "body": "…" }`.
|
|
274
|
+
Host hands that description to the manager as is; what it becomes is the
|
|
275
|
+
manager's business (for GitHub: the squash's title and message, the pull
|
|
276
|
+
request's first paragraph). A slot that fails is skipped and the journal says
|
|
277
|
+
so.
|
|
278
|
+
|
|
279
|
+
Subtasks are never described: their folds are folded again into the
|
|
280
|
+
feature's, so their message is the Subtask's intention — its headline when
|
|
281
|
+
the Planner wrote one (a line, a blank line, the rest: the shape of a commit),
|
|
282
|
+
else its first sentence cut at a word, no markdown. That headline is also what
|
|
283
|
+
the tracker's Plan, the progress comments and the Submission's change list
|
|
284
|
+
show, so a Planner is asked to write one.
|
|
285
|
+
|
|
286
|
+
### Write another slot
|
|
287
|
+
|
|
288
|
+
Anything that prints the right line. The plumbing a slot written in Node would
|
|
289
|
+
otherwise repeat is [`@bluewombat/slot-kit`](../slot-kit/README.md).
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
## Submissions
|
|
293
|
+
|
|
294
|
+
Without an Authority, a finished feature is folded straight into WorkLineStable
|
|
295
|
+
and that is the end of it. With one, the assembled feature is **offered** to an
|
|
296
|
+
outside judge first, and only an `accepted` verdict lets it into the work line.
|
|
297
|
+
|
|
298
|
+
It is **declared**, never inferred:
|
|
299
|
+
|
|
300
|
+
```json
|
|
301
|
+
"authority": {
|
|
302
|
+
"enabled": true,
|
|
303
|
+
"publish": ["node", "./node_modules/@bluewombat/slots/publishers/git.mjs"],
|
|
304
|
+
"refresh": ["node", "./node_modules/@bluewombat/slots/refreshers/git.mjs"]
|
|
305
|
+
}
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
`enabled` says whether the Project has an outside judge at all. `publish` is the
|
|
309
|
+
slot that puts the work in front of it, and `refresh` the one that reads back
|
|
310
|
+
what it accepted — both are required when enabled. The manager must declare
|
|
311
|
+
`submit` and `fold` — `@bluewombat/manager-github` does. Enabled against a manager
|
|
312
|
+
that does not, or with nothing naming the work line it is offered to, is refused
|
|
313
|
+
before the run starts rather than quietly folded locally.
|
|
314
|
+
|
|
315
|
+
### The reference work line
|
|
316
|
+
|
|
317
|
+
With an Authority, the reference work line lives where the Authority is, and the
|
|
318
|
+
directory here is only Host's copy of it. A manager that names that reference
|
|
319
|
+
(`referenceManager`, in words the Isolation strategy reads — `remote` and
|
|
320
|
+
`branch` for `@bluewombat/isolation-git`) spares the config from repeating them:
|
|
321
|
+
leave `workLine.stable` and `workLine.branch` out, and Host keeps the copy at
|
|
322
|
+
`<home>/work-line`, fetched on the first run, and refreshed before every pass that has work to drive. The
|
|
323
|
+
Refresher's `--target` and the Submission's target are read from the reference.
|
|
324
|
+
|
|
325
|
+
Everything about that copy is settled before a run writes anything: a key the
|
|
326
|
+
strategy does not know, a branch the remote does not have, a `workLine.branch`
|
|
327
|
+
that disagrees with the reference, or a `workLine.stable` that is not a copy of
|
|
328
|
+
it each stop `mason run` at boot, and `mason doctor` reports the same offline
|
|
329
|
+
(`reference`, `work line`). Without a reference, `workLine.stable` is required
|
|
330
|
+
and is the operator's directory, as before.
|
|
331
|
+
|
|
332
|
+
```
|
|
333
|
+
assembled → Publisher places the work → manager opens the Submission
|
|
334
|
+
→ submitted, and the run comes back to it on each poll
|
|
335
|
+
assembly.gates judge what was published
|
|
336
|
+
refused → assembly.fix, then the same Submission is offered again
|
|
337
|
+
accepted → the Authority folds it → done
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
Neither half judges: that is what `assembly.gates` are for. The Authority places
|
|
341
|
+
and folds; a Gate says whether the fold may happen. After the
|
|
342
|
+
Authority folds, Host brings its copy of the work line up to date with a
|
|
343
|
+
fast-forward — the work line moved where the Authority is, and the next
|
|
344
|
+
Isolation must start from that version, not from a copy left behind.
|
|
345
|
+
|
|
346
|
+
A refusal is not a second failure path: its report reaches `assembly.fix`
|
|
347
|
+
exactly as a Gate's report reaches `builder.repair`. After `maxRefusals`
|
|
348
|
+
refusals the feature escalates with that report as its Trace, and `done` names
|
|
349
|
+
the Submission rather than a directory on the machine that ran.
|
|
350
|
+
|
|
351
|
+
## Write another manager
|
|
352
|
+
|
|
353
|
+
Host loads whatever `manager` names, as long as it exports `createManager`. The
|
|
354
|
+
contract and a worked example are in
|
|
355
|
+
[`@bluewombat/manager-kit`](../manager-kit/README.md). Nothing in Host changes.
|
|
356
|
+
|
|
357
|
+
## Import
|
|
358
|
+
|
|
359
|
+
```ts
|
|
360
|
+
import { openHost } from "@bluewombat/runtime";
|
|
361
|
+
|
|
362
|
+
const host = await openHost({
|
|
363
|
+
manager: "@bluewombat/manager-fake",
|
|
364
|
+
managerOptions: { source: "./.mason/source", target: "./.mason/threads" },
|
|
365
|
+
// …paths and slots
|
|
366
|
+
});
|
|
367
|
+
await host.runOnce();
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
`manager` also takes an already-loaded module, which is how a test drives Host
|
|
371
|
+
without resolving a package.
|
|
372
|
+
|
|
373
|
+
## Stack
|
|
374
|
+
|
|
375
|
+
Repository stack — TypeScript, Node 24.20, Biome, `node:test`. See [docs/DEVELOPMENT.md](../../../docs/DEVELOPMENT.md).
|
|
376
|
+
|
|
377
|
+
## Setup
|
|
378
|
+
|
|
379
|
+
From the repository root:
|
|
380
|
+
|
|
381
|
+
```bash
|
|
382
|
+
nvm use
|
|
383
|
+
npm install
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
## Commands (development)
|
|
387
|
+
|
|
388
|
+
| Task | Command |
|
|
389
|
+
| --------- | ---------------- |
|
|
390
|
+
| Lint | `npm run lint` |
|
|
391
|
+
| Format | `npm run format` |
|
|
392
|
+
| Typecheck | `npm run tsc` |
|
|
393
|
+
| Test | `npm test` |
|
|
394
|
+
| Build | `npm run build` |
|
|
395
|
+
|
|
396
|
+
Binary after build: `dist/cli.js` (package bin name `mason`).
|
|
397
|
+
|
|
398
|
+
## Layers
|
|
399
|
+
|
|
400
|
+
Host is the most tangled package by nature: it is where everything else meets.
|
|
401
|
+
So it is cut into four layers, and imports point one way only:
|
|
402
|
+
|
|
403
|
+
```
|
|
404
|
+
operator → loop → plugins → config
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
A file imports from its own layer or a lower one, never a higher one. That is
|
|
408
|
+
the whole rule. `scripts/check-host.mjs` enforces it in `npm run lint`, so a
|
|
409
|
+
change that breaks it does not merge.
|
|
410
|
+
|
|
411
|
+
| Layer | Holds | Does not hold | May import |
|
|
412
|
+
| -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------- | --------------------------------------------- |
|
|
413
|
+
| `config/` | What a Project wrote: argv, `mason.config.json`, discovery, `HostOptions`. Pure: JSON in, options out | A runtime value (a Journal, a Port, a process handle) | Node, `manager-kit` types |
|
|
414
|
+
| `plugins/` | Load a package by name and check its shape: a manager, an isolation strategy | What to do with the loaded thing | `config` |
|
|
415
|
+
| `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 |
|
|
416
|
+
| `operator/` | Tools for a human: `init`, `setup`, `doctor`, `watch` / `status`, `cancel`, `board`, the terminal prompt | Anything the loop needs — the loop cannot see this layer | everything below |
|
|
417
|
+
| `index.ts`, `cli.ts` | The package surface and the dispatch. Nothing else sits at the top of `src/` | Logic | everything |
|
|
418
|
+
|
|
419
|
+
Host's code names no plugin. A manager, an isolation strategy, a persistence
|
|
420
|
+
backend, the shipped slots: each is loaded by the name a config gives, through
|
|
421
|
+
`plugins/`, never imported. The names Host falls back to — the two isolation
|
|
422
|
+
strategies `init` writes, the default `persist`, the bootstrap Planner — are
|
|
423
|
+
strings in `config/defaults.ts` and nowhere else; `check-host.mjs` refuses an
|
|
424
|
+
import of any of them and a spelled-out name anywhere else. Host's
|
|
425
|
+
`package.json` declares exactly those defaults, so a config `init` wrote runs
|
|
426
|
+
with nothing else installed. A manager has no default, so it is not declared.
|
|
427
|
+
|
|
428
|
+
Two consequences worth stating, because they are the easy mistakes:
|
|
429
|
+
|
|
430
|
+
- A test hands `openHost` its film through the second argument
|
|
431
|
+
(`openHost(options, { journal })`), never through `HostOptions`. Options are
|
|
432
|
+
what a Project can write; a Journal is not.
|
|
433
|
+
- "Is this path a git work line?" (`inspectWorkLine`) belongs to the loop, and
|
|
434
|
+
`doctor` and `init` ask the loop. The remote helpers that only `init` and
|
|
435
|
+
`doctor` need (`git-remote.ts`) stay in `operator/`.
|
|
436
|
+
|
|
437
|
+
### Working in Host
|
|
438
|
+
|
|
439
|
+
Each kind of change has one place, and the order below is the order that keeps
|
|
440
|
+
the layers true. Skipping ahead is how a package like this one tangles.
|
|
441
|
+
|
|
442
|
+
1. **A change to the loop** touches `loop/` and its tests, nothing above. If it
|
|
443
|
+
needs a new value from the config, add the key in `config/` first, with its
|
|
444
|
+
parse test, then read it in `loop/`.
|
|
445
|
+
2. **A new pluggable thing** is a `Shape` in `plugins/` — a name for refusals
|
|
446
|
+
and a `check` of the imported module — loaded by `plugins/load.ts` like the
|
|
447
|
+
others; a config key that names it; a name in `config/defaults.ts` when it
|
|
448
|
+
has a default; and a call from `loop/open-host.ts`. Host imports the
|
|
449
|
+
default package so the config `init` writes runs, and never imports a
|
|
450
|
+
plugin from code. The strategy's `refOf` is the model: the plugin says, Host
|
|
451
|
+
relays.
|
|
452
|
+
3. **A new command for a human** is a file in `operator/`. It may read the
|
|
453
|
+
config, load plugins, and open the loop; it may not be needed by any of
|
|
454
|
+
them.
|
|
455
|
+
4. **Splitting `operator/` into its own package** comes after all of these,
|
|
456
|
+
and only if it keeps growing. `init` needs the config writers and `doctor`
|
|
457
|
+
the plugin loaders, and the layer rule already gives the isolation a
|
|
458
|
+
package would.
|
|
459
|
+
|
|
460
|
+
## Layout
|
|
461
|
+
|
|
462
|
+
```
|
|
463
|
+
SPECS.md behavioural contract
|
|
464
|
+
README.md how to install, configure and run; the layers above
|
|
465
|
+
src/
|
|
466
|
+
index.ts the package surface
|
|
467
|
+
cli.ts mason run | watch | status | cancel | init | setup | doctor
|
|
468
|
+
config/ argv, JSON config, config discovery → HostOptions; defaults: the plugin names Host ships with
|
|
469
|
+
plugins/ load: the one loader; manager, isolation, persist: one shape each; discover
|
|
470
|
+
loop/ open-host (composition), tick (the loop), deliveries, drive, probe, report, authority, work-line, cursor, lock, journal, trace, transformers
|
|
471
|
+
operator/ init, setup, doctor, live (watch / status), cancel, board, prompt, git-remote
|
|
472
|
+
fixtures/ stand-in slots for the loop's tests
|
|
473
|
+
```
|