#!/usr/bin/env python3 """스킬과 검사기의 버전을 한 장으로 뽑는다. 통과 판정은 「어느 문서를 어느 검사기로 봤는가」에 묶여야 한다. 스킬은 `SKILL.md` 의 `metadata.version` 이 그 값이고, 스크립트 검사기는 버전 칸이 없으므로 파일 내용의 sha256 앞 12자를 쓴다. 버전을 올리는 것을 잊어도 sha 는 따라 움직인다. python3 scripts/skill-versions.py # 사람이 읽는 표 python3 scripts/skill-versions.py --json # 판정에 붙일 값 """ from __future__ import annotations import argparse import glob import hashlib import json import os import re ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) GATE_SCRIPTS = [ "scripts/verify-tech-log-tree.py", "scripts/verify-project-layout.py", "scripts/verify-pipeline-run.py", "scripts/verify-pipeline.py", "scripts/audit-records.py", "scripts/check-figure-text.py", "scripts/check-figure-overlap.py", "scripts/build-tech-log-tree.py", "scripts/studio-body.py", "scripts/capture-evidence.py", ] def _sha12(path: str) -> str | None: try: with open(path, "rb") as fh: return hashlib.sha256(fh.read()).hexdigest()[:12] except OSError: return None def collect() -> dict: skills = {} for path in sorted(glob.glob(os.path.join(ROOT, ".agents/skills/*/SKILL.md"))): name = os.path.basename(os.path.dirname(path)) text = open(path, encoding="utf-8").read() m = re.search(r"^metadata:\n(?: .*\n)*? version:\s*\"?([^\"\n]+)\"?", text, re.M) skills[name] = {"version": m.group(1).strip() if m else None, "sha12": _sha12(path)} checkers = {} for rel in GATE_SCRIPTS: checkers[rel] = {"version": None, "sha12": _sha12(os.path.join(ROOT, rel))} for path in sorted(glob.glob(os.path.join(ROOT, ".agents/skills/*/scripts/*.mjs"))): rel = os.path.relpath(path, ROOT) checkers[rel] = {"version": None, "sha12": _sha12(path)} return {"skills": skills, "checkers": checkers} def main() -> int: ap = argparse.ArgumentParser(description="스킬·검사기의 버전과 내용 해시를 뽑는다.") ap.add_argument("--json", action="store_true") args = ap.parse_args() data = collect() if args.json: print(json.dumps(data, ensure_ascii=False, indent=2)) return 0 missing = 0 print("스킬") for name, v in data["skills"].items(): mark = " " if v["version"] else "✗" if not v["version"]: missing += 1 print(f" {mark} {name:<40} {v['version'] or '버전 없음':<10} {v['sha12']}") print("\n검사기 — 버전 칸이 없어 내용 해시로 묶는다") for name, v in data["checkers"].items(): print(f" {name:<70} {v['sha12']}") print(f"\nSKILL VERSIONS: {'FAIL' if missing else 'PASS'} — " f"스킬 {len(data['skills'])} · 버전 없음 {missing} · 검사기 {len(data['checkers'])}") return 1 if missing else 0 if __name__ == "__main__": raise SystemExit(main())