mandrel 1.81.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 (121) hide show
  1. package/.agents/README.md +46 -5
  2. package/.agents/docs/SDLC.md +97 -82
  3. package/.agents/docs/agentrc-reference.json +10 -2
  4. package/.agents/docs/configuration.md +4 -1
  5. package/.agents/docs/execution-reference.md +52 -0
  6. package/.agents/docs/workflows.md +1 -1
  7. package/.agents/instructions.md +85 -45
  8. package/.agents/personas/architect.md +8 -5
  9. package/.agents/personas/engineer-mobile.md +3 -2
  10. package/.agents/personas/engineer-web.md +3 -2
  11. package/.agents/personas/engineer.md +6 -5
  12. package/.agents/personas/product.md +19 -13
  13. package/.agents/personas/project-manager.md +9 -8
  14. package/.agents/personas/qa-engineer.md +10 -6
  15. package/.agents/personas/refactorer.md +3 -2
  16. package/.agents/personas/technical-writer.md +2 -1
  17. package/.agents/personas/ux-designer.md +2 -2
  18. package/.agents/schemas/agentrc.schema.json +41 -3
  19. package/.agents/schemas/qa-ledger.schema.json +2 -2
  20. package/.agents/scripts/acceptance-spec-reconciler.js +143 -59
  21. package/.agents/scripts/epic-deliver-prepare.js +40 -31
  22. package/.agents/scripts/epic-plan-decompose.js +2 -5
  23. package/.agents/scripts/epic-plan-spec.js +16 -19
  24. package/.agents/scripts/hierarchy-gate.js +11 -11
  25. package/.agents/scripts/lib/ITicketingProvider.js +4 -3
  26. package/.agents/scripts/lib/bdd-runner-detect.js +1 -1
  27. package/.agents/scripts/lib/bdd-scenario-scanner.js +1 -1
  28. package/.agents/scripts/lib/cli-args.js +1 -5
  29. package/.agents/scripts/lib/codebase-snapshot.js +1 -1
  30. package/.agents/scripts/lib/config/explain.js +4 -1
  31. package/.agents/scripts/lib/config/temp-paths.js +1 -4
  32. package/.agents/scripts/lib/config-settings-schema.js +30 -1
  33. package/.agents/scripts/lib/epic-body-sections.js +310 -0
  34. package/.agents/scripts/lib/epic-plan-clarity.js +38 -1
  35. package/.agents/scripts/lib/epic-plan-ideation.js +15 -3
  36. package/.agents/scripts/lib/findings/promote-finding.js +3 -3
  37. package/.agents/scripts/lib/findings/severity.js +5 -6
  38. package/.agents/scripts/lib/label-constants.js +7 -17
  39. package/.agents/scripts/lib/label-taxonomy.js +4 -21
  40. package/.agents/scripts/lib/orchestration/check-baselines/phases/evaluate.js +65 -2
  41. package/.agents/scripts/lib/orchestration/context-hydration-engine.js +105 -9
  42. package/.agents/scripts/lib/orchestration/doc-reader.js +29 -0
  43. package/.agents/scripts/lib/orchestration/docs-digest.js +134 -0
  44. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/context.js +23 -22
  45. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/persist-helpers.js +7 -10
  46. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/planning-artifacts.js +4 -38
  47. package/.agents/scripts/lib/orchestration/epic-plan-lease-guard.js +8 -9
  48. package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/authoring-context.js +11 -5
  49. package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/cli-args.js +26 -5
  50. package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/plan-epic.js +102 -304
  51. package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/prompts.js +32 -29
  52. package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/run-spec-phase.js +19 -20
  53. package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/spec-authoring-grounding.js +1 -1
  54. package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/spec-freshness.js +6 -9
  55. package/.agents/scripts/lib/orchestration/epic-plan-state-store.js +3 -4
  56. package/.agents/scripts/lib/orchestration/epic-runner/phases/build-wave-dag.js +1 -1
  57. package/.agents/scripts/lib/orchestration/epic-runner/phases/snapshot.js +20 -27
  58. package/.agents/scripts/lib/orchestration/lifecycle/listeners/acceptance-reconciler.js +11 -5
  59. package/.agents/scripts/lib/orchestration/lifecycle/listeners/finalizer.js +22 -59
  60. package/.agents/scripts/lib/orchestration/lifecycle/listeners/index.js +1 -1
  61. package/.agents/scripts/lib/orchestration/planning-context-budget.js +1 -1
  62. package/.agents/scripts/lib/orchestration/preflight-cache.js +1 -1
  63. package/.agents/scripts/lib/orchestration/spec-freshness.js +3 -3
  64. package/.agents/scripts/lib/orchestration/spec-section-validator.js +1 -1
  65. package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/refresh-commit.js +15 -1
  66. package/.agents/scripts/lib/orchestration/ticket-validator-sizing.js +122 -1
  67. package/.agents/scripts/lib/orchestration/ticketing/bulk.js +5 -8
  68. package/.agents/scripts/lib/orchestration/ticketing/reads.js +2 -2
  69. package/.agents/scripts/lib/plan-phase-cleanup.js +1 -2
  70. package/.agents/scripts/lib/qa/console-allowlist.js +5 -4
  71. package/.agents/scripts/lib/qa/qa-context-hydrator.js +6 -85
  72. package/.agents/scripts/lib/qa/resolve-qa-contract.js +144 -8
  73. package/.agents/scripts/lib/templates/decomposer-prompts.js +14 -8
  74. package/.agents/scripts/lifecycle-emit.js +1 -1
  75. package/.agents/scripts/lint-label-vocabulary.js +2 -3
  76. package/.agents/scripts/providers/github/mappers.js +0 -3
  77. package/.agents/scripts/providers/github/tickets.js +7 -18
  78. package/.agents/scripts/single-story-init.js +0 -1
  79. package/.agents/scripts/story-init.js +1 -29
  80. package/.agents/skills/core/epic-plan-consolidate/SKILL.md +43 -22
  81. package/.agents/skills/core/epic-plan-consolidate/examples.md +51 -0
  82. package/.agents/skills/core/epic-plan-decompose-author/SKILL.md +27 -40
  83. package/.agents/skills/core/epic-plan-decompose-author/examples.md +47 -0
  84. package/.agents/skills/core/epic-plan-premortem/SKILL.md +15 -13
  85. package/.agents/skills/core/epic-plan-premortem/examples.md +53 -0
  86. package/.agents/skills/core/epic-plan-spec-author/SKILL.md +143 -151
  87. package/.agents/skills/core/epic-plan-spec-author/examples.md +91 -0
  88. package/.agents/skills/core/hydrate-context/SKILL.md +10 -5
  89. package/.agents/skills/core/knowledge-transfer/SKILL.md +3 -2
  90. package/.agents/skills/core/scope-triage/SKILL.md +2 -1
  91. package/.agents/skills/skills.index.json +8 -8
  92. package/.agents/skills/stack/qa/qa-explore-driving/SKILL.md +52 -38
  93. package/.agents/templates/epic-from-idea.md +4 -0
  94. package/.agents/workflows/audit-to-stories.md +2 -2
  95. package/.agents/workflows/helpers/code-review.md +11 -9
  96. package/.agents/workflows/helpers/deliver-epic-reference.md +514 -0
  97. package/.agents/workflows/helpers/deliver-epic.md +173 -490
  98. package/.agents/workflows/helpers/epic-audit.md +11 -8
  99. package/.agents/workflows/helpers/epic-deliver-story.md +45 -27
  100. package/.agents/workflows/helpers/epic-plan-decompose.md +17 -12
  101. package/.agents/workflows/helpers/epic-plan-spec.md +68 -68
  102. package/.agents/workflows/helpers/parallel-tooling.md +2 -1
  103. package/.agents/workflows/helpers/plan-epic-reference.md +136 -0
  104. package/.agents/workflows/helpers/plan-epic.md +141 -256
  105. package/.agents/workflows/helpers/plan-story.md +31 -61
  106. package/.agents/workflows/helpers/qa-run-scenario.md +194 -0
  107. package/.agents/workflows/helpers/scope-triage-gate.md +97 -0
  108. package/.agents/workflows/helpers/single-story-deliver-reference.md +423 -0
  109. package/.agents/workflows/helpers/single-story-deliver.md +129 -393
  110. package/.agents/workflows/helpers/worktree-lifecycle.md +1 -1
  111. package/.agents/workflows/plan.md +8 -8
  112. package/.agents/workflows/qa-assist.md +2 -1
  113. package/.agents/workflows/qa-explore.md +63 -32
  114. package/.agents/workflows/qa-run.md +293 -130
  115. package/docs/CHANGELOG.md +35 -0
  116. package/package.json +1 -1
  117. package/.agents/schemas/qa-finding.schema.json +0 -133
  118. package/.agents/scripts/lib/issue-link-parser.js +0 -74
  119. package/.agents/scripts/lib/orchestration/finalize/close-planning-tickets.js +0 -116
  120. package/.agents/scripts/lib/orchestration/planning-state-manager.js +0 -318
  121. package/.agents/scripts/lib/story-init/hierarchy-tracer.js +0 -57
@@ -66,7 +66,6 @@ export const KNOWN_AXES = Object.freeze([
66
66
  'type',
67
67
  'status',
68
68
  'persona',
69
- 'context',
70
69
  'acceptance',
71
70
  'meta',
72
71
  'planning',
@@ -156,11 +155,11 @@ export function findVocabularyViolations(src, axes = KNOWN_AXES) {
156
155
  // - value is a lowercase identifier (`[a-z][a-z0-9-]*`)
157
156
  //
158
157
  // The whole-body anchor avoids false positives on:
159
- // - `context::acceptance-spec` — canonical label, value contains a
158
+ // - `acceptance::n-a` — canonical label, value contains a
160
159
  // hyphen but is not at axis position.
161
160
  // - `<type>/<slug>` — template placeholders.
162
161
  // - `delivery.maxTokenBudget` — config-key paths with `.`.
163
- // - `context.{prdId,techSpecId}` — JS destructure shapes.
162
+ // - `delivery.{maxTickets}` — JS destructure shapes.
164
163
  // - `agent-protocol.md` — filenames / concept slugs.
165
164
  //
166
165
  // Real drift like `type/epic` matches because the WHOLE body is
@@ -11,8 +11,6 @@
11
11
  * Extracted from `../github.js` in Story #1846 / Task #1859.
12
12
  */
13
13
 
14
- import { parseLinkedIssues } from '../../lib/issue-link-parser.js';
15
-
16
14
  function normalizeLabels(issue) {
17
15
  const raw = issue?.labels;
18
16
  if (!raw) return [];
@@ -50,7 +48,6 @@ export function issueToEpic(issue) {
50
48
  body: issue.body ?? '',
51
49
  labels,
52
50
  labelSet: new Set(labels),
53
- linkedIssues: parseLinkedIssues(issue.body),
54
51
  };
55
52
  }
56
53
 
@@ -112,7 +112,7 @@ export class TicketGateway {
112
112
  this._cache = cache ?? createInlineTicketCache();
113
113
  /**
114
114
  * Per-instance memo of `getTickets(epicId, filters)` results (Story
115
- * #3988). The planning-state-manager fetches the same child list twice
115
+ * #3988). Planning-era healing fetched the same child list twice
116
116
  * per planning pass; without this memo each fetch re-pays the full
117
117
  * search/list round-trip. Invalidated on every write surface.
118
118
  * @type {Map<string, object[]>}
@@ -322,24 +322,13 @@ export class TicketGateway {
322
322
  // Mirror the Epic create path (issues.js:160 → `labels: TYPE_LABELS.EPIC`):
323
323
  // inject TYPE_LABELS.STORY so a spec that omits the labels array cannot
324
324
  // produce an unlabeled, undispatchable Story. Dedupe to avoid duplicates
325
- // when the caller already carries the label.
326
- //
327
- // Context spec tickets (PRD / Tech Spec / Acceptance Spec) are a distinct
328
- // ticket class created through this same factory carrying a `context::*`
329
- // label (and no `type::story`). They MUST NOT be stamped `type::story`:
330
- // doing so makes every `type::story`-counting consumer — the decompose
331
- // open-children guard (`assertNoOpenPlanChildren`), the delivery wave
332
- // builder (`discoverOpenStories`), the plan healthcheck — mis-classify
333
- // them as deliverable Stories. Skip the injection when the caller's labels
334
- // already classify the ticket as context.
325
+ // when the caller already carries the label. (Story #4324 retired the
326
+ // `context::*` ticket classes, so every ticket created through this
327
+ // factory is a Story.)
335
328
  const callerLabels = ticketData.labels ?? [];
336
- const isContextTicket = callerLabels.some(
337
- (l) => typeof l === 'string' && l.startsWith('context::'),
338
- );
339
- const labels =
340
- isContextTicket || callerLabels.includes(TYPE_LABELS.STORY)
341
- ? callerLabels
342
- : [TYPE_LABELS.STORY, ...callerLabels];
329
+ const labels = callerLabels.includes(TYPE_LABELS.STORY)
330
+ ? callerLabels
331
+ : [TYPE_LABELS.STORY, ...callerLabels];
343
332
  const result = await this._gh.api({
344
333
  method: 'POST',
345
334
  endpoint: `/repos/${this.owner}/${this.repo}/issues`,
@@ -23,7 +23,6 @@
23
23
  * 6. Flip the Story to `agent::executing`.
24
24
  *
25
25
  * What this script does NOT do (and why):
26
- * - Skips `traceHierarchy` — no Epic → no PRD/Tech-Spec.
27
26
  * - Skips `validateBlockers` against the body's `Blocked by:` markers —
28
27
  * pre-flight is still the operator's responsibility, but the Epic-scope
29
28
  * blocker chain doesn't fit.
@@ -9,7 +9,6 @@
9
9
  * pipeline stages from `lib/story-init/`:
10
10
  *
11
11
  * 1. context-resolver — fetch the Story + optionally mark as recut.
12
- * 2. hierarchy-tracer — resolve Feature/Epic → PRD / Tech Spec.
13
12
  * 3. blocker-validator — refuse to proceed while dependencies are open.
14
13
  * 4. task-graph-builder — fetch + topologically sort child Tasks.
15
14
  * 5. branch-initializer — materialise the story branch (single-tree
@@ -53,7 +52,6 @@ import {
53
52
  planStoryBranchSeed,
54
53
  } from './lib/story-init/branch-initializer.js';
55
54
  import { resolveContext } from './lib/story-init/context-resolver.js';
56
- import { traceHierarchy } from './lib/story-init/hierarchy-tracer.js';
57
55
  import { transitionStoryToExecuting } from './lib/story-init/state-transitioner.js';
58
56
  import { buildTaskGraph } from './lib/story-init/task-graph-builder.js';
59
57
  import { createPhaseTimer } from './lib/util/phase-timer.js';
@@ -88,8 +86,6 @@ export async function runStoryInit({
88
86
  dryRun: dryRunParam,
89
87
  cwd: cwdParam,
90
88
  recutOf: recutOfParam,
91
- prdId: prdIdParam,
92
- techSpecId: techSpecIdParam,
93
89
  injectedProvider,
94
90
  injectedConfig,
95
91
  } = {}) {
@@ -100,17 +96,10 @@ export async function runStoryInit({
100
96
  dryRun: !!dryRunParam,
101
97
  cwd: cwdParam ?? null,
102
98
  recutOf: recutOfParam ?? null,
103
- prdId: prdIdParam ?? null,
104
- techSpecId: techSpecIdParam ?? null,
105
99
  }
106
100
  : parseSprintArgs();
107
101
  const { storyId, dryRun } = parsed;
108
102
  const recutOf = recutOfParam ?? parsed.recutOf ?? null;
109
- // Story #4253: pre-resolved Epic linkages (from the /deliver fan-out's
110
- // one-time Epic resolution). When both are present, hierarchy-tracer skips
111
- // the per-Story getEpic round-trip; when absent it resolves them itself.
112
- const threadedPrdId = prdIdParam ?? parsed.prdId ?? null;
113
- const threadedTechSpecId = techSpecIdParam ?? parsed.techSpecId ?? null;
114
103
  // Worktree-aware cwd resolution: explicit param > --cwd flag > env > PROJECT_ROOT.
115
104
  const cwd = path.resolve(cwdParam ?? parsed.cwd ?? PROJECT_ROOT);
116
105
 
@@ -155,20 +144,7 @@ export async function runStoryInit({
155
144
  input: { storyId, recutOf, dryRun },
156
145
  });
157
146
 
158
- // Stage 2 — hierarchy. When the /deliver fan-out threaded --prd/--tech-spec
159
- // (both resolved once by the parent), this short-circuits the per-Story
160
- // getEpic. Absent flags fall back to the legacy getEpic resolution.
161
- const { prdId, techSpecId } = await traceHierarchy({
162
- provider,
163
- logger: stageLogger,
164
- input: { epicId, prdId: threadedPrdId, techSpecId: threadedTechSpecId },
165
- });
166
-
167
147
  progress('CONTEXT', `Epic: #${epicId}, Parent: #${parentId ?? 'none'}`);
168
- progress(
169
- 'CONTEXT',
170
- `PRD: #${prdId ?? 'none'}, Tech Spec: #${techSpecId ?? 'none'}`,
171
- );
172
148
 
173
149
  // Stage 3 — blockers.
174
150
  const { openBlockers } = await validateBlockers({
@@ -335,8 +311,6 @@ export async function runStoryInit({
335
311
  installStatus,
336
312
  sortedTasks,
337
313
  parentId,
338
- prdId,
339
- techSpecId,
340
314
  dryRun,
341
315
  recutOf,
342
316
  hierarchy: hierarchyMode,
@@ -473,8 +447,6 @@ function buildStoryInitResult({
473
447
  installStatus,
474
448
  sortedTasks,
475
449
  parentId,
476
- prdId,
477
- techSpecId,
478
450
  dryRun,
479
451
  recutOf,
480
452
  hierarchy,
@@ -506,7 +478,7 @@ function buildStoryInitResult({
506
478
  labels: t.labels,
507
479
  dependencies: t.dependsOn ?? parseBlockedBy(t.body ?? ''),
508
480
  })),
509
- context: { parentId, prdId, techSpecId },
481
+ context: { parentId },
510
482
  dryRun,
511
483
  };
512
484
  }
@@ -6,7 +6,7 @@ description: >-
6
6
  `/plan`, after `epic-plan-decompose-author` writes
7
7
  `temp/epic-<Epic_ID>/tickets.json` and before `epic-plan-decompose.js`
8
8
  validates and persists it. Reconciles the draft against the Tech Spec
9
- "Delivery Slicing" target via scope-preserving operations only.
9
+ "Delivery Slicing" ceiling via scope-preserving operations only.
10
10
  allowed_tools:
11
11
  - Read
12
12
  - Write
@@ -17,24 +17,26 @@ allowed_tools:
17
17
 
18
18
  ## Policy Capsule
19
19
 
20
- - Run only after `epic-plan-decompose-author` has written `temp/epic-<Epic_ID>/tickets.json`; fail loudly if the draft array is missing. Read the PRD / Tech Spec from `temp/epic-<Epic_ID>/decomposer-context.json` (the same envelope the author skill consumed) — never re-fetch from GitHub, and never call the GitHub API from this Skill.
20
+ - Run only after `epic-plan-decompose-author` has written `temp/epic-<Epic_ID>/tickets.json`; fail loudly if the draft array is missing. Read the sectioned Epic body (which carries the folded Tech Spec sections) from `temp/epic-<Epic_ID>/decomposer-context.json` (the same envelope the author skill consumed) — never re-fetch from GitHub, and never call the GitHub API from this Skill.
21
21
  - Emit exactly two artifacts inside `temp/epic-<Epic_ID>/`: the **consolidated** `tickets.json` (overwriting the draft array in place) and a human-readable `consolidation-report.md` (the rationale + before/after diff the operator reviews at the HITL gate). Both MUST exist before returning.
22
22
  - **Scope conservation is the load-bearing invariant.** You are a *critic*, not a second author: you MUST NOT add scope, invent tickets, or drop acceptance criteria. Every acceptance item and every `verify` entry present in the draft MUST survive into the consolidated array (possibly re-homed onto a merged Story). **This is your contract, not a machine guarantee:** there is **no runtime acceptance-union diff** on your output. The only deterministic runtime backstop the validator applies after you run is the standard ticket-structure validation — it does not re-derive the pre-consolidation acceptance/verify union, so a critic that silently dropped an acceptance item would **not** be caught downstream. (The repo's unit test exercises a *pure model* of the merge over an over-fragmented fixture to document the intended invariant; it does not inspect this Skill's actual output.) Conserve scope yourself, deliberately, on every merge.
23
23
  - Your operations are constrained to exactly two shapes: **(1) merge two or more Stories** into one (union their `changes`/`acceptance`/`verify`/`references`, keep one coherent `goal`); **(2) rewire `depends_on`** so the edges still reference surviving sibling-Story slugs. No other mutation is permitted.
24
- - Consume the Tech Spec **"Delivery Slicing"** section as the authoritative target grouping when one is present: cluster the draft's Stories toward the N shippable Stories the Architect proposed. When the section is **absent**, degrade gracefully — apply only the cohesion rules below and leave the rest of the draft shape intact.
24
+ - Consume the Tech Spec **"Delivery Slicing"** section as a **ceiling, not a target**, when one is present: the Architect's proposed N shippable Stories is an *upper bound*, not a floor. Cluster the draft's Stories toward that grouping, and go **below** N when slices form dependent single-consumer chains — a slice whose "Independent? No" row carries no justification (parallelism, risk isolation, or delivery-envelope pressure) folds into its consumer. You **never** split above N. When the section is **absent**, degrade gracefully — apply only the cohesion rules below and leave the rest of the draft shape intact.
25
25
  - Apply the same cohesion heuristic the author skill leads with: **one Story = one coherent change with one reason to exist**, and the **single-consumer merge rule** (a Story whose only consumer is one sibling Story is merged into that sibling). Lead every merge decision with the change's reason, not its file count.
26
26
  - **Missing reason-to-exist check (cohesion gate).** Every draft Story body MUST carry a non-empty `reason_to_exist` meta field (the parseable form of "one Story = one coherent change with one reason to exist", encoded in the `<!-- meta: {"reason_to_exist": "..."} -->` comment and surfaced as `body.reason_to_exist` by `lib/story-body/story-body.js`). Flag any Story whose body carries **no** non-empty reason to exist: a Story that cannot state its single reason in one sentence is a cohesion smell — it is probably two Stories, or two Stories that should be merged into one. Name each flagged Story in the consolidation report so the operator sees the cohesion gap at the HITL diff gate.
27
27
  - After every merge, **rewire `depends_on`**: drop self-edges, collapse edges that now point at the absorbing Story onto itself, and re-point any edge that named a now-deleted slug at its surviving successor. Never leave a `depends_on` referencing a slug absent from the consolidated array — the validator HARD-rejects unknown deps.
28
- - The consolidation report MUST name each operation applied (merged slugs → surviving slug, rewired edges) with a one-line reason, plus a before/after Story-count line, so the operator can approve or reject at the HITL diff gate before the persist call.
28
+ - The consolidation report MUST name each operation applied (merged slugs → surviving slug, rewired edges) with a one-line reason, plus a before/after Story-count line, so the operator can approve or reject at the HITL diff gate before the persist call. When the consolidated count drops **below** the Delivery-Slicing count (a below-ceiling coarsening), the report MUST surface that fact explicitly — e.g. `Below ceiling: N (Delivery Slicing) → M (consolidated)` — and attach a one-line rationale to each below-ceiling merge (which dependent single-consumer slice folded into which consumer, and why it was unjustified), so the operator sees the coarsening at the Phase 8.3 advisory diff.
29
29
 
30
30
  ## Role
31
31
 
32
32
  Senior Project Manager + Orchestrator, acting as a **holistic critic** with
33
- fresh context. This Skill is deliberately *separate* from
34
- `epic-plan-decompose-author` (the generator): a same-pass self-critique is the
35
- weak mode this is built to escape. The generator maps PRD capabilities to
36
- Stories ~1:1; this critic steps back and looks at the *whole* decomposition
37
- against the Tech Spec's intentional grouping before any GitHub write.
33
+ fresh context deliberately *separate* from `epic-plan-decompose-author` (the
34
+ generator) so the pass is a fresh-context review, not a same-pass self-critique.
35
+
36
+ > **Read [`examples.md`](./examples.md) on demand** for the extended rationale:
37
+ > why this critic runs with fresh context, why scope conservation is your
38
+ > contract rather than a runtime guarantee, and how the below-ceiling
39
+ > coarsening case is reported.
38
40
 
39
41
  ## When to use
40
42
 
@@ -54,10 +56,13 @@ reads:
54
56
  - `temp/epic-<Epic_ID>/tickets.json` — the **draft** Story array the
55
57
  `epic-plan-decompose-author` Skill wrote. This is the consolidation input.
56
58
  - `temp/epic-<Epic_ID>/decomposer-context.json` — the authoring envelope
57
- emitted by `epic-plan-decompose.js --emit-context`. Read `prd.body` /
58
- `prd` and `techSpec.body` / `techSpec` from it. The **"Delivery Slicing"**
59
- section (authored by `epic-plan-spec-author` in the Tech Spec) is the
60
- target grouping when present; degrade gracefully when it is absent.
59
+ emitted by `epic-plan-decompose.js --emit-context`. Read `epicBody`
60
+ from it the sectioned Epic body carrying the folded Tech Spec
61
+ sections (there is no separate `techSpec` key Story #4324). The
62
+ **"Delivery Slicing"**
63
+ section (authored by `epic-plan-spec-author`) is the
64
+ grouping **ceiling** when present (you may merge below it, never split above
65
+ it); degrade gracefully when it is absent.
61
66
 
62
67
  ## Outputs
63
68
 
@@ -74,16 +79,21 @@ Both files MUST exist before the Skill returns.
74
79
 
75
80
  ## Procedure
76
81
 
77
- ### Step 1 — Load the draft and the target
82
+ ### Step 1 — Load the draft and the ceiling
78
83
 
79
84
  Read `temp/epic-<Epic_ID>/tickets.json` (the draft array) and
80
- `temp/epic-<Epic_ID>/decomposer-context.json` (for the PRD / Tech Spec). Locate
81
- the Tech Spec **"Delivery Slicing"** section. Pin two facts before mutating
85
+ `temp/epic-<Epic_ID>/decomposer-context.json` (for the `epicBody`, which
86
+ carries the Tech Spec sections). Locate
87
+ the **"Delivery Slicing"** section. Pin two facts before mutating
82
88
  anything:
83
89
 
84
- 1. The **target grouping** — the N shippable Stories the Architect proposed in
90
+ 1. The **grouping ceiling** — the N shippable Stories the Architect proposed in
85
91
  Delivery Slicing, or `null` when the section is absent (graceful-degrade
86
- mode: cohesion rules only).
92
+ mode: cohesion rules only). Treat N as an upper bound you may go below, not a
93
+ floor you must hit. Note which slices are marked "Independent? No" and
94
+ whether each carries a justification (parallelism, risk isolation, or
95
+ delivery-envelope pressure) — an unjustified dependent slice is a
96
+ fold-into-consumer candidate.
87
97
  2. The **draft Story count** — so you can spot over-fragmented capability
88
98
  clusters.
89
99
 
@@ -92,17 +102,23 @@ anything:
92
102
  Across the draft Story array, decide which Stories merge:
93
103
 
94
104
  - **Over-fragmented capability** → when several draft Stories map to one
95
- Delivery-Slicing target (or one coherent reason to exist), merge them into a
105
+ Delivery-Slicing slice (or one coherent reason to exist), merge them into a
96
106
  single Story: union their `changes` / `acceptance` / `verify` / `references`,
97
107
  write one coherent `goal`, and keep the union of labels.
98
- - **Single-consumer Story** → merge into the one sibling that consumes it.
108
+ - **Single-consumer Story** → merge into the one sibling that consumes it. This
109
+ is the primary lever for going **below** the Delivery-Slicing ceiling: an
110
+ "Independent? No" slice whose only consumer is one sibling folds into that
111
+ sibling unless its row justifies staying separate (parallelism, risk
112
+ isolation, or delivery-envelope pressure). Coarsening below N here is
113
+ expected, not an over-reach — record it as a below-ceiling merge (see Step 4).
99
114
  - **Missing reason to exist** → flag any Story whose body carries no non-empty
100
115
  `reason_to_exist` meta field. A Story that cannot name its single coherent
101
116
  reason is a cohesion smell; record it in the report so the operator can
102
117
  re-scope it (merge, split, or have the author supply the reason) at the HITL
103
118
  gate.
104
119
 
105
- Record each decision with its one-line reason for the report.
120
+ Never split a draft Story to *reach* N N is a ceiling, and adding scope is
121
+ out of contract. Record each decision with its one-line reason for the report.
106
122
 
107
123
  ### Step 3 — Rewire dependencies and conserve scope
108
124
 
@@ -120,7 +136,12 @@ After applying the operations:
120
136
 
121
137
  Write the consolidated array to `temp/epic-<Epic_ID>/tickets.json` (2-space
122
138
  indent, machine-consumed) and the rationale + before/after diff to
123
- `temp/epic-<Epic_ID>/consolidation-report.md`.
139
+ `temp/epic-<Epic_ID>/consolidation-report.md`. When the consolidated count
140
+ landed **below** the Delivery-Slicing ceiling, the report MUST call that out
141
+ explicitly — a `Below ceiling: N (Delivery Slicing) → M (consolidated)` line
142
+ plus a one-line rationale per below-ceiling merge (which dependent
143
+ single-consumer slice folded into which consumer, and why it was unjustified) —
144
+ so the operator sees the coarsening at the Phase 8.3 advisory diff.
124
145
 
125
146
  ### Step 5 — Hand back to `/plan`
126
147
 
@@ -0,0 +1,51 @@
1
+ # epic-plan-consolidate — examples & extended rationale
2
+
3
+ Read this file on demand. The SKILL.md carries the operating contract (Policy
4
+ Capsule, Inputs / Outputs, Procedure, Constraints); this file carries the
5
+ extended rationale behind the consolidation pass — why it is a separate,
6
+ fresh-context critic and how the scope-conservation invariant is enforced by
7
+ you rather than by the runtime.
8
+
9
+ ## Why a separate fresh-context critic
10
+
11
+ This Skill acts as a **holistic critic** with fresh context (a Senior Project
12
+ Manager + Orchestrator lens). It is deliberately *separate* from
13
+ `epic-plan-decompose-author` (the generator): a same-pass self-critique is the
14
+ weak mode this is built to escape. The generator maps Epic capabilities to
15
+ Stories ~1:1; this critic steps back and looks at the *whole* decomposition
16
+ against the Tech Spec's intentional grouping before any GitHub write.
17
+
18
+ ## Scope conservation is your contract, not a machine guarantee
19
+
20
+ Scope conservation is the load-bearing invariant: you MUST NOT add scope,
21
+ invent tickets, or drop acceptance criteria. Every acceptance item and every
22
+ `verify` entry present in the draft MUST survive into the consolidated array
23
+ (possibly re-homed onto a merged Story).
24
+
25
+ **This is your contract, not a machine guarantee.** There is **no runtime
26
+ acceptance-union diff** on your output. The only deterministic runtime backstop
27
+ the validator applies after you run is the standard ticket-structure
28
+ validation — it does not re-derive the pre-consolidation acceptance/verify
29
+ union, so a critic that silently dropped an acceptance item would **not** be
30
+ caught downstream. (The repo's unit test exercises a *pure model* of the merge
31
+ over an over-fragmented fixture to document the intended invariant; it does not
32
+ inspect this Skill's actual output.) Conserve scope yourself, deliberately, on
33
+ every merge — and if you cannot preserve an item, you have over-reached, so back
34
+ the operation out.
35
+
36
+ ## Ceiling, not target — the below-ceiling coarsening case
37
+
38
+ Consume the Tech Spec **"Delivery Slicing"** section as a **ceiling, not a
39
+ target** when present: the Architect's proposed N shippable Stories is an
40
+ *upper bound*, not a floor. Cluster the draft's Stories toward that grouping,
41
+ and go **below** N when slices form dependent single-consumer chains — a slice
42
+ whose "Independent? No" row carries no justification (parallelism, risk
43
+ isolation, or delivery-envelope pressure) folds into its consumer. You **never**
44
+ split above N.
45
+
46
+ When the consolidated count drops **below** the Delivery-Slicing count, the
47
+ consolidation report MUST surface that fact explicitly — e.g.
48
+ `Below ceiling: N (Delivery Slicing) → M (consolidated)` — and attach a one-line
49
+ rationale to each below-ceiling merge (which dependent single-consumer slice
50
+ folded into which consumer, and why it was unjustified), so the operator sees
51
+ the coarsening at the Phase 8.3 advisory diff.
@@ -32,9 +32,10 @@ allowed_tools:
32
32
 
33
33
  ## Role
34
34
 
35
- Senior Project Manager + Orchestrator. The Skill's job is to take a PRD plus
36
- a Tech Spec and emit a flat Story backlog the orchestrator can execute
37
- autonomously.
35
+ Senior Project Manager + Orchestrator. The Skill's job is to take the
36
+ sectioned Epic body (whose managed sections carry the folded Tech Spec and
37
+ Acceptance Table — Story #4324) and emit a flat Story backlog the
38
+ orchestrator can execute autonomously.
38
39
 
39
40
  ## When to use
40
41
 
@@ -55,10 +56,12 @@ reads:
55
56
  `node .agents/scripts/epic-plan-decompose.js --epic <Epic_ID> --emit-context`.
56
57
  Fields:
57
58
  - `epic.id`, `epic.title`
58
- - `prd.body` (or `prd.bodySummary` when downgraded by the
59
- planning-context budget) required for User-Story extraction
60
- - `techSpec.body` (or `techSpec.bodySummary`) required for module
61
- boundary + dependency-DAG extraction
59
+ - `epicBody` the sectioned Epic body. It carries everything the
60
+ decomposer needs in one document: the `## User Stories` section
61
+ (User-Story extraction), the folded Tech Spec sections
62
+ (`## Delivery Slicing` onward — module boundary + dependency-DAG
63
+ extraction), and the `## Acceptance Table` AC-ID table. There is no
64
+ separate `techSpec` envelope key (Story #4324).
62
65
  - `heuristics[]` — risk heuristics surfaced from
63
66
  `agentSettings.planning.riskHeuristics`. Apply each one against the
64
67
  Stories you are emitting; flag matches via `risk::high` labels.
@@ -117,7 +120,7 @@ Pin three values explicitly before writing any tickets:
117
120
  Apply the fully-rendered decomposer system prompt — the `systemPrompt`
118
121
  field of the loaded context envelope, produced by
119
122
  [`decomposer-prompts.js`](../../../scripts/lib/templates/decomposer-prompts.js)
120
- — to the PRD + Tech Spec bodies. That rendered string is the authoritative
123
+ — to the sectioned Epic body (`epicBody`). That rendered string is the authoritative
121
124
  prompt body (with `maxTickets`, `maxTokenBudget`, and the sizing thresholds
122
125
  already interpolated); the authoring-guidance sections below complement it
123
126
  without restating it. Emit JSON only (no prose, no Markdown fence). The
@@ -224,6 +227,8 @@ They are NOT top-level ticket fields.
224
227
 
225
228
  The first question is **cohesion, not count**: *is this one coherent change with one reason to exist?* File count cannot tell a trivial 10-file mechanical rename from a hard 3-file parser+caller+config change — so lead with the change's reason, not its size. Size against the real one-pass delivery envelope (`maxTokenBudget`): a Story is correctly sized when a single agent can hold its full change, acceptance, and verification in one pass within that budget.
226
229
 
230
+ The envelope also has a **floor**, not just a ceiling: a Story that would plausibly use well under a third of `maxTokenBudget` and is neither parallel-deliverable nor orthogonal to its siblings is a **merge candidate** — modern frontier models one-shot capability-sized changes, so a chain of small dependent Stories needlessly pays a full per-Story delivery session (hydration, branch, PR, review, CI) per link. This is soft guidance, not a threshold or validator finding; the canonical phrasing lives in `DELIVERABLE_GRANULARITY_GUIDANCE.envelopeFloor` in `ticket-validator-sizing.js`, which the decomposer prompt interpolates — do not restate a divergent version here.
231
+
227
232
  - **One Story = one coherent change with one reason to exist.** If you cannot state that reason in a sentence, the Story is probably two Stories.
228
233
  - **Single-consumer merge rule.** A Story whose only consumer is one sibling Story should be **merged into that sibling** rather than emitted separately — a single-consumer downstream slice is not its own unit of work.
229
234
  - **Split independent, parallelizable work** into sibling Stories — but only when the pieces genuinely have separate reasons to exist.
@@ -237,11 +242,11 @@ The first question is **cohesion, not count**: *is this one coherent change with
237
242
 
238
243
  #### DELIVERY SLICING (consume the Tech Spec target grouping when present)
239
244
 
240
- The Tech Spec may carry a `## Delivery Slicing` section authored by the
241
- Architect, proposing how the PRD's enumerated capabilities cluster into N
245
+ The Epic body's Tech Spec sections may carry a `## Delivery Slicing` section authored by the
246
+ Architect, proposing how the Epic's enumerated capabilities cluster into N
242
247
  shippable Stories. When that section is **present**, treat it as the **target
243
248
  grouping**: prefer emitting Stories that match the Architect's proposed
244
- clusters rather than mapping PRD capabilities 1:1. When it is **absent**,
249
+ clusters rather than mapping Epic capabilities 1:1. When it is **absent**,
245
250
  degrade gracefully — decompose at deliverable granularity using the cohesion
246
251
  rules above, exactly as before. The Phase 8 holistic consolidation pass
247
252
  (`epic-plan-consolidate`) reconciles your draft against this same Delivery
@@ -263,7 +268,7 @@ Declaring `wide` with a non-empty reason **lifts the `hardFiles` rejection** —
263
268
 
264
269
  #### BRAND / COPY / STYLE WORK
265
270
 
266
- - Stories that touch user-visible copy, brand assets, or visual style MUST cite the relevant section of `docs/style-guide.md` in `acceptance` (e.g. `"acceptance": ["Hero copy matches docs/style-guide.md §3 (voice & tone)"]`). If `docs/style-guide.md` does not exist or has no relevant section, state that explicitly: `"acceptance": ["docs/style-guide.md absent — copy reviewed against the inline brand brief in PRD §2"]`. Silence on style sourcing is a smell.
271
+ - Stories that touch user-visible copy, brand assets, or visual style MUST cite the relevant section of `docs/style-guide.md` in `acceptance` (e.g. `"acceptance": ["Hero copy matches docs/style-guide.md §3 (voice & tone)"]`). If `docs/style-guide.md` does not exist or has no relevant section, state that explicitly: `"acceptance": ["docs/style-guide.md absent — copy reviewed against the inline brand brief in the Epic body"]`. Silence on style sourcing is a smell.
267
272
 
268
273
  #### BINDING ACCEPTANCE vs ADVISORY CHANGES (authoring altitude)
269
274
 
@@ -281,11 +286,11 @@ When a Story's acceptance describes a **signed-in / authenticated** persona reac
281
286
  - Phrase it as: `"From the signed-in home, the persona navigates to Reports → Export and sees the export button"` — not `"GET /reports/export returns the export view"`.
282
287
  - This applies to signed-in journeys only; an unauthenticated landing page or a deliberately deep-linkable share URL is exempt — say so in the acceptance item when you take that exemption.
283
288
 
284
- ### WAVE-0 BDD SCAFFOLD STORY (features-first; emit when the Acceptance Spec has `new`-disposition rows)
289
+ ### WAVE-0 BDD SCAFFOLD STORY (features-first; emit when the Acceptance Table has `new`-disposition rows)
285
290
 
286
- The Acceptance Spec's AC table (columns `AC ID | Outcome | Feature File | Scenario | Disposition`) tags each row's `Disposition` with one of `new | updated | unchanged`. A `new` row names a `.feature` file + scenario that does NOT yet exist on `main`. The framework is features-first: implementation Stories reference those `.feature` paths in their `verify[]` lines, so the files MUST already exist when those Stories run — otherwise verification fails mid-delivery on a missing file (observed gap: Epic #18 in `dsj1984/athportal` had 9 `new` rows and no Story tasked with creating the feature files Stories #1457 / #1466 verified against).
291
+ The Epic body's `## Acceptance Table` section (columns `AC ID | Outcome | Feature File | Scenario | Disposition`) tags each row's `Disposition` with one of `new | updated | unchanged`. A `new` row names a `.feature` file + scenario that does NOT yet exist on `main`. The framework is features-first: implementation Stories reference those `.feature` paths in their `verify[]` lines, so the files MUST already exist when those Stories run — otherwise verification fails mid-delivery on a missing file (observed gap: Epic #18 in `dsj1984/athportal` had 9 `new` rows and no Story tasked with creating the feature files Stories #1457 / #1466 verified against).
287
292
 
288
- When the Acceptance Spec contains **one or more `Disposition: new` rows**, you MUST emit **exactly one** dedicated wave-0 scaffold Story whose sole job is to create those `.feature` files with `@skip`-tagged scenarios BEFORE any implementation Story runs:
293
+ When the Acceptance Table contains **one or more `Disposition: new` rows**, you MUST emit **exactly one** dedicated wave-0 scaffold Story whose sole job is to create those `.feature` files with `@skip`-tagged scenarios BEFORE any implementation Story runs:
289
294
 
290
295
  - **goal** (in body string): contains the literal token `bdd-scaffold`.
291
296
  - **depends_on**: EMPTY (`[]`) — the scaffold runs first, in wave 0.
@@ -295,30 +300,12 @@ When the Acceptance Spec contains **one or more `Disposition: new` rows**, you M
295
300
  - **verify** (top-level array): a grep/validate command (tier `validate`), NOT an e2e runner — verifying that a file exists with the required tags needs no browser/playwright run. Include a check that each new AC ID's namespaced tag is present in the scaffolded files, alongside the `@skip` check.
296
301
  - Each implementation Story whose `verify[]` references a scaffolded `.feature` path MUST add `depends_on: ["<scaffold-slug>"]` so the scaffold lands in an earlier wave. Omitting the link trips the soft `missing-bdd-scaffold` finding in `ticket-validator-conflicts.js` (advisory, not a hard block).
297
302
 
298
- When the Acceptance Spec contains **zero `new`-disposition rows** (every row is `updated` or `unchanged`), do NOT emit a scaffold Story — there is nothing to create.
299
-
300
- **Worked example.** Epic #42, Acceptance Spec with two `new` rows (`AC-1` -> `tests/features/billing/invoice.feature`, `AC-2` -> `tests/features/billing/refund.feature`). The scaffold Story below uses a serialized string `body`, top-level `acceptance`/`verify` arrays, an empty `depends_on`, and tags each scenario with both `@skip` and its namespaced `@epic-42-ac-N` tag:
301
-
302
- {
303
- "slug": "scaffold-billing-feature-files",
304
- "type": "story",
305
- "title": "Scaffold @skip-tagged billing feature files",
306
- "depends_on": [],
307
- "labels": ["type::story", "persona::qa-engineer"],
308
- "acceptance": [
309
- "tests/features/billing/invoice.feature and tests/features/billing/refund.feature both exist on the branch",
310
- "every Scenario in the two new feature files is preceded by an @skip tag (grep for un-skipped scenarios returns zero matches)",
311
- "the invoice.feature scenario carries @epic-42-ac-1 and the refund.feature scenario carries @epic-42-ac-2"
312
- ],
313
- "verify": [
314
- "test -f tests/features/billing/invoice.feature && test -f tests/features/billing/refund.feature (validate)",
315
- "test -z \"$(grep -rL '@skip' tests/features/billing/*.feature)\" (validate)",
316
- "grep -q '@epic-42-ac-1' tests/features/billing/invoice.feature && grep -q '@epic-42-ac-2' tests/features/billing/refund.feature (validate)"
317
- ],
318
- "body": "## Goal\nbdd-scaffold: create the @skip-tagged, @epic-42-ac-N-tagged feature files the billing-flows implementation Stories verify against, so wave-0 lands them before any implementation Story runs.\n\n## Changes\n- {\"path\": \"tests/features/billing/invoice.feature\", \"assumption\": \"creates\"}\n- {\"path\": \"tests/features/billing/refund.feature\", \"assumption\": \"creates\"}\n\n## Acceptance\n- [ ] tests/features/billing/invoice.feature and tests/features/billing/refund.feature both exist on the branch\n- [ ] every Scenario in the two new feature files is preceded by an @skip tag\n- [ ] the invoice.feature scenario carries @epic-42-ac-1 and the refund.feature scenario carries @epic-42-ac-2\n\n## Verify\n- test -f tests/features/billing/invoice.feature && test -f tests/features/billing/refund.feature (validate)\n- test -z \"$(grep -rL '@skip' tests/features/billing/*.feature)\" (validate)\n- grep -q '@epic-42-ac-1' tests/features/billing/invoice.feature && grep -q '@epic-42-ac-2' tests/features/billing/refund.feature (validate)\n"
319
- }
320
-
321
- The implementation Stories that later un-skip and flesh out these scenarios each carry `depends_on: ["scaffold-billing-feature-files"]`, placing them in a later wave than the scaffold. They MUST NOT add the `@epic-42-ac-N` tag themselves — it is already present from the scaffold pass; their job is to remove `@skip` once the scenario passes.
303
+ When the Acceptance Table contains **zero `new`-disposition rows** (every row is `updated` or `unchanged`), do NOT emit a scaffold Story — there is nothing to create.
304
+
305
+ > **Read [`examples.md`](./examples.md) on demand** for a fully worked wave-0
306
+ > scaffold Story (Epic #42, two `new` rows) showing the serialized string
307
+ > `body`, the top-level `acceptance`/`verify` arrays, the empty `depends_on`,
308
+ > and the paired `@skip` + `@epic-<id>-ac-N` tagging.
322
309
 
323
310
  ### SCOPE-OVERLAP FLAGGING (docs/runbook downstream of config work)
324
311
 
@@ -407,7 +394,7 @@ any logical ordering requirement via Story-level `depends_on`.
407
394
  - Do **not** call the GitHub API from this Skill. Persistence is the
408
395
  script's job; the Skill is pure JSON authoring.
409
396
  - Do **not** write outside `temp/epic-<Epic_ID>/`. Reads may cover the
410
- PRD/Tech Spec bodies plus any docs the context envelope cites.
397
+ Epic body plus any docs the context envelope cites.
411
398
  - The decomposer prompt's `${maxTickets}` value is the **reviewability
412
399
  budget** (Story #2798). Staying under is the default; exceeding it
413
400
  requires both an `over_budget_rationale` in the JSON output and the
@@ -0,0 +1,47 @@
1
+ # epic-plan-decompose-author — examples & extended rationale
2
+
3
+ Read this file on demand while authoring the ticket array. The SKILL.md carries
4
+ the operating contract (Policy Capsule, Inputs / Outputs, Procedure, the
5
+ authoring guidance that complements the rendered decomposer prompt, and
6
+ Constraints); this file carries the worked WAVE-0 BDD scaffold Story and the
7
+ extended reasoning behind it.
8
+
9
+ ## WAVE-0 BDD scaffold Story — worked example
10
+
11
+ The contract lives in SKILL.md under **WAVE-0 BDD SCAFFOLD STORY**: when the
12
+ Epic body's `## Acceptance Table` carries one or more `Disposition: new` rows,
13
+ emit **exactly one** dedicated wave-0 scaffold Story whose sole job is to create
14
+ those `.feature` files with `@skip`-tagged scenarios (each also carrying its
15
+ namespaced `@epic-<id>-ac-N` tag) BEFORE any implementation Story runs.
16
+
17
+ **Worked example.** Epic #42, Acceptance Table with two `new` rows
18
+ (`AC-1` -> `tests/features/billing/invoice.feature`,
19
+ `AC-2` -> `tests/features/billing/refund.feature`). The scaffold Story below
20
+ uses a serialized string `body`, top-level `acceptance`/`verify` arrays, an
21
+ empty `depends_on`, and tags each scenario with both `@skip` and its namespaced
22
+ `@epic-42-ac-N` tag:
23
+
24
+ {
25
+ "slug": "scaffold-billing-feature-files",
26
+ "type": "story",
27
+ "title": "Scaffold @skip-tagged billing feature files",
28
+ "depends_on": [],
29
+ "labels": ["type::story", "persona::qa-engineer"],
30
+ "acceptance": [
31
+ "tests/features/billing/invoice.feature and tests/features/billing/refund.feature both exist on the branch",
32
+ "every Scenario in the two new feature files is preceded by an @skip tag (grep for un-skipped scenarios returns zero matches)",
33
+ "the invoice.feature scenario carries @epic-42-ac-1 and the refund.feature scenario carries @epic-42-ac-2"
34
+ ],
35
+ "verify": [
36
+ "test -f tests/features/billing/invoice.feature && test -f tests/features/billing/refund.feature (validate)",
37
+ "test -z \"$(grep -rL '@skip' tests/features/billing/*.feature)\" (validate)",
38
+ "grep -q '@epic-42-ac-1' tests/features/billing/invoice.feature && grep -q '@epic-42-ac-2' tests/features/billing/refund.feature (validate)"
39
+ ],
40
+ "body": "## Goal\nbdd-scaffold: create the @skip-tagged, @epic-42-ac-N-tagged feature files the billing-flows implementation Stories verify against, so wave-0 lands them before any implementation Story runs.\n\n## Changes\n- {\"path\": \"tests/features/billing/invoice.feature\", \"assumption\": \"creates\"}\n- {\"path\": \"tests/features/billing/refund.feature\", \"assumption\": \"creates\"}\n\n## Acceptance\n- [ ] tests/features/billing/invoice.feature and tests/features/billing/refund.feature both exist on the branch\n- [ ] every Scenario in the two new feature files is preceded by an @skip tag\n- [ ] the invoice.feature scenario carries @epic-42-ac-1 and the refund.feature scenario carries @epic-42-ac-2\n\n## Verify\n- test -f tests/features/billing/invoice.feature && test -f tests/features/billing/refund.feature (validate)\n- test -z \"$(grep -rL '@skip' tests/features/billing/*.feature)\" (validate)\n- grep -q '@epic-42-ac-1' tests/features/billing/invoice.feature && grep -q '@epic-42-ac-2' tests/features/billing/refund.feature (validate)\n"
41
+ }
42
+
43
+ The implementation Stories that later un-skip and flesh out these scenarios each
44
+ carry `depends_on: ["scaffold-billing-feature-files"]`, placing them in a later
45
+ wave than the scaffold. They MUST NOT add the `@epic-42-ac-N` tag themselves —
46
+ it is already present from the scaffold pass; their job is to remove `@skip`
47
+ once the scenario passes.
@@ -5,7 +5,7 @@ description: >-
5
5
  ticket array an Epic's decompose phase produced. Use during Phase 8 of
6
6
  `/plan`, after `epic-plan-decompose-author` / `epic-plan-consolidate` write
7
7
  `temp/epic-<Epic_ID>/tickets.json` and before `epic-plan-decompose.js`
8
- validates and persists it. Reads the PRD / Tech Spec AND the actual cited
8
+ validates and persists it. Reads the sectioned Epic body AND the cited
9
9
  code surfaces, then emits predicted-rework findings before any GitHub write.
10
10
  allowed_tools:
11
11
  - Read
@@ -18,7 +18,7 @@ allowed_tools:
18
18
 
19
19
  ## Policy Capsule
20
20
 
21
- - Run only after a draft `temp/epic-<Epic_ID>/tickets.json` exists (authored by `epic-plan-decompose-author`, and consolidated by `epic-plan-consolidate` if that pass ran); fail loudly if the draft array is missing. Read the PRD / Tech Spec from `temp/epic-<Epic_ID>/decomposer-context.json` (the same envelope the author skill consumed) — never re-fetch from GitHub, and never call the GitHub API from this Skill.
21
+ - Run only after a draft `temp/epic-<Epic_ID>/tickets.json` exists (authored by `epic-plan-decompose-author`, and consolidated by `epic-plan-consolidate` if that pass ran); fail loudly if the draft array is missing. Read the sectioned Epic body from `temp/epic-<Epic_ID>/decomposer-context.json` (the same envelope the author skill consumed) — never re-fetch from GitHub, and never call the GitHub API from this Skill.
22
22
  - **You MUST read the actual cited code surfaces.** For every Story, open the files named in its `changes[]` / `references[]` (resolve each path against the repo root; use `Read` / `Grep`) and read enough of each to judge whether the Story's `acceptance[]` is verifiable against the real code and whether its `changes[]` assumptions hold. This is the load-bearing difference between this critic and the structural file-assumption gate: that gate proves a path **exists** (or does not); this critic reads what the file actually **contains**. A pre-mortem that did not open the cited files has not run.
23
23
  - Emit exactly one artifact: a human-readable `temp/epic-<Epic_ID>/premortem-report.md` — the predicted-rework findings the operator reviews at the Phase 8 HITL diff. It MUST exist before returning.
24
24
  - **This critic never writes to GitHub and never persists `tickets.json`.** It is read-and-report only: it does NOT mutate the draft array, does NOT create issues, and does NOT flip any label. Re-authoring on its findings is the author skill's job (the workflow re-runs `epic-plan-decompose-author` on the report before the persist call).
@@ -28,14 +28,15 @@ allowed_tools:
28
28
 
29
29
  ## Role
30
30
 
31
- Senior Engineer + Architect, acting as a **fresh-context pre-mortem critic**.
32
- This Skill is deliberately *separate* from `epic-plan-decompose-author` (the
33
- generator) and from `epic-plan-consolidate` (the scope-preserving merge critic):
34
- a same-pass self-critique is the weak mode this is built to escape. The
35
- generator maps PRD capabilities to Stories against the spec text; this critic
36
- opens the **actual cited code** and asks "if I tried to deliver this exact
37
- backlog, where would it rework?" before any GitHub write makes the rework
38
- expensive.
31
+ Senior Engineer + Architect, acting as a **fresh-context pre-mortem critic** —
32
+ deliberately *separate* from `epic-plan-decompose-author` (the generator) and
33
+ `epic-plan-consolidate` (the scope-preserving merge critic) so it is a
34
+ fresh-context, code-reading review, not a same-pass self-critique.
35
+
36
+ > **Read [`examples.md`](./examples.md) on demand** for the extended rationale:
37
+ > why this critic opens the actual cited code, why it is additive-recommendation
38
+ > (not scope-preserving) unlike consolidation, and the three predicted-rework
39
+ > finding classes in full.
39
40
 
40
41
  ## When to use
41
42
 
@@ -56,8 +57,9 @@ The workflow passes the Epic ID as the Skill argument. The Skill itself reads:
56
57
  - `temp/epic-<Epic_ID>/tickets.json` — the **draft** (or consolidated) Story
57
58
  array. This is the pre-mortem subject.
58
59
  - `temp/epic-<Epic_ID>/decomposer-context.json` — the authoring envelope emitted
59
- by `epic-plan-decompose.js --emit-context`. Read `prd.body` / `prd` and
60
- `techSpec.body` / `techSpec` from it.
60
+ by `epic-plan-decompose.js --emit-context`. Read `epicBody` from it
61
+ the sectioned Epic body carrying the folded Tech Spec sections
62
+ (there is no separate `techSpec` key — Story #4324).
61
63
  - **The repository working tree** — the actual files each Story's `changes[]` /
62
64
  `references[]` name. Resolve each path against the repo root and read it.
63
65
 
@@ -78,7 +80,7 @@ artifact and mutates **no** GitHub state.
78
80
  ### Step 1 — Load the draft and the spec
79
81
 
80
82
  Read `temp/epic-<Epic_ID>/tickets.json` (the Story array) and
81
- `temp/epic-<Epic_ID>/decomposer-context.json` (for the PRD / Tech Spec). If the
83
+ `temp/epic-<Epic_ID>/decomposer-context.json` (for the `epicBody`). If the
82
84
  draft array is missing, fail loudly and instruct the caller to run the
83
85
  `epic-plan-decompose-author` Skill first.
84
86