Files
claude-archeflow-plugin/lib/archeflow-rollback.sh
Christian Nennemann 362fb9ada9 fix: address v0.5.0 review findings
- Add --to/--test-cmd mutual exclusivity guard in rollback script
- Convert all jq string interpolation to --arg (cmd_extract, cmd_inject, cmd_forget)
- Fix CRITICAL/WARNING grep to match table rows only (not prose)
- Add thorough+cycle-1 guard to fast-path bash snippet in check-phase
- Clarify prev_run_id selection comment (tail -1 = most recent non-current)
2026-04-04 08:44:16 +02:00

109 lines
3.4 KiB
Bash
Executable File

#!/usr/bin/env bash
# archeflow-rollback.sh — Auto-revert a merge that fails post-merge tests,
# or roll back to a specific PDCA phase boundary.
#
# Usage:
# archeflow-rollback.sh <run_id> [--test-cmd <cmd>] # Post-merge test + revert
# archeflow-rollback.sh <run_id> --to <phase> # Roll back to phase boundary
#
# --to <phase>: Roll back to the given phase boundary (plan, do, or check).
# Delegates to archeflow-git.sh rollback and emits a decision event.
#
# If --test-cmd not provided (and --to not used), reads test_command from .archeflow/config.yaml.
# Returns 0 if tests pass (or rollback succeeds), 1 if tests fail (merge reverted).
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
RUN_ID="${1:?Usage: archeflow-rollback.sh <run_id> [--test-cmd <cmd>] [--to <phase>]}"
shift
# Parse options
TEST_CMD=""
TARGET_PHASE=""
while [[ $# -gt 0 ]]; do
case "$1" in
--test-cmd) TEST_CMD="$2"; shift 2 ;;
--to) TARGET_PHASE="$2"; shift 2 ;;
*) echo "Unknown option: $1" >&2; exit 2 ;;
esac
done
# Mutual exclusivity check
if [[ -n "$TARGET_PHASE" && -n "$TEST_CMD" ]]; then
echo "ERROR: --to and --test-cmd are mutually exclusive." >&2
exit 2
fi
# --- Phase rollback mode ---
if [[ -n "$TARGET_PHASE" ]]; then
# Validate phase name
case "$TARGET_PHASE" in
plan|do|check) ;;
*)
echo "ERROR: Invalid phase '$TARGET_PHASE'. Must be one of: plan, do, check" >&2
exit 2
;;
esac
echo "Rolling back run $RUN_ID to phase boundary: $TARGET_PHASE"
# Delegate to archeflow-git.sh
if [[ ! -x "$SCRIPT_DIR/archeflow-git.sh" ]]; then
echo "ERROR: archeflow-git.sh not found or not executable" >&2
exit 1
fi
"$SCRIPT_DIR/archeflow-git.sh" rollback "$RUN_ID" --to "$TARGET_PHASE"
# Emit decision event
if [[ -x "$SCRIPT_DIR/archeflow-event.sh" ]]; then
"$SCRIPT_DIR/archeflow-event.sh" "$RUN_ID" decision act "" \
"{\"what\":\"phase_rollback\",\"chosen\":\"rollback_to_${TARGET_PHASE}\",\"rationale\":\"user requested rollback to ${TARGET_PHASE} phase boundary\"}" ""
fi
echo "Rollback to $TARGET_PHASE complete for run $RUN_ID."
exit 0
fi
# --- Post-merge test mode ---
# 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 --mainline 1 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