mandrel 2.30.0 → 2.32.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 (253) hide show
  1. package/.agents/README.md +13 -17
  2. package/.agents/agents/acceptance-critic.md +1 -2
  3. package/.agents/docs/SDLC.md +4 -4
  4. package/.agents/docs/agentrc-reference.json +61 -57
  5. package/.agents/docs/configuration.md +274 -227
  6. package/.agents/docs/execution-reference.md +13 -14
  7. package/.agents/docs/quality-gates.md +195 -23
  8. package/.agents/instructions.md +2 -5
  9. package/.agents/rules/ci-remediation.md +44 -1
  10. package/.agents/rules/git-conventions-reference.md +27 -27
  11. package/.agents/rules/git-conventions.md +4 -2
  12. package/.agents/rules/known-tooling-behavior.md +66 -30
  13. package/.agents/rules/testing-standards.md +35 -71
  14. package/.agents/runtime-deps.json +0 -1
  15. package/.agents/schemas/agentrc.schema.json +1939 -1400
  16. package/.agents/schemas/lifecycle/README.md +21 -14
  17. package/.agents/schemas/lifecycle/ledger-record.schema.json +76 -22
  18. package/.agents/schemas/story-deliver-terminal.schema.json +2 -2
  19. package/.agents/scripts/README.md +7 -29
  20. package/.agents/scripts/apply-quality-bootstrap.js +27 -34
  21. package/.agents/scripts/bootstrap.js +28 -26
  22. package/.agents/scripts/check-baseline-drift.js +73 -13
  23. package/.agents/scripts/check-baseline-scope.js +362 -0
  24. package/.agents/scripts/check-dead-exports.js +9 -1
  25. package/.agents/scripts/check-gherkin-corpus.js +508 -0
  26. package/.agents/scripts/check-knip-entries.js +136 -0
  27. package/.agents/scripts/check-lifecycle-lint.js +36 -112
  28. package/.agents/scripts/check-schema-references.js +1 -1
  29. package/.agents/scripts/diagnose-friction.js +7 -4
  30. package/.agents/scripts/generate-config-docs.js +263 -171
  31. package/.agents/scripts/install-matrix-assert.js +0 -1
  32. package/.agents/scripts/lib/ITicketingProvider.js +0 -58
  33. package/.agents/scripts/lib/audit-baselines/staleness.js +6 -6
  34. package/.agents/scripts/lib/audit-baselines/trend.js +7 -8
  35. package/.agents/scripts/lib/audit-baselines/weights.js +4 -5
  36. package/.agents/scripts/lib/audit-suite/checklist-threading.js +1 -1
  37. package/.agents/scripts/lib/audit-to-stories/build-story-body.js +0 -1
  38. package/.agents/scripts/lib/baselines/envelope.js +41 -60
  39. package/.agents/scripts/lib/baselines/git-base.js +30 -37
  40. package/.agents/scripts/lib/baselines/kinds/_crap-new-method-gate.js +103 -0
  41. package/.agents/scripts/lib/baselines/kinds/_crap-read.js +150 -0
  42. package/.agents/scripts/lib/baselines/kinds/crap.js +25 -65
  43. package/.agents/scripts/lib/baselines/orphan-pruner.js +233 -0
  44. package/.agents/scripts/lib/baselines/refresh-service.js +6 -8
  45. package/.agents/scripts/lib/baselines/scope-assert.js +223 -0
  46. package/.agents/scripts/lib/baselines/scope-inventory.js +314 -0
  47. package/.agents/scripts/lib/bdd-step-index.js +326 -0
  48. package/.agents/scripts/lib/bootstrap/install-ledger.js +5 -3
  49. package/.agents/scripts/lib/bootstrap/issue-forms-template.js +4 -6
  50. package/.agents/scripts/lib/bootstrap/manifest.js +17 -40
  51. package/.agents/scripts/lib/bootstrap/project-bootstrap.js +12 -59
  52. package/.agents/scripts/lib/bootstrap/quality-bootstrap.js +62 -2
  53. package/.agents/scripts/lib/checks/loop-health.js +9 -37
  54. package/.agents/scripts/lib/child-exec.js +193 -0
  55. package/.agents/scripts/lib/cli/standard-args.js +1 -1
  56. package/.agents/scripts/lib/cli-args.js +64 -0
  57. package/.agents/scripts/lib/close-validation/gates.js +2 -2
  58. package/.agents/scripts/lib/close-validation/runner.js +3 -3
  59. package/.agents/scripts/lib/config/acceptance-eval.js +5 -52
  60. package/.agents/scripts/lib/config/commands.js +3 -5
  61. package/.agents/scripts/lib/config/explain.js +5 -7
  62. package/.agents/scripts/lib/config/gates/bundle-size.schema.js +32 -6
  63. package/.agents/scripts/lib/config/gates/coverage.schema.js +25 -5
  64. package/.agents/scripts/lib/config/gates/crap-incremental-coverage.schema.js +12 -2
  65. package/.agents/scripts/lib/config/gates/crap.schema.js +68 -23
  66. package/.agents/scripts/lib/config/gates/duplication.schema.js +29 -17
  67. package/.agents/scripts/lib/config/gates/index.js +5 -2
  68. package/.agents/scripts/lib/config/gates/lighthouse.schema.js +34 -6
  69. package/.agents/scripts/lib/config/gates/lint.schema.js +11 -2
  70. package/.agents/scripts/lib/config/gates/maintainability.schema.js +37 -15
  71. package/.agents/scripts/lib/config/gates/mutation.schema.js +15 -3
  72. package/.agents/scripts/lib/config/gates/shared.js +58 -9
  73. package/.agents/scripts/lib/config/github.js +0 -1
  74. package/.agents/scripts/lib/config/limits.js +3 -48
  75. package/.agents/scripts/lib/config/qa.js +105 -0
  76. package/.agents/scripts/lib/config/temp-paths.js +6 -5
  77. package/.agents/scripts/lib/config-settings-schema-delivery.js +237 -56
  78. package/.agents/scripts/lib/config-settings-schema-quality.js +209 -29
  79. package/.agents/scripts/lib/config-settings-schema.js +386 -39
  80. package/.agents/scripts/lib/crap-baseline-join.js +126 -9
  81. package/.agents/scripts/lib/crap-utils.js +84 -520
  82. package/.agents/scripts/lib/dead-exports-knip.js +79 -10
  83. package/.agents/scripts/lib/degraded-mode.js +2 -2
  84. package/.agents/scripts/lib/doc-tiers.js +3 -3
  85. package/.agents/scripts/lib/feedback-loop/graduator-core.js +46 -104
  86. package/.agents/scripts/lib/feedback-loop/retro-proposals-graduator.js +10 -8
  87. package/.agents/scripts/lib/fs-walk.js +52 -0
  88. package/.agents/scripts/lib/git-branch-lifecycle.js +2 -2
  89. package/.agents/scripts/lib/git-utils.js +16 -36
  90. package/.agents/scripts/lib/knip-entry-sync.js +469 -0
  91. package/.agents/scripts/lib/observability/metrics-ledger.js +1 -1
  92. package/.agents/scripts/lib/observability/runtime-friction.js +10 -0
  93. package/.agents/scripts/lib/observability/signal-validator.js +5 -85
  94. package/.agents/scripts/lib/observability/signals-writer.js +19 -62
  95. package/.agents/scripts/lib/observability/source-classifier.js +5 -7
  96. package/.agents/scripts/lib/observability/terse-result.js +3 -3
  97. package/.agents/scripts/lib/orchestration/behind-recovery.js +114 -0
  98. package/.agents/scripts/lib/orchestration/ceremony-routing.js +7 -8
  99. package/.agents/scripts/lib/orchestration/ci-rerun-guard.js +34 -33
  100. package/.agents/scripts/lib/orchestration/code-review.js +2 -2
  101. package/.agents/scripts/lib/orchestration/complexity-gate.js +43 -161
  102. package/.agents/scripts/lib/orchestration/diff-magnitude.js +4 -4
  103. package/.agents/scripts/lib/orchestration/label-transitions.js +3 -2
  104. package/.agents/scripts/lib/orchestration/lease-guard-shared.js +12 -38
  105. package/.agents/scripts/lib/orchestration/lifecycle/emit-merge-unlanded.js +5 -6
  106. package/.agents/scripts/lib/orchestration/plan-metrics.js +2 -3
  107. package/.agents/scripts/lib/orchestration/plan-persist/persist-helpers.js +6 -0
  108. package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +0 -1
  109. package/.agents/scripts/lib/orchestration/{lifecycle/listeners/watcher.js → pr-watch.js} +58 -208
  110. package/.agents/scripts/lib/orchestration/resolve-stories.js +5 -15
  111. package/.agents/scripts/lib/orchestration/review-providers/codex.js +1 -1
  112. package/.agents/scripts/lib/orchestration/review-providers/mi-exemptions.js +130 -0
  113. package/.agents/scripts/lib/orchestration/review-providers/native.js +30 -16
  114. package/.agents/scripts/lib/orchestration/single-story-close/phases/code-review.js +1 -1
  115. package/.agents/scripts/lib/orchestration/single-story-close/phases/confirm-merge.js +37 -26
  116. package/.agents/scripts/lib/orchestration/single-story-close/phases/conventional-subject.js +376 -0
  117. package/.agents/scripts/lib/orchestration/single-story-close/phases/normalize-pr-title.js +161 -151
  118. package/.agents/scripts/lib/orchestration/single-story-close/phases/options.js +15 -3
  119. package/.agents/scripts/lib/orchestration/single-story-close/phases/pull-request.js +10 -15
  120. package/.agents/scripts/lib/orchestration/single-story-close/phases/push.js +17 -2
  121. package/.agents/scripts/lib/orchestration/single-story-close/phases/review-block.js +5 -0
  122. package/.agents/scripts/lib/orchestration/single-story-close/phases/review-override.js +157 -0
  123. package/.agents/scripts/lib/orchestration/single-story-close/phases/worktree-reap.js +0 -14
  124. package/.agents/scripts/lib/orchestration/single-story-close/runner.js +65 -25
  125. package/.agents/scripts/lib/orchestration/single-story-lease-guard.js +20 -31
  126. package/.agents/scripts/lib/orchestration/spec-spill.js +17 -3
  127. package/.agents/scripts/lib/orchestration/story-close/phases/review-core.js +7 -6
  128. package/.agents/scripts/lib/orchestration/story-deliver-terminal.js +2 -1
  129. package/.agents/scripts/lib/orchestration/task-body-validator.js +4 -1
  130. package/.agents/scripts/lib/orchestration/ticket-lease.js +28 -127
  131. package/.agents/scripts/lib/orchestration/ticket-validator-sizing.js +1 -1
  132. package/.agents/scripts/lib/orchestration/ticketing/reads.js +5 -5
  133. package/.agents/scripts/lib/orchestration/ticketing/transition.js +5 -4
  134. package/.agents/scripts/lib/orchestration/verify-tier-repair.js +107 -0
  135. package/.agents/scripts/lib/qa/coverage-verdict.js +5 -87
  136. package/.agents/scripts/lib/signals/detectors/common.js +1 -1
  137. package/.agents/scripts/lib/signals/index.js +8 -6
  138. package/.agents/scripts/lib/signals/schema.js +20 -25
  139. package/.agents/scripts/lib/signals/write.js +8 -8
  140. package/.agents/scripts/lib/story-body/story-body.js +12 -59
  141. package/.agents/scripts/lib/temp-retention.js +1 -1
  142. package/.agents/scripts/lib/templates/decomposer-prompts.js +16 -14
  143. package/.agents/scripts/lib/ticket-body-sections.js +4 -5
  144. package/.agents/scripts/lib/worktree/lifecycle/merge-reachability.js +13 -45
  145. package/.agents/scripts/lib/worktree/lifecycle/reap.js +4 -5
  146. package/.agents/scripts/lib/worktree-manager.js +2 -3
  147. package/.agents/scripts/lint-label-vocabulary.js +2 -24
  148. package/.agents/scripts/pr-watch-with-update.js +7 -5
  149. package/.agents/scripts/providers/github/cache.js +2 -2
  150. package/.agents/scripts/providers/github/comments.js +6 -28
  151. package/.agents/scripts/providers/github/compose.js +0 -15
  152. package/.agents/scripts/providers/github/errors.js +10 -27
  153. package/.agents/scripts/providers/github/request-helpers.js +1 -2
  154. package/.agents/scripts/providers/github/sub-issues.js +10 -218
  155. package/.agents/scripts/providers/github.js +4 -7
  156. package/.agents/scripts/prune-baseline-orphans.js +181 -0
  157. package/.agents/scripts/resolve-stories.js +0 -2
  158. package/.agents/scripts/run-lint.js +61 -61
  159. package/.agents/scripts/run-test-profile.js +6 -6
  160. package/.agents/scripts/run-verify.js +48 -30
  161. package/.agents/scripts/single-story-close.js +20 -0
  162. package/.agents/scripts/single-story-init.js +12 -35
  163. package/.agents/scripts/update-dead-exports-baseline.js +321 -0
  164. package/.agents/skills/core/gates-and-baselines/SKILL.md +2 -2
  165. package/.agents/skills/skills.index.json +2 -12
  166. package/.agents/skills/stack/qa/playwright/SKILL.md +48 -0
  167. package/.agents/workflows/audit-documentation.md +5 -6
  168. package/.agents/workflows/audit-to-stories.md +2 -2
  169. package/.agents/workflows/helpers/audit-lens-core.md +11 -12
  170. package/.agents/workflows/helpers/code-quality-guardrails.md +15 -14
  171. package/.agents/workflows/helpers/code-review.md +3 -8
  172. package/.agents/workflows/helpers/deliver-reference.md +2 -1
  173. package/.agents/workflows/helpers/deliver-story-reference.md +27 -16
  174. package/.agents/workflows/helpers/worktree-lifecycle.md +1 -2
  175. package/.agents/workflows/mandrel-update.md +10 -10
  176. package/.agents/workflows/qa-assist.md +15 -20
  177. package/.agents/workflows/qa-explore.md +9 -8
  178. package/README.md +1 -1
  179. package/docs/CHANGELOG.md +49 -0
  180. package/lib/migrations/index.js +2 -0
  181. package/lib/migrations/steps/2.32.0-retire-lint-baseline-command.js +127 -0
  182. package/package.json +12 -3
  183. package/.agents/schemas/lifecycle/checkpoint.written.schema.json +0 -13
  184. package/.agents/schemas/lifecycle/close-validate.end.schema.json +0 -18
  185. package/.agents/schemas/lifecycle/close-validate.start.schema.json +0 -13
  186. package/.agents/schemas/lifecycle/code-review.end.schema.json +0 -30
  187. package/.agents/schemas/lifecycle/code-review.start.schema.json +0 -12
  188. package/.agents/schemas/lifecycle/intervention.recorded.schema.json +0 -15
  189. package/.agents/schemas/lifecycle/loop.tick.schema.json +0 -20
  190. package/.agents/schemas/lifecycle/notification.emitted.schema.json +0 -18
  191. package/.agents/schemas/lifecycle/pr.created.schema.json +0 -14
  192. package/.agents/schemas/lifecycle/retro.end.schema.json +0 -16
  193. package/.agents/schemas/lifecycle/retro.start.schema.json +0 -12
  194. package/.agents/schemas/lifecycle/story.blocked.schema.json +0 -13
  195. package/.agents/schemas/lifecycle/story.dispatch.end.schema.json +0 -17
  196. package/.agents/schemas/lifecycle/story.dispatch.start.schema.json +0 -15
  197. package/.agents/schemas/lifecycle/story.merged.schema.json +0 -13
  198. package/.agents/scripts/check-gherkin-placeholders.js +0 -663
  199. package/.agents/scripts/check-lifecycle-doc-drift.js +0 -411
  200. package/.agents/scripts/lib/audit-suite/cli.js +0 -64
  201. package/.agents/scripts/lib/bootstrap/baselines-layout-migration.js +0 -202
  202. package/.agents/scripts/lib/bootstrap/ci-workflow-template.js +0 -212
  203. package/.agents/scripts/lib/checks/baseline-drift-main-checkout.js +0 -104
  204. package/.agents/scripts/lib/checks/push-hook-parity.js +0 -106
  205. package/.agents/scripts/lib/checks/windows-coverage-noise-floor.js +0 -92
  206. package/.agents/scripts/lib/checks/worktree-bootstrap-env.js +0 -81
  207. package/.agents/scripts/lib/checks/worktree-residue-biome.js +0 -55
  208. package/.agents/scripts/lib/crap-baseline-index.js +0 -46
  209. package/.agents/scripts/lib/crap-utils-incremental.js +0 -113
  210. package/.agents/scripts/lib/dynamic-workflow/capability.js +0 -396
  211. package/.agents/scripts/lib/feedback-loop/audit-results-graduator.js +0 -335
  212. package/.agents/scripts/lib/mutation/baseline-snapshot.js +0 -239
  213. package/.agents/scripts/lib/mutation/config-detector.js +0 -119
  214. package/.agents/scripts/lib/mutation/stryker-runner.js +0 -306
  215. package/.agents/scripts/lib/mutation/survivor-report.js +0 -160
  216. package/.agents/scripts/lib/observability/active-story-env.js +0 -170
  217. package/.agents/scripts/lib/observability/tool-trace-hook.js +0 -456
  218. package/.agents/scripts/lib/orchestration/acceptance-clusters.js +0 -111
  219. package/.agents/scripts/lib/orchestration/context-envelope.js +0 -277
  220. package/.agents/scripts/lib/orchestration/detectors-phase.js +0 -194
  221. package/.agents/scripts/lib/orchestration/lifecycle/bus.js +0 -309
  222. package/.agents/scripts/lib/orchestration/lifecycle/emit-loop-tick.js +0 -181
  223. package/.agents/scripts/lib/orchestration/lifecycle/ledger-writer.js +0 -229
  224. package/.agents/scripts/lib/orchestration/lifecycle/listeners/README.md +0 -54
  225. package/.agents/scripts/lib/orchestration/lifecycle/trace-logger.js +0 -344
  226. package/.agents/scripts/lib/orchestration/lint-baseline-service.js +0 -114
  227. package/.agents/scripts/lib/orchestration/pr-base-guard.js +0 -37
  228. package/.agents/scripts/lib/orchestration/resolves-token.js +0 -127
  229. package/.agents/scripts/lib/orchestration/spec-section-validator.js +0 -130
  230. package/.agents/scripts/lib/orchestration/story-close/emit-blocked.js +0 -55
  231. package/.agents/scripts/lib/orchestration/story-close/phases/code-review.js +0 -211
  232. package/.agents/scripts/lib/planning-corpus.js +0 -37
  233. package/.agents/scripts/lib/qa/coverage-report.js +0 -181
  234. package/.agents/scripts/lib/qa/propose-missing-test.js +0 -95
  235. package/.agents/scripts/lib/qa/qa-context-hydrator.js +0 -217
  236. package/.agents/scripts/lib/signals/detectors/index.js +0 -14
  237. package/.agents/scripts/lib/signals/detectors/retry.js +0 -253
  238. package/.agents/scripts/lib/signals/detectors/rework.js +0 -167
  239. package/.agents/scripts/lib/signals/read.js +0 -268
  240. package/.agents/scripts/lib/signals/span-tree.js +0 -291
  241. package/.agents/scripts/lib/story-lifecycle.js +0 -194
  242. package/.agents/scripts/lib/story-plan.js +0 -379
  243. package/.agents/scripts/lib/util/phase-timer-state.js +0 -72
  244. package/.agents/scripts/lib/util/phase-timer.js +0 -163
  245. package/.agents/scripts/lib/workers/combined-mi-crap-worker.js +0 -169
  246. package/.agents/scripts/lint-baseline.js +0 -507
  247. package/.agents/scripts/providers/github/prs.js +0 -103
  248. package/.agents/scripts/signals-view.js +0 -309
  249. package/.agents/scripts/story-plan.js +0 -370
  250. package/.agents/scripts/sync-branch-from-base.js +0 -149
  251. package/.agents/scripts/validate-docs-freshness.js +0 -314
  252. package/.agents/skills/core/diagnose-friction/SKILL.md +0 -78
  253. package/.agents/workflows/helpers/signals.md +0 -112
@@ -29,18 +29,17 @@
29
29
  * against {@link STORY_SHAPE_CEILINGS}. A `lite` claim whose work exceeds
30
30
  * them **fails closed to `full`** (`run-plan-persist.js`). Artifact
31
31
  * cardinality is deliberately not an axis (Story #4764).
32
- * 4. **Deliver re-derives.** `/deliver` computes the route from the fetched
33
- * Story body via the **same** shape function at dispatch
34
- * ({@link resolveStoryDispatchMode}) and **reports** it, while the
35
- * dispatch *mode* answers a different question: may the engine run in the
36
- * router's own session? Only a **single-Story run** may (Story #4736)
37
- * sub-agent isolation buys nothing when there is no concurrent sibling to
38
- * isolate from. Shape cannot grant that session (Story #4829): a lite body
39
- * makes work cheap, it does not conjure a second session for a sibling to
40
- * run in. The `route::lite` label is a **human-visible hint only**, never
41
- * the control signal: a lost label or an unread marker can no longer
42
- * misroute delivery. Either way every `single-story-close.js` gate runs
43
- * unchanged.
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.
44
43
  *
45
44
  * The shape taxonomy is deliberately the one `review-depth.js` already
46
45
  * applies to the landed diff at close (`deriveChangeLevel` over the
@@ -77,10 +76,7 @@
77
76
 
78
77
  import { existsSync } from 'node:fs';
79
78
  import path from 'node:path';
80
- import {
81
- extractChangePaths,
82
- parse as parseStoryBody,
83
- } from '../story-body/story-body.js';
79
+ import { extractChangePaths } from '../story-body/story-body.js';
84
80
  import { deriveChangeLevel } from './review-depth.js';
85
81
 
86
82
  /**
@@ -97,11 +93,10 @@ const DEFAULT_COMPLEXITY_GATE = Object.freeze({
97
93
  * The persisted route marker for a lite-routed Story.
98
94
  *
99
95
  * **A human-visible hint only (Story #4722)** — never the control signal.
100
- * Persist still applies it so a lite cohort is filterable in the GitHub UI,
101
- * but `/deliver` derives the route from the Story body's own shape
102
- * ({@link resolveStoryDispatchMode}); a Story with the label whose shape
103
- * derives `full` dispatches as a sub-agent, and a lite-shaped Story with the
104
- * label absent (or its write failed) still executes inline.
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.
105
100
  */
106
101
  export const LITE_ROUTE_LABEL = 'route::lite';
107
102
 
@@ -279,8 +274,8 @@ function spansMigrationAndConsumers(paths) {
279
274
  * against ground truth by the diff backstop.
280
275
  * - **Absolute rules** — `migration-span`, `sensitive-path`. Risk, not size.
281
276
  * - **Unknown-footprint rejections** — `no-changes`, `unreadable-changes`,
282
- * `glob-footprint`, `no-acceptance`, `classification-unavailable`,
283
- * `unparseable-body`. Nothing was judged, so there is nothing to waive.
277
+ * `glob-footprint`, `no-acceptance`, `classification-unavailable`.
278
+ * Nothing was judged, so there is nothing to waive.
284
279
  *
285
280
  * A `lite` route carries `code: null`.
286
281
  */
@@ -296,7 +291,6 @@ export const SHAPE_CODES = Object.freeze({
296
291
  GLOB_FOOTPRINT: 'glob-footprint',
297
292
  NO_ACCEPTANCE: 'no-acceptance',
298
293
  CLASSIFICATION_UNAVAILABLE: 'classification-unavailable',
299
- UNPARSEABLE_BODY: 'unparseable-body',
300
294
  });
301
295
 
302
296
  /**
@@ -409,9 +403,9 @@ function normalizeCeiling(value, fallback) {
409
403
  *
410
404
  * Exported for persist (`run-plan-persist.js#resolveEffectiveRoute`), which
411
405
  * consults `enabled` to refuse a planner lite claim when the gate is off —
412
- * the schema's documented contract, and the same switch dispatch reads in
413
- * {@link resolveStoryDispatchMode}, so the two read points cannot disagree
414
- * about whether lite routing is live.
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.
415
409
  *
416
410
  * @param {object | null | undefined} config
417
411
  * @returns {{ enabled: boolean, maxArtifacts: number }}
@@ -810,60 +804,7 @@ export function deriveStoryShape({
810
804
  }
811
805
 
812
806
  /**
813
- * Derive the complexity route from a Story's **serialized body markdown**
814
- * the deliver-side entry to {@link deriveStoryShape} (`/deliver` already
815
- * fetches the body; the route is computed from it, never from a label). An
816
- * unparseable body degrades to `full`: unknown shape is not trivial shape.
817
- *
818
- * Module-private, reachable end to end through
819
- * {@link resolveStoryDispatchMode} (which returns the derived route) — so
820
- * there is no test-only export to leave production-dead.
821
- *
822
- * @param {string} body Serialized Story-body markdown.
823
- * @param {{ injectedRules?: object, selectSensitivePathClassesFn?: Function }} [opts]
824
- * @returns {ReturnType<typeof deriveStoryShape>}
825
- */
826
- function deriveStoryRouteFromBody(body, opts = {}) {
827
- let parsed;
828
- try {
829
- parsed = parseStoryBody(String(body ?? '')).body;
830
- } catch (err) {
831
- return {
832
- route: 'full',
833
- reasons: [
834
- `Story body is unparseable (${err?.message ?? err}) — shape unknown; conservative full route`,
835
- ],
836
- code: SHAPE_CODES.UNPARSEABLE_BODY,
837
- shape: null,
838
- ceilings: STORY_SHAPE_CEILINGS,
839
- preserves: LITE_PATH_INVARIANTS,
840
- };
841
- }
842
- return deriveStoryShape({
843
- changes: parsed?.changes,
844
- acceptance: parsed?.acceptance,
845
- injectedRules: opts.injectedRules,
846
- selectSensitivePathClassesFn: opts.selectSensitivePathClassesFn,
847
- });
848
- }
849
-
850
- /**
851
- * Best-effort route derivation for reporting, when the *mode* is already
852
- * pinned by run topology and only `route` remains to be filled in. A body
853
- * that will not parse yields `null` rather than throwing — the caller is not
854
- * asking the shape to decide anything.
855
- *
856
- * @param {unknown} body
857
- * @param {{ injectedRules?: object, selectSensitivePathClassesFn?: Function }} opts
858
- * @returns {ReturnType<typeof deriveStoryShape>|null}
859
- */
860
- function routeForReporting(body, opts) {
861
- if (typeof body !== 'string' || body.trim() === '') return null;
862
- return deriveStoryRouteFromBody(body, opts);
863
- }
864
-
865
- /**
866
- * Decide how `/deliver` executes a Story.
807
+ * Decide how `/deliver` executes a Story: **run topology, and nothing else.**
867
808
  *
868
809
  * **`inline` names one indivisible resource: the router's own session.** Two
869
810
  * Stories cannot both own it, so exactly one premise can grant it —
@@ -873,9 +814,7 @@ function routeForReporting(body, opts) {
873
814
  * branch refs) and a one-Story run has no sibling to race. It therefore pays
874
815
  * the spawn premium (a boot is a cache WRITE at full rate, where an inline
875
816
  * continuation is a cache read at ~10%; ~$1.43/M vs ~$1.07/M on comparable
876
- * bench work) for nothing. That is a fact about the run, not about the work, so
877
- * the shape gate's `enabled` switch — which governs *shape derivation* — does
878
- * not reach it.
817
+ * bench work) for nothing.
879
818
  *
880
819
  * **Shape cannot grant it (Story #4829).** The shape read used to return
881
820
  * `inline` for any lite-shaped body in a multi-Story run, inheriting no
@@ -883,16 +822,15 @@ function routeForReporting(body, opts) {
883
822
  * run came back `inline` for *every* Story while `stories-wave-tick.js`
884
823
  * reported the whole set ready under a concurrency cap of five — a router
885
824
  * following both signals literally runs several engines over one session and
886
- * one checkout, the precise hazard the sub-agent path exists to prevent. Both
887
- * runs were completed only by an operator overriding the verdict by hand, which
888
- * is an invariant held by judgment rather than by code. So this function has
889
- * exactly **one** `inline` exit, guarded by the topology premise; every path
890
- * below it returns `subagent`, and the derived shape is carried on `route` for
891
- * reporting only. A lite shape makes the work cheap — it does not conjure a
892
- * second session for a sibling to run in.
825
+ * one checkout, the precise hazard the sub-agent path exists to prevent.
893
826
  *
894
- * The label is read only to report hint consistency in `reasons`; it never
895
- * routes on either premise (Story #4722 AC-4/AC-5).
827
+ * **So this function reads only `storyCount` (Story #5006).** #4829 left the
828
+ * body parse, the shape derivation, the `route::lite` hint note and the
829
+ * `planning.complexityGate.enabled` branch in place to populate a `route`
830
+ * field for reporting — but the sole consumer, `resolve-stories.js`, reads
831
+ * `.mode` and discards the rest, so every one of those inputs was a parse
832
+ * whose result nothing could act on. A caller that wants the shape calls
833
+ * {@link deriveStoryShape} directly, as the light path and plan-persist do.
896
834
  *
897
835
  * Inline execution removes model-side fan-out only — it changes **where** the
898
836
  * engine runs, never **what** runs. Every deterministic
@@ -900,35 +838,13 @@ function routeForReporting(body, opts) {
900
838
  * `story-deliver-terminal` envelope are identical in both modes; see the
901
839
  * module header's non-negotiables.
902
840
  *
903
- * @param {{
904
- * body?: unknown,
905
- * labels?: unknown,
906
- * config?: object,
907
- * storyCount?: unknown,
908
- * injectedRules?: object,
909
- * selectSensitivePathClassesFn?: Function,
910
- * }} [args] `storyCount` is the number of Stories the invoking `/deliver` run
911
- * resolved. Omitted (or not a positive integer) means "unknown run size",
912
- * which cannot be shown sibling-free and therefore dispatches as a sub-agent
913
- * — never an assumed 1.
914
- * @returns {{ mode: 'inline'|'subagent', reasons: string[], route: ReturnType<typeof deriveStoryShape>|null }}
841
+ * @param {{ storyCount?: unknown }} [args] `storyCount` is the number of
842
+ * Stories the invoking `/deliver` run resolved. Omitted (or not exactly 1)
843
+ * means the run cannot be shown sibling-free and therefore dispatches as a
844
+ * sub-agent — never an assumed 1.
845
+ * @returns {{ mode: 'inline'|'subagent', reasons: string[] }}
915
846
  */
916
- export function resolveStoryDispatchMode({
917
- body,
918
- labels,
919
- config,
920
- storyCount,
921
- injectedRules,
922
- selectSensitivePathClassesFn,
923
- } = {}) {
924
- const labelList = Array.isArray(labels)
925
- ? labels.filter((l) => typeof l === 'string')
926
- : [];
927
- const hasHint = labelList.includes(LITE_ROUTE_LABEL);
928
- const hintNote = hasHint
929
- ? `the ${LITE_ROUTE_LABEL} label is present (hint only — the derived shape is the control signal)`
930
- : `the ${LITE_ROUTE_LABEL} label is absent (hint only — the derived shape is the control signal)`;
931
-
847
+ export function resolveStoryDispatchMode({ storyCount } = {}) {
932
848
  // The ONLY `inline` exit in this function, and the guard is the whole
933
849
  // contract: an inline verdict must mean the engine can actually run inline.
934
850
  if (storyCount === 1) {
@@ -936,48 +852,14 @@ export function resolveStoryDispatchMode({
936
852
  mode: 'inline',
937
853
  reasons: [
938
854
  'single-Story run — execute deliver-story inline; sub-agent isolation is load-bearing only for concurrent dispatch, and a one-Story run has no sibling to race (close gates, PR, and terminal envelope unchanged)',
939
- hintNote,
940
- ],
941
- route: routeForReporting(body, {
942
- injectedRules,
943
- selectSensitivePathClassesFn,
944
- }),
945
- };
946
- }
947
-
948
- const gate = resolveComplexityGate(config);
949
- if (!gate.enabled) {
950
- return {
951
- mode: 'subagent',
952
- reasons: [
953
- 'complexity routing disabled (planning.complexityGate.enabled=false) — standard sub-agent dispatch',
954
855
  ],
955
- route: null,
956
856
  };
957
857
  }
958
858
 
959
- if (typeof body !== 'string' || body.trim() === '') {
960
- return {
961
- mode: 'subagent',
962
- reasons: [
963
- 'no Story body to derive shape from — conservative sub-agent dispatch',
964
- hintNote,
965
- ],
966
- route: null,
967
- };
968
- }
969
-
970
- // Past the topology guard a sibling may be dispatched concurrently, so the
971
- // router's session is not available to anyone. The shape is still derived and
972
- // returned on `route` — ceremony and reporting read it — but it decides
973
- // nothing here: both shapes dispatch as a sub-agent.
974
- const route = deriveStoryRouteFromBody(body, {
975
- injectedRules,
976
- selectSensitivePathClassesFn,
977
- });
978
- const shapeNote =
979
- route.route === 'lite'
980
- ? `lite-shaped Story in a multi-Story run — the shape is inline-eligible but the router's session is not: a concurrent sibling would have to share it, racing worktrees and branch refs; sub-agent dispatch (${route.reasons[0]})`
981
- : `full-shaped Story — ${route.reasons[0]}`;
982
- return { mode: 'subagent', reasons: [shapeNote, hintNote], route };
859
+ return {
860
+ mode: 'subagent',
861
+ reasons: [
862
+ "multi-Story (or unknown-size) run — a concurrent sibling would have to share the router's session, racing worktrees and branch refs; sub-agent dispatch",
863
+ ],
864
+ };
983
865
  }
@@ -18,9 +18,9 @@
18
18
  *
19
19
  * So the axis is **changed lines over implementation files**, and the companion
20
20
  * classes the framework itself mandates are exempt from the count: obeying
21
- * `rules/testing-standards.md` (test-first), the `delivery.docsFreshness` gate,
22
- * and the baseline ratchets must not inflate the number that then rejects the
23
- * change. Re-counting those same 33 merges on implementation files alone moves
21
+ * `rules/testing-standards.md` (test-first), the documentation a change of
22
+ * consequence is expected to touch, and the baseline ratchets must not inflate
23
+ * the number that then rejects the change. Re-counting those same 33 merges on implementation files alone moves
24
24
  * the four-file pass rate from 21% to 58%.
25
25
  *
26
26
  * ## Three contracts worth not rediscovering
@@ -88,7 +88,7 @@ const COMPANION_PATH_GLOBS = Object.freeze([
88
88
  '**/*.test.tsx',
89
89
  'tests/**',
90
90
  'features/**',
91
- // Documentation — mandated by the delivery.docsFreshness gate.
91
+ // Documentation — the prose a change of consequence is expected to refresh.
92
92
  'docs/**',
93
93
  '**/*.md',
94
94
  // Generated baseline data — written by the ratchets, not hand-authored.
@@ -26,8 +26,9 @@ export function toExecuting(provider, ticketId, opts) {
26
26
  /**
27
27
  * Transition an array of tickets to `agent::done`, in order. Each call
28
28
  * triggers its own cascade (via `transitionTicketState`). Failures for
29
- * individual tickets propagate — callers that need per-ticket tolerance
30
- * should use `batchTransitionTickets` instead.
29
+ * individual tickets propagate — the loop aborts on the first error, so
30
+ * callers that need per-ticket tolerance must catch around each id
31
+ * themselves.
31
32
  *
32
33
  * @param {object} provider
33
34
  * @param {number[]} ticketIds
@@ -6,9 +6,9 @@
6
6
  * historically also the retired Epic-tier deliver/plan guards —
7
7
  * each carried their own copy of
8
8
  * the operator-handle resolution and the fail-closed acquire wrapper around
9
- * `ticket-lease.acquireLease` (anchor `heartbeatAt` to `now` so a foreign
10
- * assignee always reads as a live claim, then throw an operator-facing
11
- * refusal naming the current owner unless `--steal`). The three copies had
9
+ * `ticket-lease.acquireLease` (refuse any foreign assignee, throwing an
10
+ * operator-facing message naming the current owner unless `--steal`). The
11
+ * three copies had
12
12
  * already diverged — different `resolveOperator` signatures and different
13
13
  * missing-handle behaviour (`null` vs `throw`) — and were synchronised only
14
14
  * by docstring promise ("This mirrors the sibling lease guards…").
@@ -30,16 +30,15 @@
30
30
  * handle must degrade to a `no-operator` no-op rather than throw.
31
31
  * - **Refusal wording** — each surface renders its own operator-facing
32
32
  * message via `renderRefusal(result, ticketId)`.
33
- * - **Liveness anchoring** — the plan/standalone paths have no heartbeat
34
- * ledger, so they anchor `heartbeatAt` to `now` (fail-closed: every
35
- * foreign claim reads live). `/deliver` threads a real
36
- * `heartbeatAt` through from the lifecycle ledger, so it opts out of
37
- * anchoring.
33
+ *
34
+ * A third axis, **liveness anchoring**, is gone: Story #5006 deleted the
35
+ * lease TTL, so every foreign claim refuses unconditionally and there is no
36
+ * heartbeat to anchor.
38
37
  *
39
38
  * The unclaimed / already-held / foreign-claim decision table itself (and
40
39
  * the steal transfer) lives in `ticket-lease.acquireLease`; this kernel owns
41
- * the fail-closed parameterisation and the refuse-by-throw boundary that the
42
- * three guards previously each re-implemented.
40
+ * the refuse-by-throw boundary that the three guards previously each
41
+ * re-implemented.
43
42
  *
44
43
  * Per `.agents/rules/orchestration-error-handling.md`, failures surface via
45
44
  * `throw new Error(...)`, never `Logger.fatal`.
@@ -85,27 +84,16 @@ export function resolveOperatorFromCandidates({
85
84
 
86
85
  /**
87
86
  * Acquire a ticket lease, failing closed by throwing the surface's refusal
88
- * message when the claim is refused (live foreign owner, no `steal`).
87
+ * message when the claim is refused (foreign owner, no `steal`).
89
88
  *
90
- * When `anchorHeartbeatToNow` is set (the plan/standalone paths, which have
91
- * no heartbeat ledger), `heartbeatAt` and `now` are both anchored to the
92
- * same resolved clock value so `isClaimLive` returns true for ANY foreign
93
- * owner — `acquireLease` then refuses a foreign assignee unless `steal` is
94
- * set, while unclaimed and self-held tickets still proceed without a write.
95
- * When unset (`/deliver`), the caller-supplied `heartbeatAt` / `now`
96
- * pass through untouched.
89
+ * Unclaimed and self-held tickets proceed — the self-held case without a
90
+ * write. A foreign assignee always refuses unless `steal` is set.
97
91
  *
98
92
  * @param {object} opts
99
93
  * @param {object} opts.provider Ticketing provider.
100
94
  * @param {number} opts.ticketId Ticket to claim.
101
95
  * @param {string} opts.operator Resolved operator handle.
102
- * @param {number|null} [opts.heartbeatAt=null] Current owner's last
103
- * heartbeat (epoch ms). Ignored when `anchorHeartbeatToNow` is set.
104
96
  * @param {boolean} [opts.steal=false] Forcibly transfer a foreign claim.
105
- * @param {object} [opts.config] Resolved config (TTL default).
106
- * @param {number} [opts.now] Injectable clock (epoch ms; tests).
107
- * @param {boolean} [opts.anchorHeartbeatToNow=false] Fail-closed liveness
108
- * anchoring for surfaces with no heartbeat source.
109
97
  * @param {(result: object, ticketId: number) => string} opts.renderRefusal
110
98
  * Renders the operator-facing refusal message for a refused claim.
111
99
  * @returns {Promise<{ acquired: boolean, owner: string, previousOwner: string|null, reason: string }>}
@@ -115,28 +103,14 @@ export async function acquireLeaseFailClosed({
115
103
  provider,
116
104
  ticketId,
117
105
  operator,
118
- heartbeatAt = null,
119
106
  steal = false,
120
- config,
121
- now,
122
- anchorHeartbeatToNow = false,
123
107
  renderRefusal,
124
108
  }) {
125
- let resolvedHeartbeatAt = heartbeatAt;
126
- let resolvedNow = now;
127
- if (anchorHeartbeatToNow) {
128
- resolvedNow =
129
- typeof now === 'number' && Number.isFinite(now) ? now : Date.now();
130
- resolvedHeartbeatAt = resolvedNow;
131
- }
132
109
  const result = await acquireLease({
133
110
  provider,
134
111
  ticketId,
135
112
  operator,
136
- heartbeatAt: resolvedHeartbeatAt,
137
113
  steal,
138
- config,
139
- now: resolvedNow,
140
114
  });
141
115
  if (!result.acquired) {
142
116
  throw new Error(renderRefusal(result, ticketId));
@@ -3,12 +3,11 @@
3
3
  *
4
4
  * Programmatic helper that appends a single `merge.unlanded` NDJSON
5
5
  * record to the lifecycle ledger whenever a headless delivery run
6
- * finishes its work without a confirmed merge. Pattern mirrors
7
- * `emit-story-heartbeat.js`: direct schema validation via Ajv followed by
8
- * a synchronous `appendFileSync` this event is NOT routed through the
9
- * bus (unlike `emit-loop-tick.js`) because it fires from the standalone
10
- * `single-story-close` flow, which has no bus at all. A bare append keeps
11
- * the call site simple and dependency-free.
6
+ * finishes its work without a confirmed merge. Direct schema validation via
7
+ * Ajv followed by a synchronous `appendFileSync`: it fires from the standalone
8
+ * `single-story-close` flow, and a bare append keeps the call site simple and
9
+ * dependency-free. This was the shape that outlived the lifecycle bus — Story
10
+ * #5024 retired the bus, leaving this path the only lifecycle-ledger writer.
12
11
  *
13
12
  * Ledger destination (Story #4426 AC4): `storyLedgerPath(null, ticketId)`
14
13
  * — the standalone story-scope destination
@@ -6,9 +6,8 @@
6
6
  * the current 12-phase baseline is captured on disk. Each record is one
7
7
  * newline-terminated JSON line appended to
8
8
  * `temp/run-<id>/plan-metrics.json` (per-Epic plan CLIs) or
9
- * `temp/standalone/plan-metrics.json` (the standalone `story-plan.js` path
10
- * and Epic-less healthcheck runs — same standalone routing the friction
11
- * ledger uses).
9
+ * `temp/standalone/plan-metrics.json` (Epic-less plan and healthcheck runs —
10
+ * same standalone routing the friction ledger uses).
12
11
  *
13
12
  * Record shape (v1):
14
13
  *
@@ -20,6 +20,7 @@ import { gitSpawn } from '../../git-utils.js';
20
20
  import { validateTaskBodies } from '../task-body-validator.js';
21
21
  import { validateAndNormalizeTickets } from '../ticket-validator.js';
22
22
  import { DEFAULT_REGISTRY_PATTERNS } from '../ticket-validator-conflicts.js';
23
+ import { normalizeVerifyTiers } from '../verify-tier-repair.js';
23
24
 
24
25
  /**
25
26
  * Extensions an import specifier may elide. Probed longest-path-first when
@@ -240,6 +241,11 @@ export function validateTickets(tickets, config, opts = {}) {
240
241
  // process.cwd(), which is only the repo root by coincidence.
241
242
  cwd: opts.cwd,
242
243
  });
244
+ // Repair before judging (Story #5005): a `verify[]` entry whose tier the
245
+ // validator can already infer is auto-corrected rather than rejected, so a
246
+ // mechanically-fixable formality no longer costs a re-authoring round. An
247
+ // uninferable entry survives untouched and still hard-errors below.
248
+ normalizeVerifyTiers(validated);
243
249
  validateTaskBodies(validated);
244
250
  return validated;
245
251
  }
@@ -273,7 +273,6 @@ function bodyObjectFromTicket(ticket) {
273
273
  wide: ticket.wide ?? null,
274
274
  reason_to_exist: ticket.reason_to_exist ?? null,
275
275
  depends_on: ticket.depends_on ?? [],
276
- estimated_test_files: ticket.estimated_test_files ?? null,
277
276
  }).body;
278
277
  }
279
278