Key changes: - Interface discovered from gitea/, not invented - Gitea adapter first (Phase 1-2), GitHub second (Phase 3-5) - Removed 'Open Questions' — all resolved: - Token: workflow GITHUB_TOKEN - Binary: GitHub releases on aweiker/ai-core-review-bot - Comment schema: adapter responsibility - 8 phases with clear exit criteria - Platform-specific features (resolve, timeline) stay on concrete client Issue: #76
7.4 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
--providerflag is fine) - Unifying into one abstraction layer for its own sake
Constraints
- Same features on both platforms — anything review-bot does on Gitea should work on GitHub
- Testable — small interfaces, dependency injection, no global state
- Interface from working code — extract from gitea/, don't invent in vacuum
Part 1: Feature Inventory
What does review-bot actually do?
Core Review Flow
| Feature | Description |
|---|---|
| Get PR metadata | Title, body, head SHA, base ref |
| Get PR diff | Unified diff format |
| Get PR files | List of changed files with status |
| Get file content | Raw file at ref |
| List directory | Enumerate files in path |
| Post review | Body + inline comments + verdict |
Review Management
| Feature | Description |
|---|---|
| List reviews | Get existing reviews on PR |
| Delete review | Remove old review before re-posting |
| Get authenticated user | Who am I? |
Platform-Specific (not in shared interface)
| Feature | Gitea | GitHub |
|---|---|---|
| Resolve comment | Yes | No equivalent |
| Timeline API | Yes | No equivalent |
These stay on gitea.Client directly. Callers that need them type-assert.
Part 2: GitHub API Mapping
| Feature | Gitea API | GitHub API |
|---|---|---|
| Get PR | GET /api/v1/repos/.../pulls/{n} |
GET /repos/.../pulls/{n} |
| Get diff | .diff suffix |
Accept: application/vnd.github.diff header |
| Get files | GET .../pulls/{n}/files |
Same |
| Get file content | GET .../raw/{path}?ref= |
GET .../contents/{path}?ref= + base64 decode |
| List directory | GET .../contents/{path} |
Same |
| Post review | POST .../pulls/{n}/reviews |
Same (adapter handles comment schema) |
| List reviews | GET .../pulls/{n}/reviews |
Same |
| Delete review | DELETE .../pulls/{n}/reviews/{id} |
Same |
| Get user | GET /api/v1/user |
GET /user |
Part 3: Interface Design
Principle: Extract from working gitea/ code. The interface is discovered, not invented.
Small, role-based interfaces
// vcs/interfaces.go
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)
}
type FileReader interface {
GetFileContent(ctx context.Context, owner, repo, path, ref string) (string, error)
ListContents(ctx context.Context, owner, repo, path string) ([]ContentEntry, error)
}
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
}
type Identity interface {
GetAuthenticatedUser(ctx context.Context) (string, error)
}
// Client combines all for callers that need everything
type Client interface {
PRReader
FileReader
Reviewer
Identity
}
Types
Use what gitea/ already has. Move to vcs/types.go or re-export.
type PullRequest struct { ... } // from gitea.PullRequest
type ChangedFile struct { ... } // from gitea.ChangedFile
type ContentEntry struct { ... } // from gitea.ContentEntry
type Review struct { ... } // from gitea.Review
type ReviewRequest struct { ... } // new, for PostReview input
type ReviewComment struct { ... } // from gitea.ReviewComment
Adapter responsibilities
Each adapter (gitea, github) handles:
- API URL construction
- Auth header format (
tokenvsBearer) - Request/response mapping
- Comment schema translation (line numbers, commit IDs, etc.)
Part 4: Test Plan
Unit Tests (mock HTTP)
github/
pr_test.go # TestGetPullRequest, TestGetDiff, TestGetFiles
files_test.go # TestGetFileContent, TestListContents
review_test.go # TestPostReview, TestListReviews, TestDeleteReview
identity_test.go # TestGetAuthenticatedUser
Per method: happy path, 404, 401, 429, malformed response.
Integration Tests
Against github.com/aweiker/ai-core-review-bot:
- Fetch real PR
- Fetch real file
- Post + delete review (clean up)
End-to-End
Open PR on test repo, run full review-bot, verify review appears.
Part 5: Implementation Phases
Phase 1: Extract interfaces from gitea/
Work:
- Create
vcs/interfaces.gowith interfaces extracted from gitea/client.go signatures - Create
vcs/types.go— move or alias types from gitea/ - Verify gitea.Client satisfies vcs.Client (compile-time check)
Exit criteria: var _ vcs.Client = (*gitea.Client)(nil) compiles.
Phase 2: Gitea adapter (if needed)
Work:
- If gitea.Client method signatures don't match exactly, create wrapper
- Keep gitea/ working exactly as before
Exit criteria: Existing tests pass. No behavior change.
Phase 3: GitHub client — PRReader
Work:
github/client.go— struct, constructor, HTTP helpersgithub/pr.go— GetPullRequest, GetPullRequestDiff, GetPullRequestFiles- Unit tests
Exit criteria: go test ./github/... passes for PR methods.
Phase 4: GitHub client — FileReader
Work:
github/files.go— GetFileContent, ListContents- Unit tests
Exit criteria: Unit tests pass.
Phase 5: GitHub client — Reviewer + Identity
Work:
github/review.go— PostReview, ListReviews, DeleteReviewgithub/identity.go— GetAuthenticatedUser- Unit tests
Exit criteria: Unit tests pass.
Phase 6: Integration tests
Work:
integration/github_test.go- Test against real GitHub
Exit criteria: All integration tests pass.
Phase 7: Wire into cmd/review-bot
Work:
- Add
--provider github|giteaflag (default: gitea for backward compat) - Select client based on flag
- Update to use vcs interfaces where it makes sense
Exit criteria:
./review-bot --provider github ...works./review-bot --provider gitea ...works (same as before)- Existing Gitea workflows unchanged
Phase 8: GitHub Actions workflow + releases
Work:
.github/workflows/ci.yml— test on PR.github/workflows/release.yml— publish binary to GitHub releases.github/actions/review/action.yml— composite action- Action downloads binary from github.com/aweiker/ai-core-review-bot releases
Exit criteria:
- CI runs on github.com/aweiker/ai-core-review-bot
- Release creates downloadable binary
- Review action posts review successfully
Part 6: Decisions
| Question | Decision |
|---|---|
| Auth token | Workflow GITHUB_TOKEN (automatic) |
| Binary distribution | GitHub releases on aweiker/ai-core-review-bot |
| Comment schema | Adapter's job — translate ReviewComment to platform format |
| Default provider | gitea for backward compatibility |
| Shared types | vcs/types.go (extracted from gitea/) |
| Platform-specific features | Stay on concrete client, not interface |
Summary
8 phases. Start by extracting interfaces from working gitea/ code, not inventing them. GitHub implements the same interfaces. Each phase has clear exit criteria.