mirror of
https://github.com/instructkr/claude-code.git
synced 2026-05-14 09:56:44 +00:00
88 lines
3.1 KiB
Python
Executable File
88 lines
3.1 KiB
Python
Executable File
#!/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())
|