Files
Drew T 827295e241 tools+docs(phase-33.5): task 13.5 — the tools audit + the two dictionaries: tools/tool_census.py (two agreeing enumerations of 327 tool files; docstring/SETUP row/consumers/class derived from the tree; the authored half in config/tool_dictionary.tsv — phase · portability · the NEED each tool answers · what · adapts · verdict — with coverage asserted both ways) → docs/tool-index.md (need-keyed, KEEP-GEN, Reference-index row, wiki + how-to pointers), the kit's tools/MANIFEST.md regenerated (header states live 293 + superseded 28 = 321 rows), and the two verbatim corpora in-tree (Drew, confirmed S91): decomp-architect/corpus/tools/<phase>/ (302 copies + 28 superseded pointers + INDEX) and corpus/cookbook/ (the cookbook, its symptom index, the codegen map, a front page stating what transfers per compiler) — sha1-equal to their sources by tool_census --check in tools-health, regenerated by make kit-corpus; kit_lint exempts the corpus dirs (verbatim evidence) but syntax-checks them; G66 (consult the tool dictionary first) + G67 (translate an inherited idiom through its pass) + two memory seeds (34 at install); SETUP Step 6 installs docs/knowledge-corpus.md and checks the manifest against its own stated total; the ops-setup dictionary rows; the intake's Phase 7 cites G66/G67 and Phase 10 + Part C name the raw-cast → declared-symbol step; templates/layout-contract.md (the five-tool probe, a draft for the split). The review under Drew's criterion: 93 no-consumer tools (one Opus agent's draft, verified: 0 defects, every successor live, 0 live consumers, 0 collisions; four one-off verdicts overturned to STILL-NEEDED) → 34 retired by git mv to tools/sunset/ (28 superseded, 6 one-offs; README review table; SETUP rows moved; Archive-index group). Run 4 (fresh throwaway, the final kit): stopped on my Step-6 check (321 vs the live 293) → both sides derived → resumed → PASS 10/10, manifest 56 == 56, 4 commits, guardrails held (the one foreign path was the timeline regenerated by the detached tools-health). tools-health OK; doc_links --strict rc 0; audit_public OK over 6,842 paths; the purge probe PASSED (Phase 34's gate open). decision-log "P33.5 S91" + accelerators "P33.5 S91" banked; log + checkpoint (NEXT = task 14, xHigh, fresh session)
2026-09-07 22:09:15 -06:00

95 lines
3.9 KiB
Python

#!/usr/bin/env python3
"""P31 T6 — group the recorded PLUMBING failures into sweepable campaign groups (R38: the ledgers
already name every conflict; this derives the work queue from them instead of re-diagnosing).
Reads every classified failure ledger (newest verdict per (binary, fn)), keeps the still-open
PLUMBING rows, parses the conflict out of the recorded detail, and groups:
SELF — `conflicting types for 'FN'` where FN is the function itself
-> recover_integration --stages demacroize,tu-scope (the measured self_decl class)
CALLEE — conflicting types for another function -> --stages tu-scope (+ the
gate ladder's cast_call_sites, already standing)
DATA — conflicting types for / undefined reference to a D_* symbol
-> tu-scope where decl-shaped; the §171b-1 data-definition carry where undefined
OTHER — unparsed detail (counted, printed — R32: never silently dropped)
Output: .run/plumbing_groups.json — rows {binary, fn, sym, klass, detail}; groups keyed
(klass, binary) with per-symbol clusters, sized for the sweep order (biggest first).
"""
import collections, glob, json, os, re, sys
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import corpus
LEDGER_RE = re.compile(r"failed\.(?P<bin>[A-Za-z0-9_]+?)(?:\.\d+)?\.classified\.txt$")
CONF_RE = re.compile(r"conflicting types for [`']([A-Za-z_]\w*)")
UNDEF_RE = re.compile(r"undefined reference to [`']([A-Za-z_]\w*)")
_stub_cache = {}
def is_stub(b, fn):
if b not in _stub_cache:
try:
_stub_cache[b] = {s.symbol for s in corpus.stubs(b).values()}
except Exception:
_stub_cache[b] = set()
return fn in _stub_cache[b]
def main():
newest = {}
files = glob.glob(".run/*failed*.classified.txt") + glob.glob(".run/c294/*failed*.classified.txt")
for f in files:
m = LEDGER_RE.search(os.path.basename(f))
if not m:
continue
b = m.group("bin")
mt = os.path.getmtime(f)
for ln in open(f, errors="replace"):
if "\t" not in ln:
continue
fn, verdict = ln.rstrip("\n").split("\t", 1)
k = (b, fn)
if k not in newest or newest[k][0] < mt:
newest[k] = (mt, verdict)
rows = []
other = 0
for (b, fn), (mt, verdict) in sorted(newest.items()):
if not verdict.startswith("PLUMBING"):
continue
if not is_stub(b, fn):
continue
mc, mu = CONF_RE.search(verdict), UNDEF_RE.search(verdict)
sym = (mc or mu).group(1) if (mc or mu) else None
if sym is None:
other += 1
klass, sym = "OTHER", "?"
elif sym == fn:
klass = "SELF"
elif sym.startswith(("D_", "jtbl_")):
klass = "DATA-UNDEF" if mu else "DATA-DECL"
else:
klass = "CALLEE"
rows.append({"binary": b, "fn": fn, "sym": sym, "klass": klass, "detail": verdict[:160]})
by_kb = collections.Counter((r["klass"], r["binary"]) for r in rows)
by_sym = collections.Counter((r["klass"], r["sym"]) for r in rows if r["sym"] != "?")
out = {"n_open_plumbing": len(rows),
"by_class": dict(collections.Counter(r["klass"] for r in rows)),
"top_groups_by_binary": [{"klass": k, "binary": b, "n": n}
for (k, b), n in by_kb.most_common(30)],
"top_symbols": [{"klass": k, "sym": s, "n": n} for (k, s), n in by_sym.most_common(20)],
"rows": rows}
json.dump(out, open(".run/plumbing_groups.json", "w"), indent=1)
print(f"plumbing_groups: {len(rows)} still-open PLUMBING rows from {len(files)} ledgers")
print(f" by class: {out['by_class']} (OTHER unparsed: {other})")
for g in out["top_groups_by_binary"][:12]:
print(f" {g['klass']:10} {g['binary']:14} n={g['n']}")
print("wrote .run/plumbing_groups.json")
if __name__ == "__main__":
main()