mandrel 2.31.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 (250) 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/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 +0 -1
  14. package/.agents/schemas/agentrc.schema.json +1939 -1400
  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 +7 -29
  19. package/.agents/scripts/apply-quality-bootstrap.js +27 -34
  20. package/.agents/scripts/bootstrap.js +28 -26
  21. package/.agents/scripts/check-baseline-drift.js +73 -13
  22. package/.agents/scripts/check-baseline-scope.js +362 -0
  23. package/.agents/scripts/check-dead-exports.js +9 -1
  24. package/.agents/scripts/check-gherkin-corpus.js +508 -0
  25. package/.agents/scripts/check-knip-entries.js +136 -0
  26. package/.agents/scripts/check-lifecycle-lint.js +36 -112
  27. package/.agents/scripts/check-schema-references.js +1 -1
  28. package/.agents/scripts/diagnose-friction.js +7 -4
  29. package/.agents/scripts/generate-config-docs.js +263 -171
  30. package/.agents/scripts/install-matrix-assert.js +0 -1
  31. package/.agents/scripts/lib/ITicketingProvider.js +0 -58
  32. package/.agents/scripts/lib/audit-baselines/staleness.js +6 -6
  33. package/.agents/scripts/lib/audit-baselines/trend.js +7 -8
  34. package/.agents/scripts/lib/audit-baselines/weights.js +4 -5
  35. package/.agents/scripts/lib/audit-suite/checklist-threading.js +1 -1
  36. package/.agents/scripts/lib/audit-to-stories/build-story-body.js +0 -1
  37. package/.agents/scripts/lib/baselines/envelope.js +41 -60
  38. package/.agents/scripts/lib/baselines/git-base.js +30 -37
  39. package/.agents/scripts/lib/baselines/kinds/_crap-new-method-gate.js +103 -0
  40. package/.agents/scripts/lib/baselines/kinds/_crap-read.js +150 -0
  41. package/.agents/scripts/lib/baselines/kinds/crap.js +25 -65
  42. package/.agents/scripts/lib/baselines/orphan-pruner.js +233 -0
  43. package/.agents/scripts/lib/baselines/refresh-service.js +6 -8
  44. package/.agents/scripts/lib/baselines/scope-assert.js +223 -0
  45. package/.agents/scripts/lib/baselines/scope-inventory.js +314 -0
  46. package/.agents/scripts/lib/bdd-step-index.js +326 -0
  47. package/.agents/scripts/lib/bootstrap/install-ledger.js +5 -3
  48. package/.agents/scripts/lib/bootstrap/issue-forms-template.js +4 -6
  49. package/.agents/scripts/lib/bootstrap/manifest.js +17 -40
  50. package/.agents/scripts/lib/bootstrap/project-bootstrap.js +12 -59
  51. package/.agents/scripts/lib/bootstrap/quality-bootstrap.js +62 -2
  52. package/.agents/scripts/lib/checks/loop-health.js +9 -37
  53. package/.agents/scripts/lib/child-exec.js +193 -0
  54. package/.agents/scripts/lib/cli/standard-args.js +1 -1
  55. package/.agents/scripts/lib/cli-args.js +64 -0
  56. package/.agents/scripts/lib/close-validation/gates.js +2 -2
  57. package/.agents/scripts/lib/close-validation/runner.js +3 -3
  58. package/.agents/scripts/lib/config/acceptance-eval.js +5 -52
  59. package/.agents/scripts/lib/config/commands.js +3 -5
  60. package/.agents/scripts/lib/config/explain.js +5 -7
  61. package/.agents/scripts/lib/config/gates/bundle-size.schema.js +32 -6
  62. package/.agents/scripts/lib/config/gates/coverage.schema.js +25 -5
  63. package/.agents/scripts/lib/config/gates/crap-incremental-coverage.schema.js +12 -2
  64. package/.agents/scripts/lib/config/gates/crap.schema.js +68 -23
  65. package/.agents/scripts/lib/config/gates/duplication.schema.js +29 -17
  66. package/.agents/scripts/lib/config/gates/index.js +5 -2
  67. package/.agents/scripts/lib/config/gates/lighthouse.schema.js +34 -6
  68. package/.agents/scripts/lib/config/gates/lint.schema.js +11 -2
  69. package/.agents/scripts/lib/config/gates/maintainability.schema.js +37 -15
  70. package/.agents/scripts/lib/config/gates/mutation.schema.js +15 -3
  71. package/.agents/scripts/lib/config/gates/shared.js +58 -9
  72. package/.agents/scripts/lib/config/github.js +0 -1
  73. package/.agents/scripts/lib/config/limits.js +3 -48
  74. package/.agents/scripts/lib/config/qa.js +105 -0
  75. package/.agents/scripts/lib/config/temp-paths.js +6 -5
  76. package/.agents/scripts/lib/config-settings-schema-delivery.js +237 -56
  77. package/.agents/scripts/lib/config-settings-schema-quality.js +209 -29
  78. package/.agents/scripts/lib/config-settings-schema.js +386 -39
  79. package/.agents/scripts/lib/crap-baseline-join.js +126 -9
  80. package/.agents/scripts/lib/crap-utils.js +84 -520
  81. package/.agents/scripts/lib/dead-exports-knip.js +79 -10
  82. package/.agents/scripts/lib/degraded-mode.js +2 -2
  83. package/.agents/scripts/lib/doc-tiers.js +3 -3
  84. package/.agents/scripts/lib/feedback-loop/graduator-core.js +46 -104
  85. package/.agents/scripts/lib/feedback-loop/retro-proposals-graduator.js +10 -8
  86. package/.agents/scripts/lib/fs-walk.js +52 -0
  87. package/.agents/scripts/lib/git-branch-lifecycle.js +2 -2
  88. package/.agents/scripts/lib/git-utils.js +16 -36
  89. package/.agents/scripts/lib/knip-entry-sync.js +469 -0
  90. package/.agents/scripts/lib/observability/metrics-ledger.js +1 -1
  91. package/.agents/scripts/lib/observability/runtime-friction.js +10 -0
  92. package/.agents/scripts/lib/observability/signal-validator.js +5 -85
  93. package/.agents/scripts/lib/observability/signals-writer.js +19 -62
  94. package/.agents/scripts/lib/observability/source-classifier.js +5 -7
  95. package/.agents/scripts/lib/observability/terse-result.js +3 -3
  96. package/.agents/scripts/lib/orchestration/behind-recovery.js +114 -0
  97. package/.agents/scripts/lib/orchestration/ceremony-routing.js +7 -8
  98. package/.agents/scripts/lib/orchestration/ci-rerun-guard.js +34 -33
  99. package/.agents/scripts/lib/orchestration/code-review.js +2 -2
  100. package/.agents/scripts/lib/orchestration/complexity-gate.js +43 -161
  101. package/.agents/scripts/lib/orchestration/diff-magnitude.js +4 -4
  102. package/.agents/scripts/lib/orchestration/label-transitions.js +3 -2
  103. package/.agents/scripts/lib/orchestration/lease-guard-shared.js +12 -38
  104. package/.agents/scripts/lib/orchestration/lifecycle/emit-merge-unlanded.js +5 -6
  105. package/.agents/scripts/lib/orchestration/plan-metrics.js +2 -3
  106. package/.agents/scripts/lib/orchestration/plan-persist/persist-helpers.js +6 -0
  107. package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +0 -1
  108. package/.agents/scripts/lib/orchestration/{lifecycle/listeners/watcher.js → pr-watch.js} +58 -208
  109. package/.agents/scripts/lib/orchestration/resolve-stories.js +5 -15
  110. package/.agents/scripts/lib/orchestration/review-providers/codex.js +1 -1
  111. package/.agents/scripts/lib/orchestration/review-providers/mi-exemptions.js +130 -0
  112. package/.agents/scripts/lib/orchestration/review-providers/native.js +30 -16
  113. package/.agents/scripts/lib/orchestration/single-story-close/phases/code-review.js +1 -1
  114. package/.agents/scripts/lib/orchestration/single-story-close/phases/confirm-merge.js +37 -26
  115. package/.agents/scripts/lib/orchestration/single-story-close/phases/conventional-subject.js +376 -0
  116. package/.agents/scripts/lib/orchestration/single-story-close/phases/normalize-pr-title.js +161 -151
  117. package/.agents/scripts/lib/orchestration/single-story-close/phases/options.js +15 -3
  118. package/.agents/scripts/lib/orchestration/single-story-close/phases/pull-request.js +10 -15
  119. package/.agents/scripts/lib/orchestration/single-story-close/phases/review-block.js +5 -0
  120. package/.agents/scripts/lib/orchestration/single-story-close/phases/review-override.js +157 -0
  121. package/.agents/scripts/lib/orchestration/single-story-close/phases/worktree-reap.js +0 -14
  122. package/.agents/scripts/lib/orchestration/single-story-close/runner.js +59 -25
  123. package/.agents/scripts/lib/orchestration/single-story-lease-guard.js +20 -31
  124. package/.agents/scripts/lib/orchestration/spec-spill.js +17 -3
  125. package/.agents/scripts/lib/orchestration/story-close/phases/review-core.js +7 -6
  126. package/.agents/scripts/lib/orchestration/story-deliver-terminal.js +2 -1
  127. package/.agents/scripts/lib/orchestration/task-body-validator.js +4 -1
  128. package/.agents/scripts/lib/orchestration/ticket-lease.js +28 -127
  129. package/.agents/scripts/lib/orchestration/ticket-validator-sizing.js +1 -1
  130. package/.agents/scripts/lib/orchestration/ticketing/reads.js +5 -5
  131. package/.agents/scripts/lib/orchestration/ticketing/transition.js +5 -4
  132. package/.agents/scripts/lib/orchestration/verify-tier-repair.js +107 -0
  133. package/.agents/scripts/lib/qa/coverage-verdict.js +5 -87
  134. package/.agents/scripts/lib/signals/detectors/common.js +1 -1
  135. package/.agents/scripts/lib/signals/index.js +8 -6
  136. package/.agents/scripts/lib/signals/schema.js +20 -25
  137. package/.agents/scripts/lib/signals/write.js +8 -8
  138. package/.agents/scripts/lib/story-body/story-body.js +12 -59
  139. package/.agents/scripts/lib/temp-retention.js +1 -1
  140. package/.agents/scripts/lib/templates/decomposer-prompts.js +16 -14
  141. package/.agents/scripts/lib/ticket-body-sections.js +4 -5
  142. package/.agents/scripts/lib/worktree/lifecycle/merge-reachability.js +13 -45
  143. package/.agents/scripts/lib/worktree/lifecycle/reap.js +4 -5
  144. package/.agents/scripts/lib/worktree-manager.js +2 -3
  145. package/.agents/scripts/lint-label-vocabulary.js +2 -24
  146. package/.agents/scripts/pr-watch-with-update.js +7 -5
  147. package/.agents/scripts/providers/github/cache.js +2 -2
  148. package/.agents/scripts/providers/github/comments.js +6 -28
  149. package/.agents/scripts/providers/github/compose.js +0 -15
  150. package/.agents/scripts/providers/github/errors.js +10 -27
  151. package/.agents/scripts/providers/github/request-helpers.js +1 -2
  152. package/.agents/scripts/providers/github/sub-issues.js +10 -218
  153. package/.agents/scripts/providers/github.js +4 -7
  154. package/.agents/scripts/prune-baseline-orphans.js +181 -0
  155. package/.agents/scripts/resolve-stories.js +0 -2
  156. package/.agents/scripts/run-lint.js +61 -61
  157. package/.agents/scripts/run-test-profile.js +6 -6
  158. package/.agents/scripts/run-verify.js +48 -30
  159. package/.agents/scripts/single-story-close.js +20 -0
  160. package/.agents/scripts/single-story-init.js +12 -35
  161. package/.agents/scripts/update-dead-exports-baseline.js +321 -0
  162. package/.agents/skills/core/gates-and-baselines/SKILL.md +2 -2
  163. package/.agents/skills/skills.index.json +1 -11
  164. package/.agents/workflows/audit-documentation.md +5 -6
  165. package/.agents/workflows/audit-to-stories.md +2 -2
  166. package/.agents/workflows/helpers/audit-lens-core.md +11 -12
  167. package/.agents/workflows/helpers/code-quality-guardrails.md +15 -14
  168. package/.agents/workflows/helpers/code-review.md +3 -8
  169. package/.agents/workflows/helpers/deliver-reference.md +2 -1
  170. package/.agents/workflows/helpers/deliver-story-reference.md +27 -16
  171. package/.agents/workflows/helpers/worktree-lifecycle.md +1 -2
  172. package/.agents/workflows/mandrel-update.md +10 -10
  173. package/.agents/workflows/qa-assist.md +15 -20
  174. package/.agents/workflows/qa-explore.md +9 -8
  175. package/README.md +1 -1
  176. package/docs/CHANGELOG.md +42 -0
  177. package/lib/migrations/index.js +2 -0
  178. package/lib/migrations/steps/2.32.0-retire-lint-baseline-command.js +127 -0
  179. package/package.json +12 -3
  180. package/.agents/schemas/lifecycle/checkpoint.written.schema.json +0 -13
  181. package/.agents/schemas/lifecycle/close-validate.end.schema.json +0 -18
  182. package/.agents/schemas/lifecycle/close-validate.start.schema.json +0 -13
  183. package/.agents/schemas/lifecycle/code-review.end.schema.json +0 -30
  184. package/.agents/schemas/lifecycle/code-review.start.schema.json +0 -12
  185. package/.agents/schemas/lifecycle/intervention.recorded.schema.json +0 -15
  186. package/.agents/schemas/lifecycle/loop.tick.schema.json +0 -20
  187. package/.agents/schemas/lifecycle/notification.emitted.schema.json +0 -18
  188. package/.agents/schemas/lifecycle/pr.created.schema.json +0 -14
  189. package/.agents/schemas/lifecycle/retro.end.schema.json +0 -16
  190. package/.agents/schemas/lifecycle/retro.start.schema.json +0 -12
  191. package/.agents/schemas/lifecycle/story.blocked.schema.json +0 -13
  192. package/.agents/schemas/lifecycle/story.dispatch.end.schema.json +0 -17
  193. package/.agents/schemas/lifecycle/story.dispatch.start.schema.json +0 -15
  194. package/.agents/schemas/lifecycle/story.merged.schema.json +0 -13
  195. package/.agents/scripts/check-gherkin-placeholders.js +0 -663
  196. package/.agents/scripts/check-lifecycle-doc-drift.js +0 -411
  197. package/.agents/scripts/lib/audit-suite/cli.js +0 -64
  198. package/.agents/scripts/lib/bootstrap/baselines-layout-migration.js +0 -202
  199. package/.agents/scripts/lib/bootstrap/ci-workflow-template.js +0 -212
  200. package/.agents/scripts/lib/checks/baseline-drift-main-checkout.js +0 -104
  201. package/.agents/scripts/lib/checks/push-hook-parity.js +0 -106
  202. package/.agents/scripts/lib/checks/windows-coverage-noise-floor.js +0 -92
  203. package/.agents/scripts/lib/checks/worktree-bootstrap-env.js +0 -81
  204. package/.agents/scripts/lib/checks/worktree-residue-biome.js +0 -55
  205. package/.agents/scripts/lib/crap-baseline-index.js +0 -46
  206. package/.agents/scripts/lib/crap-utils-incremental.js +0 -113
  207. package/.agents/scripts/lib/dynamic-workflow/capability.js +0 -396
  208. package/.agents/scripts/lib/feedback-loop/audit-results-graduator.js +0 -335
  209. package/.agents/scripts/lib/mutation/baseline-snapshot.js +0 -239
  210. package/.agents/scripts/lib/mutation/config-detector.js +0 -119
  211. package/.agents/scripts/lib/mutation/stryker-runner.js +0 -306
  212. package/.agents/scripts/lib/mutation/survivor-report.js +0 -160
  213. package/.agents/scripts/lib/observability/active-story-env.js +0 -170
  214. package/.agents/scripts/lib/observability/tool-trace-hook.js +0 -456
  215. package/.agents/scripts/lib/orchestration/acceptance-clusters.js +0 -111
  216. package/.agents/scripts/lib/orchestration/context-envelope.js +0 -277
  217. package/.agents/scripts/lib/orchestration/detectors-phase.js +0 -194
  218. package/.agents/scripts/lib/orchestration/lifecycle/bus.js +0 -309
  219. package/.agents/scripts/lib/orchestration/lifecycle/emit-loop-tick.js +0 -181
  220. package/.agents/scripts/lib/orchestration/lifecycle/ledger-writer.js +0 -229
  221. package/.agents/scripts/lib/orchestration/lifecycle/listeners/README.md +0 -54
  222. package/.agents/scripts/lib/orchestration/lifecycle/trace-logger.js +0 -344
  223. package/.agents/scripts/lib/orchestration/lint-baseline-service.js +0 -114
  224. package/.agents/scripts/lib/orchestration/pr-base-guard.js +0 -37
  225. package/.agents/scripts/lib/orchestration/resolves-token.js +0 -127
  226. package/.agents/scripts/lib/orchestration/spec-section-validator.js +0 -130
  227. package/.agents/scripts/lib/orchestration/story-close/emit-blocked.js +0 -55
  228. package/.agents/scripts/lib/orchestration/story-close/phases/code-review.js +0 -211
  229. package/.agents/scripts/lib/planning-corpus.js +0 -37
  230. package/.agents/scripts/lib/qa/coverage-report.js +0 -181
  231. package/.agents/scripts/lib/qa/propose-missing-test.js +0 -95
  232. package/.agents/scripts/lib/qa/qa-context-hydrator.js +0 -217
  233. package/.agents/scripts/lib/signals/detectors/index.js +0 -14
  234. package/.agents/scripts/lib/signals/detectors/retry.js +0 -253
  235. package/.agents/scripts/lib/signals/detectors/rework.js +0 -167
  236. package/.agents/scripts/lib/signals/read.js +0 -268
  237. package/.agents/scripts/lib/signals/span-tree.js +0 -291
  238. package/.agents/scripts/lib/story-lifecycle.js +0 -194
  239. package/.agents/scripts/lib/story-plan.js +0 -379
  240. package/.agents/scripts/lib/util/phase-timer-state.js +0 -72
  241. package/.agents/scripts/lib/util/phase-timer.js +0 -163
  242. package/.agents/scripts/lib/workers/combined-mi-crap-worker.js +0 -169
  243. package/.agents/scripts/lint-baseline.js +0 -507
  244. package/.agents/scripts/providers/github/prs.js +0 -103
  245. package/.agents/scripts/signals-view.js +0 -309
  246. package/.agents/scripts/story-plan.js +0 -370
  247. package/.agents/scripts/sync-branch-from-base.js +0 -149
  248. package/.agents/scripts/validate-docs-freshness.js +0 -314
  249. package/.agents/skills/core/diagnose-friction/SKILL.md +0 -78
  250. package/.agents/workflows/helpers/signals.md +0 -112
@@ -67,7 +67,6 @@ const FRAMEWORK_RUNTIME_DEPS = [
67
67
  'js-yaml',
68
68
  'minimatch',
69
69
  'picomatch',
70
- 'string-argv',
71
70
  'typhonjs-escomplex',
72
71
  ];
73
72
 
@@ -112,17 +112,6 @@ export class ITicketingProvider {
112
112
  throw new Error('Not implemented: getTicketDependencies');
113
113
  }
114
114
 
115
- /**
116
- * Fetch recent comments across the repository.
117
- * Useful for auditing and visualization of agent telemetry.
118
- *
119
- * @param {number} [limit=100]
120
- * @returns {Promise<object[]>}
121
- */
122
- async getRecentComments(_limit = 100) {
123
- throw new Error('Not implemented: getRecentComments');
124
- }
125
-
126
115
  /**
127
116
  * Fetch all comments for a specific ticket.
128
117
  *
@@ -137,42 +126,6 @@ export class ITicketingProvider {
137
126
  // Write Operations
138
127
  // ---------------------------------------------------------------------------
139
128
 
140
- /**
141
- * Create a child ticket within an Epic's structural hierarchy.
142
- *
143
- * @param {number} parentId - GitHub Issue number of the immediate structural parent (e.g. Epic or Story).
144
- * @param {{
145
- * epicId: number,
146
- * title: string,
147
- * body: string,
148
- * labels: string[],
149
- * dependencies: number[]
150
- * }} ticketData - The ticket content and metadata.
151
- * @returns {Promise<{ id: number, url: string }>}
152
- */
153
-
154
- /**
155
- * Link an existing issue as a sub-issue of a parent.
156
- *
157
- * @param {number} parentId - GitHub Issue number of the parent.
158
- * @param {number} childId - GitHub internal database ID of the sub-issue.
159
- * @returns {Promise<void>}
160
- */
161
- async addSubIssue(_parentId, _childId) {
162
- throw new Error('Not implemented: addSubIssue');
163
- }
164
-
165
- /**
166
- * Remove a sub-issue link from a parent.
167
- *
168
- * @param {number} parentId - GitHub Issue number of the parent.
169
- * @param {number} childId - GitHub internal database ID of the sub-issue.
170
- * @returns {Promise<void>}
171
- */
172
- async removeSubIssue(_parentId, _childId) {
173
- throw new Error('Not implemented: removeSubIssue');
174
- }
175
-
176
129
  /**
177
130
  * Mutate labels, body (tasklist checkboxes), and assignees on a ticket.
178
131
  *
@@ -213,17 +166,6 @@ export class ITicketingProvider {
213
166
  throw new Error('Not implemented: deleteComment');
214
167
  }
215
168
 
216
- /**
217
- * Open a Pull Request linking the specified ticket.
218
- *
219
- * @param {string} branchName - The source branch for the PR.
220
- * @param {number} ticketId - GitHub Issue number to link.
221
- * @returns {Promise<{ number: number, url: string, htmlUrl: string }>}
222
- */
223
- async createPullRequest(_branchName, _ticketId) {
224
- throw new Error('Not implemented: createPullRequest');
225
- }
226
-
227
169
  // ---------------------------------------------------------------------------
228
170
  // Setup Operations (used by bootstrap)
229
171
  // ---------------------------------------------------------------------------
@@ -16,25 +16,25 @@
16
16
  * @module lib/audit-baselines/staleness
17
17
  */
18
18
 
19
- import { execFileSync } from 'node:child_process';
19
+ import { execFileCapture } from '../child-exec.js';
20
20
  import { KIND_SPECS } from './kinds.js';
21
21
  import { ageInDays } from './read.js';
22
22
 
23
23
  /**
24
24
  * Run a git command under `cwd`, returning trimmed stdout, or `null` on any
25
- * failure or empty result.
25
+ * failure or empty result. Buffer and shell policy come from the shared
26
+ * child-process surface ([`child-exec.js`](../child-exec.js)).
26
27
  *
27
28
  * @param {string[]} args
28
29
  * @param {{ cwd: string, run?: Function }} io
29
30
  * @returns {string | null}
30
31
  */
31
- function git(args, { cwd, run = execFileSync }) {
32
+ function git(args, { cwd, run }) {
32
33
  try {
33
34
  const stdout = String(
34
- run('git', args, {
35
+ execFileCapture('git', args, {
36
+ run,
35
37
  cwd,
36
- encoding: 'utf8',
37
- maxBuffer: 16 * 1024 * 1024,
38
38
  stdio: ['ignore', 'pipe', 'ignore'],
39
39
  }),
40
40
  ).trim();
@@ -16,7 +16,7 @@
16
16
  * @module lib/audit-baselines/trend
17
17
  */
18
18
 
19
- import { execFileSync } from 'node:child_process';
19
+ import { execFileCapture } from '../child-exec.js';
20
20
  import { trendRollupOf } from './kinds.js';
21
21
 
22
22
  /**
@@ -25,13 +25,13 @@ import { trendRollupOf } from './kinds.js';
25
25
  * @param {{ cwd: string, relPath: string, limit: number, run?: Function }} args
26
26
  * @returns {Array<{ sha: string, committedAt: string }>}
27
27
  */
28
- function listCommits({ cwd, relPath, limit, run = execFileSync }) {
28
+ function listCommits({ cwd, relPath, limit, run }) {
29
29
  let stdout;
30
30
  try {
31
- stdout = run(
31
+ stdout = execFileCapture(
32
32
  'git',
33
33
  ['log', `-n${limit}`, '--format=%H %cI', '--', relPath],
34
- { cwd, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] },
34
+ { run, cwd, stdio: ['ignore', 'pipe', 'ignore'] },
35
35
  );
36
36
  } catch {
37
37
  return [];
@@ -52,13 +52,12 @@ function listCommits({ cwd, relPath, limit, run = execFileSync }) {
52
52
  * @param {{ cwd: string, kind: string, sha: string, relPath: string, run?: Function }} args
53
53
  * @returns {object | null}
54
54
  */
55
- function rollupAt({ cwd, kind, sha, relPath, run = execFileSync }) {
55
+ function rollupAt({ cwd, kind, sha, relPath, run }) {
56
56
  let stdout;
57
57
  try {
58
- stdout = run('git', ['show', `${sha}:${relPath}`], {
58
+ stdout = execFileCapture('git', ['show', `${sha}:${relPath}`], {
59
+ run,
59
60
  cwd,
60
- encoding: 'utf8',
61
- maxBuffer: 64 * 1024 * 1024,
62
61
  stdio: ['ignore', 'pipe', 'ignore'],
63
62
  });
64
63
  } catch {
@@ -16,9 +16,9 @@
16
16
  * @module lib/audit-baselines/weights
17
17
  */
18
18
 
19
- import { execFileSync } from 'node:child_process';
20
19
  import fs from 'node:fs';
21
20
  import path from 'node:path';
21
+ import { execFileCapture } from '../child-exec.js';
22
22
  import { computeInDegree, resolveRepoGraph } from '../import-graph.js';
23
23
 
24
24
  /** Neutral multiplier every degraded weight collapses to. */
@@ -47,10 +47,10 @@ function saturate(count, half) {
47
47
  * `degraded` is true when git could not answer at all — not a git work
48
48
  * tree, no commits yet, or the binary is unavailable.
49
49
  */
50
- export function readChurn({ cwd, windowDays = 180, run = execFileSync }) {
50
+ export function readChurn({ cwd, windowDays = 180, run }) {
51
51
  let stdout;
52
52
  try {
53
- stdout = run(
53
+ stdout = execFileCapture(
54
54
  'git',
55
55
  [
56
56
  'log',
@@ -60,9 +60,8 @@ export function readChurn({ cwd, windowDays = 180, run = execFileSync }) {
60
60
  '--no-renames',
61
61
  ],
62
62
  {
63
+ run,
63
64
  cwd,
64
- encoding: 'utf8',
65
- maxBuffer: 64 * 1024 * 1024,
66
65
  // git narrates "not a git repository" on stderr; the degradation is
67
66
  // reported in the envelope, not shouted at the operator.
68
67
  stdio: ['ignore', 'pipe', 'ignore'],
@@ -41,7 +41,7 @@ import path from 'node:path';
41
41
  import { AUDIT_LENSES } from '../audit-to-stories/audit-lenses.js';
42
42
  import { getPaths, PROJECT_ROOT, resolveConfig } from '../config-resolver.js';
43
43
  import { Logger } from '../Logger.js';
44
- import { estimateTokens } from '../orchestration/context-envelope.js';
44
+ import { estimateTokens } from '../orchestration/spec-spill.js';
45
45
  import {
46
46
  changeSetLacksSiblingTest,
47
47
  matchesAnyFilePattern,
@@ -249,7 +249,6 @@ export function buildStoryBody({ group, edges = [] }) {
249
249
  wide: null,
250
250
  reason_to_exist: null,
251
251
  depends_on: [],
252
- estimated_test_files: null,
253
252
  };
254
253
 
255
254
  // Serialize via the canonical serializer (no footer — depends_on is empty).
@@ -40,46 +40,26 @@
40
40
  * @module lib/baselines/envelope
41
41
  */
42
42
 
43
- import fs from 'node:fs';
44
- import path from 'node:path';
45
- import { fileURLToPath } from 'node:url';
46
- import Ajv from 'ajv';
47
- import addFormats from 'ajv-formats';
48
-
49
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
43
+ import {
44
+ BASELINE_KIND_SCHEMA_FILES,
45
+ buildBaselineSchemaAjv,
46
+ } from '../baseline-schema-registry.js';
50
47
 
51
48
  /**
52
- * Repo-relative `.agents/schemas/baselines/` directory. Resolved off the
53
- * module URL so the envelope works the same in the main checkout, in a
54
- * worktree, and inside CI's bare clone — none of those have a stable
55
- * `process.cwd()` relative to the schemas.
49
+ * Canonical list of kinds the shared envelope supports, **derived** from the
50
+ * schema registry rather than restated (Story #5002).
51
+ *
52
+ * It used to be a hand-kept literal that happened to mirror
53
+ * `BASELINE_KIND_SCHEMA_FILES` name for name. Deriving it makes
54
+ * "`kind` is known" and "`kind`'s schema is registered" the same fact, so
55
+ * `getValidator` below cannot be handed a kind the AJV instance never
56
+ * compiled — the not-registered branch is gone because the state is
57
+ * unreachable, not because it was hidden.
56
58
  */
57
- const SCHEMAS_DIR = path.resolve(
58
- __dirname,
59
- '..',
60
- '..',
61
- '..',
62
- 'schemas',
63
- 'baselines',
59
+ export const KNOWN_KINDS = Object.freeze(
60
+ BASELINE_KIND_SCHEMA_FILES.map((file) => file.replace(/\.schema\.json$/, '')),
64
61
  );
65
62
 
66
- /**
67
- * Canonical list of kinds the shared envelope supports. The writer's
68
- * per-kind module list lives in `kinds/index.js`; this constant is the
69
- * envelope's view of the same set, kept here to break the import cycle
70
- * (`kinds/<kind>` imports envelope; envelope only needs the names).
71
- */
72
- export const KNOWN_KINDS = Object.freeze([
73
- 'lint',
74
- 'coverage',
75
- 'crap',
76
- 'maintainability',
77
- 'mutation',
78
- 'lighthouse',
79
- 'bundle-size',
80
- 'duplication',
81
- ]);
82
-
83
63
  function schemaRefFor(kind) {
84
64
  return `.agents/schemas/baselines/${kind}.schema.json`;
85
65
  }
@@ -193,36 +173,37 @@ export function buildEnvelope({
193
173
  }
194
174
 
195
175
  /**
196
- * Lazy AJV instance compiled schemas are memoised so successive writes
197
- * during a single Node process don't re-compile the seven schemas.
176
+ * Lazy AJV instance, built by the canonical
177
+ * [`baseline-schema-registry`](../baseline-schema-registry.js) the ONE
178
+ * place a baselines-directory schema is read off disk and compiled
179
+ * (Story #5002). This module used to keep its own `SCHEMAS_DIR` + AJV +
180
+ * per-kind compile cache alongside the registry's, so the writer and
181
+ * `baselines/reader.js` validated the same envelope through two independently
182
+ * configured instances; a registration added to one was invisible to the
183
+ * other. One builder means the writer and the reader cannot disagree about
184
+ * what a valid envelope is.
185
+ *
186
+ * Memoised: building it reads eleven schema files, and a single baseline
187
+ * regeneration calls `assertEnvelope` once per write plus once per re-read.
198
188
  */
199
189
  let _ajv = null;
200
- const _validators = new Map();
201
-
202
- function getAjv() {
203
- if (_ajv) return _ajv;
204
- const ajv = new Ajv({ allErrors: true, strict: false });
205
- addFormats(ajv);
206
- const envelopeSchema = JSON.parse(
207
- fs.readFileSync(
208
- path.join(SCHEMAS_DIR, 'baseline-envelope.schema.json'),
209
- 'utf8',
210
- ),
211
- );
212
- ajv.addSchema(envelopeSchema, 'baseline-envelope.schema.json');
213
- _ajv = ajv;
214
- return ajv;
190
+ function ajv() {
191
+ if (_ajv === null) {
192
+ _ajv = buildBaselineSchemaAjv();
193
+ }
194
+ return _ajv;
215
195
  }
216
196
 
197
+ /**
198
+ * Look up the pre-registered validator for a per-kind schema. Total over
199
+ * `KNOWN_KINDS` — that list is derived from the registry's own filenames, so
200
+ * every kind reaching here has a compiled schema.
201
+ *
202
+ * @param {string} kind
203
+ * @returns {import('ajv').ValidateFunction}
204
+ */
217
205
  function getValidator(kind) {
218
- if (_validators.has(kind)) return _validators.get(kind);
219
- const ajv = getAjv();
220
- const schema = JSON.parse(
221
- fs.readFileSync(path.join(SCHEMAS_DIR, `${kind}.schema.json`), 'utf8'),
222
- );
223
- const validate = ajv.compile(schema);
224
- _validators.set(kind, validate);
225
- return validate;
206
+ return ajv().getSchema(`${kind}.schema.json`);
226
207
  }
227
208
 
228
209
  /**
@@ -22,6 +22,13 @@
22
22
  // `ref` and `file` are passed as separate argv tokens, never
23
23
  // interpolated into a shell command.
24
24
  //
25
+ // Story #5009 moved the spawn itself, the stdout ceiling and the
26
+ // failure-message shape out to `lib/child-exec.js` — the one child-process
27
+ // surface — so this module now owns only what is genuinely its own: the
28
+ // `(ref, file)` LRU, the exit-code semantics below, and the test seam. The
29
+ // `__setSpawnRunner` seam still injects a mock runner; it is threaded through
30
+ // the shared wrapper as `run` rather than calling `spawnSync` directly.
31
+ //
25
32
  // Exit semantics are intentionally narrow:
26
33
  //
27
34
  // - Exit 0: stdout is the file contents, returned as a UTF-8 string.
@@ -39,7 +46,7 @@
39
46
  // means "path absent at ref". Callers may therefore treat a throw as a
40
47
  // hard failure without having to re-diagnose it.
41
48
 
42
- import { spawnSync } from 'node:child_process';
49
+ import { formatChildFailure, spawnChild } from '../child-exec.js';
43
50
 
44
51
  // ---------------------------------------------------------------------------
45
52
  // LRU cache. Small footprint — even a fan-out across all seven baseline
@@ -53,21 +60,13 @@ import { spawnSync } from 'node:child_process';
53
60
  const DEFAULT_MAX_ENTRIES = 64;
54
61
 
55
62
  /**
56
- * Story #4914 explicit stdout ceiling for every git read in this module.
57
- *
58
- * `child_process.spawnSync` defaults `maxBuffer` to 1 MB. A committed
59
- * baseline legitimately grows past that (a real consumer's crap baseline
60
- * measured 1,178,910 bytes), at which point the child is killed —
61
- * `status: null`, `signal: 'SIGTERM'`, `error.code: 'ENOBUFS'` — and the
62
- * read fails for a reason that has nothing to do with the repository.
63
+ * Injected spawn runner, or `undefined` to use the shared surface's real one.
64
+ * Story #4914's explicit 64 MB ceiling still applies to both git reads below —
65
+ * it is now `child-exec.js`'s `MAX_BUFFER_BYTES`, applied by `spawnChild`.
63
66
  *
64
- * 64 MB is not a new number: it is the bound already used at
65
- * `run-test-profile.js:87`, `audit-baselines/trend.js:61` and
66
- * `audit-baselines/weights.js:65`. This module was the outlier.
67
+ * @type {Function | undefined}
67
68
  */
68
- const MAX_BUFFER_BYTES = 64 * 1024 * 1024;
69
-
70
- let _spawnSync = spawnSync;
69
+ let _spawnRunner;
71
70
  let _cache = new Map();
72
71
  let _maxEntries = DEFAULT_MAX_ENTRIES;
73
72
 
@@ -80,12 +79,12 @@ let _maxEntries = DEFAULT_MAX_ENTRIES;
80
79
  * the contract.
81
80
  *
82
81
  * @param {object} [opts]
83
- * @param {typeof spawnSync} [opts.spawn] - Mock spawnSync.
84
- * @param {number} [opts.maxEntries] - Override LRU capacity.
82
+ * @param {Function} [opts.spawn] - Mock spawnSync.
83
+ * @param {number} [opts.maxEntries] - Override LRU capacity.
85
84
  */
86
85
  export function __setSpawnRunner(opts = {}) {
87
86
  if (opts.spawn) {
88
- _spawnSync = opts.spawn;
87
+ _spawnRunner = opts.spawn;
89
88
  }
90
89
  if (typeof opts.maxEntries === 'number') {
91
90
  _maxEntries = opts.maxEntries;
@@ -98,7 +97,7 @@ export function __setSpawnRunner(opts = {}) {
98
97
  * to keep state from leaking across files.
99
98
  */
100
99
  export function __resetForTests() {
101
- _spawnSync = spawnSync;
100
+ _spawnRunner = undefined;
102
101
  _maxEntries = DEFAULT_MAX_ENTRIES;
103
102
  _cache = new Map();
104
103
  }
@@ -179,13 +178,10 @@ export function readBaseFromGit(ref, file, opts = {}) {
179
178
 
180
179
  const cwd = opts.cwd ?? process.cwd();
181
180
  const spec = `${ref}:${file}`;
182
- const result = _spawnSync('git', ['show', spec], {
181
+ const result = spawnChild('git', ['show', spec], {
182
+ run: _spawnRunner,
183
183
  cwd,
184
- stdio: 'pipe',
185
- encoding: 'utf-8',
186
- shell: false,
187
184
  env: cleanGitEnv(),
188
- maxBuffer: MAX_BUFFER_BYTES,
189
185
  });
190
186
 
191
187
  // `child_process.spawnSync` returns `status: null` when the child died
@@ -206,9 +202,12 @@ export function readBaseFromGit(ref, file, opts = {}) {
206
202
  return null;
207
203
  }
208
204
 
209
- const stderr = (result.stderr || '').toString().trim();
210
205
  throw new Error(
211
- `readBaseFromGit: git show ${spec} failed (status=${status}): ${stderr}`,
206
+ formatChildFailure({
207
+ label: `readBaseFromGit: git show ${spec}`,
208
+ status,
209
+ stderr: result.stderr,
210
+ }),
212
211
  );
213
212
  }
214
213
 
@@ -222,9 +221,10 @@ export function readBaseFromGit(ref, file, opts = {}) {
222
221
  * Restricting the log to `-- <file>` means the returned subjects already
223
222
  * satisfy the "commit whose diff touches the baseline file" half of the
224
223
  * predicate; the caller only has to match the tag substring against each
225
- * subject. Runs through the same `spawnSync` seam as `readBaseFromGit`, so
226
- * it inherits the identical env scrubbing (drop inherited `GIT_*`) and the
227
- * spawn-not-exec security posture (argv tokens, `shell: false`).
224
+ * subject. Runs through the same shared `spawnChild` surface and injected
225
+ * runner as `readBaseFromGit`, so it inherits the identical env scrubbing
226
+ * (drop inherited `GIT_*`), the same stdout ceiling, and the spawn-not-exec
227
+ * security posture (argv tokens, `shell: false`).
228
228
  *
229
229
  * Returns an empty array whenever the range cannot be walked (missing base
230
230
  * ref, git failure, empty range) — the acknowledgment path treats "no
@@ -244,17 +244,10 @@ export function readRangeSubjectsTouchingFile(baseRef, file, opts = {}) {
244
244
  const cwd = opts.cwd ?? process.cwd();
245
245
  let result;
246
246
  try {
247
- result = _spawnSync(
247
+ result = spawnChild(
248
248
  'git',
249
249
  ['log', `${baseRef}..HEAD`, '--format=%s', '--', file],
250
- {
251
- cwd,
252
- stdio: 'pipe',
253
- encoding: 'utf-8',
254
- shell: false,
255
- env: cleanGitEnv(),
256
- maxBuffer: MAX_BUFFER_BYTES,
257
- },
250
+ { run: _spawnRunner, cwd, env: cleanGitEnv() },
258
251
  );
259
252
  } catch {
260
253
  return [];
@@ -0,0 +1,103 @@
1
+ /**
2
+ * _crap-new-method-gate.js — the policy `kinds/crap.js#compareCrap` applies to
3
+ * a method that has no baseline row (Story #5002).
4
+ *
5
+ * Underscore-prefixed like `_shared-metric.js`: this is a helper for the
6
+ * per-kind modules in this directory, not a kind of its own.
7
+ *
8
+ * A leaf — it imports nothing, so `kinds/crap.js` can consume it without
9
+ * adding an edge to anything the comparator does not already reach.
10
+ */
11
+
12
+ /**
13
+ * The set of scanned files in which **every** measured method resolved 0%
14
+ * coverage — the observable signature of a source no test loads at all.
15
+ *
16
+ * Deliberately derived from the scan rows rather than plumbed down from the
17
+ * coverage artifact: the rows are the only thing both the preview gate and the
18
+ * close-validation projection hand the comparator, and "the whole file is at
19
+ * zero" is exactly what an all-zero istanbul entry produces here.
20
+ *
21
+ * A row carrying `coverage: null` is skipped, not counted as a zero — an
22
+ * absent observation is not a measured one, and letting it vote would hand the
23
+ * relief below to a file nothing measured at all.
24
+ *
25
+ * @param {Array<{file: string, coverage?: number|null}>} currentRows
26
+ * @returns {Set<string>}
27
+ */
28
+ export function deriveUncoveredFiles(currentRows) {
29
+ const allZeroByFile = new Map();
30
+ for (const row of currentRows ?? []) {
31
+ if (typeof row?.coverage !== 'number') continue;
32
+ const soFar = allZeroByFile.get(row.file);
33
+ allZeroByFile.set(row.file, (soFar ?? true) && row.coverage === 0);
34
+ }
35
+ const uncovered = new Set();
36
+ for (const [file, allZero] of allZeroByFile) {
37
+ if (allZero) uncovered.add(file);
38
+ }
39
+ return uncovered;
40
+ }
41
+
42
+ /**
43
+ * The score a **new** method is judged against `newMethodCeiling` with.
44
+ *
45
+ * Normally the measured CRAP. In a file whose every method scored 0%, the
46
+ * complexity term `c` alone.
47
+ *
48
+ * **Why.** `crap = c²·(1 − cov)³ + c` collapses to `c² + c` at zero coverage,
49
+ * so the default ceiling of 30 caps a *brand-new* method in an untested file
50
+ * at `c ≈ 5`. For the code that is untestable by construction — argv parsing,
51
+ * `spawn` wiring, a CLI's top-level `main()` — that is not a quality signal
52
+ * but a fragmentation tax: the only way past it is to shatter cohesive wiring
53
+ * into sub-five-branch fragments, which raises the file's method count and its
54
+ * reader's cost while measuring nothing. The coverage term still governs every
55
+ * method in a file the tests *do* reach, and a genuinely sprawling function is
56
+ * still refused — `c` alone must clear the ceiling.
57
+ *
58
+ * This is a **gate** decision, not a scoring one: the persisted baseline row
59
+ * keeps its measured `c² + c`, so the regression arm on the next run compares
60
+ * like with like.
61
+ *
62
+ * @param {{file: string, crap: number, cyclomatic?: number}} row
63
+ * @param {Set<string>} uncoveredFiles
64
+ * @returns {number}
65
+ */
66
+ export function newMethodGateScore(row, uncoveredFiles) {
67
+ if (!uncoveredFiles.has(row.file)) return row.crap;
68
+ return Number.isFinite(row.cyclomatic) ? row.cyclomatic : row.crap;
69
+ }
70
+
71
+ /**
72
+ * Assemble a new-method violation.
73
+ *
74
+ * `gateScore` is written **only** when the ceiling was judged against
75
+ * something other than the measured `crap` — the same write-only-when-
76
+ * non-default idiom `projectRow` uses for its row markers — so the printer can
77
+ * name the number that actually failed instead of one nothing tested.
78
+ *
79
+ * @param {object} row
80
+ * @param {number} ceiling
81
+ * @param {number} gateScore
82
+ * @returns {object}
83
+ */
84
+ export function buildNewViolation(row, ceiling, gateScore) {
85
+ return {
86
+ ...row,
87
+ kind: 'new',
88
+ baseline: null,
89
+ ceiling,
90
+ ...(gateScore === row.crap ? {} : { gateScore }),
91
+ };
92
+ }
93
+
94
+ /**
95
+ * Render the measured-vs-gated half of a new-method violation line.
96
+ *
97
+ * @param {{crap: number, gateScore?: number}} v
98
+ * @returns {string}
99
+ */
100
+ export function formatNewViolationMeasure(v) {
101
+ if (v.gateScore === undefined) return `crap=${v.crap.toFixed(2)}`;
102
+ return `complexity=${v.gateScore} (file has no coverage; crap=${v.crap.toFixed(2)})`;
103
+ }