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
@@ -133,11 +133,15 @@ in-process (retrying the install command when
133
133
  `dependenciesInstalled === 'false'`, default `npm ci`) and rendered the
134
134
  initial snapshot (`phase: "init"`). There is no separate command to run.
135
135
 
136
- The Step 0 result envelope carries a `prepare.renderedBody` field — the
137
- markdown body for the initial Story-phase table. **Relay it verbatim to
138
- chat** so operators see the initial progress block before the first commit
139
- lands. Do the same after every transition in Step 1 / Step 3 (the body is
140
- the Story-level rollup the parent `/deliver` aggregator reads).
136
+ Step 0's init run already upserted the initial `story-run-progress`
137
+ snapshot (`phase: "init"`) as a structured comment on the Story — that
138
+ comment, refreshed by `story-phase.js` at each transition, is the
139
+ authoritative Story-level rollup the parent `/deliver` aggregator reads.
140
+ You do **not** relay `prepare.renderedBody` verbatim to chat. Instead,
141
+ relay **one line per phase transition** (e.g. `Story #<id>: init →
142
+ implementing`), and do the same after every transition in Step 1 / Step 3.
143
+ The snapshot CLI carries the full body; the chat line is a terse progress
144
+ delta, not a body dump.
141
145
 
142
146
  ---
143
147
 
@@ -162,6 +166,20 @@ Run a single Story-implementation phase against the inline `acceptance[]`
162
166
  `context.verify`). Treat the acceptance items as the contract and
163
167
  the verify items as the canonical at-keyboard checks.
164
168
 
169
+ **Docs context — read the digest, not the full set.** Do **not**
170
+ re-read every file in `project.docsContextFiles`. The parent prompt
171
+ passes a `docsDigestPath` (the per-Epic docs digest at
172
+ `temp/epic-<epicId>/docs-digest.md`, written by
173
+ `epic-deliver-prepare.js`). Read that digest — a compact per-file
174
+ outline (path, size, heading outline with line numbers, first
175
+ paragraph under each `##`) — to decide which docs bear on this Story,
176
+ then **pull the full file on demand** (jump to the section at the line
177
+ number the digest names) only when relevant. When `docsDigestPath` is
178
+ null (the project configured no `docsContextFiles`), there is no
179
+ digest and no per-Story docs mandate — read a full doc only if the
180
+ Story's own context points you at one. See
181
+ [`.agents/instructions.md` § 3](../../instructions.md).
182
+
165
183
  3. Implement the work as one or more commits on `story-<storyId>`.
166
184
  Author commits directly with the project's editor / `git commit`,
167
185
  following
@@ -240,10 +258,14 @@ partially-implemented Story picks up from whatever commits are already
240
258
  on `story-<storyId>`; the agent inspects `git log` to decide what work
241
259
  remains.
242
260
 
243
- After each `story-phase.js` call, **relay the envelope's
244
- `renderedBody` to chat** as the Story's progress update. Skip chat
245
- relay only when running in a non-interactive sub-agent context where
246
- the parent will aggregate.
261
+ After each `story-phase.js` call, relay **one line naming the phase
262
+ transition** (e.g. `Story #<id>: implementing → closing`) as the Story's
263
+ progress update not the envelope's `renderedBody` verbatim. The
264
+ `story-phase.js` CLI has already upserted the full body into the
265
+ `story-run-progress` snapshot; that comment is the authoritative rollup
266
+ the parent `/deliver` aggregator reads. Skip chat relay entirely when
267
+ running in a non-interactive sub-agent context where the parent will
268
+ aggregate.
247
269
 
248
270
  > Rebase pauses on conflicts → follow
249
271
  > [`_merge-conflict-template.md`](_merge-conflict-template.md).
@@ -338,8 +360,10 @@ regardless of the reap status.
338
360
  `story-phase.js` (typically the `phase: 'done'` snapshot at close,
339
361
  or the `phase: 'blocked'` snapshot on a blocker). The parent
340
362
  `/deliver` may inline a digest of this in its wave-level Notable
341
- section. When run interactively (no parent), omit it — the chat already
342
- has the latest body relayed during Step 1 / Step 3.
363
+ section. When run interactively (no parent), omit it — the authoritative
364
+ body lives in the `story-run-progress` snapshot the phase CLI upserted,
365
+ and the chat already carries the per-transition progress lines from
366
+ Step 1 / Step 3.
343
367
 
344
368
  ---
345
369
 
@@ -0,0 +1,136 @@
1
+ ---
2
+ description: >-
3
+ Reference companion to plan-epic.md — the recovery procedures, --resume
4
+ mechanics, troubleshooting, and background rationale blocks moved out of the
5
+ runtime core so every /plan run ingests only the phase flow. Read on demand
6
+ from the trigger-point pointers in plan-epic.md.
7
+ caller: plan-epic.md
8
+ ---
9
+
10
+ # helpers/plan-epic-reference — Epic-planning reference & recovery
11
+
12
+ > **Not a slash command.** This file lives in `helpers/` and is a
13
+ > path-included reference module (not projected into the plugin command
14
+ > tree). [`plan-epic.md`](plan-epic.md) is the runtime core — the phase
15
+ > flow, commands, and gate contracts. This file holds the secondary
16
+ > material a run needs only when it hits an edge (a recovery path, a
17
+ > troubleshooting symptom) or wants the design rationale behind a phase.
18
+ > Each section below is reached from a one-line pointer at its trigger
19
+ > point in the core.
20
+
21
+ ## Phase 7 — background rationale
22
+
23
+ The Phase 7 (Tech Spec & Acceptance Spec) core carries only the commands and
24
+ the gate contract. The design rationale for the phase's guards and managed
25
+ sections lives here.
26
+
27
+ ### Epic-lease preflight (workflow guard)
28
+
29
+ Before any Phase 7 mutation, `epic-plan-spec.js` acquires the Epic-lease via
30
+ the assignee-as-lease primitive (`lib/orchestration/ticket-lease.js`, wired
31
+ through `lib/orchestration/epic-plan-lease-guard.js`). The lease rides the
32
+ Epic's single assignee: the operator (`github.operatorHandle` in
33
+ `.agentrc.json`) claims the Epic for the duration of the plan. The guard
34
+ **fails closed**: `/plan` emits no `story.heartbeat` during its run
35
+ (heartbeats are a delivery-time signal), so there is no live-heartbeat source
36
+ to judge a concurrent plan's liveness from. Any **foreign assignee** is
37
+ therefore treated as a live claim — the persist half **exits non-zero and
38
+ names the current owner**, so two `/plan` runs cannot drive the same Epic
39
+ concurrently. Pass **`--steal`** to forcibly transfer a foreign claim once you
40
+ have confirmed the other run is dead. An **unassigned** Epic, or one **already
41
+ held by this operator**, is taken (or re-affirmed) silently. The lease is
42
+ **released after Phase 8** (decompose) completes.
43
+
44
+ ### Idempotent managed sections
45
+
46
+ The persist half is section-scoped and keyed on the Epic body: a re-run that
47
+ finds the requested sections already present
48
+ (`<!-- mandrel:tech-spec:start/end -->` /
49
+ `<!-- mandrel:acceptance-table:start/end -->`) short-circuits as
50
+ `already-planned` instead of duplicating content. Pass `--force` to overwrite
51
+ the managed sections in place (same Epic issue, refreshed section bodies).
52
+
53
+ ### One planning document
54
+
55
+ A `/plan` Epic run creates exactly **one** issue — the Epic. The planning
56
+ artifacts land as marker-delimited managed sections of the Epic body: the Tech
57
+ Spec (opening with `## Delivery Slicing`) inside
58
+ `<!-- mandrel:tech-spec:start/end -->`, and the Acceptance Spec's AC-ID table
59
+ (headed `## Acceptance Table`) inside
60
+ `<!-- mandrel:acceptance-table:start/end -->`. The PRD artifact class was
61
+ retired (Story #4314) — its one novel section, **User Stories**, lives inline
62
+ in the Epic body as a `## User Stories` section — and Story #4324 retired the
63
+ `context::tech-spec` / `context::acceptance-spec` ticket classes the same way.
64
+ The `## Acceptance Table` section captures the stable-ID acceptance criteria
65
+ table (`| AC ID | Outcome | Feature File | Scenario | Disposition |`) that
66
+ drives close-time reconciliation during `/deliver` Phase 6. Operators may opt
67
+ out for refactor-only or docs-only Epics by applying the `acceptance::n-a`
68
+ label to the Epic ticket — when present, the `epic-plan-spec-author` skill
69
+ skips the Acceptance Table output and the runtime gates (start gate, finalize
70
+ reconciler) honour the waiver — the section need not be authored when the
71
+ waiver is set. See [SDLC § Acceptance Table — the second folded planning
72
+ section](../../docs/SDLC.md#acceptance-table--the-second-folded-planning-section)
73
+ for the full lifecycle.
74
+
75
+ ### Parallel-safe file naming (per-Epic tree)
76
+
77
+ Multiple Epics may be planned or decomposed concurrently. Every temp file
78
+ written in the workflow lives under the per-Epic tree
79
+ (`temp/epic-[Epic_ID]/<artifact>`) — e.g.
80
+ `temp/epic-[Epic_ID]/planner-context.json`,
81
+ `temp/epic-[Epic_ID]/techspec.md`,
82
+ `temp/epic-[Epic_ID]/decomposer-context.json`,
83
+ `temp/epic-[Epic_ID]/tickets.json`. The directory namespace is the isolation
84
+ boundary; basenames inside it are stable. Do **not** reuse bare flat names
85
+ like `temp/techspec.md` or the legacy `temp/<artifact>-epic-<id>.<ext>` shape
86
+ — both have been retired.
87
+
88
+ **Durability.** The per-Epic tree is durable across runs: only the wrapper
89
+ scripts perform intra-phase cleanup of files they wrote in the same invocation
90
+ (see [`lib/plan-phase-cleanup.js`](../../scripts/lib/plan-phase-cleanup.js)).
91
+ Nothing else garbage-collects the tree, so cross-Epic artifacts — retros, perf
92
+ reports, signals, manifests — accumulate until an operator explicitly removes
93
+ them.
94
+
95
+ ## Phase 8 — `--resume` recovery (secondary rate limit)
96
+
97
+ The Phase 8 (Work Breakdown Decomposition) core carries the normal-path and
98
+ `--force` persist commands. The `--resume` recovery path — reached when a
99
+ large decomposition aborts mid-persist — lives here.
100
+
101
+ **Secondary rate limit on large Epics.** For backlogs over ~60 tickets,
102
+ GitHub's secondary rate limit (HTTP 403, body contains "secondary rate limit")
103
+ can trip mid-decomposition after ~80 issue creations. The http-client retries
104
+ automatically with a 30–120s backoff and the decomposer drops `concurrencyCap`
105
+ to 1 for the rest of the run on the first observation. If the run still aborts
106
+ (network drop, exhausted retries, etc.), resume from the partial backlog with:
107
+
108
+ ```bash
109
+ node .agents/scripts/epic-plan-decompose.js --epic [Epic_ID] \
110
+ --tickets temp/epic-[Epic_ID]/tickets.json --resume
111
+ ```
112
+
113
+ `--resume` is idempotent: the reconciler recovers the slug→issue map from
114
+ `temp/epic-[Epic_ID]/[Epic_ID].state.json`, and when that file is missing or
115
+ incomplete it **reseeds the map from live GitHub state** by matching each spec
116
+ slug against the open children of the Epic by title. Slugs that resolve to an
117
+ existing open child diff as Updates/no-ops; only the genuinely-missing children
118
+ are created — the existing tree is never duplicated. To force-throttle from the
119
+ first call on a known-large Epic, set `(framework constant: decomposer
120
+ concurrency): 1` in `.agentrc.json`.
121
+
122
+ ## Troubleshooting
123
+
124
+ - If `epic-plan-spec.js --emit-context` fails, confirm the Epic exists and
125
+ has a body with enough initial context.
126
+ - If `epic-plan-decompose.js` rejects the tickets file, re-read the
127
+ validator's error message — the most common causes are a ticket whose
128
+ `type` is not `story`, a Story missing its inline `acceptance[]` /
129
+ `verify[]` contract, or a dependency cycle in the Story `depends_on`
130
+ graph.
131
+ - If decomposition persisted the tickets but the Epic is not on `agent::ready`,
132
+ you likely called `runDecomposePhase` from `epic-plan-decompose.js`
133
+ directly without completing the persist flow — only the CLI surface
134
+ (`node epic-plan-decompose.js --tickets ...`) drives the full
135
+ reconciler pipeline and flips the lifecycle label. Apply `agent::ready`
136
+ by hand and re-run via the CLI next time.
@@ -92,54 +92,25 @@ Phase 5 (Re-Plan Detection).
92
92
 
93
93
  ## Phase 1.5: Scope Triage (ideation path only)
94
94
 
95
- This phase runs **only** on the ideation path, immediately after Phase 1
96
- produces the sharpened one-pager. It is **skipped entirely** when an Epic ID
97
- argument was supplied (the existing-Epic path jumps straight to Phase 5), and
98
- it is **skipped** when `/plan` was entered via a scope-triage handoff from
99
- [`/plan`](plan-story.md) a handoff is a triage decision already made,
100
- and re-triaging it would re-litigate a settled call (the no-re-triage rule the
101
- skill states once).
102
-
103
- 1. **Activate the scope-triage skill**: Read
104
- [`<agentRoot>/skills/core/scope-triage/SKILL.md`](../../skills/core/scope-triage/SKILL.md)
105
- via the `Read` tool (resolve `<agentRoot>` from `project.paths.agentRoot`
106
- default `.agents`) and apply its rubric to the Phase 1 one-pager. The skill
107
- anchors its sizing judgment **by reference** to
108
- `DELIVERABLE_GRANULARITY_GUIDANCE` / `DEFAULT_TASK_SIZING` in
109
- [`ticket-validator-sizing.js`](../../scripts/lib/orchestration/ticket-validator-sizing.js)
110
- and emits one verdict: `epic` | `story` | `borderline`. The verdict is
111
- host-LLM judgment there is **no `--flag`**, no scorer, no schema, and no
112
- label transition.
113
-
114
- 2. **Fold the verdict into the existing Phase 1 HITL stop** — do **not** add a
115
- second stop:
116
- - **`epic` verdict** → no extra prompt. The Phase 1 confirmation proceeds
117
- as a plain one-pager confirm and the run continues to Phase 2.
118
- - **`story` or `borderline` verdict** → the Phase 1 confirmation prompt
119
- presents a **three-way operator choice**:
120
- - **Recommended: single Story** (with the triage rationale) — persist the
121
- one-pager to a notes file and hand off to
122
- `/plan --from-notes <path>`, identifying the invocation as a
123
- scope-triage handoff so `/plan` skips its own gate. Then **exit
124
- `/plan`**.
125
- - **Plan as Epic anyway** — ignore the recommendation and continue to
126
- Phase 2 with the one-pager.
127
- - **Abort** — stop planning entirely.
128
-
129
- **Never auto-route.** The verdict is advisory; the operator always decides.
130
- Being wrong in the `epic` direction is cheap (Phase 8.3 consolidation and
131
- the sizing validator catch an over-planned Story later); the gate exists to
132
- avoid the ceremony tax of pushing a story-sized scope through the full Epic
133
- pipeline.
134
-
135
- > **`--yes` (headless) exception.** "Never auto-route" is the interactive
136
- > contract. Under `--yes` the operator has *pre-authorized* the
137
- > recommendation: the three-way choice resolves to its **Recommended**
138
- > branch deterministically — `single Story` hands off to
139
- > `/plan --from-notes <path>` (carrying `--yes` so the receiving story
140
- > path also auto-proceeds), and an `epic` verdict simply continues to
141
- > Phase 2. No operator wait. This is the only sanctioned auto-route, and it
142
- > exists solely to make `/plan` driveable headlessly.
95
+ This phase runs the shared scope-triage gate over the Phase 1 one-pager. The
96
+ gate mechanics verdict meanings, the three-way operator choice, the `--yes`
97
+ resolution, and the no-re-triage rule live in the single-homed fragment
98
+ [`scope-triage-gate.md`](scope-triage-gate.md); read it and follow it. This
99
+ phase supplies only its path-specific firing conditions:
100
+
101
+ - **When it runs**: only on the ideation path, immediately after Phase 1
102
+ produces the sharpened one-pager. The rubric is applied to the **Phase 1
103
+ one-pager**, and the verdict folds into the **existing Phase 1 HITL stop**
104
+ (do **not** add a second stop).
105
+ - **When it is skipped**: entirely, when an Epic ID argument was supplied (the
106
+ existing-Epic path jumps straight to Phase 5); and when `/plan` was entered
107
+ via a scope-triage handoff from [`/plan`](plan-story.md) (the no-re-triage
108
+ rule in the fragment).
109
+ - **Recommended branch on a `story` / `borderline` verdict**: persist the
110
+ one-pager to a notes file and hand off to `/plan --from-notes <path>`,
111
+ identifying the invocation as a scope-triage handoff so `/plan` skips its
112
+ own gate, then **exit `/plan`**. The alternative branches are **plan as Epic
113
+ anyway** (continue to Phase 2) and **abort**.
143
114
 
144
115
  ## Phase 2: Cross-Epic Duplicate Search
145
116
 
@@ -278,36 +249,21 @@ triage on the one-pager before the Epic existed.
278
249
 
279
250
  ### Triage
280
251
 
281
- 1. **Activate the scope-triage skill**: Read
282
- [`<agentRoot>/skills/core/scope-triage/SKILL.md`](../../skills/core/scope-triage/SKILL.md)
283
- via the `Read` tool (resolve `<agentRoot>` from `project.paths.agentRoot` —
284
- default `.agents`) and apply its rubric to the **Epic body**. The skill is
285
- artifact-agnostic — it reads the same against an Epic body as against a
286
- one-pager or a Story draft — and anchors its sizing judgment **by
287
- reference** to `DELIVERABLE_GRANULARITY_GUIDANCE` / `DEFAULT_TASK_SIZING`
288
- in
289
- [`ticket-validator-sizing.js`](../../scripts/lib/orchestration/ticket-validator-sizing.js).
290
- It emits one verdict: `epic` | `story` | `borderline`. The verdict is
291
- host-LLM judgment there is **no `--flag`**, no scorer, no schema, and no
292
- label transition behind this gate. Do **not** restate the skill's rubric or
293
- its sizing thresholds here.
294
-
295
- 2. **`epic` verdict** → proceed silently. No extra prompt, no HITL stop. The
296
- run continues straight to Phase 6. Being wrong in the `epic` direction is
297
- cheap (Phase 8.3 consolidation and the sizing validator catch an
298
- over-planned Story later), so an `epic` verdict never costs the operator a
299
- stop.
300
-
301
- 3. **`story` or `borderline` verdict** → **STOP** and present a **three-way
302
- operator choice**. Never auto-route; the verdict is advisory and the
303
- operator always decides.
304
-
305
- - **Recommended: convert to a standalone Story** (with the triage
306
- rationale) — run the conversion path below.
307
- - **Proceed as Epic anyway** — ignore the recommendation and continue to
308
- Phase 6 with the Epic unchanged.
309
- - **Abort** — stop planning entirely. The Epic is left exactly as it was;
310
- no labels move and nothing is closed.
252
+ Run the shared scope-triage gate over the **Epic body** — the gate mechanics
253
+ (verdict meanings, the three-way operator choice, the `--yes` resolution, and
254
+ the no-re-triage rule) live in [`scope-triage-gate.md`](scope-triage-gate.md);
255
+ read it and follow it. The scope-triage skill is artifact-agnostic, so it
256
+ reads the same against an Epic body as against a one-pager or a Story draft.
257
+ The path-specific bindings for this advisory:
258
+
259
+ - **`epic` verdict** → proceed silently to Phase 6. No extra prompt, no HITL
260
+ stop.
261
+ - **`story` / `borderline` verdict** **STOP** and present the three-way
262
+ choice, whose **Recommended** branch here is **convert to a standalone
263
+ Story** via the conversion path below. The alternative branches are
264
+ **proceed as Epic anyway** (continue to Phase 6 with the Epic unchanged) and
265
+ **abort** (the Epic is left exactly as it was; no labels move and nothing is
266
+ closed).
311
267
 
312
268
  ### Conversion path (close-and-recreate)
313
269
 
@@ -453,78 +409,15 @@ for the scoring logic.
453
409
 
454
410
  ## Phase 7: Epic Planning (Tech Spec & Acceptance Spec)
455
411
 
456
- > **Epic-lease preflight (workflow guard).** Before any Phase 7 mutation,
457
- > `epic-plan-spec.js` acquires the Epic-lease via the assignee-as-lease
458
- > primitive (`lib/orchestration/ticket-lease.js`, wired through
459
- > `lib/orchestration/epic-plan-lease-guard.js`). The lease rides the Epic's
460
- > single assignee: the operator (`github.operatorHandle` in `.agentrc.json`)
461
- > claims the Epic for the duration of the plan. The guard **fails closed**:
462
- > `/plan` emits no `story.heartbeat` during its run (heartbeats are a
463
- > delivery-time signal), so there is no live-heartbeat source to judge a
464
- > concurrent plan's liveness from. Any **foreign assignee** is therefore
465
- > treated as a live claim — the persist half **exits non-zero and names the
466
- > current owner**, so two `/plan` runs cannot drive the same Epic
467
- > concurrently. Pass **`--steal`** to forcibly transfer a foreign claim once
468
- > you have confirmed the other run is dead. An **unassigned** Epic, or one
469
- > **already held by this operator**, is taken (or re-affirmed) silently. The
470
- > lease is **released after Phase 8** (decompose) completes; see the Phase 8
471
- > note.
472
-
473
- <!-- separator: adjacent blockquotes -->
474
-
475
- > **Idempotent managed sections.** The persist half is section-scoped and
476
- > keyed on the Epic body: a re-run that finds the requested sections already
477
- > present (`<!-- mandrel:tech-spec:start/end -->` /
478
- > `<!-- mandrel:acceptance-table:start/end -->`) short-circuits as
479
- > `already-planned` instead of duplicating content. Pass `--force` to
480
- > overwrite the managed sections in place (same Epic issue, refreshed
481
- > section bodies).
482
-
483
- <!-- separator: adjacent blockquotes -->
484
-
485
- > **One planning document.** A `/plan` Epic run creates exactly **one**
486
- > issue — the Epic. The planning artifacts land as marker-delimited
487
- > managed sections of the Epic body: the Tech Spec (opening with
488
- > `## Delivery Slicing`) inside `<!-- mandrel:tech-spec:start/end -->`,
489
- > and the Acceptance Spec's AC-ID table (headed `## Acceptance Table`)
490
- > inside `<!-- mandrel:acceptance-table:start/end -->`. The PRD artifact
491
- > class was retired (Story #4314) — its one novel section, **User
492
- > Stories**, lives inline in the Epic body as a `## User Stories`
493
- > section — and Story #4324 retired the `context::tech-spec` /
494
- > `context::acceptance-spec` ticket classes the same way. The
495
- > `## Acceptance Table` section captures the stable-ID acceptance
496
- > criteria table (`| AC ID | Outcome | Feature File | Scenario |
497
- > Disposition |`) that drives close-time reconciliation during
498
- > `/deliver` Phase 6. Operators may opt out for refactor-only or
499
- > docs-only Epics by applying the `acceptance::n-a` label to the Epic
500
- > ticket — when present, the `epic-plan-spec-author` skill skips the
501
- > Acceptance Table output and the runtime gates (start gate, finalize
502
- > reconciler) honour the waiver — the section need not be authored when
503
- > the waiver is set. See [SDLC § Acceptance Table — the second folded
504
- > planning section](../../docs/SDLC.md#acceptance-table--the-second-folded-planning-section)
505
- > for the full lifecycle.
506
-
507
- <!-- separator: adjacent blockquotes -->
508
-
509
- > **Parallel-safe file naming (per-Epic tree).** Multiple Epics may be
510
- > planned or decomposed concurrently. Every temp file written in this
511
- > workflow lives under the per-Epic tree
512
- > (`temp/epic-[Epic_ID]/<artifact>`) — e.g.
513
- > `temp/epic-[Epic_ID]/planner-context.json`,
514
- > `temp/epic-[Epic_ID]/techspec.md`,
515
- > `temp/epic-[Epic_ID]/decomposer-context.json`,
516
- > `temp/epic-[Epic_ID]/tickets.json`. The directory namespace is the
517
- > isolation boundary; basenames inside it are stable. Do **not** reuse
518
- > bare flat names like `temp/techspec.md` or the legacy
519
- > `temp/<artifact>-epic-<id>.<ext>` shape — both have been retired.
520
- >
521
- > **Durability.** The per-Epic tree is durable across runs: only the
522
- > wrapper scripts perform intra-phase cleanup of files they wrote in
523
- > the same invocation (see
524
- > [`lib/plan-phase-cleanup.js`](../../scripts/lib/plan-phase-cleanup.js)).
525
- > Nothing else garbage-collects the tree, so cross-Epic artifacts —
526
- > retros, perf reports, signals, manifests — accumulate until an
527
- > operator explicitly removes them.
412
+ > **Phase-7 guards & managed-section rationale (reference).** This phase runs
413
+ > behind an **Epic-lease preflight** (`--steal` transfers a confirmed-dead
414
+ > claim; the lease releases after Phase 8), writes **idempotent managed
415
+ > sections** (`--force` overwrites them in place), lands **one planning
416
+ > document** (Tech Spec + Acceptance Table folded into the Epic body;
417
+ > `acceptance::n-a` waives the table), and namespaces every temp file under
418
+ > the durable **per-Epic tree** (`temp/epic-[Epic_ID]/…`). The full rationale
419
+ > for each guard is in
420
+ > [`plan-epic-reference.md` § Phase 7 background rationale](plan-epic-reference.md#phase-7--background-rationale).
528
421
 
529
422
  1. **Gather Authoring Context**: Run the spec-phase CLI in context-emission
530
423
  mode to fetch the Epic body, scraped project docs, and the recommended
@@ -883,6 +776,11 @@ node .agents/scripts/epic-plan-spec-validate.js \
883
776
  --tickets temp/epic-[Epic_ID]/tickets.json --force
884
777
  ```
885
778
 
779
+ > On a large Epic (~60+ tickets) the persist can trip GitHub's secondary
780
+ > rate limit mid-run. Resume from the partial backlog with `--resume` — the
781
+ > full recovery contract is in
782
+ > [`plan-epic-reference.md` § Phase 8 — `--resume` recovery (secondary rate limit)](plan-epic-reference.md#phase-8----resume-recovery-secondary-rate-limit).
783
+
886
784
  7. **Cross-Validation**:
887
785
  - Hierarchy completeness, dependency-DAG acyclicity, and `risk::high`
888
786
  labelling are deterministic invariants enforced by
@@ -1075,40 +973,12 @@ condition.
1075
973
  node .agents/scripts/notify.js [Epic_ID] "Planning complete, review tickets. Backlog decomposition complete. Epic is ready for /deliver." --action
1076
974
  ```
1077
975
 
1078
- ## Troubleshooting
1079
-
1080
- - If `epic-plan-spec.js --emit-context` fails, confirm the Epic exists and
1081
- has a body with enough initial context.
1082
- - If `epic-plan-decompose.js` rejects the tickets file, re-read the
1083
- validator's error message the most common causes are a ticket whose
1084
- `type` is not `story`, a Story missing its inline `acceptance[]` /
1085
- `verify[]` contract, or a dependency cycle in the Story `depends_on`
1086
- graph.
1087
- - If decomposition persisted the tickets but the Epic is not on `agent::ready`,
1088
- you likely called `runDecomposePhase` from `epic-plan-decompose.js`
1089
- directly without completing the persist flow — only the CLI surface
1090
- (`node epic-plan-decompose.js --tickets ...`) drives the full
1091
- reconciler pipeline and flips the lifecycle label. Apply `agent::ready`
1092
- by hand and re-run via the CLI next time.
1093
- - **Secondary rate limit on large Epics**: For backlogs over ~60 tickets,
1094
- GitHub's secondary rate limit (HTTP 403, body contains "secondary rate
1095
- limit") can trip mid-decomposition after ~80 issue creations. The
1096
- http-client retries automatically with a 30–120s backoff and the
1097
- decomposer drops `concurrencyCap` to 1 for the rest of the run on the
1098
- first observation. If the run still aborts (network drop, exhausted
1099
- retries, etc.), resume from the partial backlog with:
1100
-
1101
- ```bash
1102
- node .agents/scripts/epic-plan-decompose.js --epic [Epic_ID] \
1103
- --tickets temp/epic-[Epic_ID]/tickets.json --resume
1104
- ```
976
+ ## Troubleshooting & recovery
977
+
978
+ Troubleshooting symptoms (emit-context failures, tickets-file rejections, a
979
+ decomposition that persisted but left the Epic off `agent::ready`) and the
980
+ large-Epic **secondary-rate-limit `--resume`** recovery path live in the
981
+ reference companionthey are consulted only when a run hits the edge:
1105
982
 
1106
- `--resume` is idempotent: the reconciler recovers the slug→issue map
1107
- from `temp/epic-[Epic_ID]/[Epic_ID].state.json`, and when that file is
1108
- missing or incomplete it **reseeds the map from live GitHub state** by
1109
- matching each spec slug against the open children of the Epic by title.
1110
- Slugs that resolve to an existing open child diff as Updates/no-ops;
1111
- only the genuinely-missing children are created — the existing tree is
1112
- never duplicated. To force-throttle from the first call on a known-large
1113
- Epic, set `(framework constant: decomposer concurrency): 1` in
1114
- `.agentrc.json`.
983
+ - [`plan-epic-reference.md` § Troubleshooting](plan-epic-reference.md#troubleshooting)
984
+ - [`plan-epic-reference.md` § Phase 8 `--resume` recovery (secondary rate limit)](plan-epic-reference.md#phase-8----resume-recovery-secondary-rate-limit)
@@ -125,32 +125,31 @@ Write the draft to `temp/single-story-draft.md`.
125
125
 
126
126
  ### Scope-triage escalation gate (symmetric counterpart to `/plan` Phase 1.5)
127
127
 
128
- Once the draft body exists and **only** then, because the seed alone is not
129
- an honest basis for a sizing judgmentrun the
130
- [`core/scope-triage`](../../skills/core/scope-triage/SKILL.md) rubric over the
131
- **drafted Story body** to catch an Epic-sized scope before it is persisted as a
132
- standalone Story. This is the outbound mirror of `/plan` Phase 1.5's
133
- inbound downgrade gate: the two planning entry points route toward each other
134
- instead of each silently accepting wrong-sized work.
135
-
136
- **Skip the gate entirely when `/plan` was entered via a scope-triage
137
- handoff** i.e. from `/plan` Phase 1.5 (the inbound route above) or the
138
- `/plan` Phase 5.5 existing-Epic conversion path. A handoff is a triage
139
- decision already made; re-running the rubric here would re-litigate a settled
140
- call and risk a ping-pong between the two workflows (the skill's no-re-triage
141
- rule).
142
-
143
- Otherwise, activate the skill **by reference** read its `SKILL.md` via the
144
- `Read` tool and apply its rubric; do **not** restate its sizing thresholds or
145
- copy its verdict prose here. The skill anchors its sizing judgment to
146
- `DELIVERABLE_GRANULARITY_GUIDANCE` / `DEFAULT_TASK_SIZING` in
147
- [`ticket-validator-sizing.js`](../../scripts/lib/orchestration/ticket-validator-sizing.js)
148
- and emits one verdict `epic` | `story` | `borderline`. The verdict is
149
- host-LLM judgment: there is **no `--flag`**, no scorer, no schema, and no label
150
- transition behind it. (The `refine` heuristic in `story-plan.js` is unchanged
151
- it is a deterministic seed-length proxy, not a scope-size judgment.) The verdict
152
- folds into the **existing** draft-confirmation HITL stop below — it does **not**
153
- add a second stop.
128
+ This gate runs the shared scope-triage gate over the **drafted Story body** to
129
+ catch an Epic-sized scope before it is persisted as a standalone Story — the
130
+ outbound mirror of `/plan` Phase 1.5's inbound downgrade gate. The gate
131
+ mechanics (verdict meanings, the three-way operator choice, the `--yes`
132
+ resolution, and the no-re-triage rule) live in the single-homed fragment
133
+ [`scope-triage-gate.md`](scope-triage-gate.md); read it and follow it. This
134
+ phase supplies only its path-specific firing conditions:
135
+
136
+ - **When it runs**: only **after the draft body exists** the seed alone is
137
+ not an honest basis for a sizing judgment. The rubric is applied to the
138
+ **drafted Story body**, and the verdict folds into the **existing**
139
+ draft-confirmation HITL stop below (it does **not** add a second stop). (The
140
+ `refine` heuristic in `story-plan.js` is unchanged it is a deterministic
141
+ seed-length proxy, not a scope-size judgment.)
142
+ - **When it is skipped**: entirely, when `/plan` was entered via a scope-triage
143
+ handoff from `/plan` Phase 1.5 (the inbound route above) or the `/plan`
144
+ Phase 5.5 existing-Epic conversion path (the no-re-triage rule in the
145
+ fragment).
146
+ - **Recommended branch on an `epic` verdict**: escalate to `/plan --idea`
147
+ persist the notes/draft to a notes file and hand off to `/plan --idea` (or
148
+ `--from-notes <path>`), identifying the invocation as a scope-triage handoff
149
+ so `/plan` skips its own Phase 1.5 gate, then **abandon the draft and exit
150
+ `/plan`** (no standalone Story is created). The alternative branches are
151
+ **persist as a standalone Story anyway** (proceed to Phase 3 with the draft
152
+ unchanged) and **abort**.
154
153
 
155
154
  ### HITL — operator confirms the draft (verdict folded in)
156
155
 
@@ -158,41 +157,12 @@ Display the draft to the operator and **STOP**. Do not call the persist phase
158
157
  until the operator explicitly confirms the draft. This mirrors the HITL gate
159
158
  `/plan` Phase 3 enforces before opening the Epic Issue. This is the
160
159
  story-path face of `/plan`'s **gate #1** (the ideation one-pager /
161
- scope-triage confirm). The scope-triage verdict folds into this same stop:
162
-
163
- > **`--yes` (headless) auto-proceed.** When `/plan` was invoked with `--yes`,
164
- > this gate does **not** STOP: the draft confirm resolves as **approved** and
165
- > the run proceeds to Phase 3 (persist). An `epic` verdict resolves to its
166
- > **Recommended** branch escalate to `/plan --idea` (carrying `--yes`),
167
- > abandoning the draft — rather than prompting the three-way choice. Display
168
- > the draft and the verdict line for the record, then proceed without
169
- > waiting. See
170
- > [`plan.md` § Headless / non-interactive mode](../plan.md#headless--non-interactive-mode---yes).
171
-
172
- - **`story` verdict (or gate skipped via handoff)** → no extra prompt. The
173
- operator confirms the draft as usual and the run proceeds to Phase 3
174
- (persist).
175
- - **`epic` verdict** (multiple independent capabilities, a plausible
176
- sizing-ceiling breach, or a real dependency structure) → the confirmation
177
- prompt presents a **three-way operator choice**:
178
- - **Recommended: escalate to `/plan --idea`** (with the triage
179
- rationale) — persist the notes/draft to a notes file and hand off to
180
- `/plan --idea` (or `--from-notes <path>`), identifying the invocation
181
- as a **scope-triage handoff** so `/plan` skips its own Phase 1.5 gate
182
- (the skill's no-re-triage rule). Then **abandon the draft and exit
183
- `/plan`** — no standalone Story is created.
184
- - **Persist as a standalone Story anyway** — ignore the recommendation and
185
- proceed to Phase 3 with the draft unchanged. Being wrong in the `epic`
186
- direction is cheap to tolerate: if the operator persists an oversized Story,
187
- the sizing validator and delivery reality push back later. The gate's value
188
- is surfacing the recommendation while the scope is still free to change.
189
- - **Abort** — stop planning entirely. No Issue is created.
190
-
191
- **Never auto-route.** The verdict is advisory; the operator always decides,
192
- and no `agent::*` / label transition happens on either side of the choice.
193
- (**`--yes` exception:** headless mode pre-authorizes the **Recommended**
194
- branch deterministically — see the `--yes` note above — the only sanctioned
195
- auto-route, present solely to make `/plan` driveable without an operator.)
160
+ scope-triage confirm). The scope-triage verdict folds into this same stop, per
161
+ the branch bindings above; on a **`story` verdict (or gate skipped via
162
+ handoff)** there is no extra prompt and the operator confirms the draft as
163
+ usual. The `--yes` headless resolution of this stop is the fragment's see
164
+ [`scope-triage-gate.md` § `--yes` (headless) resolution](scope-triage-gate.md#--yes-headless-resolution)
165
+ and [`plan.md` § Headless / non-interactive mode](../plan.md#headless--non-interactive-mode---yes).
196
166
 
197
167
  ## Phase 3 — Persist (`gh issue create`)
198
168