Skip to content

Commit 8634ea3

Browse files
committed
fix(diffctx): MODE config now reads env on each call (was Lazy)
Convert `MODE: Lazy<ModeConfig>` static to `mode() -> ModeConfig` function, symmetric with the existing `selection()` and `rescue()` patterns in `config/selection.rs`. This is required for in-process reuse loops (`pool_eval_all_cells` for L sweep / multi-budget / hyperparameter sensitivity) where env vars change between cells — a Lazy static would freeze the value at first read and silently make per-cell env mutation a no-op. `from_mode()` in `mode.rs` now calls `mode_config()` once at the top of the match and uses the snapshot for all three branches; behavior is identical to the previous static for any single-process invocation. Smoke-verified: `DIFFCTX_OP_GRAPH_DEPTH=0` and `=2` produce different `fragment_count` outputs on the same repo+diff. Also updates paper limitations section with an honest spot-check of Aider PolyBench-500 fair-input recall (0.284 intent-to-treat). Root cause analysis from the existing checkpoints shows: - 17% (85/500) helper-process timeouts at the 600s wall - 7.4% (37/500) tree-sitter QueryError "Invalid node type at row 2, column 2: module" — grammar version mismatch in aider-chat==0.86.2 against the installed tree-sitter parsers - of the 378 ok-status runs, 45% still return recall=0 because Aider's RepoMap heuristic does not surface the deep paths typical of multi-language repos (Java packages, Python nested modules) Aider ok% by dataset: 94.0% swebench / 84.8% contextbench / 75.6% polybench. The 0.284 number is not an integration bug — ok-only sensitivity on PolyBench-500 still gives only 0.375, well below external BM25 (0.673 on the same cells).
1 parent 2506d93 commit 8634ea3

4 files changed

Lines changed: 35 additions & 10 deletions

File tree

diffctx/src/config/mode.rs

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use once_cell::sync::Lazy;
1+
use crate::config::env_overrides::read_env_usize;
22

33
pub struct ModeConfig {
44
pub bm25_top_k_primary: usize,
@@ -18,4 +18,28 @@ impl Default for ModeConfig {
1818
}
1919
}
2020

21-
pub static MODE: Lazy<ModeConfig> = Lazy::new(ModeConfig::default);
21+
// Returns a fresh `ModeConfig` snapshot, reading every env override on
22+
// each call. Symmetric with `selection()`/`rescue()` in `config/selection.rs`
23+
// — needed because in-process reuse loops (e.g. `pool_eval_all_cells` for
24+
// a depth or budget sweep) mutate the env per cell, and a `Lazy` static
25+
// would freeze the values at first access.
26+
//
27+
// `DIFFCTX_OP_GRAPH_DEPTH` overrides the graph traversal radius used by
28+
// any scoring mode that walks the typed dependency graph (currently EGO;
29+
// other modes that consume a depth parameter will read the same knob).
30+
// The framework abstracts scoring as a pluggable signal, so the depth
31+
// control is named after the underlying graph operation, not the mode.
32+
//
33+
// Used by the L sweep to run depth in {0, 1, 2, 3, 4} from the same
34+
// binary without rebuilding. At depth 0 the EGO instantiation reduces
35+
// to seed-only relevance (rel_scores carries cores at score 1.0, every
36+
// non-core fragment is filtered before selection); the post-passes
37+
// (coherence + rescue + changed-files) still run, so depth=0 measures
38+
// "framework without graph propagation in scoring", not "trivial diff
39+
// baseline".
40+
pub fn mode() -> ModeConfig {
41+
ModeConfig {
42+
ego_depth_extended: read_env_usize("DIFFCTX_OP_GRAPH_DEPTH", 2),
43+
..ModeConfig::default()
44+
}
45+
}

diffctx/src/mode.rs

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use crate::config::limits::PPR;
2-
use crate::config::mode::MODE;
2+
use crate::config::mode::mode as mode_config;
33

44
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
55
pub enum ScoringMode {
@@ -62,31 +62,32 @@ pub struct PipelineConfig {
6262

6363
impl PipelineConfig {
6464
pub fn from_mode(mode: ScoringMode) -> Self {
65+
let m = mode_config();
6566
match mode {
6667
ScoringMode::Ppr => Self {
6768
discovery: DiscoveryKind::Ensemble,
6869
scoring: ScoringKind::Ppr,
6970
low_relevance_filter: false,
70-
bm25_top_k: MODE.bm25_top_k_primary,
71-
ego_depth: MODE.ego_depth_default,
71+
bm25_top_k: m.bm25_top_k_primary,
72+
ego_depth: m.ego_depth_default,
7273
ppr_alpha: PPR.alpha,
7374
objective: ObjectiveMode::Submodular,
7475
},
7576
ScoringMode::Ego => Self {
7677
discovery: DiscoveryKind::Ensemble,
7778
scoring: ScoringKind::Ego,
7879
low_relevance_filter: false,
79-
bm25_top_k: MODE.bm25_top_k_primary,
80-
ego_depth: MODE.ego_depth_extended,
80+
bm25_top_k: m.bm25_top_k_primary,
81+
ego_depth: m.ego_depth_extended,
8182
ppr_alpha: PPR.alpha,
8283
objective: ObjectiveMode::Submodular,
8384
},
8485
ScoringMode::Bm25 => Self {
8586
discovery: DiscoveryKind::Ensemble,
8687
scoring: ScoringKind::Bm25,
8788
low_relevance_filter: false,
88-
bm25_top_k: MODE.bm25_top_k_off,
89-
ego_depth: MODE.ego_depth_default,
89+
bm25_top_k: m.bm25_top_k_off,
90+
ego_depth: m.ego_depth_default,
9091
ppr_alpha: PPR.alpha,
9192
objective: ObjectiveMode::Submodular,
9293
},
826 Bytes
Binary file not shown.

docs/Context-Selection-for-Git-Diff/v2/main.tex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -677,7 +677,7 @@ \subsection{Limitations of the Current Empirical Snapshot}
677677
\begin{itemize}
678678
\item \textbf{SWE-bench Verified file recall is saturated.} On SWE-bench Verified, both graph-based scoring modes (EGO and PPR) return $0.913$ at every budget from $8000$ to unlimited, while external BM25 produces a strictly monotone curve from $0.540$ at $B{=}8000$ up to $\sim 0.95$ at $B{=}128{,}000$. The plateau is consistent with the observation that on this benchmark the gold file set is dominated by files that are direct seeds of the patch, so any retrieval method that returns all surviving seeds achieves the same recall regardless of subsequent expansion. SWE-bench Verified is therefore informative as a baseline-comparison metric (graph $\gg$ lexical at small budgets) but uninformative as a scoring-mode comparison.
679679
\item \textbf{PolyBench-500 fragment-level metrics are not reported.} PolyBench-500 ships a \texttt{modified\_nodes} field encoding CST node paths (e.g., \texttt{file.java->program->class\_declaration:Foo->method\_declaration:bar}) rather than line ranges, and the current adapter does not yet resolve these symbolic paths to \texttt{(start\_line, end\_line)} tuples via a tree-sitter pass on the post-patch worktree. PolyBench-500 therefore contributes to file-level recall only; fragment-level and line-level metrics on this benchmark are deferred.
680-
\item \textbf{Aider oracle-mentioned variant.} Aider fair-input has completed on all three test sets (Table~\ref{tab:prelim-baselines}); the oracle-mentioned variant (\texttt{mentioned\_fnames} populated from \texttt{instance.gold\_files}, an upper-bound stress test rather than a baseline) is queued. We expect Aider-oracle to substantially outperform Aider-fair on PolyBench-500 and ContextBench Verified, where the fair-input mode trails BM25 (Aider-fair $0.284$ vs.\ BM25 $0.673$ on PolyBench-500 at $B{=}8000$); whether Aider-oracle approaches diffctx-EGO recall is the open question.
680+
\item \textbf{Aider PolyBench-500 infrastructure failure rate.} Aider fair-input on PolyBench-500 has a $24.4\%$ non-\texttt{ok} status rate at $B{=}8000$: $85$ instances ($17.0\%$) hit the $600$s helper-process timeout, and $37$ instances ($7.4\%$) fail with \texttt{QueryError: Invalid node type at row 2, column 2: module} — a tree-sitter grammar version mismatch in the bundled \texttt{aider-chat==0.86.2} package against the installed parsers. The intent-to-treat number ($0.284$ pooled at $B{=}8000$) folds these failures in as recall $0$; an \emph{ok}-only sensitivity analysis on the same cells gives $0.375$ on PolyBench-500, still well below external BM25 ($0.673$ on the same instances). For comparison, Aider's \texttt{ok\%} is $94.0\%$ on SWE-bench Verified (mostly Python, the language Aider's repo-map heuristics are most mature on) and $84.8\%$ on ContextBench Verified. The Aider oracle-mentioned variant (\texttt{mentioned\_fnames} populated from \texttt{instance.gold\_files}, an upper-bound stress test rather than a baseline) is queued; whether Aider-oracle approaches diffctx-EGO recall is the open question.
681681
\item \textbf{$(\tau, \beta_{\mathrm{core}})$ post-fix re-calibration.} The hyperparameter calibration in Section~\ref{sec:greedy} predates the seed/selection fixes that produced the v2 numbers (in particular the rescue pass for cores skipped from $\beta_{\mathrm{core}}\cdot B$ and the inclusion of pure-rename new paths in the candidate set). A focused re-calibration sweep over $\tau \in \{0.04, 0.08, 0.12, 0.16, 0.20\}$ on the v2 pipeline is scheduled; we expect the surface to remain flat as in v1 (Table~\ref{tab:calibration-grid}), but report the v2 calibration as an open item rather than relying on the v1 result.
682682
\item \textbf{Per-future timeout.} The runner's per-future timeout does not enforce in single-worker mode because \texttt{concurrent.futures.as\_completed} applies a bulk deadline rather than per-call. About 2\% of instances exceed the configured 180\,s; this increases wall-clock but does not corrupt results.
683683
\end{itemize}

0 commit comments

Comments
 (0)