mandrel 2.55.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 (131) 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 +4 -30
  4. package/.agents/docs/configuration.md +11 -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/rules/ci-remediation.md +39 -21
  9. package/.agents/schemas/agentrc.schema.json +28 -185
  10. package/.agents/schemas/story-deliver-terminal.schema.json +1 -1
  11. package/.agents/scripts/acceptance-eval.js +107 -17
  12. package/.agents/scripts/audit-to-stories.js +222 -75
  13. package/.agents/scripts/ceremony-derive.js +191 -0
  14. package/.agents/scripts/check-context-budget.js +28 -33
  15. package/.agents/scripts/check-cyclomatic.js +4 -3
  16. package/.agents/scripts/deliver-light.js +31 -94
  17. package/.agents/scripts/file-ci-gap.js +306 -0
  18. package/.agents/scripts/lib/audit-suite/checklist-threading.js +15 -2
  19. package/.agents/scripts/lib/audit-to-stories/audit-label-taxonomy.js +25 -1
  20. package/.agents/scripts/lib/audit-to-stories/dedupe-against-github.js +40 -52
  21. package/.agents/scripts/lib/audit-to-stories/finding-adapter.js +5 -1
  22. package/.agents/scripts/lib/audit-to-stories/issue-corpus.js +162 -0
  23. package/.agents/scripts/lib/audit-to-stories/issues-file.js +121 -0
  24. package/.agents/scripts/lib/audit-to-stories/ledger-commit.js +1 -1
  25. package/.agents/scripts/lib/audit-to-stories/ledger-record.js +126 -0
  26. package/.agents/scripts/lib/audit-to-stories/seed-from-findings.js +11 -0
  27. package/.agents/scripts/lib/baselines/coverage-updater-cli.js +110 -0
  28. package/.agents/scripts/lib/baselines/crap-preview-scan.js +25 -0
  29. package/.agents/scripts/lib/baselines/crap-updater-cli.js +223 -0
  30. package/.agents/scripts/lib/bdd-scenario-budget.js +21 -3
  31. package/.agents/scripts/lib/bootstrap/quality-bootstrap.js +0 -1
  32. package/.agents/scripts/lib/close-validation/gates.js +52 -1
  33. package/.agents/scripts/lib/config/acceptance-eval.js +25 -57
  34. package/.agents/scripts/lib/config/delivery-routing.js +7 -33
  35. package/.agents/scripts/lib/config/explain.js +0 -19
  36. package/.agents/scripts/lib/config/limits.js +18 -78
  37. package/.agents/scripts/lib/config/quality.js +6 -3
  38. package/.agents/scripts/lib/config/runners.js +3 -2
  39. package/.agents/scripts/lib/config-settings-schema-delivery.js +15 -68
  40. package/.agents/scripts/lib/config-settings-schema-quality.js +0 -14
  41. package/.agents/scripts/lib/config-settings-schema.js +49 -143
  42. package/.agents/scripts/lib/crap-engine.js +35 -4
  43. package/.agents/scripts/lib/crap-utils.js +17 -1
  44. package/.agents/scripts/lib/cyclomatic-ceiling.js +19 -7
  45. package/.agents/scripts/lib/feedback-loop/graduator-core.js +53 -13
  46. package/.agents/scripts/lib/feedback-loop/prior-feedback-fetcher.js +71 -25
  47. package/.agents/scripts/lib/feedback-loop/retro-proposals-graduator.js +18 -25
  48. package/.agents/scripts/lib/{audit-to-stories/ledger.js → findings/audit-ledger.js} +131 -24
  49. package/.agents/scripts/lib/findings/route-finding.js +38 -0
  50. package/.agents/scripts/lib/generated/agentrc-validator.js +1 -1
  51. package/.agents/scripts/lib/github/framework-repo.js +148 -2
  52. package/.agents/scripts/lib/label-constants.js +6 -1
  53. package/.agents/scripts/lib/observability/runtime-friction.js +1 -1
  54. package/.agents/scripts/lib/observability/source-classifier.js +2 -0
  55. package/.agents/scripts/lib/orchestration/acceptance-eval-decision.js +5 -4
  56. package/.agents/scripts/lib/orchestration/ceremony-routing.js +19 -73
  57. package/.agents/scripts/lib/orchestration/ci-gap-intake.js +605 -0
  58. package/.agents/scripts/lib/orchestration/ci-rerun-guard.js +13 -8
  59. package/.agents/scripts/lib/orchestration/complexity-gate.js +46 -212
  60. package/.agents/scripts/lib/orchestration/file-assumptions.js +32 -17
  61. package/.agents/scripts/lib/orchestration/light-escalation.js +3 -3
  62. package/.agents/scripts/lib/orchestration/light-suitability.js +66 -233
  63. package/.agents/scripts/lib/orchestration/plan-context.js +181 -387
  64. package/.agents/scripts/lib/orchestration/plan-critic-conditions.js +42 -153
  65. package/.agents/scripts/lib/orchestration/plan-critics-evaluate.js +14 -70
  66. package/.agents/scripts/lib/orchestration/plan-persist/audit-provenance.js +197 -0
  67. package/.agents/scripts/lib/orchestration/plan-persist/changes-repair.js +300 -0
  68. package/.agents/scripts/lib/orchestration/plan-persist/persist-helpers.js +131 -168
  69. package/.agents/scripts/lib/orchestration/plan-persist/run-plan-persist.js +133 -299
  70. package/.agents/scripts/lib/orchestration/plan-persist/soft-findings.js +55 -0
  71. package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +16 -65
  72. package/.agents/scripts/lib/orchestration/plan-persist/wave-serialisation.js +22 -35
  73. package/.agents/scripts/lib/orchestration/plan-text-hygiene.js +30 -139
  74. package/.agents/scripts/lib/orchestration/planning/memory-pool-advisory.js +61 -223
  75. package/.agents/scripts/lib/orchestration/run-epilogue.js +4 -4
  76. package/.agents/scripts/lib/orchestration/single-story-close/phases/close-validation.js +5 -0
  77. package/.agents/scripts/lib/orchestration/single-story-close/phases/pre-gate-steps.js +46 -16
  78. package/.agents/scripts/lib/orchestration/story-close/context-budget-writeback.js +213 -0
  79. package/.agents/scripts/lib/orchestration/story-follow-ups.js +32 -20
  80. package/.agents/scripts/lib/orchestration/task-body-validator.js +10 -63
  81. package/.agents/scripts/lib/orchestration/ticket-validator-conflicts.js +33 -539
  82. package/.agents/scripts/lib/orchestration/ticket-validator-sizing.js +21 -414
  83. package/.agents/scripts/lib/orchestration/ticket-validator.js +54 -118
  84. package/.agents/scripts/lib/orchestration/verify-credit.js +69 -24
  85. package/.agents/scripts/lib/story-body/body-format-lints.js +15 -85
  86. package/.agents/scripts/lib/story-body/story-body.js +17 -237
  87. package/.agents/scripts/lib/templates/decomposer-prompts.js +84 -121
  88. package/.agents/scripts/lib/test-isolate/cli-options.js +93 -0
  89. package/.agents/scripts/lib/test-isolate/progress-log.js +45 -0
  90. package/.agents/scripts/lib/test-isolate/render-report.js +97 -0
  91. package/.agents/scripts/lib/test-isolate/run-isolate.js +87 -0
  92. package/.agents/scripts/lib/test-run-credit.js +266 -0
  93. package/.agents/scripts/lib/wave-runner/footprint.js +48 -358
  94. package/.agents/scripts/lib/wave-runner/ready-set.js +6 -5
  95. package/.agents/scripts/lib/workers/crap-worker.js +32 -41
  96. package/.agents/scripts/plan-context.js +7 -9
  97. package/.agents/scripts/plan-critics.js +28 -54
  98. package/.agents/scripts/plan-persist.js +25 -68
  99. package/.agents/scripts/pr-watch-with-update.js +3 -2
  100. package/.agents/scripts/quality-preview.js +51 -0
  101. package/.agents/scripts/run-tests.js +12 -0
  102. package/.agents/scripts/stories-wave-tick.js +23 -45
  103. package/.agents/scripts/test-isolate.js +13 -180
  104. package/.agents/scripts/update-coverage-baseline.js +25 -70
  105. package/.agents/scripts/update-crap-baseline.js +19 -123
  106. package/.agents/skills/core/scope-triage/SKILL.md +3 -3
  107. package/.agents/workflows/audit-clean-code.md +4 -3
  108. package/.agents/workflows/audit-to-stories.md +63 -27
  109. package/.agents/workflows/helpers/acceptance-self-eval.md +41 -41
  110. package/.agents/workflows/helpers/code-quality-guardrails.md +4 -4
  111. package/.agents/workflows/helpers/code-review.md +2 -3
  112. package/.agents/workflows/helpers/deliver-digest.md +41 -57
  113. package/.agents/workflows/helpers/deliver-light.md +40 -105
  114. package/.agents/workflows/helpers/deliver-reference.md +1 -1
  115. package/.agents/workflows/helpers/deliver-story-reference.md +56 -62
  116. package/.agents/workflows/helpers/deliver-story.md +9 -13
  117. package/.agents/workflows/helpers/plan-reference.md +132 -196
  118. package/.agents/workflows/mandrel-plan.md +28 -41
  119. package/.agents/workflows/memory-consolidate.md +9 -13
  120. package/docs/CHANGELOG.md +33 -0
  121. package/lib/migrations/index.js +4 -0
  122. package/lib/migrations/steps/2.57.0-retire-delivery-limit-knobs.js +45 -0
  123. package/lib/migrations/steps/2.57.0-retire-planning-limit-knobs.js +59 -0
  124. package/package.json +1 -1
  125. package/.agents/scripts/lib/framework-version.js +0 -39
  126. package/.agents/scripts/lib/orchestration/consolidation-precondition.js +0 -223
  127. package/.agents/scripts/lib/orchestration/plan-persist/fan-out-gate.js +0 -97
  128. package/.agents/scripts/lib/orchestration/planning/decomposer-context.js +0 -26
  129. package/.agents/scripts/lib/orchestration/spec-budget.js +0 -89
  130. package/.agents/scripts/lib/orchestration/spec-spill.js +0 -74
  131. package/.agents/scripts/lib/orchestration/verify-tier-repair.js +0 -107
@@ -1,76 +1,46 @@
1
1
  /**
2
- * lib/orchestration/complexity-gate.js — shape-derived complexity routing
3
- * (Story #4722, superseding the word-count gate of Stories #4683/#4707).
4
- *
5
- * ## Route on the work, not the words
6
- *
7
- * The original gate routed a planning seed on its **word count**
8
- * (`maxSeedWords`), which is the wrong proxy in both directions: a detailed
9
- * prompt can describe trivial work, a terse one complex work. The bench
10
- * cohort (mandrel-bench 2.10.0) observed both failure modes — a lite verdict
11
- * fired at plan time and was then lost (a swallowed label write) or ignored
12
- * (deliver spawned a full story-worker anyway). This module now routes on the
13
- * **objective shape of the authored work**, staged across the pipeline:
14
- *
15
- * 1. **Plan time — signals, not routing.** {@link buildComplexitySignals}
16
- * emits advisory complexity *signals* (enumerated-artifact count,
17
- * risk-heuristic hits, repo state of predicted paths, sensitive-path
18
- * classes) carrying **no routing authority**. There is no word ceiling.
19
- * 2. **Planner judgment, ledgered.** The planner owns the
20
- * trivial-vs-standard verdict ({@link resolvePlannerRouteVerdict}) —
21
- * `lite` only with a recorded reason, persisted on plan state. This
22
- * generalizes the former one-way `applyPlannerDowngrade` seam into the
23
- * authored verdict itself; the conservative default without a recorded
24
- * reason is `full`.
25
- * 3. **Deterministic backstop at persist.** After authoring, the work has
26
- * measurable shape: {@link deriveStoryShape} reads the Story's own
27
- * effort and risk — distinct change kinds, declared magnitude,
28
- * uncertainty, deployable/migration span, and sensitive-path classes —
29
- * against {@link STORY_SHAPE_CEILINGS}. A `lite` claim whose work exceeds
30
- * them **fails closed to `full`** (`run-plan-persist.js`). Artifact
31
- * cardinality is deliberately not an axis (Story #4764).
32
- * 4. **Deliver dispatches on topology alone.** The dispatch *mode*
33
- * ({@link resolveStoryDispatchMode}) answers a different question from
34
- * the route: may the engine run in the router's own session? Only a
35
- * **single-Story run** may (Story #4736) — sub-agent isolation buys
36
- * nothing when there is no concurrent sibling to isolate from. Shape
37
- * cannot grant that session (Story #4829): a lite body makes work cheap,
38
- * it does not conjure a second session for a sibling to run in. Story
39
- * #5006 removed the shape derivation that survived there for reporting,
40
- * since no consumer read it. The `route::lite` label is a
41
- * **human-visible hint only**, never the control signal. Either way every
42
- * `single-story-close.js` gate runs unchanged.
2
+ * lib/orchestration/complexity-gate.js — shape-derived Story routing for the
3
+ * deliver side (Story #4722; plan-side lite claim deleted by Story #5312).
4
+ *
5
+ * Three surfaces survive, all read at delivery time:
6
+ *
7
+ * 1. **Seed signals ({@link buildComplexitySignals}).** `/mandrel-plan`'s
8
+ * context envelope carries the paths a seed predicts, their repo state
9
+ * (existing paths predict refactors; missing ones predict creates) and
10
+ * the `audit-rules.json` sensitive-path classes the footprint intersects.
11
+ * They ground the authoring template's `changes[]` skeleton and the
12
+ * `/prototype` offer; they route nothing.
13
+ * 2. **Story shape ({@link deriveStoryShape}).** The light path
14
+ * (`deliver-light`) reads a predicted footprint's effort and risk —
15
+ * distinct change kinds, declared magnitude, uncertainty,
16
+ * deployable/migration span, sensitive-path classes — against
17
+ * {@link STORY_SHAPE_CEILINGS} to decide whether a prompt may skip the
18
+ * Story-authoring ceremony. Artifact cardinality is deliberately not an
19
+ * axis (Story #4764).
20
+ * 3. **Dispatch mode ({@link resolveStoryDispatchMode}).** `/mandrel-deliver`
21
+ * answers a different question from the route: may the engine run in the
22
+ * router's own session? Only a **single-Story run** may (Story #4736).
23
+ *
24
+ * Story #5312 deleted the plan-side half: the planner's authored lite claim
25
+ * (`--route-downgrade-reason`), the persist-time shape backstop that
26
+ * validated it, the `route::lite` hint label, and the
27
+ * `planning.complexityGate` knobs. Persist no longer routes; every Story
28
+ * lands through the same engine and the same close gates, and the shape
29
+ * ceilings below are read only where a shape is actually decided on.
43
30
  *
44
31
  * The shape taxonomy is deliberately the one `review-depth.js` already
45
32
  * applies to the landed diff at close (`deriveChangeLevel` over the
46
33
  * `audit-rules.json` sensitive-path classes): **predicted shape at dispatch,
47
- * actual diff at close** — one taxonomy, two read points. And sensitivity
48
- * always wins: a small change whose footprint intersects a sensitive-path
49
- * class routes `full`, which keeps its fresh acceptance critic
50
- * (`ceremony-routing.js` routes a high derived level to a fresh spawn).
51
- *
52
- * ## What "lite" changes and — critically — what it never changes
53
- *
54
- * The lite route collapses the **advisory ceremony** only: the story-worker
55
- * sub-agent boot and the fresh acceptance-critic spawn. It **never** relaxes
56
- * a non-negotiable. {@link LITE_PATH_INVARIANTS} is the machine-readable
57
- * contract that the lite path still produces a Story ticket, still lands via
58
- * a PR to `main`, still runs every repo quality gate, and still honours
34
+ * actual diff at close** — one taxonomy, two read points. Sensitivity always
35
+ * wins: a small change whose footprint intersects a sensitive-path class
36
+ * routes `full`, which keeps its fresh acceptance critic.
37
+ *
38
+ * {@link LITE_PATH_INVARIANTS} is the machine-readable contract that the
39
+ * light path still produces a Story ticket, still lands via a PR to `main`,
40
+ * still runs every repo quality gate, and still honours
59
41
  * `rules/security-baseline.md`. Those gates run in `single-story-close.js`
60
42
  * regardless of route; the router cannot and does not switch them off.
61
43
  *
62
- * ## Configuration
63
- *
64
- * Operators tune the surface via `planning.complexityGate` in `.agentrc.json`:
65
- *
66
- * - `enabled` (default `true`) — `false` disables lite routing
67
- * everywhere: persist refuses lite claims and dispatch always takes the
68
- * sub-agent path.
69
- * - `maxArtifacts` (default `1`) — enumerated-artifact signal threshold;
70
- * an **input signal** for the planner, no longer a deterministic router.
71
- *
72
- * `maxSeedWords` is **removed** (hard cutover): word count routes nothing.
73
- *
74
44
  * @typedef {'lite'|'full'} ComplexityRoute
75
45
  */
76
46
 
@@ -79,27 +49,6 @@ import path from 'node:path';
79
49
  import { extractChangePaths } from '../story-body/story-body.js';
80
50
  import { deriveChangeLevel } from './review-depth.js';
81
51
 
82
- /**
83
- * Framework defaults for the complexity-routing surface. The SSOT the config
84
- * schema mirror and the configuration reference both cite. `maxSeedWords` is
85
- * gone: seed word count carries no routing authority (Story #4722).
86
- */
87
- const DEFAULT_COMPLEXITY_GATE = Object.freeze({
88
- enabled: true,
89
- maxArtifacts: 1,
90
- });
91
-
92
- /**
93
- * The persisted route marker for a lite-routed Story.
94
- *
95
- * **A human-visible hint only (Story #4722)** — never the control signal.
96
- * Persist applies it so a lite cohort is filterable in the GitHub UI, and
97
- * `deliver-light` reads the Story's own shape ({@link deriveStoryShape}) when
98
- * it needs one. Nothing routes on the label: a lost label or an unread marker
99
- * cannot misroute delivery.
100
- */
101
- export const LITE_ROUTE_LABEL = 'route::lite';
102
-
103
52
  /**
104
53
  * Effort/risk ceilings a Story's work must fit for the `lite` route
105
54
  * ({@link deriveStoryShape}). Framework constants, not operator knobs — a
@@ -378,54 +327,6 @@ const LITE_PATH_INVARIANTS = Object.freeze({
378
327
  securityBaseline: true,
379
328
  });
380
329
 
381
- /**
382
- * Coerce a candidate ceiling into a non-negative integer, falling back to the
383
- * framework default for anything malformed — a stray `-1` or `NaN` must never
384
- * widen the lite path (fail conservative).
385
- *
386
- * @param {unknown} value
387
- * @param {number} fallback
388
- * @returns {number}
389
- */
390
- function normalizeCeiling(value, fallback) {
391
- if (typeof value !== 'number' || !Number.isFinite(value) || value < 0) {
392
- return fallback;
393
- }
394
- return Math.floor(value);
395
- }
396
-
397
- /**
398
- * Resolve the effective complexity-gate config, shallow-overlaying an
399
- * operator `planning.complexityGate` block onto
400
- * {@link DEFAULT_COMPLEXITY_GATE}. Accepts the full resolved config, the bare
401
- * `planning` bag, or the bare `complexityGate` bag, mirroring the tolerant
402
- * unwrap the other routing accessors use.
403
- *
404
- * Exported for persist (`run-plan-persist.js#resolveEffectiveRoute`), which
405
- * consults `enabled` to refuse a planner lite claim when the gate is off —
406
- * the schema's documented contract. It is the only read point: Story #5006
407
- * removed the second one in {@link resolveStoryDispatchMode}, where the switch
408
- * gated a shape derivation whose result no consumer read.
409
- *
410
- * @param {object | null | undefined} config
411
- * @returns {{ enabled: boolean, maxArtifacts: number }}
412
- */
413
- export function resolveComplexityGate(config) {
414
- const raw =
415
- config?.planning?.complexityGate ?? config?.complexityGate ?? config ?? {};
416
- const bag = raw && typeof raw === 'object' ? raw : {};
417
- return {
418
- enabled:
419
- typeof bag.enabled === 'boolean'
420
- ? bag.enabled
421
- : DEFAULT_COMPLEXITY_GATE.enabled,
422
- maxArtifacts: normalizeCeiling(
423
- bag.maxArtifacts,
424
- DEFAULT_COMPLEXITY_GATE.maxArtifacts,
425
- ),
426
- };
427
- }
428
-
429
330
  /**
430
331
  * Count top-level enumerated items (`- `, `* `, `1. `) in a free-form seed —
431
332
  * each enumerated line is one predicted artifact.
@@ -464,20 +365,17 @@ function extractPredictedPaths(text) {
464
365
  }
465
366
 
466
367
  /**
467
- * Build the advisory complexity **signals** for a planning seed
468
- * (Story #4722 AC-2). Signals, not routing: the result carries
469
- * `routingAuthority: false` and no `route` field — the planner reads these
470
- * alongside its own judgment ({@link resolvePlannerRouteVerdict}) and the
471
- * deterministic shape backstop validates the authored Story at persist.
472
- *
473
- * - `artifactCount` — enumerated items in the seed, with the
474
- * configured `maxArtifacts` threshold beside it
475
- * as one input signal.
476
- * - `riskHeuristicHits` — `planning.riskHeuristics` phrases present in
477
- * the seed (same substring matcher the
478
- * pre-mortem critic uses).
479
- * - `predictedPaths` / `repoState` — path-like tokens in the seed and
480
- * which of them exist in the repo (existing
368
+ * Build the advisory complexity **signals** for a planning seed. Signals,
369
+ * not routing: the result carries `routingAuthority: false` and no `route`
370
+ * field — they ground the authoring template's pre-resolved `changes[]` and
371
+ * the `/prototype` offer, nothing else (Story #5312 deleted the risk-heuristic
372
+ * hits and the `planning.complexityGate` echo that used to ride alongside).
373
+ *
374
+ * - `artifactCount` — enumerated items in the seed, a rough width
375
+ * signal for the operator's eye only.
376
+ * - `predictedPaths` — path-like tokens the seed names, in order of
377
+ * first appearance (capped).
378
+ * - `repoState` — which predicted paths exist in the repo (existing
481
379
  * paths predict refactors; missing predict
482
380
  * creates).
483
381
  * - `sensitivePathClasses` — `audit-rules.json` sensitive-path classes the
@@ -489,8 +387,6 @@ function extractPredictedPaths(text) {
489
387
  *
490
388
  * @param {{
491
389
  * seedText?: string,
492
- * config?: object,
493
- * riskHeuristics?: string[],
494
390
  * cwd?: string,
495
391
  * pathExistsFn?: (absPath: string) => boolean,
496
392
  * injectedRules?: object,
@@ -498,37 +394,21 @@ function extractPredictedPaths(text) {
498
394
  * }} [args]
499
395
  * @returns {{
500
396
  * artifactCount: number,
501
- * maxArtifacts: number,
502
- * riskHeuristicHits: string[],
503
397
  * predictedPaths: string[],
504
398
  * repoState: { existingPaths: string[], missingPaths: string[] },
505
399
  * sensitivePathClasses: string[],
506
- * gate: { enabled: boolean },
507
400
  * advisory: true,
508
401
  * routingAuthority: false,
509
402
  * }}
510
403
  */
511
404
  export function buildComplexitySignals({
512
405
  seedText = '',
513
- config,
514
- riskHeuristics = [],
515
406
  cwd,
516
407
  pathExistsFn = existsSync,
517
408
  injectedRules,
518
409
  selectSensitivePathClassesFn,
519
410
  } = {}) {
520
- const gate = resolveComplexityGate(config);
521
411
  const text = typeof seedText === 'string' ? seedText : '';
522
- const haystack = text.toLowerCase();
523
-
524
- const riskHeuristicHits = (
525
- Array.isArray(riskHeuristics) ? riskHeuristics : []
526
- ).filter(
527
- (phrase) =>
528
- typeof phrase === 'string' &&
529
- phrase.trim().length > 0 &&
530
- haystack.includes(phrase.trim().toLowerCase()),
531
- );
532
412
 
533
413
  const predictedPaths = extractPredictedPaths(text);
534
414
  const root = typeof cwd === 'string' && cwd !== '' ? cwd : process.cwd();
@@ -552,60 +432,14 @@ export function buildComplexitySignals({
552
432
 
553
433
  return {
554
434
  artifactCount: countSeedArtifacts(text),
555
- maxArtifacts: gate.maxArtifacts,
556
- riskHeuristicHits,
557
435
  predictedPaths,
558
436
  repoState: { existingPaths, missingPaths },
559
437
  sensitivePathClasses: classes,
560
- gate: { enabled: gate.enabled },
561
438
  advisory: /** @type {const} */ (true),
562
439
  routingAuthority: /** @type {const} */ (false),
563
440
  };
564
441
  }
565
442
 
566
- /**
567
- * Resolve the planner's authored trivial-vs-standard verdict
568
- * (Story #4722 AC-2, generalizing the former one-way `applyPlannerDowngrade`
569
- * seam into the verdict itself).
570
- *
571
- * The planner — not a word count — owns the judgment, and the contract keeps
572
- * it auditable: `lite` **only** with a non-empty recorded reason (carried on
573
- * `authored` and ledgered on every created Story's `story-plan-state`
574
- * checkpoint by persist). Absent a recorded reason the conservative default
575
- * stands: `full`, with `authored: null`. Pure and total.
576
- *
577
- * The verdict is a **claim**, not the decision — persist validates it against
578
- * the authored Story's shape ({@link deriveStoryShape}) and fails closed to
579
- * `full` when the shape exceeds the ceilings.
580
- *
581
- * @param {{ reason?: unknown }} [args]
582
- * @returns {{
583
- * route: ComplexityRoute,
584
- * reasons: string[],
585
- * authored: Readonly<{ route: 'lite', reason: string }>|null,
586
- * preserves: typeof LITE_PATH_INVARIANTS,
587
- * }}
588
- */
589
- export function resolvePlannerRouteVerdict({ reason } = {}) {
590
- const recorded = typeof reason === 'string' ? reason.trim() : '';
591
- if (recorded === '') {
592
- return {
593
- route: 'full',
594
- reasons: [
595
- 'no authored lite verdict (no recorded reason) — standard full route',
596
- ],
597
- authored: null,
598
- preserves: LITE_PATH_INVARIANTS,
599
- };
600
- }
601
- return {
602
- route: 'lite',
603
- reasons: [`planner verdict: lite (recorded reason): ${recorded}`],
604
- authored: Object.freeze({ route: 'lite', reason: recorded }),
605
- preserves: LITE_PATH_INVARIANTS,
606
- };
607
- }
608
-
609
443
  /**
610
444
  * Assemble the effort/risk shape of a footprint — the evidence
611
445
  * {@link deriveStoryShape} decides on and carries on its result.
@@ -13,23 +13,23 @@
13
13
  * layer — so the gate scans `type === 'story'` tickets and reads the
14
14
  * `{ path, assumption }` entries inlined on each Story body.
15
15
  *
16
- * Rules (one error per mismatched path):
17
- * - `creates` + path **exists** → error (Story would clobber).
16
+ * Rules (one finding per mismatched path). Story #5312 demoted every
17
+ * mismatch but one to a **warning** the dry-run lists and the persist
18
+ * proceeds past — `changes[]` is an advisory sketch the deliverer revises
19
+ * against the real tree, so refusing a plan on it cost re-authoring rounds
20
+ * for a footprint nobody was bound to:
21
+ * - `creates` + path **exists** → warning (Story may clobber).
18
22
  * - `refactors-existing` (via `changes`) + path **absent and never
19
23
  * tracked** at `baseBranchRef` → auto-normalized to `creates` with a
20
24
  * logged warning (#4496 fix 5): a refactor declaration against a
21
- * path with no history is deterministically a create, so rejecting it
22
- * only forces a reject→amend→re-persist cycle for a mechanical rewrite.
25
+ * path with no history is deterministically a create.
23
26
  * - `refactors-existing` (via `changes`) + path **absent but present in
24
- * that ref's history** → hard error naming the removing commit and, when
25
- * git detects one, the rename target (Story #5265). The normalization
26
- * rescues a mislabel; it must not rescue a plan authored against a file
27
- * the tree deleted, which propagates into acceptance criteria nothing
28
- * can satisfy. Genuine mismatches keep failing — a `references`-sourced
29
- * `refactors-existing` on an absent path is a missing read dependency
30
- * and stays an error.
31
- * - `exists` + path **absent** → error (read dependency missing).
32
- * - `deletes` + path **absent** → error (nothing to delete).
27
+ * that ref's history** → warning naming the removing commit and, when
28
+ * git detects one, the rename target (Story #5265) — a plan authored
29
+ * against a file the tree deleted is worth saying out loud.
30
+ * - `exists` + path **absent** → warning (read dependency missing).
31
+ * - `deletes` + path **absent** → **error** (nothing to delete —
32
+ * the one declaration a deliverer cannot act on at all).
33
33
  *
34
34
  * Wave awareness (Story #3960): the base-branch-only rules above produce
35
35
  * false signals once an earlier Story in the same epic creates (or deletes)
@@ -386,8 +386,9 @@ function predecessorMutator(index, path, predecessors) {
386
386
  * predecessors (Story #3960). Returns an envelope:
387
387
  *
388
388
  * {
389
- * errors: string[] // one entry per mismatch, batched per Story
390
- * warnings: string[] // legacy/no-assumption deprecation nudges +
389
+ * errors: string[] // a `deletes` naming an absent path, or a
390
+ * // legacy string-bullet body — the refusals
391
+ * warnings: string[] // every other mismatch (Story #5312) +
391
392
  * // auto-normalization notices (#4496 fix 5)
392
393
  * mismatches: object[] // structured payload for downstream tooling
393
394
  * normalizations: object[] // `refactors-existing`→`creates`
@@ -513,7 +514,7 @@ export function validateStoryFileAssumptions(opts) {
513
514
  continue;
514
515
  }
515
516
  mismatches.push(finding);
516
- errors.push(renderMismatch(finding));
517
+ routeMismatch(finding, { errors, warnings });
517
518
  continue;
518
519
  }
519
520
  // Wave-aware concurrent-create check (Story #3960): two Stories with
@@ -541,7 +542,7 @@ export function validateStoryFileAssumptions(opts) {
541
542
  producerSlug: concurrent,
542
543
  };
543
544
  mismatches.push(conflict);
544
- errors.push(renderMismatch(conflict));
545
+ warnings.push(renderMismatch(conflict));
545
546
  }
546
547
  }
547
548
  }
@@ -549,6 +550,20 @@ export function validateStoryFileAssumptions(opts) {
549
550
  return { errors, warnings, mismatches, normalizations };
550
551
  }
551
552
 
553
+ /**
554
+ * Route one confirmed mismatch to the channel it belongs on (Story #5312):
555
+ * a `deletes` on an absent path is the one declaration nothing can act on and
556
+ * stays an error; every other mismatch is advisory and lands on `warnings`.
557
+ *
558
+ * @param {object} finding
559
+ * @param {{ errors: string[], warnings: string[] }} channels
560
+ * @returns {void}
561
+ */
562
+ function routeMismatch(finding, { errors, warnings }) {
563
+ const channel = finding.assumption === 'deletes' ? errors : warnings;
564
+ channel.push(renderMismatch(finding));
565
+ }
566
+
552
567
  /**
553
568
  * Route one mismatch to the errors channel or the normalization channel
554
569
  * (Story #5265).
@@ -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,