diffctx 1.17.0__tar.gz → 1.18.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {diffctx-1.17.0 → diffctx-1.18.0}/CHANGELOG.md +48 -1
- {diffctx-1.17.0 → diffctx-1.18.0}/Cargo.lock +1 -1
- {diffctx-1.17.0 → diffctx-1.18.0}/PKG-INFO +9 -6
- {diffctx-1.17.0 → diffctx-1.18.0}/README.md +5 -2
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/Cargo.toml +2 -2
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/README.md +5 -2
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/discovery.rs +126 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/effective_config.rs +2 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/graph.rs +33 -0
- diffctx-1.18.0/crates/diffctx-native/src/hook.rs +644 -0
- diffctx-1.18.0/crates/diffctx-native/src/impact.rs +707 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/in_memory_harness.rs +2 -1
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/lib.rs +2 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/locate.rs +18 -1
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/main.rs +101 -4
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/pipeline.rs +99 -11
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/pybridge.rs +58 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/render.rs +9 -1
- {diffctx-1.17.0 → diffctx-1.18.0}/pyproject.toml +3 -3
- {diffctx-1.17.0 → diffctx-1.18.0}/src/diffctx/_app.py +38 -4
- {diffctx-1.17.0 → diffctx-1.18.0}/src/diffctx/_diffctx.pyi +9 -0
- diffctx-1.18.0/src/diffctx/_native/__init__.py +7 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/src/diffctx/_native/pipeline.py +24 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/src/diffctx/cli.py +12 -2
- {diffctx-1.17.0 → diffctx-1.18.0}/src/diffctx/mcp/legacy.py +8 -10
- {diffctx-1.17.0 → diffctx-1.18.0}/src/diffctx/mcp/server.py +122 -35
- diffctx-1.18.0/src/diffctx/version.py +1 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/src/diffctx/writer.py +29 -8
- diffctx-1.17.0/src/diffctx/_native/__init__.py +0 -7
- diffctx-1.17.0/src/diffctx/version.py +0 -1
- {diffctx-1.17.0 → diffctx-1.18.0}/Cargo.toml +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/LICENSE +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/analytics.rs +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/candidate_files.rs +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/change_class.rs +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/config/analytics.rs +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/config/bm25.rs +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/config/budget.rs +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/config/category_weights.rs +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/config/edge_weights.rs +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/config/env_overrides.rs +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/config/extensions.rs +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/config/filtering.rs +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/config/fragmentation.rs +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/config/git.rs +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/config/graph_filtering.rs +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/config/importance.rs +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/config/limits.rs +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/config/mod.rs +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/config/mode.rs +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/config/needs.rs +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/config/parsers.rs +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/config/render.rs +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/config/scoring.rs +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/config/selection.rs +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/config/tokenization.rs +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/config/weights.rs +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/core.rs +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/edges/base.rs +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/edges/config_edges/build_system.rs +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/edges/config_edges/cicd.rs +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/edges/config_edges/docker.rs +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/edges/config_edges/generic.rs +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/edges/config_edges/helm.rs +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/edges/config_edges/kubernetes.rs +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/edges/config_edges/mod.rs +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/edges/document/mod.rs +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/edges/history/cochange.rs +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/edges/history/mod.rs +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/edges/mod.rs +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/edges/semantic/ansible.rs +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/edges/semantic/bazel.rs +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/edges/semantic/c_family.rs +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/edges/semantic/cargo_edges.rs +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/edges/semantic/clojure.rs +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/edges/semantic/css.rs +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/edges/semantic/dart.rs +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/edges/semantic/dbt.rs +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/edges/semantic/dotnet.rs +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/edges/semantic/elixir.rs +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/edges/semantic/erlang.rs +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/edges/semantic/go.rs +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/edges/semantic/graphql.rs +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/edges/semantic/haskell.rs +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/edges/semantic/javascript.rs +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/edges/semantic/julia.rs +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/edges/semantic/jvm.rs +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/edges/semantic/latex.rs +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/edges/semantic/lua.rs +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/edges/semantic/mod.rs +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/edges/semantic/nim.rs +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/edges/semantic/nix.rs +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/edges/semantic/ocaml.rs +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/edges/semantic/openapi.rs +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/edges/semantic/perl.rs +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/edges/semantic/php.rs +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/edges/semantic/prisma.rs +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/edges/semantic/protobuf.rs +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/edges/semantic/python.rs +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/edges/semantic/r_lang.rs +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/edges/semantic/ruby.rs +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/edges/semantic/rust_lang.rs +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/edges/semantic/shell.rs +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/edges/semantic/sql.rs +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/edges/semantic/swift.rs +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/edges/semantic/tags.rs +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/edges/semantic/terraform.rs +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/edges/semantic/zig.rs +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/edges/similarity/lexical.rs +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/edges/similarity/mod.rs +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/edges/structural/containment.rs +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/edges/structural/mod.rs +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/edges/structural/sibling.rs +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/edges/structural/testing.rs +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/excerpt.rs +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/facts/mod.rs +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/facts/scala.rs +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/filtering.rs +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/fragmentation.rs +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/git.rs +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/graph_export.rs +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/interval.rs +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/languages.rs +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/mode.rs +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/parsers/config_parser.rs +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/parsers/generic.rs +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/parsers/markdown.rs +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/parsers/mod.rs +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/parsers/tree_sitter_strategy.rs +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/paths.rs +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/peak_rss.rs +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/postpass.rs +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/ppr.rs +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/project_graph.rs +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/provenance.rs +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/resource.rs +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/run_provenance.rs +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/sanitize.rs +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/scoring.rs +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/select.rs +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/signatures.rs +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/stopwords/code.txt +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/stopwords/docs.txt +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/stopwords.rs +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/test_rng.rs +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/testfiles.rs +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/token_corpus.rs +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/tokenizer.rs +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/types.rs +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/utility/boltzmann.rs +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/utility/importance.rs +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/utility/mod.rs +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/utility/needs.rs +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/crates/diffctx-native/src/utility/objective.rs +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/rust-toolchain.toml +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/src/diffctx/__init__.py +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/src/diffctx/__main__.py +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/src/diffctx/_native/graph_analytics.py +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/src/diffctx/_native/graph_export.py +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/src/diffctx/_native/project_graph.py +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/src/diffctx/clipboard.py +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/src/diffctx/ignore.py +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/src/diffctx/logger.py +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/src/diffctx/mcp/__init__.py +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/src/diffctx/mcp/__main__.py +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/src/diffctx/mcp/fetch.py +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/src/diffctx/mcp/security.py +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/src/diffctx/py.typed +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/src/diffctx/tokens.py +0 -0
- {diffctx-1.17.0 → diffctx-1.18.0}/src/diffctx/tree.py +0 -0
|
@@ -7,10 +7,56 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [1.18.0] - 2026-09-29
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **`--mode impact`: what a change reaches outside its own diff, under 2k
|
|
15
|
+
tokens.** For each changed symbol, the callers in files the diff does not
|
|
16
|
+
touch, whether a test guards each one, and which symbols more than one
|
|
17
|
+
commit of the range edited; public API and schema changes are listed as
|
|
18
|
+
facts. It walks the graph the other modes already build, with a discovery
|
|
19
|
+
pass that follows references *into* the changed files (the other modes
|
|
20
|
+
only follow what the changed files import), and filters name-only links
|
|
21
|
+
the way a reader would: the caller must name the symbol in code and
|
|
22
|
+
reference its module. An empty impact is an answer, not an error. CLI
|
|
23
|
+
(`-f md` is the text form), MCP `mode="impact"`, the GitHub Action's
|
|
24
|
+
`mode` input, and `schemas/diffctx.impact.v1.json`.
|
|
25
|
+
- **`diffctx hook pretooluse` / `posttooluse`: the Claude Code plugin hands
|
|
26
|
+
the agent a change's impact without asking it to call anything.** Eight
|
|
27
|
+
agent sessions with the tool installed made zero unprompted calls (#289).
|
|
28
|
+
Before a commit, merge, cherry-pick, push or pull request the impact of
|
|
29
|
+
what that command records is injected as context — a plain `git commit`
|
|
30
|
+
reviews the index, `-a` the working tree, a merge the branch since its
|
|
31
|
+
merge base; after a `git diff` or `git status` the agent ran itself, the
|
|
32
|
+
pending change's impact rides on the answer it asked for. Once per change
|
|
33
|
+
(content-keyed with `git patch-id`, so a manual `--mode impact` run
|
|
34
|
+
counts), silent when nothing outside the diff depends on the change, and
|
|
35
|
+
never blocking: a range that runs out of the 30 s deadline is marked
|
|
36
|
+
reviewed rather than retried on every push. `impact_gate` (off by default)
|
|
37
|
+
denies a commit until its impact was shown once. At session start the
|
|
38
|
+
plugin fetches the release binary for the platform, verified against
|
|
39
|
+
`checksums.json`, into its data directory, backs off for six hours after a
|
|
40
|
+
failed download, and puts the one-line recipe into the session's context;
|
|
41
|
+
`impact_hook` turns all of it off (#310).
|
|
42
|
+
- **`/diffctx:commit`**: the impact first, then the commit.
|
|
43
|
+
- **`diffctx_context` is always loaded** under Claude Code's tool search
|
|
44
|
+
(`_meta.anthropic/alwaysLoad`), and its description names `impact` as the
|
|
45
|
+
pre-commit call; both skills default to the server's own range choice.
|
|
46
|
+
- **The PR review comment opens with the impact view.**
|
|
47
|
+
- **Plugin eval `breaking-caller`** measures the number under the goal: with
|
|
48
|
+
a contract change uncommitted and two callers about to break, does the
|
|
49
|
+
agent handle or name them before committing.
|
|
50
|
+
|
|
10
51
|
## [1.17.0] - 2026-09-23
|
|
11
52
|
|
|
12
53
|
### Added
|
|
13
54
|
|
|
55
|
+
- **The site lives at <https://diffctx.com>.** It was a 301 to
|
|
56
|
+
`nikolay-e.github.io/diffctx/`, so search engines indexed the github.io
|
|
57
|
+
URL and the product domain had no presence of its own. It is now the
|
|
58
|
+
GitHub Pages custom domain (github.io redirects to it), with a
|
|
59
|
+
`robots.txt` and `sitemap.xml`; package metadata points at it.
|
|
14
60
|
- **Windows and Intel-Mac are release targets.** Every test matrix runs on
|
|
15
61
|
Linux, macOS and Windows on x64 and ARM; wheels and binaries ship for
|
|
16
62
|
`x86_64-apple-darwin` and `aarch64-pc-windows-msvc` as well, and `npm
|
|
@@ -1626,7 +1672,8 @@ Earlier releases shipped as `treemapper`; see
|
|
|
1626
1672
|
<https://github.com/nikolay-e/diffctx/releases> for the corresponding GitHub
|
|
1627
1673
|
release notes (`1.0.0` through `1.6.1`).
|
|
1628
1674
|
|
|
1629
|
-
[Unreleased]: https://github.com/nikolay-e/diffctx/compare/v1.
|
|
1675
|
+
[Unreleased]: https://github.com/nikolay-e/diffctx/compare/v1.18.0...HEAD
|
|
1676
|
+
[1.18.0]: https://github.com/nikolay-e/diffctx/compare/v1.17.0...v1.18.0
|
|
1630
1677
|
[1.17.0]: https://github.com/nikolay-e/diffctx/compare/v1.16.0...v1.17.0
|
|
1631
1678
|
[1.16.0]: https://github.com/nikolay-e/diffctx/compare/v1.15.0...v1.16.0
|
|
1632
1679
|
[1.15.0]: https://github.com/nikolay-e/diffctx/compare/v1.14.0...v1.15.0
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: diffctx
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.18.0
|
|
4
4
|
Classifier: Development Status :: 5 - Production/Stable
|
|
5
5
|
Classifier: Environment :: Console
|
|
6
6
|
Classifier: Intended Audience :: Developers
|
|
@@ -44,15 +44,15 @@ Provides-Extra: mcp
|
|
|
44
44
|
License-File: LICENSE
|
|
45
45
|
Summary: Selects the minimum code an LLM needs to review a git diff
|
|
46
46
|
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
|
|
47
|
-
Home-Page: https://
|
|
47
|
+
Home-Page: https://diffctx.com/
|
|
48
48
|
Author-email: Nikolay Eremeev <nikolay.eremeev@outlook.com>
|
|
49
49
|
License-Expression: Apache-2.0
|
|
50
50
|
Requires-Python: >=3.10
|
|
51
51
|
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
|
|
52
52
|
Project-URL: Author, https://nikolay-eremeev.com
|
|
53
53
|
Project-URL: Changelog, https://github.com/nikolay-e/diffctx/blob/main/CHANGELOG.md
|
|
54
|
-
Project-URL: Documentation, https://
|
|
55
|
-
Project-URL: Homepage, https://
|
|
54
|
+
Project-URL: Documentation, https://diffctx.com/
|
|
55
|
+
Project-URL: Homepage, https://diffctx.com/
|
|
56
56
|
Project-URL: Issues, https://github.com/nikolay-e/diffctx/issues
|
|
57
57
|
Project-URL: Paper, https://doi.org/10.5281/zenodo.18824579
|
|
58
58
|
Project-URL: Repository, https://github.com/nikolay-e/diffctx
|
|
@@ -248,7 +248,10 @@ as `io.github.nikolay-e/diffctx`. One-line setup (zero-install via
|
|
|
248
248
|
[uv](https://docs.astral.sh/uv/)):
|
|
249
249
|
|
|
250
250
|
```bash
|
|
251
|
-
# Claude Code
|
|
251
|
+
# Claude Code plugin: the server plus /diffctx:diffctx and /diffctx:impact
|
|
252
|
+
claude plugin marketplace add nikolay-e/diffctx
|
|
253
|
+
claude plugin install diffctx@diffctx
|
|
254
|
+
# Claude Code, server only
|
|
252
255
|
claude mcp add diffctx -- uvx --from 'diffctx[mcp]' diffctx-mcp
|
|
253
256
|
# Codex CLI
|
|
254
257
|
codex mcp add diffctx -- uvx --from 'diffctx[mcp]' diffctx-mcp
|
|
@@ -343,7 +346,7 @@ Apache 2.0
|
|
|
343
346
|
|
|
344
347
|
---
|
|
345
348
|
|
|
346
|
-
- [Documentation site](https://
|
|
349
|
+
- [Documentation site](https://diffctx.com/) — the pipeline
|
|
347
350
|
end to end: diff → fragments → graph → relevance → selection
|
|
348
351
|
- [Command-line reference](docs/product/cli.md) — every flag with its default
|
|
349
352
|
and meaning, rendered from `diffctx --help`
|
|
@@ -189,7 +189,10 @@ as `io.github.nikolay-e/diffctx`. One-line setup (zero-install via
|
|
|
189
189
|
[uv](https://docs.astral.sh/uv/)):
|
|
190
190
|
|
|
191
191
|
```bash
|
|
192
|
-
# Claude Code
|
|
192
|
+
# Claude Code plugin: the server plus /diffctx:diffctx and /diffctx:impact
|
|
193
|
+
claude plugin marketplace add nikolay-e/diffctx
|
|
194
|
+
claude plugin install diffctx@diffctx
|
|
195
|
+
# Claude Code, server only
|
|
193
196
|
claude mcp add diffctx -- uvx --from 'diffctx[mcp]' diffctx-mcp
|
|
194
197
|
# Codex CLI
|
|
195
198
|
codex mcp add diffctx -- uvx --from 'diffctx[mcp]' diffctx-mcp
|
|
@@ -284,7 +287,7 @@ Apache 2.0
|
|
|
284
287
|
|
|
285
288
|
---
|
|
286
289
|
|
|
287
|
-
- [Documentation site](https://
|
|
290
|
+
- [Documentation site](https://diffctx.com/) — the pipeline
|
|
288
291
|
end to end: diff → fragments → graph → relevance → selection
|
|
289
292
|
- [Command-line reference](docs/product/cli.md) — every flag with its default
|
|
290
293
|
and meaning, rendered from `diffctx --help`
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
[package]
|
|
2
2
|
name = "diffctx"
|
|
3
|
-
version = "1.
|
|
3
|
+
version = "1.18.0"
|
|
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"
|
|
7
7
|
license = "Apache-2.0"
|
|
8
8
|
repository = "https://github.com/nikolay-e/diffctx"
|
|
9
|
-
homepage = "https://
|
|
9
|
+
homepage = "https://diffctx.com/"
|
|
10
10
|
documentation = "https://docs.rs/diffctx"
|
|
11
11
|
readme = "README.md"
|
|
12
12
|
keywords = ["git", "diff", "llm", "context", "tree-sitter"]
|
|
@@ -189,7 +189,10 @@ as `io.github.nikolay-e/diffctx`. One-line setup (zero-install via
|
|
|
189
189
|
[uv](https://docs.astral.sh/uv/)):
|
|
190
190
|
|
|
191
191
|
```bash
|
|
192
|
-
# Claude Code
|
|
192
|
+
# Claude Code plugin: the server plus /diffctx:diffctx and /diffctx:impact
|
|
193
|
+
claude plugin marketplace add nikolay-e/diffctx
|
|
194
|
+
claude plugin install diffctx@diffctx
|
|
195
|
+
# Claude Code, server only
|
|
193
196
|
claude mcp add diffctx -- uvx --from 'diffctx[mcp]' diffctx-mcp
|
|
194
197
|
# Codex CLI
|
|
195
198
|
codex mcp add diffctx -- uvx --from 'diffctx[mcp]' diffctx-mcp
|
|
@@ -284,7 +287,7 @@ Apache 2.0
|
|
|
284
287
|
|
|
285
288
|
---
|
|
286
289
|
|
|
287
|
-
- [Documentation site](https://
|
|
290
|
+
- [Documentation site](https://diffctx.com/) — the pipeline
|
|
288
291
|
end to end: diff → fragments → graph → relevance → selection
|
|
289
292
|
- [Command-line reference](docs/product/cli.md) — every flag with its default
|
|
290
293
|
and meaning, rendered from `diffctx --help`
|
|
@@ -117,6 +117,132 @@ fn expand_by_rare_identifiers(ctx: &DiscoveryContext) -> Vec<PathBuf> {
|
|
|
117
117
|
result
|
|
118
118
|
}
|
|
119
119
|
|
|
120
|
+
/// Files that name what the change defines: the callers a change reaches
|
|
121
|
+
/// (#310). Forward discovery follows what the changed file imports; nothing
|
|
122
|
+
/// followed the other way, so a function with a hundred importers surfaced
|
|
123
|
+
/// one of them and the impact view called the rest silence. Only the
|
|
124
|
+
/// impact mode asks for this: widening the universe moves selection, and
|
|
125
|
+
/// the corpus operating point is calibrated without it.
|
|
126
|
+
pub struct ReverseReferenceDiscovery;
|
|
127
|
+
|
|
128
|
+
/// A name held by more candidate files than this is vocabulary, not a
|
|
129
|
+
/// reference, and following it would admit the repository.
|
|
130
|
+
const MAX_REVERSE_FILES_PER_NAME: usize = 64;
|
|
131
|
+
const MAX_REVERSE_FILES: usize = 256;
|
|
132
|
+
const MIN_REVERSE_NAME_LEN: usize = 4;
|
|
133
|
+
|
|
134
|
+
static DEFINITION_RE: OnceLock<regex::Regex> = OnceLock::new();
|
|
135
|
+
|
|
136
|
+
fn definition_names(content: &str) -> Vec<String> {
|
|
137
|
+
let re = DEFINITION_RE.get_or_init(|| {
|
|
138
|
+
regex::Regex::new(
|
|
139
|
+
r"(?m)^\s*(?:export\s+(?:default\s+)?|pub(?:\([^)]*\))?\s+|public\s+|static\s+|async\s+|abstract\s+|final\s+|data\s+|sealed\s+|open\s+)*(?:fn|def|class|struct|enum|interface|trait|type|function|const|let|var|val|object|impl|protocol|module|record)\s+([A-Za-z_][A-Za-z0-9_]*)",
|
|
140
|
+
)
|
|
141
|
+
.expect("definition regex")
|
|
142
|
+
});
|
|
143
|
+
re.captures_iter(content)
|
|
144
|
+
.filter_map(|c| c.get(1).map(|m| m.as_str().to_string()))
|
|
145
|
+
.filter(|n| n.len() >= MIN_REVERSE_NAME_LEN)
|
|
146
|
+
.collect()
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
impl DiscoveryStrategy for ReverseReferenceDiscovery {
|
|
150
|
+
fn name(&self) -> &'static str {
|
|
151
|
+
"reverse_reference"
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
fn discover(&self, ctx: &DiscoveryContext) -> Vec<PathBuf> {
|
|
155
|
+
let changed_set: FxHashSet<&Path> = ctx.changed_files.iter().map(|p| p.as_path()).collect();
|
|
156
|
+
// Per changed file: the module name a reference would import it by,
|
|
157
|
+
// and the names it defines. A file that carries both is a reference
|
|
158
|
+
// however common the name; one that carries the name alone counts
|
|
159
|
+
// only while the name is rare enough to mean this definition.
|
|
160
|
+
let mut per_file: Vec<(Option<String>, FxHashSet<String>)> = Vec::new();
|
|
161
|
+
for path in &ctx.changed_files {
|
|
162
|
+
let stem = path
|
|
163
|
+
.file_stem()
|
|
164
|
+
.and_then(|s| s.to_str())
|
|
165
|
+
.filter(|s| s.len() >= MIN_REVERSE_NAME_LEN)
|
|
166
|
+
.map(|s| s.to_lowercase());
|
|
167
|
+
let names: FxHashSet<String> = ctx
|
|
168
|
+
.read_file(path)
|
|
169
|
+
.map(|c| {
|
|
170
|
+
definition_names(&c)
|
|
171
|
+
.into_iter()
|
|
172
|
+
.map(|n| n.to_lowercase())
|
|
173
|
+
.collect()
|
|
174
|
+
})
|
|
175
|
+
.unwrap_or_default();
|
|
176
|
+
if stem.is_some() || !names.is_empty() {
|
|
177
|
+
per_file.push((stem, names));
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
if per_file.is_empty() {
|
|
181
|
+
return Vec::new();
|
|
182
|
+
}
|
|
183
|
+
let mut name_holders: FxHashMap<&str, usize> = FxHashMap::default();
|
|
184
|
+
let mut strong: Vec<&PathBuf> = Vec::new();
|
|
185
|
+
let mut weak: Vec<(&PathBuf, Vec<&str>)> = Vec::new();
|
|
186
|
+
for (path, doc) in &ctx.shared_corpus().docs {
|
|
187
|
+
if changed_set.contains(path.as_path()) {
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
let mut is_strong = false;
|
|
191
|
+
let mut named: Vec<&str> = Vec::new();
|
|
192
|
+
for (stem, names) in &per_file {
|
|
193
|
+
let has_stem = stem
|
|
194
|
+
.as_deref()
|
|
195
|
+
.is_some_and(|s| doc.term_counts.contains_key(s));
|
|
196
|
+
for name in names {
|
|
197
|
+
if doc.term_counts.contains_key(name) {
|
|
198
|
+
*name_holders.entry(name.as_str()).or_default() += 1;
|
|
199
|
+
named.push(name.as_str());
|
|
200
|
+
if has_stem {
|
|
201
|
+
is_strong = true;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
if is_strong {
|
|
207
|
+
strong.push(path);
|
|
208
|
+
} else if !named.is_empty() {
|
|
209
|
+
weak.push((path, named));
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
strong.sort();
|
|
213
|
+
tracing::debug!(
|
|
214
|
+
"reverse references: {} definition sets, {} strong, {} weak, corpus {}",
|
|
215
|
+
per_file.len(),
|
|
216
|
+
strong.len(),
|
|
217
|
+
weak.len(),
|
|
218
|
+
ctx.shared_corpus().docs.len()
|
|
219
|
+
);
|
|
220
|
+
let mut result: Vec<PathBuf> = Vec::new();
|
|
221
|
+
let mut seen: FxHashSet<&PathBuf> = FxHashSet::default();
|
|
222
|
+
for f in strong {
|
|
223
|
+
if result.len() >= MAX_REVERSE_FILES {
|
|
224
|
+
return result;
|
|
225
|
+
}
|
|
226
|
+
if seen.insert(f) {
|
|
227
|
+
result.push(f.clone());
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
weak.sort_by(|a, b| a.0.cmp(b.0));
|
|
231
|
+
for (f, named) in weak {
|
|
232
|
+
if result.len() >= MAX_REVERSE_FILES {
|
|
233
|
+
break;
|
|
234
|
+
}
|
|
235
|
+
let rare = named
|
|
236
|
+
.iter()
|
|
237
|
+
.any(|n| name_holders.get(n).copied().unwrap_or(0) <= MAX_REVERSE_FILES_PER_NAME);
|
|
238
|
+
if rare && seen.insert(f) {
|
|
239
|
+
result.push(f.clone());
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
result
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
120
246
|
pub struct TestFileDiscovery;
|
|
121
247
|
|
|
122
248
|
const TEST_PREFIXES: &[&str] = &["test_", "spec_"];
|
|
@@ -196,6 +196,7 @@ pub const NON_SEMANTIC_ENV: &[&str] = &[
|
|
|
196
196
|
"DIFFCTX_ALLOWED_PATHS",
|
|
197
197
|
"DIFFCTX_BENCH_TIMEOUT_SEC",
|
|
198
198
|
"DIFFCTX_BUILD_SHA",
|
|
199
|
+
"DIFFCTX_CACHE_DIR",
|
|
199
200
|
"DIFFCTX_CONFIG_DIR",
|
|
200
201
|
"DIFFCTX_DIR_IGNORE",
|
|
201
202
|
"DIFFCTX_DIR_WHITELIST",
|
|
@@ -204,6 +205,7 @@ pub const NON_SEMANTIC_ENV: &[&str] = &[
|
|
|
204
205
|
"DIFFCTX_EVAL_STRICT",
|
|
205
206
|
"DIFFCTX_IGNORE_RELPATH",
|
|
206
207
|
"DIFFCTX_MCP_LEGACY_TOOLS",
|
|
208
|
+
"DIFFCTX_NO_MARKER",
|
|
207
209
|
"DIFFCTX_PROBE_MODE",
|
|
208
210
|
"DIFFCTX_PROBE_TAU",
|
|
209
211
|
"DIFFCTX_PROVENANCE",
|
|
@@ -426,6 +426,39 @@ impl Graph {
|
|
|
426
426
|
}
|
|
427
427
|
}
|
|
428
428
|
|
|
429
|
+
/// Invoke `f(source_id, weight)` for each node with an edge INTO `node`,
|
|
430
|
+
/// with the weight of that incoming edge.
|
|
431
|
+
pub fn for_each_reverse_neighbor<F: FnMut(&FragmentId, f64)>(
|
|
432
|
+
&self,
|
|
433
|
+
node: &FragmentId,
|
|
434
|
+
mut f: F,
|
|
435
|
+
) {
|
|
436
|
+
let rev = match self.rev_csr() {
|
|
437
|
+
Some(c) => c,
|
|
438
|
+
None => return,
|
|
439
|
+
};
|
|
440
|
+
let idx = match rev.node_to_idx.get(node) {
|
|
441
|
+
Some(&i) => i as usize,
|
|
442
|
+
None => return,
|
|
443
|
+
};
|
|
444
|
+
let s = rev.indptr[idx] as usize;
|
|
445
|
+
let e = rev.indptr[idx + 1] as usize;
|
|
446
|
+
for k in s..e {
|
|
447
|
+
let src_idx = rev.indices[k] as usize;
|
|
448
|
+
f(&rev.idx_to_node[src_idx], rev.weights[k]);
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
/// Whether `a -> b` is the stronger direction of the pair. Every builder
|
|
453
|
+
/// writes the reverse of an edge at `weight * reverse_factor` with the
|
|
454
|
+
/// factor below one, so the comparison recovers the direction the builder
|
|
455
|
+
/// meant (caller -> callee, importer -> imported) without a second table.
|
|
456
|
+
pub fn is_forward(&self, a: &FragmentId, b: &FragmentId) -> bool {
|
|
457
|
+
let ab = self.forward_edge_weight(a, b).unwrap_or(0.0);
|
|
458
|
+
let ba = self.forward_edge_weight(b, a).unwrap_or(0.0);
|
|
459
|
+
ab > ba
|
|
460
|
+
}
|
|
461
|
+
|
|
429
462
|
pub fn ego_graph(
|
|
430
463
|
&self,
|
|
431
464
|
seeds: &FxHashSet<FragmentId>,
|