genetics-mcp 2.23.0 → 2.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/bin/aarch64-apple-darwin/gcell +0 -0
  2. package/bin/aarch64-unknown-linux-musl/gcell +0 -0
  3. package/bin/manifest.json +5 -5
  4. package/bin/x86_64-apple-darwin/gcell +0 -0
  5. package/bin/x86_64-pc-windows-gnu/gcell.exe +0 -0
  6. package/bin/x86_64-unknown-linux-musl/gcell +0 -0
  7. package/genetics.mjs +2019 -227
  8. package/package.json +2 -2
  9. package/skills/gazette-report/SKILL.md +11 -10
  10. package/skills/gazette-report/scripts/tone.mjs +17 -5
  11. package/skills/gazette-report/scripts/tone.test.mjs +15 -2
  12. package/skills/gazette-report/template/skeleton.html +10 -10
  13. package/skills/genetics-define/SKILL.md +2 -0
  14. package/skills/genetics-report/SKILL.md +93 -87
  15. package/skills/genetics-report/assets/example/cases-briefing.json +1 -1
  16. package/skills/genetics-report/assets/example/cases.json +3 -3
  17. package/skills/genetics-report/assets/example/report-briefing-designs.json +26 -26
  18. package/skills/genetics-report/assets/example/report-briefing.json +3 -3
  19. package/skills/genetics-report/assets/example/report.json +11 -11
  20. package/skills/genetics-report/assets/mcp-call.mjs +109 -0
  21. package/skills/genetics-report/assets/patch.py +196 -0
  22. package/skills/genetics-report/assets/render.py +172 -0
  23. package/skills/genetics-report/assets/xlsx-plate.py +203 -0
  24. package/skills/genetics-report/references/content-pipeline.md +26 -26
  25. package/skills/genetics-report/references/edition-review.workflow.js +70 -15
  26. package/skills/genetics-report/references/history.md +76 -2
  27. package/skills/genetics-report/references/lim-portal-format.md +100 -45
  28. package/skills/genetics-report/references/linear-read-test.workflow.js +50 -14
  29. package/skills/genetics-report/references/process.md +47 -43
  30. package/skills/genetics-report/references/reader-review.workflow.js +105 -28
  31. package/skills/genetics-report/references/visual-first.md +29 -12
@@ -20,7 +20,7 @@
20
20
  |---|---|
21
21
  | `sections[].marg` (여백 주석 단) | 두 단 본문이 **한 단으로 선다.** 「문서처럼 꾸민 웹페이지」로 되돌아간다 |
22
22
  | `parts` (PART 층위) | 절만 늘어선다. 「먼저 읽을 것 / 경과 / 남은 것」이라는 **읽기 순서**가 사라진다 |
23
- | 자리별 톤(§14) | 기록 자리에 경어체를 쓰면 관보 문법 위에서 겉돈다. **요청 자리에 개조식을 쓰면 하대로 읽힌다** |
23
+ | 주어별 톤(§14) | 요청 자리 밖에 경어체를 쓰면 관보 문법 위에서 겉돈다. **독자가 할 일을 요청 자리 밖에 개조식으로 적거나, 요청 자리를 개조식으로 맺으면 명령·하대로 읽힌다** |
24
24
 
25
25
  이 셋은 **거절 사유가 아니다** — 렌더러는 비어도 문서를 낸다(초안을 막지 않는다).
26
26
  대신 ⚠ 로 세어 준다(`gazette.py` 의 `marg()` · `build()`).
@@ -28,7 +28,7 @@
28
28
 
29
29
  **왜 marg 가 본체인가.**
30
30
  Tufte 의 여백 노트가 근거다 — 참조를 문서 끝이 아니라 참조된 자리 **옆**에 둔다.
31
- 독자가 본문 흐름을 잃지 않고 「왜 읽는가 · 하나만 가져간다면 · 어디를 참조하나」를 받는다.
31
+ 독자가 본문 흐름을 잃지 않고 「왜 읽는가 · 하나만 가져간다면 · 더 볼 곳」을 받는다.
32
32
  그 단이 없으면 남는 것은 한 단짜리 글줄이고, 그것이 곧 웹페이지다.
33
33
 
34
34
  ---
@@ -64,8 +64,13 @@ python3 assets/render.py <report.json>
64
64
  - **`build.py` 를 직접 부르지 마라.**
65
65
  `cfg.get('css', ['report.css'])`(`build.py styles()`)로 떨어져 옛 서식 시트가 실린다.
66
66
  스타일 없는 문서가 대비 검사를 통과해 「거짓 초록」이 된다.
67
- - 로컬 전용 칸이 둘 있다: `out`(출력 파일명) · `forbid`(금칙 패턴).
68
- 둘 다 발행 창구가 깎아 낸다 — 서버는 `out` 을 스스로 `report.html` 로 박는다(`report_publish.php report_render()`).
67
+ - 렌더러에 안 넘어가는 칸이 둘 있다: `out`(출력 파일명) · `forbid`(금칙 패턴).
68
+ 서버는 `out` 을 스스로 `report.html` 로 박는다(`report_publish.php report_render()`).
69
+ `forbid` 는 렌더러에 넘기지 않지만 버리지도 않는다 — 입구의 `report_publish.php report_forbid_warnings()` 가 경고로 센다(§10). 로컬 `render.py` 는 `build.py guard()` 가 렌더된 HTML 에 걸리면 멈춘다.
70
+ - `tenant`(소속처 slug)는 발행 데이터 칸이 아니라 `publish_report` 의 인자다 — 포털 입구는 렌더러 몫에서 깎는다.
71
+ 소속처가 반려한 이름 목록은 그 소속처의 **반려 결정 장**(`remember_decision` 의 `rejected_names` 칸)에 산다. 포털은 발행·모의 발행 때
72
+ 서버가 그 목록을 렌더러에 넘기고, 로컬 `render.py` 는 `--tenant <slug>`(없으면 원고의 `tenant`)로 MCP `list_rejected_names` 를 불러 받는다
73
+ (§10-1 「소속처가 반려한 이름」 행). 원고에 `rejected_names` 를 적어도 쓰지 않는다 — 포털 입구도 `render.py` 도 버린다.
69
74
 
70
75
  ---
71
76
 
@@ -79,6 +84,7 @@ python3 assets/render.py <report.json>
79
84
  `gazette.py` 의 `build()` 가 이 순서로 조립한다.
80
85
  데이터로 바꿀 수 없다.
81
86
  인쇄하면 표지가 1쪽 단독, PART 간지가 각각 1쪽 단독, 절마다 새 쪽에서 시작한다.
87
+ 인쇄에서 여백 주석은 절 머리의 가로 띠로 서고 본문은 쪽 폭(174mm)을 다 쓴다 — 본문 활자는 10.5pt(2026-09-27, §11-3 「넓은 표」).
82
88
 
83
89
  | # | 가구 | 마크업 | 데이터 | 없으면 |
84
90
  |---|---|---|---|---|
@@ -243,7 +249,7 @@ python3 assets/render.py <report.json>
243
249
  "name": "지금 확인이 필요한 것",
244
250
  "short": "확인 요청",
245
251
  "sub": "수신 측 조치 사항의 전부",
246
- "lede": "총 건수 표시 1건에 대해 확인 필요.",
252
+ "lede": "총 건수 표시 1건에 대한 확인 요청임.",
247
253
  "marg": {
248
254
  "why": "무엇을 확인해야 하는가?",
249
255
  "key": "이 절이 조치 사항의 전부임. 나머지 절은 근거.",
@@ -401,7 +407,7 @@ python3 assets/render.py <report.json>
401
407
  ## 10. 경고 조건 — 발행은 되지만 빠진다 (⚠)
402
408
 
403
409
  `dry_run` 의 `warnings` 가 **실질적 유일한 품질 관문이다.**
404
- 렌더러가 센 것이 먼저 오고, 그 뒤에 규격 게이트와 그림 관문이 붙는다.
410
+ 렌더러가 센 것이 먼저 오고, 그 뒤에 규격 게이트 · 금칙어(`forbid`) · 그림 관문이 붙는다.
405
411
 
406
412
  | ⚠ | 무엇이 안 실렸는가 |
407
413
  |---|---|
@@ -426,11 +432,17 @@ python3 assets/render.py <report.json>
426
432
  | 절 …의 도판 「id」의 원본 폭 N px 이 본문 단 760px 보다 작아 늘려 그려집니다 | 흐림. 배율 2 로 다시 굽거나 원본을 더 크게(readability.md R8) |
427
433
  | N자 넘는 문장이 M개입니다 / 괄호 부연이 둘 이상인 문장이 M개입니다 | 글 층(readability.md R11a·R11g) — 한 문장 60자, 괄호 문장당 하나. 한 문장 한 줄이라 문장이 곧 줄 길이 |
428
434
  | 괄호 안이 20자 넘는 부연이 N개입니다 | 괄호 안 20자(공백 제외) — 긴 부연은 문장으로 풀거나 표·주석으로(R11g) |
429
- | 절 …표 「…」 칸이 N자입니다 / 흐름도 「…」 칸이 N자입니다 / 흐름도 「…」 칸 note 가 N자입니다 | 표 칸 20자(5열 이상 12자), 흐름도 칸 20자·note 12자 — 설명은 ※ 주석·본문으로(R11b) |
435
+ | 절 …표 「…」 칸이 N자입니다 / 흐름도 「…」 칸이 N자입니다 / 흐름도 「…」 칸 note 가 N자입니다 | 표 칸 20자(5열 이상 12자), 흐름도 칸 20자·note 12자 — 설명은 ※ 주석·본문으로(R11b). 표 칸 경고 끝에는 「경고를 피하려고 이름을 바꾸지 말고, 표 열을 줄이거나 행으로 푸십시오(원래 이름은 세지 않았습니다)」가 붙는다(2026-09-26) |
430
436
  | 도판 「id」 캡션이 N자입니다 / 캡션이 N문장입니다 / 출처 줄이 N자입니다 | 캡션 60자 한 문장, 출처 40자(R11c) |
431
437
  | 요약면 막대가 대기 N · 완료 N 인데 문서의 셈은 … 입니다 / 머리띠 요청 잔여가 N 인데 판단 항목은 N개입니다 | `bar` 의 대기(`wait`) 합이 미결 건 수·판단 항목 수 어느 것과도 다르거나, 완료 합이 나머지 건 수·전체 건 수 어느 것과도 다르다 / 손으로 적은 `remain` 이 판단 항목 수와 다르다. 대조만 하고 채우지 않는다. 건이 없는 문서의 막대·숫자가 아닌 막대 값·빈 `remain`·`stats` 는 대조하지 않는다(§3.3). **막대가 건이 아닌 다른 것을 세면 `bar_note` 에 셈 기준을 밝힌다 — `bar_note` 가 있으면 대조하지 않는다**(실측 2026-09-14: 막대를 세운 원고 셋이 모두 다른 셈이었고 셋 다 각주로 기준을 밝혀 두었다) |
432
438
  | 요약면 현황 줄이 N자입니다 / 수치 칸 라벨 「…」이 N자입니다 / 요약 N번째 문단 본문이 N자입니다 | 현황 80자, 라벨 16자, 요약 본문 160자(R11d) |
433
439
  | 절 … 도입문이 N자입니다 / 여백 주석 k 가 N자입니다 / 판단 항목 N 본문이 N자입니다 | 도입문 90, 여백 주석 30/45/40, 판단 항목 220자(R11e·f) |
440
+ | 금칙어 「패턴」에 걸린 자리가 N곳입니다 — 경로 「적중」 · …(넘으면 「그 밖 M곳」). 이 문서의 forbid 목록에 든 표현입니다. 포털은 경고로 셉니다. 로컬 렌더(build.py guard)는 렌더된 HTML 에 같은 표현이 남으면 멈춥니다 | 서버 `report_publish.php report_forbid_warnings()`(2026-09-26). 문서의 `forbid` 패턴을 발행 데이터의 글자 칸마다(`data` 한 칸만 뺌 — 렌더러가 안 그리는 `generated_by`·`work` 포함) PCRE(`u`)로 맞춘다. 자리는 5곳까지, 패턴은 앞 50개·한 개 200자까지. 렌더러에는 넘기지 않고 발행을 막지 않는다 — 로컬 렌더와 판정이 갈릴 수 있다(content-pipeline §4.3) |
441
+ | forbid 가운데 세지 못한 패턴이 N개 있습니다 — forbid.i 「패턴」(까닭) · …. 이 패턴들의 적중은 0 이 아니라 미확인입니다 | 같은 함수. 글자가 아님 · 200자 넘음 · 정규식으로 읽히지 않음 · 빈 문자열에 맞아 모든 자리에 걸림 · 실행 상한(까닭은 PCRE 문구 그대로 「끝까지 세지 못함 — …」) · 50개 넘음(「앞 50개만 셈(넘친 K개)」). 조용히 건너뛰면 「금칙어 0」으로 읽혀서 알린다 |
442
+
443
+ **원래 이름은 길이로 세지 않는다**(2026-09-26). 위 표의 문장·괄호 안·현황 줄·수치 칸 라벨·요약 본문·판단 항목 본문·도입문·여백 주석·표 칸·흐름도 칸과 note·캡션·출처 줄 셈(`gazette.py Doc.text_scan()`)은
444
+ 원래 이름 꼴 넷 — 밑줄·점·붙임표로 이은 식별자(`innodb_io_capacity`·`v4.2`) · 대문자로 시작하는 낱말의 연속(`Adaptive Hash Index`·`Extended Support`) · 글자 뒤에 숫자가 붙은 낱말 · 소문자 뒤 대문자(`iOS`) — 과 이름 바로 뒤의 판 번호(`MySQL 8.0`)를 빼고 센다.
445
+ 소문자 영어 낱말(`error`·`binlog`)은 센다. §10-1 의 문단 220자·절 400자 셈은 그대로다. 셈이 원래 이름을 비싸게 매기면 작성자가 짧은 이름을 지었다 — 합성 예로, 5열 표 칸에서 원래 이름 `Alpha Bravo Charlie`(17자)는 경고를 받고 지어낸 「보조 층」(3자)은 통과한다.
434
446
 
435
447
  경고가 하나라도 있으면 **그것부터 고친다.**
436
448
  경고를 남긴 채 고객에게 링크를 보내지 마라.
@@ -442,13 +454,13 @@ python3 assets/render.py <report.json>
442
454
  |---|---|---|
443
455
  | 「고객사 로고가 SVG 로 등록돼 있어 표지에 못 싣습니다」·「표지용 파생본이 아직 없습니다」 | 소속처 로고 설정의 상태다 | 소속처 관리 화면(사람) |
444
456
  | 「바깥에서 받아 오는 것이 있습니다」 — 그 호스트가 **본문 링크**(`[글자](주소)`·맨 주소·건 `source_url`)뿐일 때 | 입고 검사가 `<a href>` 호스트까지 센다(§11-6). 누르는 링크는 받아 오는 자원이 아니다. `<img src>` 등으로 뜨면 작성자 몫 | 서버 검사 수리 전까지 사람이 호스트 목록을 대조 |
445
- | 경어체 경고 — PART I 간지(`parts[]`)의 요청 문장이나 읽는 법(`guide`)의 확인 요청 한 줄에서만 날 때 | 둘 다 요청 자리(§14-2 · §12.5b)인데 서버 문체 관문이 이 자리는 아직 센다 | 사람이 자리를 확인 |
457
+ | 경어체 경고 — PART I 간지(`parts[]` — PART I 이 요청·결정 PART 일 때)의 문장이나 읽는 법(`guide`)의 요청 한 줄에서만 날 때 | 둘 다 요청 자리(§14-2 · §12.5b)인데 서버 문체 관문은 둘 다, 로컬 `tone.mjs` 는 읽는 법 한 줄을 센다 | 사람이 자리를 확인 |
446
458
  | 레포의 렌더러·서버 관문이 운영보다 앞선 동안, 새 규칙대로 고친 자리에 운영 `dry_run` 이 옛 규칙으로 내는 경고 | 운영은 배포된 판으로 센다 — 되돌리면 새 규칙에 걸리고, 배포되면 사라진다 | 새 판에서 0 인지 먼저 잰다 — 렌더러 경고는 레포 작업본을 부르는 로컬 `render.py`, 서버 문체 관문·입고 검사·서식 등록부 칸은 레포 개발 스택의 `dry_run`(절차: genetics lim `cases/dev-스택이-OrbStack-덧판https-base_url으로-떠-있으면-…`). 0 이면 핸드오프에 「배포 뒤 해소」 한 줄 |
447
459
 
448
460
  요청 절의 경어체는 이제 예외가 아니다. 서버 문체 관문(`report_tone.php report_tone_warnings()`)은 요청 자리(`open_items[].body`·`cta`·
449
- `colophon.note`·`asks` 절 `marg`)와 「」 인용을 빼고 세고, 로컬 `tone.mjs` 는 판단 항목 본문과 「」 인용을 뺀다.
450
- ⚠ 서버 쪽 자리 가림은 운영 반영 확인 전(2026-09-14 갈래)이다 — 배포 전의 운영 `dry_run` 이 요청 자리에서 내는 경어체 경고는 이 표의 셋째 행처럼 자리를 확인하고 넘긴다. 그래도 경어체 경고가
451
- 뜨면 기록 자리(`open_items[].title` 포함)나 `guide` 에 요청 문장이 샌 것이다 — 요청 문장은 요청 절로 옮긴다(SKILL 5a 「보고 칸에 요청문을 두지 마라」). 단 `guide` 에서 요청 절의 확인 요청을 가리키는 한 줄은 허용한다(2026-09-15 사용자 결정) — 그 한 줄에서만 나는 경어체 경고는 §10 통과 기준 표의 행으로 넘긴다.
461
+ `colophon.note`·`asks` 절 `marg`)와 「」 인용을 빼고 세고, 로컬 `tone.mjs` 는 요청 자리(판단 항목 본문·`cta`·PART I 간지·판권지 회신 안내)와 「」 인용을 뺀다(2026-09-26).
462
+ 이 표의 셋째 행 밖에서 경어체 경고가
463
+ 뜨면 요청 자리 밖이나, 판단 항목 `title`(명사형 「~ 요청」·물음이라 서버가 경어체를 세는 칸)이나, `guide` 에 요청 문장이 샌 것이다 — 요청 문장은 요청 절로 옮긴다(SKILL 반드시 지키는 것 5 — 요청 자리 밖에는 독자를 주어로 쓰지 않는다). 단 `guide` 에서 요청 절의 확인 요청을 부탁하는 한 줄은 허용한다(2026-09-15 사용자 결정) — 그 한 줄에서만 나는 경어체 경고는 §10 통과 기준 표의 행으로 넘긴다.
452
464
  건의 `how_to_check` 에서 걸린 명령형은 「보고 칸에 요청문이 N곳 있습니다 — 요청 절(open_items)로 옮깁니다」로 따로 뜬다.
453
465
  톤 게이트는 경고이지 거절이 아니다(실측 2026-09-11: 경어체 경고를 안은 `update_report` 2회 모두 발행 성공).
454
466
  「경고 0」을 요청 자리를 개조식으로 내려서 맞추려다 **말투 반려가 네 번 났다**(genetics `decisions/고객-문서-요청-자리는-하십시오체로-맺는다-…`).
@@ -459,9 +471,6 @@ python3 assets/render.py <report.json>
459
471
 
460
472
  > 「읽기가 아직도 어려움 — 색상 다변화, 핵심은 굵게, 배지·그래프」 반려(2026-09-08 고객사 검수 보고)의 처방.
461
473
  > 문턱은 `gazette.py` 상수(`PROSE_MAX` 등)와 **같은 값**이다 — 이 표가 계약이다.
462
- > ⚠ 운영 반영 확인 전(2026-09-14 추가분): 출처 줄 제작 메타 · 주체 없는 결정 명사 · 차이를 못 나르는 열 · 건 행 이름 · 선택지 기호 ·
463
- > 개발 식별자 · 그림 번호 가리킴 넷 행과 §10 의 기한·괄호 안·흐름도 note·캡션 문장 수·막대 행은 이 갈래의 렌더러에 있다.
464
- > 배포 뒤 서버 `dry_run` 에 탐침을 태워 뜨는 것을 확인하고 이 줄을 걷는다 — 그 전에는 로컬 `render.py` 가 유일한 기계 그물이다.
465
474
 
466
475
  | ⚠ | 문턱 | 처방 |
467
476
  |---|---|---|
@@ -476,10 +485,10 @@ python3 assets/render.py <report.json>
476
485
  | 절 %s 도해 「%s」의 노드 「%s」 라벨이 두 줄을 넘습니다 — 낱말을 줄이거나 wide 로 두십시오 | 노드 폭(열 폭 − 간격)에서 15px 글자로 두 줄을 넘는 라벨 | 라벨을 줄이거나 `wide: true` |
477
486
  | 절 %s 도해 「%s」의 노드 「%s」가 없는 레인 「%s」를 가리킵니다 | `nodes[].lane` 이 `lanes[].id` 에 없다 | Archify validate 를 먼저 통과시킨다 |
478
487
  | 절 N 「이름」의 k번째 문단이 M자입니다 | 문단 한 개 220자 초과(세 줄) | 두 문장으로 끊거나 수치를 표로 내린다 |
479
- | 절 N 「이름」 산문에 참조 번호가 M개입니다 | 산문(문단·도입문·여백 주석)에 6.6·2.12a 같은 번호 4개 이상 | 번호는 표·건·판단 항목에만, 산문은 뜻으로 잇는다 |
488
+ | 절 N 「이름」 산문에 참조 번호가 M개입니다 | 산문(문단·도입문·여백 주석)에 6.6·2.12a 같은 번호 4개 이상. 원래 이름(`MySQL 8.0`·`v4.2`)은 걷고 세고, 문서가 이름 바로 뒤에 쓴 판 번호는 홀로 선 자리(「8.0 행」)에서도 세지 않는다(2026-09-26) | 번호는 표·건·판단 항목에만, 산문은 뜻으로 잇는다. 경고를 피하려고 판 번호를 「새 버전」 같은 말로 바꾸지 않는다 |
480
489
  | 판단 요청 N항이 산문으로만 섭니다 | `open_items` 6항 이상인데 asks 절에 표·흐름도 0 | 한눈표(항 · 지금 · 그대로 두면 · 바꾸면 — 두 길 대칭, 권고 열·시한 칩 없음) + 관문 흐름도(§11-2). |
481
490
  | 핵심 굵게 표기가 한 곳도 없습니다 | 산문 3,000자 이상, `**…**` 도 편집기 굵게 구간도 0 | 절마다 핵심 한 구절을 `**…**` 로 굵게(§11-6). 문단 절반이 굵으면 굵기가 위계를 못 나른다. |
482
- | 과정 어휘가 %d곳에 있습니다(%s 「%s」 …) — 독자는 앞 회차·회신·판을 모릅니다. 정해진 것은 사실로 배경에, 결정 요청에는 열린 것만 | 원고의 글자 자리 어디든(`history`·`labels`·`forbid`·`origin`·`file`·`source_url`·`href`·`data`·`out`·`css`·`render` 제외)에 정규식 `정해짐 \| 회신 대기 \| 남은 물음 \| 정해진 것 \| 이미 정해진 \| 남은 것 (하나·둘·셋·넷) \| (N·첫·두·세)판 \| 정한 사항 \| 결정(한·된) 사항 \| (미 앞이 아닌) 결정임` 이 1곳 이상. `%s` 둘은 자리 경로와 걸린 낱말. 주체 없는 결정 명사(「정한 사항」)는 누가·어디서 정했는지 없이 결정 경로를 사실처럼 싣는다 — 「미결정임」·표지 칸 이름 「결정 사항」·「나머지 사항」은 안 건다 | 회차 낱말(「이번 회차」)만이 아니라 **진행 상태 어휘와 구조**가 금지다 — 상태 열(「상태: 정해짐/회신 대기」)·「~ 그대로(변화 없음)」도 같다. 정해진 것은 **사실**로 요지(배경)에, 결정 요청 절에는 **지금 열린 것만**. 회차의 자리는 `history` 하나. 원고는 직전 판의 차분이 아니라 **독자의 0 상태에서 다시 쓴다** — 판마다 「처음 읽는 사람이 이 절에서 무엇을 알아야 하나」를 다시 묻는다(SKILL 3b · readability R11h) |
491
+ | 과정 어휘가 %d곳에 있습니다(%s 「%s」 …) — 독자는 앞 회차·회신·판을 모릅니다. 정해진 것은 사실로 배경에, 결정 요청에는 열린 것만 | 원고의 글자 자리 어디든(`history`·`labels`·`forbid`·`origin`·`file`·`source_url`·`href`·`data`·`out`·`css`·`render` 제외)에 정규식 `정해짐 \| 회신 대기 \| 남은 물음 \| 정해진 것 \| 이미 정해진 \| 남은 것 (하나·둘·셋·넷) \| (N·첫·두·세)판 \| 정한 사항 \| 결정(한·된) 사항 \| (미 앞이 아닌) 결정임` 이 1곳 이상. 앞 `%s` 는 자리 경로를 5곳까지(「경로 「낱말」 · …」, 2026-09-26), 뒤 `%s` 는 마지막 자리의 걸린 낱말 — 한 곳이면 경로 하나와 낱말 하나. 주체 없는 결정 명사(「정한 사항」)는 누가·어디서 정했는지 없이 결정 경로를 사실처럼 싣는다 — 「미결정임」·표지 칸 이름 「결정 사항」·「나머지 사항」은 안 건다 | 회차 낱말(「이번 회차」)만이 아니라 **진행 상태 어휘와 구조**가 금지다 — 상태 열(「상태: 정해짐/회신 대기」)·「~ 그대로(변화 없음)」도 같다. 정해진 것은 **사실**로 요지(배경)에, 결정 요청 절에는 **지금 열린 것만**. 회차의 자리는 `history` 하나. 원고는 직전 판의 차분이 아니라 **독자의 0 상태에서 다시 쓴다** — 판마다 「처음 읽는 사람이 이 절에서 무엇을 알아야 하나」를 다시 묻는다(SKILL 3b · readability R11h) |
483
492
  | 절 %s 표 「%s」 제목이 개수로 진행 상태를 세고 있습니다 — 제목은 내용을 말해야 합니다 | `tables[].caption` 이 「…(하나·둘·셋·넷)과 … (하나·둘·셋·넷)」으로 끝난다 | 표 제목은 내용을 말한다 — 「정해진 것 셋과 남은 것 하나」처럼 상태를 세지 않는다(SKILL 3b) |
484
493
  | 권고 낱말이 %d곳에 있습니다(%s) — 결정권이 고객에게 있는 문서는 판단 항목을 물음으로 세우고, FCG 의견은 한 절에만 둡니다 | 정규식 `권고(?![량치]) \| 기각 \| 하지 않기로`(권고량·권고치 같은 값 이름은 빼고) 가 다음 자리에 1곳 이상 — `sections[].name·short·sub·lede·marg(why/key/advice)` · `sections[].tables[].caption·cols` · `summary[].label·lead` · `open_items[].title` · `notice.text` · `meta[][1]` · `parts[].title·lede` · `stats[].label`. 이름에 「의견」이 든 절(FCG 의견 절)과 `cases[]`·`appendix`·`history`·`labels`·`colophon`·`origin` 은 제외. `%s` 는 자리 이름 최대 3개 | 판단 항목은 **물음**으로, 결과는 두 길 대칭 한눈표 한 곳에, FCG 의견은 이름에 「의견」이 든 절 **하나**에만(§12.3b · §12.5b) |
485
494
  | 도판 「%s」 출처 줄에 원본 위치가 없습니다 — 쪽·행·주소·건 § 가운데 하나를 적습니다 | `assets[]` 가운데 `role:"figure"` · `provider:"user-provided"`(고객·제3자 자료에서 낸 도판. 그린 도판 `deterministic`·서버가 그린 삽화 `higgsfield` 는 세지 않음. 파일로 싣는 합성은 `user-provided` 라 센다 — 출처 줄에 건 § 나 부록 N 한 구절(결정 절처럼 건보다 앞에 서는 도판은 부록 N — 뒤 절 § 는 아래 「앞 절이 뒤 절을 가리키는 곳」에 걸린다))이고 보이는 출처 줄 `source_text` 에(`source_url` 은 도판에 그려지지 않아 보지 않는다) 정규식 `\d+\s*쪽 \| \d+\s*행 \| https?:// \| 건 § \| §\s*\d \| 부록\s*\d` 이 하나도 없다. `%s` 는 `id` | 출처 줄은 원본 위치 한 구절(readability R12e). 흐리다·못 연다고 쓰기 전에 원본을 잰다 |
@@ -489,8 +498,16 @@ python3 assets/render.py <report.json>
489
498
  | 앞 절이 뒤 절을 가리키는 곳이 %d개입니다(%s) — 낱말·규칙은 첫 등장 자리에서 풀고, 뒤 절이 앞 절을 되짚는 방향만 둡니다(readability R12h) | 절 i(배열 순서, 1부터)의 산문 — `sections[].name·sub·lede·paragraphs` · `tables[].caption·cols·rows·note` · `flow.caption·note`·`rows[].no`(건 좌표 꼴 `^§?\d{1,2}[-.]\d{1,2}$` 의 앞 숫자)·`cells[].text·note` · `marg(why/key/advice)` · 그 절이 `kind:"cases"` 면 같은 그룹 `cases[].symptom·cause·fix·how_to_check·remaining` · `kind:"asks"` 면 `cta[]`·`open_items[].title·body` · `sections[].plates` 가 가리키는 `assets[].caption·source_text` — 에서 정규식 `§\s*(\d+)\.\d+` 와 `(?<![\d.])(\d{1,2})절` 의 앞 숫자가 i 보다 큰 것을 센다. 「N차 … 제N절」(남의 문서의 절 번호, 예 「검수 보고 4차 제5절」)은 건너뛴다. `summary[].lead·body` 와 `refs[]` 는 세지 않는다 — 요약 근거 게이트(§8)가 `refs` 로 서고 「해당 절」·「근거」 단추가 되므로 뜀이 아니다. `%s` 는 자리와 걸린 낱말 최대 3개 | 낱말·규칙·예시 선언은 **첫 등장 자리에서 한 구절**로 풀고, 뒤 절 번호 대신 그 자리에서 뜻이 서는 말을 둔다. 근거 건은 `refs` 로만. 뒤 절이 앞 절을 되짚는 방향(05절에서 「03절 표」)은 둔다(readability R12h · SKILL 0.5단계 7행) |
490
499
  | 표 「%s」의 「%s」 열은 모든 행이 같습니다 — 차이를 못 나르는 열은 주석으로 | `doc_type` 에 「브리핑」 · `kind:"asks"` 절의 `tables[]` 가운데 행이 셋 이상인 표에서, 첫 열을 뺀 한 열이 모든 행에서 같은 값(빈 칸 아님)이다. asks 밖 절(일정 표 「누가」 열 등)·행 둘 이하·빈 칸뿐인 열은 안 센다 | 그 열을 빼고 ※ 주석 한 줄로(visual-first §5). 선택지를 가르는 열만 한눈표에 둔다(§12.3b 대칭 불변식) |
491
500
  | 건 행 이름 「%s」 낱말이 %d곳에도 쓰였습니다(%s) — 한 낱말은 한 장치에만, 다른 자리는 다른 낱말로 | 데이터가 준 `labels` 의 `symptom`·`cause`·`fix`·`check` 값이 `kind≠cases` 절의 `sub`·`tables[].caption`·`tables[].cols` 에 낱말로 선다(뒤에 조사 하나까지 같은 낱말 — 「판단력」·「판단이다」는 다른 낱말). 괄호 안은 자리 이름 최대 3개. `labels` 를 안 준 기본 이름·건 절 자신·절 이름·도입문·산문은 안 본다 | 의견 절의 열·부제는 「의견」으로(§3.5 · visual-first §3) |
492
- | 선택지를 「길」로 부른 곳이 %d곳 있습니다(%s 「%s」 …) — 「길」은 스킬의 장치 이름입니다. 원고에서는 선택지를 그것이 실제로 무엇인지로 부릅니다 | 글자 자리 전부(과정 어휘와 같은 자리 — `history`·`labels`·`origin` 등 제외)에서 정규식 `RX_GIL`(gazette.py) — 「두 길의」「복제본 길」「고른 길별」「지나는 길과」, 앞말이 선택지를 세는 말인 「어느 길이」「두 길이」, 칸 하나가 「길」인 열 머리는 걸리고 「막대 길이」「길게」「길다」는 안 걸린다 | 선택지는 그것이 실제로 무엇인지(고객이 부르는 이름)로 — 「복제본 전환」「직접 업그레이드」「현행 유지」. 「길」은 스킬 문서 안의 장치 이름일 뿐이다(SKILL 반드시 지키는 것 12) |
493
- | 해라체 명령이 %d곳 있습니다(%s 「%s」 …) — 독자에게 말 거는 자리는 하십시오체, 기록 자리는 개조식입니다. 캡션·확인 방법은 「… 참고」「…에서 확인」처럼 명사형으로 | 글자 자리 전부(「길」과 같은 자리)에서 정규식 `RX_CMD`(gazette.py) — 낱말 끝 「보라·하라·열라·누르라·찾으라·읽으라·두라·말라·가라·보시오·하시오」 뒤에 마침표·공백·닫는 따옴표가 오거나 글이 끝나면 센다. 「보라색」(한글이 이어짐)·「정할 것」(명사구)은 안 센다 | 캡션·확인 방법은 명사형(「… 참고」「…에서 확인」), 독자에게 말 거는 자리는 하십시오체(5a). 경위: 캡션 규칙 「무엇을 보라는 것인지」가 캡션이 됐다(2026-09-17) |
501
+ | 선택지를 「길」로 부른 곳이 %d곳 있습니다(%s 「%s」 …) — 「길」은 스킬의 장치 이름입니다. 원고에서는 선택지를 그것이 실제로 무엇인지로 부릅니다 | 글자 자리 전부(과정 어휘와 같은 자리 — `history`·`labels`·`origin` 등 제외)에서 정규식 `RX_GIL`(gazette.py) — 「두 길의」「복제본 길」「고른 길별」「지나는 길과」, 앞말이 선택지를 세는 말인 「어느 길이」「두 길이」, 칸 하나가 「길」인 열 머리는 걸리고 「막대 길이」「길게」「길다」는 안 걸린다. `%s` 둘은 과정 어휘 행과 같은 꼴(자리 5곳까지 · 마지막 낱말) | 선택지는 그것이 실제로 무엇인지(고객이 부르는 이름)로 — 「복제본 전환」「직접 업그레이드」「현행 유지」. 「길」은 스킬 문서 안의 장치 이름일 뿐이다(SKILL 반드시 지키는 것 12) |
502
+ | 해라체 명령이 %d곳 있습니다(%s 「%s」 …) — 독자가 할 일은 요청 자리에만 하십시오체로, 그 밖은 우리·사물·화면이 주어인 개조식입니다. 확인 방법은 화면이 주어인 결과(「목록을 열면 1초 안에 뜸」), 캡션·Advice 는 볼 자리의 이름으로 | 글자 자리 전부(「길」과 같은 자리)에서 정규식 `RX_CMD`(gazette.py) — 낱말 끝 「보라·하라·열라·누르라·찾으라·읽으라·두라·말라·가라·보시오·하시오」 뒤에 마침표·공백·닫는 따옴표가 오거나 글이 끝나면 센다. 「보라색」(한글이 이어짐)·「정할 것」(명사구)은 안 센다. `%s` 둘은 과정 어휘 행과 같은 꼴(자리 5곳까지 · 마지막 낱말) | 확인 방법은 화면이 주어인 결과, 캡션·Advice 는 볼 자리의 이름(「03절 대조표」), 독자에게 부탁할 것은 요청 자리에서 하십시오체(5·5a). 경위: 캡션 규칙 「무엇을 보라는 것인지」가 캡션이 됐다(2026-09-17) |
503
+ | 대화 이력 낱말이 %d곳에 있습니다(%s) — 독자는 이 문서를 만든 대화를 모릅니다. 합의·요청된 내용을 사실로 적습니다 | 과정 어휘와 같은 글자 자리(`tenant` 도 뺌)에서 정규식 `RX_TALK`(gazette.py) — 지난 대화를 가리키는 앞말 + (띄어쓰기) + 대화에서 끝난 일. 앞말 「앞서·지난번(에)·이전에·이미」 뒤에 「합의·협의」의 끝난 꼴(`_TALK_DONE` — 한·했·된·하신·하였·되었·해 주신·드린·함·됨)이나 「말씀드린·요청하신·안내드린」이 오면 센다. 「논의」의 끝난 꼴은 「지난번(에)·이전에·이미」 뒤에서만 센다 — 「앞서 논의한 바와 같이」는 문서가 제 앞 절을 되짚는 말이다. 앞말 없이 「요청하신 대로」·「말씀하신 대로」도 센다. 안 세는 것: 「앞에서」(문서 안 자리 — 「앞에서 논의한 대로」·「앞에서 말씀드린」), 절차를 말하는 「○○ 전에」·「사전에」·「○○에 앞서」(「적용 전에 협의함」·「적용에 앞서 합의한」), 끝나지 않은 꼴(「협의하여」·「협의 후」·「협의가 필요」·「이미 협의 중」), 앞말 없는 「합의한 일정」. 칸마다 한 번 세고 `%s` 는 「경로 「낱말」」 5곳까지(넘으면 「그 밖 N곳」) | 합의·요청된 내용을 **사실**로 적는다 — 누가 언제 말했는지가 아니라 무엇이 정해졌는지(SKILL 3b) |
504
+ | 책임 낱말 「탓」이 %d곳에 있습니다(%s) — 원인은 대상의 원래 이름과, 그것이 어떻게 동작했는지로 적습니다 | 같은 자리에서 정규식 `RX_BLAME`(gazette.py) — 앞에 한글이 붙지 않은 「탓」 가운데, 뒤가 한글이 아니거나(글 끝·띄어쓰기·문장부호) 조사·서술격 조사로 **시작**하는 것을 센다. 조사 목록은 반려 이름과 같은 낱말 경계 `_JOSA_HEAD` — 「으로·까지·부터·보다·처럼·마다·조차·마저·한테·밖에」와 한 글자 「은·는·이·가·을·를·의·에·께·로·와·과·도·만·나·라·란·랑·인·일·임·입·였·뿐·들」. 첫머리만 보므로 뒤에 무엇이 이어지든(「탓이었습니다」·「탓으로만」) 세고, 자리에는 뒤에 붙은 한글까지 짚는다. 「남탓」(앞에 한글)·「탓하기」·「탓할」(조사 아닌 한글)은 다른 낱말이라 안 센다 | 원인은 대상의 원래 이름과 그것이 어떻게 동작했는지로. 참조 번호 경고를 피하려고 판 번호를 「새 버전 탓」으로 바꿔 적지 않는다 — 판 번호는 이제 참조 번호로 세지 않는다(2026-09-22 반려) |
505
+ | 소속처가 반려한 이름이 %d곳에 있습니다(%s) — 작업하며 지은 이름은 문서 안에서 정의해도 독자가 모릅니다. 독자 화면의 원래 이름으로(%s) | 그 소속처의 반려 이름 목록(소속처 결정 장의 `rejected_names` 칸 — 이름마다 가장 새 결정이 이기고 뒤집힌 결정은 뺀다)이 렌더러에 왔을 때, 보이는 글자 전부(`history`·`labels` 포함 — `uid`·`id`·`plates`·`refs`·`tenant` 와 `origin`·`file` 등은 뺌)에서 이름마다 센다. 목록은 포털 발행·모의 발행이면 서버가(배포 뒤), 로컬 `render.py` 와 발행 직전 훅이면 MCP `list_rejected_names` 로 받아 얹는다 — 목록이 안 오면 이 행은 0 이 아니라 미확인이다(표 아래 「목록을 얻는 길」). 낱말 사이 띄어쓰기는 있든 없든 같은 이름이고, 앞에 한글이 붙거나 뒤에 조사 아닌 한글이 붙으면 다른 낱말이다(「예비판정」). 이름에 `near`(함께 설 낱말)가 있으면 그 낱말이 같은 문장 12자 안에 설 때만 센다. 첫 `%s` 는 원고에 적힌 글자로 자리 5곳까지, 둘째 `%s` 는 「이름 → 목록의 use」 | 0단계 이름 대응표의 독자 화면·문서 이름으로 바꾼다(SKILL 반드시 지키는 것 12). 목록은 그날 반려된 이름만 모은다 — 반려를 `remember_decision` 으로 적을 때 그 결정의 `rejected_names` 칸에 적는다(SKILL 9a단계 7행). 목록 밖의 새 이름은 독자 검토 이름 물음(`names`)과 사람 눈 |
506
+ | 반려 이름 %d개를 대조 규칙으로 못 세웠습니다(%s) — 그 이름은 세지 않았습니다 | 렌더러에 온 목록의 항목이 대조 규칙을 못 세운다 — `pattern`(임의 정규식) 칸이 있거나 `near` 가 8낱말을 넘는다(`%s` 는 「이름: 까닭」). 쓰는 문(서버)이 같은 모양을 거절하고, 손으로 고친 결정 장도 서버가 읽을 때 같은 자로 다시 재어(`wiki_page_rejected_names`) 못 읽은 장으로 거른다(`report_rejected_names_pick` 의 `skipped` — 아래 「결정 장 N곳의 반려 이름 줄을 못 읽었습니다」 행). 그래서 서버가 준 목록(포털 발행·모의 발행, `list_rejected_names` 로 받는 `render.py`·발행 직전 훅)으로는 나지 않고, 렌더러를 포털·`render.py` 없이 직접 불러(`build.py`·`coined_scan()`) cfg 에 목록을 넣은 경우(시험 등)에만 난다 | 흔한 낱말이라 문맥으로 가를 이름은 `pattern` 대신 `near` 에 함께 설 낱말을 적는다 |
507
+ | 결정 장 N곳의 반려 이름 줄을 못 읽었습니다(경로: 까닭 …) — 그 장의 이름은 세지 않았습니다 | **서버**(`report_publish.php report_rejected_names()`) — 그 소속처 결정 장의 앞머리 `rejected_names:` 줄이 JSON 목록이 아니거나 모양이 틀렸다(화면 원문 편집으로 깨진 경우). 발행·모의 발행 경고와 `list_rejected_names` 에 같이 선다 | 그 결정을 `get_case` 로 읽고 `remember_decision` 으로 같은 제목·`expected_revision` 에 다시 적는다 |
508
+ | 소속처 반려 이름을 못 읽었습니다(까닭) — 이 렌더는 반려 이름을 세지 않았습니다 | **서버** — 결정 장 폴더를 못 읽었다(읽기 상한·입출력). 이 줄이 뜬 발행·모의 발행은 반려 이름을 세지 않은 것이다(`rejected_names_checked` 가 null). `list_rejected_names` 는 같은 까닭으로 실패한다 | 잠시 뒤 다시 모의 발행한다 — 되풀이되면 운영 확인 |
509
+ | 소속처 반려 이름이 N개라 가장 새 결정부터 200개만 셉니다(M개는 세지 않았습니다) | **서버** — 그 소속처 결정 장들의 반려 이름(공백을 걷은 글자로 겹침을 뺀 수)이 렌더 입력 상한 200개(`REPORT_REJECTED_MAX`)를 넘는다. 이름마다 가장 새 결정 순으로 200개까지만 렌더러에 넘긴다 | 더는 세지 않을 이름은 그 반려 결정을 같은 제목으로 다시 적어 뺀다(목록이 통째로 바뀌고, 빠진 이름은 응답이 말한다) |
510
+ | 문서의 소속처를 몰라 반려 이름을 세지 않았습니다 | **서버** — 발행·교체하는 문서의 소속처 slug 가 비었다(`rejected_names_checked` 가 null) | 원고로 고칠 것이 없다 — 운영 확인 |
494
511
  | 선택지 기호 %s 에 이름이 %d개입니다(「%s」·「%s」) — 기호는 한 목록에만 붙이고 이름은 표마다 같게 | 모든 표에서 한 축(열 머리 줄, 또는 행 첫 칸 열)마다 `^([A-Z])[\s·:.)]+(\S.*)$` 꼴을 모으고, 그 축의 기호가 A 부터 차례(둘 이상)일 때만 선택지 정의로 친다. 문서 전체에서 한 글자에 이름이 둘 이상이면 센다(짧은 이름과 긴 이름이 갈린 것도). 산문(「방식 A」)·A 부터 차례가 아닌 축(부분을 가리키는 행)·괄호 부연 차이는 안 센다 | A·B·C 는 한 목록에만. 같은 선택지는 표마다 같은 이름(visual-first §3) |
495
512
  | 개발 식별자가 %d곳에 있습니다(%s) — 고객은 조회할 수 없습니다. 커밋·파일·도구·시험 건수는 원고 밖 상태 표에만 | 글자 자리 전부를 주소를 지운 뒤(부록 포함, `history`·`labels`·`origin`·`file`·`source_url`·`href`·`data` 와 `uid`·`id`·`plates`·`refs` 제외) 정규식으로 센다 — 16진 해시(a~f 와 숫자가 섞인 7~64자) · 코드 파일(`.ts`·`.tsx`·`.js`·`.jsx`·`.mjs`·`.py`·`.php`·`.kt`·`.swift`·`.java`) · `src/`·`lib/` · 개발 도구(`tsc`·`eslint`·`prettier`·`jest`·`pytest`·`playwright`·`git`) · `N/N 통과` · 커밋·브랜치·머지·풀 리퀘스트·스테이징. 괄호 안은 「자리 「낱말」」 최대 3개 | 확인 수준은 이용자가 확인할 수 있는 동작으로(content-pipeline §4.2 유형 B). 「나머지」·「머지않아」·「푸시 알림」·숫자만인 번호·날짜·본문 주소·대문자로 시작하는 제품 이름(「Node.js」)은 안 걸린다 |
496
513
  | 자격증명 노출 서술이 %d곳에 있습니다(%s) — 우리 쪽 취급 사실은 문서가 아니라 구두로. 권고 문장만 남기고 경로 서술은 지웁니다 | 글자 자리 전부(개발 식별자와 같은 제외 칸)에서 자격증명 명사(비밀번호·패스워드·암호(「암호화」 제외)·접속/계정/로그인 정보·자격 증명·API/비밀/인증서 키·토큰)와 노출 경로·동사(메신저·카카오톡·카톡·채팅·문자·메일·이메일·슬랙·평문·공유·전달·주고받·오갔·보냈·받았·남겼)가 **한 문장 30자 안에 같이** 선 곳 | 그 문장을 지우고 권고(「교체 권장」)만 남긴다 — 취급 사실은 구두로. 「비밀번호 재설정 기능」·「암호화 키」·「푸시 알림이 문자로」처럼 명사 하나나 경로 하나만 선 문장은 안 걸린다 |
@@ -498,12 +515,34 @@ python3 assets/render.py <report.json>
498
515
  | 번호 뒤 조사가 맞지 않는 곳이 %d개입니다(「%s」 → 「%s」) — 끝 숫자의 소리로 고르거나 {{그림:id\|은}} 으로 | 마지막 번호 뒤 조사(`으로·은·는·이·가·을·를·과·와·로`)가 끝 숫자의 받침과 어긋난다 — 받침 있음 0·1·3·6·7·8, 없음 2·4·5·9, 「으로」는 0·3·6 만 | 조사를 고치거나 `{{그림:id\|은}}` |
499
516
  | 그림 가리킴 토큰 %d곳이 번호로 서지 못합니다(%s) — plates 에 걸린 assets[].id 나 flow:<절 번호>, 조사는 은·이·을·과·으로 짝으로 | `{{그림:…}}` 의 id 가 번호 셈에 없거나(plates 에 안 걸림) 조사가 짝 밖이다. 토큰은 글자 그대로 남는다 | id 를 plates 에 걸거나 조사를 짝 안에서 |
500
517
  | 그림 가리킴 토큰이 번호로 서지 않는 칸에 있습니다(%s · 글자로 %d번 찍힘) — 토큰이 서는 곳은 본문 문단·표 칸·도판 캡션·건 본문처럼 산문으로 그리는 칸입니다 | id·조사가 **맞는** 토큰인데 `t()`·`tr()` 를 안 지나는 칸(§11-6 「안 서는 칸」 — 요지 `lead`·판단 항목 제목·절 부제·표 제목·열 머리·읽는 법 물음·표지·판권지 행·`running_footer`)에 있어 그린 결과에 원문으로 남았다. 그린 결과(본문 + 쪽 아래 머리글)를 훑어 센다 — id·조사가 틀린 토큰은 위 행이 맡으므로 두 번 세지 않는다 | 토큰을 산문 칸으로 옮기거나(본문 문단·표 칸·캡션), 그 자리에는 번호를 안 쓰는 이름으로 적는다 |
518
+ | 없는 판단 항 번호를 %d곳에서 가리킵니다(%s) — {{항:uid}} 로 가리키면 항 순서가 바뀌어도 따라갑니다 | 글자 자리의 `NN-N` 가운데 **asks 절 번호로 시작하는 것**이 `open_items` 셈에 없다. 셈은 **자리(`enumerate`)로 세므로 uid 가 없는 초안도 같다.** 건 번호(`06-13`)·날짜(`2026-09-21`)·그림·표 번호·식별자 꼬리(`ATF01-3`)·「」 인용 안·`history`·`origin` 은 건너뛴다 | `{{항:<uid>}}` 토큰으로(§11-6). 항을 빼거나 옮기면 뒤 번호가 전부 당겨진다 |
519
+ | 판단 항 번호 뒤 조사가 맞지 않는 곳이 %d개입니다(「%s」 → 「%s」) — 끝 숫자의 소리로 고르거나 {{항:uid\|은}} 으로 | 손으로 적은 판단 항 번호 뒤 조사가 끝 숫자의 받침과 어긋난다(짝과 받침 규칙은 그림 번호와 같다). 번호가 당겨지면 조사도 함께 바뀐다 — 「02-6 과」가 「02-4 와」가 된다 | 조사를 고치거나 `{{항:uid\|은}}` |
520
+ | 판단 항 가리킴 토큰 %d곳이 번호로 서지 못합니다(%s) — open_items[].uid 를 적고, 조사는 은·이·을·과·으로 짝으로 | `{{항:…}}` 의 uid 가 `open_items` 에 없거나 조사가 짝 밖이다. asks 절이 없는 문서도 여기 걸린다. 토큰은 글자 그대로 남는다 | `open_items[].uid` 를 그대로 옮겨 적는다 |
521
+ | 판단 항 가리킴 토큰이 번호로 서지 않는 칸에 있습니다(%s · 글자로 %d번 찍힘) — 토큰이 서는 곳은 본문 문단·표 칸·도판 캡션·건 본문처럼 산문으로 그리는 칸입니다 | uid·조사가 **맞는** 토큰인데 `t()`·`tr()` 를 안 지나는 칸(그림 가리킴의 「안 서는 칸」과 같다)에 있어 그린 결과에 원문으로 남았다 | 토큰을 산문 칸으로 옮기거나, 그 자리에는 번호 대신 항의 짧은 이름을 적는다 |
522
+ | 판단 항을 점 꼴로 부른 곳이 %d개입니다(%s) — 화면 표기는 「02-6」 꼴입니다. {{항:uid}} 로 가리키면 표기가 하나로 섭니다 | 「판단 2.6」·「2.6 항」(조사가 붙어도 센다 — 「2.6 항에서·항은·항을」). **실재하는 판단 항 번호일 때만** 센다 — 소수(57.3kg·18.5)·판 문자열(2.3.4)·「항목 2.5」(보통명사)·「1.5 항생제」는 안 센다 | `{{항:<uid>}}` 로 바꾼다 |
523
+ | 없는 절을 이름으로 부른 곳이 %d개입니다(%s) — sections[].name 이나 short 와 같은 글자여야 합니다 | 「○○」 절 꼴로 부른 이름이 어느 절의 `name`·`short` 와도 같지 않다. 「절」 뒤에 **조사나 비-한글 경계**가 와야 절 가리킴으로 본다 — 절차·절반·절실·절충·절판은 건너뛴다 | 절 이름을 바꿨으면 그 이름을 부르던 자리도 함께 고친다 |
524
+ | 근거를 대지 않은 판단 항이 %d개입니다(%s) — refs 에 건 번호를 적거나 본문에서 그림·표를 가리킵니다 | 판단 항에 `refs` 가 비었고 본문이 그림·표(손 번호든 `{{그림:id}}` 든)를 가리키지도 않는다 | 건에서 온 항이면 `refs`, 촬영·대조에서 온 항이면 본문에서 그 그림·표를 가리킨다 |
525
+ | %s 머리의 개수가 %s 인데 실제는 %s 입니다 — count 를 비우면 자동으로 셉니다 | `kind` 가 `cases`·`asks` 인 절의 `count` 에 숫자를 적었는데 건 수·판단 항 수와 다르다. 그 밖의 절(`note` 등)은 무엇을 세는지 렌더러가 모르므로 안 본다 | `count` 를 지운다 — 렌더러가 센 값이 선다 |
526
+ | %s 의 표 「%s」에 %s 가 없어 싣지 못했습니다%s | 표에 제목·행·열 머리 가운데 무엇이든 있는데 `cols` 나 `rows` 가 비어 그릴 수 없다. 열 머리를 `head`·`headers`·`columns`·`header` 로 적었으면 그 이름을 짚는다. **표가 빠지면 뒤 표 번호가 전부 당겨진다** | 열 머리의 이름은 `cols` 다 |
501
527
  | 절 %s 의 plates 배열 순서와 그림 번호가 다릅니다(전폭 「%s」 뒤에 본문 단 「%s」) — 차트·흐름도·본문 단 도판이 먼저 번호를 받습니다 | 한 절 `plates` 에서 `wide:true` 도판 뒤에 본문 단 도판이 적혔다 — 전폭은 그리드 밖이라 번호를 뒤에 받는다 | `plates` 를 본문 단 → 전폭 순서로 적는다 |
502
528
  | 작성일이 한 값이 아닙니다(%s) — 표지 「작성」 · 판권지 「작성」 · 발행 이력 마지막 date 는 공개하는 날 한 값으로(공개 직전 update_report 한 번) | 표지 `meta` 의 「작성」 행 · 판권지 `colophon.rows` 의 「작성」 행(`rows` 가 없으면 표지를 그대로 쓰므로 안 셈) · `history` 마지막 `date` — 셋에서 년·월·일을 0 채움으로 읽어(「2026.08.24」 = 「2026. 8. 24.」, 범위는 첫 날짜) 값이 둘 이상이다. 자리가 둘 미만이면 안 센다. `cover_date` 는 수치의 기준일이라 대조하지 않는다(§3.1) | 공개 직전 `update_report` 한 번으로 세 자리를 공개하는 날로 맞춘다(SKILL 3c · 체크리스트 10단계). 표기를 한 꼴로 맞추는 것은 사람 눈(§3.5) |
503
529
 
504
530
  위 표에서 여섯째 행(과정 어휘)부터 끝까지는 `gazette.py` 의 문구 **그대로**다(`%d`·`%s` 는 수·이름이 들어가는 자리) — 문구를 바꾸면 양쪽을 같이 바꾼다.
505
- 정규식의 `\|` 는 표 칸이라 그렇게 적은 것이고 실제로는 `|` 다. 전부 ⚠ 이며 거절이 아니고, **배포 뒤에는** `dry_run` 과 로컬 `render.py` 두 경로에 다 실린다 — 2026-09-14 에 더한 행은 위 머리의 「운영 반영 확인 전」 목록대로 그 전까지 로컬 `render.py` 에만 있다.
531
+ 예외는 **서버**라고 적은 반려 이름 네 행이다 — 렌더러가 아니라 포털(`report_publish.php report_rejected_names()`·`report_build_checked()`)의 문구라 자리를 `%` 대신 「N」·「M」·「까닭」으로 적었다.
532
+ 정규식의 `\|` 는 표 칸이라 그렇게 적은 것이고 실제로는 `|` 다. 전부 ⚠ 이며 거절이 아니고, `dry_run` 과 로컬 `render.py` 두 경로에 다 실린다.
506
533
  집행자: 렌더러 ⚠ — 「차이를 못 나르는 열」은 `gazette.py Doc.brief_checks()`, 나머지는 `gazette.py Doc.text_scan()`.
534
+ 대화 이력 · 「탓」 · 반려 이름 · 대조 규칙 넷 행은 `text_scan()` 이 부르는 `gazette.py Doc.reader_word_scan()` 이고, 렌더 없이 부르는 `gazette.py coined_scan()` 이 같은 함수라 같은 문장을 낸다(발행 직전 훅이 부르는 자리).
535
+
536
+ **목록을 얻는 길** — 렌더러는 반려 이름 목록을 스스로 찾지 않는다. 부르는 쪽이 cfg 의 `rejected_names` 칸으로 얹고,
537
+ 셋 다 같은 서버 목록(`report_publish.php report_rejected_names()` — 창구 `GET /api/wiki/rejected_names`)이다. 원고에 `rejected_names` 를 적어도 셋 다 버린다.
538
+ - **포털 발행·모의 발행** — 서버가 문서의 소속처 목록을 얹는다. 몇 개로 셌는지는 HTTP 모의 발행 응답의 `rejected_names_checked` 다(null 이면 세지 않음) — MCP 도구 글에는 이 수가 안 실리고, 못 셌으면 위 서버 행의 경고 줄이 실린다.
539
+ - **로컬 `render.py`** — `--tenant <slug>`(없으면 원고의 `tenant`)로 옆 `mcp-call.mjs` 를 띄워 `list_rejected_names`(`format: "json"`)를 부른다.
540
+ 받으면 「소속처 「slug」 반려 이름 N개로 셈」, 못 받으면 ⚠ 「반려 이름을 못 받았습니다(까닭) — 이 렌더는 소속처 「slug」의 반려 이름을 세지 않았습니다」,
541
+ 소속처가 없으면 ⚠ 「소속처(--tenant 또는 원고의 tenant)가 없어 반려 이름을 세지 않았습니다」. 어느 쪽이든 렌더는 끝까지 한다.
542
+ `mcp-call.mjs` 는 자기 자리에서 세 칸 위의 `genetics.mjs`(레포의 MCP)를 띄운다 — 스킬이 레포를 걸어 둔 자리가 아니라 복사본이면 `GENETICS_MCP` 로 그 파일을 알려 준다.
543
+ - **발행 직전 훅**(이 기계의 `~/.claude/hooks/report-gate.py` — 스킬과 함께 배급되지 않고, 설정에 등록됐을 때만 돈다) — 같은 길로 받아 `coined_scan()` 에 얹는다.
544
+ 못 받으면 대화 이력·「탓」만 세고, 경고가 있을 때 「반려 이름 미확인(까닭)」 한 줄을 덧붙인다.
545
+
507
546
  뜻이 같은지(두 낱말이 한 뜻인가)·확인 수준 동사(「반영됨」이 어느 수준인가)는 기계가 못 가른다 — 사람 눈.
508
547
  까닭(실사례 2026-09-11, 고객사 결정 브리핑 — 반려 둘): 문장은 짧아졌는데 권고가 여러 자리에 되풀이됐고,
509
548
  도판 출처 줄이 「원본이 흐림」이라 적혔으나 재 보니 원본은 선명했으며, 독자는 원본을 열 수 없었다.
@@ -571,6 +610,7 @@ CSS 로는 못 한다. 우측 판독부에 남는 것은 문서 전체의 사실
571
610
  | 그림 자체 | `assets[]` 에 `role: "figure"` · `id` · `alt`(접근성 계약) |
572
611
  | 제공자 | `provider: "user-provided"` 이면 **`origin` 이 필수다** — 누가 제공했는지. 비우면 서버가 발행을 거절한다(「출처를 못 적는 자산은 고객 문서에 싣지 않습니다」). 만든 방식(합성·재현·잘라냄·배율·촬영 환경)과 결정 경로도 `origin` 에 적는다 — 렌더되지 않는다 |
573
612
  | 그림 바이트 | `file`(**로컬 절대경로**) 또는 `source`(허용 호스트 https). 둘 다 주면 `file` 이 이긴다 |
613
+ | 영상 | `role: "video"` · `provider: "r2"` — 바이트는 문서에 안 싣는다. `file` 에 로컬 mp4 절대경로를 주면 도구가 올리고 `ref`(영상 자리 번호)를 채운다. **`thumbs`**(시크바 썸네일 판)도 도구가 발행 때 ffmpeg 로 굽는다 — 보통 적지 않는다. `thumbs: null` 이면 판을 뺀다. 되읽은 판을 고쳐 되돌려 보낼 때는 받은 `ref`·`thumbs` 를 그대로 두면 다시 안 올리고 서버가 저장된 판을 잇는다(`mcp/genetics.mjs` 의 영상 갈래 · 렌더러 `gazette.py` 의 `<video data-thumbs…>`) |
574
614
  | 캡션 | `assets[].caption` — 번호 뒤에 붙는다. **번호는 적지 않는다**(자리가 매긴다). **한 문장으로** — 마침표가 둘이면 문장 분리가 캡션을 줄로 가르고 렌더러가 ⚠ 로 센다(부연은 괄호·가운뎃점으로). 정직성은 끝의 한 낱말(「— 도안」·「— 재현」·「— 합성」)이면 된다. 판정 칩은 렌더러가 문장 블록 밖에 붙인다(2026-09-09) |
575
615
  | 출처 | `assets[].source_text` — 독자가 원본에 닿는 **위치 한 구절**(쪽·행·주소·건 § 가운데 하나, readability R12e). 제작 메타는 `origin` 에만, 바깥 원본이 없는 우리 도안·흐름은 비운다 |
576
616
  | 놓을 절 | `sections[].plates: ["그-id", ...]` — **여기 안 적으면 자리가 없어 안 실린다**(⚠). 본문 단 도판을 먼저, 전폭을 뒤에 적는다(순서가 번호와 갈리면 ⚠). 한 건의 근거인 화면은 절이 아니라 **그 건**에 적는다 — `cases[].plates`(11-10절). 건 안은 늘 본문 단 폭이라 전폭 자산은 절에 둔다(건에 두면 ⚠) |
@@ -586,7 +626,7 @@ CSS 로는 못 한다. 우측 판독부에 남는 것은 문서 전체의 사실
586
626
  **그림 번호의 순서**는 절마다 건에 묶인 도판(건 순 · `cases[].plates` 순) → 차트 → 흐름도 → 본문 단 도해 → 본문 단 도판(`plates` 순) → 전폭 도해 → 전폭 도판이다(`gazette.py Doc.fig_order()` 한 벌 —
587
627
  그리는 쪽·가리킴 토큰·번호 대조 ⚠ 가 같은 셈을 읽는다). 표는 따로 센다. 셈은 서버 기준이고 **바이트와 무관**하다 —
588
628
  그림 바이트가 없어 로컬에서 안 그려지는 도판도 자리를 차지하므로 로컬 번호가 운영 번호와 같다. `assets` 에 없는 id 는 세지 않는다.
589
- 본문에서 그림을 가리킬 때는 번호를 손으로 적지 않고 `{{그림:<id>}}` 로 쓴다(§11-6 — ⚠ **운영 반영 확인 전**이라 배포 뒤 열람 DOM 확인 전에는 쓰지 않는다).
629
+ 본문에서 그림을 가리킬 때는 번호를 손으로 적지 않고 `{{그림:<id>}}` 로 쓴다(§11-6).
590
630
 
591
631
  ### 도판의 가독성은 열람 화면에서 재라 (2026-09-08 · 2026-09-10 · 2026-09-14)
592
632
 
@@ -616,7 +656,7 @@ CSS 로는 못 한다. 우측 판독부에 남는 것은 문서 전체의 사실
616
656
 
617
657
  ### 캡션에 무엇을 쓰는가
618
658
 
619
- **이 화면에서 독자가 볼 자리**를 명사형으로 쓴다(「8.0 행의 두 날짜와 8.4 행의 종료일 참고」). 「…을 보라」 같은 해라체 명령은 어디에도 없다 — 렌더러가 ⚠ 로 센다.
659
+ **이 화면에서 독자가 볼 자리**를 명사형으로 쓴다(「8.0 행의 두 날짜와 8.4 행의 종료일」). 「…을 보라」 같은 해라체 명령은 어디에도 없다 — 렌더러가 ⚠ 로 센다.
620
660
  본문이 이미 말한 것을 되풀이하면 도판이 장식이 된다.
621
661
  그리고 `alt` 는 캡션의 사본이 아니다 — 캡션은 「독자가 볼 자리」이고 `alt` 는 「무엇이 보이는가」다.
622
662
 
@@ -713,6 +753,19 @@ CSS 로는 못 한다. 우측 판독부에 남는 것은 문서 전체의 사실
713
753
  **`kind:"status"` 의 `board_title` 대조표와는 다른 것이다.** 저건 **건 전용**이고 확인 강도
714
754
  표식·상세 링크가 붙는다. `tables` 는 kind 와 무관하게 아무 절에나 선다.
715
755
 
756
+ ### 넓은 표는 렌더러가 여백 주석 단까지 넓힌다(2026-09-27)
757
+
758
+ 본문 단(760px)에 안 드는 표는 두 단 그리드 **밖** 전폭 띠(문서 폭 약 1030px)에 선다 — 작성자가 할 일은 없다.
759
+ 렌더러(`gazette.py board_need`)가 열마다 「머리 글자 전체와 칸의 가장 긴 어절 중 큰 것 + 칸 패딩」을 더해 760px 를
760
+ 넘으면 그 표를 띠로 뺀다. 그 뒤 본문은 다시 본문 단으로 돌아가고, 여백 주석은 절 머리에 한 번 선다.
761
+ - 표를 **쪼개거나 열 이름을 줄여 넘침을 피하지 않는다** — 넘침은 렌더러가 받는다. 열 수·칸 12자 규칙(§10)은 그대로 읽기 규칙이다.
762
+ - 두 단은 뷰포트 1100px 이상에서만 선다. 그 아래는 여백 주석이 본문 위 가로 띠가 된다(본문 단이 760px 보다 좁아지는 폭).
763
+ - 좁은 화면(≤719px)에서 표는 카드로 접히고, 칸마다 열 이름이 라벨로 선다.
764
+ - 인쇄(A4)는 여백 주석을 절 머리의 가로 띠로 올리고 본문·표가 쪽 폭 174mm 를 다 쓴다. 인쇄 활자는 본문 10.5pt 다.
765
+ - 왜: 초안 212 에서 표 5장이 본문 단 밖으로 넘쳐 오른쪽 열이 가로 스크롤 뒤로 숨었고, A4 인쇄는 그 열을 잘랐으며,
766
+ 넘친 표 하나 때문에 Chrome 이 문서 전체를 0.67배로 줄여 찍었다(대표 반려 「표가 긴 경우에는 aside영역까지 넓혀서
767
+ 들어가야돼 … A4 인쇄할때는 생각 안해?」).
768
+
716
769
  ### 캡션은 라벨이지 산문이 아니다
717
770
 
718
771
  **20자 안쪽으로 둔다.** 캡션 바는 전폭 색 밴드라 글줄 상한(`--measure`)을 안 받는다 — 길게
@@ -818,14 +871,15 @@ CSS 로는 못 한다. 우측 판독부에 남는 것은 문서 전체의 사실
818
871
  | `{{ok\|보통}}` | `<span class="chip chip--ok">` | 표 셀의 상태. 톤 다섯: `ok`(초록) `wait`(황갈) `bad`(빨강) `info`(파랑) `muted`(회색). 글자 40자 안 |
819
872
  | `[글자](https://…)` | `<a class="ref" href="…" target="_blank" rel="noopener noreferrer">글자</a>` | 출처·원문·관련 문서로 가는 하이퍼링크. 부록 행·표 셀·문단 어디서나 |
820
873
  | `https://…` (맨 주소) | 같은 앵커, 글자는 주소 그대로 | 주소 자체를 보여야 할 때(부록 출처 목록). 꼬리 문장부호 `.,;:!?` 와 닫는 괘·괄호는 링크 밖에 남는다 |
821
- | `{{그림:<assets[].id>}}` ⚠ 운영 반영 확인 전 | `<a class="ref" href="#fig-04-2">그림 4-2</a>` | 본문·표 칸·캡션에서 도판을 가리킬 때. 번호는 §11-1 순서로 렌더러가 매기므로 도판을 옮겨도 따라간다. 손으로 적은 「그림 N-M」이 셈에 없으면 ⚠(§10-1) |
822
- | `{{그림:flow:<절 번호>}}` ⚠ 운영 반영 확인 전 | 그 절 흐름도의 번호 앵커 | 흐름도를 가리킬 때(절 번호는 1부터, `sections` 자리) |
823
- | `{{그림:<id>\|은}}` ⚠ 운영 반영 확인 전 | 번호 + 끝 숫자의 소리로 고른 조사 | 조사 짝은 은/는 · 이/가 · 을/를 · 과/와 · 으로/로. 모르는 id·조사는 글자 그대로 남고 ⚠ 로 센다 |
874
+ | `{{그림:<assets[].id>}}` | `<a class="ref" href="#fig-04-2">그림 4-2</a>` | 본문·표 칸·캡션에서 도판을 가리킬 때. 번호는 §11-1 순서로 렌더러가 매기므로 도판을 옮겨도 따라간다. 손으로 적은 「그림 N-M」이 셈에 없으면 ⚠(§10-1) |
875
+ | `{{그림:flow:<절 번호>}}` | 그 절 흐름도의 번호 앵커 | 흐름도를 가리킬 때(절 번호는 1부터, `sections` 자리) |
876
+ | `{{그림:<id>\|은}}` | 번호 + 끝 숫자의 소리로 고른 조사 | 조사 짝은 은/는 · 이/가 · 을/를 · 과/와 · 으로/로. 모르는 id·조사는 글자 그대로 남고 ⚠ 로 센다 |
877
+ | `{{항:<open_items[].uid>}}` | `<a class="ref" href="#case-02-4">02-4</a>` | 본문·표 칸·여백 주석에서 **판단 항**을 가리킬 때. 번호는 `open_items` 배열 순서로 렌더러가 매기므로 항을 빼거나 옮겨도 따라간다. 흐름도 행의 좌표(`flow.rows[].no`)에도 쓸 수 있다. 손으로 적은 `02-N` 이 셈에 없으면 ⚠(§10-1) |
878
+ | `{{항:<uid>\|은}}` | 번호 + 끝 숫자의 소리로 고른 조사 | 그림 가리킴과 같은 짝. 번호가 당겨지면 조사도 따라 바뀐다(「02-6 과」 → 「02-4 와」). 모르는 uid·조사는 글자 그대로 남고 ⚠ 로 센다 |
824
879
 
825
880
  그림 가리킴 토큰은 `t()`·`tr()` 를 지나는 칸에서만 선다 — **서는 칸**은 절 문단·표 칸·표 ※ 주석·도판 캡션과 출처 줄·건의 다섯 칸·판단 항목 **본문**·요약 문단 `body`·절 도입문·여백 주석·읽는 법 **답**·흐름도 칸·차트 라벨·부록 값·판권지 `note`·발행 이력 `note`·`cta`·`notice.text`·`bar_note` 다.
826
881
  **안 서는 칸**(글자만 이스케이프해 토큰이 원문으로 찍히는 자리)은 표지 칸 전부(`title`·`title_sub`·`kicker`·`client`·`doc_no`·`issuer`·`meta`·`cover_date`)·요지 `label`·요지 `lead`·판단 항목 **제목**·절 `name`·`short`·`sub`·표 제목과 열 머리·읽는 법 **물음**·PART 제목과 간지 물음·수치 칸(`stats`)·건 제목·부록 제목과 행 이름·판권지 표 행·`running_footer` 다.
827
882
  안 서는 칸에 넣은 **유효한** 토큰은 렌더 결과에 남아 ⚠ 로 선다(§10-1 「번호로 서지 않는 칸」). 차트는 가리킴 대상이 아니다(앵커가 `cht-`).
828
- **운영 반영 확인 전**이다: 이 갈래(2026-09-14)의 렌더러에만 있어 배포 전에 발행하면 토큰이 글자로 찍힌다. 배포 뒤 열람 DOM 에서 `a.ref[href^="#fig-"]` 수를 세어 확인하고 표시를 걷는다.
829
883
 
830
884
  막대 차트는 `rows[].tone` 으로 행 색을 준다 — 같은 다섯 이름, 같은 색. 예: 「처리 완료」 행은
831
885
  `muted`, 「개선안이 새로 만드는 구간」은 `bad`.
@@ -1103,14 +1157,14 @@ Submissions·WHO/SUPPORT 정책 브리프(1:3:25)가 **같은 뼈대**를 요구
1103
1157
 
1104
1158
  ```json
1105
1159
  {"title": "골든 지문이 어긋났을 때, 왜 새 결과로 갱신하면 안 되는가?",
1106
- "body": "(답) 참조 구조가 자기 인증이 되기 때문임. … 답이 바로 안 나오면 05절을 다시 읽을 것.",
1160
+ "body": "(답) 참조 구조가 자기 인증이 되기 때문입니다. … 답이 바로 떠오르지 않으면 05절을 다시 읽어 보시기 바랍니다.",
1107
1161
  "refs": ["5.1"]}
1108
1162
  ```
1109
1163
 
1110
1164
  - `title` 은 **완전한 물음문**.
1111
1165
  「골든 지문 관리」 같은 명사구는 심각도를 감춘다.
1112
1166
  - `body` 는 답을 준다 — 시험이 아니라 자기 점검이다.
1113
- 끝에 「N절을 다시 읽을 것」 복습 지침을 단다.
1167
+ 끝에 복습 지침을 단다 — 판단 항목은 요청 자리라 하십시오체다(「N절을 다시 읽어 보시기 바랍니다」, SKILL 반드시 지키는 것 5).
1114
1168
  - `cta` 는 이 문서 전체에서 독자가 지킬 **단 하나의 행동 규칙**이다.
1115
1169
 
1116
1170
  ### 12.5b `open_items` 의 세 번째 용법 — 결정 물음
@@ -1133,21 +1187,22 @@ Submissions·WHO/SUPPORT 정책 브리프(1:3:25)가 **같은 뼈대**를 요구
1133
1187
  독자는 항목 하나만 읽고 두 길 중 하나를 고를 수 있어야 한다(readability R12h · 렌더러 ⚠ §10-1 「앞 절이 뒤 절을 가리키는 곳」).
1134
1188
  `refs` 는 「근거」 단추가 되어 되짚기이지 뜀이 아니다. `§n.n` 은 건·판단 항목 앵커로만 바뀌므로 note 절을 `§5.1` 로 적으면 죽은 링크가 된다(content-pipeline §3). 「권고함」은 여기 쓰지 않는다 —
1135
1189
  의견은 이름에 「의견」이 든 절 한 곳의 몫이다. `priority:"must"` 도 쓰지 않는다 — 「가장 시급」 배지가 한 길을 먼저 판정한다.
1136
- - 요청 자리라 **본문** 종결은 하십시오체다(§14-2). 제목은 개조식 물음(「…할 것인가」)으로 두고 「…하시겠습니까?」로 묻지 않는다(§14-1).
1190
+ - 요청 자리라 **본문** 종결은 하십시오체다(§14-2). 제목은 물음(「…할 것인가」)으로 두고 「…하시겠습니까?」로 묻지 않는다(SKILL 5a 제목 행 · 질문형 꼴은 §14-1 끝).
1137
1191
 
1138
1192
  집행자: 기한 · 문장 수 · 제목의 권고 낱말 · 본문의 뒤 절 번호는 렌더러 ⚠(§10 · §10-1) · 물음 꼴과 두 길 이름은 없음 — 사람의 눈(§12.6b).
1139
1193
 
1140
1194
  **결정 문서의 둘째 할 일 — 확인 요청.** 결정 물음 말고 독자가 해야 할 일이 하나 더 있으면(시험판 화면 확인 등) `open_items` 의
1141
- **두 번째 건**으로 둔다. 제목은 「… 확인 요청」, 본문 첫 문장에 상대 기한(「시험판을 받으신 뒤 7일 안에」)을 넣는다.
1195
+ **두 번째 건**으로 둔다. 제목은 「… 확인 요청」, 본문 첫 문장에 상대 기한(「<독자 화면의 채널 이름> 앱을 받으신 뒤 7일 안에」)을 넣는다.
1196
+ 확인할 앱·환경은 독자 화면에 보이는 채널 이름으로 부른다(SKILL 0단계 물음 5) — 「시험판」 같은 작업 이름은 쓰지 않는다(SKILL 반드시 지키는 것 12).
1142
1197
 
1143
1198
  ```json
1144
- {"title": "시험판에서 고른 도안의 화면 확인 요청",
1145
- "body": "시험판을 받으신 뒤 7일 안에 확인 프로필 표의 네 경우대로 보이는지 확인해 주시기 바랍니다. 표와 다르게 보이면 그 화면을 회신해 주시기 바랍니다.",
1199
+ {"title": "<독자 화면의 채널 이름> 앱에서 고른 도안의 화면 확인 요청",
1200
+ "body": "<독자 화면의 채널 이름> 앱을 받으신 뒤 7일 안에 확인 프로필 표의 네 경우대로 보이는지 확인해 주시기 바랍니다. 표와 다르게 보이면 그 화면을 회신해 주시기 바랍니다.",
1146
1201
  "refs": ["4.2"]}
1147
1202
  ```
1148
1203
 
1149
- - 표지 `meta`·`notice` 에는 사실문으로만 올린다(「시험판 확인은 받으신 뒤 7일 안」). 요청 문장은 이 건 하나에만 둔다.
1150
- - 표지·현황 줄·다른 절에 「확인 부탁드립니다」를 흩어 두지 않는다 — 요청은 요청 절에만(SKILL 5a 「보고 칸에 요청문을 두지 마라」). `guide`(읽는 법)에는 이 건을 가리키는 요청 한 줄을 둘 수 있다(「시험판 확인은 확인 프로필 표의 네 경우로 부탁드립니다」 꼴, 2026-09-15 사용자 결정).
1204
+ - 표지 `meta`·`notice` 에는 사실문으로만 올린다(「<독자 화면의 채널 이름> 앱 확인은 받으신 뒤 7일 안」). 요청 문장은 이 건 하나에만 둔다.
1205
+ - 표지·현황 줄·다른 절에 「확인 부탁드립니다」를 흩어 두지 않는다 — 요청은 요청 절에만(SKILL 반드시 지키는 것 5). `guide`(읽는 법)에는 이 건을 가리키는 요청 한 줄을 둘 수 있다(「<독자 화면의 채널 이름> 앱 확인은 확인 프로필 표의 네 경우로 부탁드립니다」 꼴, 2026-09-15 사용자 결정).
1151
1206
  - 제목이 「… 확인 요청」으로 끝나면 렌더러가 기한 ⚠ 를 「확인 요청 「…」에 기한이 없습니다」로 가른다 — 결정 물음 문구(「결정을 요청한 것이 아닙니다」)로 오분류되지 않는다.
1152
1207
  - 무엇을 확인하면 되는지는 §12.3b 「확인 프로필」 표가 받는다.
1153
1208
 
@@ -1259,16 +1314,16 @@ Submissions·WHO/SUPPORT 정책 브리프(1:3:25)가 **같은 뼈대**를 요구
1259
1314
 
1260
1315
  ---
1261
1316
 
1262
- ## 14. 톤 — 자리가 정한다. 이것부터 바꾼다
1317
+ ## 14. 톤 — 주어와 자리가 함께 정한다. 이것부터 바꾼다
1263
1318
 
1264
- ### 14-1. 기록 자리 — 개조식
1319
+ ### 14-1. 요청 자리 밖 — 우리·사물이 주어인 개조식
1265
1320
 
1266
- 논문·과제·공문 문체다. **우리·사물이 주어인 자리**에만 건다 —
1267
- 요약, 건의 증상·원인·조치·확인 방법, 절 본문, 대조표, 부록, 판권지의 사실 서술.
1321
+ 논문·과제·공문 문체다. 요약, 건의 다섯 칸, 절 본문, 대조표, 부록, 판권지의 사실 서술은 **이 문장만으로** 쓴다 —
1322
+ 여기에는 독자를 주어로 쓰지 않는다(SKILL 반드시 지키는 것 5).
1268
1323
 
1269
1324
  | 쓰지 마라 | 써라 |
1270
1325
  |---|---|
1271
- | 인증서를 설치해 주셔야 합니다 | 인증서 설치 **필요** |
1326
+ | 인증서를 설치해 주셔야 합니다 | (독자가 할 일 — 부탁은 14-2 로. 건에는 사실만: 인증서가 없는 기기에서는 접속이 막**힘**) |
1272
1327
  | 확인했습니다 | 확인**함** |
1273
1328
  | 반영되었습니다 | 반영**됨** |
1274
1329
  | 거부합니다 | 거부하는 **상황** |
@@ -1278,10 +1333,10 @@ Submissions·WHO/SUPPORT 정책 브리프(1:3:25)가 **같은 뼈대**를 요구
1278
1333
  질문형(여백 주석의 Why, 활용법 라벨)은 `~하는가?` / `~인가?`.
1279
1334
  `~합니까?` 아님.
1280
1335
 
1281
- ### 14-2. 요청 자리 — 하십시오체
1336
+ ### 14-2. 요청 자리 — 독자가 할 일, 사실 문장까지 하십시오체
1282
1337
 
1283
- **고객이 주어이거나 실제로 말을 거는 자리**는 종결까지 존댓말이다 —
1284
- `asks` 절의 `open_items`·`cta`, PART I 간지, 판권지의 회신 안내.
1338
+ **고객이 주어인 문장(고객이 할 일)** 은 요청 자리에만 둔다. 요청 자리는 사실 문장까지 모두 종결이 존댓말이다 —
1339
+ `asks` 절의 `open_items`·`cta`, PART I 간지(요청·결정 PART 일 때), 판권지의 회신 안내, 요청 절 항을 부탁하는 읽는 법(`guide`) 한 줄(목록의 정본은 SKILL 5).
1285
1340
 
1286
1341
  | 쓰지 마라 | 써라 |
1287
1342
  |---|---|
@@ -1289,13 +1344,13 @@ Submissions·WHO/SUPPORT 정책 브리프(1:3:25)가 **같은 뼈대**를 요구
1289
1344
  | 회신 주시면 반영하겠**음** | 회신 주시면 **반영하겠습니다** |
1290
1345
  | 열어 주셔야 **함** | 열어 **주시기 바랍니다** |
1291
1346
  | 동의해 주시면 **됨** | 동의 여부를 **회신해 주시기 바랍니다** |
1292
- | 담당하시는 쪽의 확인이 **필요** | 담당하시는 쪽에서 확인해 **주셔야 합니다** |
1347
+ | 담당하시는 쪽의 확인이 **필요** | 담당하시는 쪽에서 확인해 **주시기 바랍니다** |
1293
1348
 
1294
1349
  명사형 종결(`-ㅁ/-음`)은 한국어 상대높임법의 **등급 밖**이라 청자 높임이 0 이다 —
1295
1350
  중립이 아니라 기록체이고, 사람에게 직접 쓰면 하대로 읽힌다. 「부탁드림」도 존대가 아니다.
1296
1351
  문체 관문은 이 자리를 빼고 센다 — 서버 `report_tone.php report_tone_warnings()` 는 `open_items[].body`·`cta`·`colophon.note`·`asks` 절 `marg`,
1297
- 로컬 `tone.mjs` 는 판단 항목 본문과 「」 인용만 뺀다(서버 쪽 자리 가림은 운영 반영 확인 전, §10).
1298
- 그래서 남는 경어체 경고는 **서버에서는 PART I 간지의 요청 문장, 로컬에서는 PART I 간지·`cta`·판권지 회신 안내**다 — 그 자리들은 고치지 않는다(§10 통과 기준 표 · SKILL 「Lim 발행 규칙」).
1352
+ 로컬 `tone.mjs` 는 요청 자리 가운데 넷(판단 항목 본문·`cta`·요청 PART 의 PART I 간지·판권지 회신 안내)과 「」 인용을 뺀다(2026-09-26 · §10).
1353
+ 그래서 남는 경어체 경고는 **서버의 PART I 간지 문장(요청 PART 일 때)과 두 경로의 읽는 법 요청 한 줄**이다 — 둘 다 고치지 않는다(§10 통과 기준 표 · SKILL 「Lim 발행 규칙」).
1299
1354
 
1300
1355
  **「고객 언어로 바꾼다」는 규칙은 그대로다.**
1301
1356
  개조식은 문체이지 내용이 아니다 — 내부 용어를 고객 용어로 바꾸는 일(0단계)을 건너뛰는 핑계가 되지 않는다.
@@ -1346,7 +1401,7 @@ Submissions·WHO/SUPPORT 정책 브리프(1:3:25)가 **같은 뼈대**를 요구
1346
1401
  3. 돌아온 열람 주소를 **실제로 연다** — 절 누락 · 여백 주석 · 표 마감 · 인쇄 미리보기.
1347
1402
  자동 검사가 통과해도 눈으로 연다.
1348
1403
  안 보이는 배지 · 빈 목록 항목 · 낱말 중간 줄바꿈은 눈으로만 잡힌다.
1349
- 4. 톤: `습니다|십시오|합니다|입니다|됩니다|주세요` 가 **기록 자리에** 0건인가(§14-1).
1404
+ 4. 톤: `습니다|십시오|합니다|입니다|됩니다|주세요` 가 **요청 자리 밖에** 0건인가, 그리고 독자가 주어인 문장(「~할 것」「~해야 함」「~면 됨」「~몫임」「확인 필요」「~ 참조」)이 요청 자리 밖에 없는가(§14-1 · SKILL 5 — 기계는 `how_to_check` 의 명령·당위와 해라체까지, 그 밖은 사람 눈).
1350
1405
  그리고 **요청 자리가 하십시오체로 맺는가** — 「부탁드림」·「바람」·「하시면 됨」이 남아 있으면 하대다(§14-2).
1351
1406
  5. 확인 못 한 범위를 적었는가.
1352
1407
 
@@ -1,6 +1,12 @@
1
1
  // 선형 읽기 시험 — 원고를 「앞에서 뒤로 딱 한 번」 읽히고, 독자가 다른 자리로 뛰어야 이해되는 지점을 전부 센다.
2
2
  // 실행: Workflow 도구에 이 파일을 scriptPath 로 주고 args 를 넘긴다.
3
- // args = { file: "<report.json 절대경로>", reader: "<독자 한 줄>", round: "<회차 표지, 예: 2차 측정 · 4판>" }
3
+ // args = { text: "<렌더본 텍스트 절대경로(필수)>", file: "<그 렌더본을 뽑은 report.json 절대경로(필수)>", reader: "<독자 한 줄>", round: "<회차 표지, 예: 2차 측정 · 4판>" }
4
+ // text 는 `python3 assets/render.py <report.json> --text <원고 폴더 밖 빈 폴더>/본문.txt` 로 뽑는다. file 은 렌더본이 원고보다 새로운지 재는 데만
5
+ // 쓰고 읽는 넷에게는 주지 않는다 — 독자가 보는 것은 렌더본뿐이고, 읽는 넷은 CLAUDE.md 를 싣지 않는 Explore 유형으로 돈다(2026-09-26 스킬 검토 —
6
+ // reader-review.workflow.js 머리 주석). 렌더본이 없거나 비었거나 원고보다 오래되면 읽기 전에 멈추고 무엇이 필요한지 말한다(Check — 파일은
7
+ // 에이전트 하나가 명령 한 줄로 재고 판정은 JS 가 한다. 워크플로 스크립트는 파일을 못 연다).
8
+ // 읽는 관점이 렌더본을 못 열었다고 답하면(opened=false) 그 관점은 빼고 head 「검토 미완 — 렌더본 못 엶 N(…)」·failed: true 가 붙는다.
9
+ // 확인 전: MEMORY.md 까지 빠지는지는 첫 실행 전사의 첨부로 확인한다.
4
10
  // round 는 재측정 때 반드시 바꾼다 — 같은 프롬프트면 캐시가 옛 답을 돌려준다(genetics-define 스킬 4단계와 같은 함정).
5
11
  // 돌아오는 것: merged.jumps[](at·needed·severity·fix) 전부 + order_ok/order_note + overall(「뜀 N(3등급 이상 M) · 사소 K」 + 할 일).
6
12
  // 목표: 뜀 0. 3등급(되짚어야 함) 이상이 하나라도 있으면 발행 전에 그 자리에서 푼다.
@@ -13,16 +19,37 @@
13
19
  // 요지 문단 끝의 refs(「근거」 단추). 같은 절 안에서 바로 아래 표로 이어 읽는 것도 뜀이 아니다 — 단 표 칸만으로 뜻이 안 서면 뜀이다.
14
20
  export const meta = {
15
21
  name: 'report-linear-read-test',
16
- description: '보고서 원고를 처음 읽는 사람 셋이 앞에서 뒤로 한 번만 읽으며 「여기서 다른 자리로 뛰어야 이해된다」는 지점을 전부 적고, 결정권자 하나가 요청 절에서 답을 고를 수 있는지 재고, 합쳐서 남은 뜀 지점을 낸다',
17
- phases: [{ title: 'Read', detail: '선형 읽기 3 + 결정권자 1' }, { title: 'Merge', detail: '뜀 지점 합치기' }],
22
+ description: '보고서 렌더본 텍스트(화면에 보이는 글만)를 처음 읽는 사람 셋이 앞에서 뒤로 한 번만 읽으며 「여기서 다른 자리로 뛰어야 이해된다」는 지점을 전부 적고, 결정권자 하나가 요청 절에서 답을 고를 수 있는지 재고, 합쳐서 남은 뜀 지점을 낸다',
23
+ phases: [{ title: 'Check', detail: '렌더본이 있고 비지 않았고 원고보다 새로운지 — 명령 한 줄' }, { title: 'Read', detail: '선형 읽기 3 + 결정권자 1' }, { title: 'Merge', detail: '뜀 지점 합치기' }],
18
24
  }
19
- const FILE = args.file
20
- const READER = args.reader || '고객사 담당(비개발자). 이 보고서를 처음 읽는다'
21
- const ROUND = args.round || '1차 측정'
22
- const CTX = `[${ROUND}] [읽기 전용. 파일을 수정하지 말 것. 한글로 답할 것.]
23
- 원고: ${FILE} — Lim 포털 관보 서식 보고서의 발행 JSON. 렌더 순서는 고정: 표지(title·subtitle) → 발행 이력(history) → 차례 → 활용법(guide) → 요약면(notice·bar·bar_note·stats·grades 등급 범례) → PART 간지(parts[]) → 절(sections[] 순서대로; kind 별로 summary: summary[] 문단(문단 끝 refs 는 「근거」 링크 단추) → tables → flow / note: paragraphs → tables → flow → plates 도판 캡션·출처 줄 / cases: 그 group 의 cases[] 순서대로, 렌더 표기 「NN-1」… / asks: cta → open_items → tables) → 부록(appendix) → 판권지(colophon).
25
+ const A = args || {}
26
+ const MAKE_TEXT = 'python3 assets/render.py <report.json> --text <원고 폴더 밖 빈 폴더>/본문.txt'
27
+ if (!A.text) throw new Error(`args.text 가 필요하다 — 렌더본 텍스트 파일 절대경로. 읽는 넷은 이 파일 하나만 읽는다. 만드는 법: ${MAKE_TEXT}`)
28
+ if (!A.file) throw new Error('args.file 이 필요하다 — 그 렌더본을 뽑은 report.json 절대경로. 렌더본이 원고보다 새로운지 재는 데만 쓰고 읽는 넷에게는 주지 않는다')
29
+ const TEXT = A.text
30
+ phase('Check')
31
+ // 렌더본이 있고 비지 않았고 원고보다 새로운지 — 파일 상태는 에이전트가 옮긴 명령 출력으로만 읽고 판정은 여기서 한다(머리 주석).
32
+ const shq = (p) => `'${String(p).replace(/'/g, `'\\''`)}'`
33
+ const STAT_CMD = `python3 -c 'import json,os,sys; print(json.dumps([dict(f=os.path.isfile(p), b=(len(open(p,"rb").read().strip()) if os.path.isfile(p) else -1), m=(os.path.getmtime(p) if os.path.isfile(p) else -1)) for p in sys.argv[1:]]))' ${shq(TEXT)} ${shq(A.file)}`
34
+ const PRE = { type: 'object', properties: { stdout: { type: 'string', description: '명령의 표준 출력 한 줄 — 글자 그대로' }, exit: { type: 'integer' } }, required: ['stdout', 'exit'] }
35
+ const pre = await agent(`아래 명령을 Bash 로 **한 번만, 글자 그대로** 실행하고 표준 출력 한 줄을 stdout 에 고치지 말고 옮긴다. 종료 코드는 exit 에. 명령을 바꾸거나 다른 명령·파일을 열지 않는다.\n${STAT_CMD}`,
36
+ { label: 'check:files', phase: 'Check', schema: PRE, effort: 'low' })
37
+ let st = null
38
+ try { st = pre && pre.exit === 0 ? JSON.parse(pre.stdout) : null } catch (e) { st = null }
39
+ if (!Array.isArray(st) || st.length !== 2) throw new Error(`시작 전 검사 실패 — 렌더본·원고 파일 상태를 못 읽었다(${pre ? `exit ${pre.exit} · ${String(pre.stdout).slice(0, 200)}` : '검사 에이전트 결과 없음'}). resumeFromRunId 없이 새로 띄운다`)
40
+ if (!st[1].f) throw new Error(`args.file 파일이 없다 — ${A.file}. 렌더본을 뽑은 report.json 절대경로를 준다`)
41
+ if (!st[0].f) throw new Error(`args.text 파일이 없다 — ${TEXT}. 만드는 법: ${MAKE_TEXT}`)
42
+ if (st[0].b <= 0) throw new Error(`args.text 파일이 비었다 — ${TEXT}. 렌더가 실패했을 수 있다 — 다시 뽑고 출력의 ⚠·오류를 본다: ${MAKE_TEXT}`)
43
+ if (st[0].m < st[1].m) throw new Error(`렌더본이 원고보다 오래됐다 — ${TEXT} 가 ${A.file} 보다 ${Math.round(st[1].m - st[0].m)}초 앞선다. 원고를 고친 뒤 다시 뽑는다: ${MAKE_TEXT} (그다음 resumeFromRunId 없이 새로 띄운다)`)
44
+ const READER = A.reader || '고객사 담당(비개발자). 이 보고서를 처음 읽는다'
45
+ const ROUND = A.round || '1차 측정'
46
+ const CTX = `[${ROUND}] [읽기 전용. 파일을 수정하지 말 것. 한글로 답할 것. 철저함: very thorough — 발췌하지 말고 파일 전체를 처음부터 끝까지 읽는다.]
47
+ 읽을 것: ${TEXT} — 보고서 렌더본에서 화면에 보이는 글만 화면 순서대로 뽑은 텍스트(그림·영상 자리는 「[그림]」「[영상]」, 표 칸은 「 | 」). 순서는 표지 → 발행 이력 → 차례 → 활용법(이 문서 읽는 법) → 요약면 → PART 간지 → 절(요지 문단 끝의 「근거」는 링크 단추 · 건은 「NN-1」… · 판단 항목) → 부록 → 판권지.
48
+ 이 파일 하나만 연다. 원고에 없는 지식으로 뜻을 메웠다면 그것 자체가 발견이다. 원고 JSON·같은 폴더의 다른 파일·저장소·메모·다른 문서는 열지 않는다.
49
+ (읽는 관점) 이 파일을 끝까지 읽었으면 opened=true. 열지 못했거나 비어 있으면 opened=false 로 두고 jumps 는 비운 채 overall 에 까닭을 적는다 — 다른 파일로 대신 읽지 않는다.
24
50
  독자: ${READER}
25
- 판정 기준: 앞에서 뒤로 한 번 읽어 내려가면 이해돼야 하고, 어떤 문장도 독자를 다른 절·표·부록·원고 밖 문서로 보내면 안 된다. 뒤 절이 앞 절을 되짚는 링크(도판 출처 줄의 「건 §n.n」)는 뜀이 아니다. 활용법·요약면 수치 칸의 「N절로」는 지도 구실이라 뜀이 아니다.`
51
+ 판정 기준: 앞에서 뒤로 한 번 읽어 내려가면 이해돼야 하고, 어떤 문장도 독자를 다른 절·표·부록·원고 밖 문서로 보내면 안 된다. 뒤 절이 앞 절을 되짚는 링크(도판 출처 줄의 「건 §n.n」)는 뜀이 아니다. 활용법·요약면 수치 칸의 「N절로」는 지도 구실이라 뜀이 아니다.
52
+ 대상·판·시험·상태·선택지를 부르는 낱말마다, 독자가 문서 밖(자기 화면 글자, 원본 문서, 공급사 공식 명칭)에서 본 글자인가? 이 문서가 붙인 이름이면 정의가 있어도 발견이다 — jumps 에 적는다. 처방은 뜻풀이가 아니라 원래 이름으로 바꾸는 것이다 — 원래 이름을 이 파일만으로 알 수 없으면 fix 에 이름을 지어 넣지 말고 「원래 이름으로 바꾼다(이 파일에 없음)」라고 적는다.`
26
53
  const JUMPS = { type: 'object', properties: { jumps: { type: 'array', items: { type: 'object', properties: {
27
54
  at: { type: 'string', description: '읽던 자리(절·문단·항목·표 이름)' },
28
55
  needed: { type: 'string', description: '이해하려면 필요했던 것과 그것이 있는 자리(없으면 「원고 어디에도 없음」)' },
@@ -32,16 +59,25 @@ const JUMPS = { type: 'object', properties: { jumps: { type: 'array', items: { t
32
59
  order_ok: { type: 'boolean', description: '절 순서가 앞에서 뒤로 이야기처럼 이어지는가' },
33
60
  order_note: { type: 'string', description: '순서가 끊기는 곳과 바꿀 순서(있으면)' },
34
61
  overall: { type: 'string' } }, required: ['jumps', 'order_ok', 'order_note', 'overall'] }
62
+ // 읽는 관점은 파일을 열었는지부터 답한다 — 못 연 관점의 「뜀 0」을 「뜀 없음」으로 읽지 않게. 합치기 결과(merged)는 JUMPS 그대로다.
63
+ const JUMPS_READ = { type: 'object', properties: { ...JUMPS.properties, opened: { type: 'boolean', description: '렌더본 파일을 열어 끝까지 읽었으면 true, 못 열었거나 비었으면 false' } }, required: [...JUMPS.required, 'opened'] }
35
64
  phase('Read')
36
65
  const LENSES = [
37
66
  ['first-time-a', '처음 읽는 사람 A — 문서를 위 렌더 순서대로 딱 한 번 읽는다. 앞에서 뒤로 읽다가 「이 낱말/번호/표가 뭐지, 앞에서 봤나, 뒤에 있나」 하고 멈추는 지점을 전부 적는다. 뒤에 있는 것을 앞에서 전제하거나, 앞에 있던 것을 뒤에서 다시 찾아 올라가야 하거나, 정의가 부록·원고 밖(앞 회차 보고)에만 있으면 전부 뜀이다.'],
38
67
  ['first-time-b', '처음 읽는 사람 B — 같은 규칙으로 읽되 표·흐름도·도판 캡션에 집중한다. 표 한 칸·흐름도 칸·캡션·여백 주석(marg)의 낱말이 본문 어디서 정의됐는지, 그 정의가 이 자리보다 앞에 있는지 뒤에 있는지를 판정한다. 뒤에 있으면 뜀이다.'],
39
68
  ['first-time-c', '처음 읽는 사람 C — 절과 절 사이의 이음새만 본다. 각 절의 lede·첫 문단이 바로 앞 절의 무엇을 이어받는지 한 줄로 적고, 이어받지 않고 새로 시작하는 절(맥락 단절)을 뜀으로 적는다. PART 간지의 물음이 그 PART 의 절에서 답해지는지도 본다.'],
40
- ['decider', '결정권자 — 요청 절(kind asks)의 항목만 읽는다(앞 절은 읽었다고 가정). 항목마다 「그 항목 안의 글만으로 두 길 중 하나를 고를 수 있는가」를 판정하고, 고르려면 다른 자리를 봐야 하는 항목을 뜀으로 적는다. 항목 바로 아래의 표는 같은 절 안에서 이어 읽는 것이라 뜀이 아니다 — 단 표 칸만으로 뜻이 안 서는 항이면 뜀이다. 항목과 표가 서로를 전제하면 뜀이다.'],
69
+ ['decider', '결정권자 — 요청 절(회신을 요청하는 판단 항목이 서는 절)의 항목만 읽는다(앞 절은 읽었다고 가정). 항목마다 「그 항목 안의 글만으로 두 길 중 하나를 고를 수 있는가」를 판정하고, 고르려면 다른 자리를 봐야 하는 항목을 뜀으로 적는다. 항목 바로 아래의 표는 같은 절 안에서 이어 읽는 것이라 뜀이 아니다 — 단 표 칸만으로 뜻이 안 서는 항이면 뜀이다. 항목과 표가 서로를 전제하면 뜀이다.'],
41
70
  ]
42
- const reads = (await parallel(LENSES.map(([k, inst]) => () => agent(`${CTX}\n[관점: ${k}] ${inst}\n원고 전체를 읽고 jumps 를 남김없이(0개면 0개) 적고 order_ok 를 정직하게 판정하라.`, { label: `read:${k}`, phase: 'Read', schema: JUMPS })))).filter(Boolean)
43
- log(`선형 읽기 ${reads.length}/${LENSES.length} · 뜀 ${reads.reduce((n, r) => n + r.jumps.length, 0)}`)
44
- if (reads.length < 3) { log('읽기 세션이 셋 미만 — 측정이 아니다. 한도가 풀린 뒤 round 를 바꿔 새로 띄운다'); return { merged: null, raw: reads, failed: true } }
71
+ const res = await parallel(LENSES.map(([k, inst]) => () => agent(`${CTX}\n[관점: ${k}] ${inst}\n이 파일 전체를 읽고 jumps 를 남김없이(0개면 0개) 적고 order_ok 를 정직하게 판정하라.`, { label: `read:${k}`, phase: 'Read', schema: JUMPS_READ, agentType: 'Explore' })))
72
+ const lensFail = LENSES.filter((_, i) => !res[i]).map(([k]) => k)
73
+ const unread = LENSES.filter((_, i) => res[i] && res[i].opened !== true).map(([k]) => k)
74
+ const reads = res.filter((r) => r && r.opened === true)
75
+ log(`선형 읽기 ${reads.length}/${LENSES.length} · 뜀 ${reads.reduce((n, r) => n + r.jumps.length, 0)}${unread.length ? ` · 렌더본 못 엶 ${unread.join(', ')}` : ''}`)
76
+ // 실패는 결과 머리에 적는다(reader-review.workflow.js 와 같은 머리). 렌더본을 못 연 관점이 하나라도 있으면 합쳐도 검토 미완이다.
77
+ const headOf = () => `검토 미완 — ${[lensFail.length && `관점 ${lensFail.length}개 실패(${lensFail.join(', ')})`, unread.length && `렌더본 못 엶 ${unread.length}(${unread.join(', ')})`].filter(Boolean).join(' · ')}`
78
+ if (reads.length < 3) { log('읽기 세션이 셋 미만 — 측정이 아니다. 한도가 풀린 뒤 round 를 바꿔 새로 띄운다'); return { head: headOf(), merged: null, raw: reads, failed: true } }
79
+ const failHead = unread.length ? headOf() : ''
80
+ if (failHead) log(failHead)
45
81
  phase('Merge')
46
82
  const merged = await agent(`${CTX}\n[합치기] 아래 관점들의 뜀 지점을 같은 자리끼리 합치고 severity 합산으로 정렬해 전부 남겨라(상한 없음). 관점 하나만 짚었고 severity 1 인 것은 「사소」로 따로 세어 overall 에 수만 적고 jumps 에서는 빼라. 각 항목의 fix 는 가장 구체적인 것을 고르되 「다른 절을 보라」는 처방은 금지 — 그 자리에 넣을 문장 또는 뺄 문장으로만. order_ok 가 하나라도 false 면 order_note 를 합쳐 바꿀 순서를 하나로 제안하라. overall 첫 줄에 「뜀 N(3등급 이상 M) · 사소 K」를 적고, 이어서 「작성자가 지금 바로 할 일」을 순서대로 5줄 안.\n${JSON.stringify(reads)}`, { label: 'merge', phase: 'Merge', schema: JUMPS })
47
- return { merged, raw: reads }
83
+ return { ...(failHead ? { head: failHead, failed: true } : {}), merged, raw: reads }