@opengsd/gsd-core 1.11.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 (395) 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 +1 -1
  5. package/agents/gsd-debugger.md +1 -1
  6. package/agents/gsd-dom-verifier.md +169 -0
  7. package/agents/gsd-eval-auditor.md +1 -1
  8. package/agents/gsd-executor.md +17 -9
  9. package/agents/gsd-framework-selector.md +1 -3
  10. package/agents/gsd-intel-updater.md +1 -1
  11. package/agents/gsd-mempalace-curator.md +0 -1
  12. package/agents/gsd-pattern-mapper.md +11 -0
  13. package/agents/gsd-phase-researcher.md +3 -1
  14. package/agents/gsd-plan-checker.md +15 -55
  15. package/agents/gsd-planner.md +6 -4
  16. package/agents/gsd-project-researcher.md +1 -1
  17. package/agents/gsd-research-synthesizer.md +2 -2
  18. package/agents/gsd-roadmapper.md +15 -11
  19. package/agents/gsd-ui-checker.md +63 -4
  20. package/agents/gsd-ui-researcher.md +41 -3
  21. package/agents/gsd-verifier.md +1 -1
  22. package/bin/install.js +609 -134
  23. package/commands/gsd/discuss-phase.md +1 -1
  24. package/commands/gsd/import.md +1 -1
  25. package/commands/gsd/quick.md +8 -4
  26. package/gsd-core/bin/gsd-tools.cjs +567 -51
  27. package/gsd-core/bin/lib/active-workstream-store.cjs +8 -0
  28. package/gsd-core/bin/lib/adr-parser.cjs +13 -7
  29. package/gsd-core/bin/lib/agent-install-check.cjs +162 -0
  30. package/gsd-core/bin/lib/api-coverage.cjs +30 -9
  31. package/gsd-core/bin/lib/artifacts.cjs +2 -0
  32. package/gsd-core/bin/lib/assumption-delta.cjs +30 -11
  33. package/gsd-core/bin/lib/audit.cjs +163 -41
  34. package/gsd-core/bin/lib/broken-windows.cjs +306 -28
  35. package/gsd-core/bin/lib/capability-lock.cjs +10 -4
  36. package/gsd-core/bin/lib/capability-registry.cjs +336 -95
  37. package/gsd-core/bin/lib/capability-state.cjs +18 -3
  38. package/gsd-core/bin/lib/capability-validator.cjs +205 -18
  39. package/gsd-core/bin/lib/check-command-router.cjs +145 -5
  40. package/gsd-core/bin/lib/cli-exit.cjs +496 -10
  41. package/gsd-core/bin/lib/code-review-depth.cjs +288 -0
  42. package/gsd-core/bin/lib/codex-agent-toml.cjs +410 -4
  43. package/gsd-core/bin/lib/command-arg-projection.cjs +144 -14
  44. package/gsd-core/bin/lib/command-routing-hub.cjs +31 -2
  45. package/gsd-core/bin/lib/commands.cjs +543 -44
  46. package/gsd-core/bin/lib/complexity-trigger.cjs +26 -6
  47. package/gsd-core/bin/lib/config-loader.cjs +118 -29
  48. package/gsd-core/bin/lib/config.cjs +92 -2
  49. package/gsd-core/bin/lib/configuration.cjs +129 -37
  50. package/gsd-core/bin/lib/core-utils.cjs +84 -7
  51. package/gsd-core/bin/lib/edge-probe.cjs +9 -1
  52. package/gsd-core/bin/lib/estimate-cli.cjs +55 -11
  53. package/gsd-core/bin/lib/exit-code-registry.cjs +98 -0
  54. package/gsd-core/bin/lib/frontmatter.cjs +840 -305
  55. package/gsd-core/bin/lib/gap-checker.cjs +27 -3
  56. package/gsd-core/bin/lib/git-base-branch.cjs +174 -39
  57. package/gsd-core/bin/lib/health-diagnostic-rules/consistency.cjs +7 -3
  58. package/gsd-core/bin/lib/health-diagnostic-rules/roadmap-disk-consistency.cjs +6 -3
  59. package/gsd-core/bin/lib/health-diagnostic-rules/worktree-health.cjs +22 -8
  60. package/gsd-core/bin/lib/health-diagnostic.cjs +23 -3
  61. package/gsd-core/bin/lib/host-integration.cjs +39 -6
  62. package/gsd-core/bin/lib/init-command-router.cjs +118 -21
  63. package/gsd-core/bin/lib/init.cjs +120 -41
  64. package/gsd-core/bin/lib/install-engine.cjs +68 -3
  65. package/gsd-core/bin/lib/install-model-override-resolver.cjs +33 -1
  66. package/gsd-core/bin/lib/install-profiles.cjs +78 -4
  67. package/gsd-core/bin/lib/installer-migration-report.cjs +3 -0
  68. package/gsd-core/bin/lib/installer-migrations/010-antigravity-retire-confighome-artifacts.cjs +169 -0
  69. package/gsd-core/bin/lib/installer-migrations.cjs +10 -7
  70. package/gsd-core/bin/lib/intel.cjs +101 -26
  71. package/gsd-core/bin/lib/io.cjs +160 -15
  72. package/gsd-core/bin/lib/learnings.cjs +85 -14
  73. package/gsd-core/bin/lib/legacy-cleanup.cjs +8 -2
  74. package/gsd-core/bin/lib/markdown-table.cjs +52 -4
  75. package/gsd-core/bin/lib/milestone.cjs +90 -5
  76. package/gsd-core/bin/lib/model-catalog.cjs +177 -19
  77. package/gsd-core/bin/lib/model-resolver.cjs +10 -28
  78. package/gsd-core/bin/lib/onboard-projection.cjs +5 -1
  79. package/gsd-core/bin/lib/phase-estimation.cjs +17 -8
  80. package/gsd-core/bin/lib/phase-id.cjs +70 -4
  81. package/gsd-core/bin/lib/phase-lifecycle.cjs +24 -16
  82. package/gsd-core/bin/lib/phase-locator.cjs +138 -17
  83. package/gsd-core/bin/lib/phase.cjs +405 -84
  84. package/gsd-core/bin/lib/plan-document.cjs +263 -0
  85. package/gsd-core/bin/lib/plan-scan.cjs +13 -2
  86. package/gsd-core/bin/lib/planning-command-router.cjs +61 -0
  87. package/gsd-core/bin/lib/planning-inspect.cjs +1168 -0
  88. package/gsd-core/bin/lib/planning-snapshot.cjs +18 -14
  89. package/gsd-core/bin/lib/planning-workspace.cjs +56 -0
  90. package/gsd-core/bin/lib/probe-core.cjs +4 -1
  91. package/gsd-core/bin/lib/profile-pipeline-command-router.cjs +50 -7
  92. package/gsd-core/bin/lib/profile-pipeline.cjs +6 -3
  93. package/gsd-core/bin/lib/real-home-guard.cjs +419 -0
  94. package/gsd-core/bin/lib/refactor-trigger-command-router.cjs +71 -45
  95. package/gsd-core/bin/lib/review-lane-descriptor.cjs +9 -9
  96. package/gsd-core/bin/lib/roadmap-command-router.cjs +45 -31
  97. package/gsd-core/bin/lib/roadmap-parser.cjs +79 -16
  98. package/gsd-core/bin/lib/roadmap.cjs +74 -19
  99. package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +96 -8
  100. package/gsd-core/bin/lib/runtime-artifact-layout.cjs +34 -1
  101. package/gsd-core/bin/lib/runtime-hooks-surface.cjs +287 -55
  102. package/gsd-core/bin/lib/runtime-identity.cjs +234 -0
  103. package/gsd-core/bin/lib/runtime-slash.cjs +72 -2
  104. package/gsd-core/bin/lib/shell-command-projection.cjs +71 -8
  105. package/gsd-core/bin/lib/smart-entry.cjs +12 -22
  106. package/gsd-core/bin/lib/spec-section.cjs +12 -7
  107. package/gsd-core/bin/lib/state-command-router.cjs +47 -18
  108. package/gsd-core/bin/lib/state-contract.cjs +359 -0
  109. package/gsd-core/bin/lib/state-document.cjs +186 -0
  110. package/gsd-core/bin/lib/state-md-schema.cjs +221 -0
  111. package/gsd-core/bin/lib/state-transition.cjs +517 -101
  112. package/gsd-core/bin/lib/state.cjs +946 -163
  113. package/gsd-core/bin/lib/surface.cjs +10 -2
  114. package/gsd-core/bin/lib/task-command-router.cjs +111 -1
  115. package/gsd-core/bin/lib/task-content-resolution.cjs +368 -0
  116. package/gsd-core/bin/lib/teams-status.cjs +4 -1
  117. package/gsd-core/bin/lib/uat-predicate.cjs +58 -20
  118. package/gsd-core/bin/lib/uat.cjs +1376 -125
  119. package/gsd-core/bin/lib/ui-consideration-probe.cjs +9 -1
  120. package/gsd-core/bin/lib/ui-safety-gate.cjs +37 -7
  121. package/gsd-core/bin/lib/unusable-input.cjs +13 -0
  122. package/gsd-core/bin/lib/validate-command-router.cjs +2 -2
  123. package/gsd-core/bin/lib/vendor/README.md +43 -5
  124. package/gsd-core/bin/lib/vendor/js-yaml.cjs +3014 -0
  125. package/gsd-core/bin/lib/verification.cjs +14 -1
  126. package/gsd-core/bin/lib/verify-command-grounding.cjs +846 -0
  127. package/gsd-core/bin/lib/verify.cjs +95 -40
  128. package/gsd-core/bin/lib/workstream-name-policy.cjs +25 -4
  129. package/gsd-core/bin/lib/worktree-base-ref.cjs +66 -12
  130. package/gsd-core/bin/lib/worktree-safety.cjs +177 -21
  131. package/gsd-core/bin/shared/config-defaults.manifest.json +7 -1
  132. package/gsd-core/bin/shared/config-schema.manifest.json +5 -0
  133. package/gsd-core/bin/shared/exit-codes.json +8 -0
  134. package/gsd-core/bin/shared/exit-codes.sh +20 -0
  135. package/gsd-core/bin/shared/model-catalog.json +8 -1
  136. package/gsd-core/references/agent-contracts.md +3 -2
  137. package/gsd-core/references/api-coverage.md +24 -2
  138. package/gsd-core/references/autonomous-smart-discuss.md +3 -3
  139. package/gsd-core/references/checkpoints.md +37 -19
  140. package/gsd-core/references/decimal-phase-calculation.md +5 -5
  141. package/gsd-core/references/edge-probe.md +8 -0
  142. package/gsd-core/references/execute-mvp-tdd.md +1 -3
  143. package/gsd-core/references/execute-phase-between-wave-reset.md +9 -12
  144. package/gsd-core/references/execute-phase-wave-guard.md +11 -9
  145. package/gsd-core/references/failing-direction.md +78 -0
  146. package/gsd-core/references/gate-prompts.md +1 -1
  147. package/gsd-core/references/git-integration.md +5 -5
  148. package/gsd-core/references/git-planning-commit.md +3 -3
  149. package/gsd-core/references/gsd-run-resolver.md +1 -1
  150. package/gsd-core/references/loop-hook-dispatch.md +22 -0
  151. package/gsd-core/references/model-profiles.md +1 -1
  152. package/gsd-core/references/nyquist-compliance.md +74 -0
  153. package/gsd-core/references/offer-next.md +3 -5
  154. package/gsd-core/references/phase-argument-parsing.md +3 -3
  155. package/gsd-core/references/planner-failing-direction.md +53 -0
  156. package/gsd-core/references/planner-human-verify-mode.md +15 -1
  157. package/gsd-core/references/planner-revision.md +1 -1
  158. package/gsd-core/references/planner-verify-command-grounding.md +17 -0
  159. package/gsd-core/references/planning-config.md +37 -8
  160. package/gsd-core/references/reviewer-instances.md +31 -0
  161. package/gsd-core/references/runtime-aware-dispatch.md +1 -1
  162. package/gsd-core/references/tdd.md +1 -3
  163. package/gsd-core/references/ui-brand.md +65 -21
  164. package/gsd-core/references/ui-consideration-probe.md +1 -1
  165. package/gsd-core/references/universal-anti-patterns.md +2 -2
  166. package/gsd-core/references/verify-command-path-resolvability.md +42 -0
  167. package/gsd-core/references/verify-mvp-mode.md +1 -1
  168. package/gsd-core/references/workstream-flag.md +11 -11
  169. package/gsd-core/templates/README.md +1 -1
  170. package/gsd-core/templates/SECURITY.md +3 -3
  171. package/gsd-core/templates/UI-SPEC.md +25 -3
  172. package/gsd-core/templates/VALIDATION.md +3 -3
  173. package/gsd-core/templates/phase-prompt.md +3 -0
  174. package/gsd-core/templates/state.md +7 -0
  175. package/gsd-core/workflows/_runtime-launcher.snippet.sh +1 -1
  176. package/gsd-core/workflows/add-backlog.md +1 -1
  177. package/gsd-core/workflows/add-phase.md +3 -3
  178. package/gsd-core/workflows/add-tests.md +3 -8
  179. package/gsd-core/workflows/add-todo.md +1 -1
  180. package/gsd-core/workflows/ai-integration-phase.md +4 -9
  181. package/gsd-core/workflows/audit-fix.md +12 -3
  182. package/gsd-core/workflows/audit-milestone.md +9 -9
  183. package/gsd-core/workflows/audit-uat.md +17 -2
  184. package/gsd-core/workflows/autonomous/steps/converge-fail-fast.md +2 -2
  185. package/gsd-core/workflows/autonomous.md +10 -26
  186. package/gsd-core/workflows/check-todos.md +1 -1
  187. package/gsd-core/workflows/cleanup.md +2 -2
  188. package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +1 -1
  189. package/gsd-core/workflows/code-review-fix.md +1 -1
  190. package/gsd-core/workflows/code-review.md +121 -40
  191. package/gsd-core/workflows/complete-milestone.md +15 -10
  192. package/gsd-core/workflows/debug.md +5 -3
  193. package/gsd-core/workflows/diagnose-issues.md +12 -6
  194. package/gsd-core/workflows/discuss-phase/modes/advisor.md +1 -1
  195. package/gsd-core/workflows/discuss-phase/modes/chain.md +3 -7
  196. package/gsd-core/workflows/discuss-phase/modes/text.md +1 -1
  197. package/gsd-core/workflows/discuss-phase-assumptions/steps/auto-advance-dispatch.md +1 -3
  198. package/gsd-core/workflows/discuss-phase-assumptions.md +2 -2
  199. package/gsd-core/workflows/discuss-phase.md +1 -1
  200. package/gsd-core/workflows/do.md +3 -6
  201. package/gsd-core/workflows/docs-update.md +5 -4
  202. package/gsd-core/workflows/edit-phase.md +1 -1
  203. package/gsd-core/workflows/eval-review.md +4 -9
  204. package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +1 -1
  205. package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +113 -11
  206. package/gsd-core/workflows/execute-phase/steps/gap-closure-artifacts.md +1 -1
  207. package/gsd-core/workflows/execute-phase/steps/partial-wave.md +1 -1
  208. package/gsd-core/workflows/execute-phase/steps/per-plan-executor-routing.md +1 -1
  209. package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +22 -4
  210. package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +2 -2
  211. package/gsd-core/workflows/execute-phase/steps/protected-branch.md +21 -0
  212. package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +2 -2
  213. package/gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md +39 -0
  214. package/gsd-core/workflows/execute-phase.md +38 -54
  215. package/gsd-core/workflows/execute-plan.md +17 -12
  216. package/gsd-core/workflows/explore.md +1 -1
  217. package/gsd-core/workflows/extract-learnings.md +1 -1
  218. package/gsd-core/workflows/fast.md +2 -2
  219. package/gsd-core/workflows/forensics.md +1 -1
  220. package/gsd-core/workflows/graduation.md +5 -5
  221. package/gsd-core/workflows/health.md +3 -6
  222. package/gsd-core/workflows/import.md +14 -11
  223. package/gsd-core/workflows/inbox.md +4 -5
  224. package/gsd-core/workflows/ingest-docs.md +44 -11
  225. package/gsd-core/workflows/insert-phase.md +5 -5
  226. package/gsd-core/workflows/list-seeds.md +5 -3
  227. package/gsd-core/workflows/list-workspaces.md +1 -1
  228. package/gsd-core/workflows/manager.md +12 -23
  229. package/gsd-core/workflows/map-codebase.md +1 -1
  230. package/gsd-core/workflows/milestone-summary.md +1 -1
  231. package/gsd-core/workflows/mvp-phase.md +2 -2
  232. package/gsd-core/workflows/new-milestone.md +9 -21
  233. package/gsd-core/workflows/new-project/steps/auto-mode-config.md +1 -1
  234. package/gsd-core/workflows/new-project.md +12 -26
  235. package/gsd-core/workflows/new-workspace.md +1 -1
  236. package/gsd-core/workflows/next.md +2 -2
  237. package/gsd-core/workflows/pause-work.md +1 -1
  238. package/gsd-core/workflows/plan-phase/steps/adr-ingest-express-path.md +1 -1
  239. package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +1 -1
  240. package/gsd-core/workflows/plan-phase/steps/prd-express-path.md +2 -4
  241. package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +3 -3
  242. package/gsd-core/workflows/plan-phase.md +121 -42
  243. package/gsd-core/workflows/plan-review-convergence.md +46 -9
  244. package/gsd-core/workflows/plant-seed.md +2 -2
  245. package/gsd-core/workflows/pr-branch.md +187 -51
  246. package/gsd-core/workflows/profile-user.md +16 -14
  247. package/gsd-core/workflows/progress.md +27 -12
  248. package/gsd-core/workflows/quick/steps/discussion-phase.md +1 -3
  249. package/gsd-core/workflows/quick/steps/plan-checker-loop.md +1 -3
  250. package/gsd-core/workflows/quick/steps/quick-verification.md +2 -4
  251. package/gsd-core/workflows/quick/steps/research-phase.md +2 -4
  252. package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +3 -3
  253. package/gsd-core/workflows/quick.md +20 -29
  254. package/gsd-core/workflows/remove-phase.md +4 -4
  255. package/gsd-core/workflows/remove-workspace.md +2 -2
  256. package/gsd-core/workflows/resume-project.md +8 -12
  257. package/gsd-core/workflows/review.md +193 -15
  258. package/gsd-core/workflows/scan.md +1 -1
  259. package/gsd-core/workflows/secure-phase.md +2 -2
  260. package/gsd-core/workflows/settings-advanced.md +7 -9
  261. package/gsd-core/workflows/settings-integrations.md +64 -31
  262. package/gsd-core/workflows/settings.md +3 -5
  263. package/gsd-core/workflows/ship.md +12 -6
  264. package/gsd-core/workflows/sketch-wrap-up.md +11 -17
  265. package/gsd-core/workflows/sketch.md +12 -18
  266. package/gsd-core/workflows/smart-entry.md +3 -5
  267. package/gsd-core/workflows/spec-phase.md +23 -1
  268. package/gsd-core/workflows/spike-wrap-up.md +7 -11
  269. package/gsd-core/workflows/spike.md +20 -31
  270. package/gsd-core/workflows/stats.md +2 -2
  271. package/gsd-core/workflows/sync-skills.md +1 -1
  272. package/gsd-core/workflows/thread.md +11 -7
  273. package/gsd-core/workflows/transition.md +5 -5
  274. package/gsd-core/workflows/ui-phase.md +10 -16
  275. package/gsd-core/workflows/ui-review.md +6 -10
  276. package/gsd-core/workflows/ultraplan-phase.md +5 -13
  277. package/gsd-core/workflows/undo.md +8 -16
  278. package/gsd-core/workflows/update.md +6 -10
  279. package/gsd-core/workflows/validate-phase.md +2 -2
  280. package/gsd-core/workflows/verify-work/steps/automated-ui-verification.md +25 -1
  281. package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +1 -1
  282. package/gsd-core/workflows/verify-work.md +57 -18
  283. package/hooks/dist/gsd-agent-isolation-guard.js +77 -38
  284. package/hooks/dist/gsd-config-reload.js +18 -12
  285. package/hooks/dist/gsd-context-monitor.js +19 -10
  286. package/hooks/dist/gsd-cursor-post-tool.js +3 -1
  287. package/hooks/dist/gsd-cursor-pre-tool.js +3 -1
  288. package/hooks/dist/gsd-cursor-session-start.js +2 -1
  289. package/hooks/dist/gsd-cursor-stop.js +2 -1
  290. package/hooks/dist/gsd-cursor-subagent-start.js +28 -23
  291. package/hooks/dist/gsd-cursor-subagent-stop.js +3 -1
  292. package/hooks/dist/gsd-ensure-canonical-path.js +2 -1
  293. package/hooks/dist/gsd-graphify-update.sh +22 -18
  294. package/hooks/dist/gsd-node-runner.sh +76 -0
  295. package/hooks/dist/gsd-phase-boundary.sh +1 -0
  296. package/hooks/dist/gsd-prompt-guard.js +16 -7
  297. package/hooks/dist/gsd-read-guard.js +16 -7
  298. package/hooks/dist/gsd-read-injection-scanner.js +17 -8
  299. package/hooks/dist/gsd-session-state.sh +1 -0
  300. package/hooks/dist/gsd-statusline.js +215 -26
  301. package/hooks/dist/gsd-validate-commit.sh +80 -6
  302. package/hooks/dist/gsd-windsurf-pre-command.js +16 -11
  303. package/hooks/dist/gsd-windsurf-pre-write.js +22 -13
  304. package/hooks/dist/gsd-workflow-guard.js +34 -16
  305. package/hooks/dist/gsd-worktree-path-guard.js +36 -21
  306. package/hooks/dist/gsd-write-guard.js +35 -25
  307. package/hooks/dist/lib/cli-exit.js +560 -0
  308. package/hooks/dist/lib/exit-code-registry.js +98 -0
  309. package/hooks/dist/lib/git-probe.js +84 -0
  310. package/hooks/dist/lib/hook-exit.js +81 -0
  311. package/hooks/dist/managed-hooks-registry.cjs +3 -0
  312. package/hooks/gsd-agent-isolation-guard.js +77 -38
  313. package/hooks/gsd-config-reload.js +18 -12
  314. package/hooks/gsd-context-monitor.js +19 -10
  315. package/hooks/gsd-cursor-post-tool.js +3 -1
  316. package/hooks/gsd-cursor-pre-tool.js +3 -1
  317. package/hooks/gsd-cursor-session-start.js +2 -1
  318. package/hooks/gsd-cursor-stop.js +2 -1
  319. package/hooks/gsd-cursor-subagent-start.js +28 -23
  320. package/hooks/gsd-cursor-subagent-stop.js +3 -1
  321. package/hooks/gsd-ensure-canonical-path.js +2 -1
  322. package/hooks/gsd-graphify-update.sh +22 -18
  323. package/hooks/gsd-node-runner.sh +76 -0
  324. package/hooks/gsd-phase-boundary.sh +1 -0
  325. package/hooks/gsd-prompt-guard.js +16 -7
  326. package/hooks/gsd-read-guard.js +16 -7
  327. package/hooks/gsd-read-injection-scanner.js +17 -8
  328. package/hooks/gsd-session-state.sh +1 -0
  329. package/hooks/gsd-statusline.js +215 -26
  330. package/hooks/gsd-validate-commit.sh +80 -6
  331. package/hooks/gsd-windsurf-pre-command.js +16 -11
  332. package/hooks/gsd-windsurf-pre-write.js +22 -13
  333. package/hooks/gsd-workflow-guard.js +34 -16
  334. package/hooks/gsd-worktree-path-guard.js +36 -21
  335. package/hooks/gsd-write-guard.js +35 -25
  336. package/hooks/lib/cli-exit.js +560 -0
  337. package/hooks/lib/exit-code-registry.js +98 -0
  338. package/hooks/lib/git-probe.js +84 -0
  339. package/hooks/lib/hook-exit.js +81 -0
  340. package/hooks/managed-hooks-registry.cjs +3 -0
  341. package/package.json +12 -7
  342. package/scripts/base64-scan.sh +74 -12
  343. package/scripts/build-hooks.js +5 -0
  344. package/scripts/check-glossary-refs.cjs +77 -15
  345. package/scripts/check-mutation-score-ratchet.cjs +156 -0
  346. package/scripts/ci-check-job-near-cap.cjs +49 -0
  347. package/scripts/ci-pr-mergeability.cjs +262 -0
  348. package/scripts/ci-test-scope.cjs +45 -12
  349. package/scripts/ci-timeout-report.cjs +230 -0
  350. package/scripts/docs-guard-registry.cjs +396 -0
  351. package/scripts/gen-capability-registry.cjs +8 -6
  352. package/scripts/gen-exit-code-docs.cjs +318 -0
  353. package/scripts/gen-exit-code-registry.cjs +891 -0
  354. package/scripts/gen-features.cjs +836 -0
  355. package/scripts/gen-hooks-cli-exit.cjs +239 -0
  356. package/scripts/gen-install-tree-fixtures.cjs +2 -2
  357. package/scripts/gen-loop-host-contract.cjs +134 -1
  358. package/scripts/gen-scripts-cli-exit.cjs +185 -0
  359. package/scripts/gen-state-md-docs.cjs +727 -0
  360. package/scripts/{test-failure-reasons.cjs → gsd-test-gate-reasons.cjs} +6 -0
  361. package/scripts/lib/ci-job-timing.cjs +72 -0
  362. package/scripts/lib/cli-exit.cjs +546 -44
  363. package/scripts/lib/drift-scan.cjs +32 -2
  364. package/scripts/lib/exit-code-registry.cjs +98 -0
  365. package/scripts/lib/ndjson-reporter.cjs +119 -0
  366. package/scripts/lint-allow-test-rule-refs.unverified-ceiling.json +1 -1
  367. package/scripts/lint-docs-guard-registration.cjs +495 -0
  368. package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +193 -0
  369. package/scripts/lint-eslint-glob-coverage.allowlist.json +4 -0
  370. package/scripts/{lint-fix-has-regression-test.cjs → lint-fix-has-regression-tests.cjs} +12 -6
  371. package/scripts/lint-health-diagnostic-rule-table.cjs +65 -8
  372. package/scripts/lint-mutation-test-derivation-drift.cjs +86 -0
  373. package/scripts/lint-phase-enumeration-drift.cjs +21 -8
  374. package/scripts/lint-planning-prompt-drift.cjs +38 -1
  375. package/scripts/lint-removed-but-needed.cjs +184 -16
  376. package/scripts/lint-seam-enforcement.cjs +182 -0
  377. package/scripts/lint-slug-derivation-drift.cjs +921 -0
  378. package/scripts/lint-source-test-name-collision.cjs +241 -0
  379. package/scripts/lint-state-write-path-drift.cjs +337 -432
  380. package/scripts/lint-test-file-count.allowlist.json +122 -4
  381. package/scripts/lint-test-file-count.cjs +25 -3
  382. package/scripts/lint-unreachable-guard-drift.cjs +51 -64
  383. package/scripts/lint-vendored-deps.cjs +208 -35
  384. package/scripts/mutation-matrix.cjs +599 -50
  385. package/scripts/prompt-injection-scan.sh +75 -14
  386. package/scripts/secret-scan.sh +75 -13
  387. package/scripts/select-docs-guards.cjs +56 -0
  388. package/scripts/sync-runtime-launcher.cjs +22 -3
  389. package/skills/gsd-discuss-phase/SKILL.md +1 -1
  390. package/skills/gsd-import/SKILL.md +1 -1
  391. package/skills/gsd-quick/SKILL.md +8 -4
  392. package/vscode/package.json +1 -1
  393. package/bin/lib/ui-safety-gate.cjs +0 -109
  394. package/scripts/lint-emitted-drift-ack.cjs +0 -344
  395. package/scripts/state-write-path-drift-baseline.json +0 -19
@@ -0,0 +1,239 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * gen-hooks-cli-exit.cjs — generates hooks/lib/cli-exit.js from a fresh
4
+ * compile of src/cli-exit.cts.
5
+ *
6
+ * ADR-3889 Phase 7 (#3911): shipped hooks must be able to terminate through
7
+ * `terminateNow` WITHOUT depending on any build artifact. hooks/ runs
8
+ * straight from a raw, unbuilt clone (a hook is required by name via
9
+ * `require('./lib/cli-exit.js')` relative to the hook's own __dirname), so
10
+ * the generated file is compiled to a THROWAWAY outDir rather than read from
11
+ * gsd-core/bin/lib/ — reading the tracked build output would let a stale
12
+ * build produce a false green. This mirrors scripts/gen-scripts-cli-exit.cjs
13
+ * exactly (same compile-to-temp strategy, same banner/check/write shape);
14
+ * kept as a SIBLING script rather than folded into that one because
15
+ * gen-scripts-cli-exit.cjs is hard-coded to a single output path/extension
16
+ * (`scripts/lib/cli-exit.cjs`) throughout its BANNER prose and REASON
17
+ * messaging — parameterizing it for a second, differently-extensioned
18
+ * target (`.js`, not `.cjs`, to match the hooks/lib/*.js convention) would
19
+ * tangle a script that is otherwise simple and single-purpose.
20
+ *
21
+ * Usage:
22
+ * node scripts/gen-hooks-cli-exit.cjs # same as --write
23
+ * node scripts/gen-hooks-cli-exit.cjs --write # write hooks/lib/cli-exit.js
24
+ * node scripts/gen-hooks-cli-exit.cjs --check # exit 1 if committed file is stale
25
+ * node scripts/gen-hooks-cli-exit.cjs --out <path> # override the output path (default: hooks/lib/cli-exit.js) — honoured by BOTH --write and --check
26
+ *
27
+ * `--out` restores parity with the sibling scripts/gen-exit-code-registry.cjs,
28
+ * which already supports `--out`/`--scripts-out`/`--hooks-out`/`--dts-out`/
29
+ * `--sh-out` overrides honoured by its own `--check`. This script hardcoding
30
+ * `OUTPUT_PATH` with no override was an inconsistency between two sibling
31
+ * generators, not an intentionally narrower surface — tests need to redirect
32
+ * `--check` at a disposable tmpdir copy instead of corrupting the real
33
+ * committed artifact in place.
34
+ */
35
+
36
+ 'use strict';
37
+
38
+ const { execFileSync } = require('node:child_process');
39
+ const fs = require('node:fs');
40
+ const os = require('node:os');
41
+ const path = require('node:path');
42
+
43
+ const REPO_ROOT = path.resolve(__dirname, '..');
44
+ const OUTPUT_PATH = path.join(REPO_ROOT, 'hooks', 'lib', 'cli-exit.js');
45
+ const COMPILE_TIMEOUT_MS = 60_000;
46
+
47
+ /** Frozen reason codes so tests assert on structure, not prose. */
48
+ const REASON = Object.freeze({
49
+ OK: 'ok_generated_sync',
50
+ DRIFTED: 'fail_generated_drifted',
51
+ BUILD_FAILED: 'fail_build_failed',
52
+ MISSING_EMIT: 'fail_missing_emit',
53
+ USAGE: 'fail_usage',
54
+ });
55
+
56
+ const USAGE_MESSAGE = [
57
+ 'Usage: node scripts/gen-hooks-cli-exit.cjs [--write|--check] [--out <path>]',
58
+ ' (no flag) same as --write',
59
+ ' --write write hooks/lib/cli-exit.js',
60
+ ' --check exit 1 if the committed file is stale',
61
+ ' --out override the output artifact path (default: hooks/lib/cli-exit.js)',
62
+ ].join('\n');
63
+
64
+ const BANNER = [
65
+ '// GENERATED FILE — DO NOT EDIT BY HAND.',
66
+ '// Source of truth: src/cli-exit.cts. Regenerate with:',
67
+ '// node scripts/gen-hooks-cli-exit.cjs --write',
68
+ '// Byte-compared by `npm run lint:generated-sync` (#3911, ADR-3889 Phase 7).',
69
+ '//',
70
+ '// Why this copy exists: hooks/ runs straight from a raw, unbuilt clone — a',
71
+ '// shipped hook must be able to `require(\'./lib/cli-exit.js\')` relative to',
72
+ '// its own __dirname and terminate through `terminateNow` without depending',
73
+ '// on any build artifact. gsd-core/bin/lib/cli-exit.cjs is gitignored tsc',
74
+ '// output and doubles as the build sentinel, so it cannot be required from',
75
+ '// here. `.js`, not `.cjs`, to match the hooks/lib/*.js convention. Hence one',
76
+ '// source, three emitted locations (gsd-core/bin/lib, scripts/lib, hooks/lib).',
77
+ '',
78
+ '',
79
+ ].join('\n');
80
+
81
+ /** Compile the whole project to a throwaway outDir so the work tree is untouched. */
82
+ function compileToTemp() {
83
+ const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-hooks-cli-exit-'));
84
+ try {
85
+ execFileSync(
86
+ process.execPath,
87
+ [
88
+ path.join(REPO_ROOT, 'node_modules', 'typescript', 'bin', 'tsc'),
89
+ '-p', path.join(REPO_ROOT, 'tsconfig.build.json'),
90
+ '--outDir', tmp,
91
+ // A throwaway outDir must not reuse the in-tree incremental state, or
92
+ // tsc skips emit for files it believes are already current.
93
+ '--incremental', 'false',
94
+ '--tsBuildInfoFile', 'null',
95
+ ],
96
+ { cwd: REPO_ROOT, encoding: 'utf8', stdio: 'pipe', timeout: COMPILE_TIMEOUT_MS },
97
+ );
98
+ return { ok: true, dir: tmp };
99
+ } catch (err) {
100
+ fs.rmSync(tmp, { recursive: true, force: true });
101
+ const detail = [err.stdout, err.stderr].filter(Boolean).join('\n').trim();
102
+ return { ok: false, detail };
103
+ }
104
+ }
105
+
106
+ /**
107
+ * Compile src/cli-exit.cts to a throwaway outDir and return the expected
108
+ * generated content (banner + compiled bytes, with the sibling registry
109
+ * require rewritten to the `.js` extension hooks/lib/ uses), or a failure
110
+ * descriptor.
111
+ *
112
+ * @returns {{ ok: true, content: string } | { ok: false, reason: string, detail?: string }}
113
+ */
114
+ function buildExpectedContent() {
115
+ const build = compileToTemp();
116
+ if (!build.ok) {
117
+ return { ok: false, reason: REASON.BUILD_FAILED, detail: build.detail };
118
+ }
119
+ try {
120
+ const emitted = path.join(build.dir, 'cli-exit.cjs');
121
+ if (!fs.existsSync(emitted)) {
122
+ return { ok: false, reason: REASON.MISSING_EMIT, detail: `no emit produced at ${emitted} from src/cli-exit.cts` };
123
+ }
124
+ const compiled = fs.readFileSync(emitted, 'utf8');
125
+ // hooks/lib/ ships `.js`, not `.cjs` (matching the hooks/lib/*.js
126
+ // convention — see the module header), so the sibling registry require
127
+ // tsc emits for src/cli-exit.cts's `require('./exit-code-registry.cjs')`
128
+ // must be rewritten to resolve the `.js` copy scripts/gen-exit-code-
129
+ // registry.cjs emits alongside this file, not the `.cjs` one. A literal,
130
+ // anchored replace (never a regex) so this can only ever touch the exact
131
+ // string tsc is known to emit for that one import.
132
+ const REGISTRY_REQUIRE_CJS = 'require("./exit-code-registry.cjs")';
133
+ const REGISTRY_REQUIRE_JS = 'require("./exit-code-registry.js")';
134
+ if (!compiled.includes(REGISTRY_REQUIRE_CJS)) {
135
+ return {
136
+ ok: false,
137
+ reason: REASON.MISSING_EMIT,
138
+ detail: `expected compiled output to contain ${REGISTRY_REQUIRE_CJS} (the sibling registry require) — tsc's emit shape may have changed`,
139
+ };
140
+ }
141
+ const rewritten = compiled.split(REGISTRY_REQUIRE_CJS).join(REGISTRY_REQUIRE_JS);
142
+ return { ok: true, content: BANNER + rewritten };
143
+ } finally {
144
+ fs.rmSync(build.dir, { recursive: true, force: true });
145
+ }
146
+ }
147
+
148
+ function doWrite(outPath) {
149
+ const result = buildExpectedContent();
150
+ if (!result.ok) {
151
+ console.error(`FAIL gen-hooks-cli-exit: ${result.reason}`);
152
+ if (result.detail) console.error(result.detail);
153
+ return 1;
154
+ }
155
+ fs.mkdirSync(path.dirname(outPath), { recursive: true });
156
+ fs.writeFileSync(outPath, result.content, 'utf8');
157
+ console.log(`ok gen-hooks-cli-exit: wrote ${path.relative(REPO_ROOT, outPath)}`);
158
+ return 0;
159
+ }
160
+
161
+ function doCheck(outPath) {
162
+ const result = buildExpectedContent();
163
+ if (!result.ok) {
164
+ console.error(`FAIL gen-hooks-cli-exit: ${result.reason}`);
165
+ if (result.detail) console.error(result.detail);
166
+ return 1;
167
+ }
168
+
169
+ if (!fs.existsSync(outPath)) {
170
+ console.error(`FAIL gen-hooks-cli-exit: ${REASON.MISSING_EMIT}`);
171
+ console.error(` ${path.relative(REPO_ROOT, outPath)} does not exist. Run:`);
172
+ console.error(' node scripts/gen-hooks-cli-exit.cjs --write');
173
+ return 1;
174
+ }
175
+
176
+ const committed = fs.readFileSync(outPath, 'utf8');
177
+ if (committed !== result.content) {
178
+ console.error(`FAIL gen-hooks-cli-exit: ${REASON.DRIFTED}`);
179
+ console.error(
180
+ ` ${path.relative(REPO_ROOT, outPath)} (${committed.length} bytes) != ` +
181
+ `compile of src/cli-exit.cts (${result.content.length} bytes)`,
182
+ );
183
+ console.error('');
184
+ console.error('Regenerate with:');
185
+ console.error(' node scripts/gen-hooks-cli-exit.cjs --write');
186
+ return 1;
187
+ }
188
+
189
+ console.log(`ok gen-hooks-cli-exit: ${path.relative(REPO_ROOT, outPath)} matches src/cli-exit.cts`);
190
+ return 0;
191
+ }
192
+
193
+ /**
194
+ * @returns {{mode:'write'|'check', outPath:?string}}
195
+ */
196
+ function parseArgs(argv) {
197
+ let mode = null;
198
+ let outPath = null;
199
+
200
+ for (let i = 0; i < argv.length; i++) {
201
+ const arg = argv[i];
202
+ if (arg === '--write' || arg === '--check') {
203
+ if (mode !== null) {
204
+ throw new Error(`conflicting mode flags: --${mode} and ${arg}`);
205
+ }
206
+ mode = arg === '--write' ? 'write' : 'check';
207
+ } else if (arg === '--out') {
208
+ const value = argv[++i];
209
+ if (value === undefined) throw new Error('--out requires a value');
210
+ outPath = value;
211
+ } else if (arg.startsWith('--out=')) {
212
+ outPath = arg.slice('--out='.length);
213
+ } else {
214
+ throw new Error(`unrecognized argument: ${arg}`);
215
+ }
216
+ }
217
+
218
+ return { mode: mode || 'write', outPath };
219
+ }
220
+
221
+ function main() {
222
+ let args;
223
+ try {
224
+ args = parseArgs(process.argv.slice(2));
225
+ } catch (err) {
226
+ console.error(`FAIL gen-hooks-cli-exit: ${REASON.USAGE}`);
227
+ console.error(` ${err.message}`);
228
+ console.error(USAGE_MESSAGE);
229
+ return 1;
230
+ }
231
+
232
+ const outPath = args.outPath || OUTPUT_PATH;
233
+
234
+ return args.mode === 'check' ? doCheck(outPath) : doWrite(outPath);
235
+ }
236
+
237
+ if (require.main === module) process.exitCode = main();
238
+
239
+ module.exports = { REASON, buildExpectedContent, OUTPUT_PATH, BANNER };
@@ -21,7 +21,7 @@ const ROOT = path.resolve(__dirname, '..');
21
21
  // buildParityManifest) is the canonical single source of truth in
22
22
  // tests/helpers/install-shared.cjs (issue #2266/#2267) — this generator does
23
23
  // not keep its own inline copy of the walk/exclusion logic.
24
- const { runMinimalInstall, RUNTIME_META, buildInstallTree, BUILD_SCRIPT } = require(path.join(ROOT, 'tests', 'helpers', 'install-shared.cjs'));
24
+ const { runMinimalInstall, RUNTIME_META, buildInstallTree, extraEmitRootsFor, BUILD_SCRIPT } = require(path.join(ROOT, 'tests', 'helpers', 'install-shared.cjs'));
25
25
  const FIXTURE_DIR = path.join(ROOT, 'tests', 'fixtures', 'install-tree');
26
26
 
27
27
  function cleanup(root) {
@@ -51,7 +51,7 @@ for (const runtime of targets) {
51
51
  const { configDir, root } = runMinimalInstall({ runtime, scope: 'global' });
52
52
  let actual;
53
53
  try {
54
- actual = buildInstallTree(configDir, root);
54
+ actual = buildInstallTree(configDir, root, extraEmitRootsFor(runtime, 'global', root));
55
55
  } finally {
56
56
  cleanup(root);
57
57
  }
@@ -446,8 +446,11 @@ const HOST_LOOP_FILES = STEP_WORKFLOWS.map((w) => 'gsd-core/workflows/' + w.file
446
446
  * @param {string} text Content of a workflow file (or any text).
447
447
  * @returns {Set<string>}
448
448
  */
449
+ /** The call-site shape both scanners key on — one regex, two consumers (#3606). */
450
+ const CALL_SITE_RE = /loop render-hooks\s+([a-z:]+)/g;
451
+
449
452
  function scanWiredPoints(text) {
450
- const re = /loop render-hooks\s+([a-z:]+)/g;
453
+ const re = CALL_SITE_RE;
451
454
  const result = new Set();
452
455
  let m;
453
456
  while ((m = re.exec(text)) !== null) {
@@ -456,6 +459,132 @@ function scanWiredPoints(text) {
456
459
  return result;
457
460
  }
458
461
 
462
+ // ─── Hook-kind coverage (#3606) ──────────────────────────────────────────────
463
+
464
+ const HOOK_KINDS = ['contribution', 'step', 'gate'];
465
+
466
+ /**
467
+ * The kinds one call site's dispatch text actually covers (#3606).
468
+ *
469
+ * A point having a `loop render-hooks <point>` call site proves the hooks are
470
+ * RENDERED, not that they are DISPATCHED — a consumer that iterates only
471
+ * `kind == "gate"` (or narrows `kind == "step"` to one `ref.skill`) silently
472
+ * drops every other registered kind. Coverage rules for the text following a
473
+ * call site, up to the next call site or the region cap, judged LINE by line:
474
+ *
475
+ * - A deferral line (one carrying an `@`-included path to the generic
476
+ * contract, e.g. `@gsd-core/references/loop-hook-dispatch.md`) that names a
477
+ * kind (`kind == "step"`) covers that kind; a deferral line with no kind
478
+ * discriminator ("apply each entry") covers every kind. A bare §-citation
479
+ * of the reference (validation guidance only, no `@`) covers nothing —
480
+ * plan-phase cites the gate-validation section while dispatching only gates.
481
+ * - Otherwise a kind is covered when some LINE dispatches it unconditionally:
482
+ * a `kind == "<kind>"` discriminator with NO same-line narrowing to one
483
+ * hook (`ref.skill ==`, `ref.agent ==`, `ref.command ==`). A narrowed line
484
+ * special-cases ONE hook and proves nothing about the kind generally — the
485
+ * exact hand-rolled-consumer shape the reference warns about.
486
+ *
487
+ * Quote style and spacing vary across the corpus (`kind == "step"`,
488
+ * `kind === 'gate'`), so the matcher is tolerant of both quote characters and
489
+ * of `==`/`===`.
490
+ *
491
+ * Pure: same input, same output; CRLF-safe (line splitting tolerates \r).
492
+ *
493
+ * @param {string} region Dispatch text following one call site.
494
+ * @returns {Set<string>}
495
+ */
496
+ function coveredKindsInRegion(region) {
497
+ const covered = new Set();
498
+ // Same-SEGMENT narrowing to ONE hook voids credit: `ref.skill ==`, `capId ==`,
499
+ // and `into ==` each special-case a subset, not the kind generally
500
+ // (plan-phase's `kind == "contribution" and capId == "security"` is the
501
+ // hand-rolled shape; `into == "planner"` covers only planner-targeted
502
+ // contributions). Segments, not lines: execute-phase legitimately writes
503
+ // "dispatch `kind == "step"` hooks per … . `ref.skill == "code-review"`:" —
504
+ // the deferral is one sentence, the specialization the next; narrowing in a
505
+ // DIFFERENT segment must not void the deferral's credit.
506
+ const narrowingRe = /(?:ref\.(?:skill|agent|command)|capId|into)\s*={2,3}/;
507
+ // Negated mentions describe an absence, not a dispatch ("Branch 1 — no active
508
+ // step hooks (`activeHooks` has no entry with `kind == "step"`)" — ship.md).
509
+ const negationRe = /\b(?:no|without|absent|lacks?|missing)\b[^.|]*kind\s*={2,3}/;
510
+ const deferralRe = /@\S*loop-hook-dispatch\.md/;
511
+ for (const line of region.split(/\r?\n/)) {
512
+ // Sentence segments: a `.`/`;` followed by whitespace ends a segment. A
513
+ // period NOT followed by whitespace (the `.md` inside a deferral path,
514
+ // `ref.skill`) is not a boundary.
515
+ for (const segment of line.split(/(?<=[.;])\s+/)) {
516
+ const kindDiscriminators = [];
517
+ for (const kind of HOOK_KINDS) {
518
+ if (new RegExp(`kind\\s*={2,3}\\s*["']${kind}["']`).test(segment)) kindDiscriminators.push(kind);
519
+ }
520
+ if (kindDiscriminators.length === 0) {
521
+ // A deferral with no kind discriminator ("apply each entry per …")
522
+ // still covers every kind.
523
+ if (deferralRe.test(segment)) for (const kind of HOOK_KINDS) covered.add(kind);
524
+ continue;
525
+ }
526
+ if (negationRe.test(segment)) continue;
527
+ if (deferralRe.test(segment)) {
528
+ // Deferral naming kinds ("dispatch `kind == "step"` hooks per …").
529
+ if (!narrowingRe.test(segment)) for (const kind of kindDiscriminators) covered.add(kind);
530
+ continue;
531
+ }
532
+ if (!narrowingRe.test(segment)) for (const kind of kindDiscriminators) covered.add(kind);
533
+ }
534
+ }
535
+ return covered;
536
+ }
537
+
538
+ /**
539
+ * Scan every `loop render-hooks <point>` call site in `text` and accumulate,
540
+ * per point, the union of hook kinds its dispatch regions cover (#3606).
541
+ *
542
+ * @param {string} text Content of a workflow file (or any text).
543
+ * @returns {Map<string, Set<string>>} point → covered kinds.
544
+ */
545
+ function scanWiredKinds(text) {
546
+ const result = new Map();
547
+ const siteRe = CALL_SITE_RE;
548
+ const sites = [];
549
+ let m;
550
+ while ((m = siteRe.exec(text)) !== null) sites.push({ point: m[1], start: m.index });
551
+ const REGION_CAP = 6000;
552
+ for (let i = 0; i < sites.length; i++) {
553
+ const regionEnd = i + 1 < sites.length ? sites[i + 1].start : Math.min(text.length, sites[i].start + REGION_CAP);
554
+ const region = text.slice(sites[i].start, regionEnd);
555
+ const covered = coveredKindsInRegion(region);
556
+ if (!result.has(sites[i].point)) result.set(sites[i].point, new Set());
557
+ for (const kind of covered) result.get(sites[i].point).add(kind);
558
+ }
559
+ return result;
560
+ }
561
+
562
+ /**
563
+ * Read every host-loop workflow file and return, per point, the union of hook
564
+ * kinds its call sites' dispatch text covers (#3606).
565
+ *
566
+ * @param {string} [repoRoot] Path to the repository root. Defaults to ROOT.
567
+ * @returns {Map<string, Set<string>>}
568
+ */
569
+ function getWiredKinds(repoRoot) {
570
+ const resolvedRoot = repoRoot !== undefined ? repoRoot : ROOT;
571
+ const result = new Map();
572
+ for (const relPath of HOST_LOOP_FILES) {
573
+ const absPath = path.join(resolvedRoot, relPath);
574
+ let content;
575
+ try {
576
+ content = fs.readFileSync(absPath, 'utf8');
577
+ } catch (err) {
578
+ throw new Error('getWiredKinds: cannot read host-loop file ' + absPath + ': ' + err.message);
579
+ }
580
+ for (const [point, kinds] of scanWiredKinds(content)) {
581
+ if (!result.has(point)) result.set(point, new Set());
582
+ for (const kind of kinds) result.get(point).add(kind);
583
+ }
584
+ }
585
+ return result;
586
+ }
587
+
459
588
  /**
460
589
  * Read every host-loop workflow file and return the union of all wired loop points
461
590
  * (i.e. points that have a `loop render-hooks <point>` call site).
@@ -497,6 +626,10 @@ module.exports = {
497
626
  ROLE_TO_AGENT,
498
627
  scanWiredPoints,
499
628
  getWiredLoopPoints,
629
+ coveredKindsInRegion,
630
+ scanWiredKinds,
631
+ getWiredKinds,
632
+ HOOK_KINDS,
500
633
  };
501
634
 
502
635
  // ─── CLI entry point ──────────────────────────────────────────────────────────
@@ -0,0 +1,185 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * gen-scripts-cli-exit.cjs — generates scripts/lib/cli-exit.cjs from a fresh
4
+ * compile of src/cli-exit.cts.
5
+ *
6
+ * ADR-3889 Phase 0 (#3904): scripts/lib/cli-exit.cjs used to be a hand-written
7
+ * fork of gsd-core/bin/lib/cli-exit.cjs (the compiled artifact of
8
+ * src/cli-exit.cts). The two drifted — only the .cts copy routed a
9
+ * non-ExitError throw through getJsonErrorMode() to emit a structured
10
+ * { ok:false, reason, message } envelope. This script makes scripts/lib/cli-exit.cjs
11
+ * a generated artifact of the SAME source, so the two surfaces cannot diverge
12
+ * again.
13
+ *
14
+ * scripts/ runs straight from the repo checkout and must work on an unbuilt
15
+ * clone (scripts/check-env.cjs requires this file before any build runs), so
16
+ * the generated file is compiled to a THROWAWAY outDir rather than read from
17
+ * gsd-core/bin/lib/ — reading the tracked build output would let a stale build
18
+ * produce a false green.
19
+ *
20
+ * Usage:
21
+ * node scripts/gen-scripts-cli-exit.cjs # same as --write
22
+ * node scripts/gen-scripts-cli-exit.cjs --write # write scripts/lib/cli-exit.cjs
23
+ * node scripts/gen-scripts-cli-exit.cjs --check # exit 1 if committed file is stale
24
+ */
25
+
26
+ 'use strict';
27
+
28
+ const { execFileSync } = require('node:child_process');
29
+ const fs = require('node:fs');
30
+ const os = require('node:os');
31
+ const path = require('node:path');
32
+
33
+ const REPO_ROOT = path.resolve(__dirname, '..');
34
+ const OUTPUT_PATH = path.join(REPO_ROOT, 'scripts', 'lib', 'cli-exit.cjs');
35
+ const COMPILE_TIMEOUT_MS = 60_000;
36
+
37
+ /** Frozen reason codes so tests assert on structure, not prose. */
38
+ const REASON = Object.freeze({
39
+ OK: 'ok_generated_sync',
40
+ DRIFTED: 'fail_generated_drifted',
41
+ BUILD_FAILED: 'fail_build_failed',
42
+ MISSING_EMIT: 'fail_missing_emit',
43
+ USAGE: 'fail_usage',
44
+ });
45
+
46
+ const USAGE_MESSAGE = [
47
+ 'Usage: node scripts/gen-scripts-cli-exit.cjs [--write|--check]',
48
+ ' (no flag) same as --write',
49
+ ' --write write scripts/lib/cli-exit.cjs',
50
+ ' --check exit 1 if the committed file is stale',
51
+ ].join('\n');
52
+
53
+ const BANNER = [
54
+ '// GENERATED FILE — DO NOT EDIT BY HAND.',
55
+ '// Source of truth: src/cli-exit.cts. Regenerate with:',
56
+ '// node scripts/gen-scripts-cli-exit.cjs --write',
57
+ '// Byte-compared by `npm run lint:generated-sync` (#3904, ADR-3889 Phase 0).',
58
+ '//',
59
+ '// Why this copy exists: scripts/ runs straight from the repo checkout and must',
60
+ '// work on an unbuilt clone — 64+ scripts require this file, including',
61
+ '// check-env.cjs, which runs before any build. gsd-core/bin/lib/cli-exit.cjs is',
62
+ '// gitignored tsc output and doubles as the build sentinel, so it cannot be',
63
+ '// required from here. Hence one source, two emitted locations.',
64
+ '',
65
+ '',
66
+ ].join('\n');
67
+
68
+ /** Compile the whole project to a throwaway outDir so the work tree is untouched. */
69
+ function compileToTemp() {
70
+ const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-scripts-cli-exit-'));
71
+ try {
72
+ execFileSync(
73
+ process.execPath,
74
+ [
75
+ path.join(REPO_ROOT, 'node_modules', 'typescript', 'bin', 'tsc'),
76
+ '-p', path.join(REPO_ROOT, 'tsconfig.build.json'),
77
+ '--outDir', tmp,
78
+ // A throwaway outDir must not reuse the in-tree incremental state, or
79
+ // tsc skips emit for files it believes are already current.
80
+ '--incremental', 'false',
81
+ '--tsBuildInfoFile', 'null',
82
+ ],
83
+ { cwd: REPO_ROOT, encoding: 'utf8', stdio: 'pipe', timeout: COMPILE_TIMEOUT_MS },
84
+ );
85
+ return { ok: true, dir: tmp };
86
+ } catch (err) {
87
+ fs.rmSync(tmp, { recursive: true, force: true });
88
+ const detail = [err.stdout, err.stderr].filter(Boolean).join('\n').trim();
89
+ return { ok: false, detail };
90
+ }
91
+ }
92
+
93
+ /**
94
+ * Compile src/cli-exit.cts to a throwaway outDir and return the expected
95
+ * generated content (banner + compiled bytes), or a failure descriptor.
96
+ *
97
+ * @returns {{ ok: true, content: string } | { ok: false, reason: string, detail?: string }}
98
+ */
99
+ function buildExpectedContent() {
100
+ const build = compileToTemp();
101
+ if (!build.ok) {
102
+ return { ok: false, reason: REASON.BUILD_FAILED, detail: build.detail };
103
+ }
104
+ try {
105
+ const emitted = path.join(build.dir, 'cli-exit.cjs');
106
+ if (!fs.existsSync(emitted)) {
107
+ return { ok: false, reason: REASON.MISSING_EMIT, detail: `no emit produced at ${emitted} from src/cli-exit.cts` };
108
+ }
109
+ const compiled = fs.readFileSync(emitted, 'utf8');
110
+ return { ok: true, content: BANNER + compiled };
111
+ } finally {
112
+ fs.rmSync(build.dir, { recursive: true, force: true });
113
+ }
114
+ }
115
+
116
+ function doWrite() {
117
+ const result = buildExpectedContent();
118
+ if (!result.ok) {
119
+ console.error(`FAIL gen-scripts-cli-exit: ${result.reason}`);
120
+ if (result.detail) console.error(result.detail);
121
+ return 1;
122
+ }
123
+ fs.mkdirSync(path.dirname(OUTPUT_PATH), { recursive: true });
124
+ fs.writeFileSync(OUTPUT_PATH, result.content, 'utf8');
125
+ console.log(`ok gen-scripts-cli-exit: wrote ${path.relative(REPO_ROOT, OUTPUT_PATH)}`);
126
+ return 0;
127
+ }
128
+
129
+ function doCheck() {
130
+ const result = buildExpectedContent();
131
+ if (!result.ok) {
132
+ console.error(`FAIL gen-scripts-cli-exit: ${result.reason}`);
133
+ if (result.detail) console.error(result.detail);
134
+ return 1;
135
+ }
136
+
137
+ if (!fs.existsSync(OUTPUT_PATH)) {
138
+ console.error(`FAIL gen-scripts-cli-exit: ${REASON.MISSING_EMIT}`);
139
+ console.error(` ${path.relative(REPO_ROOT, OUTPUT_PATH)} does not exist. Run:`);
140
+ console.error(' node scripts/gen-scripts-cli-exit.cjs --write');
141
+ return 1;
142
+ }
143
+
144
+ const committed = fs.readFileSync(OUTPUT_PATH, 'utf8');
145
+ if (committed !== result.content) {
146
+ console.error(`FAIL gen-scripts-cli-exit: ${REASON.DRIFTED}`);
147
+ console.error(
148
+ ` ${path.relative(REPO_ROOT, OUTPUT_PATH)} (${committed.length} bytes) != ` +
149
+ `compile of src/cli-exit.cts (${result.content.length} bytes)`,
150
+ );
151
+ console.error('');
152
+ console.error('Regenerate with:');
153
+ console.error(' node scripts/gen-scripts-cli-exit.cjs --write');
154
+ return 1;
155
+ }
156
+
157
+ console.log(`ok gen-scripts-cli-exit: ${path.relative(REPO_ROOT, OUTPUT_PATH)} matches src/cli-exit.cts`);
158
+ return 0;
159
+ }
160
+
161
+ function main() {
162
+ const flag = process.argv[2];
163
+ const extra = process.argv[3];
164
+
165
+ if (flag !== undefined && flag !== '--write' && flag !== '--check') {
166
+ console.error(`FAIL gen-scripts-cli-exit: ${REASON.USAGE}`);
167
+ console.error(` unrecognized argument: ${flag}`);
168
+ console.error(USAGE_MESSAGE);
169
+ return 1;
170
+ }
171
+
172
+ if (extra !== undefined) {
173
+ console.error(`FAIL gen-scripts-cli-exit: ${REASON.USAGE}`);
174
+ console.error(` unexpected extra argument: ${extra}`);
175
+ console.error(USAGE_MESSAGE);
176
+ return 1;
177
+ }
178
+
179
+ if (flag === '--check') return doCheck();
180
+ return doWrite();
181
+ }
182
+
183
+ if (require.main === module) process.exitCode = main();
184
+
185
+ module.exports = { REASON, buildExpectedContent, OUTPUT_PATH, BANNER };