@opengsd/gsd-core 1.16.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 (211) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -2
  3. package/.opencode/plugins/gsd-core.js +251 -1
  4. package/.opencode/plugins/gsd-statusline/tui.tsx +55 -0
  5. package/GEMINI.md +1 -1
  6. package/agents/gsd-advisor-researcher.compact.md +1 -1
  7. package/agents/gsd-advisor-researcher.md +1 -1
  8. package/agents/gsd-assumptions-analyzer.compact.md +1 -1
  9. package/agents/gsd-assumptions-analyzer.md +1 -1
  10. package/agents/gsd-code-fixer.compact.md +1 -1
  11. package/agents/gsd-code-fixer.md +1 -1
  12. package/agents/gsd-code-reviewer.compact.md +1 -1
  13. package/agents/gsd-code-reviewer.md +1 -1
  14. package/agents/gsd-codebase-mapper.compact.md +1 -1
  15. package/agents/gsd-codebase-mapper.md +1 -1
  16. package/agents/gsd-debug-session-manager.compact.md +6 -0
  17. package/agents/gsd-debug-session-manager.md +6 -0
  18. package/agents/gsd-debugger.md +1 -1
  19. package/agents/gsd-doc-verifier.compact.md +4 -4
  20. package/agents/gsd-doc-verifier.md +4 -3
  21. package/agents/gsd-doc-writer.compact.md +23 -5
  22. package/agents/gsd-doc-writer.md +19 -6
  23. package/agents/gsd-eval-auditor.compact.md +1 -1
  24. package/agents/gsd-eval-auditor.md +1 -1
  25. package/agents/gsd-executor.md +7 -3
  26. package/agents/gsd-integration-checker.compact.md +1 -1
  27. package/agents/gsd-integration-checker.md +1 -1
  28. package/agents/gsd-nyquist-auditor.compact.md +1 -1
  29. package/agents/gsd-nyquist-auditor.md +1 -1
  30. package/agents/gsd-phase-researcher.md +1 -1
  31. package/agents/gsd-plan-checker.md +2 -1
  32. package/agents/gsd-planner.md +5 -4
  33. package/agents/gsd-project-researcher.compact.md +1 -1
  34. package/agents/gsd-project-researcher.md +1 -1
  35. package/agents/gsd-research-synthesizer.compact.md +5 -5
  36. package/agents/gsd-research-synthesizer.md +5 -5
  37. package/agents/gsd-roadmapper.compact.md +1 -1
  38. package/agents/gsd-roadmapper.md +1 -1
  39. package/agents/gsd-security-auditor.compact.md +1 -1
  40. package/agents/gsd-security-auditor.md +1 -1
  41. package/agents/gsd-ui-auditor.compact.md +1 -1
  42. package/agents/gsd-ui-auditor.md +1 -1
  43. package/agents/gsd-ui-checker.compact.md +1 -1
  44. package/agents/gsd-ui-checker.md +1 -1
  45. package/agents/gsd-ui-researcher.compact.md +13 -1
  46. package/agents/gsd-ui-researcher.md +28 -1
  47. package/agents/gsd-verifier.md +1 -1
  48. package/bin/gsd-mcp-server.js +3 -1
  49. package/bin/install.js +398 -95
  50. package/commands/gsd/code-review.md +8 -0
  51. package/commands/gsd/pause-work.md +1 -1
  52. package/gsd-core/bin/gsd-tools.cjs +34 -12
  53. package/gsd-core/bin/lib/adr-parser.cjs +37 -5
  54. package/gsd-core/bin/lib/capability-ledger.cjs +25 -0
  55. package/gsd-core/bin/lib/capability-lock.cjs +29 -7
  56. package/gsd-core/bin/lib/capability-registry.cjs +138 -80
  57. package/gsd-core/bin/lib/capability-source.cjs +3 -1
  58. package/gsd-core/bin/lib/capability-state.cjs +6 -3
  59. package/gsd-core/bin/lib/capability-validator.cjs +8 -5
  60. package/gsd-core/bin/lib/capability-writer.cjs +2 -1
  61. package/gsd-core/bin/lib/commands.cjs +33 -9
  62. package/gsd-core/bin/lib/config-loader.cjs +3 -3
  63. package/gsd-core/bin/lib/decision-coverage-support.cjs +19 -0
  64. package/gsd-core/bin/lib/gate-context-drift.cjs +5 -4
  65. package/gsd-core/bin/lib/gate-decision-coverage-plan.cjs +15 -0
  66. package/gsd-core/bin/lib/gate-decision-coverage-verify.cjs +13 -0
  67. package/gsd-core/bin/lib/gate-predicate-evaluator.cjs +23 -3
  68. package/gsd-core/bin/lib/gate-predicate.cjs +8 -12
  69. package/gsd-core/bin/lib/gate-tdd-review-checkpoint.cjs +60 -5
  70. package/gsd-core/bin/lib/gate-ui-safety.cjs +2 -1
  71. package/gsd-core/bin/lib/graphify.cjs +3 -2
  72. package/gsd-core/bin/lib/init.cjs +43 -16
  73. package/gsd-core/bin/lib/install-effort-resolver.cjs +2 -2
  74. package/gsd-core/bin/lib/install-engine.cjs +11 -0
  75. package/gsd-core/bin/lib/install-model-override-resolver.cjs +4 -3
  76. package/gsd-core/bin/lib/install-profiles.cjs +5 -3
  77. package/gsd-core/bin/lib/install-shadow-report.cjs +12 -0
  78. package/gsd-core/bin/lib/installer-migrations/011-opencode-mcp-no-prompts.cjs +99 -0
  79. package/gsd-core/bin/lib/installer-migrations.cjs +54 -1
  80. package/gsd-core/bin/lib/jsonc-edit.cjs +418 -0
  81. package/gsd-core/bin/lib/loop-host-contract.cjs +2 -1
  82. package/gsd-core/bin/lib/mcp-server.cjs +5 -1
  83. package/gsd-core/bin/lib/opencode-mcp-entry.cjs +39 -0
  84. package/gsd-core/bin/lib/opencode-version.cjs +486 -0
  85. package/gsd-core/bin/lib/phase.cjs +141 -11
  86. package/gsd-core/bin/lib/quick-batch-dispatch.cjs +2 -1
  87. package/gsd-core/bin/lib/report-parser.cjs +57 -1
  88. package/gsd-core/bin/lib/review-lane-invocation.cjs +40 -0
  89. package/gsd-core/bin/lib/review-reviewer-selection.cjs +20 -1
  90. package/gsd-core/bin/lib/roadmap.cjs +56 -5
  91. package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +142 -6
  92. package/gsd-core/bin/lib/runtime-homes.cjs +18 -0
  93. package/gsd-core/bin/lib/runtime-hooks-surface.cjs +58 -3
  94. package/gsd-core/bin/lib/runtime-slash.cjs +13 -0
  95. package/gsd-core/bin/lib/shell-command-projection.cjs +2 -0
  96. package/gsd-core/bin/lib/smart-entry.cjs +100 -53
  97. package/gsd-core/bin/lib/state-document.cjs +37 -3
  98. package/gsd-core/bin/lib/state-transition.cjs +98 -19
  99. package/gsd-core/bin/lib/state.cjs +544 -126
  100. package/gsd-core/bin/lib/task-command-router.cjs +2 -3
  101. package/gsd-core/bin/lib/tdd-red-evidence.cjs +5 -0
  102. package/gsd-core/bin/lib/tdd-test-files.cjs +42 -0
  103. package/gsd-core/bin/lib/update-context.cjs +18 -1
  104. package/gsd-core/bin/lib/verification.cjs +107 -17
  105. package/gsd-core/bin/lib/verify.cjs +36 -1
  106. package/gsd-core/references/agent-contracts.md +1 -1
  107. package/gsd-core/references/agent-skills-bootstrap.md +26 -7
  108. package/gsd-core/references/git-integration.md +3 -3
  109. package/gsd-core/references/planner-reversibility.md +18 -4
  110. package/gsd-core/references/planner-verify-command-grounding.md +18 -0
  111. package/gsd-core/references/tdd.md +4 -3
  112. package/gsd-core/references/verifier-phase-gates.md +8 -1
  113. package/gsd-core/workflows/add-backlog.md +10 -2
  114. package/gsd-core/workflows/add-phase.md +5 -1
  115. package/gsd-core/workflows/add-tests.md +5 -1
  116. package/gsd-core/workflows/add-todo.md +5 -1
  117. package/gsd-core/workflows/ai-integration-phase.md +5 -1
  118. package/gsd-core/workflows/audit-milestone.md +21 -9
  119. package/gsd-core/workflows/autonomous/templates/minimal-context.md +41 -0
  120. package/gsd-core/workflows/autonomous.md +52 -64
  121. package/gsd-core/workflows/check-todos.md +5 -1
  122. package/gsd-core/workflows/code-review-fix.md +5 -1
  123. package/gsd-core/workflows/code-review.md +21 -2
  124. package/gsd-core/workflows/complete-milestone/detail/elaboration.md +5 -1
  125. package/gsd-core/workflows/complete-milestone.md +25 -5
  126. package/gsd-core/workflows/debug.md +5 -1
  127. package/gsd-core/workflows/diagnose-issues.md +5 -1
  128. package/gsd-core/workflows/discuss-phase/modes/chain.md +5 -1
  129. package/gsd-core/workflows/discuss-phase-assumptions.md +11 -3
  130. package/gsd-core/workflows/discuss-phase.md +5 -1
  131. package/gsd-core/workflows/docs-update/detail/elaboration.md +7 -6
  132. package/gsd-core/workflows/docs-update.md +27 -5
  133. package/gsd-core/workflows/edit-phase.md +5 -1
  134. package/gsd-core/workflows/eval-review.md +11 -3
  135. package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +5 -1
  136. package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +1 -1
  137. package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +12 -6
  138. package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +19 -2
  139. package/gsd-core/workflows/execute-phase/steps/regression-gate.md +18 -1
  140. package/gsd-core/workflows/execute-phase/steps/verify-phase-goal.md +5 -1
  141. package/gsd-core/workflows/execute-phase.md +40 -7
  142. package/gsd-core/workflows/execute-plan.md +8 -4
  143. package/gsd-core/workflows/extract-learnings.md +6 -2
  144. package/gsd-core/workflows/forensics.md +5 -1
  145. package/gsd-core/workflows/import.md +5 -1
  146. package/gsd-core/workflows/insert-phase.md +5 -1
  147. package/gsd-core/workflows/manager.md +10 -2
  148. package/gsd-core/workflows/map-codebase.md +5 -1
  149. package/gsd-core/workflows/milestone-summary.md +10 -2
  150. package/gsd-core/workflows/new-milestone.md +13 -5
  151. package/gsd-core/workflows/new-project.md +9 -5
  152. package/gsd-core/workflows/next.md +8 -4
  153. package/gsd-core/workflows/pause-work.md +3 -3
  154. package/gsd-core/workflows/plan-phase/steps/auto-advance-check.md +70 -0
  155. package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +5 -1
  156. package/gsd-core/workflows/plan-phase/steps/closed-phase-gate.md +6 -2
  157. package/gsd-core/workflows/plan-phase.md +41 -73
  158. package/gsd-core/workflows/plan-review-convergence.md +17 -5
  159. package/gsd-core/workflows/plant-seed.md +8 -4
  160. package/gsd-core/workflows/progress.md +6 -2
  161. package/gsd-core/workflows/quick-batch.md +5 -1
  162. package/gsd-core/workflows/quick.md +22 -7
  163. package/gsd-core/workflows/remove-phase.md +5 -1
  164. package/gsd-core/workflows/resume-project.md +19 -11
  165. package/gsd-core/workflows/review.md +6 -2
  166. package/gsd-core/workflows/scan.md +5 -1
  167. package/gsd-core/workflows/secure-phase.md +6 -2
  168. package/gsd-core/workflows/settings.md +3 -3
  169. package/gsd-core/workflows/ship.md +13 -10
  170. package/gsd-core/workflows/thread.md +10 -2
  171. package/gsd-core/workflows/transition.md +5 -1
  172. package/gsd-core/workflows/ui-phase.md +5 -1
  173. package/gsd-core/workflows/ui-review.md +6 -2
  174. package/gsd-core/workflows/ultraplan-phase.md +5 -1
  175. package/gsd-core/workflows/update.md +5 -1
  176. package/gsd-core/workflows/validate-phase.md +6 -2
  177. package/gsd-core/workflows/verify-work/detail/elaboration.md +1 -1
  178. package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +5 -1
  179. package/gsd-core/workflows/verify-work.md +11 -3
  180. package/hooks/dist/gsd-agent-isolation-guard.js +3 -1
  181. package/hooks/dist/gsd-check-update.js +26 -20
  182. package/hooks/dist/gsd-context-monitor.js +14 -5
  183. package/hooks/dist/gsd-cursor-subagent-start.js +2 -1
  184. package/hooks/dist/gsd-secret-read-guard.js +189 -15
  185. package/hooks/dist/gsd-statusline.js +321 -22
  186. package/hooks/dist/gsd-validate-commit.sh +27 -5
  187. package/hooks/gsd-agent-isolation-guard.js +3 -1
  188. package/hooks/gsd-check-update.js +26 -20
  189. package/hooks/gsd-context-monitor.js +14 -5
  190. package/hooks/gsd-cursor-subagent-start.js +2 -1
  191. package/hooks/gsd-secret-read-guard.js +189 -15
  192. package/hooks/gsd-statusline.js +321 -22
  193. package/hooks/gsd-validate-commit.sh +27 -5
  194. package/package.json +1 -1
  195. package/scripts/audit-workflow-script-paths.cjs +1 -1
  196. package/scripts/diagnose-validate-commit-in-tier.cjs +270 -0
  197. package/scripts/diagnose-validate-commit-phases.cjs +348 -0
  198. package/scripts/diagnose-windows-spawn-watcher.cjs +317 -0
  199. package/scripts/docs-guard-registry.cjs +2 -0
  200. package/scripts/gen-capability-registry.cjs +7 -1
  201. package/scripts/gen-loop-host-contract.cjs +4 -1
  202. package/scripts/lib/macos-conformance-tier.generated.cjs +13 -0
  203. package/scripts/lib/platform-conformance-tier.generated.cjs +26 -0
  204. package/scripts/lint-phase-arg-assignment.cjs +2 -2
  205. package/scripts/lint-workflow-shellcheck-baseline.json +16 -16
  206. package/scripts/mutation-matrix.cjs +3 -1
  207. package/scripts/release-tarball-smoke.cjs +15 -7
  208. package/scripts/xtrace-phase-timing.cjs +205 -0
  209. package/skills/gsd-code-review/SKILL.md +8 -0
  210. package/skills/gsd-pause-work/SKILL.md +1 -1
  211. package/vscode/package.json +1 -1
@@ -24,10 +24,14 @@ Valid GSD subagent types (use exact names — do not fall back to 'general-purpo
24
24
  Parse arguments and load project state:
25
25
 
26
26
  ```bash
27
+ IFS= read -rd '' GSD_ARGS <<'GSD_ARGS_END' || true
28
+ $ARGUMENTS
29
+ GSD_ARGS_END
30
+ GSD_ARGS=${GSD_ARGS%$'\n'}
27
31
  _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
28
32
  RESPONSE_LANGUAGE=$(gsd_run query config-get response_language --raw --default "" 2>/dev/null || echo "")
29
- PHASE_ARG=$(echo "$ARGUMENTS" | sed -E 's/--ws[[:space:]]+[A-Za-z0-9._-]+//g' | xargs)
30
- GSD_WS=$(echo " $ARGUMENTS" | sed -nE 's/.* --ws +([A-Za-z0-9][A-Za-z0-9._-]*).*/--ws \1/p' | head -n 1)
33
+ PHASE_ARG=$(echo "$GSD_ARGS" | sed -E 's/--ws[[:space:]]+[A-Za-z0-9._-]+//g' | xargs)
34
+ GSD_WS=$(echo " $GSD_ARGS" | sed -nE 's/.* --ws +([A-Za-z0-9][A-Za-z0-9._-]*).*/--ws \1/p' | head -n 1)
31
35
  INIT=$(gsd_run query init.phase-op ${GSD_WS:+--ws=${GSD_WS##* }} "${PHASE_ARG}")
32
36
  if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
33
37
  ```
@@ -100,9 +104,9 @@ Verify the work is ready to ship:
100
104
  ```
101
105
  If `gh` not found or not authenticated: provide setup instructions and exit.
102
106
 
103
- 6. **Capability ship gates (generic dispatch).**
107
+ 6. **Capability ship gates and steps (generic dispatch).**
104
108
 
105
- Resolve active `ship:pre` gate hooks from the capability registry — the registry evaluates each hook's `when` condition, so do **not** read `workflow.security_enforcement` or `workflow.windows_enforce` directly:
109
+ Resolve active `ship:pre` hooks from the capability registry — the registry evaluates each hook's `when` condition, so do **not** read `workflow.security_enforcement` or `workflow.windows_enforce` directly:
106
110
 
107
111
  ```bash
108
112
  SHIP_PRE_HOOKS_JSON=$(gsd_run loop render-hooks ship:pre --raw)
@@ -113,7 +117,7 @@ Verify the work is ready to ship:
113
117
 
114
118
  **If `activeHooks` is empty or absent:** skip this check silently and continue to the next preflight step. A capability whose `when` is off contributes no entry, and one that failed to load fails OPEN with its own warning from the resolver — neither is a block.
115
119
 
116
- **For each active entry where `kind == "gate"`** (process in array order), following `gsd-core/references/loop-hook-dispatch.md`. Entries of any other `kind` are not gates and are not enforced here. Every gate is visited exactly once by this loop — the named branches below are specializations *within* it, never a separate pass, so no gate is evaluated twice.
120
+ **For each active entry where `kind == "gate"`** (process in array order), following `gsd-core/references/loop-hook-dispatch.md`. Every gate is visited exactly once by this loop — the named branches below are specializations *within* it, never a separate pass, so no gate is evaluated twice.
117
121
 
118
122
  **Step 1 — evaluate the gate's `check`.** Dispatch by check shape; read the hook's `check` object in-context to pick the branch (the registry validates exactly one of `query`/`predicate`/`agentVerdict`). Two capability IDs carry a bespoke evaluation whose fail-closed semantics the declared predicate alone does not reproduce — take their branch, then rejoin at step 2:
119
123
 
@@ -185,7 +189,7 @@ Verify the work is ready to ship:
185
189
  - **`blocking == false`** (advisory) → never halts. If `block == true` or the result carries a non-empty message, print `⚠ {hook.capId} advisory: {message}`, then continue.
186
190
  - **`blocking == true` and `block == false`** → continue silently.
187
191
 
188
- **When every active gate has been processed without a halt:** continue to the next preflight check.
192
+ **Step dispatch** (unless a blocking gate halted): `kind == "step"` per @gsd-core/references/loop-hook-dispatch.md; honor `consumes` (skip the hook if a consumed artifact is absent) and treat `onError: halt` as `skip` with a warning, since steps never block ship. ⚠ Validate `ref.command` and `ref.agent` (against `^[A-Za-z0-9][A-Za-z0-9._-]*$`) in-context before any shell use or dispatch; skip a hook that fails. Then continue to the `push_branch` step.
189
193
  </step>
190
194
 
191
195
  <step name="push_branch">
@@ -496,17 +500,16 @@ git push origin ${CURRENT_BRANCH} 2>&1 || echo "⚠ track_shipping: ship-note pu
496
500
  # Poll mergeStateStatus with backoff to avoid racing GitHub's async state computation.
497
501
  # Note: Skip tokens recognized by GitHub Actions are [skip ci], [ci skip], [no ci], [skip actions], [actions skip], and skip-checks:true.
498
502
  # The recovery commit message MUST NOT contain any of these tokens.
503
+ # Recovery keys on the head only (BLOCKED, zero checks, skip token), never the review state (#4993).
499
504
 
500
505
  STATUS="UNKNOWN"
501
506
  CHECKS=0
502
- REVIEW_DECISION=""
503
507
  for i in {1..5}; do
504
- PR_STATE=$(gh pr view ${PR_NUMBER} --json headRefOid,mergeStateStatus,statusCheckRollup,reviewDecision -q '{head: .headRefOid, status: .mergeStateStatus, checks: ((.statusCheckRollup // []) | length), review: (.reviewDecision // "")}' 2>/dev/null || echo '{"head":"","status":"UNKNOWN","checks":0,"review":""}')
508
+ PR_STATE=$(gh pr view ${PR_NUMBER} --json headRefOid,mergeStateStatus,statusCheckRollup -q '{head: .headRefOid, status: .mergeStateStatus, checks: ((.statusCheckRollup // []) | length)}' 2>/dev/null || echo '{"head":"","status":"UNKNOWN","checks":0}')
505
509
  HEAD_OID=$(echo "$PR_STATE" | jq -r .head)
506
510
  if [ "$HEAD_OID" = "$SHIP_NOTE_SHA" ]; then
507
511
  STATUS=$(echo "$PR_STATE" | jq -r .status)
508
512
  CHECKS=$(echo "$PR_STATE" | jq -r .checks)
509
- REVIEW_DECISION=$(echo "$PR_STATE" | jq -r .review)
510
513
  fi
511
514
  if [ "$HEAD_OID" = "$SHIP_NOTE_SHA" ] && [ "$STATUS" != "UNKNOWN" ]; then
512
515
  break
@@ -514,7 +517,7 @@ for i in {1..5}; do
514
517
  sleep 3
515
518
  done
516
519
 
517
- if [ "$STATUS" = "BLOCKED" ] && [ "$CHECKS" = "0" ] && [ "$REVIEW_DECISION" != "REVIEW_REQUIRED" ] && [ "$REVIEW_DECISION" != "CHANGES_REQUESTED" ] && git log -1 --format=%B "$SHIP_NOTE_SHA" | grep -q '\[ci skip\]'; then
520
+ if [ "$STATUS" = "BLOCKED" ] && [ "$CHECKS" = "0" ] && git log -1 --format=%B "$SHIP_NOTE_SHA" | grep -q '\[ci skip\]'; then
518
521
  echo "⚠ PR is BLOCKED with zero checks. The [ci skip] trailer wedged the PR due to required checks."
519
522
  echo "Pushing an empty commit to trigger the required pipelines..."
520
523
  # gsd_run query commit requires a file list; use git directly for this intentionally empty commit.
@@ -148,7 +148,11 @@ If $ARGUMENTS is a new description (no matching thread file):
148
148
 
149
149
  1. Generate slug from description:
150
150
  ```bash
151
- SLUG=$(gsd_run query generate-slug "$ARGUMENTS" --raw)
151
+ IFS= read -rd '' GSD_ARGS <<'GSD_ARGS_END' || true
152
+ $ARGUMENTS
153
+ GSD_ARGS_END
154
+ GSD_ARGS=${GSD_ARGS%$'\n'}
155
+ SLUG=$(gsd_run query generate-slug "$GSD_ARGS" --raw)
152
156
  ```
153
157
 
154
158
  2. Create the threads directory if needed:
@@ -192,7 +196,11 @@ updated: {today ISO date}
192
196
 
193
197
  5. Commit:
194
198
  ```bash
195
- gsd_run query commit "docs: create thread — ${ARGUMENTS}" --files ".planning/threads/${SLUG}.md"
199
+ IFS= read -rd '' GSD_ARGS <<'GSD_ARGS_END' || true
200
+ $ARGUMENTS
201
+ GSD_ARGS_END
202
+ GSD_ARGS=${GSD_ARGS%$'\n'}
203
+ gsd_run query commit "docs: create thread — ${GSD_ARGS}" --files ".planning/threads/${SLUG}.md"
196
204
  ```
197
205
 
198
206
  6. Report:
@@ -446,7 +446,11 @@ This returns all phases with goals, disk status, and completion info.
446
446
  **Section-manifest gate (#2994):** `gsd_run` is already established above (`verify_completion` step) — fetch the dedicated `init.transition` bundle for the workstream-collision-check gate below:
447
447
 
448
448
  ```bash
449
- GSD_WS=$(echo " $ARGUMENTS" | sed -nE 's/.* --ws +([A-Za-z0-9][A-Za-z0-9._-]*).*/--ws \1/p' | head -n 1)
449
+ IFS= read -rd '' GSD_ARGS <<'GSD_ARGS_END' || true
450
+ $ARGUMENTS
451
+ GSD_ARGS_END
452
+ GSD_ARGS=${GSD_ARGS%$'\n'}
453
+ GSD_WS=$(echo " $GSD_ARGS" | sed -nE 's/.* --ws +([A-Za-z0-9][A-Za-z0-9._-]*).*/--ws \1/p' | head -n 1)
450
454
  INIT_TRANSITION=$(gsd_run query init.transition ${GSD_WS:+--ws=${GSD_WS##* }})
451
455
  if [[ "$INIT_TRANSITION" == @file:* ]]; then INIT_TRANSITION=$(cat "${INIT_TRANSITION#@file:}"); fi
452
456
  ```
@@ -19,8 +19,12 @@ Valid GSD subagent types (use exact names — do not fall back to 'general-purpo
19
19
  ## 1. Initialize
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.plan-phase ${GSD_WS:+--ws=${GSD_WS##* }} "$PHASE")
25
29
  if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
26
30
  AGENT_SKILLS_UI=$(gsd_run query agent-skills gsd-ui-researcher ${GSD_WS:+--ws=${GSD_WS##* }})
@@ -16,10 +16,14 @@ Valid GSD subagent types (use exact names — do not fall back to 'general-purpo
16
16
  ## 0. Initialize
17
17
 
18
18
  ```bash
19
+ IFS= read -rd '' GSD_ARGS <<'GSD_ARGS_END' || true
20
+ $ARGUMENTS
21
+ GSD_ARGS_END
22
+ GSD_ARGS=${GSD_ARGS%$'\n'}
19
23
  _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
20
24
  RESPONSE_LANGUAGE=$(gsd_run query config-get response_language --raw --default "" 2>/dev/null || echo "")
21
- PHASE_ARG=$(echo "$ARGUMENTS" | sed -E 's/--ws[[:space:]]+[A-Za-z0-9._-]+//g' | xargs)
22
- GSD_WS=$(echo " $ARGUMENTS" | sed -nE 's/.* --ws +([A-Za-z0-9][A-Za-z0-9._-]*).*/--ws \1/p' | head -n 1)
25
+ PHASE_ARG=$(echo "$GSD_ARGS" | sed -E 's/--ws[[:space:]]+[A-Za-z0-9._-]+//g' | xargs)
26
+ GSD_WS=$(echo " $GSD_ARGS" | sed -nE 's/.* --ws +([A-Za-z0-9][A-Za-z0-9._-]*).*/--ws \1/p' | head -n 1)
23
27
  INIT=$(gsd_run query init.phase-op ${GSD_WS:+--ws=${GSD_WS##* }} "${PHASE_ARG}")
24
28
  if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
25
29
  AGENT_SKILLS_UI_REVIEWER=$(gsd_run query agent-skills gsd-ui-auditor ${GSD_WS:+--ws=${GSD_WS##* }})
@@ -64,8 +64,12 @@ unplanned phase from the roadmap (same logic as /gsd:plan-phase).
64
64
  Load GSD phase context:
65
65
 
66
66
  ```bash
67
+ IFS= read -rd '' GSD_ARGS <<'GSD_ARGS_END' || true
68
+ $ARGUMENTS
69
+ GSD_ARGS_END
70
+ GSD_ARGS=${GSD_ARGS%$'\n'}
67
71
  _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
68
- GSD_WS=$(echo " $ARGUMENTS" | sed -nE 's/.* --ws +([A-Za-z0-9][A-Za-z0-9._-]*).*/--ws \1/p' | head -n 1)
72
+ GSD_WS=$(echo " $GSD_ARGS" | sed -nE 's/.* --ws +([A-Za-z0-9][A-Za-z0-9._-]*).*/--ws \1/p' | head -n 1)
69
73
  INIT=$(gsd_run query init.plan-phase ${GSD_WS:+--ws=${GSD_WS##* }} "$PHASE")
70
74
  if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
71
75
  ```
@@ -99,7 +99,11 @@ Exit.
99
99
  Determine the release channel from `$ARGUMENTS`. This selects which npm dist-tag the entire update flow targets — `latest` (stable) by default, or `next` (the RC channel established by ADR #660) when the user opts in with `--next`/`--rc`:
100
100
 
101
101
  ```bash
102
- case " $ARGUMENTS " in
102
+ IFS= read -rd '' GSD_ARGS <<'GSD_ARGS_END' || true
103
+ $ARGUMENTS
104
+ GSD_ARGS_END
105
+ GSD_ARGS=${GSD_ARGS%$'\n'}
106
+ case " $GSD_ARGS " in
103
107
  *" --next "*|*" --rc "*)
104
108
  TAG="next"
105
109
  CHANNEL_LABEL="next (RC)"
@@ -16,10 +16,14 @@ Valid GSD subagent types (use exact names — do not fall back to 'general-purpo
16
16
  ## 0. Initialize
17
17
 
18
18
  ```bash
19
+ IFS= read -rd '' GSD_ARGS <<'GSD_ARGS_END' || true
20
+ $ARGUMENTS
21
+ GSD_ARGS_END
22
+ GSD_ARGS=${GSD_ARGS%$'\n'}
19
23
  _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
20
24
  RESPONSE_LANGUAGE=$(gsd_run query config-get response_language --raw --default "" 2>/dev/null || echo "")
21
- PHASE_ARG=$(echo "$ARGUMENTS" | sed -E 's/--ws[[:space:]]+[A-Za-z0-9._-]+//g' | xargs)
22
- GSD_WS=$(echo " $ARGUMENTS" | sed -nE 's/.* --ws +([A-Za-z0-9][A-Za-z0-9._-]*).*/--ws \1/p' | head -n 1)
25
+ PHASE_ARG=$(echo "$GSD_ARGS" | sed -E 's/--ws[[:space:]]+[A-Za-z0-9._-]+//g' | xargs)
26
+ GSD_WS=$(echo " $GSD_ARGS" | sed -nE 's/.* --ws +([A-Za-z0-9][A-Za-z0-9._-]*).*/--ws \1/p' | head -n 1)
23
27
  INIT=$(gsd_run query init.phase-op ${GSD_WS:+--ws=${GSD_WS##* }} "${PHASE_ARG}")
24
28
  if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
25
29
  AGENT_SKILLS_AUDITOR=$(gsd_run query agent-skills gsd-nyquist-auditor ${GSD_WS:+--ws=${GSD_WS##* }})
@@ -267,4 +267,4 @@ alongside `pending_count`/`blocked_count`/`skipped_no_reason`, and its issues-ro
267
267
  (`diagnose_issues` vs. proceeding toward promotion) reads `unresolved_issues`, not the raw
268
268
  `issues` count from `## Summary`. The raw `issues` count is unchanged and still drives the
269
269
  presented summary numbers.
270
- ```
270
+ ```
@@ -8,9 +8,13 @@
8
8
  **User-story format guard.** When `MVP_MODE=true`, also verify the phase's goal is in User Story format via the centralized validator:
9
9
 
10
10
  ```bash
11
+ IFS= read -rd '' GSD_ARGS <<'GSD_ARGS_END' || true
12
+ $ARGUMENTS
13
+ GSD_ARGS_END
14
+ GSD_ARGS=${GSD_ARGS%$'\n'}
11
15
  _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
16
  # #4772: this file is Read, not textually substituted - write the parent command's original argument string in place of $ARGUMENTS so --ws reaches the query.
13
- GSD_WS=$(echo " $ARGUMENTS" | sed -nE 's/.* --ws +([A-Za-z0-9][A-Za-z0-9._-]*).*/--ws \1/p' | head -n 1)
17
+ GSD_WS=$(echo " $GSD_ARGS" | sed -nE 's/.* --ws +([A-Za-z0-9][A-Za-z0-9._-]*).*/--ws \1/p' | head -n 1)
14
18
  PHASE_GOAL=$(gsd_run query roadmap.get-phase "${phase_number}" ${GSD_WS:+--ws=${GSD_WS##* }} --pick goal)
15
19
  USER_STORY_VALID=$(gsd_run query user-story.validate --story "$PHASE_GOAL" --pick valid)
16
20
  if [ "$USER_STORY_VALID" != "true" ]; then
@@ -39,10 +39,14 @@ No Pass/Fail buttons. No severity questions. Just: "Here's what should happen. D
39
39
  If $ARGUMENTS contains a phase number, load context:
40
40
 
41
41
  ```bash
42
+ IFS= read -rd '' GSD_ARGS <<'GSD_ARGS_END' || true
43
+ $ARGUMENTS
44
+ GSD_ARGS_END
45
+ GSD_ARGS=${GSD_ARGS%$'\n'}
42
46
  _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
43
47
  GSD_WS=""
44
- echo "$ARGUMENTS" | grep -qE -- '--ws[[:space:]]+[A-Za-z0-9._-]+' && GSD_WS=$(echo "$ARGUMENTS" | grep -oE -- '--ws[[:space:]]+[A-Za-z0-9._-]+')
45
- PHASE_ARG=$(echo "$ARGUMENTS" | sed -E 's/--ws[[:space:]]+[A-Za-z0-9._-]+//g' | xargs)
48
+ echo "$GSD_ARGS" | grep -qE -- '--ws[[:space:]]+[A-Za-z0-9._-]+' && GSD_WS=$(echo "$GSD_ARGS" | grep -oE -- '--ws[[:space:]]+[A-Za-z0-9._-]+')
49
+ PHASE_ARG=$(echo "$GSD_ARGS" | sed -E 's/--ws[[:space:]]+[A-Za-z0-9._-]+//g' | xargs)
46
50
 
47
51
  INIT=$(gsd_run query init.verify-work "${PHASE_ARG}" ${GSD_WS:+--ws=${GSD_WS##* }})
48
52
  if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
@@ -678,7 +682,11 @@ itself (its only write is the canonicalization, #4663).
678
682
  Load the step's inputs through the SAME bundle execute-phase loads, then include the step:
679
683
 
680
684
  ```bash
681
- GSD_WS=$(echo " $ARGUMENTS" | sed -nE 's/.* --ws +([A-Za-z0-9][A-Za-z0-9._-]*).*/--ws \1/p' | head -n 1)
685
+ IFS= read -rd '' GSD_ARGS <<'GSD_ARGS_END' || true
686
+ $ARGUMENTS
687
+ GSD_ARGS_END
688
+ GSD_ARGS=${GSD_ARGS%$'\n'}
689
+ GSD_WS=$(echo " $GSD_ARGS" | sed -nE 's/.* --ws +([A-Za-z0-9][A-Za-z0-9._-]*).*/--ws \1/p' | head -n 1)
682
690
  EXECUTE_INIT=$(gsd_run query init.execute-phase "{phase}" ${GSD_WS:+--ws=${GSD_WS##* }})
683
691
  if [[ "$EXECUTE_INIT" == @file:* ]]; then EXECUTE_INIT=$(cat "${EXECUTE_INIT#@file:}"); fi
684
692
  for _k in phase_dir phase_number verifier_model phase_req_ids requirements_path section_manifest response_language; do
@@ -227,7 +227,9 @@ function resolveRuntimeIdentity(cwd, configPath, resolveRuntimeNameFromCandidate
227
227
  // never a resolution failure (this function only ever throws for the
228
228
  // config.json read above, which the caller's catch already handles).
229
229
  try {
230
- const defaultsPath = path.join(os.homedir(), '.gsd', 'defaults.json');
230
+ // #5017: the store root is GSD_HOME when set and non-blank, else the home
231
+ // directory — the config loader's rule (src/runtime-homes.cts resolveGsdHome).
232
+ const defaultsPath = path.join(process.env.GSD_HOME || os.homedir(), '.gsd', 'defaults.json');
231
233
  const defaultsRaw = fs.readFileSync(defaultsPath, 'utf-8');
232
234
  const defaultsParsed = JSON.parse(defaultsRaw);
233
235
  if (defaultsParsed && typeof defaultsParsed === 'object' && 'runtime' in defaultsParsed) {
@@ -67,26 +67,32 @@ const cacheFile = path.join(cacheDir, updateCacheFileName);
67
67
  const projectVersionFile = path.join(projectConfigDir, 'gsd-core', 'VERSION');
68
68
  const globalVersionFile = path.join(globalConfigDir, 'gsd-core', 'VERSION');
69
69
 
70
- // Ensure cache directory exists
71
- if (!fs.existsSync(cacheDir)) {
70
+ // This background check is advisory. Cache or worker failures must not make
71
+ // the SessionStart hook fail. Recursive mkdir is safe if another session
72
+ // creates the directory concurrently.
73
+ try {
72
74
  fs.mkdirSync(cacheDir, { recursive: true });
73
- }
74
75
 
75
- // Run check in background via a dedicated worker script.
76
- // Spawning a file (rather than node -e '<inline code>') keeps the worker logic
77
- // in plain JS with no template-literal regex-escaping concerns, and makes the
78
- // worker independently testable.
79
- const workerPath = path.join(__dirname, 'gsd-check-update-worker.js');
80
- const child = spawn(process.execPath, [workerPath], {
81
- stdio: 'ignore',
82
- windowsHide: true,
83
- detached: true, // Required on Windows for proper process detachment
84
- env: {
85
- ...process.env,
86
- GSD_CACHE_FILE: cacheFile,
87
- GSD_PROJECT_VERSION_FILE: projectVersionFile,
88
- GSD_GLOBAL_VERSION_FILE: globalVersionFile,
89
- },
90
- });
76
+ // Run check in background via a dedicated worker script.
77
+ // Spawning a file (rather than node -e '<inline code>') keeps the worker logic
78
+ // in plain JS with no template-literal regex-escaping concerns, and makes the
79
+ // worker independently testable.
80
+ const workerPath = path.join(__dirname, 'gsd-check-update-worker.js');
81
+ const child = spawn(process.execPath, [workerPath], {
82
+ stdio: 'ignore',
83
+ windowsHide: true,
84
+ detached: true, // Required on Windows for proper process detachment
85
+ env: {
86
+ ...process.env,
87
+ GSD_CACHE_FILE: cacheFile,
88
+ GSD_PROJECT_VERSION_FILE: projectVersionFile,
89
+ GSD_GLOBAL_VERSION_FILE: globalVersionFile,
90
+ },
91
+ });
91
92
 
92
- child.unref();
93
+ // A spawn failure can also arrive asynchronously on the child process.
94
+ child.on('error', () => {});
95
+ child.unref();
96
+ } catch {
97
+ // Update hints are best effort; a failed check must not interrupt startup.
98
+ }
@@ -14,6 +14,8 @@
14
14
  // Thresholds:
15
15
  // WARNING (remaining <= 35%): Agent should wrap up current task
16
16
  // CRITICAL (remaining <= 25%): Agent should stop immediately and save state
17
+ // "remaining" is of the auto-compact threshold — the statusline puts the
18
+ // bridge on that scale (#4985), so both fire before Claude Code compacts.
17
19
  // Both fire-points are overridable per project via .planning/config.json
18
20
  // (hooks.context_warning_threshold / hooks.context_critical_threshold, #4285);
19
21
  // the values above are the defaults used when the keys are absent or unusable.
@@ -500,23 +502,30 @@ const handleStdinEnd = () => {
500
502
  }
501
503
 
502
504
  // Build advisory warning message (never use imperative commands that
503
- // override user preferences — see #884)
505
+ // override user preferences — see #884). The percentages are of the
506
+ // auto-compact threshold (#4985); the token counts, when the bridge has
507
+ // them, are the ones /context shows (#2451).
508
+ const kTokens = n => `${Math.round(n / 1000)}k`;
509
+ const tokenDetail = Number.isFinite(metrics.used_tokens) && Number.isFinite(metrics.threshold_tokens)
510
+ ? ` (${kTokens(metrics.used_tokens)} of ${kTokens(metrics.threshold_tokens)} usable tokens)`
511
+ : '';
512
+ const usage = `Usage at ${usedPct}%${tokenDetail}. Remaining: ${remaining}%. `;
504
513
  let message;
505
514
  if (isCritical) {
506
515
  message = isGsdActive
507
- ? `CONTEXT CRITICAL: Usage at ${usedPct}%. Remaining: ${remaining}%. ` +
516
+ ? `CONTEXT CRITICAL: ${usage}` +
508
517
  'Context is nearly exhausted. Do NOT start new complex work or write handoff files — ' +
509
518
  'GSD state is already tracked in STATE.md. Inform the user so they can run ' +
510
519
  '/gsd:pause-work at the next natural stopping point.'
511
- : `CONTEXT CRITICAL: Usage at ${usedPct}%. Remaining: ${remaining}%. ` +
520
+ : `CONTEXT CRITICAL: ${usage}` +
512
521
  'Context is nearly exhausted. Inform the user that context is low and ask how they ' +
513
522
  'want to proceed. Do NOT autonomously save state or write handoff files unless the user asks.';
514
523
  } else {
515
524
  message = isGsdActive
516
- ? `CONTEXT WARNING: Usage at ${usedPct}%. Remaining: ${remaining}%. ` +
525
+ ? `CONTEXT WARNING: ${usage}` +
517
526
  'Context is getting limited. Avoid starting new complex work. If not between ' +
518
527
  'defined plan steps, inform the user so they can prepare to pause.'
519
- : `CONTEXT WARNING: Usage at ${usedPct}%. Remaining: ${remaining}%. ` +
528
+ : `CONTEXT WARNING: ${usage}` +
520
529
  'Be aware that context is getting limited. Avoid unnecessary exploration or ' +
521
530
  'starting new complex work.';
522
531
  }
@@ -435,7 +435,8 @@ function resolveFallbackIsolation(root, configPath) {
435
435
  }
436
436
  if (!runtimeId) {
437
437
  try {
438
- const defaultsPath = path.join(os.homedir(), '.gsd', 'defaults.json');
438
+ // #5017: GSD_HOME when set and non-blank, else the home directory — the loader's rule.
439
+ const defaultsPath = path.join(process.env.GSD_HOME || os.homedir(), '.gsd', 'defaults.json');
439
440
  const defaultsParsed = JSON.parse(fs.readFileSync(defaultsPath, 'utf-8'));
440
441
  if (defaultsParsed && typeof defaultsParsed === 'object' && 'runtime' in defaultsParsed) {
441
442
  runtimeId = resolveRuntimeNameFromCandidates(defaultsParsed.runtime) || null;