Skip to main content

Diffrat

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.

Sample report

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

Setup

Requires Python 3.11+ and git on PATH.

pip install diffrat
diffrat --version
diffrat review --base main

diffrat 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 --version

For local tests, lint, typecheck, and diffrat review --check, install extras:

pip install -e ".[dev]"

External dogfood sessions: docs/feedback-checklist.md.

Demo

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

Common commands

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.

Status

1.1.1 is the current release on PyPI as diffrat (formerly developed as Numbat; see D-008). 1.0.0 was the first product release:

  • diffrat review with unstaged, --staged, --base, and --range; optional --json and --brief (triage without hunks)
  • Bounded hunks, git context, file categories, deterministic Focus/Risk hints
  • Optional --check for 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.

Focus / Risk, categories, and ordering

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.

Review quality

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.

Changes section (diff hunks)

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.

Single-file deep diff (--hunks-for)

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

Optional local checks (--check)

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.

Scriptable gate (--fail-on)

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_secret

With --json, output includes top-level fail_on.requested / fail_on.matched.

Configuration

Offline and deterministic by default (D-005). No API keys required for the heuristic report.

Optional LLM analysis (Phase 3)

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

  1. pyproject.toml[tool.diffrat] (base)
  2. .diffrat.toml overrides duplicate keys

Invalid content-rule regex → stderr warning and skip; review continues.

[tool.diffrat.checks]

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"

[tool.diffrat.content_rules]

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.

Tests and quality

pytest
ruff check .
mypy .

Architecture and context

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

How this project is built

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.

Limitations

  • 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 numbat was already taken; this product uses diffrat (D-008)

License

MIT — see LICENSE.

Contact and contributions

Maintained by Szymon Iwacz. Contributions via pull request; agents never merge except under authorized eligible self-correcting-review auto-merge.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

diffrat-1.1.1.tar.gz (39.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

diffrat-1.1.1-py3-none-any.whl (41.3 kB view details)

Uploaded Python 3

File details

Details for the file diffrat-1.1.1.tar.gz.

File metadata

  • Download URL: diffrat-1.1.1.tar.gz
  • Upload date:
  • Size: 39.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for diffrat-1.1.1.tar.gz
Algorithm Hash digest
SHA256 63959ce58ae7ea100522489a229a3d00eb5c6b24a55e5bda11813695f3178fd4
MD5 637c421f21dd60af9f78d79c4fee32cd
BLAKE2b-256 8f547d7cfa41fa7ca8dd76ce49d1265a44f77eaf03c7f4d0fb3a410082bb0b13

See more details on using hashes here.

Provenance

The following attestation bundles were made for diffrat-1.1.1.tar.gz:

Publisher: publish.yml on szymoniwacz/diffrat

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file diffrat-1.1.1-py3-none-any.whl.

File metadata

  • Download URL: diffrat-1.1.1-py3-none-any.whl
  • Upload date:
  • Size: 41.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for diffrat-1.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 fb2d1bacff577e07fbe38b2ddd85cccb102a947f9bea01945cf9779e5d8c6f25
MD5 d30fe71f0bdc4022c0cbdbb81d581fd6
BLAKE2b-256 99ac88bd03bb3fdf6fce6cbb493bedf175667f4f5a5d3dbf662ce99448dba0d7

See more details on using hashes here.

Provenance

The following attestation bundles were made for diffrat-1.1.1-py3-none-any.whl:

Publisher: publish.yml on szymoniwacz/diffrat

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

1.1.2

2 files

This release

1.1.1 This release

2 files

1.1.0

2 files

1.0.0

2 files

0.0.1

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page