POSA_Copyrighter/scripts/update_diagram_manifest.py
changukyu 29534f441a docs: 심사 가이드 다이어그램 — archify workflow 5번째, 운영자 관점의 심사 절차
docs/operations/review-guide.md 의 6단계를 3레인(읽기·근거 확인 / 판정 / 기준 DB 에 남는 것)
workflow 로: 한 줄 요약 → 점수 구성 → 증거 열기 → 근거 표시 → 판정 → 다음 미심사.
분기는 미수행 하한(보류 후 재분석)과 판정 뒤 DB(주의 후보 자동 · 클리어 기준 등록 수동).

첫 안은 4레인·3항목 카드로 1440x900 에서 127px 넘쳐 레인 병합·제목 한 줄·카드 2항목으로
압축했고, 노드 폭 148 로 높이 예산(종횡비 1.52)을 맞췄다. validate 9/9 showcase,
visual-check 4개 뷰포트 containment pass, manifest 5종 갱신. rebuild 스크립트·manifest
갱신기 목록에 등록, PROJECT_MAP 표 추가. 문서·다이어그램만 변경.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BStHFrjQj2VypecSG9ex1V
2026-09-23 19:51:54 +09:00

94 lines
3.2 KiB
Python

"""docs/diagrams/manifest.json 을 산출물 실제 바이트에서 다시 쓴다.
수신증의 단일 작성자다. PowerShell 과 Python 이 각자 JSON 을 쓰면 들여쓰기·키 순서가
갈려 매니페스트가 왔다 갔다 하므로, rebuild-diagrams.ps1 도 이 스크립트를 부른다.
단독 실행도 된다: python scripts/update_diagram_manifest.py
"""
from __future__ import annotations
import hashlib
import io
import json
import re
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
DIAGRAM_DIR = ROOT / "docs" / "diagrams"
# (타입, 파일 basename). 다이어그램을 추가하면 여기와 tests/test_diagram_sync.py 의
# NODE_COLLECTION 을 함께 고친다.
DIAGRAMS = [
("architecture", "system-architecture"),
("dataflow", "evidence-dataflow"),
("workflow", "operator-review-workflow"),
("workflow", "review-guide"),
("lifecycle", "submission-lifecycle"),
]
NOTE = (
"archify deliver 는 바이트 결정적이다. 이 수신증은 tests/test_diagram_sync.py 가 "
"강제한다 - HTML 을 직접 고치거나 spec 만 고치고 재생성을 잊으면 커밋 게이트가 막는다. "
"갱신은 손으로 하지 말고 scripts/rebuild-diagrams.ps1 로 한다."
)
def _sha256(path: Path) -> str:
return hashlib.sha256(path.read_bytes()).hexdigest()
def _generator_stamp(html: Path) -> str:
match = re.search(
r'<meta name="generator" content="([^"]+)"', html.read_text(encoding="utf-8")
)
if not match:
raise SystemExit(f"{html.name} 에 generator meta 가 없다 - archify 산출물이 맞는가?")
return match.group(1)
def main() -> int:
entries = []
for diagram_type, base in DIAGRAMS:
spec = DIAGRAM_DIR / f"{base}.{diagram_type}.json"
html = DIAGRAM_DIR / f"{base}.html"
for path in (spec, html):
if not path.exists():
raise SystemExit(f"없는 파일: {path}")
entries.append(
{
"type": diagram_type,
"spec": spec.name,
"html": html.name,
"specSha256": _sha256(spec),
"htmlSha256": _sha256(html),
"htmlBytes": html.stat().st_size,
}
)
generators = {_generator_stamp(DIAGRAM_DIR / e["html"]) for e in entries}
if len(generators) != 1:
raise SystemExit(
f"산출물마다 generator 가 다르다: {sorted(generators)} - 전부 재생성해야 한다."
)
manifest = {
"note": NOTE,
"generator": generators.pop(),
"diagrams": entries,
}
target = DIAGRAM_DIR / "manifest.json"
with io.open(target, "w", encoding="utf-8", newline="\n") as handle:
json.dump(manifest, handle, ensure_ascii=False, indent=2)
handle.write("\n")
# 비ASCII 는 stdout 에 넣지 않는다 (콘솔이 cp949 라 UnicodeEncodeError 로 죽은 이력).
print("manifest updated: %d diagrams, generator %s" % (len(entries), manifest["generator"]))
for entry in entries:
print(" %-30s %s.. %d B" % (entry["html"], entry["htmlSha256"][:8], entry["htmlBytes"]))
return 0
if __name__ == "__main__":
sys.exit(main())