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
@@ -0,0 +1,362 @@
1
+ #!/usr/bin/env node
2
+
3
+ // .agents/scripts/check-baseline-scope.js — Story #5012.
4
+ //
5
+ // Assert that every committed baseline's ROW SET still describes the tree.
6
+ //
7
+ // `check-baselines.js` answers "did a measured value regress?" and answers it
8
+ // well. Nothing answered the prior question: does this baseline still measure
9
+ // the right files? A row can point at a file deleted months ago, and an
10
+ // in-scope file can carry no row at all, while every gate stays green — a
11
+ // ratchet is perfectly capable of being green over almost nothing.
12
+ //
13
+ // This gate is measurement-free by construction. It runs no scorer, needs no
14
+ // coverage artifact and no test run: it walks the directories the gate's own
15
+ // config names (`lib/baselines/scope-inventory.js`) and compares that set
16
+ // against the committed rows in both directions
17
+ // (`lib/baselines/scope-assert.js`). That is what makes it cheap enough to be
18
+ // a required check, and what makes its companion —
19
+ // `prune-baseline-orphans.js` — a one-command remedy rather than a full
20
+ // re-score.
21
+ //
22
+ // Exit codes:
23
+ // 0 no fatal divergence (inherited divergence may still be warned about)
24
+ // 1 fatal divergence — attributable to this change set, or strict mode
25
+ // 2 the check could not run (unreadable config, unusable repository)
26
+
27
+ // Fail-fast if the framework's runtime deps are not installed — must be the
28
+ // first import so the check runs before any third-party-importing sibling
29
+ // module is evaluated (Story #3432).
30
+ import './lib/runtime-deps/ensure-installed.js';
31
+ import fs from 'node:fs';
32
+ import path from 'node:path';
33
+ import { _internals as readerInternals } from './lib/baselines/reader.js';
34
+ import {
35
+ assertScope,
36
+ attributeDivergence,
37
+ resolveStrictness,
38
+ } from './lib/baselines/scope-assert.js';
39
+ import {
40
+ buildScopeInventory,
41
+ SCOPE_KINDS,
42
+ } from './lib/baselines/scope-inventory.js';
43
+ import { runAsCli } from './lib/cli-utils.js';
44
+ import { getQuality } from './lib/config/quality.js';
45
+ import { resolveConfig } from './lib/config-resolver.js';
46
+ import { gitSpawn } from './lib/git-utils.js';
47
+
48
+ const EXIT_PASS = 0;
49
+ const EXIT_DIVERGED = 1;
50
+ const EXIT_CANNOT_RUN = 2;
51
+
52
+ /**
53
+ * Files whose edit makes merge-base attribution untrustworthy: once a branch
54
+ * has rewritten the scope rules themselves, "which side of the merge-base
55
+ * introduced this row" is no longer a question the diff can answer.
56
+ */
57
+ const SCOPE_CONFIG_PATHS = Object.freeze([
58
+ '.c8rc.cjs',
59
+ '.agentrc.json',
60
+ '.agentrc.local.json',
61
+ ]);
62
+
63
+ const HELP = {
64
+ invocation:
65
+ 'node .agents/scripts/check-baseline-scope.js [--kind <kind>] [--base <ref>] [--strict] [--json]',
66
+ summary:
67
+ "Assert each committed baseline's row set still describes the tree — in-scope files with no row, and rows whose file is gone or out of scope.",
68
+ flags: [
69
+ ['--kind <kind>', 'Check one kind only (repeatable). Default: all.'],
70
+ ['--base <ref>', 'Attribution base ref. Default: origin/<baseBranch>.'],
71
+ ['--strict', 'Treat every divergence as fatal, skipping attribution.'],
72
+ ['--json', 'Emit the report as JSON instead of text.'],
73
+ ['--cwd <dir>', 'Repository root to check. Default: process.cwd().'],
74
+ ],
75
+ notes: [
76
+ 'Exit codes:\n 0 no fatal divergence\n 1 fatal divergence\n 2 the check could not run',
77
+ 'Remedy for a stale row: node .agents/scripts/prune-baseline-orphans.js',
78
+ ],
79
+ };
80
+
81
+ /**
82
+ * Parse argv into an options bag. Unknown flags are a config error rather than
83
+ * a silent no-op — a typo'd `--kinds` must not read as "check everything".
84
+ *
85
+ * @param {string[]} argv
86
+ * @returns {{ kinds: string[], base: string | null, strict: boolean, json: boolean, cwd: string }}
87
+ */
88
+ export function parseArgs(argv = []) {
89
+ const out = { kinds: [], base: null, strict: false, json: false, cwd: null };
90
+ let i = 0;
91
+ while (i < argv.length) {
92
+ const arg = argv[i];
93
+ const value = argv[i + 1];
94
+ i += 1;
95
+ if (arg === '--strict') out.strict = true;
96
+ else if (arg === '--json') out.json = true;
97
+ else if (arg === '--kind') {
98
+ out.kinds.push(value);
99
+ i += 1;
100
+ } else if (arg === '--base') {
101
+ out.base = value;
102
+ i += 1;
103
+ } else if (arg === '--cwd') {
104
+ out.cwd = value;
105
+ i += 1;
106
+ } else throw new Error(`unknown flag "${arg}" (try --help)`);
107
+ }
108
+ const unknown = out.kinds.filter((k) => !SCOPE_KINDS.includes(k));
109
+ if (unknown.length > 0) {
110
+ throw new Error(
111
+ `unknown --kind ${unknown.join(', ')}; expected one of ${SCOPE_KINDS.join(', ')}`,
112
+ );
113
+ }
114
+ if (out.kinds.length === 0) out.kinds = [...SCOPE_KINDS];
115
+ out.cwd = out.cwd ?? process.cwd();
116
+ return out;
117
+ }
118
+
119
+ /**
120
+ * Resolve the merge base between HEAD and the requested base ref, preferring
121
+ * the remote-tracking ref so a stale local `main` cannot widen attribution.
122
+ *
123
+ * Returns `{ base: null }` when no candidate resolves — the strictness
124
+ * resolver treats that as a reason to fail towards strict, never as a licence
125
+ * to skip.
126
+ *
127
+ * @param {{ cwd: string, baseRef: string }} params
128
+ * @returns {{ base: string | null, aheadOfBase: boolean }}
129
+ */
130
+ function resolveMergeBase({ cwd, baseRef }) {
131
+ for (const candidate of [`origin/${baseRef}`, baseRef]) {
132
+ const merged = gitSpawn(cwd, 'merge-base', candidate, 'HEAD');
133
+ if (merged.status !== 0 || merged.stdout.length === 0) continue;
134
+ const head = gitSpawn(cwd, 'rev-parse', 'HEAD');
135
+ return {
136
+ base: merged.stdout,
137
+ aheadOfBase: head.status === 0 && head.stdout !== merged.stdout,
138
+ };
139
+ }
140
+ return { base: null, aheadOfBase: false };
141
+ }
142
+
143
+ /**
144
+ * Enumerate what `<base>..HEAD` did to the tree, split into the two sets
145
+ * attribution needs. A rename contributes its old path to `removed` and its
146
+ * new path to `added`, which is exactly how a rename strands a row.
147
+ *
148
+ * @param {{ cwd: string, base: string | null }} params
149
+ * @returns {{ added: string[], removed: string[], changed: string[] }}
150
+ */
151
+ function readChangeSet({ cwd, base }) {
152
+ const empty = { added: [], removed: [], changed: [] };
153
+ if (base === null) return empty;
154
+ const diff = gitSpawn(cwd, 'diff', '--name-status', '-M', `${base}..HEAD`);
155
+ if (diff.status !== 0) return empty;
156
+ const added = [];
157
+ const removed = [];
158
+ const changed = [];
159
+ for (const line of diff.stdout.split('\n').filter(Boolean)) {
160
+ const [code, first, second] = line.split('\t');
161
+ const letter = code[0];
162
+ if (letter === 'R' || letter === 'C') {
163
+ removed.push(first);
164
+ added.push(second);
165
+ changed.push(first, second);
166
+ continue;
167
+ }
168
+ if (letter === 'A') added.push(first);
169
+ if (letter === 'D') removed.push(first);
170
+ changed.push(first);
171
+ }
172
+ return { added, removed, changed };
173
+ }
174
+
175
+ /**
176
+ * Repo-relative paths of every baseline file in play — the "did this change
177
+ * set edit a baseline?" input to the strictness resolver.
178
+ *
179
+ * @param {{ cwd: string, kinds: string[] }} params
180
+ * @returns {string[]}
181
+ */
182
+ function baselinePathsFor({ cwd, kinds }) {
183
+ return kinds.map((kind) =>
184
+ path
185
+ .relative(cwd, readerInternals.resolveBaselinePath(kind, { cwd }))
186
+ .split(path.sep)
187
+ .join('/'),
188
+ );
189
+ }
190
+
191
+ /**
192
+ * Read a baseline's rows, or `null` when the kind ships no baseline in this
193
+ * repository. Deliberately does NOT go through `reader.load`: an envelope too
194
+ * stale to satisfy its schema is exactly the state this gate exists to report,
195
+ * and refusing to read it would turn the report into a crash.
196
+ *
197
+ * @param {{ cwd: string, kind: string }} params
198
+ * @returns {Array<object> | null}
199
+ */
200
+ function readRows({ cwd, kind }) {
201
+ const abs = readerInternals.resolveBaselinePath(kind, { cwd });
202
+ try {
203
+ const parsed = JSON.parse(fs.readFileSync(abs, 'utf8'));
204
+ return Array.isArray(parsed?.rows) ? parsed.rows : [];
205
+ } catch {
206
+ return null;
207
+ }
208
+ }
209
+
210
+ /**
211
+ * Assess one kind: inventory → both-directions assertion → attribution.
212
+ *
213
+ * @param {object} params
214
+ * @returns {object} One entry of the report's `kinds` array.
215
+ */
216
+ function assessKind({ kind, cwd, quality, strict, changeSet }) {
217
+ const rows = readRows({ cwd, kind });
218
+ if (rows === null) {
219
+ return { kind, present: false, skipped: true, reason: 'no baseline file' };
220
+ }
221
+ const inventory = buildScopeInventory({ kind, cwd, quality });
222
+ const found = assertScope({
223
+ inventory,
224
+ rows,
225
+ existsOnDisk: (rel) => fs.existsSync(path.resolve(cwd, rel)),
226
+ });
227
+ if (found.skipped) return { kind, present: true, ...found };
228
+ const attributed = attributeDivergence({
229
+ missing: found.missing,
230
+ extra: found.extra,
231
+ added: changeSet.added,
232
+ removed: changeSet.removed,
233
+ strict,
234
+ });
235
+ return {
236
+ kind,
237
+ present: true,
238
+ skipped: false,
239
+ reason: null,
240
+ inScopeCount: inventory.files.length,
241
+ rowCount: rows.length,
242
+ ...attributed,
243
+ };
244
+ }
245
+
246
+ /**
247
+ * Run the whole assessment and return a report plus its exit code.
248
+ *
249
+ * @param {{ argv?: string[] }} [params]
250
+ * @returns {{ report: object, exitCode: number }}
251
+ */
252
+ export function runScopeCheck({ argv = [] } = {}) {
253
+ const opts = parseArgs(argv);
254
+ const cwd = opts.cwd;
255
+ const config = resolveConfig({ cwd });
256
+ const quality = getQuality(config) ?? { gates: {} };
257
+ const baseRef = opts.base ?? config?.project?.baseBranch ?? 'main';
258
+ const { base, aheadOfBase } = resolveMergeBase({ cwd, baseRef });
259
+ const changeSet = readChangeSet({ cwd, base });
260
+ const strictness = opts.strict
261
+ ? { strict: true, reason: 'operator requested --strict' }
262
+ : resolveStrictness({
263
+ base,
264
+ aheadOfBase,
265
+ changedFiles: changeSet.changed,
266
+ baselinePaths: baselinePathsFor({ cwd, kinds: opts.kinds }),
267
+ scopeConfigPaths: SCOPE_CONFIG_PATHS,
268
+ });
269
+ const kinds = opts.kinds.map((kind) =>
270
+ assessKind({ kind, cwd, quality, strict: strictness.strict, changeSet }),
271
+ );
272
+ const fatalCount = kinds.reduce((sum, k) => sum + (k.fatalCount ?? 0), 0);
273
+ const report = {
274
+ schemaVersion: '1',
275
+ base,
276
+ strict: strictness.strict,
277
+ strictReason: strictness.reason,
278
+ fatalCount,
279
+ warningCount: kinds.reduce((sum, k) => sum + (k.warningCount ?? 0), 0),
280
+ kinds,
281
+ };
282
+ return { report, exitCode: fatalCount > 0 ? EXIT_DIVERGED : EXIT_PASS };
283
+ }
284
+
285
+ /**
286
+ * Render one kind's findings as indented text lines.
287
+ *
288
+ * @param {object} entry
289
+ * @returns {string[]}
290
+ */
291
+ function renderKind(entry) {
292
+ if (entry.skipped) return [` - ${entry.kind}: skipped (${entry.reason})`];
293
+ const head =
294
+ ` - ${entry.kind}: ${entry.rowCount} row(s) over ${entry.inScopeCount} ` +
295
+ `in-scope file(s) — ${entry.fatalCount} fatal, ${entry.warningCount} inherited`;
296
+ const lines = [head];
297
+ for (const file of entry.fatal.missing) {
298
+ lines.push(` ✗ missing row: ${file}`);
299
+ }
300
+ for (const row of entry.fatal.extra) {
301
+ lines.push(` ✗ stale row (${row.reason}): ${row.path}`);
302
+ }
303
+ return lines;
304
+ }
305
+
306
+ /**
307
+ * Render the whole report as text.
308
+ *
309
+ * @param {object} report
310
+ * @returns {string}
311
+ */
312
+ export function formatReport(report) {
313
+ const mode = report.strict ? 'strict' : 'attributed';
314
+ const lines = [
315
+ `[check-baseline-scope] ${report.fatalCount} fatal, ${report.warningCount} inherited ` +
316
+ `(${mode}: ${report.strictReason})`,
317
+ ...report.kinds.flatMap(renderKind),
318
+ ];
319
+ if (report.fatalCount > 0) {
320
+ lines.push(
321
+ '',
322
+ 'A baseline no longer describes the tree. Prune provably-inert rows with:',
323
+ ' node .agents/scripts/prune-baseline-orphans.js',
324
+ 'A missing row means a file was added without being measured — run that',
325
+ "kind's producer (npm run coverage:update / maintainability:update).",
326
+ );
327
+ }
328
+ return lines.join('\n');
329
+ }
330
+
331
+ /**
332
+ * CLI entry point. Returns its exit code rather than calling `process.exit()`,
333
+ * so `runAsCli`'s `propagateExitCode` path settles it through `flushStdio` and
334
+ * an unbounded report is not truncated at a pipe boundary (Story #4783).
335
+ *
336
+ * @returns {Promise<number>}
337
+ */
338
+ async function main() {
339
+ let result;
340
+ try {
341
+ result = runScopeCheck({ argv: process.argv.slice(2) });
342
+ } catch (err) {
343
+ const message = err?.message ?? String(err);
344
+ process.stdout.write(
345
+ `${JSON.stringify({ schemaVersion: '1', error: message }, null, 2)}\n`,
346
+ );
347
+ return EXIT_CANNOT_RUN;
348
+ }
349
+ const opts = { json: process.argv.includes('--json') };
350
+ process.stdout.write(
351
+ opts.json
352
+ ? `${JSON.stringify(result.report, null, 2)}\n`
353
+ : `${formatReport(result.report)}\n`,
354
+ );
355
+ return result.exitCode;
356
+ }
357
+
358
+ runAsCli(import.meta.url, main, {
359
+ source: 'check-baseline-scope',
360
+ usage: HELP,
361
+ propagateExitCode: true,
362
+ });
@@ -14,13 +14,21 @@
14
14
  * production-dead code hides behind its own tests. See
15
15
  * `lib/dead-exports-mode.js` for why the two passes carry separate baselines.
16
16
  *
17
+ * Story #5001 widens what the ratchet can see: knip's `files` category (a
18
+ * module nothing imports) now maps to a `{ file, symbol: '*' }` row alongside
19
+ * the per-export rows, and `knip.json` lists `.agents/scripts/*.js` entry
20
+ * points explicitly instead of by blanket glob — so a top-level CLI that
21
+ * nothing invokes surfaces as dead instead of being declared live by the glob.
22
+ *
17
23
  * Contract:
18
24
  * - Reads the committed baseline at `baselines/dead-exports.json` — or
19
25
  * `baselines/dead-exports-production.json` under `--production`
20
26
  * (override either with `--baseline <path>`). Envelope shape:
21
27
  * { $schema, kernelVersion, generatedAt, rows: [{ file, symbol }] }
22
28
  * - Spawns `npx knip --reporter json --no-progress` (plus `--production`),
23
- * parses stdout, extracts `{ file, symbol }` rows from `issues[].exports[]`.
29
+ * parses stdout, extracts `{ file, symbol }` rows from `issues[].exports[]`
30
+ * **and** `issues[].files[]` — the latter as one `{ file, symbol: '*' }`
31
+ * whole-file row per module nothing imports (Story #5001).
24
32
  * - Diffs current vs. baseline by `(file, symbol)` identity.
25
33
  * - Prints `+ <file>: <symbol>` for each added dead export and
26
34
  * `- <file>: <symbol>` for each removed one, then a summary line.