# AsyncReview Developer Reference Wiki

> AsyncReview is an agentic GitHub PR and issue review system with a Python RLM core, a packaged NPX CLI, and a FastAPI-backed React review UI. This fast wiki organizes the repository by developer entry points, review data flow, and operational extension boundaries.

## Context Links

- [Agent index](https://grok-wiki.com/public/wiki/asyncfuncai-asyncreview-fb80a82d8c3a/llms.txt)
- [Human interactive wiki](https://grok-wiki.com/public/wiki/asyncfuncai-asyncreview-fb80a82d8c3a)
- [GitHub repository](https://github.com/AsyncFuncAI/AsyncReview)

## Repository Metadata

- Repository: AsyncFuncAI/AsyncReview

- Generated: 2026-05-19T06:24:39.449Z
- Updated: 2026-05-22T00:11:09.447Z
- Runtime: Codex CLI
- Format: Technical
- Pages: 3

## Page Index

- 01. [Technical Orientation](https://grok-wiki.com/public/wiki/asyncfuncai-asyncreview-fb80a82d8c3a/pages/01-technical-orientation.md) - What AsyncReview is, how its Python packages, CLI commands, RLM review loop, and web review surface fit together, and how the rest of this developer reference is organized.
- 02. [CLI, Skill & Runtime Packaging](https://grok-wiki.com/public/wiki/asyncfuncai-asyncreview-fb80a82d8c3a/pages/02-cli-skill-runtime-packaging.md) - The command-line and skill-facing workflow: NPX command parsing, API key handling, Python runner setup, virtual GitHub review execution, release runtime caching, and portable skill invocation.
- 03. [API, Web Review Flow & Extension Boundaries](https://grok-wiki.com/public/wiki/asyncfuncai-asyncreview-fb80a82d8c3a/pages/03-api-web-review-flow-extension-boundaries.md) - The closing operational map for the FastAPI backend, GitHub PR cache, diff Q&A and auto-review RLMs, React diff viewer, streaming responses, provider-key boundaries, and what developers should inspect before extending the system.

## Source File Index

- `cli/github_fetcher.py`
- `cli/main.py`
- `cli/virtual_runner.py`
- `cr/cli.py`
- `cr/diff_rlm.py`
- `cr/diff_types.py`
- `cr/github.py`
- `cr/server.py`
- `cr/suggestions.py`
- `INSTALLATION.md`
- `npx/src/cli.ts`
- `npx/src/index.ts`
- `npx/src/launcher.ts`
- `npx/src/python-runner.ts`
- `pyproject.toml`
- `README.md`
- `skills/asyncreview/SKILL.md`
- `web/src/App.tsx`
- `web/src/components/ChatPanel.tsx`
- `web/src/components/DiffViewer.tsx`

---

## 01. Technical Orientation

> What AsyncReview is, how its Python packages, CLI commands, RLM review loop, and web review surface fit together, and how the rest of this developer reference is organized.

- Page Markdown: https://grok-wiki.com/public/wiki/asyncfuncai-asyncreview-fb80a82d8c3a/pages/01-technical-orientation.md
- Generated: 2026-05-19T06:24:30.939Z

### Source Files

- `README.md`
- `pyproject.toml`
- `INSTALLATION.md`
- `cr/cli.py`
- `cli/main.py`
- `web/src/App.tsx`

<details>
<summary>Relevant source files</summary>
The following files were used as context for generating this wiki page:
- [README.md](README.md)
- [INSTALLATION.md](INSTALLATION.md)
- [pyproject.toml](pyproject.toml)
- [cr/cli.py](cr/cli.py)
- [cli/main.py](cli/main.py)
- [cr/rlm_runner.py](cr/rlm_runner.py)
- [cr/diff_rlm.py](cr/diff_rlm.py)
- [cr/server.py](cr/server.py)
- [cr/github.py](cr/github.py)
- [cr/config.py](cr/config.py)
- [cr/snapshot.py](cr/snapshot.py)
- [web/src/App.tsx](web/src/App.tsx)
- [web/src/components/ChatPanel.tsx](web/src/components/ChatPanel.tsx)
- [web/vite.config.ts](web/vite.config.ts)
- [npx/src/index.ts](npx/src/index.ts)
- [npx/src/launcher.ts](npx/src/launcher.ts)
- [skills/asyncreview/SKILL.md](skills/asyncreview/SKILL.md)
</details>

# Technical Orientation

AsyncReview is a code-review system for GitHub pull requests, GitHub issues, and local repositories. Its core idea is to give a language-model review loop executable context: the model can reason, generate Python, run that code through DSPy’s RLM interpreter, inspect repository or diff data, and return answers with cited sources.

This page explains how the Python packages, command-line entry points, recursive review loop, GitHub ingestion layer, and React web surface fit together. It also orients the rest of the developer reference: start here for boundaries and data flow, then move into focused pages for the CLI, RLM runtime, GitHub loading, web UI, configuration, and tests.

Sources: [README.md:3-6](), [README.md:38-48](), [pyproject.toml:1-22](), [pyproject.toml:32-42]()

## Product Workflows

AsyncReview exposes three main developer workflows:

| Workflow | User entry point | Backend path | Primary responsibility |
|---|---|---|---|
| Zero-install GitHub review | `npx asyncreview review --url ... -q ...` | Node launcher plus packaged Python runtime | Review a GitHub PR or issue from a URL without cloning the repository. |
| Local repository Q&A | `cr ask` or `cr review -q ...` | `cr.cli` and `CodebaseReviewRLM` | Snapshot a local repo and run an RLM question-answer loop over the codebase. |
| Web PR review | `cr serve` plus `web` dev server | FastAPI API, GitHub loader, diff RLMs, React UI | Load a PR, render changed files, run automatic review, and support chat over diffs. |

The README positions `npx asyncreview` as the default path for PR and issue review, while the installation guide describes the full local API server and web UI setup. The Python package publishes both `cr` and `asyncreview` console scripts, so the repository intentionally contains both local-codebase and GitHub-URL review surfaces.

Sources: [README.md:49-62](), [README.md:63-99](), [INSTALLATION.md:106-153](), [pyproject.toml:32-34]()

## High-Level Architecture

```mermaid
flowchart LR
  subgraph CLI["Command-line surfaces"]
    CR["cr: local ask/review/serve"]
    ASYNC["asyncreview: GitHub URL review"]
    NPX["npx wrapper"]
  end

  subgraph Python["Python review runtime"]
    SNAP["cr.snapshot"]
    RLM["cr.rlm_runner.CodebaseReviewRLM"]
    DIFF["cr.diff_rlm DiffQARLM / FastAutoReview"]
    GH["cr.github / cli.github_fetcher"]
    API["cr.server FastAPI"]
  end

  subgraph Web["Web review surface"]
    APP["web/src/App.tsx"]
    CHAT["ChatPanel SSE chat"]
    DIFFVIEW["DiffViewer / PR panels"]
  end

  subgraph External["External systems"]
    GITHUB["GitHub API"]
    DSPY["DSPy RLM + PythonInterpreter"]
    DENO["Deno/Pyodide execution"]
  end

  NPX --> ASYNC
  CR --> SNAP
  CR --> RLM
  CR --> API
  ASYNC --> GH
  ASYNC --> RLM
  API --> GH
  API --> DIFF
  APP --> API
  CHAT --> API
  GH --> GITHUB
  RLM --> DSPY
  DIFF --> DSPY
  DSPY --> DENO
```

The important boundary is that the web app does not call GitHub or the model directly. It calls local API endpoints under `/api`, which are proxied by Vite to the FastAPI server. The FastAPI server owns PR loading, cached review IDs, diff question-answering, automatic review, and streaming events. The CLI paths bypass the React UI and run either local snapshots or virtual GitHub contexts directly.

Sources: [cr/cli.py:136-178](), [cli/main.py:109-178](), [cr/server.py:109-187](), [web/vite.config.ts:4-14](), [web/src/App.tsx:107-164]()

## Python Packages and Entry Points

The root Python project is named `cr` and builds two packages: `cr` and `cli`. Its dependencies include DSPy, Rich, dotenv, HTTPX, FastAPI, Uvicorn, and SSE support. The two published console scripts are:

| Script | Target | Role |
|---|---|---|
| `cr` | `cr.cli:main` | Local repository review, interactive ask mode, and API server startup. |
| `asyncreview` | `cli.main:main` | GitHub PR/issue URL review for packaged or NPX usage. |

`cr.cli` defines `review`, `ask`, and `serve`. `review` builds a local snapshot and runs a one-shot question. `ask` builds the same snapshot, keeps conversation history in memory, and supports local commands such as `history`, `files`, and `info`. `serve` imports `cr.server:app` and starts Uvicorn using configured host and port.

Sources: [pyproject.toml:13-22](), [pyproject.toml:32-42](), [cr/cli.py:25-103](), [cr/cli.py:105-134](), [cr/cli.py:136-178]()

### GitHub URL CLI

The `asyncreview` Python CLI is focused on URL-based review. It validates a GitHub URL, creates a `VirtualReviewRunner`, runs the review asynchronously, and formats the result as text, Markdown, or JSON. It accepts an optional model override and quiet mode for scripting.

Sources: [cli/main.py:53-107](), [cli/main.py:130-175]()

The NPX package adds a Node command wrapper. The TypeScript CLI validates that exactly one of `--url` or `--path` is supplied, while the runtime launcher detects OS/architecture, downloads a release tarball into a user cache if needed, and spawns the cached runtime with inherited stdio and environment.

Sources: [npx/src/index.ts:12-42](), [npx/src/launcher.ts:31-87](), [npx/src/launcher.ts:138-206]()

## Local Codebase Review Loop

Local review starts by converting a repository directory into a `CodebaseSnapshot`. Snapshotting walks the tree, filters default ignored directories and file types, honors include/exclude globs, prioritizes important manifests and source/test directories, skips oversized or binary files, decodes UTF-8 content, detects language, extracts simple symbols, and records repository metadata.

Sources: [cr/config.py:16-24](), [cr/config.py:35-139](), [cr/snapshot.py:229-253](), [cr/snapshot.py:260-323]()

`CodebaseReviewRLM` then configures DSPy with the main model, creates a `PythonInterpreter` backed by a Deno command, and constructs a DSPy `RLM` with the signature `codebase, conversation_history, question -> answer, sources`. Each run builds a fresh snapshot, formats conversation history, invokes the RLM, normalizes returned sources, captures trace steps from DSPy logging, and optionally saves traces under the configured traces directory.

Sources: [cr/rlm_runner.py:26-70](), [cr/rlm_runner.py:73-161](), [cr/rlm_runner.py:187-220](), [cr/rlm_runner.py:222-306]()

```python
# cr/rlm_runner.py
self._rlm = dspy.RLM(
    signature="codebase, conversation_history, question -> answer, sources",
    max_iterations=MAX_ITERATIONS,
    max_llm_calls=MAX_LLM_CALLS,
    sub_lm=dspy.LM(SUB_MODEL),
    verbose=True,
    interpreter=interpreter,
)
```

Sources: [cr/rlm_runner.py:212-219]()

## GitHub PR and Issue Review

The web backend and URL CLI both depend on GitHub API data, but they use different adapters.

The web path uses `cr.github`. It parses PR URLs, fetches PR metadata, changed files, commits, and issue comments, converts changed files into dictionaries containing path, status, additions, deletions, and patch, then stores the resulting `PRInfo` in an in-memory cache keyed by an eight-character review ID. File contents are fetched later from the GitHub contents API at both base and head SHAs.

Sources: [cr/github.py:20-47](), [cr/github.py:50-80](), [cr/github.py:82-169](), [cr/github.py:172-235]()

The URL CLI path uses `VirtualReviewRunner`. It parses the URL, fetches either PR or issue data through `cli.github_fetcher`, builds a text review context, configures DSPy RLM, runs the synchronous RLM in an executor, and returns answer, sources, and metadata.

Sources: [cli/virtual_runner.py:19-83](), [cli/virtual_runner.py:85-140]()

## Diff Review RLMs

The web review loop has two distinct model paths in `cr.diff_rlm`:

| Class | Trigger | Model style | Output |
|---|---|---|---|
| `DiffQARLM` | User asks a question in chat | DSPy RLM with executable Python iterations | Markdown/code answer blocks plus diff citations. |
| `FastAutoReview` | PR is loaded in the web app | DSPy `ChainOfThought` predictor | Issues grouped into bug, investigation, or informational categories plus a summary. |

`DiffQARLM` builds diff context from file contents, formats conversation and selected line ranges, and calls an RLM signature of `diff_context, pr_info, selection, conversation, question -> answer, citations`. Its streaming variant fetches up to 50 files in parallel, injects `file_data` into the REPL variables, manually advances RLM iterations, yields `RLMIteration` objects for the UI, and finally yields parsed answer blocks and citations.

Sources: [cr/diff_rlm.py:1-6](), [cr/diff_rlm.py:181-208](), [cr/diff_rlm.py:210-278](), [cr/diff_rlm.py:280-442]()

`FastAutoReview` is intentionally simpler. It uses PR patches for up to 100 files, adds instructions requiring diff-bounded citations, runs a synchronous DSPy predictor in a worker thread, parses JSON-like issue output, and converts issue dictionaries into typed `ReviewIssue` objects.

Sources: [cr/diff_rlm.py:446-574]()

## FastAPI Review API

`cr.server` is the integration layer for the web app. It creates a FastAPI app, enables permissive CORS for local development, lazily initializes shared `DiffQARLM` and `FastAutoReview` instances, and exposes endpoints for PR loading, file content retrieval, diff chat, automatic review, suggestions, streaming chat, health checks, and SSE testing.

| Endpoint | Purpose |
|---|---|
| `POST /api/github/load_pr` | Load PR metadata and changed files, returning a `reviewId`. |
| `GET /api/github/file` | Fetch base/head contents for one PR file. |
| `POST /api/diff/ask` | Non-streaming diff Q&A. |
| `POST /api/diff/review` | Automatic bug/risk review. |
| `POST /api/diff/ask/stream` | SSE stream of RLM iterations and final answer blocks. |
| `POST /api/suggestions` | Generate follow-up suggestions with fallback defaults. |
| `GET /health` | Server health check. |

Sources: [cr/server.py:17-49](), [cr/server.py:51-107](), [cr/server.py:109-187](), [cr/server.py:190-217](), [cr/server.py:219-311](), [cr/server.py:314-343]()

The streaming endpoint emits structured SSE messages: `start`, repeated `iteration` events, answer `block` events, `citations`, and `complete`. This is the bridge that lets the UI show the recursive process rather than only the final answer.

Sources: [cr/server.py:219-284](), [cr/server.py:287-311]()

## Web Review Surface

The React app is organized around three panes: a file list and PR loader, a summary/diff pane, and a resizable right panel with chat, flags, and bugs. Loading a PR posts the entered URL to `/api/github/load_pr`, stores returned `PRInfo`, and selects the first changed file. When a `reviewId` appears, the app automatically posts to `/api/diff/review` and splits returned issues into bug and flag lists.

Sources: [web/src/App.tsx:11-24](), [web/src/App.tsx:107-164](), [web/src/App.tsx:178-180](), [web/src/App.tsx:182-246](), [web/src/App.tsx:274-393]()

`ChatPanel` owns the interactive review conversation. It offers quick actions, sends questions to `/api/diff/ask/stream`, includes the current diff selection when present, reads the response body as an SSE stream, appends RLM iterations to the assistant message, renders final Markdown/code blocks, and requests follow-up suggestions after assistant responses.

Sources: [web/src/components/ChatPanel.tsx:16-21](), [web/src/components/ChatPanel.tsx:72-167](), [web/src/components/ChatPanel.tsx:189-328](), [web/src/components/ChatPanel.tsx:330-361](), [web/src/components/ChatPanel.tsx:370-490]()

## Configuration and Portability

The current implementation defaults to Gemini model identifiers through DSPy: `MAIN_MODEL` defaults to `gemini/gemini-3-pro-preview`, and `SUB_MODEL` defaults to `gemini/gemini-3-flash-preview`. It also reads `GEMINI_API_KEY`, `GITHUB_TOKEN`, snapshot size limits, cache paths, GitHub API base URL, and API host/port from environment variables. The architecture should be read as BYOK-friendly because keys are injected from the environment, but the checked-in code is not provider-neutral at the model-adapter level unless those model strings and DSPy configuration are changed.

Sources: [cr/config.py:1-33](), [cr/rlm_runner.py:200-220](), [cli/virtual_runner.py:61-83]()

The repository also includes an `asyncreview` skill definition for agent integrations. That file describes AsyncReview as a portable skill invoked through `npx asyncreview`, with prerequisites around `GEMINI_API_KEY` and optional `GITHUB_TOKEN`. Treat this as workflow documentation for compatible agents, not as a separate runtime dependency for the Python or web implementation.

Sources: [skills/asyncreview/SKILL.md:1-5](), [skills/asyncreview/SKILL.md:19-28](), [skills/asyncreview/SKILL.md:174-179]()

## How This Developer Reference Is Organized

Use this page as the map. The rest of the reference should stay source-backed and split by implementation responsibility:

| Reference page | What it should cover | Main source owners |
|---|---|---|
| CLI Commands | `cr`, `asyncreview`, NPX launcher, output formats, local vs URL review. | `cr/cli.py`, `cli/main.py`, `cli/virtual_runner.py`, `npx/src/*` |
| RLM Runtime | DSPy setup, Deno interpreter command, trace capture, local snapshots, iteration limits. | `cr/rlm_runner.py`, `cr/snapshot.py`, `cr/config.py` |
| GitHub Ingestion | PR URL parsing, API calls, review ID cache, file content loading. | `cr/github.py`, `cli/github_fetcher.py` |
| Web API | FastAPI request/response models, endpoints, SSE event contract. | `cr/server.py`, `cr/diff_rlm.py` |
| Web UI | PR loading, diff viewing, chat streaming, flags and bugs panels. | `web/src/App.tsx`, `web/src/components/*`, `web/src/types.ts` |
| Configuration and Setup | Environment variables, local server ports, frontend proxy, install prerequisites. | `INSTALLATION.md`, `cr/config.py`, `web/vite.config.ts`, `pyproject.toml` |

Sources: [INSTALLATION.md:13-19](), [INSTALLATION.md:61-79](), [INSTALLATION.md:106-153](), [INSTALLATION.md:195-225](), [web/package.json:5-30]()

## Closing Summary

AsyncReview is best understood as a set of review surfaces over one core pattern: collect code or diff context, run a DSPy-backed review loop or predictor, and present cited findings through CLI output or a web UI. The local `cr` command focuses on repository snapshots, `asyncreview` focuses on GitHub URLs, and the web app coordinates PR loading, automated issue discovery, and streaming diff chat through FastAPI.

Sources: [cr/cli.py:25-38](), [cli/main.py:53-83](), [cr/server.py:140-187](), [web/src/App.tsx:139-180]()

---

## 02. CLI, Skill & Runtime Packaging

> The command-line and skill-facing workflow: NPX command parsing, API key handling, Python runner setup, virtual GitHub review execution, release runtime caching, and portable skill invocation.

- Page Markdown: https://grok-wiki.com/public/wiki/asyncfuncai-asyncreview-fb80a82d8c3a/pages/02-cli-skill-runtime-packaging.md
- Generated: 2026-05-19T06:24:39.448Z

### Source Files

- `npx/src/index.ts`
- `npx/src/cli.ts`
- `npx/src/python-runner.ts`
- `npx/src/launcher.ts`
- `cli/main.py`
- `cli/virtual_runner.py`
- `cli/github_fetcher.py`
- `skills/asyncreview/SKILL.md`

<details>
<summary>Relevant source files</summary>
The following files were used as context for generating this wiki page:
- [npx/src/index.ts](npx/src/index.ts)
- [npx/src/cli.ts](npx/src/cli.ts)
- [npx/src/api-key.ts](npx/src/api-key.ts)
- [npx/src/python-runner.ts](npx/src/python-runner.ts)
- [npx/src/launcher.ts](npx/src/launcher.ts)
- [npx/package.json](npx/package.json)
- [npx/python/cli/main.py](npx/python/cli/main.py)
- [npx/python/cli/virtual_runner.py](npx/python/cli/virtual_runner.py)
- [npx/python/cli/github_fetcher.py](npx/python/cli/github_fetcher.py)
- [npx/python/cli/local_fetcher.py](npx/python/cli/local_fetcher.py)
- [npx/python/cli/repo_tools.py](npx/python/cli/repo_tools.py)
- [npx/python/cli/local_repo_tools.py](npx/python/cli/local_repo_tools.py)
- [npx/python/cli/output_formatter.py](npx/python/cli/output_formatter.py)
- [scripts/build_runtime_local.sh](scripts/build_runtime_local.sh)
- [skills/asyncreview/SKILL.md](skills/asyncreview/SKILL.md)
- [pyproject.toml](pyproject.toml)
</details>

# CLI, Skill & Runtime Packaging

AsyncReview exposes a command-line review workflow that can be used directly through `npx asyncreview` or indirectly through a portable skill definition. The workflow starts in a small Node.js wrapper, resolves credentials, launches packaged Python review code, and optionally downloads a cached release runtime before handing control to the real CLI.

This page covers the packaging and runtime path rather than the full review intelligence. It focuses on command parsing, API key handling, Python and Deno setup, GitHub and local review execution, release runtime caching, and how the skill file invokes the same command surface.

## Runtime Entry Points

The npm package publishes `asyncreview` as `./dist/launcher.js`, not the direct Commander entrypoint. The launcher reads the package version, detects the host platform, downloads a matching runtime tarball from GitHub Releases when the cached runtime is missing, then executes the cached `bin/asyncreview` with the original arguments. Sources: [npx/package.json:1-18](), [npx/src/launcher.ts:24-30](), [npx/src/launcher.ts:31-87](), [npx/src/launcher.ts:172-211]()

The direct TypeScript CLI still exists inside the runtime. It defines the `review` command and options such as `--url`, `--path`, `--question`, `--expert`, `--output`, `--quiet`, `--model`, `--api`, and `--github-token`. It performs the first mutual-exclusion check for `--url` versus `--path`, then delegates to `runReview`. Sources: [npx/src/index.ts:12-40](), [npx/src/cli.ts:17-30]()

```text
User / skill
  |
  | npx asyncreview review ...
  v
npm bin: dist/launcher.js
  |
  | cache miss: download asyncreview-runtime-v<version>-<platform>.tar.gz
  v
cached runtime/bin/asyncreview
  |
  | exec node app/dist/index.js
  v
Node CLI -> Python module cli.main -> VirtualReviewRunner
```

Sources: [npx/src/launcher.ts:138-170](), [scripts/build_runtime_local.sh:124-171](), [npx/src/python-runner.ts:282-329]()

## Command Options And Dispatch

At the Node layer, `review` accepts either a GitHub URL or local path. `--question` is optional only when `--expert` is supplied, and `--output` defaults to `text`. Sources: [npx/src/index.ts:17-29]()

The packaged Python CLI in `npx/python/cli/main.py` mirrors those review sources with an argparse mutually exclusive group for `--url` and `--path`. It also adds `--submit`, which is valid only for GitHub URL reviews and posts the review result as a GitHub issue/PR comment. Sources: [npx/python/cli/main.py:227-333](), [npx/python/cli/main.py:141-154]()

| Option | Owner | Behavior |
| --- | --- | --- |
| `--url`, `-u` | Node and Python | Review a GitHub PR or issue URL. |
| `--path`, `-p` | Node and Python | Review a local directory. |
| `--question`, `-q` | Node and Python | Supplies the user question; optional with `--expert`. |
| `--expert` | Node and Python | Replaces or augments the question with an expert review prompt. |
| `--output`, `-o` | Node and Python | Selects `text`, `markdown`, or `json`. |
| `--quiet` | Node and Python | Suppresses progress output and prints raw final output. |
| `--model`, `-m` | Node and Python | Overrides the configured model name. |
| `--api` | Node only | Supplies the Gemini API key before spawning Python. |
| `--github-token` | Node only | Supplies the GitHub token before spawning Python. |
| `--submit` | Python packaged CLI | Posts the final result back to GitHub; URL mode only. |

Sources: [npx/src/index.ts:17-29](), [npx/python/cli/main.py:254-301](), [npx/python/cli/main.py:305-330]()

## Credential Resolution

The Node CLI resolves credentials before invoking Python. `getApiKey` checks `--api`, then `GEMINI_API_KEY`, then prompts interactively for a Gemini API key. `getGitHubToken` checks `--github-token`, then `GITHUB_TOKEN`, then `gh auth token`, and finally prompts if a token is required. Sources: [npx/src/api-key.ts:8-42](), [npx/src/api-key.ts:44-102]()

`runReview` always resolves an API key, but only resolves a GitHub token when URL mode is used. The resolved values are passed into the Python process through environment variables: `GEMINI_API_KEY` is always set and `GITHUB_TOKEN` is included only when available. Sources: [npx/src/cli.ts:29-64](), [npx/src/python-runner.ts:318-327]()

This implementation is BYOK in the practical sense: callers can provide their own model API key and GitHub token through flags, environment variables, or the GitHub CLI. The current code is not provider-neutral at the model layer, because both the credential name and DSPy model prefix are Gemini-specific. A future provider-neutral extension would need to abstract `GEMINI_API_KEY` and the `gemini/` model prefix rather than only changing the skill file. Sources: [npx/src/api-key.ts:14-24](), [npx/python/cli/virtual_runner.py:201-218]()

## Python Runner Setup

`npx/src/python-runner.ts` owns the bridge from Node to Python. It computes `NPX_ROOT`, detects bundled mode by checking for a sibling `pydeps/` directory, and builds `PYTHONPATH` from bundled dependencies plus `app/python` or from the local `npx/python` tree in development. Sources: [npx/src/python-runner.ts:12-31]()

The runner prefers a local virtual environment Python at `npx/.venv/bin/python`, then falls back to `python3` or `python` if the version is Python 3.11 or newer. It can create a virtual environment and install the Python package with `pip install .`, although the main execution path chooses the venv Python only when `cli.main`, `rich`, and `dspy` import successfully. Sources: [npx/src/python-runner.ts:44-82](), [npx/src/python-runner.ts:163-178](), [npx/src/python-runner.ts:186-245](), [npx/src/python-runner.ts:268-281]()

The spawned command is:

```text
python -m cli.main review [--url URL | --path PATH] --output FORMAT [-q QUESTION] [--expert] [--quiet] [--model MODEL]
```

Sources: [npx/src/python-runner.ts:282-313]()

The Python package metadata declares Python `>=3.11`, exposes `asyncreview = "cli.main:main"`, and packages both `cr` and `cli`. Sources: [pyproject.toml:1-7](), [pyproject.toml:32-41]()

## GitHub Review Execution

For GitHub reviews, the packaged Python CLI validates the URL, selects either the expert prompt or the supplied question, constructs a `VirtualReviewRunner`, and calls `runner.review(url, actual_question)`. Sources: [npx/python/cli/main.py:70-118]()

GitHub URL parsing supports pull request URLs and issue URLs. PR fetching collects metadata, changed files with patches, commits, discussion comments, branch information, and the PR head SHA. Issue fetching collects metadata, labels, body, and comments. Sources: [npx/python/cli/github_fetcher.py:15-48](), [npx/python/cli/github_fetcher.py:62-158](), [npx/python/cli/github_fetcher.py:161-202]()

`VirtualReviewRunner.review` creates `RepoTools(owner, repo, head_sha)` so follow-up file reads use the PR head commit for consistency. It then builds the review context and runs DSPy RLM asynchronously with a tool set for fetching files, listing directories, and searching code. Sources: [npx/python/cli/virtual_runner.py:225-285](), [npx/python/cli/virtual_runner.py:93-182]()

`RepoTools` uses the GitHub API with optional token authorization, path sanitization, bounded concurrency, simple retry/backoff for rate limits, a small file cache, a 200 KB per-file limit, and explicit skip/error stubs for inaccessible or unsupported files. Sources: [npx/python/cli/repo_tools.py:13-35](), [npx/python/cli/repo_tools.py:40-51](), [npx/python/cli/repo_tools.py:85-115](), [npx/python/cli/repo_tools.py:136-215]()

## Local Directory Review

Local review uses the same RLM runner but swaps the repository tools. The Python CLI validates the local path, builds either an expert prompt or the supplied question, then calls `runner.review_local(abs_path, actual_question)`. Sources: [npx/python/cli/main.py:157-224]()

`build_local_context` includes the absolute path, git branch and repository name when available, recent commits, staged and unstaged diff content, and a bounded project structure. The diff is truncated at 100 KB. Sources: [npx/python/cli/local_fetcher.py:9-24](), [npx/python/cli/local_fetcher.py:53-95](), [npx/python/cli/local_fetcher.py:98-155](), [npx/python/cli/local_fetcher.py:235-295]()

`LocalRepoTools` implements the same fetch/list/search interface against the filesystem. It resolves paths under the configured root, rejects traversal outside that root, skips common generated directories, caps file reads at the shared 200 KB limit, and searches text-like source extensions with `grep` using an argument list instead of `shell=True`. Sources: [npx/python/cli/local_repo_tools.py:36-65](), [npx/python/cli/local_repo_tools.py:67-98](), [npx/python/cli/local_repo_tools.py:99-150](), [npx/python/cli/local_repo_tools.py:152-198]()

## RLM Configuration And Output

The runner configures DSPy lazily on first use. It prefixes model names with `gemini/` when needed, disables LM caching, builds a Deno-backed Python interpreter command, and passes custom tools into `dspy.RLM` with the signature `context, question -> answer, sources`. Sources: [npx/python/cli/virtual_runner.py:184-223]()

The CLI formats output after the runner returns `answer`, `sources`, and metadata. Text output is plain terminal text, markdown output includes a `### Sources` section for embedding in docs or skills, and JSON output contains `answer`, `sources`, `model`, and optional metadata. Sources: [npx/python/cli/main.py:120-139](), [npx/python/cli/main.py:205-224](), [npx/python/cli/output_formatter.py:7-23](), [npx/python/cli/output_formatter.py:26-43](), [npx/python/cli/output_formatter.py:46-65]()

## Release Runtime Caching

The release runtime is a platform-specific tarball named `asyncreview-runtime-v<version>-<platform>.tar.gz`. The launcher maps OS and CPU architecture to keys such as `darwin-arm64`, `linux-x64`, or `windows-x64`, then stores extracted runtimes below the user cache directory by version and platform. Sources: [npx/src/launcher.ts:31-75](), [npx/src/launcher.ts:138-163]()

The local build script stages a self-contained runtime by compiling the TypeScript runtime entrypoint, copying `npx/python/cli` and `npx/python/cr`, installing production Node dependencies, installing Python dependencies into `pydeps/`, downloading a Deno binary, and writing `bin/asyncreview`. Sources: [scripts/build_runtime_local.sh:38-71](), [scripts/build_runtime_local.sh:73-123](), [scripts/build_runtime_local.sh:124-171]()

At runtime, `bin/asyncreview` adds bundled Deno to `PATH`, sets `PYTHONPATH` to `pydeps` and `app/python`, sets `DENO_DIR`, verifies Python dependencies once, optionally reinstalls them for the user's Python version, and finally executes `node app/dist/index.js`. Sources: [scripts/build_runtime_local.sh:130-170]()

## Skill-Facing Invocation

The repository includes a portable skill definition at `skills/asyncreview/SKILL.md`. Its frontmatter describes the skill as AI-powered GitHub PR/Issue review and restricts the tool surface to `Bash(npx asyncreview:*)`, which keeps skill invocation file-based and portable across agents that understand this skill format. Sources: [skills/asyncreview/SKILL.md:1-5]()

The skill workflow tells an agent to check `GEMINI_API_KEY`, determine whether the repository is private, set `GITHUB_TOKEN` when needed, formulate a specific question, and run `npx asyncreview review --url <URL> -q "<question>"`. It documents PR and issue URL formats, output formats, private repository handling, and expert review examples. Sources: [skills/asyncreview/SKILL.md:19-28](), [skills/asyncreview/SKILL.md:31-45](), [skills/asyncreview/SKILL.md:47-83](), [skills/asyncreview/SKILL.md:144-178](), [skills/asyncreview/SKILL.md:254-283]()

For Grok-Wiki or other catalog-style integrations, the portable boundary should remain the skill file plus the `npx asyncreview` command. The skill source can come from a local file, repository, or catalog entry without changing the runtime contract. Provider neutrality at that integration layer means the catalog should not assume a hosted connector or a specific model provider; however, this repository's current executable implementation still expects Gemini credentials and Gemini-style model names. Sources: [skills/asyncreview/SKILL.md:1-5](), [npx/src/api-key.ts:8-24](), [npx/python/cli/virtual_runner.py:201-218]()

## Notes On Repository Context

No `STRATEGY.md` or `docs/solutions/**` sources were present in the inspected checkout, so this page uses repository code and the local AsyncReview skill file as the source of truth. The root `cli/main.py` exists, but the Node runner's `PYTHON_CODE_ROOT` points to `npx/python`, and the runtime build script copies `npx/python/cli` and `npx/python/cr`; therefore packaged runtime behavior should be verified against `npx/python/cli/*`. Sources: [npx/src/python-runner.ts:12-21](), [scripts/build_runtime_local.sh:65-71]()

## Summary

AsyncReview packages a skill-friendly review workflow as a small npm launcher, a cached platform runtime, and a Python DSPy runner. The command surface is intentionally simple for users and agents, while the runtime handles credential resolution, Python path setup, GitHub or local context assembly, agentic repository exploration tools, and output formatting. The portable skill layer is file-based, but the current model execution path is Gemini-specific and would need an explicit provider abstraction to become fully provider-neutral. Sources: [npx/src/launcher.ts:172-211](), [npx/src/python-runner.ts:315-329](), [npx/python/cli/virtual_runner.py:184-223](), [skills/asyncreview/SKILL.md:19-28]()

---

## 03. API, Web Review Flow & Extension Boundaries

> The closing operational map for the FastAPI backend, GitHub PR cache, diff Q&A and auto-review RLMs, React diff viewer, streaming responses, provider-key boundaries, and what developers should inspect before extending the system.

- Page Markdown: https://grok-wiki.com/public/wiki/asyncfuncai-asyncreview-fb80a82d8c3a/pages/03-api-web-review-flow-extension-boundaries.md
- Generated: 2026-05-19T06:24:33.201Z

### Source Files

- `cr/server.py`
- `cr/github.py`
- `cr/diff_rlm.py`
- `cr/diff_types.py`
- `cr/suggestions.py`
- `web/src/App.tsx`
- `web/src/components/DiffViewer.tsx`
- `web/src/components/ChatPanel.tsx`

<details>
<summary>Relevant source files</summary>
The following files were used as context for generating this wiki page:
- [cr/server.py](cr/server.py)
- [cr/github.py](cr/github.py)
- [cr/diff_rlm.py](cr/diff_rlm.py)
- [cr/diff_types.py](cr/diff_types.py)
- [cr/suggestions.py](cr/suggestions.py)
- [cr/config.py](cr/config.py)
- [cr/rlm_runner.py](cr/rlm_runner.py)
- [web/src/App.tsx](web/src/App.tsx)
- [web/src/components/DiffViewer.tsx](web/src/components/DiffViewer.tsx)
- [web/src/components/ChatPanel.tsx](web/src/components/ChatPanel.tsx)
- [web/src/types.ts](web/src/types.ts)
- [web/vite.config.ts](web/vite.config.ts)
- [tests/test_server.py](tests/test_server.py)
- [tests/test_diff_rlm.py](tests/test_diff_rlm.py)
- [README.md](README.md)
- [INSTALLATION.md](INSTALLATION.md)
</details>

# API, Web Review Flow & Extension Boundaries

This page maps the operational path from a GitHub pull request URL to the local web review experience: PR loading, cached file retrieval, diff rendering, automated review, diff Q&A, streaming RLM progress, and follow-up suggestions. It is intended as the closing developer map for extending AsyncReview without crossing the wrong boundary between frontend state, backend API contracts, GitHub data access, and model-provider configuration.

Implementation claims below are grounded in repository code. The selected Compound Engineering guidance was used as page-shaping guidance from the provided bundled snapshot metadata, not as an installed local workflow; no local `STRATEGY.md` or `docs/solutions/**` source is cited because those source classes were not present in this checkout.

## Product Flow First

The web flow starts with a user entering a GitHub PR URL. The React app posts it to the backend, receives a `reviewId` and PR metadata, selects the first changed file, starts an automated review, and lets the user inspect files in a diff viewer. The chat panel sends questions about the whole PR or the current diff selection to a streaming endpoint, then renders RLM iteration steps followed by answer blocks. Sources: [web/src/App.tsx:107-133](), [web/src/App.tsx:139-164](), [web/src/App.tsx:374-393](), [web/src/components/ChatPanel.tsx:189-215]()

The backend owns GitHub access and model calls. The frontend does not receive provider keys or GitHub tokens; it talks to local `/api/*` routes through Vite's proxy during development. The backend reads model names and tokens from environment-backed config, which keeps BYOK behavior on the server side. Sources: [web/vite.config.ts:4-15](), [cr/config.py:9-14](), [cr/config.py:27-33](), [cr/github.py:39-47]()

```mermaid
flowchart LR
  subgraph UI["React web UI"]
    App["App.tsx\nPR URL, tabs, review state"]
    DiffViewer["DiffViewer.tsx\nfile contents, selection, annotations"]
    ChatPanel["ChatPanel.tsx\nSSE chat + RLM timeline"]
  end

  subgraph API["FastAPI backend"]
    Server["cr/server.py\nHTTP + SSE routes"]
    GitHub["cr/github.py\nPR cache + GitHub API"]
    DiffRLM["cr/diff_rlm.py\nDiffQARLM + FastAutoReview"]
    Suggestions["cr/suggestions.py\nfollow-up prompts"]
  end

  subgraph External["External boundaries"]
    GitHubAPI["GitHub REST API"]
    DSPy["DSPy LM provider\nMAIN_MODEL / SUB_MODEL"]
    Deno["Deno PythonInterpreter\nRLM execution"]
  end

  App -->|POST /api/github/load_pr| Server
  App -->|POST /api/diff/review| Server
  DiffViewer -->|GET /api/github/file| Server
  ChatPanel -->|POST /api/diff/ask/stream| Server
  ChatPanel -->|POST /api/suggestions| Server
  Server --> GitHub
  Server --> DiffRLM
  Server --> Suggestions
  GitHub --> GitHubAPI
  DiffRLM --> DSPy
  DiffRLM --> Deno
```

Sources: [cr/server.py:109-190](), [cr/server.py:219-311](), [cr/github.py:50-80](), [cr/diff_rlm.py:190-208](), [web/src/components/DiffViewer.tsx:96-133]()

## Backend API Surface

| Route | Method | Owner | Purpose | Key dependency |
|---|---:|---|---|---|
| `/api/github/load_pr` | `POST` | `api_load_pr` | Parse and load GitHub PR metadata, files, commits, and comments into a cached `PRInfo` | `load_pr` |
| `/api/github/file` | `GET` | `api_get_file` | Fetch base and head contents for one path by `reviewId` | `get_file_contents` |
| `/api/diff/ask` | `POST` | `api_ask` | Non-streaming diff Q&A returning answer blocks and citations | `DiffQARLM.ask` |
| `/api/diff/review` | `POST` | `api_review` | Automated review returning issues and summary | `FastAutoReview.review` |
| `/api/diff/ask/stream` | `POST` | `api_ask_stream` | SSE version of diff Q&A with iteration, block, citation, and completion events | `DiffQARLM.ask_stream` |
| `/api/suggestions` | `POST` | `api_suggestions` | Generate short follow-up suggestions, with static fallback on error | `SuggestionGenerator` |
| `/health` | `GET` | `health` | Minimal service status | none |

Sources: [cr/server.py:109-187](), [cr/server.py:190-216](), [cr/server.py:287-317]()

The API layer is thin by design: request DTOs are Pydantic models, backend domain objects are dataclasses, and response models serialize dataclass `to_dict()` output. Errors from invalid PR URLs become `400`, missing review/file state becomes `404`, and unexpected processing failures become `500` except suggestions, which intentionally falls back to generic prompts. Sources: [cr/server.py:51-107](), [cr/server.py:113-119](), [cr/server.py:128-137](), [cr/server.py:167-187](), [cr/server.py:207-216]()

## GitHub PR Cache and File Retrieval

`cr/github.py` is the boundary for GitHub REST access. `load_pr()` parses a GitHub PR URL, creates a short UUID-based `review_id`, fetches PR metadata, changed files, commits, and issue comments, converts them into a `PRInfo`, and stores it in the process-local `_pr_cache`. This cache is explicitly in-memory, so `reviewId` values are not durable across backend restarts or multi-process deployments. Sources: [cr/github.py:16-17](), [cr/github.py:20-36](), [cr/github.py:50-80](), [cr/github.py:82-129](), [cr/github.py:142-169]()

File retrieval is intentionally lazy. The PR load response includes changed file metadata and patches, but the diff viewer asks for a single file's base and head contents only after that file is selected. `get_file_contents()` uses the cached PR's `base_sha` and `head_sha` to call GitHub contents endpoints twice, returning `None` on either side for added or deleted files. Sources: [cr/github.py:177-235](), [web/src/components/DiffViewer.tsx:96-133]()

### Cache Shape

```text
load_pr(prUrl)
  -> PRInfo(reviewId, repo, baseSha, headSha, files, commitsList, comments)
  -> _pr_cache[reviewId]

get_file_contents(reviewId, path)
  -> oldFile from baseSha, if it exists
  -> newFile from headSha, if it exists
```

Sources: [cr/diff_types.py:104-150](), [cr/github.py:166-174](), [cr/github.py:190-235]()

## Diff Q&A RLM

`DiffQARLM` is the user-driven Q&A engine. It configures a DSPy `RLM` lazily, using `MAIN_MODEL` for the main LM, `SUB_MODEL` as the sub-LM, and a `PythonInterpreter` backed by the Deno command from `build_deno_command()`. The RLM signature accepts diff context, PR info, selection, conversation, and question, and returns answer text plus citations. Sources: [cr/diff_rlm.py:181-208](), [cr/rlm_runner.py:26-70]()

The non-streaming path fetches only the first five PR files when explicit contexts are not provided. The streaming path fetches up to fifty files in parallel, builds both prompt-visible `DiffFileContext` values and a `file_data` dictionary injected into the interpreter, then manually drives RLM internals to emit `RLMIteration` objects before yielding the final answer blocks and citations. Sources: [cr/diff_rlm.py:210-278](), [cr/diff_rlm.py:280-345](), [cr/diff_rlm.py:375-442]()

The backend parses answer markdown into `AnswerBlock` values and parses citations from either dictionaries or string forms such as `path:10-20`. Tests cover modified, added, deleted, and patch-only context generation, citation parsing, and markdown/code block splitting. Sources: [cr/diff_rlm.py:111-178](), [tests/test_diff_rlm.py:17-98](), [tests/test_diff_rlm.py:100-166]()

## Streaming Response Contract

The SSE endpoint returns `EventSourceResponse` from `_stream_ask_response()`. Event payloads are JSON inside `data:` lines and use a `type` discriminator:

| Event type | Meaning | Frontend behavior |
|---|---|---|
| `start` | The backend accepted the question | Logged only |
| `iteration` | One RLM reasoning/code/output step | Added to the current assistant message timeline |
| `block` | One final answer block | Appended to assistant content |
| `citations` | Final citation list | Emitted by backend, but currently not handled in `ChatPanel` |
| `complete` | Stream is complete | Emitted by backend |
| `error` | Backend exception during stream | Shown as chat error |

Sources: [cr/server.py:219-284](), [cr/server.py:287-311](), [web/src/components/ChatPanel.tsx:229-295](), [web/src/components/ChatPanel.tsx:297-322]()

A developer extending citations should inspect both sides: the backend already streams a `citations` event, while the chat panel currently processes `start`, `iteration`, `block`, and `error` but not `citations`. The `onCitationClick` prop is also accepted and intentionally unused with a TODO, so clickable answer citations are an unfinished integration boundary. Sources: [cr/server.py:271-281](), [web/src/components/ChatPanel.tsx:169-176](), [web/src/components/ChatPanel.tsx:252-288]()

## Automated Review RLM

`FastAutoReview` is separate from `DiffQARLM`. It uses `SUB_MODEL` and a DSPy `ChainOfThought` predictor over `ReviewSignature`, not the iterative RLM loop. It builds context from PR patches for up to one hundred files, asks for JSON-like issues plus a summary, parses the returned list, and converts items into `ReviewIssue` dataclasses. Sources: [cr/diff_rlm.py:446-475](), [cr/diff_rlm.py:487-533](), [cr/diff_rlm.py:535-574]()

The instruction block strongly constrains automated-review citations to visible diff hunks. This is important because the frontend validates citation line numbers against loaded old/new file contents before creating line annotations, and invalid citations are dropped from the annotation list. Sources: [cr/diff_rlm.py:493-521](), [web/src/components/DiffViewer.tsx:227-275]()

One extension caveat: `FastAutoReview` asks the model for categories including `bug`, and `App.tsx` separates `bug` issues from flags, but the backend `ReviewIssue` type annotation lists only `investigation` and `informational`. Runtime Python will still carry the string, but type hints and generated assumptions are inconsistent. Inspect this before tightening validation or adding API schemas around review issues. Sources: [cr/diff_types.py:81-90](), [cr/diff_rlm.py:493-500](), [web/src/App.tsx:178-180](), [web/src/types.ts:99-106]()

## React Diff Viewer Boundary

`DiffViewer` owns file-content loading and translation into `@pierre/diffs/react` props. It fetches `/api/github/file` when `reviewId` or selected path changes, converts backend `FileContents` to Pierre file objects, synthesizes empty old/new files for added/deleted paths, and passes line selection and annotations to `MultiFileDiff`. Sources: [web/src/components/DiffViewer.tsx:96-162](), [web/src/components/DiffViewer.tsx:281-286](), [web/src/components/DiffViewer.tsx:449-471]()

Selection flows upward from `DiffViewer` to `App`, then down into `ChatPanel`. A selected line range becomes a `DiffSelection` with `path`, `side`, `startLine`, `endLine`, and `mode`; the chat panel includes that selection in the streaming ask request. Sources: [web/src/components/DiffViewer.tsx:164-178](), [web/src/App.tsx:90-92](), [web/src/App.tsx:239-246](), [web/src/components/ChatPanel.tsx:203-214]()

Issue annotations flow the other direction. Auto-review issues live in `App`, are split into Bugs and Flags tabs, and are passed into the diff viewer. Clicking an issue uses its first citation to switch files, highlight lines, and focus the inline annotation popover after scrolling. Sources: [web/src/App.tsx:94-105](), [web/src/App.tsx:166-180](), [web/src/App.tsx:239-246](), [web/src/components/DiffViewer.tsx:37-94]()

## Provider-Key and Portability Boundaries

AsyncReview is BYOK-oriented in this implementation: model and GitHub credentials are environment variables read by the Python backend, while the web client uses relative API paths. Defaults are Gemini model strings, but `MAIN_MODEL` and `SUB_MODEL` are env-configurable DSPy LM identifiers, so a provider change should be treated as a backend configuration and DSPy compatibility concern rather than a React concern. Sources: [cr/config.py:9-14](), [cr/diff_rlm.py:190-208](), [cr/diff_rlm.py:461-469](), [web/src/components/ChatPanel.tsx:200-215]()

GitHub access is similarly backend-contained. `GITHUB_TOKEN` is read in config and attached only to GitHub API headers; frontend requests identify loaded PRs by `reviewId`, not by token or provider key. This keeps the web flow portable across file, repository, or catalog skill sources as long as those sources are adapted behind backend ingestion and retrieval APIs rather than exposed directly to the browser. Sources: [cr/config.py:27-29](), [cr/github.py:39-47](), [cr/github.py:172-235]()

Installation docs confirm the local server/web split: the FastAPI server runs at `127.0.0.1:8000`, the web UI at `localhost:3000`, and Deno is required for sandboxed code execution. Sources: [INSTALLATION.md:13-19](), [INSTALLATION.md:106-125]()

## What to Inspect Before Extending

| Extension goal | Inspect first | Reason |
|---|---|---|
| Add a new API route | `cr/server.py` request/response models and exception patterns | API DTOs are centralized and thin wrappers over backend modules |
| Change PR metadata | `cr/github.py`, `cr/diff_types.py`, `web/src/types.ts` | Metadata crosses GitHub ingestion, backend serialization, and frontend typing |
| Make streamed citations clickable | `_stream_ask_response`, `ChatPanel`, `App.handleCitationClick` | Backend emits citations, but chat currently ignores that event |
| Improve automated review categories | `ReviewIssue`, `FastAutoReview`, `App` issue filters | `bug` is used by UI/model instructions but not listed in the Python type annotation |
| Change model provider or model names | `cr/config.py`, `cr/diff_rlm.py`, `cr/suggestions.py` | Provider choice is expressed through DSPy LM strings and environment variables |
| Change diff rendering behavior | `DiffViewer.tsx`, `web/src/types.ts` | Selection, highlighting, annotations, and file content normalization are local to the viewer |
| Scale beyond one backend process | `cr/github.py` cache | The current PR cache is process-local memory |

Sources: [cr/server.py:51-107](), [cr/github.py:16-17](), [cr/diff_types.py:104-150](), [web/src/types.ts:10-48](), [web/src/components/ChatPanel.tsx:169-176]()

## Test Coverage Landmarks

Server tests cover the health endpoint, PR loading, file retrieval for normal and added files, missing review handling, Q&A response shape, and automated review response shape. Diff RLM tests cover context construction, citation parsing, and answer block parsing. These are good first checks after changing API contracts or parsing logic, but they do not fully exercise live GitHub, live model calls, or browser SSE rendering. Sources: [tests/test_server.py:17-169](), [tests/test_diff_rlm.py:17-166]()

## Closing Summary

The main extension rule is to preserve boundaries: React owns interaction state and diff presentation, FastAPI owns transport and serialization, `cr/github.py` owns GitHub access and the process-local PR cache, and `cr/diff_rlm.py` owns model-driven review and Q&A behavior. Before adding features, trace whether the change belongs to file ingestion, typed DTOs, streamed events, diff rendering, or provider configuration, then update both sides of any contract that crosses that boundary. Sources: [cr/server.py:109-190](), [cr/github.py:166-235](), [cr/diff_rlm.py:181-208](), [web/src/components/DiffViewer.tsx:96-178](), [web/src/components/ChatPanel.tsx:189-328]()

---
