diff --git a/lib/archeflow-rollback.sh b/lib/archeflow-rollback.sh new file mode 100755 index 0000000..6d3aace --- /dev/null +++ b/lib/archeflow-rollback.sh @@ -0,0 +1,61 @@ +#!/usr/bin/env bash +# archeflow-rollback.sh — Auto-revert a merge that fails post-merge tests. +# +# Usage: archeflow-rollback.sh [--test-cmd ] +# +# If --test-cmd not provided, reads test_command from .archeflow/config.yaml. +# Returns 0 if tests pass, 1 if tests fail (merge reverted). + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +RUN_ID="${1:?Usage: archeflow-rollback.sh [--test-cmd ]}" +shift + +# Parse optional --test-cmd +TEST_CMD="" +while [[ $# -gt 0 ]]; do + case "$1" in + --test-cmd) TEST_CMD="$2"; shift 2 ;; + *) echo "Unknown option: $1" >&2; exit 2 ;; + esac +done + +# Read test_command from config if not provided +if [[ -z "$TEST_CMD" ]]; then + if [[ -f ".archeflow/config.yaml" ]]; then + TEST_CMD=$(grep -E "^test_command:" .archeflow/config.yaml | sed 's/^test_command:\s*//' | tr -d '"' || true) + fi +fi + +if [[ -z "$TEST_CMD" ]]; then + echo "ERROR: No test command specified (use --test-cmd or set test_command in .archeflow/config.yaml)" >&2 + exit 2 +fi + +# Verify HEAD is an ArcheFlow merge +HEAD_MSG=$(git log -1 --format=%s HEAD) +if [[ "$HEAD_MSG" != *"$RUN_ID"* ]] && [[ "$HEAD_MSG" != *"archeflow"* ]]; then + echo "WARNING: HEAD commit does not appear to be an ArcheFlow merge: $HEAD_MSG" >&2 + echo "Proceeding anyway..." >&2 +fi + +echo "Running post-merge tests: $TEST_CMD" + +if timeout 300 bash -c "$TEST_CMD"; then + echo "Tests passed — merge is good." + exit 0 +fi + +echo "Tests FAILED — reverting merge..." +git revert --no-edit HEAD + +# Emit event if event script exists +if [[ -x "$SCRIPT_DIR/archeflow-event.sh" ]]; then + "$SCRIPT_DIR/archeflow-event.sh" "$RUN_ID" decision act "" \ + "{\"what\":\"post_merge_test\",\"chosen\":\"revert\",\"rationale\":\"test suite failed after merge\"}" "" +fi + +REVERT_HASH=$(git rev-parse --short HEAD) +echo "Merge reverted (commit: $REVERT_HASH). Tests must pass before re-merging." +exit 1 diff --git a/skills/git-integration/SKILL.md b/skills/git-integration/SKILL.md index cd415e1..d0499a1 100644 --- a/skills/git-integration/SKILL.md +++ b/skills/git-integration/SKILL.md @@ -219,6 +219,32 @@ The helper script reads this config if it exists. All values have sensible defau --- +## Post-Merge Rollback + +After merging, the run skill validates the merge by running the project's test suite. If tests fail, the merge is automatically reverted. + +### Script + +```bash +./lib/archeflow-rollback.sh [--test-cmd ] +``` + +**Behavior:** +1. Reads `test_command` from `.archeflow/config.yaml` (or uses `--test-cmd` override) +2. Runs the test suite with a 5-minute timeout +3. If tests pass: exits 0 (merge is good) +4. If tests fail: runs `git revert --no-edit HEAD`, emits a `decision` event, exits 1 +5. Verifies HEAD is an ArcheFlow merge commit before reverting (warning if not, proceeds anyway) + +**Integration with run skill:** Called in section 4c (All Approved) after `archeflow-git.sh merge`. If it returns non-zero, the orchestrator cycles back with "integration test failure" feedback or reports to the user if max cycles are reached. + +**Configuration:** Set `test_command` in `.archeflow/config.yaml`: +```yaml +test_command: "npm test" # or "pytest", "cargo test", etc. +``` + +--- + ## Safety Rules These rules are inherited from `archeflow:orchestration` and reinforced here: diff --git a/skills/run/SKILL.md b/skills/run/SKILL.md index 44fdce0..9eb6b2c 100644 --- a/skills/run/SKILL.md +++ b/skills/run/SKILL.md @@ -380,30 +380,19 @@ If all reviewers approved (and completion criteria met, if defined): ./lib/archeflow-git.sh merge "$RUN_ID" --no-ff ``` -4. **Post-merge test validation:** +4. **Post-merge test validation** (using the auto-rollback script): ```bash - # Read test_command from config - TEST_CMD=$(grep -E "^test_command:" .archeflow/config.yaml | sed 's/^test_command:\s*//' | tr -d '"' || true) - - if [[ -n "$TEST_CMD" ]]; then - echo "Running post-merge tests: $TEST_CMD" - if ! bash -c "$TEST_CMD"; then - echo "Post-merge tests FAILED — reverting merge..." - git revert --no-edit HEAD - - # Emit decision event for the revert - ./lib/archeflow-event.sh "$RUN_ID" decision act "" \ - '{"what":"post_merge_test","chosen":"revert","rationale":"test suite failed after merge"}' "$SEQ_CHECK_TO_ACT" - - # If cycles remain, cycle back with integration test failure feedback - if [[ "$CYCLE" -lt "$MAX_CYCLES" ]]; then - echo "Cycling back with integration test failure feedback..." - # Build act-feedback.md with "integration test failure on main" as top finding - # Continue to step 4d (Issues Found) - else - echo "Max cycles reached. Reporting failure to user." - # Continue to step 4e (Max Cycles Reached) - fi + # Run tests and auto-revert if they fail + if ! ./lib/archeflow-rollback.sh "$RUN_ID"; then + # Rollback script already reverted HEAD and emitted decision event + # If cycles remain, cycle back with integration test failure feedback + if [[ "$CYCLE" -lt "$MAX_CYCLES" ]]; then + echo "Cycling back with integration test failure feedback..." + # Build act-feedback.md with "integration test failure on main" as top finding + # Continue to step 4d (Issues Found) + else + echo "Max cycles reached. Reporting failure to user." + # Continue to step 4e (Max Cycles Reached) fi fi ```