@bluewombat/conductor 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 +58 -0
- package/SPECS.md +385 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/run/open-conductor.d.ts +76 -0
- package/dist/run/open-conductor.d.ts.map +1 -0
- package/dist/run/open-conductor.js +775 -0
- package/dist/run/open-conductor.js.map +1 -0
- package/dist/run/paths.d.ts +3 -0
- package/dist/run/paths.d.ts.map +1 -0
- package/dist/run/paths.js +8 -0
- package/dist/run/paths.js.map +1 -0
- package/dist/transformers/port.d.ts +166 -0
- package/dist/transformers/port.d.ts.map +1 -0
- package/dist/transformers/port.js +2 -0
- package/dist/transformers/port.js.map +1 -0
- package/package.json +46 -0
package/README.md
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# Conductor
|
|
2
|
+
|
|
3
|
+
Kernel package, not a Transformer: sequences Transformers for one FeatureStandard. It
|
|
4
|
+
writes the WorkLedger before it acts. Isolated workspaces are a consequence of
|
|
5
|
+
that state.
|
|
6
|
+
|
|
7
|
+
Behavioural contract: [SPECS.md](./SPECS.md).
|
|
8
|
+
|
|
9
|
+
## Stack
|
|
10
|
+
|
|
11
|
+
Repository stack — TypeScript, Node 24.20, Biome, `node:test`. See [docs/DEVELOPMENT.md](../../../docs/DEVELOPMENT.md).
|
|
12
|
+
|
|
13
|
+
## Setup
|
|
14
|
+
|
|
15
|
+
From the repository root:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
nvm use
|
|
19
|
+
npm install
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Commands
|
|
23
|
+
|
|
24
|
+
| Task | Command |
|
|
25
|
+
| --------- | ---------------- |
|
|
26
|
+
| Lint | `npm run lint` |
|
|
27
|
+
| Format | `npm run format` |
|
|
28
|
+
| Typecheck | `npm run tsc` |
|
|
29
|
+
| Test | `npm test` |
|
|
30
|
+
| Build | `npm run build` |
|
|
31
|
+
|
|
32
|
+
There is no CLI in this implementation. The caller injects a WorkLedger and a
|
|
33
|
+
Transformer Port (Isolation, Breakdown, Implementer, Integration).
|
|
34
|
+
|
|
35
|
+
**Optional Authority.** When injected, Conductor offers the assembled feature
|
|
36
|
+
and does not fold it into WorkLineStable itself. See [SPECS.md](./SPECS.md) §8.
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
import { openConductor } from "@bluewombat/conductor";
|
|
40
|
+
|
|
41
|
+
const conductor = openConductor({
|
|
42
|
+
ledger,
|
|
43
|
+
transformers,
|
|
44
|
+
workLineStable: "/absolute/path/to/stable",
|
|
45
|
+
workspaceRoot: "/absolute/path/to/workspaces",
|
|
46
|
+
});
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Layout
|
|
50
|
+
|
|
51
|
+
```
|
|
52
|
+
SPECS.md behavioural contract
|
|
53
|
+
README.md how to build and run
|
|
54
|
+
src/
|
|
55
|
+
index.ts openConductor
|
|
56
|
+
transformers/port.ts Transformer Port
|
|
57
|
+
run/ loop, pause, reconcile (imports Port + WorkLedger)
|
|
58
|
+
```
|
package/SPECS.md
ADDED
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
# Conductor
|
|
2
|
+
|
|
3
|
+
Not a Transformer. It has many operations, not one IN and one OUT.
|
|
4
|
+
It sequences other people's jobs: WorkLedger commands, Isolation, Breakdown,
|
|
5
|
+
Implementer, Integration. It does not store FeatureStandards, does not isolate a
|
|
6
|
+
directory, does not split a Plan, does not run a Builder, does not fold a Child,
|
|
7
|
+
and does not report to a FeatureManager.
|
|
8
|
+
|
|
9
|
+
Conductor writes the WorkLedger **before** it acts. Isolated workspaces are a
|
|
10
|
+
consequence of that state, never the source of it.
|
|
11
|
+
|
|
12
|
+
## 1. Job
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
caller Conductor others
|
|
16
|
+
------ --------- ------
|
|
17
|
+
run a Project check pause, reconcile WorkLedger
|
|
18
|
+
claim or resume isolate / breakDown
|
|
19
|
+
write ledger, then act implement / integrate
|
|
20
|
+
← idle | done | escalated | paused | refused
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
**In:** an opened WorkLedger, injected transformers, a WorkLineStable path, a workspace
|
|
24
|
+
root, and a Project. **Out:** a typed result. No JSON lines, no exit code, no
|
|
25
|
+
Status protocol. Those belong to Transformers. This package is a function set imported
|
|
26
|
+
in process.
|
|
27
|
+
|
|
28
|
+
There is no required CLI. If a CLI appears later, it is a thin adapter over the
|
|
29
|
+
same functions.
|
|
30
|
+
|
|
31
|
+
## 2. Faces and wiring
|
|
32
|
+
|
|
33
|
+
| Face | Entry | Must not |
|
|
34
|
+
| ------ | ------------------------------------------------------------------ | ------------------------------------------------ |
|
|
35
|
+
| Import | `openConductor({ ledger, transformers, workLineStable, workspaceRoot })` | Import a Transformer package from `src/run/` |
|
|
36
|
+
| Transformers | A value that implements the Transformer Port | Live under `src/run/` as a concrete Transformer import |
|
|
37
|
+
|
|
38
|
+
The caller builds the transformers (`runIsolator`, `runBreakdown`, `runImplementer`,
|
|
39
|
+
`runIntegrator`, closed over Planner / Builder / Gate argv) and passes them in.
|
|
40
|
+
Conductor does not import `@bluewombat/isolator` (or the other three) from its
|
|
41
|
+
run loop. The composition root that wires Transformers is the caller — Host
|
|
42
|
+
(`packages/host/runtime`) — or a later thin adapter in this package.
|
|
43
|
+
|
|
44
|
+
Transformers do not import this package. WorkLedger does not import this package.
|
|
45
|
+
|
|
46
|
+
## 3. Two layers
|
|
47
|
+
|
|
48
|
+
```
|
|
49
|
+
run loop src/run/ order, pause, reconcile,
|
|
50
|
+
ledger-before-act, path choice
|
|
51
|
+
│
|
|
52
|
+
│ Transformer Port + WorkLedger
|
|
53
|
+
▼
|
|
54
|
+
transformers / ledger injected Isolation, Breakdown, Attempts,
|
|
55
|
+
Integration, durable state
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
| Layer | Owns | Does not own |
|
|
59
|
+
| --------- | ----------------------------------------------------------- | ------------------------------------------------ |
|
|
60
|
+
| Conductor | Order of steps, pause, orphan destroy, when to call whom | FeatureStandard transitions, git, Builder, Gates |
|
|
61
|
+
| Ledger | Legal transitions, bail, Plan freeze | Directories, Transformer outcomes |
|
|
62
|
+
| Transformers | One Isolation / Breakdown / Attempt loop / Integration each | WorkLedger commands, pause, claim |
|
|
63
|
+
|
|
64
|
+
A caller never talks to Isolator through Conductor internals. It talks to
|
|
65
|
+
`runProject`. The run loop never talks to `node:child_process`. It talks to
|
|
66
|
+
the Transformer Port. Conductor may use `node:fs` only to **delete** directories it
|
|
67
|
+
previously declared (after a successful Integration, on bail expiry, or as
|
|
68
|
+
orphan destroy). It does not copy, merge, or create a workspace.
|
|
69
|
+
|
|
70
|
+
## 4. Isolation
|
|
71
|
+
|
|
72
|
+
Hard rules. Breaking one is a spec bug, not a refactor.
|
|
73
|
+
|
|
74
|
+
1. **`src/run/` does not import a Transformer package** (`@bluewombat/isolator`,
|
|
75
|
+
`@bluewombat/implementer`, `@bluewombat/integrator`,
|
|
76
|
+
`@bluewombat/feature-breakdown`). It imports the Transformer Port type and
|
|
77
|
+
WorkLedger types. A test that fails if `run` grows those imports is part of
|
|
78
|
+
this contract.
|
|
79
|
+
2. **`src/run/` does not import `work-ledger` persistence adapters.** It talks
|
|
80
|
+
to the WorkLedger command surface the caller opened.
|
|
81
|
+
3. **No `if (git)` / `if (copy)` in Conductor.** Isolation strategy is Isolator's.
|
|
82
|
+
4. **Domain tests inject a Transformer Port and a WorkLedger.** They may use a
|
|
83
|
+
test-double ledger (in the test file) and fake transformers that record call order.
|
|
84
|
+
They do not require a Transformer `dist/`.
|
|
85
|
+
5. **The caller, not Conductor, chooses Planner / Builder / Gate argv.** Those
|
|
86
|
+
slots close over the transformer functions.
|
|
87
|
+
|
|
88
|
+
## 5. Transformer Port
|
|
89
|
+
|
|
90
|
+
Four operations. Real Transformers or test doubles; the Port does not care.
|
|
91
|
+
|
|
92
|
+
| Operation | In (minimum) | Out (minimum) |
|
|
93
|
+
| ----------- | ---------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- |
|
|
94
|
+
| `isolate` | `id`, `parent`, `child` (absolute paths), `durationMs` | `isolated` / `failed` / `invalid-invocation` / `interrupted` |
|
|
95
|
+
| `breakDown` | FeatureStandard JSON (key, intention, optional title) | `planned` + subtasks + `planned_at`, or `refused` + code + reason, or `unavailable` / `interrupted` |
|
|
96
|
+
| `implement` | `id`, intention, `workspace`, durations / attempt bound, optional definition of done, `report`, `stage`, `produce` | `validated` / `escalated` / `interrupted` / `invalid-invocation`, plus ordered Traces |
|
|
97
|
+
| `integrate` | `id`, `parent`, `child`, `durationMs` | `integrated` / `conflict` / `failed` / `invalid-invocation` / `interrupted` |
|
|
98
|
+
|
|
99
|
+
Conductor maps ledger fields into `breakDown` JSON. It does not reopen
|
|
100
|
+
FeatureAdapter's closed shape. Recognised keys are `key`, `intention`, `title`.
|
|
101
|
+
|
|
102
|
+
A Subtask carries the `definition_of_done` its Plan gave it. An assembly carries
|
|
103
|
+
none: what it must meet is the Subtasks it is made of, and each was judged on its
|
|
104
|
+
own.
|
|
105
|
+
|
|
106
|
+
`stage` says what is being judged — one unit of work, or the feature they
|
|
107
|
+
assemble into. They are not the same situation: a check that is right for one can
|
|
108
|
+
be wrong for the other. `produce` says whether the Task may make anything: a
|
|
109
|
+
stage built only of already-validated work has nothing to make, and asking a
|
|
110
|
+
producer to run there is how a correct result gets rewritten so a check expecting
|
|
111
|
+
a change is satisfied. `report` is what a pass before this one left unresolved,
|
|
112
|
+
and it reaches the producer as any Gate's report does.
|
|
113
|
+
|
|
114
|
+
A Trace carries `ended` and, when the Attempt did not end well, the `report` of
|
|
115
|
+
whatever refused it — the first Gate that did not pass, or the producer's own
|
|
116
|
+
detail. `ended` says an Attempt failed; `report` says why, and whoever drives the
|
|
117
|
+
next one cannot invent it.
|
|
118
|
+
|
|
119
|
+
`implement` Traces are Implementer's Traces. Conductor records each Subtask
|
|
120
|
+
Trace as a WorkLedger Attempt (`ended`, and `report` / `refusedBy` when the
|
|
121
|
+
Attempt did not end well) before it decides escalate vs integrate.
|
|
122
|
+
|
|
123
|
+
`integrate` may also carry `subject` and `mergeSubject` for the fold's history.
|
|
124
|
+
They are not required by the Port's outcome.
|
|
125
|
+
|
|
126
|
+
An optional **Authority Port** (`submit` / `fold`) is not a transformer. When the
|
|
127
|
+
caller injects one, an assembled feature is offered outside instead of being
|
|
128
|
+
folded into WorkLineStable by Integrator. `workLineTarget` names that work line
|
|
129
|
+
for the Authority. `maxRefusals` (default 3) is how many times the Authority's
|
|
130
|
+
Gates may send the work back before Conductor escalates.
|
|
131
|
+
|
|
132
|
+
## 6. Paths
|
|
133
|
+
|
|
134
|
+
Constructor: `openConductor({ ledger, transformers, workLineStable, workspaceRoot,
|
|
135
|
+
transformerDurationMs?, authority?, workLineTarget?, maxRefusals?, observe? })`.
|
|
136
|
+
`observe` is told, mid-pass, when the plan is recorded, when a Subtask is
|
|
137
|
+
integrated (with the count), and when the work is submitted — awaited, and a
|
|
138
|
+
throw from it changes nothing about the run.
|
|
139
|
+
`workLineStable` and `workspaceRoot` are absolute directories. `workspaceRoot`
|
|
140
|
+
must not be `workLineStable`. Conductor does not create `workLineStable`.
|
|
141
|
+
|
|
142
|
+
Physical layout is this package's private choice:
|
|
143
|
+
|
|
144
|
+
```
|
|
145
|
+
<workspaceRoot>/<encoded-key>/feature
|
|
146
|
+
<workspaceRoot>/<encoded-key>/subtask-<encoded-id>
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
`<encoded-key>` and `<encoded-id>` are `encodeURIComponent` so `fake:42` is one
|
|
150
|
+
path segment. Conductor never uses the raw key as a directory name.
|
|
151
|
+
|
|
152
|
+
It **declares** that path on the WorkLedger, **then** calls `isolate`. After
|
|
153
|
+
`integrate` of a Subtask returns `integrated`, it deletes the Subtask directory
|
|
154
|
+
and `clearWorkspace` for the Subtask. After `markDone`, it deletes the feature
|
|
155
|
+
directory and `clearWorkspace` for the feature.
|
|
156
|
+
|
|
157
|
+
## 7. Commands
|
|
158
|
+
|
|
159
|
+
Each command is a function on the opened conductor. Illegal input returns
|
|
160
|
+
`refused` with a `code`. It does not throw for domain reasons. A refused
|
|
161
|
+
command does not start a Transformer.
|
|
162
|
+
|
|
163
|
+
| Command | Meaning | Refused when |
|
|
164
|
+
| ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------- |
|
|
165
|
+
| `reconcile` | `expireBail` on expired keys. Destroy directories under `workspaceRoot` that are not on `listDeclaredWorkspaces`. Delete declared Subtask dirs after expiry. | Paths not absolute (constructor already threw) |
|
|
166
|
+
| `pause` | In-process flag: no new `claim`, no new `startSubtask`, no `markMerging` / final fold into WorkLineStable. Current `implement` / `isolate` / `integrate` may finish | — |
|
|
167
|
+
| `resume` | Clear the pause flag. Does not itself take work | — |
|
|
168
|
+
| `runProject` | Reconcile, then take or resume the active FeatureStandard on that Project, and drive it until a terminal or wait state | `workLineStable` missing on disk |
|
|
169
|
+
| `cancel` | `ledger.cancel(key)` then destroy declared workspaces for that key | Ledger `point-of-no-return` |
|
|
170
|
+
|
|
171
|
+
`runProject` outcomes:
|
|
172
|
+
|
|
173
|
+
| Outcome | Meaning |
|
|
174
|
+
| ----------- | --------------------------------------------------------------------------------------------- |
|
|
175
|
+
| `idle` | No `received` FeatureStandard and no active one on that Project |
|
|
176
|
+
| `done` | The FeatureStandard is `done` in WorkLineStable |
|
|
177
|
+
| `escalated` | Frozen for a human. Isolated Subtask workspace kept when a Subtask was in play |
|
|
178
|
+
| `paused` | Stopped at a pause boundary. Ledger holds the current state |
|
|
179
|
+
| `refused` | Constructor-level or ledger refusal the loop cannot handle (`persist-failed`, missing stable) |
|
|
180
|
+
|
|
181
|
+
`runProject` does **not** wait for a human `ready`. After `escalated`, the
|
|
182
|
+
caller (FeatureManager path) calls `ledger.resumeReady(key)` then `runProject`
|
|
183
|
+
again.
|
|
184
|
+
|
|
185
|
+
## 8. Happy path (ledger before act)
|
|
186
|
+
|
|
187
|
+
One FeatureStandard, one Project. Pause flag is off.
|
|
188
|
+
|
|
189
|
+
1. `reconcile`.
|
|
190
|
+
2. If the Project already has an active FeatureStandard, resume it. Else `claim`.
|
|
191
|
+
Empty → `idle`.
|
|
192
|
+
3. **Planning.** `breakDown`. On `planned`: `recordPlan` **before** any Isolation.
|
|
193
|
+
On `refused`: `refusePlan`, return `escalated`. On `unavailable`: return
|
|
194
|
+
`refused` with code `unavailable` (ledger stays `planning`; retryable). On
|
|
195
|
+
`interrupted`: return `refused` with code `interrupted`. On
|
|
196
|
+
`invalid-invocation`: return `refused` with code `transformer-invalid`.
|
|
197
|
+
4. **Feature workspace.** `declareWorkspace({ feature })` **then** `isolate`
|
|
198
|
+
(parent = WorkLineStable, child = feature path). Failure: do not start
|
|
199
|
+
Subtasks; `runProject` returns `refused` (`isolate-failed` or `transformer-invalid`)
|
|
200
|
+
and leaves the ledger in `running` so a retry isolates again if the directory
|
|
201
|
+
is missing. If a Submission is already recorded and the feature directory is
|
|
202
|
+
gone, Conductor escalates `kind: "submitted"` rather than isolating again
|
|
203
|
+
over what the Authority was shown.
|
|
204
|
+
5. **Subtask loop** while `nextRunnable` is a Subtask and pause is off:
|
|
205
|
+
1. `startSubtask`.
|
|
206
|
+
2. `declareWorkspace({ subtask })` **then** `isolate` (parent = feature path).
|
|
207
|
+
3. `implement` in that child.
|
|
208
|
+
4. For each Trace, `recordAttempt` with the next number and that Trace's
|
|
209
|
+
`ended`, plus `report` / `refusedBy` when present.
|
|
210
|
+
5. `validated` → `integrate` child into feature. `integrated` →
|
|
211
|
+
`markSubtaskIntegrated`, delete child, `clearWorkspace` subtask.
|
|
212
|
+
`conflict` → `escalate({ kind: "subtask", subtaskId })`, **keep** the child.
|
|
213
|
+
6. `escalated` from Implementer → `escalate({ kind: "subtask", subtaskId })`
|
|
214
|
+
(Trace already recorded). Keep the child. The next `startSubtask` for
|
|
215
|
+
that id **destroys** the kept Child first (resume from zero).
|
|
216
|
+
7. After a Subtask is marked integrated, if `pending_fingerprint` is set,
|
|
217
|
+
`escalate({ kind: "plan" })` with a report that says the intention was
|
|
218
|
+
edited in flight and what resume does — not that a Plan was refused,
|
|
219
|
+
which is what `kind: "plan"` reads as without it. The ledger consumes
|
|
220
|
+
the pending field. Resume of `kind: "plan"` unfreezes the Plan
|
|
221
|
+
(`resumeReady`). A newer FeatureStandard body lands by `admit` of the
|
|
222
|
+
corrected document (from `escalated` → `received`).
|
|
223
|
+
6. When the ledger is `integrating` and pause is off:
|
|
224
|
+
1. If `pending_fingerprint` is set, escalate `kind: "plan"` as in 5.7 and
|
|
225
|
+
stop. Do not align.
|
|
226
|
+
2. If a Submission `last_report` is set (the Authority sent the work back),
|
|
227
|
+
`implement` on the feature workspace with `stage: "assembly"`,
|
|
228
|
+
`produce: true`, and that report **before** align. `escalated` →
|
|
229
|
+
`escalate({ kind: "assembly" })`.
|
|
230
|
+
3. **Align.** `integrate` with WorkLineStable as **child** and the feature
|
|
231
|
+
workspace as **parent**. `conflict` → return `refused` (`align-conflict`);
|
|
232
|
+
state stays `integrating`. Cancel is still allowed. Retry `runProject`
|
|
233
|
+
retries align. Do not `escalate({ kind: "merging" })` here: that would set
|
|
234
|
+
`born_in_merging` and block cancel before WorkLineStable is the fold parent.
|
|
235
|
+
4. **Without an Authority.** `implement` on the feature workspace with
|
|
236
|
+
`stage: "assembly"` and `produce: false` (judgement only). `validated` →
|
|
237
|
+
`markMerging`. `escalated` → `escalate({ kind: "assembly" })`.
|
|
238
|
+
Then **final fold**: `integrate` feature child into WorkLineStable.
|
|
239
|
+
`integrated` → `markDone`, delete feature directory, `clearWorkspace`.
|
|
240
|
+
`conflict` → `escalate({ kind: "merging" })`, keep the feature directory.
|
|
241
|
+
5. **With an Authority.** `submit` the assembled feature. `submitted` →
|
|
242
|
+
`markSubmitted`, return `paused`. `refused` → `escalate({ kind: "submitted" })`.
|
|
243
|
+
`unavailable` → return `paused` (retry later). On a later `runProject` in
|
|
244
|
+
`submitted`: judge with `produce: false`. Blocking or over `maxRefusals` →
|
|
245
|
+
`escalate({ kind: "submitted" })`. Retryable refusal → `recordRefusal`,
|
|
246
|
+
return `paused` (next pass repairs then aligns, then offers again).
|
|
247
|
+
`validated` → Authority `fold`. `folded` → `markMerging` then `markDone` (with the fold's `reference`, when the Authority named one)
|
|
248
|
+
(the Authority moved the work line; Conductor does not `integrate` into
|
|
249
|
+
WorkLineStable). `conflict` → `escalate({ kind: "submitted" })`.
|
|
250
|
+
|
|
251
|
+
## 9. Pause and interrupt
|
|
252
|
+
|
|
253
|
+
Pause is an in-process flag on the opened conductor. It is not stored in the
|
|
254
|
+
WorkLedger (pause is Conductor admission, not a FeatureStandard state).
|
|
255
|
+
|
|
256
|
+
Boundaries where pause is honoured: before `claim`, before `startSubtask`,
|
|
257
|
+
before align, before `markMerging`. A running `implement` finishes.
|
|
258
|
+
|
|
259
|
+
Interrupt is the Transformer Port's problem (the same `interruptFlag` the Transformers
|
|
260
|
+
already take). If a transformer returns `interrupted`, Conductor does not mark the
|
|
261
|
+
Subtask integrated. It leaves the ledger as the last successful command left
|
|
262
|
+
it (typically `running` with that Subtask `running`) and returns `refused`
|
|
263
|
+
with code `interrupted`. Reconcile + `expireBail` after process restart is how
|
|
264
|
+
that Subtask becomes `runnable` again when the bail expires. First
|
|
265
|
+
implementation may also `expireBail` immediately on interrupt if the bail is
|
|
266
|
+
already expired; it does not invent a new ledger command.
|
|
267
|
+
|
|
268
|
+
## 10. Reconcile
|
|
269
|
+
|
|
270
|
+
After process start, and at the beginning of every `runProject`:
|
|
271
|
+
|
|
272
|
+
1. `listDeclaredWorkspaces`.
|
|
273
|
+
2. Delete any directory under `workspaceRoot` whose absolute path is **not**
|
|
274
|
+
on that list (orphans). Never delete `workLineStable`.
|
|
275
|
+
3. `expireBail` for those declared keys, and for `activeOn(project)` when that
|
|
276
|
+
FeatureStandard's bail is past expiry. Then delete a Subtask directory if
|
|
277
|
+
the ledger cleared it.
|
|
278
|
+
|
|
279
|
+
The ledger decides Feature state. Disk never does. Conductor does not scan
|
|
280
|
+
every Project; `runProject` reconciles the workspaces on disk plus the Project
|
|
281
|
+
it was asked to run.
|
|
282
|
+
|
|
283
|
+
## 11. Refusals
|
|
284
|
+
|
|
285
|
+
| Code | When |
|
|
286
|
+
| ---------------- | ---------------------------------------------------------------- |
|
|
287
|
+
| `stable-missing` | `workLineStable` is not a directory |
|
|
288
|
+
| `root-invalid` | `workspaceRoot` is not absolute, or equals `workLineStable` |
|
|
289
|
+
| `persist-failed` | A ledger command returned `persist-failed` |
|
|
290
|
+
| `interrupted` | A transformer returned `interrupted` |
|
|
291
|
+
| `isolate-failed` | Feature Isolation did not return `isolated` |
|
|
292
|
+
| `align-conflict` | Align Integration returned `conflict`; state stays `integrating` |
|
|
293
|
+
| `transformer-invalid` | A transformer returned `invalid-invocation` |
|
|
294
|
+
| `unavailable` | `breakDown` returned `unavailable` (Planner did not answer) |
|
|
295
|
+
|
|
296
|
+
Ledger codes (`project-busy`, `point-of-no-return`, …) pass through on `cancel`
|
|
297
|
+
and when `claim` refuses.
|
|
298
|
+
|
|
299
|
+
## 12. What is implemented
|
|
300
|
+
|
|
301
|
+
Shipped:
|
|
302
|
+
|
|
303
|
+
- Import face, `openConductor({ ledger, transformers, workLineStable, workspaceRoot })`
|
|
304
|
+
- Optional Authority (`submit` / `fold`), `workLineTarget`, `maxRefusals`
|
|
305
|
+
- `reconcile`, `pause`, `resume`, `runProject`, `cancel`
|
|
306
|
+
- Happy path: align, assembly judgement (`produce: false`), `markMerging`, final fold
|
|
307
|
+
- Authority path: offer, judge, `recordRefusal` / fold, no Integrator fold into WorkLineStable
|
|
308
|
+
- `pending_fingerprint` consumed after a Subtask integrates and when entering `integrating`
|
|
309
|
+
- Ledger-before-act, declared workspaces, orphan destroy
|
|
310
|
+
- Isolation tests in §4
|
|
311
|
+
- Pause boundaries
|
|
312
|
+
|
|
313
|
+
Not shipped:
|
|
314
|
+
|
|
315
|
+
- FeatureListener / FeatureAdapter / FeatureEmitter (the caller wires them
|
|
316
|
+
through a manager package; Host's suite is the first such assembly)
|
|
317
|
+
- CLI
|
|
318
|
+
- Persistent pause across processes
|
|
319
|
+
- A human `ready` daemon (caller invokes `resumeReady` then `runProject`)
|
|
320
|
+
- SQLite, a second workspace layout
|
|
321
|
+
- Consuming `pending_fingerprint` while the Feature is `submitted` (admit stores
|
|
322
|
+
it; the next Subtask integrate / integrating entry is what consumes it)
|
|
323
|
+
|
|
324
|
+
## 13. Layout
|
|
325
|
+
|
|
326
|
+
```
|
|
327
|
+
SPECS.md this contract
|
|
328
|
+
README.md how to build and run
|
|
329
|
+
src/
|
|
330
|
+
index.ts openConductor
|
|
331
|
+
transformers/port.ts Transformer Port only
|
|
332
|
+
run/ loop, pause, reconcile, paths; imports Port + WorkLedger
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
## 14. Acceptance
|
|
336
|
+
|
|
337
|
+
1. `runProject` on a Project with one `received` FeatureStandard calls `claim`
|
|
338
|
+
then `breakDown` then `recordPlan` **before** any `isolate`.
|
|
339
|
+
2. `declareWorkspace` for the feature path is recorded **before** `isolate` of
|
|
340
|
+
that path. Same for a Subtask path.
|
|
341
|
+
3. Fake transformers, one Subtask, all `validated` / `integrated`, no Authority:
|
|
342
|
+
Feature ends `done`; `isolate` was called twice (feature, then Subtask);
|
|
343
|
+
`integrate` was called three times (Subtask fold, align, final fold);
|
|
344
|
+
assembly `implement` ran once on the feature workspace with `produce: false`.
|
|
345
|
+
4. `breakDown` `refused` → `refusePlan`; no `isolate`; outcome `escalated`.
|
|
346
|
+
5. `implement` `escalated` after Traces → each Trace is `recordAttempt`d;
|
|
347
|
+
then `escalate`; no Subtask `integrate`; child directory not deleted by
|
|
348
|
+
Conductor.
|
|
349
|
+
6. Subtask `integrate` `conflict` → `escalate`; child kept.
|
|
350
|
+
7. `cancel` of a `running` Feature → ledger `cancelled`; declared directories
|
|
351
|
+
destroyed. `cancel` after `markMerging` → ledger `point-of-no-return`;
|
|
352
|
+
WorkLineStable untouched.
|
|
353
|
+
8. A directory under `workspaceRoot` that is not declared is gone after
|
|
354
|
+
`reconcile`. `workLineStable` is never deleted.
|
|
355
|
+
9. `pause` after `recordPlan` and feature isolate, before the first
|
|
356
|
+
`startSubtask` → outcome `paused`; no `startSubtask`.
|
|
357
|
+
10. `src/run/` has no import of `@bluewombat/isolator`, `@bluewombat/implementer`,
|
|
358
|
+
`@bluewombat/integrator`, or `@bluewombat/feature-breakdown`.
|
|
359
|
+
11. Opening Conductor with a Transformer Port test double and a WorkLedger test
|
|
360
|
+
double still passes 1–9. No Transformer `dist/` and no filesystem persist are
|
|
361
|
+
required for those.
|
|
362
|
+
|
|
363
|
+
Gap, named: align `conflict` leaves `integrating` (`align-conflict`) rather than
|
|
364
|
+
a first-class ledger kind. Do not hide that. A later WorkLedger command can
|
|
365
|
+
close it. Assembly `escalated` uses `escalate({ kind: "assembly" })`;
|
|
366
|
+
`resumeReady` returns that Feature to `integrating`.
|
|
367
|
+
|
|
368
|
+
## 15. Choices
|
|
369
|
+
|
|
370
|
+
1. **Not a Transformer.** Sequencing many verbs is not Implementer-shaped.
|
|
371
|
+
2. **Injected Transformer Port.** Depending on four Transformer packages would make the
|
|
372
|
+
run loop untestable without `dist/` and would freeze Isolation strategy
|
|
373
|
+
inside Conductor. The caller wires Transformers.
|
|
374
|
+
3. **Injected WorkLedger.** Conductor does not open persist. Glue already chose
|
|
375
|
+
the adapter.
|
|
376
|
+
4. **Ledger before act, always.** Directories that exist without a declaration
|
|
377
|
+
are orphans and die.
|
|
378
|
+
5. **No Emitter in this package.** Reporting is FeatureEmitter. Conductor
|
|
379
|
+
returns a result; the caller reports.
|
|
380
|
+
6. **Pause is in-process.** A FeatureStandard state for pause would confuse
|
|
381
|
+
`escalated` and `ready`. Restart clears pause; reconcile + bail remain.
|
|
382
|
+
7. **Stack: TypeScript on Node.js 24.20, Biome, `node:test` run through `tsx`.**
|
|
383
|
+
Same as WorkLedger. Recorded here; commands live in this package's README.
|
|
384
|
+
|
|
385
|
+
How this package is built and run: [README.md](./README.md).
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export type { Conductor, ConductorMoment, ConductorRefusalCode, OpenConductorOptions, ProjectRunResult, } from "./run/open-conductor.js";
|
|
2
|
+
export { foldMessageOf, openConductor, subjectOf } from "./run/open-conductor.js";
|
|
3
|
+
export { featureWorkspacePath, subtaskWorkspacePath } from "./run/paths.js";
|
|
4
|
+
export type { AuthorityFoldResult, AuthorityPort, BreakDownResult, ImplementResult, SubmitInput, SubmitResult, TransformerPort, } from "./transformers/port.js";
|
|
5
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,SAAS,EACT,eAAe,EACf,oBAAoB,EACpB,oBAAoB,EACpB,gBAAgB,GACjB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AAClF,OAAO,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAC5E,YAAY,EACV,mBAAmB,EACnB,aAAa,EACb,eAAe,EACf,eAAe,EACf,WAAW,EACX,YAAY,EACZ,eAAe,GAChB,MAAM,wBAAwB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AAClF,OAAO,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import type { CommandResult, WorkLedger } from "@bluewombat/work-ledger";
|
|
2
|
+
import type { AuthorityPort, TransformerPort } from "../transformers/port.js";
|
|
3
|
+
export type ConductorRefusalCode = "stable-missing" | "root-invalid" | "persist-failed" | "interrupted" | "isolate-failed" | "align-conflict" | "transformer-invalid" | "unavailable" | "not-found" | "point-of-no-return" | "project-busy" | "illegal-transition";
|
|
4
|
+
export type ProjectRunResult = {
|
|
5
|
+
outcome: "idle";
|
|
6
|
+
} | {
|
|
7
|
+
outcome: "done";
|
|
8
|
+
key: string;
|
|
9
|
+
} | {
|
|
10
|
+
outcome: "escalated";
|
|
11
|
+
key: string;
|
|
12
|
+
} | {
|
|
13
|
+
outcome: "paused";
|
|
14
|
+
key?: string;
|
|
15
|
+
} | {
|
|
16
|
+
outcome: "refused";
|
|
17
|
+
code: ConductorRefusalCode;
|
|
18
|
+
};
|
|
19
|
+
export type OpenConductorOptions = {
|
|
20
|
+
ledger: WorkLedger;
|
|
21
|
+
transformers: TransformerPort;
|
|
22
|
+
workLineStable: string;
|
|
23
|
+
workspaceRoot: string;
|
|
24
|
+
transformerDurationMs?: number;
|
|
25
|
+
/**
|
|
26
|
+
* The outside judge of a Submission. Absent: an assembled feature is folded
|
|
27
|
+
* into the work line directly, with nobody else asked.
|
|
28
|
+
*/
|
|
29
|
+
authority?: AuthorityPort;
|
|
30
|
+
/** The work line a Submission is offered to. Required with an Authority. */
|
|
31
|
+
workLineTarget?: string;
|
|
32
|
+
/** How many times the Authority may send the work back before it escalates. */
|
|
33
|
+
maxRefusals?: number;
|
|
34
|
+
/**
|
|
35
|
+
* Told at the moments a pass changes what an outsider would want to know:
|
|
36
|
+
* the plan is in, a Subtask is integrated, the work is submitted. Awaited,
|
|
37
|
+
* so what the caller says lands before the next step; what it throws is
|
|
38
|
+
* its own problem and does not stop the run.
|
|
39
|
+
*/
|
|
40
|
+
observe?: (moment: ConductorMoment) => Promise<void> | void;
|
|
41
|
+
};
|
|
42
|
+
/** One thing that happened inside a pass, for whoever is watching from outside. */
|
|
43
|
+
export type ConductorMoment = {
|
|
44
|
+
kind: "planned";
|
|
45
|
+
key: string;
|
|
46
|
+
} | {
|
|
47
|
+
kind: "subtask-integrated";
|
|
48
|
+
key: string;
|
|
49
|
+
subtaskId: string;
|
|
50
|
+
integrated: number;
|
|
51
|
+
total: number;
|
|
52
|
+
} | {
|
|
53
|
+
kind: "submitted";
|
|
54
|
+
key: string;
|
|
55
|
+
reference: string;
|
|
56
|
+
};
|
|
57
|
+
export type Conductor = {
|
|
58
|
+
reconcile(project: string): Promise<void>;
|
|
59
|
+
pause(): void;
|
|
60
|
+
resume(): void;
|
|
61
|
+
runProject(project: string): Promise<ProjectRunResult>;
|
|
62
|
+
cancel(key: string): Promise<CommandResult>;
|
|
63
|
+
};
|
|
64
|
+
/**
|
|
65
|
+
* A fold's subject out of prose. Text written the way a commit is — a headline,
|
|
66
|
+
* a blank line, the rest — gives its headline. Otherwise the first sentence,
|
|
67
|
+
* cut at a word when even that runs past what one line of history shows. A
|
|
68
|
+
* subject is not markdown, so the backticks prose uses for names come off; and
|
|
69
|
+
* it ends on a word, not on an ellipsis — the whole text follows in the body
|
|
70
|
+
* for whoever wants it.
|
|
71
|
+
*/
|
|
72
|
+
export declare function subjectOf(text: string): string;
|
|
73
|
+
/** Subject line, then the whole text when the subject did not carry it all. */
|
|
74
|
+
export declare function foldMessageOf(text: string): string;
|
|
75
|
+
export declare function openConductor(options: OpenConductorOptions): Conductor;
|
|
76
|
+
//# sourceMappingURL=open-conductor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"open-conductor.d.ts","sourceRoot":"","sources":["../../src/run/open-conductor.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,aAAa,EAIb,UAAU,EACX,MAAM,yBAAyB,CAAC;AACjC,OAAO,KAAK,EACV,aAAa,EAGb,eAAe,EAChB,MAAM,yBAAyB,CAAC;AAMjC,MAAM,MAAM,oBAAoB,GAC5B,gBAAgB,GAChB,cAAc,GACd,gBAAgB,GAChB,aAAa,GACb,gBAAgB,GAChB,gBAAgB,GAChB,qBAAqB,GACrB,aAAa,GACb,WAAW,GACX,oBAAoB,GACpB,cAAc,GACd,oBAAoB,CAAC;AAEzB,MAAM,MAAM,gBAAgB,GACxB;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,GACnB;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GAChC;IAAE,OAAO,EAAE,WAAW,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GACrC;IAAE,OAAO,EAAE,QAAQ,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GACnC;IAAE,OAAO,EAAE,SAAS,CAAC;IAAC,IAAI,EAAE,oBAAoB,CAAA;CAAE,CAAC;AAEvD,MAAM,MAAM,oBAAoB,GAAG;IACjC,MAAM,EAAE,UAAU,CAAC;IACnB,YAAY,EAAE,eAAe,CAAC;IAC9B,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;IACtB,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B;;;OAGG;IACH,SAAS,CAAC,EAAE,aAAa,CAAC;IAC1B,4EAA4E;IAC5E,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,+EAA+E;IAC/E,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;OAKG;IACH,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,eAAe,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CAC7D,CAAC;AAEF,mFAAmF;AACnF,MAAM,MAAM,eAAe,GACvB;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GAChC;IACE,IAAI,EAAE,oBAAoB,CAAC;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;CACf,GACD;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAAC;AAE1D,MAAM,MAAM,SAAS,GAAG;IACtB,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1C,KAAK,IAAI,IAAI,CAAC;IACd,MAAM,IAAI,IAAI,CAAC;IACf,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAAC;IACvD,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC;CAC7C,CAAC;AAyDF;;;;;;;GAOG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAc9C;AAED,+EAA+E;AAC/E,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAMlD;AAED,wBAAgB,aAAa,CAAC,OAAO,EAAE,oBAAoB,GAAG,SAAS,CAqtBtE"}
|