Skip to content

Commit 73678ac

Browse files
committed
feat(bench)+docs: --depths flag + editorial paper fixes (D)
(1) Multi-depth orchestrator (E from previous review): `run_final_eval.py --depths L1,L2,...` adds an outer loop over EGO graph traversal radius. Heavy phase (graph build + scoring) re-runs per depth because rel_scores depend on radius; budgets within a depth still share scored state via the existing multi-budget reuse path. Output: <name>_budget_sweep/L<depth>/b<budget>.checkpoint.jsonl When --depths is empty, behavior is unchanged from the previous commit (single depth from MODE.ego_depth_extended). The flag is a no-op for non-diffctx baselines and non-EGO scoring modes (warning printed). Per-instance EvalResult.extra now records `ego_depth` when the run explicitly set the depth, so the aggregator can stratify by L. The per-manifest dispatcher was extracted into `_process_manifest` / `_sweep_dir` helpers to keep `main` readable through the depth × manifest × budgets nested loop. Behavior is bit-identical to the previous single-budget and single-depth single-budget paths when those flags are empty. (2) Editorial paper fixes (D): a. Post-hoc diff-understanding framing in §1. The intro now states explicitly that diffctx solves "post-hoc diff understanding" — the patch is given as input, recall measures coverage of the files the patch touches plus the dependency neighborhood needed to interpret them. Distinct from pre-solution issue resolution (the standard SWE-bench setup); we use those benchmarks' gold patches as known diffs against which retrieval is scored, never as a target an agent must generate. Closes the open framing item from external review. b. "42 languages" downsize. Contributions list and Appendix tab:language-support now group languages by empirical-validation tier: Tier 1 (n>=10 in eval): Python, TS, JS, Java, Go, C, Rust, C++. Tier 2 (semantic edge builders, no large-scale validation): ~30 languages including C#, Ruby, PHP, Swift, Haskell, etc. Tier 3 (parser only, no semantic edges): YAML, JSON, TOML. Generic fallback: Markdown, plain text, dotfiles, unparsed. The original undifferentiated "42 languages" claim conflated three different levels of support; the tiered table makes the empirical contribution and the implementation upper bound separately legible.
1 parent 8634ea3 commit 73678ac

3 files changed

Lines changed: 161 additions & 92 deletions

File tree

benchmarks/run_final_eval.py

Lines changed: 138 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,93 @@ def _load_winner(path: Path) -> RunParams:
6767
)
6868

6969

70+
def _sweep_dir(out: Path, name: str, depth: int | None) -> Path:
71+
"""Per-(manifest, depth) sweep subdirectory for budget-sharded checkpoints."""
72+
base = out / f"{name}_budget_sweep"
73+
if depth is not None:
74+
base = base / f"L{depth}"
75+
base.mkdir(parents=True, exist_ok=True)
76+
return base
77+
78+
79+
def _process_manifest(
80+
manifest_path: Path,
81+
adapters: Any,
82+
args: argparse.Namespace,
83+
params: RunParams,
84+
budgets_list: list[int],
85+
eval_fn: Any,
86+
eval_all_cells_fn: Any,
87+
depth: int | None,
88+
) -> list[Any]:
89+
name = manifest_path.stem.removeprefix("test_")
90+
ids = read_manifest(manifest_path)
91+
instances = [i for i in filter_instances_by_manifest(adapters, ids) if i.source_benchmark == name]
92+
if args.limit:
93+
instances = instances[: args.limit]
94+
depth_label = f" L={depth}" if depth is not None else ""
95+
print(f"\n[{name}{depth_label}] {len(instances)} instances")
96+
97+
if eval_all_cells_fn is not None:
98+
params_list = [
99+
RunParams(
100+
tau=params.tau,
101+
core_budget_fraction=params.core_budget_fraction,
102+
budget=b,
103+
scoring=params.scoring,
104+
)
105+
for b in budgets_list
106+
]
107+
ckpt_dir = _sweep_dir(args.out, name, depth)
108+
results_by_budget = run_eval_set_multi_budget(
109+
instances,
110+
eval_all_cells_fn,
111+
params_list,
112+
workers=args.workers,
113+
timeout_per_instance=args.timeout_per_instance,
114+
resume_dir=ckpt_dir,
115+
checkpoint_dir=ckpt_dir,
116+
)
117+
headline_budget = params.budget if params.budget in results_by_budget else budgets_list[-1]
118+
return results_by_budget[headline_budget]
119+
120+
if len(budgets_list) > 1:
121+
ckpt_dir = _sweep_dir(args.out, name, depth)
122+
results_by_budget: dict[int, list[Any]] = {}
123+
for b in budgets_list:
124+
cell_params = RunParams(
125+
tau=params.tau,
126+
core_budget_fraction=params.core_budget_fraction,
127+
budget=b,
128+
scoring=params.scoring,
129+
)
130+
ckpt_b = ckpt_dir / f"b{b}.checkpoint.jsonl"
131+
rs = run_eval_set(
132+
instances,
133+
eval_fn,
134+
cell_params,
135+
workers=args.workers,
136+
timeout_per_instance=args.timeout_per_instance,
137+
resume_from=ckpt_b,
138+
checkpoint_path=ckpt_b,
139+
)
140+
results_by_budget[b] = rs
141+
headline_budget = params.budget if params.budget in results_by_budget else budgets_list[-1]
142+
return results_by_budget[headline_budget]
143+
144+
depth_suffix = f"_L{depth}" if depth is not None else ""
145+
ckpt = args.out / f"{name}{depth_suffix}.checkpoint.jsonl"
146+
return run_eval_set(
147+
instances,
148+
eval_fn,
149+
params,
150+
workers=args.workers,
151+
timeout_per_instance=args.timeout_per_instance,
152+
resume_from=ckpt,
153+
checkpoint_path=ckpt,
154+
)
155+
156+
70157
def main() -> int:
71158
p = argparse.ArgumentParser(description=__doc__)
72159
p.add_argument("--winner", type=Path, required=True)
@@ -95,6 +182,19 @@ def main() -> int:
95182
"each budget as a separate process). Output: <name>__b<budget>.checkpoint.jsonl. "
96183
"Empty (default): use winner.budget as a single cell with the legacy path.",
97184
)
185+
p.add_argument(
186+
"--depths",
187+
type=str,
188+
default="",
189+
help="Comma-separated EGO graph traversal depths (e.g. '0,1,2,3,4'). "
190+
"When set with --baseline=diffctx and --scoring=ego, the orchestrator "
191+
"loops over each depth as the outer axis and reuses --budgets within "
192+
"each depth. Heavy phase (graph build + scoring) is re-run per depth "
193+
"because rel_scores depend on the traversal radius; budgets within a "
194+
"depth share scored state. Output: <name>_budget_sweep/L<depth>/b<budget>.checkpoint.jsonl. "
195+
"Empty (default): single depth from MODE.ego_depth_extended (= 2 unless "
196+
"DIFFCTX_OP_GRAPH_DEPTH is set in the calling shell).",
197+
)
98198
args = p.parse_args()
99199

100200
repo_root = args.repos_dir or default_repos_dir()
@@ -125,6 +225,13 @@ def main() -> int:
125225
"looping per budget without compute_scored_state reuse."
126226
)
127227

228+
depths_list: list[int] = []
229+
if args.depths.strip():
230+
depths_list = [int(x.strip()) for x in args.depths.split(",") if x.strip()]
231+
if args.baseline != "diffctx" or params.scoring != "ego":
232+
print(f"--depths set but baseline={args.baseline} scoring={params.scoring}; " "depths only affect EGO. Ignoring.")
233+
depths_list = []
234+
128235
eval_fn = _make_eval_fn(args.baseline, repo_root, request_timeout=args.timeout_per_instance)
129236
eval_all_cells_fn = (
130237
make_diffctx_eval_all_cells_fn(repo_root) if args.baseline == "diffctx" and len(budgets_list) > 1 else None
@@ -133,88 +240,38 @@ def main() -> int:
133240
args.out.mkdir(parents=True, exist_ok=True)
134241
reports = []
135242
all_results = []
136-
for manifest_path in manifests:
137-
name = manifest_path.stem.removeprefix("test_")
138-
ids = read_manifest(manifest_path)
139-
instances = [i for i in filter_instances_by_manifest(adapters, ids) if i.source_benchmark == name]
140-
if args.limit:
141-
instances = instances[: args.limit]
142-
print(f"\n[{name}] {len(instances)} instances")
143-
144-
if eval_all_cells_fn is not None:
145-
# Multi-budget reuse path: compute_scored_state runs once per
146-
# instance, select_with_params runs once per budget. The
147-
# checkpoint files are sharded as <name>__b<budget>.checkpoint.jsonl
148-
# so the existing aggregator picks them up unchanged.
149-
params_list = [
150-
RunParams(
151-
tau=params.tau,
152-
core_budget_fraction=params.core_budget_fraction,
153-
budget=b,
154-
scoring=params.scoring,
155-
)
156-
for b in budgets_list
157-
]
158-
ckpt_dir = args.out / f"{name}_budget_sweep"
159-
ckpt_dir.mkdir(parents=True, exist_ok=True)
160-
results_by_budget = run_eval_set_multi_budget(
161-
instances,
162-
eval_all_cells_fn,
163-
params_list,
164-
workers=args.workers,
165-
timeout_per_instance=args.timeout_per_instance,
166-
resume_dir=ckpt_dir,
167-
checkpoint_dir=ckpt_dir,
168-
)
169-
# Pick the single "headline" budget (winner) for the per-manifest
170-
# aggregation; per-budget JSONLs land in the sweep subdir.
171-
headline_budget = params.budget if params.budget in results_by_budget else budgets_list[-1]
172-
results = results_by_budget[headline_budget]
173-
elif len(budgets_list) > 1:
174-
# Multi-budget loop for non-diffctx baselines. No reuse, but
175-
# uniform output schema with the diffctx path.
176-
results = []
177-
ckpt_dir = args.out / f"{name}_budget_sweep"
178-
ckpt_dir.mkdir(parents=True, exist_ok=True)
179-
results_by_budget: dict[int, list[Any]] = {}
180-
for b in budgets_list:
181-
cell_params = RunParams(
182-
tau=params.tau,
183-
core_budget_fraction=params.core_budget_fraction,
184-
budget=b,
185-
scoring=params.scoring,
186-
)
187-
ckpt_b = ckpt_dir / f"b{b}.checkpoint.jsonl"
188-
rs = run_eval_set(
189-
instances,
190-
eval_fn,
191-
cell_params,
192-
workers=args.workers,
193-
timeout_per_instance=args.timeout_per_instance,
194-
resume_from=ckpt_b,
195-
checkpoint_path=ckpt_b,
196-
)
197-
results_by_budget[b] = rs
198-
headline_budget = params.budget if params.budget in results_by_budget else budgets_list[-1]
199-
results = results_by_budget[headline_budget]
200-
else:
201-
ckpt = args.out / f"{name}.checkpoint.jsonl"
202-
results = run_eval_set(
203-
instances,
204-
eval_fn,
205-
params,
206-
workers=args.workers,
207-
timeout_per_instance=args.timeout_per_instance,
208-
resume_from=ckpt,
209-
checkpoint_path=ckpt,
210-
)
211243

212-
for r in results:
213-
r.extra.setdefault("benchmark_manifest", name)
214-
all_results.extend(results)
215-
report = aggregate_test_set(name, results)
216-
reports.append(report)
217-
(args.out / f"{name}.json").write_text(json.dumps([asdict(r) for r in results], indent=2, default=str))
244+
# When --depths is set, loop EGO traversal radius as the outer axis.
245+
# Heavy phase (graph build + scoring) re-runs per depth because
246+
# rel_scores depend on radius; budgets within a depth share scored state.
247+
# When --depths is empty, the depth is whatever DIFFCTX_OP_GRAPH_DEPTH
248+
# the parent shell set (default 2 from MODE.ego_depth_extended).
249+
loop_depths: list[int | None] = list(depths_list) if depths_list else [None]
250+
for depth in loop_depths:
251+
if depth is not None:
252+
_os.environ["DIFFCTX_OP_GRAPH_DEPTH"] = str(depth)
253+
print(f"\n=== Sweep depth L={depth} (DIFFCTX_OP_GRAPH_DEPTH={depth}) ===")
254+
for manifest_path in manifests:
255+
results = _process_manifest(
256+
manifest_path=manifest_path,
257+
adapters=adapters,
258+
args=args,
259+
params=params,
260+
budgets_list=budgets_list,
261+
eval_fn=eval_fn,
262+
eval_all_cells_fn=eval_all_cells_fn,
263+
depth=depth,
264+
)
265+
name = manifest_path.stem.removeprefix("test_")
266+
for r in results:
267+
r.extra.setdefault("benchmark_manifest", name)
268+
if depth is not None:
269+
r.extra.setdefault("ego_depth", depth)
270+
all_results.extend(results)
271+
report = aggregate_test_set(name, results)
272+
reports.append(report)
273+
depth_suffix = f"_L{depth}" if depth is not None else ""
274+
(args.out / f"{name}{depth_suffix}.json").write_text(json.dumps([asdict(r) for r in results], indent=2, default=str))
218275

219276
paper_table = render_paper_table(reports)
220277
lang_agg = aggregate_by_language(all_results)
2.59 KB
Binary file not shown.

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

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -45,13 +45,15 @@ \section{Introduction}
4545

4646
We address this gap by formalizing diff context selection as \textbf{budgeted submodular maximization} over Code Property Graphs. Our key insight is that ``understanding a change'' can be operationalized as covering explanatory concepts---definitions, call targets, type signatures, impacted tests---that propagate through structural dependencies with diminishing returns.
4747

48+
\paragraph{Task scope: post-hoc diff understanding.} We address \emph{post-hoc diff understanding}: a patch $\Delta$ is given as input, and the task is to retrieve a compact set of explanatory code fragments needed to review or reason about that patch within a token budget. This is distinct from \emph{pre-solution issue resolution} (the standard SWE-bench setup), where an agent must produce $\Delta$ from a problem statement without seeing the gold patch. The benchmarks in Section~\ref{sec:eval} are repurposed from issue-resolution datasets: we use their gold patches as known diffs against which file-level retrieval is scored, never as a target an agent must generate. The patch is on the input side; the recall metric measures whether the retrieved context covers the files the patch touches and the dependency neighborhood needed to interpret them.
49+
4850
\paragraph{Contributions.}
4951
\begin{enumerate}
5052
\item A formal problem statement casting context selection as budgeted utility maximization over a partition matroid on multi-resolution fragments. We do not claim a new approximation algorithm; we use known optimization structure to make context selection explicit, analyzable, and extensible. An ``algorithm $\times$ constraint $\times$ guarantee'' table (Table~\ref{tab:algo-constraint-guarantee}) separates the deployed heuristic from analyzable variants and from the submodular concept-coverage extension
5153
\item A typed-edge dependency-graph framework with per-category extraction and treatment (e.g., hub-suppression exemption), generalizing flat code-graph approaches
5254
\item A pluggable relevance-scoring abstraction $\hat{w}(f, \Delta)$ where three algorithm families (bounded ego-network expansion, Personalized PageRank, BM25) instantiate the abstract signal under a single retrieval framework
5355
\item An evaluation protocol using file-level recall against annotated golden contexts at a fixed token budget on multi-language software-engineering benchmarks, with paired-bootstrap statistical comparison against external baselines (BM25 over patch identifiers; Aider repo-map)
54-
\item A reference implementation supporting 42 programming languages and configuration formats (Appendix~\ref{sec:impl-inventory})
56+
\item A reference implementation with tree-sitter parsing for 42 source and configuration formats and dedicated semantic edge builders for 36 of them (Appendix~\ref{sec:impl-inventory}). Of these, eight (Python, JavaScript, TypeScript, Java, Go, Rust, C, C++) appear in the empirical evaluation with $n{\geq}10$ instances; the rest are supported but unvalidated at scale (Section~\ref{sec:impl-inventory}, Table~\ref{tab:language-support})
5557
\end{enumerate}
5658

5759
\newpage
@@ -792,24 +794,34 @@ \section{Heuristics Annex}
792794
\section{Language Support}
793795
\label{sec:impl-inventory}
794796

795-
The reference implementation provides dedicated tree-sitter extraction for 42 languages, grouped by domain. Languages without dedicated extractors fall back to a generic strategy (paragraph-level fragmentation for prose-like content, statement-level for code-like content).
797+
The reference implementation includes tree-sitter parsing for 42 source and configuration formats and dedicated semantic edge builders for 36 of them. Coverage is uneven: a handful of languages have been used extensively in the evaluation; most others are present at the parser/edge-builder level but unvalidated against a benchmark of meaningful size. Table~\ref{tab:language-support} groups languages by tier of empirical exercise so the contribution and the open work are not conflated.
796798

797799
\begin{table}[h]
798800
\centering
799801
\small
800-
\begin{tabular}{lp{8.5cm}}
802+
\begin{tabular}{lp{9.5cm}}
801803
\toprule
802-
\textbf{Group} & \textbf{Languages} \\
804+
\textbf{Tier} & \textbf{Languages} \\
805+
\midrule
806+
Tier 1 — empirically validated ($n{\geq}10$ in evaluation) &
807+
Python ($n{=}891$), TypeScript ($n{=}191$), JavaScript ($n{=}185$),
808+
Java ($n{=}140$), Go ($n{=}40$), C ($n{=}23$), Rust ($n{=}20$), C++ ($n{=}10$) \\
809+
\midrule
810+
Tier 2 — semantic edge builders, no large-scale empirical validation &
811+
C\#, Kotlin, Ruby, PHP, Swift, Scala (general-purpose);
812+
Haskell, Elixir, Erlang, OCaml, Clojure, Julia (functional);
813+
Lua, R, Perl, Bash/Zsh/Fish/Ksh, Dart, Groovy, Objective-C (scripting);
814+
HTML, CSS/SCSS/LESS, GraphQL, HCL/Terraform, CMake, Make, Nix (markup/build);
815+
Cargo, Bazel, dbt, OpenAPI, Prisma, Protobuf, SQL, Ansible (domain) \\
816+
\midrule
817+
Tier 3 — parser only, structural fragmentation without semantic edges &
818+
YAML, JSON, TOML \\
803819
\midrule
804-
General-purpose & Python, JavaScript, TypeScript (incl.\ JSX/TSX), Go, Rust, Java, Kotlin, C, C++, C\#, Ruby, PHP, Swift, Scala \\
805-
Functional & Haskell, Elixir, Erlang, OCaml, Clojure, Julia, F\# \\
806-
Scripting & Lua, R, Perl, Bash/Zsh/Fish/Ksh, Dart, Groovy, Objective-C \\
807-
Markup/Data & HTML, CSS/SCSS/LESS, YAML, JSON, GraphQL, HCL/Terraform, CMake, Make, Nix \\
808-
Domain-specific & Cargo (Rust), Bazel, dbt, OpenAPI, Prisma, Protobuf, SQL, Ansible \\
809-
Generic fallback & Markdown, plain text, TOML, dotfiles, unparsed sources \\
820+
Generic fallback &
821+
Markdown, plain text, dotfiles, unparsed sources (paragraph or statement fragmentation) \\
810822
\bottomrule
811823
\end{tabular}
812-
\caption{Languages supported by the reference implementation. General-purpose and functional languages have full semantic edge builders (call/import resolution, type references); markup and data formats use schema-aware extractors where applicable.}
824+
\caption{Tiered coverage of the reference implementation. Tier 1 has both edge builders and per-language recall numbers in Table~\ref{tab:prelim-lang}. Tier 2 implements the same edge builders but is not exercised against a benchmark of meaningful size in this paper; its inclusion is a code-level claim, not an empirical one. Tier 3 supplies parsing only — sufficient to fragment files for selection but not to draw structural edges. Treat the count of supported formats as an implementation upper bound; only Tier 1 carries empirical evidence.}
813825
\label{tab:language-support}
814826
\end{table}
815827

0 commit comments

Comments
 (0)