Files
BFM-decomp/tools/mine_hindsight.py
T

118 lines
5.5 KiB
Python

#!/usr/bin/env python3
"""mine_hindsight.py — gather every recorded hindsight in the project into one file with file:line anchors (P33 F2).
tools/mine_hindsight.py [--out .run/P33/hindsight.md]
Three sources, none of them re-typed (R31: cite, never backfill):
1. docs/decision-log.md — every `Hindsight / for the wiki` bullet (plain or bold spelling, with its continuation
lines), under the dated entry heading it belongs to;
2. phase-ends/PhaseEnd_*.md — every "What we believed, what failed, and what we would do sooner" section (P31, P32);
3. phase-ends/PhaseEnd_*.md — every `## Deviations` table (all phases).
Output: a Markdown digest (scratch; the retrospective cites the SOURCES by heading/path, this file is the working set).
Prints the census with denominators (R41): entries scanned, hindsight bullets found, PhaseEnds with a believed section,
deviation rows.
"""
import argparse
import pathlib
import re
import sys
REPO = pathlib.Path(__file__).resolve().parent.parent
LOG = REPO / "docs" / "decision-log.md"
HIND_RE = re.compile(r"^\s*- (?:\*\*)?Hindsight[^:]*:(?:\*\*)?\s*(.*)$") # `- Hindsight / for the wiki: …` (plain or bold)
HIND_SEC_RE = re.compile(r"^#{2,4} .*Hindsight", re.I) # `### Hindsight path` sections (newer entries)
def mine_log():
lines = LOG.read_text(encoding="utf-8", errors="replace").splitlines()
heading, entries, out = None, 0, []
i = 0
while i < len(lines):
ln = lines[i]
if ln.startswith("## "):
heading = (ln[3:].strip(), i + 1)
if not ln.startswith("## [date]"):
entries += 1
m = HIND_RE.match(ln)
if m and heading and not heading[0].startswith("[date]"):
body = [m.group(1)]
j = i + 1
while j < len(lines) and lines[j].startswith(" ") and not lines[j].lstrip().startswith("- "):
body.append(lines[j].strip()); j += 1
out.append((heading[0], heading[1], i + 1, " ".join(body)))
i = j; continue
if HIND_SEC_RE.match(ln) and not ln.startswith("## ") and heading and not heading[0].startswith("[date]"):
j = i + 1
body = []
while j < len(lines) and not lines[j].startswith("#"):
if lines[j].strip():
body.append(lines[j].strip())
j += 1
out.append((heading[0], heading[1], i + 1, " ".join(body)))
i = j; continue
i += 1
return entries, out
def mine_believed():
out = []
for f in sorted((REPO / "phase-ends").glob("PhaseEnd_Phase*.md"), key=lambda p: [int(x) if x.isdigit() else x for x in re.split(r"(\d+)", p.stem)]):
text = f.read_text(encoding="utf-8", errors="replace").splitlines()
for i, ln in enumerate(text):
if ln.startswith("## What we believed"):
j = i + 1
while j < len(text) and not text[j].startswith("## "):
j += 1
out.append((f.name, i + 1, "\n".join(text[i:j]).strip()))
return out
def mine_deviations():
out = []
for f in sorted((REPO / "phase-ends").glob("PhaseEnd_Phase*.md"), key=lambda p: [int(x) if x.isdigit() else x for x in re.split(r"(\d+)", p.stem)]):
text = f.read_text(encoding="utf-8", errors="replace").splitlines()
for i, ln in enumerate(text):
if ln.startswith("## Deviations"):
rows = []
j = i + 1
while j < len(text) and not text[j].startswith("## "):
if text[j].startswith("|") and not text[j].startswith("|---") and "| Item |" not in text[j]:
rows.append((j + 1, text[j]))
j += 1
out.append((f.name, i + 1, rows))
return out
def main(argv):
ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
ap.add_argument("--out", default=str(REPO / ".run" / "P33" / "hindsight.md"))
a = ap.parse_args(argv)
entries, hind = mine_log()
believed = mine_believed()
devs = mine_deviations()
nrows = sum(len(r) for _, _, r in devs)
md = ["# Mined hindsight — the working set for docs/retrospective.md (generated by tools/mine_hindsight.py; scratch)", "",
f"decision-log: {entries} entries, {len(hind)} Hindsight bullets · believed-sections: {len(believed)} PhaseEnds · "
f"deviations: {nrows} rows across {len(devs)} PhaseEnds", "",
"## 1. Decision-log hindsight bullets (docs/decision-log.md:line — entry heading)", ""]
for head, hl, l, body in hind:
md.append(f"- **`docs/decision-log.md:{l}`** (entry `:{hl}` — {head[:110]}): {body}")
md += ["", "## 2. 'What we believed' sections", ""]
for fn, l, text in believed:
md += [f"### `phase-ends/{fn}:{l}`", "", text, ""]
md += ["## 3. Deviations tables (Item | Plan | Actual | Reason)", ""]
for fn, l, rows in devs:
md.append(f"### `phase-ends/{fn}:{l}` ({len(rows)} rows)")
for rl, row in rows:
md.append(f"- `:{rl}` {row}")
md.append("")
out = pathlib.Path(a.out); out.parent.mkdir(parents=True, exist_ok=True)
out.write_text("\n".join(md) + "\n", encoding="utf-8")
print(f"mine_hindsight: {entries} decision-log entries → {len(hind)} Hindsight bullets; {len(believed)} believed-sections; "
f"{nrows} deviation rows in {len(devs)} PhaseEnds -> {out}")
return 0
if __name__ == "__main__":
sys.exit(main(sys.argv[1:]))