#!/usr/bin/env python3 from __future__ import annotations import json import re import sys from pathlib import Path ROOT = Path(__file__).resolve().parents[1] REQUIRED = [ ROOT / "SKILL.md", ROOT / "references" / "decision-policy.md", ROOT / "references" / "rule-catalog.md", ROOT / "references" / "output-modes.md", ROOT / "tests" / "cases.json", ROOT / "tests" / "evaluation-rubric.md", ] def fail(message: str) -> None: print(f"FAIL: {message}") raise SystemExit(1) def parse_frontmatter(text: str) -> dict[str, str]: match = re.match(r"^---\n(.*?)\n---\n", text, re.S) if not match: fail("SKILL.md must begin with YAML frontmatter") data: dict[str, str] = {} for line in match.group(1).splitlines(): if not line.strip() or line.lstrip().startswith("#"): continue if ":" not in line: fail(f"invalid frontmatter line: {line!r}") key, value = line.split(":", 1) data[key.strip()] = value.strip().strip('"').strip("'") return data def main() -> None: missing = [str(path.relative_to(ROOT)) for path in REQUIRED if not path.exists()] if missing: fail("missing required files: " + ", ".join(missing)) skill_text = (ROOT / "SKILL.md").read_text(encoding="utf-8") frontmatter = parse_frontmatter(skill_text) name = frontmatter.get("name", "") description = frontmatter.get("description", "") if name != ROOT.name: fail(f"frontmatter name {name!r} must match directory {ROOT.name!r}") if not re.fullmatch(r"[A-Za-z0-9-]+", name): fail("name must contain only letters, numbers, and hyphens") if not description.startswith("Use when "): fail("description must start with 'Use when '") if len((name + description).encode("utf-8")) > 1024: fail("name + description frontmatter exceeds 1024 bytes") if "cite" in skill_text or "turn" in frontmatter.get("description", ""): fail("runtime-specific citation markers must not appear in SKILL.md") cases = json.loads((ROOT / "tests" / "cases.json").read_text(encoding="utf-8")) if not isinstance(cases, list) or not cases: fail("tests/cases.json must be a non-empty array") ids: set[str] = set() allowed_actions = {"correct", "keep", "suggest", "review"} required_keys = {"id", "category", "input", "expected_text", "expected_action", "rule_id", "explanation"} for index, case in enumerate(cases): missing_keys = required_keys - set(case) if missing_keys: fail(f"case #{index} missing keys: {sorted(missing_keys)}") if case["id"] in ids: fail(f"duplicate case id: {case['id']}") ids.add(case["id"]) if case["expected_action"] not in allowed_actions: fail(f"invalid expected_action in {case['id']}: {case['expected_action']}") print(f"PASS: package structure valid; {len(cases)} test cases loaded") if __name__ == "__main__": main()