diffcontext 0.5.1__tar.gz → 0.5.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {diffcontext-0.5.1 → diffcontext-0.5.3}/PKG-INFO +41 -25
- {diffcontext-0.5.1 → diffcontext-0.5.3}/README.md +38 -24
- {diffcontext-0.5.1 → diffcontext-0.5.3}/diffcontext/__init__.py +1 -1
- {diffcontext-0.5.1 → diffcontext-0.5.3}/diffcontext/cli/__init__.py +21 -1
- {diffcontext-0.5.1 → diffcontext-0.5.3}/diffcontext/context/compiler.py +135 -104
- diffcontext-0.5.3/diffcontext/mcp_server.py +250 -0
- {diffcontext-0.5.1 → diffcontext-0.5.3}/diffcontext/pipeline.py +49 -0
- {diffcontext-0.5.1 → diffcontext-0.5.3}/diffcontext.egg-info/PKG-INFO +41 -25
- {diffcontext-0.5.1 → diffcontext-0.5.3}/diffcontext.egg-info/SOURCES.txt +2 -0
- {diffcontext-0.5.1 → diffcontext-0.5.3}/diffcontext.egg-info/entry_points.txt +1 -0
- {diffcontext-0.5.1 → diffcontext-0.5.3}/diffcontext.egg-info/requires.txt +3 -0
- {diffcontext-0.5.1 → diffcontext-0.5.3}/pyproject.toml +6 -0
- {diffcontext-0.5.1 → diffcontext-0.5.3}/tests/test_compiler_meta.py +68 -0
- {diffcontext-0.5.1 → diffcontext-0.5.3}/tests/test_core.py +55 -0
- diffcontext-0.5.3/tests/test_mcp_server.py +111 -0
- {diffcontext-0.5.1 → diffcontext-0.5.3}/LICENSE +0 -0
- {diffcontext-0.5.1 → diffcontext-0.5.3}/diffcontext/_warn_once.py +0 -0
- {diffcontext-0.5.1 → diffcontext-0.5.3}/diffcontext/cache.py +0 -0
- {diffcontext-0.5.1 → diffcontext-0.5.3}/diffcontext/context/__init__.py +0 -0
- {diffcontext-0.5.1 → diffcontext-0.5.3}/diffcontext/context/selector.py +0 -0
- {diffcontext-0.5.1 → diffcontext-0.5.3}/diffcontext/diff/__init__.py +0 -0
- {diffcontext-0.5.1 → diffcontext-0.5.3}/diffcontext/diff/git_diff.py +0 -0
- {diffcontext-0.5.1 → diffcontext-0.5.3}/diffcontext/diff/state_manager.py +0 -0
- {diffcontext-0.5.1 → diffcontext-0.5.3}/diffcontext/graph_builder.py +0 -0
- {diffcontext-0.5.1 → diffcontext-0.5.3}/diffcontext/history.py +0 -0
- {diffcontext-0.5.1 → diffcontext-0.5.3}/diffcontext/impact/__init__.py +0 -0
- {diffcontext-0.5.1 → diffcontext-0.5.3}/diffcontext/impact/blast_radius.py +0 -0
- {diffcontext-0.5.1 → diffcontext-0.5.3}/diffcontext/impact/scoring.py +0 -0
- {diffcontext-0.5.1 → diffcontext-0.5.3}/diffcontext/impact/traversal.py +0 -0
- {diffcontext-0.5.1 → diffcontext-0.5.3}/diffcontext/impact/visualizer.py +0 -0
- {diffcontext-0.5.1 → diffcontext-0.5.3}/diffcontext/languages/__init__.py +0 -0
- {diffcontext-0.5.1 → diffcontext-0.5.3}/diffcontext/languages/typescript.py +0 -0
- {diffcontext-0.5.1 → diffcontext-0.5.3}/diffcontext/lexical.py +0 -0
- {diffcontext-0.5.1 → diffcontext-0.5.3}/diffcontext/models.py +0 -0
- {diffcontext-0.5.1 → diffcontext-0.5.3}/diffcontext/parser.py +0 -0
- {diffcontext-0.5.1 → diffcontext-0.5.3}/diffcontext/py.typed +0 -0
- {diffcontext-0.5.1 → diffcontext-0.5.3}/diffcontext/rerank/__init__.py +0 -0
- {diffcontext-0.5.1 → diffcontext-0.5.3}/diffcontext/rerank/features.py +0 -0
- {diffcontext-0.5.1 → diffcontext-0.5.3}/diffcontext/rerank/model.py +0 -0
- {diffcontext-0.5.1 → diffcontext-0.5.3}/diffcontext/resolver.py +0 -0
- {diffcontext-0.5.1 → diffcontext-0.5.3}/diffcontext/scanner.py +0 -0
- {diffcontext-0.5.1 → diffcontext-0.5.3}/diffcontext/symbols.py +0 -0
- {diffcontext-0.5.1 → diffcontext-0.5.3}/diffcontext/verify/__init__.py +0 -0
- {diffcontext-0.5.1 → diffcontext-0.5.3}/diffcontext/verify/cases.py +0 -0
- {diffcontext-0.5.1 → diffcontext-0.5.3}/diffcontext/verify/history.py +0 -0
- {diffcontext-0.5.1 → diffcontext-0.5.3}/diffcontext/verify/sufficiency.py +0 -0
- {diffcontext-0.5.1 → diffcontext-0.5.3}/diffcontext.egg-info/dependency_links.txt +0 -0
- {diffcontext-0.5.1 → diffcontext-0.5.3}/diffcontext.egg-info/top_level.txt +0 -0
- {diffcontext-0.5.1 → diffcontext-0.5.3}/setup.cfg +0 -0
- {diffcontext-0.5.1 → diffcontext-0.5.3}/tests/test_cache.py +0 -0
- {diffcontext-0.5.1 → diffcontext-0.5.3}/tests/test_cutoff.py +0 -0
- {diffcontext-0.5.1 → diffcontext-0.5.3}/tests/test_determinism.py +0 -0
- {diffcontext-0.5.1 → diffcontext-0.5.3}/tests/test_downstream_eval.py +0 -0
- {diffcontext-0.5.1 → diffcontext-0.5.3}/tests/test_git_diff.py +0 -0
- {diffcontext-0.5.1 → diffcontext-0.5.3}/tests/test_harness_api.py +0 -0
- {diffcontext-0.5.1 → diffcontext-0.5.3}/tests/test_history.py +0 -0
- {diffcontext-0.5.1 → diffcontext-0.5.3}/tests/test_incremental.py +0 -0
- {diffcontext-0.5.1 → diffcontext-0.5.3}/tests/test_index_scoping.py +0 -0
- {diffcontext-0.5.1 → diffcontext-0.5.3}/tests/test_lexical.py +0 -0
- {diffcontext-0.5.1 → diffcontext-0.5.3}/tests/test_rerank.py +0 -0
- {diffcontext-0.5.1 → diffcontext-0.5.3}/tests/test_semantic_ablation.py +0 -0
- {diffcontext-0.5.1 → diffcontext-0.5.3}/tests/test_semantic_audit.py +0 -0
- {diffcontext-0.5.1 → diffcontext-0.5.3}/tests/test_semantic_gap.py +0 -0
- {diffcontext-0.5.1 → diffcontext-0.5.3}/tests/test_semantic_metrics.py +0 -0
- {diffcontext-0.5.1 → diffcontext-0.5.3}/tests/test_semantic_mine.py +0 -0
- {diffcontext-0.5.1 → diffcontext-0.5.3}/tests/test_service_clone.py +0 -0
- {diffcontext-0.5.1 → diffcontext-0.5.3}/tests/test_src_layout.py +0 -0
- {diffcontext-0.5.1 → diffcontext-0.5.3}/tests/test_stats.py +0 -0
- {diffcontext-0.5.1 → diffcontext-0.5.3}/tests/test_token_budget.py +0 -0
- {diffcontext-0.5.1 → diffcontext-0.5.3}/tests/test_typescript_adapter.py +0 -0
- {diffcontext-0.5.1 → diffcontext-0.5.3}/tests/test_verify.py +0 -0
- {diffcontext-0.5.1 → diffcontext-0.5.3}/tests/test_version.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: diffcontext
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.3
|
|
4
4
|
Summary: Static-analysis-powered repository context compiler for LLMs
|
|
5
5
|
Author-email: Trakshan Mishra <trakshanmishra477@gmail.com>
|
|
6
6
|
License: MIT
|
|
@@ -30,6 +30,8 @@ Provides-Extra: typescript
|
|
|
30
30
|
Requires-Dist: tree-sitter>=0.21; extra == "typescript"
|
|
31
31
|
Requires-Dist: tree-sitter-typescript>=0.21; extra == "typescript"
|
|
32
32
|
Requires-Dist: tree-sitter-javascript>=0.21; extra == "typescript"
|
|
33
|
+
Provides-Extra: mcp
|
|
34
|
+
Requires-Dist: mcp>=1.0; extra == "mcp"
|
|
33
35
|
Dynamic: license-file
|
|
34
36
|
|
|
35
37
|
# DiffContext
|
|
@@ -52,6 +54,10 @@ It is built for people wiring LLMs into real codebases — agent loops, PR
|
|
|
52
54
|
review bots, CI checks — anywhere you have to decide what goes in the prompt
|
|
53
55
|
and the repository is far too large to send.
|
|
54
56
|
|
|
57
|
+
And it grades itself: point it at your repo and it mines your git history,
|
|
58
|
+
runs retrieval against real co-change pairs, and prints **NULL RESULT** when
|
|
59
|
+
it doesn't fit — finding that out *is* the feature.
|
|
60
|
+
|
|
55
61
|
## The problem
|
|
56
62
|
|
|
57
63
|
Ask an assistant to change one function in a 50,000-line project and you have
|
|
@@ -71,24 +77,6 @@ git change ──► changed functions ──► hybrid retrieval ──► toke
|
|
|
71
77
|
graph ∪ BM25 ∪ file top-k + tokens
|
|
72
78
|
```
|
|
73
79
|
|
|
74
|
-
## Does it make the model better?
|
|
75
|
-
|
|
76
|
-
Yes — measured end to end, not by proxy. On 128 ContextBench Python tasks
|
|
77
|
-
judged by each repository's own test suite (no LLM-as-judge), **context
|
|
78
|
-
roughly quadruples pass@1: 5.5% → 25.8%**, exact McNemar p < 0.0001.
|
|
79
|
-
|
|
80
|
-
Two qualifiers, both in [`benchmarks/contextbench/RESULTS.md`](benchmarks/contextbench/RESULTS.md)
|
|
81
|
-
§6: **(a)** the seed functions given to every arm are **oracle** — extracted
|
|
82
|
-
from the gold patch — so this measures *"given correct localization, does
|
|
83
|
-
context quality matter?"*, not end-to-end issue solving (localization is
|
|
84
|
-
handed to every arm for free); **(b)** 121 of the 128 effective tasks are
|
|
85
|
-
django, so this is largely a django result.
|
|
86
|
-
|
|
87
|
-
The honest companion: the three context variants (default / gap / depboost)
|
|
88
|
-
are statistically **indistinguishable** from each other, p = 0.36–0.81. The
|
|
89
|
-
win is context versus no context — not this selector versus that one. Full
|
|
90
|
-
results: [`benchmarks/contextbench/RESULTS.md`](benchmarks/contextbench/RESULTS.md).
|
|
91
|
-
|
|
92
80
|
## Install
|
|
93
81
|
|
|
94
82
|
```bash
|
|
@@ -97,6 +85,14 @@ pip install diffcontext
|
|
|
97
85
|
|
|
98
86
|
Zero runtime dependencies, Python 3.9+.
|
|
99
87
|
|
|
88
|
+
For MCP integration (Claude Code / Cursor / Windsurf):
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
pip install "diffcontext[mcp]"
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
See [docs/MCP.md](docs/MCP.md) for the server config.
|
|
95
|
+
|
|
100
96
|
From source for development:
|
|
101
97
|
|
|
102
98
|
```bash
|
|
@@ -114,6 +110,31 @@ diffcontext verify --from-history 20 --calibrate
|
|
|
114
110
|
|
|
115
111
|
More commands: [USAGE.md](USAGE.md). Production recipes: [docs/USE_CASES.md](docs/USE_CASES.md).
|
|
116
112
|
|
|
113
|
+
## Don't trust our benchmarks — run yours (2 minutes)
|
|
114
|
+
|
|
115
|
+
`diffcontext verify --from-history 20 --calibrate` mines test cases from
|
|
116
|
+
*your* repo's git history and grades retrieval against them — and prints
|
|
117
|
+
**NULL RESULT** rather than a decorative number when the tool doesn't fit
|
|
118
|
+
your repo. Finding that out *is* the feature.
|
|
119
|
+
|
|
120
|
+
## Does it make the model better?
|
|
121
|
+
|
|
122
|
+
Yes — measured end to end, not by proxy. On 128 ContextBench Python tasks
|
|
123
|
+
judged by each repository's own test suite (no LLM-as-judge), **context
|
|
124
|
+
roughly quadruples pass@1: 5.5% → 25.8%**, exact McNemar p < 0.0001.
|
|
125
|
+
|
|
126
|
+
Two qualifiers, both in [`benchmarks/contextbench/RESULTS.md`](benchmarks/contextbench/RESULTS.md)
|
|
127
|
+
§6: **(a)** the seed functions given to every arm are **oracle** — extracted
|
|
128
|
+
from the gold patch — so this measures *"given correct localization, does
|
|
129
|
+
context quality matter?"*, not end-to-end issue solving (localization is
|
|
130
|
+
handed to every arm for free); **(b)** 121 of the 128 effective tasks are
|
|
131
|
+
django, so this is largely a django result.
|
|
132
|
+
|
|
133
|
+
The honest companion: the three context variants (default / gap / depboost)
|
|
134
|
+
are statistically **indistinguishable** from each other, p = 0.36–0.81. The
|
|
135
|
+
win is context versus no context — not this selector versus that one. Full
|
|
136
|
+
results: [`benchmarks/contextbench/RESULTS.md`](benchmarks/contextbench/RESULTS.md).
|
|
137
|
+
|
|
117
138
|
## What this is not
|
|
118
139
|
|
|
119
140
|
- **Not a code generator.** It selects and packs context; the model writes
|
|
@@ -168,12 +189,6 @@ numbers did not survive:
|
|
|
168
189
|
Full write-up: [docs/auditing-my-own-benchmark.md](docs/auditing-my-own-benchmark.md)
|
|
169
190
|
· raw pass: [benchmarks/RIGOR_REPORT_2026-07.md](benchmarks/RIGOR_REPORT_2026-07.md).
|
|
170
191
|
|
|
171
|
-
**Don't trust our benchmarks — run yours (2 minutes):**
|
|
172
|
-
`diffcontext verify --from-history 20 --calibrate` mines test cases from
|
|
173
|
-
*your* repo's git history and grades retrieval against them — and prints
|
|
174
|
-
**NULL RESULT** rather than a decorative number when the tool doesn't fit
|
|
175
|
-
your repo. Finding that out *is* the feature.
|
|
176
|
-
|
|
177
192
|
## Use as a library
|
|
178
193
|
|
|
179
194
|
```python
|
|
@@ -209,6 +224,7 @@ dispatch are measured blind spots — itemized in
|
|
|
209
224
|
|
|
210
225
|
- [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) — pipeline, module map, agent API
|
|
211
226
|
- [docs/BENCHMARKS.md](docs/BENCHMARKS.md) — all numbers, downstream pass@1, limitations
|
|
227
|
+
- [docs/MCP.md](docs/MCP.md) — MCP server for Claude Code / Cursor / Windsurf
|
|
212
228
|
- [docs/ROADMAP.md](docs/ROADMAP.md) — prioritized plan with measured motivations
|
|
213
229
|
- [diffcontext-service/](diffcontext-service/) — FastAPI service + web UI
|
|
214
230
|
- [observability/](observability/) — retrieval pipeline tracing
|
|
@@ -18,6 +18,10 @@ It is built for people wiring LLMs into real codebases — agent loops, PR
|
|
|
18
18
|
review bots, CI checks — anywhere you have to decide what goes in the prompt
|
|
19
19
|
and the repository is far too large to send.
|
|
20
20
|
|
|
21
|
+
And it grades itself: point it at your repo and it mines your git history,
|
|
22
|
+
runs retrieval against real co-change pairs, and prints **NULL RESULT** when
|
|
23
|
+
it doesn't fit — finding that out *is* the feature.
|
|
24
|
+
|
|
21
25
|
## The problem
|
|
22
26
|
|
|
23
27
|
Ask an assistant to change one function in a 50,000-line project and you have
|
|
@@ -37,24 +41,6 @@ git change ──► changed functions ──► hybrid retrieval ──► toke
|
|
|
37
41
|
graph ∪ BM25 ∪ file top-k + tokens
|
|
38
42
|
```
|
|
39
43
|
|
|
40
|
-
## Does it make the model better?
|
|
41
|
-
|
|
42
|
-
Yes — measured end to end, not by proxy. On 128 ContextBench Python tasks
|
|
43
|
-
judged by each repository's own test suite (no LLM-as-judge), **context
|
|
44
|
-
roughly quadruples pass@1: 5.5% → 25.8%**, exact McNemar p < 0.0001.
|
|
45
|
-
|
|
46
|
-
Two qualifiers, both in [`benchmarks/contextbench/RESULTS.md`](benchmarks/contextbench/RESULTS.md)
|
|
47
|
-
§6: **(a)** the seed functions given to every arm are **oracle** — extracted
|
|
48
|
-
from the gold patch — so this measures *"given correct localization, does
|
|
49
|
-
context quality matter?"*, not end-to-end issue solving (localization is
|
|
50
|
-
handed to every arm for free); **(b)** 121 of the 128 effective tasks are
|
|
51
|
-
django, so this is largely a django result.
|
|
52
|
-
|
|
53
|
-
The honest companion: the three context variants (default / gap / depboost)
|
|
54
|
-
are statistically **indistinguishable** from each other, p = 0.36–0.81. The
|
|
55
|
-
win is context versus no context — not this selector versus that one. Full
|
|
56
|
-
results: [`benchmarks/contextbench/RESULTS.md`](benchmarks/contextbench/RESULTS.md).
|
|
57
|
-
|
|
58
44
|
## Install
|
|
59
45
|
|
|
60
46
|
```bash
|
|
@@ -63,6 +49,14 @@ pip install diffcontext
|
|
|
63
49
|
|
|
64
50
|
Zero runtime dependencies, Python 3.9+.
|
|
65
51
|
|
|
52
|
+
For MCP integration (Claude Code / Cursor / Windsurf):
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
pip install "diffcontext[mcp]"
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
See [docs/MCP.md](docs/MCP.md) for the server config.
|
|
59
|
+
|
|
66
60
|
From source for development:
|
|
67
61
|
|
|
68
62
|
```bash
|
|
@@ -80,6 +74,31 @@ diffcontext verify --from-history 20 --calibrate
|
|
|
80
74
|
|
|
81
75
|
More commands: [USAGE.md](USAGE.md). Production recipes: [docs/USE_CASES.md](docs/USE_CASES.md).
|
|
82
76
|
|
|
77
|
+
## Don't trust our benchmarks — run yours (2 minutes)
|
|
78
|
+
|
|
79
|
+
`diffcontext verify --from-history 20 --calibrate` mines test cases from
|
|
80
|
+
*your* repo's git history and grades retrieval against them — and prints
|
|
81
|
+
**NULL RESULT** rather than a decorative number when the tool doesn't fit
|
|
82
|
+
your repo. Finding that out *is* the feature.
|
|
83
|
+
|
|
84
|
+
## Does it make the model better?
|
|
85
|
+
|
|
86
|
+
Yes — measured end to end, not by proxy. On 128 ContextBench Python tasks
|
|
87
|
+
judged by each repository's own test suite (no LLM-as-judge), **context
|
|
88
|
+
roughly quadruples pass@1: 5.5% → 25.8%**, exact McNemar p < 0.0001.
|
|
89
|
+
|
|
90
|
+
Two qualifiers, both in [`benchmarks/contextbench/RESULTS.md`](benchmarks/contextbench/RESULTS.md)
|
|
91
|
+
§6: **(a)** the seed functions given to every arm are **oracle** — extracted
|
|
92
|
+
from the gold patch — so this measures *"given correct localization, does
|
|
93
|
+
context quality matter?"*, not end-to-end issue solving (localization is
|
|
94
|
+
handed to every arm for free); **(b)** 121 of the 128 effective tasks are
|
|
95
|
+
django, so this is largely a django result.
|
|
96
|
+
|
|
97
|
+
The honest companion: the three context variants (default / gap / depboost)
|
|
98
|
+
are statistically **indistinguishable** from each other, p = 0.36–0.81. The
|
|
99
|
+
win is context versus no context — not this selector versus that one. Full
|
|
100
|
+
results: [`benchmarks/contextbench/RESULTS.md`](benchmarks/contextbench/RESULTS.md).
|
|
101
|
+
|
|
83
102
|
## What this is not
|
|
84
103
|
|
|
85
104
|
- **Not a code generator.** It selects and packs context; the model writes
|
|
@@ -134,12 +153,6 @@ numbers did not survive:
|
|
|
134
153
|
Full write-up: [docs/auditing-my-own-benchmark.md](docs/auditing-my-own-benchmark.md)
|
|
135
154
|
· raw pass: [benchmarks/RIGOR_REPORT_2026-07.md](benchmarks/RIGOR_REPORT_2026-07.md).
|
|
136
155
|
|
|
137
|
-
**Don't trust our benchmarks — run yours (2 minutes):**
|
|
138
|
-
`diffcontext verify --from-history 20 --calibrate` mines test cases from
|
|
139
|
-
*your* repo's git history and grades retrieval against them — and prints
|
|
140
|
-
**NULL RESULT** rather than a decorative number when the tool doesn't fit
|
|
141
|
-
your repo. Finding that out *is* the feature.
|
|
142
|
-
|
|
143
156
|
## Use as a library
|
|
144
157
|
|
|
145
158
|
```python
|
|
@@ -175,6 +188,7 @@ dispatch are measured blind spots — itemized in
|
|
|
175
188
|
|
|
176
189
|
- [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) — pipeline, module map, agent API
|
|
177
190
|
- [docs/BENCHMARKS.md](docs/BENCHMARKS.md) — all numbers, downstream pass@1, limitations
|
|
191
|
+
- [docs/MCP.md](docs/MCP.md) — MCP server for Claude Code / Cursor / Windsurf
|
|
178
192
|
- [docs/ROADMAP.md](docs/ROADMAP.md) — prioritized plan with measured motivations
|
|
179
193
|
- [diffcontext-service/](diffcontext-service/) — FastAPI service + web UI
|
|
180
194
|
- [observability/](observability/) — retrieval pipeline tracing
|
|
@@ -32,7 +32,7 @@ Usage as a library:
|
|
|
32
32
|
print(ctx.reduction_pct) # how much code was filtered out
|
|
33
33
|
"""
|
|
34
34
|
|
|
35
|
-
__version__ = "0.5.
|
|
35
|
+
__version__ = "0.5.3"
|
|
36
36
|
|
|
37
37
|
# Public, semver-covered API. Everything not listed here (graph_builder,
|
|
38
38
|
# resolver, symbols, scanner, parser internals) is importable but carries no
|
|
@@ -23,6 +23,7 @@ import time
|
|
|
23
23
|
from ..pipeline import index_repository, analyze_impact, compile, warn_unknown_symbols
|
|
24
24
|
from ..diff.git_diff import find_changed_symbols
|
|
25
25
|
from ..impact.visualizer import render_blast_radius, render_verification
|
|
26
|
+
from .. import __version__ as DC_VERSION
|
|
26
27
|
|
|
27
28
|
|
|
28
29
|
def main():
|
|
@@ -37,6 +38,7 @@ def main():
|
|
|
37
38
|
prog="diffcontext",
|
|
38
39
|
description="Static-analysis-powered repository context compiler for LLMs",
|
|
39
40
|
)
|
|
41
|
+
parser.add_argument("--version", action="version", version=f"diffcontext {DC_VERSION}")
|
|
40
42
|
sub = parser.add_subparsers(dest="command", help="Available commands")
|
|
41
43
|
|
|
42
44
|
# --- index ---
|
|
@@ -113,6 +115,22 @@ def main():
|
|
|
113
115
|
"reaches related files with no call or lexical connection)",
|
|
114
116
|
)
|
|
115
117
|
p_compile.add_argument("--notes", type=str, default=None, help="Developer notes to prepend to the context output")
|
|
118
|
+
p_compile.add_argument(
|
|
119
|
+
"--query-text", type=str, default=None,
|
|
120
|
+
help="Bug report or issue text. Biases retrieval toward symbols "
|
|
121
|
+
"semantically related to the described problem — the one "
|
|
122
|
+
"signal the graph alone can't provide.",
|
|
123
|
+
)
|
|
124
|
+
p_compile.add_argument(
|
|
125
|
+
"--meta", choices=["full", "compact", "off"], default="full",
|
|
126
|
+
help=(
|
|
127
|
+
"Disclosure-header level. 'full' (default): counts, architecture "
|
|
128
|
+
"snapshot, dropped manifest, graph confidence, warnings. 'compact': "
|
|
129
|
+
"counts + dropped top-3 + warnings only (~60%% smaller). 'off': no "
|
|
130
|
+
"meta-header, just code sections. The pass@1 effect of meta level "
|
|
131
|
+
"is UNMEASURED."
|
|
132
|
+
),
|
|
133
|
+
)
|
|
116
134
|
p_compile.add_argument("--json", action="store_true", help="Output as JSON")
|
|
117
135
|
|
|
118
136
|
# --- blast (NEW: visual blast radius) ---
|
|
@@ -374,15 +392,17 @@ def _cmd_compile(args):
|
|
|
374
392
|
from ..history import CoChangeIndex
|
|
375
393
|
history = CoChangeIndex(args.repo)
|
|
376
394
|
|
|
395
|
+
query_weight = 0.3 if args.query_text else 0.0
|
|
377
396
|
impact = analyze_impact(
|
|
378
397
|
idx, changed, max_depth=args.depth, hybrid=not args.graph_only,
|
|
379
398
|
history=history,
|
|
399
|
+
query_text=args.query_text, query_weight=query_weight,
|
|
380
400
|
)
|
|
381
401
|
max_tokens = args.max_tokens if args.max_tokens > 0 else None
|
|
382
402
|
top_k = args.top_k * len(changed) if args.top_k > 0 else None
|
|
383
403
|
cutoff = args.cutoff if args.cutoff != "topk" else None
|
|
384
404
|
ctx = compile(idx, impact, max_tokens=max_tokens, notes=args.notes,
|
|
385
|
-
top_k=top_k, cutoff=cutoff)
|
|
405
|
+
top_k=top_k, cutoff=cutoff, meta=args.meta)
|
|
386
406
|
|
|
387
407
|
if args.json:
|
|
388
408
|
# Existing keys are kept for backwards compatibility. Added for
|
|
@@ -136,6 +136,7 @@ def compile_context(
|
|
|
136
136
|
token_counter: Optional[Callable[[str], int]] = None,
|
|
137
137
|
scoring_config: Optional[ScoringConfig] = None,
|
|
138
138
|
max_tokens: Optional[int] = None,
|
|
139
|
+
meta: str = "full",
|
|
139
140
|
) -> ContextPackage:
|
|
140
141
|
"""
|
|
141
142
|
Build the final context package from selected symbols.
|
|
@@ -164,6 +165,11 @@ def compile_context(
|
|
|
164
165
|
unlimited). Used to keep the meta-header proportionate:
|
|
165
166
|
under tight budgets the architecture snapshot is
|
|
166
167
|
compacted so meta can't dwarf the code it annotates.
|
|
168
|
+
meta: Disclosure level: "full" (default — counts, architecture
|
|
169
|
+
snapshot, dropped manifest, graph confidence, warnings),
|
|
170
|
+
"compact" (counts + dropped top-3 only — ~60% smaller,
|
|
171
|
+
keeps warnings), "off" (no meta-header, just code). The
|
|
172
|
+
pass@1 effect of meta level is UNMEASURED.
|
|
167
173
|
"""
|
|
168
174
|
dropped_ids = dropped_ids or []
|
|
169
175
|
skipped_files = skipped_files or []
|
|
@@ -230,23 +236,27 @@ def compile_context(
|
|
|
230
236
|
code_text = "\n\n".join(parts)
|
|
231
237
|
context_tokens = count(code_text)
|
|
232
238
|
|
|
233
|
-
meta
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
239
|
+
if meta == "off":
|
|
240
|
+
meta_text = ""
|
|
241
|
+
else:
|
|
242
|
+
meta_text = _build_meta_header(
|
|
243
|
+
symbols = symbols,
|
|
244
|
+
selected_ids = sel_ids,
|
|
245
|
+
dropped_ids = drop_ids,
|
|
246
|
+
skipped_files = skipped_files,
|
|
247
|
+
changed_ids = changed_ids,
|
|
248
|
+
graph = graph,
|
|
249
|
+
reverse = reverse,
|
|
250
|
+
graph_confidence = graph_confidence,
|
|
251
|
+
token_budget = total_repo_tokens, # not the budget cap; just total repo
|
|
252
|
+
context_tokens = context_tokens,
|
|
253
|
+
scores = scores,
|
|
254
|
+
notes = notes,
|
|
255
|
+
scoring_config = scoring_config,
|
|
256
|
+
max_tokens = max_tokens,
|
|
257
|
+
count = count,
|
|
258
|
+
meta_level = meta,
|
|
259
|
+
)
|
|
250
260
|
|
|
251
261
|
suggestions = _build_suggestions(
|
|
252
262
|
changed_ids = changed_ids,
|
|
@@ -258,7 +268,7 @@ def compile_context(
|
|
|
258
268
|
scores = scores,
|
|
259
269
|
)
|
|
260
270
|
|
|
261
|
-
full_text =
|
|
271
|
+
full_text = meta_text + "\n\n" + code_text if meta_text else code_text
|
|
262
272
|
if suggestions:
|
|
263
273
|
full_text += "\n\n" + suggestions
|
|
264
274
|
return items, full_text
|
|
@@ -357,6 +367,7 @@ def _build_meta_header(
|
|
|
357
367
|
scoring_config: Optional[ScoringConfig] = None,
|
|
358
368
|
max_tokens: Optional[int] = None,
|
|
359
369
|
count: Optional[Callable[[str], int]] = None,
|
|
370
|
+
meta_level: str = "full",
|
|
360
371
|
) -> str:
|
|
361
372
|
count = count or (lambda text: max(1, len(text) // 4))
|
|
362
373
|
total_syms = len(symbols)
|
|
@@ -374,108 +385,119 @@ def _build_meta_header(
|
|
|
374
385
|
for s in changed_ids
|
|
375
386
|
) if graph else 0
|
|
376
387
|
|
|
388
|
+
compact = meta_level == "compact"
|
|
389
|
+
|
|
377
390
|
lines = [
|
|
378
391
|
"=== DIFFCONTEXT META ===",
|
|
379
392
|
f"Repo symbols total : {total_syms}",
|
|
380
|
-
f"Symbols scored : {scored_cnt}",
|
|
381
393
|
f"Symbols IN context : {selected_cnt}",
|
|
382
394
|
f"Symbols DROPPED : {dropped_cnt} ← you cannot see these",
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
395
|
+
]
|
|
396
|
+
if not compact:
|
|
397
|
+
lines.extend([
|
|
398
|
+
f"Symbols scored : {scored_cnt}",
|
|
399
|
+
f"Graph edges total : {total_edges}",
|
|
400
|
+
f"Graph confidence : {graph_confidence * 100:.0f}%"
|
|
401
|
+
+ (" ✓" if graph_confidence >= 0.9 else " ⚠ incomplete"),
|
|
402
|
+
# Always-present disclosure, same category as the DROPPED manifest:
|
|
403
|
+
# benchmarked cross-subsystem conceptual co-changes score 0% recall
|
|
404
|
+
# for every static method (see EVAL_V2_REPORT.md failure taxonomy),
|
|
405
|
+
# so a confident-looking 100% must not read as "nothing was missed".
|
|
406
|
+
"Note: graph confidence = STRUCTURAL completeness only. Static "
|
|
407
|
+
"analysis cannot see cross-subsystem conceptual coupling (e.g. a "
|
|
408
|
+
"settings flag and the unrelated code that reads it) — such "
|
|
409
|
+
"related code may exist and not be listed anywhere above.",
|
|
410
|
+
])
|
|
411
|
+
lines.extend([
|
|
394
412
|
f"Changed symbols : {len(changed_ids)}",
|
|
395
413
|
f"Direct callers found : {direct_callers}",
|
|
396
414
|
f"Direct callees found : {direct_callees}",
|
|
397
415
|
f"Context tokens (code) : {context_tokens:,}",
|
|
398
416
|
"Output tokens (full) : {FULL_OUTPUT_TOKENS}",
|
|
399
417
|
f"Scoring basis : {describe_scoring_basis(scoring_config)}",
|
|
400
|
-
]
|
|
418
|
+
])
|
|
401
419
|
|
|
402
420
|
# --- Repository Architecture Snapshot ---
|
|
403
|
-
#
|
|
404
|
-
#
|
|
405
|
-
#
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
modules_total
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
if sym_id in symbols:
|
|
421
|
+
# Skipped entirely in compact mode — it's the largest meta component
|
|
422
|
+
# (per-module listing + docstrings). Compact keeps counts + dropped
|
|
423
|
+
# top-3 only. The pass@1 effect of meta level is UNMEASURED.
|
|
424
|
+
if not compact:
|
|
425
|
+
# Build rel_file -> absolute_path mapping from symbol table.
|
|
426
|
+
# sym_id gives us relative path; sym.file gives us the absolute path we
|
|
427
|
+
# need to actually open the file for its docstring.
|
|
428
|
+
rel_to_abs: Dict[str, str] = {}
|
|
429
|
+
for sym_id, sym in symbols.items():
|
|
430
|
+
rel_file = sym_id.split(":", 1)[0]
|
|
431
|
+
if rel_file not in rel_to_abs:
|
|
432
|
+
rel_to_abs[rel_file] = sym.file # sym.file is always absolute
|
|
433
|
+
|
|
434
|
+
modules_total = {}
|
|
435
|
+
modules_selected = {}
|
|
436
|
+
for sym_id in symbols:
|
|
420
437
|
file_name = sym_id.split(":", 1)[0]
|
|
421
|
-
|
|
438
|
+
modules_total[file_name] = modules_total.get(file_name, 0) + 1
|
|
422
439
|
|
|
423
|
-
|
|
424
|
-
|
|
440
|
+
for sym_id in selected_ids:
|
|
441
|
+
if sym_id in symbols:
|
|
442
|
+
file_name = sym_id.split(":", 1)[0]
|
|
443
|
+
modules_selected[file_name] = modules_selected.get(file_name, 0) + 1
|
|
425
444
|
|
|
426
|
-
|
|
427
|
-
|
|
445
|
+
lines.append("")
|
|
446
|
+
lines.append("=== REPOSITORY ARCHITECTURE SNAPSHOT ===")
|
|
428
447
|
|
|
429
|
-
|
|
430
|
-
|
|
448
|
+
loaded_files = []
|
|
449
|
+
blind_files = []
|
|
431
450
|
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
# FIX: use absolute path, not the relative file_name
|
|
435
|
-
abs_path = rel_to_abs.get(file_name, "")
|
|
436
|
-
doc_str = _get_module_docstring(abs_path) if abs_path else ""
|
|
437
|
-
if doc_str:
|
|
438
|
-
doc_snippet = f" — {doc_str}"
|
|
451
|
+
for file_name, total in sorted(modules_total.items()):
|
|
452
|
+
selected = modules_selected.get(file_name, 0)
|
|
439
453
|
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
# cost multiples of the code it annotates (measured: --max-tokens 500 on
|
|
448
|
-
# black produced ~2,600 total tokens, 5x the request). Compact it when
|
|
449
|
-
# it would exceed ~25% of the symbol budget.
|
|
450
|
-
snapshot_cost = count("\n".join(loaded_files + blind_files))
|
|
451
|
-
snapshot_budget = max(max_tokens // 4, 150) if max_tokens else None
|
|
452
|
-
if snapshot_budget is not None and snapshot_cost > snapshot_budget:
|
|
453
|
-
n_loaded = len(loaded_files)
|
|
454
|
-
n_blind = len(blind_files)
|
|
455
|
-
lines.append(
|
|
456
|
-
f"MODULES: {len(modules_total)} files — {n_loaded} in context, "
|
|
457
|
-
f"{n_blind} blind spots"
|
|
458
|
-
)
|
|
459
|
-
lines.append(
|
|
460
|
-
" (per-module snapshot omitted under tight budget — raise "
|
|
461
|
-
"--max-tokens to see it)"
|
|
462
|
-
)
|
|
463
|
-
else:
|
|
464
|
-
lines.append("MODULES IN CONTEXT:")
|
|
465
|
-
if loaded_files:
|
|
466
|
-
lines.extend(loaded_files)
|
|
467
|
-
else:
|
|
468
|
-
lines.append(" (none)")
|
|
454
|
+
doc_snippet = ""
|
|
455
|
+
if file_name.endswith(".py"):
|
|
456
|
+
# FIX: use absolute path, not the relative file_name
|
|
457
|
+
abs_path = rel_to_abs.get(file_name, "")
|
|
458
|
+
doc_str = _get_module_docstring(abs_path) if abs_path else ""
|
|
459
|
+
if doc_str:
|
|
460
|
+
doc_snippet = f" — {doc_str}"
|
|
469
461
|
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
462
|
+
if selected > 0:
|
|
463
|
+
loaded_files.append(f" - {file_name} ({selected}/{total} symbols loaded){doc_snippet}")
|
|
464
|
+
else:
|
|
465
|
+
blind_files.append(f" - {file_name} ({total} symbols){doc_snippet}")
|
|
466
|
+
|
|
467
|
+
# Budget proportionality: the snapshot scales with repo size, not with
|
|
468
|
+
# the requested budget. Under a tight budget an uncapped snapshot can
|
|
469
|
+
# cost multiples of the code it annotates (measured: --max-tokens 500 on
|
|
470
|
+
# black produced ~2,600 total tokens, 5x the request). Compact it when
|
|
471
|
+
# it would exceed ~25% of the symbol budget.
|
|
472
|
+
snapshot_cost = count("\n".join(loaded_files + blind_files))
|
|
473
|
+
snapshot_budget = max(max_tokens // 4, 150) if max_tokens else None
|
|
474
|
+
if snapshot_budget is not None and snapshot_cost > snapshot_budget:
|
|
475
|
+
n_loaded = len(loaded_files)
|
|
476
|
+
n_blind = len(blind_files)
|
|
477
|
+
lines.append(
|
|
478
|
+
f"MODULES: {len(modules_total)} files — {n_loaded} in context, "
|
|
479
|
+
f"{n_blind} blind spots"
|
|
480
|
+
)
|
|
481
|
+
lines.append(
|
|
482
|
+
" (per-module snapshot omitted under tight budget — raise "
|
|
483
|
+
"--max-tokens to see it)"
|
|
484
|
+
)
|
|
477
485
|
else:
|
|
478
|
-
lines.append("
|
|
486
|
+
lines.append("MODULES IN CONTEXT:")
|
|
487
|
+
if loaded_files:
|
|
488
|
+
lines.extend(loaded_files)
|
|
489
|
+
else:
|
|
490
|
+
lines.append(" (none)")
|
|
491
|
+
|
|
492
|
+
lines.append("")
|
|
493
|
+
lines.append("KNOWN MODULES (NOT IN CONTEXT - BLIND SPOTS):")
|
|
494
|
+
if blind_files:
|
|
495
|
+
_BLIND_CAP = 25
|
|
496
|
+
lines.extend(blind_files[:_BLIND_CAP])
|
|
497
|
+
if len(blind_files) > _BLIND_CAP:
|
|
498
|
+
lines.append(f" ... and {len(blind_files) - _BLIND_CAP} more modules")
|
|
499
|
+
else:
|
|
500
|
+
lines.append(" (none)")
|
|
479
501
|
|
|
480
502
|
if skipped_files:
|
|
481
503
|
lines.append("")
|
|
@@ -484,17 +506,26 @@ def _build_meta_header(
|
|
|
484
506
|
lines.append(f" ✗ {f}")
|
|
485
507
|
|
|
486
508
|
if dropped_cnt > 0:
|
|
487
|
-
#
|
|
488
|
-
#
|
|
489
|
-
|
|
509
|
+
# Compact mode: top-3 only. Full mode: top 15 (top 5 under <2000
|
|
510
|
+
# budget) — the dropped manifest itself costs more than some budgets.
|
|
511
|
+
if compact:
|
|
512
|
+
drop_cap = 3
|
|
513
|
+
elif max_tokens and max_tokens < 2000:
|
|
514
|
+
drop_cap = 5
|
|
515
|
+
else:
|
|
516
|
+
drop_cap = 15
|
|
490
517
|
lines.append("")
|
|
491
518
|
lines.append(f"DROPPED SYMBOLS ({dropped_cnt}) — scored but cut by token budget:")
|
|
492
519
|
for d in dropped_ids[:drop_cap]:
|
|
493
520
|
lines.append(f" - {d} (score: {scores.get(d, 0):.0f})")
|
|
494
521
|
if dropped_cnt > drop_cap:
|
|
495
522
|
lines.append(f" ... and {dropped_cnt - drop_cap} more")
|
|
496
|
-
|
|
523
|
+
if not compact:
|
|
524
|
+
lines.append(" → If any of these are critical, re-run with a higher --max-tokens.")
|
|
497
525
|
|
|
526
|
+
# Warnings are kept in compact mode — they flag real graph holes and
|
|
527
|
+
# dropped references the model needs to know about. Only the
|
|
528
|
+
# architecture snapshot and the verbose counts are compacted away.
|
|
498
529
|
warnings = []
|
|
499
530
|
if skipped_files:
|
|
500
531
|
warnings.append(
|
|
@@ -506,7 +537,7 @@ def _build_meta_header(
|
|
|
506
537
|
f"⚠ {dropped_cnt} symbol(s) were dropped. "
|
|
507
538
|
"References to them in the code below are NOT backed by visible implementations."
|
|
508
539
|
)
|
|
509
|
-
if graph_confidence < 0.8:
|
|
540
|
+
if graph_confidence < 0.8 and not compact:
|
|
510
541
|
warnings.append(
|
|
511
542
|
f"⚠ Graph confidence is {graph_confidence * 100:.0f}%. "
|
|
512
543
|
"Many calls could not be resolved — likely external/stdlib deps or dynamic dispatch."
|