Skip to content

Commit 75afc68

Browse files
feat: silent-fix signal in the gate + bulk PR-ref warming for scale
- build_security_dataset.py: silent_fix_prob>=0.70 now also ADMITS a row (gate), not just tiers it — so classifying gate-dropped rows becomes a recall lever. Test updated. - local_diffs.py: `warm-prs` bulk-fetches every PR head ref per repo (one fetch -> all PR diffs local & instant; geth = 14,456 refs / +120MB), and PR lookup now tolerates both ref layouts. Makes a full 17k-row classification LLM-bound instead of network-bound. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 5d95356 commit 75afc68

3 files changed

Lines changed: 47 additions & 6 deletions

File tree

collection/local_diffs.py

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -76,13 +76,35 @@ def get_commit_diff(client: str, sha: str) -> str | None:
7676
return r.stdout if r.returncode == 0 and r.stdout.strip() else None
7777

7878

79+
def warm_prs(client: str) -> None:
80+
"""Bulk-fetch every PR head ref so all PR diffs are served locally/instantly.
81+
One fetch per repo (~+120 MB blobless for geth) instead of a network call per
82+
PR — turns the 17k-row classification into an LLM-bound-only job."""
83+
p = ensure_clone(client)
84+
r = _run(["git", "-C", str(p), "fetch", "--filter=blob:none", "--quiet",
85+
"origin", "+refs/pull/*/head:refs/pull/*/head"], timeout=1200)
86+
n = _run(["git", "-C", str(p), "for-each-ref", "refs/pull/", "--format=x"]).stdout.count("x")
87+
print(f"[local_diffs] {client}: {n} PR refs local"
88+
+ ("" if r.returncode == 0 else " (some conflicts skipped)"), file=sys.stderr)
89+
90+
91+
def _resolve_pr_ref(p: Path, n: str) -> str | None:
92+
"""PR head ref, tolerating both layouts (refs/pull/N/head and refs/pull/N)."""
93+
for ref in (f"refs/pull/{n}/head", f"refs/pull/{n}"):
94+
if _run(["git", "-C", str(p), "rev-parse", "--verify", "--quiet", ref]).returncode == 0:
95+
return ref
96+
return None
97+
98+
7999
def get_pr_diff(client: str, n: str) -> str | None:
80100
p = ensure_clone(client)
81-
ref = f"refs/pull/{n}/head"
82-
if _run(["git", "-C", str(p), "rev-parse", "--verify", "--quiet", ref]).returncode != 0:
83-
f = _run(["git", "-C", str(p), "fetch", "--quiet", "origin", f"{ref}:{ref}"], timeout=300)
84-
if f.returncode != 0:
101+
ref = _resolve_pr_ref(p, n)
102+
if ref is None:
103+
target = f"refs/pull/{n}/head"
104+
if _run(["git", "-C", str(p), "fetch", "--quiet", "origin",
105+
f"{target}:{target}"], timeout=300).returncode != 0:
85106
return None
107+
ref = target
86108
head = _run(["git", "-C", str(p), "rev-parse", ref]).stdout.strip()
87109
if not head:
88110
return None
@@ -153,6 +175,7 @@ def main() -> int:
153175
ap = argparse.ArgumentParser()
154176
sub = ap.add_subparsers(dest="cmd", required=True)
155177
w = sub.add_parser("warm"); w.add_argument("--client", required=True)
178+
wp = sub.add_parser("warm-prs"); wp.add_argument("--client", required=True)
156179
rf = sub.add_parser("refresh"); rf.add_argument("--client", required=True)
157180
d = sub.add_parser("diff")
158181
d.add_argument("--client", required=True); d.add_argument("--url", required=True)
@@ -163,6 +186,10 @@ def main() -> int:
163186
ensure_clone(c)
164187
print(f"[local_diffs] {c} ready at {repo_path(c)}")
165188
return 0
189+
if a.cmd == "warm-prs":
190+
for c in clients:
191+
warm_prs(c)
192+
return 0
166193
if a.cmd == "refresh":
167194
for c in clients:
168195
refresh(c)

pipeline/build_security_dataset.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -344,7 +344,16 @@ def build(df: pd.DataFrame) -> tuple[pd.DataFrame, dict]:
344344
# match also fires on release notes that merely *list* a "fix crash". T2
345345
# already dropped dep-bump/CI titles upstream.
346346
has_fiximpact = df["title"].fillna("").str.contains(FIX_IMPACT_RE)
347-
df["security_relevant"] = has_id | has_sev | has_kw | has_stride | has_cwe | has_fiximpact
347+
# Recall expansion via the learned classifier: a row the LLM confidently
348+
# calls a silent fix (silent_fix_prob >= 0.70) is admitted even if the
349+
# deterministic keyword gate missed it. Only fires where a classification
350+
# exists (column present); absent -> no effect.
351+
if "silent_fix_prob" in df.columns:
352+
has_silentfix = pd.to_numeric(df["silent_fix_prob"], errors="coerce").fillna(0) >= 0.70
353+
else:
354+
has_silentfix = pd.Series(False, index=df.index)
355+
df["security_relevant"] = (has_id | has_sev | has_kw | has_stride | has_cwe
356+
| has_fiximpact | has_silentfix)
348357

349358
sec = df[df["security_relevant"]].copy()
350359
# fix_commit (issue #89 field, method-2 backlink): the fixing commit SHA is

tests/test_security_dataset.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,12 @@ def test_every_row_has_a_security_signal(df):
5858
r"|race condition)\b[^.\n]{0,25}"
5959
r"\b(?:fix|fixed|prevent|avoid|guard against|resolved|patch)\w*\b", re.I)
6060
has_fiximpact = df["title"].fillna("").str.contains(fiximpact)
61-
assert bool((has_sev | has_kw | has_stride | has_cwe | has_id | has_fiximpact).all())
61+
if "silent_fix_prob" in df.columns:
62+
has_silentfix = pd.to_numeric(df["silent_fix_prob"], errors="coerce").fillna(0) >= 0.70
63+
else:
64+
has_silentfix = pd.Series(False, index=df.index)
65+
assert bool((has_sev | has_kw | has_stride | has_cwe | has_id | has_fiximpact
66+
| has_silentfix).all())
6267

6368

6469
def test_confidence_values(df):

0 commit comments

Comments
 (0)