@opengsd/gsd-core 1.18.0 → 1.20.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 (308) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +2 -1
  3. package/.opencode/plugins/gsd-core.js +2 -3
  4. package/agents/gsd-code-fixer.compact.md +7 -7
  5. package/agents/gsd-code-fixer.md +13 -7
  6. package/agents/gsd-code-reviewer.compact.md +49 -8
  7. package/agents/gsd-code-reviewer.md +53 -14
  8. package/agents/gsd-executor.md +8 -4
  9. package/agents/gsd-verifier.md +1 -1
  10. package/bin/install.js +422 -103
  11. package/commands/gsd/update.md +5 -2
  12. package/gsd-core/bin/gsd-tools.cjs +179 -30
  13. package/gsd-core/bin/lib/agent-install-check.cjs +29 -3
  14. package/gsd-core/bin/lib/args-terminator.cjs +114 -0
  15. package/gsd-core/bin/lib/broken-windows.cjs +59 -1
  16. package/gsd-core/bin/lib/capability-lifecycle.cjs +1 -12
  17. package/gsd-core/bin/lib/capability-lock.cjs +13 -2
  18. package/gsd-core/bin/lib/capability-registry.cjs +376 -103
  19. package/gsd-core/bin/lib/capability-state.cjs +14 -8
  20. package/gsd-core/bin/lib/capability-validator.cjs +32 -0
  21. package/gsd-core/bin/lib/commands.cjs +27 -17
  22. package/gsd-core/bin/lib/config-loader.cjs +13 -3
  23. package/gsd-core/bin/lib/config.cjs +256 -58
  24. package/gsd-core/bin/lib/core-utils.cjs +45 -0
  25. package/gsd-core/bin/lib/decision-coverage-support.cjs +74 -0
  26. package/gsd-core/bin/lib/frontmatter.cjs +21 -3
  27. package/gsd-core/bin/lib/gate-api-coverage-verify-pre.cjs +2 -2
  28. package/gsd-core/bin/lib/gate-context-drift.cjs +9 -4
  29. package/gsd-core/bin/lib/gate-decision-coverage-plan.cjs +6 -1
  30. package/gsd-core/bin/lib/gate-decision-coverage-verify.cjs +6 -1
  31. package/gsd-core/bin/lib/gate-evaluation-scope.cjs +134 -13
  32. package/gsd-core/bin/lib/gate-phase-context.cjs +7 -8
  33. package/gsd-core/bin/lib/gate-schema-drift.cjs +1 -1
  34. package/gsd-core/bin/lib/gate-tdd-review-checkpoint.cjs +63 -22
  35. package/gsd-core/bin/lib/gate-ui-safety.cjs +23 -5
  36. package/gsd-core/bin/lib/git-info-exclude.cjs +52 -0
  37. package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +31 -12
  38. package/gsd-core/bin/lib/health-diagnostic-rules/roadmap-disk-consistency.cjs +9 -7
  39. package/gsd-core/bin/lib/health-diagnostic-rules/state-consistency.cjs +3 -3
  40. package/gsd-core/bin/lib/health-diagnostic-types.cjs +4 -0
  41. package/gsd-core/bin/lib/health-diagnostic.cjs +52 -1
  42. package/gsd-core/bin/lib/host-integration.cjs +13 -6
  43. package/gsd-core/bin/lib/init.cjs +245 -68
  44. package/gsd-core/bin/lib/install-engine.cjs +246 -32
  45. package/gsd-core/bin/lib/install-profiles.cjs +9 -4
  46. package/gsd-core/bin/lib/installer-migration-report.cjs +2 -0
  47. package/gsd-core/bin/lib/io.cjs +6 -0
  48. package/gsd-core/bin/lib/markdown-line-diff.cjs +147 -0
  49. package/gsd-core/bin/lib/markdown-sectionizer.cjs +147 -1
  50. package/gsd-core/bin/lib/markdown-table.cjs +23 -0
  51. package/gsd-core/bin/lib/mcp-server.cjs +8 -5
  52. package/gsd-core/bin/lib/milestone-lock.cjs +8 -1
  53. package/gsd-core/bin/lib/milestone-release-map.cjs +244 -0
  54. package/gsd-core/bin/lib/milestone-release-tags.cjs +440 -0
  55. package/gsd-core/bin/lib/milestone.cjs +61 -51
  56. package/gsd-core/bin/lib/model-catalog.cjs +8 -0
  57. package/gsd-core/bin/lib/normalize-test-command.cjs +67 -0
  58. package/gsd-core/bin/lib/phase-entry-grammar.cjs +119 -0
  59. package/gsd-core/bin/lib/phase-id-display.cjs +2 -0
  60. package/gsd-core/bin/lib/phase-id.cjs +629 -38
  61. package/gsd-core/bin/lib/phase-lifecycle.cjs +12 -44
  62. package/gsd-core/bin/lib/phase-locator.cjs +328 -19
  63. package/gsd-core/bin/lib/phase.cjs +3066 -653
  64. package/gsd-core/bin/lib/planning-document.cjs +26 -5
  65. package/gsd-core/bin/lib/planning-inspect.cjs +27 -35
  66. package/gsd-core/bin/lib/planning-snapshot.cjs +15 -8
  67. package/gsd-core/bin/lib/planning-workspace.cjs +21 -3
  68. package/gsd-core/bin/lib/pristine-baseline.cjs +11 -3
  69. package/gsd-core/bin/lib/read-only-git-env.cjs +79 -0
  70. package/gsd-core/bin/lib/refactor-trigger-command-router.cjs +2 -2
  71. package/gsd-core/bin/lib/review-lane-descriptor.cjs +57 -2
  72. package/gsd-core/bin/lib/review-lane-invocation.cjs +125 -18
  73. package/gsd-core/bin/lib/review-lane-runner.cjs +33 -4
  74. package/gsd-core/bin/lib/reviewer-step-dispatch.cjs +11 -9
  75. package/gsd-core/bin/lib/roadmap-parser.cjs +735 -137
  76. package/gsd-core/bin/lib/roadmap-progress.cjs +136 -0
  77. package/gsd-core/bin/lib/roadmap-upgrade.cjs +47 -7
  78. package/gsd-core/bin/lib/roadmap.cjs +383 -123
  79. package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +381 -14
  80. package/gsd-core/bin/lib/runtime-artifact-layout.cjs +8 -4
  81. package/gsd-core/bin/lib/runtime-hooks-surface.cjs +60 -16
  82. package/gsd-core/bin/lib/shell-command-projection.cjs +151 -19
  83. package/gsd-core/bin/lib/smart-entry.cjs +35 -11
  84. package/gsd-core/bin/lib/state-contract.cjs +39 -15
  85. package/gsd-core/bin/lib/state-document.cjs +344 -27
  86. package/gsd-core/bin/lib/state-transition.cjs +156 -32
  87. package/gsd-core/bin/lib/state.cjs +527 -186
  88. package/gsd-core/bin/lib/surface.cjs +9 -0
  89. package/gsd-core/bin/lib/task-command-router.cjs +5 -25
  90. package/gsd-core/bin/lib/task-content-resolution.cjs +46 -1
  91. package/gsd-core/bin/lib/template.cjs +2 -2
  92. package/gsd-core/bin/lib/validate.cjs +3 -2
  93. package/gsd-core/bin/lib/vendor/re2js.cjs +863 -577
  94. package/gsd-core/bin/lib/vendor/re2js.d.cts +284 -219
  95. package/gsd-core/bin/lib/verification.cjs +96 -9
  96. package/gsd-core/bin/lib/verify.cjs +4 -2
  97. package/gsd-core/bin/lib/workstream-inventory.cjs +26 -20
  98. package/gsd-core/bin/lib/worktree-safety.cjs +22 -1
  99. package/gsd-core/bin/shared/config-defaults.manifest.json +1 -0
  100. package/gsd-core/bin/shared/config-schema.manifest.json +1 -0
  101. package/gsd-core/bin/verify-reapply-patches.cjs +411 -10
  102. package/gsd-core/references/cicd-review-patterns.md +45 -0
  103. package/gsd-core/references/git-integration.md +11 -0
  104. package/gsd-core/references/loop-hook-dispatch.md +13 -6
  105. package/gsd-core/references/necessity-review.md +48 -0
  106. package/gsd-core/references/planning-config.md +2 -0
  107. package/gsd-core/references/tdd.md +2 -2
  108. package/gsd-core/templates/DEBUG.md +16 -0
  109. package/gsd-core/templates/config.json +2 -1
  110. package/gsd-core/templates/verification-report.md +1 -1
  111. package/gsd-core/workflows/audit-fix.md +2 -1
  112. package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +10 -1
  113. package/gsd-core/workflows/code-review-fix.md +46 -5
  114. package/gsd-core/workflows/code-review.md +64 -7
  115. package/gsd-core/workflows/complete-milestone/steps/project-md-evolution-review.md +145 -0
  116. package/gsd-core/workflows/complete-milestone/steps/release-map.md +80 -0
  117. package/gsd-core/workflows/complete-milestone.md +37 -150
  118. package/gsd-core/workflows/diagnose-issues.md +29 -5
  119. package/gsd-core/workflows/discuss-phase/modes/power.md +1 -1
  120. package/gsd-core/workflows/discuss-phase-assumptions.md +33 -2
  121. package/gsd-core/workflows/discuss-phase-power.md +19 -2
  122. package/gsd-core/workflows/discuss-phase.md +6 -10
  123. package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +13 -7
  124. package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +7 -2
  125. package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +2 -1
  126. package/gsd-core/workflows/execute-phase/steps/verify-phase-goal.md +2 -2
  127. package/gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md +11 -2
  128. package/gsd-core/workflows/execute-phase.md +10 -6
  129. package/gsd-core/workflows/execute-plan.md +1 -1
  130. package/gsd-core/workflows/health.md +2 -1
  131. package/gsd-core/workflows/import.md +1 -1
  132. package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +2 -2
  133. package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +13 -9
  134. package/gsd-core/workflows/plan-phase.md +14 -14
  135. package/gsd-core/workflows/pr-branch.md +2 -0
  136. package/gsd-core/workflows/quick/steps/plan-checker-loop.md +1 -1
  137. package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +8 -1
  138. package/gsd-core/workflows/quick-batch/steps/plan-checker-loop.md +2 -1
  139. package/gsd-core/workflows/quick.md +1 -1
  140. package/gsd-core/workflows/reapply-patches.md +104 -0
  141. package/gsd-core/workflows/review.md +70 -154
  142. package/gsd-core/workflows/section-manifest.json +5 -0
  143. package/gsd-core/workflows/secure-phase.md +1 -1
  144. package/gsd-core/workflows/settings-advanced.md +57 -9
  145. package/gsd-core/workflows/settings.md +1 -0
  146. package/gsd-core/workflows/ship.md +15 -4
  147. package/gsd-core/workflows/spec-phase.md +3 -6
  148. package/gsd-core/workflows/verify-work.md +4 -3
  149. package/hooks/dist/gsd-agent-isolation-guard.js +11 -1
  150. package/hooks/dist/gsd-cursor-subagent-start.js +6 -2
  151. package/hooks/dist/gsd-graphify-update.sh +6 -0
  152. package/hooks/dist/gsd-path-export.js +95 -0
  153. package/hooks/dist/gsd-secret-read-guard.js +172 -39
  154. package/hooks/dist/gsd-statusline.js +37 -3
  155. package/hooks/dist/gsd-windsurf-pre-write.js +29 -2
  156. package/hooks/dist/gsd-workflow-guard.js +3 -2
  157. package/hooks/dist/gsd-worktree-path-guard.js +29 -2
  158. package/hooks/dist/lib/git-probe.js +16 -0
  159. package/hooks/dist/lib/isolation-sentinel.js +44 -0
  160. package/hooks/dist/managed-hooks-registry.cjs +1 -0
  161. package/hooks/gsd-agent-isolation-guard.js +11 -1
  162. package/hooks/gsd-cursor-subagent-start.js +6 -2
  163. package/hooks/gsd-graphify-update.sh +6 -0
  164. package/hooks/gsd-path-export.js +95 -0
  165. package/hooks/gsd-secret-read-guard.js +172 -39
  166. package/hooks/gsd-statusline.js +37 -3
  167. package/hooks/gsd-windsurf-pre-write.js +29 -2
  168. package/hooks/gsd-workflow-guard.js +3 -2
  169. package/hooks/gsd-worktree-path-guard.js +29 -2
  170. package/hooks/hooks.json +1 -0
  171. package/hooks/lib/git-probe.js +16 -0
  172. package/hooks/lib/isolation-sentinel.js +44 -0
  173. package/hooks/managed-hooks-registry.cjs +1 -0
  174. package/package.json +15 -8
  175. package/pi/gsd.cjs +1047 -10
  176. package/pi/package-agents/gsd-advisor-researcher.md +113 -0
  177. package/pi/package-agents/gsd-ai-researcher.md +111 -0
  178. package/pi/package-agents/gsd-assumptions-analyzer.md +110 -0
  179. package/pi/package-agents/gsd-code-fixer.md +774 -0
  180. package/pi/package-agents/gsd-code-reviewer.md +441 -0
  181. package/pi/package-agents/gsd-codebase-mapper.md +850 -0
  182. package/pi/package-agents/gsd-debug-session-manager.md +416 -0
  183. package/pi/package-agents/gsd-debugger.md +1274 -0
  184. package/pi/package-agents/gsd-doc-classifier.md +269 -0
  185. package/pi/package-agents/gsd-doc-synthesizer.md +259 -0
  186. package/pi/package-agents/gsd-doc-verifier.md +212 -0
  187. package/pi/package-agents/gsd-doc-writer.md +626 -0
  188. package/pi/package-agents/gsd-dom-verifier.md +164 -0
  189. package/pi/package-agents/gsd-domain-researcher.md +144 -0
  190. package/pi/package-agents/gsd-eval-auditor.md +185 -0
  191. package/pi/package-agents/gsd-eval-planner.md +149 -0
  192. package/pi/package-agents/gsd-executor.md +898 -0
  193. package/pi/package-agents/gsd-framework-selector.md +159 -0
  194. package/pi/package-agents/gsd-integration-checker.md +476 -0
  195. package/pi/package-agents/gsd-intel-updater.md +338 -0
  196. package/pi/package-agents/gsd-mempalace-curator.md +49 -0
  197. package/pi/package-agents/gsd-nyquist-auditor.md +200 -0
  198. package/pi/package-agents/gsd-pattern-mapper.md +340 -0
  199. package/pi/package-agents/gsd-phase-researcher.md +897 -0
  200. package/pi/package-agents/gsd-plan-checker.md +1077 -0
  201. package/pi/package-agents/gsd-planner.md +1003 -0
  202. package/pi/package-agents/gsd-project-researcher.md +611 -0
  203. package/pi/package-agents/gsd-research-synthesizer.md +259 -0
  204. package/pi/package-agents/gsd-roadmapper.md +779 -0
  205. package/pi/package-agents/gsd-security-auditor.md +171 -0
  206. package/pi/package-agents/gsd-ui-auditor.md +581 -0
  207. package/pi/package-agents/gsd-ui-checker.md +417 -0
  208. package/pi/package-agents/gsd-ui-researcher.md +469 -0
  209. package/pi/package-agents/gsd-user-profiler.md +174 -0
  210. package/pi/package-agents/gsd-verifier.md +963 -0
  211. package/pi/package-agents/manifest.json +39 -0
  212. package/pi/package-skills/gsd-add-tests/SKILL.md +39 -0
  213. package/pi/package-skills/gsd-ai-integration-phase/SKILL.md +36 -0
  214. package/pi/package-skills/gsd-audit-fix/SKILL.md +34 -0
  215. package/pi/package-skills/gsd-audit-milestone/SKILL.md +40 -0
  216. package/pi/package-skills/gsd-audit-uat/SKILL.md +26 -0
  217. package/pi/package-skills/gsd-autonomous/SKILL.md +52 -0
  218. package/pi/package-skills/gsd-capture/SKILL.md +68 -0
  219. package/pi/package-skills/gsd-cleanup/SKILL.md +25 -0
  220. package/pi/package-skills/gsd-code-review/SKILL.md +71 -0
  221. package/pi/package-skills/gsd-complete-milestone/SKILL.md +147 -0
  222. package/pi/package-skills/gsd-config/SKILL.md +60 -0
  223. package/pi/package-skills/gsd-debug/SKILL.md +64 -0
  224. package/pi/package-skills/gsd-discuss-phase/SKILL.md +74 -0
  225. package/pi/package-skills/gsd-docs-update/SKILL.md +50 -0
  226. package/pi/package-skills/gsd-eval-review/SKILL.md +35 -0
  227. package/pi/package-skills/gsd-execute-phase/SKILL.md +62 -0
  228. package/pi/package-skills/gsd-explore/SKILL.md +26 -0
  229. package/pi/package-skills/gsd-extract-learnings/SKILL.md +25 -0
  230. package/pi/package-skills/gsd-fast/SKILL.md +33 -0
  231. package/pi/package-skills/gsd-forensics/SKILL.md +59 -0
  232. package/pi/package-skills/gsd-graphify/SKILL.md +210 -0
  233. package/pi/package-skills/gsd-health/SKILL.md +36 -0
  234. package/pi/package-skills/gsd-help/SKILL.md +37 -0
  235. package/pi/package-skills/gsd-import/SKILL.md +46 -0
  236. package/pi/package-skills/gsd-inbox/SKILL.md +42 -0
  237. package/pi/package-skills/gsd-ingest-docs/SKILL.md +43 -0
  238. package/pi/package-skills/gsd-manager/SKILL.md +46 -0
  239. package/pi/package-skills/gsd-map-codebase/SKILL.md +85 -0
  240. package/pi/package-skills/gsd-mempalace-capture/SKILL.md +111 -0
  241. package/pi/package-skills/gsd-mempalace-recall/SKILL.md +111 -0
  242. package/pi/package-skills/gsd-milestone-summary/SKILL.md +54 -0
  243. package/pi/package-skills/gsd-mvp-phase/SKILL.md +45 -0
  244. package/pi/package-skills/gsd-new-milestone/SKILL.md +49 -0
  245. package/pi/package-skills/gsd-new-project/SKILL.md +49 -0
  246. package/pi/package-skills/gsd-next/SKILL.md +34 -0
  247. package/pi/package-skills/gsd-ns-context/SKILL.md +31 -0
  248. package/pi/package-skills/gsd-ns-ideate/SKILL.md +30 -0
  249. package/pi/package-skills/gsd-ns-manage/SKILL.md +42 -0
  250. package/pi/package-skills/gsd-ns-project/SKILL.md +34 -0
  251. package/pi/package-skills/gsd-ns-review/SKILL.md +35 -0
  252. package/pi/package-skills/gsd-ns-workflow/SKILL.md +44 -0
  253. package/pi/package-skills/gsd-onboard/SKILL.md +46 -0
  254. package/pi/package-skills/gsd-pause-work/SKILL.md +48 -0
  255. package/pi/package-skills/gsd-phase/SKILL.md +61 -0
  256. package/pi/package-skills/gsd-plan-phase/SKILL.md +62 -0
  257. package/pi/package-skills/gsd-plan-review-convergence/SKILL.md +63 -0
  258. package/pi/package-skills/gsd-pr-branch/SKILL.md +31 -0
  259. package/pi/package-skills/gsd-profile-user/SKILL.md +48 -0
  260. package/pi/package-skills/gsd-progress/SKILL.md +51 -0
  261. package/pi/package-skills/gsd-quick/SKILL.md +179 -0
  262. package/pi/package-skills/gsd-quick-batch/SKILL.md +114 -0
  263. package/pi/package-skills/gsd-resume-work/SKILL.md +31 -0
  264. package/pi/package-skills/gsd-review/SKILL.md +50 -0
  265. package/pi/package-skills/gsd-review-backlog/SKILL.md +65 -0
  266. package/pi/package-skills/gsd-secure-phase/SKILL.md +37 -0
  267. package/pi/package-skills/gsd-settings/SKILL.md +30 -0
  268. package/pi/package-skills/gsd-ship/SKILL.md +27 -0
  269. package/pi/package-skills/gsd-sketch/SKILL.md +56 -0
  270. package/pi/package-skills/gsd-spec-phase/SKILL.md +63 -0
  271. package/pi/package-skills/gsd-spike/SKILL.md +53 -0
  272. package/pi/package-skills/gsd-stats/SKILL.md +22 -0
  273. package/pi/package-skills/gsd-surface/SKILL.md +177 -0
  274. package/pi/package-skills/gsd-thread/SKILL.md +29 -0
  275. package/pi/package-skills/gsd-ui-phase/SKILL.md +35 -0
  276. package/pi/package-skills/gsd-ui-review/SKILL.md +35 -0
  277. package/pi/package-skills/gsd-ultraplan-phase/SKILL.md +38 -0
  278. package/pi/package-skills/gsd-undo/SKILL.md +38 -0
  279. package/pi/package-skills/gsd-update/SKILL.md +54 -0
  280. package/pi/package-skills/gsd-validate-phase/SKILL.md +37 -0
  281. package/pi/package-skills/gsd-verify-work/SKILL.md +41 -0
  282. package/pi/package-skills/gsd-workspace/SKILL.md +57 -0
  283. package/pi/package-skills/gsd-workstreams/SKILL.md +73 -0
  284. package/scripts/build-hooks.js +4 -0
  285. package/scripts/ci-next-health.cjs +246 -23
  286. package/scripts/command-contract-helpers.cjs +1 -1
  287. package/scripts/docs-guard-registry.cjs +22 -7
  288. package/scripts/gen-features.cjs +14 -2
  289. package/scripts/gen-health-docs.cjs +13 -1
  290. package/scripts/gen-pi-package-skills.cjs +217 -0
  291. package/scripts/lib/drift-scan.cjs +27 -0
  292. package/scripts/lib/macos-conformance-tier.generated.cjs +17 -1
  293. package/scripts/lib/platform-conformance-tier.generated.cjs +37 -1
  294. package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +3 -3
  295. package/scripts/lint-phase-enumeration-drift.cjs +8 -4
  296. package/scripts/lint-retired-runtime-name.cjs +5 -0
  297. package/scripts/lint-roadmap-progress-drift.cjs +421 -0
  298. package/scripts/mutation-matrix.cjs +23 -1
  299. package/scripts/npm-audit-baseline.cjs +41 -7
  300. package/scripts/reviewed-or-maintainer-policy.cjs +181 -0
  301. package/scripts/select-docs-guards.cjs +1 -1
  302. package/scripts/self-test-report-policy.cjs +350 -0
  303. package/scripts/sync-next-version.cjs +38 -6
  304. package/skills/gsd-update/SKILL.md +5 -2
  305. package/vscode/package.json +1 -1
  306. package/scripts/diagnose-validate-commit-in-tier.cjs +0 -270
  307. package/scripts/diagnose-validate-commit-phases.cjs +0 -348
  308. package/scripts/xtrace-phase-timing.cjs +0 -205
@@ -2,15 +2,24 @@
2
2
 
3
3
  Detail for the `kind == "gate"` dispatch at the execute:wave:post hook point, extracted from the host step 5.7 (#3606 — the host file carries a frozen pre-phase-6 byte ceiling, #1168; evaluation detail lives here).
4
4
 
5
- ⚠ **Validate `check` before shell use** (third-party manifest input) — `gsd-core/references/loop-hook-dispatch.md` § `gate`.
5
+ ⚠ **Validate `check` before shell use** (third-party manifest input; wherever `${hook.check.query}` appears, write the dot-normalized query, each `.` as `-`, and validate and pass that same value) — `gsd-core/references/loop-hook-dispatch.md` § `gate`.
6
6
 
7
- **For each active entry where `kind == "gate"`** (process in array order), run the gate check — a `predicate` gate (ADR-2008 / #2008) uses the check CLI's `predicate` form with the predicate JSON and phase number (shown in the block below):
7
+ **For each active entry where `kind == "gate"`** (process in array order), run the gate check — a `predicate` gate (ADR-2008 / #2008) uses the check CLI's `predicate` form with the predicate JSON, phase directory and phase number (shown in the second block below):
8
+
9
+ When `check.query` is set (a named-query gate):
8
10
 
9
11
  ```bash
10
12
  _GSD_SHIM_NAME="gsd-tools.cjs"; _GSD_RUNTIME_ROOT="${RUNTIME_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"; GSD_TOOLS="${_GSD_RUNTIME_ROOT}/gsd-core/bin/${_GSD_SHIM_NAME}"; _gsd_at() { for _p; do if [ -f "$_p" ]; then GSD_TOOLS="$_p"; return 0; fi; done; return 1; }; _gsd_id_ok() { case "$("$1" runtime-identity --raw 2>/dev/null || true)" in '{"packageName":"@opengsd/gsd-core"'*'}') return 0;; *) return 1;; esac; }; _gsd_homes() { set -- "${CLAUDE_CONFIG_DIR:-$HOME/.claude}" "${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}" "$HOME/.gemini/antigravity-ide" "$HOME/.gemini/antigravity-cli" "${AUGMENT_CONFIG_DIR:-$HOME/.augment}" "${CLINE_CONFIG_DIR:-$HOME/.cline}" "${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}" "${CODEX_HOME:-$HOME/.codex}" "${COPILOT_CONFIG_DIR:-${COPILOT_HOME:-$HOME/.copilot}}" "${CURSOR_CONFIG_DIR:-$HOME/.cursor}" "${HERMES_HOME:-$HOME/.hermes}" "${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}" "${KIMI_CONFIG_DIR:-$HOME/.config/agents}" "$HOME/.agents" "${KIMI_CODE_HOME:-$HOME/.kimi-code}" "${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}" "${PI_CODING_AGENT_DIR:-$HOME/.pi/agent}" "${QWEN_CONFIG_DIR:-$HOME/.qwen}" "${TRAE_CONFIG_DIR:-$HOME/.trae}" "${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}" "${ZCODE_CONFIG_DIR:-$HOME/.zcode}" "${GROK_AGENTS_HOME:-$HOME/.agents}"; for _h; do _gsd_at "$_h/gsd-core/bin/${_GSD_SHIM_NAME}" && return 0; done; return 1; }; if _gsd_at "${_GSD_RUNTIME_ROOT}/gsd-core/bin/${_GSD_SHIM_NAME}" "${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" "${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}"; then gsd_run() { node "$GSD_TOOLS" "$@"; }; elif _gsd_homes; then gsd_run() { node "$GSD_TOOLS" "$@"; }; elif unset -f gsd_run; _G="$(command -v gsd_run)"; [ -n "$_G" ] && _gsd_id_ok "$_G"; then GSD_TOOLS="$_G"; gsd_run() { "$GSD_TOOLS" "$@"; }; else echo "ERROR: gsd-tools.cjs not found at $GSD_TOOLS and no identity-proving gsd_run is on PATH. Run: npx -y @opengsd/gsd-core@latest --claude --local" >&2; exit 1; fi; GSD_IDENTITY_STATUS=unverified; _gsd_id_ok gsd_run && GSD_IDENTITY_STATUS=ok; export GSD_IDENTITY_STATUS; [ "$GSD_IDENTITY_STATUS" = ok ] || echo "WARNING: \"$GSD_TOOLS\" did not prove it is @opengsd/gsd-core - it is either a different package or an @opengsd/gsd-core older than the runtime-identity verb. See docs/how-to/diagnose-a-foreign-gsd-tools.md" >&2; if [ -n "${CLAUDE_ENV_FILE:-}" ] && [ -n "${GSD_TOOLS:-}" ]; then printf "export PATH='%s':\"\$PATH\"\n" "${GSD_TOOLS%/*}" >> "$CLAUDE_ENV_FILE" 2>/dev/null || true; fi
11
13
  GATE_RESULT=$(gsd_run check ${hook.check.query} "${PHASE_NUMBER}" --raw) && CHECK_EXIT=0 || CHECK_EXIT=$?
12
14
  ```
13
15
 
16
+ When `check.predicate` is set (a predicate gate), pass every phase value this step holds (`phase_dir` and `phase_number` from the init bundle) (#5290):
17
+
18
+ ```bash
19
+ # same shell as the block above: gsd_run is already defined by its preamble
20
+ GATE_RESULT=$(gsd_run check predicate --predicate '<predicate JSON>' --phase-dir "${PHASE_DIR}" --phase-number "${PHASE_NUMBER}" --raw) && CHECK_EXIT=0 || CHECK_EXIT=$?
21
+ ```
22
+
14
23
  **Step 1 — did the CHECK COMMAND itself succeed?**
15
24
 
16
25
  If the check command failed (non-zero `CHECK_EXIT`, empty output, or unparseable JSON):
@@ -605,11 +605,13 @@ increases monotonically across waves. `{status}` is `complete` (success),
605
605
  - Bad: "Executing terrain generation plan"
606
606
  - Good: "Procedural terrain generator using Perlin noise — creates height maps and biome zones. Required before vehicle physics."
607
607
 
608
- 2.5. **Per-plan worktree decision (run for each plan in this wave BEFORE its dispatch):**
608
+ 2.5. **Per-plan worktree decision (run for each plan in this wave BEFORE any dispatch):**
609
609
 
610
- Read and execute `gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md` for each plan. It extracts `PLAN_FILES` from the plan's JSON, intersects against `SUBMODULE_PATHS` (with normalization, bidirectional matching, and glob-prefix handling), and sets `USE_WORKTREES_FOR_PLAN` to `false` when the plan touches a submodule path. Append `plan_id` to a `WAVE_WORKTREE_PLANS` accumulator when `USE_WORKTREES_FOR_PLAN != false`.
610
+ Read and execute **Part 1** of `gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md` for each plan. It extracts `PLAN_FILES` from the plan's JSON, intersects against `SUBMODULE_PATHS` (with normalization, bidirectional matching, and glob-prefix handling), and sets `USE_WORKTREES_FOR_PLAN` to `false` when the plan touches a submodule path. Append `plan_id` to a `WAVE_WORKTREE_PLANS` accumulator when `USE_WORKTREES_FOR_PLAN != false`. **Store each plan's decision** (`plan_id → USE_WORKTREES_FOR_PLAN`): the shared `USE_WORKTREES_FOR_PLAN` value is overwritten by the next plan's decision.
611
611
 
612
- The dispatch branches in step 3 gate on both `USE_WORKTREES` and `USE_WORKTREES_FOR_PLAN` (#2474).
612
+ The plan-scoped dispatch-isolation record is **not written here**. It is a single slot, so it is written in step 3, immediately before each plan's own dispatch (Part 2 of the gate, #5516).
613
+
614
+ The dispatch branches in step 3 gate on both `USE_WORKTREES` and the dispatched plan's own stored `USE_WORKTREES_FOR_PLAN` (#2474).
613
615
 
614
616
  2.75. **Execute:wave:pre capability dispatch:**
615
617
 
@@ -632,6 +634,8 @@ increases monotonically across waves. `{status}` is `complete` (success),
632
634
 
633
635
  **Substitute `{plan_id}` in the prompt below with this plan's `id` field** from the `phase-plan-index` JSON loaded in step 1 (the same field referred to elsewhere in this workflow as `plan.id`) — unmodified and un-truncated, never a paraphrase. The guard hooks compare this value verbatim against the sentinel the per-plan gate wrote (`per-plan-worktree-gate.md`'s `plan_id`); a paraphrase or an omission costs the dispatch its recorded isolation decision.
634
636
 
637
+ **Record this plan's isolation immediately before its dispatch (#5516).** Restore the dispatched plan's **stored decision** from step 2.5 (`plan_id` := this plan's `id`, `USE_WORKTREES_FOR_PLAN` := the value stored for that plan, and `PLAN_FILES` / `PLAN_DELETIONS` re-extracted from this plan's JSON, since the later plans' decisions overwrote them) and run **Part 2** of `gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md`, so the single-slot sentinel names the plan being dispatched. Do not run it for any other plan, and do not batch it ahead of the dispatches.
638
+
635
639
  **Executor routing (#1689/#3370).** Per plan, run `gsd-core/workflows/execute-phase/steps/per-plan-executor-routing.md` to set `EXECUTOR_TYPE` for `subagent_type="{EXECUTOR_TYPE}"` below.
636
640
 
637
641
  **TDD-applicability resolution (#4266/#4272).** Run `gsd-core/workflows/execute-phase/steps/tdd-applicability-resolution.md`.
@@ -954,7 +958,7 @@ increases monotonically across waves. `{status}` is `complete` (success),
954
958
  if [ "${TEST_EXIT}" -eq 0 ]; then
955
959
  # Update ROADMAP plan progress for each completed plan in this wave
956
960
  for plan_id in {completed_plan_ids}; do
957
- gsd_run query roadmap.update-plan-progress "${PHASE_NUMBER}" "${plan_id}" "complete"
961
+ PROGRESS=$(gsd_run query roadmap.update-plan-progress "${PHASE_NUMBER}" "${plan_id}" "complete") && REASON=$(jq -er '.reason//""' <<<"$PROGRESS") && [ "$REASON" != missing_phase_details ] || { echo "ERROR: ROADMAP update-plan-progress did not complete for ${PHASE_NUMBER}-${plan_id} (missing_phase_details decline, failed query, or jq not installed). STOP: skip this plan's completion steps." >&2; exit 1; }
958
962
  done
959
963
 
960
964
  # Only commit tracking files if they actually changed
@@ -1114,8 +1118,8 @@ When executor returns a checkpoint AND `AUTO_MODE` is `true`:
1114
1118
  [Awaiting section from agent return]
1115
1119
  ```
1116
1120
  5. User responds: "approved"/"done" | issue description | decision selection
1117
- 6. **Spawn continuation agent (NOT resume)** using continuation-prompt.md template:
1118
- - `{completed_tasks_table}`: From checkpoint return
1121
+ 6. **Spawn continuation agent (NOT resume)** with a prompt built from these placeholders (there is no separate template file; this list is the contract):
1122
+ - `{completed_tasks_table}`: From checkpoint return, wrapped in a `<completed_tasks>` tag (the executor's continuation handling keys on it)
1119
1123
  - `{resume_task_number}` + `{resume_task_name}`: Current task
1120
1124
  - `{user_response}`: What user provided
1121
1125
  - `{resume_instructions}`: Based on checkpoint type
@@ -504,7 +504,7 @@ IS_WORKTREE=$([ -f .git ] && echo "true" || echo "false")
504
504
 
505
505
  if [ "$IS_WORKTREE" != "true" ]; then
506
506
  # use_worktrees: false → this handler is the sole post-plan sync point (#2661)
507
- gsd_run query roadmap.update-plan-progress "${PHASE}"
507
+ PROGRESS=$(gsd_run query roadmap.update-plan-progress "${PHASE}") && REASON=$(jq -er '.reason//""' <<<"$PROGRESS") && [ "$REASON" != missing_phase_details ] || { echo "ERROR: ROADMAP update-plan-progress did not complete for ${PHASE}-${PLAN} (missing_phase_details decline, failed query, or jq not installed). STOP: skip this plan's completion steps." >&2; exit 1; }
508
508
  fi
509
509
  ```
510
510
  Counts PLAN vs SUMMARY files on disk. Updates progress table row with correct count and status (`In Progress` or `Complete` with date).
@@ -249,7 +249,7 @@ Report final status.
249
249
  | W027 | warning | Stale git worktree (not modified in a long time) | No |
250
250
  | W028 | warning | A GSD-owned install scope shadows another on this machine | No |
251
251
  | W029 | warning | .planning/ matches a gitignore rule but is still tracked by git (gitignore has no effect on already-tracked files) | No |
252
- | W030 | warning | Phase verification report status is outside the closed set | No |
252
+ | W030 | warning | Phase verification report status is outside the closed set | Yes (known legacy statuses only) |
253
253
  | I001 | info | Plan without SUMMARY (may be in progress) | No |
254
254
  | I010 | info | Resolved CWD reported alongside the E010 home-directory guard | No |
255
255
 
@@ -266,6 +266,7 @@ Note: this table is **generated** — do not hand-edit it. It is produced by `no
266
266
  | addNyquistKey | Add workflow.nyquist_validation: true to config.json | None — matches existing default |
267
267
  | addAiIntegrationPhaseKey | Add workflow.ai_integration_phase: true to config.json | None — matches existing default |
268
268
  | backfillMilestones | Synthesize missing MILESTONES.md entries from `.planning/milestones/vX.Y-ROADMAP.md` snapshots | None — additive only; triggered by `--backfill` flag |
269
+ | repairVerificationStatus | Rewrite a known legacy verification `status` (`pass_with_caveats`) to `passed`, keeping the original as `legacy_status` | None — only the `status` line changes; unknown statuses are never touched |
269
270
 
270
271
  **Not repairable (too risky):**
271
272
  - PROJECT.md, ROADMAP.md content
@@ -227,7 +227,7 @@ Agent({
227
227
 
228
228
  > **ORCHESTRATOR RULE — CODEX RUNTIME**: After calling Agent() above, stop working on this task immediately. Do not read more files, edit code, or run tests related to this task while the subagent is active. Wait for the subagent to return its result. This prevents duplicate work, conflicting edits, and wasted context. Only resume when the subagent result is available.
229
229
 
230
- Handle the checker return by severity, never by the sentinel alone: count BLOCKER + WARNING entries in the YAML issues block; an entry whose severity is missing or unrecognized counts as a BLOCKER (fail closed). If the return is `## VERIFICATION PASSED`, or the count is zero — every entry is explicitly INFO — display `ℹ advisory — {dimension}: {description}` per INFO entry and treat the plan as imported; INFO is advisory and never blocks an import (#3724). Otherwise:
230
+ Handle the checker return by severity, never by the sentinel alone: count BLOCKER + WARNING entries in the YAML issues block; an entry whose severity is missing or unrecognized counts as a BLOCKER (fail closed). A `## VERIFICATION PASSED` return that lists an entry whose severity is BLOCKER or WARNING, in the YAML issues block or listed in prose (a mention such as "no warnings" or a zero count is not an entry) is counted as `## ISSUES FOUND`, the prose entries counted as BLOCKER/WARNING entries (#5578). If the return is `## VERIFICATION PASSED` with no such entry, or the count is zero — every entry is explicitly INFO — display `ℹ advisory — {dimension}: {description}` per INFO entry and treat the plan as imported; INFO is advisory and never blocks an import (#3724). Otherwise:
231
231
  - Display the blocking issues to the user
232
232
  - Ask the user to resolve issues before the plan is considered imported
233
233
  - Do not delete the written file — the user can fix and re-validate manually
@@ -50,7 +50,7 @@ Display:
50
50
  Spawn the planner in **outline-only** mode — it must write only the outline manifest, not any
51
51
  PLAN.md files:
52
52
 
53
- **Dispatch/wait gate — `PLANNER_STALL_DETECTION_ENABLED`:**
53
+ **Dispatch/wait gate — `PLANNER_STALL_DETECTION_ENABLED` (config key `planner.stall_detection_enabled`, resolved in `plan-phase/steps/stall-detection-helpers.md`):**
54
54
  - **`true` (default):** use `run_in_background=true` in the Agent() call below, then use `gsd_stall_watch` after it.
55
55
 
56
56
  ```javascript
@@ -146,7 +146,7 @@ GSD_ARGS=${GSD_ARGS%$'\n'}
146
146
  4. Spawn the planner in **single-plan** mode — it must write exactly one PLAN.md file. The prompt
147
147
  is unchanged per plan; what changes is whether the runnable set's Agent() calls are issued one
148
148
  at a time (serial) or together in one message (concurrent, every call still carrying
149
- `run_in_background=true` exactly as today when `PLANNER_STALL_DETECTION_ENABLED` is `true`
149
+ `run_in_background=true` exactly as today when `PLANNER_STALL_DETECTION_ENABLED` (config key `planner.stall_detection_enabled`) is `true`
150
150
  (default)):
151
151
  ```javascript
152
152
  Agent(
@@ -103,23 +103,27 @@ PLANNER_STALL_THRESHOLD_MINUTES=$(gsd_run query config-get planner.stall_thresho
103
103
  # a reliability risk this fix cannot afford: a malformed config value would
104
104
  # abort the stall-watcher itself with a bash syntax error, silently defeating
105
105
  # the exact hang-recovery this issue is about. Reject anything that is not a
106
- # bare non-negative integer before it is ever used, so a bad config value
107
- # degrades to the safe default instead of crashing the watcher.
108
- [[ "$PLANNER_STALL_INTERVAL_MINUTES" =~ ^[0-9]+$ ]] || PLANNER_STALL_INTERVAL_MINUTES=5
109
- [[ "$PLANNER_STALL_THRESHOLD_MINUTES" =~ ^[0-9]+$ ]] || PLANNER_STALL_THRESHOLD_MINUTES=10
106
+ # bare positive integer before it is ever used, so a bad config value
107
+ # degrades to the safe default instead of crashing the watcher. Zero is
108
+ # rejected too (#3923): an interval of 0 makes every gsd_stall_watch cycle a
109
+ # zero-length sleep, and a threshold of 0 declares a healthy agent stalled on
110
+ # the first check. At most 9 digits, so `* 60` can never overflow.
111
+ [[ "$PLANNER_STALL_INTERVAL_MINUTES" =~ ^[1-9][0-9]{0,8}$ ]] || PLANNER_STALL_INTERVAL_MINUTES=5
112
+ [[ "$PLANNER_STALL_THRESHOLD_MINUTES" =~ ^[1-9][0-9]{0,8}$ ]] || PLANNER_STALL_THRESHOLD_MINUTES=10
110
113
 
111
114
  # gsd_stall_should_recover — pure decision function, no IO, no sleeping. Given how
112
115
  # long the orchestrator has been waiting plus two liveness signals (a completion
113
116
  # marker found in the agent's output file, and fresh on-disk artifact activity),
114
117
  # decides whether to keep waiting, treat the wait as satisfied, or auto-surface the
115
118
  # existing accept/retry/stop recovery menu (9a/11a). Never kills or retries anything
116
- # itself — it only classifies. Re-validates both numeric args as bare non-negative
117
- # integers (defense in depth — safe to call with any input, not just the resolved
118
- # config globals above) before either ever reaches arithmetic expansion.
119
+ # itself — it only classifies. Re-validates both numeric args as bounded bare
120
+ # non-negative integers (defense in depth — safe to call with any input, not just
121
+ # the resolved config globals above, which are always >= 1) before either ever
122
+ # reaches arithmetic expansion. It accepts 0 so callers can test boundaries.
119
123
  gsd_stall_should_recover() {
120
124
  local elapsed_seconds="$1" threshold_minutes="$2" marker_found="$3" artifact_fresh="$4"
121
- [[ "$elapsed_seconds" =~ ^[0-9]+$ ]] || elapsed_seconds=0
122
- [[ "$threshold_minutes" =~ ^[0-9]+$ ]] || threshold_minutes=10
125
+ [[ "$elapsed_seconds" =~ ^[0-9]{1,15}$ ]] || elapsed_seconds=0
126
+ [[ "$threshold_minutes" =~ ^[0-9]{1,9}$ ]] || threshold_minutes=10
123
127
  local threshold_seconds=$(( threshold_minutes * 60 ))
124
128
  if [ "$marker_found" = "true" ]; then
125
129
  echo "marker_received"; return 0
@@ -528,7 +528,7 @@ Read the `activeHooks` array directly from `PLAN_PRE_HOOKS_JSON` / `HOOKS_JSON`
528
528
  - In pipeline / `--auto` mode, invoke the hook's `ref.skill` via `Skill(skill="gsd-${ref.skill}", args="${PHASE} --auto ${GSD_WS}")`.
529
529
  - In manual mode, display the existing non-blocking `/gsd:ai-integration-phase {N}` recommendation and let the user continue planning without AI-SPEC or stop to run the capability workflow first.
530
530
 
531
- Run the UI deterministic gate whenever **any** `plan:pre` UI hook is active — including the step-only case (`workflow.ui_safety_gate` off). (`check.query` = `"ui.plan-gate"`; router normalizes dots→hyphens.)
531
+ Run the UI deterministic gate whenever **any** `plan:pre` UI hook is active — including the step-only case (`workflow.ui_safety_gate` off). (`check.query` = `"ui.plan-gate"`, dot-normalized to `ui-plan-gate` per `loop-hook-dispatch.md` § `gate`.)
532
532
 
533
533
  ```bash
534
534
  GATE=$(gsd_run check ui-plan-gate "${PHASE}" --raw) && GATE_EXIT=0 || GATE_EXIT=$?
@@ -635,10 +635,10 @@ SKETCH_FINDINGS_PATH=$(ls ./.claude/skills/sketch-findings-*/SKILL.md 2>/dev/nul
635
635
  # Resolve the phase SPEC (carries the ## Edge Coverage section the planner lifts resolved
636
636
  # edges from). UNCONDITIONAL — must NOT live in §4.5 Check AI-SPEC, which is skipped
637
637
  # on non-AI phases; gating it there silently starves the planner of the SPEC (#550 review).
638
- # Glob the plain phase SPEC, excluding the -AI-SPEC.md / -UI-SPEC.md variants.
638
+ # init resolves the plain phase SPEC (never the -AI-SPEC.md / -UI-SPEC.md variants) through the one shared predicate.
639
639
  PHASE_DIR_FOR_SPEC=$(_gsd_field "$INIT" phase_dir)
640
- SPEC_FILE=$(ls "${PHASE_DIR_FOR_SPEC}"/*-SPEC.md 2>/dev/null | grep -Ev -- '-(AI|UI)-SPEC\.md$' | head -1)
641
- SPEC_PATH="${SPEC_FILE}"
640
+ SPEC_PATH=$(_gsd_field "$INIT" spec_path)
641
+ SPEC_FILE="${SPEC_PATH}"
642
642
  # Resolve the phase UI-SPEC separately (the glob above excludes -UI-SPEC.md); it carries the
643
643
  # ## UI Considerations section the planner lifts by the same rule as ## Edge Coverage (#1867).
644
644
  UI_SPEC_FILE=$(ls "${PHASE_DIR_FOR_SPEC}"/*-UI-SPEC.md 2>/dev/null | head -1)
@@ -960,7 +960,7 @@ Every task MUST include these fields — they are NOT optional:
960
960
 
961
961
  **If `CHUNKED_MODE` is `false` (default):** Spawn the planner as a single long-lived Agent:
962
962
 
963
- **Dispatch/wait gate — `PLANNER_STALL_DETECTION_ENABLED`:**
963
+ **Dispatch/wait gate — `PLANNER_STALL_DETECTION_ENABLED` (config key `planner.stall_detection_enabled`, resolved in `plan-phase/steps/stall-detection-helpers.md`):**
964
964
  - **`true` (default):** use `run_in_background=true` in the Agent() call shown below, then use `gsd_stall_watch` as specified after it.
965
965
 
966
966
  ```text
@@ -1109,7 +1109,7 @@ Agent(
1109
1109
  )
1110
1110
  ```
1111
1111
 
1112
- **Dispatch/wait gate — `PLANNER_STALL_DETECTION_ENABLED`:**
1112
+ **Dispatch/wait gate — `PLANNER_STALL_DETECTION_ENABLED` (config key `planner.stall_detection_enabled`, resolved in `plan-phase/steps/stall-detection-helpers.md`):**
1113
1113
  - **`true` (default):** use `run_in_background=true` in the Agent() call above, then use `gsd_stall_watch` below.
1114
1114
 
1115
1115
  **ORCHESTRATOR RULE — ALL RUNTIMES (when `PLANNER_STALL_DETECTION_ENABLED` is `true`):** `TS=$(date +%s)`; repeat `CHECKER_STALL_RESULT=$(gsd_stall_watch "$TS" "{outputFile}" "${PHASE_DIR}"'/*-PLAN.md' "## VERIFICATION PASSED" "## ISSUES FOUND")` while waiting/active.
@@ -1118,7 +1118,7 @@ Agent(
1118
1118
 
1119
1119
  ## 11. Handle Checker Return
1120
1120
 
1121
- - **`marker_received` + `## VERIFICATION PASSED`:** Display confirmation, proceed to step 13.
1121
+ - **`marker_received` + `## VERIFICATION PASSED`:** If the return lists an entry whose severity is BLOCKER or WARNING, in the YAML issues block or listed in prose (a mention such as "no warnings" or a zero count is not an entry), treat it as `## ISSUES FOUND` instead: display issues, check iteration count, proceed to step 12, which counts those prose entries as BLOCKER/WARNING entries (the no-YAML-block shortcut there does not apply). The sentinel never decides alone, and an entry with a missing or unrecognized severity counts as a BLOCKER (#5578). Otherwise display confirmation, proceed to step 13.
1122
1122
  - **`marker_received` + `## ISSUES FOUND`:** Display issues, check iteration count, proceed to step 12.
1123
1123
  - **`stalled`:** Automatically surface 11a's recovery choice (Accept verification / Retry checker / Stop) — no manual interrupt needed.
1124
1124
  - **Empty / truncated / no recognized marker:** → Filesystem fallback (step 11a).
@@ -1143,7 +1143,7 @@ Track `stall_reentry_count` (starts at 0; incremented each time "Adjust approach
1143
1143
 
1144
1144
  **If iteration_count < 3:**
1145
1145
 
1146
- Parse issue count from checker return: count BLOCKER + WARNING entries in the YAML issues block (structured output from gsd-plan-checker); an entry whose severity is missing or unrecognized counts as a BLOCKER (fail closed). If the checker's return contains no YAML issues block (i.e., the plan was approved with no issues), treat `issue_count` as 0 and skip the stall check — the plan passed. Proceed to step 13 — likewise when every entry in the block is explicitly INFO (display them as advisories). Advisory format: `ℹ advisory — {dimension}: {description}` per INFO entry, listed once before the step-13 output.
1146
+ Parse issue count from checker return: count BLOCKER + WARNING entries in the YAML issues block (structured output from gsd-plan-checker); an entry whose severity is missing or unrecognized counts as a BLOCKER (fail closed). Entries a `## VERIFICATION PASSED` return lists in prose count the same way (#5578). If the checker's return contains no YAML issues block and lists no BLOCKER or WARNING entry in prose (i.e., the plan was approved with no issues), treat `issue_count` as 0 and skip the stall check — the plan passed. Proceed to step 13 — likewise when every entry in the block is explicitly INFO (display them as advisories). Advisory format: `ℹ advisory — {dimension}: {description}` per INFO entry, listed once before the step-13 output.
1147
1147
 
1148
1148
  Display (only when entering the revision loop — skip if the paragraph above already proceeded to step 13): `Revision iteration {N}/3 -- {blocker_count} blockers, {warning_count} warnings`
1149
1149
 
@@ -1206,7 +1206,7 @@ Agent(
1206
1206
  )
1207
1207
  ```
1208
1208
 
1209
- **Dispatch/wait gate — `PLANNER_STALL_DETECTION_ENABLED`:**
1209
+ **Dispatch/wait gate — `PLANNER_STALL_DETECTION_ENABLED` (config key `planner.stall_detection_enabled`, resolved in `plan-phase/steps/stall-detection-helpers.md`):**
1210
1210
  - **`true` (default):** use `run_in_background=true` in the Agent() call above, then use `gsd_stall_watch` below.
1211
1211
 
1212
1212
  **ORCHESTRATOR RULE — ALL RUNTIMES (when `PLANNER_STALL_DETECTION_ENABLED` is `true`):** (7.99; no marker, mtimes only) `TS=$(date +%s)`; repeat `PLANNER_STALL_RESULT=$(gsd_stall_watch "$TS" "{outputFile}" "${PHASE_DIR}"'/*-PLAN.md')` while waiting/active — `stalled` -> 1) Accept as revised, to step 13, 2) Retry, 3) Stop.
@@ -1288,7 +1288,7 @@ Spawn checker again (step 10), then increment `iteration_count`.
1288
1288
 
1289
1289
  **If iteration_count >= 3:**
1290
1290
 
1291
- Recount BLOCKER + WARNING by the same rule — an entry whose severity is missing or unrecognized counts as a BLOCKER (fail closed). If `issue_count` is 0 — PASSED, or every entry in the block is explicitly INFO — display any advisories and proceed to step 13; the gate below fires on everything else (#3724).
1291
+ Recount BLOCKER + WARNING by the same rule — an entry whose severity is missing or unrecognized counts as a BLOCKER (fail closed). If `issue_count` is 0 — PASSED with no BLOCKER/WARNING entry in the YAML block or in prose, or every entry in the block is explicitly INFO — display any advisories and proceed to step 13; the gate below fires on everything else (#3724).
1292
1292
 
1293
1293
  Display: `Max iterations reached. {N} issues remain:` + issue list
1294
1294
 
@@ -1441,7 +1441,7 @@ This step is derived entirely from existing PLAN frontmatter — no extra LLM pa
1441
1441
  gsd_run query roadmap.annotate-dependencies "${PHASE_NUMBER}"
1442
1442
  ```
1443
1443
 
1444
- This operation is idempotent: if wave headers or cross-cutting constraints already exist in the ROADMAP phase section, the command returns without modifying the file. Skip this step if `plan_count` is 0.
1444
+ Skip this step if `plan_count` is 0. Re-running regenerates the annotation from current PLAN frontmatter; an unchanged result writes nothing, and an unrecognised checklist row leaves ROADMAP.md untouched and reports `skipped: "unrecognised_checklist_row"`. The `**Cross-cutting constraints:**` header line ends in an HTML-comment marker; that block is machine-owned and the bullets directly under it are regenerated. Put hand-written bullets after a blank line. Bullets kept from a block written before the marker existed are reported as `stale_legacy_constraints`.
1445
1445
 
1446
1446
  ## 13d. Commit Plans if commit_docs is true
1447
1447
 
@@ -1478,7 +1478,7 @@ every other registered hook at this point).
1478
1478
 
1479
1479
  **Step and contribution dispatch:** dispatch every `kind == "step"` hook and inject every `kind == "contribution"` fragment per @gsd-core/references/loop-hook-dispatch.md (skip each kind silently when none), before gate evaluation below.
1480
1480
 
1481
- ⚠ **Validate `check` before shell use** (third-party manifest input) — `loop-hook-dispatch.md` § `gate`.
1481
+ ⚠ **Validate `check` before shell use** (third-party manifest input; wherever `${hook.check.query}` appears, write the dot-normalized query, each `.` as `-`, and validate and pass that same value) — `loop-hook-dispatch.md` § `gate`.
1482
1482
 
1483
1483
  **For each active entry where `kind == "gate"`** (process in array order). **Dispatch by check shape** (the registry validates exactly one of `query`/`predicate`/`agentVerdict`):
1484
1484
 
@@ -1486,9 +1486,9 @@ every other registered hook at this point).
1486
1486
  # named-query gate:
1487
1487
  GATE_RESULT=$(gsd_run check ${hook.check.query} "${PHASE_DIR}" "${PHASE_REQ_IDS}" --raw) && CHECK_EXIT=0 || CHECK_EXIT=$?
1488
1488
  ```
1489
- OR, for a generic `predicate` gate (ADR-2008 / #2008), inline the predicate as compact JSON (note the `--phase-dir`/`--phase-req-ids` flags feed `${PHASE_DIR}`/`${PHASE_REQ_IDS}` interpolation):
1489
+ OR, for a generic `predicate` gate (ADR-2008 / #2008), inline the predicate as compact JSON (note the `--phase-dir`/`--phase-number`/`--phase-req-ids` flags feed `${PHASE_DIR}`/`${PHASE_NUMBER}`/`${PHASE_REQ_IDS}` interpolation):
1490
1490
  ```bash
1491
- GATE_RESULT=$(gsd_run check predicate --predicate '<hook.check.predicate as JSON>' --phase-dir "${PHASE_DIR}" --phase-req-ids "${PHASE_REQ_IDS}" --raw) && CHECK_EXIT=0 || CHECK_EXIT=$?
1491
+ GATE_RESULT=$(gsd_run check predicate --predicate '<hook.check.predicate as JSON>' --phase-dir "${PHASE_DIR}" --phase-number "${PHASE_NUMBER}" --phase-req-ids "${PHASE_REQ_IDS}" --raw) && CHECK_EXIT=0 || CHECK_EXIT=$?
1492
1492
  ```
1493
1493
  (Read the hook's `check` object in-context to pick the branch; a gate with neither is a malformed registry entry — skip with a warning.)
1494
1494
 
@@ -90,6 +90,8 @@ jq dependency). Write dirty repo names to a temp file so the list survives acros
90
90
  subsequent command executions:
91
91
 
92
92
  ```bash
93
+ # #5548: drop inherited repo-location vars (set inside git hooks) so cwd, not GIT_DIR, picks ROOT.
94
+ unset GIT_ALTERNATE_OBJECT_DIRECTORIES GIT_OBJECT_DIRECTORY GIT_DIR GIT_WORK_TREE GIT_IMPLICIT_WORK_TREE GIT_GRAFT_FILE GIT_INDEX_FILE GIT_NO_REPLACE_OBJECTS GIT_REPLACE_REF_BASE GIT_PREFIX GIT_SHALLOW_FILE GIT_COMMON_DIR
93
95
  ROOT=$(git rev-parse --show-toplevel)
94
96
  DIRTY_FILE=$(mktemp)
95
97
 
@@ -82,7 +82,7 @@ Agent(
82
82
 
83
83
  **Handle checker return:**
84
84
 
85
- - **`## VERIFICATION PASSED`:** Display confirmation, proceed to step 6.
85
+ - **`## VERIFICATION PASSED`:** If the return lists an entry whose severity is BLOCKER or WARNING, in the YAML issues block or listed in prose (a mention such as "no warnings" or a zero count is not an entry), handle it as `## ISSUES FOUND` below, counting the prose entries as BLOCKER/WARNING entries; the sentinel never decides alone, and an entry with a missing or unrecognized severity counts as a BLOCKER (#5578). Otherwise display confirmation, proceed to step 6.
86
86
  - **`## ISSUES FOUND`:** Count BLOCKER + WARNING entries in the YAML issues block; an entry whose severity is missing or unrecognized counts as a BLOCKER (fail closed). If zero — every entry is explicitly INFO — display `ℹ advisory — {dimension}: {description}` per entry and proceed to step 6; INFO is advisory and never enters the loop (#3724). Otherwise display issues, check iteration count, enter revision loop.
87
87
 
88
88
  **Revision loop (max 2 iterations):**
@@ -16,7 +16,6 @@ _GSD_SHIM_NAME="gsd-tools.cjs"; _GSD_RUNTIME_ROOT="${RUNTIME_DIR:-$(git rev-pars
16
16
  QUICK_PLAN_PARENT=""
17
17
  QUICK_PLAN_COMMIT=""
18
18
  if [ "${USE_WORKTREES}" != "false" ]; then
19
- QUICK_PLAN_PARENT=$(git rev-parse HEAD)
20
19
  COMMIT_DOCS=$(gsd_run query config-get commit_docs --raw 2>/dev/null || echo "true")
21
20
  if [ "$COMMIT_DOCS" != "false" ]; then
22
21
  # pre-dispatch plan commit — PLAN.md is always in scope. CONTEXT.md/RESEARCH.md
@@ -52,5 +51,13 @@ if [ "${USE_WORKTREES}" != "false" ]; then
52
51
  if [ -z "$QUICK_PLAN_COMMIT" ]; then
53
52
  QUICK_PLAN_COMMIT=$(git rev-parse HEAD)
54
53
  fi
54
+ # #5209: the alternate base is the PLAN commit's immediate parent, read AFTER the
55
+ # commit attempt so it holds whether the planner already committed the plan or this
56
+ # step just did. Empty when docs commits are off and for a root commit.
57
+ if [ "$COMMIT_DOCS" != "false" ]; then
58
+ _GSD_PLAN_AT=$(git log -1 --format=%H -- "${QUICK_DIR}/${quick_id}-PLAN.md" 2>/dev/null || true)
59
+ [ -n "$_GSD_PLAN_AT" ] || _GSD_PLAN_AT="$QUICK_PLAN_COMMIT"
60
+ QUICK_PLAN_PARENT=$(git rev-parse --verify --quiet "${_GSD_PLAN_AT}^" 2>/dev/null || true)
61
+ fi
55
62
  fi
56
63
  ```
@@ -73,7 +73,8 @@ that require a ROADMAP phase goal.
73
73
  counts as BLOCKER, fail closed; pure INFO entries are advisory only and never
74
74
  enter the revision loop):
75
75
 
76
- - **`## VERIFICATION PASSED`** or all-INFO: proceed to the next item.
76
+ - **`## VERIFICATION PASSED`** (with no BLOCKER/WARNING entry) or all-INFO: proceed to the next item.
77
+ - **`## VERIFICATION PASSED` that nevertheless lists an entry whose severity is BLOCKER or WARNING, in the YAML issues block or listed in prose (a mention such as "no warnings" or a zero count is not an entry):** handled as `## ISSUES FOUND`, counting the prose entries as BLOCKER/WARNING entries; the sentinel never decides alone (#5578).
77
78
  - **Any BLOCKER/WARNING:** revision loop, max 2 iterations total for this item.
78
79
 
79
80
  **Revision (iteration < 2):**
@@ -541,7 +541,7 @@ SUMMARY.md and stop — the user must rerun with worktrees disabled.
541
541
 
542
542
  > **ORCHESTRATOR RULE — CODEX RUNTIME**: After calling Agent() above, stop working on this task immediately. Do not read more files, edit code, or run tests related to this task while the subagent is active. Wait for the subagent to return its result. This prevents duplicate work, conflicting edits, and wasted context. Only resume when the subagent result is available.
543
543
 
544
- If the executor ran isolated (`ISOLATION = "harness-worktree"` at dispatch), append its returned `{agent_id, worktree_path, branch, expected_base, allowed_bases}` metadata to `QUICK_WORKTREE_MANIFEST` before cleanup. Set `expected_base` to `${EXPECTED_BASE}` and `allowed_bases` to `["${EXPECTED_BASE}", "${QUICK_PLAN_PARENT}"]` with duplicates removed. If any required field is unavailable, stop and ask for recovery; do not discover global worktrees.
544
+ If the executor ran isolated (`ISOLATION = "harness-worktree"` at dispatch), append its returned `{agent_id, worktree_path, branch, expected_base, allowed_bases}` metadata to `QUICK_WORKTREE_MANIFEST` before cleanup. Set `expected_base` to `${EXPECTED_BASE}` and `allowed_bases` to `["${EXPECTED_BASE}", "${QUICK_PLAN_PARENT}"]` with duplicates and empty values removed. If any required field is unavailable, stop and ask for recovery; do not discover global worktrees.
545
545
 
546
546
  After executor returns:
547
547
  1. **Worktree cleanup:** If the executor ran isolated (`ISOLATION = "harness-worktree"` at dispatch), merge the worktree branch back and clean up:
@@ -10,6 +10,21 @@ After a GSD update wipes and reinstalls files, this workflow merges user's previ
10
10
 
11
11
  <process>
12
12
 
13
+ ## Step 0: Parse interactive mode (#4435)
14
+
15
+ Read the arguments passed after `--reapply`:
16
+
17
+ - no `--interactive` → `INTERACTIVE_MODE=off` (the default; Step 4 runs exactly as before)
18
+ - bare `--interactive` or `--interactive=exceptions` → `INTERACTIVE_MODE=exceptions`
19
+ - `--interactive=all` → `INTERACTIVE_MODE=all`
20
+ - any other `--interactive=<value>` → STOP with: `unknown --interactive value '<value>' (expected exceptions or all)`. Do not reapply anything.
21
+
22
+ When the mode is not `off`, tell the user once, before the first prompt: on the first interactive run
23
+ for this install, `--interactive=all` is recommended, because the recommendations have not yet been
24
+ checked against this install's customizations.
25
+
26
+ **Text mode (`workflow.text_mode: true` in config or `--text` flag):** Set `TEXT_MODE=true` if `--text` is present in the arguments OR `workflow.text_mode` is `true`. When TEXT_MODE is active, replace every `AskUserQuestion` call with a plain-text numbered list and ask the user to type their choice number. This is required for non-Claude runtimes where `AskUserQuestion` is not available.
27
+
13
28
  ## Step 1: Detect backed-up patches
14
29
 
15
30
  Check for local patches directory:
@@ -250,6 +265,75 @@ merge paths below, unchanged. If the classifier cannot run at all (e.g. `GSD_HOM
250
265
  unset), fall back to merging every file as before; never report `Incorporated` without the
251
266
  classifier's structured confirmation.
252
267
 
268
+ ### Step 4 interactive review (#4435) — only when `INTERACTIVE_MODE` is not `off`
269
+
270
+ Verbatim matching cannot see an edit that upstream adopted in different words, an edit whose
271
+ surrounding stock text upstream rewrote, or an edit whose anchor lines upstream deleted. Interactive
272
+ mode shows each user edit (hunk) and lets the USER decide. Present and gate: the tool recommends,
273
+ the user decides, and nothing is retired without the user's answer.
274
+
275
+ ```bash
276
+ DECISIONS_FILE="$(mktemp "${TMPDIR:-/tmp}/gsd-reapply-decisions.XXXXXX")"
277
+ HUNKS_ARGS=(
278
+ --patches-dir "$PATCHES_DIR"
279
+ --config-dir "$CONFIG_DIR"
280
+ )
281
+ if [ -d "$PRISTINE_DIR" ]; then
282
+ HUNKS_ARGS+=(--pristine-dir "$PRISTINE_DIR")
283
+ fi
284
+ HUNKS_ARGS+=(--hunks --json)
285
+
286
+ # Informational: exits 0. Each hunk carries hunk_hash, kind, stock_lines, user_lines,
287
+ # recommendation (keep | retire | review), reasons, and exception (true/false).
288
+ HUNKS_OUTPUT="$(node "${GSD_HOME}/gsd-core/bin/verify-reapply-patches.cjs" "${HUNKS_ARGS[@]}")"
289
+ ```
290
+
291
+ The decisions file lives in a temp directory, never inside `gsd-local-patches/`, because the
292
+ Step 5a gate treats every file in that directory as a backed-up patch.
293
+
294
+ For each file in `HUNKS_OUTPUT.files`, for each hunk:
295
+
296
+ - `INTERACTIVE_MODE=all`: present EVERY hunk.
297
+ - `INTERACTIVE_MODE=exceptions`: present only hunks with `exception: true`. Every other hunk is kept
298
+ without asking. Print one summary line for each:
299
+ `kept without review: {file} hunk {hunk_id}: "{first user line, truncated to 60 chars}"`.
300
+
301
+ Presenting a hunk means asking the user one question (`AskUserQuestion`, or under TEXT_MODE a
302
+ plain-text numbered list the user answers by number) that shows:
303
+
304
+ - **Stock text:** `stock_lines` (or "(none — your lines were inserted)" when empty)
305
+ - **Your text:** `user_lines` (or "(none — you deleted the stock text)" when empty)
306
+ - **Recommendation:** `recommendation`, with `reasons` explained in plain words:
307
+ `already_upstream` (the shipped file already has it), `partially_upstream` (only some of it is
308
+ there), `region_changed_upstream` (upstream also changed this spot, for example by rewording it),
309
+ `anchor_orphaned` (the lines it sat between are gone), `baseline_unconfirmed` (no verified
310
+ pre-update copy, so read the whole file), `diff_too_large` (the file changed too much to split it
311
+ into separate edits)
312
+ - Options: **Keep** (graft it), **Retire** (drop it; keep upstream's text), **Rewrite** (the user
313
+ supplies replacement text, which is applied instead)
314
+
315
+ Write a decision entry only from the user's answer to that hunk's question. Never write `retire` or
316
+ `rewrite` for a hunk the user was not asked about, and never pick an answer on the user's behalf,
317
+ even when the recommendation is `retire`. A hunk kept without review in `exceptions` mode is
318
+ recorded as `keep`. Record each answer in `DECISIONS_FILE` as JSON:
319
+
320
+ ```json
321
+ { "decisions": [ { "file": "<file>", "hunk_hash": "<hunk_hash>", "disposition": "keep|retire|rewrite" } ] }
322
+ ```
323
+
324
+ A `rewrite` entry must also carry `"replacement_lines": [...]` — exactly the lines the user supplied.
325
+ The Step 5a gate then requires those lines to be present in the merged file, so a rewrite is verified
326
+ like any kept edit. `keep` and `retire` entries never carry `replacement_lines`.
327
+
328
+ Shell variables do not survive between separate command invocations. Write `INTERACTIVE_MODE` and the
329
+ `DECISIONS_FILE` path down when you set them, and set both again in the same command that runs the
330
+ Step 5a verifier.
331
+
332
+ Then merge each file with the paths below, applying only `keep` hunks and the user's replacement
333
+ text for `rewrite` hunks. A file whose hunks were all retired is left exactly as installed and
334
+ reported `Incorporated` ("Retired by user decision"). A file with some hunks retired is reported
335
+ `Merged` with "{N} hunk(s) retired by user decision".
336
+
253
337
  For each file in `backup-meta.json`:
254
338
 
255
339
  1. **Read the backed-up version** (user's modified copy from `gsd-local-patches/`)
@@ -352,6 +436,11 @@ VERIFY_ARGS=(
352
436
  if [ -d "$PRISTINE_DIR" ]; then
353
437
  VERIFY_ARGS+=(--pristine-dir "$PRISTINE_DIR")
354
438
  fi
439
+ # #4435: hunks the user retired or rewrote are excused only through the recorded,
440
+ # hash-bound decisions. A decision whose hunk no longer matches exits 2 (stale).
441
+ if [ "$INTERACTIVE_MODE" != "off" ] && [ -s "$DECISIONS_FILE" ]; then
442
+ VERIFY_ARGS+=(--decisions "$DECISIONS_FILE")
443
+ fi
355
444
  VERIFY_ARGS+=(--json)
356
445
 
357
446
  # Capture stdout (the structured JSON report) separately from stderr so that
@@ -456,6 +545,18 @@ Then re-run /gsd:update --reapply to re-verify.
456
545
 
457
546
  Do not proceed to cleanup until the verifier exits 0.
458
547
 
548
+ **If `VERIFY_STATUS` is 2 and stderr names a `stale decision`** (#4435), a recorded answer no longer
549
+ matches its hunk because the backup or baseline changed after the user saw it. STOP, discard
550
+ `DECISIONS_FILE`, and re-run `/gsd:update --reapply --interactive` so the user decides on the current
551
+ text. Never edit the decisions file to make it match.
552
+
553
+ When decisions were passed, the JSON report carries `decisions.retired` — every hunk excused by a
554
+ user `retire` or `rewrite` decision. Before Step 6, show the user this list (file, hunk id,
555
+ disposition) and ask them to confirm that each one is a decision they made. The gate can prove that a
556
+ decision matches the exact text that was shown, but not who made it, so the user's confirmation of
557
+ this list is the final check. If the user disowns any entry, restore that hunk from the backup and
558
+ re-run Step 5a without it. List each confirmed entry in the Step 7 report.
559
+
459
560
  **Only when `VERIFY_STATUS` is 0** (or when all files had zero significant user-added lines, which the verifier reports as `Failures: 0`) may execution continue to gate 5b.
460
561
 
461
562
  ### 5b: Hunk Verification Table review (advisory gate, #1999)
@@ -493,6 +594,8 @@ Do not proceed to cleanup until both gates (5a and 5b) pass.
493
594
 
494
595
  ## Step 6: Cleanup option
495
596
 
597
+ If `DECISIONS_FILE` was created (#4435), delete it now: `rm -f "$DECISIONS_FILE"`.
598
+
496
599
  Ask user:
497
600
  - "Keep patch backups for reference?" → preserve `gsd-local-patches/`
498
601
  - "Clean up patch backups?" → remove `gsd-local-patches/` directory
@@ -520,6 +623,7 @@ Ask user:
520
623
  - [ ] User modifications identified and merged into new version
521
624
  - [ ] Superseded customizations classified `Incorporated` by the deterministic pre-flight classifier (pristine-confirmed) and not re-grafted
522
625
  - [ ] Conflicts surfaced to user with both versions shown
626
+ - [ ] With `--interactive`, every presented hunk was decided by the user (never by the agent), retired/rewritten hunks were passed to the Step 5a gate through the hash-bound decisions file, and each retirement is listed in the report
523
627
  - [ ] Status reported for each file with summary of what was preserved
524
628
  - [ ] Post-merge verification checks each file for dropped hunks and warns if content appears missing
525
629
  </success_criteria>