
Pr Reviewer
- 64 installs
- 22 repo stars
- Updated December 29, 2025
- spillwavesolutions/pr-reviewer-skill
Helps with ai & agent building tasks during AI-assisted development.
About
pr-reviewer is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted coding.
- pr-reviewer
- AI & Agent Building
- AI-coding skill
Pr Reviewer by the numbers
- 64 all-time installs (skills.sh)
- Ranked #6,160 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/spillwavesolutions/pr-reviewer-skill --skill pr-reviewerAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 64 |
|---|---|
| repo stars | ★ 22 |
| Last updated | December 29, 2025 |
| Repository | spillwavesolutions/pr-reviewer-skill ↗ |
What it does
Helps with ai & agent building tasks during AI-assisted development.
Files
PR Reviewer Skill
Conduct comprehensive, professional code reviews for GitHub Pull Requests using industry-standard criteria and automated tooling.
Table of Contents
- Purpose
- When to Use
- Review Process Workflow
- Reference Documentation
- Scripts Reference
- Best Practices
- Quick Reference Commands
- Tips for Effective Reviews
- Resources
Purpose
This skill performs code reviews by:
1. Automating data collection - Fetching all PR-related information (metadata, diff, comments, commits, issues) 2. Organizing review workspace - Creating structured directory with all artifacts 3. Applying systematic criteria - Reviewing against comprehensive quality checklist 4. Facilitating inline feedback - Optionally adding comments directly to PR code 5. Ensuring completeness - Checking functionality, security, testing, maintainability
When to Use
Activate this skill when:
- A GitHub PR URL is provided with a review request
- Receiving "review this PR" or "code review" requests
- Checking PR quality before merging
- Providing systematic feedback on proposed changes
- GitHub PR review is mentioned in any context
Review Process Workflow
IMPORTANT: This skill uses a two-stage approval process. Nothing is posted to GitHub until explicit approval with /send or /send-decline.
Overview
1. Fetch PR data - Collect all information 2. Generate review files - Create detailed, human, and inline comment files 3. Review and edit - Examine files, make changes as needed (use /show) 4. Approve and post - Use /send (approve) or /send-decline (request changes)
Step 1: Fetch PR Data
Use fetch_pr_data.py to automatically collect all PR information:
python scripts/fetch_pr_data.py <pr_url> [--output-dir <dir>] [--no-clone]Actions performed:
- Parse PR URL to extract owner, repo, and PR number
- Create directory structure:
<output-dir>/PRs/<repo-name>/<PR-NUMBER>/ - Fetch PR metadata (title, author, state, branches, labels)
- Download PR diff and commit history
- Retrieve all PR comments and reviews
- Extract ticket references (JIRA, GitHub issues)
- Optionally clone source branch and generate git diff
Example:
python scripts/fetch_pr_data.py https://github.com/facebook/react/pull/28476
# Custom output directory
python scripts/fetch_pr_data.py https://github.com/owner/repo/pull/123 --output-dir /tmp/reviews
# Skip cloning (faster, no git diff)
python scripts/fetch_pr_data.py https://github.com/owner/repo/pull/123 --no-cloneOutput structure:
/tmp/PRs/<repo-name>/<PR-NUMBER>/
├── metadata.json # PR metadata (title, author, branches)
├── diff.patch # PR diff from gh CLI
├── git_diff.patch # Git diff (if cloned)
├── comments.json # Review comments on code
├── commits.json # Commit history
├── related_issues.json # Linked GitHub issues
├── ticket_numbers.json # Extracted ticket references
├── SUMMARY.txt # Human-readable summary
└── source/ # Cloned repository (if not --no-clone)Step 2: Analyze PR Data
After fetching, analyze collected data against review criteria:
1. Read SUMMARY.txt - High-level overview 2. Review metadata.json - PR context, labels, assignees 3. Examine diff.patch - Code changes 4. Check comments.json - Existing feedback 5. Review commits.json - Commit quality and messages 6. Check related_issues.json - Linked tickets/issues 7. Apply review criteria - Evaluate against comprehensive checklist
Use the Read tool to examine files:
Read /tmp/PRs/<repo-name>/<PR-NUMBER>/SUMMARY.txt
Read /tmp/PRs/<repo-name>/<PR-NUMBER>/metadata.json
Read /tmp/PRs/<repo-name>/<PR-NUMBER>/diff.patchStep 3: Generate Review Files
CRITICAL: After analysis, use generate_review_files.py to create structured review documents:
python scripts/generate_review_files.py <pr_review_dir> --findings <findings_json> [--metadata <metadata_json>]Creates three files in pr_review_dir/pr/:
1. `pr/review.md` - Detailed internal review with emojis and line numbers 2. `pr/human.md` - Clean review for posting (no emojis, em-dashes, line numbers) 3. `pr/inline.md` - Proposed inline comments with code snippets
Also creates slash commands in .claude/commands/:
/send- Post human.md and approve PR/send-decline- Post human.md and request changes/show- Open review directory in VS Code
Findings JSON structure:
{
"summary": "Overall assessment of the PR...",
"metadata": {
"repository": "owner/repo",
"number": 123,
"title": "PR title",
"author": "username",
"head_branch": "feature",
"base_branch": "main"
},
"blockers": [
{
"category": "Security",
"issue": "SQL injection vulnerability",
"file": "src/db/queries.py",
"line": 45,
"details": "Using string concatenation for SQL query",
"fix": "Use parameterized queries",
"code_snippet": "result = db.execute('SELECT * FROM users WHERE id = ' + user_id)"
}
],
"important": [...],
"nits": [...],
"suggestions": ["Consider adding...", "Future enhancement..."],
"questions": ["Is this intended to...", "Should we..."],
"praise": ["Excellent test coverage", "Clear documentation"],
"inline_comments": [
{
"file": "src/app.py",
"line": 42,
"comment": "Consider edge case handling for empty input",
"code_snippet": "def process(data):\n return data.strip()",
"start_line": 41,
"end_line": 43,
"owner": "owner",
"repo": "repo",
"pr_number": 123
}
]
}Step 4: Review and Edit Files
Use `/show` to open the review directory in VS Code.
Actions available:
- Read
pr/review.md- Detailed analysis - Edit
pr/human.md- Modify before posting - Review
pr/inline.md- Check proposed comments - Adjust any content as needed
NOTHING is posted until explicit approval in Step 5.
Step 5: Approve and Post
Post the review when ready:
Option A: Approve the PR
/send- Posts
pr/human.mdas comment - Approves the PR
- Confirms action
Option B: Request Changes
/send-decline- Posts
pr/human.mdas comment - Requests changes on the PR
- Confirms action
Posting inline comments (optional, after /send or /send-decline): Review pr/inline.md and run the provided commands for specific code comments.
Step 6: Apply Review Criteria
Reference references/review_criteria.md for comprehensive checklist. Review against these categories:
| Category | Key Questions |
|---|---|
| Functionality | Does code solve the problem? Bugs? Edge cases? |
| Readability | Clear code? Meaningful names? DRY? |
| Style | Follows linter rules? Consistent with codebase? |
| Performance | Efficient algorithms? Scalable? |
| Security | Vulnerabilities addressed? Secrets protected? |
| Testing | Tests exist? Cover happy paths and edge cases? |
| PR Quality | Focused scope? Clean commits? Clear description? |
Priority markers for findings:
- Blocker: Must be fixed before merge
- Important: Should be addressed
- Nit: Nice to have, optional
- Suggestion: Consider for future
- Question: Clarification needed
- Praise: Good work
For detailed criteria: Read references/review_criteria.md
Reference Documentation
This skill includes comprehensive reference guides:
| Reference | Purpose |
|---|---|
references/review_criteria.md | Complete checklist covering functionality, security, testing, and more |
references/gh_cli_guide.md | Quick reference for GitHub CLI commands |
references/scenarios.md | Detailed workflows for common review scenarios |
references/troubleshooting.md | Common issues and solutions |
Scripts Reference
scripts/fetch_pr_data.py
Automated PR data fetching and organization.
python scripts/fetch_pr_data.py <pr_url> [options]
Options:
--output-dir DIR Base output directory (default: /tmp)
--no-clone Skip cloning repositoryscripts/generate_review_files.py
Generate structured review files from analysis findings.
python scripts/generate_review_files.py <pr_review_dir> --findings <findings_json> [--metadata <metadata_json>]Creates:
pr/review.md- Detailed internal reviewpr/human.md- Clean review for postingpr/inline.md- Proposed inline comments with commands.claude/commands/send.md- Slash command to approve and post.claude/commands/send-decline.md- Slash command to request changes.claude/commands/show.md- Slash command to open in VS CodeREVIEW_READY.txt- Summary of next steps
scripts/add_inline_comment.py
Add inline code review comments to specific lines in PR.
python scripts/add_inline_comment.py <owner> <repo> <pr_number> <commit_id> <file_path> <line> "<comment>" [options]
Options:
--side RIGHT|LEFT Side of diff (default: RIGHT)
--start-line N Starting line for multi-line comment
--start-side RIGHT|LEFT Starting side for multi-line commentBest Practices
Communication
- Frame feedback as suggestions, not criticism
- Explain why an issue matters, not just what is wrong
- Acknowledge excellent practices
- Prioritize blockers first, style issues last
Review Efficiency
- Use scripts to automate data fetching and comment posting
- Reference
review_criteria.mdas checklist - Focus: Critical issues > Important > Nice-to-have
- Review promptly (within 24 hours if possible)
Inline Comments
- Reference exact lines and files
- Provide better alternatives
- Test inline comments on test PRs first
- Use sparingly to avoid overwhelming
PR Size Handling
- Large PRs (>400 lines): Suggest splitting
- Review in logical chunks
- Focus on architecture for large changes
For detailed scenarios: Read references/scenarios.md
Quick Reference Commands
# Fetch PR data
python scripts/fetch_pr_data.py https://github.com/owner/repo/pull/123
# Add inline comment
python scripts/add_inline_comment.py owner repo 123 latest "src/app.py" 42 "Comment"
# View PR in browser
gh pr view 123 --repo owner/repo --web
# Check PR status
gh pr checks 123 --repo owner/repo
# View existing comments
gh api /repos/owner/repo/pulls/123/comments --jq '.[] | {path, line, body}'Tips for Effective Reviews
1. Start with context: Read PR description, linked issues, commit messages 2. Understand intent: Identify the problem being solved 3. Check tests first: Verify tests demonstrate the fix/feature 4. Look for patterns: Repeated issues suggest architecture problems 5. Consider alternatives: Evaluate simpler approaches 6. Think about maintenance: Assess future modification ease 7. Remember humans: Maintain kindness, respect, and constructive tone
For troubleshooting: Read references/troubleshooting.md
Resources
- Review Criteria:
references/review_criteria.md - gh CLI Guide:
references/gh_cli_guide.md - Scenarios:
references/scenarios.md - Troubleshooting:
references/troubleshooting.md - GitHub PR Review Docs: https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/reviewing-changes-in-pull-requests
- Google Engineering Practices: https://google.github.io/eng-practices/review/
- OWASP Top 10: https://owasp.org/www-project-top-ten/
# Operating System Files
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db
# IDE and Editor Files
.idea/
.vscode/
*.swp
*.swo
*~
.project
.classpath
.settings/
# Build and Dependency Directories
node_modules/
dist/
build/
target/
*.egg-info/
__pycache__/
*.pyc
*.pyo
# Environment and Configuration
.env
.env.local
.env.*.local
*.log
# Temporary Files
*.tmp
*.temp
.cache/
# Coverage and Testing
coverage/
.coverage
*.cover
.pytest_cache/
# Python Virtual Environments
venv/
env/
ENV/
# Claude Code specific
.claude/cache/
PR Reviewer Skill for Claude Code
  
Comprehensive GitHub Pull Request code review skill that automates data collection, analyzes against industry-standard criteria, and generates structured review files with an approval workflow.
Overview
PR Reviewer is a Claude Code skill that transforms your code review process. It automatically fetches all PR data using GitHub CLI, applies systematic analysis against industry-standard review criteria, and generates professional review documents ready for posting. The two-stage approval workflow ensures nothing is posted until you explicitly approve.
Features
- Automated Data Collection - Fetches PR metadata, diffs, comments, commits, and related issues via GitHub CLI
- Systematic Analysis - Reviews against comprehensive criteria: security, testing, maintainability, performance
- Structured Review Files - Generates detailed internal review, clean public review, and inline comment templates
- Two-Stage Approval - Nothing posts to GitHub until you explicitly approve with
/sendor/send-decline - Inline Comments - Adds specific feedback directly to code lines with posting commands
- Ticket Tracking - Extracts and links JIRA/GitHub issue references
- Professional Templates - Clean, respectful review format without emojis or excessive formatting
Installation
Installing with Skilz (Recommended)
The easiest way to install this skill is using the Skilz Universal Installer:
# Install Skilz (one-time setup)
curl -fsSL https://raw.githubusercontent.com/SpillwaveSolutions/skilz/main/install.sh | bash
# Install this skill
skilz install SpillwaveSolutions_pr-reviewer-skill/pr-reviewerView on the Skilz Marketplace: pr-reviewer
Manual Installation
1. Install GitHub CLI (if not already installed):
# macOS
brew install gh
# Linux
sudo apt install gh # or yum, dnf, etc.
# Windows
winget install GitHub.cli2. Authenticate with GitHub:
gh auth login3. Clone this skill to your Claude Code skills directory:
cd ~/.claude/skills
git clone https://github.com/SpillwaveSolutions/pr-reviewer-skill.git pr-reviewer4. Install Python dependencies (if needed):
cd pr-reviewer
pip install requests # Only needed for add_inline_comment.pyQuick Start
Basic PR Review
1. Fetch PR data:
python scripts/fetch_pr_data.py https://github.com/owner/repo/pull/1232. Analyze the PR by reading the generated files:
/tmp/PRs/<repo-name>/123/SUMMARY.txt
/tmp/PRs/<repo-name>/123/diff.patch
/tmp/PRs/<repo-name>/123/metadata.json3. Generate review files with your findings:
python scripts/generate_review_files.py /tmp/PRs/<repo-name>/123 --findings findings.json4. Review and edit the generated files:
/show # Opens review directory in VS Code5. Approve and post:
/send # Approve PR and post review
# or
/send-decline # Request changes and post reviewWorkflow
┌─────────────────────────────────────────────────────────────────┐
│ 1. Fetch PR Data │
│ python scripts/fetch_pr_data.py <pr_url> │
│ → Collects metadata, diff, comments, commits, issues │
└─────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────┐
│ 2. Analyze PR │
│ → Read SUMMARY.txt, diff.patch, metadata.json │
│ → Apply review criteria (security, testing, etc.) │
│ → Create findings JSON with your analysis │
└─────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────┐
│ 3. Generate Review Files │
│ python scripts/generate_review_files.py <dir> --findings ... │
│ → Creates review.md, human.md, inline.md │
│ → Generates /send, /send-decline, /show commands │
└─────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────┐
│ 4. Review & Edit │
│ /show → Opens in VS Code │
│ → Edit pr/human.md if needed │
│ → Review pr/inline.md for proposed comments │
└─────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────┐
│ 5. Approve & Post │
│ /send (approve) or /send-decline (request changes) │
│ → Posts pr/human.md as review comment │
│ → Optionally post inline comments from pr/inline.md │
└─────────────────────────────────────────────────────────────────┘Review Criteria
This skill reviews PRs against comprehensive industry-standard criteria:
1. Functionality & Correctness
- Does code solve the intended problem?
- Are there bugs or logical errors?
- Edge cases and error handling covered?
2. Security & Best Practices
- Common vulnerabilities (SQL injection, XSS, CSRF)?
- Secrets not hardcoded?
- Dependencies justified and secure?
3. Testing & Quality Assurance
- Tests exist for new code?
- Tests cover happy paths, errors, edge cases?
- CI/CD checks pass?
4. Readability & Maintainability
- Code clarity and meaningful names?
- Functions follow Single Responsibility?
- Code duplication avoided (DRY)?
5. Performance & Efficiency
- Algorithm efficiency (avoid O(n²) where possible)?
- Scalability under load?
6. Style & Conventions
- Follows project linter rules?
- Consistent with existing codebase?
7. Overall PR Quality
- PR scope focused (single feature/fix)?
- Clean commit history?
- Clear PR description?
See `references/review_criteria.md` for complete checklist.
Scripts
fetch_pr_data.py
Automated PR data collection and organization.
python scripts/fetch_pr_data.py <pr_url> [options]
Options:
--output-dir DIR Base output directory (default: /tmp)
--no-clone Skip cloning repository (faster)Output structure:
/tmp/PRs/<repo-name>/<PR-NUMBER>/
├── metadata.json # PR metadata (title, author, branches, etc.)
├── diff.patch # PR diff from gh CLI
├── git_diff.patch # Git diff (if cloned)
├── comments.json # Review comments on code
├── commits.json # Commit history
├── related_issues.json # Linked GitHub issues
├── ticket_numbers.json # Extracted ticket references
├── SUMMARY.txt # Human-readable summary
└── source/ # Cloned repository (if not --no-clone)generate_review_files.py
Generates structured review documents from analysis findings.
python scripts/generate_review_files.py <pr_review_dir> --findings <findings_json> [--metadata <metadata_json>]Creates:
pr/review.md- Detailed internal review with emojis and line numberspr/human.md- Clean review for posting (no emojis, em-dashes, line numbers)pr/inline.md- Proposed inline comments with posting commands.claude/commands/send.md- Slash command to approve and post.claude/commands/send-decline.md- Slash command to request changes.claude/commands/show.md- Slash command to open in VS Code
Example findings JSON:
{
"summary": "Overall assessment of the PR",
"metadata": {
"repository": "owner/repo",
"number": 123,
"title": "PR title",
"author": "username"
},
"blockers": [
{
"category": "Security",
"issue": "SQL injection vulnerability",
"file": "src/db/queries.py",
"line": 45,
"details": "Using string concatenation for SQL query",
"fix": "Use parameterized queries",
"code_snippet": "result = db.execute('SELECT * FROM users WHERE id = ' + user_id)"
}
],
"important": [...],
"nits": [...],
"suggestions": ["Consider adding...", "Future enhancement..."],
"questions": ["Is this intended to...", "Should we..."],
"praise": ["Excellent test coverage", "Clear documentation"],
"inline_comments": [
{
"file": "src/app.py",
"line": 42,
"comment": "Consider edge case handling for empty input",
"code_snippet": "def process(data):\n return data.strip()",
"start_line": 41,
"end_line": 43
}
]
}add_inline_comment.py
Posts inline comments to specific lines in a PR.
python scripts/add_inline_comment.py <owner> <repo> <pr_number> <commit_id> <file_path> <line> "<comment>" [options]
Options:
--side RIGHT|LEFT Side of diff (default: RIGHT)
--start-line N Starting line for multi-line comment
--start-side RIGHT|LEFT Starting side for multi-line commentExample:
python scripts/add_inline_comment.py facebook react 28476 latest "packages/react/src/React.js" 42 "Consider edge case handling here"Usage with Claude Code
When you install this skill, Claude Code can automatically use it when you:
1. Provide a GitHub PR URL and request a review 2. Say "review this PR" or "code review" 3. Ask to check PR quality before merging 4. Mention GitHub PR review in any context
Trigger phrases:
- "review pr"
- "code review"
- "review pull request"
- "check pr"
- "github.com//pull/" (any PR URL)
Examples
Example 1: Quick Review
User: Can you review this PR? https://github.com/facebook/react/pull/28476
Claude Code:
1. Runs fetch_pr_data.py to collect all PR data
2. Reads SUMMARY.txt and metadata.json for context
3. Scans diff.patch for critical issues
4. Applies security, functionality, and testing criteria
5. Creates findings JSON with analysis
6. Runs generate_review_files.py to create review files
7. Tells you to review pr/review.md and pr/human.md
8. Reminds you to use /show to edit, then /send or /send-declineExample 2: Comprehensive Review with Inline Comments
User: Do a thorough review and add inline comments where needed
Claude Code:
1. Fetches complete PR data including cloned repository
2. Analyzes all files against full review_criteria.md checklist
3. Identifies blockers, important issues, and nits
4. Creates findings JSON with detailed inline_comments array
5. Generates all review files (review.md, human.md, inline.md)
6. Provides /show, /send, /send-decline commands
7. You review, edit, approve, and optionally post inline commentsExample 3: Security-Focused Review
User: Check this PR for security issues
Claude Code:
1. Fetches PR data
2. Focuses on security criteria (SQL injection, XSS, secrets, etc.)
3. Examines dependencies and authentication changes
4. Reports security findings with severity levels
5. Generates review with security-focused recommendationsBest Practices
Communication
- Be constructive - Frame as suggestions, not criticism
- Explain why - Don't just say what's wrong, explain why it matters
- Acknowledge good work - Call out excellent practices
- Prioritize - Focus on blockers first, style issues last
Review Efficiency
- Use scripts - Automate data fetching and comment posting
- Reference criteria - Use
review_criteria.mdas checklist - Focus review - Critical issues > Important > Nice-to-have
- Be timely - Review promptly (within 24 hours if possible)
Inline Comments
- Be specific - Reference exact lines and files
- Provide examples - Show better alternatives
- Test first - Try inline comments on test PRs
- Use sparingly - Too many inline comments can overwhelm
Troubleshooting
"gh CLI not found"
Install GitHub CLI: https://cli.github.com/
"Permission denied" errors
Check authentication:
gh auth status
gh auth refresh -s repo"Invalid PR URL"
Ensure URL format: https://github.com/owner/repo/pull/NUMBER
Rate limit errors
# Check rate limit
gh api /rate_limit
# Authenticated users get higher limits
gh auth loginReference Documentation
- `references/review_criteria.md` - Complete checklist with examples
- `references/gh_cli_guide.md` - GitHub CLI commands and patterns
- `SKILL.md` - Detailed skill documentation for Claude Code
Resources
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
1. Fork the repository 2. Create your feature branch (git checkout -b feature/amazing-feature) 3. Commit your changes (git commit -m 'Add amazing feature') 4. Push to the branch (git push origin feature/amazing-feature) 5. Open a Pull Request
License
This project is licensed under the MIT License - see the LICENSE file for details.
Acknowledgments
- Built for Claude Code
- Uses GitHub CLI for API interactions
- Inspired by industry-standard code review practices
Support
For issues, questions, or suggestions:
- Open an issue on GitHub
- Consult the documentation in
SKILL.mdandreferences/
---
Made with care for better code reviews
GitHub CLI (gh) Guide for PR Reviews
This reference provides quick commands and patterns for accessing PR data using the GitHub CLI.
Prerequisites
Install GitHub CLI: https://cli.github.com/
Authenticate:
gh auth loginBasic PR Information
View PR Details
gh pr view <number> --repo <owner>/<repo>
# With JSON output
gh pr view <number> --repo <owner>/<repo> --json number,title,body,state,author,headRefName,baseRefNameView PR Diff
gh pr diff <number> --repo <owner>/<repo>
# Save to file
gh pr diff <number> --repo <owner>/<repo> > pr_diff.patchList PR Files
gh pr view <number> --repo <owner>/<repo> --json files --jq '.files[].path'PR Comments and Reviews
Get PR Comments (Review Comments on Code)
gh api /repos/<owner>/<repo>/pulls/<number>/comments
# Paginate through all comments
gh api /repos/<owner>/<repo>/pulls/<number>/comments --paginate
# With JQ filtering
gh api /repos/<owner>/<repo>/pulls/<number>/comments --jq '.[] | {path, line, body, user: .user.login}'Get PR Reviews
gh api /repos/<owner>/<repo>/pulls/<number>/reviews
# With formatted output
gh api /repos/<owner>/<repo>/pulls/<number>/reviews --jq '.[] | {state, user: .user.login, body}'Get Issue Comments (General PR Comments)
gh api /repos/<owner>/<repo>/issues/<number>/commentsCommit Information
List PR Commits
gh api /repos/<owner>/<repo>/pulls/<number>/commits
# Get commit messages
gh api /repos/<owner>/<repo>/pulls/<number>/commits --jq '.[] | {sha: .sha[0:7], message: .commit.message}'
# Get latest commit SHA
gh api /repos/<owner>/<repo>/pulls/<number>/commits --jq '.[-1].sha'Get Commit Details
gh api /repos/<owner>/<repo>/commits/<sha>
# Get commit diff
gh api /repos/<owner>/<repo>/commits/<sha> -H "Accept: application/vnd.github.diff"Branches
Get Branch Information
# Source branch (head)
gh pr view <number> --repo <owner>/<repo> --json headRefName --jq '.headRefName'
# Target branch (base)
gh pr view <number> --repo <owner>/<repo> --json baseRefName --jq '.baseRefName'Compare Branches
gh api /repos/<owner>/<repo>/compare/<base>...<head>
# Get files changed
gh api /repos/<owner>/<repo>/compare/<base>...<head> --jq '.files[] | {filename, status, additions, deletions}'Related Issues and Tickets
Get Linked Issues
# Get PR body which may contain issue references
gh pr view <number> --repo <owner>/<repo> --json body --jq '.body'
# Search for issue references (#123 format)
gh pr view <number> --repo <owner>/<repo> --json body --jq '.body' | grep -oE '#[0-9]+'Get Issue Details
gh issue view <number> --repo <owner>/<repo>
# JSON format
gh issue view <number> --repo <owner>/<repo> --json number,title,body,state,labels,assigneesGet Issue Comments
gh api /repos/<owner>/<repo>/issues/<number>/commentsPR Status Checks
Get PR Status
gh pr checks <number> --repo <owner>/<repo>
# JSON format
gh api /repos/<owner>/<repo>/commits/<sha>/statusGet Check Runs
gh api /repos/<owner>/<repo>/commits/<sha>/check-runsAdding Comments
Add Inline Code Comment
gh api -X POST /repos/<owner>/<repo>/pulls/<number>/comments \
-f body="Your comment here" \
-f commit_id="<sha>" \
-f path="src/file.py" \
-f side="RIGHT" \
-f line=42Add Multi-line Inline Comment
gh api -X POST /repos/<owner>/<repo>/pulls/<number>/comments \
-f body="Multi-line comment" \
-f commit_id="<sha>" \
-f path="src/file.py" \
-f side="RIGHT" \
-f start_line=40 \
-f start_side="RIGHT" \
-f line=45Add General PR Comment
gh pr comment <number> --repo <owner>/<repo> --body "Your comment"
# Or via API
gh api -X POST /repos/<owner>/<repo>/issues/<number>/comments \
-f body="Your comment"Creating a Review
Create Review with Comments
gh api -X POST /repos/<owner>/<repo>/pulls/<number>/reviews \
-f body="Overall review comments" \
-f event="COMMENT" \
-f commit_id="<sha>" \
-f comments='[{"path":"src/file.py","line":42,"body":"Comment on line 42"}]'Submit Review (Approve/Request Changes)
# Approve
gh api -X POST /repos/<owner>/<repo>/pulls/<number>/reviews \
-f body="LGTM!" \
-f event="APPROVE" \
-f commit_id="<sha>"
# Request changes
gh api -X POST /repos/<owner>/<repo>/pulls/<number>/reviews \
-f body="Please address these issues" \
-f event="REQUEST_CHANGES" \
-f commit_id="<sha>"Searching and Filtering
Search Code in PR
# Get PR diff and search
gh pr diff <number> --repo <owner>/<repo> | grep "search_term"
# Search in specific files
gh pr view <number> --repo <owner>/<repo> --json files --jq '.files[] | select(.path | contains("search_term"))'Filter by File Type
gh pr view <number> --repo <owner>/<repo> --json files --jq '.files[] | select(.path | endswith(".py"))'Labels, Assignees, and Metadata
Get Labels
gh pr view <number> --repo <owner>/<repo> --json labels --jq '.labels[].name'Get Assignees
gh pr view <number> --repo <owner>/<repo> --json assignees --jq '.assignees[].login'Get Reviewers
gh pr view <number> --repo <owner>/<repo> --json reviewRequests --jq '.reviewRequests[].login'Advanced Queries
Get PR Timeline
gh api /repos/<owner>/<repo>/issues/<number>/timelineGet PR Events
gh api /repos/<owner>/<repo>/issues/<number>/eventsGet All PR Data
gh pr view <number> --repo <owner>/<repo> --json \
number,title,body,state,author,headRefName,baseRefName,\
commits,reviews,comments,files,labels,assignees,milestone,\
createdAt,updatedAt,mergedAt,closedAt,url,isDraftCommon JQ Patterns
Extract specific fields
--jq '.field'
--jq '.array[].field'
--jq '.[] | {field1, field2}'Filter arrays
--jq '.[] | select(.field == "value")'
--jq '.[] | select(.field | contains("substring"))'Count items
--jq '. | length'
--jq '.array | length'Map and transform
--jq '.array | map(.field)'
--jq '.[] | {newField: .oldField}'Line Number Considerations for Inline Comments
IMPORTANT: The line parameter for inline comments refers to the line number in the diff, not the absolute line number in the file.
Understanding Diff Line Numbers
In a diff:
- Lines are numbered relative to the diff context, not the file
- The
sideparameter determines which version: "RIGHT": New version (after changes)"LEFT": Old version (before changes)
Finding Diff Line Numbers
# Get diff with line numbers
gh pr diff <number> --repo <owner>/<repo> | cat -n
# Get specific file diff
gh api /repos/<owner>/<repo>/pulls/<number>/files --jq '.[] | select(.filename == "path/to/file")'Example Diff
@@ -10,7 +10,8 @@ def process_data(data):
if not data:
return None
- result = old_function(data)
+ # New implementation
+ result = new_function(data)
return resultIn this diff:
- Line 13 (old) would be
side: "LEFT" - Line 14-15 (new) would be
side: "RIGHT" - Line numbers are relative to the diff hunk starting at line 10
Error Handling
Common Errors
Resource not found:
# Check repo access
gh repo view <owner>/<repo>
# Check PR exists
gh pr list --repo <owner>/<repo> | grep <number>API rate limit:
# Check rate limit
gh api /rate_limit
# Use authentication to get higher limits
gh auth loginPermission denied:
# Check authentication
gh auth status
# May need additional scopes
gh auth refresh -s repoTips and Best Practices
1. Use `--paginate` for large result sets (comments, commits) 2. Combine with `jq` for powerful filtering and formatting 3. Cache results by saving to files to avoid repeated API calls 4. Check rate limits when making many API calls 5. Use `--json` output for programmatic parsing 6. Specify `--repo` when outside repository directory 7. Get latest commit before adding inline comments 8. Test comments on draft PRs or test repositories first
Reference Links
- GitHub CLI Manual: https://cli.github.com/manual/
- GitHub REST API: https://docs.github.com/en/rest
- JQ Manual: https://jqlang.github.io/jq/manual/
- PR Review Comments API: https://docs.github.com/en/rest/pulls/comments
- PR Reviews API: https://docs.github.com/en/rest/pulls/reviews
Code Review Criteria
This document outlines the comprehensive criteria for conducting pull request code reviews. Use this as a checklist when reviewing PRs to ensure thorough, consistent, and constructive feedback.
Review Process Overview
When reviewing a PR, the goal is to ensure changes are:
- Correct: Solves the intended problem without bugs
- Maintainable: Easy to understand and modify
- Aligned: Follows project standards and conventions
- Secure: Free from vulnerabilities
- Tested: Covered by appropriate tests
1. Functionality and Correctness
Problem Resolution
- [ ] Does the code solve the intended problem?
- Verify changes address the issue or feature described in the PR
- Cross-reference with linked tickets (JIRA, GitHub issues)
- Test manually or run the code if possible
Bugs and Logic
- [ ] Are there bugs or logical errors?
- Check for off-by-one errors
- Verify null/undefined/None handling
- Review assumptions about inputs and outputs
- Look for race conditions or concurrency issues
- Check loop termination conditions
Edge Cases and Error Handling
- [ ] Edge cases handled?
- Empty collections (arrays, lists, maps)
- Null/None/undefined values
- Boundary values (min/max integers, empty strings)
- Invalid or malformed inputs
- [ ] Error handling implemented?
- Network failures
- File system errors
- Database connection issues
- API errors and timeouts
- Graceful degradation
Compatibility
- [ ] Works across supported environments?
- Browser compatibility (if web app)
- OS versions (if desktop/mobile)
- Database versions
- Language/runtime versions
- Doesn't break existing features (regression check)
2. Readability and Maintainability
Code Clarity
- [ ] Easy to read and understand?
- Meaningful variable names (avoid
x,temp,data) - Meaningful function names (verb-first, descriptive)
- Short methods/functions (ideally < 50 lines)
- Logical structure and flow
- Minimal nested complexity
Modularity
- [ ] Single Responsibility Principle?
- Functions/methods do one thing well
- Classes have a clear, focused purpose
- No "god objects" or overly complex logic
- [ ] Suggest refactoring if needed:
- Extract complex logic into helper functions
- Break large functions into smaller ones
- Separate concerns (UI, business logic, data access)
Code Duplication
- [ ] DRY (Don't Repeat Yourself)?
- Repeated code abstracted into helpers
- Shared logic moved to libraries/utilities
- Avoid copy-paste programming
Future-Proofing
- [ ] Allows for easy extensions?
- Avoid hard-coded values (use constants/configs)
- Use dependency injection where appropriate
- Follow SOLID principles
- Consider extensibility without modification
3. Style and Conventions
Style Guide Adherence
- [ ] Follows project linter rules?
- ESLint (JavaScript/TypeScript)
- Pylint/Flake8/Black (Python)
- RuboCop (Ruby)
- Checkstyle/PMD (Java)
- golangci-lint (Go)
- [ ] Formatting consistent?
- Proper indentation (spaces vs. tabs)
- Consistent spacing
- Line length limits
- Import/require organization
Codebase Consistency
- [ ] Matches existing patterns?
- Follows established architectural patterns
- Uses existing utilities and helpers
- Consistent naming conventions
- Matches idioms of the language/framework
Comments and Documentation
- [ ] Sufficient comments?
- Complex algorithms explained
- Non-obvious decisions documented
- API contracts clarified
- TODOs tracked with ticket numbers
- [ ] Not excessive?
- Code should be self-documenting where possible
- Avoid obvious comments ("increment i")
- [ ] Documentation updated?
- README reflects new features
- API docs updated
- Inline docs (JSDoc, docstrings, etc.)
- Architecture diagrams current
4. Performance and Efficiency
Resource Usage
- [ ] Algorithm efficiency?
- Avoid O(n²) or worse in loops
- Use appropriate data structures
- Minimize database queries (N+1 problem)
- Avoid unnecessary computations
Scalability
- [ ] Performs well under load?
- No blocking operations in critical paths
- Async/await for I/O operations
- Pagination for large datasets
- Caching where appropriate
Optimization Balance
- [ ] Optimizations necessary?
- Premature optimization avoided
- Readability not sacrificed for micro-optimizations
- Benchmark before complex optimizations
- Profile to identify actual bottlenecks
5. Security and Best Practices
Vulnerabilities
- [ ] Common security issues addressed?
- SQL injection (use parameterized queries)
- XSS (Cross-Site Scripting) - proper escaping
- CSRF (Cross-Site Request Forgery) - tokens
- Command injection
- Path traversal
- Authentication/authorization checks
Data Handling
- [ ] Sensitive data protected?
- Encrypted in transit (HTTPS/TLS)
- Encrypted at rest
- Input validation and sanitization
- Output encoding
- PII handling compliance (GDPR, etc.)
- [ ] Secrets management?
- No hardcoded passwords/API keys
- Use environment variables
- Use secret management systems
- No secrets in logs
Dependencies
- [ ] New packages justified?
- Actually necessary
- From trusted sources
- Up-to-date and maintained
- No known vulnerabilities
- License compatible
- [ ] Dependency management?
- Lock files committed
- Minimal dependency footprint
- Consider alternatives if bloated
6. Testing and Quality Assurance
Test Coverage
- [ ] Tests exist for new code?
- Unit tests for individual functions/methods
- Integration tests for workflows
- End-to-end tests for critical paths
- [ ] Tests cover scenarios?
- Happy paths
- Error conditions
- Edge cases
- Boundary conditions
Test Quality
- [ ] Tests are meaningful?
- Not just for coverage metrics
- Assert actual behavior
- Test intent, not implementation
- Avoid brittle tests
- [ ] Test maintainability?
- Clear test names
- Arrange-Act-Assert pattern
- Minimal test duplication
- Fast execution
CI/CD Integration
- [ ] Automated checks pass?
- Linting
- Tests (unit, integration, e2e)
- Build process
- Security scans
- Code coverage thresholds
7. Overall PR Quality
Scope
- [ ] PR is focused?
- Single feature/fix per PR
- Not too large (< 400 lines ideal)
- Suggest splitting if combines unrelated changes
Commit History
- [ ] Clean, atomic commits?
- Each commit is logical unit
- Descriptive commit messages
- Follow conventional commits if applicable
- Avoid "fix", "update", "wip" vagueness
PR Description
- [ ] Clear description?
- Explains why changes were made
- Links to tickets/issues
- Steps to reproduce/test
- Screenshots for UI changes
- Breaking changes called out
- Migration steps if needed
Impact Assessment
- [ ] Considered downstream effects?
- API changes (breaking vs. backward-compatible)
- Database schema changes
- Impact on other teams/services
- Performance implications
- Monitoring and alerting needs
Review Feedback Guidelines
Communication Style
- Be constructive and kind
- Frame as suggestions: "Consider X because Y"
- Not criticism: "This is wrong"
- Acknowledge good work
- Explain the "why" behind feedback
Prioritization
- Focus on critical issues first:
1. Bugs and correctness 2. Security vulnerabilities 3. Performance problems 4. Design/architecture issues 5. Style and conventions
Feedback Markers
Use clear markers to indicate severity:
- 🔴 Blocker: Must be fixed before merge
- 🟡 Important: Should be addressed
- 🟢 Nit: Nice to have, optional
- 💡 Suggestion: Consider for future
- ❓ Question: Clarification needed
- ✅ Praise: Good work!
Time Efficiency
- Review promptly (within 24 hours)
- For large PRs, review in chunks
- Request smaller PRs if too large
- Use automated tools to catch style issues
Decision Making
- Approve: Solid overall, minor nits acceptable
- Request Changes: Blockers must be addressed
- Comment: Provide feedback without blocking
Language/Framework-Specific Considerations
JavaScript/TypeScript
- Type safety (TypeScript)
- Promise handling (avoid callback hell)
- Memory leaks (event listeners)
- Bundle size impact
Python
- PEP 8 compliance
- Type hints (Python 3.5+)
- Virtual environment dependencies
- Generator usage for memory efficiency
Java
- Memory management
- Exception handling (checked vs. unchecked)
- Thread safety
- Immutability where appropriate
Go
- Error handling (no exceptions)
- Goroutine management
- Channel usage
- Interface design
SQL/Database
- Index usage
- Query performance
- Transaction boundaries
- Migration reversibility
Frontend (React, Vue, Angular)
- Component reusability
- State management
- Accessibility (a11y)
- Performance (re-renders, bundle size)
Tools and Automation
Leverage tools to automate checks:
- Linters: ESLint, Pylint, RuboCop
- Formatters: Prettier, Black, gofmt
- Security: Snyk, CodeQL, Dependabot
- Coverage: Codecov, Coveralls
- Performance: Lighthouse, WebPageTest
- Accessibility: axe, WAVE
Resources
- Google Engineering Practices: https://google.github.io/eng-practices/review/
- GitHub Code Review Guide: https://github.com/features/code-review
- OWASP Top 10: https://owasp.org/www-project-top-ten/
- Clean Code (Robert C. Martin)
- Code Complete (Steve McConnell)
Common Review Scenarios
Detailed workflows for specific review use cases.
Scenario 1: Quick Review Request
Trigger: User provides PR URL and requests review.
Workflow: 1. Run fetch_pr_data.py to collect data 2. Read SUMMARY.txt and metadata.json 3. Scan diff.patch for obvious issues 4. Apply critical criteria (security, bugs, tests) 5. Create findings JSON with analysis 6. Run generate_review_files.py to create review files 7. Direct user to review pr/review.md and pr/human.md 8. Remind user to use /show to edit, then /send or /send-decline
Scenario 2: Thorough Review with Inline Comments
Trigger: User requests comprehensive review with inline comments.
Workflow: 1. Run fetch_pr_data.py with cloning enabled 2. Read all collected files (metadata, diff, comments, commits) 3. Apply full review_criteria.md checklist 4. Identify critical issues, important issues, and nits 5. Create findings JSON with inline_comments array 6. Run generate_review_files.py to create all files 7. Direct user to:
- Review
pr/review.mdfor detailed analysis - Edit
pr/human.mdif needed - Check
pr/inline.mdfor proposed comments - Use
/showto open in VS Code - Use
/sendor/send-declinewhen ready - Optionally post inline comments from
pr/inline.md
Scenario 3: Security-Focused Review
Trigger: User requests security-specific review.
Workflow: 1. Fetch PR data 2. Focus on review_criteria.md Section 5 (Security) 3. Check for: SQL injection, XSS, CSRF, secrets exposure 4. Examine dependencies in metadata 5. Review authentication/authorization changes 6. Report security findings with severity ratings
Scenario 4: Review with Related Tickets
Trigger: User requests review against linked JIRA/GitHub ticket.
Workflow: 1. Fetch PR data (captures ticket references) 2. Read related_issues.json 3. Compare PR changes against ticket requirements 4. Verify all acceptance criteria met 5. Note any missing functionality 6. Suggest additional tests if needed
Scenario 5: Large PR Review (>400 lines)
Trigger: PR contains more than 400 lines of changes.
Workflow: 1. Suggest splitting into smaller PRs if feasible 2. Review in logical chunks by file or feature 3. Focus on architecture and design first 4. Document structural concerns before line-level issues 5. Prioritize security and correctness over style
Troubleshooting Guide
Common issues and solutions for the PR Reviewer skill.
gh CLI Not Found
Install GitHub CLI: https://cli.github.com/
# macOS
brew install gh
# Linux
sudo apt install gh # or yum, dnf, etc.
# Authenticate
gh auth loginPermission Denied Errors
Check authentication:
gh auth status
gh auth refresh -s repoInvalid PR URL
Ensure URL format: https://github.com/owner/repo/pull/NUMBER
Line Number Mismatch in Diff
Inline comment line numbers are relative to the diff, not absolute file positions. Use gh pr diff <number> to see diff line numbers.
Rate Limit Errors
# Check rate limit
gh api /rate_limit
# Authenticated users get higher limits
gh auth loginCommon Error Patterns
| Error | Cause | Solution |
|---|---|---|
| 401 Unauthorized | Token expired | Run gh auth refresh |
| 403 Forbidden | Missing scope | Run gh auth refresh -s repo |
| 404 Not Found | Private repo access | Verify repo permissions |
| 422 Unprocessable | Invalid request | Check command arguments |
#!/usr/bin/env python3
"""
Add inline code review comments to a GitHub PR.
Usage:
python add_inline_comment.py <owner> <repo> <pr_number> <commit_id> <file_path> <line> <comment> [--side RIGHT|LEFT]
Example:
python add_inline_comment.py owner repo 123 abc123def "src/main.py" 42 "Consider refactoring this logic"
python add_inline_comment.py owner repo 123 abc123def "src/main.py" 42 "Check edge cases" --side LEFT
"""
import argparse
import json
import subprocess
import sys
from typing import Optional
def add_inline_comment(
owner: str,
repo: str,
pr_number: str,
commit_id: str,
path: str,
line: int,
body: str,
side: str = "RIGHT",
start_line: Optional[int] = None,
start_side: Optional[str] = None
) -> dict:
"""
Add an inline comment to a PR using gh CLI.
Args:
owner: Repository owner
repo: Repository name
pr_number: Pull request number
commit_id: SHA of the commit to comment on
path: File path relative to repo root
line: Line number in the diff
body: Comment text
side: "RIGHT" (new version) or "LEFT" (old version)
start_line: For multi-line comments, the starting line
start_side: For multi-line comments, the starting side
Returns:
API response as dict
Raises:
RuntimeError: If gh command fails
"""
# Build the API request body
request_body = {
"body": body,
"commit_id": commit_id,
"path": path,
"side": side,
"line": line
}
# Add multi-line comment fields if provided
if start_line is not None:
request_body["start_line"] = start_line
if start_side is not None:
request_body["start_side"] = start_side
# Convert to JSON string for gh CLI
request_json = json.dumps(request_body)
# Build gh api command
cmd = [
'gh', 'api',
'-X', 'POST',
'-H', 'Accept: application/vnd.github+json',
f'/repos/{owner}/{repo}/pulls/{pr_number}/comments',
'--input', '-'
]
try:
result = subprocess.run(
cmd,
input=request_json,
capture_output=True,
text=True,
check=True
)
return json.loads(result.stdout)
except subprocess.CalledProcessError as e:
raise RuntimeError(f"Failed to add comment: {e.stderr}")
except FileNotFoundError:
raise RuntimeError("gh CLI not found. Please install: https://cli.github.com/")
def get_latest_commit(owner: str, repo: str, pr_number: str) -> str:
"""Get the latest commit SHA for a PR."""
try:
result = subprocess.run([
'gh', 'api',
f'/repos/{owner}/{repo}/pulls/{pr_number}/commits',
'--jq', '.[-1].sha'
], capture_output=True, text=True, check=True)
return result.stdout.strip()
except subprocess.CalledProcessError as e:
raise RuntimeError(f"Failed to get commits: {e.stderr}")
def main():
parser = argparse.ArgumentParser(
description='Add inline code review comment to GitHub PR',
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog=__doc__
)
parser.add_argument('owner', help='Repository owner')
parser.add_argument('repo', help='Repository name')
parser.add_argument('pr_number', help='Pull request number')
parser.add_argument('commit_id', help='Commit SHA (use "latest" to auto-fetch)')
parser.add_argument('path', help='File path relative to repo root')
parser.add_argument('line', type=int, help='Line number in the diff')
parser.add_argument('body', help='Comment text')
parser.add_argument('--side', choices=['RIGHT', 'LEFT'], default='RIGHT',
help='Side of the diff (RIGHT=new, LEFT=old)')
parser.add_argument('--start-line', type=int,
help='Starting line for multi-line comment')
parser.add_argument('--start-side', choices=['RIGHT', 'LEFT'],
help='Starting side for multi-line comment')
args = parser.parse_args()
try:
# Get latest commit if requested
commit_id = args.commit_id
if commit_id.lower() == 'latest':
print(f"Fetching latest commit for PR #{args.pr_number}...")
commit_id = get_latest_commit(args.owner, args.repo, args.pr_number)
print(f"Latest commit: {commit_id}")
# Add the inline comment
print(f"Adding comment to {args.path}:{args.line}...")
response = add_inline_comment(
owner=args.owner,
repo=args.repo,
pr_number=args.pr_number,
commit_id=commit_id,
path=args.path,
line=args.line,
body=args.body,
side=args.side,
start_line=args.start_line,
start_side=args.start_side
)
print(f"\n✅ Comment added successfully!")
print(f"Comment ID: {response.get('id')}")
print(f"URL: {response.get('html_url')}")
except Exception as e:
print(f"Error: {e}", file=sys.stderr)
sys.exit(1)
if __name__ == '__main__':
main()
#!/usr/bin/env python3
"""
Fetch GitHub PR data using gh CLI and organize it for review.
Usage:
python fetch_pr_data.py <pr_url> [--output-dir <dir>]
Example:
python fetch_pr_data.py https://github.com/owner/repo/pull/123
python fetch_pr_data.py https://github.com/owner/repo/pull/123 --output-dir /tmp/custom
"""
import argparse
import json
import os
import re
import subprocess
import sys
from pathlib import Path
from typing import Dict, List, Optional, Tuple
def parse_pr_url(pr_url: str) -> Tuple[str, str, str]:
"""
Parse GitHub PR URL to extract owner, repo, and PR number.
Args:
pr_url: GitHub PR URL (e.g., https://github.com/owner/repo/pull/123)
Returns:
Tuple of (owner, repo, pr_number)
Raises:
ValueError: If URL format is invalid
"""
pattern = r'github\.com/([^/]+)/([^/]+)/pull/(\d+)'
match = re.search(pattern, pr_url)
if not match:
raise ValueError(f"Invalid GitHub PR URL: {pr_url}")
return match.group(1), match.group(2), match.group(3)
def run_gh_command(args: List[str]) -> str:
"""
Run gh CLI command and return output.
Args:
args: Command arguments to pass to gh
Returns:
Command output as string
Raises:
RuntimeError: If gh command fails
"""
try:
result = subprocess.run(
['gh'] + args,
capture_output=True,
text=True,
check=True
)
return result.stdout
except subprocess.CalledProcessError as e:
raise RuntimeError(f"gh command failed: {e.stderr}")
except FileNotFoundError:
raise RuntimeError("gh CLI not found. Please install: https://cli.github.com/")
def fetch_pr_metadata(owner: str, repo: str, pr_number: str) -> Dict:
"""Fetch PR metadata using gh pr view."""
repo_spec = f"{owner}/{repo}"
output = run_gh_command([
'pr', 'view', pr_number,
'--repo', repo_spec,
'--json', 'number,title,body,state,author,headRefName,baseRefName,commits,reviews,comments,files,labels,assignees,milestone,createdAt,updatedAt,mergedAt,closedAt,url,isDraft'
])
return json.loads(output)
def fetch_pr_diff(owner: str, repo: str, pr_number: str) -> str:
"""Fetch PR diff using gh pr diff."""
repo_spec = f"{owner}/{repo}"
return run_gh_command(['pr', 'diff', pr_number, '--repo', repo_spec])
def fetch_pr_comments(owner: str, repo: str, pr_number: str) -> List[Dict]:
"""Fetch PR review comments."""
repo_spec = f"{owner}/{repo}"
output = run_gh_command([
'api',
f'/repos/{owner}/{repo}/pulls/{pr_number}/comments',
'--paginate'
])
return json.loads(output)
def fetch_commits(owner: str, repo: str, pr_number: str) -> List[Dict]:
"""Fetch commit details for the PR."""
repo_spec = f"{owner}/{repo}"
output = run_gh_command([
'api',
f'/repos/{owner}/{repo}/pulls/{pr_number}/commits',
'--paginate'
])
return json.loads(output)
def extract_ticket_numbers(text: str) -> List[str]:
"""
Extract ticket/issue numbers from text.
Looks for patterns like: JIRA-123, #123, PROJ-456, etc.
"""
patterns = [
r'#(\d+)', # GitHub issues: #123
r'([A-Z]+-\d+)', # JIRA style: PROJ-123
r'([A-Z]{2,}-\d+)', # Generic ticket: ABC-123
]
tickets = []
for pattern in patterns:
matches = re.findall(pattern, text)
tickets.extend(matches)
return list(set(tickets)) # Remove duplicates
def fetch_github_issue(owner: str, repo: str, issue_number: str) -> Optional[Dict]:
"""Fetch GitHub issue details if it exists."""
try:
output = run_gh_command([
'api',
f'/repos/{owner}/{repo}/issues/{issue_number.lstrip("#")}'
])
return json.loads(output)
except RuntimeError:
return None
def setup_pr_review_dir(base_dir: str, repo: str, pr_number: str) -> Path:
"""Create and return the PR review directory."""
pr_review_dir = Path(base_dir) / 'PRs' / repo / pr_number
pr_review_dir.mkdir(parents=True, exist_ok=True)
return pr_review_dir
def clone_pr_branch(owner: str, repo: str, branch: str, target_dir: Path) -> None:
"""Clone the PR source branch into target directory."""
repo_url = f"https://github.com/{owner}/{repo}.git"
clone_dir = target_dir / "source"
if clone_dir.exists():
print(f"Repository already cloned at {clone_dir}, pulling latest...")
subprocess.run(['git', '-C', str(clone_dir), 'pull'], check=True)
else:
print(f"Cloning {repo_url} branch {branch}...")
subprocess.run([
'git', 'clone',
'--branch', branch,
'--single-branch',
repo_url,
str(clone_dir)
], check=True)
def get_branch_diff(clone_dir: Path, base_branch: str, head_branch: str) -> str:
"""Get git diff between base and head branches."""
# Fetch base branch if not already present
subprocess.run([
'git', '-C', str(clone_dir),
'fetch', 'origin', f'{base_branch}:{base_branch}'
], check=False) # Don't fail if branch exists
result = subprocess.run([
'git', '-C', str(clone_dir),
'diff', f'origin/{base_branch}...{head_branch}'
], capture_output=True, text=True, check=True)
return result.stdout
def save_data(pr_review_dir: Path, data: Dict) -> None:
"""Save all fetched data to JSON files in the PR review directory."""
for filename, content in data.items():
filepath = pr_review_dir / filename
if filename.endswith('.json'):
with open(filepath, 'w') as f:
json.dump(content, f, indent=2)
else:
with open(filepath, 'w') as f:
f.write(content)
print(f"Saved: {filepath}")
def main():
parser = argparse.ArgumentParser(
description='Fetch GitHub PR data for code review',
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog=__doc__
)
parser.add_argument('pr_url', help='GitHub PR URL')
parser.add_argument('--output-dir', default='/tmp',
help='Base output directory (default: /tmp)')
parser.add_argument('--no-clone', action='store_true',
help='Skip cloning the repository')
args = parser.parse_args()
try:
# Parse PR URL
owner, repo, pr_number = parse_pr_url(args.pr_url)
print(f"Fetching PR #{pr_number} from {owner}/{repo}...")
# Setup review directory
pr_review_dir = setup_pr_review_dir(args.output_dir, repo, pr_number)
print(f"PR review directory: {pr_review_dir}")
# Fetch PR metadata
print("Fetching PR metadata...")
metadata = fetch_pr_metadata(owner, repo, pr_number)
# Fetch PR diff
print("Fetching PR diff...")
diff = fetch_pr_diff(owner, repo, pr_number)
# Fetch comments
print("Fetching PR comments...")
comments = fetch_pr_comments(owner, repo, pr_number)
# Fetch commits
print("Fetching commit history...")
commits = fetch_commits(owner, repo, pr_number)
# Extract and fetch ticket information
print("Extracting ticket references...")
all_text = f"{metadata.get('title', '')} {metadata.get('body', '')}"
for commit in commits:
all_text += f" {commit.get('commit', {}).get('message', '')}"
ticket_numbers = extract_ticket_numbers(all_text)
related_issues = {}
for ticket in ticket_numbers:
if ticket.startswith('#') or ticket.isdigit():
issue_num = ticket.lstrip('#')
print(f"Fetching GitHub issue #{issue_num}...")
issue = fetch_github_issue(owner, repo, issue_num)
if issue:
related_issues[ticket] = issue
# Clone repository and get diff (optional)
git_diff = ""
if not args.no_clone:
try:
print("Cloning repository...")
clone_pr_branch(owner, repo, metadata['headRefName'], pr_review_dir)
print("Generating git diff...")
clone_dir = pr_review_dir / "source"
git_diff = get_branch_diff(
clone_dir,
metadata['baseRefName'],
metadata['headRefName']
)
except Exception as e:
print(f"Warning: Could not clone repository: {e}")
# Save all data
print("\nSaving data...")
data = {
'metadata.json': metadata,
'diff.patch': diff,
'comments.json': comments,
'commits.json': commits,
'related_issues.json': related_issues,
'ticket_numbers.json': ticket_numbers,
}
if git_diff:
data['git_diff.patch'] = git_diff
save_data(pr_review_dir, data)
# Create summary file
summary = f"""PR Review Summary
==================
Repository: {owner}/{repo}
PR Number: #{pr_number}
Title: {metadata.get('title', 'N/A')}
Author: {metadata.get('author', {}).get('login', 'N/A')}
State: {metadata.get('state', 'N/A')}
Draft: {metadata.get('isDraft', False)}
Branches:
Source: {metadata.get('headRefName', 'N/A')}
Target: {metadata.get('baseRefName', 'N/A')}
Files Changed: {len(metadata.get('files', []))}
Commits: {len(commits)}
Comments: {len(comments)}
Reviews: {len(metadata.get('reviews', []))}
Related Tickets:
{chr(10).join(f" - {ticket}" for ticket in ticket_numbers) if ticket_numbers else " None found"}
Review Directory: {pr_review_dir}
"""
summary_file = pr_review_dir / 'SUMMARY.txt'
with open(summary_file, 'w') as f:
f.write(summary)
print(f"\n{summary}")
print(f"\nAll data saved to: {pr_review_dir}")
except Exception as e:
print(f"Error: {e}", file=sys.stderr)
sys.exit(1)
if __name__ == '__main__':
main()
#!/usr/bin/env python3
"""
Generate structured review files from PR analysis.
Creates three review files:
- pr/review.md: Detailed review for internal use
- pr/human.md: Short, clean review for posting (no emojis, em-dashes, line numbers)
- pr/inline.md: List of inline comments with code snippets
Usage:
python generate_review_files.py <pr_review_dir> --findings <findings_json>
Example:
python generate_review_files.py /tmp/PRs/myrepo/123 --findings findings.json
"""
import argparse
import json
import os
import sys
from pathlib import Path
from typing import Dict, List, Any
def create_pr_directory(pr_review_dir: Path) -> Path:
"""Create the pr/ subdirectory for review files."""
pr_dir = pr_review_dir / "pr"
pr_dir.mkdir(parents=True, exist_ok=True)
return pr_dir
def load_findings(findings_file: str) -> Dict[str, Any]:
"""
Load review findings from JSON file.
Expected structure:
{
"summary": "Overall assessment...",
"blockers": [{
"category": "Security",
"issue": "SQL injection vulnerability",
"file": "src/db/queries.py",
"line": 45,
"details": "Using string concatenation...",
"fix": "Use parameterized queries",
"code_snippet": "result = db.execute(...)"
}],
"important": [...],
"nits": [...],
"suggestions": [...],
"questions": [...],
"praise": [...],
"inline_comments": [{
"file": "src/app.py",
"line": 42,
"comment": "Consider edge case handling",
"code_snippet": "def process(data):\n return data.strip()",
"start_line": 41,
"end_line": 43
}]
}
"""
with open(findings_file, 'r') as f:
return json.load(f)
def generate_detailed_review(findings: Dict[str, Any], metadata: Dict[str, Any]) -> str:
"""Generate detailed review.md with full analysis."""
review = f"""# Pull Request Review - Detailed Analysis
## PR Information
**Repository**: {metadata.get('repository', 'N/A')}
**PR Number**: #{metadata.get('number', 'N/A')}
**Title**: {metadata.get('title', 'N/A')}
**Author**: {metadata.get('author', 'N/A')}
**Branch**: {metadata.get('head_branch', 'N/A')} → {metadata.get('base_branch', 'N/A')}
## Summary
{findings.get('summary', 'No summary provided')}
"""
# Add blockers
blockers = findings.get('blockers', [])
if blockers:
review += "## 🔴 Critical Issues (Blockers)\n\n"
review += "**These MUST be fixed before merging.**\n\n"
for i, blocker in enumerate(blockers, 1):
review += f"### {i}. {blocker.get('category', 'Issue')}: {blocker.get('issue', 'Unknown')}\n\n"
if blocker.get('file'):
review += f"**File**: `{blocker['file']}"
if blocker.get('line'):
review += f":{blocker['line']}"
review += "`\n\n"
review += f"**Problem**: {blocker.get('details', 'No details')}\n\n"
if blocker.get('fix'):
review += f"**Solution**: {blocker['fix']}\n\n"
if blocker.get('code_snippet'):
review += f"**Current Code**:\n```\n{blocker['code_snippet']}\n```\n\n"
review += "---\n\n"
# Add important issues
important = findings.get('important', [])
if important:
review += "## 🟡 Important Issues\n\n"
review += "**Should be addressed before merging.**\n\n"
for i, issue in enumerate(important, 1):
review += f"### {i}. {issue.get('category', 'Issue')}: {issue.get('issue', 'Unknown')}\n\n"
if issue.get('file'):
review += f"**File**: `{issue['file']}"
if issue.get('line'):
review += f":{issue['line']}"
review += "`\n\n"
review += f"**Impact**: {issue.get('details', 'No details')}\n\n"
if issue.get('fix'):
review += f"**Suggestion**: {issue['fix']}\n\n"
if issue.get('code_snippet'):
review += f"**Code**:\n```\n{issue['code_snippet']}\n```\n\n"
review += "---\n\n"
# Add nits
nits = findings.get('nits', [])
if nits:
review += "## 🟢 Minor Issues (Nits)\n\n"
review += "**Nice to have, but not blocking.**\n\n"
for i, nit in enumerate(nits, 1):
review += f"{i}. **{nit.get('category', 'Style')}**: {nit.get('issue', 'Unknown')}\n"
if nit.get('file'):
review += f" - File: `{nit['file']}`\n"
if nit.get('details'):
review += f" - {nit['details']}\n"
review += "\n"
# Add suggestions
suggestions = findings.get('suggestions', [])
if suggestions:
review += "## 💡 Suggestions for Future\n\n"
for i, suggestion in enumerate(suggestions, 1):
review += f"{i}. {suggestion}\n"
review += "\n"
# Add questions
questions = findings.get('questions', [])
if questions:
review += "## ❓ Questions / Clarifications Needed\n\n"
for i, question in enumerate(questions, 1):
review += f"{i}. {question}\n"
review += "\n"
# Add praise
praise = findings.get('praise', [])
if praise:
review += "## ✅ Positive Notes\n\n"
for item in praise:
review += f"- {item}\n"
review += "\n"
# Add overall recommendation
review += "## Overall Recommendation\n\n"
if blockers:
review += "**Request Changes** - Critical issues must be addressed.\n"
elif important:
review += "**Request Changes** - Important issues should be fixed.\n"
else:
review += "**Approve** - Looks good! Minor nits can be addressed optionally.\n"
return review
def generate_human_review(findings: Dict[str, Any], metadata: Dict[str, Any]) -> str:
"""
Generate short, clean human.md for posting.
Rules:
- No emojis
- No em dashes (use regular hyphens)
- No code line numbers
- Concise and professional
"""
def clean_text(text: str) -> str:
"""Remove em-dashes and replace with regular hyphens."""
if not text:
return text
# Replace em dash (—) with regular hyphen (-)
# Also replace en dash (–) with regular hyphen
return text.replace('—', '-').replace('–', '-')
title = clean_text(metadata.get('title', 'N/A'))
summary = clean_text(findings.get('summary', 'No summary provided'))
review = f"""# Code Review
**PR #{metadata.get('number', 'N/A')}**: {title}
## Summary
{summary}
"""
# Add blockers - no emojis
blockers = findings.get('blockers', [])
if blockers:
review += "## Critical Issues - Must Fix\n\n"
for i, blocker in enumerate(blockers, 1):
# No emojis, no em dashes, no line numbers
issue = clean_text(blocker.get('issue', 'Issue'))
details = clean_text(blocker.get('details', 'No details'))
fix = clean_text(blocker.get('fix', ''))
review += f"{i}. **{issue}**\n"
if blocker.get('file'):
# File path without line number
review += f" - File: `{blocker['file']}`\n"
review += f" - {details}\n"
if fix:
review += f" - Fix: {fix}\n"
review += "\n"
# Add important issues
important = findings.get('important', [])
if important:
review += "## Important Issues - Should Fix\n\n"
for i, issue_item in enumerate(important, 1):
issue = clean_text(issue_item.get('issue', 'Issue'))
details = clean_text(issue_item.get('details', 'No details'))
fix = clean_text(issue_item.get('fix', ''))
review += f"{i}. **{issue}**\n"
if issue_item.get('file'):
review += f" - File: `{issue_item['file']}`\n"
review += f" - {details}\n"
if fix:
review += f" - Suggestion: {fix}\n"
review += "\n"
# Add nits - keep brief
nits = findings.get('nits', [])
if nits and len(nits) <= 3: # Only include if few
review += "## Minor Issues\n\n"
for i, nit in enumerate(nits, 1):
issue = clean_text(nit.get('issue', 'Issue'))
review += f"{i}. {issue}"
if nit.get('file'):
review += f" in `{nit['file']}`"
review += "\n"
review += "\n"
# Add praise
praise = findings.get('praise', [])
if praise:
review += "## Positive Notes\n\n"
for item in praise:
clean_item = clean_text(item)
review += f"- {clean_item}\n"
review += "\n"
# Add overall recommendation - no emojis
if blockers:
review += "## Recommendation\n\nRequest changes - critical issues need to be addressed before merging.\n"
elif important:
review += "## Recommendation\n\nRequest changes - please address the important issues listed above.\n"
else:
review += "## Recommendation\n\nApprove - the code looks good. Minor items can be addressed optionally.\n"
return review
def generate_inline_comments_file(findings: Dict[str, Any]) -> str:
"""
Generate inline.md with list of proposed inline comments.
Includes code snippets with line number headers.
"""
inline_comments = findings.get('inline_comments', [])
if not inline_comments:
return "# Inline Comments\n\nNo inline comments proposed.\n"
content = "# Proposed Inline Comments\n\n"
content += f"**Total Comments**: {len(inline_comments)}\n\n"
content += "Review these before posting. Edit as needed.\n\n"
content += "---\n\n"
for i, comment in enumerate(inline_comments, 1):
content += f"## Comment {i}\n\n"
content += f"**File**: `{comment.get('file', 'unknown')}`\n"
content += f"**Line**: {comment.get('line', 'N/A')}\n"
if comment.get('start_line') and comment.get('end_line'):
content += f"**Range**: Lines {comment['start_line']}-{comment['end_line']}\n"
content += f"\n**Comment**:\n{comment.get('comment', 'No comment')}\n\n"
if comment.get('code_snippet'):
# Add line numbers in header
start = comment.get('start_line', comment.get('line', 1))
end = comment.get('end_line', comment.get('line', 1))
if start == end:
content += f"**Code (Line {start})**:\n"
else:
content += f"**Code (Lines {start}-{end})**:\n"
content += f"```\n{comment['code_snippet']}\n```\n\n"
# Add command to post this comment
owner = comment.get('owner', 'OWNER')
repo = comment.get('repo', 'REPO')
pr_num = comment.get('pr_number', 'PR_NUM')
content += "**Command to post**:\n```bash\n"
content += f"python scripts/add_inline_comment.py {owner} {repo} {pr_num} latest \\\n"
content += f" \"{comment.get('file', 'file.py')}\" {comment.get('line', 42)} \\\n"
content += f" \"{comment.get('comment', 'comment')}\"\n"
content += "```\n\n"
content += "---\n\n"
return content
def generate_claude_commands(pr_review_dir: Path, metadata: Dict[str, Any]):
"""Generate .claude directory with custom slash commands."""
claude_dir = pr_review_dir / ".claude" / "commands"
claude_dir.mkdir(parents=True, exist_ok=True)
owner = metadata.get('owner', 'owner')
repo = metadata.get('repo', 'repo')
pr_number = metadata.get('number', '123')
# /send command - approve and post human.md
send_cmd = f"""Post the human-friendly review and approve the PR.
Steps:
1. Read the file `pr/human.md` in the current directory
2. Post the review content as a PR comment using:
`gh pr comment {pr_number} --repo {owner}/{repo} --body-file pr/human.md`
3. Approve the PR using:
`gh pr review {pr_number} --repo {owner}/{repo} --approve`
4. Confirm to the user that the review was posted and PR was approved
"""
with open(claude_dir / "send.md", 'w') as f:
f.write(send_cmd)
# /send-decline command - request changes and post human.md
send_decline_cmd = f"""Post the human-friendly review and request changes on the PR.
Steps:
1. Read the file `pr/human.md` in the current directory
2. Post the review content as a PR comment using:
`gh pr comment {pr_number} --repo {owner}/{repo} --body-file pr/human.md`
3. Request changes on the PR using:
`gh pr review {pr_number} --repo {owner}/{repo} --request-changes`
4. Confirm to the user that the review was posted and changes were requested
"""
with open(claude_dir / "send-decline.md", 'w') as f:
f.write(send_decline_cmd)
# /show command - open in VS Code
show_cmd = f"""Open the PR review directory in VS Code for editing.
Steps:
1. Run `code .` to open the current directory in VS Code
2. Tell the user they can now edit the review files:
- pr/review.md (detailed review)
- pr/human.md (short review for posting)
- pr/inline.md (inline comments)
3. Remind them to use /send or /send-decline when ready to post
"""
with open(claude_dir / "show.md", 'w') as f:
f.write(show_cmd)
print(f"✅ Created slash commands in {claude_dir}")
print(" - /send (approve and post)")
print(" - /send-decline (request changes and post)")
print(" - /show (open in VS Code)")
def main():
parser = argparse.ArgumentParser(
description='Generate structured review files from PR analysis',
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog=__doc__
)
parser.add_argument('pr_review_dir', help='PR review directory path')
parser.add_argument('--findings', required=True, help='JSON file with review findings')
parser.add_argument('--metadata', help='JSON file with PR metadata (optional)')
args = parser.parse_args()
try:
# Load findings
findings = load_findings(args.findings)
# Load metadata if provided
metadata = {}
if args.metadata and os.path.exists(args.metadata):
with open(args.metadata, 'r') as f:
metadata = json.load(f)
# Extract metadata from findings if not provided
if not metadata:
metadata = findings.get('metadata', {})
# Create pr directory
pr_review_dir = Path(args.pr_review_dir)
pr_dir = create_pr_directory(pr_review_dir)
print(f"📝 Generating review files in {pr_dir}...")
# Generate detailed review
detailed_review = generate_detailed_review(findings, metadata)
review_file = pr_dir / "review.md"
with open(review_file, 'w') as f:
f.write(detailed_review)
print(f"✅ Created detailed review: {review_file}")
# Generate human-friendly review
human_review = generate_human_review(findings, metadata)
human_file = pr_dir / "human.md"
with open(human_file, 'w') as f:
f.write(human_review)
print(f"✅ Created human review: {human_file}")
# Generate inline comments file
inline_comments = generate_inline_comments_file(findings)
inline_file = pr_dir / "inline.md"
with open(inline_file, 'w') as f:
f.write(inline_comments)
print(f"✅ Created inline comments: {inline_file}")
# Generate Claude slash commands
generate_claude_commands(pr_review_dir, metadata)
# Create summary file
summary = f"""PR Review Files Generated
========================
Directory: {pr_review_dir}
Files created:
- pr/review.md - Detailed analysis for your review
- pr/human.md - Clean version for posting (no emojis, no line numbers)
- pr/inline.md - Proposed inline comments with code snippets
Slash commands available:
- /send - Post human.md and approve PR
- /send-decline - Post human.md and request changes
- /show - Open directory in VS Code
Next steps:
1. Review the files (use /show to open in VS Code)
2. Edit as needed
3. Use /send or /send-decline when ready to post
IMPORTANT: Nothing will be posted until you run /send or /send-decline
"""
summary_file = pr_review_dir / "REVIEW_READY.txt"
with open(summary_file, 'w') as f:
f.write(summary)
print(f"\n{summary}")
except Exception as e:
print(f"Error: {e}", file=sys.stderr)
sys.exit(1)
if __name__ == '__main__':
main()