diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index bb97044..0540187 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -10,7 +10,7 @@ "name": "agent-loop", "source": "./", "description": "Autonomous generator-evaluator agent loop for long-running coding tasks. Plan interactively, then execute with full visibility.", - "version": "0.8.0", + "version": "0.9.0", "author": { "name": "Sheldon" }, diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index 3bf91e8..b4b1971 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "agent-loop", - "version": "0.8.0", + "version": "0.9.0", "description": "Autonomous generator-evaluator agent loop for long-running coding tasks. Run /agent-loop:run to start.", "author": { "name": "Sheldon" diff --git a/lib/archive.sh b/lib/archive.sh index d67d123..8bca8a3 100644 --- a/lib/archive.sh +++ b/lib/archive.sh @@ -1,11 +1,17 @@ #!/bin/bash -# Branch archiving — archives previous run artifacts when the branch changes. -# Preserves prd.json, progress.md, and contracts from the previous feature. +# Run archiving — preserves prd.json, progress.md, and contracts from completed runs. # -# Design: At the end of each run, snapshot_for_archive saves current artifacts -# to .archive-staging/. On the next run, if the branch changed, check_archive -# moves the snapshot to archive/ and cleans up. This avoids archiving the -# WRONG artifacts (the new feature's) when prd.json has already been overwritten. +# Two archive triggers: +# 1. Branch change: check_archive detects a new branch and archives the staged snapshot. +# 2. Completed run: archive_and_reset is called by the /run skill when prd.json shows +# all stories passed (or the branch was deleted). This handles the common workflow +# of merging a feature branch back to main and starting a new feature. +# +# Archive layout: +# .loop/archive/ +# runs.log — one-line-per-run index for quick lookup +# 2026-03-15-auth-system/ — full artifacts from that run +# prd.json, progress.md, contracts/ LAST_BRANCH_FILE="$LOOP_DIR/.last-branch" STAGING_DIR="$LOOP_DIR/.archive-staging" @@ -85,5 +91,91 @@ archive_run() { rm -f "$LOOP_DIR/progress.md" rm -rf "$LOOP_DIR/contracts" + append_runs_log "$branch_name" "$archive_dir" log "Archived previous run to $archive_dir" } + +# Archive current run artifacts and reset for a new run. +# Called by the /run skill when a completed run is detected (all stories passed +# or the feature branch no longer exists). Unlike check_archive (which reads from +# staging), this archives the LIVE artifacts directly since we know they belong +# to the completed run. +archive_and_reset() { + local loop_dir="${1:-.loop}" + local prd="$loop_dir/prd.json" + + [ -f "$prd" ] || return 0 + + # Read branch name from current prd.json + local branch_name="" + if command -v jq &>/dev/null; then + branch_name=$(jq -r '.branchName // empty' "$prd" 2>/dev/null) + elif command -v python3 &>/dev/null; then + branch_name=$(LOOP_PRD="$prd" python3 -c " +import json, os +print(json.load(open(os.environ['LOOP_PRD'])).get('branchName', ''), end='') +" 2>/dev/null) + fi + + local feature_name + feature_name=$(echo "${branch_name:-unknown}" | sed 's|.*/||') + + local archive_dir="$loop_dir/archive/$(date +%Y-%m-%d)-${feature_name}" + mkdir -p "$archive_dir" + + # Archive live artifacts + [ -f "$prd" ] && cp "$prd" "$archive_dir/" + [ -f "$loop_dir/progress.md" ] && cp "$loop_dir/progress.md" "$archive_dir/" + [ -f "$loop_dir/progress-archive.md" ] && cp "$loop_dir/progress-archive.md" "$archive_dir/" + [ -d "$loop_dir/contracts" ] && cp -r "$loop_dir/contracts" "$archive_dir/" + + # Verify archive has content before deleting originals + if ! find "$archive_dir" -maxdepth 1 -type f | read -r; then + echo "[archive] WARNING: Archive directory is empty — skipping reset to prevent data loss" + return 1 + fi + + append_runs_log "$branch_name" "$archive_dir" + + # Reset run-specific files (keep config.json, init.sh, harness files) + rm -f "$loop_dir/prd.json" + rm -f "$loop_dir/progress.md" + rm -f "$loop_dir/progress-archive.md" + rm -rf "$loop_dir/contracts" + rm -rf "$loop_dir/.archive-staging" + rm -f "$loop_dir/.last-branch" + rm -f "$loop_dir/.verdict" + + echo "[archive] Archived completed run to $archive_dir" + echo "[archive] .loop/ reset — ready for new stories" +} + +# Append a one-line summary to the runs log. +append_runs_log() { + local branch_name="$1" + local archive_dir="$2" + local runs_log + runs_log="$(dirname "$archive_dir")/runs.log" + + # Read story counts from the archived prd.json + local total=0 passed=0 blocked=0 + local archived_prd="$archive_dir/prd.json" + if [ -f "$archived_prd" ]; then + if command -v jq &>/dev/null; then + total=$(jq '.userStories | length' "$archived_prd" 2>/dev/null || echo 0) + passed=$(jq '[.userStories[] | select(.passes == true)] | length' "$archived_prd" 2>/dev/null || echo 0) + blocked=$(jq '[.userStories[] | select(.blocked == true)] | length' "$archived_prd" 2>/dev/null || echo 0) + elif command -v python3 &>/dev/null; then + eval "$(LOOP_PRD="$archived_prd" python3 -c " +import json, os +d = json.load(open(os.environ['LOOP_PRD'])) +s = d.get('userStories', []) +print(f'total={len(s)} passed={sum(1 for x in s if x.get(\"passes\"))} blocked={sum(1 for x in s if x.get(\"blocked\"))}') +" 2>/dev/null)" || true + fi + fi + + printf '%s %-30s %s/%s passed %s blocked\n' \ + "$(date +%Y-%m-%d)" "${branch_name:-unknown}" "$passed" "$total" "$blocked" \ + >> "$runs_log" +} diff --git a/setup.sh b/setup.sh index 887927c..1e05fa9 100755 --- a/setup.sh +++ b/setup.sh @@ -57,9 +57,17 @@ 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." - echo "[setup] To re-initialize, delete .loop/ first: rm -rf .loop" - exit 1 + 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" diff --git a/skills/run/SKILL.md b/skills/run/SKILL.md index de6ab2c..142d0ff 100644 --- a/skills/run/SKILL.md +++ b/skills/run/SKILL.md @@ -44,7 +44,38 @@ Show the output. If setup fails, stop. Check if `.loop/prd.json` exists. -**If it does NOT exist**, generate it: +**If it exists**, check whether the previous run is complete. Run: + +```bash +source .loop/lib/state.sh 2>/dev/null; LOOP_DIR=.loop all_stories_pass 2>/dev/null && echo "ALL_PASSED" || echo "IN_PROGRESS" +``` + +Also check if the feature branch from prd.json still exists: + +```bash +jq -r '.branchName // empty' .loop/prd.json +``` + +If the result is `ALL_PASSED` **or** the branch no longer exists (deleted after merge), the previous run is done. Archive it and reset: + +```bash +LOOP_DIR=.loop source .loop/lib/state.sh && source .loop/lib/archive.sh && archive_and_reset .loop +``` + +Tell the user: *"Archived previous run ({branch}). Starting fresh."* + +Then proceed to generate new stories below. + +If the result is `IN_PROGRESS` and the branch still exists, the previous run is still active. Tell the user: + +> **Existing run found** — {passed}/{total} stories complete on `{branch}`. +> +> - Say **go** to resume the existing run +> - Say **archive** to archive this run and start fresh + +Wait for the user to decide before proceeding. + +**If `prd.json` does NOT exist**, generate it: 1. Search for existing specs or plans: - `docs/superpowers/specs/*.md` diff --git a/skills/stories/SKILL.md b/skills/stories/SKILL.md index 0bb23da..bdb2c24 100644 --- a/skills/stories/SKILL.md +++ b/skills/stories/SKILL.md @@ -13,6 +13,20 @@ Dispatch the planner agent to decompose a spec into stories. The planner agent c Verify `.loop/config.json` exists. If not, tell the user to run `/agent-loop:setup` first and stop. +If `.loop/prd.json` already exists, check whether the previous run is complete: + +```bash +source .loop/lib/state.sh 2>/dev/null; LOOP_DIR=.loop all_stories_pass 2>/dev/null && echo "ALL_PASSED" || echo "IN_PROGRESS" +``` + +If `ALL_PASSED`, archive the completed run before generating new stories: + +```bash +LOOP_DIR=.loop source .loop/lib/state.sh && source .loop/lib/archive.sh && archive_and_reset .loop +``` + +If `IN_PROGRESS`, warn the user that a run is active and ask whether to archive it or abort. + ### 2. Find the spec Check these locations: