Skip to content

Commit 3eb7b79

Browse files
committed
refactor(diffctx): remove artificial caps, tune edge weights
Remove max_discovered_files (200), max_expansion_files (50), and max_candidate_files (5000) caps — PPR + tau are now the sole quality filters. Lower all same-package/module/namespace edge weights to 0.05 and reduce lexical similarity weights across all languages to prevent over-discovery noise. Add threshold sanity checks and error handling to benchmark scripts. YAML tests: 1408 passed (+8), 29 xfailed (-8), avg score 84.0% (+0.8pp)
1 parent 57333ff commit 3eb7b79

15 files changed

Lines changed: 71 additions & 134 deletions

benchmarks/contextbench_diffctx.py

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ def run_diffctx(repo_dir: Path, budget: int = 8000) -> dict | None:
143143
],
144144
capture_output=True,
145145
text=True,
146-
timeout=120,
146+
timeout=600,
147147
)
148148
if r.returncode != 0:
149149
print(f" DIFFCTX FAIL (rc={r.returncode}): {r.stderr[:300]}")
@@ -351,7 +351,22 @@ def aggregate(results: list[dict]) -> None:
351351
print(f"\nFailures: {dict(by_status)}")
352352

353353

354+
def _print_threshold_sanity_check():
355+
v = subprocess.run(
356+
[
357+
sys.executable,
358+
"-c",
359+
"from treemapper.diffctx.filtering import _LOW_RELEVANCE_THRESHOLD; print(_LOW_RELEVANCE_THRESHOLD)",
360+
],
361+
capture_output=True,
362+
text=True,
363+
).stdout.strip()
364+
print(f"diffctx _LOW_RELEVANCE_THRESHOLD = {v}", file=sys.stderr)
365+
366+
354367
def main():
368+
_print_threshold_sanity_check()
369+
355370
import argparse
356371

357372
parser = argparse.ArgumentParser()
@@ -389,9 +404,12 @@ def main():
389404

390405
results = []
391406
for inst in instances:
392-
r = evaluate_instance(inst, args.budget)
393-
if r:
394-
results.append(r)
407+
try:
408+
r = evaluate_instance(inst, args.budget)
409+
if r:
410+
results.append(r)
411+
except Exception as e:
412+
print(f" ERROR: {type(e).__name__}: {e}")
395413

396414
aggregate(results)
397415

benchmarks/forensic_contextbench.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -363,7 +363,22 @@ def evaluate_one(inst: dict, budget: int) -> dict:
363363
}
364364

365365

366+
def _print_threshold_sanity_check():
367+
v = subprocess.run(
368+
[
369+
sys.executable,
370+
"-c",
371+
"from treemapper.diffctx.filtering import _LOW_RELEVANCE_THRESHOLD; print(_LOW_RELEVANCE_THRESHOLD)",
372+
],
373+
capture_output=True,
374+
text=True,
375+
).stdout.strip()
376+
print(f"diffctx _LOW_RELEVANCE_THRESHOLD = {v}", file=sys.stderr)
377+
378+
366379
def main():
380+
_print_threshold_sanity_check()
381+
367382
ap = argparse.ArgumentParser()
368383
ap.add_argument("--limit", type=int, default=5)
369384
ap.add_argument("--budget", type=int, default=8000)

src/treemapper/diffctx/config/limits.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,8 @@ class AlgorithmLimits:
2020
max_fragments: int = field(default_factory=lambda: _env_int("TREEMAPPER_MAX_FRAGMENTS", 200))
2121
max_generated_fragments: int = 5
2222
max_generated_lines: int = 30
23-
max_candidate_files: int = 5000
24-
max_discovered_files: int = field(default_factory=lambda: _env_int("TREEMAPPER_MAX_DISCOVERED", 200))
2523
skip_expensive_threshold: int = 2000
2624
rare_identifier_threshold: int = 3
27-
max_expansion_files: int = field(default_factory=lambda: _env_int("TREEMAPPER_MAX_EXPANSION", 50))
2825
overhead_per_fragment: int = 18
2926

3027

src/treemapper/diffctx/config/weights.py

Lines changed: 26 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -30,43 +30,43 @@ class EdgeWeightConfig:
3030
"go_import": EdgeWeightConfig(0.70, 0.40),
3131
"go_type": EdgeWeightConfig(0.65, 0.40),
3232
"go_func": EdgeWeightConfig(0.60, 0.40),
33-
"go_same_package": EdgeWeightConfig(0.55, 0.40),
33+
"go_same_package": EdgeWeightConfig(0.05, 0.40),
3434
"rust_mod": EdgeWeightConfig(0.70, 0.40),
3535
"rust_use": EdgeWeightConfig(0.65, 0.40),
3636
"rust_type": EdgeWeightConfig(0.65, 0.40),
3737
"rust_fn": EdgeWeightConfig(0.60, 0.40),
38-
"rust_same_crate": EdgeWeightConfig(0.50, 0.40),
38+
"rust_same_crate": EdgeWeightConfig(0.05, 0.40),
3939
"jvm_import": EdgeWeightConfig(0.75, 0.40),
4040
"jvm_inheritance": EdgeWeightConfig(0.80, 0.40),
4141
"jvm_type": EdgeWeightConfig(0.60, 0.40),
42-
"jvm_same_package": EdgeWeightConfig(0.10, 0.40),
42+
"jvm_same_package": EdgeWeightConfig(0.05, 0.40),
4343
"jvm_annotation": EdgeWeightConfig(0.50, 0.40),
4444
"c_include": EdgeWeightConfig(0.65, 0.40),
4545
"c_call": EdgeWeightConfig(0.55, 0.40),
4646
"c_type": EdgeWeightConfig(0.50, 0.40),
4747
"dotnet_using": EdgeWeightConfig(0.65, 0.40),
4848
"dotnet_inheritance": EdgeWeightConfig(0.75, 0.40),
4949
"dotnet_type": EdgeWeightConfig(0.60, 0.40),
50-
"dotnet_same_namespace": EdgeWeightConfig(0.50, 0.40),
50+
"dotnet_same_namespace": EdgeWeightConfig(0.05, 0.40),
5151
"dotnet_attribute": EdgeWeightConfig(0.50, 0.40),
5252
"dotnet_partial": EdgeWeightConfig(0.80, 0.40),
5353
"ruby_require": EdgeWeightConfig(0.65, 0.40),
5454
"ruby_include": EdgeWeightConfig(0.60, 0.40),
5555
"ruby_const": EdgeWeightConfig(0.55, 0.40),
56-
"ruby_same_dir": EdgeWeightConfig(0.45, 0.40),
56+
"ruby_same_dir": EdgeWeightConfig(0.05, 0.40),
5757
"php_use": EdgeWeightConfig(0.65, 0.40),
5858
"php_require": EdgeWeightConfig(0.60, 0.40),
5959
"php_inheritance": EdgeWeightConfig(0.75, 0.40),
6060
"php_type": EdgeWeightConfig(0.55, 0.40),
61-
"php_same_namespace": EdgeWeightConfig(0.10, 0.40),
61+
"php_same_namespace": EdgeWeightConfig(0.05, 0.40),
6262
"shell_source": EdgeWeightConfig(0.60, 0.35),
6363
"shell_script": EdgeWeightConfig(0.50, 0.35),
6464
"swift_import": EdgeWeightConfig(0.65, 0.40),
6565
"swift_conformance": EdgeWeightConfig(0.70, 0.40),
6666
"swift_extension": EdgeWeightConfig(0.65, 0.40),
6767
"swift_type": EdgeWeightConfig(0.60, 0.40),
6868
"swift_func": EdgeWeightConfig(0.55, 0.40),
69-
"swift_same_module": EdgeWeightConfig(0.15, 0.40),
69+
"swift_same_module": EdgeWeightConfig(0.05, 0.40),
7070
"zig_import": EdgeWeightConfig(0.65, 0.40),
7171
"zig_type": EdgeWeightConfig(0.60, 0.40),
7272
"zig_fn": EdgeWeightConfig(0.55, 0.40),
@@ -156,24 +156,24 @@ class LangWeights:
156156

157157

158158
LANG_WEIGHTS: dict[str, LangWeights] = {
159-
"python": LangWeights(0.65, 0.70, 0.50, 0.20, 0.35),
160-
"javascript": LangWeights(0.50, 0.55, 0.45, 0.25, 0.35),
161-
"jsx": LangWeights(0.50, 0.55, 0.45, 0.25, 0.35),
162-
"typescript": LangWeights(0.70, 0.75, 0.65, 0.15, 0.25),
163-
"tsx": LangWeights(0.70, 0.75, 0.65, 0.15, 0.25),
164-
"rust": LangWeights(0.90, 0.95, 0.85, 0.10, 0.15),
165-
"java": LangWeights(0.85, 0.90, 0.80, 0.10, 0.15),
166-
"kotlin": LangWeights(0.80, 0.85, 0.75, 0.12, 0.18),
167-
"scala": LangWeights(0.80, 0.85, 0.75, 0.12, 0.18),
168-
"go": LangWeights(0.80, 0.85, 0.75, 0.12, 0.20),
169-
"c": LangWeights(0.60, 0.65, 0.55, 0.15, 0.25),
170-
"cpp": LangWeights(0.65, 0.70, 0.60, 0.15, 0.25),
171-
"csharp": LangWeights(0.75, 0.80, 0.70, 0.12, 0.20),
172-
"fsharp": LangWeights(0.70, 0.75, 0.65, 0.12, 0.20),
173-
"ruby": LangWeights(0.60, 0.65, 0.55, 0.15, 0.25),
174-
"php": LangWeights(0.60, 0.65, 0.55, 0.15, 0.25),
175-
"shell": LangWeights(0.40, 0.45, 0.35, 0.20, 0.30),
176-
"swift": LangWeights(0.75, 0.80, 0.70, 0.12, 0.20),
159+
"python": LangWeights(0.65, 0.70, 0.50, 0.10, 0.20),
160+
"javascript": LangWeights(0.50, 0.55, 0.45, 0.10, 0.20),
161+
"jsx": LangWeights(0.50, 0.55, 0.45, 0.10, 0.20),
162+
"typescript": LangWeights(0.70, 0.75, 0.65, 0.10, 0.18),
163+
"tsx": LangWeights(0.70, 0.75, 0.65, 0.10, 0.18),
164+
"rust": LangWeights(0.90, 0.95, 0.85, 0.05, 0.10),
165+
"java": LangWeights(0.85, 0.90, 0.80, 0.05, 0.10),
166+
"kotlin": LangWeights(0.80, 0.85, 0.75, 0.05, 0.12),
167+
"scala": LangWeights(0.80, 0.85, 0.75, 0.05, 0.12),
168+
"go": LangWeights(0.80, 0.85, 0.75, 0.05, 0.12),
169+
"c": LangWeights(0.60, 0.65, 0.55, 0.08, 0.15),
170+
"cpp": LangWeights(0.65, 0.70, 0.60, 0.08, 0.15),
171+
"csharp": LangWeights(0.75, 0.80, 0.70, 0.05, 0.12),
172+
"fsharp": LangWeights(0.70, 0.75, 0.65, 0.05, 0.12),
173+
"ruby": LangWeights(0.60, 0.65, 0.55, 0.08, 0.15),
174+
"php": LangWeights(0.60, 0.65, 0.55, 0.08, 0.15),
175+
"shell": LangWeights(0.40, 0.45, 0.35, 0.10, 0.18),
176+
"swift": LangWeights(0.75, 0.80, 0.70, 0.05, 0.12),
177177
}
178178

179-
DEFAULT_LANG_WEIGHTS = LangWeights(0.55, 0.60, 0.50, 0.15, 0.25)
179+
DEFAULT_LANG_WEIGHTS = LangWeights(0.55, 0.60, 0.50, 0.08, 0.15)

src/treemapper/diffctx/pipeline.py

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,6 @@
4545
logger = logging.getLogger(__name__)
4646

4747
_OVERHEAD_PER_FRAGMENT = LIMITS.overhead_per_fragment
48-
_MAX_DISCOVERED_FILES = LIMITS.max_discovered_files
4948
_UNLIMITED_BUDGET = 10_000_000
5049

5150

@@ -253,19 +252,12 @@ def build_diff_context(
253252
seen_frag_ids: set[FragmentId] = set()
254253
all_fragments = _process_files_for_fragments(changed_files, root_dir, preferred_revs, seen_frag_ids, batch_reader)
255254

256-
all_candidate_files, _ = _collect_candidate_files(root_dir, set(changed_files), combined_spec)
255+
all_candidate_files = _collect_candidate_files(root_dir, set(changed_files), combined_spec)
257256
all_candidate_files = _filter_whitelist(all_candidate_files, root_dir, wl_spec)
258257

259258
t1 = time.perf_counter()
260259

261260
edge_discovered = discover_all_related_files(changed_files, all_candidate_files, root_dir)
262-
if len(edge_discovered) > _MAX_DISCOVERED_FILES:
263-
logger.debug(
264-
"diffctx: capping edge-discovered files from %d to %d",
265-
len(edge_discovered),
266-
_MAX_DISCOVERED_FILES,
267-
)
268-
edge_discovered = edge_discovered[:_MAX_DISCOVERED_FILES]
269261
edge_discovered = [_normalize_path(p, root_dir) for p in edge_discovered]
270262
all_fragments.extend(_process_files_for_fragments(edge_discovered, root_dir, preferred_revs, seen_frag_ids, batch_reader))
271263

@@ -277,7 +269,6 @@ def build_diff_context(
277269
changed_files + edge_discovered,
278270
combined_spec,
279271
candidate_files=all_candidate_files,
280-
changed_files=changed_files,
281272
)
282273
expanded_files = [_normalize_path(p, root_dir) for p in expanded_files]
283274
all_fragments.extend(_process_files_for_fragments(expanded_files, root_dir, preferred_revs, seen_frag_ids, batch_reader))

src/treemapper/diffctx/project_graph.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ def build_project_graph(
149149
combined_spec = get_ignore_specs(root_dir, ignore_file, no_default_ignores, None)
150150
wl_spec = get_whitelist_spec(whitelist_file, root_dir)
151151

152-
all_candidate_files, _ = _collect_candidate_files(root_dir, set(), combined_spec)
152+
all_candidate_files = _collect_candidate_files(root_dir, set(), combined_spec)
153153
all_candidate_files = _filter_whitelist(all_candidate_files, root_dir, wl_spec)
154154

155155
logger.info("project_graph: found %d candidate files", len(all_candidate_files))

src/treemapper/diffctx/universe.py

Lines changed: 6 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,8 @@
1919
from . import git as _git # noqa: E402
2020

2121
_RARE_THRESHOLD = LIMITS.rare_identifier_threshold
22-
_MAX_EXPANSION_FILES = LIMITS.max_expansion_files
2322
_FALLBACK_MAX_FILES = 10_000
24-
_MAX_DISCOVERED_FILES = LIMITS.max_discovered_files
2523
_MAX_FILE_SIZE = LIMITS.max_file_size
26-
_MAX_CANDIDATE_FILES = LIMITS.max_candidate_files
2724
_MIN_CONCEPT_LENGTH = 4
2825

2926
_BUILD_SYSTEM_NAMES = frozenset(
@@ -106,36 +103,7 @@ def _is_candidate_file(file_path: Path, root_dir: Path, included_set: set[Path],
106103
return True
107104

108105

109-
def _prioritize_candidates(
110-
candidates: list[Path],
111-
changed_files: set[Path],
112-
) -> list[Path]:
113-
changed_dirs: set[Path] = set()
114-
changed_extensions: set[str] = set()
115-
for f in changed_files:
116-
changed_dirs.add(f.parent)
117-
if f.parent.parent != f.parent:
118-
changed_dirs.add(f.parent.parent)
119-
if f.suffix:
120-
changed_extensions.add(f.suffix.lower())
121-
122-
priority: list[Path] = []
123-
rest: list[Path] = []
124-
for c in candidates:
125-
if c.parent in changed_dirs or c.suffix.lower() in changed_extensions:
126-
priority.append(c)
127-
else:
128-
rest.append(c)
129-
130-
budget = _MAX_CANDIDATE_FILES - len(priority)
131-
if budget > 0:
132-
priority.extend(rest[:budget])
133-
return priority[:_MAX_CANDIDATE_FILES]
134-
135-
136-
def _collect_candidate_files(
137-
root_dir: Path, included_set: set[Path], combined_spec: pathspec.PathSpec
138-
) -> tuple[list[Path], bool]:
106+
def _collect_candidate_files(root_dir: Path, included_set: set[Path], combined_spec: pathspec.PathSpec) -> list[Path]:
139107
try:
140108
result = subprocess.run(
141109
["git", "ls-files", "-z"],
@@ -147,16 +115,7 @@ def _collect_candidate_files(
147115
if result.returncode == 0 and result.stdout:
148116
out = result.stdout.decode("utf-8", errors="surrogateescape")
149117
files = [root_dir / f for f in out.split("\0") if f]
150-
candidates = [f for f in files if _is_candidate_file(f, root_dir, included_set, combined_spec)]
151-
is_large_repo = len(candidates) > _MAX_CANDIDATE_FILES
152-
if is_large_repo:
153-
logger.debug(
154-
"diffctx: %d candidates exceed cap %d, prioritizing by proximity",
155-
len(candidates),
156-
_MAX_CANDIDATE_FILES,
157-
)
158-
candidates = _prioritize_candidates(candidates, included_set)
159-
return candidates, is_large_repo
118+
return [f for f in files if _is_candidate_file(f, root_dir, included_set, combined_spec)]
160119
except (subprocess.SubprocessError, OSError):
161120
pass
162121
logger.warning("diffctx: git ls-files failed, falling back to rglob (limit %d files)", _FALLBACK_MAX_FILES)
@@ -167,39 +126,15 @@ def _collect_candidate_files(
167126
break
168127
if _is_candidate_file(f, root_dir, included_set, combined_spec):
169128
fallback.append(f)
170-
return fallback, False
171-
172-
173-
def _path_distance(a: Path, b: Path) -> int:
174-
a_parts = a.parent.parts
175-
b_parts = b.parent.parts
176-
common = 0
177-
for x, y in zip(a_parts, b_parts):
178-
if x != y:
179-
break
180-
common += 1
181-
return (len(a_parts) - common) + (len(b_parts) - common)
129+
return fallback
182130

183131

184132
def _build_ident_index(
185133
files: list[Path],
186134
concepts: frozenset[str],
187-
changed_files: list[Path] | None = None,
188135
) -> dict[str, list[Path]]:
189-
if changed_files:
190-
changed_dirs = {f.parent for f in changed_files}
191-
192-
def sort_key(p: Path) -> tuple[int, int, str]:
193-
in_same_dir = 0 if p.parent in changed_dirs else 1
194-
min_dist = min((_path_distance(p, cf) for cf in changed_files), default=0)
195-
return (in_same_dir, min_dist, str(p))
196-
197-
prioritized = sorted(files, key=sort_key)[:2000]
198-
else:
199-
prioritized = sorted(files)[:2000]
200-
201136
inverted_index: dict[str, list[Path]] = defaultdict(list)
202-
for file_path in prioritized:
137+
for file_path in files:
203138
try:
204139
content = file_path.read_text(encoding="utf-8")
205140
file_idents = extract_identifiers(content, skip_stopwords=False)
@@ -240,8 +175,6 @@ def _collect_expansion_files(
240175
for file_path in inverted_index.get(concept, []):
241176
if file_path not in included_set and not _is_build_or_manifest(file_path):
242177
expansion_files.add(file_path)
243-
if len(expansion_files) >= _MAX_EXPANSION_FILES:
244-
return list(expansion_files)
245178

246179
return list(expansion_files)
247180

@@ -286,7 +219,6 @@ def _expand_universe_by_rare_identifiers(
286219
already_included: list[Path],
287220
combined_spec: pathspec.PathSpec,
288221
candidate_files: list[Path] | None = None,
289-
changed_files: list[Path] | None = None,
290222
) -> list[Path]:
291223
if not concepts:
292224
return []
@@ -295,8 +227,8 @@ def _expand_universe_by_rare_identifiers(
295227
if candidate_files is not None:
296228
files = [f for f in candidate_files if f not in included_set]
297229
else:
298-
files, _ = _collect_candidate_files(root_dir, included_set, combined_spec)
299-
inverted_index = _build_ident_index(files, concepts, changed_files=changed_files)
230+
files = _collect_candidate_files(root_dir, included_set, combined_spec)
231+
inverted_index = _build_ident_index(files, concepts)
300232

301233
included_concept_counts: dict[str, int] = {}
302234
for f in already_included:

tests/cases/diff/go_035_http_handler.yaml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
11
name: go_035_http_handler
2-
xfail:
3-
category: go-cross-file-discovery
42
repo:
53
initial_files:
64
api/handlers.go: |

tests/cases/diff/jvm_and_compiled_060_swift_async_await.yaml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
11
name: jvm_and_compiled_060_swift_async_await
2-
xfail:
3-
category: swift-same-module-edges
42
repo:
53
initial_files:
64
User.swift: |

tests/cases/diff/jvm_and_compiled_061_swift_codable.yaml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
11
name: jvm_and_compiled_061_swift_codable
2-
xfail:
3-
category: swift-same-module-edges
42
repo:
53
initial_files:
64
User.swift: |

0 commit comments

Comments
 (0)