mirror of
https://github.com/Druthulu/BFM-decomp
synced 2026-09-26 13:33:34 -04:00
8a698e1b79
- 21 read-only Opus slices over the 26 worklogs (30,540 lines read; the three giants by line range), briefed by .run/P33.5/log-mining/BRIEF.md with the "already banked?" grep protocol: 777 candidates, 634 already banked, 143 NEW; every cited log line verified to exist by harvest.py and read; 142 banked + 1 dropped (the miner's own low-value verdict) - decomp-kernels.md: twelve kernels DK-69–DK-80 (instrument blind spots; verdict staleness + the health suite; what earns belief; denominators/units/labels; leverage vs tractability + campaign scoping; models and prompts; the unattended run; agents and the tree; edits that keep proofs; the search harness + the compiler as evidence; maintaining the knowledge base; hosts and services), each provenance line generated from the slices' cited worklog lines (bank.py); section 8 retitled; Coverage "In all: DK-1 … DK-80"; every count mention → 80 (kit README, methodology, SETUP row, wiki page) - docs/accelerators.md "P33.5 S92" (two accelerators: a distillation ships with a coverage check; the end-of-project worklog pass recovers the fixed-but-never-generalised lessons — 1 in 5 here) → cited by the twelve kernels (kit_coverage: 59 entries, 42 cited + 15 dispositioned, 0 UNCOVERED; the matcher now requires an un-numbered entry's FULL group text); docs/decision-log.md "P33.5 S92" (R31: the question, the measurement, the pivot, the 82%/18% why, the hindsight path) - evidence tracked under .run/P33.5/log-mining/ (BRIEF, 21 slice reports, HARVEST.md, HARVEST_TABLE.md, harvest.py, bank.py; a dated .gitignore block); expected-manifest +1 (docs/inherited-record.md); make kit-corpus regenerated the record copies - verify: tool_census --check OK (358 copies); kit_coverage OK; kit_lint OK (0 leaks over 436 files); doc_links --strict OK; wiki_render --selftest 32/0; audit_public OK on the new files; 80 DK ids cited, 0 dangling
213 lines
11 KiB
Python
213 lines
11 KiB
Python
#!/usr/bin/env python3
|
||
"""Assert that the day-one kit's distillation cites (or explicitly dispositions) EVERY rule and EVERY hindsight entry of the source project.
|
||
|
||
P33.5 task 14.5 (Drew, 2026-09-07: "the whole of our experience?"). The kit's registry seed (G-rules) and kernels (DK) were written
|
||
from the distilled records; this tool derives the two source populations and refuses a silent gap (R32):
|
||
|
||
* the RULES — every `- **R<n>` heading of phase-ends/DIGEST.md §3 (asserted contiguous 1..N) — must be cited by a `provenance:`
|
||
line of decomp-architect/templates/registry-E.decomp.md, OR have a disposition row in config/kit_coverage_map.tsv
|
||
(kind=rule): `G<ids>` (it became those rules — the ids must exist), `DK-<n>` (realised as a kernel), `ENV`
|
||
(environment-specific, not portable), `PA` (ProjectArchitect's own rule), `FOLDED:G<id>` (absorbed into a named rule without
|
||
a citation), `SEED:<file>` (a memory seed of the kit), `KIT:<path>` (a template or installer step), `RECORD` (kept only in the
|
||
verbatim record — a project-specific procedure rule);
|
||
* the ACCELERATORS — every entry of docs/accelerators.md at SUB-ENTRY granularity (the `## ` headings, and within a heading
|
||
the bold `**(n)` / `**n.` items) — must be cited by a `provenance:` line of decomp-architect/corpus/decomp-kernels.md or of
|
||
the registry seed, OR have a disposition row (kind=accelerator): `DK-<n>` / `G<n>` (folded, uncited), `COOKBOOK` (an
|
||
idiom-level entry that lives in the cookbook corpus), `RECORD`, `NOT-PORTABLE` (this project only — the note says why).
|
||
|
||
Every count is printed with its denominator (R41); an unknown key or an unknown G/DK id in the map is refused (R43); a row for an
|
||
entry that IS cited is informational (printed, not an error). Exit 1 on any gap. In `make tools-health` after tool_census --check.
|
||
"""
|
||
import csv
|
||
import pathlib
|
||
import re
|
||
import sys
|
||
|
||
REPO = pathlib.Path(__file__).resolve().parent.parent
|
||
DIGEST = REPO / "phase-ends" / "DIGEST.md"
|
||
ACCEL = REPO / "docs" / "accelerators.md"
|
||
REGISTRY = REPO / "decomp-architect" / "templates" / "registry-E.decomp.md"
|
||
KERNELS = REPO / "decomp-architect" / "corpus" / "decomp-kernels.md"
|
||
MAP = REPO / "config" / "kit_coverage_map.tsv"
|
||
RULE_DISPOSITIONS = ("ENV", "PA", "RECORD")
|
||
ACCEL_DISPOSITIONS = ("COOKBOOK", "RECORD", "NOT-PORTABLE")
|
||
|
||
|
||
def provenance_lines(path):
|
||
return [ln for ln in path.read_text(encoding="utf-8").splitlines() if ln.startswith("provenance:")]
|
||
|
||
|
||
def rule_ids():
|
||
ids = sorted({int(m) for m in re.findall(r"^- \*\*R(\d+)\b", DIGEST.read_text(encoding="utf-8"), re.M)})
|
||
if ids != list(range(1, ids[-1] + 1)):
|
||
sys.exit(f"kit_coverage: DIGEST §3 rule ids are not contiguous 1..{ids[-1]}: missing {sorted(set(range(1, ids[-1] + 1)) - set(ids))}")
|
||
return ids
|
||
|
||
|
||
def g_ids():
|
||
return {int(m) for m in re.findall(r"^### G(\d+)\b", REGISTRY.read_text(encoding="utf-8"), re.M)}
|
||
|
||
|
||
def dk_ids():
|
||
return {int(m) for m in re.findall(r"^### DK-(\d+)\b", KERNELS.read_text(encoding="utf-8"), re.M)}
|
||
|
||
|
||
def accel_entries():
|
||
"""[(key, heading-or-item text, line)] — one per `## ` heading; a heading with bold numbered items yields one per item instead."""
|
||
lines = ACCEL.read_text(encoding="utf-8").splitlines()
|
||
heads = [(i, ln[3:].strip()) for i, ln in enumerate(lines) if ln.startswith("## ")]
|
||
out = []
|
||
for n, (i, title) in enumerate(heads):
|
||
end = heads[n + 1][0] if n + 1 < len(heads) else len(lines)
|
||
m = re.match(r"(A\d+|#\d+|S\d+(?: \(\d+\))?|P\d+(?:\.\d+)?(?: [A-Z]\w*)?(?: S\d+)?)\b", title)
|
||
if m:
|
||
group = m.group(1)
|
||
else:
|
||
t = re.search(r"\((P\d+ S\d+)\)", title)
|
||
group = t.group(1) if t else title
|
||
items = [(j, lines[j]) for j in range(i + 1, end) if re.match(r"^\*\*(\(\d+\)|\d+\.)\s", lines[j])]
|
||
if items:
|
||
for j, ln in items:
|
||
num = re.match(r"^\*\*\(?(\d+)", ln).group(1)
|
||
out.append((f"{group} ({num})", ln[2:80].strip("* "), j + 1))
|
||
else:
|
||
out.append((group, title, i + 1))
|
||
return out
|
||
|
||
|
||
def accel_cited(key, prov_text):
|
||
"""Is this entry cited by any provenance line? Token rules mirror how the kernels cite the ledger (see the census in the task log)."""
|
||
m = re.match(r"^(A\d+|#\d+|S\d+|P\d+(?:\.\d+)?(?: [A-Z]\w*)?(?: S\d+)?)(?: \((\d+)\))?$", key)
|
||
if not m:
|
||
return False
|
||
group, num = m.group(1), m.group(2)
|
||
tok = re.escape(group.split(" ")[0])
|
||
if group.startswith("P31 S58"): # the harness wounds, cited as "harness wound n" or "… wounds" 1–5 (a range)
|
||
for ln in prov_text:
|
||
if "accelerator" not in ln.lower() or "wound" not in ln.lower():
|
||
continue
|
||
tail = ln.lower().split("wound", 1)[1][:60]
|
||
nums = set()
|
||
for a, b in re.findall(r"(\d+)\s*[–-]\s*(\d+)", tail):
|
||
nums |= set(range(int(a), int(b) + 1))
|
||
nums |= {int(x) for x in re.findall(r"(?<![\d–-])(\d+)(?![\d–-])", tail)}
|
||
if num is None or int(num) in nums:
|
||
return True
|
||
return False
|
||
if group.startswith("P31 S71"): # the journals entry
|
||
pat = r"(journal|S71)"
|
||
elif num:
|
||
pat = rf"\b{tok}\b(?: [A-Z]\w*)? \({num}\b"
|
||
else:
|
||
pat = rf"(?<![\w.]){re.escape(group)}\b(?! \()" # the whole group text: 'P33.5 S92' is not cited by 'P33.5 S91 (1)'
|
||
for ln in prov_text:
|
||
if "accelerator" in ln.lower() and re.search(pat, ln):
|
||
return True
|
||
return False
|
||
|
||
|
||
def read_map():
|
||
rows = []
|
||
if not MAP.exists():
|
||
sys.exit(f"kit_coverage: {MAP.relative_to(REPO)} is missing (R43 — refuse, never assume an empty map)")
|
||
with MAP.open(encoding="utf-8", newline="") as f:
|
||
rd = csv.DictReader(f, delimiter="\t")
|
||
if rd.fieldnames != ["kind", "key", "disposition", "note"]:
|
||
sys.exit(f"kit_coverage: map columns must be kind/key/disposition/note, got {rd.fieldnames}")
|
||
for r in rd:
|
||
if r["kind"] not in ("rule", "accelerator") or not r["key"] or not r["disposition"]:
|
||
sys.exit(f"kit_coverage: bad map row {r}")
|
||
rows.append(r)
|
||
return rows
|
||
|
||
|
||
def check_disposition(kind, disp, gs, dks):
|
||
"""Validate a disposition string; return an error string or None."""
|
||
parts = [x.strip() for x in disp.split("+")]
|
||
for part in parts:
|
||
if kind == "rule" and part in RULE_DISPOSITIONS:
|
||
continue
|
||
if kind == "accelerator" and part in ACCEL_DISPOSITIONS:
|
||
continue
|
||
m = re.match(r"^(FOLDED:)?G(\d+)$", part)
|
||
if m:
|
||
if int(m.group(2)) not in gs:
|
||
return f"G{m.group(2)} does not exist in the registry seed"
|
||
continue
|
||
m = re.match(r"^DK-(\d+)$", part)
|
||
if m: # a rule realised as a kernel, or an accelerator folded into one
|
||
if int(m.group(1)) not in dks:
|
||
return f"DK-{m.group(1)} does not exist in the kernels"
|
||
continue
|
||
m = re.match(r"^SEED:([\w-]+\.md)$", part) # realised as a memory seed of the kit
|
||
if m:
|
||
if not (REPO / "decomp-architect" / "memory-seed" / m.group(1)).exists():
|
||
return f"memory seed {m.group(1)} does not exist"
|
||
continue
|
||
m = re.match(r"^KIT:([\w./-]+)$", part) # realised as a kit template / installer step (a file under the package)
|
||
if m:
|
||
if not (REPO / "decomp-architect" / m.group(1)).exists():
|
||
return f"kit file {m.group(1)} does not exist"
|
||
continue
|
||
return f"unknown disposition token {part!r} for kind {kind}"
|
||
return None
|
||
|
||
|
||
def main():
|
||
rules = rule_ids()
|
||
gs, dks = g_ids(), dk_ids()
|
||
reg_prov = provenance_lines(REGISTRY)
|
||
ker_prov = provenance_lines(KERNELS)
|
||
cited_rules = {int(m) for ln in reg_prov for m in re.findall(r"\bR(\d+)\b", ln)}
|
||
entries = accel_entries()
|
||
prov_all = reg_prov + ker_prov
|
||
cited_accel = {k for k, _, _ in entries if accel_cited(k, prov_all)}
|
||
rows = read_map()
|
||
errs, info = [], []
|
||
map_rules = {}
|
||
map_accel = {}
|
||
for r in rows:
|
||
e = check_disposition(r["kind"], r["disposition"], gs, dks)
|
||
if e:
|
||
errs.append(f"map row {r['kind']} {r['key']}: {e}")
|
||
if r["kind"] == "rule":
|
||
m = re.match(r"^R(\d+)$", r["key"])
|
||
if not m or int(m.group(1)) not in rules:
|
||
errs.append(f"map row names an unknown rule {r['key']!r} (rules are R1..R{rules[-1]})")
|
||
continue
|
||
map_rules[int(m.group(1))] = r
|
||
else:
|
||
if r["key"] not in {k for k, _, _ in entries}:
|
||
errs.append(f"map row names an unknown accelerator entry {r['key']!r}")
|
||
continue
|
||
map_accel[r["key"]] = r
|
||
uncovered_rules = [n for n in rules if n not in cited_rules and n not in map_rules]
|
||
uncovered_accel = [(k, t, ln) for k, t, ln in entries if k not in cited_accel and k not in map_accel]
|
||
for n in sorted(map_rules):
|
||
if n in cited_rules:
|
||
info.append(f"R{n} is cited AND dispositioned ({map_rules[n]['disposition']}) — the row is informational")
|
||
for k in map_accel:
|
||
if k in cited_accel:
|
||
info.append(f"accelerator {k!r} is cited AND dispositioned ({map_accel[k]['disposition']}) — the row is informational")
|
||
from collections import Counter
|
||
rd = Counter(r["disposition"].split("+")[0].split(":")[0] for r in map_rules.values() if int(r["key"][1:]) not in cited_rules)
|
||
ad = Counter(r["disposition"].split("+")[0] for k, r in map_accel.items() if k not in cited_accel)
|
||
print(f"kit_coverage: rules R1..R{rules[-1]} — {len(cited_rules & set(rules))} cited by the registry seed's provenance lines, "
|
||
f"{len([n for n in rules if n not in cited_rules and n in map_rules])} dispositioned ({dict(rd)}), {len(uncovered_rules)} UNCOVERED")
|
||
print(f"kit_coverage: accelerators — {len(entries)} entries (headings + numbered items of docs/accelerators.md), "
|
||
f"{len(cited_accel)} cited by kernel/registry provenance lines, {len([k for k, _, _ in entries if k not in cited_accel and k in map_accel])} dispositioned ({dict(ad)}), {len(uncovered_accel)} UNCOVERED")
|
||
for i in info:
|
||
print(" INFO", i)
|
||
for n in uncovered_rules:
|
||
errs.append(f"rule R{n} is neither cited by a registry provenance line nor dispositioned in {MAP.relative_to(REPO)}")
|
||
for k, t, ln in uncovered_accel:
|
||
errs.append(f"accelerator {k!r} (docs/accelerators.md:{ln} — {t[:60]}) is neither cited by a kernel/registry provenance line nor dispositioned")
|
||
for e in errs:
|
||
print(" GAP", e)
|
||
print("kit_coverage: OK" if not errs else f"kit_coverage: FAIL ({len(errs)} gap(s))")
|
||
return 1 if errs else 0
|
||
|
||
|
||
if __name__ == "__main__":
|
||
sys.exit(main())
|