|
| 1 | +#!/usr/bin/env python3 |
| 2 | +"""Every showcase piece and asset-sheet reference has a SELECT row (#367). |
| 3 | +
|
| 4 | +scripts/asset_sheet.py renders a candidate's hero asset by the regexes in its |
| 5 | +SELECT table and refuses any name without a row, so a piece added without one |
| 6 | +leaves its committed asset sheet impossible to regenerate. butter-churn shipped |
| 7 | +that way. This reads SELECT with ast (no Pillow needed) and requires a row for: |
| 8 | +
|
| 9 | +- every piece in showcase/gallery.json, and |
| 10 | +- every name in the CLAUDE.md asset-quality reference set (read the same way |
| 11 | + asset_sheet.reference_set() reads it). |
| 12 | +
|
| 13 | + python tests/check_asset_sheet_select.py (exit 0 ok, 1 on a gap) |
| 14 | +""" |
| 15 | +from __future__ import annotations |
| 16 | + |
| 17 | +import ast |
| 18 | +import json |
| 19 | +import re |
| 20 | +import sys |
| 21 | +from pathlib import Path |
| 22 | + |
| 23 | +ROOT = Path(__file__).resolve().parent.parent |
| 24 | + |
| 25 | + |
| 26 | +def select_keys(root: Path) -> set[str]: |
| 27 | + tree = ast.parse((root / "scripts" / "asset_sheet.py").read_text(encoding="utf-8")) |
| 28 | + for node in tree.body: |
| 29 | + if isinstance(node, ast.Assign) and any( |
| 30 | + isinstance(t, ast.Name) and t.id == "SELECT" for t in node.targets): |
| 31 | + return {k.value for k in node.value.keys if isinstance(k, ast.Constant)} |
| 32 | + raise SystemExit("scripts/asset_sheet.py has no SELECT table") |
| 33 | + |
| 34 | + |
| 35 | +def reference_set(root: Path) -> list[str]: |
| 36 | + src = (root / "scripts" / "asset_sheet.py").read_text(encoding="utf-8") |
| 37 | + pattern = re.search(r'_REF_LINE = re\.compile\(r"(.+?)", re\.S\)', src).group(1) |
| 38 | + m = re.search(pattern, (root / "CLAUDE.md").read_text(encoding="utf-8"), re.S) |
| 39 | + return re.findall(r"`([a-z0-9-]+)`", m.group(1)) if m else [] |
| 40 | + |
| 41 | + |
| 42 | +def check(root: Path = ROOT) -> list[str]: |
| 43 | + keys = select_keys(root) |
| 44 | + pieces = [p["name"] for p in json.loads( |
| 45 | + (root / "showcase" / "gallery.json").read_text(encoding="utf-8"))["pieces"]] |
| 46 | + refs = reference_set(root) |
| 47 | + errors = [f"showcase piece {n!r} has no SELECT row in scripts/asset_sheet.py" |
| 48 | + for n in pieces if n not in keys] |
| 49 | + errors += [f"asset-sheet reference {n!r} (CLAUDE.md) has no SELECT row" |
| 50 | + for n in refs if n not in keys] |
| 51 | + if not refs: |
| 52 | + errors.append("could not read the asset-sheet reference set from CLAUDE.md") |
| 53 | + return errors |
| 54 | + |
| 55 | + |
| 56 | +def main() -> int: |
| 57 | + errors = check() |
| 58 | + for e in errors: |
| 59 | + print(f"::error::{e}", file=sys.stderr) |
| 60 | + if not errors: |
| 61 | + print("asset_sheet.py SELECT covers every showcase piece and reference") |
| 62 | + return 1 if errors else 0 |
| 63 | + |
| 64 | + |
| 65 | +if __name__ == "__main__": |
| 66 | + sys.exit(main()) |
0 commit comments