diffctx 1.18.4__tar.gz → 1.19.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.18.4 → diffctx-1.19.0}/CHANGELOG.md +263 -1
- {diffctx-1.18.4 → diffctx-1.19.0}/Cargo.lock +14 -3
- {diffctx-1.18.4 → diffctx-1.19.0}/PKG-INFO +2 -2
- {diffctx-1.18.4 → diffctx-1.19.0}/README.md +1 -1
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/Cargo.toml +10 -1
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/README.md +1 -1
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/bindings.rs +17 -18
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/candidate_files.rs +15 -7
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/discovery.rs +7 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/edges/base.rs +10 -1
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/edges/config_edges/helm.rs +7 -1
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/edges/mod.rs +7 -3
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/edges/semantic/jvm.rs +123 -2
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/edges/structural/testing.rs +27 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/effective_config.rs +1 -0
- diffctx-1.19.0/crates/diffctx-native/src/facts/kotlin.rs +1285 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/facts/mod.rs +1 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/git.rs +158 -94
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/graph.rs +6 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/hook.rs +607 -653
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/impact.rs +1246 -492
- diffctx-1.19.0/crates/diffctx-native/src/kotlin_bindings.rs +1054 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/lib.rs +1 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/locate.rs +10 -16
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/main.rs +26 -16
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/parsers/mod.rs +41 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/parsers/tree_sitter_strategy.rs +524 -19
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/pipeline.rs +151 -43
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/pybridge.rs +13 -7
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/resource.rs +80 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/source.rs +79 -28
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/token_corpus.rs +3 -1
- {diffctx-1.18.4 → diffctx-1.19.0}/pyproject.toml +1 -1
- {diffctx-1.18.4 → diffctx-1.19.0}/src/diffctx/_app.py +6 -1
- {diffctx-1.18.4 → diffctx-1.19.0}/src/diffctx/_diffctx.pyi +1 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/src/diffctx/_native/pipeline.py +2 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/src/diffctx/cli.py +2 -1
- {diffctx-1.18.4 → diffctx-1.19.0}/src/diffctx/mcp/security.py +11 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/src/diffctx/mcp/server.py +17 -7
- diffctx-1.19.0/src/diffctx/version.py +1 -0
- diffctx-1.18.4/src/diffctx/version.py +0 -1
- {diffctx-1.18.4 → diffctx-1.19.0}/Cargo.toml +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/LICENSE +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/analytics.rs +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/change_class.rs +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/config/analytics.rs +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/config/bm25.rs +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/config/budget.rs +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/config/category_weights.rs +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/config/edge_weights.rs +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/config/env_overrides.rs +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/config/extensions.rs +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/config/filtering.rs +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/config/fragmentation.rs +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/config/git.rs +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/config/graph_filtering.rs +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/config/importance.rs +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/config/limits.rs +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/config/mod.rs +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/config/mode.rs +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/config/needs.rs +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/config/parsers.rs +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/config/render.rs +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/config/scoring.rs +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/config/selection.rs +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/config/tokenization.rs +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/config/weights.rs +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/container.rs +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/core.rs +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/edges/config_edges/build_system.rs +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/edges/config_edges/cicd.rs +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/edges/config_edges/docker.rs +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/edges/config_edges/generic.rs +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/edges/config_edges/kubernetes.rs +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/edges/config_edges/mod.rs +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/edges/document/mod.rs +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/edges/history/cochange.rs +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/edges/history/mod.rs +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/edges/semantic/ansible.rs +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/edges/semantic/bazel.rs +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/edges/semantic/c_family.rs +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/edges/semantic/cargo_edges.rs +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/edges/semantic/clojure.rs +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/edges/semantic/css.rs +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/edges/semantic/dart.rs +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/edges/semantic/dbt.rs +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/edges/semantic/dotnet.rs +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/edges/semantic/elixir.rs +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/edges/semantic/erlang.rs +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/edges/semantic/go.rs +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/edges/semantic/graphql.rs +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/edges/semantic/haskell.rs +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/edges/semantic/javascript.rs +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/edges/semantic/julia.rs +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/edges/semantic/latex.rs +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/edges/semantic/lua.rs +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/edges/semantic/mod.rs +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/edges/semantic/nim.rs +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/edges/semantic/nix.rs +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/edges/semantic/ocaml.rs +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/edges/semantic/openapi.rs +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/edges/semantic/perl.rs +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/edges/semantic/php.rs +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/edges/semantic/prisma.rs +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/edges/semantic/protobuf.rs +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/edges/semantic/python.rs +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/edges/semantic/r_lang.rs +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/edges/semantic/ruby.rs +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/edges/semantic/rust_lang.rs +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/edges/semantic/shell.rs +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/edges/semantic/sql.rs +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/edges/semantic/swift.rs +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/edges/semantic/tags.rs +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/edges/semantic/terraform.rs +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/edges/semantic/zig.rs +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/edges/similarity/lexical.rs +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/edges/similarity/mod.rs +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/edges/structural/containment.rs +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/edges/structural/mod.rs +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/edges/structural/sibling.rs +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/excerpt.rs +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/facts/scala.rs +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/filtering.rs +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/fragmentation.rs +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/graph_export.rs +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/in_memory_harness.rs +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/interval.rs +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/languages.rs +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/mode.rs +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/parsers/config_parser.rs +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/parsers/generic.rs +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/parsers/markdown.rs +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/paths.rs +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/peak_rss.rs +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/postpass.rs +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/ppr.rs +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/project_graph.rs +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/provenance.rs +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/render.rs +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/run_provenance.rs +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/sanitize.rs +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/scoring.rs +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/select.rs +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/signatures.rs +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/stopwords/code.txt +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/stopwords/docs.txt +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/stopwords.rs +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/test_rng.rs +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/testfiles.rs +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/tokenizer.rs +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/types.rs +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/utility/boltzmann.rs +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/utility/importance.rs +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/utility/mod.rs +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/utility/needs.rs +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/crates/diffctx-native/src/utility/objective.rs +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/rust-toolchain.toml +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/src/diffctx/__init__.py +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/src/diffctx/__main__.py +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/src/diffctx/_native/__init__.py +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/src/diffctx/_native/graph_analytics.py +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/src/diffctx/_native/graph_export.py +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/src/diffctx/_native/project_graph.py +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/src/diffctx/clipboard.py +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/src/diffctx/ignore.py +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/src/diffctx/logger.py +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/src/diffctx/mcp/__init__.py +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/src/diffctx/mcp/__main__.py +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/src/diffctx/mcp/fetch.py +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/src/diffctx/mcp/launch.py +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/src/diffctx/mcp/legacy.py +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/src/diffctx/py.typed +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/src/diffctx/tokens.py +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/src/diffctx/tree.py +0 -0
- {diffctx-1.18.4 → diffctx-1.19.0}/src/diffctx/writer.py +0 -0
|
@@ -7,6 +7,267 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [1.19.0] - 2026-10-14
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- Kotlin (`.kt`, `.kts`) parses with tree-sitter, through
|
|
15
|
+
`brokk-tree-sitter-kotlin` 0.4.6 — the fwcd grammar as its maintained fork
|
|
16
|
+
publishes it, since fwcd's own crate still pins tree-sitter < 0.23. On
|
|
17
|
+
1,036 real files it lost no declaration and broke on none;
|
|
18
|
+
`tree-sitter-kotlin-sg` 0.4.1 silently dropped seven classes (a bodyless
|
|
19
|
+
annotated class swallowed the next one) and `tree-sitter-kotlin-ng` broke
|
|
20
|
+
whole classes on a `get(...)` call. Classes, interfaces, objects,
|
|
21
|
+
companions (`Companion` when unnamed), functions, extension functions,
|
|
22
|
+
secondary constructors (named after their class), properties (a property
|
|
23
|
+
spans its getter and setter; a local `val` is not a member) and type
|
|
24
|
+
aliases become fragments (#303). This reverses the 1.7.0 removal, which
|
|
25
|
+
dropped `.kt` read through the Java grammar, not a Kotlin one.
|
|
26
|
+
- Kotlin has a binding model, read off shared syntax facts (one parse per
|
|
27
|
+
blob, shared by the fragmenter, `--symbol` and impact): a use binds through
|
|
28
|
+
its import or alias, the declared package (not the directory), the
|
|
29
|
+
receiver's declared type — a local, a parameter, a property of the class
|
|
30
|
+
or of a source-visible base class, a constructor or a function with a
|
|
31
|
+
written return type, called plainly or on a typed name
|
|
32
|
+
(`val x = repo.find(id)`) — an object or companion qualifier, or the
|
|
33
|
+
implicit `this`, which in an `inner class` reaches the outer instance too.
|
|
34
|
+
What the syntax cannot settle stays a candidate with its reason
|
|
35
|
+
(`receiver_unresolved`, `overloaded`, `wildcard_import`, `dispatch`,
|
|
36
|
+
`project_dependency_unknown`); `internal` stops at the project, which its
|
|
37
|
+
test source set shares. Callable references (`Obj::f`) are references,
|
|
38
|
+
implementations of a changed abstract member are their own relation, a
|
|
39
|
+
removed declaration is found by comparing the file's declarations at the
|
|
40
|
+
base and the head, and a public contract changes when the header does —
|
|
41
|
+
parameter defaults and all. In the context graph a Kotlin member use links
|
|
42
|
+
only to the definition it resolves to: a candidate suggests, it does not
|
|
43
|
+
link.
|
|
44
|
+
|
|
45
|
+
### Security
|
|
46
|
+
|
|
47
|
+
- Impact and `--symbol` no longer name a withheld file that refers to the
|
|
48
|
+
change. 1.18.4's fix covered the changed side only: a file under
|
|
49
|
+
`.diffctx/ignore` or with a secret name that called a removed definition or
|
|
50
|
+
named a deleted path still appeared as a caller or a stale reference. The
|
|
51
|
+
withhold policy now applies inside the `git grep` readers, so every surface
|
|
52
|
+
gets only admissible files, and the files a read withheld count toward
|
|
53
|
+
`withheld_files`. The header says "N file(s) withheld by ignore rules"
|
|
54
|
+
rather than "N more", since they need not be changed files.
|
|
55
|
+
|
|
56
|
+
### Fixed
|
|
57
|
+
|
|
58
|
+
- **`A..B` is what `B` changed since it forked from `A`**, in every mode and
|
|
59
|
+
at every entry point (CLI, MCP, the hook, the Action): the range resolves
|
|
60
|
+
once to the merge base and `B`'s commit. Read as two trees, a base that
|
|
61
|
+
moved on after the fork put its own later commits into the change as
|
|
62
|
+
reverts — a PR's target branch in the Action (#485), a remote-tracking
|
|
63
|
+
ref a `pull` on the same line had just moved in the hook (#462, which now
|
|
64
|
+
also measures a push from where the push itself found the ref). The
|
|
65
|
+
Action deepens a shallow checkout until the fork point is known, and says
|
|
66
|
+
so when it is not. Histories with no common commit keep git's two-tree
|
|
67
|
+
reading.
|
|
68
|
+
- A range is answered from its snapshot alone. The Helm chart root was
|
|
69
|
+
probed on the disk, so a working tree without the range's `Chart.yaml`
|
|
70
|
+
dropped the chart's templates from pack and locate (#408). A snapshot
|
|
71
|
+
read that failed — out of time, cancelled, a broken stream — read as an
|
|
72
|
+
absent file and now names its limit; a snapshot listing that failed no
|
|
73
|
+
longer hands discovery or the token cache the working tree's files in its
|
|
74
|
+
place (#443).
|
|
75
|
+
- The changed-file count of uncommitted work is what git would commit.
|
|
76
|
+
Untracked files are analysed as before but named apart (`untracked_files`,
|
|
77
|
+
"2 changed file(s), 1 untracked"), and an untracked binary — a local
|
|
78
|
+
database, a build artefact — is no source change at all; two `.db` files
|
|
79
|
+
and a notes file read as "5 changed file(s)" for a 2-file edit (#418,
|
|
80
|
+
#423). A search for a name defined only in an untracked file is answered
|
|
81
|
+
with its callers, as the inspection that follows already was (#448).
|
|
82
|
+
- `--full A..B` listed a file the range deleted, or renamed away, as
|
|
83
|
+
changed and showed its old body read from `A`. One rule now names what a
|
|
84
|
+
range's head no longer holds, for every mode; staged pack, `--full` and
|
|
85
|
+
locate are pinned to read the index alone, renames and mode changes
|
|
86
|
+
included (#409).
|
|
87
|
+
- A one-second impact run ends near its second: the loops over input-sized
|
|
88
|
+
lists (stale-reference lines, unanalysed and namer files, test edges)
|
|
89
|
+
iterate through the deadline instead of polling it by hand, and the
|
|
90
|
+
stale-reference loop, which had no poll, ran such a run to 4.5 s (now
|
|
91
|
+
1.04 s, #510). A deadline that loop hit is named in `limits`; it was read
|
|
92
|
+
before the loop ran, so a run that had dropped its stale references
|
|
93
|
+
looked complete. `DIFFCTX_TEST_DEADLINE_AFTER_POLLS=n` stops a run at its
|
|
94
|
+
`n`th poll, so tests stop it at every point without racing the clock,
|
|
95
|
+
and a corpus case its deadline stopped says so instead of failing on a
|
|
96
|
+
score (#390).
|
|
97
|
+
- A call inside an f-string replacement field (`f"{fence(text)}"`) or a
|
|
98
|
+
template-literal substitution (`` `${formatBytes(n)}` ``) is a caller. The
|
|
99
|
+
hand lexer that impact and the binding resolver read code through
|
|
100
|
+
blanked every quoted byte, interpolations included, so both read
|
|
101
|
+
"0 callers" (#460, #475). Comments and string literals now come off the
|
|
102
|
+
file's grammar, interpolations kept, in every language with one; the
|
|
103
|
+
lexer and its special cases (`//` as floor division, a Rust lifetime,
|
|
104
|
+
`*` block-comment lines) are gone (#506).
|
|
105
|
+
- A removed definition is one the file declared at the base and does not
|
|
106
|
+
declare at the head, both read off the grammar. Read off removed lines by
|
|
107
|
+
regex, a Rust tail expression `Err("zero")` became a removed member named
|
|
108
|
+
`Err` with every `Err(` in the crate as its callers, a bare `int(text)` in
|
|
109
|
+
a Python method a removed `int`, and a private function next to an
|
|
110
|
+
`impl` a removed API (#515, #458). A member is as public as the class or
|
|
111
|
+
interface it sits in, by nesting, not by the nearest class above it.
|
|
112
|
+
- A caller of a removed definition is code that can still reach it:
|
|
113
|
+
through an import the binding model resolves to the file it left (a
|
|
114
|
+
removed member through its imported class), else from the same runtime.
|
|
115
|
+
By name alone, `subprocess.run`, another file's own `run`, a shell `die`
|
|
116
|
+
and a TypeScript `test.describe` were callers of a deleted Python script's
|
|
117
|
+
functions, and the import line was a second caller (#470, #471, #458,
|
|
118
|
+
#450). Files the range deleted resolve as files an import still names.
|
|
119
|
+
- A dependency bump is a change: impact names the lock files a range
|
|
120
|
+
changes (`lockfile_changes`) and never answers "No changes to analyse" for
|
|
121
|
+
them (#495).
|
|
122
|
+
- The hook's search pre-filter in the plugin script read the session's
|
|
123
|
+
cwd, not the Grep's own `path`, and ignored untracked files, so a search
|
|
124
|
+
over a pending change elsewhere never reached the binary. `bash -lc
|
|
125
|
+
"git commit …"` and `$(git -C r commit)` are named as unread rather than
|
|
126
|
+
logged as no trigger; reminders and gate denials are logged as such. A
|
|
127
|
+
cache directory the hook cannot write falls back to the temporary
|
|
128
|
+
directory instead of silently rerunning every preview (#407).
|
|
129
|
+
- Before a commit or push the hook says what it did when it has no impact
|
|
130
|
+
to show: one line when the change reaches nothing outside the diff, and
|
|
131
|
+
one when it could not read the command (a git inside a subshell, a `cd`
|
|
132
|
+
it cannot resolve), with the command to run instead. Silence then means
|
|
133
|
+
the hook did not run, not a clean review; the hook log keeps tens of
|
|
134
|
+
thousands of runs so an audit reads a whole session (#464).
|
|
135
|
+
- The MCP tool timed out at 300 s on a monorepo range the CLI answered in
|
|
136
|
+
four seconds (#457), for two reasons. It widened a subdirectory
|
|
137
|
+
`repo_path` to the whole repository; it now analyses the directory asked
|
|
138
|
+
about, as the CLI does with the same path. And impact grepped every
|
|
139
|
+
removed name as a fixed string in every file, so a deleted `call` matched
|
|
140
|
+
each `callback` in the repository's data and the run never stopped
|
|
141
|
+
(#413): the grep now reads whole words in code files only, at most 20
|
|
142
|
+
lines a file, and a name on more than 500 lines is answered "too common to
|
|
143
|
+
trace by name" instead of listed.
|
|
144
|
+
- Impact is fitted to its cap on the text the reader receives: markdown was
|
|
145
|
+
shed against the size of its JSON, so a 148-token answer cited a
|
|
146
|
+
2000-token cap and lost its contract block; an omitted count printed
|
|
147
|
+
under the previous list's heading (#451). `--budget` (CLI) and
|
|
148
|
+
`budget_tokens` (MCP) set the cap, which stayed 2000 whatever was asked,
|
|
149
|
+
and a shed answer says which flag lists the rest (#466).
|
|
150
|
+
- The MCP tool's refusal of `symbol` together with `diff_ref` or
|
|
151
|
+
`fragment_ids` names the call that answers: drop them and ask in
|
|
152
|
+
`mode="impact"`, which looks the name up in the working tree (#499).
|
|
153
|
+
- Impact no longer reads "not examined" as "nothing there". A failed `git
|
|
154
|
+
grep` or file listing marks the run partial (`read_failed`) instead of
|
|
155
|
+
dropping the removed definition's callers and the stale references with
|
|
156
|
+
exit 0; a failed snapshot listing no longer falls back to the working
|
|
157
|
+
tree. A changed code file over the size cap or unreadable is named in
|
|
158
|
+
`unanalysed_files` (it answered `"empty": true` while pack said
|
|
159
|
+
`file_too_large`). Limits follow pack's rule, so the contribution and
|
|
160
|
+
total-byte caps, which drop callers, are named; the per-node edge cap
|
|
161
|
+
every repository trips still is not. A caller whose edge that cap cut
|
|
162
|
+
faces the same tests an edge would have and stays a caller (`--symbol`
|
|
163
|
+
answered "0 callers" for a function its route handler calls). A
|
|
164
|
+
definition changed only in comments, formatting or position is listed
|
|
165
|
+
in `layout_only` and the answer says its callers were not traced, where
|
|
166
|
+
it said "No resolved static callers"; the hook's search answer says the
|
|
167
|
+
same instead of claiming none. Under a limit, "nothing calls them" reads
|
|
168
|
+
"no caller found in this partial run". Unparsed files that name a
|
|
169
|
+
camelCase definition are disclosed: identifiers are stored lowercased,
|
|
170
|
+
and the lookup compared them as written.
|
|
171
|
+
- Impact no longer calls a definition whose only callers are inside the diff
|
|
172
|
+
one that "nothing calls": a helper extracted from a function the same
|
|
173
|
+
commit changes read as dead code, on 62–75% of that list across three
|
|
174
|
+
ranges of this repository. Such definitions are listed under "Changed,
|
|
175
|
+
called only from inside the diff, no static test link" and in the new
|
|
176
|
+
`untested_called_in_diff` field; an in-diff source counts when it passes
|
|
177
|
+
the tests any caller passes. 1.18.3 never printed the clause, so 1.18.4
|
|
178
|
+
added the false statement (#441).
|
|
179
|
+
- A control statement removed from a method body is no removed member:
|
|
180
|
+
`match (a, b) {` in a Rust `impl` read as a removed API `match`, and
|
|
181
|
+
every file spelling the keyword became its caller. `when`, `try`,
|
|
182
|
+
`synchronized`, `using`, `lock`, `fixed`, `foreach` and `elseif` are
|
|
183
|
+
filtered with it (#492).
|
|
184
|
+
- Impact looks a fragment id up as the fragment, not as the signature
|
|
185
|
+
variant that shares its id when the signature spans the whole fragment: a
|
|
186
|
+
Kotlin class whose header ends in `) {` answered `--symbol` with "0
|
|
187
|
+
definition(s)".
|
|
188
|
+
- The JVM builder links an owner-qualified short member (`UI.DP`) to its
|
|
189
|
+
declaration. Names under three characters never linked by name, so a
|
|
190
|
+
constant read through its object was missed.
|
|
191
|
+
- A JVM framework annotation registers what it annotates and nothing else:
|
|
192
|
+
a class-level `@Component` or `@Service` no longer marks every method a
|
|
193
|
+
registered entry point (which hid its missing test link); method-level
|
|
194
|
+
entry points (`@GetMapping`, `@Scheduled`, `@Bean`, …) count only when the
|
|
195
|
+
file imports them from a framework package.
|
|
196
|
+
- Impact names a caller by its whole definition, not by the signature
|
|
197
|
+
variant the graph linked: a use in the body — a constructor call after a
|
|
198
|
+
long parameter list — was dropped from the caller's sites.
|
|
199
|
+
- A name shared across runtimes no runtime crosses is neither a caller nor
|
|
200
|
+
a test link: a Kotlin test no longer reads as guarding a Swift function of
|
|
201
|
+
the same name, and a Kotlin or Swift spelling of a Python or TypeScript
|
|
202
|
+
name is not its caller. JVM languages still call each other, JavaScript
|
|
203
|
+
its HTML and components, Swift and Objective-C their C (#487, #458).
|
|
204
|
+
- A changed symbol whose name is spelled in a file in view that did not
|
|
205
|
+
parse cleanly lists that file (`missed_in`, "callers may be missing")
|
|
206
|
+
instead of reading as one nothing calls (#454).
|
|
207
|
+
- JVM tests pair with their subject the way JVM projects name them:
|
|
208
|
+
`FooTest`, `FooTests`, `FooSpec` and `FooIT` beside `Foo` are discovered
|
|
209
|
+
and linked in both directions, and `src/<set>/<java|kotlin|scala|groovy>/`
|
|
210
|
+
is a source root, so a test importing `com.acme.Foo` reaches
|
|
211
|
+
`src/main/kotlin/com/acme/Foo.kt`. A related test file is not a guard: a
|
|
212
|
+
method its tests never call keeps "no static test link".
|
|
213
|
+
- `--symbol Owner.name` reads Kotlin's owners and receivers from the syntax:
|
|
214
|
+
`String.pad` names the extension, `Worker.Companion.create` the companion
|
|
215
|
+
member; and the declaration search that keeps a capped query's definition
|
|
216
|
+
file knows `fun` and `typealias`.
|
|
217
|
+
- A Kotlin type the text declares at the start of a line but the parse did
|
|
218
|
+
not read — a grammar's silent misreading, with no error node — marks the
|
|
219
|
+
file analysed only in part (0 of yay-tsa's 1,036 files with the shipped
|
|
220
|
+
grammar; local classes count as read).
|
|
221
|
+
- The parse outcome is recorded when the fragmenter parses a file, not
|
|
222
|
+
inferred from the fragments that came out: a file that parsed with errors
|
|
223
|
+
is reported as analysed only in part even when a definition survived, and
|
|
224
|
+
a timed-out parse is named as such.
|
|
225
|
+
|
|
226
|
+
- Impact names the changed code files it could not read for definitions —
|
|
227
|
+
no grammar for the language in this build, or a parse that broke — in its
|
|
228
|
+
header and in `unanalysed_files`, and never calls such a change empty: a
|
|
229
|
+
Kotlin-only commit read "0 callers outside the diff" and the commit hook
|
|
230
|
+
stayed silent (#454). A script that parses cleanly and defines nothing is
|
|
231
|
+
not listed.
|
|
232
|
+
- `--symbol` no longer answers only "no definition found" when the files
|
|
233
|
+
naming the symbol were read as text: it names them and the reason, so an
|
|
234
|
+
unparsed definition is not mistaken for a missing one (#454).
|
|
235
|
+
- Locate's `unparsed_files` uses the same test: code with no grammar in this
|
|
236
|
+
build (Kotlin, F#, Fortran) or a broken parse is listed and lowers
|
|
237
|
+
confidence; a script that parses cleanly and defines nothing (a
|
|
238
|
+
`build.gradle.kts`, a Python script of top-level statements) no longer is.
|
|
239
|
+
Data files (`.sql`, `.toml`, `.txt`) stay unlisted (#330).
|
|
240
|
+
|
|
241
|
+
### Changed
|
|
242
|
+
|
|
243
|
+
- **Plugin hook: a commit, merge, cherry-pick, revert or push is reviewed
|
|
244
|
+
from what git recorded, once the line ran** — read off the reflog (`HEAD`'s
|
|
245
|
+
last entry, a remote-tracking ref's "update by push"), never predicted
|
|
246
|
+
from the command text. The prediction took fifteen parser commits in
|
|
247
|
+
nine days and still reviewed another session's staged file for
|
|
248
|
+
`git commit --pathspec-from-file` instead of the paths the commit took
|
|
249
|
+
(#508). A loop, `xargs`, `git apply --cached`, `git --git-dir … commit` or
|
|
250
|
+
an escaped quote now stage what they stage, and the review says what the
|
|
251
|
+
commit took (#405, #436). A commit of a change already shown gets a
|
|
252
|
+
one-line pointer instead of silence (#501); a push of several refs
|
|
253
|
+
reviews the first and counts the rest; a `--squash` merge is reviewed by
|
|
254
|
+
the commit that records it. `L=/abs; cd $L && git commit` follows the
|
|
255
|
+
variable the line sets (#422). The opt-in strict gate still reviews
|
|
256
|
+
before git runs, and only what needs no prediction: the index a plain
|
|
257
|
+
`git commit` records; a commit line that stages for itself is denied with
|
|
258
|
+
the instruction to stage as its own command. Without the gate the impact
|
|
259
|
+
now arrives with the commit's result, which is when Claude Code delivered
|
|
260
|
+
the old preview too (#429).
|
|
261
|
+
- Claude plugin: the MCP server and the hooks launch `uvx diffctx[mcp]==X`
|
|
262
|
+
with no other option, and `plugin/uv.lock` pins the release's whole
|
|
263
|
+
dependency tree with PyPI hashes, the shape the plugin directory reads as a
|
|
264
|
+
locked launch ("Launcher lock invalid" before). `constraints.txt` is gone:
|
|
265
|
+
uv resolves diffctx's dependencies within their declared ranges, as every
|
|
266
|
+
other uvx install already did.
|
|
267
|
+
- CI and the pre-commit hook run the `impact`, `hook`, `bindings`, `staged`,
|
|
268
|
+
`git_faults`, `in_memory` and `context_schema` test targets, which no gate
|
|
269
|
+
ran before.
|
|
270
|
+
|
|
10
271
|
## [1.18.4] - 2026-10-07
|
|
11
272
|
|
|
12
273
|
### Security
|
|
@@ -2038,7 +2299,8 @@ Earlier releases shipped as `treemapper`; see
|
|
|
2038
2299
|
<https://github.com/nikolay-e/diffctx/releases> for the corresponding GitHub
|
|
2039
2300
|
release notes (`1.0.0` through `1.6.1`).
|
|
2040
2301
|
|
|
2041
|
-
[Unreleased]: https://github.com/nikolay-e/diffctx/compare/v1.
|
|
2302
|
+
[Unreleased]: https://github.com/nikolay-e/diffctx/compare/v1.19.0...HEAD
|
|
2303
|
+
[1.19.0]: https://github.com/nikolay-e/diffctx/compare/v1.18.4...v1.19.0
|
|
2042
2304
|
[1.18.4]: https://github.com/nikolay-e/diffctx/compare/v1.18.3...v1.18.4
|
|
2043
2305
|
[1.18.3]: https://github.com/nikolay-e/diffctx/compare/v1.18.2...v1.18.3
|
|
2044
2306
|
[1.18.2]: https://github.com/nikolay-e/diffctx/compare/v1.18.1...v1.18.2
|
|
@@ -94,6 +94,16 @@ version = "2.11.1"
|
|
|
94
94
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
95
95
|
checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3"
|
|
96
96
|
|
|
97
|
+
[[package]]
|
|
98
|
+
name = "brokk-tree-sitter-kotlin"
|
|
99
|
+
version = "0.4.6"
|
|
100
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
101
|
+
checksum = "11536d4605247354ad8992b7ae605e85508c07d64ed0710ff8494558660e40d0"
|
|
102
|
+
dependencies = [
|
|
103
|
+
"cc",
|
|
104
|
+
"tree-sitter-language",
|
|
105
|
+
]
|
|
106
|
+
|
|
97
107
|
[[package]]
|
|
98
108
|
name = "bstr"
|
|
99
109
|
version = "1.13.1"
|
|
@@ -107,9 +117,9 @@ dependencies = [
|
|
|
107
117
|
|
|
108
118
|
[[package]]
|
|
109
119
|
name = "cc"
|
|
110
|
-
version = "1.2.
|
|
120
|
+
version = "1.2.56"
|
|
111
121
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
112
|
-
checksum = "
|
|
122
|
+
checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2"
|
|
113
123
|
dependencies = [
|
|
114
124
|
"find-msvc-tools",
|
|
115
125
|
"shlex",
|
|
@@ -194,10 +204,11 @@ checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
|
|
|
194
204
|
|
|
195
205
|
[[package]]
|
|
196
206
|
name = "diffctx"
|
|
197
|
-
version = "1.
|
|
207
|
+
version = "1.19.0"
|
|
198
208
|
dependencies = [
|
|
199
209
|
"aho-corasick",
|
|
200
210
|
"anyhow",
|
|
211
|
+
"brokk-tree-sitter-kotlin",
|
|
201
212
|
"clap",
|
|
202
213
|
"dunce",
|
|
203
214
|
"glob",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: diffctx
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.19.0
|
|
4
4
|
Classifier: Development Status :: 5 - Production/Stable
|
|
5
5
|
Classifier: Environment :: Console
|
|
6
6
|
Classifier: Intended Audience :: Developers
|
|
@@ -77,7 +77,7 @@ zero.
|
|
|
77
77
|
## Install
|
|
78
78
|
|
|
79
79
|
```bash
|
|
80
|
-
# Claude Code: the MCP server, /diffctx:impact, and hooks that run it
|
|
80
|
+
# Claude Code: the MCP server, /diffctx:impact, and hooks that run it on each commit and push
|
|
81
81
|
claude plugin marketplace add nikolay-e/diffctx
|
|
82
82
|
claude plugin install diffctx@diffctx
|
|
83
83
|
|
|
@@ -18,7 +18,7 @@ zero.
|
|
|
18
18
|
## Install
|
|
19
19
|
|
|
20
20
|
```bash
|
|
21
|
-
# Claude Code: the MCP server, /diffctx:impact, and hooks that run it
|
|
21
|
+
# Claude Code: the MCP server, /diffctx:impact, and hooks that run it on each commit and push
|
|
22
22
|
claude plugin marketplace add nikolay-e/diffctx
|
|
23
23
|
claude plugin install diffctx@diffctx
|
|
24
24
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[package]
|
|
2
2
|
name = "diffctx"
|
|
3
|
-
version = "1.
|
|
3
|
+
version = "1.19.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"
|
|
@@ -66,6 +66,7 @@ lang-extra = [
|
|
|
66
66
|
"tree-sitter-svelte-ng",
|
|
67
67
|
"tree-sitter-scala",
|
|
68
68
|
"tree-sitter-swift",
|
|
69
|
+
"brokk-tree-sitter-kotlin",
|
|
69
70
|
]
|
|
70
71
|
|
|
71
72
|
[dependencies]
|
|
@@ -86,6 +87,14 @@ tree-sitter-ruby = { version = "0.23", optional = true }
|
|
|
86
87
|
tree-sitter-php = { version = "0.24", optional = true }
|
|
87
88
|
tree-sitter-scala = { version = "0.26", optional = true }
|
|
88
89
|
tree-sitter-swift = { version = "0.7", optional = true }
|
|
90
|
+
# The fwcd grammar (fwcd's own crate stops at 0.3.8, pinned to tree-sitter
|
|
91
|
+
# < 0.23, fwcd#242), as BrokkAi publishes its maintained fork with symbol-
|
|
92
|
+
# prefixed C exports. Exact pin: a fork's next release is reviewed, not taken.
|
|
93
|
+
# On yay-tsa's 1,036 files it lost no type declaration and broke on none;
|
|
94
|
+
# tree-sitter-kotlin-sg 0.4.1 silently dropped 7 classes (a bodyless
|
|
95
|
+
# annotated class swallowed the next one) and broke 5 Compose files, and
|
|
96
|
+
# tree-sitter-kotlin-ng dropped 8 and broke whole classes on `get(...)`.
|
|
97
|
+
brokk-tree-sitter-kotlin = { version = "=0.4.6", optional = true }
|
|
89
98
|
tree-sitter-html = { version = "0.23", optional = true }
|
|
90
99
|
tree-sitter-bash = { version = "0.25", optional = true }
|
|
91
100
|
tree-sitter-css = { version = "0.25", optional = true }
|
|
@@ -18,7 +18,7 @@ zero.
|
|
|
18
18
|
## Install
|
|
19
19
|
|
|
20
20
|
```bash
|
|
21
|
-
# Claude Code: the MCP server, /diffctx:impact, and hooks that run it
|
|
21
|
+
# Claude Code: the MCP server, /diffctx:impact, and hooks that run it on each commit and push
|
|
22
22
|
claude plugin marketplace add nikolay-e/diffctx
|
|
23
23
|
claude plugin install diffctx@diffctx
|
|
24
24
|
|
|
@@ -747,6 +747,8 @@ impl Evidence {
|
|
|
747
747
|
pub enum Relation {
|
|
748
748
|
Call,
|
|
749
749
|
Reference,
|
|
750
|
+
/// A class that implements the changed abstract member.
|
|
751
|
+
Implementation,
|
|
750
752
|
}
|
|
751
753
|
|
|
752
754
|
/// The changed definition: `name` in `path`, a method of `class` when it
|
|
@@ -1056,9 +1058,8 @@ impl Resolver<'_> {
|
|
|
1056
1058
|
return direct;
|
|
1057
1059
|
}
|
|
1058
1060
|
}
|
|
1059
|
-
let
|
|
1060
|
-
|
|
1061
|
-
constructed_as(site.content, head, python).or_else(|| annotated_as(site.content, head))
|
|
1061
|
+
if let Some(ctor) = constructed_as(site.path, site.content, head)
|
|
1062
|
+
.or_else(|| annotated_as(site.content, head))
|
|
1062
1063
|
{
|
|
1063
1064
|
return self.constructor_is(site, &ctor, def, class);
|
|
1064
1065
|
}
|
|
@@ -1097,12 +1098,10 @@ impl Resolver<'_> {
|
|
|
1097
1098
|
pub fn classify(&self, site: &Site, def: &Definition) -> Option<Finding> {
|
|
1098
1099
|
let words = self.names_for(site, def);
|
|
1099
1100
|
let params = params_of(site.content);
|
|
1100
|
-
let python = lang_of(site.path) == Some(Lang::Python);
|
|
1101
|
-
let mut triple = None;
|
|
1102
1101
|
let mut best: Option<Finding> = None;
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
if is_import_line(
|
|
1102
|
+
let masked = crate::impact::code_text(site.path, site.content);
|
|
1103
|
+
for (i, code) in masked.lines().enumerate() {
|
|
1104
|
+
if is_import_line(code) {
|
|
1106
1105
|
continue;
|
|
1107
1106
|
}
|
|
1108
1107
|
let line_no = site.start_line + i as u32;
|
|
@@ -1198,7 +1197,7 @@ impl Resolver<'_> {
|
|
|
1198
1197
|
}
|
|
1199
1198
|
}
|
|
1200
1199
|
|
|
1201
|
-
fn is_import_line(code: &str) -> bool {
|
|
1200
|
+
pub(crate) fn is_import_line(code: &str) -> bool {
|
|
1202
1201
|
let head = code.trim_start();
|
|
1203
1202
|
head.starts_with("import ")
|
|
1204
1203
|
|| head.starts_with("from ")
|
|
@@ -1260,21 +1259,21 @@ fn bases_of(header: &str) -> Vec<String> {
|
|
|
1260
1259
|
/// constructor `x` holds. A binding is read within one line: across a
|
|
1261
1260
|
/// newline, `as srv:` and the next line's `result = srv.call_tool(...)`
|
|
1262
1261
|
/// read as an annotated assignment from `srv.call_tool` (#399).
|
|
1263
|
-
fn constructed_as(
|
|
1262
|
+
fn constructed_as(path: &str, content: &str, var: &str) -> Option<String> {
|
|
1264
1263
|
let re = Regex::new(&format!(
|
|
1265
1264
|
r"(?:^|[^\w$.])({})\s*(?::\s*[\w$.\[\]<>]+\s*)?=\s*(?:new\s+|await\s+)?([\w$.]+)\s*\(",
|
|
1266
1265
|
regex::escape(var)
|
|
1267
1266
|
))
|
|
1268
1267
|
.ok()?;
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1268
|
+
crate::impact::code_text(path, content)
|
|
1269
|
+
.lines()
|
|
1270
|
+
.find_map(|code| {
|
|
1271
|
+
entered_as(code, var).or_else(|| {
|
|
1272
|
+
re.captures_iter(code)
|
|
1273
|
+
.find(|c| c.get(1).is_some_and(|v| !after_as(code, v.start())))
|
|
1274
|
+
.map(|c| c[2].to_string())
|
|
1275
|
+
})
|
|
1276
1276
|
})
|
|
1277
|
-
})
|
|
1278
1277
|
}
|
|
1279
1278
|
|
|
1280
1279
|
/// Whether the name at `at` is the target of an `as` (`with … as x`,
|
|
@@ -109,13 +109,21 @@ pub fn collect_candidate_files(
|
|
|
109
109
|
ctx: &crate::resource::RunContext,
|
|
110
110
|
) -> Vec<PathBuf> {
|
|
111
111
|
let source = crate::source::current();
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
112
|
+
match source.list_blobs(scope) {
|
|
113
|
+
Some(Ok(blobs)) => {
|
|
114
|
+
let admitted = blobs
|
|
115
|
+
.into_iter()
|
|
116
|
+
.filter(|b| is_snapshot_candidate(b, root_dir, included_set, &source, ctx))
|
|
117
|
+
.map(|b| b.path)
|
|
118
|
+
.collect();
|
|
119
|
+
return filter_ignored_and_secret(root_dir, admitted);
|
|
120
|
+
}
|
|
121
|
+
Some(Err(e)) => {
|
|
122
|
+
tracing::warn!("discovery: the snapshot's listing failed ({e})");
|
|
123
|
+
ctx.note(crate::resource::reason_for(&e));
|
|
124
|
+
return Vec::new();
|
|
125
|
+
}
|
|
126
|
+
None => {}
|
|
119
127
|
}
|
|
120
128
|
match tracked_candidates(root_dir, included_set, scope, ctx) {
|
|
121
129
|
Some(files) => filter_ignored_and_secret(root_dir, files),
|
|
@@ -316,6 +316,13 @@ impl DiscoveryStrategy for TestFileDiscovery {
|
|
|
316
316
|
for suffix in TEST_SUFFIXES {
|
|
317
317
|
test_stems.insert(format!("{stem}{suffix}"));
|
|
318
318
|
}
|
|
319
|
+
let ext = crate::edges::base::file_ext(f);
|
|
320
|
+
if crate::edges::structural::testing::JVM_TEST_SUFFIX_EXTENSIONS.contains(&ext.as_str())
|
|
321
|
+
{
|
|
322
|
+
for suffix in crate::edges::structural::testing::JVM_TEST_SUFFIXES {
|
|
323
|
+
test_stems.insert(format!("{stem}{suffix}"));
|
|
324
|
+
}
|
|
325
|
+
}
|
|
319
326
|
}
|
|
320
327
|
|
|
321
328
|
let mut discovered: Vec<PathBuf> = Vec::new();
|
|
@@ -47,10 +47,19 @@ pub trait EdgeBuilder: Send + Sync {
|
|
|
47
47
|
static INDEX_FILE_STEMS: Lazy<FxHashSet<&str>> =
|
|
48
48
|
Lazy::new(|| ["__init__", "index", "mod"].iter().copied().collect());
|
|
49
49
|
|
|
50
|
+
/// JVM source roots: `src/<source set>/<language>/` precedes the package
|
|
51
|
+
/// path in Gradle and Maven layouts, so `src/test/kotlin/com/acme/FooTest`
|
|
52
|
+
/// is the module `com.acme.FooTest`, the name its imports spell.
|
|
53
|
+
const JVM_SOURCE_LANGUAGES: &[&str] = &["java", "kotlin", "scala", "groovy"];
|
|
54
|
+
|
|
50
55
|
fn strip_source_prefix(parts: &[&str]) -> Vec<String> {
|
|
51
56
|
for (i, part) in parts.iter().enumerate() {
|
|
52
57
|
if *part == "src" || *part == "lib" || *part == "packages" {
|
|
53
|
-
|
|
58
|
+
let mut rest = &parts[i + 1..];
|
|
59
|
+
if *part == "src" && rest.len() > 2 && JVM_SOURCE_LANGUAGES.contains(&rest[1]) {
|
|
60
|
+
rest = &rest[2..];
|
|
61
|
+
}
|
|
62
|
+
return rest.iter().map(|s| s.to_string()).collect();
|
|
54
63
|
}
|
|
55
64
|
}
|
|
56
65
|
parts.iter().map(|s| s.to_string()).collect()
|
|
@@ -79,10 +79,16 @@ fn extract_yaml_keys(content: &str, max_depth: usize) -> FxHashSet<String> {
|
|
|
79
79
|
keys
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
+
/// The chart directory above `path`, in the analysed source: a snapshot's
|
|
83
|
+
/// chart is its own, whatever the disk holds (#408).
|
|
82
84
|
fn get_chart_root(path: &Path) -> Option<PathBuf> {
|
|
85
|
+
let source = crate::source::current();
|
|
83
86
|
let mut current = path.parent()?.to_path_buf();
|
|
84
87
|
for _ in 0..5 {
|
|
85
|
-
if
|
|
88
|
+
if ["Chart.yaml", "chart.yaml"]
|
|
89
|
+
.iter()
|
|
90
|
+
.any(|name| source.read_bytes(¤t.join(name)).is_some())
|
|
91
|
+
{
|
|
86
92
|
return Some(current);
|
|
87
93
|
}
|
|
88
94
|
let parent = current.parent()?;
|
|
@@ -418,10 +418,13 @@ fn collect_capped_edges_from(
|
|
|
418
418
|
cap_out_edges_per_source(&mut edges, max_per_node);
|
|
419
419
|
ctx.record_usage(|u| u.final_edges = edges.len() as u64);
|
|
420
420
|
|
|
421
|
-
let
|
|
421
|
+
let capped_sources: FxHashSet<FragmentId> = out_degree
|
|
422
422
|
.iter()
|
|
423
|
-
.
|
|
424
|
-
.
|
|
423
|
+
.enumerate()
|
|
424
|
+
.filter(|&(_, &d)| d as usize > max_per_node)
|
|
425
|
+
.map(|(i, _)| idx_to_node[i].clone())
|
|
426
|
+
.collect();
|
|
427
|
+
let nodes_capped = capped_sources.len();
|
|
425
428
|
let cap_stats = EdgeCapStats {
|
|
426
429
|
edges_before_cap: deduped_edge_count,
|
|
427
430
|
edges_after_cap: edges.len(),
|
|
@@ -429,6 +432,7 @@ fn collect_capped_edges_from(
|
|
|
429
432
|
nodes_capped,
|
|
430
433
|
max_out_edges_per_node: max_per_node,
|
|
431
434
|
emissions_by_category,
|
|
435
|
+
capped_sources,
|
|
432
436
|
};
|
|
433
437
|
|
|
434
438
|
CappedEdges {
|