genetics-mcp 2.24.0 → 2.26.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.
@@ -7,7 +7,7 @@
7
7
 
8
8
  | 경로 | 무슨 일이 일어나는가 | 근거 |
9
9
  |---|---|---|
10
- | 로컬 렌더 | `cfg.update(t['lim-report'])` 가 `font` 키를 통째로 교체 | `assets/render.py:157-158` |
10
+ | 로컬 렌더 | `cfg.update(t['lim-report'])` 가 `font` 키를 통째로 교체 | `assets/render.py main()` |
11
11
  | 포털 발행 | `font` 가 `REPORT_CLIENT_KEYS` 에 없어 `report_client_fields()` 가 깎아 냄 | `report_publish.php:31-` |
12
12
 
13
13
  그래서 `font.embed: false` 도 안 먹는다 — 교체된 블록에 `embed` 키가 없어 기본값 `True` 로 돌아간다.
@@ -65,7 +65,7 @@ python3 assets/render.py <report.json>
65
65
  `cfg.get('css', ['report.css'])`(`build.py styles()`)로 떨어져 옛 서식 시트가 실린다.
66
66
  스타일 없는 문서가 대비 검사를 통과해 「거짓 초록」이 된다.
67
67
  - 렌더러에 안 넘어가는 칸이 둘 있다: `out`(출력 파일명) · `forbid`(금칙 패턴).
68
- 서버는 `out` 을 스스로 `report.html` 로 박는다(`report_publish.php report_render()`).
68
+ 서버는 `out` 을 스스로 `report.html` 로 박는다(`report_publish.php report_render()`). 로컬 `render.py` 는 원고에 `out` 이 없으면(`get_report` 로 받은 원고) 원고 이름 옆 `.html` 로 쓴다.
69
69
  `forbid` 는 렌더러에 넘기지 않지만 버리지도 않는다 — 입구의 `report_publish.php report_forbid_warnings()` 가 경고로 센다(§10). 로컬 `render.py` 는 `build.py guard()` 가 렌더된 HTML 에 걸리면 멈춘다.
70
70
  - `tenant`(소속처 slug)는 발행 데이터 칸이 아니라 `publish_report` 의 인자다 — 포털 입구는 렌더러 몫에서 깎는다.
71
71
  소속처가 반려한 이름 목록은 그 소속처의 **반려 결정 장**(`remember_decision` 의 `rejected_names` 칸)에 산다. 포털은 발행·모의 발행 때
@@ -82,7 +82,8 @@ python3 assets/render.py <report.json>
82
82
  ```
83
83
 
84
84
  `gazette.py` 의 `build()` 가 이 순서로 조립한다.
85
- 데이터로 바꿀 수 없다.
85
+ 가구 순서는 고정이다. `summary_position:"front"` 를 선택하면 `summary[]` 요지를 앞의 요약면에 함께 싣는다.
86
+ 생략하면 기존 요약면(현황·수치)과 `kind:"summary"` 절의 배치를 유지한다.
86
87
  인쇄하면 표지가 1쪽 단독, PART 간지가 각각 1쪽 단독, 절마다 새 쪽에서 시작한다.
87
88
  인쇄에서 여백 주석은 절 머리의 가로 띠로 서고 본문은 쪽 폭(174mm)을 다 쓴다 — 본문 활자는 10.5pt(2026-09-27, §11-3 「넓은 표」).
88
89
 
@@ -108,6 +109,13 @@ python3 assets/render.py <report.json>
108
109
  `REPORT_CLIENT_KEYS`(`report_publish.php report_client_fields()` 가 이것으로 깎는다)가 화이트리스트다.
109
110
  **여기 없는 키는 조용히 버려진다** — 보낸 쪽은 실린 줄 안다.
110
111
 
112
+ **서버가 검증해 붙이는 근거는 작성 인자가 아니다.** 웹 대화의 보고서 worker가 현재 읽을 수 있는
113
+ 원문을 확인한 뒤 붙이는 `report_evidence` 는 서버 전용 출력이다. 그 안의 `url`·`version` 은 확인한
114
+ 원문 주소와 판의 표시값이며, 호출자가 넣은 값은 원문 검증이나 열람 권한 부여가 되지 않는다.
115
+ `publish_report`·`update_report` 의 원고에는 이 목록을 넣지 않는다 — `report_client_fields()` 가 제거한다.
116
+ 대화 보고서의 `[R1]` 같은 참조는 worker가 실제로 확인한 현재 근거 목록에 속해야 하고, 목록 밖 참조는
117
+ 검증에서 거절된다. 일반 원고의 작성 근거는 아래 `summary[].refs`·`cases[].source_url` 등 해당 자리의 계약을 쓴다.
118
+
111
119
  ### 3.1 표지·머리
112
120
 
113
121
  | 칸 | 뜻 | 없으면 |
@@ -141,7 +149,7 @@ python3 assets/render.py <report.json>
141
149
  | `sections` | 절 목록. 4절 참조 | 본문이 없다 |
142
150
  | `groups` | 건 묶음 `[{key,no,name}]` | `cases` 가 어디에도 안 실린다(**거절**) |
143
151
  | `cases` | 건 목록. 3.5절 참조 | `kind:"cases"` 절이 빈다 |
144
- | `summary` | 요약 문단 `[{lead,body,refs[]}]` | `kind:"summary"` 절이 빈다 |
152
+ | `summary` | 요약 문단 `[{lead,body,refs[]}]`. `summary_position:"front"` 이면 앞 요약면에도 선다 | 요지 부분이 빈다 |
145
153
  | `open_items` | 요청 항목 `[{title,priority,body,refs[]}]` | `kind:"asks"` 절이 빈다 |
146
154
  | `cta` | 요청 절 머리 강조문(문자열 목록) | 강조문이 안 선다 |
147
155
  | `grades` | 확인 등급 사전. 7절 참조 | 대조표 범례와 등급 표식이 안 선다 |
@@ -150,6 +158,7 @@ python3 assets/render.py <report.json>
150
158
 
151
159
  | 칸 | 뜻 | 없으면 |
152
160
  |---|---|---|
161
+ | `summary_position` | `"front"` 를 선택하면 `summary[]` 요지를 문서 앞 요약면에 싣는다. 앞 요약만 필요하면 `kind:"summary"` 절을 따로 두지 않는다 — 둘 다 두면 같은 요지가 두 번 실린다 | 기존 현황·수치 요약면과 `kind:"summary"` 절 배치 유지 |
153
162
  | `notice` | 현황 줄 **객체** `{state,text}` | 현황 줄이 안 선다 |
154
163
  | `bar` | 상태 막대 `[{value,label,tone}]`. 건을 세는 막대면 대기(`wait`) 합은 미결 건 수나 판단 항목 수, 완료 합은 나머지 건 수나 전체 건 수와 같아야 한다 — 어긋나면 렌더러 ⚠(§10). **건이 아닌 다른 것을 세는 막대는 `bar_note` 에 셈 기준을 밝힌다**(그러면 대조하지 않는다) | 막대가 안 선다 |
155
164
  | `bar_alt` | 막대의 대체 텍스트(`role="img"` 의 이름) | 「전체 분포」로 선다 |
@@ -274,7 +283,9 @@ python3 assets/render.py <report.json>
274
283
  | `count_unit` | 배지 단위 | 「건」 |
275
284
  | `board_title` | `kind:"status"` 대조표의 캡션 | `name` 을 쓴다 |
276
285
  | `board_hint` | 캡션 아래 보조 한 줄 | 보조 줄이 빠진다 |
277
- | `paragraphs` | `kind:"note"` 강조 박스의 항목(문자열 목록) | 그 절이 빈다 |
286
+ | `paragraphs` | `kind:"note"` 본문 문자열 목록. 기본은 강조 박스 항목, `presentation:"prose"` 이면 실제 산문 문단. 산문 문단은 최대 120개이며 비어 있지 않은 문자열이어야 한다 | 그 절이 빈다 |
287
+ | `presentation` | `kind:"note"` 에서 `"prose"` 를 선택하면 `paragraphs` 를 `<p>` 문단으로 싣는다. 다른 `kind` 의 장치는 바뀌지 않는다 | 기존 강조 박스·`limit_label` 유지 |
288
+ | `paragraph_kinds` | 산문 문단 역할 목록. `paragraphs` 와 같은 길이·순서로 `fact`·`context`·`analysis`·`proposal`·`unknown` 을 둔다. `analysis` 는 「해석」, `proposal` 은 「제안」, `unknown` 은 「추가 확인 필요」를 표시하고 `fact`·`context` 는 별도 표식 없이 읽힌다. 역할은 사실 검증이나 열람 권한을 대신하지 않는다 | 역할 표식 없음 |
278
289
  | `limit_label` | 강조 박스의 머리 글자 | `name` 을 쓴다 |
279
290
  | `note` | `kind:"status"` 에서 표 아래 한 줄 | 그 줄이 빠진다 |
280
291
  | `flow` | 의존 흐름도 한 개(11-2절). `kind` 와 무관하다 | 도해가 빠진다 |
@@ -292,6 +303,10 @@ python3 assets/render.py <report.json>
292
303
  예시 데이터가 `"no": "01"` 을 적고 있는 것은 사람이 읽기 위한 것이다 —
293
304
  **순서와 다르게 적으면 문서와 어긋난 원고가 된다.**
294
305
 
306
+ `kind:"note", presentation:"prose"` 문단도 이 순번을 쓴다. 1번째 절의 2번째 문단은
307
+ `case-01-2` 앵커가 되고 요약의 `refs:["1.2"]`·`§1.2` 가 같은 실제 문단을 가리킨다.
308
+ 그 문단을 사례로 다시 만들 필요가 없다. 절이나 문단 순서를 바꾸면 참조도 함께 고친다.
309
+
295
310
  ### ⚠ `groups[].no` 는 그 그룹을 싣는 절의 순번과 같아야 한다
296
311
 
297
312
  `§n.n` 마커의 앞 숫자가 **두 곳에서 다른 뜻**이다.
@@ -316,10 +331,11 @@ python3 assets/render.py <report.json>
316
331
  | `cases` | 건 필드표(증상·원인·조치·확인 방법·미결·원본) | 그 절의 `group` 에 속한 `cases[]` |
317
332
  | `status` | 대조표(4괘선 + 확인 강도 표식) | **전체** `cases[]` · 절의 `board_title`·`board_hint`·`note` |
318
333
  | `summary` | 요지 블록(리드 + 본문 + 해당 절) | `summary[]` |
319
- | `note` | 강조 박스(미확인 사항 등) | 절의 `paragraphs[]` · `limit_label` |
334
+ | `note` | 기본은 강조 박스(미확인 사항 등), `presentation:"prose"` 를 선택하면 산문 문단 | 절의 `paragraphs[]` · 기본 박스의 `limit_label` · 산문의 `paragraph_kinds[]` |
320
335
 
321
336
  `asks` 와 `summary` 는 **문서에 하나만 두는 것이 맞다** — 둘 다 최상위 목록을 통째로 그린다.
322
337
  둘을 두 절에 걸면 같은 목록이 두 번 실린다.
338
+ `summary_position:"front"` 도 같은 요지를 앞에 추가하므로, 앞 요약만 원하는 문서에는 `kind:"summary"` 절을 두지 않는다.
323
339
 
324
340
  ---
325
341
 
@@ -369,7 +385,9 @@ python3 assets/render.py <report.json>
369
385
 
370
386
  ## 8. 요약 근거 게이트 — 발행이 **거절**된다
371
387
 
372
- `summary[]` 의 문단마다 근거로 삼은 **사례 번호**를 최소 하나 밝혀야 한다.
388
+ `summary[]` 의 문단마다 근거로 삼은 **실제 사례 번호 또는 산문 문단 번호**를 최소 하나 밝혀야 한다.
389
+ 산문 문단은 `kind:"note", presentation:"prose"` 절의 비어 있지 않은 `paragraphs[]` 항목이다.
390
+ 그 번호는 절·문단의 배열 순번이며 렌더러 앵커와 같다(4절).
373
391
 
374
392
  - 두 검사가 짝이다: **참조 존재**(문단마다 하나 이상) · **참조 실재**(그 번호가 실제로 있어야).
375
393
  - 두 모양을 다 센다: `refs: ["4.1"]` 과 본문 안 `§4.1`.
@@ -396,7 +414,7 @@ python3 assets/render.py <report.json>
396
414
  | `cases[].group` 이 없는 그룹 | 그 건이 어디에도 안 실린다 |
397
415
  | `sections[].part` 가 없는 PART 번호 | 조용히 첫 묶음으로 자리를 옮긴다 |
398
416
  | `summary` 문단에 근거 표기 0개 | 독자가 근거를 찾아 헤맨다 |
399
- | `summary` 가 없는 사례 번호를 근거로 댐 | 같음 |
417
+ | `summary` 가 없는 사례·산문 문단 번호를 근거로 댐 | 같음 |
400
418
  | `sections`·`cases`·`summary` 가 목록이 아님 | `{}` 를 보내면 뒤에서 날 TypeError 가 샌다 |
401
419
  | `history`·`guide`·`parts`·`appendix`·`bar` 가 목록이 아님 | 같음 |
402
420
  | `notice`·`colophon` 이 객체가 아님 | 같음 |
@@ -420,7 +438,7 @@ python3 assets/render.py <report.json>
420
438
  | 열린 검토 회신 #N(이름 · 날짜) 「회신 본문」 — 집은 문장 「…」이 이 원문에 없습니다 — 원문이 바뀜 / 문장을 잃어 상위로 / 자리를 잃음 | `update_report` 만. **아직 반영 표시가 안 된** 회신이 집은 문장이 새 원문에 없다(3.5절 uid 항) — 이 판이 그 회신을 반영한 것이면 발행 뒤 `resolve_note(id, reply, rev)`, 아니면 회신 본문부터 읽는다(SKILL 9b). 핀을 살리려고 문장을 되돌리지 않는다 |
421
439
  | 절 …의 도판 「…」를 assets 에서 못 찾았습니다 | `plates` 가 없는 `assets[].id` 를 가리킨다(건 `plates` 면 「건 NN-n 의 도판」) |
422
440
  | 건 NN-n 에 묶인 도판 「…」는 전폭(wide)인데 건 안이라 본문 단 폭으로 섭니다 | `cases[].plates` 가 `wide:true` 자산을 가리킨다 — 건 안은 두 단 그리드라 전폭이 못 선다. 절 `plates` 로 옮기거나 `wide` 를 끈다(11-10절) |
423
- | 도판 「…」에 그림 바이트가 없습니다 | `file`·`source` 없이 이름만 왔다. 로컬 `render.py` 는 `file` 을 바이트로 굳히지 않아 `file` 이 있어도 뜬다 — 로컬에서는 이 줄을 빼고 나머지 ⚠ 를 읽는다 |
441
+ | 도판 「…」에 그림 바이트가 없습니다 | `file`·`source` 없이 이름만 왔다. 로컬 `render.py` 는 `file` 을 바이트로 굳히지 않아 `file` 이 있어도 뜬다 — 로컬에서는 이 줄을 빼고 나머지 ⚠ 를 읽는다. `get_report` 로 받은 표식(`(생략 — NB)`)은 치수가 있고 `file`·`source` 가 없으면 로컬 `render.py kept_stand_ins()` 가 서버 기록 치수의 자리 상자로 그리고 ⚠ 대신 「보관 그림 N장」 한 줄로 센다(원고에 `out` 이 있으면 — 받은 판이 아니라 작성 중인 원고라는 뜻 — 그 줄이 ⚠ 로 선다) — 그 HTML 은 검토본이다 |
424
442
  | `chapters`·`figure`(**최상위 칸**) 는 자리가 없어 | 옛 서식 전용 칸. `assets[].role:"figure"` 와 다른 것이다 |
425
443
  | 절 …의 `principles`/`logic` 은 자리가 없어 | 같음 |
426
444
  | `wide`/`marg_width` 값이 길이 단위가 아니라 | CSS 값 검사 |
@@ -454,11 +472,11 @@ python3 assets/render.py <report.json>
454
472
  |---|---|---|
455
473
  | 「고객사 로고가 SVG 로 등록돼 있어 표지에 못 싣습니다」·「표지용 파생본이 아직 없습니다」 | 소속처 로고 설정의 상태다 | 소속처 관리 화면(사람) |
456
474
  | 「바깥에서 받아 오는 것이 있습니다」 — 그 호스트가 **본문 링크**(`[글자](주소)`·맨 주소·건 `source_url`)뿐일 때 | 입고 검사가 `<a href>` 호스트까지 센다(§11-6). 누르는 링크는 받아 오는 자원이 아니다. `<img src>` 등으로 뜨면 작성자 몫 | 서버 검사 수리 전까지 사람이 호스트 목록을 대조 |
457
- | 경어체 경고 — PART I 간지(`parts[]` — PART I 이 요청·결정 PART 일 때)의 문장이나 읽는 법(`guide`)의 요청 한 줄에서만 날 때 | 둘 다 요청 자리(§14-2 · §12.5b)인데 서버 문체 관문은 둘 다, 로컬 `tone.mjs` 는 읽는 법 한 줄을 센다 | 사람이 자리를 확인 |
475
+ | 경어체 경고 — 읽는 법(`guide`)의 요청 한 줄에서만 날 때 | 요청 자리(§12.5b)인데 자리로 못 가려 두 문체 관문 모두 센다 | 사람이 자리를 확인 |
458
476
  | 레포의 렌더러·서버 관문이 운영보다 앞선 동안, 새 규칙대로 고친 자리에 운영 `dry_run` 이 옛 규칙으로 내는 경고 | 운영은 배포된 판으로 센다 — 되돌리면 새 규칙에 걸리고, 배포되면 사라진다 | 새 판에서 0 인지 먼저 잰다 — 렌더러 경고는 레포 작업본을 부르는 로컬 `render.py`, 서버 문체 관문·입고 검사·서식 등록부 칸은 레포 개발 스택의 `dry_run`(절차: genetics lim `cases/dev-스택이-OrbStack-덧판https-base_url으로-떠-있으면-…`). 0 이면 핸드오프에 「배포 뒤 해소」 한 줄 |
459
477
 
460
- 요청 절의 경어체는 이제 예외가 아니다. 서버 문체 관문(`report_tone.php report_tone_warnings()`)은 요청 자리(`open_items[].body`·`cta`·
461
- `colophon.note`·`asks` 절 `marg`)와 「」 인용을 빼고 세고, 로컬 `tone.mjs` 는 요청 자리(판단 항목 본문·`cta`·PART I 간지·판권지 회신 안내)와 「」 인용을 뺀다(2026-09-26).
478
+ 요청 절의 경어체는 이제 예외가 아니다. 두 문체 관문(서버 `report_tone.php report_tone_warnings()`·로컬 `tone.mjs`)은 같은 요청 자리(`open_items[].body`·`cta`·
479
+ `colophon.note`·요청 PART 의 PART I 간지 도입문·물음)와 「」 인용을 빼고 센다(2026-09-29 — `asks` 절 `marg`·PART 제목은 센다).
462
480
  이 표의 셋째 행 밖에서 경어체 경고가
463
481
  뜨면 요청 자리 밖이나, 판단 항목 `title`(명사형 「~ 요청」·물음이라 서버가 경어체를 세는 칸)이나, `guide` 에 요청 문장이 샌 것이다 — 요청 문장은 요청 절로 옮긴다(SKILL 반드시 지키는 것 5 — 요청 자리 밖에는 독자를 주어로 쓰지 않는다). 단 `guide` 에서 요청 절의 확인 요청을 부탁하는 한 줄은 허용한다(2026-09-15 사용자 결정) — 그 한 줄에서만 나는 경어체 경고는 §10 통과 기준 표의 행으로 넘긴다.
464
482
  건의 `how_to_check` 에서 걸린 명령형은 「보고 칸에 요청문이 N곳 있습니다 — 요청 절(open_items)로 옮깁니다」로 따로 뜬다.
@@ -495,7 +513,7 @@ python3 assets/render.py <report.json>
495
513
  | 도판 「%s」 출처 줄에 제작 설명이나 그림 번호가 있습니다(「%s」) — 위치 한 구절만, 제작 기록은 origin 으로 | `role:"figure"` 전부(provider 무관)의 `source_text` 를 주소를 지운 뒤 정규식 `(합성·재현)(이며·임·함·한 것·했) \| 예시(이며·임·라) \| (실제·실기기) 화면이 아님 \| 도안이 아님 \| 자리 표시 \| 규격(과 문구)?의 출처 \| 찍(음·은 것) \| 배율 \| N배 밀도 \| 크롭 \| 잘라 \| 자름 \| 그림 N-N` 으로 센다. **캡션은 보지 않는다** — 캡션의 한 낱말 「— 합성」·「재현」·「갈무리」는 정직성 이름표다. 출처 줄에서도 맨 낱말 「합성」·「재현」은 안 걸고 서술형만 건다. 「골밀도」 같은 값 이름은 「N배 밀도」가 아니라 안 걸린다 | 출처 줄에는 위치 한 구절만. 만든 방식·결정 경로는 `origin`(렌더 안 됨)과 도판 HTML 머리 주석에. 바깥 원본이 없는 우리 도안·흐름은 출처 줄을 비운다(readability R12e) |
496
514
  | 표 「%s」 주석이 %d문장입니다 — ※ 주석은 %d문장 안 | `tables[].note` 문장 3 이상(둘째 `%d` 는 상한 2). 문장은 렌더러가 문장 블록을 세우는 판별(`boundary.py` — 날짜 마침표 「2026. 9. 30.」·「9. 8.」은 경계가 아님)로 센다. | 두 문장 안으로, 나머지는 본문·건으로(readability R12a) |
497
515
  | 판단 항목 「%s」 본문이 %d문장입니다 — %d문장 안 | `open_items[].body` 문장 5 이상(둘째 `%d` 는 상한 4 · 자수 220 은 §10 의 R11f 행이 따로 센다) | 기한과 두 길 이름 → 길마다 달라지는 것 → 근거 절 번호까지 네 문장. 상세는 건에(readability R12b) |
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행) |
516
+ | 앞 절이 뒤 절을 가리키는 곳이 %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` 과 건에 묶인 표 `cases[].tables[].caption·cols·rows·note` · `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행) |
499
517
  | 표 「%s」의 「%s」 열은 모든 행이 같습니다 — 차이를 못 나르는 열은 주석으로 | `doc_type` 에 「브리핑」 · `kind:"asks"` 절의 `tables[]` 가운데 행이 셋 이상인 표에서, 첫 열을 뺀 한 열이 모든 행에서 같은 값(빈 칸 아님)이다. asks 밖 절(일정 표 「누가」 열 등)·행 둘 이하·빈 칸뿐인 열은 안 센다 | 그 열을 빼고 ※ 주석 한 줄로(visual-first §5). 선택지를 가르는 열만 한눈표에 둔다(§12.3b 대칭 불변식) |
500
518
  | 건 행 이름 「%s」 낱말이 %d곳에도 쓰였습니다(%s) — 한 낱말은 한 장치에만, 다른 자리는 다른 낱말로 | 데이터가 준 `labels` 의 `symptom`·`cause`·`fix`·`check` 값이 `kind≠cases` 절의 `sub`·`tables[].caption`·`tables[].cols` 에 낱말로 선다(뒤에 조사 하나까지 같은 낱말 — 「판단력」·「판단이다」는 다른 낱말). 괄호 안은 자리 이름 최대 3개. `labels` 를 안 준 기본 이름·건 절 자신·절 이름·도입문·산문은 안 본다 | 의견 절의 열·부제는 「의견」으로(§3.5 · visual-first §3) |
501
519
  | 선택지를 「길」로 부른 곳이 %d곳 있습니다(%s 「%s」 …) — 「길」은 스킬의 장치 이름입니다. 원고에서는 선택지를 그것이 실제로 무엇인지로 부릅니다 | 글자 자리 전부(과정 어휘와 같은 자리 — `history`·`labels`·`origin` 등 제외)에서 정규식 `RX_GIL`(gazette.py) — 「두 길의」「복제본 길」「고른 길별」「지나는 길과」, 앞말이 선택지를 세는 말인 「어느 길이」「두 길이」, 칸 하나가 「길」인 열 머리는 걸리고 「막대 길이」「길게」「길다」는 안 걸린다. `%s` 둘은 과정 어휘 행과 같은 꼴(자리 5곳까지 · 마지막 낱말) | 선택지는 그것이 실제로 무엇인지(고객이 부르는 이름)로 — 「복제본 전환」「직접 업그레이드」「현행 유지」. 「길」은 스킬 문서 안의 장치 이름일 뿐이다(SKILL 반드시 지키는 것 12) |
@@ -525,7 +543,7 @@ python3 assets/render.py <report.json>
525
543
  | %s 머리의 개수가 %s 인데 실제는 %s 입니다 — count 를 비우면 자동으로 셉니다 | `kind` 가 `cases`·`asks` 인 절의 `count` 에 숫자를 적었는데 건 수·판단 항 수와 다르다. 그 밖의 절(`note` 등)은 무엇을 세는지 렌더러가 모르므로 안 본다 | `count` 를 지운다 — 렌더러가 센 값이 선다 |
526
544
  | %s 의 표 「%s」에 %s 가 없어 싣지 못했습니다%s | 표에 제목·행·열 머리 가운데 무엇이든 있는데 `cols` 나 `rows` 가 비어 그릴 수 없다. 열 머리를 `head`·`headers`·`columns`·`header` 로 적었으면 그 이름을 짚는다. **표가 빠지면 뒤 표 번호가 전부 당겨진다** | 열 머리의 이름은 `cols` 다 |
527
545
  | 절 %s 의 plates 배열 순서와 그림 번호가 다릅니다(전폭 「%s」 뒤에 본문 단 「%s」) — 차트·흐름도·본문 단 도판이 먼저 번호를 받습니다 | 한 절 `plates` 에서 `wide:true` 도판 뒤에 본문 단 도판이 적혔다 — 전폭은 그리드 밖이라 번호를 뒤에 받는다 | `plates` 를 본문 단 → 전폭 순서로 적는다 |
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) |
546
+ | 작성일이 한 값이 아닙니다(%s) — 표지 「작성」 · 판권지 「작성」 · 발행 이력 마지막 date 는 공개하는 날 한 값으로(공개 직전 update_report 한 번) | 표지 `meta` 의 「작성」 행 · 판권지 `colophon.rows` 의 「작성」 행(`rows` 가 없으면 표지를 그대로 쓰므로 안 셈) · `history` 마지막 `date` — 셋에서 년·월·일을 0 채움으로 읽어(「2026.08.24」 = 「2026. 8. 24.」, 범위는 첫 날짜) 값이 둘 이상이다. 자리가 둘 미만이면 안 센다. `cover_date` 는 수치의 기준일이라 대조하지 않는다(§3.1) | 공개 직전에 한 번, 세 자리를 공개하는 날로 맞춘다 — SKILL 8단계 「수정 절차」로(받은 판에서 출발 · `body_rev` 를 실어 교체 — SKILL 3c · 체크리스트 10단계). 표기를 한 꼴로 맞추는 것은 사람 눈(§3.5) |
529
547
 
530
548
  위 표에서 여섯째 행(과정 어휘)부터 끝까지는 `gazette.py` 의 문구 **그대로**다(`%d`·`%s` 는 수·이름이 들어가는 자리) — 문구를 바꾸면 양쪽을 같이 바꾼다.
531
549
  예외는 **서버**라고 적은 반려 이름 네 행이다 — 렌더러가 아니라 포털(`report_publish.php report_rejected_names()`·`report_build_checked()`)의 문구라 자리를 `%` 대신 「N」·「M」·「까닭」으로 적었다.
@@ -539,7 +557,7 @@ python3 assets/render.py <report.json>
539
557
  - **로컬 `render.py`** — `--tenant <slug>`(없으면 원고의 `tenant`)로 옆 `mcp-call.mjs` 를 띄워 `list_rejected_names`(`format: "json"`)를 부른다.
540
558
  받으면 「소속처 「slug」 반려 이름 N개로 셈」, 못 받으면 ⚠ 「반려 이름을 못 받았습니다(까닭) — 이 렌더는 소속처 「slug」의 반려 이름을 세지 않았습니다」,
541
559
  소속처가 없으면 ⚠ 「소속처(--tenant 또는 원고의 tenant)가 없어 반려 이름을 세지 않았습니다」. 어느 쪽이든 렌더는 끝까지 한다.
542
- `mcp-call.mjs` 는 자기 자리에서 세 칸 위의 `genetics.mjs`(레포의 MCP)를 띄운다 — 스킬이 레포를 걸어 둔 자리가 아니라 복사본이면 `GENETICS_MCP` 로 그 파일을 알려 준다.
560
+ MCP 서버는 `mcp-call.mjs` 가 찾아 띄운다 — 저장소 안 스킬이면 저장소의 `genetics.mjs`, 복사본으로 깔린 스킬이면 게시본이다(찾는 순서는 그 파일 머리 주석).
543
561
  - **발행 직전 훅**(이 기계의 `~/.claude/hooks/report-gate.py` — 스킬과 함께 배급되지 않고, 설정에 등록됐을 때만 돈다) — 같은 길로 받아 `coined_scan()` 에 얹는다.
544
562
  못 받으면 대화 이력·「탓」만 세고, 경고가 있을 때 「반려 이름 미확인(까닭)」 한 줄을 덧붙인다.
545
563
 
@@ -1348,9 +1366,8 @@ Submissions·WHO/SUPPORT 정책 브리프(1:3:25)가 **같은 뼈대**를 요구
1348
1366
 
1349
1367
  명사형 종결(`-ㅁ/-음`)은 한국어 상대높임법의 **등급 밖**이라 청자 높임이 0 이다 —
1350
1368
  중립이 아니라 기록체이고, 사람에게 직접 쓰면 하대로 읽힌다. 「부탁드림」도 존대가 아니다.
1351
- 문체 관문은 이 자리를 빼고 센다 — 서버 `report_tone.php report_tone_warnings()` 는 `open_items[].body`·`cta`·`colophon.note`·`asks` 절 `marg`,
1352
- 로컬 `tone.mjs` 는 요청 자리 가운데 넷(판단 항목 본문·`cta`·요청 PART 의 PART I 간지·판권지 회신 안내)과 「」 인용을 뺀다(2026-09-26 · §10).
1353
- 그래서 남는 경어체 경고는 **서버의 PART I 간지 문장(요청 PART 일 때)과 두 경로의 읽는 법 요청 한 줄**이다 — 둘 다 고치지 않는다(§10 통과 기준 표 · SKILL 「Lim 발행 규칙」).
1369
+ 두 문체 관문(서버 `report_tone.php report_tone_warnings()`·로컬 `tone.mjs`)은 이 자리 가운데 넷(판단 항목 본문·`cta`·판권지 회신 안내·요청 PART 의 PART I 간지 도입문·물음)과 「」 인용을 빼고 센다(2026-09-29 · §10).
1370
+ 그래서 남는 경어체 경고는 **읽는 법 요청 한 줄**뿐이다 — 고치지 않는다(§10 통과 기준 표 · SKILL 「Lim 발행 규칙」).
1354
1371
 
1355
1372
  **「고객 언어로 바꾼다」는 규칙은 그대로다.**
1356
1373
  개조식은 문체이지 내용이 아니다 — 내부 용어를 고객 용어로 바꾸는 일(0단계)을 건너뛰는 핑계가 되지 않는다.
@@ -228,8 +228,8 @@ RANK_TAG = ['live', 'data', 'done', 'soon'] # 배지 종류
228
228
 
229
229
  요청 자리 밖에서 문체를 안 바꾸면 낱말이 아니라 **조판의 문제**로 남는다 — 경어체 서술문은
230
230
  관보 문법 위에서 겉돈다. 반대로 요청 자리에서 개조식을 고집하면 **하대로 읽힌다** — 이쪽이
231
- 거듭 반려된 자리다(SKILL 5·5a · 경위 history.md §5). 서버 `report_tone.php report_tone_warnings()` 는 요청 자리(`open_items` 본문·`cta`·판권지 `note`·`asks` 절 여백 주석)와 「」 인용을 안 세고,
232
- 로컬 `tone.mjs` 는 요청 자리 가운데 넷(판단 항목 본문·`cta`·요청 PART 의 PART I 간지·판권지 회신 안내)과 「」 인용을 뺀다(2026-09-26). 읽는 법(`guide`)의 요청 한 줄은 두 경로 모두 센다. 그래서 남는 경어체 경고는 서버의 PART I 간지(요청 PART 일 때) 문장과 두 경로의 읽는 법 요청 한 줄이고, 둘 다 의도이므로 **사람이 걸러 읽고 그대로 둔다**.
231
+ 거듭 반려된 자리다(SKILL 5·5a · 경위 history.md §5). 두 문체 관문(서버 `report_tone.php report_tone_warnings()`·로컬 `tone.mjs`)은 같은 요청 자리 넷(판단 항목 본문·`cta`·판권지 회신 안내·요청 PART 의 PART I 간지 도입문·물음)과 「」 인용을 뺀다(2026-09-29).
232
+ 읽는 법(`guide`)의 요청 한 줄은 두 관문 모두 센다 — 남는 경어체 경고가 그 한 줄뿐이면 의도이므로 **사람이 걸러 읽고 그대로 둔다**.
233
233
 
234
234
  ### `summary` — 검수·보고판 4개의 `{lead, body, refs}`
235
235
 
@@ -459,7 +459,7 @@ RANK_TAG = ['live', 'data', 'done', 'soon'] # 배지 종류
459
459
  |---|---|---|
460
460
  | `dry_run` warnings(로컬 렌더 ⚠ 포함) | 무엇이 **안 실렸는가** · 규칙 낱말·모양 | 작성자가 고칠 수 있는 ⚠ 0건 |
461
461
  | 검사기 8종 | 조판이 **성립했는가** | 각 검사기의 0건 |
462
- | 독자 검토(`reader-review.workflow.js`) | 처음 읽는 사람·결정권자가 **읽히는가** · 도판을 열어 잰 틀 · 1차 자료와 맞는가 | Verify 를 통과한 발견 반영 · 「⚠ 도판 N장 미열람」 없음(SKILL 0.5단계 7행) |
462
+ | 독자 검토(`reader-review.workflow.js`) | 처음 읽는 사람·결정권자가 **읽히는가** · 도판을 열어 잰 틀 · 1차 자료와 맞는가 | Verify 를 통과한 발견 반영 · 「⚠ 도판 N장 미열람」 없음(SKILL 체크리스트 7단계 독자 검토 행 — 미열람·「서버 보관」 줄의 id 가 모두 원고에서 보관 표식인 장이면 사람이 열람 주소에서 보고 핸드오프에 적어 통과) |
463
463
  | 사람의 눈 | 이 문서가 **이 독자에게 맞는가** | 판단 |
464
464
 
465
465
  ### `dry_run` 이 실질적 유일한 품질 관문이다
@@ -570,9 +570,9 @@ RANK_TAG = ['live', 'data', 'done', 'soon'] # 배지 종류
570
570
  **기본은 초안이다.**
571
571
  도구가 `publish` 를 명시적으로 참으로 주지 않는 한 고객에게 안 보인다. 여는 것은 10단계다.
572
572
 
573
- 수정은 SKILL.md 8단계 「수정 절차」(`get_report` 머리줄 확인 → 백업 → 고침 → 로컬 렌더·dry_run·독자 검토 → `update_report` → 9단계).
573
+ 수정은 SKILL.md 8단계 「수정 절차」(받은 판 보관 → draft 를 `patch.py --freeze` 로 굳혀 새 판에 적용 → `render.py` 로 새 판을 그대로 로컬 렌더 → 받은 판의 `body_rev` 를 실은 dry_run(그 사이 본문 판이 바뀌었으면 여기서 409) → 판 갱신 검토 → 같은 `body_rev` 를 실어 `update_report` → 9단계).
574
574
  **같은 링크가 새 내용을 가리킨다.** 초안 `update_report` 도 **본문 판(`body_rev`)** 을 올리고 사용자가 그 판을 읽으므로 독자 검토를 통과한 본문 판만 올린다. 사용자가 「바로 반영」을 지시하면 먼저 올리고 검토를 곧바로 뒤따라 돌린다 — 발행 이력 행(`history`)은 공개 뒤에만 는다(SKILL 3c).
575
- 사람이 고친 초안이면 덮기 전에 묻는다 — `update_report` 에 `body_rev` 잠금 칸이 아직 없어 기계가 막지 않는다.
575
+ `list_reports` 줄에 「덮음: …」이 있으면(사람이 고친 초안·발행 본문) 덮기 전에 묻는다 — 받은 판의 `body_rev`(`get_report` 머리줄 값)를 실은 교체는 그 뒤 본문 판이 바뀌었으면 서버가 409 로 막지만, 편집기에 저장만 해 둔 사람 초안은 판을 안 올려 그 잠금에 안 걸린다. 목록의 「덮음」이 유일한 신호다.
576
576
 
577
577
  포털 셸이 문서 **둘레**에 놓는 것(검토 회신·핵심 요약·진척률 바)은 저작 대상이 아니다.
578
578
  문서 안에 「댓글로 알려 주세요」를 쓰면 같은 것이 두 번 있게 된다.
@@ -597,6 +597,7 @@ python3 ~/.claude/skills/genetics-report/assets/render.py report.json
597
597
  ## 9단계 — 발행 뒤 판정
598
598
 
599
599
  초안 발행·수정마다 돈다(공개 전에도). 이 블록 목록 대조와 열람 주소 열기가 SKILL.md 「작업 체크리스트」 9단계 두 행이다 — 둘 다 대조 기계는 아직 없고 사람이 한다.
600
+ 고친 판이면 `get_report(id)` 로 되읽은 JSON 을 새 `body_rev` 를 파일 이름에 넣어 둔다 — 다음 수정이 받은 판과 내용으로 견줄 기록이다.
600
601
 
601
602
  브라우저를 안 열고도 「무엇을 냈는가」를 확인하는 창구다.
602
603
 
@@ -649,9 +650,10 @@ MCP 가 찍는 요약 줄은 **옛 서식의 셈**만 읽는다(`genetics.mjs ou
649
650
 
650
651
  공개 전 수정은 판이 아니다(SKILL 3c). 공개하기 직전에 한 번만 날짜를 맞춘다.
651
652
 
652
- 1. 작성일 세 자리 — `meta` 의 작성 · 판권지(`colophon`) 작성 · `history` 마지막 `date` — 를 공개하는 날 한 값으로 고친다. `history` 는 공개할 판 한 행(「최초 발행」)만 둔다.
653
+ 1. 올라 있는 판을 받아(SKILL 8단계 「수정 절차」 1~3 — 로컬 `report.json` 은 그사이 수정 절차를 돈 문서면 낡았다) 작성일 세 자리 — `meta` 의 작성 · 판권지(`colophon`) 작성 · `history` 마지막 `date` — 를 공개하는 날 한 값으로 고친다. `history` 는 공개할 판 한 행(「최초 발행」)만 둔다.
654
+ draft 는 있는 자리만 적는다 — `meta[label=작성][1]` · `colophon.rows[label=작성][1]`(판권지에 `rows` 가 있을 때만) · `history[0].date`. 칸에 날짜 말고 다른 글이 함께 있으면(「2026. 9. 27. · FCG」) `set` 대신 `replace` 로 날짜 조각만 바꾼다.
653
655
  `cover_date` 는 요약면 「기준」 날짜(수치의 기준일)라 이 셋에 묶지 않는다.
654
- 2. `update_report` 한 번 → 9단계 두 행.
656
+ 2. 로컬 렌더 → 받은 판의 `body_rev` 를 실은 dry_run → 같은 `body_rev` 로 `update_report` 한 번(수정 절차 4~6 — 날짜만 고친 판이라 판 갱신 검토는 돌리지 않는다) → 9단계 두 행.
655
657
  3. 사용자 승인을 받고 `release_report(id)` 로 연다 — 그것이 사람의 승인 행위다.
656
658
 
657
659
  공개 뒤 내용이 바뀐 update 에만 `history` 행을 더한다. 문서 밖 결정의 날짜는 본문에 적지 않는다(결정 장부).
@@ -4,7 +4,7 @@
4
4
  // text: "<렌더본 텍스트 절대경로(필수) — `python3 assets/render.py <report.json> --text <원고 폴더 밖 빈 폴더>/본문.txt`>",
5
5
  // reader: "<독자 한 줄: 누구이며 무엇을 하나 — 할 일이 둘이면 둘 다>",
6
6
  // example: "<예시 한 명 한 줄: 번호·측정일·실제 값>", bitmapBudget: 3,
7
- // scratch: "<측정 스크립트·풀어낸 도판을 둘 스크래치 절대경로>",
7
+ // scratch: "<측정 스크립트를 둘 스크래치 절대경로>",
8
8
  // plates: "<도판 HTML·PNG 작업 폴더(선택) — assets[].file 경로가 옮겨졌을 때 찾는 곳>",
9
9
  // locked: ["<정해진 것 한 줄>", …], latest: "<사용자 최신 지적 원문>",
10
10
  // sources: [{ name: "<자료 이름>", path: "<절대경로>", kind: "code|doc|pdf" }, …],
@@ -25,7 +25,7 @@
25
25
  // merged.findings 앞에는 사실 틀림(class 사실틀림) 가운데 **Verify 가 참·부분으로 받은 것**이 합치지 않은 독립 항목으로 서고 개수 제한 밖이다.
26
26
  // 미확인으로 판정된 사실 틀림은 합치는 쪽으로 가 problem 끝에 「(미확인)」이 붙는다. 뒤는 합친 상위 10.
27
27
  // 발견을 버리는 조건은 진단 거짓 하나다. 처방이 규칙에 걸리면(정해진 것 되돌리기·노이즈·포인터·새 이름 짓기) 발견은 남기고 rx 에 「처방 다시 — 까닭」.
28
- // 못 연 도판이 있으면 merged.overall 머리 줄이 「⚠ 도판 N장 미열람」이다. severity<3·상한 초과·검증 실패는 반영하지 않고 raw.unverified 로.
28
+ // 못 연 도판이 있으면 merged.overall 머리 줄이 「⚠ 도판 N장 미열람」이다(서버 보관이라 열 길이 없던 장은 「도판 N장 서버 보관」 줄로 따로 선다 — 미열람이 아니다). severity<3·상한 초과·검증 실패는 반영하지 않고 raw.unverified 로.
29
29
  // 2026-09-08 고객사 검수 보고 4차에서 이 검토가 용어 충돌(한 낱말 두 값)·수치 불일치·참조 오류·반올림 설명 오류를 잡았다.
30
30
  // 2026-09-11 실사례(고객사 결정 브리핑) 반려 둘에서 더한 것 — decider 관점에 「권고가 제목·열·색·요약에 배어 동의만 받는 틀인지」,
31
31
  // 새 관점 source(원본 대조: 링크를 실제로 열어 인용·쪽·값을 대조, 「흐리다」는 원본 해상도로 재측정). 원고에 확인 없이 「원본이 흐림」이 실렸었다.
@@ -91,9 +91,9 @@ const LOCKED = Array.isArray(A.locked) ? A.locked : (A.locked ? [String(A.locked
91
91
  const SOURCES = Array.isArray(A.sources) ? A.sources : []
92
92
  const STATE = A.state || ''
93
93
  const VERIFY_MAX = 12
94
- const CTX = `[원고·도판·저장소 파일은 수정하지 않는다. 측정 스크립트·풀어낸 그림은 scratch 아래에만 쓴다. 한글로 답할 것.]
94
+ const CTX = `[원고·도판·저장소 파일은 수정하지 않는다. 측정 스크립트는 scratch 아래에만 쓴다. 한글로 답할 것.]
95
95
  원고: ${FILE} (Lim 포털 관보 서식 보고서의 발행 JSON. parts[] PART(title·lede·questions — 그 PART 가 답하는 물음), sections[] 절(sections[].part 로 PART 에 속함), cases[] 건, open_items[] 판단 요청, summary, guide 읽는 법, appendix, assets 도판)
96
- 도판: assets[].file 은 열어 볼 로컬 PNG 다. 서버 보관 도판(file 없음)은 get_report 로 받은 data URI 를 scratch 에 풀어 연다. 도판 작업 폴더: ${PLATES}
96
+ 도판: assets[].file 은 열어 볼 로컬 PNG 다. 서버 보관 도판(data 가 「(생략 — NB)」 표식)은 get_report 로 바이트를 받을 수 없다 — 도판 작업 폴더에 같은 도판의 원본 PNG 가 있으면 그것을 열고(파일 이름이 id 와 달라도 캡션·내용으로 찾는다), 없으면 unopened 에 그 장의 id 하나씩, why 를 「서버 보관」으로 둔다. 로고(role 이 logo)는 도판이 아니다 — unopened 에 넣지 않는다. 도판 작업 폴더: ${PLATES}
97
97
  scratch: ${SCRATCH}
98
98
  독자: ${READER}
99
99
  예시 한 명: ${EXAMPLE}
@@ -169,7 +169,15 @@ const unread = LENSES.filter(([k], i) => TEXT_LENSES.has(k) && res[i] && res[i].
169
169
  const reads = res.map((r, i) => r && { lens: LENSES[i][0], ...r }).filter((r) => r && !unread.includes(r.lens))
170
170
  log(`독자 관점 ${reads.length}/${LENSES.length} · 발견 ${reads.reduce((n, r) => n + r.findings.length, 0)}${failed.length ? ` · 실패 ${failed.join(', ')}` : ''}${unread.length ? ` · 렌더본 못 엶 ${unread.join(', ')}` : ''}`)
171
171
  const fig = reads.find((r) => r.lens === 'figures')
172
- const figHead = !fig ? '⚠ 도판 관점 실패 — 도판 전부 미열람' : (fig.unopened.length ? `⚠ 도판 ${fig.unopened.length}장 미열람(${fig.unopened.map((u) => u.id).join(', ')})` : '')
172
+ // 서버 보관 도판(원본 PNG 가 이 기계에 없다)은 검토자가 열 길이 없다 — 열 수 있었는데 못 연 「미열람」과 갈라 적는다(그 장은 사람이 열람 주소에서 본다).
173
+ // 가르는 열쇠는 검토자가 적은 글(「서버 보관」)이라 판정이 아니라 **분류 표시**다 — 통과는 사람이 그 id 가 원고의 보관 표식인지 보고 정한다(SKILL 체크리스트 7행).
174
+ // 한 줄로 묶어 세면 보관 그림이 있는 문서를 고친 판은 통과 기준 「⚠ 도판 N장 미열람 없음」(SKILL 체크리스트 7행)을 채울 길이 없다(2026-09-30 검토).
175
+ const held = fig ? fig.unopened.filter((u) => /서버 보관/.test(`${u.why} ${u.id}`)) : []
176
+ const missed = fig ? fig.unopened.filter((u) => !held.includes(u)) : []
177
+ const figHead = !fig ? '⚠ 도판 관점 실패 — 도판 전부 미열람' : [
178
+ missed.length ? `⚠ 도판 ${missed.length}장 미열람(${missed.map((u) => u.id).join(', ')})` : '',
179
+ held.length ? `도판 ${held.length}장 서버 보관 — 검토자가 열지 못했다, 열람 주소에서 사람이 본다(${held.map((u) => u.id).join(', ')})` : '',
180
+ ].filter(Boolean).join('\n')
173
181
  if (figHead) log(figHead)
174
182
 
175
183
  phase('Verify')
@@ -147,7 +147,7 @@ CSS 가 만드는 글자(장식 기호·인쇄 쪽 번호)는 없고, 클래스
147
147
  { file: "<report.json 절대경로>",
148
148
  text: "<렌더본 텍스트 절대경로 — 필수>",
149
149
  reader: "<독자 한 줄 — 할 일이 둘이면 둘 다>", example: "<예시 한 명 한 줄>", bitmapBudget: 3,
150
- scratch: "<측정 스크립트·풀어낸 도판을 둘 스크래치 절대경로>", plates: "<도판 HTML·PNG 작업 폴더(선택)>",
150
+ scratch: "<측정 스크립트를 둘 스크래치 절대경로>", plates: "<도판 HTML·PNG 작업 폴더(선택)>",
151
151
  locked: ["<정해진 것 한 줄>", …], latest: "<사용자 최신 지적 원문>",
152
152
  sources: [{ name, path, kind: "code|doc|pdf" }, …],
153
153
  decisions: "<0단계 결정 장부 decisions.json 경로(선택)>", state: "<코드 작업 상태 표 경로(선택)>" }
@@ -156,7 +156,7 @@ CSS 가 만드는 글자(장식 기호·인쇄 쪽 번호)는 없고, 클래스
156
156
  `sources` 는 문서마다 달라서 스킬에 경로를 적지 않는다. `state` 를 주면 여덟째 관점이 켜진다.
157
157
  `text` 가 없으면 워크플로가 멈춘다. 「처음 읽는 사람」·「선형 읽기」 두 관점은 이 파일 하나만 받고(정해진 것·결정 장부·최신 지적·상태 표·내부 자료는 안 받는다)
158
158
  CLAUDE.md 를 싣지 않는 Explore 유형으로 돈다 — 자동 메모리(MEMORY.md)까지 빠지는지는 첫 실행 전사로 확인하기 전이다. 나머지 관점은 위 인자를 그대로 받는다.
159
- 판 갱신 검토(`edition-review.workflow.js`)와 선형 읽기 실측(`linear-read-test.workflow.js`)도 `args.text` 가 필수다 — 선형 읽기 실측은 `file` 을 더 받지 않는다.
159
+ 판 갱신 검토(`edition-review.workflow.js`)와 선형 읽기 실측(`linear-read-test.workflow.js`)도 `args.text` 가 필수다 — 선형 읽기 실측은 `file` 도 받는다 — 렌더본이 원고보다 새로운지 재는 데만 쓰고, 읽는 넷에게는 주지 않는다.
160
160
 
161
161
  | 관점 | 보는 것 | 경계 |
162
162
  |---|---|---|
@@ -166,7 +166,7 @@ CLAUDE.md 를 싣지 않는 Explore 유형으로 돈다 — 자동 메모리(MEM
166
166
  | visual 정보 디자이너 | 그림이면 한 번에 보일 곳 · 표 숫자를 되풀이하는 문단 | — |
167
167
  | source 원본 대조 | 고객이 누를 수 있는 원본(`source_url`·출처 줄 주소·쪽·본문 링크)과 인용의 일치 | 앱 동작은 판정하지 않는다(facts 몫) — 다른 문서에 없다고 거짓이라 하지 않는다 |
168
168
  | structure 구조 | PART 물음 ↔ 절 내용 · PART 사이 되풀이 · 요지·읽는 법 물음이 건의 관찰·판단을 바꿔 말한 곳(문장 쌍 전수) · 요지의 이야기 순서와 칸 수, 근거 문단 `body` 비움 | 처방은 빼기로 |
169
- | figures 도판 | readability R13·§4 를 읽고 `assets[].file` PNG 를 **Read 로 연다**. 같은 이름 `.html` 이 있으면 머리 주석·CSS 를 읽는다. 두 칸이면 틀 윗선·아랫선 y 를 재어 px 차를 evidence 에. 짚을 곳 — R13 위반 · 틀 잘림(윗선 여백 0 포함) · 선택지 도안 가운데 하나만 합성·실사·크게·먼저 · 그림↔본문 반박 · 캡션 「왼쪽은…」 불일치 · 칩 이름과 캡션·표 이름 불일치 | 캡션·alt 만으로 판정하지 않는다. 서버 보관 도판은 `get_report` data URI 를 scratch 에 풀어 연다. 못 연 도판은 `unopened` 에 |
169
+ | figures 도판 | readability R13·§4 를 읽고 `assets[].file` PNG 를 **Read 로 연다**. 같은 이름 `.html` 이 있으면 머리 주석·CSS 를 읽는다. 두 칸이면 틀 윗선·아랫선 y 를 재어 px 차를 evidence 에. 짚을 곳 — R13 위반 · 틀 잘림(윗선 여백 0 포함) · 선택지 도안 가운데 하나만 합성·실사·크게·먼저 · 그림↔본문 반박 · 캡션 「왼쪽은…」 불일치 · 칩 이름과 캡션·표 이름 불일치 | 캡션·alt 만으로 판정하지 않는다. 서버 보관 도판(「(생략 — NB)」 표식)은 `get_report` 로 바이트를 받을 수 없다 — 원본 PNG 가 도판 폴더에 있으면 그것을 열고(파일 이름이 id 와 달라도 캡션·내용으로 찾는다), 없으면 `unopened` 에 「서버 보관」으로. 로고(`role: logo`)는 도판이 아니다 — `unopened` 에 넣지 않는다. 못 연 도판은 `unopened` 에 |
170
170
  | facts 사실 대조 | FCG 내부 1차 자료로 수치를 다시 계산 · 앱 동작은 `kind:"code"` 파일:줄로만 · 결정의 주체·자리·시점은 결정 장부·`locked` 와 대조 | 판정 못 하면 미확인 |
171
171
  | state 상태 대조(`state` 있을 때) | 「반영·적용·고침·보이지 않음·빠짐」 같은 완료형 문장마다 상태 표의 확인 수준(코드·시험판·실기기·운영) | — |
172
172
 
@@ -182,7 +182,7 @@ CLAUDE.md 를 싣지 않는 Explore 유형으로 돈다 — 자동 메모리(MEM
182
182
  `rx` 가 달린 발견은 규칙에 맞는 처방을 새로 적는다(이름 발견은 원래 이름으로 — 새 이름·뜻풀이 금지). 선형 읽기 뜀과 이름 발견은 상위 10 과 별개로 전부 돌아간다.
183
183
 
184
184
  돌아오는 것: `{ head?, failed?, merged: { findings[](… · rx · verdicts), overall }, questions[], passed[], dropped[], jumps[], names[], raw: { reads, verdicts, unverified } }`.
185
- `merged.findings` 앞에는 **참·부분으로 판정된** 사실 틀림이 합치지 않은 독립 항목으로 서고 개수 제한 밖이다. 못 연 도판이 있으면 `overall` 머리 줄이 「⚠ 도판 N장 미열람(id…)」 — 그 도판은 사람이 연다.
185
+ `merged.findings` 앞에는 **참·부분으로 판정된** 사실 틀림이 합치지 않은 독립 항목으로 서고 개수 제한 밖이다. 못 연 도판이 있으면 `overall` 머리 줄이 「⚠ 도판 N장 미열람(id…)」 — 그 도판은 사람이 연다. 검토자가 「서버 보관」이라 적은 도판은 「도판 N장 서버 보관」 줄로 따로 선다 — 그 id 가 원고에서 보관 표식(`data` 가 「(생략 — NB)」)인 장인지 확인하고 열람 주소에서 사람이 본다(체크리스트 7단계 독자 검토 행).
186
186
  검증이 1건이라도 실패하거나 관점이 결과를 못 내면 `head` 「검토 미완 — 검증 N건 실패」(관점 실패는 이어 적는다)와 `failed: true` 가 붙고 `overall` 첫 줄도 같다 — 실패를 「발견 0」으로 읽지 않고 검토를 다시 돌린다.
187
187
  통과 기준(SKILL 체크리스트 7단계 독자 검토 행): `jumps` 가운데 심각도 3 이상 0건 · `names` 0건 — 못 없앤 것은 까닭을 핸드오프에.
188
188
 
@@ -202,8 +202,8 @@ CLAUDE.md 를 싣지 않는 Explore 유형으로 돈다 — 자동 메모리(MEM
202
202
  단 경고가 규칙의 뜻과 어긋나는 자리를 짚으면(남의 문서 절 번호를 이 문서의 뒤 절로 세는 것처럼) 원고를 깎지 않는다 — 오탐으로 판정해 렌더러를 고치고 대조군 시험을 둔다. 그 수리가 운영에 오르기 전이면 번호 대신 이름으로 위치를 살린다. 렌더러·서버 관문이 운영보다 앞선 동안 옛 규칙이 내는 경고는 `lim-portal-format.md` §10 통과 기준 표의 행으로 넘긴다.
203
203
  2. 발행 뒤 **열람 주소를 실제로 연다**(Cursor 인앱 브라우저 `iab` 나 헤드리스 크롬). 그림 수·이미지 로드 수·표·흐름도·차트 수를 세고
204
204
  판단 절과 그림 절을 캡처로 본다. 「구조 확인 outline」은 블록이 섰는지만 말하지 보이는지는 말하지 않는다.
205
- 3. 큰 원고(70KB 이상)는 채팅에 붙여넣지 말고 MCP 서버를 자식으로 띄워 파일 인자로 보낸다
206
- (`node mcp/genetics.mjs` 를 stdio JSON-RPC 로 — 스크래치 `mcp-call.mjs` 30줄).
205
+ 3. 큰 원고(70KB 이상)는 채팅에 붙여넣지 말고 파일 인자로 보낸다 — `node assets/mcp-call.mjs <도구> <인자.json> [결과 파일]`
206
+ (회차 폴더에 같은 도구를 다시 짓지 않는다 — 그렇게 지은 사본들이 64KiB 넘는 응답에서 한글을 깨뜨렸다).
207
207
 
208
208
  ## 9. 하지 않는 것
209
209
 
@@ -1,24 +0,0 @@
1
- {
2
- "name": "scripts",
3
- "lockfileVersion": 3,
4
- "requires": true,
5
- "packages": {
6
- "": {
7
- "dependencies": {
8
- "playwright-core": "^1.62.1"
9
- }
10
- },
11
- "node_modules/playwright-core": {
12
- "version": "1.62.1",
13
- "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.62.1.tgz",
14
- "integrity": "sha512-wPYSwEBJY9GHraISXqyqtx0na0LpO3XEX7jNDhntbex7tzUS7kLnZsOlFruFJB4Hi/rhDMjXGqHewDZ68nYZVw==",
15
- "license": "Apache-2.0",
16
- "bin": {
17
- "playwright-core": "cli.js"
18
- },
19
- "engines": {
20
- "node": ">=20"
21
- }
22
- }
23
- }
24
- }