mandrel 1.68.0 → 1.69.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/.agents/docs/agentrc-reference.json +1 -2
  2. package/.agents/docs/configuration.md +2 -4
  3. package/.agents/schemas/agentrc.schema.json +1 -5
  4. package/.agents/schemas/lifecycle/epic.automerge.end.schema.json +2 -1
  5. package/.agents/scripts/epic-deliver-preflight.js +30 -13
  6. package/.agents/scripts/epic-deliver-prepare.js +40 -53
  7. package/.agents/scripts/epic-execute-record-wave.js +119 -133
  8. package/.agents/scripts/lib/baselines/refresh-service.js +13 -1
  9. package/.agents/scripts/lib/config/explain.js +0 -2
  10. package/.agents/scripts/lib/config/limits.js +19 -8
  11. package/.agents/scripts/lib/config-settings-schema.js +1 -2
  12. package/.agents/scripts/lib/maintainability-utils.js +32 -9
  13. package/.agents/scripts/lib/orchestration/epic-cleanup.js +11 -7
  14. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/cli.js +6 -6
  15. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/context.js +11 -5
  16. package/.agents/scripts/lib/orchestration/epic-run-state-store.js +203 -110
  17. package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter/composition.js +38 -78
  18. package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter/transport.js +16 -13
  19. package/.agents/scripts/lib/orchestration/epic-runner/sub-agent-return.js +10 -7
  20. package/.agents/scripts/lib/orchestration/lifecycle/listeners/automerge-predicate.js +37 -24
  21. package/.agents/scripts/lib/orchestration/manifest-builder.js +6 -0
  22. package/.agents/scripts/lib/orchestration/ticket-validator-sizing.js +6 -2
  23. package/.agents/scripts/lib/orchestration/wave-record-io.js +18 -77
  24. package/.agents/scripts/lib/orchestration/wave-record-notifications.js +78 -122
  25. package/.agents/scripts/lib/orchestration/wave-record-projection.js +21 -226
  26. package/.agents/scripts/lib/presentation/dispatch-manifest-render.js +18 -1
  27. package/.agents/scripts/lib/presentation/manifest-render-waves.js +77 -4
  28. package/.agents/scripts/lib/story-adjacency.js +14 -10
  29. package/.agents/scripts/lib/story-body/story-body.js +36 -4
  30. package/.agents/scripts/lib/templates/decomposer-prompts.js +23 -3
  31. package/.agents/scripts/lib/wave-runner/ready-set.js +295 -0
  32. package/.agents/scripts/lib/wave-runner/tick.js +312 -206
  33. package/.agents/scripts/lib/wave-runner/wave-runner-error.js +2 -1
  34. package/.agents/scripts/lint-label-vocabulary.js +1 -1
  35. package/.agents/scripts/stories-wave-tick.js +262 -161
  36. package/.agents/skills/core/epic-plan-consolidate/SKILL.md +6 -0
  37. package/.agents/skills/core/epic-plan-decompose-author/SKILL.md +108 -101
  38. package/.agents/skills/skills.index.json +2 -2
  39. package/.agents/workflows/deliver.md +12 -9
  40. package/.agents/workflows/helpers/deliver-epic.md +126 -90
  41. package/.agents/workflows/helpers/deliver-stories.md +131 -85
  42. package/.agents/workflows/helpers/plan-epic.md +13 -10
  43. package/.agents/workflows/plan.md +1 -1
  44. package/docs/CHANGELOG.md +14 -0
  45. package/package.json +1 -1
  46. package/.agents/scripts/lib/wave-runner/wave-checkpoint.js +0 -91
@@ -62,8 +62,9 @@ reads:
62
62
  - `heuristics[]` — risk heuristics surfaced from
63
63
  `agentSettings.planning.riskHeuristics`. Apply each one against the
64
64
  Stories you are emitting; flag matches via `risk::high` labels.
65
- - `maxTickets` — reviewability budget from
66
- `agentSettings.planning.maxTickets` (Story #2798). Default: stay
65
+ - `maxTickets` — reviewability budget; a framework constant
66
+ (`LIMITS_DEFAULTS.maxTickets` in `.agents/scripts/lib/config/limits.js`,
67
+ not operator-configurable — Story #2798, Story #4163). Default: stay
67
68
  under. When the plan genuinely needs more, emit the full plan with
68
69
  an `over_budget_rationale` and rely on the operator's
69
70
  `--allow-over-budget` override at persist time. The script logs the
@@ -71,8 +72,16 @@ reads:
71
72
  - `contextMode` — `"full"` or `"summary"`. When `"summary"`, work
72
73
  from the `bodySummary` fields rather than re-fetching the bodies.
73
74
 
74
- The legacy `systemPrompt` field is also emitted as a backstop, but this
75
- Skill's body below is the authoritative version going forward.
75
+ - `systemPrompt` the **authoritative, fully-rendered decomposer system
76
+ prompt** (Story #4162). It is produced by `renderDecomposerSystemPrompt`
77
+ in
78
+ [`decomposer-prompts.js`](../../../scripts/lib/templates/decomposer-prompts.js),
79
+ the single source of the prompt body, with `maxTickets`,
80
+ `maxTokenBudget`, and the sizing thresholds already interpolated. Apply
81
+ **this** string as your system prompt — this SKILL deliberately does NOT
82
+ embed a second verbatim copy of the prompt body, so the two surfaces
83
+ cannot drift. The sections below are authoring guidance that complements
84
+ the rendered prompt; they are not a replacement copy of it.
76
85
 
77
86
  ## Outputs
78
87
 
@@ -103,11 +112,17 @@ Pin three values explicitly before writing any tickets:
103
112
  3. `heuristics[]` — render the active risk heuristics in front of you
104
113
  so the planning persona can mention them as Stories are emitted.
105
114
 
106
- ### Step 2 — Decompose against the system prompt
107
-
108
- Apply the decomposer system prompt below to the PRD + Tech Spec bodies.
109
- Emit JSON only (no prose, no Markdown fence). The downstream validator
110
- in [`lib/orchestration/ticket-validator.js`](../../../scripts/lib/orchestration/ticket-validator.js)
115
+ ### Step 2 — Decompose against the rendered system prompt
116
+
117
+ Apply the fully-rendered decomposer system prompt the `systemPrompt`
118
+ field of the loaded context envelope, produced by
119
+ [`decomposer-prompts.js`](../../../scripts/lib/templates/decomposer-prompts.js)
120
+ — to the PRD + Tech Spec bodies. That rendered string is the authoritative
121
+ prompt body (with `maxTickets`, `maxTokenBudget`, and the sizing thresholds
122
+ already interpolated); the authoring-guidance sections below complement it
123
+ without restating it. Emit JSON only (no prose, no Markdown fence). The
124
+ downstream validator in
125
+ [`lib/orchestration/ticket-validator.js`](../../../scripts/lib/orchestration/ticket-validator.js)
111
126
  will reject anything off-shape. Merge narrow, single-module slices into
112
127
  their capability first; emit one Story per capability slice a frontier
113
128
  model can deliver and self-verify in one pass.
@@ -125,75 +140,69 @@ Return control. The caller invokes
125
140
  temp/epic-<Epic_ID>/tickets.json`, which validates, persists, and flips
126
141
  the Epic to `agent::ready`.
127
142
 
128
- ## Decomposer system prompt (authoritative)
129
-
130
- The value `${maxTickets}` is substituted at runtime from the
131
- `maxTickets` field in the loaded context. Treat it as the reviewability
132
- budget (Story #2798) stay under by default; over-budget plans need an
133
- `over_budget_rationale` plus operator `--allow-over-budget` to persist.
134
-
135
- ```text
136
- You are an expert Senior Project Manager and Orchestrator.
137
- Your job is to take a Product Requirements Document (PRD) and a Technical Specification and decompose them into a flat list of Story tickets for an AI Agent to execute.
138
-
139
- ### HIERARCHY RULES:
140
- 1. **Stories**: Capability-sized, verifiable units of work (e.g., "Implement JWT Token Exchange").
141
- - Attach directly to the Epic — there is NO Feature tier; thematic grouping is prose in the Epic body / Tech Spec.
142
- - **Story-Level Execution**: Each Story is executed on a single Story branch (`story-<storyId>`), implemented in one Story-implementation phase, then merged into the Epic branch. The Story body carries the full execution contract (goal, changes, acceptance, verify).
143
- - Do NOT emit any other ticket tier the validator only accepts `type::story`.
144
-
145
- ### LABEL CONVENTIONS:
146
- - Every ticket must have the `type::story` label.
147
- - Every ticket must have a `persona::[engineer|architect|qa-engineer|engineer-web|etc]` label indicating WHO should execute it.
148
-
149
- ### OUTPUT FORMAT:
150
- You MUST respond ONLY with a valid JSON array of objects. No prose, no markdown blocks.
151
-
152
- ### JSON SCHEMA:
153
- [
154
- {
155
- "slug": "hyphen-case-id",
156
- "type": "story",
157
- "title": "Short descriptive title",
158
- "body": <string see BODY RULES below>,
159
- "acceptance": ["<testable criterion>", ...], // STORIES ONLY — top-level, read by validator
160
- "verify": ["<exact command> (<tier>)", ...], // STORIES ONLY — top-level, read by validator
161
- "labels": ["type::story", "persona::..."],
162
- "depends_on": ["slug_of_blocking_dependency"] (optional array of Story slugs that block execution)
163
- }
164
- ]
165
-
166
- **Slug format**: `^[a-z0-9][a-z0-9-]*$` hyphen-case only. Underscores are rejected by the validator.
167
-
168
- ### STORY BODY SCHEMA (REQUIRED FOR EVERY STORY):
169
- For Stories, `body` MUST be a **string** — the serialized markdown produced by calling `serialize()` from `lib/story-body/story-body.js`. Do NOT emit `body` as a JSON object: `createOp` in `epic-spec-reconciler-ops.js` will throw `StoryBodyParseError` when it receives an object body (Story #3302 serialize-or-throw contract), and `composeStoryBody` in the GitHub provider also discards non-string bodies producing an empty issue. The canonical pipeline requires a serialized string body end-to-end. The freshness gate (`collectTaskChangesPaths`) and the assumption gate (`collectStoryAssumptionEntries`) both parse the string body via `story-body.js#parse` to recover `changes[]`/`references[]` — they operate correctly only on the serialized string form.
170
-
171
- The `acceptance[]` and `verify[]` arrays live at the **top level** of the Story ticket object (not nested inside `body`). The validator's `hasInlineAcceptanceAndVerify(story)` reads `story.acceptance` and `story.verify` directly nesting them inside a body object makes them invisible to the validator, causing the backlog to be treated as the legacy 4-tier shape and producing a `Cross-Validation Failed: Backlog must contain at least one Task.` error.
172
-
173
- The serialized `body` string renders these markdown sections (in order):
174
-
175
- ## Goal
176
- <one sentence why this Story exists within the Epic>
177
-
178
- ## Changes
179
- - {"path": "<file path>", "assumption": "creates" | "refactors-existing" | "deletes"}
180
- - ...
181
-
182
- ## Acceptance
183
- - [ ] <testable, observable criterion>
184
- - ...
185
-
186
- ## Verify
187
- - <exact command or test path> (<tier>)
188
- - ...
189
-
190
- ## References
191
- - {"path": "<read-only dependency path>", "assumption": "exists"}
192
- - ...
193
-
194
- Fields `wide` and `estimated_test_files` are encoded as a `<!-- meta: {...} -->` comment appended to the serialized body string (handled by `serialize()`). They are NOT top-level ticket fields.
195
-
196
- #### STORY BODY RULES:
143
+ ## Decomposer system prompt — single-sourced (Story #4162)
144
+
145
+ The authoritative decomposer system-prompt **body** is single-sourced in
146
+ [`decomposer-prompts.js`](../../../scripts/lib/templates/decomposer-prompts.js)
147
+ (`renderDecomposerSystemPrompt`) and delivered to you fully rendered in the
148
+ `systemPrompt` field of `temp/epic-<Epic_ID>/decomposer-context.json` with
149
+ `maxTickets`, `maxTokenBudget`, the `DEFAULT_TASK_SIZING` thresholds, and the
150
+ risk heuristics already interpolated. **Apply that rendered string as your
151
+ system prompt.** This SKILL deliberately does **not** embed a second verbatim
152
+ copy of the prompt body (the preamble, the JSON output schema, the hierarchy /
153
+ label / output-format rules); duplicating it here is exactly the drift the
154
+ single-source contract exists to prevent, and the guard test in
155
+ `tests/ticket-decomposer.test.js` fails if a second full copy reappears on
156
+ either surface.
157
+
158
+ The value `${maxTickets}` in the rendered prompt is substituted at runtime
159
+ from the `maxTickets` field of the loaded context. Treat it as the
160
+ **reviewability budget** (Story #2798) — stay under by default; over-budget
161
+ plans need an `over_budget_rationale` plus operator `--allow-over-budget` to
162
+ persist. It is **not** a hard authoring cap: the prompt no longer carries any
163
+ ticket-count limit directive, and you MUST never truncate the JSON array to fit
164
+ (Story #4162). The rendered prompt also names the delivery token
165
+ budget (`maxTokenBudget`) as the real one-pass sizing envelope size each
166
+ Story so a single agent can deliver and self-verify it within that budget.
167
+
168
+ ## Authoring guidance (complements the rendered prompt)
169
+
170
+ The sections below are the SKILL's authoring guidance. They do **not** restate
171
+ the rendered prompt body — they record the contract details and sizing
172
+ heuristics you apply when shaping the ticket array. Where a number appears it
173
+ is the `DEFAULT_TASK_SIZING` default; the rendered prompt interpolates the live
174
+ value, which always wins.
175
+
176
+ ### STORY BODY SHAPE (string body, top-level acceptance/verify)
177
+
178
+ For Stories, `body` MUST be a **string** — the serialized markdown produced by
179
+ calling `serialize()` from `lib/story-body/story-body.js`. Do NOT emit `body`
180
+ as a JSON object: `createOp` in `epic-spec-reconciler-ops.js` will throw
181
+ `StoryBodyParseError` when it receives an object body (Story #3302
182
+ serialize-or-throw contract), and `composeStoryBody` in the GitHub provider
183
+ also discards non-string bodies producing an empty issue. The freshness gate
184
+ (`collectTaskChangesPaths`) and the assumption gate
185
+ (`collectStoryAssumptionEntries`) both parse the string body via
186
+ `story-body.js#parse` to recover `changes[]`/`references[]` — they operate
187
+ correctly only on the serialized string form.
188
+
189
+ The `acceptance[]` and `verify[]` arrays live at the **top level** of the
190
+ Story ticket object (not nested inside `body`). The validator's
191
+ `hasInlineAcceptanceAndVerify(story)` reads `story.acceptance` and
192
+ `story.verify` directly — nesting them inside a body object makes them
193
+ invisible to the validator, causing the backlog to be treated as the legacy
194
+ 4-tier shape and producing a `Cross-Validation Failed: Backlog must contain at
195
+ least one Task.` error.
196
+
197
+ The serialized `body` string renders these markdown sections in order: `##
198
+ Goal` (one sentence), `## Changes` (object-form `{ "path", "assumption" }`
199
+ bullets), `## Acceptance` (checkbox items), `## Verify` (command + tier), and
200
+ `## References` (read-only `{ "path", "assumption": "exists" }` bullets).
201
+ Fields `wide` and `estimated_test_files` are encoded as a `<!-- meta: {...}
202
+ -->` comment appended to the serialized body string (handled by `serialize()`).
203
+ They are NOT top-level ticket fields.
204
+
205
+ #### STORY BODY RULES
197
206
 
198
207
  - **slug**: MUST be hyphen-case (`^[a-z0-9][a-z0-9-]*$`). Do not use underscores.
199
208
  - **goal** (in body string): One sentence stating WHY this Story exists within the Epic.
@@ -204,15 +213,15 @@ Fields `wide` and `estimated_test_files` are encoded as a `<!-- meta: {...} -->`
204
213
  - **verify** (top-level array on ticket object): Each entry MUST name a testing tier in parentheses, drawn from `unit` / `contract` / `e2e` / `validate`. Example: `npm run test -- src/x.test.ts (unit)`, `npm run validate (validate)`. Stories with zero verify entries SHOULD fail validation; if a Story is genuinely unverifiable in isolation (e.g., a copy edit auditor will eyeball), the literal entry `manual:<reason>` is allowed so the absence is intentional, not lazy. Manual entries without a reason are rejected.
205
214
  - **estimated_test_files** (optional, encoded in body meta comment): Integer estimate of how many test files this Story creates or modifies. Omit when the number is not estimable. Informational only — it does not gate the decompose.
206
215
 
207
- #### FORBIDDEN SUBJECT-PREFIX PRESCRIPTIONS (Conventional-Commits only):
216
+ #### FORBIDDEN SUBJECT-PREFIX PRESCRIPTIONS (Conventional-Commits only)
208
217
 
209
218
  - `acceptance` items MUST NOT prescribe a commit subject that begins with a non-Conventional-Commits prefix. The allowed leading types are `feat|fix|chore|refactor|perf|docs|style|test|build|ci|revert` (matching `commitlint.config.js` and `release-please-config.json`). Historic ad-hoc subject prefixes — such as the legacy `baseline-refresh` token used as a leading prefix — are FORBIDDEN as subject prescriptions, because they fail the local `commit-msg` hook and the close-time validator (`ticket-validator.js` → `validateAcceptanceSubjectPrefix`) will reject the decompose with `code: 'forbidden-subject-prefix'`. When a Story needs a baseline-refresh-style classification, prescribe a Conventional-Commits subject (e.g. `chore(baselines): refresh maintainability snapshot`) and, if a machine-readable marker is required, prescribe a body trailer such as `baseline-refresh: true` (note the trailing space and value, not a subject prefix). See Epic #2501 for the rationale.
210
219
 
211
- #### STORY SIZING — DELIVERABLE GRANULARITY, COHESION FIRST (the numeric ceiling is only a backstop):
220
+ #### STORY SIZING — DELIVERABLE GRANULARITY, COHESION FIRST (the numeric ceiling is only a backstop)
212
221
 
213
- **Decompose at deliverable granularity, not module/task level.** A Story is a **capability slice a frontier model delivers and self-verifies in one pass** — a shippable slice a reviewer would accept as a single PR, a capability or user-visible surface, **not a single module or file**. Fold module-level slices into the capability they belong to rather than emitting one Story per module. (This definition is the single source of truth in `DELIVERABLE_GRANULARITY_GUIDANCE` in `ticket-validator-sizing.js`; the decomposer prompt interpolates the same string — do not restate a divergent version here.)
222
+ **Decompose at deliverable granularity, not module/task level.** A Story is a **capability slice a frontier model delivers and self-verifies in one pass** — a shippable slice a reviewer would accept as a single PR, a capability or user-visible surface, **not a single module or file**. Fold module-level slices into the capability they belong to rather than emitting one Story per module. (This definition is the single source of truth in `DELIVERABLE_GRANULARITY_GUIDANCE` in `ticket-validator-sizing.js`; the rendered decomposer prompt interpolates the same string — do not restate a divergent version here.)
214
223
 
215
- 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.
224
+ 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.
216
225
 
217
226
  - **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.
218
227
  - **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.
@@ -221,11 +230,11 @@ The first question is **cohesion, not count**: *is this one coherent change with
221
230
 
222
231
  **Numeric backstop.** The thresholds are defined **once**, in the `DEFAULT_TASK_SIZING` constant in `ticket-validator-sizing.js` (operator-overridable via `agentSettings.planning.taskSizing`). They are a backstop, not the primary rule — do not restate divergent numbers anywhere else. The defaults:
223
232
 
224
- - A Story touching more than **`softFiles` (8)** files emits an advisory width finding — a nudge to check cohesion or declare `wide`.
233
+ - A Story touching more than **`softFiles` (15)** files emits an advisory width finding — a nudge to check cohesion or declare `wide`.
225
234
  - A Story touching more than **`hardFiles` (30)** files is **rejected** unless it declares `wide` with a reason.
226
235
  - A Story with more than **`maxAcceptance` (14)** acceptance items is **rejected**; more than **`softAcceptanceCount` (10)** emits an advisory warning.
227
236
 
228
- #### DELIVERY SLICING (consume the Tech Spec target grouping when present):
237
+ #### DELIVERY SLICING (consume the Tech Spec target grouping when present)
229
238
 
230
239
  The Tech Spec may carry a `## Delivery Slicing` section authored by the
231
240
  Architect, proposing how the PRD's enumerated capabilities cluster into N
@@ -238,31 +247,31 @@ rules above, exactly as before. The Phase 8 holistic consolidation pass
238
247
  Slicing target before persist, so aligning to it here reduces the work the
239
248
  consolidation critic has to do.
240
249
 
241
- #### `wide` DECLARATION (optional — for legitimately broad changes):
250
+ #### `wide` DECLARATION (optional — for legitimately broad changes)
242
251
 
243
252
  A Story whose footprint is legitimately broad declares `wide` carrying a one-line human-readable reason. Encode it in the `<!-- meta: {"wide": {"reason": "..."}} -->` comment that `serialize()` appends to the body string — e.g. `"wide": { "reason": "hard contract cutover: migrate every <X> call site in one PR" }`.
244
253
 
245
254
  Declaring `wide` with a non-empty reason **lifts the `hardFiles` rejection** — no Story is rejected for width when it states why it is broad. Omit `wide` for ordinary Stories; a wide footprint with no `wide` declaration emits only an advisory nudge (check cohesion or declare `wide`), never a rejection on its own. Glob entries in `changes[]` (bullets containing `*`) are `unknown-width`: the numeric ceiling is skipped, and a glob Story with no `wide` declaration emits the same advisory nudge.
246
255
 
247
- #### UI / TESTID INVARIANCE (per CLAUDE.md safety rule):
256
+ #### UI / TESTID INVARIANCE (per CLAUDE.md safety rule)
248
257
 
249
258
  - Stories that touch UI (`*.tsx`, `*.astro`, `*.svelte`, `*.vue`, components folders) MUST end `changes` with one of:
250
259
  - `data-testid invariance: <list of testids that MUST be preserved>`, or
251
260
  - `data-testid changes: <old> -> <new>` paired with a corresponding `tests/e2e/*.spec.ts` edit in the same Story or a depends_on Story.
252
261
  - Renaming a testid without the matching e2e edit is FORBIDDEN.
253
262
 
254
- #### BRAND / COPY / STYLE WORK:
263
+ #### BRAND / COPY / STYLE WORK
255
264
 
256
265
  - 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.
257
266
 
258
- #### BINDING ACCEPTANCE vs ADVISORY CHANGES (authoring altitude):
267
+ #### BINDING ACCEPTANCE vs ADVISORY CHANGES (authoring altitude)
259
268
 
260
269
  `acceptance[]` and `verify[]` are the **binding contract** the executor MUST satisfy — they are the sole definition of "done." `changes[]` and `references[]` are an **advisory implementation sketch**: your best prediction of the file footprint, which the executor is permitted to revise when the real codebase diverges from the sketch. Author at that altitude:
261
270
 
262
271
  - Write `acceptance[]`/`verify[]` to capture the **outcome**, independent of any one file layout. Do NOT pin an incidental implementation detail (an internal helper name, a private file path) into an acceptance item that the advisory `changes[]` is free to reshape — assert the observable behaviour instead.
263
272
  - Keep `changes[]`/`references[]` as the honest predicted footprint. They still pass through the structural file-assumption gate (the `creates`/`refactors-existing`/`deletes` probes against the base branch) and the New-File Contract unchanged — advisory does NOT mean unvalidated. The executor's latitude to revise the approach never licenses skipping `acceptance[]`/`verify[]` or any `rules/security-baseline.md` MUST.
264
273
 
265
- #### NAVIGATE-DON'T-DEEP-LINK (signed-in acceptance scenarios):
274
+ #### NAVIGATE-DON'T-DEEP-LINK (signed-in acceptance scenarios)
266
275
 
267
276
  When a Story's acceptance describes a **signed-in / authenticated** persona reaching a feature surface, author it so the persona starts from their authenticated home and **reaches the feature through navigation** — clicking a nav door, menu entry, or link the UI actually exposes — **never** via a hardcoded deep-link URL.
268
277
 
@@ -270,7 +279,7 @@ When a Story's acceptance describes a **signed-in / authenticated** persona reac
270
279
  - 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"`.
271
280
  - 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.
272
281
 
273
- ### WAVE-0 BDD SCAFFOLD STORY (features-first; emit when the Acceptance Spec has `new`-disposition rows):
282
+ ### WAVE-0 BDD SCAFFOLD STORY (features-first; emit when the Acceptance Spec has `new`-disposition rows)
274
283
 
275
284
  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).
276
285
 
@@ -285,7 +294,7 @@ When the Acceptance Spec contains **one or more `Disposition: new` rows**, you M
285
294
 
286
295
  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.
287
296
 
288
- **Worked example.** 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, and an empty `depends_on` (the JSON is indented to keep it inside this prompt block):
297
+ **Worked example.** 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, and an empty `depends_on`:
289
298
 
290
299
  {
291
300
  "slug": "scaffold-billing-feature-files",
@@ -306,12 +315,13 @@ When the Acceptance Spec contains **zero `new`-disposition rows** (every row is
306
315
 
307
316
  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.
308
317
 
309
- ### SCOPE-OVERLAP FLAGGING (docs/runbook downstream of config work):
318
+ ### SCOPE-OVERLAP FLAGGING (docs/runbook downstream of config work)
319
+
310
320
  When a "docs update" / "runbook" / "README" Story appears downstream of an earlier Story in the same Epic whose AC already covers updating the same document (e.g. a "config + runbook" Story followed by a "docs" Story touching the same runbook), the downstream Story's deliverable may be fully absorbed by the earlier Story. Flag the risk directly in the Story `body.acceptance` by appending an item of the form:
311
321
  "Scope verification note: this Story's deliverable may already be satisfied by Story #<slug-or-id>'s AC — before implementing, `git diff main -- <path>` against the upstream Story branch and confirm whether a substantive edit is still required, or whether only a cross-reference remains."
312
322
  This prevents the executing agent from redoing work the upstream Story already merged.
313
323
 
314
- ### CROSS-CUTTING CONFIG FILE EDITS (shared root files across Stories):
324
+ ### CROSS-CUTTING CONFIG FILE EDITS (shared root files across Stories)
315
325
 
316
326
  If two or more Stories in the same decomposition edit any of the shared
317
327
  configuration files enumerated below, you MUST either:
@@ -362,7 +372,7 @@ Shared configuration files (non-exhaustive):
362
372
  `.agentrc.json` (accepts `["…"]` to replace or `{ append: [...] }` to
363
373
  add to the framework default).
364
374
 
365
- ### WIDELY-USED SYMBOL DELETION (Story #2962):
375
+ ### WIDELY-USED SYMBOL DELETION (Story #2962)
366
376
 
367
377
  When a Story's `body.changes` declares `{ path, assumption: "deletes" }`,
368
378
  the decomposer probes the base branch at plan time via `git grep -l`
@@ -382,13 +392,10 @@ Do NOT silently allow two Stories to write the same root configuration
382
392
  file in the same wave; parallel dispatch would produce a merge conflict
383
393
  on every Story-to-Epic close after the first.
384
394
 
385
- CRITICAL: Dependencies should follow execution blockers. Stories attach directly to the Epic — never emit a 'parent_slug' field.
386
- IMPORTANT DEPENDENCY RULE: A Story's `depends_on` MUST only reference other Stories within the SAME Epic. If two Stories have a logical ordering requirement, express it via Story-level `depends_on`.
387
- WARNING: You MUST conserve your output limit. Do NOT generate more than ${maxTickets} tickets in total. Merge narrow work into cohesive capability Stories. Do NOT cut off the JSON array prematurely!
388
-
389
- ### RISK HEURISTICS (planning metadata if any apply):
390
- <rendered from `heuristics[]` in the context envelope; each item prepended with "- ".>
391
- ```
395
+ CRITICAL: Dependencies should follow execution blockers. Stories attach
396
+ directly to the Epic never emit a `parent_slug` field. A Story's
397
+ `depends_on` MUST only reference other Stories within the SAME Epic; express
398
+ any logical ordering requirement via Story-level `depends_on`.
392
399
 
393
400
  ## Constraints
394
401
 
@@ -1,5 +1,5 @@
1
1
  {
2
- "generatedAt": "2026-06-15T13:34:12.163Z",
2
+ "generatedAt": "2026-06-16T00:37:23.856Z",
3
3
  "generator": "generate-skills-index.js@1",
4
4
  "skills": [
5
5
  {
@@ -128,7 +128,7 @@
128
128
  "category": "core",
129
129
  "path": ".agents/skills/core/epic-plan-consolidate/SKILL.md",
130
130
  "description": "Run a holistic, pre-persist consolidation pass over the draft Story ticket array an Epic's decompose phase produced. Use during Phase 8 of `/plan`, after `epic-plan-decompose-author` writes `temp/epic-<Epic_ID>/tickets.json` and before `epic-plan-decompose.js` validates and persists it. Reconciles the draft against the Tech Spec \"Delivery Slicing\" target via scope-preserving operations only.",
131
- "policyCapsuleBullets": 8,
131
+ "policyCapsuleBullets": 9,
132
132
  "allowedTools": ["Read", "Write", "Bash"],
133
133
  "vendor": null
134
134
  },
@@ -21,8 +21,8 @@ path selection only — all phase content lives in the two path helpers:
21
21
  close-validation, epic-audit, code-review, retro, finalize, watch,
22
22
  auto-merge gate, cleanup).
23
23
  - [`helpers/deliver-stories.md`](helpers/deliver-stories.md) — the
24
- standalone multi-Story path (`stories-wave-tick.js` wave plan, operator
25
- confirmation, parallel fan-out to
24
+ standalone multi-Story path (`stories-wave-tick.js` continuous ready-set
25
+ loop, operator confirmation, parallel fan-out to
26
26
  [`helpers/single-story-deliver`](helpers/single-story-deliver.md)).
27
27
 
28
28
  ## Input matrix (authoritative)
@@ -100,13 +100,16 @@ plans).
100
100
  /deliver <id> <id> … --dep <from>:<to> --concurrency <n> --yes
101
101
  ```
102
102
 
103
- behaves exactly as the retired multi-Story command did: the same
104
- `stories-wave-tick.js` wave plan, the same operator confirmation gate
105
- (suppressed by `--yes`), and the same parallel fan-out — one Agent call per
106
- Story per wave, capped by the resolved `concurrencyCap` — to
107
- [`helpers/single-story-deliver`](helpers/single-story-deliver.md). The
108
- parallelism lives **inside** the standalone segment; segments themselves
109
- remain strictly sequential.
103
+ preserves the retired multi-Story command's full surface — the same flags,
104
+ the same operator confirmation gate (suppressed by `--yes`), and the same
105
+ parallel fan-out to
106
+ [`helpers/single-story-deliver`](helpers/single-story-deliver.md)but
107
+ schedules through `stories-wave-tick.js`'s **continuous ready-set loop**
108
+ (the shared `selectReadySet` core) rather than a static wave plan: each
109
+ Story dispatches the instant its own dependencies are done, capped by the
110
+ resolved global `concurrencyCap` and guarded against file-overlap
111
+ co-dispatch, exactly as the Epic path is. The parallelism lives **inside**
112
+ the standalone segment; segments themselves remain strictly sequential.
110
113
 
111
114
  ## Procedure
112
115