mandrel 2.56.0 → 2.58.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 (114) hide show
  1. package/.agents/agents/plan-critic.md +13 -18
  2. package/.agents/agents/story-worker.md +25 -33
  3. package/.agents/docs/agentrc-reference.json +0 -30
  4. package/.agents/docs/configuration.md +8 -28
  5. package/.agents/docs/execution-reference.md +5 -5
  6. package/.agents/docs/quality-gates.md +8 -7
  7. package/.agents/instructions.md +9 -10
  8. package/.agents/schemas/agentrc.schema.json +9 -185
  9. package/.agents/schemas/story-deliver-terminal.schema.json +1 -1
  10. package/.agents/scripts/acceptance-eval.js +107 -17
  11. package/.agents/scripts/ceremony-derive.js +191 -0
  12. package/.agents/scripts/check-context-budget.js +28 -33
  13. package/.agents/scripts/check-cyclomatic.js +4 -3
  14. package/.agents/scripts/deliver-light.js +31 -94
  15. package/.agents/scripts/evidence-gate.js +17 -1
  16. package/.agents/scripts/lib/audit-suite/checklist-threading.js +15 -2
  17. package/.agents/scripts/lib/baselines/coverage-updater-cli.js +110 -0
  18. package/.agents/scripts/lib/baselines/crap-preview-scan.js +25 -0
  19. package/.agents/scripts/lib/baselines/crap-updater-cli.js +223 -0
  20. package/.agents/scripts/lib/bdd-scenario-budget.js +21 -3
  21. package/.agents/scripts/lib/bootstrap/quality-bootstrap.js +0 -1
  22. package/.agents/scripts/lib/close-validation/gates.js +52 -1
  23. package/.agents/scripts/lib/config/acceptance-eval.js +25 -57
  24. package/.agents/scripts/lib/config/delivery-routing.js +7 -33
  25. package/.agents/scripts/lib/config/explain.js +0 -19
  26. package/.agents/scripts/lib/config/limits.js +18 -78
  27. package/.agents/scripts/lib/config/quality.js +6 -3
  28. package/.agents/scripts/lib/config/runners.js +3 -2
  29. package/.agents/scripts/lib/config-settings-schema-delivery.js +15 -68
  30. package/.agents/scripts/lib/config-settings-schema-quality.js +0 -14
  31. package/.agents/scripts/lib/config-settings-schema.js +16 -143
  32. package/.agents/scripts/lib/crap-engine.js +35 -4
  33. package/.agents/scripts/lib/crap-utils.js +17 -1
  34. package/.agents/scripts/lib/cyclomatic-ceiling.js +19 -7
  35. package/.agents/scripts/lib/generated/agentrc-validator.js +1 -1
  36. package/.agents/scripts/lib/observability/runtime-friction.js +1 -1
  37. package/.agents/scripts/lib/observability/source-classifier.js +1 -0
  38. package/.agents/scripts/lib/orchestration/acceptance-eval-decision.js +5 -4
  39. package/.agents/scripts/lib/orchestration/ceremony-routing.js +19 -73
  40. package/.agents/scripts/lib/orchestration/code-review.js +7 -3
  41. package/.agents/scripts/lib/orchestration/complexity-gate.js +46 -212
  42. package/.agents/scripts/lib/orchestration/file-assumptions.js +32 -17
  43. package/.agents/scripts/lib/orchestration/light-escalation.js +3 -3
  44. package/.agents/scripts/lib/orchestration/light-suitability.js +66 -233
  45. package/.agents/scripts/lib/orchestration/pinned-identifier-lint.js +137 -0
  46. package/.agents/scripts/lib/orchestration/plan-context.js +189 -387
  47. package/.agents/scripts/lib/orchestration/plan-critic-conditions.js +42 -153
  48. package/.agents/scripts/lib/orchestration/plan-critics-evaluate.js +14 -70
  49. package/.agents/scripts/lib/orchestration/plan-persist/acceptance-handle-repair.js +107 -0
  50. package/.agents/scripts/lib/orchestration/plan-persist/changes-repair.js +305 -0
  51. package/.agents/scripts/lib/orchestration/plan-persist/persist-helpers.js +138 -170
  52. package/.agents/scripts/lib/orchestration/plan-persist/run-plan-persist.js +128 -297
  53. package/.agents/scripts/lib/orchestration/plan-persist/soft-findings.js +55 -0
  54. package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +16 -65
  55. package/.agents/scripts/lib/orchestration/plan-persist/wave-serialisation.js +22 -35
  56. package/.agents/scripts/lib/orchestration/plan-text-hygiene.js +36 -135
  57. package/.agents/scripts/lib/orchestration/planning/memory-pool-advisory.js +61 -223
  58. package/.agents/scripts/lib/orchestration/review-base-ref.js +138 -0
  59. package/.agents/scripts/lib/orchestration/single-story-close/phases/close-validation.js +5 -0
  60. package/.agents/scripts/lib/orchestration/single-story-close/phases/code-review.js +37 -5
  61. package/.agents/scripts/lib/orchestration/single-story-close/phases/pre-gate-steps.js +46 -16
  62. package/.agents/scripts/lib/orchestration/single-story-close/runner.js +6 -1
  63. package/.agents/scripts/lib/orchestration/story-close/context-budget-writeback.js +213 -0
  64. package/.agents/scripts/lib/orchestration/task-body-validator.js +10 -63
  65. package/.agents/scripts/lib/orchestration/ticket-validator-conflicts.js +33 -539
  66. package/.agents/scripts/lib/orchestration/ticket-validator-sizing.js +21 -414
  67. package/.agents/scripts/lib/orchestration/ticket-validator.js +54 -118
  68. package/.agents/scripts/lib/orchestration/verify-credit.js +69 -24
  69. package/.agents/scripts/lib/story-body/body-format-lints.js +15 -85
  70. package/.agents/scripts/lib/story-body/story-body.js +54 -240
  71. package/.agents/scripts/lib/templates/decomposer-prompts.js +133 -121
  72. package/.agents/scripts/lib/test-isolate/cli-options.js +93 -0
  73. package/.agents/scripts/lib/test-isolate/progress-log.js +45 -0
  74. package/.agents/scripts/lib/test-isolate/render-report.js +97 -0
  75. package/.agents/scripts/lib/test-isolate/run-isolate.js +87 -0
  76. package/.agents/scripts/lib/test-run-credit.js +277 -0
  77. package/.agents/scripts/lib/wave-runner/footprint.js +48 -358
  78. package/.agents/scripts/lib/wave-runner/ready-set.js +6 -5
  79. package/.agents/scripts/lib/workers/crap-worker.js +32 -41
  80. package/.agents/scripts/plan-context.js +7 -9
  81. package/.agents/scripts/plan-critics.js +28 -54
  82. package/.agents/scripts/plan-persist.js +25 -68
  83. package/.agents/scripts/quality-preview.js +51 -0
  84. package/.agents/scripts/run-tests.js +12 -0
  85. package/.agents/scripts/stories-wave-tick.js +23 -45
  86. package/.agents/scripts/test-isolate.js +13 -180
  87. package/.agents/scripts/update-coverage-baseline.js +25 -70
  88. package/.agents/scripts/update-crap-baseline.js +19 -123
  89. package/.agents/skills/core/scope-triage/SKILL.md +3 -3
  90. package/.agents/workflows/audit-clean-code.md +4 -3
  91. package/.agents/workflows/helpers/acceptance-self-eval.md +41 -41
  92. package/.agents/workflows/helpers/code-quality-guardrails.md +4 -4
  93. package/.agents/workflows/helpers/code-review.md +2 -3
  94. package/.agents/workflows/helpers/deliver-digest.md +46 -55
  95. package/.agents/workflows/helpers/deliver-light.md +40 -105
  96. package/.agents/workflows/helpers/deliver-reference.md +1 -1
  97. package/.agents/workflows/helpers/deliver-story-reference.md +54 -55
  98. package/.agents/workflows/helpers/deliver-story.md +10 -13
  99. package/.agents/workflows/helpers/plan-reference.md +163 -221
  100. package/.agents/workflows/mandrel-plan.md +31 -40
  101. package/.agents/workflows/memory-consolidate.md +9 -13
  102. package/docs/CHANGELOG.md +36 -0
  103. package/lib/cli/registry.js +98 -2
  104. package/lib/migrations/index.js +4 -0
  105. package/lib/migrations/steps/2.57.0-retire-delivery-limit-knobs.js +45 -0
  106. package/lib/migrations/steps/2.57.0-retire-planning-limit-knobs.js +59 -0
  107. package/package.json +1 -1
  108. package/.agents/scripts/lib/framework-version.js +0 -39
  109. package/.agents/scripts/lib/orchestration/consolidation-precondition.js +0 -223
  110. package/.agents/scripts/lib/orchestration/plan-persist/fan-out-gate.js +0 -97
  111. package/.agents/scripts/lib/orchestration/planning/decomposer-context.js +0 -26
  112. package/.agents/scripts/lib/orchestration/spec-budget.js +0 -89
  113. package/.agents/scripts/lib/orchestration/spec-spill.js +0 -74
  114. package/.agents/scripts/lib/orchestration/verify-tier-repair.js +0 -107
@@ -1,10 +1,10 @@
1
1
  # /mandrel-plan — on-demand reference appendix
2
2
 
3
3
  > **Applies when:** you are executing [`/mandrel-plan`](../mandrel-plan.md) and hit one of the
4
- > situations below — input-mode derivation, the Gate #1 light handoff,
5
- > shape-derived complexity routing, tickets-mode supersede authoring, critic
6
- > dispatch detail, a failed persist, or source-id resolution. The spine stays
7
- > resident; this file is read on demand.
4
+ > situations below — input-mode derivation, the Gate #1 advisory line,
5
+ > tickets-mode supersede authoring, the operator-invoked pre-mortem, a
6
+ > failed persist, or source-id resolution. The spine stays resident; this
7
+ > file is read on demand.
8
8
 
9
9
  ## Deriving the input mode
10
10
 
@@ -92,140 +92,58 @@ The marker keeps the operator's undelegated decisions findable after the
92
92
  fact: reviewing a `--yes` plan means scanning its decisions-made-by-default,
93
93
  not re-deriving which assumptions were really the agent's to make.
94
94
 
95
- ## Gate #1 → the memory-pool advisory (`memoryPoolAdvisory`)
96
-
97
- On a truthy `memoryPoolAdvisory.recommend`, name
98
- [`/memory-consolidate`](../memory-consolidate.md) at Gate #1, quoting its
99
- `reasons[]`. Purely advisory: a stale pool degrades recall, it does not make
100
- the plan wrong, so it never blocks and never reroutes.
101
-
102
- ## Gate #1 → graduating a CI-gap intake filing (`intake`)
103
-
104
- The envelope's `priorFeedback` arrays carry the open `meta::*` feedback issues.
105
- A row flagged `intake: true` is a **CI-gap intake filing** — written by
106
- [`file-ci-gap.js`](../../scripts/file-ci-gap.js) when a delivery reached an
107
- Option-2 verdict in [`ci-remediation.md`](../../rules/ci-remediation.md) and the
108
- root cause was outside its scope. It carries evidence (failure signature, run
109
- link, occurrence history, ownership routing) but **no `## Spec`, no
110
- `acceptance[]` / `verify[]` and no `agent::*` label**, so `/mandrel-deliver`
111
- cannot take it: it is intake awaiting graduation, by design. Delivery files it
112
- and moves on rather than blocking on a planning pass nobody is present for.
113
-
114
- Graduating one is exactly **tickets mode**: `/mandrel-plan <issue number>`
115
- rewrites it into a Story, `supersedes[]` claims it, and persist closes it.
116
-
117
- At Gate #1, in **ask** and **seed** mode, name any open intake rows and offer
118
- that instead of the seed in front of you — a filing that keeps recurring
119
- (its `## Occurrences` table is the count) is usually the better next Story than
120
- whatever prompted this run. It is **advisory**: never reroute automatically, and
121
- skip the offer entirely under `--yes`, where nobody is at the keyboard to take
122
- it. A `platformGaps[]` row is the same shape with a different owner — the
123
- Story it graduates into may well be a config or runbook change rather than code.
124
-
125
- ## Gate #1 → the light path (in-session handoff)
126
-
127
- On a confirmed `deliverLightSuggestion`, `/mandrel-plan` routes into
128
- [`deliver-light.md`](deliver-light.md) **without ending the session**. Two
129
- things make that safe, and both are worth understanding before changing it:
130
-
131
- 1. **The handoff carries the envelope, not the seed.** Gate #1 already holds
132
- the interrogated `complexitySignals`; fill the light gate's `--creates`
133
- / `--refactors` / `--acceptance` / `--reason` from those. Re-deriving from
134
- raw seed text throws away the better signal and can disagree with the
135
- suggestion that routed you.
136
- 2. **The gate still runs.** The suggestion is read against seed-time ceilings
137
- (`DELIVER_LIGHT_SUGGESTION_CEILINGS` — artifacts, risk hits, sensitive-path
138
- classes); the light gate is read against the predicted work's effort and risk
139
- (`STORY_SHAPE_CEILINGS` — change kinds, magnitude, uncertainty, deployable
140
- span). Two different checks on purpose, so a confirm is not a bypass.
141
-
142
- **When the light gate answers `ask-operator`**, the two ceiling sets disagreed.
143
- Resume `/mandrel-plan` at step 2 (Author) **in this same session** — the interrogation
144
- is still valid and re-paying for it buys nothing. This bounce-back is not an
145
- escalation.
146
-
147
- **Under `--yes` the offer is recorded and planning proceeds** — it is *never*
148
- auto-downgraded to light. An unattended run has nobody to confirm the reroute,
149
- and a suggestion is not a confirmation. The same rule governs unknown triage
150
- unattended: AFK unknowns are still researched, but no free-form operator
151
- question is asked — each HITL unknown lands in Key Assumptions marked a
152
- decision-made-by-default, so the record shows what was decided for the operator
153
- rather than pretending it was decided with them.
154
-
155
- Escalation in the *other* direction — an over-scope prompt on the light path —
156
- is terminal and requires a fresh session. The rule that separates the two, and
157
- why it must not be flattened into symmetry:
158
- [`deliver-light.md` § Why the two directions differ](deliver-light.md).
159
-
160
- ## Gate #1 → the `/prototype` offer (`uiSurface`)
161
-
162
- `complexitySignals.uiSurface` is the second advisory Gate #1 offer, and the
163
- weaker of the two on purpose: it carries **no routing authority and adds no
164
- gate**. Both halves are derived from observables already in the checkout — the
165
- `hasWebSurface` applicability predicate the `target: "web"` audit lenses gate
166
- on, and whether any predicted path matches a web lens `filePattern` registered
167
- in `audit-rules.json`. There is no configuration key to set: a project with no
168
- rendered frontend resolves falsey and the offer never fires.
169
-
170
- When it does fire, **name [`/prototype`](../prototype.md) and stop there.**
171
- `/mandrel-plan` must never invoke it — operator invocation is the entire design, because
172
- the value is a human looking at a layout before its UI acceptance criteria are
173
- frozen.
174
-
175
- **Under `--yes` the offer is recorded and planning proceeds** — no reroute, no
176
- prototype written, no gate raised. This is exactly how `deliverLightSuggestion`
177
- behaves unattended, and for the same reason: an unattended run has nobody to
178
- review an artifact, so recording the offer is the whole of the right behaviour.
179
-
180
- ## Shape-derived complexity routing (`complexitySignals`)
181
-
182
- Complexity routes on the **objective shape of the authored work**, never on
183
- seed word count — a detailed prompt can describe trivial work, a terse one
184
- complex work. The pipeline stages the
185
- decision:
186
-
187
- - **Signals, not routing.** The envelope's `complexitySignals` field is
188
- advisory only (`routingAuthority: false`): enumerated-artifact count (with
189
- the configured `maxArtifacts` threshold beside it as one input),
190
- `planning.riskHeuristics` phrases present in the seed, the repo state of
191
- predicted paths (existing paths predict refactors; missing predict
192
- creates), and the `audit-rules.json` sensitive-path classes the predicted
193
- footprint intersects.
194
- - **You author the verdict.** Judge the signals: a genuinely trivial scope
195
- (small additive footprint, no risk hits, no sensitive class) earns a `lite`
196
- claim via `plan-persist.js --route-downgrade-reason "<why>"`. The reason is
197
- recorded on every created Story's `story-plan-state` checkpoint, making the
198
- judgment auditable; without a recorded reason the conservative default
199
- (`full`) stands.
200
- - **Persist backstops the claim deterministically.** After authoring, the
201
- work has measurable shape, so persist validates the `lite` claim against
202
- each Story's own shape — distinct change kinds, declared magnitude,
203
- uncertainty, deployable/migration span, glob-free footprint, and
204
- sensitive-path classes, against the framework `STORY_SHAPE_CEILINGS` (effort
205
- and risk, never artifact counts) — and **fails closed to
206
- `full`** when any Story exceeds them (the refusal is ledgered on the
207
- checkpoint too). The lite route is **not** licence to drop a
208
- non-negotiable — every decision's `preserves` field enumerates what still
209
- holds: the Story ticket, the PR-to-`main` landing, every repo quality gate,
210
- and the security baseline. Those gates run in `single-story-close.js`
211
- regardless of route.
212
-
213
- **The label is a hint; deliver re-derives.** Persist labels a
214
- lite cohort's Stories with **`route::lite`** as a *human-visible hint only* —
215
- `/mandrel-deliver` computes the route from each fetched Story body via the same shape
216
- function at dispatch, so neither a lost label nor an unread marker can
217
- misroute delivery: a lite-shaped Story derives `lite` even with the label
218
- absent, and a sensitive-footprint Story routes `full` and keeps its fresh
219
- critic even with the label present. The derived route sets ceremony, not
220
- where the engine runs — sub-agent boots are collapsed by a **single-Story
221
- run**, never by a trivial shape. The `route::*` axis stays runtime-derived: hand-authored
222
- `route::*` entries in `labels[]` are dropped by persist.
223
-
224
- The knobs (`planning.complexityGate.{enabled, maxArtifacts}`) are documented
225
- in [`.agents/docs/configuration.md`](../../docs/configuration.md) under
226
- `### planning`; the defaults live on `DEFAULT_COMPLEXITY_GATE` and the shape
227
- ceilings on `STORY_SHAPE_CEILINGS` in
228
- [`lib/orchestration/complexity-gate.js`](../../scripts/lib/orchestration/complexity-gate.js).
95
+ ## Gate #1 → the one advisory line
96
+
97
+ Gate #1 stops for exactly two things — the sharpened plan intent and any HITL
98
+ unknown — and everything else the envelope surfaced collapses to **one
99
+ advisory line** beneath it (Story #5312). Nothing on that line stops the run,
100
+ reroutes it, or is invoked by `/mandrel-plan`; each item names something the
101
+ operator may prefer to do instead, and the run proceeds either way. Under
102
+ `--yes` the line is recorded and planning continues — an unattended run has
103
+ nobody to take an offer.
104
+
105
+ The line names, in order, whichever of these the envelope carries:
106
+
107
+ - **`duplicates[]`** — open Stories the seed resembles (never Epics). Name
108
+ the top one or two by id and title; a plan that duplicates open work is
109
+ still the operator's call.
110
+ - **Open `intake` rows** (`priorFeedback`) — CI-gap intake filings written by
111
+ [`file-ci-gap.js`](../../scripts/file-ci-gap.js) when a delivery reached an
112
+ Option-2 verdict in [`ci-remediation.md`](../../rules/ci-remediation.md).
113
+ They carry evidence but no `## Spec`, no `acceptance[]` / `verify[]` and no
114
+ `agent::*` label, so `/mandrel-deliver` cannot take one: graduating it is
115
+ exactly **tickets mode** (`/mandrel-plan <issue number>`), and a filing
116
+ that keeps recurring (its `## Occurrences` table is the count) is often the
117
+ better next Story than the seed in front of you. A `platformGaps[]` row is
118
+ the same shape with a different owner.
119
+ - **`memoryPoolAdvisory.recommend`** — name
120
+ [`/memory-consolidate`](../memory-consolidate.md), quoting its
121
+ `reasons[]`. The one arm left measures the `MEMORY.md` index against the
122
+ harness's byte cap; a stale pool degrades recall, it does not make the plan
123
+ wrong.
124
+ - **`complexitySignals.uiSurface`** — name [`/prototype`](../prototype.md)
125
+ and stop there. The signal carries **no routing authority and adds no
126
+ gate**: both halves are derived from observables already in the checkout —
127
+ the `hasWebSurface` applicability predicate the `target: "web"` audit
128
+ lenses gate on, and whether any predicted path matches a web lens
129
+ `filePattern` in `audit-rules.json`; a project with no rendered frontend
130
+ resolves falsey and the offer never fires. `/mandrel-plan` must never invoke
131
+ it — operator invocation is the entire design, because the value is a human
132
+ looking at a layout before its UI acceptance criteria are frozen. Under
133
+ `--yes` the offer is recorded and planning proceeds — no reroute, no
134
+ prototype written, no gate raised.
135
+
136
+ ## `complexitySignals` are advisory
137
+
138
+ The envelope's `complexitySignals` field carries the paths the seed predicts,
139
+ their repo state (existing paths predict refactors; missing predict creates)
140
+ and the `audit-rules.json` sensitive-path classes the footprint intersects —
141
+ `routingAuthority: false`, no `route` field. They ground the authoring
142
+ template's pre-resolved `changes[]` and the `/prototype` offer, nothing else.
143
+ Story #5312 deleted the plan-side lite claim that used to read them
144
+ (`--route-downgrade-reason`, the persist shape backstop, the `route::lite`
145
+ hint): every Story lands through the same engine and the same close gates,
146
+ and ceremony is derived from the landed diff at close.
229
147
 
230
148
  ## Correct-by-construction authoring template
231
149
 
@@ -233,38 +151,43 @@ ceilings on `STORY_SHAPE_CEILINGS` in
233
151
  **correct-by-construction** skeleton, built from the same repo probe the
234
152
  `complexitySignals` ran:
235
153
 
236
- - **`verify[]` placeholders already end with a valid `(tier)` tag.** Keep
237
- every filled entry's trailing tag one of `(unit)` / `(contract)` /
238
- `(e2e)` / `(validate)` (or use the `manual:<reason>` escape) — a tierless
239
- entry is exactly the mechanical persist round-trip the template exists to
240
- prevent.
154
+ - **`verify[]` entries are commands.** There is no tier suffix and no
155
+ `manual:<reason>` escape (Story #5312): write the exact command or test
156
+ path the deliverer runs and the acceptance critic reads as evidence.
157
+ - **`changes[]` names what the deliverer authors.** Generated artifacts —
158
+ quality baselines, generated test indexes, migration journals, lockfiles —
159
+ are omitted: the work regenerates them, the refresh is a close-gate concern,
160
+ and a declared shared artifact path reserves a footprint that needlessly
161
+ serializes sibling Stories at dispatch.
241
162
  - **`changes[]` arrive pre-resolved to creates-vs-refactors.** Every path
242
163
  the seed predicted is probed against the repo: an existing path is
243
164
  emitted with `assumption: "refactors-existing"`, a missing one with
244
- `assumption: "creates"`. Trust the pre-resolved assumption — verify
245
- against the repo before overriding one (authoring `creates` for a file
246
- that exists at base is a validator rejection). The persist gates stay
247
- authoritative: they probe the base branch ref, not the working tree.
248
- - **Keep `## Spec` near contract-level prose.** **Aim for ~250 words; an
249
- advisory warning fires past 350** (`SPEC_SOFT_WORD_BUDGET`). Two numbers,
250
- two jobs: ~250 is the authoring target — the nudge toward a contract-level
251
- Spec (interfaces, invariants, load-bearing constraints; no per-file
252
- behavior narration) — while 350 is the slacker threshold at which persist
253
- actually warns, so the warning marks a real outlier instead of ordinary
254
- variance. Neither fails the persist. The hard fail-closed ceiling
255
- (~1500 tokens, `spec-spill.js`) is unchanged.
165
+ `assumption: "creates"`. The persist gates stay authoritative — they probe
166
+ the base branch ref, not the working tree — but a `creates` on a path that
167
+ exists at base, or a `refactors-existing` on one that does not, is a
168
+ dry-run **warning**, not a rejection; only a `deletes` naming an absent
169
+ path is refused. A plain-string bullet or a trailing parenthetical is
170
+ repaired into the object form by probing base, and the repair is reported.
171
+ - **Keep `## Spec` at contract-level prose** — interfaces, invariants,
172
+ load-bearing constraints; no per-file behavior narration — and as long as
173
+ the work needs. There is no word or token budget.
256
174
 
257
175
  A faithfully-filled skeleton — placeholders replaced, pre-resolved entries
258
- kept, tags valid — passes the persist ticket validators with no
259
- round-trip.
176
+ kept — passes the persist ticket validators with no round-trip.
260
177
 
261
178
  ### Authored entry shape
262
179
 
263
180
  Each `stories.json` entry: `slug` (`^[a-z0-9][a-z0-9-]*$`), `type: "story"`,
264
181
  `title`, `body` (`goal`, optional `spec`, `changes[{path, assumption}]` —
265
182
  `creates|refactors-existing|deletes`, `non_goals`, `reason_to_exist`),
266
- top-level `acceptance[]`, `verify[]` (`… (unit|contract|e2e|validate)`), and
267
- `depends_on[]` (a sibling slug, or `#<id>` for an existing open Story).
183
+ top-level `acceptance[]`, `verify[]` (each a **bare command** — there is no
184
+ tier suffix), and `depends_on[]` (a sibling slug, or `#<id>` for an existing
185
+ open Story).
186
+
187
+ Author `acceptance[]` **without** the `AC-<n>:` handle: the body renderer
188
+ numbers each checkbox from its array position, so a carried handle renders
189
+ doubled. Persist normalises one off rather than refusing, and names the strip
190
+ on the dry-run's repair list.
268
191
 
269
192
  Nothing in that shape inventories the repo for the author. `changes[]` arrives
270
193
  pre-resolved against the working tree, and Phase 8's
@@ -336,18 +259,28 @@ together, and a path two same-wave Stories both write is exactly where that
336
259
  promise breaks. Promise and caveat belong on one durable surface — previously
337
260
  the caveat was a stderr warning nobody kept.
338
261
 
339
- Two `planning.*` knobs upgrade a conflict class from advisory to a hard
340
- refusal. **Both default to `false` and are documented, not recommended:**
341
-
342
- | Knob | Upgrades | Why it is off |
343
- | --- | --- | --- |
344
- | `planning.failOnSharedEditors` | `shared-editor` → `hard` | Co-editing one file is routine and often correct; the delivery scheduler already serializes file-overlapping Stories. |
345
- | `planning.requireExplicitCrossStoryDeps` | `implicit-cross-story-dep` → `hard` | Path references are matched by substring, so a legitimate mention in prose can read as a dependency. |
346
-
347
- Turn one on for a repo where the class is genuinely fatal; expect a refusal to
348
- name the Stories and the fix (a `depends_on` edge, or folding the shared edit
349
- into one Story). The sibling knobs `failOnRegistryConflicts`,
350
- `failOnMissingBddScaffold` and `failOnLargeFanOut` behave the same way.
262
+ Every conflict class is advisory (Story #5312 retired the
263
+ `planning.failOn*` / `requireExplicitCrossStoryDeps` upgrade knobs with the
264
+ registry and fan-out findings): co-editing one file is routine and often
265
+ correct — the delivery scheduler already serializes file-overlapping Stories —
266
+ and a path reference matched by substring can read as a dependency a prose
267
+ mention never meant. A finding names the Stories and the fix (a `depends_on`
268
+ edge, or folding the shared edit into one Story) for the operator to weigh.
269
+
270
+ ## Tickets mode — the source ticket is evidence
271
+
272
+ A `--tickets` envelope carries a third author prompt beside
273
+ `systemPrompts.story` and `systemPrompts.storySplitRules`:
274
+ **`systemPrompts.storyTicketsRules`**. It exists because a
275
+ source ticket arrives already in Story shape — rendered `AC-<n>:` checkboxes,
276
+ a `## Verify` list, a `## Changes` footprint — and an author reading it as a
277
+ template carries that shape forward instead of re-deriving it. The addendum
278
+ binds the author to re-derive `acceptance[]` from the goal, to express
279
+ mechanical checks (a refreshed baseline, a lint exiting 0, a regenerated
280
+ index) as `verify[]` commands rather than acceptance items, and to take the
281
+ source's verify entries for the commands they name rather than their shape.
282
+ Read it whenever the mode is `tickets`; the other three modes do not carry
283
+ the field.
351
284
 
352
285
  ## Tickets mode — authoring `supersedes[]`
353
286
 
@@ -382,67 +315,76 @@ total by default — an authored map is the only thing that can say
382
315
  `#11-#14 → #20` while `#15 → #21`, which a blanket "superseded by
383
316
  this plan-run" reference could not.
384
317
 
385
- ## Critic dispatch detail
318
+ ## The pre-mortem critic — operator-invoked
319
+
320
+ The maker-blind **pre-mortem** critic is not a step of the spine (Story #5312
321
+ retired step 2.5 with the consolidation critic, whose one deterministic input
322
+ was a `## Delivery Slicing` table no Story carries). Run it when the operator
323
+ asks for it, after Author and before Persist — the last point a finding folds
324
+ into a re-author:
325
+
326
+ ```bash
327
+ node .agents/scripts/plan-critics.js \
328
+ --stories temp/plan-<slug>/stories.json \
329
+ [--tech-spec temp/plan-<slug>/techspec.md]
330
+ ```
386
331
 
387
- The **pre-mortem** critic fires on any of three deterministic triggers: the
388
- draft ticket count reaching half the reviewability budget, a
389
- `planning.riskHeuristics` phrase matching the plan text, or the
390
- **external-dependency** probe finding an out-of-repo marker — a
391
- scoped package the plan names that no repo manifest declares, a cross-repo
392
- `github.com/<owner>/<repo>` reference, or an endpoint named as a service
393
- prerequisite. That third trigger is what gives the default N=1 plan a cheap
394
- viability check, since the size trigger is unreachable at one ticket and this
395
- repo's resolved `riskHeuristics` is empty. The probe is conservative — explicit
396
- markers only, so a plan naming no such artifact dispatches exactly as before.
332
+ It fires on one deterministic trigger: the **external-dependency** probe
333
+ finding an out-of-repo marker — a scoped package the plan names that no repo
334
+ manifest declares, a cross-repo `github.com/<owner>/<repo>` reference, or an
335
+ endpoint named as a service prerequisite. The probe is conservative —
336
+ explicit markers only. It exits 0 on **any** verdict (verdicts route work,
337
+ they do not gate) and exits **1** only on a usage/IO error — no critic ran.
397
338
 
398
339
  ```jsonc
399
340
  {
400
- "consolidation": { "critic": "consolidation", "dispatch": false, "reasons": ["…"] },
401
- "premortem": { "critic": "pre-mortem", "dispatch": true, "reasons": ["…"] },
402
- "textHygiene": { "critic": "text-hygiene", "findings": [] }
341
+ "premortem": { "critic": "pre-mortem", "dispatch": true, "reasons": ["…"] }
403
342
  }
404
343
  ```
405
344
 
406
- The verdict's third entry, `textHygiene`, is advisory-only: it
407
- carries deterministic body lints (`dangling-citation` / `open-question` /
408
- `slicing-mass`) with no dispatch semantics — it spawns nothing and never
409
- gates the run. Fold `textHygiene.findings[]` into the re-author round the
410
- same way critic findings fold in: fix each named defect in `stories.json`
411
- (anchor or inline the citation, resolve the question into a declarative
412
- assumption, thin the Slicing checkpoint) and re-run the critic step. Empty
413
- `findings` add nothing to the round.
414
-
415
- **Dispatch shape.** When `delivery.routing.roleScopedAgents` is enabled (the
416
- **default**), dispatch each firing critic with `subagent_type: plan-critic` —
417
- it boots on the role-scoped [`plan-critic`](../../agents/plan-critic.md)
418
- context (its own system prompt, no `CLAUDE.md` @-closure) that carries the
419
- maker-blind invariant, the `consolidation` and `pre-mortem` charters, and the
420
- output shape standalone. When the kill-switch is off
421
- (`roleScopedAgents: false`) or the host cannot spawn at this depth, fall back
422
- to a generic sub-agent and hand it the same charter (the `consolidation` /
423
- `pre-mortem` definitions in [`plan-critic.md`](../../agents/plan-critic.md)).
424
- **When both critics fire, dispatch them in a single turn.** Consolidation and
425
- pre-mortem read the same immutable draft, share no write path, and neither
426
- consumes the other's verdict — the textbook independent fan-out of
427
- [`parallel-tooling.md`](parallel-tooling.md) Rule 3. Issue both `Agent` calls
428
- together in one assistant turn rather than awaiting the first verdict before
429
- spawning the second; serialized critics double the round's wall clock and buy
430
- nothing, because you fold both verdicts into the same re-author round anyway.
431
-
432
- Either way the critic is **maker-blind**: hand it the draft artifacts
433
- (`stories.json`, and `techspec.md` when present) — never the authoring
434
- transcript or the reasons the planner believed its own draft is sound. A
435
- critic that reads the maker's case grades the case, not the draft.
345
+ On `dispatch: true`, dispatch **one fresh-context, maker-blind sub-agent**.
346
+ When `delivery.routing.roleScopedAgents` is enabled (the **default**), use
347
+ `subagent_type: plan-critic` — it boots on the role-scoped
348
+ [`plan-critic`](../../agents/plan-critic.md) context (its own system prompt,
349
+ no `CLAUDE.md` @-closure) that carries the maker-blind invariant, the
350
+ `pre-mortem` charter, and the output shape standalone. When the kill-switch
351
+ is off (`roleScopedAgents: false`) or the host cannot spawn at this depth,
352
+ fall back to a generic sub-agent and hand it the same charter. Either way the
353
+ critic is **maker-blind**: hand it the draft artifacts (`stories.json`, and
354
+ `techspec.md` when present) — never the authoring transcript or the reasons
355
+ the planner believed its own draft is sound. A critic that reads the maker's
356
+ case grades the case, not the draft. Fold surviving findings into Gate #2 or
357
+ a re-author round.
436
358
 
437
359
  ## What `--dry-run` actually gates
438
360
 
439
361
  `plan-persist.js --dry-run` is the same command with GitHub writes suppressed,
440
- and every gate runs before the first `createIssue` would fire — the validator,
441
- the body parse, the DAG, the capacity and Spec-budget ceilings, the
442
- reachability check, the split and supersede partitions, and the Tech Spec fold.
443
- That is the whole point of running it first: a dry run that comes back clean
444
- has already paid for every deterministic refusal, so the real persist has
445
- nothing left to discover except network failure.
362
+ and every gate runs before the first `createIssue` would fire. Since
363
+ Story #5312 the gates split two ways, and the dry-run is where the second
364
+ half is read:
365
+
366
+ **Hard — the run refuses:** a body that does not parse, a ticket that is not
367
+ a Story, an empty `acceptance[]` or `verify[]`, an unknown or cyclic
368
+ `depends_on`, the acceptance partition at N>1, the supersede partition, a
369
+ forbidden commit-subject prefix, and a `deletes` entry naming a path absent
370
+ at base.
371
+
372
+ **Warnings — listed, then the persist proceeds:** a `creates` on a path that
373
+ exists at base or a `refactors-existing` on one that does not (including a
374
+ path the base branch deleted or renamed, named with the removing commit), a
375
+ goal or acceptance path absent at base, a `verify[]` command naming an absent
376
+ test file, an `open-question` in a body (`Flag if…`, `TBD`, a trailing `?`),
377
+ and a `pinned-identifier` in an acceptance item — a backticked bare symbol
378
+ that is not a path, a label, a kebab token, a flag or a command, which the
379
+ advisory `changes[]` is free to reshape out from under the criterion. The
380
+ list also names every **repair** the run applied — a plain-string bullet or a
381
+ trailing parenthetical rewritten into `{ path, assumption }` by probing base,
382
+ and an `AC-<n>:` handle normalised off an acceptance item. The same list rides the result
383
+ envelope as `warnings[]` and `repairs[]`, so a `--chain-on-clean` run loses
384
+ nothing.
385
+
386
+ A dry run that comes back clean has paid for every deterministic refusal, so
387
+ the real persist has nothing left to discover except network failure.
446
388
 
447
389
  ## The container Epic (Gate #3)
448
390
 
@@ -56,11 +56,14 @@ node .agents/scripts/plan-context.js --seed "<seed>" \
56
56
  and derives source ids from its `sourceTickets[]`; it also writes
57
57
  **`stories.template.json`**, step 2's skeleton.
58
58
 
59
- The envelope carries docs context, the story-author prompt, `sourceTickets[]`,
59
+ The envelope carries docs context, the story-author prompt (`systemPrompts.story`,
60
+ plus `systemPrompts.storySplitRules` for an N>1 draft and
61
+ `systemPrompts.storyTicketsRules` in tickets mode), `sourceTickets[]`,
60
62
  `duplicates[]` (open **Stories**, never Epics), `epicCandidates[]` +
61
63
  `dependencyCandidates[]` (Gate #3; path collisions), `priorFeedback` and
62
- advisory `complexitySignals` (**no routing authority**). A trivial scope claims
63
- the lite route at persist, failing closed to `full`.
64
+ advisory `complexitySignals` (**no routing authority**). An envelope over the
65
+ planner-context ceiling is written truncated with a `truncated` note, never
66
+ refused.
64
67
 
65
68
  **Triage each unknown by resolver** ([ref](helpers/plan-reference.md)): an
66
69
  **AFK** unknown (research settles it) is resolved before authoring, never
@@ -68,16 +71,13 @@ assumed; a **HITL** unknown goes to Gate #1. Under `--yes` do not ask free-form
68
71
  operator questions — AFK unknowns are still researched; only HITL unknowns land
69
72
  in Key Assumptions, each a decision-made-by-default.
70
73
 
71
- **Gate #1** — STOP to confirm the sharpened plan intent, any
72
- duplicate-candidate review, and any `intake` row worth graduating
73
- ([ref](helpers/plan-reference.md)). Under `--yes`, auto-proceed.
74
-
75
- On a truthy `deliverLightSuggestion.suggested`, offer — advisory, never an
76
- automatic reroute — to deliver the seed instead; on confirm route **in this
77
- session** into [`helpers/deliver-light.md`](helpers/deliver-light.md), its gate
78
- filled from this envelope. A truthy `complexitySignals.uiSurface` names
79
- [`/prototype`](prototype.md); never invoke it here.
80
- [Both](helpers/plan-reference.md).
74
+ **Gate #1** — STOP for exactly two things: confirm the sharpened plan intent,
75
+ and settle any HITL unknown the operator owns. Everything else the envelope
76
+ surfaced — `duplicates[]`, open `intake` rows, a truthy
77
+ `memoryPoolAdvisory.recommend`, a truthy `complexitySignals.uiSurface` naming
78
+ [`/prototype`](prototype.md) (never invoke it here) — collapses to
79
+ **one advisory line** under the gate; none of it stops the run or reroutes
80
+ it ([ref](helpers/plan-reference.md)). Under `--yes`, auto-proceed.
81
81
 
82
82
  ### 2. Author
83
83
 
@@ -93,34 +93,22 @@ included. One rescue: a **never-tracked** one normalises to `creates`. Fields:
93
93
  [ref](helpers/plan-reference.md).
94
94
 
95
95
  Artifacts under `temp/plan-<slug>/`: `stories.json` (**length 1 by default**;
96
- over-budget Specs fail closed — split or tighten, never under `docs/`); optional
96
+ a Spec is as long as the work needs, inline, never under `docs/`); optional
97
97
  `techspec.md` (**N===1 only**, folded into `## Spec`) and
98
98
  `acceptance-manifest.json` (N>1 — `--plan-acceptance`). Use the envelope
99
- `systemPrompts.story`; split only under the policy above.
99
+ `systemPrompts.story`; split only under the policy above, and when you do,
100
+ read `systemPrompts.storySplitRules` too — it carries the schedule and
101
+ partition rules the core omits. In **tickets mode** also read
102
+ `systemPrompts.storyTicketsRules`: the source ticket is evidence, not a
103
+ template — re-derive `acceptance[]` rather than carrying its list, handles
104
+ and tier suffixes forward.
100
105
 
101
106
  **Tickets mode:** every Story authors a top-level `supersedes[]`; persist
102
107
  refuses a partial map ([shape](helpers/plan-reference.md)).
103
108
 
104
- ### 2.5 Critics
105
-
106
- ```bash
107
- node .agents/scripts/plan-critics.js \
108
- --stories temp/plan-<slug>/stories.json \
109
- [--tech-spec temp/plan-<slug>/techspec.md]
110
- ```
111
-
112
- Run **before** persist — the last point a finding folds into a re-author. It
113
- exits 0 on **any** verdict (verdicts route work, they do not gate) and exits
114
- **1** only on a usage/IO error — no critic ran: **do not proceed to Persist**,
115
- fix and re-run.
116
-
117
- - **Both `dispatch: false`** — proceed to Persist (each skip is ledgered).
118
- - **Either `dispatch: true`** — dispatch **one fresh-context, maker-blind
119
- sub-agent per firing critic** (hand it only the draft artifacts, never the
120
- authoring transcript), fold findings into Gate #2 or a re-author round, re-run
121
- this step. Pre-mortem triggers (incl. the external-dependency probe), the
122
- advisory-only `textHygiene.findings[]` lints and dispatch shape:
123
- [reference](helpers/plan-reference.md).
109
+ The maker-blind **pre-mortem** critic is not a step of this spine: run
110
+ `plan-critics.js` only when the operator asks for it
111
+ ([how](helpers/plan-reference.md)).
124
112
 
125
113
  ### 3. Persist
126
114
 
@@ -132,7 +120,11 @@ to review (`--force-review`). Under `--yes`, auto-proceed.
132
120
  `--epic-goal`). Never unasked ([ref](helpers/plan-reference.md)).
133
121
 
134
122
  Run persist `--dry-run` **first** — same command, writes suppressed; every gate
135
- runs before the first `createIssue` ([list](helpers/plan-reference.md)):
123
+ runs before the first `createIssue`, and the run **lists its warnings**
124
+ (a `creates` / `refactors-existing` the base branch disagrees with, a goal or
125
+ acceptance path absent at base, an open question in a body) and the
126
+ `changes[]` repairs it applied ([list](helpers/plan-reference.md)). Read
127
+ them; they never stop the persist:
136
128
 
137
129
  ```bash
138
130
  node .agents/scripts/plan-persist.js \
@@ -144,8 +136,8 @@ node .agents/scripts/plan-persist.js \
144
136
  [--epic <id> | --epic-title "<name>" --epic-goal "<one paragraph>"]
145
137
  ```
146
138
 
147
- At lite shape `--chain-on-clean` folds a clean dry-run into the persist; a full
148
- plan keeps its review trip.
139
+ `--chain-on-clean` folds a clean dry-run into the persist for **any** plan —
140
+ the dry-run's warning list is the review.
149
141
 
150
142
  Persist creates `type::story` issue(s), a **metadata-only** `plan-run::<id>`
151
143
  label, `blocked by #<id>` footers for every `depends_on` edge, and on a Gate #3
@@ -155,8 +147,7 @@ also comments on and closes each source id ([ref](helpers/plan-reference.md)).
155
147
 
156
148
  ## Constraints
157
149
 
158
- - `/mandrel-plan` starts delivery **only** through a confirmed Gate #1 light
159
- route — never off its Stories, which land via
150
+ - `/mandrel-plan` never starts delivery — its Stories land via
160
151
  [`/mandrel-deliver`](mandrel-deliver.md).
161
152
  - Duplicate search targets open Stories (`type::story`), not Epics; and
162
153
  deterministic gates still fail closed under `--yes`.
@@ -97,17 +97,14 @@ Then write the receipt to `.consolidation-stamp.json` in the pool root:
97
97
  ```
98
98
 
99
99
  `entryCount` is the surviving non-index `*.md` count **after** the rewrite —
100
- count the directory, never the plan. It is the baseline the next run measures
101
- growth against, so a wrong number silently mis-arms the nudge.
102
-
103
- The `/mandrel-plan` Phase 0 advisory re-arms on exactly three conditions: the
104
- stamp aging past `planning.memoryPool.staleAfterDays` (30),
105
- `planning.memoryPool.growthDelta` (25) entries written since that count, or
106
- `MEMORY.md` exceeding `planning.memoryPool.indexByteCeiling` (24576) bytes.
107
- Pool size alone never triggers it — a pass that keeps every entry still quiets
108
- the first two arms. A stamp with no `entryCount` leaves growth unmeasured, and
109
- only the age and index arms can speak until the next pass writes one; a stamp
110
- dated in the future reads as no stamp at all.
100
+ count the directory, never the plan. The stamp is the operator's record of
101
+ the pass; nothing in the framework reads it back.
102
+
103
+ The `/mandrel-plan` Phase 0 advisory re-arms on exactly one condition:
104
+ `MEMORY.md` exceeding `planning.memoryPool.indexByteCeiling` (24576) bytes —
105
+ the harness cap past which the index it loads is truncated. Pool size and
106
+ stamp age never trigger it (Story #5312 retired those arms): a pass that
107
+ rewrites long index lines short quiets it without pruning an entry.
111
108
 
112
109
  Write it **only** after Gate #2 — the stamp asserts an operator reviewed the
113
110
  pass, so writing it early makes it a lie.
@@ -116,8 +113,7 @@ Close with counts: entries read, corrected, merged, pruned, the new total, and
116
113
  **the rewritten `MEMORY.md`'s size in bytes beside that count** — the index is
117
114
  truncated at the byte ceiling, so a pass that pruned entries but left the
118
115
  index over the cap has not fixed the loss, and the number is the only way the
119
- operator can see that. Then the forecast the operator would otherwise derive
120
- by hand: when the advisory next fires, and which arm reaches it first.
116
+ operator can see that.
121
117
 
122
118
  ## Constraints
123
119