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.
- package/.agents/README.md +41 -0
- package/.agents/docs/SDLC.md +4 -2
- package/.agents/docs/agentrc-reference.json +10 -2
- package/.agents/docs/execution-reference.md +52 -0
- package/.agents/instructions.md +76 -38
- package/.agents/schemas/agentrc.schema.json +31 -3
- package/.agents/schemas/qa-ledger.schema.json +2 -2
- package/.agents/scripts/epic-deliver-prepare.js +41 -1
- package/.agents/scripts/lib/config/explain.js +4 -1
- package/.agents/scripts/lib/config-settings-schema.js +25 -1
- package/.agents/scripts/lib/epic-body-sections.js +88 -0
- package/.agents/scripts/lib/findings/promote-finding.js +3 -3
- package/.agents/scripts/lib/findings/severity.js +5 -6
- package/.agents/scripts/lib/orchestration/check-baselines/phases/evaluate.js +65 -2
- package/.agents/scripts/lib/orchestration/context-hydration-engine.js +96 -11
- package/.agents/scripts/lib/orchestration/doc-reader.js +29 -0
- package/.agents/scripts/lib/orchestration/docs-digest.js +134 -0
- package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/refresh-commit.js +15 -1
- package/.agents/scripts/lib/qa/console-allowlist.js +5 -4
- package/.agents/scripts/lib/qa/resolve-qa-contract.js +144 -8
- package/.agents/skills/core/epic-plan-consolidate/SKILL.md +7 -5
- package/.agents/skills/core/epic-plan-consolidate/examples.md +51 -0
- package/.agents/skills/core/epic-plan-decompose-author/SKILL.md +4 -22
- package/.agents/skills/core/epic-plan-decompose-author/examples.md +47 -0
- package/.agents/skills/core/epic-plan-premortem/SKILL.md +9 -8
- package/.agents/skills/core/epic-plan-premortem/examples.md +53 -0
- package/.agents/skills/core/epic-plan-spec-author/SKILL.md +21 -81
- package/.agents/skills/core/epic-plan-spec-author/examples.md +91 -0
- package/.agents/skills/skills.index.json +3 -3
- package/.agents/skills/stack/qa/qa-explore-driving/SKILL.md +52 -38
- package/.agents/workflows/helpers/deliver-epic-reference.md +514 -0
- package/.agents/workflows/helpers/deliver-epic.md +164 -469
- package/.agents/workflows/helpers/epic-deliver-story.md +35 -11
- package/.agents/workflows/helpers/plan-epic-reference.md +136 -0
- package/.agents/workflows/helpers/plan-epic.md +56 -186
- package/.agents/workflows/helpers/plan-story.md +31 -61
- package/.agents/workflows/helpers/qa-run-scenario.md +194 -0
- package/.agents/workflows/helpers/scope-triage-gate.md +97 -0
- package/.agents/workflows/helpers/single-story-deliver-reference.md +423 -0
- package/.agents/workflows/helpers/single-story-deliver.md +128 -392
- package/.agents/workflows/qa-explore.md +63 -32
- package/.agents/workflows/qa-run.md +293 -130
- package/docs/CHANGELOG.md +14 -0
- package/package.json +1 -1
- 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,
|
|
69
|
-
with `--skip-epic-audit`.
|
|
70
|
-
|
|
71
|
-
|
|
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).
|
|
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.).
|
|
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.
|
|
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
|
|
183
|
-
>
|
|
184
|
-
>
|
|
185
|
-
>
|
|
186
|
-
>
|
|
187
|
-
>
|
|
188
|
-
>
|
|
189
|
-
>
|
|
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
|
-
> **
|
|
288
|
-
>
|
|
289
|
-
>
|
|
290
|
-
>
|
|
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
|
|
310
|
-
>
|
|
311
|
-
>
|
|
312
|
-
>
|
|
313
|
-
>
|
|
314
|
-
>
|
|
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)
|
|
349
|
-
suppress per-Story chat relay
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
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.**
|
|
401
|
-
>
|
|
402
|
-
>
|
|
403
|
-
>
|
|
404
|
-
>
|
|
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
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
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
|
-
|
|
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
|
|
603
|
-
|
|
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`.
|
|
613
|
-
|
|
614
|
-
the
|
|
615
|
-
|
|
616
|
-
|
|
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
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
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
|
-
|
|
715
|
-
|
|
716
|
-
|
|
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
|
-
|
|
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
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
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
|
|
832
|
-
|
|
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
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
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`
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
`epic.
|
|
891
|
-
|
|
892
|
-
|
|
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
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
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
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
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
|
|
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
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
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
|
|
1003
|
-
inside
|
|
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.
|