mandrel 2.56.0 → 2.57.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 (106) hide show
  1. package/.agents/agents/plan-critic.md +13 -18
  2. package/.agents/agents/story-worker.md +25 -34
  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/lib/audit-suite/checklist-threading.js +15 -2
  16. package/.agents/scripts/lib/baselines/coverage-updater-cli.js +110 -0
  17. package/.agents/scripts/lib/baselines/crap-preview-scan.js +25 -0
  18. package/.agents/scripts/lib/baselines/crap-updater-cli.js +223 -0
  19. package/.agents/scripts/lib/bdd-scenario-budget.js +21 -3
  20. package/.agents/scripts/lib/bootstrap/quality-bootstrap.js +0 -1
  21. package/.agents/scripts/lib/close-validation/gates.js +52 -1
  22. package/.agents/scripts/lib/config/acceptance-eval.js +25 -57
  23. package/.agents/scripts/lib/config/delivery-routing.js +7 -33
  24. package/.agents/scripts/lib/config/explain.js +0 -19
  25. package/.agents/scripts/lib/config/limits.js +18 -78
  26. package/.agents/scripts/lib/config/quality.js +6 -3
  27. package/.agents/scripts/lib/config/runners.js +3 -2
  28. package/.agents/scripts/lib/config-settings-schema-delivery.js +15 -68
  29. package/.agents/scripts/lib/config-settings-schema-quality.js +0 -14
  30. package/.agents/scripts/lib/config-settings-schema.js +16 -143
  31. package/.agents/scripts/lib/crap-engine.js +35 -4
  32. package/.agents/scripts/lib/crap-utils.js +17 -1
  33. package/.agents/scripts/lib/cyclomatic-ceiling.js +19 -7
  34. package/.agents/scripts/lib/generated/agentrc-validator.js +1 -1
  35. package/.agents/scripts/lib/observability/runtime-friction.js +1 -1
  36. package/.agents/scripts/lib/observability/source-classifier.js +1 -0
  37. package/.agents/scripts/lib/orchestration/acceptance-eval-decision.js +5 -4
  38. package/.agents/scripts/lib/orchestration/ceremony-routing.js +19 -73
  39. package/.agents/scripts/lib/orchestration/complexity-gate.js +46 -212
  40. package/.agents/scripts/lib/orchestration/file-assumptions.js +32 -17
  41. package/.agents/scripts/lib/orchestration/light-escalation.js +3 -3
  42. package/.agents/scripts/lib/orchestration/light-suitability.js +66 -233
  43. package/.agents/scripts/lib/orchestration/plan-context.js +181 -387
  44. package/.agents/scripts/lib/orchestration/plan-critic-conditions.js +42 -153
  45. package/.agents/scripts/lib/orchestration/plan-critics-evaluate.js +14 -70
  46. package/.agents/scripts/lib/orchestration/plan-persist/changes-repair.js +300 -0
  47. package/.agents/scripts/lib/orchestration/plan-persist/persist-helpers.js +131 -168
  48. package/.agents/scripts/lib/orchestration/plan-persist/run-plan-persist.js +118 -297
  49. package/.agents/scripts/lib/orchestration/plan-persist/soft-findings.js +55 -0
  50. package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +16 -65
  51. package/.agents/scripts/lib/orchestration/plan-persist/wave-serialisation.js +22 -35
  52. package/.agents/scripts/lib/orchestration/plan-text-hygiene.js +30 -139
  53. package/.agents/scripts/lib/orchestration/planning/memory-pool-advisory.js +61 -223
  54. package/.agents/scripts/lib/orchestration/single-story-close/phases/close-validation.js +5 -0
  55. package/.agents/scripts/lib/orchestration/single-story-close/phases/pre-gate-steps.js +46 -16
  56. package/.agents/scripts/lib/orchestration/story-close/context-budget-writeback.js +213 -0
  57. package/.agents/scripts/lib/orchestration/task-body-validator.js +10 -63
  58. package/.agents/scripts/lib/orchestration/ticket-validator-conflicts.js +33 -539
  59. package/.agents/scripts/lib/orchestration/ticket-validator-sizing.js +21 -414
  60. package/.agents/scripts/lib/orchestration/ticket-validator.js +54 -118
  61. package/.agents/scripts/lib/orchestration/verify-credit.js +69 -24
  62. package/.agents/scripts/lib/story-body/body-format-lints.js +15 -85
  63. package/.agents/scripts/lib/story-body/story-body.js +17 -237
  64. package/.agents/scripts/lib/templates/decomposer-prompts.js +84 -121
  65. package/.agents/scripts/lib/test-isolate/cli-options.js +93 -0
  66. package/.agents/scripts/lib/test-isolate/progress-log.js +45 -0
  67. package/.agents/scripts/lib/test-isolate/render-report.js +97 -0
  68. package/.agents/scripts/lib/test-isolate/run-isolate.js +87 -0
  69. package/.agents/scripts/lib/test-run-credit.js +266 -0
  70. package/.agents/scripts/lib/wave-runner/footprint.js +48 -358
  71. package/.agents/scripts/lib/wave-runner/ready-set.js +6 -5
  72. package/.agents/scripts/lib/workers/crap-worker.js +32 -41
  73. package/.agents/scripts/plan-context.js +7 -9
  74. package/.agents/scripts/plan-critics.js +28 -54
  75. package/.agents/scripts/plan-persist.js +25 -68
  76. package/.agents/scripts/quality-preview.js +51 -0
  77. package/.agents/scripts/run-tests.js +12 -0
  78. package/.agents/scripts/stories-wave-tick.js +23 -45
  79. package/.agents/scripts/test-isolate.js +13 -180
  80. package/.agents/scripts/update-coverage-baseline.js +25 -70
  81. package/.agents/scripts/update-crap-baseline.js +19 -123
  82. package/.agents/skills/core/scope-triage/SKILL.md +3 -3
  83. package/.agents/workflows/audit-clean-code.md +4 -3
  84. package/.agents/workflows/helpers/acceptance-self-eval.md +41 -41
  85. package/.agents/workflows/helpers/code-quality-guardrails.md +4 -4
  86. package/.agents/workflows/helpers/code-review.md +2 -3
  87. package/.agents/workflows/helpers/deliver-digest.md +41 -57
  88. package/.agents/workflows/helpers/deliver-light.md +40 -105
  89. package/.agents/workflows/helpers/deliver-reference.md +1 -1
  90. package/.agents/workflows/helpers/deliver-story-reference.md +37 -58
  91. package/.agents/workflows/helpers/deliver-story.md +9 -13
  92. package/.agents/workflows/helpers/plan-reference.md +132 -219
  93. package/.agents/workflows/mandrel-plan.md +27 -40
  94. package/.agents/workflows/memory-consolidate.md +9 -13
  95. package/docs/CHANGELOG.md +23 -0
  96. package/lib/migrations/index.js +4 -0
  97. package/lib/migrations/steps/2.57.0-retire-delivery-limit-knobs.js +45 -0
  98. package/lib/migrations/steps/2.57.0-retire-planning-limit-knobs.js +59 -0
  99. package/package.json +1 -1
  100. package/.agents/scripts/lib/framework-version.js +0 -39
  101. package/.agents/scripts/lib/orchestration/consolidation-precondition.js +0 -223
  102. package/.agents/scripts/lib/orchestration/plan-persist/fan-out-gate.js +0 -97
  103. package/.agents/scripts/lib/orchestration/planning/decomposer-context.js +0 -26
  104. package/.agents/scripts/lib/orchestration/spec-budget.js +0 -89
  105. package/.agents/scripts/lib/orchestration/spec-spill.js +0 -74
  106. package/.agents/scripts/lib/orchestration/verify-tier-repair.js +0 -107
@@ -76,7 +76,7 @@ function buildRerunBackstopCommand(storyId) {
76
76
  * number, or `null` when absent/malformed.
77
77
  *
78
78
  * This is the only Story context a **gate-stage** rejection can legitimately
79
- * claim: the signals stream is keyed on a Story id, and an `ask-operator` gate
79
+ * claim: the signals stream is keyed on a Story id, and an escalating gate
80
80
  * has authored no receipt yet — deliberately, since not creating one is the
81
81
  * point of that outcome. A bare prompt's rejection therefore has no stream to
82
82
  * land in, and attributing it to a fabricated id would be worse than recording
@@ -93,8 +93,8 @@ function normalizeAmendsId(amends) {
93
93
  }
94
94
 
95
95
  /**
96
- * Record a suitability-gate refusal (`ask-operator`) as friction, attributed to
97
- * the `--amends` target when there is one.
96
+ * Record a suitability-gate refusal (`escalate-plan`) as friction, attributed
97
+ * to the `--amends` target when there is one.
98
98
  *
99
99
  * @param {{
100
100
  * gate: object,
@@ -27,13 +27,15 @@
27
27
  * and epic-scope span — never artifact counts (Story #4764), so this gate
28
28
  * is deliberately **coarse**: it rejects clearly-epic work, and invariant
29
29
  * 3 below does the real enforcement against ground truth.
30
- * 2. **Over-scope stops, never silently proceeds ({@link
31
- * resolveLightGateOutcome}).** An over-ceiling prompt does **not**
32
- * hard-fail — it STOPS and asks the operator to escalate to `/mandrel-plan` or
33
- * proceed light. Both answers are executable: `proceed-light` is recorded
34
- * through {@link resolveOperatorOverride}, which waives a *size
35
- * prediction* only, never a risk rule, and only with a human present.
36
- * Under `--yes` (unattended) it fails closed to recommending `/mandrel-plan`.
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.
37
39
  * 3. **Diff-derived backstop ({@link checkLightDiffBackstop}).** After
38
40
  * implementation the **actual** change set is re-checked with
39
41
  * {@link module:lib/orchestration/review-depth.deriveChangeLevel} plus the
@@ -61,32 +63,10 @@ import {
61
63
  } from './complexity-gate.js';
62
64
  import { deriveChangeLevel } from './review-depth.js';
63
65
 
64
- /**
65
- * The shape objections an attended operator may waive (Story #4815) — an
66
- * **allowlist**, deliberately, so a rule added to
67
- * {@link module:lib/orchestration/complexity-gate.SHAPE_CODES} later is
68
- * non-negotiable until someone decides otherwise here. A denylist would make
69
- * every new rule silently overridable.
70
- *
71
- * These four are the *size predictions*: coarse by design (§ Scope by effort),
72
- * and already bounded for real by {@link checkLightDiffBackstop} against the
73
- * landed diff. Everything absent is absent on purpose — `migration-span` and
74
- * `sensitive-path` are **risk**, not size, and the unknown-footprint codes
75
- * describe a shape that was never judged, so there is no false positive to
76
- * appeal.
77
- */
78
- export const OVERRIDABLE_SHAPE_CODES = Object.freeze([
79
- SHAPE_CODES.CHANGE_KINDS,
80
- SHAPE_CODES.MAGNITUDE,
81
- SHAPE_CODES.UNCERTAINTY,
82
- SHAPE_CODES.DEPLOYABLE_SPAN,
83
- ]);
84
-
85
66
  /**
86
67
  * Detect the **un-waivable** risk rules a predicted footprint trips —
87
- * `sensitive-path` and `migration-span`, the complement of
88
- * {@link OVERRIDABLE_SHAPE_CODES} — **independent of which rule the shape
89
- * decision happened to record** (Story #4875).
68
+ * `sensitive-path` and `migration-span` — **independent of which rule the
69
+ * shape decision happened to record** (Story #4875).
90
70
  *
91
71
  * No re-slicing, shrinking, or operator answer satisfies one: a footprint
92
72
  * intersecting a sensitive-path class routes `full` however small the change,
@@ -129,7 +109,7 @@ function deriveUnwaivableRisk(decision) {
129
109
  reason:
130
110
  `un-waivable: the predicted footprint intersects sensitive-path ` +
131
111
  `class(es) ${classes.join(', ')} — this is risk, not size, so no ` +
132
- `re-slicing, shrinking, or operator override satisfies it and the ` +
112
+ `re-slicing or shrinking satisfies it and the ` +
133
113
  `diff backstop would refuse the same footprint after the work is ` +
134
114
  `finished; take this to /mandrel-plan now`,
135
115
  };
@@ -141,8 +121,8 @@ function deriveUnwaivableRisk(decision) {
141
121
  classes: [],
142
122
  reason:
143
123
  `un-waivable: the predicted footprint pairs a migration with its ` +
144
- `consumers — this is risk, not size, so no re-slicing or operator ` +
145
- `override satisfies it; take this to /mandrel-plan now`,
124
+ `consumers — this is risk, not size, so no re-slicing ` +
125
+ `satisfies it; take this to /mandrel-plan now`,
146
126
  };
147
127
  }
148
128
  return { present: false, code: null, classes: [], reason: null };
@@ -213,10 +193,8 @@ function resolveDiffCeilings(ceilings) {
213
193
  }
214
194
 
215
195
  /**
216
- * Resolve the model's trivial-vs-standard verdict, held to the same ledgering
217
- * contract the planner's authored verdict is
218
- * ({@link module:lib/orchestration/complexity-gate.resolvePlannerRouteVerdict}):
219
- * a `lite` route counts **only** with a non-empty recorded reason. A lite claim
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
220
198
  * without a recorded reason, or any non-`lite` route, fails closed to `full` —
221
199
  * an unaudited "trust me, it's small" never buys the light path.
222
200
  *
@@ -291,9 +269,13 @@ export function resolveLedgeredVerdict({ route, reason } = {}) {
291
269
  * unwaivable: ReturnType<typeof deriveUnwaivableRisk>,
292
270
  * ceilings: typeof STORY_SHAPE_CEILINGS,
293
271
  * reasons: string[],
272
+ * warnings: string[],
294
273
  * }} `unwaivable` names an absolute risk rule the predicted footprint trips
295
274
  * even when the recorded `shape.code` is a size prediction (Story #4875), so
296
- * the operator learns at prediction time that no override can help.
275
+ * 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.
297
279
  */
298
280
  export function deriveLightSuitability({
299
281
  predictedChanges,
@@ -316,12 +298,11 @@ export function deriveLightSuitability({
316
298
  selectSensitivePathClassesFn,
317
299
  });
318
300
  const unwaivable = deriveUnwaivableRisk(shape);
319
- // A tripped risk rule is decisive on its own: the shape decision may have
320
- // recorded an earlier ceiling rule, but a sensitive footprint can never be
321
- // lite, so the conjunction must not be able to read `suitable` from a shape
322
- // whose recorded code was waived downstream.
323
- const suitable =
324
- shape.route === 'lite' && ledger.route === 'lite' && !unwaivable.present;
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;
325
306
  const reasons = [`shape: ${shape.reasons[0]}`];
326
307
  if (unwaivable.present) reasons.push(unwaivable.reason);
327
308
  reasons.push(`verdict: ${ledger.note}`);
@@ -333,193 +314,78 @@ export function deriveLightSuitability({
333
314
  unwaivable,
334
315
  ceilings: STORY_SHAPE_CEILINGS,
335
316
  reasons,
317
+ warnings: shapeWarnings(shape),
336
318
  };
337
319
  }
338
320
 
339
321
  /**
340
- * Adjudicate an operator's recorded `proceed-light` answer to the gate's own
341
- * question (Story #4815). The gate has always *offered* `proceed-light` as one
342
- * of two options; before this there was no input that could carry the answer,
343
- * so the only way past a coarse prediction was to re-shape the declaration
344
- * until the gate stopped objecting — precisely the under-declaring the coarse
345
- * design anticipates.
346
- *
347
- * Applying it takes **all** of:
348
- *
349
- * 1. **The gate actually objected.** An override cannot pre-authorize a
350
- * scope nothing rejected.
351
- * 2. **The run is attended.** `--yes` means nobody is at the keyboard, so
352
- * there is no operator whose answer this could be (§ Escalation is
353
- * terminal). Checked here as well as at the CLI, so the pure core carries
354
- * the guarantee rather than the shell.
355
- * 3. **The objection is a size prediction** — a code in
356
- * {@link OVERRIDABLE_SHAPE_CODES}, **and** the footprint trips no
357
- * un-waivable risk rule ({@link deriveUnwaivableRisk}, Story #4875).
358
- * Sensitivity and migration
359
- * span are risk and stay absolute however small the change — including
360
- * when an earlier ceiling rule is the one the shape recorded.
361
- * 4. **The ledgered verdict is already `lite`.** The override substitutes for
362
- * the *shape* half of the conjunction only; an unaudited "trust me, it's
363
- * small" buys nothing it did not buy before.
364
- *
365
- * A refusal is reported, never silent — an operator who typed the flag must
366
- * learn why it did not take. Pure and total.
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.
367
325
  *
368
- * @param {{
369
- * suitability?: object,
370
- * yes?: boolean,
371
- * operatorOverride?: unknown,
372
- * }} [args] `operatorOverride` is the operator's recorded reason; blank or
373
- * absent means no override was requested.
374
- * @returns {{
375
- * applied: boolean,
376
- * record: { recordedReason: string, overriddenCode: string, overriddenReason: string }|null,
377
- * note: string|null,
378
- * }}
326
+ * @param {ReturnType<typeof deriveStoryShape>} shape
327
+ * @returns {string[]}
379
328
  */
380
- export function resolveOperatorOverride({
381
- suitability,
382
- yes = false,
383
- operatorOverride,
384
- } = {}) {
385
- const recordedReason =
386
- typeof operatorOverride === 'string' ? operatorOverride.trim() : '';
387
- const refuse = (note) => ({ applied: false, record: null, note });
388
-
389
- if (recordedReason === '') return refuse(null);
390
- if (suitability?.suitable === true) {
391
- return refuse(
392
- 'operator override ignored — the gate raised no objection to override',
393
- );
394
- }
395
- if (yes === true) {
396
- return refuse(
397
- 'operator override refused — it is attended-only, and --yes means nobody is at the keyboard; over-scope still fails closed to /mandrel-plan',
398
- );
399
- }
400
-
401
- const code = suitability?.shape?.code ?? null;
402
- // Checked BEFORE the overridable-code test on purpose (Story #4875): when a
403
- // footprint trips both a ceiling rule and a risk rule, the shape records the
404
- // ceiling rule, which IS overridable — so testing the recorded code alone
405
- // would apply the override and send un-landable work to the backstop.
406
- const unwaivable = suitability?.unwaivable;
407
- if (unwaivable?.present === true) {
408
- return refuse(
409
- `operator override refused — the predicted footprint also trips the ` +
410
- `un-waivable "${unwaivable.code}" rule${
411
- unwaivable.classes.length > 0
412
- ? ` (${unwaivable.classes.join(', ')})`
413
- : ''
414
- }; waiving the size prediction cannot make this land light, and the ` +
415
- `diff backstop would refuse the finished work. Escalate to /mandrel-plan.`,
416
- );
417
- }
418
- if (!OVERRIDABLE_SHAPE_CODES.includes(code)) {
419
- return refuse(
420
- `operator override refused — "${code ?? 'unknown'}" is not an overridable size prediction (overridable: ${OVERRIDABLE_SHAPE_CODES.join(', ')}); risk rules and unknown footprints are non-negotiable`,
421
- );
422
- }
423
- if (suitability?.ledger?.route !== 'lite') {
424
- return refuse(
425
- 'operator override refused — it substitutes for the predicted shape only; the model verdict must still be a ledgered lite with a recorded reason',
426
- );
427
- }
428
-
429
- return {
430
- applied: true,
431
- record: {
432
- recordedReason,
433
- overriddenCode: code,
434
- overriddenReason: suitability?.shape?.reasons?.[0] ?? '',
435
- },
436
- note: `operator override applied — proceeding light despite "${code}"; recorded reason: ${recordedReason}. The diff backstop still bounds the actual change set.`,
437
- };
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
+ ];
438
337
  }
439
338
 
440
339
  /**
441
340
  * Resolve what the light gate does with a suitability decision (Story #4740
442
- * AC-3). Over-scope never hard-fails: it STOPS and asks the operator to choose,
443
- * unless the run is unattended (`--yes`), where it fails closed to recommending
444
- * `/mandrel-plan` rather than silently proceeding light.
445
- *
446
- * - suitable → `proceed-light`
447
- * - over-scope + attended (`yes:false`) → `ask-operator` (escalate | proceed)
448
- * - over-scope + attended + an applied operator override (Story #4815)
449
- * → `proceed-light`, carrying the
450
- * decision in `override`
451
- * - over-scope + unattended (`yes:true`) → `escalate-plan`
341
+ * AC-3; Story #5313).
452
342
  *
453
- * The override is adjudicated by {@link resolveOperatorOverride} and cannot
454
- * reach the unattended branch: `escalate-plan` is resolved first and the
455
- * override refuses itself under `yes` anyway.
343
+ * - suitable → `proceed-light`, carrying the predicted-shape
344
+ * `warnings[]` (possibly empty) so an over-ceiling
345
+ * prediction is stated, never silent.
346
+ * - not suitable → `escalate-plan` — only an un-ledgered verdict or an
347
+ * un-waivable risk rule gets here, and neither has an
348
+ * answer an operator could give, so there is no
349
+ * attended/unattended split any more.
456
350
  *
457
351
  * Pure and total.
458
352
  *
459
353
  * @param {{
460
- * suitability?: { suitable?: boolean, reasons?: string[] },
461
- * yes?: boolean,
462
- * operatorOverride?: unknown,
354
+ * suitability?: { suitable?: boolean, reasons?: string[], warnings?: string[] },
463
355
  * }} [args]
464
356
  * @returns {{
465
- * action: 'proceed-light'|'ask-operator'|'escalate-plan',
466
- * options?: string[],
467
- * override?: object,
357
+ * action: 'proceed-light'|'escalate-plan',
358
+ * warnings: string[],
468
359
  * reasons: string[],
469
360
  * }}
470
361
  */
471
- export function resolveLightGateOutcome({
472
- suitability,
473
- yes = false,
474
- operatorOverride,
475
- } = {}) {
476
- const override = resolveOperatorOverride({
477
- suitability,
478
- yes,
479
- operatorOverride,
480
- });
362
+ export function resolveLightGateOutcome({ suitability } = {}) {
481
363
  const reasons = Array.isArray(suitability?.reasons)
482
364
  ? [...suitability.reasons]
483
365
  : [];
484
- if (override.note !== null) reasons.push(override.note);
366
+ const warnings = Array.isArray(suitability?.warnings)
367
+ ? [...suitability.warnings]
368
+ : [];
485
369
 
486
370
  if (suitability?.suitable === true) {
487
371
  return {
488
372
  action: 'proceed-light',
373
+ warnings,
489
374
  reasons: [
490
375
  ...reasons,
491
- 'predicted shape and ledgered verdict both lite — proceed light',
492
- ],
493
- };
494
- }
495
-
496
- if (yes === true) {
497
- return {
498
- action: 'escalate-plan',
499
- reasons: [
500
- ...reasons,
501
- '--yes on over-scope fails closed to /mandrel-plan (never silently proceeds light)',
502
- ],
503
- };
504
- }
505
-
506
- if (override.applied) {
507
- return {
508
- action: 'proceed-light',
509
- override: override.record,
510
- reasons: [
511
- ...reasons,
512
- 'predicted scope exceeded a light ceiling and the operator answered proceed-light — proceeding on the recorded override',
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',
513
379
  ],
514
380
  };
515
381
  }
516
382
 
517
383
  return {
518
- action: 'ask-operator',
519
- options: ['escalate-plan', 'proceed-light'],
384
+ action: 'escalate-plan',
385
+ warnings,
520
386
  reasons: [
521
387
  ...reasons,
522
- 'predicted scope exceeds the light ceilings — STOP and ask the operator to escalate to /mandrel-plan or proceed light',
388
+ 'the ledgered verdict is not lite or an un-waivable risk rule fired — fails closed to /mandrel-plan (never silently proceeds light)',
523
389
  ],
524
390
  };
525
391
  }
@@ -852,53 +718,22 @@ function toReceiptChanges(changedFiles) {
852
718
  return entries;
853
719
  }
854
720
 
855
- /**
856
- * Render an applied operator override as an audit paragraph for the receipt
857
- * body (Story #4815). An override that leaves no trace on the ticket is an
858
- * invisible decision: the whole point of routing it through the receipt is
859
- * that a later reader can see the gate objected, on what grounds, and who
860
- * decided to proceed anyway.
861
- *
862
- * @param {unknown} override The `record` from {@link resolveOperatorOverride}.
863
- * @returns {string} A leading-space-prefixed sentence, or `''` when absent.
864
- */
865
- function renderOverrideNote(override) {
866
- if (!override || typeof override !== 'object') return '';
867
- const { overriddenCode, overriddenReason, recordedReason } = override;
868
- if (typeof recordedReason !== 'string' || recordedReason.trim() === '') {
869
- return '';
870
- }
871
- return (
872
- ` OPERATOR SCOPE OVERRIDE: the suitability gate objected on ` +
873
- `"${overriddenCode}" (${overriddenReason}) and the operator answered ` +
874
- `proceed-light — recorded reason: ${recordedReason.trim()}. The ` +
875
- `prediction was waived, not the diff backstop, which still bounds the ` +
876
- `landed change set.`
877
- );
878
- }
879
-
880
721
  /**
881
722
  * Build the minimal receipt `type::story` ticket for the light path
882
723
  * (Story #4740 AC-5) — the input `assemblePlanStories` / `createStoryIssues`
883
724
  * consume, so the light path reuses the plan-persist story-creation surface
884
725
  * rather than reimplementing issue authoring. The body carries the operator
885
- * prompt (goal + spec), the diff-derived footprint (`changes[]`), and any
886
- * operator scope override, so history and `refs #<id>` on the commit survive.
726
+ * prompt (goal + spec) and the diff-derived footprint (`changes[]`), so
727
+ * history and `refs #<id>` on the commit survive.
887
728
  *
888
729
  * @param {{
889
730
  * prompt?: unknown,
890
731
  * changedFiles?: unknown,
891
732
  * amends?: unknown,
892
- * override?: unknown,
893
733
  * }} [args]
894
734
  * @returns {{ slug: string, title: string, body: object, labels: string[] }}
895
735
  */
896
- export function buildReceiptStoryTicket({
897
- prompt,
898
- changedFiles,
899
- amends,
900
- override,
901
- } = {}) {
736
+ export function buildReceiptStoryTicket({ prompt, changedFiles, amends } = {}) {
902
737
  const text = typeof prompt === 'string' ? prompt.trim() : '';
903
738
  if (text === '') {
904
739
  throw new Error(
@@ -908,7 +743,6 @@ export function buildReceiptStoryTicket({
908
743
  const amendsId = normalizeAmends(amends);
909
744
  const amendNote = amendsId !== null ? ` Amends #${amendsId}.` : '';
910
745
  const changes = toReceiptChanges(changedFiles);
911
- const overrideNote = renderOverrideNote(override);
912
746
 
913
747
  return {
914
748
  slug: slugifyPrompt(text),
@@ -919,8 +753,7 @@ export function buildReceiptStoryTicket({
919
753
  spec:
920
754
  `Delivered via /deliver-light as a validated single-session change — ` +
921
755
  `the /mandrel-plan session is removed for genuinely small work while every ` +
922
- `single-story-close gate runs byte-identical.${amendNote}` +
923
- `${overrideNote} ` +
756
+ `single-story-close gate runs byte-identical.${amendNote} ` +
924
757
  `Operator prompt: ${text}`,
925
758
  changes,
926
759
  acceptance: [