From b177c1704fa75b8b90a1b771925dfcc02819ae60 Mon Sep 17 00:00:00 2001 From: Tom Boucher Date: Tue, 2 Jun 2026 16:14:38 -0400 Subject: [PATCH] fix(#614): resolve gsd-tools via runtime shim in discuss-phase mode routing (#618) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(#614): resolve gsd-tools via runtime shim in discuss-phase mode routing The discuss-phase mode-routing snippet and the codebase-drift gate called the bare `gsd-tools` binary. On a shim-only install (gsd-tools.cjs present but `gsd-tools` not on PATH) the call exits 127, `2>/dev/null` hides it, and `|| echo` silently substitutes a default — so `workflow.discuss_mode: assumptions` was ignored and routing always fell back to standard discuss mode. Both sites now resolve the binary through the canonical `_GSD_SHIM_NAME` probe and call `gsd_run`. Discuss-phase fails loudly on a genuinely missing shim (interactive — wrong mode is worse than an error); the non-blocking drift gate uses a soft `return 127` fallback so it still skips gracefully when nothing is resolvable. Co-Authored-By: Claude Opus 4.8 * chore(#614): set changeset pr to 618 * fix(#614): scope to discuss-phase mode routing; revert drift-gate change The runtime-launcher-parity invariant requires exactly one canonical (byte-equal) gsd_run preamble per workflow .md before the first gsd_run call. codebase-drift-gate.md has two independent gsd_run bash blocks; hardening its first block cleanly conflicts with that invariant and risks the auto-remap block's separate execution scope. Descope the drift-gate hardening to a follow-up and keep this PR focused on the titled bug: the discuss-phase mode-routing snippet now resolves gsd-tools via the runtime shim (gsd_run) instead of the bare PATH command, so shim-only installs no longer silently fall back to standard discuss mode. Drift-gate file reverted to its next state; its test assertion removed. Co-Authored-By: Claude Opus 4.8 --------- Co-authored-by: Claude Opus 4.8 --- .../614-discuss-phase-shim-resolution.md | 5 +++++ commands/gsd/discuss-phase.md | 3 ++- tests/discuss-mode.test.cjs | 21 +++++++++++++++++++ 3 files changed, 28 insertions(+), 1 deletion(-) create mode 100644 .changeset/614-discuss-phase-shim-resolution.md diff --git a/.changeset/614-discuss-phase-shim-resolution.md b/.changeset/614-discuss-phase-shim-resolution.md new file mode 100644 index 000000000..72c55c139 --- /dev/null +++ b/.changeset/614-discuss-phase-shim-resolution.md @@ -0,0 +1,5 @@ +--- +type: Fixed +pr: 618 +--- +**`/gsd-discuss-phase` now honors `workflow.discuss_mode: assumptions` on shim-only installs** — mode routing resolves `gsd-tools` via the runtime shim instead of the bare PATH command, so a missing PATH binary no longer silently falls back to standard discuss mode. diff --git a/commands/gsd/discuss-phase.md b/commands/gsd/discuss-phase.md index d7aa4ad64..2b21790d1 100644 --- a/commands/gsd/discuss-phase.md +++ b/commands/gsd/discuss-phase.md @@ -47,7 +47,8 @@ Context files are resolved in-workflow using `init phase-op` and roadmap/state t **Mode routing:** ```bash -DISCUSS_MODE=$(gsd-tools query config-get workflow.discuss_mode 2>/dev/null || echo "discuss") +_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}/get-shit-done/bin/${_GSD_SHIM_NAME}"; if [ -f "$GSD_TOOLS" ]; then gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.claude/get-shit-done/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.claude/get-shit-done/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/get-shit-done/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="$HOME/.claude/get-shit-done/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 +DISCUSS_MODE=$(gsd_run query config-get workflow.discuss_mode 2>/dev/null || echo "discuss") ``` If `--assumptions` is in $ARGUMENTS: diff --git a/tests/discuss-mode.test.cjs b/tests/discuss-mode.test.cjs index 8f91af26c..727bdaa8c 100644 --- a/tests/discuss-mode.test.cjs +++ b/tests/discuss-mode.test.cjs @@ -164,4 +164,25 @@ describe('workflow.discuss_mode config', () => { assert.ok(doc.includes('discuss'), 'doc should mention discuss'); assert.ok(doc.includes('config-set'), 'doc should show how to configure'); }); + + test('discuss-phase command mode-routing uses gsd_run (shim-safe) not bare gsd-tools', () => { + const command = fs.readFileSync( + path.join(__dirname, '..', 'commands', 'gsd', 'discuss-phase.md'), 'utf8' + ); + // Must contain the canonical shim probe marker + assert.ok( + command.includes('_GSD_SHIM_NAME'), + 'discuss-phase.md must define _GSD_SHIM_NAME shim probe before mode routing' + ); + // Must use gsd_run for the config lookup + assert.ok( + command.includes('gsd_run query config-get workflow.discuss_mode'), + 'discuss-phase.md must use gsd_run (not bare gsd-tools) for discuss_mode lookup' + ); + // Must NOT contain the bare footgun pattern: gsd-tools immediately before the silent default + assert.ok( + !command.includes('gsd-tools query config-get workflow.discuss_mode 2>/dev/null || echo'), + 'discuss-phase.md must NOT use bare gsd-tools binary for discuss_mode lookup (shim-only install footgun)' + ); + }); });