fix(#448): resolve UI safety gate helper against the GSD install dir (#539)

* fix(#448): resolve UI safety gate helper against the GSD install dir

The §5.6 UI Design Contract Gate (and autonomous §3a.5) resolved
ui-safety-gate.cjs via `git rev-parse --show-toplevel`, i.e. the
consuming project's git root — which has no bin/lib. The node call
failed, its exit code was conflated with "no UI", and the gate
silently no-opped so frontend phases skipped the UI-SPEC prompt.

Resolve the helper against the GSD install dir via RUNTIME_DIR (the
same idiom §1 uses for gsd-tools), with git-toplevel and $HOME/.claude
fallbacks. When the helper genuinely can't be found, fail OPEN with a
stderr warning (assume UI present) rather than silently skipping.

Tests: bug-3706 structural guard now requires RUNTIME_DIR resolution
and forbids the consuming-project GSD_REPO_ROOT anchor; a new
behavioral test resolves and runs the helper from a temp consuming
project (no bin/lib) with RUNTIME_DIR set. autonomous-ui-steps updated
to match.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* docs(#448): add changeset fragment for PR #539

* fix(#448): add get-shit-done/bin/lib/ to UI gate probe and deploy helper there

The installer copies get-shit-done/ to the target but not root bin/lib/, so
the helper was never found for installed users. Placing ui-safety-gate.cjs in
get-shit-done/bin/lib/ ensures the installer deploys it, and probing that path
first makes the gate work correctly in installed runtimes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore: update changeset to cover get-shit-done/bin/lib/ deployment

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore: update inventory for ui-safety-gate.cjs in get-shit-done/bin/lib/

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Tom Boucher
2026-05-31 14:42:37 -04:00
committed by GitHub
parent 3d39064406
commit 6fc84528e5
8 changed files with 230 additions and 22 deletions

View File

@@ -284,11 +284,11 @@ Check if this phase has frontend indicators and whether a UI-SPEC already exists
PHASE_SECTION=$(gsd_run query roadmap.get-phase ${PHASE_NUM} 2>/dev/null)
# Shell-free word-boundary gate (#3718): Node.js helper — no locale env-var dependency.
# Reads via stdin to avoid OS ARG_MAX limits on large phase text.
# Path anchored to repo root; falls back to CWD if git is unavailable
# Exit codes mirror grep: 0 = UI tokens found, 1 = not found.
GSD_REPO_ROOT=$(git rev-parse --show-toplevel 2>/dev/null || echo ".")
printf '%s' "$PHASE_SECTION" | node "${GSD_REPO_ROOT}/bin/lib/ui-safety-gate.cjs" > /dev/null 2>&1
HAS_UI=$?
# Resolve the helper against the GSD install dir via RUNTIME_DIR (#448) — NOT the consuming
# project's git root — falling back to git toplevel / $HOME/.claude. Exit codes mirror grep (0=UI,1=none).
_GSD_RT="${RUNTIME_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"
UI_GATE_JS=$(for _c in "$_GSD_RT/get-shit-done/bin/lib/ui-safety-gate.cjs" "$_GSD_RT/bin/lib/ui-safety-gate.cjs" "$_GSD_RT/.claude/bin/lib/ui-safety-gate.cjs" "$HOME/.claude/get-shit-done/bin/lib/ui-safety-gate.cjs" "$HOME/.claude/bin/lib/ui-safety-gate.cjs"; do [ -f "$_c" ] && { echo "$_c"; break; }; done)
if [ -n "$UI_GATE_JS" ]; then printf '%s' "$PHASE_SECTION" | node "$UI_GATE_JS" >/dev/null 2>&1; HAS_UI=$?; else echo "WARN: ui-safety-gate.cjs not found via RUNTIME_DIR/\$HOME (#448) — assuming UI present" >&2; HAS_UI=0; fi
UI_SPEC_FILE=$(ls "${PHASE_DIR}"/*-UI-SPEC.md 2>/dev/null | head -1)
```

View File

@@ -625,11 +625,11 @@ Check if phase has frontend indicators:
PHASE_SECTION=$(gsd_run query roadmap.get-phase "${PHASE}" 2>/dev/null)
# Shell-free word-boundary gate (#3718): Node.js helper — no locale env-var dependency.
# Reads via stdin to avoid OS ARG_MAX limits on large phase text.
# Path anchored to repo root; falls back to CWD if git is unavailable
# Exit codes mirror grep: 0 = UI tokens found, 1 = not found.
GSD_REPO_ROOT=$(git rev-parse --show-toplevel 2>/dev/null || echo ".")
printf '%s' "$PHASE_SECTION" | node "${GSD_REPO_ROOT}/bin/lib/ui-safety-gate.cjs" > /dev/null 2>&1
HAS_UI=$?
# Resolve the helper against the GSD install dir via RUNTIME_DIR (#448) — NOT the consuming
# project's git root — falling back to git toplevel / $HOME/.claude. Exit codes mirror grep (0=UI,1=none).
_GSD_RT="${RUNTIME_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"
UI_GATE_JS=$(for _c in "$_GSD_RT/get-shit-done/bin/lib/ui-safety-gate.cjs" "$_GSD_RT/bin/lib/ui-safety-gate.cjs" "$_GSD_RT/.claude/bin/lib/ui-safety-gate.cjs" "$HOME/.claude/get-shit-done/bin/lib/ui-safety-gate.cjs" "$HOME/.claude/bin/lib/ui-safety-gate.cjs"; do [ -f "$_c" ] && { echo "$_c"; break; }; done)
if [ -n "$UI_GATE_JS" ]; then printf '%s' "$PHASE_SECTION" | node "$UI_GATE_JS" >/dev/null 2>&1; HAS_UI=$?; else echo "WARN: ui-safety-gate.cjs not found via RUNTIME_DIR/\$HOME (#448) — assuming UI present" >&2; HAS_UI=0; fi
```
**If `HAS_UI` is 0 (frontend indicators found):**