@opengsd/gsd-core 1.10.0 → 1.12.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 (544) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/agents/gsd-code-fixer.md +1 -1
  4. package/agents/gsd-debug-session-manager.md +12 -1
  5. package/agents/gsd-debugger.md +1 -1
  6. package/agents/gsd-doc-synthesizer.md +2 -4
  7. package/agents/gsd-dom-verifier.md +169 -0
  8. package/agents/gsd-eval-auditor.md +1 -1
  9. package/agents/gsd-executor.md +22 -14
  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 +5 -3
  13. package/agents/gsd-pattern-mapper.md +11 -0
  14. package/agents/gsd-phase-researcher.md +23 -2
  15. package/agents/gsd-plan-checker.md +50 -53
  16. package/agents/gsd-planner.md +50 -50
  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 +63 -4
  21. package/agents/gsd-ui-researcher.md +41 -3
  22. package/agents/gsd-user-profiler.md +3 -0
  23. package/agents/gsd-verifier.md +13 -4
  24. package/bin/install.js +1448 -1103
  25. package/commands/gsd/code-review.md +1 -1
  26. package/commands/gsd/discuss-phase.md +1 -1
  27. package/commands/gsd/execute-phase.md +1 -1
  28. package/commands/gsd/import.md +1 -1
  29. package/commands/gsd/map-codebase.md +1 -1
  30. package/commands/gsd/mempalace-capture.md +1 -1
  31. package/commands/gsd/mempalace-recall.md +1 -1
  32. package/commands/gsd/new-milestone.md +1 -1
  33. package/commands/gsd/quick.md +9 -5
  34. package/commands/gsd/review-backlog.md +2 -1
  35. package/commands/gsd/verify-work.md +1 -1
  36. package/gsd-core/bin/gsd-tools.cjs +1035 -138
  37. package/gsd-core/bin/lib/active-workstream-store.cjs +146 -22
  38. package/gsd-core/bin/lib/adr-parser.cjs +13 -7
  39. package/gsd-core/bin/lib/agent-install-check.cjs +392 -32
  40. package/gsd-core/bin/lib/api-coverage.cjs +33 -14
  41. package/gsd-core/bin/lib/artifacts.cjs +5 -0
  42. package/gsd-core/bin/lib/assumption-delta.cjs +32 -15
  43. package/gsd-core/bin/lib/audit-command-router.cjs +9 -2
  44. package/gsd-core/bin/lib/audit.cjs +1026 -268
  45. package/gsd-core/bin/lib/broken-windows.cjs +306 -28
  46. package/gsd-core/bin/lib/capability-consent.cjs +149 -15
  47. package/gsd-core/bin/lib/capability-lifecycle.cjs +45 -0
  48. package/gsd-core/bin/lib/capability-lock.cjs +10 -4
  49. package/gsd-core/bin/lib/capability-registry.cjs +845 -130
  50. package/gsd-core/bin/lib/capability-source.cjs +92 -0
  51. package/gsd-core/bin/lib/capability-state.cjs +18 -3
  52. package/gsd-core/bin/lib/capability-trust.cjs +444 -25
  53. package/gsd-core/bin/lib/capability-validator.cjs +700 -40
  54. package/gsd-core/bin/lib/capability-writer.cjs +3 -2
  55. package/gsd-core/bin/lib/check-command-router.cjs +216 -42
  56. package/gsd-core/bin/lib/claude-orchestration.cjs +56 -3
  57. package/gsd-core/bin/lib/cli-exit.cjs +496 -10
  58. package/gsd-core/bin/lib/code-review-depth.cjs +288 -0
  59. package/gsd-core/bin/lib/codex-agent-toml.cjs +735 -0
  60. package/gsd-core/bin/lib/command-aliases.cjs +22 -0
  61. package/gsd-core/bin/lib/command-arg-projection.cjs +144 -14
  62. package/gsd-core/bin/lib/command-roster.cjs +44 -1
  63. package/gsd-core/bin/lib/command-routing-hub.cjs +31 -2
  64. package/gsd-core/bin/lib/commands.cjs +1172 -108
  65. package/gsd-core/bin/lib/commonjs-marker.cjs +12 -6
  66. package/gsd-core/bin/lib/complexity-trigger.cjs +1192 -0
  67. package/gsd-core/bin/lib/config-loader.cjs +187 -23
  68. package/gsd-core/bin/lib/config.cjs +102 -3
  69. package/gsd-core/bin/lib/configuration.cjs +129 -37
  70. package/gsd-core/bin/lib/core-utils.cjs +208 -33
  71. package/gsd-core/bin/lib/decisions.cjs +23 -0
  72. package/gsd-core/bin/lib/edge-probe.cjs +9 -1
  73. package/gsd-core/bin/lib/estimate-cli.cjs +55 -11
  74. package/gsd-core/bin/lib/exit-code-registry.cjs +98 -0
  75. package/gsd-core/bin/lib/fallow-runner.cjs +20 -44
  76. package/gsd-core/bin/lib/frontmatter.cjs +899 -229
  77. package/gsd-core/bin/lib/gap-checker.cjs +95 -10
  78. package/gsd-core/bin/lib/git-base-branch.cjs +276 -39
  79. package/gsd-core/bin/lib/gsd2-import.cjs +10 -1
  80. package/gsd-core/bin/lib/health-diagnostic-rules/agent-install.cjs +101 -0
  81. package/gsd-core/bin/lib/health-diagnostic-rules/config-validation.cjs +348 -0
  82. package/gsd-core/bin/lib/health-diagnostic-rules/consistency.cjs +149 -0
  83. package/gsd-core/bin/lib/health-diagnostic-rules/install-surface-shadowing.cjs +98 -0
  84. package/gsd-core/bin/lib/health-diagnostic-rules/milestone-archive-hygiene.cjs +100 -0
  85. package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +222 -0
  86. package/gsd-core/bin/lib/health-diagnostic-rules/roadmap-disk-consistency.cjs +268 -0
  87. package/gsd-core/bin/lib/health-diagnostic-rules/root-existence.cjs +161 -0
  88. package/gsd-core/bin/lib/health-diagnostic-rules/state-consistency.cjs +303 -0
  89. package/gsd-core/bin/lib/health-diagnostic-rules/worktree-health.cjs +187 -0
  90. package/gsd-core/bin/lib/health-diagnostic-types.cjs +68 -0
  91. package/gsd-core/bin/lib/health-diagnostic.cjs +451 -0
  92. package/gsd-core/bin/lib/host-integration.cjs +39 -6
  93. package/gsd-core/bin/lib/host-runtime-detection.cjs +134 -0
  94. package/gsd-core/bin/lib/init-command-router.cjs +118 -21
  95. package/gsd-core/bin/lib/init.cjs +439 -168
  96. package/gsd-core/bin/lib/install-effort-resolver.cjs +73 -30
  97. package/gsd-core/bin/lib/install-engine.cjs +811 -259
  98. package/gsd-core/bin/lib/install-fs-adapter.cjs +262 -0
  99. package/gsd-core/bin/lib/install-model-override-resolver.cjs +235 -0
  100. package/gsd-core/bin/lib/install-profiles.cjs +212 -61
  101. package/gsd-core/bin/lib/install-scope.cjs +270 -0
  102. package/gsd-core/bin/lib/install-shadow-report.cjs +385 -0
  103. package/gsd-core/bin/lib/installed-surface-resolver.cjs +381 -0
  104. package/gsd-core/bin/lib/installer-migration-report.cjs +3 -0
  105. package/gsd-core/bin/lib/installer-migrations/010-antigravity-retire-confighome-artifacts.cjs +169 -0
  106. package/gsd-core/bin/lib/installer-migrations.cjs +148 -38
  107. package/gsd-core/bin/lib/intel.cjs +101 -26
  108. package/gsd-core/bin/lib/io.cjs +170 -15
  109. package/gsd-core/bin/lib/learnings.cjs +85 -14
  110. package/gsd-core/bin/lib/legacy-cleanup.cjs +8 -2
  111. package/gsd-core/bin/lib/markdown-sectionizer.cjs +2 -1
  112. package/gsd-core/bin/lib/markdown-table.cjs +183 -22
  113. package/gsd-core/bin/lib/milestone-lock.cjs +248 -0
  114. package/gsd-core/bin/lib/milestone.cjs +842 -73
  115. package/gsd-core/bin/lib/model-catalog.cjs +232 -16
  116. package/gsd-core/bin/lib/model-resolver.cjs +193 -68
  117. package/gsd-core/bin/lib/normalize-test-command.cjs +1 -1
  118. package/gsd-core/bin/lib/onboard-projection.cjs +5 -1
  119. package/gsd-core/bin/lib/pattern.cjs +122 -0
  120. package/gsd-core/bin/lib/phase-estimation.cjs +18 -9
  121. package/gsd-core/bin/lib/phase-id.cjs +514 -40
  122. package/gsd-core/bin/lib/phase-lifecycle.cjs +52 -19
  123. package/gsd-core/bin/lib/phase-locator.cjs +262 -34
  124. package/gsd-core/bin/lib/phase.cjs +1038 -214
  125. package/gsd-core/bin/lib/plan-dependency-graph.cjs +72 -1
  126. package/gsd-core/bin/lib/plan-document.cjs +263 -0
  127. package/gsd-core/bin/lib/plan-drift-guard.cjs +120 -0
  128. package/gsd-core/bin/lib/plan-scan.cjs +98 -3
  129. package/gsd-core/bin/lib/planning-command-router.cjs +61 -0
  130. package/gsd-core/bin/lib/planning-inspect.cjs +1168 -0
  131. package/gsd-core/bin/lib/planning-scope.cjs +31 -0
  132. package/gsd-core/bin/lib/planning-snapshot.cjs +894 -0
  133. package/gsd-core/bin/lib/planning-workspace.cjs +112 -6
  134. package/gsd-core/bin/lib/probe-core.cjs +5 -2
  135. package/gsd-core/bin/lib/profile-output.cjs +1 -1
  136. package/gsd-core/bin/lib/profile-pipeline-command-router.cjs +50 -7
  137. package/gsd-core/bin/lib/profile-pipeline.cjs +6 -3
  138. package/gsd-core/bin/lib/real-home-guard.cjs +419 -0
  139. package/gsd-core/bin/lib/refactor-trigger-command-router.cjs +766 -0
  140. package/gsd-core/bin/lib/retired-artifact-cleanup.cjs +11 -6
  141. package/gsd-core/bin/lib/review-lane-descriptor.cjs +22 -13
  142. package/gsd-core/bin/lib/review-lane-invocation.cjs +30 -0
  143. package/gsd-core/bin/lib/review-lane-runner.cjs +421 -66
  144. package/gsd-core/bin/lib/review-reviewer-selection.cjs +13 -18
  145. package/gsd-core/bin/lib/roadmap-command-router.cjs +59 -11
  146. package/gsd-core/bin/lib/roadmap-parser.cjs +1006 -184
  147. package/gsd-core/bin/lib/roadmap-upgrade.cjs +37 -10
  148. package/gsd-core/bin/lib/roadmap.cjs +442 -96
  149. package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +702 -52
  150. package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +14 -2
  151. package/gsd-core/bin/lib/runtime-artifact-layout.cjs +459 -55
  152. package/gsd-core/bin/lib/runtime-config-adapter-registry.cjs +3 -2
  153. package/gsd-core/bin/lib/runtime-homes.cjs +69 -3
  154. package/gsd-core/bin/lib/runtime-hooks-surface.cjs +402 -58
  155. package/gsd-core/bin/lib/runtime-identity.cjs +234 -0
  156. package/gsd-core/bin/lib/runtime-name-policy.cjs +3 -1
  157. package/gsd-core/bin/lib/runtime-slash.cjs +96 -8
  158. package/gsd-core/bin/lib/security.cjs +104 -5
  159. package/gsd-core/bin/lib/shell-command-projection.cjs +342 -7
  160. package/gsd-core/bin/lib/smart-entry.cjs +133 -23
  161. package/gsd-core/bin/lib/spec-section.cjs +12 -7
  162. package/gsd-core/bin/lib/state-command-router.cjs +52 -19
  163. package/gsd-core/bin/lib/state-contract.cjs +359 -0
  164. package/gsd-core/bin/lib/state-document.cjs +338 -8
  165. package/gsd-core/bin/lib/state-md-schema.cjs +221 -0
  166. package/gsd-core/bin/lib/state-transition.cjs +846 -176
  167. package/gsd-core/bin/lib/state.cjs +2589 -369
  168. package/gsd-core/bin/lib/surface.cjs +33 -11
  169. package/gsd-core/bin/lib/task-command-router.cjs +111 -1
  170. package/gsd-core/bin/lib/task-content-resolution.cjs +368 -0
  171. package/gsd-core/bin/lib/teams-status.cjs +4 -1
  172. package/gsd-core/bin/lib/text-lines.cjs +80 -0
  173. package/gsd-core/bin/lib/token-scanner.cjs +76 -0
  174. package/gsd-core/bin/lib/uat-predicate.cjs +67 -23
  175. package/gsd-core/bin/lib/uat.cjs +1761 -167
  176. package/gsd-core/bin/lib/ui-consideration-probe.cjs +9 -1
  177. package/gsd-core/bin/lib/ui-frontend-evidence.cjs +157 -0
  178. package/gsd-core/bin/lib/ui-safety-gate.cjs +51 -12
  179. package/gsd-core/bin/lib/unusable-input.cjs +37 -0
  180. package/gsd-core/bin/lib/update-context.cjs +8 -2
  181. package/gsd-core/bin/lib/user-artifact-staging.cjs +705 -0
  182. package/gsd-core/bin/lib/validate-command-router.cjs +2 -2
  183. package/gsd-core/bin/lib/validate.cjs +20 -6
  184. package/gsd-core/bin/lib/vendor/README.md +75 -0
  185. package/gsd-core/bin/lib/vendor/js-yaml.cjs +3014 -0
  186. package/gsd-core/bin/lib/vendor/re2js.cjs +6480 -0
  187. package/gsd-core/bin/lib/vendor/re2js.d.cts +938 -0
  188. package/gsd-core/bin/lib/verification-command-router.cjs +2 -1
  189. package/gsd-core/bin/lib/verification.cjs +272 -9
  190. package/gsd-core/bin/lib/verify-command-grounding.cjs +846 -0
  191. package/gsd-core/bin/lib/verify.cjs +453 -918
  192. package/gsd-core/bin/lib/workstream-inventory-builder.cjs +53 -32
  193. package/gsd-core/bin/lib/workstream-inventory.cjs +63 -10
  194. package/gsd-core/bin/lib/workstream-name-policy.cjs +25 -4
  195. package/gsd-core/bin/lib/workstream.cjs +2 -2
  196. package/gsd-core/bin/lib/worktree-base-ref.cjs +66 -12
  197. package/gsd-core/bin/lib/worktree-safety.cjs +341 -18
  198. package/gsd-core/bin/shared/config-defaults.manifest.json +8 -1
  199. package/gsd-core/bin/shared/config-schema.manifest.json +12 -1
  200. package/gsd-core/bin/shared/exit-codes.json +8 -0
  201. package/gsd-core/bin/shared/exit-codes.sh +20 -0
  202. package/gsd-core/bin/shared/model-catalog.json +8 -1
  203. package/gsd-core/references/agent-contracts.md +44 -26
  204. package/gsd-core/references/api-coverage.md +24 -2
  205. package/gsd-core/references/autonomous-smart-discuss.md +3 -3
  206. package/gsd-core/references/checkpoints.md +39 -21
  207. package/gsd-core/references/context-budget.md +1 -1
  208. package/gsd-core/references/decimal-phase-calculation.md +5 -5
  209. package/gsd-core/references/dispatch-isolation-gate.md +138 -0
  210. package/gsd-core/references/doc-conflict-engine.md +1 -1
  211. package/gsd-core/references/edge-probe.md +8 -0
  212. package/gsd-core/references/execute-mvp-tdd.md +4 -6
  213. package/gsd-core/references/execute-phase-between-wave-reset.md +15 -14
  214. package/gsd-core/references/execute-phase-context-guard.md +1 -1
  215. package/gsd-core/references/execute-phase-response-language.md +1 -1
  216. package/gsd-core/references/execute-phase-wave-guard.md +17 -11
  217. package/gsd-core/references/failing-direction.md +78 -0
  218. package/gsd-core/references/gate-prompts.md +1 -1
  219. package/gsd-core/references/git-integration.md +5 -5
  220. package/gsd-core/references/git-planning-commit.md +5 -4
  221. package/gsd-core/references/gsd-run-resolver.md +1 -1
  222. package/gsd-core/references/loop-hook-dispatch.md +61 -2
  223. package/gsd-core/references/model-profiles.md +12 -4
  224. package/gsd-core/references/mvp-concepts.md +9 -9
  225. package/gsd-core/references/nyquist-compliance.md +74 -0
  226. package/gsd-core/references/offer-next.md +3 -5
  227. package/gsd-core/references/phase-argument-parsing.md +3 -3
  228. package/gsd-core/references/planner-failing-direction.md +53 -0
  229. package/gsd-core/references/planner-guidance.md +3 -9
  230. package/gsd-core/references/planner-human-verify-mode.md +15 -1
  231. package/gsd-core/references/planner-preconditions.md +1 -1
  232. package/gsd-core/references/planner-reviews.md +1 -1
  233. package/gsd-core/references/planner-revision.md +1 -1
  234. package/gsd-core/references/planner-verify-command-grounding.md +17 -0
  235. package/gsd-core/references/planning-config.md +44 -13
  236. package/gsd-core/references/reviewer-instances.md +31 -0
  237. package/gsd-core/references/revision-loop.md +1 -1
  238. package/gsd-core/references/runtime-aware-dispatch.md +1 -1
  239. package/gsd-core/references/specless-probe-fallback.md +1 -1
  240. package/gsd-core/references/tdd.md +1 -3
  241. package/gsd-core/references/ui-brand.md +65 -21
  242. package/gsd-core/references/ui-consideration-probe.md +1 -1
  243. package/gsd-core/references/universal-anti-patterns.md +5 -5
  244. package/gsd-core/references/verifier-phase-gates.md +192 -0
  245. package/gsd-core/references/verify-command-path-resolvability.md +42 -0
  246. package/gsd-core/references/verify-mvp-mode.md +2 -2
  247. package/gsd-core/references/workstream-flag.md +33 -17
  248. package/gsd-core/templates/README.md +1 -1
  249. package/gsd-core/templates/SECURITY.md +3 -3
  250. package/gsd-core/templates/UI-SPEC.md +25 -3
  251. package/gsd-core/templates/VALIDATION.md +3 -3
  252. package/gsd-core/templates/discussion-log.md +1 -1
  253. package/gsd-core/templates/phase-prompt.md +5 -4
  254. package/gsd-core/templates/state.md +11 -4
  255. package/gsd-core/templates/verification-report.md +9 -1
  256. package/gsd-core/workflows/_runtime-launcher.snippet.sh +1 -1
  257. package/gsd-core/workflows/add-backlog.md +1 -1
  258. package/gsd-core/workflows/add-phase.md +3 -3
  259. package/gsd-core/workflows/add-tests.md +3 -8
  260. package/gsd-core/workflows/add-todo.md +1 -1
  261. package/gsd-core/workflows/ai-integration-phase.md +13 -20
  262. package/gsd-core/workflows/audit-fix.md +12 -3
  263. package/gsd-core/workflows/audit-milestone.md +9 -9
  264. package/gsd-core/workflows/audit-uat.md +17 -2
  265. package/gsd-core/workflows/autonomous/steps/converge-fail-fast.md +2 -2
  266. package/gsd-core/workflows/autonomous.md +11 -27
  267. package/gsd-core/workflows/check-todos.md +1 -1
  268. package/gsd-core/workflows/cleanup.md +64 -5
  269. package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +14 -4
  270. package/gsd-core/workflows/code-review-fix.md +38 -11
  271. package/gsd-core/workflows/code-review.md +159 -52
  272. package/gsd-core/workflows/complete-milestone.md +151 -23
  273. package/gsd-core/workflows/debug.md +12 -8
  274. package/gsd-core/workflows/diagnose-issues.md +47 -15
  275. package/gsd-core/workflows/discuss-phase/modes/advisor.md +1 -1
  276. package/gsd-core/workflows/discuss-phase/modes/chain.md +5 -8
  277. package/gsd-core/workflows/discuss-phase/modes/default.md +1 -1
  278. package/gsd-core/workflows/discuss-phase/modes/text.md +1 -1
  279. package/gsd-core/workflows/discuss-phase-assumptions/steps/auto-advance-dispatch.md +1 -3
  280. package/gsd-core/workflows/discuss-phase-assumptions.md +4 -3
  281. package/gsd-core/workflows/discuss-phase.md +1 -1
  282. package/gsd-core/workflows/do.md +3 -6
  283. package/gsd-core/workflows/docs-update.md +5 -4
  284. package/gsd-core/workflows/edit-phase.md +27 -2
  285. package/gsd-core/workflows/eval-review.md +7 -14
  286. package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +1 -1
  287. package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +142 -15
  288. package/gsd-core/workflows/execute-phase/steps/gap-closure-artifacts.md +1 -1
  289. package/gsd-core/workflows/execute-phase/steps/partial-wave.md +1 -1
  290. package/gsd-core/workflows/execute-phase/steps/per-plan-executor-routing.md +77 -0
  291. package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +24 -4
  292. package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +2 -2
  293. package/gsd-core/workflows/execute-phase/steps/protected-branch.md +21 -0
  294. package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +2 -2
  295. package/gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md +39 -0
  296. package/gsd-core/workflows/execute-phase.md +72 -100
  297. package/gsd-core/workflows/execute-plan.md +52 -15
  298. package/gsd-core/workflows/explore.md +131 -4
  299. package/gsd-core/workflows/extract-learnings.md +1 -1
  300. package/gsd-core/workflows/fast.md +10 -2
  301. package/gsd-core/workflows/forensics.md +1 -1
  302. package/gsd-core/workflows/graduation.md +5 -5
  303. package/gsd-core/workflows/health.md +76 -10
  304. package/gsd-core/workflows/import.md +18 -15
  305. package/gsd-core/workflows/inbox.md +4 -5
  306. package/gsd-core/workflows/ingest-docs.md +49 -16
  307. package/gsd-core/workflows/insert-phase.md +5 -5
  308. package/gsd-core/workflows/list-seeds.md +5 -3
  309. package/gsd-core/workflows/list-workspaces.md +1 -1
  310. package/gsd-core/workflows/manager.md +12 -23
  311. package/gsd-core/workflows/map-codebase.md +1 -1
  312. package/gsd-core/workflows/milestone-summary.md +1 -1
  313. package/gsd-core/workflows/mvp-phase.md +8 -5
  314. package/gsd-core/workflows/new-milestone.md +22 -29
  315. package/gsd-core/workflows/new-project/steps/auto-mode-config.md +1 -1
  316. package/gsd-core/workflows/new-project.md +26 -40
  317. package/gsd-core/workflows/new-workspace.md +1 -1
  318. package/gsd-core/workflows/next.md +14 -2
  319. package/gsd-core/workflows/pause-work.md +1 -1
  320. package/gsd-core/workflows/plan-phase/steps/adr-ingest-express-path.md +1 -1
  321. package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +1 -1
  322. package/gsd-core/workflows/plan-phase/steps/prd-express-path.md +2 -4
  323. package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +3 -3
  324. package/gsd-core/workflows/plan-phase.md +162 -59
  325. package/gsd-core/workflows/plan-review-convergence.md +96 -11
  326. package/gsd-core/workflows/plant-seed.md +2 -2
  327. package/gsd-core/workflows/pr-branch.md +187 -51
  328. package/gsd-core/workflows/profile-user.md +16 -14
  329. package/gsd-core/workflows/progress.md +61 -18
  330. package/gsd-core/workflows/quick/steps/discussion-phase.md +1 -3
  331. package/gsd-core/workflows/quick/steps/plan-checker-loop.md +5 -7
  332. package/gsd-core/workflows/quick/steps/quick-verification.md +28 -9
  333. package/gsd-core/workflows/quick/steps/research-phase.md +4 -6
  334. package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +3 -3
  335. package/gsd-core/workflows/quick.md +55 -44
  336. package/gsd-core/workflows/remove-phase.md +4 -4
  337. package/gsd-core/workflows/remove-workspace.md +2 -2
  338. package/gsd-core/workflows/resume-project.md +8 -12
  339. package/gsd-core/workflows/review.md +219 -20
  340. package/gsd-core/workflows/scan.md +1 -1
  341. package/gsd-core/workflows/secure-phase.md +3 -3
  342. package/gsd-core/workflows/session-report.md +2 -1
  343. package/gsd-core/workflows/settings-advanced.md +7 -9
  344. package/gsd-core/workflows/settings-integrations.md +64 -31
  345. package/gsd-core/workflows/settings.md +69 -7
  346. package/gsd-core/workflows/ship.md +116 -50
  347. package/gsd-core/workflows/sketch-wrap-up.md +11 -17
  348. package/gsd-core/workflows/sketch.md +12 -18
  349. package/gsd-core/workflows/smart-entry.md +3 -5
  350. package/gsd-core/workflows/spec-phase.md +53 -13
  351. package/gsd-core/workflows/spike-wrap-up.md +7 -11
  352. package/gsd-core/workflows/spike.md +20 -31
  353. package/gsd-core/workflows/stats.md +2 -2
  354. package/gsd-core/workflows/sync-skills.md +64 -9
  355. package/gsd-core/workflows/thread.md +11 -7
  356. package/gsd-core/workflows/transition.md +49 -14
  357. package/gsd-core/workflows/ui-phase.md +15 -21
  358. package/gsd-core/workflows/ui-review.md +8 -12
  359. package/gsd-core/workflows/ultraplan-phase.md +5 -13
  360. package/gsd-core/workflows/undo.md +8 -16
  361. package/gsd-core/workflows/update.md +7 -11
  362. package/gsd-core/workflows/validate-phase.md +3 -3
  363. package/gsd-core/workflows/verify-work/steps/automated-ui-verification.md +25 -1
  364. package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +1 -1
  365. package/gsd-core/workflows/verify-work.md +66 -25
  366. package/hooks/dist/gsd-agent-isolation-guard.js +158 -30
  367. package/hooks/dist/gsd-check-update-worker.js +56 -13
  368. package/hooks/dist/gsd-check-update.js +19 -1
  369. package/hooks/dist/gsd-config-reload.js +18 -12
  370. package/hooks/dist/gsd-context-monitor.js +19 -10
  371. package/hooks/dist/gsd-cursor-post-tool.js +3 -1
  372. package/hooks/dist/gsd-cursor-pre-tool.js +2 -3
  373. package/hooks/dist/gsd-cursor-session-start.js +2 -1
  374. package/hooks/dist/gsd-cursor-stop.js +2 -1
  375. package/hooks/dist/gsd-cursor-subagent-start.js +83 -3
  376. package/hooks/dist/gsd-cursor-subagent-stop.js +6 -3
  377. package/hooks/dist/gsd-ensure-canonical-path.js +2 -1
  378. package/hooks/dist/gsd-graphify-update.sh +22 -18
  379. package/hooks/dist/gsd-node-runner.sh +76 -0
  380. package/hooks/dist/gsd-phase-boundary.sh +1 -0
  381. package/hooks/dist/gsd-prompt-guard.js +37 -27
  382. package/hooks/dist/gsd-read-guard.js +16 -7
  383. package/hooks/dist/gsd-read-injection-scanner.js +55 -32
  384. package/hooks/dist/gsd-session-state.sh +1 -0
  385. package/hooks/dist/gsd-statusline.js +231 -24
  386. package/hooks/dist/gsd-update-banner.js +22 -1
  387. package/hooks/dist/gsd-validate-commit.sh +80 -6
  388. package/hooks/dist/gsd-windsurf-pre-command.js +16 -11
  389. package/hooks/dist/gsd-windsurf-pre-write.js +22 -13
  390. package/hooks/dist/gsd-workflow-guard.js +162 -46
  391. package/hooks/dist/gsd-worktree-path-guard.js +36 -21
  392. package/hooks/dist/gsd-write-guard.js +35 -25
  393. package/hooks/dist/lib/cli-exit.js +560 -0
  394. package/hooks/dist/lib/exit-code-registry.js +98 -0
  395. package/hooks/dist/lib/git-cmd.js +92 -59
  396. package/hooks/dist/lib/git-probe.js +84 -0
  397. package/hooks/dist/lib/hook-exit.js +81 -0
  398. package/hooks/dist/lib/injection-patterns.js +45 -0
  399. package/hooks/dist/lib/isolation-deny-reason.js +39 -0
  400. package/hooks/dist/lib/isolation-sentinel.js +9 -0
  401. package/hooks/dist/managed-hooks-registry.cjs +3 -0
  402. package/hooks/gsd-agent-isolation-guard.js +158 -30
  403. package/hooks/gsd-check-update-worker.js +56 -13
  404. package/hooks/gsd-check-update.js +19 -1
  405. package/hooks/gsd-config-reload.js +18 -12
  406. package/hooks/gsd-context-monitor.js +19 -10
  407. package/hooks/gsd-cursor-post-tool.js +3 -1
  408. package/hooks/gsd-cursor-pre-tool.js +2 -3
  409. package/hooks/gsd-cursor-session-start.js +2 -1
  410. package/hooks/gsd-cursor-stop.js +2 -1
  411. package/hooks/gsd-cursor-subagent-start.js +83 -3
  412. package/hooks/gsd-cursor-subagent-stop.js +6 -3
  413. package/hooks/gsd-ensure-canonical-path.js +2 -1
  414. package/hooks/gsd-graphify-update.sh +22 -18
  415. package/hooks/gsd-node-runner.sh +76 -0
  416. package/hooks/gsd-phase-boundary.sh +1 -0
  417. package/hooks/gsd-prompt-guard.js +37 -27
  418. package/hooks/gsd-read-guard.js +16 -7
  419. package/hooks/gsd-read-injection-scanner.js +55 -32
  420. package/hooks/gsd-session-state.sh +1 -0
  421. package/hooks/gsd-statusline.js +231 -24
  422. package/hooks/gsd-update-banner.js +22 -1
  423. package/hooks/gsd-validate-commit.sh +80 -6
  424. package/hooks/gsd-windsurf-pre-command.js +16 -11
  425. package/hooks/gsd-windsurf-pre-write.js +22 -13
  426. package/hooks/gsd-workflow-guard.js +162 -46
  427. package/hooks/gsd-worktree-path-guard.js +36 -21
  428. package/hooks/gsd-write-guard.js +35 -25
  429. package/hooks/lib/cli-exit.js +560 -0
  430. package/hooks/lib/exit-code-registry.js +98 -0
  431. package/hooks/lib/git-cmd.js +92 -59
  432. package/hooks/lib/git-probe.js +84 -0
  433. package/hooks/lib/hook-exit.js +81 -0
  434. package/hooks/lib/injection-patterns.js +45 -0
  435. package/hooks/lib/isolation-deny-reason.js +39 -0
  436. package/hooks/lib/isolation-sentinel.js +9 -0
  437. package/hooks/managed-hooks-registry.cjs +3 -0
  438. package/package.json +28 -11
  439. package/pi/gsd.cjs +19 -5
  440. package/scripts/base64-scan.sh +74 -12
  441. package/scripts/baselines/planning-prompt-drift-baseline.json +4 -0
  442. package/scripts/baselines/planning-snapshot-bypass-baseline.json +12 -0
  443. package/scripts/baselines/unreachable-guard-drift-baseline.json +4 -0
  444. package/scripts/build-hooks.js +5 -0
  445. package/scripts/changeset/lint.cjs +60 -5
  446. package/scripts/check-alias-drift.cjs +7 -43
  447. package/scripts/check-contract-drift.cjs +297 -0
  448. package/scripts/check-glossary-refs.cjs +77 -15
  449. package/scripts/check-mutation-score-ratchet.cjs +156 -0
  450. package/scripts/ci-check-job-near-cap.cjs +49 -0
  451. package/scripts/ci-pr-mergeability.cjs +262 -0
  452. package/scripts/ci-test-scope.cjs +64 -14
  453. package/scripts/ci-timeout-report.cjs +230 -0
  454. package/scripts/command-contract-helpers.cjs +903 -1
  455. package/scripts/docs-guard-registry.cjs +396 -0
  456. package/scripts/gen-adr-index.cjs +728 -38
  457. package/scripts/gen-capability-registry.cjs +11 -21
  458. package/scripts/gen-context-index.cjs +2 -11
  459. package/scripts/gen-exit-code-docs.cjs +318 -0
  460. package/scripts/gen-exit-code-registry.cjs +891 -0
  461. package/scripts/gen-features.cjs +836 -0
  462. package/scripts/gen-health-docs.cjs +390 -0
  463. package/scripts/gen-hooks-cli-exit.cjs +239 -0
  464. package/scripts/gen-install-tree-fixtures.cjs +2 -2
  465. package/scripts/gen-inventory-manifest.cjs +50 -4
  466. package/scripts/gen-loop-host-contract.cjs +138 -25
  467. package/scripts/gen-registry.cjs +3 -14
  468. package/scripts/gen-scripts-cli-exit.cjs +185 -0
  469. package/scripts/gen-state-md-docs.cjs +727 -0
  470. package/scripts/{test-failure-reasons.cjs → gsd-test-gate-reasons.cjs} +6 -0
  471. package/scripts/lib/alias-drift-families.cjs +46 -0
  472. package/scripts/lib/ci-job-timing.cjs +72 -0
  473. package/scripts/lib/cli-exit.cjs +546 -44
  474. package/scripts/lib/drift-scan.cjs +308 -0
  475. package/scripts/lib/exit-code-registry.cjs +98 -0
  476. package/scripts/lib/ndjson-reporter.cjs +119 -0
  477. package/scripts/lint-allow-test-rule-refs.allowlist.json +1 -26
  478. package/scripts/lint-allow-test-rule-refs.effective-ceiling.json +4 -0
  479. package/scripts/lint-allow-test-rule-refs.unverified-ceiling.json +3 -0
  480. package/scripts/lint-canary-version-leak.cjs +73 -0
  481. package/scripts/lint-command-contract.cjs +96 -13
  482. package/scripts/lint-completion-predicate-drift.cjs +933 -0
  483. package/scripts/lint-completion-ratio-drift.cjs +214 -0
  484. package/scripts/lint-default-flip-documentation.cjs +193 -0
  485. package/scripts/lint-docs-guard-registration.cjs +495 -0
  486. package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +193 -0
  487. package/scripts/lint-eslint-glob-coverage.allowlist.json +38 -0
  488. package/scripts/lint-eslint-glob-coverage.cjs +340 -0
  489. package/scripts/{lint-fix-has-regression-test.cjs → lint-fix-has-regression-tests.cjs} +12 -6
  490. package/scripts/lint-frontmatter-scalar-broad-grep.cjs +237 -0
  491. package/scripts/lint-health-diagnostic-rule-table.cjs +461 -0
  492. package/scripts/lint-hooks-runtime-build-seam.cjs +262 -0
  493. package/scripts/lint-milestone-window-drift.cjs +468 -0
  494. package/scripts/lint-mutation-test-derivation-drift.cjs +86 -0
  495. package/scripts/lint-phase-enumeration-drift.cjs +492 -0
  496. package/scripts/lint-plan-count-drift.cjs +318 -0
  497. package/scripts/lint-planning-artifact-writer-drift.cjs +398 -0
  498. package/scripts/lint-planning-prompt-drift.cjs +471 -0
  499. package/scripts/lint-planning-snapshot-bypass-drift.cjs +544 -0
  500. package/scripts/lint-regression-test-names.cjs +15 -13
  501. package/scripts/lint-removed-but-needed.cjs +488 -0
  502. package/scripts/lint-seam-enforcement.cjs +182 -0
  503. package/scripts/lint-slug-derivation-drift.cjs +921 -0
  504. package/scripts/lint-source-test-name-collision.cjs +241 -0
  505. package/scripts/lint-state-field-drift.cjs +805 -0
  506. package/scripts/lint-state-write-path-drift.cjs +950 -0
  507. package/scripts/lint-test-file-count.allowlist.json +137 -8
  508. package/scripts/lint-test-file-count.cjs +25 -3
  509. package/scripts/lint-unreachable-guard-drift.cjs +830 -0
  510. package/scripts/lint-vendored-deps.cjs +297 -0
  511. package/scripts/mutation-matrix.cjs +599 -50
  512. package/scripts/pr-changed-files.cjs +63 -0
  513. package/scripts/pr-template-policy.cjs +14 -4
  514. package/scripts/prompt-injection-scan.sh +100 -14
  515. package/scripts/require-issue-link-policy.cjs +192 -0
  516. package/scripts/secret-scan.sh +75 -13
  517. package/scripts/select-docs-guards.cjs +56 -0
  518. package/scripts/sync-runtime-launcher.cjs +24 -7
  519. package/skills/gsd-autonomous/SKILL.md +0 -1
  520. package/skills/gsd-code-review/SKILL.md +1 -1
  521. package/skills/gsd-discuss-phase/SKILL.md +1 -1
  522. package/skills/gsd-execute-phase/SKILL.md +1 -2
  523. package/skills/gsd-import/SKILL.md +1 -1
  524. package/skills/gsd-map-codebase/SKILL.md +1 -1
  525. package/skills/gsd-mempalace-capture/SKILL.md +1 -1
  526. package/skills/gsd-mempalace-recall/SKILL.md +1 -1
  527. package/skills/gsd-new-milestone/SKILL.md +1 -1
  528. package/skills/gsd-next/SKILL.md +0 -1
  529. package/skills/gsd-plan-phase/SKILL.md +0 -1
  530. package/skills/gsd-progress/SKILL.md +0 -1
  531. package/skills/gsd-quick/SKILL.md +9 -5
  532. package/skills/gsd-review-backlog/SKILL.md +2 -1
  533. package/skills/gsd-stats/SKILL.md +0 -1
  534. package/skills/gsd-verify-work/SKILL.md +1 -1
  535. package/vscode/package.json +1 -1
  536. package/bin/lib/ui-safety-gate.cjs +0 -107
  537. package/gsd-core/workflows/discovery-phase.md +0 -298
  538. package/gsd-core/workflows/plan-milestone-gaps.md +0 -281
  539. package/gsd-core/workflows/verify-phase.md +0 -574
  540. package/scripts/affected-tests-lib.cjs +0 -554
  541. package/scripts/lint-allow-test-rule-refs.cjs +0 -162
  542. package/scripts/lint-emitted-drift-ack.cjs +0 -344
  543. package/scripts/run-affected-tests.cjs +0 -7
  544. package/scripts/run-tests.cjs +0 -1051
@@ -65,7 +65,7 @@ const TRACKED_PREFIXES = [
65
65
  const TRACKED_EXACT = new Set(['bin/install.js', 'package.json']);
66
66
 
67
67
  /**
68
- * Paths CONTEXT.md documents whose ABSENCE is the healthy steady state (#2778).
68
+ * Paths CONTEXT.md documents whose ABSENCE is the healthy steady state.
69
69
  *
70
70
  * `TRACKED_PREFIXES` skips claims this gate *cannot* check. This is the narrower
71
71
  * third case: a claim it must not check, because "does not exist" is the correct
@@ -78,14 +78,43 @@ const TRACKED_EXACT = new Set(['bin/install.js', 'package.json']);
78
78
  * shipping an empty stub. So the file is absent on a healthy `next` and present
79
79
  * only inside a PR that needs it, and asserting either way is wrong.
80
80
  *
81
- * Until #2778 this passed only by accident: CONTEXT.md's `RULESET.` entries are
82
- * themselves backtick-wrapped and contain backticks, so the sequential pairing in
83
- * `extractTrackedRefs` happened to leave this token outside a code span. Any edit
84
- * that shifted the parity — such as #2778's own — exposed it. A gate that passes
85
- * by luck is not passing; naming the exemption makes the intent explicit and
86
- * survives the next edit.
81
+ * The emitted-attribution family (`tests/fixtures/golden-install-parity`,
82
+ * `tests/golden-install-parity.test.cjs`, `scripts/gen-golden-install-parity-zcode.cjs`,
83
+ * `tests/agent-size-baseline.json`, `tests/workflow-size-baseline.json`,
84
+ * `scripts/git-merge-regen-driver.cjs`, `scripts/update-size-baseline.cjs`) was RETIRED
85
+ * by the #2724 cutover — the differential attribution check replaced the committed
86
+ * baselines and their generator/bridge tooling. CONTEXT.md's RULESET.EMITTED_ATTRIBUTION
87
+ * predicate documents that retirement ("Historically …"), so the mentions are history,
88
+ * not live claims, and their absence is exactly the healthy state the retirement
89
+ * produced. The whole documented family is listed, not just the members today's tick
90
+ * parity happens to hide — the two tooling paths were invisible only because of where
91
+ * line 585's inner backticks sat, which is the #2778 luck this gate must not rely on.
92
+ *
93
+ * `scripts/eslint-rules` appears only inside a CONTRASTIVE mention ("the local
94
+ * plugin lives at `eslint-rules/` (repo root, NOT `scripts/eslint-rules/`)") — the
95
+ * predicate asserts where the directory is NOT, so non-existence is the claim
96
+ * being made, not drift away from one.
97
+ *
98
+ * Until #2778 this set's first entry passed only by accident: CONTEXT.md's
99
+ * `RULESET.` entries are themselves backtick-wrapped and contain backticks, so the
100
+ * sequential pairing in `extractTrackedRefs` happened to leave this token outside a
101
+ * code span. Any edit that shifted the parity — such as #2778's own — exposed it.
102
+ * A gate that passes by luck is not passing; naming the exemption makes the intent
103
+ * explicit and survives the next edit. #3604 removed the luck itself (pairing is
104
+ * per line), which is what surfaced the entries above: each names a path whose
105
+ * absence is deliberate, so each is exempted by name for the same reason.
87
106
  */
88
- const INTENTIONALLY_ABSENT = new Set(['tests/emitted-drift-ack.json']);
107
+ const INTENTIONALLY_ABSENT = new Set([
108
+ 'tests/emitted-drift-ack.json',
109
+ 'tests/fixtures/golden-install-parity',
110
+ 'tests/golden-install-parity.test.cjs',
111
+ 'scripts/gen-golden-install-parity-zcode.cjs',
112
+ 'tests/agent-size-baseline.json',
113
+ 'tests/workflow-size-baseline.json',
114
+ 'scripts/git-merge-regen-driver.cjs',
115
+ 'scripts/update-size-baseline.cjs',
116
+ 'scripts/eslint-rules',
117
+ ]);
89
118
 
90
119
  /**
91
120
  * Shape a backticked token must have to even be considered a path candidate:
@@ -119,18 +148,51 @@ function isWithinRoot(token) {
119
148
  /**
120
149
  * Every distinct, trackable file-path token referenced in `text`, with any
121
150
  * trailing `:<line>` suffix stripped.
151
+ *
152
+ * #3604: pairing is per LINE, not over the whole text. A single whole-text pass
153
+ * `[^`]+` crosses newlines, so one odd-backtick line (the `RULESET.*` predicate
154
+ * format is backtick-wrapped and its values sometimes contain backticks) shifted
155
+ * the pairing of every later line — a tracked token's visibility depended on
156
+ * where it sat, which is how a renamed test file stayed invisible for weeks.
157
+ *
158
+ * The fact-store predicate lines (one `CLASS.subkey=value` fact per line,
159
+ * backtick-wrapped as a whole) carry REAL paths in their values; the span itself
160
+ * is not path-shaped (spaces, `=`), so a second pass harvests path-shaped tracked
161
+ * tokens from inside any predicate-shaped span rather than letting the outer
162
+ * wrapper hide them.
163
+ *
164
+ * Fragment guards: a real path in this repo never ends in `-` or `.` — those are
165
+ * remnants of glob/template mentions (`scripts/gen-*.cjs`, `tests/foo.*.test.cjs`)
166
+ * split at the `*` — and `NNNN` is the ADR filename template token
167
+ * (CONTRIBUTING's "Do not compute a next number locally"), never a real path.
122
168
  */
123
169
  function extractTrackedRefs(text) {
124
170
  const tokens = new Set();
125
- const re = /`([^`]+)`/g;
126
- let m;
127
- while ((m = re.exec(text)) !== null) {
128
- const raw = m[1];
129
- if (!PATH_TOKEN_RE.test(raw)) continue;
171
+ const add = (raw) => {
172
+ if (!PATH_TOKEN_RE.test(raw)) return;
130
173
  const token = raw.replace(/:\d+$/, '');
131
- if (!isTracked(token)) continue;
132
- if (!isWithinRoot(token)) continue;
174
+ // Fragment guard, on the EMITTED token (after the :line strip, so
175
+ // `src/foo-:12` is judged on `src/foo-`): glob/template remnants end in `-`
176
+ // or `.`, a real path in this repo never does.
177
+ if (!/[A-Za-z0-9_]$/.test(token)) return;
178
+ if (token.includes('NNNN')) return;
179
+ if (!isTracked(token)) return;
180
+ if (!isWithinRoot(token)) return;
133
181
  tokens.add(token);
182
+ };
183
+ const subTokenRe = /[\w.-]+(?:\/[\w.-]+)*/g;
184
+ for (const line of text.split(/\r?\n/)) {
185
+ const re = /`([^`]+)`/g;
186
+ let m;
187
+ while ((m = re.exec(line)) !== null) {
188
+ const span = m[1];
189
+ if (PATH_TOKEN_RE.test(span)) {
190
+ add(span);
191
+ continue;
192
+ }
193
+ if (!/^[A-Z][A-Za-z0-9_.-]*=/.test(span)) continue;
194
+ for (const sub of span.matchAll(subTokenRe)) add(sub[0]);
195
+ }
134
196
  }
135
197
  return tokens;
136
198
  }
@@ -0,0 +1,156 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /**
5
+ * scripts/check-mutation-score-ratchet.cjs
6
+ *
7
+ * #3881 follow-up ("one YAML parser" mutation-matrix piece 3): makes each
8
+ * module's `minScore` floor (scripts/mutation-matrix.cjs COVERED) ratchet
9
+ * UPWARD instead of sitting wherever it was last hand-set.
10
+ *
11
+ * Two halves of "never lowerable without a reasoned marker":
12
+ * 1. `tests/mutation-matrix-ratchet.test.cjs`'s RATCHET_BASELINE already
13
+ * enforces the DOWN side: any change to a module's minScore — up or
14
+ * down — must land in the same diff as a matching RATCHET_BASELINE
15
+ * edit, making the change visible in code review (mirrors this repo's
16
+ * other shrink-only baselines, e.g. scripts/lint-unreachable-guard-drift.cjs's
17
+ * $comment convention: a number cannot move silently).
18
+ * 2. THIS script enforces the UP side: after a real CI Stryker run, if the
19
+ * module's ACHIEVED mutation score exceeds its declared floor by more
20
+ * than RATCHET_SLACK points, that is not "run-to-run variance" (this
21
+ * file's own header documents variance as 1-2 points; see
22
+ * scripts/mutation-matrix.cjs's "Floors are measured scores minus 1-2
23
+ * pts") — it is unclaimed headroom, and the floor should have been
24
+ * raised. Exits 1 telling the author exactly what to raise it to, so a
25
+ * module that improves cannot silently keep a stale, low bar forever.
26
+ *
27
+ * Consumes Stryker's `json` reporter output (mutation-testing-report-schema
28
+ * document; stryker.config.mjs's `reporters` includes `'json'`, default path
29
+ * `reports/mutation/mutation.json`) via `mutation-testing-metrics` — the same
30
+ * package Stryker itself uses internally to compute a report's score — rather
31
+ * than re-deriving killed/survived counts by hand.
32
+ *
33
+ * Wired into `.github/workflows/mutation.yml`'s `mutate` job as a step AFTER
34
+ * `npx stryker run`, gated on the shard having passed (a shard that failed
35
+ * MUTATION_BREAK is a floor problem, not a ratchet problem — Stryker's own
36
+ * exit code already reports that).
37
+ *
38
+ * CLI:
39
+ * node scripts/check-mutation-score-ratchet.cjs --module <name> [--report <path>] [--matrix <path>]
40
+ * `--report` defaults to reports/mutation/mutation.json (Stryker's own default).
41
+ * `--matrix` defaults to this repo's own scripts/mutation-matrix.cjs; it is an injectable seam
42
+ * so tests can point the CLI at a synthetic COVERED fixture instead of the real, live-ratcheting
43
+ * config (see tests/mutation-score-ratchet.test.cjs — a test asserting this script's fail/pass
44
+ * behaviour must never hardcode a real module's numeric floor, because that floor is exactly
45
+ * what this mechanism exists to move).
46
+ */
47
+
48
+ const fs = require('node:fs');
49
+ const path = require('node:path');
50
+ const { ExitError, runMain } = require('./lib/cli-exit.cjs');
51
+
52
+ // Slack, in mutation-score points, above a module's declared minScore floor before this
53
+ // ratchet demands the floor be raised. Deliberately larger than the 1-2 point run-to-run
54
+ // TIMEOUT variance margin scripts/mutation-matrix.cjs's floors are already set with (its own
55
+ // header: "Floors are measured scores minus 1-2 pts for run-to-run variance") — that smaller
56
+ // margin is calibrated for the noise BETWEEN two runs of the SAME test list against the SAME
57
+ // source. RATCHET_SLACK instead has to tolerate the noise this file's own history already
58
+ // shows for the frontmatter module across genuine measurement events (63.35 -> 66.67 ->
59
+ // 60.58 -> pending, each a real CI measurement, not pure jitter, spanning >3 points on its
60
+ // own). 5 points sits comfortably above that observed band without being so wide that a
61
+ // module sitting well above TARGET_MUTATION_SCORE (80) could coast for years without its
62
+ // floor ever being asked to move.
63
+ const RATCHET_SLACK = 5;
64
+
65
+ /**
66
+ * Pure: does `achievedScore` (0-100) exceed `floor` (minScore) by more than `slack`?
67
+ * Returns `{ shouldRatchet, suggestedFloor }` — `suggestedFloor` follows the SAME formula
68
+ * scripts/mutation-matrix.cjs's own header documents for setting a floor from a measured
69
+ * score: `floor(measured) - 1`.
70
+ */
71
+ function evaluateRatchet(achievedScore, floor, slack = RATCHET_SLACK) {
72
+ const shouldRatchet = achievedScore - floor > slack;
73
+ return {
74
+ shouldRatchet,
75
+ suggestedFloor: shouldRatchet ? Math.floor(achievedScore) - 1 : floor,
76
+ };
77
+ }
78
+
79
+ /**
80
+ * Extract the achieved mutation score from a Stryker `json` reporter document via
81
+ * `mutation-testing-metrics` (the same library Stryker itself uses to compute scores).
82
+ * Throws a descriptive error if the document has no scoreable mutants at all (an empty
83
+ * `files` map, or every file having zero mutants) — that is a wiring bug in the caller
84
+ * (wrong report path, or the shard mutated nothing), never a score of 0.
85
+ */
86
+ function extractAchievedScore(report) {
87
+ // Lazy require: this dependency is only needed on the actual score-extraction path, not
88
+ // when this module is required purely for evaluateRatchet (used by unit tests without the
89
+ // mutation-testing-metrics package's schema-validation overhead).
90
+ const { calculateMutationTestMetrics } = require('mutation-testing-metrics');
91
+ const metrics = calculateMutationTestMetrics(report);
92
+ const score = metrics && metrics.systemUnderTestMetrics && metrics.systemUnderTestMetrics.metrics
93
+ ? metrics.systemUnderTestMetrics.metrics.mutationScore
94
+ : undefined;
95
+ if (typeof score !== 'number' || Number.isNaN(score)) {
96
+ throw new Error('mutation report produced no scoreable mutants — check --report points at the right shard\'s reports/mutation/mutation.json');
97
+ }
98
+ return score;
99
+ }
100
+
101
+ function parseArgs(argv) {
102
+ const out = { module: null, report: 'reports/mutation/mutation.json', matrix: null };
103
+ for (let i = 0; i < argv.length; i++) {
104
+ const arg = argv[i];
105
+ if (arg === '--module') {
106
+ out.module = argv[++i];
107
+ } else if (arg === '--report') {
108
+ out.report = argv[++i];
109
+ } else if (arg === '--matrix') {
110
+ out.matrix = argv[++i];
111
+ } else {
112
+ throw new Error(`unknown argument: ${arg}`);
113
+ }
114
+ }
115
+ if (!out.module) throw new Error('--module <name> is required');
116
+ return out;
117
+ }
118
+
119
+ function main() {
120
+ const args = parseArgs(process.argv.slice(2));
121
+ const matrixPath = args.matrix ? path.resolve(args.matrix) : path.join(__dirname, 'mutation-matrix.cjs');
122
+ const { COVERED } = require(matrixPath);
123
+ const entry = COVERED[args.module];
124
+ if (!entry) {
125
+ throw new ExitError(1, `check-mutation-score-ratchet: unknown module '${args.module}' — not in ${args.matrix ? matrixPath : 'scripts/mutation-matrix.cjs'} COVERED`);
126
+ }
127
+
128
+ const reportPath = path.resolve(args.report);
129
+ if (!fs.existsSync(reportPath)) {
130
+ throw new ExitError(1, `check-mutation-score-ratchet: report not found at ${reportPath} — run \`npx stryker run\` first (with the 'json' reporter enabled)`);
131
+ }
132
+ let report;
133
+ try {
134
+ report = JSON.parse(fs.readFileSync(reportPath, 'utf8'));
135
+ } catch (err) {
136
+ throw new ExitError(1, `check-mutation-score-ratchet: ${reportPath} is not valid JSON: ${err.message}`);
137
+ }
138
+
139
+ const achieved = extractAchievedScore(report);
140
+ const { shouldRatchet, suggestedFloor } = evaluateRatchet(achieved, entry.minScore);
141
+
142
+ if (!shouldRatchet) {
143
+ console.log(`ok mutation-score-ratchet: ${args.module} achieved ${achieved.toFixed(2)}% against floor ${entry.minScore} (within ${RATCHET_SLACK}-point slack)`);
144
+ return;
145
+ }
146
+
147
+ console.error(`mutation-score-ratchet: ${args.module} achieved ${achieved.toFixed(2)}%, more than ${RATCHET_SLACK} points above its declared floor (${entry.minScore}).`);
148
+ console.error(` This is unclaimed headroom, not run-to-run variance — raise the floor.`);
149
+ console.error(` remedy: in scripts/mutation-matrix.cjs, set COVERED['${args.module}'].minScore = ${suggestedFloor} (floor(achieved) - 1, this file's own convention),`);
150
+ console.error(` and update the matching entry in tests/mutation-matrix-ratchet.test.cjs's RATCHET_BASELINE in the same diff.`);
151
+ throw new ExitError(1);
152
+ }
153
+
154
+ module.exports = { RATCHET_SLACK, evaluateRatchet, extractAchievedScore };
155
+
156
+ if (require.main === module) runMain(main);
@@ -0,0 +1,49 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const fs = require('node:fs');
5
+ const { computeElapsedPct, isNearCap, formatNearCapNotice } = require('./lib/ci-job-timing.cjs');
6
+
7
+ function run(env = process.env, nowMs = Date.now(), appendFileSync = fs.appendFileSync) {
8
+ const label = env.CI_JOB_LABEL;
9
+ const startEpochMs = Number(env.CI_JOB_START_EPOCH_MS);
10
+ const timeoutMinutes = Number(env.CI_JOB_TIMEOUT_MINUTES);
11
+
12
+ if (!label || !Number.isFinite(startEpochMs) || !Number.isFinite(timeoutMinutes) || timeoutMinutes <= 0) {
13
+ console.error(
14
+ 'ci-check-job-near-cap: missing or invalid CI_JOB_LABEL / CI_JOB_START_EPOCH_MS / '
15
+ + 'CI_JOB_TIMEOUT_MINUTES — skipping near-cap check (advisory only, not a failure).',
16
+ );
17
+ return { skipped: true };
18
+ }
19
+
20
+ const { elapsedMs, capMs, pct } = computeElapsedPct({
21
+ startedAt: new Date(startEpochMs).toISOString(),
22
+ completedAt: new Date(nowMs).toISOString(),
23
+ timeoutMinutes,
24
+ });
25
+
26
+ if (!isNearCap(pct)) {
27
+ return { skipped: false, nearCap: false, pct };
28
+ }
29
+
30
+ const { warningLine, summaryMarkdown } = formatNearCapNotice({ label, pct, elapsedMs, capMs });
31
+ console.log(warningLine);
32
+
33
+ if (env.GITHUB_STEP_SUMMARY) {
34
+ try {
35
+ appendFileSync(env.GITHUB_STEP_SUMMARY, `${summaryMarkdown}\n`);
36
+ } catch (err) {
37
+ console.error(`ci-check-job-near-cap: could not write GITHUB_STEP_SUMMARY: ${err.message}`);
38
+ }
39
+ }
40
+
41
+ return { skipped: false, nearCap: true, pct };
42
+ }
43
+
44
+ module.exports = { run };
45
+
46
+ if (require.main === module) {
47
+ run();
48
+ process.exitCode = 0;
49
+ }
@@ -0,0 +1,262 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ // ci-pr-mergeability.cjs — #3833 PR mergeability preflight.
5
+ //
6
+ // Risk asymmetry drives this design: a false positive (a clean PR wrongly
7
+ // classified CONFLICTED) skips every gated compute lane in all eight gated
8
+ // workflows on EVERY PR — a repo-wide outage. A false negative (a real
9
+ // conflict missed) is today's behavior; the per-job scripts/ci-rebase-check.cjs
10
+ // backstop still catches it there. So the weight is entirely on the
11
+ // false-positive surface: the single predicate `mergeable === false`, and
12
+ // every falsy-but-not-false value around it.
13
+ //
14
+ // GitHub's `mergeable` field on GET /repos/:owner/:repo/pulls/:number is a
15
+ // documented tri-state: `true`, `false`, or `null` while a background
16
+ // computation is still running. `null` is falsy — `if (!mergeable)` would
17
+ // misclassify "still computing" as "conflicted" and would have red every PR
18
+ // in the repo the moment this job started running. Only strict `=== false`
19
+ // counts as a conflict.
20
+ //
21
+ // `mergeable_state` is read only for the human-facing annotation, never for
22
+ // the verdict: its `blocked` value means "required checks have not passed",
23
+ // which is true for every PR while THIS preflight job itself is one of the
24
+ // required checks still running — classifying on it would self-deadlock the
25
+ // pipeline (the gate can never go green because it is itself ungreen).
26
+ //
27
+ // Because `mergeable` starts null and GitHub computes it asynchronously, this
28
+ // polls with linear backoff up to a small budget, then fails OPEN
29
+ // (INDETERMINATE => exit 0) rather than blocking the world on an API that
30
+ // hasn't finished thinking.
31
+
32
+ const { ExitError, runMain } = require('./lib/cli-exit.cjs');
33
+
34
+ const VERDICT = Object.freeze({
35
+ MERGEABLE: 'MERGEABLE',
36
+ CONFLICTED: 'CONFLICTED',
37
+ INDETERMINATE: 'INDETERMINATE',
38
+ SKIPPED_NOT_A_PR: 'SKIPPED_NOT_A_PR',
39
+ });
40
+
41
+ const MAX_ATTEMPTS = 5;
42
+ const BASE_DELAY_MS = 2000;
43
+
44
+ /**
45
+ * Pure, total classifier: payload -> VERDICT atom. Never throws.
46
+ * Deliberately reads ONLY `mergeable`, and only with strict equality — see
47
+ * the header comment for why `mergeable_state` and truthiness are excluded.
48
+ */
49
+ function classifyMergeability(payload) {
50
+ if (payload === null || typeof payload !== 'object') return VERDICT.INDETERMINATE;
51
+ if (payload.mergeable === true) return VERDICT.MERGEABLE;
52
+ if (payload.mergeable === false) return VERDICT.CONFLICTED;
53
+ return VERDICT.INDETERMINATE;
54
+ }
55
+
56
+ /**
57
+ * Linear backoff: attempt 1..N-1 gives 1x..(N-1)x baseMs. Non-positive/NaN
58
+ * attempts clamp to one base interval rather than producing 0 or NaN delays.
59
+ */
60
+ function nextDelayMs(attempt, { baseMs = BASE_DELAY_MS } = {}) {
61
+ const n = Number.isFinite(attempt) && attempt > 0 ? Math.floor(attempt) : 1;
62
+ return baseMs * n;
63
+ }
64
+
65
+ /**
66
+ * Dependency-injected poll loop. Resolves as soon as GitHub reports a
67
+ * definite mergeable/conflicted state, or gives up after maxAttempts and
68
+ * reports INDETERMINATE (fail-open).
69
+ *
70
+ * @returns {Promise<{verdict:string, attempts:number, payload:*, reason:string}>}
71
+ */
72
+ async function resolveMergeability({
73
+ fetchPr,
74
+ sleep,
75
+ eventName,
76
+ prNumber,
77
+ maxAttempts = MAX_ATTEMPTS,
78
+ baseDelayMs = BASE_DELAY_MS,
79
+ } = {}) {
80
+ if (eventName !== 'pull_request') {
81
+ return { verdict: VERDICT.SKIPPED_NOT_A_PR, attempts: 0, payload: null, reason: 'not-a-pull-request' };
82
+ }
83
+ if (!Number.isInteger(prNumber) || prNumber <= 0) {
84
+ return { verdict: VERDICT.INDETERMINATE, attempts: 0, payload: null, reason: 'no-pr-number' };
85
+ }
86
+
87
+ let attempts = 0;
88
+ let lastPayload = null;
89
+ let lastError = null;
90
+
91
+ for (let i = 1; i <= maxAttempts; i++) {
92
+ attempts = i;
93
+ let payload;
94
+ try {
95
+ payload = await fetchPr(prNumber);
96
+ lastPayload = payload;
97
+ lastError = null;
98
+ } catch (err) {
99
+ lastError = err;
100
+ payload = null;
101
+ }
102
+
103
+ const verdict = classifyMergeability(payload);
104
+ if (verdict === VERDICT.MERGEABLE || verdict === VERDICT.CONFLICTED) {
105
+ return { verdict, attempts, payload, reason: 'resolved' };
106
+ }
107
+
108
+ if (i < maxAttempts) {
109
+ await sleep(nextDelayMs(i, { baseMs: baseDelayMs }));
110
+ }
111
+ }
112
+
113
+ return {
114
+ verdict: VERDICT.INDETERMINATE,
115
+ attempts,
116
+ payload: lastPayload,
117
+ reason: lastError ? 'fetch-failed' : 'mergeability-not-computed',
118
+ };
119
+ }
120
+
121
+ function usage() {
122
+ return [
123
+ 'Usage:',
124
+ ' node scripts/ci-pr-mergeability.cjs',
125
+ '',
126
+ 'CI-only preflight: polls the GitHub API for a PR\'s mergeable state and',
127
+ 'fails the job (exit 1) only on a definite conflict. Everything else',
128
+ '(unknown, unreadable, not a PR) fails open (exit 0).',
129
+ '',
130
+ 'Environment variables read:',
131
+ ' GITHUB_EVENT_NAME workflow trigger event; only "pull_request" is polled',
132
+ ' GITHUB_REPOSITORY owner/repo',
133
+ ' GITHUB_TOKEN optional bearer token for the API read',
134
+ ' GITHUB_BASE_REF PR base branch name (default: next)',
135
+ ' GITHUB_API_URL GitHub API base URL (default: https://api.github.com)',
136
+ ' PR_NUMBER the pull request number',
137
+ ' GITHUB_OUTPUT path to append verdict=/mergeable= step outputs to',
138
+ ' GITHUB_STEP_SUMMARY path to append a human-readable summary to',
139
+ ' GSD_PR_MERGEABILITY_BASE_DELAY_MS optional numeric override of the base backoff, for tests',
140
+ ].join('\n');
141
+ }
142
+
143
+ function parseArgs(argv) {
144
+ for (const arg of argv) {
145
+ if (arg === '--help' || arg === '-h') {
146
+ process.stdout.write(`${usage()}\n`);
147
+ throw new ExitError(0);
148
+ }
149
+ throw new Error(`unknown argument: ${arg}`);
150
+ }
151
+ }
152
+
153
+ function writeOutput(lines) {
154
+ const outputPath = process.env.GITHUB_OUTPUT;
155
+ if (typeof outputPath !== 'string' || outputPath === '') return;
156
+ try {
157
+ const fs = require('node:fs');
158
+ fs.appendFileSync(outputPath, `${lines.join('\n')}\n`);
159
+ } catch (err) {
160
+ // A failure while REPORTING the verdict must never invert the gate.
161
+ process.stderr.write(`::warning::failed to write GITHUB_OUTPUT: ${err.message}\n`);
162
+ }
163
+ }
164
+
165
+ function writeSummary(text) {
166
+ const summaryPath = process.env.GITHUB_STEP_SUMMARY;
167
+ if (typeof summaryPath !== 'string' || summaryPath === '') return;
168
+ try {
169
+ const fs = require('node:fs');
170
+ fs.appendFileSync(summaryPath, `${text}\n`);
171
+ } catch (err) {
172
+ process.stderr.write(`::warning::failed to write GITHUB_STEP_SUMMARY: ${err.message}\n`);
173
+ }
174
+ }
175
+
176
+ // `argv` defaults to real CLI argv but is a parameter so tests can call
177
+ // main() in-process (e.g. main([])) without inheriting the test runner's own
178
+ // argv, which would otherwise trip parseArgs's "unknown argument" branch.
179
+ async function main(argv = process.argv.slice(2)) {
180
+ parseArgs(argv);
181
+
182
+ const eventName = process.env.GITHUB_EVENT_NAME;
183
+ const repo = process.env.GITHUB_REPOSITORY || '';
184
+ const token = process.env.GITHUB_TOKEN || '';
185
+ const baseBranch = process.env.GITHUB_BASE_REF || 'next';
186
+ const apiBase = process.env.GITHUB_API_URL || 'https://api.github.com';
187
+ const prNumberRaw = process.env.PR_NUMBER;
188
+ const prNumber = Number.parseInt(prNumberRaw, 10);
189
+
190
+ const overrideDelay = Number(process.env.GSD_PR_MERGEABILITY_BASE_DELAY_MS);
191
+ const baseDelayMs = Number.isFinite(overrideDelay) && overrideDelay >= 0 ? overrideDelay : BASE_DELAY_MS;
192
+
193
+ const fetchPr = async (number) => {
194
+ const url = `${apiBase}/repos/${repo}/pulls/${number}`;
195
+ const headers = {
196
+ accept: 'application/vnd.github+json',
197
+ 'x-github-api-version': '2022-11-28',
198
+ 'user-agent': 'gsd-core-ci-pr-mergeability',
199
+ };
200
+ if (token) headers.authorization = `Bearer ${token}`;
201
+ const response = await fetch(url, { headers, signal: AbortSignal.timeout(10000) });
202
+ if (!response.ok) {
203
+ throw new Error(`GitHub API responded ${response.status}`);
204
+ }
205
+ const text = await response.text();
206
+ try {
207
+ return JSON.parse(text);
208
+ } catch {
209
+ return null;
210
+ }
211
+ };
212
+
213
+ const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
214
+
215
+ const result = await resolveMergeability({
216
+ fetchPr,
217
+ sleep,
218
+ eventName,
219
+ prNumber: Number.isNaN(prNumber) ? NaN : prNumber,
220
+ maxAttempts: MAX_ATTEMPTS,
221
+ baseDelayMs,
222
+ });
223
+
224
+ const mergeable = result.verdict === VERDICT.CONFLICTED ? 'false' : 'true';
225
+ writeOutput([`verdict=${result.verdict}`, `mergeable=${mergeable}`]);
226
+ writeSummary(`PR mergeability preflight: ${result.verdict}`);
227
+
228
+ if (result.verdict === VERDICT.CONFLICTED) {
229
+ process.stderr.write(
230
+ `::error::This pull request cannot cleanly merge ${baseBranch}. Rebase onto current ${baseBranch} and push again; no CI will run until the conflict is resolved.\n`,
231
+ );
232
+ const state = result.payload && typeof result.payload === 'object' ? result.payload.mergeable_state : undefined;
233
+ if (state) {
234
+ process.stderr.write(`::error::mergeable_state: ${state}\n`);
235
+ }
236
+ return 1;
237
+ }
238
+
239
+ if (result.verdict === VERDICT.INDETERMINATE) {
240
+ process.stderr.write(
241
+ '::warning::GitHub did not report mergeability within the polling budget; proceeding (fail-open). scripts/ci-rebase-check.cjs remains the in-job backstop.\n',
242
+ );
243
+ return 0;
244
+ }
245
+
246
+ process.stdout.write(`PR mergeability preflight: ${result.verdict}\n`);
247
+ return 0;
248
+ }
249
+
250
+ if (require.main === module) {
251
+ runMain(main);
252
+ }
253
+
254
+ module.exports = {
255
+ VERDICT,
256
+ MAX_ATTEMPTS,
257
+ BASE_DELAY_MS,
258
+ classifyMergeability,
259
+ nextDelayMs,
260
+ resolveMergeability,
261
+ main,
262
+ };