mirror of
https://github.com/Druthulu/BFM-decomp
synced 2026-10-03 08:07:25 -04:00
e7ebb1cd45
R42: gate_main reverted 61 byte-proven overlay banks it could not distinguish from its own substitution (sweep_parallel gates commit=False by design). Fixed by committing overlay banks before the main batch, chunking main at 8 to bound bisect cost, and replacing every blind 'git checkout -- src/ config/' with commit-or-refuse in ox_campaign and idiom_serial. R43: sweep_parallel had an explicit branch admitting main, which cannot be gated incrementally — wave ab banked 0/105 main cards while its non-main cards banked 94/115 (82%), and the wave read as a drafting failure. sweep_parallel now refuses main and names gate_main.py. Also: validate_targets now prefers the card's own addr field (named symbols like SYS_OBJ_F00 were MALFORMED and discarded whole 220-card waves); ox_campaign deals model lanes by smallest-ratio scheduling (a 73-card wave had put 73 shards on ox and 0 on deepseek); docs/accelerators.md gains the four vacuous-check defects.
126 lines
5.8 KiB
Python
126 lines
5.8 KiB
Python
#!/usr/bin/env python3
|
|
"""idiom_harvest.py — pull the idioms a wave LEARNED out of its shard logs (P31 S58).
|
|
|
|
WHY THIS EXISTS. Waves ab..ah banked 1,262 functions and produced 211 explicit
|
|
"the cookbook did NOT cover this" findings, and not one reached `docs/matching-cookbook.md`.
|
|
R16 makes the feedback loop mandatory ("after each match, feed the GENERALIZABLE lesson back into
|
|
BOTH the cookbook AND the tooling"), and the flywheel is the whole reason the bank rate climbs
|
|
wave over wave. A campaign that drafts the next wave without harvesting the last one is spending
|
|
the models to re-learn what it already knows. Drew, 2026-08-23: harvest after each wave lands,
|
|
BEFORE the next wave's cards are drawn — "that's how our whole system works."
|
|
|
|
WHAT IT TRUSTS. Only notes from functions the WHOLE-BINARY BYTE-GATE accepted. `api_agent` writes
|
|
its note beside a `match_one` verdict, and match_one masks relocations — a wrong-symbol draft
|
|
reports MATCH and its "lesson" is then a lesson about a function we did not actually reproduce.
|
|
Banked-ness is derived from `corpus.stubs()` (R33: derive from the invariant, do not re-parse):
|
|
a function that is no longer an INCLUDE_ASM stub in its binary is banked, by construction.
|
|
|
|
tools/idiom_harvest.py --waves ab,ac,ad,ae,af,ag --out .run/idiom_candidates.md
|
|
tools/idiom_harvest.py --waves ah # the per-wave campaign call
|
|
"""
|
|
import argparse
|
|
import ast
|
|
import glob
|
|
import json
|
|
import os
|
|
import re
|
|
import sys
|
|
|
|
REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
os.chdir(REPO)
|
|
sys.path.insert(0, "tools")
|
|
import corpus # noqa: E402
|
|
|
|
VERDICT = re.compile(r"^ (func_[0-9A-Fa-f]+|[A-Za-z_]\w*): (\S.*?) after (\d+) oracle call\(s\), "
|
|
r"\$[\d.]+, (\{.*)$")
|
|
# The phrases a model reaches for when it believes it found something the cookbook lacks. Kept
|
|
# broad on purpose: a false candidate costs a line in a review file, a missed one costs the idiom.
|
|
NOVEL = re.compile(r"cookbook did ?n[o']?t|cookbook DID NOT|not (?:already )?(?:in|covered|flagged)"
|
|
r"|new (?:micro-)?idiom|NEW IDIOM|undocumented|no section covers", re.I)
|
|
|
|
|
|
def wave_binaries(tag):
|
|
"""fn -> binary for every card in the wave, so banked-ness can be checked per binary."""
|
|
p = f".run/wave_{tag}_cards.json"
|
|
if not os.path.exists(p):
|
|
return {}
|
|
c = json.load(open(p))
|
|
c = c if isinstance(c, list) else c.get("cards", [])
|
|
return {x["fn"]: x["binary"] for x in c}
|
|
|
|
|
|
def main():
|
|
ap = argparse.ArgumentParser()
|
|
ap.add_argument("--waves", required=True, help="comma-separated wave tags")
|
|
ap.add_argument("--out", default=".run/idiom_candidates.md")
|
|
ap.add_argument("--all-verdicts", action="store_true",
|
|
help="include notes from drafts the byte-gate REJECTED (off by default: an "
|
|
"unbanked draft's lesson is unproven and may be actively wrong)")
|
|
a = ap.parse_args()
|
|
|
|
stub_cache = {}
|
|
|
|
def is_banked(fn, binary):
|
|
if not binary:
|
|
return None
|
|
if binary not in stub_cache:
|
|
try:
|
|
stub_cache[binary] = {s.symbol for s in corpus.stubs(binary).values()}
|
|
except Exception:
|
|
stub_cache[binary] = None
|
|
stubs = stub_cache[binary]
|
|
return None if stubs is None else (fn not in stubs)
|
|
|
|
rows, seen = [], set()
|
|
n_verdicts = n_notes = 0
|
|
for tag in a.waves.split(","):
|
|
binof = wave_binaries(tag)
|
|
for p in sorted(glob.glob(f".run/wave_{tag}/shard*.log")):
|
|
for line in open(p, errors="ignore"):
|
|
m = VERDICT.match(line.rstrip("\n"))
|
|
if not m:
|
|
continue
|
|
n_verdicts += 1
|
|
fn, verdict, calls, blob = m.group(1), m.group(2), int(m.group(3)), m.group(4)
|
|
try:
|
|
d = ast.literal_eval(blob)
|
|
except (ValueError, SyntaxError):
|
|
continue
|
|
note = (d.get("notes") or "").strip() if isinstance(d, dict) else ""
|
|
if not note:
|
|
continue
|
|
n_notes += 1
|
|
banked = is_banked(fn, binof.get(fn))
|
|
if not a.all_verdicts and banked is not True:
|
|
continue
|
|
if not NOVEL.search(note):
|
|
continue
|
|
key = note[:160]
|
|
if key in seen:
|
|
continue
|
|
seen.add(key)
|
|
rows.append({"wave": tag, "fn": fn, "binary": binof.get(fn, "?"),
|
|
"oracle_calls": calls, "banked": banked, "note": note})
|
|
|
|
rows.sort(key=lambda r: (-len(r["note"]), r["fn"]))
|
|
with open(a.out, "w") as fh:
|
|
fh.write(f"# Idiom candidates — waves {a.waves}\n\n")
|
|
fh.write(f"Parsed {n_verdicts} verdict lines, {n_notes} carried a note. "
|
|
f"**{len(rows)} novel-idiom candidates** from BYTE-GATE-BANKED functions"
|
|
f"{' (plus rejected drafts: --all-verdicts)' if a.all_verdicts else ''}.\n\n"
|
|
"Each note is the drafting model's own claim. It is EVIDENCE, not a cookbook "
|
|
"entry: the function is byte-proven, the explanation is not. Verify the tell "
|
|
"against the .s before writing a section.\n\n")
|
|
for r in rows:
|
|
fh.write(f"\n## {r['fn']} · {r['binary']} · wave {r['wave']} · "
|
|
f"{r['oracle_calls']} oracle call(s)\n\n{r['note']}\n")
|
|
print(f"verdict lines {n_verdicts} · notes {n_notes} · novel candidates {len(rows)} -> {a.out}")
|
|
# R32: assert the denominator rather than reporting a bare count.
|
|
if n_verdicts == 0:
|
|
sys.exit("R32: 0 verdict lines parsed — wrong wave tag or the log format moved")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main() or 0)
|