#!/usr/bin/env bash # gstack-team-init — generate repo-level bootstrap files for team mode # # Usage: # gstack-team-init optional # gentle CLAUDE.md suggestion, one-time offer # gstack-team-init required # CLAUDE.md enforcement + PreToolUse hook # # Run from the root of your team's repo (not from the gstack directory). set -euo pipefail MODE="${1:-}" if [ "$MODE" != "optional" ] && [ "$MODE" != "required" ]; then echo "Usage: gstack-team-init {optional|required}" >&2 echo "" >&2 echo " optional — suggest gstack install once per developer (gentle)" >&2 echo " required — enforce gstack install, block work without it" >&2 exit 1 fi # Must be in a git repo if ! git rev-parse --show-toplevel >/dev/null 2>&1; then echo "Error: not in a git repository. Run from your project root." >&2 exit 1 fi REPO_ROOT=$(git rev-parse --show-toplevel) CLAUDE_MD="$REPO_ROOT/CLAUDE.md" GENERATED=() # ── Migrate vendored copy if present ────────────────────────── if [ -d "$REPO_ROOT/.claude/skills/gstack" ] && [ ! -L "$REPO_ROOT/.claude/skills/gstack" ]; then if [ -f "$REPO_ROOT/.claude/skills/gstack/VERSION" ] || [ -d "$REPO_ROOT/.claude/skills/gstack/.git" ]; then echo " Found vendored gstack copy at $REPO_ROOT/.claude/skills/gstack" echo " Team mode uses the global install — removing vendored copy..." ( cd "$REPO_ROOT" && git rm -r --cached .claude/skills/gstack/ 2>/dev/null ) || true if [ -f "$REPO_ROOT/.gitignore" ]; then if ! grep -qF '.claude/skills/gstack/' "$REPO_ROOT/.gitignore" 2>/dev/null; then echo '.claude/skills/gstack/' >> "$REPO_ROOT/.gitignore" fi else echo '.claude/skills/gstack/' > "$REPO_ROOT/.gitignore" fi rm -rf "$REPO_ROOT/.claude/skills/gstack" GENERATED+=(".gitignore") echo " Removed vendored copy and added .claude/skills/gstack/ to .gitignore" fi fi # ── CLAUDE.md snippet ────────────────────────────────────────── if [ "$MODE" = "optional" ]; then SNIPPET='## gstack (recommended) This project uses [gstack](https://github.com/garrytan/gstack) for AI-assisted workflows. Install it for the best experience: ```bash git clone --depth 1 https://github.com/garrytan/gstack.git ~/.claude/skills/gstack cd ~/.claude/skills/gstack && ./setup --team ``` Skills like /qa, /ship, /review, /investigate, and /browse become available after install. Use /browse for all web browsing. Use ~/.claude/skills/gstack/... for gstack file paths.' else SNIPPET=$(cat <<'SNIPPET_EOF' ## gstack (REQUIRED — global install) **Before doing ANY work, verify gstack is installed:** ``` node -e "const fs=require('node:fs'),os=require('node:os'),p=require('node:path'); console.log(fs.existsSync(p.join(os.homedir(),'.claude','skills','gstack','bin')) ? 'GSTACK_OK' : 'GSTACK_MISSING')" ``` If GSTACK_MISSING: STOP. Do not proceed. Tell the user: > gstack is required for all AI-assisted work in this repo. > Install it: > ```bash > git clone --depth 1 https://github.com/garrytan/gstack.git ~/.claude/skills/gstack > cd ~/.claude/skills/gstack && ./setup --team > ``` > Then restart your AI coding tool. Do not skip skills, ignore gstack errors, or work around missing gstack. Using gstack skills: After install, skills like /qa, /ship, /review, /investigate, and /browse are available. Use /browse for all web browsing. Use ~/.claude/skills/gstack/... for gstack file paths (the global path). SNIPPET_EOF ) fi # Check if CLAUDE.md already has a gstack section if [ -f "$CLAUDE_MD" ] && grep -q "## gstack" "$CLAUDE_MD" 2>/dev/null; then echo "CLAUDE.md already has a gstack section. Skipping CLAUDE.md update." echo " To replace it, remove the existing ## gstack section and re-run." else if [ -f "$CLAUDE_MD" ]; then echo "" >> "$CLAUDE_MD" fi echo "$SNIPPET" >> "$CLAUDE_MD" GENERATED+=("CLAUDE.md") echo " + CLAUDE.md — added gstack $MODE section" fi # ── Required mode: enforcement hook ──────────────────────────── if [ "$MODE" = "required" ]; then HOOKS_DIR="$REPO_ROOT/.claude/hooks" SETTINGS="$REPO_ROOT/.claude/settings.json" # Create a CommonJS hook so consumer package.json module settings cannot change # how Node 18+ loads it. mkdir -p "$HOOKS_DIR" cat > "$HOOKS_DIR/check-gstack.cjs" << 'HOOK_EOF' 'use strict'; // Block skill usage when gstack is not installed globally. const fs = require('node:fs'); const os = require('node:os'); const path = require('node:path'); const homeDir = process.env.HOME || process.env.USERPROFILE || os.homedir(); const gstackBin = path.join(homeDir, '.claude', 'skills', 'gstack', 'bin'); let verificationError = null; let installed = false; try { installed = fs.statSync(gstackBin).isDirectory(); } catch (error) { if (error && error.code !== 'ENOENT') { verificationError = error; } } if (installed) { process.stdout.write('{}\n'); } else { const projectDir = process.env.CLAUDE_PROJECT_DIR || '(unknown project)'; const heading = verificationError ? 'BLOCKED: the global gstack install could not be verified.' : 'BLOCKED: gstack is not installed globally.'; const instructions = `${heading} gstack is required for AI-assisted work in ${projectDir}. Install it: git clone --depth 1 https://github.com/garrytan/gstack.git ~/.claude/skills/gstack cd ~/.claude/skills/gstack && ./setup --team Then restart your AI coding tool. `; process.stderr.write(instructions); process.stdout.write(`${JSON.stringify({ hookSpecificOutput: { hookEventName: 'PreToolUse', permissionDecision: 'deny', permissionDecisionReason: instructions, }, })}\n`); } HOOK_EOF GENERATED+=(".claude/hooks/check-gstack.cjs") echo " + .claude/hooks/check-gstack.cjs — cross-platform enforcement hook" # Add hook to project-level settings.json if command -v bun >/dev/null 2>&1; then GSTACK_SETTINGS_PATH="$SETTINGS" bun -e " const fs = require('fs'); const settingsPath = process.env.GSTACK_SETTINGS_PATH; let settings = {}; try { settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8')); } catch {} if (!settings.hooks) settings.hooks = {}; if (!settings.hooks.PreToolUse) settings.hooks.PreToolUse = []; const hookCommand = \"node -e \\\"const deny=reason=>{console.error(reason);console.log(JSON.stringify({hookSpecificOutput:{hookEventName:'PreToolUse',permissionDecision:'deny',permissionDecisionReason:reason}}))};const projectDir=process.env.CLAUDE_PROJECT_DIR;if(!projectDir){deny('BLOCKED: CLAUDE_PROJECT_DIR is unavailable, so the required gstack hook cannot be loaded.')}else{try{require(require('node:path').join(projectDir, '.claude', 'hooks', 'check-gstack.cjs'))}catch{deny('BLOCKED: the required gstack hook could not be loaded. Verify project hook setup and retry.')}}\\\"\"; let found = false; settings.hooks.PreToolUse = settings.hooks.PreToolUse.filter(entry => { if (entry.matcher !== 'Skill' || !entry.hooks) return true; const matchingHooks = entry.hooks.filter( h => h.command && h.command.includes('check-gstack') ); if (matchingHooks.length === 0) return true; entry.hooks = entry.hooks.filter( h => !h.command || !h.command.includes('check-gstack') ); if (!found) { entry.hooks.push({ type: 'command', command: hookCommand }); found = true; } return entry.hooks.length > 0; }); if (!found) { settings.hooks.PreToolUse.push({ matcher: 'Skill', hooks: [{ type: 'command', command: hookCommand }] }); } const tmp = settingsPath + '.tmp'; fs.writeFileSync(tmp, JSON.stringify(settings, null, 2) + '\n'); fs.renameSync(tmp, settingsPath); " 2>/dev/null GENERATED+=(".claude/settings.json") echo " + .claude/settings.json — PreToolUse hook registered" # Remove the obsolete Bash hook only after its replacement is generated and # registered successfully. Other project hooks are left untouched. if [ -e "$HOOKS_DIR/check-gstack.sh" ] || [ -L "$HOOKS_DIR/check-gstack.sh" ]; then rm "$HOOKS_DIR/check-gstack.sh" echo " - .claude/hooks/check-gstack.sh — removed legacy enforcement hook" fi else echo " ! bun not found — manually add the PreToolUse hook to .claude/settings.json" fi fi # ── Summary ──────────────────────────────────────────────────── echo "" echo "Team mode ($MODE) initialized." echo "" if [ ${#GENERATED[@]} -gt 0 ]; then echo "Commit the generated files:" echo " git add ${GENERATED[*]}" echo " git commit -m \"chore: require gstack for AI-assisted work\"" fi echo "" echo "Each developer then runs:" echo " git clone --depth 1 https://github.com/garrytan/gstack.git ~/.claude/skills/gstack" echo " cd ~/.claude/skills/gstack && ./setup --team"