mandrel 2.31.0 → 2.33.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 (267) hide show
  1. package/.agents/README.md +13 -17
  2. package/.agents/agents/acceptance-critic.md +1 -2
  3. package/.agents/docs/SDLC.md +12 -9
  4. package/.agents/docs/agentrc-reference.json +63 -58
  5. package/.agents/docs/configuration.md +275 -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/git-conventions-reference.md +27 -27
  10. package/.agents/rules/git-conventions.md +4 -2
  11. package/.agents/rules/known-tooling-behavior.md +66 -30
  12. package/.agents/rules/testing-standards.md +35 -71
  13. package/.agents/runtime-deps.json +2 -2
  14. package/.agents/schemas/agentrc.schema.json +1955 -1410
  15. package/.agents/schemas/lifecycle/README.md +21 -14
  16. package/.agents/schemas/lifecycle/ledger-record.schema.json +76 -22
  17. package/.agents/schemas/story-deliver-terminal.schema.json +2 -2
  18. package/.agents/scripts/README.md +16 -29
  19. package/.agents/scripts/apply-quality-bootstrap.js +27 -34
  20. package/.agents/scripts/audit-to-stories.js +160 -41
  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 +159 -0
  27. package/.agents/scripts/check-lifecycle-lint.js +108 -124
  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 +81 -35
  38. package/.agents/scripts/lib/audit-to-stories/wire-dependencies.js +185 -0
  39. package/.agents/scripts/lib/baselines/envelope.js +41 -60
  40. package/.agents/scripts/lib/baselines/git-base.js +30 -37
  41. package/.agents/scripts/lib/baselines/kinds/_crap-new-method-gate.js +103 -0
  42. package/.agents/scripts/lib/baselines/kinds/_crap-read.js +150 -0
  43. package/.agents/scripts/lib/baselines/kinds/crap.js +25 -65
  44. package/.agents/scripts/lib/baselines/orphan-pruner.js +233 -0
  45. package/.agents/scripts/lib/baselines/refresh-service.js +6 -8
  46. package/.agents/scripts/lib/baselines/scope-assert.js +223 -0
  47. package/.agents/scripts/lib/baselines/scope-inventory.js +314 -0
  48. package/.agents/scripts/lib/bdd-step-index.js +326 -0
  49. package/.agents/scripts/lib/bootstrap/install-ledger.js +5 -3
  50. package/.agents/scripts/lib/bootstrap/issue-forms-template.js +4 -6
  51. package/.agents/scripts/lib/bootstrap/manifest.js +17 -40
  52. package/.agents/scripts/lib/bootstrap/project-bootstrap.js +12 -59
  53. package/.agents/scripts/lib/bootstrap/quality-bootstrap.js +62 -2
  54. package/.agents/scripts/lib/checks/loop-health.js +9 -37
  55. package/.agents/scripts/lib/child-exec.js +193 -0
  56. package/.agents/scripts/lib/cli/standard-args.js +1 -1
  57. package/.agents/scripts/lib/cli-args.js +64 -0
  58. package/.agents/scripts/lib/close-validation/gates.js +2 -2
  59. package/.agents/scripts/lib/close-validation/runner.js +3 -3
  60. package/.agents/scripts/lib/config/acceptance-eval.js +5 -52
  61. package/.agents/scripts/lib/config/commands.js +3 -5
  62. package/.agents/scripts/lib/config/explain.js +5 -7
  63. package/.agents/scripts/lib/config/gates/bundle-size.schema.js +32 -6
  64. package/.agents/scripts/lib/config/gates/coverage.schema.js +25 -5
  65. package/.agents/scripts/lib/config/gates/crap-incremental-coverage.schema.js +12 -2
  66. package/.agents/scripts/lib/config/gates/crap.schema.js +68 -23
  67. package/.agents/scripts/lib/config/gates/duplication.schema.js +29 -17
  68. package/.agents/scripts/lib/config/gates/index.js +5 -2
  69. package/.agents/scripts/lib/config/gates/lighthouse.schema.js +34 -6
  70. package/.agents/scripts/lib/config/gates/lint.schema.js +11 -2
  71. package/.agents/scripts/lib/config/gates/maintainability.schema.js +37 -15
  72. package/.agents/scripts/lib/config/gates/mutation.schema.js +15 -3
  73. package/.agents/scripts/lib/config/gates/shared.js +58 -9
  74. package/.agents/scripts/lib/config/github.js +0 -1
  75. package/.agents/scripts/lib/config/limits.js +3 -48
  76. package/.agents/scripts/lib/config/qa.js +105 -0
  77. package/.agents/scripts/lib/config/runners.js +38 -16
  78. package/.agents/scripts/lib/config/temp-paths.js +6 -5
  79. package/.agents/scripts/lib/config-settings-schema-delivery.js +245 -56
  80. package/.agents/scripts/lib/config-settings-schema-quality.js +209 -29
  81. package/.agents/scripts/lib/config-settings-schema.js +386 -39
  82. package/.agents/scripts/lib/crap-baseline-join.js +126 -9
  83. package/.agents/scripts/lib/crap-utils.js +84 -520
  84. package/.agents/scripts/lib/dead-exports-knip.js +79 -10
  85. package/.agents/scripts/lib/degraded-mode.js +2 -2
  86. package/.agents/scripts/lib/dependency-parser.js +20 -7
  87. package/.agents/scripts/lib/doc-tiers.js +3 -3
  88. package/.agents/scripts/lib/feedback-loop/graduator-core.js +46 -104
  89. package/.agents/scripts/lib/feedback-loop/retro-proposals-graduator.js +10 -8
  90. package/.agents/scripts/lib/findings/provenance-field.js +135 -0
  91. package/.agents/scripts/lib/findings/route-finding.js +57 -8
  92. package/.agents/scripts/lib/fs-walk.js +52 -0
  93. package/.agents/scripts/lib/git-branch-lifecycle.js +2 -2
  94. package/.agents/scripts/lib/git-utils.js +16 -36
  95. package/.agents/scripts/lib/knip-config-resolver.js +181 -0
  96. package/.agents/scripts/lib/knip-entry-sync.js +508 -0
  97. package/.agents/scripts/lib/observability/metrics-ledger.js +1 -1
  98. package/.agents/scripts/lib/observability/runtime-friction.js +10 -0
  99. package/.agents/scripts/lib/observability/signal-validator.js +5 -85
  100. package/.agents/scripts/lib/observability/signals-writer.js +19 -62
  101. package/.agents/scripts/lib/observability/source-classifier.js +5 -7
  102. package/.agents/scripts/lib/observability/terse-result.js +3 -3
  103. package/.agents/scripts/lib/orchestration/behind-recovery.js +114 -0
  104. package/.agents/scripts/lib/orchestration/ceremony-routing.js +7 -8
  105. package/.agents/scripts/lib/orchestration/ci-rerun-guard.js +34 -33
  106. package/.agents/scripts/lib/orchestration/code-review.js +2 -2
  107. package/.agents/scripts/lib/orchestration/complexity-gate.js +43 -161
  108. package/.agents/scripts/lib/orchestration/diff-magnitude.js +4 -4
  109. package/.agents/scripts/lib/orchestration/label-transitions.js +3 -2
  110. package/.agents/scripts/lib/orchestration/lease-guard-shared.js +12 -38
  111. package/.agents/scripts/lib/orchestration/lifecycle/emit-merge-unlanded.js +5 -6
  112. package/.agents/scripts/lib/orchestration/plan-metrics.js +2 -3
  113. package/.agents/scripts/lib/orchestration/plan-persist/persist-helpers.js +7 -26
  114. package/.agents/scripts/lib/orchestration/plan-persist/run-plan-persist.js +69 -5
  115. package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +69 -13
  116. package/.agents/scripts/lib/orchestration/plan-persist/summary.js +49 -0
  117. package/.agents/scripts/lib/orchestration/{lifecycle/listeners/watcher.js → pr-watch.js} +58 -208
  118. package/.agents/scripts/lib/orchestration/resolve-stories.js +77 -50
  119. package/.agents/scripts/lib/orchestration/review-providers/codex.js +1 -1
  120. package/.agents/scripts/lib/orchestration/review-providers/mi-exemptions.js +130 -0
  121. package/.agents/scripts/lib/orchestration/review-providers/native.js +30 -16
  122. package/.agents/scripts/lib/orchestration/single-story-close/phases/code-review.js +1 -1
  123. package/.agents/scripts/lib/orchestration/single-story-close/phases/confirm-merge.js +37 -26
  124. package/.agents/scripts/lib/orchestration/single-story-close/phases/conventional-subject.js +376 -0
  125. package/.agents/scripts/lib/orchestration/single-story-close/phases/normalize-pr-title.js +161 -151
  126. package/.agents/scripts/lib/orchestration/single-story-close/phases/options.js +15 -3
  127. package/.agents/scripts/lib/orchestration/single-story-close/phases/pull-request.js +10 -15
  128. package/.agents/scripts/lib/orchestration/single-story-close/phases/review-block.js +5 -0
  129. package/.agents/scripts/lib/orchestration/single-story-close/phases/review-override.js +157 -0
  130. package/.agents/scripts/lib/orchestration/single-story-close/phases/worktree-reap.js +0 -14
  131. package/.agents/scripts/lib/orchestration/single-story-close/runner.js +59 -25
  132. package/.agents/scripts/lib/orchestration/single-story-lease-guard.js +20 -31
  133. package/.agents/scripts/lib/orchestration/spec-spill.js +17 -3
  134. package/.agents/scripts/lib/orchestration/story-close/phases/review-core.js +7 -6
  135. package/.agents/scripts/lib/orchestration/story-deliver-terminal.js +2 -1
  136. package/.agents/scripts/lib/orchestration/task-body-validator.js +4 -1
  137. package/.agents/scripts/lib/orchestration/ticket-lease.js +28 -127
  138. package/.agents/scripts/lib/orchestration/ticket-validator-conflicts.js +116 -1
  139. package/.agents/scripts/lib/orchestration/ticket-validator-sizing.js +1 -1
  140. package/.agents/scripts/lib/orchestration/ticket-validator.js +38 -0
  141. package/.agents/scripts/lib/orchestration/ticketing/reads.js +5 -5
  142. package/.agents/scripts/lib/orchestration/ticketing/transition.js +5 -4
  143. package/.agents/scripts/lib/orchestration/verify-tier-repair.js +107 -0
  144. package/.agents/scripts/lib/qa/coverage-verdict.js +5 -87
  145. package/.agents/scripts/lib/signals/detectors/common.js +1 -1
  146. package/.agents/scripts/lib/signals/index.js +8 -6
  147. package/.agents/scripts/lib/signals/schema.js +20 -25
  148. package/.agents/scripts/lib/signals/write.js +8 -8
  149. package/.agents/scripts/lib/story-body/footer-block.js +97 -0
  150. package/.agents/scripts/lib/story-body/story-body.js +18 -81
  151. package/.agents/scripts/lib/temp-retention.js +1 -1
  152. package/.agents/scripts/lib/templates/decomposer-prompts.js +16 -14
  153. package/.agents/scripts/lib/ticket-body-sections.js +4 -5
  154. package/.agents/scripts/lib/wave-runner/footprint.js +306 -0
  155. package/.agents/scripts/lib/wave-runner/ready-set.js +198 -181
  156. package/.agents/scripts/lib/worktree/lifecycle/merge-reachability.js +13 -45
  157. package/.agents/scripts/lib/worktree/lifecycle/reap.js +4 -5
  158. package/.agents/scripts/lib/worktree-manager.js +2 -3
  159. package/.agents/scripts/lint-label-vocabulary.js +2 -24
  160. package/.agents/scripts/pr-watch-with-update.js +7 -5
  161. package/.agents/scripts/providers/github/blocked-by-add.js +25 -10
  162. package/.agents/scripts/providers/github/cache.js +2 -2
  163. package/.agents/scripts/providers/github/comments.js +6 -28
  164. package/.agents/scripts/providers/github/compose.js +0 -15
  165. package/.agents/scripts/providers/github/errors.js +10 -27
  166. package/.agents/scripts/providers/github/request-helpers.js +1 -2
  167. package/.agents/scripts/providers/github/sub-issues.js +10 -218
  168. package/.agents/scripts/providers/github.js +4 -7
  169. package/.agents/scripts/prune-baseline-orphans.js +181 -0
  170. package/.agents/scripts/resolve-stories.js +21 -7
  171. package/.agents/scripts/run-lint.js +61 -61
  172. package/.agents/scripts/run-test-profile.js +6 -6
  173. package/.agents/scripts/run-verify.js +48 -30
  174. package/.agents/scripts/single-story-close.js +20 -0
  175. package/.agents/scripts/single-story-init.js +12 -35
  176. package/.agents/scripts/stories-wave-tick.js +192 -9
  177. package/.agents/scripts/update-dead-exports-baseline.js +321 -0
  178. package/.agents/skills/core/gates-and-baselines/SKILL.md +2 -2
  179. package/.agents/skills/skills.index.json +1 -11
  180. package/.agents/workflows/audit-documentation.md +5 -6
  181. package/.agents/workflows/audit-to-stories.md +28 -2
  182. package/.agents/workflows/helpers/audit-lens-core.md +11 -12
  183. package/.agents/workflows/helpers/code-quality-guardrails.md +15 -14
  184. package/.agents/workflows/helpers/code-review.md +3 -8
  185. package/.agents/workflows/helpers/deliver-reference.md +30 -2
  186. package/.agents/workflows/helpers/deliver-story-reference.md +84 -16
  187. package/.agents/workflows/helpers/plan-reference.md +76 -0
  188. package/.agents/workflows/helpers/worktree-lifecycle.md +1 -2
  189. package/.agents/workflows/mandrel-update.md +10 -10
  190. package/.agents/workflows/qa-assist.md +15 -20
  191. package/.agents/workflows/qa-explore.md +9 -8
  192. package/README.md +1 -1
  193. package/docs/CHANGELOG.md +58 -0
  194. package/lib/migrations/index.js +2 -0
  195. package/lib/migrations/steps/2.32.0-retire-lint-baseline-command.js +127 -0
  196. package/package.json +14 -5
  197. package/.agents/schemas/lifecycle/checkpoint.written.schema.json +0 -13
  198. package/.agents/schemas/lifecycle/close-validate.end.schema.json +0 -18
  199. package/.agents/schemas/lifecycle/close-validate.start.schema.json +0 -13
  200. package/.agents/schemas/lifecycle/code-review.end.schema.json +0 -30
  201. package/.agents/schemas/lifecycle/code-review.start.schema.json +0 -12
  202. package/.agents/schemas/lifecycle/intervention.recorded.schema.json +0 -15
  203. package/.agents/schemas/lifecycle/loop.tick.schema.json +0 -20
  204. package/.agents/schemas/lifecycle/notification.emitted.schema.json +0 -18
  205. package/.agents/schemas/lifecycle/pr.created.schema.json +0 -14
  206. package/.agents/schemas/lifecycle/retro.end.schema.json +0 -16
  207. package/.agents/schemas/lifecycle/retro.start.schema.json +0 -12
  208. package/.agents/schemas/lifecycle/story.blocked.schema.json +0 -13
  209. package/.agents/schemas/lifecycle/story.dispatch.end.schema.json +0 -17
  210. package/.agents/schemas/lifecycle/story.dispatch.start.schema.json +0 -15
  211. package/.agents/schemas/lifecycle/story.merged.schema.json +0 -13
  212. package/.agents/scripts/check-gherkin-placeholders.js +0 -663
  213. package/.agents/scripts/check-lifecycle-doc-drift.js +0 -411
  214. package/.agents/scripts/lib/audit-suite/cli.js +0 -64
  215. package/.agents/scripts/lib/bootstrap/baselines-layout-migration.js +0 -202
  216. package/.agents/scripts/lib/bootstrap/ci-workflow-template.js +0 -212
  217. package/.agents/scripts/lib/checks/baseline-drift-main-checkout.js +0 -104
  218. package/.agents/scripts/lib/checks/push-hook-parity.js +0 -106
  219. package/.agents/scripts/lib/checks/windows-coverage-noise-floor.js +0 -92
  220. package/.agents/scripts/lib/checks/worktree-bootstrap-env.js +0 -81
  221. package/.agents/scripts/lib/checks/worktree-residue-biome.js +0 -55
  222. package/.agents/scripts/lib/crap-baseline-index.js +0 -46
  223. package/.agents/scripts/lib/crap-utils-incremental.js +0 -113
  224. package/.agents/scripts/lib/dynamic-workflow/capability.js +0 -396
  225. package/.agents/scripts/lib/feedback-loop/audit-results-graduator.js +0 -335
  226. package/.agents/scripts/lib/mutation/baseline-snapshot.js +0 -239
  227. package/.agents/scripts/lib/mutation/config-detector.js +0 -119
  228. package/.agents/scripts/lib/mutation/stryker-runner.js +0 -306
  229. package/.agents/scripts/lib/mutation/survivor-report.js +0 -160
  230. package/.agents/scripts/lib/observability/active-story-env.js +0 -170
  231. package/.agents/scripts/lib/observability/tool-trace-hook.js +0 -456
  232. package/.agents/scripts/lib/orchestration/acceptance-clusters.js +0 -111
  233. package/.agents/scripts/lib/orchestration/context-envelope.js +0 -277
  234. package/.agents/scripts/lib/orchestration/detectors-phase.js +0 -194
  235. package/.agents/scripts/lib/orchestration/lifecycle/bus.js +0 -309
  236. package/.agents/scripts/lib/orchestration/lifecycle/emit-loop-tick.js +0 -181
  237. package/.agents/scripts/lib/orchestration/lifecycle/ledger-writer.js +0 -229
  238. package/.agents/scripts/lib/orchestration/lifecycle/listeners/README.md +0 -54
  239. package/.agents/scripts/lib/orchestration/lifecycle/trace-logger.js +0 -344
  240. package/.agents/scripts/lib/orchestration/lint-baseline-service.js +0 -114
  241. package/.agents/scripts/lib/orchestration/pr-base-guard.js +0 -37
  242. package/.agents/scripts/lib/orchestration/resolves-token.js +0 -127
  243. package/.agents/scripts/lib/orchestration/spec-section-validator.js +0 -130
  244. package/.agents/scripts/lib/orchestration/story-close/emit-blocked.js +0 -55
  245. package/.agents/scripts/lib/orchestration/story-close/phases/code-review.js +0 -211
  246. package/.agents/scripts/lib/planning-corpus.js +0 -37
  247. package/.agents/scripts/lib/qa/coverage-report.js +0 -181
  248. package/.agents/scripts/lib/qa/propose-missing-test.js +0 -95
  249. package/.agents/scripts/lib/qa/qa-context-hydrator.js +0 -217
  250. package/.agents/scripts/lib/signals/detectors/index.js +0 -14
  251. package/.agents/scripts/lib/signals/detectors/retry.js +0 -253
  252. package/.agents/scripts/lib/signals/detectors/rework.js +0 -167
  253. package/.agents/scripts/lib/signals/read.js +0 -268
  254. package/.agents/scripts/lib/signals/span-tree.js +0 -291
  255. package/.agents/scripts/lib/story-lifecycle.js +0 -194
  256. package/.agents/scripts/lib/story-plan.js +0 -379
  257. package/.agents/scripts/lib/util/phase-timer-state.js +0 -72
  258. package/.agents/scripts/lib/util/phase-timer.js +0 -163
  259. package/.agents/scripts/lib/workers/combined-mi-crap-worker.js +0 -169
  260. package/.agents/scripts/lint-baseline.js +0 -507
  261. package/.agents/scripts/providers/github/prs.js +0 -103
  262. package/.agents/scripts/signals-view.js +0 -309
  263. package/.agents/scripts/story-plan.js +0 -370
  264. package/.agents/scripts/sync-branch-from-base.js +0 -149
  265. package/.agents/scripts/validate-docs-freshness.js +0 -314
  266. package/.agents/skills/core/diagnose-friction/SKILL.md +0 -78
  267. package/.agents/workflows/helpers/signals.md +0 -112
@@ -139,19 +139,18 @@ and the `rules/security-baseline.md` MUSTs all run exactly as for a
139
139
  full-ceremony Story. The lite route's `preserves` field is the machine-readable
140
140
  record of those non-negotiables; there is no lite-specific gate bypass.
141
141
 
142
- **Deliver derives the route from the Story body's shape and the
143
- dispatch mode from the run.** Persist stamps a lite cohort's Stories with the
144
- `route::lite` label as a _human-visible hint only_ (and ledgers the authored
145
- verdict — recorded reason plus per-Story shape evidence — on the
146
- `story-plan-state` checkpoint); the label is never the control signal.
147
- `/deliver` computes the route from the fetched Story body via
148
- `resolveStoryDispatchMode` (`lib/orchestration/complexity-gate.js`) the same
149
- shape taxonomy `deriveChangeLevel` applies to the landed diff at close:
150
- `changes[]` count, acceptance count, creates-vs-refactors mix, sensitive-path
151
- classes. A footprint intersecting a sensitive-path class derives `full` —
152
- sensitivity wins, and the Story keeps its fresh acceptance critic.
153
-
154
- That derived route sets ceremony. It does **not** set the dispatch mode,
142
+ **Ceremony comes from the landed diff; the dispatch mode comes from the
143
+ run.** Persist stamps a lite cohort's Stories with the `route::lite` label as a
144
+ _human-visible hint only_ (and ledgers the authored verdict — recorded reason
145
+ plus per-Story shape evidence — on the `story-plan-state` checkpoint); the
146
+ label is never the control signal. Ceremony is resolved from the **derived
147
+ change level** (`deriveChangeLevel` over the computed change set digest § 3),
148
+ not from a body-shape read: a footprint intersecting a sensitive-path class
149
+ derives `high`, so the Story keeps its fresh acceptance critic. The light path
150
+ is the one caller that reads the authored body's shape, through
151
+ `deriveStoryShape` (`lib/orchestration/complexity-gate.js`).
152
+
153
+ That derived level sets ceremony. It does **not** set the dispatch mode,
155
154
  because `inline` names one indivisible resource — the router's own session —
156
155
  and only run topology can say whether it is free: a **single-Story run**
157
156
  executes inline, and every Story of a multi-Story run dispatches as a
@@ -162,6 +161,63 @@ terminal envelope are byte-identical either way.
162
161
 
163
162
  ---
164
163
 
164
+ ## Declared dependency edges — what actually gates dispatch
165
+
166
+ `resolve-stories.js` builds each `dag[].dependsOn` from the **union of two
167
+ declared-edge channels**, and nothing else: the Story body's footer block and
168
+ the issue's native GitHub `blocked_by` relations. Both are read strictly — an
169
+ edge the resolver cannot read is never quietly reported as an edge that does
170
+ not exist.
171
+
172
+ **The body channel is footer-scoped.** Only a `blocked by #N` line standing
173
+ alone inside the `---` footer block declares an edge:
174
+
175
+ ```markdown
176
+ ## Goal
177
+
178
+
179
+
180
+ ---
181
+
182
+ blocked by #42
183
+ ```
184
+
185
+ Prose elsewhere in the body declares **nothing**, and this is a deliberate,
186
+ user-visible change from the whole-body scan that preceded it. A sentence
187
+ merely mentioning a blocker — an example, a changelog note, an acceptance
188
+ criterion quoting the phrase — used to mint a real dispatch gate that withheld
189
+ the Story until an unrelated issue closed. `plan-persist` has always
190
+ serialized the canonical footer form, so no machine-authored body is affected;
191
+ only a **hand-written prose edge** stops gating, and the fix is to move it into
192
+ the footer block. The loose spellings never reached the footer grammar either:
193
+ `depends on #N`, `Blocked by: #N`, and `blocked by #N once X lands` all declare
194
+ nothing. One grammar serves both readers — the body parser and the
195
+ dispatch-edge parser share it — so what a Story body round-trips and what gates
196
+ dispatch cannot drift apart.
197
+
198
+ **The native channel fails loud.** The read paginates to exhaustion (a
199
+ first-page read silently truncated a Story's gates at GitHub's 30-item
200
+ default), and **a 404 is not an empty result**. An issue with no dependencies
201
+ answers `200 []`; a 404 is how GitHub also answers a token that cannot see the
202
+ dependencies API, so treating it as "no edges" erased every native edge in the
203
+ run under a mis-scoped token, silently, with a clean exit code. Any non-OK
204
+ read now fails the resolution naming the Story — check the token's scopes
205
+ first. The one degrade that is scoped rather than fatal is a **cross-repo
206
+ edge**: another repository's issue number cannot be matched against this
207
+ repo's same-numbered issue without risking a false match, so that edge is
208
+ dropped with a warning naming the Story, and its siblings resolve normally.
209
+
210
+ **Edges are monotone — retraction is not built.** Both channels only ever
211
+ _add_ a gate for the current resolution. Removing a `blocked by` footer line
212
+ or deleting a native relation makes the edge absent from the **next** resolve,
213
+ but nothing reconciles an edge that a previous run already acted on, and the
214
+ write path never deletes a native relation it did not need. In practice that
215
+ means: re-resolve after editing edges, and treat a stale gate as a body/issue
216
+ edit plus a fresh `resolve-stories.js` run, never as something delivery
217
+ un-declares on your behalf. This is a known limitation, not an oversight.
218
+
219
+ ---
220
+
165
221
  ## Step 1 — Implementation detail
166
222
 
167
223
  **Docs context — digest-first.** Read a full doc only when the Story's own
@@ -200,8 +256,8 @@ critic (the redundant pre-pass buys no measurable quality and roughly
200
256
  triples the acceptance-block cost). `acceptance-eval.js` is the
201
257
  deterministic **scorer** of that one authored verdict — schema validation,
202
258
  round cap, proceed / redraft / block — not an independent additional pass
203
- over the criteria. The M4-B floor holds: one verdict per cluster, the
204
- cluster count owned by `acceptance-clusters.js` alone.
259
+ over the criteria. The M4-B floor holds: one verdict per cluster, with the
260
+ cluster count owned by the dispatching caller and never by routing.
205
261
 
206
262
  **One round = N cluster critics → ONE merged verdict → ONE gate call.** The
207
263
  clusters are how a round is _authored_; they are not how it is _scored_.
@@ -279,7 +335,9 @@ floor forces `fresh`). Review depth reads the same derived level via
279
335
  `review-depth.js` inside close, so the two decisions cannot disagree.
280
336
 
281
337
  **Inline-dispatch override.** When the Story dispatches
282
- `inline` (`resolveStoryDispatchMode` → `inline`, i.e. a single-Story run), run
338
+ `inline` (`resolveStoryDispatchMode` → `inline`, which is exactly a
339
+ single-Story run — the function reads the resolved set size and nothing
340
+ else), run
283
341
  every acceptance critic **inline** — do not spawn fresh-context critic
284
342
  sub-agents regardless of what the profile would otherwise resolve. The self-eval rigor
285
343
  (scoring each `acceptance[]` item against the one computed change set, with
@@ -393,6 +451,16 @@ judgment that help text cannot carry.
393
451
  wants the PR left at `agent::closing` for a human land (or a wrapper that
394
452
  will invoke `single-story-confirm-merge.js` itself). Reports `pending` —
395
453
  the work is not done, nothing is broken, and one named command finishes it.
454
+ - `--override-review-block "<reason>"` — when the Story-scope review's
455
+ **critical** blocker is one you have read and judged wrong (a false positive,
456
+ or a finding the ratchet correctly exempts). It is the only sanctioned way
457
+ past that halt: reach for it instead of merging the PR by hand, because a
458
+ hand-merge bypasses the gate and records nothing. The reason is mandatory and
459
+ is written to three places (Story comment, PR comment, a
460
+ `review-block-overridden` friction signal), and the terminal envelope reports
461
+ `gates.codeReview: "overridden"` rather than `"passed"`. If you find yourself
462
+ reaching for it twice for the same shape of finding, the gate is
463
+ miscalibrated — fix the gate, not the run.
396
464
  - `--max-wait-seconds <n>` — from a headless caller with no host
397
465
  tool-invocation ceiling, to keep single-block semantics
398
466
  without editing the consumer's config.
@@ -243,6 +243,82 @@ as a hard error — so the grounding contract is the author's own targeted reads
243
243
  plus that gate. There is no pre-computed codebase snapshot to fall back on,
244
244
  and no manifest-derived replacement to build.
245
245
 
246
+ ### Per-Story audit provenance (`provenance`)
247
+
248
+ An audit-seeded plan carries dedup identities forward so the next sweep
249
+ recognises what it already planned. The optional top-level `provenance` field
250
+ says **which of them this Story owns**:
251
+
252
+ ```jsonc
253
+ {
254
+ "slug": "own-the-seam",
255
+ "provenance": {
256
+ "fingerprints": ["<40-char sha1, one per finding this Story tracks>"],
257
+ "semanticKeys": ["architecture␟lib/owned.js"]
258
+ }
259
+ }
260
+ ```
261
+
262
+ Both arrays are optional; a malformed entry is a validator rejection, never a
263
+ silent drop — a dropped identity is invisible until the next sweep re-files
264
+ work this plan already tracked.
265
+
266
+ | `provenance` | What persist stamps |
267
+ | --- | --- |
268
+ | Present | **Exactly** the identities listed — siblings' groups never leak in. |
269
+ | Present but empty (`{}`) | Nothing. "Owns no findings" is a real answer. |
270
+ | **Absent** | The **whole seed's** footers (the union) — the recall-safe default. |
271
+
272
+ **The union fallback is load-bearing, not legacy.** Leaving the authoring agent
273
+ to hand-carry provenance out of the seed's HTML comments was measured to fail —
274
+ a remembered step is no step at all — and the mechanical union carry is what
275
+ closed it. Attribution is additive: it sharpens a plan that opts in and changes
276
+ nothing for one that does not. Never remove the fallback to "finish the
277
+ migration".
278
+
279
+ Attribution is what makes the next sweep's dedup answerable rather than
280
+ arbitrary. Under the union every sibling carried every key, so a finding
281
+ confirming against several open Stories could only pick one at random, and a
282
+ key whose owning Story had since **closed** was masked by any open neighbour —
283
+ a genuine regression filed as a routine update. With ownership stamped, the
284
+ issue carrying a finding's own fingerprint decides both the match and its
285
+ state (`lib/findings/route-finding.js`).
286
+
287
+ The audit path authors this mechanically from the per-group footers the seed
288
+ already carries — see [`audit-to-stories`](../audit-to-stories.md). A `--seed`
289
+ or `--tickets` plan has nothing to attribute and omits the field.
290
+
291
+ ## Cross-Story conflict analysis at persist
292
+
293
+ The conflict passes run **twice**: once over the raw `stories.json` payload
294
+ (alongside the freshness, file-assumption and sizing gates), and again over the
295
+ **assembled, footer-stamped bodies** — the artifact persist actually posts.
296
+ The second pass is not belt-and-braces. The canonical authoring shape carries
297
+ `acceptance[]` / `verify[]` at the ticket's top level and assembly folds them
298
+ into the body, so the passes that scan `body.acceptance` / `body.verify`
299
+ (`implicit-cross-story-dep`, `missing-bdd-scaffold`) saw two empty arrays on
300
+ the real payload and emitted nothing. Both passes complete before the first
301
+ `createIssue`, so a refusal still costs no writes.
302
+
303
+ `shared-editor` findings are rendered into the posted `plan-summary` comment,
304
+ directly beneath the wave table: the table promises which Stories can run
305
+ together, and a path two same-wave Stories both write is exactly where that
306
+ promise breaks. Promise and caveat belong on one durable surface — previously
307
+ the caveat was a stderr warning nobody kept.
308
+
309
+ Two `planning.*` knobs upgrade a conflict class from advisory to a hard
310
+ refusal. **Both default to `false` and are documented, not recommended:**
311
+
312
+ | Knob | Upgrades | Why it is off |
313
+ | --- | --- | --- |
314
+ | `planning.failOnSharedEditors` | `shared-editor` → `hard` | Co-editing one file is routine and often correct; the delivery scheduler already serializes file-overlapping Stories. |
315
+ | `planning.requireExplicitCrossStoryDeps` | `implicit-cross-story-dep` → `hard` | Path references are matched by substring, so a legitimate mention in prose can read as a dependency. |
316
+
317
+ Turn one on for a repo where the class is genuinely fatal; expect a refusal to
318
+ name the Stories and the fix (a `depends_on` edge, or folding the shared edit
319
+ into one Story). The sibling knobs `failOnRegistryConflicts`,
320
+ `failOnMissingBddScaffold` and `failOnLargeFanOut` behave the same way.
321
+
246
322
  ## Tickets mode — authoring `supersedes[]`
247
323
 
248
324
  In `--tickets` mode each Story carries a top-level `supersedes` array claiming
@@ -116,8 +116,7 @@ PowerShell `Get-CimInstance Win32_Process`, terminating them with
116
116
  - The close output reports `pending-cleanup persistent-lock: story-N, ...`.
117
117
  - `git worktree list` shows `.worktrees/story-N/` for a closed Story.
118
118
  - `npm run lint` fails because of a nested `biome.json` in a half-reaped
119
- worktree. The `worktree-residue-biome` self-healing check detects this
120
- failure mode.
119
+ worktree.
121
120
 
122
121
  ### Manual usage
123
122
 
@@ -150,22 +150,22 @@ in Step 5). Full procedure:
150
150
  node .agents/scripts/apply-quality-bootstrap.js
151
151
  ```
152
152
 
153
- Runs the same idempotent installs the quality-gates phase of
154
- [`bootstrap.js`](../scripts/bootstrap.js) uses — `applyQualityBootstrap`
155
- then `migrateBaselinesLayout` — and prints a `{ quality, baselines }` JSON
156
- envelope. The four quality-bootstrap outcomes: **helper** (materialize
153
+ Runs the same idempotent install the quality-gates phase of
154
+ [`bootstrap.js`](../scripts/bootstrap.js) uses — `applyQualityBootstrap`
155
+ and prints a `{ quality }` JSON envelope. The five quality-bootstrap
156
+ outcomes: **helper** (materialize
157
157
  [`code-quality-guardrails.md`](helpers/code-quality-guardrails.md)),
158
158
  **hook** (install the `.husky/pre-commit` diff-scoped `quality:preview`
159
159
  invocation — a pre-existing **custom hook is never overwritten silently**;
160
160
  the action is `custom-hook-skip` and the helper returns the snippet to
161
161
  append by hand), **scripts** (backfill `quality:preview` /
162
162
  `quality:watch` only when absent), **config** (seed missing
163
- `delivery.quality.*` defaults — operator overrides survive). The baselines
164
- step migrates legacy per-Epic snapshot layouts into the ephemeral
165
- `temp/epic/<id>/baselines/` namespace when upgrading from pre-v2 shapes; the
166
- main-tracked root baselines are never touched. A second run reports
167
- `no-change` on every path — the idempotence contract this workflow
168
- requires.
163
+ `delivery.quality.*` defaults — operator overrides survive), and
164
+ **legacyBaselines** (`git rm` a committed pre-v2 `baselines/epic/` tree
165
+ the Story-only v2 model retired every reader of those per-Epic snapshots;
166
+ the main-tracked root baselines are never touched). A second run reports
167
+ `no-change` / `already-present` / `absent` on every path — the idempotence
168
+ contract this workflow requires.
169
169
 
170
170
  ## Step 3.6 — Refresh the harness permission allowlist
171
171
 
@@ -172,27 +172,21 @@ every decision to the shared helpers; never re-derive them in prose.
172
172
  surfaces it touches, the **options**, and a brief **recommendation** with
173
173
  trade-offs. Still pin the relevant `file:line` anchor(s) where the change
174
174
  would land.
175
- 3. **Hydrate the QA context** to locate code precisely, via
176
- [`qa-context-hydrator.js`](../scripts/lib/qa/qa-context-hydrator.js) it
177
- resolves the source ticket body, the feature-file set, the surface map, and
178
- recent git log:
179
-
180
- ```js
181
- import { hydrateQaContext } from '../scripts/lib/qa/qa-context-hydrator.js';
182
- const context = await hydrateQaContext({ ticketNumber, githubPort, gitPort, surfaceMap });
183
- ```
184
-
175
+ 3. **Locate the code.** Read the source ticket with
176
+ `gh issue view <ticketNumber> --json title,body,labels`, and verify each
177
+ surface-map path resolves with `git cat-file -e HEAD:<path>` flag every
178
+ miss rather than citing a path that does not exist.
185
179
  4. **Compute the coverage verdict** for the surface the observation points at,
186
180
  via [`coverage-verdict.js`](../scripts/lib/qa/coverage-verdict.js) — the
187
181
  deterministic seam behind the
188
182
  [`core/qa-coverage-mapping`](../skills/core/qa-coverage-mapping/SKILL.md)
189
183
  skill. Read that skill for how to assemble the `surface` input and read the
190
- per-tier `{present|absent}` verdict. Optionally render a human-readable
191
- summary via [`coverage-report.js`](../scripts/lib/qa/coverage-report.js).
192
- 5. **Propose the missing test** (if any) from that verdict, via
193
- [`propose-missing-test.js`](../scripts/lib/qa/propose-missing-test.js) it
194
- names the lowest absent tier, or returns `null` when every tier is covered.
195
- Record its `description` as the ledger item's `missingTest`.
184
+ per-tier `{present|absent}` verdict.
185
+ 5. **Name the missing test** (if any) from that verdict: take the lowest tier
186
+ the verdict marks `absent` (unit → contract → acceptance) and write one
187
+ concrete sentence describing the test that would close it. Every tier
188
+ `present` means no missing test. Record that sentence as the ledger item's
189
+ `missingTest`.
196
190
  6. **Classify** the finding via
197
191
  [`classify-finding.js`](../scripts/lib/findings/classify-finding.js) so the
198
192
  tentative `class` resolves to the correct focus/meta label set. The helper
@@ -273,10 +267,11 @@ the `/qa-assist`-specific deltas are:
273
267
  - **Persistent, resumable rolling session** — `/qa-assist` defaults to resuming
274
268
  the same session and appending; a reused session carries the untriaged backlog
275
269
  forward and never overwrites a prior ledger.
276
- - **Enrichment helpers are deterministic** — context hydration
277
- ([`qa-context-hydrator.js`](../scripts/lib/qa/qa-context-hydrator.js)),
278
- coverage verdict/report, missing-test, and classification are never re-derived
279
- in prose.
270
+ - **Enrichment delegates where a helper exists** — the coverage verdict and
271
+ the finding classification come from their deterministic helpers, never from
272
+ prose. Context lookup and the missing-test sentence are the model's own work:
273
+ they are judgments, not computations, and routing them through a module only
274
+ bought a round-trip.
280
275
 
281
276
  ## See also
282
277
 
@@ -176,10 +176,11 @@ For each observation the agent makes while driving:
176
176
  [`core/qa-coverage-mapping`](../skills/core/qa-coverage-mapping/SKILL.md)
177
177
  skill. Read that skill for how to assemble the `surface` input and read the
178
178
  per-tier `{present|absent}` verdict.
179
- 3. **Propose the missing test** (if any) from that verdict, via
180
- [`propose-missing-test.js`](../scripts/lib/qa/propose-missing-test.js) it
181
- names the lowest absent tier, or returns `null` when every tier is covered.
182
- Record its `description` as the ledger item's `missingTest` (or `null`).
179
+ 3. **Name the missing test** (if any) from that verdict: take the lowest tier
180
+ the verdict marks `absent` (unit → contract → acceptance) and write one
181
+ concrete sentence describing the test that would close it. Every tier
182
+ `present` means no missing test. Record that sentence as the ledger item's
183
+ `missingTest` (or `null`).
183
184
  4. **Append a `QaLedgerItem`** to the ledger (shape per
184
185
  [`helpers/qa-core.md`](helpers/qa-core.md)): a stable `id`, the redacted
185
186
  `evidence`, the `coverage` label (the `surface`, or `unknown`), a tentative
@@ -230,10 +231,10 @@ the `/qa-explore`-specific deltas are:
230
231
  and fall back to static.
231
232
  - **Broken navigation is a finding, not a workaround** — never URL-jump around a
232
233
  missing affordance, a nav 404, or a guard redirect loop.
233
- - **Delegate coverage decisions to the helpers.** Coverage verdict
234
- ([`coverage-verdict.js`](../scripts/lib/qa/coverage-verdict.js)) and
235
- missing-test ([`propose-missing-test.js`](../scripts/lib/qa/propose-missing-test.js))
236
- are deterministic never re-derive them in prose.
234
+ - **Delegate the coverage verdict to the helper.** Tier placement comes from
235
+ [`coverage-verdict.js`](../scripts/lib/qa/coverage-verdict.js) — deterministic,
236
+ never re-derived in prose. The missing-test sentence is yours to write from
237
+ that verdict's lowest `absent` tier.
237
238
 
238
239
  ## See also
239
240
 
package/README.md CHANGED
@@ -87,7 +87,7 @@ time to confirm the install is healthy.
87
87
  > Prefer a surgical alternative? Replace `shamefully-hoist` with a scoped
88
88
  > `public-hoist-pattern[]=` line per package listed in
89
89
  > `.agents/runtime-deps.json` (`ajv`, `ajv-formats`, `js-yaml`, `minimatch`,
90
- > `picomatch`, `string-argv`, `typhonjs-escomplex`). If `mandrel doctor`
90
+ > `picomatch`, `typhonjs-escomplex`). If `mandrel doctor`
91
91
  > reports `runtime-deps missing: …`, this is the fix.
92
92
 
93
93
  `bootstrap.js` is interactive on a TTY and auto-accepts the
package/docs/CHANGELOG.md CHANGED
@@ -15,6 +15,64 @@ All notable changes to this project will be documented in this file.
15
15
  -->
16
16
  <!-- markdownlint-disable-file MD004 MD012 MD037 -->
17
17
 
18
+ ## [2.33.0](https://github.com/dsj1984/mandrel/compare/mandrel-v2.32.0...mandrel-v2.33.0) (2026-08-07)
19
+
20
+
21
+ ### Added
22
+
23
+ * deliver: widen footprints from edit intent only, explain every withhold, and give standalone audit cohorts declared edges ([#5044](https://github.com/dsj1984/mandrel/issues/5044)) ([#5049](https://github.com/dsj1984/mandrel/issues/5049)) ([6bd0653](https://github.com/dsj1984/mandrel/commit/6bd0653e88e07ddbcc90c3c947b5f135374649d4))
24
+ * plan: stamp only the provenance a Story owns, and run cross-Story conflict analysis over the bodies persist actually writes ([#5045](https://github.com/dsj1984/mandrel/issues/5045)) ([#5048](https://github.com/dsj1984/mandrel/issues/5048)) ([804b308](https://github.com/dsj1984/mandrel/commit/804b3087c001f21815aecb109bb636e59a87b2cb))
25
+
26
+
27
+ ### Fixed
28
+
29
+ * check-knip-entries: resolve the entry list through knip's own config resolver, and skip cleanly when there is no knip config ([#5039](https://github.com/dsj1984/mandrel/issues/5039)) ([#5041](https://github.com/dsj1984/mandrel/issues/5041)) ([7f69a18](https://github.com/dsj1984/mandrel/commit/7f69a18ead79da77779981bec3eed8772dc05643))
30
+ * deliver: make the declared-edge channel trustworthy — paginate native reads, fail loud on 404, parse footers strictly ([#5046](https://github.com/dsj1984/mandrel/issues/5046)) ([#5047](https://github.com/dsj1984/mandrel/issues/5047)) ([38b543d](https://github.com/dsj1984/mandrel/commit/38b543dec2634318b2407b54404239e7ae34d381))
31
+ * **lint:** add a --root scan seam so the lint test stops mutating the shared tree (refs [#5052](https://github.com/dsj1984/mandrel/issues/5052)) ([#5053](https://github.com/dsj1984/mandrel/issues/5053)) ([69d1952](https://github.com/dsj1984/mandrel/commit/69d1952fb7410659ca21a9bb0d34eefadb654d2e))
32
+ * plan: resolve the cross-Story conflict policy once for the raw and assembled passes ([#5050](https://github.com/dsj1984/mandrel/issues/5050)) ([711199a](https://github.com/dsj1984/mandrel/commit/711199a861bc1e85218a09fe4674c507b0c0edb1))
33
+
34
+ ## [2.32.0](https://github.com/dsj1984/mandrel/compare/mandrel-v2.31.0...mandrel-v2.32.0) (2026-08-06)
35
+
36
+
37
+ ### ⚠ BREAKING CHANGES
38
+
39
+ * `.agents/scripts/validate-docs-freshness.js` is removed from the published payload. It had no invoker in `package.json` scripts, Husky hooks, CI workflows, or any workflow markdown, so no shipped flow loses a step. A consumer calling it by hand should drop the call; the `delivery.docsFreshness.paths` key it read is unchanged and is still consumed by the audit-documentation lens.
40
+ * `project.commands.lintBaseline` is removed from the `.agentrc.json` schema. The `project.commands` block is `additionalProperties: false`, so a config still carrying the key now fails validation instead of being ignored. `npx mandrel update` deletes it for you; delete it by hand otherwise. Nothing replaces it — the framework no longer ships a lint-baseline capture CLI, and a consumer that wants the `lint` baseline kind writes `baselines/lint.json` from its own linter.
41
+ * `ITicketingProvider` no longer declares `getRecentComments`, `addSubIssue`, `removeSubIssue` or `createPullRequest`, and `GitHubProvider` no longer installs them. A consumer implementing or calling those members must drop them: open pull requests with `gh pr create` (as `single-story-close.js` does) and read a ticket's comments through `getTicketComments`.
42
+ * `apply-quality-bootstrap.js` no longer prints a `baselines` key. Its stdout envelope is now `{ quality }`, and the retired baselines migration is reported as `quality.legacyBaselines` instead. A consumer parsing `.baselines.action` from `/mandrel-update` Step 3.5 must read `.quality.legacyBaselines.action` (`absent` | `pruned`); the pre-v2 snapshot relocation into `temp/epic/<id>/baselines/` no longer happens at all.
43
+ * the delivery.lease config block (delivery.lease.ttlMs) is removed from .agentrc.json. The schema now rejects it as an unknown key, so a consumer carrying it must delete the block. Nothing replaces it: the lease has no TTL, and a stranded claim is cleared with --steal.
44
+ * `delivery.acceptanceEval.clusterCeiling` is removed from the agentrc schema. The block is `additionalProperties: false`, so a consumer `.agentrc.json` still carrying the key now fails AJV validation — delete the key; nothing read it.
45
+
46
+ ### Added
47
+
48
+ * baseline honesty surface: framework-owned scope/staleness gate + measurement-free orphan pruner ([#5012](https://github.com/dsj1984/mandrel/issues/5012)) ([#5016](https://github.com/dsj1984/mandrel/issues/5016)) ([71f7134](https://github.com/dsj1984/mandrel/commit/71f713419bba8523ab6bbca60fb962d5919f18b1))
49
+ * **baselines:** baseline-refresh: schedule the full-scope re-score ([#5028](https://github.com/dsj1984/mandrel/issues/5028)) ([eee9005](https://github.com/dsj1984/mandrel/commit/eee9005933a572f69ac3faa94bfc1d7193dfd7d4))
50
+ * **gates:** make the dead-exports ratchet detect whole-file death (refs [#5001](https://github.com/dsj1984/mandrel/issues/5001)) ([#5014](https://github.com/dsj1984/mandrel/issues/5014)) ([38dfc3c](https://github.com/dsj1984/mandrel/commit/38dfc3c690c14c80b0fd2dc97dd7cbd599a19322))
51
+ * one child-process exec wrapper: single maxBuffer SSOT, normalized errors, lint-banned raw spawns ([#5009](https://github.com/dsj1984/mandrel/issues/5009)) ([#5029](https://github.com/dsj1984/mandrel/issues/5029)) ([956dcfc](https://github.com/dsj1984/mandrel/commit/956dcfc777fe74b2c35e208ee1a44ee7a47a3fd1))
52
+ * planning surface diet: delete the parallel authoring pipeline and stop rejecting output the validator can already fix ([#5005](https://github.com/dsj1984/mandrel/issues/5005)) ([#5020](https://github.com/dsj1984/mandrel/issues/5020)) ([a62efd5](https://github.com/dsj1984/mandrel/commit/a62efd5d27f9d82761f150a5edd0388eb0370493))
53
+ * **quality:** derive knip's CLI entry list instead of trusting memory ([#5026](https://github.com/dsj1984/mandrel/issues/5026)) ([7554851](https://github.com/dsj1984/mandrel/commit/7554851979de86622baf58f2467b5198d3202ed9))
54
+ * ship a fail-closed dead-exports baseline producer so the ratchet has an updater, not a hand-edit ([#5011](https://github.com/dsj1984/mandrel/issues/5011)) ([#5033](https://github.com/dsj1984/mandrel/issues/5033)) ([eede007](https://github.com/dsj1984/mandrel/commit/eede007c502fab485de480c2517516f8e015491d))
55
+ * ship a static gherkin corpus gate: must-compile with the real parser, must-bind scoped per step root ([#5013](https://github.com/dsj1984/mandrel/issues/5013)) ([#5034](https://github.com/dsj1984/mandrel/issues/5034)) ([9f4e83e](https://github.com/dsj1984/mandrel/commit/9f4e83ede2c7a55a6dd94e2d8b162e54fb4de217))
56
+
57
+
58
+ ### Fixed
59
+
60
+ * close six defects found reviewing the v2.32.0 release diff ([#5035](https://github.com/dsj1984/mandrel/issues/5035)) ([625004c](https://github.com/dsj1984/mandrel/commit/625004ccc65a65508ef99b239ddd09fafb13c56f))
61
+ * **close:** review lens must honour maintainability ignoreGlobs; add a logged override ([#5030](https://github.com/dsj1984/mandrel/issues/5030)) ([e21ff82](https://github.com/dsj1984/mandrel/commit/e21ff82b8ee80c80a9d3ff7803247c992f0a3abc))
62
+ * derive the squash subject from release impact, keep acronyms, propagate breaking changes ([#5027](https://github.com/dsj1984/mandrel/issues/5027)) ([a7bb9af](https://github.com/dsj1984/mandrel/commit/a7bb9af0353853c60ac6a2b59ea04b7c66695dbc))
63
+
64
+
65
+ ### Changed
66
+
67
+ * cRAP surface diet: delete the dead combined scan, unify the baseline read path, stop the c≈5 cap on untestable CLI wiring ([#5002](https://github.com/dsj1984/mandrel/issues/5002)) ([#5017](https://github.com/dsj1984/mandrel/issues/5017)) ([7624c12](https://github.com/dsj1984/mandrel/commit/7624c125f974cce0820d494b63efad6e56b9beae))
68
+ * delivery/orchestration sweep: retire Epic-era close residue and collapse self-documented-unreachable machinery ([#5006](https://github.com/dsj1984/mandrel/issues/5006)) ([#5021](https://github.com/dsj1984/mandrel/issues/5021)) ([7c19dc5](https://github.com/dsj1984/mandrel/commit/7c19dc5d449f4a9926a7b59c9c9f0dbadf92e091))
69
+ * **orchestration:** delete production-dead story-lifecycle module ([#5025](https://github.com/dsj1984/mandrel/issues/5025)) ([811966c](https://github.com/dsj1984/mandrel/commit/811966c0e1d0f2a746261e342f64acfe1b6f6bd5))
70
+ * providers/QA/misc sweep: prune Epic-era provider surface, dead subsystems, and QA round-trip ceremony ([#5008](https://github.com/dsj1984/mandrel/issues/5008)) ([#5023](https://github.com/dsj1984/mandrel/issues/5023)) ([a019c14](https://github.com/dsj1984/mandrel/commit/a019c144149b23b7102fcd39e466842a3880cfa2))
71
+ * remove the write-only observability surface: tool-trace hooks, dead signals viewer, Epic-era graduator ([#5003](https://github.com/dsj1984/mandrel/issues/5003)) ([#5018](https://github.com/dsj1984/mandrel/issues/5018)) ([3a6273f](https://github.com/dsj1984/mandrel/commit/3a6273f3dc78796ac89a62c1fefa0d95200bac8e))
72
+ * retire the orphaned lifecycle-bus stratum: bus, both observers, the emitter-less schemas, and the doc-drift guard behind them ([#5024](https://github.com/dsj1984/mandrel/issues/5024)) ([#5032](https://github.com/dsj1984/mandrel/issues/5032)) ([caed250](https://github.com/dsj1984/mandrel/commit/caed2501520ce292832b99f12a5478350954c8b9))
73
+ * rules refresh: retire the phantom docs-freshness gate, demote §1.H, condense testing pedagogy ([#5010](https://github.com/dsj1984/mandrel/issues/5010)) ([#5031](https://github.com/dsj1984/mandrel/issues/5031)) ([18715c4](https://github.com/dsj1984/mandrel/commit/18715c4a13448b0992ad8b005193414a0fcf493f))
74
+ * single-source the config surface and finish the bootstrap vestige cleanup ([#5007](https://github.com/dsj1984/mandrel/issues/5007)) ([#5022](https://github.com/dsj1984/mandrel/issues/5022)) ([073415e](https://github.com/dsj1984/mandrel/commit/073415ebbefb2b5d4df05e3e52bbd4b11cf73120))
75
+
18
76
  ## [2.31.0](https://github.com/dsj1984/mandrel/compare/mandrel-v2.30.0...mandrel-v2.31.0) (2026-08-05)
19
77
 
20
78
 
@@ -57,6 +57,7 @@ import { retireVerifyConcurrencyCap } from './steps/2.1.0-retire-verify-concurre
57
57
  import { retireEpicAcTags } from './steps/2.2.0-retire-epic-ac-tags.js';
58
58
  import { retireMaxSeedWords } from './steps/2.11.0-retire-max-seed-words.js';
59
59
  import { retireCodebaseSnapshot } from './steps/2.20.0-retire-codebase-snapshot.js';
60
+ import { retireLintBaselineCommand } from './steps/2.32.0-retire-lint-baseline-command.js';
60
61
 
61
62
  /**
62
63
  * Ordered registry of migration steps. MUST stay sorted ascending by
@@ -75,6 +76,7 @@ export const migrations = [
75
76
  retireEpicAcTags,
76
77
  retireMaxSeedWords,
77
78
  retireCodebaseSnapshot,
79
+ retireLintBaselineCommand,
78
80
  ];
79
81
 
80
82
  /**
@@ -0,0 +1,127 @@
1
+ // lib/migrations/steps/2.32.0-retire-lint-baseline-command.js
2
+ /**
3
+ * Story #5004 follow-up — strip the retired `project.commands.lintBaseline`
4
+ * key from a consumer's `.agentrc.json`.
5
+ *
6
+ * #5004 (PR #5019) deleted `lint-baseline.js` and
7
+ * `lib/orchestration/lint-baseline-service.js`: the CLI spawned the configured
8
+ * command and parsed ESLint-shaped JSON, a shape this repo's own `npm run
9
+ * lint` (Biome + markdownlint fan-out) never produced, and nothing invoked the
10
+ * service. The key left the runtime AJV schema, the published mirror,
11
+ * `.agentrc.json` and `agentrc-reference.json` in the same change.
12
+ *
13
+ * `project.commands` carries `additionalProperties: false`, so a consumer
14
+ * whose config still sets `lintBaseline` hits a hard validation failure on
15
+ * upgrade, not a warning. This step strips the key before that check runs —
16
+ * the same contract-cutover pattern as `2.11.0-retire-max-seed-words.js`.
17
+ *
18
+ * It sweeps **both** config surfaces. `config-resolver.js` deep-merges
19
+ * `.agentrc.local.json` over `.agentrc.json` and validates the result, so a
20
+ * key surviving in the operator's overlay fails exactly as a base one would.
21
+ * Sweeping only the base would report "nothing to migrate" and leave that
22
+ * consumer hard-broken with no self-service remedy — re-running
23
+ * `mandrel update` would keep reporting clean — while this commit's
24
+ * `BREAKING CHANGE:` footer promises the upgrade deletes the key for them.
25
+ *
26
+ * The `lint` baseline KIND survives for consumers whose own linter writes
27
+ * `baselines/lint.json`; only the framework-owned capture shell is gone, so
28
+ * nothing here touches the baseline file or the gate config.
29
+ *
30
+ * This step is also the carrier for the release note #5004 never emitted: its
31
+ * commit ships the `BREAKING CHANGE:` footer that the squash subject on `main`
32
+ * (`227e1af4`) lacks, and which release-please therefore could not surface.
33
+ * See `single-story-close/phases/conventional-subject.js` for the close-side
34
+ * fix that stops the next one being lost.
35
+ */
36
+
37
+ import nodeFs from 'node:fs';
38
+ import path from 'node:path';
39
+
40
+ /**
41
+ * Both config surfaces the resolver reads. `.agentrc.local.json` is deep-merged
42
+ * over the base *before* the AJV gate runs
43
+ * (`.agents/scripts/lib/config-resolver.js`), so a `lintBaseline` surviving in
44
+ * the overlay fails validation exactly as one in the base would — and a step
45
+ * that swept only the base would leave that consumer hard-broken with no
46
+ * self-service remedy, since re-running `mandrel update` would keep reporting
47
+ * clean. The overlay is operator-owned and gitignored, which is why it is easy
48
+ * to forget and why the sweep has to name it explicitly.
49
+ */
50
+ const AGENTRC_FILENAMES = Object.freeze([
51
+ '.agentrc.json',
52
+ '.agentrc.local.json',
53
+ ]);
54
+
55
+ /**
56
+ * @param {unknown} ctx
57
+ * @param {string} filename
58
+ * @returns {string}
59
+ */
60
+ function resolveAgentrcPath(ctx, filename) {
61
+ const projectRoot = ctx?.projectRoot ?? process.cwd();
62
+ return path.join(projectRoot, filename);
63
+ }
64
+
65
+ /**
66
+ * @param {unknown} ctx
67
+ * @param {typeof nodeFs} fsImpl
68
+ * @param {string} filename
69
+ * @returns {object | null}
70
+ */
71
+ function readAgentrcConfig(ctx, fsImpl, filename) {
72
+ try {
73
+ const raw = fsImpl.readFileSync(resolveAgentrcPath(ctx, filename), 'utf8');
74
+ return JSON.parse(raw);
75
+ } catch {
76
+ return null;
77
+ }
78
+ }
79
+
80
+ /**
81
+ * @param {object | null} config
82
+ * @returns {boolean}
83
+ */
84
+ function hasRetiredKey(config) {
85
+ const commands = config?.project?.commands;
86
+ return Boolean(commands) && Object.hasOwn(commands, 'lintBaseline');
87
+ }
88
+
89
+ export const retireLintBaselineCommand = {
90
+ version: '2.32.0',
91
+ description:
92
+ 'strip retired project.commands.lintBaseline from .agentrc.json ' +
93
+ '(the framework lint-baseline capture CLI is gone — Story #5004)',
94
+ /**
95
+ * @param {{ projectRoot?: string, fs?: typeof nodeFs }} [ctx]
96
+ * @returns {boolean}
97
+ */
98
+ detect(ctx) {
99
+ const fsImpl = ctx?.fs ?? nodeFs;
100
+ return AGENTRC_FILENAMES.some((filename) =>
101
+ hasRetiredKey(readAgentrcConfig(ctx, fsImpl, filename)),
102
+ );
103
+ },
104
+ /**
105
+ * @param {{ projectRoot?: string, fs?: typeof nodeFs }} [ctx]
106
+ * @returns {void}
107
+ */
108
+ apply(ctx) {
109
+ const fsImpl = ctx?.fs ?? nodeFs;
110
+ for (const filename of AGENTRC_FILENAMES) {
111
+ const config = readAgentrcConfig(ctx, fsImpl, filename);
112
+ // An absent overlay is the common case, not an error.
113
+ if (!config || !hasRetiredKey(config)) continue;
114
+
115
+ delete config.project.commands.lintBaseline;
116
+ // An emptied `commands` block is left in place: unlike
117
+ // `planning.complexityGate`, `project` is a required block and an empty
118
+ // `commands` object is valid against the schema, so pruning it would be
119
+ // a cosmetic edit to a config the consumer owns.
120
+
121
+ fsImpl.writeFileSync(
122
+ resolveAgentrcPath(ctx, filename),
123
+ `${JSON.stringify(config, null, 2)}\n`,
124
+ );
125
+ }
126
+ },
127
+ };