142 lines
4.8 KiB
Python
142 lines
4.8 KiB
Python
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())
|