mandrel 1.68.0 → 1.70.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/.agents/README.md +1 -1
- package/.agents/docs/agentrc-reference.json +1 -2
- package/.agents/docs/configuration.md +2 -4
- package/.agents/docs/workflows.md +1 -1
- package/.agents/schemas/agentrc.schema.json +1 -5
- package/.agents/schemas/lifecycle/epic.automerge.end.schema.json +2 -1
- package/.agents/scripts/agents-update-preflight.js +235 -0
- package/.agents/scripts/apply-quality-bootstrap.js +79 -0
- package/.agents/scripts/audit-labels-bootstrap.js +52 -30
- package/.agents/scripts/audit-to-stories.js +54 -0
- package/.agents/scripts/bootstrap.js +13 -3
- package/.agents/scripts/epic-deliver-preflight.js +30 -13
- package/.agents/scripts/epic-deliver-prepare.js +40 -53
- package/.agents/scripts/epic-execute-record-wave.js +119 -133
- package/.agents/scripts/generate-config-docs.js +189 -94
- package/.agents/scripts/lib/audit-suite/findings.js +0 -4
- package/.agents/scripts/lib/audit-to-stories/audit-lenses.js +99 -0
- package/.agents/scripts/lib/audit-to-stories/build-story-body.js +13 -5
- package/.agents/scripts/lib/baseline-snapshot.js +163 -4
- package/.agents/scripts/lib/baselines/refresh-service.js +13 -5
- package/.agents/scripts/lib/config/baselines.js +0 -20
- package/.agents/scripts/lib/config/explain.js +0 -2
- package/.agents/scripts/lib/config/limits.js +19 -8
- package/.agents/scripts/lib/config/temp-paths.js +0 -31
- package/.agents/scripts/lib/config-settings-schema.js +1 -2
- package/.agents/scripts/lib/crap-utils.js +281 -0
- package/.agents/scripts/lib/maintainability-utils.js +32 -9
- package/.agents/scripts/lib/orchestration/dispatch-engine.js +0 -2
- package/.agents/scripts/lib/orchestration/epic-cleanup.js +11 -7
- package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/cli.js +6 -6
- package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/context.js +11 -5
- package/.agents/scripts/lib/orchestration/epic-run-state-store.js +203 -110
- package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter/composition.js +38 -162
- package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter/signals.js +3 -4
- package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter/transport.js +16 -13
- package/.agents/scripts/lib/orchestration/epic-runner/sub-agent-return.js +10 -7
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/automerge-predicate.js +37 -24
- package/.agents/scripts/lib/orchestration/lifecycle/trace-logger.js +0 -4
- package/.agents/scripts/lib/orchestration/manifest-builder.js +6 -0
- package/.agents/scripts/lib/orchestration/retro/phases/compose-body.js +101 -70
- package/.agents/scripts/lib/orchestration/spec-renderer.js +42 -14
- package/.agents/scripts/lib/orchestration/ticket-lease.js +3 -0
- package/.agents/scripts/lib/orchestration/ticket-validator-sizing.js +6 -2
- package/.agents/scripts/lib/orchestration/wave-record-io.js +18 -77
- package/.agents/scripts/lib/orchestration/wave-record-notifications.js +78 -122
- package/.agents/scripts/lib/orchestration/wave-record-projection.js +21 -226
- package/.agents/scripts/lib/presentation/dispatch-manifest-render.js +18 -1
- package/.agents/scripts/lib/presentation/manifest-render-waves.js +77 -4
- package/.agents/scripts/lib/story-adjacency.js +14 -10
- package/.agents/scripts/lib/story-body/story-body.js +142 -65
- package/.agents/scripts/lib/templates/decomposer-prompts.js +23 -3
- package/.agents/scripts/lib/test-tiers.js +13 -7
- package/.agents/scripts/lib/wave-runner/ready-set.js +295 -0
- package/.agents/scripts/lib/wave-runner/tick.js +446 -216
- package/.agents/scripts/lib/wave-runner/wave-runner-error.js +2 -1
- package/.agents/scripts/lib/workers/combined-mi-crap-worker.js +226 -0
- package/.agents/scripts/lint-label-vocabulary.js +1 -1
- package/.agents/scripts/providers/github/issues.js +48 -0
- package/.agents/scripts/providers/github.js +1 -0
- package/.agents/scripts/stories-wave-tick.js +262 -161
- package/.agents/skills/core/epic-plan-consolidate/SKILL.md +6 -0
- package/.agents/skills/core/epic-plan-decompose-author/SKILL.md +108 -101
- package/.agents/skills/skills.index.json +2 -2
- package/.agents/workflows/agents-update.md +205 -28
- package/.agents/workflows/deliver.md +12 -9
- package/.agents/workflows/helpers/deliver-epic.md +126 -90
- package/.agents/workflows/helpers/deliver-stories.md +131 -85
- package/.agents/workflows/helpers/plan-epic.md +13 -10
- package/.agents/workflows/plan.md +1 -1
- package/README.md +20 -0
- package/docs/CHANGELOG.md +46 -0
- package/lib/cli/registry.js +49 -6
- package/lib/cli/update.js +335 -332
- package/package.json +16 -11
- package/.agents/scripts/lib/wave-runner/wave-checkpoint.js +0 -91
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
---
|
|
2
2
|
description: >-
|
|
3
3
|
Deliver one or more standalone Stories end-to-end. Accepts 1+ Story IDs,
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
— each
|
|
7
|
-
|
|
4
|
+
builds a dependency DAG, asks the operator to confirm it, then drives a
|
|
5
|
+
continuous ready-set loop via `stories-wave-tick.js` (the shared
|
|
6
|
+
`selectReadySet` core) — dispatching each Story the instant its own deps
|
|
7
|
+
are done, under one global concurrency cap and one file-overlap guard,
|
|
8
|
+
each dispatch delegating to `helpers/single-story-deliver`. Stories
|
|
9
|
+
without an `Epic: #N` reference only; Epic-attached Stories use `/deliver`.
|
|
8
10
|
---
|
|
9
11
|
|
|
10
12
|
# helpers/deliver-stories — multi-Story delivery path (invoked by /deliver)
|
|
@@ -16,17 +18,24 @@ This helper is the **standalone multi-Story delivery path** behind
|
|
|
16
18
|
standalone Stories — either as the sole route (Story-only input) or as the
|
|
17
19
|
**standalone segment** of a mixed segment plan (run first, before any Epic
|
|
18
20
|
segments; see [`deliver.md`](../deliver.md)). It takes one or more Story
|
|
19
|
-
IDs, builds a dependency
|
|
20
|
-
|
|
21
|
-
|
|
21
|
+
IDs, builds a dependency DAG, optionally confirms it with the operator, and
|
|
22
|
+
then drives a **continuous ready-set loop**: it dispatches each Story the
|
|
23
|
+
instant its **own** dependencies are done — there is no wave barrier where a
|
|
24
|
+
whole group must drain before the next opens — capped by one global
|
|
25
|
+
concurrency limit and guarded by one file-overlap rule, the same scheduling
|
|
26
|
+
core (`lib/wave-runner/ready-set.js#selectReadySet`) the Epic path uses.
|
|
22
27
|
|
|
23
28
|
```text
|
|
24
29
|
/deliver 101 102 103
|
|
25
30
|
→ Phase 0 — Validate input & build DAG
|
|
26
|
-
→ Phase 1 — stories-wave-tick.js →
|
|
27
|
-
→ Phase 2 —
|
|
28
|
-
|
|
29
|
-
|
|
31
|
+
→ Phase 1 — stories-wave-tick.js → DAG sanity + operator confirmation
|
|
32
|
+
→ Phase 2 — continuous ready-set loop:
|
|
33
|
+
repeat until every Story is done:
|
|
34
|
+
stories-wave-tick.js --dag … --done <doneIds> --in-flight <n>
|
|
35
|
+
→ ready[] = Stories dispatchable on this beat
|
|
36
|
+
Agent tool × ready.length parallel calls (≤ concurrencyCap − inFlight)
|
|
37
|
+
helpers/single-story-deliver <storyId>
|
|
38
|
+
as each child returns done, add its id to <doneIds> and re-tick
|
|
30
39
|
→ Phase 3 — Summary
|
|
31
40
|
```
|
|
32
41
|
|
|
@@ -42,17 +51,19 @@ each wave, serialised across waves.
|
|
|
42
51
|
This helper **refuses** Stories that carry an `Epic: #N` reference in
|
|
43
52
|
their body. Those Stories belong to an Epic's dispatch manifest and must flow
|
|
44
53
|
through `/deliver <epicId>`. Use `/single-story-deliver` for a single
|
|
45
|
-
Epic-free Story when you want the leaner one-story path without
|
|
46
|
-
|
|
54
|
+
Epic-free Story when you want the leaner one-story path without the
|
|
55
|
+
ready-set scheduling loop.
|
|
47
56
|
|
|
48
57
|
> **Concurrency cap.** The cap is resolved **deterministically in code** by
|
|
49
|
-
> `stories-wave-tick.js`
|
|
50
|
-
>
|
|
51
|
-
> `stories-
|
|
52
|
-
> `delivery.deliverRunner.concurrencyCap` in `.agentrc.json`
|
|
53
|
-
> `.agentrc.local.json` override is honored) or per-run via the
|
|
54
|
-
> flag below.
|
|
55
|
-
>
|
|
58
|
+
> `stories-wave-tick.js` — the same `resolveConfig` + `getRunners` seam
|
|
59
|
+
> `/deliver` uses — and emitted as the `concurrencyCap` field on the
|
|
60
|
+
> `stories-ready-set` envelope on **every** beat. The default is 3; override
|
|
61
|
+
> persistently via `delivery.deliverRunner.concurrencyCap` in `.agentrc.json`
|
|
62
|
+
> (a `.agentrc.local.json` override is honored) or per-run via the
|
|
63
|
+
> `--concurrency` flag below. The cap is global, not per-wave: the core caps
|
|
64
|
+
> each beat's `ready[]` at `concurrencyCap − inFlight`, so the workflow simply
|
|
65
|
+
> dispatches every id in `ready[]` — do **not** re-read or merge config
|
|
66
|
+
> yourself, and do **not** re-throttle the returned set.
|
|
56
67
|
|
|
57
68
|
---
|
|
58
69
|
|
|
@@ -66,13 +77,13 @@ machinery.
|
|
|
66
77
|
**no** `Epic: #N` reference. At least one is required.
|
|
67
78
|
- `--dep <fromId>:<toId>` — Declare an explicit dependency edge: `<fromId>`
|
|
68
79
|
must complete before `<toId>` runs. Repeat for each edge. When omitted,
|
|
69
|
-
all Stories are treated as independent (
|
|
70
|
-
`blocked by #N` references between the supplied IDs are
|
|
71
|
-
automatically.
|
|
80
|
+
all Stories are treated as independent (every Story is dispatchable on the
|
|
81
|
+
first beat) unless `blocked by #N` references between the supplied IDs are
|
|
82
|
+
detected automatically.
|
|
72
83
|
- `--yes` — Skip the operator confirmation in Phase 1 and proceed
|
|
73
84
|
immediately. Safe for scripted / sub-agent invocations.
|
|
74
|
-
- `--concurrency <n>` — Override the
|
|
75
|
-
only. Passed through to `stories-wave-tick.js`
|
|
85
|
+
- `--concurrency <n>` — Override the global concurrency cap for this run
|
|
86
|
+
only. Passed through to `stories-wave-tick.js` on every beat, which validates
|
|
76
87
|
it (must be a positive integer) and reflects it in the emitted
|
|
77
88
|
`concurrencyCap` field. When omitted, the cap is resolved from
|
|
78
89
|
`delivery.deliverRunner.concurrencyCap` (default 3).
|
|
@@ -108,9 +119,9 @@ Construct the DAG input array:
|
|
|
108
119
|
|
|
109
120
|
---
|
|
110
121
|
|
|
111
|
-
## Phase 1 —
|
|
122
|
+
## Phase 1 — DAG sanity check and operator confirmation
|
|
112
123
|
|
|
113
|
-
### 1a.
|
|
124
|
+
### 1a. Sanity-check the DAG (first beat, nothing done yet)
|
|
114
125
|
|
|
115
126
|
```bash
|
|
116
127
|
node .agents/scripts/stories-wave-tick.js --dag '<dag-json>'
|
|
@@ -123,17 +134,16 @@ verbatim to `stories-wave-tick.js`. The script resolves the cap from config
|
|
|
123
134
|
(`delivery.deliverRunner.concurrencyCap`, default 3) and the override wins for
|
|
124
135
|
that run.
|
|
125
136
|
|
|
126
|
-
|
|
137
|
+
With no `--done` / `--in-flight` (the first beat), stdout is one JSON
|
|
138
|
+
envelope describing the Stories dispatchable **right now** — the roots:
|
|
127
139
|
|
|
128
140
|
```json
|
|
129
141
|
{
|
|
130
|
-
"kind": "stories-
|
|
131
|
-
"
|
|
132
|
-
{ "waveIndex": 0, "stories": [101, 103] },
|
|
133
|
-
{ "waveIndex": 1, "stories": [102] }
|
|
134
|
-
],
|
|
142
|
+
"kind": "stories-ready-set",
|
|
143
|
+
"ready": [101, 103],
|
|
135
144
|
"totalStories": 3,
|
|
136
145
|
"concurrencyCap": 3,
|
|
146
|
+
"inFlight": 0,
|
|
137
147
|
"cycleError": null
|
|
138
148
|
}
|
|
139
149
|
```
|
|
@@ -141,27 +151,35 @@ Stdout is one JSON envelope:
|
|
|
141
151
|
- **`cycleError` non-null** → STOP. Report the cycle to the operator and
|
|
142
152
|
exit. The Story set cannot be delivered until the circular dependency is
|
|
143
153
|
resolved.
|
|
144
|
-
- **`
|
|
145
|
-
- **`
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
-
|
|
154
|
+
- **`totalStories === 0`** → STOP. Zero Stories resolved — report and exit.
|
|
155
|
+
- **`ready` empty with `totalStories > 0` on the first beat** → STOP. Every
|
|
156
|
+
Story is gated behind a dependency outside the supplied set; report and
|
|
157
|
+
exit (the operator must include the missing prerequisites).
|
|
158
|
+
- **`concurrencyCap`** is the resolved global cap. Phase 2 re-reads it from
|
|
159
|
+
each beat's envelope and never re-derives it from config.
|
|
160
|
+
- An invalid `--concurrency` / `--in-flight` / `--done` value makes the
|
|
149
161
|
script exit non-zero with an `inputError` → STOP and surface the message.
|
|
150
162
|
|
|
151
|
-
|
|
163
|
+
This first call is a **sanity gate**, not the schedule: Phase 2 re-ticks
|
|
164
|
+
continuously as Stories complete. The envelope no longer enumerates future
|
|
165
|
+
waves — ordering emerges beat by beat from the live `--done` set.
|
|
152
166
|
|
|
153
|
-
|
|
154
|
-
`waves[0].stories.length === 1`, skip the confirmation prompt
|
|
155
|
-
automatically and proceed. A single-Story plan has no ordering ambiguity
|
|
156
|
-
and no meaningful operator decision to make — the plan *is* "deliver this
|
|
157
|
-
one Story". Prompting for confirmation would just be friction.
|
|
167
|
+
### 1b. Operator confirmation (skipped with `--yes` or for single-story runs)
|
|
158
168
|
|
|
159
|
-
|
|
169
|
+
**Auto-skip rule (Story #3302):** When `totalStories === 1`, skip the
|
|
170
|
+
confirmation prompt automatically and proceed. A single-Story run has no
|
|
171
|
+
ordering ambiguity and no meaningful operator decision to make — the run
|
|
172
|
+
*is* "deliver this one Story". Prompting for confirmation would just be
|
|
173
|
+
friction.
|
|
174
|
+
|
|
175
|
+
Otherwise, present the dependency DAG to the operator in a readable table —
|
|
176
|
+
the roots dispatch immediately, dependents unlock as their prerequisites
|
|
177
|
+
complete:
|
|
160
178
|
|
|
161
179
|
```text
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
180
|
+
Delivery plan — 3 Stories (continuous, dependency-driven)
|
|
181
|
+
Ready now: #101 "<title>", #103 "<title>"
|
|
182
|
+
Unlocks after its deps complete: #102 "<title>" (needs #101)
|
|
165
183
|
|
|
166
184
|
Proceed? [Y/n]
|
|
167
185
|
```
|
|
@@ -169,23 +187,44 @@ Proceed? [Y/n]
|
|
|
169
187
|
Wait for the operator to confirm before dispatching. When the operator
|
|
170
188
|
types `n` or `N`, abort cleanly with a summary of the plan that was
|
|
171
189
|
declined. When `--yes` was passed, skip this step and proceed regardless
|
|
172
|
-
of
|
|
190
|
+
of Story count.
|
|
173
191
|
|
|
174
192
|
---
|
|
175
193
|
|
|
176
|
-
## Phase 2 —
|
|
194
|
+
## Phase 2 — Continuous ready-set loop
|
|
195
|
+
|
|
196
|
+
Drive a single loop, re-ticking after every child returns. Maintain two
|
|
197
|
+
pieces of state across the loop:
|
|
198
|
+
|
|
199
|
+
- **`doneIds`** — the set of Story IDs that have returned `status: 'done'`
|
|
200
|
+
(start empty).
|
|
201
|
+
- **`inFlight`** — the count of Stories dispatched but not yet returned
|
|
202
|
+
(start at 0).
|
|
203
|
+
|
|
204
|
+
Each iteration (one **beat**):
|
|
205
|
+
|
|
206
|
+
### 2a. Tick the ready set
|
|
207
|
+
|
|
208
|
+
```bash
|
|
209
|
+
node .agents/scripts/stories-wave-tick.js --dag '<dag-json>' \
|
|
210
|
+
--done '<comma-separated doneIds>' --in-flight <inFlight> [--concurrency <n>]
|
|
211
|
+
```
|
|
177
212
|
|
|
178
|
-
|
|
213
|
+
The envelope's `ready[]` is the set of Stories whose **own** dependencies
|
|
214
|
+
are all in `doneIds` and that fit the remaining capacity
|
|
215
|
+
(`concurrencyCap − inFlight`) without a file-overlap collision — the core
|
|
216
|
+
has already applied the global cap and the overlap guard, so the list is
|
|
217
|
+
safe to dispatch verbatim. **Termination:** when `ready` is empty **and**
|
|
218
|
+
`inFlight === 0` **and** `doneIds` covers every Story, the run is complete →
|
|
219
|
+
go to Phase 3. When `ready` is empty but `inFlight > 0`, dispatch nothing
|
|
220
|
+
and wait for an in-flight child to return (a dependent is waiting on it).
|
|
179
221
|
|
|
180
|
-
###
|
|
222
|
+
### 2b. Fan out per-Story Agent calls
|
|
181
223
|
|
|
182
|
-
Emit **one `Agent` tool call per Story
|
|
183
|
-
`
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
as each child returns — never exceed the cap, never wait for the whole
|
|
187
|
-
batch before refilling. Do **not** re-derive the cap from config here; the
|
|
188
|
-
envelope field is the single deterministic source.
|
|
224
|
+
Emit **one `Agent` tool call per Story ID in `ready[]`**, all with
|
|
225
|
+
`run_in_background: true`. The list is already capped, so dispatch the whole
|
|
226
|
+
of `ready[]` and increment `inFlight` by `ready.length`. Do **not** re-derive
|
|
227
|
+
or re-throttle the cap here; the core sized `ready[]` for you.
|
|
189
228
|
|
|
190
229
|
Each Agent call:
|
|
191
230
|
|
|
@@ -199,7 +238,7 @@ Each Agent call:
|
|
|
199
238
|
internally-blocking step (`gh pr checks --watch` blocks the turn), **not**
|
|
200
239
|
a reason to suspend and hand back. A child that ends its turn with
|
|
201
240
|
free-form prose and an unconfirmed merge (e.g. "I'll wait for the
|
|
202
|
-
background watch task…") has violated the contract — the
|
|
241
|
+
background watch task…") has violated the contract — the loop cannot
|
|
203
242
|
advance, and the Story strands at `agent::closing` (the Story #1553 /
|
|
204
243
|
PR #1554 failure mode). There is no "pending" return status: the child
|
|
205
244
|
returns `done` (merge confirmed), `blocked` (transitioned + friction
|
|
@@ -212,11 +251,14 @@ Each Agent call:
|
|
|
212
251
|
|
|
213
252
|
Use `subagent_type: general-purpose`.
|
|
214
253
|
|
|
215
|
-
### 2b
|
|
254
|
+
### 2b′. Collect each return and re-tick
|
|
216
255
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
256
|
+
As each child returns, decrement `inFlight` by 1 and apply its outcome
|
|
257
|
+
(§ 2d). When a child returns `done`, add its id to `doneIds`. Then **re-tick
|
|
258
|
+
immediately** (§ 2a) so a newly-unblocked Story dispatches the instant its
|
|
259
|
+
last dependency clears — do not wait for the whole batch to drain. This is
|
|
260
|
+
the no-false-barrier behavior: an unrelated still-running Story never holds
|
|
261
|
+
back a Story whose own deps are already done.
|
|
220
262
|
|
|
221
263
|
### 2c. Per-Story return contract
|
|
222
264
|
|
|
@@ -240,36 +282,38 @@ The status enum is **closed** — `done`, `blocked`, or `failed`. There is no
|
|
|
240
282
|
**not** a returnable suspension: the child blocks on `gh pr checks --watch`
|
|
241
283
|
*inside its own turn*, confirms the merge, flips `agent::done`, and only then
|
|
242
284
|
returns `status: "done"`. A child that returns prose instead — parking on the
|
|
243
|
-
CI wait with an unconfirmed merge — breaks the
|
|
285
|
+
CI wait with an unconfirmed merge — breaks the loop's ability to advance
|
|
244
286
|
and leaves the Story at `agent::closing` (Story #1553 / PR #1554). The
|
|
245
287
|
single-homed restatement of this no-park rule for the child's own perspective
|
|
246
288
|
is [`single-story-deliver.md` § Step 7](single-story-deliver.md#return-contract).
|
|
247
289
|
|
|
248
|
-
### 2d.
|
|
290
|
+
### 2d. Per-return outcome handling
|
|
249
291
|
|
|
250
|
-
|
|
292
|
+
As each child returns (before the next re-tick):
|
|
251
293
|
|
|
252
|
-
-
|
|
253
|
-
|
|
254
|
-
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
294
|
+
- **`status === 'done'`** → Add its id to `doneIds` and re-tick. Print a
|
|
295
|
+
one-line per-Story complete summary.
|
|
296
|
+
- **`status === 'blocked'`** → STOP dispatching new Stories. Drain the
|
|
297
|
+
Stories still in flight (let them return), then post a summary of blocked
|
|
298
|
+
Stories and their `blockerCommentId` references. Do not dispatch any
|
|
299
|
+
further beat. Wait for the operator to resolve each blocker and re-run
|
|
300
|
+
`/deliver` with the same set (already-done Stories will short-circuit
|
|
301
|
+
because `single-story-close.js` is idempotent).
|
|
302
|
+
- **`status === 'failed'`** → STOP dispatching new Stories, drain the
|
|
303
|
+
in-flight set, and report the failures. The operator must fix the failing
|
|
304
|
+
Stories before re-running.
|
|
261
305
|
|
|
262
306
|
---
|
|
263
307
|
|
|
264
308
|
## Phase 3 — Summary
|
|
265
309
|
|
|
266
|
-
Print a final run summary:
|
|
310
|
+
Print a final run summary listing every delivered Story in completion order:
|
|
267
311
|
|
|
268
312
|
```text
|
|
269
|
-
/deliver — 3 Stories delivered
|
|
313
|
+
/deliver — 3 Stories delivered (continuous, dependency-driven)
|
|
270
314
|
|
|
271
|
-
|
|
272
|
-
|
|
315
|
+
#101 ✅ done, #103 ✅ done (dispatched first — roots)
|
|
316
|
+
#102 ✅ done (after #101 cleared)
|
|
273
317
|
|
|
274
318
|
All Stories delivered. PRs opened, auto-merge armed. CI will merge each
|
|
275
319
|
PR when checks pass; each child then confirms the merge and flips its
|
|
@@ -331,11 +375,13 @@ blocked or unstarted Stories execute.
|
|
|
331
375
|
|
|
332
376
|
- **Never** pass Epic-attached Stories to this command. Detect `Epic: #N`
|
|
333
377
|
in Phase 0 and STOP.
|
|
334
|
-
- **Never**
|
|
335
|
-
|
|
336
|
-
- **Never** exceed `
|
|
337
|
-
`stories-wave-tick.js`) parallel Agent calls at any moment.
|
|
338
|
-
|
|
378
|
+
- **Never** dispatch a new beat once any Story has returned `blocked` or
|
|
379
|
+
`failed` — drain the in-flight set and stop.
|
|
380
|
+
- **Never** exceed the resolved `concurrencyCap` (emitted by
|
|
381
|
+
`stories-wave-tick.js` on every beat) parallel Agent calls at any moment.
|
|
382
|
+
The core caps each beat's `ready[]` at `concurrencyCap − inFlight`;
|
|
383
|
+
dispatch the whole list and never add to it.
|
|
384
|
+
- **Always** confirm the dependency DAG with the operator before dispatching,
|
|
339
385
|
unless `--yes` was passed.
|
|
340
386
|
- **Label transitions**: drive every `agent::*` state change through
|
|
341
387
|
`node .agents/scripts/update-ticket-state.js --ticket <id> --state <state>`.
|
|
@@ -713,16 +713,19 @@ node .agents/scripts/epic-plan-spec-validate.js \
|
|
|
713
713
|
ticket array to `temp/epic-[Epic_ID]/tickets.json`. Do **not** inline
|
|
714
714
|
the JSON authoring in the workflow body.
|
|
715
715
|
|
|
716
|
-
The `maxTickets` cap
|
|
717
|
-
|
|
718
|
-
`.agents/scripts/lib/config/limits.js
|
|
719
|
-
not a hard authoring ceiling: a draft over
|
|
720
|
-
time and is rejected at persist unless rerun
|
|
721
|
-
(after confirming the over-budget rationale
|
|
722
|
-
`epic-plan-decompose.js` script also logs the
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
716
|
+
The `maxTickets` cap is a **framework constant**
|
|
717
|
+
(`LIMITS_DEFAULTS.maxTickets` in
|
|
718
|
+
`.agents/scripts/lib/config/limits.js`; not operator-configurable) and
|
|
719
|
+
a **reviewability budget**, not a hard authoring ceiling: a draft over
|
|
720
|
+
budget warns at authoring time and is rejected at persist unless rerun
|
|
721
|
+
with `--allow-over-budget` (after confirming the over-budget rationale
|
|
722
|
+
on the Epic). The `epic-plan-decompose.js` script also logs the
|
|
723
|
+
resolved budget to stderr. The decomposer system prompt is
|
|
724
|
+
single-sourced in
|
|
725
|
+
`.agents/scripts/lib/templates/decomposer-prompts.js`
|
|
726
|
+
(`renderDecomposerSystemPrompt`); the `epic-plan-decompose-author`
|
|
727
|
+
skill references that rendered `systemPrompt` rather than carrying
|
|
728
|
+
its own copy of the prompt body.
|
|
726
729
|
|
|
727
730
|
When the Tech Spec carries a `## Delivery Slicing` section (authored by
|
|
728
731
|
`epic-plan-spec-author` in Phase 7), the decompose-author skill authors
|
|
@@ -43,7 +43,7 @@ forwards them to the active path helper:
|
|
|
43
43
|
| `--from-notes <path>` | both | Pre-triaged handoff notes; skips triage. |
|
|
44
44
|
| `--force` | Epic | Close + recreate an existing ticket tree on re-plan. |
|
|
45
45
|
| `--force-review` | Epic | Force the operator review gate even when risk routing would skip it. |
|
|
46
|
-
| `--allow-over-budget` | Epic | Permit a decomposition that exceeds `
|
|
46
|
+
| `--allow-over-budget` | Epic | Permit a decomposition that exceeds the framework `maxTickets` reviewability budget. |
|
|
47
47
|
| `--steal` | Epic | Forcibly transfer a foreign Epic-lease. |
|
|
48
48
|
| `--dry-run` | both | Author + validate without GitHub writes. |
|
|
49
49
|
| `--body <path>` | Story | Pre-authored Story body file; validate (and create, unless `--dry-run`) without re-authoring. |
|
package/README.md
CHANGED
|
@@ -69,6 +69,26 @@ explicit `npx mandrel sync` above is the belt-and-suspenders step for
|
|
|
69
69
|
`--ignore-scripts` or sandboxed-CI installs. Run `npx mandrel doctor` any
|
|
70
70
|
time to confirm the install is healthy.
|
|
71
71
|
|
|
72
|
+
> **pnpm users — hoist mandrel's runtime deps.** The materialized
|
|
73
|
+
> `./.agents/scripts/*.js` run from your project root and resolve their
|
|
74
|
+
> third-party deps (ajv, js-yaml, …) from your top-level `node_modules`.
|
|
75
|
+
> npm and yarn hoist transitive deps there automatically; pnpm's default
|
|
76
|
+
> isolated layout does **not** — it keeps them in the `.pnpm` virtual store,
|
|
77
|
+
> so the framework scripts (and `mandrel doctor`'s `runtime-deps` check)
|
|
78
|
+
> cannot see them. Add the following to your `.npmrc` before installing:
|
|
79
|
+
>
|
|
80
|
+
> ```ini
|
|
81
|
+
> # Lift mandrel's runtime deps to the top-level node_modules so the
|
|
82
|
+
> # materialized .agents/scripts can resolve them.
|
|
83
|
+
> shamefully-hoist=true
|
|
84
|
+
> ```
|
|
85
|
+
>
|
|
86
|
+
> Prefer a surgical alternative? Replace `shamefully-hoist` with a scoped
|
|
87
|
+
> `public-hoist-pattern[]=` line per package listed in
|
|
88
|
+
> `.agents/runtime-deps.json` (`ajv`, `ajv-formats`, `js-yaml`, `minimatch`,
|
|
89
|
+
> `picomatch`, `string-argv`, `typhonjs-escomplex`). If `mandrel doctor`
|
|
90
|
+
> reports `runtime-deps missing: …`, this is the fix.
|
|
91
|
+
|
|
72
92
|
`bootstrap.js` is interactive on a TTY and auto-accepts the
|
|
73
93
|
owner/repo/base branch/operator handle it can infer from your local
|
|
74
94
|
`git remote` and `git config user.name` — you only get prompted for
|
package/docs/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,52 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [1.70.0](https://github.com/dsj1984/mandrel/compare/mandrel-v1.69.0...mandrel-v1.70.0) (2026-06-16)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Added
|
|
9
|
+
|
|
10
|
+
* **agents-update:** add a first-run preflight before the updater (refs [#4170](https://github.com/dsj1984/mandrel/issues/4170)) ([#4177](https://github.com/dsj1984/mandrel/issues/4177)) ([82d30c6](https://github.com/dsj1984/mandrel/commit/82d30c6877d567d8272e1f433da00797a71bdeb9))
|
|
11
|
+
* **agents-update:** make partial-upgrade recovery a first-class workflow step (refs [#4172](https://github.com/dsj1984/mandrel/issues/4172)) ([#4179](https://github.com/dsj1984/mandrel/issues/4179)) ([9959fd8](https://github.com/dsj1984/mandrel/commit/9959fd8e480067b1c35d25c9e37f8be0908f9a9e))
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
|
|
16
|
+
* /audit-to-stories: fix silent-no-op dedup (GitHubProvider.searchIssues missing) + junk audit:: label derivation ([#4195](https://github.com/dsj1984/mandrel/issues/4195)) ([#4207](https://github.com/dsj1984/mandrel/issues/4207)) ([d4fed9b](https://github.com/dsj1984/mandrel/commit/d4fed9b6a3077c6c9371ec00debae5c422ac89c6))
|
|
17
|
+
* **agents-update:** invoke mandrel via npx and detect install state (refs [#4169](https://github.com/dsj1984/mandrel/issues/4169)) ([#4174](https://github.com/dsj1984/mandrel/issues/4174)) ([9aab7ba](https://github.com/dsj1984/mandrel/commit/9aab7ba3f2c425a5ecaf16e1008078538093a985))
|
|
18
|
+
* **bootstrap:** relax owner/repo under --skip-github so non-interactive init works in a fresh dir ([#4181](https://github.com/dsj1984/mandrel/issues/4181)) ([e560116](https://github.com/dsj1984/mandrel/commit/e560116aa33178f182752558e7d646c2cb7cdeb4))
|
|
19
|
+
* **deps:** bump markdownlint-cli2 0.18.1 -> 0.22.1, force patched js-yaml/markdown-it (refs [#4187](https://github.com/dsj1984/mandrel/issues/4187)) ([#4200](https://github.com/dsj1984/mandrel/issues/4200)) ([9fe1e09](https://github.com/dsj1984/mandrel/commit/9fe1e09daf91fab0ca5d44d5a701cd2576406a5b))
|
|
20
|
+
* **install-matrix:** hoist mandrel runtime deps in pnpm legs so doctor is honest ([#4180](https://github.com/dsj1984/mandrel/issues/4180)) ([5e7aca7](https://github.com/dsj1984/mandrel/commit/5e7aca7d48b9ab9d0ddd5c157a28bea8978fb90a))
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
### Performance
|
|
24
|
+
|
|
25
|
+
* **baselines:** collapse the two escomplex passes in full-tree regen into one (refs [#4192](https://github.com/dsj1984/mandrel/issues/4192)) ([#4205](https://github.com/dsj1984/mandrel/issues/4205)) ([4f971c3](https://github.com/dsj1984/mandrel/commit/4f971c36723521058322b346980d6e1822c9d35d))
|
|
26
|
+
* **doctor:** short-circuit payload-drift on statSync size mismatch (refs [#4193](https://github.com/dsj1984/mandrel/issues/4193)) ([#4204](https://github.com/dsj1984/mandrel/issues/4204)) ([a85c050](https://github.com/dsj1984/mandrel/commit/a85c0501a02ff346024fa9d8c9aa48046999f08c))
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
### Changed
|
|
30
|
+
|
|
31
|
+
* **agents-update:** extract quality-bootstrap heredoc into a tested script (refs [#4171](https://github.com/dsj1984/mandrel/issues/4171)) ([#4178](https://github.com/dsj1984/mandrel/issues/4178)) ([032ecf7](https://github.com/dsj1984/mandrel/commit/032ecf70f5840ad5291c0bcdb6b11a9d23bec1be))
|
|
32
|
+
* **baselines:** triage dead-export allowlist 198 -> 185 (refs [#4184](https://github.com/dsj1984/mandrel/issues/4184)) ([#4197](https://github.com/dsj1984/mandrel/issues/4197)) ([2a39e61](https://github.com/dsj1984/mandrel/commit/2a39e616bb9a843325bf151d8f057f85a43fcd95))
|
|
33
|
+
* **renderers:** table-drive flat section ladders to lower CC ceilings (refs [#4186](https://github.com/dsj1984/mandrel/issues/4186)) ([#4201](https://github.com/dsj1984/mandrel/issues/4201)) ([36df937](https://github.com/dsj1984/mandrel/commit/36df9370d8e9cdef8f6bc47d0341ef8425314222))
|
|
34
|
+
* **update:** retire No-Shim in-process path and extract pure planUpdate (refs [#4182](https://github.com/dsj1984/mandrel/issues/4182)) ([#4198](https://github.com/dsj1984/mandrel/issues/4198)) ([d5ed325](https://github.com/dsj1984/mandrel/commit/d5ed32560d98fa43d6103789efac530f16244a91))
|
|
35
|
+
* **wave-runner:** decompose tick() into coordinator + phases (refs [#4183](https://github.com/dsj1984/mandrel/issues/4183)) ([#4196](https://github.com/dsj1984/mandrel/issues/4196)) ([0d7a6b4](https://github.com/dsj1984/mandrel/commit/0d7a6b4326b60ec3f2dd58a0798c8a37b6117d9c))
|
|
36
|
+
|
|
37
|
+
## [1.69.0](https://github.com/dsj1984/mandrel/compare/mandrel-v1.68.0...mandrel-v1.69.0) (2026-06-16)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
### Added
|
|
41
|
+
|
|
42
|
+
* Epic [#4151](https://github.com/dsj1984/mandrel/issues/4151) ([#4159](https://github.com/dsj1984/mandrel/issues/4159)) ([f4ad69b](https://github.com/dsj1984/mandrel/commit/f4ad69b1663fca95c8e121f837a2e511ab306706))
|
|
43
|
+
* Epic [#4158](https://github.com/dsj1984/mandrel/issues/4158) ([#4167](https://github.com/dsj1984/mandrel/issues/4167)) ([939265e](https://github.com/dsj1984/mandrel/commit/939265ef589a24a72d6beebad14197a245ec31ec))
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
### Fixed
|
|
47
|
+
|
|
48
|
+
* **baselines:** apply ignoreGlobs in diff-scope maintainability refresh ([#4166](https://github.com/dsj1984/mandrel/issues/4166)) ([436e3a4](https://github.com/dsj1984/mandrel/commit/436e3a424db9aac54f720a9a993ad0dd285998f3))
|
|
49
|
+
* **lifecycle:** allow epicId in epic.automerge.end schema so the CLI can emit it ([#4168](https://github.com/dsj1984/mandrel/issues/4168)) ([5536cdc](https://github.com/dsj1984/mandrel/commit/5536cdc348e4958b9d05eebd3cd90e1554a1675c))
|
|
50
|
+
|
|
5
51
|
## [1.68.0](https://github.com/dsj1984/mandrel/compare/mandrel-v1.67.0...mandrel-v1.68.0) (2026-06-15)
|
|
6
52
|
|
|
7
53
|
|
package/lib/cli/registry.js
CHANGED
|
@@ -371,7 +371,14 @@ function runRuntimeDeps({
|
|
|
371
371
|
return {
|
|
372
372
|
ok: false,
|
|
373
373
|
detail: `missing: ${missing.join(', ')}`,
|
|
374
|
-
remedy:
|
|
374
|
+
remedy:
|
|
375
|
+
'The framework runtime deps are not resolvable from the consumer root, ' +
|
|
376
|
+
'where the materialized .agents/scripts/*.js run. npm and yarn hoist ' +
|
|
377
|
+
"them automatically — run the installer if you haven't. pnpm's default " +
|
|
378
|
+
'isolated layout does NOT hoist transitive deps to the top level: add ' +
|
|
379
|
+
'`shamefully-hoist=true` (or a scoped `public-hoist-pattern[]` for ' +
|
|
380
|
+
'each dep) to your `.npmrc` and reinstall. ' +
|
|
381
|
+
`Missing: ${missing.join(', ')}`,
|
|
375
382
|
};
|
|
376
383
|
}
|
|
377
384
|
|
|
@@ -457,6 +464,39 @@ function runAgentsMaterialized({ cwd, existsSync, resolvePackage } = {}) {
|
|
|
457
464
|
// from sync.js) are imported from `lib/cli/sync.js` at the top of this file
|
|
458
465
|
// (Story #4048 B3 — no mirror copies).
|
|
459
466
|
|
|
467
|
+
/**
|
|
468
|
+
* Decide whether a single materialized payload file has drifted from its
|
|
469
|
+
* package-payload source.
|
|
470
|
+
*
|
|
471
|
+
* Size short-circuit (Story #4193): when the injected `fsImpl` exposes
|
|
472
|
+
* `statSync`, a `statSync(src).size !== statSync(dest).size` mismatch is
|
|
473
|
+
* sufficient proof of drift, so it returns `true` on two `stat` syscalls
|
|
474
|
+
* **without reading either file's contents** — the overwhelmingly common
|
|
475
|
+
* drift shape after a stale `sync` is a size change. Equal sizes do not
|
|
476
|
+
* guarantee equal content, so an equal-size pair falls through to the
|
|
477
|
+
* `readFileSync` + `Buffer.equals` byte comparison. The short-circuit is
|
|
478
|
+
* opt-in on `statSync` being present; a seam that omits it (e.g. a legacy
|
|
479
|
+
* test double) transparently uses the byte-read path, preserving the prior
|
|
480
|
+
* behaviour exactly.
|
|
481
|
+
*
|
|
482
|
+
* Security: returns only a boolean — it never surfaces file contents to the
|
|
483
|
+
* caller (security baseline §5 — Data Leakage & Logging).
|
|
484
|
+
*
|
|
485
|
+
* @param {string} src - absolute path to the package-payload file
|
|
486
|
+
* @param {string} dest - absolute path to the materialized file
|
|
487
|
+
* @param {{ readFileSync: Function, statSync?: Function }} fsImpl
|
|
488
|
+
* @returns {boolean} `true` when the files differ
|
|
489
|
+
*/
|
|
490
|
+
function payloadFileDrifted(src, dest, fsImpl) {
|
|
491
|
+
if (
|
|
492
|
+
typeof fsImpl.statSync === 'function' &&
|
|
493
|
+
fsImpl.statSync(src).size !== fsImpl.statSync(dest).size
|
|
494
|
+
) {
|
|
495
|
+
return true;
|
|
496
|
+
}
|
|
497
|
+
return !fsImpl.readFileSync(src).equals(fsImpl.readFileSync(dest));
|
|
498
|
+
}
|
|
499
|
+
|
|
460
500
|
/**
|
|
461
501
|
* Compare the consumer's materialized `./.agents/<f>` bytes against the
|
|
462
502
|
* installed package payload (`node_modules/mandrel/.agents/<f>`),
|
|
@@ -469,16 +509,21 @@ function runAgentsMaterialized({ cwd, existsSync, resolvePackage } = {}) {
|
|
|
469
509
|
* §5 — Data Leakage & Logging). The comparison is short-circuiting and never
|
|
470
510
|
* accumulates file contents.
|
|
471
511
|
*
|
|
512
|
+
* Performance: per-file drift is decided by `payloadFileDrifted`, which gates
|
|
513
|
+
* the byte read behind a cheap `statSync` size comparison (Story #4193) so a
|
|
514
|
+
* size-changed file — the common drift shape after a stale `sync` — reports
|
|
515
|
+
* without reading either file's contents.
|
|
516
|
+
*
|
|
472
517
|
* Injectable seams (used by tests so no real filesystem or package is needed):
|
|
473
518
|
* - `cwd()` — replaces `process.cwd`.
|
|
474
519
|
* - `fsImpl` — replaces the `node:fs` surface (`existsSync`, `readdirSync`,
|
|
475
|
-
* `readFileSync`).
|
|
520
|
+
* `readFileSync`, and optionally `statSync` for the size short-circuit).
|
|
476
521
|
* - `resolvePackageRoot(fromDir)` — replaces `mandrel` resolution;
|
|
477
522
|
* throws when the package is not installed.
|
|
478
523
|
*
|
|
479
524
|
* @param {{
|
|
480
525
|
* cwd?: () => string,
|
|
481
|
-
* fsImpl?: { existsSync: (p: string) => boolean, readdirSync: Function, readFileSync: Function },
|
|
526
|
+
* fsImpl?: { existsSync: (p: string) => boolean, readdirSync: Function, readFileSync: Function, statSync?: Function },
|
|
482
527
|
* resolvePackageRoot?: (fromDir: string) => string,
|
|
483
528
|
* }} [opts]
|
|
484
529
|
* @returns {{ ok: boolean, detail: string, remedy?: string }}
|
|
@@ -534,10 +579,8 @@ export function runAgentsDrift({ cwd, fsImpl = fs, resolvePackageRoot } = {}) {
|
|
|
534
579
|
};
|
|
535
580
|
}
|
|
536
581
|
|
|
537
|
-
const srcBytes = fsImpl.readFileSync(src);
|
|
538
|
-
const destBytes = fsImpl.readFileSync(dest);
|
|
539
582
|
comparedCount += 1;
|
|
540
|
-
if (
|
|
583
|
+
if (payloadFileDrifted(src, dest, fsImpl)) {
|
|
541
584
|
return {
|
|
542
585
|
ok: false,
|
|
543
586
|
detail: `${relLabel} differs from the installed package payload`,
|