* fix(#381): make gsd_run launcher reachable in fresh-shell-per-block runtimes On runtimes that execute each fenced bash block in a separate shell process (e.g. Claude Code — documented behavior: each Bash command is a separate process; inline shell functions and exported vars do not persist between calls), the once-per-file gsd_run() function was undefined in every block after the preamble block, and the call was swallowed by `2>/dev/null || echo "{}"` into silent empty state. Fix (budget-neutral session-level resolution): - Ship gsd-core/bin/gsd_run, a POSIX sh wrapper that symlink-resolves its own location and execs the co-located gsd-tools.cjs. Exposed on PATH via the npm `bin` field (global installs) and shipped to local installs via the recursive gsd-core/ copy. - The per-file launcher preamble now appends `export PATH='<bindir>':"$PATH"` to the file named by $CLAUDE_ENV_FILE (Claude Code's documented env-persistence mechanism) so later fresh-shell blocks resolve gsd_run from PATH. Guarded as a strict no-op when CLAUDE_ENV_FILE is unset; the inline gsd_run() definition remains the fallback for all other runtimes. The single-quoted dir neutralizes shell metacharacters at source time. - Propagated via scripts/sync-runtime-launcher.cjs to all launcher-using files. - XL workflow byte budget 93000 -> 93200 (the ~130B clause pushes plan-phase.md to 93135; legitimate content growth, ratchet-up per #717). Regression tests (I)/(J) in runtime-launcher-parity.test.cjs cover wrapper delegation and end-to-end PATH persistence (sourcing the env file with a space-bearing install path). Known limitation: an install path containing a literal single-quote yields a malformed env-file line and falls back to the status quo (no regression); rare on sanitized home directories. Closes #381 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs(#381): add changeset for gsd_run fresh-shell reachability fix Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * test(#381): scope test (J) bare-PATH execution to POSIX (Windows Git Bash exec bit) Windows Git Bash (msys2) does not honor Node's chmod exec bit for PATH-executing extension-less scripts, so the bare `gsd_run` command lookup failed there even though the env-file PATH persistence was correct. The env-file content assertions (the fix's actual cross-platform logic) still run on every platform; only the final source-and-execute sub-step is gated to non-win32. Global installs on Windows are covered by npm's generated bin shim. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
12 KiB
<required_reading> Read all files referenced by the invoking prompt's execution_context before starting. </required_reading>
**Parse arguments:**Check if --repair, --backfill, or --context flags are present in the command arguments.
REPAIR_FLAG=""
BACKFILL_FLAG=""
CONTEXT_MODE=""
if arguments contain "--repair"; then
REPAIR_FLAG="--repair"
fi
if arguments contain "--backfill"; then
BACKFILL_FLAG="--backfill"
fi
if arguments contain "--context"; then
CONTEXT_MODE="true"
fi
If CONTEXT_MODE is set, jump to the context_check step and skip the
integrity validation steps. The two modes are orthogonal — context utilization
has nothing to do with .planning/ directory health.
The model running this workflow self-reports the current session's
approximate tokensUsed and the active model's contextWindow. Use the values
visible in your runtime (Claude Code's /context slash command output, or the
model's own session telemetry). If the runtime exposes neither, prompt the user
once via AskUserQuestion for both numbers.
TEXT_MODE fallback: when text_mode is true (config or --text flag) the
runtime is non-Claude (Codex, Gemini, etc.) and AskUserQuestion is not
available — replace the prompt with a plain-text two-question sequence
("Approximate tokens used? Context window size?") and read the answers as
plain text from the user's response.
_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}"; if [ -f "$GSD_TOOLS" ]; then gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif command -v gsd-tools >/dev/null 2>&1; then GSD_TOOLS="$(command -v gsd-tools)"; gsd_run() { "$GSD_TOOLS" "$@"; }; elif [ -f "$HOME/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="$HOME/.claude/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; else echo "ERROR: gsd-tools.cjs not found at $GSD_TOOLS and gsd-tools is not on PATH. Run: npx -y @opengsd/gsd-core@latest --claude --local" >&2; exit 1; fi; 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
gsd_run query validate.context \
--tokens-used "$TOKENS_USED" \
--context-window "$CONTEXT_WINDOW"
The query prints a one-line status (Context utilization: NN% (state)) plus
a recommendation line for the warning and critical states. Print the SDK
output verbatim and end the workflow — do not mix in .planning/
health output, the two modes are independent diagnostics.
gsd_run query validate.health $REPAIR_FLAG $BACKFILL_FLAG
Parse JSON output:
status: "healthy" | "degraded" | "broken"errors[]: Critical issues (code, message, fix, repairable)warnings[]: Non-critical issuesinfo[]: Informational notesrepairable_count: Number of auto-fixable issuesrepairs_performed[]: Actions taken if --repair was used
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
GSD Health Check
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Status: HEALTHY | DEGRADED | BROKEN
Errors: N | Warnings: N | Info: N
If repairs were performed:
## Repairs Performed
- ✓ config.json: Created with defaults
- ✓ STATE.md: Regenerated from roadmap
If errors exist:
## Errors
- [E001] config.json: JSON parse error at line 5
Fix: Run /gsd:health --repair to reset to defaults
- [E002] PROJECT.md not found
Fix: Run /gsd:new-project to create
If warnings exist:
## Warnings
- [W002] STATE.md references phase 5, but only phases 1-3 exist
Fix: Review STATE.md manually before changing it; repair will not overwrite an existing STATE.md
- [W005] Phase directory "1-setup" doesn't follow NN-name format
Fix: Rename to match pattern (e.g., 01-setup)
If info exists:
## Info
- [I001] 02-implementation/02-01-PLAN.md has no SUMMARY.md
Note: May be in progress
Footer (if repairable issues exist and --repair was NOT used):
---
N issues can be auto-repaired. Run: /gsd:health --repair
Ask user if they want to run repairs:
Would you like to run /gsd:health --repair to fix N issues automatically?
If yes, re-run with --repair flag and display results.
**If repairs were performed:**Re-run health check without --repair to confirm issues are resolved:
gsd_run query validate.health
Report final status.
<error_codes>
| Code | Severity | Description | Repairable |
|---|---|---|---|
| E001 | error | .planning/ directory not found | No |
| E002 | error | PROJECT.md not found | No |
| E003 | error | ROADMAP.md not found | No |
| E004 | error | STATE.md not found | Yes |
| E005 | error | config.json parse error | Yes |
| W001 | warning | PROJECT.md missing required section | No |
| W002 | warning | STATE.md references invalid phase | No |
| W003 | warning | config.json not found | Yes |
| W004 | warning | config.json invalid field value | No |
| W005 | warning | Phase directory naming mismatch | No |
| W006 | warning | Phase in ROADMAP but no directory | No |
| W007 | warning | Phase on disk but not in ROADMAP | No |
| W008 | warning | config.json: workflow.nyquist_validation absent (defaults to enabled but agents may skip) | Yes |
| W009 | warning | Phase has Validation Architecture in RESEARCH.md but no VALIDATION.md | No |
| W018 | warning | MILESTONES.md missing entry for archived milestone snapshot | Yes (--backfill) |
| W019 | warning | Unrecognized .planning/ root file — not a canonical GSD artifact | No |
| I001 | info | Plan without SUMMARY (may be in progress) | No |
</error_codes>
<repair_actions>
| Action | Effect | Risk |
|---|---|---|
| createConfig | Create config.json with defaults | None |
| resetConfig | Delete + recreate config.json | Loses custom settings |
| regenerateState | Create STATE.md from ROADMAP structure when it is missing | Loses session history |
| addNyquistKey | Add workflow.nyquist_validation: true to config.json | None — matches existing default |
| backfillMilestones | Synthesize missing MILESTONES.md entries from .planning/milestones/vX.Y-ROADMAP.md snapshots |
None — additive only; triggered by --backfill flag |
Not repairable (too risky):
- PROJECT.md, ROADMAP.md content
- Phase directory renaming
- Orphaned plan cleanup
</repair_actions>
<stale_task_cleanup> Windows-specific: Check for stale Claude Code task directories that accumulate on crash/freeze. These are left behind when subagents are force-killed and consume disk space.
When --repair is active, detect and clean up:
# Check for stale task directories (older than 24 hours)
TASKS_DIR="$HOME/.claude/tasks"
if [ -d "$TASKS_DIR" ]; then
STALE_COUNT=$( (find "$TASKS_DIR" -maxdepth 1 -type d -mtime +1 2>/dev/null || true) | wc -l )
if [ "$STALE_COUNT" -gt 0 ]; then
echo "⚠️ Found $STALE_COUNT stale task directories in ~/.claude/tasks/"
echo " These are leftover from crashed subagent sessions."
echo " Run: rm -rf ~/.claude/tasks/* (safe — only affects dead sessions)"
fi
fi
Report as info diagnostic: I002 | info | Stale subagent task directories found | Yes (--repair removes them)
</stale_task_cleanup>