Approve Shu infantry handpainted sprite v8
This commit is contained in:
141
scripts/check-unit-sprite-approval-gate.py
Normal file
141
scripts/check-unit-sprite-approval-gate.py
Normal file
@@ -0,0 +1,141 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
DOCS_DIR = ROOT / "docs"
|
||||
MANIFEST_PATH = DOCS_DIR / "unit-sprite-rework-manifest.json"
|
||||
AUDIT_PATH = DOCS_DIR / "unit-sprite-rework-audit.json"
|
||||
|
||||
|
||||
def load_json(path: Path) -> dict:
|
||||
if not path.exists():
|
||||
raise FileNotFoundError(f"missing required file: {path.relative_to(ROOT)}")
|
||||
return json.loads(path.read_text(encoding="utf-8"))
|
||||
|
||||
|
||||
def sorted_unique(values: list[str]) -> list[str]:
|
||||
return sorted(dict.fromkeys(values))
|
||||
|
||||
|
||||
def row_by_stem(audit: dict) -> dict[str, dict]:
|
||||
return {row["stem"]: row for row in audit.get("rows", [])}
|
||||
|
||||
|
||||
def validate_manifest(manifest: dict, audit: dict) -> list[str]:
|
||||
issues: list[str] = []
|
||||
rows = row_by_stem(audit)
|
||||
approved = sorted_unique(manifest.get("approvedBaseline", []))
|
||||
pending = sorted_unique(manifest.get("pendingApproval", []))
|
||||
next_after = sorted_unique(manifest.get("nextAfterApproval", []))
|
||||
overlap = sorted(set(approved) & set(pending))
|
||||
|
||||
if overlap:
|
||||
issues.append(f"units cannot be both approved and pending: {', '.join(overlap)}")
|
||||
|
||||
for stem in approved + pending + next_after:
|
||||
if stem not in rows:
|
||||
issues.append(f"manifest references unknown unit sheet: {stem}")
|
||||
|
||||
for stem in approved:
|
||||
row = rows.get(stem)
|
||||
if not row:
|
||||
continue
|
||||
if row["base"]["status"] != "ok" or row["actions"]["status"] != "ok":
|
||||
issues.append(f"approved unit has technical issue: {stem}")
|
||||
if row["review_stage"] != "approved-baseline":
|
||||
issues.append(f"approved unit has wrong review stage in audit: {stem} -> {row['review_stage']}")
|
||||
|
||||
for stem in pending:
|
||||
row = rows.get(stem)
|
||||
if not row:
|
||||
continue
|
||||
if row["base"]["status"] != "ok" or row["actions"]["status"] != "ok":
|
||||
issues.append(f"pending sample has technical issue: {stem}")
|
||||
if row["review_stage"] != "pending-approval":
|
||||
issues.append(f"pending sample has wrong review stage in audit: {stem} -> {row['review_stage']}")
|
||||
|
||||
return issues
|
||||
|
||||
|
||||
def release_blockers(manifest: dict, audit: dict) -> list[str]:
|
||||
blockers: list[str] = []
|
||||
pending = sorted_unique(manifest.get("pendingApproval", []))
|
||||
if pending and manifest.get("approvalPolicy", {}).get("holdCommitPushDeployUntilBatchApproved", True):
|
||||
blockers.append(f"pending approval units block commit/push/deploy: {', '.join(pending)}")
|
||||
|
||||
stage_counts = audit.get("stage_counts", {})
|
||||
if stage_counts.get("pending-approval", 0) != len(pending):
|
||||
blockers.append(
|
||||
"audit pending-approval count does not match manifest "
|
||||
f"({stage_counts.get('pending-approval', 0)} != {len(pending)})"
|
||||
)
|
||||
|
||||
return blockers
|
||||
|
||||
|
||||
def sample_summary(manifest: dict, audit: dict) -> list[str]:
|
||||
rows = row_by_stem(audit)
|
||||
pending = sorted_unique(manifest.get("pendingApproval", []))
|
||||
lines = [
|
||||
"Unit sprite sample gate: OK",
|
||||
f"approved baseline: {len(manifest.get('approvedBaseline', []))}",
|
||||
f"pending approval: {len(pending)}",
|
||||
f"queued rework: {audit.get('stage_counts', {}).get('queued-rework', 0)}",
|
||||
]
|
||||
for stem in pending:
|
||||
row = rows.get(stem)
|
||||
if row:
|
||||
base = row["base"]
|
||||
lines.append(
|
||||
f"{stem}: {row['review_stage']}, base={base['status']}, "
|
||||
f"battle={base.get('battle_min_width_px')}..{base.get('battle_max_width_px')} x "
|
||||
f"{base.get('battle_min_height_px')}..{base.get('battle_max_height_px')}"
|
||||
)
|
||||
return lines
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description="Check sprite rework approval gates.")
|
||||
parser.add_argument(
|
||||
"--mode",
|
||||
choices=("sample", "release"),
|
||||
default="sample",
|
||||
help="sample allows pending approval; release fails while pending approval remains.",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
try:
|
||||
manifest = load_json(MANIFEST_PATH)
|
||||
audit = load_json(AUDIT_PATH)
|
||||
except Exception as exc:
|
||||
print(f"Unit sprite approval gate: FAILED\n- {exc}")
|
||||
return 1
|
||||
|
||||
issues = validate_manifest(manifest, audit)
|
||||
if issues:
|
||||
print("Unit sprite approval gate: FAILED")
|
||||
for issue in issues:
|
||||
print(f"- {issue}")
|
||||
return 1
|
||||
|
||||
if args.mode == "release":
|
||||
blockers = release_blockers(manifest, audit)
|
||||
if blockers:
|
||||
print("Unit sprite release gate: BLOCKED")
|
||||
for blocker in blockers:
|
||||
print(f"- {blocker}")
|
||||
return 2
|
||||
print("Unit sprite release gate: OK")
|
||||
return 0
|
||||
|
||||
print("\n".join(sample_summary(manifest, audit)))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user