diffctx 1.12.2__tar.gz → 1.12.3__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.12.2 → diffctx-1.12.3}/CHANGELOG.md +80 -6
- {diffctx-1.12.2 → diffctx-1.12.3}/Cargo.lock +11 -11
- diffctx-1.12.3/Cargo.toml +20 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/PKG-INFO +43 -13
- {diffctx-1.12.2/crates/diffctx-native → diffctx-1.12.3}/README.md +42 -12
- {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/Cargo.toml +4 -4
- {diffctx-1.12.2 → diffctx-1.12.3/crates/diffctx-native}/README.md +42 -12
- {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/analytics.rs +53 -14
- {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/candidate_files.rs +26 -2
- {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/config/analytics.rs +0 -2
- diffctx-1.12.3/crates/diffctx-native/src/config/env_overrides.rs +273 -0
- diffctx-1.12.3/crates/diffctx-native/src/discovery.rs +579 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/edges/mod.rs +5 -2
- {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/filtering.rs +93 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/git.rs +567 -9
- {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/graph.rs +340 -30
- {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/graph_export.rs +57 -0
- diffctx-1.12.3/crates/diffctx-native/src/interval.rs +210 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/languages.rs +69 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/parsers/generic.rs +55 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/parsers/tree_sitter_strategy.rs +517 -1
- {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/pipeline.rs +138 -19
- {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/postpass.rs +98 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/ppr.rs +231 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/pybridge.rs +26 -190
- {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/render.rs +115 -7
- {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/select.rs +256 -3
- diffctx-1.12.3/crates/diffctx-native/src/signatures.rs +312 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/token_corpus.rs +111 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/pyproject.toml +4 -2
- {diffctx-1.12.2 → diffctx-1.12.3}/src/diffctx/_app.py +45 -11
- {diffctx-1.12.2 → diffctx-1.12.3}/src/diffctx/_native/pipeline.py +47 -4
- {diffctx-1.12.2 → diffctx-1.12.3}/src/diffctx/cli.py +65 -2
- {diffctx-1.12.2 → diffctx-1.12.3}/src/diffctx/mcp/README.md +11 -2
- {diffctx-1.12.2 → diffctx-1.12.3}/src/diffctx/mcp/security.py +23 -5
- {diffctx-1.12.2 → diffctx-1.12.3}/src/diffctx/mcp/server.py +137 -30
- diffctx-1.12.3/src/diffctx/version.py +1 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/src/diffctx/writer.py +29 -6
- diffctx-1.12.2/Cargo.toml +0 -12
- diffctx-1.12.2/crates/diffctx-native/src/config/env_overrides.rs +0 -125
- diffctx-1.12.2/crates/diffctx-native/src/discovery.rs +0 -293
- diffctx-1.12.2/crates/diffctx-native/src/interval.rs +0 -78
- diffctx-1.12.2/crates/diffctx-native/src/signatures.rs +0 -144
- diffctx-1.12.2/src/diffctx/version.py +0 -1
- {diffctx-1.12.2 → diffctx-1.12.3}/LICENSE +0 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/config/bm25.rs +0 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/config/budget.rs +0 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/config/category_weights.rs +0 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/config/edge_weights.rs +0 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/config/extensions.rs +0 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/config/filtering.rs +0 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/config/fragmentation.rs +0 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/config/git.rs +0 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/config/graph_filtering.rs +0 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/config/importance.rs +0 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/config/limits.rs +0 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/config/mod.rs +0 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/config/mode.rs +0 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/config/needs.rs +0 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/config/parsers.rs +0 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/config/render.rs +0 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/config/scoring.rs +0 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/config/selection.rs +0 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/config/tokenization.rs +0 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/config/weights.rs +0 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/core.rs +0 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/edges/base.rs +0 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/edges/config_edges/build_system.rs +0 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/edges/config_edges/cicd.rs +0 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/edges/config_edges/docker.rs +0 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/edges/config_edges/generic.rs +0 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/edges/config_edges/helm.rs +0 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/edges/config_edges/kubernetes.rs +0 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/edges/config_edges/mod.rs +0 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/edges/document/mod.rs +0 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/edges/history/cochange.rs +0 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/edges/history/mod.rs +0 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/edges/semantic/ansible.rs +0 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/edges/semantic/bazel.rs +0 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/edges/semantic/c_family.rs +0 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/edges/semantic/cargo_edges.rs +0 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/edges/semantic/clojure.rs +0 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/edges/semantic/css.rs +0 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/edges/semantic/dart.rs +0 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/edges/semantic/dbt.rs +0 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/edges/semantic/dotnet.rs +0 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/edges/semantic/elixir.rs +0 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/edges/semantic/erlang.rs +0 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/edges/semantic/go.rs +0 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/edges/semantic/graphql.rs +0 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/edges/semantic/haskell.rs +0 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/edges/semantic/javascript.rs +0 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/edges/semantic/julia.rs +0 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/edges/semantic/jvm.rs +0 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/edges/semantic/latex.rs +0 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/edges/semantic/lua.rs +0 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/edges/semantic/mod.rs +0 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/edges/semantic/nim.rs +0 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/edges/semantic/nix.rs +0 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/edges/semantic/ocaml.rs +0 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/edges/semantic/openapi.rs +0 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/edges/semantic/perl.rs +0 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/edges/semantic/php.rs +0 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/edges/semantic/prisma.rs +0 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/edges/semantic/protobuf.rs +0 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/edges/semantic/python.rs +0 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/edges/semantic/r_lang.rs +0 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/edges/semantic/ruby.rs +0 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/edges/semantic/rust_lang.rs +0 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/edges/semantic/shell.rs +0 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/edges/semantic/sql.rs +0 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/edges/semantic/swift.rs +0 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/edges/semantic/tags.rs +0 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/edges/semantic/terraform.rs +0 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/edges/semantic/zig.rs +0 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/edges/similarity/lexical.rs +0 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/edges/similarity/mod.rs +0 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/edges/structural/containment.rs +0 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/edges/structural/mod.rs +0 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/edges/structural/sibling.rs +0 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/edges/structural/testing.rs +0 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/excerpt.rs +0 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/fragmentation.rs +0 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/lib.rs +0 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/main.rs +0 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/memory_pipeline.rs +0 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/mode.rs +0 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/parsers/config_parser.rs +0 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/parsers/markdown.rs +0 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/parsers/mod.rs +0 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/peak_rss.rs +0 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/project_graph.rs +0 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/scoring.rs +0 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/stopwords.rs +0 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/test_harness.rs +0 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/tokenizer.rs +0 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/types.rs +0 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/utility/boltzmann.rs +0 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/utility/importance.rs +0 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/utility/mod.rs +0 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/utility/needs.rs +0 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/crates/diffctx-native/src/utility/scoring.rs +0 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/rust-toolchain.toml +0 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/src/diffctx/__init__.py +0 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/src/diffctx/__main__.py +0 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/src/diffctx/_native/__init__.py +0 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/src/diffctx/_native/graph_analytics.py +0 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/src/diffctx/_native/graph_export.py +0 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/src/diffctx/_native/project_graph.py +0 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/src/diffctx/clipboard.py +0 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/src/diffctx/ignore.py +0 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/src/diffctx/logger.py +0 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/src/diffctx/mcp/__init__.py +0 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/src/diffctx/mcp/__main__.py +0 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/src/diffctx/mcp/formatting.py +0 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/src/diffctx/py.typed +0 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/src/diffctx/tokens.py +0 -0
- {diffctx-1.12.2 → diffctx-1.12.3}/src/diffctx/tree.py +0 -0
|
@@ -7,8 +7,74 @@ 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
|
+
- **Scoop install on Windows.** This repository is now itself the bucket:
|
|
13
|
+
`scoop bucket add diffctx https://github.com/nikolay-e/diffctx` then
|
|
14
|
+
`scoop install diffctx/diffctx`. The manifest moved from `packaging/scoop/`
|
|
15
|
+
to `bucket/diffctx.json`, where `scoop bucket add` reads it, so CD pushing
|
|
16
|
+
the regenerated manifest to `main` is the publication step. Previously the
|
|
17
|
+
manifest was correct but unreachable — no bucket existed, so
|
|
18
|
+
`scoop search diffctx` found nothing.
|
|
19
|
+
|
|
20
|
+
- `--with-raw-diff` bundles git's raw unified diff ahead of the selected
|
|
21
|
+
context in every Python-CLI format (md/yaml/json/txt) and in the Python API
|
|
22
|
+
(`build_diff_context(..., with_raw_diff=True)`). Additive only — selection is
|
|
23
|
+
byte-identical with and without it — and not charged to `--budget`; the
|
|
24
|
+
stderr token summary reports the real output size and breaks out the patch's
|
|
25
|
+
share. Lock-file (#112), ignored and secret-like sections stay omitted. Not
|
|
26
|
+
in the native binary or the MCP server yet (#150).
|
|
27
|
+
- Token counting is documented explicitly: every count and `--budget` use
|
|
28
|
+
tiktoken `o200k_base` (GPT-4o family) and are approximate for other model
|
|
29
|
+
families — `docs/product/token-budget.md`, plus a `Token counting` section in
|
|
30
|
+
`--help` (#150).
|
|
31
|
+
- A GitHub Action (`action.yml`) runs diffctx as a CI step and exposes the
|
|
32
|
+
context file, its exact token count and an `empty` flag as step outputs, so a
|
|
33
|
+
downstream job can feed an LLM without re-tokenizing
|
|
34
|
+
(`docs/product/github-action.md`, #145).
|
|
35
|
+
- The Claude Code plugin declares its MCP server self-bootstrapping:
|
|
36
|
+
`uvx --from 'diffctx[mcp]' diffctx-mcp`. Installing the plugin no longer
|
|
37
|
+
assumes `pip install 'diffctx[mcp]'` has already happened — `uv` fetches it
|
|
38
|
+
on first use. (The entry point rather than the `diffctx mcp` subcommand: the
|
|
39
|
+
subcommand only exists from this release on, so on any older version the
|
|
40
|
+
bare command would try to map a directory named `mcp`.)
|
|
41
|
+
- `diffctx mcp` starts the MCP server, alongside the existing `diffctx-mcp`
|
|
42
|
+
console script. MCP registries publish a *package* name, and clients that
|
|
43
|
+
derive the executable from it run `diffctx` — which started a tree-mapping
|
|
44
|
+
run and wrote 31 MB of the working directory into the protocol transport
|
|
45
|
+
instead of speaking MCP. Both spellings now reach the same server.
|
|
46
|
+
|
|
47
|
+
### Security
|
|
48
|
+
|
|
49
|
+
- **A diff range could re-enable repository-configured diff commands.** The
|
|
50
|
+
range validator allowed a leading `-`, so `--ext-diff` or `--textconv` passed
|
|
51
|
+
as a "range" landed in argv *after* the `--no-ext-diff --no-textconv` that
|
|
52
|
+
every diff invocation sets — undoing them and letting `.gitattributes` run
|
|
53
|
+
external commands. Neither side of a range may now begin with a dash, and
|
|
54
|
+
single revisions are additionally rejected for whitespace or control
|
|
55
|
+
characters (a newline split one `cat-file --batch` request into two). Refs
|
|
56
|
+
starting with a dash are unaddressable on a git command line, so nothing
|
|
57
|
+
legitimate is lost.
|
|
58
|
+
- MCP tool calls had no wall-clock deadline — the CLI's watchdog covered only
|
|
59
|
+
the CLI, leaving parse, fragmentation and scoring unbounded on the MCP path.
|
|
60
|
+
All three tools now share the CLI's 300 s default.
|
|
61
|
+
- `SECURITY.md` documents prompt injection via repository content honestly: it
|
|
62
|
+
is inherent to moving repository text into a model's context, diffctx does
|
|
63
|
+
not detect or neutralize it, and output must be treated as untrusted.
|
|
64
|
+
|
|
10
65
|
### Fixed
|
|
11
66
|
|
|
67
|
+
- **A changed file could vanish from the diff entirely when `.gitignore`
|
|
68
|
+
excluded its parent directory.** `check-ignore --no-index` — required for
|
|
69
|
+
`.diffctx/ignore` to apply to tracked files — also revives git's rule that a
|
|
70
|
+
file cannot be re-included once a parent directory is excluded. pandoc
|
|
71
|
+
excludes every dotted root entry with `/*.*` and re-includes `!.github/**`;
|
|
72
|
+
git keeps the tracked workflow file, diffctx dropped it, and a one-file
|
|
73
|
+
commit rendered as an empty selection with exit 4. An exclusion inherited
|
|
74
|
+
from an excluded ancestor no longer counts; a pattern matching the path
|
|
75
|
+
itself still excludes it, so `.diffctx/ignore` and per-directory
|
|
76
|
+
`.gitignore` rules are unaffected (#153).
|
|
77
|
+
|
|
12
78
|
- **The container images ran as root.** The published 1.12.1 image had no
|
|
13
79
|
`USER` directive, so every `docker run` executed as uid 0 while the README
|
|
14
80
|
promised an unprivileged user. The image now runs as uid 10001.
|
|
@@ -20,8 +86,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
20
86
|
- **The native binary hard-capped every run at 4096 tokens.** `--budget`
|
|
21
87
|
carried a fixed clap default instead of leaving the budget unset, so the
|
|
22
88
|
auto-sizing the Python CLI has always used never ran on the binary shipped
|
|
23
|
-
via crates.io, npm, the container images
|
|
24
|
-
|
|
89
|
+
via crates.io, npm, the container images and the release archives — the
|
|
90
|
+
same command returned a truncated selection there (14-file
|
|
25
91
|
self-eat range: 15 fragments across 10 files, against 172 across 44). The
|
|
26
92
|
binary now defaults to auto sizing, and `--budget -1` means unlimited as it
|
|
27
93
|
does in Python.
|
|
@@ -58,6 +124,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
58
124
|
actionable facts about such a run — were dropped. They are now always
|
|
59
125
|
written.
|
|
60
126
|
|
|
127
|
+
### Removed
|
|
128
|
+
|
|
129
|
+
- **AUR support.** `diffctx-bin` had never been submitted (the AUR RPC reported
|
|
130
|
+
`resultcount: 0`), the publishing job gated on a credential this repository
|
|
131
|
+
does not hold, and every release regenerated a PKGBUILD nobody could install.
|
|
132
|
+
Arch users are served by `pipx install diffctx` and `cargo install diffctx`.
|
|
133
|
+
|
|
61
134
|
### Changed
|
|
62
135
|
|
|
63
136
|
- **Lock files no longer render their hunks in diff mode.** A dependency bump
|
|
@@ -98,10 +171,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
98
171
|
the release tag and smoke-tested against a real repository before the tag
|
|
99
172
|
moves: `docker run --rm -v "$PWD:/repo" ghcr.io/nikolay-e/diffctx . --diff HEAD~1`.
|
|
100
173
|
Mirrored to Docker Hub as `nikolajer/diffctx`.
|
|
101
|
-
-
|
|
102
|
-
|
|
103
|
-
(`packaging/
|
|
104
|
-
|
|
174
|
+
- npm wrapper (`packaging/npm/`) that downloads the platform binary and
|
|
175
|
+
verifies its SHA-256 against the published checksum. Scoop
|
|
176
|
+
(`packaging/scoop/diffctx.json`) and AUR (`packaging/aur/`) manifests are
|
|
177
|
+
generated from the same checksums but are not published to any bucket or
|
|
178
|
+
to the AUR.
|
|
105
179
|
|
|
106
180
|
### Fixed
|
|
107
181
|
|
|
@@ -179,9 +179,9 @@ dependencies = [
|
|
|
179
179
|
|
|
180
180
|
[[package]]
|
|
181
181
|
name = "crossbeam-epoch"
|
|
182
|
-
version = "0.9.
|
|
182
|
+
version = "0.9.20"
|
|
183
183
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
184
|
-
checksum = "
|
|
184
|
+
checksum = "2d6914041f254d6e9176c01941b21115dcfb7089e55135a35411081bd106ef3f"
|
|
185
185
|
dependencies = [
|
|
186
186
|
"crossbeam-utils",
|
|
187
187
|
]
|
|
@@ -194,7 +194,7 @@ checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
|
|
|
194
194
|
|
|
195
195
|
[[package]]
|
|
196
196
|
name = "diffctx"
|
|
197
|
-
version = "1.12.
|
|
197
|
+
version = "1.12.3"
|
|
198
198
|
dependencies = [
|
|
199
199
|
"anyhow",
|
|
200
200
|
"clap",
|
|
@@ -399,9 +399,9 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
|
|
|
399
399
|
|
|
400
400
|
[[package]]
|
|
401
401
|
name = "libc"
|
|
402
|
-
version = "0.2.
|
|
402
|
+
version = "0.2.189"
|
|
403
403
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
404
|
-
checksum = "
|
|
404
|
+
checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"
|
|
405
405
|
|
|
406
406
|
[[package]]
|
|
407
407
|
name = "libmimalloc-sys"
|
|
@@ -1105,9 +1105,9 @@ dependencies = [
|
|
|
1105
1105
|
|
|
1106
1106
|
[[package]]
|
|
1107
1107
|
name = "tree-sitter-javascript"
|
|
1108
|
-
version = "0.
|
|
1108
|
+
version = "0.25.0"
|
|
1109
1109
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1110
|
-
checksum = "
|
|
1110
|
+
checksum = "68204f2abc0627a90bdf06e605f5c470aa26fdcb2081ea553a04bdad756693f5"
|
|
1111
1111
|
dependencies = [
|
|
1112
1112
|
"cc",
|
|
1113
1113
|
"tree-sitter-language",
|
|
@@ -1211,9 +1211,9 @@ dependencies = [
|
|
|
1211
1211
|
|
|
1212
1212
|
[[package]]
|
|
1213
1213
|
name = "tree-sitter-python"
|
|
1214
|
-
version = "0.
|
|
1214
|
+
version = "0.25.0"
|
|
1215
1215
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1216
|
-
checksum = "
|
|
1216
|
+
checksum = "6bf85fd39652e740bf60f46f4cda9492c3a9ad75880575bf14960f775cb74a1c"
|
|
1217
1217
|
dependencies = [
|
|
1218
1218
|
"cc",
|
|
1219
1219
|
"tree-sitter-language",
|
|
@@ -1271,9 +1271,9 @@ dependencies = [
|
|
|
1271
1271
|
|
|
1272
1272
|
[[package]]
|
|
1273
1273
|
name = "tree-sitter-swift"
|
|
1274
|
-
version = "0.
|
|
1274
|
+
version = "0.7.3"
|
|
1275
1275
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1276
|
-
checksum = "
|
|
1276
|
+
checksum = "fe36052155b9dd69ca82b3b8f1b4ccfb2d867125ac1a4db1dd7331829242668c"
|
|
1277
1277
|
dependencies = [
|
|
1278
1278
|
"cc",
|
|
1279
1279
|
"tree-sitter-language",
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
[workspace]
|
|
2
|
+
members = ["crates/diffctx-native"]
|
|
3
|
+
resolver = "2"
|
|
4
|
+
|
|
5
|
+
[profile.release]
|
|
6
|
+
opt-level = 3
|
|
7
|
+
lto = "fat"
|
|
8
|
+
codegen-units = 1
|
|
9
|
+
strip = true
|
|
10
|
+
panic = "abort"
|
|
11
|
+
debug = "line-tables-only"
|
|
12
|
+
split-debuginfo = "packed"
|
|
13
|
+
|
|
14
|
+
# `cargo test --release` with panic = "abort" compiles the whole dependency
|
|
15
|
+
# graph twice (abort for the cdylib lib artifact, unwind for the test
|
|
16
|
+
# universe); the hashless cdylib output names then collide (cargo#6313).
|
|
17
|
+
# Tests run through this profile instead: same optimizations, one universe.
|
|
18
|
+
[profile.release-unwind]
|
|
19
|
+
inherits = "release"
|
|
20
|
+
panic = "unwind"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: diffctx
|
|
3
|
-
Version: 1.12.
|
|
3
|
+
Version: 1.12.3
|
|
4
4
|
Classifier: Development Status :: 5 - Production/Stable
|
|
5
5
|
Classifier: Environment :: Console
|
|
6
6
|
Classifier: Intended Audience :: Developers
|
|
@@ -105,6 +105,9 @@ changed lines and stops once more context stops paying for itself.
|
|
|
105
105
|
| Python API | ✗ | ✗ | ✗ | ✓ |
|
|
106
106
|
| MCP server | ✗ | ✗ | ✗ | ✓ |
|
|
107
107
|
|
|
108
|
+
Fuller positioning — measured results, and when a whole-repo packer or a
|
|
109
|
+
persistent code-graph server fits better: [COMPARISON.md](COMPARISON.md).
|
|
110
|
+
|
|
108
111
|
## Install
|
|
109
112
|
|
|
110
113
|
```bash
|
|
@@ -122,6 +125,13 @@ npx diffctx . --diff HEAD~1 # npm wrapper over the native binary
|
|
|
122
125
|
docker run --rm -v "$PWD:/repo" ghcr.io/nikolay-e/diffctx . --diff HEAD~1
|
|
123
126
|
```
|
|
124
127
|
|
|
128
|
+
On Windows, via Scoop (this repository is the bucket):
|
|
129
|
+
|
|
130
|
+
```powershell
|
|
131
|
+
scoop bucket add diffctx https://github.com/nikolay-e/diffctx
|
|
132
|
+
scoop install diffctx/diffctx
|
|
133
|
+
```
|
|
134
|
+
|
|
125
135
|
The image runs as a non-root user (uid 10001) and writes to stdout — the
|
|
126
136
|
native binary has no `-o` flag, so redirect to capture: `... --diff HEAD~1 >
|
|
127
137
|
context.yaml`.
|
|
@@ -158,11 +168,12 @@ the `--budget` token cap is hit.
|
|
|
158
168
|
| Flag | Default | Description |
|
|
159
169
|
|-------------|---------|--------------------------------------------------------------------------|
|
|
160
170
|
| `--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) |
|
|
161
|
-
| `--budget` | auto | Hard token
|
|
171
|
+
| `--budget` | auto | Hard cap in o200k_base tokens (see [Token counting](docs/product/token-budget.md)): `N` enforces a fixed cap, `-1` disables it, `0` is a strict-zero floor (empty selection; use `--full` for changed files only) |
|
|
162
172
|
| `--alpha` | 0.60 | PPR damping; higher = context clusters tighter around changes (`--scoring ppr` only) |
|
|
163
173
|
| `--tau` | 0.12 | Relevance threshold for full fragment content; lower-scoring fragments are stubbed or dropped (lower = more context) |
|
|
164
174
|
| `--full` | false | Only the changed files, every fragment, no related-code context |
|
|
165
175
|
| `--timeout` | 300 | Wall-clock deadline in seconds; on expiry diffctx exits 124 instead of hanging |
|
|
176
|
+
| `--with-raw-diff` | false | Also embed git's raw unified diff ahead of the selected fragments — additive (selection unchanged), not charged to `--budget`, lock/ignored/secret-like sections omitted. Python CLI only |
|
|
166
177
|
|
|
167
178
|
Calibration of `--alpha`, `--tau`, and the edge-weight priors:
|
|
168
179
|
[`docs/engineering/parameter-strategy.md`](docs/engineering/parameter-strategy.md).
|
|
@@ -186,19 +197,20 @@ diffctx graph . --level file -f graphml -o g.xml # file-level graph as GraphML
|
|
|
186
197
|
<!-- BEGIN USAGE -->
|
|
187
198
|
```bash
|
|
188
199
|
# full codebase export:
|
|
189
|
-
diffctx .
|
|
190
|
-
diffctx . -f md -c
|
|
191
|
-
diffctx . -f json -o tree.json
|
|
192
|
-
diffctx . --no-content
|
|
193
|
-
diffctx . --max-depth 3
|
|
194
|
-
diffctx . -i custom.ignore
|
|
200
|
+
diffctx . # Markdown to stdout + token count
|
|
201
|
+
diffctx . -f md -c # Markdown → clipboard
|
|
202
|
+
diffctx . -f json -o tree.json # JSON → file
|
|
203
|
+
diffctx . --no-content # structure only, no file contents
|
|
204
|
+
diffctx . --max-depth 3 # limit depth
|
|
205
|
+
diffctx . -i custom.ignore # custom ignore patterns
|
|
195
206
|
|
|
196
207
|
# diff context mode (requires git repo):
|
|
197
|
-
diffctx . --diff
|
|
198
|
-
diffctx . --diff HEAD~1
|
|
199
|
-
diffctx . --diff main..feature
|
|
200
|
-
diffctx . --diff HEAD~1 --budget 30000
|
|
201
|
-
diffctx . --diff HEAD~1 -c
|
|
208
|
+
diffctx . --diff # uncommitted changes (working tree vs HEAD)
|
|
209
|
+
diffctx . --diff HEAD~1 # context for last commit
|
|
210
|
+
diffctx . --diff main..feature # context for feature branch
|
|
211
|
+
diffctx . --diff HEAD~1 --budget 30000 # limit to ~30k tokens
|
|
212
|
+
diffctx . --diff HEAD~1 -c # diff context to clipboard
|
|
213
|
+
diffctx . --diff HEAD~1 --with-raw-diff # raw patch + selected context
|
|
202
214
|
```
|
|
203
215
|
<!-- END USAGE -->
|
|
204
216
|
|
|
@@ -209,6 +221,11 @@ Every run reports token count and size on stderr — `12,847 tokens
|
|
|
209
221
|
`<binary file: N bytes>`, `<file too large: N bytes>`, or
|
|
210
222
|
`<unreadable content: not utf-8>`.
|
|
211
223
|
|
|
224
|
+
Counts come from tiktoken's `o200k_base` encoder and are exact only for the
|
|
225
|
+
GPT-4o family; Claude, Gemini and others tokenize differently, so treat
|
|
226
|
+
`--budget` as an upper bound in o200k tokens and leave headroom. Details:
|
|
227
|
+
[`docs/product/token-budget.md`](docs/product/token-budget.md).
|
|
228
|
+
|
|
212
229
|
## Python API
|
|
213
230
|
|
|
214
231
|
```python
|
|
@@ -224,6 +241,7 @@ ctx = build_diff_context(
|
|
|
224
241
|
full=False,
|
|
225
242
|
scoring_mode="ego",
|
|
226
243
|
timeout=300,
|
|
244
|
+
with_raw_diff=False, # True also embeds the raw unified diff (not charged to budget)
|
|
227
245
|
)
|
|
228
246
|
print(to_markdown(ctx))
|
|
229
247
|
|
|
@@ -264,6 +282,12 @@ anchored `/root_only.txt`). `.diffctx/whitelist` acts as an include-only filter,
|
|
|
264
282
|
and the output file is always auto-ignored. `--no-default-ignores` disables the
|
|
265
283
|
built-in patterns; `--no-ignores` disables all ignore rules (tree mode only).
|
|
266
284
|
|
|
285
|
+
An excluded path never appears in the output in any role: in diff mode it is
|
|
286
|
+
dropped both from `changed_files` and from the candidate universe, so it cannot
|
|
287
|
+
come back as a related-context fragment either (including under `--full`). The
|
|
288
|
+
same guarantee covers secret-like paths (`id_rsa`, `*.pem`, `*.key`, ...),
|
|
289
|
+
which are filtered even without an ignore entry.
|
|
290
|
+
|
|
267
291
|
## Token cache
|
|
268
292
|
|
|
269
293
|
Diff mode caches per-blob tokenization under
|
|
@@ -313,6 +337,12 @@ Apache 2.0
|
|
|
313
337
|
|
|
314
338
|
- [Documentation site](https://nikolay-e.github.io/diffctx/) — the pipeline
|
|
315
339
|
end to end: diff → fragments → graph → relevance → selection
|
|
340
|
+
- [GitHub Action](docs/product/github-action.md) — diff context as a CI step
|
|
341
|
+
for LLM review
|
|
342
|
+
- [Token counting](docs/product/token-budget.md) — which encoder, and what
|
|
343
|
+
`--budget` means for non-GPT models
|
|
344
|
+
- [Comparison](COMPARISON.md) — measured results, and when a whole-repo packer
|
|
345
|
+
or a persistent code-graph server fits better
|
|
316
346
|
- [Changelog](CHANGELOG.md)
|
|
317
347
|
- [Security policy](SECURITY.md) — threat model and vulnerability reporting
|
|
318
348
|
- [Parameter strategy](docs/engineering/parameter-strategy.md) — how `--alpha`,
|
|
@@ -27,6 +27,9 @@ changed lines and stops once more context stops paying for itself.
|
|
|
27
27
|
| Python API | ✗ | ✗ | ✗ | ✓ |
|
|
28
28
|
| MCP server | ✗ | ✗ | ✗ | ✓ |
|
|
29
29
|
|
|
30
|
+
Fuller positioning — measured results, and when a whole-repo packer or a
|
|
31
|
+
persistent code-graph server fits better: [COMPARISON.md](COMPARISON.md).
|
|
32
|
+
|
|
30
33
|
## Install
|
|
31
34
|
|
|
32
35
|
```bash
|
|
@@ -44,6 +47,13 @@ npx diffctx . --diff HEAD~1 # npm wrapper over the native binary
|
|
|
44
47
|
docker run --rm -v "$PWD:/repo" ghcr.io/nikolay-e/diffctx . --diff HEAD~1
|
|
45
48
|
```
|
|
46
49
|
|
|
50
|
+
On Windows, via Scoop (this repository is the bucket):
|
|
51
|
+
|
|
52
|
+
```powershell
|
|
53
|
+
scoop bucket add diffctx https://github.com/nikolay-e/diffctx
|
|
54
|
+
scoop install diffctx/diffctx
|
|
55
|
+
```
|
|
56
|
+
|
|
47
57
|
The image runs as a non-root user (uid 10001) and writes to stdout — the
|
|
48
58
|
native binary has no `-o` flag, so redirect to capture: `... --diff HEAD~1 >
|
|
49
59
|
context.yaml`.
|
|
@@ -80,11 +90,12 @@ the `--budget` token cap is hit.
|
|
|
80
90
|
| Flag | Default | Description |
|
|
81
91
|
|-------------|---------|--------------------------------------------------------------------------|
|
|
82
92
|
| `--scoring` | `ego` | `ego` = bounded expansion around changed nodes (fast, predictable radius); `ppr` = Personalized PageRank (global, smoother decay, slower); `bm25` = lexical retrieval against the diff hunks (baseline for sparse graphs) |
|
|
83
|
-
| `--budget` | auto | Hard token
|
|
93
|
+
| `--budget` | auto | Hard cap in o200k_base tokens (see [Token counting](docs/product/token-budget.md)): `N` enforces a fixed cap, `-1` disables it, `0` is a strict-zero floor (empty selection; use `--full` for changed files only) |
|
|
84
94
|
| `--alpha` | 0.60 | PPR damping; higher = context clusters tighter around changes (`--scoring ppr` only) |
|
|
85
95
|
| `--tau` | 0.12 | Relevance threshold for full fragment content; lower-scoring fragments are stubbed or dropped (lower = more context) |
|
|
86
96
|
| `--full` | false | Only the changed files, every fragment, no related-code context |
|
|
87
97
|
| `--timeout` | 300 | Wall-clock deadline in seconds; on expiry diffctx exits 124 instead of hanging |
|
|
98
|
+
| `--with-raw-diff` | false | Also embed git's raw unified diff ahead of the selected fragments — additive (selection unchanged), not charged to `--budget`, lock/ignored/secret-like sections omitted. Python CLI only |
|
|
88
99
|
|
|
89
100
|
Calibration of `--alpha`, `--tau`, and the edge-weight priors:
|
|
90
101
|
[`docs/engineering/parameter-strategy.md`](docs/engineering/parameter-strategy.md).
|
|
@@ -108,19 +119,20 @@ diffctx graph . --level file -f graphml -o g.xml # file-level graph as GraphML
|
|
|
108
119
|
<!-- BEGIN USAGE -->
|
|
109
120
|
```bash
|
|
110
121
|
# full codebase export:
|
|
111
|
-
diffctx .
|
|
112
|
-
diffctx . -f md -c
|
|
113
|
-
diffctx . -f json -o tree.json
|
|
114
|
-
diffctx . --no-content
|
|
115
|
-
diffctx . --max-depth 3
|
|
116
|
-
diffctx . -i custom.ignore
|
|
122
|
+
diffctx . # Markdown to stdout + token count
|
|
123
|
+
diffctx . -f md -c # Markdown → clipboard
|
|
124
|
+
diffctx . -f json -o tree.json # JSON → file
|
|
125
|
+
diffctx . --no-content # structure only, no file contents
|
|
126
|
+
diffctx . --max-depth 3 # limit depth
|
|
127
|
+
diffctx . -i custom.ignore # custom ignore patterns
|
|
117
128
|
|
|
118
129
|
# diff context mode (requires git repo):
|
|
119
|
-
diffctx . --diff
|
|
120
|
-
diffctx . --diff HEAD~1
|
|
121
|
-
diffctx . --diff main..feature
|
|
122
|
-
diffctx . --diff HEAD~1 --budget 30000
|
|
123
|
-
diffctx . --diff HEAD~1 -c
|
|
130
|
+
diffctx . --diff # uncommitted changes (working tree vs HEAD)
|
|
131
|
+
diffctx . --diff HEAD~1 # context for last commit
|
|
132
|
+
diffctx . --diff main..feature # context for feature branch
|
|
133
|
+
diffctx . --diff HEAD~1 --budget 30000 # limit to ~30k tokens
|
|
134
|
+
diffctx . --diff HEAD~1 -c # diff context to clipboard
|
|
135
|
+
diffctx . --diff HEAD~1 --with-raw-diff # raw patch + selected context
|
|
124
136
|
```
|
|
125
137
|
<!-- END USAGE -->
|
|
126
138
|
|
|
@@ -131,6 +143,11 @@ Every run reports token count and size on stderr — `12,847 tokens
|
|
|
131
143
|
`<binary file: N bytes>`, `<file too large: N bytes>`, or
|
|
132
144
|
`<unreadable content: not utf-8>`.
|
|
133
145
|
|
|
146
|
+
Counts come from tiktoken's `o200k_base` encoder and are exact only for the
|
|
147
|
+
GPT-4o family; Claude, Gemini and others tokenize differently, so treat
|
|
148
|
+
`--budget` as an upper bound in o200k tokens and leave headroom. Details:
|
|
149
|
+
[`docs/product/token-budget.md`](docs/product/token-budget.md).
|
|
150
|
+
|
|
134
151
|
## Python API
|
|
135
152
|
|
|
136
153
|
```python
|
|
@@ -146,6 +163,7 @@ ctx = build_diff_context(
|
|
|
146
163
|
full=False,
|
|
147
164
|
scoring_mode="ego",
|
|
148
165
|
timeout=300,
|
|
166
|
+
with_raw_diff=False, # True also embeds the raw unified diff (not charged to budget)
|
|
149
167
|
)
|
|
150
168
|
print(to_markdown(ctx))
|
|
151
169
|
|
|
@@ -186,6 +204,12 @@ anchored `/root_only.txt`). `.diffctx/whitelist` acts as an include-only filter,
|
|
|
186
204
|
and the output file is always auto-ignored. `--no-default-ignores` disables the
|
|
187
205
|
built-in patterns; `--no-ignores` disables all ignore rules (tree mode only).
|
|
188
206
|
|
|
207
|
+
An excluded path never appears in the output in any role: in diff mode it is
|
|
208
|
+
dropped both from `changed_files` and from the candidate universe, so it cannot
|
|
209
|
+
come back as a related-context fragment either (including under `--full`). The
|
|
210
|
+
same guarantee covers secret-like paths (`id_rsa`, `*.pem`, `*.key`, ...),
|
|
211
|
+
which are filtered even without an ignore entry.
|
|
212
|
+
|
|
189
213
|
## Token cache
|
|
190
214
|
|
|
191
215
|
Diff mode caches per-blob tokenization under
|
|
@@ -235,6 +259,12 @@ Apache 2.0
|
|
|
235
259
|
|
|
236
260
|
- [Documentation site](https://nikolay-e.github.io/diffctx/) — the pipeline
|
|
237
261
|
end to end: diff → fragments → graph → relevance → selection
|
|
262
|
+
- [GitHub Action](docs/product/github-action.md) — diff context as a CI step
|
|
263
|
+
for LLM review
|
|
264
|
+
- [Token counting](docs/product/token-budget.md) — which encoder, and what
|
|
265
|
+
`--budget` means for non-GPT models
|
|
266
|
+
- [Comparison](COMPARISON.md) — measured results, and when a whole-repo packer
|
|
267
|
+
or a persistent code-graph server fits better
|
|
238
268
|
- [Changelog](CHANGELOG.md)
|
|
239
269
|
- [Security policy](SECURITY.md) — threat model and vulnerability reporting
|
|
240
270
|
- [Parameter strategy](docs/engineering/parameter-strategy.md) — how `--alpha`,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[package]
|
|
2
2
|
name = "diffctx"
|
|
3
|
-
version = "1.12.
|
|
3
|
+
version = "1.12.3"
|
|
4
4
|
edition = "2024"
|
|
5
5
|
rust-version = "1.85"
|
|
6
6
|
description = "Selects the minimum code an LLM needs to review a git diff: walks the dependency graph outward from changed lines and stops when extra context stops paying for itself"
|
|
@@ -72,8 +72,8 @@ lang-extra = [
|
|
|
72
72
|
mimalloc = { version = "0.1", default-features = false }
|
|
73
73
|
clap = { version = "4", features = ["derive"] }
|
|
74
74
|
tree-sitter = "0.25"
|
|
75
|
-
tree-sitter-python = { version = "0.
|
|
76
|
-
tree-sitter-javascript = { version = "0.
|
|
75
|
+
tree-sitter-python = { version = "0.25", optional = true }
|
|
76
|
+
tree-sitter-javascript = { version = "0.25", optional = true }
|
|
77
77
|
tree-sitter-typescript = { version = "0.23", optional = true }
|
|
78
78
|
tree-sitter-go = { version = "0.25", optional = true }
|
|
79
79
|
tree-sitter-rust = { version = "0.24", optional = true }
|
|
@@ -84,7 +84,7 @@ tree-sitter-c-sharp = { version = "0.23", optional = true }
|
|
|
84
84
|
tree-sitter-ruby = { version = "0.23", optional = true }
|
|
85
85
|
tree-sitter-php = { version = "0.24", optional = true }
|
|
86
86
|
tree-sitter-scala = { version = "0.26", optional = true }
|
|
87
|
-
tree-sitter-swift = { version = "0.
|
|
87
|
+
tree-sitter-swift = { version = "0.7", optional = true }
|
|
88
88
|
tree-sitter-html = { version = "0.23", optional = true }
|
|
89
89
|
tree-sitter-bash = { version = "0.25", optional = true }
|
|
90
90
|
tree-sitter-css = { version = "0.25", optional = true }
|
|
@@ -27,6 +27,9 @@ changed lines and stops once more context stops paying for itself.
|
|
|
27
27
|
| Python API | ✗ | ✗ | ✗ | ✓ |
|
|
28
28
|
| MCP server | ✗ | ✗ | ✗ | ✓ |
|
|
29
29
|
|
|
30
|
+
Fuller positioning — measured results, and when a whole-repo packer or a
|
|
31
|
+
persistent code-graph server fits better: [COMPARISON.md](COMPARISON.md).
|
|
32
|
+
|
|
30
33
|
## Install
|
|
31
34
|
|
|
32
35
|
```bash
|
|
@@ -44,6 +47,13 @@ npx diffctx . --diff HEAD~1 # npm wrapper over the native binary
|
|
|
44
47
|
docker run --rm -v "$PWD:/repo" ghcr.io/nikolay-e/diffctx . --diff HEAD~1
|
|
45
48
|
```
|
|
46
49
|
|
|
50
|
+
On Windows, via Scoop (this repository is the bucket):
|
|
51
|
+
|
|
52
|
+
```powershell
|
|
53
|
+
scoop bucket add diffctx https://github.com/nikolay-e/diffctx
|
|
54
|
+
scoop install diffctx/diffctx
|
|
55
|
+
```
|
|
56
|
+
|
|
47
57
|
The image runs as a non-root user (uid 10001) and writes to stdout — the
|
|
48
58
|
native binary has no `-o` flag, so redirect to capture: `... --diff HEAD~1 >
|
|
49
59
|
context.yaml`.
|
|
@@ -80,11 +90,12 @@ the `--budget` token cap is hit.
|
|
|
80
90
|
| Flag | Default | Description |
|
|
81
91
|
|-------------|---------|--------------------------------------------------------------------------|
|
|
82
92
|
| `--scoring` | `ego` | `ego` = bounded expansion around changed nodes (fast, predictable radius); `ppr` = Personalized PageRank (global, smoother decay, slower); `bm25` = lexical retrieval against the diff hunks (baseline for sparse graphs) |
|
|
83
|
-
| `--budget` | auto | Hard token
|
|
93
|
+
| `--budget` | auto | Hard cap in o200k_base tokens (see [Token counting](docs/product/token-budget.md)): `N` enforces a fixed cap, `-1` disables it, `0` is a strict-zero floor (empty selection; use `--full` for changed files only) |
|
|
84
94
|
| `--alpha` | 0.60 | PPR damping; higher = context clusters tighter around changes (`--scoring ppr` only) |
|
|
85
95
|
| `--tau` | 0.12 | Relevance threshold for full fragment content; lower-scoring fragments are stubbed or dropped (lower = more context) |
|
|
86
96
|
| `--full` | false | Only the changed files, every fragment, no related-code context |
|
|
87
97
|
| `--timeout` | 300 | Wall-clock deadline in seconds; on expiry diffctx exits 124 instead of hanging |
|
|
98
|
+
| `--with-raw-diff` | false | Also embed git's raw unified diff ahead of the selected fragments — additive (selection unchanged), not charged to `--budget`, lock/ignored/secret-like sections omitted. Python CLI only |
|
|
88
99
|
|
|
89
100
|
Calibration of `--alpha`, `--tau`, and the edge-weight priors:
|
|
90
101
|
[`docs/engineering/parameter-strategy.md`](docs/engineering/parameter-strategy.md).
|
|
@@ -108,19 +119,20 @@ diffctx graph . --level file -f graphml -o g.xml # file-level graph as GraphML
|
|
|
108
119
|
<!-- BEGIN USAGE -->
|
|
109
120
|
```bash
|
|
110
121
|
# full codebase export:
|
|
111
|
-
diffctx .
|
|
112
|
-
diffctx . -f md -c
|
|
113
|
-
diffctx . -f json -o tree.json
|
|
114
|
-
diffctx . --no-content
|
|
115
|
-
diffctx . --max-depth 3
|
|
116
|
-
diffctx . -i custom.ignore
|
|
122
|
+
diffctx . # Markdown to stdout + token count
|
|
123
|
+
diffctx . -f md -c # Markdown → clipboard
|
|
124
|
+
diffctx . -f json -o tree.json # JSON → file
|
|
125
|
+
diffctx . --no-content # structure only, no file contents
|
|
126
|
+
diffctx . --max-depth 3 # limit depth
|
|
127
|
+
diffctx . -i custom.ignore # custom ignore patterns
|
|
117
128
|
|
|
118
129
|
# diff context mode (requires git repo):
|
|
119
|
-
diffctx . --diff
|
|
120
|
-
diffctx . --diff HEAD~1
|
|
121
|
-
diffctx . --diff main..feature
|
|
122
|
-
diffctx . --diff HEAD~1 --budget 30000
|
|
123
|
-
diffctx . --diff HEAD~1 -c
|
|
130
|
+
diffctx . --diff # uncommitted changes (working tree vs HEAD)
|
|
131
|
+
diffctx . --diff HEAD~1 # context for last commit
|
|
132
|
+
diffctx . --diff main..feature # context for feature branch
|
|
133
|
+
diffctx . --diff HEAD~1 --budget 30000 # limit to ~30k tokens
|
|
134
|
+
diffctx . --diff HEAD~1 -c # diff context to clipboard
|
|
135
|
+
diffctx . --diff HEAD~1 --with-raw-diff # raw patch + selected context
|
|
124
136
|
```
|
|
125
137
|
<!-- END USAGE -->
|
|
126
138
|
|
|
@@ -131,6 +143,11 @@ Every run reports token count and size on stderr — `12,847 tokens
|
|
|
131
143
|
`<binary file: N bytes>`, `<file too large: N bytes>`, or
|
|
132
144
|
`<unreadable content: not utf-8>`.
|
|
133
145
|
|
|
146
|
+
Counts come from tiktoken's `o200k_base` encoder and are exact only for the
|
|
147
|
+
GPT-4o family; Claude, Gemini and others tokenize differently, so treat
|
|
148
|
+
`--budget` as an upper bound in o200k tokens and leave headroom. Details:
|
|
149
|
+
[`docs/product/token-budget.md`](docs/product/token-budget.md).
|
|
150
|
+
|
|
134
151
|
## Python API
|
|
135
152
|
|
|
136
153
|
```python
|
|
@@ -146,6 +163,7 @@ ctx = build_diff_context(
|
|
|
146
163
|
full=False,
|
|
147
164
|
scoring_mode="ego",
|
|
148
165
|
timeout=300,
|
|
166
|
+
with_raw_diff=False, # True also embeds the raw unified diff (not charged to budget)
|
|
149
167
|
)
|
|
150
168
|
print(to_markdown(ctx))
|
|
151
169
|
|
|
@@ -186,6 +204,12 @@ anchored `/root_only.txt`). `.diffctx/whitelist` acts as an include-only filter,
|
|
|
186
204
|
and the output file is always auto-ignored. `--no-default-ignores` disables the
|
|
187
205
|
built-in patterns; `--no-ignores` disables all ignore rules (tree mode only).
|
|
188
206
|
|
|
207
|
+
An excluded path never appears in the output in any role: in diff mode it is
|
|
208
|
+
dropped both from `changed_files` and from the candidate universe, so it cannot
|
|
209
|
+
come back as a related-context fragment either (including under `--full`). The
|
|
210
|
+
same guarantee covers secret-like paths (`id_rsa`, `*.pem`, `*.key`, ...),
|
|
211
|
+
which are filtered even without an ignore entry.
|
|
212
|
+
|
|
189
213
|
## Token cache
|
|
190
214
|
|
|
191
215
|
Diff mode caches per-blob tokenization under
|
|
@@ -235,6 +259,12 @@ Apache 2.0
|
|
|
235
259
|
|
|
236
260
|
- [Documentation site](https://nikolay-e.github.io/diffctx/) — the pipeline
|
|
237
261
|
end to end: diff → fragments → graph → relevance → selection
|
|
262
|
+
- [GitHub Action](docs/product/github-action.md) — diff context as a CI step
|
|
263
|
+
for LLM review
|
|
264
|
+
- [Token counting](docs/product/token-budget.md) — which encoder, and what
|
|
265
|
+
`--budget` means for non-GPT models
|
|
266
|
+
- [Comparison](COMPARISON.md) — measured results, and when a whole-repo packer
|
|
267
|
+
or a persistent code-graph server fits better
|
|
238
268
|
- [Changelog](CHANGELOG.md)
|
|
239
269
|
- [Security policy](SECURITY.md) — threat model and vulnerability reporting
|
|
240
270
|
- [Parameter strategy](docs/engineering/parameter-strategy.md) — how `--alpha`,
|