Skip to content

likhith-v1/prr

Repository files navigation

prr

CI Status License: MIT Python Ollama Static analysis

Status: v0.1.0 — first release. The full pipeline — chunking, static pass, LLM review, filtering, terminal/JSON/markdown output, and GitHub PR mode — is built and tested: 124 tests green on Linux and macOS CI, with fake model backends so CI never needs a live Ollama. See CHANGELOG.md.

A local Python code-review CLI: tree-sitter chunking → ruff / mypy / bandit → local LLM review → schema validation → terminal or GitHub PR output. Review a file, scan a project, or post inline comments on a pull request — fully offline against an Ollama model, no cloud API keys required for the review brain.

The design bet is a reviewer you can trust precisely because it trusts nothing: every model finding must parse, validate against one shared schema, and anchor to a real source line — or it's dropped. The cat theme is load-bearing: error → 😾, warning → 🙀, info → 😸, and a clean run ends in a purr.

$ uv run prr review sample.py

  /\_____/\
 (  ⩺ × ⩻ )   prr is not happy
  > !! !! <

5 finding(s) in sample.py

╭─────────────────────────────────────────────╮
│ 😾  line 13  [bug]                          │
│                                             │
│ F821 Undefined name `nam`                   │
│                                             │
│ suggestion:                                 │
│ print("hi " + name)                         │
╰─────────────────────────────────────────────╯

╭─────────────────────────────────────────────╮
│ 🙀  line 35  [bug]                          │
│                                             │
│ E722 Do not use bare `except`               │
╰─────────────────────────────────────────────╯
...

Findings render as severity-coloured panels; a clean run prints a purring cat instead. Exit code 1 on any error-severity finding, so it slots into scripts and hooks.

See CONTRIBUTORS.md for maintainer and assistant acknowledgements.


Highlights

  • Fully local — the review brain is an Ollama model on your machine (or a box you point at). No cloud APIs, no keys, nothing leaves your network.
  • Fail-closed on model output — unparseable JSON is dropped after one retry; invalid, unanchored, or out-of-range findings are dropped, never trusted blindly.
  • One schema end to end — every producer and consumer (model, static tools, filter, CLI, GitHub output) speaks core.schema.Finding.
  • One batched PR reviewprr review --pr owner/repo#n posts a single review with inline comments, one-click GitHub suggestion blocks, and a cat verdict.
  • Seeded eval for model swapsprr eval replays known-bug fixtures through the real pipeline so you can compare models before switching.

What it does

Layer Responsibility Where
Chunking tree-sitter split of Python files into functions, methods, classes, and module-level code; CRLF-safe, 1-based line numbers core/ingest.py
Static pass ruff, mypy, bandit on Python; eslint on TS/JS when installed; output normalized to Finding core/detect_static.py
Context assembly attaches file context and prior static findings to each chunk before the model sees it core/context.py
Model seam single review() entry point; Ollama backend (default) or vLLM, selected in config core/model.py
Filter validates anchors and line ranges, dedupes, applies severity/confidence thresholds and caps core/filter.py
CLI prr review / scan / eval, terminal panels or `--format json markdown`
GitHub output PR diff parsing and one batched inline review with suggestions core/github_out.py, core/diff.py
Eval seeded regression cases run through the normal pipeline, exit codes for CI core/eval.py, core/eval_cases/

Quick start

Requirements: macOS, native Linux, or WSL2 · Python 3.11+ · uv · Ollama with the configured model pulled. Static tools (ruff, mypy, bandit) install as Python dependencies — no separate system installs.

# 1. Clone and install (pinned in uv.lock)
git clone https://github.com/likhith-v1/prr.git
cd prr
uv sync --extra test

# 2. Pull the default model (one-time)
ollama pull qwen2.5-coder:14b

# 3. Review the bundled sample file
uv run prr review sample.py

# 4. Scan a project
uv run prr scan .

# 5. Run the test suite (no Ollama needed)
uv run --extra test pytest

Optional: install eslint on your PATH (e.g. npm install -g eslint) to include .ts, .tsx, .js, and .jsx files in prr scan — static analysis only; the LLM pass stays Python-only.


Requirements

Area Requirement
Platform macOS, native Linux, or WSL2 (no native Windows — use WSL2)
Python 3.11+
Tooling uv for dependency management and command running
Model backend Ollama with the configured model pulled (ollama pull qwen2.5-coder:14b), or a vLLM server (uv sync --extra vllm, backend: vllm)
Static tools ruff, mypy, bandit bundled as dependencies; eslint optional on PATH for TS/JS

Architecture

%%{init: {"themeVariables": {"fontSize": "16px"}}}%%
flowchart TB
    files["input files<br/>review · scan · PR added lines"]
    chunk["tree-sitter chunker<br/>core/ingest.py<br/>functions · methods · classes · module-level"]
    static["static pass<br/>core/detect_static.py<br/>ruff · mypy · bandit · eslint"]
    ctx["context assembly<br/>core/context.py<br/>chunk + prior static findings"]
    model["model seam<br/>core/model.py<br/>Ollama default · vLLM optional"]
    filt["validate + filter<br/>core/filter.py<br/>anchor · dedupe · threshold · cap"]
    cli["terminal · json · markdown<br/>frontends/cli.py"]
    gh["one batched PR review<br/>core/github_out.py"]

    files --> chunk
    files --> static
    chunk --> ctx
    static --> ctx
    ctx --> model
    model -->|object-wrapper JSON| filt
    static --> filt
    filt --> cli
    filt --> gh
Loading

Every producer and consumer speaks one schema — core.schema.Finding:

class Finding(BaseModel):
    path: str
    line: int
    end_line: int | None = None
    severity: Literal["info", "warning", "error"]
    category: Literal["bug", "security", "style", "perf", "test", "other"]
    comment: str
    suggestion: str | None = None
    source: Literal["llm", "ruff", "mypy", "bandit", "eslint"]
    confidence: float = 1.0

Model output must be a JSON object: {"findings": [...]}. Invalid output fails closed:

  • unparseable JSON is dropped after one retry
  • invalid findings are dropped
  • findings whose snippet cannot be anchored to the source line are dropped
  • findings outside the file's line range are dropped by the filter

When static tools and the LLM flag the same line, static tools keep the located fact; the LLM can add explanation or a replacement suggestion.


Repository layout

prr/
├── core/
│   ├── schema.py          # Finding — the shared contract everything speaks
│   ├── ingest.py          # tree-sitter chunking (1-based, CRLF-safe)
│   ├── context.py         # context assembly for model review chunks
│   ├── detect_static.py   # ruff / mypy / bandit / eslint runners + parsers
│   ├── model.py           # the model seam — Ollama and vLLM backends
│   ├── filter.py          # validate, dedupe, threshold, cap, sort
│   ├── diff.py            # unified-diff (GitHub patch) parsing for PR mode
│   ├── github_out.py      # fetch PR data, post one batched inline review
│   ├── eval.py            # seeded regression eval
│   ├── eval_cases/        # anti-pattern fixtures (.py.txt) + cases.yaml
│   ├── config.py          # config.yaml loading and validation
│   └── prompts/review.txt # the review prompt
├── frontends/
│   ├── cli.py             # prr review / scan / eval
│   └── action_entry.py    # GitHub Actions entrypoint (self-hosted runner)
├── tests/                 # 124 tests, fake model backends, no live Ollama
├── docs/plan/             # the original week-by-week build plan
├── config.yaml            # default configuration
└── sample.py              # bundled buggy file to try prr on

Usage

Review one file

uv run prr review path/to/file.py

Scan a file or directory

uv run prr scan .
uv run prr scan src/

When eslint is on PATH, scan also includes .ts, .tsx, .js, and .jsx files (static analysis only). Ignored paths come from config.yaml.

Machine-readable output

uv run prr review sample.py --format json
uv run prr scan . --format markdown

Review a GitHub pull request

Fetches changed Python files at the PR head, reviews only added lines, and posts one batched review with inline comments and a summary.

export GITHUB_TOKEN=...   # PAT with pull-request access
uv run prr review --pr owner/repo#123 --dry-run   # preview without posting
uv run prr review --pr owner/repo#123             # post the review

With GitHub CLI authenticated:

gh auth login
export GITHUB_TOKEN="$(gh auth token)"
uv run prr review --pr owner/repo#123

Run the seeded eval

uv run prr eval

Configuration

prr reads config.yaml from the current working directory. Pass --config to override:

model: qwen2.5-coder:14b
# ollama_host: http://localhost:11434
# backend: vllm                              # default is ollama
# vllm_base_url: http://localhost:8000/v1
severity_threshold: info
min_confidence: 0.7
max_comments_per_file: 20
max_comments_per_pr: 10
ignore_paths:
  - .git/**
  - .venv/**
  - .uv-cache/**
  - .pytest_cache/**
  - .ruff_cache/**
  - __pycache__/**
Setting Purpose
model Model name (Ollama tag or vLLM model ID)
ollama_host Ollama server URL (see below)
backend ollama (default) or vllm
vllm_base_url vLLM server URL, e.g. http://localhost:8000/v1
severity_threshold Drop findings below this severity
min_confidence Drop LLM findings below this confidence
max_comments_per_file Cap findings per file after filtering
max_comments_per_pr Cap inline comments posted per PR review
ignore_paths Glob patterns skipped by prr scan

Ollama host resolution (first match wins):

  1. config.yamlollama_host
  2. OLLAMA_HOST environment variable
  3. Ollama client default (http://localhost:11434)

WSL2 and Ollama on Windows

On WSL2 2.3+, http://127.0.0.1:11434 usually works without any extra configuration — recent WSL2 forwards localhost automatically to the Windows host. Verify before running prr:

curl http://127.0.0.1:11434/api/tags

If that fails, find the Windows host IP and set ollama_host:

# Most reliable on WSL2: read the nameserver entry
grep nameserver /etc/resolv.conf | awk '{print $2}'
# config.yaml
ollama_host: http://192.168.x.x:11434

Or set it as an environment variable instead:

export OLLAMA_HOST=http://192.168.x.x:11434

Windows side checklist:

  • Ollama is running (tray icon or ollama serve).
  • "Expose Ollama to the network" is enabled in Ollama settings.
  • Windows Firewall allows inbound TCP 11434 on the Private profile.
  • The model is pulled: ollama pull qwen2.5-coder:14b.

Optional — mirrored networking (makes localhost more reliable across WSL restarts). In %UserProfile%\.wslconfig:

[wsl2]
networkingMode=mirrored

Then restart WSL: wsl --shutdown, reopen the terminal.


GitHub PR reviews

prr review --pr owner/repo#n:

  • posts inline comments on the RIGHT (new) side of the diff
  • renders suggestion fields as one-click GitHub suggestion blocks
  • includes a summary with severity counts and a cat verdict
  • drops findings outside added lines
  • notes patchless or skipped Python files in the summary
  • runs file-scoped static analysis only (ruff and bandit; mypy is skipped until full-checkout review is supported)
  • caps comments at max_comments_per_pr; overflow is noted in the summary

Use --dry-run to inspect the review without posting.


GitHub Actions automation

.github/workflows/review.yml runs prr on pull_request opened, synchronize, and reopened events. It targets a self-hosted runner labeled self-hosted and gpu — install that runner on a machine that can reach Ollama.

The workflow:

  • checks out the trusted base commit, then reviews the PR head SHA from the event payload
  • uses the repository-scoped Actions GITHUB_TOKEN to post reviews
  • stays green when prr finds code issues; it fails only on runtime errors (config, GitHub API, model backend, or review posting failures)
  • runs only for same-repository PRs (fork PRs are skipped)

Security note: a self-hosted runner executes repository code on your machine. Use trusted or private repositories, and treat the runner host as part of your trust boundary.


Eval and model swaps

prr eval runs the normal pipeline over small synthetic cases stored as package fixtures (.py.txt files materialized in a temp workspace, so prr scan . never reviews them). It reports caught, missed, and false-positive findings; only warning- and error-severity findings outside the expected set count as false positives — info-severity noise is tolerated.

Exit code Meaning
0 No misses or false positives
1 Regression detected
2 Config, case loading, model, or runtime failure

Model swap procedure:

  1. Change model: in config.yaml.
  2. Run uv run prr eval.
  3. Keep the new model only if eval results improve or hold steady.

Tech stack

Area Stack
Schema & validation pydantic v2 — core.schema.Finding is the contract
Chunking tree-sitter + tree-sitter-python
Model backends ollama client (default) · openai client for vLLM (--extra vllm)
Static analysis ruff · mypy · bandit (bundled) · eslint (optional, on PATH)
Terminal UI rich — severity-coloured panels and the cats
Config & HTTP PyYAML · httpx
Testing pytest with fake model backends — CI never needs a GPU or Ollama

Development

uv run --extra test pytest
uv run ruff check core frontends tests

If the environment blocks the default uv cache:

uv --cache-dir .uv-cache run --extra test pytest
uv --cache-dir .uv-cache run ruff check core frontends tests

CI runs tests and lint on ubuntu-latest and macos-latest for every push and pull request. See AGENTS.md for contributor conventions and docs/plan/ for the original build plan.


Future plans

  • mypy in PR mode — enable the type-check pass on PRs once full-checkout review lands (today PR mode is file-scoped: ruff + bandit only).
  • LLM pass for TS/JS — eslint already feeds the static pass; extend chunking and the review prompt beyond Python.
  • More languages — the core is language-agnostic: tree-sitter grammar + the matching linter (clippy, etc.) per language.
  • Verified suggestions — apply high-severity suggestions in a throwaway clone and run ruff/tests before surfacing them.

Changelog

See CHANGELOG.md for release history.

License

MIT — see LICENSE.

About

A local Python code-review CLI that pairs ruff, mypy, and bandit with an Ollama model — review files, scan projects, and post inline GitHub PR comments.

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages