diffctx 1.14.0__tar.gz → 1.16.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.14.0 → diffctx-1.16.0}/CHANGELOG.md +569 -6
- {diffctx-1.14.0 → diffctx-1.16.0}/Cargo.lock +70 -7
- diffctx-1.16.0/Cargo.toml +25 -0
- {diffctx-1.14.0 → diffctx-1.16.0}/PKG-INFO +56 -34
- {diffctx-1.14.0 → diffctx-1.16.0}/README.md +53 -12
- {diffctx-1.14.0 → diffctx-1.16.0}/crates/diffctx-native/Cargo.toml +12 -5
- {diffctx-1.14.0 → diffctx-1.16.0}/crates/diffctx-native/README.md +53 -12
- {diffctx-1.14.0 → diffctx-1.16.0}/crates/diffctx-native/src/analytics.rs +209 -226
- {diffctx-1.14.0 → diffctx-1.16.0}/crates/diffctx-native/src/candidate_files.rs +38 -14
- diffctx-1.16.0/crates/diffctx-native/src/change_class.rs +169 -0
- {diffctx-1.14.0 → diffctx-1.16.0}/crates/diffctx-native/src/config/budget.rs +6 -0
- {diffctx-1.14.0 → diffctx-1.16.0}/crates/diffctx-native/src/config/category_weights.rs +4 -5
- diffctx-1.16.0/crates/diffctx-native/src/config/edge_weights.rs +165 -0
- {diffctx-1.14.0 → diffctx-1.16.0}/crates/diffctx-native/src/config/limits.rs +5 -0
- {diffctx-1.14.0 → diffctx-1.16.0}/crates/diffctx-native/src/config/scoring.rs +2 -6
- {diffctx-1.14.0 → diffctx-1.16.0}/crates/diffctx-native/src/config/weights.rs +36 -180
- {diffctx-1.14.0 → diffctx-1.16.0}/crates/diffctx-native/src/edges/base.rs +280 -53
- {diffctx-1.14.0 → diffctx-1.16.0}/crates/diffctx-native/src/edges/config_edges/build_system.rs +8 -16
- {diffctx-1.14.0 → diffctx-1.16.0}/crates/diffctx-native/src/edges/config_edges/cicd.rs +98 -38
- {diffctx-1.14.0 → diffctx-1.16.0}/crates/diffctx-native/src/edges/config_edges/docker.rs +99 -36
- {diffctx-1.14.0 → diffctx-1.16.0}/crates/diffctx-native/src/edges/config_edges/generic.rs +53 -4
- diffctx-1.16.0/crates/diffctx-native/src/edges/config_edges/kubernetes.rs +951 -0
- {diffctx-1.14.0 → diffctx-1.16.0}/crates/diffctx-native/src/edges/history/cochange.rs +2 -6
- {diffctx-1.14.0 → diffctx-1.16.0}/crates/diffctx-native/src/edges/mod.rs +187 -12
- {diffctx-1.14.0 → diffctx-1.16.0}/crates/diffctx-native/src/edges/semantic/ansible.rs +25 -19
- {diffctx-1.14.0 → diffctx-1.16.0}/crates/diffctx-native/src/edges/semantic/bazel.rs +5 -14
- {diffctx-1.14.0 → diffctx-1.16.0}/crates/diffctx-native/src/edges/semantic/c_family.rs +57 -95
- {diffctx-1.14.0 → diffctx-1.16.0}/crates/diffctx-native/src/edges/semantic/cargo_edges.rs +18 -32
- {diffctx-1.14.0 → diffctx-1.16.0}/crates/diffctx-native/src/edges/semantic/clojure.rs +22 -51
- {diffctx-1.14.0 → diffctx-1.16.0}/crates/diffctx-native/src/edges/semantic/css.rs +13 -24
- {diffctx-1.14.0 → diffctx-1.16.0}/crates/diffctx-native/src/edges/semantic/dart.rs +24 -109
- {diffctx-1.14.0 → diffctx-1.16.0}/crates/diffctx-native/src/edges/semantic/dbt.rs +33 -56
- {diffctx-1.14.0 → diffctx-1.16.0}/crates/diffctx-native/src/edges/semantic/dotnet.rs +34 -128
- {diffctx-1.14.0 → diffctx-1.16.0}/crates/diffctx-native/src/edges/semantic/elixir.rs +19 -45
- {diffctx-1.14.0 → diffctx-1.16.0}/crates/diffctx-native/src/edges/semantic/erlang.rs +21 -52
- {diffctx-1.14.0 → diffctx-1.16.0}/crates/diffctx-native/src/edges/semantic/go.rs +237 -65
- {diffctx-1.14.0 → diffctx-1.16.0}/crates/diffctx-native/src/edges/semantic/graphql.rs +18 -43
- {diffctx-1.14.0 → diffctx-1.16.0}/crates/diffctx-native/src/edges/semantic/haskell.rs +27 -123
- {diffctx-1.14.0 → diffctx-1.16.0}/crates/diffctx-native/src/edges/semantic/javascript.rs +19 -89
- {diffctx-1.14.0 → diffctx-1.16.0}/crates/diffctx-native/src/edges/semantic/julia.rs +20 -42
- {diffctx-1.14.0 → diffctx-1.16.0}/crates/diffctx-native/src/edges/semantic/jvm.rs +238 -138
- {diffctx-1.14.0 → diffctx-1.16.0}/crates/diffctx-native/src/edges/semantic/latex.rs +14 -21
- {diffctx-1.14.0 → diffctx-1.16.0}/crates/diffctx-native/src/edges/semantic/lua.rs +18 -44
- {diffctx-1.14.0 → diffctx-1.16.0}/crates/diffctx-native/src/edges/semantic/nim.rs +20 -89
- {diffctx-1.14.0 → diffctx-1.16.0}/crates/diffctx-native/src/edges/semantic/nix.rs +13 -20
- {diffctx-1.14.0 → diffctx-1.16.0}/crates/diffctx-native/src/edges/semantic/ocaml.rs +22 -46
- {diffctx-1.14.0 → diffctx-1.16.0}/crates/diffctx-native/src/edges/semantic/openapi.rs +20 -34
- {diffctx-1.14.0 → diffctx-1.16.0}/crates/diffctx-native/src/edges/semantic/perl.rs +21 -63
- {diffctx-1.14.0 → diffctx-1.16.0}/crates/diffctx-native/src/edges/semantic/php.rs +17 -41
- {diffctx-1.14.0 → diffctx-1.16.0}/crates/diffctx-native/src/edges/semantic/prisma.rs +25 -45
- {diffctx-1.14.0 → diffctx-1.16.0}/crates/diffctx-native/src/edges/semantic/protobuf.rs +19 -43
- {diffctx-1.14.0 → diffctx-1.16.0}/crates/diffctx-native/src/edges/semantic/python.rs +197 -120
- {diffctx-1.14.0 → diffctx-1.16.0}/crates/diffctx-native/src/edges/semantic/r_lang.rs +23 -82
- {diffctx-1.14.0 → diffctx-1.16.0}/crates/diffctx-native/src/edges/semantic/ruby.rs +17 -39
- {diffctx-1.14.0 → diffctx-1.16.0}/crates/diffctx-native/src/edges/semantic/rust_lang.rs +228 -185
- {diffctx-1.14.0 → diffctx-1.16.0}/crates/diffctx-native/src/edges/semantic/shell.rs +13 -23
- {diffctx-1.14.0 → diffctx-1.16.0}/crates/diffctx-native/src/edges/semantic/sql.rs +18 -34
- {diffctx-1.14.0 → diffctx-1.16.0}/crates/diffctx-native/src/edges/semantic/swift.rs +19 -43
- {diffctx-1.14.0 → diffctx-1.16.0}/crates/diffctx-native/src/edges/semantic/terraform.rs +11 -53
- {diffctx-1.14.0 → diffctx-1.16.0}/crates/diffctx-native/src/edges/semantic/zig.rs +23 -80
- {diffctx-1.14.0 → diffctx-1.16.0}/crates/diffctx-native/src/edges/similarity/lexical.rs +0 -4
- {diffctx-1.14.0 → diffctx-1.16.0}/crates/diffctx-native/src/edges/structural/containment.rs +1 -19
- {diffctx-1.14.0 → diffctx-1.16.0}/crates/diffctx-native/src/edges/structural/sibling.rs +2 -9
- {diffctx-1.14.0 → diffctx-1.16.0}/crates/diffctx-native/src/edges/structural/testing.rs +22 -31
- diffctx-1.16.0/crates/diffctx-native/src/effective_config.rs +448 -0
- diffctx-1.16.0/crates/diffctx-native/src/facts/mod.rs +28 -0
- diffctx-1.16.0/crates/diffctx-native/src/facts/scala.rs +235 -0
- {diffctx-1.14.0 → diffctx-1.16.0}/crates/diffctx-native/src/fragmentation.rs +66 -17
- {diffctx-1.14.0 → diffctx-1.16.0}/crates/diffctx-native/src/git.rs +356 -92
- {diffctx-1.14.0 → diffctx-1.16.0}/crates/diffctx-native/src/graph.rs +49 -15
- {diffctx-1.14.0 → diffctx-1.16.0}/crates/diffctx-native/src/graph_export.rs +6 -16
- diffctx-1.14.0/crates/diffctx-native/src/memory_pipeline.rs → diffctx-1.16.0/crates/diffctx-native/src/in_memory_harness.rs +194 -97
- {diffctx-1.14.0 → diffctx-1.16.0}/crates/diffctx-native/src/languages.rs +23 -6
- diffctx-1.16.0/crates/diffctx-native/src/lib.rs +70 -0
- {diffctx-1.14.0 → diffctx-1.16.0}/crates/diffctx-native/src/locate.rs +47 -23
- {diffctx-1.14.0 → diffctx-1.16.0}/crates/diffctx-native/src/main.rs +122 -108
- {diffctx-1.14.0 → diffctx-1.16.0}/crates/diffctx-native/src/mode.rs +26 -65
- {diffctx-1.14.0 → diffctx-1.16.0}/crates/diffctx-native/src/parsers/config_parser.rs +5 -77
- {diffctx-1.14.0 → diffctx-1.16.0}/crates/diffctx-native/src/parsers/markdown.rs +1 -9
- {diffctx-1.14.0 → diffctx-1.16.0}/crates/diffctx-native/src/parsers/mod.rs +8 -0
- {diffctx-1.14.0 → diffctx-1.16.0}/crates/diffctx-native/src/parsers/tree_sitter_strategy.rs +342 -871
- diffctx-1.16.0/crates/diffctx-native/src/paths.rs +187 -0
- {diffctx-1.14.0 → diffctx-1.16.0}/crates/diffctx-native/src/pipeline.rs +928 -263
- {diffctx-1.14.0 → diffctx-1.16.0}/crates/diffctx-native/src/postpass.rs +118 -18
- {diffctx-1.14.0 → diffctx-1.16.0}/crates/diffctx-native/src/ppr.rs +2 -7
- {diffctx-1.14.0 → diffctx-1.16.0}/crates/diffctx-native/src/project_graph.rs +18 -75
- {diffctx-1.14.0 → diffctx-1.16.0}/crates/diffctx-native/src/pybridge.rs +214 -230
- {diffctx-1.14.0 → diffctx-1.16.0}/crates/diffctx-native/src/render.rs +208 -33
- diffctx-1.16.0/crates/diffctx-native/src/resource.rs +425 -0
- diffctx-1.16.0/crates/diffctx-native/src/run_provenance.rs +126 -0
- diffctx-1.16.0/crates/diffctx-native/src/sanitize.rs +183 -0
- {diffctx-1.14.0 → diffctx-1.16.0}/crates/diffctx-native/src/scoring.rs +188 -144
- {diffctx-1.14.0 → diffctx-1.16.0}/crates/diffctx-native/src/select.rs +577 -58
- {diffctx-1.14.0 → diffctx-1.16.0}/crates/diffctx-native/src/signatures.rs +34 -7
- diffctx-1.16.0/crates/diffctx-native/src/stopwords/code.txt +79 -0
- diffctx-1.16.0/crates/diffctx-native/src/stopwords/docs.txt +8 -0
- diffctx-1.16.0/crates/diffctx-native/src/stopwords.rs +112 -0
- diffctx-1.16.0/crates/diffctx-native/src/test_rng.rs +15 -0
- {diffctx-1.14.0 → diffctx-1.16.0}/crates/diffctx-native/src/tokenizer.rs +83 -2
- {diffctx-1.14.0 → diffctx-1.16.0}/crates/diffctx-native/src/types.rs +20 -5
- {diffctx-1.14.0 → diffctx-1.16.0}/crates/diffctx-native/src/utility/boltzmann.rs +25 -34
- {diffctx-1.14.0 → diffctx-1.16.0}/crates/diffctx-native/src/utility/mod.rs +1 -2
- {diffctx-1.14.0 → diffctx-1.16.0}/crates/diffctx-native/src/utility/needs.rs +44 -62
- diffctx-1.14.0/crates/diffctx-native/src/utility/scoring.rs → diffctx-1.16.0/crates/diffctx-native/src/utility/objective.rs +6 -28
- {diffctx-1.14.0 → diffctx-1.16.0}/pyproject.toml +24 -66
- diffctx-1.16.0/rust-toolchain.toml +12 -0
- {diffctx-1.14.0 → diffctx-1.16.0}/src/diffctx/__init__.py +5 -14
- {diffctx-1.14.0 → diffctx-1.16.0}/src/diffctx/_app.py +42 -20
- {diffctx-1.14.0 → diffctx-1.16.0}/src/diffctx/_diffctx.pyi +16 -16
- {diffctx-1.14.0 → diffctx-1.16.0}/src/diffctx/_native/graph_analytics.py +6 -3
- {diffctx-1.14.0 → diffctx-1.16.0}/src/diffctx/_native/pipeline.py +14 -17
- diffctx-1.16.0/src/diffctx/_native/project_graph.py +39 -0
- {diffctx-1.14.0 → diffctx-1.16.0}/src/diffctx/cli.py +60 -15
- {diffctx-1.14.0 → diffctx-1.16.0}/src/diffctx/ignore.py +8 -15
- {diffctx-1.14.0 → diffctx-1.16.0}/src/diffctx/mcp/fetch.py +82 -35
- diffctx-1.16.0/src/diffctx/mcp/legacy.py +269 -0
- {diffctx-1.14.0 → diffctx-1.16.0}/src/diffctx/mcp/security.py +7 -1
- diffctx-1.16.0/src/diffctx/mcp/server.py +297 -0
- {diffctx-1.14.0 → diffctx-1.16.0}/src/diffctx/tree.py +13 -1
- diffctx-1.16.0/src/diffctx/version.py +1 -0
- {diffctx-1.14.0 → diffctx-1.16.0}/src/diffctx/writer.py +280 -35
- diffctx-1.14.0/Cargo.toml +0 -20
- diffctx-1.14.0/crates/diffctx-native/src/config/edge_weights.rs +0 -257
- diffctx-1.14.0/crates/diffctx-native/src/deadline.rs +0 -142
- diffctx-1.14.0/crates/diffctx-native/src/edges/config_edges/kubernetes.rs +0 -552
- diffctx-1.14.0/crates/diffctx-native/src/lib.rs +0 -39
- diffctx-1.14.0/crates/diffctx-native/src/paths.rs +0 -64
- diffctx-1.14.0/crates/diffctx-native/src/stopwords.rs +0 -990
- diffctx-1.14.0/crates/diffctx-native/src/test_harness.rs +0 -406
- diffctx-1.14.0/rust-toolchain.toml +0 -4
- diffctx-1.14.0/src/diffctx/_native/project_graph.py +0 -36
- diffctx-1.14.0/src/diffctx/mcp/README.md +0 -127
- diffctx-1.14.0/src/diffctx/mcp/formatting.py +0 -9
- diffctx-1.14.0/src/diffctx/mcp/server.py +0 -533
- diffctx-1.14.0/src/diffctx/version.py +0 -1
- {diffctx-1.14.0 → diffctx-1.16.0}/LICENSE +0 -0
- {diffctx-1.14.0 → diffctx-1.16.0}/crates/diffctx-native/src/config/analytics.rs +0 -0
- {diffctx-1.14.0 → diffctx-1.16.0}/crates/diffctx-native/src/config/bm25.rs +0 -0
- {diffctx-1.14.0 → diffctx-1.16.0}/crates/diffctx-native/src/config/env_overrides.rs +0 -0
- {diffctx-1.14.0 → diffctx-1.16.0}/crates/diffctx-native/src/config/extensions.rs +0 -0
- {diffctx-1.14.0 → diffctx-1.16.0}/crates/diffctx-native/src/config/filtering.rs +0 -0
- {diffctx-1.14.0 → diffctx-1.16.0}/crates/diffctx-native/src/config/fragmentation.rs +0 -0
- {diffctx-1.14.0 → diffctx-1.16.0}/crates/diffctx-native/src/config/git.rs +0 -0
- {diffctx-1.14.0 → diffctx-1.16.0}/crates/diffctx-native/src/config/graph_filtering.rs +0 -0
- {diffctx-1.14.0 → diffctx-1.16.0}/crates/diffctx-native/src/config/importance.rs +0 -0
- {diffctx-1.14.0 → diffctx-1.16.0}/crates/diffctx-native/src/config/mod.rs +0 -0
- {diffctx-1.14.0 → diffctx-1.16.0}/crates/diffctx-native/src/config/mode.rs +0 -0
- {diffctx-1.14.0 → diffctx-1.16.0}/crates/diffctx-native/src/config/needs.rs +0 -0
- {diffctx-1.14.0 → diffctx-1.16.0}/crates/diffctx-native/src/config/parsers.rs +0 -0
- {diffctx-1.14.0 → diffctx-1.16.0}/crates/diffctx-native/src/config/render.rs +0 -0
- {diffctx-1.14.0 → diffctx-1.16.0}/crates/diffctx-native/src/config/selection.rs +0 -0
- {diffctx-1.14.0 → diffctx-1.16.0}/crates/diffctx-native/src/config/tokenization.rs +0 -0
- {diffctx-1.14.0 → diffctx-1.16.0}/crates/diffctx-native/src/core.rs +0 -0
- {diffctx-1.14.0 → diffctx-1.16.0}/crates/diffctx-native/src/discovery.rs +0 -0
- {diffctx-1.14.0 → diffctx-1.16.0}/crates/diffctx-native/src/edges/config_edges/helm.rs +0 -0
- {diffctx-1.14.0 → diffctx-1.16.0}/crates/diffctx-native/src/edges/config_edges/mod.rs +0 -0
- {diffctx-1.14.0 → diffctx-1.16.0}/crates/diffctx-native/src/edges/document/mod.rs +0 -0
- {diffctx-1.14.0 → diffctx-1.16.0}/crates/diffctx-native/src/edges/history/mod.rs +0 -0
- {diffctx-1.14.0 → diffctx-1.16.0}/crates/diffctx-native/src/edges/semantic/mod.rs +0 -0
- {diffctx-1.14.0 → diffctx-1.16.0}/crates/diffctx-native/src/edges/semantic/tags.rs +0 -0
- {diffctx-1.14.0 → diffctx-1.16.0}/crates/diffctx-native/src/edges/similarity/mod.rs +0 -0
- {diffctx-1.14.0 → diffctx-1.16.0}/crates/diffctx-native/src/edges/structural/mod.rs +0 -0
- {diffctx-1.14.0 → diffctx-1.16.0}/crates/diffctx-native/src/excerpt.rs +0 -0
- {diffctx-1.14.0 → diffctx-1.16.0}/crates/diffctx-native/src/filtering.rs +0 -0
- {diffctx-1.14.0 → diffctx-1.16.0}/crates/diffctx-native/src/interval.rs +0 -0
- {diffctx-1.14.0 → diffctx-1.16.0}/crates/diffctx-native/src/parsers/generic.rs +0 -0
- {diffctx-1.14.0 → diffctx-1.16.0}/crates/diffctx-native/src/peak_rss.rs +0 -0
- {diffctx-1.14.0 → diffctx-1.16.0}/crates/diffctx-native/src/provenance.rs +0 -0
- {diffctx-1.14.0 → diffctx-1.16.0}/crates/diffctx-native/src/testfiles.rs +0 -0
- {diffctx-1.14.0 → diffctx-1.16.0}/crates/diffctx-native/src/token_corpus.rs +0 -0
- {diffctx-1.14.0 → diffctx-1.16.0}/crates/diffctx-native/src/utility/importance.rs +0 -0
- {diffctx-1.14.0 → diffctx-1.16.0}/src/diffctx/__main__.py +0 -0
- {diffctx-1.14.0 → diffctx-1.16.0}/src/diffctx/_native/__init__.py +0 -0
- {diffctx-1.14.0 → diffctx-1.16.0}/src/diffctx/_native/graph_export.py +0 -0
- {diffctx-1.14.0 → diffctx-1.16.0}/src/diffctx/clipboard.py +0 -0
- {diffctx-1.14.0 → diffctx-1.16.0}/src/diffctx/logger.py +0 -0
- {diffctx-1.14.0 → diffctx-1.16.0}/src/diffctx/mcp/__init__.py +0 -0
- {diffctx-1.14.0 → diffctx-1.16.0}/src/diffctx/mcp/__main__.py +0 -0
- {diffctx-1.14.0 → diffctx-1.16.0}/src/diffctx/py.typed +0 -0
- {diffctx-1.14.0 → diffctx-1.16.0}/src/diffctx/tokens.py +0 -0
|
@@ -7,6 +7,566 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [1.16.0] - 2026-09-16
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **A FAQ and a benchmarks page** (#151): `docs/product/faq.md` answers the
|
|
15
|
+
recurring critiques (heuristic or oracle, the `tree-sitter` extra that no
|
|
16
|
+
longer exists, whose tokens `--budget` counts, MCP versus a prompt builder,
|
|
17
|
+
monorepo limits, the raw diff, secrets) and `BENCHMARKS.md` carries every
|
|
18
|
+
published number with what it was measured on and how to reproduce it.
|
|
19
|
+
- **A command-line reference page** (#240): `docs/product/cli.md` lists every
|
|
20
|
+
flag with its default and meaning, rendered from the parsers by
|
|
21
|
+
`scripts/update_cli_reference.py` and pinned by a test so it cannot drift
|
|
22
|
+
from `diffctx --help`. Linked from the site's nav, `llms.txt` and the
|
|
23
|
+
README.
|
|
24
|
+
- **Every changed file gets a witness before any file gets a second one**
|
|
25
|
+
(#263). Selection now opens with an evidence floor: one representation per
|
|
26
|
+
changed file — the same fragment the relevance pass would place, its
|
|
27
|
+
stand-in when that does not fit, a clipped head of the change when nothing
|
|
28
|
+
does — ordered by change class (hand-written content before mechanical
|
|
29
|
+
version/tag bumps) and then by path. Relevance and density compete only
|
|
30
|
+
for what is left. A range of nine one-line image-tag bumps plus four
|
|
31
|
+
rewritten manifests at `--budget 4000` now keeps all thirteen; before, the
|
|
32
|
+
four manifests were dropped in favour of the bumps. A file that still gets
|
|
33
|
+
nothing marks the artifact `coverage.status: degraded` with
|
|
34
|
+
`evidence_budget_exceeded`.
|
|
35
|
+
- **The rendered document is what `--budget` bounds** (#259). Every
|
|
36
|
+
renderer — JSON, YAML, Markdown, text, the CLI and the Python `to_*`
|
|
37
|
+
functions — counts its own output and drops context fragments from the
|
|
38
|
+
tail (a changed fragment only when no context is left) until it fits,
|
|
39
|
+
noting `selection_budget_exceeded` in coverage. The engine's envelope
|
|
40
|
+
estimate still shapes what selection admits, but it no longer decides
|
|
41
|
+
whether the artifact honours the cap.
|
|
42
|
+
- **The artifact inventories every changed file and says which ones it
|
|
43
|
+
left out** (#263). `changes` carries one row per changed file — its
|
|
44
|
+
`class` (`content`, `unknown`, `mechanical`: one-line version/tag/digest
|
|
45
|
+
edits of the kind an image updater writes, `generated`), the `reason`, and
|
|
46
|
+
`represented`, false when no fragment of the file is in the output. JSON,
|
|
47
|
+
YAML, the Python dict, Markdown and text all say it; `locate`'s coverage
|
|
48
|
+
lists `unrepresented_changed_files`. An incomplete context is no longer
|
|
49
|
+
indistinguishable from a complete one on any surface.
|
|
50
|
+
- **The artifact carries the commit messages of the range, whole.**
|
|
51
|
+
`commit_messages` lists every message — subject and body, up to 20
|
|
52
|
+
commits, 2 000 characters each — newest first; Markdown and text render
|
|
53
|
+
them instead of the subject of whichever commit happened to be last, and
|
|
54
|
+
every message feeds the query expansion, not only the head's subject.
|
|
55
|
+
`commit_message` stays the head's subject for existing readers. The list is
|
|
56
|
+
bounded by the budget — every subject, bodies newest-first while they fit a
|
|
57
|
+
tenth of it — and, like the inventory rows, charged before selection.
|
|
58
|
+
- **One artifact, `diffctx.context.v1`, on every surface.** JSON and YAML
|
|
59
|
+
output open with `schema: diffctx.context.v1`; the document is generated
|
|
60
|
+
from the engine's type and pinned as `schemas/diffctx.context.v1.json`
|
|
61
|
+
(JSON Schema 2020-12, a test regenerates and compares it). The Python dict
|
|
62
|
+
is that same serialization — the hand-written `set_item` copy of the
|
|
63
|
+
shape, the one that never ran on the CLI path, is gone (#229).
|
|
64
|
+
- **Every run records what produced it.** JSON and YAML output (and the
|
|
65
|
+
Python dict) carry a `provenance` block — `diffctx.provenance.v1`: engine
|
|
66
|
+
version, the input revisions as object ids, the resolved effective
|
|
67
|
+
configuration (`diffctx.effective_config.v1`: every parameter and every
|
|
68
|
+
`DIFFCTX_*` override that shaped the selection) and its 64-bit hash, the
|
|
69
|
+
selection parameters actually used (budget, tau, gate). The full record —
|
|
70
|
+
every parameter, the tokenizer, the resource caps, ~500 tokens — is
|
|
71
|
+
opt-in with `DIFFCTX_PROVENANCE=full`. Two runs that differ can now say
|
|
72
|
+
what differed between them.
|
|
73
|
+
- **`DIFFCTX_EVAL_STRICT=1`** refuses to start when the environment carries a
|
|
74
|
+
`DIFFCTX_*` name the effective configuration does not know — an evaluation
|
|
75
|
+
row shaped by an undeclared knob is a number nobody can reproduce.
|
|
76
|
+
- **`DIFFCTX_TOKEN_SAFETY_FACTOR`** (≥ 1.0, default 1.0) scales every token
|
|
77
|
+
count for consumers whose model tokenizes denser than `o200k_base`;
|
|
78
|
+
`--budget N` stays "N o200k_base accounting tokens" and the factor is in
|
|
79
|
+
provenance. No model-specific default is shipped.
|
|
80
|
+
- **A run that hits a limit says so instead of failing.** `--timeout` is
|
|
81
|
+
cooperative now: the phase it interrupts stops at its next bounded unit (a
|
|
82
|
+
file, an edge builder, a diffusion batch) and the run renders what it has,
|
|
83
|
+
with a `coverage` block — `status: partial`, `limit_reasons`, and the
|
|
84
|
+
resources consumed — on JSON, YAML, Markdown, text, the Python dict and
|
|
85
|
+
`locate`'s coverage. The same vocabulary covers the new memory caps:
|
|
86
|
+
`DIFFCTX_MAX_EDGE_CONTRIBUTIONS` (20M, counted before deduplication — the
|
|
87
|
+
number that owned 11 GB on a 10k-module monorepo, #196),
|
|
88
|
+
`DIFFCTX_MAX_SOURCE_BYTES` (256 MB of parsed source, never the changed
|
|
89
|
+
files themselves), `DIFFCTX_MAX_CANDIDATE_FILES` (200k) and
|
|
90
|
+
`DIFFCTX_MAX_NEEDS` (4k: every candidate is scored against every
|
|
91
|
+
information need mined from the diff, and a repository-sized diff produced
|
|
92
|
+
a need set that made selection take 413 s on this repository's own
|
|
93
|
+
history — 6 s under the cap). The caps are part of the effective
|
|
94
|
+
configuration and its hash.
|
|
95
|
+
|
|
96
|
+
### Security
|
|
97
|
+
|
|
98
|
+
- **Credential-shaped strings are redacted on every read surface** (#147).
|
|
99
|
+
A last pass over the diff artifact (fragments, commit messages), the
|
|
100
|
+
`--with-raw-diff` bundle, the MCP fetch and glob readers and tree mode
|
|
101
|
+
replaces strings matching a small set of high-confidence shapes — AWS
|
|
102
|
+
access key ids, GitHub, Slack, Stripe, Google and OpenAI keys, JWTs, PEM
|
|
103
|
+
private-key blocks — with `[REDACTED:<category>]`; the artifact carries a
|
|
104
|
+
`redactions` block (count, categories) and `sanitization_redaction` among
|
|
105
|
+
its coverage limit reasons. Defence in depth, not a guarantee: only shapes
|
|
106
|
+
that cannot be anything else are matched, and SECURITY.md says what is not
|
|
107
|
+
caught. The withhold policy (secret-by-name files, ignore rules) is
|
|
108
|
+
unchanged and remains the mechanism to rely on.
|
|
109
|
+
- **A `diff_ref` could turn the read-only MCP fetch into a file write.** With
|
|
110
|
+
`fragment_ids` set the engine never sees `diff_ref`, and `fetch_fragments`
|
|
111
|
+
passed whatever followed `..` straight to `git show <rev>:<path>` — so
|
|
112
|
+
`HEAD..--output=/tmp/x` wrote a file. A rev starting with `-` is refused and
|
|
113
|
+
`git show` runs with `--end-of-options`.
|
|
114
|
+
- **An explicit file argument bypassed the secret-path floor.** The tree walk
|
|
115
|
+
applies `is_secret_path` to every entry, but `diffctx id_rsa` and
|
|
116
|
+
`diffctx '**/*'` fed the expanded paths straight to the reader; the same
|
|
117
|
+
policy now applies to them, with a one-line notice of how many were
|
|
118
|
+
withheld.
|
|
119
|
+
- **The MCP allow-list is checked before the filesystem is.** `validate_repo_path`
|
|
120
|
+
probed `is_dir()` and walked up to `.git` before consulting
|
|
121
|
+
`DIFFCTX_ALLOWED_PATHS`, so a refused caller learned whether a path existed;
|
|
122
|
+
the order now matches what SECURITY.md promised.
|
|
123
|
+
- **The scratch git dir for non-repository reads is created exclusively.** Its
|
|
124
|
+
name was `temp_dir/diffctx-scratch-git-<pid>-<n>`, guessable, and `git init`
|
|
125
|
+
adopts an existing `.git` — a pre-planted one with its own `excludesFile`
|
|
126
|
+
decided what the reader called ignored. Created with `create_dir` (0700), a
|
|
127
|
+
collision moves to the next name.
|
|
128
|
+
- **A `.diffctx/ignore` policy that could not be read failed open.** When
|
|
129
|
+
`ls-files` or a pattern file read failed, the lookup reported "no patterns
|
|
130
|
+
declared" and a subsequent `check-ignore` failure landed on the fail-open
|
|
131
|
+
branch. Discovery failure now fails closed; a bare repository, which cannot
|
|
132
|
+
carry a policy, is the one exception.
|
|
133
|
+
|
|
134
|
+
- **An ignored directory's contents were readable through the MCP glob**
|
|
135
|
+
(regression introduced with #228, caught the same day). `withheld_paths`
|
|
136
|
+
reused the engine's *attribution* lookup, which deliberately drops
|
|
137
|
+
ancestor-inherited exclusions — a tracked file under a directory that
|
|
138
|
+
`--no-index` reports excluded is not really ignored (#153). For the diff,
|
|
139
|
+
whose paths are tracked by construction, that is right; for a reader walking
|
|
140
|
+
the working tree it is exactly wrong, because `.venv/x.py` is ignored *by*
|
|
141
|
+
the `.venv/` rule on its parent. `git check-ignore` called those paths
|
|
142
|
+
ignored while the engine called them servable, so `get_file_context` and
|
|
143
|
+
`diffctx_context` would return the contents of `.venv/`, `dist/`, `target/`
|
|
144
|
+
and any repository's `secrets/`. Fixed with a lookup that keeps
|
|
145
|
+
ancestor-inherited matches; `.git/` is withheld outright — it is not
|
|
146
|
+
gitignored at all, and `.git/config` carries the remote URL.
|
|
147
|
+
- **The glob reader applies the tree-mode noise filter again**: unifying it
|
|
148
|
+
onto the engine alone (#228) also dropped `node_modules/`, `target/` and
|
|
149
|
+
lock files from `get_file_context`, contradicting the tool's own
|
|
150
|
+
description. Both filters now apply there — the noise spec `get_tree_map`
|
|
151
|
+
uses, and the engine as the security floor. `fetch_fragments` keeps the
|
|
152
|
+
engine alone, which is what makes a ranked `build/gen.py` fetchable.
|
|
153
|
+
|
|
154
|
+
- **One secret-path policy for every surface** (#227): tree mode kept its own,
|
|
155
|
+
shorter list, so `.netrc`, `credentials`, `_netrc`, `.npmrc`, `.pypirc`,
|
|
156
|
+
`id_*_sk`, `*.ppk`, `*.p8` and `*.asc` — all withheld by diff mode — were
|
|
157
|
+
printed in full by `diffctx .`. Tree mode now asks the engine
|
|
158
|
+
(`_diffctx.is_secret_path`), unconditionally: `--no-default-ignores` drops
|
|
159
|
+
the noise filters, never the secret policy, the same contract diff mode has.
|
|
160
|
+
- **The MCP fetch refuses exactly what the engine withholds** (#228). It
|
|
161
|
+
re-derived admissibility with pathspec, so it served a `.netrc` the
|
|
162
|
+
selection had refused and refused a `build/gen.py` the selection had
|
|
163
|
+
ranked. `fragment_ids` and the legacy glob reader now ask the engine
|
|
164
|
+
(`_diffctx.withheld_paths`: secret by name, or ignored as `git check-ignore`
|
|
165
|
+
resolves `.gitignore` / `.diffctx/ignore`) in one batched call. Outside a
|
|
166
|
+
git work tree — the legacy reader accepts any directory — git runs with a
|
|
167
|
+
scratch `--git-dir`, so `.diffctx/ignore` binds there too.
|
|
168
|
+
|
|
169
|
+
### Fixed
|
|
170
|
+
|
|
171
|
+
- **A `---` bundle is every resource it holds, not its first** (#258). The
|
|
172
|
+
Kubernetes edge builder now reads one view per YAML document, so a
|
|
173
|
+
Service in the third document is indexed as a Service, its selector meets
|
|
174
|
+
the Deployment in the second, and two Deployments' labels no longer
|
|
175
|
+
collapse into one last-wins map. A generated manifest keeps its
|
|
176
|
+
`apiVersion:`/`kind:`/`metadata:` pairs through the generated-file cut,
|
|
177
|
+
which used to drop them as the shortest fragments and with them the
|
|
178
|
+
detection that made the file a manifest at all.
|
|
179
|
+
- **Scala imports, packages and inheritance come off the parse tree**
|
|
180
|
+
(#243). `tree-sitter-scala` already parsed every Scala file for
|
|
181
|
+
fragmentation; the edge layer re-read the text with a regex on its fifth
|
|
182
|
+
patch. `facts::scala` now walks `import_declaration` (braces, `=>` and
|
|
183
|
+
`as` renames, `_`/`*`/`given` wildcards, multi-line groups),
|
|
184
|
+
`package_clause` chains and `extends_clause` types; the regex reader
|
|
185
|
+
stays as the fallback for text that does not parse cleanly, and the two
|
|
186
|
+
are held to agree on every shape the tests name. An import inside a
|
|
187
|
+
block comment is no longer an import.
|
|
188
|
+
- **The compose `context:` and CI→`package.json` channels revived on
|
|
189
|
+
2026-09-02 were dead on arrival.** Both built their path reference from the
|
|
190
|
+
fragment's absolute path, while the fragment index is keyed repo-relative,
|
|
191
|
+
so nothing matched — and the CI walk, climbing to `/`, degraded to the bare
|
|
192
|
+
name and linked every `package.json` again. Both resolve in the index's own
|
|
193
|
+
key space now, the CI walk matches one exact path per level, and each
|
|
194
|
+
builder has a test with absolute paths and a repo root, the shape
|
|
195
|
+
production uses. Q-class: one overflow candidate moves on the bitcheck
|
|
196
|
+
fixture (its workflows run `npm`), nothing in the selection.
|
|
197
|
+
- **A plain directory whose scratch `git init` failed withheld every file**:
|
|
198
|
+
"no git dir" was read as "policy unreadable" and failed closed. It has no
|
|
199
|
+
policy to read; gitignore filtering is skipped there, secret-by-name still
|
|
200
|
+
applies.
|
|
201
|
+
- **A blank line after `selector:` let sibling keys back into the label
|
|
202
|
+
block**; the indentation bar is taken from the first line carrying a key.
|
|
203
|
+
- **`envelope_tokens` was computed before the lockfile/ignored lists it
|
|
204
|
+
summarises were set** on the empty-selection path.
|
|
205
|
+
- **`TestSetReport.n` reports instances attempted**, not survivors, so a cell
|
|
206
|
+
that failed on every instance no longer reads as "ran fine, recall 0".
|
|
207
|
+
- **The dev-loop harness honours `DIFFCTX_YAML_MIN_SCORE`** like the CI gate,
|
|
208
|
+
and `compare` / `aggregate-seeds` keep their exit code when run as scripts.
|
|
209
|
+
|
|
210
|
+
- **`stratified-analysis` returned no rows at all**: the 2026-09-01 fix that
|
|
211
|
+
taught it the multi-budget checkpoint layout left the `rows.append` under a
|
|
212
|
+
`continue` and the `return` inside the loop, so the first cell's rows were
|
|
213
|
+
never appended and every later cell was never visited. Sonar's
|
|
214
|
+
unreachable-code rule caught it; a test now feeds it two cells in both
|
|
215
|
+
layouts and expects every row back — it fails on the previous code.
|
|
216
|
+
- **The demo names what it left out.** Its output panel showed the selection
|
|
217
|
+
and nothing else; the real artifact lists omitted changed files and locate
|
|
218
|
+
mode reports overflow. The toy now prints which fragments did not fit the
|
|
219
|
+
budget and how many fell below τ, and a direct click on stage 5 renders
|
|
220
|
+
the selection instead of "0 tokens".
|
|
221
|
+
|
|
222
|
+
- **Edge builders that linked the wrong thing, or everything.** A compose
|
|
223
|
+
file's `context: .` stripped to `.` and `path.contains(".")` matched every
|
|
224
|
+
file with an extension — the compose file linked to the whole universe at
|
|
225
|
+
naming weight; `.` now means the compose file's own directory and a named
|
|
226
|
+
context takes the path channel's ambiguity bar. A Service's selector regex
|
|
227
|
+
kept consuming less-indented sibling keys (`type: ClusterIP`) and made each
|
|
228
|
+
a required label, so kubectl-ordered Services never matched their workload;
|
|
229
|
+
label blocks now end where their indentation does. `envFrom:
|
|
230
|
+
configMapRef/secretRef` — the standard way to inject a whole map — was not
|
|
231
|
+
a recognised reference. A shared base image linked every workload pairwise
|
|
232
|
+
at naming weight; the image channel takes the same eight-file bar as every
|
|
233
|
+
other reference channel and never links a manifest to itself. A CI file
|
|
234
|
+
mentioning `npm` linked to every `package.json` in the repository; it links
|
|
235
|
+
to the nearest one above it. A bare-name reference (`config.yaml`,
|
|
236
|
+
`main.go`) linked to the first fragment of the first same-named file; it
|
|
237
|
+
links each such file's representative, abstaining above the bar, and a
|
|
238
|
+
module directory (`import Database`) still resolves as a path. Cargo
|
|
239
|
+
entry-point edges landed on the last fragment of the target file instead
|
|
240
|
+
of its representative, and Ansible role siblings were emitted
|
|
241
|
+
fragment × fragment (22k emissions for two task files) — now one edge per
|
|
242
|
+
file pair through representatives, capped like directory siblings. All
|
|
243
|
+
Q-class; corpus 2902/2902, no baseline movement.
|
|
244
|
+
- **The eval harness's multi-budget checkpoints were invisible to
|
|
245
|
+
`stratified-analysis` and `backfill-checkpoints`**: both globbed only the
|
|
246
|
+
flat `<set>.checkpoint.jsonl`, so every diffctx cell of a full sweep
|
|
247
|
+
contributed zero rows. One discovery helper serves all three readers.
|
|
248
|
+
`--repos-dir` now relocates the bare-clone cache too (it read an
|
|
249
|
+
import-time constant and sent clones to the home directory while the disk
|
|
250
|
+
probe measured the flag's volume); `run-final` names its checkpoint after
|
|
251
|
+
the ablation parameters so cells sharing an `--out` stop resuming each
|
|
252
|
+
other's rows; `clone_fail`/`error` rows are re-evaluated on resume instead
|
|
253
|
+
of being checkpointed as verdicts; infrastructure failures are excluded
|
|
254
|
+
from the headline recall as `apply_gold_patch` always documented; per-depth
|
|
255
|
+
reports no longer pool under one name; `compare` and `aggregate-seeds`
|
|
256
|
+
exit non-zero on an unusable input.
|
|
257
|
+
|
|
258
|
+
- **A filename containing a newline desynchronised the cat-file batch**: the
|
|
259
|
+
request stream is line-delimited, so one such path became two requests and
|
|
260
|
+
every later file was served the previous request's leftover body. Paths with
|
|
261
|
+
control characters go through `git show` on argv instead.
|
|
262
|
+
- **The native binary without `--diff` ran a bare `git diff`** (index vs
|
|
263
|
+
worktree, staged edits invisible) while the pipeline read the same absence
|
|
264
|
+
as "vs HEAD"; both now mean HEAD, and git/environment failures exit 3 as the
|
|
265
|
+
README and the Python CLI already promised (was 1).
|
|
266
|
+
- **`diffctx . | head` exits 141 in tree and pack mode**, as documented; the
|
|
267
|
+
writer swallowed the broken pipe and the run exited 0.
|
|
268
|
+
- **`-o FILE` no longer downgrades the output to mode 0600** — the temp file
|
|
269
|
+
takes the mode the umask gives a new file before it replaces the target.
|
|
270
|
+
- **A fragment id past the end of its file is refused**, not silently clamped
|
|
271
|
+
to the last line.
|
|
272
|
+
- **The discovery walk fallback used the attribution ignore lookup**, so an
|
|
273
|
+
ignored directory's contents entered the universe when `ls-files` failed.
|
|
274
|
+
- **`--alpha` was documented backwards**: it is PPR's continuation
|
|
275
|
+
probability, so a higher value sends relevance further from the change.
|
|
276
|
+
README, CLI help and the Action docs said "tighter".
|
|
277
|
+
- **`--budget 0` was described four incompatible ways**; every surface now
|
|
278
|
+
says what happens — no fragments, changed files listed as omitted.
|
|
279
|
+
- **Three CI gates could not fail**: the sdist "contains Rust sources" check
|
|
280
|
+
returned `head`'s exit status, the results-branch push loop exited 0 after
|
|
281
|
+
three failed pushes, and the radon complexity step returned 0 whatever it
|
|
282
|
+
found (deleted; the pre-commit xenon hook is the gate). The sensitivity
|
|
283
|
+
workflow gets pipefail, the Hetzner cleanup no longer cancels itself, the
|
|
284
|
+
dead v1-artifact heartbeat is gone, and the release bump now patches
|
|
285
|
+
`CITATION.cff` (the test that asserts it equal to `__version__` would have
|
|
286
|
+
turned the next release red).
|
|
287
|
+
- **`bitcheck.sh`'s pinned commits are anchored by `bitcheck/<sha>` tags** on
|
|
288
|
+
both remotes — the history rewrite had left them reachable from nothing.
|
|
289
|
+
- **The corpus harness verifies `auto_garbage`** as SCHEMA.md promised; the
|
|
290
|
+
injection half existed alone. The pre-commit corpus sample covers every
|
|
291
|
+
stratum and runs on case edits.
|
|
292
|
+
|
|
293
|
+
- **`--budget` now covers the whole artifact** (#241). It bounded only the
|
|
294
|
+
fragments: the commit message and the changed/deleted/renamed/lockfile/
|
|
295
|
+
ignored path lists rendered for free, and the changed-file list was printed
|
|
296
|
+
twice — once in full, once again as a "not represented" footer. On an
|
|
297
|
+
83-file range `--budget 1000` produced 3 150 tokens and `--budget 0`
|
|
298
|
+
produced 2 464, with the selection dutifully under budget the whole time.
|
|
299
|
+
The summary is charged before selection starts and printed once, with
|
|
300
|
+
omitted entries marked in place. A budget too small to hold the summary now
|
|
301
|
+
yields the summary and no fragments — a changed path is never dropped to fit.
|
|
302
|
+
- **One generated file can no longer take the budget from every other changed
|
|
303
|
+
file** (#238). Cores that miss the core-budget reservation are swept
|
|
304
|
+
cheapest-first, and that sweep applied no per-file ceiling — so a changed
|
|
305
|
+
`records.json`, whose record-sized cores are the cheapest candidates in the
|
|
306
|
+
run, refilled the whole budget while the files it was deferred behind got
|
|
307
|
+
nothing. The sweep now runs in two rounds: the ceiling holds in the first,
|
|
308
|
+
and only what it blocked is replayed in the second, so unclaimed budget
|
|
309
|
+
still flows back.
|
|
310
|
+
- **The Kubernetes edge builder emitted nothing at all** (#226). Detection ran
|
|
311
|
+
per fragment and required `apiVersion:` and `kind:` in the same one, but the
|
|
312
|
+
YAML parser splits a manifest by top-level key — so no fragment of any
|
|
313
|
+
manifest ever qualified and all six channels (selector→workload, configmap,
|
|
314
|
+
secret, service, volume, image) were dead. Detection runs on the file, whose
|
|
315
|
+
text is rebuilt from its fragments by line number, and each edge is anchored
|
|
316
|
+
on the smallest fragment covering the construct it is about.
|
|
317
|
+
- **The deadline test measured the repository's history, not the deadline.**
|
|
318
|
+
It asked for `HEAD~20`, which stopped exceeding the ceiling when the history
|
|
319
|
+
was squashed into epochs; it now runs the root commit to HEAD, the largest
|
|
320
|
+
diff the repo can produce.
|
|
321
|
+
- **`python -m eval equivalence` could not fail** (#233): the CLI dispatcher
|
|
322
|
+
discarded each subcommand's return value, so the gate printed
|
|
323
|
+
"EQUIVALENCE FAILED" and exited 0. Any CI job wired to the documented
|
|
324
|
+
invocation was decorative.
|
|
325
|
+
|
|
326
|
+
- **A POSIX filename containing a backslash keeps its name and its own
|
|
327
|
+
content** (#239). Every path written into output — and the `rev:path` spec
|
|
328
|
+
handed to `git show` — rewrote `\` to `/`, so with `src\utils.py`
|
|
329
|
+
(changed) and `src/utils.py` (untouched) both present, the changed file was
|
|
330
|
+
listed under the other's name and rendered with the other's body. All
|
|
331
|
+
eight sites now go through `paths.rs`, which rewrites only on Windows.
|
|
332
|
+
- **The corpus harness anchors a pure deletion where git does.** The
|
|
333
|
+
in-memory diff started a deletion-only hunk one line below git's
|
|
334
|
+
`@@ -5 +4,0 @@`, so `core_selection_range` — and therefore every
|
|
335
|
+
deletion-carrying corpus case — scored a selection production never
|
|
336
|
+
produces. Pinned by a test that compares both sides on the same deletion.
|
|
337
|
+
- **A truncated generated fragment costs what it weighs**: it carried
|
|
338
|
+
`token_count: 0`, free for the budget while still rendering in full.
|
|
339
|
+
- **`cat-file --batch` stays in sync**: a swallowed record terminator used to
|
|
340
|
+
shift the stream one byte for every later read, and a child that could not
|
|
341
|
+
be queried counted as alive and was never restarted.
|
|
342
|
+
- **Naming only files roots the tree at their common parent**, not the
|
|
343
|
+
current directory, so `.diffctx/ignore` and relative paths resolve where
|
|
344
|
+
the files live; the wrapper node is named after that root instead of ".".
|
|
345
|
+
- **A failed native import no longer buries the error being handled**: the
|
|
346
|
+
CLI's git-error handler imported the extension inside the `except` clause.
|
|
347
|
+
- **The quotient-graph registry holds the graphs it keys by `id()`**; a
|
|
348
|
+
collected graph's recycled id could label the next graph with the wrong
|
|
349
|
+
node keys.
|
|
350
|
+
- Boltzmann objective: the selection reason is decided after the loop, cores
|
|
351
|
+
count toward the reported utility, and overlap goes through the shared
|
|
352
|
+
`IntervalIndex` instead of a fourth hand-written check.
|
|
353
|
+
|
|
354
|
+
### Changed
|
|
355
|
+
|
|
356
|
+
- **File-level relations land on one fragment per file, and a change
|
|
357
|
+
lifts to its file** (#196). An import, a directory sibling, a shared
|
|
358
|
+
package or crate root, a covering test are relations between files: the
|
|
359
|
+
Python, Rust and Go builders link the file's representative fragment (the
|
|
360
|
+
same one every builder names, ties now broken by position rather than by
|
|
361
|
+
the order a builder happened to hold the fragments in), and `from m import
|
|
362
|
+
x` / `pkg.Sym` / `Mod::sym` link the fragment that defines the named
|
|
363
|
+
symbol. In return the ego walk seeds each changed fragment's file
|
|
364
|
+
representative at the containment discount, so a function added to an
|
|
365
|
+
existing module still meets the module's importers and its tests. A name
|
|
366
|
+
used in more than 64 files without importing its definer (`hass`,
|
|
367
|
+
`config`, `entry`) is vocabulary and no longer fans out to its
|
|
368
|
+
definitions; qualified calls (`Type::new(`, `pkg.New(`), method calls
|
|
369
|
+
and definition lines no longer feed the unqualified call channel, which
|
|
370
|
+
linked every `::new(` to every `fn new` in reach; Go imports come from
|
|
371
|
+
the import block, not from any quoted line; Python relative imports
|
|
372
|
+
resolve to the sibling module they name. Same machine, same instances as
|
|
373
|
+
the issue's table: home-assistant 38.1M → 2.8M python edges, 100 s → 24 s,
|
|
374
|
+
10.9 → 2.5 GB; polars 35.4M → 0.8M rust edges, 54 s → 4 s, 5.4 → 0.8 GB;
|
|
375
|
+
kubernetes 14.3M → 4.9M go edges (the rest of that run is parsing).
|
|
376
|
+
Q-class: 16 corpus cases lift above the threshold, one `gap` case is
|
|
377
|
+
baselined (`gap_132`: the file lift admits a consumer of a sibling type).
|
|
378
|
+
Edge-weight profile `v2-2026-09-16`.
|
|
379
|
+
- **One heavy phase for the product and the corpus harness.** From the
|
|
380
|
+
fragments onward — token counts, cores and their stand-ins, signature
|
|
381
|
+
variants, seed weights, scoring, information needs — both paths call
|
|
382
|
+
`pipeline::score_from_fragments`; the harness's own spelling of those
|
|
383
|
+
steps is gone, and the `DIFFCTX_OBJECTIVE` override, which only the
|
|
384
|
+
product used to see, is resolved with the mode for every caller (#232).
|
|
385
|
+
- **The compute deadline is no longer a panic, and never an exception.**
|
|
386
|
+
`ComputeTimeoutError` stays importable but the engine does not raise it;
|
|
387
|
+
a git subprocess that overruns its share of the timeout is still a
|
|
388
|
+
`GitError`. The native CLI exits 0 with a partial artifact at the deadline;
|
|
389
|
+
124 is reserved for the watchdog that fires 30 s later when a phase could
|
|
390
|
+
not stop. `--timeout 0` is therefore a git failure (exit 3), not an abort.
|
|
391
|
+
|
|
392
|
+
- **The hero comparison names both baselines.** It measured whole changed
|
|
393
|
+
files pasted (17.6×) while the headline invites comparison with a raw
|
|
394
|
+
`git diff` — the question three first-visit probes actually asked. The raw
|
|
395
|
+
diff of the same commit is 169,809 tokens, so that ratio is 3.9×, and the
|
|
396
|
+
caption says so next to the number.
|
|
397
|
+
|
|
398
|
+
- **The `tree-sitter` extra is gone** (`full` = charset-normalizer alone):
|
|
399
|
+
it shipped seventeen grammar wheels nothing in the package imports — parsing
|
|
400
|
+
lives in the Rust crate. `uv.lock` shrinks by ~370 lines.
|
|
401
|
+
- **The landing page's copy now matches the tool**: five scoring modes, the
|
|
402
|
+
τ dial at the shipped 0.05, YAML labelled as the shown (not default)
|
|
403
|
+
format, α attributed to PPR, the toy's numbers reconciled with the hero
|
|
404
|
+
measurement and its denominator's generated snapshot named, `llms.txt`
|
|
405
|
+
naming the one tool that exists.
|
|
406
|
+
|
|
407
|
+
- **The landing page's headline comparison names its source.** It shipped
|
|
408
|
+
`48,210 → 6,930 tokens, 7×` with no provenance on the page and none in the
|
|
409
|
+
repository either; three independent first-visit probes each reached the
|
|
410
|
+
same verdict — the one number meant to justify adoption could not be checked.
|
|
411
|
+
It now reads 768,268 → 43,769 (17.6×), measured on
|
|
412
|
+
home-assistant/core@1d885bd0 (66 files changed) with the command printed
|
|
413
|
+
beside it, and says plainly that the ratio tracks how much of the changed
|
|
414
|
+
files is unrelated to the change. A test gates the provenance, not the value.
|
|
415
|
+
|
|
416
|
+
- **The GIL is released for the last three heavy extension calls** (#245):
|
|
417
|
+
`build_project_graph` (a full repository walk plus a tree-sitter parse of
|
|
418
|
+
every file), `resolve_diff_range` (spawns `git`) and `count_tokens`. Held
|
|
419
|
+
under the GIL these blocked every other thread in the host interpreter for
|
|
420
|
+
their whole duration, which is precisely the wall-clock a latency column is
|
|
421
|
+
supposed to attribute to diffctx.
|
|
422
|
+
- **The eval sweep no longer runs 45 duplicate cells** (#233): `depth` reaches
|
|
423
|
+
the runner only when it is non-negative, but `internal-bm25`, `rrf` and
|
|
424
|
+
`pit` had no depth excludes, so each ran five byte-identical copies of its
|
|
425
|
+
cell per test set — half the matrix, about a third of the machine hours. The
|
|
426
|
+
smoke cell also asserts its own result now instead of printing it, and is
|
|
427
|
+
handed `HF_TOKEN` so dataset reads are not rate-limited.
|
|
428
|
+
|
|
429
|
+
- **The eval harness's Python dependencies are a `uv` dependency group**
|
|
430
|
+
(`uv sync --group eval`), locked in `uv.lock` like everything else.
|
|
431
|
+
`requirements-eval.txt` + `.lock` — a second resolver with its own bot —
|
|
432
|
+
are gone; `Dockerfile.eval` exports the group with hashes.
|
|
433
|
+
- `paper/v1` left the tree (it lives at tag `paper-v1-grid`); five
|
|
434
|
+
never-called statistics helpers and `reset_to_commit` left `eval/`.
|
|
435
|
+
- Test hygiene: locate and pack are pinned to each other's `changed` set
|
|
436
|
+
(pack used to be compared with itself); the path-jail refusal property
|
|
437
|
+
runs on every example and registers the legacy tools once; the deadline
|
|
438
|
+
test requires the ceiling to fire; the panic-profile test reads the
|
|
439
|
+
release table only; the Rust and Python `.diffctx/ignore` anchorers share
|
|
440
|
+
one fixture table instead of a "mirrors" comment.
|
|
441
|
+
- **A git subprocess whose output could not be read no longer reports an
|
|
442
|
+
empty diff.** The pipe reader's failure collapsed into an empty buffer
|
|
443
|
+
returned as success, so an unreadable `git diff` was indistinguishable from
|
|
444
|
+
a range with no changes. It is an error now. `is_git_repo` likewise stops
|
|
445
|
+
answering "not a git repository" when git did not run at all (missing from
|
|
446
|
+
PATH, permission denied): only git's own "no" is a "no".
|
|
447
|
+
- **The weak stopping threshold never ships without the admission gate.**
|
|
448
|
+
`tau = 0.05` was calibrated with the per-file naming gate on (#65);
|
|
449
|
+
`--scoring bm25` has no graph to build a gate from and rode the weak stop
|
|
450
|
+
anyway, re-admitting exactly the diffuse tail the gate blocks. A scorer that
|
|
451
|
+
produces no gate — BM25, or `DIFFCTX_FILE_ADMISSION=0` — now uses the
|
|
452
|
+
pre-gate threshold (0.12) whenever `--tau` is left at its default value
|
|
453
|
+
(an explicit `--tau 0.05` is indistinguishable from the default and gets the
|
|
454
|
+
same substitution; any other explicit value is used as given).
|
|
455
|
+
- **A range whose changed-file list is empty still discloses what it
|
|
456
|
+
withheld**: the third early exit in `resolve_change_set` returned zeroed
|
|
457
|
+
lock-file, ignored and policy counts it had already computed.
|
|
458
|
+
- **Rust lifetimes no longer break signature extraction.** `'` was a string
|
|
459
|
+
opener in every language; `fn f<'a>(x: &'a T)` has no closing quote, the
|
|
460
|
+
scanner swallowed the rest of the line and lost the parameter list, and the
|
|
461
|
+
stub fell back to two lines cut mid-signature.
|
|
462
|
+
- **`.env` and `.editorconfig` are recognized.** They were registered as
|
|
463
|
+
extensions, and a dotfile has none, so neither ever entered the candidate
|
|
464
|
+
universe. A bare lowercase `build` or `workspace` is no longer classified as
|
|
465
|
+
Bazel — only the capitalized names Bazel actually uses.
|
|
466
|
+
- **Scala imports inside an unbalanced brace group are kept.** The
|
|
467
|
+
multi-line join consumed up to 32 lines and never re-scanned them, so an
|
|
468
|
+
`import` in that window vanished; it now stops at the next `import` line.
|
|
469
|
+
Scala 3 `as` renames split on any whitespace, not the literal single space.
|
|
470
|
+
- **The C-family pairing loop polls the deadline per pair, not per stem
|
|
471
|
+
bucket** (#210): envoy's 520 files sharing one stem are a single bucket, so
|
|
472
|
+
the bucket-level poll never fired inside the case it was added for.
|
|
473
|
+
- `scripts/bitcheck.sh` builds the binary it measures. It read whatever
|
|
474
|
+
`target/release/diffctx` the last `cargo test --release` had left, so a
|
|
475
|
+
`record`/`check` pair around an unbuilt edit compared one stale binary with
|
|
476
|
+
itself.
|
|
477
|
+
- The MCP server warns at startup when `DIFFCTX_ALLOWED_PATHS` is unset, so
|
|
478
|
+
running unconfined is a choice the log records rather than a default nobody
|
|
479
|
+
sees.
|
|
480
|
+
- CI: `sensitivity-check` installs the pinned Rust toolchain before syncing
|
|
481
|
+
(the sync compiles the extension); a release run that skipped PyPI attaches
|
|
482
|
+
its wheels and sdist to the GitHub release instead of shipping none.
|
|
483
|
+
- **An expired compute deadline no longer kills the process.** The release
|
|
484
|
+
profile set `panic = "abort"`, so the deadline — which fires as a panic on
|
|
485
|
+
purpose, because the phases it guards return no `Result` — reached a
|
|
486
|
+
published wheel as SIGABRT instead of an error (measured: signal 6 from a
|
|
487
|
+
`pip install`ed 1.15.0 wheel). For the MCP server, which abandons a
|
|
488
|
+
timed-out worker thread and keeps serving, that took every concurrent
|
|
489
|
+
request down with it. The profile now unwinds, which is what lets PyO3
|
|
490
|
+
convert a panic into a Python exception at all, and the deadline arrives as
|
|
491
|
+
`TimeoutError` (`_diffctx.ComputeTimeoutError`) that a caller can catch. The
|
|
492
|
+
test profile is now the shipped profile: the old `release-unwind` clone is
|
|
493
|
+
exactly what let this mechanism pass its own test while aborting in
|
|
494
|
+
production.
|
|
495
|
+
- **A timeout too small for git to answer no longer accuses the repository.**
|
|
496
|
+
`is_git_repo` collapsed every failure into "false", so `timeout=0` reported
|
|
497
|
+
a valid repo as "not a git repository" instead of saying it timed out.
|
|
498
|
+
- **The `changed` role is recorded where the substitution happens** (#209),
|
|
499
|
+
not re-derived from a shared start line in three places (pack render,
|
|
500
|
+
`locate`, and the post-pass coverage rank, the last scanning every core id
|
|
501
|
+
per candidate). `SelectionResult::stand_in_ids` carries the fact and
|
|
502
|
+
`types::carries_change` is the single predicate.
|
|
503
|
+
- **`<script type=>` is read from the AST** rather than scanned out of the
|
|
504
|
+
start tag's text (#213), so `type = "..."` with spaces around the `=` and
|
|
505
|
+
single-quoted values are handled by the grammar instead of by hand.
|
|
506
|
+
|
|
507
|
+
- **The per-file admission gate (#65) now covers every selection path**
|
|
508
|
+
(#211): both post-selection passes and both singleton comparators used to
|
|
509
|
+
admit files the greedy was forbidden to open. The rescue pass keeps a
|
|
510
|
+
measured escape — when the selection holds no context at all, an admissible
|
|
511
|
+
answer beats an empty one. Singletons admit via a wider declared-relation
|
|
512
|
+
reachability (config keys, latex/terraform references) so weak-but-declared
|
|
513
|
+
channels are not starved. Net on the 2725-case oracle corpus: 133 cases
|
|
514
|
+
fixed, 6 explained regressions (`known_below_threshold.txt` 195 → 68).
|
|
515
|
+
- **`--alpha` now actually reaches PPR scoring** (it was validated and then
|
|
516
|
+
silently discarded; every run used the default damping regardless).
|
|
517
|
+
- **A fragment deferred by the per-file ceiling still sets the stopping
|
|
518
|
+
peak** (#197): the tau bar was being calibrated on whatever foreign-file
|
|
519
|
+
fragment came after the deferred argmax.
|
|
520
|
+
- **Test-file imports are read from every fragment of the file** (#217):
|
|
521
|
+
module-level imports land in a fragment appended after the definitions, so
|
|
522
|
+
the old first-fragment read never saw them and the `test_direct` weight
|
|
523
|
+
never fired for tree-sitter languages. Test-naming edges also join the
|
|
524
|
+
naming class, so a `widget_test.go` with no import is admissible context.
|
|
525
|
+
|
|
526
|
+
## [1.15.0] - 2026-08-19
|
|
527
|
+
|
|
528
|
+
### Added
|
|
529
|
+
|
|
530
|
+
- **Changed files that produced no fragments are disclosed in the md/text
|
|
531
|
+
output** ("Changed files not represented in the output"): a range whose
|
|
532
|
+
selection dropped some changed files no longer hides the gap — the footer
|
|
533
|
+
names exactly the changed files absent from the fragment list. Derived
|
|
534
|
+
from the already policy-clean `changed_files`, so withheld paths can never
|
|
535
|
+
appear in it.
|
|
536
|
+
|
|
537
|
+
### Fixed
|
|
538
|
+
|
|
539
|
+
- **Path-reference ambiguity cap counted duplicate index keys** (#207): every
|
|
540
|
+
file was indexed under its absolute AND repo-relative path, so
|
|
541
|
+
`MAX_FILES_PER_PATH_REF` (8) effectively halved, and host directory
|
|
542
|
+
components (`/home/runner/work/...`) matched references they had nothing
|
|
543
|
+
to do with. The index now carries one repo-relative posix entry per file.
|
|
544
|
+
- **`<script>` type detection** (#213): `data-type="json"` no longer opts a
|
|
545
|
+
real JavaScript block out of parsing, and a legal MIME parameter
|
|
546
|
+
(`type="text/javascript; charset=utf-8"`) no longer degrades the block to
|
|
547
|
+
raw line windows.
|
|
548
|
+
- **Scala import/package extraction gaps** (#215): Scala 3 renames
|
|
549
|
+
(`import a.b.C as D`, `a.{B as C}`) keep the original name, multi-line
|
|
550
|
+
brace imports keep their selectors, and nested package blocks no longer
|
|
551
|
+
concatenate sibling scopes into a nonexistent package.
|
|
552
|
+
- **Rust `pub use` re-export registration no longer depends on file
|
|
553
|
+
iteration order** (#199): registration now runs after the definition maps
|
|
554
|
+
are complete, so a re-export defined in a later-iterated file still
|
|
555
|
+
resolves (`rust_027_arc_mutex` left the corpus baseline on this fix).
|
|
556
|
+
- **The per-file budget ceiling now also gates signature substitution**
|
|
557
|
+
(#212): a file at its ceiling can no longer keep placing signature stubs
|
|
558
|
+
ahead of other files' cores.
|
|
559
|
+
- **A signature stub substituted for a changed core renders as `changed`**
|
|
560
|
+
(#209): the stub shares the core's location, and both surfaces (pack render
|
|
561
|
+
and locate) now agree it carries the change, same as an excerpt. Ordinary
|
|
562
|
+
context signatures stay context.
|
|
563
|
+
- **Config-key word boundaries treat non-ASCII bytes as word bytes** (#216):
|
|
564
|
+
a config key ending exactly where a non-ASCII letter begins (e.g. inside
|
|
565
|
+
`tokenización`) is no longer a match, restoring the Unicode-`\b` rejection
|
|
566
|
+
the Aho-Corasick rewrite lost.
|
|
567
|
+
|
|
568
|
+
## [1.14.0] - 2026-08-16
|
|
569
|
+
|
|
10
570
|
### Fixed
|
|
11
571
|
|
|
12
572
|
- **Changed secret-classified files (`id_rsa`, `*.key`, `*.pem`, …) are now
|
|
@@ -125,8 +685,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
125
685
|
precision, because BM25 gives any generic-token match a small positive score
|
|
126
686
|
and `1/(k + rank)` promotes it to real fused mass. `pit` keeps the position
|
|
127
687
|
instead, via each component's empirical CDF: a fragment in the 5th percentile
|
|
128
|
-
of a signal contributes 0.05 from it, so two weak opinions cannot manufacture
|
|
129
|
-
strong candidate. `score = blend·PIT(ego) + (1-blend)·PIT(bm25) + bonus·[both
|
|
688
|
+
of a signal contributes 0.05 from it, so two weak opinions cannot manufacture
|
|
689
|
+
a strong candidate. `score = blend·PIT(ego) + (1-blend)·PIT(bm25) + bonus·[both
|
|
130
690
|
in top-k]`, with `DIFFCTX_PIT_BLEND=0.65`, `DIFFCTX_PIT_AGREEMENT_BONUS=0.10`,
|
|
131
691
|
`DIFFCTX_PIT_AGREEMENT_TOP_K=20`. Measured on the full corpus it recovers 40 of
|
|
132
692
|
those 79 cases (`rrf` 450 below-threshold, `pit` 410) and still trails `ego`
|
|
@@ -839,9 +1399,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
839
1399
|
mapping and `--diff` context, since such material is never legitimate LLM
|
|
840
1400
|
context: `*.pem`, `*.key`, `*.pfx`, `*.p12`, `*.keystore`, `*.jks`, and SSH
|
|
841
1401
|
private keys `id_rsa`/`id_dsa`/`id_ecdsa`/`id_ed25519` (public `.pub` keys stay
|
|
842
|
-
visible). The `--diff` path previously applied no ignore filtering at all, so
|
|
843
|
-
changed key file would have leaked into context. Use `--no-default-ignores`
|
|
844
|
-
opt out of tree-mode default ignores. (`.env` files are intentionally still
|
|
1402
|
+
visible). The `--diff` path previously applied no ignore filtering at all, so
|
|
1403
|
+
a changed key file would have leaked into context. Use `--no-default-ignores`
|
|
1404
|
+
to opt out of tree-mode default ignores. (`.env` files are intentionally still
|
|
845
1405
|
included — a changed `.env` is legitimate change context; redacting secret
|
|
846
1406
|
*values* is a separate planned content-scan feature.)
|
|
847
1407
|
|
|
@@ -991,7 +1551,10 @@ Earlier releases shipped as `treemapper`; see
|
|
|
991
1551
|
<https://github.com/nikolay-e/diffctx/releases> for the corresponding GitHub
|
|
992
1552
|
release notes (`1.0.0` through `1.6.1`).
|
|
993
1553
|
|
|
994
|
-
[Unreleased]: https://github.com/nikolay-e/diffctx/compare/v1.
|
|
1554
|
+
[Unreleased]: https://github.com/nikolay-e/diffctx/compare/v1.16.0...HEAD
|
|
1555
|
+
[1.16.0]: https://github.com/nikolay-e/diffctx/compare/v1.15.0...v1.16.0
|
|
1556
|
+
[1.15.0]: https://github.com/nikolay-e/diffctx/compare/v1.14.0...v1.15.0
|
|
1557
|
+
[1.14.0]: https://github.com/nikolay-e/diffctx/compare/v1.13.0...v1.14.0
|
|
995
1558
|
[1.13.0]: https://github.com/nikolay-e/diffctx/compare/v1.12.3...v1.13.0
|
|
996
1559
|
[1.12.3]: https://github.com/nikolay-e/diffctx/compare/v1.12.2...v1.12.3
|
|
997
1560
|
[1.12.2]: https://github.com/nikolay-e/diffctx/compare/v1.12.1...v1.12.2
|