@opengsd/gsd-core 1.11.0 → 1.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (498) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.opencode/plugins/gsd-core.js +12 -0
  4. package/agents/gsd-code-fixer.md +1 -1
  5. package/agents/gsd-debug-session-manager.md +1 -1
  6. package/agents/gsd-debugger.md +1 -1
  7. package/agents/gsd-dom-verifier.md +169 -0
  8. package/agents/gsd-eval-auditor.md +1 -1
  9. package/agents/gsd-executor.md +78 -42
  10. package/agents/gsd-framework-selector.md +1 -3
  11. package/agents/gsd-intel-updater.md +1 -1
  12. package/agents/gsd-mempalace-curator.md +0 -1
  13. package/agents/gsd-pattern-mapper.md +11 -0
  14. package/agents/gsd-phase-researcher.md +3 -1
  15. package/agents/gsd-plan-checker.md +91 -112
  16. package/agents/gsd-planner.md +20 -4
  17. package/agents/gsd-project-researcher.md +1 -1
  18. package/agents/gsd-research-synthesizer.md +2 -2
  19. package/agents/gsd-roadmapper.md +15 -11
  20. package/agents/gsd-ui-checker.md +82 -7
  21. package/agents/gsd-ui-researcher.md +70 -3
  22. package/agents/gsd-verifier.md +24 -2
  23. package/bin/install.js +847 -200
  24. package/commands/gsd/discuss-phase.md +1 -1
  25. package/commands/gsd/execute-phase.md +1 -1
  26. package/commands/gsd/import.md +1 -1
  27. package/commands/gsd/ns-workflow.md +2 -1
  28. package/commands/gsd/phase.md +1 -1
  29. package/commands/gsd/quick-batch.md +105 -0
  30. package/commands/gsd/quick.md +8 -4
  31. package/commands/gsd/surface.md +18 -8
  32. package/gsd-core/bin/gsd-tools.cjs +761 -100
  33. package/gsd-core/bin/lib/active-workstream-store.cjs +8 -0
  34. package/gsd-core/bin/lib/adr-parser.cjs +13 -7
  35. package/gsd-core/bin/lib/agent-install-check.cjs +162 -0
  36. package/gsd-core/bin/lib/api-coverage.cjs +30 -9
  37. package/gsd-core/bin/lib/artifacts.cjs +2 -0
  38. package/gsd-core/bin/lib/assumption-delta.cjs +30 -11
  39. package/gsd-core/bin/lib/audit.cjs +163 -41
  40. package/gsd-core/bin/lib/broken-windows.cjs +306 -28
  41. package/gsd-core/bin/lib/capability-activation.cjs +27 -0
  42. package/gsd-core/bin/lib/capability-lock.cjs +10 -4
  43. package/gsd-core/bin/lib/capability-registry.cjs +785 -144
  44. package/gsd-core/bin/lib/capability-state.cjs +25 -4
  45. package/gsd-core/bin/lib/capability-validator.cjs +321 -18
  46. package/gsd-core/bin/lib/capability-writer.cjs +14 -4
  47. package/gsd-core/bin/lib/check-command-router.cjs +229 -6
  48. package/gsd-core/bin/lib/claude-orchestration.cjs +10 -25
  49. package/gsd-core/bin/lib/cli-exit.cjs +496 -10
  50. package/gsd-core/bin/lib/clusters.cjs +1 -0
  51. package/gsd-core/bin/lib/code-review-depth.cjs +288 -0
  52. package/gsd-core/bin/lib/codex-agent-toml.cjs +410 -4
  53. package/gsd-core/bin/lib/command-aliases.cjs +16 -0
  54. package/gsd-core/bin/lib/command-arg-projection.cjs +144 -14
  55. package/gsd-core/bin/lib/command-routing-hub.cjs +31 -2
  56. package/gsd-core/bin/lib/commands.cjs +877 -54
  57. package/gsd-core/bin/lib/complexity-trigger.cjs +26 -6
  58. package/gsd-core/bin/lib/config-loader.cjs +121 -29
  59. package/gsd-core/bin/lib/config.cjs +92 -2
  60. package/gsd-core/bin/lib/configuration.cjs +129 -37
  61. package/gsd-core/bin/lib/core-utils.cjs +118 -14
  62. package/gsd-core/bin/lib/decisions.cjs +213 -1
  63. package/gsd-core/bin/lib/edge-probe.cjs +23 -2
  64. package/gsd-core/bin/lib/estimate-cli.cjs +55 -11
  65. package/gsd-core/bin/lib/exit-code-registry.cjs +98 -0
  66. package/gsd-core/bin/lib/file-overlap-partitioner.cjs +74 -0
  67. package/gsd-core/bin/lib/frontmatter.cjs +975 -326
  68. package/gsd-core/bin/lib/gap-checker.cjs +41 -8
  69. package/gsd-core/bin/lib/git-base-branch.cjs +182 -39
  70. package/gsd-core/bin/lib/health-diagnostic-rules/consistency.cjs +7 -3
  71. package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +8 -2
  72. package/gsd-core/bin/lib/health-diagnostic-rules/roadmap-disk-consistency.cjs +60 -14
  73. package/gsd-core/bin/lib/health-diagnostic-rules/state-consistency.cjs +75 -22
  74. package/gsd-core/bin/lib/health-diagnostic-rules/worktree-health.cjs +22 -8
  75. package/gsd-core/bin/lib/health-diagnostic.cjs +23 -3
  76. package/gsd-core/bin/lib/host-integration.cjs +96 -11
  77. package/gsd-core/bin/lib/init-command-router.cjs +132 -21
  78. package/gsd-core/bin/lib/init.cjs +252 -56
  79. package/gsd-core/bin/lib/install-engine.cjs +252 -15
  80. package/gsd-core/bin/lib/install-model-override-resolver.cjs +78 -1
  81. package/gsd-core/bin/lib/install-profiles.cjs +100 -18
  82. package/gsd-core/bin/lib/installer-migration-report.cjs +4 -0
  83. package/gsd-core/bin/lib/installer-migrations/010-antigravity-retire-confighome-artifacts.cjs +169 -0
  84. package/gsd-core/bin/lib/installer-migrations.cjs +10 -7
  85. package/gsd-core/bin/lib/intel.cjs +101 -26
  86. package/gsd-core/bin/lib/io.cjs +195 -15
  87. package/gsd-core/bin/lib/learnings.cjs +85 -14
  88. package/gsd-core/bin/lib/legacy-cleanup.cjs +8 -2
  89. package/gsd-core/bin/lib/loop-resolver.cjs +14 -8
  90. package/gsd-core/bin/lib/markdown-table.cjs +175 -4
  91. package/gsd-core/bin/lib/milestone.cjs +112 -7
  92. package/gsd-core/bin/lib/model-catalog.cjs +177 -19
  93. package/gsd-core/bin/lib/model-resolver.cjs +10 -28
  94. package/gsd-core/bin/lib/onboard-projection.cjs +5 -1
  95. package/gsd-core/bin/lib/phase-command-router.cjs +13 -6
  96. package/gsd-core/bin/lib/phase-estimation.cjs +17 -8
  97. package/gsd-core/bin/lib/phase-id.cjs +321 -13
  98. package/gsd-core/bin/lib/phase-lifecycle.cjs +24 -16
  99. package/gsd-core/bin/lib/phase-locator.cjs +138 -17
  100. package/gsd-core/bin/lib/phase.cjs +1175 -115
  101. package/gsd-core/bin/lib/plan-document.cjs +273 -0
  102. package/gsd-core/bin/lib/plan-scan.cjs +13 -2
  103. package/gsd-core/bin/lib/planning-command-router.cjs +61 -0
  104. package/gsd-core/bin/lib/planning-inspect.cjs +1168 -0
  105. package/gsd-core/bin/lib/planning-snapshot.cjs +165 -34
  106. package/gsd-core/bin/lib/planning-workspace.cjs +159 -28
  107. package/gsd-core/bin/lib/probe-core.cjs +4 -1
  108. package/gsd-core/bin/lib/profile-pipeline-command-router.cjs +50 -7
  109. package/gsd-core/bin/lib/profile-pipeline.cjs +6 -3
  110. package/gsd-core/bin/lib/quick-batch-command-router.cjs +285 -0
  111. package/gsd-core/bin/lib/quick-batch-dispatch.cjs +250 -0
  112. package/gsd-core/bin/lib/quick-batch.cjs +840 -0
  113. package/gsd-core/bin/lib/real-home-guard.cjs +419 -0
  114. package/gsd-core/bin/lib/refactor-trigger-command-router.cjs +71 -45
  115. package/gsd-core/bin/lib/review-lane-descriptor.cjs +62 -14
  116. package/gsd-core/bin/lib/review-lane-invocation.cjs +73 -1
  117. package/gsd-core/bin/lib/review-lane-runner.cjs +136 -10
  118. package/gsd-core/bin/lib/roadmap-command-router.cjs +45 -31
  119. package/gsd-core/bin/lib/roadmap-parser.cjs +577 -41
  120. package/gsd-core/bin/lib/roadmap.cjs +248 -64
  121. package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +329 -41
  122. package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +16 -17
  123. package/gsd-core/bin/lib/runtime-artifact-layout.cjs +320 -109
  124. package/gsd-core/bin/lib/runtime-hooks-surface.cjs +487 -83
  125. package/gsd-core/bin/lib/runtime-identity.cjs +234 -0
  126. package/gsd-core/bin/lib/runtime-slash.cjs +72 -2
  127. package/gsd-core/bin/lib/shell-command-projection.cjs +75 -8
  128. package/gsd-core/bin/lib/smart-entry.cjs +19 -31
  129. package/gsd-core/bin/lib/spec-section.cjs +12 -7
  130. package/gsd-core/bin/lib/state-command-router.cjs +47 -18
  131. package/gsd-core/bin/lib/state-contract.cjs +359 -0
  132. package/gsd-core/bin/lib/state-document.cjs +216 -5
  133. package/gsd-core/bin/lib/state-md-schema.cjs +231 -0
  134. package/gsd-core/bin/lib/state-transition.cjs +850 -145
  135. package/gsd-core/bin/lib/state.cjs +1629 -287
  136. package/gsd-core/bin/lib/surface.cjs +33 -10
  137. package/gsd-core/bin/lib/task-command-router.cjs +111 -1
  138. package/gsd-core/bin/lib/task-content-resolution.cjs +368 -0
  139. package/gsd-core/bin/lib/tdd-red-evidence.cjs +133 -0
  140. package/gsd-core/bin/lib/teams-status.cjs +4 -1
  141. package/gsd-core/bin/lib/uat-predicate.cjs +58 -20
  142. package/gsd-core/bin/lib/uat.cjs +2542 -387
  143. package/gsd-core/bin/lib/ui-consideration-probe.cjs +9 -1
  144. package/gsd-core/bin/lib/ui-safety-gate.cjs +37 -7
  145. package/gsd-core/bin/lib/unusable-input.cjs +13 -0
  146. package/gsd-core/bin/lib/update-context.cjs +6 -2
  147. package/gsd-core/bin/lib/validate-command-router.cjs +2 -2
  148. package/gsd-core/bin/lib/validate.cjs +230 -12
  149. package/gsd-core/bin/lib/vendor/README.md +43 -5
  150. package/gsd-core/bin/lib/vendor/js-yaml.cjs +3014 -0
  151. package/gsd-core/bin/lib/verification-command-router.cjs +2 -1
  152. package/gsd-core/bin/lib/verification.cjs +287 -13
  153. package/gsd-core/bin/lib/verify-command-grounding.cjs +846 -0
  154. package/gsd-core/bin/lib/verify-command-router.cjs +1 -0
  155. package/gsd-core/bin/lib/verify.cjs +441 -56
  156. package/gsd-core/bin/lib/workstream-inventory.cjs +20 -2
  157. package/gsd-core/bin/lib/workstream-name-policy.cjs +25 -4
  158. package/gsd-core/bin/lib/worktree-base-ref.cjs +66 -12
  159. package/gsd-core/bin/lib/worktree-safety.cjs +185 -21
  160. package/gsd-core/bin/shared/config-defaults.manifest.json +7 -1
  161. package/gsd-core/bin/shared/config-schema.manifest.json +13 -0
  162. package/gsd-core/bin/shared/exit-codes.json +8 -0
  163. package/gsd-core/bin/shared/exit-codes.sh +20 -0
  164. package/gsd-core/bin/shared/model-catalog.json +8 -1
  165. package/gsd-core/bin/verify-reapply-patches.cjs +70 -3
  166. package/gsd-core/references/agent-contracts.md +6 -5
  167. package/gsd-core/references/api-coverage.md +24 -2
  168. package/gsd-core/references/autonomous-smart-discuss.md +3 -3
  169. package/gsd-core/references/checkpoints.md +37 -19
  170. package/gsd-core/references/decimal-phase-calculation.md +5 -5
  171. package/gsd-core/references/edge-probe.md +17 -5
  172. package/gsd-core/references/execute-mvp-tdd.md +18 -18
  173. package/gsd-core/references/execute-phase-between-wave-reset.md +9 -12
  174. package/gsd-core/references/execute-phase-response-language.md +6 -0
  175. package/gsd-core/references/execute-phase-wave-guard.md +11 -9
  176. package/gsd-core/references/executor-examples.md +42 -0
  177. package/gsd-core/references/failing-direction.md +78 -0
  178. package/gsd-core/references/few-shot-examples/plan-checker.md +15 -15
  179. package/gsd-core/references/gate-prompts.md +1 -1
  180. package/gsd-core/references/git-integration.md +5 -5
  181. package/gsd-core/references/git-planning-commit.md +3 -3
  182. package/gsd-core/references/gsd-run-resolver.md +1 -1
  183. package/gsd-core/references/loop-hook-dispatch.md +22 -0
  184. package/gsd-core/references/model-profiles.md +1 -1
  185. package/gsd-core/references/mvp-concepts.md +2 -2
  186. package/gsd-core/references/nyquist-compliance.md +74 -0
  187. package/gsd-core/references/offer-next.md +3 -5
  188. package/gsd-core/references/phase-argument-parsing.md +3 -3
  189. package/gsd-core/references/plan-checker-examples.md +41 -0
  190. package/gsd-core/references/planner-antipatterns.md +25 -0
  191. package/gsd-core/references/planner-chunked.md +5 -1
  192. package/gsd-core/references/planner-coupling.md +42 -0
  193. package/gsd-core/references/planner-failing-direction.md +53 -0
  194. package/gsd-core/references/planner-human-verify-mode.md +15 -1
  195. package/gsd-core/references/planner-quick-batch.md +71 -0
  196. package/gsd-core/references/planner-reviews.md +47 -0
  197. package/gsd-core/references/planner-revision.md +76 -3
  198. package/gsd-core/references/planner-verify-command-grounding.md +17 -0
  199. package/gsd-core/references/planning-config.md +39 -9
  200. package/gsd-core/references/response-language-directive.md +9 -0
  201. package/gsd-core/references/reviewer-instances.md +31 -0
  202. package/gsd-core/references/revision-loop.md +118 -11
  203. package/gsd-core/references/runtime-aware-dispatch.md +1 -1
  204. package/gsd-core/references/tdd.md +15 -12
  205. package/gsd-core/references/ui-brand.md +65 -21
  206. package/gsd-core/references/ui-consideration-probe.md +1 -1
  207. package/gsd-core/references/universal-anti-patterns.md +2 -2
  208. package/gsd-core/references/verifier-evidence-gate.md +160 -0
  209. package/gsd-core/references/verify-command-path-resolvability.md +42 -0
  210. package/gsd-core/references/verify-mvp-mode.md +1 -1
  211. package/gsd-core/references/workstream-flag.md +11 -11
  212. package/gsd-core/templates/README.md +1 -1
  213. package/gsd-core/templates/SECURITY.md +3 -3
  214. package/gsd-core/templates/UI-SPEC.md +25 -3
  215. package/gsd-core/templates/VALIDATION.md +3 -3
  216. package/gsd-core/templates/phase-prompt.md +7 -0
  217. package/gsd-core/templates/state.md +7 -0
  218. package/gsd-core/templates/verification-report.md +5 -0
  219. package/gsd-core/workflows/_runtime-launcher.snippet.sh +1 -1
  220. package/gsd-core/workflows/add-backlog.md +3 -1
  221. package/gsd-core/workflows/add-phase.md +5 -3
  222. package/gsd-core/workflows/add-tests.md +4 -9
  223. package/gsd-core/workflows/add-todo.md +2 -2
  224. package/gsd-core/workflows/ai-integration-phase.md +5 -10
  225. package/gsd-core/workflows/analyze-dependencies.md +2 -0
  226. package/gsd-core/workflows/audit-fix.md +14 -3
  227. package/gsd-core/workflows/audit-milestone.md +11 -9
  228. package/gsd-core/workflows/audit-uat.md +19 -2
  229. package/gsd-core/workflows/autonomous/steps/converge-fail-fast.md +2 -2
  230. package/gsd-core/workflows/autonomous.md +12 -26
  231. package/gsd-core/workflows/check-todos.md +2 -2
  232. package/gsd-core/workflows/cleanup.md +3 -3
  233. package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +16 -14
  234. package/gsd-core/workflows/code-review-fix.md +3 -1
  235. package/gsd-core/workflows/code-review.md +192 -69
  236. package/gsd-core/workflows/complete-milestone.md +28 -14
  237. package/gsd-core/workflows/debug.md +6 -4
  238. package/gsd-core/workflows/diagnose-issues.md +17 -7
  239. package/gsd-core/workflows/discuss-phase/modes/advisor.md +3 -1
  240. package/gsd-core/workflows/discuss-phase/modes/all.md +2 -0
  241. package/gsd-core/workflows/discuss-phase/modes/analyze.md +2 -0
  242. package/gsd-core/workflows/discuss-phase/modes/auto.md +2 -0
  243. package/gsd-core/workflows/discuss-phase/modes/batch.md +2 -0
  244. package/gsd-core/workflows/discuss-phase/modes/chain.md +5 -7
  245. package/gsd-core/workflows/discuss-phase/modes/default.md +2 -0
  246. package/gsd-core/workflows/discuss-phase/modes/power.md +2 -0
  247. package/gsd-core/workflows/discuss-phase/modes/text.md +3 -1
  248. package/gsd-core/workflows/discuss-phase/templates/context.md +2 -0
  249. package/gsd-core/workflows/discuss-phase/templates/discussion-log.md +2 -0
  250. package/gsd-core/workflows/discuss-phase-assumptions/steps/auto-advance-dispatch.md +1 -3
  251. package/gsd-core/workflows/discuss-phase-assumptions.md +3 -3
  252. package/gsd-core/workflows/discuss-phase-power.md +2 -0
  253. package/gsd-core/workflows/discuss-phase.md +2 -2
  254. package/gsd-core/workflows/do.md +46 -19
  255. package/gsd-core/workflows/docs-update.md +6 -5
  256. package/gsd-core/workflows/edit-phase.md +3 -1
  257. package/gsd-core/workflows/eval-review.md +5 -10
  258. package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +3 -1
  259. package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +129 -11
  260. package/gsd-core/workflows/execute-phase/steps/gap-closure-artifacts.md +1 -1
  261. package/gsd-core/workflows/execute-phase/steps/partial-wave.md +1 -1
  262. package/gsd-core/workflows/execute-phase/steps/per-plan-executor-routing.md +1 -1
  263. package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +29 -5
  264. package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +2 -2
  265. package/gsd-core/workflows/execute-phase/steps/protected-branch.md +21 -0
  266. package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +4 -2
  267. package/gsd-core/workflows/execute-phase/steps/tdd-applicability-resolution.md +25 -0
  268. package/gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md +39 -0
  269. package/gsd-core/workflows/execute-phase/steps/worktree-recovery-policy.md +2 -0
  270. package/gsd-core/workflows/execute-phase.md +68 -66
  271. package/gsd-core/workflows/execute-plan.md +25 -20
  272. package/gsd-core/workflows/explore.md +3 -1
  273. package/gsd-core/workflows/extract-learnings.md +3 -1
  274. package/gsd-core/workflows/fast.md +8 -2
  275. package/gsd-core/workflows/forensics.md +3 -1
  276. package/gsd-core/workflows/graduation.md +6 -6
  277. package/gsd-core/workflows/health.md +4 -7
  278. package/gsd-core/workflows/help/modes/brief.md +2 -0
  279. package/gsd-core/workflows/help/modes/default.md +2 -0
  280. package/gsd-core/workflows/help/modes/full.md +12 -0
  281. package/gsd-core/workflows/help/modes/topic.md +2 -0
  282. package/gsd-core/workflows/help.md +2 -0
  283. package/gsd-core/workflows/import.md +17 -14
  284. package/gsd-core/workflows/inbox.md +5 -6
  285. package/gsd-core/workflows/ingest-docs.md +45 -12
  286. package/gsd-core/workflows/insert-phase.md +7 -5
  287. package/gsd-core/workflows/list-phase-assumptions.md +2 -0
  288. package/gsd-core/workflows/list-seeds.md +7 -3
  289. package/gsd-core/workflows/list-workspaces.md +3 -1
  290. package/gsd-core/workflows/manager.md +15 -26
  291. package/gsd-core/workflows/map-codebase.md +3 -1
  292. package/gsd-core/workflows/milestone-summary.md +3 -1
  293. package/gsd-core/workflows/mvp-phase.md +3 -3
  294. package/gsd-core/workflows/new-milestone.md +10 -22
  295. package/gsd-core/workflows/new-project/steps/auto-mode-config.md +1 -1
  296. package/gsd-core/workflows/new-project.md +17 -29
  297. package/gsd-core/workflows/new-workspace.md +2 -2
  298. package/gsd-core/workflows/next.md +4 -2
  299. package/gsd-core/workflows/node-repair.md +2 -0
  300. package/gsd-core/workflows/note.md +2 -0
  301. package/gsd-core/workflows/onboard.md +1 -1
  302. package/gsd-core/workflows/pause-work.md +20 -5
  303. package/gsd-core/workflows/plan-phase/steps/adr-ingest-express-path.md +1 -1
  304. package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +100 -18
  305. package/gsd-core/workflows/plan-phase/steps/prd-express-path.md +4 -4
  306. package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +12 -3
  307. package/gsd-core/workflows/plan-phase.md +251 -54
  308. package/gsd-core/workflows/plan-review-convergence.md +148 -19
  309. package/gsd-core/workflows/plant-seed.md +3 -3
  310. package/gsd-core/workflows/pr-branch.md +195 -51
  311. package/gsd-core/workflows/profile-user.md +17 -15
  312. package/gsd-core/workflows/progress/steps/forensic-audit.md +1 -1
  313. package/gsd-core/workflows/progress.md +52 -15
  314. package/gsd-core/workflows/quick/steps/discussion-phase.md +1 -3
  315. package/gsd-core/workflows/quick/steps/plan-checker-loop.md +38 -5
  316. package/gsd-core/workflows/quick/steps/quick-verification.md +2 -4
  317. package/gsd-core/workflows/quick/steps/research-phase.md +5 -7
  318. package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +3 -3
  319. package/gsd-core/workflows/quick-batch/steps/batch-init.md +55 -0
  320. package/gsd-core/workflows/quick-batch/steps/completion.md +65 -0
  321. package/gsd-core/workflows/quick-batch/steps/merge-wave.md +100 -0
  322. package/gsd-core/workflows/quick-batch/steps/plan-checker-loop.md +147 -0
  323. package/gsd-core/workflows/quick-batch/steps/planner-wave.md +158 -0
  324. package/gsd-core/workflows/quick-batch/steps/research-phase.md +95 -0
  325. package/gsd-core/workflows/quick-batch/steps/resume-mode.md +49 -0
  326. package/gsd-core/workflows/quick-batch/steps/verification-wave.md +73 -0
  327. package/gsd-core/workflows/quick-batch/steps/worktree-dispatch.md +169 -0
  328. package/gsd-core/workflows/quick-batch.md +203 -0
  329. package/gsd-core/workflows/quick.md +33 -32
  330. package/gsd-core/workflows/reapply-patches.md +2 -0
  331. package/gsd-core/workflows/remove-phase.md +6 -4
  332. package/gsd-core/workflows/remove-workspace.md +3 -3
  333. package/gsd-core/workflows/resume-project.md +14 -14
  334. package/gsd-core/workflows/review.md +404 -21
  335. package/gsd-core/workflows/scan.md +3 -1
  336. package/gsd-core/workflows/section-manifest.json +12 -0
  337. package/gsd-core/workflows/secure-phase.md +3 -3
  338. package/gsd-core/workflows/session-report.md +2 -0
  339. package/gsd-core/workflows/settings-advanced.md +9 -9
  340. package/gsd-core/workflows/settings-integrations.md +66 -32
  341. package/gsd-core/workflows/settings.md +4 -6
  342. package/gsd-core/workflows/ship.md +22 -16
  343. package/gsd-core/workflows/sketch-wrap-up.md +13 -17
  344. package/gsd-core/workflows/sketch.md +13 -19
  345. package/gsd-core/workflows/smart-entry.md +4 -6
  346. package/gsd-core/workflows/spec-phase.md +31 -4
  347. package/gsd-core/workflows/spike-wrap-up.md +9 -11
  348. package/gsd-core/workflows/spike.md +21 -32
  349. package/gsd-core/workflows/stats.md +4 -2
  350. package/gsd-core/workflows/sync-skills.md +13 -5
  351. package/gsd-core/workflows/thread.md +13 -7
  352. package/gsd-core/workflows/transition.md +7 -5
  353. package/gsd-core/workflows/ui-phase.md +36 -21
  354. package/gsd-core/workflows/ui-review.md +7 -11
  355. package/gsd-core/workflows/ultraplan-phase.md +7 -13
  356. package/gsd-core/workflows/undo.md +9 -17
  357. package/gsd-core/workflows/update.md +47 -48
  358. package/gsd-core/workflows/validate-phase.md +3 -3
  359. package/gsd-core/workflows/verify-work/steps/automated-ui-verification.md +25 -1
  360. package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +1 -1
  361. package/gsd-core/workflows/verify-work.md +106 -21
  362. package/hooks/dist/gsd-agent-isolation-guard.js +77 -38
  363. package/hooks/dist/gsd-check-update-worker.js +19 -2
  364. package/hooks/dist/gsd-config-reload.js +18 -12
  365. package/hooks/dist/gsd-context-monitor.js +302 -22
  366. package/hooks/dist/gsd-cursor-post-tool.js +3 -1
  367. package/hooks/dist/gsd-cursor-pre-tool.js +3 -1
  368. package/hooks/dist/gsd-cursor-session-start.js +2 -1
  369. package/hooks/dist/gsd-cursor-stop.js +2 -1
  370. package/hooks/dist/gsd-cursor-subagent-start.js +28 -23
  371. package/hooks/dist/gsd-cursor-subagent-stop.js +3 -1
  372. package/hooks/dist/gsd-ensure-canonical-path.js +2 -1
  373. package/hooks/dist/gsd-graphify-update.sh +22 -18
  374. package/hooks/dist/gsd-node-runner.sh +77 -0
  375. package/hooks/dist/gsd-phase-boundary.sh +1 -0
  376. package/hooks/dist/gsd-prompt-guard.js +46 -12
  377. package/hooks/dist/gsd-read-guard.js +18 -7
  378. package/hooks/dist/gsd-read-injection-scanner.js +22 -13
  379. package/hooks/dist/gsd-secret-read-guard.js +1079 -0
  380. package/hooks/dist/gsd-session-state.sh +1 -0
  381. package/hooks/dist/gsd-statusline.js +222 -29
  382. package/hooks/dist/gsd-validate-commit.sh +523 -12
  383. package/hooks/dist/gsd-windsurf-pre-command.js +16 -11
  384. package/hooks/dist/gsd-windsurf-pre-write.js +22 -13
  385. package/hooks/dist/gsd-workflow-guard.js +36 -17
  386. package/hooks/dist/gsd-worktree-path-guard.js +36 -21
  387. package/hooks/dist/gsd-write-guard.js +35 -25
  388. package/hooks/dist/lib/cli-exit.js +560 -0
  389. package/hooks/dist/lib/exit-code-registry.js +98 -0
  390. package/hooks/dist/lib/git-cmd.js +210 -1
  391. package/hooks/dist/lib/git-probe.js +84 -0
  392. package/hooks/dist/lib/hook-exit.js +81 -0
  393. package/hooks/dist/lib/injection-patterns.js +36 -6
  394. package/hooks/dist/managed-hooks-registry.cjs +4 -0
  395. package/hooks/gsd-agent-isolation-guard.js +77 -38
  396. package/hooks/gsd-check-update-worker.js +19 -2
  397. package/hooks/gsd-config-reload.js +18 -12
  398. package/hooks/gsd-context-monitor.js +302 -22
  399. package/hooks/gsd-cursor-post-tool.js +3 -1
  400. package/hooks/gsd-cursor-pre-tool.js +3 -1
  401. package/hooks/gsd-cursor-session-start.js +2 -1
  402. package/hooks/gsd-cursor-stop.js +2 -1
  403. package/hooks/gsd-cursor-subagent-start.js +28 -23
  404. package/hooks/gsd-cursor-subagent-stop.js +3 -1
  405. package/hooks/gsd-ensure-canonical-path.js +2 -1
  406. package/hooks/gsd-graphify-update.sh +22 -18
  407. package/hooks/gsd-node-runner.sh +77 -0
  408. package/hooks/gsd-phase-boundary.sh +1 -0
  409. package/hooks/gsd-prompt-guard.js +46 -12
  410. package/hooks/gsd-read-guard.js +18 -7
  411. package/hooks/gsd-read-injection-scanner.js +22 -13
  412. package/hooks/gsd-secret-read-guard.js +1079 -0
  413. package/hooks/gsd-session-state.sh +1 -0
  414. package/hooks/gsd-statusline.js +222 -29
  415. package/hooks/gsd-validate-commit.sh +523 -12
  416. package/hooks/gsd-windsurf-pre-command.js +16 -11
  417. package/hooks/gsd-windsurf-pre-write.js +22 -13
  418. package/hooks/gsd-workflow-guard.js +36 -17
  419. package/hooks/gsd-worktree-path-guard.js +36 -21
  420. package/hooks/gsd-write-guard.js +35 -25
  421. package/hooks/hooks.json +6 -0
  422. package/hooks/lib/cli-exit.js +560 -0
  423. package/hooks/lib/exit-code-registry.js +98 -0
  424. package/hooks/lib/git-cmd.js +210 -1
  425. package/hooks/lib/git-probe.js +84 -0
  426. package/hooks/lib/hook-exit.js +81 -0
  427. package/hooks/lib/injection-patterns.js +36 -6
  428. package/hooks/managed-hooks-registry.cjs +4 -0
  429. package/package.json +14 -9
  430. package/scripts/base64-scan.sh +74 -12
  431. package/scripts/build-hooks.js +12 -0
  432. package/scripts/check-glossary-refs.cjs +77 -15
  433. package/scripts/check-mutation-score-ratchet.cjs +156 -0
  434. package/scripts/ci-check-job-near-cap.cjs +49 -0
  435. package/scripts/ci-pr-mergeability.cjs +262 -0
  436. package/scripts/ci-test-scope.cjs +52 -12
  437. package/scripts/ci-timeout-report.cjs +230 -0
  438. package/scripts/docs-guard-registry.cjs +406 -0
  439. package/scripts/gen-capability-registry.cjs +8 -6
  440. package/scripts/gen-exit-code-docs.cjs +318 -0
  441. package/scripts/gen-exit-code-registry.cjs +891 -0
  442. package/scripts/gen-features.cjs +836 -0
  443. package/scripts/gen-hooks-cli-exit.cjs +239 -0
  444. package/scripts/gen-install-tree-fixtures.cjs +2 -2
  445. package/scripts/gen-loop-host-contract.cjs +189 -4
  446. package/scripts/gen-scripts-cli-exit.cjs +185 -0
  447. package/scripts/gen-state-md-docs.cjs +727 -0
  448. package/scripts/{test-failure-reasons.cjs → gsd-test-gate-reasons.cjs} +6 -0
  449. package/scripts/lib/ci-job-timing.cjs +72 -0
  450. package/scripts/lib/cli-exit.cjs +546 -44
  451. package/scripts/lib/drift-scan.cjs +32 -2
  452. package/scripts/lib/exit-code-registry.cjs +98 -0
  453. package/scripts/lib/ndjson-reporter.cjs +119 -0
  454. package/scripts/lib/shellcheck-fetch.cjs +247 -0
  455. package/scripts/lint-allow-test-rule-refs.allowlist.json +0 -6
  456. package/scripts/lint-allow-test-rule-refs.effective-ceiling.json +1 -1
  457. package/scripts/lint-allow-test-rule-refs.unverified-ceiling.json +1 -1
  458. package/scripts/lint-docs-guard-registration.cjs +495 -0
  459. package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +198 -0
  460. package/scripts/lint-eslint-glob-coverage.allowlist.json +4 -0
  461. package/scripts/{lint-fix-has-regression-test.cjs → lint-fix-has-regression-tests.cjs} +12 -6
  462. package/scripts/lint-health-diagnostic-rule-table.cjs +65 -8
  463. package/scripts/lint-mutation-test-derivation-drift.cjs +86 -0
  464. package/scripts/lint-phase-enumeration-drift.cjs +45 -14
  465. package/scripts/lint-phase-id-drift.cjs +133 -8
  466. package/scripts/lint-planning-prompt-drift.cjs +38 -1
  467. package/scripts/lint-portable-grep.cjs +176 -0
  468. package/scripts/lint-removed-but-needed.cjs +184 -16
  469. package/scripts/lint-response-language-coverage.cjs +524 -0
  470. package/scripts/lint-seam-enforcement.cjs +182 -0
  471. package/scripts/lint-slug-derivation-drift.cjs +921 -0
  472. package/scripts/lint-source-test-name-collision.cjs +241 -0
  473. package/scripts/lint-state-write-path-drift.cjs +337 -432
  474. package/scripts/lint-test-file-count.allowlist.json +124 -4
  475. package/scripts/lint-test-file-count.cjs +25 -3
  476. package/scripts/lint-unreachable-guard-drift.cjs +51 -64
  477. package/scripts/lint-vendored-deps.cjs +208 -35
  478. package/scripts/lint-workflow-shellcheck-baseline.json +1027 -0
  479. package/scripts/lint-workflow-shellcheck.cjs +614 -0
  480. package/scripts/mutation-matrix.cjs +599 -50
  481. package/scripts/npm-audit-baseline.cjs +376 -0
  482. package/scripts/prompt-injection-scan.sh +83 -14
  483. package/scripts/require-issue-link-policy.cjs +16 -1
  484. package/scripts/secret-scan.sh +75 -13
  485. package/scripts/select-docs-guards.cjs +56 -0
  486. package/scripts/sync-runtime-launcher.cjs +22 -3
  487. package/skills/gsd-discuss-phase/SKILL.md +1 -1
  488. package/skills/gsd-execute-phase/SKILL.md +1 -1
  489. package/skills/gsd-import/SKILL.md +1 -1
  490. package/skills/gsd-ns-workflow/SKILL.md +1 -0
  491. package/skills/gsd-phase/SKILL.md +1 -1
  492. package/skills/gsd-quick/SKILL.md +8 -4
  493. package/skills/gsd-quick-batch/SKILL.md +105 -0
  494. package/skills/gsd-surface/SKILL.md +18 -8
  495. package/vscode/package.json +1 -1
  496. package/bin/lib/ui-safety-gate.cjs +0 -109
  497. package/scripts/lint-emitted-drift-ack.cjs +0 -344
  498. package/scripts/state-write-path-drift-baseline.json +0 -19
@@ -0,0 +1,234 @@
1
+ "use strict";
2
+ /**
3
+ * Runtime identity — prove which package's `gsd-tools` actually executed (#3146).
4
+ *
5
+ * The predecessor package `get-shit-done-cc` publishes a colliding `gsd-tools`
6
+ * bin (verified 2026-08-24 against 1.42.3: `bin.gsd-tools -> bin/gsd-sdk.js`),
7
+ * and exposes verbs of the same name with different semantics. #3129 is the
8
+ * worked example: `phases.clear` archives here and **deletes** there, and the
9
+ * output is success-shaped either way, so 43 phase directories went missing
10
+ * with no warning.
11
+ *
12
+ * That collision is now prevented in the resolver rather than detected here:
13
+ * the launcher's PATH branch resolves `gsd_run`, which only this package
14
+ * publishes and which self-locates to its sibling shim, so a foreign binary is
15
+ * unreachable from PATH. The path-based branches — a project-local install, a
16
+ * runtime config directory — have no such structural guarantee: they trust
17
+ * their configured location. #3841 closes that remaining route by making the
18
+ * launcher preamble PROBE the resolved tool once, before any verb runs, and
19
+ * warn when it cannot prove it is this package. This module backs both the
20
+ * `runtime-identity` verb the preamble calls and the vocabulary that gate
21
+ * reports in (`IDENTITY_STATUS`).
22
+ *
23
+ * The rollout is warn-then-fail (the #3146 maintainer ruling): the preamble
24
+ * prints one actionable line and proceeds. It cannot hard-fail yet because
25
+ * `no_identity_verb` does not distinguish a foreign package from an
26
+ * `@opengsd/gsd-core` older than the verb — and during rollout the old-version
27
+ * case is the common one, so a hard failure would stop working installs.
28
+ *
29
+ * Parsing is deliberately STRICT. The whole value of the check is telling "us"
30
+ * from "not us"; a lenient parse that accepts the predecessor's usage text as
31
+ * close-enough would reproduce the exact silent success #3129 already produced.
32
+ * Liberality is spent on visibility instead — distinct reason codes, each
33
+ * naming what actually happened.
34
+ *
35
+ * The classifier and the shell preamble are held in agreement by a
36
+ * cross-surface parity test (#3841): both read stdout only and must reach the
37
+ * same verdict for the same payload, or the preamble's warning and this
38
+ * module's diagnostic would silently diverge on the same input.
39
+ */
40
+ Object.defineProperty(exports, "__esModule", { value: true });
41
+ exports.IDENTITY_RAW_PREFIX = exports.IDENTITY_STATUS = exports.EXPECTED_PACKAGE_NAME = void 0;
42
+ exports.classifyIdentityProbe = classifyIdentityProbe;
43
+ exports.statusForVerdict = statusForVerdict;
44
+ exports.buildIdentityPayload = buildIdentityPayload;
45
+ exports.explainVerdict = explainVerdict;
46
+ exports.cmdRuntimeIdentity = cmdRuntimeIdentity;
47
+ const package_identity_cjs_1 = require("./package-identity.cjs");
48
+ const capability_loader_cjs_1 = require("./capability-loader.cjs");
49
+ /** The package name a legitimate GSD runtime reports. Baked at build time (#498). */
50
+ exports.EXPECTED_PACKAGE_NAME = package_identity_cjs_1.packageName;
51
+ /**
52
+ * The two-valued status the launcher preamble exports as `GSD_IDENTITY_STATUS`
53
+ * (#3841). Frozen so it is a VALUE a test can assert on: CONTRIBUTING forbids
54
+ * proving the gate fired by matching its human-readable warning prose.
55
+ *
56
+ * Two values, not five: the shell has no use for the distinction between
57
+ * `no_identity_verb` and `unparseable` — it either holds proof or it does not.
58
+ * The five-way {@link IdentityReason} stays the diagnostic vocabulary;
59
+ * {@link statusForVerdict} is the only bridge between them.
60
+ */
61
+ exports.IDENTITY_STATUS = Object.freeze({
62
+ OK: 'ok',
63
+ UNVERIFIED: 'unverified',
64
+ });
65
+ /**
66
+ * The exact byte prefix the preamble anchors its `case` pattern to.
67
+ *
68
+ * ANCHORED, never a substring search. `JSON.stringify` emits keys in insertion
69
+ * order and `buildIdentityPayload` inserts `packageName` first, so a genuine
70
+ * `--raw` payload always STARTS with this. An unanchored match would verify the
71
+ * decoy `{"packageName":"get-shit-done-cc","note":"@opengsd/gsd-core"}`, which
72
+ * is exactly the shape a colliding package could publish.
73
+ *
74
+ * The preamble anchors at BOTH ends: its `case` pattern is this prefix, then
75
+ * anything, then a literal `}`, so a truncated payload fails too. That is safe
76
+ * for any future additive field — a JSON object's own closing brace is always
77
+ * the last character, whatever the last value's type. It also pairs the literal
78
+ * `{` for the raw-text brace guards the preamble is inlined into (#3841); the
79
+ * pairing is a consequence of the stronger check, not the reason for it.
80
+ */
81
+ exports.IDENTITY_RAW_PREFIX = `{"packageName":"${exports.EXPECTED_PACKAGE_NAME}"`;
82
+ /** Cap on echoed probe output so a warning can never dump a whole usage screen. */
83
+ const EVIDENCE_MAX_CHARS = 200;
84
+ function excerpt(text) {
85
+ const flat = text.replace(/\s+/g, ' ').trim();
86
+ return flat.length > EVIDENCE_MAX_CHARS ? `${flat.slice(0, EVIDENCE_MAX_CHARS)}…` : flat;
87
+ }
88
+ /**
89
+ * `stdout` as a plain JSON object, or `null` when it is not one.
90
+ *
91
+ * `JSON.parse` accepts `0`, `"str"`, `[]`, `null` and `true`; arrays and `null`
92
+ * are also `object` to `typeof`. All are rejected here so no downstream
93
+ * truthiness check can mistake `[]` for a verified identity.
94
+ */
95
+ function parseIdentityRecord(stdout) {
96
+ let parsed;
97
+ try {
98
+ parsed = JSON.parse(stdout);
99
+ }
100
+ catch {
101
+ return null;
102
+ }
103
+ if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed))
104
+ return null;
105
+ return parsed;
106
+ }
107
+ /**
108
+ * Pure classifier: probe result -> verdict. Total — never throws, for any input.
109
+ *
110
+ * Non-object JSON (`0`, `"str"`, `[]`, `null`, `true`) is `unparseable`, not
111
+ * `ok` and not a crash: `JSON.parse` accepts all of them, so a naive truthiness
112
+ * check would let `[]` through as a verified identity.
113
+ */
114
+ function classifyIdentityProbe(probe, expected = exports.EXPECTED_PACKAGE_NAME) {
115
+ if (probe.spawnFailed || probe.timedOut) {
116
+ return {
117
+ reason: 'probe_failed',
118
+ expected,
119
+ detail: probe.timedOut ? 'identity probe timed out' : 'identity probe could not be spawned',
120
+ };
121
+ }
122
+ // PARSE BEFORE CONSULTING THE EXIT CODE (#3841).
123
+ //
124
+ // The exit-code rule below is right for its stated reason -- the predecessor
125
+ // prints a usage screen and exits 1, and that is "something else answered",
126
+ // not a parse problem -- but it used to run FIRST, so it also caught a tool
127
+ // that had already proved its identity and merely exited non-zero afterwards.
128
+ // The shell preamble this module speaks for reads stdout ONLY (its command
129
+ // substitution discards the status), so the two surfaces disagreed on exactly
130
+ // that input: shell `ok`, classifier `no_identity_verb`. Measured against the
131
+ // real snippet, not inferred. Since this classifier is the engine for the
132
+ // announced hard-fail phase, such an install would have flipped from verified
133
+ // to refused at rollout, in the phase where that stops the run.
134
+ //
135
+ // The predecessor defence is unaffected: a usage screen yields no usable
136
+ // payload, so it still falls through to the exit-code branch below.
137
+ const record = parseIdentityRecord(probe.stdout);
138
+ if (record !== null) {
139
+ const actual = record.packageName;
140
+ if (typeof actual === 'string' && actual.length > 0) {
141
+ // Unknown keys are ignored so a future payload addition cannot fail an
142
+ // older check. The shell anchor is deliberately open in the middle for the
143
+ // same reason, and the parity suite pins both halves of that (case P10).
144
+ const version = typeof record.version === 'string' ? record.version : undefined;
145
+ if (actual !== expected)
146
+ return { reason: 'identity_mismatch', expected, actual, version };
147
+ // ANCHOR PARITY (#3841). The shell preamble cannot parse JSON: it matches
148
+ // a `case` pattern anchored at the START of stdout, so a payload only
149
+ // verifies there when it begins at the first byte and serializes
150
+ // `packageName` first. A purely structural parse would accept
151
+ // `{"note":"x","packageName":"<us>"}` and a leading-whitespace payload
152
+ // that the shell rejects -- a FAIL-OPEN disagreement between the two
153
+ // surfaces.
154
+ //
155
+ // Reproduce those two properties SEMANTICALLY rather than byte-matching
156
+ // IDENTITY_RAW_PREFIX. The prefix describes the `--raw` wire format, but
157
+ // this classifier is also handed the DEFAULT pretty serialization
158
+ // (`runtime-identity` with no `--raw`, which is two-space indented) --
159
+ // byte-matching the compact prefix rejected that, which the remote matrix
160
+ // caught. `startsWith('{')` and a first-key check hold for both.
161
+ const firstKey = Object.keys(record)[0];
162
+ if (!probe.stdout.startsWith('{') || firstKey !== 'packageName') {
163
+ return {
164
+ reason: 'unparseable',
165
+ expected,
166
+ detail: 'payload names this package but is not in the anchored wire shape',
167
+ };
168
+ }
169
+ return { reason: 'ok', expected, actual, version };
170
+ }
171
+ }
172
+ // Nothing was proved. NOW the exit status is the discriminator: a non-zero
173
+ // exit means a binary that does not implement this verb answered.
174
+ if (probe.exitCode !== 0) {
175
+ return {
176
+ reason: 'no_identity_verb',
177
+ expected,
178
+ detail: `exit ${String(probe.exitCode)}: ${excerpt(probe.stdout)}`,
179
+ };
180
+ }
181
+ return {
182
+ reason: 'unparseable',
183
+ expected,
184
+ detail: record === null ? excerpt(probe.stdout) : 'payload has no usable packageName',
185
+ };
186
+ }
187
+ /**
188
+ * Collapse a five-way verdict onto the two-valued shell status.
189
+ *
190
+ * Total by construction: anything that is not `ok` is `unverified`. A future
191
+ * sixth reason code therefore fails CLOSED here rather than silently verifying.
192
+ */
193
+ function statusForVerdict(verdict) {
194
+ return verdict.reason === 'ok' ? exports.IDENTITY_STATUS.OK : exports.IDENTITY_STATUS.UNVERIFIED;
195
+ }
196
+ /**
197
+ * Build this runtime's identity payload.
198
+ *
199
+ * Version is REPORTED but not asserted in the warn phase, so a dev tree
200
+ * reporting readHostVersion()'s fail-closed `0.0.0` still verifies. Carrying it
201
+ * now means the hard-fail phase can gate on compatibility without a payload
202
+ * change (and therefore without a second round of Hyrum's-Law exposure).
203
+ */
204
+ function buildIdentityPayload(deps = {}) {
205
+ const readVersion = deps.readVersion ?? (() => (0, capability_loader_cjs_1.readHostVersion)());
206
+ const readName = deps.readPackageName ?? (() => exports.EXPECTED_PACKAGE_NAME);
207
+ return { packageName: readName(), version: readVersion() };
208
+ }
209
+ /** Render a verdict as the one-line actionable warning the preamble prints. */
210
+ function explainVerdict(verdict, resolvedPath) {
211
+ const head = `WARNING: "${resolvedPath}" did not report a ${verdict.expected} identity.`;
212
+ const why = {
213
+ ok: 'identity verified',
214
+ identity_mismatch: `it reported "${verdict.actual ?? '(unknown)'}" instead`,
215
+ no_identity_verb: 'it does not implement the runtime-identity verb — either a different package, or a gsd-core predating the verb',
216
+ unparseable: 'its response could not be parsed as an identity payload',
217
+ probe_failed: 'the identity probe could not be run',
218
+ };
219
+ const tail = 'A workflow shipped by this package may be running against a different tool. ' +
220
+ 'See docs/how-to/diagnose-a-foreign-gsd-tools.md';
221
+ const evidence = verdict.detail ? ` (${verdict.detail})` : '';
222
+ return `${head} Reason: ${why[verdict.reason]}${evidence}. ${tail}`;
223
+ }
224
+ /**
225
+ * CLI arm for `gsd-tools runtime-identity`.
226
+ *
227
+ * Kept on the CJS fast path for the same reason as `current-timestamp`: it is a
228
+ * pure local read, and the launcher preamble spawns it once per workflow run,
229
+ * so SDK bridge startup would be a per-run tax on every workflow.
230
+ */
231
+ function cmdRuntimeIdentity(raw = false, deps = {}) {
232
+ const payload = buildIdentityPayload(deps);
233
+ process.stdout.write(`${JSON.stringify(payload, null, raw ? 0 : 2)}\n`);
234
+ }
@@ -29,6 +29,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
29
29
  };
30
30
  Object.defineProperty(exports, "__esModule", { value: true });
31
31
  exports.formatGsdSlash = formatGsdSlash;
32
+ exports.readInstallRuntimeMarker = readInstallRuntimeMarker;
33
+ exports._setInstallRuntimeMarkerForTests = _setInstallRuntimeMarkerForTests;
34
+ exports._resetInstallRuntimeMarkerCacheForTests = _resetInstallRuntimeMarkerCacheForTests;
32
35
  exports.resolveExplicitRuntime = resolveExplicitRuntime;
33
36
  exports.resolveRuntime = resolveRuntime;
34
37
  exports.formatGsdSlashFor = formatGsdSlashFor;
@@ -75,6 +78,58 @@ function formatGsdSlash(commandName, runtime) {
75
78
  }
76
79
  return `/gsd-${token}${tail}`;
77
80
  }
81
+ // ─── #3897 rung 2: the per-install `.gsd-runtime` marker, promoted from ──────
82
+ // model-resolver.cts (#2297) to this module — the canonical owner of runtime
83
+ // identity (resolveRuntime, formatGsdSlash). `bin/install.js` writes
84
+ // `<install>/gsd-core/.gsd-runtime` beside VERSION for every runtime install
85
+ // (#2297). This module compiles to `gsd-core/bin/lib/runtime-slash.cjs`
86
+ // (ADR-457) — the SAME directory `model-resolver.cjs` compiles to — so the
87
+ // `__dirname`-relative path below is unchanged from the promoted original.
88
+ //
89
+ // Cache + test-seam shape, byte-for-behaviour preserved from
90
+ // model-resolver.cts:65-77 (N5): module-level `let`, `try/catch -> null`,
91
+ // `_setInstallRuntimeMarkerForTests` / `_resetInstallRuntimeMarkerCacheForTests`.
92
+ // `model-resolver.cts` now imports this implementation instead of holding its
93
+ // own copy; the two `hooks/*.js` marker readers delegate here too (through
94
+ // `ensureRuntimeBuild()`, per `scripts/lint-hooks-runtime-build-seam.cjs`).
95
+ let _installMarkerCache;
96
+ /**
97
+ * Read the per-install runtime marker, cached for the process lifetime.
98
+ * Never throws (N4) — an absent/unreadable marker (dev/source tree, an
99
+ * install predating #2297, EACCES, hostile content, ...) is "no signal",
100
+ * never a resolution failure; a throw here would break a session in
101
+ * `gsd-agent-isolation-guard.js`, which is worse than resolving the wrong
102
+ * runtime.
103
+ *
104
+ * The raw marker string is returned as-is (trimmed) — callers MUST route it
105
+ * through `resolveRuntimeNameFromCandidates` before trusting it (N1); this
106
+ * function itself does not normalize so its cached value matches exactly
107
+ * what #2297's tests already assert about the underlying file read.
108
+ */
109
+ function readInstallRuntimeMarker() {
110
+ if (_installMarkerCache !== undefined)
111
+ return _installMarkerCache;
112
+ try {
113
+ const markerPath = node_path_1.default.join(__dirname, '..', '..', '.gsd-runtime');
114
+ const raw = node_fs_1.default.readFileSync(markerPath, 'utf8').trim();
115
+ _installMarkerCache = raw || null;
116
+ }
117
+ catch {
118
+ // No marker: dev/source tree, or an install predating #2297. Fall through
119
+ // to the 'claude' default (keeps tier aliases — never worse than the bug).
120
+ _installMarkerCache = null;
121
+ }
122
+ return _installMarkerCache;
123
+ }
124
+ // Test seams for the install-marker rung (the dev/source tree has no marker,
125
+ // so the file read always bottoms out at 'claude' — these let tests exercise
126
+ // the third precedence rung and reset the module-level cache between cases).
127
+ function _setInstallRuntimeMarkerForTests(value) {
128
+ _installMarkerCache = value;
129
+ }
130
+ function _resetInstallRuntimeMarkerCacheForTests() {
131
+ _installMarkerCache = undefined;
132
+ }
78
133
  /**
79
134
  * Resolve the explicit runtime for a project directory, from the two
80
135
  * explicit sources only — no default is applied.
@@ -121,17 +176,32 @@ function resolveExplicitRuntime(projectDir, env = process.env) {
121
176
  /**
122
177
  * Resolve the effective runtime for a project directory.
123
178
  *
124
- * process.env.GSD_RUNTIME > config.runtime > 'claude'
179
+ * process.env.GSD_RUNTIME > config.runtime > install marker > 'claude'
125
180
  *
126
181
  * Mirrors the precedence already used by profile-output.cjs and the rest of
127
182
  * the runtime resolution chain. Returns a lowercased string so downstream
128
183
  * comparisons can be case-blind.
129
184
  *
185
+ * #3897 rung 2: the per-install `.gsd-runtime` marker is the THIRD rung,
186
+ * between the two explicit sources and the 'claude' default — matching
187
+ * model-resolver.cts's own description of it as "the third precedence rung"
188
+ * before this reader was promoted here. The marker's raw contents are never
189
+ * trusted verbatim (N1): they are routed through the SAME
190
+ * `resolveRuntimeNameFromCandidates` normalization the env rung uses, so an
191
+ * unknown or hostile marker value degrades exactly like an unknown
192
+ * GSD_RUNTIME value would.
193
+ *
130
194
  * @param projectDir - path to the project directory, or null/undefined
131
195
  * @returns the resolved runtime name
132
196
  */
133
197
  function resolveRuntime(projectDir) {
134
- return resolveExplicitRuntime(projectDir) ?? 'claude';
198
+ const explicit = resolveExplicitRuntime(projectDir);
199
+ if (explicit)
200
+ return explicit;
201
+ const markerRuntime = (0, runtime_name_policy_cjs_1.resolveRuntimeNameFromCandidates)(readInstallRuntimeMarker());
202
+ if (markerRuntime)
203
+ return markerRuntime;
204
+ return 'claude';
135
205
  }
136
206
  /**
137
207
  * Convenience: format using the runtime resolved from a project directory.
@@ -19,6 +19,7 @@ exports.PATH_ACTION_REASON = void 0;
19
19
  exports.toPosixPath = toPosixPath;
20
20
  exports.toNativePath = toNativePath;
21
21
  exports.posixNormalize = posixNormalize;
22
+ exports.toComparablePathKey = toComparablePathKey;
22
23
  exports.hookCommandNeedsPowerShellCallOperator = hookCommandNeedsPowerShellCallOperator;
23
24
  exports.formatHookCommandForRuntime = formatHookCommandForRuntime;
24
25
  exports.shellHookOmitsBashRunner = shellHookOmitsBashRunner;
@@ -43,6 +44,7 @@ exports.projectPersistentPathExportActions = projectPersistentPathExportActions;
43
44
  exports.isSpawnTimeout = isSpawnTimeout;
44
45
  exports.execGit = execGit;
45
46
  exports.execNpm = execNpm;
47
+ exports.envGet = envGet;
46
48
  exports.resolveExecutableBinary = resolveExecutableBinary;
47
49
  exports.projectSpawnInvocation = projectSpawnInvocation;
48
50
  exports.execTool = execTool;
@@ -50,6 +52,7 @@ exports.resolveGsdToolsPath = resolveGsdToolsPath;
50
52
  exports.dispatchGsdCommand = dispatchGsdCommand;
51
53
  exports.probeTty = probeTty;
52
54
  exports.normalizeContent = normalizeContent;
55
+ exports.contentChangedAfterNormalize = contentChangedAfterNormalize;
53
56
  exports.retryRenameSync = retryRenameSync;
54
57
  exports.platformWriteSync = platformWriteSync;
55
58
  exports.platformReadSync = platformReadSync;
@@ -95,6 +98,20 @@ function toNativePath(p) {
95
98
  function posixNormalize(p) {
96
99
  return p.replace(/\\/g, '/');
97
100
  }
101
+ /**
102
+ * #3663 — comparison key for a path that must equal another path regardless
103
+ * of spelling: separator form (forward slashes via posixNormalize), relative
104
+ * segments and trailing separators (path.resolve + strip), and — ONLY on
105
+ * win32's case-insensitive filesystem — letter casing. POSIX stays
106
+ * case-sensitive: differently-cased paths are genuinely different
107
+ * directories there. This module owns the platform-conditional fold so the
108
+ * policy lives at the seam instead of accreting per-call-site copies (the
109
+ * class init.cts's normalizeForCompare/toComparableRaw predate).
110
+ */
111
+ function toComparablePathKey(p, platform = process.platform) {
112
+ const normalized = posixNormalize(node_path_1.default.resolve(p)).replace(/\/+$/g, '');
113
+ return platform === 'win32' ? normalized.toLowerCase() : normalized;
114
+ }
98
115
  /**
99
116
  * Return true when a managed hook command must be prefixed with PowerShell's
100
117
  * call operator so a quoted executable token is invokable by the target
@@ -218,6 +235,15 @@ const MANAGED_HOOK_BASENAMES_BY_SURFACE = {
218
235
  'gsd-read-injection-scanner.js',
219
236
  'gsd-update-banner.js',
220
237
  'gsd-workflow-guard.js',
238
+ // #3662: the three PreToolUse guards are GSD-managed JS hooks (registered
239
+ // only-if-absent by applySettingsJsonHooks) but sat outside this set, so
240
+ // the #2979 runner rewriter never reached them — the issue's reported
241
+ // mixed state lived on exactly these hooks.
242
+ 'gsd-write-guard.js',
243
+ 'gsd-agent-isolation-guard.js',
244
+ 'gsd-worktree-path-guard.js',
245
+ // #4221: secret-file read guard (Read|Grep|Bash).
246
+ 'gsd-secret-read-guard.js',
221
247
  ]),
222
248
  'codex-toml': new Set([
223
249
  'gsd-check-update.js',
@@ -237,6 +263,16 @@ const MANAGED_HOOK_COMMAND_BASENAMES_BY_SURFACE = {
237
263
  'gsd-session-state.sh',
238
264
  'gsd-validate-commit.sh',
239
265
  'gsd-phase-boundary.sh',
266
+ // #3662: same three guards as MANAGED_HOOK_BASENAMES_BY_SURFACE above —
267
+ // their absence here meant isManagedHookCommand never recognized them, so
268
+ // the settings.json→settings.local.json migration filter (and uninstall
269
+ // cleanup) skipped GSD's own guard entries. Folded-in fix, surfaced by the
270
+ // #3662 issue thread.
271
+ 'gsd-write-guard.js',
272
+ 'gsd-agent-isolation-guard.js',
273
+ 'gsd-worktree-path-guard.js',
274
+ // #4221: secret-file read guard (Read|Grep|Bash).
275
+ 'gsd-secret-read-guard.js',
240
276
  ]),
241
277
  'codex-toml': new Set([
242
278
  'gsd-check-update.js',
@@ -330,9 +366,15 @@ const ANCHORED_HOOK_SCRIPT_TOKEN = /^"\$[A-Za-z_][A-Za-z0-9_]*"\//;
330
366
  * Non-Windows keeps the original script token shape when provided (single
331
367
  * quote / bareword / quoted), while Windows normalizes to double-quoted
332
368
  * forward-slash path tokens for stable cross-shell behavior.
369
+ *
370
+ * #3662: `runnerToken` is the runner the current install would emit — since
371
+ * #3662 that is the runtime-resolving chain token (see
372
+ * buildNodeRunnerChainToken), not a bare absolute path, so re-projected
373
+ * entries converge onto a runner that resolves in every environment sharing
374
+ * the config root.
333
375
  */
334
- function projectLegacySettingsHookCommand({ absoluteRunner, scriptPath, scriptToken, runtime = 'generic', platform = process.platform, }) {
335
- if (!absoluteRunner || !scriptPath)
376
+ function projectLegacySettingsHookCommand({ runnerToken, scriptPath, scriptToken, runtime = 'generic', platform = process.platform, }) {
377
+ if (!runnerToken || !scriptPath)
336
378
  return null;
337
379
  const normalizedScriptPath = platform === 'win32' ? posixNormalize(scriptPath) : scriptPath;
338
380
  // #1693: a script path already carrying a `"$CLAUDE_PROJECT_DIR"`-anchored
@@ -351,7 +393,7 @@ function projectLegacySettingsHookCommand({ absoluteRunner, scriptPath, scriptTo
351
393
  : JSON.stringify(normalizedScriptPath))
352
394
  : (scriptToken || JSON.stringify(normalizedScriptPath));
353
395
  return projectShellCommandText({
354
- runnerToken: absoluteRunner,
396
+ runnerToken,
355
397
  argTokens: [commandScriptToken],
356
398
  runtime,
357
399
  platform,
@@ -628,8 +670,12 @@ function _isFile(candidate, requireExecutable = false, platform = process.platfo
628
670
  *
629
671
  * Exact match wins when present, so a caller who sets the canonical name pays
630
672
  * no scan.
673
+ *
674
+ * Exported so callers outside this seam can route an exact-case-sensitive env
675
+ * read through it instead of accessing a non-`process.env` object directly
676
+ * (see `local/no-exact-case-env-access`).
631
677
  */
632
- function _envGet(env, name) {
678
+ function envGet(env, name) {
633
679
  const exact = env[name];
634
680
  if (exact !== undefined)
635
681
  return exact;
@@ -728,7 +774,7 @@ function resolveExecutableBinary(name, opts = {}) {
728
774
  return _isFile(name, requireExecutable, platform) ? name : null;
729
775
  }
730
776
  const env = opts.env ?? process.env;
731
- const rawPath = opts.pathOverride !== undefined ? opts.pathOverride : (_envGet(env, 'PATH') || '');
777
+ const rawPath = opts.pathOverride !== undefined ? opts.pathOverride : (envGet(env, 'PATH') || '');
732
778
  const pathSegments = String(rawPath).split(node_path_1.default.delimiter).filter(Boolean);
733
779
  const segments = [...(opts.prependPaths ?? []), ...pathSegments];
734
780
  if (platform !== 'win32') {
@@ -739,7 +785,7 @@ function resolveExecutableBinary(name, opts = {}) {
739
785
  }
740
786
  return null;
741
787
  }
742
- const exts = String(_envGet(env, 'PATHEXT') || DEFAULT_PATHEXT).split(';').filter(Boolean);
788
+ const exts = String(envGet(env, 'PATHEXT') || DEFAULT_PATHEXT).split(';').filter(Boolean);
743
789
  // A name already ending in a PATHEXT-listed extension is an address, not a stem:
744
790
  // probing `foo.exe` as `foo.exe.EXE` would miss the file sitting right there.
745
791
  // Compared case-insensitively because PATHEXT casing is not guaranteed.
@@ -840,7 +886,7 @@ function projectSpawnInvocation(command, args = [], opts = {}) {
840
886
  return resolved ? { command: resolved, args, resolved } : { command, args, resolved: null };
841
887
  }
842
888
  return {
843
- command: String(_envGet(env, 'ComSpec') || 'cmd.exe'),
889
+ command: String(envGet(env, 'ComSpec') || 'cmd.exe'),
844
890
  args: ['/d', '/s', '/c', _buildVerbatimCmdLine(target, args)],
845
891
  resolved,
846
892
  windowsVerbatimArguments: true,
@@ -1022,7 +1068,11 @@ function _normalizeMd(content) {
1022
1068
  if (i === 0 || !insideFence[i - 1])
1023
1069
  result.push('');
1024
1070
  }
1025
- if (/^(\s*[-*+]\s|\s*\d+\.\s)/.test(line) && i > 0 && prevTrimmed !== '' && !/^(\s*[-*+]\s|\s*\d+\.\s)/.test(prev) && prevTrimmed !== '---')
1071
+ // #3854: the `!/^\s/.test(prev)` guard mirrors the after-a-bullet rule below —
1072
+ // an indented non-bullet line is a CONTINUATION of the previous list item, not a
1073
+ // preceding paragraph, so no separating blank may be injected before this bullet
1074
+ // (that injection converted every tight multi-line list to a loose one on write).
1075
+ if (/^(\s*[-*+]\s|\s*\d+\.\s)/.test(line) && i > 0 && prevTrimmed !== '' && !/^(\s*[-*+]\s|\s*\d+\.\s)/.test(prev) && !/^\s/.test(prev) && prevTrimmed !== '---')
1026
1076
  result.push('');
1027
1077
  result.push(line);
1028
1078
  if (/^#{1,6}\s/.test(trimmed) && i < lines.length - 1 && (lines[i + 1] ?? '').trimEnd() !== '')
@@ -1052,6 +1102,23 @@ function normalizeContent(filePath, content, opts = {}) {
1052
1102
  }
1053
1103
  return { content: normalized, encoding };
1054
1104
  }
1105
+ /**
1106
+ * True iff persisting `after` via `platformWriteSync` would land different
1107
+ * on-disk bytes than `before` already has (or would have, normalized the
1108
+ * same way). `platformWriteSync` runs Markdown normalization (CRLF strip,
1109
+ * blank-line-run collapse, single trailing newline) before writing, so a
1110
+ * caller comparing raw pre-normalize strings (`after !== before`) can report
1111
+ * `true` even when the persisted bytes are byte-identical — e.g. a
1112
+ * transform that regenerates a section fresh on every call, including a
1113
+ * genuine no-op re-run, in a different-but-equivalent raw shape than the
1114
+ * already-normalized on-disk original (#3685 / #3691). Any "did this write
1115
+ * change the file?" flag MUST go through this seam (or an equivalent
1116
+ * post-write re-read of the actual on-disk bytes) instead of a raw `!==` —
1117
+ * do not simplify this back to a direct string comparison.
1118
+ */
1119
+ function contentChangedAfterNormalize(filePath, before, after) {
1120
+ return normalizeContent(filePath, after).content !== normalizeContent(filePath, before).content;
1121
+ }
1055
1122
  // Rename errnos that are transient on Windows: a concurrent reader (or an AV
1056
1123
  // scanner / indexer) holding the target open makes renameSync fail briefly.
1057
1124
  // Same idiom as capability-ledger.cts / capability-consent.cts.
@@ -43,6 +43,10 @@ const node_fs_1 = __importDefault(require("node:fs"));
43
43
  const node_path_1 = __importDefault(require("node:path"));
44
44
  const node_child_process_1 = require("node:child_process");
45
45
  const markdown_sectionizer_cjs_1 = require("./markdown-sectionizer.cjs");
46
+ // #3696: the calendar-validity predicate moved to the STATE.md document module
47
+ // so `state validate` can assert the same `last_activity` invariant this reader
48
+ // already enforces (ADR-227). Two copies would let the two surfaces disagree
49
+ // about whether a STATE.md is usable — which is the defect #3696 reports.
46
50
  // eslint-disable-next-line @typescript-eslint/no-require-imports
47
51
  const ioMod = require("./io.cjs");
48
52
  const { output } = ioMod;
@@ -60,7 +64,7 @@ const stateDocument = require("./state-document.cjs");
60
64
  const { stateFieldValue } = stateDocument;
61
65
  // eslint-disable-next-line @typescript-eslint/no-require-imports
62
66
  const phaseId = require("./phase-id.cjs");
63
- const { comparePhaseNum, extractPhaseToken, matchPhaseDirs, normalizePhaseName, stripProjectCodePrefix } = phaseId;
67
+ const { comparePhaseNum, extractPhaseToken, matchPhaseDirs, normalizePhaseName, parsePhaseFromProse, stripProjectCodePrefix } = phaseId;
64
68
  // eslint-disable-next-line @typescript-eslint/no-require-imports
65
69
  const stateMod = require("./state.cjs");
66
70
  const { readStateHeadFreshness } = stateMod;
@@ -160,26 +164,6 @@ const ISO_LEADING_RE = /^(\d{4})-(\d{2})-(\d{2})((?:[T ]\d{2}:\d{2}(?::\d{2}(?:\
160
164
  * failed open on every letter-led description and re-opened #2570.
161
165
  */
162
166
  const ZONE_DESIGNATOR_RE = /^\s*[A-Z]{2,5}(?![A-Za-z])/;
163
- /**
164
- * True only when y/m/d name a date that actually exists on the calendar.
165
- *
166
- * `Date.parse` validates shape but not value: it rolls an out-of-range day
167
- * FORWARD rather than rejecting it (`2026-02-30` -> `2026-03-02`,
168
- * `2026-04-31` -> `2026-05-01`). Shape-only validation would therefore
169
- * propagate a different, wrong instant instead of failing safe — precisely
170
- * what ADR-227 ("validate shape AND value; on failure of either layer coerce
171
- * to the contract's safe default, never propagate") exists to prevent. A
172
- * round-trip through Date.UTC detects the rollover: any component the
173
- * constructor normalised comes back changed.
174
- */
175
- function isRealCalendarDate(year, month, day) {
176
- if (month < 1 || month > 12 || day < 1 || day > 31)
177
- return false;
178
- const probe = new Date(Date.UTC(year, month - 1, day));
179
- return (probe.getUTCFullYear() === year &&
180
- probe.getUTCMonth() === month - 1 &&
181
- probe.getUTCDate() === day);
182
- }
183
167
  function parseActivityTimestamp(raw) {
184
168
  if (!raw)
185
169
  return null;
@@ -190,7 +174,7 @@ function parseActivityTimestamp(raw) {
190
174
  // Reject an impossible calendar date outright rather than letting
191
175
  // Date.parse substitute a rolled-forward one. null = "no activity signal",
192
176
  // the safe default staleActivity already fails open on.
193
- if (!isRealCalendarDate(Number(year), Number(month), Number(day)))
177
+ if (!stateDocument.isRealCalendarDate(Number(year), Number(month), Number(day)))
194
178
  return null;
195
179
  // The date is real, so stay as liberal as before (Postel): a whole-string
196
180
  // parse still wins when the engine can make sense of the value. Reading the
@@ -249,10 +233,7 @@ function readGitSignals(cwd) {
249
233
  }
250
234
  /** Leading numeric phase token from a STATE.md scalar or body `Phase:` value. */
251
235
  function phaseTokenFromState(raw) {
252
- if (!raw?.trim())
253
- return null;
254
- const match = raw.trim().match(/^(\d+(?:[A-Z])?(?:\.\d+)*)/i);
255
- return match ? match[1] : null;
236
+ return parsePhaseFromProse(raw).phase;
256
237
  }
257
238
  /**
258
239
  * Detect whether the current phase's verify report indicates failure. The
@@ -445,7 +426,7 @@ function detectSignals(cwd, now = Date.now) {
445
426
  const stateHeadRaw = stateFieldValue(fm, body, 'state_head', 'State Head').value;
446
427
  const freshness = readStateHeadFreshness(cwd, stateHeadRaw);
447
428
  return {
448
- current_phase: parseIntOrNull(currentPhaseRaw),
429
+ current_phase: phaseTokenFromState(currentPhaseRaw),
449
430
  total_phases: parseIntOrNull(totalPhasesRaw),
450
431
  status: (statusRaw || '').toLowerCase(),
451
432
  progress: parseIntOrNull(progressRaw),
@@ -488,11 +469,12 @@ function isComplete(s) {
488
469
  return false;
489
470
  }
490
471
  else {
491
- // Legacy path: STATE.md comparison. Still subject to the two-scale bug,
492
- // but only fires when ROADMAP.md is absent or has no Progress table.
472
+ // Legacy path: STATE.md comparison. It only fires when ROADMAP.md is
473
+ // absent or has no Progress table, and uses canonical phase-id ordering
474
+ // so dotted phase ids are never coerced to JavaScript numbers.
493
475
  if (s.total_phases === null || s.current_phase === null)
494
476
  return false;
495
- if (s.current_phase < s.total_phases)
477
+ if (comparePhaseNum(s.current_phase, s.total_phases) < 0)
496
478
  return false;
497
479
  }
498
480
  // Status regex: require milestone-level completion language. The pre-fix
@@ -542,7 +524,13 @@ function classify(s) {
542
524
  return 'planning';
543
525
  if (/\bexecut(e|ing)|active|in.progress|building\b/i.test(s.status))
544
526
  return 'executing';
545
- if (/\bverify|review|needs.review|pending.review\b/i.test(s.status))
527
+ // #3864: `verif` (not `verify`) — the same stem state-document's
528
+ // normalizeStateStatus matches. "verified" and "verification" contain no
529
+ // "verify" substring, so the exact word left them falling through to
530
+ // `unknown` (or idle-stranded on a clean unpushed tree — differently
531
+ // wrong). verify_failed is tested above, so a failed verification still
532
+ // wins over this branch.
533
+ if (/\bverif|review|needs.review|pending.review\b/i.test(s.status))
546
534
  return 'verify-pending';
547
535
  if (isIdleStranded(s))
548
536
  return 'idle-stranded';
@@ -26,6 +26,9 @@ exports.SECTION_HEADERS = void 0;
26
26
  exports.countSectionDataRows = countSectionDataRows;
27
27
  exports.specSectionStatus = specSectionStatus;
28
28
  const node_fs_1 = __importDefault(require("node:fs"));
29
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
30
+ const cliExitModule = require("./cli-exit.cjs");
31
+ const { ExitError, runMain } = cliExitModule;
29
32
  /**
30
33
  * Canonical SPEC section header matchers — the SINGLE source of truth for the headings
31
34
  * `templates/spec.md` renders. Suffix-tolerant by design: `prohibitions` matches BOTH
@@ -101,11 +104,13 @@ const VALID_KEYS = ['edges', 'prohibitions'];
101
104
  // 2 only on a usage error (missing args / bad key). `require.main === module` so it runs only when
102
105
  // the compiled `.cjs` is executed directly, never when imported by tests.
103
106
  if (require.main === module) {
104
- const specFile = process.argv[2];
105
- const key = process.argv[3];
106
- if (!specFile || !key || !VALID_KEYS.includes(key)) {
107
- process.stderr.write('usage: spec-section.cjs <specFile> <edges|prohibitions>\n');
108
- process.exit(2);
109
- }
110
- process.stdout.write(JSON.stringify(specSectionStatus(specFile, key)) + '\n');
107
+ runMain(() => {
108
+ const specFile = process.argv[2];
109
+ const key = process.argv[3];
110
+ if (!specFile || !key || !VALID_KEYS.includes(key)) {
111
+ process.stderr.write('usage: spec-section.cjs <specFile> <edges|prohibitions>\n');
112
+ throw new ExitError(2);
113
+ }
114
+ process.stdout.write(JSON.stringify(specSectionStatus(specFile, key)) + '\n');
115
+ });
111
116
  }