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.
This commit is contained in:
197
lib/archeflow-review.sh
Executable file
197
lib/archeflow-review.sh
Executable file
@@ -0,0 +1,197 @@
|
||||
#!/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 "$@"
|
||||
@@ -37,23 +37,28 @@ af-review --evidence # Enable evidence-gating (stricter)
|
||||
|
||||
### Step 1: Get the Diff
|
||||
|
||||
```bash
|
||||
# Uncommitted changes
|
||||
DIFF=$(git diff HEAD)
|
||||
Use `lib/archeflow-review.sh` to extract the diff and stats:
|
||||
|
||||
# Branch diff
|
||||
DIFF=$(git diff main...HEAD)
|
||||
```bash
|
||||
# Uncommitted changes (default)
|
||||
DIFF=$(bash lib/archeflow-review.sh)
|
||||
|
||||
# Branch diff against main
|
||||
DIFF=$(bash lib/archeflow-review.sh --branch feat/batch-api)
|
||||
|
||||
# Commit range
|
||||
DIFF=$(git diff HEAD~3..HEAD)
|
||||
DIFF=$(bash lib/archeflow-review.sh --commit HEAD~3..HEAD)
|
||||
|
||||
# If diff is too large (>500 lines), split by file
|
||||
if [[ $(echo "$DIFF" | wc -l) -gt 500 ]]; then
|
||||
# Review per-file to keep context focused
|
||||
FILES=$(git diff --name-only HEAD)
|
||||
fi
|
||||
# Override base branch
|
||||
DIFF=$(bash lib/archeflow-review.sh --branch feat/x --base develop)
|
||||
|
||||
# Stats only (no diff output)
|
||||
bash lib/archeflow-review.sh --stat-only
|
||||
```
|
||||
|
||||
The script prints the diff to stdout and stats to stderr. It exits 1 if the diff
|
||||
is empty (nothing to review). For large diffs (>500 lines), it warns on stderr.
|
||||
|
||||
### Step 2: Spawn Reviewers
|
||||
|
||||
Default: Guardian only (fastest, highest ROI).
|
||||
|
||||
Reference in New Issue
Block a user