Metadata-Version: 2.3
Name: diffrag
Version: 0.3.0
Summary: AI-powered git diff analysis and code review with RAG context
Keywords: code-review,git,ai,llm,rag,openai
Author: Eduardo Nunez
Author-email: Eduardo Nunez <edu.a.n.van.eyl@gmail.com>
License: MIT License
         
         Copyright (c) 2026 Eduardo Nunez
         
         Permission is hereby granted, free of charge, to any person obtaining a copy
         of this software and associated documentation files (the "Software"), to deal
         in the Software without restriction, including without limitation the rights
         to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
         copies of the Software, and to permit persons to whom the Software is
         furnished to do so, subject to the following conditions:
         
         The above copyright notice and this permission notice shall be included in all
         copies or substantial portions of the Software.
         
         THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
         IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
         FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
         AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
         LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
         OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
         SOFTWARE.
Classifier: Development Status :: 3 - Alpha
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: Natural Language :: English
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Software Development
Classifier: Topic :: Software Development :: Version Control :: Git
Requires-Dist: chromadb>=1.5.8
Requires-Dist: click>=8.1
Requires-Dist: httpx>=0.28
Requires-Dist: pathspec>=0.12
Requires-Dist: platformdirs>=4.0
Requires-Dist: rich>=15.0.0
Requires-Dist: tiktoken>=0.9
Requires-Dist: tqdm>=4.67.3
Requires-Python: >=3.12
Project-URL: Homepage, https://gitlab.com/eanveyl-group/ai-code-reviewer
Project-URL: Repository, https://gitlab.com/eanveyl-group/ai-code-reviewer
Project-URL: Issues, https://gitlab.com/eanveyl-group/ai-code-reviewer/-/issues
Project-URL: Changelog, https://gitlab.com/eanveyl-group/ai-code-reviewer/-/blob/main/CHANGELOG.md
Description-Content-Type: text/markdown

# diffrag

AI-powered code reviewer for git repositories. Point it at two branches and get an LLM-generated review that understands the broader codebase through RAG (Retrieval-Augmented Generation).

## Features

- **AI code review** — analyzes the diff between any two branches and reports on breaking changes, API surface changes, SOLID violations, code smells, security concerns, missing documentation, and more.
- **RAG context** — the full repository is indexed into a ChromaDB vector store so the LLM can reference relevant surrounding code, not just the changed lines.
- **Smart index caching** — the vector index is rebuilt only when the HEAD commit changes; repeated runs on the same commit reuse the cached index instantly.
- **Interactive Q&A** — after a review you can open an interactive REPL and ask follow-up questions about the diff or the codebase; the session remembers prior exchanges so follow-up questions work naturally.
- **Flexible AI backends** — works with any OpenAI-compatible endpoint (OpenWebUI, Ollama `/v1`, OpenAI) as well as Ollama's native API.
- **Fully configurable** — settings can be set once in a user-level global config or overridden per-project; sane defaults work out of the box. Indexes are stored in the OS user cache directory — your project directories stay clean.
- **`.gitignore`-aware indexing** — the repository's `.gitignore` is parsed automatically and its patterns are respected during indexing; extra excludes can be added via config.

## Installation

```bash
uv add diffrag          # add to a project
uv tool install diffrag # install as a standalone CLI tool
```

Requires Python ≥ 3.12.

## Setup

### 1. Create a config file

diffrag looks for configuration in the following order (first match wins):

1. `--config <path>` — explicit path passed on the command line
2. `.diffrag.toml` — in the current working directory (project-level override)
3. `$(diffrag config-path)` — user-level global config (set once, used everywhere)
4. Built-in defaults — everything works without any config file

**Recommended: set up a global config once** so every repository uses the same
model and endpoint without needing its own `.diffrag.toml`:

```bash
# Create the global config (creates the directory too)
diffrag create-config

# Open it in your editor
$EDITOR "$(diffrag config-path)"
```

For a project-specific override (e.g. a different `base_branch` or `verbosity`):

```bash
diffrag create-config --out .diffrag.toml
```

Per-project `.diffrag.toml` files take precedence over the global config.

**Minimal config — Ollama for both LLM and embeddings:**

```toml
[ai]
base_url = "http://localhost:11434"
model    = "llama3.2"

[embedding]
base_url = "http://localhost:11434"
model    = "nomic-embed-text"
```

**OpenWebUI for the LLM, Ollama for embeddings:**

```toml
[ai]
base_url = "http://localhost:3000"
model    = "gpt-4o"
api_key  = "your-openwebui-token"

[embedding]
base_url = "http://localhost:11434"
model    = "nomic-embed-text"
```

The `[ai]` and `[embedding]` sections are independent — you can mix and match any OpenAI-compatible endpoint for each role.

---

## CLI usage

### `review` — generate a code review

```bash
# Review current branch (HEAD) against main
diffrag review --base main

# Review a specific branch against main
diffrag review --base main --head feature/my-feature

# Review against a tag or commit SHA
diffrag review --base v1.2.0 --head HEAD

# Review a repo at a different path
diffrag review --repo /path/to/repo --base main

# Use a non-default config file
diffrag review --base main --config /path/to/my.toml
```

**Index caching:** on the first run the repository is indexed and the result is
persisted to the OS user cache directory (run `diffrag db-path` to see the exact
path for a given repo). On subsequent runs the tool compares the current HEAD SHA
against the stored one — if they match the index is reused immediately; if they
differ (new commit or branch switch) the index is rebuilt automatically. Your
project directories are never modified.

### `ask` — interactive Q&A about the diff

```bash
diffrag ask --base main
diffrag ask --base main --head feature/my-feature
```

Opens an interactive prompt backed by the same RAG index. Run `review` at least
once beforehand so the index is populated. Type `exit` or press Ctrl-D to quit.

Each answer is informed by the repository context and the diff. The session
accumulates conversation history, so follow-up questions can refer back to
earlier answers:

```
> What does the change in auth.py affect downstream?
> Can you give a concrete example of how that breaks existing callers?
> Which tests cover the modified functions?
```

### `show-config` — inspect effective configuration

```bash
diffrag show-config
diffrag show-config --config /path/to/my.toml
```

Prints a table of every setting that is in effect, including defaults for keys
not present in your config file.

### `db-path` — print the database path for a repository

```bash
diffrag db-path
diffrag db-path --repo /path/to/repo
```

Prints the path where the vector index for that repository is (or will be)
stored. Useful for inspecting or deleting a stale index.

### `create-config` — create a starter config file

```bash
diffrag create-config                       # write to user-level global config
diffrag create-config --out .diffrag.toml  # write a project-level override
diffrag create-config --force               # overwrite if the file already exists
```

Creates a config file pre-populated with all available settings and their
defaults. The destination directory is created automatically.

### `config-path` — print the user-level config path

```bash
diffrag config-path
```

Prints the OS-appropriate path for the user-level config file (it may not exist
yet). Useful for opening the config in an editor: `$EDITOR $(diffrag config-path)`.

### Global options

| Option | Description |
|--------|-------------|
| `-v` / `--verbose` | Enable debug-level logging |
| `-c` / `--config PATH` | Path to a `.toml` config file (overrides auto-discovery) |

---

## Configuration reference

All keys are optional. Unset keys fall back to the defaults shown below.
Place the file at `$(diffrag config-path)` for global defaults, or at `.diffrag.toml`
in a project root to override per-repository.

```toml
[ai]
# Base URL of the OpenAI-compatible or Ollama endpoint used for review generation.
# Do NOT include a /v1 suffix — it is appended automatically.
base_url = "http://localhost:11434"

# Model identifier sent with every completion request.
model = "llama3.2"

# Bearer token for the Authorization header. Leave empty for local servers.
api_key = ""

# Per-read timeout in seconds. Requests use streaming, so this is the maximum
# time allowed between consecutive chunks — not the total generation time.
# Increase only if the model stalls mid-generation for unusually long periods.
timeout = 120.0


[embedding]
# Endpoint used for computing text embeddings. Can be the same server as [ai]
# or a different one (e.g. a dedicated embedding service).
base_url = "http://localhost:11434"

# Embedding model. nomic-embed-text works well with Ollama.
model = "nomic-embed-text"

api_key = ""
timeout = 60.0


[indexing]
# The index is always stored in the OS user cache directory, keyed by repo.
# Run "diffrag db-path" to see the exact path. This cannot be configured.

# Maximum number of characters per text chunk when splitting files.
chunk_size = 1024

# Character overlap between consecutive chunks (helps preserve context at boundaries).
chunk_overlap = 128

# The repository's .gitignore is parsed automatically and its patterns are
# applied first. The list below provides additional excludes on top of .gitignore.
# Any path component matching one of these strings is also skipped.
excluded_patterns = [
    ".git", ".venv", "__pycache__", "node_modules",
    ".idea", ".mypy_cache", ".ruff_cache", "dist", "build"
]

# Files larger than this (in bytes) are silently skipped.
# Binary files are always skipped regardless of this setting.
max_file_size = 1000000


[review]
# Default base branch when --base is not specified on the command line.
base_branch = "main"

# Number of context chunks retrieved from the vector index per diff chunk.
# Higher values give the LLM more surrounding context but increase prompt size.
similarity_top_k = 5

# Approximate token budget per diff chunk before it is split into individual hunks.
# Reduce this if your model has a small context window.
max_prompt_tokens = 6000

# Detail level of generated reviews. Can be overridden per-run with --verbosity.
# "brief"    — top 3 critical issues only, under 200 words
# "standard" — balanced default
# "detailed" — exhaustive, includes minor nits and suggested fixes per issue
verbosity = "standard"
```

---

## Library usage

`diffrag` is also usable as a Python library:

```python
import asyncio
from pathlib import Path
from diffrag import (
    Settings,
    GitRepository,
    OpenAICompatClient,
    RepoIndexer,
    CodeReviewer,
)

settings = Settings.from_toml(Path(".diffrag.toml"))

ai_client = OpenAICompatClient(
    base_url=settings.ai.base_url,
    model=settings.ai.model,
    api_key=settings.ai.api_key,
)
embed_client = OpenAICompatClient(
    base_url=settings.embedding.base_url,
    model=settings.embedding.model,
    api_key=settings.embedding.api_key,
)
indexer = RepoIndexer(
    db_path=settings.indexing.db_path,
    embedding_client=embed_client,
)
reviewer = CodeReviewer(ai_client=ai_client, indexer=indexer)
repo = GitRepository(Path("/path/to/repo"))


async def main() -> None:
    # review() checks the HEAD commit hash and only rebuilds the index when needed
    result = await reviewer.review(repo, base="main", head="feature/my-feature")
    print(result.summary)

    # ask follow-up questions about the same diff; pass history for multi-turn
    diff = repo.get_diff("main", "feature/my-feature")
    history: list[tuple[str, str]] = []
    q1 = "Are there any breaking API changes?"
    a1 = await reviewer.ask(q1, diff)
    print(a1)
    history.append((q1, a1))
    a2 = await reviewer.ask("Which callers are affected?", diff, history=history)
    print(a2)


asyncio.run(main())
```

For Ollama's native API (supports `num_ctx` and other Ollama-specific options):

```python
from diffrag import OllamaClient

embed_client = OllamaClient(
    base_url="http://localhost:11434",
    model="nomic-embed-text",
)
```

---

## How the review pipeline works

1. **Diff** — `git diff base...head` is run for each changed file; results are parsed into typed `FileDiff` / `Hunk` objects.
2. **Index** — every non-binary file in the repository is split into overlapping character chunks and embedded into ChromaDB. The HEAD commit hash is saved alongside the index; on future runs this hash is compared and the rebuild is skipped if nothing has changed.
3. **Per-file review** — for each changed file:
   - The LLM summarises the diff in plain English (used as a targeted search query).
   - The summary is embedded and the top-*k* nearest chunks are retrieved from the index.
   - The LLM reviews the raw diff in light of the retrieved context.
   - Large diffs are split into individual git hunks and reviewed separately.
4. **Consolidation** — all per-file reviews are sent to the LLM in a final prompt that produces a single, cross-cutting summary.

---

## Development

```bash
# Install with dev dependencies
uv sync --group dev

# Run tests
uv run pytest

# Lint and format checks
uv run ruff check src tests
uv run ruff format --check src tests

# Type checking
uv run ty check src

# Run all checks via tox
uv run tox

# Build Sphinx docs
uv run sphinx-build docs docs/_build/html
```

## Project structure

```
src/diffrag/
├── config/       # Settings dataclasses + TOML loading (settings.py)
├── diff/         # GitRepository, FileDiff, Hunk models (git.py, models.py)
├── ai/           # AIClient / EmbeddingClient protocols, OpenAI-compat + Ollama clients
├── indexing/     # ChromaDB-backed RepoIndexer with staleness detection
├── review/       # CodeReviewer orchestrator + prompt templates
└── cli/          # Click CLI: review / ask / show-config / create-config / db-path / config-path
```
