refactor: 문서 개선 중
This commit is contained in:
@@ -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 이었다.
|
||||
|
||||
Reference in New Issue
Block a user