@opengsd/gsd-core 1.11.0 → 1.13.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 (498) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.opencode/plugins/gsd-core.js +12 -0
  4. package/agents/gsd-code-fixer.md +1 -1
  5. package/agents/gsd-debug-session-manager.md +1 -1
  6. package/agents/gsd-debugger.md +1 -1
  7. package/agents/gsd-dom-verifier.md +169 -0
  8. package/agents/gsd-eval-auditor.md +1 -1
  9. package/agents/gsd-executor.md +78 -42
  10. package/agents/gsd-framework-selector.md +1 -3
  11. package/agents/gsd-intel-updater.md +1 -1
  12. package/agents/gsd-mempalace-curator.md +0 -1
  13. package/agents/gsd-pattern-mapper.md +11 -0
  14. package/agents/gsd-phase-researcher.md +3 -1
  15. package/agents/gsd-plan-checker.md +91 -112
  16. package/agents/gsd-planner.md +20 -4
  17. package/agents/gsd-project-researcher.md +1 -1
  18. package/agents/gsd-research-synthesizer.md +2 -2
  19. package/agents/gsd-roadmapper.md +15 -11
  20. package/agents/gsd-ui-checker.md +82 -7
  21. package/agents/gsd-ui-researcher.md +70 -3
  22. package/agents/gsd-verifier.md +24 -2
  23. package/bin/install.js +847 -200
  24. package/commands/gsd/discuss-phase.md +1 -1
  25. package/commands/gsd/execute-phase.md +1 -1
  26. package/commands/gsd/import.md +1 -1
  27. package/commands/gsd/ns-workflow.md +2 -1
  28. package/commands/gsd/phase.md +1 -1
  29. package/commands/gsd/quick-batch.md +105 -0
  30. package/commands/gsd/quick.md +8 -4
  31. package/commands/gsd/surface.md +18 -8
  32. package/gsd-core/bin/gsd-tools.cjs +761 -100
  33. package/gsd-core/bin/lib/active-workstream-store.cjs +8 -0
  34. package/gsd-core/bin/lib/adr-parser.cjs +13 -7
  35. package/gsd-core/bin/lib/agent-install-check.cjs +162 -0
  36. package/gsd-core/bin/lib/api-coverage.cjs +30 -9
  37. package/gsd-core/bin/lib/artifacts.cjs +2 -0
  38. package/gsd-core/bin/lib/assumption-delta.cjs +30 -11
  39. package/gsd-core/bin/lib/audit.cjs +163 -41
  40. package/gsd-core/bin/lib/broken-windows.cjs +306 -28
  41. package/gsd-core/bin/lib/capability-activation.cjs +27 -0
  42. package/gsd-core/bin/lib/capability-lock.cjs +10 -4
  43. package/gsd-core/bin/lib/capability-registry.cjs +785 -144
  44. package/gsd-core/bin/lib/capability-state.cjs +25 -4
  45. package/gsd-core/bin/lib/capability-validator.cjs +321 -18
  46. package/gsd-core/bin/lib/capability-writer.cjs +14 -4
  47. package/gsd-core/bin/lib/check-command-router.cjs +229 -6
  48. package/gsd-core/bin/lib/claude-orchestration.cjs +10 -25
  49. package/gsd-core/bin/lib/cli-exit.cjs +496 -10
  50. package/gsd-core/bin/lib/clusters.cjs +1 -0
  51. package/gsd-core/bin/lib/code-review-depth.cjs +288 -0
  52. package/gsd-core/bin/lib/codex-agent-toml.cjs +410 -4
  53. package/gsd-core/bin/lib/command-aliases.cjs +16 -0
  54. package/gsd-core/bin/lib/command-arg-projection.cjs +144 -14
  55. package/gsd-core/bin/lib/command-routing-hub.cjs +31 -2
  56. package/gsd-core/bin/lib/commands.cjs +877 -54
  57. package/gsd-core/bin/lib/complexity-trigger.cjs +26 -6
  58. package/gsd-core/bin/lib/config-loader.cjs +121 -29
  59. package/gsd-core/bin/lib/config.cjs +92 -2
  60. package/gsd-core/bin/lib/configuration.cjs +129 -37
  61. package/gsd-core/bin/lib/core-utils.cjs +118 -14
  62. package/gsd-core/bin/lib/decisions.cjs +213 -1
  63. package/gsd-core/bin/lib/edge-probe.cjs +23 -2
  64. package/gsd-core/bin/lib/estimate-cli.cjs +55 -11
  65. package/gsd-core/bin/lib/exit-code-registry.cjs +98 -0
  66. package/gsd-core/bin/lib/file-overlap-partitioner.cjs +74 -0
  67. package/gsd-core/bin/lib/frontmatter.cjs +975 -326
  68. package/gsd-core/bin/lib/gap-checker.cjs +41 -8
  69. package/gsd-core/bin/lib/git-base-branch.cjs +182 -39
  70. package/gsd-core/bin/lib/health-diagnostic-rules/consistency.cjs +7 -3
  71. package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +8 -2
  72. package/gsd-core/bin/lib/health-diagnostic-rules/roadmap-disk-consistency.cjs +60 -14
  73. package/gsd-core/bin/lib/health-diagnostic-rules/state-consistency.cjs +75 -22
  74. package/gsd-core/bin/lib/health-diagnostic-rules/worktree-health.cjs +22 -8
  75. package/gsd-core/bin/lib/health-diagnostic.cjs +23 -3
  76. package/gsd-core/bin/lib/host-integration.cjs +96 -11
  77. package/gsd-core/bin/lib/init-command-router.cjs +132 -21
  78. package/gsd-core/bin/lib/init.cjs +252 -56
  79. package/gsd-core/bin/lib/install-engine.cjs +252 -15
  80. package/gsd-core/bin/lib/install-model-override-resolver.cjs +78 -1
  81. package/gsd-core/bin/lib/install-profiles.cjs +100 -18
  82. package/gsd-core/bin/lib/installer-migration-report.cjs +4 -0
  83. package/gsd-core/bin/lib/installer-migrations/010-antigravity-retire-confighome-artifacts.cjs +169 -0
  84. package/gsd-core/bin/lib/installer-migrations.cjs +10 -7
  85. package/gsd-core/bin/lib/intel.cjs +101 -26
  86. package/gsd-core/bin/lib/io.cjs +195 -15
  87. package/gsd-core/bin/lib/learnings.cjs +85 -14
  88. package/gsd-core/bin/lib/legacy-cleanup.cjs +8 -2
  89. package/gsd-core/bin/lib/loop-resolver.cjs +14 -8
  90. package/gsd-core/bin/lib/markdown-table.cjs +175 -4
  91. package/gsd-core/bin/lib/milestone.cjs +112 -7
  92. package/gsd-core/bin/lib/model-catalog.cjs +177 -19
  93. package/gsd-core/bin/lib/model-resolver.cjs +10 -28
  94. package/gsd-core/bin/lib/onboard-projection.cjs +5 -1
  95. package/gsd-core/bin/lib/phase-command-router.cjs +13 -6
  96. package/gsd-core/bin/lib/phase-estimation.cjs +17 -8
  97. package/gsd-core/bin/lib/phase-id.cjs +321 -13
  98. package/gsd-core/bin/lib/phase-lifecycle.cjs +24 -16
  99. package/gsd-core/bin/lib/phase-locator.cjs +138 -17
  100. package/gsd-core/bin/lib/phase.cjs +1175 -115
  101. package/gsd-core/bin/lib/plan-document.cjs +273 -0
  102. package/gsd-core/bin/lib/plan-scan.cjs +13 -2
  103. package/gsd-core/bin/lib/planning-command-router.cjs +61 -0
  104. package/gsd-core/bin/lib/planning-inspect.cjs +1168 -0
  105. package/gsd-core/bin/lib/planning-snapshot.cjs +165 -34
  106. package/gsd-core/bin/lib/planning-workspace.cjs +159 -28
  107. package/gsd-core/bin/lib/probe-core.cjs +4 -1
  108. package/gsd-core/bin/lib/profile-pipeline-command-router.cjs +50 -7
  109. package/gsd-core/bin/lib/profile-pipeline.cjs +6 -3
  110. package/gsd-core/bin/lib/quick-batch-command-router.cjs +285 -0
  111. package/gsd-core/bin/lib/quick-batch-dispatch.cjs +250 -0
  112. package/gsd-core/bin/lib/quick-batch.cjs +840 -0
  113. package/gsd-core/bin/lib/real-home-guard.cjs +419 -0
  114. package/gsd-core/bin/lib/refactor-trigger-command-router.cjs +71 -45
  115. package/gsd-core/bin/lib/review-lane-descriptor.cjs +62 -14
  116. package/gsd-core/bin/lib/review-lane-invocation.cjs +73 -1
  117. package/gsd-core/bin/lib/review-lane-runner.cjs +136 -10
  118. package/gsd-core/bin/lib/roadmap-command-router.cjs +45 -31
  119. package/gsd-core/bin/lib/roadmap-parser.cjs +577 -41
  120. package/gsd-core/bin/lib/roadmap.cjs +248 -64
  121. package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +329 -41
  122. package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +16 -17
  123. package/gsd-core/bin/lib/runtime-artifact-layout.cjs +320 -109
  124. package/gsd-core/bin/lib/runtime-hooks-surface.cjs +487 -83
  125. package/gsd-core/bin/lib/runtime-identity.cjs +234 -0
  126. package/gsd-core/bin/lib/runtime-slash.cjs +72 -2
  127. package/gsd-core/bin/lib/shell-command-projection.cjs +75 -8
  128. package/gsd-core/bin/lib/smart-entry.cjs +19 -31
  129. package/gsd-core/bin/lib/spec-section.cjs +12 -7
  130. package/gsd-core/bin/lib/state-command-router.cjs +47 -18
  131. package/gsd-core/bin/lib/state-contract.cjs +359 -0
  132. package/gsd-core/bin/lib/state-document.cjs +216 -5
  133. package/gsd-core/bin/lib/state-md-schema.cjs +231 -0
  134. package/gsd-core/bin/lib/state-transition.cjs +850 -145
  135. package/gsd-core/bin/lib/state.cjs +1629 -287
  136. package/gsd-core/bin/lib/surface.cjs +33 -10
  137. package/gsd-core/bin/lib/task-command-router.cjs +111 -1
  138. package/gsd-core/bin/lib/task-content-resolution.cjs +368 -0
  139. package/gsd-core/bin/lib/tdd-red-evidence.cjs +133 -0
  140. package/gsd-core/bin/lib/teams-status.cjs +4 -1
  141. package/gsd-core/bin/lib/uat-predicate.cjs +58 -20
  142. package/gsd-core/bin/lib/uat.cjs +2542 -387
  143. package/gsd-core/bin/lib/ui-consideration-probe.cjs +9 -1
  144. package/gsd-core/bin/lib/ui-safety-gate.cjs +37 -7
  145. package/gsd-core/bin/lib/unusable-input.cjs +13 -0
  146. package/gsd-core/bin/lib/update-context.cjs +6 -2
  147. package/gsd-core/bin/lib/validate-command-router.cjs +2 -2
  148. package/gsd-core/bin/lib/validate.cjs +230 -12
  149. package/gsd-core/bin/lib/vendor/README.md +43 -5
  150. package/gsd-core/bin/lib/vendor/js-yaml.cjs +3014 -0
  151. package/gsd-core/bin/lib/verification-command-router.cjs +2 -1
  152. package/gsd-core/bin/lib/verification.cjs +287 -13
  153. package/gsd-core/bin/lib/verify-command-grounding.cjs +846 -0
  154. package/gsd-core/bin/lib/verify-command-router.cjs +1 -0
  155. package/gsd-core/bin/lib/verify.cjs +441 -56
  156. package/gsd-core/bin/lib/workstream-inventory.cjs +20 -2
  157. package/gsd-core/bin/lib/workstream-name-policy.cjs +25 -4
  158. package/gsd-core/bin/lib/worktree-base-ref.cjs +66 -12
  159. package/gsd-core/bin/lib/worktree-safety.cjs +185 -21
  160. package/gsd-core/bin/shared/config-defaults.manifest.json +7 -1
  161. package/gsd-core/bin/shared/config-schema.manifest.json +13 -0
  162. package/gsd-core/bin/shared/exit-codes.json +8 -0
  163. package/gsd-core/bin/shared/exit-codes.sh +20 -0
  164. package/gsd-core/bin/shared/model-catalog.json +8 -1
  165. package/gsd-core/bin/verify-reapply-patches.cjs +70 -3
  166. package/gsd-core/references/agent-contracts.md +6 -5
  167. package/gsd-core/references/api-coverage.md +24 -2
  168. package/gsd-core/references/autonomous-smart-discuss.md +3 -3
  169. package/gsd-core/references/checkpoints.md +37 -19
  170. package/gsd-core/references/decimal-phase-calculation.md +5 -5
  171. package/gsd-core/references/edge-probe.md +17 -5
  172. package/gsd-core/references/execute-mvp-tdd.md +18 -18
  173. package/gsd-core/references/execute-phase-between-wave-reset.md +9 -12
  174. package/gsd-core/references/execute-phase-response-language.md +6 -0
  175. package/gsd-core/references/execute-phase-wave-guard.md +11 -9
  176. package/gsd-core/references/executor-examples.md +42 -0
  177. package/gsd-core/references/failing-direction.md +78 -0
  178. package/gsd-core/references/few-shot-examples/plan-checker.md +15 -15
  179. package/gsd-core/references/gate-prompts.md +1 -1
  180. package/gsd-core/references/git-integration.md +5 -5
  181. package/gsd-core/references/git-planning-commit.md +3 -3
  182. package/gsd-core/references/gsd-run-resolver.md +1 -1
  183. package/gsd-core/references/loop-hook-dispatch.md +22 -0
  184. package/gsd-core/references/model-profiles.md +1 -1
  185. package/gsd-core/references/mvp-concepts.md +2 -2
  186. package/gsd-core/references/nyquist-compliance.md +74 -0
  187. package/gsd-core/references/offer-next.md +3 -5
  188. package/gsd-core/references/phase-argument-parsing.md +3 -3
  189. package/gsd-core/references/plan-checker-examples.md +41 -0
  190. package/gsd-core/references/planner-antipatterns.md +25 -0
  191. package/gsd-core/references/planner-chunked.md +5 -1
  192. package/gsd-core/references/planner-coupling.md +42 -0
  193. package/gsd-core/references/planner-failing-direction.md +53 -0
  194. package/gsd-core/references/planner-human-verify-mode.md +15 -1
  195. package/gsd-core/references/planner-quick-batch.md +71 -0
  196. package/gsd-core/references/planner-reviews.md +47 -0
  197. package/gsd-core/references/planner-revision.md +76 -3
  198. package/gsd-core/references/planner-verify-command-grounding.md +17 -0
  199. package/gsd-core/references/planning-config.md +39 -9
  200. package/gsd-core/references/response-language-directive.md +9 -0
  201. package/gsd-core/references/reviewer-instances.md +31 -0
  202. package/gsd-core/references/revision-loop.md +118 -11
  203. package/gsd-core/references/runtime-aware-dispatch.md +1 -1
  204. package/gsd-core/references/tdd.md +15 -12
  205. package/gsd-core/references/ui-brand.md +65 -21
  206. package/gsd-core/references/ui-consideration-probe.md +1 -1
  207. package/gsd-core/references/universal-anti-patterns.md +2 -2
  208. package/gsd-core/references/verifier-evidence-gate.md +160 -0
  209. package/gsd-core/references/verify-command-path-resolvability.md +42 -0
  210. package/gsd-core/references/verify-mvp-mode.md +1 -1
  211. package/gsd-core/references/workstream-flag.md +11 -11
  212. package/gsd-core/templates/README.md +1 -1
  213. package/gsd-core/templates/SECURITY.md +3 -3
  214. package/gsd-core/templates/UI-SPEC.md +25 -3
  215. package/gsd-core/templates/VALIDATION.md +3 -3
  216. package/gsd-core/templates/phase-prompt.md +7 -0
  217. package/gsd-core/templates/state.md +7 -0
  218. package/gsd-core/templates/verification-report.md +5 -0
  219. package/gsd-core/workflows/_runtime-launcher.snippet.sh +1 -1
  220. package/gsd-core/workflows/add-backlog.md +3 -1
  221. package/gsd-core/workflows/add-phase.md +5 -3
  222. package/gsd-core/workflows/add-tests.md +4 -9
  223. package/gsd-core/workflows/add-todo.md +2 -2
  224. package/gsd-core/workflows/ai-integration-phase.md +5 -10
  225. package/gsd-core/workflows/analyze-dependencies.md +2 -0
  226. package/gsd-core/workflows/audit-fix.md +14 -3
  227. package/gsd-core/workflows/audit-milestone.md +11 -9
  228. package/gsd-core/workflows/audit-uat.md +19 -2
  229. package/gsd-core/workflows/autonomous/steps/converge-fail-fast.md +2 -2
  230. package/gsd-core/workflows/autonomous.md +12 -26
  231. package/gsd-core/workflows/check-todos.md +2 -2
  232. package/gsd-core/workflows/cleanup.md +3 -3
  233. package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +16 -14
  234. package/gsd-core/workflows/code-review-fix.md +3 -1
  235. package/gsd-core/workflows/code-review.md +192 -69
  236. package/gsd-core/workflows/complete-milestone.md +28 -14
  237. package/gsd-core/workflows/debug.md +6 -4
  238. package/gsd-core/workflows/diagnose-issues.md +17 -7
  239. package/gsd-core/workflows/discuss-phase/modes/advisor.md +3 -1
  240. package/gsd-core/workflows/discuss-phase/modes/all.md +2 -0
  241. package/gsd-core/workflows/discuss-phase/modes/analyze.md +2 -0
  242. package/gsd-core/workflows/discuss-phase/modes/auto.md +2 -0
  243. package/gsd-core/workflows/discuss-phase/modes/batch.md +2 -0
  244. package/gsd-core/workflows/discuss-phase/modes/chain.md +5 -7
  245. package/gsd-core/workflows/discuss-phase/modes/default.md +2 -0
  246. package/gsd-core/workflows/discuss-phase/modes/power.md +2 -0
  247. package/gsd-core/workflows/discuss-phase/modes/text.md +3 -1
  248. package/gsd-core/workflows/discuss-phase/templates/context.md +2 -0
  249. package/gsd-core/workflows/discuss-phase/templates/discussion-log.md +2 -0
  250. package/gsd-core/workflows/discuss-phase-assumptions/steps/auto-advance-dispatch.md +1 -3
  251. package/gsd-core/workflows/discuss-phase-assumptions.md +3 -3
  252. package/gsd-core/workflows/discuss-phase-power.md +2 -0
  253. package/gsd-core/workflows/discuss-phase.md +2 -2
  254. package/gsd-core/workflows/do.md +46 -19
  255. package/gsd-core/workflows/docs-update.md +6 -5
  256. package/gsd-core/workflows/edit-phase.md +3 -1
  257. package/gsd-core/workflows/eval-review.md +5 -10
  258. package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +3 -1
  259. package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +129 -11
  260. package/gsd-core/workflows/execute-phase/steps/gap-closure-artifacts.md +1 -1
  261. package/gsd-core/workflows/execute-phase/steps/partial-wave.md +1 -1
  262. package/gsd-core/workflows/execute-phase/steps/per-plan-executor-routing.md +1 -1
  263. package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +29 -5
  264. package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +2 -2
  265. package/gsd-core/workflows/execute-phase/steps/protected-branch.md +21 -0
  266. package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +4 -2
  267. package/gsd-core/workflows/execute-phase/steps/tdd-applicability-resolution.md +25 -0
  268. package/gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md +39 -0
  269. package/gsd-core/workflows/execute-phase/steps/worktree-recovery-policy.md +2 -0
  270. package/gsd-core/workflows/execute-phase.md +68 -66
  271. package/gsd-core/workflows/execute-plan.md +25 -20
  272. package/gsd-core/workflows/explore.md +3 -1
  273. package/gsd-core/workflows/extract-learnings.md +3 -1
  274. package/gsd-core/workflows/fast.md +8 -2
  275. package/gsd-core/workflows/forensics.md +3 -1
  276. package/gsd-core/workflows/graduation.md +6 -6
  277. package/gsd-core/workflows/health.md +4 -7
  278. package/gsd-core/workflows/help/modes/brief.md +2 -0
  279. package/gsd-core/workflows/help/modes/default.md +2 -0
  280. package/gsd-core/workflows/help/modes/full.md +12 -0
  281. package/gsd-core/workflows/help/modes/topic.md +2 -0
  282. package/gsd-core/workflows/help.md +2 -0
  283. package/gsd-core/workflows/import.md +17 -14
  284. package/gsd-core/workflows/inbox.md +5 -6
  285. package/gsd-core/workflows/ingest-docs.md +45 -12
  286. package/gsd-core/workflows/insert-phase.md +7 -5
  287. package/gsd-core/workflows/list-phase-assumptions.md +2 -0
  288. package/gsd-core/workflows/list-seeds.md +7 -3
  289. package/gsd-core/workflows/list-workspaces.md +3 -1
  290. package/gsd-core/workflows/manager.md +15 -26
  291. package/gsd-core/workflows/map-codebase.md +3 -1
  292. package/gsd-core/workflows/milestone-summary.md +3 -1
  293. package/gsd-core/workflows/mvp-phase.md +3 -3
  294. package/gsd-core/workflows/new-milestone.md +10 -22
  295. package/gsd-core/workflows/new-project/steps/auto-mode-config.md +1 -1
  296. package/gsd-core/workflows/new-project.md +17 -29
  297. package/gsd-core/workflows/new-workspace.md +2 -2
  298. package/gsd-core/workflows/next.md +4 -2
  299. package/gsd-core/workflows/node-repair.md +2 -0
  300. package/gsd-core/workflows/note.md +2 -0
  301. package/gsd-core/workflows/onboard.md +1 -1
  302. package/gsd-core/workflows/pause-work.md +20 -5
  303. package/gsd-core/workflows/plan-phase/steps/adr-ingest-express-path.md +1 -1
  304. package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +100 -18
  305. package/gsd-core/workflows/plan-phase/steps/prd-express-path.md +4 -4
  306. package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +12 -3
  307. package/gsd-core/workflows/plan-phase.md +251 -54
  308. package/gsd-core/workflows/plan-review-convergence.md +148 -19
  309. package/gsd-core/workflows/plant-seed.md +3 -3
  310. package/gsd-core/workflows/pr-branch.md +195 -51
  311. package/gsd-core/workflows/profile-user.md +17 -15
  312. package/gsd-core/workflows/progress/steps/forensic-audit.md +1 -1
  313. package/gsd-core/workflows/progress.md +52 -15
  314. package/gsd-core/workflows/quick/steps/discussion-phase.md +1 -3
  315. package/gsd-core/workflows/quick/steps/plan-checker-loop.md +38 -5
  316. package/gsd-core/workflows/quick/steps/quick-verification.md +2 -4
  317. package/gsd-core/workflows/quick/steps/research-phase.md +5 -7
  318. package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +3 -3
  319. package/gsd-core/workflows/quick-batch/steps/batch-init.md +55 -0
  320. package/gsd-core/workflows/quick-batch/steps/completion.md +65 -0
  321. package/gsd-core/workflows/quick-batch/steps/merge-wave.md +100 -0
  322. package/gsd-core/workflows/quick-batch/steps/plan-checker-loop.md +147 -0
  323. package/gsd-core/workflows/quick-batch/steps/planner-wave.md +158 -0
  324. package/gsd-core/workflows/quick-batch/steps/research-phase.md +95 -0
  325. package/gsd-core/workflows/quick-batch/steps/resume-mode.md +49 -0
  326. package/gsd-core/workflows/quick-batch/steps/verification-wave.md +73 -0
  327. package/gsd-core/workflows/quick-batch/steps/worktree-dispatch.md +169 -0
  328. package/gsd-core/workflows/quick-batch.md +203 -0
  329. package/gsd-core/workflows/quick.md +33 -32
  330. package/gsd-core/workflows/reapply-patches.md +2 -0
  331. package/gsd-core/workflows/remove-phase.md +6 -4
  332. package/gsd-core/workflows/remove-workspace.md +3 -3
  333. package/gsd-core/workflows/resume-project.md +14 -14
  334. package/gsd-core/workflows/review.md +404 -21
  335. package/gsd-core/workflows/scan.md +3 -1
  336. package/gsd-core/workflows/section-manifest.json +12 -0
  337. package/gsd-core/workflows/secure-phase.md +3 -3
  338. package/gsd-core/workflows/session-report.md +2 -0
  339. package/gsd-core/workflows/settings-advanced.md +9 -9
  340. package/gsd-core/workflows/settings-integrations.md +66 -32
  341. package/gsd-core/workflows/settings.md +4 -6
  342. package/gsd-core/workflows/ship.md +22 -16
  343. package/gsd-core/workflows/sketch-wrap-up.md +13 -17
  344. package/gsd-core/workflows/sketch.md +13 -19
  345. package/gsd-core/workflows/smart-entry.md +4 -6
  346. package/gsd-core/workflows/spec-phase.md +31 -4
  347. package/gsd-core/workflows/spike-wrap-up.md +9 -11
  348. package/gsd-core/workflows/spike.md +21 -32
  349. package/gsd-core/workflows/stats.md +4 -2
  350. package/gsd-core/workflows/sync-skills.md +13 -5
  351. package/gsd-core/workflows/thread.md +13 -7
  352. package/gsd-core/workflows/transition.md +7 -5
  353. package/gsd-core/workflows/ui-phase.md +36 -21
  354. package/gsd-core/workflows/ui-review.md +7 -11
  355. package/gsd-core/workflows/ultraplan-phase.md +7 -13
  356. package/gsd-core/workflows/undo.md +9 -17
  357. package/gsd-core/workflows/update.md +47 -48
  358. package/gsd-core/workflows/validate-phase.md +3 -3
  359. package/gsd-core/workflows/verify-work/steps/automated-ui-verification.md +25 -1
  360. package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +1 -1
  361. package/gsd-core/workflows/verify-work.md +106 -21
  362. package/hooks/dist/gsd-agent-isolation-guard.js +77 -38
  363. package/hooks/dist/gsd-check-update-worker.js +19 -2
  364. package/hooks/dist/gsd-config-reload.js +18 -12
  365. package/hooks/dist/gsd-context-monitor.js +302 -22
  366. package/hooks/dist/gsd-cursor-post-tool.js +3 -1
  367. package/hooks/dist/gsd-cursor-pre-tool.js +3 -1
  368. package/hooks/dist/gsd-cursor-session-start.js +2 -1
  369. package/hooks/dist/gsd-cursor-stop.js +2 -1
  370. package/hooks/dist/gsd-cursor-subagent-start.js +28 -23
  371. package/hooks/dist/gsd-cursor-subagent-stop.js +3 -1
  372. package/hooks/dist/gsd-ensure-canonical-path.js +2 -1
  373. package/hooks/dist/gsd-graphify-update.sh +22 -18
  374. package/hooks/dist/gsd-node-runner.sh +77 -0
  375. package/hooks/dist/gsd-phase-boundary.sh +1 -0
  376. package/hooks/dist/gsd-prompt-guard.js +46 -12
  377. package/hooks/dist/gsd-read-guard.js +18 -7
  378. package/hooks/dist/gsd-read-injection-scanner.js +22 -13
  379. package/hooks/dist/gsd-secret-read-guard.js +1079 -0
  380. package/hooks/dist/gsd-session-state.sh +1 -0
  381. package/hooks/dist/gsd-statusline.js +222 -29
  382. package/hooks/dist/gsd-validate-commit.sh +523 -12
  383. package/hooks/dist/gsd-windsurf-pre-command.js +16 -11
  384. package/hooks/dist/gsd-windsurf-pre-write.js +22 -13
  385. package/hooks/dist/gsd-workflow-guard.js +36 -17
  386. package/hooks/dist/gsd-worktree-path-guard.js +36 -21
  387. package/hooks/dist/gsd-write-guard.js +35 -25
  388. package/hooks/dist/lib/cli-exit.js +560 -0
  389. package/hooks/dist/lib/exit-code-registry.js +98 -0
  390. package/hooks/dist/lib/git-cmd.js +210 -1
  391. package/hooks/dist/lib/git-probe.js +84 -0
  392. package/hooks/dist/lib/hook-exit.js +81 -0
  393. package/hooks/dist/lib/injection-patterns.js +36 -6
  394. package/hooks/dist/managed-hooks-registry.cjs +4 -0
  395. package/hooks/gsd-agent-isolation-guard.js +77 -38
  396. package/hooks/gsd-check-update-worker.js +19 -2
  397. package/hooks/gsd-config-reload.js +18 -12
  398. package/hooks/gsd-context-monitor.js +302 -22
  399. package/hooks/gsd-cursor-post-tool.js +3 -1
  400. package/hooks/gsd-cursor-pre-tool.js +3 -1
  401. package/hooks/gsd-cursor-session-start.js +2 -1
  402. package/hooks/gsd-cursor-stop.js +2 -1
  403. package/hooks/gsd-cursor-subagent-start.js +28 -23
  404. package/hooks/gsd-cursor-subagent-stop.js +3 -1
  405. package/hooks/gsd-ensure-canonical-path.js +2 -1
  406. package/hooks/gsd-graphify-update.sh +22 -18
  407. package/hooks/gsd-node-runner.sh +77 -0
  408. package/hooks/gsd-phase-boundary.sh +1 -0
  409. package/hooks/gsd-prompt-guard.js +46 -12
  410. package/hooks/gsd-read-guard.js +18 -7
  411. package/hooks/gsd-read-injection-scanner.js +22 -13
  412. package/hooks/gsd-secret-read-guard.js +1079 -0
  413. package/hooks/gsd-session-state.sh +1 -0
  414. package/hooks/gsd-statusline.js +222 -29
  415. package/hooks/gsd-validate-commit.sh +523 -12
  416. package/hooks/gsd-windsurf-pre-command.js +16 -11
  417. package/hooks/gsd-windsurf-pre-write.js +22 -13
  418. package/hooks/gsd-workflow-guard.js +36 -17
  419. package/hooks/gsd-worktree-path-guard.js +36 -21
  420. package/hooks/gsd-write-guard.js +35 -25
  421. package/hooks/hooks.json +6 -0
  422. package/hooks/lib/cli-exit.js +560 -0
  423. package/hooks/lib/exit-code-registry.js +98 -0
  424. package/hooks/lib/git-cmd.js +210 -1
  425. package/hooks/lib/git-probe.js +84 -0
  426. package/hooks/lib/hook-exit.js +81 -0
  427. package/hooks/lib/injection-patterns.js +36 -6
  428. package/hooks/managed-hooks-registry.cjs +4 -0
  429. package/package.json +14 -9
  430. package/scripts/base64-scan.sh +74 -12
  431. package/scripts/build-hooks.js +12 -0
  432. package/scripts/check-glossary-refs.cjs +77 -15
  433. package/scripts/check-mutation-score-ratchet.cjs +156 -0
  434. package/scripts/ci-check-job-near-cap.cjs +49 -0
  435. package/scripts/ci-pr-mergeability.cjs +262 -0
  436. package/scripts/ci-test-scope.cjs +52 -12
  437. package/scripts/ci-timeout-report.cjs +230 -0
  438. package/scripts/docs-guard-registry.cjs +406 -0
  439. package/scripts/gen-capability-registry.cjs +8 -6
  440. package/scripts/gen-exit-code-docs.cjs +318 -0
  441. package/scripts/gen-exit-code-registry.cjs +891 -0
  442. package/scripts/gen-features.cjs +836 -0
  443. package/scripts/gen-hooks-cli-exit.cjs +239 -0
  444. package/scripts/gen-install-tree-fixtures.cjs +2 -2
  445. package/scripts/gen-loop-host-contract.cjs +189 -4
  446. package/scripts/gen-scripts-cli-exit.cjs +185 -0
  447. package/scripts/gen-state-md-docs.cjs +727 -0
  448. package/scripts/{test-failure-reasons.cjs → gsd-test-gate-reasons.cjs} +6 -0
  449. package/scripts/lib/ci-job-timing.cjs +72 -0
  450. package/scripts/lib/cli-exit.cjs +546 -44
  451. package/scripts/lib/drift-scan.cjs +32 -2
  452. package/scripts/lib/exit-code-registry.cjs +98 -0
  453. package/scripts/lib/ndjson-reporter.cjs +119 -0
  454. package/scripts/lib/shellcheck-fetch.cjs +247 -0
  455. package/scripts/lint-allow-test-rule-refs.allowlist.json +0 -6
  456. package/scripts/lint-allow-test-rule-refs.effective-ceiling.json +1 -1
  457. package/scripts/lint-allow-test-rule-refs.unverified-ceiling.json +1 -1
  458. package/scripts/lint-docs-guard-registration.cjs +495 -0
  459. package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +198 -0
  460. package/scripts/lint-eslint-glob-coverage.allowlist.json +4 -0
  461. package/scripts/{lint-fix-has-regression-test.cjs → lint-fix-has-regression-tests.cjs} +12 -6
  462. package/scripts/lint-health-diagnostic-rule-table.cjs +65 -8
  463. package/scripts/lint-mutation-test-derivation-drift.cjs +86 -0
  464. package/scripts/lint-phase-enumeration-drift.cjs +45 -14
  465. package/scripts/lint-phase-id-drift.cjs +133 -8
  466. package/scripts/lint-planning-prompt-drift.cjs +38 -1
  467. package/scripts/lint-portable-grep.cjs +176 -0
  468. package/scripts/lint-removed-but-needed.cjs +184 -16
  469. package/scripts/lint-response-language-coverage.cjs +524 -0
  470. package/scripts/lint-seam-enforcement.cjs +182 -0
  471. package/scripts/lint-slug-derivation-drift.cjs +921 -0
  472. package/scripts/lint-source-test-name-collision.cjs +241 -0
  473. package/scripts/lint-state-write-path-drift.cjs +337 -432
  474. package/scripts/lint-test-file-count.allowlist.json +124 -4
  475. package/scripts/lint-test-file-count.cjs +25 -3
  476. package/scripts/lint-unreachable-guard-drift.cjs +51 -64
  477. package/scripts/lint-vendored-deps.cjs +208 -35
  478. package/scripts/lint-workflow-shellcheck-baseline.json +1027 -0
  479. package/scripts/lint-workflow-shellcheck.cjs +614 -0
  480. package/scripts/mutation-matrix.cjs +599 -50
  481. package/scripts/npm-audit-baseline.cjs +376 -0
  482. package/scripts/prompt-injection-scan.sh +83 -14
  483. package/scripts/require-issue-link-policy.cjs +16 -1
  484. package/scripts/secret-scan.sh +75 -13
  485. package/scripts/select-docs-guards.cjs +56 -0
  486. package/scripts/sync-runtime-launcher.cjs +22 -3
  487. package/skills/gsd-discuss-phase/SKILL.md +1 -1
  488. package/skills/gsd-execute-phase/SKILL.md +1 -1
  489. package/skills/gsd-import/SKILL.md +1 -1
  490. package/skills/gsd-ns-workflow/SKILL.md +1 -0
  491. package/skills/gsd-phase/SKILL.md +1 -1
  492. package/skills/gsd-quick/SKILL.md +8 -4
  493. package/skills/gsd-quick-batch/SKILL.md +105 -0
  494. package/skills/gsd-surface/SKILL.md +18 -8
  495. package/vscode/package.json +1 -1
  496. package/bin/lib/ui-safety-gate.cjs +0 -109
  497. package/scripts/lint-emitted-drift-ack.cjs +0 -344
  498. package/scripts/state-write-path-drift-baseline.json +0 -19
@@ -31,6 +31,7 @@
31
31
 
32
32
  const { execFileSync } = require('child_process');
33
33
  const fs = require('fs');
34
+ const path = require('node:path');
34
35
 
35
36
  const { ExitError, runMain } = require('./lib/cli-exit.cjs');
36
37
 
@@ -92,28 +93,221 @@ function readStdinSync() {
92
93
  // confirmed equivalent mutant is acceptable.
93
94
  //
94
95
  // HOW TO UPDATE:
95
- // 1. Run the per-module Stryker shard locally.
96
- // 2. Note the reported score.
97
- // 3. Set minScore = floor(score) - 1 (never lower than current value).
98
- // 4. Open a PR — the CI gate will enforce the new floor on every future run.
96
+ // 1. The per-module Stryker shard CANNOT be run locally: Stryker's tap
97
+ // runner (see stryker.config.mjs) spawns
98
+ // `node --test-reporter=tap -r <hook> <testFile>` once per covering test
99
+ // file per mutant, and .claude/hooks/block-local-node-test.sh's matcher
100
+ // still denies that form — its pattern `node(\s+-\S+)*\s+--test([\s=-]|$)`
101
+ // matches `--test-reporter` because `-` is in the trailing character
102
+ // class. Push the branch instead and let CI run the shard for the
103
+ // changed module.
104
+ // 2. Read the measured score from the CI shard's output.
105
+ // 3. Set minScore = floor(measured) - 1 (never lower than current value)
106
+ // and update the matching RATCHET_BASELINE entry in the same diff.
107
+ // 4. Open/update the PR — the CI gate will enforce the new floor on every
108
+ // future run.
99
109
 
100
110
  /** Long-run target for all modules (ADR-456). */
101
111
  const TARGET_MUTATION_SCORE = 80;
102
112
 
113
+ // ── Derived test-list engine (#3881 follow-up, "one YAML parser" mutation-matrix
114
+ // piece 2) ─────────────────────────────────────────────────────────────────
115
+ //
116
+ // PROBLEM THIS REPLACES: `tests: [...]` used to be a hand-maintained array per
117
+ // module, and `stryker.config.mjs`'s DEFAULT_TEST_CMD hand-duplicated the union
118
+ // of every such array in a second literal. The two drifted independently — PR
119
+ // #3888 shipped four new frontmatter test files that were never added to either
120
+ // list, so their mutants had zero constraining coverage and the shard's score
121
+ // silently fell (see the frontmatter entry's own PR #3888 note below, kept for
122
+ // history). A hand list can be forgotten; a derivation cannot forget a file that
123
+ // exists on disk.
124
+ //
125
+ // SIGNAL: a test file that directly `require()`s a covered module's built
126
+ // artifact (`gsd-core/bin/lib/<name>.cjs`) is declaring, by that require, that
127
+ // it constrains that module. That signal alone is far too broad to feed a
128
+ // per-mutant re-run budget: measured directly (no filter) against this tree,
129
+ // config-schema alone picks up 22 files most of this repo's spawn-heavy
130
+ // integration suites require incidentally for fixture setup — including a file
131
+ // literally named `graphify-auto-update.slow.test.cjs`. Stryker's command
132
+ // runner re-runs the WHOLE test command once per mutant, so an incidental
133
+ // integration require would multiply that module's shard cost by 10-20x for
134
+ // zero mutation-killing benefit (those suites do not assert on config-schema's
135
+ // internals; they merely load it as a dependency of something else under test).
136
+ //
137
+ // NARROWING RULE: a file is auto-derived into a module's shard only when BOTH
138
+ // hold:
139
+ // 1. it directly requires that module's `gsd-core/bin/lib/<name>.cjs`, and
140
+ // 2. its own filename starts with the module's name followed by `.` or `-`
141
+ // (i.e. `<name>.test.cjs`, `<name>.unit.test.cjs`, `<name>.property.test.cjs`,
142
+ // `<name>-anything.test.cjs`) — the file DECLARES itself, by its own name,
143
+ // to be that module's dedicated test surface. This is the exact naming
144
+ // shape every entry in this registry already used before this change
145
+ // (`*.property.test.cjs` / `*.unit.test.cjs`, or `<name>.test.cjs`), now
146
+ // made load-bearing instead of merely conventional.
147
+ // Measured effect of narrowing config-schema this way: 22 candidates -> 1
148
+ // (config-schema.property.test.cjs, the file already in the shard) — the
149
+ // naming filter is what keeps the derivation from silently tripling that
150
+ // shard's cost, per the piece-2 "watch the cost consequence" requirement.
151
+ //
152
+ // ESCAPE HATCHES (both REQUIRED to be explicit, reasoned per-module entries —
153
+ // never a silent list):
154
+ // - `extraTests`: files that constrain this module (genuinely, by writing
155
+ // assertions against its behaviour) but do not match the naming rule
156
+ // above — either because the module was extracted from another file's
157
+ // tests (context-composer) or because the file's own name follows a
158
+ // different, still-legible convention (feat-3881-yaml-parser-consequences.test.cjs).
159
+ // - `excludeTests`: files that DO match the naming + require signal above
160
+ // (so the derivation would otherwise auto-include them) but are
161
+ // deliberately withheld from the per-mutant shard for a measured,
162
+ // documented reason (almost always: they are integration-shaped and
163
+ // spawn a subprocess per case, so Stryker's per-mutant re-run of the
164
+ // whole file cannot finish inside the shard's timeout — the exact #2790
165
+ // planning-inspect.test.cjs precedent this file already documented before
166
+ // this change; the derivation engine now enforces that precedent by
167
+ // construction instead of leaving it to reviewer memory).
168
+ // `computeModuleTests` combines all three into the final `tests` array; the
169
+ // guard `scripts/lint-mutation-test-derivation-drift.cjs` independently
170
+ // verifies every SIGNAL-matching file (require + naming rule, unfiltered by
171
+ // this module's own excludeTests) has an explicit disposition — auto-derived,
172
+ // named in extraTests, or named in excludeTests — so a file that newly starts
173
+ // matching the naming rule (like #3888's four files would have, had they been
174
+ // named `frontmatter*`) cannot silently fall through the cracks again.
175
+ const TESTS_DIR = path.join(__dirname, '..', 'tests');
176
+ let _testRequireCache = null;
177
+
178
+ /**
179
+ * Scan every `tests/*.test.cjs` file once and cache, per covered module name,
180
+ * which files directly `require('gsd-core/bin/lib/<name>.cjs')` (or a relative
181
+ * equivalent — `../gsd-core/bin/lib/<name>` etc. — the require path always
182
+ * ends in the literal segment matched below). Pure w.r.t. process lifetime;
183
+ * the tests/ directory does not change while this process runs.
184
+ *
185
+ * @returns {Map<string, Set<string>>} module name -> Set of basenames (e.g. 'frontmatter.test.cjs')
186
+ */
187
+ function scanTestRequires() {
188
+ if (_testRequireCache) return _testRequireCache;
189
+ const REQUIRE_RE = /require\(\s*['"](?:[./]*)?gsd-core\/bin\/lib\/([a-zA-Z0-9_-]+)(?:\.cjs)?['"]\s*\)/g;
190
+ const cache = new Map();
191
+ let entries;
192
+ try {
193
+ entries = fs.readdirSync(TESTS_DIR).filter((f) => f.endsWith('.test.cjs'));
194
+ } catch {
195
+ entries = [];
196
+ }
197
+ for (const file of entries) {
198
+ const text = fs.readFileSync(path.join(TESTS_DIR, file), 'utf8');
199
+ let m;
200
+ REQUIRE_RE.lastIndex = 0;
201
+ while ((m = REQUIRE_RE.exec(text))) {
202
+ const mod = m[1];
203
+ if (!cache.has(mod)) cache.set(mod, new Set());
204
+ cache.get(mod).add(file);
205
+ }
206
+ }
207
+ _testRequireCache = cache;
208
+ return cache;
209
+ }
210
+
211
+ /**
212
+ * Every test file that directly requires `<moduleName>`'s built artifact —
213
+ * the FULL, unfiltered signal set (used by the derivation-drift guard, which
214
+ * must see every candidate regardless of naming, so it can demand an explicit
215
+ * disposition for each one).
216
+ *
217
+ * @param {string} moduleName
218
+ * @returns {string[]} sorted basenames
219
+ */
220
+ function findRequiringTestFiles(moduleName) {
221
+ const set = scanTestRequires().get(moduleName);
222
+ return set ? [...set].sort() : [];
223
+ }
224
+
225
+ /** True when `file`'s own name declares it a dedicated test surface for `moduleName`
226
+ * (`<moduleName>.test.cjs`, or starts with `<moduleName>.` / `<moduleName>-`). */
227
+ function matchesModuleNamingRule(moduleName, file) {
228
+ return file === `${moduleName}.test.cjs`
229
+ || file.startsWith(`${moduleName}.`)
230
+ || file.startsWith(`${moduleName}-`);
231
+ }
232
+
233
+ /**
234
+ * Auto-derived candidates for `moduleName`: requires the module's artifact AND
235
+ * matches the naming rule. Does NOT apply that module's own `excludeTests` —
236
+ * callers combine that separately (`computeModuleTests` for the real shard,
237
+ * the guard for candidate enumeration).
238
+ */
239
+ function deriveNamedTests(moduleName) {
240
+ return findRequiringTestFiles(moduleName).filter((f) => matchesModuleNamingRule(moduleName, f));
241
+ }
242
+
243
+ /**
244
+ * Final `tests` array for a COVERED entry: auto-derived (require + naming
245
+ * rule) UNION `extraTests` MINUS `excludeTests`, sorted, each prefixed
246
+ * `tests/`. Throws if `excludeTests` names a file that isn't actually a
247
+ * derived candidate (an exclusion of nothing is a stale/typo'd entry, not a
248
+ * real decision) or if `extraTests` names a file already auto-derived (that
249
+ * would silently mask which mechanism is responsible for its presence).
250
+ */
251
+ function computeModuleTests(moduleName, entry) {
252
+ const derived = new Set(deriveNamedTests(moduleName));
253
+ const extra = entry.extraTests || [];
254
+ const exclude = entry.excludeTests || [];
255
+ for (const f of extra) {
256
+ if (derived.has(f)) {
257
+ throw new Error(`mutation-matrix: COVERED['${moduleName}'].extraTests names '${f}', which is already auto-derived — remove it from extraTests (it is redundant and hides which mechanism includes it)`);
258
+ }
259
+ }
260
+ for (const f of exclude) {
261
+ if (!derived.has(f)) {
262
+ throw new Error(`mutation-matrix: COVERED['${moduleName}'].excludeTests names '${f}', which is not an auto-derived candidate for this module — remove the stale exclusion`);
263
+ }
264
+ }
265
+ const excludeSet = new Set(exclude);
266
+ const final = new Set();
267
+ for (const f of derived) if (!excludeSet.has(f)) final.add(f);
268
+ for (const f of extra) final.add(f);
269
+ return [...final].sort().map((f) => `tests/${f}`);
270
+ }
271
+
103
272
  // ── Single source of truth: covered modules ───────────────────────────────────
104
- // Each entry: { cjs: '<built artifact>', tests: ['tests/...', ...], minScore: N }
273
+ // Each entry: { cjs: '<built artifact>', extraTests: [...], excludeTests: [...], minScore: N }
274
+ // `tests` is no longer hand-written — computeModuleTests() derives it below
275
+ // from direct `require()`s of the module's artifact (see the derivation-engine
276
+ // header above). extraTests/excludeTests are the two REQUIRED, reasoned escape
277
+ // hatches; leave both `[]` (omit the key) when a module needs neither.
105
278
  //
106
279
  // minScore is the CI break threshold for this module's shard.
107
280
  // Floors are measured scores minus 1–2 pts for run-to-run variance.
108
281
  // Measured CI scores 2026-06-14 (issue #1187, timeout-free — source of truth):
109
282
  // context-utilization 92.31% → floor 80 (target already met)
110
283
  // prompt-budget 68.33% → floor 66 (local was 99.6% — TIMEOUT INFLATION; CI is the truth)
111
- // frontmatter 63.35% → floor 62
284
+ // frontmatter 63.35% → floor 62 (SUPERSEDED — see 2026-08-25 below)
112
285
  // adr-parser 69.30% → floor 68
113
286
  // config-schema 54.55% → floor 52 (local was 69.7% — TIMEOUT INFLATION; CI is the truth)
114
287
  // active-workstream-store 81.91% → floor 80
115
288
  // core-utils 77.52% → floor 75
116
289
  //
290
+ // Measured CI score 2026-08-25 (#3706, PR 3867):
291
+ // frontmatter 66.67% → floor 65
292
+ // #3706 added agentScalarNeedsDoubleQuoting to frontmatter.cts and exported
293
+ // escapeDoubleQuoted, but the tests constraining them lived in
294
+ // tests/runtime-converters.test.cjs, which this lane does NOT run — the same trap the
295
+ // #1882 note on the frontmatter entry describes. The shard fell to 60.58 and broke the
296
+ // floor. Direct unit tests for both were added to tests/frontmatter.unit.test.cjs, each
297
+ // clause paired with a near-miss that must answer the opposite way, which took the module
298
+ // above its pre-change score. Floor ratcheted per the HOW TO UPDATE formula above, and
299
+ // RATCHET_BASELINE — which lives in tests/mutation-matrix-ratchet.test.cjs, not here — is
300
+ // updated in the same diff as that procedure requires.
301
+ //
302
+ // PR #3888 (#3881 follow-up): the frontmatter shard's new tests were never registered here
303
+ // (only the pre-existing frontmatter.property/unit + unusable-input ran), so Stryker's
304
+ // mutants in the new vendored-parser adapter code had nothing constraining them. Score fell
305
+ // to 55.8% against the 65 floor (748 killed / 593 survived / 17 timeout) and the shard also
306
+ // blew the 15-minute cap. Fixed by registering the branch's four new/changed frontmatter
307
+ // test files in the tests array above (see that entry's inline comment for which files and
308
+ // why) and giving the shard a measured 180-minute budget via timeoutMinutes. minScore left
309
+ // at 65 pending a fresh CI measurement with the corrected test list.
310
+ //
117
311
  // LESSON: floors MUST be calibrated from CI mutation runs (CI runs with
118
312
  // timeout≈0, deterministic). Local runs count timeouts as kills and
119
313
  // inflate scores significantly (prompt-budget: 99.6% local vs 68.3% CI;
@@ -122,84 +316,311 @@ const TARGET_MUTATION_SCORE = 80;
122
316
  const COVERED = {
123
317
  'context-utilization': {
124
318
  cjs: 'gsd-core/bin/lib/context-utilization.cjs',
125
- tests: [
126
- 'tests/context-utilization.property.test.cjs',
127
- ],
319
+ // Derived: context-utilization.property.test.cjs (pre-existing) +
320
+ // context-utilization.test.cjs (piece-2 derivation find: it directly requires and
321
+ // matches the naming rule, but was never hand-added to the old literal list —
322
+ // exactly the #3888 drift class this derivation exists to stop. Measured cost:
323
+ // +50ms over the property-only baseline (58ms -> 108ms, in-process, 0 subprocess
324
+ // spawns) — negligible for a shard whose floor is already at TARGET.
128
325
  // After mutation-killer assertions added in #1187: measured 92.31% (2026-06-14).
129
326
  // 3 survivors are __esModule boilerplate (genuinely equivalent CJS interop mutants).
130
- // minScore raised to TARGET (80) — module now meets ADR-456 goal.
131
- minScore: 80,
327
+ // minScore raised to TARGET (80) — module now meets ADR-456 goal. Not yet
328
+ // re-measured against the wider (derived) test list; the added file only adds
329
+ // assertions, never removes any, so the floor cannot have fallen.
330
+ // CI run 33012034388 (2026-08-25, #3881 ratchet): measured 92.31% (unchanged from
331
+ // the #1187 measurement above — same test list, re-confirmed by the mutation
332
+ // ratchet's own audit). Floor = floor(92.31) - 1 = 91.
333
+ minScore: 91,
132
334
  },
133
335
  // context-composer: extracted from prompt-budget by #2929. Needs its own entry because
134
336
  // mutation coverage does not migrate with relocated code — scoring only prompt-budget.cjs
135
- // would leave the extracted ladder unmeasured.
337
+ // would leave the extracted ladder unmeasured. Its own filename never matches the
338
+ // "context-composer*" naming rule for prompt-budget-parity.test.cjs / prompt-budget.unit.test.cjs
339
+ // — both genuinely constrain context-composer.cjs (the ladder was relocated INTO it), so
340
+ // both are declared via extraTests rather than silently missing from the derivation.
136
341
  'context-composer': {
137
342
  cjs: 'gsd-core/bin/lib/context-composer.cjs',
138
- tests: [
139
- 'tests/prompt-budget-parity.test.cjs',
140
- 'tests/prompt-budget.unit.test.cjs',
141
- 'tests/context-composer.test.cjs',
142
- 'tests/context-composer.property.test.cjs',
343
+ extraTests: [
344
+ 'prompt-budget-parity.test.cjs',
345
+ 'prompt-budget.unit.test.cjs',
143
346
  ],
144
- minScore: 66,
347
+ // CI run 33012034388 (2026-08-25, #3881 ratchet): measured 79.92%. Floor =
348
+ // floor(79.92) - 1 = 78.
349
+ minScore: 78,
145
350
  },
146
351
  'prompt-budget': {
147
352
  cjs: 'gsd-core/bin/lib/prompt-budget.cjs',
148
- tests: [
149
- 'tests/prompt-budget.property.test.cjs',
150
- 'tests/prompt-budget.unit.test.cjs',
151
- ],
353
+ // Derived: property + unit (pre-existing) plus two piece-2 derivation finds that
354
+ // directly require prompt-budget.cjs and match the naming rule but were never in the
355
+ // old hand list — prompt-budget-parity.test.cjs and prompt-budget.test.cjs. Measured
356
+ // cost: 475ms (2-file) -> 527ms (4-file), in-process, 0 subprocess spawns; +52ms is
357
+ // negligible next to this module's own mutant count.
152
358
  // CI 68.33% timeout-free (164 killed / 1 timeout / 240 total) 2026-06-14;
153
- // local was 99.6% — timeout inflation. Floor = 68 - 2 margin.
154
- minScore: 66,
359
+ // local was 99.6% — timeout inflation. Floor = 68 - 2 margin. Not yet re-measured
360
+ // against the wider (derived) test list; both added files only add assertions, never
361
+ // remove any, so the floor cannot have fallen.
362
+ // CI run 33012034388 (2026-08-25, #3881 ratchet): re-measured against the wider
363
+ // (derived) test list at 88.95%. Floor = floor(88.95) - 1 = 87.
364
+ minScore: 87,
155
365
  },
156
366
  frontmatter: {
157
367
  cjs: 'gsd-core/bin/lib/frontmatter.cjs',
158
- tests: [
159
- 'tests/frontmatter.property.test.cjs',
160
- 'tests/frontmatter.unit.test.cjs',
161
- // #1882 added the unterminated-fence detection to frontmatter.cjs, and the tests that
162
- // constrain it live here. Without this entry the mutants in that branch are covered by
163
- // no test in the shard, so the module's score drops even though the behaviour is tested.
164
- 'tests/unusable-input.test.cjs',
368
+ // extraTests: files that genuinely constrain frontmatter.cjs but do not match the
369
+ // "frontmatter*" naming rule, so the derivation cannot find them on its own —
370
+ // each earns its slot on evidence, not blanket inclusion (verified no two duplicate
371
+ // the same constraining assertion):
372
+ // - unusable-input.test.cjs: #1882 added the unterminated-fence detection to
373
+ // frontmatter.cjs, and the tests that constrain it live here. Without this entry
374
+ // the mutants in that branch are covered by no test in the shard.
375
+ // - feat-3881-yaml-parser-consequences.test.cjs: consequence/boundary matrix for the
376
+ // #3881 parser swap (state-transition interop, unusable-input counters, and — as of
377
+ // the piece-1 mutation-matrix fix below — the relocated anchor-alias-bomb + B1/B2
378
+ // block-scalar assertions). Nothing else in the shard drives
379
+ // extractFrontmatter/reconstructFrontmatter through those seams.
380
+ extraTests: [
381
+ 'unusable-input.test.cjs',
382
+ 'feat-3881-yaml-parser-consequences.test.cjs',
383
+ ],
384
+ // excludeTests: files the derivation WOULD auto-include (require frontmatter.cjs
385
+ // directly AND match the "frontmatter*" naming rule) but are deliberately withheld:
386
+ // - frontmatter-cli.test.cjs: 778-line CLI-integration file, 39 subprocess-spawn
387
+ // references (spawnSync/execFileSync/runGsdTools) — the same #2790
388
+ // planning-inspect.test.cjs shape (a `node --test <file>` invocation Stryker's
389
+ // command runner re-runs whole, once per mutant, at whatever its slowest spawn
390
+ // case costs). Never measured in a shard; excluded up front on the same evidence
391
+ // class rather than discovered by a timeout.
392
+ // - frontmatter.test.cjs: mutation-matrix piece 1 (#3881 follow-up). This
393
+ // 2932-line integration file cost 3132ms of the shard's ~4800ms per-run
394
+ // (measured via node:test's run() API — node --test is hard-blocked locally,
395
+ // this is the sanctioned substitute), which at ~1850 mutants (source grew 1.8x
396
+ // for #3881) projected to ~96 of the shard's 140-minute total. Its two
397
+ // genuinely-unique assertion classes — anchor-alias-bomb refusal (billion-laughs
398
+ // -style anchor/alias expansion must be rejected, not expanded) and the B1/B2
399
+ // block-scalar assertions (parsing commands/gsd/add-tests.md must not invent a
400
+ // phantom "Example" key) — were relocated verbatim into
401
+ // feat-3881-yaml-parser-consequences.test.cjs (already in this shard via
402
+ // extraTests above) rather than deleted, so the mutants they kill stay killed.
403
+ // frontmatter.test.cjs itself is UNCHANGED and keeps running in the normal
404
+ // (non-mutation) suite — only the mutation shard drops it.
405
+ excludeTests: [
406
+ 'frontmatter-cli.test.cjs',
407
+ 'frontmatter.test.cjs',
165
408
  ],
166
- minScore: 62,
409
+ minScore: 65,
410
+ // MEASUREMENT, not a projection. Under the tap runner with coverageAnalysis: 'perTest'
411
+ // (#3915), Stryker now re-runs only the test files that cover each mutated line instead
412
+ // of all six files for every one of ~1900 mutants. Measured result: the frontmatter shard
413
+ // completed in 713s (11m53s) — GitHub Actions run 33026833181, job wall time including
414
+ // checkout and `npm ci` — against 1751s (29m11s) on the command runner in run
415
+ // 33021042847. A 59% reduction.
416
+ // 20 minutes is 1.68x the measured 713s. The override is not simply deleted because the
417
+ // shared default is 15 minutes, which 11m53s would fit inside — but only at 79% of
418
+ // budget — and this module's mutant count grew 1.8x in a single change (#3881), so a
419
+ // shard sitting at 79% of the shared default is one growth spurt from a red lane. 20
420
+ // keeps a real margin while still cutting the previous 60-minute budget by 3x.
421
+ // Scoped to this shard only via timeoutMinutes below — every other shard keeps the
422
+ // 15-minute default emitted by buildResult(), well under GitHub Actions' 360-minute job
423
+ // ceiling.
424
+ timeoutMinutes: 20,
425
+ // isolation: no knob left to tune (#3915). Per-file process isolation is now INHERENT
426
+ // to @stryker-mutator/tap-runner — it drives Node's own `--test-reporter=tap` once per
427
+ // covering test FILE, so every file already runs in its own process by construction.
428
+ // The prior audit's 'none' vs 'process' comparison (recorded here before this change)
429
+ // is moot: there is nothing left to opt in or out of.
167
430
  },
431
+ // adr-parser / config-schema / active-workstream-store / core-utils: derivation reproduces
432
+ // their prior hand lists exactly (every constraining file's own name already matched the
433
+ // "<module>*" rule) — no extraTests/excludeTests needed. Note config-schema in particular:
434
+ // an UNFILTERED require-scan finds 22 files that require config-schema.cjs, but only
435
+ // config-schema.property.test.cjs matches the naming rule — the naming filter is what
436
+ // keeps this shard from silently ballooning to include spawn-heavy integration suites
437
+ // (e.g. graphify-auto-update.slow.test.cjs) that merely load config-schema as a fixture
438
+ // dependency of something else under test.
168
439
  'adr-parser': {
169
440
  cjs: 'gsd-core/bin/lib/adr-parser.cjs',
170
- tests: [
171
- 'tests/adr-parser.property.test.cjs',
172
- 'tests/adr-parser.test.cjs',
173
- 'tests/adr-parser.unit.test.cjs',
174
- ],
175
441
  minScore: 68,
176
442
  },
177
443
  'config-schema': {
178
444
  cjs: 'gsd-core/bin/lib/config-schema.cjs',
179
- tests: [
180
- 'tests/config-schema.property.test.cjs',
181
- ],
182
445
  // CI 54.55% timeout-free (18 killed / 0 timeout / 33 total) 2026-06-14;
183
446
  // local was 69.7% — timeout inflation. Floor = 54 - 2 margin.
184
- minScore: 52,
447
+ // CI run 33012034388 (2026-08-25, #3881 ratchet): measured 75.51%. Floor =
448
+ // floor(75.51) - 1 = 74.
449
+ minScore: 74,
185
450
  },
186
451
  'active-workstream-store': {
187
452
  cjs: 'gsd-core/bin/lib/active-workstream-store.cjs',
188
- tests: [
189
- 'tests/active-workstream-store.test.cjs',
190
- 'tests/active-workstream-store.unit.test.cjs',
191
- ],
192
- minScore: 80,
453
+ // CI run 33012034388 (2026-08-25, #3881 ratchet): measured 87.42%. Floor =
454
+ // floor(87.42) - 1 = 86.
455
+ minScore: 86,
193
456
  },
194
457
  'core-utils': {
195
458
  cjs: 'gsd-core/bin/lib/core-utils.cjs',
196
- tests: [
197
- 'tests/core-utils.test.cjs',
198
- ],
199
459
  minScore: 75, // measured 77.52% (2026-06-14, issue #1187); floor = 77 - 2
200
460
  },
461
+ // planning-inspect / plan-document / planning-command-router: net-new modules
462
+ // added by #2790. Registered here so the Stryker gate stops SKIPPING them
463
+ // (previously has_work: "false" — ~1000 LOC entirely outside mutation scoring).
464
+ //
465
+ // WHY THESE SHARDS POINT AT tests/planning-inspect.unit.test.cjs, NOT
466
+ // tests/planning-inspect.test.cjs. CI evidence: two shards pointed at the
467
+ // integration file were CANCELLED at the workflow's 15-minute cap —
468
+ // "Mutation testing 4% (elapsed: ~3m, remaining: ~1h 19m) 27/640 tested".
469
+ // tests/planning-inspect.test.cjs is INTEGRATION-shaped (91 cases, most
470
+ // spawning a `gsd-tools` child process via `runGsdTools`); Stryker's command
471
+ // runner treats the whole `node --test <file>` invocation as ONE test costing
472
+ // whatever the slowest case costs (measured ~20s), and re-runs that entire
473
+ // file once per mutant — 640 mutants x 20s cannot finish in 15 minutes.
474
+ // tests/planning-inspect.unit.test.cjs is the dedicated, spawn-free,
475
+ // in-process mutation surface for exactly these three modules (measured
476
+ // locally: the whole file runs in well under a second) — the same shape
477
+ // every other entry in this registry already uses (*.property.test.cjs /
478
+ // *.unit.test.cjs). The integration suite is UNAFFECTED by this change: it
479
+ // keeps running in full in the normal (non-mutation) test job, and remains
480
+ // the source of truth for spawn-boundary/CLI-dispatch/read-only-proof
481
+ // behaviour that an in-process unit file cannot exercise.
482
+ //
483
+ // Measured CI scores (GitHub Actions run 32392791843, all three shards
484
+ // PASSED — not a local run; mutation shards run `node --test`, hard-blocked
485
+ // in this repo's local environment):
486
+ // planning-command-router 95.65% → floor 94 (already exceeds TARGET_MUTATION_SCORE (80))
487
+ // plan-document 76.58% → floor 75
488
+ // planning-inspect 57.03% → floor 56 (well below TARGET (80) — ratchet
489
+ // candidate; comfortably clears its own floor but has real room to grow.
490
+ // Raise as its tests improve, never lower it.)
491
+ //
492
+ // All three shards point at tests/planning-inspect.unit.test.cjs (in-process,
493
+ // spawn-free, ~0.3s dry run), not tests/planning-inspect.test.cjs — that is
494
+ // what made measurement possible at all. The integration file spawns a
495
+ // subprocess per case via runGsdTools; Stryker's command runner treats the
496
+ // whole `node --test <file>` invocation as one test costing whatever the
497
+ // slowest case costs (measured ~20s), and re-runs that entire file once per
498
+ // mutant, so 640 mutants x 20s could not finish inside the 15-minute shard
499
+ // cap. The integration suite is unaffected by this change: it keeps running
500
+ // in full in the normal (non-mutation) test job.
501
+ // planning-inspect's own name matches "planning-inspect.unit.test.cjs" via the naming
502
+ // rule, so that file is auto-derived. planning-inspect.test.cjs (the excluded integration
503
+ // file the comment above names) ALSO matches the naming rule and directly requires the
504
+ // module, so it must be an explicit excludeTests entry now — the derivation would
505
+ // otherwise auto-include it and reproduce the exact 15-minute-cap cancellation the
506
+ // comment above documents.
507
+ 'planning-inspect': {
508
+ cjs: 'gsd-core/bin/lib/planning-inspect.cjs',
509
+ excludeTests: ['planning-inspect.test.cjs'],
510
+ minScore: 56,
511
+ },
512
+ // plan-document / planning-command-router: their own names never appear in any test
513
+ // filename (the shared dedicated unit file is named after planning-inspect, the module
514
+ // #2790 extracted them alongside), so the naming-rule derivation finds nothing — same
515
+ // cross-cutting shape as context-composer above. Declared via extraTests.
516
+ 'plan-document': {
517
+ cjs: 'gsd-core/bin/lib/plan-document.cjs',
518
+ extraTests: ['planning-inspect.unit.test.cjs'],
519
+ minScore: 75,
520
+ },
521
+ 'planning-command-router': {
522
+ cjs: 'gsd-core/bin/lib/planning-command-router.cjs',
523
+ extraTests: ['planning-inspect.unit.test.cjs'],
524
+ minScore: 94,
525
+ },
526
+ // model-catalog: net-new registration by #3007. The module was entirely
527
+ // outside mutation scoring (has_work: "false") before this entry, so the
528
+ // #3007 per-model Codex effort rewrite (renderEffortForRuntime's
529
+ // CODEX_MODEL_EFFORT lookup, the 'ultra' policy rejection, the ladder
530
+ // walk-up clamp) had zero mutation coverage.
531
+ //
532
+ // Same #2790 precedent as planning-inspect above: this shard points at a
533
+ // dedicated tests/model-catalog.unit.test.cjs, NOT tests/model-resolver.test.cjs
534
+ // — that integration file uses runGsdTools heavily and would hit the same
535
+ // 15-minute shard-cap cancellation #2790 documented (a `node --test <file>`
536
+ // invocation is ONE test costing whatever its slowest case costs, re-run
537
+ // per mutant). tests/model-catalog.unit.test.cjs is spawn-free, in-process,
538
+ // and runs in well under a second.
539
+ //
540
+ // Prior context (#3007, GitHub Actions run 32605073352, job 97108869486):
541
+ // model-catalog 59.62% → floor 58 (248 killed, 168 survived, 0 timeouts,
542
+ // 0 errors). SUPERSEDED by the 2026-08-27 measurement below.
543
+ //
544
+ // The shard completed in 57 seconds — concrete evidence the spawn-free
545
+ // unit-file design above worked: the #2790 precedent's 15-minute shard-cap
546
+ // cancellations do not apply here, and for comparison the `frontmatter`
547
+ // shard in the same run took 9m46s.
548
+ // model-catalog: derivation finds two files never in the old hand list —
549
+ // model-catalog-runtime-defaults.test.cjs and model-catalog-valid-tiers.test.cjs — both
550
+ // directly require model-catalog.cjs and match the "model-catalog*" naming rule. Measured
551
+ // cost: 50ms (1-file) -> 196ms (3-file), in-process, 0 subprocess spawns; still far under
552
+ // the 57s the shard already measured for the single-file set.
553
+ //
554
+ // CI run 33029755081 (2026-08-27, #3915): measured 75.26% (295 killed / 49 survived /
555
+ // 48 no-coverage / 24 runtime-error, totalValid 392). Floor = floor(75.26) - 1 = 74,
556
+ // following this file's documented convention.
557
+ //
558
+ // Why it moved so far: under #3915's tap-runner swap this shard first came back at
559
+ // 57.91% against the old floor of 58. Diagnosis from the mutation report's own JSON:
560
+ // all 24 of its RuntimeError mutants are in the module's load-time catalog bootstrap,
561
+ // so mutating them makes model-catalog.cjs throw at `require`. Under `node --test`
562
+ // that is a failed test file and the mutant counts as Killed; under the tap runner
563
+ // the process dies before emitting TAP, which Stryker classifies RuntimeError and
564
+ // EXCLUDES from the denominator. Adding those 24 back as killed reproduces
565
+ // 248/416 = 59.62 exactly — the pre-swap #3007 number — so detection never
566
+ // regressed, only its classification changed.
567
+ //
568
+ // The floor was NOT lowered to absorb that. 11 new behavioural tests in
569
+ // tests/model-catalog.unit.test.cjs killed 68 previously-surviving mutants (227 ->
570
+ // 295 killed), taking the module from 57.91 to 75.26 — now within striking distance
571
+ // of TARGET_MUTATION_SCORE (80) instead of the 59.62 it sat at before this change.
572
+ //
573
+ // The floor MUST come from a CI shard, never a local run — same rule as every other
574
+ // entry in this file.
575
+ 'model-catalog': {
576
+ cjs: 'gsd-core/bin/lib/model-catalog.cjs',
577
+ minScore: 74,
578
+ },
579
+ // state-contract: net-new module from #3227. Without this entry the
580
+ // Stryker gate reports has_work: "false" and SKIPS it entirely — the
581
+ // exact gap #2790 (planning-inspect / plan-document / planning-command-router)
582
+ // and #3007 (model-catalog) each had to fix after the fact.
583
+ //
584
+ // Same #2790 precedent as planning-inspect / model-catalog above: this
585
+ // shard points at tests/state-contract.unit.test.cjs, NOT
586
+ // tests/state-contract.test.cjs — the latter spawns a `gsd-tools` child
587
+ // process per case via runGsdTools, and Stryker's command runner treats
588
+ // the whole `node --test <file>` invocation as ONE test costing whatever
589
+ // its slowest case costs, re-run once per mutant, so it cannot finish
590
+ // inside the 15-minute shard cap. tests/state-contract.unit.test.cjs is
591
+ // spawn-free and in-process.
592
+ //
593
+ // Measured CI score (GitHub Actions run 32769289750, job 97565813640,
594
+ // `Stryker (state-contract)`, PASSED in 2m23s):
595
+ // state-contract 66.25% → floor 65 (below TARGET_MUTATION_SCORE (80) —
596
+ // ratchet candidate like planning-inspect (56) and model-catalog (58):
597
+ // comfortably clears its own floor but has real room to grow. Raise as
598
+ // its tests improve, never lower it.)
599
+ // Floor follows this file's documented rule, minScore = floor(measured) - 1,
600
+ // matching the sibling precedent exactly (57.03 → 56, 76.58 → 75,
601
+ // 95.65 → 94, 59.62 → 58, 66.25 → 65).
602
+ //
603
+ // The floor MUST come from a CI shard, never a local run: local runs count
604
+ // timeouts as kills and inflate scores badly (this file already records
605
+ // prompt-budget 99.6% local vs 68.33% CI, and config-schema 69.7% local vs
606
+ // 54.55% CI).
607
+ // state-contract.test.cjs matches the naming rule and directly requires state-contract.cjs
608
+ // but is the same spawn-heavy integration shape as planning-inspect.test.cjs (446 lines,
609
+ // 16 subprocess-spawn references) — excluded explicitly rather than left to fall through.
610
+ 'state-contract': {
611
+ cjs: 'gsd-core/bin/lib/state-contract.cjs',
612
+ excludeTests: ['state-contract.test.cjs'],
613
+ minScore: 65,
614
+ },
201
615
  };
202
616
 
617
+ // Compute the final, derived `tests` array for every COVERED entry. Done once, after the
618
+ // full COVERED literal above is built, so every entry's extraTests/excludeTests declarations
619
+ // are visible to computeModuleTests regardless of source order.
620
+ for (const [moduleName, entry] of Object.entries(COVERED)) {
621
+ entry.tests = computeModuleTests(moduleName, entry);
622
+ }
623
+
203
624
  // ── Files that, when changed, invalidate ALL modules ─────────────────────────
204
625
  // Changes to the Stryker config, this script itself, or any covered test file
205
626
  // affect all mutation scores and must force a full re-run.
@@ -296,6 +717,11 @@ function buildResult(moduleNames) {
296
717
  mutate: COVERED[name].cjs,
297
718
  tests: COVERED[name].tests.join(' '),
298
719
  minScore: COVERED[name].minScore,
720
+ // Per-shard CI job timeout in minutes. Defaults to 15 (the shared per-shard budget);
721
+ // only a module that documents a measured need for more (see the frontmatter entry
722
+ // above) sets a higher value. Threaded through mutation.yml's job-level
723
+ // `timeout-minutes: ${{ matrix.timeoutMinutes }}`.
724
+ timeoutMinutes: COVERED[name].timeoutMinutes || 15,
299
725
  }));
300
726
 
301
727
  return {
@@ -315,6 +741,7 @@ function printHuman(result, changedFiles) {
315
741
  console.log(` mutate: ${shard.mutate}`);
316
742
  console.log(` tests: ${shard.tests}`);
317
743
  console.log(` minScore: ${shard.minScore}`);
744
+ console.log(` timeoutMinutes:${shard.timeoutMinutes}`);
318
745
  }
319
746
  }
320
747
 
@@ -375,8 +802,130 @@ function resolveMutationBreak(raw) {
375
802
  return n;
376
803
  }
377
804
 
805
+ /**
806
+ * Sorted, de-duplicated union of every COVERED module's `tests` array. This
807
+ * is the tap-runner's local/full-run default (see resolveMutationTestFiles
808
+ * below) — the same union stryker.config.mjs's since-removed DEFAULT_TEST_CMD
809
+ * string used to build for the command runner.
810
+ *
811
+ * @returns {string[]}
812
+ */
813
+ function allCoveredTests() {
814
+ return [...new Set(Object.values(COVERED).flatMap((mod) => mod.tests))].sort();
815
+ }
816
+
817
+ // ── MUTATION_TEST_FILES resolver ──────────────────────────────────────────────
818
+ /**
819
+ * Resolves the per-shard tap-runner test-file list from the MUTATION_TEST_FILES
820
+ * env var. Fail-closed twin of resolveMutationBreak above, for #3915's swap from
821
+ * Stryker's `command` runner to `@stryker-mutator/tap-runner`: the tap runner
822
+ * takes an explicit `tap.testFiles` array rather than a shell command, so there
823
+ * is no single string to inject a per-shard test list into — this function is
824
+ * that injection point instead.
825
+ *
826
+ * Fail-closed contract:
827
+ * - undefined → allCoveredTests() (local run: no env set, documented backstop)
828
+ * - non-string → throws (the realistic caller mistake: COVERED[*].tests is an
829
+ * array, but the env var this reads is the SPACE-JOINED STRING form of it —
830
+ * passing the array itself, or any other non-string, is a wiring bug)
831
+ * - set but empty/whitespace-only → throws (CI shard wiring is broken:
832
+ * matrix.tests missing)
833
+ * - otherwise → trim, split on whitespace, de-duplicate, sort, and, per
834
+ * entry: (1) resolve it against the repo root and reject any entry whose
835
+ * resolved path escapes the repo root (e.g. via `../` segments); (2)
836
+ * reject any entry that does not exist on disk, or that exists but is
837
+ * not a regular file (e.g. names a directory) — each failure throws
838
+ * naming the offending entry(ies)
839
+ *
840
+ * This function is the single call site for reading MUTATION_TEST_FILES.
841
+ * stryker.config.mjs imports and calls it, so a bad value must fail
842
+ * immediately rather than silently degrade: the tap runner's own
843
+ * `findTestyLookingFiles` resolves `tap.testFiles` via `glob()`, and a
844
+ * non-matching glob pattern yields an EMPTY list SILENTLY — which would
845
+ * produce a fast, confident, meaningless mutation run (every mutant reported
846
+ * killed or survived against zero tests) instead of a loud error.
847
+ *
848
+ * The `undefined` branch also runs the same on-disk existence check as every
849
+ * other branch, so a stale `extraTests`/`excludeTests` entry in COVERED fails
850
+ * loudly here rather than silently producing a shard pointed at a phantom file.
851
+ *
852
+ * @param {string|undefined} raw - value of process.env.MUTATION_TEST_FILES
853
+ * @returns {string[]} sorted, de-duplicated, existence-checked test file paths
854
+ */
855
+ function resolveMutationTestFiles(raw) {
856
+ let entries;
857
+ if (raw === undefined) {
858
+ // Local run with no MUTATION_TEST_FILES set — use the derived full-run default.
859
+ entries = allCoveredTests();
860
+ } else if (typeof raw !== 'string') {
861
+ throw new Error(
862
+ `MUTATION_TEST_FILES must be a string (space-joined test file paths), got ${typeof raw} — ` +
863
+ "COVERED[*].tests is an array internally, but the env var this reads is always the " +
864
+ 'SPACE-JOINED STRING form of it; passing the array (or any other non-string) directly is a wiring bug'
865
+ );
866
+ } else if (raw.trim() === '') {
867
+ throw new Error(
868
+ 'MUTATION_TEST_FILES is set but empty — CI shard wiring is broken (matrix.tests missing?)'
869
+ );
870
+ } else {
871
+ entries = [...new Set(raw.trim().split(/\s+/))].sort();
872
+ }
873
+
874
+ const repoRoot = path.join(__dirname, '..');
875
+
876
+ const escaped = [];
877
+ const missing = [];
878
+ const notFile = [];
879
+ for (const entry of entries) {
880
+ const resolved = path.resolve(repoRoot, entry);
881
+ const rel = path.relative(repoRoot, resolved);
882
+ if (rel === '' || rel.startsWith('..') || path.isAbsolute(rel)) {
883
+ escaped.push(entry);
884
+ continue;
885
+ }
886
+ if (!fs.existsSync(resolved)) {
887
+ missing.push(entry);
888
+ continue;
889
+ }
890
+ if (!fs.statSync(resolved).isFile()) {
891
+ notFile.push(entry);
892
+ }
893
+ }
894
+
895
+ if (escaped.length > 0) {
896
+ throw new Error(
897
+ `MUTATION_TEST_FILES names ${escaped.length} entry(ies) that escape the repo root: ${escaped.join(', ')}`
898
+ );
899
+ }
900
+ if (missing.length > 0) {
901
+ throw new Error(
902
+ `MUTATION_TEST_FILES names ${missing.length} file(s) that do not exist on disk: ${missing.join(', ')}`
903
+ );
904
+ }
905
+ if (notFile.length > 0) {
906
+ throw new Error(
907
+ `MUTATION_TEST_FILES names ${notFile.length} entry(ies) that are not a regular file: ${notFile.join(', ')}`
908
+ );
909
+ }
910
+
911
+ return entries;
912
+ }
913
+
378
914
  // Export internals for programmatic use (tests/mutation-matrix-ratchet.test.cjs).
379
915
  // The require.main guard prevents main() from running when this file is require()d.
380
- module.exports = { COVERED, TARGET_MUTATION_SCORE, resolveMutationBreak, readStdinSync };
916
+ module.exports = {
917
+ COVERED,
918
+ TARGET_MUTATION_SCORE,
919
+ resolveMutationBreak,
920
+ allCoveredTests,
921
+ resolveMutationTestFiles,
922
+ readStdinSync,
923
+ // Derivation-engine internals — exported for tests/mutation-test-derivation-drift.test.cjs
924
+ // and scripts/lint-mutation-test-derivation-drift.cjs.
925
+ findRequiringTestFiles,
926
+ matchesModuleNamingRule,
927
+ deriveNamedTests,
928
+ computeModuleTests,
929
+ };
381
930
 
382
931
  if (require.main === module) runMain(main);