@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
@@ -45,7 +45,7 @@ const configLoaderMod = require("./config-loader.cjs");
45
45
  const { loadConfig } = configLoaderMod;
46
46
  // eslint-disable-next-line @typescript-eslint/no-require-imports
47
47
  const installProfilesMod = require("./install-profiles.cjs");
48
- const { readActiveProfile, loadSkillsManifest, resolveProfile, parseRequires, parseCallsAgents } = installProfilesMod;
48
+ const { readActiveProfile, loadSkillsManifest, resolveProfile, parseRequires, parseCallsAgents, workflowAgentRefs } = installProfilesMod;
49
49
  // eslint-disable-next-line @typescript-eslint/no-require-imports
50
50
  const surfaceMod = require("./surface.cjs");
51
51
  const { resolveSurface } = surfaceMod;
@@ -299,7 +299,17 @@ function _loadInstalledSkillsManifest(configDir) {
299
299
  * no gsd-*.md files (so _resolveManifest can use size>0 as the "flat layout
300
300
  * present" signal and fall through to the installed-skills branch otherwise).
301
301
  */
302
- function _loadFlatCommandsGsdManifest(commandsParentDir) {
302
+ function _loadFlatCommandsGsdManifest(commandsParentDir, workflowsDir) {
303
+ // #3798: derive agents from the command body PLUS the workflow files it
304
+ // references, exactly like loadSkillsManifest does for the nested layout —
305
+ // the flat (#1858) layout retained the original defect otherwise, and the
306
+ // parity test in tests/capability-state.test.cjs asserts the two loaders
307
+ // produce identical _calls_agents_* sets. Default: <pkg>/gsd-core/workflows
308
+ // one level above the commands dir — the shape of both the repo checkout
309
+ // (<repo>/commands/) and a flat runtime install (the runtime config dir's
310
+ // commands/ with the package's gsd-core/ beside it).
311
+ const flatWorkflowsDir = workflowsDir
312
+ || node_path_1.default.resolve(commandsParentDir, '..', 'gsd-core', 'workflows');
303
313
  const manifest = new Map();
304
314
  let entries;
305
315
  try {
@@ -327,7 +337,12 @@ function _loadFlatCommandsGsdManifest(commandsParentDir) {
327
337
  try {
328
338
  const content = node_fs_1.default.readFileSync(node_path_1.default.join(commandsParentDir, entry.name), 'utf8');
329
339
  manifest.set(stem, parseRequires(content));
330
- manifest.set(`_calls_agents_${stem}`, parseCallsAgents(content));
340
+ manifest.set(`_calls_agents_${stem}`, [
341
+ ...new Set([
342
+ ...parseCallsAgents(content),
343
+ ...workflowAgentRefs(content, flatWorkflowsDir),
344
+ ]),
345
+ ]);
331
346
  }
332
347
  catch {
333
348
  manifest.set(stem, []);
@@ -396,6 +396,11 @@ function validateCapability(cap, folderId) {
396
396
  errors.push(...validateRuntimeBody(cap));
397
397
  // A host that is ALSO a reviewer keeps exactly one manifest (ADR-2782 D1).
398
398
  errors.push(...validateReviewerBody(cap));
399
+ // ADR-3646: a runtime capability installs a host CLI, it does not resolve
400
+ // task content — taskContentResolver is feature-only.
401
+ if (cap.taskContentResolver !== undefined) {
402
+ errors.push('role:runtime capability must not have a "taskContentResolver" body (feature-only field)');
403
+ }
399
404
  } else if (cap.role === 'reviewer') {
400
405
  // ADR-2782 D3 — a lane that is not an install target. No runtime body, no
401
406
  // install surface, no runtimeCompat (it surfaces through no host runtime).
@@ -718,6 +723,118 @@ function validateFeatureBody(cap) {
718
723
  }
719
724
  }
720
725
 
726
+ // ADR-3646: optional per-task external-tracker content-resolution seam.
727
+ errors.push(...validateTaskContentResolver(cap));
728
+
729
+ return errors;
730
+ }
731
+
732
+ /**
733
+ * ADR-3646 — validate an OPTIONAL `taskContentResolver` body on a `role:
734
+ * "feature"` capability. Absence is never an error (most manifests won't
735
+ * have one); presence is strictly validated.
736
+ *
737
+ * Wrapped in try/catch to degrade any unexpected throw (a hostile Proxy, a
738
+ * throwing getter, etc.) to a single validation error rather than crashing
739
+ * every consumer of loadRegistry, per the #1461 OVL-1 discipline that
740
+ * `validateReviewerBody` follows.
741
+ *
742
+ * @param {object} cap The parsed capability manifest.
743
+ * @returns {string[]} Array of error strings; empty = valid or absent.
744
+ */
745
+ function validateTaskContentResolver(cap) {
746
+ try {
747
+ return validateTaskContentResolverFields(cap);
748
+ } catch (err) {
749
+ return ['capability taskContentResolver body could not be validated: ' + safeErrorMessage(err)];
750
+ }
751
+ }
752
+
753
+ /**
754
+ * Upper bound for `taskContentResolver.invoke.timeoutMs`, specific to this
755
+ * field only. `isPositiveIntegerMs()` has no ceiling and stays that way — it
756
+ * is shared with the reviewer lane's `timeoutFloorMs` and probe `timeoutMs`,
757
+ * which may legitimately need a longer or unbounded value. Without a ceiling
758
+ * here, a manifest could declare `Number.MAX_SAFE_INTEGER` and let
759
+ * `resolve-content` hang near-indefinitely on a stuck/malicious resolver,
760
+ * defeating the feature's "bounded subprocess" design intent.
761
+ */
762
+ const TASK_CONTENT_RESOLVER_TIMEOUT_CEILING_MS = 120000;
763
+
764
+ function validateTaskContentResolverFields(cap) {
765
+ const errors = [];
766
+ if (typeof cap !== 'object' || cap === null || Array.isArray(cap)) return errors;
767
+
768
+ const tcr = cap.taskContentResolver;
769
+ if (tcr === undefined) return errors; // optional — never an error to omit
770
+
771
+ const ctx = 'capability "' + (typeof cap.id === 'string' ? cap.id : '(unknown)') + '"';
772
+
773
+ if (typeof tcr !== 'object' || tcr === null || Array.isArray(tcr)) {
774
+ const got = tcr === null ? 'null' : Array.isArray(tcr) ? 'array' : typeof tcr;
775
+ errors.push(
776
+ ctx + ' taskContentResolver must be an object (got: ' + got + '). ' +
777
+ 'Omit the key entirely to declare no resolver — an explicit null is not an omission.',
778
+ );
779
+ return errors; // cannot validate fields of a non-object
780
+ }
781
+
782
+ // ── trackerPrefix — same grammar as a capability id ─────────────────────
783
+ if (typeof tcr.trackerPrefix !== 'string' || tcr.trackerPrefix.length === 0 || !KEBAB_RE.test(tcr.trackerPrefix)) {
784
+ errors.push(
785
+ ctx + ' taskContentResolver.trackerPrefix must be a non-empty kebab-case string matching ' +
786
+ String(KEBAB_RE) + ' (got: ' + describeValue(tcr.trackerPrefix) + ')',
787
+ );
788
+ }
789
+
790
+ // ── invoke ────────────────────────────────────────────────────────────
791
+ const inv = tcr.invoke;
792
+ if (typeof inv !== 'object' || inv === null || Array.isArray(inv)) {
793
+ const got = inv === null ? 'null' : Array.isArray(inv) ? 'array' : typeof inv;
794
+ errors.push(ctx + ' taskContentResolver.invoke must be an object (got: ' + got + ')');
795
+ return errors; // cannot validate sub-fields of a non-object
796
+ }
797
+
798
+ if (typeof inv.binary !== 'string' || inv.binary.length === 0) {
799
+ errors.push(ctx + ' taskContentResolver.invoke.binary must be a non-empty string');
800
+ }
801
+
802
+ if (!Array.isArray(inv.args)) {
803
+ errors.push(ctx + ' taskContentResolver.invoke.args must be an array of strings');
804
+ } else {
805
+ let hasPlaceholder = false;
806
+ for (const a of inv.args) {
807
+ if (typeof a !== 'string') {
808
+ errors.push(ctx + ' taskContentResolver.invoke.args entries must be strings (got: ' + describeValue(a) + ')');
809
+ } else if (a === '{{id}}') {
810
+ hasPlaceholder = true;
811
+ }
812
+ }
813
+ if (!hasPlaceholder) {
814
+ errors.push(
815
+ ctx + ' taskContentResolver.invoke.args must contain a "{{id}}" placeholder — ' +
816
+ 'without it the resolved tracker id could never reach the resolver subprocess',
817
+ );
818
+ }
819
+ }
820
+
821
+ if (!isPositiveIntegerMs(inv.timeoutMs)) {
822
+ errors.push(
823
+ ctx + ' taskContentResolver.invoke.timeoutMs must be a positive integer of milliseconds — ' +
824
+ 'an unbounded resolver call could hang task execution indefinitely ' +
825
+ '(got: ' + describeValue(inv.timeoutMs) + ')',
826
+ );
827
+ } else if (inv.timeoutMs > TASK_CONTENT_RESOLVER_TIMEOUT_CEILING_MS) {
828
+ // Ceiling specific to this field — `isPositiveIntegerMs()` itself stays
829
+ // unbounded because it is shared with the reviewer lane's
830
+ // `timeoutFloorMs`/probe `timeoutMs`, which have no such ceiling.
831
+ errors.push(
832
+ ctx + ' taskContentResolver.invoke.timeoutMs must not exceed ' +
833
+ TASK_CONTENT_RESOLVER_TIMEOUT_CEILING_MS + 'ms — an unbounded-in-practice value defeats the ' +
834
+ '"bounded subprocess" design intent (got: ' + inv.timeoutMs + ')',
835
+ );
836
+ }
837
+
721
838
  return errors;
722
839
  }
723
840
 
@@ -943,7 +1060,7 @@ const HTTP_ONLY_INVOKE_FIELDS = ['hostConfigKey', 'defaultHost', 'path', 'model
943
1060
 
944
1061
  // Feature-only fields are as forbidden on a lane-only capability as on a runtime
945
1062
  // one; a `role: "reviewer"` capability owns no artefacts and wires no loop point.
946
- const FEATURE_FIELDS_FORBIDDEN_ON_REVIEWER = ['skills', 'agents', 'steps', 'contributions', 'gates', 'hooks', 'activationKey'];
1063
+ const FEATURE_FIELDS_FORBIDDEN_ON_REVIEWER = ['skills', 'agents', 'steps', 'contributions', 'gates', 'hooks', 'activationKey', 'taskContentResolver'];
947
1064
 
948
1065
  // GATE A: installSurface → allowed hooksSurface values (DEFECT.GENERATIVE-FIX: parity invariant)
949
1066
  // Derived from the actual pairings in the 16 real runtime descriptors.
@@ -1588,6 +1705,16 @@ function validateRuntimeBody(cap) {
1588
1705
  'runtime.orchestratorExec.promptFlag must be a string or null (got: ' + JSON.stringify(oe.promptFlag) + ')',
1589
1706
  );
1590
1707
  }
1708
+
1709
+ // modelFlag — optional; string or null (#3714). `null`/absent means the
1710
+ // host offers no per-invocation model override on this exec path; a
1711
+ // string names the flag that pins the executor's model (codex: --model).
1712
+ // Deliberately asymmetric: only codex declares this today.
1713
+ if (oe.modelFlag !== undefined && oe.modelFlag !== null && typeof oe.modelFlag !== 'string') {
1714
+ errors.push(
1715
+ 'runtime.orchestratorExec.modelFlag must be a string or null (got: ' + JSON.stringify(oe.modelFlag) + ')',
1716
+ );
1717
+ }
1591
1718
  }
1592
1719
  }
1593
1720
 
@@ -3174,6 +3301,11 @@ function validateCrossCapability(capMap, centralKeys, centralPatterns = []) {
3174
3301
  const laneSlugClaims = new Map(); // slug → capId[]
3175
3302
  const laneFlagClaims = new Map(); // flag → capId[]
3176
3303
  const laneSectionClaims = new Map(); // reviewsSection → capId[]
3304
+ // ADR-3646: task-content resolver tracker-prefix uniqueness across the
3305
+ // MERGED first-party ∪ overlay set, mirroring the reviewer-lane collision
3306
+ // pattern above — two resolvers claiming the same prefix would make
3307
+ // `execute:task` dispatch ambiguous (which capability's resolver runs?).
3308
+ const trackerPrefixClaims = new Map(); // trackerPrefix → capId[]
3177
3309
 
3178
3310
  // Claims are ACCUMULATED and reported after the sweep, never reported on the
3179
3311
  // second claimant. Reporting pairwise-on-collision looks equivalent and is not:
@@ -3194,6 +3326,15 @@ function validateCrossCapability(capMap, centralKeys, centralPatterns = []) {
3194
3326
  };
3195
3327
 
3196
3328
  for (const [capId, cap] of capMap) {
3329
+ // ADR-3646: a MALFORMED taskContentResolver body was already reported by
3330
+ // validateCapability — do not double-report; only claim well-shaped
3331
+ // bodies. Independent of the reviewer-lane checks below, so it runs even
3332
+ // for capabilities that carry no `reviewer` body at all.
3333
+ const tcr = cap.taskContentResolver;
3334
+ if (typeof tcr === 'object' && tcr !== null && !Array.isArray(tcr)) {
3335
+ claim(trackerPrefixClaims, tcr.trackerPrefix, capId);
3336
+ }
3337
+
3197
3338
  const r = cap.reviewer;
3198
3339
  // A capability with no lane contributes to no uniqueness set. A MALFORMED
3199
3340
  // body was already reported by validateCapability — do not double-report.
@@ -3215,6 +3356,7 @@ function validateCrossCapability(capMap, centralKeys, centralPatterns = []) {
3215
3356
  [laneSlugClaims, 'slug'],
3216
3357
  [laneFlagClaims, 'flag'],
3217
3358
  [laneSectionClaims, 'reviewsSection'],
3359
+ [trackerPrefixClaims, 'taskContentResolver.trackerPrefix'],
3218
3360
  ]) {
3219
3361
  for (const [key, claimants] of claims) {
3220
3362
  if (claimants.length < 2) continue;
@@ -3435,26 +3577,55 @@ function topoSortContributions(entries) {
3435
3577
 
3436
3578
  // ─── Gen-time wired guard ─────────────────────────────────────────────────────
3437
3579
 
3580
+ /**
3581
+ * Hook group (capability.json array name) → hook kind (dispatch discriminator).
3582
+ * Single source of truth for both validateHooksWired and the scanner-parity
3583
+ * test in tests/capability-registry.test.cjs (#3606).
3584
+ */
3585
+ const HOOK_GROUP_KINDS = Object.freeze({
3586
+ steps: 'step',
3587
+ contributions: 'contribution',
3588
+ gates: 'gate',
3589
+ });
3590
+
3438
3591
  /**
3439
3592
  * Validate that every hook point declared by a capability has a corresponding
3440
- * `loop render-hooks <point>` call site in one of the host-loop workflow files.
3593
+ * `loop render-hooks <point>` call site in one of the host-loop workflow files,
3594
+ * AND — #3606 — that the call site's dispatch text covers the hook's KIND.
3595
+ *
3596
+ * A call site proves hooks are rendered, not dispatched: a consumer that
3597
+ * iterates only `kind == "gate"` (or narrows `kind == "step"` to one
3598
+ * `ref.skill`) silently drops every other registered kind — a capability can
3599
+ * be wired, enabled, resolved active, and never run. See
3600
+ * gsd-core/references/loop-hook-dispatch.md ("A point whose workflow
3601
+ * hand-rolls one kind does not implement this contract").
3441
3602
  *
3442
3603
  * Only valid loop points (in VALID_LOOP_POINTS) are checked here. Invalid points
3443
3604
  * are already caught by validateStep/validateContribution/validateGate — do not
3444
3605
  * double-report.
3445
3606
  *
3607
+ * KNOWN LIMITATION (#3606): coverage is the UNION across all call sites for a
3608
+ * point in the five STEP_WORKFLOWS host files. Consumers outside that universe
3609
+ * (quick.md, autonomous.md, code-review*.md, audit-milestone.md,
3610
+ * secure-phase.md, validate-phase.md) are not per-file checked — a narrowed
3611
+ * consumer there passes as long as one host file covers the point. Per-file
3612
+ * coverage maps are the tightening path.
3613
+ *
3446
3614
  * @param {object} cap Validated capability object.
3447
- * @param {Set<string>} wiredSet Set of points that have call sites in host workflows.
3448
- * @returns {string[]} Array of error strings; empty means all points are wired.
3615
+ * @param {Map<string, Set<string>>} wiredKinds Per point, the hook kinds the
3616
+ * host workflows' call-site dispatch text covers (getWiredKinds). A point
3617
+ * absent from the map is unwired.
3618
+ * @returns {string[]} Array of error strings; empty means all points
3619
+ * are wired and every registered kind is covered.
3449
3620
  */
3450
- function validateHooksWired(cap, wiredSet) {
3621
+ function validateHooksWired(cap, wiredKinds) {
3451
3622
  const errors = [];
3452
3623
  const capId = cap.id || '(unknown)';
3453
3624
 
3454
- function checkPoint(point, groupName, idx) {
3625
+ function checkPoint(point, groupName, kind, idx) {
3455
3626
  // Only flag valid points that are unwired — invalid points are schema-validator's job.
3456
3627
  if (!VALID_LOOP_POINTS.has(point)) return;
3457
- if (!wiredSet.has(point)) {
3628
+ if (!wiredKinds.has(point)) {
3458
3629
  errors.push(
3459
3630
  'capability "' + capId + '" ' + groupName + '[' + idx + '].point "' + point +
3460
3631
  '" is declared but not wired in any host-loop workflow ' +
@@ -3462,20 +3633,34 @@ function validateHooksWired(cap, wiredSet) {
3462
3633
  'Wire the call site in the host workflow ' +
3463
3634
  '(see scripts/gen-loop-host-contract.cjs STEP_WORKFLOWS) or remove the hook.',
3464
3635
  );
3636
+ return;
3637
+ }
3638
+ const covered = wiredKinds.get(point);
3639
+ if (covered.size === 0) {
3640
+ errors.push(
3641
+ 'capability "' + capId + '" ' + groupName + '[' + idx + '].point "' + point +
3642
+ '" has `loop render-hooks ' + point + '` call site(s), but their dispatch text covers NO ' +
3643
+ 'hook kind — every consumer is narrowed to specific hooks. Dispatch every registered kind ' +
3644
+ 'per gsd-core/references/loop-hook-dispatch.md.',
3645
+ );
3646
+ return;
3647
+ }
3648
+ if (!covered.has(kind)) {
3649
+ errors.push(
3650
+ 'capability "' + capId + '" ' + groupName + '[' + idx + '].point "' + point +
3651
+ '" registers a ' + kind + ' hook, but the host call site\'s dispatch text never ' +
3652
+ 'covers `kind == "' + kind + '"` (it covers: ' + [...covered].sort().join(', ') + '). ' +
3653
+ 'A hand-rolled single-kind consumer silently never dispatches the other kinds — ' +
3654
+ 'dispatch every registered kind per gsd-core/references/loop-hook-dispatch.md.',
3655
+ );
3465
3656
  }
3466
3657
  }
3467
3658
 
3468
- for (let i = 0; i < (cap.steps || []).length; i++) {
3469
- const hook = cap.steps[i];
3470
- if (hook.point !== undefined) checkPoint(hook.point, 'steps', i);
3471
- }
3472
- for (let i = 0; i < (cap.contributions || []).length; i++) {
3473
- const hook = cap.contributions[i];
3474
- if (hook.point !== undefined) checkPoint(hook.point, 'contributions', i);
3475
- }
3476
- for (let i = 0; i < (cap.gates || []).length; i++) {
3477
- const hook = cap.gates[i];
3478
- if (hook.point !== undefined) checkPoint(hook.point, 'gates', i);
3659
+ for (const [group, kind] of Object.entries(HOOK_GROUP_KINDS)) {
3660
+ for (let i = 0; i < (cap[group] || []).length; i++) {
3661
+ const hook = cap[group][i];
3662
+ if (hook.point !== undefined) checkPoint(hook.point, group, kind, i);
3663
+ }
3479
3664
  }
3480
3665
 
3481
3666
  return errors;
@@ -3681,6 +3866,7 @@ module.exports = {
3681
3866
  validateCommandEntry,
3682
3867
  validateRuntimeCompat,
3683
3868
  validateFeatureBody,
3869
+ validateTaskContentResolver,
3684
3870
  validateConfigHome,
3685
3871
  validateArtifactKindEntry,
3686
3872
  validateArtifactLayout,
@@ -3699,6 +3885,7 @@ module.exports = {
3699
3885
  topoSortSteps,
3700
3886
  topoSortContributions,
3701
3887
  validateHooksWired,
3888
+ HOOK_GROUP_KINDS,
3702
3889
  validateConfigSliceEntry,
3703
3890
  classifyCrossErrors,
3704
3891
  runConfigFormatParityGate,
@@ -52,6 +52,9 @@ const { scanPhasePlans } = planScanMod;
52
52
  // eslint-disable-next-line @typescript-eslint/no-require-imports
53
53
  const planningScopeMod = require("./planning-scope.cjs");
54
54
  const { SCOPE } = planningScopeMod;
55
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
56
+ const verifyCommandGroundingMod = require("./verify-command-grounding.cjs");
57
+ const { probePhaseVerifyCommands, probePhaseFailingDirections } = verifyCommandGroundingMod;
55
58
  // ─── Helpers ──────────────────────────────────────────────────────────────────
56
59
  function normalizePhrase(text) {
57
60
  // eslint-disable-next-line @typescript-eslint/no-base-to-string
@@ -783,14 +786,11 @@ function cmdTddReviewCheckpoint(projectDir, args, raw) {
783
786
  }
784
787
  const violations = rows.filter(r => r.status === 'FAIL').length;
785
788
  // Build review table
786
- const sep = '━'.repeat(53);
787
789
  const tableHeader = '| Plan | RED | GREEN | REFACTOR | Status |';
788
790
  const tableDivider = '|------|-----|-------|----------|--------|';
789
791
  const tableRows = rows.map(r => `| ${r.planId.padEnd(4)} | ${r.red ? ' ✓ ' : ' ✗ '} | ${r.green ? ' ✓ ' : ' ✗ '} | ${r.refactor ? ' ✓ ' : ' — '} | ${r.status.padEnd(6)} |`);
790
792
  let table = [
791
- sep,
792
- ` TDD REVIEW — Phase ${phase}`,
793
- sep,
793
+ `### TDD REVIEW — Phase ${phase}`,
794
794
  '',
795
795
  `TDD Plans: ${tddPlanFiles.length} | Gate violations: ${violations}`,
796
796
  '',
@@ -825,6 +825,111 @@ function cmdTddReviewCheckpoint(projectDir, args, raw) {
825
825
  // the host-loop dispatch's advisory branch can surface it.
826
826
  output(result, raw, undefined);
827
827
  }
828
+ /**
829
+ * Resolve a phase argument to an absolute phase directory, or '' when it
830
+ * cannot be resolved. Shared by every `check` arm that probes a phase's
831
+ * PLAN.md files, so the two never drift (DEFECT.GENERATIVE-FIX-DIVERGENCE).
832
+ * Never throws — the callers emit a degraded JSON payload instead, because
833
+ * a consumer must be able to tell "nothing to report" from "could not look".
834
+ */
835
+ function resolvePhaseDirOrEmpty(projectDir, phase) {
836
+ try {
837
+ const result = findPhaseInternal(projectDir, phase);
838
+ if (result && typeof result === 'object') {
839
+ // findPhaseInternal returns { directory: '<relative-posix-path>', ... }
840
+ // directory is relative to cwd — resolve it to absolute.
841
+ const relDir = typeof result['directory'] === 'string' ? result['directory'] : '';
842
+ if (relDir) {
843
+ return node_path_1.default.resolve(projectDir, relDir);
844
+ }
845
+ }
846
+ else if (typeof result === 'string') {
847
+ return result;
848
+ }
849
+ }
850
+ catch { /* phase dir lookup failure → caller emits degraded payload */ }
851
+ return '';
852
+ }
853
+ // ─── verify-command-paths (#2401) ──────────────────────────────────────────────
854
+ /**
855
+ * verify-command-paths: probes every `<automated>` verify command declared in a
856
+ * phase's `-PLAN.md` files against the filesystem WITHOUT executing anything —
857
+ * see verify-command-grounding.cjs for the recognizer contract.
858
+ *
859
+ * Args: check verify-command-paths <phase>
860
+ * Invocable as: gsd_run check verify-command-paths <phase>
861
+ *
862
+ * When the phase cannot be resolved to a directory, this emits a non-throwing
863
+ * degraded JSON payload (status/commands/counts all zeroed, `readError`
864
+ * populated) rather than calling `error()` — the plan-checker parses this
865
+ * result and must be able to distinguish "nothing to report" from "could not
866
+ * look", which a non-zero exit / thrown error would collapse.
867
+ */
868
+ function cmdVerifyCommandPaths(projectDir, args, raw) {
869
+ // args[0] = 'check', args[1] = 'verify-command-paths', args[2] = phase
870
+ const phase = args[2] || '';
871
+ if (!phase) {
872
+ output({
873
+ status: 'unresolvable',
874
+ commands: [],
875
+ counts: { blocker: 0, warning: 0, total: 0 },
876
+ readError: 'verify-command-paths requires a phase argument: check verify-command-paths <phase>',
877
+ }, raw, undefined);
878
+ return;
879
+ }
880
+ const phaseDir = resolvePhaseDirOrEmpty(projectDir, phase);
881
+ if (!phaseDir) {
882
+ output({
883
+ status: 'unresolvable',
884
+ commands: [],
885
+ counts: { blocker: 0, warning: 0, total: 0 },
886
+ readError: `could not resolve phase directory for phase ${phase}`,
887
+ }, raw, undefined);
888
+ return;
889
+ }
890
+ const probed = probePhaseVerifyCommands({ phaseDir, projectRoot: projectDir });
891
+ output(probed, raw, undefined);
892
+ }
893
+ // ─── verify-failure-directions (#3172) ─────────────────────────────────────────
894
+ /**
895
+ * verify-failure-directions: probes every `<automated>` verify command
896
+ * declared in a phase's `-PLAN.md` files for a stated `<fails_when>` failing
897
+ * direction — see verify-command-grounding.cjs for the recognizer contract.
898
+ *
899
+ * Args: check verify-failure-directions <phase>
900
+ * Invocable as: gsd_run check verify-failure-directions <phase>
901
+ *
902
+ * When the phase cannot be resolved to a directory, this emits a non-throwing
903
+ * degraded JSON payload (status/commands/counts all zeroed, `readError`
904
+ * populated) rather than calling `error()` — the plan-checker parses this
905
+ * result and must be able to distinguish "nothing to report" from "could not
906
+ * look", which a non-zero exit / thrown error would collapse.
907
+ */
908
+ function cmdVerifyFailureDirections(projectDir, args, raw) {
909
+ // args[0] = 'check', args[1] = 'verify-failure-directions', args[2] = phase
910
+ const phase = args[2] || '';
911
+ if (!phase) {
912
+ output({
913
+ status: 'unresolvable',
914
+ commands: [],
915
+ counts: { blocker: 0, warning: 0, total: 0 },
916
+ readError: 'verify-failure-directions requires a phase argument: check verify-failure-directions <phase>',
917
+ }, raw, undefined);
918
+ return;
919
+ }
920
+ const phaseDir = resolvePhaseDirOrEmpty(projectDir, phase);
921
+ if (!phaseDir) {
922
+ output({
923
+ status: 'unresolvable',
924
+ commands: [],
925
+ counts: { blocker: 0, warning: 0, total: 0 },
926
+ readError: `could not resolve phase directory for phase ${phase}`,
927
+ }, raw, undefined);
928
+ return;
929
+ }
930
+ const result = probePhaseFailingDirections({ phaseDir });
931
+ output(result, raw, undefined);
932
+ }
828
933
  // ─── gap-analysis-plan-post ───────────────────────────────────────────────────
829
934
  /**
830
935
  * gap-analysis-plan-post: non-blocking advisory check that runs the post-planning
@@ -1203,6 +1308,27 @@ function cmdApiCoverageVerifyPre(projectDir, args, raw) {
1203
1308
  }, raw, undefined);
1204
1309
  return;
1205
1310
  }
1311
+ // An EMPTY scope is not a negative verdict. This gate's neighbouring arms
1312
+ // already fail closed (unresolvable phase → block; unreadable plan → block),
1313
+ // but a phase with no plan body AND no roadmap section fell through to
1314
+ // detection over zero bytes and CERTIFIED "no external-API integration" —
1315
+ // clearing a blocking seal gate on a probe that examined nothing
1316
+ // (ADR-3889 failure class (c), #3909). The discriminator is BYTES EXAMINED,
1317
+ // never SIGNALS FOUND: a phase with real plans and no API vocabulary still
1318
+ // reaches the pass below unchanged.
1319
+ if (scope.text.trim() === '') {
1320
+ output({
1321
+ block: true,
1322
+ passed: false,
1323
+ coverage_present: false,
1324
+ detected: false,
1325
+ scope_unavailable: true,
1326
+ message: 'api-coverage: the phase scope is empty — no plan body and no roadmap section were ' +
1327
+ 'found, so nothing was examined. Refusing to certify no external-API integration ' +
1328
+ 'from an unestablished scope. Add the phase plan, or add a COVERAGE.md declaration.',
1329
+ }, raw, undefined);
1330
+ return;
1331
+ }
1206
1332
  const detection = detectApiIntegration(scope.text);
1207
1333
  if (detection.detected) {
1208
1334
  // Surface only verb/noun (typed, bounded) — NOT raw prose snippets — so the
@@ -1324,6 +1450,18 @@ function routeCheckCommand({ args, cwd, raw }) {
1324
1450
  cmdGapAnalysisPlanPost(cwd, args, raw);
1325
1451
  return;
1326
1452
  }
1453
+ if (subcommand === 'verify-command-paths') {
1454
+ // Deterministic filesystem probe for <automated> verify commands (#2401) —
1455
+ // never executes anything; see verify-command-grounding.cjs.
1456
+ cmdVerifyCommandPaths(cwd, args, raw);
1457
+ return;
1458
+ }
1459
+ if (subcommand === 'verify-failure-directions') {
1460
+ // Presence probe for a stated <fails_when> per <automated> command
1461
+ // (#3172) — never executes anything; see verify-command-grounding.cjs.
1462
+ cmdVerifyFailureDirections(cwd, args, raw);
1463
+ return;
1464
+ }
1327
1465
  if (subcommand === 'api-coverage-verify-pre') {
1328
1466
  // ai-integration capability blocking gate at verify:pre (#1562). Dot-to-
1329
1467
  // hyphen normalization means query "api-coverage.verify-pre" routes here.
@@ -1370,7 +1508,7 @@ function routeCheckCommand({ args, cwd, raw }) {
1370
1508
  (0, prohibition_enforcement_cjs_1.routeProhibitionEnforcement)(args, raw);
1371
1509
  return;
1372
1510
  }
1373
- error('Unknown check subcommand. Available: api-coverage-verify-pre, auto-mode, decision-coverage-plan, decision-coverage-verify, gap-analysis-plan-post, predicate, prohibition-enforcement, tdd-review-checkpoint, ui-plan-gate, ui-safety-gate, verify-schema-drift, verify-codebase-drift', ERROR_REASON.SDK_UNKNOWN_COMMAND);
1511
+ error('Unknown check subcommand. Available: api-coverage-verify-pre, auto-mode, decision-coverage-plan, decision-coverage-verify, gap-analysis-plan-post, predicate, prohibition-enforcement, tdd-review-checkpoint, ui-plan-gate, ui-safety-gate, verify-command-paths, verify-failure-directions, verify-schema-drift, verify-codebase-drift', ERROR_REASON.SDK_UNKNOWN_COMMAND);
1374
1512
  }
1375
1513
  module.exports = {
1376
1514
  routeCheckCommand,
@@ -1379,6 +1517,8 @@ module.exports = {
1379
1517
  computeUiPlanGate,
1380
1518
  computeUiSafetyGate,
1381
1519
  cmdGapAnalysisPlanPost,
1520
+ cmdVerifyCommandPaths,
1521
+ cmdVerifyFailureDirections,
1382
1522
  cmdTddReviewCheckpoint,
1383
1523
  cmdCheckPredicate,
1384
1524
  buildPredicateDeps,