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

127 lines
5.6 KiB
Python

#!/usr/bin/env python3
"""sweep_citations.py — deterministically localise every gcc-source citation in a markdown doc.
WHY THIS IS A TOOL AND NOT AN AGENT WAVE. The codegen-map audits (T34/T35) established that our
docs cite `tools/reference/gcc-papermario/` = gcc **2.8.1**, while we build with **2.7.2**
(`tools/reference/gcc-2.7.2/`), and that the drift is large and NON-uniform (+27 in sched.c, +611 in
reload1.c) — large enough that a citation lands inside a DIFFERENT FUNCTION and every sentence built
on it still reads plausibly. Deciding *whether a claim is true* needs judgement. Deciding *where a
line number actually points* does not: it is a lookup, and a lookup belongs in zero-token tooling.
So this tool answers the mechanical half exactly, and leaves only genuine judgement for a human/agent:
symbol-form cite (`combine_regs:1825`) -> compare to the symbol's REAL 2.7.2 definition line.
Verdict is fully determined: OK / DRIFT(+N) / NO-SYMBOL.
file-form cite (`cse.c:8626`) -> report which function that line falls INSIDE in 2.7.2,
so a reader can see at a glance whether the doc's
surrounding prose is talking about that function at all.
A cite whose enclosing function differs from the symbol the prose names is the exact signature of the
"landed in a neighbouring function" failure — that is what to grep the report for.
python3 tools/sweep_citations.py docs/matching-cookbook.md [--src tools/reference/gcc-2.7.2]
"""
import argparse
import os
import re
import sys
DEF_RE = re.compile(r"^([a-z_][a-z0-9_]*)\s*\(") # K&R definition: name on its own line
# `sym:1234` or `file.c:1234` (optionally a range `1234-1250`)
CITE_RE = re.compile(r"([A-Za-z_][A-Za-z0-9_./-]*?)\s*:\s*(\d{2,5})(?:\s*-\s*\d{2,5})?")
SKIP_PREFIX = ("http", "https", "ftp", "Phase", "phase", "§")
def index_source(src):
"""(defs, lines) — defs: symbol -> [(file, line)] ; lines: file -> [text]."""
defs, lines = {}, {}
for root, _dirs, files in os.walk(src):
for f in files:
if not f.endswith((".c", ".h", ".md")):
continue
p = os.path.join(root, f)
rel = os.path.relpath(p, src)
try:
L = open(p, errors="replace").read().split("\n")
except Exception:
continue
lines[rel] = L
lines.setdefault(os.path.basename(rel), L) # allow bare "cse.c"
for i, ln in enumerate(L, 1):
m = DEF_RE.match(ln)
if m:
defs.setdefault(m.group(1), []).append((rel, i))
return defs, lines
def enclosing(lines, fname, line):
"""Name of the function whose K&R definition most recently precedes `line`."""
L = lines.get(fname) or lines.get(os.path.basename(fname))
if not L or not (1 <= line <= len(L)):
return None, len(L) if L else 0
best = None
for i in range(line - 1, -1, -1):
m = DEF_RE.match(L[i])
if m:
best = (m.group(1), i + 1)
break
return best, len(L)
def main():
ap = argparse.ArgumentParser()
ap.add_argument("doc")
ap.add_argument("--src", default="tools/reference/gcc-2.7.2")
ap.add_argument("--only-problems", action="store_true")
a = ap.parse_args()
if not os.path.isdir(a.src):
print("no source tree at %s" % a.src)
sys.exit(2)
defs, lines = index_source(a.src)
rows, seen = [], set()
for n, raw in enumerate(open(a.doc, errors="replace").read().split("\n"), 1):
for m in CITE_RE.finditer(raw):
who, ln = m.group(1), int(m.group(2))
if who.startswith(SKIP_PREFIX) or who.isdigit():
continue
key = (who, ln)
if key in seen:
continue
seen.add(key)
is_file = who.endswith((".c", ".h", ".md")) or "/" in who
if is_file:
base = os.path.basename(who)
if base not in lines and base.replace(".md", "") not in lines:
continue
enc, total = enclosing(lines, base, ln)
if enc is None:
rows.append((n, who, ln, "OUT-OF-RANGE", "file has %d lines" % total))
else:
rows.append((n, who, ln, "IN-FN", "%s (defined :%d)" % (enc[0], enc[1])))
else:
if who not in defs:
continue # not a gcc symbol we can resolve; skip quietly
cands = defs[who]
near = min(cands, key=lambda c: abs(c[1] - ln))
d = ln - near[1]
verdict = "OK" if abs(d) <= 3 else "DRIFT %+d" % d
rows.append((n, who, ln, verdict, "%s real :%d" % (near[0], near[1])))
probs = [r for r in rows if r[3].startswith("DRIFT") or r[3] == "OUT-OF-RANGE"]
print("%s — %d resolvable citations" % (a.doc, len(rows)))
print(" symbol-form OK : %d" % len([r for r in rows if r[3] == "OK"]))
print(" symbol-form DRIFTED : %d" % len([r for r in rows if r[3].startswith("DRIFT")]))
print(" file-form localised : %d" % len([r for r in rows if r[3] == "IN-FN"]))
print(" out of range : %d" % len([r for r in rows if r[3] == "OUT-OF-RANGE"]))
print()
show = probs if a.only_problems else rows
print("%-6s %-30s %-7s %-12s %s" % ("mdline", "cite", "line", "verdict", "in real 2.7.2"))
for n, who, ln, v, note in show:
print("%-6d %-30s %-7d %-12s %s" % (n, who[:30], ln, v, note))
if __name__ == "__main__":
main()