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
@@ -11,11 +11,11 @@ links here from the sections that used to inline this content.
11
11
 
12
12
  ## Friction telemetry
13
13
 
14
- Reference mechanics behind the friction-telemetry MUST in
15
- [`instructions.md` § 1.H](../instructions.md). The always-loaded core keeps the
16
- MUST, the command, and the when-to-fire triggers; the detail below is consulted
17
- only when reasoning about **where** a friction record lands and **how** it is
18
- validated.
14
+ Reference mechanics behind the optional friction-telemetry tool pointed at
15
+ from [`instructions.md` § 1.H](../instructions.md). Capture is a **tool, not a
16
+ mandate** reach for `diagnose-friction.js` when a wrapped command's failure
17
+ shape is worth attributing; the detail below is consulted only when reasoning
18
+ about **where** a friction record lands and **how** it is validated.
19
19
 
20
20
  - **Canonical record + schema validation**: `diagnose-friction.js` appends one
21
21
  `kind: friction` record, validated write-time against
@@ -62,10 +62,10 @@ Unrecognized `AGENT_LOG_LEVEL` values fall back to `info`. There is no
62
62
  `debug` level alias.
63
63
 
64
64
  This is a diagnostic knob: set it when you need quieter script embedding
65
- (`silent`) or a deeper trace (`verbose`). The friction-telemetry MUST it sits
66
- under capture friction as a local NDJSON signal via `diagnose-friction.js`
67
- stays in [`instructions.md` § 1.H](../instructions.md); its record-landing and
68
- schema mechanics are in [§ Friction telemetry](#friction-telemetry) above.
65
+ (`silent`) or a deeper trace (`verbose`). This table is the SSOT for the
66
+ levels; the optional friction-capture tool it sits beside is pointed at from
67
+ [`instructions.md` § 1.H](../instructions.md), and that tool's record-landing
68
+ and schema mechanics are in [§ Friction telemetry](#friction-telemetry) above.
69
69
 
70
70
  ---
71
71
 
@@ -85,11 +85,10 @@ over-ceiling envelope or an over-budget Story count.
85
85
  > before shipping the raw seed anyway. The schema now **rejects**
86
86
  > `planning.context`, so a config carrying it fails loudly rather than silently
87
87
  > capping nothing. The ceiling below is the replacement and the only live bound
88
- > on planner-context size. Separately, `elideEnvelope` in
89
- > `lib/orchestration/context-envelope.js` which this section used to credit
90
- > with limiting hydrated prompt size has no production caller either (it is
91
- > carried in `baselines/dead-exports-production.json`). Only `estimateTokens`
92
- > from that module is live.
88
+ > on planner-context size. Separately, the `ContextEnvelope` SDK this section
89
+ > used to credit with limiting hydrated prompt size had no production caller
90
+ > and was deleted in Story #5005; only its `estimateTokens` helper survived,
91
+ > re-homed in `lib/orchestration/spec-spill.js`.
93
92
 
94
93
  ### Planner-context envelope (`/plan`)
95
94
 
@@ -172,15 +172,20 @@ baseline still trips the gate.
172
172
  run on push; use `npm run verify` locally before a PR. CI enforces the
173
173
  authoritative full gate set on every PR.
174
174
  - **CI** (`.github/workflows/ci.yml`): the `validate` job runs
175
- **Lint and Format** (`npm run lint`), a **Maintainability Check**
176
- (`npm run maintainability:check` → `check-baselines.js --gate
177
- maintainability`, diff-scoped on PRs via
178
- `delivery.quality.gateScoping`, full scope on push-to-main via
179
- `BASELINE_SCOPE=full`), and **Run Tests with Coverage**
175
+ **Lint and Format** (`npm run lint`) and **Run Tests with Coverage**
180
176
  (`npm run test:coverage`), uploading the `test-results` and
181
177
  `coverage-final` artifacts. A separate required **baselines** job runs
182
178
  the unified `node .agents/scripts/check-baselines.js --format text`,
183
- which enforces floors across every configured gate.
179
+ which enforces floors across every configured gate and is the only
180
+ baseline gate on the per-change path. (Story #5004 removed a
181
+ `Maintainability Check` step from `validate` that re-ran
182
+ `check-baselines.js --gate maintainability` at the same scope; a later
183
+ correction pass revisited its record of what the step's
184
+ `BASELINE_SCOPE=full` branch did — see `docs/ci-contract.md`.)
185
+ - **Nightly** (`.github/workflows/baseline-drift.yml`): the only
186
+ automated **full-scope re-score**. See
187
+ [`check-baseline-drift.js`](#check-baseline-driftjs--the-scheduled-full-scope-re-score)
188
+ below.
184
189
 
185
190
  ### Opt-out
186
191
 
@@ -248,16 +253,27 @@ the `delivery.acceptanceEval` field reference is in
248
253
  > Baseline envelope, axes, and component model: see the
249
254
  > [Baseline reference](#baseline-reference) section below.
250
255
 
251
- The lint baseline engine enforces zero-deterioration during Story
252
- delivery. Integrations fail if new lint warnings are introduced, and the
253
- baseline automatically tightens when the codebase improves.
256
+ The `lint` baseline kind enforces zero-deterioration during Story
257
+ delivery: `check-baselines.js --gate lint` fails if new lint warnings are
258
+ introduced, and the baseline tightens when the codebase improves.
254
259
 
255
260
  The canonical baseline file lives at `baselines/lint.json` (override via
256
- `delivery.quality.gates.lint.baselinePath`). Refresh with:
257
-
258
- ```bash
259
- node .agents/scripts/lint-baseline.js capture
260
- ```
261
+ `delivery.quality.gates.lint.baselinePath`).
262
+
263
+ **There is no framework capture CLI.** Story #5004 retired the
264
+ `lint-baseline.js` shell that used to write this file: it spawned a
265
+ configured lint command and parsed the linter's JSON, a shape only
266
+ ESLint-style output satisfies, and this repo's own `npm run lint`
267
+ (Biome + markdownlint fan-out) never produced it, so the gate was
268
+ configured-but-unfed. A consumer that wants the kind writes
269
+ `baselines/lint.json` from its own linter in the envelope shape documented
270
+ under [Baseline reference](#baseline-reference); a consumer that does not is
271
+ unaffected, because an absent baseline leaves the gate unconfigured.
272
+
273
+ > **Upgrading?** The `project.commands.lintBaseline` key that fed the retired
274
+ > shell is gone from the config schema, which is `additionalProperties: false`
275
+ > — a `.agentrc.json` still carrying it now **fails validation** rather than
276
+ > being silently ignored. Delete the key.
261
277
 
262
278
  Refresh commits should use a `baseline-refresh:` subject + non-empty body so
263
279
  the operator can spot baseline edits in review — same convention as the CRAP
@@ -320,6 +336,58 @@ its `new-method count over c=<flag>` column.
320
336
 
321
337
  ---
322
338
 
339
+ ## Gherkin corpus gate (opt-in)
340
+
341
+ `check-gherkin-corpus.js` is a static gate over a project's `.feature` corpus.
342
+ It runs inside `npm run lint` — the same required check as the arch-cycle
343
+ ratchet — and it enforces two things:
344
+
345
+ - **must-compile.** Every in-scope `.feature` is parsed with the real
346
+ `@cucumber/gherkin` parser and a failure is reported at `file:line:column`.
347
+ Re-implementing acceptance is the defect the gate exists to prevent: a
348
+ hand-rolled reader skips what it does not recognise, so a corpus that cannot
349
+ generate reads clean.
350
+ - **must-bind.** Every active scenario's steps are resolved against the step
351
+ definitions of **its own scope only**. A file that fails must-compile is
352
+ excluded from must-bind — a broken file parses as an arbitrary subset of
353
+ itself, and linting the remainder buries the one actionable finding.
354
+
355
+ The gate is **opt-in**: with no `qa.gherkinLint` block in `.agentrc.json` it
356
+ reports that it is not configured and exits 0, even when `.feature` files
357
+ exist on disk. An upgrade must never redden the lint of a corpus the consumer
358
+ never asked the framework to police. This repository does not configure it.
359
+
360
+ ```jsonc
361
+ "qa": {
362
+ "gherkinLint": {
363
+ "scopes": {
364
+ "web": {
365
+ "featureRoots": ["apps/web/tests/features"],
366
+ "stepRoots": ["apps/web/tests/steps"]
367
+ }
368
+ },
369
+ "exemptionTags": ["@skip"],
370
+ "stepWaivers": []
371
+ }
372
+ }
373
+ ```
374
+
375
+ Inside the opt-in the gate fails **closed**. An unresolvable
376
+ `@cucumber/gherkin`, or a scope resolving zero step definitions, exits 1
377
+ naming the cause and the remedy — reporting every step as unbound would be the
378
+ same blackout in a different costume. The parser is an optional peer
379
+ dependency resolved from the consumer project's own module chain, so a
380
+ consumer with no BDD tier gains nothing; install it with
381
+ `npm install --save-dev @cucumber/gherkin` when enabling the gate.
382
+
383
+ Two escapes exist because the step index is a source scan (heuristic) while
384
+ the parser is exact: `exemptionTags` (default `["@skip"]`) drops a scenario
385
+ from must-bind, and `stepWaivers` drops one exact step text. Neither is an
386
+ escape from must-compile — a parse error in an exempt scenario's file still
387
+ fails the run.
388
+
389
+ ---
390
+
323
391
  ## CRAP gate — Consumer onboarding
324
392
 
325
393
  > Baseline envelope, axes, and component model: see the
@@ -556,9 +624,110 @@ tolerance, **in either direction**. A row that silently improved is equally
556
624
  strong evidence the baseline no longer describes the tree.
557
625
 
558
626
  Exit codes: `0` no drift (or every kind skipped), `1` drift detected, `2` the
559
- check could not run. It is designed to be wired as a scheduled CI job;
560
- scheduling it is deliberately consumer-side work, and nothing in this
561
- repository runs it automatically.
627
+ check could not run.
628
+
629
+ **`--require-scored`.** "Every kind skipped" mapping to `0` is a
630
+ fail-open trap for the scheduled use this CLI was built for. Measured: with no
631
+ `coverage/coverage-final.json` on disk, `check-baseline-drift.js --gate crap`
632
+ prints `✅ No baseline drift detected` and exits `0` — a nightly job wired that
633
+ way is green and inert. Pass `--require-scored` and any skipped kind exits `2`
634
+ instead, naming the kind and the skip reason. Use it in every scheduled
635
+ invocation.
636
+
637
+ This repository schedules the maintainability kind in
638
+ `.github/workflows/baseline-drift.yml` (framework repo only — that path is not
639
+ part of the materialized `.agents/` payload) — nightly at 05:43 UTC plus
640
+ `workflow_dispatch`; it files or updates one
641
+ `meta::baseline-drift` issue with the report, closes it when the tree comes
642
+ back clean, and fails the run. A consumer materializing `.agents/` still owns
643
+ its own schedule.
644
+
645
+ `crap` is deliberately **not** in that job. Its drift identity is
646
+ `path::method@startLine`, so anything that shifts a method's line re-keys its
647
+ row: measured on this tree with a real coverage artifact, 82 rows drifted but
648
+ 1438 were reported added and 898 removed — and 853 of those removals are the
649
+ same `path::method` reappearing at a different line. The added/removed axis is
650
+ re-keying churn, not drift, and the remedy the report prints
651
+ (`npm run crap:update -- --full-scope`) additionally re-measures, pulling in
652
+ near-empty coverage entries minted by CLI-spawning tests. Fixing the identity
653
+ is a prerequisite to scheduling the kind.
654
+
655
+ ### `check-baseline-scope.js` — is this baseline still measuring the tree?
656
+
657
+ Drift detection assumes the row set is right and asks whether its numbers
658
+ moved. The prior question went unasked: **does this baseline still describe
659
+ the tree at all?** A ratchet is perfectly capable of being green while
660
+ measuring almost nothing — a row can point at a file deleted months ago, and
661
+ an in-scope file can carry no row whatsoever, and every gate above stays
662
+ green.
663
+
664
+ The scope gate asserts the row set in **both directions**, recomputing each
665
+ kind's in-scope file set from the gate's own configuration —
666
+ `.c8rc.cjs` `include`/`exclude` for coverage,
667
+ `delivery.quality.gates.<kind>.{targetDirs,ignoreGlobs}` for the rest —
668
+ through the same helpers the refresh scorers use, so the gate and the
669
+ producers cannot disagree about scope:
670
+
671
+ ```bash
672
+ npm run baselines:scope # every kind
673
+ node .agents/scripts/check-baseline-scope.js --kind coverage --json
674
+ node .agents/scripts/check-baseline-scope.js --strict # skip attribution
675
+ ```
676
+
677
+ Two design constraints are worth knowing before reading a report:
678
+
679
+ - **Only dense kinds assert `missing`.** `coverage` and `maintainability`
680
+ emit one row per in-scope file, so a file with no row is a real hole. `crap`
681
+ (per-method, coverage-gated), `duplication` (rows only where clones exist),
682
+ `lint` and `mutation` are sparse by construction — asserting `missing`
683
+ against them yields hundreds of phantom findings on a healthy tree, so they
684
+ assert `extra` only. `lighthouse` (`route`) and `bundle-size` (`bundle`) are
685
+ not file-keyed and are excluded from both.
686
+ - **A PR is blocked only for divergence it created.** Whole-tree equality
687
+ would red every open PR the moment anyone lands an in-scope file, so the
688
+ gate blocks on divergence attributable to `merge-base(base, HEAD)..HEAD` and
689
+ warns about the inherited remainder. It fails towards **strict** — every
690
+ finding fatal — when no base resolves, when HEAD is not ahead of it, or when
691
+ the change set edits a baseline or the config defining its scope.
692
+
693
+ Exit codes: `0` no fatal divergence, `1` fatal divergence, `2` the check could
694
+ not run. It runs in the required `baselines` CI job.
695
+
696
+ ### `prune-baseline-orphans.js` — the cheap remedy that makes the gate fair
697
+
698
+ A hard gate is only defensible while clearing it costs a command. Re-deriving
699
+ a whole baseline to express a *deletion* spends a coverage run or a full-tree
700
+ MI pass, which is exactly why stale rows accumulate. The pruner is that
701
+ deletion, done as arithmetic:
702
+
703
+ ```bash
704
+ npm run baselines:prune # write the prune
705
+ node .agents/scripts/prune-baseline-orphans.js --check # report only, exit 1
706
+ ```
707
+
708
+ It removes exactly two provably-inert row classes across every file-keyed
709
+ baseline — a row whose file is **absent** from disk, and a row for a file now
710
+ **out-of-scope** under the gate's own `targetDirs`/`ignoreGlobs` — and it is
711
+ **measurement-free by contract**: it never adds a row, never restamps
712
+ `generatedAt` (a fresh stamp over rows nobody re-measured is the precise
713
+ failure an age check exists to catch), and recomputes `rollup` through the
714
+ kind's own arithmetic so the pruned envelope still validates against its
715
+ schema. An unreadable scope config degrades to orphan-only pruning rather than
716
+ reading unknown scope as empty scope, which would hand it the whole baseline.
717
+
718
+ A **missing** row is the one thing the pruner will not fix: a file added
719
+ without being measured needs its producer (`npm run coverage:update`,
720
+ `npm run maintainability:update`), because inventing a row would be claiming a
721
+ measurement nobody took.
722
+
723
+ **CI does not run the pruner in either mode.** `--check` exits 1 on any stale
724
+ row without asking which change set introduced it, so pairing it with
725
+ `check-baseline-scope.js` in the required job cancelled that gate's merge-base
726
+ attribution: a row inherited from `main` — say one PR deletes a file while a
727
+ second, branched earlier, re-adds its row through a baseline refresh — reds
728
+ every open PR on divergence its author did not create and cannot fix from
729
+ their branch. The scope gate reports that row as an inherited warning; the
730
+ pruner is the remedy an operator (or agent) runs with the branch in hand.
562
731
 
563
732
  ---
564
733
 
@@ -651,10 +820,13 @@ Cross-references:
651
820
  configuration surface that backs the gates.
652
821
  - [`.agents/README.md`](../README.md) — consumer onboarding.
653
822
 
654
- > The `mutation` gate ships **dormant** (built-but-unwired, intentionally
655
- > opt-in). The former `update-mutation-baseline.js` refresh CLI was retired
656
- > with the rest of the zero-consumer script surface (#4482); the
657
- > `lib/mutation/` snapshot machinery remains for a future activation.
823
+ > `mutation` is a **registered baseline kind with no shipped runner**. The
824
+ > envelope, schema, and floor config below describe a `baselines/mutation.json`
825
+ > the framework can read and ratchet, but nothing in Mandrel invokes Stryker or
826
+ > writes that file: the `update-mutation-baseline.js` refresh CLI was retired
827
+ > in #4482 and the `lib/mutation/` snapshot machinery in #5008. Activating the
828
+ > gate means shipping a runner first — treat the kind as a reserved slot, not a
829
+ > dormant feature.
658
830
 
659
831
  ### Envelope
660
832
 
@@ -911,8 +1083,8 @@ Refresh paths:
911
1083
  `baselines/crap.json`.
912
1084
  - `node .agents/scripts/update-maintainability-baseline.js` — rewrites
913
1085
  `baselines/maintainability.json`.
914
- - `node .agents/scripts/lint-baseline.js capture` — rewrites
915
- `baselines/lint.json`.
1086
+ - `baselines/lint.json` has no framework refresh CLI see
1087
+ [Lint baseline ratchet](#lint-baseline-ratchet).
916
1088
 
917
1089
  After a kernel bump, regenerate every baseline whose `kernelVersion`
918
1090
  drifted, then commit the refreshed files. The writer guarantees
@@ -77,11 +77,8 @@ present, else the **Tech Stack** section of `docs/architecture.md`.
77
77
 
78
78
  ### H. Observability & Friction Telemetry
79
79
 
80
- You MUST log operational friction (repeated tool errors, unrecoverable
81
- command failures, self-corrected ambiguity, automatable boilerplate):
82
- `node .agents/scripts/diagnose-friction.js --story [STORY_ID] --cmd <cmd> <args...>`
83
- — a **local NDJSON signal** (not posted to the ticket). Schema and the
84
- `AGENT_LOG_LEVEL` table:
80
+ Optional: `diagnose-friction.js` wraps a command and records its failure
81
+ shape as a local NDJSON signal —
85
82
  [`docs/execution-reference.md`](docs/execution-reference.md#friction-telemetry).
86
83
 
87
84
  ### I. Anti-Thrashing Protocol
@@ -51,6 +51,7 @@ the two options above. Name the verdict you reached in the `friction` comment.
51
51
  | **defect-in-diff** | The failure reproduces on the branch and not on an unmodified `main` | Option 1 — fix at source |
52
52
  | **pre-existing** | The same check fails on an unmodified `main` too | Option 2 — file `meta::framework-gap`; remediate here only if it blocks this delivery |
53
53
  | **capacity** | Proven exhaustion of a runner resource, not a property of the diff (see below) | Option 2 — file `meta::framework-gap` **and** escalate to the operator |
54
+ | **unreproducible-tier** | The tier cannot be exercised in this sandbox at all, proven by an attempted attach (see below) | Option 2 — file `meta::framework-gap` **and** escalate on first encounter |
54
55
 
55
56
  ### The `capacity` verdict
56
57
 
@@ -82,10 +83,46 @@ the operator, who owns the runner pool. Do not sit in a retry loop waiting for
82
83
  capacity to return.
83
84
 
84
85
  **Rerunning a failed job to reach green stays forbidden under every verdict,
85
- `capacity` included.** The verdict changes who owns the fix and where it is
86
+ `capacity` and `unreproducible-tier` included.** The verdict changes who owns the fix and where it is
86
87
  filed; it never licenses a re-run, and it is not a route to a green bar. A
87
88
  capacity-blocked delivery ends `agent::blocked` — not merged.
88
89
 
90
+ ### The `unreproducible-tier` verdict
91
+
92
+ A check can fail on a tier the sandbox cannot run at all — most often a
93
+ browser suite whose Playwright `webServer` block supervises a dev server the
94
+ local process manager daemonizes, which aborts the run with
95
+ `Process from config.webServer exited early` before any test executes. The
96
+ failure is a property of the sandbox's ability to *host* the suite, not of the
97
+ diff.
98
+
99
+ This is the same structural hole the `capacity` verdict was added to fill, one
100
+ step earlier in the loop. Without it the honest reading is `flaky`, which routes
101
+ to Option 1 — and fix-at-source requires reproducing the failure, which is the
102
+ one thing that cannot be done. The agent then spends the full timebox
103
+ rediscovering that before escalating anyway, and any fix it does author is
104
+ written blind against a tier it never ran.
105
+
106
+ **Unreproducible must be proven, not inferred.** "The suite did not run for me"
107
+ is not the verdict — it is the symptom every misconfiguration produces. Cite
108
+ both:
109
+
110
+ - **The attempted attach.** Work the attach-don't-boot seam in the
111
+ [`playwright`](../skills/stack/qa/playwright/SKILL.md) skill — boot the server
112
+ out-of-band, point the suite at the running origin, set `reuseExistingServer`
113
+ — and name which step failed and how. A tier that runs once attached was never
114
+ unreproducible.
115
+ - **The observed signature.** The verbatim line the runner aborted on, so a
116
+ later reader can tell a lifetime-ownership mismatch from a genuine boot
117
+ failure in the app under test.
118
+
119
+ Absent both readings the verdict is unavailable and the failure routes as it did
120
+ before. On the verdict: file the `meta::framework-gap` issue with the run link,
121
+ the failure signature, and the attach attempt; flip the Story to
122
+ `agent::blocked` with a `friction` comment naming the verdict; and hand back to
123
+ the operator, who owns the sandbox. Do not author a fix for a tier you could not
124
+ run — a blind fix to a suite nobody exercised is how the gap compounds.
125
+
89
126
  ## Verifier
90
127
 
91
128
  The check is resolved only when it is **green with zero reruns of the failed
@@ -131,3 +168,9 @@ operator under **any** of:
131
168
  signature) and escalate on the first encounter rather than burning iterations
132
169
  trying to code around it. A proven-capacity failure is this case: reach the
133
170
  `capacity` verdict above and escalate on the first encounter.
171
+ - **Unrunnable tier → escalate immediately.** A tier the sandbox cannot host at
172
+ all is this case too: work the attach seam once, reach the
173
+ `unreproducible-tier` verdict above with its two readings, and escalate on the
174
+ **first encounter**. The 30-minute timebox is a ceiling here, never a budget
175
+ to spend — every minute past the failed attach buys nothing, because no
176
+ iteration can make an unhostable suite run.
@@ -43,6 +43,33 @@ Practical guidance when authoring a contract change:
43
43
  can detect "I cannot read this artifact"); they are **not** an
44
44
  invitation to keep multiple readers alive in the same release.
45
45
 
46
+ ### Declaring the break so consumers see it
47
+
48
+ A hard cutover is only honest if the consumer can find out about it. Prose in
49
+ a commit body does not qualify: release-please recognizes exactly two signals,
50
+ a `!` before the colon in the subject and a `BREAKING CHANGE:` footer, and
51
+ sees nothing else. Story #5004 removed `project.commands.lintBaseline` from a
52
+ schema block that is `additionalProperties: false` — a config that used to be
53
+ silently ignored now fails validation — described it in three body paragraphs,
54
+ and shipped with neither signal. It reached `main` as `docs:` and would have
55
+ been absent from the release notes entirely.
56
+
57
+ So a contract change MUST declare itself in one of two places, and close
58
+ (`normalize-pr-title.js`) propagates either to the squash subject and PR body:
59
+
60
+ 1. **A commit footer** — a `BREAKING CHANGE:` (or `BREAKING-CHANGE:`) line in
61
+ the body of whichever commit does the breaking. This is the default; write
62
+ it as you write the commit. The keyword is uppercase and starts its own
63
+ line, per Conventional Commits.
64
+ 2. **A Story-body declaration** — the same footer as its own line in the Story
65
+ issue, naturally at the end of `## Spec`. Use this when the break is known
66
+ at plan time, or when it spans several commits and no single one owns it.
67
+
68
+ Close reads both, marks the PR title `<type>!: …`, and appends the collected
69
+ note to the PR body as a footer. Consumers whose on-disk state needs changing
70
+ should also get a step in `lib/migrations/` — see that directory's README;
71
+ `2.32.0-retire-lint-baseline-command.js` is the step #5004 should have shipped.
72
+
46
73
  ## Push Validation — the known false-negative signature
47
74
 
48
75
  The core rule is: **never bypass hooks** (`--no-verify`, `--no-gpg-sign`, or
@@ -125,33 +152,6 @@ different hazards:
125
152
  (`git -C <main-repo> checkout -- <files>`), then re-run `/deliver <storyId>`.
126
153
  Never `git reset --hard` or `git checkout --force` to clear the way.
127
154
 
128
- ## Documentation Freshness Gate
129
-
130
- The `validate-docs-freshness.js` gate (run during `/deliver`) asks a
131
- falsifiable question of every doc in `delivery.docsFreshness.paths` +
132
- `project.docsContextFiles`: **was this doc actually updated for the Epic?**
133
- A doc passes on either of two conditions, but they are not
134
- interchangeable:
135
-
136
- - **Living docs are satisfied by being rewritten, not annotated.** For
137
- any non-changelog doc (architecture, decisions, README, guides, …) the
138
- gate passes **only** when an Epic-referencing commit touched the file —
139
- a commit whose message references `#<epicId>` and changes the doc.
140
- Rewrite the doc as part of the Epic's work; do not sprinkle `#<epicId>`
141
- into its prose to satisfy the check. An appended `#<epicId>` annotation
142
- alone **fails** the gate for these files, and the failure message names
143
- the file and the rewrite-not-append contract.
144
- - **`#<epicId>` body annotations pass only for changelog files.** A
145
- changelog-class file (basename matches `/changelog/i`, e.g.
146
- `docs/CHANGELOG.md`) may pass on a body annotation, because an appended
147
- release note keyed to the Epic is the legitimate, expected update there.
148
- This is the single sanctioned annotation path; every other doc must use
149
- the rewrite path above.
150
-
151
- This restriction exists to remove the perverse incentive by which the
152
- gate would otherwise reward manufacturing fake provenance — appending
153
- Epic-ID history into living docs purely to clear the check.
154
-
155
155
  ## Meta Labels (Retrospective Signal Routing)
156
156
 
157
157
  Two `meta::*` labels route retrospective signals into durable substrates so
@@ -5,8 +5,8 @@ core**: branch shapes, the Conventional-Commit subject contract, issue
5
5
  references, and the non-negotiable push/hygiene MUSTs. Deeper mechanics —
6
6
  the hard-cutover contract policy, the full push-validation procedure and
7
7
  the push-hook false-negative signature, checkout-hygiene scope rules,
8
- shared-checkout merge contention, the documentation-freshness gate, and
9
- the `meta::*` labels — live in the on-demand
8
+ shared-checkout merge contention, and the `meta::*` labels — live in the
9
+ on-demand
10
10
  [`git-conventions-reference.md`](git-conventions-reference.md); read it
11
11
  **before** doing the matching work.
12
12
 
@@ -32,6 +32,8 @@ subject referencing the Story via `(refs #<storyId>)` — see
32
32
  It does **not** run on squash-merge titles edited in the GitHub UI —
33
33
  author the PR title in conventional form so the squash commit parses
34
34
  for release-please.
35
+ - Consumer-breaking changes MUST carry a `BREAKING CHANGE:` footer
36
+ (breaking commit or Story body); prose never reaches release-please.
35
37
 
36
38
  ## Push Validation & Reliability (MUSTs)
37
39
 
@@ -23,13 +23,14 @@ delivery cycle.
23
23
 
24
24
  ## 1. `npm run lint` prints `Summary: 0 error(s)` and can still exit 1
25
25
 
26
- **Behavior.** `npm run lint` is `run-lint.js`, which spawns six tools
27
- concurrently with inherited stdio and exits with the first non-zero code.
28
- `Summary: 0 error(s)` is **markdownlint-cli2's own verdict**, not the
29
- aggregate it is printed whether or not Biome, the lifecycle lint, the
30
- workflow-CLI lint, the label-vocabulary lint, or the arch-cycle ratchet
31
- failed. Because the tools run in parallel, that line can land anywhere in
32
- the output, including last, so the tail of a failing run reads green.
26
+ **Behavior.** `npm run lint` is `run-lint.js`, which spawns eight tools
27
+ concurrently with inherited stdio and exits with the first non-zero code
28
+ Biome, markdownlint-cli2, the lifecycle lint, the workflow-CLI lint, the
29
+ label-vocabulary lint, the workflow-timeout gate, the arch-cycle ratchet, and
30
+ the Gherkin corpus gate. `Summary: 0 error(s)` is **markdownlint-cli2's own
31
+ verdict**, not the aggregate it is printed whether or not any of the other
32
+ seven failed. Because the tools run in parallel, that line can land anywhere
33
+ in the output, including last, so the tail of a failing run reads green.
33
34
  Biome's format diagnostics are `error`-severity, so a file that only needs
34
35
  reformatting fails the check while emitting no lint rule name at all.
35
36
 
@@ -55,34 +56,53 @@ with `npm run format`; never reach for `--no-verify`.
55
56
  `node .agents/scripts/check-baselines.js`, but that script only runs the
56
57
  gates configured under `delivery.quality.gates` — currently **crap,
57
58
  maintainability, and duplication**. CI's job named `baselines` in
58
- `.github/workflows/ci.yml` runs that script **and then five standalone
59
- ratchets** the script knows nothing about, so a locally green
59
+ `.github/workflows/ci.yml` runs that script **and then nine standalone
60
+ commands** the script knows nothing about, so a locally green
60
61
  `check-baselines.js` is not evidence that the `baselines` check will pass.
61
62
 
62
- | Ratchet CI's `baselines` job runs | Covered by `check-baselines.js` | Covered by `npm run lint` | Covered by `npm run verify` |
63
+ | Command CI's `baselines` job runs | Covered by `check-baselines.js` | Covered by `npm run lint` | Covered by `npm run verify` |
63
64
  | --- | --- | --- | --- |
64
- | `.agents/scripts/check-arch-cycles.js` | no | **yes** | via `lint` |
65
- | `.agents/scripts/check-dead-exports.js` | no | no | **yes** |
66
- | `.agents/scripts/check-dead-exports.js --production` | no | no | **yes** |
67
- | `.agents/scripts/check-context-budget.js` | no | no | **yes** |
68
- | `.agents/scripts/check-workflow-citations.js` | no | no | **no** |
69
-
70
- `check-workflow-citations.js` is currently in **no** local aggregate command
71
- it is reachable only as `npm run check:workflow-citations` or a direct
72
- invocation.
65
+ | `check-arch-cycles.js` | no | **yes** | via `lint` |
66
+ | `check-dead-exports.js` | no | no | **yes** |
67
+ | `check-dead-exports.js --production` | no | no | **yes** |
68
+ | `check-context-budget.js` | no | no | **yes** |
69
+ | `check-workflow-citations.js` | no | no | **no** |
70
+ | `check-cyclomatic.js` | no | no | **yes** |
71
+ | `check-schema-references.js` | no | no | **yes** |
72
+ | `check-knip-entries.js` | no | no | **yes** |
73
+ | `check-baseline-scope.js` | no | no | **no** |
74
+
75
+ Two are still in **no** local aggregate command —
76
+ `check-workflow-citations.js` and `check-baseline-scope.js`. Each is reachable
77
+ only as its own npm script (`check:workflow-citations`, `baselines:scope`) or a
78
+ direct invocation.
79
+
80
+ `prune-baseline-orphans.js --check` left this table in v2.32.0: it no longer
81
+ runs in CI in any mode. It reports the same absent / out-of-scope rows as
82
+ `check-baseline-scope.js` but without merge-base attribution, so holding it in
83
+ the required job made the scope gate's inherited-divergence warning
84
+ unreachable — a stale row on `main` red every open PR regardless of who landed
85
+ it. It stays the operator's remedy, via `npm run baselines:prune`.
86
+
87
+ `check-context-budget.js` additionally runs in `.husky/pre-push`. It is
88
+ zero-tolerance in **both** directions — a change that *shrinks* the
89
+ always-loaded doc closure reds it exactly as growth does, and the remedy is a
90
+ committed baseline refresh, not a smaller diff.
73
91
 
74
92
  **Reproduce.**
75
93
 
76
94
  ```bash
77
95
  node .agents/scripts/check-baselines.js --format text # names the 3 gates it ran
78
- sed -n '/name: baselines/,/windows-smoke/p' .github/workflows/ci.yml | grep 'check-'
79
- grep "label: '" .agents/scripts/run-verify.js # the 7 steps verify covers
96
+ sed -n '/name: baselines/,/windows-smoke/p' .github/workflows/ci.yml | grep 'js'
97
+ grep "label: '" .agents/scripts/run-verify.js # the 9 steps verify covers
80
98
  ```
81
99
 
82
- **Safe move.** `npm run verify` is the closest local mirror; run
83
- `node .agents/scripts/check-workflow-citations.js` alongside it when the
84
- change touches workflow prose under `.agents/workflows/`. Reproducing only
85
- the `.agentrc.json` command before a push is a false green.
100
+ **Safe move.** `npm run verify` is the closest local mirror. Run
101
+ `node .agents/scripts/check-workflow-citations.js` alongside it when the change
102
+ touches workflow prose under `.agents/workflows/`, and
103
+ `npm run baselines:scope && npm run baselines:prune -- --check` when it adds,
104
+ deletes or moves files inside a scored `targetDirs` root. Reproducing only the
105
+ `.agentrc.json` command before a push is a false green.
86
106
 
87
107
  ## 3. The two dead-export passes disagree, and the production pass is silent without `!`
88
108
 
@@ -90,10 +110,21 @@ the `.agentrc.json` command before a push is a false green.
90
110
  baselines. The default pass treats `tests/**` as knip entry points, so an
91
111
  export whose only importer is a test still reads as *used*; the
92
112
  `--production` pass discounts test importers and therefore sees a much
93
- larger surface — `baselines/dead-exports.json` carries 165 rows against
94
- `baselines/dead-exports-production.json`'s 667. A new export that is only
113
+ larger surface — `baselines/dead-exports.json` carries 130 rows against
114
+ `baselines/dead-exports-production.json`'s 695. A new export that is only
95
115
  imported by its test passes the default pass and fails the production one.
96
116
 
117
+ Rows come in two shapes. `{ file, symbol: '<name>' }` is one unused export;
118
+ `{ file, symbol: '*' }` is **whole-file death** — a module nothing imports.
119
+ Knip reports such a module once, under its `files` category, and suppresses
120
+ that module's per-export rows, so a file losing its last importer *shrinks*
121
+ the export row set. Reading only `exports` therefore scored losing a whole
122
+ module as an improvement; the `*` rows exist so it reads as the regression
123
+ it is. `knip.json` lists the `.agents/scripts` CLIs as explicit entry paths
124
+ for the same reason: a blanket `.agents/scripts/*.js!` glob declared every
125
+ top-level CLI reachable by construction, so no uninvoked one could ever
126
+ surface. Adding a CLI means adding its entry line.
127
+
97
128
  The production pass depends entirely on the `!` suffix on the `entry` and
98
129
  `project` patterns in `knip.json`: `!` marks a pattern as
99
130
  production-relevant. Strip the suffixes and `knip --production` reports
@@ -109,6 +140,11 @@ grep -c '!"' knip.json # the production markers
109
140
  ```
110
141
 
111
142
  **Safe move.** Run both passes before pushing. When an export is genuinely
112
- test-only, keep it and refresh the production baseline deliberately
113
- `.agents/rules/test-seams.md` governs which seams are sanctioned. Never
114
- remove the `!` suffixes from `knip.json` to quieten the production pass.
143
+ test-only, keep it and refresh both baselines deliberately with `npm run
144
+ dead-exports:update` — `.agents/scripts/update-dead-exports-baseline.js`,
145
+ which rewrites the rows knip currently reports and fails closed rather than
146
+ persisting an empty snapshot from a knip run that never worked. Never
147
+ hand-edit `baselines/dead-exports*.json`: a hand-written row set is the one
148
+ input no gate re-derives. `.agents/rules/test-seams.md` governs which seams
149
+ are sanctioned. Never remove the `!` suffixes from `knip.json` to quieten the
150
+ production pass.