roborev is an automatic code review daemon for git commits. It runs locally, triggered by post-commit hooks, and uses AI agents (Codex, Claude Code, Gemini, Copilot, etc.) to review commits in parallel. It also supports background fix jobs, CI integration via GitHub PRs, and PostgreSQL sync for multi-machine setups.
When a task involves multiple steps (e.g., implement + commit + PR), complete ALL steps in sequence without stopping. If creating a branch, committing, and opening a PR, finish the entire chain.
After making any Go code changes, always run go fmt ./... and go vet ./... before committing. Stage ALL resulting changes, including formatting-only files.
Always commit after completing each piece of work — do not wait to be asked. When committing changes, always stage ALL modified files (including formatting, generated files, and ancillary changes). Run git diff and git status before committing to ensure nothing is left unstaged.
CLI (roborev) → HTTP API → Daemon (roborev daemon run) → Worker Pool → Agents
↓ ↓
SQLite DB ←──sync──→ PostgreSQL (optional)
↓
CI Poller → GitHub PRs
- Daemon: HTTP server on port 7373 (auto-finds available port if busy)
- Workers: Pool of 4 (configurable) parallel review workers
- Storage: SQLite at
~/.roborev/reviews.dbwith WAL mode - Config: Global at
~/.roborev/config.toml, per-repo at.roborev.toml - Data dir: Set
ROBOREV_DATA_DIRenv var to override~/.roborev - Runtime info: Daemon writes PID/addr/port to
~/.roborev/daemon.json
| Package | Purpose |
|---|---|
cmd/roborev/ |
CLI entry point, 25+ Cobra subcommands |
cmd/roborev/tui/ |
Bubbletea terminal UI (queue/review/task views) |
internal/agent/ |
Agent interface, registry, 11 implementations |
internal/config/ |
Config structs, loading, 20+ Resolve* functions |
internal/daemon/ |
HTTP server, worker pool, CI poller, hooks, SSE |
internal/storage/ |
SQLite + PostgreSQL, schema migrations, job CRUD |
internal/prompt/ |
Review prompt construction, system prompts |
internal/git/ |
Git operations (diff, commit info, branches) |
internal/worktree/ |
Isolated git worktrees for fix jobs |
internal/review/ |
Synthesis, batch processing, verdict parsing |
internal/github/ |
GitHub REST API wrappers |
internal/githook/ |
Git hook installation/management |
internal/ghaction/ |
GitHub Actions integration |
internal/skills/ |
Agent skill discovery and management |
internal/streamfmt/ |
Streaming output formatting |
internal/testutil/ |
Test helpers (TestRepo, HTTP fixtures) |
internal/testenv/ |
Test environment setup |
internal/update/ |
Self-update mechanics |
internal/version/ |
Version string (injected at build time) |
| Path | Purpose |
|---|---|
cmd/roborev/main.go |
CLI entry point, all Cobra commands |
internal/daemon/server.go |
HTTP API routes and handlers (~2000 lines) |
internal/daemon/worker.go |
Worker pool, job processing, retry/failover |
internal/daemon/ci_poller.go |
GitHub PR polling, synthesis, comment posting |
internal/storage/db.go |
SQLite schema definition, 18 migrations |
internal/storage/jobs.go |
Job CRUD, state transitions, verdict parsing |
internal/storage/models.go |
Core types: ReviewJob, Repo, Commit, Review |
internal/storage/postgres.go |
PostgreSQL schema (v1-v6) and operations |
internal/storage/sync.go |
Sync state management (cursors, machine IDs) |
internal/agent/agent.go |
Agent interface, registry, alias resolution |
internal/config/config.go |
Config/RepoConfig structs, Resolve* functions |
internal/prompt/prompt.go |
Prompt builder (single, range, dirty) |
internal/worktree/worktree.go |
Worktree create/patch-capture/apply |
internal/review/synthesis.go |
Multi-agent review synthesis for CI |
type Agent interface {
Name() string
Review(ctx context.Context, repoPath, commitSHA, prompt string, output io.Writer) (string, error)
WithReasoning(level ReasoningLevel) Agent
WithAgentic(agentic bool) Agent
WithModel(model string) Agent
CommandLine() string
}codex, claude-code, gemini, copilot, opencode, cursor, kiro, kilo, droid, pi, test
"claude"→"claude-code""agent"→"cursor"
Agents are discovered via PATH lookup (CommandAgent.CommandName()). The test agent is always available. GetAvailable(preferred) walks a fallback cascade: codex → claude-code → gemini → copilot → opencode → cursor → kiro → kilo → droid → pi.
ReasoningFast ("fast"/"low"), ReasoningStandard ("standard"/"medium"), ReasoningThorough ("thorough"/"high")
- Create
internal/agent/newagent.go - Implement the
Agentinterface - Call
Register()ininit()
repos: id, root_path (UNIQUE), name, created_at
commits: id, repo_id → repos, sha (UNIQUE per repo), author, subject, timestamp
review_jobs (48 columns): Core job state including agent, model, reasoning, status, timestamps, worker tracking, retry_count, prompt, diff_content, output_prefix, job_type, review_type, parent_job_id, patch, and sync fields (uuid, source_machine_id, updated_at, synced_at).
reviews: id, job_id → review_jobs (UNIQUE), agent, prompt, output, closed (bool), verdict_bool, sync fields
responses: id, job_id (or commit_id for legacy), responder, response, sync fields
ci_pr_batches: Tracks CI review batches per PR (total/completed/failed/synthesized jobs)
ci_pr_batch_jobs: Links batches to individual review jobs
queued → running → done | failed | canceled | applied | rebased
review (single commit), range (commit range), dirty (uncommitted), task (custom prompt), compact (consolidated multi-agent), fix (background worktree fix)
Empty/"review" (standard), "security", "design" — changes the system prompt
18 incremental migrations in db.go handle column additions, CHECK constraint updates, and table rebuilds. Each migration runs idempotically.
- CLI flags (
--agent,--model, etc.) - Per-repo
.roborev.toml(RepoConfig) - Global
~/.roborev/config.toml(Config) - Hardcoded defaults
| Function | Purpose |
|---|---|
ResolveAgent() |
CLI → repo → global → default |
ResolveModel() |
Same chain for model selection |
ResolveAgentForWorkflow(cli, repoPath, globalCfg, workflow, level) |
Workflow + reasoning-level routing |
ResolveModelForWorkflow(cli, repoPath, globalCfg, workflow, level) |
Same for models |
ResolveBackupAgentForWorkflow(repoPath, globalCfg, workflow) |
Failover agent: repo workflow → repo generic → global workflow → global default |
ResolveBackupModelForWorkflow(repoPath, globalCfg, workflow) |
Same chain for backup models |
ResolveJobTimeout() |
Per-repo or global (default 30 min) |
ResolveMaxPromptSize() |
Prompt truncation threshold |
ResolveReviewReasoning() / RefineReasoning() / FixReasoning() |
Default reasoning level per workflow |
Fields follow the naming {Workflow}{Setting}{Level}:
- Workflows: Review, Refine, Fix, Security, Design
- Settings: Agent, Model, BackupAgent, BackupModel
- Levels (agent/model only): Fast, Standard, Thorough
TOML tags: review_agent_fast, fix_model_thorough, security_backup_agent, etc.
The internal lookupFieldByTag() helper resolves these via reflection on the struct's TOML tags.
agent, model, backup_agent, backup_model, review_context_count, review_guidelines, job_timeout_minutes, max_prompt_size, allow_unsafe_agents, per-workflow agent/model/backup overrides, hooks config
- Worker goroutine calls
db.ClaimJob(workerID)— atomically sets status=running - Registers job for cancellation tracking (
registerRunningJob) - Checks agent cooldown (quota exhaustion)
- Builds prompt via
prompt.Builder(or uses stored prompt for task/compact/fix jobs) - Gets agent via
agent.GetAvailableWithConfig(), applies reasoning/model/agentic settings - For fix jobs: creates isolated worktree via
worktree.Create() - Invokes
Agent.Review()with streaming output capture - For fix jobs: captures patch via
worktree.CapturePatch() - Stores result via
db.CompleteJob()ordb.CompleteFixJob() - Broadcasts
review.completedevent, fires hooks
- Retries: Up to 3 retries for transient failures (
db.RetryJob). Resets status to queued. - Failover: After retries exhausted (or on quota errors), switches to backup agent via
db.FailoverJob. Resets retry_count, sets backup agent/model. - Cooldown: Quota exhaustion errors (
isQuotaError) trigger per-agent cooldown (default 30 min, parsed from error message). Cooldowns are tracked in-memory with RWMutex.
failoverWorkflow(job) maps job kind to config workflow key:
- Fix jobs (
job.IsFixJob()) →"fix" - Non-default ReviewType (security, design) → that review type
- Everything else →
"review"
Both resolveBackupAgent and resolveBackupModel use this shared helper.
Race-safe 3-phase check: running map → DB lookup → pending cancels set. Test hooks (testHookAfterSecondCheck, testHookCooldownLockUpgrade) allow deterministic synchronization in tests.
All endpoints prefixed with /api/:
| Endpoint | Method | Purpose |
|---|---|---|
/enqueue |
POST | Queue review job |
/jobs |
GET | List/filter jobs (supports repo_id, status, branch, job_type filters) |
/job/cancel |
POST | Cancel running job |
/job/rerun |
POST | Re-enqueue done/failed job |
/job/output |
GET | Accumulated output lines |
/job/log |
GET | SSE stream of job output |
/job/fix |
POST | Enqueue fix job (parent_job_id, agentic) |
/job/patch |
POST | Get patch for completed fix job |
/job/applied |
POST | Mark fix patch as applied |
/job/rebased |
POST | Mark fix patch as rebased |
/repos |
GET | List registered repos |
/repos/register |
POST | Register repo by path |
/branches |
GET | List branches |
/review |
GET | Get review by job_id |
/review/close |
POST | Mark review closed |
/comment |
POST | Add comment to review |
/comments |
GET | List comments |
/jobs/batch |
POST | Enqueue batch (for CI) |
/status |
GET | Daemon status (version, queue stats, workers) |
/health |
GET | Health check (uptime, components, errors) |
/stream/events |
GET | SSE for real-time events |
/sync/now |
POST | Trigger immediate Postgres sync |
/sync/status |
GET | Sync status |
/activity |
GET | Activity log |
/remap |
POST | Remap job UUIDs after recovery |
Polls GitHub PRs at a configurable interval. For each open PR:
- Finds registered local repo matching
owner/repo - Fetches PR head ref, computes merge base
- Creates CIPRBatch, enqueues jobs for configured agents x review types
- Listens for
review.completedevents - Synthesizes results across agents (via
review.BuildSynthesisPrompt) - Posts PR comment, sets commit status (pending/success/failure)
Configured via [ci] section: enabled, github_repo, poll_interval, agents, review_types, min_severity, etc.
Builder methods: Build() (single/range), BuildDirty() (uncommitted changes)
System prompts: Vary by review type (standard, security, design) and agent. Include bug/security/testing/regression/quality criteria.
Context: Includes recent reviews in repo, project guidelines from .roborev.toml, previous review attempts for same commit, developer responses.
Max prompt size: 250KB (configurable). Falls back to file listing if diff exceeds limit.
Used for fix jobs to run agents in isolated environments:
Create(repoPath, ref)— Creates detached HEAD worktree, inits submodulesCapturePatch()—git add -A+git diff --cached, returns patch stringApplyPatch(repoPath, patch)—git apply --binaryfrom stdinCheckPatch(repoPath, patch)— Dry-run check, returnsPatchConflictErroron conflicts- Suppresses hooks via
core.hooksPath=/dev/null
Cursor-based bidirectional sync:
- Jobs synced by ID cursor
- Reviews synced by (updated_at, id) compound cursor
- Responses synced by ID cursor
- Machine ID differentiates local vs remote jobs
ResolveRepoIdentity()maps repos across machines (git remote URL or.roborev-idfile)
go build ./... # Build
go test ./... # Test (unit tests only)
go test -tags integration ./... # Test (unit + integration)
go test -tags postgres ./... # Test (requires TEST_POSTGRES_URL)
make install # Install to ~/.local/bin
make lint # golangci-lint with --fix
roborev init # Initialize in a repo
roborev status # Check daemon/queue
roborev daemon run # Start daemon in foreground
roborev tui # Terminal UI- No tag: unit tests only (default
go test ./...) //go:build integration: Slow integration tests//go:build postgres: RequiresTEST_POSTGRES_URL//go:build acp: ACP adapter tests (requiresROBOREV_RUN_ACP_INTEGRATION=1)
| Helper | Package | Purpose |
|---|---|---|
testutil.NewTestRepo() |
testutil | Temp git repo with git init |
testutil.NewTestRepoWithCommit() |
testutil | Repo with initial commit |
testutil.InitTestRepo(t) |
testutil | Standard setup with base.txt |
testutil.GetHeadSHA(t, dir) |
testutil | Get HEAD SHA of a test repo |
openTestDB(t) |
storage | In-memory SQLite for tests |
createJobChain(t, db, path, sha) |
storage | Creates repo + commit + job |
createRepo(t, db, path) |
storage | Creates repo only |
createCommit(t, db, repoID, sha) |
storage | Creates commit only |
claimJob(t, db, workerID) |
storage | Claims next queued job |
newWorkerTestContext(t, n) |
daemon | Full worker pool test setup |
workerTestContext.createAndClaimJob() |
daemon | Enqueue + claim in one call |
workerTestContext.createAndClaimJobWithAgent() |
daemon | Same with custom agent |
workerTestContext.exhaustRetries() |
daemon | Exhaust retry count for failover tests |
NewStaticConfig(cfg) |
daemon | ConfigGetter that returns fixed config |
- All tests use
t.TempDir()for isolation testagent is always available (no PATH lookup)- Worker pool test hooks enable deterministic synchronization
- Table-driven tests are preferred
- HTTP over gRPC: Simple HTTP/JSON for the daemon API
- No CGO in releases: Build with
CGO_ENABLED=0for static binaries (except sqlite which needs CGO locally) - Test agent: Use
agent = "test"for testing without calling real AI - Isolated tests: All tests use
t.TempDir()for temp directories
Sanitize untrusted strings before TUI display: stripControlChars() for table cells, sanitizeForDisplay() for multi-line content, sanitizeEscapes() for streaming lines.
- Daemon tasks must not modify the git working tree. Background jobs (reviews, CI polling, synthesis) are read-only with respect to the user's repo checkout. They read source files and write results to the database only. CLI commands like
roborev fixrun synchronously in the foreground and may modify files. Backgroundfixjobs run agents in isolated git worktrees (viainternal/worktree) and store resulting patches in the database — patches are only applied to the working tree when the user explicitly confirms in the TUI.
When creating PRs, do NOT include a "Test plan" section. The PR body should contain only a Summary section with bullet points describing what changed.
- Keep it simple, no over-engineering
- Prefer stdlib over external dependencies
- Tests should be fast and isolated
- No emojis in code or output (except commit messages)
- Never amend commits; always create new commits for fixes
- Never push or pull unless explicitly asked by the user
- NEVER merge pull requests. Do not run
gh pr mergeor any equivalent. Only the user merges PRs. This is non-negotiable. - NEVER change git branches without explicit user confirmation. Always ask before switching, creating, or checking out branches. This is non-negotiable.