@opengsd/gsd-core 1.10.0 → 1.11.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 (328) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/agents/gsd-debug-session-manager.md +11 -0
  4. package/agents/gsd-doc-synthesizer.md +2 -4
  5. package/agents/gsd-executor.md +5 -5
  6. package/agents/gsd-mempalace-curator.md +5 -2
  7. package/agents/gsd-phase-researcher.md +20 -1
  8. package/agents/gsd-plan-checker.md +37 -0
  9. package/agents/gsd-planner.md +44 -46
  10. package/agents/gsd-user-profiler.md +3 -0
  11. package/agents/gsd-verifier.md +12 -3
  12. package/bin/install.js +841 -971
  13. package/bin/lib/ui-safety-gate.cjs +2 -0
  14. package/commands/gsd/code-review.md +1 -1
  15. package/commands/gsd/execute-phase.md +1 -1
  16. package/commands/gsd/map-codebase.md +1 -1
  17. package/commands/gsd/mempalace-capture.md +1 -1
  18. package/commands/gsd/mempalace-recall.md +1 -1
  19. package/commands/gsd/new-milestone.md +1 -1
  20. package/commands/gsd/quick.md +1 -1
  21. package/commands/gsd/review-backlog.md +2 -1
  22. package/commands/gsd/verify-work.md +1 -1
  23. package/gsd-core/bin/gsd-tools.cjs +469 -88
  24. package/gsd-core/bin/lib/active-workstream-store.cjs +138 -22
  25. package/gsd-core/bin/lib/agent-install-check.cjs +230 -32
  26. package/gsd-core/bin/lib/api-coverage.cjs +3 -5
  27. package/gsd-core/bin/lib/artifacts.cjs +3 -0
  28. package/gsd-core/bin/lib/assumption-delta.cjs +2 -4
  29. package/gsd-core/bin/lib/audit-command-router.cjs +9 -2
  30. package/gsd-core/bin/lib/audit.cjs +876 -240
  31. package/gsd-core/bin/lib/broken-windows.cjs +1 -1
  32. package/gsd-core/bin/lib/capability-consent.cjs +149 -15
  33. package/gsd-core/bin/lib/capability-lifecycle.cjs +45 -0
  34. package/gsd-core/bin/lib/capability-registry.cjs +575 -101
  35. package/gsd-core/bin/lib/capability-source.cjs +92 -0
  36. package/gsd-core/bin/lib/capability-trust.cjs +444 -25
  37. package/gsd-core/bin/lib/capability-validator.cjs +495 -22
  38. package/gsd-core/bin/lib/capability-writer.cjs +3 -2
  39. package/gsd-core/bin/lib/check-command-router.cjs +71 -37
  40. package/gsd-core/bin/lib/claude-orchestration.cjs +56 -3
  41. package/gsd-core/bin/lib/codex-agent-toml.cjs +329 -0
  42. package/gsd-core/bin/lib/command-aliases.cjs +22 -0
  43. package/gsd-core/bin/lib/command-roster.cjs +44 -1
  44. package/gsd-core/bin/lib/commands.cjs +651 -86
  45. package/gsd-core/bin/lib/commonjs-marker.cjs +12 -6
  46. package/gsd-core/bin/lib/complexity-trigger.cjs +1172 -0
  47. package/gsd-core/bin/lib/config-loader.cjs +75 -0
  48. package/gsd-core/bin/lib/config.cjs +10 -1
  49. package/gsd-core/bin/lib/core-utils.cjs +127 -29
  50. package/gsd-core/bin/lib/decisions.cjs +23 -0
  51. package/gsd-core/bin/lib/fallow-runner.cjs +20 -44
  52. package/gsd-core/bin/lib/frontmatter.cjs +155 -20
  53. package/gsd-core/bin/lib/gap-checker.cjs +68 -7
  54. package/gsd-core/bin/lib/git-base-branch.cjs +102 -0
  55. package/gsd-core/bin/lib/gsd2-import.cjs +10 -1
  56. package/gsd-core/bin/lib/health-diagnostic-rules/agent-install.cjs +101 -0
  57. package/gsd-core/bin/lib/health-diagnostic-rules/config-validation.cjs +348 -0
  58. package/gsd-core/bin/lib/health-diagnostic-rules/consistency.cjs +145 -0
  59. package/gsd-core/bin/lib/health-diagnostic-rules/install-surface-shadowing.cjs +98 -0
  60. package/gsd-core/bin/lib/health-diagnostic-rules/milestone-archive-hygiene.cjs +100 -0
  61. package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +222 -0
  62. package/gsd-core/bin/lib/health-diagnostic-rules/roadmap-disk-consistency.cjs +265 -0
  63. package/gsd-core/bin/lib/health-diagnostic-rules/root-existence.cjs +161 -0
  64. package/gsd-core/bin/lib/health-diagnostic-rules/state-consistency.cjs +303 -0
  65. package/gsd-core/bin/lib/health-diagnostic-rules/worktree-health.cjs +173 -0
  66. package/gsd-core/bin/lib/health-diagnostic-types.cjs +68 -0
  67. package/gsd-core/bin/lib/health-diagnostic.cjs +431 -0
  68. package/gsd-core/bin/lib/host-runtime-detection.cjs +134 -0
  69. package/gsd-core/bin/lib/init.cjs +321 -129
  70. package/gsd-core/bin/lib/install-effort-resolver.cjs +73 -30
  71. package/gsd-core/bin/lib/install-engine.cjs +745 -258
  72. package/gsd-core/bin/lib/install-fs-adapter.cjs +262 -0
  73. package/gsd-core/bin/lib/install-model-override-resolver.cjs +203 -0
  74. package/gsd-core/bin/lib/install-profiles.cjs +134 -57
  75. package/gsd-core/bin/lib/install-scope.cjs +270 -0
  76. package/gsd-core/bin/lib/install-shadow-report.cjs +385 -0
  77. package/gsd-core/bin/lib/installed-surface-resolver.cjs +381 -0
  78. package/gsd-core/bin/lib/installer-migrations.cjs +138 -31
  79. package/gsd-core/bin/lib/io.cjs +10 -0
  80. package/gsd-core/bin/lib/markdown-sectionizer.cjs +2 -1
  81. package/gsd-core/bin/lib/markdown-table.cjs +133 -20
  82. package/gsd-core/bin/lib/milestone-lock.cjs +248 -0
  83. package/gsd-core/bin/lib/milestone.cjs +754 -70
  84. package/gsd-core/bin/lib/model-catalog.cjs +59 -1
  85. package/gsd-core/bin/lib/model-resolver.cjs +183 -40
  86. package/gsd-core/bin/lib/normalize-test-command.cjs +1 -1
  87. package/gsd-core/bin/lib/pattern.cjs +122 -0
  88. package/gsd-core/bin/lib/phase-estimation.cjs +1 -1
  89. package/gsd-core/bin/lib/phase-id.cjs +444 -36
  90. package/gsd-core/bin/lib/phase-lifecycle.cjs +28 -3
  91. package/gsd-core/bin/lib/phase-locator.cjs +125 -18
  92. package/gsd-core/bin/lib/phase.cjs +646 -143
  93. package/gsd-core/bin/lib/plan-dependency-graph.cjs +72 -1
  94. package/gsd-core/bin/lib/plan-drift-guard.cjs +120 -0
  95. package/gsd-core/bin/lib/plan-scan.cjs +86 -2
  96. package/gsd-core/bin/lib/planning-scope.cjs +31 -0
  97. package/gsd-core/bin/lib/planning-snapshot.cjs +890 -0
  98. package/gsd-core/bin/lib/planning-workspace.cjs +56 -6
  99. package/gsd-core/bin/lib/probe-core.cjs +1 -1
  100. package/gsd-core/bin/lib/profile-output.cjs +1 -1
  101. package/gsd-core/bin/lib/refactor-trigger-command-router.cjs +740 -0
  102. package/gsd-core/bin/lib/retired-artifact-cleanup.cjs +11 -6
  103. package/gsd-core/bin/lib/review-lane-descriptor.cjs +13 -4
  104. package/gsd-core/bin/lib/review-lane-invocation.cjs +30 -0
  105. package/gsd-core/bin/lib/review-lane-runner.cjs +421 -66
  106. package/gsd-core/bin/lib/review-reviewer-selection.cjs +13 -18
  107. package/gsd-core/bin/lib/roadmap-command-router.cjs +34 -0
  108. package/gsd-core/bin/lib/roadmap-parser.cjs +943 -184
  109. package/gsd-core/bin/lib/roadmap-upgrade.cjs +37 -10
  110. package/gsd-core/bin/lib/roadmap.cjs +385 -94
  111. package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +608 -46
  112. package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +14 -2
  113. package/gsd-core/bin/lib/runtime-artifact-layout.cjs +426 -55
  114. package/gsd-core/bin/lib/runtime-config-adapter-registry.cjs +3 -2
  115. package/gsd-core/bin/lib/runtime-homes.cjs +69 -3
  116. package/gsd-core/bin/lib/runtime-hooks-surface.cjs +115 -3
  117. package/gsd-core/bin/lib/runtime-name-policy.cjs +3 -1
  118. package/gsd-core/bin/lib/runtime-slash.cjs +27 -9
  119. package/gsd-core/bin/lib/security.cjs +104 -5
  120. package/gsd-core/bin/lib/shell-command-projection.cjs +275 -3
  121. package/gsd-core/bin/lib/smart-entry.cjs +142 -22
  122. package/gsd-core/bin/lib/state-command-router.cjs +5 -1
  123. package/gsd-core/bin/lib/state-document.cjs +152 -8
  124. package/gsd-core/bin/lib/state-transition.cjs +371 -117
  125. package/gsd-core/bin/lib/state.cjs +1794 -357
  126. package/gsd-core/bin/lib/surface.cjs +23 -9
  127. package/gsd-core/bin/lib/text-lines.cjs +80 -0
  128. package/gsd-core/bin/lib/token-scanner.cjs +76 -0
  129. package/gsd-core/bin/lib/uat-predicate.cjs +9 -3
  130. package/gsd-core/bin/lib/uat.cjs +399 -56
  131. package/gsd-core/bin/lib/ui-frontend-evidence.cjs +157 -0
  132. package/gsd-core/bin/lib/ui-safety-gate.cjs +14 -5
  133. package/gsd-core/bin/lib/unusable-input.cjs +24 -0
  134. package/gsd-core/bin/lib/update-context.cjs +8 -2
  135. package/gsd-core/bin/lib/user-artifact-staging.cjs +705 -0
  136. package/gsd-core/bin/lib/validate.cjs +20 -6
  137. package/gsd-core/bin/lib/vendor/README.md +37 -0
  138. package/gsd-core/bin/lib/vendor/re2js.cjs +6480 -0
  139. package/gsd-core/bin/lib/vendor/re2js.d.cts +938 -0
  140. package/gsd-core/bin/lib/verification-command-router.cjs +2 -1
  141. package/gsd-core/bin/lib/verification.cjs +258 -8
  142. package/gsd-core/bin/lib/verify.cjs +368 -888
  143. package/gsd-core/bin/lib/workstream-inventory-builder.cjs +53 -32
  144. package/gsd-core/bin/lib/workstream-inventory.cjs +63 -10
  145. package/gsd-core/bin/lib/workstream.cjs +2 -2
  146. package/gsd-core/bin/lib/worktree-safety.cjs +176 -9
  147. package/gsd-core/bin/shared/config-defaults.manifest.json +1 -0
  148. package/gsd-core/bin/shared/config-schema.manifest.json +7 -1
  149. package/gsd-core/references/agent-contracts.md +43 -26
  150. package/gsd-core/references/checkpoints.md +2 -2
  151. package/gsd-core/references/context-budget.md +1 -1
  152. package/gsd-core/references/dispatch-isolation-gate.md +138 -0
  153. package/gsd-core/references/doc-conflict-engine.md +1 -1
  154. package/gsd-core/references/execute-mvp-tdd.md +3 -3
  155. package/gsd-core/references/execute-phase-between-wave-reset.md +6 -2
  156. package/gsd-core/references/execute-phase-context-guard.md +1 -1
  157. package/gsd-core/references/execute-phase-response-language.md +1 -1
  158. package/gsd-core/references/execute-phase-wave-guard.md +6 -2
  159. package/gsd-core/references/gate-prompts.md +1 -1
  160. package/gsd-core/references/git-planning-commit.md +2 -1
  161. package/gsd-core/references/loop-hook-dispatch.md +39 -2
  162. package/gsd-core/references/model-profiles.md +12 -4
  163. package/gsd-core/references/mvp-concepts.md +9 -9
  164. package/gsd-core/references/planner-guidance.md +3 -9
  165. package/gsd-core/references/planner-preconditions.md +1 -1
  166. package/gsd-core/references/planner-reviews.md +1 -1
  167. package/gsd-core/references/planning-config.md +8 -6
  168. package/gsd-core/references/revision-loop.md +1 -1
  169. package/gsd-core/references/specless-probe-fallback.md +1 -1
  170. package/gsd-core/references/universal-anti-patterns.md +3 -3
  171. package/gsd-core/references/verifier-phase-gates.md +192 -0
  172. package/gsd-core/references/verify-mvp-mode.md +1 -1
  173. package/gsd-core/references/workstream-flag.md +22 -6
  174. package/gsd-core/templates/discussion-log.md +1 -1
  175. package/gsd-core/templates/phase-prompt.md +2 -4
  176. package/gsd-core/templates/state.md +4 -4
  177. package/gsd-core/templates/verification-report.md +9 -1
  178. package/gsd-core/workflows/ai-integration-phase.md +9 -11
  179. package/gsd-core/workflows/autonomous.md +1 -1
  180. package/gsd-core/workflows/cleanup.md +62 -3
  181. package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +13 -3
  182. package/gsd-core/workflows/code-review-fix.md +37 -10
  183. package/gsd-core/workflows/code-review.md +38 -12
  184. package/gsd-core/workflows/complete-milestone.md +141 -18
  185. package/gsd-core/workflows/debug.md +7 -5
  186. package/gsd-core/workflows/diagnose-issues.md +35 -9
  187. package/gsd-core/workflows/discuss-phase/modes/chain.md +2 -1
  188. package/gsd-core/workflows/discuss-phase/modes/default.md +1 -1
  189. package/gsd-core/workflows/discuss-phase-assumptions.md +2 -1
  190. package/gsd-core/workflows/edit-phase.md +26 -1
  191. package/gsd-core/workflows/eval-review.md +3 -5
  192. package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +31 -6
  193. package/gsd-core/workflows/execute-phase/steps/per-plan-executor-routing.md +77 -0
  194. package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +2 -0
  195. package/gsd-core/workflows/execute-phase.md +38 -50
  196. package/gsd-core/workflows/execute-plan.md +36 -4
  197. package/gsd-core/workflows/explore.md +131 -4
  198. package/gsd-core/workflows/fast.md +10 -2
  199. package/gsd-core/workflows/health.md +73 -4
  200. package/gsd-core/workflows/import.md +4 -4
  201. package/gsd-core/workflows/ingest-docs.md +5 -5
  202. package/gsd-core/workflows/mvp-phase.md +6 -3
  203. package/gsd-core/workflows/new-milestone.md +14 -9
  204. package/gsd-core/workflows/new-project.md +14 -14
  205. package/gsd-core/workflows/next.md +12 -0
  206. package/gsd-core/workflows/plan-phase.md +41 -17
  207. package/gsd-core/workflows/plan-review-convergence.md +50 -2
  208. package/gsd-core/workflows/progress.md +34 -6
  209. package/gsd-core/workflows/quick/steps/plan-checker-loop.md +4 -4
  210. package/gsd-core/workflows/quick/steps/quick-verification.md +27 -6
  211. package/gsd-core/workflows/quick/steps/research-phase.md +2 -2
  212. package/gsd-core/workflows/quick.md +35 -15
  213. package/gsd-core/workflows/review.md +26 -5
  214. package/gsd-core/workflows/secure-phase.md +1 -1
  215. package/gsd-core/workflows/session-report.md +2 -1
  216. package/gsd-core/workflows/settings.md +66 -2
  217. package/gsd-core/workflows/ship.md +104 -44
  218. package/gsd-core/workflows/spec-phase.md +30 -12
  219. package/gsd-core/workflows/sync-skills.md +63 -8
  220. package/gsd-core/workflows/transition.md +46 -11
  221. package/gsd-core/workflows/ui-phase.md +5 -5
  222. package/gsd-core/workflows/ui-review.md +2 -2
  223. package/gsd-core/workflows/update.md +1 -1
  224. package/gsd-core/workflows/validate-phase.md +1 -1
  225. package/gsd-core/workflows/verify-work.md +9 -7
  226. package/hooks/dist/gsd-agent-isolation-guard.js +103 -14
  227. package/hooks/dist/gsd-check-update-worker.js +56 -13
  228. package/hooks/dist/gsd-check-update.js +19 -1
  229. package/hooks/dist/gsd-cursor-pre-tool.js +0 -3
  230. package/hooks/dist/gsd-cursor-subagent-start.js +77 -2
  231. package/hooks/dist/gsd-cursor-subagent-stop.js +3 -2
  232. package/hooks/dist/gsd-prompt-guard.js +21 -20
  233. package/hooks/dist/gsd-read-injection-scanner.js +38 -24
  234. package/hooks/dist/gsd-statusline.js +18 -0
  235. package/hooks/dist/gsd-update-banner.js +22 -1
  236. package/hooks/dist/gsd-workflow-guard.js +134 -36
  237. package/hooks/dist/lib/git-cmd.js +92 -59
  238. package/hooks/dist/lib/injection-patterns.js +45 -0
  239. package/hooks/dist/lib/isolation-deny-reason.js +39 -0
  240. package/hooks/dist/lib/isolation-sentinel.js +9 -0
  241. package/hooks/gsd-agent-isolation-guard.js +103 -14
  242. package/hooks/gsd-check-update-worker.js +56 -13
  243. package/hooks/gsd-check-update.js +19 -1
  244. package/hooks/gsd-cursor-pre-tool.js +0 -3
  245. package/hooks/gsd-cursor-subagent-start.js +77 -2
  246. package/hooks/gsd-cursor-subagent-stop.js +3 -2
  247. package/hooks/gsd-prompt-guard.js +21 -20
  248. package/hooks/gsd-read-injection-scanner.js +38 -24
  249. package/hooks/gsd-statusline.js +18 -0
  250. package/hooks/gsd-update-banner.js +22 -1
  251. package/hooks/gsd-workflow-guard.js +134 -36
  252. package/hooks/lib/git-cmd.js +92 -59
  253. package/hooks/lib/injection-patterns.js +45 -0
  254. package/hooks/lib/isolation-deny-reason.js +39 -0
  255. package/hooks/lib/isolation-sentinel.js +9 -0
  256. package/package.json +21 -9
  257. package/pi/gsd.cjs +19 -5
  258. package/scripts/baselines/planning-prompt-drift-baseline.json +4 -0
  259. package/scripts/baselines/planning-snapshot-bypass-baseline.json +12 -0
  260. package/scripts/baselines/unreachable-guard-drift-baseline.json +4 -0
  261. package/scripts/changeset/lint.cjs +60 -5
  262. package/scripts/check-alias-drift.cjs +7 -43
  263. package/scripts/check-contract-drift.cjs +297 -0
  264. package/scripts/ci-test-scope.cjs +19 -2
  265. package/scripts/command-contract-helpers.cjs +903 -1
  266. package/scripts/gen-adr-index.cjs +728 -38
  267. package/scripts/gen-capability-registry.cjs +3 -15
  268. package/scripts/gen-context-index.cjs +2 -11
  269. package/scripts/gen-health-docs.cjs +390 -0
  270. package/scripts/gen-inventory-manifest.cjs +50 -4
  271. package/scripts/gen-loop-host-contract.cjs +4 -24
  272. package/scripts/gen-registry.cjs +3 -14
  273. package/scripts/lib/alias-drift-families.cjs +46 -0
  274. package/scripts/lib/drift-scan.cjs +278 -0
  275. package/scripts/lint-allow-test-rule-refs.allowlist.json +1 -26
  276. package/scripts/lint-allow-test-rule-refs.effective-ceiling.json +4 -0
  277. package/scripts/lint-allow-test-rule-refs.unverified-ceiling.json +3 -0
  278. package/scripts/lint-canary-version-leak.cjs +73 -0
  279. package/scripts/lint-command-contract.cjs +96 -13
  280. package/scripts/lint-completion-predicate-drift.cjs +933 -0
  281. package/scripts/lint-completion-ratio-drift.cjs +214 -0
  282. package/scripts/lint-default-flip-documentation.cjs +193 -0
  283. package/scripts/lint-eslint-glob-coverage.allowlist.json +34 -0
  284. package/scripts/lint-eslint-glob-coverage.cjs +340 -0
  285. package/scripts/lint-frontmatter-scalar-broad-grep.cjs +237 -0
  286. package/scripts/lint-health-diagnostic-rule-table.cjs +404 -0
  287. package/scripts/lint-hooks-runtime-build-seam.cjs +262 -0
  288. package/scripts/lint-milestone-window-drift.cjs +468 -0
  289. package/scripts/lint-phase-enumeration-drift.cjs +479 -0
  290. package/scripts/lint-plan-count-drift.cjs +318 -0
  291. package/scripts/lint-planning-artifact-writer-drift.cjs +398 -0
  292. package/scripts/lint-planning-prompt-drift.cjs +434 -0
  293. package/scripts/lint-planning-snapshot-bypass-drift.cjs +544 -0
  294. package/scripts/lint-regression-test-names.cjs +15 -13
  295. package/scripts/lint-removed-but-needed.cjs +320 -0
  296. package/scripts/lint-state-field-drift.cjs +805 -0
  297. package/scripts/lint-state-write-path-drift.cjs +1045 -0
  298. package/scripts/lint-test-file-count.allowlist.json +21 -10
  299. package/scripts/lint-unreachable-guard-drift.cjs +843 -0
  300. package/scripts/lint-vendored-deps.cjs +124 -0
  301. package/scripts/pr-changed-files.cjs +63 -0
  302. package/scripts/pr-template-policy.cjs +14 -4
  303. package/scripts/prompt-injection-scan.sh +25 -0
  304. package/scripts/require-issue-link-policy.cjs +192 -0
  305. package/scripts/state-write-path-drift-baseline.json +19 -0
  306. package/scripts/sync-runtime-launcher.cjs +2 -4
  307. package/skills/gsd-autonomous/SKILL.md +0 -1
  308. package/skills/gsd-code-review/SKILL.md +1 -1
  309. package/skills/gsd-execute-phase/SKILL.md +1 -2
  310. package/skills/gsd-map-codebase/SKILL.md +1 -1
  311. package/skills/gsd-mempalace-capture/SKILL.md +1 -1
  312. package/skills/gsd-mempalace-recall/SKILL.md +1 -1
  313. package/skills/gsd-new-milestone/SKILL.md +1 -1
  314. package/skills/gsd-next/SKILL.md +0 -1
  315. package/skills/gsd-plan-phase/SKILL.md +0 -1
  316. package/skills/gsd-progress/SKILL.md +0 -1
  317. package/skills/gsd-quick/SKILL.md +1 -1
  318. package/skills/gsd-review-backlog/SKILL.md +2 -1
  319. package/skills/gsd-stats/SKILL.md +0 -1
  320. package/skills/gsd-verify-work/SKILL.md +1 -1
  321. package/vscode/package.json +1 -1
  322. package/gsd-core/workflows/discovery-phase.md +0 -298
  323. package/gsd-core/workflows/plan-milestone-gaps.md +0 -281
  324. package/gsd-core/workflows/verify-phase.md +0 -574
  325. package/scripts/affected-tests-lib.cjs +0 -554
  326. package/scripts/lint-allow-test-rule-refs.cjs +0 -162
  327. package/scripts/run-affected-tests.cjs +0 -7
  328. package/scripts/run-tests.cjs +0 -1051
@@ -13,7 +13,7 @@
13
13
  const cjsCommandRouterAdapter = require("./cjs-command-router-adapter.cjs");
14
14
  const { routeCjsCommandFamily } = cjsCommandRouterAdapter;
15
15
  // ─── Implementation ───────────────────────────────────────────────────────────
16
- const VERIFICATION_SUBCOMMANDS = ['status'];
16
+ const VERIFICATION_SUBCOMMANDS = ['status', 'resolve-file'];
17
17
  function routeVerificationCommand({ verification, args, cwd, raw, error, }) {
18
18
  routeCjsCommandFamily({
19
19
  args,
@@ -23,6 +23,7 @@ function routeVerificationCommand({ verification, args, cwd, raw, error, }) {
23
23
  unknownMessage: (_subcommand, available) => `Unknown verification subcommand. Available: ${available.join(', ')}`,
24
24
  handlers: {
25
25
  status: () => verification.cmdVerificationStatus(cwd, args[2], raw),
26
+ 'resolve-file': () => verification.cmdVerificationResolveFile(cwd, args[2], raw),
26
27
  },
27
28
  });
28
29
  }
@@ -40,11 +40,14 @@ const phaseId = require("./phase-id.cjs");
40
40
  const frontmatterMod = require("./frontmatter.cjs");
41
41
  // eslint-disable-next-line @typescript-eslint/no-require-imports -- plan-scan.cjs is an export= CommonJS module
42
42
  const scanPhasePlans = require("./plan-scan.cjs");
43
+ // eslint-disable-next-line @typescript-eslint/no-require-imports -- planning-scope.cjs is an export= CommonJS module
44
+ const planningScopeMod = require("./planning-scope.cjs");
43
45
  const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs");
44
46
  const runtime_slash_cjs_1 = require("./runtime-slash.cjs");
45
47
  const { output, error } = io;
46
- const { extractPhaseToken } = phaseId;
48
+ const { extractPhaseToken, scopeToPhase } = phaseId;
47
49
  const { extractFrontmatter } = frontmatterMod;
50
+ const { SCOPE } = planningScopeMod;
48
51
  // ─── Constants ────────────────────────────────────────────────────────────────
49
52
  /** The set of status values that the gsd-verifier agent emits. */
50
53
  const VERIFIER_STATUSES = ['passed', 'gaps_found', 'human_needed'];
@@ -100,7 +103,7 @@ const VERIFICATION_ROUTING_TABLE = {
100
103
  // the file has no parseable frontmatter status. Never emitted by the verifier.
101
104
  missing: {
102
105
  status: 'missing',
103
- next_action: 'No verification report found — the verify step never completed. Re-run execute-phase.',
106
+ next_action: 'No verification report found — the verify step never completed. Running execute-phase is safe here: it resumes at the verification gates and does not re-run plans that already have a SUMMARY.md (see #2868).',
104
107
  next_command: 'execute-phase',
105
108
  },
106
109
  // INTERNAL SENTINEL: constructed when the file has a status value not in
@@ -211,6 +214,145 @@ function missingResult(runtime, phaseArg) {
211
214
  next_command: projectNextCommand(route.next_command, runtime, phaseArg),
212
215
  };
213
216
  }
217
+ /**
218
+ * #3518: the shared phase-pinned artifact-selection core BOTH single-pick
219
+ * resolvers (`resolveVerificationFile` for `*-VERIFICATION.md`,
220
+ * `resolveUatFile` for `*-UAT.md`) delegate to — one rule, not two grammars
221
+ * that agree today and drift tomorrow (epic #3473 F2's defect class).
222
+ *
223
+ * `bareName` is the artifact filename WITHOUT the leading dash (`'UAT.md'`);
224
+ * a "dashed" candidate is any entry ending `-${bareName}`.
225
+ *
226
+ * Selection order:
227
+ * 1. `options.phaseToken` given and `<phaseToken>-${bareName}` is among
228
+ * the candidates — that exact file always wins: it is THIS phase's own
229
+ * artifact, and no other candidate (whichever phase's token it carries)
230
+ * can outrank it (#3492 / #3518).
231
+ * 2. Fallback — no exact phase-token match (or no token given): alphabetically
232
+ * first of the dashed candidates that are THIS phase's own, per
233
+ * `scopeToPhase(candidates, options.phaseDirName)` (#3511 reconciliation,
234
+ * below). Load-bearing: a phase whose only artifact is non-canonically
235
+ * named must keep resolving to it, not to null — this fix must not turn
236
+ * "found an artifact" into "found nothing" for anyone. A
237
+ * non-canonically-named artifact of THIS phase (e.g.
238
+ * `03-CORRECTION-VERIFICATION.md` in `03-foo`) still passes
239
+ * `isPhaseArtifact` (it names phase 03, same as the directory), so it
240
+ * is still returned here.
241
+ * 3. `options.allowBare` only — a bare `${bareName}`, ranked BELOW both
242
+ * of the above. Rationale: a dashed file names its phase, a bare one
243
+ * does not, so a dashed file (canonical or not) is always the better
244
+ * answer when both exist. Reached when neither (1) nor (2) found any
245
+ * candidate — including when (2)'s scoping filtered every dashed
246
+ * candidate out as belonging to some OTHER phase.
247
+ *
248
+ * #3511 RECONCILIATION with `isPhaseArtifact` (`src/phase-id.cts`): that
249
+ * predicate's own docblock used to flag this fallback as an open gap — its
250
+ * aggregate scans exclude a cross-phase stray, but this single-pick resolver
251
+ * did not, so it could return a stray as THE artifact while the aggregate
252
+ * scans correctly ignored it. Closed by scoping step (2) above through
253
+ * `scopeToPhase` (`src/phase-id.cts`, itself built on `isPhaseArtifact`):
254
+ * `options.phaseDirName` threads the phase directory's basename in, and the
255
+ * fallback now filters candidates through `scopeToPhase(candidates,
256
+ * phaseDirName)` before picking alphabetically-first. This does NOT reopen
257
+ * the #3357 guarantee — that guarantee is "a phase whose only report is
258
+ * non-canonically named must keep working", and a non-canonically-named
259
+ * artifact of THIS phase still passes `isPhaseArtifact` (it is membership by
260
+ * phase number, not by canonical shape), so it is still returned. Only a
261
+ * file belonging to a DIFFERENT phase is now excluded — and excluding it is
262
+ * correct: returning another phase's artifact as this phase's own is worse
263
+ * than reporting none (confidently wrong beats honestly empty).
264
+ * The fail-safe now lives entirely inside `isPhaseArtifact`, not in
265
+ * `scopeToPhase` (which is a plain filter with no unfiltered fallback):
266
+ * (a) when phase-number membership cannot be determined for `phaseDirName` at
267
+ * all (no reliable token — the zero-token directory case), every candidate is
268
+ * treated as belonging to the phase; (b) the `firstLetterPrefixed`
269
+ * bracket-ambiguity case, where a letter-prefixed-decimal dir is
270
+ * string-indistinguishable from a bracket-dir token, also includes
271
+ * everything rather than guess; (c) a token-less filename (bare
272
+ * `${bareName}`) is accepted by directory containment alone. Outside
273
+ * those cases, when scoping DOES remove every dashed candidate — a real
274
+ * cross-phase stray, or a phase whose own artifact is genuinely absent — the
275
+ * fallback below correctly falls through to `allowBare`/`null`: reporting no
276
+ * artifact, not another phase's. `options.phaseDirName` omitted entirely skips
277
+ * the filter outright (the ternary below), which is unscoped, pre-#3511
278
+ * behavior.
279
+ *
280
+ * Pure — takes an already-read directory listing and does no I/O of its own,
281
+ * so every call site keeps its existing `fsImpl` seam and no-throw contract
282
+ * untouched.
283
+ */
284
+ function resolvePhaseArtifactFile(entries, bareName, options = {}) {
285
+ const candidates = entries.filter((f) => f.endsWith(`-${bareName}`)).sort();
286
+ if (candidates.length > 0) {
287
+ if (options.phaseToken) {
288
+ const thisPhaseFile = `${options.phaseToken}-${bareName}`;
289
+ if (candidates.includes(thisPhaseFile))
290
+ return thisPhaseFile;
291
+ }
292
+ // #3511: scope the fallback to files that belong to THIS phase, so a
293
+ // stray cross-phase file can no longer outrank a return of null.
294
+ // `phaseDirName` omitted, or membership undeterminable for it, →
295
+ // unscoped `candidates` (pre-#3511 behavior); otherwise strays are
296
+ // filtered out, and if that leaves nothing the code falls through to
297
+ // `allowBare`/`null` deliberately.
298
+ const scoped = options.phaseDirName
299
+ ? scopeToPhase(candidates, options.phaseDirName)
300
+ : candidates;
301
+ if (scoped.length > 0)
302
+ return scoped[0];
303
+ }
304
+ if (options.allowBare && entries.includes(bareName))
305
+ return bareName;
306
+ return null;
307
+ }
308
+ /**
309
+ * Resolve which `*-VERIFICATION.md` entry in a phase directory's listing IS
310
+ * the phase's verification report, when more than one such file exists.
311
+ *
312
+ * #3357: a phase dir can legitimately hold more than one `*-VERIFICATION.md`
313
+ * — the real per-phase report (`03-VERIFICATION.md`) alongside an ad-hoc plan
314
+ * worksheet (`03-CORRECTION-VERIFICATION.md`). Picking "alphabetically first"
315
+ * (`'C' < 'V'`) silently chose the worksheet, which usually has no
316
+ * frontmatter `status:`, so a phase with a PASSING report read as `missing`.
317
+ * This was two independent hand-rolled `.sort()[0]` picks
318
+ * (findStaleVerificationSummary and readVerificationStatus) — this is the
319
+ * single resolver both now call (#3473 F2).
320
+ *
321
+ * Selection order: see `resolvePhaseArtifactFile` (the shared core this
322
+ * delegates to since #3518, itself phase-scoped since #3511) —
323
+ * phase-token-pinned, then phase-scoped alphabetically-first dashed
324
+ * fallback, then (allowBare only) a bare `VERIFICATION.md`. #3518 extracted
325
+ * this into the shared core without changing behavior; #3511's
326
+ * `phaseDirName` scoping now lives inside that shared core rather than here.
327
+ */
328
+ function resolveVerificationFile(entries, options = {}) {
329
+ return resolvePhaseArtifactFile(entries, 'VERIFICATION.md', options);
330
+ }
331
+ /**
332
+ * #3518: resolve which `*-UAT.md` entry in a phase directory's listing IS
333
+ * the phase's UAT artifact, when more than one such file exists — the UAT
334
+ * counterpart of `resolveVerificationFile`, sharing its exact selection rule
335
+ * via `resolvePhaseArtifactFile`.
336
+ *
337
+ * The bug this closes: both `uat_path` projectors in `src/init.cts` picked
338
+ * with a bare `.find((f) => f.endsWith('-UAT.md') || f === 'UAT.md')` over an
339
+ * unsorted `readdir` listing — no phase-membership check and no ordering — so
340
+ * a stray or cross-phase `04-UAT.md` sitting in phase 03's directory could
341
+ * become phase 03's `uat_path`, and WHICH file won was filesystem-dependent
342
+ * (creation order on APFS, hash order on ext4/XFS): two machines on the same
343
+ * commit could emit different `uat_path` values for the same phase. `uat_path`
344
+ * is consumed downstream by workflows that then read the named file, so a
345
+ * wrong path routes UAT state from another phase.
346
+ *
347
+ * Deterministic by construction: same answer on every machine. Phase-scoped
348
+ * (#3511): passing `options.phaseDirName` filters the alphabetically-first
349
+ * fallback (tier 2) to artifacts that belong to THIS phase — see
350
+ * `resolvePhaseArtifactFile` for the full selection order and scoping
351
+ * rationale.
352
+ */
353
+ function resolveUatFile(entries, options = {}) {
354
+ return resolvePhaseArtifactFile(entries, 'UAT.md', options);
355
+ }
214
356
  function findStaleVerificationSummary(phaseDir, fsImpl = node_fs_1.default, phaseCleanCommitTimesMs = defaultPhaseCleanCommitTimesMs) {
215
357
  // FS errors (TOCTOU: a SUMMARY listed by scanPhasePlans then removed before statSync;
216
358
  // unreadable dir; broken symlink; file->dir swap) must degrade rather than throw
@@ -223,7 +365,13 @@ function findStaleVerificationSummary(phaseDir, fsImpl = node_fs_1.default, phas
223
365
  // this function only reports what it actually knows.
224
366
  try {
225
367
  const phaseFiles = fsImpl.readdirSync(phaseDir);
226
- const verificationFile = phaseFiles.filter((f) => f.endsWith('-VERIFICATION.md')).sort()[0];
368
+ // #3492: pin selection to THIS phase's own token so a stray cross-phase
369
+ // or sentinel-numbered canonically-shaped file cannot outrank this
370
+ // phase's own (possibly non-canonical) report. #3511: phaseDirName scopes
371
+ // the fallback path to this same phase (see resolveVerificationFile docs).
372
+ const phaseDirName = node_path_1.default.basename(phaseDir);
373
+ const phaseToken = extractPhaseToken(phaseDirName);
374
+ const verificationFile = resolveVerificationFile(phaseFiles, { phaseToken, phaseDirName });
227
375
  if (!verificationFile)
228
376
  return { determined: true, stale: false };
229
377
  const summaryFiles = scanPhasePlans(phaseDir).summaryFiles
@@ -261,8 +409,10 @@ function findStaleVerificationSummary(phaseDir, fsImpl = node_fs_1.default, phas
261
409
  * phaseDir and return the routing result.
262
410
  *
263
411
  * Behavior:
264
- * 1. Find the first file matching `*-VERIFICATION.md` (sorted, take first).
265
- * If none → status 'missing'.
412
+ * 1. Find the phase's verification report via `resolveVerificationFile`
413
+ * (canonical `<phase-token>-VERIFICATION.md` preferred; falls back to the
414
+ * alphabetically-first `*-VERIFICATION.md` that belongs to THIS phase when
415
+ * none is canonical — #3357/#3511). If none → status 'missing'.
266
416
  * 2. Extract `status` from FRONTMATTER ONLY via the shared extractFrontmatter
267
417
  * parser (DEFECT.FRONTMATTER-SCALAR-BROAD-GREP fix — parser anchors at byte 0).
268
418
  * If no frontmatter block or no `status` key → status 'missing'.
@@ -299,8 +449,12 @@ function readVerificationStatus(phaseDir, opts = {}) {
299
449
  let verificationFile = null;
300
450
  try {
301
451
  const entries = fsImpl.readdirSync(phaseDir);
302
- const candidates = entries.filter((f) => f.endsWith('-VERIFICATION.md')).sort();
303
- verificationFile = candidates.length > 0 ? candidates[0] : null;
452
+ // #3492: pin selection to THIS phase's own token (already derived above
453
+ // for the routed command argument) so a stray cross-phase or
454
+ // sentinel-numbered canonically-shaped file cannot outrank this phase's
455
+ // own (possibly non-canonical) report. #3511: baseName also scopes the
456
+ // fallback path to this same phase (see resolveVerificationFile docs).
457
+ verificationFile = resolveVerificationFile(entries, { phaseToken, phaseDirName: baseName });
304
458
  }
305
459
  catch {
306
460
  // Directory unreadable → treat as missing
@@ -374,11 +528,63 @@ function readVerificationStatus(phaseDir, opts = {}) {
374
528
  const unknownRoute = VERIFICATION_ROUTING_TABLE['unknown'];
375
529
  return {
376
530
  status: unknownRoute.status,
377
- next_action: `Unexpected verification status '${rawStatus}'. Re-run execute-phase verification.`,
531
+ next_action: `Unexpected verification status '${rawStatus}'. If this is an intentional non-standard marker (e.g. a hand-set failed/superseded state), no action is needed. Otherwise, run execute-phase to regenerate verification — it will not re-run plans that already have a SUMMARY.md.`,
378
532
  next_command: projectNextCommand(unknownRoute.next_command, runtime, phaseArg),
379
533
  ...(staleCheckIndeterminate ? { staleCheckIndeterminate: true } : {}),
380
534
  };
381
535
  }
536
+ /**
537
+ * isPhaseComplete — the single canonical owner of "is phase P complete?"
538
+ * (ADR-3180 §7.4, Decision 1). Sited beside readVerificationStatus, which it
539
+ * wraps.
540
+ *
541
+ * DISK-STRICT (#2957, maintainer decision 2026-08-08; ADR-3180 §7.4 amended
542
+ * af92fd4c9): readVerificationStatus is called UNCONDITIONALLY here — plan
543
+ * count is NOT a precondition. A phase with zero plans and a passing
544
+ * `*-VERIFICATION.md` is complete (#3168). A ROADMAP checkbox has no machine
545
+ * authority and is never consulted — this function never reads ROADMAP.md.
546
+ *
547
+ * `complete` is exactly `verification.status === 'passed'`. `verification`
548
+ * carries the FULL routing result (status/next_action/next_command), so a
549
+ * caller can distinguish a failing verdict (`gaps_found`/`human_needed`/
550
+ * `stale`/`unknown`) from an absent one (`missing`) — both are "not
551
+ * complete", but they are not the same non-answer.
552
+ *
553
+ * `scope` is UNREADABLE when `phaseDir` itself could not be listed — this is
554
+ * INDEPENDENT of readVerificationStatus's own no-throw fail-open contract for
555
+ * a missing `*-VERIFICATION.md` file (a well-formed answer,
556
+ * `verification.status === 'missing'`, scope COMPLETE): a caller must not
557
+ * read `value.complete: false` here as a confident "not complete" the way it
558
+ * can for a genuinely-checked missing file.
559
+ *
560
+ * Does NOT import scanPhasePlans / plan-scan.cjs — the owner consumes plan
561
+ * counts from its caller when a caller needs them for a different question
562
+ * (e.g. buildPhaseCompletionProjection's own `implementation_complete`); it
563
+ * never re-derives or requires them itself.
564
+ */
565
+ function isPhaseComplete(phaseDir, deps = {}) {
566
+ const fsImpl = deps.fs ?? node_fs_1.default;
567
+ let readable = true;
568
+ try {
569
+ fsImpl.readdirSync(phaseDir);
570
+ }
571
+ catch {
572
+ readable = false;
573
+ }
574
+ const verification = readVerificationStatus(phaseDir, {
575
+ fs: deps.fs,
576
+ phaseCleanCommitTimesMs: deps.phaseCleanCommitTimesMs,
577
+ runtime: deps.runtime,
578
+ phaseNumber: deps.phaseNumber,
579
+ });
580
+ return {
581
+ value: {
582
+ complete: verification.status === 'passed',
583
+ verification,
584
+ },
585
+ scope: readable ? SCOPE.COMPLETE : SCOPE.UNREADABLE,
586
+ };
587
+ }
382
588
  /**
383
589
  * CLI command handler: resolve phaseDir against cwd, call readVerificationStatus,
384
590
  * emit via io.output().
@@ -396,11 +602,55 @@ function cmdVerificationStatus(cwd, phaseDirArg, raw) {
396
602
  const result = readVerificationStatus(phaseDir, { runtime: (0, runtime_slash_cjs_1.resolveRuntime)(cwd) });
397
603
  output(result, raw);
398
604
  }
605
+ /**
606
+ * CLI command handler: resolve which `*-VERIFICATION.md` in `phaseDirArg` is
607
+ * the phase's own report, via the shared `resolveVerificationFile` seam, and
608
+ * emit its absolute path.
609
+ *
610
+ * #3492 F3: the ONE seam shell callers (verify-work.md's writer, transition.md's
611
+ * awk reader) route through instead of hand-rolling `ls *-VERIFICATION.md |
612
+ * head -1` / an awk glob scan — both of which pick alphabetically-first and so
613
+ * diverge from every JS reader now pinned to the phase's own token.
614
+ *
615
+ * Emits `{ verification_file: "<absolute path>" | "" }` (empty when no
616
+ * candidate resolves, including an unreadable directory). `raw` emits the
617
+ * bare path string (possibly empty) so `VAR=$(gsd_run query
618
+ * verification.resolve-file "$PHASE_DIR" --raw)` is directly assignable.
619
+ *
620
+ * @param cwd - Current working directory (used to resolve phaseDirArg).
621
+ * @param phaseDirArg - Phase directory path (absolute or relative to cwd).
622
+ * @param raw - Whether to emit raw (non-JSON) output.
623
+ */
624
+ function cmdVerificationResolveFile(cwd, phaseDirArg, raw) {
625
+ if (!phaseDirArg) {
626
+ error('phase directory required for verification.resolve-file');
627
+ return;
628
+ }
629
+ const phaseDir = node_path_1.default.resolve(cwd, phaseDirArg);
630
+ let verificationPath = '';
631
+ try {
632
+ const entries = node_fs_1.default.readdirSync(phaseDir);
633
+ const phaseDirName = node_path_1.default.basename(phaseDir);
634
+ const phaseToken = extractPhaseToken(phaseDirName);
635
+ const verificationFile = resolveVerificationFile(entries, { allowBare: true, phaseToken, phaseDirName });
636
+ if (verificationFile) {
637
+ verificationPath = node_path_1.default.join(phaseDir, verificationFile);
638
+ }
639
+ }
640
+ catch {
641
+ verificationPath = '';
642
+ }
643
+ output({ verification_file: verificationPath }, raw, verificationPath);
644
+ }
399
645
  module.exports = {
400
646
  VERIFIER_STATUSES,
401
647
  VERIFICATION_ROUTING_TABLE,
402
648
  defaultPhaseCleanCommitTimesMs,
649
+ resolveVerificationFile,
650
+ resolveUatFile,
403
651
  findStaleVerificationSummary,
404
652
  readVerificationStatus,
653
+ isPhaseComplete,
405
654
  cmdVerificationStatus,
655
+ cmdVerificationResolveFile,
406
656
  };