fix(windows): detect Git Bash/MSYS2/Cygwin from OSTYPE, as gstack-doctor does, so uname stubs in setup fixtures stay untouched

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
This commit is contained in:
garrytanandcapy-ai[bot] committed 2026-10-25 23:47:58 +00:00
1 parent 27e5dce493
commit de5998fd49
47 files changed
+77 -82

No files matched your search

+1 -1
View File
@@ -303,4 +303,4 @@ Agents poll `eval:bg:*` logs for the `### gstack-detach EXIT=<code> ###` sentine
- Every Codex run's verdict goes through `lib/outside-review-result.ts`; its reason codes live in `lib/gate-outcomes.ts`, each with a `docs/troubleshooting.md` anchor.
- `test/module-size-ratchet.test.ts` keeps refactored owner modules at or under 800 lines (150 per function) and residual files from growing.
- The `claude` CLI binary resolves via `lib/claude-bin.ts` (re-exported from `browse/src/claude-bin.ts` for browse internals; `Bun.which()` + `GSTACK_CLAUDE_BIN` override). Set `GSTACK_CLAUDE_BIN=wsl` plus `GSTACK_CLAUDE_BIN_ARGS='["claude"]'` to run Claude through WSL on Windows.
- Every Bun gstack starts ignores the working directory's `.env` files and `bunfig.toml`: bun shebangs are `#!/usr/bin/env -S bun --no-env-file --config=/dev/null`, skill prose passes `bun --no-env-file --config=/dev/null`, shipped shell files define `GSTACK_BUN_CONFIG` with the line in `test/bun-entry-isolation.test.ts` (`NUL` under MINGW, MSYS and Cygwin, where native Bun cannot open `/dev/null`, #3415) and pass `bun --no-env-file --config="${GSTACK_BUN_CONFIG:-/dev/null}"`, TypeScript spawns use `bunIsolationArgs()` from `lib/bun-isolation.ts`, and `bun build --compile` adds `--no-compile-autoload-dotenv --no-compile-autoload-bunfig`. `test/bun-entry-isolation.test.ts` enforces all four.
- Every Bun gstack starts ignores the working directory's `.env` files and `bunfig.toml`: bun shebangs are `#!/usr/bin/env -S bun --no-env-file --config=/dev/null`, skill prose passes `bun --no-env-file --config=/dev/null`, shipped shell files define `GSTACK_BUN_CONFIG` with the line in `test/bun-entry-isolation.test.ts` (`NUL` when `OSTYPE` is `msys` or `cygwin`, where native Bun cannot open `/dev/null`, #3415) and pass `bun --no-env-file --config="${GSTACK_BUN_CONFIG:-/dev/null}"`, TypeScript spawns use `bunIsolationArgs()` from `lib/bun-isolation.ts`, and `bun build --compile` adds `--no-compile-autoload-dotenv --no-compile-autoload-bunfig`. `test/bun-entry-isolation.test.ts` enforces all four.
+2 -2
View File
@@ -5,12 +5,12 @@
**Windows: gstack's shell bins stop losing review, question, learning and timeline records when Git Bash path conversion is off (#3415).** Since GSTA-56 every shell bin started Bun with `--config=/dev/null`. Native `bun.exe` cannot open `/dev/null`: it exits 1 with `EINVAL` on Bun 1.3.10, 1.4.2 and 1.4.4 (measured on windows-latest). Git Bash hides this by rewriting the argument to `nul`, but only while MSYS path conversion is on. With it off (`MSYS_NO_PATHCONV` set to any value, even empty; `MSYS2_ARG_CONV_EXCL=*`; or Cygwin), `gstack-review-log`, `gstack-question-log` and `gstack-learnings-log` exited 1 with nothing written, and `gstack-timeline-log` exited 0 with nothing written.
### Fixed
- **40 shipped shell files** (the bash bins, the Claude hook wrappers, `setup`, `scripts/build-cso.sh` and one migration) define `GSTACK_BUN_CONFIG` once (`NUL` under MINGW, MSYS and Cygwin, `/dev/null` elsewhere) and start Bun with `--config="${GSTACK_BUN_CONFIG:-/dev/null}"`. Bun 1.4.2 and later, gstack's minimum, read `NUL` as an empty config whether or not conversion is on. The `:-/dev/null` default fails closed: a call that loses its definition still passes `/dev/null`, so POSIX is unchanged and Windows fails loudly as before, and never reads the repository's `bunfig.toml`. `bin/gstack-codex-probe` is sourced into the caller's shell, so it defines the variable inside the one function that starts Bun and still runs nothing at source time.
- **40 shipped shell files** (the bash bins, the Claude hook wrappers, `setup`, `scripts/build-cso.sh` and one migration) define `GSTACK_BUN_CONFIG` once (`NUL` when `OSTYPE` is `msys` or `cygwin`, as in Git Bash, MSYS2 and Cygwin, the check `gstack-doctor` already uses; `/dev/null` elsewhere) and start Bun with `--config="${GSTACK_BUN_CONFIG:-/dev/null}"`. Bun 1.4.2 and later, gstack's minimum, read `NUL` as an empty config whether or not conversion is on. The `:-/dev/null` default fails closed: a call that loses its definition still passes `/dev/null`, so POSIX is unchanged and Windows fails loudly as before, and never reads the repository's `bunfig.toml`. `bin/gstack-codex-probe` is sourced into the caller's shell, so it defines the variable inside the one function that starts Bun and still runs nothing at source time.
- **`bin/gstack-timeline-log`**: invalid input still exits 0 with nothing logged (the hook-path contract). Its validator now exits 3 for invalid input, and any other non-zero exit, which is Bun itself failing, is reported on stderr and returned instead of being skipped as invalid.
- Unchanged: bun shebangs and skill prose keep `--config=/dev/null`. A shebang cannot name a per-platform path, and with conversion off their POSIX script paths (`/c/Users/...`) fail in `bun.exe` anyway, so they work wherever they worked before. TypeScript spawns keep `bunIsolationArgs()`, and `scripts/ubicloud/` keeps `/dev/null` because it runs on Linux.
### Tests
- `test/bun-config-shell-bins.test.ts` (new; force-included in the Windows lane): on every platform, the four record bins run through `lib/script-invocation.ts` with conversion off in a repository whose `bunfig.toml` preload and `.env` are hostile. Each writes exactly one record, and neither the preload nor the `.env` state root takes effect. Invalid timeline input still exits 0 silently. On POSIX hosts a simulated Windows bash (uname reports MINGW64, and a `bun` stand-in rejects `/dev/null` with EINVAL as `bun.exe` does) reproduces the bug: on main the four bins write nothing and the timeline control exits 0 (5 failures); with the fix they pass. The controls, with uname reporting Linux, show each bin exits non-zero with nothing written.
- `test/bun-config-shell-bins.test.ts` (new; force-included in the Windows lane): on every platform, the four record bins run through `lib/script-invocation.ts` with conversion off in a repository whose `bunfig.toml` preload and `.env` are hostile. Each writes exactly one record, and neither the preload nor the `.env` state root takes effect. Invalid timeline input still exits 0 silently. On POSIX hosts a simulated Windows bash (`OSTYPE=msys`, and a `bun` stand-in rejects `/dev/null` with EINVAL as `bun.exe` does) reproduces the bug: on main the four bins write nothing and the timeline control exits 0 (5 failures); with the fix they pass. The controls, without `OSTYPE=msys`, show each bin exits non-zero with nothing written.
- `test/bun-isolation-launch.test.ts`: the shell-file spelling stays isolated with conversion off, and a win32-only control shows that the literal `--config=/dev/null` fails there with EINVAL.
- `test/bun-entry-isolation.test.ts`: shipped shell files must use the fail-closed spelling and define `GSTACK_BUN_CONFIG` with the canonical line before first use. Skill sources keep the literal spelling.
- `test/learnings-injection.test.ts`, `browse/test/learnings-injection.test.ts` and `test/setup-hook-canonical-paths.test.ts` now anchor on the new spelling. Their assertions are unchanged.
+1 -1
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env bash
# Native Read barrier. A broken install must return deny JSON, never silence.
set -euo pipefail
GSTACK_BUN_CONFIG=/dev/null; case "$(uname -s)" in MINGW*|MSYS*|CYGWIN*) GSTACK_BUN_CONFIG=NUL ;; esac
GSTACK_BUN_CONFIG=/dev/null; case "${OSTYPE:-}" in msys*|cygwin*) GSTACK_BUN_CONFIG=NUL ;; esac
_AUTOPLAN_DECIDED=''
_autoplan_backstop() {
if [ -z "$_AUTOPLAN_DECIDED" ]; then
+1 -1
View File
@@ -77,7 +77,7 @@
BASH_COMPAT=50
set -uo pipefail
GSTACK_BUN_CONFIG=/dev/null; case "$(uname -s)" in MINGW*|MSYS*|CYGWIN*) GSTACK_BUN_CONFIG=NUL ;; esac
GSTACK_BUN_CONFIG=/dev/null; case "${OSTYPE:-}" in msys*|cygwin*) GSTACK_BUN_CONFIG=NUL ;; esac
. "$(dirname "$0")/gstack-state-root.sh" 2>/dev/null || { echo "$0: cannot resolve the gstack state root: $(dirname "$0")/gstack-state-root.sh is missing. fix: reinstall with ./setup or /gstack-upgrade (docs/state-root.md)" >&2; exit 1; }
gstack_state_root_select
+1 -1
View File
@@ -24,7 +24,7 @@
# GSTACK_CHROMIUM_NO_SANDBOX=1 for the probe. Bash 3.2 plus POSIX tools.
BASH_COMPAT=50
set -u
GSTACK_BUN_CONFIG=/dev/null; case "$(uname -s)" in MINGW*|MSYS*|CYGWIN*) GSTACK_BUN_CONFIG=NUL ;; esac
GSTACK_BUN_CONFIG=/dev/null; case "${OSTYPE:-}" in msys*|cygwin*) GSTACK_BUN_CONFIG=NUL ;; esac
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)" || { echo "gstack-browser-ensure: cannot resolve the install root" >&2; exit 1; }
DOCS="https://github.com/garrytan/gstack/blob/main/docs/troubleshooting.md#browser-ensure-failed"
+1 -1
View File
@@ -157,7 +157,7 @@ _gstack_codex_select_model() {
# working-directory context forwarded to the shared role resolver.
# The TOML is read by the TypeScript resolver; bash never parses it.
local _kind="${1:-exec}" _explicit="${2:-}" _role="${3:-}" _cwd="${4:-}" _line _rc _rest
local GSTACK_BUN_CONFIG=/dev/null; case "$(uname -s)" in MINGW*|MSYS*|CYGWIN*) GSTACK_BUN_CONFIG=NUL ;; esac
local GSTACK_BUN_CONFIG=/dev/null; case "${OSTYPE:-}" in msys*|cygwin*) GSTACK_BUN_CONFIG=NUL ;; esac
case "$_kind" in exec|review) ;; *) echo "ERROR: _gstack_codex_select_model takes exec or review" >&2; return 2 ;; esac
case "$_role" in ''|plan-review) ;; *) echo "ERROR: _gstack_codex_select_model role must be plan-review" >&2; return 2 ;; esac
if ! command -v bun >/dev/null 2>&1; then
+1 -1
View File
@@ -20,7 +20,7 @@
# Writes via bin/gstack-question-log so source tagging, dedup, and async
# derive all apply uniformly.
set -euo pipefail
GSTACK_BUN_CONFIG=/dev/null; case "$(uname -s)" in MINGW*|MSYS*|CYGWIN*) GSTACK_BUN_CONFIG=NUL ;; esac
GSTACK_BUN_CONFIG=/dev/null; case "${OSTYPE:-}" in msys*|cygwin*) GSTACK_BUN_CONFIG=NUL ;; esac
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
. "$(dirname "$0")/gstack-state-root.sh" 2>/dev/null || { echo "$0: cannot resolve the gstack state root: $(dirname "$0")/gstack-state-root.sh is missing. fix: reinstall with ./setup or /gstack-upgrade (docs/state-root.md)" >&2; exit 1; }
gstack_state_root_select
+1 -1
View File
@@ -28,7 +28,7 @@
# docs/designs/PLAN_TUNING_V0.md). Event file: ~/.gstack/projects/{SLUG}/
# question-events.jsonl.
set -euo pipefail
GSTACK_BUN_CONFIG=/dev/null; case "$(uname -s)" in MINGW*|MSYS*|CYGWIN*) GSTACK_BUN_CONFIG=NUL ;; esac
GSTACK_BUN_CONFIG=/dev/null; case "${OSTYPE:-}" in msys*|cygwin*) GSTACK_BUN_CONFIG=NUL ;; esac
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
+1 -1
View File
@@ -23,7 +23,7 @@
# gstack-distill-apply --proposal <N> --gbrain-published true
# gstack-distill-apply --list # show pending proposals
set -euo pipefail
GSTACK_BUN_CONFIG=/dev/null; case "$(uname -s)" in MINGW*|MSYS*|CYGWIN*) GSTACK_BUN_CONFIG=NUL ;; esac
GSTACK_BUN_CONFIG=/dev/null; case "${OSTYPE:-}" in msys*|cygwin*) GSTACK_BUN_CONFIG=NUL ;; esac
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
. "$(dirname "$0")/gstack-state-root.sh" 2>/dev/null || { echo "$0: cannot resolve the gstack state root: $(dirname "$0")/gstack-state-root.sh is missing. fix: reinstall with ./setup or /gstack-upgrade (docs/state-root.md)" >&2; exit 1; }
gstack_state_root_select
+1 -1
View File
@@ -20,7 +20,7 @@
# auditability via --status when you want it.
# Per D6: Anthropic SDK direct call, fail-loud on missing ANTHROPIC_API_KEY.
set -euo pipefail
GSTACK_BUN_CONFIG=/dev/null; case "$(uname -s)" in MINGW*|MSYS*|CYGWIN*) GSTACK_BUN_CONFIG=NUL ;; esac
GSTACK_BUN_CONFIG=/dev/null; case "${OSTYPE:-}" in msys*|cygwin*) GSTACK_BUN_CONFIG=NUL ;; esac
# Heredoc delivery guard. bash 5.2+ writes a heredoc body <=64KiB through a
# pipe in the forked child before exec, with no reader on the other end. On
+1 -1
View File
@@ -40,7 +40,7 @@
BASH_COMPAT=50
set -u
GSTACK_BUN_CONFIG=/dev/null; case "$(uname -s)" in MINGW*|MSYS*|CYGWIN*) GSTACK_BUN_CONFIG=NUL ;; esac
GSTACK_BUN_CONFIG=/dev/null; case "${OSTYPE:-}" in msys*|cygwin*) GSTACK_BUN_CONFIG=NUL ;; esac
usage() {
sed -n '3,13p' "$0" | sed 's/^# \{0,1\}//'
+1 -1
View File
@@ -19,7 +19,7 @@
# remote's capy/support.env, `unknown` when its fetch failed, or ""), LIVE.
# GSTACK_CAPY_WRAPPER_PATH (optional) names the wrapper a below-minimum fix runs.
GSTACK_BUN_CONFIG=/dev/null; case "$(uname -s)" in MINGW*|MSYS*|CYGWIN*) GSTACK_BUN_CONFIG=NUL ;; esac
GSTACK_BUN_CONFIG=/dev/null; case "${OSTYPE:-}" in msys*|cygwin*) GSTACK_BUN_CONFIG=NUL ;; esac
. "$BIN/gstack-doctor-components.sh" || { echo "gstack-doctor: $BIN/gstack-doctor-components.sh is missing; reinstall with cd $SRC && ./setup" >&2; exit 1; }
. "$BIN/gstack-install-registry.sh" 2>/dev/null || { echo "gstack-doctor: $BIN/gstack-install-registry.sh is missing; reinstall with cd $SRC && ./setup" >&2; exit 1; }
. "$BIN/gstack-bun-version.sh" 2>/dev/null || { echo "gstack-doctor: $BIN/gstack-bun-version.sh is missing; reinstall with cd $SRC && ./setup" >&2; exit 1; }
+1 -1
View File
@@ -36,7 +36,7 @@
# 2 — prerequisite missing or invalid argument
# 3 — post-install validation failed (PATH shadow, broken binary, etc.)
set -euo pipefail
GSTACK_BUN_CONFIG=/dev/null; case "$(uname -s)" in MINGW*|MSYS*|CYGWIN*) GSTACK_BUN_CONFIG=NUL ;; esac
GSTACK_BUN_CONFIG=/dev/null; case "${OSTYPE:-}" in msys*|cygwin*) GSTACK_BUN_CONFIG=NUL ;; esac
# --- defaults ---
# No version pin by default — install the latest default-branch HEAD (#1744).
+1 -1
View File
@@ -39,7 +39,7 @@
# Depends on: bun (already a gstack dependency), gbrain, git.
set -euo pipefail
GSTACK_BUN_CONFIG=/dev/null; case "$(uname -s)" in MINGW*|MSYS*|CYGWIN*) GSTACK_BUN_CONFIG=NUL ;; esac
GSTACK_BUN_CONFIG=/dev/null; case "${OSTYPE:-}" in msys*|cygwin*) GSTACK_BUN_CONFIG=NUL ;; esac
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
CONFIG_BIN="$SCRIPT_DIR/gstack-config"
+1 -1
View File
@@ -37,7 +37,7 @@
# (needed when a hook runs TypeScript) is not found. Set GSTACK_HOOK_CHECK_BUN to
# the bun to run (setup passes its own).
set -u
GSTACK_BUN_CONFIG=/dev/null; case "$(uname -s)" in MINGW*|MSYS*|CYGWIN*) GSTACK_BUN_CONFIG=NUL ;; esac
GSTACK_BUN_CONFIG=/dev/null; case "${OSTYPE:-}" in msys*|cygwin*) GSTACK_BUN_CONFIG=NUL ;; esac
LIST_ONLY=0 SETUP_FILE=""
while [ $# -gt 1 ]; do
+1 -1
View File
@@ -31,7 +31,7 @@
# spawned sessions (ambient OpenClaw) only warn. GSTACK_ALLOW_UNSUPPORTED_RUNTIME=1
# admits anyway and says so.
GSTACK_BUN_CONFIG=/dev/null; case "$(uname -s)" in MINGW*|MSYS*|CYGWIN*) GSTACK_BUN_CONFIG=NUL ;; esac
GSTACK_BUN_CONFIG=/dev/null; case "${OSTYPE:-}" in msys*|cygwin*) GSTACK_BUN_CONFIG=NUL ;; esac
# gstack_source_root BIN_DIR — the source checkout behind an install: an
# installed runtime root's recorded .source-path (setup _link_runtime_dists),
+1 -1
View File
@@ -21,7 +21,7 @@
# session tokens are outstanding.
set -euo pipefail
GSTACK_BUN_CONFIG=/dev/null; case "$(uname -s)" in MINGW*|MSYS*|CYGWIN*) GSTACK_BUN_CONFIG=NUL ;; esac
GSTACK_BUN_CONFIG=/dev/null; case "${OSTYPE:-}" in msys*|cygwin*) GSTACK_BUN_CONFIG=NUL ;; esac
# The --help heredoc is over 512 bytes; keep it off a pipe (test/heredoc-pipe-deadlock.test.ts).
BASH_COMPAT=50
+1 -1
View File
@@ -10,7 +10,7 @@
# Allowlist file: ~/.gstack/ios-qa-allowlist.json (mode 0600).
set -euo pipefail
GSTACK_BUN_CONFIG=/dev/null; case "$(uname -s)" in MINGW*|MSYS*|CYGWIN*) GSTACK_BUN_CONFIG=NUL ;; esac
GSTACK_BUN_CONFIG=/dev/null; case "${OSTYPE:-}" in msys*|cygwin*) GSTACK_BUN_CONFIG=NUL ;; esac
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
GSTACK_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
+1 -1
View File
@@ -3,7 +3,7 @@
# package and the app-owned typed state accessors.
set -euo pipefail
GSTACK_BUN_CONFIG=/dev/null; case "$(uname -s)" in MINGW*|MSYS*|CYGWIN*) GSTACK_BUN_CONFIG=NUL ;; esac
GSTACK_BUN_CONFIG=/dev/null; case "${OSTYPE:-}" in msys*|cygwin*) GSTACK_BUN_CONFIG=NUL ;; esac
usage() {
cat <<'EOF'
+1 -1
View File
@@ -6,7 +6,7 @@
# Append-only storage. Duplicates (same key+type) are resolved at read time
# by gstack-learnings-search ("latest winner" per key+type).
set -euo pipefail
GSTACK_BUN_CONFIG=/dev/null; case "$(uname -s)" in MINGW*|MSYS*|CYGWIN*) GSTACK_BUN_CONFIG=NUL ;; esac
GSTACK_BUN_CONFIG=/dev/null; case "${OSTYPE:-}" in msys*|cygwin*) GSTACK_BUN_CONFIG=NUL ;; esac
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
# Windows git-bash (#1950): pwd yields a POSIX path (/c/Users/...), which Bun
# on Windows cannot resolve as an ES module specifier in the import below.
+1 -1
View File
@@ -8,7 +8,7 @@
# exits 127 and a failed read exits non-zero with its stderr (#2790): "could
# not read" must never look like "nothing recorded".
set -euo pipefail
GSTACK_BUN_CONFIG=/dev/null; case "$(uname -s)" in MINGW*|MSYS*|CYGWIN*) GSTACK_BUN_CONFIG=NUL ;; esac
GSTACK_BUN_CONFIG=/dev/null; case "${OSTYPE:-}" in msys*|cygwin*) GSTACK_BUN_CONFIG=NUL ;; esac
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
eval "$("$SCRIPT_DIR/gstack-slug" 2>/dev/null)"
. "$(dirname "$0")/gstack-state-root.sh" 2>/dev/null || { echo "$0: cannot resolve the gstack state root: $(dirname "$0")/gstack-state-root.sh is missing. fix: reinstall with ./setup or /gstack-upgrade (docs/state-root.md)" >&2; exit 1; }
+1 -1
View File
@@ -32,7 +32,7 @@
# Heredoc delivery guard (see bin/gstack-settings-hook for the rationale).
BASH_COMPAT=50
set -uo pipefail
GSTACK_BUN_CONFIG=/dev/null; case "$(uname -s)" in MINGW*|MSYS*|CYGWIN*) GSTACK_BUN_CONFIG=NUL ;; esac
GSTACK_BUN_CONFIG=/dev/null; case "${OSTYPE:-}" in msys*|cygwin*) GSTACK_BUN_CONFIG=NUL ;; esac
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
+1 -1
View File
@@ -26,7 +26,7 @@
#
# Append-only JSONL. Dedup is at read time in gstack-question-sensitivity --read-log.
set -euo pipefail
GSTACK_BUN_CONFIG=/dev/null; case "$(uname -s)" in MINGW*|MSYS*|CYGWIN*) GSTACK_BUN_CONFIG=NUL ;; esac
GSTACK_BUN_CONFIG=/dev/null; case "${OSTYPE:-}" in msys*|cygwin*) GSTACK_BUN_CONFIG=NUL ;; esac
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
# Windows git-bash (#1950): pwd yields a POSIX path (/c/Users/...), which Bun
# on Windows cannot resolve as an ES module specifier in bun -e imports.
+1 -1
View File
@@ -35,7 +35,7 @@
# --check already ignores those prefs; storing them made --stats lie.
# always-ask on a one-way id is fine (it agrees with the safety override).
set -euo pipefail
GSTACK_BUN_CONFIG=/dev/null; case "$(uname -s)" in MINGW*|MSYS*|CYGWIN*) GSTACK_BUN_CONFIG=NUL ;; esac
GSTACK_BUN_CONFIG=/dev/null; case "${OSTYPE:-}" in msys*|cygwin*) GSTACK_BUN_CONFIG=NUL ;; esac
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
+1 -1
View File
@@ -9,7 +9,7 @@
# GSTACK_INSTALL_DIR — override gstack install directory
# GSTACK_SKILLS_DIR — override target skills directory
set -euo pipefail
GSTACK_BUN_CONFIG=/dev/null; case "$(uname -s)" in MINGW*|MSYS*|CYGWIN*) GSTACK_BUN_CONFIG=NUL ;; esac
GSTACK_BUN_CONFIG=/dev/null; case "${OSTYPE:-}" in msys*|cygwin*) GSTACK_BUN_CONFIG=NUL ;; esac
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
GSTACK_CONFIG="${SCRIPT_DIR}/gstack-config"
+1 -1
View File
@@ -17,7 +17,7 @@
# a model read the code. Caller-supplied binding fields are always discarded.
# Plan-tier rows retain their legacy binding behavior.
set -euo pipefail
GSTACK_BUN_CONFIG=/dev/null; case "$(uname -s)" in MINGW*|MSYS*|CYGWIN*) GSTACK_BUN_CONFIG=NUL ;; esac
GSTACK_BUN_CONFIG=/dev/null; case "${OSTYPE:-}" in msys*|cygwin*) GSTACK_BUN_CONFIG=NUL ;; esac
export GIT_OPTIONAL_LOCKS=0
export GIT_CONFIG_PARAMETERS="${GIT_CONFIG_PARAMETERS:+$GIT_CONFIG_PARAMETERS }'core.fsmonitor=false' 'core.untrackedCache=false'"
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
+1 -1
View File
@@ -9,7 +9,7 @@
# rows using review_freshness; legacy or uncaptured diff evidence cannot fall
# back to HEAD equality. Plan rows are unchanged.
set -euo pipefail
GSTACK_BUN_CONFIG=/dev/null; case "$(uname -s)" in MINGW*|MSYS*|CYGWIN*) GSTACK_BUN_CONFIG=NUL ;; esac
GSTACK_BUN_CONFIG=/dev/null; case "${OSTYPE:-}" in msys*|cygwin*) GSTACK_BUN_CONFIG=NUL ;; esac
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
case "$(uname -s)" in
MINGW*|MSYS*|CYGWIN*) command -v cygpath >/dev/null 2>&1 && SCRIPT_DIR="$(cygpath -m "$SCRIPT_DIR")" ;;
+1 -1
View File
@@ -69,7 +69,7 @@
BASH_COMPAT=50
set -euo pipefail
GSTACK_BUN_CONFIG=/dev/null; case "$(uname -s)" in MINGW*|MSYS*|CYGWIN*) GSTACK_BUN_CONFIG=NUL ;; esac
GSTACK_BUN_CONFIG=/dev/null; case "${OSTYPE:-}" in msys*|cygwin*) GSTACK_BUN_CONFIG=NUL ;; esac
ACTION="${1:-}"
SETTINGS_FILE="${GSTACK_SETTINGS_FILE:-${CLAUDE_CONFIG_DIR:-$HOME/.claude}/settings.json}"
+1 -1
View File
@@ -8,7 +8,7 @@
# Fix-range passes (scope "fix-range", #3339) are skipped: a narrowed dispatch
# says nothing about a specialist's hit rate on a whole branch.
set -euo pipefail
GSTACK_BUN_CONFIG=/dev/null; case "$(uname -s)" in MINGW*|MSYS*|CYGWIN*) GSTACK_BUN_CONFIG=NUL ;; esac
GSTACK_BUN_CONFIG=/dev/null; case "${OSTYPE:-}" in msys*|cygwin*) GSTACK_BUN_CONFIG=NUL ;; esac
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
eval "$("$SCRIPT_DIR/gstack-slug" 2>/dev/null)"
. "$(dirname "$0")/gstack-state-root.sh" 2>/dev/null || { echo "$0: cannot resolve the gstack state root: $(dirname "$0")/gstack-state-root.sh is missing. fix: reinstall with ./setup or /gstack-upgrade (docs/state-root.md)" >&2; exit 1; }
+1 -1
View File
@@ -18,7 +18,7 @@
BASH_COMPAT=50
set -euo pipefail
GSTACK_BUN_CONFIG=/dev/null; case "$(uname -s)" in MINGW*|MSYS*|CYGWIN*) GSTACK_BUN_CONFIG=NUL ;; esac
GSTACK_BUN_CONFIG=/dev/null; case "${OSTYPE:-}" in msys*|cygwin*) GSTACK_BUN_CONFIG=NUL ;; esac
MODE="${1:-}"
+1 -1
View File
@@ -21,7 +21,7 @@
#
# NOTE: Uses set -uo pipefail (no -e) — telemetry must never exit non-zero
set -uo pipefail
GSTACK_BUN_CONFIG=/dev/null; case "$(uname -s)" in MINGW*|MSYS*|CYGWIN*) GSTACK_BUN_CONFIG=NUL ;; esac
GSTACK_BUN_CONFIG=/dev/null; case "${OSTYPE:-}" in msys*|cygwin*) GSTACK_BUN_CONFIG=NUL ;; esac
GSTACK_DIR="${GSTACK_DIR:-$(cd "$(dirname "$0")/.." && pwd)}"
SCRIPT_DIR="$GSTACK_DIR/bin"
+1 -1
View File
@@ -10,7 +10,7 @@
# Optional: branch, outcome, duration_s, session, ts.
# Validation failure → skip silently (non-blocking).
set -euo pipefail
GSTACK_BUN_CONFIG=/dev/null; case "$(uname -s)" in MINGW*|MSYS*|CYGWIN*) GSTACK_BUN_CONFIG=NUL ;; esac
GSTACK_BUN_CONFIG=/dev/null; case "${OSTYPE:-}" in msys*|cygwin*) GSTACK_BUN_CONFIG=NUL ;; esac
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
eval "$("$SCRIPT_DIR/gstack-slug" 2>/dev/null)"
. "$(dirname "$0")/gstack-state-root.sh" 2>/dev/null || { echo "$0: cannot resolve the gstack state root: $(dirname "$0")/gstack-state-root.sh is missing. fix: reinstall with ./setup or /gstack-upgrade (docs/state-root.md)" >&2; exit 1; }
+1 -1
View File
@@ -10,7 +10,7 @@
# --branch, or an unknown argument exits 2 before any read (#1723); --help
# prints the usage line and exits 0.
set -euo pipefail
GSTACK_BUN_CONFIG=/dev/null; case "$(uname -s)" in MINGW*|MSYS*|CYGWIN*) GSTACK_BUN_CONFIG=NUL ;; esac
GSTACK_BUN_CONFIG=/dev/null; case "${OSTYPE:-}" in msys*|cygwin*) GSTACK_BUN_CONFIG=NUL ;; esac
USAGE='Usage: gstack-timeline-read [--since "7 days ago"|7d|YYYY-MM-DD] [--limit N] [--branch NAME]'
SINCE_FORMS='accepted: "<N> minute|hour|day|week|month[s] ago", "<N>h|d|w", or an ISO date YYYY-MM-DD or YYYY-MM-DDTHH:MM:SSZ'
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
+1 -1
View File
@@ -5,7 +5,7 @@
# shared old renders are retired. This post-setup pass repairs missed upgrades.
# Idempotent and non-fatal: foreign entries and failed replacements stay intact.
set -u
GSTACK_BUN_CONFIG=/dev/null; case "$(uname -s)" in MINGW*|MSYS*|CYGWIN*) GSTACK_BUN_CONFIG=NUL ;; esac
GSTACK_BUN_CONFIG=/dev/null; case "${OSTYPE:-}" in msys*|cygwin*) GSTACK_BUN_CONFIG=NUL ;; esac
INSTALL_DIR="${GSTACK_INSTALL_DIR:-$HOME/.claude/skills/gstack}"
if [ -f "$INSTALL_DIR/bin/gstack-migrate-claude-code" ] && command -v bun >/dev/null 2>&1; then
bun --no-env-file --config="${GSTACK_BUN_CONFIG:-/dev/null}" "$INSTALL_DIR/bin/gstack-migrate-claude-code" --install-dir "$INSTALL_DIR" || true
+1 -1
View File
@@ -3,6 +3,6 @@
# wrapper makes the TypeScript hook executable via bun. Settings.json
# references this file directly.
set -e
GSTACK_BUN_CONFIG=/dev/null; case "$(uname -s)" in MINGW*|MSYS*|CYGWIN*) GSTACK_BUN_CONFIG=NUL ;; esac
GSTACK_BUN_CONFIG=/dev/null; case "${OSTYPE:-}" in msys*|cygwin*) GSTACK_BUN_CONFIG=NUL ;; esac
HERE="$(cd "$(dirname "$0")" && pwd)"
exec bun --no-env-file --config="${GSTACK_BUN_CONFIG:-/dev/null}" "$HERE/auq-error-fallback-hook.ts"
@@ -12,7 +12,7 @@
# vendor process running with the prompt on its stdin. Forwarded, the signal
# reaches the .ts, which kills the vendor's process group on its way out.
# `<&0` keeps stdin: bash hands background jobs /dev/null otherwise.
GSTACK_BUN_CONFIG=/dev/null; case "$(uname -s)" in MINGW*|MSYS*|CYGWIN*) GSTACK_BUN_CONFIG=NUL ;; esac
GSTACK_BUN_CONFIG=/dev/null; case "${OSTYPE:-}" in msys*|cygwin*) GSTACK_BUN_CONFIG=NUL ;; esac
HERE="$(cd "$(dirname "$0")" && pwd)" || exit 0
bun --no-env-file --config="${GSTACK_BUN_CONFIG:-/dev/null}" "$HERE/memorable-user-prompt-hook.ts" <&0 &
_child=$!
+1 -1
View File
@@ -3,6 +3,6 @@
# wrapper makes the TypeScript hook executable via bun. Settings.json
# references this file directly.
set -e
GSTACK_BUN_CONFIG=/dev/null; case "$(uname -s)" in MINGW*|MSYS*|CYGWIN*) GSTACK_BUN_CONFIG=NUL ;; esac
GSTACK_BUN_CONFIG=/dev/null; case "${OSTYPE:-}" in msys*|cygwin*) GSTACK_BUN_CONFIG=NUL ;; esac
HERE="$(cd "$(dirname "$0")" && pwd)"
exec bun --no-env-file --config="${GSTACK_BUN_CONFIG:-/dev/null}" "$HERE/question-log-hook.ts"
+1 -1
View File
@@ -3,6 +3,6 @@
# wrapper makes the TypeScript hook executable via bun. Settings.json
# references this file directly.
set -e
GSTACK_BUN_CONFIG=/dev/null; case "$(uname -s)" in MINGW*|MSYS*|CYGWIN*) GSTACK_BUN_CONFIG=NUL ;; esac
GSTACK_BUN_CONFIG=/dev/null; case "${OSTYPE:-}" in msys*|cygwin*) GSTACK_BUN_CONFIG=NUL ;; esac
HERE="$(cd "$(dirname "$0")" && pwd)"
exec bun --no-env-file --config="${GSTACK_BUN_CONFIG:-/dev/null}" "$HERE/question-preference-hook.ts"
+1 -1
View File
@@ -5,7 +5,7 @@
#
# FAIL-OPEN (F5): a Stop-event telemetry repair must never block the session.
# Every failure path — bun missing, script crash — still exits 0.
GSTACK_BUN_CONFIG=/dev/null; case "$(uname -s)" in MINGW*|MSYS*|CYGWIN*) GSTACK_BUN_CONFIG=NUL ;; esac
GSTACK_BUN_CONFIG=/dev/null; case "${OSTYPE:-}" in msys*|cygwin*) GSTACK_BUN_CONFIG=NUL ;; esac
HERE="$(cd "$(dirname "$0")" && pwd)" || exit 0
bun --no-env-file --config="${GSTACK_BUN_CONFIG:-/dev/null}" "$HERE/timeline-stop-hook.ts" || true
exit 0
+2 -2
View File
@@ -6,8 +6,8 @@
* code under review. Every Bun gstack starts passes --no-env-file and an empty
* --config: shebangs and skill prose spell it
* `bun --no-env-file --config=/dev/null`, shipped shell files spell
* `--config="${GSTACK_BUN_CONFIG:-/dev/null}"` (NUL under MINGW, MSYS and
* Cygwin, #3415), TypeScript spawns use bunIsolationArgs().
* `--config="${GSTACK_BUN_CONFIG:-/dev/null}"` (NUL when OSTYPE is msys or
* cygwin, #3415), TypeScript spawns use bunIsolationArgs().
* test/bun-entry-isolation.test.ts holds every entry point to that form.
*
* POSIX passes /dev/null. Native Windows has no /dev/null and `--config=NUL`
+1 -1
View File
@@ -1,6 +1,6 @@
#!/usr/bin/env bash
# Bash shim — Claude Code runs hook commands through /bin/sh; this runs the
# TypeScript hook with bun.
GSTACK_BUN_CONFIG=/dev/null; case "$(uname -s)" in MINGW*|MSYS*|CYGWIN*) GSTACK_BUN_CONFIG=NUL ;; esac
GSTACK_BUN_CONFIG=/dev/null; case "${OSTYPE:-}" in msys*|cygwin*) GSTACK_BUN_CONFIG=NUL ;; esac
HERE="$(cd "$(dirname "$0")" && pwd)"
exec bun --no-env-file --config="${GSTACK_BUN_CONFIG:-/dev/null}" "$HERE/mode-handoff-hook.ts"
+1 -1
View File
@@ -1,6 +1,6 @@
#!/usr/bin/env bash
set -eu
GSTACK_BUN_CONFIG=/dev/null; case "$(uname -s)" in MINGW*|MSYS*|CYGWIN*) GSTACK_BUN_CONFIG=NUL ;; esac
GSTACK_BUN_CONFIG=/dev/null; case "${OSTYPE:-}" in msys*|cygwin*) GSTACK_BUN_CONFIG=NUL ;; esac
CSO_BUILD_ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
cd "$CSO_BUILD_ROOT"
+1 -1
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env bash
# gstack setup — build browser binary + register skills with Claude Code / Codex
set -e
GSTACK_BUN_CONFIG=/dev/null; case "$(uname -s)" in MINGW*|MSYS*|CYGWIN*) GSTACK_BUN_CONFIG=NUL ;; esac
GSTACK_BUN_CONFIG=/dev/null; case "${OSTYPE:-}" in msys*|cygwin*) GSTACK_BUN_CONFIG=NUL ;; esac
umask 077 # Restrict new files to owner-only (0o600 files, 0o700 dirs)
# Heredoc delivery guard. bash 5.2+ writes a heredoc body <=64KiB through a
+29 -34
View File
@@ -1,7 +1,8 @@
/**
* #3415: gstack's shell bins start Bun with
* --config="${GSTACK_BUN_CONFIG:-/dev/null}", and each defines
* GSTACK_BUN_CONFIG as NUL under MINGW, MSYS and Cygwin. Native bun.exe cannot
* GSTACK_BUN_CONFIG as NUL when OSTYPE is msys or cygwin (Git Bash, MSYS2,
* Cygwin). Native bun.exe cannot
* open /dev/null (EINVAL), so a Windows bash with MSYS path conversion off
* (MSYS_NO_PATHCONV=1, MSYS2_ARG_CONV_EXCL=*) made the literal spelling fail:
* the record bins wrote nothing, and gstack-timeline-log still exited 0.
@@ -9,10 +10,10 @@
* On every platform the free lanes run, windows-latest included, the record
* bins run through lib/script-invocation.ts with conversion off in a hostile
* repository: each writes its record, and the repository's bunfig.toml preload
* and .env never load. On POSIX a simulated Windows bash (uname reporting
* MINGW64, a bun that rejects /dev/null the way bun.exe does) reproduces the
* bug; its controls, with uname reporting Linux, show each bin fails loudly
* with nothing written instead of exiting 0.
* and .env never load. On POSIX a simulated Windows bash (OSTYPE=msys, a bun
* that rejects /dev/null the way bun.exe does) reproduces the bug; its
* controls, without OSTYPE=msys, show each bin fails loudly with nothing
* written instead of exiting 0.
*/
import { afterAll, beforeAll, describe, expect, test } from 'bun:test';
import { spawnSync } from 'node:child_process';
@@ -29,8 +30,7 @@ let repo = '';
let stateRoot = '';
let evilRoot = '';
let preloadMarker = '';
let fakeWindows = '';
let fakeBunOnly = '';
let fakeBun = '';
const BINS: Array<{ bin: string; input: string; log: string }> = [
{ bin: 'gstack-review-log', log: 'main-reviews.jsonl', input: JSON.stringify({ skill: 'plan-eng-review', timestamp: '2026-10-25T00:00:00Z', status: 'clean', unresolved: 0, critical_gaps: 0, issues_found: 0, mode: 'FULL_REVIEW', commit: 'abc1234' }) },
@@ -39,16 +39,16 @@ const BINS: Array<{ bin: string; input: string; log: string }> = [
{ bin: 'gstack-timeline-log', log: 'timeline.jsonl', input: JSON.stringify({ skill: 'review', event: 'started', branch: 'main' }) },
];
function env(pathPrefix?: string): NodeJS.ProcessEnv {
function env(pathPrefix?: string, extra: Record<string, string> = {}): NodeJS.ProcessEnv {
const out: NodeJS.ProcessEnv = {};
for (const [k, v] of Object.entries(process.env)) if (!/^(GSTACK_|MSYS)/.test(k)) out[k] = v;
for (const [k, v] of Object.entries(process.env)) if (!/^(GSTACK_|MSYS|OSTYPE$)/.test(k)) out[k] = v;
const PATH = Object.keys(out).find(k => k.toUpperCase() === 'PATH') ?? 'PATH';
if (pathPrefix) out[PATH] = pathPrefix + path.delimiter + (out[PATH] ?? '');
return { ...out, HOME: path.join(tmp, 'home'), GSTACK_STATE_ROOT: stateRoot, MSYS_NO_PATHCONV: '1', MSYS2_ARG_CONV_EXCL: '*' };
return { ...out, HOME: path.join(tmp, 'home'), GSTACK_STATE_ROOT: stateRoot, MSYS_NO_PATHCONV: '1', MSYS2_ARG_CONV_EXCL: '*', ...extra };
}
function run(bin: string, input: string, pathPrefix?: string) {
return runScriptSync(path.join(ROOT, 'bin', bin), [input], { cwd: repo, env: env(pathPrefix), encoding: 'utf8', timeout: 60_000 });
function run(bin: string, input: string, pathPrefix?: string, extra: Record<string, string> = {}) {
return runScriptSync(path.join(ROOT, 'bin', bin), [input], { cwd: repo, env: env(pathPrefix, extra), encoding: 'utf8', timeout: 60_000 });
}
function records(log: string): string[] {
@@ -84,26 +84,21 @@ beforeAll(() => {
fs.writeFileSync(path.join(repo, 'preload.ts'), `require('fs').writeFileSync(${JSON.stringify(preloadMarker)}, 'ran');\n`);
fs.writeFileSync(path.join(repo, 'bunfig.toml'), 'preload = ["./preload.ts"]\n');
if (IS_WINDOWS) return;
fakeWindows = path.join(tmp, 'fake-windows');
fakeBunOnly = path.join(tmp, 'fake-bun-only');
for (const dir of [fakeWindows, fakeBunOnly]) {
fs.mkdirSync(dir);
writeExec(path.join(dir, 'bun'), [
'#!/usr/bin/env bash',
'a=()',
'for x in "$@"; do',
' case "$x" in',
' --config=/dev/null) echo "EINVAL: Invalid argument (open())" >&2; echo "while reading config \\"/dev/null\\"" >&2; exit 1 ;;',
' --config=NUL) a+=(--config=/dev/null) ;;',
' *) a+=("$x") ;;',
' esac',
'done',
`exec ${JSON.stringify(process.execPath)} "\${a[@]}"`,
'',
].join('\n'));
}
writeExec(path.join(fakeWindows, 'uname'), '#!/usr/bin/env bash\n[ "$1" = -s ] && { echo MINGW64_NT-10.0-26100; exit 0; }\nexec /usr/bin/uname "$@"\n');
writeExec(path.join(fakeWindows, 'cygpath'), '#!/usr/bin/env bash\nprintf "%s\\n" "$2"\n');
fakeBun = path.join(tmp, 'fake-bun');
fs.mkdirSync(fakeBun);
writeExec(path.join(fakeBun, 'bun'), [
'#!/usr/bin/env bash',
'a=()',
'for x in "$@"; do',
' case "$x" in',
' --config=/dev/null) echo "EINVAL: Invalid argument (open())" >&2; echo "while reading config \\"/dev/null\\"" >&2; exit 1 ;;',
' --config=NUL) a+=(--config=/dev/null) ;;',
' *) a+=("$x") ;;',
' esac',
'done',
`exec ${JSON.stringify(process.execPath)} "\${a[@]}"`,
'',
].join('\n'));
});
afterAll(() => { if (tmp) fs.rmSync(tmp, { recursive: true, force: true }); });
@@ -143,7 +138,7 @@ describe.skipIf(IS_WINDOWS)('#3415: simulated Windows bash (POSIX hosts)', () =>
for (const { bin, input, log } of BINS) {
test(`${bin} passes NUL and writes its record`, () => {
resetState();
const r = run(bin, input, fakeWindows);
const r = run(bin, input, fakeBun, { OSTYPE: 'msys' });
expect(r.stderr).not.toContain('EINVAL');
expect(r.status).toBe(0);
expect(records(log).length).toBe(1);
@@ -152,7 +147,7 @@ describe.skipIf(IS_WINDOWS)('#3415: simulated Windows bash (POSIX hosts)', () =>
test(`control: when Bun rejects the config, ${bin} fails loudly and writes nothing`, () => {
resetState();
const r = run(bin, input, fakeBunOnly);
const r = run(bin, input, fakeBun);
expect(r.stderr).toContain('EINVAL');
expect(r.status).not.toBe(0);
expect(records(log)).toEqual([]);
+1 -1
View File
@@ -30,7 +30,7 @@ const ROOT = path.resolve(import.meta.dir, '..');
const FLAGS = '--no-env-file --config=/dev/null';
const ISOLATED_SHEBANG = `#!/usr/bin/env -S bun ${FLAGS}`;
const SHELL_FLAGS = '--no-env-file --config="${GSTACK_BUN_CONFIG:-/dev/null}"';
const CONFIG_DEF = 'GSTACK_BUN_CONFIG=/dev/null; case "$(uname -s)" in MINGW*|MSYS*|CYGWIN*) GSTACK_BUN_CONFIG=NUL ;; esac';
const CONFIG_DEF = 'GSTACK_BUN_CONFIG=/dev/null; case "${OSTYPE:-}" in msys*|cygwin*) GSTACK_BUN_CONFIG=NUL ;; esac';
const TRACKED = spawnSync('git', ['ls-files', '-z'], { cwd: ROOT, encoding: 'utf8', timeout: 30_000 }).stdout.split('\0').filter(Boolean)
.filter(f => !/(^|\/)test\//.test(f) && fs.existsSync(path.join(ROOT, f)) && fs.statSync(path.join(ROOT, f)).isFile());
+1 -1
View File
@@ -18,7 +18,7 @@ import { bunIsolationArgs } from '../lib/bun-isolation';
import { bashCommandInvocation } from '../lib/script-invocation';
const PRINT = 'console.log("env=" + (process.env.GSTACK_LAUNCH_PROBE ?? "unset"))';
const CONFIG_DEF = 'GSTACK_BUN_CONFIG=/dev/null; case "$(uname -s)" in MINGW*|MSYS*|CYGWIN*) GSTACK_BUN_CONFIG=NUL ;; esac';
const CONFIG_DEF = 'GSTACK_BUN_CONFIG=/dev/null; case "${OSTYPE:-}" in msys*|cygwin*) GSTACK_BUN_CONFIG=NUL ;; esac';
const NO_PATHCONV = { MSYS_NO_PATHCONV: '1', MSYS2_ARG_CONV_EXCL: '*' };
let project = '';
let preloadMarker = '';
+1 -1
View File
@@ -16,7 +16,7 @@ import * as path from 'path';
const ROOT = path.resolve(import.meta.dir, '..');
const PLAN_ARG = '${_PLAN_MODE_ARG:+"$_PLAN_MODE_ARG"}';
const GENERATOR = /(?:\bbun(?: --no-env-file --config=\/dev\/null)?|\bbun_cmd|_gstack_render_bun) run (?:gen:skill-docs(?::user)?|scripts\/gen-skill-docs\.ts) --[a-z]/;
const GENERATOR = /(?:\bbun(?: --no-env-file --config=(?:\/dev\/null|"\$\{GSTACK_BUN_CONFIG:-\/dev\/null\}"))?|\bbun_cmd|_gstack_render_bun) run (?:gen:skill-docs(?::user)?|scripts\/gen-skill-docs\.ts) --[a-z]/;
const CONFIG_READ = '[ "$("$GSTACK_CONFIG" get plan_mode_preamble 2>/dev/null || true)" != off ] || _PLAN_MODE_ARG="--plan-mode=off"';
/** Generator invocations in a file: not comments, and not echoed or logged instructions. */