Initial commit: Auto Review Check toolkit

Scripts for automating PR review loops:
- poll_pr_comments.sh: Poll for new comments without wasting tokens
- wait_for_checks.sh: Wait for CI checks to complete
- fetch_pr_comments.sh: Fetch and format PR comments

Includes SKILL.md for use as a Claude Code skill.
This commit is contained in:
Harivansh Rathi 2026-01-29 16:13:28 -05:00
commit e899b6b09e
6 changed files with 602 additions and 0 deletions

86
scripts/fetch_pr_comments.sh Executable file
View file

@ -0,0 +1,86 @@
#!/usr/bin/env bash
#
# Fetch all PR comments (both inline review comments and PR-level comments)
# Outputs structured JSON for Claude to process
#
# Usage: fetch_pr_comments.sh <owner/repo> <pr-number> [--unresolved-only]
#
# Example:
# fetch_pr_comments.sh myorg/myrepo 123
# fetch_pr_comments.sh myorg/myrepo 123 --unresolved-only
#
set -euo pipefail
REPO="${1:-}"
PR_NUMBER="${2:-}"
UNRESOLVED_ONLY=false
usage() {
echo "Usage: fetch_pr_comments.sh <owner/repo> <pr-number> [--unresolved-only]"
echo ""
echo "Options:"
echo " --unresolved-only Only show unresolved review threads"
echo ""
echo "Example:"
echo " fetch_pr_comments.sh myorg/myrepo 123"
exit 1
}
if [[ -z "$REPO" || -z "$PR_NUMBER" ]]; then
usage
fi
shift 2
while [[ $# -gt 0 ]]; do
case "$1" in
--unresolved-only)
UNRESOLVED_ONLY=true
shift
;;
*)
usage
;;
esac
done
echo "# PR #${PR_NUMBER} Comments"
echo ""
# Fetch inline review comments
echo "## Inline Review Comments"
echo ""
REVIEW_COMMENTS=$(gh api "repos/${REPO}/pulls/${PR_NUMBER}/comments" \
--jq '.[] | "### \(.path):\(.line // .original_line // "N/A")\n**Author:** \(.user.login)\n**Created:** \(.created_at)\n\n\(.body)\n\n---\n"' 2>/dev/null || echo "No inline comments")
if [[ -n "$REVIEW_COMMENTS" && "$REVIEW_COMMENTS" != "No inline comments" ]]; then
echo "$REVIEW_COMMENTS"
else
echo "No inline review comments."
echo ""
fi
# Fetch PR-level comments (issue comments)
echo "## PR-Level Comments"
echo ""
ISSUE_COMMENTS=$(gh api "repos/${REPO}/issues/${PR_NUMBER}/comments" \
--jq '.[] | "### Comment by \(.user.login)\n**Created:** \(.created_at)\n\n\(.body)\n\n---\n"' 2>/dev/null || echo "No PR comments")
if [[ -n "$ISSUE_COMMENTS" && "$ISSUE_COMMENTS" != "No PR comments" ]]; then
echo "$ISSUE_COMMENTS"
else
echo "No PR-level comments."
echo ""
fi
# Fetch review threads with resolution status
echo "## Review Threads Summary"
echo ""
gh pr view "$PR_NUMBER" -R "$REPO" --json reviews,reviewDecision \
--jq '"Review Decision: \(.reviewDecision // "PENDING")\n\nReviews:\n" + (.reviews | map("- \(.author.login): \(.state)") | join("\n"))' 2>/dev/null || echo "Could not fetch review summary"
echo ""

109
scripts/poll_pr_comments.sh Executable file
View file

@ -0,0 +1,109 @@
#!/usr/bin/env bash
#
# Poll for new PR comments using GitHub CLI
# Returns when new comments are found or timeout is reached
#
# Usage: poll_pr_comments.sh <owner/repo> <pr-number> [--timeout <minutes>] [--interval <seconds>]
#
# Example:
# poll_pr_comments.sh myorg/myrepo 123 --timeout 30 --interval 60
#
set -euo pipefail
REPO="${1:-}"
PR_NUMBER="${2:-}"
TIMEOUT_MINUTES=60
POLL_INTERVAL=30
INITIAL_COMMENT_COUNT=""
usage() {
echo "Usage: poll_pr_comments.sh <owner/repo> <pr-number> [--timeout <minutes>] [--interval <seconds>]"
echo ""
echo "Options:"
echo " --timeout <minutes> Maximum time to wait (default: 60)"
echo " --interval <seconds> Time between polls (default: 30)"
echo ""
echo "Example:"
echo " poll_pr_comments.sh myorg/myrepo 123 --timeout 30 --interval 60"
exit 1
}
if [[ -z "$REPO" || -z "$PR_NUMBER" ]]; then
usage
fi
shift 2
while [[ $# -gt 0 ]]; do
case "$1" in
--timeout)
TIMEOUT_MINUTES="$2"
shift 2
;;
--interval)
POLL_INTERVAL="$2"
shift 2
;;
*)
usage
;;
esac
done
get_comment_count() {
local review_comments issue_comments total
# Get review comments (inline code comments)
review_comments=$(gh api "repos/${REPO}/pulls/${PR_NUMBER}/comments" --jq 'length' 2>/dev/null || echo "0")
# Get issue comments (PR-level comments)
issue_comments=$(gh api "repos/${REPO}/issues/${PR_NUMBER}/comments" --jq 'length' 2>/dev/null || echo "0")
total=$((review_comments + issue_comments))
echo "$total"
}
get_unresolved_threads() {
# Get review threads that are not resolved
gh pr view "$PR_NUMBER" -R "$REPO" --json reviewDecision,reviews,latestReviews 2>/dev/null || echo "{}"
}
echo "Polling for comments on PR #${PR_NUMBER} in ${REPO}"
echo "Timeout: ${TIMEOUT_MINUTES} minutes, Poll interval: ${POLL_INTERVAL} seconds"
echo ""
# Get initial comment count
INITIAL_COMMENT_COUNT=$(get_comment_count)
echo "Initial comment count: ${INITIAL_COMMENT_COUNT}"
START_TIME=$(date +%s)
TIMEOUT_SECONDS=$((TIMEOUT_MINUTES * 60))
while true; do
CURRENT_TIME=$(date +%s)
ELAPSED=$((CURRENT_TIME - START_TIME))
if [[ $ELAPSED -ge $TIMEOUT_SECONDS ]]; then
echo ""
echo "Timeout reached after ${TIMEOUT_MINUTES} minutes"
echo "No new comments detected"
exit 0
fi
CURRENT_COUNT=$(get_comment_count)
if [[ "$CURRENT_COUNT" -gt "$INITIAL_COMMENT_COUNT" ]]; then
NEW_COMMENTS=$((CURRENT_COUNT - INITIAL_COMMENT_COUNT))
echo ""
echo "NEW_COMMENTS_DETECTED: ${NEW_COMMENTS} new comment(s) found!"
echo "Total comments: ${CURRENT_COUNT}"
exit 0
fi
REMAINING=$((TIMEOUT_SECONDS - ELAPSED))
REMAINING_MINS=$((REMAINING / 60))
echo -ne "\rWaiting... (${REMAINING_MINS}m remaining, current count: ${CURRENT_COUNT}) "
sleep "$POLL_INTERVAL"
done

99
scripts/wait_for_checks.sh Executable file
View file

@ -0,0 +1,99 @@
#!/usr/bin/env bash
#
# Wait for GitHub checks to complete on a PR
# Returns check status when all checks complete or timeout is reached
#
# Usage: wait_for_checks.sh <owner/repo> <pr-number> [--timeout <minutes>]
#
# Example:
# wait_for_checks.sh myorg/myrepo 123 --timeout 15
#
set -euo pipefail
REPO="${1:-}"
PR_NUMBER="${2:-}"
TIMEOUT_MINUTES=30
POLL_INTERVAL=15
usage() {
echo "Usage: wait_for_checks.sh <owner/repo> <pr-number> [--timeout <minutes>]"
echo ""
echo "Options:"
echo " --timeout <minutes> Maximum time to wait (default: 30)"
echo ""
echo "Example:"
echo " wait_for_checks.sh myorg/myrepo 123 --timeout 15"
exit 1
}
if [[ -z "$REPO" || -z "$PR_NUMBER" ]]; then
usage
fi
shift 2
while [[ $# -gt 0 ]]; do
case "$1" in
--timeout)
TIMEOUT_MINUTES="$2"
shift 2
;;
*)
usage
;;
esac
done
echo "Waiting for checks on PR #${PR_NUMBER} in ${REPO}"
echo "Timeout: ${TIMEOUT_MINUTES} minutes"
echo ""
START_TIME=$(date +%s)
TIMEOUT_SECONDS=$((TIMEOUT_MINUTES * 60))
while true; do
CURRENT_TIME=$(date +%s)
ELAPSED=$((CURRENT_TIME - START_TIME))
if [[ $ELAPSED -ge $TIMEOUT_SECONDS ]]; then
echo ""
echo "TIMEOUT: Checks did not complete within ${TIMEOUT_MINUTES} minutes"
exit 1
fi
# Get check status
CHECK_STATUS=$(gh pr checks "$PR_NUMBER" -R "$REPO" 2>/dev/null || echo "error")
# Check if all checks passed
if echo "$CHECK_STATUS" | grep -q "All checks were successful"; then
echo ""
echo "CHECKS_PASSED: All checks successful"
exit 0
fi
# Check if any checks failed
if echo "$CHECK_STATUS" | grep -qE "fail|error"; then
echo ""
echo "CHECKS_FAILED: Some checks failed"
echo "$CHECK_STATUS"
exit 1
fi
# Check if checks are still pending
PENDING=$(echo "$CHECK_STATUS" | grep -c "pending\|queued\|in_progress" || true)
if [[ "$PENDING" -eq 0 ]]; then
# No pending checks and no failures means success
echo ""
echo "CHECKS_COMPLETE"
echo "$CHECK_STATUS"
exit 0
fi
REMAINING=$((TIMEOUT_SECONDS - ELAPSED))
REMAINING_MINS=$((REMAINING / 60))
echo -ne "\rWaiting for checks... (${REMAINING_MINS}m remaining, ${PENDING} pending) "
sleep "$POLL_INTERVAL"
done