@opengsd/gsd-core 1.9.1 → 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 (426) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +2 -3
  3. package/.opencode/plugins/gsd-core.js +8 -1
  4. package/agents/gsd-code-fixer.md +27 -3
  5. package/agents/gsd-debug-session-manager.md +11 -0
  6. package/agents/gsd-debugger.md +12 -246
  7. package/agents/gsd-doc-synthesizer.md +2 -4
  8. package/agents/gsd-executor.md +12 -10
  9. package/agents/gsd-integration-checker.md +3 -0
  10. package/agents/gsd-mempalace-curator.md +5 -2
  11. package/agents/gsd-phase-researcher.md +20 -1
  12. package/agents/gsd-plan-checker.md +46 -0
  13. package/agents/gsd-planner.md +49 -54
  14. package/agents/gsd-roadmapper.md +21 -3
  15. package/agents/gsd-user-profiler.md +3 -0
  16. package/agents/gsd-verifier.md +26 -73
  17. package/bin/install.js +1272 -1238
  18. package/bin/lib/ui-safety-gate.cjs +2 -0
  19. package/commands/gsd/code-review.md +1 -1
  20. package/commands/gsd/execute-phase.md +1 -1
  21. package/commands/gsd/map-codebase.md +1 -1
  22. package/commands/gsd/mempalace-capture.md +2 -2
  23. package/commands/gsd/mempalace-recall.md +1 -1
  24. package/commands/gsd/new-milestone.md +2 -2
  25. package/commands/gsd/plan-phase.md +1 -1
  26. package/commands/gsd/quick.md +1 -1
  27. package/commands/gsd/review-backlog.md +2 -1
  28. package/commands/gsd/verify-work.md +1 -1
  29. package/gsd-core/bin/gsd-tools.cjs +1009 -115
  30. package/gsd-core/bin/lib/active-workstream-store.cjs +153 -12
  31. package/gsd-core/bin/lib/agent-install-check.cjs +268 -38
  32. package/gsd-core/bin/lib/api-coverage.cjs +123 -5
  33. package/gsd-core/bin/lib/artifacts.cjs +3 -0
  34. package/gsd-core/bin/lib/assumption-delta.cjs +2 -4
  35. package/gsd-core/bin/lib/audit-command-router.cjs +9 -2
  36. package/gsd-core/bin/lib/audit.cjs +926 -202
  37. package/gsd-core/bin/lib/broken-windows.cjs +36 -6
  38. package/gsd-core/bin/lib/capability-consent.cjs +149 -15
  39. package/gsd-core/bin/lib/capability-lifecycle.cjs +45 -0
  40. package/gsd-core/bin/lib/capability-registry.cjs +608 -148
  41. package/gsd-core/bin/lib/capability-source.cjs +92 -0
  42. package/gsd-core/bin/lib/capability-trust.cjs +444 -25
  43. package/gsd-core/bin/lib/capability-validator.cjs +507 -24
  44. package/gsd-core/bin/lib/capability-writer.cjs +3 -2
  45. package/gsd-core/bin/lib/check-command-router.cjs +114 -38
  46. package/gsd-core/bin/lib/claude-orchestration.cjs +56 -3
  47. package/gsd-core/bin/lib/codex-agent-toml.cjs +329 -0
  48. package/gsd-core/bin/lib/command-aliases.cjs +94 -0
  49. package/gsd-core/bin/lib/command-roster.cjs +44 -1
  50. package/gsd-core/bin/lib/commands.cjs +665 -99
  51. package/gsd-core/bin/lib/commonjs-marker.cjs +142 -0
  52. package/gsd-core/bin/lib/complexity-trigger.cjs +1172 -0
  53. package/gsd-core/bin/lib/config-loader.cjs +76 -0
  54. package/gsd-core/bin/lib/config.cjs +22 -2
  55. package/gsd-core/bin/lib/context-composer.cjs +278 -0
  56. package/gsd-core/bin/lib/context-predicates.cjs +506 -0
  57. package/gsd-core/bin/lib/core-utils.cjs +217 -40
  58. package/gsd-core/bin/lib/decisions.cjs +23 -0
  59. package/gsd-core/bin/lib/docs.cjs +3 -2
  60. package/gsd-core/bin/lib/external-job.cjs +19 -4
  61. package/gsd-core/bin/lib/fallow-runner.cjs +20 -44
  62. package/gsd-core/bin/lib/frontmatter.cjs +239 -32
  63. package/gsd-core/bin/lib/gap-checker.cjs +68 -7
  64. package/gsd-core/bin/lib/gate-predicate-evaluator.cjs +57 -6
  65. package/gsd-core/bin/lib/git-base-branch.cjs +160 -15
  66. package/gsd-core/bin/lib/graphify.cjs +142 -27
  67. package/gsd-core/bin/lib/gsd2-import.cjs +37 -5
  68. package/gsd-core/bin/lib/health-diagnostic-rules/agent-install.cjs +101 -0
  69. package/gsd-core/bin/lib/health-diagnostic-rules/config-validation.cjs +348 -0
  70. package/gsd-core/bin/lib/health-diagnostic-rules/consistency.cjs +145 -0
  71. package/gsd-core/bin/lib/health-diagnostic-rules/install-surface-shadowing.cjs +98 -0
  72. package/gsd-core/bin/lib/health-diagnostic-rules/milestone-archive-hygiene.cjs +100 -0
  73. package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +222 -0
  74. package/gsd-core/bin/lib/health-diagnostic-rules/roadmap-disk-consistency.cjs +265 -0
  75. package/gsd-core/bin/lib/health-diagnostic-rules/root-existence.cjs +161 -0
  76. package/gsd-core/bin/lib/health-diagnostic-rules/state-consistency.cjs +303 -0
  77. package/gsd-core/bin/lib/health-diagnostic-rules/worktree-health.cjs +173 -0
  78. package/gsd-core/bin/lib/health-diagnostic-types.cjs +68 -0
  79. package/gsd-core/bin/lib/health-diagnostic.cjs +431 -0
  80. package/gsd-core/bin/lib/host-integration.cjs +13 -1
  81. package/gsd-core/bin/lib/host-runtime-detection.cjs +134 -0
  82. package/gsd-core/bin/lib/init-command-router.cjs +83 -8
  83. package/gsd-core/bin/lib/init.cjs +1325 -169
  84. package/gsd-core/bin/lib/install-effort-resolver.cjs +73 -30
  85. package/gsd-core/bin/lib/install-engine.cjs +805 -264
  86. package/gsd-core/bin/lib/install-fs-adapter.cjs +262 -0
  87. package/gsd-core/bin/lib/install-model-override-resolver.cjs +203 -0
  88. package/gsd-core/bin/lib/install-profiles.cjs +160 -57
  89. package/gsd-core/bin/lib/install-scope.cjs +270 -0
  90. package/gsd-core/bin/lib/install-shadow-report.cjs +385 -0
  91. package/gsd-core/bin/lib/installed-surface-resolver.cjs +381 -0
  92. package/gsd-core/bin/lib/installer-migration-authoring.cjs +3 -1
  93. package/gsd-core/bin/lib/installer-migration-report.cjs +4 -0
  94. package/gsd-core/bin/lib/installer-migrations/007-retire-config-root-commonjs-marker.cjs +149 -0
  95. package/gsd-core/bin/lib/installer-migrations/008-cursor-retire-commands-surface.cjs +55 -0
  96. package/gsd-core/bin/lib/installer-migrations/009-pi-retire-reserved-hooks-dir.cjs +199 -0
  97. package/gsd-core/bin/lib/installer-migrations.cjs +206 -13
  98. package/gsd-core/bin/lib/io.cjs +38 -3
  99. package/gsd-core/bin/lib/markdown-sectionizer.cjs +8 -1
  100. package/gsd-core/bin/lib/markdown-table.cjs +133 -20
  101. package/gsd-core/bin/lib/mcp-catalog.cjs +518 -0
  102. package/gsd-core/bin/lib/mcp-server.cjs +135 -3
  103. package/gsd-core/bin/lib/milestone-lock.cjs +248 -0
  104. package/gsd-core/bin/lib/milestone.cjs +821 -109
  105. package/gsd-core/bin/lib/model-catalog.cjs +59 -1
  106. package/gsd-core/bin/lib/model-resolver.cjs +183 -40
  107. package/gsd-core/bin/lib/normalize-test-command.cjs +1 -1
  108. package/gsd-core/bin/lib/pattern.cjs +122 -0
  109. package/gsd-core/bin/lib/phase-estimation.cjs +1 -1
  110. package/gsd-core/bin/lib/phase-id.cjs +507 -36
  111. package/gsd-core/bin/lib/phase-lifecycle.cjs +28 -3
  112. package/gsd-core/bin/lib/phase-locator.cjs +258 -58
  113. package/gsd-core/bin/lib/phase.cjs +891 -156
  114. package/gsd-core/bin/lib/plan-dependency-graph.cjs +303 -0
  115. package/gsd-core/bin/lib/plan-drift-guard.cjs +120 -0
  116. package/gsd-core/bin/lib/plan-scan.cjs +86 -2
  117. package/gsd-core/bin/lib/planning-scope.cjs +31 -0
  118. package/gsd-core/bin/lib/planning-snapshot.cjs +890 -0
  119. package/gsd-core/bin/lib/planning-workspace.cjs +60 -6
  120. package/gsd-core/bin/lib/probe-core.cjs +1 -1
  121. package/gsd-core/bin/lib/profile-output.cjs +1 -1
  122. package/gsd-core/bin/lib/prompt-budget.cjs +128 -165
  123. package/gsd-core/bin/lib/refactor-trigger-command-router.cjs +740 -0
  124. package/gsd-core/bin/lib/retired-artifact-cleanup.cjs +85 -0
  125. package/gsd-core/bin/lib/review-lane-descriptor.cjs +108 -0
  126. package/gsd-core/bin/lib/review-lane-invocation.cjs +30 -0
  127. package/gsd-core/bin/lib/review-lane-runner.cjs +447 -68
  128. package/gsd-core/bin/lib/review-reviewer-selection.cjs +13 -18
  129. package/gsd-core/bin/lib/roadmap-command-router.cjs +76 -9
  130. package/gsd-core/bin/lib/roadmap-parser.cjs +1035 -194
  131. package/gsd-core/bin/lib/roadmap-upgrade.cjs +37 -10
  132. package/gsd-core/bin/lib/roadmap.cjs +405 -84
  133. package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +795 -100
  134. package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +14 -2
  135. package/gsd-core/bin/lib/runtime-artifact-layout.cjs +440 -57
  136. package/gsd-core/bin/lib/runtime-config-adapter-registry.cjs +3 -2
  137. package/gsd-core/bin/lib/runtime-homes.cjs +220 -41
  138. package/gsd-core/bin/lib/runtime-hooks-surface.cjs +220 -44
  139. package/gsd-core/bin/lib/runtime-name-policy.cjs +3 -1
  140. package/gsd-core/bin/lib/runtime-slash.cjs +27 -9
  141. package/gsd-core/bin/lib/section-manifest.cjs +209 -0
  142. package/gsd-core/bin/lib/security.cjs +104 -5
  143. package/gsd-core/bin/lib/shell-command-projection.cjs +388 -30
  144. package/gsd-core/bin/lib/smart-entry.cjs +154 -22
  145. package/gsd-core/bin/lib/state-command-router.cjs +5 -1
  146. package/gsd-core/bin/lib/state-document.cjs +152 -8
  147. package/gsd-core/bin/lib/state-transition.cjs +424 -105
  148. package/gsd-core/bin/lib/state.cjs +1927 -401
  149. package/gsd-core/bin/lib/surface.cjs +35 -10
  150. package/gsd-core/bin/lib/text-lines.cjs +80 -0
  151. package/gsd-core/bin/lib/token-scanner.cjs +76 -0
  152. package/gsd-core/bin/lib/uat-predicate.cjs +20 -4
  153. package/gsd-core/bin/lib/uat.cjs +706 -64
  154. package/gsd-core/bin/lib/ui-frontend-evidence.cjs +157 -0
  155. package/gsd-core/bin/lib/ui-safety-gate.cjs +14 -5
  156. package/gsd-core/bin/lib/unusable-input.cjs +33 -0
  157. package/gsd-core/bin/lib/update-context.cjs +8 -2
  158. package/gsd-core/bin/lib/user-artifact-staging.cjs +705 -0
  159. package/gsd-core/bin/lib/validate.cjs +20 -6
  160. package/gsd-core/bin/lib/vendor/README.md +37 -0
  161. package/gsd-core/bin/lib/vendor/re2js.cjs +6480 -0
  162. package/gsd-core/bin/lib/vendor/re2js.d.cts +938 -0
  163. package/gsd-core/bin/lib/verification-command-router.cjs +2 -1
  164. package/gsd-core/bin/lib/verification.cjs +287 -20
  165. package/gsd-core/bin/lib/verify.cjs +368 -880
  166. package/gsd-core/bin/lib/workflow-fragments.cjs +557 -0
  167. package/gsd-core/bin/lib/workstream-inventory-builder.cjs +203 -19
  168. package/gsd-core/bin/lib/workstream-inventory.cjs +576 -31
  169. package/gsd-core/bin/lib/workstream.cjs +8 -2
  170. package/gsd-core/bin/lib/worktree-base-ref.cjs +50 -6
  171. package/gsd-core/bin/lib/worktree-safety.cjs +450 -125
  172. package/gsd-core/bin/shared/config-defaults.manifest.json +1 -0
  173. package/gsd-core/bin/shared/config-schema.manifest.json +9 -1
  174. package/gsd-core/references/agent-contracts.md +43 -26
  175. package/gsd-core/references/artifact-types.md +10 -3
  176. package/gsd-core/references/autonomous-ui-design-contract.md +42 -0
  177. package/gsd-core/references/checkpoints.md +2 -2
  178. package/gsd-core/references/context-budget.md +1 -1
  179. package/gsd-core/references/debugger-techniques.md +255 -0
  180. package/gsd-core/references/dispatch-isolation-gate.md +138 -0
  181. package/gsd-core/references/doc-conflict-engine.md +1 -1
  182. package/gsd-core/references/execute-mvp-tdd.md +3 -3
  183. package/gsd-core/references/execute-phase-between-wave-reset.md +6 -2
  184. package/gsd-core/references/execute-phase-context-guard.md +1 -1
  185. package/gsd-core/references/execute-phase-response-language.md +1 -1
  186. package/gsd-core/references/execute-phase-wave-guard.md +6 -2
  187. package/gsd-core/references/gate-prompts.md +1 -1
  188. package/gsd-core/references/git-planning-commit.md +2 -1
  189. package/gsd-core/references/loop-hook-dispatch.md +39 -2
  190. package/gsd-core/references/model-profiles.md +12 -4
  191. package/gsd-core/references/mvp-concepts.md +9 -9
  192. package/gsd-core/references/planner-guidance.md +3 -9
  193. package/gsd-core/references/planner-preconditions.md +1 -1
  194. package/gsd-core/references/planner-reviews.md +1 -1
  195. package/gsd-core/references/planning-config.md +8 -6
  196. package/gsd-core/references/research-documentation-lookup.md +5 -3
  197. package/gsd-core/references/revision-loop.md +1 -1
  198. package/gsd-core/references/specless-probe-fallback.md +8 -7
  199. package/gsd-core/references/universal-anti-patterns.md +3 -3
  200. package/gsd-core/references/verifier-phase-gates.md +192 -0
  201. package/gsd-core/references/verifier-wiring-patterns.md +100 -0
  202. package/gsd-core/references/verify-mvp-mode.md +1 -1
  203. package/gsd-core/references/workstream-flag.md +22 -6
  204. package/gsd-core/references/worktree-branch-check.md +2 -2
  205. package/gsd-core/templates/discussion-log.md +1 -1
  206. package/gsd-core/templates/phase-prompt.md +2 -4
  207. package/gsd-core/templates/state.md +4 -4
  208. package/gsd-core/templates/summary-complex.md +2 -0
  209. package/gsd-core/templates/summary-minimal.md +2 -0
  210. package/gsd-core/templates/summary-standard.md +2 -0
  211. package/gsd-core/templates/summary.md +2 -0
  212. package/gsd-core/templates/verification-report.md +9 -1
  213. package/gsd-core/workflows/ai-integration-phase.md +9 -11
  214. package/gsd-core/workflows/audit-milestone.md +3 -0
  215. package/gsd-core/workflows/autonomous/steps/converge-banner.md +1 -0
  216. package/gsd-core/workflows/autonomous/steps/converge-dispatch-bg.md +11 -0
  217. package/gsd-core/workflows/autonomous/steps/converge-dispatch-inline.md +7 -0
  218. package/gsd-core/workflows/autonomous/steps/converge-fail-fast.md +21 -0
  219. package/gsd-core/workflows/autonomous/steps/converge-loop.md +7 -0
  220. package/gsd-core/workflows/autonomous.md +33 -70
  221. package/gsd-core/workflows/cleanup.md +62 -3
  222. package/gsd-core/workflows/code-review/steps/dispatch-fix.md +39 -0
  223. package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +93 -0
  224. package/gsd-core/workflows/code-review-fix.md +37 -10
  225. package/gsd-core/workflows/code-review.md +74 -166
  226. package/gsd-core/workflows/complete-milestone/steps/git-tag.md +29 -0
  227. package/gsd-core/workflows/complete-milestone.md +160 -95
  228. package/gsd-core/workflows/debug.md +16 -17
  229. package/gsd-core/workflows/diagnose-issues.md +56 -8
  230. package/gsd-core/workflows/discuss-phase/modes/chain.md +2 -1
  231. package/gsd-core/workflows/discuss-phase/modes/default.md +1 -1
  232. package/gsd-core/workflows/discuss-phase-assumptions/steps/auto-advance-dispatch.md +15 -0
  233. package/gsd-core/workflows/discuss-phase-assumptions.md +7 -17
  234. package/gsd-core/workflows/docs-update/steps/dispatch-monorepo-packages.md +51 -0
  235. package/gsd-core/workflows/docs-update.md +8 -51
  236. package/gsd-core/workflows/edit-phase.md +26 -1
  237. package/gsd-core/workflows/eval-review.md +3 -5
  238. package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +64 -7
  239. package/gsd-core/workflows/execute-phase/steps/gap-closure-artifacts.md +50 -0
  240. package/gsd-core/workflows/execute-phase/steps/partial-wave.md +31 -0
  241. package/gsd-core/workflows/execute-phase/steps/per-plan-executor-routing.md +77 -0
  242. package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +21 -0
  243. package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +42 -0
  244. package/gsd-core/workflows/execute-phase/steps/regression-gate.md +43 -37
  245. package/gsd-core/workflows/execute-phase.md +103 -187
  246. package/gsd-core/workflows/execute-plan.md +36 -4
  247. package/gsd-core/workflows/explore.md +131 -4
  248. package/gsd-core/workflows/fast.md +10 -2
  249. package/gsd-core/workflows/health.md +73 -4
  250. package/gsd-core/workflows/help/modes/full.md +6 -1
  251. package/gsd-core/workflows/import.md +4 -4
  252. package/gsd-core/workflows/ingest-docs.md +7 -6
  253. package/gsd-core/workflows/mvp-phase.md +6 -3
  254. package/gsd-core/workflows/new-milestone/steps/project-md-milestone-write.md +16 -0
  255. package/gsd-core/workflows/new-milestone/steps/reset-phase-safety.md +19 -0
  256. package/gsd-core/workflows/new-milestone.md +35 -47
  257. package/gsd-core/workflows/new-project/steps/auto-mode-config.md +176 -0
  258. package/gsd-core/workflows/new-project/steps/auto-mode-detection.md +32 -0
  259. package/gsd-core/workflows/new-project/steps/codebase-map-offer.md +18 -0
  260. package/gsd-core/workflows/new-project.md +27 -240
  261. package/gsd-core/workflows/next.md +12 -0
  262. package/gsd-core/workflows/plan-phase/steps/adr-ingest-express-path.md +15 -0
  263. package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +110 -0
  264. package/gsd-core/workflows/plan-phase/steps/prd-express-gate.md +8 -0
  265. package/gsd-core/workflows/plan-phase/steps/research-only-early-exit.md +17 -0
  266. package/gsd-core/workflows/plan-phase/steps/research-only-modifiers.md +16 -0
  267. package/gsd-core/workflows/plan-phase/steps/reviews-prerequisite.md +17 -0
  268. package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +149 -0
  269. package/gsd-core/workflows/plan-phase.md +89 -209
  270. package/gsd-core/workflows/plan-review-convergence.md +50 -2
  271. package/gsd-core/workflows/progress/steps/forensic-audit.md +125 -0
  272. package/gsd-core/workflows/progress/steps/mvp-display.md +18 -0
  273. package/gsd-core/workflows/progress.md +45 -159
  274. package/gsd-core/workflows/quick/steps/discussion-phase.md +124 -0
  275. package/gsd-core/workflows/quick/steps/plan-checker-loop.md +111 -0
  276. package/gsd-core/workflows/quick/steps/quick-verification.md +67 -0
  277. package/gsd-core/workflows/quick/steps/research-phase.md +72 -0
  278. package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +37 -0
  279. package/gsd-core/workflows/quick.md +55 -405
  280. package/gsd-core/workflows/resume-project.md +3 -0
  281. package/gsd-core/workflows/review/steps/reviewer-instances-note-1.md +4 -0
  282. package/gsd-core/workflows/review/steps/reviewer-instances-note-2.md +3 -0
  283. package/gsd-core/workflows/review.md +41 -13
  284. package/gsd-core/workflows/section-manifest.json +219 -0
  285. package/gsd-core/workflows/secure-phase.md +1 -1
  286. package/gsd-core/workflows/session-report.md +2 -1
  287. package/gsd-core/workflows/settings.md +66 -2
  288. package/gsd-core/workflows/ship.md +104 -44
  289. package/gsd-core/workflows/sketch.md +1 -1
  290. package/gsd-core/workflows/spec-phase.md +41 -20
  291. package/gsd-core/workflows/spike-wrap-up.md +20 -5
  292. package/gsd-core/workflows/spike.md +50 -16
  293. package/gsd-core/workflows/sync-skills.md +106 -13
  294. package/gsd-core/workflows/transition/steps/workstream-collision-check.md +17 -0
  295. package/gsd-core/workflows/transition.md +53 -31
  296. package/gsd-core/workflows/ui-phase.md +13 -12
  297. package/gsd-core/workflows/ui-review.md +2 -2
  298. package/gsd-core/workflows/update/steps/channel-banner.md +7 -0
  299. package/gsd-core/workflows/update.md +19 -8
  300. package/gsd-core/workflows/validate-phase.md +1 -1
  301. package/gsd-core/workflows/verify-work/steps/automated-ui-verification.md +36 -0
  302. package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +21 -0
  303. package/gsd-core/workflows/verify-work.md +17 -65
  304. package/hooks/dist/gsd-agent-isolation-guard.js +517 -0
  305. package/hooks/dist/gsd-check-update-worker.js +64 -12
  306. package/hooks/dist/gsd-check-update.js +19 -1
  307. package/hooks/dist/gsd-cursor-pre-tool.js +0 -3
  308. package/hooks/dist/gsd-cursor-subagent-start.js +607 -26
  309. package/hooks/dist/gsd-cursor-subagent-stop.js +3 -2
  310. package/hooks/dist/gsd-prompt-guard.js +21 -20
  311. package/hooks/dist/gsd-read-injection-scanner.js +45 -24
  312. package/hooks/dist/gsd-statusline.js +90 -6
  313. package/hooks/dist/gsd-update-banner.js +22 -1
  314. package/hooks/dist/gsd-workflow-guard.js +134 -36
  315. package/hooks/dist/gsd-worktree-path-guard.js +2 -1
  316. package/hooks/dist/gsd-write-guard.js +359 -0
  317. package/hooks/dist/lib/git-cmd.js +92 -59
  318. package/hooks/dist/lib/injection-patterns.js +45 -0
  319. package/hooks/dist/lib/isolation-deny-reason.js +39 -0
  320. package/hooks/dist/lib/isolation-sentinel.js +277 -0
  321. package/hooks/dist/managed-hooks-registry.cjs +2 -0
  322. package/hooks/gsd-agent-isolation-guard.js +517 -0
  323. package/hooks/gsd-check-update-worker.js +64 -12
  324. package/hooks/gsd-check-update.js +19 -1
  325. package/hooks/gsd-cursor-pre-tool.js +0 -3
  326. package/hooks/gsd-cursor-subagent-start.js +607 -26
  327. package/hooks/gsd-cursor-subagent-stop.js +3 -2
  328. package/hooks/gsd-prompt-guard.js +21 -20
  329. package/hooks/gsd-read-injection-scanner.js +45 -24
  330. package/hooks/gsd-statusline.js +90 -6
  331. package/hooks/gsd-update-banner.js +22 -1
  332. package/hooks/gsd-workflow-guard.js +134 -36
  333. package/hooks/gsd-worktree-path-guard.js +2 -1
  334. package/hooks/gsd-write-guard.js +359 -0
  335. package/hooks/hooks.json +12 -0
  336. package/hooks/lib/git-cmd.js +92 -59
  337. package/hooks/lib/injection-patterns.js +45 -0
  338. package/hooks/lib/isolation-deny-reason.js +39 -0
  339. package/hooks/lib/isolation-sentinel.js +277 -0
  340. package/hooks/managed-hooks-registry.cjs +2 -0
  341. package/package.json +31 -10
  342. package/pi/gsd.cjs +71 -12
  343. package/scripts/baselines/planning-prompt-drift-baseline.json +4 -0
  344. package/scripts/baselines/planning-snapshot-bypass-baseline.json +12 -0
  345. package/scripts/baselines/unreachable-guard-drift-baseline.json +4 -0
  346. package/scripts/build-hooks.js +9 -0
  347. package/scripts/changeset/lint.cjs +68 -6
  348. package/scripts/changeset/serialize.cjs +5 -1
  349. package/scripts/check-alias-drift.cjs +7 -43
  350. package/scripts/check-contract-drift.cjs +297 -0
  351. package/scripts/ci-test-scope.cjs +19 -2
  352. package/scripts/command-contract-helpers.cjs +903 -1
  353. package/scripts/gen-adr-index.cjs +728 -38
  354. package/scripts/gen-capability-matrix.cjs +1 -1
  355. package/scripts/gen-capability-registry.cjs +3 -15
  356. package/scripts/gen-context-index.cjs +439 -0
  357. package/scripts/gen-health-docs.cjs +390 -0
  358. package/scripts/gen-inventory-manifest.cjs +150 -4
  359. package/scripts/gen-loop-host-contract.cjs +4 -24
  360. package/scripts/gen-prompt-budget-parity-corpus.cjs +645 -0
  361. package/scripts/gen-registry.cjs +3 -14
  362. package/scripts/gen-section-manifest.cjs +638 -0
  363. package/scripts/generate-package-identity.cjs +4 -2
  364. package/scripts/lib/alias-drift-families.cjs +46 -0
  365. package/scripts/lib/drift-scan.cjs +278 -0
  366. package/scripts/lint-allow-test-rule-refs.allowlist.json +15 -54
  367. package/scripts/lint-allow-test-rule-refs.effective-ceiling.json +4 -0
  368. package/scripts/lint-allow-test-rule-refs.unverified-ceiling.json +3 -0
  369. package/scripts/lint-canary-version-leak.cjs +73 -0
  370. package/scripts/lint-command-contract.cjs +96 -13
  371. package/scripts/lint-compiled-artifact-sync.cjs +6 -1
  372. package/scripts/lint-completion-predicate-drift.cjs +933 -0
  373. package/scripts/lint-completion-ratio-drift.cjs +214 -0
  374. package/scripts/lint-default-flip-documentation.cjs +193 -0
  375. package/scripts/lint-docs-command-form.cjs +195 -0
  376. package/scripts/lint-docs-required.cjs +9 -1
  377. package/scripts/lint-emitted-drift-ack.cjs +215 -20
  378. package/scripts/lint-eslint-glob-coverage.allowlist.json +34 -0
  379. package/scripts/lint-eslint-glob-coverage.cjs +340 -0
  380. package/scripts/lint-example-parser-parity.cjs +395 -0
  381. package/scripts/lint-frontmatter-scalar-broad-grep.cjs +237 -0
  382. package/scripts/lint-health-diagnostic-rule-table.cjs +404 -0
  383. package/scripts/lint-hooks-runtime-build-seam.cjs +262 -0
  384. package/scripts/lint-milestone-window-drift.cjs +468 -0
  385. package/scripts/lint-phase-enumeration-drift.cjs +479 -0
  386. package/scripts/lint-plan-count-drift.cjs +318 -0
  387. package/scripts/lint-planning-artifact-writer-drift.cjs +398 -0
  388. package/scripts/lint-planning-prompt-drift.cjs +434 -0
  389. package/scripts/lint-planning-snapshot-bypass-drift.cjs +544 -0
  390. package/scripts/lint-regression-test-names.cjs +15 -13
  391. package/scripts/lint-removed-but-needed.cjs +320 -0
  392. package/scripts/lint-state-field-drift.cjs +805 -0
  393. package/scripts/lint-state-write-path-drift.cjs +1045 -0
  394. package/scripts/lint-test-file-count.allowlist.json +40 -3
  395. package/scripts/lint-unreachable-guard-drift.cjs +843 -0
  396. package/scripts/lint-vendored-deps.cjs +124 -0
  397. package/scripts/mutation-matrix.cjs +13 -0
  398. package/scripts/pr-changed-files.cjs +63 -0
  399. package/scripts/pr-template-policy.cjs +14 -4
  400. package/scripts/prompt-injection-scan.sh +52 -6
  401. package/scripts/require-issue-link-policy.cjs +192 -0
  402. package/scripts/state-write-path-drift-baseline.json +19 -0
  403. package/scripts/sync-runtime-launcher.cjs +2 -4
  404. package/skills/gsd-autonomous/SKILL.md +0 -1
  405. package/skills/gsd-code-review/SKILL.md +1 -1
  406. package/skills/gsd-execute-phase/SKILL.md +1 -2
  407. package/skills/gsd-map-codebase/SKILL.md +1 -1
  408. package/skills/gsd-mempalace-capture/SKILL.md +2 -2
  409. package/skills/gsd-mempalace-recall/SKILL.md +1 -1
  410. package/skills/gsd-new-milestone/SKILL.md +2 -2
  411. package/skills/gsd-next/SKILL.md +0 -1
  412. package/skills/gsd-plan-phase/SKILL.md +1 -2
  413. package/skills/gsd-progress/SKILL.md +0 -1
  414. package/skills/gsd-quick/SKILL.md +1 -1
  415. package/skills/gsd-review-backlog/SKILL.md +2 -1
  416. package/skills/gsd-stats/SKILL.md +0 -1
  417. package/skills/gsd-verify-work/SKILL.md +1 -1
  418. package/vscode/package.json +1 -1
  419. package/gsd-core/workflows/discovery-phase.md +0 -298
  420. package/gsd-core/workflows/plan-milestone-gaps.md +0 -281
  421. package/gsd-core/workflows/verify-phase.md +0 -577
  422. package/scripts/affected-tests-lib.cjs +0 -554
  423. package/scripts/gen-emitted-baseline.cjs +0 -145
  424. package/scripts/lint-allow-test-rule-refs.cjs +0 -162
  425. package/scripts/run-affected-tests.cjs +0 -7
  426. package/scripts/run-tests.cjs +0 -1050
@@ -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,25 +214,173 @@ 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
- // unreadable dir; broken symlink; file->dir swap) must degrade to "not stale" rather
217
- // than throw uncaught into callers that are NOT under the planning lock
218
- // (init.manager / init.progress / uat-predicate). Mirrors readVerificationStatus's
219
- // no-throw contract; `fsImpl` threads the same injectable-fs seam for parity/testing.
220
- // (Review B1 on #1548.)
358
+ // unreadable dir; broken symlink; file->dir swap) must degrade rather than throw
359
+ // uncaught into callers that are NOT under the planning lock (init.manager /
360
+ // init.progress / uat-predicate). Mirrors readVerificationStatus's no-throw
361
+ // contract; `fsImpl` threads the same injectable-fs seam for parity/testing.
362
+ // (Review B1 on #1548.) The degraded result is `{determined:false}`, NOT the
363
+ // same value as a completed "nothing is stale" check — see StaleCheckResult
364
+ // doc and #3057 B3. The caller decides how to route an indeterminate result;
365
+ // this function only reports what it actually knows.
221
366
  try {
222
367
  const phaseFiles = fsImpl.readdirSync(phaseDir);
223
- 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 });
224
375
  if (!verificationFile)
225
- return null;
376
+ return { determined: true, stale: false };
226
377
  const summaryFiles = scanPhasePlans(phaseDir).summaryFiles
227
378
  .slice()
228
379
  .sort();
229
380
  // No summary can be newer than the verification → never stale. Return before
230
381
  // touching git so a phase with no summaries costs zero subprocesses. (#2348)
231
382
  if (summaryFiles.length === 0)
232
- return null;
383
+ return { determined: true, stale: false };
233
384
  // Each file's effective "last changed" time = its commit time when committed
234
385
  // AND clean (content-tied and clone-stable), else its filesystem mtime (the
235
386
  // uncommitted working-tree edit). Both are real wall-clock change times, so
@@ -244,13 +395,13 @@ function findStaleVerificationSummary(phaseDir, fsImpl = node_fs_1.default, phas
244
395
  // The caller only needs whether the phase is stale, not which summary —
245
396
  // the first stale summary (in sorted order) is enough. Short-circuit.
246
397
  if (effectiveTimeMs(summaryFile) > verificationTimeMs) {
247
- return { verificationFile, summaryFile };
398
+ return { determined: true, stale: true, verificationFile, summaryFile };
248
399
  }
249
400
  }
250
- return null;
401
+ return { determined: true, stale: false };
251
402
  }
252
403
  catch {
253
- return null;
404
+ return { determined: false };
254
405
  }
255
406
  }
256
407
  /**
@@ -258,13 +409,21 @@ function findStaleVerificationSummary(phaseDir, fsImpl = node_fs_1.default, phas
258
409
  * phaseDir and return the routing result.
259
410
  *
260
411
  * Behavior:
261
- * 1. Find the first file matching `*-VERIFICATION.md` (sorted, take first).
262
- * 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'.
263
416
  * 2. Extract `status` from FRONTMATTER ONLY via the shared extractFrontmatter
264
417
  * parser (DEFECT.FRONTMATTER-SCALAR-BROAD-GREP fix — parser anchors at byte 0).
265
418
  * If no frontmatter block or no `status` key → status 'missing'.
266
419
  * 3. Map to routing table. Unknown non-empty value → status 'unknown'.
267
420
  *
421
+ * The internal staleness check can itself fail (fs / scanPhasePlans / clock
422
+ * error); when it does, `status` is routed as if nothing were stale (the
423
+ * pre-existing no-throw fail-open contract — unchanged), but the returned
424
+ * result carries `staleCheckIndeterminate: true` so a caller can distinguish
425
+ * "checked; nothing is stale" from "could not check" (#3057 B3).
426
+ *
268
427
  * @param phaseDir - Absolute path to the phase directory.
269
428
  * @param opts - Options. `opts.fs` allows test injection (defaults to node:fs).
270
429
  * `opts.runtime` selects the command surface `next_command` is
@@ -290,8 +449,12 @@ function readVerificationStatus(phaseDir, opts = {}) {
290
449
  let verificationFile = null;
291
450
  try {
292
451
  const entries = fsImpl.readdirSync(phaseDir);
293
- const candidates = entries.filter((f) => f.endsWith('-VERIFICATION.md')).sort();
294
- 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 });
295
458
  }
296
459
  catch {
297
460
  // Directory unreadable → treat as missing
@@ -331,8 +494,8 @@ function readVerificationStatus(phaseDir, opts = {}) {
331
494
  next_command: projectNextCommand('plan-phase', runtime, `${phaseArg} --gaps`),
332
495
  };
333
496
  }
334
- const staleVerification = findStaleVerificationSummary(phaseDir, fsImpl, phaseCleanCommitTimesMs);
335
- if (staleVerification) {
497
+ const staleCheck = findStaleVerificationSummary(phaseDir, fsImpl, phaseCleanCommitTimesMs);
498
+ if (staleCheck.determined && staleCheck.stale) {
336
499
  const entry = VERIFICATION_ROUTING_TABLE['stale'];
337
500
  return {
338
501
  status: entry.status,
@@ -340,6 +503,12 @@ function readVerificationStatus(phaseDir, opts = {}) {
340
503
  next_command: projectNextCommand('verify-work', runtime, phaseArg),
341
504
  };
342
505
  }
506
+ // staleCheck is either {determined:true, stale:false} (checked; nothing
507
+ // stale) or {determined:false} (could not check — fs/scan/clock failure).
508
+ // Both fall through to normal routing below (the pre-existing no-throw
509
+ // fail-open contract is unchanged), but the indeterminate case is flagged
510
+ // on the returned result so a caller can tell the two apart (#3057 B3).
511
+ const staleCheckIndeterminate = !staleCheck.determined;
343
512
  // 3. Route — exclude internal sentinels from raw-file lookup (they are
344
513
  // constructed internally above, never written by the verifier).
345
514
  if (rawStatus in VERIFICATION_ROUTING_TABLE &&
@@ -352,14 +521,68 @@ function readVerificationStatus(phaseDir, opts = {}) {
352
521
  status: entry.status,
353
522
  next_action: entry.next_action,
354
523
  next_command: projectNextCommand(entry.next_command, runtime, phaseArg),
524
+ ...(staleCheckIndeterminate ? { staleCheckIndeterminate: true } : {}),
355
525
  };
356
526
  }
357
527
  // Unknown value
358
528
  const unknownRoute = VERIFICATION_ROUTING_TABLE['unknown'];
359
529
  return {
360
530
  status: unknownRoute.status,
361
- 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.`,
362
532
  next_command: projectNextCommand(unknownRoute.next_command, runtime, phaseArg),
533
+ ...(staleCheckIndeterminate ? { staleCheckIndeterminate: true } : {}),
534
+ };
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,
363
586
  };
364
587
  }
365
588
  /**
@@ -379,11 +602,55 @@ function cmdVerificationStatus(cwd, phaseDirArg, raw) {
379
602
  const result = readVerificationStatus(phaseDir, { runtime: (0, runtime_slash_cjs_1.resolveRuntime)(cwd) });
380
603
  output(result, raw);
381
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
+ }
382
645
  module.exports = {
383
646
  VERIFIER_STATUSES,
384
647
  VERIFICATION_ROUTING_TABLE,
385
648
  defaultPhaseCleanCommitTimesMs,
649
+ resolveVerificationFile,
650
+ resolveUatFile,
386
651
  findStaleVerificationSummary,
387
652
  readVerificationStatus,
653
+ isPhaseComplete,
388
654
  cmdVerificationStatus,
655
+ cmdVerificationResolveFile,
389
656
  };