콘텐츠로 이동

백로그 등록부 검사 — 무엇을 항목으로 읽고, 무엇에 실패하는가

Status: IMPLEMENTEDscripts/check-backlog-registers.py. .github/workflows/build.ymldocs-links 잡 두 번째 스텝이 검사를 먼저, --self-test 를 그다음에 돌린다(§2.7). 출처는 이슈 #88이고, 페이지에 보이는 것과 읽기를 맞춘 결정(§2.2)은 #102에서 더해졌다. 그 결정이 따르지 않는 raw HTML 블록과, 링크 검사와 제목을 다르게 읽는 자리를 적어 두기로 한 결정(§2.2)은 #121 에서 더해졌다. 남은 사각지대는 §4 에 있다.

살아 있는 명세는 그 스크립트의 모듈 docstring 이다. 이 문서는 결정과 그 근거, 기각한 대안, 하지 말 것만 담는다. 문법의 글자와 셈은 docstring 의 절 — WHAT IS AN ITEM, WHAT IS A STATE, TITLE, INDEX, THE DECISIONS, WHERE IT RUNS, NOT CHECKED, BLIND SPOT, SHARP EDGES — 에서 읽는다. 둘이 다르게 말하면 docstring 이 맞고 이 문서를 고친다.

등록하는 사람을 위한 서술은 ../../backlog/README.md 규칙 일곱의 끝에 있다.


이 문서가 design/ 에 있는 이유

문서 도구의 설계를 project/ 가 아니라 design/ 에 두는 이유는 translation-structure-check.md 의 첫 절과 같다 — 이 문서는 따라야 할 규칙이 아니라, 규칙을 강제하는 장치의 근거다.

docstring 에 접어 넣지도 않는다. docstring 은 명세이고, ../README.md 가 설계 문서의 몫으로 꼽는 기각한 대안까지 담으면 명세가 이력이 된다. 대안을 지우면 그것들은 git 이력에만 남는다. 짝을 이루는 선례인 구조 검사도 스크립트와 설계 문서를 함께 둔다. 이 문서가 또 하나의 낡는 사본이 되지 않도록, 문법의 글자 · 줄 번호 · 구현 계획 · 정정 기록처럼 편집마다 낡는 것은 여기에 두지 않는다. 결정이 바뀔 때만 이 문서가 바뀐다.


1. 문제

docs/backlog/ 의 등록부는 저마다 항목 수를 항목 밖에서 두 번 적는다 — 등록부 자신의 H1 표제 (등록 항목 N건 (열림 X · 닫힘 Y …))에 한 번, docs/backlog/README.md 의 색인 표 (| 항목 수 | 열림 | 닫힘 | 해소 |)에 한 번. 둘 다 항목 제목에서 손으로 옮긴 사본이고, 이 검사 전에는 어느 사본도, 그리고 항목 ID 가 한 번만 나오는지도 검사하는 것이 없었다. 2026-09-10 에 두 브랜치가 각자 llm-config-surface-open-items.md## L-13 을 더했고, git 은 두 항목 본문을 충돌 없이 합쳤다. 충돌한 것은 건수 두 줄뿐이었고, 그것이 누군가 알아챈 유일한 이유다. README 의 규칙 일곱(요약표는 항목이 아니다)과 그 날짜 문단들이 그 전의 건수 드리프트를 기록하고 있으며, 전부 사람이 읽다가 찾았다.

할 일은 표준 라이브러리만 쓰는 검사를 CI 에서 돌려 세 가지에 실패시키는 것이다 — 한 등록부 안에서 반복된 항목 ID, 항목과 어긋난 표제 건수, 항목과 어긋난 색인 행. 그것을 제목 모양 · 상태 단어 · ID 접두어가 제각각인 등록부들에서 해내야 하고, 읽을 수 없는 등록부나 제목을 말없이 건너뛰어서는 안 된다. 건너뛴 제목은 아무도 세지 않는 항목이다.


2. 결정

쟁점 결정 기각한 대안
무엇이 항목인가 (§2.1) 줄 맨 앞의 ATX 제목 가운데 ID 런과 /· 로 시작하는 것. ID 로 시작하는데 항목이 아닌 제목은 발견. 번호로 인용되는 등록부 하나는 선언된 번호 읽기 제목 어디든 있는 ID · 모든 등록부 정규화 · 숨은 표식 · 두 번째 제목 문법 · 번호 등록부에 ID · 면제
페이지가 보여 주는 것 (§2.2) 주석 블록은 읽지 않는다. 들여쓰기 · > · 리스트 표지 뒤의 ID 제목은 unread-heading. setext 와 HTML 제목은 BLIND SPOT 에 이름을 적는다. 주석이 아닌 raw HTML 블록은 따르지 않고 그 종류를 적는다. 링크 검사와 제목을 다르게 읽는 자리는 합치지 않고 docs_tree.anchors_of 에 적는다 주석 속 제목을 센다 · 보고한다 · 밀려난 제목을 센다 · BLIND SPOT 에만 적는다 · 새 발견 종류 · raw HTML 블록도 가린다 · 주석 읽기를 anchors_of 로 옮긴다
상태 어휘 (§2.3) 굵은 글씨의 상태 단어와 , 항목 절 안의 상태 기록. 한정어는 절대 상태가 아니다 — 결정됨 평문의 상태 단어 · 마지막 단어가 이긴다 · 결정 대기 의 칸 · 결정됨 을 열림으로
등록부 간 유일성 (§2.4) 검사한다. 기존 L-1 하나만 이유와 함께 선언한다. 새 번호는 그 접두어를 쓰는 모든 등록부 기준 검사하지 않음 · 접두어 소유 · L-1 개명 · 맨 인용 스캔
색인 (§2.5) 헤더 이름으로 찾는 단 하나의 표. > 줄은 절대 행이 아니다 ## 목록 으로 찾기 · 파이프가 든 모든 줄 · 정확한 여섯 열
읽을 수 없는 등록부 (§2.6) 실패한다. 면제가 없고 모든 발견이 exit 1 보고만 한다 · 등록부별 면제
CI 자리와 순서 (§2.7) docs-links 잡의 두 번째 스텝, 검사 다음 셀프 테스트 translations 잡 · 새 잡 · 잡 개명 · 셀프 테스트 먼저
셀프 테스트의 모양 (§2.8) 코퍼스를 고정하고 하나씩 바꿔 기준선과의 차이를 센다. 대상은 규칙으로, 발견이 없는 자리에만 겨눈다 절대 건수 · 이름으로 겨누기 · 같은 종류만 피하기 · 건너뛰기
일부러 검사하지 않는 것 (§2.9) 절 소계 · 파생 표 · 상태의 참 · docs/design/backlog/ · 번호 재사용과 순서

2.1 무엇이 항목인가

결정.

  • 펜스 블록과 HTML 주석 블록 밖, 줄 맨 앞에서 시작하는 레벨 2 이상의 ATX 제목만 항목을 정의한다. 표 · 인용 블록 · 리스트 항목 · 산문은 절대 항목을 정의하지 않는다. 펜스는 docs_tree.unfence 의 읽기이고, check-doc-links.py 가 앵커를 계산할 때 쓰는 것과 같다.
  • 제목 텍스트가 ID 런으로 시작하고 그 뒤에 공백과 또는 · 가 오면 항목이다. ID 런은 ID 하나, 또는 · 로 이어진 같은 접두어의 ID 여럿이고, 접두어가 다른 첫 ID 에서 멈춘다 — B-19 · B-20 · B-24 — … 는 항목 셋, B-7 · U-8 — … 은 B-7 하나다.
  • ID 토큰의 경계는 ASCII 다. 뒤에 영숫자 · _ · - 가 오지 않아야 ID 이므로 SBS-04bP1-14 는 ID 가 아니고, 조사가 붙은 L-16에 는 ID 다.
  • 항목 절은 같거나 더 얕은 레벨의 다음 제목까지, 또는 깊이와 무관하게 다음 항목 제목까지다.
  • ID 로 시작하는데 항목이 아닌 제목은 발견(unread-heading)이다. 마크업(* · 백틱 · ~ · _)과 선두의 영숫자 아닌 글자를 벗긴 뒤에 본다 — ### T-1 (원문), ## **L-16** — …, ### ✅ B-35 — … 가 전부 걸린다.
  • 번호로 인용되는 등록부 하나는 읽기를 선언한다 (interrupt-open-items.md). 항목은 N ≥ 1 인 레벨 2 의 ## N. 이고, ## 0. 은 정정 기록이다. 번호가 감싸인 레벨 2 제목(## **6.** …)은 발견이다. 선언은 스크립트 상수(READINGS)에 이유와 함께 살고 스스로 무효가 된다 — 파일이 없거나 ID 항목 제목이 하나라도 있으면 선언이 발견이다.

왜.

  • 이 검사가 들어올 때 열한 등록부 중 열은 이미 모든 항목을 ID 가 앞선 제목으로 쓰고 있었다. 규칙은 한 줄로 말할 수 있고, 검사는 그 한 줄을 강제한다. 다음 작성자는 휴리스틱 묶음이 아니라 docstring 의 문법을 읽는다.
  • 읽지 않은 ID 제목이 발견인 것은 그것이 아무에게도 알리지 않고 항목이 빠지는 길이기 때문이다. 마크업을 벗기지 않으면 ## **L-16** — … 은 항목도 읽지 않은 제목도 아니어서 말없이 빠진다.
  • ASCII 경계인 이유도 같다. 파이썬의 \b 는 유니코드라서 L-16에 를 ID 토큰으로 보지 않고, 그 제목은 말없이 빠진다. ASCII 경계에서는 ID 이므로 unread-heading 이 된다.
  • 읽기를 선언할 때와 제목을 고칠 때를 가르는 원칙. 등록부가 다른 모양으로 한결같이 쓰여 있고 그 모양으로 인용될 때 읽기를 선언한다 — 인터럽트 등록부의 항목은 interrupt.md · routing.md(§2), inbox-collect-durability.md(§5), architecture-review-open-items.md(1번), 백로그 README(2번 · 3번)가 번호로 가리킨다. 제목 몇 개가 자기 등록부의 모양을 깨고 그것을 가리키는 링크가 없을 때는 제목을 고친다. 스타터 등록부 §5 의 라벨이 앞선 묶음 제목들과 B-21 이 되살아난 소절 제목은 번호를 앞에 두도록 고쳐졌고(되살아나 닫힌 B-21 은 해소 묶음 제목의 이름에서 빠졌다), translation-tooling 등록부의 ### T-1 (원문)### 원문 — T-1 이 되었다. 건수는 하나도 움직이지 않았다.

기각한 대안.

대안 기각한 이유
제목 어디에든 있는 ID (휴리스틱) 가짜 중복이 난다. #### 1차 전제 정정 … — B-20 의 … 같은 산문 소제목은 B-20 자신의 묶음 안에 있고, 되살아난 이력을 적는 소절은 같은 ID 를 되풀이한다
모든 등록부를 한 모양으로 정규화하고, 열린 항목에도 상태를 적게 한다 상태 없는 스타일로 쓰는 두 등록부(llm-config-surface, reasoning-delta-stream)에서만 제목 편집이 스무 건을 넘고, 그 스타일로 항목을 더하는 모든 브랜치가 병합하는 족족 빨개진다. 검사는 관례를 있는 그대로 읽어야 한다
숨은 항목별 표식 (<!-- item: L-13 state: 열림 -->) 상태의 세 번째 사본이고, 렌더된 페이지에서는 보이지 않는다. 검사는 표식을 건수와 대조하겠지만 독자가 실제로 보는 제목과 표식을 대조하는 것은 아무것도 없다 — 규칙 일곱의 파생 뷰를 한 층 더 내린 것이다
라벨이 앞선 묶음 제목을 위한 두 번째 문법 (<label> — B-19 · B-20 · B-24) 한 파일의 세 줄을 위한 두 번째 문법이다. 산문 소제목과 구별하려면 "ID 런 뒤에 아무것도 없다" 는 꼬리 규칙이 필요한데 모르는 사이에 깨지기 쉽고, 그래도 되살아난 B-21 을 해소로 읽는다
인터럽트 항목에 ID 를 준다 모든 항목에 두 번째 이름을 주는데, 인용하는 문서들은 여전히 번호를 쓴다
스타터 등록부를 면제한다 가장 큰 등록부의 유일한 검사를 끄고, 그곳은 README 가 이미 드리프트를 기록한 곳이다

2.2 페이지가 보여 주는 것

docs/backlog/ 는 mkdocs 가 빌드하지 않는다(mkdocs.ymlexclude_docs). 등록부의 유일한 렌더링은 GitHub 의 것이고, 읽기는 주석 블록과 밀려난 제목에서 그 렌더링을 양쪽 방향으로 따른다 — 페이지에 없는 제목을 세지 않고, 페이지에 제목으로 보이는 ID 를 말없이 건너뛰지 않는다. 따르지 않는 곳이 하나 있다. 주석이 아닌 raw HTML 블록이고, 아래 소절이 그 종류를 적는다.

주석 블록은 읽지 않는다

결정. 제목을 읽기 전에 등록부 텍스트는 펜스를 비우고(docs_tree.unfence), 이어서 HTML 주석 블록을 비운다 (스크립트의 uncomment). 둘 다 줄 번호를 보존한다. 주석 블록은 CommonMark 가 정의하는 것이다 — 세 칸까지 들여 쓴 <!-- 로 시작하는 줄에서 열리고, 여는 줄을 포함해 --> 를 담은 첫 줄에서 닫히며, 닫히지 않으면 파일 끝까지 간다. GitHub 도 그렇게 그린다. 그 안의 제목은 항목도, 읽지 않은 제목도, 상태 기록도, 절의 끝도, 표제도 아니다.

한 줄 안의 인라인 주석(## L-5 — 제목 <!-- 메모 -->)은 건드리지 않는다. 문법은 제목 텍스트를 읽고, 그 절은 텍스트의 일부다. 그런 제목은 없고, 인라인 주석을 규칙으로 만들려면 그것만의 스위치가 필요하다. 경계로 적어 두고 만들지 않았다.

왜.

  • 이 검사는 두 사본이 독자가 셀 수 있는 항목과 일치하게 하려고 있다. 주석으로 가린 항목은 그런 항목이 아니다.
  • 표제가 그것을 세야 한다면 검사에는 맞고 페이지를 읽는 모든 사람에게는 틀린 숫자가 된다. 항목 하나를 주석으로 가리는 데 편집이 둘(주석과 표제) 들고, 그동안 페이지의 건수는 바뀌지 않는다.

순서는 펜스 다음 주석이다. unfence 가 첫 단계이므로 검사는 펜스를 check-doc-links.py 와 똑같이 읽고, 펜스 안 예시의 <!-- 는 주석을 열지 못한다. 대가는 시끄러운 날카로운 자리 둘이다 — 짝이 맞지 않는 펜스 표지를 담은 주석 블록은 그 뒤를 전부 가리고, unfence 가 짝을 페이지와 다르게 지어 드러낸 진짜 펜스 안의 <!-- 도 그 뒤를 가린다(§4). 둘째가 이 주석 읽기를 링크 검사로 옮기지 않는 이유다(아래 "링크 검사와 제목을 다르게 읽는 자리는 합치지 않고 적는다").

기각한 대안.

대안 기각한 이유
세고, 센다고 문서화한다 표제가 독자가 찾을 수 없는 항목을 세야 한다. 숨은 항목별 표식(§2.1)을 기각한 이유 — 렌더된 페이지에서 보이지 않는다 — 가 그대로 적용된다
주석 안의 항목 모양 제목을 발견으로 보고한다 주석은 작성자가 텍스트를 페이지에서 내리는 수단이고, 등록하기 전에 제목을 초안으로 적어 두는 것도 정당한 쓰임이다. 항목을 지운 것인지는 트리 하나로 알 수 없다 — 번호 재사용과 순서를 검사하지 않는 이유와 같고(§2.9), 페이지로서는 주석으로 가린 항목이 곧 지운 항목이다

줄 맨 앞에서 밀려난 ID 제목은 발견이다

결정. 항목으로 세는 제목 모양은 그대로다 — 줄 맨 앞의 ATX 제목뿐이다. 대신 읽지 않은 제목의 그물이 넓어진다. 다음 둘을 모두 만족하는 줄은 밀려난(displaced) 제목이다.

  1. 쓰인 그대로는 ATX 제목이 아니지만, 앞의 표지를 벗기면 ATX 제목이 된다. 표지는 공백, >, 리스트 표지(- * + N. N) 뒤에 공백)의 어떤 순서 · 조합이든 된다.
  2. 그 제목 텍스트가 줄 맨 앞에 있었다면 읽기에 영향을 준다 — 마크업을 벗기면 ID 로 시작하거나, 번호 읽기에서 번호 항목이거나, 현재 항목 절 안의 상태 기록이다.

밀려난 제목은 unread-heading 으로 보고되고, 메시지는 어떻게 밀려났는지(들여 씀 · 인용 블록 안 · 리스트 항목 안)와 고치는 법을 말한다 — 제목을 줄 맨 앞으로 옮기거나, 인용이나 리스트가 항목을 인용할 뿐이면 제목을 ID 로 시작하지 않는다(### 원문 — T-1). 밀려난 제목은 절대 항목 · 기록 · 절의 끝이 되지 않는다. 둘째 조건에 맞지 않는 밀려난 제목은 무시한다.

왜. #102 가 내놓은 두 선택지 — BLIND SPOT 에 적는다, 센다 — 를 등록부 작성자가 실제로 만드는 세 상황에 대고 돌렸다.

선택지 밀려난 항목, 표제가 센다 (등록부 = 페이지) 같은데 표제가 세지 않는다 (등록부 ≠ 페이지) 항목 제목을 인용하는 인용 블록, 표제 그대로 (등록부 = 페이지)
BLIND SPOT 에 적는다 빨강, 그리고 틀림 — 항목은 둘로 읽히는데 페이지는 셋을 보여 준다 초록, 그리고 틀림 초록
센다 초록 빨강 (title) 빨강, 틀린 조언과 함께duplicate-id/shared-id 가 인용의 번호를 바꾸라고 하고, title 이 붙는다
보고한다 (채택) 빨강 — unread-heading 이 줄과 고치는 법을 대고, title 빨강 — unread-heading 빨강 — 인용에 맞는 조언을 담은 unread-heading
  • BLIND SPOT 에 적는 것은 되지 않는다. 뒤집힌 판정을 문서화해도 초록 줄 "every register's title and index row agree with its items" 가 참이 되지는 않는다.
  • 세는 것은 앞의 두 열에서는 되지만, 계약을 더 많이 바꾼다.
  • 검사가 이 부류에 이미 강제하는 규칙을 깬다. ## **L-16** — …, ## `L-16` — …, ### ✅ B-35 — … 는 전부 항목 제목으로 렌더되지만 세지 않고 unread-heading 으로 보고한다. 세면 들여쓰기가 문법 밖에서 세어지는 유일한 모양이 되고, README 가 작성자에게 주는 문법이 "줄 맨 앞 제목의 ID" 가 아니게 된다.
  • 인용 블록과 리스트 항목이 항목의 그릇이 된다. "Tables, blockquotes, list items and prose never define an item" 은 작성자가 인용할 때 기대는 약속이고, 절의 끝과 상태 기록까지 인용 안에서 읽히기 시작한다.
  • 링크 검사가 링크할 수 없는 항목을 센다. docs_tree.anchors_of 는 줄 맨 앞의 ATX 제목만 보므로, 들여 쓰거나 인용한 항목에는 check-doc-links.py 가 보기에 앵커가 없다. 보고하면 제목이 두 검사가 모두 읽는 유일한 모양으로 옮겨 간다.
  • 더 큰 변경이다. 세면 docstring 과 README 의 항목 문법이 바뀌고, 보고하면 문법도 발견 종류도 그대로다.
  • 들여쓰기는 폭을 가리지 않는다. 검사는 블록 문맥이 아니라 줄을 읽는다. 네 칸 이상이면 최상위에서는 코드 블록이지만, 내용을 그만큼 들여 쓰는 리스트 항목(1. … 다음 줄의 ## L-5 — …) 안에서는 제목이다. 세 칸에서 멈추면 리스트 연속 줄의 제목이 조용해진다. 폭을 두지 않으면 그 틈이 닫히고, 대가는 시끄러운 오탐 하나다 — 들여 쓴 코드 블록 안의 항목 제목 예시. 등록부는 예시를 펜스에 쓰고, docstring 도 그렇게 말한다. 이 저장소는 조용한 쪽보다 시끄러운 쪽을 고른다(§2.6).
  • 리스트 항목도 넣는다. 리스트 항목 안의 제목도 GitHub 이 그리고 검사가 말없이 건너뛰던 같은 부류다. 두세 칸 들여 쓴 연속 줄은 이미 들여쓰기 규칙에 걸리므로, - ## … 만 빼면 한 구조의 한가운데에 경계를 긋게 된다.

기각한 대안.

대안 기각한 이유
BLIND SPOT 에 이름만 적는다 (#102 의 선택지) 위 표의 첫 행. 뒤집힌 판정이 그대로 남는다
센다 (#102 의 선택지) 위의 네 가지 대가
들여 쓴 것은 세고 인용한 것은 보고한다 한 부류에 답이 둘이다. 들여 쓴 것을 세는 쪽은 여전히 링크 검사와 갈라지고 **L-16** 선례와 어긋난다
ID 가 없는 것까지, 밀려난 제목을 전부 보고한다 등록부의 평범한 인용 제목(> ## 배경)을 금지하게 되는데, 그것으로 닫는 절 경계의 틈(§4)은 한 번도 일어난 적이 없다. 읽지 않은 제목의 그물은 늘 ID 범위였다
새 발견 종류 (displaced-heading) 고치는 법이 unread-heading 과 같은 모양("이 제목은 읽히지 않는다, 다시 써라")이다. 새 종류는 문서화된 집합과 --github 애노테이션 어휘만 키우고, 작성자가 할 일은 달라지지 않는다

setext 와 HTML 제목은 BLIND SPOT 에 이름을 적는다

결정. CE-3 — … 아래에 ---=== 를 그은 setext 제목과 <h2>CE-3 — …</h2> 같은 HTML 제목은 읽지도 보고하지도 않는다. docstring 의 BLIND SPOT 과 README 가 둘의 이름을 적고, 셀프 테스트가 "아무것도 더하지 않는다" 를 고정한다 — 나중에 바꾸려면 docstring 도 함께 바꿔야 한다.

왜. 밀려난 ATX 제목은 줄 하나로 정확히 알아볼 수 있다. 문법이 이미 읽는 # 줄에 표지가 붙었을 뿐이다. 이 둘은 그렇지 않다.

  • 문법은 ATX 만 읽는다고 선언되어 있고, 남은 비-ATX 모양이 이 둘이다.
  • setext 는 앞 문단이 필요하다. 밑줄은 그 위 문단 전체를 제목으로 만들고, 리스트 항목 뒤나 빈 줄 뒤의 --- 는 구분선이다. 게으른 연속 줄까지 따지면 두 줄짜리 휴리스틱이 되고, 틀린 휴리스틱은 잡아야 할 문단을 놓치는 조용한 방향으로 실패한다. 구조 검사도 같은 판단을 적어 두었다(translation-structure-check.md §8 의 다섯째).
  • HTML 제목은 속성을 달 수 있고(<h2 id="…">), 대문자로 쓸 수 있고, 여러 줄에 걸칠 수 있다. 한 줄 패턴은 그중 한 표기를 잡고 나머지에는 조용할 것이다. 일부만 잡는 그물은 전부 잡는 그물처럼 읽히고, 그것이 이 검사가 막으려는 실패다. "HTML 제목은 읽지 않는다" 는 참인 문장이지만, 일부만 보고하는 검사는 나머지에 대해 거짓말을 한다.
  • 등록부에 둘 다 없다.

그러므로 밀려난 제목의 논거(BLIND SPOT 에 적어도 판정은 뒤집힌 채 남는다)와 이 결정은 부딪히지 않는다. 앞의 것은 줄 규칙 하나로 틈을 전부 닫을 수 있어서 보고하고, 이것은 줄 규칙으로는 틈의 일부만 닫히므로 이름을 적는다.

기각한 대안. 문단이 ID 로 시작하는 setext 제목을 보고한다 — 위의 휴리스틱이다. 한 줄짜리 <h2> 만 보고한다 — 위의 부분 그물이다.

주석이 아닌 raw HTML 블록은 따르지 않고, 그 종류를 적는다

결정. 검사가 알아보는 HTML 블록은 주석 하나다. CommonMark 0.31.2 §4.6 의 나머지 여섯 종류 — <pre> · <script> · <style> · <textarea>(1), <?(3), <!DOCTYPE(4), <![CDATA[(5), <details> · <div> 같은 블록 태그(6), 줄에 홀로 선 그 밖의 완결된 태그(7) — 안의 ATX 줄은 페이지에 제목으로 보이지 않지만, 검사는 제목으로 읽고 센다. docstring 의 결정 여섯이 종류와 각 블록이 끝나는 자리를 적고, README 규칙 일곱이 작성자에게 고치는 법을 말한다 — <details> 의 여는 태그 뒤에 빈 줄을 두면 블록이 거기서 끝나 페이지도 그 제목을 보여 준다. 셀프 테스트가 <details> 안의 항목 제목이 읽힌다는 것을 고정한다.

왜.

  • 이 블록을 가리려면 그 시작이 펜스나 인용 · 리스트 항목 안에 있는지 알아야 하는데, 검사는 한 줄씩 읽고 펜스는 docs_tree.unfence 의 것이다. unfence 는 펜스 표지를 위치로만 짝짓는다 — 긴 펜스 안의 짧은 표지가 그 펜스를 닫고, 리스트 표지 줄에서 연 펜스의 닫는 표지가 새 펜스를 연다. 그렇게 드러난 줄에 블록 시작이 있으면, 진짜 펜스 안의 예시인데도 그 뒤의 제목을 가린다. 페이지가 보여 주는 항목을 세지 않게 되는 것이다. 주석에는 그 자리가 이미 있고(§4), 가리는 종류를 여섯 더 늘리면 그 자리가 여섯 종류로 번진다.
  • 121 의 첫 설계가 이 블록들을 가리려 했다. 세 번의 설계 리뷰가 매번 페이지가 보여 주는 제목을 가리는 새 입력을

    찾았고, 마지막 리뷰는 cmark-gfm 과 무작위로 대조해서 찾았다. 고칠 때마다 닫힌 것은 보고된 입력뿐이었다.
  • 따르지 않을 때 틀리는 방향은 정해져 있고 이름을 댈 수 있다. 판정이 뒤집힌다 — 페이지를 세는 표제는 빨개지고, 가려진 제목까지 센 표제는 초록이다. 그 틀림은 모양 하나(여는 태그 바로 아래의 제목)와 고치는 법 하나(빈 줄)로 적힌다. 가리는 쪽의 틀림은 펜스 짝이 어긋나는 모든 입력에 흩어져서 그렇게 적을 수 없다.
  • 등록부에 raw HTML 블록이 없다.

기각한 대안.

대안 기각한 이유
주석처럼 가린다 (#121 의 첫 설계) 위 첫째와 둘째. 가리는 규칙을 좁히고(두 스펙이 함께 여는 시작만, 줄 맨 앞에서만) 펜스 거부권을 겹쳐도, 한 줄씩 읽는 검사는 컨테이너 안의 펜스를 CommonMark 처럼 짝짓지 못한다
"양쪽 방향으로 따른다" 는 문장을 그대로 둔다 주석과 밀려난 제목에만 참인 문장이다. <details> 안의 제목을 세는 한 그 문장은 거짓이다

링크 검사와 제목을 다르게 읽는 자리는 합치지 않고 적는다

결정. 링크 검사(docs_tree.anchors_of)는 주석 블록을 가리지 않고 밀려난 제목을 보고하지 않는다. 그래서 두 검사는 세 모양에서 제목을 다르게 읽는다.

  1. 주석 블록 안 — 링크 검사는 앵커를 주므로 그리로 가는 링크가 조용히 통과한다. 이 검사는 읽지 않는다.
  2. 한 칸에서 세 칸 들여쓰기 · > · 리스트 표지 뒤, 리스트 연속 줄 — 링크 검사는 앵커를 주지 않으므로 맞는 링크가 실패한다. 이 검사는 unread-heading 이다.
  3. unfence 가 짝을 어긋나게 지어 드러낸 진짜 펜스 안의 <!-- 뒤 — 링크 검사는 페이지대로 앵커를 준다. 이 검사는 가린다.

이 차이는 한 읽기로 합치지 않고, 모양마다 이유와 함께 anchors_of 의 docstring 에 적는다. 두 검사의 셀프 테스트가 저마다 자기 쪽을 고정한다.

왜.

  • 이 검사의 주석 읽기를 anchors_of 로 옮기면 첫째 모양의 조용한 통과는 닫히지만, 셋째 모양이 링크 검사로 옮겨 간다 — 페이지가 보여 주는 제목으로 가는 맞는 링크가 실패하기 시작한다. 이 검사에서 그 자리는 표제 불일치로 드러나는 날카로운 자리이고(§4), 항목을 주석으로 가리는 작성자의 쓰임(위 "주석 블록은 읽지 않는다")이 그 값을 치른다. 저장소의 모든 마크다운을 읽는 링크 검사에는 그 값을 치를 쓰임이 없다 — 이 결정을 내릴 때 트리에 주석 블록 안의 제목은 없었고, 읽기를 옮겨도 앵커가 바뀌는 파일은 없었다.
  • 밀려난 제목에 앵커를 주려면 같은 인용이나 리스트 항목 안에서 연 펜스 · 주석 · HTML 블록이 그 제목을 담는지 알아야 하는데, 그것은 줄에 적혀 있지 않다. 사이트의 Python-Markdown 도 한 칸에서 세 칸 들여쓰기, 1) 리스트, 문단 바로 뒤의 리스트 표지, 리스트 연속 줄의 제목을 제목으로 그리지 않는다. 앵커를 주지 않으면 실패는 시끄럽고, 제목을 줄 맨 앞으로 옮기면 두 렌더링 모두에서 고쳐진다.
  • 적어 두지 않으면 차이는 둘 중 한 검사를 고치는 사람에게 보이지 않는다. 한쪽 스크립트에만 적으면 다른 쪽을 고치는 사람이 읽지 않는다. 그래서 두 스크립트가 함께 가져오는 docs_tree.py 에 적고, 두 셀프 테스트가 양쪽을 고정해 적힌 글이 조용히 낡지 않게 한다.

기각한 대안.

대안 기각한 이유
주석 읽기를 anchors_of 로 옮긴다 위 첫째. 링크 검사가 페이지에 보이는 제목을 건너뛰는 이유가 unfence 의 것 위에 하나 더 늘어난다
링크 검사가 밀려난 제목에 앵커를 준다 위 둘째. 인용 안에서 연 주석이 담은 > ## … 에 앵커가 생겨, 첫째 모양의 조용한 통과가 인용 안에서 되살아난다. 사이트가 제목으로 그리지 않는 모양에도 앵커가 생긴다
펜스 읽기부터 CommonMark 에 맞춘다 링크가 검사되는 줄과 두 검사가 읽는 제목이 저장소 전체에서 바뀌는 별개의 변경이다. unfence 를 다시 볼지는 그것대로 정할 일이다

2.3 상태 어휘

결정.

  • 상태는 항목 제목에서, 인라인 코드를 비우고 ~~…~~ 스팬을 지운 뒤에 읽는다. 백틱 안의 단어는 예시이고, 그은 단어는 예전 상태다.
  • 명시적 상태는 텍스트가 열림 · 닫힘 · 완료 · 해소시작하고, 그 뒤에 스팬의 끝 · 공백 · ( . , — · : 중 하나가 오는 각 **…** 스팬이다. 이 경계가 해소된닫힌 이 상태로 읽히지 않게 한다.
  • 는 제목 어디에 있든 닫힘이다. 세는 칸은 열림 → 열림, 닫힘 · 완료 · → 닫힘, 해소 → 해소이고, 아무것도 없으면 열림이다.
  • 상태 기록은 항목 절 안에서 항목이 아닌 제목이 같은 경계 아래 상태 단어로 시작하는 것이다 (### 닫힘 (2026-09-10, #73)). 인라인 코드를 비우고 그은 스팬을 지우고 ** 를 없앤 뒤에 본다. 절 안의 마지막 기록이 이기므로 되살아난 이력이 순서대로 읽힌다.
  • 셋은 선택이 아니라 발견(state)이다 — 한 제목에 서로 다른 명시적 상태 둘, 명시적 열림이나 해소와 함께 있는 , 마지막 기록과 다른 명시적 제목 상태. 그런 항목에는 상태가 없으므로 그 등록부의 표제 · 색인 은 비교하지 않고 합계만 비교한다. 끝나지 않은 편집 하나가 state · title · index 로 세 번 보고되지 않는다.
  • 한정어는 절대 상태가 아니다. 결정 대기 · 트리거 대기 · 설계 대기 · 소비자 대기 · 막힘 · 접힘 · 결정됨 은 굵은 글씨 안에서 선두 열림 뒤에 오는 텍스트(**열림 · 결정 대기**)이거나 검사가 보지 않는 텍스트다. 접힘 에 색인 칸을 따로 주지 않는 이유는 README 규칙 일곱의 끝에 있다.
  • 결정됨 은 열림의 한 종류도 아니다. README 규칙 넷의 "결정됨이되 열림" 은 문서화나 구현이 남은 항목이라 열림으로 적거나 아무것도 적지 않는다. 남은 일이 없는 결정은 로 닫는다 — B-10 의 **결정됨 (2026-08-05): 유지** … ✅ 은 글리프로 닫힘이고, 그 표제와 README 도 그렇게 센다.
  • 표제와 색인의 열은 정확히 열림 · 닫힘 · 해소 다. 표제의 괄호는 · 로 나누고, <열림|닫힘|해소> <n> 은 건수이며 빠진 열은 0 이다. 모르는 열(완료 3, 접힘 1), 숫자 없는 열 이름, 두 번 쓴 열은 발견이다. 그 밖의 텍스트는 한정어이고 무시한다((열림 1 · 결정 대기)).

왜.

  • 이 검사가 들어올 때 항목 제목에 적힌 상태는 전부 굵은 글씨이거나 였다. 상태 단어가 평문에 나오는 자리는 함정이었다 — B-15 의 *(절반 완료 …)*열린 항목에 있다.
  • 완료 를 닫힘으로 알아보면 건수를 하나도 바꾸지 않는 스타터 제목 편집 열여섯 건을 아낀다.
  • 제목이 상태 둘을 말하면 끝나지 않은 편집이다. 이 저장소가 상태를 바꾸는 방식은 옛 상태를 긋는 것이고(T-1 의 ~~열림~~ **닫힘**), 긋지 않은 둘 중 하나를 고르면 작성자가 무엇을 뜻했는지를 검사가 정하게 된다.
  • 기록에서도 그은 스팬을 지운다. 표식만 지우면 ### ~~열림~~ **닫힘 (…)** 기록이 열림으로 읽혀, 같은 모양의 항목 제목과 반대가 된다.
  • 결정됨 은 결정문이 써졌다는 표시이지 남은 일이 없다는 표시가 아니다. 규칙 넷은 그 둘을 같은 칸에 세기를 거절하고, 어느 칸인지는 제목의 상태 단어나 가 말한다.

기각한 대안.

대안 기각한 이유
평문에서 상태 단어를 읽는다 기울임을 벗기는 규칙이 필요하고, 제목 문장 속의 단어까지 읽게 된다. B-15 가 열린 채 닫힘으로 읽힌다
마지막 상태 단어가 이긴다 끝나지 않은 편집을 조용히 받아들인다
결정 대기 를 네 번째 열로 센다 색인에 그 열이 없고, 규칙 넷은 결정을 진척으로 세기를 거절한다
결정됨 을 열림으로 센다 B-10 과 그 등록부의 표제가 빨개지고, "고치는" 방법이 남은 일이 없는 항목을 다시 여는 것이 된다 — 기록된 결정을 검사가 뒤집는 것이다

2.4 등록부 간 유일성

결정. 둘 이상의 등록부에서 읽힌 항목 ID 는 발견(shared-id)이다. 예외는 스크립트 상수(SHARED_IDS)에 이유와 함께 공유로 선언된 ID 뿐이고, 그것은 llm-config-surface-open-items.mdopenai-model-capabilities-open-items.mdL-1 하나다. 선언은 스스로 무효가 된다 — 충돌이 없어지면 선언이 발견이다. 번호 읽기의 항목은 이 규칙의 ID 가 아니다(늘 등록부와 함께 인용된다).

중복이나 공유로 실패하면 메시지가 가져갈 번호를 말한다 — 그 접두어를 쓰는 어느 등록부도 아직 쓰지 않은 다음 번호다.

왜.

  • 설계 문서와 CHANGELOG 는 항목을 등록부 없이 L-13 으로 인용한다. 이 검사는 선언된 예외를 빼면 맨 ID 가 많아야 한 등록부를 가리킨다는 것을 보장하고, 독자는 등록부를 열지 않고도 그 진술에 기댈 수 있다.
  • 모호함이 만들어지는 순간에, 그것을 만든 사람에게서 실패한다. 등록부별 "다음 빈 번호" 가 바로 충돌을 만든다 — openai 등록부의 다음 빈 번호는 L-2 이고, llm-config-surface 가 이미 갖고 있다.
  • 기존 L-1 은 이미 독자에게 일을 시켰다. 설계 문서 하나가 그 충돌을 설명하는 데 한 문단을 들였고, 여러 문서가 L-1 을 손으로 한정한다. 그래도 개명하지 않고 선언하는 이유는, 인용이 맨몸이라 인용마다 어느 L-1 을 뜻했는지 읽어서 정해야 하고, 그것이 곧 모호함 자체이기 때문이다.

기각한 대안.

대안 기각한 이유
검사하지 않는다 규칙이 산문에만 산다. 구조 일치 규칙에 강제 장치가 없던 T-1 이 그 끝을 보여 준 이 저장소 자신의 사례이고, openai 등록부의 다음 항목은 아무에게도 알리지 않고 충돌을 되풀이한다
접두어 소유 (접두어 하나는 등록부 하나의 것) L 을 공유로 선언해야 하고, 그러면 openai 등록부의 L-2 가 통과한다 — 가장 일어날 법한 바로 그 충돌이다
openai 의 L-1 을 개명한다 기존 인용이 더 이상 없는 이름을 가리키게 된다
설계 문서의 맨 인용을 스캔한다 산문을 읽어야 하고, 그래도 인용이 어느 등록부를 뜻했는지 알 수 없다

2.5 색인

결정.

  • 색인은 docs/backlog/README.md 에서 펜스 밖에 있고 헤더가 항목 수 · 열림 · 닫힘 · 해소이름으로 갖는 단 하나의 표다. 맞는 표가 없거나 둘 이상이면 발견이다. 열은 헤더 이름으로 찾으므로 앞에 열이 새로 생겨도 아무것도 움직이지 않는다.
  • 행은 구분선 바로 뒤에서 | 로 시작하는 줄들이고, 표는 그렇지 않은 첫 줄에서 끝난다. 행의 첫 칸 링크가 등록부를 가리킨다.
  • 모든 등록부는 정확히 한 행을 갖고, 모든 행은 존재하는 등록부를 가리키고, 모든 건수 칸은 정수다.

왜. 색인 아래의 날짜 문단은 옛 값을 일부러 인용한다. 그 문단들은 표가 끝난 뒤의 > 인용 블록이고, 값은 문장 속 인라인 코드에 있다. > 로 시작하는 줄은 절대 표를 잇지 않고, 문장 속의 파이프는 절대 표를 시작하지 않는다. 셀프 테스트가 둘 다 끼워 넣으므로, 나중에 파이프가 든 모든 줄을 읽도록 "단순화" 하면 트리가 아니라 셀프 테스트가 실패한다. 행이 없는 등록부를 발견으로 두는 것은, 그것이 색인 비교가 말없이 건너뛰는 등록부이기 때문이다 — README 는 "위 표에 다섯 번째 문서가 빠져 있었다" 를 이미 기록한다.

기각한 대안. ## 목록 제목으로 표를 찾는다 — 헤더 이름 조회는 절 이름 변경과 새 열을 둘 다 견딘다. 파일의 모든 | 줄을 파싱한다 — 행 모양으로 적힌 노트를 읽게 된다. 여섯 열 헤더를 정확히 요구한다 — 열 하나를 더하는 것이 이유 없이 검사를 깨뜨린다.

2.6 읽을 수 없으면 실패한다

결정.

  • README.md 를 뺀 모든 docs/backlog/*.md 가 등록부다. 번역 접미사가 붙은 파일(docs_tree.translation_suffix)은 등록부가 아니고, 요약 줄에서 세기만 한다.
  • 면제 장치는 없다. 모든 발견이 exit 1 이고, docs/backlog/ 나 그 README.md 가 없는 것도 환경이 아니라 내용이므로 실패한다.
  • 모든 실행은 발견이 0건이어도 모든 등록부를 그 셈과 함께 찍는다. 읽히지 않은 등록부는 그것이 내지 못한 줄로 보인다.
  • 선언에 대한 발견은 고칠 줄을 가리킨다 — 낡은 SHARED_IDS 와 없는 파일의 READINGS 는 스크립트의 그 상수 줄에, ID 항목 제목을 가진 번호 등록부는 그 첫 제목에.

왜.

  • 이 드리프트가 아무도 모르게 지나간 방식이 침묵이다. 보고되되 실행을 실패시키지 않는 발견은 아무 뜻도 없는 초록 실행이다. check-translation-staleness.py 가 UNRESOLVABLE 로 그것을 배웠다 — 번역 32건 중 19건이 해석 불가였던 동안 잡은 초록이었다.
  • 읽을 수 없는 등록부를 고칠 몫은 언제나 그것을 고치는 사람에게 있다 — 제목 어순을 바꾸거나 표제를 쓴다. 다른 누구도 막히지 않는다. 그것이 UNRESOLVABLE 을 exit 1 로 만든 논거다.

기각한 대안. 실패시키지 않고 보고한다 — 위의, 아무 뜻도 없는 초록이다. structure_exempt 식의 등록부별 면제 — 그것이 필요한 등록부가 없고, 등록부 하나를 면제하면 그 등록부가 가진 유일한 검사가 꺼진다(structure_exempt 는 여러 축 가운데 하나만 끈다).

2.7 CI 자리와 순서

결정. docs-links 잡의 두 번째 스텝에서, 한 스텝 안에 검사(--github) 다음 --self-test 를 돈다. 잡 이름은 그대로다.

왜.

  • 브랜치 룰셋은 잡을 이름으로 요구한다. 새 잡은 돌기는 하지만 누군가 룰셋을 고칠 때까지 병합을 막지 않고 — 아무것도 막지 않으면서 빨개질 수 있는 검사다 — docs-links 를 개명하면 필수 컨텍스트가 깨진다.
  • docs-links 는 기본 얕은 체크아웃 위의 순수 텍스트 잡이고, 이 검사는 이력을 읽지 않는다. translations 는 이력이 필요해서 전체를 체크아웃하고 자기 심각도를 번역 신선도로 정의하므로, 거기서 실패하면 실패한 체크에 엉뚱한 도메인의 이름이 붙는다.
  • 두 문서 잡은 setup-pythonpip install 도 하지 않으므로, 검사는 표준 라이브러리만 쓴다.
  • 순서. 러너는 run:bash -e 로 돌리므로 먼저 실패한 줄이 스텝을 끝낸다. 이 셀프 테스트는 변조를 실제 트리에 겨누므로, 먼저 돌면 셀프 테스트 케이스를 깨뜨리는 트리(겨눌 대상이 남지 않은 트리, 읽을 수 없는 색인 표)에서 틀린 제목을 이름으로 대는 검사 출력이 나오기 전에 스텝이 멈춘다. 검사가 먼저면 빨간 스텝은 언제나 트리에서 무엇이 틀렸는지를 말하고, 셀프 테스트는 검사를 통과한 트리에서만 돌므로 거기서의 실패는 검사기를 뜻한다.
  • 구조 검사가 반대 순서를 유지하는 것도 맞다. 그 셀프 테스트는 모든 쌍에 대해 읽기만 바꾸고 대상을 겨누지 않으므로, 트리 상태가 구조적으로 상쇄된다.

기각한 대안. translations 잡, 새 잡, docs-links 개명 — 위의 이유. 셀프 테스트를 먼저 — 구조 검사의 모양을 그대로 따르는 순서지만, 위 "순서" 의 실패를 그대로 갖는다.

2.8 셀프 테스트의 모양

결정.

  • 코퍼스를 고정하고 한 번에 하나만 바꾼다. 각 케이스는 변조가 더한 발견을 변조 없는 실행과 비교해 센다. 비교는 (종류, 경로) 의 멀티셋으로 하고 줄 번호는 뺀다 — 줄 하나를 끼우면 뒤의 모든 줄이 밀린다.
  • 실제 트리를 겨누는 케이스(필수 실패 셋과 날짜 노트, 공유 ID)의 대상은 이름이 아니라 규칙으로 고르고, 기준선에 어떤 종류든 발견이 있는 자리는 고르지 않는다 — 경로에 발견이 하나도 없는 등록부, 그 줄에도 그 등록부에도 발견이 없는 색인 행, 공유로 선언되지 않은 ID(스크립트의 Tree).
  • 드리프트 케이스가 그 규칙을 증명한다. 그 케이스들이 겨눌 등록부와 행에 정확히 드리프트를 놓고, 드리프트가 발견을 만드는 것을 확인한 뒤, 같은 케이스들을 드리프트 트리에서 다시 돌린다.
  • 읽기 규칙마다 스위치(Rules)가 있고, 케이스가 하나씩 끄면서 합성 등록부에 그 틀린 읽기가 이름 대는 발견 종류가 반드시 더해지는지 본다. 합성 등록부는 명세된 문법 아래에서 깨끗이 읽히고 — 그것도 케이스다 — 펜스 안 예시와 주석으로 가린 제목을 일부러 담는다. 접두어는 실제 등록부가 쓰지 않는 첫 후보다.
  • 나머지 발견 종류도 한 번씩 발화시킨다. 페이지가 보여 주는 모양(§2.2)은 표제가 그대로일 때와 그 항목을 셀 때 둘 다 정확한 판정으로 고정한다. BLIND SPOT · SHARP EDGES · 결정 여섯이 이름을 적은 모양 가운데 여럿에 케이스가 있지만 전부는 아니다 — 케이스가 없다고 잰 SHARP EDGES 의 펜스 · 주석 모양은 백로그 T-9 에 있다. 읽히는 모양(<details> 안의 제목)은 색인 행이 여전히 8 이므로 두 판정 모두에 index 가 더해진다.
  • 링크 검사와 제목을 다르게 읽는 모양은 양쪽에서 고정한다 — 이 검사 쪽은 이 셀프 테스트가, 링크 검사 쪽은 check-doc-links.py --self-test 가. 그쪽은 트리를 읽지 않고, 모양마다 한 쪽짜리 페이지를 만들어 앵커가 풀리는지만 묻는다.
  • 공유 선언과 번호 읽기 선언의 케이스는 합성으로 만든다.
  • 겨눌 것을 찾지 못한 케이스는 건너뛰지 않고 "could not construct" 로 실패하되, 따로 세어 검사기가 아니라 트리를 가리키고 셀프 테스트 없는 실행을 권한다.

왜.

  • 잡을 것이 없는 트리에서 본 실행은 조용히 읽기를 멈춘 검사와 같은 초록 줄을 찍는다. 셀프 테스트가 그 둘을 가르는 유일한 것이다.
  • 절대 건수 대신 기준선과의 차이를 세면, 이미 드리프트가 있는 트리는 셀프 테스트를 초록으로 두고, 실제 트리에 대한 판정을 가진 본 실행이 그것을 한 번 보고한다.
  • 발견이 있는 자리를 겨누면 변조가 아무것도 더하지 못한다. 이미 틀린 표제는 여전히 표제 발견 하나이고, 읽을 수 없는 항목 상태는 그 등록부의 열 비교를 꺼서 칸 사이의 이동이 보이지 않는다. 그런 케이스는 본 실행이 보고하는 평범한 드리프트에서 빨개지고, CI 에서 진짜 발견을 가린다.
  • 규칙을 끄면 실제 등록부의 읽기도 바뀌므로(같은 접두어 규칙을 끄면 스타터의 B-7 · U-8 이 U-8 을 낸다), 스위치 케이스는 이름 댄 종류만 요구하고 나머지는 허용한다.
  • 선언 케이스를 실제 선언에 기대면, 선언이 정당하게 사라지는 날 "could not construct" 가 CI 를 빨갛게 만든다.
  • BLIND SPOT 을 고정하는 것은 그것을 사고가 아니라 결정으로 만들기 위해서다.
  • 합성 등록부의 기준 케이스가 펜스와 주석 읽기에 묶여 있는 것은 의도다. 그 둘 중 하나가 코드에서 빠지면 기준 케이스와 그 위에 선 케이스들이 함께 빨개지는데, 그것이 셀프 테스트가 할 일이다.

기각한 대안.

대안 기각한 이유
절대 기대 건수 드리프트가 있는 트리에서 셀프 테스트가 빨개지고, 판정이 두 곳으로 갈린다
이름으로 겨누기 (특정 등록부 · ID) 그 등록부가 바뀌거나 사라지는 날 케이스가 깨진다
같은 종류의 발견만 피해서 겨누기 읽을 수 없는 상태가 열 비교를 끄는 경로를 놓친다 — 분할 케이스가 평범한 드리프트에서 빨개진다
구성할 수 없는 케이스를 건너뛴다 건너뛴 케이스는 통과한 케이스와 밖에서 구분되지 않는다
스위치를 끄면 발견이 "하나라도" 생기면 된다 규칙이 엉뚱한 이유로 무언가를 바꾸기만 해도 통과한다

2.9 일부러 검사하지 않는 것

검사하지 않음
등록부 안의 절 소계 (## 1. … — 7건 (…)) 건수의 세 번째 사본이고, 한 등록부만 갖고 있다. 그 등록부 §5 의 소계는 일부러 비표준이다 — 해소된 항목을 빼고, 열이 아닌 대기 를 적는다(README 의 날짜 문단). 검사하려면 기록된 그 소계를 다시 쓰거나 절 하나를 면제해야 하는데, 면제는 없다(§2.6). 소계 여섯 중 다섯은 항목과 맞고 여섯째는 문서화되어 있어서, 항목으로 등록할 관측 가능한 실패도 없다. 절을 고친 사람이 소계를 손으로 다시 센다
ID 를 상태와 함께 나열하는 파생 표 (스타터의 이슈 표, 트리거 표, 셈 표) 규칙 일곱의 파생 뷰다. 항목은 제목이지 표 칸이 아니다
제목의 상태가 인지 검사는 건수의 사본들을 제목과 일치시킬 뿐이다. "일치는 검증이 아니라 복제다" 가 이 검사에도 적용되고, README 문단이 그렇게 말한다
docs/design/backlog/ 다른 종류의 백로그이고, 건수가 없다
번호 재사용과 순서 트리 하나로는 정할 수 없고, 이력이 필요하다

3. 하지 말 것

  • 평문에서 상태를 읽지 말 것. B-15 의 *(절반 완료 …)* 가 열린 항목을 닫는다.
  • 제목이 상태 둘을 말할 때 하나를 고르지 말 것. 끝나지 않은 편집이고, 고르는 것은 작성자의 몫이다.
  • > 줄이 색인 표를 잇게 하지 말 것. 파이프가 든 모든 줄을 행으로 읽도록 단순화하는 것도 같은 금지다 — 날짜 노트가 행이 된다.
  • 주석으로 가린 제목을 세지도, 보고하지도 말 것. 페이지에 없는 항목이다.
  • 들여 쓰거나 인용하거나 리스트 안에 둔 ID 제목을 세지 말고, 조용히 건너뛰지도 말 것. 세면 인용이 항목이 되고, 건너뛰면 판정이 뒤집힌다.
  • setext 나 HTML 제목에 한 줄 패턴을 붙이지 말 것. 일부만 잡는 그물이 BLIND SPOT 의 참인 문장을 거짓으로 만든다.
  • 번호 등록부에 다른 목적의 ## N. 을 더하지 말 것 (## 6. 관련). 항목으로 세어져 표제 불일치로 실패한다. 번호를 다르게 붙이거나 붙이지 않는다.
  • 등록부별 면제를 더하지 말 것. 그 등록부의 유일한 검사를 끄는 것이다.
  • 다음 번호를 한 등록부 안에서 구하지 말 것. 그것이 L-2 충돌이다.
  • 셀프 테스트의 변조를 기준선에 발견이 있는 자리에 겨누지 말 것. 평범한 드리프트가 셀프 테스트를 빨갛게 만든다.
  • CI 에서 --self-test 를 검사보다 먼저 돌리지 말 것. 빨간 스텝이 트리의 무엇이 틀렸는지 말하기 전에 멈춘다.
  • 셀프 테스트가 빨개졌다고 합성 등록부에서 펜스 예시나 주석 블록을 빼지 말 것. 기준 케이스가 그 읽기에 묶여 있는 것이 그 읽기를 지키는 장치다.
  • 주석이 아닌 raw HTML 블록을 줄 단위로 가리지 말 것. unfence 가 짝을 어긋나게 지은 펜스 안에서 드러난 블록 시작이 페이지가 보여 주는 제목을 가린다(§2.2).
  • 주석 읽기를 anchors_of 로 옮기지 말 것. 링크 검사가 페이지에 보이는 제목으로 가는 맞는 링크에 실패하기 시작한다. 두 검사가 제목을 다르게 읽는 자리를 바꿀 때는 docs_tree.anchors_of 의 글도 함께 바꾼다 — 두 셀프 테스트가 그 글을 고정한다(§2.2).

4. 남은 사각지대

전문은 docstring 의 결정 여섯과 BLIND SPOT · SHARP EDGES 이고, 작성자 쪽 서술은 README 규칙 일곱의 끝이다. 링크 검사와 제목을 다르게 읽는 자리는 docs_tree.anchors_of 의 docstring 에 있다.

  • 세지 못하는 항목. ID 로 읽히는 번호로 시작하지 않는 항목 — 번호가 없거나(## 새 항목 — …), 접두어가 패턴에 맞지 않거나(## ABCD-1 — …, ## l-16 — …), 번호 등록부에서 레벨 2 의 ## N. 이 아닌 것. 그리고 # 줄로 쓰지 않은 항목 — setext 와 HTML 제목(§2.2). 작성자가 표제 건수도 그대로 두었다면 두 실수가 서로를 지운다. 검사는 의도가 아니라 제목을 센다.
  • 시끄러운 날카로운 자리.
  • 우연히 상태 단어로 시작하는 소제목(### 해소 조건)은 기록으로 읽힌다 — 문구를 바꾼다.
  • 괄호로 연 ID(### (L-3 참고) 배경)는 unread-heading 이다 — 단어로 시작한다.
  • 밀려남은 줄만 보고 판단한다. 리스트 연속 줄의 제목은 항목 글자만큼(- 아래 두 칸, 1. 아래 세 칸) 들여 쓰이고 들여 쓴 코드 블록 안의 것은 네 칸이며, 둘 다 보고된다 — 네 칸 코드 블록 안의 항목 제목 예시도 실패한다.
  • 펜스와 주석도 줄만 보고 알아본다. 펜스 표지는 들여쓰기와 무관하게 알아보지만 > 뒤와 리스트 표지 줄에서는 알아보지 못하고, 다음 표지가 문자 · 길이와 무관하게 그 펜스를 닫는다. 그래서 리스트 연속 줄의 펜스는 페이지처럼 그 안의 제목을 가리고, 네 칸 코드 블록 안의 표지 쌍은 페이지가 보여 주는 제목을 가리며, 리스트 표지 줄에서 연 펜스의 닫는 표지는 페이지에 없는 펜스를 연다. 주석은 세 칸까지 들여 쓴 뒤에서 — 리스트 연속 줄에서도 — 알아보지만 > 뒤, 리스트 표지 줄, 네 칸 이상 뒤에서는 알아보지 못한다. 알아보지 못한 펜스나 주석 안의 줄은 그것이 없는 것처럼 읽히므로, 그 안의 제목이 > 나 들여쓰기 뒤에 있으면 보고된다 — 예시는 > 뒤도 리스트 표지 줄도 아닌 곳에서 연 펜스에 둔다.
  • 펜스를 주석보다 먼저 읽으므로, 짝이 맞지 않는 펜스 표지를 담은 주석 블록은 그 뒤를 전부 가리고, unfence 가 일찍 닫았거나 열린 줄 모른 진짜 펜스 안의 <!-- 도 그렇다. 가려진 제목은 읽힌 것보다 많은 항목을 세는 표제로 드러난다.
  • 조용할 수 있는 경계 둘.
  • 밀려난 제목 가운데 ID · 번호 · 상태 기록이 아닌 것(## 관련)은 페이지에서는 항목 절을 끝내지만 읽기에서는 끝내지 않는다. 그 뒤의 상태 기록이 앞 항목에 붙어 표제와 어긋나면 statetitle 로 드러나고, 어긋나지 않으면 조용하다. 그런 제목을 모두 보고하는 대안을 기각한 이유는 §2.2 에 있다.
  • 주석이 아닌 raw HTML 블록 안의 제목은 페이지에 제목으로 보이지 않는데도 읽고 센다. 그 제목까지 센 표제는 초록이다. 이 블록을 가리지 않는 이유는 §2.2 에 있다.

참조 파일 지도

무엇 어디
검사와 살아 있는 명세 (문법 · 결정 · 사각지대) scripts/check-backlog-registers.py (모듈 docstring)
셀프 테스트 (Rules, Tree, 드리프트 케이스, 합성 등록부) scripts/check-backlog-registers.pyself_test
펜스 읽기 · ATX 제목 · 번역 파일 판정 · 앵커 계산 scripts/docs_tree.py (unfence, ATX_HEADING, translation_suffix, anchors_of)
링크 검사와 이 검사가 제목을 다르게 읽는 자리, 그리고 그 이유 scripts/docs_tree.py (anchors_of 의 docstring)
링크 · 앵커 검사와 그쪽 모양을 고정하는 셀프 테스트 scripts/check-doc-links.py (--self-test)
UNRESOLVABLE 선례 scripts/check-translation-staleness.py (헤더 docstring)
CI 스텝과 순서 .github/workflows/build.yml (docs-links)
등록하는 사람을 위한 규칙 · 색인 · 날짜 문단 docs/backlog/README.md (규칙 넷, 규칙 일곱의 끝, 목록)
번호로 읽는 등록부 docs/backlog/interrupt-open-items.md
공유 ID L-1 의 두 등록부 docs/backlog/llm-config-surface-open-items.md, docs/backlog/openai-model-capabilities-open-items.md
비표준 절 소계 · B-10 · B-21 docs/backlog/spring-boot-starter-open-items.md
셀프 테스트와 setext 판단의 선례 docs/design/documentation/translation-structure-check.md
등록부가 사이트에 빌드되지 않는다는 사실 mkdocs.yml (exclude_docs)

관련 문서