diffctx 1.9.1__tar.gz → 1.10.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {diffctx-1.9.1 → diffctx-1.10.1}/CHANGELOG.md +75 -1
- {diffctx-1.9.1 → diffctx-1.10.1}/PKG-INFO +7 -4
- {diffctx-1.9.1 → diffctx-1.10.1}/README.md +6 -3
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/Cargo.lock +23 -57
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/Cargo.toml +4 -4
- diffctx-1.10.1/diffctx/src/config/budget.rs +30 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/src/config/limits.rs +8 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/src/edges/semantic/python.rs +7 -5
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/src/edges/structural/testing.rs +1 -1
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/src/filtering.rs +2 -1
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/src/fragmentation.rs +20 -5
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/src/git.rs +22 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/src/graph.rs +5 -1
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/src/main.rs +47 -11
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/src/memory_pipeline.rs +1 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/src/parsers/tree_sitter_strategy.rs +19 -1
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/src/pipeline.rs +30 -3
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/src/postpass.rs +105 -2
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/src/project_graph.rs +2 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/src/select.rs +4 -1
- {diffctx-1.9.1 → diffctx-1.10.1}/pyproject.toml +3 -3
- {diffctx-1.9.1 → diffctx-1.10.1}/src/diffctx/cli.py +3 -3
- {diffctx-1.9.1 → diffctx-1.10.1}/src/diffctx/diffctx/pipeline.py +2 -2
- {diffctx-1.9.1 → diffctx-1.10.1}/src/diffctx/ignore.py +14 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/src/diffctx/main.py +2 -2
- diffctx-1.10.1/src/diffctx/mcp/README.md +142 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/src/diffctx/mcp/__main__.py +1 -1
- {diffctx-1.9.1 → diffctx-1.10.1}/src/diffctx/mcp/server.py +15 -8
- diffctx-1.10.1/src/diffctx/version.py +1 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/src/diffctx/writer.py +39 -25
- diffctx-1.9.1/diffctx/src/config/budget.rs +0 -21
- diffctx-1.9.1/src/diffctx/mcp/README.md +0 -104
- diffctx-1.9.1/src/diffctx/version.py +0 -1
- {diffctx-1.9.1 → diffctx-1.10.1}/LICENSE +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/src/analytics.rs +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/src/candidate_files.rs +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/src/config/analytics.rs +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/src/config/bm25.rs +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/src/config/category_weights.rs +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/src/config/edge_weights.rs +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/src/config/env_overrides.rs +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/src/config/extensions.rs +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/src/config/filtering.rs +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/src/config/fragmentation.rs +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/src/config/git.rs +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/src/config/graph_filtering.rs +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/src/config/importance.rs +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/src/config/mod.rs +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/src/config/mode.rs +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/src/config/needs.rs +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/src/config/parsers.rs +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/src/config/render.rs +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/src/config/scoring.rs +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/src/config/selection.rs +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/src/config/tokenization.rs +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/src/config/weights.rs +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/src/core.rs +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/src/discovery.rs +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/src/edges/base.rs +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/src/edges/config_edges/build_system.rs +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/src/edges/config_edges/cicd.rs +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/src/edges/config_edges/docker.rs +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/src/edges/config_edges/generic.rs +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/src/edges/config_edges/helm.rs +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/src/edges/config_edges/kubernetes.rs +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/src/edges/config_edges/mod.rs +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/src/edges/document/mod.rs +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/src/edges/history/cochange.rs +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/src/edges/history/mod.rs +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/src/edges/mod.rs +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/src/edges/semantic/ansible.rs +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/src/edges/semantic/bazel.rs +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/src/edges/semantic/c_family.rs +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/src/edges/semantic/cargo_edges.rs +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/src/edges/semantic/clojure.rs +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/src/edges/semantic/css.rs +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/src/edges/semantic/dart.rs +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/src/edges/semantic/dbt.rs +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/src/edges/semantic/dotnet.rs +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/src/edges/semantic/elixir.rs +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/src/edges/semantic/erlang.rs +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/src/edges/semantic/go.rs +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/src/edges/semantic/graphql.rs +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/src/edges/semantic/haskell.rs +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/src/edges/semantic/javascript.rs +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/src/edges/semantic/julia.rs +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/src/edges/semantic/jvm.rs +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/src/edges/semantic/latex.rs +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/src/edges/semantic/lua.rs +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/src/edges/semantic/mod.rs +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/src/edges/semantic/nim.rs +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/src/edges/semantic/nix.rs +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/src/edges/semantic/ocaml.rs +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/src/edges/semantic/openapi.rs +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/src/edges/semantic/perl.rs +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/src/edges/semantic/php.rs +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/src/edges/semantic/prisma.rs +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/src/edges/semantic/protobuf.rs +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/src/edges/semantic/r_lang.rs +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/src/edges/semantic/ruby.rs +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/src/edges/semantic/rust_lang.rs +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/src/edges/semantic/shell.rs +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/src/edges/semantic/sql.rs +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/src/edges/semantic/swift.rs +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/src/edges/semantic/tags.rs +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/src/edges/semantic/terraform.rs +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/src/edges/semantic/zig.rs +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/src/edges/similarity/lexical.rs +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/src/edges/similarity/mod.rs +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/src/edges/structural/containment.rs +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/src/edges/structural/mod.rs +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/src/edges/structural/sibling.rs +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/src/graph_export.rs +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/src/interval.rs +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/src/languages.rs +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/src/lib.rs +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/src/mode.rs +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/src/parsers/config_parser.rs +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/src/parsers/generic.rs +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/src/parsers/markdown.rs +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/src/parsers/mod.rs +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/src/ppr.rs +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/src/pybridge.rs +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/src/render.rs +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/src/scoring.rs +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/src/signatures.rs +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/src/stopwords.rs +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/src/test_harness.rs +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/src/tokenizer.rs +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/src/types.rs +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/src/utility/boltzmann.rs +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/src/utility/importance.rs +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/src/utility/mod.rs +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/src/utility/needs.rs +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/src/utility/scoring.rs +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/tests/common/mod.rs +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/tests/fixtures/garbage/garbage_api.py +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/tests/fixtures/garbage/garbage_constants.py +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/tests/fixtures/garbage/garbage_handlers.py +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/tests/fixtures/garbage/garbage_models.py +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/tests/fixtures/garbage/garbage_module.js +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/tests/fixtures/garbage/garbage_services.py +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/tests/fixtures/garbage/garbage_types.py +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/tests/fixtures/garbage/garbage_unrelated.yaml +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/tests/fixtures/garbage/garbage_utils.py +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/tests/fixtures/garbage/garbage_validators.py +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/diffctx/tests/yaml_cases.rs +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/rust-toolchain.toml +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/src/diffctx/__init__.py +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/src/diffctx/__main__.py +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/src/diffctx/clipboard.py +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/src/diffctx/diffctx/__init__.py +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/src/diffctx/diffctx/graph_analytics.py +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/src/diffctx/diffctx/graph_export.py +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/src/diffctx/diffctx/project_graph.py +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/src/diffctx/logger.py +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/src/diffctx/mcp/__init__.py +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/src/diffctx/mcp/formatting.py +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/src/diffctx/mcp/security.py +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/src/diffctx/py.typed +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/src/diffctx/tokens.py +0 -0
- {diffctx-1.9.1 → diffctx-1.10.1}/src/diffctx/tree.py +0 -0
|
@@ -7,7 +7,81 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
-
## [1.
|
|
10
|
+
## [1.10.0] - 2026-06-20
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- The default `--tau` (stopping threshold) is now **0.12** across the CLI, MCP,
|
|
15
|
+
and Python API — the calibrated grid optimum — bringing the Python side in
|
|
16
|
+
line with the standalone Rust binary (was 0.08). Diff-context selections are
|
|
17
|
+
slightly tighter by default.
|
|
18
|
+
- The MCP `get_tree_map` / `get_file_context` default `max_file_bytes` is now
|
|
19
|
+
256 KB, matching the documented CLI default (was 100 KB).
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
|
|
23
|
+
- Document/citation edges can no longer create self-edges (a fragment linking to
|
|
24
|
+
itself); `Graph::add_edge` now rejects `src == dst`.
|
|
25
|
+
- Test→source import edges no longer match `import` statements that appear inside
|
|
26
|
+
comments or string literals (the import regex is now line-anchored).
|
|
27
|
+
- The MCP `get_file_context` clipboard confirmation now reports the number of
|
|
28
|
+
files actually copied, excluding files skipped for exceeding the size cap.
|
|
29
|
+
|
|
30
|
+
### Docs
|
|
31
|
+
|
|
32
|
+
- Corrected the AST-parsing language count (was "12 languages", now "30+"),
|
|
33
|
+
aligned the Rust crate version with the wheel, completed the MCP server README
|
|
34
|
+
(all three tools documented), and fixed the documented `EGO.per_hop_decay`
|
|
35
|
+
default (0.5). Documented the `scoring_mode` / `timeout` Python API params,
|
|
36
|
+
clarified `--budget` / `--tau` help text, and marked the `DIFFCTX_OP_*` /
|
|
37
|
+
`DIFFCTX_*` tuning env vars as an experimental, non-public interface.
|
|
38
|
+
|
|
39
|
+
## [1.9.2] - 2026-06-14
|
|
40
|
+
|
|
41
|
+
### Added
|
|
42
|
+
|
|
43
|
+
- Private-key and keystore files are now excluded from output in **both** tree
|
|
44
|
+
mapping and `--diff` context, since such material is never legitimate LLM
|
|
45
|
+
context: `*.pem`, `*.key`, `*.pfx`, `*.p12`, `*.keystore`, `*.jks`, and SSH
|
|
46
|
+
private keys `id_rsa`/`id_dsa`/`id_ecdsa`/`id_ed25519` (public `.pub` keys stay
|
|
47
|
+
visible). The `--diff` path previously applied no ignore filtering at all, so a
|
|
48
|
+
changed key file would have leaked into context. Use `--no-default-ignores` to
|
|
49
|
+
opt out of tree-mode default ignores. (`.env` files are intentionally still
|
|
50
|
+
included — a changed `.env` is legitimate change context; redacting secret
|
|
51
|
+
*values* is a separate planned content-scan feature.)
|
|
52
|
+
|
|
53
|
+
### Fixed
|
|
54
|
+
|
|
55
|
+
- **Diffs touching files larger than 100 KB no longer produce empty output.** A
|
|
56
|
+
changed file (e.g. a 142 KB `Math.h`) was subject to the same 100 KB cap as
|
|
57
|
+
context-discovery candidates, so the whole diff yielded "no semantic context".
|
|
58
|
+
Changed files — the subject of the diff — are now parsed up to 5 MB.
|
|
59
|
+
- **C/C++ symbol names** for variable declarations now report the variable, not
|
|
60
|
+
the type: a changed `const unsigned blane = …` is labeled `blane`, not
|
|
61
|
+
`unsigned`. Covers `init`/`array`/pointer/function declarators and C++
|
|
62
|
+
`reference`/`parenthesized` declarators (`int &r`, `int (*f)()`).
|
|
63
|
+
- Changed-file fragments are no longer dropped by the generated-file reduction
|
|
64
|
+
(cap of 5 + 30-line content truncation), which could discard the small
|
|
65
|
+
fragment covering the edited hunk and mislocate the change.
|
|
66
|
+
- Binary detection no longer misclassifies changed text files that embed ANSI
|
|
67
|
+
escape / control bytes (snapshot and terminal-recording fixtures); it now uses
|
|
68
|
+
git's NUL-byte heuristic, so such files are no longer silently dropped.
|
|
69
|
+
- Diff-context selection is now deterministic across processes: ego-graph score
|
|
70
|
+
accumulation, context-fragment capping, and the greedy selection heap use
|
|
71
|
+
stable tie-breaks, so identical inputs always yield byte-identical output.
|
|
72
|
+
- Python module-import edges are bidirectional (matching every other language
|
|
73
|
+
and Python's own symbol references), so a changed imported module now
|
|
74
|
+
propagates relevance back to its importers.
|
|
75
|
+
|
|
76
|
+
### Hardened
|
|
77
|
+
|
|
78
|
+
- `git cat-file` blob reads are bounded (drain-and-reject above 16 MB) instead of
|
|
79
|
+
allocating an arbitrarily large buffer up front.
|
|
80
|
+
|
|
81
|
+
## [1.9.1] - 2026-06-14
|
|
82
|
+
|
|
83
|
+
> Supersedes 1.9.0, which was tagged but never published to PyPI (release-process
|
|
84
|
+
> hiccup); 1.9.1 ships the same changes. There is no 1.9.0 on PyPI.
|
|
11
85
|
|
|
12
86
|
### Added
|
|
13
87
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: diffctx
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.10.1
|
|
4
4
|
Classifier: Development Status :: 5 - Production/Stable
|
|
5
5
|
Classifier: Environment :: Console
|
|
6
6
|
Classifier: Intended Audience :: Developers
|
|
@@ -127,7 +127,7 @@ commit, instead of dumping every changed file in full.*
|
|
|
127
127
|
[releases page](https://github.com/nikolay-e/diffctx/releases/latest).
|
|
128
128
|
|
|
129
129
|
> Diff context mode works out of the box. Adding `[tree-sitter]` enables AST-level
|
|
130
|
-
> parsing for more accurate context selection across
|
|
130
|
+
> parsing for more accurate context selection across 30+ languages.
|
|
131
131
|
|
|
132
132
|
## Diff Context Mode
|
|
133
133
|
|
|
@@ -170,7 +170,7 @@ emitted, whichever comes first.
|
|
|
170
170
|
| `--scoring` | `ego` | Scoring mode: `ego`, `ppr`, or `bm25` |
|
|
171
171
|
| `--budget` | auto | Token cap. `auto` lets selection converge; `-1` disables the cap; `N` enforces a fixed cap |
|
|
172
172
|
| `--alpha` | 0.60 | How tightly context clusters around changes (PPR damping; 0–1, higher = more focused) |
|
|
173
|
-
| `--tau` | 0.
|
|
173
|
+
| `--tau` | 0.12 | Minimum relevance required to include a fragment (lower = more context) |
|
|
174
174
|
| `--full` | false | Include every changed fragment; skip the smart-selection step entirely |
|
|
175
175
|
|
|
176
176
|
Calibration of `--alpha`, `--tau`, and the edge-weight priors is documented
|
|
@@ -209,6 +209,7 @@ diffctx . --max-depth 3 # limit depth
|
|
|
209
209
|
diffctx . -i custom.ignore # custom ignore patterns
|
|
210
210
|
|
|
211
211
|
# diff context mode (requires git repo):
|
|
212
|
+
diffctx . --diff # uncommitted changes (working tree vs HEAD)
|
|
212
213
|
diffctx . --diff HEAD~1 # context for last commit
|
|
213
214
|
diffctx . --diff main..feature # context for feature branch
|
|
214
215
|
diffctx . --diff HEAD~1 --budget 30000 # limit to ~30k tokens
|
|
@@ -302,8 +303,10 @@ ctx = build_diff_context(
|
|
|
302
303
|
# <0 = unlimited (10M-token soft ceiling)
|
|
303
304
|
# >0 = explicit token cap
|
|
304
305
|
alpha=0.6, # PPR damping factor
|
|
305
|
-
tau=0.
|
|
306
|
+
tau=0.12, # stopping threshold
|
|
306
307
|
full=False, # skip smart selection
|
|
308
|
+
scoring_mode="ego", # "ego" (default), "ppr", or "bm25"
|
|
309
|
+
timeout=300, # seconds before the pipeline aborts
|
|
307
310
|
)
|
|
308
311
|
yaml_str = to_yaml(ctx)
|
|
309
312
|
```
|
|
@@ -50,7 +50,7 @@ commit, instead of dumping every changed file in full.*
|
|
|
50
50
|
[releases page](https://github.com/nikolay-e/diffctx/releases/latest).
|
|
51
51
|
|
|
52
52
|
> Diff context mode works out of the box. Adding `[tree-sitter]` enables AST-level
|
|
53
|
-
> parsing for more accurate context selection across
|
|
53
|
+
> parsing for more accurate context selection across 30+ languages.
|
|
54
54
|
|
|
55
55
|
## Diff Context Mode
|
|
56
56
|
|
|
@@ -93,7 +93,7 @@ emitted, whichever comes first.
|
|
|
93
93
|
| `--scoring` | `ego` | Scoring mode: `ego`, `ppr`, or `bm25` |
|
|
94
94
|
| `--budget` | auto | Token cap. `auto` lets selection converge; `-1` disables the cap; `N` enforces a fixed cap |
|
|
95
95
|
| `--alpha` | 0.60 | How tightly context clusters around changes (PPR damping; 0–1, higher = more focused) |
|
|
96
|
-
| `--tau` | 0.
|
|
96
|
+
| `--tau` | 0.12 | Minimum relevance required to include a fragment (lower = more context) |
|
|
97
97
|
| `--full` | false | Include every changed fragment; skip the smart-selection step entirely |
|
|
98
98
|
|
|
99
99
|
Calibration of `--alpha`, `--tau`, and the edge-weight priors is documented
|
|
@@ -132,6 +132,7 @@ diffctx . --max-depth 3 # limit depth
|
|
|
132
132
|
diffctx . -i custom.ignore # custom ignore patterns
|
|
133
133
|
|
|
134
134
|
# diff context mode (requires git repo):
|
|
135
|
+
diffctx . --diff # uncommitted changes (working tree vs HEAD)
|
|
135
136
|
diffctx . --diff HEAD~1 # context for last commit
|
|
136
137
|
diffctx . --diff main..feature # context for feature branch
|
|
137
138
|
diffctx . --diff HEAD~1 --budget 30000 # limit to ~30k tokens
|
|
@@ -225,8 +226,10 @@ ctx = build_diff_context(
|
|
|
225
226
|
# <0 = unlimited (10M-token soft ceiling)
|
|
226
227
|
# >0 = explicit token cap
|
|
227
228
|
alpha=0.6, # PPR damping factor
|
|
228
|
-
tau=0.
|
|
229
|
+
tau=0.12, # stopping threshold
|
|
229
230
|
full=False, # skip smart selection
|
|
231
|
+
scoring_mode="ego", # "ego" (default), "ppr", or "bm25"
|
|
232
|
+
timeout=300, # seconds before the pipeline aborts
|
|
230
233
|
)
|
|
231
234
|
yaml_str = to_yaml(ctx)
|
|
232
235
|
```
|
|
@@ -194,7 +194,7 @@ checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
|
|
|
194
194
|
|
|
195
195
|
[[package]]
|
|
196
196
|
name = "diffctx"
|
|
197
|
-
version = "1.
|
|
197
|
+
version = "1.10.0"
|
|
198
198
|
dependencies = [
|
|
199
199
|
"anyhow",
|
|
200
200
|
"clap",
|
|
@@ -355,12 +355,6 @@ version = "0.5.0"
|
|
|
355
355
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
356
356
|
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
|
357
357
|
|
|
358
|
-
[[package]]
|
|
359
|
-
name = "hermit-abi"
|
|
360
|
-
version = "0.5.2"
|
|
361
|
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
362
|
-
checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c"
|
|
363
|
-
|
|
364
358
|
[[package]]
|
|
365
359
|
name = "id-arena"
|
|
366
360
|
version = "2.3.0"
|
|
@@ -411,23 +405,23 @@ checksum = "52ff2c0fe9bc6cb6b14a0592c2ff4fa9ceb83eea9db979b0487cd054946a2b8f"
|
|
|
411
405
|
|
|
412
406
|
[[package]]
|
|
413
407
|
name = "libmimalloc-sys"
|
|
414
|
-
version = "0.1.
|
|
408
|
+
version = "0.1.49"
|
|
415
409
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
416
|
-
checksum = "
|
|
410
|
+
checksum = "6a45a52f43e1c16f667ccfe4dd8c85b7f7c204fd5e3bf46c5b0db9a5c3c0b8e9"
|
|
417
411
|
dependencies = [
|
|
418
412
|
"cc",
|
|
419
413
|
]
|
|
420
414
|
|
|
421
415
|
[[package]]
|
|
422
416
|
name = "libtest-mimic"
|
|
423
|
-
version = "0.
|
|
417
|
+
version = "0.8.2"
|
|
424
418
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
425
|
-
checksum = "
|
|
419
|
+
checksum = "14e6ba06f0ade6e504aff834d7c34298e5155c6baca353cc6a4aaff2f9fd7f33"
|
|
426
420
|
dependencies = [
|
|
421
|
+
"anstream",
|
|
422
|
+
"anstyle",
|
|
427
423
|
"clap",
|
|
428
424
|
"escape8259",
|
|
429
|
-
"termcolor",
|
|
430
|
-
"threadpool",
|
|
431
425
|
]
|
|
432
426
|
|
|
433
427
|
[[package]]
|
|
@@ -459,9 +453,9 @@ checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
|
|
|
459
453
|
|
|
460
454
|
[[package]]
|
|
461
455
|
name = "mimalloc"
|
|
462
|
-
version = "0.1.
|
|
456
|
+
version = "0.1.52"
|
|
463
457
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
464
|
-
checksum = "
|
|
458
|
+
checksum = "2d4139bb28d14ad1facf21d5eb8825051b326e172d216b39f6d31df53cc97862"
|
|
465
459
|
dependencies = [
|
|
466
460
|
"libmimalloc-sys",
|
|
467
461
|
]
|
|
@@ -475,16 +469,6 @@ dependencies = [
|
|
|
475
469
|
"windows-sys",
|
|
476
470
|
]
|
|
477
471
|
|
|
478
|
-
[[package]]
|
|
479
|
-
name = "num_cpus"
|
|
480
|
-
version = "1.17.0"
|
|
481
|
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
482
|
-
checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b"
|
|
483
|
-
dependencies = [
|
|
484
|
-
"hermit-abi",
|
|
485
|
-
"libc",
|
|
486
|
-
]
|
|
487
|
-
|
|
488
472
|
[[package]]
|
|
489
473
|
name = "once_cell"
|
|
490
474
|
version = "1.21.4"
|
|
@@ -622,9 +606,9 @@ dependencies = [
|
|
|
622
606
|
|
|
623
607
|
[[package]]
|
|
624
608
|
name = "regex"
|
|
625
|
-
version = "1.12.
|
|
609
|
+
version = "1.12.4"
|
|
626
610
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
627
|
-
checksum = "
|
|
611
|
+
checksum = "f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba"
|
|
628
612
|
dependencies = [
|
|
629
613
|
"aho-corasick",
|
|
630
614
|
"memchr",
|
|
@@ -645,9 +629,9 @@ dependencies = [
|
|
|
645
629
|
|
|
646
630
|
[[package]]
|
|
647
631
|
name = "regex-syntax"
|
|
648
|
-
version = "0.8.
|
|
632
|
+
version = "0.8.11"
|
|
649
633
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
650
|
-
checksum = "
|
|
634
|
+
checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4"
|
|
651
635
|
|
|
652
636
|
[[package]]
|
|
653
637
|
name = "rustc-hash"
|
|
@@ -721,9 +705,9 @@ dependencies = [
|
|
|
721
705
|
|
|
722
706
|
[[package]]
|
|
723
707
|
name = "serde_json"
|
|
724
|
-
version = "1.0.
|
|
708
|
+
version = "1.0.150"
|
|
725
709
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
726
|
-
checksum = "
|
|
710
|
+
checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9"
|
|
727
711
|
dependencies = [
|
|
728
712
|
"indexmap",
|
|
729
713
|
"itoa",
|
|
@@ -772,9 +756,9 @@ dependencies = [
|
|
|
772
756
|
|
|
773
757
|
[[package]]
|
|
774
758
|
name = "smallvec"
|
|
775
|
-
version = "1.15.
|
|
759
|
+
version = "1.15.2"
|
|
776
760
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
777
|
-
checksum = "
|
|
761
|
+
checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90"
|
|
778
762
|
|
|
779
763
|
[[package]]
|
|
780
764
|
name = "streaming-iterator"
|
|
@@ -818,15 +802,6 @@ dependencies = [
|
|
|
818
802
|
"windows-sys",
|
|
819
803
|
]
|
|
820
804
|
|
|
821
|
-
[[package]]
|
|
822
|
-
name = "termcolor"
|
|
823
|
-
version = "1.4.1"
|
|
824
|
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
825
|
-
checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755"
|
|
826
|
-
dependencies = [
|
|
827
|
-
"winapi-util",
|
|
828
|
-
]
|
|
829
|
-
|
|
830
805
|
[[package]]
|
|
831
806
|
name = "thiserror"
|
|
832
807
|
version = "2.0.18"
|
|
@@ -856,15 +831,6 @@ dependencies = [
|
|
|
856
831
|
"cfg-if",
|
|
857
832
|
]
|
|
858
833
|
|
|
859
|
-
[[package]]
|
|
860
|
-
name = "threadpool"
|
|
861
|
-
version = "1.8.1"
|
|
862
|
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
863
|
-
checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa"
|
|
864
|
-
dependencies = [
|
|
865
|
-
"num_cpus",
|
|
866
|
-
]
|
|
867
|
-
|
|
868
834
|
[[package]]
|
|
869
835
|
name = "tiktoken-rs"
|
|
870
836
|
version = "0.12.0"
|
|
@@ -1058,9 +1024,9 @@ dependencies = [
|
|
|
1058
1024
|
|
|
1059
1025
|
[[package]]
|
|
1060
1026
|
name = "tree-sitter-go"
|
|
1061
|
-
version = "0.
|
|
1027
|
+
version = "0.25.0"
|
|
1062
1028
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1063
|
-
checksum = "
|
|
1029
|
+
checksum = "c8560a4d2f835cc0d4d2c2e03cbd0dde2f6114b43bc491164238d333e28b16ea"
|
|
1064
1030
|
dependencies = [
|
|
1065
1031
|
"cc",
|
|
1066
1032
|
"tree-sitter-language",
|
|
@@ -1244,9 +1210,9 @@ dependencies = [
|
|
|
1244
1210
|
|
|
1245
1211
|
[[package]]
|
|
1246
1212
|
name = "tree-sitter-r"
|
|
1247
|
-
version = "1.
|
|
1213
|
+
version = "1.3.0"
|
|
1248
1214
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1249
|
-
checksum = "
|
|
1215
|
+
checksum = "ffc9954ec870dcad6cffdd302b405306c68cf031ed79a78cd9746f6740d9fe20"
|
|
1250
1216
|
dependencies = [
|
|
1251
1217
|
"cc",
|
|
1252
1218
|
"tree-sitter-language",
|
|
@@ -1264,9 +1230,9 @@ dependencies = [
|
|
|
1264
1230
|
|
|
1265
1231
|
[[package]]
|
|
1266
1232
|
name = "tree-sitter-rust"
|
|
1267
|
-
version = "0.
|
|
1233
|
+
version = "0.24.2"
|
|
1268
1234
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1269
|
-
checksum = "
|
|
1235
|
+
checksum = "439e577dbe07423ec2582ac62c7531120dbfccfa6e5f92406f93dd271a120e45"
|
|
1270
1236
|
dependencies = [
|
|
1271
1237
|
"cc",
|
|
1272
1238
|
"tree-sitter-language",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[package]
|
|
2
2
|
name = "diffctx"
|
|
3
|
-
version = "1.
|
|
3
|
+
version = "1.10.0"
|
|
4
4
|
edition = "2024"
|
|
5
5
|
rust-version = "1.85"
|
|
6
6
|
|
|
@@ -70,8 +70,8 @@ tree-sitter = "0.25"
|
|
|
70
70
|
tree-sitter-python = { version = "0.23", optional = true }
|
|
71
71
|
tree-sitter-javascript = { version = "0.23", optional = true }
|
|
72
72
|
tree-sitter-typescript = { version = "0.23", optional = true }
|
|
73
|
-
tree-sitter-go = { version = "0.
|
|
74
|
-
tree-sitter-rust = { version = "0.
|
|
73
|
+
tree-sitter-go = { version = "0.25", optional = true }
|
|
74
|
+
tree-sitter-rust = { version = "0.24", optional = true }
|
|
75
75
|
tree-sitter-java = { version = "0.23", optional = true }
|
|
76
76
|
tree-sitter-c = { version = "0.24", optional = true }
|
|
77
77
|
tree-sitter-cpp = { version = "0.23", optional = true }
|
|
@@ -126,7 +126,7 @@ pyo3 = { version = "0.29", features = ["extension-module", "abi3-py310"], option
|
|
|
126
126
|
[dev-dependencies]
|
|
127
127
|
tempfile = "3"
|
|
128
128
|
serde_yaml = "0.9"
|
|
129
|
-
libtest-mimic = "0.
|
|
129
|
+
libtest-mimic = "0.8"
|
|
130
130
|
|
|
131
131
|
[[test]]
|
|
132
132
|
name = "yaml_cases"
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
use once_cell::sync::Lazy;
|
|
2
|
+
|
|
3
|
+
pub struct BudgetConfig {
|
|
4
|
+
pub unlimited: u32,
|
|
5
|
+
pub auto_multiplier: f64,
|
|
6
|
+
pub auto_min: u32,
|
|
7
|
+
pub auto_max: u32,
|
|
8
|
+
/// Per-core-fragment ceiling when sizing the auto budget. A core fragment
|
|
9
|
+
/// that is a whole-file `Chunk` (e.g. a 2000-line unparsed template touched
|
|
10
|
+
/// by one hunk) would otherwise inflate the budget by its entire byte
|
|
11
|
+
/// weight, dragging in the whole repo as "context". Capping each core
|
|
12
|
+
/// fragment's budget contribution decouples "how big are the files I edited"
|
|
13
|
+
/// from "how much of the repo do I pull in". Does not affect whether the
|
|
14
|
+
/// changed file is included (it always is) — only the context budget it earns.
|
|
15
|
+
pub core_token_cap_per_fragment: u32,
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
impl Default for BudgetConfig {
|
|
19
|
+
fn default() -> Self {
|
|
20
|
+
Self {
|
|
21
|
+
unlimited: 10_000_000,
|
|
22
|
+
auto_multiplier: 3.0,
|
|
23
|
+
auto_min: 8_000,
|
|
24
|
+
auto_max: 48_000,
|
|
25
|
+
core_token_cap_per_fragment: 1_500,
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
pub static BUDGET: Lazy<BudgetConfig> = Lazy::new(BudgetConfig::default);
|
|
@@ -4,6 +4,7 @@ use crate::config::env_overrides::{read_env_f64, read_env_fraction, read_env_ope
|
|
|
4
4
|
|
|
5
5
|
pub struct AlgorithmLimits {
|
|
6
6
|
pub max_file_size: usize,
|
|
7
|
+
pub max_changed_file_size: usize,
|
|
7
8
|
pub max_fragments: usize,
|
|
8
9
|
pub max_generated_fragments: usize,
|
|
9
10
|
pub max_generated_lines: usize,
|
|
@@ -21,6 +22,7 @@ impl Default for AlgorithmLimits {
|
|
|
21
22
|
.unwrap_or(200);
|
|
22
23
|
Self {
|
|
23
24
|
max_file_size: 100_000,
|
|
25
|
+
max_changed_file_size: 5_000_000,
|
|
24
26
|
max_fragments,
|
|
25
27
|
max_generated_fragments: 5,
|
|
26
28
|
max_generated_lines: 30,
|
|
@@ -31,6 +33,12 @@ impl Default for AlgorithmLimits {
|
|
|
31
33
|
}
|
|
32
34
|
}
|
|
33
35
|
|
|
36
|
+
/// Hard ceiling for a single `git cat-file` blob read. Sized with headroom
|
|
37
|
+
/// above `max_changed_file_size` (5 MB) so legitimate large changed files still
|
|
38
|
+
/// load, while a pathological multi-hundred-MB blob is drained and rejected
|
|
39
|
+
/// instead of allocated up front.
|
|
40
|
+
pub const MAX_BLOB_READ_BYTES: usize = 16_000_000;
|
|
41
|
+
|
|
34
42
|
pub const DEFAULT_PPR_ALPHA: f64 = 0.60;
|
|
35
43
|
/// Calibrated on v1 manifest (2119 instances, 4 benchmarks). Winner
|
|
36
44
|
/// of (tau, cbf) grid = (0.12, 0.5) at min(per_benchmark file_recall)
|
|
@@ -256,11 +256,13 @@ impl EdgeBuilder for PythonEdgeBuilder {
|
|
|
256
256
|
if tgt == &f.id {
|
|
257
257
|
continue;
|
|
258
258
|
}
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
259
|
+
base::add_edge(
|
|
260
|
+
&mut edges,
|
|
261
|
+
&f.id,
|
|
262
|
+
tgt,
|
|
263
|
+
PYTHON_SEMANTIC.import_weight,
|
|
264
|
+
PYTHON_SEMANTIC.reverse_factor,
|
|
265
|
+
);
|
|
264
266
|
}
|
|
265
267
|
}
|
|
266
268
|
}
|
|
@@ -11,7 +11,7 @@ use super::super::EdgeDict;
|
|
|
11
11
|
use super::super::base::{self, EdgeBuilder, add_edge_unidirectional, path_to_module};
|
|
12
12
|
|
|
13
13
|
static IMPORT_RE: Lazy<Regex> =
|
|
14
|
-
Lazy::new(|| Regex::new(r"(?:from\s+([\w.]+)\s+import|import\s+([\w.]+))").unwrap());
|
|
14
|
+
Lazy::new(|| Regex::new(r"(?m)^\s*(?:from\s+([\w.]+)\s+import|import\s+([\w.]+))").unwrap());
|
|
15
15
|
|
|
16
16
|
fn is_python_test(name: &str) -> bool {
|
|
17
17
|
name.starts_with("test_") || name.ends_with("_test.py")
|
|
@@ -289,7 +289,7 @@ pub fn cap_context_fragments(
|
|
|
289
289
|
file_frags.sort_by(|a, b| {
|
|
290
290
|
let sa = rel.get(&a.id).copied().unwrap_or(0.0);
|
|
291
291
|
let sb = rel.get(&b.id).copied().unwrap_or(0.0);
|
|
292
|
-
sb.
|
|
292
|
+
sb.total_cmp(&sa).then_with(|| a.id.cmp(&b.id))
|
|
293
293
|
});
|
|
294
294
|
result.extend(
|
|
295
295
|
file_frags
|
|
@@ -299,5 +299,6 @@ pub fn cap_context_fragments(
|
|
|
299
299
|
}
|
|
300
300
|
}
|
|
301
301
|
|
|
302
|
+
result.sort_by(|a, b| a.id.cmp(&b.id));
|
|
302
303
|
result
|
|
303
304
|
}
|
|
@@ -14,7 +14,11 @@ use crate::parsers::fragment_file;
|
|
|
14
14
|
use crate::tokenizer::count_tokens;
|
|
15
15
|
use crate::types::{Fragment, FragmentId, FragmentKind, extract_identifiers};
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
// Content reaching here is already-decoded UTF-8 text, so the only reliable
|
|
18
|
+
// binary signal is an embedded NUL (matching git's own heuristic). The old
|
|
19
|
+
// range flagged ESC/BS/etc., wrongly dropping changed text fixtures that embed
|
|
20
|
+
// ANSI escape codes (snapshot/terminal-recording files).
|
|
21
|
+
static BINARY_CTRL_RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"\x00").unwrap());
|
|
18
22
|
|
|
19
23
|
static GENERATED_FILENAME_PATTERNS: Lazy<FxHashSet<&'static str>> = Lazy::new(|| {
|
|
20
24
|
[
|
|
@@ -193,6 +197,7 @@ fn read_file_content(
|
|
|
193
197
|
root_dir: &Path,
|
|
194
198
|
preferred_revs: &[String],
|
|
195
199
|
mut batch_reader: Option<&mut CatFileBatch>,
|
|
200
|
+
is_changed: bool,
|
|
196
201
|
) -> Option<String> {
|
|
197
202
|
let ext = file_path
|
|
198
203
|
.extension()
|
|
@@ -208,7 +213,11 @@ fn read_file_content(
|
|
|
208
213
|
.unwrap_or_else(|_| root_dir.to_path_buf());
|
|
209
214
|
let rel = abs_path.strip_prefix(&resolved_root).ok()?;
|
|
210
215
|
|
|
211
|
-
let max_size =
|
|
216
|
+
let max_size = if is_changed {
|
|
217
|
+
LIMITS.max_changed_file_size
|
|
218
|
+
} else {
|
|
219
|
+
LIMITS.max_file_size
|
|
220
|
+
};
|
|
212
221
|
for rev in preferred_revs {
|
|
213
222
|
if let Some(reader) = batch_reader.as_deref_mut() {
|
|
214
223
|
match reader.get(rev, rel) {
|
|
@@ -229,7 +238,7 @@ fn read_file_content(
|
|
|
229
238
|
|
|
230
239
|
if abs_path.exists() && abs_path.is_file() {
|
|
231
240
|
if let Ok(meta) = std::fs::metadata(&abs_path) {
|
|
232
|
-
if meta.len() as usize >
|
|
241
|
+
if meta.len() as usize > max_size {
|
|
233
242
|
return None;
|
|
234
243
|
}
|
|
235
244
|
}
|
|
@@ -249,6 +258,7 @@ pub fn process_files_for_fragments(
|
|
|
249
258
|
preferred_revs: &[String],
|
|
250
259
|
seen_frag_ids: &mut FxHashSet<FragmentId>,
|
|
251
260
|
mut batch_reader: Option<&mut CatFileBatch>,
|
|
261
|
+
is_changed: bool,
|
|
252
262
|
) -> Vec<Fragment> {
|
|
253
263
|
let max_frags = LIMITS.max_fragments;
|
|
254
264
|
let max_generated = LIMITS.max_generated_fragments;
|
|
@@ -267,6 +277,7 @@ pub fn process_files_for_fragments(
|
|
|
267
277
|
root_dir,
|
|
268
278
|
preferred_revs,
|
|
269
279
|
batch_reader.as_deref_mut(),
|
|
280
|
+
is_changed,
|
|
270
281
|
)?;
|
|
271
282
|
Some((file_path.clone(), content))
|
|
272
283
|
})
|
|
@@ -277,7 +288,11 @@ pub fn process_files_for_fragments(
|
|
|
277
288
|
.map(|(file_path, content)| {
|
|
278
289
|
let path_arc: Arc<str> = Arc::from(file_path.to_string_lossy().as_ref());
|
|
279
290
|
let mut raw_frags = fragment_file(path_arc, content);
|
|
280
|
-
|
|
291
|
+
// Changed files are the subject of the diff: never apply the
|
|
292
|
+
// aggressive generated-file reduction (cap=5 + 30-line content
|
|
293
|
+
// truncation), which can drop the small fragment covering the
|
|
294
|
+
// edited hunk before core identification runs.
|
|
295
|
+
let generated = !is_changed && is_generated_file(file_path, content);
|
|
281
296
|
let cap = if generated { max_generated } else { max_frags };
|
|
282
297
|
if raw_frags.len() > cap {
|
|
283
298
|
raw_frags.sort_by(|a, b| b.line_count().cmp(&a.line_count()));
|
|
@@ -310,7 +325,7 @@ pub fn create_whole_file_fragment(
|
|
|
310
325
|
preferred_revs: &[String],
|
|
311
326
|
batch_reader: Option<&mut CatFileBatch>,
|
|
312
327
|
) -> Option<Fragment> {
|
|
313
|
-
let content = read_file_content(path, root_dir, preferred_revs, batch_reader)?;
|
|
328
|
+
let content = read_file_content(path, root_dir, preferred_revs, batch_reader, true)?;
|
|
314
329
|
let trimmed = content.trim();
|
|
315
330
|
if trimmed.is_empty() {
|
|
316
331
|
return None;
|
|
@@ -594,6 +594,28 @@ impl CatFileBatch {
|
|
|
594
594
|
GitError::CommandFailed(format!("cat-file: invalid size in header: {}", header_str))
|
|
595
595
|
})?;
|
|
596
596
|
|
|
597
|
+
// Guard against allocating an unbounded blob. Anything larger than the
|
|
598
|
+
// biggest size we will ever parse is drained from the stream in bounded
|
|
599
|
+
// chunks (to keep the cat-file pipe in sync for the next request) and
|
|
600
|
+
// rejected, instead of allocating `size` bytes up front (OOM on a
|
|
601
|
+
// pathological multi-hundred-MB blob).
|
|
602
|
+
if size > crate::config::limits::MAX_BLOB_READ_BYTES {
|
|
603
|
+
let mut remaining = size;
|
|
604
|
+
let mut scratch = [0u8; 65536];
|
|
605
|
+
while remaining > 0 {
|
|
606
|
+
let want = remaining.min(scratch.len());
|
|
607
|
+
reader.read_exact(&mut scratch[..want])?;
|
|
608
|
+
remaining -= want;
|
|
609
|
+
}
|
|
610
|
+
let mut trailing = [0u8; 1];
|
|
611
|
+
let _ = reader.read_exact(&mut trailing);
|
|
612
|
+
return Err(GitError::CommandFailed(format!(
|
|
613
|
+
"cat-file: blob too large ({} bytes): {}",
|
|
614
|
+
size,
|
|
615
|
+
spec.trim()
|
|
616
|
+
)));
|
|
617
|
+
}
|
|
618
|
+
|
|
597
619
|
let mut content = vec![0u8; size];
|
|
598
620
|
reader.read_exact(&mut content)?;
|
|
599
621
|
|
|
@@ -113,6 +113,9 @@ impl Graph {
|
|
|
113
113
|
if weight.is_nan() || weight.is_infinite() || weight <= 0.0 {
|
|
114
114
|
return;
|
|
115
115
|
}
|
|
116
|
+
if src == dst {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
116
119
|
debug_assert!(
|
|
117
120
|
self.csr_cache.is_none(),
|
|
118
121
|
"add_edge called after Graph was frozen"
|
|
@@ -230,10 +233,11 @@ impl Graph {
|
|
|
230
233
|
return FxHashMap::default();
|
|
231
234
|
}
|
|
232
235
|
|
|
233
|
-
let valid_seed_idxs: Vec<u32> = seeds
|
|
236
|
+
let mut valid_seed_idxs: Vec<u32> = seeds
|
|
234
237
|
.iter()
|
|
235
238
|
.filter_map(|s| fwd.node_to_idx.get(s).copied())
|
|
236
239
|
.collect();
|
|
240
|
+
valid_seed_idxs.sort_unstable();
|
|
237
241
|
|
|
238
242
|
let per_seed: Vec<Vec<(u32, u32, f64)>> = valid_seed_idxs
|
|
239
243
|
.par_iter()
|