#!/usr/bin/env python3 """ Generate a deterministic summary of spec/coverage/*.json (sections + rule ids). Outputs JSON and a short Markdown summary for status docs. """ from __future__ import annotations import argparse import json from datetime import datetime, timezone from pathlib import Path def load_coverage_files(coverage_dir: Path) -> list[dict]: files = sorted(coverage_dir.glob("*.json")) payloads = [] for path in files: payload = json.loads(path.read_text()) payloads.append({"path": path, "data": payload}) return payloads def summarize(payloads: list[dict]) -> dict: books = {} all_rule_ids: set[str] = set() for item in payloads: data = item["data"] book = data.get("book", "UNKNOWN") sections = data.get("sections", []) status_counts: dict[str, int] = {} rule_ids: set[str] = set() for section in sections: status = section.get("status", "unknown") status_counts[status] = status_counts.get(status, 0) + 1 for rid in section.get("rule_ids", []): rule_ids.add(rid) books[book] = { "sections": len(sections), "rules": len(rule_ids), "status_counts": dict(sorted(status_counts.items())), } all_rule_ids.update(rule_ids) return { "generated_utc": datetime.now(timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z"), "books": dict(sorted(books.items())), "total_rules": len(all_rule_ids), } def write_json(out_path: Path, summary: dict) -> None: out_path.write_text(json.dumps(summary, indent=2) + "\n") def write_md(out_path: Path, summary: dict) -> None: lines: list[str] = [] lines.append("# Coverage Summary") lines.append("") lines.append(f"Generated (UTC): {summary['generated_utc']}") lines.append("") lines.append("## By Book") lines.append("") for book, stats in summary["books"].items(): lines.append(f"- {book}: sections={stats['sections']}, rules={stats['rules']}, status_counts={stats['status_counts']}") lines.append("") lines.append(f"Total unique rule_ids: {summary['total_rules']}") lines.append("") out_path.write_text("\n".join(lines)) def main() -> int: parser = argparse.ArgumentParser(description="Summarize spec coverage files.") parser.add_argument("--coverage-dir", default="spec/coverage", help="Coverage directory (default: spec/coverage)") parser.add_argument("--out-json", default="out/coverage-summary.json", help="Output JSON path") parser.add_argument("--out-md", default="out/coverage-summary.md", help="Output Markdown path") args = parser.parse_args() coverage_dir = Path(args.coverage_dir) payloads = load_coverage_files(coverage_dir) summary = summarize(payloads) write_json(Path(args.out_json), summary) write_md(Path(args.out_md), summary) return 0 if __name__ == "__main__": raise SystemExit(main())