@opengsd/gsd-core 1.10.0 → 1.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (328) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/agents/gsd-debug-session-manager.md +11 -0
  4. package/agents/gsd-doc-synthesizer.md +2 -4
  5. package/agents/gsd-executor.md +5 -5
  6. package/agents/gsd-mempalace-curator.md +5 -2
  7. package/agents/gsd-phase-researcher.md +20 -1
  8. package/agents/gsd-plan-checker.md +37 -0
  9. package/agents/gsd-planner.md +44 -46
  10. package/agents/gsd-user-profiler.md +3 -0
  11. package/agents/gsd-verifier.md +12 -3
  12. package/bin/install.js +841 -971
  13. package/bin/lib/ui-safety-gate.cjs +2 -0
  14. package/commands/gsd/code-review.md +1 -1
  15. package/commands/gsd/execute-phase.md +1 -1
  16. package/commands/gsd/map-codebase.md +1 -1
  17. package/commands/gsd/mempalace-capture.md +1 -1
  18. package/commands/gsd/mempalace-recall.md +1 -1
  19. package/commands/gsd/new-milestone.md +1 -1
  20. package/commands/gsd/quick.md +1 -1
  21. package/commands/gsd/review-backlog.md +2 -1
  22. package/commands/gsd/verify-work.md +1 -1
  23. package/gsd-core/bin/gsd-tools.cjs +469 -88
  24. package/gsd-core/bin/lib/active-workstream-store.cjs +138 -22
  25. package/gsd-core/bin/lib/agent-install-check.cjs +230 -32
  26. package/gsd-core/bin/lib/api-coverage.cjs +3 -5
  27. package/gsd-core/bin/lib/artifacts.cjs +3 -0
  28. package/gsd-core/bin/lib/assumption-delta.cjs +2 -4
  29. package/gsd-core/bin/lib/audit-command-router.cjs +9 -2
  30. package/gsd-core/bin/lib/audit.cjs +876 -240
  31. package/gsd-core/bin/lib/broken-windows.cjs +1 -1
  32. package/gsd-core/bin/lib/capability-consent.cjs +149 -15
  33. package/gsd-core/bin/lib/capability-lifecycle.cjs +45 -0
  34. package/gsd-core/bin/lib/capability-registry.cjs +575 -101
  35. package/gsd-core/bin/lib/capability-source.cjs +92 -0
  36. package/gsd-core/bin/lib/capability-trust.cjs +444 -25
  37. package/gsd-core/bin/lib/capability-validator.cjs +495 -22
  38. package/gsd-core/bin/lib/capability-writer.cjs +3 -2
  39. package/gsd-core/bin/lib/check-command-router.cjs +71 -37
  40. package/gsd-core/bin/lib/claude-orchestration.cjs +56 -3
  41. package/gsd-core/bin/lib/codex-agent-toml.cjs +329 -0
  42. package/gsd-core/bin/lib/command-aliases.cjs +22 -0
  43. package/gsd-core/bin/lib/command-roster.cjs +44 -1
  44. package/gsd-core/bin/lib/commands.cjs +651 -86
  45. package/gsd-core/bin/lib/commonjs-marker.cjs +12 -6
  46. package/gsd-core/bin/lib/complexity-trigger.cjs +1172 -0
  47. package/gsd-core/bin/lib/config-loader.cjs +75 -0
  48. package/gsd-core/bin/lib/config.cjs +10 -1
  49. package/gsd-core/bin/lib/core-utils.cjs +127 -29
  50. package/gsd-core/bin/lib/decisions.cjs +23 -0
  51. package/gsd-core/bin/lib/fallow-runner.cjs +20 -44
  52. package/gsd-core/bin/lib/frontmatter.cjs +155 -20
  53. package/gsd-core/bin/lib/gap-checker.cjs +68 -7
  54. package/gsd-core/bin/lib/git-base-branch.cjs +102 -0
  55. package/gsd-core/bin/lib/gsd2-import.cjs +10 -1
  56. package/gsd-core/bin/lib/health-diagnostic-rules/agent-install.cjs +101 -0
  57. package/gsd-core/bin/lib/health-diagnostic-rules/config-validation.cjs +348 -0
  58. package/gsd-core/bin/lib/health-diagnostic-rules/consistency.cjs +145 -0
  59. package/gsd-core/bin/lib/health-diagnostic-rules/install-surface-shadowing.cjs +98 -0
  60. package/gsd-core/bin/lib/health-diagnostic-rules/milestone-archive-hygiene.cjs +100 -0
  61. package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +222 -0
  62. package/gsd-core/bin/lib/health-diagnostic-rules/roadmap-disk-consistency.cjs +265 -0
  63. package/gsd-core/bin/lib/health-diagnostic-rules/root-existence.cjs +161 -0
  64. package/gsd-core/bin/lib/health-diagnostic-rules/state-consistency.cjs +303 -0
  65. package/gsd-core/bin/lib/health-diagnostic-rules/worktree-health.cjs +173 -0
  66. package/gsd-core/bin/lib/health-diagnostic-types.cjs +68 -0
  67. package/gsd-core/bin/lib/health-diagnostic.cjs +431 -0
  68. package/gsd-core/bin/lib/host-runtime-detection.cjs +134 -0
  69. package/gsd-core/bin/lib/init.cjs +321 -129
  70. package/gsd-core/bin/lib/install-effort-resolver.cjs +73 -30
  71. package/gsd-core/bin/lib/install-engine.cjs +745 -258
  72. package/gsd-core/bin/lib/install-fs-adapter.cjs +262 -0
  73. package/gsd-core/bin/lib/install-model-override-resolver.cjs +203 -0
  74. package/gsd-core/bin/lib/install-profiles.cjs +134 -57
  75. package/gsd-core/bin/lib/install-scope.cjs +270 -0
  76. package/gsd-core/bin/lib/install-shadow-report.cjs +385 -0
  77. package/gsd-core/bin/lib/installed-surface-resolver.cjs +381 -0
  78. package/gsd-core/bin/lib/installer-migrations.cjs +138 -31
  79. package/gsd-core/bin/lib/io.cjs +10 -0
  80. package/gsd-core/bin/lib/markdown-sectionizer.cjs +2 -1
  81. package/gsd-core/bin/lib/markdown-table.cjs +133 -20
  82. package/gsd-core/bin/lib/milestone-lock.cjs +248 -0
  83. package/gsd-core/bin/lib/milestone.cjs +754 -70
  84. package/gsd-core/bin/lib/model-catalog.cjs +59 -1
  85. package/gsd-core/bin/lib/model-resolver.cjs +183 -40
  86. package/gsd-core/bin/lib/normalize-test-command.cjs +1 -1
  87. package/gsd-core/bin/lib/pattern.cjs +122 -0
  88. package/gsd-core/bin/lib/phase-estimation.cjs +1 -1
  89. package/gsd-core/bin/lib/phase-id.cjs +444 -36
  90. package/gsd-core/bin/lib/phase-lifecycle.cjs +28 -3
  91. package/gsd-core/bin/lib/phase-locator.cjs +125 -18
  92. package/gsd-core/bin/lib/phase.cjs +646 -143
  93. package/gsd-core/bin/lib/plan-dependency-graph.cjs +72 -1
  94. package/gsd-core/bin/lib/plan-drift-guard.cjs +120 -0
  95. package/gsd-core/bin/lib/plan-scan.cjs +86 -2
  96. package/gsd-core/bin/lib/planning-scope.cjs +31 -0
  97. package/gsd-core/bin/lib/planning-snapshot.cjs +890 -0
  98. package/gsd-core/bin/lib/planning-workspace.cjs +56 -6
  99. package/gsd-core/bin/lib/probe-core.cjs +1 -1
  100. package/gsd-core/bin/lib/profile-output.cjs +1 -1
  101. package/gsd-core/bin/lib/refactor-trigger-command-router.cjs +740 -0
  102. package/gsd-core/bin/lib/retired-artifact-cleanup.cjs +11 -6
  103. package/gsd-core/bin/lib/review-lane-descriptor.cjs +13 -4
  104. package/gsd-core/bin/lib/review-lane-invocation.cjs +30 -0
  105. package/gsd-core/bin/lib/review-lane-runner.cjs +421 -66
  106. package/gsd-core/bin/lib/review-reviewer-selection.cjs +13 -18
  107. package/gsd-core/bin/lib/roadmap-command-router.cjs +34 -0
  108. package/gsd-core/bin/lib/roadmap-parser.cjs +943 -184
  109. package/gsd-core/bin/lib/roadmap-upgrade.cjs +37 -10
  110. package/gsd-core/bin/lib/roadmap.cjs +385 -94
  111. package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +608 -46
  112. package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +14 -2
  113. package/gsd-core/bin/lib/runtime-artifact-layout.cjs +426 -55
  114. package/gsd-core/bin/lib/runtime-config-adapter-registry.cjs +3 -2
  115. package/gsd-core/bin/lib/runtime-homes.cjs +69 -3
  116. package/gsd-core/bin/lib/runtime-hooks-surface.cjs +115 -3
  117. package/gsd-core/bin/lib/runtime-name-policy.cjs +3 -1
  118. package/gsd-core/bin/lib/runtime-slash.cjs +27 -9
  119. package/gsd-core/bin/lib/security.cjs +104 -5
  120. package/gsd-core/bin/lib/shell-command-projection.cjs +275 -3
  121. package/gsd-core/bin/lib/smart-entry.cjs +142 -22
  122. package/gsd-core/bin/lib/state-command-router.cjs +5 -1
  123. package/gsd-core/bin/lib/state-document.cjs +152 -8
  124. package/gsd-core/bin/lib/state-transition.cjs +371 -117
  125. package/gsd-core/bin/lib/state.cjs +1794 -357
  126. package/gsd-core/bin/lib/surface.cjs +23 -9
  127. package/gsd-core/bin/lib/text-lines.cjs +80 -0
  128. package/gsd-core/bin/lib/token-scanner.cjs +76 -0
  129. package/gsd-core/bin/lib/uat-predicate.cjs +9 -3
  130. package/gsd-core/bin/lib/uat.cjs +399 -56
  131. package/gsd-core/bin/lib/ui-frontend-evidence.cjs +157 -0
  132. package/gsd-core/bin/lib/ui-safety-gate.cjs +14 -5
  133. package/gsd-core/bin/lib/unusable-input.cjs +24 -0
  134. package/gsd-core/bin/lib/update-context.cjs +8 -2
  135. package/gsd-core/bin/lib/user-artifact-staging.cjs +705 -0
  136. package/gsd-core/bin/lib/validate.cjs +20 -6
  137. package/gsd-core/bin/lib/vendor/README.md +37 -0
  138. package/gsd-core/bin/lib/vendor/re2js.cjs +6480 -0
  139. package/gsd-core/bin/lib/vendor/re2js.d.cts +938 -0
  140. package/gsd-core/bin/lib/verification-command-router.cjs +2 -1
  141. package/gsd-core/bin/lib/verification.cjs +258 -8
  142. package/gsd-core/bin/lib/verify.cjs +368 -888
  143. package/gsd-core/bin/lib/workstream-inventory-builder.cjs +53 -32
  144. package/gsd-core/bin/lib/workstream-inventory.cjs +63 -10
  145. package/gsd-core/bin/lib/workstream.cjs +2 -2
  146. package/gsd-core/bin/lib/worktree-safety.cjs +176 -9
  147. package/gsd-core/bin/shared/config-defaults.manifest.json +1 -0
  148. package/gsd-core/bin/shared/config-schema.manifest.json +7 -1
  149. package/gsd-core/references/agent-contracts.md +43 -26
  150. package/gsd-core/references/checkpoints.md +2 -2
  151. package/gsd-core/references/context-budget.md +1 -1
  152. package/gsd-core/references/dispatch-isolation-gate.md +138 -0
  153. package/gsd-core/references/doc-conflict-engine.md +1 -1
  154. package/gsd-core/references/execute-mvp-tdd.md +3 -3
  155. package/gsd-core/references/execute-phase-between-wave-reset.md +6 -2
  156. package/gsd-core/references/execute-phase-context-guard.md +1 -1
  157. package/gsd-core/references/execute-phase-response-language.md +1 -1
  158. package/gsd-core/references/execute-phase-wave-guard.md +6 -2
  159. package/gsd-core/references/gate-prompts.md +1 -1
  160. package/gsd-core/references/git-planning-commit.md +2 -1
  161. package/gsd-core/references/loop-hook-dispatch.md +39 -2
  162. package/gsd-core/references/model-profiles.md +12 -4
  163. package/gsd-core/references/mvp-concepts.md +9 -9
  164. package/gsd-core/references/planner-guidance.md +3 -9
  165. package/gsd-core/references/planner-preconditions.md +1 -1
  166. package/gsd-core/references/planner-reviews.md +1 -1
  167. package/gsd-core/references/planning-config.md +8 -6
  168. package/gsd-core/references/revision-loop.md +1 -1
  169. package/gsd-core/references/specless-probe-fallback.md +1 -1
  170. package/gsd-core/references/universal-anti-patterns.md +3 -3
  171. package/gsd-core/references/verifier-phase-gates.md +192 -0
  172. package/gsd-core/references/verify-mvp-mode.md +1 -1
  173. package/gsd-core/references/workstream-flag.md +22 -6
  174. package/gsd-core/templates/discussion-log.md +1 -1
  175. package/gsd-core/templates/phase-prompt.md +2 -4
  176. package/gsd-core/templates/state.md +4 -4
  177. package/gsd-core/templates/verification-report.md +9 -1
  178. package/gsd-core/workflows/ai-integration-phase.md +9 -11
  179. package/gsd-core/workflows/autonomous.md +1 -1
  180. package/gsd-core/workflows/cleanup.md +62 -3
  181. package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +13 -3
  182. package/gsd-core/workflows/code-review-fix.md +37 -10
  183. package/gsd-core/workflows/code-review.md +38 -12
  184. package/gsd-core/workflows/complete-milestone.md +141 -18
  185. package/gsd-core/workflows/debug.md +7 -5
  186. package/gsd-core/workflows/diagnose-issues.md +35 -9
  187. package/gsd-core/workflows/discuss-phase/modes/chain.md +2 -1
  188. package/gsd-core/workflows/discuss-phase/modes/default.md +1 -1
  189. package/gsd-core/workflows/discuss-phase-assumptions.md +2 -1
  190. package/gsd-core/workflows/edit-phase.md +26 -1
  191. package/gsd-core/workflows/eval-review.md +3 -5
  192. package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +31 -6
  193. package/gsd-core/workflows/execute-phase/steps/per-plan-executor-routing.md +77 -0
  194. package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +2 -0
  195. package/gsd-core/workflows/execute-phase.md +38 -50
  196. package/gsd-core/workflows/execute-plan.md +36 -4
  197. package/gsd-core/workflows/explore.md +131 -4
  198. package/gsd-core/workflows/fast.md +10 -2
  199. package/gsd-core/workflows/health.md +73 -4
  200. package/gsd-core/workflows/import.md +4 -4
  201. package/gsd-core/workflows/ingest-docs.md +5 -5
  202. package/gsd-core/workflows/mvp-phase.md +6 -3
  203. package/gsd-core/workflows/new-milestone.md +14 -9
  204. package/gsd-core/workflows/new-project.md +14 -14
  205. package/gsd-core/workflows/next.md +12 -0
  206. package/gsd-core/workflows/plan-phase.md +41 -17
  207. package/gsd-core/workflows/plan-review-convergence.md +50 -2
  208. package/gsd-core/workflows/progress.md +34 -6
  209. package/gsd-core/workflows/quick/steps/plan-checker-loop.md +4 -4
  210. package/gsd-core/workflows/quick/steps/quick-verification.md +27 -6
  211. package/gsd-core/workflows/quick/steps/research-phase.md +2 -2
  212. package/gsd-core/workflows/quick.md +35 -15
  213. package/gsd-core/workflows/review.md +26 -5
  214. package/gsd-core/workflows/secure-phase.md +1 -1
  215. package/gsd-core/workflows/session-report.md +2 -1
  216. package/gsd-core/workflows/settings.md +66 -2
  217. package/gsd-core/workflows/ship.md +104 -44
  218. package/gsd-core/workflows/spec-phase.md +30 -12
  219. package/gsd-core/workflows/sync-skills.md +63 -8
  220. package/gsd-core/workflows/transition.md +46 -11
  221. package/gsd-core/workflows/ui-phase.md +5 -5
  222. package/gsd-core/workflows/ui-review.md +2 -2
  223. package/gsd-core/workflows/update.md +1 -1
  224. package/gsd-core/workflows/validate-phase.md +1 -1
  225. package/gsd-core/workflows/verify-work.md +9 -7
  226. package/hooks/dist/gsd-agent-isolation-guard.js +103 -14
  227. package/hooks/dist/gsd-check-update-worker.js +56 -13
  228. package/hooks/dist/gsd-check-update.js +19 -1
  229. package/hooks/dist/gsd-cursor-pre-tool.js +0 -3
  230. package/hooks/dist/gsd-cursor-subagent-start.js +77 -2
  231. package/hooks/dist/gsd-cursor-subagent-stop.js +3 -2
  232. package/hooks/dist/gsd-prompt-guard.js +21 -20
  233. package/hooks/dist/gsd-read-injection-scanner.js +38 -24
  234. package/hooks/dist/gsd-statusline.js +18 -0
  235. package/hooks/dist/gsd-update-banner.js +22 -1
  236. package/hooks/dist/gsd-workflow-guard.js +134 -36
  237. package/hooks/dist/lib/git-cmd.js +92 -59
  238. package/hooks/dist/lib/injection-patterns.js +45 -0
  239. package/hooks/dist/lib/isolation-deny-reason.js +39 -0
  240. package/hooks/dist/lib/isolation-sentinel.js +9 -0
  241. package/hooks/gsd-agent-isolation-guard.js +103 -14
  242. package/hooks/gsd-check-update-worker.js +56 -13
  243. package/hooks/gsd-check-update.js +19 -1
  244. package/hooks/gsd-cursor-pre-tool.js +0 -3
  245. package/hooks/gsd-cursor-subagent-start.js +77 -2
  246. package/hooks/gsd-cursor-subagent-stop.js +3 -2
  247. package/hooks/gsd-prompt-guard.js +21 -20
  248. package/hooks/gsd-read-injection-scanner.js +38 -24
  249. package/hooks/gsd-statusline.js +18 -0
  250. package/hooks/gsd-update-banner.js +22 -1
  251. package/hooks/gsd-workflow-guard.js +134 -36
  252. package/hooks/lib/git-cmd.js +92 -59
  253. package/hooks/lib/injection-patterns.js +45 -0
  254. package/hooks/lib/isolation-deny-reason.js +39 -0
  255. package/hooks/lib/isolation-sentinel.js +9 -0
  256. package/package.json +21 -9
  257. package/pi/gsd.cjs +19 -5
  258. package/scripts/baselines/planning-prompt-drift-baseline.json +4 -0
  259. package/scripts/baselines/planning-snapshot-bypass-baseline.json +12 -0
  260. package/scripts/baselines/unreachable-guard-drift-baseline.json +4 -0
  261. package/scripts/changeset/lint.cjs +60 -5
  262. package/scripts/check-alias-drift.cjs +7 -43
  263. package/scripts/check-contract-drift.cjs +297 -0
  264. package/scripts/ci-test-scope.cjs +19 -2
  265. package/scripts/command-contract-helpers.cjs +903 -1
  266. package/scripts/gen-adr-index.cjs +728 -38
  267. package/scripts/gen-capability-registry.cjs +3 -15
  268. package/scripts/gen-context-index.cjs +2 -11
  269. package/scripts/gen-health-docs.cjs +390 -0
  270. package/scripts/gen-inventory-manifest.cjs +50 -4
  271. package/scripts/gen-loop-host-contract.cjs +4 -24
  272. package/scripts/gen-registry.cjs +3 -14
  273. package/scripts/lib/alias-drift-families.cjs +46 -0
  274. package/scripts/lib/drift-scan.cjs +278 -0
  275. package/scripts/lint-allow-test-rule-refs.allowlist.json +1 -26
  276. package/scripts/lint-allow-test-rule-refs.effective-ceiling.json +4 -0
  277. package/scripts/lint-allow-test-rule-refs.unverified-ceiling.json +3 -0
  278. package/scripts/lint-canary-version-leak.cjs +73 -0
  279. package/scripts/lint-command-contract.cjs +96 -13
  280. package/scripts/lint-completion-predicate-drift.cjs +933 -0
  281. package/scripts/lint-completion-ratio-drift.cjs +214 -0
  282. package/scripts/lint-default-flip-documentation.cjs +193 -0
  283. package/scripts/lint-eslint-glob-coverage.allowlist.json +34 -0
  284. package/scripts/lint-eslint-glob-coverage.cjs +340 -0
  285. package/scripts/lint-frontmatter-scalar-broad-grep.cjs +237 -0
  286. package/scripts/lint-health-diagnostic-rule-table.cjs +404 -0
  287. package/scripts/lint-hooks-runtime-build-seam.cjs +262 -0
  288. package/scripts/lint-milestone-window-drift.cjs +468 -0
  289. package/scripts/lint-phase-enumeration-drift.cjs +479 -0
  290. package/scripts/lint-plan-count-drift.cjs +318 -0
  291. package/scripts/lint-planning-artifact-writer-drift.cjs +398 -0
  292. package/scripts/lint-planning-prompt-drift.cjs +434 -0
  293. package/scripts/lint-planning-snapshot-bypass-drift.cjs +544 -0
  294. package/scripts/lint-regression-test-names.cjs +15 -13
  295. package/scripts/lint-removed-but-needed.cjs +320 -0
  296. package/scripts/lint-state-field-drift.cjs +805 -0
  297. package/scripts/lint-state-write-path-drift.cjs +1045 -0
  298. package/scripts/lint-test-file-count.allowlist.json +21 -10
  299. package/scripts/lint-unreachable-guard-drift.cjs +843 -0
  300. package/scripts/lint-vendored-deps.cjs +124 -0
  301. package/scripts/pr-changed-files.cjs +63 -0
  302. package/scripts/pr-template-policy.cjs +14 -4
  303. package/scripts/prompt-injection-scan.sh +25 -0
  304. package/scripts/require-issue-link-policy.cjs +192 -0
  305. package/scripts/state-write-path-drift-baseline.json +19 -0
  306. package/scripts/sync-runtime-launcher.cjs +2 -4
  307. package/skills/gsd-autonomous/SKILL.md +0 -1
  308. package/skills/gsd-code-review/SKILL.md +1 -1
  309. package/skills/gsd-execute-phase/SKILL.md +1 -2
  310. package/skills/gsd-map-codebase/SKILL.md +1 -1
  311. package/skills/gsd-mempalace-capture/SKILL.md +1 -1
  312. package/skills/gsd-mempalace-recall/SKILL.md +1 -1
  313. package/skills/gsd-new-milestone/SKILL.md +1 -1
  314. package/skills/gsd-next/SKILL.md +0 -1
  315. package/skills/gsd-plan-phase/SKILL.md +0 -1
  316. package/skills/gsd-progress/SKILL.md +0 -1
  317. package/skills/gsd-quick/SKILL.md +1 -1
  318. package/skills/gsd-review-backlog/SKILL.md +2 -1
  319. package/skills/gsd-stats/SKILL.md +0 -1
  320. package/skills/gsd-verify-work/SKILL.md +1 -1
  321. package/vscode/package.json +1 -1
  322. package/gsd-core/workflows/discovery-phase.md +0 -298
  323. package/gsd-core/workflows/plan-milestone-gaps.md +0 -281
  324. package/gsd-core/workflows/verify-phase.md +0 -574
  325. package/scripts/affected-tests-lib.cjs +0 -554
  326. package/scripts/lint-allow-test-rule-refs.cjs +0 -162
  327. package/scripts/run-affected-tests.cjs +0 -7
  328. package/scripts/run-tests.cjs +0 -1051
@@ -30,12 +30,12 @@ Agent(
30
30
  **Task:** ${DESCRIPTION}
31
31
  **Output:** ${QUICK_DIR}/${quick_id}-RESEARCH.md
32
32
 
33
- <files_to_read>
33
+ <required_reading>
34
34
  - ${STATE_PATH} (Project state — what's already built)
35
35
  - ${PROJECT_PATH} (Project context)
36
36
  - ./CLAUDE.md or ./.claude/CLAUDE.md (if exists — project-specific guidelines)
37
37
  ${DISCUSS_MODE ? '- ' + QUICK_DIR + '/' + quick_id + '-CONTEXT.md (User decisions — research should align with these)' : ''}
38
- </files_to_read>
38
+ </required_reading>
39
39
 
40
40
  ${AGENT_SKILLS_PLANNER}
41
41
 
@@ -154,12 +154,16 @@ PROJECT_PATH="$(dirname "${quick_dir}")/PROJECT.md"
154
154
  ```bash
155
155
  USE_WORKTREES=$(gsd_run query config-get workflow.use_worktrees --raw 2>/dev/null || echo "true")
156
156
  RUNTIME=$(gsd_run query config-get runtime --default claude --raw 2>/dev/null || echo "claude")
157
- if [ "$RUNTIME" != "claude" ] && [ "$USE_WORKTREES" != "false" ]; then
158
- echo "FATAL: git worktree isolation (isolation=\"worktree\") is unsupported on runtime '$RUNTIME' — it would run executor agents unisolated against the main checkout. Set workflow.use_worktrees=false." >&2
159
- exit 1
160
- fi
161
157
  ```
162
158
 
159
+ **Resolve isolation now (#2584/#2652).** Read @gsd-core/references/dispatch-isolation-gate.md
160
+ and run its `Resolve ISOLATION`, `Single-agent dispatch sites`, and `Resolve the harness flag`
161
+ blocks in order; they set `ISOLATION`/`HARNESS_FLAG` via `query dispatch-isolation`.
162
+ `ISOLATION` — not `RUNTIME` — gates every worktree decision below. Substitute `{harnessFlag}`
163
+ in Step 6's `Agent()` with `$HARNESS_FLAG`+comma when `ISOLATION = "harness-worktree"`, else
164
+ empty. `{harnessFlag}`
165
+ is a template placeholder, not a shell variable.
166
+
163
167
  If `USE_WORKTREES` is not `"false"`, run a startup orphan sweep before spawning any executors. This reaps locked worktrees whose lock-owner process is dead, whose branch is merged into the default branch, and whose lock file mtime is older than 5 minutes. Running it at startup prevents accumulation of orphaned worktrees from prior sessions that exited without cleanup (#3707).
164
168
 
165
169
  ```bash
@@ -309,12 +313,12 @@ Agent(
309
313
  **Directory:** ${QUICK_DIR}
310
314
  **Description:** ${DESCRIPTION}
311
315
 
312
- <files_to_read>
316
+ <required_reading>
313
317
  - ${STATE_PATH} (Project State)
314
318
  - ./CLAUDE.md or ./.claude/CLAUDE.md (if exists — follow project-specific guidelines)
315
319
  ${DISCUSS_MODE ? '- ' + QUICK_DIR + '/' + quick_id + '-CONTEXT.md (User decisions — locked, do not revisit)' : ''}
316
320
  ${RESEARCH_MODE ? '- ' + QUICK_DIR + '/' + quick_id + '-RESEARCH.md (Research findings — use to inform implementation choices)' : ''}
317
- </files_to_read>
321
+ </required_reading>
318
322
 
319
323
  ${AGENT_SKILLS_PLANNER}
320
324
 
@@ -376,21 +380,37 @@ halts with a base-mismatch fatal — potentially many commits behind, not just o
376
380
  immediately before capturing `EXPECTED_BASE` so it reflects the most current local state.
377
381
 
378
382
  ```bash
379
- if [ "$RUNTIME" = "claude" ] && [ "${USE_WORKTREES:-true}" != "false" ]; then
383
+ if [ "$ISOLATION" = "harness-worktree" ] && [ "${USE_WORKTREES:-true}" != "false" ]; then
380
384
  _QUICK_SHOULD_DEGRADE=$(gsd_run query worktree.base-check --pick shouldDegrade 2>/dev/null || true)
381
385
  if [ "$_QUICK_SHOULD_DEGRADE" = "true" ]; then
382
386
  _QUICK_DEGRADE_MSG=$(gsd_run query worktree.base-check --pick message 2>/dev/null || true)
383
387
  [ -n "$_QUICK_DEGRADE_MSG" ] && printf '%s\n' "$_QUICK_DEGRADE_MSG" >&2
384
388
  echo "⚠ [#1941] Worktree fork base diverged from orchestrator HEAD — auto-degrading to sequential mode for this quick task to avoid a base-mismatch halt." >&2
385
389
  USE_WORKTREES=false
390
+ ISOLATION=none
386
391
  fi
387
392
  fi
393
+
394
+ # Re-resolve (and, as a side effect, re-persist) now that the base-check
395
+ # auto-degrade above may have changed $ISOLATION since the Step 2 gate's
396
+ # `dispatch-isolation` call (#3045). That first call recorded the NATURALLY
397
+ # resolved mode into the run-scoped sentinel the isolation guard hooks read
398
+ # (hooks/gsd-agent-isolation-guard.js, hooks/gsd-cursor-subagent-start.js via
399
+ # hooks/lib/isolation-sentinel.js). The degrade above is decided HERE, in
400
+ # shell — the resolver cannot see it — so without this the sentinel still
401
+ # asserts `harness-worktree` while the dispatch below correctly omits the
402
+ # harness flag, and the guard denies the dispatch with exit 2. `--force-isolation`
403
+ # pushes the FINAL, shell-computed value through that SAME single write path
404
+ # (`none` also clears the stored harnessFlag, since none applies to sequential
405
+ # dispatch). Best-effort: a write failure here must never fail the task — the
406
+ # guards' own sentinel-absent fallback is safe, just less precise.
407
+ gsd_run query dispatch-isolation --raw --force-isolation "$ISOLATION" >/dev/null 2>&1 || true
388
408
  ```
389
409
 
390
410
  Capture current HEAD before spawning (used for worktree branch check):
391
411
  ```bash
392
412
  EXPECTED_BASE=$(git rev-parse HEAD)
393
- if [ "${USE_WORKTREES:-true}" != "false" ]; then
413
+ if [ "$ISOLATION" = "harness-worktree" ]; then # keyed on ISOLATION like every other dispatch-coupled branch (#2652)
394
414
  # BSD/macOS mktemp only randomizes XXXXXX when it is the final path component, so make a
395
415
  # suffixless temp then append the extension — portable across BSD + GNU (#1520).
396
416
  QUICK_WORKTREE_MANIFEST=$(mktemp "${TMPDIR:-/tmp}/gsd-quick-worktree-XXXXXX") && mv "$QUICK_WORKTREE_MANIFEST" "${QUICK_WORKTREE_MANIFEST}.json" && QUICK_WORKTREE_MANIFEST="${QUICK_WORKTREE_MANIFEST}.json" || exit 1
@@ -406,7 +426,7 @@ Agent(
406
426
  prompt="
407
427
  Execute quick task ${quick_id}.
408
428
 
409
- ${USE_WORKTREES !== "false" ? `
429
+ ${ISOLATION === "harness-worktree" ? `
410
430
  <worktree_branch_check>
411
431
  ORCHESTRATOR build-time embed (NOT a sub-agent runtime step): before this dispatch, read \`gsd-core/references/worktree-branch-check.md\`, substitute \`{EXPECTED_BASE}\` with the base SHA captured above (${EXPECTED_BASE}), substitute \`{EXPECTED_BASE_ALTERNATE}\` with \`${QUICK_PLAN_PARENT}\` when it differs from \`${EXPECTED_BASE}\` (otherwise empty), and replace this note with that fragment's \`<worktree_branch_check>\` block so the dispatched prompt carries the runnable guard verbatim — do not pass this instruction through in its place.
412
432
  </worktree_branch_check>
@@ -426,12 +446,12 @@ fi
426
446
  \`\`\`
427
447
  ` : ''}
428
448
 
429
- <files_to_read>
449
+ <required_reading>
430
450
  - ${QUICK_DIR}/${quick_id}-PLAN.md (Plan)
431
451
  - ${STATE_PATH} (Project state)
432
452
  - ./CLAUDE.md or ./.claude/CLAUDE.md (Project instructions, if exists)
433
453
  - .claude/skills/ or .agents/skills/ (Project skills, if either exists — list skills, read SKILL.md for each, follow relevant rules during implementation)
434
- </files_to_read>
454
+ </required_reading>
435
455
 
436
456
  ${AGENT_SKILLS_EXECUTOR}
437
457
 
@@ -480,17 +500,17 @@ SUMMARY.md and stop — the user must rerun with worktrees disabled.
480
500
  ",
481
501
  subagent_type="gsd-executor",
482
502
  model="{executor_model}",
483
- ${USE_WORKTREES !== "false" ? 'isolation="worktree",' : ''}
503
+ {harnessFlag}
484
504
  description="Execute: ${DESCRIPTION}"
485
505
  )
486
506
  ```
487
507
 
488
508
  > **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.
489
509
 
490
- If the executor ran with `isolation="worktree"`, 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.
510
+ 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.
491
511
 
492
512
  After executor returns:
493
- 1. **Worktree cleanup:** If the executor ran with `isolation="worktree"`, merge the worktree branch back and clean up:
513
+ 1. **Worktree cleanup:** If the executor ran isolated (`ISOLATION = "harness-worktree"` at dispatch), merge the worktree branch back and clean up:
494
514
  ```bash
495
515
  QUICK_WORKTREE_MANIFEST=${QUICK_WORKTREE_MANIFEST:-$WAVE_WORKTREE_MANIFEST}
496
516
  [ -n "${QUICK_WORKTREE_MANIFEST:-}" ] && [ -f "$QUICK_WORKTREE_MANIFEST" ] || {
@@ -504,7 +524,7 @@ After executor returns:
504
524
  # Fail closed: SDK refusal (safety guard #3174/#3384) must surface — do not swallow exit 1.
505
525
  gsd_run query worktree.cleanup-wave --manifest "$QUICK_WORKTREE_MANIFEST" || exit 1
506
526
  ```
507
- If `workflow.use_worktrees` is `false`, skip this step.
527
+ If `ISOLATION` was not `"harness-worktree"` at dispatch (including a #1941 base-check degrade — that is *this* file's degrade; #2649 is the `diagnose-issues.md` / `execute-plan.md` one), skip this step.
508
528
 
509
529
  > **ISOLATED-RUN RECOVERY — FAIL SAFE (#1292):** When an isolated (worktree) run is *rejected* — the user declines to merge it, the orchestrator surfaces recovery guidance for a blocked/halted plan, or the run over-reached the requested scope — the worktree-isolation contract MUST hold through recovery. Do **NOT** propose continuing on `main`/the primary checkout as the default or recommended recovery path. Default to a **safe halt** and offer: (a) re-attempt in a **fresh, narrowly-scoped worktree**, or (b) inspect or discard the rejected worktree without merging. Any path that edits the primary checkout requires an **explicit, clearly-labeled confirmation** from the user first — editing `main` directly is never the proposed or default option for a run the user configured to be isolated.
510
530
 
@@ -266,11 +266,13 @@ done
266
266
  if [ -f ".planning/PROJECT.md" ]; then
267
267
  cp .planning/PROJECT.md "${RUN_DIR}/gsd-review-project.md"
268
268
  fi
269
- if ls "${PHASE_DIR}/"*"-CONTEXT.md" >/dev/null 2>&1; then
270
- cat "${PHASE_DIR}/"*"-CONTEXT.md" > "${RUN_DIR}/gsd-review-context.md"
269
+ _CTX=( "${PHASE_DIR}"/*-CONTEXT.md )
270
+ if [ ${#_CTX[@]} -gt 0 ]; then
271
+ cat "${_CTX[@]}" > "${RUN_DIR}/gsd-review-context.md"
271
272
  fi
272
- if ls "${PHASE_DIR}/"*"-RESEARCH.md" >/dev/null 2>&1; then
273
- cat "${PHASE_DIR}/"*"-RESEARCH.md" > "${RUN_DIR}/gsd-review-research.md"
273
+ _RESEARCH=( "${PHASE_DIR}"/*-RESEARCH.md )
274
+ if [ ${#_RESEARCH[@]} -gt 0 ]; then
275
+ cat "${_RESEARCH[@]}" > "${RUN_DIR}/gsd-review-research.md"
274
276
  fi
275
277
  if [ -f ".planning/REQUIREMENTS.md" ]; then
276
278
  cp .planning/REQUIREMENTS.md "${RUN_DIR}/gsd-review-requirements.md"
@@ -421,12 +423,31 @@ names, each gets its own `## <Adapter> Review (<instance>)` section, and ≥2 sa
421
423
  instances print a one-line shared-adapter caveat. Format in
422
424
  `gsd-core/references/reviewer-instances.md`.
423
425
 
426
+ **Resolved model (#2295):** each lane's `review-lane invoke --json` line in
427
+ `{run_dir}/gsd-review-lane-results.jsonl` carries a `model` object; render `models:` from
428
+ its `value` and `model_sources:` from its `source`. Both maps carry exactly one entry per
429
+ reviewer that appears in `reviewers:` — the two key sets always match. Write the literal
430
+ `unknown` rather than omitting a key: an omitted key is indistinguishable from the feature
431
+ not having run, and a reader must be able to tell *no model recorded* from *nothing to
432
+ look at*. Emit every `models:`/`model_sources:` value as a DOUBLE-QUOTED YAML scalar — a
433
+ legitimate model id can contain `:` (`llama3:70b`, `qwen2.5:7b`), which is unquotable as a
434
+ bare scalar; a control character is already refused at the recording seam, so quoting is
435
+ what closes the remaining `:`/`#`/leading-`-` cases. When GSD applied a reasoning effort to
436
+ a lane, its `value` already carries a `(reasoning=<level>)` suffix (e.g.
437
+ `gpt-5.6-sol (reasoning=high)`) — render it as-is, without re-deriving or re-formatting it.
438
+
424
439
  ```markdown
425
440
  ---
426
441
  phase: {N}
427
442
  reviewers: [gemini, claude, codex, coderabbit, opencode, qwen, cursor, antigravity, ollama, lm_studio, llama_cpp] # populate at runtime with only the reviewers actually invoked
428
443
  reviewed_at: {ISO timestamp}
429
444
  plans_reviewed: [{list of PLAN.md files}]
445
+ models: # resolved model per reviewer; `unknown` when not recoverable
446
+ codex: "gpt-5.6-sol (reasoning=low)"
447
+ antigravity: "unknown"
448
+ model_sources: # how each value above was determined
449
+ codex: "banner"
450
+ antigravity: "unknown"
430
451
  trimmed_reviewers: # only present if at least one reviewer was trimmed
431
452
  ollama:
432
453
  budget: 6000
@@ -464,7 +485,7 @@ trimmed_reviewers: # only present if at least one reviewer was trimmed
464
485
 
465
486
  ## Consensus Summary
466
487
 
467
- {synthesize common concerns across all reviewers. CodeRabbit is a diff-only reviewer (it never received the source-grounding prompt), so do not weight its verdict as a grounded plan review — fold in its diff findings, but base plan-level consensus on the prompt-fed reviewers. A reviewer output carrying the `[reviewed-without-repo-access]` marker (or beginning with `REVIEWED-WITHOUT-REPO-ACCESS`) ran without repo access (#2176) — treat it the same way: note its concerns, but do not count its verdict at full consensus weight.}
488
+ {synthesize common concerns across all reviewers. CodeRabbit is a diff-only reviewer (it never received the source-grounding prompt), so do not weight its verdict as a grounded plan review — fold in its diff findings, but base plan-level consensus on the prompt-fed reviewers. A reviewer output carrying the `[reviewed-without-repo-access]` marker (or beginning with `REVIEWED-WITHOUT-REPO-ACCESS`) ran without repo access (#2176) — treat it the same way: note its concerns, but do not count its verdict at full consensus weight. A reviewer output carrying the `[reviewed-without-source-citations]` marker (#3194) declared source-grounded evidence but cited no `file:line` evidence, so it reviewed the plan text only — treat it the same way: note its concerns, but do not count its verdict at full consensus weight.}
468
489
 
469
490
  ### Agreed Strengths
470
491
  {strengths mentioned by 2+ reviewers}
@@ -115,7 +115,7 @@ Print: `◆ Spawning security auditor... (runs in a subagent — no output until
115
115
  ```
116
116
  Agent(
117
117
  prompt="Read ~/.claude/agents/gsd-security-auditor.md for instructions.\n\n" +
118
- "<files_to_read>{PLAN, SUMMARY, impl files, SECURITY.md}</files_to_read>" +
118
+ "<required_reading>{PLAN, SUMMARY, impl files, SECURITY.md}</required_reading>" +
119
119
  "<threat_register>{threat register}</threat_register>" +
120
120
  "<config>asvs_level: {SECURITY_ASVS}, block_on: {SECURITY_BLOCK_ON}</config>" +
121
121
  "<constraints>Never modify implementation files. Verify mitigations exist — do not scan for new threats. Escalate implementation gaps. Return a structured verdict only — do NOT write SECURITY.md (the orchestrator owns the file write).</constraints>" +
@@ -34,7 +34,8 @@ Read `.planning/ROADMAP.md` to get milestone name and goals.
34
34
 
35
35
  Check for existing reports:
36
36
  ```bash
37
- ls -la .planning/reports/SESSION_REPORT*.md 2>/dev/null || echo "No previous reports"
37
+ _REPORTS=( .planning/reports/SESSION_REPORT*.md )
38
+ if [ -e "${_REPORTS[0]}" ]; then ls -la "${_REPORTS[@]}"; else echo "No previous reports"; fi
38
39
  ```
39
40
  </step>
40
41
 
@@ -59,7 +59,7 @@ Parse current values (default to `true` if not present):
59
59
  - `graphify.auto_update` — opt-in: auto-rebuild graph after main HEAD advances (#3347) (default: `false`)
60
60
  - `model_profile` — which model each agent uses (default: `balanced`)
61
61
  - `git.branching_strategy` — branching approach (default: `"none"`)
62
- - `workflow.use_worktrees` — whether parallel executor agents run in worktree isolation (default: `true`)
62
+ - `workflow.use_worktrees` — whether parallel executor agents run in worktree isolation (honored when the runtime declares a `dispatch.isolation` primitive — `harness-worktree` or `orchestrator-worktree`; runtimes declaring `none` default it to `false` and fail closed on an explicit `true` — #1521, #2486, #2584)
63
63
  - `model_policy.provider` — provider slug for model policy (default: `null`; known values: anthropic, openai, google, qwen; set via /gsd:config --advanced)
64
64
  - `model_policy.budget` — budget level for model policy (default: `null`; known values: high, medium, low; set via /gsd:config --advanced)
65
65
  - `model_policy.high` — model ID for high-cost tier (default: `null`; set via /gsd:config --advanced)
@@ -87,6 +87,30 @@ configure `model_overrides` manually in .planning/config.json to target specific
87
87
  models per agent.
88
88
  ```
89
89
 
90
+ **Isolation resolution for the Worktrees question (#2486):** resolve the runtime's declared executor-isolation primitive and the current worktrees value before presenting the questions. Use `inspect-dispatch-isolation`, the **sentinel-free** inspection verb — never `dispatch-isolation`, whose #3045 contract records the resolved decision to the executor-dispatch sentinel as an unconditional side effect; a settings menu must not be able to stamp a sentinel the isolation guards then enforce against real dispatches. Sentinel-free is the exact claim: the shared CLI bootstrap still runs, but nothing it does can block a dispatch. The worktrees read deliberately carries no `--default`/fallback: an absent key must stay distinguishable from an explicit `false` (empty output = key absent), which the pre-selection rule below depends on.
91
+
92
+ A failed query is not a capability verdict, so track the two apart: settings still fails closed, but
93
+ says so instead of claiming the runtime declares no primitive. The verb fail-closes an unknown runtime — or an
94
+ internal resolution error — to `none` and exits 0, so `INSPECTED_RESOLVED=false` means only "the query did not answer"
95
+ (#2486 review):
96
+
97
+ ```bash
98
+ _INSPECTED_RAW=$(gsd_run query inspect-dispatch-isolation --raw 2>/dev/null)
99
+ _ISOLATION_RC=$?
100
+ if [ $_ISOLATION_RC -ne 0 ] || [ -z "$_INSPECTED_RAW" ]; then
101
+ INSPECTED_ISOLATION=none
102
+ INSPECTED_RESOLVED=false # no verdict learned — not the same as "declares none"
103
+ else
104
+ INSPECTED_ISOLATION="$_INSPECTED_RAW"
105
+ INSPECTED_RESOLVED=true
106
+ fi
107
+ case "$INSPECTED_ISOLATION" in
108
+ harness-worktree|orchestrator-worktree|none) ;;
109
+ *) INSPECTED_ISOLATION=none; INSPECTED_RESOLVED=false ;; # out of vocabulary is not a verdict either
110
+ esac
111
+ USE_WORKTREES_CURRENT=$(gsd_run query config-get workflow.use_worktrees --raw 2>/dev/null)
112
+ ```
113
+
90
114
  Use AskUserQuestion with current values pre-selected. Questions are grouped into six visual sections; the first question in each section carries the section-denoting `header` field (AskUserQuestion renders abbreviated section tags for grouping, max 12 chars).
91
115
 
92
116
  Section layout:
@@ -113,6 +137,46 @@ Context Warnings, Research Qs
113
137
 
114
138
  **Conditional visibility — graphify.auto_update:** This question is shown only when the user's chosen `graphify.enabled` value is on. If `graphify.enabled` is off, omit the `graphify.auto_update` question and preserve the existing `graphify.auto_update` value in config (do not overwrite). Implementation: ask Graphify first; only ask Graph auto-update when Graphify is enabled.
115
139
 
140
+ **Conditional options — Worktrees (#2486):** whether a runtime can honor `workflow.use_worktrees: true` depends on its **declared `dispatch.isolation` capability, not its name** (#2584): `harness-worktree` (the host isolates each executor) and `orchestrator-worktree` (GSD drives the worktrees) both run parallel; `none` fails closed on an explicit `true`. Branch on the same negotiation the execution gate resolves, via the sentinel-free `inspect-dispatch-isolation` verb, which fail-closes unknown/undeclared/`undocumented` to `none`. The rule is one-directional: **never persist a value the isolation gate would fail closed on.** **Do not add a runtime-name test here.** Branch on `$INSPECTED_ISOLATION`:
141
+
142
+ - **If `INSPECTED_ISOLATION` ≠ `none`** (`harness-worktree` or `orchestrator-worktree`): present the Worktrees question exactly as written below — unchanged behavior. The `none` branch is the entirety of the #2486 change.
143
+ - **If `INSPECTED_ISOLATION` = `none`:** replace the question's options with the two below — do NOT offer an enabling option, and NEVER write `workflow.use_worktrees: true` from this workflow when the runtime declares no isolation primitive, regardless of the user's answer:
144
+
145
+ ```
146
+ {
147
+ question: "Use git worktrees for parallel agent isolation?",
148
+ header: "Worktrees",
149
+ multiSelect: false,
150
+ options: [
151
+ { label: "No (Recommended)", description: "Write use_worktrees: false. {FINDING}, so parallel plans run sequentially and {CONSEQUENCE}." },
152
+ { label: "Leave unchanged", description: "Do not write the key. {ABSENCE}; an existing explicit value is kept intact (e.g. for a worktree-capable install sharing this config)." }
153
+ ]
154
+ }
155
+ ```
156
+
157
+ **The three placeholders** carry the only difference between a capability GSD resolved and one it could not, so no text in this branch ever asserts a verdict that was not reached. Substitute them everywhere they appear — the two `description`s above, the pre-selection rationale, and the notice — from whichever column applies:
158
+
159
+ | | `INSPECTED_RESOLVED=true` | `INSPECTED_RESOLVED=false` |
160
+ |---|---|---|
161
+ | `{FINDING}` | this runtime has no usable executor-isolation primitive | GSD could not resolve this runtime's executor-isolation capability |
162
+ | `{CONSEQUENCE}` | execution fails closed on an explicit true | GSD cannot tell whether execution will accept an explicit true |
163
+ | `{ABSENCE}` | absent, it resolves to false wherever this emit stamped that default | absent, it is the safe state until the capability resolves |
164
+
165
+ Failing closed is right in both columns — never write `workflow.use_worktrees: true` from this branch either way. Only the explanation changes.
166
+
167
+ Persistence: "No (Recommended)" → write `workflow.use_worktrees: false`; "Leave unchanged" → do not write `workflow.use_worktrees` at all (preserve the existing value or absence).
168
+
169
+ Pre-selection: the generic "current values pre-selected" rule does not apply when `INSPECTED_ISOLATION` is `none` — an explicit `true` has no matching option by design. Pre-select "No (Recommended)" in every case, including an absent key (`$USE_WORKTREES_CURRENT` empty, the no-`--default` read's absent signal): absence resolves to `false` only where this emit stamped that default and to `true` otherwise, so leaving it absent can preserve the very state this branch prevents, while an explicit `false` is correct under either emit (#2486 review). The same applies when it is an explicit non-false value — the broken-inheritance case the notice below covers. "Leave unchanged" remains available for the deliberate shared-config case, but is never the default here. The default must be the repair the "(Recommended)" label points to, so accepting it never keeps a value this branch could not justify. In TEXT_MODE, mark that option as the default in the numbered list.
170
+
171
+ Additionally, if `$USE_WORKTREES_CURRENT` is non-empty and not `false` (the config carries an explicit `true` — e.g. inherited from a worktree-capable install sharing the repo; empty means the key is absent, which needs no notice), prepend this notice before the question:
172
+
173
+ ```
174
+ Note: the project config currently sets workflow.use_worktrees: true.
175
+ {FINDING}, so {CONSEQUENCE}. Choose "No" to repair it for this runtime, or
176
+ "Leave unchanged" to keep it for a worktree-capable install that shares this
177
+ config.
178
+ ```
179
+
116
180
  ```
117
181
  // Model profile is selected via a two-question split because AskUserQuestion enforces a
118
182
  // hard 4-option cap and there are 5 valid profiles (quality, balanced, budget, adaptive,
@@ -411,7 +475,7 @@ Merge new settings into existing config.json:
411
475
  "research_before_questions": true/false,
412
476
  "discuss_mode": "discuss" | "assumptions",
413
477
  "skip_discuss": true/false,
414
- "use_worktrees": true/false
478
+ "use_worktrees": true/false // never written as true when the runtime's dispatch.isolation is none; omitted entirely when the user chose "Leave unchanged" (#2486)
415
479
  },
416
480
  "plan_review": {
417
481
  "source_grounding": true/false
@@ -55,14 +55,14 @@ Verify the work is ready to ship:
55
55
  # The gate decides on ONE read. --pick takes a single field, so the two
56
56
  # human-facing fields are read only on the blocking path below — never on the
57
57
  # passing path — rather than issuing three queries up front (#2589).
58
- STATUS=$(gsd_run query verification.status "${PHASE_DIR}" --pick status 2>/dev/null || echo "")
58
+ STATUS=$(gsd_run query verification.status "${PHASE_DIR}" --pick status 2>/dev/null)
59
59
  ```
60
60
  Only `passed` may ship. If `$STATUS` is `passed`, verification is complete — continue to the next preflight check; do not read any further verification field.
61
61
 
62
62
  Any other value (including `gaps_found`, `human_needed`, `missing`, and `unknown`) blocks with `PHASE_VERIFICATION_INCOMPLETE`. Only then, read the two message fields:
63
63
  ```bash
64
- NEXT_ACTION=$(gsd_run query verification.status "${PHASE_DIR}" --pick next_action 2>/dev/null || echo "")
65
- NEXT_COMMAND=$(gsd_run query verification.status "${PHASE_DIR}" --pick next_command 2>/dev/null || echo "")
64
+ NEXT_ACTION=$(gsd_run query verification.status "${PHASE_DIR}" --pick next_action 2>/dev/null)
65
+ NEXT_COMMAND=$(gsd_run query verification.status "${PHASE_DIR}" --pick next_command 2>/dev/null)
66
66
  ```
67
67
  Present `$NEXT_ACTION` to the user and, when `$NEXT_COMMAND` is non-empty, show it as the command to run next. These two are message text only — the block/allow decision has already been made from `$STATUS`, so a concurrent write between the reads cannot change the gate's verdict. The query already handles missing files and unexpected values, so no per-status arm is needed.
68
68
 
@@ -91,9 +91,9 @@ Verify the work is ready to ship:
91
91
  ```
92
92
  If `gh` not found or not authenticated: provide setup instructions and exit.
93
93
 
94
- 6. **Security ship gate (capability-driven).**
94
+ 6. **Capability ship gates (generic dispatch).**
95
95
 
96
- Resolve active `ship:pre` gate hooks from the capability registry — the registry evaluates each hook's `when` condition, so do **not** read `workflow.security_enforcement` directly:
96
+ Resolve active `ship:pre` gate hooks from the capability registry — the registry evaluates each hook's `when` condition, so do **not** read `workflow.security_enforcement` or `workflow.windows_enforce` directly:
97
97
 
98
98
  ```bash
99
99
  SHIP_PRE_HOOKS_JSON=$(gsd_run loop render-hooks ship:pre --raw)
@@ -102,57 +102,83 @@ Verify the work is ready to ship:
102
102
 
103
103
  Read the `activeHooks` array from `SHIP_PRE_HOOKS_JSON` in-context (do NOT pipe it through a shell parser).
104
104
 
105
- If an active entry exists with `kind == "gate"`, `capId == "security"`, and `blocking == true`, enforce its predicate (`SECURITY.md` frontmatter `threats_open == 0`) before shipping:
105
+ **If `activeHooks` is empty or absent:** skip this check silently and continue to the next preflight step. A capability whose `when` is off contributes no entry, and one that failed to load fails OPEN with its own warning from the resolver — neither is a block.
106
106
 
107
- - **`SECURITY_FILE` is empty** → block with `SECURITY_SHIP_GATE_NO_REVIEW`:
108
- ```
109
- ⚠ Security enforcement is enabled but no SECURITY.md exists for this phase.
110
- Run /gsd:secure-phase {phase} and resolve findings before shipping.
111
- ```
112
- - **`SECURITY_FILE` exists** → read its frontmatter `threats_open`. The gate passes **only** when `threats_open` is exactly `0`. For any other value — `threats_open` > 0, or a missing / non-numeric / unparsable field — **fail closed and block** with `SECURITY_SHIP_GATE_OPEN_THREATS` (the predicate is strict equality to `0`; never ship on an ambiguous value):
113
- ```
114
- ⚠ Security ship gate: SECURITY.md does not assert threats_open == 0 (found: {threats_open|unset}).
115
- Resolve open threats (or re-run /gsd:secure-phase {phase}) before shipping.
116
- ```
107
+ **For each active entry where `kind == "gate"`** (process in array order), following `gsd-core/references/loop-hook-dispatch.md`. Entries of any other `kind` are not gates and are not enforced here. Every gate is visited exactly once by this loop — the named branches below are specializations *within* it, never a separate pass, so no gate is evaluated twice.
117
108
 
118
- If no active security `ship:pre` gate hook is present (security enforcement off), skip this check silently.
109
+ **Step 1 — evaluate the gate's `check`.** Dispatch by check shape; read the hook's `check` object in-context to pick the branch (the registry validates exactly one of `query`/`predicate`/`agentVerdict`). Two capability IDs carry a bespoke evaluation whose fail-closed semantics the declared predicate alone does not reproduce — take their branch, then rejoin at step 2:
119
110
 
120
- 7. **Broken-windows ship gate (capability-driven, issue #1950).**
111
+ - **`capId == "security"`** — enforce against `SECURITY_FILE`:
112
+ - **`SECURITY_FILE` is empty** → `block: true`, `SECURITY_SHIP_GATE_NO_REVIEW`:
113
+ ```
114
+ ⚠ Security enforcement is enabled but no SECURITY.md exists for this phase.
115
+ Run /gsd:secure-phase {phase} and resolve findings before shipping.
116
+ ```
117
+ - **`SECURITY_FILE` exists** → read its frontmatter `threats_open`. The gate passes **only** when `threats_open` is exactly `0`. For any other value — `threats_open` > 0, or a missing / non-numeric / unparsable field — **fail closed** with `block: true` and `SECURITY_SHIP_GATE_OPEN_THREATS` (the predicate is strict equality to `0`; never ship on an ambiguous value):
118
+ ```
119
+ ⚠ Security ship gate: SECURITY.md does not assert threats_open == 0 (found: {threats_open|unset}).
120
+ Resolve open threats (or re-run /gsd:secure-phase {phase}) before shipping.
121
+ ```
121
122
 
122
- The `SHIP_PRE_HOOKS_JSON` resolved in step 6 already includes any `broken-windows` gate. Inspect `activeHooks` for an entry with `capId == "broken-windows"` and `kind == "gate"`:
123
+ - **`capId == "broken-windows"`** (issue #1950) — enforce against the ledger's typed status. The ledger lives at the **project root** (cross-phase, not phase-scoped):
123
124
 
124
- ```bash
125
- WINDOWS_GATE_ACTIVE=$(printf '%s' "$SHIP_PRE_HOOKS_JSON" | jq -r \
126
- '.activeHooks[]? | select(.capId == "broken-windows" and .kind == "gate" and .blocking == true) | .capId' \
127
- 2>/dev/null | head -1)
128
- ```
125
+ ```bash
126
+ WINDOWS_STATUS_JSON=$(gsd_run windows status --raw 2>/dev/null || echo '')
127
+ WINDOWS_OPEN_COUNT=$(printf '%s' "$WINDOWS_STATUS_JSON" | jq -r '.ledger.open_count // "?"' 2>/dev/null || echo '?')
128
+ ```
129
129
 
130
- If `$WINDOWS_GATE_ACTIVE` is non-empty, enforce the gate by reading the ledger's typed status. The ledger lives at the **project root** (cross-phase, not phase-scoped):
130
+ - **`WINDOWS_OPEN_COUNT == "0"`** → `block: false`; the gate passes.
131
+ - **`WINDOWS_OPEN_COUNT` is a positive integer** → `block: true`, `WINDOWS_SHIP_GATE_OPEN`:
132
+ ```
133
+ ⚠ Broken-windows ship gate: WINDOWS.md has {WINDOWS_OPEN_COUNT} open window(s).
134
+ Resolve each entry before shipping, or explicitly waive with a recorded reason:
135
+ gsd_run windows fixed <id> # defect resolved
136
+ gsd_run windows waive <id> "<reason>" # justified deferral (reason required)
137
+ Then re-run /gsd:ship.
138
+ ```
139
+ - **`WINDOWS_OPEN_COUNT` is `"?"`, empty, or non-numeric** → **fail closed** with `block: true` and `WINDOWS_SHIP_GATE_READ_FAILED` (the gate is strict equality to `0`; never ship on an unreadable ledger):
140
+ ```
141
+ ⚠ Broken-windows ship gate: could not read open_count from .planning/WINDOWS.md.
142
+ Inspect the file or run `gsd_run windows status --raw` to diagnose. The ledger
143
+ may be malformed; fix it before shipping (an unparseable ledger is a broken window).
144
+ ```
145
+
146
+ The ledger is **optional and backward-compatible**: on a project where `gsd_run windows status` returns `open_count: 0` (no `.planning/WINDOWS.md` yet, or an empty ledger), the gate passes silently. It only blocks when at least one entry is `open`.
147
+
148
+ - **Every other `capId`** — run the gate's own declared check through the generic evaluator. This arm is what makes a third-party capability's declared gate enforceable at all (#3559); before it existed, a gate whose `capId` was not named above was resolved and then silently dropped.
149
+
150
+ ⚠ **Validate `check` before shell use** (third-party manifest input) — `loop-hook-dispatch.md` § `gate`.
151
+
152
+ For a named-query gate (only a value that has passed validation is run):
153
+ ```bash
154
+ GATE_RESULT=$(gsd_run check ${hook.check.query} "${PHASE_DIR}" --raw)
155
+ CHECK_EXIT=$?
156
+ ```
131
157
 
132
- ```bash
133
- WINDOWS_STATUS_JSON=$(gsd_run windows status --raw 2>/dev/null || echo '')
134
- WINDOWS_OPEN_COUNT=$(printf '%s' "$WINDOWS_STATUS_JSON" | jq -r '.ledger.open_count // "?"' 2>/dev/null || echo '?')
135
- ```
158
+ (The named-query argument convention — a single `"${PHASE_DIR}"` positional — mirrors `verify-work.md`'s `verify:pre` arm verbatim. No capability declares a `check.query` gate at `ship:pre` today; the arm exists so the documented check contract is complete rather than half-implemented.)
136
159
 
137
- - **`WINDOWS_OPEN_COUNT == "0"`** → gate passes; continue to the next preflight check.
138
- - **`WINDOWS_OPEN_COUNT` is a positive integer** → block with `WINDOWS_SHIP_GATE_OPEN`:
160
+ For a `predicate` gate (ADR-2008 / #2008), serialize `hook.check.predicate` to compact JSON and pass it as a **single argv element**:
161
+ ```bash
162
+ GATE_RESULT=$(gsd_run check predicate --predicate '<hook.check.predicate as JSON>' --phase-dir "${PHASE_DIR}" --phase-number "${PHASE_NUMBER}" --raw)
163
+ CHECK_EXIT=$?
139
164
  ```
140
- ⚠ Broken-windows ship gate: WINDOWS.md has {WINDOWS_OPEN_COUNT} open window(s).
141
- Resolve each entry before shipping, or explicitly waive with a recorded reason:
142
- gsd_run windows fixed <id> # defect resolved
143
- gsd_run windows waive <id> "<reason>" # justified deferral (reason required)
144
- Then re-run /gsd:ship.
145
- ```
146
- - **`WINDOWS_OPEN_COUNT` is `"?"`, empty, or non-numeric** → **fail closed and block** with `WINDOWS_SHIP_GATE_READ_FAILED` (the gate is strict equality to `0`; never ship on an unreadable ledger):
165
+ A gate carrying neither — including an `agentVerdict` check, which has no runner at `ship:pre` — cannot be evaluated here. Record a warning naming the `capId` and treat it as a check-command failure routed per step 1a, **never** as a silent pass.
166
+
167
+ **Step 1a — did the CHECK COMMAND itself fail?** (non-zero `CHECK_EXIT`, empty output, or unparseable JSON). The two named branches above cannot reach this state — their failure modes are already folded into a fail-closed `block: true`.
168
+ - **`onError == "halt"`** → stop the ship. Do NOT push, do NOT create a PR. Surface: `⚠ Gate check command failed ({hook.capId}): command error. Resolve before shipping.`
169
+ - **`onError == "skip"`** → record a warning naming the `capId`, then continue to the next gate. Do NOT read `GATE_RESULT.block`.
170
+
171
+ **Step 2 — read the gate's `block` decision.** Only reached when the check produced a verdict.
172
+
173
+ - **`blocking == true` and `block == true`** → HALT the ship — do NOT push, do NOT create a PR — surfacing that gate's own message:
147
174
  ```
148
- ⚠ Broken-windows ship gate: could not read open_count from .planning/WINDOWS.md.
149
- Inspect the file or run `gsd_run windows status --raw` to diagnose. The ledger
150
- may be malformed; fix it before shipping (an unparseable ledger is a broken window).
175
+ ⚠ Ship blocked by capability gate ({hook.capId}): {message}
151
176
  ```
177
+ This halt is **not** bypassed by `onError` — `onError` covers check-command failure (step 1a), never the gate's block decision.
178
+ - **`blocking == false`** (advisory) → never halts. If `block == true` or the result carries a non-empty message, print `⚠ {hook.capId} advisory: {message}`, then continue.
179
+ - **`blocking == true` and `block == false`** → continue silently.
152
180
 
153
- The ledger is **optional and backward-compatible**: on a project where `gsd_run windows status` returns `open_count: 0` (no `.planning/WINDOWS.md` yet, or an empty ledger), the gate passes silently. The gate only blocks when at least one entry is `open`.
154
-
155
- If no active `broken-windows` `ship:pre` gate hook is present (gate disabled via `workflow.windows_enforce=false`, the default — tracking continues but the gate is opt-in), skip this check silently.
181
+ **When every active gate has been processed without a halt:** continue to the next preflight check.
156
182
  </step>
157
183
 
158
184
  <step name="push_branch">
@@ -455,7 +481,41 @@ would otherwise trigger (GitHub honors `[ci skip]` / `[skip ci]`):
455
481
 
456
482
  ```bash
457
483
  gsd_run query commit "docs(${padded_phase}): ship phase ${PHASE_NUMBER} — PR #${PR_NUMBER} [ci skip]" --files .planning/STATE.md
484
+ SHIP_NOTE_SHA=$(git rev-parse HEAD)
458
485
  git push origin ${CURRENT_BRANCH} 2>&1 || echo "⚠ track_shipping: ship-note push failed — it is local-only; rerun: git push origin ${CURRENT_BRANCH}"
486
+
487
+ # Preserve the skip-token optimization for repositories without a required-check
488
+ # wedge; only synthesize a second CI-triggering commit when GitHub reports one (#2783).
489
+ # Poll mergeStateStatus with backoff to avoid racing GitHub's async state computation.
490
+ # Note: Skip tokens recognized by GitHub Actions are [skip ci], [ci skip], [no ci], [skip actions], [actions skip], and skip-checks:true.
491
+ # The recovery commit message MUST NOT contain any of these tokens.
492
+
493
+ STATUS="UNKNOWN"
494
+ CHECKS=0
495
+ REVIEW_DECISION=""
496
+ for i in {1..5}; do
497
+ PR_STATE=$(gh pr view ${PR_NUMBER} --json headRefOid,mergeStateStatus,statusCheckRollup,reviewDecision -q '{head: .headRefOid, status: .mergeStateStatus, checks: ((.statusCheckRollup // []) | length), review: (.reviewDecision // "")}' 2>/dev/null || echo '{"head":"","status":"UNKNOWN","checks":0,"review":""}')
498
+ HEAD_OID=$(echo "$PR_STATE" | jq -r .head)
499
+ if [ "$HEAD_OID" = "$SHIP_NOTE_SHA" ]; then
500
+ STATUS=$(echo "$PR_STATE" | jq -r .status)
501
+ CHECKS=$(echo "$PR_STATE" | jq -r .checks)
502
+ REVIEW_DECISION=$(echo "$PR_STATE" | jq -r .review)
503
+ fi
504
+ if [ "$HEAD_OID" = "$SHIP_NOTE_SHA" ] && [ "$STATUS" != "UNKNOWN" ]; then
505
+ break
506
+ fi
507
+ sleep 3
508
+ done
509
+
510
+ if [ "$STATUS" = "BLOCKED" ] && [ "$CHECKS" = "0" ] && [ "$REVIEW_DECISION" != "REVIEW_REQUIRED" ] && [ "$REVIEW_DECISION" != "CHANGES_REQUESTED" ] && git log -1 --format=%B "$SHIP_NOTE_SHA" | grep -q '\[ci skip\]'; then
511
+ echo "⚠ PR is BLOCKED with zero checks. The [ci skip] trailer wedged the PR due to required checks."
512
+ echo "Pushing an empty commit to trigger the required pipelines..."
513
+ # gsd_run query commit requires a file list; use git directly for this intentionally empty commit.
514
+ git commit --allow-empty -m "chore: trigger CI (recover from ship-note skip-token)"
515
+ git push origin ${CURRENT_BRANCH} 2>&1 || echo "⚠ track_shipping: recovery push failed — rerun: git push origin ${CURRENT_BRANCH}"
516
+ elif [ "$STATUS" = "UNKNOWN" ]; then
517
+ echo "⚠ track_shipping: PR mergeStateStatus is UNKNOWN after polling; PR may require manual check re-trigger."
518
+ fi
459
519
  ```
460
520
  </step>
461
521
 
@@ -251,9 +251,11 @@ if ! node -e 'const a=require(process.argv[1]);if(!Array.isArray(a)||a.length===
251
251
  exit 1
252
252
  fi
253
253
  # Invoke the compiled engine and CAPTURE its report — it computes which categories apply per
254
- # requirement. The resolved/dismissed/unresolved rows in $COVERAGE (resolved items carry
255
- # verification: explicit|backstop) drive the
256
- # resolution loop below (canonical taxonomy compute, NOT LLM re-derivation from prose).
254
+ # requirement. The report is RENDERED into context below (#3102); its resolved/dismissed/
255
+ # unresolved rows (resolved items carry verification: explicit|backstop) are the deterministic
256
+ # FLOOR the resolution loop consumes and unions with its own classification — the loop no longer
257
+ # re-derives the taxonomy from prose unaided. Floor, never ceiling: the classifier has a measured
258
+ # recall gap (ADR-857 §98 / ADR-550 D7b), so the model still ADDS any category the engine missed.
257
259
  # The engine FAILS CLOSED (exit 2) on an invalid authored shape or bad input — so the capture
258
260
  # MUST be exit-checked. A bare `COVERAGE=$(node …)` swallows that exit code, leaves $COVERAGE
259
261
  # empty, and lets the workflow fall through to prose re-derivation: fail-OPEN at the boundary
@@ -270,6 +272,16 @@ if ! printf '%s' "$COVERAGE" | node -e 'let s="";process.stdin.on("data",d=>s+=d
270
272
  echo "ERROR: edge-probe produced an unparseable or malformed coverage report — refusing to proceed with the resolution loop." >&2
271
273
  exit 1
272
274
  fi
275
+ # Render the validated report into the model's visible context (#3102). Until here $COVERAGE was
276
+ # captured, shape-checked, and reduced to coverage.applicable — the engine's per-requirement
277
+ # items[] never reached the model, so the resolution loop below re-derived edge categories from
278
+ # requirement PROSE (the data-flow twin of #2733's control-flow discard). These rows are the
279
+ # deterministic FLOOR the resolution loop consumes. Printed RAW (not a bespoke table) so this
280
+ # step holds NO knowledge of the item schema: an ADR-550 D7a-style re-cut of the item/coverage
281
+ # shape cannot silently desync a hand-rolled renderer here — the engine stays the single source.
282
+ echo "### Edge-probe coverage report (deterministic proposals — the FLOOR for the resolution loop below):"
283
+ printf '%s\n' "$COVERAGE"
284
+ echo "### (end edge-probe coverage report)"
273
285
  # Zero-applicable guard: a report where the engine proposed NO applicable edge across ANY
274
286
  # requirement is far more likely a shape-classification miss (or malformed requirements) than
275
287
  # a genuinely edge-free spec — the same fail-open shape as an invalid shape yielding
@@ -287,9 +299,14 @@ edge-free spec, or should we revisit the requirement wording / authored shapes?"
287
299
  an empty `## Edge Coverage` section after explicit confirmation.
288
300
 
289
301
  For each Requirement gathered so far:
290
- 1. Classify its shape and raise only applicable edge categories (relevance filter — see
291
- the taxonomy in the reference). Reuse any edges the Round-4 Failure Analyst already
292
- surfaced as pre-resolved.
302
+ 1. Start from the edge-probe rows RENDERED above — the deterministic `items[]` are the FLOOR:
303
+ every proposed `(requirement_id, category)` MUST be resolved below (Specify / Dismiss-with-
304
+ reason / Backstop / Defer), none silently dropped. Then raise any applicable category the
305
+ engine MISSED — the rows are a floor, never a ceiling: the classifier has a measured recall
306
+ gap on terse prose (ADR-857 §98 / ADR-550 D7b), e.g. a CSV-export requirement whose
307
+ `encoding` edge the shape cue under-fires. Union the engine's rows with your own
308
+ classification (relevance filter — see the taxonomy in the reference); do not narrow to them.
309
+ Reuse any edges the Round-4 Failure Analyst already surfaced as pre-resolved.
293
310
  2. For each raised category, propose a CONCRETE candidate edge (not "consider
294
311
  boundaries" — e.g. "R2 merges intervals; what about `[[1,2],[2,3]]` that only touch?").
295
312
  3. Resolve each with the user (AskUserQuestion; text mode → numbered list):
@@ -313,9 +330,11 @@ For each Requirement gathered so far:
313
330
  - On "anyway": write SPEC.md with those rows marked `⚠ Edge unresolved — planner must
314
331
  treat as assumption`.
315
332
 
316
- **`--auto` mode:** auto-`resolved` (verification: explicit) where a defensible acceptance
317
- criterion can be written; otherwise auto-`resolved` (verification: backstop) (never
318
- auto-dismiss — a wrong dismissal is the exact silent failure being eliminated). Log:
333
+ **`--auto` mode:** resolve over the **same rendered floor** (#3102) — every engine-proposed row
334
+ from Step 5.5's report (step 1) plus any category the classifier missed, never a narrower set.
335
+ For each: auto-`resolved` (verification: explicit) where a defensible acceptance criterion can be
336
+ written; otherwise auto-`resolved` (verification: backstop) (never auto-dismiss — a wrong
337
+ dismissal is the exact silent failure being eliminated). Log:
319
338
  `[auto] edge coverage: E explicit, B backstop, U unresolved`.
320
339
 
321
340
  **`unclassified` exception (#1110):** `--auto` leaves an `unclassified` candidate
@@ -458,11 +477,10 @@ Write to: `{phase_dir}/{padded_phase}-SPEC.md`
458
477
  ## Step 7: Commit
459
478
 
460
479
  ```bash
461
- git add "${phase_dir}/${padded_phase}-SPEC.md"
462
- git commit -m "spec(phase-${phase_number}): add SPEC.md for ${phase_name} — ${requirement_count} requirements (#2213)" -- "${phase_dir}/${padded_phase}-SPEC.md"
480
+ gsd_run query commit "spec(phase-${phase_number}): add SPEC.md for ${phase_name} — ${requirement_count} requirements (#2213)" --files "${phase_dir}/${padded_phase}-SPEC.md"
463
481
  ```
464
482
 
465
- If `commit_docs` is false: Skip commit. Note that SPEC.md was written but not committed.
483
+ If `commit_docs` is false the CLI returns `skipped`; SPEC.md is written, not committed.
466
484
 
467
485
  ## Step 8: Wrap Up
468
486