neon-sprawl/scripts/check_decomposition_languag...

76 lines
2.4 KiB
Python

#!/usr/bin/env python3
"""Fail if decomposition docs regress hybrid vocabulary (gig vs non-combat skill).
Catches phrases like "one combat skill" or bare "combat skill" that imply a combat
SkillDef. Allows "non-combat skill(s)" and "anti-combat …" when the hyphen prefix
is immediately before ``combat``.
Run in CI (PR gate) and locally after editing ``docs/decomposition/**``.
"""
from __future__ import annotations
import re
import sys
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parent.parent
DECOMP_ROOT = REPO_ROOT / "docs/decomposition"
# Standalone phrases that almost always indicate a doc bug vs game-design hybrid model.
LITERAL_BANS = [
"one combat skill",
"one combat skills",
]
# Match "combat skill" / "combat skills" unless prefixed by "non-" or "anti-" (hyphenated).
COMBAT_SKILL = re.compile(r"\bcombat skills?\b", re.IGNORECASE)
def line_has_bad_combat_skill(line: str) -> bool:
for m in COMBAT_SKILL.finditer(line):
start = m.start()
if start >= 4 and line[start - 4 : start].lower() == "non-":
continue
if start >= 5 and line[start - 5 : start].lower() == "anti-":
continue
return True
return False
def check_file(path: Path) -> list[str]:
errors: list[str] = []
text = path.read_text(encoding="utf-8")
lower = text.lower()
for banned in LITERAL_BANS:
if banned in lower:
errors.append(f"{path.relative_to(REPO_ROOT)}: contains forbidden phrase {banned!r}")
for i, line in enumerate(text.splitlines(), start=1):
if line_has_bad_combat_skill(line):
errors.append(f"{path.relative_to(REPO_ROOT)}:{i}: suspicious 'combat skill' (use gig or 'non-combat skill'): {line.strip()[:200]}")
return errors
def main() -> int:
if not DECOMP_ROOT.is_dir():
print(f"error: missing {DECOMP_ROOT}", file=sys.stderr)
return 1
all_errors: list[str] = []
for path in sorted(DECOMP_ROOT.rglob("*.md")):
all_errors.extend(check_file(path))
if all_errors:
for msg in all_errors:
print(msg, file=sys.stderr)
print(f"decomposition language check failed ({len(all_errors)} issue(s))", file=sys.stderr)
return 1
print("decomposition language OK: hybrid vocabulary (no stray combat SkillDef phrasing)")
return 0
if __name__ == "__main__":
raise SystemExit(main())