mirror of
https://github.com/jkdansereau/goldeneye-pc-port.git
synced 2026-10-23 15:52:42 +02:00
* docs(M-102): fix findings-index false-positive, release <version> bug, land parked site cleanup
Docs triage pass (docs/dev/notes/PROMPT-docs-triage-session.md).
- gen_findings_index.py: D194's row was silently bucketed FIXED (an open
design item) because it had no distinct status cell, so the generator's
fallback keyword search matched "fixed" somewhere inside 6,500 chars of
narrative -- the same misleading-index failure mode that cost a session
on D227 (see docs/dev/findings.md SS H "D227 -- M-98"/"M-99"). Gave D194
a real status cell. Also extended LEAD_RE/bucket() with 5 verdict tokens
already in use in the doc (ANALYZED, GUARDED, DIAGNOSTIC SHIPPED,
ROOT-CAUSED, NOT A BUG) and added a leading verdict word to 4 more rows
that had a status cell but no recognized token -- fixes all 10 rows the
generator previously bucketed "?" (D175 D174 D173 D220 D160 D133 D136
D156 D153, plus D194). findings-index.csv regenerated; --check passes;
diff reviewed, only these 10 rows moved.
- ci.yml: release-notes.md's <version> placeholders were never substituted
before `gh release create --notes-file`, so every published release body
(including the live v0.1.0 release) ships the literal text. Now sed'd to
the tag first.
- Landed docs/dev/notes/UNLANDED-docs-site-cleanup-steps3-5.{md,patch}
(parked M-101, never pushed, branch deleted): deletes the orphaned
docs/StructureGuide.md (0 tracked references), adds title/description
frontmatter to 5 published Pages docs, rewrites docs/index.md's stale
"Phase 2 of 4 / no audio yet / 19-of-21" status text to match the
current README. The 2 files whose hunks no longer applied via `git am`
(docs/index.md, docs/dev/agentic-development.md) were redone by hand
against current main.
- docs/BACKLOG.md (local, gitignored): retargeted a dead
docs/PORT-LEARNINGS.md reference to docs/porting-notes.md.
Larger triage items (findings.md monster-row convention, full
status-accuracy sweep, duplicate-defect clustering, dev-notes plan
triage, HANDOFF rotation policy) scoped but not started -- see
docs/dev/notes/FOLLOWUP-docs-triage-session.md (gitignored).
Claude-Session: https://claude.ai/code/session_016TaKy6AAsVUTfjkhrX6CjG
* docs/index.md: fix stale in-level-music claim before merge
This PR predates the M-102c/M-105 correction (already landed in the
README via PR #55): in-level music is NOT absent, it plays with some
tracks having wrong-sounding instruments / occasional garbling (D77
fixed, D230 filed for the residual). This branch's own rewritten Status
paragraph still said music 'doesn't yet' play -- fixing it to match
before merge so main doesn't briefly regress to a stale claim this
session already corrected elsewhere.
171 lines
5.5 KiB
Python
171 lines
5.5 KiB
Python
#!/usr/bin/env python3
|
||
"""Generate docs/dev/findings-index.csv from the §F/§H tables in findings.md.
|
||
|
||
Agentic speed-ups plan Step 4 / R8: findings.md is ~200 KB. Agents should grep
|
||
this CSV (label, status, one-liner) to decide *which* Dxx entries to actually
|
||
read, instead of linear-reading the log.
|
||
|
||
Heuristic parser: every markdown table row whose first cell matches a Dxx
|
||
label (`D12`, `D88.4`, `D75 · D77`, `D24-D30`, ...). The topic cell is
|
||
truncated to a one-liner; the status cell is normalised to a short bucket
|
||
(RESOLVED / OPEN / FIXED / PARTIAL / PROPOSED / ?) with the raw text kept.
|
||
|
||
Usage:
|
||
python tools_pc/gen_findings_index.py # write the CSV
|
||
python tools_pc/gen_findings_index.py --check # exit 1 if stale
|
||
python tools_pc/gen_findings_index.py --stdout # print, don't write
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
import csv
|
||
import io
|
||
import re
|
||
import sys
|
||
from pathlib import Path
|
||
|
||
ROOT = Path(__file__).resolve().parent.parent
|
||
SRC = ROOT / "docs" / "dev" / "findings.md"
|
||
OUT = ROOT / "docs" / "dev" / "findings-index.csv"
|
||
|
||
LABEL_RE = re.compile(r"^D\d+(?:\.\d+)?(?:[-–]D\d+)?(?:\s*[·,]\s*D\d+(?:\.\d+)?)*\+?$")
|
||
|
||
STATUS_BUCKETS = [
|
||
("FIXED", re.compile(r"\bfixed\b", re.I)),
|
||
("RESOLVED", re.compile(r"\bresolved\b", re.I)),
|
||
("PARTIAL", re.compile(r"\bpartial|clamp landed|landed\b|safety-net", re.I)),
|
||
("PROPOSED", re.compile(r"\bproposed|not applied|design\b", re.I)),
|
||
("OPEN", re.compile(r"\bopen\b|not started|awaiting|owed|pending", re.I)),
|
||
]
|
||
|
||
|
||
def one_liner(cell: str, limit: int = 160) -> str:
|
||
"""Strip markdown emphasis/code and collapse to a single short line."""
|
||
txt = cell.replace("**", "").replace("`", "")
|
||
txt = re.sub(r"\[([^\]]+)\]\([^)]+\)", r"\1", txt) # [t](u) -> t
|
||
txt = re.sub(r"\s+", " ", txt).strip()
|
||
# first sentence-ish
|
||
m = re.match(r"(.{20,}?[.—:])\s", txt)
|
||
if m and len(m.group(1)) <= limit:
|
||
txt = m.group(1)
|
||
if len(txt) > limit:
|
||
txt = txt[: limit - 1].rstrip() + "…"
|
||
return txt
|
||
|
||
|
||
LEAD_RE = re.compile(
|
||
r"^\**\s*(OPEN|RESOLVED|FIXED|PROPOSED|PARTIAL|LANDED|CLAMP|DISPROVEN"
|
||
r"|ROOT-CAUSED|GUARDED|ANALYZED|DIAGNOSTIC SHIPPED|NOT A BUG)",
|
||
re.I,
|
||
)
|
||
|
||
|
||
def bucket(status_cell: str) -> str:
|
||
# An explicit leading verdict token wins over prose keywords deeper in the
|
||
# cell (e.g. "OPEN (parked). ... D168 fixed ..." is OPEN, not FIXED).
|
||
lead = LEAD_RE.match(status_cell.strip())
|
||
if lead:
|
||
tok = lead.group(1).upper()
|
||
return {
|
||
"LANDED": "PARTIAL",
|
||
"CLAMP": "PARTIAL",
|
||
"DISPROVEN": "RESOLVED",
|
||
"ROOT-CAUSED": "RESOLVED",
|
||
"GUARDED": "PARTIAL",
|
||
"ANALYZED": "PARTIAL",
|
||
"DIAGNOSTIC SHIPPED": "PARTIAL",
|
||
"NOT A BUG": "RESOLVED",
|
||
}.get(tok, tok)
|
||
for name, rx in STATUS_BUCKETS:
|
||
if rx.search(status_cell):
|
||
return name
|
||
return "?"
|
||
|
||
|
||
def split_row(line: str) -> list[str] | None:
|
||
line = line.strip()
|
||
if not line.startswith("|"):
|
||
return None
|
||
# split on '|' that is not inside backticks
|
||
cells, buf, tick = [], [], False
|
||
for ch in line:
|
||
if ch == "`":
|
||
tick = not tick
|
||
buf.append(ch)
|
||
elif ch == "|" and not tick:
|
||
cells.append("".join(buf))
|
||
buf = []
|
||
else:
|
||
buf.append(ch)
|
||
cells.append("".join(buf))
|
||
cells = [c.strip() for c in cells]
|
||
# leading/trailing empty from outer pipes
|
||
if cells and cells[0] == "":
|
||
cells = cells[1:]
|
||
if cells and cells[-1] == "":
|
||
cells = cells[:-1]
|
||
return cells or None
|
||
|
||
|
||
def rows():
|
||
section = None
|
||
for raw in SRC.read_text(encoding="utf-8").splitlines():
|
||
h = re.match(r"^#+\s+([A-Z])\.\s", raw) or re.match(r"^#+\s.*§?([FH])\b", raw)
|
||
if raw.startswith("### ") and re.search(r"\b([FH])\.", raw):
|
||
section = re.search(r"\b([FH])\.", raw).group(1)
|
||
cells = split_row(raw)
|
||
if not cells or len(cells) < 2:
|
||
continue
|
||
label = cells[0].replace("–", "-")
|
||
if not LABEL_RE.match(cells[0]):
|
||
continue
|
||
topic = cells[1]
|
||
status = cells[2] if len(cells) > 2 else ""
|
||
yield {
|
||
"label": label,
|
||
"bucket": bucket(status),
|
||
"one_liner": one_liner(topic),
|
||
"status_raw": one_liner(status, 200) if status else "",
|
||
}
|
||
|
||
|
||
def build_csv() -> str:
|
||
buf = io.StringIO()
|
||
w = csv.DictWriter(
|
||
buf, fieldnames=["label", "bucket", "one_liner", "status_raw"], lineterminator="\n"
|
||
)
|
||
w.writeheader()
|
||
seen = set()
|
||
for r in rows():
|
||
key = r["label"]
|
||
if key in seen:
|
||
continue
|
||
seen.add(key)
|
||
w.writerow(r)
|
||
return buf.getvalue()
|
||
|
||
|
||
def main() -> int:
|
||
args = set(sys.argv[1:])
|
||
text = (
|
||
"# GENERATED by tools_pc/gen_findings_index.py -- do not hand-edit.\n"
|
||
"# Grep this to pick which findings.md entries to read; it is not authoritative.\n"
|
||
+ build_csv()
|
||
)
|
||
if "--stdout" in args:
|
||
sys.stdout.write(text)
|
||
return 0
|
||
want = text.encode("utf-8")
|
||
if "--check" in args:
|
||
cur = OUT.read_bytes() if OUT.exists() else b""
|
||
if cur != want:
|
||
sys.stderr.write("findings-index.csv is stale: re-run gen_findings_index.py\n")
|
||
return 1
|
||
return 0
|
||
OUT.write_bytes(want)
|
||
print(f"wrote {OUT.relative_to(ROOT)} ({text.count(chr(10))} lines)")
|
||
return 0
|
||
|
||
|
||
if __name__ == "__main__":
|
||
raise SystemExit(main())
|