Files
scadaproj/code-reviews/regen-readme.py
T
Joseph Doherty 5f75cd4dab Add per-library code-review scaffolding for the ZB.MOM.WW.* shared libs
Adapts the code-reviews convention (process, README generator, template) from
the ScadaBridge app model (per-src/-module, Akka conventions) to scadaproj's
reality: six shared libraries reviewed against their components/ specs.

- REVIEW-PROCESS.md: review unit is a library; library->component-spec mapping;
  checklist re-targeted for reusable .NET libs (public API/semver, packaging &
  dependency hygiene, spec/shared-contract adherence) instead of actor/supervision.
- _template/findings.md: library/packages/component-spec/shared-contract header.
- regen-readme.py: per-library prose, data-driven Summary, '-' for unreviewed.
- Seed Auth/Theme/Health/Telemetry/Configuration/Audit findings stubs (0 findings).
- README.md generated; --check passes.
2026-06-01 10:46:16 -04:00

199 lines
8.4 KiB
Python
Executable File

#!/usr/bin/env python3
"""Regenerate code-reviews/README.md from the per-library findings.md files.
The findings files are the source of truth; README.md is a generated index.
Run this after recording, 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"}
NOT_REVIEWED = "" # rendered for libraries with no recorded review date/commit yet
def discover_libraries():
"""Library folders are every subdirectory of code-reviews/ holding a findings.md,
excluding the _template folder. Returned sorted for a stable README order."""
libraries = []
for name in sorted(os.listdir(BASE)):
if name.startswith(("_", ".")):
continue
if os.path.isfile(os.path.join(BASE, name, "findings.md")):
libraries.append(name)
return libraries
def parse_header(library, text):
"""Extract (last_reviewed, commit) from the library's header table.
Returns None for either field when it is absent or still templated, so the
README can render a 'not yet reviewed' dash instead of a fake baseline."""
last = re.search(r"\|\s*Last reviewed\s*\|\s*([0-9]{4}-[0-9]{2}-[0-9]{2})", text)
commit = re.search(r"\|\s*Commit reviewed\s*\|\s*`([^`<]+)`", text)
return (
last.group(1) if last else None,
commit.group(1) if commit else None,
)
def parse_findings(library):
"""Parse one library's findings.md into ((last_reviewed, commit), [(library, id, severity, title, status), ...])."""
text = open(os.path.join(BASE, library, "findings.md")).read()
header = parse_header(library, text)
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"{library}/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"{library}/findings.md: {fid} is missing a Severity or Status field")
findings.append((library, fid, sev.group(1), title, status.group(1).strip()))
return header, findings
def finding_number(finding):
return int(re.search(r"-(\d+)$", finding[1]).group(1))
def build_readme(libraries, per_library):
pending = sorted(
(f for fs in per_library.values() for f in fs[1] if f[4] in PENDING_STATUSES),
key=lambda f: (SEVERITY_ORDER.get(f[2], 9), f[0], finding_number(f)),
)
reviewed = sum(1 for lib in libraries if per_library[lib][0][0] is not None)
def severity_total(sev):
return sum(1 for f in pending if f[2] == sev)
def open_count(library, sev):
return sum(1 for f in per_library[library][1]
if f[2] == sev and f[4] in PENDING_STATUSES)
lines = []
add = lines.append
add("# Code Reviews")
add("")
add("Comprehensive, per-library code reviews of the `ZB.MOM.WW.*` shared libraries hosted")
add("in this repo. Each library (one self-contained `.slnx` at the repo root) has its own")
add("folder containing a `findings.md`. This README is the aggregated index — the single")
add("place to see all outstanding work.")
add("")
add("> Generated by `regen-readme.py` from the per-library `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 library at a time against a fixed checklist.")
add("- Each library is reviewed against its normalized component spec under `components/`.")
add("- Every finding is recorded in the library'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(" libraries.")
add("")
add("See **[REVIEW-PROCESS.md](REVIEW-PROCESS.md)** for the full procedure: the review")
add("checklist, severity definitions, finding format, the library → component-spec mapping,")
add("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 library review")
add("└── <Library>/findings.md # one folder per ZB.MOM.WW.* shared library")
add("```")
add("")
add("## Summary")
add("")
add(f"{reviewed} of {len(libraries)} libraries reviewed. "
f"{len(pending)} pending finding{'s' if len(pending) != 1 else ''} across all libraries.")
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("## Library Status")
add("")
add("| Library | Last reviewed | Commit | Open (C/H/M/L) | Open | Total |")
add("|---------|---------------|--------|----------------|------|-------|")
for library in libraries:
counts = [open_count(library, s) for s in ("Critical", "High", "Medium", "Low")]
last_reviewed, commit = per_library[library][0]
last_cell = last_reviewed if last_reviewed else NOT_REVIEWED
commit_cell = f"`{commit}`" if commit else NOT_REVIEWED
add(f"| [{library}]({library}/findings.md) | {last_cell} | {commit_cell} "
f"| {counts[0]}/{counts[1]}/{counts[2]}/{counts[3]} "
f"| {sum(counts)} | {len(per_library[library][1])} |")
add("")
add("## Pending Findings")
add("")
add("Every `Open` / `In Progress` finding across all libraries, highest severity first.")
add("Resolved findings drop off this list but remain recorded in their library's")
add("`findings.md` (see [REVIEW-PROCESS.md](REVIEW-PROCESS.md) §4–§5). Full detail —")
add("description, location, recommendation — lives in the library'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 | Library | Title |")
add("|----|---------|-------|")
for library, fid, _, title, _ in rows:
add(f"| {fid} | [{library}]({library}/findings.md) | {title} |")
add("")
return "\n".join(lines)
def main():
check = "--check" in sys.argv[1:]
libraries = discover_libraries()
per_library = {m: parse_findings(m) for m in libraries}
content = build_readme(libraries, per_library)
readme_path = os.path.join(BASE, "README.md")
pending = sum(1 for fs in per_library.values()
for f in fs[1] if f[4] in PENDING_STATUSES)
total = sum(len(fs[1]) for fs in per_library.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(libraries)} libraries.")
if __name__ == "__main__":
main()