diffctx 1.12.2 → 1.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -10,22 +10,16 @@
10
10
  Instead of pasting whole files, it walks the dependency graph outward from the
11
11
  changed lines and stops once more context stops paying for itself.
12
12
 
13
- > Coming from [`treemapper`](https://pypi.org/project/treemapper/)? That name is
14
- > deprecated. Every command, flag, and API call works unchanged:
15
- > `treemapper` → `diffctx`, `treemapper-mcp` → `diffctx-mcp`.
16
-
17
- ## Why not just use `tree` or repomix?
18
-
19
- | | `tree` | repomix | Claude Code Review | **diffctx** |
20
- |---|:---:|:---:|:---:|:---:|
21
- | **Primary use case** | directory listing | full repo export | automated PR review | **diff context for code review** |
22
- | Smart diff context | ✗ | ✗ | ✓ | ✓ |
23
- | Works with any LLM | ✓ | ✓ | Claude only | ✓ |
24
- | Free / local / offline | ✓ | ✓ | $15–25/review | ✓ |
25
- | GitHub required | ✗ | ✗ | ✓ | ✗ |
26
- | Multiple output formats | ✗ | limited | — | YAML/JSON/MD/txt |
27
- | Python API | ✗ | ✗ | ✗ | ✓ |
28
- | MCP server | ✗ | ✗ | ✗ | ✓ |
13
+ > Formerly published as `treemapper` — every command, flag, and API call works unchanged.
14
+
15
+ ## How it compares
16
+
17
+ Whole-repo packers (repomix and friends) seed on the repository and export
18
+ everything; persistent code-graph servers answer structural queries against a
19
+ maintained index. diffctx is **diff-seeded**: the input is a change, the
20
+ output is the fragments needed to understand it, packed under a hard token
21
+ budget — local, deterministic, no index, no model calls. Measured results and
22
+ when the other two families fit better: [COMPARISON.md](COMPARISON.md).
29
23
 
30
24
  ## Install
31
25
 
@@ -44,18 +38,18 @@ npx diffctx . --diff HEAD~1 # npm wrapper over the native binary
44
38
  docker run --rm -v "$PWD:/repo" ghcr.io/nikolay-e/diffctx . --diff HEAD~1
45
39
  ```
46
40
 
47
- The image runs as a non-root user (uid 10001) and writes to stdout — the
48
- native binary has no `-o` flag, so redirect to capture: `... --diff HEAD~1 >
49
- context.yaml`.
41
+ On Windows, via Scoop (this repository is the bucket):
42
+
43
+ ```powershell
44
+ scoop bucket add diffctx https://github.com/nikolay-e/diffctx
45
+ scoop install diffctx/diffctx
46
+ ```
50
47
 
51
48
  Prebuilt binaries for linux (x86_64/aarch64), macOS (arm64) and Windows (x64)
52
49
  are attached to every [release](https://github.com/nikolay-e/diffctx/releases/latest).
53
- The native binary covers diff mode with YAML/JSON output; tree mode, Markdown
54
- output, the `graph` subcommand and the MCP server live in the Python package.
55
- `cargo add diffctx` embeds the pipeline in a Rust project — the library is
56
- imported as `_diffctx` (`use _diffctx::pipeline::build_diff_context`), since
57
- the crate doubles as the Python extension module
58
- ([docs.rs](https://docs.rs/diffctx)).
50
+ The native binary and Docker image cover diff mode with YAML/JSON output and
51
+ write to stdout (redirect to capture); tree mode, Markdown output, the `graph`
52
+ subcommand and the MCP server live in the Python package.
59
53
 
60
54
  ## Quick start
61
55
 
@@ -77,20 +71,24 @@ builds a code graph (imports, co-changes, type refs), propagates relevance
77
71
  outward from the changed lines, and stops when relevance drops below `--tau` or
78
72
  the `--budget` token cap is hit.
79
73
 
74
+ `--diff` takes a git range (`HEAD~1..HEAD`, `main..feature`) or a **duration
75
+ window ending now** — `24h`, `8d`, `90min`, `1h30m`, `2w` (units `s`, `m`/`min`,
76
+ `h`, `d`, `w`, composable). A window diffs the working tree against the last
77
+ commit before it, so it covers the commits made inside the window *plus* the
78
+ uncommitted and untracked work on top — `diffctx . --diff 24h` is "everything I
79
+ touched today". A ref that happens to look like a duration (a branch `24h`)
80
+ keeps its git meaning.
81
+
80
82
  | Flag | Default | Description |
81
83
  |-------------|---------|--------------------------------------------------------------------------|
82
- | `--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) |
83
- | `--budget` | auto | Hard token cap: `N` enforces a fixed cap, `-1` disables it, `0` is a strict-zero floor (empty selection; use `--full` for changed files only) |
84
+ | `--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) |
85
+ | `--budget` | auto | Hard cap in o200k_base tokens (see [Token counting](docs/product/token-budget.md)): `N` enforces a fixed cap, `-1` disables it, `0` is a strict-zero floor (empty selection; use `--full` for changed files only) |
84
86
  | `--alpha` | 0.60 | PPR damping; higher = context clusters tighter around changes (`--scoring ppr` only) |
85
87
  | `--tau` | 0.12 | Relevance threshold for full fragment content; lower-scoring fragments are stubbed or dropped (lower = more context) |
86
88
  | `--full` | false | Only the changed files, every fragment, no related-code context |
87
89
  | `--timeout` | 300 | Wall-clock deadline in seconds; on expiry diffctx exits 124 instead of hanging |
88
-
89
- Calibration of `--alpha`, `--tau`, and the edge-weight priors:
90
- [`docs/engineering/parameter-strategy.md`](docs/engineering/parameter-strategy.md).
91
- Theory:
92
- [diffctx: Budgeted Typed-Graph Retrieval for Diff-Aware Code Context
93
- Selection (Zenodo, 2026)](https://doi.org/10.5281/zenodo.18824579).
90
+ | `--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 |
91
+ | `--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"` |
94
92
 
95
93
  ### `graph` subcommand
96
94
 
@@ -108,28 +106,31 @@ diffctx graph . --level file -f graphml -o g.xml # file-level graph as GraphML
108
106
  <!-- BEGIN USAGE -->
109
107
  ```bash
110
108
  # full codebase export:
111
- diffctx . # Markdown to stdout + token count
112
- diffctx . -f md -c # Markdown → clipboard
113
- diffctx . -f json -o tree.json # JSON → file
114
- diffctx . --no-content # structure only, no file contents
115
- diffctx . --max-depth 3 # limit depth
116
- diffctx . -i custom.ignore # custom ignore patterns
109
+ diffctx . # Markdown to stdout + token count
110
+ diffctx . -f md -c # Markdown → clipboard
111
+ diffctx . -f json -o tree.json # JSON → file
112
+ diffctx . --no-content # structure only, no file contents
113
+ diffctx . --max-depth 3 # limit depth
114
+ diffctx . -i custom.ignore # custom ignore patterns
117
115
 
118
116
  # diff context mode (requires git repo):
119
- diffctx . --diff # uncommitted changes (working tree vs HEAD)
120
- diffctx . --diff HEAD~1 # context for last commit
121
- diffctx . --diff main..feature # context for feature branch
122
- diffctx . --diff HEAD~1 --budget 30000 # limit to ~30k tokens
123
- diffctx . --diff HEAD~1 -c # diff context to clipboard
117
+ diffctx . --diff # uncommitted changes (working tree vs HEAD)
118
+ diffctx . --diff HEAD~1 # context for last commit
119
+ diffctx . --diff main..feature # context for feature branch
120
+ diffctx . --diff 24h # everything changed in the last 24 hours
121
+ diffctx . --diff 8d # same over 8 days (also 90s, 10min, 1h30m, 2w)
122
+ diffctx . --diff HEAD~1 --budget 30000 # limit to ~30k tokens
123
+ diffctx . --diff HEAD~1 -c # diff context to clipboard
124
+ diffctx . --diff HEAD~1 --with-raw-diff # raw patch + selected context
125
+ diffctx . --diff HEAD~1 --mode locate # ranked navigation JSON, no source
124
126
  ```
125
127
  <!-- END USAGE -->
126
128
 
127
129
  Every run reports token count and size on stderr — `12,847 tokens
128
- (o200k_base), 52.3 KB` (tiktoken, the GPT-4o tokenizer; `~`-prefixed above
129
- 1 MB). `-c/--copy` copies output via `pbcopy` (macOS), `clip` (Windows), or
130
- `wl-copy`/`xclip`/`xsel` (Linux). Unreadable files become placeholders like
131
- `<binary file: N bytes>`, `<file too large: N bytes>`, or
132
- `<unreadable content: not utf-8>`.
130
+ (o200k_base), 52.3 KB`. Counts are exact only for the GPT-4o family; Claude,
131
+ Gemini and others tokenize differently, so treat `--budget` as an upper bound
132
+ and leave headroom ([details](docs/product/token-budget.md)). Unreadable files
133
+ become placeholders like `<binary file: N bytes>`.
133
134
 
134
135
  ## Python API
135
136
 
@@ -146,6 +147,7 @@ ctx = build_diff_context(
146
147
  full=False,
147
148
  scoring_mode="ego",
148
149
  timeout=300,
150
+ with_raw_diff=False, # True also embeds the raw unified diff (not charged to budget)
149
151
  )
150
152
  print(to_markdown(ctx))
151
153
 
@@ -163,19 +165,36 @@ print(to_yaml(tree))
163
165
 
164
166
  ## MCP server
165
167
 
168
+ [![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)
169
+ [![diffctx MCP server](https://glama.ai/mcp/servers/nikolay-e/diffctx/badges/score.svg)](https://glama.ai/mcp/servers/nikolay-e/diffctx)
170
+
166
171
  diffctx includes an [MCP](https://modelcontextprotocol.io) server that lets AI
167
172
  assistants (Claude Code, Cursor, Windsurf, etc.) call diff context analysis
168
- automatically during code review. Install with `pip install 'diffctx[mcp]'`,
169
- then register it — for Claude Code:
173
+ automatically during code review. It is published in the official MCP registry
174
+ as `io.github.nikolay-e/diffctx`. One-line setup (zero-install via
175
+ [uv](https://docs.astral.sh/uv/)):
170
176
 
171
177
  ```bash
172
- claude mcp add diffctx -- diffctx-mcp
178
+ # Claude Code
179
+ claude mcp add diffctx -- uvx --from 'diffctx[mcp]' diffctx-mcp
180
+ # Codex CLI
181
+ codex mcp add diffctx -- uvx --from 'diffctx[mcp]' diffctx-mcp
182
+ # Gemini CLI
183
+ gemini mcp add diffctx uvx -- --from 'diffctx[mcp]' diffctx-mcp
184
+ # VS Code
185
+ code --add-mcp '{"name":"diffctx","command":"uvx","args":["--from","diffctx[mcp]","diffctx-mcp"]}'
173
186
  ```
174
187
 
175
- The server exposes three tools — `get_diff_context`, `get_tree_map`, and
176
- `get_file_context` — that assistants call when reviewing PRs, explaining
177
- changes, or investigating broken tests. Tool reference and configs for
178
- Cursor, Continue, Windsurf, and Zed:
188
+ With `pip install 'diffctx[mcp]'` already done, replace the
189
+ `uvx --from 'diffctx[mcp]' diffctx-mcp` tail with plain `diffctx-mcp`.
190
+
191
+ The server exposes one tool, `diffctx_context`, that assistants call when
192
+ reviewing PRs, explaining changes, or investigating broken tests. It ranks the
193
+ code that explains a diff, then reads only the fragments the assistant picked —
194
+ two calls that pay for the selection instead of a whole pack. The wider
195
+ `get_tree_map` and `get_file_context` tools are opt-in via
196
+ `DIFFCTX_MCP_LEGACY_TOOLS=1`. Tool reference and JSON configs for Claude
197
+ Desktop, Cursor, Continue, Windsurf, and Zed:
179
198
  [`src/diffctx/mcp/README.md`](src/diffctx/mcp/README.md).
180
199
 
181
200
  ## Ignore patterns
@@ -186,21 +205,18 @@ anchored `/root_only.txt`). `.diffctx/whitelist` acts as an include-only filter,
186
205
  and the output file is always auto-ignored. `--no-default-ignores` disables the
187
206
  built-in patterns; `--no-ignores` disables all ignore rules (tree mode only).
188
207
 
189
- ## Token cache
190
-
191
- Diff mode caches per-blob tokenization under
192
- `~/Library/Caches/diffctx/token-cache` (macOS),
193
- `$XDG_CACHE_HOME/diffctx/token-cache` (Linux) or
194
- `%LOCALAPPDATA%\diffctx\token-cache` (Windows). It is a pure speedup: deleting
195
- it only costs one cold run.
208
+ An excluded path never appears in the output in any role: in diff mode it is
209
+ dropped both from `changed_files` and from the candidate universe, so it cannot
210
+ come back as a related-context fragment either (including under `--full`). The
211
+ same guarantee covers secret-like paths (`id_rsa`, `*.pem`, `*.key`, ...),
212
+ which are filtered even without an ignore entry.
196
213
 
197
- | Variable | Effect |
198
- |----------|--------|
199
- | `DIFFCTX_TOKEN_CACHE_DIR` | Relocate the cache |
200
- | `DIFFCTX_TOKEN_CACHE_MAX_BYTES` | Size cap, default `536870912` (512 MB); `0` disables eviction |
214
+ ## Token cache
201
215
 
202
- Eviction is amortized: each run trims one of the cache's 256 shards back under
203
- its share of the cap, oldest entries first.
216
+ Diff mode caches per-blob tokenization in the OS cache directory (e.g.
217
+ `~/Library/Caches/diffctx/token-cache`) — a pure speedup, safe to delete.
218
+ `DIFFCTX_TOKEN_CACHE_DIR` relocates it; `DIFFCTX_TOKEN_CACHE_MAX_BYTES` caps
219
+ its size (default 512 MB, `0` disables eviction).
204
220
 
205
221
  ## Exit codes
206
222
 
@@ -215,14 +231,6 @@ its share of the cap, oldest entries first.
215
231
  | `130`| Interrupted (Ctrl-C) |
216
232
  | `141`| Broken pipe (e.g. piping into `head`) |
217
233
 
218
- ## Repository layout
219
-
220
- Rust product code lives in `crates/diffctx-native/`; the Python CLI and MCP
221
- package live in `src/diffctx/`. Evaluation code, immutable datasets, and paper
222
- artifacts are intentionally separated under `eval/`, `datasets/`, and `paper/`.
223
- See [repository ownership boundaries](docs/architecture/repository-layout.md)
224
- for the lifecycle and entry point of each area.
225
-
226
234
  ## License
227
235
 
228
236
  Apache 2.0
@@ -235,6 +243,14 @@ Apache 2.0
235
243
 
236
244
  - [Documentation site](https://nikolay-e.github.io/diffctx/) — the pipeline
237
245
  end to end: diff → fragments → graph → relevance → selection
246
+ - [GitHub Action](docs/product/github-action.md) — diff context as a CI step
247
+ for LLM review
248
+ - [Token counting](docs/product/token-budget.md) — which encoder, and what
249
+ `--budget` means for non-GPT models
250
+ - [Comparison](COMPARISON.md) — measured results, and when a whole-repo packer
251
+ or a persistent code-graph server fits better
252
+ - [Paper](https://doi.org/10.5281/zenodo.18824579) — budgeted typed-graph
253
+ retrieval for diff-aware context selection (Zenodo, 2026)
238
254
  - [Changelog](CHANGELOG.md)
239
255
  - [Security policy](SECURITY.md) — threat model and vulnerability reporting
240
256
  - [Parameter strategy](docs/engineering/parameter-strategy.md) — how `--alpha`,
package/checksums.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
- "diffctx-1.12.2-aarch64-apple-darwin.tar.gz": "fa575452f5120305c7a6c2ce2abd9bc58b93a775fbe430c740f54c5646379295",
3
- "diffctx-1.12.2-aarch64-unknown-linux-gnu.tar.gz": "c36b6a56241f96a410f291b5069001adf075fa0fbce152818d0ae8eab2af00cc",
4
- "diffctx-1.12.2-x86_64-pc-windows-msvc.zip": "f2d063f64d9b4b23c28efa9b87e7d18aa57947e75c5fbe37eba99824e03eebfc",
5
- "diffctx-1.12.2-x86_64-unknown-linux-gnu.tar.gz": "3faa5da74a60e3289a4f86110be397a80b4733ca3d0655e999c0bd53a679197b"
2
+ "diffctx-1.13.0-aarch64-apple-darwin.tar.gz": "bc657ca9ceafee4127c454940e9d4700dd9ba8df338326b1e67a04a430479902",
3
+ "diffctx-1.13.0-aarch64-unknown-linux-gnu.tar.gz": "46b726b644f4a7d3759ecb5c4b48f1467f26f3c0880c7db66b6deb64c92da5c7",
4
+ "diffctx-1.13.0-x86_64-pc-windows-msvc.zip": "43a862384d393ca8f54e81812f530fc434e07f8d85faa1219cd893fd5932ef88",
5
+ "diffctx-1.13.0-x86_64-unknown-linux-gnu.tar.gz": "0585f5df531dbac369ced4398fa745a161c6f6fdaff52e457e646da43bc79f50"
6
6
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "diffctx",
3
3
  "mcpName": "io.github.nikolay-e/diffctx",
4
- "version": "1.12.2",
4
+ "version": "1.13.0",
5
5
  "description": "Selects the minimum code an LLM needs to review a git diff",
6
6
  "keywords": [
7
7
  "git",