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
@@ -0,0 +1,321 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * update-dead-exports-baseline.js — the producer behind the dead-export
5
+ * ratchet (Story #5011).
6
+ *
7
+ * `check-dead-exports.js` has shipped since Story #1852 with no way to write
8
+ * the baseline it ratchets against. Every seed and every refresh was therefore
9
+ * a hand-edit: run the checker with `--json`, copy `currentRows` out of the
10
+ * report, re-sort them, and preserve `$schema` / `kernelVersion` / `mode` by
11
+ * hand. That is the gap this CLI closes — dead-exports now sits beside its four
12
+ * `update-*-baseline.js` siblings instead of being the one baseline an operator
13
+ * had to author in a text editor.
14
+ *
15
+ * **Fail closed, unlike the checker.** `check-dead-exports.js` treats a knip
16
+ * spawn or parse failure as advisory (exit 0 + a stderr warning) because it
17
+ * still holds a committed snapshot to compare against — a broken knip install
18
+ * must not redden CI on its own. The producer has no such fallback: the file it
19
+ * is about to write *is* the snapshot. An empty row set persisted from a failed
20
+ * run would silently grandfather every dead export in the repository and blind
21
+ * the ratchet permanently. So this CLI exits non-zero and writes **nothing**
22
+ * whenever knip cannot run, its report cannot be parsed, or the knip version
23
+ * cannot be resolved. That asymmetry with the checker is deliberate.
24
+ *
25
+ * **Envelope, not the shared writer.** Dead-exports does not route through
26
+ * `lib/baselines/writer.js`: that writer admits only the kinds registered in
27
+ * `lib/baselines/envelope.js` and requires a `*` rollup row, while dead-exports
28
+ * is an out-of-band ratchet kind (`lib/audit-baselines/kinds.js`) that carries
29
+ * no rollup. This CLI writes exactly the shape the checker already reads and
30
+ * both committed baselines already carry:
31
+ *
32
+ * { $schema, kernelVersion, generatedAt, [mode], rows: [{ file, symbol }] }
33
+ *
34
+ * `kernelVersion` is knip's own installed version — knip is the scorer, so a
35
+ * knip upgrade is what invalidates the rows. `mode` is stamped on the
36
+ * `--production` pass only, matching the committed production baseline.
37
+ *
38
+ * Rows are de-duplicated and sorted by `(file, symbol)` with the same
39
+ * comparator `check-dead-exports.js` uses for its diff output, so a re-run
40
+ * against an unchanged tree differs only in `generatedAt` and review sees real
41
+ * movement rather than reordering noise.
42
+ *
43
+ * The pass is selected with `--production`, and the baseline/label/mode triple
44
+ * comes from `lib/dead-exports-mode.js` — the same resolver the checker uses.
45
+ * Resolving that pairing independently here is precisely how a producer would
46
+ * end up writing production rows over the default baseline.
47
+ */
48
+
49
+ import fs from 'node:fs';
50
+ import path from 'node:path';
51
+ import process from 'node:process';
52
+ import { runAsCli } from './lib/cli-utils.js';
53
+ import {
54
+ extractRowsFromKnip,
55
+ readKnipOutput,
56
+ runKnip,
57
+ } from './lib/dead-exports-knip.js';
58
+ import { resolveDeadExportsMode } from './lib/dead-exports-mode.js';
59
+
60
+ /** `$schema` ref stamped into every dead-export baseline envelope. */
61
+ export const DEAD_EXPORTS_SCHEMA_REF =
62
+ 'https://mandrel.dev/baselines/dead-exports.schema.json';
63
+
64
+ /**
65
+ * Parse argv for `--production`, `--baseline <path>` and `--knip-output
66
+ * <path>`. `--knip-output` is the test seam: it feeds a pre-captured knip JSON
67
+ * report instead of spawning knip, and mirrors the checker's flag of the same
68
+ * name so a captured report drives both sides of the ratchet.
69
+ *
70
+ * @param {string[]} argv
71
+ * @returns {{ baselinePath: string | null, knipOutputPath: string | null, production: boolean }}
72
+ */
73
+ export function parseArgv(argv = []) {
74
+ const out = { baselinePath: null, knipOutputPath: null, production: false };
75
+ const valueFlags = {
76
+ '--baseline': 'baselinePath',
77
+ '--knip-output': 'knipOutputPath',
78
+ };
79
+ for (let i = 0; i < argv.length; i += 1) {
80
+ const arg = argv[i];
81
+ if (arg === '--production') {
82
+ out.production = true;
83
+ continue;
84
+ }
85
+ const field = valueFlags[arg];
86
+ const next = argv[i + 1];
87
+ if (field && next && !next.startsWith('--')) {
88
+ out[field] = next;
89
+ i += 1;
90
+ }
91
+ }
92
+ return out;
93
+ }
94
+
95
+ /**
96
+ * Resolve the knip version to stamp as `kernelVersion`, read from the
97
+ * installed package rather than recalled. Returns `null` when knip is not
98
+ * installed or its manifest is unreadable — the caller treats that as a
99
+ * fail-closed condition, because an unstamped baseline cannot signal to a
100
+ * later run that the scorer moved underneath it.
101
+ *
102
+ * @param {{ cwd?: string, readFileImpl?: typeof fs.readFileSync }} [opts]
103
+ * @returns {string | null}
104
+ */
105
+ export function resolveKnipKernelVersion({
106
+ cwd = process.cwd(),
107
+ readFileImpl = fs.readFileSync,
108
+ } = {}) {
109
+ try {
110
+ const manifest = path.resolve(cwd, 'node_modules', 'knip', 'package.json');
111
+ const version = JSON.parse(readFileImpl(manifest, 'utf-8'))?.version;
112
+ return typeof version === 'string' && version.length > 0 ? version : null;
113
+ } catch {
114
+ return null;
115
+ }
116
+ }
117
+
118
+ /**
119
+ * De-duplicate and sort `{ file, symbol }` rows into the committed order.
120
+ *
121
+ * Identity is `(file, symbol)` — the same identity `diffRows` in
122
+ * `check-dead-exports.js` uses — so two knip issue records naming the same
123
+ * dead file collapse to one row. The comparator is that checker's comparator,
124
+ * which is why re-sorting an already-committed baseline is a no-op.
125
+ *
126
+ * @param {Array<{ file?: unknown, symbol?: unknown }>} rows
127
+ * @returns {Array<{ file: string, symbol: string }>}
128
+ */
129
+ export function normalizeRows(rows) {
130
+ const seen = new Set();
131
+ const out = [];
132
+ for (const row of rows ?? []) {
133
+ if (typeof row?.file !== 'string' || typeof row?.symbol !== 'string')
134
+ continue;
135
+ const key = `${row.file}\0${row.symbol}`;
136
+ if (seen.has(key)) continue;
137
+ seen.add(key);
138
+ out.push({ file: row.file, symbol: row.symbol });
139
+ }
140
+ return out.sort(
141
+ (a, b) => a.file.localeCompare(b.file) || a.symbol.localeCompare(b.symbol),
142
+ );
143
+ }
144
+
145
+ /**
146
+ * Assemble the baseline envelope. `mode` is stamped on the production pass
147
+ * only: the default baseline has never carried the key, and adding it would
148
+ * churn the committed file for no signal.
149
+ *
150
+ * @param {{ kernelVersion: string, mode: string, rows: Array<object>, generatedAt: string }} args
151
+ * @returns {object}
152
+ */
153
+ export function buildEnvelope({ kernelVersion, mode, rows, generatedAt }) {
154
+ const envelope = {
155
+ $schema: DEAD_EXPORTS_SCHEMA_REF,
156
+ kernelVersion,
157
+ generatedAt,
158
+ };
159
+ if (mode === 'production') envelope.mode = mode;
160
+ envelope.rows = rows;
161
+ return envelope;
162
+ }
163
+
164
+ /**
165
+ * Obtain a knip report, either from the `--knip-output` seam or by spawning
166
+ * knip for the requested pass. Never throws; a failure is reported as
167
+ * `{ ok: false, error }` so the caller can fail closed without a try/catch.
168
+ *
169
+ * @param {{
170
+ * cwd: string,
171
+ * production: boolean,
172
+ * knipOutputPath: string | null,
173
+ * runKnipImpl: typeof runKnip,
174
+ * readKnipOutputImpl: typeof readKnipOutput,
175
+ * }} args
176
+ * @returns {{ ok: true, envelope: unknown } | { ok: false, error: string }}
177
+ */
178
+ export function collectKnipReport({
179
+ cwd,
180
+ production,
181
+ knipOutputPath,
182
+ runKnipImpl,
183
+ readKnipOutputImpl,
184
+ }) {
185
+ if (knipOutputPath) {
186
+ const envelope = readKnipOutputImpl(path.resolve(cwd, knipOutputPath));
187
+ return envelope == null
188
+ ? { ok: false, error: `could not read knip report at ${knipOutputPath}` }
189
+ : { ok: true, envelope };
190
+ }
191
+ return runKnipImpl({ cwd, production });
192
+ }
193
+
194
+ /**
195
+ * Reject a report the extractor cannot read. `extractRowsFromKnip` is total —
196
+ * it answers `[]` for any shape it does not recognise — which is the right
197
+ * posture for an advisory checker and the wrong one for a producer: an
198
+ * unrecognised report and a genuinely clean repository would persist the same
199
+ * empty row set. Returns an error string, or `null` when the report is usable.
200
+ *
201
+ * @param {unknown} envelope
202
+ * @returns {string | null}
203
+ */
204
+ export function describeUnusableReport(envelope) {
205
+ if (!envelope || typeof envelope !== 'object')
206
+ return 'knip report is not a JSON object';
207
+ if (!Array.isArray(envelope.issues))
208
+ return 'knip report carries no `issues` array';
209
+ return null;
210
+ }
211
+
212
+ /**
213
+ * Top-level CLI entry, exported so tests drive the whole pipeline through
214
+ * injected hooks without spawning knip or touching a committed baseline.
215
+ *
216
+ * @param {{
217
+ * argv?: string[],
218
+ * cwd?: string,
219
+ * stdout?: { write: (s: string) => void },
220
+ * stderr?: { write: (s: string) => void },
221
+ * runKnipImpl?: typeof runKnip,
222
+ * readKnipOutputImpl?: typeof readKnipOutput,
223
+ * readFileImpl?: typeof fs.readFileSync,
224
+ * writeFileImpl?: typeof fs.writeFileSync,
225
+ * renameImpl?: typeof fs.renameSync,
226
+ * now?: () => string,
227
+ * }} [opts]
228
+ * @returns {Promise<number>} 0 on a written baseline; 1 on any fail-closed path.
229
+ */
230
+ export async function runCli({
231
+ argv = process.argv.slice(2),
232
+ cwd = process.cwd(),
233
+ stdout = process.stdout,
234
+ stderr = process.stderr,
235
+ runKnipImpl = runKnip,
236
+ readKnipOutputImpl = readKnipOutput,
237
+ readFileImpl = fs.readFileSync,
238
+ writeFileImpl = fs.writeFileSync,
239
+ renameImpl = fs.renameSync,
240
+ now = () => new Date().toISOString(),
241
+ } = {}) {
242
+ const { baselinePath, knipOutputPath, production } = parseArgv(argv);
243
+ const { mode, label, baseline } = resolveDeadExportsMode(production);
244
+ const target = path.resolve(cwd, baselinePath ?? baseline);
245
+
246
+ const kernelVersion = resolveKnipKernelVersion({ cwd, readFileImpl });
247
+ if (!kernelVersion) {
248
+ stderr.write(
249
+ `[${label}] ❌ cannot resolve knip's version from node_modules/knip/package.json — refusing to write ${target}\n`,
250
+ );
251
+ return 1;
252
+ }
253
+
254
+ const report = collectKnipReport({
255
+ cwd,
256
+ production,
257
+ knipOutputPath,
258
+ runKnipImpl,
259
+ readKnipOutputImpl,
260
+ });
261
+ const failure = report.ok
262
+ ? describeUnusableReport(report.envelope)
263
+ : report.error;
264
+ if (failure) {
265
+ stderr.write(
266
+ `[${label}] ❌ ${failure} — refusing to write ${target} (an empty baseline would grandfather every dead export)\n`,
267
+ );
268
+ return 1;
269
+ }
270
+
271
+ const rows = normalizeRows(extractRowsFromKnip(report.envelope));
272
+ const envelope = buildEnvelope({
273
+ kernelVersion,
274
+ mode,
275
+ rows,
276
+ generatedAt: now(),
277
+ });
278
+ // Write-then-rename, matching `lib/baselines/writer.js`: a crash or a full
279
+ // disk mid-write must not leave a truncated envelope behind. An unparseable
280
+ // baseline reads as empty to `check-dead-exports.js`, which would report
281
+ // every pre-existing row as newly added.
282
+ const tmpTarget = `${target}.tmp`;
283
+ writeFileImpl(tmpTarget, `${JSON.stringify(envelope, null, 2)}\n`, 'utf-8');
284
+ renameImpl(tmpTarget, target);
285
+ stdout.write(
286
+ `[${label}] ✅ wrote ${rows.length} row(s) to ${target} (kernelVersion=${kernelVersion}).\n`,
287
+ );
288
+ return 0;
289
+ }
290
+
291
+ async function main() {
292
+ return runCli();
293
+ }
294
+
295
+ runAsCli(import.meta.url, main, {
296
+ source: 'dead-exports-baseline',
297
+ propagateExitCode: true,
298
+ errorPrefix: '[dead-exports-baseline] ❌ Fatal error',
299
+ usage: {
300
+ invocation:
301
+ 'node .agents/scripts/update-dead-exports-baseline.js [--production] [--baseline <path>] [--knip-output <path>]',
302
+ summary:
303
+ 'Scan → write one dead-export baseline. Regenerates the rows `check-dead-exports.js` ratchets against, so the snapshot is produced rather than hand-edited.',
304
+ flags: [
305
+ [
306
+ '--production',
307
+ 'Write the production-pass baseline (baselines/dead-exports-production.json) instead of the default one.',
308
+ ],
309
+ ['--baseline <path>', 'Write to this path instead of the mode default.'],
310
+ [
311
+ '--knip-output <path>',
312
+ 'Read a saved knip JSON report instead of running knip.',
313
+ ],
314
+ ],
315
+ notes: [
316
+ 'Fails closed: when knip cannot run or its report cannot be parsed, the CLI exits 1 and writes nothing — the checker is advisory on that failure, the producer must not be.',
317
+ 'There is no scope flag: knip scores the whole graph, so a diff-scoped refresh is not expressible. Run `npm run dead-exports:update` to refresh both passes.',
318
+ 'Exit codes:\n 0 baseline written\n 1 knip unavailable, unparseable, or version unresolvable',
319
+ ],
320
+ },
321
+ });
@@ -18,7 +18,7 @@ allowed_tools:
18
18
  - **No gate may be skipped.** Failing lint means fix lint, not disable the rule; a failing test means fix the code, not `.skip` or delete the test. Gates are ordered shift-left so cheap checks fail first, and CI failure output is fed back verbatim with the directive to reproduce and fix locally before re-pushing.
19
19
  - **Introducing a gate that asserts on pre-existing state** (doc-drift, lint-vocabulary, dependency-cycle, missing-coverage) MUST land green at merge: either advisory-first (report-only until the backlog is burned down) or with the populated baseline committed in the same change that turns the gate on. Never wire a gate into `requiredChecks` that lands red on latent findings nobody authored.
20
20
  - **Refresh a baseline only when the change is deliberate** — a rename/move, an operator-approved complexity bump, a signed-off perf delta, an intentional API-surface change. Never refresh to paper over an unintentional regression; fix the regression instead.
21
- - **Pick the refresh that matches why you are refreshing** — the two are different scopes, and the wrong one silently no-ops. `npm run <kind>:update` is **diff-scoped**: it re-scores only files changed in `origin/main..HEAD` and preserves every other row verbatim. That is what you want after changing code. `npm run <kind>:reanchor` is the same script with `--full-scope`: it re-scores every file in every target dir. That is what you want when the baseline is **stale** — a scorer-version bump, a coverage-shape change, accumulated drift — because a diff-scoped run would leave the stale rows exactly as they were and the staleness finding would re-fire. Kinds: `coverage`, `crap`, `maintainability`, `duplication`; dead-exports and lighthouse have no npm scriptregenerate the rows and edit `baselines/dead-exports*.json` / `baselines/lighthouse.json` directly. Run either on the **Story branch**, not on `main`.
21
+ - **Pick the refresh that matches why you are refreshing** — the two are different scopes, and the wrong one silently no-ops. `npm run <kind>:update` is **diff-scoped**: it re-scores only files changed in `origin/main..HEAD` and preserves every other row verbatim. That is what you want after changing code. `npm run <kind>:reanchor` is the same script with `--full-scope`: it re-scores every file in every target dir. That is what you want when the baseline is **stale** — a scorer-version bump, a coverage-shape change, accumulated drift — because a diff-scoped run would leave the stale rows exactly as they were and the staleness finding would re-fire. Kinds with both scopes: `coverage`, `crap`, `maintainability`, `duplication`. Dead-exports has a producer but no scope axisknip scores the whole graph, so `npm run dead-exports:update` (both passes, via `.agents/scripts/update-dead-exports-baseline.js`) is the only sanctioned way to write `baselines/dead-exports.json` / `baselines/dead-exports-production.json`; never hand-edit those files. Lighthouse still has no producer — edit `baselines/lighthouse.json` directly. Run any of them on the **Story branch**, not on `main`.
22
22
  - Verify the refresh diff is scoped to the relevant `baselines/<kind>.json` (plus cosmetic `package-lock.json` churn only). If unrelated files appear, STOP — the refresh is contaminated. Stage baseline files **explicitly** (`git add baselines/<kind>.json`); never `git add -A` in a refresh commit.
23
23
  - Commit-subject contract: a **Conventional-Commits** subject `chore(baselines): refresh <kind> snapshot for <reason>` — never an ad-hoc leading token like `baseline-refresh:` (commitlint and the planner validator reject it). The body is **mandatory** and non-empty: what changed, why the new floor is correct, and the Story that triggered it.
24
24
  - Add the machine-readable trailer `baseline-refresh: true` (git-trailer `Key: value` style) and `Story: #<storyId>` to the body whenever observability classification matters. Never pass `--no-verify`; the `commit-msg` hook (commitlint) MUST run and pass.
@@ -101,7 +101,7 @@ own as the parseable marker for any future reader.)
101
101
  | Maintainability | `npm run maintainability:update` | `npm run maintainability:reanchor` |
102
102
  | Coverage | `npm run coverage:update` | `npm run coverage:reanchor` |
103
103
  | Duplication | `npm run duplication:update` | `npm run duplication:reanchor` |
104
- | Dead-exports | edit `baselines/dead-exports.json` / `baselines/dead-exports-production.json` (rows are `(file, symbol)`; `check-dead-exports.js --json` prints the current rows) | same — no scope flag applies |
104
+ | Dead-exports | `npm run dead-exports:update` (regenerates both passes; never hand-edit the JSON) | same — no scope flag applies |
105
105
  | Lighthouse | edit `baselines/lighthouse.json` | same — no scope flag applies |
106
106
 
107
107
  1. **Run the matching command for your reason** — left column after a code
@@ -1,5 +1,5 @@
1
1
  {
2
- "generatedAt": "2026-08-05T00:59:17.768Z",
2
+ "generatedAt": "2026-08-06T10:26:18.702Z",
3
3
  "generator": "generate-skills-index.js@1",
4
4
  "skills": [
5
5
  {
@@ -42,16 +42,6 @@
42
42
  "allowedTools": null,
43
43
  "vendor": null
44
44
  },
45
- {
46
- "name": "diagnose-friction",
47
- "tier": "core",
48
- "category": "core",
49
- "path": ".agents/skills/core/diagnose-friction/SKILL.md",
50
- "description": "Wrap a shell command with diagnostic capture. On failure, print static suggestions and append a structured `friction` record to the per-Story signals.ndjson stream. Use whenever a script in the orchestration loop invokes a tool whose failure shape we want the analyzer to attribute.",
51
- "policyCapsuleBullets": 9,
52
- "allowedTools": ["Bash", "Read"],
53
- "vendor": null
54
- },
55
45
  {
56
46
  "name": "documentation-and-adrs",
57
47
  "tier": "core",
@@ -7,10 +7,10 @@ description: Audit the repository's main documentation for staleness, semantic d
7
7
  You are a Staff Engineer & Documentation Steward verifying the repository's prose
8
8
  documentation is **up to date and complete**. Prose rots silently: commands get
9
9
  renamed, scripts move, workflows change shape, version/topology claims go stale.
10
- The deterministic gates (`check-doc-links.js`, `check-lifecycle-doc-drift.js`,
11
- `validate-docs-freshness.js`) catch broken links, generator drift, and
12
- per-delivery freshness they cannot tell whether the prose still describes how
13
- the code actually behaves. That semantic verification is this lens's job. The
10
+ The deterministic gates (`check-doc-links.js` and the generators' `--check`
11
+ mode) catch broken links and generator drift — they cannot tell whether the
12
+ prose still describes how the code actually behaves. That semantic
13
+ verification is this lens's job. The
14
14
  shared lens machinery — read-only constraint, scope interpretation, report
15
15
  envelope + finding-block skeleton, severity scale, self-cross-check, and
16
16
  execution strategy — lives in
@@ -78,7 +78,6 @@ are cheap, exact, and de-duplicate the easy findings:
78
78
 
79
79
  ```bash
80
80
  node .agents/scripts/check-doc-links.js
81
- node .agents/scripts/check-lifecycle-doc-drift.js
82
81
  node .agents/scripts/generate-config-docs.js --check
83
82
  node .agents/scripts/generate-lifecycle-docs.js --check
84
83
  node .agents/scripts/generate-workflows-doc.js --check
@@ -87,7 +86,7 @@ node .agents/scripts/resolve-doc-tiers.js --json
87
86
 
88
87
  Fold the results in as findings:
89
88
 
90
- - **Checker failures** (broken links, lifecycle drift) become individual
89
+ - **Checker failures** (broken links, generator drift) become individual
91
90
  findings with `Category: Link Integrity` (or `Generator Drift` for the
92
91
  lifecycle gate), citing the checker output verbatim.
93
92
  - **Generator dirtiness** (any `--check` reporting stale output, including
@@ -189,6 +189,32 @@ Labels applied:
189
189
  (cross-audit groups carry multiple).
190
190
  - `risk::high` — added when any finding in the group is Critical.
191
191
 
192
+ ### Phase 5c — Wire the cohort's declared ordering (**required**)
193
+
194
+ Creating the Issues is only the first pass. `groupFindings` detects `edges[]`
195
+ between groups, but at emit time no group has an issue number, so each body
196
+ ships with an empty `depends_on` and the cohort has **no declared ordering
197
+ at all**. Replay the numbers you just opened:
198
+
199
+ ```bash
200
+ node .agents/scripts/audit-to-stories.js --wire-edges \
201
+ --plan temp/audits/audit-to-stories-plan.json \
202
+ --ids '{"<groupKey>": <issueNumber>, ...}' \
203
+ --out temp/audits/audit-to-stories-wired.json
204
+ ```
205
+
206
+ Each entry in the emitted `--json` payload carries its own `groupKey` and
207
+ `dependsOn`, so the map is a lookup, not a reconstruction. The pass re-renders
208
+ every Story that has a resolvable blocker with a canonical
209
+ `---` / `blocked by #N` footer **and** mirrors the same edges as native
210
+ GitHub `blocked_by` relations. An edge whose target was never opened (deduped,
211
+ ledger-suppressed) drops rather than becoming a `blocked by #undefined`.
212
+
213
+ **Do not skip this.** `/deliver` has no other source for this cohort's order:
214
+ its footprint guard ignores the shared provenance footers, so an unwired cohort
215
+ is genuinely unordered and `/deliver` will co-dispatch Stories the edges say
216
+ must follow one another.
217
+
192
218
  ## Phase 6 — Idempotency (folded into Phase 1 scan)
193
219
 
194
220
  The `--scan` step routes each group's findings through the shared
@@ -227,8 +253,8 @@ runs FIRST and widens the net across open + closed issues; the exact
227
253
  was reworded but whose *location* is unchanged still confirms against the Issue
228
254
  that already tracks that location, because the audit filers stamp a
229
255
  location-based `audit-semantic-keys` footer alongside the `audit-fingerprints`
230
- footer. Close-time filings from the
231
- [`audit-results-graduator`](../scripts/lib/feedback-loop/audit-results-graduator.js)
256
+ footer. Filings from the
257
+ [`retro-proposals-graduator`](../scripts/lib/feedback-loop/retro-proposals-graduator.js)
232
258
  carry the same canonical `audit-fingerprints` footer, so a sweep recognizes a
233
259
  graduator-filed issue and never re-files it.
234
260
 
@@ -229,18 +229,17 @@ which path produced it.
229
229
  > stage before synthesising the report. It derives its per-dimension prompts
230
230
  > from the *lens* markdown at run time — the lens stays the single source of
231
231
  > truth. This is a performance optimization over path 1, **not** a separate
232
- > contract: strategy selection lives in
233
- > [`../../scripts/lib/dynamic-workflow/capability.js`](../../scripts/lib/dynamic-workflow/capability.js)
234
- > (`selectAuditStrategy`), and it is not covered by the No-Shim / hard-cutover
235
- > rule in [`../../rules/git-conventions.md`](../../rules/git-conventions.md)
236
- > because there is one report contract and only the execution strategy varies —
237
- > the same capability-degradation pattern the protocol endorses for live-docs
238
- > fallback. Force a path for testing with `MANDREL_AUDIT_STRATEGY=sequential`
239
- > or `MANDREL_AUDIT_STRATEGY=orchestrated`; exercise the real disable signals
240
- > with `CLAUDE_CODE_DISABLE_WORKFLOWS=1` or `disableWorkflows: true` in
241
- > `.claude/settings.json`. On the orchestrated path the analysis subagents are
242
- > granted only read/search tools (`Read`, `Grep`, `Glob`) — the single write is
243
- > the final report artifact.
232
+ > contract, and it is not covered by the No-Shim / hard-cutover rule in
233
+ > [`../../rules/git-conventions.md`](../../rules/git-conventions.md) because
234
+ > there is one report contract and only the execution strategy varies — the
235
+ > same capability-degradation pattern the protocol endorses for live-docs
236
+ > fallback. **The host owns the choice.** Mandrel ships no in-repo strategy
237
+ > selector and no force-override env var: Claude Code launches the saved
238
+ > workflow when it can, and you get path 1 or 2 above when it cannot.
239
+ > Suppress the orchestrated path with `CLAUDE_CODE_DISABLE_WORKFLOWS=1`
240
+ > or `disableWorkflows: true` in `.claude/settings.json`. On the orchestrated
241
+ > path the analysis subagents are granted only read/search tools (`Read`,
242
+ > `Grep`, `Glob`) — the single write is the final report artifact.
244
243
 
245
244
  ## Parallel tooling {#parallel-tooling}
246
245
 
@@ -95,17 +95,18 @@ suppress the noise.
95
95
 
96
96
  ## When a number changes
97
97
 
98
- Update three places **in the same commit**:
99
-
100
- 1. `delivery.quality.codingGuardrails.<key>` in
101
- [`agentrc-reference.json`](../../docs/agentrc-reference.json).
102
- 2. The matching schema bound in
103
- [`schemas/agentrc.schema.json`](../../schemas/agentrc.schema.json) and the
104
- AJV mirror in
105
- [`scripts/lib/config-settings-schema.js`](../../scripts/lib/config-settings-schema.js).
106
- 3. The threshold cell or sentence in this helper.
107
-
108
- The drift test (`tests/config-schema-mirror-drift.test.js`) catches schema
109
- divergence between the JSON mirror and the AJV runtime; the helper-prose
110
- drift is caught by `audit-clean-code` and `agent-protocol` linking back here
111
- rather than restating the numbers.
98
+ Update two places **in the same commit**:
99
+
100
+ 1. `CODING_GUARDRAILS_DEFAULTS` in
101
+ [`scripts/lib/config/quality.js`](../../scripts/lib/config/quality.js).
102
+ The schema literal in
103
+ [`config-settings-schema-quality.js`](../../scripts/lib/config-settings-schema-quality.js)
104
+ imports it, and `npm run docs:gen` propagates the number into
105
+ [`agentrc-reference.json`](../../docs/agentrc-reference.json), the shipped
106
+ JSON-Schema mirror, and the `configuration.md` key table.
107
+ 2. The threshold cell or sentence in this helper.
108
+
109
+ The generator-fidelity test (`tests/config-schema-mirror-drift.test.js`)
110
+ catches a stale generated artifact; the helper-prose drift is caught by
111
+ `audit-clean-code` and `agent-protocol` linking back here rather than
112
+ restating the numbers.
@@ -293,14 +293,9 @@ For every finding, provide:
293
293
 
294
294
  Findings that Step 4.5 remediated on `[HEAD_REF]` MUST be rendered under a
295
295
  dedicated **`## Fixed on-branch`** heading, **not** in the severity groups
296
- above. This is the contract seam that keeps remediated findings from
297
- spawning ghost follow-up issues: the
298
- [audit-results graduator](../../scripts/lib/feedback-loop/audit-results-graduator.js)
299
- (the sole canonical reader of the unified comment)
300
- skips every entry inside this section (both because a fixed entry is
301
- rendered with a **✅ prefix** — so it carries no leading severity emoji the
302
- parser would match — and because the parser has an explicit
303
- Fixed-on-branch section guard).
296
+ above. This keeps a remediated finding legible as remediated to every reader
297
+ of the unified comment — human or downstream — rather than reading as an
298
+ outstanding severity-grouped finding.
304
299
 
305
300
  Render each fixed finding as a `✅`-prefixed line naming its original
306
301
  severity, the file path in backticks, and the remediating commit SHA, e.g.:
@@ -94,7 +94,7 @@ probe logs a warning and leans on init's lease refusal alone.
94
94
  **Overlapping footprints are reserved across beats, not just within one.** A
95
95
  Story sharing a **concrete** path with a still-implementing Story is withheld
96
96
  and named in `inFlightReservation: { available, withheld: [{ id, blockedBy,
97
- reason }], note }`, where `reason` is `in-flight-earlier-beat` or
97
+ reason, source, paths }], note }`, where `reason` is `in-flight-earlier-beat` or
98
98
  `foreign-lease`. Like `foreignHeld` this is neither a failure nor a wedge — the
99
99
  Story re-admits automatically once its blocker leaves the in-flight set — and
100
100
  it exists so an unfilled slot is explained rather than mysterious. A **glob**
@@ -103,6 +103,33 @@ across beats; it still serializes its own beat. Reservation needs the in-flight
103
103
  Stories' footprints, so it is a `--probe-live` capability: under `--dag` the
104
104
  report is `available: false` and selection de-conflicts within the beat only.
105
105
 
106
+ **Beat-local skips are reported too**, in `footprintGuard: { mode, withheld,
107
+ advisory, note }`. They used to be an unreported skip, so a Story simply
108
+ vanished from `ready[]` and an unfilled slot read exactly like a cap that was
109
+ never reached. Every entry in **either** report carries the colliding `paths`
110
+ and a `source` tag:
111
+
112
+ - `declared-overlap` — both Stories' `changes[]` named the path (or a declared
113
+ glob). Intended serialization; two Stories rewriting the same generated
114
+ baseline must not co-dispatch.
115
+ - `scraped-overlap` — only the text evidence produced it. Real signal — a
116
+ declaration is only a lower bound — but the class where a false positive is
117
+ possible.
118
+
119
+ **The evidence scrape excludes exactly three token sources**, each structurally
120
+ incapable of naming an edit target: `audit-fingerprints` /
121
+ `audit-semantic-keys` provenance footers, paths under `project.paths.tempRoot`,
122
+ and markdown-link URL interiors. Nothing else is stripped — a
123
+ `<!-- DECOMPOSITION -->` block's paths are genuine intent
124
+ ([`instructions.md` § 7](../../instructions.md)) and still count.
125
+
126
+ **`delivery.deliverRunner.footprintGuard`** selects what a collision does:
127
+
128
+ | Mode | Effect |
129
+ | --- | --- |
130
+ | `enforce` (default) | A collision withholds the Story. Keep this unless you have a reason — the guard encodes delivery-time-only knowledge (open implementation windows, foreign leases, ground moved since planning) no `depends_on` edge can carry. |
131
+ | `advisory` | Collisions are still **detected** and listed in `footprintGuard.advisory`, but never withhold; dispatch follows the declared `depends_on` edges alone. A throughput trade for a run whose ordering is fully declared. |
132
+
106
133
  ## Dispatch mechanics (role-scoped by default)
107
134
 
108
135
  **A single-Story run executes inline.** Sub-agent isolation is
@@ -118,7 +145,8 @@ engine runs, never what runs — gates, PR, and terminal envelope are identical.
118
145
  **Read the mode; never infer it from shape.** Before spawning
119
146
  anything, read the Story's `dispatchMode` from the resolver envelope
120
147
  (`stories[].dispatchMode`, produced by `resolveStoryDispatchMode` in
121
- `lib/orchestration/complexity-gate.js`). A Story with `dispatchMode: "inline"`
148
+ `lib/orchestration/complexity-gate.js`, which decides on the resolved set size
149
+ alone — it does not read the Story body). A Story with `dispatchMode: "inline"`
122
150
  executes [`deliver-story.md`](deliver-story.md) **inline in this session** — no
123
151
  `story-worker` sub-agent boot and no fresh acceptance-critic sub-agents
124
152
  (sub-agent boots are the dominant deliver-phase token cost at trivial scope) —