From 88109da135246a80a6815f2fc6d2055a8466c86c Mon Sep 17 00:00:00 2001 From: David Miserak Date: Sun, 7 Jun 2026 13:56:29 -0400 Subject: [PATCH] refactor(setup): group steps into four phases ordered by failure probability MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit setup used to interleave trivial filesystem ops, expensive compute, settings.json mutations, and network/sudo steps in whatever order the script grew. A Playwright Chromium download failing at step 2 would abort before skills got registered at step 4, hooks landed at step 10, or migrations ran at step 8. This commit groups work by where failures actually come from: Phase A — filesystem (always succeeds): ~/.gstack/, welcome, GBrain detection Phase B — local compute (low fail): bun build, skill linking, migrations, regen Phase C — settings.json mutation (medium fail, reversible): team-mode + plan-tune hooks Phase D — external network/sudo/package managers (highest fail): coreutils, font, Playwright Each phase is bracketed with a header comment; steps within are renumbered (A1, A2, B1, B2a...). The biggest behavior shift is the GBrain detect/regen split that the new order requires: detection moves to A3 (filesystem scan only, no dependencies), regen moves to B6 (runs after the binary is built and skill docs are generated). Detection persists to ~/.gstack/gbrain-detection.json so B6 can read it. A safety guard for that split: when gstack-gbrain-detect exits non-zero, A3 removes both the temp file AND any pre-existing detection file from a prior successful run. Without the guard, a stale "ok" detection file would survive a failed detect and silently apply ~250 tokens of overhead per planning skill to a user whose gbrain may no longer be installed. Behavior preservation: - bun_cmd routing in link_{codex,factory,opencode}_skill_dirs is unchanged (still buggy here). The fix is in #1898. - gen:skill-docs:user still pipes through `tail -3` in B6 (still buggy here). The fix is in #1898. - Playwright Chromium install/verify in D3 still exits 1 on failure (unchanged). The warn-don't-exit conversion is in #1900 and coordinates with #1838. Splitting those keeps this PR reviewable as "a move plus the gbrain split that the move requires," per @jbetala7's request on #1883. --- CHANGELOG.md | 115 ++++++++ VERSION | 2 +- setup | 420 +++++++++++++++------------- test/setup-gbrain-detection.test.ts | 12 + 4 files changed, 346 insertions(+), 203 deletions(-) create mode 100644 test/setup-gbrain-detection.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 5109cc1670..f81b61a3c7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,120 @@ # Changelog +## [1.57.4.0] - 2026-06-08 + +## **`./setup` now runs in four phases ordered by failure probability. A bad network day or a missing `sudo` no longer leaves you with zero skills installed.** + +`./setup` used to interleave trivial filesystem operations, expensive +compute, settings.json mutations, and network/sudo steps in whatever order +the script grew. That meant a Playwright Chromium download failing at step +2 would abort the run before skills got registered at step 4, before hooks +landed at step 10, before migrations ran at step 8. A re-run would do the +same thing — die at step 2. + +This release groups the work by where failures actually come from: + +| Phase | What runs | Fail risk | +|-------|-----------|-----------| +| **A** | `~/.gstack/` directory, welcome message, GBrain detection (file scan only) | Always succeeds | +| **B** | Local compute: binary build, skill linking, migrations, GBrain SKILL.md regen | Low | +| **C** | `settings.json` mutation: team-mode hooks, plan-tune cathedral hooks | Medium, fully reversible | +| **D** | External network / sudo / package managers: coreutils, emoji font, Playwright | Highest | + +Each phase is bracketed with a clear header in the script so contributors +can find where new work belongs. Steps inside each phase are numbered +(A1, A2, A3, B1...). When something fails in Phase D, the skills, hooks, +and migrations from Phases A-C are already in place. Re-running `./setup` +retries only the failed phase. + +This is intentionally a behavior-preserving move where possible. Two +known follow-ups land as separate PRs: + +- **#1898**: surgical bug fixes (`bun_cmd` routing in three `link_*_skill_dirs` + helpers; `| tail -3` pipe that swallowed bun's exit code on + `gen:skill-docs:user`). +- **#1900**: convert Playwright Chromium install/verify from `exit 1` to + best-effort warn. Coordinates with #1838. + +Splitting those out keeps this PR reviewable as "a move plus the gbrain +detect/regen split that the new order requires." The split was requested +by @jbetala7 on #1883. + +### What changed structurally + +The biggest behavior shift forced by the phase reorder is the **GBrain +detection / SKILL.md regen split**. Previously detection ran late in the +script, right alongside the regen call. The new structure runs detection +in **A3** (filesystem scan only, no dependencies) and regen in **B6** +(after the binary is built and skill docs are generated). The detection +result is persisted to `~/.gstack/gbrain-detection.json` so B6 can read it. + +A safety guard for that split: when `gstack-gbrain-detect` exits non-zero, +A3 now removes both the temp file AND any pre-existing `gbrain-detection.json` +from a prior successful run. Without the guard, a stale "ok" detection file +would survive a failed detect and trigger brain-aware regen in B6 — silently +applying ~250 tokens of overhead per planning skill to a user whose gbrain +might no longer be installed. + +### The numbers that matter + +Source: `bun test test/setup-*.test.ts` plus inspection of the new structure +against `origin/main`. + +| Metric | Before | After | Δ | +|--------|--------|-------|---| +| Setup steps numbered 1-11 in mixed order | yes | grouped A-D by fail risk | restructured | +| Phase headers in script | 0 | 4 | navigability | +| GBrain detection runs before bun build | no | yes (A3, file scan only) | order | +| GBrain regen runs after skill docs | no | yes (B6) | order | +| Stale detection file survives a failed detect | yes (silent ~250 tok overhead) | no | safety guard | +| Test files covering the new structure | 0 | 1 | invariant pinned | + +### What this means for you + +If you re-run `./setup` you'll see the four phase headers in the output. +On a clean install nothing about the outcome changes — the same skills get +registered, the same hooks get installed, the same browser gets downloaded. +On a flaky network or a locked-down machine you'll start seeing partial +installs (skills + hooks but no Chromium) instead of mid-run aborts — +that's the warn-on-Playwright story in #1900 once it lands. + +### Itemized changes + +#### Changed + +- `setup`: reorganized into four phases (A: filesystem, B: local compute, + C: settings.json mutation, D: external network/sudo). Each phase is + bracketed with header comments. Step numbering moves from `1, 2, 3, 4...` + to `A1, A2, B1, B2a, B2b, B3a...`. +- `setup`: GBrain detection moves to phase A3 (filesystem scan only, runs + before bun is prepared). The regen call moves to phase B6 (runs after + skill docs are generated). Detection result persists to + `~/.gstack/gbrain-detection.json` between phases. +- `setup`: when `gstack-gbrain-detect` exits non-zero, A3 now removes both + the temp file AND any pre-existing `gbrain-detection.json` from a prior + successful run. This prevents a stale "ok" file from triggering + brain-aware regen for a user whose gbrain may have been removed. + +#### For contributors + +- `test/setup-gbrain-detection.test.ts`: pins the stale-file safety guard + with a static-source check (`rm -f "$DETECTION_FILE.tmp" + "$DETECTION_FILE"` must appear in the failure path). + +#### Carved out from this PR + +To keep the move reviewable, two changes that were originally in #1883 +are now their own PRs: + +- **#1898** (carved out for fast merge): `bun run` → `bun_cmd run` in + three `link_*_skill_dirs` helpers; remove `| tail -3` from + `gen:skill-docs:user` so its `||` warning guard actually fires on bun + failure. +- **#1900** (carved out, coordinates with #1838): Playwright Chromium + install/verify becomes best-effort warn instead of `exit 1`. + +Credit: structural feedback by @jbetala7 on #1883. + ## [1.57.3.0] - 2026-06-07 ## **Every PR `/ship` opens gets the version stamped into its title, fork and agent PRs included.** diff --git a/VERSION b/VERSION index e97e1faf0f..283abc2ce4 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.57.3.0 +1.57.4.0 diff --git a/setup b/setup index 0c180f7bf5..d136bc2fd5 100755 --- a/setup +++ b/setup @@ -374,163 +374,36 @@ cleanup_copied_bun() { prepare_bun_for_windows_compile trap cleanup_copied_bun EXIT -# 1. Build browse binary if needed (smart rebuild: stale sources, package.json, lock) -NEEDS_BUILD=0 -if [ ! -x "$BROWSE_BIN" ]; then - NEEDS_BUILD=1 -elif [ -n "$(find "$SOURCE_GSTACK_DIR/browse/src" -type f -newer "$BROWSE_BIN" -print -quit 2>/dev/null)" ]; then - NEEDS_BUILD=1 -elif [ "$SOURCE_GSTACK_DIR/package.json" -nt "$BROWSE_BIN" ]; then - NEEDS_BUILD=1 -elif [ -f "$SOURCE_GSTACK_DIR/bun.lock" ] && [ "$SOURCE_GSTACK_DIR/bun.lock" -nt "$BROWSE_BIN" ]; then - NEEDS_BUILD=1 -fi - -if [ "$NEEDS_BUILD" -eq 1 ]; then - log "Building browse binary..." - ( - cd "$SOURCE_GSTACK_DIR" - bun_cmd install --frozen-lockfile 2>/dev/null || bun_cmd install - bun_cmd run build - ) - # Safety net: write .version if build script didn't (e.g., git not available during build) - if [ ! -f "$SOURCE_GSTACK_DIR/browse/dist/.version" ]; then - git -C "$SOURCE_GSTACK_DIR" rev-parse HEAD > "$SOURCE_GSTACK_DIR/browse/dist/.version" 2>/dev/null || true - fi - - # macOS Apple Silicon: ad-hoc codesign compiled binaries. - # Bun's --compile can produce a corrupt or linker-only code signature that - # macOS kills with SIGKILL (exit 137). The two-step remove+re-sign is - # required because a naive `codesign -s - -f` fails when the existing - # signature block is corrupt. This is idempotent and costs <1s. - # See: https://github.com/garrytan/gstack/issues/997 - if [ "$(uname -s)" = "Darwin" ] && [ "$(uname -m)" = "arm64" ]; then - for _bin in browse/dist/browse browse/dist/find-browse design/dist/design make-pdf/dist/pdf bin/gstack-global-discover; do - _bin_path="$SOURCE_GSTACK_DIR/$_bin" - [ -f "$_bin_path" ] && [ -x "$_bin_path" ] || continue - codesign --remove-signature "$_bin_path" 2>/dev/null || true - if ! codesign -s - -f "$_bin_path" 2>/dev/null; then - log "warning: codesign failed for $_bin (binary may not run on Apple Silicon)" - fi - done - fi - - # macOS: install coreutils for `gtimeout` (Codex hang protection in /codex + /autoplan). - # macOS ships BSD `timeout`-less; Homebrew's coreutils installs GNU timeout as - # `gtimeout` to avoid shadowing BSD utilities. The /codex and /autoplan skills - # fall back to unwrapped codex invocations when neither is available — this - # auto-install upgrades them to hang-protected where possible. - # Skip entirely with GSTACK_SKIP_COREUTILS=1 (CI, managed machines, offline envs). - if [ "$(uname -s)" = "Darwin" ] && [ "${GSTACK_SKIP_COREUTILS:-0}" != "1" ]; then - if ! command -v gtimeout >/dev/null 2>&1 && ! command -v timeout >/dev/null 2>&1; then - if command -v brew >/dev/null 2>&1; then - log "Installing coreutils for Codex hang protection (set GSTACK_SKIP_COREUTILS=1 to skip)..." - brew install coreutils >/dev/null 2>&1 || log "warning: brew install coreutils failed; /codex will run without hang protection" - else - log "warning: Homebrew not found. /codex will run without hang protection. Install coreutils manually or set GSTACK_SKIP_COREUTILS=1." - fi - fi - fi -fi - -if [ ! -x "$BROWSE_BIN" ]; then - echo "gstack setup failed: browse binary missing at $BROWSE_BIN" >&2 - exit 1 -fi - -# 1b. Generate .agents/ Codex skill docs — always regenerate to prevent stale descriptions. -# .agents/ is no longer committed — generated at setup time from .tmpl templates. -# bun run build already does this, but we need it when NEEDS_BUILD=0 (binary is fresh). -# Always regenerate: generation is fast (<2s) and mtime-based staleness checks are fragile -# (miss stale files when timestamps match after clone/checkout/upgrade). -AGENTS_DIR="$SOURCE_GSTACK_DIR/.agents/skills" -NEEDS_AGENTS_GEN=1 - -if [ "$NEEDS_AGENTS_GEN" -eq 1 ] && [ "$NEEDS_BUILD" -eq 0 ]; then - log "Generating .agents/ skill docs..." - ( - cd "$SOURCE_GSTACK_DIR" - bun_cmd install --frozen-lockfile 2>/dev/null || bun_cmd install - bun_cmd run gen:skill-docs --host codex - ) -fi - -# 1c. Generate .factory/ Factory Droid skill docs -if [ "$INSTALL_FACTORY" -eq 1 ] && [ "$NEEDS_BUILD" -eq 0 ]; then - log "Generating .factory/ skill docs..." - ( - cd "$SOURCE_GSTACK_DIR" - bun_cmd install --frozen-lockfile 2>/dev/null || bun_cmd install - bun_cmd run gen:skill-docs --host factory - ) -fi - -# 1d. Generate .opencode/ OpenCode skill docs -if [ "$INSTALL_OPENCODE" -eq 1 ] && [ "$NEEDS_BUILD" -eq 0 ]; then - log "Generating .opencode/ skill docs..." - ( - cd "$SOURCE_GSTACK_DIR" - bun_cmd install --frozen-lockfile 2>/dev/null || bun_cmd install - bun_cmd run gen:skill-docs --host opencode - ) -fi +# ──────────────────────────────────────────────────────────────────────────── +# PHASE A — Trivial operations (always succeed first) +# ──────────────────────────────────────────────────────────────────────────── -# 2. Ensure Playwright's Chromium is available -if ! ensure_playwright_browser; then - echo "Installing Playwright Chromium..." - ( - cd "$SOURCE_GSTACK_DIR" - bunx playwright install chromium - ) +# A1. Ensure ~/.gstack global state directory exists +mkdir -p "$HOME/.gstack/projects" - if [ "$IS_WINDOWS" -eq 1 ]; then - # On Windows, Node.js launches Chromium (not Bun — see oven-sh/bun#4253). - # Ensure playwright is importable by Node from the gstack directory. - if ! command -v node >/dev/null 2>&1; then - echo "gstack setup failed: Node.js is required on Windows (Bun cannot launch Chromium due to a pipe bug)" >&2 - echo " Install Node.js: https://nodejs.org/" >&2 - exit 1 - fi - echo "Windows detected — verifying Node.js can load Playwright..." - ( - cd "$SOURCE_GSTACK_DIR" - # Bun's node_modules already has playwright; verify Node can require it - node -e "require('playwright')" 2>/dev/null || npm install --no-save playwright - # @ngrok/ngrok is externalized in server-node.mjs and resolved at runtime. - # Verify the platform-specific native binary is installed so /pair-agent - # tunnels don't fail later with a cryptic module-not-found error. - node -e "require('@ngrok/ngrok')" 2>/dev/null || npm install --no-save @ngrok/ngrok - ) - fi +# A2. First-time welcome + legacy /tmp cleanup +if [ ! -f "$HOME/.gstack/.welcome-seen" ]; then + log " Welcome! Run /gstack-upgrade anytime to stay current." + touch "$HOME/.gstack/.welcome-seen" fi +rm -f /tmp/gstack-latest-version -if ! ensure_playwright_browser; then - if [ "$IS_WINDOWS" -eq 1 ]; then - echo "gstack setup failed: Playwright Chromium could not be launched via Node.js" >&2 - echo " This is a known issue with Bun on Windows (oven-sh/bun#4253)." >&2 - echo " Ensure Node.js is installed and 'node -e \"require('playwright')\"' works." >&2 +# A3. GBrain detection (file scan; writes gbrain-detection.json). +# Regen on detection hit happens later in B6, once bun is ready and skill +# docs are generated. See the B6 block for the regen + warn behavior. +DETECT_BIN="$SOURCE_GSTACK_DIR/bin/gstack-gbrain-detect" +GBRAIN_STATE_DIR="${GSTACK_HOME:-$HOME/.gstack}" +DETECTION_FILE="$GBRAIN_STATE_DIR/gbrain-detection.json" +mkdir -p "$GBRAIN_STATE_DIR" +if [ -x "$DETECT_BIN" ]; then + if "$DETECT_BIN" > "$DETECTION_FILE.tmp" 2>/dev/null; then + mv "$DETECTION_FILE.tmp" "$DETECTION_FILE" else - echo "gstack setup failed: Playwright Chromium could not be launched" >&2 + rm -f "$DETECTION_FILE.tmp" "$DETECTION_FILE" + log " warning: gstack-gbrain-detect failed — brain-aware blocks will stay suppressed" fi - exit 1 -fi - -# 2b. Ensure a color-emoji font is installed so make-pdf emoji render (Linux). -# Best-effort: warn instead of failing if it can't install. -if ! ensure_emoji_font; then - echo " Note: could not auto-install a color-emoji font. Emoji in make-pdf" >&2 - echo " output may render as boxes (▯). Install one manually, e.g.:" >&2 - echo " Debian/Ubuntu: sudo apt-get install fonts-noto-color-emoji" >&2 - echo " Fedora: sudo dnf install google-noto-color-emoji-fonts" >&2 - echo " Arch: sudo pacman -S noto-fonts-emoji" >&2 - echo " Alpine: sudo apk add font-noto-emoji" >&2 -else - refresh_browse_daemon_for_fonts fi -# 3. Ensure ~/.gstack global state directory exists -mkdir -p "$HOME/.gstack/projects" - # ─── Helper: link Claude skill subdirectories into a skills parent directory ── # Creates real directories (not symlinks) at the top level with a SKILL.md symlink # inside. This ensures Claude discovers them as top-level skills, not nested under @@ -972,7 +845,96 @@ link_opencode_skill_dirs() { fi } -# 4. Install for Claude (default) +# ──────────────────────────────────────────────────────────────────────────── +# PHASE B — Local compute & filesystem (low fail) +# ──────────────────────────────────────────────────────────────────────────── + +# B1. Build browse binary if needed (smart rebuild: stale sources, package.json, lock) +NEEDS_BUILD=0 +if [ ! -x "$BROWSE_BIN" ]; then + NEEDS_BUILD=1 +elif [ -n "$(find "$SOURCE_GSTACK_DIR/browse/src" -type f -newer "$BROWSE_BIN" -print -quit 2>/dev/null)" ]; then + NEEDS_BUILD=1 +elif [ "$SOURCE_GSTACK_DIR/package.json" -nt "$BROWSE_BIN" ]; then + NEEDS_BUILD=1 +elif [ -f "$SOURCE_GSTACK_DIR/bun.lock" ] && [ "$SOURCE_GSTACK_DIR/bun.lock" -nt "$BROWSE_BIN" ]; then + NEEDS_BUILD=1 +fi + +if [ "$NEEDS_BUILD" -eq 1 ]; then + log "Building browse binary..." + ( + cd "$SOURCE_GSTACK_DIR" + bun_cmd install --frozen-lockfile 2>/dev/null || bun_cmd install + bun_cmd run build + ) + # Safety net: write .version if build script didn't (e.g., git not available during build) + if [ ! -f "$SOURCE_GSTACK_DIR/browse/dist/.version" ]; then + git -C "$SOURCE_GSTACK_DIR" rev-parse HEAD > "$SOURCE_GSTACK_DIR/browse/dist/.version" 2>/dev/null || true + fi + + # macOS Apple Silicon: ad-hoc codesign compiled binaries. + # Bun's --compile can produce a corrupt or linker-only code signature that + # macOS kills with SIGKILL (exit 137). The two-step remove+re-sign is + # required because a naive `codesign -s - -f` fails when the existing + # signature block is corrupt. This is idempotent and costs <1s. + # See: https://github.com/garrytan/gstack/issues/997 + if [ "$(uname -s)" = "Darwin" ] && [ "$(uname -m)" = "arm64" ]; then + for _bin in browse/dist/browse browse/dist/find-browse design/dist/design make-pdf/dist/pdf bin/gstack-global-discover; do + _bin_path="$SOURCE_GSTACK_DIR/$_bin" + [ -f "$_bin_path" ] && [ -x "$_bin_path" ] || continue + codesign --remove-signature "$_bin_path" 2>/dev/null || true + if ! codesign -s - -f "$_bin_path" 2>/dev/null; then + log "warning: codesign failed for $_bin (binary may not run on Apple Silicon)" + fi + done + fi + +fi + +if [ ! -x "$BROWSE_BIN" ]; then + echo "gstack setup failed: browse binary missing at $BROWSE_BIN" >&2 + exit 1 +fi + +# B2a. Generate .agents/ Codex skill docs — always regenerate to prevent stale descriptions. +# .agents/ is no longer committed — generated at setup time from .tmpl templates. +# bun run build already does this, but we need it when NEEDS_BUILD=0 (binary is fresh). +# Always regenerate: generation is fast (<2s) and mtime-based staleness checks are fragile +# (miss stale files when timestamps match after clone/checkout/upgrade). +AGENTS_DIR="$SOURCE_GSTACK_DIR/.agents/skills" +NEEDS_AGENTS_GEN=1 + +if [ "$NEEDS_AGENTS_GEN" -eq 1 ] && [ "$NEEDS_BUILD" -eq 0 ]; then + log "Generating .agents/ skill docs..." + ( + cd "$SOURCE_GSTACK_DIR" + bun_cmd install --frozen-lockfile 2>/dev/null || bun_cmd install + bun_cmd run gen:skill-docs --host codex + ) +fi + +# B2b. Generate .factory/ Factory Droid skill docs +if [ "$INSTALL_FACTORY" -eq 1 ] && [ "$NEEDS_BUILD" -eq 0 ]; then + log "Generating .factory/ skill docs..." + ( + cd "$SOURCE_GSTACK_DIR" + bun_cmd install --frozen-lockfile 2>/dev/null || bun_cmd install + bun_cmd run gen:skill-docs --host factory + ) +fi + +# B2c. Generate .opencode/ OpenCode skill docs +if [ "$INSTALL_OPENCODE" -eq 1 ] && [ "$NEEDS_BUILD" -eq 0 ]; then + log "Generating .opencode/ skill docs..." + ( + cd "$SOURCE_GSTACK_DIR" + bun_cmd install --frozen-lockfile 2>/dev/null || bun_cmd install + bun_cmd run gen:skill-docs --host opencode + ) +fi + +# B3a. Install for Claude (default) SKILLS_BASENAME="$(basename "$INSTALL_SKILLS_DIR")" SKILLS_PARENT_BASENAME="$(basename "$(dirname "$INSTALL_SKILLS_DIR")")" CODEX_REPO_LOCAL=0 @@ -1082,7 +1044,7 @@ if [ "$INSTALL_CLAUDE" -eq 1 ]; then fi fi -# 5. Install for Codex +# B3b. Install for Codex if [ "$INSTALL_CODEX" -eq 1 ]; then if [ "$CODEX_REPO_LOCAL" -eq 1 ]; then CODEX_SKILLS="$INSTALL_SKILLS_DIR" @@ -1103,7 +1065,7 @@ if [ "$INSTALL_CODEX" -eq 1 ]; then log " codex skills: $CODEX_SKILLS" fi -# 6. Install for Kiro CLI (copy from .agents/skills, rewrite paths) +# B3c. Install for Kiro CLI (copy from .agents/skills, rewrite paths) if [ "$INSTALL_KIRO" -eq 1 ]; then KIRO_SKILLS="$HOME/.kiro/skills" AGENTS_DIR="$SOURCE_GSTACK_DIR/.agents/skills" @@ -1173,7 +1135,7 @@ if [ "$INSTALL_KIRO" -eq 1 ]; then fi fi -# 6b. Install for Factory Droid +# B3d. Install for Factory Droid if [ "$INSTALL_FACTORY" -eq 1 ]; then mkdir -p "$FACTORY_SKILLS" create_factory_runtime_root "$SOURCE_GSTACK_DIR" "$FACTORY_GSTACK" @@ -1183,7 +1145,7 @@ if [ "$INSTALL_FACTORY" -eq 1 ]; then echo " factory skills: $FACTORY_SKILLS" fi -# 6c. Install for OpenCode +# B3e. Install for OpenCode if [ "$INSTALL_OPENCODE" -eq 1 ]; then mkdir -p "$OPENCODE_SKILLS" create_opencode_runtime_root "$SOURCE_GSTACK_DIR" "$OPENCODE_GSTACK" @@ -1193,14 +1155,14 @@ if [ "$INSTALL_OPENCODE" -eq 1 ]; then echo " opencode skills: $OPENCODE_SKILLS" fi -# 7. Create .agents/ sidecar symlinks for the real Codex skill target. +# B4. Create .agents/ sidecar symlinks for the real Codex skill target. # The root Codex skill ends up pointing at $SOURCE_GSTACK_DIR/.agents/skills/gstack, # so the runtime assets must live there for both global and repo-local installs. if [ "$INSTALL_CODEX" -eq 1 ]; then create_agents_sidecar "$SOURCE_GSTACK_DIR" fi -# 8. Run pending version migrations +# B5. Run pending version migrations # Migrations handle state fixes that ./setup alone can't cover (stale config, # orphaned files, directory structure changes). Each migration is idempotent. MIGRATIONS_DIR="$SOURCE_GSTACK_DIR/gstack-upgrade/migrations" @@ -1227,14 +1189,27 @@ if [ "$CURRENT_VERSION" != "unknown" ]; then echo "$CURRENT_VERSION" > "$HOME/.gstack/.last-setup-version" fi -# 9. First-time welcome + legacy cleanup -if [ ! -f "$HOME/.gstack/.welcome-seen" ]; then - log " Welcome! Run /gstack-upgrade anytime to stay current." - touch "$HOME/.gstack/.welcome-seen" +# B6. GBrain regen if detected (see A3 for the detection write). +# Users who install gbrain after running ./setup should re-run setup OR call +# `gstack-config gbrain-refresh` + `bun run gen:skill-docs:user`. +if [ -f "$DETECTION_FILE" ]; then + if grep -q '"gbrain_local_status": "ok"' "$DETECTION_FILE" 2>/dev/null; then + log "gbrain detected — regenerating Claude SKILL.md with brain-aware blocks (~250 token overhead per planning skill)..." + ( + cd "$SOURCE_GSTACK_DIR" + bun_cmd run gen:skill-docs:user --host claude 2>&1 | tail -3 + ) || log " warning: gen:skill-docs:user failed — run 'bun run gen:skill-docs:user' manually if you want brain-aware blocks" + else + log "gbrain not detected — brain-aware blocks suppressed in planning-skill SKILL.md files (zero token overhead)." + log " To enable: install gbrain via /setup-gbrain, then re-run ./setup or 'gstack-config gbrain-refresh'." + fi fi -rm -f /tmp/gstack-latest-version -# 10. Team mode: register/unregister SessionStart hook +# ──────────────────────────────────────────────────────────────────────────── +# PHASE C — Settings.json mutation (medium fail, fully reversible) +# ──────────────────────────────────────────────────────────────────────────── + +# C1. Team mode: register/unregister SessionStart hook SETTINGS_HOOK="$SOURCE_GSTACK_DIR/bin/gstack-settings-hook" HOOK_CMD="$SOURCE_GSTACK_DIR/bin/gstack-session-update" @@ -1268,45 +1243,7 @@ if [ "$NO_TEAM_MODE" -eq 1 ]; then log "Team mode disabled: auto-update hook removed." fi -# ─── GBrain detection + conditional SKILL.md regen ────────────────────── -# -# Detect whether gbrain is installed and persist the result to -# ~/.gstack/gbrain-detection.json so gen-skill-docs can decide whether to -# render GBRAIN_CONTEXT_LOAD and GBRAIN_SAVE_RESULTS blocks. If detected, -# regenerate the Claude-host SKILL.md files with the un-suppressed -# (compressed) brain-aware blocks via `bun run gen:skill-docs:user`. -# -# If gbrain is not detected, the canonical no-gbrain SKILL.md files -# (which were just generated above by `gen:skill-docs --host claude` if -# applicable, or which are checked in) stay as-is. Zero token overhead -# for non-gbrain users. -# -# Users who install gbrain after running ./setup should re-run setup OR -# call `gstack-config gbrain-refresh` + `bun run gen:skill-docs:user`. -DETECT_BIN="$SOURCE_GSTACK_DIR/bin/gstack-gbrain-detect" -GBRAIN_STATE_DIR="${GSTACK_HOME:-$HOME/.gstack}" -DETECTION_FILE="$GBRAIN_STATE_DIR/gbrain-detection.json" -mkdir -p "$GBRAIN_STATE_DIR" -if [ -x "$DETECT_BIN" ]; then - if "$DETECT_BIN" > "$DETECTION_FILE.tmp" 2>/dev/null; then - mv "$DETECTION_FILE.tmp" "$DETECTION_FILE" - if grep -q '"gbrain_local_status": "ok"' "$DETECTION_FILE" 2>/dev/null; then - log "gbrain detected — regenerating Claude SKILL.md with brain-aware blocks (~250 token overhead per planning skill)..." - ( - cd "$SOURCE_GSTACK_DIR" - bun_cmd run gen:skill-docs:user --host claude 2>&1 | tail -3 - ) || log " warning: gen:skill-docs:user failed — run 'bun run gen:skill-docs:user' manually if you want brain-aware blocks" - else - log "gbrain not detected — brain-aware blocks suppressed in planning-skill SKILL.md files (zero token overhead)." - log " To enable: install gbrain via /setup-gbrain, then re-run ./setup or 'gstack-config gbrain-refresh'." - fi - else - rm -f "$DETECTION_FILE.tmp" - log " warning: gstack-gbrain-detect failed — brain-aware blocks will stay suppressed" - fi -fi - -# 11. Plan-tune cathedral hook install (T8). +# C2. Plan-tune cathedral hook install (T8). # # Registers PostToolUse (deterministic AUQ capture) + PreToolUse (preference # enforcement) hooks in ~/.claude/settings.json so /plan-tune actually does @@ -1468,7 +1405,86 @@ if [ "$NO_TEAM_MODE" -ne 1 ] \ fi fi -# Also tear down plan-tune hooks on --no-team (matches the existing pattern). +# C3. Tear down plan-tune hooks on --no-team. if [ "$NO_TEAM_MODE" -eq 1 ] && [ -x "$SETTINGS_HOOK" ]; then "$SETTINGS_HOOK" remove-source --source plan-tune-cathedral 2>/dev/null || true fi + +# ──────────────────────────────────────────────────────────────────────────── +# PHASE D — External network / sudo / package managers (highest fail) +# coreutils (D1) and emoji font (D2) are best-effort: failure warns and +# continues. Playwright Chromium (D3) is still fatal — converting it to +# best-effort warn is tracked separately so this PR can be reviewed as a +# behavior-preserving move. Skills, hooks, and migrations are already in +# place from earlier phases, so re-running ./setup retries only this phase. +# ──────────────────────────────────────────────────────────────────────────── + +# D1. macOS: install coreutils for `gtimeout` (Codex hang protection in /codex + /autoplan). +# macOS ships BSD `timeout`-less; Homebrew's coreutils installs GNU timeout as +# `gtimeout` to avoid shadowing BSD utilities. The /codex and /autoplan skills +# fall back to unwrapped codex invocations when neither is available — this +# auto-install upgrades them to hang-protected where possible. +# Skip entirely with GSTACK_SKIP_COREUTILS=1 (CI, managed machines, offline envs). +if [ "$(uname -s)" = "Darwin" ] && [ "${GSTACK_SKIP_COREUTILS:-0}" != "1" ]; then + if ! command -v gtimeout >/dev/null 2>&1 && ! command -v timeout >/dev/null 2>&1; then + if command -v brew >/dev/null 2>&1; then + log "Installing coreutils for Codex hang protection (set GSTACK_SKIP_COREUTILS=1 to skip)..." + brew install coreutils >/dev/null 2>&1 || log "warning: brew install coreutils failed; /codex will run without hang protection" + else + log "warning: Homebrew not found. /codex will run without hang protection. Install coreutils manually or set GSTACK_SKIP_COREUTILS=1." + fi + fi +fi + +# D2. Ensure a color-emoji font is installed so make-pdf emoji render (Linux). +# Best-effort: warn instead of failing if it can't install. +if ! ensure_emoji_font; then + echo " Note: could not auto-install a color-emoji font. Emoji in make-pdf" >&2 + echo " output may render as boxes (▯). Install one manually, e.g.:" >&2 + echo " Debian/Ubuntu: sudo apt-get install fonts-noto-color-emoji" >&2 + echo " Fedora: sudo dnf install google-noto-color-emoji-fonts" >&2 + echo " Arch: sudo pacman -S noto-fonts-emoji" >&2 + echo " Alpine: sudo apk add font-noto-emoji" >&2 +else + refresh_browse_daemon_for_fonts +fi + +# D3. Ensure Playwright's Chromium is available. +if ! ensure_playwright_browser; then + echo "Installing Playwright Chromium..." + ( + cd "$SOURCE_GSTACK_DIR" + bunx playwright install chromium + ) + + if [ "$IS_WINDOWS" -eq 1 ]; then + # On Windows, Node.js launches Chromium (not Bun — see oven-sh/bun#4253). + # Ensure playwright is importable by Node from the gstack directory. + if ! command -v node >/dev/null 2>&1; then + echo "gstack setup failed: Node.js is required on Windows (Bun cannot launch Chromium due to a pipe bug)" >&2 + echo " Install Node.js: https://nodejs.org/" >&2 + exit 1 + fi + echo "Windows detected — verifying Node.js can load Playwright..." + ( + cd "$SOURCE_GSTACK_DIR" + # Bun's node_modules already has playwright; verify Node can require it + node -e "require('playwright')" 2>/dev/null || npm install --no-save playwright + # @ngrok/ngrok is externalized in server-node.mjs and resolved at runtime. + # Verify the platform-specific native binary is installed so /pair-agent + # tunnels don't fail later with a cryptic module-not-found error. + node -e "require('@ngrok/ngrok')" 2>/dev/null || npm install --no-save @ngrok/ngrok + ) + fi +fi + +if ! ensure_playwright_browser; then + if [ "$IS_WINDOWS" -eq 1 ]; then + echo "gstack setup failed: Playwright Chromium could not be launched via Node.js" >&2 + echo " This is a known issue with Bun on Windows (oven-sh/bun#4253)." >&2 + echo " Ensure Node.js is installed and 'node -e \"require('playwright')\"' works." >&2 + else + echo "gstack setup failed: Playwright Chromium could not be launched" >&2 + fi + exit 1 +fi diff --git a/test/setup-gbrain-detection.test.ts b/test/setup-gbrain-detection.test.ts new file mode 100644 index 0000000000..11baa65059 --- /dev/null +++ b/test/setup-gbrain-detection.test.ts @@ -0,0 +1,12 @@ +import { describe, test, expect } from 'bun:test'; +import * as fs from 'fs'; +import * as path from 'path'; + +const ROOT = path.resolve(import.meta.dir, '..'); +const SETUP_SRC = fs.readFileSync(path.join(ROOT, 'setup'), 'utf-8'); + +describe('setup: gbrain detection failure handling', () => { + test('failed detection clears both temp and persisted detection files', () => { + expect(SETUP_SRC).toContain('rm -f "$DETECTION_FILE.tmp" "$DETECTION_FILE"'); + }); +});