2026-02-21 22:59:00 -06:00
#!/bin/bash
2026-03-10 08:28:29 -07:00
notify( ) {
local title = " $1 "
local message = " $2 "
local pr = " $3 "
# Terminal escape sequence
printf "\e]9;%s | PR #%s | %s\a" " $title " " $pr " " $message "
# Native macOS notification
if [ [ " $( uname) " = = "Darwin" ] ] ; then
osascript -e " display notification \" $message \" with title \" $title \" subtitle \"PR # $pr \" "
fi
}
2026-02-21 22:59:00 -06:00
pr_number = $1
if [ [ -z " $pr_number " ] ] ; then
echo "Usage: async-review <pr_number>"
exit 1
fi
2026-02-21 23:07:58 -06:00
base_dir = $( git rev-parse --show-toplevel 2>/dev/null)
if [ [ -z " $base_dir " ] ] ; then
echo "❌ Must be run from within a git repository."
2026-02-21 22:59:00 -06:00
exit 1
fi
2026-02-21 23:07:58 -06:00
# Use the repository's local .gemini/tmp directory for ephemeral worktrees and logs
pr_dir = " $base_dir /.gemini/tmp/async-reviews/pr- $pr_number "
target_dir = " $pr_dir /worktree "
log_dir = " $pr_dir /logs "
2026-02-21 22:59:00 -06:00
cd " $base_dir " || exit 1
2026-03-09 17:13:39 -07:00
mkdir -p " $log_dir "
rm -f " $log_dir /setup.exit " " $log_dir /final-assessment.exit " " $log_dir /final-assessment.md "
echo "🧹 Cleaning up previous worktree if it exists..." | tee -a " $log_dir /setup.log "
git worktree remove -f " $target_dir " >> " $log_dir /setup.log " 2>& 1 || true
git branch -D " gemini-async-pr- $pr_number " >> " $log_dir /setup.log " 2>& 1 || true
git worktree prune >> " $log_dir /setup.log " 2>& 1 || true
echo " 📡 Fetching PR # $pr_number ... " | tee -a " $log_dir /setup.log "
if ! git fetch origin -f " pull/ $pr_number /head:gemini-async-pr- $pr_number " >> " $log_dir /setup.log " 2>& 1; then
echo 1 > " $log_dir /setup.exit "
echo " ❌ Fetch failed. Check $log_dir /setup.log "
2026-03-10 08:28:29 -07:00
notify "Async Review Failed" "Fetch failed." " $pr_number "
2026-03-09 17:13:39 -07:00
exit 1
fi
2026-02-21 22:59:00 -06:00
if [ [ ! -d " $target_dir " ] ] ; then
2026-03-09 17:13:39 -07:00
echo "🧹 Pruning missing worktrees..." | tee -a " $log_dir /setup.log "
git worktree prune >> " $log_dir /setup.log " 2>& 1
echo " 🌿 Creating worktree in $target_dir ... " | tee -a " $log_dir /setup.log "
if ! git worktree add " $target_dir " " gemini-async-pr- $pr_number " >> " $log_dir /setup.log " 2>& 1; then
echo 1 > " $log_dir /setup.exit "
echo " ❌ Worktree creation failed. Check $log_dir /setup.log "
2026-03-10 08:28:29 -07:00
notify "Async Review Failed" "Worktree creation failed." " $pr_number "
2026-03-09 17:13:39 -07:00
exit 1
fi
2026-02-21 22:59:00 -06:00
else
2026-03-09 17:13:39 -07:00
echo "🌿 Worktree already exists." | tee -a " $log_dir /setup.log "
2026-02-21 22:59:00 -06:00
fi
2026-03-09 17:13:39 -07:00
echo 0 > " $log_dir /setup.exit "
2026-02-21 22:59:00 -06:00
cd " $target_dir " || exit 1
echo " 🚀 Launching background tasks. Logs saving to: $log_dir "
2026-03-10 12:12:22 -07:00
echo " ↳ [1/5] Grabbing PR diff..."
rm -f " $log_dir /pr-diff.exit "
{ gh pr diff " $pr_number " > " $log_dir /pr-diff.diff " 2>& 1; echo $? > " $log_dir /pr-diff.exit " ; } &
echo " ↳ [2/5] Starting build and lint..."
2026-03-09 17:13:39 -07:00
rm -f " $log_dir /build-and-lint.exit "
{ { npm run clean && npm ci && npm run format && npm run build && npm run lint:ci && npm run typecheck; } > " $log_dir /build-and-lint.log " 2>& 1; echo $? > " $log_dir /build-and-lint.exit " ; } &
2026-02-21 22:59:00 -06:00
2026-02-21 23:07:58 -06:00
# Dynamically resolve gemini binary (fallback to your nightly path)
GEMINI_CMD = $( which gemini || echo " $HOME /.gcli/nightly/node_modules/.bin/gemini " )
2026-02-21 22:59:00 -06:00
2026-03-10 12:12:22 -07:00
echo " ↳ [3/5] Starting Gemini code review..."
2026-02-21 22:59:00 -06:00
rm -f " $log_dir /review.exit "
{ " $GEMINI_CMD " --approval-mode= yolo /review-frontend " $pr_number " > " $log_dir /review.md " 2>& 1; echo $? > " $log_dir /review.exit " ; } &
2026-03-10 12:12:22 -07:00
echo " ↳ [4/5] Starting automated tests (waiting for build and lint)..."
2026-03-09 17:13:39 -07:00
rm -f " $log_dir /npm-test.exit "
{
while [ ! -f " $log_dir /build-and-lint.exit " ] ; do sleep 1; done
if [ " $( cat " $log_dir /build-and-lint.exit " ) " = = "0" ] ; then
2026-03-10 12:12:22 -07:00
if gh pr checks " $pr_number " > " $log_dir /ci-checks.log " 2>& 1; then
echo "CI checks passed. Skipping local npm tests." > " $log_dir /npm-test.log "
echo 0 > " $log_dir /npm-test.exit "
else
echo "CI checks did not pass. Failing checks:" > " $log_dir /npm-test.log "
gh pr checks " $pr_number " --json name,bucket -q '.[] | select(.bucket=="fail") | .name' >> " $log_dir /npm-test.log " 2>& 1
echo "Attempting to extract failing test files from CI logs..." >> " $log_dir /npm-test.log "
pr_branch = $( gh pr view " $pr_number " --json headRefName -q '.headRefName' 2>/dev/null)
run_id = $( gh run list --branch " $pr_branch " --workflow ci.yml --json databaseId -q '.[0].databaseId' 2>/dev/null)
failed_files = ""
if [ [ -n " $run_id " ] ] ; then
failed_files = $( gh run view " $run_id " --log-failed 2>/dev/null | grep -o -E '(packages/[a-zA-Z0-9_-]+|integration-tests|evals)/[a-zA-Z0-9_/-]+\.test\.ts(x)?' | sort | uniq)
fi
if [ [ -n " $failed_files " ] ] ; then
echo "Found failing test files from CI:" >> " $log_dir /npm-test.log "
for f in $failed_files ; do echo " - $f " >> " $log_dir /npm-test.log " ; done
echo "Running ONLY failing tests locally..." >> " $log_dir /npm-test.log "
exit_code = 0
for file in $failed_files ; do
if [ [ " $file " = = packages/* ] ] ; then
ws_dir = $( echo " $file " | cut -d'/' -f1,2)
else
ws_dir = $( echo " $file " | cut -d'/' -f1)
fi
rel_file = ${ file # $ws_dir / }
echo " --- Running $rel_file in workspace $ws_dir --- " >> " $log_dir /npm-test.log "
if ! npm run test:ci -w " $ws_dir " -- " $rel_file " >> " $log_dir /npm-test.log " 2>& 1; then
exit_code = 1
fi
done
echo $exit_code > " $log_dir /npm-test.exit "
else
echo "Could not extract specific failing files. Running full local test suite..." >> " $log_dir /npm-test.log "
npm run test:ci >> " $log_dir /npm-test.log " 2>& 1; echo $? > " $log_dir /npm-test.exit "
fi
fi
2026-03-09 17:13:39 -07:00
else
echo "Skipped due to build-and-lint failure" > " $log_dir /npm-test.log "
echo 1 > " $log_dir /npm-test.exit "
fi
} &
2026-03-10 12:12:22 -07:00
echo " ↳ [5/5] Starting Gemini test execution (waiting for build and lint)..."
2026-02-21 22:59:00 -06:00
rm -f " $log_dir /test-execution.exit "
2026-03-09 17:13:39 -07:00
{
while [ ! -f " $log_dir /build-and-lint.exit " ] ; do sleep 1; done
if [ " $( cat " $log_dir /build-and-lint.exit " ) " = = "0" ] ; then
" $GEMINI_CMD " --approval-mode= yolo " Analyze the diff for PR $pr_number using 'gh pr diff $pr_number '. Instead of running the project's automated test suite (like 'npm test'), physically exercise the newly changed code in the terminal (e.g., by writing a temporary script to call the new functions, or testing the CLI command directly). Verify the feature's behavior works as expected. IMPORTANT: Do NOT modify any source code to fix errors. Just exercise the code and log the results, reporting any failures clearly. Do not ask for user confirmation. " > " $log_dir /test-execution.log " 2>& 1; echo $? > " $log_dir /test-execution.exit "
else
echo "Skipped due to build-and-lint failure" > " $log_dir /test-execution.log "
echo 1 > " $log_dir /test-execution.exit "
fi
} &
2026-02-21 22:59:00 -06:00
echo "✅ All tasks dispatched!"
echo " You can monitor progress with: tail -f $log_dir /*.log "
echo " Read your review later at: $log_dir /review.md "
2026-03-09 17:13:39 -07:00
# Polling loop to wait for all background tasks to finish
2026-03-10 12:12:22 -07:00
tasks = ( "pr-diff" "build-and-lint" "review" "npm-test" "test-execution" )
log_files = ( "pr-diff.diff" "build-and-lint.log" "review.md" "npm-test.log" "test-execution.log" )
2026-03-09 17:13:39 -07:00
declare -A task_done
for t in " ${ tasks [@] } " ; do task_done[ $t ] = 0; done
all_done = 0
while [ [ $all_done -eq 0 ] ] ; do
clear
echo "=================================================="
echo " 🚀 Async PR Review Status for PR # $pr_number "
echo "=================================================="
echo ""
all_done = 1
for i in " ${ !tasks[@] } " ; do
t = " ${ tasks [ $i ] } "
if [ [ -f " $log_dir / $t .exit " ] ] ; then
exit_code = $( cat " $log_dir / $t .exit " )
if [ [ " $exit_code " = = "0" ] ] ; then
echo " ✅ $t : SUCCESS "
else
echo " ❌ $t : FAILED (exit code $exit_code ) "
fi
task_done[ $t ] = 1
else
echo " ⏳ $t : RUNNING "
all_done = 0
fi
done
echo ""
echo "=================================================="
echo "📝 Live Logs (Last 5 lines of running tasks)"
echo "=================================================="
for i in " ${ !tasks[@] } " ; do
t = " ${ tasks [ $i ] } "
log_file = " ${ log_files [ $i ] } "
if [ [ ${ task_done [ $t ] } -eq 0 ] ] ; then
if [ [ -f " $log_dir / $log_file " ] ] ; then
echo ""
echo " --- $t --- "
tail -n 5 " $log_dir / $log_file "
fi
fi
done
if [ [ $all_done -eq 0 ] ] ; then
sleep 3
fi
done
clear
echo "=================================================="
echo " 🚀 Async PR Review Status for PR # $pr_number "
echo "=================================================="
echo ""
for t in " ${ tasks [@] } " ; do
exit_code = $( cat " $log_dir / $t .exit " )
if [ [ " $exit_code " = = "0" ] ] ; then
echo " ✅ $t : SUCCESS "
else
echo " ❌ $t : FAILED (exit code $exit_code ) "
fi
done
echo ""
echo "⏳ Tasks complete! Synthesizing final assessment..."
if ! " $GEMINI_CMD " --approval-mode= yolo -p " Read the review at $log_dir /review.md, the automated test logs at $log_dir /npm-test.log, and the manual test execution logs at $log_dir /test-execution.log. Summarize the results, state whether the build and tests passed based on $log_dir /build-and-lint.exit and $log_dir /npm-test.exit, and give a final recommendation for PR $pr_number . " > " $log_dir /final-assessment.md " 2>& 1; then
echo $? > " $log_dir /final-assessment.exit "
echo "❌ Final assessment synthesis failed!"
echo " Check $log_dir /final-assessment.md for details. "
2026-03-10 08:28:29 -07:00
notify "Async Review Failed" "Final assessment synthesis failed." " $pr_number "
2026-03-09 17:13:39 -07:00
exit 1
fi
echo 0 > " $log_dir /final-assessment.exit "
echo " ✅ Final assessment complete! Check $log_dir /final-assessment.md "
2026-03-10 08:28:29 -07:00
notify "Async Review Complete" "Review and test execution finished successfully." " $pr_number "