35 lines
1.1 KiB
Python
35 lines
1.1 KiB
Python
|
|
#!/usr/bin/env python3
|
||
|
|
"""AM-1 / M-D1-COV: every numbered GR-rule needs >=1 scenario.
|
||
|
|
|
||
|
|
Compares the rule IDs declared in specs/GroundRules.md against the
|
||
|
|
`covers:` lists in scenarios/ground/*.yaml. Exits non-zero when a
|
||
|
|
scenario claims a rule the spec does not define, so coverage can never
|
||
|
|
be inflated by a typo'd or invented rule ID.
|
||
|
|
"""
|
||
|
|
import glob
|
||
|
|
import re
|
||
|
|
import sys
|
||
|
|
|
||
|
|
spec = open("specs/GroundRules.md").read()
|
||
|
|
rules = sorted(set(re.findall(r"\*\*(GR-[A-Z]+\d+)", spec)))
|
||
|
|
|
||
|
|
covered = set()
|
||
|
|
for path in sorted(glob.glob("scenarios/ground/*.yaml")):
|
||
|
|
match = re.search(r"covers: \[(.*?)\]", open(path).read(), re.S)
|
||
|
|
if match:
|
||
|
|
covered |= {c.strip() for c in match.group(1).split(",") if c.strip()}
|
||
|
|
|
||
|
|
known = set(rules)
|
||
|
|
hit = sorted(known & covered)
|
||
|
|
missing = [r for r in rules if r not in covered]
|
||
|
|
invented = sorted(covered - known)
|
||
|
|
|
||
|
|
pct = 100 * len(hit) // len(rules) if rules else 0
|
||
|
|
print(f"AM-1 rule coverage: {len(hit)}/{len(rules)} ({pct}%)")
|
||
|
|
if missing:
|
||
|
|
print(" uncovered:", " ".join(missing))
|
||
|
|
if invented:
|
||
|
|
print(" ERROR — claimed but not defined in the spec:", " ".join(invented))
|
||
|
|
sys.exit(1)
|
||
|
|
sys.exit(0 if not missing else 2)
|