#!/usr/bin/env python3 """Regenerate code-reviews/README.md from the per-module findings.md files. The findings files are the source of truth; README.md is a generated index. Run this after resolving or re-triaging a finding so the aggregated tables stay in sync (see REVIEW-PROCESS.md section 5). Usage: python3 regen-readme.py # rewrite README.md python3 regen-readme.py --check # exit 1 if README.md is stale (for CI) Works from any directory — paths are resolved relative to this script. """ import os import re import sys BASE = os.path.dirname(os.path.abspath(__file__)) SEVERITY_ORDER = {"Critical": 0, "High": 1, "Medium": 2, "Low": 3} PENDING_STATUSES = {"Open", "In Progress"} def discover_modules(): """Module folders are every subdirectory of code-reviews/ holding a findings.md, excluding the _template folder. Returned sorted for a stable README order.""" modules = [] for name in sorted(os.listdir(BASE)): if name.startswith(("_", ".")): continue if os.path.isfile(os.path.join(BASE, name, "findings.md")): modules.append(name) return modules def parse_findings(module): """Parse one module's findings.md into (module, id, severity, title, status) tuples.""" text = open(os.path.join(BASE, module, "findings.md")).read() findings = [] for block in re.split(r"^### ", text, flags=re.M)[1:]: head = block.splitlines()[0].strip() m = re.match(r"([A-Za-z][A-Za-z0-9]*-\d+)\b(.*)", head) if not m: raise SystemExit(f"{module}/findings.md: unparseable finding heading: {head!r}") fid = m.group(1).strip() title = m.group(2).strip().lstrip("—–-").strip().replace("|", "\\|") sev = re.search(r"\|\s*Severity\s*\|\s*([A-Za-z]+)", block) status = re.search(r"\|\s*Status\s*\|\s*([A-Za-z ]+?)\s*\|", block) if not sev or not status: raise SystemExit(f"{module}/findings.md: {fid} is missing a Severity or Status field") findings.append((module, fid, sev.group(1), title, status.group(1).strip())) return findings def finding_number(finding): return int(re.search(r"-(\d+)$", finding[1]).group(1)) def build_readme(modules, per_module): pending = sorted( (f for fs in per_module.values() for f in fs if f[4] in PENDING_STATUSES), key=lambda f: (SEVERITY_ORDER.get(f[2], 9), f[0], finding_number(f)), ) def severity_total(sev): return sum(1 for f in pending if f[2] == sev) def open_count(module, sev): return sum(1 for f in per_module[module] if f[2] == sev and f[4] in PENDING_STATUSES) lines = [] add = lines.append add("# Code Reviews") add("") add("Comprehensive, per-module code reviews of the ScadaLink codebase. Each module (one") add("buildable project under `src/`) has its own folder containing a `findings.md`. This") add("README is the aggregated index — the single place to see all outstanding work.") add("") add("> Generated by `regen-readme.py` from the per-module `findings.md` files. Do not") add("> edit by hand — edit the findings files and re-run the script.") add("") add("## How it works") add("") add("- Reviews are performed one module at a time against a fixed checklist.") add("- Every finding is recorded in the module's `findings.md` with a severity and status.") add("- Findings are **never deleted** — they are closed by changing their status, keeping") add(" a full audit trail.") add("- This README aggregates every **pending** finding (`Open` / `In Progress`) across all") add(" modules.") add("") add("See **[REVIEW-PROCESS.md](REVIEW-PROCESS.md)** for the full procedure: the review") add("checklist, severity definitions, finding format, and how to mark items resolved.") add("") add("## Layout") add("") add("```") add("code-reviews/") add("├── README.md # this file — process overview + pending findings") add("├── REVIEW-PROCESS.md # how to perform a review and track findings") add("├── regen-readme.py # regenerates this README from the findings files") add("├── _template/findings.md # copy-this template for a module review") add("└── /findings.md # one folder per src/ project") add("```") add("") add("## Baseline review — 2026-05-16") add("") add("All 19 modules were reviewed at commit `9c60592` (241 findings: 6 Critical, 46 High,") add("100 Medium, 89 Low). The tables below track what remains **open** as findings are") add("resolved and re-triaged; findings discovered after the baseline are appended to their") add("module file and counted in **Total**.") add("") add("| Severity | Open findings |") add("|----------|---------------|") for sev in ("Critical", "High", "Medium", "Low"): add(f"| {sev} | {severity_total(sev)} |") add(f"| **Total** | **{len(pending)}** |") add("") add("## Module Status") add("") add("| Module | Last reviewed | Commit | Open (C/H/M/L) | Open | Total |") add("|--------|---------------|--------|----------------|------|-------|") for module in modules: counts = [open_count(module, s) for s in ("Critical", "High", "Medium", "Low")] add(f"| [{module}]({module}/findings.md) | 2026-05-16 | `9c60592` " f"| {counts[0]}/{counts[1]}/{counts[2]}/{counts[3]} " f"| {sum(counts)} | {len(per_module[module])} |") add("") add("## Pending Findings") add("") add("Every `Open` / `In Progress` finding across all modules, highest severity first.") add("Resolved findings drop off this list but remain recorded in their module's") add("`findings.md` (see [REVIEW-PROCESS.md](REVIEW-PROCESS.md) §4–§5). Full detail —") add("description, location, recommendation — lives in the module's `findings.md`.") add("") for sev in ("Critical", "High", "Medium", "Low"): rows = [f for f in pending if f[2] == sev] add(f"### {sev} ({len(rows)})") add("") if not rows: add("_None open._") add("") continue add("| ID | Module | Title |") add("|----|--------|-------|") for module, fid, _, title, _ in rows: add(f"| {fid} | [{module}]({module}/findings.md) | {title} |") add("") return "\n".join(lines) def main(): check = "--check" in sys.argv[1:] modules = discover_modules() per_module = {m: parse_findings(m) for m in modules} content = build_readme(modules, per_module) readme_path = os.path.join(BASE, "README.md") pending = sum(1 for fs in per_module.values() for f in fs if f[4] in PENDING_STATUSES) total = sum(len(fs) for fs in per_module.values()) if check: current = open(readme_path).read() if os.path.exists(readme_path) else "" if current != content: print("README.md is stale — run: python3 code-reviews/regen-readme.py") sys.exit(1) print(f"README.md is up to date ({pending} pending / {total} total).") return open(readme_path, "w").write(content) print(f"README.md regenerated — {pending} pending, {total} total findings " f"across {len(modules)} modules.") if __name__ == "__main__": main()