commit e899b6b09e5e41d29fdda2ad34038aad4989eba2 Author: Harivansh Rathi Date: Thu Jan 29 16:13:28 2026 -0500 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. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..b77bf2a --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..348af59 --- /dev/null +++ b/README.md @@ -0,0 +1,122 @@ +# Auto Review Check + +Automate the PR review loop workflow - implement code, create PRs, wait for reviews, and iterate on feedback without wasting tokens or context. + +## Overview + +This toolkit provides scripts to efficiently manage the PR review cycle: + +- **Poll for comments** without consuming AI context/tokens +- **Wait for CI checks** to complete +- **Fetch and format comments** for review + +## Installation + +```bash +git clone https://github.com/harivansh-afk/auto-review-check.git +cd auto-review-check +chmod +x scripts/*.sh +``` + +## Prerequisites + +- [GitHub CLI](https://cli.github.com/) installed and authenticated (`gh auth login`) +- [Graphite CLI](https://graphite.dev/docs/graphite-cli) (optional, for `gt` commands) + +## Usage + +### Poll for New Comments + +Wait for new comments on a PR without consuming resources: + +```bash +./scripts/poll_pr_comments.sh [--timeout ] [--interval ] +``` + +**Example:** +```bash +./scripts/poll_pr_comments.sh myorg/myrepo 123 --timeout 30 --interval 60 +``` + +The script: +- Polls GitHub API at specified intervals (default: 30 seconds) +- Returns immediately when new comments are detected +- Exits after timeout with status code 0 + +### Wait for CI Checks + +Wait for GitHub Actions or other checks to complete: + +```bash +./scripts/wait_for_checks.sh [--timeout ] +``` + +**Example:** +```bash +./scripts/wait_for_checks.sh myorg/myrepo 123 --timeout 15 +``` + +Exit codes: +- `0` - All checks passed +- `1` - Checks failed or timeout reached + +### Fetch PR Comments + +Retrieve and format all comments for review: + +```bash +./scripts/fetch_pr_comments.sh [--unresolved-only] +``` + +**Example:** +```bash +./scripts/fetch_pr_comments.sh myorg/myrepo 123 +./scripts/fetch_pr_comments.sh myorg/myrepo 123 --unresolved-only +``` + +Output includes: +- Inline review comments with file:line locations +- PR-level comments +- Review decision status + +## Workflow Example + +Full review loop workflow: + +```bash +# 1. Create branch and PR with Graphite +gt create --all --message "feat: add new feature" +gt submit --ai --stack + +# 2. Wait for CI checks +./scripts/wait_for_checks.sh myorg/myrepo 123 --timeout 15 + +# 3. Poll for review comments +./scripts/poll_pr_comments.sh myorg/myrepo 123 --timeout 60 --interval 30 + +# 4. Fetch and review comments +./scripts/fetch_pr_comments.sh myorg/myrepo 123 + +# 5. Address feedback, then push updates +gt modify --all +gt submit +``` + +## Quick Reference + +| Task | Command | +| ------------------- | ------------------------------------------------------ | +| Poll for comments | `./scripts/poll_pr_comments.sh owner/repo 123` | +| Wait for checks | `./scripts/wait_for_checks.sh owner/repo 123` | +| Fetch comments | `./scripts/fetch_pr_comments.sh owner/repo 123` | +| Create branch (gt) | `gt create -am "feat: description"` | +| Submit PR (gt) | `gt submit --ai --stack` | +| Amend changes (gt) | `gt modify -a` | + +## Claude Code Skill + +This can also be used as a Claude Code skill. Copy the contents to `.claude/skills/implement-review-loop/` in your project. + +## License + +MIT diff --git a/SKILL.md b/SKILL.md new file mode 100644 index 0000000..8b001b7 --- /dev/null +++ b/SKILL.md @@ -0,0 +1,165 @@ +--- +name: implement-review-loop +description: Implement code from a plan, create PRs with Graphite CLI (gt create/submit), wait for code reviews, and iterate on feedback. Use when the user says "implement", "implement the plan", "create PR", "review loop", or wants to go from plan to merged PR. This skill handles the full cycle - implement code, create branch with Graphite, submit PR, wait for reviews (without wasting tokens), read comments, fix code, and push updates. +--- + +# Implement Review Loop + +End-to-end workflow for implementing code from a plan, creating PRs with Graphite, and iterating on review feedback. + +## Prerequisites + +- Graphite CLI installed (`gt --version`) +- GitHub CLI installed (`gh --version`) +- Repository initialized with Graphite (`gt init` if not) +- Devin Review available via npx (`npx devin-review`) + +## Workflow Overview + +``` +Plan -> Implement -> Create Branch (gt) -> Submit PR -> Wait for Review -> Read Comments -> Fix -> Push -> Repeat +``` + +## Phase 1: Implement the Plan + +1. Read the current plan (from context, file, or conversation) +2. Implement all changes specified in the plan +3. Run tests and linting to verify implementation +4. Stage changes: `git add ` + +## Phase 2: Create Branch and PR with Graphite + +Create branch with auto-generated name from commit message: + +```bash +gt create --all --message "feat: " +``` + +Submit the PR with auto-generated description: + +```bash +gt submit --ai --stack +``` + +Or with manual title/description: + +```bash +gt submit --edit-title --edit-description +``` + +Capture the PR URL from output for later use. + +## Phase 3: Request Code Review (Optional) + +Run Devin Review for AI-powered code review: + +```bash +cd /path/to/repo +npx devin-review https://github.com/owner/repo/pull/123 +``` + +This creates an isolated worktree and sends diff to Devin for analysis. + +## Phase 4: Wait for Reviews (Token-Efficient) + +Use the polling script to wait without consuming tokens: + +```bash +./scripts/poll_pr_comments.sh owner/repo 123 --timeout 60 --interval 30 +``` + +The script: + +- Polls GitHub API every 30 seconds (configurable) +- Returns when new comments are detected +- Exits after timeout with no-comments status +- Does NOT consume Claude context while waiting + +For CI checks: + +```bash +./scripts/wait_for_checks.sh owner/repo 123 --timeout 15 +``` + +## Phase 5: Read and Address Comments + +Fetch all comments for review: + +```bash +./scripts/fetch_pr_comments.sh owner/repo 123 +``` + +This outputs: + +- Inline review comments with file:line locations +- PR-level comments +- Review decision status + +For each comment: + +1. Read the feedback +2. Implement the fix +3. Stage the changes + +## Phase 6: Push Updates + +Amend current commit and push: + +```bash +gt modify --all +gt submit +``` + +Or create a new fixup commit: + +```bash +gt create --all --message "fix: address review feedback" +gt submit +``` + +## Phase 7: Loop Until Approved + +Repeat phases 4-6 until: + +- All comments are resolved +- PR is approved +- Ready to merge + +## Quick Reference + +| Task | Command | +| -------------- | ---------------------------------------- | +| Create branch | `gt create -am "feat: description"` | +| Submit PR | `gt submit --ai --stack` | +| View PR | `gt pr` | +| Amend changes | `gt modify -a` | +| Push updates | `gt submit` | +| Poll comments | `./scripts/poll_pr_comments.sh repo pr` | +| Fetch comments | `./scripts/fetch_pr_comments.sh repo pr` | +| Devin review | `npx devin-review ` | + +## Bundled Scripts + +### poll_pr_comments.sh + +Polls for new PR comments without consuming context: + +```bash +./scripts/poll_pr_comments.sh [--timeout ] [--interval ] +``` + +### wait_for_checks.sh + +Waits for GitHub checks to complete: + +```bash +./scripts/wait_for_checks.sh [--timeout ] +``` + +### fetch_pr_comments.sh + +Fetches and formats all PR comments: + +```bash +./scripts/fetch_pr_comments.sh [--unresolved-only] +``` diff --git a/scripts/fetch_pr_comments.sh b/scripts/fetch_pr_comments.sh new file mode 100755 index 0000000..dbfcf61 --- /dev/null +++ b/scripts/fetch_pr_comments.sh @@ -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 [--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 [--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 "" diff --git a/scripts/poll_pr_comments.sh b/scripts/poll_pr_comments.sh new file mode 100755 index 0000000..93ec0e9 --- /dev/null +++ b/scripts/poll_pr_comments.sh @@ -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 [--timeout ] [--interval ] +# +# 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 [--timeout ] [--interval ]" + echo "" + echo "Options:" + echo " --timeout Maximum time to wait (default: 60)" + echo " --interval 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 diff --git a/scripts/wait_for_checks.sh b/scripts/wait_for_checks.sh new file mode 100755 index 0000000..bd9aa01 --- /dev/null +++ b/scripts/wait_for_checks.sh @@ -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 [--timeout ] +# +# 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 [--timeout ]" + echo "" + echo "Options:" + echo " --timeout 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