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.
Files changed (72) hide show
  1. {diffcontext-0.5.1 → diffcontext-0.5.3}/PKG-INFO +41 -25
  2. {diffcontext-0.5.1 → diffcontext-0.5.3}/README.md +38 -24
  3. {diffcontext-0.5.1 → diffcontext-0.5.3}/diffcontext/__init__.py +1 -1
  4. {diffcontext-0.5.1 → diffcontext-0.5.3}/diffcontext/cli/__init__.py +21 -1
  5. {diffcontext-0.5.1 → diffcontext-0.5.3}/diffcontext/context/compiler.py +135 -104
  6. diffcontext-0.5.3/diffcontext/mcp_server.py +250 -0
  7. {diffcontext-0.5.1 → diffcontext-0.5.3}/diffcontext/pipeline.py +49 -0
  8. {diffcontext-0.5.1 → diffcontext-0.5.3}/diffcontext.egg-info/PKG-INFO +41 -25
  9. {diffcontext-0.5.1 → diffcontext-0.5.3}/diffcontext.egg-info/SOURCES.txt +2 -0
  10. {diffcontext-0.5.1 → diffcontext-0.5.3}/diffcontext.egg-info/entry_points.txt +1 -0
  11. {diffcontext-0.5.1 → diffcontext-0.5.3}/diffcontext.egg-info/requires.txt +3 -0
  12. {diffcontext-0.5.1 → diffcontext-0.5.3}/pyproject.toml +6 -0
  13. {diffcontext-0.5.1 → diffcontext-0.5.3}/tests/test_compiler_meta.py +68 -0
  14. {diffcontext-0.5.1 → diffcontext-0.5.3}/tests/test_core.py +55 -0
  15. diffcontext-0.5.3/tests/test_mcp_server.py +111 -0
  16. {diffcontext-0.5.1 → diffcontext-0.5.3}/LICENSE +0 -0
  17. {diffcontext-0.5.1 → diffcontext-0.5.3}/diffcontext/_warn_once.py +0 -0
  18. {diffcontext-0.5.1 → diffcontext-0.5.3}/diffcontext/cache.py +0 -0
  19. {diffcontext-0.5.1 → diffcontext-0.5.3}/diffcontext/context/__init__.py +0 -0
  20. {diffcontext-0.5.1 → diffcontext-0.5.3}/diffcontext/context/selector.py +0 -0
  21. {diffcontext-0.5.1 → diffcontext-0.5.3}/diffcontext/diff/__init__.py +0 -0
  22. {diffcontext-0.5.1 → diffcontext-0.5.3}/diffcontext/diff/git_diff.py +0 -0
  23. {diffcontext-0.5.1 → diffcontext-0.5.3}/diffcontext/diff/state_manager.py +0 -0
  24. {diffcontext-0.5.1 → diffcontext-0.5.3}/diffcontext/graph_builder.py +0 -0
  25. {diffcontext-0.5.1 → diffcontext-0.5.3}/diffcontext/history.py +0 -0
  26. {diffcontext-0.5.1 → diffcontext-0.5.3}/diffcontext/impact/__init__.py +0 -0
  27. {diffcontext-0.5.1 → diffcontext-0.5.3}/diffcontext/impact/blast_radius.py +0 -0
  28. {diffcontext-0.5.1 → diffcontext-0.5.3}/diffcontext/impact/scoring.py +0 -0
  29. {diffcontext-0.5.1 → diffcontext-0.5.3}/diffcontext/impact/traversal.py +0 -0
  30. {diffcontext-0.5.1 → diffcontext-0.5.3}/diffcontext/impact/visualizer.py +0 -0
  31. {diffcontext-0.5.1 → diffcontext-0.5.3}/diffcontext/languages/__init__.py +0 -0
  32. {diffcontext-0.5.1 → diffcontext-0.5.3}/diffcontext/languages/typescript.py +0 -0
  33. {diffcontext-0.5.1 → diffcontext-0.5.3}/diffcontext/lexical.py +0 -0
  34. {diffcontext-0.5.1 → diffcontext-0.5.3}/diffcontext/models.py +0 -0
  35. {diffcontext-0.5.1 → diffcontext-0.5.3}/diffcontext/parser.py +0 -0
  36. {diffcontext-0.5.1 → diffcontext-0.5.3}/diffcontext/py.typed +0 -0
  37. {diffcontext-0.5.1 → diffcontext-0.5.3}/diffcontext/rerank/__init__.py +0 -0
  38. {diffcontext-0.5.1 → diffcontext-0.5.3}/diffcontext/rerank/features.py +0 -0
  39. {diffcontext-0.5.1 → diffcontext-0.5.3}/diffcontext/rerank/model.py +0 -0
  40. {diffcontext-0.5.1 → diffcontext-0.5.3}/diffcontext/resolver.py +0 -0
  41. {diffcontext-0.5.1 → diffcontext-0.5.3}/diffcontext/scanner.py +0 -0
  42. {diffcontext-0.5.1 → diffcontext-0.5.3}/diffcontext/symbols.py +0 -0
  43. {diffcontext-0.5.1 → diffcontext-0.5.3}/diffcontext/verify/__init__.py +0 -0
  44. {diffcontext-0.5.1 → diffcontext-0.5.3}/diffcontext/verify/cases.py +0 -0
  45. {diffcontext-0.5.1 → diffcontext-0.5.3}/diffcontext/verify/history.py +0 -0
  46. {diffcontext-0.5.1 → diffcontext-0.5.3}/diffcontext/verify/sufficiency.py +0 -0
  47. {diffcontext-0.5.1 → diffcontext-0.5.3}/diffcontext.egg-info/dependency_links.txt +0 -0
  48. {diffcontext-0.5.1 → diffcontext-0.5.3}/diffcontext.egg-info/top_level.txt +0 -0
  49. {diffcontext-0.5.1 → diffcontext-0.5.3}/setup.cfg +0 -0
  50. {diffcontext-0.5.1 → diffcontext-0.5.3}/tests/test_cache.py +0 -0
  51. {diffcontext-0.5.1 → diffcontext-0.5.3}/tests/test_cutoff.py +0 -0
  52. {diffcontext-0.5.1 → diffcontext-0.5.3}/tests/test_determinism.py +0 -0
  53. {diffcontext-0.5.1 → diffcontext-0.5.3}/tests/test_downstream_eval.py +0 -0
  54. {diffcontext-0.5.1 → diffcontext-0.5.3}/tests/test_git_diff.py +0 -0
  55. {diffcontext-0.5.1 → diffcontext-0.5.3}/tests/test_harness_api.py +0 -0
  56. {diffcontext-0.5.1 → diffcontext-0.5.3}/tests/test_history.py +0 -0
  57. {diffcontext-0.5.1 → diffcontext-0.5.3}/tests/test_incremental.py +0 -0
  58. {diffcontext-0.5.1 → diffcontext-0.5.3}/tests/test_index_scoping.py +0 -0
  59. {diffcontext-0.5.1 → diffcontext-0.5.3}/tests/test_lexical.py +0 -0
  60. {diffcontext-0.5.1 → diffcontext-0.5.3}/tests/test_rerank.py +0 -0
  61. {diffcontext-0.5.1 → diffcontext-0.5.3}/tests/test_semantic_ablation.py +0 -0
  62. {diffcontext-0.5.1 → diffcontext-0.5.3}/tests/test_semantic_audit.py +0 -0
  63. {diffcontext-0.5.1 → diffcontext-0.5.3}/tests/test_semantic_gap.py +0 -0
  64. {diffcontext-0.5.1 → diffcontext-0.5.3}/tests/test_semantic_metrics.py +0 -0
  65. {diffcontext-0.5.1 → diffcontext-0.5.3}/tests/test_semantic_mine.py +0 -0
  66. {diffcontext-0.5.1 → diffcontext-0.5.3}/tests/test_service_clone.py +0 -0
  67. {diffcontext-0.5.1 → diffcontext-0.5.3}/tests/test_src_layout.py +0 -0
  68. {diffcontext-0.5.1 → diffcontext-0.5.3}/tests/test_stats.py +0 -0
  69. {diffcontext-0.5.1 → diffcontext-0.5.3}/tests/test_token_budget.py +0 -0
  70. {diffcontext-0.5.1 → diffcontext-0.5.3}/tests/test_typescript_adapter.py +0 -0
  71. {diffcontext-0.5.1 → diffcontext-0.5.3}/tests/test_verify.py +0 -0
  72. {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.1
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.1"
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 = _build_meta_header(
234
- symbols = symbols,
235
- selected_ids = sel_ids,
236
- dropped_ids = drop_ids,
237
- skipped_files = skipped_files,
238
- changed_ids = changed_ids,
239
- graph = graph,
240
- reverse = reverse,
241
- graph_confidence = graph_confidence,
242
- token_budget = total_repo_tokens, # not the budget cap; just total repo
243
- context_tokens = context_tokens,
244
- scores = scores,
245
- notes = notes,
246
- scoring_config = scoring_config,
247
- max_tokens = max_tokens,
248
- count = count,
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 = meta + "\n\n" + code_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
- f"Graph edges total : {total_edges}",
384
- f"Graph confidence : {graph_confidence * 100:.0f}%"
385
- + (" ✓" if graph_confidence >= 0.9 else " ⚠ incomplete"),
386
- # Always-present disclosure, same category as the DROPPED manifest:
387
- # benchmarked cross-subsystem conceptual co-changes score 0% recall
388
- # for every static method (see EVAL_V2_REPORT.md failure taxonomy),
389
- # so a confident-looking 100% must not read as "nothing was missed".
390
- "Note: graph confidence = STRUCTURAL completeness only. Static "
391
- "analysis cannot see cross-subsystem conceptual coupling (e.g. a "
392
- "settings flag and the unrelated code that reads it) — such "
393
- "related code may exist and not be listed anywhere above.",
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
- # Build rel_file -> absolute_path mapping from symbol table.
404
- # sym_id gives us relative path; sym.file gives us the absolute path we
405
- # need to actually open the file for its docstring.
406
- rel_to_abs: Dict[str, str] = {}
407
- for sym_id, sym in symbols.items():
408
- rel_file = sym_id.split(":", 1)[0]
409
- if rel_file not in rel_to_abs:
410
- rel_to_abs[rel_file] = sym.file # sym.file is always absolute
411
-
412
- modules_total = {}
413
- modules_selected = {}
414
- for sym_id in symbols:
415
- file_name = sym_id.split(":", 1)[0]
416
- modules_total[file_name] = modules_total.get(file_name, 0) + 1
417
-
418
- for sym_id in selected_ids:
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
- modules_selected[file_name] = modules_selected.get(file_name, 0) + 1
438
+ modules_total[file_name] = modules_total.get(file_name, 0) + 1
422
439
 
423
- lines.append("")
424
- lines.append("=== REPOSITORY ARCHITECTURE SNAPSHOT ===")
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
- loaded_files = []
427
- blind_files = []
445
+ lines.append("")
446
+ lines.append("=== REPOSITORY ARCHITECTURE SNAPSHOT ===")
428
447
 
429
- for file_name, total in sorted(modules_total.items()):
430
- selected = modules_selected.get(file_name, 0)
448
+ loaded_files = []
449
+ blind_files = []
431
450
 
432
- doc_snippet = ""
433
- if file_name.endswith(".py"):
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
- if selected > 0:
441
- loaded_files.append(f" - {file_name} ({selected}/{total} symbols loaded){doc_snippet}")
442
- else:
443
- blind_files.append(f" - {file_name} ({total} symbols){doc_snippet}")
444
-
445
- # Budget proportionality: the snapshot scales with repo size, not with
446
- # the requested budget. Under a tight budget an uncapped snapshot can
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
- lines.append("")
471
- lines.append("KNOWN MODULES (NOT IN CONTEXT - BLIND SPOTS):")
472
- if blind_files:
473
- _BLIND_CAP = 25
474
- lines.extend(blind_files[:_BLIND_CAP])
475
- if len(blind_files) > _BLIND_CAP:
476
- lines.append(f" ... and {len(blind_files) - _BLIND_CAP} more modules")
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(" (none)")
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
- # Under a tight budget, the top-15 manifest itself costs more than
488
- # some requested budgets show the top 5 and keep the count honest.
489
- drop_cap = 5 if (max_tokens and max_tokens < 2000) else 15
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
- lines.append(" → If any of these are critical, re-run with a higher --max-tokens.")
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."