diffctx 1.11.0__tar.gz → 1.12.1__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.11.0 → diffctx-1.12.1}/CHANGELOG.md +103 -0
- diffctx-1.12.1/PKG-INFO +287 -0
- diffctx-1.12.1/README.md +210 -0
- {diffctx-1.11.0 → diffctx-1.12.1}/diffctx/Cargo.lock +16 -16
- {diffctx-1.11.0 → diffctx-1.12.1}/diffctx/Cargo.toml +13 -11
- diffctx-1.12.1/diffctx/README.md +210 -0
- {diffctx-1.11.0 → diffctx-1.12.1}/diffctx/src/config/limits.rs +4 -3
- {diffctx-1.11.0 → diffctx-1.12.1}/diffctx/src/config/selection.rs +6 -6
- {diffctx-1.11.0 → diffctx-1.12.1}/diffctx/src/discovery.rs +25 -52
- {diffctx-1.11.0 → diffctx-1.12.1}/diffctx/src/edges/history/cochange.rs +1 -4
- diffctx-1.12.1/diffctx/src/edges/mod.rs +282 -0
- {diffctx-1.11.0 → diffctx-1.12.1}/diffctx/src/git.rs +31 -15
- {diffctx-1.11.0 → diffctx-1.12.1}/diffctx/src/graph.rs +199 -50
- {diffctx-1.11.0 → diffctx-1.12.1}/diffctx/src/lib.rs +2 -0
- {diffctx-1.11.0 → diffctx-1.12.1}/diffctx/src/main.rs +5 -2
- {diffctx-1.11.0 → diffctx-1.12.1}/diffctx/src/parsers/tree_sitter_strategy.rs +6 -1
- diffctx-1.12.1/diffctx/src/peak_rss.rs +46 -0
- {diffctx-1.11.0 → diffctx-1.12.1}/diffctx/src/pipeline.rs +21 -4
- {diffctx-1.11.0 → diffctx-1.12.1}/diffctx/src/postpass.rs +21 -8
- {diffctx-1.11.0 → diffctx-1.12.1}/diffctx/src/project_graph.rs +16 -28
- {diffctx-1.11.0 → diffctx-1.12.1}/diffctx/src/pybridge.rs +20 -0
- {diffctx-1.11.0 → diffctx-1.12.1}/diffctx/src/render.rs +19 -5
- {diffctx-1.11.0 → diffctx-1.12.1}/diffctx/src/scoring.rs +17 -4
- {diffctx-1.11.0 → diffctx-1.12.1}/diffctx/src/signatures.rs +22 -1
- diffctx-1.12.1/diffctx/src/token_corpus.rs +228 -0
- {diffctx-1.11.0 → diffctx-1.12.1}/diffctx/src/types.rs +14 -1
- {diffctx-1.11.0 → diffctx-1.12.1}/pyproject.toml +1 -17
- {diffctx-1.11.0 → diffctx-1.12.1}/src/diffctx/cli.py +210 -56
- diffctx-1.12.1/src/diffctx/diffctx/graph_analytics.py +293 -0
- {diffctx-1.11.0 → diffctx-1.12.1}/src/diffctx/diffctx/graph_export.py +16 -8
- {diffctx-1.11.0 → diffctx-1.12.1}/src/diffctx/diffctx/pipeline.py +1 -14
- diffctx-1.12.1/src/diffctx/diffctx/project_graph.py +36 -0
- {diffctx-1.11.0 → diffctx-1.12.1}/src/diffctx/ignore.py +11 -3
- {diffctx-1.11.0 → diffctx-1.12.1}/src/diffctx/logger.py +3 -2
- {diffctx-1.11.0 → diffctx-1.12.1}/src/diffctx/main.py +109 -33
- {diffctx-1.11.0 → diffctx-1.12.1}/src/diffctx/tokens.py +1 -4
- {diffctx-1.11.0 → diffctx-1.12.1}/src/diffctx/tree.py +11 -0
- diffctx-1.12.1/src/diffctx/version.py +1 -0
- {diffctx-1.11.0 → diffctx-1.12.1}/src/diffctx/writer.py +28 -6
- diffctx-1.11.0/PKG-INFO +0 -388
- diffctx-1.11.0/README.md +0 -310
- diffctx-1.11.0/diffctx/src/edges/mod.rs +0 -155
- diffctx-1.11.0/diffctx/tests/common/mod.rs +0 -274
- diffctx-1.11.0/diffctx/tests/fixtures/garbage/garbage_api.py +0 -31
- diffctx-1.11.0/diffctx/tests/fixtures/garbage/garbage_constants.py +0 -11
- diffctx-1.11.0/diffctx/tests/fixtures/garbage/garbage_handlers.py +0 -27
- diffctx-1.11.0/diffctx/tests/fixtures/garbage/garbage_models.py +0 -25
- diffctx-1.11.0/diffctx/tests/fixtures/garbage/garbage_module.js +0 -23
- diffctx-1.11.0/diffctx/tests/fixtures/garbage/garbage_services.py +0 -37
- diffctx-1.11.0/diffctx/tests/fixtures/garbage/garbage_types.py +0 -31
- diffctx-1.11.0/diffctx/tests/fixtures/garbage/garbage_unrelated.yaml +0 -8
- diffctx-1.11.0/diffctx/tests/fixtures/garbage/garbage_utils.py +0 -31
- diffctx-1.11.0/diffctx/tests/fixtures/garbage/garbage_validators.py +0 -23
- diffctx-1.11.0/diffctx/tests/yaml_cases.rs +0 -279
- diffctx-1.11.0/src/diffctx/diffctx/graph_analytics.py +0 -59
- diffctx-1.11.0/src/diffctx/diffctx/project_graph.py +0 -27
- diffctx-1.11.0/src/diffctx/version.py +0 -1
- {diffctx-1.11.0 → diffctx-1.12.1}/LICENSE +0 -0
- {diffctx-1.11.0 → diffctx-1.12.1}/diffctx/src/analytics.rs +0 -0
- {diffctx-1.11.0 → diffctx-1.12.1}/diffctx/src/candidate_files.rs +0 -0
- {diffctx-1.11.0 → diffctx-1.12.1}/diffctx/src/config/analytics.rs +0 -0
- {diffctx-1.11.0 → diffctx-1.12.1}/diffctx/src/config/bm25.rs +0 -0
- {diffctx-1.11.0 → diffctx-1.12.1}/diffctx/src/config/budget.rs +0 -0
- {diffctx-1.11.0 → diffctx-1.12.1}/diffctx/src/config/category_weights.rs +0 -0
- {diffctx-1.11.0 → diffctx-1.12.1}/diffctx/src/config/edge_weights.rs +0 -0
- {diffctx-1.11.0 → diffctx-1.12.1}/diffctx/src/config/env_overrides.rs +0 -0
- {diffctx-1.11.0 → diffctx-1.12.1}/diffctx/src/config/extensions.rs +0 -0
- {diffctx-1.11.0 → diffctx-1.12.1}/diffctx/src/config/filtering.rs +0 -0
- {diffctx-1.11.0 → diffctx-1.12.1}/diffctx/src/config/fragmentation.rs +0 -0
- {diffctx-1.11.0 → diffctx-1.12.1}/diffctx/src/config/git.rs +0 -0
- {diffctx-1.11.0 → diffctx-1.12.1}/diffctx/src/config/graph_filtering.rs +0 -0
- {diffctx-1.11.0 → diffctx-1.12.1}/diffctx/src/config/importance.rs +0 -0
- {diffctx-1.11.0 → diffctx-1.12.1}/diffctx/src/config/mod.rs +0 -0
- {diffctx-1.11.0 → diffctx-1.12.1}/diffctx/src/config/mode.rs +0 -0
- {diffctx-1.11.0 → diffctx-1.12.1}/diffctx/src/config/needs.rs +0 -0
- {diffctx-1.11.0 → diffctx-1.12.1}/diffctx/src/config/parsers.rs +0 -0
- {diffctx-1.11.0 → diffctx-1.12.1}/diffctx/src/config/render.rs +0 -0
- {diffctx-1.11.0 → diffctx-1.12.1}/diffctx/src/config/scoring.rs +0 -0
- {diffctx-1.11.0 → diffctx-1.12.1}/diffctx/src/config/tokenization.rs +0 -0
- {diffctx-1.11.0 → diffctx-1.12.1}/diffctx/src/config/weights.rs +0 -0
- {diffctx-1.11.0 → diffctx-1.12.1}/diffctx/src/core.rs +0 -0
- {diffctx-1.11.0 → diffctx-1.12.1}/diffctx/src/edges/base.rs +0 -0
- {diffctx-1.11.0 → diffctx-1.12.1}/diffctx/src/edges/config_edges/build_system.rs +0 -0
- {diffctx-1.11.0 → diffctx-1.12.1}/diffctx/src/edges/config_edges/cicd.rs +0 -0
- {diffctx-1.11.0 → diffctx-1.12.1}/diffctx/src/edges/config_edges/docker.rs +0 -0
- {diffctx-1.11.0 → diffctx-1.12.1}/diffctx/src/edges/config_edges/generic.rs +0 -0
- {diffctx-1.11.0 → diffctx-1.12.1}/diffctx/src/edges/config_edges/helm.rs +0 -0
- {diffctx-1.11.0 → diffctx-1.12.1}/diffctx/src/edges/config_edges/kubernetes.rs +0 -0
- {diffctx-1.11.0 → diffctx-1.12.1}/diffctx/src/edges/config_edges/mod.rs +0 -0
- {diffctx-1.11.0 → diffctx-1.12.1}/diffctx/src/edges/document/mod.rs +0 -0
- {diffctx-1.11.0 → diffctx-1.12.1}/diffctx/src/edges/history/mod.rs +0 -0
- {diffctx-1.11.0 → diffctx-1.12.1}/diffctx/src/edges/semantic/ansible.rs +0 -0
- {diffctx-1.11.0 → diffctx-1.12.1}/diffctx/src/edges/semantic/bazel.rs +0 -0
- {diffctx-1.11.0 → diffctx-1.12.1}/diffctx/src/edges/semantic/c_family.rs +0 -0
- {diffctx-1.11.0 → diffctx-1.12.1}/diffctx/src/edges/semantic/cargo_edges.rs +0 -0
- {diffctx-1.11.0 → diffctx-1.12.1}/diffctx/src/edges/semantic/clojure.rs +0 -0
- {diffctx-1.11.0 → diffctx-1.12.1}/diffctx/src/edges/semantic/css.rs +0 -0
- {diffctx-1.11.0 → diffctx-1.12.1}/diffctx/src/edges/semantic/dart.rs +0 -0
- {diffctx-1.11.0 → diffctx-1.12.1}/diffctx/src/edges/semantic/dbt.rs +0 -0
- {diffctx-1.11.0 → diffctx-1.12.1}/diffctx/src/edges/semantic/dotnet.rs +0 -0
- {diffctx-1.11.0 → diffctx-1.12.1}/diffctx/src/edges/semantic/elixir.rs +0 -0
- {diffctx-1.11.0 → diffctx-1.12.1}/diffctx/src/edges/semantic/erlang.rs +0 -0
- {diffctx-1.11.0 → diffctx-1.12.1}/diffctx/src/edges/semantic/go.rs +0 -0
- {diffctx-1.11.0 → diffctx-1.12.1}/diffctx/src/edges/semantic/graphql.rs +0 -0
- {diffctx-1.11.0 → diffctx-1.12.1}/diffctx/src/edges/semantic/haskell.rs +0 -0
- {diffctx-1.11.0 → diffctx-1.12.1}/diffctx/src/edges/semantic/javascript.rs +0 -0
- {diffctx-1.11.0 → diffctx-1.12.1}/diffctx/src/edges/semantic/julia.rs +0 -0
- {diffctx-1.11.0 → diffctx-1.12.1}/diffctx/src/edges/semantic/jvm.rs +0 -0
- {diffctx-1.11.0 → diffctx-1.12.1}/diffctx/src/edges/semantic/latex.rs +0 -0
- {diffctx-1.11.0 → diffctx-1.12.1}/diffctx/src/edges/semantic/lua.rs +0 -0
- {diffctx-1.11.0 → diffctx-1.12.1}/diffctx/src/edges/semantic/mod.rs +0 -0
- {diffctx-1.11.0 → diffctx-1.12.1}/diffctx/src/edges/semantic/nim.rs +0 -0
- {diffctx-1.11.0 → diffctx-1.12.1}/diffctx/src/edges/semantic/nix.rs +0 -0
- {diffctx-1.11.0 → diffctx-1.12.1}/diffctx/src/edges/semantic/ocaml.rs +0 -0
- {diffctx-1.11.0 → diffctx-1.12.1}/diffctx/src/edges/semantic/openapi.rs +0 -0
- {diffctx-1.11.0 → diffctx-1.12.1}/diffctx/src/edges/semantic/perl.rs +0 -0
- {diffctx-1.11.0 → diffctx-1.12.1}/diffctx/src/edges/semantic/php.rs +0 -0
- {diffctx-1.11.0 → diffctx-1.12.1}/diffctx/src/edges/semantic/prisma.rs +0 -0
- {diffctx-1.11.0 → diffctx-1.12.1}/diffctx/src/edges/semantic/protobuf.rs +0 -0
- {diffctx-1.11.0 → diffctx-1.12.1}/diffctx/src/edges/semantic/python.rs +0 -0
- {diffctx-1.11.0 → diffctx-1.12.1}/diffctx/src/edges/semantic/r_lang.rs +0 -0
- {diffctx-1.11.0 → diffctx-1.12.1}/diffctx/src/edges/semantic/ruby.rs +0 -0
- {diffctx-1.11.0 → diffctx-1.12.1}/diffctx/src/edges/semantic/rust_lang.rs +0 -0
- {diffctx-1.11.0 → diffctx-1.12.1}/diffctx/src/edges/semantic/shell.rs +0 -0
- {diffctx-1.11.0 → diffctx-1.12.1}/diffctx/src/edges/semantic/sql.rs +0 -0
- {diffctx-1.11.0 → diffctx-1.12.1}/diffctx/src/edges/semantic/swift.rs +0 -0
- {diffctx-1.11.0 → diffctx-1.12.1}/diffctx/src/edges/semantic/tags.rs +0 -0
- {diffctx-1.11.0 → diffctx-1.12.1}/diffctx/src/edges/semantic/terraform.rs +0 -0
- {diffctx-1.11.0 → diffctx-1.12.1}/diffctx/src/edges/semantic/zig.rs +0 -0
- {diffctx-1.11.0 → diffctx-1.12.1}/diffctx/src/edges/similarity/lexical.rs +0 -0
- {diffctx-1.11.0 → diffctx-1.12.1}/diffctx/src/edges/similarity/mod.rs +0 -0
- {diffctx-1.11.0 → diffctx-1.12.1}/diffctx/src/edges/structural/containment.rs +0 -0
- {diffctx-1.11.0 → diffctx-1.12.1}/diffctx/src/edges/structural/mod.rs +0 -0
- {diffctx-1.11.0 → diffctx-1.12.1}/diffctx/src/edges/structural/sibling.rs +0 -0
- {diffctx-1.11.0 → diffctx-1.12.1}/diffctx/src/edges/structural/testing.rs +0 -0
- {diffctx-1.11.0 → diffctx-1.12.1}/diffctx/src/filtering.rs +0 -0
- {diffctx-1.11.0 → diffctx-1.12.1}/diffctx/src/fragmentation.rs +0 -0
- {diffctx-1.11.0 → diffctx-1.12.1}/diffctx/src/graph_export.rs +0 -0
- {diffctx-1.11.0 → diffctx-1.12.1}/diffctx/src/interval.rs +0 -0
- {diffctx-1.11.0 → diffctx-1.12.1}/diffctx/src/languages.rs +0 -0
- {diffctx-1.11.0 → diffctx-1.12.1}/diffctx/src/memory_pipeline.rs +0 -0
- {diffctx-1.11.0 → diffctx-1.12.1}/diffctx/src/mode.rs +0 -0
- {diffctx-1.11.0 → diffctx-1.12.1}/diffctx/src/parsers/config_parser.rs +0 -0
- {diffctx-1.11.0 → diffctx-1.12.1}/diffctx/src/parsers/generic.rs +0 -0
- {diffctx-1.11.0 → diffctx-1.12.1}/diffctx/src/parsers/markdown.rs +0 -0
- {diffctx-1.11.0 → diffctx-1.12.1}/diffctx/src/parsers/mod.rs +0 -0
- {diffctx-1.11.0 → diffctx-1.12.1}/diffctx/src/ppr.rs +0 -0
- {diffctx-1.11.0 → diffctx-1.12.1}/diffctx/src/select.rs +0 -0
- {diffctx-1.11.0 → diffctx-1.12.1}/diffctx/src/stopwords.rs +0 -0
- {diffctx-1.11.0 → diffctx-1.12.1}/diffctx/src/test_harness.rs +0 -0
- {diffctx-1.11.0 → diffctx-1.12.1}/diffctx/src/tokenizer.rs +0 -0
- {diffctx-1.11.0 → diffctx-1.12.1}/diffctx/src/utility/boltzmann.rs +0 -0
- {diffctx-1.11.0 → diffctx-1.12.1}/diffctx/src/utility/importance.rs +0 -0
- {diffctx-1.11.0 → diffctx-1.12.1}/diffctx/src/utility/mod.rs +0 -0
- {diffctx-1.11.0 → diffctx-1.12.1}/diffctx/src/utility/needs.rs +0 -0
- {diffctx-1.11.0 → diffctx-1.12.1}/diffctx/src/utility/scoring.rs +0 -0
- {diffctx-1.11.0 → diffctx-1.12.1}/rust-toolchain.toml +0 -0
- {diffctx-1.11.0 → diffctx-1.12.1}/src/diffctx/__init__.py +0 -0
- {diffctx-1.11.0 → diffctx-1.12.1}/src/diffctx/__main__.py +0 -0
- {diffctx-1.11.0 → diffctx-1.12.1}/src/diffctx/clipboard.py +0 -0
- {diffctx-1.11.0 → diffctx-1.12.1}/src/diffctx/diffctx/__init__.py +0 -0
- {diffctx-1.11.0 → diffctx-1.12.1}/src/diffctx/mcp/README.md +0 -0
- {diffctx-1.11.0 → diffctx-1.12.1}/src/diffctx/mcp/__init__.py +0 -0
- {diffctx-1.11.0 → diffctx-1.12.1}/src/diffctx/mcp/__main__.py +0 -0
- {diffctx-1.11.0 → diffctx-1.12.1}/src/diffctx/mcp/formatting.py +0 -0
- {diffctx-1.11.0 → diffctx-1.12.1}/src/diffctx/mcp/security.py +0 -0
- {diffctx-1.11.0 → diffctx-1.12.1}/src/diffctx/mcp/server.py +0 -0
- {diffctx-1.11.0 → diffctx-1.12.1}/src/diffctx/py.typed +0 -0
|
@@ -7,6 +7,109 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- Standalone native binaries (linux x86_64/aarch64, macOS arm64, Windows
|
|
13
|
+
x64) are built and attached to every GitHub release. The README had
|
|
14
|
+
promised them since 1.9.x; releases only ever carried wheels and an sdist.
|
|
15
|
+
- The Rust engine is published to crates.io as the `diffctx` crate
|
|
16
|
+
(`cargo install diffctx` for the native CLI, `cargo add diffctx` to embed the
|
|
17
|
+
selection pipeline). Previously the name held only a reservation stub; the
|
|
18
|
+
crate now carries the released engine, starting at 1.12.0.
|
|
19
|
+
- Container image `ghcr.io/nikolay-e/diffctx` (linux amd64/arm64), built from
|
|
20
|
+
the release tag and smoke-tested against a real repository before the tag
|
|
21
|
+
moves: `docker run --rm -v "$PWD:/repo" ghcr.io/nikolay-e/diffctx . --diff HEAD~1`.
|
|
22
|
+
- Packaging manifests generated from the release checksums: Scoop
|
|
23
|
+
(`packaging/scoop/diffctx.json`), AUR (`packaging/aur/`) and an npm wrapper
|
|
24
|
+
(`packaging/npm/`) that downloads the platform binary and verifies its
|
|
25
|
+
SHA-256 against the published checksum.
|
|
26
|
+
|
|
27
|
+
### Fixed
|
|
28
|
+
|
|
29
|
+
- The native binary silently emitted YAML for every unrecognized `--format`,
|
|
30
|
+
including `md` — the documented default of the Python CLI. It now accepts
|
|
31
|
+
only `yaml`/`json` and exits 2 on anything else.
|
|
32
|
+
|
|
33
|
+
## [1.12.0] - 2026-07-23
|
|
34
|
+
|
|
35
|
+
### Added
|
|
36
|
+
|
|
37
|
+
- `--timeout SECONDS` — wall-clock deadline for `--diff` analysis (default
|
|
38
|
+
300); exceeding it exits `124` instead of hanging indefinitely (#70).
|
|
39
|
+
- `--no-ignores` — turns off every ignore rule (built-in patterns, project
|
|
40
|
+
`.gitignore`, `.diffctx/ignore`). `--no-default-ignores` only disables the
|
|
41
|
+
built-in list; its help now says so. Not supported with `--diff`.
|
|
42
|
+
- Output format is inferred from the `-o` extension when `-f` is omitted, so
|
|
43
|
+
`-o out.json` no longer writes Markdown into a `.json` file; a mismatch
|
|
44
|
+
between `-f` and the extension warns.
|
|
45
|
+
|
|
46
|
+
### Fixed
|
|
47
|
+
|
|
48
|
+
- **All error logging was dead.** An import-time `NullHandler` made
|
|
49
|
+
`setup_logging` skip attaching a real handler, so `--log-level` was a no-op
|
|
50
|
+
and all 19 `logger.error/warning/exception` sites were silent — `diffctx . -o
|
|
51
|
+
/bad/path.md` exited 1 with no message at all.
|
|
52
|
+
- **diffctx invoked from inside a git hook silently analyzed the wrong
|
|
53
|
+
repository.** Git exports repo-locating env vars (`GIT_DIR`,
|
|
54
|
+
`GIT_INDEX_FILE`, `GIT_WORK_TREE`, ...) to hook subprocesses; inherited,
|
|
55
|
+
they overrode `-C` on every internal git call. All git spawns now scrub
|
|
56
|
+
these variables (`git_command()` in `git.rs`).
|
|
57
|
+
- YAML output preserved file content byte-exactly except for trailing
|
|
58
|
+
newlines; the block chomping indicator is now chosen per content.
|
|
59
|
+
- Arrow-function fragments bound to variables were never stub-eligible (#106).
|
|
60
|
+
- Decorated definitions rendered as a bare `@decorator` line without the
|
|
61
|
+
`class X:` / `def x():` header.
|
|
62
|
+
- `--max-depth`-pruned directories were labelled `_(empty directory)_` — a
|
|
63
|
+
factual lie to the reader; they now read
|
|
64
|
+
`_(children omitted: --max-depth reached)_` (`truncated: true` in
|
|
65
|
+
YAML/JSON).
|
|
66
|
+
- Mixed directory + glob arguments dropped the glob files' parent path from
|
|
67
|
+
node names.
|
|
68
|
+
- Double Ctrl-C printed a ~60-line traceback.
|
|
69
|
+
- Lock files `uv.lock`, `pdm.lock`, `bun.lock`, `bun.lockb`, `deno.lock` and
|
|
70
|
+
`flake.lock` leaked into output; they now join the other lock files in the
|
|
71
|
+
default ignore patterns.
|
|
72
|
+
- Large-repo hangs/OOM on trivial diffs (#70, #95): discovery no longer
|
|
73
|
+
re-reads and re-tokenizes the whole candidate universe per ensemble
|
|
74
|
+
strategy (one shared pass + a persistent per-blob token cache keyed by
|
|
75
|
+
`(blob OID, tokenizer epoch)`), and edge construction is two-pass with a
|
|
76
|
+
bounded per-source top-K instead of materializing up to tens of millions
|
|
77
|
+
of raw edges before the cap; pass 2 replays a compact 16-byte-per-emission
|
|
78
|
+
log instead of re-running the builders, so generation cost stays 1x.
|
|
79
|
+
Verified: gitpod 8000s-hang -> 35.7s,
|
|
80
|
+
pytorch 1848s-SIGKILL -> 7.4s, mui/material-ui OOM class recovered.
|
|
81
|
+
Outputs are bit-identical (gated by `benchmarks/equivalence_gate.py`).
|
|
82
|
+
|
|
83
|
+
### Known limitations
|
|
84
|
+
|
|
85
|
+
- Near-dense edge emission on huge same-directory trees (observed: 199M
|
|
86
|
+
raw edges, 37GB peak on one mui/material-ui instance) remains expensive
|
|
87
|
+
even with bounded construction; tracked in #116.
|
|
88
|
+
|
|
89
|
+
### Changed
|
|
90
|
+
|
|
91
|
+
- **The token budget is now a hard cap.** The changed-files post-pass no
|
|
92
|
+
longer exceeds the budget to guarantee representation: a changed file
|
|
93
|
+
whose cheapest representative does not fit stays unrepresented (visible
|
|
94
|
+
as changed-file retention < 1). `--budget 0` therefore yields an empty
|
|
95
|
+
selection (use `--full` for changed files only); CLI help updated.
|
|
96
|
+
- Latency telemetry: new `graph_build_ms` phase (graph construction was
|
|
97
|
+
previously misattributed to `scoring_ms`, which now measures pure
|
|
98
|
+
ranking) and `peak_rss_bytes` (in-process peak memory). Release builds
|
|
99
|
+
carry line tables (`debug = "line-tables-only"`) for profiling at no
|
|
100
|
+
runtime cost.
|
|
101
|
+
- CLI diagnostics are honest end to end: an exit-code table in `--help`
|
|
102
|
+
(2 usage, 3 environment, 4 empty diff, 124 timeout), flag-value validation
|
|
103
|
+
exits 2 instead of 1, git failures report a single line plus a
|
|
104
|
+
`git log --oneline` hint on unknown revisions, conflicting flags warn
|
|
105
|
+
(`-q`+`--log-level`, `--full`+selection flags, ...), a failed clipboard
|
|
106
|
+
copy warns before falling back to stdout, and `--tau` / `--scoring` /
|
|
107
|
+
`--alpha` / `--budget 0` help text describes what actually happens.
|
|
108
|
+
- `graph --summary` reports category shares as percentages, suppresses
|
|
109
|
+
degenerate top-referenced lists, detects cycles over dominant-direction
|
|
110
|
+
edges only, derives churn from `git log --since`, and disambiguates
|
|
111
|
+
duplicate mermaid labels to relative paths.
|
|
112
|
+
|
|
10
113
|
## [1.11.0] - 2026-07-07
|
|
11
114
|
|
|
12
115
|
### Changed
|
diffctx-1.12.1/PKG-INFO
ADDED
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: diffctx
|
|
3
|
+
Version: 1.12.1
|
|
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
|
+
Home-Page: https://github.com/nikolay-e/diffctx
|
|
68
|
+
Author-email: Nikolay Eremeev <nikolay.eremeev@outlook.com>
|
|
69
|
+
License-Expression: Apache-2.0
|
|
70
|
+
Requires-Python: >=3.10
|
|
71
|
+
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
|
|
72
|
+
Project-URL: Changelog, https://github.com/nikolay-e/diffctx/releases
|
|
73
|
+
Project-URL: Homepage, https://github.com/nikolay-e/diffctx
|
|
74
|
+
Project-URL: Issues, https://github.com/nikolay-e/diffctx/issues
|
|
75
|
+
Project-URL: Repository, https://github.com/nikolay-e/diffctx
|
|
76
|
+
|
|
77
|
+
# diffctx — smart diff context for LLM code review
|
|
78
|
+
|
|
79
|
+
[](https://github.com/nikolay-e/diffctx/actions/workflows/ci.yml)
|
|
80
|
+
[](https://pypi.org/project/diffctx/)
|
|
81
|
+
[](https://crates.io/crates/diffctx)
|
|
82
|
+
[](https://pypi.org/project/diffctx/)
|
|
83
|
+
|
|
84
|
+
**diffctx selects the minimum code an LLM needs to review a git diff.**
|
|
85
|
+
Instead of pasting whole files, it walks the dependency graph from the changed
|
|
86
|
+
lines outward and stops as soon as additional context stops paying for itself.
|
|
87
|
+
|
|
88
|
+
> Coming from [`treemapper`](https://pypi.org/project/treemapper/)? That name is
|
|
89
|
+
> deprecated — it was a thin wrapper around diffctx. Every command, flag, and
|
|
90
|
+
> API call works unchanged: `treemapper` → `diffctx`, `treemapper-mcp` → `diffctx-mcp`.
|
|
91
|
+
|
|
92
|
+
## Why not just use `tree` or repomix?
|
|
93
|
+
|
|
94
|
+
| | `tree` | repomix | Claude Code Review | **diffctx** |
|
|
95
|
+
|---|:---:|:---:|:---:|:---:|
|
|
96
|
+
| **Primary use case** | directory listing | full repo export | automated PR review | **diff context for code review** |
|
|
97
|
+
| Smart diff context | ✗ | ✗ | ✓ | ✓ |
|
|
98
|
+
| Works with any LLM | ✓ | ✓ | Claude only | ✓ |
|
|
99
|
+
| Free / local / offline | ✓ | ✓ | $15–25/review | ✓ |
|
|
100
|
+
| GitHub required | ✗ | ✗ | ✓ | ✗ |
|
|
101
|
+
| Multiple output formats | ✗ | limited | — | YAML/JSON/MD/txt |
|
|
102
|
+
| Python API | ✗ | ✗ | ✗ | ✓ |
|
|
103
|
+
| MCP server | ✗ | ✗ | ✗ | ✓ |
|
|
104
|
+
|
|
105
|
+
## Install
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
uvx diffctx . --diff HEAD~1 # zero-install, run once via uv
|
|
109
|
+
pipx install diffctx # recommended: isolated CLI, no venv needed
|
|
110
|
+
pip install diffctx # or: into an active environment
|
|
111
|
+
pipx install 'diffctx[mcp]' # + MCP server for AI assistants
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
The `[tree-sitter]` extra adds AST-level parsing for more accurate context
|
|
115
|
+
selection across 30+ languages.
|
|
116
|
+
|
|
117
|
+
Without Python:
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
cargo install diffctx # native CLI from crates.io
|
|
121
|
+
docker run --rm -v "$PWD:/repo" ghcr.io/nikolay-e/diffctx . --diff HEAD~1
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Prebuilt binaries for linux (x86_64/aarch64), macOS (arm64) and Windows (x64)
|
|
125
|
+
are attached to every [release](https://github.com/nikolay-e/diffctx/releases/latest).
|
|
126
|
+
The native binary covers diff mode with YAML/JSON output; tree mode, Markdown
|
|
127
|
+
output, the `graph` subcommand and the MCP server live in the Python package.
|
|
128
|
+
`cargo add diffctx` embeds the selection pipeline in a Rust project
|
|
129
|
+
([docs.rs](https://docs.rs/diffctx)).
|
|
130
|
+
|
|
131
|
+
## Quick start
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
diffctx . --diff HEAD~1 # smart context for last commit → paste into Claude/ChatGPT
|
|
135
|
+
diffctx . -f md -c # full codebase export → clipboard in Markdown
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+

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

|
|
63
|
+
|
|
64
|
+
*`diffctx . --diff HEAD~1` selects only the fragments — functions, imports,
|
|
65
|
+
type definitions — that an LLM actually needs to review the last commit,
|
|
66
|
+
instead of dumping every changed file in full.*
|
|
67
|
+
|
|
68
|
+
## Diff context mode
|
|
69
|
+
|
|
70
|
+
Finds the minimal set of code fragments needed to understand a change —
|
|
71
|
+
imports, callers, type definitions, config dependencies — across 50+ file
|
|
72
|
+
types. It builds a code graph (imports, co-changes, type refs), propagates
|
|
73
|
+
relevance from the changed lines outward, and stops when relevance drops below
|
|
74
|
+
`--tau` or the `--budget` token cap is reached.
|
|
75
|
+
|
|
76
|
+
| Flag | Default | Description |
|
|
77
|
+
|-------------|---------|--------------------------------------------------------------------------|
|
|
78
|
+
| `--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) |
|
|
79
|
+
| `--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) |
|
|
80
|
+
| `--alpha` | 0.60 | PPR damping; higher = context clusters tighter around changes (`--scoring ppr` only) |
|
|
81
|
+
| `--tau` | 0.12 | Relevance threshold for full fragment content; lower-scoring fragments are stubbed or dropped (lower = more context) |
|
|
82
|
+
| `--full` | false | Only the changed files, every fragment, no related-code context |
|
|
83
|
+
| `--timeout` | 300 | Wall-clock deadline in seconds; on expiry diffctx exits 124 instead of hanging |
|
|
84
|
+
|
|
85
|
+
Calibration of `--alpha`, `--tau`, and the edge-weight priors:
|
|
86
|
+
[`docs/parameter-strategy.md`](docs/parameter-strategy.md). Theory:
|
|
87
|
+
[Context-Selection for Git Diff (Zenodo, 2026)](https://doi.org/10.5281/zenodo.18824580).
|
|
88
|
+
|
|
89
|
+
### `graph` subcommand
|
|
90
|
+
|
|
91
|
+
Explore the underlying dependency graph directly, without a diff:
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
diffctx graph . # Mermaid graph of directory deps (default)
|
|
95
|
+
diffctx graph . --summary # cycles, hotspots, coupling metrics
|
|
96
|
+
diffctx graph . --level fragment -f json # fragment-level graph as JSON
|
|
97
|
+
diffctx graph . --level file -f graphml -o g.xml # file-level graph as GraphML
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Usage
|
|
101
|
+
|
|
102
|
+
<!-- BEGIN USAGE -->
|
|
103
|
+
```bash
|
|
104
|
+
# full codebase export:
|
|
105
|
+
diffctx . # Markdown to stdout + token count
|
|
106
|
+
diffctx . -f md -c # Markdown → clipboard
|
|
107
|
+
diffctx . -f json -o tree.json # JSON → file
|
|
108
|
+
diffctx . --no-content # structure only, no file contents
|
|
109
|
+
diffctx . --max-depth 3 # limit depth
|
|
110
|
+
diffctx . -i custom.ignore # custom ignore patterns
|
|
111
|
+
|
|
112
|
+
# diff context mode (requires git repo):
|
|
113
|
+
diffctx . --diff # uncommitted changes (working tree vs HEAD)
|
|
114
|
+
diffctx . --diff HEAD~1 # context for last commit
|
|
115
|
+
diffctx . --diff main..feature # context for feature branch
|
|
116
|
+
diffctx . --diff HEAD~1 --budget 30000 # limit to ~30k tokens
|
|
117
|
+
diffctx . --diff HEAD~1 -c # diff context to clipboard
|
|
118
|
+
```
|
|
119
|
+
<!-- END USAGE -->
|
|
120
|
+
|
|
121
|
+
Every run reports token count and size on stderr — `12,847 tokens
|
|
122
|
+
(o200k_base), 52.3 KB` (tiktoken, the GPT-4o tokenizer; `~`-prefixed
|
|
123
|
+
approximation above 1 MB). `-c/--copy` sends output to the clipboard via
|
|
124
|
+
`pbcopy` (macOS), `clip` (Windows), or `wl-copy`/`xclip`/`xsel` (Linux).
|
|
125
|
+
Unreadable files are replaced by placeholders such as `<binary file: N bytes>`,
|
|
126
|
+
`<file too large: N bytes>`, or `<unreadable content: not utf-8>`.
|
|
127
|
+
|
|
128
|
+
## Python API
|
|
129
|
+
|
|
130
|
+
```python
|
|
131
|
+
from pathlib import Path
|
|
132
|
+
from diffctx import build_diff_context, map_directory, to_json, to_markdown, to_text, to_yaml
|
|
133
|
+
|
|
134
|
+
ctx = build_diff_context(
|
|
135
|
+
Path("."),
|
|
136
|
+
"HEAD~1..HEAD",
|
|
137
|
+
budget_tokens=None, # None = auto; 0 = strict-zero floor (empty); -1 = uncapped; N = hard cap
|
|
138
|
+
alpha=0.6,
|
|
139
|
+
tau=0.12,
|
|
140
|
+
full=False,
|
|
141
|
+
scoring_mode="ego",
|
|
142
|
+
timeout=300,
|
|
143
|
+
)
|
|
144
|
+
print(to_markdown(ctx))
|
|
145
|
+
|
|
146
|
+
tree = map_directory(
|
|
147
|
+
".",
|
|
148
|
+
max_depth=None,
|
|
149
|
+
no_content=False,
|
|
150
|
+
max_file_bytes=None,
|
|
151
|
+
ignore_file=None,
|
|
152
|
+
no_default_ignores=False,
|
|
153
|
+
whitelist_file=None,
|
|
154
|
+
)
|
|
155
|
+
print(to_yaml(tree))
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## MCP server
|
|
159
|
+
|
|
160
|
+
diffctx includes an [MCP](https://modelcontextprotocol.io) server that lets AI
|
|
161
|
+
assistants (Claude Code, Cursor, Windsurf, etc.) call diff context analysis
|
|
162
|
+
automatically during code review. Install with `pip install 'diffctx[mcp]'`
|
|
163
|
+
and add to your MCP client config (e.g. `~/.claude/mcp.json` for Claude Code):
|
|
164
|
+
|
|
165
|
+
```json
|
|
166
|
+
{
|
|
167
|
+
"mcpServers": {
|
|
168
|
+
"diffctx": {
|
|
169
|
+
"command": "diffctx-mcp"
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
The server exposes a `get_diff_context` tool that assistants call when
|
|
176
|
+
reviewing PRs, explaining changes, or investigating broken tests. Configs for
|
|
177
|
+
Cursor, Continue, Windsurf, and Zed:
|
|
178
|
+
[`src/diffctx/mcp/README.md`](src/diffctx/mcp/README.md).
|
|
179
|
+
|
|
180
|
+
## Ignore patterns
|
|
181
|
+
|
|
182
|
+
Respects `.gitignore` and `.diffctx/ignore` automatically — hierarchically at
|
|
183
|
+
every directory level, with gitignore semantics (negation `!important.log`,
|
|
184
|
+
anchored `/root_only.txt`). `.diffctx/whitelist` acts as an include-only
|
|
185
|
+
filter, and the output file is always auto-ignored. `--no-default-ignores`
|
|
186
|
+
disables the built-in patterns; `--no-ignores` disables all ignore rules
|
|
187
|
+
(tree mode only).
|
|
188
|
+
|
|
189
|
+
## Exit codes
|
|
190
|
+
|
|
191
|
+
| Code | Meaning |
|
|
192
|
+
|------|---------|
|
|
193
|
+
| `0` | Success — output contains content |
|
|
194
|
+
| `1` | Runtime error (bad path, permission denied, etc.) |
|
|
195
|
+
| `2` | Usage error (invalid flags/arguments) |
|
|
196
|
+
| `3` | Environment error (`--diff` outside a git repo, git not installed, no commits yet) |
|
|
197
|
+
| `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` |
|
|
198
|
+
| `130`| Interrupted (Ctrl-C) |
|
|
199
|
+
| `141`| Broken pipe (e.g. piping into `head`) |
|
|
200
|
+
|
|
201
|
+
## License
|
|
202
|
+
|
|
203
|
+
Apache 2.0
|
|
204
|
+
|
|
205
|
+
---
|
|
206
|
+
|
|
207
|
+
- [Changelog](CHANGELOG.md)
|
|
208
|
+
- [Security policy](SECURITY.md) — threat model and vulnerability reporting
|
|
209
|
+
- [Parameter strategy](docs/parameter-strategy.md) — how `--alpha`,
|
|
210
|
+
`--tau`, and edge weights are calibrated
|