Files
claude-archeflow-plugin/lib/archeflow-review.sh
Christian Nennemann 1bf1376a80 feat: implement archeflow-review.sh for Guardian-only diff review
Standalone bash script that extracts git diffs for af-review without
PDCA orchestration. Supports --branch, --commit, and uncommitted modes.
Reports stats (files/lines changed) to stderr, diff to stdout.
2026-04-04 18:39:06 +02:00

198 lines
5.9 KiB
Bash
Executable File

#!/usr/bin/env bash
# archeflow-review.sh — Get a git diff for Guardian review, with stats.
#
# Standalone diff helper for af-review. No PDCA orchestration — just extracts
# the right diff and reports stats so the Claude Code agent can feed it to
# Guardian (or other reviewers).
#
# Usage:
# archeflow-review.sh # Uncommitted changes (staged + unstaged)
# archeflow-review.sh --branch feat/batch-api # Branch diff vs main
# archeflow-review.sh --commit HEAD~3..HEAD # Commit range
# archeflow-review.sh --base develop # Override base branch (default: main)
# archeflow-review.sh --stat-only # Only print stats, no diff output
#
# Output:
# Prints the diff to stdout. Stats go to stderr so they don't pollute the diff.
# Exit code 0 if diff is non-empty, 1 if empty (nothing to review).
set -euo pipefail
# ---------------------------------------------------------------------------
# Globals
# ---------------------------------------------------------------------------
BASE_BRANCH="main"
MODE="uncommitted" # uncommitted | branch | commit
TARGET=""
STAT_ONLY="false"
# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
die() {
echo "[af-review] ERROR: $*" >&2
exit 1
}
info() {
echo "[af-review] $*" >&2
}
# Print diff stats (files changed, insertions, deletions) to stderr.
print_stats() {
local diff_text="$1"
local files_changed lines_added lines_removed total_lines
files_changed=$(echo "$diff_text" | grep -c '^diff --git' || true)
lines_added=$(echo "$diff_text" | grep -c '^+[^+]' || true)
lines_removed=$(echo "$diff_text" | grep -c '^-[^-]' || true)
total_lines=$(echo "$diff_text" | wc -l | tr -d ' ')
info "--- Review Stats ---"
info "Files changed: ${files_changed}"
info "Lines added: +${lines_added}"
info "Lines removed: -${lines_removed}"
info "Diff size: ${total_lines} lines"
if [[ "$total_lines" -gt 500 ]]; then
info "Warning: large diff (>500 lines). Consider reviewing per-file."
fi
}
# Detect the default base branch (main or master).
detect_base_branch() {
if git show-ref --verify --quiet "refs/heads/main" 2>/dev/null; then
echo "main"
elif git show-ref --verify --quiet "refs/heads/master" 2>/dev/null; then
echo "master"
else
echo "main"
fi
}
# ---------------------------------------------------------------------------
# Argument parsing
# ---------------------------------------------------------------------------
parse_args() {
while [[ $# -gt 0 ]]; do
case "$1" in
--branch)
MODE="branch"
TARGET="${2:?Missing branch name after --branch}"
shift 2
;;
--commit)
MODE="commit"
TARGET="${2:?Missing commit range after --commit}"
shift 2
;;
--base)
BASE_BRANCH="${2:?Missing base branch after --base}"
shift 2
;;
--stat-only)
STAT_ONLY="true"
shift
;;
-h|--help)
echo "Usage: $0 [--branch <name>] [--commit <range>] [--base <branch>] [--stat-only]"
echo ""
echo " (no args) Review uncommitted changes (staged + unstaged)"
echo " --branch <name> Review branch diff against base (default: main)"
echo " --commit <range> Review a commit range (e.g. HEAD~3..HEAD)"
echo " --base <branch> Override base branch (default: auto-detect main/master)"
echo " --stat-only Print stats only, no diff output"
exit 0
;;
*)
die "Unknown argument: $1. Use --help for usage."
;;
esac
done
}
# ---------------------------------------------------------------------------
# Diff extraction
# ---------------------------------------------------------------------------
get_diff() {
local diff_text=""
case "$MODE" in
uncommitted)
# Combine staged and unstaged changes against HEAD
diff_text=$(git diff HEAD 2>/dev/null || true)
if [[ -z "$diff_text" ]]; then
# Maybe everything is staged, try just staged
diff_text=$(git diff --cached 2>/dev/null || true)
fi
;;
branch)
# Verify target branch exists
if ! git show-ref --verify --quiet "refs/heads/${TARGET}" 2>/dev/null; then
# Maybe it's a remote branch
if ! git rev-parse --verify "${TARGET}" &>/dev/null; then
die "Branch '${TARGET}' not found."
fi
fi
diff_text=$(git diff "${BASE_BRANCH}...${TARGET}" 2>/dev/null || true)
;;
commit)
# Validate commit range resolves
if ! git rev-parse "${TARGET}" &>/dev/null 2>&1; then
die "Invalid commit range: '${TARGET}'"
fi
diff_text=$(git diff "${TARGET}" 2>/dev/null || true)
;;
esac
echo "$diff_text"
}
# ---------------------------------------------------------------------------
# Main
# ---------------------------------------------------------------------------
main() {
# Verify we're in a git repo
if ! git rev-parse --is-inside-work-tree &>/dev/null; then
die "Not inside a git repository."
fi
parse_args "$@"
# Auto-detect base branch if not overridden
if [[ "$BASE_BRANCH" == "main" ]]; then
BASE_BRANCH=$(detect_base_branch)
fi
# Describe what we're reviewing
case "$MODE" in
uncommitted) info "Reviewing: uncommitted changes vs HEAD" ;;
branch) info "Reviewing: branch '${TARGET}' vs '${BASE_BRANCH}'" ;;
commit) info "Reviewing: commit range '${TARGET}'" ;;
esac
local diff_text
diff_text=$(get_diff)
# Validate non-empty
if [[ -z "$diff_text" ]]; then
info "No changes found. Nothing to review."
exit 1
fi
# Print stats to stderr
print_stats "$diff_text"
# Output the diff to stdout (unless stat-only)
if [[ "$STAT_ONLY" != "true" ]]; then
echo "$diff_text"
fi
}
main "$@"