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
94 lines
3.2 KiB
Python
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())
|