mirror of
https://github.com/garrytan/gstack.git
synced 2026-05-02 11:45:20 +02:00
f075cb757f
* feat: ETHOS.md — gstack builder philosophy Standalone document capturing the four principles: The Golden Age, Boil the Lake, Search Before Building, and Build for Yourself. Introduces the three-layer knowledge framework (tried-and-true, new-and-popular, first-principles) and the Eureka Moment concept — when first-principles reasoning reveals conventional wisdom is wrong. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: Search Before Building preamble section + CLAUDE.md Add generateSearchBeforeBuildingSection(ctx) to gen-skill-docs.ts. Every workflow skill now gets a compact router section covering: - Three layers of knowledge (tried-and-true, new-and-popular, first-principles) - Eureka moment format and jq-based JSONL logging - WebSearch fallback clause - ETHOS.md reference via ctx.paths.skillRoot resolver Also adds compact "Search before building" section to CLAUDE.md. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: skill-specific Search Before Building integrations 8 template changes: - /office-hours: Phase 2.75 Landscape Awareness (WebSearch + three-layer synthesis) - /plan-eng-review: Step 0 search check with layer provenance annotations - /investigate: external pattern search + search escalation on hypothesis failure - /plan-ceo-review: Landscape Check before scope challenge - /review: search-before-recommending for fix patterns - /qa-only: WebSearch in allowed-tools - /design-consultation: three-layer synthesis backport in Phase 2 Step 3 - /retro: eureka moment tracking from ~/.gstack/analytics/eureka.jsonl All search steps include WebSearch fallback clause. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: v0.9.5.0 — Builder Ethos (CHANGELOG + VERSION + TODOS) ETHOS.md + Search Before Building across all workflow skills. Deferred: first-time intro flow (blocked on blog post). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address Codex review — sanitize search, privacy gate, ETHOS.md sidecar Three fixes from adversarial Codex review: - /investigate: sanitize error messages before searching (strip hostnames, IPs, file paths, SQL, customer data). Skip search if unsanitizable. - /office-hours: add privacy gate before landscape search. Use generalized category terms, never the user's specific product name or stealth idea. - setup: link ETHOS.md into .agents/skills/gstack/ sidecar so workspace- local Codex sessions can find the builder philosophy. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: sanitize Phase 2 external pattern search in /investigate The Phase 2 external search also sent raw error messages to WebSearch. Apply same sanitization rule as Phase 3 search escalation. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * docs: sync documentation with shipped changes - ARCHITECTURE.md: preamble now handles 5 things (add Search Before Building) - CLAUDE.md: add ETHOS.md to project structure tree - README.md: add ETHOS.md to docs table Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
270 lines
8.4 KiB
Bash
Executable File
270 lines
8.4 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 | bash" >&2
|
|
exit 1
|
|
fi
|
|
|
|
GSTACK_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
SKILLS_DIR="$(dirname "$GSTACK_DIR")"
|
|
BROWSE_BIN="$GSTACK_DIR/browse/dist/browse"
|
|
|
|
IS_WINDOWS=0
|
|
case "$(uname -s)" in
|
|
MINGW*|MSYS*|CYGWIN*|Windows_NT) IS_WINDOWS=1 ;;
|
|
esac
|
|
|
|
# ─── Parse --host flag ─────────────────────────────────────────
|
|
HOST="claude"
|
|
while [ $# -gt 0 ]; do
|
|
case "$1" in
|
|
--host) HOST="$2"; shift 2 ;;
|
|
--host=*) HOST="${1#--host=}"; shift ;;
|
|
*) shift ;;
|
|
esac
|
|
done
|
|
|
|
case "$HOST" in
|
|
claude|codex|auto) ;;
|
|
*) echo "Unknown --host value: $HOST (expected claude, codex, or auto)" >&2; exit 1 ;;
|
|
esac
|
|
|
|
# For auto: detect which agents are installed
|
|
INSTALL_CLAUDE=0
|
|
INSTALL_CODEX=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
|
|
# If neither found, default to claude
|
|
if [ "$INSTALL_CLAUDE" -eq 0 ] && [ "$INSTALL_CODEX" -eq 0 ]; then
|
|
INSTALL_CLAUDE=1
|
|
fi
|
|
elif [ "$HOST" = "claude" ]; then
|
|
INSTALL_CLAUDE=1
|
|
elif [ "$HOST" = "codex" ]; then
|
|
INSTALL_CODEX=1
|
|
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 "$GSTACK_DIR"
|
|
node -e "const { chromium } = require('playwright'); (async () => { const b = await chromium.launch(); await b.close(); })()" 2>/dev/null
|
|
)
|
|
else
|
|
(
|
|
cd "$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 "$GSTACK_DIR/browse/src" -type f -newer "$BROWSE_BIN" -print -quit 2>/dev/null)" ]; then
|
|
NEEDS_BUILD=1
|
|
elif [ "$GSTACK_DIR/package.json" -nt "$BROWSE_BIN" ]; then
|
|
NEEDS_BUILD=1
|
|
elif [ -f "$GSTACK_DIR/bun.lock" ] && [ "$GSTACK_DIR/bun.lock" -nt "$BROWSE_BIN" ]; then
|
|
NEEDS_BUILD=1
|
|
fi
|
|
|
|
if [ "$NEEDS_BUILD" -eq 1 ]; then
|
|
echo "Building browse binary..."
|
|
(
|
|
cd "$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 "$GSTACK_DIR/browse/dist/.version" ]; then
|
|
git -C "$GSTACK_DIR" rev-parse HEAD > "$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
|
|
|
|
# 2. Ensure Playwright's Chromium is available
|
|
if ! ensure_playwright_browser; then
|
|
echo "Installing Playwright Chromium..."
|
|
(
|
|
cd "$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 "$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 ──
|
|
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
|
|
target="$skills_dir/$skill_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+=("$skill_name")
|
|
fi
|
|
fi
|
|
done
|
|
if [ ${#linked[@]} -gt 0 ]; then
|
|
echo " linked skills: ${linked[*]}"
|
|
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 " warning: no .agents/skills/ directory found — run 'bun run build' first" >&2
|
|
return 1
|
|
fi
|
|
|
|
for skill_dir in "$agents_dir"/gstack*/; do
|
|
if [ -f "$skill_dir/SKILL.md" ]; then
|
|
skill_name="$(basename "$skill_dir")"
|
|
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="$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="$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
|
|
}
|
|
|
|
# 4. Install for Claude (default)
|
|
SKILLS_BASENAME="$(basename "$SKILLS_DIR")"
|
|
if [ "$INSTALL_CLAUDE" -eq 1 ]; then
|
|
if [ "$SKILLS_BASENAME" = "skills" ]; then
|
|
link_claude_skill_dirs "$GSTACK_DIR" "$SKILLS_DIR"
|
|
echo "gstack ready (claude)."
|
|
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
|
|
CODEX_SKILLS="$HOME/.codex/skills"
|
|
CODEX_GSTACK="$CODEX_SKILLS/gstack"
|
|
mkdir -p "$CODEX_SKILLS"
|
|
|
|
# Symlink gstack source for runtime assets (bin/, browse/dist/)
|
|
if [ -L "$CODEX_GSTACK" ] || [ ! -e "$CODEX_GSTACK" ]; then
|
|
ln -snf "$GSTACK_DIR" "$CODEX_GSTACK"
|
|
fi
|
|
# Install generated Codex-format skills (not Claude source dirs)
|
|
link_codex_skill_dirs "$GSTACK_DIR" "$CODEX_SKILLS"
|
|
|
|
echo "gstack ready (codex)."
|
|
echo " browse: $BROWSE_BIN"
|
|
echo " codex skills: $CODEX_SKILLS"
|
|
fi
|
|
|
|
# 6. Create .agents/ sidecar symlinks (useful for Codex/Gemini/Cursor workspace-local)
|
|
if [ "$INSTALL_CODEX" -eq 1 ]; then
|
|
# Detect repo root: if we're inside a skills directory, go up two levels
|
|
if [ "$SKILLS_BASENAME" = "skills" ]; then
|
|
REPO_ROOT="$(dirname "$SKILLS_DIR")"
|
|
else
|
|
REPO_ROOT="$GSTACK_DIR"
|
|
fi
|
|
create_agents_sidecar "$REPO_ROOT"
|
|
fi
|
|
|
|
# 7. First-time welcome + legacy cleanup
|
|
if [ ! -d "$HOME/.gstack" ]; then
|
|
mkdir -p "$HOME/.gstack"
|
|
echo " Welcome! Run /gstack-upgrade anytime to stay current."
|
|
fi
|
|
rm -f /tmp/gstack-latest-version
|