diffctx 1.10.2__tar.gz → 1.12.0__tar.gz
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.
- {diffctx-1.10.2 → diffctx-1.12.0}/CHANGELOG.md +92 -0
- diffctx-1.12.0/PKG-INFO +273 -0
- diffctx-1.12.0/README.md +197 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/Cargo.lock +18 -18
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/Cargo.toml +4 -3
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/analytics.rs +6 -8
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/config/limits.rs +8 -4
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/config/needs.rs +4 -2
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/config/scoring.rs +2 -2
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/config/selection.rs +6 -6
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/discovery.rs +25 -52
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/edges/history/cochange.rs +1 -4
- diffctx-1.12.0/diffctx/src/edges/mod.rs +282 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/filtering.rs +8 -8
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/fragmentation.rs +12 -1
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/git.rs +81 -15
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/graph.rs +550 -118
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/graph_export.rs +9 -12
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/lib.rs +2 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/memory_pipeline.rs +4 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/parsers/tree_sitter_strategy.rs +6 -1
- diffctx-1.12.0/diffctx/src/peak_rss.rs +46 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/pipeline.rs +129 -12
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/postpass.rs +23 -12
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/project_graph.rs +18 -33
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/pybridge.rs +53 -13
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/render.rs +73 -12
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/scoring.rs +17 -6
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/select.rs +15 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/signatures.rs +22 -1
- diffctx-1.12.0/diffctx/src/token_corpus.rs +228 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/types.rs +14 -1
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/utility/boltzmann.rs +2 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/tests/yaml_cases.rs +2 -5
- {diffctx-1.10.2 → diffctx-1.12.0}/pyproject.toml +3 -19
- {diffctx-1.10.2 → diffctx-1.12.0}/src/diffctx/cli.py +214 -55
- diffctx-1.12.0/src/diffctx/diffctx/graph_analytics.py +293 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/src/diffctx/diffctx/graph_export.py +16 -8
- {diffctx-1.10.2 → diffctx-1.12.0}/src/diffctx/diffctx/pipeline.py +22 -16
- diffctx-1.12.0/src/diffctx/diffctx/project_graph.py +36 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/src/diffctx/ignore.py +11 -3
- {diffctx-1.10.2 → diffctx-1.12.0}/src/diffctx/logger.py +3 -2
- {diffctx-1.10.2 → diffctx-1.12.0}/src/diffctx/main.py +116 -33
- {diffctx-1.10.2 → diffctx-1.12.0}/src/diffctx/mcp/README.md +6 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/src/diffctx/mcp/server.py +35 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/src/diffctx/tokens.py +1 -4
- {diffctx-1.10.2 → diffctx-1.12.0}/src/diffctx/tree.py +11 -0
- diffctx-1.12.0/src/diffctx/version.py +1 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/src/diffctx/writer.py +82 -23
- diffctx-1.10.2/PKG-INFO +0 -376
- diffctx-1.10.2/README.md +0 -298
- diffctx-1.10.2/diffctx/src/edges/mod.rs +0 -137
- diffctx-1.10.2/src/diffctx/diffctx/graph_analytics.py +0 -59
- diffctx-1.10.2/src/diffctx/diffctx/project_graph.py +0 -27
- diffctx-1.10.2/src/diffctx/version.py +0 -1
- {diffctx-1.10.2 → diffctx-1.12.0}/LICENSE +0 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/candidate_files.rs +0 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/config/analytics.rs +0 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/config/bm25.rs +0 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/config/budget.rs +0 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/config/category_weights.rs +0 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/config/edge_weights.rs +0 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/config/env_overrides.rs +0 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/config/extensions.rs +0 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/config/filtering.rs +0 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/config/fragmentation.rs +0 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/config/git.rs +0 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/config/graph_filtering.rs +0 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/config/importance.rs +0 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/config/mod.rs +0 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/config/mode.rs +0 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/config/parsers.rs +0 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/config/render.rs +0 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/config/tokenization.rs +0 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/config/weights.rs +0 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/core.rs +0 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/edges/base.rs +0 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/edges/config_edges/build_system.rs +0 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/edges/config_edges/cicd.rs +0 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/edges/config_edges/docker.rs +0 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/edges/config_edges/generic.rs +0 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/edges/config_edges/helm.rs +0 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/edges/config_edges/kubernetes.rs +0 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/edges/config_edges/mod.rs +0 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/edges/document/mod.rs +0 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/edges/history/mod.rs +0 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/edges/semantic/ansible.rs +0 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/edges/semantic/bazel.rs +0 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/edges/semantic/c_family.rs +0 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/edges/semantic/cargo_edges.rs +0 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/edges/semantic/clojure.rs +0 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/edges/semantic/css.rs +0 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/edges/semantic/dart.rs +0 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/edges/semantic/dbt.rs +0 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/edges/semantic/dotnet.rs +0 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/edges/semantic/elixir.rs +0 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/edges/semantic/erlang.rs +0 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/edges/semantic/go.rs +0 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/edges/semantic/graphql.rs +0 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/edges/semantic/haskell.rs +0 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/edges/semantic/javascript.rs +0 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/edges/semantic/julia.rs +0 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/edges/semantic/jvm.rs +0 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/edges/semantic/latex.rs +0 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/edges/semantic/lua.rs +0 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/edges/semantic/mod.rs +0 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/edges/semantic/nim.rs +0 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/edges/semantic/nix.rs +0 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/edges/semantic/ocaml.rs +0 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/edges/semantic/openapi.rs +0 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/edges/semantic/perl.rs +0 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/edges/semantic/php.rs +0 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/edges/semantic/prisma.rs +0 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/edges/semantic/protobuf.rs +0 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/edges/semantic/python.rs +0 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/edges/semantic/r_lang.rs +0 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/edges/semantic/ruby.rs +0 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/edges/semantic/rust_lang.rs +0 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/edges/semantic/shell.rs +0 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/edges/semantic/sql.rs +0 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/edges/semantic/swift.rs +0 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/edges/semantic/tags.rs +0 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/edges/semantic/terraform.rs +0 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/edges/semantic/zig.rs +0 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/edges/similarity/lexical.rs +0 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/edges/similarity/mod.rs +0 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/edges/structural/containment.rs +0 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/edges/structural/mod.rs +0 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/edges/structural/sibling.rs +0 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/edges/structural/testing.rs +0 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/interval.rs +0 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/languages.rs +0 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/main.rs +0 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/mode.rs +0 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/parsers/config_parser.rs +0 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/parsers/generic.rs +0 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/parsers/markdown.rs +0 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/parsers/mod.rs +0 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/ppr.rs +0 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/stopwords.rs +0 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/test_harness.rs +0 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/tokenizer.rs +0 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/utility/importance.rs +0 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/utility/mod.rs +0 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/utility/needs.rs +0 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/src/utility/scoring.rs +0 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/tests/common/mod.rs +0 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/tests/fixtures/garbage/garbage_api.py +0 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/tests/fixtures/garbage/garbage_constants.py +0 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/tests/fixtures/garbage/garbage_handlers.py +0 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/tests/fixtures/garbage/garbage_models.py +0 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/tests/fixtures/garbage/garbage_module.js +0 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/tests/fixtures/garbage/garbage_services.py +0 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/tests/fixtures/garbage/garbage_types.py +0 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/tests/fixtures/garbage/garbage_unrelated.yaml +0 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/tests/fixtures/garbage/garbage_utils.py +0 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/diffctx/tests/fixtures/garbage/garbage_validators.py +0 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/rust-toolchain.toml +0 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/src/diffctx/__init__.py +0 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/src/diffctx/__main__.py +0 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/src/diffctx/clipboard.py +0 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/src/diffctx/diffctx/__init__.py +0 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/src/diffctx/mcp/__init__.py +0 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/src/diffctx/mcp/__main__.py +0 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/src/diffctx/mcp/formatting.py +0 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/src/diffctx/mcp/security.py +0 -0
- {diffctx-1.10.2 → diffctx-1.12.0}/src/diffctx/py.typed +0 -0
|
@@ -7,6 +7,98 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [1.12.0] - 2026-07-23
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- `--timeout SECONDS` — wall-clock deadline for `--diff` analysis (default
|
|
15
|
+
300); exceeding it exits `124` instead of hanging indefinitely (#70).
|
|
16
|
+
- `--no-ignores` — turns off every ignore rule (built-in patterns, project
|
|
17
|
+
`.gitignore`, `.diffctx/ignore`). `--no-default-ignores` only disables the
|
|
18
|
+
built-in list; its help now says so. Not supported with `--diff`.
|
|
19
|
+
- Output format is inferred from the `-o` extension when `-f` is omitted, so
|
|
20
|
+
`-o out.json` no longer writes Markdown into a `.json` file; a mismatch
|
|
21
|
+
between `-f` and the extension warns.
|
|
22
|
+
|
|
23
|
+
### Fixed
|
|
24
|
+
|
|
25
|
+
- **All error logging was dead.** An import-time `NullHandler` made
|
|
26
|
+
`setup_logging` skip attaching a real handler, so `--log-level` was a no-op
|
|
27
|
+
and all 19 `logger.error/warning/exception` sites were silent — `diffctx . -o
|
|
28
|
+
/bad/path.md` exited 1 with no message at all.
|
|
29
|
+
- **diffctx invoked from inside a git hook silently analyzed the wrong
|
|
30
|
+
repository.** Git exports repo-locating env vars (`GIT_DIR`,
|
|
31
|
+
`GIT_INDEX_FILE`, `GIT_WORK_TREE`, ...) to hook subprocesses; inherited,
|
|
32
|
+
they overrode `-C` on every internal git call. All git spawns now scrub
|
|
33
|
+
these variables (`git_command()` in `git.rs`).
|
|
34
|
+
- YAML output preserved file content byte-exactly except for trailing
|
|
35
|
+
newlines; the block chomping indicator is now chosen per content.
|
|
36
|
+
- Arrow-function fragments bound to variables were never stub-eligible (#106).
|
|
37
|
+
- Decorated definitions rendered as a bare `@decorator` line without the
|
|
38
|
+
`class X:` / `def x():` header.
|
|
39
|
+
- `--max-depth`-pruned directories were labelled `_(empty directory)_` — a
|
|
40
|
+
factual lie to the reader; they now read
|
|
41
|
+
`_(children omitted: --max-depth reached)_` (`truncated: true` in
|
|
42
|
+
YAML/JSON).
|
|
43
|
+
- Mixed directory + glob arguments dropped the glob files' parent path from
|
|
44
|
+
node names.
|
|
45
|
+
- Double Ctrl-C printed a ~60-line traceback.
|
|
46
|
+
- Lock files `uv.lock`, `pdm.lock`, `bun.lock`, `bun.lockb`, `deno.lock` and
|
|
47
|
+
`flake.lock` leaked into output; they now join the other lock files in the
|
|
48
|
+
default ignore patterns.
|
|
49
|
+
- Large-repo hangs/OOM on trivial diffs (#70, #95): discovery no longer
|
|
50
|
+
re-reads and re-tokenizes the whole candidate universe per ensemble
|
|
51
|
+
strategy (one shared pass + a persistent per-blob token cache keyed by
|
|
52
|
+
`(blob OID, tokenizer epoch)`), and edge construction is two-pass with a
|
|
53
|
+
bounded per-source top-K instead of materializing up to tens of millions
|
|
54
|
+
of raw edges before the cap; pass 2 replays a compact 16-byte-per-emission
|
|
55
|
+
log instead of re-running the builders, so generation cost stays 1x.
|
|
56
|
+
Verified: gitpod 8000s-hang -> 35.7s,
|
|
57
|
+
pytorch 1848s-SIGKILL -> 7.4s, mui/material-ui OOM class recovered.
|
|
58
|
+
Outputs are bit-identical (gated by `benchmarks/equivalence_gate.py`).
|
|
59
|
+
|
|
60
|
+
### Known limitations
|
|
61
|
+
|
|
62
|
+
- Near-dense edge emission on huge same-directory trees (observed: 199M
|
|
63
|
+
raw edges, 37GB peak on one mui/material-ui instance) remains expensive
|
|
64
|
+
even with bounded construction; tracked in #116.
|
|
65
|
+
|
|
66
|
+
### Changed
|
|
67
|
+
|
|
68
|
+
- **The token budget is now a hard cap.** The changed-files post-pass no
|
|
69
|
+
longer exceeds the budget to guarantee representation: a changed file
|
|
70
|
+
whose cheapest representative does not fit stays unrepresented (visible
|
|
71
|
+
as changed-file retention < 1). `--budget 0` therefore yields an empty
|
|
72
|
+
selection (use `--full` for changed files only); CLI help updated.
|
|
73
|
+
- Latency telemetry: new `graph_build_ms` phase (graph construction was
|
|
74
|
+
previously misattributed to `scoring_ms`, which now measures pure
|
|
75
|
+
ranking) and `peak_rss_bytes` (in-process peak memory). Release builds
|
|
76
|
+
carry line tables (`debug = "line-tables-only"`) for profiling at no
|
|
77
|
+
runtime cost.
|
|
78
|
+
- CLI diagnostics are honest end to end: an exit-code table in `--help`
|
|
79
|
+
(2 usage, 3 environment, 4 empty diff, 124 timeout), flag-value validation
|
|
80
|
+
exits 2 instead of 1, git failures report a single line plus a
|
|
81
|
+
`git log --oneline` hint on unknown revisions, conflicting flags warn
|
|
82
|
+
(`-q`+`--log-level`, `--full`+selection flags, ...), a failed clipboard
|
|
83
|
+
copy warns before falling back to stdout, and `--tau` / `--scoring` /
|
|
84
|
+
`--alpha` / `--budget 0` help text describes what actually happens.
|
|
85
|
+
- `graph --summary` reports category shares as percentages, suppresses
|
|
86
|
+
degenerate top-referenced lists, detects cycles over dominant-direction
|
|
87
|
+
edges only, derives churn from `git log --since`, and disambiguates
|
|
88
|
+
duplicate mermaid labels to relative paths.
|
|
89
|
+
|
|
90
|
+
## [1.11.0] - 2026-07-07
|
|
91
|
+
|
|
92
|
+
### Changed
|
|
93
|
+
|
|
94
|
+
- **The default `--format` is now `md` (Markdown), previously `yaml`.** Markdown
|
|
95
|
+
is ~7% more token-efficient than YAML on real diffs and is preferred by
|
|
96
|
+
reviewers for code-fragment-heavy output (evidence:
|
|
97
|
+
`benchmarks/real_world_diff_bench/`). YAML remains available via `-f yaml`.
|
|
98
|
+
This also changes the default stdout format of `treemapper` (a passthrough
|
|
99
|
+
wrapper over this engine); downstream scripts that parse the default output
|
|
100
|
+
must now pass `-f yaml` explicitly. (#104)
|
|
101
|
+
|
|
10
102
|
## [1.10.0] - 2026-06-20
|
|
11
103
|
|
|
12
104
|
### Changed
|
diffctx-1.12.0/PKG-INFO
ADDED
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: diffctx
|
|
3
|
+
Version: 1.12.0
|
|
4
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
5
|
+
Classifier: Environment :: Console
|
|
6
|
+
Classifier: Intended Audience :: Developers
|
|
7
|
+
Classifier: Operating System :: OS Independent
|
|
8
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
14
|
+
Classifier: Topic :: Software Development
|
|
15
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
16
|
+
Classifier: Topic :: Software Development :: Version Control :: Git
|
|
17
|
+
Classifier: Topic :: Utilities
|
|
18
|
+
Classifier: Typing :: Typed
|
|
19
|
+
Requires-Dist: pathspec>=0.11,<2.0
|
|
20
|
+
Requires-Dist: black>=23.0.0,<27.0 ; extra == 'dev'
|
|
21
|
+
Requires-Dist: charset-normalizer>=3.0,<4.0 ; extra == 'dev'
|
|
22
|
+
Requires-Dist: coverage>=7.0,<8.0 ; extra == 'dev'
|
|
23
|
+
Requires-Dist: diffctx[tree-sitter] ; extra == 'dev'
|
|
24
|
+
Requires-Dist: hypothesis>=6.0,<7.0 ; extra == 'dev'
|
|
25
|
+
Requires-Dist: import-linter>=2.0,<3.0 ; extra == 'dev'
|
|
26
|
+
Requires-Dist: mypy>=1.0,<3.0 ; extra == 'dev'
|
|
27
|
+
Requires-Dist: pre-commit>=3.0,<5.0 ; extra == 'dev'
|
|
28
|
+
Requires-Dist: pygit2>=1.12,<2.0 ; extra == 'dev'
|
|
29
|
+
Requires-Dist: pytest>=7.0,<10.0 ; extra == 'dev'
|
|
30
|
+
Requires-Dist: pytest-asyncio>=0.23,<2.0 ; extra == 'dev'
|
|
31
|
+
Requires-Dist: pytest-cov>=3.0,<8.0 ; extra == 'dev'
|
|
32
|
+
Requires-Dist: pytest-timeout>=2.1,<3.0 ; extra == 'dev'
|
|
33
|
+
Requires-Dist: pytest-xdist>=3.0,<4.0 ; extra == 'dev'
|
|
34
|
+
Requires-Dist: pyyaml>=6.0.2,<8.0 ; extra == 'dev'
|
|
35
|
+
Requires-Dist: radon>=6.0,<7.0 ; extra == 'dev'
|
|
36
|
+
Requires-Dist: ruff>=0.4,<1.0 ; extra == 'dev'
|
|
37
|
+
Requires-Dist: tiktoken>=0.9,<1.0 ; extra == 'dev'
|
|
38
|
+
Requires-Dist: types-pyyaml>=6.0,<7.0 ; extra == 'dev'
|
|
39
|
+
Requires-Dist: charset-normalizer>=3.0,<4.0 ; extra == 'full'
|
|
40
|
+
Requires-Dist: diffctx[tree-sitter] ; extra == 'full'
|
|
41
|
+
Requires-Dist: anyio>=4.5,<5.0 ; extra == 'mcp'
|
|
42
|
+
Requires-Dist: mcp>=1.27,<2.0 ; extra == 'mcp'
|
|
43
|
+
Requires-Dist: tree-sitter>=0.21,<1.0 ; extra == 'tree-sitter'
|
|
44
|
+
Requires-Dist: tree-sitter-c>=0.21,<1.0 ; extra == 'tree-sitter'
|
|
45
|
+
Requires-Dist: tree-sitter-c-sharp>=0.21,<1.0 ; extra == 'tree-sitter'
|
|
46
|
+
Requires-Dist: tree-sitter-cpp>=0.22,<1.0 ; extra == 'tree-sitter'
|
|
47
|
+
Requires-Dist: tree-sitter-elixir>=0.3,<1.0 ; extra == 'tree-sitter'
|
|
48
|
+
Requires-Dist: tree-sitter-go>=0.21,<1.0 ; extra == 'tree-sitter'
|
|
49
|
+
Requires-Dist: tree-sitter-html>=0.23,<1.0 ; extra == 'tree-sitter'
|
|
50
|
+
Requires-Dist: tree-sitter-java>=0.21,<1.0 ; extra == 'tree-sitter'
|
|
51
|
+
Requires-Dist: tree-sitter-javascript>=0.21,<1.0 ; extra == 'tree-sitter'
|
|
52
|
+
Requires-Dist: tree-sitter-lua>=0.5,<1.0 ; extra == 'tree-sitter'
|
|
53
|
+
Requires-Dist: tree-sitter-php>=0.24,<1.0 ; extra == 'tree-sitter'
|
|
54
|
+
Requires-Dist: tree-sitter-python>=0.21,<1.0 ; extra == 'tree-sitter'
|
|
55
|
+
Requires-Dist: tree-sitter-ruby>=0.21,<1.0 ; extra == 'tree-sitter'
|
|
56
|
+
Requires-Dist: tree-sitter-rust>=0.21,<1.0 ; extra == 'tree-sitter'
|
|
57
|
+
Requires-Dist: tree-sitter-scala>=0.24,<1.0 ; extra == 'tree-sitter'
|
|
58
|
+
Requires-Dist: tree-sitter-swift>=0.0.1,<1.0 ; extra == 'tree-sitter'
|
|
59
|
+
Requires-Dist: tree-sitter-typescript>=0.21,<1.0 ; extra == 'tree-sitter'
|
|
60
|
+
Provides-Extra: dev
|
|
61
|
+
Provides-Extra: full
|
|
62
|
+
Provides-Extra: mcp
|
|
63
|
+
Provides-Extra: tree-sitter
|
|
64
|
+
License-File: LICENSE
|
|
65
|
+
Summary: Export codebase structure and contents for AI/LLM context
|
|
66
|
+
Keywords: ai,chatgpt,claude,code-analysis,code-context,code-review,code-to-prompt,codebase,context,context-selection,diff-context,directory-tree,export,git-diff,json,llm,llm-context,mcp,model-context-protocol,tree,yaml
|
|
67
|
+
Author-email: Nikolay Eremeev <nikolay.eremeev@outlook.com>
|
|
68
|
+
License-Expression: Apache-2.0
|
|
69
|
+
Requires-Python: >=3.10
|
|
70
|
+
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
|
|
71
|
+
Project-URL: Changelog, https://github.com/nikolay-e/diffctx/releases
|
|
72
|
+
Project-URL: Homepage, https://github.com/nikolay-e/diffctx
|
|
73
|
+
Project-URL: Issues, https://github.com/nikolay-e/diffctx/issues
|
|
74
|
+
Project-URL: Repository, https://github.com/nikolay-e/diffctx
|
|
75
|
+
|
|
76
|
+
# diffctx — smart diff context for LLM code review
|
|
77
|
+
|
|
78
|
+
[](https://github.com/nikolay-e/diffctx/actions/workflows/ci.yml)
|
|
79
|
+
[](https://pypi.org/project/diffctx/)
|
|
80
|
+
[](https://pypi.org/project/diffctx/)
|
|
81
|
+
|
|
82
|
+
**diffctx selects the minimum code an LLM needs to review a git diff.**
|
|
83
|
+
Instead of pasting whole files, it walks the dependency graph from the changed
|
|
84
|
+
lines outward and stops as soon as additional context stops paying for itself.
|
|
85
|
+
|
|
86
|
+
> Coming from [`treemapper`](https://pypi.org/project/treemapper/)? That name is
|
|
87
|
+
> deprecated — it was a thin wrapper around diffctx. Every command, flag, and
|
|
88
|
+
> API call works unchanged: `treemapper` → `diffctx`, `treemapper-mcp` → `diffctx-mcp`.
|
|
89
|
+
|
|
90
|
+
## Why not just use `tree` or repomix?
|
|
91
|
+
|
|
92
|
+
| | `tree` | repomix | Claude Code Review | **diffctx** |
|
|
93
|
+
|---|:---:|:---:|:---:|:---:|
|
|
94
|
+
| **Primary use case** | directory listing | full repo export | automated PR review | **diff context for code review** |
|
|
95
|
+
| Smart diff context | ✗ | ✗ | ✓ | ✓ |
|
|
96
|
+
| Works with any LLM | ✓ | ✓ | Claude only | ✓ |
|
|
97
|
+
| Free / local / offline | ✓ | ✓ | $15–25/review | ✓ |
|
|
98
|
+
| GitHub required | ✗ | ✗ | ✓ | ✗ |
|
|
99
|
+
| Multiple output formats | ✗ | limited | — | YAML/JSON/MD/txt |
|
|
100
|
+
| Python API | ✗ | ✗ | ✗ | ✓ |
|
|
101
|
+
| MCP server | ✗ | ✗ | ✗ | ✓ |
|
|
102
|
+
|
|
103
|
+
## Install
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
uvx diffctx . --diff HEAD~1 # zero-install, run once via uv
|
|
107
|
+
pipx install diffctx # recommended: isolated CLI, no venv needed
|
|
108
|
+
pip install diffctx # or: into an active environment
|
|
109
|
+
pipx install 'diffctx[mcp]' # + MCP server for AI assistants
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
A standalone binary (no Python required) is on the
|
|
113
|
+
[releases page](https://github.com/nikolay-e/diffctx/releases/latest).
|
|
114
|
+
The `[tree-sitter]` extra adds AST-level parsing for more accurate context
|
|
115
|
+
selection across 30+ languages.
|
|
116
|
+
|
|
117
|
+
## Quick start
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
diffctx . --diff HEAD~1 # smart context for last commit → paste into Claude/ChatGPT
|
|
121
|
+
diffctx . -f md -c # full codebase export → clipboard in Markdown
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+

|
|
125
|
+
|
|
126
|
+
*`diffctx . --diff HEAD~1` selects only the fragments — functions, imports,
|
|
127
|
+
type definitions — that an LLM actually needs to review the last commit,
|
|
128
|
+
instead of dumping every changed file in full.*
|
|
129
|
+
|
|
130
|
+
## Diff context mode
|
|
131
|
+
|
|
132
|
+
Finds the minimal set of code fragments needed to understand a change —
|
|
133
|
+
imports, callers, type definitions, config dependencies — across 50+ file
|
|
134
|
+
types. It builds a code graph (imports, co-changes, type refs), propagates
|
|
135
|
+
relevance from the changed lines outward, and stops when relevance drops below
|
|
136
|
+
`--tau` or the `--budget` token cap is reached.
|
|
137
|
+
|
|
138
|
+
| Flag | Default | Description |
|
|
139
|
+
|-------------|---------|--------------------------------------------------------------------------|
|
|
140
|
+
| `--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) |
|
|
141
|
+
| `--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) |
|
|
142
|
+
| `--alpha` | 0.60 | PPR damping; higher = context clusters tighter around changes (`--scoring ppr` only) |
|
|
143
|
+
| `--tau` | 0.12 | Relevance threshold for full fragment content; lower-scoring fragments are stubbed or dropped (lower = more context) |
|
|
144
|
+
| `--full` | false | Only the changed files, every fragment, no related-code context |
|
|
145
|
+
| `--timeout` | 300 | Wall-clock deadline in seconds; on expiry diffctx exits 124 instead of hanging |
|
|
146
|
+
|
|
147
|
+
Calibration of `--alpha`, `--tau`, and the edge-weight priors:
|
|
148
|
+
[`docs/parameter-strategy.md`](docs/parameter-strategy.md). Theory:
|
|
149
|
+
[Context-Selection for Git Diff (Zenodo, 2026)](https://doi.org/10.5281/zenodo.18824580).
|
|
150
|
+
|
|
151
|
+
### `graph` subcommand
|
|
152
|
+
|
|
153
|
+
Explore the underlying dependency graph directly, without a diff:
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
diffctx graph . # Mermaid graph of directory deps (default)
|
|
157
|
+
diffctx graph . --summary # cycles, hotspots, coupling metrics
|
|
158
|
+
diffctx graph . --level fragment -f json # fragment-level graph as JSON
|
|
159
|
+
diffctx graph . --level file -f graphml -o g.xml # file-level graph as GraphML
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
## Usage
|
|
163
|
+
|
|
164
|
+
<!-- BEGIN USAGE -->
|
|
165
|
+
```bash
|
|
166
|
+
# full codebase export:
|
|
167
|
+
diffctx . # Markdown to stdout + token count
|
|
168
|
+
diffctx . -f md -c # Markdown → clipboard
|
|
169
|
+
diffctx . -f json -o tree.json # JSON → file
|
|
170
|
+
diffctx . --no-content # structure only, no file contents
|
|
171
|
+
diffctx . --max-depth 3 # limit depth
|
|
172
|
+
diffctx . -i custom.ignore # custom ignore patterns
|
|
173
|
+
|
|
174
|
+
# diff context mode (requires git repo):
|
|
175
|
+
diffctx . --diff # uncommitted changes (working tree vs HEAD)
|
|
176
|
+
diffctx . --diff HEAD~1 # context for last commit
|
|
177
|
+
diffctx . --diff main..feature # context for feature branch
|
|
178
|
+
diffctx . --diff HEAD~1 --budget 30000 # limit to ~30k tokens
|
|
179
|
+
diffctx . --diff HEAD~1 -c # diff context to clipboard
|
|
180
|
+
```
|
|
181
|
+
<!-- END USAGE -->
|
|
182
|
+
|
|
183
|
+
Every run reports token count and size on stderr — `12,847 tokens
|
|
184
|
+
(o200k_base), 52.3 KB` (tiktoken, the GPT-4o tokenizer; `~`-prefixed
|
|
185
|
+
approximation above 1 MB). `-c/--copy` sends output to the clipboard via
|
|
186
|
+
`pbcopy` (macOS), `clip` (Windows), or `wl-copy`/`xclip`/`xsel` (Linux).
|
|
187
|
+
Unreadable files are replaced by placeholders such as `<binary file: N bytes>`,
|
|
188
|
+
`<file too large: N bytes>`, or `<unreadable content: not utf-8>`.
|
|
189
|
+
|
|
190
|
+
## Python API
|
|
191
|
+
|
|
192
|
+
```python
|
|
193
|
+
from pathlib import Path
|
|
194
|
+
from diffctx import build_diff_context, map_directory, to_json, to_markdown, to_text, to_yaml
|
|
195
|
+
|
|
196
|
+
ctx = build_diff_context(
|
|
197
|
+
Path("."),
|
|
198
|
+
"HEAD~1..HEAD",
|
|
199
|
+
budget_tokens=None, # None = auto; 0 = strict-zero floor (empty); -1 = uncapped; N = hard cap
|
|
200
|
+
alpha=0.6,
|
|
201
|
+
tau=0.12,
|
|
202
|
+
full=False,
|
|
203
|
+
scoring_mode="ego",
|
|
204
|
+
timeout=300,
|
|
205
|
+
)
|
|
206
|
+
print(to_markdown(ctx))
|
|
207
|
+
|
|
208
|
+
tree = map_directory(
|
|
209
|
+
".",
|
|
210
|
+
max_depth=None,
|
|
211
|
+
no_content=False,
|
|
212
|
+
max_file_bytes=None,
|
|
213
|
+
ignore_file=None,
|
|
214
|
+
no_default_ignores=False,
|
|
215
|
+
whitelist_file=None,
|
|
216
|
+
)
|
|
217
|
+
print(to_yaml(tree))
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
## MCP server
|
|
221
|
+
|
|
222
|
+
diffctx includes an [MCP](https://modelcontextprotocol.io) server that lets AI
|
|
223
|
+
assistants (Claude Code, Cursor, Windsurf, etc.) call diff context analysis
|
|
224
|
+
automatically during code review. Install with `pip install 'diffctx[mcp]'`
|
|
225
|
+
and add to your MCP client config (e.g. `~/.claude/mcp.json` for Claude Code):
|
|
226
|
+
|
|
227
|
+
```json
|
|
228
|
+
{
|
|
229
|
+
"mcpServers": {
|
|
230
|
+
"diffctx": {
|
|
231
|
+
"command": "diffctx-mcp"
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
The server exposes a `get_diff_context` tool that assistants call when
|
|
238
|
+
reviewing PRs, explaining changes, or investigating broken tests. Configs for
|
|
239
|
+
Cursor, Continue, Windsurf, and Zed:
|
|
240
|
+
[`src/diffctx/mcp/README.md`](src/diffctx/mcp/README.md).
|
|
241
|
+
|
|
242
|
+
## Ignore patterns
|
|
243
|
+
|
|
244
|
+
Respects `.gitignore` and `.diffctx/ignore` automatically — hierarchically at
|
|
245
|
+
every directory level, with gitignore semantics (negation `!important.log`,
|
|
246
|
+
anchored `/root_only.txt`). `.diffctx/whitelist` acts as an include-only
|
|
247
|
+
filter, and the output file is always auto-ignored. `--no-default-ignores`
|
|
248
|
+
disables the built-in patterns; `--no-ignores` disables all ignore rules
|
|
249
|
+
(tree mode only).
|
|
250
|
+
|
|
251
|
+
## Exit codes
|
|
252
|
+
|
|
253
|
+
| Code | Meaning |
|
|
254
|
+
|------|---------|
|
|
255
|
+
| `0` | Success — output contains content |
|
|
256
|
+
| `1` | Runtime error (bad path, permission denied, etc.) |
|
|
257
|
+
| `2` | Usage error (invalid flags/arguments) |
|
|
258
|
+
| `3` | Environment error (`--diff` outside a git repo, git not installed, no commits yet) |
|
|
259
|
+
| `4` | `--diff` produced no semantic context (clean tree, binary-only, everything filtered); output is still emitted. Deletion/rename-only diffs list `deleted_files`/`renamed_files` and exit `0` |
|
|
260
|
+
| `130`| Interrupted (Ctrl-C) |
|
|
261
|
+
| `141`| Broken pipe (e.g. piping into `head`) |
|
|
262
|
+
|
|
263
|
+
## License
|
|
264
|
+
|
|
265
|
+
Apache 2.0
|
|
266
|
+
|
|
267
|
+
---
|
|
268
|
+
|
|
269
|
+
- [Changelog](CHANGELOG.md)
|
|
270
|
+
- [Security policy](SECURITY.md) — threat model and vulnerability reporting
|
|
271
|
+
- [Parameter strategy](docs/parameter-strategy.md) — how `--alpha`,
|
|
272
|
+
`--tau`, and edge weights are calibrated
|
|
273
|
+
|
diffctx-1.12.0/README.md
ADDED
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
# diffctx — smart diff context for LLM code review
|
|
2
|
+
|
|
3
|
+
[](https://github.com/nikolay-e/diffctx/actions/workflows/ci.yml)
|
|
4
|
+
[](https://pypi.org/project/diffctx/)
|
|
5
|
+
[](https://pypi.org/project/diffctx/)
|
|
6
|
+
|
|
7
|
+
**diffctx selects the minimum code an LLM needs to review a git diff.**
|
|
8
|
+
Instead of pasting whole files, it walks the dependency graph from the changed
|
|
9
|
+
lines outward and stops as soon as additional context stops paying for itself.
|
|
10
|
+
|
|
11
|
+
> Coming from [`treemapper`](https://pypi.org/project/treemapper/)? That name is
|
|
12
|
+
> deprecated — it was a thin wrapper around diffctx. Every command, flag, and
|
|
13
|
+
> API call works unchanged: `treemapper` → `diffctx`, `treemapper-mcp` → `diffctx-mcp`.
|
|
14
|
+
|
|
15
|
+
## Why not just use `tree` or repomix?
|
|
16
|
+
|
|
17
|
+
| | `tree` | repomix | Claude Code Review | **diffctx** |
|
|
18
|
+
|---|:---:|:---:|:---:|:---:|
|
|
19
|
+
| **Primary use case** | directory listing | full repo export | automated PR review | **diff context for code review** |
|
|
20
|
+
| Smart diff context | ✗ | ✗ | ✓ | ✓ |
|
|
21
|
+
| Works with any LLM | ✓ | ✓ | Claude only | ✓ |
|
|
22
|
+
| Free / local / offline | ✓ | ✓ | $15–25/review | ✓ |
|
|
23
|
+
| GitHub required | ✗ | ✗ | ✓ | ✗ |
|
|
24
|
+
| Multiple output formats | ✗ | limited | — | YAML/JSON/MD/txt |
|
|
25
|
+
| Python API | ✗ | ✗ | ✗ | ✓ |
|
|
26
|
+
| MCP server | ✗ | ✗ | ✗ | ✓ |
|
|
27
|
+
|
|
28
|
+
## Install
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
uvx diffctx . --diff HEAD~1 # zero-install, run once via uv
|
|
32
|
+
pipx install diffctx # recommended: isolated CLI, no venv needed
|
|
33
|
+
pip install diffctx # or: into an active environment
|
|
34
|
+
pipx install 'diffctx[mcp]' # + MCP server for AI assistants
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
A standalone binary (no Python required) is on the
|
|
38
|
+
[releases page](https://github.com/nikolay-e/diffctx/releases/latest).
|
|
39
|
+
The `[tree-sitter]` extra adds AST-level parsing for more accurate context
|
|
40
|
+
selection across 30+ languages.
|
|
41
|
+
|
|
42
|
+
## Quick start
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
diffctx . --diff HEAD~1 # smart context for last commit → paste into Claude/ChatGPT
|
|
46
|
+
diffctx . -f md -c # full codebase export → clipboard in Markdown
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+

|
|
50
|
+
|
|
51
|
+
*`diffctx . --diff HEAD~1` selects only the fragments — functions, imports,
|
|
52
|
+
type definitions — that an LLM actually needs to review the last commit,
|
|
53
|
+
instead of dumping every changed file in full.*
|
|
54
|
+
|
|
55
|
+
## Diff context mode
|
|
56
|
+
|
|
57
|
+
Finds the minimal set of code fragments needed to understand a change —
|
|
58
|
+
imports, callers, type definitions, config dependencies — across 50+ file
|
|
59
|
+
types. It builds a code graph (imports, co-changes, type refs), propagates
|
|
60
|
+
relevance from the changed lines outward, and stops when relevance drops below
|
|
61
|
+
`--tau` or the `--budget` token cap is reached.
|
|
62
|
+
|
|
63
|
+
| Flag | Default | Description |
|
|
64
|
+
|-------------|---------|--------------------------------------------------------------------------|
|
|
65
|
+
| `--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) |
|
|
66
|
+
| `--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) |
|
|
67
|
+
| `--alpha` | 0.60 | PPR damping; higher = context clusters tighter around changes (`--scoring ppr` only) |
|
|
68
|
+
| `--tau` | 0.12 | Relevance threshold for full fragment content; lower-scoring fragments are stubbed or dropped (lower = more context) |
|
|
69
|
+
| `--full` | false | Only the changed files, every fragment, no related-code context |
|
|
70
|
+
| `--timeout` | 300 | Wall-clock deadline in seconds; on expiry diffctx exits 124 instead of hanging |
|
|
71
|
+
|
|
72
|
+
Calibration of `--alpha`, `--tau`, and the edge-weight priors:
|
|
73
|
+
[`docs/parameter-strategy.md`](docs/parameter-strategy.md). Theory:
|
|
74
|
+
[Context-Selection for Git Diff (Zenodo, 2026)](https://doi.org/10.5281/zenodo.18824580).
|
|
75
|
+
|
|
76
|
+
### `graph` subcommand
|
|
77
|
+
|
|
78
|
+
Explore the underlying dependency graph directly, without a diff:
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
diffctx graph . # Mermaid graph of directory deps (default)
|
|
82
|
+
diffctx graph . --summary # cycles, hotspots, coupling metrics
|
|
83
|
+
diffctx graph . --level fragment -f json # fragment-level graph as JSON
|
|
84
|
+
diffctx graph . --level file -f graphml -o g.xml # file-level graph as GraphML
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Usage
|
|
88
|
+
|
|
89
|
+
<!-- BEGIN USAGE -->
|
|
90
|
+
```bash
|
|
91
|
+
# full codebase export:
|
|
92
|
+
diffctx . # Markdown to stdout + token count
|
|
93
|
+
diffctx . -f md -c # Markdown → clipboard
|
|
94
|
+
diffctx . -f json -o tree.json # JSON → file
|
|
95
|
+
diffctx . --no-content # structure only, no file contents
|
|
96
|
+
diffctx . --max-depth 3 # limit depth
|
|
97
|
+
diffctx . -i custom.ignore # custom ignore patterns
|
|
98
|
+
|
|
99
|
+
# diff context mode (requires git repo):
|
|
100
|
+
diffctx . --diff # uncommitted changes (working tree vs HEAD)
|
|
101
|
+
diffctx . --diff HEAD~1 # context for last commit
|
|
102
|
+
diffctx . --diff main..feature # context for feature branch
|
|
103
|
+
diffctx . --diff HEAD~1 --budget 30000 # limit to ~30k tokens
|
|
104
|
+
diffctx . --diff HEAD~1 -c # diff context to clipboard
|
|
105
|
+
```
|
|
106
|
+
<!-- END USAGE -->
|
|
107
|
+
|
|
108
|
+
Every run reports token count and size on stderr — `12,847 tokens
|
|
109
|
+
(o200k_base), 52.3 KB` (tiktoken, the GPT-4o tokenizer; `~`-prefixed
|
|
110
|
+
approximation above 1 MB). `-c/--copy` sends output to the clipboard via
|
|
111
|
+
`pbcopy` (macOS), `clip` (Windows), or `wl-copy`/`xclip`/`xsel` (Linux).
|
|
112
|
+
Unreadable files are replaced by placeholders such as `<binary file: N bytes>`,
|
|
113
|
+
`<file too large: N bytes>`, or `<unreadable content: not utf-8>`.
|
|
114
|
+
|
|
115
|
+
## Python API
|
|
116
|
+
|
|
117
|
+
```python
|
|
118
|
+
from pathlib import Path
|
|
119
|
+
from diffctx import build_diff_context, map_directory, to_json, to_markdown, to_text, to_yaml
|
|
120
|
+
|
|
121
|
+
ctx = build_diff_context(
|
|
122
|
+
Path("."),
|
|
123
|
+
"HEAD~1..HEAD",
|
|
124
|
+
budget_tokens=None, # None = auto; 0 = strict-zero floor (empty); -1 = uncapped; N = hard cap
|
|
125
|
+
alpha=0.6,
|
|
126
|
+
tau=0.12,
|
|
127
|
+
full=False,
|
|
128
|
+
scoring_mode="ego",
|
|
129
|
+
timeout=300,
|
|
130
|
+
)
|
|
131
|
+
print(to_markdown(ctx))
|
|
132
|
+
|
|
133
|
+
tree = map_directory(
|
|
134
|
+
".",
|
|
135
|
+
max_depth=None,
|
|
136
|
+
no_content=False,
|
|
137
|
+
max_file_bytes=None,
|
|
138
|
+
ignore_file=None,
|
|
139
|
+
no_default_ignores=False,
|
|
140
|
+
whitelist_file=None,
|
|
141
|
+
)
|
|
142
|
+
print(to_yaml(tree))
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## MCP server
|
|
146
|
+
|
|
147
|
+
diffctx includes an [MCP](https://modelcontextprotocol.io) server that lets AI
|
|
148
|
+
assistants (Claude Code, Cursor, Windsurf, etc.) call diff context analysis
|
|
149
|
+
automatically during code review. Install with `pip install 'diffctx[mcp]'`
|
|
150
|
+
and add to your MCP client config (e.g. `~/.claude/mcp.json` for Claude Code):
|
|
151
|
+
|
|
152
|
+
```json
|
|
153
|
+
{
|
|
154
|
+
"mcpServers": {
|
|
155
|
+
"diffctx": {
|
|
156
|
+
"command": "diffctx-mcp"
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
The server exposes a `get_diff_context` tool that assistants call when
|
|
163
|
+
reviewing PRs, explaining changes, or investigating broken tests. Configs for
|
|
164
|
+
Cursor, Continue, Windsurf, and Zed:
|
|
165
|
+
[`src/diffctx/mcp/README.md`](src/diffctx/mcp/README.md).
|
|
166
|
+
|
|
167
|
+
## Ignore patterns
|
|
168
|
+
|
|
169
|
+
Respects `.gitignore` and `.diffctx/ignore` automatically — hierarchically at
|
|
170
|
+
every directory level, with gitignore semantics (negation `!important.log`,
|
|
171
|
+
anchored `/root_only.txt`). `.diffctx/whitelist` acts as an include-only
|
|
172
|
+
filter, and the output file is always auto-ignored. `--no-default-ignores`
|
|
173
|
+
disables the built-in patterns; `--no-ignores` disables all ignore rules
|
|
174
|
+
(tree mode only).
|
|
175
|
+
|
|
176
|
+
## Exit codes
|
|
177
|
+
|
|
178
|
+
| Code | Meaning |
|
|
179
|
+
|------|---------|
|
|
180
|
+
| `0` | Success — output contains content |
|
|
181
|
+
| `1` | Runtime error (bad path, permission denied, etc.) |
|
|
182
|
+
| `2` | Usage error (invalid flags/arguments) |
|
|
183
|
+
| `3` | Environment error (`--diff` outside a git repo, git not installed, no commits yet) |
|
|
184
|
+
| `4` | `--diff` produced no semantic context (clean tree, binary-only, everything filtered); output is still emitted. Deletion/rename-only diffs list `deleted_files`/`renamed_files` and exit `0` |
|
|
185
|
+
| `130`| Interrupted (Ctrl-C) |
|
|
186
|
+
| `141`| Broken pipe (e.g. piping into `head`) |
|
|
187
|
+
|
|
188
|
+
## License
|
|
189
|
+
|
|
190
|
+
Apache 2.0
|
|
191
|
+
|
|
192
|
+
---
|
|
193
|
+
|
|
194
|
+
- [Changelog](CHANGELOG.md)
|
|
195
|
+
- [Security policy](SECURITY.md) — threat model and vulnerability reporting
|
|
196
|
+
- [Parameter strategy](docs/parameter-strategy.md) — how `--alpha`,
|
|
197
|
+
`--tau`, and edge weights are calibrated
|
|
@@ -63,9 +63,9 @@ dependencies = [
|
|
|
63
63
|
|
|
64
64
|
[[package]]
|
|
65
65
|
name = "anyhow"
|
|
66
|
-
version = "1.0.
|
|
66
|
+
version = "1.0.103"
|
|
67
67
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
68
|
-
checksum = "
|
|
68
|
+
checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d87726c0798709bf56d9ed84af3"
|
|
69
69
|
|
|
70
70
|
[[package]]
|
|
71
71
|
name = "base64"
|
|
@@ -123,9 +123,9 @@ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
|
|
123
123
|
|
|
124
124
|
[[package]]
|
|
125
125
|
name = "clap"
|
|
126
|
-
version = "4.6.
|
|
126
|
+
version = "4.6.2"
|
|
127
127
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
128
|
-
checksum = "
|
|
128
|
+
checksum = "dd059f9da4f5c36b3787f65d38ccaab1cc315f07b01f89abc8359ee6a8205011"
|
|
129
129
|
dependencies = [
|
|
130
130
|
"clap_builder",
|
|
131
131
|
"clap_derive",
|
|
@@ -133,9 +133,9 @@ dependencies = [
|
|
|
133
133
|
|
|
134
134
|
[[package]]
|
|
135
135
|
name = "clap_builder"
|
|
136
|
-
version = "4.6.
|
|
136
|
+
version = "4.6.2"
|
|
137
137
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
138
|
-
checksum = "
|
|
138
|
+
checksum = "f09628afdcc538b57f3c6341e9c8e9970f18e4a481690a64974d7023bd33548b"
|
|
139
139
|
dependencies = [
|
|
140
140
|
"anstream",
|
|
141
141
|
"anstyle",
|
|
@@ -194,11 +194,12 @@ checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
|
|
|
194
194
|
|
|
195
195
|
[[package]]
|
|
196
196
|
name = "diffctx"
|
|
197
|
-
version = "1.
|
|
197
|
+
version = "1.12.0"
|
|
198
198
|
dependencies = [
|
|
199
199
|
"anyhow",
|
|
200
200
|
"clap",
|
|
201
201
|
"glob",
|
|
202
|
+
"libc",
|
|
202
203
|
"libtest-mimic",
|
|
203
204
|
"mimalloc",
|
|
204
205
|
"once_cell",
|
|
@@ -210,7 +211,6 @@ dependencies = [
|
|
|
210
211
|
"serde_json",
|
|
211
212
|
"serde_yaml",
|
|
212
213
|
"similar",
|
|
213
|
-
"smallvec",
|
|
214
214
|
"tempfile",
|
|
215
215
|
"thiserror",
|
|
216
216
|
"tiktoken-rs",
|
|
@@ -399,9 +399,9 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
|
|
|
399
399
|
|
|
400
400
|
[[package]]
|
|
401
401
|
name = "libc"
|
|
402
|
-
version = "0.2.
|
|
402
|
+
version = "0.2.186"
|
|
403
403
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
404
|
-
checksum = "
|
|
404
|
+
checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
|
|
405
405
|
|
|
406
406
|
[[package]]
|
|
407
407
|
name = "libmimalloc-sys"
|
|
@@ -606,9 +606,9 @@ dependencies = [
|
|
|
606
606
|
|
|
607
607
|
[[package]]
|
|
608
608
|
name = "regex"
|
|
609
|
-
version = "1.
|
|
609
|
+
version = "1.13.1"
|
|
610
610
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
611
|
-
checksum = "
|
|
611
|
+
checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d"
|
|
612
612
|
dependencies = [
|
|
613
613
|
"aho-corasick",
|
|
614
614
|
"memchr",
|
|
@@ -618,9 +618,9 @@ dependencies = [
|
|
|
618
618
|
|
|
619
619
|
[[package]]
|
|
620
620
|
name = "regex-automata"
|
|
621
|
-
version = "0.4.
|
|
621
|
+
version = "0.4.16"
|
|
622
622
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
623
|
-
checksum = "
|
|
623
|
+
checksum = "8fcfdb36bda0c880c5931cdc7a2bcdc8ba4556847b9d912bca70bc94708711ad"
|
|
624
624
|
dependencies = [
|
|
625
625
|
"aho-corasick",
|
|
626
626
|
"memchr",
|
|
@@ -635,9 +635,9 @@ checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4"
|
|
|
635
635
|
|
|
636
636
|
[[package]]
|
|
637
637
|
name = "rustc-hash"
|
|
638
|
-
version = "2.1.
|
|
638
|
+
version = "2.1.3"
|
|
639
639
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
640
|
-
checksum = "
|
|
640
|
+
checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d"
|
|
641
641
|
|
|
642
642
|
[[package]]
|
|
643
643
|
name = "rustix"
|
|
@@ -964,9 +964,9 @@ dependencies = [
|
|
|
964
964
|
|
|
965
965
|
[[package]]
|
|
966
966
|
name = "tree-sitter-cmake"
|
|
967
|
-
version = "0.7.
|
|
967
|
+
version = "0.7.4"
|
|
968
968
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
969
|
-
checksum = "
|
|
969
|
+
checksum = "164e0c4f4236ec5ceff14824a5528615cf462e100467e49826442ff57d327061"
|
|
970
970
|
dependencies = [
|
|
971
971
|
"cc",
|
|
972
972
|
"tree-sitter-language",
|