Files
review-bot/docs/DESIGN-vcs-abstraction.md
T
Rodin 3fc31c0822 docs: flip design — extract interfaces from working gitea/ code
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
2026-05-11 10:11:13 -07:00

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 --provider flag is fine)
  • Unifying into one abstraction layer for its own sake

Constraints

  1. Same features on both platforms — anything review-bot does on Gitea should work on GitHub
  2. Testable — small interfaces, dependency injection, no global state
  3. 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 (token vs Bearer)
  • 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.go with 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 helpers
  • github/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, DeleteReview
  • github/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|gitea flag (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.