|
| 1 | +# Better Reviewers |
| 2 | + |
| 3 | +A Go program that intelligently finds and assigns reviewers for GitHub pull requests based on code context and reviewer activity. |
| 4 | + |
| 5 | +## Features |
| 6 | + |
| 7 | +- **Context-based reviewer selection**: Finds reviewers who have previously worked on the lines being changed |
| 8 | +- **Activity-based reviewer selection**: Identifies reviewers who have been active on similar files |
| 9 | +- **Configurable time constraints**: Only processes PRs within specified age ranges |
| 10 | +- **Dry-run mode**: Test the logic without actually assigning reviewers |
| 11 | +- **Polling support**: Continuously monitor repositories for new PRs |
| 12 | +- **Comprehensive logging**: Detailed logs to understand reviewer selection decisions |
| 13 | + |
| 14 | +## Installation |
| 15 | + |
| 16 | +```bash |
| 17 | +go build -o better-reviewers |
| 18 | +``` |
| 19 | + |
| 20 | +## Prerequisites |
| 21 | + |
| 22 | +- Go 1.21 or later |
| 23 | +- GitHub CLI (`gh`) installed and authenticated |
| 24 | +- GitHub token with appropriate permissions (repo access) |
| 25 | + |
| 26 | +## Usage |
| 27 | + |
| 28 | +### Single PR Analysis |
| 29 | + |
| 30 | +```bash |
| 31 | +./better-reviewers -pr "https://github.com/owner/repo/pull/123" |
| 32 | +./better-reviewers -pr "owner/repo#123" |
| 33 | +``` |
| 34 | + |
| 35 | +### Project Monitoring |
| 36 | + |
| 37 | +```bash |
| 38 | +./better-reviewers -project "owner/repo" |
| 39 | +``` |
| 40 | + |
| 41 | +### Organization Monitoring (Coming Soon) |
| 42 | + |
| 43 | +```bash |
| 44 | +./better-reviewers -org "myorg" |
| 45 | +``` |
| 46 | + |
| 47 | +### Polling Mode |
| 48 | + |
| 49 | +```bash |
| 50 | +./better-reviewers -project "owner/repo" -poll 1h |
| 51 | +``` |
| 52 | + |
| 53 | +### Dry Run Mode |
| 54 | + |
| 55 | +```bash |
| 56 | +./better-reviewers -pr "owner/repo#123" -dry-run |
| 57 | +``` |
| 58 | + |
| 59 | +## Command Line Options |
| 60 | + |
| 61 | +### Target Flags (Mutually Exclusive) |
| 62 | + |
| 63 | +- `-pr`: Pull request URL or shorthand (e.g., `https://github.com/owner/repo/pull/123` or `owner/repo#123`) |
| 64 | +- `-project`: GitHub project to monitor (e.g., `owner/repo`) |
| 65 | +- `-org`: GitHub organization to monitor (not yet implemented) |
| 66 | + |
| 67 | +### Behavior Flags |
| 68 | + |
| 69 | +- `-poll`: Polling interval (e.g., `1h`, `30m`). If not set, runs once |
| 70 | +- `-dry-run`: Run in dry-run mode (no actual reviewer assignments) |
| 71 | +- `-min-age`: Minimum time since last commit or review for PR assignment (default: 1h) |
| 72 | +- `-max-age`: Maximum time since last commit or review for PR assignment (default: 180 days) |
| 73 | + |
| 74 | +## How It Works |
| 75 | + |
| 76 | +### Reviewer Selection Algorithm |
| 77 | + |
| 78 | +The program finds exactly two reviewers for each PR: a **primary reviewer** (with author context) and a **secondary reviewer** (who actively reviews code). |
| 79 | + |
| 80 | +#### Primary Reviewer (Author Context) |
| 81 | + |
| 82 | +The primary reviewer is selected based on who knows the code best, in this priority order: |
| 83 | + |
| 84 | +1. **Blame-based Authors**: |
| 85 | + - Examines GitHub blame history for changed files |
| 86 | + - Considers the top 5 PRs by overlap with edited lines |
| 87 | + - Selects authors of these PRs who still have write access |
| 88 | + - Verifies write access via author_association |
| 89 | + |
| 90 | +2. **Directory Author** (fallback): |
| 91 | + - Most recent author of a merged PR in the same directory |
| 92 | + |
| 93 | +3. **Project Author** (fallback): |
| 94 | + - Most recent author of a merged PR in the project |
| 95 | + |
| 96 | +#### Secondary Reviewer (Active Reviewer) |
| 97 | + |
| 98 | +The secondary reviewer is selected based on review activity, in this priority order: |
| 99 | + |
| 100 | +1. **Blame-based Reviewers**: |
| 101 | + - Examines the same GitHub blame history |
| 102 | + - Considers the top 5 PRs by overlap with edited lines |
| 103 | + - Selects reviewers/approvers of these PRs |
| 104 | + |
| 105 | +2. **Directory Reviewer** (fallback): |
| 106 | + - Most recent reviewer of a merged PR in the same directory |
| 107 | + |
| 108 | +3. **Project Reviewer** (fallback): |
| 109 | + - Most recent reviewer of a merged PR in the project |
| 110 | + |
| 111 | +### Assignment Rules |
| 112 | + |
| 113 | +- Always attempts to assign exactly 2 reviewers: primary and secondary |
| 114 | +- The PR author cannot be a reviewer |
| 115 | +- Primary and secondary must be different people |
| 116 | +- Each reviewer selection logs the mechanism used (e.g., "primary-blame-author", "secondary-directory-reviewer") |
| 117 | +- **Draft PRs**: Skips reviewer assignment for draft PRs but logs who would have been assigned |
| 118 | + |
| 119 | +### Error Handling |
| 120 | + |
| 121 | +- If after exhausting all fallback mechanisms, the only candidate found is the PR author, the program will error with a clear message |
| 122 | +- This ensures that the PR author is never assigned as their own reviewer |
| 123 | +- The error message will indicate that all candidates have been exhausted |
| 124 | + |
| 125 | +### GitHub API Usage |
| 126 | + |
| 127 | +The program uses: |
| 128 | +- GitHub REST API v3 for PR data, file changes, and reviews |
| 129 | +- GitHub GraphQL API v4 for blame data and efficient directory/project searches |
| 130 | +- **No longer uses the slow Search API** - replaced with GraphQL queries |
| 131 | +- Caches blame data to avoid redundant API calls |
| 132 | +- 120-second timeout for API calls to handle slow responses |
| 133 | +- Retry logic with exponential backoff for GraphQL requests (up to 3 attempts) |
| 134 | +- Proper authentication using `gh auth token` for all API calls |
| 135 | + |
| 136 | +## Architecture |
| 137 | + |
| 138 | +The codebase is organized into several key files: |
| 139 | + |
| 140 | +- `main.go`: Command-line interface and main program logic |
| 141 | +- `github.go`: GitHub API client implementation |
| 142 | +- `reviewer.go`: Core reviewer finding and assignment logic |
| 143 | +- `analysis.go`: Blame data analysis and PR relationship detection |
| 144 | +- `main_test.go`: Unit tests for core functionality |
| 145 | + |
| 146 | +## Testing |
| 147 | + |
| 148 | +Run the test suite: |
| 149 | + |
| 150 | +```bash |
| 151 | +go test -v |
| 152 | +``` |
| 153 | + |
| 154 | +## Example Output |
| 155 | + |
| 156 | +### Successful Primary/Secondary Selection |
| 157 | +``` |
| 158 | +2024/01/15 10:30:45 Processing PR owner/repo#123: Add new feature |
| 159 | +2024/01/15 10:30:45 Analyzing 3 changed files for PR 123 |
| 160 | +2024/01/15 10:30:46 === Finding PRIMARY reviewer (author context) === |
| 161 | +2024/01/15 10:30:46 Checking blame-based authors for primary reviewer |
| 162 | +2024/01/15 10:30:46 Found author alice from PR 89 with 25 line overlap |
| 163 | +2024/01/15 10:30:46 Found author charlie from PR 76 with 15 line overlap |
| 164 | +2024/01/15 10:30:46 Selected blame-based author: alice (score: 25, association: MEMBER) |
| 165 | +2024/01/15 10:30:47 === Finding SECONDARY reviewer (active reviewer) === |
| 166 | +2024/01/15 10:30:47 Checking blame-based reviewers for secondary reviewer |
| 167 | +2024/01/15 10:30:47 Found reviewer bob from PR 89 with 25 line overlap |
| 168 | +2024/01/15 10:30:47 Found reviewer dave from PR 76 with 15 line overlap |
| 169 | +2024/01/15 10:30:47 Selected blame-based reviewer: bob (score: 25) |
| 170 | +2024/01/15 10:30:47 PRIMARY reviewer selected: alice (method: primary-blame-author) |
| 171 | +2024/01/15 10:30:47 SECONDARY reviewer selected: bob (method: secondary-blame-reviewer) |
| 172 | +2024/01/15 10:30:47 Found 2 reviewer candidates for PR 123 |
| 173 | +2024/01/15 10:30:47 Adding reviewers [alice bob] to PR owner/repo#123 |
| 174 | +2024/01/15 10:30:48 Successfully added reviewers [alice bob] to PR 123 |
| 175 | +``` |
| 176 | + |
| 177 | +### Fallback Mechanism in Action |
| 178 | +``` |
| 179 | +2024/01/15 10:31:00 Processing PR owner/repo#124: Fix minor bug |
| 180 | +2024/01/15 10:31:00 Analyzing 3 changed files for PR 124 |
| 181 | +2024/01/15 10:31:01 === Finding PRIMARY reviewer (author context) === |
| 182 | +2024/01/15 10:31:01 Checking blame-based authors for primary reviewer |
| 183 | +2024/01/15 10:31:01 No blame-based authors found, checking directory authors |
| 184 | +2024/01/15 10:31:02 PRIMARY reviewer selected: charlie (method: primary-directory-author) |
| 185 | +2024/01/15 10:31:02 === Finding SECONDARY reviewer (active reviewer) === |
| 186 | +2024/01/15 10:31:02 Checking blame-based reviewers for secondary reviewer |
| 187 | +2024/01/15 10:31:02 No blame-based reviewers found, checking directory reviewers |
| 188 | +2024/01/15 10:31:03 No directory reviewers found, checking project reviewers |
| 189 | +2024/01/15 10:31:03 SECONDARY reviewer selected: dave (method: secondary-project-reviewer) |
| 190 | +2024/01/15 10:31:03 Found 2 reviewer candidates for PR 124 |
| 191 | +2024/01/15 10:31:03 Adding reviewers [charlie dave] to PR owner/repo#124 |
| 192 | +2024/01/15 10:31:03 Successfully added reviewers [charlie dave] to PR 124 |
| 193 | +``` |
| 194 | + |
| 195 | +### Error Case: Only PR Author Found |
| 196 | +``` |
| 197 | +2024/01/15 10:32:00 Processing PR owner/repo#125: Initial commit |
| 198 | +2024/01/15 10:32:00 Top changed files for PR 125: [main.go, go.mod, README.md] |
| 199 | +2024/01/15 10:32:01 Finding context reviewers using blame data for 3 files |
| 200 | +2024/01/15 10:32:01 Finding activity reviewers for 3 files |
| 201 | +2024/01/15 10:32:01 Found only 0 candidates, trying fallback to line authors |
| 202 | +2024/01/15 10:32:01 Finding line authors with write access for fallback |
| 203 | +2024/01/15 10:32:01 Line author fallback candidate: john-doe (lines: 50, association: OWNER, method: fallback-line-author) |
| 204 | +2024/01/15 10:32:02 Found only 1 candidates, trying fallback to recent file authors |
| 205 | +2024/01/15 10:32:02 Found only 1 candidates, trying directory-based fallbacks |
| 206 | +2024/01/15 10:32:02 Found only 1 candidates, trying project-wide fallbacks |
| 207 | +2024/01/15 10:32:03 Project author fallback candidate: john-doe (method: fallback-project-author) |
| 208 | +2024/01/15 10:32:03 Failed to find reviewer candidates: exhausted all reviewer candidates: the only candidate found was the PR author (john-doe) |
| 209 | +``` |
| 210 | + |
| 211 | +### Draft PR Handling |
| 212 | +``` |
| 213 | +2024/01/15 10:33:00 Processing PR owner/repo#126 [DRAFT]: WIP: New feature |
| 214 | +2024/01/15 10:33:00 Top changed files for PR 126: [feature.go, feature_test.go] |
| 215 | +2024/01/15 10:33:01 Finding context reviewers using blame data for 2 files |
| 216 | +2024/01/15 10:33:01 Context reviewer candidate: alice (score: 20, method: context-blame-approver) |
| 217 | +2024/01/15 10:33:01 Activity reviewer candidate: bob (PR size: 120, method: activity-recent-approver) |
| 218 | +2024/01/15 10:33:01 Found 2 reviewer candidates for PR 126 |
| 219 | +2024/01/15 10:33:01 Selected reviewer: alice (method: context-blame-approver, context score: 20, activity score: 0) |
| 220 | +2024/01/15 10:33:01 Selected reviewer: bob (method: activity-recent-approver, context score: 0, activity score: 120) |
| 221 | +2024/01/15 10:33:01 PR 126 is a draft - skipping reviewer assignment |
| 222 | +2024/01/15 10:33:01 Would have assigned reviewers [alice bob] to PR 126 if it wasn't a draft |
| 223 | +``` |
| 224 | + |
| 225 | +## Contributing |
| 226 | + |
| 227 | +1. Follow Go best practices and code review guidelines from go.dev |
| 228 | +2. Add tests for new functionality |
| 229 | +3. Ensure comprehensive logging for debugging |
| 230 | +4. Use minimal external dependencies |
| 231 | + |
| 232 | +## License |
| 233 | + |
| 234 | +This project is provided as-is for educational and productivity purposes. |
0 commit comments