diffcontext 0.5.1__tar.gz → 0.5.2__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.2}/PKG-INFO +41 -25
- {diffcontext-0.5.1 → diffcontext-0.5.2}/README.md +38 -24
- {diffcontext-0.5.1 → diffcontext-0.5.2}/diffcontext/__init__.py +1 -1
- {diffcontext-0.5.1 → diffcontext-0.5.2}/diffcontext/cli/__init__.py +11 -1
- {diffcontext-0.5.1 → diffcontext-0.5.2}/diffcontext/context/compiler.py +138 -104
- diffcontext-0.5.2/diffcontext/mcp_server.py +226 -0
- {diffcontext-0.5.1 → diffcontext-0.5.2}/diffcontext/pipeline.py +10 -0
- {diffcontext-0.5.1 → diffcontext-0.5.2}/diffcontext.egg-info/PKG-INFO +41 -25
- {diffcontext-0.5.1 → diffcontext-0.5.2}/diffcontext.egg-info/SOURCES.txt +2 -0
- {diffcontext-0.5.1 → diffcontext-0.5.2}/diffcontext.egg-info/entry_points.txt +1 -0
- {diffcontext-0.5.1 → diffcontext-0.5.2}/diffcontext.egg-info/requires.txt +3 -0
- {diffcontext-0.5.1 → diffcontext-0.5.2}/pyproject.toml +6 -0
- {diffcontext-0.5.1 → diffcontext-0.5.2}/tests/test_compiler_meta.py +69 -0
- diffcontext-0.5.2/tests/test_mcp_server.py +111 -0
- {diffcontext-0.5.1 → diffcontext-0.5.2}/LICENSE +0 -0
- {diffcontext-0.5.1 → diffcontext-0.5.2}/diffcontext/_warn_once.py +0 -0
- {diffcontext-0.5.1 → diffcontext-0.5.2}/diffcontext/cache.py +0 -0
- {diffcontext-0.5.1 → diffcontext-0.5.2}/diffcontext/context/__init__.py +0 -0
- {diffcontext-0.5.1 → diffcontext-0.5.2}/diffcontext/context/selector.py +0 -0
- {diffcontext-0.5.1 → diffcontext-0.5.2}/diffcontext/diff/__init__.py +0 -0
- {diffcontext-0.5.1 → diffcontext-0.5.2}/diffcontext/diff/git_diff.py +0 -0
- {diffcontext-0.5.1 → diffcontext-0.5.2}/diffcontext/diff/state_manager.py +0 -0
- {diffcontext-0.5.1 → diffcontext-0.5.2}/diffcontext/graph_builder.py +0 -0
- {diffcontext-0.5.1 → diffcontext-0.5.2}/diffcontext/history.py +0 -0
- {diffcontext-0.5.1 → diffcontext-0.5.2}/diffcontext/impact/__init__.py +0 -0
- {diffcontext-0.5.1 → diffcontext-0.5.2}/diffcontext/impact/blast_radius.py +0 -0
- {diffcontext-0.5.1 → diffcontext-0.5.2}/diffcontext/impact/scoring.py +0 -0
- {diffcontext-0.5.1 → diffcontext-0.5.2}/diffcontext/impact/traversal.py +0 -0
- {diffcontext-0.5.1 → diffcontext-0.5.2}/diffcontext/impact/visualizer.py +0 -0
- {diffcontext-0.5.1 → diffcontext-0.5.2}/diffcontext/languages/__init__.py +0 -0
- {diffcontext-0.5.1 → diffcontext-0.5.2}/diffcontext/languages/typescript.py +0 -0
- {diffcontext-0.5.1 → diffcontext-0.5.2}/diffcontext/lexical.py +0 -0
- {diffcontext-0.5.1 → diffcontext-0.5.2}/diffcontext/models.py +0 -0
- {diffcontext-0.5.1 → diffcontext-0.5.2}/diffcontext/parser.py +0 -0
- {diffcontext-0.5.1 → diffcontext-0.5.2}/diffcontext/py.typed +0 -0
- {diffcontext-0.5.1 → diffcontext-0.5.2}/diffcontext/rerank/__init__.py +0 -0
- {diffcontext-0.5.1 → diffcontext-0.5.2}/diffcontext/rerank/features.py +0 -0
- {diffcontext-0.5.1 → diffcontext-0.5.2}/diffcontext/rerank/model.py +0 -0
- {diffcontext-0.5.1 → diffcontext-0.5.2}/diffcontext/resolver.py +0 -0
- {diffcontext-0.5.1 → diffcontext-0.5.2}/diffcontext/scanner.py +0 -0
- {diffcontext-0.5.1 → diffcontext-0.5.2}/diffcontext/symbols.py +0 -0
- {diffcontext-0.5.1 → diffcontext-0.5.2}/diffcontext/verify/__init__.py +0 -0
- {diffcontext-0.5.1 → diffcontext-0.5.2}/diffcontext/verify/cases.py +0 -0
- {diffcontext-0.5.1 → diffcontext-0.5.2}/diffcontext/verify/history.py +0 -0
- {diffcontext-0.5.1 → diffcontext-0.5.2}/diffcontext/verify/sufficiency.py +0 -0
- {diffcontext-0.5.1 → diffcontext-0.5.2}/diffcontext.egg-info/dependency_links.txt +0 -0
- {diffcontext-0.5.1 → diffcontext-0.5.2}/diffcontext.egg-info/top_level.txt +0 -0
- {diffcontext-0.5.1 → diffcontext-0.5.2}/setup.cfg +0 -0
- {diffcontext-0.5.1 → diffcontext-0.5.2}/tests/test_cache.py +0 -0
- {diffcontext-0.5.1 → diffcontext-0.5.2}/tests/test_core.py +0 -0
- {diffcontext-0.5.1 → diffcontext-0.5.2}/tests/test_cutoff.py +0 -0
- {diffcontext-0.5.1 → diffcontext-0.5.2}/tests/test_determinism.py +0 -0
- {diffcontext-0.5.1 → diffcontext-0.5.2}/tests/test_downstream_eval.py +0 -0
- {diffcontext-0.5.1 → diffcontext-0.5.2}/tests/test_git_diff.py +0 -0
- {diffcontext-0.5.1 → diffcontext-0.5.2}/tests/test_harness_api.py +0 -0
- {diffcontext-0.5.1 → diffcontext-0.5.2}/tests/test_history.py +0 -0
- {diffcontext-0.5.1 → diffcontext-0.5.2}/tests/test_incremental.py +0 -0
- {diffcontext-0.5.1 → diffcontext-0.5.2}/tests/test_index_scoping.py +0 -0
- {diffcontext-0.5.1 → diffcontext-0.5.2}/tests/test_lexical.py +0 -0
- {diffcontext-0.5.1 → diffcontext-0.5.2}/tests/test_rerank.py +0 -0
- {diffcontext-0.5.1 → diffcontext-0.5.2}/tests/test_semantic_ablation.py +0 -0
- {diffcontext-0.5.1 → diffcontext-0.5.2}/tests/test_semantic_audit.py +0 -0
- {diffcontext-0.5.1 → diffcontext-0.5.2}/tests/test_semantic_gap.py +0 -0
- {diffcontext-0.5.1 → diffcontext-0.5.2}/tests/test_semantic_metrics.py +0 -0
- {diffcontext-0.5.1 → diffcontext-0.5.2}/tests/test_semantic_mine.py +0 -0
- {diffcontext-0.5.1 → diffcontext-0.5.2}/tests/test_service_clone.py +0 -0
- {diffcontext-0.5.1 → diffcontext-0.5.2}/tests/test_src_layout.py +0 -0
- {diffcontext-0.5.1 → diffcontext-0.5.2}/tests/test_stats.py +0 -0
- {diffcontext-0.5.1 → diffcontext-0.5.2}/tests/test_token_budget.py +0 -0
- {diffcontext-0.5.1 → diffcontext-0.5.2}/tests/test_typescript_adapter.py +0 -0
- {diffcontext-0.5.1 → diffcontext-0.5.2}/tests/test_verify.py +0 -0
- {diffcontext-0.5.1 → diffcontext-0.5.2}/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.2
|
|
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.2"
|
|
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
|
|
@@ -113,6 +113,16 @@ def main():
|
|
|
113
113
|
"reaches related files with no call or lexical connection)",
|
|
114
114
|
)
|
|
115
115
|
p_compile.add_argument("--notes", type=str, default=None, help="Developer notes to prepend to the context output")
|
|
116
|
+
p_compile.add_argument(
|
|
117
|
+
"--meta", choices=["full", "compact", "off"], default="full",
|
|
118
|
+
help=(
|
|
119
|
+
"Disclosure-header level. 'full' (default): counts, architecture "
|
|
120
|
+
"snapshot, dropped manifest, graph confidence, warnings. 'compact': "
|
|
121
|
+
"counts + dropped top-3 + warnings only (~60%% smaller — the A/B "
|
|
122
|
+
"test showed full meta costs ~10pp pass@1 at 4000 tokens by "
|
|
123
|
+
"displacing code). 'off': no meta-header, just code sections."
|
|
124
|
+
),
|
|
125
|
+
)
|
|
116
126
|
p_compile.add_argument("--json", action="store_true", help="Output as JSON")
|
|
117
127
|
|
|
118
128
|
# --- blast (NEW: visual blast radius) ---
|
|
@@ -382,7 +392,7 @@ def _cmd_compile(args):
|
|
|
382
392
|
top_k = args.top_k * len(changed) if args.top_k > 0 else None
|
|
383
393
|
cutoff = args.cutoff if args.cutoff != "topk" else None
|
|
384
394
|
ctx = compile(idx, impact, max_tokens=max_tokens, notes=args.notes,
|
|
385
|
-
top_k=top_k, cutoff=cutoff)
|
|
395
|
+
top_k=top_k, cutoff=cutoff, meta=args.meta)
|
|
386
396
|
|
|
387
397
|
if args.json:
|
|
388
398
|
# 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,13 @@ 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
|
+
"off" (no meta-header at all, just code sections). The
|
|
172
|
+
A/B test showed full meta costs ~10pp pass@1 at 4000
|
|
173
|
+
tokens by displacing code; "compact" is the measured
|
|
174
|
+
middle ground.
|
|
167
175
|
"""
|
|
168
176
|
dropped_ids = dropped_ids or []
|
|
169
177
|
skipped_files = skipped_files or []
|
|
@@ -230,23 +238,27 @@ def compile_context(
|
|
|
230
238
|
code_text = "\n\n".join(parts)
|
|
231
239
|
context_tokens = count(code_text)
|
|
232
240
|
|
|
233
|
-
meta
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
241
|
+
if meta == "off":
|
|
242
|
+
meta_text = ""
|
|
243
|
+
else:
|
|
244
|
+
meta_text = _build_meta_header(
|
|
245
|
+
symbols = symbols,
|
|
246
|
+
selected_ids = sel_ids,
|
|
247
|
+
dropped_ids = drop_ids,
|
|
248
|
+
skipped_files = skipped_files,
|
|
249
|
+
changed_ids = changed_ids,
|
|
250
|
+
graph = graph,
|
|
251
|
+
reverse = reverse,
|
|
252
|
+
graph_confidence = graph_confidence,
|
|
253
|
+
token_budget = total_repo_tokens, # not the budget cap; just total repo
|
|
254
|
+
context_tokens = context_tokens,
|
|
255
|
+
scores = scores,
|
|
256
|
+
notes = notes,
|
|
257
|
+
scoring_config = scoring_config,
|
|
258
|
+
max_tokens = max_tokens,
|
|
259
|
+
count = count,
|
|
260
|
+
meta_level = meta,
|
|
261
|
+
)
|
|
250
262
|
|
|
251
263
|
suggestions = _build_suggestions(
|
|
252
264
|
changed_ids = changed_ids,
|
|
@@ -258,7 +270,7 @@ def compile_context(
|
|
|
258
270
|
scores = scores,
|
|
259
271
|
)
|
|
260
272
|
|
|
261
|
-
full_text =
|
|
273
|
+
full_text = meta_text + "\n\n" + code_text if meta_text else code_text
|
|
262
274
|
if suggestions:
|
|
263
275
|
full_text += "\n\n" + suggestions
|
|
264
276
|
return items, full_text
|
|
@@ -357,6 +369,7 @@ def _build_meta_header(
|
|
|
357
369
|
scoring_config: Optional[ScoringConfig] = None,
|
|
358
370
|
max_tokens: Optional[int] = None,
|
|
359
371
|
count: Optional[Callable[[str], int]] = None,
|
|
372
|
+
meta_level: str = "full",
|
|
360
373
|
) -> str:
|
|
361
374
|
count = count or (lambda text: max(1, len(text) // 4))
|
|
362
375
|
total_syms = len(symbols)
|
|
@@ -374,108 +387,120 @@ def _build_meta_header(
|
|
|
374
387
|
for s in changed_ids
|
|
375
388
|
) if graph else 0
|
|
376
389
|
|
|
390
|
+
compact = meta_level == "compact"
|
|
391
|
+
|
|
377
392
|
lines = [
|
|
378
393
|
"=== DIFFCONTEXT META ===",
|
|
379
394
|
f"Repo symbols total : {total_syms}",
|
|
380
|
-
f"Symbols scored : {scored_cnt}",
|
|
381
395
|
f"Symbols IN context : {selected_cnt}",
|
|
382
396
|
f"Symbols DROPPED : {dropped_cnt} ← you cannot see these",
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
397
|
+
]
|
|
398
|
+
if not compact:
|
|
399
|
+
lines.extend([
|
|
400
|
+
f"Symbols scored : {scored_cnt}",
|
|
401
|
+
f"Graph edges total : {total_edges}",
|
|
402
|
+
f"Graph confidence : {graph_confidence * 100:.0f}%"
|
|
403
|
+
+ (" ✓" if graph_confidence >= 0.9 else " ⚠ incomplete"),
|
|
404
|
+
# Always-present disclosure, same category as the DROPPED manifest:
|
|
405
|
+
# benchmarked cross-subsystem conceptual co-changes score 0% recall
|
|
406
|
+
# for every static method (see EVAL_V2_REPORT.md failure taxonomy),
|
|
407
|
+
# so a confident-looking 100% must not read as "nothing was missed".
|
|
408
|
+
"Note: graph confidence = STRUCTURAL completeness only. Static "
|
|
409
|
+
"analysis cannot see cross-subsystem conceptual coupling (e.g. a "
|
|
410
|
+
"settings flag and the unrelated code that reads it) — such "
|
|
411
|
+
"related code may exist and not be listed anywhere above.",
|
|
412
|
+
])
|
|
413
|
+
lines.extend([
|
|
394
414
|
f"Changed symbols : {len(changed_ids)}",
|
|
395
415
|
f"Direct callers found : {direct_callers}",
|
|
396
416
|
f"Direct callees found : {direct_callees}",
|
|
397
417
|
f"Context tokens (code) : {context_tokens:,}",
|
|
398
418
|
"Output tokens (full) : {FULL_OUTPUT_TOKENS}",
|
|
399
419
|
f"Scoring basis : {describe_scoring_basis(scoring_config)}",
|
|
400
|
-
]
|
|
420
|
+
])
|
|
401
421
|
|
|
402
422
|
# --- Repository Architecture Snapshot ---
|
|
403
|
-
#
|
|
404
|
-
#
|
|
405
|
-
#
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
rel_file
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
423
|
+
# Skipped entirely in compact mode — it's the largest meta component
|
|
424
|
+
# (per-module listing + docstrings), and the A/B test showed meta
|
|
425
|
+
# displaces code under tight budgets. Compact keeps counts + dropped
|
|
426
|
+
# top-3 only.
|
|
427
|
+
if not compact:
|
|
428
|
+
# Build rel_file -> absolute_path mapping from symbol table.
|
|
429
|
+
# sym_id gives us relative path; sym.file gives us the absolute path we
|
|
430
|
+
# need to actually open the file for its docstring.
|
|
431
|
+
rel_to_abs: Dict[str, str] = {}
|
|
432
|
+
for sym_id, sym in symbols.items():
|
|
433
|
+
rel_file = sym_id.split(":", 1)[0]
|
|
434
|
+
if rel_file not in rel_to_abs:
|
|
435
|
+
rel_to_abs[rel_file] = sym.file # sym.file is always absolute
|
|
436
|
+
|
|
437
|
+
modules_total = {}
|
|
438
|
+
modules_selected = {}
|
|
439
|
+
for sym_id in symbols:
|
|
420
440
|
file_name = sym_id.split(":", 1)[0]
|
|
421
|
-
|
|
441
|
+
modules_total[file_name] = modules_total.get(file_name, 0) + 1
|
|
422
442
|
|
|
423
|
-
|
|
424
|
-
|
|
443
|
+
for sym_id in selected_ids:
|
|
444
|
+
if sym_id in symbols:
|
|
445
|
+
file_name = sym_id.split(":", 1)[0]
|
|
446
|
+
modules_selected[file_name] = modules_selected.get(file_name, 0) + 1
|
|
425
447
|
|
|
426
|
-
|
|
427
|
-
|
|
448
|
+
lines.append("")
|
|
449
|
+
lines.append("=== REPOSITORY ARCHITECTURE SNAPSHOT ===")
|
|
428
450
|
|
|
429
|
-
|
|
430
|
-
|
|
451
|
+
loaded_files = []
|
|
452
|
+
blind_files = []
|
|
431
453
|
|
|
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}"
|
|
454
|
+
for file_name, total in sorted(modules_total.items()):
|
|
455
|
+
selected = modules_selected.get(file_name, 0)
|
|
439
456
|
|
|
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)")
|
|
457
|
+
doc_snippet = ""
|
|
458
|
+
if file_name.endswith(".py"):
|
|
459
|
+
# FIX: use absolute path, not the relative file_name
|
|
460
|
+
abs_path = rel_to_abs.get(file_name, "")
|
|
461
|
+
doc_str = _get_module_docstring(abs_path) if abs_path else ""
|
|
462
|
+
if doc_str:
|
|
463
|
+
doc_snippet = f" — {doc_str}"
|
|
469
464
|
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
465
|
+
if selected > 0:
|
|
466
|
+
loaded_files.append(f" - {file_name} ({selected}/{total} symbols loaded){doc_snippet}")
|
|
467
|
+
else:
|
|
468
|
+
blind_files.append(f" - {file_name} ({total} symbols){doc_snippet}")
|
|
469
|
+
|
|
470
|
+
# Budget proportionality: the snapshot scales with repo size, not with
|
|
471
|
+
# the requested budget. Under a tight budget an uncapped snapshot can
|
|
472
|
+
# cost multiples of the code it annotates (measured: --max-tokens 500 on
|
|
473
|
+
# black produced ~2,600 total tokens, 5x the request). Compact it when
|
|
474
|
+
# it would exceed ~25% of the symbol budget.
|
|
475
|
+
snapshot_cost = count("\n".join(loaded_files + blind_files))
|
|
476
|
+
snapshot_budget = max(max_tokens // 4, 150) if max_tokens else None
|
|
477
|
+
if snapshot_budget is not None and snapshot_cost > snapshot_budget:
|
|
478
|
+
n_loaded = len(loaded_files)
|
|
479
|
+
n_blind = len(blind_files)
|
|
480
|
+
lines.append(
|
|
481
|
+
f"MODULES: {len(modules_total)} files — {n_loaded} in context, "
|
|
482
|
+
f"{n_blind} blind spots"
|
|
483
|
+
)
|
|
484
|
+
lines.append(
|
|
485
|
+
" (per-module snapshot omitted under tight budget — raise "
|
|
486
|
+
"--max-tokens to see it)"
|
|
487
|
+
)
|
|
477
488
|
else:
|
|
478
|
-
lines.append("
|
|
489
|
+
lines.append("MODULES IN CONTEXT:")
|
|
490
|
+
if loaded_files:
|
|
491
|
+
lines.extend(loaded_files)
|
|
492
|
+
else:
|
|
493
|
+
lines.append(" (none)")
|
|
494
|
+
|
|
495
|
+
lines.append("")
|
|
496
|
+
lines.append("KNOWN MODULES (NOT IN CONTEXT - BLIND SPOTS):")
|
|
497
|
+
if blind_files:
|
|
498
|
+
_BLIND_CAP = 25
|
|
499
|
+
lines.extend(blind_files[:_BLIND_CAP])
|
|
500
|
+
if len(blind_files) > _BLIND_CAP:
|
|
501
|
+
lines.append(f" ... and {len(blind_files) - _BLIND_CAP} more modules")
|
|
502
|
+
else:
|
|
503
|
+
lines.append(" (none)")
|
|
479
504
|
|
|
480
505
|
if skipped_files:
|
|
481
506
|
lines.append("")
|
|
@@ -484,17 +509,26 @@ def _build_meta_header(
|
|
|
484
509
|
lines.append(f" ✗ {f}")
|
|
485
510
|
|
|
486
511
|
if dropped_cnt > 0:
|
|
487
|
-
#
|
|
488
|
-
#
|
|
489
|
-
|
|
512
|
+
# Compact mode: top-3 only. Full mode: top 15 (top 5 under <2000
|
|
513
|
+
# budget) — the dropped manifest itself costs more than some budgets.
|
|
514
|
+
if compact:
|
|
515
|
+
drop_cap = 3
|
|
516
|
+
elif max_tokens and max_tokens < 2000:
|
|
517
|
+
drop_cap = 5
|
|
518
|
+
else:
|
|
519
|
+
drop_cap = 15
|
|
490
520
|
lines.append("")
|
|
491
521
|
lines.append(f"DROPPED SYMBOLS ({dropped_cnt}) — scored but cut by token budget:")
|
|
492
522
|
for d in dropped_ids[:drop_cap]:
|
|
493
523
|
lines.append(f" - {d} (score: {scores.get(d, 0):.0f})")
|
|
494
524
|
if dropped_cnt > drop_cap:
|
|
495
525
|
lines.append(f" ... and {dropped_cnt - drop_cap} more")
|
|
496
|
-
|
|
526
|
+
if not compact:
|
|
527
|
+
lines.append(" → If any of these are critical, re-run with a higher --max-tokens.")
|
|
497
528
|
|
|
529
|
+
# Warnings are kept in compact mode — they flag real graph holes and
|
|
530
|
+
# dropped references the model needs to know about. Only the
|
|
531
|
+
# architecture snapshot and the verbose counts are compacted away.
|
|
498
532
|
warnings = []
|
|
499
533
|
if skipped_files:
|
|
500
534
|
warnings.append(
|
|
@@ -506,7 +540,7 @@ def _build_meta_header(
|
|
|
506
540
|
f"⚠ {dropped_cnt} symbol(s) were dropped. "
|
|
507
541
|
"References to them in the code below are NOT backed by visible implementations."
|
|
508
542
|
)
|
|
509
|
-
if graph_confidence < 0.8:
|
|
543
|
+
if graph_confidence < 0.8 and not compact:
|
|
510
544
|
warnings.append(
|
|
511
545
|
f"⚠ Graph confidence is {graph_confidence * 100:.0f}%. "
|
|
512
546
|
"Many calls could not be resolved — likely external/stdlib deps or dynamic dispatch."
|