@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
@@ -33,11 +33,13 @@ No Pass/Fail buttons. No severity questions. Just: "Here's what should happen. D
33
33
 
34
34
  <process>
35
35
 
36
+ **Compact Content Gate.** Read and follow `gsd-core/references/compact-content-gate.md` now — it states the `workflow.compact_content` check and the resolution rule this spine defers to. When it directs a Read, read `gsd-core/workflows/verify-work/detail/elaboration.md` in full before continuing past this point; its content elaborates on the resume/reconcile steps and the full gap-closure sub-flow below.
37
+
36
38
  <step name="initialize" priority="first">
37
39
  If $ARGUMENTS contains a phase number, load context:
38
40
 
39
41
  ```bash
40
- _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; }; 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 unset -f gsd_run; _G="$(command -v gsd_run)"; then GSD_TOOLS="$_G"; gsd_run() { "$GSD_TOOLS" "$@"; }; elif _gsd_at "${CLAUDE_CONFIG_DIR:-$HOME/.claude}/gsd-core/bin/${_GSD_SHIM_NAME}" "${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}" "${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}" "${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}" "${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}" "${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}" "${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}" "${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}" "${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}" "${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}" "${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}" "${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}" "${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}" "${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}" "${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}" "${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}"; then gsd_run() { node "$GSD_TOOLS" "$@"; }; else echo "ERROR: gsd-tools.cjs not found at $GSD_TOOLS and gsd_run is not on PATH. Run: npx -y @opengsd/gsd-core@latest --claude --local" >&2; exit 1; fi; GSD_IDENTITY_STATUS=unverified; case "$(gsd_run runtime-identity --raw 2>/dev/null || true)" in '{"packageName":"@opengsd/gsd-core"'*'}') GSD_IDENTITY_STATUS=ok;; esac; 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
42
+ _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() { _gsd_at "${CLAUDE_CONFIG_DIR:-$HOME/.claude}/gsd-core/bin/${_GSD_SHIM_NAME}" "${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}" "${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}" "${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}" "${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}" "${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}" "${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}" "${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}" "${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}" "${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}" "${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}" "${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}" "${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}" "${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}" "${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}" "${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}"; }; 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
41
43
  GSD_WS=""
42
44
  echo "$ARGUMENTS" | grep -qE -- '--ws[[:space:]]+[A-Za-z0-9._-]+' && GSD_WS=$(echo "$ARGUMENTS" | grep -oE -- '--ws[[:space:]]+[A-Za-z0-9._-]+')
43
45
  PHASE_ARG=$(echo "$ARGUMENTS" | sed -E 's/--ws[[:space:]]+[A-Za-z0-9._-]+//g' | xargs)
@@ -181,18 +183,31 @@ instrument — the executor's own narration is never the last word. For each `*-
181
183
  BASE=$(grep -oE '^plan_head_before: [0-9a-f]{7,40}' "$SUMMARY_FILE" | awk '{print $2}')
182
184
  CLAIMED=$(grep -oE '^commits: [0-9]+' "$SUMMARY_FILE" | grep -oE '[0-9]+' || echo absent)
183
185
  ACTUAL=$(git rev-list --count "${BASE}"..HEAD)
186
+ AFTER=$(grep -oE '^plan_head_after: [0-9a-f]{7,40}' "$SUMMARY_FILE" | awk '{print $2}')
184
187
  ```
185
188
  - A `commits: absent` or `plan_head_before: absent` SUMMARY (pre-#3968 legacy) is reported as
186
189
  a WARNING with the measured git state, not a mismatch.
187
- - `ACTUAL == CLAIMED` is consistent. `ACTUAL == CLAIMED + 1` is ALSO consistent: the
188
- SUMMARY/metadata commit itself lands after the executor measured, so exactly one
189
- post-measurement commit is expected.
190
- - Anything else is a **BLOCKER** — the phase must not read as done: real project evidence
191
- (#3968) showed 14 plans declaring `commits: 1` with zero git activity, their code sitting
192
- uncommitted and one `git reset --hard` from loss. Record it as `commit_claim_mismatch`
193
- with both numbers and the SUMMARY path; a mismatch means either the executor narrated
194
- instead of measuring or commits were lost after the fact — both require reconciliation
195
- before the phase can pass.
190
+ - **Bounded reconciliation (#4670).** A SUMMARY carrying `plan_head_after:` (the executor's
191
+ HEAD at its measurement moment — after the last task commit, before the SUMMARY commit) is
192
+ reconciled against the plan's OWN window:
193
+ ```bash
194
+ if git merge-base --is-ancestor "$AFTER" HEAD 2>/dev/null \
195
+ && [ "$(git rev-list --count "${BASE}..${AFTER}")" = "$CLAIMED" ]; then
196
+ : # consistent
197
+ fi
198
+ ```
199
+ Consistent → done. Anything else is a **BLOCKER** — `commit_claim_mismatch` with both
200
+ numbers and the SUMMARY path: commits claimed but never made (#3968), task commits lost
201
+ after the fact, or the plan's recorded window rewritten afterwards (a rebase/amend/cherry-pick
202
+ of those commits makes `$AFTER` a non-ancestor — recount that plan's commits manually
203
+ before treating it as a genuine mismatch). The unbounded `${BASE}..HEAD` count is NOT
204
+ evidence either way: it grows with every later plan's commits and execute-phase's own
205
+ phase-completion commit, so an honest plan would read as a mismatch (#4670).
206
+ - **Legacy fallback (#4670).** A SUMMARY with a base but no `plan_head_after:` (pre-#4670)
207
+ cannot be bounded to its own window — report the measured `${BASE}..HEAD` count as a
208
+ **WARNING** with the SUMMARY's task-commit list for manual counting. The old
209
+ `ACTUAL == CLAIMED` / `ACTUAL == CLAIMED + 1` tolerance was a guess that later plans'
210
+ commits defeat; it must never produce a BLOCKER on the unsound window.
196
211
  </step>
197
212
 
198
213
  <step name="extract_tests">
@@ -375,7 +390,7 @@ Display the returned checkpoint EXACTLY as-is:
375
390
  - Do NOT add commentary before or after the block.
376
391
  - If you notice protocol/meta markers such as `to=all:`, role-routing text, XML system tags, hidden instruction markers, ad copy, or any unrelated suffix, discard the draft and output `{CHECKPOINT}` only.
377
392
 
378
- **Text mode (`workflow.text_mode: true` in config or `--text` flag):** Set `TEXT_MODE=true` if `--text` is present in `$ARGUMENTS` OR `text_mode` from init JSON 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 (OpenAI Codex, Gemini CLI, etc.) where `AskUserQuestion` is not available.
393
+ **Text mode (`workflow.text_mode: true` in config or `--text` flag):** Set `TEXT_MODE=true` if `--text` is present in `$ARGUMENTS` OR `text_mode` from init JSON 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 (OpenAI Codex, Antigravity, etc.) where `AskUserQuestion` is not available.
379
394
  Wait for user response (plain text, no AskUserQuestion).
380
395
  </step>
381
396
 
@@ -489,50 +504,20 @@ If no more tests → Go to `complete_session`
489
504
  </step>
490
505
 
491
506
  <step name="reconcile_gaps">
492
- **Reconcile diagnosed gaps against completed gap-closure plans (#1921):**
493
-
494
- When verify-work resumes after `/gsd:execute-phase --gaps-only`, the UAT `## Gaps` entries still read `status: failed` even though their fix plans have executed. Without reconciliation verify-work re-diagnoses them as fresh blockers and spawns new gap plans — losing the verification state. This step closes the loop.
495
-
496
- Read the UAT `## Gaps` section and the phase dir `*-PLAN.md` frontmatter. For each gap with `status: failed`:
497
- 1. Find a `*-PLAN.md` whose frontmatter `gap_ids` includes the gap's `gap_id` (`G-{phase}-{N}`).
498
- 2. If such a plan exists AND has a matching `*-SUMMARY.md` in the phase dir (the plan was executed by `--gaps-only`), the gap is **resolved** — update its YAML in place:
499
- ```yaml
500
- - gap_id: G-{phase}-{N}
501
- status: resolved # was: failed
502
- resolved_by: {plan basename}
503
- resolved_at: {today}
504
- ```
505
- 3. If no plan references the `gap_id`, or the plan has no SUMMARY, leave the gap `status: failed` (still open).
507
+ **Reconcile diagnosed gaps against completed gap-closure plans (#1921):** when verify-work resumes after `/gsd:execute-phase --gaps-only`, UAT `## Gaps` entries still read `status: failed` even though their fix plans already executed — without reconciliation they'd be re-diagnosed as fresh blockers. For each `status: failed` gap with a `*-PLAN.md` whose `gap_ids` names it AND a matching `*-SUMMARY.md`, mark it `resolved` (with `resolved_by`/`resolved_at`) in place; otherwise leave it `failed`. Resolved gaps are never re-diagnosed or re-planned; a later regression gets a fresh `gap_id`, not a reopened old one.
506
508
 
507
- Read plan frontmatter directly in-context — do not pipe it through a shell parser. After reconciliation, announce:
508
- ```
509
- Reconciled gap-closure state: {resolved_count} gap(s) resolved by executed plans, {open_count} still open.
510
- ```
511
-
512
- Resolved gaps are NOT re-diagnosed and do NOT spawn new gap plans. If the user later reports the same behavior as still broken, treat it as a new issue (a regression) with a fresh `gap_id`.
509
+ Exact YAML shape and the announcement line: `gsd-core/workflows/verify-work/detail/elaboration.md` § 1.
513
510
  </step>
514
511
 
515
512
  <step name="resume_from_file">
516
- **Resume testing from UAT file:**
517
-
518
- **First run `reconcile_gaps`** (above) so gaps already fixed by `/gsd:execute-phase --gaps-only` are marked `resolved` before testing resumes (#1921).
519
-
520
- Read the full UAT file.
513
+ **Resume testing from UAT file:** first run `reconcile_gaps` (above), then read the full UAT file.
521
514
 
522
515
  Find first test with `result: [pending]`.
523
516
  If no `[pending]` test found → go to `complete_session`.
524
517
 
525
- Announce:
526
- ```
527
- Resuming: Phase {phase} UAT
528
- Progress: {passed + issues + skipped}/{total}
529
- Issues found so far: {issues count}
530
-
531
- Continuing from Test {N}...
532
- ```
518
+ Otherwise announce progress and continue from that test at `present_test`.
533
519
 
534
- Update Current Test section with the pending test.
535
- Proceed to `present_test`.
520
+ Exact resume-announcement wording: `gsd-core/workflows/verify-work/detail/elaboration.md` § 2.
536
521
  </step>
537
522
 
538
523
  <step name="complete_session">
@@ -570,6 +555,42 @@ Commit the UAT file:
570
555
  gsd_run query commit "test({phase_num}): complete UAT - {passed} passed, {issues} issues" --files ".planning/phases/XX-name/{phase_num}-UAT.md"
571
556
  ```
572
557
 
558
+ **If the UAT file has a non-empty `## Deferred Follow-Ups` section,** those items are currently visible only inside this phase's `*-UAT.md` — offer to promote them to the roadmap backlog so they stay visible at the project level (#4546; reuses the exact entry mechanism `next.md`'s `prior_phase_completeness` step uses for plans-without-summaries):
559
+
560
+ ```
561
+ Deferred follow-ups recorded: {N}
562
+
563
+ They currently live only in {phase_num}-UAT.md. Promote them to the ROADMAP.md backlog?
564
+
565
+ [P] Promote to ROADMAP.md 999.x backlog
566
+ [K] Keep them in the UAT file only
567
+
568
+ Choice [K]:
569
+ ```
570
+
571
+ (TEXT_MODE: present this as a plain-text numbered list per the text-mode convention and wait for the typed choice.)
572
+
573
+ **If the user chooses [P]:**
574
+ 1. Compute the next backlog number: `{backlog_number}` = the smallest positive integer not already used by an existing `### Phase 999.{n}` heading in `.planning/ROADMAP.md` — scan the headings rather than counting them, since numbering may be non-contiguous. If `.planning/ROADMAP.md` does not exist, create it containing only a `## Backlog` section and use `1`.
575
+ 2. Append to that `## Backlog` section one backlog entry per deferred follow-up (each with its own `999.{backlog_number}` heading, incrementing per entry), with `test`/`idea`/`deferred_at` verbatim from the section's YAML and `{idea}` flattened to a single line (newlines → spaces — a multi-line response would corrupt the single-line entry; this mirrors `next.md`'s use of a slug for the same reason):
576
+
577
+ ```markdown
578
+ ### Phase 999.{backlog_number}: Follow-up — Phase {phase_num} deferred UAT follow-up: Test {test} (BACKLOG)
579
+
580
+ **Goal:** Resolve the UAT checkpoint deferred during Phase {phase_num} verification
581
+ **Source phase:** {phase_num}
582
+ **Deferred at:** {date} during /gsd:verify-work {phase} session completion
583
+ **Follow-ups:**
584
+ - [ ] Test {test}: {idea} (deferred {deferred_at})
585
+ ```
586
+
587
+ 3. Commit the deferral record:
588
+ ```bash
589
+ gsd_run query commit "docs: defer Phase {phase_num} UAT follow-ups to backlog" --files .planning/ROADMAP.md
590
+ ```
591
+
592
+ **If the user chooses [K]:** continue to the summary unchanged — the deferred items remain in the UAT file's `## Deferred Follow-Ups` section.
593
+
573
594
  Present summary:
574
595
  ```
575
596
  ## UAT Complete: Phase {phase}
@@ -631,7 +652,7 @@ If an active secure-phase step hook exists AND `SECURITY_FILE` exists: check fro
631
652
 
632
653
  If no active secure-phase step hook exists OR (`SECURITY_FILE` exists AND `threats_open` is `0`):
633
654
 
634
- If execution verification is waiting only on human UAT and this session recorded zero issues, canonicalize the report before the shared completion predicate:
655
+ If execution verification is waiting only on human UAT and this session recorded zero issues, canonicalize the report before the shared completion predicate. (#4663) Zero issues is NOT pass evidence on its own — blocked rows are not issues by this workflow's own rule, so a session that observed nothing (0 passed / 0 issues / N blocked) must NOT flip the report. The flip runs the SAME UAT-row predicate the phase-close uses, in its `--uat-only` form: it skips the verification-status blockers (the report still reads `human_needed` at this point — the full predicate could never pass here), and `passed` means at least one UAT check passed with no row pending/blocked/failed or skipped without a reason. The flagged transition-gate call below stays the final say on canonical verification:
635
656
 
636
657
  ```bash
637
658
  PHASE_DIR=$(printf '%s' "$INIT" | jq -r '.phase_dir // empty')
@@ -640,19 +661,35 @@ VERIFICATION_STATUS=$(gsd_run query verification.status "$PHASE_DIR" 2>/dev/null
640
661
  VERIFICATION_STATUS_VALUE=$(printf '%s' "$VERIFICATION_STATUS" | jq -r '.status // empty' 2>/dev/null || echo "")
641
662
  PHASE_VERIFICATION_STATUS="$VERIFICATION_STATUS_VALUE"
642
663
  if [ "$VERIFICATION_STATUS_VALUE" = "human_needed" ]; then
643
- gsd_run query frontmatter.set "$VERIFICATION_FILE" --field status --value passed
664
+ UAT_PRECHECK=$(gsd_run phase uat-passed "{phase}" --uat-only 2>/dev/null)
665
+ UAT_PRECHECK_PASSED=$(printf '%s' "$UAT_PRECHECK" | jq -r '.passed // false' 2>/dev/null || echo "false")
666
+ if [ "$UAT_PRECHECK_PASSED" = "true" ]; then
667
+ gsd_run query frontmatter.set "$VERIFICATION_FILE" --field status --value passed
668
+ else
669
+ UAT_BLOCKERS=$(printf '%s' "$UAT_PRECHECK" | jq -r '.blockers | length' 2>/dev/null)
670
+ [ -n "$UAT_BLOCKERS" ] || UAT_BLOCKERS="?"
671
+ echo "NOT canonicalizing: ${UAT_BLOCKERS} UAT row(s) blocked or not passing; verification stays human_needed. Resolve or pass them, then re-run /gsd:verify-work {phase}." >&2
672
+ fi
644
673
  fi
645
674
  ```
646
675
 
647
- If `PHASE_VERIFICATION_STATUS` is `stale`, stop before phase advancement and present:
676
+ If `PHASE_VERIFICATION_STATUS` is `stale`, the covered source files changed after the verifier
677
+ last ran — re-run the VERIFIER, not this workflow (`/gsd:verify-work` never rewrites
678
+ VERIFICATION.md; its only write is the human_needed canonicalization, #4663). Spawn the
679
+ verifier for this phase exactly as execute-phase's `verify_phase_goal` step does (subagent
680
+ `gsd-verifier`; phase directory, goal, requirement IDs, and all SUMMARYs in
681
+ `<required_reading>`), then re-read `verification.status` and continue at the fresh/passed
682
+ case below. (#4682)
648
683
 
649
684
  ```
650
- All UAT tests passed, but phase advancement is blocked until canonical verification is fresh.
685
+ Verification is stale: covered source files changed after the verifier last ran.
651
686
 
652
687
  Blocking completion:
653
688
  verification is stale
654
689
 
655
- - `/gsd:verify-work {phase}` — re-run verification against the latest summaries
690
+ - Re-run the verifier for phase {phase} (dispatch `gsd-verifier` as in execute-phase's
691
+ verify_phase_goal step) to regenerate VERIFICATION.md with a fresh digest, then re-run
692
+ `/gsd:verify-work {phase}`
656
693
  ```
657
694
 
658
695
  Otherwise, check the shared UAT-plus-verification completion predicate before transition:
@@ -726,138 +763,29 @@ SECURITY: File paths in output are constructed from validated path components on
726
763
  </step>
727
764
 
728
765
  <step name="diagnose_issues">
729
- **Diagnose root causes before planning fixes:**
730
-
731
- ```
732
- ---
733
-
734
- {N} issues found. Diagnosing root causes...
735
-
736
- Spawning parallel debug agents to investigate each issue.
737
- ```
738
-
739
- - Load diagnose-issues workflow
740
- - Follow @~/.claude/gsd-core/workflows/diagnose-issues.md
741
- - Spawn parallel debug agents for each issue
742
- - Collect root causes
743
- - Update UAT.md with root causes
744
- - Proceed to `plan_gap_closure`
745
-
746
- Diagnosis runs automatically - no user prompt. Parallel agents investigate simultaneously, so overhead is minimal and fixes are more accurate.
766
+ When UAT testing found issues, this sub-flow (diagnose_issues -> plan_gap_closure -> verify_gap_plans -> revision_loop) runs before present_ready; a session with zero issues never reaches it. Spawn parallel debug agents (one per issue, via diagnose-issues.md) to find root causes with no user prompt, then update UAT.md and proceed to plan_gap_closure.
747
767
  </step>
748
768
 
749
769
  <step name="plan_gap_closure">
750
- **Auto-plan fixes from diagnosed gaps:**
751
-
752
- Display:
753
- ```
754
- ### GSD ► PLANNING FIXES
755
-
756
- ◆ Spawning planner for gap closure... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)
757
- ```
758
-
759
- Spawn gsd-planner in --gaps mode:
760
-
761
- <!-- #2517 model-omit-on-inherit -->
762
-
763
- > **Model omission (#2517).** Omit the `model` parameter entirely when the value it would carry (`planner_model`, `checker_model`) is `"inherit"` or empty. An empty value 404s on runtimes without native tier aliases — the default on non-Claude runtimes. Omitting it inherits the orchestrator's model. See @gsd-core/references/model-profile-resolution.md.
764
-
765
- ````
766
- Agent(
767
- prompt="""
768
- <planning_context>
769
-
770
- **Phase:** {phase_number}
771
- **Mode:** gap_closure
772
-
773
- <required_reading>
774
- - {phase_dir}/{phase_num}-UAT.md (UAT with diagnoses)
775
- - {state_path} (Project State)
776
- - {roadmap_path} (Roadmap)
777
- </required_reading>
778
-
779
- ${AGENT_SKILLS_PLANNER}
780
-
781
- </planning_context>
782
-
783
- <downstream_consumer>
784
- Output consumed by /gsd:execute-phase
785
- Plans must be executable prompts.
786
-
787
- <!-- #2508 runtime-aware-dispatch -->
788
-
789
- > **Runtime-aware dispatch (#2508 Phase 4).** GSD workflows dispatch specialized subagents by role. Before dispatching on a built-in-only runtime (kimi-code — three built-ins only), resolve the role to a built-in via `gsd_run query resolve-dispatch-type --requested <role> --raw`. On named-dispatch runtimes (Claude/OpenCode/…) the role is returned unchanged; on kimi-code it maps to `coder`/`explore`/`plan` by role-suffix. The persona rides `${AGENT_SKILLS_<ROLE>}` (Phase 3) regardless. See @gsd-core/references/runtime-aware-dispatch.md.
790
-
791
- **Gap linkage (#1921):** each created `*-PLAN.md` MUST list the UAT gap ids it addresses in its frontmatter:
792
- ```yaml
793
- ---
794
- gap_closure: true
795
- gap_ids: [G-{phase}-{N}, ...] # the ## Gaps gap_id values this plan fixes
796
- ---
797
- ```
798
- This lets `/gsd:verify-work` reconcile resolved gaps on resume (a gap whose plan has a matching `*-SUMMARY.md` is marked `status: resolved`, not re-diagnosed as a fresh blocker).
799
- </downstream_consumer>
800
- """,
801
- subagent_type="gsd-planner",
802
- model="{planner_model}",
803
- description="Plan gap fixes for Phase {phase}"
804
- )
805
- ````
770
+ Spawn gsd-planner in --gaps mode against the UAT (with diagnoses), `{state_path}` (Project State), and `{roadmap_path}` (Roadmap). Each created PLAN.md MUST carry `gap_closure: true` and `gap_ids: [...]` in its frontmatter (#1921) so a later verify-work resume can reconcile it.
806
771
 
772
+ <!-- gsd:protected -->
807
773
  > **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.
808
774
 
809
- On return:
810
- - **PLANNING COMPLETE:** Proceed to `verify_gap_plans`
811
- - **PLANNING INCONCLUSIVE:** Report and offer manual intervention
775
+ PLANNING COMPLETE proceeds to verify_gap_plans; PLANNING INCONCLUSIVE reports and offers manual intervention.
812
776
  </step>
813
777
 
814
778
  <step name="verify_gap_plans">
815
- **Verify fix plans with checker:**
816
-
817
- Display:
818
- ```
819
- ### GSD ► VERIFYING FIX PLANS
820
-
821
- ◆ Spawning plan checker... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)
822
- ```
823
-
824
- Initialize: `iteration_count = 1`
825
-
826
- Spawn gsd-plan-checker:
827
-
828
- ```
829
- Agent(
830
- prompt="""
831
- <verification_context>
832
-
833
- **Phase:** {phase_number}
834
- **Phase Goal:** Close diagnosed gaps from UAT
835
-
836
- <required_reading>
837
- - {phase_dir}/*-PLAN.md (Plans to verify)
838
- </required_reading>
839
-
840
- ${AGENT_SKILLS_CHECKER}
841
-
842
- </verification_context>
843
-
844
- <expected_output>
845
- Return one of:
846
- - ## VERIFICATION PASSED — all checks pass
847
- - ## ISSUES FOUND — structured issue list
848
- </expected_output>
849
- """,
850
- subagent_type="gsd-plan-checker",
851
- model="{checker_model}",
852
- description="Verify Phase {phase} fix plans"
853
- )
854
- ```
779
+ Spawn gsd-plan-checker against the fix plans (iteration_count starts at 1), model="{checker_model}" (omit on inherit/empty, #2517).
855
780
 
781
+ <!-- gsd:protected -->
856
782
  > **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.
857
783
 
858
784
  On return:
859
785
  - **VERIFICATION PASSED:** Proceed to `present_ready`
860
786
  - **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 `present_ready`; INFO is advisory and never enters the loop (#3724). Otherwise proceed to `revision_loop`
787
+
788
+ Exact Agent() prompt fields: `gsd-core/workflows/verify-work/detail/elaboration.md` § 2.
861
789
  </step>
862
790
 
863
791
  <step name="revision_loop">
@@ -869,26 +797,6 @@ Display: `Sending back to planner for revision... (iteration {N}/3)`
869
797
 
870
798
  Spawn gsd-planner with revision context:
871
799
 
872
- ```
873
- Agent(
874
- prompt="""
875
- <revision_context>
876
-
877
- **Phase:** {phase_number}
878
- **Mode:** revision
879
-
880
- <required_reading>
881
- - {phase_dir}/*-PLAN.md (Existing plans)
882
- </required_reading>
883
-
884
- ${AGENT_SKILLS_PLANNER}
885
-
886
- **Checker issues:**
887
- {structured_issues_from_checker}
888
-
889
- </revision_context>
890
-
891
- <instructions>
892
800
  Read existing PLAN.md files. Make targeted updates to address checker issues.
893
801
 
894
802
  `required_property` + evidence + severity BIND. `fix_hint` is ONE non-binding example route: a
@@ -900,14 +808,8 @@ the alternatives rather than applying or working around it. Full contract:
900
808
  `gsd-core/references/planner-revision.md`, which you load in revision mode.
901
809
 
902
810
  Do NOT replan from scratch unless issues are fundamental.
903
- </instructions>
904
- """,
905
- subagent_type="gsd-planner",
906
- model="{planner_model}",
907
- description="Revise Phase {phase} plans"
908
- )
909
- ```
910
811
 
812
+ <!-- gsd:protected -->
911
813
  > **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.
912
814
 
913
815
  **If the planner returns `## REVISION_CONFLICT`:** do NOT increment `iteration_count` and do NOT
@@ -938,9 +840,12 @@ Offer options:
938
840
  2. Provide guidance (user gives direction, retry)
939
841
  3. Abandon (exit, user runs /gsd:plan-phase manually)
940
842
 
941
- Wait for user response.
843
+ Then wait for the user to pick one.
844
+
845
+ Exact Agent() prompt fields (revision_context, required_reading): `gsd-core/workflows/verify-work/detail/elaboration.md` § 3.
942
846
  </step>
943
847
 
848
+
944
849
  <step name="present_ready">
945
850
  **Present completion and next steps:**
946
851
 
@@ -63,8 +63,8 @@
63
63
  const fs = require('fs');
64
64
  const path = require('path');
65
65
  const os = require('os');
66
- const { readSentinel, VALID_ISOLATION, extractDispatchIdentifiers, sentinelAppliesToDispatch } = require('./lib/isolation-sentinel.js');
67
- const { REASON_CODE } = require('./lib/isolation-deny-reason.js');
66
+ const { readSentinel, VALID_ISOLATION, extractDispatchIdentifiers, sentinelAppliesToDispatch, buildSentinelDiscard } = require('./lib/isolation-sentinel.js');
67
+ const { REASON_CODE, describeSentinelDiscard } = require('./lib/isolation-deny-reason.js');
68
68
  const { HOOK_ON_CRASH, allow, deny, crash } = require('./lib/hook-exit.js');
69
69
 
70
70
  // Required at module top, alongside the other ./lib requires — NOT behind
@@ -337,6 +337,30 @@ function resolveRegistryIsolation(cwd, configPath) {
337
337
  if (!useWorktrees) isolation = 'none';
338
338
  }
339
339
 
340
+ // #4734: a harness worktree can never be created in a directory that is not
341
+ // a git repository (or a repository with no commits) — git's definitive
342
+ // exit-128 answer on `rev-parse HEAD`. Demanding the isolation flag there
343
+ // blocked every flag-less dispatch the moment the sentinel went stale, on a
344
+ // root where no worktree can exist. Degrade to 'none' exactly like the
345
+ // use_worktrees opt-out above; the classification is owned by
346
+ // `classifyGitHead` (worktree-base-ref.cjs) — the same single owner the
347
+ // base-check's degrade decision uses — and ambiguous or failed resolutions
348
+ // keep the conservative (enforce) default, matching that check's own
349
+ // fail-closed treatment of the same classes.
350
+ if (isolation === 'harness-worktree') {
351
+ try {
352
+ ensureRuntimeBuild();
353
+ const { classifyGitHead } = require('../gsd-core/bin/lib/worktree-base-ref.cjs');
354
+ if (classifyGitHead({ cwd }).status === 'definitive-absence') {
355
+ isolation = 'none';
356
+ }
357
+ } catch {
358
+ // Unbuilt or unreadable runtime library — keep the conservative
359
+ // (enforce) default rather than silently disabling the guard, same
360
+ // posture as the ladder fallback above.
361
+ }
362
+ }
363
+
340
364
  return { isolation, harnessFlag };
341
365
  }
342
366
 
@@ -385,16 +409,20 @@ function resolveIsolationState(cwd, { clock = Date, dispatchIds = null } = {}) {
385
409
  projectExists = false;
386
410
  }
387
411
  if (!projectExists) {
388
- return { gsdProject: false, isolation: null, harnessFlag: null, error: null };
412
+ return { gsdProject: false, isolation: null, harnessFlag: null, error: null, sentinelDiscarded: null };
389
413
  }
390
414
 
391
415
  const sentinel = readSentinel(cwd, { clock });
392
- if (sentinel.present && !sentinel.stale && sentinelAppliesToDispatch(sentinel, dispatchIds)) {
416
+ // Hoisted so the "sentinel was present/fresh but did not apply" case below
417
+ // (#4594 row 15 — Postel's-Law finding) can distinguish itself from
418
+ // "absent"/"stale" without re-deriving applicability.
419
+ const applies = sentinelAppliesToDispatch(sentinel, dispatchIds);
420
+ if (sentinel.present && !sentinel.stale && applies) {
393
421
  if (sentinel.isolation !== 'harness-worktree') {
394
- return { gsdProject: true, isolation: sentinel.isolation, harnessFlag: null, error: null };
422
+ return { gsdProject: true, isolation: sentinel.isolation, harnessFlag: null, error: null, sentinelDiscarded: null };
395
423
  }
396
424
  if (sentinel.harnessFlag) {
397
- return { gsdProject: true, isolation: 'harness-worktree', harnessFlag: sentinel.harnessFlag, error: null };
425
+ return { gsdProject: true, isolation: 'harness-worktree', harnessFlag: sentinel.harnessFlag, error: null, sentinelDiscarded: null };
398
426
  }
399
427
  // #3045 BLOCKER 2 fix: the sentinel already PROVED this dispatch requires
400
428
  // isolation (it resolved harness-worktree) but carries no usable flag —
@@ -423,6 +451,7 @@ function resolveIsolationState(cwd, { clock = Date, dispatchIds = null } = {}) {
423
451
  'dispatch-isolation sentinel resolved "harness-worktree" but recorded no harness_flag — ' +
424
452
  'cannot verify what parameter the dispatch must carry.'
425
453
  ),
454
+ sentinelDiscarded: null,
426
455
  };
427
456
  }
428
457
 
@@ -430,11 +459,19 @@ function resolveIsolationState(cwd, { clock = Date, dispatchIds = null } = {}) {
430
459
  // conservative fallback (#3045 finding — must still cover fail-closed case
431
460
  // (a): a project that opted out of worktrees entirely via
432
461
  // workflow.use_worktrees).
462
+ //
463
+ // #4594 row 15: a PRESENT, FRESH sentinel that simply does not apply to
464
+ // THIS dispatch (identifiers disagree) is a distinct case from "absent" or
465
+ // "stale" — record what was discarded so evaluateDispatch can name it in a
466
+ // block reason instead of silently falling through to a registry-resolution
467
+ // message that never mentions the sentinel existed.
468
+ const sentinelDiscarded = buildSentinelDiscard(sentinel, dispatchIds);
469
+
433
470
  try {
434
471
  const { isolation, harnessFlag } = resolveRegistryIsolation(cwd, configPath);
435
- return { gsdProject: true, isolation, harnessFlag, error: null };
472
+ return { gsdProject: true, isolation, harnessFlag, error: null, sentinelDiscarded };
436
473
  } catch (err) {
437
- return { gsdProject: true, isolation: null, harnessFlag: null, error: err };
474
+ return { gsdProject: true, isolation: null, harnessFlag: null, error: err, sentinelDiscarded };
438
475
  }
439
476
  }
440
477
 
@@ -464,10 +501,16 @@ function evaluateDispatch(data, { clock = Date } = {}) {
464
501
  }
465
502
 
466
503
  const cwd = data.cwd || process.cwd();
467
- // #3045 SECURITY F2: best-effort plan/phase extraction from this
468
- // dispatch's own description text, so a fresh sentinel that disagrees
469
- // with THIS dispatch is treated as inapplicable rather than trusted.
470
- const dispatchIds = extractDispatchIdentifiers(toolInput.description);
504
+ // #3045 SECURITY F2 / #4594: best-effort plan/phase extraction from this
505
+ // dispatch's own text, so a fresh sentinel that disagrees with THIS
506
+ // dispatch is treated as inapplicable rather than trusted. PROMPT FIRST:
507
+ // `description` is short, model-authored free text that only carries usable
508
+ // identity when the model happens to reproduce the dispatch template
509
+ // verbatim, while the canonical `[gsd:dispatch phase="…" plan="…"]` marker
510
+ // (or, failing that, the prose fallback) lives in the prompt body itself —
511
+ // `description` is kept only as a fallback for a marker/prose match that
512
+ // exists solely in it.
513
+ const dispatchIds = extractDispatchIdentifiers(toolInput.prompt, toolInput.description);
471
514
  const state = resolveIsolationState(cwd, { clock, dispatchIds });
472
515
 
473
516
  if (!state.gsdProject) return { action: 'allow' };
@@ -493,7 +536,8 @@ function evaluateDispatch(data, { clock = Date } = {}) {
493
536
  `required — a guard that cannot verify must not answer "safe" (#3050). Retry once the ` +
494
537
  `project configuration is readable.`;
495
538
  const reasonCode = isBuildFailure ? REASON_CODE.RUNTIME_BUILD_FAILED : REASON_CODE.CONFIG_UNREADABLE;
496
- return { action: 'block', reason, reasonCode };
539
+ const fullReason = state.sentinelDiscarded ? reason + describeSentinelDiscard(state.sentinelDiscarded) : reason;
540
+ return { action: 'block', reason: fullReason, reasonCode, sentinelDiscarded: state.sentinelDiscarded };
497
541
  }
498
542
 
499
543
  if (state.isolation !== 'harness-worktree') return { action: 'allow' };
@@ -503,13 +547,14 @@ function evaluateDispatch(data, { clock = Date } = {}) {
503
547
 
504
548
  if (toolInput[parsed.param] === parsed.value) return { action: 'allow' };
505
549
 
506
- const reason =
550
+ let reason =
507
551
  `Agent isolation guard: this project's dispatch isolation resolves to "harness-worktree", ` +
508
552
  `but the Agent() dispatch for subagent_type="${subagentType}" is missing ` +
509
553
  `${parsed.param}="${parsed.value}". Add ${parsed.param}="${parsed.value}" to the Agent() ` +
510
554
  `call so the executor runs in an isolated worktree instead of the primary checkout ` +
511
555
  `(gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md).`;
512
- return { action: 'block', reason, reasonCode: REASON_CODE.HARNESS_FLAG_MISSING };
556
+ if (state.sentinelDiscarded) reason += describeSentinelDiscard(state.sentinelDiscarded);
557
+ return { action: 'block', reason, reasonCode: REASON_CODE.HARNESS_FLAG_MISSING, sentinelDiscarded: state.sentinelDiscarded };
513
558
  }
514
559
 
515
560
  /* istanbul ignore next -- stdin adapter, exercised via spawnSync in tests */
@@ -524,7 +569,12 @@ function main() {
524
569
  const data = JSON.parse(input);
525
570
  const decision = evaluateDispatch(data);
526
571
  if (decision.action === 'block') {
527
- const out = { decision: 'block', reason: decision.reason, reason_code: decision.reasonCode };
572
+ const out = {
573
+ decision: 'block',
574
+ reason: decision.reason,
575
+ reason_code: decision.reasonCode,
576
+ sentinel_discarded: decision.sentinelDiscarded ?? null,
577
+ };
528
578
  // Kimi feeds stderr (not stdout) back to the model on exit 2.
529
579
  deny(out, decision.reason);
530
580
  }