The review pipeline is a 6-phase async system that discovers, filters, analyzes, and prioritizes (or posts) PR reviews. It supports two entry modes: PR-level review (single PR via --pr) and batch review (all open PRs across configured repos).
A separate review sla subcommand checks review turnaround against SLA thresholds and escalates overdue PRs.
dobbe review digest / post
|
v
+---------------------------+
| parse_pr_ref(--pr value) |
| (if --pr is provided) |
+---------------------------+
|
+-----------------+-----------------+
| |
--pr given no --pr given
(single PR) (batch: all PRs)
| |
v v
+---------------------------+ +----------------------------------+
| PHASE 1a: FETCH SINGLE PR | | PHASE 1b: DISCOVER ALL PRs |
| | | |
| _fetch_single_pr() | | For each repo (parallel, sem=5): |
| -> gh api (direct) | | _fetch_prs_for_repo() |
| -> PRInfo for 1 PR | | -> Claude: PR_DISCOVERY_PROMPT |
| No Claude call needed | | -> parse JSON -> list[PRInfo] |
+---------------------------+ +----------------------------------+
| |
+-----------------+-----------------+
|
v
+----------------------------------+
| PHASE 2: FILTER |
| |
| _filter_prs(prs, skip_labels, |
| skip_authors, skip_drafts) |
| |
| Remove: |
| - Draft PRs (batch mode only) |
| - Labels matching --skip-label |
| - Authors matching --skip-author|
+----------------------------------+
|
v
+------------------------------------+----------------------------+
| PHASE 3: PREPARE CONTEXT | PHASE 4: PRE-FETCH DIFFS |
| (runs concurrently with Phase 4) | (runs concurrently) |
| | |
| _ensure_repo_contexts() | For each PR (sem=5): |
| - Clone repos to local cache | _fetch_diff_for_pr() |
| - Build/load codebase context | - Fetch diff via gh CLI |
| - Respects --rebuild-context, | - Truncate to |
| --context-ttl, --no-context | max_diff_lines (2000) |
| - Returns repo -> (path, context) | - Pass inline to prompt |
+------------------------------------+----------------------------+
|
v
+----------------------------------+
| PHASE 5: ANALYZE |
| |
| For each PR (parallel, sem=5): |
| _analyze_pr(pr, mcps, diff, |
| codebase_context, cwd) |
| |
| Claude: PR_REVIEW_PROMPT |
| - Security, tests, breaking, |
| quality, complexity |
| - Output: risk_level, summary, |
| concerns[], recommendations[] |
| - Parse JSON -> PRReviewResult |
| - On error: analysis_error set |
+----------------------------------+
|
v
+-----------------+-----------------+
| |
(digest path) (post path)
| |
v v
+---------------------------+ +----------------------------------+
| PHASE 6a: PRIORITIZE | | PHASE 6b: DEDUP + POST |
| | | |
| _prioritize_results | | For each result: |
| Sort by: | | 1. Check existing review at |
| 1. Risk level | | current head SHA |
| (critical first) | | 2. Skip if already reviewed |
| 2. Age (oldest first) | | 3. Build review payload: |
| | | - Body with summary |
| Flag stale PRs | | - Inline comments at file:line|
| (> stale_days) | | 4. Post via gh CLI |
| | | (unless --dry-run) |
| Return ReviewDigest | | |
| | | Return ReviewPostReport |
+---------------------------+ +----------------------------------+
The review command group has three subcommands:
| Subcommand | Purpose | Phases Used |
|---|---|---|
review digest |
Analyze PRs, output a prioritized digest | 1-5 + 6a (prioritize) |
review post |
Analyze PRs, post reviews to GitHub | 1-5 + 6b (dedup + post) |
review sla |
Check review turnaround against SLA thresholds | Standalone (no pipeline) |
--pr)The --pr flag enables single-PR review, which is the recommended path for everyday use. It accepts flexible input formats parsed by parse_pr_ref():
parse_pr_ref(value) -> (repo_slug | None, pr_number)
"42" -> (None, 42)
"https://github.com/org/repo/pull/42" -> ("org/repo", 42)
"org/repo#42" -> ("org/repo", 42)
Resolution rules:
42) – requires --repo or CWD auto-detect to resolve the repository. Returns (None, 42).https://github.com/org/repo/pull/42) – extracts repo slug and PR number from the URL path via regex. The extracted repo is used automatically, so --repo is not needed.org/repo#42) – splits on # to extract repo slug and PR number. Also makes --repo unnecessary.If the URL or shorthand provides a repo and --repo was not explicitly given, the parsed repo is used. If --repo was also given, the explicit flag takes precedence.
When --pr is used, Phase 1 calls _fetch_single_pr() (direct gh api, no Claude call), which is faster and cheaper than the batch discovery path.
When --pr is not provided, the pipeline discovers all open PRs across configured repos. Both digest and post warn the user before proceeding because batch mode is expensive (multiple Claude calls).
Uses PR_DISCOVERY_PROMPT_TEMPLATE - instructs Claude to find open PRs where the specified reviewers are requested. The prompt adapts based on GitHub MCP availability:
mcp__github__* tools for direct API accessgh pr list via BashReturns structured JSON parsed into PRInfo objects with full metadata: additions, deletions, changed files, labels, draft status, description.
Phase 3 (_ensure_repo_contexts) clones each repo and builds a codebase context summary that helps Claude understand the project structure during analysis. Context is cached to avoid rebuilding on every run.
Three flags control caching behavior:
--rebuild-contextForces a fresh context build regardless of cache state. Use this after major refactors or when you suspect the cached context is stale. Under the hood, ensure_context_async() is called with rebuild_context=True, which skips the cache check and regenerates the context from scratch.
--context-ttl <days>Controls how long a cached context is considered valid before automatic rebuild. Default: 7 days (configurable via dobbe setup under [review] context_ttl). The pipeline passes this value to ensure_context_async(), which compares the cache timestamp against the TTL and rebuilds only if expired.
--no-contextSkips context preparation entirely. The pipeline sets no_context=True, which bypasses Phase 3 altogether – no cloning, no context building. Analysis runs in “diff-only” mode, where Claude sees only the PR diff and metadata. This is faster and cheaper but produces less context-aware reviews.
Context flow:
--no-context?
|
+-- yes --> skip Phase 3 entirely, context=None
|
+-- no --> clone repo (if not cached)
|
v
--rebuild-context?
|
+-- yes --> rebuild context from scratch
|
+-- no --> cache exists and age < context-ttl?
|
+-- yes --> load cached context
|
+-- no --> rebuild context
Phase 3 (context) and Phase 4 (diffs) run concurrently via asyncio.create_task to minimize wall-clock time.
Phase 4 fetches diffs in parallel before analysis. This is a performance optimization - embedding the diff directly in the analysis prompt gives Claude better context than asking it to fetch the diff itself.
max_diff_lines (default: 2000) to stay within prompt limitsThe analysis prompt (PR_REVIEW_PROMPT_TEMPLATE) asks Claude to perform a senior engineer code review across 5 categories:
| Category | What It Checks |
|---|---|
| Security | Input validation, auth, injection, secrets exposure |
| Test coverage | New code has tests, edge cases covered |
| Breaking changes | API removals, type changes, behavioral shifts |
| Code quality | Naming, structure, duplication, error handling |
| Complexity | Cognitive load, nesting depth, function length |
Each concern includes:
The _prioritize_results() function sorts using a severity order:
CRITICAL (0) > HIGH (1) > MEDIUM (2) > LOW (3)
Within the same risk level, PRs are sorted by age (oldest first), ensuring long-waiting reviews surface to the top.
For review post, Phase 6b handles deduplication and posting:
Before posting, dobbe checks:
If both conditions indicate a prior review exists at the current SHA, the PR is skipped with the reason recorded in PostResult.skipped_reason.
The review payload posted to GitHub includes:
file_path and line_number becomes an inline review comment positioned at the exact code locationReviews are posted via gh api commands through the Bash tool. In --dry-run mode, the payload is logged but not posted.
review sla)The review sla subcommand operates independently of the main review pipeline. It does not run through the 6-phase system; instead it calls check_pr_slas() directly, which:
hours_waiting and sla_threshold_hoursOutput includes total checked, total overdue, and a table of overdue PRs. Supports --format (table/json/markdown) and --output for file export.
When --notify slack is provided alongside overdue PRs, the command builds an escalation message via build_escalation_message() and posts it to the configured Slack channel using the notification subsystem.
All phases use asyncio with semaphore-controlled concurrency:
max_concurrent)errors list without failing the entire pipelinePRReviewResult with analysis_error set instead of crashingPostResult.error