feat: auto-archive completed runs before starting new features
When /agent-loop:run detects a previous run with all stories passed (or the feature branch deleted after merge), it archives the old artifacts and resets .loop/ automatically — no more manual rm -rf .loop. - Add archive_and_reset() for on-demand archiving from skills - Add runs.log index tracking all archived runs - Update /run and /stories skills to detect completed runs - setup.sh archives instead of hard-failing when prd.json exists - Bump version to 0.9.0
This commit is contained in:
@@ -10,7 +10,7 @@
|
|||||||
"name": "agent-loop",
|
"name": "agent-loop",
|
||||||
"source": "./",
|
"source": "./",
|
||||||
"description": "Autonomous generator-evaluator agent loop for long-running coding tasks. Plan interactively, then execute with full visibility.",
|
"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": {
|
"author": {
|
||||||
"name": "Sheldon"
|
"name": "Sheldon"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "agent-loop",
|
"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.",
|
"description": "Autonomous generator-evaluator agent loop for long-running coding tasks. Run /agent-loop:run to start.",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "Sheldon"
|
"name": "Sheldon"
|
||||||
|
|||||||
104
lib/archive.sh
104
lib/archive.sh
@@ -1,11 +1,17 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# Branch archiving — archives previous run artifacts when the branch changes.
|
# Run archiving — preserves prd.json, progress.md, and contracts from completed runs.
|
||||||
# Preserves prd.json, progress.md, and contracts from the previous feature.
|
|
||||||
#
|
#
|
||||||
# Design: At the end of each run, snapshot_for_archive saves current artifacts
|
# Two archive triggers:
|
||||||
# to .archive-staging/. On the next run, if the branch changed, check_archive
|
# 1. Branch change: check_archive detects a new branch and archives the staged snapshot.
|
||||||
# moves the snapshot to archive/ and cleans up. This avoids archiving the
|
# 2. Completed run: archive_and_reset is called by the /run skill when prd.json shows
|
||||||
# WRONG artifacts (the new feature's) when prd.json has already been overwritten.
|
# 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"
|
LAST_BRANCH_FILE="$LOOP_DIR/.last-branch"
|
||||||
STAGING_DIR="$LOOP_DIR/.archive-staging"
|
STAGING_DIR="$LOOP_DIR/.archive-staging"
|
||||||
@@ -85,5 +91,91 @@ archive_run() {
|
|||||||
rm -f "$LOOP_DIR/progress.md"
|
rm -f "$LOOP_DIR/progress.md"
|
||||||
rm -rf "$LOOP_DIR/contracts"
|
rm -rf "$LOOP_DIR/contracts"
|
||||||
|
|
||||||
|
append_runs_log "$branch_name" "$archive_dir"
|
||||||
log "Archived previous run to $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"
|
||||||
|
}
|
||||||
|
|||||||
12
setup.sh
12
setup.sh
@@ -57,9 +57,17 @@ PROJECT_ROOT="$(pwd)"
|
|||||||
LOOP_DIR="$PROJECT_ROOT/.loop"
|
LOOP_DIR="$PROJECT_ROOT/.loop"
|
||||||
|
|
||||||
if [ -d "$LOOP_DIR" ] && [ -f "$LOOP_DIR/prd.json" ]; then
|
if [ -d "$LOOP_DIR" ] && [ -f "$LOOP_DIR/prd.json" ]; then
|
||||||
echo "[setup] .loop/ already exists with prd.json."
|
echo "[setup] .loop/ already exists with prd.json — archiving previous run..."
|
||||||
echo "[setup] To re-initialize, delete .loop/ first: rm -rf .loop"
|
# 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
|
exit 1
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
mkdir -p "$LOOP_DIR"
|
mkdir -p "$LOOP_DIR"
|
||||||
|
|||||||
@@ -44,7 +44,38 @@ Show the output. If setup fails, stop.
|
|||||||
|
|
||||||
Check if `.loop/prd.json` exists.
|
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:
|
1. Search for existing specs or plans:
|
||||||
- `docs/superpowers/specs/*.md`
|
- `docs/superpowers/specs/*.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.
|
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
|
### 2. Find the spec
|
||||||
|
|
||||||
Check these locations:
|
Check these locations:
|
||||||
|
|||||||
Reference in New Issue
Block a user