mandrel 2.58.0 → 2.60.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 (124) hide show
  1. package/.agents/README.md +17 -12
  2. package/.agents/agents/acceptance-critic.md +24 -43
  3. package/.agents/agents/story-worker.md +18 -19
  4. package/.agents/docs/SDLC.md +12 -13
  5. package/.agents/docs/agentrc-reference.json +1 -2
  6. package/.agents/docs/configuration.md +29 -46
  7. package/.agents/docs/quality-gates.md +9 -5
  8. package/.agents/docs/workflows.md +1 -1
  9. package/.agents/instructions.md +5 -7
  10. package/.agents/rules/ci-remediation.md +41 -8
  11. package/.agents/rules/known-tooling-behavior.md +65 -15
  12. package/.agents/runtime-deps.json +7 -2
  13. package/.agents/schemas/acceptance-eval-verdict.schema.json +1 -1
  14. package/.agents/schemas/agentrc.schema.json +6 -11
  15. package/.agents/schemas/crap-baseline.schema.json +1 -1
  16. package/.agents/schemas/crap-report.schema.json +1 -1
  17. package/.agents/schemas/story-deliver-terminal.schema.json +3 -3
  18. package/.agents/scripts/README.md +11 -1
  19. package/.agents/scripts/acceptance-eval.js +25 -27
  20. package/.agents/scripts/ceremony-derive.js +15 -10
  21. package/.agents/scripts/check-context-budget.js +148 -228
  22. package/.agents/scripts/check-schema-references.js +5 -3
  23. package/.agents/scripts/check-workflow-citations.js +33 -147
  24. package/.agents/scripts/coverage-capture.js +7 -4
  25. package/.agents/scripts/deliver-light.js +41 -100
  26. package/.agents/scripts/deliver-run.js +631 -0
  27. package/.agents/scripts/file-ci-gap.js +59 -11
  28. package/.agents/scripts/install-matrix-assert.js +48 -3
  29. package/.agents/scripts/lib/audit-to-stories/seed-from-findings.js +51 -33
  30. package/.agents/scripts/lib/baselines/crap-preview-incremental.js +6 -2
  31. package/.agents/scripts/lib/baselines/kinds/_crap-read.js +0 -8
  32. package/.agents/scripts/lib/baselines/kinds/crap.js +35 -18
  33. package/.agents/scripts/lib/changed-files.js +30 -0
  34. package/.agents/scripts/lib/config/delivery-routing.js +5 -4
  35. package/.agents/scripts/lib/config/explain.js +1 -3
  36. package/.agents/scripts/lib/config/gates/crap-incremental-coverage.schema.js +1 -1
  37. package/.agents/scripts/lib/config-resolver.js +1 -0
  38. package/.agents/scripts/lib/config-settings-schema-delivery.js +28 -21
  39. package/.agents/scripts/lib/coverage-capture-fullscope.js +10 -2
  40. package/.agents/scripts/lib/coverage-capture-incremental.js +3 -2
  41. package/.agents/scripts/lib/coverage-capture-usage.js +4 -1
  42. package/.agents/scripts/lib/crap-engine.js +2 -2
  43. package/.agents/scripts/lib/crap-utils.js +21 -5
  44. package/.agents/scripts/lib/doc-tiers.js +4 -2
  45. package/.agents/scripts/lib/escomplex-ast-compat.js +39 -17
  46. package/.agents/scripts/lib/escomplex-kernel.js +298 -0
  47. package/.agents/scripts/lib/feedback-loop/graduator-core.js +7 -6
  48. package/.agents/scripts/lib/feedback-loop/retro-proposals-graduator.js +7 -5
  49. package/.agents/scripts/lib/generated/agentrc-validator.js +1 -1
  50. package/.agents/scripts/lib/gh-exec.js +160 -0
  51. package/.agents/scripts/lib/maintainability-engine.js +3 -3
  52. package/.agents/scripts/lib/observability/source-classifier.js +1 -0
  53. package/.agents/scripts/lib/orchestration/ceremony-routing.js +74 -132
  54. package/.agents/scripts/lib/orchestration/ci-rerun-guard.js +123 -12
  55. package/.agents/scripts/lib/orchestration/complexity-gate.js +180 -352
  56. package/.agents/scripts/lib/orchestration/light-suitability.js +71 -136
  57. package/.agents/scripts/lib/orchestration/plan-context.js +44 -50
  58. package/.agents/scripts/lib/orchestration/plan-persist/changes-repair.js +8 -6
  59. package/.agents/scripts/lib/orchestration/plan-persist/run-plan-persist.js +104 -119
  60. package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +41 -25
  61. package/.agents/scripts/lib/orchestration/plan-persist/summary.js +11 -11
  62. package/.agents/scripts/lib/orchestration/plan-persist/supersede-ops.js +63 -29
  63. package/.agents/scripts/lib/orchestration/plan-persist/wave-collision-gate.js +107 -0
  64. package/.agents/scripts/lib/orchestration/review-depth.js +14 -11
  65. package/.agents/scripts/lib/orchestration/run-epilogue.js +260 -182
  66. package/.agents/scripts/lib/orchestration/run-scoped-config.js +63 -99
  67. package/.agents/scripts/lib/orchestration/single-story-close/phases/base-sync.js +3 -3
  68. package/.agents/scripts/lib/orchestration/single-story-close/phases/graphql-preflight.js +137 -0
  69. package/.agents/scripts/lib/orchestration/single-story-close/runner.js +105 -18
  70. package/.agents/scripts/lib/orchestration/story-deliver-terminal.js +4 -3
  71. package/.agents/scripts/lib/orchestration/story-follow-ups.js +156 -39
  72. package/.agents/scripts/lib/orchestration/story-init-envelope.js +71 -0
  73. package/.agents/scripts/lib/orchestration/task-body-validator.js +8 -17
  74. package/.agents/scripts/lib/orchestration/ticket-validator-conflicts.js +25 -209
  75. package/.agents/scripts/lib/orchestration/ticket-validator-sizing.js +8 -5
  76. package/.agents/scripts/lib/orchestration/ticket-validator.js +44 -183
  77. package/.agents/scripts/lib/orchestration/ticketing/reads.js +14 -25
  78. package/.agents/scripts/lib/runtime-deps/dep-resolution.js +155 -0
  79. package/.agents/scripts/lib/runtime-deps/ensure-installed.js +44 -9
  80. package/.agents/scripts/lib/runtime-deps/parser-major.js +110 -0
  81. package/.agents/scripts/lib/runtime-deps/preflight.js +6 -25
  82. package/.agents/scripts/lib/runtime-deps/scan-imports.js +46 -1
  83. package/.agents/scripts/lib/skills/walk-skill-files.js +1 -1
  84. package/.agents/scripts/lib/story-body/body-format-lints.js +58 -12
  85. package/.agents/scripts/lib/story-body/story-body.js +83 -29
  86. package/.agents/scripts/lib/templates/decomposer-prompts.js +28 -33
  87. package/.agents/scripts/lib/wave-runner/live-probe.js +31 -5
  88. package/.agents/scripts/merge-baseline.js +4 -5
  89. package/.agents/scripts/plan-context.js +117 -28
  90. package/.agents/scripts/plan-persist.js +79 -39
  91. package/.agents/scripts/plan-run-epilogue.js +11 -8
  92. package/.agents/scripts/pr-watch-with-update.js +9 -2
  93. package/.agents/scripts/run-verify.js +13 -6
  94. package/.agents/scripts/single-story-init.js +7 -57
  95. package/.agents/scripts/stories-wave-tick.js +160 -26
  96. package/.agents/skills/core/gates-and-baselines/reference.md +0 -1
  97. package/.agents/skills/skills.index.json +2 -12
  98. package/.agents/skills/stack/qa/playwright/SKILL.md +26 -0
  99. package/.agents/workflows/audit-to-stories.md +14 -11
  100. package/.agents/workflows/helpers/acceptance-self-eval.md +84 -157
  101. package/.agents/workflows/helpers/code-review.md +4 -2
  102. package/.agents/workflows/helpers/deliver-digest.md +31 -24
  103. package/.agents/workflows/helpers/deliver-light.md +92 -101
  104. package/.agents/workflows/helpers/deliver-reference.md +116 -100
  105. package/.agents/workflows/helpers/deliver-story-reference.md +58 -124
  106. package/.agents/workflows/helpers/deliver-story.md +17 -18
  107. package/.agents/workflows/helpers/plan-reference.md +82 -60
  108. package/.agents/workflows/mandrel-deliver.md +47 -31
  109. package/.agents/workflows/mandrel-plan.md +32 -30
  110. package/.agents/workflows/mandrel-update.md +36 -21
  111. package/README.md +3 -3
  112. package/docs/CHANGELOG.md +43 -0
  113. package/lib/cli/registry.js +45 -25
  114. package/lib/cli/update.js +376 -17
  115. package/lib/migrations/index.js +2 -0
  116. package/lib/migrations/steps/2.60.0-retire-audit-results-autofile.js +40 -0
  117. package/package.json +8 -2
  118. package/.agents/schemas/model-attribution.schema.json +0 -53
  119. package/.agents/scripts/lib/orchestration/model-attribution.js +0 -418
  120. package/.agents/scripts/lib/orchestration/split-policy-validator.js +0 -188
  121. package/.agents/scripts/lib/orchestration/story-plan-state.js +0 -33
  122. package/.agents/scripts/lib/orchestration/structured-comment-parser.js +0 -67
  123. package/.agents/scripts/lib/templates/spec-author-prompts.js +0 -76
  124. package/.agents/skills/core/scope-triage/SKILL.md +0 -48
@@ -16,35 +16,29 @@
16
16
  *
17
17
  * ## Four invariants keep it proportional, not a planning bypass
18
18
  *
19
- * 1. **Suitability gate ({@link deriveLightSuitability}).** The prompt's
20
- * predicted footprint is judged by the **shared shape machinery**
19
+ * 1. **Risk gate ({@link deriveLightSuitability}).** The prompt's predicted
20
+ * footprint is judged for **risk** by the shared machinery
21
21
  * ({@link module:lib/orchestration/complexity-gate.deriveStoryShape} over
22
- * {@link module:lib/orchestration/complexity-gate.STORY_SHAPE_CEILINGS})
23
- * **and** a ledgered model verdict carrying a recorded reason
24
- * ({@link resolveLedgeredVerdict}). Both must agree on `lite`; either
25
- * falling short fails closed to `full`. The shape axes are effort and
26
- * risk — distinct change kinds, a coarse magnitude bucket, uncertainty,
27
- * and epic-scope span — never artifact counts (Story #4764), so this gate
28
- * is deliberately **coarse**: it rejects clearly-epic work, and invariant
29
- * 3 below does the real enforcement against ground truth.
30
- * 2. **The predicted shape is a warning, not a gate ({@link
31
- * resolveLightGateOutcome}, Story #5313).** An over-ceiling prediction
32
- * proceeds light with a `warnings[]` entry naming the exceeded axis — the
33
- * prediction is a guess, and invariant 3 bounds the real change set. Only
34
- * the two things no re-slicing can fix still refuse: an un-ledgered
35
- * verdict and an un-waivable risk rule (a sensitive-path class or a
36
- * migration span), which route `full` through an `escalated` terminal.
37
- * The former `ask-operator` outcome and its `--operator-proceed-light`
38
- * answer are gone with the gate they answered.
22
+ * the `audit-rules.json` sensitive-path classes and the
23
+ * migration-with-consumers span) **and** a ledgered verdict carrying a
24
+ * recorded reason ({@link resolveLedgeredVerdict}). A tripped risk rule
25
+ * or an unrecorded reason fails closed to `full`.
26
+ * 2. **Nothing the caller declares about its own size decides (Story
27
+ * #5344).** The predicted-shape ceilings — declared change kinds, a
28
+ * magnitude bucket, an uncertainty bucket, a deployable span — are gone,
29
+ * along with the `warnings[]` Story #5313 had already demoted them to.
30
+ * They were self-declared by the agent asking to proceed, and once they
31
+ * only warned they decided nothing at all. What is left reads evidence:
32
+ * the predicted PATHS at the gate, and the actual diff at invariant 3.
39
33
  * 3. **Diff-derived backstop ({@link checkLightDiffBackstop}).** After
40
34
  * implementation the **actual** change set is re-checked with
41
35
  * {@link module:lib/orchestration/review-depth.deriveChangeLevel} plus the
42
36
  * implementation-only magnitude ceilings of {@link LIGHT_DIFF_CEILINGS} —
43
- * the diff is the real scope signal — and an over-ceiling diff is blocked
44
- * rather than landed silently. Story #4856 moved this from a `maxFiles: 4`
37
+ * the diff is the real scope signal, and since Story #5344 it is the ONLY
38
+ * size block on the path. Story #4856 moved it from a `maxFiles: 4`
45
39
  * cardinality ceiling to changed lines over implementation files, and made
46
- * a block **recycle** its receipt Story through `/mandrel-plan` tickets mode
47
- * instead of orphaning it.
40
+ * a block **recycle** its receipt Story through `/mandrel-plan` tickets
41
+ * mode instead of orphaning it.
48
42
  * 4. **Minimal receipt Story ({@link buildReceiptStoryTicket}).** A
49
43
  * `type::story` ticket is authored inline so `refs #`, history, telemetry,
50
44
  * and the `agent::executing -> agent::done` state machine survive.
@@ -56,11 +50,7 @@
56
50
  * @module lib/orchestration/light-suitability
57
51
  */
58
52
 
59
- import {
60
- deriveStoryShape,
61
- SHAPE_CODES,
62
- STORY_SHAPE_CEILINGS,
63
- } from './complexity-gate.js';
53
+ import { deriveStoryShape, SHAPE_CODES } from './complexity-gate.js';
64
54
  import { deriveChangeLevel } from './review-depth.js';
65
55
 
66
56
  /**
@@ -71,17 +61,16 @@ import { deriveChangeLevel } from './review-depth.js';
71
61
  * No re-slicing, shrinking, or operator answer satisfies one: a footprint
72
62
  * intersecting a sensitive-path class routes `full` however small the change,
73
63
  * and the diff backstop refuses the same footprint again at the end. But
74
- * {@link deriveStoryShape} reports only the **first** rule a shape trips and
75
- * evaluates the ceiling rules first, so a prompt tripping both `change-kinds`
76
- * and `sensitive-path` is reported as a size objection — which reads as
77
- * appealable, is waivable by an attended operator, and sends the work all the
78
- * way to an implementation the backstop then refuses.
64
+ * {@link deriveStoryShape} reports only the **first** rule a footprint trips,
65
+ * and it can reject on an unknown footprint (a glob, an absent acceptance
66
+ * list) before either risk rule is reached — so the recorded `code` is not a
67
+ * reliable answer to "is this risk or is this something I can fix?".
79
68
  *
80
- * The recovery is that the shape decision attaches the built effort shape to
69
+ * The recovery is that the shape decision attaches the built risk shape to
81
70
  * every footprint it can judge at all, and that shape carries the risk facts
82
- * (`sensitiveClasses`, `migrationSpan`) whether or not a risk rule fired.
83
- * Reading them here surfaces the objection first-hit reporting hides — the
84
- * difference between a wasted session and a redirected one.
71
+ * (`sensitiveClasses`, `migrationSpan`) whether or not a risk rule was the
72
+ * recorded one. Reading them here surfaces the objection first-hit reporting
73
+ * hides — the difference between a wasted session and a redirected one.
85
74
  *
86
75
  * Pure and total.
87
76
  *
@@ -193,96 +182,87 @@ function resolveDiffCeilings(ceilings) {
193
182
  }
194
183
 
195
184
  /**
196
- * Resolve the model's trivial-vs-standard verdict, held to a ledgering
197
- * contract: a `lite` route counts **only** with a non-empty recorded reason. A lite claim
198
- * without a recorded reason, or any non-`lite` route, fails closed to `full` —
199
- * an unaudited "trust me, it's small" never buys the light path.
185
+ * Resolve the model's ledgered light verdict: the **recorded reason** is the
186
+ * whole of it. A prompt arriving with no reason fails closed to `full` — an
187
+ * unaudited "trust me, it's small" never buys the light path.
188
+ *
189
+ * Story #5344 removed the `--route lite|full` half. It was a second
190
+ * self-declaration on top of the reason, and it carried no information the
191
+ * reason did not: a caller writing a reason is claiming `lite`, and one that
192
+ * meant `full` would not be invoking this gate. What survives is the part that
193
+ * leaves a record a human can read afterwards.
194
+ *
195
+ * Story #5366 removed the `route` field this used to carry beside `recorded`.
196
+ * It was a second spelling of the same boolean — `recorded === false` IS the
197
+ * fail-closed route — and two fields that must agree are a chance for them
198
+ * not to.
200
199
  *
201
200
  * Pure and total.
202
201
  *
203
- * @param {{ route?: unknown, reason?: unknown }} [verdict]
202
+ * @param {{ reason?: unknown }} [verdict]
204
203
  * @returns {{
205
- * route: 'lite'|'full',
206
204
  * reason: string|null,
207
205
  * recorded: boolean,
208
206
  * note: string,
209
207
  * }}
210
208
  */
211
- export function resolveLedgeredVerdict({ route, reason } = {}) {
209
+ export function resolveLedgeredVerdict({ reason } = {}) {
212
210
  const recordedReason = typeof reason === 'string' ? reason.trim() : '';
213
- if (route !== 'lite') {
214
- return {
215
- route: 'full',
216
- reason: recordedReason || null,
217
- recorded: recordedReason !== '',
218
- note: 'model verdict is not lite — standard /mandrel-plan route',
219
- };
220
- }
221
211
  if (recordedReason === '') {
222
212
  return {
223
- route: 'full',
224
213
  reason: null,
225
214
  recorded: false,
226
- note: 'lite claim without a recorded reason — fails closed to full (the verdict must be ledgered)',
215
+ note: 'no recorded reason — fails closed to full (the light verdict must be ledgered)',
227
216
  };
228
217
  }
229
218
  return {
230
- route: 'lite',
231
219
  reason: recordedReason,
232
220
  recorded: true,
233
- note: `model verdict: lite (recorded reason): ${recordedReason}`,
221
+ note: `light verdict (recorded reason): ${recordedReason}`,
234
222
  };
235
223
  }
236
224
 
237
225
  /**
238
226
  * Judge whether an operator prompt's predicted footprint is suitable for the
239
- * light path. The deterministic effort/risk derivation and the ledgered model
240
- * verdict must **both** agree on `lite`; anything else — clearly-epic work, a
241
- * sensitive-path footprint, an unledgered verdict — resolves to `full` (the
242
- * conservative default that routes the operator to `/mandrel-plan`).
243
- *
244
- * The predicted axes are declared by the caller: `predictedKinds` (the distinct
245
- * kinds of change; absent, each entry's `assumption` is its kind, so N
246
- * instances of one mechanical edit count once), `predictedMagnitude`
247
- * (`trivial` | `moderate` | `substantial`), and `predictedUncertainty`
248
- * (`determined` | `needs-design`). A malformed bucket fails closed; an absent
249
- * one carries no signal, because a marginal footprint must not be rejected on
250
- * counts the diff backstop is the right place to enforce.
227
+ * light path. Two things can refuse, and both are checks the caller cannot
228
+ * satisfy by re-describing its own request: an **un-waivable risk rule** read
229
+ * off the predicted paths (a sensitive-path class, a migration paired with its
230
+ * consumers) and an **un-ledgered verdict** (no recorded reason). Everything
231
+ * else proceeds light and is bounded for real by
232
+ * {@link checkLightDiffBackstop} against the actual diff.
233
+ *
234
+ * Story #5344 removed the declared effort axes — `predictedKinds`,
235
+ * `predictedMagnitude`, `predictedUncertainty` — and the `warnings[]` Story
236
+ * #5313 had demoted them to. A bucket the caller picks about its own request
237
+ * is not a measurement, and once it only warned it was not even a gate.
238
+ * Story #5366 removed `predictedAcceptance` for the same reason from the
239
+ * other end: its zero-check was the only thing that read it, and the
240
+ * `--acceptance` flag that fed it clamped to a floor of one.
241
+ *
242
+ * The result reports `suitable` and nothing that restates it. The `route`
243
+ * field it used to carry was a second spelling of that same boolean, and no
244
+ * caller read it — {@link resolveLightGateOutcome} branches on `suitable`.
251
245
  *
252
246
  * Pure and total: never throws, never mutates its inputs.
253
247
  *
254
248
  * @param {{
255
249
  * predictedChanges?: unknown,
256
- * predictedAcceptance?: unknown,
257
- * predictedKinds?: unknown,
258
- * predictedMagnitude?: unknown,
259
- * predictedUncertainty?: unknown,
260
- * verdict?: { route?: unknown, reason?: unknown },
250
+ * verdict?: { reason?: unknown },
261
251
  * injectedRules?: object,
262
252
  * selectSensitivePathClassesFn?: Function,
263
253
  * }} [args]
264
254
  * @returns {{
265
255
  * suitable: boolean,
266
- * route: 'lite'|'full',
267
256
  * shape: ReturnType<typeof deriveStoryShape>,
268
257
  * ledger: ReturnType<typeof resolveLedgeredVerdict>,
269
258
  * unwaivable: ReturnType<typeof deriveUnwaivableRisk>,
270
- * ceilings: typeof STORY_SHAPE_CEILINGS,
271
259
  * reasons: string[],
272
- * warnings: string[],
273
260
  * }} `unwaivable` names an absolute risk rule the predicted footprint trips
274
- * even when the recorded `shape.code` is a size prediction (Story #4875), so
261
+ * even when the recorded `shape.code` is something else (Story #4875), so
275
262
  * the operator learns at prediction time that no re-slicing can help.
276
- * `warnings` carries the predicted-shape objection when the shape is past a
277
- * light ceiling (Story #5313): it names the exceeded axis, and it never
278
- * decides `suitable` — the diff backstop bounds the real change set.
279
263
  */
280
264
  export function deriveLightSuitability({
281
265
  predictedChanges,
282
- predictedAcceptance,
283
- predictedKinds,
284
- predictedMagnitude,
285
- predictedUncertainty,
286
266
  verdict,
287
267
  injectedRules,
288
268
  selectSensitivePathClassesFn,
@@ -290,59 +270,22 @@ export function deriveLightSuitability({
290
270
  const ledger = resolveLedgeredVerdict(verdict ?? {});
291
271
  const shape = deriveStoryShape({
292
272
  changes: predictedChanges,
293
- acceptance: predictedAcceptance,
294
- kinds: predictedKinds,
295
- magnitude: predictedMagnitude,
296
- uncertainty: predictedUncertainty,
297
273
  injectedRules,
298
274
  selectSensitivePathClassesFn,
299
275
  });
300
276
  const unwaivable = deriveUnwaivableRisk(shape);
301
- // Story #5313: the predicted shape no longer decides. A tripped risk rule is
302
- // decisive on its own — a sensitive footprint can never be lite — and the
303
- // ledgered verdict must still be lite; everything the shape ceilings say is
304
- // carried as a warning for the operator and bounded for real by the backstop.
305
- const suitable = ledger.route === 'lite' && !unwaivable.present;
277
+ const suitable = ledger.recorded && !unwaivable.present;
306
278
  const reasons = [`shape: ${shape.reasons[0]}`];
307
279
  if (unwaivable.present) reasons.push(unwaivable.reason);
308
280
  reasons.push(`verdict: ${ledger.note}`);
309
- return {
310
- suitable,
311
- route: suitable ? 'lite' : 'full',
312
- shape,
313
- ledger,
314
- unwaivable,
315
- ceilings: STORY_SHAPE_CEILINGS,
316
- reasons,
317
- warnings: shapeWarnings(shape),
318
- };
319
- }
320
-
321
- /**
322
- * The predicted-shape objection as a warning (Story #5313): one entry naming
323
- * the exceeded axis (`shape.code`) and the shape's own reason, or none when
324
- * the prediction is within every light ceiling.
325
- *
326
- * @param {ReturnType<typeof deriveStoryShape>} shape
327
- * @returns {string[]}
328
- */
329
- function shapeWarnings(shape) {
330
- if (shape?.route === 'lite') return [];
331
- const axis = shape?.code ?? 'unknown';
332
- const reason = shape?.reasons?.[0] ?? 'no reason recorded';
333
- return [
334
- `predicted shape exceeds a light ceiling on "${axis}": ${reason} — ` +
335
- 'proceeding light; the diff backstop bounds the actual change set',
336
- ];
281
+ return { suitable, shape, ledger, unwaivable, reasons };
337
282
  }
338
283
 
339
284
  /**
340
285
  * Resolve what the light gate does with a suitability decision (Story #4740
341
- * AC-3; Story #5313).
286
+ * AC-3; Story #5313; Story #5344).
342
287
  *
343
- * - suitable → `proceed-light`, carrying the predicted-shape
344
- * `warnings[]` (possibly empty) so an over-ceiling
345
- * prediction is stated, never silent.
288
+ * - suitable → `proceed-light`.
346
289
  * - not suitable → `escalate-plan` — only an un-ledgered verdict or an
347
290
  * un-waivable risk rule gets here, and neither has an
348
291
  * answer an operator could give, so there is no
@@ -351,11 +294,10 @@ function shapeWarnings(shape) {
351
294
  * Pure and total.
352
295
  *
353
296
  * @param {{
354
- * suitability?: { suitable?: boolean, reasons?: string[], warnings?: string[] },
297
+ * suitability?: { suitable?: boolean, reasons?: string[] },
355
298
  * }} [args]
356
299
  * @returns {{
357
300
  * action: 'proceed-light'|'escalate-plan',
358
- * warnings: string[],
359
301
  * reasons: string[],
360
302
  * }}
361
303
  */
@@ -363,29 +305,22 @@ export function resolveLightGateOutcome({ suitability } = {}) {
363
305
  const reasons = Array.isArray(suitability?.reasons)
364
306
  ? [...suitability.reasons]
365
307
  : [];
366
- const warnings = Array.isArray(suitability?.warnings)
367
- ? [...suitability.warnings]
368
- : [];
369
308
 
370
309
  if (suitability?.suitable === true) {
371
310
  return {
372
311
  action: 'proceed-light',
373
- warnings,
374
312
  reasons: [
375
313
  ...reasons,
376
- warnings.length > 0
377
- ? 'ledgered verdict lite and no un-waivable risk — proceeding light with a predicted-shape warning'
378
- : 'predicted shape and ledgered verdict both lite — proceed light',
314
+ 'ledgered verdict recorded and no un-waivable risk rule fired — proceed light; the diff backstop bounds the actual change set',
379
315
  ],
380
316
  };
381
317
  }
382
318
 
383
319
  return {
384
320
  action: 'escalate-plan',
385
- warnings,
386
321
  reasons: [
387
322
  ...reasons,
388
- 'the ledgered verdict is not lite or an un-waivable risk rule fired — fails closed to /mandrel-plan (never silently proceeds light)',
323
+ 'the verdict is un-ledgered or an un-waivable risk rule fired — fails closed to /mandrel-plan (never silently proceeds light)',
389
324
  ],
390
325
  };
391
326
  }
@@ -31,10 +31,6 @@ import {
31
31
  renderStorySplitRules,
32
32
  ticketsModePromptField,
33
33
  } from '../templates/decomposer-prompts.js';
34
- import {
35
- renderAcceptanceSpecSystemPrompt,
36
- renderTechSpecSystemPrompt,
37
- } from '../templates/spec-author-prompts.js';
38
34
  import { concurrentMap, FANOUT_CONCURRENCY } from '../util/concurrent-map.js';
39
35
  import { buildComplexitySignals } from './complexity-gate.js';
40
36
  import { findDependencyCandidates } from './dependency-candidates.js';
@@ -260,18 +256,17 @@ export const STORIES_TEMPLATE_FILENAME = 'stories.template.json';
260
256
 
261
257
  /**
262
258
  * Build the template's `changes[]` entries from the envelope's advisory
263
- * complexity signals (Story #4723). Each seed-predicted path is
264
- * pre-resolved to its creates-vs-refactors assumption against the repo
265
- * snapshot the signals already probed: a path present in the repo is a
266
- * `refactors-existing`, a missing one is a `creates`. Order follows
259
+ * complexity signals (Story #4723). Each seed-predicted path is emitted as a
260
+ * **bare path string** — the default authored form since Story #5342. The
261
+ * template used to pre-resolve each one to a creates-vs-refactors assumption
262
+ * against the repo snapshot; persist re-derives it against the base-branch
263
+ * ref, which is the authoritative probe, so the skeleton no longer carries a
264
+ * second answer for the author to keep in sync. Order follows
267
265
  * `predictedPaths` (first appearance in the seed). Falls back to the
268
266
  * single instructive placeholder entry when the seed predicted no paths.
269
267
  *
270
- * @param {{
271
- * predictedPaths?: string[],
272
- * repoState?: { existingPaths?: string[], missingPaths?: string[] },
273
- * }|null|undefined} complexitySignals
274
- * @returns {Array<{ path: string, assumption: string }>}
268
+ * @param {{ predictedPaths?: string[] }|null|undefined} complexitySignals
269
+ * @returns {string[]}
275
270
  */
276
271
  function buildTemplateChanges(complexitySignals) {
277
272
  const predicted = Array.isArray(complexitySignals?.predictedPaths)
@@ -279,18 +274,7 @@ function buildTemplateChanges(complexitySignals) {
279
274
  (p) => typeof p === 'string' && p.length > 0,
280
275
  )
281
276
  : [];
282
- if (predicted.length === 0) {
283
- return [{ path: 'path/to/file.ext', assumption: 'refactors-existing' }];
284
- }
285
- const existing = new Set(
286
- Array.isArray(complexitySignals?.repoState?.existingPaths)
287
- ? complexitySignals.repoState.existingPaths
288
- : [],
289
- );
290
- return predicted.map((path) => ({
291
- path,
292
- assumption: existing.has(path) ? 'refactors-existing' : 'creates',
293
- }));
277
+ return predicted.length === 0 ? ['path/to/file.ext'] : predicted;
294
278
  }
295
279
 
296
280
  /**
@@ -309,10 +293,10 @@ function buildTemplateChanges(complexitySignals) {
309
293
  *
310
294
  * Correct-by-construction skeleton (Story #4723): when the envelope's
311
295
  * `complexitySignals` predicted a footprint the `changes[]` entries arrive
312
- * pre-resolved to creates-vs-refactors against the repo snapshot — a
313
- * faithfully-filled skeleton passes the persist ticket validators without a
314
- * mechanical round-trip. The persist gates stay
315
- * authoritative (they probe the base branch ref, not the working tree).
296
+ * already filled in, as the bare paths Story #5342 made the default form —
297
+ * a faithfully-filled skeleton passes the persist ticket validators without a
298
+ * mechanical round-trip. Persist derives each assumption by probing the base
299
+ * branch ref and reports the derivation.
316
300
  *
317
301
  * Pure and deterministic; the output is valid JSON (parseable as-is), with
318
302
  * instructive placeholder values rather than comments.
@@ -329,6 +313,17 @@ export function renderStoriesTemplate({ complexitySignals = null } = {}) {
329
313
  title: 'Fill: short descriptive title',
330
314
  body: {
331
315
  goal: 'Fill: one sentence stating why this Story exists.',
316
+ // A filled, multi-checkpoint example rather than a placeholder
317
+ // (Story #5332): `## Slicing` is how one cohesive sweep stays one
318
+ // Story, so the skeleton has to show an author what a checkpoint
319
+ // list looks like. Each line is a stage of the work — a commit
320
+ // boundary inside one session — never a restatement of an
321
+ // acceptance item, which states what is true once the Story lands.
322
+ slicing:
323
+ '1. Re-anchor the shared constant and its consumers.\n' +
324
+ '2. Move the gate ahead of the first write and arm the refusal.\n' +
325
+ '3. Delete the superseded module, its test and its flag.\n' +
326
+ '4. Regenerate the affected baselines; run the full gate chain.',
332
327
  spec:
333
328
  'Optional — contract and invariants only: interfaces, status ' +
334
329
  'codes, security invariants, and load-bearing constraints with ' +
@@ -340,7 +335,7 @@ export function renderStoriesTemplate({ complexitySignals = null } = {}) {
340
335
  non_goals: [],
341
336
  },
342
337
  acceptance: [
343
- 'Fill: an outcome a PR reviewer can confirm from the diff and the verify output (three to six items)',
338
+ 'Fill: an outcome a PR reviewer can confirm from the diff and the verify output — as many as the capability has, no target and no ceiling',
344
339
  ],
345
340
  verify: [
346
341
  'Fill: exact command or test path — the mechanical check the acceptance item rests on',
@@ -367,25 +362,23 @@ function countEnumeratedItems(text) {
367
362
  }
368
363
 
369
364
  /**
370
- * Delta-shaped change-request verbs — the `core/scope-triage` skill's
371
- * change-request rubric routes these to `story` by default when the
372
- * footprint stays inside Story width.
365
+ * Delta-shaped change-request verbs — a change request naming one of these
366
+ * stays a Story by default when the footprint stays inside Story width.
373
367
  */
374
368
  const DELTA_VERB_RE =
375
369
  /\b(fix(?:es)?|tweak(?:s)?|extend(?:s)?|update(?:s)?|adjust(?:s)?|rename(?:s)?|correct(?:s)?|patch(?:es)?|bug|regression|flaky)\b/i;
376
370
 
377
371
  /**
378
- * Deterministic, CLI-applied scope-triage verdict over a raw `--seed` text
379
- * (#4496 fix 6). Embedding the verdict in the `--seed` envelope removes the
380
- * two skill Reads (`core/scope-triage` + the gate fragment's rubric pass)
381
- * from the headless path; the attended path keeps the skill-based judgment.
372
+ * Deterministic, CLI-applied scope signal over a raw `--seed` text
373
+ * (#4496 fix 6). Embedding it in the `--seed` envelope keeps the headless
374
+ * path from needing a judgment pass of its own.
382
375
  *
383
- * The heuristics anchor to the same granularity SSOT the skill anchors to —
376
+ * The heuristics anchor to the granularity SSOT —
384
377
  * `DELIVERABLE_GRANULARITY_GUIDANCE` in `ticket-validator-sizing.js` (one
385
378
  * Story = one coherent capability slice; multiple independent capabilities =
386
- * an Epic) — and to the skill's change-request delta rubric. Like the skill,
387
- * the verdict is **advisory**: being wrong in the `epic` direction is cheap,
388
- * and `borderline` is a first-class output, not a forced call.
379
+ * an Epic) — and to the change-request delta rubric above. The verdict is
380
+ * **advisory**: being wrong in the `epic` direction is cheap, and
381
+ * `borderline` is a first-class output, not a forced call.
389
382
  *
390
383
  * @param {{ seedText?: string }} args
391
384
  * @returns {{ verdict: 'epic'|'story'|'borderline', reasons: string[], advisory: true, appliedBy: 'cli' }}
@@ -634,12 +627,15 @@ function withAdvisorySignals(complexitySignals, { config, cwd } = {}) {
634
627
 
635
628
  /**
636
629
  * Render the authoring system prompts the collapsed pipeline's single
637
- * authoring pass consumes. The spec/acceptance prompts render from
638
- * `lib/templates/spec-author-prompts.js` (the M3/M8 handshake — envelope
639
- * authoritative from day one); the story prompt is the N=1 core from
640
- * `lib/templates/decomposer-prompts.js`, with the schedule and partition
641
- * rules a planner reads only when the default-single split policy clears
642
- * carried separately as `storySplitRules` (Story #5312).
630
+ * authoring pass consumes: the N=1 core from
631
+ * `lib/templates/decomposer-prompts.js`, with the schedule rules and the
632
+ * collision refusal a planner reads only when the default-single split policy
633
+ * clears carried separately as `storySplitRules` (Story #5312).
634
+ *
635
+ * Story #5332 deleted the `spec` / `acceptance` fields with the module that
636
+ * rendered them: nothing read either, and both contradicted the current
637
+ * contract — one asserting Spec budgets that no longer exist, the other
638
+ * demanding the verify tier suffixes tickets mode now strips.
643
639
  *
644
640
  * `storyTicketsRules` is the one mode-conditional field (Story #5323): it
645
641
  * only means anything when the seed is an existing ticket, and an envelope
@@ -647,12 +643,10 @@ function withAdvisorySignals(complexitySignals, { config, cwd } = {}) {
647
643
  * ticket that a `--seed` run does not have.
648
644
  *
649
645
  * @param {{ mode?: string }} [args]
650
- * @returns {{ spec: string, acceptance: string, story: string, storySplitRules: string, storyTicketsRules?: string }}
646
+ * @returns {{ story: string, storySplitRules: string, storyTicketsRules?: string }}
651
647
  */
652
648
  export function buildSystemPrompts({ mode } = {}) {
653
649
  return {
654
- spec: renderTechSpecSystemPrompt(),
655
- acceptance: renderAcceptanceSpecSystemPrompt(),
656
650
  story: renderStoryAuthorCore(),
657
651
  storySplitRules: renderStorySplitRules(),
658
652
  ...ticketsModePromptField(mode),
@@ -36,6 +36,7 @@
36
36
  * @module lib/orchestration/plan-persist/changes-repair
37
37
  */
38
38
 
39
+ import { matchBarePathToken } from '../../story-body/body-format-lints.js';
39
40
  import { FILE_ASSUMPTION_VALUES } from '../file-assumption-enum.js';
40
41
 
41
42
  /** The `## Changes` heading (either level the parser accepts). */
@@ -50,11 +51,6 @@ const TRAILING_PARENTHETICAL_RE = /\s*\([^)]*\)\s*$/;
50
51
  /** The humanized canonical bullet: `` `path` — assumption ``. */
51
52
  const HUMANIZED_RE = /^`([^`]+)`\s+—\s+(\S+)$/;
52
53
 
53
- // A token that looks like a file path / glob / module id: it carries a `/` or a
54
- // `.`-separated segment. Deliberately loose — a false positive only produces a
55
- // `{ path, assumption }` entry the base-branch probes then judge.
56
- const PATH_LIKE_RE = /^[\w@*-]*[/.][\w@./*-]+$/;
57
-
58
54
  /**
59
55
  * Strip a trailing parenthetical from a path token, reporting whether one
60
56
  * was present.
@@ -73,6 +69,12 @@ function stripParenthetical(raw) {
73
69
  * backticks, strip a trailing parenthetical. Returns `null` when nothing
74
70
  * path-shaped survives.
75
71
  *
72
+ * What counts as path-shaped is `matchBarePathToken` — the same grammar the
73
+ * story-body parser admits a bare bullet under, imported rather than
74
+ * restated (Story #5361). The repair pass scoring a narrower class than the
75
+ * parser is what let a route-segment path be repaired on one surface and
76
+ * refused on the other.
77
+ *
76
78
  * @param {string} raw
77
79
  * @returns {string|null}
78
80
  */
@@ -87,7 +89,7 @@ function salvagePath(raw) {
87
89
  .replace(/[`'"]+$/, '')
88
90
  .trim();
89
91
  s = stripParenthetical(s).path;
90
- return s !== '' && PATH_LIKE_RE.test(s) ? s : null;
92
+ return matchBarePathToken(s);
91
93
  }
92
94
 
93
95
  /**