dobbe

Review Pipeline Deep Dive

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.

Pipeline Flowchart

                     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          |
+---------------------------+   +----------------------------------+

Subcommands Overview

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-Level Review Mode (--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:

  1. Plain number (42) – requires --repo or CWD auto-detect to resolve the repository. Returns (None, 42).
  2. GitHub URL (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.
  3. Shorthand (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.

Batch Review Mode (default)

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).

Claude-based discovery

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:

Returns structured JSON parsed into PRInfo objects with full metadata: additions, deletions, changed files, labels, draft status, description.

Codebase Context Caching

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-context

Forces 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-context

Skips 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.

Diff Pre-fetching

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.

Analysis Model

The 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:

Prioritization

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.

Review Posting

For review post, Phase 6b handles deduplication and posting:

Deduplication

Before posting, dobbe checks:

  1. Head SHA match - has the PR been updated since the last review?
  2. Existing review - does a dobbe-authored review already exist?

If both conditions indicate a prior review exists at the current SHA, the PR is skipped with the reason recorded in PostResult.skipped_reason.

Payload Construction

The review payload posted to GitHub includes:

Posting

Reviews are posted via gh api commands through the Bash tool. In --dry-run mode, the payload is logged but not posted.

SLA Monitoring (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:

  1. Fetches open PRs for the target org or repo
  2. Computes how long each PR has been waiting for review
  3. Compares waiting time against the SLA threshold (configurable per-org)
  4. Returns a list of overdue PRs with hours_waiting and sla_threshold_hours

Output includes total checked, total overdue, and a table of overdue PRs. Supports --format (table/json/markdown) and --output for file export.

Escalation

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.

Concurrency Model

All phases use asyncio with semaphore-controlled concurrency:

Error Handling

See Also