#!/bin/bash # Agent Loop — project setup script # Scaffolds .loop/ directory and generates config.json. # Called by /agent-loop:setup or /agent-loop:run, or run directly. # # Usage: # setup.sh # mode: implement, explore, or fix # setup.sh implement # scaffold + config for implement mode # setup.sh # defaults to implement set -euo pipefail # --- Parse arguments --- ACTION="scaffold" MODE="${1:-implement}" WORKTREE_PATH="" MAIN_LOOP_DIR="" case "$MODE" in --update) ACTION="update" MODE="${2:-implement}" ;; --init-worktree) ACTION="init-worktree" WORKTREE_PATH="$2" MAIN_LOOP_DIR="$3" if [ -z "$WORKTREE_PATH" ] || [ -z "$MAIN_LOOP_DIR" ]; then echo "[setup] ERROR: --init-worktree requires " echo "[setup] Usage: setup.sh --init-worktree /path/to/worktree /path/to/main/.loop" exit 1 fi ;; esac # --- Validate mode --- if [ "$ACTION" = "scaffold" ] && [[ ! "$MODE" =~ ^(implement|explore|fix)$ ]]; then echo "[setup] ERROR: Invalid mode '$MODE'. Must be: implement, explore, fix" exit 1 fi # --- Find harness source --- SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" HARNESS_SRC="" # Option 1: Running from the plugin cache or repo (script is next to prompts/) if [ -d "$SCRIPT_DIR/prompts" ]; then HARNESS_SRC="$SCRIPT_DIR" fi # Option 2: Plugin cache (glob for any version) if [ -z "$HARNESS_SRC" ]; then HARNESS_SRC=$(ls -d ~/.claude/plugins/cache/agent-loop/agent-loop/*/prompts/.. 2>/dev/null | head -1) fi # Option 3: Global install if [ -z "$HARNESS_SRC" ] && [ -d "$HOME/.claude/loop/prompts" ]; then HARNESS_SRC="$HOME/.claude/loop" fi if [ -z "$HARNESS_SRC" ]; then echo "[setup] ERROR: Cannot find agent-loop harness files." echo "[setup] Install the plugin: /plugin install agent-loop@agent-loop" exit 1 fi echo "[setup] Harness source: $HARNESS_SRC" # Read plugin version from source PLUGIN_VERSION="" if [ -f "$HARNESS_SRC/.claude-plugin/plugin.json" ]; then if command -v jq &>/dev/null; then PLUGIN_VERSION=$(jq -r '.version // empty' "$HARNESS_SRC/.claude-plugin/plugin.json" 2>/dev/null) elif command -v python3 &>/dev/null; then PLUGIN_VERSION=$(python3 -c "import json; print(json.load(open('$HARNESS_SRC/.claude-plugin/plugin.json')).get('version',''),end='')" 2>/dev/null) fi fi # --- Update-only mode: refresh harness files without touching run state --- if [ "$ACTION" = "update" ]; then LOOP_DIR="$(pwd)/.loop" if [ ! -d "$LOOP_DIR" ]; then echo "[setup] ERROR: No .loop/ directory found. Run setup first." exit 1 fi echo "[setup] Updating harness files..." cp -r "$HARNESS_SRC/prompts" "$LOOP_DIR/" cp -r "$HARNESS_SRC/templates" "$LOOP_DIR/" cp -r "$HARNESS_SRC/lib" "$LOOP_DIR/" cp "$HARNESS_SRC/loop.sh" "$LOOP_DIR/" chmod +x "$LOOP_DIR/loop.sh" [ -n "$PLUGIN_VERSION" ] && echo "$PLUGIN_VERSION" > "$LOOP_DIR/.harness-version" echo "[setup] Harness updated to ${PLUGIN_VERSION:-unknown}. Run state (prd.json, contracts, config.json) unchanged." exit 0 fi # --- Init-worktree mode: initialize .loop/ in a worktree from main's config --- if [ "$ACTION" = "init-worktree" ]; then LOOP_DIR="$WORKTREE_PATH/.loop" mkdir -p "$LOOP_DIR" # Copy harness files from plugin source cp -r "$HARNESS_SRC/prompts" "$LOOP_DIR/" cp -r "$HARNESS_SRC/templates" "$LOOP_DIR/" cp -r "$HARNESS_SRC/lib" "$LOOP_DIR/" cp "$HARNESS_SRC/loop.sh" "$LOOP_DIR/" chmod +x "$LOOP_DIR/loop.sh" # Copy project config and init from main's .loop/ [ -f "$MAIN_LOOP_DIR/config.json" ] && cp "$MAIN_LOOP_DIR/config.json" "$LOOP_DIR/" [ -f "$MAIN_LOOP_DIR/init.sh" ] && cp "$MAIN_LOOP_DIR/init.sh" "$LOOP_DIR/" # Stamp harness version [ -n "$PLUGIN_VERSION" ] && echo "$PLUGIN_VERSION" > "$LOOP_DIR/.harness-version" # Create .gitignore for worktree's .loop/ cat > "$LOOP_DIR/.gitignore" << 'GITIGNORE' * GITIGNORE echo "[setup] Worktree .loop/ initialized at $LOOP_DIR" exit 0 fi # --- Ensure git repo exists --- if ! git rev-parse --git-dir &>/dev/null; then echo "[setup] No git repo found. Initializing..." git init git checkout -b main 2>/dev/null || true fi # --- Scaffold .loop/ --- PROJECT_ROOT="$(pwd)" LOOP_DIR="$PROJECT_ROOT/.loop" if [ -d "$LOOP_DIR" ] && [ -f "$LOOP_DIR/prd.json" ]; then echo "[setup] .loop/ already exists with prd.json — archiving previous run..." # Source state.sh (needed by archive.sh for story queries) and archive.sh LOOP_DIR="$LOOP_DIR" source "$LOOP_DIR/lib/state.sh" 2>/dev/null || true LOOP_DIR="$LOOP_DIR" source "$LOOP_DIR/lib/archive.sh" 2>/dev/null || true if type archive_and_reset &>/dev/null; then archive_and_reset "$LOOP_DIR" else # Fallback for old harness versions without archive_and_reset echo "[setup] WARNING: Could not archive (old harness version). To re-initialize, delete .loop/ first: rm -rf .loop" exit 1 fi fi mkdir -p "$LOOP_DIR" # Copy harness files cp -r "$HARNESS_SRC/prompts" "$LOOP_DIR/" cp -r "$HARNESS_SRC/templates" "$LOOP_DIR/" cp -r "$HARNESS_SRC/lib" "$LOOP_DIR/" cp "$HARNESS_SRC/loop.sh" "$LOOP_DIR/" chmod +x "$LOOP_DIR/loop.sh" # Stamp harness version [ -n "$PLUGIN_VERSION" ] && echo "$PLUGIN_VERSION" > "$LOOP_DIR/.harness-version" # Verify critical files for f in prompts/generator/_base.md prompts/evaluator/_base.md templates/progress.md.template lib/state.sh loop.sh; do if [ ! -f "$LOOP_DIR/$f" ]; then echo "[setup] ERROR: Missing $LOOP_DIR/$f — harness copy failed" exit 1 fi done # --- Create .gitignore --- cat > "$LOOP_DIR/.gitignore" << 'GITIGNORE' prd.json progress.md progress-archive.md config.json init.sh contracts/ triage/ archive/ .archive-staging/ .last-branch .harness-version .active-worktree .loop.lock GITIGNORE # --- Generate config.json --- cat > "$LOOP_DIR/config.json" << EOF { "tool": "claude", "mode": "$MODE", "maxIterations": 20, "skipEval": false, "evalRetries": 3, "autoHooks": true, "branchPrefix": "loop/", "scopeBudgets": { "explore": { "maxFilesToRead": 15, "maxLinesToWrite": 0, "maxFilesToModify": 0 }, "implement": { "maxFilesToRead": 50, "maxLinesToWrite": 500, "maxFilesToModify": 10 }, "fix": { "maxFilesToRead": 30, "maxLinesToWrite": 200, "maxFilesToModify": 5 } } } EOF # --- Generate init.sh stub --- cat > "$LOOP_DIR/init.sh" << 'INITSH' #!/bin/bash set -euo pipefail echo "[init] Environment ready." INITSH chmod +x "$LOOP_DIR/init.sh" # --- Done --- echo "" echo "[setup] .loop/ scaffolded successfully." echo "[setup] Mode: $MODE" echo "[setup] Config: .loop/config.json" echo "" echo "Next steps (in Claude Code):" echo " /agent-loop:stories # Generate stories from your spec or description" echo ""