{
  "markdown": "# diffctx — smart diff context for LLM code review\n\n[![CI](https://github.com/nikolay-e/diffctx/actions/workflows/ci.yml/badge.svg)](https://github.com/nikolay-e/diffctx/actions/workflows/ci.yml)\n[![PyPI](https://img.shields.io/pypi/v/diffctx)](https://pypi.org/project/diffctx/)\n[![crates.io](https://img.shields.io/crates/v/diffctx)](https://crates.io/crates/diffctx)\n[![npm](https://img.shields.io/npm/v/diffctx)](https://www.npmjs.com/package/diffctx)\n[![License](https://img.shields.io/pypi/l/diffctx)](https://pypi.org/project/diffctx/)\n\n**diffctx selects the minimum code an LLM needs to review a git diff.**\nInstead of pasting whole files, it walks the dependency graph outward from the\nchanged lines and stops once more context stops paying for itself.\n\n> Formerly published as `treemapper` — every command, flag, and API call works unchanged.\n\n## How it compares\n\nWhole-repo packers (repomix and friends) seed on the repository and export\neverything; persistent code-graph servers answer structural queries against a\nmaintained index. diffctx is **diff-seeded**: the input is a change, the\noutput is the fragments needed to understand it, packed under a hard token\nbudget — local, deterministic, no index, no model calls. Measured results and\nwhen the other two families fit better: [COMPARISON.md](COMPARISON.md).\n\n## Install\n\n```bash\nuvx diffctx . --diff HEAD~1             # zero-install, run once via uv\npipx install diffctx                    # recommended: isolated CLI, no venv needed\npip install diffctx                     # or: into an active environment\npipx install 'diffctx[mcp]'             # + MCP server for AI assistants\n```\n\nWithout Python:\n\n```bash\ncargo install diffctx                   # native CLI from crates.io\nnpx diffctx . --diff HEAD~1             # npm wrapper over the native binary\ndocker run --rm -v \"$PWD:/repo\" ghcr.io/nikolay-e/diffctx . --diff HEAD~1\n```\n\nOn Windows, via Scoop (this repository is the bucket):\n\n```powershell\nscoop bucket add diffctx https://github.com/nikolay-e/diffctx\nscoop install diffctx/diffctx\n```\n\nPrebuilt binaries for linux (x86_64/aarch64), macOS (arm64) and Windows (x64)\nare attached to every [release](https://github.com/nikolay-e/diffctx/releases/latest).\nThe native binary and Docker image cover diff mode with YAML/JSON output and\nwrite to stdout (redirect to capture); tree mode, Markdown output, the `graph`\nsubcommand and the MCP server live in the Python package.\n\n## Quick start\n\n```bash\ndiffctx . --diff HEAD~1       # smart context for last commit → paste into Claude/ChatGPT\ndiffctx . -f md -c            # full codebase export → clipboard in Markdown\n```\n\n![diffctx demo](https://raw.githubusercontent.com/nikolay-e/diffctx/main/docs/demo/demo.gif)\n\n*`diffctx . --diff HEAD~1` selects only the fragments an LLM needs to review the\nlast commit, instead of dumping every changed file in full.*\n\n## Diff context mode\n\nFinds the minimal set of fragments needed to understand a change — imports,\ncallers, type definitions, config dependencies — across 50+ file types. It\nbuilds a code graph (imports, co-changes, type refs), propagates relevance\noutward from the changed lines, and stops when relevance drops below `--tau` or\nthe `--budget` token cap is hit.\n\n`--diff` takes a git range (`HEAD~1..HEAD`, `main..feature`) or a **duration\nwindow ending now** — `24h`, `8d`, `90min`, `1h30m`, `2w` (units `s`, `m`/`min`,\n`h`, `d`, `w`, composable). A window diffs the working tree against the last\ncommit before it, so it covers the commits made inside the window *plus* the\nuncommitted and untracked work on top — `diffctx . --diff 24h` is \"everything I\ntouched today\". A ref that happens to look like a duration (a branch `24h`)\nkeeps its git meaning.\n\n| Flag        | Default | Description                                                              |\n|-------------|---------|--------------------------------------------------------------------------|\n| `--scoring` | `ego`   | `ego` = bounded expansion around changed nodes (fast, predictable radius); `ppr` = Personalized PageRank (global, smoother decay, slower); `bm25` = lexical retrieval against the diff hunks (baseline for sparse graphs); `rrf` = reciprocal-rank fusion of `ego` and `bm25` (widest recall, no scale calibration between the two signals); `pit` = the same fusion on score percentiles |\n| `--budget`  | auto    | Cap in o200k_base tokens on the whole artifact (see [Token counting](docs/product/token-budget.md)): the change summary is charged first and the selection gets what is left, so a budget smaller than the summary yields the summary alone. `N` = fixed cap, `-1` disables it, `0` is a strict-zero floor (no fragments; use `--full` for changed files only) |\n| `--alpha`   | 0.60    | PPR continuation probability: higher = relevance travels further from the change, lower = tighter around it (`--scoring ppr` only) |\n| `--tau`     | 0.05    | Relevance threshold for full fragment content; lower-scoring fragments are stubbed or dropped (lower = more context) |\n| `--full`    | false   | Only the changed files, every fragment, no related-code context          |\n| `--timeout` | 300     | Wall-clock deadline in seconds; on expiry diffctx exits 124 instead of hanging |\n| `--with-raw-diff` | false | Also embed git's raw unified diff ahead of the selected fragments — additive (selection unchanged), not charged to `--budget`, lock/ignored/secret-like sections omitted. Python CLI only |\n| `--mode` | `pack` | `locate` emits the same ranked selection as compact `diffctx.locate.v1` JSON — path, lines, score, provenance reasons, a blast-radius `summary` and per-item impact `group` (`test`/`type`/`config`), NO source bodies. Adds a `coverage` block naming what the run could not see (`unparsed_files`, `zero_edge_files`, `ppr_truncated`, `next_up`, a heuristic `confidence`) and an `overflow` ranking of what the budget left behind — omitted entirely when there is nothing to disclose. `diffctx . --diff --mode locate` = impact of your uncommitted change. The MCP tool takes it as `mode=\"locate\"` |\n\n### `graph` subcommand\n\nExplore the underlying dependency graph directly, without a diff:\n\n```bash\ndiffctx graph .                                  # Mermaid graph of directory deps (default)\ndiffctx graph . --summary                        # cycles, hotspots, coupling metrics\ndiffctx graph . --level fragment -f json         # fragment-level graph as JSON\ndiffctx graph . --level file -f graphml -o g.xml # file-level graph as GraphML\n```\n\n## Usage\n\n<!-- BEGIN USAGE -->\n```bash\n# full codebase export:\ndiffctx .                                 # Markdown to stdout + token count\ndiffctx . -f md -c                        # Markdown → clipboard\ndiffctx . -f json -o tree.json            # JSON → file\ndiffctx . --no-content                    # structure only, no file contents\ndiffctx . --max-depth 3                   # limit depth\ndiffctx . -i custom.ignore                # custom ignore patterns\n\n# diff context mode (requires git repo):\ndiffctx . --diff                          # uncommitted changes (working tree vs HEAD)\ndiffctx . --diff HEAD~1                   # context for last commit\ndiffctx . --diff main..feature            # context for feature branch\ndiffctx . --diff 24h                      # everything changed in the last 24 hours\ndiffctx . --diff 8d                       # same over 8 days (also 90s, 10min, 1h30m, 2w)\ndiffctx . --diff HEAD~1 --budget 30000    # limit to ~30k tokens\ndiffctx . --diff HEAD~1 -c                # diff context to clipboard\ndiffctx . --diff HEAD~1 --with-raw-diff   # raw patch + selected context\ndiffctx . --diff HEAD~1 --mode locate     # ranked navigation JSON, no source\n```\n<!-- END USAGE -->\n\nEvery run reports token count and size on stderr — `12,847 tokens\n(o200k_base), 52.3 KB`. Counts are exact only for the GPT-4o family; Claude,\nGemini and others tokenize differently, so treat `--budget` as an upper bound\nand leave headroom ([details](docs/product/token-budget.md)). Unreadable files\nbecome placeholders like `<binary file: N bytes>`.\n\n## Python API\n\n```python\nfrom pathlib import Path\nfrom diffctx import build_diff_context, map_directory, to_json, to_markdown, to_text, to_yaml\n\nctx = build_diff_context(\n    Path(\".\"),\n    \"HEAD~1..HEAD\",\n    budget_tokens=None,       # None = auto; 0 = no fragments; -1 = uncapped; N = cap on the whole artifact\n    alpha=0.6,\n    tau=0.05,\n    full=False,\n    scoring_mode=\"ego\",\n    timeout=300,\n    with_raw_diff=False,      # True also embeds the raw unified diff (not charged to budget)\n)\nprint(to_markdown(ctx))\n\ntree = map_directory(\n    \".\",\n    max_depth=None,\n    no_content=False,\n    max_file_bytes=None,\n    ignore_file=None,\n    no_default_ignores=False,\n    whitelist_file=None,\n)\nprint(to_yaml(tree))\n```\n\n## MCP server\n\n[![MCP Registry](https://img.shields.io/badge/MCP_Registry-io.github.nikolay--e%2Fdiffctx-blue)](https://registry.modelcontextprotocol.io/v0/servers?search=io.github.nikolay-e/diffctx)\n[![diffctx MCP server](https://glama.ai/mcp/servers/nikolay-e/diffctx/badges/score.svg)](https://glama.ai/mcp/servers/nikolay-e/diffctx)\n\ndiffctx includes an [MCP](https://modelcontextprotocol.io) server that lets AI\nassistants (Claude Code, Cursor, Windsurf, etc.) call diff context analysis\nautomatically during code review. It is published in the official MCP registry\nas `io.github.nikolay-e/diffctx`. One-line setup (zero-install via\n[uv](https://docs.astral.sh/uv/)):\n\n```bash\n# Claude Code\nclaude mcp add diffctx -- uvx --from 'diffctx[mcp]' diffctx-mcp\n# Codex CLI\ncodex mcp add diffctx -- uvx --from 'diffctx[mcp]' diffctx-mcp\n# Gemini CLI\ngemini mcp add diffctx uvx -- --from 'diffctx[mcp]' diffctx-mcp\n# VS Code\ncode --add-mcp '{\"name\":\"diffctx\",\"command\":\"uvx\",\"args\":[\"--from\",\"diffctx[mcp]\",\"diffctx-mcp\"]}'\n```\n\nWith `pip install 'diffctx[mcp]'` already done, replace the\n`uvx --from 'diffctx[mcp]' diffctx-mcp` tail with plain `diffctx-mcp`.\n\nThe server exposes one tool, `diffctx_context`, that assistants call when\nreviewing PRs, explaining changes, or investigating broken tests. It ranks the\ncode that explains a diff, then reads only the fragments the assistant picked —\ntwo calls that pay for the selection instead of a whole pack. The wider\n`get_tree_map` and `get_file_context` tools are opt-in via\n`DIFFCTX_MCP_LEGACY_TOOLS=1`. Filesystem confinement via\n`DIFFCTX_ALLOWED_PATHS`: see [SECURITY.md](SECURITY.md).\n\nEvery stdio client takes the same server shape; only the config file differs:\n\n| Client | Config file | Key |\n|---|---|---|\n| Claude Code (project) | `.mcp.json` | `mcpServers` |\n| Claude Desktop | `claude_desktop_config.json` | `mcpServers` |\n| Cursor | `~/.cursor/mcp.json` | `mcpServers` |\n| Windsurf | `~/.codeium/windsurf/mcp_config.json` | `mcpServers` |\n| Continue | `~/.continue/config.json` | `experimental.modelContextProtocolServers` (transport object) |\n| Zed | `~/.config/zed/settings.json` | `context_servers` (`command.path`) |\n\n```json\n{\n  \"mcpServers\": {\n    \"diffctx\": {\n      \"command\": \"uvx\",\n      \"args\": [\"--from\", \"diffctx[mcp]\", \"diffctx-mcp\"]\n    }\n  }\n}\n```\n\nWith `pip install 'diffctx[mcp]'` already done, `\"command\": \"diffctx-mcp\"` with\nno args works everywhere instead. Use the `diffctx-mcp` entry point, not the\n`diffctx mcp` subcommand: the latter only exists from 1.12.3 onward and would\nmap a directory named `mcp` on older releases.\n\n## Ignore patterns\n\nRespects `.gitignore` and `.diffctx/ignore` automatically — hierarchically at\nevery directory level, with full gitignore semantics (negation `!important.log`,\nanchored `/root_only.txt`), and the output file is always auto-ignored. Three\ncontrols are tree mode only and are refused with `--diff`: `.diffctx/whitelist`\n(`-w`) as an include-only filter, `-i` for an extra ignore file, and\n`--no-default-ignores` / `--no-ignores` to drop the built-in patterns or every\nignore rule.\n\nAn excluded path never appears in the output in any role: in diff mode it is\ndropped both from `changed_files` and from the candidate universe, so it cannot\ncome back as a related-context fragment either (including under `--full`). The\nsame guarantee covers secret-like paths (`id_rsa`, `*.pem`, `*.key`, ...),\nwhich are filtered even without an ignore entry.\n\n## Token cache\n\nDiff mode caches per-blob tokenization in the OS cache directory (e.g.\n`~/Library/Caches/diffctx/token-cache`) — a pure speedup, safe to delete.\n`DIFFCTX_TOKEN_CACHE_DIR` relocates it; `DIFFCTX_TOKEN_CACHE_MAX_BYTES` caps\nits size (default 512 MB, `0` disables eviction).\n\n## Exit codes\n\n| Code | Meaning |\n|------|---------|\n| `0`  | Success — output contains content |\n| `1`  | Runtime error (bad path, permission denied, etc.) |\n| `2`  | Usage error (invalid flags/arguments) |\n| `3`  | Environment error (`--diff` outside a git repo, git not installed, no commits yet) |\n| `4`  | `--diff` produced no semantic context (clean tree, binary-only, everything filtered); output is still emitted. Deletion/rename/lockfile-only diffs list `deleted_files`/`renamed_files`/`lockfile_changes` and exit `0` |\n| `124`| `--diff` exceeded the `--timeout` wall-clock deadline |\n| `130`| Interrupted (Ctrl-C) |\n| `141`| Broken pipe (e.g. piping into `head`) |\n\n## License\n\nApache 2.0\n\n<!-- mcp-name: io.github.nikolay-e/diffctx -->\n<!-- Ownership marker read from the PyPI description by the MCP registry. -->\n<!-- Must survive edits verbatim: one space after the colon, case-sensitive. -->\n\n---\n\n- [Documentation site](https://nikolay-e.github.io/diffctx/) — the pipeline\n  end to end: diff → fragments → graph → relevance → selection\n- [GitHub Action](docs/product/github-action.md) — diff context as a CI step\n  for LLM review\n- [Token counting](docs/product/token-budget.md) — which encoder, and what\n  `--budget` means for non-GPT models\n- [Comparison](COMPARISON.md) — measured results, and when a whole-repo packer\n  or a persistent code-graph server fits better\n- [Paper](https://doi.org/10.5281/zenodo.18824579) — budgeted typed-graph\n  retrieval for diff-aware context selection (Zenodo, 2026)\n- [Changelog](CHANGELOG.md)\n- [Security policy](SECURITY.md) — threat model and vulnerability reporting\n- [Parameter strategy](docs/engineering/parameter-strategy.md) — how `--alpha`,\n  `--tau`, and edge weights are calibrated\n",
  "bytes": 14299,
  "sha": "837f815a5daf86fea664c63b555cfa1243b6b9c82761d7662ccda67cc5f6ff2b",
  "repo_slug": "nikolay-e/diffctx",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_nikolay_e_diffctx_1d9db0d8/readme"
}