mandrel 1.67.0 → 1.69.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/docs/agentrc-reference.json +1 -2
- package/.agents/docs/configuration.md +2 -4
- package/.agents/schemas/agentrc.schema.json +1 -5
- package/.agents/schemas/lifecycle/epic.automerge.end.schema.json +2 -1
- 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/lib/baselines/refresh-service.js +13 -1
- package/.agents/scripts/lib/config/explain.js +0 -2
- package/.agents/scripts/lib/config/limits.js +19 -8
- package/.agents/scripts/lib/config-settings-schema-delivery.js +17 -0
- package/.agents/scripts/lib/config-settings-schema.js +12 -2
- package/.agents/scripts/lib/maintainability-utils.js +32 -9
- 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-plan-spec/phases/run-spec-phase.js +32 -1
- package/.agents/scripts/lib/orchestration/epic-run-state-store.js +203 -110
- package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter/composition.js +38 -78
- 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/listeners/watcher.js +13 -8
- package/.agents/scripts/lib/orchestration/manifest-builder.js +6 -0
- package/.agents/scripts/lib/orchestration/planning-risk.js +45 -6
- 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 +36 -4
- package/.agents/scripts/lib/templates/decomposer-prompts.js +23 -3
- package/.agents/scripts/lib/wave-runner/ready-set.js +295 -0
- package/.agents/scripts/lib/wave-runner/tick.js +312 -206
- package/.agents/scripts/lib/wave-runner/wave-runner-error.js +2 -1
- package/.agents/scripts/lint-label-vocabulary.js +1 -1
- 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/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/docs/CHANGELOG.md +26 -0
- package/package.json +1 -1
- 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/docs/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,32 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [1.69.0](https://github.com/dsj1984/mandrel/compare/mandrel-v1.68.0...mandrel-v1.69.0) (2026-06-16)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Added
|
|
9
|
+
|
|
10
|
+
* 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))
|
|
11
|
+
* 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))
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
|
|
16
|
+
* **baselines:** apply ignoreGlobs in diff-scope maintainability refresh ([#4166](https://github.com/dsj1984/mandrel/issues/4166)) ([436e3a4](https://github.com/dsj1984/mandrel/commit/436e3a424db9aac54f720a9a993ad0dd285998f3))
|
|
17
|
+
* **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))
|
|
18
|
+
|
|
19
|
+
## [1.68.0](https://github.com/dsj1984/mandrel/compare/mandrel-v1.67.0...mandrel-v1.68.0) (2026-06-15)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
### Added
|
|
23
|
+
|
|
24
|
+
* **plan:** waive acceptance disposition when no BDD runner detected (refs [#4145](https://github.com/dsj1984/mandrel/issues/4145)) ([#4147](https://github.com/dsj1984/mandrel/issues/4147)) ([f119919](https://github.com/dsj1984/mandrel/commit/f119919c4e69b4a583eeec7c8fe860116dcdf87c))
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
### Fixed
|
|
28
|
+
|
|
29
|
+
* **watcher:** default gh ports in watchPrToTerminal so the CLI path runs (refs [#4144](https://github.com/dsj1984/mandrel/issues/4144)) ([#4148](https://github.com/dsj1984/mandrel/issues/4148)) ([764cc05](https://github.com/dsj1984/mandrel/commit/764cc05b4414f2dff0fb1247b647460df32d037d))
|
|
30
|
+
|
|
5
31
|
## [1.67.0](https://github.com/dsj1984/mandrel/compare/mandrel-v1.66.0...mandrel-v1.67.0) (2026-06-15)
|
|
6
32
|
|
|
7
33
|
|
package/package.json
CHANGED
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Wave-checkpoint reducers — pure functions that read the
|
|
3
|
-
* `epic-run-state` checkpoint payload and the per-wave outcome maps.
|
|
4
|
-
*
|
|
5
|
-
* These two helpers historically lived inside the iterate-waves phase
|
|
6
|
-
* (`lib/orchestration/epic-runner/phases/iterate-waves.js`) but are
|
|
7
|
-
* consumed by both the phase and the stateless wave-runner `tick.js`.
|
|
8
|
-
* Hosting them here severs the planner→phase import coupling: `tick.js`
|
|
9
|
-
* no longer reaches up into the epic-runner phase tree just to derive a
|
|
10
|
-
* force-fresh set, and the phase imports them back from this neutral
|
|
11
|
-
* module.
|
|
12
|
-
*
|
|
13
|
-
* Both functions are pure: they read their argument and return a value
|
|
14
|
-
* without I/O, so they can be unit-tested in isolation.
|
|
15
|
-
*
|
|
16
|
-
* @module lib/wave-runner/wave-checkpoint
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Pull the set of Story IDs that the prior checkpoint marked as part of
|
|
21
|
-
* a halted wave. Story #1795 — used by the resume-check cache pre-warm:
|
|
22
|
-
* Stories appearing in this set are force-fresh-fetched on resume (the
|
|
23
|
-
* operator may have hand-edited their labels during the blocker
|
|
24
|
-
* window); every other Story serves its resume-check from the
|
|
25
|
-
* provider's in-process cache.
|
|
26
|
-
*
|
|
27
|
-
* Tolerant of partial/legacy checkpoint shapes: a missing or
|
|
28
|
-
* unparseable checkpoint returns an empty set so the resume-check
|
|
29
|
-
* gracefully degrades to "use cache for all" — the existing
|
|
30
|
-
* cold-start fallback inside `getTicket` still issues the real fetch.
|
|
31
|
-
*
|
|
32
|
-
* @param {object | null | undefined} checkpoint
|
|
33
|
-
* @returns {Set<number>}
|
|
34
|
-
*/
|
|
35
|
-
export function collectHaltedStoryIds(checkpoint) {
|
|
36
|
-
const halted = new Set();
|
|
37
|
-
const waves = checkpoint?.waves;
|
|
38
|
-
if (!Array.isArray(waves)) return halted;
|
|
39
|
-
for (const wave of waves) {
|
|
40
|
-
if (wave?.status !== 'halted') continue;
|
|
41
|
-
const stories = Array.isArray(wave.stories) ? wave.stories : [];
|
|
42
|
-
for (const story of stories) {
|
|
43
|
-
const id =
|
|
44
|
-
story?.storyId ??
|
|
45
|
-
story?.id ??
|
|
46
|
-
(typeof story === 'number' ? story : null);
|
|
47
|
-
if (Number.isInteger(id) && id > 0) halted.add(id);
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
return halted;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Cross-event invariant guard for `wave.end` (Acceptance Spec AC-8 /
|
|
55
|
-
* Repeatability AC #5 — wave completeness).
|
|
56
|
-
*
|
|
57
|
-
* The schema layer can declare key/value types for `outcomes` but
|
|
58
|
-
* cannot enforce that the key set equals the `wave.start.storyIds` set
|
|
59
|
-
* from earlier in the run. We enforce it here, before emit, so a
|
|
60
|
-
* violation throws synchronously and the ledger never carries a
|
|
61
|
-
* non-conformant record.
|
|
62
|
-
*
|
|
63
|
-
* Throws a typed `Error` with `code: 'WAVE_COMPLETENESS_VIOLATION'` and
|
|
64
|
-
* attached diagnostic fields so tests and operators can reason about
|
|
65
|
-
* the mismatch without grepping the message.
|
|
66
|
-
*
|
|
67
|
-
* @param {{ waveIndex: number, storyIds: number[], outcomes: Record<string, string> }} args
|
|
68
|
-
*/
|
|
69
|
-
export function assertWaveCompleteness({ waveIndex, storyIds, outcomes }) {
|
|
70
|
-
const expected = new Set(storyIds);
|
|
71
|
-
const actual = new Set(
|
|
72
|
-
Object.keys(outcomes ?? {})
|
|
73
|
-
.map((k) => Number(k))
|
|
74
|
-
.filter((n) => Number.isInteger(n)),
|
|
75
|
-
);
|
|
76
|
-
const missing = [...expected].filter((id) => !actual.has(id));
|
|
77
|
-
const extra = [...actual].filter((id) => !expected.has(id));
|
|
78
|
-
if (missing.length === 0 && extra.length === 0) return;
|
|
79
|
-
const err = new Error(
|
|
80
|
-
`wave-completeness violation for wave #${waveIndex}: ${
|
|
81
|
-
missing.length ? `missing outcomes for [${missing.join(', ')}]` : ''
|
|
82
|
-
}${missing.length && extra.length ? '; ' : ''}${
|
|
83
|
-
extra.length ? `extra outcomes for [${extra.join(', ')}]` : ''
|
|
84
|
-
}`,
|
|
85
|
-
);
|
|
86
|
-
err.code = 'WAVE_COMPLETENESS_VIOLATION';
|
|
87
|
-
err.waveIndex = waveIndex;
|
|
88
|
-
err.missing = missing;
|
|
89
|
-
err.extra = extra;
|
|
90
|
-
throw err;
|
|
91
|
-
}
|