Each /agent-loop:run now creates a git worktree for the feature branch before generating stories. This provides full isolation: - Multiple loops can run in parallel on different specs in the same project - Main working directory stays on main, always available - Each worktree has its own .loop/ state, tmux session, and branch - Completed runs are archived to main's .loop/archive/ with runs.log Changes: - setup.sh: add --init-worktree mode for initializing worktree .loop/ - archive.sh: add archive_from_worktree() for cross-directory archiving - loop.sh: replace branch checkout with validation (worktree is pre-checked-out) - agents/planner.md: accept absolute path prefix for worktree .loop/ writes - skills/run/SKILL.md: full rewrite — worktree creation in Phase 2, launch in Phase 3, archive on completion, .active-worktree tracking file - skills/stories/SKILL.md: worktree-aware, defer to /run for full flow Bump to 0.12.0.
225 lines
6.9 KiB
Bash
Executable File
225 lines
6.9 KiB
Bash
Executable File
#!/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> # 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 <worktree_path> <main_loop_dir>"
|
|
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 ""
|