mandrel 1.82.0 → 1.83.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.
Files changed (45) hide show
  1. package/.agents/README.md +41 -0
  2. package/.agents/docs/SDLC.md +4 -2
  3. package/.agents/docs/agentrc-reference.json +10 -2
  4. package/.agents/docs/execution-reference.md +52 -0
  5. package/.agents/instructions.md +76 -38
  6. package/.agents/schemas/agentrc.schema.json +31 -3
  7. package/.agents/schemas/qa-ledger.schema.json +2 -2
  8. package/.agents/scripts/epic-deliver-prepare.js +41 -1
  9. package/.agents/scripts/lib/config/explain.js +4 -1
  10. package/.agents/scripts/lib/config-settings-schema.js +25 -1
  11. package/.agents/scripts/lib/epic-body-sections.js +88 -0
  12. package/.agents/scripts/lib/findings/promote-finding.js +3 -3
  13. package/.agents/scripts/lib/findings/severity.js +5 -6
  14. package/.agents/scripts/lib/orchestration/check-baselines/phases/evaluate.js +65 -2
  15. package/.agents/scripts/lib/orchestration/context-hydration-engine.js +96 -11
  16. package/.agents/scripts/lib/orchestration/doc-reader.js +29 -0
  17. package/.agents/scripts/lib/orchestration/docs-digest.js +134 -0
  18. package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/refresh-commit.js +15 -1
  19. package/.agents/scripts/lib/qa/console-allowlist.js +5 -4
  20. package/.agents/scripts/lib/qa/resolve-qa-contract.js +144 -8
  21. package/.agents/skills/core/epic-plan-consolidate/SKILL.md +7 -5
  22. package/.agents/skills/core/epic-plan-consolidate/examples.md +51 -0
  23. package/.agents/skills/core/epic-plan-decompose-author/SKILL.md +4 -22
  24. package/.agents/skills/core/epic-plan-decompose-author/examples.md +47 -0
  25. package/.agents/skills/core/epic-plan-premortem/SKILL.md +9 -8
  26. package/.agents/skills/core/epic-plan-premortem/examples.md +53 -0
  27. package/.agents/skills/core/epic-plan-spec-author/SKILL.md +21 -81
  28. package/.agents/skills/core/epic-plan-spec-author/examples.md +91 -0
  29. package/.agents/skills/skills.index.json +3 -3
  30. package/.agents/skills/stack/qa/qa-explore-driving/SKILL.md +52 -38
  31. package/.agents/workflows/helpers/deliver-epic-reference.md +514 -0
  32. package/.agents/workflows/helpers/deliver-epic.md +164 -469
  33. package/.agents/workflows/helpers/epic-deliver-story.md +35 -11
  34. package/.agents/workflows/helpers/plan-epic-reference.md +136 -0
  35. package/.agents/workflows/helpers/plan-epic.md +56 -186
  36. package/.agents/workflows/helpers/plan-story.md +31 -61
  37. package/.agents/workflows/helpers/qa-run-scenario.md +194 -0
  38. package/.agents/workflows/helpers/scope-triage-gate.md +97 -0
  39. package/.agents/workflows/helpers/single-story-deliver-reference.md +423 -0
  40. package/.agents/workflows/helpers/single-story-deliver.md +128 -392
  41. package/.agents/workflows/qa-explore.md +63 -32
  42. package/.agents/workflows/qa-run.md +293 -130
  43. package/docs/CHANGELOG.md +14 -0
  44. package/package.json +1 -1
  45. package/.agents/schemas/qa-finding.schema.json +0 -133
@@ -12,6 +12,14 @@ description: >-
12
12
 
13
13
  # helpers/deliver-epic — Epic delivery path (invoked by /deliver)
14
14
 
15
+ > **Runtime core.** This file is the always-ingested Epic-delivery path:
16
+ > phase flow, commands, gate contracts, and return shapes. The recovery
17
+ > procedures, historical rationale, and troubleshooting detail live in the
18
+ > sibling [`deliver-epic-reference.md`](deliver-epic-reference.md); each
19
+ > moved procedure keeps a one-line pointer at its trigger point below. The
20
+ > reference is not projected to `.claude/commands/` — it is consulted on
21
+ > demand.
22
+
15
23
  ## Overview
16
24
 
17
25
  This helper is the **Epic delivery path** behind `/deliver` — the router
@@ -65,11 +73,10 @@ spawned.
65
73
  - `--full-retro` — force the six-section retro regardless of manifest
66
74
  cleanliness. `--skip-retro` wins over `--full-retro`.
67
75
  - `--skip-integration-gate` — skip Phase 6.5 (log the override). The
68
- explicit operator override for the post-wave integration gate, consistent
69
- with `--skip-epic-audit`. Use only when the deliberately-global checks are
70
- known to be irrelevant for this Epic (e.g., a docs-only Epic that adds no
71
- surface). Skipping the gate is recorded as a manual intervention and
72
- disqualifies auto-merge, exactly like the other `--skip-*` overrides.
76
+ explicit operator override for the post-wave integration gate,
77
+ consistent with `--skip-epic-audit`. Skipping the gate is recorded as a
78
+ manual intervention and disqualifies auto-merge, exactly like the other
79
+ `--skip-*` overrides.
73
80
 
74
81
  Every other runtime modifier is sourced from the Epic's labels or from
75
82
  `delivery.deliverRunner` in `.agentrc.json`.
@@ -97,10 +104,7 @@ Every other runtime modifier is sourced from the Epic's labels or from
97
104
  (`--event epic.close.end` / `--event epic.automerge.start` /
98
105
  `--event epic.merge.armed`); the matching listener chain runs the
99
106
  bus-driven side effects (acceptance reconcile, automerge-armer,
100
- branch cleanup). PR open + planning-artifact close are
101
- operator-/host-LLM-driven in the current wiring — see Phase 7.1 for
102
- the canonical manual sequence and `finalizer.js` for the listener's
103
- no-op disclaimer. The append-only NDJSON ledger at
107
+ branch cleanup). The append-only NDJSON ledger at
104
108
  `temp/epic-<id>/lifecycle.ndjson` is the resume target. See
105
109
  [`docs/LIFECYCLE.md`](../../../docs/LIFECYCLE.md) for the bus
106
110
  contract, event taxonomy, ledger format, and listener model.
@@ -152,9 +156,7 @@ run upserts the same comment in place.
152
156
 
153
157
  Threshold defaults live in `delivery.preflight.*` in `.agentrc.json`
154
158
  (all keys default to "no cap" — the gate is opt-in until an operator
155
- configures `maxStories` etc.). The CI-firehose mitigation
156
- (`delivery.ci.skipForStoryPushes: true`) and these threshold keys are
157
- the two operator-tunable knobs F13 ships.
159
+ configures `maxStories` etc.).
158
160
 
159
161
  ### Phase 1 main — Seed the wave plan
160
162
 
@@ -167,10 +169,15 @@ Validates `type::epic`, enumerates `type::story` descendants, parses
167
169
  (to enumerate the open Story set), and upserts the `epic-run-state`
168
170
  checkpoint in the per-Story-status shape (a flat `stories` map seeded at
169
171
  `pending`, plus the global `concurrencyCap`). Treat the printed JSON as
170
- `state`: `{ epicId, storyCount, concurrencyCap, stories, checkpointInitializedAt }`.
172
+ `state`: `{ epicId, storyCount, concurrencyCap, stories, checkpointInitializedAt, docsDigestPath }`.
171
173
  `stories` is the flat dispatch hint (`{ storyId, worktree, title }` per open
172
174
  Story); the ready-set `tick` (Phase 2) decides which to dispatch on each
173
- beat. Flip the Epic to `agent::executing` (idempotent) after the CLI returns.
175
+ beat. `docsDigestPath` is the repo-relative path to the per-Epic docs digest
176
+ (`temp/epic-<epicId>/docs-digest.md`) that prepare writes from
177
+ `project.docsContextFiles` — thread it into every child prompt (§ 2b, item 6).
178
+ It is `null` when the project configured no `docsContextFiles` (no digest is
179
+ written). Flip the Epic to `agent::executing` (idempotent) after the CLI
180
+ returns.
174
181
 
175
182
  **No spec-ticket linkage to resolve (Story #4324).** The Tech Spec lives
176
183
  as managed sections of the Epic body itself — there is no separate
@@ -179,52 +186,14 @@ into the per-Story `story-init.js` invocations. Story agents receive the
179
186
  Tech Spec via context hydration, which embeds the Epic body (with the
180
187
  `## Acceptance Table` section stripped) directly into each Story prompt.
181
188
 
182
- > **Preflight guards (Story #3482 / F-workflow-guards).** Before the
183
- > snapshot phase runs — and before any worktree is created — prepare runs
184
- > two **fail-closed** guards
185
- > ([`lib/orchestration/epic-deliver-lease-guard.js`](../../scripts/lib/orchestration/epic-deliver-lease-guard.js)):
186
- >
187
- > 1. **Checkout safety.** Prepare refuses to start when the working tree is
188
- > dirty or HEAD is on a branch other than the expected one (`epic/<id>`
189
- > on a resume, or the project base branch on a fresh run). It will
190
- > **not** check `epic/<id>` out over your work — the historic
191
- > HEAD-yank footgun. Remediation: commit/stash/clean the tree, or
192
- > switch to the expected branch, then re-run.
193
- > 2. **Epic lease.** Prepare acquires the assignee-as-lease on the Epic
194
- > ticket (`ticket-lease.acquireLease`). On a **live foreign claim**
195
- > (a teammate's run with a fresh `story.heartbeat` within
196
- > `delivery.lease.ttlMs`) it exits non-zero and names the current owner;
197
- > a **stale** claim is silently reclaimed. The operator identity is
198
- > resolved from `--as <handle>` → `github.operatorHandle` →
199
- > `git config user.email`. Pass `--steal` to forcibly transfer a live
200
- > foreign claim (the takeover is logged for auditability). The committed
201
- > `github.operatorHandle` is the non-personal `@[USERNAME]` placeholder,
202
- > which resolves to null — so when none of the three sources yields a real
203
- > identity the guard **fails closed** (throws after the checkout guard
204
- > runs) rather than driving an ownerless, unguarded delivery. Set your own
205
- > handle in `.agentrc.local.json`, pass `--as <handle>`, or configure
206
- > `git user.email`. The lease is the cross-clone coordination layer, while
207
- > `epic-merge-lock.js` continues to serialize same-machine sessions.
208
- >
209
- > Both guards throw on failure, which `runAsCli` maps to `process.exit(1)`
210
- > per [`orchestration-error-handling.md`](../../rules/orchestration-error-handling.md).
211
-
212
- Once the preflight guards pass, the snapshot phase applies one more gate:
213
-
214
- > **Acceptance-table start gate.** Before the wave loop fans out, the
215
- > snapshot phase
216
- > ([`lib/orchestration/epic-runner/phases/snapshot.js`](../../scripts/lib/orchestration/epic-runner/phases/snapshot.js))
217
- > asserts that the Epic either carries the `acceptance::n-a` waiver
218
- > label **or** has a `## Acceptance Table` managed section in its
219
- > body — section presence is sufficient.
220
- > The reviewer's OK during `/plan` Phase 7 is the approval
221
- > signal. Neither condition met →
222
- > the snapshot throws a clear error
223
- > (`[epic-deliver] Epic #<id> cannot launch: …` — naming the missing
224
- > `## Acceptance Table` section and the absent waiver) and `runAsCli`
225
- > maps it to `process.exit(1)`. Operator remediation: either run
226
- > `/plan` Phase 7 to author the acceptance table, or apply the
227
- > `acceptance::n-a` label to opt out.
189
+ > **Preflight guards + acceptance-table start gate.** Before the snapshot
190
+ > phase runs — and before any worktree is created — prepare runs two
191
+ > **fail-closed** guards (checkout safety + Epic lease), then the snapshot
192
+ > phase asserts the Epic carries an `acceptance::n-a` waiver or a
193
+ > `## Acceptance Table` section. Both throw on failure. See
194
+ > [`deliver-epic-reference.md` § Phase 1 Preflight guards](deliver-epic-reference.md#phase-1--preflight-guards-story-3482--f-workflow-guards)
195
+ > for the remediation detail (dirty-tree recovery, `--steal`, waiver
196
+ > options).
228
197
 
229
198
  ---
230
199
 
@@ -284,11 +253,10 @@ spans). The [`signals` helper](signals.md)
284
253
  (`node .agents/scripts/signals-view.js`) renders the forensics signals in the
285
254
  span-tree view.
286
255
 
287
- > **Fail-closed on an old-shape checkpoint.** If the Epic still carries a
288
- > pre-ready-set (`plan` / `currentWave` / `totalWaves`) `epic-run-state`
289
- > checkpoint, the tick **refuses to run** and throws an explicit operator
290
- > message — re-run `epic-deliver-prepare.js --epic <id>` to re-seed the
291
- > checkpoint in the per-Story-status shape, then re-run `/deliver`.
256
+ > **Old-shape checkpoint fail-closed.** A pre-ready-set
257
+ > (`plan` / `currentWave` / `totalWaves`) checkpoint makes the tick refuse
258
+ > to run and throw; re-seed via `epic-deliver-prepare.js`. See
259
+ > [`deliver-epic-reference.md` § Fail-closed on an old-shape checkpoint](deliver-epic-reference.md#fail-closed-on-an-old-shape-checkpoint).
292
260
 
293
261
  ### 2b. Dispatch — fan out per-Story Agent calls
294
262
 
@@ -306,15 +274,12 @@ Dispatch the ready set as background calls (`run_in_background: true`) and,
306
274
  as each child returns, record it (§ 2c) and **re-tick** (§ 2a) to pull the
307
275
  next ready set — never wait for the whole set before refilling.
308
276
 
309
- > **Throughput tradeoff.** The default `concurrencyCap` of 3 is the GLOBAL
310
- > in-flight cap, intentionally conservative — it keeps host-quota
311
- > consumption low and avoids flooding the GitHub API. For Epics with wide
312
- > dependency-free fronts where the host has adequate parallel-agent quota,
313
- > raising `delivery.deliverRunner.concurrencyCap` in `.agentrc.json` reduces
314
- > wall-clock time proportionally to the extra concurrency. The safe default
315
- > is left in place; this is a deliberate operator-tuning knob, not a hidden
316
- > performance ceiling. See `agentrc-reference.json`
317
- > `delivery.deliverRunner.concurrencyCap` for the configuration surface.
277
+ > **Throughput + capability tuning.** The default `concurrencyCap` of 3 is
278
+ > a deliberate operator-tuning knob (raise
279
+ > `delivery.deliverRunner.concurrencyCap`), and the optional per-call
280
+ > `model:` escape hatch lets mechanical Stories run on a cheaper capability.
281
+ > See [`deliver-epic-reference.md` § Throughput tradeoff](deliver-epic-reference.md#throughput-tradeoff)
282
+ > and Sub-agent dispatch capability](deliver-epic-reference.md#sub-agent-dispatch-capability).
318
283
 
319
284
  **Ledger the dispatch BEFORE the Agent call.** Immediately before each
320
285
  per-Story `Agent` tool call (one shell-out per Story, every attempt —
@@ -345,13 +310,24 @@ Each Agent call's prompt must (1) name the Story + Epic ids, (2)
345
310
  instruct the child to invoke `helpers/epic-deliver-story <storyId>`
346
311
  (whose Step 4 defines the child's return shape), (3) remind the child
347
312
  of the **non-interactive contract** (no clarifying questions;
348
- transition to `agent::blocked` and exit if stuck), (4) ask the child to
349
- suppress per-Story chat relay, and (5) require the child to emit a
350
- `story.heartbeat` lifecycle event at least once per Story-level phase
351
- transition via `node .agents/scripts/story-phase.js` (or whenever it
352
- stalls on a long-running step), and if it cannot make progress to
353
- transition to `agent::blocked` rather than fall silent. The pairing of
354
- `story.heartbeat` and `agent::blocked` is what lets the §2e Idle
313
+ transition to `agent::blocked` and exit if stuck), (4) tell the child to
314
+ suppress per-Story chat relay and instead relay **one line per phase
315
+ transition** (e.g. `Story #<id>: implementing closing`) the child's
316
+ authoritative progress lands in the `story-run-progress` snapshot the
317
+ `story-phase.js` CLI upserts, not in a verbatim body dump, (5) require the
318
+ child to emit a `story.heartbeat` lifecycle event at least once per
319
+ Story-level phase transition via `node .agents/scripts/story-phase.js` (or
320
+ whenever it stalls on a long-running step), and if it cannot make progress
321
+ to transition to `agent::blocked` rather than fall silent, and (6) pass the
322
+ **docs digest path** — the `docsDigestPath` field from the
323
+ `epic-deliver-prepare.js` envelope (§ Phase 1 main), which points at
324
+ `temp/epic-<epicId>/docs-digest.md`. Instruct the child to read that
325
+ digest instead of re-reading the full `project.docsContextFiles` set,
326
+ and to pull individual docs files on demand (per
327
+ [`.agents/instructions.md` § 3](../../instructions.md)). When
328
+ `docsDigestPath` is null (the project configured no `docsContextFiles`),
329
+ say so — the child then has no per-Story docs mandate. The pairing of
330
+ `story.heartbeat` and `agent::blocked` is what lets the § 2d Idle
355
331
  Watchdog distinguish a working child from a dead one; a silent child
356
332
  with no recent heartbeat and no blocker label is the failure mode the
357
333
  watchdog is built to catch.
@@ -362,13 +338,6 @@ to enforce. GitHub state is the contract: `epic-execute-record-wave.js`
362
338
  hint and reconciles any unparseable, empty, or missing return directly
363
339
  from the Story's live labels and comments.
364
340
 
365
- **Sub-agent dispatch.** `Agent` calls emit no `model:` argument by
366
- default — children inherit from the `general-purpose` sub-agent
367
- definition and the parent's worktree context. No
368
- `--dangerously-skip-permissions` (no subprocess is spawned). If a
369
- specific call needs to override the inherited model, pass `model:` as a
370
- per-call literal at the `Agent(...)` site.
371
-
372
341
  ### 2c. Record the Story outcomes
373
342
 
374
343
  As dispatched Stories return (record them as they land — you need not wait
@@ -397,14 +366,11 @@ Print `renderedBody` verbatim, then optionally append a short **Notable**
397
366
  section (0–5 bullets on newly blocked / failed / slow Stories, friction,
398
367
  elapsed-time surprises).
399
368
 
400
- > **Crash recovery.** If the host crashed *after* a child finished but
401
- > *before* its return was recorded, the next `tick` re-derives that Story's
402
- > state directly from its live label (the tick reads labels every beat), so a
403
- > done-but-unrecorded Story is recognised as done and never re-dispatched —
404
- > there is no falsely-`complete` empty wave to recover from. If you want to
405
- > reconcile a known-completed Story whose return text was lost, re-record it
406
- > from its live state by passing `--results '[{"storyId":<n>,"status":"done"}]'`
407
- > (verification re-checks the live label before recording `done`).
369
+ > **Crash recovery.** A child that finished but was never recorded is
370
+ > re-derived from its live label on the next `tick` and never
371
+ > re-dispatched. See
372
+ > [`deliver-epic-reference.md` § Crash recovery (record step)](deliver-epic-reference.md#crash-recovery-record-step)
373
+ > for the manual re-record command.
408
374
 
409
375
  ### 2d. Loop on `nextAction`
410
376
 
@@ -430,69 +396,14 @@ After `2c`, re-run `wave-tick.js`. Branch on the new envelope:
430
396
  the done count equals the in-scope Story count — a stuck Story surfaces
431
397
  as `halt`, not a false `epic-complete`.)
432
398
 
433
- ### 2e. Idle Watchdog
434
-
435
- A Story's implementation loop can run for many minutes between
436
- `story.dispatch.start` and the post-merge `story.merged` record. While
437
- `observe` keeps the host LLM polling the Epic, it does **not** flag a
438
- sub-agent that has gone silent (host crash, mid-Story stall, lost
439
- return). The Idle Watchdog closes that gap.
440
-
441
- **Cadence.** While any Story is in flight (i.e. `nextAction.kind` is
442
- `observe` or the most recent dispatch's `in-flight` list is non-empty),
443
- re-tick every **30 minutes** with the watchdog flag:
444
-
445
- ```bash
446
- node .agents/scripts/wave-tick.js --epic <epicId> --check-idle 30
447
- ```
448
-
449
- > **Why 30, not 10 (Story #3900).** Heartbeats fire only at Story-level
450
- > phase transitions, and `implementing → closing` routinely exceeds 10
451
- > minutes for a healthy Story. A 10-minute threshold therefore tripped the
452
- > watchdog on every long-running Story, whose prescribed remediation —
453
- > re-dispatch — put two agents on one `story-<id>` branch (the worst
454
- > failure mode in the system). The threshold is widened to 30 minutes and
455
- > the staleness test now also consults a deterministic branch-commit
456
- > signal (below), so a Story still gaining commits is never flagged.
457
-
458
- The `--check-idle <minutes>` mode scans the per-Epic lifecycle ledger
459
- (`temp/epic-<epicId>/lifecycle.ndjson`) for Stories that carry a
460
- `story.dispatch.start` without a matching `story.dispatch.end` (the
461
- canonical in-flight list — see § 2a's `nextAction['in-flight']`), and
462
- compares each in-flight Story's most recent ledger event (any
463
- `story.*` event, notably the `story.heartbeat` records emitted by
464
- `story-phase.js` at each Story-level phase transition) against the
465
- threshold. **Before flagging a stall, it also checks the last commit on
466
- `story-<id>` via `git log` (Story #3900): a Story whose branch carries a
467
- commit newer than the threshold is making forward progress and is left
468
- in-flight, never stalled — deterministic protection against the
469
- false-positive re-dispatch hazard even when no heartbeat has landed.**
470
- The CLI emits one envelope on stdout and exits non-zero when at least
471
- one in-flight Story has been silent for ≥ the threshold:
472
-
473
- ```json
474
- {
475
- "kind": "wave-stall",
476
- "epicId": <n>,
477
- "thresholdMinutes": <n>,
478
- "checkedAt": "<ISO-8601>",
479
- "stalled": [{ "storyId": <n>, "lastEventAt": "<ISO-8601>", "idleMinutes": <n> }],
480
- "inFlight": [<storyId>, ...]
481
- }
482
- ```
483
-
484
- **On a stall.** When the watchdog exits non-zero, post the envelope
485
- verbatim as a `wave-stall` structured comment on the Epic (use
486
- [`post-structured-comment.js`](../../scripts/post-structured-comment.js)
487
- with `--kind wave-stall`), then re-evaluate the affected Stories: if a
488
- child sub-agent has crashed (no `story.dispatch.end`, no recent
489
- heartbeat, no commit on `story-<id>`), re-dispatch the Story per § 2b
490
- incrementing the `--attempt` counter; if the child is alive but
491
- genuinely blocked, flip the Story to `agent::blocked` and proceed per
492
- § 2d's `observe` branch.
493
-
494
- Stop the watchdog cadence once `wave-tick.js` returns `epic-complete` —
495
- there are no in-flight Stories left to monitor.
399
+ > **Idle Watchdog.** While any Story is in flight, re-tick every 30 minutes
400
+ > with `wave-tick.js --epic <epicId> --check-idle 30` so a silent child
401
+ > (crashed host, lost return) is surfaced as a `wave-stall` and
402
+ > re-dispatched or blocked. The full cadence, staleness test (heartbeat +
403
+ > deterministic branch-commit signal), stall envelope, and the "why 30 not
404
+ > 10" rationale are in
405
+ > [`deliver-epic-reference.md` § 2e. Idle Watchdog](deliver-epic-reference.md#2e-idle-watchdog).
406
+ > Stop the cadence once the tick returns `epic-complete`.
496
407
 
497
408
  ---
498
409
 
@@ -593,219 +504,91 @@ canonical compose-and-post surface at
593
504
  Propagate `--full-retro` to bypass the compact-path heuristic.
594
505
 
595
506
  Retro fires here (before the PR opens) so it stays in the operator's
596
- local session with full env access (env vars, credentials, MCP).
597
-
598
- After the GitHub upsert succeeds, the retro body is also **mirrored
599
- locally** to the per-Epic temp tree at `temp/epic-<epicId>/retro.md`
600
- (path resolved via
507
+ local session with full env access (env vars, credentials, MCP). After
508
+ the GitHub upsert succeeds, the retro body is also **mirrored locally** to
509
+ `temp/epic-<epicId>/retro.md` (path resolved via
601
510
  [`lib/config/temp-paths.js`](../../scripts/lib/config/temp-paths.js)'s
602
- `epicRetroMirrorPath`, which honours `project.paths.tempRoot`).
603
- Operators can read the retro without re-fetching from GitHub. GitHub
604
- remains the source of truth — a mirror-write failure only logs a warn
605
- and never fails the phase.
511
+ `epicRetroMirrorPath`). GitHub remains the source of truth — a
512
+ mirror-write failure only logs a warn and never fails the phase.
606
513
 
607
514
  ---
608
515
 
609
516
  ## Phase 6.5 — Post-wave integration gate (Epic #4131, F1/F4)
610
517
 
611
518
  This phase runs **after** the Phase 2 wave loop reports `epic-complete` and
612
- **before** the Phase 7 finalize emit opens the PR to `main`. Where every gate
613
- before it (`selectAudits`, the cross-epic-leak guard `#3362`, diff-against-base,
614
- the file-assumption gate) is **change-set-scoped** — it narrows its evidence to
615
- the Epic's `changedFiles` this is the one **deliberately-global** gate: its
616
- evidence spans the **whole product**, not just the change set. The disease it
617
- catches is the surface that each Story shipped correctly in isolation yet that
618
- the assembled product cannot reach: a route nobody can navigate to, or a
619
- persona journey that the integrated waves silently broke. A change-set lens
620
- never sees that, because no single Story's diff contains the orphan.
519
+ **before** the Phase 7 finalize emit opens the PR to `main`. It is the one
520
+ **deliberately-global** gate its evidence spans the whole product, not just
521
+ the Epic's change set — so it catches the surface each Story shipped correctly
522
+ in isolation yet the assembled product cannot reach (an unnavigable route, a
523
+ broken persona journey).
621
524
 
622
525
  Skip when `--skip-integration-gate` (log the override; record a manual
623
- intervention per [§ Recording manual interventions](#recording-manual-interventions)).
624
- The gate is otherwise **always evaluated**, but it is a **silent no-op when
625
- unconfigured** (see *No-op when unconfigured* below) so consumers who ship no
626
- nav config and no journey suite are unaffected.
627
-
628
- ### 6.5a — Whole-product navigability (reuses the Phase 4 lens)
629
-
630
- Run the `navigability` lens
631
- ([`audit-navigability.md`](../audit-navigability.md), the deliberately-global
632
- lens delivered in this Epic's wave 0) in **whole-route mode** against the
633
- integrated `epic/<epicId>` tip. Unlike its Phase 4 change-set-routed invocation,
634
- here it is driven explicitly over the consumer's **entire** route tree + nav
635
- registryit is on the global-lens allowlist (`GLOBAL_LENS_ALLOWLIST` in
636
- [`lib/audit-suite/selector.js`](../../scripts/lib/audit-suite/selector.js)) and
637
- exempt from the cross-epic-leak guard `#3362`, so a route orphaned anywhere in
638
- the product is in scope even when no Story in this Epic touched it.
639
-
640
- The lens reads the consumer's navigability config (resolved from `.agentrc.json`):
641
-
642
- - `delivery.quality.navigability.routeGlobs` the route-tree SSOT the lens
643
- enumerates.
644
- - `delivery.quality.navigability.navRegistry` the nav-registry SSOT the lens
645
- cross-checks every route against.
646
-
647
- A whole-product finding is a **hard failure**: an **orphaned route** (a route
648
- with no nav door for any entitled persona) or a **dead nav href** (a nav door
649
- pointing at a route that no longer exists). On a hard failure the gate **blocks
650
- finalize** and names the orphaned surface (route identifier / nav-door
651
- identifier only never the route body or persona PII, per
652
- `security-baseline.md`). Do **not** proceed to Phase 7.
653
-
654
- ### 6.5b — Consumer journey suite
655
-
656
- Run the consumer's per-persona journey suite — the integrated, cross-Epic
657
- persona-journey verification — over the `epic/<epicId>` tip:
658
-
659
- - `delivery.quality.navigability.journeySuite` — path/command for the journey
660
- suite. Absent ⇒ this sub-step is skipped (no-op).
661
-
662
- A failing journey (a persona cannot complete an end-to-end journey through the
663
- assembled product) is a **hard failure**: block finalize and name the broken
664
- journey. This is the runtime complement to 6.5a's static reachability check —
665
- 6.5a proves a door exists; the journey suite proves the door, and everything
666
- behind it, actually works when the waves are integrated.
667
-
668
- ### 6.5c — `@pending` ≠ green for surface-adding Epics (F4)
669
-
670
- The Phase 7 finalize chain runs the acceptance-spec reconciler
671
- ([`acceptance-spec-reconciler.js`](../../scripts/acceptance-spec-reconciler.js)),
672
- which classifies every AC ID into `satisfied` (covered by a non-pending
673
- scenario), `pending` (covered **only** by scenarios tagged `@pending`), or
674
- `missing`. For a **surface-adding Epic** — one whose risk verdict carries a
675
- surface-adding signal (a route-adding change set, the `navigability` lens routed
676
- in Phase 4, or a configured `routeGlobs` match) — this phase tightens that
677
- contract: an AC whose coverage is **only `@pending`** is treated as
678
- **unsatisfied**, not green. A surface-adding Epic whose acceptance coverage is
679
- **only `@pending`** therefore **fails the close gate** instead of passing —
680
- shipping a new surface behind a deferred-forever `@pending` scenario is exactly
681
- the late-gate gap this Epic exists to close.
682
-
683
- This is **purely additive** and **scoped to surface-adding Epics**:
684
- refactor-only and docs-only Epics (no surface-adding signal) are **unaffected** —
685
- their `@pending` handling is exactly as before, and the existing
686
- `satisfied` / `missing` reconciliation is **not** de-scoped for any Epic.
687
-
688
- ### No-op when unconfigured
689
-
690
- With **no** navigability config (`routeGlobs` / `navRegistry`) and **no**
691
- `journeySuite` present in `.agentrc.json`, this entire phase degrades to a
692
- **silent no-op**: 6.5a and 6.5b skip (nothing to enumerate or run) and 6.5c's
693
- surface-adding signal cannot fire without route globs, so the `@pending`
694
- tightening never engages. The gate adds **zero** behaviour to an unconfigured
695
- consumer — it neither blocks finalize nor changes the existing change-set-scoped
696
- gates. The override flag `--skip-integration-gate` is the explicit operator
697
- escape hatch (consistent with `--skip-epic-audit`) for a configured consumer who
698
- wants to bypass the gate for a specific run.
699
-
700
- ### Fail safe and loud
701
-
702
- Per the Tech Spec's security note, this gate sits on the **critical path** to
703
- finalize. It MUST fail **safe and loud**: a hard failure **blocks** finalize and
704
- **names the cause** (the orphaned route, dead href, broken journey, or
705
- `@pending`-only AC), never silently passes. A genuinely unconfigured consumer is
706
- the **only** silent path, and that path **passes** (it is a no-op, not a block).
707
- On a hard failure, post a friction structured comment naming the surface, flip
708
- the Epic to `agent::blocked`, and park for the operator — do **not** open the PR.
526
+ intervention per
527
+ [`deliver-epic-reference.md` § Recording manual interventions](deliver-epic-reference.md#recording-manual-interventions)).
528
+ The gate is otherwise **always evaluated** but a **silent no-op when
529
+ unconfigured** (no `routeGlobs` / `navRegistry` / `journeySuite` in
530
+ `.agentrc.json`).
531
+
532
+ Sub-steps and hard-failure semantics:
533
+
534
+ - **6.5a — Whole-product navigability**: run the `navigability` lens in
535
+ whole-route mode over the `epic/<epicId>` tip. An **orphaned** route (no
536
+ nav door for any entitled persona) or a dead nav href is a hard failure
537
+ that **blocks finalize** and names the surface.
538
+ - **6.5b Consumer journey suite**: run
539
+ `delivery.quality.navigability.journeySuite` over the tip. A failing
540
+ persona journey is a hard failure that **blocks finalize** and names the
541
+ broken journey.
542
+ - **6.5c — `@pending` ≠ green for surface-adding Epics (F4)**: for a
543
+ **surface-adding** Epic, an AC covered **only** by `@pending` scenarios is
544
+ treated as unsatisfied and **fails the close gate** instead of passing
545
+ green. This is **purely additive** and scoped to surface-adding Epics
546
+ refactor-only and docs-only Epics are **unaffected** and the existing
547
+ `satisfied` / `missing` reconciliation is **not de-scoped** for any Epic.
548
+
549
+ On any hard failure, post a friction structured comment naming the surface
550
+ (route / nav-door identifier only never route bodies or persona PII per
551
+ `security-baseline.md`), flip the Epic to `agent::blocked`, and **do not**
552
+ open the PR the gate fails safe and loud. See
553
+ [`deliver-epic-reference.md` § Phase 6.5](deliver-epic-reference.md#phase-65--post-wave-integration-gate-epic-4131-f1f4)
554
+ for the full lens config, the surface-adding-signal derivation, the no-op
555
+ degradation contract, and the fail-safe-and-loud security note.
709
556
 
710
557
  ---
711
558
 
712
559
  ## Phase 7 — Finalize (open PR to main)
713
560
 
714
- ### 7.0 Sync Epic branch from `main` (Story #2580)
715
-
716
- Before firing the close-tail emit, sync the Epic branch with
717
- `origin/main` so the PR opens with the latest base commits already
718
- integrated. The Epic branch may be behind `main` if other PRs merged
719
- during the wave loop; without this step, the Epic→`main` PR opens
720
- "behind base" and (with branch-protection's `up-to-date branch` rule)
721
- stalls at the merge gate.
561
+ Before the close-tail emit, sync the Epic branch with `origin/main` so the
562
+ PR opens with the latest base commits already integrated (a stale base
563
+ stalls at branch-protection's `up-to-date branch` rule):
722
564
 
723
565
  ```bash
724
566
  git checkout epic/<epicId>
725
567
  node .agents/scripts/sync-branch-from-base.js \
726
568
  --branch epic/<epicId> --base main
569
+ git push origin epic/<epicId>
727
570
  ```
728
571
 
729
- Outcomes:
730
-
731
- - **`fast-forward` / `merge-commit` / `noop-already-current`** → push
732
- the resulting tip and continue to Phase 7.1: `git push origin epic/<epicId>`.
733
- - **`conflict`** → resolve in the Epic checkout (`git merge --no-edit
734
- origin/main`, fix conflicts, `git commit --no-edit`), then re-run the
735
- sync command. Once it exits 0, continue. Operator-recoverable; not an
736
- agent loop.
737
- - **`fetch-failed`** → re-check network / `origin` access and re-run.
738
-
739
- This is a workflow-level step (operator-driven), not part of the
740
- close-tail listener chain. The sync runs from the main checkout so
741
- the resulting tip lands on `epic/<epicId>` before Phase 7.1 fires
742
- the bus-driven close-tail.
743
-
744
- ### 7.1 — Fire the close-tail emit
572
+ Then fire the close-tail emit:
745
573
 
746
574
  ```bash
747
575
  node .agents/scripts/lifecycle-emit.js --epic <epicId> --event epic.close.end
748
576
  ```
749
577
 
750
- Emits `epic.close.end` onto the lifecycle bus. **Every close-time
751
- responsibility below runs inside the listener chain the operator
752
- shells nothing manually. The `Finalizer` listener (Story #2894 —
753
- bus-owned finalize) composes three helpers under
754
- `.agents/scripts/lib/orchestration/finalize/` and emits the canonical
755
- chain.** Treat this section as a runtime contract `/deliver`
756
- just fires the emit and reads the resulting ledger.
757
-
758
- 1. **Acceptance-table reconciliation — bus-driven.** The
759
- `AcceptanceReconciler` listener invokes
760
- [`acceptance-spec-reconciler.js`](../../scripts/acceptance-spec-reconciler.js)
761
- to diff the AC IDs declared in the Epic body's `## Acceptance Table`
762
- section against `@ac-*` / `@pending` tags in `tests/features/**`,
763
- recording each row's verification outcome
764
- (`satisfied` / `pending` / `missing`) into the table's Disposition
765
- column via a section-scoped upsert of the Epic body. A
766
- non-OK reconciliation throws (per
767
- [`rules/orchestration-error-handling.md`](../../rules/orchestration-error-handling.md)),
768
- aborting finalize **before** any PR is opened — so the Epic stays
769
- unfinalized until the AC coverage gap is fixed. The reconciler returns
770
- `status: 'waived'` without scanning features when the Epic carries
771
- `acceptance::n-a`, and defends against direct CLI invocation by
772
- refusing to run when the body has no `## Acceptance Table` section
773
- and no waiver is set (the
774
- start gate in Phase 1 would normally catch that first).
775
- 2. **PR open — bus-driven (Story #2894).** On
776
- `acceptance.reconcile.ok` the `Finalizer` listener invokes
777
- [`openOrLocatePr`](../../scripts/lib/orchestration/finalize/open-or-locate-pr.js)
778
- with `{ epicId, headBranch: 'epic/<id>', baseBranch: 'main' }`.
779
- The helper probes for an existing open PR on the head branch
780
- first (idempotent locate path — a re-run of `/deliver`
781
- on the same branch short-circuits without opening a duplicate)
782
- and only opens a new PR when none exists. The listener then
783
- emits `pr.created` → `epic.finalize.end` and **stops** (Story
784
- #3367). It does **not** emit `epic.merge.ready`: that event is
785
- the sole `AutomergeArmer` trigger, and emitting it from finalize
786
- would cascade `epic.close.end` synchronously through the arm →
787
- `MergeWatcher` → `Cleaner` → `BranchCleaner` reap, deleting the
788
- `epic/<id>` branch before the PR merged and bypassing the
789
- `AutomergePredicate` disqualification gate. The auto-merge arm is
790
- driven later from the gated watch path (`pr.created` → `Watcher`
791
- → `epic.watch.end` → `AutomergePredicate` → `epic.merge.ready` →
792
- `AutomergeArmer`) re-entered in Phase 8.5. The merge-lockout rule
793
- in [`check-lifecycle-lint.js`](../../scripts/check-lifecycle-lint.js)
794
- keeps `gh pr merge --auto --squash --delete-branch` confined to
795
- `AutomergeArmer` — Phase 7 never shells the merge command.
796
- 3. **Hand-off — bus-driven (Story #2894).** After `openOrLocatePr`
797
- returns, the `Finalizer` chains
798
- [`postHandoffComment`](../../scripts/lib/orchestration/finalize/post-handoff-comment.js)
799
- to upsert the canonical `epic-handoff` structured comment naming
800
- the PR URL. The helper is idempotent — the handoff comment is
801
- edited in place via `upsertStructuredComment` rather than
802
- appending a duplicate. There is **no planning-ticket close sweep**
803
- (Story #4324): the planning artifacts live as sections of the Epic
804
- body itself, so there are no context tickets to close and nothing
805
- blocks the Epic's `Closes #<id>` auto-close path.
806
-
807
- Branch cleanup is out-of-band (Phase 9 reaps local refs after merge; the
808
- rare "scrap and reset" case for an unmerged Epic is handled manually).
578
+ `epic.close.end` drives the bus-owned `Finalizer` chain: acceptance-table
579
+ reconciliation (throws and aborts finalize on a coverage gap, `waived` under
580
+ `acceptance::n-a`), idempotent PR open/locate against `main`, and the
581
+ `epic-handoff` comment naming the PR URL. The chain emits `pr.created` →
582
+ `epic.finalize.end` and **stops** — it never emits `epic.merge.ready` (the
583
+ auto-merge arm is driven later from the Phase 8.5 gated watch path). The
584
+ operator shells nothing beyond the sync and the single emit.
585
+
586
+ See
587
+ [`deliver-epic-reference.md` § Phase 7 — Finalize](deliver-epic-reference.md#phase-7--finalize-close-tail-listener-chain)
588
+ for the branch-sync outcome table (conflict / fetch-failed recovery) and the
589
+ full three-step listener contract (why finalize must not emit
590
+ `epic.merge.ready`, the merge-lockout lint rule, the no planning-ticket close
591
+ sweep).
809
592
 
810
593
  ---
811
594
 
@@ -828,43 +611,13 @@ node <agentRoot>/scripts/pr-watch-with-update.js --pr <prNumber>
828
611
  calls per session and `--poll-interval-ms MS` (default 10000) to
829
612
  override the polling cadence.
830
613
 
831
- Exit 0 → proceed to Phase 8.5. Non-zero → remediate (below) and re-run
832
- the helper. Auto-merge stays armed across retries; the
833
- `epic.automerge.start` emit in Phase 8.5 re-runs the `AutomergeArmer`
834
- listener, which re-checks `mergeStateStatus` before firing merge, so a
835
- second BEHIND that arrives between the helper exiting clean and Phase
836
- 8.5 starting is also caught.
837
-
838
- ### 8.1 Remediation
839
-
840
- For each failed required check: fetch the log
841
- (`gh run view <runId> --log-failed`), classify and fix:
842
-
843
- - **lint / format** → `npm run lint` + `npx biome check --apply` (or
844
- `format --write`); commit, push.
845
- - **maintainability / crap baseline drift** → re-run the ratcheted
846
- script. Refresh the baseline only when drift is justified by the
847
- diff; otherwise fix at source.
848
- - **test failure** → reproduce with `npm test`, fix source or test.
849
- - **coverage threshold** → add tests (preferred); refresh baseline only
850
- when the diff demonstrably can't be covered.
851
- - **anything else** → read the log, fix at source.
852
-
853
- Push to `epic/<epicId>` and re-run
854
- `node <agentRoot>/scripts/pr-watch-with-update.js --pr <prNumber>`.
614
+ Exit 0 → proceed to Phase 8.5. Non-zero → remediate and re-run the helper
615
+ (push fixes to `epic/<epicId>`; auto-merge stays armed across retries).
855
616
 
856
- ### 8.2 When to halt
857
-
858
- Three consecutive iterations on the same failure class without
859
- convergence friction comment, flip to `agent::blocked`, park. Unknown
860
- failure class on first encounter → attempt source-level fix; log
861
- friction if diagnosis takes more than one round.
862
-
863
- ### 8.3 Hard prohibitions
864
-
865
- **Never** `gh pr merge` from Phase 8 (Phase 8.5 is the only merge
866
- site). **Never** force-push to `main`. **Never** push empty commits or
867
- refresh baselines to dodge a red check.
617
+ > **Remediation + hard prohibitions.** For the per-check fix table (lint,
618
+ > baseline drift, test, coverage), the three-strikes halt rule, and the
619
+ > never-merge / never-force-push / never-dodge prohibitions, see
620
+ > [`deliver-epic-reference.md` § Phase 8 Watch-and-iterate remediation](deliver-epic-reference.md#phase-8--watch-and-iterate-remediation).
868
621
 
869
622
  ---
870
623
 
@@ -878,57 +631,29 @@ node .agents/scripts/lifecycle-emit.js --epic <epicId> \
878
631
  --event epic.automerge.start --pr-url <prUrl>
879
632
  ```
880
633
 
881
- `AutomergePredicate` subscribes to `epic.automerge.start` (Story #3901
882
- before that this event had **zero** subscribers and the entire Phase 8.5
883
- gate was a dead wire). It evaluates the structured-signal verdict and
884
- emits `epic.merge.ready` on a clean verdict or `epic.merge.blocked`
885
- otherwise. The CI-freshness gate is skipped on this event because Phase 8
886
- already polled every required check to green — `epic.automerge.start`
887
- carries `prUrl` but no `checkOutcomes`.
888
-
889
- The `AutomergeArmer` listener subscribes to the downstream
890
- `epic.merge.ready` outcome and fires `gh pr merge --auto --squash
891
- --delete-branch` only when `clean: true`. `clean: true` requires
892
- **all** of:
893
-
894
- - `state.manualInterventions[]` is empty;
895
- - every wave's `status === "complete"`;
896
- - no story envelope carries a `blockerCommentId` or non-`done` status;
897
- - code-review reports `0` 🔴 + `0` 🟠 findings;
898
- - the retro's machine-readable `automerge-verdict` trailer reports
899
- `cleanSprint: true` (Story #3901 — the predicate reads the parsed JSON
900
- trailer `retro-run.js` writes into the retro body, **not** an emoji
901
- string-match on the human-facing "🟢 Clean sprint" prose).
902
-
903
- When clean, the listener fires `gh pr merge --squash --delete-branch`.
904
- Otherwise the listener records disqualifying reasons via
905
- `epic.merge.blocked` and exits without merging — operator merges
906
- manually.
907
-
908
- Close the phase wrapper by emitting `epic.automerge.end` (records the
909
- arm outcome on the ledger; `merged: true` once GitHub completes the
910
- squash, `merged: false` with a reason for predicate-blocked or
911
- armed-but-pending):
634
+ `AutomergePredicate` evaluates the structured-signal verdict and emits
635
+ `epic.merge.ready` on a clean verdict or `epic.merge.blocked` otherwise. The
636
+ downstream `AutomergeArmer` fires `gh pr merge --auto --squash
637
+ --delete-branch` **only** when `clean: true` (empty manual-interventions,
638
+ every wave complete, no story blocked, `0` 🔴 + `0` 🟠 review findings, and
639
+ the retro's `automerge-verdict` trailer reports `cleanSprint: true`).
640
+ Otherwise it records disqualifying reasons and exits without merging — the
641
+ operator merges manually.
642
+
643
+ Close the phase wrapper by emitting `epic.automerge.end` (records the arm
644
+ outcome on the ledger; `merged: true` once GitHub completes the squash,
645
+ `merged: false` with a reason otherwise):
912
646
 
913
647
  ```bash
914
648
  node .agents/scripts/lifecycle-emit.js --epic <epicId> \
915
649
  --event epic.automerge.end --pr-url <prUrl> --merged <true|false>
916
650
  ```
917
651
 
918
- ### Recording manual interventions
919
-
920
- Whenever you step outside the happy path during a delivery, record it
921
- (each entry disqualifies auto-merge):
922
-
923
- ```bash
924
- node .agents/scripts/epic-deliver-note-intervention.js \
925
- --epic <epicId> --reason "<one-line description>"
926
- ```
927
-
928
- Triggers: `AskUserQuestion` mid-run; `git restore`/`reset` against the
929
- tree; child-reported `--no-ff` recovery, stash dance, or out-of-band
930
- merge surgery; child closes via `--skipValidation`; force-pushing or
931
- empty-committing to dodge CI diagnosis.
652
+ > **Predicate wiring + manual-intervention recording.** For the full
653
+ > `clean: true` predicate contract (Story #3901 — the trailer read, the
654
+ > CI-freshness skip) and the `epic-deliver-note-intervention.js` command +
655
+ > its trigger list, see
656
+ > [`deliver-epic-reference.md` § Phase 8.5 — Auto-merge predicate detail](deliver-epic-reference.md#phase-85--auto-merge-predicate-detail).
932
657
 
933
658
  ---
934
659
 
@@ -936,54 +661,24 @@ empty-committing to dodge CI diagnosis.
936
661
 
937
662
  Phase 9 runs **automatically** inside the lifecycle bus once auto-merge
938
663
  arms: the `BranchCleaner` listener subscribes to `epic.cleanup.start`
939
- and reaps local refs before `Cleaner` archives the `temp/epic-<id>/`
940
- tree. No operator step is required on the auto-merge path.
941
-
942
- What gets reaped (in order, all in-process):
943
-
944
- 1. The main checkout is switched off `epic/<id>` to `baseBranch` when
945
- needed (otherwise `git branch -D epic/<id>` is refused).
946
- 2. Every `story-<id>` listed in the `epic-run-state` checkpoint, plus
947
- `epic/<id>`. Attached worktrees are removed with the standard
948
- `git worktree remove` → `--force` → filesystem-rm fallback (the
949
- last step covers Windows file-locks).
950
- 3. `git remote prune <remote>` drops stale `<remote>/...` tracking
951
- refs left behind by `gh pr merge --delete-branch`.
952
- 4. The `wt-branch` scratch ref left by `story-close.js`'s internal
953
- merge worktree is deleted when no worktree still points at it.
954
-
955
- Per-branch failures aggregate into the listener's classification log
956
- (`reaped` / `failed` / `no-state` / `skipped-duplicate`) and are
957
- visible in `temp/epic-<id>/lifecycle.ndjson`. They do not block the
958
- rest of cleanup.
664
+ and reaps local refs (the `epic/<id>` branch, every `story-<id>` in the
665
+ checkpoint, attached worktrees, and stale tracking refs) before `Cleaner`
666
+ archives the `temp/epic-<id>/` tree. No operator step is required on the
667
+ auto-merge path.
959
668
 
960
669
  For out-of-band cleanup re-entry (resume after a crash, or operator
961
- override), fire `epic.merge.armed` via the lifecycle-emit helper:
670
+ override), fire `epic.merge.armed`:
962
671
 
963
672
  ```bash
964
673
  node .agents/scripts/lifecycle-emit.js --epic <epicId> \
965
674
  --event epic.merge.armed --pr-url <prUrl>
966
675
  ```
967
676
 
968
- If Phase 8.5 fell back to the operator-merges-button path (`gh pr
969
- merge --auto` was declined), the `epic.merge.armed` event never fires
970
- inside this run and Phase 9 will not run automatically. After the
971
- operator merges the PR, `epic/<epicId>` and each `story-<id>` ref can
972
- be reaped manually:
973
-
974
- ```bash
975
- git checkout main
976
- git pull --ff-only origin main
977
- git branch -D epic/<epicId>
978
- git branch -D story-<id1> story-<id2> ...
979
- git remote prune origin
980
- ```
981
-
982
- Note that `git-cleanup.js` alone will not catch `story-<id>` refs in
983
- this case because the epic PR squash-merges break the `git branch
984
- --merged main` signal and the stories never had their own PRs. Wiring
985
- a CLI surface that drives the BranchCleaner listener for this
986
- fallback is tracked as follow-up to Story #2398.
677
+ > **Reap order + operator-merges fallback.** For the full in-process reap
678
+ > order, the per-branch classification log, and the manual reap sequence
679
+ > when Phase 8.5 fell back to the operator-merges-button path (auto-merge
680
+ > declined, `epic.merge.armed` never fired), see
681
+ > [`deliver-epic-reference.md` § Phase 9 — Local branch cleanup detail](deliver-epic-reference.md#phase-9--local-branch-cleanup-detail).
987
682
 
988
683
  ---
989
684
 
@@ -999,8 +694,8 @@ the `epic-run-progress` structured comment.
999
694
  ## Constraints
1000
695
 
1001
696
  - **Never** merge `epic/<epicId>` to `main` outside Phase 8.5.
1002
- - **Never** dispatch more than one wave at a time; concurrency lives
1003
- inside a single wave's fan-out, capped at `concurrencyCap`.
697
+ - **Never** dispatch more than the global `concurrencyCap` allows;
698
+ concurrency lives inside the ready-set fan-out.
1004
699
  - **Never** flip Story-level labels from this skill; **never** invoke
1005
700
  `helpers/epic-deliver-story` yourself (children run it via Agent fan-out,
1006
701
  even for single-Story waves); **never** spawn a subprocess for dispatch.