Diffrat is a local review triage CLI. Point it at a git diff and it tells you what to look at first — offline by default, with an optional LLM layer only when you configure it.
Use it before opening a PR, or when reviewing a branch, to get a ranked file list, Focus/Risk hints, and bounded hunks without leaving the terminal.
The GitHub About section uses the same pitch: local git diff review triage CLI.
Example of diffrat review --base main on a small feature branch (sections and
formatting match current text output):
Review Report
=============
Git context
-----------
Branch: feature/risk-score-tweak
Base: main
Commits since base: 2
Recent commits:
a1b2c3d Tune risk weights for config paths
e4f5a6b Cover scoring edge cases in tests
Summary
-------
Files changed: 4
Lines added: 83
Lines deleted: 15
Total lines changed: 98
Review quality
--------------
- Understand in seconds: ok
- One thing well: ok
- Safe to change in six months: warn (tests_touched)
Files
-----
source
src/diffrat/scoring.py [source] risk=17 +28 -6
src/diffrat/review.py [source] risk=7 +12 -3
tests
tests/test_scoring.py [tests] risk=19 +35 -4
docs
README.md [docs] risk=5 +8 -2
Review order
------------
1. tests/test_scoring.py [tests] (+35 -4 lines)
2. src/diffrat/scoring.py [source] (+28 -6 lines)
3. src/diffrat/review.py [source] (+12 -3 lines)
4. README.md [docs] (+8 -2 lines)
Changes
-------
tests/test_scoring.py
@@ -10,0 +11,8 @@
+def test_config_boost_for_toml() -> None:
+ assert _config_boost('pyproject.toml') > 0
src/diffrat/scoring.py
@@ -40,7 +40,10 @@
RISK_WEIGHT_CONFIG_CATEGORY = 10
+
+def _config_boost(path: str) -> int:
+ return RISK_WEIGHT_CONFIG_CATEGORY if path.endswith('.toml') else 0
src/diffrat/review.py
@@ -88,6 +88,9 @@
parser.add_argument("--json", action="store_true")
+ parser.add_argument(
+ "--fail-on",
+ help="comma-separated hint codes that fail the review",
+ )
README.md
@@ -1,3 +1,5 @@
# Diffrat
+
+Local review triage for git diffs.
Focus / Risk
------------
- [warn] [tests_touched] Tests touched — confirm coverage matches behavior changes
Requires Python 3.11+ and git on PATH.
pip install diffrat
diffrat --version
diffrat review --base maindiffrat review needs a real diff. On a clean main with no local changes,
--base main returns exit code 2 (no changes on branch since main) — that
is expected. Use unstaged/staged edits or a feature branch, then rerun.
From source (development):
git clone https://github.com/szymoniwacz/diffrat.git
cd diffrat
pip install -e .
diffrat --versionFor local tests, lint, typecheck, and diffrat review --check, install extras:
pip install -e ".[dev]"External dogfood sessions: docs/feedback-checklist.md.
Five-minute presenter runbook (install → real diff → --brief → --fail-on,
triage not auto-approve): docs/demo/runbook.md.
Includes a labeled sample brief report under docs/demo/.
Run from inside a git repository:
# Unstaged changes (working tree vs index) — default
diffrat review
# Staged changes
diffrat review --staged
# Branch vs base (merge-base through HEAD; default base is main)
diffrat review --base main
# Two-dot range
diffrat review --range main..feature
# Structured JSON for scripting
diffrat review --base main --json
# Triage-first report (omit Changes / hunk payloads)
diffrat review --base main --brief
diffrat review --base main --brief --json
# Path-scoped local validators/tests for touched files
diffrat review --base main --check
diffrat review --help--json writes a structured document to stdout (schema_version identifies the
format). When LLM analysis is enabled, JSON includes additive llm_status and
llm_findings on success or llm_status and llm_error on failure; all
llm_* keys are omitted when LLM is disabled.
Errors and empty-diff messages go to stderr with the same exit codes as the
text report.
--brief keeps Git context (when applicable), Summary, Files, Review order, and
Focus/Risk, but omits the text Changes section. With --json, changes.files
is empty while changes.limits remains. --brief works with --staged,
--base, and --range. It is mutually exclusive with --hunks-for.
1.1.2 is the current release on PyPI as
diffrat (formerly developed as Numbat;
see D-008). 1.0.0 was the first product release:
diffrat reviewwith unstaged,--staged,--base, and--range; optional--jsonand--brief(triage without hunks)- Bounded hunks, git context, file categories, deterministic Focus/Risk hints
- Optional
--checkfor path-scoped local validators and tests - Optional LLM analysis when
DIFFRAT_LLM_*is set (ADR-0001 / D-005); heuristics-only remains the default without API keys
Phase 4 (CI bots / GitHub App) is deferred. See .ai/project/roadmap.md.
Each changed file gets a coarse category: source, tests, config, docs,
ci, or other.
Focus/Risk hints are deterministic (paths, diff size, content on source /
ci hunks). No network or API key is required for the heuristic report. JSON
adds category on each file and a top-level focus_risk array
(schema_version stays "1"). Each hint has code, message, and
severity (risk, warn, or info) from src/diffrat/scoring.py. Content
hints may include optional path and line. Hints sort by severity, then code.
Each file also gets a non-negative integer risk_score. The text Files list
and JSON files[] sort by descending score (ties by path). Files groups by
category (source, tests, ci, config, docs, other). Review order
lists up to five highest-priority paths. Text lines show risk=<score> (binary
files use fixed score 5).
| Signal | Weight constant | Points |
|---|---|---|
| Line share of non-binary diff | RISK_WEIGHT_LINE_SHARE_MAX (50) |
scaled by file lines ÷ total |
| Security-sensitive path | RISK_WEIGHT_SECURITY_SENSITIVE |
40 |
source without tests in diff |
RISK_WEIGHT_SOURCE_WITHOUT_TESTS |
25 |
ci category |
RISK_WEIGHT_CI_CATEGORY |
20 |
config category |
RISK_WEIGHT_CONFIG_CATEGORY |
10 |
| Binary file | RISK_WEIGHT_BINARY |
5 (fixed) |
Common hint themes include large diffs, tests/config/CI touched, security-sensitive
paths, rename/copy, category composition, generated artifacts, lockfile/manifest
consistency, git-context signals on branch/range reviews, and content codes such
as possible_secret, debug_leftover (including Ruby binding.pry / byebug /
call-like puts(), dangerous_call, broad_exception,
hardcoded_url_or_ip, plus validator typo patterns (e.g.
PROJECT_EXECUTOR_COMMENT_FILTER). Full code list: src/diffrat/scoring.py.
After Summary, text reports include a Review quality section that rolls
Focus/Risk hints into three pillars (understand in seconds, one thing well,
safe to change in six months). JSON adds review_quality.pillars[] with per-pillar
status (ok / warn / risk) and matched codes.
Pillar definitions, status rules, and the full code→pillar table:
docs/review-quality.md.
Text reports include a Changes section with unified-diff hunks unless
--brief is set. JSON has a top-level changes object (changes.files is empty
under --brief). Output is bounded:
| Limit | Value |
|---|---|
| Max files in Changes | 20 |
| Max diff lines per file | 100 |
Limits appear in diffrat review --help and JSON changes.limits.
--hunks-for=<path> shows Changes for one repository-relative path only
(500-line budget). Files, Review order, and Focus / Risk still
cover the full diff. Missing path → exit 1. Cannot combine with --brief.
diffrat review --staged --hunks-for=src/foo.py
diffrat review --base main --hunks-for=src/foo.py --json| Touched path pattern | Command run |
|---|---|
ci/ or .github/workflows/ |
ci_validator from [tool.diffrat.checks] when configured (no default command) |
src/<package>/<module>.py |
pytest tests/test_<module>.py, mypy src/<package>/<module>.py, and bandit -r … when bandit is on PATH |
tests/test_<name>.py |
pytest tests/test_<name>.py |
other tests/ files |
pytest tests |
pyproject.toml |
ruff check . and pip-audit when available |
| lockfile / dependency manifests | pip-audit when available |
Failed checks → stderr + exit 3. Missing optional tools are recorded as
skipped and do not fail the run alone.
Fail when requested hint codes appear (comma-separated, no spaces):
| Exit code | Meaning |
|---|---|
0 |
Success (no requested codes matched) |
1 |
Git error, usage error, or invalid --fail-on token |
2 |
Empty diff (evaluated before --fail-on) |
3 |
--check failure (takes precedence over exit 4) |
4 |
At least one requested hint code matched |
diffrat review --base main --fail-on=regex_typo,possible_secret
diffrat review --base main --json --fail-on=regex_typo,possible_secretWith --json, output includes top-level fail_on.requested / fail_on.matched.
Offline and deterministic by default (D-005). No API keys required for the heuristic report.
Opt-in only — no DIFFRAT_LLM_* variables means no network requests. When
provider and API key are set, Diffrat sends diff-scoped prompts to an
OpenAI-compatible endpoint.
| Variable | Required | Purpose |
|---|---|---|
DIFFRAT_LLM_PROVIDER |
When enabled | e.g. openai, ollama |
DIFFRAT_LLM_API_KEY |
When enabled | API key or token |
DIFFRAT_LLM_BASE_URL |
Local/custom | API root URL (not /chat/completions) |
Copy-paste setup, troubleshooting, and JSON field shapes:
docs/llm.md.
Optional per-repo TOML at the git root (or cwd):
pyproject.toml→[tool.diffrat](base).diffrat.tomloverrides duplicate keys
Invalid content-rule regex → stderr warning and skip; review continues.
Map check code → command string (no shell=True). In v1, only ci_validator
may be configured. Without this entry, CI/workflow path changes still produce a
Focus/Risk warning, but --check does not run a project-specific validator.
[tool.diffrat.checks]
ci_validator = "python ci/validate-workflow-contracts.py --mode project"Regex rules on added hunk lines. Shorthand or table form with optional
paths. See D-006 and this repo’s pyproject.toml for dogfood examples.
Local (same gates as CI on pull requests and main). CI runs that set on
Python 3.11, 3.12, and 3.13:
pytest
ruff format --check src tests
ruff check .
mypy .
bandit -r src/diffrat/checks.py src/diffrat/review_quality.py src/diffrat/scoring.py.ai/project/product-context.md— product identity and workflows.ai/project/scope.md— in-scope and deferred work.ai/docs/architecture-direction.md— CLI component boundaries
Developed with a documentation-first AI delivery workflow. That system is
private and not part of the installable CLI — Setup above is enough to run
diffrat. Maintainer setup: docs/ai-workflow-setup.md.
- No CI integration or GitHub App (Phase 4 deferred)
- LLM analysis needs explicit env configuration; non-OpenAI-shaped APIs need a compatibility layer or future adapter (ADR-0001)
- The PyPI name
numbatwas already taken; this product usesdiffrat(D-008)
MIT — see LICENSE.
Maintained by Szymon Iwacz. Contributions via pull request; agents never merge
except under authorized eligible self-correcting-review auto-merge.