@opengsd/gsd-core 1.13.0 → 1.15.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 (441) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/README.ja-JP.md +3 -3
  4. package/README.ko-KR.md +3 -3
  5. package/README.pt-BR.md +3 -3
  6. package/README.zh-CN.md +3 -3
  7. package/agents/gsd-advisor-researcher.compact.md +85 -0
  8. package/agents/gsd-ai-researcher.compact.md +96 -0
  9. package/agents/gsd-assumptions-analyzer.compact.md +81 -0
  10. package/agents/gsd-code-fixer.compact.md +459 -0
  11. package/agents/gsd-code-fixer.md +9 -8
  12. package/agents/gsd-code-reviewer.compact.md +269 -0
  13. package/agents/gsd-code-reviewer.md +15 -3
  14. package/agents/gsd-codebase-mapper.compact.md +760 -0
  15. package/agents/gsd-debug-session-manager.compact.md +360 -0
  16. package/agents/gsd-debug-session-manager.md +17 -2
  17. package/agents/gsd-debugger.md +2 -2
  18. package/agents/gsd-doc-classifier.compact.md +192 -0
  19. package/agents/gsd-doc-synthesizer.compact.md +200 -0
  20. package/agents/gsd-doc-verifier.compact.md +143 -0
  21. package/agents/gsd-doc-writer.compact.md +440 -0
  22. package/agents/gsd-dom-verifier.compact.md +138 -0
  23. package/agents/gsd-domain-researcher.compact.md +141 -0
  24. package/agents/gsd-eval-auditor.compact.md +160 -0
  25. package/agents/gsd-eval-auditor.md +1 -1
  26. package/agents/gsd-eval-planner.compact.md +137 -0
  27. package/agents/gsd-executor.md +13 -8
  28. package/agents/gsd-framework-selector.compact.md +82 -0
  29. package/agents/gsd-integration-checker.compact.md +245 -0
  30. package/agents/gsd-intel-updater.compact.md +226 -0
  31. package/agents/gsd-intel-updater.md +1 -1
  32. package/agents/gsd-mempalace-curator.compact.md +45 -0
  33. package/agents/gsd-nyquist-auditor.compact.md +179 -0
  34. package/agents/gsd-pattern-mapper.compact.md +275 -0
  35. package/agents/gsd-phase-researcher.md +19 -11
  36. package/agents/gsd-plan-checker.md +8 -7
  37. package/agents/gsd-planner.md +12 -8
  38. package/agents/gsd-project-researcher.compact.md +587 -0
  39. package/agents/gsd-project-researcher.md +1 -1
  40. package/agents/gsd-research-synthesizer.compact.md +212 -0
  41. package/agents/gsd-research-synthesizer.md +1 -1
  42. package/agents/gsd-roadmapper.compact.md +454 -0
  43. package/agents/gsd-roadmapper.md +13 -0
  44. package/agents/gsd-security-auditor.compact.md +162 -0
  45. package/agents/gsd-ui-auditor.compact.md +404 -0
  46. package/agents/gsd-ui-auditor.md +155 -17
  47. package/agents/gsd-ui-checker.compact.md +277 -0
  48. package/agents/gsd-ui-researcher.compact.md +282 -0
  49. package/agents/gsd-ui-researcher.md +1 -1
  50. package/agents/gsd-user-profiler.compact.md +108 -0
  51. package/agents/gsd-verifier.md +10 -9
  52. package/bin/install.js +848 -163
  53. package/commands/gsd/autonomous.md +2 -2
  54. package/commands/gsd/capture.md +1 -1
  55. package/commands/gsd/cleanup.md +1 -0
  56. package/commands/gsd/code-review.md +2 -1
  57. package/commands/gsd/complete-milestone.md +1 -0
  58. package/commands/gsd/config.md +1 -0
  59. package/commands/gsd/debug.md +1 -0
  60. package/commands/gsd/graphify.md +1 -0
  61. package/commands/gsd/health.md +1 -0
  62. package/commands/gsd/mempalace-capture.md +8 -3
  63. package/commands/gsd/mempalace-recall.md +1 -0
  64. package/commands/gsd/new-milestone.md +1 -0
  65. package/commands/gsd/new-project.md +1 -0
  66. package/commands/gsd/next.md +1 -0
  67. package/commands/gsd/pause-work.md +1 -0
  68. package/commands/gsd/phase.md +1 -0
  69. package/commands/gsd/plan-review-convergence.md +6 -6
  70. package/commands/gsd/pr-branch.md +1 -0
  71. package/commands/gsd/progress.md +1 -1
  72. package/commands/gsd/quick-batch.md +1 -1
  73. package/commands/gsd/resume-work.md +1 -0
  74. package/commands/gsd/review-backlog.md +1 -0
  75. package/commands/gsd/review.md +2 -3
  76. package/commands/gsd/settings.md +2 -1
  77. package/commands/gsd/stats.md +1 -0
  78. package/commands/gsd/thread.md +1 -0
  79. package/commands/gsd/workspace.md +1 -0
  80. package/commands/gsd/workstreams.md +1 -0
  81. package/gsd-core/bin/check-latest-version.cjs +8 -3
  82. package/gsd-core/bin/gsd-tools.cjs +672 -146
  83. package/gsd-core/bin/lib/adr-parser.cjs +4 -2
  84. package/gsd-core/bin/lib/artifacts.cjs +2 -1
  85. package/gsd-core/bin/lib/audit.cjs +119 -34
  86. package/gsd-core/bin/lib/broken-windows.cjs +168 -49
  87. package/gsd-core/bin/lib/capability-lifecycle.cjs +10 -6
  88. package/gsd-core/bin/lib/capability-loader.cjs +135 -1
  89. package/gsd-core/bin/lib/capability-registry.cjs +96 -189
  90. package/gsd-core/bin/lib/capability-source.cjs +19 -2
  91. package/gsd-core/bin/lib/capability-validator.cjs +14 -2
  92. package/gsd-core/bin/lib/check-command-router.cjs +213 -49
  93. package/gsd-core/bin/lib/code-review-depth.cjs +2 -2
  94. package/gsd-core/bin/lib/codex-agent-toml.cjs +21 -25
  95. package/gsd-core/bin/lib/commands.cjs +823 -112
  96. package/gsd-core/bin/lib/config-loader.cjs +66 -4
  97. package/gsd-core/bin/lib/config.cjs +186 -45
  98. package/gsd-core/bin/lib/coverage.cjs +1 -1
  99. package/gsd-core/bin/lib/decisions.cjs +164 -45
  100. package/gsd-core/bin/lib/external-descriptor-trust.cjs +29 -14
  101. package/gsd-core/bin/lib/frontmatter.cjs +13 -0
  102. package/gsd-core/bin/lib/graphify.cjs +10 -2
  103. package/gsd-core/bin/lib/gsd2-import.cjs +1 -2
  104. package/gsd-core/bin/lib/health-diagnostic-rules/state-consistency.cjs +12 -1
  105. package/gsd-core/bin/lib/health-diagnostic-rules/worktree-health.cjs +1 -1
  106. package/gsd-core/bin/lib/host-runtime-detection.cjs +9 -0
  107. package/gsd-core/bin/lib/init.cjs +614 -86
  108. package/gsd-core/bin/lib/install-engine.cjs +29 -3
  109. package/gsd-core/bin/lib/install-profiles.cjs +14 -0
  110. package/gsd-core/bin/lib/installer-migrations.cjs +41 -5
  111. package/gsd-core/bin/lib/loop-resolver.cjs +50 -31
  112. package/gsd-core/bin/lib/mcp-catalog.cjs +2 -2
  113. package/gsd-core/bin/lib/milestone.cjs +37 -13
  114. package/gsd-core/bin/lib/model-resolver.cjs +253 -53
  115. package/gsd-core/bin/lib/phase-command-router.cjs +16 -2
  116. package/gsd-core/bin/lib/phase-id-card.cjs +32 -0
  117. package/gsd-core/bin/lib/phase-id-display.cjs +78 -0
  118. package/gsd-core/bin/lib/phase-id.cjs +268 -27
  119. package/gsd-core/bin/lib/phase-lifecycle.cjs +61 -0
  120. package/gsd-core/bin/lib/phase-locator.cjs +29 -10
  121. package/gsd-core/bin/lib/phase.cjs +393 -88
  122. package/gsd-core/bin/lib/plan-document.cjs +49 -1
  123. package/gsd-core/bin/lib/planning-document.cjs +459 -0
  124. package/gsd-core/bin/lib/planning-inspect.cjs +52 -19
  125. package/gsd-core/bin/lib/planning-snapshot.cjs +61 -12
  126. package/gsd-core/bin/lib/planning-workspace.cjs +57 -3
  127. package/gsd-core/bin/lib/pr-branch-patterns.cjs +57 -0
  128. package/gsd-core/bin/lib/pristine-baseline.cjs +182 -0
  129. package/gsd-core/bin/lib/probe-core.cjs +7 -1
  130. package/gsd-core/bin/lib/prohibition-enforcement.cjs +91 -4
  131. package/gsd-core/bin/lib/project-root.cjs +41 -2
  132. package/gsd-core/bin/lib/quick-batch.cjs +1 -1
  133. package/gsd-core/bin/lib/refactor-trigger-command-router.cjs +61 -2
  134. package/gsd-core/bin/lib/research-store.cjs +11 -12
  135. package/gsd-core/bin/lib/review-lane-descriptor.cjs +10 -30
  136. package/gsd-core/bin/lib/review-lane-invocation.cjs +23 -0
  137. package/gsd-core/bin/lib/review-reviewer-selection.cjs +2 -2
  138. package/gsd-core/bin/lib/reviewer-step-dispatch.cjs +337 -0
  139. package/gsd-core/bin/lib/roadmap-command-router.cjs +12 -4
  140. package/gsd-core/bin/lib/roadmap-parser.cjs +219 -18
  141. package/gsd-core/bin/lib/roadmap-upgrade.cjs +1539 -13
  142. package/gsd-core/bin/lib/roadmap.cjs +356 -42
  143. package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +310 -41
  144. package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +15 -4
  145. package/gsd-core/bin/lib/runtime-artifact-layout.cjs +13 -5
  146. package/gsd-core/bin/lib/runtime-homes.cjs +4 -0
  147. package/gsd-core/bin/lib/runtime-hooks-surface.cjs +408 -37
  148. package/gsd-core/bin/lib/runtime-name-policy.cjs +111 -1
  149. package/gsd-core/bin/lib/security.cjs +126 -7
  150. package/gsd-core/bin/lib/shell-command-projection.cjs +10 -6
  151. package/gsd-core/bin/lib/state-document.cjs +130 -28
  152. package/gsd-core/bin/lib/state-md-schema.cjs +21 -14
  153. package/gsd-core/bin/lib/state-transition.cjs +181 -30
  154. package/gsd-core/bin/lib/state.cjs +265 -27
  155. package/gsd-core/bin/lib/surface.cjs +77 -3
  156. package/gsd-core/bin/lib/task-command-router.cjs +12 -6
  157. package/gsd-core/bin/lib/tdd-red-evidence.cjs +78 -5
  158. package/gsd-core/bin/lib/uat-predicate.cjs +47 -4
  159. package/gsd-core/bin/lib/uat.cjs +9 -1
  160. package/gsd-core/bin/lib/ui-consideration-probe.cjs +15 -2
  161. package/gsd-core/bin/lib/ui-frontend-evidence.cjs +100 -9
  162. package/gsd-core/bin/lib/undo-commit-selection.cjs +131 -0
  163. package/gsd-core/bin/lib/update-context.cjs +30 -24
  164. package/gsd-core/bin/lib/vendor/js-yaml.cjs +11 -3
  165. package/gsd-core/bin/lib/verification.cjs +315 -30
  166. package/gsd-core/bin/lib/verify-command-grounding.cjs +47 -3
  167. package/gsd-core/bin/lib/verify.cjs +320 -48
  168. package/gsd-core/bin/lib/workstream-inventory.cjs +1 -0
  169. package/gsd-core/bin/lib/worktree-base-ref.cjs +482 -73
  170. package/gsd-core/bin/lib/worktree-safety.cjs +797 -58
  171. package/gsd-core/bin/shared/config-defaults.manifest.json +4 -0
  172. package/gsd-core/bin/shared/config-schema.manifest.json +6 -0
  173. package/gsd-core/bin/verify-reapply-patches.cjs +439 -80
  174. package/gsd-core/references/checkpoints.md +5 -3
  175. package/gsd-core/references/compact-content-gate.md +66 -0
  176. package/gsd-core/references/edge-probe-fixtures/01-round-half-even/expected-coverage.json +28 -3
  177. package/gsd-core/references/edge-probe-fixtures/02-merge-intervals/expected-coverage.json +37 -4
  178. package/gsd-core/references/edge-probe-fixtures/03-truncate-graphemes/expected-coverage.json +28 -3
  179. package/gsd-core/references/edge-probe-fixtures/04-money-rounding/expected-coverage.json +28 -3
  180. package/gsd-core/references/edge-probe-fixtures/05-list-dedupe/expected-coverage.json +37 -4
  181. package/gsd-core/references/edge-probe-fixtures/06-resolved-mixed/expected-coverage.json +37 -4
  182. package/gsd-core/references/edge-probe.md +195 -21
  183. package/gsd-core/references/execute-phase-between-wave-reset.md +7 -6
  184. package/gsd-core/references/execute-phase-wave-guard.md +22 -11
  185. package/gsd-core/references/gsd-run-resolver.md +1 -1
  186. package/gsd-core/references/loop-hook-dispatch.md +18 -0
  187. package/gsd-core/references/model-profiles.md +13 -4
  188. package/gsd-core/references/phase-argument-parsing.md +9 -7
  189. package/gsd-core/references/phase-id-convention.md +28 -0
  190. package/gsd-core/references/planner-gap-closure.md +2 -0
  191. package/gsd-core/references/planner-load-graph-context.md +24 -13
  192. package/gsd-core/references/planner-verify-command-grounding.md +14 -0
  193. package/gsd-core/references/planning-config.md +14 -2
  194. package/gsd-core/references/tdd.md +30 -4
  195. package/gsd-core/references/thinking-models-planning.md +18 -2
  196. package/gsd-core/references/ui-consideration-probe.md +10 -5
  197. package/gsd-core/references/verification-patterns.md +17 -4
  198. package/gsd-core/references/verify-command-path-resolvability.md +10 -2
  199. package/gsd-core/references/worktree-path-safety.md +433 -2
  200. package/gsd-core/templates/README.md +7 -1
  201. package/gsd-core/templates/state.md +6 -3
  202. package/gsd-core/templates/summary.compact.md +212 -0
  203. package/gsd-core/templates/user-setup.compact.md +199 -0
  204. package/gsd-core/templates/user-setup.md +0 -9
  205. package/gsd-core/templates/verification-report.md +1 -1
  206. package/gsd-core/workflows/_runtime-launcher.snippet.sh +1 -1
  207. package/gsd-core/workflows/add-backlog.md +1 -1
  208. package/gsd-core/workflows/add-phase.md +1 -1
  209. package/gsd-core/workflows/add-tests.md +2 -2
  210. package/gsd-core/workflows/add-todo.md +6 -5
  211. package/gsd-core/workflows/ai-integration-phase.md +11 -3
  212. package/gsd-core/workflows/audit-fix.md +1 -1
  213. package/gsd-core/workflows/audit-milestone.md +1 -1
  214. package/gsd-core/workflows/audit-uat.md +1 -1
  215. package/gsd-core/workflows/autonomous/steps/converge-fail-fast.md +9 -18
  216. package/gsd-core/workflows/autonomous.md +29 -16
  217. package/gsd-core/workflows/check-todos.md +6 -4
  218. package/gsd-core/workflows/cleanup.md +5 -3
  219. package/gsd-core/workflows/code-review/steps/dispatch-fix.md +4 -3
  220. package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +8 -1
  221. package/gsd-core/workflows/code-review-fix.md +108 -22
  222. package/gsd-core/workflows/code-review.md +216 -73
  223. package/gsd-core/workflows/complete-milestone/detail/elaboration.md +274 -0
  224. package/gsd-core/workflows/complete-milestone.md +41 -264
  225. package/gsd-core/workflows/debug.md +3 -3
  226. package/gsd-core/workflows/diagnose-issues.md +1 -1
  227. package/gsd-core/workflows/discuss-phase/modes/advisor.md +1 -1
  228. package/gsd-core/workflows/discuss-phase/modes/chain.md +1 -1
  229. package/gsd-core/workflows/discuss-phase-assumptions.md +1 -1
  230. package/gsd-core/workflows/discuss-phase.md +1 -1
  231. package/gsd-core/workflows/do.md +2 -2
  232. package/gsd-core/workflows/docs-update/detail/elaboration.md +179 -0
  233. package/gsd-core/workflows/docs-update.md +17 -158
  234. package/gsd-core/workflows/edit-phase.md +1 -1
  235. package/gsd-core/workflows/eval-review.md +10 -3
  236. package/gsd-core/workflows/execute-phase/detail/elaboration.md +124 -0
  237. package/gsd-core/workflows/execute-phase/steps/code-review-disposition.md +1017 -0
  238. package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +19 -4
  239. package/gsd-core/workflows/execute-phase/steps/completion-reconciliation.md +56 -0
  240. package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +43 -4
  241. package/gsd-core/workflows/execute-phase/steps/executor-progress-policy.md +43 -0
  242. package/gsd-core/workflows/execute-phase/steps/gap-closure-artifacts.md +1 -1
  243. package/gsd-core/workflows/execute-phase/steps/partial-wave.md +1 -1
  244. package/gsd-core/workflows/execute-phase/steps/per-plan-executor-routing.md +1 -1
  245. package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +1 -1
  246. package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +46 -9
  247. package/gsd-core/workflows/execute-phase/steps/protected-branch.md +1 -1
  248. package/gsd-core/workflows/execute-phase/steps/ready-wave-gate.md +37 -0
  249. package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +1 -1
  250. package/gsd-core/workflows/execute-phase/steps/sequential-root-pin.md +35 -0
  251. package/gsd-core/workflows/execute-phase/steps/stale-reverification.md +24 -0
  252. package/gsd-core/workflows/execute-phase/steps/tdd-applicability-resolution.md +1 -1
  253. package/gsd-core/workflows/execute-phase/steps/threat-id-gate.md +28 -0
  254. package/gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md +1 -1
  255. package/gsd-core/workflows/execute-phase/steps/worktree-base-check.md +25 -0
  256. package/gsd-core/workflows/execute-phase.md +83 -172
  257. package/gsd-core/workflows/execute-plan.md +24 -10
  258. package/gsd-core/workflows/explore.md +3 -3
  259. package/gsd-core/workflows/extract-learnings.md +2 -1
  260. package/gsd-core/workflows/fast.md +1 -1
  261. package/gsd-core/workflows/forensics.md +1 -1
  262. package/gsd-core/workflows/graduation.md +1 -1
  263. package/gsd-core/workflows/health.md +2 -2
  264. package/gsd-core/workflows/help/modes/full.compact.md +398 -0
  265. package/gsd-core/workflows/help/modes/full.md +5 -5
  266. package/gsd-core/workflows/help/modes/topic.md +15 -5
  267. package/gsd-core/workflows/help.md +1 -1
  268. package/gsd-core/workflows/import.md +2 -2
  269. package/gsd-core/workflows/inbox.md +2 -2
  270. package/gsd-core/workflows/ingest-docs.md +3 -3
  271. package/gsd-core/workflows/insert-phase.md +1 -1
  272. package/gsd-core/workflows/list-seeds.md +1 -1
  273. package/gsd-core/workflows/list-workspaces.md +1 -1
  274. package/gsd-core/workflows/manager.md +2 -2
  275. package/gsd-core/workflows/map-codebase.md +52 -5
  276. package/gsd-core/workflows/milestone-summary.md +1 -1
  277. package/gsd-core/workflows/mvp-phase.md +1 -1
  278. package/gsd-core/workflows/new-milestone.md +56 -14
  279. package/gsd-core/workflows/new-project/detail/elaboration.md +216 -0
  280. package/gsd-core/workflows/new-project/steps/auto-mode-config.md +3 -3
  281. package/gsd-core/workflows/new-project/steps/codebase-map-offer.md +1 -1
  282. package/gsd-core/workflows/new-project.md +39 -209
  283. package/gsd-core/workflows/new-workspace.md +2 -2
  284. package/gsd-core/workflows/next.md +1 -1
  285. package/gsd-core/workflows/note.md +1 -1
  286. package/gsd-core/workflows/onboard.md +1 -1
  287. package/gsd-core/workflows/pause-work.md +1 -1
  288. package/gsd-core/workflows/plan-phase/detail/elaboration.md +209 -0
  289. package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +17 -5
  290. package/gsd-core/workflows/plan-phase/steps/prd-express-path.md +1 -1
  291. package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +23 -5
  292. package/gsd-core/workflows/plan-phase.md +45 -187
  293. package/gsd-core/workflows/plan-review-convergence.md +21 -5
  294. package/gsd-core/workflows/plant-seed.md +62 -20
  295. package/gsd-core/workflows/pr-branch.md +132 -20
  296. package/gsd-core/workflows/profile-user.md +2 -2
  297. package/gsd-core/workflows/progress.md +1 -1
  298. package/gsd-core/workflows/quick/steps/plan-checker-loop.md +25 -0
  299. package/gsd-core/workflows/quick/steps/quick-verification.md +1 -1
  300. package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +1 -1
  301. package/gsd-core/workflows/quick-batch/steps/batch-init.md +1 -1
  302. package/gsd-core/workflows/quick-batch/steps/completion.md +1 -1
  303. package/gsd-core/workflows/quick-batch/steps/merge-wave.md +1 -1
  304. package/gsd-core/workflows/quick-batch/steps/planner-wave.md +1 -1
  305. package/gsd-core/workflows/quick-batch/steps/research-phase.md +1 -1
  306. package/gsd-core/workflows/quick-batch/steps/resume-mode.md +1 -1
  307. package/gsd-core/workflows/quick-batch/steps/verification-wave.md +1 -1
  308. package/gsd-core/workflows/quick-batch/steps/worktree-dispatch.md +1 -1
  309. package/gsd-core/workflows/quick-batch.md +1 -1
  310. package/gsd-core/workflows/quick.md +29 -10
  311. package/gsd-core/workflows/reapply-patches.md +86 -6
  312. package/gsd-core/workflows/remove-phase.md +1 -1
  313. package/gsd-core/workflows/remove-workspace.md +2 -2
  314. package/gsd-core/workflows/resume-project.md +1 -1
  315. package/gsd-core/workflows/review.md +31 -16
  316. package/gsd-core/workflows/scan.md +1 -1
  317. package/gsd-core/workflows/secure-phase.md +3 -2
  318. package/gsd-core/workflows/settings-advanced.md +30 -10
  319. package/gsd-core/workflows/settings-integrations.md +2 -3
  320. package/gsd-core/workflows/settings.md +22 -9
  321. package/gsd-core/workflows/ship.md +3 -2
  322. package/gsd-core/workflows/sketch-wrap-up.md +1 -1
  323. package/gsd-core/workflows/sketch.md +1 -1
  324. package/gsd-core/workflows/smart-entry.md +2 -2
  325. package/gsd-core/workflows/spec-phase.md +15 -5
  326. package/gsd-core/workflows/spike-wrap-up.md +1 -1
  327. package/gsd-core/workflows/spike.md +1 -1
  328. package/gsd-core/workflows/stats.md +1 -1
  329. package/gsd-core/workflows/sync-skills.md +5 -5
  330. package/gsd-core/workflows/thread.md +1 -1
  331. package/gsd-core/workflows/transition.md +1 -1
  332. package/gsd-core/workflows/ui-phase.md +44 -8
  333. package/gsd-core/workflows/ui-review.md +18 -4
  334. package/gsd-core/workflows/ultraplan-phase.md +1 -1
  335. package/gsd-core/workflows/undo.md +339 -20
  336. package/gsd-core/workflows/update.md +14 -12
  337. package/gsd-core/workflows/validate-phase.md +3 -2
  338. package/gsd-core/workflows/verify-work/detail/elaboration.md +230 -0
  339. package/gsd-core/workflows/verify-work/steps/automated-ui-verification.md +1 -1
  340. package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +1 -1
  341. package/gsd-core/workflows/verify-work.md +101 -196
  342. package/hooks/dist/gsd-agent-isolation-guard.js +66 -16
  343. package/hooks/dist/gsd-context-monitor.js +88 -15
  344. package/hooks/dist/gsd-cursor-subagent-start.js +34 -14
  345. package/hooks/dist/gsd-secret-read-guard.js +71 -19
  346. package/hooks/dist/gsd-statusline.js +81 -20
  347. package/hooks/dist/gsd-validate-commit.sh +97 -8
  348. package/hooks/dist/gsd-worktree-path-guard.js +25 -14
  349. package/hooks/dist/gsd-write-guard.js +46 -1
  350. package/hooks/dist/lib/dispatch-identity.js +187 -0
  351. package/hooks/dist/lib/filename-classification.js +64 -0
  352. package/hooks/dist/lib/isolation-deny-reason.js +53 -1
  353. package/hooks/dist/lib/isolation-sentinel.js +58 -19
  354. package/hooks/gsd-agent-isolation-guard.js +66 -16
  355. package/hooks/gsd-context-monitor.js +88 -15
  356. package/hooks/gsd-cursor-subagent-start.js +34 -14
  357. package/hooks/gsd-secret-read-guard.js +71 -19
  358. package/hooks/gsd-statusline.js +81 -20
  359. package/hooks/gsd-validate-commit.sh +97 -8
  360. package/hooks/gsd-worktree-path-guard.js +25 -14
  361. package/hooks/gsd-write-guard.js +46 -1
  362. package/hooks/lib/dispatch-identity.js +187 -0
  363. package/hooks/lib/filename-classification.js +64 -0
  364. package/hooks/lib/isolation-deny-reason.js +53 -1
  365. package/hooks/lib/isolation-sentinel.js +58 -19
  366. package/package.json +11 -6
  367. package/scripts/benchmark-compact-content-variants.cjs +298 -0
  368. package/scripts/benchmark-compact-content.cjs +368 -0
  369. package/scripts/build-hooks.js +15 -6
  370. package/scripts/check-contract-drift.cjs +131 -12
  371. package/scripts/check-env.cjs +36 -8
  372. package/scripts/check-glossary-refs.cjs +25 -21
  373. package/scripts/ci-next-health.cjs +271 -0
  374. package/scripts/ci-prepare-test-scope.cjs +7 -7
  375. package/scripts/ci-test-scope.cjs +126 -20
  376. package/scripts/ci-timeout-report.cjs +1 -1
  377. package/scripts/command-contract-helpers.cjs +3 -0
  378. package/scripts/diff-touches-shipped-paths.cjs +1 -1
  379. package/scripts/docs-guard-registry.cjs +35 -2
  380. package/scripts/gen-adr-index.cjs +8 -2
  381. package/scripts/gen-inventory-manifest.cjs +12 -0
  382. package/scripts/gen-loop-host-contract.cjs +69 -0
  383. package/scripts/gen-platform-conformance-tier.cjs +557 -0
  384. package/scripts/lib/drift-scan.cjs +1 -1
  385. package/scripts/lib/macos-conformance-tier.generated.cjs +224 -0
  386. package/scripts/lib/ndjson-reporter.cjs +3 -2
  387. package/scripts/lib/npm-version-check-diagnosis.cjs +59 -0
  388. package/scripts/lib/platform-conformance-tier.generated.cjs +287 -0
  389. package/scripts/lib/suite-detection.cjs +32 -0
  390. package/scripts/lint-allowed-tools-parity.cjs +221 -0
  391. package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +47 -3
  392. package/scripts/lint-phase-arg-assignment.cjs +257 -0
  393. package/scripts/lint-phase-id-drift.cjs +623 -13
  394. package/scripts/lint-pr-branch-pattern-drift.cjs +148 -0
  395. package/scripts/lint-response-language-coverage.cjs +9 -3
  396. package/scripts/lint-retired-runtime-name.cjs +619 -0
  397. package/scripts/lint-source-test-name-collision.cjs +1 -1
  398. package/scripts/lint-state-write-path-drift.cjs +93 -0
  399. package/scripts/lint-test-file-count.allowlist.json +29 -9
  400. package/scripts/lint-vendored-deps.cjs +128 -17
  401. package/scripts/lint-workflow-shellcheck-baseline.json +100 -0
  402. package/scripts/prompt-injection-scan.sh +18 -0
  403. package/scripts/release-tarball-smoke.cjs +194 -1
  404. package/scripts/workflow-size.cjs +139 -0
  405. package/skills/gsd-autonomous/SKILL.md +2 -2
  406. package/skills/gsd-capture/SKILL.md +1 -1
  407. package/skills/gsd-cleanup/SKILL.md +1 -0
  408. package/skills/gsd-code-review/SKILL.md +2 -1
  409. package/skills/gsd-complete-milestone/SKILL.md +1 -0
  410. package/skills/gsd-config/SKILL.md +1 -0
  411. package/skills/gsd-debug/SKILL.md +1 -0
  412. package/skills/gsd-graphify/SKILL.md +1 -0
  413. package/skills/gsd-health/SKILL.md +1 -0
  414. package/skills/gsd-mempalace-capture/SKILL.md +8 -3
  415. package/skills/gsd-mempalace-recall/SKILL.md +1 -0
  416. package/skills/gsd-new-milestone/SKILL.md +1 -0
  417. package/skills/gsd-new-project/SKILL.md +1 -0
  418. package/skills/gsd-next/SKILL.md +1 -0
  419. package/skills/gsd-pause-work/SKILL.md +1 -0
  420. package/skills/gsd-phase/SKILL.md +1 -0
  421. package/skills/gsd-plan-review-convergence/SKILL.md +5 -5
  422. package/skills/gsd-pr-branch/SKILL.md +1 -0
  423. package/skills/gsd-progress/SKILL.md +1 -1
  424. package/skills/gsd-quick-batch/SKILL.md +1 -1
  425. package/skills/gsd-resume-work/SKILL.md +1 -0
  426. package/skills/gsd-review/SKILL.md +2 -3
  427. package/skills/gsd-review-backlog/SKILL.md +1 -0
  428. package/skills/gsd-settings/SKILL.md +2 -1
  429. package/skills/gsd-stats/SKILL.md +1 -0
  430. package/skills/gsd-thread/SKILL.md +1 -0
  431. package/skills/gsd-workspace/SKILL.md +1 -0
  432. package/skills/gsd-workstreams/SKILL.md +1 -0
  433. package/vscode/package.json +1 -1
  434. package/gsd-core/templates/claude-md.md +0 -145
  435. package/gsd-core/templates/codebase/concerns.md +0 -310
  436. package/gsd-core/templates/codebase/conventions.md +0 -307
  437. package/gsd-core/templates/codebase/integrations.md +0 -280
  438. package/gsd-core/templates/codebase/structure.md +0 -285
  439. package/gsd-core/templates/codebase/testing.md +0 -480
  440. package/gsd-core/templates/debug-subagent-prompt.md +0 -91
  441. package/gsd-core/templates/discovery.md +0 -146
@@ -127,6 +127,23 @@ function preferFirst(entries, preferred) {
127
127
  const rest = entries.filter(([rt]) => rt !== preferred);
128
128
  return [...pref, ...rest];
129
129
  }
130
+ // GLOBAL probe: absolute env candidates first (in preferFirst order, first
131
+ // hasInstall hit wins), then $HOME-relative. Single resolver shared by the
132
+ // preferredConfigDir fast path's same-path dedup and the full cascade (#4197),
133
+ // so both compare against the global dir the resolution would actually select —
134
+ // an env-directed candidate, not necessarily the $HOME-relative pathname.
135
+ function resolveGlobalCandidate(fs, env, home, preferred) {
136
+ for (const [rt, absdir] of preferFirst(envRuntimeDirs({ env, home }), preferred)) {
137
+ if (hasInstall(fs, absdir))
138
+ return { runtime: rt, dir: node_path_1.default.resolve(absdir) };
139
+ }
140
+ for (const [rt, reldir] of preferFirst(exports.RUNTIME_DIRS, preferred)) {
141
+ const cand = node_path_1.default.resolve(home, reldir);
142
+ if (hasInstall(fs, cand))
143
+ return { runtime: rt, dir: cand };
144
+ }
145
+ return { runtime: '', dir: '' };
146
+ }
130
147
  /**
131
148
  * Pure resolver. Returns { installedVersion, scope, runtime, gsdDir }.
132
149
  */
@@ -137,11 +154,17 @@ function resolveUpdateContext({ home, cwd, env = {}, fs, preferredConfigDir = ''
137
154
  // Fast path: a validated preferredConfigDir (custom --config-dir install).
138
155
  if (preferredConfigDir && hasInstall(fs, preferredConfigDir)) {
139
156
  const resolvedPref = node_path_1.default.resolve(preferredConfigDir);
157
+ // Same-path dedup the cascade applies (#4197): a preferred dir that IS the
158
+ // selected global install (an env candidate or the $HOME-relative dir) is
159
+ // GLOBAL even when cwd === $HOME also makes it the cwd-relative match.
160
+ const { dir: globalDir } = resolveGlobalCandidate(fs, env, home, preferred);
140
161
  let scope = 'GLOBAL';
141
- for (const [, reldir] of exports.RUNTIME_DIRS) {
142
- if (node_path_1.default.resolve(cwd, reldir) === resolvedPref) {
143
- scope = 'LOCAL';
144
- break;
162
+ if (resolvedPref !== globalDir) {
163
+ for (const [, reldir] of exports.RUNTIME_DIRS) {
164
+ if (node_path_1.default.resolve(cwd, reldir) === resolvedPref) {
165
+ scope = 'LOCAL';
166
+ break;
167
+ }
145
168
  }
146
169
  }
147
170
  return {
@@ -151,7 +174,6 @@ function resolveUpdateContext({ home, cwd, env = {}, fs, preferredConfigDir = ''
151
174
  gsdDir: preferredConfigDir,
152
175
  };
153
176
  }
154
- const orderedEnv = preferFirst(envRuntimeDirs({ env, home }), preferred);
155
177
  const orderedRuntime = preferFirst(exports.RUNTIME_DIRS, preferred);
156
178
  // LOCAL probe (relative to cwd).
157
179
  let localRuntime = '', localDir = '';
@@ -163,25 +185,9 @@ function resolveUpdateContext({ home, cwd, env = {}, fs, preferredConfigDir = ''
163
185
  break;
164
186
  }
165
187
  }
166
- // GLOBAL probe: absolute env candidates first, then $HOME-relative.
167
- let globalRuntime = '', globalDir = '';
168
- for (const [rt, absdir] of orderedEnv) {
169
- if (hasInstall(fs, absdir)) {
170
- globalRuntime = rt;
171
- globalDir = node_path_1.default.resolve(absdir);
172
- break;
173
- }
174
- }
175
- if (!globalRuntime) {
176
- for (const [rt, reldir] of orderedRuntime) {
177
- const cand = node_path_1.default.resolve(home, reldir);
178
- if (hasInstall(fs, cand)) {
179
- globalRuntime = rt;
180
- globalDir = cand;
181
- break;
182
- }
183
- }
184
- }
188
+ // GLOBAL probe: absolute env candidates first, then $HOME-relative — the
189
+ // same resolver the fast path dedups against.
190
+ const { runtime: globalRuntime, dir: globalDir } = resolveGlobalCandidate(fs, env, home, preferred);
185
191
  const localValid = trustedVersionAt(fs, localDir);
186
192
  const isLocal = !!localValid && (!globalDir || localDir !== globalDir);
187
193
  if (isLocal) {
@@ -1262,16 +1262,21 @@
1262
1262
  state.result += _result;
1263
1263
  }
1264
1264
  }
1265
+ function chargeMergeWork(state) {
1266
+ state.totalMergeKeys++;
1267
+ if (state.maxTotalMergeKeys !== -1 && state.totalMergeKeys > state.maxTotalMergeKeys) {
1268
+ throwError(state, "merge keys exceeded maxTotalMergeKeys (" + state.maxTotalMergeKeys + ")");
1269
+ }
1270
+ }
1265
1271
  function mergeMappings(state, destination, source, overridableKeys) {
1266
1272
  if (!common2.isObject(source)) {
1267
1273
  throwError(state, "cannot merge mappings; the provided source object is unacceptable");
1268
1274
  }
1275
+ chargeMergeWork(state);
1269
1276
  var sourceKeys = Object.keys(source);
1270
1277
  for (var index = 0, quantity = sourceKeys.length; index < quantity; index += 1) {
1271
1278
  var key = sourceKeys[index];
1272
- if (state.maxTotalMergeKeys !== -1 && ++state.totalMergeKeys > state.maxTotalMergeKeys) {
1273
- throwError(state, "merge keys exceeded maxTotalMergeKeys (" + state.maxTotalMergeKeys + ")");
1274
- }
1279
+ chargeMergeWork(state);
1275
1280
  if (!_hasOwnProperty.call(destination, key)) {
1276
1281
  setProperty(destination, key, source[key]);
1277
1282
  overridableKeys[key] = true;
@@ -1299,6 +1304,9 @@
1299
1304
  }
1300
1305
  if (keyTag === "tag:yaml.org,2002:merge") {
1301
1306
  if (Array.isArray(valueNode)) {
1307
+ if (valueNode.length > 100) {
1308
+ throwError(state, "abnormal merge sequence size");
1309
+ }
1302
1310
  for (var _index = 0, _quantity = valueNode.length; _index < _quantity; _index += 1) {
1303
1311
  mergeMappings(state, _result, valueNode[_index], overridableKeys);
1304
1312
  }
@@ -48,9 +48,10 @@ const coreUtilsMod = require("./core-utils.cjs");
48
48
  const planningScopeMod = require("./planning-scope.cjs");
49
49
  const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs");
50
50
  const runtime_slash_cjs_1 = require("./runtime-slash.cjs");
51
+ const security_cjs_1 = require("./security.cjs");
51
52
  const { output, error } = io;
52
53
  const { extractPhaseToken, scopeToPhase } = phaseId;
53
- const { extractFrontmatter } = frontmatterMod;
54
+ const { extractFrontmatter, FRONTMATTER_UNPARSEABLE } = frontmatterMod;
54
55
  const { normalizeLineEndings } = coreUtilsMod;
55
56
  const { SCOPE } = planningScopeMod;
56
57
  // ─── Constants ────────────────────────────────────────────────────────────────
@@ -101,8 +102,14 @@ const VERIFICATION_ROUTING_TABLE = {
101
102
  },
102
103
  stale: {
103
104
  status: 'stale',
104
- next_action: 'Verification is stale. Re-run verify-work before transition.',
105
- next_command: '',
105
+ // #4682: staleness means covered source files changed after the verifier
106
+ // last ran — the only remedy is re-running the verifier.
107
+ // /gsd-verify-work never rewrites VERIFICATION.md, so advising it from
108
+ // here was an advice loop. execute-phase resumes at the verification
109
+ // gates and re-runs the verifier (its resume tree routes a stale report
110
+ // to re-verification), which regenerates VERIFICATION.md and its digest.
111
+ next_action: 'Verification is stale — covered source files changed after the verifier last ran. Re-run execute-phase for this phase: it resumes at the verification gates and re-runs the verifier, regenerating VERIFICATION.md and its digest. verify-work alone cannot refresh a stale report.',
112
+ next_command: 'execute-phase',
106
113
  },
107
114
  // INTERNAL SENTINEL: constructed when no *-VERIFICATION.md file exists or when
108
115
  // the file has no parseable frontmatter status. Never emitted by the verifier.
@@ -111,6 +118,15 @@ const VERIFICATION_ROUTING_TABLE = {
111
118
  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).',
112
119
  next_command: 'execute-phase',
113
120
  },
121
+ // #4806: the report EXISTS but its frontmatter is not parseable YAML —
122
+ // fundamentally different from "missing" (the verify step DID run; re-running
123
+ // execute-phase cannot fix a YAML typo). Consumers treat any non-'passed'
124
+ // status as blocking, so this value fails safe while telling the truth.
125
+ unparseable: {
126
+ status: 'unparseable',
127
+ next_action: "The *-VERIFICATION.md frontmatter is not parseable YAML — fix the syntax error in the report itself. Re-running execute-phase cannot fix a YAML typo in an existing report.",
128
+ next_command: '',
129
+ },
114
130
  // INTERNAL SENTINEL: constructed when the file has a status value not in
115
131
  // VERIFIER_STATUSES. Never emitted by the verifier.
116
132
  unknown: {
@@ -173,8 +189,95 @@ function canonicalizeCoveredFiles(files) {
173
189
  * Bump on any change to the digest's input shape (path list, hashing order,
174
190
  * per-file hash algorithm) so an old stored digest can never collide with a
175
191
  * differently-computed new one — a version mismatch is just a mismatch.
192
+ *
193
+ * Version history:
194
+ * v1 (#4155) — every covered path's whole bytes, uniformly.
195
+ * v2 (#4623) — repo-wide planning documents (`isSharedPlanningDoc`) are
196
+ * excluded from the hash by construction.
197
+ *
198
+ * A stored digest names its own version (`v<N>:sha256:…`), and
199
+ * `readVerificationStatus` recomputes under the STORED version rather than
200
+ * this constant — so bumping it does not flip every already-verified phase
201
+ * to `stale` on upgrade. A legacy v1 report keeps v1 semantics, shared
202
+ * documents included, until it is re-fingerprinted; only a version outside
203
+ * `KNOWN_FINGERPRINT_VERSIONS` is unrecomputable and fails closed.
204
+ */
205
+ const FINGERPRINT_VERSION = 2;
206
+ const KNOWN_FINGERPRINT_VERSIONS = new Set([1, 2]);
207
+ /**
208
+ * #4623: the planning roots whose DIRECT children are repo-wide planning
209
+ * documents, as project-root-relative posix paths. Always `.planning`; plus
210
+ * the phase's OWN planning root when a phase directory is known — the parent
211
+ * of its `phases/` directory, which is how `planningDir` lays out every
212
+ * scope (`.planning`, `.planning/<project>`, `.planning/workstreams/<ws>`,
213
+ * `.planning/<project>/workstreams/<ws>`; `planning-workspace.cts`). Derived
214
+ * from the phase's position rather than from a list of layouts so a
215
+ * workstream-scoped `ROADMAP.md` is recognised without this function
216
+ * knowing what a workstream is, and so `.planning/research/notes.md` is
217
+ * NOT mistaken for one — lexically the two are indistinguishable from
218
+ * `.planning/<project>/ROADMAP.md`. A phase directory that does not sit
219
+ * under the project root (unit fixtures at a bare tmpdir) contributes no
220
+ * extra root.
221
+ */
222
+ function sharedPlanningRoots(projectRoot, phaseDir) {
223
+ const roots = ['.planning'];
224
+ if (phaseDir) {
225
+ const phasesDir = node_path_1.default.dirname(node_path_1.default.resolve(phaseDir));
226
+ const planningDir = node_path_1.default.dirname(phasesDir);
227
+ const rel = normalizeRel(node_path_1.default.relative(node_path_1.default.resolve(projectRoot), planningDir));
228
+ // Two structural checks, both load-bearing: the phase dir's PARENT must be
229
+ // the `phases/` directory `planningDir` lays every scope out with, and the
230
+ // derived root must sit inside `.planning/`. Without them any accepted
231
+ // directory — `<root>/src/phases/01-fake` — would nominate `src` as a
232
+ // planning root and silently drop real implementation evidence from the
233
+ // digest (found by the cross-AI review of this change). A shape that fails
234
+ // either check contributes no extra root; `.planning` itself is already
235
+ // present.
236
+ if (node_path_1.default.basename(phasesDir) === 'phases' &&
237
+ rel.startsWith('.planning/') &&
238
+ !rel.includes('/../') &&
239
+ !roots.includes(rel)) {
240
+ roots.push(rel);
241
+ }
242
+ }
243
+ return roots;
244
+ }
245
+ /**
246
+ * #4623: a covered path names a repo-wide planning document when it sits
247
+ * DIRECTLY under one of `sharedPlanningRoots` — `ROADMAP.md`,
248
+ * `REQUIREMENTS.md`, `STATE.md`, `PROJECT.md`, `MILESTONES.md`,
249
+ * `config.json`, … — as opposed to a phase's own artifacts under
250
+ * `<root>/phases/<phase>/` or a research note under `.planning/research/`.
251
+ * Every phase rewrites these as ordinary bookkeeping (a roadmap checkbox, a
252
+ * requirement's traceability cell, STATE.md's position), so hashing their
253
+ * whole bytes into one phase's digest coupled every phase's staleness to
254
+ * every other phase's close — and to its OWN close, since `phase.complete`
255
+ * and `requirements mark-complete` write them after the verifier has
256
+ * already run.
257
+ *
258
+ * Defined by position, not by a name list, so the set cannot drift as new
259
+ * top-level planning documents appear (the tree already carries a dozen).
260
+ * `rel` is expected posix-normalized (`canonicalizeCoveredFiles`), so a
261
+ * `./.planning/ROADMAP.md` spelling has already collapsed to the bare form.
262
+ */
263
+ function isSharedPlanningDoc(rel, roots = ['.planning']) {
264
+ if (rel === '' || rel.endsWith('/'))
265
+ return false;
266
+ return roots.includes(node_path_1.default.posix.dirname(rel));
267
+ }
268
+ /**
269
+ * #4623: the fingerprint version a stored `covered_digest` was computed
270
+ * under, or `null` when the prefix is absent, malformed, or names a version
271
+ * this build cannot recompute (an unknown version is a mismatch by
272
+ * construction — the fail-closed shape `FINGERPRINT_VERSION`'s doc promises).
176
273
  */
177
- const FINGERPRINT_VERSION = 1;
274
+ function parseFingerprintVersion(digest) {
275
+ const m = /^v(\d+):sha256:/.exec(digest);
276
+ if (!m)
277
+ return null;
278
+ const version = Number(m[1]);
279
+ return KNOWN_FINGERPRINT_VERSIONS.has(version) ? version : null;
280
+ }
178
281
  /**
179
282
  * #4155: recompute the deterministic content fingerprint over a verifier's
180
283
  * declared covered-input set (phase PLAN/SUMMARY, mapped requirements,
@@ -215,14 +318,23 @@ const FINGERPRINT_VERSION = 1;
215
318
  * confinement work (against `projectRoot`, the correct boundary for this
216
319
  * data), so no security property is lost by bypassing a narrower seam here.
217
320
  */
218
- function computeCoveredDigest(projectRoot, coveredFiles) {
321
+ function computeCoveredDigest(projectRoot, coveredFiles, version = FINGERPRINT_VERSION, opts = {}) {
322
+ // #4623: `version` selects the input shape to hash under — the CURRENT
323
+ // one for a fresh fingerprint (the CLI verb), or the STORED one when
324
+ // `readVerificationStatus` recomputes against a report's own digest.
325
+ // `opts.phaseDir` lets v2 recognise the phase's own planning root
326
+ // (`sharedPlanningRoots`); without it only `.planning/` itself is shared.
327
+ if (!KNOWN_FINGERPRINT_VERSIONS.has(version))
328
+ return null;
219
329
  const uniqueSorted = canonicalizeCoveredFiles(coveredFiles);
220
330
  if (uniqueSorted.length === 0)
221
331
  return null;
332
+ const sharedRoots = version >= 2 ? sharedPlanningRoots(projectRoot, opts.phaseDir) : [];
333
+ let hashed = 0;
222
334
  // Canonicalize the root ONCE — every candidate's realpath is checked against
223
335
  // this, not the possibly-symlinked `projectRoot` argument itself. Always via
224
336
  // the REAL fs, never fsImpl: `projectRoot` is a trusted anchor the CALLER
225
- // derived (findProjectRoot), not attacker-influenced covered-input data —
337
+ // derived (resolveProjectRoot), not attacker-influenced covered-input data —
226
338
  // routing it through a caller-scoped containment seam (e.g. #4155's
227
339
  // containmentEnforcingVerificationFs, confined to `.planning/`, a proper
228
340
  // SUBSET of `projectRoot`) would reject the root itself and fail every
@@ -251,13 +363,25 @@ function computeCoveredDigest(projectRoot, coveredFiles) {
251
363
  // confinement check above is not enough. realpathSync resolves the
252
364
  // actual target; re-confining against realRoot closes that gap.
253
365
  const real = node_fs_1.default.realpathSync(resolved);
254
- const realRel = node_path_1.default.relative(realRoot, real);
255
- if (realRel === '' || realRel === '..' || realRel.startsWith(`..${node_path_1.default.sep}`) || node_path_1.default.isAbsolute(realRel)) {
366
+ // Both operands are already realpath-resolved (this fn's own realpathSync calls
367
+ // above), so the shared containment comparison applies directly (ADR-4650) —
368
+ // no re-resolution through assertWithinRoot/tryWithinRoot, which would redo work
369
+ // this function already owns for its exists-vs-escaped tri-state.
370
+ if (!(0, security_cjs_1.isContainedIn)(real, realRoot)) {
256
371
  return null;
257
372
  }
258
373
  const st = node_fs_1.default.statSync(real);
259
374
  if (!st.isFile())
260
375
  return null;
376
+ // #4623 (v2+): a repo-wide planning document is VALIDATED exactly as
377
+ // every other covered path — confined, present, a regular file; the
378
+ // fail-closed contract above is unchanged — but its bytes contribute
379
+ // nothing to the digest. It may stay declared in `covered_files` (the
380
+ // verifier's instructions long said to list the mapped requirement,
381
+ // and every report already written does); its bookkeeping churn can
382
+ // no longer read as drift.
383
+ if (isSharedPlanningDoc(rel, sharedRoots))
384
+ continue;
261
385
  bytes = node_fs_1.default.readFileSync(real);
262
386
  }
263
387
  catch {
@@ -265,12 +389,19 @@ function computeCoveredDigest(projectRoot, coveredFiles) {
265
389
  }
266
390
  const fileHash = node_crypto_1.default.createHash('sha256').update(bytes).digest('hex');
267
391
  parts.push(`${rel}\n${fileHash}\n`);
392
+ hashed++;
268
393
  }
394
+ // #4623 (v2+): a declaration made ONLY of shared planning documents has no
395
+ // evidence in it at all — a constant digest over the header would satisfy
396
+ // the fingerprint pair while grounding the verification in nothing. Fail
397
+ // closed, the same way an empty declaration does.
398
+ if (version >= 2 && hashed === 0)
399
+ return null;
269
400
  const aggregate = node_crypto_1.default
270
401
  .createHash('sha256')
271
- .update(`v${FINGERPRINT_VERSION}\n${parts.join('')}`, 'utf-8')
402
+ .update(`v${version}\n${parts.join('')}`, 'utf-8')
272
403
  .digest('hex');
273
- return `v${FINGERPRINT_VERSION}:sha256:${aggregate}`;
404
+ return `v${version}:sha256:${aggregate}`;
274
405
  }
275
406
  /**
276
407
  * #4155: the content fingerprint only recomputes digests for paths the
@@ -471,7 +602,7 @@ function resolvePhaseArtifactFile(entries, bareName, options = {}) {
471
602
  // filtered out, and if that leaves nothing the code falls through to
472
603
  // `allowBare`/`null` deliberately.
473
604
  const scoped = options.phaseDirName
474
- ? scopeToPhase(candidates, options.phaseDirName)
605
+ ? scopeToPhase(candidates, options.phaseDirName, options.convention)
475
606
  : candidates;
476
607
  if (scoped.length > 0)
477
608
  return scoped[0];
@@ -528,7 +659,7 @@ function resolveVerificationFile(entries, options = {}) {
528
659
  function resolveUatFile(entries, options = {}) {
529
660
  return resolvePhaseArtifactFile(entries, 'UAT.md', options);
530
661
  }
531
- function findStaleVerificationSummary(phaseDir, fsImpl = defaultFsImpl, phaseCleanCommitTimesMs = defaultPhaseCleanCommitTimesMs) {
662
+ function findStaleVerificationSummary(phaseDir, fsImpl = defaultFsImpl, phaseCleanCommitTimesMs = defaultPhaseCleanCommitTimesMs, convention) {
532
663
  // FS errors (TOCTOU: a SUMMARY listed by scanPhasePlans then removed before statSync;
533
664
  // unreadable dir; broken symlink; file->dir swap) must degrade rather than throw
534
665
  // uncaught into callers that are NOT under the planning lock (init.manager /
@@ -544,9 +675,20 @@ function findStaleVerificationSummary(phaseDir, fsImpl = defaultFsImpl, phaseCle
544
675
  // or sentinel-numbered canonically-shaped file cannot outrank this
545
676
  // phase's own (possibly non-canonical) report. #3511: phaseDirName scopes
546
677
  // the fallback path to this same phase (see resolveVerificationFile docs).
678
+ // #4187: allowBare — this staleness seam must see the same report set the
679
+ // status reader sees, or a bare report could never read `stale` while its
680
+ // dashed twin could (two answers from one verb).
547
681
  const phaseDirName = node_path_1.default.basename(phaseDir);
548
- const phaseToken = extractPhaseToken(phaseDirName);
549
- const verificationFile = resolveVerificationFile(phaseFiles, { phaseToken, phaseDirName });
682
+ // #612: derive the token with the resolved convention so a bracket dir's
683
+ // own token is read behind its `{CODE}.{MM}-` prefix. #4187: keep the bare
684
+ // report tier aligned with the status reader.
685
+ const phaseToken = extractPhaseToken(phaseDirName, convention);
686
+ const verificationFile = resolveVerificationFile(phaseFiles, {
687
+ allowBare: true,
688
+ phaseToken,
689
+ phaseDirName,
690
+ convention,
691
+ });
550
692
  if (!verificationFile)
551
693
  return { determined: true, stale: false };
552
694
  const summaryFiles = scanPhasePlans(phaseDir).summaryFiles
@@ -587,7 +729,9 @@ function findStaleVerificationSummary(phaseDir, fsImpl = defaultFsImpl, phaseCle
587
729
  * 1. Find the phase's verification report via `resolveVerificationFile`
588
730
  * (canonical `<phase-token>-VERIFICATION.md` preferred; falls back to the
589
731
  * alphabetically-first `*-VERIFICATION.md` that belongs to THIS phase when
590
- * none is canonical — #3357/#3511). If none → status 'missing'.
732
+ * none is canonical — #3357/#3511; and, when the directory's only report
733
+ * is a bare `VERIFICATION.md`, that file — #4187, matching
734
+ * `verification.resolve-file`). If none → status 'missing'.
591
735
  * 2. Extract `status` from FRONTMATTER ONLY via the shared extractFrontmatter
592
736
  * parser (DEFECT.FRONTMATTER-SCALAR-BROAD-GREP fix — parser anchors at byte 0).
593
737
  * If no frontmatter block or no `status` key → status 'missing'.
@@ -608,7 +752,12 @@ function readVerificationStatus(phaseDir, opts = {}) {
608
752
  const fsImpl = opts.fs ?? defaultFsImpl;
609
753
  const phaseCleanCommitTimesMs = opts.phaseCleanCommitTimesMs ?? defaultPhaseCleanCommitTimesMs;
610
754
  const runtime = opts.runtime ?? 'claude';
611
- // Phase token for the gaps_found command
755
+ // Phase token for the gaps_found command — deliberately convention-LESS
756
+ // even when `opts.convention` is present: the token becomes a bare COMMAND
757
+ // ARGUMENT below, and a bare bracket phase number is milestone-ambiguous
758
+ // (`02` cannot tell GSD.01-02 from GSD.02-02), so the argument keeps its
759
+ // pre-#612 shape. The convention-aware token is derived separately for
760
+ // FILE RESOLUTION only (`resolutionToken`, at the readdir below).
612
761
  const baseName = node_path_1.default.basename(phaseDir);
613
762
  const phaseToken = extractPhaseToken(baseName);
614
763
  const derivedPhaseNumber = phaseToken.length > 0 ? phaseToken : baseName;
@@ -624,12 +773,21 @@ function readVerificationStatus(phaseDir, opts = {}) {
624
773
  let verificationFile = null;
625
774
  try {
626
775
  const entries = fsImpl.readdirSync(phaseDir);
627
- // #3492: pin selection to THIS phase's own token (already derived above
628
- // for the routed command argument) so a stray cross-phase or
629
- // sentinel-numbered canonically-shaped file cannot outrank this phase's
630
- // own (possibly non-canonical) report. #3511: baseName also scopes the
631
- // fallback path to this same phase (see resolveVerificationFile docs).
632
- verificationFile = resolveVerificationFile(entries, { phaseToken, phaseDirName: baseName });
776
+ // #3492: pin selection to THIS phase's own token so a stray cross-phase
777
+ // or sentinel-numbered canonically-shaped file cannot outrank this phase's
778
+ // own report. #612: derive a separate convention-aware RESOLUTION token
779
+ // for bracket directories while the routed command argument above stays
780
+ // convention-less and milestone-unambiguous. #4187: keep the bare report
781
+ // tier aligned with every other verification reader.
782
+ const resolutionToken = opts.convention === 'bracket'
783
+ ? extractPhaseToken(baseName, opts.convention)
784
+ : phaseToken;
785
+ verificationFile = resolveVerificationFile(entries, {
786
+ allowBare: true,
787
+ phaseToken: resolutionToken,
788
+ phaseDirName: baseName,
789
+ convention: opts.convention,
790
+ });
633
791
  }
634
792
  catch {
635
793
  // Directory unreadable → treat as missing
@@ -656,6 +814,16 @@ function readVerificationStatus(phaseDir, opts = {}) {
656
814
  // same root cause as the false-clean class fixed elsewhere in #3707-CR.
657
815
  const content = normalizeLineEndings(fsImpl.readFileSync(filePath, 'utf-8'));
658
816
  fm = extractFrontmatter(content, filePath);
817
+ // #4806: an unparseable frontmatter block is NOT "missing" — the file
818
+ // exists and verification ran. Report a distinct status so the caller is
819
+ // sent to fix the YAML, not to re-run execute-phase.
820
+ if (fm[FRONTMATTER_UNPARSEABLE] === true) {
821
+ return {
822
+ status: 'unparseable',
823
+ next_action: "The *-VERIFICATION.md frontmatter is not parseable YAML — fix the syntax error in the report itself. Re-running execute-phase cannot fix a YAML typo in an existing report.",
824
+ next_command: '',
825
+ };
826
+ }
659
827
  const statusVal = fm['status'];
660
828
  // status is always a scalar string in a well-formed VERIFICATION.md frontmatter;
661
829
  // only accept string values — arrays and objects are not valid status values.
@@ -710,13 +878,23 @@ function readVerificationStatus(phaseDir, opts = {}) {
710
878
  // short-circuit in turn: the live-directory re-scan (for a plan/summary
711
879
  // added AFTER verification and never declared in covered_files) only
712
880
  // runs once the digest itself has already matched.
881
+ //
882
+ // #4623: recompute under the STORED digest's own version, not the
883
+ // current constant — a v1 report written before the shared-document
884
+ // exclusion keeps v1 semantics rather than going stale on upgrade. An
885
+ // unknown version parses to `null`, which `computeCoveredDigest`
886
+ // refuses (returns `null`), so the compare below fails closed.
887
+ const storedVersion = hasWellFormedFingerprint && typeof coveredDigestVal === 'string'
888
+ ? parseFingerprintVersion(coveredDigestVal)
889
+ : null;
713
890
  isStale =
714
891
  !hasWellFormedFingerprint ||
715
- computeCoveredDigest((0, project_root_cjs_1.findProjectRoot)(phaseDir), coveredFilesVal) !== coveredDigestVal ||
892
+ storedVersion === null ||
893
+ computeCoveredDigest((0, project_root_cjs_1.resolveProjectRoot)(phaseDir), coveredFilesVal, storedVersion, { phaseDir }) !== coveredDigestVal ||
716
894
  !allCurrentArtifactsCovered(phaseDir, coveredFilesVal);
717
895
  }
718
896
  else {
719
- const staleCheck = findStaleVerificationSummary(phaseDir, fsImpl, phaseCleanCommitTimesMs);
897
+ const staleCheck = findStaleVerificationSummary(phaseDir, fsImpl, phaseCleanCommitTimesMs, opts.convention);
720
898
  isStale = staleCheck.determined && staleCheck.stale;
721
899
  // staleCheck is either {determined:true, stale:false} (checked; nothing
722
900
  // stale) or {determined:false} (could not check — fs/scan/clock failure).
@@ -730,7 +908,10 @@ function readVerificationStatus(phaseDir, opts = {}) {
730
908
  return {
731
909
  status: entry.status,
732
910
  next_action: entry.next_action,
733
- next_command: projectNextCommand('verify-work', runtime, phaseArg),
911
+ // #4682: execute-phase resumes at the verification gates and re-runs
912
+ // the verifier, regenerating VERIFICATION.md and its digest — the same
913
+ // routing the `missing` sentinel has used since #2868.
914
+ next_command: projectNextCommand('execute-phase', runtime, phaseArg),
734
915
  };
735
916
  }
736
917
  // 3. Route — exclude internal sentinels from raw-file lookup (they are
@@ -800,6 +981,7 @@ function isPhaseComplete(phaseDir, deps = {}) {
800
981
  phaseCleanCommitTimesMs: deps.phaseCleanCommitTimesMs,
801
982
  runtime: deps.runtime,
802
983
  phaseNumber: deps.phaseNumber,
984
+ convention: deps.convention,
803
985
  });
804
986
  return {
805
987
  value: {
@@ -866,6 +1048,69 @@ function cmdVerificationResolveFile(cwd, phaseDirArg, raw) {
866
1048
  }
867
1049
  output({ verification_file: verificationPath }, raw, verificationPath);
868
1050
  }
1051
+ /**
1052
+ * #4623: parse the argv tokens after `verification.fingerprint <phaseDir>`
1053
+ * into a covered-file list. The router hands over a raw positional slice,
1054
+ * so every `--files`-style form other `gsd-tools` verbs accept (`commit
1055
+ * --files a b`, `docs/CLI-TOOLS.md`) used to reach `computeCoveredDigest`
1056
+ * with the literal token `--files` — or an unsplit `"a,b"` — as a covered
1057
+ * path, and the whole command failed closed with "a covered file is
1058
+ * missing, unreadable, or escapes the project root". On the reporting
1059
+ * project that message convinced two people the digest was permanently
1060
+ * unrecomputable.
1061
+ *
1062
+ * Accepted, all equivalent and freely mixed:
1063
+ * - bare positionals `a b` (the documented form, unchanged)
1064
+ * - a single flag `--files a`
1065
+ * - a comma-separated value `--files a,b` (also `--files=a,b`)
1066
+ * - a repeated flag `--files a --files b`
1067
+ *
1068
+ * Only a `--files` VALUE is comma-split: a bare positional keeps its bytes,
1069
+ * so the documented form's behaviour on a comma-bearing filename is
1070
+ * unchanged. Any other `--flag` is an explicit usage error, never a path —
1071
+ * a mis-typed flag must not fail as "file missing" again. (`--raw` never
1072
+ * reaches here; the CLI entry point splices it out before routing.)
1073
+ */
1074
+ function parseFingerprintFileArgs(tokens) {
1075
+ const files = [];
1076
+ const EMPTY_VALUE = '--files requires at least one path for verification.fingerprint (a path, or a comma-separated list)';
1077
+ const splitList = (value) => value
1078
+ .split(',')
1079
+ .map((s) => s.trim())
1080
+ .filter((s) => s.length > 0);
1081
+ for (let i = 0; i < tokens.length; i++) {
1082
+ const token = tokens[i];
1083
+ if (token === '--files') {
1084
+ const value = tokens[i + 1];
1085
+ if (value === undefined || value.startsWith('--')) {
1086
+ return { error: '--files requires a value for verification.fingerprint (a path, or a comma-separated list)' };
1087
+ }
1088
+ const list = splitList(value);
1089
+ // An empty or all-comma value is a usage error, never a silent no-op —
1090
+ // the caller would otherwise meet the generic zero-files error and go
1091
+ // looking for a missing path.
1092
+ if (list.length === 0)
1093
+ return { error: EMPTY_VALUE };
1094
+ files.push(...list);
1095
+ i++;
1096
+ }
1097
+ else if (token.startsWith('--files=')) {
1098
+ const list = splitList(token.slice('--files='.length));
1099
+ if (list.length === 0)
1100
+ return { error: EMPTY_VALUE };
1101
+ files.push(...list);
1102
+ }
1103
+ else if (token.startsWith('--')) {
1104
+ return {
1105
+ error: `unknown flag ${token} for verification.fingerprint (covered files are bare positionals or --files <a[,b]>, repeatable)`,
1106
+ };
1107
+ }
1108
+ else {
1109
+ files.push(token);
1110
+ }
1111
+ }
1112
+ return { files };
1113
+ }
869
1114
  /**
870
1115
  * CLI command handler (#4155): compute the covered-input fingerprint the
871
1116
  * verifier embeds in VERIFICATION.md frontmatter (`covered_files`,
@@ -881,7 +1126,13 @@ function cmdVerificationResolveFile(cwd, phaseDirArg, raw) {
881
1126
  * @param cwd - Current working directory.
882
1127
  * @param phaseDirArg - Phase directory path (absolute or relative to cwd);
883
1128
  * its project root is the base covered paths resolve against.
884
- * @param files - Covered-input paths, relative to the project root.
1129
+ * Must be an existing directory (#4623): with the
1130
+ * phase dir omitted, the first covered file used to be
1131
+ * taken as the phase dir and the rest hashed — a
1132
+ * plausible digest over the wrong set, at exit 0.
1133
+ * @param fileArgs - The argv tokens after the phase dir, parsed by
1134
+ * `parseFingerprintFileArgs`: covered-input paths
1135
+ * relative to the project root, bare or via `--files`.
885
1136
  * @param raw - Whether to emit raw (non-JSON) output: just the
886
1137
  * `covered_digest` string, so `VAR=$(gsd_run query
887
1138
  * verification.fingerprint "$PHASE_DIR" ... --raw)` is
@@ -889,17 +1140,34 @@ function cmdVerificationResolveFile(cwd, phaseDirArg, raw) {
889
1140
  * from the caller's own input list in that mode, so
890
1141
  * only the computed digest needs a raw form.
891
1142
  */
892
- function cmdVerificationFingerprint(cwd, phaseDirArg, files, raw) {
1143
+ function cmdVerificationFingerprint(cwd, phaseDirArg, fileArgs, raw) {
893
1144
  if (!phaseDirArg) {
894
1145
  error('phase directory required for verification.fingerprint');
895
1146
  return;
896
1147
  }
1148
+ const phaseDir = node_path_1.default.resolve(cwd, phaseDirArg);
1149
+ let phaseDirIsDir = false;
1150
+ try {
1151
+ phaseDirIsDir = node_fs_1.default.statSync(phaseDir).isDirectory();
1152
+ }
1153
+ catch {
1154
+ // not found → not a directory
1155
+ }
1156
+ if (!phaseDirIsDir) {
1157
+ error(`phase directory not found: ${phaseDirArg} — verification.fingerprint takes the phase directory first, then the covered files`);
1158
+ return;
1159
+ }
1160
+ const parsed = parseFingerprintFileArgs(fileArgs);
1161
+ if ('error' in parsed) {
1162
+ error(parsed.error);
1163
+ return;
1164
+ }
1165
+ const files = parsed.files;
897
1166
  if (files.length === 0) {
898
1167
  error('at least one covered file required for verification.fingerprint');
899
1168
  return;
900
1169
  }
901
- const phaseDir = node_path_1.default.resolve(cwd, phaseDirArg);
902
- const projectRoot = (0, project_root_cjs_1.findProjectRoot)(phaseDir);
1170
+ const projectRoot = (0, project_root_cjs_1.resolveProjectRoot)(phaseDir);
903
1171
  // canonicalizeCoveredFiles here is for the emitted `covered_files` field —
904
1172
  // computeCoveredDigest canonicalizes its own `coveredFiles` argument
905
1173
  // internally too (it must, for callers like readVerificationStatus that
@@ -907,8 +1175,21 @@ function cmdVerificationFingerprint(cwd, phaseDirArg, files, raw) {
907
1175
  // already-canonical list keeps that internal pass a cheap no-op rather
908
1176
  // than a second meaningfully different canonicalization.
909
1177
  const uniqueSorted = canonicalizeCoveredFiles(files);
910
- const digest = computeCoveredDigest(projectRoot, uniqueSorted);
1178
+ const digest = computeCoveredDigest(projectRoot, uniqueSorted, FINGERPRINT_VERSION, { phaseDir });
911
1179
  if (digest === null) {
1180
+ // #4623: name the one null that is NOT a bad path — a declaration made
1181
+ // only of shared planning documents hashes nothing under v2, and the
1182
+ // generic message below would send the caller looking for a missing file
1183
+ // that is not missing. Discriminated AFTER the v2 attempt, and only when a
1184
+ // v1 pass over the same list (which hashes, and therefore validates, every
1185
+ // path) succeeds: an all-shared list with a missing or directory member is
1186
+ // a bad path first, and gets the generic message.
1187
+ const sharedRoots = sharedPlanningRoots(projectRoot, phaseDir);
1188
+ if (uniqueSorted.every((f) => isSharedPlanningDoc(f, sharedRoots)) &&
1189
+ computeCoveredDigest(projectRoot, uniqueSorted, 1) !== null) {
1190
+ error(`could not compute fingerprint — every covered file is a repo-wide planning document (direct children of ${sharedRoots.join(', ')} never enter the digest); declare the phase's own artifacts and implementation files`);
1191
+ return;
1192
+ }
912
1193
  error('could not compute fingerprint — a covered file is missing, unreadable, or escapes the project root');
913
1194
  return;
914
1195
  }
@@ -926,5 +1207,9 @@ module.exports = {
926
1207
  cmdVerificationStatus,
927
1208
  cmdVerificationResolveFile,
928
1209
  computeCoveredDigest,
1210
+ sharedPlanningRoots,
1211
+ isSharedPlanningDoc,
1212
+ parseFingerprintVersion,
1213
+ parseFingerprintFileArgs,
929
1214
  cmdVerificationFingerprint,
930
1215
  };