refactor: 문서 개선 중

This commit is contained in:
donghyeon-ka
2026-09-21 14:30:55 +09:00
parent c93cdea150
commit 805a18f486
1497 changed files with 525837 additions and 59152 deletions
+33 -11
View File
@@ -55,7 +55,7 @@ REQUIRED_FIELDS = {
}
# 글을 써도 되는 readiness. 나머지는 글감으로만 남는다
GENERATABLE = {"case": {"READY"}, "concept": {"READY"}, "reference": {"READY"},
"question": {"OPEN"}, "decision": {"READY"}, "setup": {"READY"}}
"question": {"OPEN", "RESOLVED"}, "decision": {"READY"}, "setup": {"READY"}}
# 표를 손으로 채우다 종류를 빠뜨리면 그 종류의 글감은 **아무 칸도 요구받지 않는다** —
# 조용히 통과한다. 빠진 것이 있으면 import 할 때 걸리게 둔다
@@ -128,6 +128,16 @@ def _headings(path: str) -> list[tuple[int, str, str]]:
return out
def _numbered_heading_anchors(heads: list[tuple[int, str, str]]) -> set[str]:
"""제목 앞의 절 번호를 section marker 앵커 집합으로 만든다."""
out: set[str] = set()
for _, title, _ in heads:
match = re.match(r"^((?:\d+|[A-Za-z])(?:\.\d+)*)\b", title)
if match:
out.add(f"§{match.group(1)}")
return out
def _anchor_base(anchor: str, slugs: list[str]) -> str | None:
"""앵커가 어느 절 슬러그로 시작하는가. 가장 긴 것을 고른다.
@@ -318,8 +328,8 @@ def verify(project: str) -> Report:
readiness = str(node.get("readiness") or "").upper()
if readiness and readiness not in READINESS:
rep.error("readiness 값이 계약에 없다", f"{where}{readiness}")
if kind == "question" and readiness and readiness != "OPEN":
rep.error("OPEN QUESTION readiness 는 OPEN ", f"{where}{readiness}")
if kind == "question" and readiness and readiness not in {"OPEN", "RESOLVED"}:
rep.error("QUESTION readiness 는 OPEN 또는 RESOLVED ", f"{where}{readiness}")
if kind == "decision":
status = str(node.get("decision-status") or "").strip("`").upper()
if status and status not in DECISION_STATUS:
@@ -414,18 +424,21 @@ def verify(project: str) -> Report:
# 가리키지 않는 앵커가 그대로 통과했다
heads = _headings(ssot_path) if os.path.exists(ssot_path) else []
head_slugs = [h[2] for h in heads]
numbered_anchors = _numbered_heading_anchors(heads)
if heads and has_contract:
# 앵커 형식은 프로젝트마다 다르다 — 절 제목 슬러그를 쓰는 곳도 있고
# `§1.1`·`10-2`·`a18` 처럼 번호나 마커를 쓰는 곳도 있다. 형식을 강요하지 않고,
# 슬러그를 쓰는 프로젝트에서만 실재를 대조한다
# 앵커 형식은 프로젝트마다 다르다 — 절 제목 슬러그와 §1.1 같은 번호 형식은
# 둘 다 실재 여부를 대조한다. 그 밖의 사용자 정의 marker만 검증 불가 경고로 남긴다.
seen_anchors: list[tuple[str, str]] = []
for where, refs in _all_anchors(index):
for ref in refs:
if "#" in ref:
seen_anchors.append((where, ref.split("#", 1)[1]))
anchor = ref.split("#", 1)[1].split()[0].strip("`")
seen_anchors.append((where, anchor))
resolved = [(w, a, _anchor_base(a, head_slugs)) for w, a in seen_anchors]
hit = sum(1 for _, _, b in resolved if b)
slug_style = seen_anchors and hit * 2 >= len(seen_anchors)
slug_hits = sum(1 for _, _, base in resolved if base)
number_hits = sum(1 for _, anchor in seen_anchors if anchor in numbered_anchors)
slug_style = bool(seen_anchors) and slug_hits * 2 >= len(seen_anchors)
number_style = bool(seen_anchors) and number_hits * 2 >= len(seen_anchors)
pointed: list[tuple[str, str]] = []
if slug_style:
@@ -435,9 +448,18 @@ def verify(project: str) -> Report:
f"{where} — #{anchor_slug}")
else:
pointed.append((base_slug, anchor_slug[len(base_slug):].lstrip("-")))
elif number_style:
for where, anchor in seen_anchors:
if anchor.startswith("§") and anchor not in numbered_anchors:
rep.error("SSOT 에 없는 번호 절을 가리키는 앵커",
f"{where} — #{anchor}")
for section in scope.get("sections") or []:
if str(section).startswith("§") and str(section) not in numbered_anchors:
rep.error("candidateScope 가 SSOT 에 없는 번호 절을 가리킨다",
str(section))
elif seen_anchors:
rep.warn("앵커가 절 제목이 아니라 번호·마커",
f"{len(seen_anchors)}건 — 검사기가 그 절이 실재하는지 대조하지 못한다")
rep.warn("앵커가 검사 가능한 절 제목/번호 형식이 아니",
f"{len(seen_anchors)}건 — 사용자 정의 marker는 실재 여부를 대조하지 못한다")
# 범위 안의 절을 후보 대장이 하나도 안 짚었나.
# 검사기는 「후보 ↔ 글감」만 봐서 SSOT 재료를 통째로 지나쳐도 error 가 0 이었다.