Files
gstack/setup
T
Garry Tan cd66fc2f89 fix: 6 critical fixes + community PR guardrails (v0.13.2.0) (#602)
* fix(security): commit bun.lock to pin dependency versions

Remove bun.lock from .gitignore and commit the lockfile. Every bun install
now uses exact pinned versions instead of resolving floating ^ ranges from
npm fresh. Closes the supply-chain vector from #566.

Co-Authored-By: boinger <boinger@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: gstack-slug falls back to dirname/unknown when git context is absent

Add || true to git commands and fallback defaults so gstack-slug works
outside git repos. Prevents unbound variable crash that kills every
review skill when no git context exists.

Co-Authored-By: collinstraka-clov <collinstraka-clov@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: setup auto-selects default after 10s timeout to prevent CI hangs

Add -t 10 to the read command in the skill-prefix prompt. In CI, Docker,
and Conductor workspaces where a TTY exists but nobody is watching, the
prompt now auto-selects short names after 10 seconds instead of blocking
forever.

Co-Authored-By: stedfn <stedfn@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: browse CLI Windows lockfile — use string flag instead of numeric constants

Bun compiled binaries on Windows don't handle numeric fs.constants
correctly. The string flag 'wx' is semantically identical to
O_CREAT | O_EXCL | O_WRONLY per Node docs and works on all platforms.

Fixes #599

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: add ~/.gstack/projects/ to plan file search path

/office-hours writes design docs to ~/.gstack/projects/$SLUG/ but /ship
and /review only searched ~/.claude/plans, ~/.codex/plans, and .gstack/plans.
Add the project-scoped directory as the first search location so plan
validation finds design docs created by the standard workflow.

Fixes #591

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: autoplan dual-voice — sequential foreground execution instead of broken parallel

Background subagents don't inherit tool permissions in Claude Code, so the
Claude subagent in dual-voice mode was silently failing on every invocation.
Every autoplan run was degrading to single-reviewer mode without warning.

Change all three phases (CEO, Design, Eng) from "simultaneously" to
sequential foreground execution: Claude subagent first (Agent tool,
foreground), then Codex (Bash). Both complete before the consensus table.

Fixes #497

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore: regenerate SKILL.md files from updated templates

Regenerated from autoplan/SKILL.md.tmpl (dual-voice fix) and
scripts/resolvers/review.ts (plan search path fix).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs: add community PR guardrails — protect ETHOS.md and voice

Add explicit CLAUDE.md rule requiring AskUserQuestion before accepting
any community PR that touches ETHOS.md, removes promotional material,
or changes Garry's voice. No exceptions, no auto-merging.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore: bump version and changelog (v0.13.2.0)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: gen-skill-docs detects symlink loop, skips codex write that overwrites Claude SKILL.md

When .agents/skills/gstack is symlinked to the repo root (vendored dev mode),
gen-skill-docs --host codex was writing the Codex-transformed SKILL.md through
the symlink, overwriting the Claude version. This caused SKILL.md and
agents/openai.yaml to silently revert to Codex paths after every build.

Now detects when the codex output path resolves to the same real file as the
Claude output and skips the write. Content is still generated for token budget
tracking. The openai.yaml write is also skipped for the same symlink case.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: resolve all 7 test failures — version sync, zsh glob guard, symlink-aware codex tests

1. package.json version synced with VERSION file (0.13.3.0)
2. design-shotgun/SKILL.md.tmpl: added setopt +o nomatch guard to
   bash block with variant-*.png glob
3. Codex generation tests: skip skills where .agents/skills/{name}
   is a symlink back to repo root (vendored dev mode). These can't
   have proper codex content since gen-skill-docs skips the write
   to avoid overwriting the Claude SKILL.md.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: boinger <boinger@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: collinstraka-clov <collinstraka-clov@users.noreply.github.com>
Co-authored-by: stedfn <stedfn@users.noreply.github.com>
2026-03-28 11:31:56 -06:00

579 lines
21 KiB
Bash
Executable File

#!/usr/bin/env bash
# gstack setup — build browser binary + register skills with Claude Code / Codex
set -e
if ! command -v bun >/dev/null 2>&1; then
echo "Error: bun is required but not installed." >&2
echo "Install it: curl -fsSL https://bun.sh/install | BUN_VERSION=1.3.10 bash" >&2
exit 1
fi
INSTALL_GSTACK_DIR="$(cd "$(dirname "$0")" && pwd)"
SOURCE_GSTACK_DIR="$(cd "$(dirname "$0")" && pwd -P)"
INSTALL_SKILLS_DIR="$(dirname "$INSTALL_GSTACK_DIR")"
BROWSE_BIN="$SOURCE_GSTACK_DIR/browse/dist/browse"
CODEX_SKILLS="$HOME/.codex/skills"
CODEX_GSTACK="$CODEX_SKILLS/gstack"
IS_WINDOWS=0
case "$(uname -s)" in
MINGW*|MSYS*|CYGWIN*|Windows_NT) IS_WINDOWS=1 ;;
esac
# ─── Parse flags ──────────────────────────────────────────────
HOST="claude"
LOCAL_INSTALL=0
SKILL_PREFIX=1
SKILL_PREFIX_FLAG=0
while [ $# -gt 0 ]; do
case "$1" in
--host) [ -z "$2" ] && echo "Missing value for --host (expected claude, codex, kiro, or auto)" >&2 && exit 1; HOST="$2"; shift 2 ;;
--host=*) HOST="${1#--host=}"; shift ;;
--local) LOCAL_INSTALL=1; shift ;;
--prefix) SKILL_PREFIX=1; SKILL_PREFIX_FLAG=1; shift ;;
--no-prefix) SKILL_PREFIX=0; SKILL_PREFIX_FLAG=1; shift ;;
*) shift ;;
esac
done
case "$HOST" in
claude|codex|kiro|auto) ;;
*) echo "Unknown --host value: $HOST (expected claude, codex, kiro, or auto)" >&2; exit 1 ;;
esac
# ─── Resolve skill prefix preference ─────────────────────────
# Priority: CLI flag > saved config > interactive prompt (or flat default for non-TTY)
GSTACK_CONFIG="$SOURCE_GSTACK_DIR/bin/gstack-config"
if [ "$SKILL_PREFIX_FLAG" -eq 0 ]; then
_saved_prefix="$("$GSTACK_CONFIG" get skill_prefix 2>/dev/null || true)"
if [ "$_saved_prefix" = "true" ]; then
SKILL_PREFIX=1
elif [ "$_saved_prefix" = "false" ]; then
SKILL_PREFIX=0
else
# No saved preference — prompt interactively (or default flat for non-TTY)
if [ -t 0 ]; then
echo ""
echo "Skill naming: how should gstack skills appear?"
echo ""
echo " 1) Short names: /qa, /ship, /review"
echo " Recommended. Clean and fast to type."
echo ""
echo " 2) Namespaced: /gstack-qa, /gstack-ship, /gstack-review"
echo " Use this if you run other skill packs alongside gstack to avoid conflicts."
echo ""
printf "Choice [1/2] (default: 1, auto-selects in 10s): "
read -t 10 -r _prefix_choice </dev/tty 2>/dev/null || _prefix_choice=""
case "$_prefix_choice" in
2) SKILL_PREFIX=1 ;;
*) SKILL_PREFIX=0 ;;
esac
else
SKILL_PREFIX=0
fi
# Save the choice for future runs
"$GSTACK_CONFIG" set skill_prefix "$([ "$SKILL_PREFIX" -eq 1 ] && echo true || echo false)" 2>/dev/null || true
fi
else
# Flag was passed explicitly — persist the choice
"$GSTACK_CONFIG" set skill_prefix "$([ "$SKILL_PREFIX" -eq 1 ] && echo true || echo false)" 2>/dev/null || true
fi
# --local: install to .claude/skills/ in the current working directory
if [ "$LOCAL_INSTALL" -eq 1 ]; then
if [ "$HOST" = "codex" ]; then
echo "Error: --local is only supported for Claude Code (not Codex)." >&2
exit 1
fi
INSTALL_SKILLS_DIR="$(pwd)/.claude/skills"
mkdir -p "$INSTALL_SKILLS_DIR"
HOST="claude"
INSTALL_CODEX=0
fi
# For auto: detect which agents are installed
INSTALL_CLAUDE=0
INSTALL_CODEX=0
INSTALL_KIRO=0
if [ "$HOST" = "auto" ]; then
command -v claude >/dev/null 2>&1 && INSTALL_CLAUDE=1
command -v codex >/dev/null 2>&1 && INSTALL_CODEX=1
command -v kiro-cli >/dev/null 2>&1 && INSTALL_KIRO=1
# If none found, default to claude
if [ "$INSTALL_CLAUDE" -eq 0 ] && [ "$INSTALL_CODEX" -eq 0 ] && [ "$INSTALL_KIRO" -eq 0 ]; then
INSTALL_CLAUDE=1
fi
elif [ "$HOST" = "claude" ]; then
INSTALL_CLAUDE=1
elif [ "$HOST" = "codex" ]; then
INSTALL_CODEX=1
elif [ "$HOST" = "kiro" ]; then
INSTALL_KIRO=1
fi
migrate_direct_codex_install() {
local gstack_dir="$1"
local codex_gstack="$2"
local migrated_dir="$HOME/.gstack/repos/gstack"
[ "$gstack_dir" = "$codex_gstack" ] || return 0
[ -L "$gstack_dir" ] && return 0
mkdir -p "$(dirname "$migrated_dir")"
if [ -e "$migrated_dir" ] && [ "$migrated_dir" != "$gstack_dir" ]; then
echo "gstack setup failed: direct Codex install detected at $gstack_dir" >&2
echo "A migrated repo already exists at $migrated_dir; move one of them aside and rerun setup." >&2
exit 1
fi
echo "Migrating direct Codex install to $migrated_dir to avoid duplicate skill discovery..."
mv "$gstack_dir" "$migrated_dir"
SOURCE_GSTACK_DIR="$migrated_dir"
INSTALL_GSTACK_DIR="$migrated_dir"
INSTALL_SKILLS_DIR="$(dirname "$INSTALL_GSTACK_DIR")"
BROWSE_BIN="$SOURCE_GSTACK_DIR/browse/dist/browse"
}
if [ "$INSTALL_CODEX" -eq 1 ]; then
migrate_direct_codex_install "$SOURCE_GSTACK_DIR" "$CODEX_GSTACK"
fi
ensure_playwright_browser() {
if [ "$IS_WINDOWS" -eq 1 ]; then
# On Windows, Bun can't launch Chromium due to broken pipe handling
# (oven-sh/bun#4253). Use Node.js to verify Chromium works instead.
(
cd "$SOURCE_GSTACK_DIR"
node -e "const { chromium } = require('playwright'); (async () => { const b = await chromium.launch(); await b.close(); })()" 2>/dev/null
)
else
(
cd "$SOURCE_GSTACK_DIR"
bun --eval 'import { chromium } from "playwright"; const browser = await chromium.launch(); await browser.close();'
) >/dev/null 2>&1
fi
}
# 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
echo "Building browse binary..."
(
cd "$SOURCE_GSTACK_DIR"
bun install
bun 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
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
echo "Generating .agents/ skill docs..."
(
cd "$SOURCE_GSTACK_DIR"
bun install --frozen-lockfile 2>/dev/null || bun install
bun run gen:skill-docs --host codex
)
fi
# 2. 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
)
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
# 3. Ensure ~/.gstack global state directory exists
mkdir -p "$HOME/.gstack/projects"
# ─── Helper: link Claude skill subdirectories into a skills parent directory ──
# When SKILL_PREFIX=1 (default), symlinks are prefixed with "gstack-" to avoid
# namespace pollution (e.g., gstack-review instead of review).
# Use --no-prefix to restore the old flat names.
link_claude_skill_dirs() {
local gstack_dir="$1"
local skills_dir="$2"
local linked=()
for skill_dir in "$gstack_dir"/*/; do
if [ -f "$skill_dir/SKILL.md" ]; then
skill_name="$(basename "$skill_dir")"
# Skip node_modules
[ "$skill_name" = "node_modules" ] && continue
# Apply gstack- prefix unless --no-prefix or already prefixed
if [ "$SKILL_PREFIX" -eq 1 ]; then
case "$skill_name" in
gstack-*) link_name="$skill_name" ;;
*) link_name="gstack-$skill_name" ;;
esac
else
link_name="$skill_name"
fi
target="$skills_dir/$link_name"
# Create or update symlink; skip if a real file/directory exists
if [ -L "$target" ] || [ ! -e "$target" ]; then
ln -snf "gstack/$skill_name" "$target"
linked+=("$link_name")
fi
fi
done
if [ ${#linked[@]} -gt 0 ]; then
echo " linked skills: ${linked[*]}"
fi
}
# ─── Helper: remove old unprefixed Claude skill symlinks ──────────────────────
# Migration: when switching from flat names to gstack- prefixed names,
# clean up stale symlinks that point into the gstack directory.
cleanup_old_claude_symlinks() {
local gstack_dir="$1"
local skills_dir="$2"
local removed=()
for skill_dir in "$gstack_dir"/*/; do
if [ -f "$skill_dir/SKILL.md" ]; then
skill_name="$(basename "$skill_dir")"
[ "$skill_name" = "node_modules" ] && continue
# Skip already-prefixed dirs (gstack-upgrade) — no old symlink to clean
case "$skill_name" in gstack-*) continue ;; esac
old_target="$skills_dir/$skill_name"
# Only remove if it's a symlink pointing into gstack/
if [ -L "$old_target" ]; then
link_dest="$(readlink "$old_target" 2>/dev/null || true)"
case "$link_dest" in
gstack/*|*/gstack/*)
rm -f "$old_target"
removed+=("$skill_name")
;;
esac
fi
fi
done
if [ ${#removed[@]} -gt 0 ]; then
echo " cleaned up old symlinks: ${removed[*]}"
fi
}
# ─── Helper: remove old prefixed Claude skill symlinks ────────────────────────
# Reverse migration: when switching from gstack- prefixed names to flat names,
# clean up stale gstack-* symlinks that point into the gstack directory.
cleanup_prefixed_claude_symlinks() {
local gstack_dir="$1"
local skills_dir="$2"
local removed=()
for skill_dir in "$gstack_dir"/*/; do
if [ -f "$skill_dir/SKILL.md" ]; then
skill_name="$(basename "$skill_dir")"
[ "$skill_name" = "node_modules" ] && continue
# Only clean up prefixed symlinks for dirs that AREN'T already prefixed
# (e.g., remove gstack-qa but NOT gstack-upgrade which is the real dir name)
case "$skill_name" in gstack-*) continue ;; esac
prefixed_target="$skills_dir/gstack-$skill_name"
# Only remove if it's a symlink pointing into gstack/
if [ -L "$prefixed_target" ]; then
link_dest="$(readlink "$prefixed_target" 2>/dev/null || true)"
case "$link_dest" in
gstack/*|*/gstack/*)
rm -f "$prefixed_target"
removed+=("gstack-$skill_name")
;;
esac
fi
fi
done
if [ ${#removed[@]} -gt 0 ]; then
echo " cleaned up prefixed symlinks: ${removed[*]}"
fi
}
# ─── Helper: link generated Codex skills into a skills parent directory ──
# Installs from .agents/skills/gstack-* (the generated Codex-format skills)
# instead of source dirs (which have Claude paths).
link_codex_skill_dirs() {
local gstack_dir="$1"
local skills_dir="$2"
local agents_dir="$gstack_dir/.agents/skills"
local linked=()
if [ ! -d "$agents_dir" ]; then
echo " Generating .agents/ skill docs..."
( cd "$gstack_dir" && bun run gen:skill-docs --host codex )
fi
if [ ! -d "$agents_dir" ]; then
echo " warning: .agents/skills/ generation failed — run 'bun run gen:skill-docs --host codex' manually" >&2
return 1
fi
for skill_dir in "$agents_dir"/gstack*/; do
if [ -f "$skill_dir/SKILL.md" ]; then
skill_name="$(basename "$skill_dir")"
# Skip the sidecar directory — it contains runtime asset symlinks (bin/,
# browse/), not a skill. Linking it would overwrite the root gstack
# symlink that Step 5 already pointed at the repo root.
[ "$skill_name" = "gstack" ] && continue
target="$skills_dir/$skill_name"
# Create or update symlink
if [ -L "$target" ] || [ ! -e "$target" ]; then
ln -snf "$skill_dir" "$target"
linked+=("$skill_name")
fi
fi
done
if [ ${#linked[@]} -gt 0 ]; then
echo " linked skills: ${linked[*]}"
fi
}
# ─── Helper: create .agents/skills/gstack/ sidecar symlinks ──────────
# Codex/Gemini/Cursor read skills from .agents/skills/. We link runtime
# assets (bin/, browse/dist/, review/, qa/, etc.) so skill templates can
# resolve paths like $SKILL_ROOT/review/design-checklist.md.
create_agents_sidecar() {
local repo_root="$1"
local agents_gstack="$repo_root/.agents/skills/gstack"
mkdir -p "$agents_gstack"
# Sidecar directories that skills reference at runtime
for asset in bin browse review qa; do
local src="$SOURCE_GSTACK_DIR/$asset"
local dst="$agents_gstack/$asset"
if [ -d "$src" ] || [ -f "$src" ]; then
if [ -L "$dst" ] || [ ! -e "$dst" ]; then
ln -snf "$src" "$dst"
fi
fi
done
# Sidecar files that skills reference at runtime
for file in ETHOS.md; do
local src="$SOURCE_GSTACK_DIR/$file"
local dst="$agents_gstack/$file"
if [ -f "$src" ]; then
if [ -L "$dst" ] || [ ! -e "$dst" ]; then
ln -snf "$src" "$dst"
fi
fi
done
}
# ─── Helper: create a minimal ~/.codex/skills/gstack runtime root ───────────
# Codex scans ~/.codex/skills recursively. Exposing the whole repo here causes
# duplicate skills because source SKILL.md files and generated Codex skills are
# both discoverable. Keep this directory limited to runtime assets + root skill.
create_codex_runtime_root() {
local gstack_dir="$1"
local codex_gstack="$2"
local agents_dir="$gstack_dir/.agents/skills"
if [ -L "$codex_gstack" ]; then
rm -f "$codex_gstack"
elif [ -d "$codex_gstack" ] && [ "$codex_gstack" != "$gstack_dir" ]; then
# Old direct installs left a real directory here with stale source skills.
# Remove it so we start fresh with only the minimal runtime assets.
rm -rf "$codex_gstack"
fi
mkdir -p "$codex_gstack" "$codex_gstack/browse" "$codex_gstack/gstack-upgrade" "$codex_gstack/review"
if [ -f "$agents_dir/gstack/SKILL.md" ]; then
ln -snf "$agents_dir/gstack/SKILL.md" "$codex_gstack/SKILL.md"
fi
if [ -d "$gstack_dir/bin" ]; then
ln -snf "$gstack_dir/bin" "$codex_gstack/bin"
fi
if [ -d "$gstack_dir/browse/dist" ]; then
ln -snf "$gstack_dir/browse/dist" "$codex_gstack/browse/dist"
fi
if [ -d "$gstack_dir/browse/bin" ]; then
ln -snf "$gstack_dir/browse/bin" "$codex_gstack/browse/bin"
fi
if [ -f "$agents_dir/gstack-upgrade/SKILL.md" ]; then
ln -snf "$agents_dir/gstack-upgrade/SKILL.md" "$codex_gstack/gstack-upgrade/SKILL.md"
fi
# Review runtime assets (individual files, NOT the whole review/ dir which has SKILL.md)
for f in checklist.md design-checklist.md greptile-triage.md TODOS-format.md; do
if [ -f "$gstack_dir/review/$f" ]; then
ln -snf "$gstack_dir/review/$f" "$codex_gstack/review/$f"
fi
done
# ETHOS.md — referenced by "Search Before Building" in all skill preambles
if [ -f "$gstack_dir/ETHOS.md" ]; then
ln -snf "$gstack_dir/ETHOS.md" "$codex_gstack/ETHOS.md"
fi
}
# 4. Install for Claude (default)
SKILLS_BASENAME="$(basename "$INSTALL_SKILLS_DIR")"
SKILLS_PARENT_BASENAME="$(basename "$(dirname "$INSTALL_SKILLS_DIR")")"
CODEX_REPO_LOCAL=0
if [ "$SKILLS_BASENAME" = "skills" ] && [ "$SKILLS_PARENT_BASENAME" = ".agents" ]; then
CODEX_REPO_LOCAL=1
fi
if [ "$INSTALL_CLAUDE" -eq 1 ]; then
if [ "$SKILLS_BASENAME" = "skills" ]; then
# Clean up stale symlinks from the opposite prefix mode
if [ "$SKILL_PREFIX" -eq 1 ]; then
cleanup_old_claude_symlinks "$SOURCE_GSTACK_DIR" "$INSTALL_SKILLS_DIR"
else
cleanup_prefixed_claude_symlinks "$SOURCE_GSTACK_DIR" "$INSTALL_SKILLS_DIR"
fi
link_claude_skill_dirs "$SOURCE_GSTACK_DIR" "$INSTALL_SKILLS_DIR"
if [ "$LOCAL_INSTALL" -eq 1 ]; then
echo "gstack ready (project-local)."
echo " skills: $INSTALL_SKILLS_DIR"
else
echo "gstack ready (claude)."
fi
echo " browse: $BROWSE_BIN"
else
echo "gstack ready (claude)."
echo " browse: $BROWSE_BIN"
echo " (skipped skill symlinks — not inside .claude/skills/)"
fi
fi
# 5. Install for Codex
if [ "$INSTALL_CODEX" -eq 1 ]; then
if [ "$CODEX_REPO_LOCAL" -eq 1 ]; then
CODEX_SKILLS="$INSTALL_SKILLS_DIR"
CODEX_GSTACK="$INSTALL_GSTACK_DIR"
fi
mkdir -p "$CODEX_SKILLS"
# Skip runtime root creation for repo-local installs — the checkout IS the runtime root.
# create_codex_runtime_root would create self-referential symlinks (bin → bin, etc.).
if [ "$CODEX_REPO_LOCAL" -eq 0 ]; then
create_codex_runtime_root "$SOURCE_GSTACK_DIR" "$CODEX_GSTACK"
fi
# Install generated Codex-format skills (not Claude source dirs)
link_codex_skill_dirs "$SOURCE_GSTACK_DIR" "$CODEX_SKILLS"
echo "gstack ready (codex)."
echo " browse: $BROWSE_BIN"
echo " codex skills: $CODEX_SKILLS"
fi
# 6. 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"
mkdir -p "$KIRO_SKILLS"
# Create gstack dir with symlinks for runtime assets, copy+sed for SKILL.md
KIRO_GSTACK="$KIRO_SKILLS/gstack"
# Remove old whole-dir symlink from previous installs
[ -L "$KIRO_GSTACK" ] && rm -f "$KIRO_GSTACK"
mkdir -p "$KIRO_GSTACK" "$KIRO_GSTACK/browse" "$KIRO_GSTACK/gstack-upgrade" "$KIRO_GSTACK/review"
ln -snf "$SOURCE_GSTACK_DIR/bin" "$KIRO_GSTACK/bin"
ln -snf "$SOURCE_GSTACK_DIR/browse/dist" "$KIRO_GSTACK/browse/dist"
ln -snf "$SOURCE_GSTACK_DIR/browse/bin" "$KIRO_GSTACK/browse/bin"
# ETHOS.md — referenced by "Search Before Building" in all skill preambles
if [ -f "$SOURCE_GSTACK_DIR/ETHOS.md" ]; then
ln -snf "$SOURCE_GSTACK_DIR/ETHOS.md" "$KIRO_GSTACK/ETHOS.md"
fi
# gstack-upgrade skill
if [ -f "$AGENTS_DIR/gstack-upgrade/SKILL.md" ]; then
ln -snf "$AGENTS_DIR/gstack-upgrade/SKILL.md" "$KIRO_GSTACK/gstack-upgrade/SKILL.md"
fi
# Review runtime assets (individual files, not whole dir)
for f in checklist.md design-checklist.md greptile-triage.md TODOS-format.md; do
if [ -f "$SOURCE_GSTACK_DIR/review/$f" ]; then
ln -snf "$SOURCE_GSTACK_DIR/review/$f" "$KIRO_GSTACK/review/$f"
fi
done
# Rewrite root SKILL.md paths for Kiro
sed -e "s|~/.claude/skills/gstack|~/.kiro/skills/gstack|g" \
-e "s|\.claude/skills/gstack|.kiro/skills/gstack|g" \
-e "s|\.claude/skills|.kiro/skills|g" \
"$SOURCE_GSTACK_DIR/SKILL.md" > "$KIRO_GSTACK/SKILL.md"
if [ ! -d "$AGENTS_DIR" ]; then
echo " warning: no .agents/skills/ directory found — run 'bun run build' first" >&2
else
for skill_dir in "$AGENTS_DIR"/gstack*/; do
[ -f "$skill_dir/SKILL.md" ] || continue
skill_name="$(basename "$skill_dir")"
target_dir="$KIRO_SKILLS/$skill_name"
mkdir -p "$target_dir"
# Generated Codex skills use $HOME/.codex (not ~/), plus $GSTACK_ROOT variables.
# Rewrite the default GSTACK_ROOT value and any remaining literal paths.
sed -e 's|\$HOME/.codex/skills/gstack|$HOME/.kiro/skills/gstack|g' \
-e "s|~/.codex/skills/gstack|~/.kiro/skills/gstack|g" \
-e "s|~/.claude/skills/gstack|~/.kiro/skills/gstack|g" \
"$skill_dir/SKILL.md" > "$target_dir/SKILL.md"
done
echo "gstack ready (kiro)."
echo " browse: $BROWSE_BIN"
echo " kiro skills: $KIRO_SKILLS"
fi
fi
# 7. 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. First-time welcome + legacy cleanup
if [ ! -f "$HOME/.gstack/.welcome-seen" ]; then
echo " Welcome! Run /gstack-upgrade anytime to stay current."
touch "$HOME/.gstack/.welcome-seen"
fi
rm -f /tmp/gstack-latest-version