@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
@@ -150,6 +150,18 @@
150
150
  "read": "gsd-core/workflows/progress/steps/forensic-audit.md"
151
151
  }
152
152
  ],
153
+ "quick-batch": [
154
+ {
155
+ "id": "research-phase",
156
+ "when": "flag:--research",
157
+ "read": "gsd-core/workflows/quick-batch/steps/research-phase.md"
158
+ },
159
+ {
160
+ "id": "verification-wave",
161
+ "when": "flag:--validate",
162
+ "read": "gsd-core/workflows/quick-batch/steps/verification-wave.md"
163
+ }
164
+ ],
153
165
  "quick": [
154
166
  {
155
167
  "id": "discussion-phase",
@@ -23,7 +23,7 @@ if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
23
23
  AGENT_SKILLS_AUDITOR=$(gsd_run query agent-skills gsd-security-auditor)
24
24
  ```
25
25
 
26
- **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.
26
+ **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.
27
27
 
28
28
  Parse: `phase_dir`, `phase_number`, `phase_name`, `phase_slug`, `padded_phase`.
29
29
 
@@ -1,3 +1,5 @@
1
+ @~/.claude/gsd-core/references/response-language-directive.md
2
+
1
3
  <purpose>
2
4
  Generate a post-session summary document capturing work performed, outcomes achieved, and estimated resource usage. Writes SESSION_REPORT.md to .planning/reports/ for human review and stakeholder sharing.
3
5
  </purpose>
@@ -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
  <purpose>
2
4
  Interactive configuration of GSD power-user knobs — plan bounce, node repair, subagent timeouts,
3
5
  inline plan threshold, cross-AI execution, base branch, branch templates, response language,
@@ -60,7 +60,7 @@ if [[ -z "${GSD_CONFIG_PATH:-}" ]]; then
60
60
  fi
61
61
  ```
62
62
 
63
- **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.
63
+ **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.
64
64
 
65
65
  Store `$GSD_CONFIG_PATH`. Every subsequent read/write uses it.
66
66
  </step>
@@ -163,12 +163,13 @@ namespace — any other slug fails with `Unknown config key`.
163
163
  Settable keys (the shipped registry's model-bearing lanes):
164
164
 
165
165
  `review.models.agy` (Antigravity), `review.models.claude`, `review.models.codex`,
166
- `review.models.gemini`, `review.models.kimi-code`, `review.models.llama_cpp`,
167
- `review.models.lm_studio`, `review.models.ollama`, `review.models.opencode`.
166
+ `review.models.cursor`, `review.models.gemini`, `review.models.kimi-code`,
167
+ `review.models.llama_cpp`, `review.models.lm_studio`, `review.models.ollama`,
168
+ `review.models.opencode`.
168
169
 
169
- Reviewer lanes `cursor`, `qwen`, and `coderabbit` declare no `modelConfigKey` —
170
- there is nothing to configure for them here (whether they should have a
171
- per-lane model key is a separate question, out of scope for this workflow).
170
+ Reviewer lanes `qwen` and `coderabbit` declare no `modelConfigKey` — there is
171
+ nothing to configure for them here (whether they should have a per-lane model
172
+ key is a separate question, out of scope for this workflow).
172
173
  If the user asks for one of those, say exactly that and skip.
173
174
 
174
175
  ```text
@@ -208,8 +209,8 @@ If it is not one of the settable keys, print:
208
209
 
209
210
  ```text
210
211
  Rejected: review.models.<slug> is not settable — only the reviewer lanes whose
211
- keys are enumerated above can be configured here. (cursor, qwen, and
212
- coderabbit have no per-lane model key.)
212
+ keys are enumerated above can be configured here. (qwen and coderabbit have no
213
+ per-lane model key.)
213
214
  ```
214
215
 
215
216
  and re-prompt.
@@ -28,7 +28,7 @@ if [[ -z "${GSD_CONFIG_PATH:-}" ]]; then
28
28
  fi
29
29
  ```
30
30
 
31
- **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.
31
+ **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.
32
32
 
33
33
  Creates `config.json` (at the resolved path) with defaults if missing. `INIT` still holds `state.load` output for any step that needs STATE fields.
34
34
  Store `$GSD_CONFIG_PATH` — all subsequent reads and writes use this path, not a hardcoded `.planning/config.json`, so active-workstream installs target the correct file (#2282).
@@ -30,7 +30,7 @@ INIT=$(gsd_run query init.phase-op "${PHASE_ARG}")
30
30
  if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
31
31
  ```
32
32
 
33
- **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.
33
+ **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.
34
34
 
35
35
  Parse from init JSON: `phase_found`, `phase_dir`, `phase_number`, `phase_name`, `padded_phase`, `commit_docs`.
36
36
 
@@ -309,16 +309,16 @@ Example configured sections:
309
309
 
310
310
  **8. TDD Audit section:**
311
311
 
312
- Reconstruct the per-commit TDD gate trail before squash-merge discards it. Walk the PR branch's own commits (merges excluded) and read each commit's `gate_status:` trailer with Git's native trailer machinery — never a raw `%B` grep, which would also match the string written in prose:
312
+ Reconstruct the per-commit TDD gate trail before squash-merge discards it. Walk the PR branch's own commits (merges excluded) and read each commit's `gate-status:` trailer with Git's native trailer machinery — never a raw `%B` grep, which would also match the string written in prose:
313
313
 
314
314
  ```bash
315
315
  # Anchor on the merge-base so a stale local ${BASE_BRANCH} ref cannot over-count.
316
316
  RANGE_BASE=$(git merge-base "${BASE_BRANCH}" HEAD)
317
317
  git log "${RANGE_BASE}..HEAD" --no-merges --reverse \
318
- --format='%H%x1f%s%x1f%(trailers:key=gate_status,valueonly,separator=%x2c)%x1e'
318
+ --format='%H%x1f%s%x1f%(trailers:key=gate-status,valueonly,separator=%x2c)%x1e'
319
319
  ```
320
320
 
321
- Records are separated by `\x1e`; the fields inside each are `\x1f`-separated — `<sha>`, `<subject>`, `<gate_status value>`.
321
+ Records are separated by `\x1e`; the fields inside each are `\x1f`-separated — `<sha>`, `<subject>`, `<gate-status value>`.
322
322
 
323
323
  Pair commits by their conventional-commit type (the `type:` prefix of the subject):
324
324
 
@@ -326,16 +326,16 @@ Pair commits by their conventional-commit type (the `type:` prefix of the subjec
326
326
  - A `refactor:`, `docs:`, or `chore:` commit that is not consumed as an Impl pairing is a standalone row with Impl commit `—`.
327
327
  - A `feat:`/`fix:` commit with no preceding unpaired `test:` is a standalone row.
328
328
 
329
- Surface each commit's `gate_status:` value, normalized to exactly one of `skill`, `fallback`, `exempt`, or `missing` — never the raw trailer text. A commit whose trailer is absent, whose value is none of the first three, or which carries more than one `gate_status:` trailer (ambiguous) is counted as **missing** and still listed. This section is informational; it never blocks the ship.
329
+ Surface each commit's `gate-status:` value, normalized to exactly one of `skill`, `fallback`, `exempt`, or `missing` — never the raw trailer text. A commit whose trailer is absent, whose value is none of the first three, or which carries more than one `gate-status:` trailer (ambiguous) is counted as **missing** and still listed. This section is informational; it never blocks the ship.
330
330
 
331
- **Self-suppress when every commit is missing (#2431):** the execute pipeline only writes `gate_status:` trailers when TDD mode is active. If every commit in the scan normalizes to `missing`, skip this section and the aggregate trailer (step 9) entirely — a 100%-missing table is pure noise. Only emit when at least one commit carries a real value (`skill`, `fallback`, or `exempt`).
331
+ **Self-suppress when every commit is missing (#2431):** the execute pipeline only writes `gate-status:` trailers when TDD mode is active. If every commit in the scan normalizes to `missing`, skip this section and the aggregate trailer (step 9) entirely — a 100%-missing table is pure noise. Only emit when at least one commit carries a real value (`skill`, `fallback`, or `exempt`).
332
332
 
333
- Harden every table cell against injection, not just subjects: escape `|` as `\|` and strip `\r`/`\n` from both commit subjects and the rendered `gate_status` value. Prefer NUL (`-z` / `%x00`) record separation, and reject any record whose fields contain the `\x1f`/`\x1e` delimiters, so an adversarial commit message cannot corrupt record or field boundaries.
333
+ Harden every table cell against injection, not just subjects: escape `|` as `\|` and strip `\r`/`\n` from both commit subjects and the rendered `gate-status` value. Prefer NUL (`-z` / `%x00`) record separation, and reject any record whose fields contain the `\x1f`/`\x1e` delimiters, so an adversarial commit message cannot corrupt record or field boundaries.
334
334
 
335
335
  ```markdown
336
336
  ## TDD Audit
337
337
 
338
- | Test commit | Impl commit | gate_status |
338
+ | Test commit | Impl commit | gate-status |
339
339
  |---|---|---|
340
340
  | `a1b2c3d` test: failing parser test | `e4f5g6h` feat: implement parser | skill |
341
341
  | `i7j8k9l` test: failing export test | `m0n1o2p` feat: implement export | fallback |
@@ -346,12 +346,12 @@ Aggregate: 2 skill, 1 fallback, 1 exempt — 0 missing.
346
346
 
347
347
  This `## TDD Audit` section is the final body section — it renders after the configured `pr_body_sections`, immediately before the aggregate trailer — so the frozen core sections and the append-only configured sections both keep their existing order.
348
348
 
349
- **9. Aggregate gate_status trailer (final line)** (only when step 8 was emitted — i.e., at least one real `gate_status` value exists):
349
+ **9. Aggregate gate-status trailer (final line)** (only when step 8 was emitted — i.e., at least one real `gate-status` value exists):
350
350
 
351
351
  After every other section — including any configured `pr_body_sections` — emit the audit aggregate as a single Git trailer on the **final line** of the PR body, preceded by a blank line so it parses as a valid trailer:
352
352
 
353
353
  ```
354
- gate_status: skill=2, fallback=1, exempt=1, missing=0
354
+ gate-status: skill=2, fallback=1, exempt=1, missing=0
355
355
  ```
356
356
 
357
357
  Use the exact key order `skill=`, `fallback=`, `exempt=`, `missing=` so downstream tooling parses it stably. Keeping it last means a GitHub squash-merge that defaults its commit message to the PR description carries the aggregate into `${BASE_BRANCH}`, preserving the audit footprint in `git log` after the PR branch is deleted. (Best-effort: it depends on the repo's squash-message default; the in-body `## TDD Audit` section is the source of truth regardless.)
@@ -1,3 +1,5 @@
1
+ @~/.claude/gsd-core/references/response-language-directive.md
2
+
1
3
  <purpose>
2
4
  Curate sketch design findings and package them into a persistent project skill for future
3
5
  UI implementation. Reads from `.planning/sketches/`, writes skill to `./.claude/skills/sketch-findings-[project]/`
@@ -102,7 +102,7 @@ RESPONSE_LANGUAGE=$(gsd_run query config-get response_language --raw --default "
102
102
  COMMIT_DOCS=$(gsd_run query config-get commit_docs --raw 2>/dev/null || echo "true")
103
103
  ```
104
104
 
105
- **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.
105
+ **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.
106
106
  </step>
107
107
 
108
108
  <step name="mood_intake">
@@ -34,7 +34,7 @@ RESPONSE_LANGUAGE=$(gsd_run query config-get response_language --raw --default "
34
34
  SNAPSHOT=$(gsd_run smart-entry --json 2>/dev/null)
35
35
  ```
36
36
 
37
- **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.
37
+ **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.
38
38
 
39
39
  Parse `SNAPSHOT` as JSON. It has the shape:
40
40
 
@@ -63,7 +63,7 @@ if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
63
63
 
64
64
  Parse JSON for: `phase_found`, `phase_dir`, `phase_number`, `phase_name`, `phase_slug`, `padded_phase`, `state_path`, `requirements_path`, `roadmap_path`, `planning_path`, `response_language`, `commit_docs`.
65
65
 
66
- **If `response_language` is set:** All user-facing text in this workflow MUST be in `{response_language}`. Technical terms, code, and file paths stay in English.
66
+ **If `response_language` is set:** All user-facing text in this workflow — narration between tool calls, status updates, progress notes, findings, questions, and report prose — MUST be in `{response_language}`. Technical terms, code, and file paths stay in English.
67
67
 
68
68
  **If `phase_found` is false:**
69
69
  ```
@@ -192,21 +192,25 @@ If gate passes (ambiguity ≤ 0.20 AND all minimums met):
192
192
  Run AFTER the ambiguity gate passes (you probe edges of clear requirements, not vague
193
193
  ones). Reference: @~/.claude/gsd-core/references/edge-probe.md.
194
194
 
195
- **Non-English projects — the probe input is translated, the SPEC is not.** The shape cues the
196
- classifier matches are **English** word-boundary patterns, so requirement prose written in
197
- another language matches nothing, classifies to zero shapes, and lands every row in
198
- `unclassified` (#1110) — the taxonomy contributes nothing and `--auto` leaves it all
199
- `unresolved`. The `$REQS_JSON` payload below is **engine input, never user-facing output**, so
200
- the `response_language` rule at the top of this workflow does not govern it: write each
201
- requirement's `text` as a faithful **English** translation of the SPEC requirement.
202
- The SPEC keeps the original language — only the probe input is translated, and requirement
195
+ **Non-English projects — `text_en` carries the classifier-facing translation; the SPEC is
196
+ not.** The shape cues the classifier matches are **English** word-boundary patterns, so
197
+ requirement prose written in another language matches nothing, classifies to zero shapes, and
198
+ lands every row in `unclassified` (#1110) — the taxonomy contributes nothing and `--auto`
199
+ leaves it all `unresolved`. When this project has `response_language` set, add an optional
200
+ `text_en` key to each `$REQS_JSON` entry: a faithful **English** translation of that
201
+ requirement's `text`. `text_en` is **engine input, never user-facing output**, so the
202
+ `response_language` rule at the top of this workflow does not govern it — but `text` itself is
203
+ NOT translated: write it as the requirement's own text, exactly as it appears in the SPEC.
204
+ The SPEC keeps the original language — only `text_en` is translated, and requirement
203
205
  `id`s are never translated or renumbered (coverage rows join back on `id`, and any Acceptance
204
- Criteria you write from the resolved edges go into the SPEC in `response_language`).
205
- Translate **every** requirement, not only the ones that look edge-relevant: the
206
+ Criteria you write from the resolved edges go into the SPEC in `response_language`). Populate
207
+ `text_en` for **every** requirement, not only the ones that look edge-relevant: the
206
208
  `$APPLICABLE = 0` warning below fires only when *all* requirements are unclassified, so a
207
- partly-classified spec slips through with no signal at all.
208
- If a requirement still classifies to zero shapes after translation, it carries no cue in any
209
- language (the recorded recall gap — ADR-857 §98 / ADR-550 D7b, not a translation failure);
209
+ partly-classified spec slips through with no signal at all. When `response_language` is unset
210
+ (an English-language project), omit `text_en` — `text` is already English and the engine
211
+ falls back to it automatically (`text_en ?? text`).
212
+ If a requirement still classifies to zero shapes with `text_en` populated, it carries no cue in
213
+ any language (the recorded recall gap — ADR-857 §98 / ADR-550 D7b, not a translation failure);
210
214
  author an explicit `shapes` array on that requirement instead of relying on the prose
211
215
  classifier.
212
216
 
@@ -251,11 +255,12 @@ fi
251
255
 
252
256
  # Write the Requirements gathered in THIS spec session to a temp JSON, then invoke the
253
257
  # canonical coverage compute. Populate the heredoc from the SPEC's Requirements — one object
254
- # per requirement: {"id","text","shapes"?}. This is the load-bearing step: an empty file makes
255
- # the probe a no-op, so the guard below fails loud rather than silently skipping (RR-04).
256
- # When `response_language` is set, `text` carries a faithful ENGLISH translation (see above) —
257
- # the shape cues are English-only, so original-language prose classifies to zero shapes and the
258
- # probe becomes a silent no-op. Keep every `id` exactly as it appears in the SPEC.
258
+ # per requirement: {"id","text","text_en"?,"shapes"?}. This is the load-bearing step: an empty
259
+ # file makes the probe a no-op, so the guard below fails loud rather than silently skipping
260
+ # (RR-04). When `response_language` is set, ALSO add `text_en` — a faithful ENGLISH
261
+ # translation of `text` (see above) — the shape cues are English-only, so original-language
262
+ # `text` alone classifies to zero shapes; `text` itself stays the SPEC's own requirement text
263
+ # and is never translated. Keep every `id` exactly as it appears in the SPEC.
259
264
  # BSD/macOS mktemp only randomizes XXXXXX when it is the final path component, so make a
260
265
  # suffixless temp then append the extension — portable across BSD + GNU (#1520).
261
266
  REQS_JSON=$(mktemp "${TMPDIR:-/tmp}/edge-probe-reqs-XXXXXX") && mv "$REQS_JSON" "${REQS_JSON}.json" && REQS_JSON="${REQS_JSON}.json" || exit 1
@@ -1,3 +1,5 @@
1
+ @~/.claude/gsd-core/references/response-language-directive.md
2
+
1
3
  <purpose>
2
4
  Package spike experiment findings into a persistent project skill — an implementation blueprint
3
5
  for future build conversations. Reads from `.planning/spikes/`, writes skill to
@@ -18,7 +18,7 @@ _GSD_SHIM_NAME="gsd-tools.cjs"; _GSD_RUNTIME_ROOT="${RUNTIME_DIR:-$(git rev-pars
18
18
  RESPONSE_LANGUAGE=$(gsd_run query config-get response_language --raw --default "" 2>/dev/null || echo "")
19
19
  ```
20
20
 
21
- **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.
21
+ **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.
22
22
 
23
23
  <step name="banner">
24
24
  ```
@@ -1,3 +1,5 @@
1
+ @~/.claude/gsd-core/references/response-language-directive.md
2
+
1
3
  <purpose>
2
4
  Display comprehensive project statistics including phases, plans, requirements, git metrics, and timeline.
3
5
  </purpose>
@@ -1,3 +1,5 @@
1
+ @~/.claude/gsd-core/references/response-language-directive.md
2
+
1
3
  # sync-skills — Cross-Runtime GSD Skill Sync
2
4
 
3
5
  **Command:** `/gsd-sync-skills`
@@ -30,14 +32,14 @@ IS_APPLY=false
30
32
 
31
33
  # Parse --from
32
34
  if [[ "$@" == *"--from"* ]]; then
33
- FROM_RUNTIME=$(echo "$@" | grep -oP '(?<=--from )\S+')
35
+ FROM_RUNTIME=$(echo "$@" | sed -E 's/.*--from[[:space:]]+([^[:space:]]+).*/\1/')
34
36
  fi
35
37
 
36
38
  # Parse --to
37
39
  if [[ "$@" == *"--to all"* ]]; then
38
40
  TO_RUNTIMES=(antigravity augment claude cline codebuddy codex copilot cursor grok hermes kilo kimi kimi-code opencode pi qwen trae windsurf zcode)
39
41
  elif [[ "$@" == *"--to"* ]]; then
40
- TO_RUNTIMES=( $(echo "$@" | grep -oP '(?<=--to )\S+') )
42
+ TO_RUNTIMES=( $(echo "$@" | sed -E 's/.*--to[[:space:]]+([^[:space:]]+).*/\1/') )
41
43
  fi
42
44
 
43
45
  # Parse --apply
@@ -237,12 +239,18 @@ mkdir -p "$DEST_ROOT"
237
239
  # empty. If per-runtime conversion is ever wired in, this is where it would go;
238
240
  # until then the cp -r must never run for a destination != source.
239
241
 
240
- for SKILL in $CREATE_LIST $UPDATE_LIST; do
242
+ # Rewrapped through unquoted command substitution (gsd-core#4109): a bare
243
+ # `$VAR` word-splits under bash but not zsh, collapsing every element onto
244
+ # one iteration there.
245
+ for SKILL in $(printf '%s' "$CREATE_LIST") $(printf '%s' "$UPDATE_LIST"); do
241
246
  rm -rf "$DEST_ROOT/$SKILL"
242
247
  cp -r "$SRC_SKILLS_ROOT/$SKILL" "$DEST_ROOT/$SKILL"
243
248
  done
244
249
 
245
- for SKILL in $REMOVE_LIST; do
250
+ # Rewrapped through unquoted command substitution (gsd-core#4109): a bare
251
+ # `$VAR` word-splits under bash but not zsh, collapsing every element onto
252
+ # one iteration there.
253
+ for SKILL in $(printf '%s' "$REMOVE_LIST"); do
246
254
  rm -rf "$DEST_ROOT/$SKILL"
247
255
  done
248
256
  ```
@@ -1,3 +1,5 @@
1
+ @~/.claude/gsd-core/references/response-language-directive.md
2
+
1
3
  # Thread Workflow
2
4
 
3
5
  Invoked by `/gsd:thread` (`commands/gsd/thread.md`).
@@ -1,3 +1,5 @@
1
+ @~/.claude/gsd-core/references/response-language-directive.md
2
+
1
3
  <internal_workflow>
2
4
 
3
5
  **This is an INTERNAL workflow — NOT a user-facing command.**
@@ -28,7 +28,7 @@ AGENT_SKILLS_UI_CHECKER=$(gsd_run query agent-skills gsd-ui-checker)
28
28
 
29
29
  Parse JSON for: `phase_dir`, `phase_number`, `phase_name`, `phase_slug`, `padded_phase`, `has_context`, `has_research`, `commit_docs`, `response_language`.
30
30
 
31
- **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.
31
+ **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.
32
32
 
33
33
  **File paths:** `state_path`, `roadmap_path`, `requirements_path`, `context_path`, `research_path`.
34
34
 
@@ -238,8 +238,9 @@ Display blocking issues. Proceed to step 9.
238
238
  Track `revision_count` (starts at 0).
239
239
 
240
240
  **If `revision_count` < 2:**
241
- - Increment `revision_count`
242
- - Re-spawn gsd-ui-researcher with revision context:
241
+ - Re-spawn gsd-ui-researcher with revision context. `revision_count` is incremented on the
242
+ researcher's RETURN, not here — a return of `## REVISION_CONFLICT` must not spend an
243
+ iteration, and an increment made before dispatch cannot be withheld afterwards:
243
244
 
244
245
  ```markdown
245
246
  <revision>
@@ -248,12 +249,32 @@ The UI checker found issues with the current UI-SPEC.md.
248
249
  ### Issues to Fix
249
250
  {paste blocking issues from checker return}
250
251
 
251
- Read the existing UI-SPEC.md, fix ONLY the listed issues, re-write the file.
252
+ `required_property` + evidence + severity BIND. `fix_hint` is ONE non-binding example route: a
253
+ smaller or different mechanism reaching the same property resolves the issue in full — say which
254
+ you used. Re-check the user's locked answers, capability guidance (CLAUDE.md, project skills) and
255
+ the constraints this UI-SPEC already encodes BEFORE editing; if a hint would contradict one, or
256
+ the property is unreachable without breaking one, return `## REVISION_CONFLICT` with the conflict
257
+ and the alternatives rather than applying or working around it — see your `## Revision Conflict`
258
+ section for its shape.
259
+
260
+ Read the existing UI-SPEC.md, resolve ONLY the listed issues, re-write the file.
252
261
  Do NOT re-ask the user questions that are already answered.
253
262
  </revision>
254
263
  ```
255
264
 
256
- - After researcher returns → re-spawn checker (step 7)
265
+ - **If the researcher returns `## REVISION_CONFLICT`:** do NOT increment `revision_count` and do
266
+ NOT re-spawn the checker — a conflict is not resolvable by re-running the same loop. Present the
267
+ conflict and its alternatives to the user and ask which to take: adopt a named alternative /
268
+ override the named constraint and apply the hint / amend the constraint itself. Every option
269
+ resolves the conflict — accepting the spec with the BLOCK still open is NOT offered here, because
270
+ the blocking `required_property` still fails; that choice belongs to the cap escalation below.
271
+ Re-spawn the researcher with the chosen resolution and return to this step.
272
+
273
+ **Bounded:** a conflict naming the SAME `required_property` twice in a row (no successful revision in between) is a stall, and so is
274
+ the THIRD conflict return of this loop whatever property it names — alternating property names
275
+ would otherwise never trip the repeat rule. Stop re-spawning and route it to the same cap
276
+ escalation below, so declining to spend an iteration cannot make this path unbounded.
277
+ - **On any other return:** increment `revision_count`, then re-spawn checker (step 7)
257
278
 
258
279
  **If `revision_count` >= 2:**
259
280
  ```
@@ -23,7 +23,7 @@ if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
23
23
  AGENT_SKILLS_UI_REVIEWER=$(gsd_run query agent-skills gsd-ui-auditor)
24
24
  ```
25
25
 
26
- **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.
26
+ **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.
27
27
 
28
28
  Parse: `phase_dir`, `phase_number`, `phase_name`, `phase_slug`, `padded_phase`, `commit_docs`.
29
29
 
@@ -1,3 +1,5 @@
1
+ @~/.claude/gsd-core/references/response-language-directive.md
2
+
1
3
  # Ultraplan Phase Workflow [BETA]
2
4
 
3
5
  Offload GSD's plan phase to Claude Code's ultraplan cloud infrastructure.
@@ -13,7 +13,7 @@ _GSD_SHIM_NAME="gsd-tools.cjs"; _GSD_RUNTIME_ROOT="${RUNTIME_DIR:-$(git rev-pars
13
13
  RESPONSE_LANGUAGE=$(gsd_run query config-get response_language --raw --default "" 2>/dev/null || echo "")
14
14
  ```
15
15
 
16
- **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.
16
+ **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.
17
17
 
18
18
  <step name="banner" priority="first">
19
19
  Display the stage banner:
@@ -7,14 +7,14 @@ Read all files referenced by the invoking prompt's execution_context before star
7
7
  </required_reading>
8
8
 
9
9
  <process>
10
- **If `response_language` is configured:** All user-facing questions, prompts, and explanations in this workflow MUST be presented in that language. Technical terms, code, file paths, and subagent prompts stay in English — only user-facing output is translated.
10
+ **If `response_language` is configured:** All user-facing output of this workflow — narration between tool calls, status updates, progress notes, findings, questions, prompts, and explanations — MUST be presented in that language. Technical terms, code, file paths, and subagent prompts stay in English — only user-facing output is translated.
11
11
 
12
12
  <step name="get_installed_version">
13
13
  Detect the installed GSD version, scope, runtime, and config dir.
14
14
 
15
15
  First, derive `PREFERRED_CONFIG_DIR` and `PREFERRED_RUNTIME` from the invoking prompt's `execution_context` path — this is the one input only the workflow knows:
16
16
  - If the path contains `/gsd-core/workflows/update.md`, strip that suffix and store the remainder as `PREFERRED_CONFIG_DIR`.
17
- - Infer `PREFERRED_RUNTIME` from the path: `/.codex/` -> `codex`; `/.gemini/antigravity-ide/`, `/.gemini/antigravity-cli/`, `/.gemini/antigravity/`, `/.agents/` or `/.agent/` -> `antigravity` (`.agents` is the canonical local Antigravity install dir (#791); `.agent` is the legacy form (#503); see bin/install.js `getDirName('antigravity')`); `/.config/kilo/` or `/.kilo/` -> `kilo`; `/.config/opencode/` or `/.opencode/` -> `opencode`; otherwise `claude`.
17
+ - Infer `PREFERRED_RUNTIME` from the path: `/.claude/` -> `claude`; `/.codex/` -> `codex`; `/.gemini/antigravity-ide/`, `/.gemini/antigravity-cli/`, `/.gemini/antigravity/`, `/.agents/` or `/.agent/` -> `antigravity` (`.agents` is the canonical local Antigravity install dir (#791); `.agent` is the legacy form (#503); see bin/install.js `getDirName('antigravity')`); `/.windsurf/`, `/.devin/` -> `windsurf`; `/.config/kilo/` or `/.kilo/` -> `kilo`; `/.config/opencode/` or `/.opencode/` -> `opencode`; otherwise leave it empty.
18
18
 
19
19
  Then resolve the install context via the deterministic projection (#498). **Do NOT re-derive scope, runtime, or version by hand** — `update-context` owns that cascade in tested code (`gsd-core/bin/lib/update-context.cjs`), the same way `check-latest-version` owns the package name (#2992):
20
20
 
@@ -50,17 +50,17 @@ if [ -n "$UC" ]; then
50
50
  # then silently degrades to the fresh-install fallback. The field name is
51
51
  # passed as argv, never interpolated into the script text.
52
52
  uc_field() {
53
- printf '%s' "$UC" | node -e "let d='';process.stdin.setEncoding('utf8');process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{try{const v=JSON.parse(d)[process.argv[1]];process.stdout.write(v==null?'':String(v));}catch{}})" "$1" 2>/dev/null
53
+ printf '%s' "${2:-$UC}" | node -e "let d='';process.stdin.setEncoding('utf8');process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{try{const v=JSON.parse(d)[process.argv[1]];process.stdout.write(v==null?'':String(v));}catch{}})" "$1" 2>/dev/null
54
54
  }
55
55
  INSTALLED_VERSION="$(uc_field installedVersion)"
56
56
  INSTALL_SCOPE="$(uc_field scope)"
57
57
  TARGET_RUNTIME="$(uc_field runtime)"
58
58
  GSD_DIR="$(uc_field gsdDir)"
59
59
  else
60
- # No tool resolvable / projection failed -> treat as a fresh install.
60
+ # No tool resolvable / projection failed -> no update target is known.
61
61
  INSTALLED_VERSION="0.0.0"
62
62
  INSTALL_SCOPE="UNKNOWN"
63
- TARGET_RUNTIME="claude"
63
+ TARGET_RUNTIME=""
64
64
  GSD_DIR=""
65
65
  fi
66
66
 
@@ -73,15 +73,26 @@ echo "$GSD_DIR"
73
73
  Parse output:
74
74
  - Line 1 = installed version (`0.0.0` means unknown version)
75
75
  - Line 2 = install scope (`LOCAL`, `GLOBAL`, or `UNKNOWN`)
76
- - Line 3 = target runtime (`claude`, `opencode`, `kilo`, `codex`, `antigravity`)
77
- - Line 4 = resolved GSD config dir (e.g. `/Users/me/.claude`, `/Users/me/.gemini`); empty if scope is `UNKNOWN`. Capture this as `GSD_DIR` and pass it to subsequent steps so they don't re-derive the runtime path.
78
- - If scope is `UNKNOWN`, proceed to install using the `--claude --global` fallback.
76
+ - Line 3 = target runtime (`claude`, `opencode`, `kilo`, `codex`, `antigravity`, `windsurf`); empty when no installed target is resolved
77
+ - Line 4 = resolved GSD config dir (e.g. `/Users/me/.claude`, `/Users/me/.gemini`); empty when no installed target is resolved. Capture this as `GSD_DIR` and pass it to subsequent steps so they don't re-derive the runtime path.
79
78
 
80
79
  `update-context` reproduces the previous detection cascade — preferred-config-dir fast path, local-over-global with same-path dedup (so `CWD=$HOME` does not misdetect as LOCAL), env-var overrides (`CLAUDE_CONFIG_DIR`, `OPENCODE_CONFIG_DIR`, `KILO_CONFIG`, `XDG_CONFIG_HOME`, `CODEX_HOME`, …), and semver validation — but as a tested projection rather than ~280 lines of inline bash. Branch coverage lives in `tests/update-context.test.cjs`.
81
80
 
82
81
  If multiple runtime installs are detected and the invoking runtime cannot be determined from execution_context, ask the user which runtime to update before running install.
83
82
 
84
- **If VERSION file missing (version resolves to `0.0.0`):** report the installed version as Unknown and proceed to install (treated as `0.0.0` for comparison).
83
+ **If `INSTALL_SCOPE` is `UNKNOWN`, `TARGET_RUNTIME` is empty, or `GSD_DIR` is empty:** this gate takes precedence over the VERSION-missing case below — a fully-unresolved target also reports version `0.0.0`, and must exit here rather than fall through to "proceed to install".
84
+
85
+ ```text
86
+ UPDATE_TARGET_UNRESOLVED
87
+
88
+ GSD could not resolve an installed update target. No update was performed.
89
+
90
+ Rerun from a valid installed runtime: `/gsd:update`. For a fresh installation, run `npx -y --package=@opengsd/gsd-core@latest -- gsd-core --global`.
91
+ ```
92
+
93
+ Exit.
94
+
95
+ **Otherwise, if VERSION file missing (version resolves to `0.0.0`) but the target above resolved:** report the installed version as Unknown and proceed to install (treated as `0.0.0` for comparison).
85
96
  </step>
86
97
 
87
98
  <step name="parse_update_channel">
@@ -121,34 +132,31 @@ Extract `section_manifest` from `INIT_UPDATE` — gates the `channel-banner` sec
121
132
  <step name="check_latest_version">
122
133
  Check npm for latest version via the deterministic script. **Do NOT run `npm view` or `npm search` directly** — the package name must come from the script, not from a free choice at execution time. (#2992: LLM-driven prescriptions of npm package names produced wrong-package queries; moving the package name into a script constant closes that gap.)
123
134
 
124
- The `GSD_DIR` value emitted by `get_installed_version` (line 4) resolves to the runtime-specific config dir (`~/.claude/`, `~/.gemini/`, `~/.codex/`, etc.), so the script invocation works for every runtime — not just Claude. If `GSD_DIR` is empty (scope `UNKNOWN`), skip this step and go directly to install.
135
+ The `GSD_DIR` value emitted by `get_installed_version` (line 4) resolves to the runtime-specific config dir (`~/.claude/`, `~/.gemini/`, `~/.codex/`, etc.), so the script invocation works for every runtime — not just Claude. An unresolved target exits in `get_installed_version` before this step.
125
136
 
126
- `LATEST_RESULT` is a JSON document with the documented shape `{ ok: bool, version: string, reason: string, detail?: string }`. Parse via `jq` ONLY when the script actually ran. When `GSD_DIR` is empty (scope `UNKNOWN`), skip the check entirely and seed the parsed fields with their no-op values so downstream logic does not mistake an unset `LATEST_RESULT` for a failed network check (#2993 CR feedback):
137
+ `LATEST_RESULT` is a JSON document with the documented shape `{ ok: bool, version: string, reason: string, detail?: string }`. Parse it with the Node-only `uc_field` helper. When the script cannot run or returns nothing, preserve its failure as a meaningful diagnostic (#2993 CR feedback):
127
138
 
128
139
  ```bash
129
- if [ -z "$GSD_DIR" ]; then
130
- # No install detected — fall through to install step; version-check is skipped.
131
- LATEST_RESULT=""
140
+ uc_field() {
141
+ printf '%s' "$2" | node -e "let d='';process.stdin.setEncoding('utf8');process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{try{const v=JSON.parse(d)[process.argv[1]];process.stdout.write(v==null?'':String(v));}catch{}})" "$1" 2>/dev/null
142
+ }
143
+ if LATEST_RESULT="$(node "$GSD_DIR/gsd-core/bin/check-latest-version.cjs" --json --tag "$TAG" 2>/dev/null)"; then
132
144
  LATEST_STATUS=0
133
- LATEST_OK=false
134
- LATEST_VERSION=""
135
- LATEST_REASON="no_install_detected"
136
145
  else
137
- LATEST_RESULT="$(node "$GSD_DIR/gsd-core/bin/check-latest-version.cjs" --json --tag "$TAG" 2>/dev/null)"
138
146
  LATEST_STATUS=$?
139
- # #2993 CR: when node is missing or the script doesn't exist, LATEST_RESULT
140
- # is empty and piping it to `jq` produces a parse error on stderr while
141
- # leaving LATEST_OK / LATEST_REASON as empty strings. Fail the check with a
142
- # meaningful reason instead of a blank diagnostic.
143
- if [ -n "$LATEST_RESULT" ]; then
144
- LATEST_OK="$(printf '%s' "$LATEST_RESULT" | jq -r '.ok // false')"
145
- LATEST_VERSION="$(printf '%s' "$LATEST_RESULT" | jq -r '.version // empty')"
146
- LATEST_REASON="$(printf '%s' "$LATEST_RESULT" | jq -r '.reason // empty')"
147
- else
148
- LATEST_OK=false
149
- LATEST_VERSION=""
150
- LATEST_REASON="script_not_found_or_node_unavailable"
151
- fi
147
+ fi
148
+ # #2993 CR: when node is missing or the script doesn't exist, LATEST_RESULT
149
+ # is empty. Fail the check with a meaningful reason instead of a blank
150
+ # diagnostic.
151
+ if [ -n "$LATEST_RESULT" ]; then
152
+ LATEST_OK="$(uc_field ok "$LATEST_RESULT")"
153
+ LATEST_OK="${LATEST_OK:-false}"
154
+ LATEST_VERSION="$(uc_field version "$LATEST_RESULT")"
155
+ LATEST_REASON="$(uc_field reason "$LATEST_RESULT")"
156
+ else
157
+ LATEST_OK=false
158
+ LATEST_VERSION=""
159
+ LATEST_REASON="script_not_found_or_node_unavailable"
152
160
  fi
153
161
  ```
154
162
 
@@ -305,8 +313,8 @@ detected in `get_installed_version`:
305
313
 
306
314
  ```bash
307
315
  # RUNTIME_DIR is the resolved config directory (e.g. ~/.config/opencode, ~/.gemini).
308
- # get_installed_version emits it as GSD_DIR (LOCAL or GLOBAL install dir, or empty
309
- # when scope is UNKNOWN). Empty RUNTIME_DIR skips the backup below.
316
+ # get_installed_version emits it as GSD_DIR for a resolved LOCAL or GLOBAL install.
317
+ # The unresolved-target gate exits before this step; the empty guard remains defensive.
310
318
  RUNTIME_DIR="$GSD_DIR"
311
319
  ```
312
320
 
@@ -387,11 +395,6 @@ npx -y --package=@opengsd/gsd-core@"$TAG" -- gsd-core "$RUNTIME_FLAG" --local
387
395
  npx -y --package=@opengsd/gsd-core@"$TAG" -- gsd-core "$RUNTIME_FLAG" --global
388
396
  ```
389
397
 
390
- **If UNKNOWN install:**
391
- ```bash
392
- npx -y --package=@opengsd/gsd-core@"$TAG" -- gsd-core --claude --global
393
- ```
394
-
395
398
  Capture output. If install fails, show error and exit.
396
399
 
397
400
  Clear the update cache so statusline indicator disappears:
@@ -23,7 +23,7 @@ if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
23
23
  AGENT_SKILLS_AUDITOR=$(gsd_run query agent-skills gsd-nyquist-auditor)
24
24
  ```
25
25
 
26
- **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.
26
+ **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.
27
27
 
28
28
  Parse: `phase_dir`, `phase_number`, `phase_name`, `phase_slug`, `padded_phase`.
29
29