@opengsd/gsd-core 1.12.0 → 1.13.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 (286) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.opencode/plugins/gsd-core.js +12 -0
  4. package/agents/gsd-executor.md +63 -35
  5. package/agents/gsd-plan-checker.md +76 -57
  6. package/agents/gsd-planner.md +14 -0
  7. package/agents/gsd-ui-checker.md +19 -3
  8. package/agents/gsd-ui-researcher.md +29 -0
  9. package/agents/gsd-verifier.md +23 -1
  10. package/bin/install.js +239 -67
  11. package/commands/gsd/execute-phase.md +1 -1
  12. package/commands/gsd/ns-workflow.md +2 -1
  13. package/commands/gsd/phase.md +1 -1
  14. package/commands/gsd/quick-batch.md +105 -0
  15. package/commands/gsd/surface.md +18 -8
  16. package/gsd-core/bin/gsd-tools.cjs +195 -50
  17. package/gsd-core/bin/lib/capability-activation.cjs +27 -0
  18. package/gsd-core/bin/lib/capability-registry.cjs +514 -114
  19. package/gsd-core/bin/lib/capability-state.cjs +7 -1
  20. package/gsd-core/bin/lib/capability-validator.cjs +120 -4
  21. package/gsd-core/bin/lib/capability-writer.cjs +14 -4
  22. package/gsd-core/bin/lib/check-command-router.cjs +85 -2
  23. package/gsd-core/bin/lib/claude-orchestration.cjs +10 -25
  24. package/gsd-core/bin/lib/clusters.cjs +1 -0
  25. package/gsd-core/bin/lib/command-aliases.cjs +16 -0
  26. package/gsd-core/bin/lib/commands.cjs +337 -13
  27. package/gsd-core/bin/lib/config-loader.cjs +3 -0
  28. package/gsd-core/bin/lib/core-utils.cjs +34 -7
  29. package/gsd-core/bin/lib/decisions.cjs +213 -1
  30. package/gsd-core/bin/lib/edge-probe.cjs +14 -1
  31. package/gsd-core/bin/lib/file-overlap-partitioner.cjs +74 -0
  32. package/gsd-core/bin/lib/frontmatter.cjs +137 -23
  33. package/gsd-core/bin/lib/gap-checker.cjs +22 -13
  34. package/gsd-core/bin/lib/git-base-branch.cjs +10 -2
  35. package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +8 -2
  36. package/gsd-core/bin/lib/health-diagnostic-rules/roadmap-disk-consistency.cjs +54 -11
  37. package/gsd-core/bin/lib/health-diagnostic-rules/state-consistency.cjs +75 -22
  38. package/gsd-core/bin/lib/host-integration.cjs +57 -5
  39. package/gsd-core/bin/lib/init-command-router.cjs +14 -0
  40. package/gsd-core/bin/lib/init.cjs +132 -15
  41. package/gsd-core/bin/lib/install-engine.cjs +184 -12
  42. package/gsd-core/bin/lib/install-model-override-resolver.cjs +45 -0
  43. package/gsd-core/bin/lib/install-profiles.cjs +22 -14
  44. package/gsd-core/bin/lib/installer-migration-report.cjs +1 -0
  45. package/gsd-core/bin/lib/io.cjs +35 -0
  46. package/gsd-core/bin/lib/loop-resolver.cjs +14 -8
  47. package/gsd-core/bin/lib/markdown-table.cjs +123 -0
  48. package/gsd-core/bin/lib/milestone.cjs +22 -2
  49. package/gsd-core/bin/lib/phase-command-router.cjs +13 -6
  50. package/gsd-core/bin/lib/phase-id.cjs +251 -9
  51. package/gsd-core/bin/lib/phase.cjs +774 -35
  52. package/gsd-core/bin/lib/plan-document.cjs +10 -0
  53. package/gsd-core/bin/lib/planning-snapshot.cjs +147 -20
  54. package/gsd-core/bin/lib/planning-workspace.cjs +103 -28
  55. package/gsd-core/bin/lib/quick-batch-command-router.cjs +285 -0
  56. package/gsd-core/bin/lib/quick-batch-dispatch.cjs +250 -0
  57. package/gsd-core/bin/lib/quick-batch.cjs +840 -0
  58. package/gsd-core/bin/lib/review-lane-descriptor.cjs +53 -5
  59. package/gsd-core/bin/lib/review-lane-invocation.cjs +73 -1
  60. package/gsd-core/bin/lib/review-lane-runner.cjs +136 -10
  61. package/gsd-core/bin/lib/roadmap-parser.cjs +499 -26
  62. package/gsd-core/bin/lib/roadmap.cjs +187 -58
  63. package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +233 -33
  64. package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +16 -17
  65. package/gsd-core/bin/lib/runtime-artifact-layout.cjs +286 -108
  66. package/gsd-core/bin/lib/runtime-hooks-surface.cjs +215 -43
  67. package/gsd-core/bin/lib/shell-command-projection.cjs +4 -0
  68. package/gsd-core/bin/lib/smart-entry.cjs +7 -9
  69. package/gsd-core/bin/lib/state-document.cjs +30 -5
  70. package/gsd-core/bin/lib/state-md-schema.cjs +23 -13
  71. package/gsd-core/bin/lib/state-transition.cjs +333 -44
  72. package/gsd-core/bin/lib/state.cjs +684 -125
  73. package/gsd-core/bin/lib/surface.cjs +23 -8
  74. package/gsd-core/bin/lib/tdd-red-evidence.cjs +133 -0
  75. package/gsd-core/bin/lib/uat.cjs +1419 -515
  76. package/gsd-core/bin/lib/update-context.cjs +6 -2
  77. package/gsd-core/bin/lib/validate.cjs +230 -12
  78. package/gsd-core/bin/lib/verification-command-router.cjs +2 -1
  79. package/gsd-core/bin/lib/verification.cjs +273 -12
  80. package/gsd-core/bin/lib/verify-command-router.cjs +1 -0
  81. package/gsd-core/bin/lib/verify.cjs +346 -16
  82. package/gsd-core/bin/lib/workstream-inventory.cjs +20 -2
  83. package/gsd-core/bin/lib/worktree-safety.cjs +8 -0
  84. package/gsd-core/bin/shared/config-schema.manifest.json +8 -0
  85. package/gsd-core/bin/verify-reapply-patches.cjs +70 -3
  86. package/gsd-core/references/agent-contracts.md +3 -3
  87. package/gsd-core/references/edge-probe.md +17 -13
  88. package/gsd-core/references/execute-mvp-tdd.md +18 -16
  89. package/gsd-core/references/execute-phase-response-language.md +6 -0
  90. package/gsd-core/references/executor-examples.md +42 -0
  91. package/gsd-core/references/few-shot-examples/plan-checker.md +15 -15
  92. package/gsd-core/references/mvp-concepts.md +2 -2
  93. package/gsd-core/references/plan-checker-examples.md +41 -0
  94. package/gsd-core/references/planner-antipatterns.md +25 -0
  95. package/gsd-core/references/planner-chunked.md +5 -1
  96. package/gsd-core/references/planner-coupling.md +42 -0
  97. package/gsd-core/references/planner-quick-batch.md +71 -0
  98. package/gsd-core/references/planner-reviews.md +47 -0
  99. package/gsd-core/references/planner-revision.md +75 -2
  100. package/gsd-core/references/planning-config.md +2 -1
  101. package/gsd-core/references/response-language-directive.md +9 -0
  102. package/gsd-core/references/revision-loop.md +118 -11
  103. package/gsd-core/references/tdd.md +14 -9
  104. package/gsd-core/references/verifier-evidence-gate.md +160 -0
  105. package/gsd-core/templates/phase-prompt.md +4 -0
  106. package/gsd-core/templates/verification-report.md +5 -0
  107. package/gsd-core/workflows/add-backlog.md +2 -0
  108. package/gsd-core/workflows/add-phase.md +2 -0
  109. package/gsd-core/workflows/add-tests.md +1 -1
  110. package/gsd-core/workflows/add-todo.md +1 -1
  111. package/gsd-core/workflows/ai-integration-phase.md +1 -1
  112. package/gsd-core/workflows/analyze-dependencies.md +2 -0
  113. package/gsd-core/workflows/audit-fix.md +2 -0
  114. package/gsd-core/workflows/audit-milestone.md +2 -0
  115. package/gsd-core/workflows/audit-uat.md +2 -0
  116. package/gsd-core/workflows/autonomous.md +2 -0
  117. package/gsd-core/workflows/check-todos.md +1 -1
  118. package/gsd-core/workflows/cleanup.md +1 -1
  119. package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +15 -13
  120. package/gsd-core/workflows/code-review-fix.md +2 -0
  121. package/gsd-core/workflows/code-review.md +73 -31
  122. package/gsd-core/workflows/complete-milestone.md +13 -4
  123. package/gsd-core/workflows/debug.md +1 -1
  124. package/gsd-core/workflows/diagnose-issues.md +5 -1
  125. package/gsd-core/workflows/discuss-phase/modes/advisor.md +2 -0
  126. package/gsd-core/workflows/discuss-phase/modes/all.md +2 -0
  127. package/gsd-core/workflows/discuss-phase/modes/analyze.md +2 -0
  128. package/gsd-core/workflows/discuss-phase/modes/auto.md +2 -0
  129. package/gsd-core/workflows/discuss-phase/modes/batch.md +2 -0
  130. package/gsd-core/workflows/discuss-phase/modes/chain.md +2 -0
  131. package/gsd-core/workflows/discuss-phase/modes/default.md +2 -0
  132. package/gsd-core/workflows/discuss-phase/modes/power.md +2 -0
  133. package/gsd-core/workflows/discuss-phase/modes/text.md +2 -0
  134. package/gsd-core/workflows/discuss-phase/templates/context.md +2 -0
  135. package/gsd-core/workflows/discuss-phase/templates/discussion-log.md +2 -0
  136. package/gsd-core/workflows/discuss-phase-assumptions.md +1 -1
  137. package/gsd-core/workflows/discuss-phase-power.md +2 -0
  138. package/gsd-core/workflows/discuss-phase.md +1 -1
  139. package/gsd-core/workflows/do.md +43 -13
  140. package/gsd-core/workflows/docs-update.md +1 -1
  141. package/gsd-core/workflows/edit-phase.md +2 -0
  142. package/gsd-core/workflows/eval-review.md +1 -1
  143. package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +2 -0
  144. package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +17 -1
  145. package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +8 -2
  146. package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +2 -0
  147. package/gsd-core/workflows/execute-phase/steps/tdd-applicability-resolution.md +25 -0
  148. package/gsd-core/workflows/execute-phase/steps/worktree-recovery-policy.md +2 -0
  149. package/gsd-core/workflows/execute-phase.md +32 -14
  150. package/gsd-core/workflows/execute-plan.md +8 -8
  151. package/gsd-core/workflows/explore.md +2 -0
  152. package/gsd-core/workflows/extract-learnings.md +2 -0
  153. package/gsd-core/workflows/fast.md +6 -0
  154. package/gsd-core/workflows/forensics.md +2 -0
  155. package/gsd-core/workflows/graduation.md +1 -1
  156. package/gsd-core/workflows/health.md +1 -1
  157. package/gsd-core/workflows/help/modes/brief.md +2 -0
  158. package/gsd-core/workflows/help/modes/default.md +2 -0
  159. package/gsd-core/workflows/help/modes/full.md +12 -0
  160. package/gsd-core/workflows/help/modes/topic.md +2 -0
  161. package/gsd-core/workflows/help.md +2 -0
  162. package/gsd-core/workflows/import.md +3 -3
  163. package/gsd-core/workflows/inbox.md +1 -1
  164. package/gsd-core/workflows/ingest-docs.md +1 -1
  165. package/gsd-core/workflows/insert-phase.md +2 -0
  166. package/gsd-core/workflows/list-phase-assumptions.md +2 -0
  167. package/gsd-core/workflows/list-seeds.md +2 -0
  168. package/gsd-core/workflows/list-workspaces.md +2 -0
  169. package/gsd-core/workflows/manager.md +3 -3
  170. package/gsd-core/workflows/map-codebase.md +2 -0
  171. package/gsd-core/workflows/milestone-summary.md +2 -0
  172. package/gsd-core/workflows/mvp-phase.md +1 -1
  173. package/gsd-core/workflows/new-milestone.md +1 -1
  174. package/gsd-core/workflows/new-project.md +5 -3
  175. package/gsd-core/workflows/new-workspace.md +1 -1
  176. package/gsd-core/workflows/next.md +2 -0
  177. package/gsd-core/workflows/node-repair.md +2 -0
  178. package/gsd-core/workflows/note.md +2 -0
  179. package/gsd-core/workflows/onboard.md +1 -1
  180. package/gsd-core/workflows/pause-work.md +19 -4
  181. package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +100 -18
  182. package/gsd-core/workflows/plan-phase/steps/prd-express-path.md +2 -0
  183. package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +9 -0
  184. package/gsd-core/workflows/plan-phase.md +130 -12
  185. package/gsd-core/workflows/plan-review-convergence.md +102 -10
  186. package/gsd-core/workflows/plant-seed.md +1 -1
  187. package/gsd-core/workflows/pr-branch.md +11 -3
  188. package/gsd-core/workflows/profile-user.md +1 -1
  189. package/gsd-core/workflows/progress/steps/forensic-audit.md +1 -1
  190. package/gsd-core/workflows/progress.md +25 -3
  191. package/gsd-core/workflows/quick/steps/plan-checker-loop.md +37 -2
  192. package/gsd-core/workflows/quick/steps/research-phase.md +3 -3
  193. package/gsd-core/workflows/quick-batch/steps/batch-init.md +55 -0
  194. package/gsd-core/workflows/quick-batch/steps/completion.md +65 -0
  195. package/gsd-core/workflows/quick-batch/steps/merge-wave.md +100 -0
  196. package/gsd-core/workflows/quick-batch/steps/plan-checker-loop.md +147 -0
  197. package/gsd-core/workflows/quick-batch/steps/planner-wave.md +158 -0
  198. package/gsd-core/workflows/quick-batch/steps/research-phase.md +95 -0
  199. package/gsd-core/workflows/quick-batch/steps/resume-mode.md +49 -0
  200. package/gsd-core/workflows/quick-batch/steps/verification-wave.md +73 -0
  201. package/gsd-core/workflows/quick-batch/steps/worktree-dispatch.md +169 -0
  202. package/gsd-core/workflows/quick-batch.md +203 -0
  203. package/gsd-core/workflows/quick.md +13 -3
  204. package/gsd-core/workflows/reapply-patches.md +2 -0
  205. package/gsd-core/workflows/remove-phase.md +2 -0
  206. package/gsd-core/workflows/remove-workspace.md +1 -1
  207. package/gsd-core/workflows/resume-project.md +6 -2
  208. package/gsd-core/workflows/review.md +215 -10
  209. package/gsd-core/workflows/scan.md +2 -0
  210. package/gsd-core/workflows/section-manifest.json +12 -0
  211. package/gsd-core/workflows/secure-phase.md +1 -1
  212. package/gsd-core/workflows/session-report.md +2 -0
  213. package/gsd-core/workflows/settings-advanced.md +2 -0
  214. package/gsd-core/workflows/settings-integrations.md +9 -8
  215. package/gsd-core/workflows/settings.md +1 -1
  216. package/gsd-core/workflows/ship.md +10 -10
  217. package/gsd-core/workflows/sketch-wrap-up.md +2 -0
  218. package/gsd-core/workflows/sketch.md +1 -1
  219. package/gsd-core/workflows/smart-entry.md +1 -1
  220. package/gsd-core/workflows/spec-phase.md +24 -19
  221. package/gsd-core/workflows/spike-wrap-up.md +2 -0
  222. package/gsd-core/workflows/spike.md +1 -1
  223. package/gsd-core/workflows/stats.md +2 -0
  224. package/gsd-core/workflows/sync-skills.md +12 -4
  225. package/gsd-core/workflows/thread.md +2 -0
  226. package/gsd-core/workflows/transition.md +2 -0
  227. package/gsd-core/workflows/ui-phase.md +26 -5
  228. package/gsd-core/workflows/ui-review.md +1 -1
  229. package/gsd-core/workflows/ultraplan-phase.md +2 -0
  230. package/gsd-core/workflows/undo.md +1 -1
  231. package/gsd-core/workflows/update.md +41 -38
  232. package/gsd-core/workflows/validate-phase.md +1 -1
  233. package/gsd-core/workflows/verify-work.md +49 -3
  234. package/hooks/dist/gsd-check-update-worker.js +19 -2
  235. package/hooks/dist/gsd-context-monitor.js +283 -12
  236. package/hooks/dist/gsd-node-runner.sh +1 -0
  237. package/hooks/dist/gsd-prompt-guard.js +30 -5
  238. package/hooks/dist/gsd-read-guard.js +2 -0
  239. package/hooks/dist/gsd-read-injection-scanner.js +5 -5
  240. package/hooks/dist/gsd-secret-read-guard.js +1079 -0
  241. package/hooks/dist/gsd-statusline.js +7 -3
  242. package/hooks/dist/gsd-validate-commit.sh +444 -7
  243. package/hooks/dist/gsd-workflow-guard.js +2 -1
  244. package/hooks/dist/lib/git-cmd.js +210 -1
  245. package/hooks/dist/lib/injection-patterns.js +36 -6
  246. package/hooks/dist/managed-hooks-registry.cjs +1 -0
  247. package/hooks/gsd-check-update-worker.js +19 -2
  248. package/hooks/gsd-context-monitor.js +283 -12
  249. package/hooks/gsd-node-runner.sh +1 -0
  250. package/hooks/gsd-prompt-guard.js +30 -5
  251. package/hooks/gsd-read-guard.js +2 -0
  252. package/hooks/gsd-read-injection-scanner.js +5 -5
  253. package/hooks/gsd-secret-read-guard.js +1079 -0
  254. package/hooks/gsd-statusline.js +7 -3
  255. package/hooks/gsd-validate-commit.sh +444 -7
  256. package/hooks/gsd-workflow-guard.js +2 -1
  257. package/hooks/hooks.json +6 -0
  258. package/hooks/lib/git-cmd.js +210 -1
  259. package/hooks/lib/injection-patterns.js +36 -6
  260. package/hooks/managed-hooks-registry.cjs +1 -0
  261. package/package.json +5 -5
  262. package/scripts/build-hooks.js +11 -4
  263. package/scripts/ci-test-scope.cjs +7 -0
  264. package/scripts/docs-guard-registry.cjs +10 -0
  265. package/scripts/gen-loop-host-contract.cjs +67 -15
  266. package/scripts/lib/shellcheck-fetch.cjs +247 -0
  267. package/scripts/lint-allow-test-rule-refs.allowlist.json +0 -6
  268. package/scripts/lint-allow-test-rule-refs.effective-ceiling.json +1 -1
  269. package/scripts/lint-allow-test-rule-refs.unverified-ceiling.json +1 -1
  270. package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +5 -0
  271. package/scripts/lint-phase-enumeration-drift.cjs +24 -6
  272. package/scripts/lint-phase-id-drift.cjs +133 -8
  273. package/scripts/lint-portable-grep.cjs +176 -0
  274. package/scripts/lint-response-language-coverage.cjs +524 -0
  275. package/scripts/lint-test-file-count.allowlist.json +3 -1
  276. package/scripts/lint-workflow-shellcheck-baseline.json +1027 -0
  277. package/scripts/lint-workflow-shellcheck.cjs +614 -0
  278. package/scripts/npm-audit-baseline.cjs +376 -0
  279. package/scripts/prompt-injection-scan.sh +8 -0
  280. package/scripts/require-issue-link-policy.cjs +16 -1
  281. package/skills/gsd-execute-phase/SKILL.md +1 -1
  282. package/skills/gsd-ns-workflow/SKILL.md +1 -0
  283. package/skills/gsd-phase/SKILL.md +1 -1
  284. package/skills/gsd-quick-batch/SKILL.md +105 -0
  285. package/skills/gsd-surface/SKILL.md +18 -8
  286. package/vscode/package.json +1 -1
@@ -9,6 +9,27 @@ runs (~3–5 min each), committing each plan individually for crash resilience.
9
9
  For recovering plans from a prior *non-chunked* run, use step 6's "Add more plans" or
10
10
  proceed to `/gsd:execute-phase` — don't start a fresh chunked run over them.
11
11
 
12
+ Set `CHUNKED_PARALLEL` from config, here rather than in `plan-phase.md`, so the two extra
13
+ `gsd_run` calls it costs are paid only on a run that already reached this section (#3777) —
14
+ `CHUNKED_MODE` is `true` at this point, per the skip-check above. STRICT equality on `"true"`
15
+ is deliberate, matching `review.parallel_lanes` (#3034): a mistyped or non-canonical value
16
+ (`"1"`, `"yes"`, `"TRUE"`) gets the conservative behavior, and any tooling failure
17
+ (`config-get`/`dispatch-capacity` erroring or printing nothing) fails safe to serial — never the
18
+ opposite polarity, since firing concurrent Agent() dispatch is the risky direction here, not the
19
+ safe default. `dispatch-capacity` is the same negotiated dispatch-capability query
20
+ `quick-batch-dispatch.cts` already consults (#3673) — a host that declares no `maxConcurrency`
21
+ (reason `missing`/`undocumented`) resolves to the fail-closed floor of `1`, which degrades this
22
+ flag to serial regardless of the config value:
23
+ ```bash
24
+ _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
25
+ CHUNKED_PARALLEL_CFG=$(gsd_run query config-get planning.chunked_parallel --raw 2>/dev/null || echo "false")
26
+ DISPATCH_CAPACITY=$(gsd_run query dispatch-capacity --raw 2>/dev/null || echo "1")
27
+ CHUNKED_PARALLEL=false
28
+ if [[ "$CHUNKED_PARALLEL_CFG" == "true" ]] && [[ "${DISPATCH_CAPACITY:-1}" -gt 1 ]] 2>/dev/null; then
29
+ CHUNKED_PARALLEL=true
30
+ fi
31
+ ```
32
+
12
33
  ### 8.5.1 Outline Phase (outline-only mode, ~2 min)
13
34
 
14
35
  **Resume detection:** If `${PHASE_DIR}/${PADDED_PHASE}-PLAN-OUTLINE.md` exists and contains
@@ -17,7 +38,7 @@ the `## OUTLINE COMPLETE` marker (written by the outline agent — #2762), skip
17
38
  ```bash
18
39
  OUTLINE_FILE="${PHASE_DIR}/${PADDED_PHASE}-PLAN-OUTLINE.md"
19
40
  if [[ -f "$OUTLINE_FILE" ]] && grep -q "^## OUTLINE COMPLETE" "$OUTLINE_FILE"; then
20
- # reuse existing outline — skip to 8.5.2
41
+ : # reuse existing outline — skip to 8.5.2
21
42
  fi
22
43
  ```
23
44
 
@@ -58,25 +79,65 @@ Handle return:
58
79
 
59
80
  ### 8.5.2 Per-Plan Tasks (single-plan mode, ~3-5 min each)
60
81
 
61
- For each plan entry extracted from `PLAN-OUTLINE.md`:
82
+ Plans are dispatched **wave by wave**, in ascending Wave order (a blank/missing Wave value on a
83
+ row is treated as Wave `1` — see §8.5.1's example table). Within one Wave, dispatch is either
84
+ serial (today's behavior, always used when `CHUNKED_PARALLEL` is `false`) or concurrent
85
+ (`CHUNKED_PARALLEL` is `true` — resolved above from `planning.chunked_parallel` gated on
86
+ `dispatch-capacity`, #3777). A Wave containing only one runnable entry always takes the serial
87
+ path regardless of `CHUNKED_PARALLEL` — there is nothing to batch.
62
88
 
63
- 1. **Resume check:** Skip if `${PHASE_DIR}/{plan_id}-PLAN.md` exists with valid frontmatter
64
- (resume safety) — UNLESS `--reviews` is set, whose purpose is to REPLAN with review
65
- feedback (§6), so existing plans are overwritten, not skipped (#2762).
89
+ **For each Wave, in order:**
66
90
 
91
+ 1. **Build the batch's plan-ID list** from the outline rows sharing this Wave value, in outline
92
+ row order, deduplicated defensively — a malformed outline naming the same Plan ID twice must
93
+ never produce two concurrent Agent() calls targeting the same `{plan_id}-PLAN.md` output path
94
+ (mirrors `review.md`'s `DISPATCH_SLUGS` dedup, #3034):
95
+ ```bash
96
+ # Rewrapped through unquoted command substitution, not consumed as a bare
97
+ # `$WAVE_PLAN_IDS`: bash word-splits an unquoted scalar on IFS by default,
98
+ # but zsh does not, so a bare re-split collapses every Plan ID onto one
99
+ # iteration under zsh (gsd-core#4109 — same fix review.md's DISPATCH_SLUGS
100
+ # loop already applies). Unquoted `$(...)` re-splits identically under
101
+ # both shells regardless of `SH_WORD_SPLIT`.
102
+ BATCH_PLAN_IDS=""
103
+ for PLAN_ID in $(printf '%s' "$WAVE_PLAN_IDS"); do
104
+ case " $BATCH_PLAN_IDS " in
105
+ *" $PLAN_ID "*) continue ;;
106
+ esac
107
+ BATCH_PLAN_IDS="$BATCH_PLAN_IDS $PLAN_ID"
108
+ done
109
+ ```
110
+ `WAVE_PLAN_IDS` is the space-separated Plan ID list for this Wave, in outline row order — the
111
+ orchestrator (already reading the outline table to extract plan entries, per §8.5.1) populates
112
+ it directly from the table before this block runs.
113
+
114
+ 2. **Resume check, per entry:** for each `plan_id` in `BATCH_PLAN_IDS`, skip it (remove it from
115
+ this batch's runnable set) if `${PHASE_DIR}/{plan_id}-PLAN.md` exists with valid frontmatter —
116
+ UNLESS `--reviews` is set, whose purpose is to REPLAN with review feedback (§6), so existing
117
+ plans are overwritten, not skipped (#2762). Unchanged from before this change, applied per entry
118
+ before dispatch rather than immediately before each individual spawn:
67
119
  ```bash
68
120
  PLAN_FILE="${PHASE_DIR}/${plan_id}-PLAN.md"
69
121
  if [[ -f "$PLAN_FILE" ]] && head -1 "$PLAN_FILE" | grep -q '^---' && [[ "$ARGUMENTS" != *"--reviews"* ]]; then
70
- continue # resume safety — NOT under --reviews (replan)
122
+ : # resume safety — skip this plan, remove from the batch's runnable set — NOT under --reviews (replan)
71
123
  fi
72
124
  ```
125
+ If every entry in the Wave is resumed (runnable set empty), skip straight to the next Wave —
126
+ nothing to dispatch, nothing to wait on.
73
127
 
74
- 2. Display:
128
+ 3. Display:
75
129
  ```text
76
130
  ◆ Chunked mode: planning {plan_id} ({k}/{N})... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)
77
131
  ```
78
-
79
- 3. Spawn the planner in **single-plan** mode — it must write exactly one PLAN.md file:
132
+ Serial dispatch (`CHUNKED_PARALLEL` is `false`, or the runnable set has exactly one entry):
133
+ print one line per plan, immediately before that plan's own Agent() call, exactly as before.
134
+ Concurrent dispatch: print one line per plan in the runnable set, immediately before issuing
135
+ the batch's Agent() calls together.
136
+
137
+ 4. Spawn the planner in **single-plan** mode — it must write exactly one PLAN.md file. The prompt
138
+ is unchanged per plan; what changes is whether the runnable set's Agent() calls are issued one
139
+ at a time (serial) or together in one message (concurrent, every call still carrying
140
+ `run_in_background=true` exactly as today):
80
141
  ```javascript
81
142
  Agent(
82
143
  prompt="{same planning_context as step 8, plus:}
@@ -94,17 +155,38 @@ For each plan entry extracted from `PLAN-OUTLINE.md`:
94
155
  run_in_background=true
95
156
  )
96
157
  ```
97
-
98
- **ORCHESTRATOR RULE — ALL RUNTIMES:** `TS=$(date +%s)`; repeat `PLANNER_STALL_RESULT=$(gsd_stall_watch "$TS" "{outputFile}" "$PLAN_FILE" "## PLAN COMPLETE")` while waiting/active — `stalled` falls into step 4 (preserves prior committed chunks).
99
-
100
- 4. **Verify disk:** Check `${PHASE_DIR}/{plan_id}-PLAN.md` exists. If missing: offer 1) Retry, 2) Stop.
101
-
102
- 5. **Commit per-plan:**
158
+ **Serial dispatch:** issue one Agent() call, wait for it (step 5), verify and commit it
159
+ (steps 6-7), THEN move to the next entry in the runnable set — byte-identical to the
160
+ pre-#3777 loop.
161
+ **Concurrent dispatch:** issue every runnable entry's Agent() call together, in this one
162
+ message, before waiting on any of them.
163
+
164
+ 5. **ORCHESTRATOR RULE — ALL RUNTIMES, per batch:** for every entry dispatched in this round,
165
+ `TS=$(date +%s)`; repeat `PLANNER_STALL_RESULT=$(gsd_stall_watch "$TS" "{outputFile}" "$PLAN_FILE" "## PLAN COMPLETE")`
166
+ while waiting/active for THAT entry. Serial dispatch waits on one entry at a time (unchanged).
167
+ Concurrent dispatch waits on every entry issued in step 4 before proceeding — this is the
168
+ "per-batch" join the config makes possible: nothing in step 6 runs until every plan dispatched
169
+ this round has reached `marker_received` or `stalled`. A `stalled` entry falls into step 7's
170
+ Retry/Stop recovery for that one plan; it does not block verifying/committing sibling entries
171
+ in the same batch that already reached `marker_received`.
172
+
173
+ 6. **Verify disk, per entry:** check `${PHASE_DIR}/{plan_id}-PLAN.md` exists for each entry that
174
+ reached `marker_received`. Unchanged per-plan check.
175
+
176
+ 7. **Commit, per entry, in outline row order (not completion order):** for each verified entry —
177
+ never one combined commit for the batch — preserving crash resilience: an interrupt mid-batch
178
+ leaves every already-verified entry committed, exactly as a mid-loop interrupt did before this
179
+ change.
103
180
  ```bash
104
- _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
105
181
  gsd_run query commit "docs(${PADDED_PHASE}): plan ${plan_id} (chunked)" --files "${PHASE_DIR}/${plan_id}-PLAN.md"
106
182
  ```
107
183
 
108
- After all N plans are written and committed, treat this as `## PLANNING COMPLETE` and continue
109
- to step 9.
184
+ 8. **Recovery:** any entry that did not reach `marker_received` (missing file in step 6, or
185
+ `stalled` in step 5) offers 1) Retry, 2) Stop — scoped to that one plan. Sibling entries in the
186
+ same batch that already verified and committed keep their commits either way; only the
187
+ failed/stalled plan is retried or the run stopped.
188
+
189
+ Move to the next Wave only once every entry in the current Wave is committed or the run was
190
+ stopped. After every Wave's plans are written and committed, treat this as `## PLANNING COMPLETE`
191
+ and continue to step 9.
110
192
 
@@ -1,3 +1,5 @@
1
+ Apply response_language to all user-facing prose — narration between tool calls, status updates, progress notes, and findings included; preserve code, paths, and identifiers.
2
+
1
3
  # PRD Express Path — generate CONTEXT.md from a PRD
2
4
 
3
5
  Runs when `--prd <filepath>` is provided (§3.5 of `plan-phase.md`).
@@ -48,6 +48,15 @@ min), real, bounded call that reliably hands control back — the outer
48
48
  threshold is enforced by `dispatch_ts` accumulating across calls, not by one
49
49
  call's own duration.
50
50
 
51
+ **Never a wake-up call between cycles (#4079):** while the orchestrator waits
52
+ between `gsd_stall_watch` cycles, it must NOT call `ScheduleWakeup` (or any
53
+ host wake/sleep-scheduling tool, e.g. the `/loop` pacing surface) to
54
+ literalize "I'll wait". The `gsd_stall_watch` bash call IS the wait mechanism;
55
+ wake-up scheduling is never part of it, and a partial-args `ScheduleWakeup`
56
+ call surfaces the host's red validation error (`prompt` is required when
57
+ `stop` is not true). Just issue the next watch call (or let the blocking
58
+ Agent() return) — nothing else.
59
+
51
60
  **Disclosed tradeoff:** the first cycle always sleeps a full
52
61
  `PLANNER_STALL_INTERVAL_MINUTES` before its first check, so a planner that
53
62
  completes in seconds is not observed by this path until that interval
@@ -100,7 +100,7 @@ Parse JSON for: `researcher_model`, `planner_model`, `checker_model`, `research_
100
100
 
101
101
  **#2517:** omit the `model=` param from an `Agent()` call when its `researcher`/`planner`/`checker`_model is `"inherit"` or empty — passing `model=""` 404s on non-Claude runtimes; omitting inherits the orchestrator model (mirrors execute-phase).
102
102
 
103
- **If `response_language` is set:** All user-facing orchestrator output MUST be in `{response_language}`; technical terms, code, paths, and subagent prompts stay in English. Pass `response_language: {value}` into every spawned subagent prompt.
103
+ **If `response_language` is set:** All user-facing orchestrator output — narration between tool calls, status updates, progress notes, findings, questions, prompts, and explanations — MUST be in `{response_language}`; technical terms, code, paths, and subagent prompts stay in English. Pass `response_language: {value}` into every spawned subagent prompt.
104
104
 
105
105
  **File paths (for <required_reading> blocks):** `state_path`, `roadmap_path`, `requirements_path`, `context_path`, `research_path`, `verification_path`, `uat_path`, `reviews_path`. These are null if files don't exist.
106
106
 
@@ -287,6 +287,43 @@ fi
287
287
 
288
288
  If `AI_SPEC_FILE` is non-empty, pass `AI_SPEC_PATH` and `FRAMEWORK_LINE` to the planner in step 8 so it can reference the AI design contract. If it is empty, the active `ai-integration` capability hook in step 5.6 handles any AI-system nudge or `/gsd:ai-integration-phase` dispatch.
289
289
 
290
+ ## 4.6. Context Drift Pre-Check (drift plan:pre gate)
291
+
292
+ Capability-driven dispatch, same lazy-init pattern already used elsewhere in this file for
293
+ `PLAN_PRE_HOOKS_JSON`:
294
+
295
+ ```bash
296
+ if [ -z "${PLAN_PRE_HOOKS_JSON:-}" ]; then
297
+ PLAN_PRE_HOOKS_JSON=$(gsd_run loop render-hooks plan:pre --raw)
298
+ fi
299
+ ```
300
+
301
+ If `activeHooks` (from `PLAN_PRE_HOOKS_JSON`) has a `kind == "gate"`, `capId == "drift"`,
302
+ `check.query == "verify.context-drift"` entry (`workflow.context_drift_precheck` on), run the
303
+ check before either the research-reuse decision (§5.1) or the pattern-mapper reuse decision
304
+ (§7.8) can fire — both would otherwise silently reuse a stale artifact with zero signal.
305
+ Otherwise skip to §5.
306
+
307
+ ```bash
308
+ DRIFT=$(gsd_run verify context-drift "${PHASE}" 2>/dev/null || echo '{"skipped":true}')
309
+ ```
310
+
311
+ If `skipped` is true, continue silently to §5 — nothing to compare (no CONTEXT.md yet, no
312
+ upstream artifacts yet, or the phase directory did not resolve).
313
+
314
+ If `stale_artifacts` is a non-empty array, print `message` verbatim (it names each stale
315
+ artifact and the command to regenerate it). Then:
316
+
317
+ - If `DRIFT.block` is `false` (the default, `workflow.context_drift_action: warn`): continue to
318
+ §5 — this is advisory only, exactly like the codebase-drift pre-check at §5.65.
319
+ - If `DRIFT.block` is `true` (opt-in `workflow.context_drift_action: block`): **exit the
320
+ plan-phase workflow** rather than continuing. Do not spawn the researcher, the planner, or the
321
+ pattern mapper against a premise the user has not yet reconciled. Point the user at re-running
322
+ `/gsd:plan-phase {X}` once the named artifacts are regenerated, or at disabling the check with
323
+ `gsd_run query config-set workflow.context_drift_action warn` if the flag was a false positive.
324
+
325
+ If `stale_artifacts` is empty, continue silently to §5 — nothing to report.
326
+
290
327
  ## 5. Handle Research
291
328
 
292
329
  **Skip if:** `--gaps` flag or `--skip-research` flag or `--reviews` flag.
@@ -372,7 +409,7 @@ Agent(
372
409
  )
373
410
  ```
374
411
 
375
- > **ORCHESTRATOR RULE — ALL RUNTIMES**: 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.
412
+ > **ORCHESTRATOR RULE — ALL RUNTIMES**: 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. Never call `ScheduleWakeup` or any host wake/sleep-scheduling tool to literalize this wait (#4079) — the Agent() call returns on its own; a partial-args wake call surfaces a red validation error.
376
413
 
377
414
  ### Handle Researcher Return
378
415
 
@@ -547,8 +584,6 @@ map is refreshed first. (`drift_action: auto-remap` stays at `execute:wave:post`
547
584
  ls "${PHASE_DIR}"/*-PLAN.md 2>/dev/null || true
548
585
  ```
549
586
 
550
- **If exists AND `--reviews` flag:** Skip prompt — go straight to replanning (the purpose of `--reviews` is to replan with review feedback).
551
-
552
587
  **If exists AND no `--reviews` flag:** Offer: 1) Add more plans, 2) View existing, 3) Replan from scratch.
553
588
 
554
589
  ## 7. Use Context Paths from INIT
@@ -584,6 +619,11 @@ UI_SPEC_FILE=$(ls "${PHASE_DIR_FOR_SPEC}"/*-UI-SPEC.md 2>/dev/null | head -1)
584
619
  UI_SPEC_PATH="${UI_SPEC_FILE}"
585
620
  ```
586
621
 
622
+ **If plans exist AND the `--reviews` flag is set:** Before replanning from `--reviews`, scan
623
+ `REVIEWS_PATH` for open plan-revision conflicts inside the writer-owned delimiter pair. Go
624
+ straight to replanning with those records included, and flip the matching line to `- [x]` once
625
+ the chosen resolution is applied, using the SAME close gate as step 12 below.
626
+
587
627
  ## 7.5. Verify Nyquist Artifacts
588
628
 
589
629
  Skip if `nyquist_validation_enabled` is false OR `research_enabled` is false.
@@ -635,7 +675,7 @@ Agent(
635
675
  )
636
676
  ```
637
677
 
638
- > **ORCHESTRATOR RULE — ALL RUNTIMES**: 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.
678
+ > **ORCHESTRATOR RULE — ALL RUNTIMES**: 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. Never call `ScheduleWakeup` or any host wake/sleep-scheduling tool to literalize this wait (#4079) — the Agent() call returns on its own; a partial-args wake call surfaces a red validation error.
639
679
 
640
680
  **Handle return:**
641
681
  - **`## PATTERN MAPPING COMPLETE`:** Update `PATTERNS_PATH` to the created file path, continue to step 8.
@@ -745,7 +785,7 @@ ${AGENT_SKILLS_PLANNER}
745
785
 
746
786
  For each current actionable finding in REVIEWS.md, the planner MUST either:
747
787
  - incorporate it into a PLAN.md task, `<action>`, `<acceptance_criteria>`, `<verify>`, `must_haves`, threat model, or artifact list; or
748
- - explicitly document a deferral/rejection rationale in the relevant PLAN.md so the executor and reviewer can see the decision.
788
+ - explicitly document a deferral/rejection rationale in the relevant PLAN.md, using the Review Dispositions Ledger in `gsd-core/references/planner-reviews.md`.
749
789
 
750
790
  Historical findings already incorporated, explicitly deferred/rejected in PLAN.md, or marked fully resolved do not require new plan changes.
751
791
  </review_incorporation_contract>
@@ -1165,9 +1205,9 @@ Track `stall_reentry_count` (starts at 0; incremented each time "Adjust approach
1165
1205
 
1166
1206
  **If iteration_count < 3:**
1167
1207
 
1168
- Parse issue count from checker return: count BLOCKER + WARNING entries in the YAML issues block (structured output from gsd-plan-checker). If the checker's return contains no YAML issues block (i.e., the plan was approved with no issues), treat `issue_count` as 0 and skip the stall check — the plan passed. Proceed to step 13.
1208
+ Parse issue count from checker return: count BLOCKER + WARNING entries in the YAML issues block (structured output from gsd-plan-checker); an entry whose severity is missing or unrecognized counts as a BLOCKER (fail closed). If the checker's return contains no YAML issues block (i.e., the plan was approved with no issues), treat `issue_count` as 0 and skip the stall check — the plan passed. Proceed to step 13 — likewise when every entry in the block is explicitly INFO (display them as advisories). Advisory format: `ℹ advisory — {dimension}: {description}` per INFO entry, listed once before the step-13 output.
1169
1209
 
1170
- Display: `Revision iteration {N}/3 -- {blocker_count} blockers, {warning_count} warnings`
1210
+ Display (only when entering the revision loop — skip if the paragraph above already proceeded to step 13): `Revision iteration {N}/3 -- {blocker_count} blockers, {warning_count} warnings`
1171
1211
 
1172
1212
  **Stall detection:** If `issue_count >= prev_issue_count`:
1173
1213
  Display: `Revision loop stalled — issue count not decreasing ({issue_count} issues remain after {N} iterations)`
@@ -1207,9 +1247,14 @@ ${AGENT_SKILLS_PLANNER}
1207
1247
  </revision_context>
1208
1248
 
1209
1249
  <instructions>
1210
- Make targeted updates to address checker issues.
1211
- Do NOT replan from scratch unless issues are fundamental.
1212
- Return what changed.
1250
+ `required_property` + evidence + severity BIND. `fix_hint` is ONE non-binding example route: a
1251
+ smaller or different mechanism reaching the same property resolves it — say which. Re-check CONTEXT.md's locked decisions, capability guidance, and existing plan constraints
1252
+ BEFORE editing; if a hint would contradict one, or the
1253
+ property is unreachable without breaking one, return `## REVISION_CONFLICT` with the conflict and
1254
+ the alternatives rather than applying or working around it. Full contract:
1255
+ `gsd-core/references/planner-revision.md`.
1256
+
1257
+ Do NOT replan from scratch unless fundamental. Return what changed.
1213
1258
  </instructions>
1214
1259
  ```
1215
1260
 
@@ -1225,10 +1270,83 @@ Agent(
1225
1270
 
1226
1271
  **ORCHESTRATOR RULE — ALL RUNTIMES:** (7.99; no marker, mtimes only) `TS=$(date +%s)`; repeat `PLANNER_STALL_RESULT=$(gsd_stall_watch "$TS" "{outputFile}" "${PHASE_DIR}"'/*-PLAN.md')` while waiting/active — `stalled` -> 1) Accept as revised, to step 13, 2) Retry, 3) Stop.
1227
1272
 
1228
- After planner returns -> spawn checker again (step 10), increment iteration_count.
1273
+ **If the planner returns `## REVISION_CONFLICT`:** follow the shared Conflict Return protocol in
1274
+ `gsd-core/references/revision-loop.md`, with this workflow's bindings:
1275
+
1276
+ ```bash
1277
+ if ! CONVERGENCE_ENABLED=$(gsd_run query config-get workflow.plan_review_convergence --raw 2>/dev/null); then
1278
+ echo "BLOCKED: cannot read workflow.plan_review_convergence." >&2
1279
+ exit 1
1280
+ fi
1281
+ REVIEWS_FILE="${REVIEWS_PATH}"
1282
+ if [ "${CONVERGENCE_ENABLED}" = "true" ] && [ -n "${REVIEWS_FILE}" ] && [ ! -f "${REVIEWS_FILE}" ]; then
1283
+ echo "BLOCKED: cannot persist plan-revision conflict -- REVIEWS_PATH not a regular file: ${REVIEWS_FILE}" >&2
1284
+ exit 1
1285
+ fi
1286
+ ```
1287
+
1288
+ - Counter not spent: `iteration_count`.
1289
+ - Record channel: `$REVIEWS_FILE`'s `## Plan-Revision Conflicts` section. plan-phase wrote the
1290
+ line, so plan-phase closes it.
1291
+ - After re-spawning, return to this step, not the checker.
1292
+ - Escalates via the iteration cap on repeated `required_property`, and on the THIRD conflict
1293
+ return of this loop whatever property it names.
1294
+ - Sanitize-then-insert is real shell; fields reach `awk` via `ENVIRON`, never `-v` (decodes
1295
+ literal `\n` as a real newline). Export the row's
1296
+ `CONFLICT_DIMENSION/_PLAN/_PROPERTY/_CONSTRAINT/_ALTERNATIVES`, then run:
1297
+
1298
+ ```bash
1299
+ if [ "${CONVERGENCE_ENABLED}" = "true" ] && [ -n "${REVIEWS_FILE}" ]; then
1300
+ san() { printf '%s' "$1" | tr '\r\n\t' ' ' | sed -E 's/^[[:space:]]*[#|`-]+[[:space:]]*//'; }
1301
+ LINE="- [ ] REVISION_CONFLICT $(san "${CONFLICT_DIMENSION}")/$(san "${CONFLICT_PLAN}") — required_property: $(san "${CONFLICT_PROPERTY}") | conflicts with: $(san "${CONFLICT_CONSTRAINT}") | alternatives: $(san "${CONFLICT_ALTERNATIVES}")"
1302
+ END='<!-- gsd:plan-revision-conflicts:end -->'
1303
+ TMP=$(mktemp "${REVIEWS_FILE}.XXXXXX")
1304
+ if ! LINE="$LINE" END="$END" awk '
1305
+ { cur = $0; sub(/\r$/, "", cur) }
1306
+ cur == ENVIRON["LINE"] { seen = 1 }
1307
+ cur == ENVIRON["END"] && !ins { if (!seen) print ENVIRON["LINE"]; ins = 1 }
1308
+ { print }
1309
+ END { if (!ins) exit 2 }
1310
+ ' "${REVIEWS_FILE}" > "${TMP}"; then
1311
+ rm -f "${TMP}"
1312
+ echo "BLOCKED: no end delimiter in '${REVIEWS_FILE}'." >&2
1313
+ exit 1
1314
+ fi
1315
+ mv "${TMP}" "${REVIEWS_FILE}"
1316
+ fi
1317
+ ```
1318
+
1319
+ **Otherwise (revised plans, not `## REVISION_CONFLICT`):** if this re-spawn followed a
1320
+ resolved conflict, close its record — nothing persists across fences, so export `REVIEWS_FILE`,
1321
+ the same `CONFLICT_DIMENSION`/`CONFLICT_PLAN` used to open it, and `CONFLICT_RESOLUTION` (a
1322
+ one-line summary). Then run:
1323
+
1324
+ ```bash
1325
+ if [ "${CONVERGENCE_ENABLED}" = "true" ] && [ -n "${REVIEWS_FILE}" ]; then
1326
+ san() { printf '%s' "$1" | tr '\r\n\t' ' ' | sed -E 's/^[[:space:]]*[#|`-]+[[:space:]]*//'; }
1327
+ PREFIX="- [ ] REVISION_CONFLICT $(san "${CONFLICT_DIMENSION}")/$(san "${CONFLICT_PLAN}") — "
1328
+ RES=$(printf '%s' "${CONFLICT_RESOLUTION}" | tr '\r\n\t' ' ')
1329
+ TMP=$(mktemp "${REVIEWS_FILE}.XXXXXX")
1330
+ if ! PREFIX="$PREFIX" RES="$RES" awk '
1331
+ { cur = $0; sub(/\r$/, "", cur) }
1332
+ !d && index(cur, ENVIRON["PREFIX"]) == 1 { print "- [x]" substr(cur, 6) " | resolved: " ENVIRON["RES"]; d = 1; next }
1333
+ { print }
1334
+ END { if (!d) exit 2 }
1335
+ ' "${REVIEWS_FILE}" > "${TMP}"; then
1336
+ rm -f "${TMP}"
1337
+ echo "BLOCKED: no open conflict '${CONFLICT_DIMENSION}/${CONFLICT_PLAN}' in '${REVIEWS_FILE}'." >&2
1338
+ exit 1
1339
+ fi
1340
+ mv "${TMP}" "${REVIEWS_FILE}"
1341
+ fi
1342
+ ```
1343
+
1344
+ Spawn checker again (step 10), then increment `iteration_count`.
1229
1345
 
1230
1346
  **If iteration_count >= 3:**
1231
1347
 
1348
+ Recount BLOCKER + WARNING by the same rule — an entry whose severity is missing or unrecognized counts as a BLOCKER (fail closed). If `issue_count` is 0 — PASSED, or every entry in the block is explicitly INFO — display any advisories and proceed to step 13; the gate below fires on everything else (#3724).
1349
+
1232
1350
  Display: `Max iterations reached. {N} issues remain:` + issue list
1233
1351
 
1234
1352
  Offer: 1) Force proceed, 2) Provide guidance and retry, 3) Abandon
@@ -119,7 +119,7 @@ if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
119
119
 
120
120
  Parse JSON for: `phase_dir`, `phase_number`, `padded_phase`, `phase_name`, `has_plans`, `plan_count`, `commit_docs`, `text_mode`, `response_language`.
121
121
 
122
- **If `response_language` is set:** All user-facing output should be in `{response_language}`.
122
+ **If `response_language` is set:** All user-facing output — narration between tool calls, status updates, progress notes, findings, questions, and report prose — should be in `{response_language}`.
123
123
 
124
124
  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.
125
125
 
@@ -341,13 +341,20 @@ Run this pass whenever the source-grounding pass ran — it is the second axis o
341
341
 
342
342
  **Coverage, never silence.** If STATE.md or CONTEXT.md is absent, that axis is skipped and the skip is recorded in the same "Verification coverage" block. A clean pass must never mean "nothing was compared."
343
343
 
344
- After agent returns, verify REVIEWS.md exists:
344
+ After agent returns, verify REVIEWS.md exists. Assign the path directly and quote it — an unquoted
345
+ `${phase_dir}` inside `$(ls …)` word-splits and glob-expands, and a discarded stderr hides it (#3899):
345
346
  ```bash
346
- REVIEWS_FILE=$(ls ${phase_dir}/${padded_phase}-REVIEWS.md 2>/dev/null)
347
+ if [ -z "${phase_dir}" ]; then
348
+ echo "ERROR: phase_dir is empty — cannot resolve the expected REVIEWS.md path." >&2
349
+ exit 1
350
+ fi
351
+ REVIEWS_FILE="${phase_dir}/${padded_phase}-REVIEWS.md"
352
+ if [ ! -f "${REVIEWS_FILE}" ] || [ ! -r "${REVIEWS_FILE}" ]; then
353
+ echo "ERROR: expected reviews file is not a readable file: '${REVIEWS_FILE}'. Confirm the phase directory resolved correctly before concluding the review agent produced nothing." >&2
354
+ exit 1
355
+ fi
347
356
  ```
348
357
 
349
- If REVIEWS_FILE is empty: Error — review agent did not produce REVIEWS.md. Exit.
350
-
351
358
  ### 5b. Extract unresolved counts from CYCLE_SUMMARY Contract
352
359
 
353
360
  **Do NOT grep REVIEWS.md for HIGH or actionable counts.** REVIEWS.md accumulates history across cycles — resolved findings from prior cycles remain in the file as audit trail, inflating a raw grep count and causing false stall detection.
@@ -390,7 +397,68 @@ if [ "${ACTIONABLE_COUNT}" -gt 0 ] && [ -z "${ACTIONABLE_LINES}" ]; then
390
397
  fi
391
398
  ```
392
399
 
393
- **If HIGH_COUNT == 0 and ACTIONABLE_COUNT == 0 (converged):**
400
+ **Open plan-revision conflicts are part of the converged condition (#3771).** An entry under
401
+ `## Plan-Revision Conflicts` in REVIEWS.md is a checker `fix_hint` that contradicted a locked
402
+ decision, capability guidance, or an existing plan constraint, recorded by `/gsd:plan-phase`
403
+ together with the alternatives the planner considered. It is NOT counted by `CYCLE_SUMMARY`, so
404
+ it must be read from the file directly — evaluate this BEFORE the converged branch below, or a
405
+ run would write `planned-phase` and print the success banner over a conflict nobody resolved:
406
+
407
+ ```bash
408
+ if [ ! -f "${REVIEWS_FILE}" ]; then
409
+ # Fail CLOSED. A missing/non-file REVIEWS.md is "I cannot tell", never "no conflicts".
410
+ echo "BLOCKED: cannot read REVIEWS.md ('${REVIEWS_FILE}') to check for open plan-revision conflicts. Refusing to declare convergence on an unverifiable gate." >&2
411
+ exit 1
412
+ fi
413
+ if OPEN_CONFLICTS=$(awk '
414
+ BEGIN { saw_title = 0; in_owned = 0; saw_heading = 0; done = 0; count = 0 }
415
+ { sub(/\r$/, "") }
416
+ !saw_title && /^# Cross-AI Plan Review — Phase / { saw_title = 1; next }
417
+ saw_title && !in_owned && !done {
418
+ if ($0 == "") next
419
+ if ($0 == "<!-- gsd:plan-revision-conflicts:begin -->") { in_owned = 1; next }
420
+ exit 2
421
+ }
422
+ in_owned && $0 == "<!-- gsd:plan-revision-conflicts:begin -->" { exit 2 }
423
+ in_owned && !saw_heading && $0 == "" { next }
424
+ in_owned && !saw_heading && $0 == "## Plan-Revision Conflicts" { saw_heading = 1; next }
425
+ in_owned && !saw_heading { exit 2 }
426
+ in_owned && $0 == "<!-- gsd:plan-revision-conflicts:end -->" {
427
+ done = 1
428
+ in_owned = 0
429
+ print count
430
+ exit
431
+ }
432
+ in_owned && /^- \[ \] REVISION_CONFLICT .*required_property:/ { count++ }
433
+ END { if (!done) exit 2 }
434
+ ' "${REVIEWS_FILE}"); then
435
+ :
436
+ else
437
+ awk_status=$?
438
+ echo "BLOCKED: could not parse the writer-owned plan-revision conflict block in '${REVIEWS_FILE}' (awk exit ${awk_status}). Refusing to declare convergence on an unverifiable gate." >&2
439
+ exit 1
440
+ fi
441
+ ```
442
+
443
+ `/gsd:review` emits exactly one writer-owned slot immediately after the artifact title,
444
+ between `<!-- gsd:plan-revision-conflicts:begin -->` and
445
+ `<!-- gsd:plan-revision-conflicts:end -->`. Inside that slot, `/gsd:plan-phase` records each
446
+ conflict as a `- [ ] REVISION_CONFLICT` checklist line and flips it to
447
+ `- [x] REVISION_CONFLICT` when resolved. The reader counts only the first fixed slot at that
448
+ position and stops at its explicit end delimiter. Reviewer output is rendered after the slot, so
449
+ raw reviewer text containing either the heading or an exact conflict-shaped checklist line cannot
450
+ forge blocking state. There is deliberately no fallback to the prior global line-shape scan: that
451
+ shape never merged to `next`, and accepting both grammars would recreate the reviewer collision.
452
+
453
+ **Only `/gsd:plan-phase` mutates the contents of this slot.** The review agent preserves the
454
+ existing `## Plan-Revision Conflicts` block byte-for-byte between its delimiters; every other
455
+ agent with write access to REVIEWS.md must leave it alone. Appending, editing, reordering or
456
+ deleting a line there forges the state of a blocking gate. Readers read. If `OPEN_CONFLICTS` > 0, convergence has NOT been
457
+ achieved regardless of the counts: skip the converged branch and continue to 5c so the next cycle
458
+ arbitrates. Escalation at `MAX_CYCLES` is unchanged and still terminates the loop, so an
459
+ unresolvable conflict escalates rather than deadlocking.
460
+
461
+ **If HIGH_COUNT == 0 and ACTIONABLE_COUNT == 0 and OPEN_CONFLICTS == 0 (converged):**
394
462
 
395
463
  ```bash
396
464
  gsd_run state planned-phase --phase "${PHASE}" --name "${phase_name}" --plans "${PLAN_COUNT}"
@@ -410,11 +478,11 @@ Display:
410
478
 
411
479
  Exit — convergence achieved.
412
480
 
413
- **If HIGH_COUNT > 0 or ACTIONABLE_COUNT > 0:** Continue to 5c.
481
+ **If HIGH_COUNT > 0 or ACTIONABLE_COUNT > 0 or OPEN_CONFLICTS > 0:** Continue to 5c.
414
482
 
415
483
  ### 5c. Stall Detection + Escalation Check
416
484
 
417
- Display: `◆ Cycle {cycle}/{MAX_CYCLES} — {HIGH_COUNT} HIGH, {ACTIONABLE_COUNT} actionable non-HIGH review concerns found`
485
+ Display: `◆ Cycle {cycle}/{MAX_CYCLES} — {HIGH_COUNT} HIGH, {ACTIONABLE_COUNT} actionable non-HIGH review concerns, {OPEN_CONFLICTS} open plan-revision conflicts found`
418
486
 
419
487
  **Stall detection:** If `UNRESOLVED_COUNT >= prev_unresolved_count`:
420
488
  ```text
@@ -424,6 +492,29 @@ Display: `◆ Cycle {cycle}/{MAX_CYCLES} — {HIGH_COUNT} HIGH, {ACTIONABLE_COUN
424
492
 
425
493
  **Max cycles check:** If `cycle >= MAX_CYCLES`:
426
494
 
495
+ **If `OPEN_CONFLICTS` > 0 (#3771): "Proceed anyway" is never offered.** An open plan-revision
496
+ conflict is a blocker — this loop's whole purpose is to surface it rather than let a success
497
+ banner paper over it, so escalation cannot end in the same silent acceptance a HIGH/actionable
498
+ concern can. Only "Manual review" is available:
499
+
500
+ If `TEXT_MODE` is true, present as plain text:
501
+ ```text
502
+ Plan convergence did not complete after {MAX_CYCLES} cycles.
503
+ {OPEN_CONFLICTS} open plan-revision conflict(s) remain — these are blockers and cannot be accepted:
504
+
505
+ {HIGH_LINES}
506
+
507
+ {ACTIONABLE_LINES}
508
+
509
+ Review the concerns in: {REVIEWS_FILE}
510
+
511
+ To replan manually: /gsd:plan-phase {PHASE} --reviews
512
+ To restart loop: /gsd:plan-review-convergence {PHASE} {REVIEWER_FLAGS}
513
+ ```
514
+ Exit workflow.
515
+
516
+ **Otherwise (`OPEN_CONFLICTS` == 0):**
517
+
427
518
  If `TEXT_MODE` is true, present as plain-text numbered list:
428
519
  ```text
429
520
  Plan convergence did not complete after {MAX_CYCLES} cycles.
@@ -478,7 +569,7 @@ Display: `◆ Replanning inline with review feedback... (plan-phase runs here in
478
569
  Skill(skill="gsd-plan-phase", args="{PHASE} --reviews --skip-research {GSD_WS}")
479
570
  ```
480
571
 
481
- Run plan-phase **inline** (do NOT wrap it in Agent()). Same rationale as step 4: the convergence orchestrator runs at depth 0 with Agent available, so inline plan-phase can spawn gsd-planner and gsd-plan-checker at depth 1. Wrapping in Agent() pushes plan-phase to depth 1 where the Agent tool is absent — the replan loop can never produce a revised plan when HIGHs are found. This is the root cause of bug #936. Actionable MEDIUM/LOW findings must be incorporated into executable PLAN.md content or explicitly deferred/rejected in the relevant PLAN.md before convergence can complete. Wait until plan-phase completes (outputs '## PLANNING COMPLETE') and updated PLAN.md files are committed before continuing.
572
+ Run plan-phase **inline** (do NOT wrap it in Agent()). Same rationale as step 4: the convergence orchestrator runs at depth 0 with Agent available, so inline plan-phase can spawn gsd-planner and gsd-plan-checker at depth 1. Wrapping in Agent() pushes plan-phase to depth 1 where the Agent tool is absent — the replan loop can never produce a revised plan when HIGHs are found. This is the root cause of bug #936. Actionable MEDIUM/LOW findings must be incorporated into executable PLAN.md content or explicitly deferred/rejected in the relevant PLAN.md before convergence can complete. The same holds for any open `## Plan-Revision Conflicts` entry (#3771): the replan must resolve it by adopting one of its recorded alternatives, overriding the named constraint, or amending the constraint — and mark the entry resolved. Re-running the planner against an unchanged conflict cannot resolve it and only burns a cycle. Wait until plan-phase completes (outputs '## PLANNING COMPLETE') and updated PLAN.md files are committed before continuing.
482
573
 
483
574
  After plan-phase completes → go back to **step 5a** (review again).
484
575
 
@@ -497,7 +588,8 @@ After plan-phase completes → go back to **step 5a** (review again).
497
588
  - [ ] Abort with clear error if current_actionable is absent or malformed
498
589
  - [ ] Warn if ACTIONABLE_COUNT > 0 but ## Current Actionable Non-HIGH Concerns section is absent from return message
499
590
  - [ ] The review Agent fully completes gsd-review before returning (plan-phase runs inline — no Agent wrap)
500
- - [ ] Loop exits on: no HIGH concerns and no actionable non-HIGH concerns (converged) OR max cycles (escalation)
591
+ - [ ] Loop exits on: no HIGH concerns, no actionable non-HIGH concerns, and OPEN_CONFLICTS == 0 (converged) OR max cycles (escalation)
592
+ - [ ] OPEN_CONFLICTS read from REVIEWS.md and evaluated BEFORE the converged branch writes state or prints the banner
501
593
  - [ ] Stall detection reported when total unresolved review concern count is not decreasing
502
594
  - [ ] STATE.md updated on convergence completion
503
595
  </success_criteria>
@@ -140,7 +140,7 @@ RESPONSE_LANGUAGE=$(gsd_run query config-get response_language --raw --default "
140
140
  gsd_run query commit "docs: plant seed — {$IDEA}" --files .planning/seeds/SEED-{PADDED}-{slug}.md
141
141
  ```
142
142
 
143
- **If `response_language` is set:** All user-facing questions, prompts, and explanations in this workflow MUST be presented in `{response_language}`. Technical terms, code, file paths, and subagent prompts stay in English — only user-facing output is translated.
143
+ **If `response_language` is set:** All user-facing output of this workflow — narration between tool calls, status updates, progress notes, findings, questions, prompts, and explanations — MUST be presented in `{response_language}`. Technical terms, code, file paths, and subagent prompts stay in English — only user-facing output is translated.
144
144
  </step>
145
145
 
146
146
  <step name="confirm">
@@ -1,3 +1,5 @@
1
+ @~/.claude/gsd-core/references/response-language-directive.md
2
+
1
3
  <purpose>
2
4
  Create a clean branch for pull requests by filtering .planning/ paths out of the
3
5
  cherry-picked history. Two modes, selected by the `planning.pr_strict` config key:
@@ -261,7 +263,10 @@ if [ "$PR_STRICT" = "true" ]; then
261
263
  FILTER_PATHS=".planning/"
262
264
  FORBIDDEN_RE="^\.planning/"
263
265
  else
264
- FILTER_PATHS=$(for d in $TRANSIENT_DIRS; do printf '.planning/%s/ ' "$d"; done)
266
+ # Rewrapped through unquoted command substitution (gsd-core#4109): a bare
267
+ # `$VAR` word-splits under bash but not zsh, collapsing every element onto
268
+ # one iteration there.
269
+ FILTER_PATHS=$(for d in $(printf '%s' "$TRANSIENT_DIRS"); do printf '.planning/%s/ ' "$d"; done)
265
270
  FORBIDDEN_RE="^\.planning/($(echo "$TRANSIENT_DIRS" | tr ' ' '|'))/"
266
271
  fi
267
272
  ```
@@ -322,12 +327,15 @@ touching the same planning path makes `git cherry-pick` abort with *"untracked w
322
327
  files would be overwritten by merge"*, and every remaining commit is silently dropped.
323
328
 
324
329
  ```bash
325
- for HASH in $INCLUDED_COMMITS; do
330
+ # Rewrapped through unquoted command substitution (gsd-core#4109): a bare
331
+ # `$VAR` word-splits under bash but not zsh, collapsing every element onto
332
+ # one iteration there.
333
+ for HASH in $(printf '%s' "$INCLUDED_COMMITS"); do
326
334
  # A modify/delete conflict on a filtered path is EXPECTED and is resolved below — the
327
335
  # filtered path is absent from HEAD by construction. Do not treat it as a failure here.
328
336
  git cherry-pick --no-commit "$HASH" || true
329
337
 
330
- for P in $FILTER_PATHS; do
338
+ for P in $(printf '%s' "$FILTER_PATHS"); do
331
339
  git rm -r -f -q --ignore-unmatch -- "$P" 2>/dev/null || true
332
340
  git checkout HEAD -- "$P" 2>/dev/null || true
333
341
  done
@@ -19,7 +19,7 @@ _GSD_SHIM_NAME="gsd-tools.cjs"; _GSD_RUNTIME_ROOT="${RUNTIME_DIR:-$(git rev-pars
19
19
  RESPONSE_LANGUAGE=$(gsd_run query config-get response_language --raw --default "" 2>/dev/null || echo "")
20
20
  ```
21
21
 
22
- **If `response_language` is set:** All user-facing questions, prompts, and explanations in this workflow MUST be presented in `{response_language}`. Technical terms, code, file paths, and subagent prompts stay in English — only user-facing output is translated.
22
+ **If `response_language` is set:** All user-facing output of this workflow — narration between tool calls, status updates, progress notes, findings, questions, prompts, and explanations — MUST be presented in `{response_language}`. Technical terms, code, file paths, and subagent prompts stay in English — only user-facing output is translated.
23
23
 
24
24
  ## 1. Initialize
25
25