#!/usr/bin/env python3 """Validate the generated Claw Code 2.0 board coverage and schema.""" from __future__ import annotations import argparse import json import re from pathlib import Path REQUIRED = { "id", "title", "source_anchor", "source_type", "release_bucket", "status", "dependencies", "verification_required", "deferral_rationale", } STATUSES = { "context", "active", "open", "done_verify", "stale_done", "superseded", "deferred_with_rationale", "rejected_not_claw", } def roadmap_heading_lines(path: Path) -> list[int]: lines = [] for line_no, line in enumerate(path.read_text(encoding="utf-8").splitlines(), 1): if re.match(r"^#{1,6}\s+", line): lines.append(line_no) return lines def main() -> int: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("--repo-root", type=Path, default=Path.cwd()) parser.add_argument("--board", type=Path, default=None) args = parser.parse_args() repo_root = args.repo_root.resolve() board_path = args.board or (repo_root / ".omx" / "cc2" / "board.json") board = json.loads(board_path.read_text(encoding="utf-8")) errors: list[str] = [] ids = set() for index, item in enumerate(board.get("items", []), 1): missing = REQUIRED - set(item) if missing: errors.append(f"item {index} missing required fields: {sorted(missing)}") if item.get("id") in ids: errors.append(f"duplicate id: {item.get('id')}") ids.add(item.get("id")) if item.get("status") not in STATUSES: errors.append(f"{item.get('id')} invalid status {item.get('status')}") if not isinstance(item.get("dependencies"), list): errors.append(f"{item.get('id')} dependencies must be list") expected = roadmap_heading_lines(repo_root / "ROADMAP.md") mapped = [item.get("source_line") for item in board.get("items", []) if item.get("source_type") == "roadmap_heading"] unmapped = sorted(set(expected) - set(mapped)) duplicates = sorted(line for line in set(mapped) if mapped.count(line) != 1) if unmapped: errors.append(f"unmapped ROADMAP headings: {unmapped}") if duplicates: errors.append(f"duplicate ROADMAP heading mappings: {duplicates}") coverage = board.get("coverage", {}) if coverage.get("roadmap_headings_total") != len(expected): errors.append("coverage roadmap_headings_total does not match ROADMAP.md") if coverage.get("roadmap_headings_mapped") != len(mapped): errors.append("coverage roadmap_headings_mapped does not match board items") if errors: print("FAIL cc2 board validation") for error in errors: print(f"- {error}") return 1 print("PASS cc2 board validation") print(f"- board: {board_path}") print(f"- items: {len(board.get('items', []))}") print(f"- ROADMAP headings mapped: {len(mapped)}/{len(expected)}") print(f"- ROADMAP actions mapped: {coverage.get('roadmap_actions_mapped')}/{coverage.get('roadmap_actions_total')}") return 0 if __name__ == "__main__": raise SystemExit(main())