@opengsd/gsd-core 1.17.0 → 1.18.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 (176) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/agents/gsd-advisor-researcher.compact.md +1 -1
  4. package/agents/gsd-advisor-researcher.md +1 -1
  5. package/agents/gsd-assumptions-analyzer.compact.md +1 -1
  6. package/agents/gsd-assumptions-analyzer.md +1 -1
  7. package/agents/gsd-code-fixer.compact.md +1 -1
  8. package/agents/gsd-code-fixer.md +1 -1
  9. package/agents/gsd-code-reviewer.compact.md +1 -1
  10. package/agents/gsd-code-reviewer.md +1 -1
  11. package/agents/gsd-codebase-mapper.compact.md +1 -1
  12. package/agents/gsd-codebase-mapper.md +1 -1
  13. package/agents/gsd-debugger.md +1 -1
  14. package/agents/gsd-doc-verifier.compact.md +4 -4
  15. package/agents/gsd-doc-verifier.md +4 -3
  16. package/agents/gsd-doc-writer.compact.md +23 -5
  17. package/agents/gsd-doc-writer.md +19 -6
  18. package/agents/gsd-eval-auditor.compact.md +1 -1
  19. package/agents/gsd-eval-auditor.md +1 -1
  20. package/agents/gsd-executor.md +7 -3
  21. package/agents/gsd-integration-checker.compact.md +1 -1
  22. package/agents/gsd-integration-checker.md +1 -1
  23. package/agents/gsd-nyquist-auditor.compact.md +1 -1
  24. package/agents/gsd-nyquist-auditor.md +1 -1
  25. package/agents/gsd-phase-researcher.md +1 -1
  26. package/agents/gsd-plan-checker.md +2 -1
  27. package/agents/gsd-planner.md +5 -4
  28. package/agents/gsd-project-researcher.compact.md +1 -1
  29. package/agents/gsd-project-researcher.md +1 -1
  30. package/agents/gsd-research-synthesizer.compact.md +5 -5
  31. package/agents/gsd-research-synthesizer.md +5 -5
  32. package/agents/gsd-roadmapper.compact.md +1 -1
  33. package/agents/gsd-roadmapper.md +1 -1
  34. package/agents/gsd-security-auditor.compact.md +1 -1
  35. package/agents/gsd-security-auditor.md +1 -1
  36. package/agents/gsd-ui-auditor.compact.md +1 -1
  37. package/agents/gsd-ui-auditor.md +1 -1
  38. package/agents/gsd-ui-checker.compact.md +1 -1
  39. package/agents/gsd-ui-checker.md +1 -1
  40. package/agents/gsd-ui-researcher.compact.md +13 -1
  41. package/agents/gsd-ui-researcher.md +28 -1
  42. package/agents/gsd-verifier.md +1 -1
  43. package/bin/install.js +38 -2
  44. package/commands/gsd/code-review.md +8 -0
  45. package/commands/gsd/pause-work.md +1 -1
  46. package/gsd-core/bin/gsd-tools.cjs +18 -11
  47. package/gsd-core/bin/lib/capability-ledger.cjs +25 -0
  48. package/gsd-core/bin/lib/capability-lock.cjs +29 -7
  49. package/gsd-core/bin/lib/capability-registry.cjs +70 -64
  50. package/gsd-core/bin/lib/capability-source.cjs +3 -1
  51. package/gsd-core/bin/lib/capability-state.cjs +6 -3
  52. package/gsd-core/bin/lib/capability-validator.cjs +6 -4
  53. package/gsd-core/bin/lib/commands.cjs +5 -1
  54. package/gsd-core/bin/lib/config-loader.cjs +3 -3
  55. package/gsd-core/bin/lib/decision-coverage-support.cjs +19 -0
  56. package/gsd-core/bin/lib/gate-decision-coverage-plan.cjs +15 -0
  57. package/gsd-core/bin/lib/gate-decision-coverage-verify.cjs +13 -0
  58. package/gsd-core/bin/lib/gate-predicate-evaluator.cjs +23 -3
  59. package/gsd-core/bin/lib/gate-predicate.cjs +8 -12
  60. package/gsd-core/bin/lib/gate-tdd-review-checkpoint.cjs +60 -5
  61. package/gsd-core/bin/lib/init.cjs +11 -4
  62. package/gsd-core/bin/lib/install-effort-resolver.cjs +2 -2
  63. package/gsd-core/bin/lib/install-model-override-resolver.cjs +4 -3
  64. package/gsd-core/bin/lib/install-profiles.cjs +5 -3
  65. package/gsd-core/bin/lib/phase.cjs +120 -11
  66. package/gsd-core/bin/lib/report-parser.cjs +57 -1
  67. package/gsd-core/bin/lib/review-lane-invocation.cjs +40 -0
  68. package/gsd-core/bin/lib/review-reviewer-selection.cjs +20 -1
  69. package/gsd-core/bin/lib/roadmap.cjs +56 -5
  70. package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +47 -2
  71. package/gsd-core/bin/lib/runtime-homes.cjs +18 -0
  72. package/gsd-core/bin/lib/shell-command-projection.cjs +2 -0
  73. package/gsd-core/bin/lib/smart-entry.cjs +24 -16
  74. package/gsd-core/bin/lib/state-document.cjs +37 -3
  75. package/gsd-core/bin/lib/state-transition.cjs +31 -10
  76. package/gsd-core/bin/lib/state.cjs +90 -60
  77. package/gsd-core/bin/lib/task-command-router.cjs +2 -3
  78. package/gsd-core/bin/lib/tdd-red-evidence.cjs +5 -0
  79. package/gsd-core/bin/lib/tdd-test-files.cjs +42 -0
  80. package/gsd-core/bin/lib/verify.cjs +36 -1
  81. package/gsd-core/references/agent-contracts.md +1 -1
  82. package/gsd-core/references/agent-skills-bootstrap.md +26 -7
  83. package/gsd-core/references/git-integration.md +3 -3
  84. package/gsd-core/references/planner-reversibility.md +18 -4
  85. package/gsd-core/references/planner-verify-command-grounding.md +18 -0
  86. package/gsd-core/references/tdd.md +4 -3
  87. package/gsd-core/references/verifier-phase-gates.md +8 -1
  88. package/gsd-core/workflows/add-backlog.md +10 -2
  89. package/gsd-core/workflows/add-phase.md +5 -1
  90. package/gsd-core/workflows/add-tests.md +5 -1
  91. package/gsd-core/workflows/add-todo.md +5 -1
  92. package/gsd-core/workflows/ai-integration-phase.md +5 -1
  93. package/gsd-core/workflows/audit-milestone.md +21 -9
  94. package/gsd-core/workflows/autonomous/templates/minimal-context.md +41 -0
  95. package/gsd-core/workflows/autonomous.md +52 -64
  96. package/gsd-core/workflows/check-todos.md +5 -1
  97. package/gsd-core/workflows/code-review-fix.md +5 -1
  98. package/gsd-core/workflows/code-review.md +5 -1
  99. package/gsd-core/workflows/complete-milestone/detail/elaboration.md +5 -1
  100. package/gsd-core/workflows/complete-milestone.md +25 -5
  101. package/gsd-core/workflows/debug.md +5 -1
  102. package/gsd-core/workflows/diagnose-issues.md +5 -1
  103. package/gsd-core/workflows/discuss-phase/modes/chain.md +5 -1
  104. package/gsd-core/workflows/discuss-phase-assumptions.md +11 -3
  105. package/gsd-core/workflows/discuss-phase.md +5 -1
  106. package/gsd-core/workflows/docs-update/detail/elaboration.md +7 -6
  107. package/gsd-core/workflows/docs-update.md +27 -5
  108. package/gsd-core/workflows/edit-phase.md +5 -1
  109. package/gsd-core/workflows/eval-review.md +11 -3
  110. package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +5 -1
  111. package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +12 -6
  112. package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +19 -2
  113. package/gsd-core/workflows/execute-phase/steps/regression-gate.md +18 -1
  114. package/gsd-core/workflows/execute-phase/steps/verify-phase-goal.md +5 -1
  115. package/gsd-core/workflows/execute-phase.md +37 -6
  116. package/gsd-core/workflows/execute-plan.md +8 -4
  117. package/gsd-core/workflows/extract-learnings.md +6 -2
  118. package/gsd-core/workflows/forensics.md +5 -1
  119. package/gsd-core/workflows/import.md +5 -1
  120. package/gsd-core/workflows/insert-phase.md +5 -1
  121. package/gsd-core/workflows/manager.md +10 -2
  122. package/gsd-core/workflows/map-codebase.md +5 -1
  123. package/gsd-core/workflows/milestone-summary.md +10 -2
  124. package/gsd-core/workflows/new-milestone.md +13 -5
  125. package/gsd-core/workflows/new-project.md +9 -5
  126. package/gsd-core/workflows/next.md +8 -4
  127. package/gsd-core/workflows/pause-work.md +3 -3
  128. package/gsd-core/workflows/plan-phase/steps/auto-advance-check.md +70 -0
  129. package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +5 -1
  130. package/gsd-core/workflows/plan-phase/steps/closed-phase-gate.md +6 -2
  131. package/gsd-core/workflows/plan-phase.md +41 -73
  132. package/gsd-core/workflows/plan-review-convergence.md +17 -5
  133. package/gsd-core/workflows/plant-seed.md +8 -4
  134. package/gsd-core/workflows/progress.md +6 -2
  135. package/gsd-core/workflows/quick-batch.md +5 -1
  136. package/gsd-core/workflows/quick.md +22 -7
  137. package/gsd-core/workflows/remove-phase.md +5 -1
  138. package/gsd-core/workflows/resume-project.md +5 -1
  139. package/gsd-core/workflows/review.md +6 -2
  140. package/gsd-core/workflows/scan.md +5 -1
  141. package/gsd-core/workflows/secure-phase.md +6 -2
  142. package/gsd-core/workflows/settings.md +3 -3
  143. package/gsd-core/workflows/ship.md +13 -10
  144. package/gsd-core/workflows/thread.md +10 -2
  145. package/gsd-core/workflows/transition.md +5 -1
  146. package/gsd-core/workflows/ui-phase.md +5 -1
  147. package/gsd-core/workflows/ui-review.md +6 -2
  148. package/gsd-core/workflows/ultraplan-phase.md +5 -1
  149. package/gsd-core/workflows/update.md +5 -1
  150. package/gsd-core/workflows/validate-phase.md +6 -2
  151. package/gsd-core/workflows/verify-work/detail/elaboration.md +1 -1
  152. package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +5 -1
  153. package/gsd-core/workflows/verify-work.md +11 -3
  154. package/hooks/dist/gsd-agent-isolation-guard.js +3 -1
  155. package/hooks/dist/gsd-cursor-subagent-start.js +2 -1
  156. package/hooks/dist/gsd-secret-read-guard.js +72 -14
  157. package/hooks/dist/gsd-validate-commit.sh +27 -5
  158. package/hooks/gsd-agent-isolation-guard.js +3 -1
  159. package/hooks/gsd-cursor-subagent-start.js +2 -1
  160. package/hooks/gsd-secret-read-guard.js +72 -14
  161. package/hooks/gsd-validate-commit.sh +27 -5
  162. package/package.json +1 -1
  163. package/scripts/audit-workflow-script-paths.cjs +1 -1
  164. package/scripts/diagnose-validate-commit-in-tier.cjs +270 -0
  165. package/scripts/diagnose-validate-commit-phases.cjs +348 -0
  166. package/scripts/diagnose-windows-spawn-watcher.cjs +317 -0
  167. package/scripts/docs-guard-registry.cjs +2 -0
  168. package/scripts/lib/macos-conformance-tier.generated.cjs +9 -0
  169. package/scripts/lib/platform-conformance-tier.generated.cjs +16 -0
  170. package/scripts/lint-phase-arg-assignment.cjs +2 -2
  171. package/scripts/lint-workflow-shellcheck-baseline.json +16 -16
  172. package/scripts/release-tarball-smoke.cjs +1 -0
  173. package/scripts/xtrace-phase-timing.cjs +205 -0
  174. package/skills/gsd-code-review/SKILL.md +8 -0
  175. package/skills/gsd-pause-work/SKILL.md +1 -1
  176. package/vscode/package.json +1 -1
@@ -87,8 +87,12 @@ Load all context in one call:
87
87
  @~/.claude/gsd-core/references/gsd-run-resolver.md
88
88
 
89
89
  ```bash
90
- WAVE_PARAM=""; if [[ "$ARGUMENTS" =~ (^|[[:space:]])--wave[[:space:]]+([^[:space:]-][^[:space:]]*) ]]; then WAVE_PARAM="--wave ${BASH_REMATCH[2]}"; fi
91
- GSD_WS=$(echo " $ARGUMENTS" | sed -nE 's/.* --ws +([A-Za-z0-9][A-Za-z0-9._-]*).*/--ws \1/p' | head -n 1)
90
+ IFS= read -rd '' GSD_ARGS <<'GSD_ARGS_END' || true
91
+ $ARGUMENTS
92
+ GSD_ARGS_END
93
+ GSD_ARGS=${GSD_ARGS%$'\n'}
94
+ WAVE_PARAM=""; if [[ "$GSD_ARGS" =~ (^|[[:space:]])--wave[[:space:]]+([^[:space:]-][^[:space:]]*) ]]; then WAVE_PARAM="--wave ${BASH_REMATCH[2]}"; fi
95
+ GSD_WS=$(echo " $GSD_ARGS" | sed -nE 's/.* --ws +([A-Za-z0-9][A-Za-z0-9._-]*).*/--ws \1/p' | head -n 1)
92
96
  INIT=$(gsd_run query init.execute-phase ${GSD_WS:+--ws=${GSD_WS##* }} "${PHASE_ARG}" $WAVE_PARAM)
93
97
  if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
94
98
  AGENT_SKILLS=$(gsd_run query agent-skills gsd-executor ${GSD_WS:+--ws=${GSD_WS##* }})
@@ -104,6 +108,15 @@ Parse JSON for: `executor_model`, `verifier_model`, `commit_docs`, `parallelizat
104
108
 
105
109
  @~/.claude/gsd-core/references/execute-phase-response-language.md
106
110
 
111
+ Reset the wave-failure tally (#4915). Fenced `bash` blocks are separate shells, so the post-merge gate's cumulative count reaches step 5.8 through a handoff file; a previous run's tally must not leak in:
112
+
113
+ ```bash
114
+ # gsd-handoff:begin wave-failure-reset
115
+ _GSD_HANDOFF="$( { git rev-parse --absolute-git-dir 2>/dev/null || echo "${TMPDIR:-/tmp}/gsd-$(pwd | cksum | cut -d' ' -f1)"; } )/gsd-handoff"; mkdir -p "$_GSD_HANDOFF"
116
+ : > "$_GSD_HANDOFF/wave-failures"
117
+ # gsd-handoff:end wave-failure-reset
118
+ ```
119
+
107
120
  Read runtime/worktree config and fail closed before any executor dispatch:
108
121
 
109
122
  ```bash
@@ -172,16 +185,24 @@ inline path for each plan.
172
185
 
173
186
  **REQUIRED — Sync chain flag with intent.** If user invoked manually (no `--auto`), clear the ephemeral chain flag from any previous interrupted `--auto` chain. This prevents stale `_auto_chain_active: true` from causing unwanted auto-advance. This does NOT touch `workflow.auto_advance` (the user's persistent settings preference). You MUST execute this bash block before any config reads:
174
187
  ```bash
188
+ IFS= read -rd '' GSD_ARGS <<'GSD_ARGS_END' || true
189
+ $ARGUMENTS
190
+ GSD_ARGS_END
191
+ GSD_ARGS=${GSD_ARGS%$'\n'}
175
192
  # REQUIRED: prevents stale auto-chain from previous --auto runs
176
- if [[ ! "$ARGUMENTS" =~ --auto ]]; then
193
+ if [[ ! "$GSD_ARGS" =~ --auto ]]; then
177
194
  gsd_run query config-set workflow._auto_chain_active false || true
178
195
  fi
179
196
  ```
180
197
 
181
198
  Resolve `MVP_MODE` once via the centralized `phase.mvp-mode` query verb (precedence chain: CLI flag → ROADMAP `**Mode:** mvp` → `workflow.mvp_mode` config → false):
182
199
  ```bash
200
+ IFS= read -rd '' GSD_ARGS <<'GSD_ARGS_END' || true
201
+ $ARGUMENTS
202
+ GSD_ARGS_END
203
+ GSD_ARGS=${GSD_ARGS%$'\n'}
183
204
  MVP_FLAG_ARG=""
184
- if [[ "$ARGUMENTS" =~ (^|[[:space:]])--mvp([[:space:]]|$) ]]; then MVP_FLAG_ARG="--cli-flag"; fi
205
+ if [[ "$GSD_ARGS" =~ (^|[[:space:]])--mvp([[:space:]]|$) ]]; then MVP_FLAG_ARG="--cli-flag"; fi
185
206
  MVP_MODE=$(gsd_run query phase.mvp-mode "${PHASE_NUMBER}" $MVP_FLAG_ARG --pick active)
186
207
  EXECUTE_POST_HOOKS_JSON=$(gsd_run loop render-hooks execute:post --raw)
187
208
  TDD_MODE=$(gsd_run loop render-hooks execute:post --active-cap tdd)
@@ -482,7 +503,7 @@ if printf '%s' "$ORCH_BRANCH" | grep -Eq '^((worktree-)?agent-|worktree-wf_)'; t
482
503
  if [ "$_WT_HAS_COMMITS" -eq 1 ] || [ -n "$_WT_DIRTY_ALL" ]; then
483
504
  echo "" >&2
484
505
  echo " To integrate before continuing:" >&2
485
- [ -n "$_WT_DIRTY_ALL" ] && echo " 1. git add -A && git commit -m 'wip: recover worktree state' # from THIS worktree" >&2
506
+ [ -n "$_WT_DIRTY_ALL" ] && echo " 1. git add -A && git commit -m 'chore(recover): recover worktree state' # from THIS worktree" >&2
486
507
  echo " 2. cd <orchestrator worktree> # a checkout whose branch is NOT agent-*/worktree-agent-*" >&2
487
508
  echo " 3. git merge --no-ff $ORCH_BRANCH # or: git cherry-pick <sha>... for selected commits" >&2
488
509
  echo " 4. re-run the phase from there" >&2
@@ -755,7 +776,7 @@ increases monotonically across waves. `{status}` is `complete` (success),
755
776
 
756
777
  ```
757
778
  <sequential_execution>
758
- You are running as a SEQUENTIAL executor agent on the main working tree.
779
+ You are running as a SEQUENTIAL executor agent on the main working tree (set SEQUENTIAL=true in your Step 0 commit guard; a linked phase worktree on the configured branch is allowed, #4799).
759
780
  Use normal git commits (with hooks). Do NOT use --no-verify.
760
781
  Run the `<project_root_pin>` guard before your first Edit/Write and before every commit (#4254).
761
782
  REQUIRED ORDER: Write SUMMARY.md → commit → only then any narration. No text between Write and commit (truncation risk; #2070 rescue is not primary defense).
@@ -971,6 +992,16 @@ increases monotonically across waves. `{status}` is `complete` (success),
971
992
 
972
993
  5.8. **Handle test gate failures (when `WAVE_FAILURE_COUNT > 0`):**
973
994
 
995
+ Read the count the post-merge gate recorded (#4915); a shell variable set in step 5.6 is gone by now:
996
+
997
+ ```bash
998
+ # gsd-handoff:begin wave-failure-read
999
+ _GSD_HANDOFF="$( { git rev-parse --absolute-git-dir 2>/dev/null || echo "${TMPDIR:-/tmp}/gsd-$(pwd | cksum | cut -d' ' -f1)"; } )/gsd-handoff"; mkdir -p "$_GSD_HANDOFF"
1000
+ WAVE_FAILURE_COUNT=$(wc -l < "$_GSD_HANDOFF/wave-failures" 2>/dev/null | tr -d ' ' || true); WAVE_FAILURE_COUNT=${WAVE_FAILURE_COUNT:-0}
1001
+ echo "WAVE_FAILURE_COUNT=${WAVE_FAILURE_COUNT}"
1002
+ # gsd-handoff:end wave-failure-read
1003
+ ```
1004
+
974
1005
  ```
975
1006
  ## ⚠ Post-Merge Test Failure (cumulative failures: ${WAVE_FAILURE_COUNT})
976
1007
 
@@ -45,7 +45,11 @@ Load execution context (paths only to minimize orchestrator context):
45
45
  @~/.claude/gsd-core/references/gsd-run-resolver.md
46
46
 
47
47
  ```bash
48
- GSD_WS=$(echo " $ARGUMENTS" | sed -nE 's/.* --ws +([A-Za-z0-9][A-Za-z0-9._-]*).*/--ws \1/p' | head -n 1)
48
+ IFS= read -rd '' GSD_ARGS <<'GSD_ARGS_END' || true
49
+ $ARGUMENTS
50
+ GSD_ARGS_END
51
+ GSD_ARGS=${GSD_ARGS%$'\n'}
52
+ GSD_WS=$(echo " $GSD_ARGS" | sed -nE 's/.* --ws +([A-Za-z0-9][A-Za-z0-9._-]*).*/--ws \1/p' | head -n 1)
49
53
  INIT=$(gsd_run query init.execute-phase ${GSD_WS:+--ws=${GSD_WS##* }} "${PHASE}")
50
54
  if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
51
55
  ```
@@ -137,7 +141,7 @@ Otherwise: Apply checkpoint-based routing below.
137
141
  gsd_run query dispatch-isolation --raw --force-isolation none >/dev/null 2>&1 || true
138
142
  ```
139
143
 
140
- Segment dispatches therefore carry no `{harnessFlag}`.
144
+ Segment dispatches therefore carry no `{harnessFlag}` and DO carry a `<sequential_execution>` block (`You are running as a SEQUENTIAL executor agent on the main working tree (set SEQUENTIAL=true in your Step 0 commit guard, #4799).`) so the segment executor's commit guard does not apply the per-agent branch allow-list in a linked phase worktree.
141
145
 
142
146
  <!-- #2508 runtime-aware-dispatch -->
143
147
 
@@ -514,14 +518,14 @@ Extract requirement IDs from the plan's frontmatter (e.g., `requirements: [AUTH-
514
518
  **Shared-ID gate (#2388):** a requirement ID declared by more than one plan in this phase must not read `Complete` until every plan declaring it has finished (produced a `*-SUMMARY.md`) — otherwise the first plan to finish flips it `Complete` while its sibling plans are still running, before phase verification ever gets a chance to catch a real gap. Compute the ready subset first, then mark only those:
515
519
 
516
520
  ```bash
517
- READY=$(gsd_run query requirements.ready-ids "${PLAN_PATH}" ${REQ_IDS} --raw)
521
+ READY=$(gsd_run query requirements.ready-ids "${PLAN_PATH}" ${REQ_IDS})
518
522
  READY_IDS=$(printf '%s' "$READY" | jq -r '.ready[]' 2>/dev/null | tr '\n' ' ')
519
523
  if [ -n "$(printf '%s' "$READY_IDS" | tr -d '[:space:]')" ]; then
520
524
  gsd_run query requirements.mark-complete ${READY_IDS}
521
525
  fi
522
526
  ```
523
527
 
524
- `requirements.ready-ids` is read-only: it scans sibling `*-PLAN.md` files in this plan's phase directory and blocks an ID only when a sibling ALSO declares it and that sibling has no `*-SUMMARY.md` yet. An ID no sibling declares is always ready (single-plan requirements mark immediately, no added latency). A blocked ID is re-evaluated the next time any plan in this phase finishes its own `update_requirements` step, and becomes ready once the LAST declaring plan's SUMMARY exists.
528
+ `requirements.ready-ids` is read-only and prints JSON by default (`--raw` prints a prose summary `jq` cannot read, so the ready list would always be empty, #4956): it scans sibling `*-PLAN.md` files in this plan's phase directory and blocks an ID only when a sibling ALSO declares it and that sibling has no `*-SUMMARY.md` yet. An ID no sibling declares is always ready (single-plan requirements mark immediately, no added latency). A blocked ID is re-evaluated the next time any plan in this phase finishes its own `update_requirements` step, and becomes ready once the LAST declaring plan's SUMMARY exists.
525
529
  </step>
526
530
 
527
531
  <step name="git_commit_metadata">
@@ -18,9 +18,13 @@ Analyze completed phase artifacts (PLAN.md, SUMMARY.md, VERIFICATION.md, UAT.md,
18
18
  Parse arguments and load project state:
19
19
 
20
20
  ```bash
21
+ IFS= read -rd '' GSD_ARGS <<'GSD_ARGS_END' || true
22
+ $ARGUMENTS
23
+ GSD_ARGS_END
24
+ GSD_ARGS=${GSD_ARGS%$'\n'}
21
25
  _GSD_SHIM_NAME="gsd-tools.cjs"; _GSD_RUNTIME_ROOT="${RUNTIME_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"; GSD_TOOLS="${_GSD_RUNTIME_ROOT}/gsd-core/bin/${_GSD_SHIM_NAME}"; _gsd_at() { for _p; do if [ -f "$_p" ]; then GSD_TOOLS="$_p"; return 0; fi; done; return 1; }; _gsd_id_ok() { case "$("$1" runtime-identity --raw 2>/dev/null || true)" in '{"packageName":"@opengsd/gsd-core"'*'}') return 0;; *) return 1;; esac; }; _gsd_homes() { set -- "${CLAUDE_CONFIG_DIR:-$HOME/.claude}" "${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}" "$HOME/.gemini/antigravity-ide" "$HOME/.gemini/antigravity-cli" "${AUGMENT_CONFIG_DIR:-$HOME/.augment}" "${CLINE_CONFIG_DIR:-$HOME/.cline}" "${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}" "${CODEX_HOME:-$HOME/.codex}" "${COPILOT_CONFIG_DIR:-${COPILOT_HOME:-$HOME/.copilot}}" "${CURSOR_CONFIG_DIR:-$HOME/.cursor}" "${HERMES_HOME:-$HOME/.hermes}" "${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}" "${KIMI_CONFIG_DIR:-$HOME/.config/agents}" "$HOME/.agents" "${KIMI_CODE_HOME:-$HOME/.kimi-code}" "${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}" "${PI_CODING_AGENT_DIR:-$HOME/.pi/agent}" "${QWEN_CONFIG_DIR:-$HOME/.qwen}" "${TRAE_CONFIG_DIR:-$HOME/.trae}" "${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}" "${ZCODE_CONFIG_DIR:-$HOME/.zcode}" "${GROK_AGENTS_HOME:-$HOME/.agents}"; for _h; do _gsd_at "$_h/gsd-core/bin/${_GSD_SHIM_NAME}" && return 0; done; return 1; }; if _gsd_at "${_GSD_RUNTIME_ROOT}/gsd-core/bin/${_GSD_SHIM_NAME}" "${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" "${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}"; then gsd_run() { node "$GSD_TOOLS" "$@"; }; elif _gsd_homes; then gsd_run() { node "$GSD_TOOLS" "$@"; }; elif unset -f gsd_run; _G="$(command -v gsd_run)"; [ -n "$_G" ] && _gsd_id_ok "$_G"; then GSD_TOOLS="$_G"; gsd_run() { "$GSD_TOOLS" "$@"; }; else echo "ERROR: gsd-tools.cjs not found at $GSD_TOOLS and no identity-proving gsd_run is on PATH. Run: npx -y @opengsd/gsd-core@latest --claude --local" >&2; exit 1; fi; GSD_IDENTITY_STATUS=unverified; _gsd_id_ok gsd_run && GSD_IDENTITY_STATUS=ok; export GSD_IDENTITY_STATUS; [ "$GSD_IDENTITY_STATUS" = ok ] || echo "WARNING: \"$GSD_TOOLS\" did not prove it is @opengsd/gsd-core - it is either a different package or an @opengsd/gsd-core older than the runtime-identity verb. See docs/how-to/diagnose-a-foreign-gsd-tools.md" >&2; if [ -n "${CLAUDE_ENV_FILE:-}" ] && [ -n "${GSD_TOOLS:-}" ]; then printf "export PATH='%s':\"\$PATH\"\n" "${GSD_TOOLS%/*}" >> "$CLAUDE_ENV_FILE" 2>/dev/null || true; fi
22
- PHASE_ARG=$(echo "$ARGUMENTS" | sed -E 's/--ws[[:space:]]+[A-Za-z0-9._-]+//g' | xargs)
23
- GSD_WS=$(echo " $ARGUMENTS" | sed -nE 's/.* --ws +([A-Za-z0-9][A-Za-z0-9._-]*).*/--ws \1/p' | head -n 1)
26
+ PHASE_ARG=$(echo "$GSD_ARGS" | sed -E 's/--ws[[:space:]]+[A-Za-z0-9._-]+//g' | xargs)
27
+ GSD_WS=$(echo " $GSD_ARGS" | sed -nE 's/.* --ws +([A-Za-z0-9][A-Za-z0-9._-]*).*/--ws \1/p' | head -n 1)
24
28
  INIT=$(gsd_run query init.phase-op ${GSD_WS:+--ws=${GSD_WS##* }} "${PHASE_ARG}")
25
29
  if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
26
30
  ```
@@ -14,7 +14,11 @@ Only write the forensic report.
14
14
  ## Step 1: Get Problem Description
15
15
 
16
16
  ```bash
17
- PROBLEM="$ARGUMENTS"
17
+ IFS= read -rd '' GSD_ARGS <<'GSD_ARGS_END' || true
18
+ $ARGUMENTS
19
+ GSD_ARGS_END
20
+ GSD_ARGS=${GSD_ARGS%$'\n'}
21
+ PROBLEM="$GSD_ARGS"
18
22
  ```
19
23
 
20
24
  If `$ARGUMENTS` is empty, ask the user:
@@ -178,7 +178,11 @@ Apply GSD naming convention for the output filename:
178
178
  Determine the target directory by querying `init.phase-op` for the phase number extracted in `plan_read_input`. This ensures the `project_code` prefix from `.planning/config.json` is applied:
179
179
 
180
180
  ```bash
181
- GSD_WS=$(echo " $ARGUMENTS" | sed -nE 's/.* --ws +([A-Za-z0-9][A-Za-z0-9._-]*).*/--ws \1/p' | head -n 1)
181
+ IFS= read -rd '' GSD_ARGS <<'GSD_ARGS_END' || true
182
+ $ARGUMENTS
183
+ GSD_ARGS_END
184
+ GSD_ARGS=${GSD_ARGS%$'\n'}
185
+ GSD_WS=$(echo " $GSD_ARGS" | sed -nE 's/.* --ws +([A-Za-z0-9][A-Za-z0-9._-]*).*/--ws \1/p' | head -n 1)
182
186
  INIT=$(gsd_run query init.phase-op ${GSD_WS:+--ws=${GSD_WS##* }} "{NN}")
183
187
  if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
184
188
  expected_phase_dir=$(echo "$INIT" | node -e "process.stdout.write(JSON.parse(require('fs').readFileSync('/dev/stdin','utf8')).expected_phase_dir)")
@@ -36,8 +36,12 @@ Validate first argument is an integer.
36
36
  Load phase operation context:
37
37
 
38
38
  ```bash
39
+ IFS= read -rd '' GSD_ARGS <<'GSD_ARGS_END' || true
40
+ $ARGUMENTS
41
+ GSD_ARGS_END
42
+ GSD_ARGS=${GSD_ARGS%$'\n'}
39
43
  _GSD_SHIM_NAME="gsd-tools.cjs"; _GSD_RUNTIME_ROOT="${RUNTIME_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"; GSD_TOOLS="${_GSD_RUNTIME_ROOT}/gsd-core/bin/${_GSD_SHIM_NAME}"; _gsd_at() { for _p; do if [ -f "$_p" ]; then GSD_TOOLS="$_p"; return 0; fi; done; return 1; }; _gsd_id_ok() { case "$("$1" runtime-identity --raw 2>/dev/null || true)" in '{"packageName":"@opengsd/gsd-core"'*'}') return 0;; *) return 1;; esac; }; _gsd_homes() { set -- "${CLAUDE_CONFIG_DIR:-$HOME/.claude}" "${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}" "$HOME/.gemini/antigravity-ide" "$HOME/.gemini/antigravity-cli" "${AUGMENT_CONFIG_DIR:-$HOME/.augment}" "${CLINE_CONFIG_DIR:-$HOME/.cline}" "${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}" "${CODEX_HOME:-$HOME/.codex}" "${COPILOT_CONFIG_DIR:-${COPILOT_HOME:-$HOME/.copilot}}" "${CURSOR_CONFIG_DIR:-$HOME/.cursor}" "${HERMES_HOME:-$HOME/.hermes}" "${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}" "${KIMI_CONFIG_DIR:-$HOME/.config/agents}" "$HOME/.agents" "${KIMI_CODE_HOME:-$HOME/.kimi-code}" "${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}" "${PI_CODING_AGENT_DIR:-$HOME/.pi/agent}" "${QWEN_CONFIG_DIR:-$HOME/.qwen}" "${TRAE_CONFIG_DIR:-$HOME/.trae}" "${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}" "${ZCODE_CONFIG_DIR:-$HOME/.zcode}" "${GROK_AGENTS_HOME:-$HOME/.agents}"; for _h; do _gsd_at "$_h/gsd-core/bin/${_GSD_SHIM_NAME}" && return 0; done; return 1; }; if _gsd_at "${_GSD_RUNTIME_ROOT}/gsd-core/bin/${_GSD_SHIM_NAME}" "${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" "${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}"; then gsd_run() { node "$GSD_TOOLS" "$@"; }; elif _gsd_homes; then gsd_run() { node "$GSD_TOOLS" "$@"; }; elif unset -f gsd_run; _G="$(command -v gsd_run)"; [ -n "$_G" ] && _gsd_id_ok "$_G"; then GSD_TOOLS="$_G"; gsd_run() { "$GSD_TOOLS" "$@"; }; else echo "ERROR: gsd-tools.cjs not found at $GSD_TOOLS and no identity-proving gsd_run is on PATH. Run: npx -y @opengsd/gsd-core@latest --claude --local" >&2; exit 1; fi; GSD_IDENTITY_STATUS=unverified; _gsd_id_ok gsd_run && GSD_IDENTITY_STATUS=ok; export GSD_IDENTITY_STATUS; [ "$GSD_IDENTITY_STATUS" = ok ] || echo "WARNING: \"$GSD_TOOLS\" did not prove it is @opengsd/gsd-core - it is either a different package or an @opengsd/gsd-core older than the runtime-identity verb. See docs/how-to/diagnose-a-foreign-gsd-tools.md" >&2; if [ -n "${CLAUDE_ENV_FILE:-}" ] && [ -n "${GSD_TOOLS:-}" ]; then printf "export PATH='%s':\"\$PATH\"\n" "${GSD_TOOLS%/*}" >> "$CLAUDE_ENV_FILE" 2>/dev/null || true; fi
40
- GSD_WS=$(echo " $ARGUMENTS" | sed -nE 's/.* --ws +([A-Za-z0-9][A-Za-z0-9._-]*).*/--ws \1/p' | head -n 1)
44
+ GSD_WS=$(echo " $GSD_ARGS" | sed -nE 's/.* --ws +([A-Za-z0-9][A-Za-z0-9._-]*).*/--ws \1/p' | head -n 1)
41
45
  INIT=$(gsd_run query init.phase-op ${GSD_WS:+--ws=${GSD_WS##* }} "${after_phase}")
42
46
  if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
43
47
  ```
@@ -19,8 +19,12 @@ Read all files referenced by the invoking prompt's execution_context before star
19
19
  Bootstrap via manager init:
20
20
 
21
21
  ```bash
22
+ IFS= read -rd '' GSD_ARGS <<'GSD_ARGS_END' || true
23
+ $ARGUMENTS
24
+ GSD_ARGS_END
25
+ GSD_ARGS=${GSD_ARGS%$'\n'}
22
26
  _GSD_SHIM_NAME="gsd-tools.cjs"; _GSD_RUNTIME_ROOT="${RUNTIME_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"; GSD_TOOLS="${_GSD_RUNTIME_ROOT}/gsd-core/bin/${_GSD_SHIM_NAME}"; _gsd_at() { for _p; do if [ -f "$_p" ]; then GSD_TOOLS="$_p"; return 0; fi; done; return 1; }; _gsd_id_ok() { case "$("$1" runtime-identity --raw 2>/dev/null || true)" in '{"packageName":"@opengsd/gsd-core"'*'}') return 0;; *) return 1;; esac; }; _gsd_homes() { set -- "${CLAUDE_CONFIG_DIR:-$HOME/.claude}" "${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}" "$HOME/.gemini/antigravity-ide" "$HOME/.gemini/antigravity-cli" "${AUGMENT_CONFIG_DIR:-$HOME/.augment}" "${CLINE_CONFIG_DIR:-$HOME/.cline}" "${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}" "${CODEX_HOME:-$HOME/.codex}" "${COPILOT_CONFIG_DIR:-${COPILOT_HOME:-$HOME/.copilot}}" "${CURSOR_CONFIG_DIR:-$HOME/.cursor}" "${HERMES_HOME:-$HOME/.hermes}" "${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}" "${KIMI_CONFIG_DIR:-$HOME/.config/agents}" "$HOME/.agents" "${KIMI_CODE_HOME:-$HOME/.kimi-code}" "${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}" "${PI_CODING_AGENT_DIR:-$HOME/.pi/agent}" "${QWEN_CONFIG_DIR:-$HOME/.qwen}" "${TRAE_CONFIG_DIR:-$HOME/.trae}" "${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}" "${ZCODE_CONFIG_DIR:-$HOME/.zcode}" "${GROK_AGENTS_HOME:-$HOME/.agents}"; for _h; do _gsd_at "$_h/gsd-core/bin/${_GSD_SHIM_NAME}" && return 0; done; return 1; }; if _gsd_at "${_GSD_RUNTIME_ROOT}/gsd-core/bin/${_GSD_SHIM_NAME}" "${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" "${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}"; then gsd_run() { node "$GSD_TOOLS" "$@"; }; elif _gsd_homes; then gsd_run() { node "$GSD_TOOLS" "$@"; }; elif unset -f gsd_run; _G="$(command -v gsd_run)"; [ -n "$_G" ] && _gsd_id_ok "$_G"; then GSD_TOOLS="$_G"; gsd_run() { "$GSD_TOOLS" "$@"; }; else echo "ERROR: gsd-tools.cjs not found at $GSD_TOOLS and no identity-proving gsd_run is on PATH. Run: npx -y @opengsd/gsd-core@latest --claude --local" >&2; exit 1; fi; GSD_IDENTITY_STATUS=unverified; _gsd_id_ok gsd_run && GSD_IDENTITY_STATUS=ok; export GSD_IDENTITY_STATUS; [ "$GSD_IDENTITY_STATUS" = ok ] || echo "WARNING: \"$GSD_TOOLS\" did not prove it is @opengsd/gsd-core - it is either a different package or an @opengsd/gsd-core older than the runtime-identity verb. See docs/how-to/diagnose-a-foreign-gsd-tools.md" >&2; if [ -n "${CLAUDE_ENV_FILE:-}" ] && [ -n "${GSD_TOOLS:-}" ]; then printf "export PATH='%s':\"\$PATH\"\n" "${GSD_TOOLS%/*}" >> "$CLAUDE_ENV_FILE" 2>/dev/null || true; fi
23
- GSD_WS=$(echo " $ARGUMENTS" | sed -nE 's/.* --ws +([A-Za-z0-9][A-Za-z0-9._-]*).*/--ws \1/p' | head -n 1)
27
+ GSD_WS=$(echo " $GSD_ARGS" | sed -nE 's/.* --ws +([A-Za-z0-9][A-Za-z0-9._-]*).*/--ws \1/p' | head -n 1)
24
28
  INIT=$(gsd_run query init.manager ${GSD_WS:+--ws=${GSD_WS##* }})
25
29
  if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
26
30
  ```
@@ -63,7 +67,11 @@ Proceed to dashboard step.
63
67
  **Every time this step is reached**, re-read state from disk to pick up changes from background agents:
64
68
 
65
69
  ```bash
66
- GSD_WS=$(echo " $ARGUMENTS" | sed -nE 's/.* --ws +([A-Za-z0-9][A-Za-z0-9._-]*).*/--ws \1/p' | head -n 1)
70
+ IFS= read -rd '' GSD_ARGS <<'GSD_ARGS_END' || true
71
+ $ARGUMENTS
72
+ GSD_ARGS_END
73
+ GSD_ARGS=${GSD_ARGS%$'\n'}
74
+ GSD_WS=$(echo " $GSD_ARGS" | sed -nE 's/.* --ws +([A-Za-z0-9][A-Za-z0-9._-]*).*/--ws \1/p' | head -n 1)
67
75
  INIT=$(gsd_run query init.manager ${GSD_WS:+--ws=${GSD_WS##* }})
68
76
  if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
69
77
  ```
@@ -72,8 +72,12 @@ documents refreshed.
72
72
  Load codebase mapping context:
73
73
 
74
74
  ```bash
75
+ IFS= read -rd '' GSD_ARGS <<'GSD_ARGS_END' || true
76
+ $ARGUMENTS
77
+ GSD_ARGS_END
78
+ GSD_ARGS=${GSD_ARGS%$'\n'}
75
79
  _GSD_SHIM_NAME="gsd-tools.cjs"; _GSD_RUNTIME_ROOT="${RUNTIME_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"; GSD_TOOLS="${_GSD_RUNTIME_ROOT}/gsd-core/bin/${_GSD_SHIM_NAME}"; _gsd_at() { for _p; do if [ -f "$_p" ]; then GSD_TOOLS="$_p"; return 0; fi; done; return 1; }; _gsd_id_ok() { case "$("$1" runtime-identity --raw 2>/dev/null || true)" in '{"packageName":"@opengsd/gsd-core"'*'}') return 0;; *) return 1;; esac; }; _gsd_homes() { set -- "${CLAUDE_CONFIG_DIR:-$HOME/.claude}" "${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}" "$HOME/.gemini/antigravity-ide" "$HOME/.gemini/antigravity-cli" "${AUGMENT_CONFIG_DIR:-$HOME/.augment}" "${CLINE_CONFIG_DIR:-$HOME/.cline}" "${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}" "${CODEX_HOME:-$HOME/.codex}" "${COPILOT_CONFIG_DIR:-${COPILOT_HOME:-$HOME/.copilot}}" "${CURSOR_CONFIG_DIR:-$HOME/.cursor}" "${HERMES_HOME:-$HOME/.hermes}" "${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}" "${KIMI_CONFIG_DIR:-$HOME/.config/agents}" "$HOME/.agents" "${KIMI_CODE_HOME:-$HOME/.kimi-code}" "${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}" "${PI_CODING_AGENT_DIR:-$HOME/.pi/agent}" "${QWEN_CONFIG_DIR:-$HOME/.qwen}" "${TRAE_CONFIG_DIR:-$HOME/.trae}" "${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}" "${ZCODE_CONFIG_DIR:-$HOME/.zcode}" "${GROK_AGENTS_HOME:-$HOME/.agents}"; for _h; do _gsd_at "$_h/gsd-core/bin/${_GSD_SHIM_NAME}" && return 0; done; return 1; }; if _gsd_at "${_GSD_RUNTIME_ROOT}/gsd-core/bin/${_GSD_SHIM_NAME}" "${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" "${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}"; then gsd_run() { node "$GSD_TOOLS" "$@"; }; elif _gsd_homes; then gsd_run() { node "$GSD_TOOLS" "$@"; }; elif unset -f gsd_run; _G="$(command -v gsd_run)"; [ -n "$_G" ] && _gsd_id_ok "$_G"; then GSD_TOOLS="$_G"; gsd_run() { "$GSD_TOOLS" "$@"; }; else echo "ERROR: gsd-tools.cjs not found at $GSD_TOOLS and no identity-proving gsd_run is on PATH. Run: npx -y @opengsd/gsd-core@latest --claude --local" >&2; exit 1; fi; GSD_IDENTITY_STATUS=unverified; _gsd_id_ok gsd_run && GSD_IDENTITY_STATUS=ok; export GSD_IDENTITY_STATUS; [ "$GSD_IDENTITY_STATUS" = ok ] || echo "WARNING: \"$GSD_TOOLS\" did not prove it is @opengsd/gsd-core - it is either a different package or an @opengsd/gsd-core older than the runtime-identity verb. See docs/how-to/diagnose-a-foreign-gsd-tools.md" >&2; if [ -n "${CLAUDE_ENV_FILE:-}" ] && [ -n "${GSD_TOOLS:-}" ]; then printf "export PATH='%s':\"\$PATH\"\n" "${GSD_TOOLS%/*}" >> "$CLAUDE_ENV_FILE" 2>/dev/null || true; fi
76
- GSD_WS=$(echo " $ARGUMENTS" | sed -nE 's/.* --ws +([A-Za-z0-9][A-Za-z0-9._-]*).*/--ws \1/p' | head -n 1)
80
+ GSD_WS=$(echo " $GSD_ARGS" | sed -nE 's/.* --ws +([A-Za-z0-9][A-Za-z0-9._-]*).*/--ws \1/p' | head -n 1)
77
81
  INIT=$(gsd_run query init.map-codebase ${GSD_WS:+--ws=${GSD_WS##* }})
78
82
  if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
79
83
  AGENT_SKILLS_MAPPER=$(gsd_run query agent-skills gsd-codebase-mapper ${GSD_WS:+--ws=${GSD_WS##* }})
@@ -10,7 +10,11 @@ Designed for team onboarding — a new contributor can read the output and under
10
10
  ## Step 1: Resolve Version
11
11
 
12
12
  ```bash
13
- VERSION="$ARGUMENTS"
13
+ IFS= read -rd '' GSD_ARGS <<'GSD_ARGS_END' || true
14
+ $ARGUMENTS
15
+ GSD_ARGS_END
16
+ GSD_ARGS=${GSD_ARGS%$'\n'}
17
+ VERSION="$GSD_ARGS"
14
18
  ```
15
19
 
16
20
  If `$ARGUMENTS` is empty:
@@ -55,8 +59,12 @@ Read all files that exist. Missing files are fine — the summary adapts to what
55
59
  Find all phase directories:
56
60
 
57
61
  ```bash
62
+ IFS= read -rd '' GSD_ARGS <<'GSD_ARGS_END' || true
63
+ $ARGUMENTS
64
+ GSD_ARGS_END
65
+ GSD_ARGS=${GSD_ARGS%$'\n'}
58
66
  _GSD_SHIM_NAME="gsd-tools.cjs"; _GSD_RUNTIME_ROOT="${RUNTIME_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"; GSD_TOOLS="${_GSD_RUNTIME_ROOT}/gsd-core/bin/${_GSD_SHIM_NAME}"; _gsd_at() { for _p; do if [ -f "$_p" ]; then GSD_TOOLS="$_p"; return 0; fi; done; return 1; }; _gsd_id_ok() { case "$("$1" runtime-identity --raw 2>/dev/null || true)" in '{"packageName":"@opengsd/gsd-core"'*'}') return 0;; *) return 1;; esac; }; _gsd_homes() { set -- "${CLAUDE_CONFIG_DIR:-$HOME/.claude}" "${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}" "$HOME/.gemini/antigravity-ide" "$HOME/.gemini/antigravity-cli" "${AUGMENT_CONFIG_DIR:-$HOME/.augment}" "${CLINE_CONFIG_DIR:-$HOME/.cline}" "${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}" "${CODEX_HOME:-$HOME/.codex}" "${COPILOT_CONFIG_DIR:-${COPILOT_HOME:-$HOME/.copilot}}" "${CURSOR_CONFIG_DIR:-$HOME/.cursor}" "${HERMES_HOME:-$HOME/.hermes}" "${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}" "${KIMI_CONFIG_DIR:-$HOME/.config/agents}" "$HOME/.agents" "${KIMI_CODE_HOME:-$HOME/.kimi-code}" "${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}" "${PI_CODING_AGENT_DIR:-$HOME/.pi/agent}" "${QWEN_CONFIG_DIR:-$HOME/.qwen}" "${TRAE_CONFIG_DIR:-$HOME/.trae}" "${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}" "${ZCODE_CONFIG_DIR:-$HOME/.zcode}" "${GROK_AGENTS_HOME:-$HOME/.agents}"; for _h; do _gsd_at "$_h/gsd-core/bin/${_GSD_SHIM_NAME}" && return 0; done; return 1; }; if _gsd_at "${_GSD_RUNTIME_ROOT}/gsd-core/bin/${_GSD_SHIM_NAME}" "${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" "${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}"; then gsd_run() { node "$GSD_TOOLS" "$@"; }; elif _gsd_homes; then gsd_run() { node "$GSD_TOOLS" "$@"; }; elif unset -f gsd_run; _G="$(command -v gsd_run)"; [ -n "$_G" ] && _gsd_id_ok "$_G"; then GSD_TOOLS="$_G"; gsd_run() { "$GSD_TOOLS" "$@"; }; else echo "ERROR: gsd-tools.cjs not found at $GSD_TOOLS and no identity-proving gsd_run is on PATH. Run: npx -y @opengsd/gsd-core@latest --claude --local" >&2; exit 1; fi; GSD_IDENTITY_STATUS=unverified; _gsd_id_ok gsd_run && GSD_IDENTITY_STATUS=ok; export GSD_IDENTITY_STATUS; [ "$GSD_IDENTITY_STATUS" = ok ] || echo "WARNING: \"$GSD_TOOLS\" did not prove it is @opengsd/gsd-core - it is either a different package or an @opengsd/gsd-core older than the runtime-identity verb. See docs/how-to/diagnose-a-foreign-gsd-tools.md" >&2; if [ -n "${CLAUDE_ENV_FILE:-}" ] && [ -n "${GSD_TOOLS:-}" ]; then printf "export PATH='%s':\"\$PATH\"\n" "${GSD_TOOLS%/*}" >> "$CLAUDE_ENV_FILE" 2>/dev/null || true; fi
59
- GSD_WS=$(echo " $ARGUMENTS" | sed -nE 's/.* --ws +([A-Za-z0-9][A-Za-z0-9._-]*).*/--ws \1/p' | head -n 1)
67
+ GSD_WS=$(echo " $GSD_ARGS" | sed -nE 's/.* --ws +([A-Za-z0-9][A-Za-z0-9._-]*).*/--ws \1/p' | head -n 1)
60
68
  gsd_run query init.progress ${GSD_WS:+--ws=${GSD_WS##* }}
61
69
  ```
62
70
 
@@ -30,10 +30,14 @@ Parse `$ARGUMENTS` before doing anything else:
30
30
  Parse `GSD_WS` and `MILESTONE_ARG` using the established idiom (see `verify-work.md`):
31
31
 
32
32
  ```bash
33
+ IFS= read -rd '' GSD_ARGS <<'GSD_ARGS_END' || true
34
+ $ARGUMENTS
35
+ GSD_ARGS_END
36
+ GSD_ARGS=${GSD_ARGS%$'\n'}
33
37
  _GSD_SHIM_NAME="gsd-tools.cjs"; _GSD_RUNTIME_ROOT="${RUNTIME_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"; GSD_TOOLS="${_GSD_RUNTIME_ROOT}/gsd-core/bin/${_GSD_SHIM_NAME}"; _gsd_at() { for _p; do if [ -f "$_p" ]; then GSD_TOOLS="$_p"; return 0; fi; done; return 1; }; _gsd_id_ok() { case "$("$1" runtime-identity --raw 2>/dev/null || true)" in '{"packageName":"@opengsd/gsd-core"'*'}') return 0;; *) return 1;; esac; }; _gsd_homes() { set -- "${CLAUDE_CONFIG_DIR:-$HOME/.claude}" "${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}" "$HOME/.gemini/antigravity-ide" "$HOME/.gemini/antigravity-cli" "${AUGMENT_CONFIG_DIR:-$HOME/.augment}" "${CLINE_CONFIG_DIR:-$HOME/.cline}" "${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}" "${CODEX_HOME:-$HOME/.codex}" "${COPILOT_CONFIG_DIR:-${COPILOT_HOME:-$HOME/.copilot}}" "${CURSOR_CONFIG_DIR:-$HOME/.cursor}" "${HERMES_HOME:-$HOME/.hermes}" "${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}" "${KIMI_CONFIG_DIR:-$HOME/.config/agents}" "$HOME/.agents" "${KIMI_CODE_HOME:-$HOME/.kimi-code}" "${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}" "${PI_CODING_AGENT_DIR:-$HOME/.pi/agent}" "${QWEN_CONFIG_DIR:-$HOME/.qwen}" "${TRAE_CONFIG_DIR:-$HOME/.trae}" "${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}" "${ZCODE_CONFIG_DIR:-$HOME/.zcode}" "${GROK_AGENTS_HOME:-$HOME/.agents}"; for _h; do _gsd_at "$_h/gsd-core/bin/${_GSD_SHIM_NAME}" && return 0; done; return 1; }; if _gsd_at "${_GSD_RUNTIME_ROOT}/gsd-core/bin/${_GSD_SHIM_NAME}" "${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" "${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}"; then gsd_run() { node "$GSD_TOOLS" "$@"; }; elif _gsd_homes; then gsd_run() { node "$GSD_TOOLS" "$@"; }; elif unset -f gsd_run; _G="$(command -v gsd_run)"; [ -n "$_G" ] && _gsd_id_ok "$_G"; then GSD_TOOLS="$_G"; gsd_run() { "$GSD_TOOLS" "$@"; }; else echo "ERROR: gsd-tools.cjs not found at $GSD_TOOLS and no identity-proving gsd_run is on PATH. Run: npx -y @opengsd/gsd-core@latest --claude --local" >&2; exit 1; fi; GSD_IDENTITY_STATUS=unverified; _gsd_id_ok gsd_run && GSD_IDENTITY_STATUS=ok; export GSD_IDENTITY_STATUS; [ "$GSD_IDENTITY_STATUS" = ok ] || echo "WARNING: \"$GSD_TOOLS\" did not prove it is @opengsd/gsd-core - it is either a different package or an @opengsd/gsd-core older than the runtime-identity verb. See docs/how-to/diagnose-a-foreign-gsd-tools.md" >&2; if [ -n "${CLAUDE_ENV_FILE:-}" ] && [ -n "${GSD_TOOLS:-}" ]; then printf "export PATH='%s':\"\$PATH\"\n" "${GSD_TOOLS%/*}" >> "$CLAUDE_ENV_FILE" 2>/dev/null || true; fi
34
38
  GSD_WS=""
35
- echo "$ARGUMENTS" | grep -qE -- '--ws[[:space:]]+[A-Za-z0-9._-]+' && GSD_WS=$(echo "$ARGUMENTS" | grep -oE -- '--ws[[:space:]]+[A-Za-z0-9._-]+')
36
- MILESTONE_ARG=$(echo "$ARGUMENTS" | sed -E 's/--ws[[:space:]]+[A-Za-z0-9._-]+//g' | xargs)
39
+ echo "$GSD_ARGS" | grep -qE -- '--ws[[:space:]]+[A-Za-z0-9._-]+' && GSD_WS=$(echo "$GSD_ARGS" | grep -oE -- '--ws[[:space:]]+[A-Za-z0-9._-]+')
40
+ MILESTONE_ARG=$(echo "$GSD_ARGS" | sed -E 's/--ws[[:space:]]+[A-Za-z0-9._-]+//g' | xargs)
37
41
  # #4456: persist GSD_WS to a file so later steps' bash fences (each a
38
42
  # separate shell) can forward it — the same cross-fence problem Step 5/6
39
43
  # already solve for OUTGOING_MILESTONE via .gsd-outgoing-milestone.
@@ -296,7 +300,11 @@ gsd_run query commit "docs: start milestone v[X.Y] [Name]" --files "$PROJECT_PAT
296
300
  ## 7. Load Context and Resolve Models
297
301
 
298
302
  ```bash
299
- RESET_PHASE_NUMBERS_PARAM=""; if [[ "$ARGUMENTS" =~ (^|[[:space:]])--reset-phase-numbers([[:space:]]|$) ]]; then RESET_PHASE_NUMBERS_PARAM="--reset-phase-numbers"; fi
303
+ IFS= read -rd '' GSD_ARGS <<'GSD_ARGS_END' || true
304
+ $ARGUMENTS
305
+ GSD_ARGS_END
306
+ GSD_ARGS=${GSD_ARGS%$'\n'}
307
+ RESET_PHASE_NUMBERS_PARAM=""; if [[ "$GSD_ARGS" =~ (^|[[:space:]])--reset-phase-numbers([[:space:]]|$) ]]; then RESET_PHASE_NUMBERS_PARAM="--reset-phase-numbers"; fi
300
308
  GSD_WS_ARG=$(cat .planning/.gsd-ws-arg 2>/dev/null || true)
301
309
  INIT=$(gsd_run query init.new-milestone $RESET_PHASE_NUMBERS_PARAM ${GSD_WS_ARG:+--ws=${GSD_WS_ARG##* }})
302
310
  if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
@@ -435,10 +443,10 @@ Commit after writing.
435
443
 
436
444
  > **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.
437
445
 
438
- **Synthesizer output self-heal (#222) — verify SUMMARY.md materialized:** The synthesizer's canonical output is `.planning/research/SUMMARY.md` on disk; its brief structured return (`## SYNTHESIS COMPLETE` plus a few `###` confirmation lines) is NOT the file content. A known LLM false-refusal (issue #222) sometimes makes the agent return the full SUMMARY.md document inline — fabricating a write restriction (e.g. "the runtime is blocking file writes") — instead of writing the file. Prompt hardening alone does not fully eliminate it, so the orchestrator MUST absorb the failure deterministically before spawning `gsd-roadmapper`:
446
+ **Synthesizer output self-heal (#222) — verify SUMMARY.md materialized:** The synthesizer's canonical output is `.planning/research/SUMMARY.md` on disk; its brief structured return (`## SYNTHESIS COMPLETE` plus a few `###` confirmation lines) is NOT the file content. The restriction is not always fabricated: Claude Code refuses a subagent's `Write` to a few exact basenames — `SUMMARY.md` among them — with `Subagents should return findings as text, not write report files` (observed on Claude Code 2.1.301), and the synthesizer is told to return the full document inline when its `Write` hits that error; the refusal is real, not fabricated. It depends on the exact basename (`SUMMARY.md`, `REPORT.md`, `FINDINGS.md`, `ANALYSIS.md`, case-insensitive; a prefixed name such as `01-01-SUMMARY.md` and other agents' files write fine), so do not tell other agents to avoid `Write`. Either cause lands in step 2. A known LLM false-refusal (issue #222) sometimes makes the agent return the full SUMMARY.md document inline — fabricating a write restriction (e.g. "the runtime is blocking file writes") — instead of writing the file. Prompt hardening alone does not fully eliminate it, so the orchestrator MUST absorb either cause deterministically before spawning `gsd-roadmapper`:
439
447
 
440
448
  1. Verify `.planning/research/SUMMARY.md` exists AND is substantive — non-empty, and free of any leftover `<!-- gsd:write-continue -->` continuation sentinel (which marks a truncated/incomplete write). You may validate with `gsd_run verify-summary .planning/research/SUMMARY.md` — it exits 0 regardless, so check its JSON `passed` field (`"passed": false` means missing or invalid), not the process exit code. If it passes, continue normally.
441
- 2. If it is MISSING or invalid AND the synthesizer's return message contains the FULL SUMMARY.md document — recognizable by the template's top-level markers `# Project Research Summary`, `## Key Findings`, `## Implications for Roadmap`, and `## Sources`, not merely the brief `## SYNTHESIS COMPLETE` confirmation — the false-refusal fired: write that returned document to `.planning/research/SUMMARY.md` with the Write tool, then commit ALL research artifacts the synthesizer owns (it commits on behalf of the four researchers) with `gsd_run query commit "docs: complete project research" --files .planning/research/` unless they are already committed. Log `⚠ #222 self-heal: synthesizer returned SUMMARY.md inline without writing it; orchestrator persisted the file.`
449
+ 2. If it is MISSING or invalid AND the synthesizer's return message contains the FULL SUMMARY.md document — recognizable by the template's top-level markers `# Project Research Summary`, `## Key Findings`, `## Implications for Roadmap`, and `## Sources`, not merely the brief `## SYNTHESIS COMPLETE` confirmation — the synthesizer returned the document inline (the #222 false-refusal, or the runtime's real refusal of the name `SUMMARY.md`): write that returned document to `.planning/research/SUMMARY.md` with the Write tool, then commit ALL research artifacts the synthesizer owns (it commits on behalf of the four researchers) with `gsd_run query commit "docs: complete project research" --files .planning/research/` unless they are already committed. Log `⚠ #222 self-heal: synthesizer returned SUMMARY.md inline without writing it; orchestrator persisted the file.`
442
450
  3. If it is MISSING or invalid AND the return is only a brief confirmation (no full SUMMARY document to recover), the synthesizer genuinely failed — surface the error and stop; do NOT spawn `gsd-roadmapper` against a missing or incomplete SUMMARY.md.
443
451
 
444
452
  This guarantees `gsd-roadmapper` (which lists SUMMARY.md as required reading) never runs against a missing or truncated SUMMARY.md.
@@ -30,8 +30,12 @@ If `section_manifest` is `null` or `"auto-mode-detection"` is in its `included`
30
30
  **MANDATORY FIRST STEP — Execute these checks before ANY user interaction:**
31
31
 
32
32
  ```bash
33
+ IFS= read -rd '' GSD_ARGS <<'GSD_ARGS_END' || true
34
+ $ARGUMENTS
35
+ GSD_ARGS_END
36
+ GSD_ARGS=${GSD_ARGS%$'\n'}
33
37
  _GSD_SHIM_NAME="gsd-tools.cjs"; _GSD_RUNTIME_ROOT="${RUNTIME_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"; GSD_TOOLS="${_GSD_RUNTIME_ROOT}/gsd-core/bin/${_GSD_SHIM_NAME}"; _gsd_at() { for _p; do if [ -f "$_p" ]; then GSD_TOOLS="$_p"; return 0; fi; done; return 1; }; _gsd_id_ok() { case "$("$1" runtime-identity --raw 2>/dev/null || true)" in '{"packageName":"@opengsd/gsd-core"'*'}') return 0;; *) return 1;; esac; }; _gsd_homes() { set -- "${CLAUDE_CONFIG_DIR:-$HOME/.claude}" "${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}" "$HOME/.gemini/antigravity-ide" "$HOME/.gemini/antigravity-cli" "${AUGMENT_CONFIG_DIR:-$HOME/.augment}" "${CLINE_CONFIG_DIR:-$HOME/.cline}" "${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}" "${CODEX_HOME:-$HOME/.codex}" "${COPILOT_CONFIG_DIR:-${COPILOT_HOME:-$HOME/.copilot}}" "${CURSOR_CONFIG_DIR:-$HOME/.cursor}" "${HERMES_HOME:-$HOME/.hermes}" "${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}" "${KIMI_CONFIG_DIR:-$HOME/.config/agents}" "$HOME/.agents" "${KIMI_CODE_HOME:-$HOME/.kimi-code}" "${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}" "${PI_CODING_AGENT_DIR:-$HOME/.pi/agent}" "${QWEN_CONFIG_DIR:-$HOME/.qwen}" "${TRAE_CONFIG_DIR:-$HOME/.trae}" "${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}" "${ZCODE_CONFIG_DIR:-$HOME/.zcode}" "${GROK_AGENTS_HOME:-$HOME/.agents}"; for _h; do _gsd_at "$_h/gsd-core/bin/${_GSD_SHIM_NAME}" && return 0; done; return 1; }; if _gsd_at "${_GSD_RUNTIME_ROOT}/gsd-core/bin/${_GSD_SHIM_NAME}" "${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" "${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}"; then gsd_run() { node "$GSD_TOOLS" "$@"; }; elif _gsd_homes; then gsd_run() { node "$GSD_TOOLS" "$@"; }; elif unset -f gsd_run; _G="$(command -v gsd_run)"; [ -n "$_G" ] && _gsd_id_ok "$_G"; then GSD_TOOLS="$_G"; gsd_run() { "$GSD_TOOLS" "$@"; }; else echo "ERROR: gsd-tools.cjs not found at $GSD_TOOLS and no identity-proving gsd_run is on PATH. Run: npx -y @opengsd/gsd-core@latest --claude --local" >&2; exit 1; fi; GSD_IDENTITY_STATUS=unverified; _gsd_id_ok gsd_run && GSD_IDENTITY_STATUS=ok; export GSD_IDENTITY_STATUS; [ "$GSD_IDENTITY_STATUS" = ok ] || echo "WARNING: \"$GSD_TOOLS\" did not prove it is @opengsd/gsd-core - it is either a different package or an @opengsd/gsd-core older than the runtime-identity verb. See docs/how-to/diagnose-a-foreign-gsd-tools.md" >&2; if [ -n "${CLAUDE_ENV_FILE:-}" ] && [ -n "${GSD_TOOLS:-}" ]; then printf "export PATH='%s':\"\$PATH\"\n" "${GSD_TOOLS%/*}" >> "$CLAUDE_ENV_FILE" 2>/dev/null || true; fi
34
- AUTO_PARAM=""; if [[ "$ARGUMENTS" =~ (^|[[:space:]])--auto([[:space:]]|$) ]]; then AUTO_PARAM="--auto"; fi
38
+ AUTO_PARAM=""; if [[ "$GSD_ARGS" =~ (^|[[:space:]])--auto([[:space:]]|$) ]]; then AUTO_PARAM="--auto"; fi
35
39
  INIT=$(gsd_run query init.new-project $AUTO_PARAM)
36
40
  if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
37
41
  AGENT_SKILLS_RESEARCHER=$(gsd_run query agent-skills gsd-project-researcher)
@@ -299,10 +303,10 @@ gsd_run query commit "docs: initialize project" --files .planning/PROJECT.md
299
303
 
300
304
  **If auto mode:** Skip — config was collected in Step 2a. Proceed to Step 5.5.
301
305
 
302
- **Check for global defaults** at `~/.gsd/defaults.json`. If the file exists, read and display its contents before asking:
306
+ **Check for global defaults** at `${GSD_HOME:-$HOME}/.gsd/defaults.json` (`~/.gsd/defaults.json` unless `GSD_HOME` is set). If the file exists, read and display its contents before asking:
303
307
 
304
308
  ```bash
305
- DEFAULTS_RAW=$(cat ~/.gsd/defaults.json 2>/dev/null)
309
+ DEFAULTS_RAW=$(cat "${GSD_HOME:-$HOME}/.gsd/defaults.json" 2>/dev/null)
306
310
  ```
307
311
 
308
312
  Format the JSON into human-readable bullets using these label mappings:
@@ -757,10 +761,10 @@ Commit after writing.
757
761
  <!-- gsd:protected -->
758
762
  > **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.
759
763
 
760
- **Synthesizer output self-heal (#222) — verify SUMMARY.md materialized:** The synthesizer's canonical output is `.planning/research/SUMMARY.md` on disk; its brief structured return (`## SYNTHESIS COMPLETE` plus a few `###` confirmation lines) is NOT the file content. A known LLM false-refusal (issue #222) sometimes makes the agent return the full SUMMARY.md document inline — fabricating a write restriction (e.g. "the runtime is blocking file writes") — instead of writing the file. Prompt hardening alone does not fully eliminate it, so the orchestrator MUST absorb the failure deterministically before spawning `gsd-roadmapper`:
764
+ **Synthesizer output self-heal (#222) — verify SUMMARY.md materialized:** The synthesizer's canonical output is `.planning/research/SUMMARY.md` on disk; its brief structured return (`## SYNTHESIS COMPLETE` plus a few `###` confirmation lines) is NOT the file content. The restriction is not always fabricated: Claude Code refuses a subagent's `Write` to a few exact basenames — `SUMMARY.md` among them — with `Subagents should return findings as text, not write report files` (observed on Claude Code 2.1.301), and the synthesizer is told to return the full document inline when its `Write` hits that error; the refusal is real, not fabricated. It depends on the exact basename (`SUMMARY.md`, `REPORT.md`, `FINDINGS.md`, `ANALYSIS.md`, case-insensitive; a prefixed name such as `01-01-SUMMARY.md` and other agents' files write fine), so do not tell other agents to avoid `Write`. Either cause lands in step 2. A known LLM false-refusal (issue #222) sometimes makes the agent return the full SUMMARY.md document inline — fabricating a write restriction (e.g. "the runtime is blocking file writes") — instead of writing the file. Prompt hardening alone does not fully eliminate it, so the orchestrator MUST absorb either cause deterministically before spawning `gsd-roadmapper`:
761
765
 
762
766
  1. Verify `.planning/research/SUMMARY.md` exists AND is substantive — non-empty, and free of any leftover `<!-- gsd:write-continue -->` continuation sentinel (which marks a truncated/incomplete write). You may validate with `gsd_run verify-summary .planning/research/SUMMARY.md` — it exits 0 regardless, so check its JSON `passed` field (`"passed": false` means missing or invalid), not the process exit code. If it passes, continue normally.
763
- 2. If it is MISSING or invalid AND the synthesizer's return message contains the FULL SUMMARY.md document — recognizable by the template's top-level markers `# Project Research Summary`, `## Key Findings`, `## Implications for Roadmap`, and `## Sources`, not merely the brief `## SYNTHESIS COMPLETE` confirmation — the false-refusal fired: write that returned document to `.planning/research/SUMMARY.md` with the Write tool, then commit ALL research artifacts the synthesizer owns (it commits on behalf of the four researchers) with `gsd_run query commit "docs: complete project research" --files .planning/research/` unless they are already committed. Log `⚠ #222 self-heal: synthesizer returned SUMMARY.md inline without writing it; orchestrator persisted the file.`
767
+ 2. If it is MISSING or invalid AND the synthesizer's return message contains the FULL SUMMARY.md document — recognizable by the template's top-level markers `# Project Research Summary`, `## Key Findings`, `## Implications for Roadmap`, and `## Sources`, not merely the brief `## SYNTHESIS COMPLETE` confirmation — the synthesizer returned the document inline (the #222 false-refusal, or the runtime's real refusal of the name `SUMMARY.md`): write that returned document to `.planning/research/SUMMARY.md` with the Write tool, then commit ALL research artifacts the synthesizer owns (it commits on behalf of the four researchers) with `gsd_run query commit "docs: complete project research" --files .planning/research/` unless they are already committed. Log `⚠ #222 self-heal: synthesizer returned SUMMARY.md inline without writing it; orchestrator persisted the file.`
764
768
  3. If it is MISSING or invalid AND the return is only a brief confirmation (no full SUMMARY document to recover), the synthesizer genuinely failed — surface the error and stop; do NOT spawn `gsd-roadmapper` against a missing or incomplete SUMMARY.md.
765
769
 
766
770
  This guarantees `gsd-roadmapper` (which lists SUMMARY.md as required reading) never runs against a missing or truncated SUMMARY.md.
@@ -271,8 +271,12 @@ If STATE.md shows paused_at:
271
271
  Parse the arguments passed to this workflow to detect the plan strategy and build convergence pass-through args:
272
272
 
273
273
  ```bash
274
+ IFS= read -rd '' GSD_ARGS <<'GSD_ARGS_END' || true
275
+ $ARGUMENTS
276
+ GSD_ARGS_END
277
+ GSD_ARGS=${GSD_ARGS%$'\n'}
274
278
  PLAN_STRATEGY="local"
275
- if echo "$ARGUMENTS" | grep -qE '(^|[[:space:]])\-\-(converge|cross-ai)([[:space:]]|$)'; then
279
+ if echo "$GSD_ARGS" | grep -qE '(^|[[:space:]])\-\-(converge|cross-ai)([[:space:]]|$)'; then
276
280
  PLAN_STRATEGY="converge"
277
281
  fi
278
282
 
@@ -280,14 +284,14 @@ CONVERGENCE_ARGS=""
280
284
  # Lane flags derived from the declared roster (#2800/#2272); --all and --text are convergence
281
285
  # controls, not reviewer lanes, so they stay literal.
282
286
  for REVIEW_FLAG in $(gsd_run review-lane flags) --all --text; do
283
- if echo "$ARGUMENTS" | grep -qE "(^|[[:space:]])${REVIEW_FLAG}([[:space:]]|$)"; then
287
+ if echo "$GSD_ARGS" | grep -qE "(^|[[:space:]])${REVIEW_FLAG}([[:space:]]|$)"; then
284
288
  CONVERGENCE_ARGS="${CONVERGENCE_ARGS} ${REVIEW_FLAG}"
285
289
  fi
286
290
  done
287
291
 
288
292
  MAX_CYCLES_ARG=""
289
- if echo "$ARGUMENTS" | grep -qE '\-\-max-cycles\s+[0-9]+'; then
290
- MAX_CYCLES_ARG=$(echo "$ARGUMENTS" | grep -oE '\-\-max-cycles\s+[0-9]+' | awk '{print $2}')
293
+ if echo "$GSD_ARGS" | grep -qE '\-\-max-cycles\s+[0-9]+'; then
294
+ MAX_CYCLES_ARG=$(echo "$GSD_ARGS" | grep -oE '\-\-max-cycles\s+[0-9]+' | awk '{print $2}')
291
295
  CONVERGENCE_ARGS="${CONVERGENCE_ARGS} --max-cycles ${MAX_CYCLES_ARG}"
292
296
  fi
293
297
  ```
@@ -229,7 +229,7 @@ timestamp=$(gsd_run query current-timestamp full --raw)
229
229
 
230
230
  <step name="commit">
231
231
  ```bash
232
- gsd_run query commit "wip: [context-name] paused at [X]/[Y]" --files [handoff-path] .planning/HANDOFF.json
232
+ gsd_run query commit "docs(pause): [context-name] paused at [X]/[Y]" --files [handoff-path] .planning/HANDOFF.json
233
233
  ```
234
234
  </step>
235
235
 
@@ -246,7 +246,7 @@ Current state:
246
246
  - Task: [X] of [Y]
247
247
  - Status: [in_progress/blocked]
248
248
  - Blockers: [count] ({human_actions_pending count} need human action)
249
- - Committed as WIP
249
+ - Committed handoff
250
250
 
251
251
  To resume: /gsd:resume-work
252
252
 
@@ -260,6 +260,6 @@ To resume: /gsd:resume-work
260
260
  - [ ] .continue-here.md created at correct path for detected context
261
261
  - [ ] Required Reading, Anti-Patterns, and Infrastructure State sections filled
262
262
  - [ ] Pre-Execution Critique section filled if pausing between design and execution
263
- - [ ] Committed as WIP
263
+ - [ ] Committed handoff
264
264
  - [ ] User knows location and how to resume
265
265
  </success_criteria>
@@ -0,0 +1,70 @@
1
+ # Auto-Advance Check (plan-phase step 15)
2
+
3
+ This file is Read, not textually substituted: wherever a snippet below shows `$ARGUMENTS`, write the parent command's original argument string there (#4772).
4
+
5
+ Check for auto-advance trigger using values already loaded in step 1:
6
+
7
+ 1. Parse `--auto` and `--chain` flags from $ARGUMENTS
8
+ 2. Use `auto_chain_active` and `auto_advance` from the INIT JSON parsed in step 1 — **do not issue additional `config-get` calls for these values** (they are already present in the init output). Issuing redundant `config-get` calls for values already in INIT can cause infinite read loops on some runtimes.
9
+ 3. **Sync chain flag with intent** — if user invoked manually (no `--auto` and no `--chain`), clear the ephemeral chain flag from any previous interrupted `--auto` chain. This does NOT touch `workflow.auto_advance` (the user's persistent settings preference):
10
+ ```bash
11
+ _GSD_SHIM_NAME="gsd-tools.cjs"; _GSD_RUNTIME_ROOT="${RUNTIME_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"; GSD_TOOLS="${_GSD_RUNTIME_ROOT}/gsd-core/bin/${_GSD_SHIM_NAME}"; _gsd_at() { for _p; do if [ -f "$_p" ]; then GSD_TOOLS="$_p"; return 0; fi; done; return 1; }; _gsd_id_ok() { case "$("$1" runtime-identity --raw 2>/dev/null || true)" in '{"packageName":"@opengsd/gsd-core"'*'}') return 0;; *) return 1;; esac; }; _gsd_homes() { set -- "${CLAUDE_CONFIG_DIR:-$HOME/.claude}" "${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}" "$HOME/.gemini/antigravity-ide" "$HOME/.gemini/antigravity-cli" "${AUGMENT_CONFIG_DIR:-$HOME/.augment}" "${CLINE_CONFIG_DIR:-$HOME/.cline}" "${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}" "${CODEX_HOME:-$HOME/.codex}" "${COPILOT_CONFIG_DIR:-${COPILOT_HOME:-$HOME/.copilot}}" "${CURSOR_CONFIG_DIR:-$HOME/.cursor}" "${HERMES_HOME:-$HOME/.hermes}" "${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}" "${KIMI_CONFIG_DIR:-$HOME/.config/agents}" "$HOME/.agents" "${KIMI_CODE_HOME:-$HOME/.kimi-code}" "${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}" "${PI_CODING_AGENT_DIR:-$HOME/.pi/agent}" "${QWEN_CONFIG_DIR:-$HOME/.qwen}" "${TRAE_CONFIG_DIR:-$HOME/.trae}" "${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}" "${ZCODE_CONFIG_DIR:-$HOME/.zcode}" "${GROK_AGENTS_HOME:-$HOME/.agents}"; for _h; do _gsd_at "$_h/gsd-core/bin/${_GSD_SHIM_NAME}" && return 0; done; return 1; }; if _gsd_at "${_GSD_RUNTIME_ROOT}/gsd-core/bin/${_GSD_SHIM_NAME}" "${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" "${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}"; then gsd_run() { node "$GSD_TOOLS" "$@"; }; elif _gsd_homes; then gsd_run() { node "$GSD_TOOLS" "$@"; }; elif unset -f gsd_run; _G="$(command -v gsd_run)"; [ -n "$_G" ] && _gsd_id_ok "$_G"; then GSD_TOOLS="$_G"; gsd_run() { "$GSD_TOOLS" "$@"; }; else echo "ERROR: gsd-tools.cjs not found at $GSD_TOOLS and no identity-proving gsd_run is on PATH. Run: npx -y @opengsd/gsd-core@latest --claude --local" >&2; exit 1; fi; GSD_IDENTITY_STATUS=unverified; _gsd_id_ok gsd_run && GSD_IDENTITY_STATUS=ok; export GSD_IDENTITY_STATUS; [ "$GSD_IDENTITY_STATUS" = ok ] || echo "WARNING: \"$GSD_TOOLS\" did not prove it is @opengsd/gsd-core - it is either a different package or an @opengsd/gsd-core older than the runtime-identity verb. See docs/how-to/diagnose-a-foreign-gsd-tools.md" >&2; if [ -n "${CLAUDE_ENV_FILE:-}" ] && [ -n "${GSD_TOOLS:-}" ]; then printf "export PATH='%s':\"\$PATH\"\n" "${GSD_TOOLS%/*}" >> "$CLAUDE_ENV_FILE" 2>/dev/null || true; fi
12
+ IFS= read -rd '' GSD_ARGS <<'GSD_ARGS_END' || true
13
+ $ARGUMENTS
14
+ GSD_ARGS_END
15
+ GSD_ARGS=${GSD_ARGS%$'\n'}
16
+ if [[ ! "$GSD_ARGS" =~ --auto ]] && [[ ! "$GSD_ARGS" =~ --chain ]]; then
17
+ gsd_run query config-set workflow._auto_chain_active false || true
18
+ fi
19
+ ```
20
+
21
+ Set local variables from INIT (parsed once in step 1):
22
+ - `AUTO_CHAIN` = `auto_chain_active` from INIT JSON (boolean, default false)
23
+ - `AUTO_CFG` = `auto_advance` from INIT JSON (boolean, default false)
24
+
25
+ **If `--auto` or `--chain` flag present AND `AUTO_CHAIN` is not true:** Persist chain flag to config (handles direct invocation without prior discuss-phase):
26
+ ```bash
27
+ IFS= read -rd '' GSD_ARGS <<'GSD_ARGS_END' || true
28
+ $ARGUMENTS
29
+ GSD_ARGS_END
30
+ GSD_ARGS=${GSD_ARGS%$'\n'}
31
+ if ([[ "$GSD_ARGS" =~ --auto ]] || [[ "$GSD_ARGS" =~ --chain ]]) && [[ "$AUTO_CHAIN" != "true" ]]; then
32
+ gsd_run query config-set workflow._auto_chain_active true
33
+ fi
34
+ ```
35
+
36
+ **If `--auto` or `--chain` flag present OR `AUTO_CHAIN` is true OR `AUTO_CFG` is true:**
37
+
38
+ Display banner:
39
+ ```
40
+ ### GSD ► AUTO-ADVANCING TO EXECUTE
41
+
42
+ Plans ready. Launching execute-phase...
43
+ ```
44
+
45
+ Launch execute-phase using the Skill tool to avoid nested Task sessions (which cause runtime freezes due to deep agent nesting):
46
+ ```
47
+ Skill(skill="gsd-execute-phase", args="${PHASE} --auto --no-transition ${GSD_WS}")
48
+ ```
49
+
50
+ The `--no-transition` flag tells execute-phase to return status after verification instead of chaining further. This keeps the auto-advance chain flat — each phase runs at the same nesting level rather than spawning deeper Task agents.
51
+
52
+ **Handle execute-phase return:**
53
+ - **PHASE COMPLETE** → Display final summary:
54
+ ```
55
+ ### GSD ► PHASE ${PHASE} COMPLETE ✓
56
+
57
+ Auto-advance pipeline finished.
58
+
59
+ Next: /gsd:discuss-phase ${NEXT_PHASE} --auto ${GSD_WS}
60
+ ```
61
+ - **GAPS FOUND / VERIFICATION FAILED** → Display result, stop chain:
62
+ ```
63
+ Auto-advance stopped: Execution needs review.
64
+
65
+ Review the output above and continue manually:
66
+ /gsd:execute-phase ${PHASE} ${GSD_WS}
67
+ ```
68
+
69
+ **If neither `--auto` nor config enabled:**
70
+ Route to `<offer_next>` (existing behavior).
@@ -122,8 +122,12 @@ path regardless of `CHUNKED_PARALLEL` — there is nothing to batch.
122
122
  plans are overwritten, not skipped (#2762). Unchanged from before this change, applied per entry
123
123
  before dispatch rather than immediately before each individual spawn:
124
124
  ```bash
125
+ IFS= read -rd '' GSD_ARGS <<'GSD_ARGS_END' || true
126
+ $ARGUMENTS
127
+ GSD_ARGS_END
128
+ GSD_ARGS=${GSD_ARGS%$'\n'}
125
129
  PLAN_FILE="${PHASE_DIR}/${plan_id}-PLAN.md"
126
- if [[ -f "$PLAN_FILE" ]] && head -1 "$PLAN_FILE" | grep -q '^---' && [[ "$ARGUMENTS" != *"--reviews"* ]]; then
130
+ if [[ -f "$PLAN_FILE" ]] && head -1 "$PLAN_FILE" | grep -q '^---' && [[ "$GSD_ARGS" != *"--reviews"* ]]; then
127
131
  : # resume safety — skip this plan, remove from the batch's runnable set — NOT under --reviews (replan)
128
132
  fi
129
133
  ```
@@ -5,13 +5,17 @@ The init JSON includes `phase_status` — one of `Pending | Planned | In Progres
5
5
  Parse `phase_status` from the init JSON, then:
6
6
 
7
7
  ```bash
8
+ IFS= read -rd '' GSD_ARGS <<'GSD_ARGS_END' || true
9
+ $ARGUMENTS
10
+ GSD_ARGS_END
11
+ GSD_ARGS=${GSD_ARGS%$'\n'}
8
12
  FORCE_REPLAN=false
9
- if [[ "$ARGUMENTS" =~ (^|[[:space:]])--force([[:space:]]|$) ]]; then
13
+ if [[ "$GSD_ARGS" =~ (^|[[:space:]])--force([[:space:]]|$) ]]; then
10
14
  FORCE_REPLAN=true
11
15
  fi
12
16
 
13
17
  if [ "${phase_status}" = "Complete" ]; then
14
- if [[ "$ARGUMENTS" =~ (^|[[:space:]])--reviews([[:space:]]|$) ]]; then
18
+ if [[ "$GSD_ARGS" =~ (^|[[:space:]])--reviews([[:space:]]|$) ]]; then
15
19
  # --reviews on a closed phase is never legitimate — concerns belong in a
16
20
  # new phase or issue against the closed phase's commits.
17
21
  cat <<EOF >&2