- Goal: AI code reviews on GitHub with AI Core - Feature inventory with API mapping - Small interfaces (PRReader, FileReader, Reviewer, Identity) - Test plan: unit (mock HTTP) + integration (real GitHub) - 7 implementation phases with exit criteria Issue: #76
8.3 KiB
GitHub Support for review-bot
Goal
AI code reviews on GitHub PRs using SAP AI Core as the LLM provider.
Non-Goals
- Auto-detection of platform (explicit is fine)
- Single binary supporting both (two entry points OK)
- Unifying Gitea and GitHub into one abstraction layer
Constraints
- Same features on both platforms — anything review-bot does on Gitea should work on GitHub
- Testable — small interfaces, dependency injection, no global state
- Verified — the strat/review-bot github/ code was never tested; we build from scratch with tests
Part 1: Feature Inventory
What does review-bot actually do?
Core Review Flow
| Feature | Description | Used In |
|---|---|---|
| Get PR metadata | Title, body, head SHA, base ref | Review context |
| Get PR diff | Unified diff format | LLM input |
| Get PR files | List of changed files with status | Filtering, context |
| Get file content | Raw file at ref | Conventions, patterns |
| List directory | Enumerate files in path | Pattern repos |
| Post review | Body + inline comments + verdict | Output |
Review Management
| Feature | Description | Used In |
|---|---|---|
| List reviews | Get existing reviews on PR | Supersede check |
| Delete review | Remove old review before re-posting | Supersede flow |
| Get authenticated user | Who am I? | Filter own reviews |
Optional/Extended
| Feature | Description | Used In |
|---|---|---|
| Resolve comment | Mark inline comment resolved | Gitea-specific |
| Edit comment | Update existing comment | Supersede variant |
| Request reviewer | Add reviewer to PR | Workflow integration |
| Get commit statuses | CI status | Gate logic |
Part 2: GitHub API Mapping
Supported
| Feature | Gitea API | GitHub API | Notes |
|---|---|---|---|
| Get PR | GET /api/v1/repos/{owner}/{repo}/pulls/{n} |
GET /repos/{owner}/{repo}/pulls/{n} |
Same structure |
| Get diff | GET .../pulls/{n}.diff |
GET .../pulls/{n} + Accept: application/vnd.github.diff |
Different mechanism |
| Get files | GET .../pulls/{n}/files |
GET .../pulls/{n}/files |
Same |
| Get file content | GET .../raw/{path}?ref= |
GET .../contents/{path}?ref= + base64 decode |
Different format |
| List directory | GET .../contents/{path} |
GET .../contents/{path} |
Same |
| Post review | POST .../pulls/{n}/reviews |
POST .../pulls/{n}/reviews |
Minor field differences |
| List reviews | GET .../pulls/{n}/reviews |
GET .../pulls/{n}/reviews |
Same |
| Delete review | DELETE .../pulls/{n}/reviews/{id} |
DELETE .../pulls/{n}/reviews/{id} |
Same |
| Get user | GET /api/v1/user |
GET /user |
Same |
Gaps
| Feature | Gitea | GitHub | Workaround |
|---|---|---|---|
| Resolve comment | POST .../pulls/comments/{id}/resolve |
No equivalent | Skip (document limitation) |
| Timeline API | GET .../issues/{n}/timeline |
No equivalent | Use events API or skip |
Part 3: Interface Design
Small, role-based interfaces. Test what you use.
// pr.go — PR operations
type PRReader interface {
GetPullRequest(ctx context.Context, owner, repo string, number int) (*PullRequest, error)
GetPullRequestDiff(ctx context.Context, owner, repo string, number int) (string, error)
GetPullRequestFiles(ctx context.Context, owner, repo string, number int) ([]ChangedFile, error)
}
// files.go — File/content operations
type FileReader interface {
GetFileContent(ctx context.Context, owner, repo, path, ref string) (string, error)
ListContents(ctx context.Context, owner, repo, path string) ([]ContentEntry, error)
}
// review.go — Review operations
type Reviewer interface {
PostReview(ctx context.Context, owner, repo string, number int, req ReviewRequest) (*Review, error)
ListReviews(ctx context.Context, owner, repo string, number int) ([]Review, error)
DeleteReview(ctx context.Context, owner, repo string, number int, reviewID int64) error
}
// identity.go — Auth/identity
type Identity interface {
GetAuthenticatedUser(ctx context.Context) (string, error)
}
Composite
// Client combines all capabilities for callers that need everything
type Client interface {
PRReader
FileReader
Reviewer
Identity
}
Types (shared)
type PullRequest struct {
Number int
Title string
Body string
HeadSHA string
HeadRef string
BaseRef string
}
type ChangedFile struct {
Filename string
Status string // added, modified, removed, renamed
}
type ContentEntry struct {
Name string
Path string
Type string // file, dir
}
type ReviewRequest struct {
Event string // APPROVE, REQUEST_CHANGES, COMMENT
Body string
Comments []ReviewComment
}
type ReviewComment struct {
Path string
Line int
Body string
}
type Review struct {
ID int64
User string
State string
Body string
}
Part 4: Test Plan
Unit Tests (mock HTTP)
Each interface method gets a test against a mock HTTP server.
github/
pr_test.go # TestGetPullRequest, TestGetDiff, TestGetFiles
files_test.go # TestGetFileContent, TestListContents
review_test.go # TestPostReview, TestListReviews, TestDeleteReview
identity_test.go # TestGetAuthenticatedUser
Test cases per method:
- Happy path
- 404 not found
- 401 unauthorized
- Rate limited (429)
- Malformed response
Integration Tests (real GitHub)
Against github.com/aweiker/ai-core-review-bot:
integration/
github_test.go # +build integration
Requires GITHUB_TOKEN env var. Run manually or in CI with secrets.
| Test | Verifies |
|---|---|
| Fetch PR #1 | PRReader works |
| Fetch README.md | FileReader works |
| Post + delete draft review | Reviewer works (clean up after) |
| Get authenticated user | Identity works |
End-to-End Test
Open a real PR on the test repo, run review-bot against it, verify review appears.
Part 5: Implementation Phases
Phase 1: Types + Interfaces
Files:
vcs/types.go— shared typesvcs/interfaces.go— PRReader, FileReader, Reviewer, Identity, Client
Exit criteria: Compiles. No implementation yet.
PR: #1 on feature branch
Phase 2: GitHub Client — PRReader
Files:
github/client.go— struct, constructor, HTTP helpersgithub/pr.go— GetPullRequest, GetPullRequestDiff, GetPullRequestFilesgithub/pr_test.go— unit tests
Exit criteria: go test ./github/... passes for PR methods.
PR: #2
Phase 3: GitHub Client — FileReader
Files:
github/files.go— GetFileContent, ListContentsgithub/files_test.go
Exit criteria: Unit tests pass.
PR: #3
Phase 4: GitHub Client — Reviewer + Identity
Files:
github/review.go— PostReview, ListReviews, DeleteReviewgithub/identity.go— GetAuthenticatedUsergithub/review_test.go,github/identity_test.go
Exit criteria: Unit tests pass.
PR: #4
Phase 5: Integration Tests
Files:
integration/github_test.go
Exit criteria: All integration tests pass against real GitHub.
PR: #5
Phase 6: Wire into cmd/review-bot
Files:
cmd/review-bot/main.go— add--provider githubflag- Update to use vcs interfaces
Exit criteria:
./review-bot --provider github ...works- Existing Gitea path unchanged
PR: #6
Phase 7: GitHub Actions Workflow
Files:
.github/workflows/ci.yml.github/workflows/review.yml.github/actions/review/action.yml
Exit criteria: CI runs on github.com/aweiker/ai-core-review-bot, review posts successfully.
PR: #7
Part 6: Open Questions
-
Auth token format — GitHub PAT vs fine-grained token vs GitHub App? Start with PAT.
-
Rate limiting — GitHub has stricter limits. Add retry with backoff? (Already have this for Gitea.)
-
Pagination — GitHub paginates at 100. Do we need >100 files/reviews? Probably not for MVP.
-
Comment threading — GitHub review comments work differently. Test and document.
-
Draft reviews — Use for integration tests to avoid noise?
Summary
7 phases, each with clear exit criteria and a PR. Build from types up, test at each layer, integrate last.