genetics-mcp 2.9.1 → 2.9.3
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.
- package/bin/aarch64-apple-darwin/gcell +0 -0
- package/bin/aarch64-unknown-linux-musl/gcell +0 -0
- package/bin/manifest.json +4 -4
- package/bin/x86_64-apple-darwin/gcell +0 -0
- package/bin/x86_64-unknown-linux-musl/gcell +0 -0
- package/genetics.mjs +658 -26
- package/package.json +1 -1
- package/skills/genetics-cell-id/SKILL.md +204 -0
- package/skills/genetics-report/SKILL.md +217 -93
- package/skills/genetics-report/assets/example/cases.json +19 -22
- package/skills/genetics-report/assets/example/report.json +198 -53
- package/skills/genetics-report/assets/render.py +21 -10
- package/skills/genetics-report/assets/subset_font.py +24 -8
- package/skills/genetics-report/references/content-pipeline.md +316 -733
- package/skills/genetics-report/references/design-system.md +20 -723
- package/skills/genetics-report/references/font-embedding.md +132 -108
- package/skills/genetics-report/references/illustrations.md +20 -462
- package/skills/genetics-report/references/lim-portal-format.md +756 -182
- package/skills/genetics-report/references/process.md +414 -403
- package/skills/genetics-report/references/qa.md +28 -712
- package/skills/genetics-report/assets/generate-illustrations.py +0 -159
- package/skills/genetics-report/assets/qa.py +0 -198
- package/skills/genetics-report/assets/visual-baseline.py +0 -459
- package/skills/genetics-report/references/textbook-mode.md +0 -111
- package/skills/genetics-report/references/wysiwyg.md +0 -751
|
@@ -1,891 +1,474 @@
|
|
|
1
1
|
# 콘텐츠 파이프라인 — 데이터 모델과 원고 규칙
|
|
2
2
|
|
|
3
|
-
내부 작업 기록을
|
|
4
|
-
|
|
5
|
-
폰트는 `references/font-embedding.md`가 담당한다. 여기서는 **무엇을 어떤 필드에 적고,
|
|
6
|
-
빌더가 그것을 어떤 규칙으로 조판하는가**만 다룬다.
|
|
3
|
+
내부 작업 기록을 **관보 서식의 고객 문서**로 바꾸는 쪽의 사양임.
|
|
4
|
+
다루는 것은 다섯 가지임 — 자료 수집 · 절과 건 번호 · 한국어 원고 규칙 · 고객 언어 치환(제목 재작성 포함) · 어휘 게이트.
|
|
7
5
|
|
|
8
|
-
|
|
6
|
+
**조판 규칙은 여기 적지 않음.**
|
|
7
|
+
활자 스케일 · 괘선 · 간격 격자 · 글줄 폭 근거(`1ch` · `--measure`) · 인쇄 페이지 문법 · 검사기 8종은 전부 `gazette-report` 스킬이 원전임.
|
|
8
|
+
두 벌로 두면 갈라짐.
|
|
9
9
|
|
|
10
|
-
|
|
|
11
|
-
|
|
12
|
-
| `
|
|
13
|
-
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
```bash
|
|
18
|
-
python3 ~/.claude/skills/genetics-report/assets/build.py report.json # → cfg["out"]
|
|
19
|
-
python3 ~/.claude/skills/genetics-report/assets/qa.py report.json # 실패 0 이어야 통과
|
|
20
|
-
```
|
|
10
|
+
| 찾는 것 | 어디 |
|
|
11
|
+
|---|---|
|
|
12
|
+
| 문서 골격 · 표지 칸 · `kind` 별 본문 장치 · 장 색(`tone`) · PART · 요약 근거 게이트 | `references/lim-portal-format.md` |
|
|
13
|
+
| 활자 · 괘선 · 인쇄 · 검사기 실행법 | `gazette-report` 스킬 |
|
|
14
|
+
| 수집 절차 · 독자 정의 · 작업 순서 | `references/process.md` · SKILL.md 0단계 |
|
|
15
|
+
| 폰트 서브셋 · 커버리지 | `references/font-embedding.md` |
|
|
16
|
+
| 이 문서 | 각 칸에 **무엇을 적는가**, 그리고 그 글을 **어떻게 쓰는가** |
|
|
21
17
|
|
|
22
|
-
>
|
|
23
|
-
>
|
|
24
|
-
> 그 프로젝트의 `report/report.json` · `cases.json`).
|
|
25
|
-
> 규칙만 가져가고 이름은 각자의 것으로 갈아 끼운다.
|
|
18
|
+
> 예시는 전부 `assets/example/report.json` · `assets/example/cases.json` 에서 인용함.
|
|
19
|
+
> 그 두 파일이 v2 데이터의 최소 완본이고, 이 문서의 규칙과 짝임.
|
|
26
20
|
|
|
27
21
|
---
|
|
28
22
|
|
|
29
23
|
## 0. 가장 먼저 정의할 것 — 독자
|
|
30
24
|
|
|
31
|
-
다른 모든 규칙이 여기서
|
|
25
|
+
다른 모든 규칙이 여기서 연역됨.
|
|
26
|
+
이 단계를 건너뛰면 나머지는 그냥 취향 목록이 됨.
|
|
32
27
|
|
|
33
|
-
| 정의할 것 |
|
|
28
|
+
| 정의할 것 | 예시의 답 | 이 답이 결정하는 것 |
|
|
34
29
|
|---|---|---|
|
|
35
|
-
| 독자는 누구인가 | 고객사 검수 담당 — 읽고 **직접 확인해 볼** 사람 | 모든 건에 `how_to_check
|
|
36
|
-
| 독자가 조회할 수 있는 참조는 무엇인가 | 자기가 올린
|
|
37
|
-
| 독자가 결정해 줘야 할 것이 있는가 |
|
|
38
|
-
| 독자가 알 필요 없는 것은 무엇인가 | 릴리스
|
|
30
|
+
| 독자는 누구인가 | 고객사 검수 담당 — 읽고 **직접 확인해 볼** 사람 | 모든 건에 `how_to_check` 가 사실상 필수인 이유 (1.1) |
|
|
31
|
+
| 독자가 조회할 수 있는 참조는 무엇인가 | 자기가 올린 제보 원본 **하나뿐** | `source_url` 을 접수건에만 다는 이유 (1.2) |
|
|
32
|
+
| 독자가 결정해 줘야 할 것이 있는가 | 있음 → `open_items` + 건별 `remaining` | 문서에 `kind:"asks"` 절이 존재하는 이유 |
|
|
33
|
+
| 독자가 알 필요 없는 것은 무엇인가 | 릴리스 절차 · 티켓 운영 · 채택 안 된 설계안 | 4장 치환 규칙 전체 |
|
|
39
34
|
|
|
40
|
-
**핵심
|
|
41
|
-
|
|
42
|
-
|
|
35
|
+
**핵심 논리.**
|
|
36
|
+
내부 티켓 번호를 빼는 이유는 「기밀」이 아니라 **「무의미」**임.
|
|
37
|
+
고객은 그 번호를 조회할 수 없음.
|
|
38
|
+
조회할 수 없는 참조를 실으면 문서가 독자가 아니라 자기 조직을 향해 말하는 꼴이 됨.
|
|
39
|
+
이 잣대 — *독자가 그것으로 무엇을 할 수 있는가* — 가 4·5장 모든 결정의 기준임.
|
|
43
40
|
|
|
44
41
|
---
|
|
45
42
|
|
|
46
|
-
## 1.
|
|
47
|
-
|
|
48
|
-
```
|
|
49
|
-
report.json ─┬─→ build.py ─→ 단일 HTML ─→ qa.py (exit 1 이면 게이트 실패)
|
|
50
|
-
cases.json ──┘ │
|
|
51
|
-
figure.html ────────┘ (선택, 원본 그대로 삽입)
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
`build(cfg_path)` 한 함수가 전부를 조립한다. 순서는 고정이다.
|
|
55
|
-
|
|
56
|
-
| 단계 | 하는 일 | 근거 |
|
|
57
|
-
|---|---|---|
|
|
58
|
-
| 1 | `report.json` 로드, `cfg['cases']`를 **report.json 위치 기준 상대경로**로 로드 | 설정 파일과 데이터가 함께 이동한다 |
|
|
59
|
-
| 2 | `text_fix` 적용 — 메모리 안에서만 (6장) | 원본 JSON은 절대 수정하지 않는다 |
|
|
60
|
-
| 3 | `groups`로 건을 나눠 절 번호 `{no}.{i}` 부여 (4장) | |
|
|
61
|
-
| 4 | `sections` 순서대로 본문 조립, 각 텍스트는 `chunk()`/`link()` 통과 (5장) | |
|
|
62
|
-
| 5 | `palette.derive()` → `:root` 주입, `audit()` 대비 검사 assert | 색을 손으로 고르지 않는다 |
|
|
63
|
-
| 6 | CSS·JS·폰트를 인라인 → 외부 리소스 0 | 메일 첨부 한 장으로 열려야 한다 |
|
|
64
|
-
| 7 | `§` 잔존·`forbid` 유출을 **assert로 검사** (8장) | 유출은 타협 불가 |
|
|
65
|
-
|
|
66
|
-
### 1.1 원본 불변 원칙
|
|
67
|
-
|
|
68
|
-
```python
|
|
69
|
-
# build.py — build() 안, cases 로드 직후
|
|
70
|
-
for c in cases:
|
|
71
|
-
for k in fields:
|
|
72
|
-
if c.get(k):
|
|
73
|
-
for a, b in fixes:
|
|
74
|
-
c[k] = c[k].replace(a, b)
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
| 규칙 | 근거 | 어기면 생기는 일 |
|
|
78
|
-
|---|---|---|
|
|
79
|
-
| **빌드는 입력 JSON 파일을 절대 고쳐 쓰지 않는다.** 고객 언어로의 변환은 전부 빌드 시점 메모리 안에서 일어난다 | 내부 원본(배포 서술, 코드 규모 수치)이 JSON에 남아 있어야 나중에 "우리가 실제로 뭘 했는지"를 되짚을 수 있다. **고객본은 원본의 파생물이지 대체물이 아니다** | 원본을 직접 고치면 내부 사실이 영구 유실된다. 다음 회차에 "저장 경로가 몇 곳이었지?"를 물을 근거가 사라진다 |
|
|
80
|
-
| **외부 리소스 0.** CSS/JS를 문자열로 읽어 인라인, 폰트는 base64 | 메일 첨부·사내망·오프라인에서 열려야 한다 | `qa.py`의 외부 리소스 검사가 실패로 잡는다. 고객 PC에서 스타일 없는 날 HTML이 열린다 |
|
|
81
|
-
|
|
82
|
-
### 1.2 색은 손으로 고르지 않는다
|
|
83
|
-
|
|
84
|
-
`assets/palette.py`가 팔레트 6색(primary·secondary·bg·surface·ink·mid)에서 문서용 토큰을
|
|
85
|
-
**유도**하고, `audit()`이 **17쌍의 대비를 되잰다.** 9개 팔레트 전부가 통과한다.
|
|
86
|
-
`build.py`가 그 결과를 `:root`로 주입하므로 `report.css`는 색 토큰을 선언하지 않는다.
|
|
87
|
-
|
|
88
|
-
```python
|
|
89
|
-
tokens = P.derive(P.PALETTES[cfg.get('palette', 'korean-navy')] ...)
|
|
90
|
-
bad = [r for r in P.audit(tokens) if not r[5]]
|
|
91
|
-
assert not bad, f'팔레트 대비 미달: {[(r[0], r[3]) for r in bad]}'
|
|
92
|
-
```
|
|
93
|
-
|
|
94
|
-
콘텐츠 쪽에서 알아야 할 것은 하나다. **`--accent`는 배경·괘선 전용이고 글자로 쓰지 않는다.**
|
|
95
|
-
글자는 `--accent-mid`(24px+ Bold, 3:1) / `--accent-ink`(작은 글자, 4.5:1),
|
|
96
|
-
칩은 `--accent-chip` + `--accent-on` 쌍으로만 쓴다. 자세한 역할은 `references/design-system.md`.
|
|
43
|
+
## 1. 자료 수집 — `cases.json`
|
|
97
44
|
|
|
98
|
-
|
|
45
|
+
평면 배열임.
|
|
46
|
+
**배열 순서가 곧 문서 내 정렬 순서**이고, 그룹 안에서 1부터 건 번호가 붙음.
|
|
99
47
|
|
|
100
|
-
|
|
48
|
+
### 1.1 다섯 칸 — 이것이 본체다
|
|
101
49
|
|
|
102
|
-
|
|
103
|
-
|
|
50
|
+
건마다 **증상 · 원인 · 조치 · 확인 방법 · 확인 요청** 다섯 칸을 채움.
|
|
51
|
+
관보 렌더러는 이 다섯을 `.sheet__grid` 표의 다섯 행으로 세움(`gazette.py:445-457`).
|
|
104
52
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
| 키 | 필수 | 타입 | 의미 · 기본값 |
|
|
53
|
+
| 칸 | 필드 | 독자 관점의 뜻 | 비면 |
|
|
108
54
|
|---|---|---|---|
|
|
109
|
-
| `
|
|
110
|
-
| `
|
|
111
|
-
| `
|
|
112
|
-
| `
|
|
113
|
-
| `
|
|
114
|
-
| `subtitle` | 필수 | str | 표지 부제 — 이 문서가 무엇인지 한 문장 |
|
|
115
|
-
| `meta` | 필수 | `[[k, v], …]` | 표지 하단 정의 목록. **2원소 배열의 배열**(dict 아님) |
|
|
116
|
-
| `grades` | 필수 | `{라벨: [클래스, 설명]}` | 확인 등급. **선언 순서 = 강도 순서** (2.3) |
|
|
117
|
-
| `groups` | 필수 | `[{key, no}]` | 건 분류와 절 번호 (4.1) |
|
|
118
|
-
| `sections` | 필수 | `[{no, id, name, kind, …}]` | 문서 순서대로의 절 정의 (2.2) |
|
|
119
|
-
| `summary` | 조건부 | `[{lead, body}]` | `kind:"summary"` 절이 있으면 필수 |
|
|
120
|
-
| `open_items` | 선택 | `[{title, body, refs?}]` | `kind:"asks"` 절의 본문. 기본 `[]` |
|
|
121
|
-
| `cta` | 선택 | `[str]` | `kind:"asks"` 절 맨 위 Primary 블록. **HTML 그대로 통과** (2.5) |
|
|
122
|
-
| `stats` | 선택 | `[{value, label, unit?, accent?}]` | 첫 절 앞 지표 밴드. 기본 없음 |
|
|
123
|
-
| `figure` | 선택 | str | `kind:"summary"` 절 끝에 삽입할 HTML 조각 경로 (2.6) |
|
|
124
|
-
| `footer` | 선택 | `[str]` | 꼬리말 조각들. 기본 `[]` |
|
|
125
|
-
| `labels` | 선택 | `{키: 문구}` | 고정 문구 재정의 (2.7). 기본 `{}` |
|
|
126
|
-
| `text_fix` | 선택 | `[{from, to}]` | 고객 언어 치환 (6장). 기본 `[]` |
|
|
127
|
-
| `forbid` | 선택 | `[정규식]` | 산출물 금칙어. 걸리면 **빌드 실패** (8장) |
|
|
128
|
-
| `allow_external` | 선택 | `[str]` | 허용할 외부 URL 부분문자열. **`qa.py`만 읽는다** |
|
|
129
|
-
| `palette` | 선택 | str \| dict | 팔레트 이름 또는 6색 dict. 기본 `"korean-navy"` |
|
|
130
|
-
| `font` | 선택 | dict | `{embed, file, family, weight, license}` (2.8) |
|
|
131
|
-
| `lang` | 선택 | str | `<html lang>`. 기본 `"ko"` |
|
|
132
|
-
| `description` | 선택 | str | `<meta name="description">`. 기본 `""` |
|
|
133
|
-
| `editor` | 선택 | bool | `<body data-report-edit>`. 기본 `true` |
|
|
134
|
-
| `nav_width` | 선택 | str | 좌측 목차 폭 `--nav-w`. 기본 `"224px"` |
|
|
135
|
-
| `pad_x` | 선택 | str | 좌우 여백 `--pad-x`. 기본 `"clamp(20px,4vw,68px)"` |
|
|
136
|
-
|
|
137
|
-
### 2.2 `sections[]` — 절 정의
|
|
138
|
-
|
|
139
|
-
문서에 나오는 순서 그대로 적는다. 목차·본문이 이 배열 하나에서 나온다.
|
|
140
|
-
|
|
141
|
-
| 필드 | 필수 | 의미 |
|
|
142
|
-
|---|---|---|
|
|
143
|
-
| `no` | 필수 | 화면에 찍히는 절 번호 문자열. 예: `"01"`. 번호 없는 절은 `""` |
|
|
144
|
-
| `id` | 필수 | HTML `id` = 목차 링크 대상. 예: `"sec-3"` |
|
|
145
|
-
| `name` | 필수 | 절 제목. 예: `"접수하신 9건"` |
|
|
146
|
-
| `kind` | 필수 | 아래 5종 중 하나 |
|
|
147
|
-
| `group` | `kind:"cases"`일 때 필수 | 어느 `groups[].key`의 건을 여기에 펼칠지 |
|
|
148
|
-
| `lede` | 선택 | 절 제목 아래 한 문단. `link()`를 통과하므로 `§` 참조를 쓸 수 있다 |
|
|
149
|
-
| `paragraphs` | `kind:"note"`일 때 필수 | 문단 배열 |
|
|
55
|
+
| 증상 | `symptom` | 「화면에서 무엇이 잘못 보였나」 — 제보자가 자기 경험과 대조할 유일한 앵커 | 그 행이 통째로 빠짐 |
|
|
56
|
+
| 원인 | `cause` | 「왜 그랬나」 — 조치를 신뢰할지 판단할 근거 | 같음 |
|
|
57
|
+
| 조치 | `fix` | 「무엇을 바꿨고 어디까지 확인했나」 — 본문 최장 | 같음 |
|
|
58
|
+
| 확인 방법 | `how_to_check` | **독자가 직접 할 일.** 「이 순서로 열어보면 확인된다」 | 같음 |
|
|
59
|
+
| 확인 요청 | `remaining` | **독자가 결정해 줘야 할 일.** 대조표의 「미결」 열이 이것으로 갈림 | 그 행이 빠지고 대조표는 「완료」로 표시 |
|
|
150
60
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
| kind | 렌더 | 읽는 설정 |
|
|
154
|
-
|---|---|---|
|
|
155
|
-
| `summary` | `.ex-grid` — `lead`(단언) + `body`(부연) 카드들, 이어서 `figure` | `cfg['summary']`, `cfg['figure']` |
|
|
156
|
-
| `status` | 등급 범례 + 전건 판정표 `<table class="verdict">` | `cfg['grades']`, 전 건 |
|
|
157
|
-
| `cases` | 그 그룹의 건 카드들 | `cfg['groups']`, cases 파일 |
|
|
158
|
-
| `asks` | `cta` 블록 + `open_items` 카드들 | `cfg['cta']`, `cfg['open_items']` |
|
|
159
|
-
| `note` | `.appendix` — 회색 작은 활자 꼬리말 문단 | `s['paragraphs']` |
|
|
160
|
-
| `lesson` | 교과서 본문 — 개념·예제·정리·코드·그림 블록 | `s['blocks']` — 스키마는 `references/textbook-mode.md` |
|
|
161
|
-
|
|
162
|
-
| 규칙 | 근거 | 어기면 생기는 일 |
|
|
61
|
+
| 규칙 | 근거 | 어기면 |
|
|
163
62
|
|---|---|---|
|
|
164
|
-
| `
|
|
165
|
-
| `
|
|
166
|
-
|
|
|
167
|
-
| `id`는 문서 안에서 유일 | 목차 링크 대상 | 목차가 엉뚱한 곳으로 간다 |
|
|
168
|
-
|
|
169
|
-
### 2.3 `grades` — 확인 등급
|
|
170
|
-
|
|
171
|
-
```json
|
|
172
|
-
"grades": {
|
|
173
|
-
"운영 환경 확인": ["m-live", "운영 환경에서 실제로 동작시켜 결과까지 확인"],
|
|
174
|
-
"실데이터 대조": ["m-data", "운영 데이터를 건별로 대조해 확인"],
|
|
175
|
-
"재발 점검 적용": ["m-test", "같은 문제가 다시 생기면 걸러지도록 상시 점검 적용"],
|
|
176
|
-
"반영 확인": ["m-code", "수정이 운영에 반영된 것까지 확인 (화면 재현은 미실시)"]
|
|
177
|
-
}
|
|
178
|
-
```
|
|
63
|
+
| 모든 건에 `how_to_check` 를 채움 | 고객이 자기 화면에서 직접 밟을 수 있는 순서가 없으면 검수 요청이 아니라 통보임 | **렌더러가 조용히 그 행만 빼고 표를 세움.** 전 건 중 하나만 반쪽인 것을 눈으로는 못 찾음 |
|
|
64
|
+
| `remaining` 은 **우리가 끝낼 수 없어 상대의 판단·확인이 필요한 것만.** 없으면 비움 | 대조표의 「미결/완료」가 독자의 할 일 목록임(`gazette.py:497`) | 전건에 뭔가 적으면 그 열이 무의미해짐 |
|
|
65
|
+
| 다섯 칸이 다 비면 그 건은 **아예 안 실림** | `if not rows: continue`(`gazette.py:465`) | 건수는 세는데 문서에는 없음 |
|
|
179
66
|
|
|
180
|
-
|
|
181
|
-
정의된 `m-live` / `m-data` / `m-test` / `m-code` 넷을 쓴다(4단계를 넘기려면 CSS도 늘려야 한다).
|
|
67
|
+
### 1.2 나머지 칸
|
|
182
68
|
|
|
183
|
-
|
|
|
69
|
+
| 필드 | 필수 | 뜻 |
|
|
184
70
|
|---|---|---|
|
|
185
|
-
|
|
|
186
|
-
|
|
|
187
|
-
|
|
|
188
|
-
| `
|
|
189
|
-
|
|
|
71
|
+
| `group` | **필수** | `groups[].key` 중 하나. MCP 스키마에서도 유일한 필수 칸임 |
|
|
72
|
+
| `title` | 사실상 필수 | 보고서 표제. **티켓 제목이 아니라 다시 쓴 문장** (5장) |
|
|
73
|
+
| `grade` | 선택 | 확인 등급 — `grades` 의 키와 글자가 같아야 함 |
|
|
74
|
+
| `source_url` | 선택 | 고객이 **아는 유일한 외부 참조.** `http(s)` 가 아니면 서버가 거절 |
|
|
75
|
+
| `source_text` | 선택 | 그 링크의 글자. 생략하면 `제보 원본 보기` |
|
|
190
76
|
|
|
191
|
-
`
|
|
77
|
+
`grade` 값이 `grades` 에 없어도 **죽지 않음** — 자리 판별이 실패해 가장 약한 표식으로 떨어짐(`gazette.py:751-762`).
|
|
78
|
+
오타가 조용히 넘어가는 자리라 발행 뒤 눈으로 확인해야 함.
|
|
192
79
|
|
|
193
|
-
|
|
80
|
+
`group` 이 `groups[].key` 중 하나가 아니면 그 건은 **어디에도 안 실림.**
|
|
81
|
+
렌더러는 ⚠ 로 세고(`gazette.py:786`), 서버는 발행을 거절함(`report_publish.php:190`).
|
|
194
82
|
|
|
195
|
-
|
|
196
|
-
{"lead": "접수해 주신 9건은 모두 조치를 마치고 운영에 반영했습니다.",
|
|
197
|
-
"body": "성격은 세 갈래였습니다. … 문제(§3.1~§3.2), … 문제(§3.3, §3.4, …)입니다."}
|
|
198
|
-
```
|
|
83
|
+
### 1.3 스키마에 **넣지 않는** 것
|
|
199
84
|
|
|
200
|
-
|
|
|
201
|
-
|---|---|---|
|
|
202
|
-
| `lead` | 필수 | `.ex-lead` — 굵은 한 문장 단언 |
|
|
203
|
-
| `body` | 필수 | 부연 한 문단 |
|
|
204
|
-
|
|
205
|
-
둘 다 `link(e(...))`만 통과한다 — **`chunk()`를 거치지 않는다**(5.6). `§` 참조를 쓸 수 있다.
|
|
206
|
-
|
|
207
|
-
**4칸으로 쓴다.** 검수자가 첫 화면에서 판단을 끝낼 수 있어야 한다.
|
|
208
|
-
|
|
209
|
-
1. 접수한 것은 어떻게 됐는가
|
|
210
|
-
2. 우리가 추가로 찾은 것 중 중요한 것
|
|
211
|
-
3. 여러 건을 관통하는 공통 원인 (있으면 `figure`로 그림)
|
|
212
|
-
4. 상대가 무엇을 하면 되는가
|
|
213
|
-
|
|
214
|
-
`lead`는 **완전문**으로 쓴다. 여기가 판단이 일어나는 자리다(SKILL.md 카피 게이트).
|
|
215
|
-
|
|
216
|
-
### 2.5 `open_items` · `cta` — 05 확인 요청
|
|
217
|
-
|
|
218
|
-
```json
|
|
219
|
-
{"title": "검증에 쓴 표본이 작습니다",
|
|
220
|
-
"body": "화자 구분 정확도로 말씀드린 92.3%는 상담 녹음 한 건으로 잰 수치입니다. …",
|
|
221
|
-
"refs": ["§3.6", "§4.9"]}
|
|
222
|
-
```
|
|
223
|
-
|
|
224
|
-
| 필드 | 필수 | 통과하는 파이프 | 주의 |
|
|
225
|
-
|---|---|---|---|
|
|
226
|
-
| `title` | 필수 | `e()` **만** | **`link()`를 타지 않는다.** 여기에 `§`를 쓰면 8장의 `assert '§' not in doc`에 걸려 빌드가 실패한다. 참조는 `refs`에 넣는다 |
|
|
227
|
-
| `body` | 필수 | `link(e(...))` | `§` 참조 가능 |
|
|
228
|
-
| `refs` | 선택 | 각 원소가 `link(e(...))` | `["§3.6", "§4.9"]` → `해당 절 — 3.6 · 4.9` |
|
|
229
|
-
|
|
230
|
-
`cta`는 문자열 배열이고 **`e()`를 거치지 않는다** — `link(p)`만 통과한다.
|
|
231
|
-
|
|
232
|
-
| 규칙 | 근거 | 어기면 생기는 일 |
|
|
233
|
-
|---|---|---|
|
|
234
|
-
| `cta` 문자열 안의 `<`·`&`는 **직접 이스케이프**한다 | 이 자리는 `<strong>`으로 한 구절만 강조하려고 raw HTML을 허용한 곳이다 | 태그가 깨지거나 의도치 않은 마크업이 열린다 |
|
|
235
|
-
| `cta`에서 강조하는 것은 **상대가 할 일**과 **파괴적 동작 경고**뿐 | 예시: `"[발송] 버튼은 실제로 고객에게 알림톡이 나가므로"` — 검수자가 확인하다가 실제 발송을 일으키는 사고를 막는다 | 안내 문서가 사고를 유발한다 |
|
|
236
|
-
| `title`은 **완전문**으로 | 여기가 판단이 일어나는 자리다. `"표본 부족"` 같은 명사구는 심각도를 감춘다 | 독자가 "그래서 어쩌라는 건가"를 되묻는다 |
|
|
237
|
-
|
|
238
|
-
### 2.6 `stats` · `figure`
|
|
239
|
-
|
|
240
|
-
```json
|
|
241
|
-
{"value": "6", "unit": "가지", "label": "확인을 부탁드리는 사항", "accent": true}
|
|
242
|
-
```
|
|
243
|
-
|
|
244
|
-
| 필드 | 필수 | 의미 |
|
|
245
|
-
|---|---|---|
|
|
246
|
-
| `value` | 필수 | 큰 숫자 |
|
|
247
|
-
| `unit` | 선택 | 숫자 뒤 작은 단위. 기본 `""` |
|
|
248
|
-
| `label` | 필수 | 아래 설명 한 줄 |
|
|
249
|
-
| `accent` | 선택 | `true`면 `.stat.is-open` — 값이 `--accent-ink`로 바뀐다. **"상대가 해야 할 일" 칸 하나에만** |
|
|
250
|
-
|
|
251
|
-
`figure`는 HTML 조각 파일 경로다. `kind:"summary"` 절 끝에 **원본 그대로** 삽입된다.
|
|
252
|
-
|
|
253
|
-
| 규칙 | 근거 | 어기면 생기는 일 |
|
|
85
|
+
| 넣지 않는 것 | 이유 | 대신 |
|
|
254
86
|
|---|---|---|
|
|
255
|
-
|
|
|
256
|
-
|
|
|
87
|
+
| 내부 티켓 번호 | 고객이 조회할 수 없는 참조 (0장) | `forbid` 에 번호 패턴을 등재해 유출을 빌드가 막음 (4.3) |
|
|
88
|
+
| 이슈 트래커 이름 | 같은 이유 | `forbid` 에 `[Aa]sana` 같은 패턴 |
|
|
89
|
+
| 원본 티켓 제목 | 검색용 문자열이지 읽기용 문장이 아님 (5장) | `title` 을 처음부터 표제로 씀 |
|
|
90
|
+
| 근거 수치 별도 필드 | `fix` 본문에 이미 녹아 있음. 중복 필드는 아무도 안 읽는데 채워야 하는 칸이 됨 | `fix` 안에 서술 |
|
|
257
91
|
|
|
258
|
-
|
|
92
|
+
> **이식 규칙.**
|
|
93
|
+
> 새 프로젝트의 스키마는 **렌더러가 실제로 읽는 필드만**으로 정의함.
|
|
94
|
+
> 나머지는 작업 중 메모로 다른 파일에 분리하거나 지움.
|
|
95
|
+
> 안 그러면 다음 사람이 아무도 읽지 않는 필드를 건마다 채우는 낭비를 함.
|
|
259
96
|
|
|
260
|
-
|
|
97
|
+
### 1.4 `grades` 의 값 배열에서 관보가 읽는 것
|
|
261
98
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
| `toc` | `목차` | 좌측 목차 제목 · `<nav aria-label>` |
|
|
265
|
-
| `symptom` | `증상` | 건 카드 |
|
|
266
|
-
| `cause` | `원인` | 건 카드 |
|
|
267
|
-
| `fix` | `조치` | 건 카드 |
|
|
268
|
-
| `check` | `확인 방법` | 건 카드 |
|
|
269
|
-
| `ask` | `확인 요청` | 건 카드 |
|
|
270
|
-
| `source` | `제보 원본 보기` | 건 카드 외부 링크 (`↗`는 코드가 붙인다) |
|
|
271
|
-
| `col_sec` | `절` | 판정표 열 머리 |
|
|
272
|
-
| `col_title` | `건명` | 판정표 열 머리 |
|
|
273
|
-
| `col_grade` | `확인 등급` | 판정표 열 머리 |
|
|
274
|
-
| `col_open` | `확인 요청` | 판정표 열 머리 |
|
|
275
|
-
| `refs` | `해당 절` | `open_items[].refs` 앞머리 |
|
|
276
|
-
|
|
277
|
-
`labels`로 덮을 수 **없는** 문구: 판정표 본문의 `있음`/`—`, 범례의 `N건`. 다른 언어로 낼 일이
|
|
278
|
-
생기면 `build.py`를 고쳐야 한다.
|
|
279
|
-
|
|
280
|
-
### 2.8 `font`
|
|
281
|
-
|
|
282
|
-
```json
|
|
283
|
-
"font": {"embed": true, "file": "fonts/pretendard-report.woff2",
|
|
284
|
-
"family": "Pretendard", "weight": "400 700", "license": "…"}
|
|
285
|
-
```
|
|
99
|
+
등급을 **어떻게 매기는가**는 `references/process.md` 3단계가 원전임.
|
|
100
|
+
여기서는 데이터 모양만 적음.
|
|
286
101
|
|
|
287
|
-
`
|
|
288
|
-
`embed:false`로 끄면 시안 반복이 빨라진다. 서브셋을 다시 만드는 법·커버리지·왜 가변 1종인지는
|
|
289
|
-
`references/font-embedding.md`에 있다. **여기서 다시 설명하지 않는다.**
|
|
102
|
+
`grades` 는 `{이름: [클래스, 설명]}` 이고 **키 선언 순서가 곧 강도 순서**임(`gazette.py:80-83`).
|
|
290
103
|
|
|
291
|
-
|
|
|
104
|
+
| 사실 | 귀결 |
|
|
292
105
|
|---|---|
|
|
293
|
-
|
|
|
106
|
+
| 값 배열의 0번(클래스)·1번(설명)을 관보는 **안 읽음** | 등급 설명문을 문서에 실으려면 `appendix` 나 `guide` 에 사람이 따로 적어야 함 |
|
|
107
|
+
| 값 배열 2·3번에 이름을 주면 자리 판별보다 그것이 이김 | 4단을 넘길 때만 씀 |
|
|
108
|
+
| **등급 라벨을 처음부터 고객 언어로 짓는다** | `코드 확인`은 「코드만 보고 끝냈다」로 읽혀 **실제보다 약하게** 들림. `반영 확인`은 확인한 범위를 말함 |
|
|
294
109
|
|
|
295
110
|
---
|
|
296
111
|
|
|
297
|
-
##
|
|
298
|
-
|
|
299
|
-
평면 배열이다. **배열 순서가 곧 문서 내 정렬 순서**이며, 그룹 안에서 1부터 절 번호가 붙는다.
|
|
112
|
+
## 2. 절 번호와 상호참조
|
|
300
113
|
|
|
301
|
-
|
|
302
|
-
빌드는 `if c.get(k)` 진릿값으로 판정하므로 **빈 문자열 == 없음**으로 동작한다.
|
|
114
|
+
### 2.1 번호는 렌더러가 매긴다 — 데이터가 정하지 않는다
|
|
303
115
|
|
|
304
|
-
|
|
116
|
+
**v1 과 가장 크게 달라진 자리임.**
|
|
305
117
|
|
|
306
|
-
|
|
|
307
|
-
|---|---|---|---|
|
|
308
|
-
| `group` | **필수** | 이 건이 어느 절에 속하는가 | `groups[].key`와 대조 → 절 번호 |
|
|
309
|
-
| `title` | **필수** | 보고서 표제. **티켓 제목이 아니라 다시 쓴 문장** (7장) | 건 카드 `<h3>`, 목차, 판정표 |
|
|
310
|
-
| `grade` | **필수** | 확인 등급 — "얼마나 믿어도 되는가" | 칩·범례·판정표. `grades` 키와 일치해야 함 |
|
|
311
|
-
| `symptom` | **필수** | "화면에서 무엇이 잘못 보였나" — 제보자가 자기 경험과 대조할 유일한 앵커 | `.row.r-sym` |
|
|
312
|
-
| `cause` | **필수** | "왜 그랬나" — 조치를 신뢰할지 판단할 근거. 증상과 나란히(`.dx`) | `.row.r-cause` |
|
|
313
|
-
| `fix` | **필수** | "무엇을 바꿨고 어디까지 확인했나" — 본문 최장. 청킹의 주 대상 | `.row.r-fix` |
|
|
314
|
-
| `how_to_check` | 선택(실질 필수) | **독자가 직접 할 일.** "이 순서로 열어보면 확인된다" | `.row.r-check` |
|
|
315
|
-
| `remaining` | 선택 | **독자가 결정해 줘야 할 일.** 라벨이 `확인 요청`인 것이 핵심 | `.row.r-open`, 판정표 `확인 요청` 열 |
|
|
316
|
-
| `source_url` | 선택 | 고객이 **아는 유일한 외부 참조** | `제보 원본 보기 ↗` |
|
|
317
|
-
|
|
318
|
-
`title`·`grade`·`symptom`·`cause`·`fix`는 대괄호 접근이라 **키가 없으면 `KeyError`**로 빌드가 죽는다.
|
|
319
|
-
`group`만은 예외로 `.get()`이다(`[c for c in cases if c.get('group') == g['key']]`) — 키가 없으면
|
|
320
|
-
그 건이 **어느 그룹에도 안 잡히고**, 바로 뒤의 `assert placed == len(cases)`가
|
|
321
|
-
`그룹에 배정되지 않은 건이 있다: 17/18`로 잡는다. 죽는 지점이 다를 뿐 결과는 같다.
|
|
322
|
-
나머지 셋(`how_to_check`·`remaining`·`source_url`)은 `.get()`이라 없어도 빌드가 지나간다.
|
|
323
|
-
|
|
324
|
-
### 3.2 스키마에 **넣지 않는** 것
|
|
325
|
-
|
|
326
|
-
| 넣지 않는 것 | 이유 | 대신 |
|
|
327
|
-
|---|---|---|
|
|
328
|
-
| 내부 티켓 번호 | 고객이 조회할 수 없는 참조 (0장) | `forbid`에 번호 패턴을 등재해 유출을 빌드가 막는다 (8장) |
|
|
329
|
-
| 이슈 트래커 이름 | 같은 이유 | `forbid`에 `[Aa]sana` 같은 패턴 |
|
|
330
|
-
| 원본 티켓 제목 | 검색용 문자열이지 읽기용 문장이 아니다 (7장) | `title`을 처음부터 표제로 쓴다 |
|
|
331
|
-
| 근거 수치 별도 필드 | `fix` 본문에 이미 녹아 있다. 중복 필드는 아무도 안 읽는데 채워야 하는 칸이 된다 | `fix` 안에 서술 |
|
|
332
|
-
|
|
333
|
-
> **이식 규칙:** 새 프로젝트의 스키마는 **빌드가 실제로 읽는 필드만**으로 정의한다.
|
|
334
|
-
> 나머지는 "작업 중 메모"로 다른 파일에 분리하거나 지운다. 안 그러면 다음 사람이
|
|
335
|
-
> 아무도 읽지 않는 필드를 건마다 채우는 낭비를 한다.
|
|
336
|
-
|
|
337
|
-
### 3.3 `how_to_check` — 빌드는 봐주고 QA는 안 봐준다
|
|
338
|
-
|
|
339
|
-
| 규칙 | 근거 | 어기면 생기는 일 |
|
|
340
|
-
|---|---|---|
|
|
341
|
-
| 모든 건에 `how_to_check`를 채운다 | **고객이 자기 화면에서 직접 밟을 수 있는 순서**가 없으면 검수 요청이 아니라 통보다 | 빌드는 통과하지만(`row()`가 빈 문자열이면 블록을 통째로 생략) `qa.py`의 구조 일관성 검사가 `확인 방법 17/18`로 잡아 **실패**시킨다. 눈으로는 18건 중 1건이 반쪽인 걸 못 찾는다 |
|
|
342
|
-
| `remaining`은 **우리가 끝낼 수 없어 상대의 판단·확인이 필요한 것만.** 없으면 비운다 | 있는 건과 없는 건이 판정표 `확인 요청` 열에서 `있음`/`—`로 갈린다 — 이 열이 독자의 할 일 목록이다 | 전건에 뭔가 적으면 그 열이 무의미해진다 |
|
|
343
|
-
| `how_to_check`와 `remaining` 중 하나만 있으면 `.ck.one`으로 1단 렌더 | 빈 칸이 남지 않는다 | — |
|
|
344
|
-
|
|
345
|
-
### 3.4 필드 설계에서 배울 것
|
|
346
|
-
|
|
347
|
-
| 결정 | 근거 | 어기면 생기는 일 |
|
|
118
|
+
| 층위 | 형식 | 누가 정하는가 |
|
|
348
119
|
|---|---|---|
|
|
349
|
-
|
|
|
350
|
-
|
|
|
351
|
-
|
|
|
120
|
+
| 절 | `제1절` `제2절` … | **`sections` 배열의 자리.** `ch = '%02d' % i`(`gazette.py:794`) |
|
|
121
|
+
| 절 앵커 | `#sec-1` … | 같음. `'id': 'sec-%d' % i`(`gazette.py:811`) |
|
|
122
|
+
| 건 번호 | `04-1` `05-1` … | 그 절의 자리 + 그룹 안 순번(`gazette.py:797-798`) |
|
|
123
|
+
| 건 앵커 | `#case-04-1` … | 같음 |
|
|
352
124
|
|
|
353
|
-
|
|
125
|
+
**⚠ 정정 — 데이터의 `sections[].no` 와 `sections[].id` 는 렌더러가 읽지 않음.**
|
|
126
|
+
실측: `"no": "01"` · `"id": "sec-1"` 만 주고 렌더하면 화면에는 `제1절` 이 찍힘.
|
|
127
|
+
`no` 를 바꿔도 아무 일도 일어나지 않음.
|
|
128
|
+
절 번호를 `01` 로 찍고 싶으면 **`label` 에 적음**(`gazette.py:812`) — 예시가 그렇게 함.
|
|
129
|
+
데이터의 `id` 로 문서 안 링크를 걸면 **깨짐** — 앵커는 언제나 `sec-{자리}` 임.
|
|
354
130
|
|
|
355
|
-
|
|
131
|
+
### 2.2 `groups[].no` 를 절 자리에 맞춘다 ★ 이것이 유일한 정합 규칙
|
|
356
132
|
|
|
357
|
-
|
|
133
|
+
두 곳이 건 번호를 **따로** 계산함.
|
|
358
134
|
|
|
359
|
-
|
|
|
135
|
+
| 어디 | 무엇으로 계산 | 코드 |
|
|
360
136
|
|---|---|---|
|
|
361
|
-
|
|
|
362
|
-
|
|
|
363
|
-
|
|
364
|
-
```python
|
|
365
|
-
groups = cfg['groups']
|
|
366
|
-
for g in groups:
|
|
367
|
-
g['cases'] = [c for c in cases if c.get('group') == g['key']]
|
|
368
|
-
for i, c in enumerate(g['cases'], 1):
|
|
369
|
-
c['sec'] = f"{int(g['no'])}.{i}"
|
|
370
|
-
placed = sum(len(g['cases']) for g in groups)
|
|
371
|
-
assert placed == len(cases), '그룹에 배정되지 않은 건이 있다: …'
|
|
372
|
-
```
|
|
137
|
+
| 렌더러(앵커·본문 표기) | 그 `kind:"cases"` 절이 `sections` 에서 **몇 번째인가** | `gazette.py:793-798` |
|
|
138
|
+
| 서버(요약 근거 게이트) | `groups[].no` | `report_publish.php:249-258` |
|
|
373
139
|
|
|
374
|
-
|
|
375
|
-
번호만 보고 알게** 한다. 예시 프로젝트에서 `3.x`는 "내가 제보한 것", `4.x`는 "그쪽이 찾아낸 것"이다.
|
|
140
|
+
둘이 어긋나면 이렇게 됨.
|
|
376
141
|
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
|
381
|
-
|---|---|---|
|
|
382
|
-
| 모든 건의 `group`이 `groups[].key` 중 하나여야 한다 | `assert placed == len(cases)`가 지킨다 | 오타 하나로 건이 **문서에서 통째로 사라진다.** assert가 `17/18`로 즉시 알려 준다 |
|
|
383
|
-
| 앵커 id는 점을 하이픈으로 (`3.1` → `s3-1`) | `aid = lambda c: 's' + c['sec'].replace('.', '-')`. **HTML id의 점은 CSS 셀렉터에서 클래스 구분자로 읽힌다** | 스타일·스크립트가 조용히 빗나간다 |
|
|
142
|
+
| 상황 | 결과 |
|
|
143
|
+
|---|---|
|
|
144
|
+
| `groups[].no` = 3, 그 절이 4번째 | 요약에 `§3.1` 이라 적어야 서버가 통과시킴. 그런데 렌더러가 만든 앵커는 `#case-04-1` 이라 **링크가 죽음** |
|
|
145
|
+
| `groups[].no` = 4, 그 절이 4번째 | 통과하고 링크도 살아 있음 |
|
|
384
146
|
|
|
385
|
-
|
|
147
|
+
> **규칙: `groups[].no` 는 그 그룹을 펼치는 `kind:"cases"` 절의 `sections` 안 자리(1부터)와 같아야 한다.**
|
|
148
|
+
> 예시가 그렇게 되어 있음 — `no: "4"` 인 그룹은 4번째 절, `no: "5"` 는 5번째 절.
|
|
149
|
+
> 절을 하나 끼워 넣으면 **뒤의 `groups[].no` 를 전부 밀어야 함.**
|
|
386
150
|
|
|
387
|
-
|
|
388
|
-
SECREF = re.compile(r'§(\d+)\.(\d{1,2})')
|
|
151
|
+
### 2.3 `§` 마커 — 원고 문법
|
|
389
152
|
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
lambda m: f'<a class="xref" href="#s{m.group(1)}-{m.group(2)}">'
|
|
394
|
-
f'{m.group(1)}.{m.group(2)}</a>', t)
|
|
395
|
-
```
|
|
153
|
+
원고에는 `§4.1` 처럼 적음.
|
|
154
|
+
렌더러가 링크로 바꾸고 기호를 버림(`gazette.py:42-49`).
|
|
155
|
+
`§` 는 법령·논문 조판 기호이지 업무 보고서의 활자가 아님.
|
|
396
156
|
|
|
397
157
|
| 단계 | 하는 일 |
|
|
398
158
|
|---|---|
|
|
399
|
-
|
|
|
400
|
-
|
|
|
401
|
-
|
|
|
159
|
+
| 집필 | `§4.1` — 앞자리는 그 절의 자리, 뒷자리는 그룹 안 순번 |
|
|
160
|
+
| 렌더 | `<a class="ref" href="#case-04-1">4-1</a>`. `§` 소멸 |
|
|
161
|
+
| 검사 | 링크로 안 바뀐 `§n.n` 이 남으면 **빌드가 죽음**(`build.py:390-393`) |
|
|
402
162
|
|
|
403
|
-
|
|
404
|
-
된다. 정규식은 `§` + 숫자 + `.` + 1~2자리만 본다.
|
|
163
|
+
코드 블록 안의 `§3.1` 은 검사에서 제외됨 — 거기서는 그냥 글자임.
|
|
405
164
|
|
|
406
|
-
|
|
165
|
+
### 2.4 표기가 갈리는 자리 — 근거는 `refs[]` 로 단다
|
|
407
166
|
|
|
408
|
-
|
|
167
|
+
실측(예시 렌더 결과).
|
|
409
168
|
|
|
410
|
-
|
|
|
411
|
-
|---|---|
|
|
412
|
-
| **활자 규범** | `§`는 법령·논문 조판 기호다. 고객사 검수 담당이 읽는 업무 보고서의 활자가 아니다 |
|
|
413
|
-
| **이동 비용** | `§`는 "저기에 뭔가 있다"만 알리고 독자가 손으로 찾아가게 만든다. `3.1` 링크는 클릭 한 번이다. **검수자는 건과 건을 오가며 대조하는 독자**이므로 이 비용이 문서의 실사용성을 좌우한다 |
|
|
414
|
-
| **부수 효과 — 이게 진짜 이득** | `§`가 살아 있으면 **`link()`를 안 타는 텍스트가 있다는 뜻**이다. 그래서 `§` 자체가 유출 검사 1순위 토큰이 된다 |
|
|
415
|
-
|
|
416
|
-
### 4.4 `link()`를 타지 않는 자리 (알고 쓸 것)
|
|
417
|
-
|
|
418
|
-
| 자리 | 통과하는 것 | 결과 |
|
|
169
|
+
| 어디 | 앵커 | 화면 글자 |
|
|
419
170
|
|---|---|---|
|
|
420
|
-
|
|
|
421
|
-
| `
|
|
422
|
-
|
|
|
423
|
-
| `stats[].label`, `meta`, `footer` | `e()`만 | 같음 |
|
|
171
|
+
| 본문 인라인 `§4.1` | `#case-04-1` | `4-1` |
|
|
172
|
+
| `summary[].refs` · `open_items[].refs` | `#case-04-1` | `04-1` |
|
|
173
|
+
| 건 제목 옆 번호 · 차례 하위 항목 · 대조표 | `#case-04-1` | `04-1` |
|
|
424
174
|
|
|
425
|
-
|
|
426
|
-
`
|
|
427
|
-
|
|
175
|
+
링크는 셋 다 맞게 걸림.
|
|
176
|
+
그런데 **같은 건이 문서 안에서 `4-1` 과 `04-1` 두 이름으로 불림.**
|
|
177
|
+
`_link()` 는 자리수를 안 채우고(`gazette.py:47`), `ref_text()` 는 채움(`gazette.py:741`).
|
|
178
|
+
원고에서 고칠 수 있는 것이 아님 — `§04.1` 이라 적어도 결과는 `4-1` 임.
|
|
428
179
|
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|---|---|---|
|
|
433
|
-
| 참조 마커에 **원고 전용 sentinel 문자**를 쓰고, 최종 출력에서 **반드시 제거** | 남으면 곧 버그 신호가 되므로 공짜 검사기를 얻는다 | sentinel을 안 쓰면 "변환 안 된 참조"를 기계가 구분할 방법이 없다 |
|
|
434
|
-
| 그 제거를 **경고가 아니라 assert**로 | 경고는 아무도 안 본다 | 8.1 참조 |
|
|
180
|
+
> **귀결: 요약과 확인 요청의 근거는 `refs[]` 로 단다.**
|
|
181
|
+
> 본문 안 인라인 `§n.n` 은 문장 흐름상 꼭 필요할 때만 씀.
|
|
182
|
+
> `refs` 는 `§` 를 떼고 `["4.1"]` 로 적어도 되고 `["04-1"]`·`["01절"]` 도 받음(`gazette.py:716-727`).
|
|
435
183
|
|
|
436
184
|
---
|
|
437
185
|
|
|
438
|
-
##
|
|
186
|
+
## 3. 한국어 원고 규칙 — 문장 단위 줄바꿈
|
|
439
187
|
|
|
440
|
-
###
|
|
188
|
+
### 3.1 렌더러가 무엇을 하는가
|
|
441
189
|
|
|
442
|
-
|
|
443
|
-
|
|
190
|
+
한글 보고서는 마침표에서 줄을 바꿈.
|
|
191
|
+
문장을 이어 붙인 한글 문단은 기계가 쓴 글처럼 읽힘.
|
|
444
192
|
|
|
445
|
-
|
|
446
|
-
|
|
193
|
+
렌더러가 마크업을 다 세운 뒤 `sentbreak.py` 를 태워 문장마다 `<span class="sn">` 을 새김(`gazette.py:970-988`).
|
|
194
|
+
`.sn` 은 블록으로 서고 이어지는 줄만 내어씀.
|
|
195
|
+
**낱말은 하나도 바뀌지 않음** — 원고를 고치는 도구가 아님.
|
|
447
196
|
|
|
448
|
-
|
|
197
|
+
문장 경계 판별은 `boundary.py` 가 함.
|
|
198
|
+
날짜 · 버전 · 경로 · 머리글자를 걸러 냄.
|
|
449
199
|
|
|
450
|
-
|
|
200
|
+
### 3.2 경계 판별이 놓치는 자리 — 실측
|
|
451
201
|
|
|
452
|
-
|
|
202
|
+
`boundary.count()` 로 직접 잰 결과임.
|
|
203
|
+
왼쪽 숫자가 잡아낸 경계 수임.
|
|
453
204
|
|
|
454
|
-
|
|
455
|
-
marks = list(re.finditer(r'\((\d)\)', text))
|
|
456
|
-
is_list = len(marks) >= 2
|
|
457
|
-
```
|
|
458
|
-
|
|
459
|
-
| 규칙 | 근거 | 어기면 생기는 일 |
|
|
205
|
+
| 잰 값 | 원고 | 왜 |
|
|
460
206
|
|---|---|---|
|
|
461
|
-
|
|
|
462
|
-
|
|
|
463
|
-
|
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
207
|
+
| 0 | `저장 순서를 바꿈.운영 반영까지 확인함.` | 마침표 뒤 **공백이 없음** |
|
|
208
|
+
| 1 | `저장 순서를 바꿈. 운영 반영까지 확인함.` | 정상 |
|
|
209
|
+
| 0 | `조치를 마침. iOS 쪽은 차기 회차 예정임.` | 다음 문장이 **라틴 소문자**로 시작 |
|
|
210
|
+
| 1 | `조치를 마침. IOS 쪽은 차기 회차 예정임.` | 대문자는 잡힘 |
|
|
211
|
+
| 0 | `조치를 마침. api.php 를 확인함.` | 같은 이유 |
|
|
212
|
+
| 0 | `0.3초로 줄임. 2026. 8. 24. 기준임.` | 다음 문장이 **숫자 날짜**로 시작 — 날짜를 먼저 가리므로 |
|
|
213
|
+
| 1 | `0.3초로 줄임. 2026년 8월 24일 기준임.` | 한글 날짜는 잡힘 |
|
|
214
|
+
| 0 | `2026. 8. 24.` 단독 | 날짜는 문장이 아님 |
|
|
215
|
+
| 1 | `A. 첫째. B. 둘째.` | 머리글자 `A.`·`B.` 는 배제, `첫째.` 뒤만 경계 |
|
|
216
|
+
| 2 | `조치함! 확인 바람? 회신 필요함.` | `!` `?` 도 경계임 |
|
|
217
|
+
| 1 | `총 3건임. (1) 저장 (2) 목록 (3) 표시.` | 열거는 안 쪼개짐 — 한 문장임 |
|
|
218
|
+
|
|
219
|
+
| 원고 규칙 | 근거 |
|
|
220
|
+
|---|---|
|
|
221
|
+
| **마침표 뒤에 공백 하나를 반드시 둔다** | 없으면 두 문장이 한 덩어리로 남음 |
|
|
222
|
+
| **문장을 라틴 소문자로 시작하지 않는다** | `iOS`·`api.php` 로 문장을 열면 앞 문장에 붙음. 한글 낱말을 앞에 세울 것 — `앱 쪽은 iOS …` |
|
|
223
|
+
| **문장을 숫자 날짜로 시작하지 않는다** | `2026. 8. 24.` 는 문장 끝이 아니라고 판정됨. 한글 날짜로 쓰거나 문장 안쪽으로 보낼 것 |
|
|
224
|
+
| **문장 중간에 마침표를 남기지 않는다** | 버전·경로는 그대로 두어도 안전하지만, 약어 마침표는 경계를 흔듦 |
|
|
476
225
|
|
|
477
|
-
|
|
226
|
+
### 3.3 문장을 짧게 끊는다
|
|
478
227
|
|
|
479
|
-
|
|
480
|
-
한국어에서 `(1)` 은 두 가지로 쓰이기 때문이다.
|
|
481
|
-
· 열거 마커 — "(1) 같은 날짜의 예약은 …" → 목록으로
|
|
482
|
-
· 항목 참조 — "(1)은 새 일지를 열 때 …" → 산문 그대로 ("1번은" 의 뜻)
|
|
483
|
-
뒤에 **조사가 바로 붙으면 참조**다. 이걸 목록으로 쪼개면 조사만 남는다.
|
|
484
|
-
```
|
|
228
|
+
자동 조판이 문장을 세워 주지만 **문장의 길이는 사람의 몫임.**
|
|
485
229
|
|
|
486
|
-
|
|
230
|
+
| 규칙 | 근거 |
|
|
231
|
+
|---|---|
|
|
232
|
+
| 한 문장에 사실 하나 | 두 사실을 접속으로 이으면 한 줄이 접히고, 접힌 둘째 줄과 다음 문장의 첫 줄이 구별되지 않음 |
|
|
233
|
+
| 복문을 두 문장으로 푼다 | 「같은 배포에 ~가 함께 포함됐음」은 배포 단위를 아는 사람만 이해하는 구조임. 사실을 앞세우고 부연을 뒤로 |
|
|
234
|
+
| 명사구로 끝내지 않는다(제목은 예외) | `(… 남아 보임)` 같은 괄호 부기는 완결 문장으로 바꿈 |
|
|
235
|
+
| 조치 서술은 **「변경 → 검증」 두 박자** | 무엇을 바꿨는지 한 문장, 어디까지 확인했는지 한 문장. 예시의 `fix` 가 전부 이 모양임 |
|
|
487
236
|
|
|
488
|
-
|
|
237
|
+
글줄 폭이 몇 자인가 · 왜 그 폭인가는 여기서 다루지 않음 → `gazette-report`.
|
|
489
238
|
|
|
490
|
-
|
|
239
|
+
### 3.4 열거는 어디에 두는가
|
|
491
240
|
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
```
|
|
241
|
+
**⚠ 정정 — `(1)(2)` 를 문단 안에 적어도 목록으로 서지 않음.**
|
|
242
|
+
v1 의 `chunk()` 가 하던 일임.
|
|
243
|
+
관보 렌더러는 그 함수를 부르지 않음 — 본문 조각은 `link(e(...))` 만 통과함(`gazette.py:120-122`).
|
|
244
|
+
문단 안의 `(1)(2)` 는 그냥 글자로 남음.
|
|
497
245
|
|
|
498
|
-
|
|
246
|
+
목록으로 서는 자리는 넷뿐임.
|
|
499
247
|
|
|
500
|
-
|
|
|
248
|
+
| 자리 | 데이터 |
|
|
501
249
|
|---|---|
|
|
502
|
-
|
|
|
503
|
-
|
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
마커의 역할이 섞이지 않는다는 전제이고, 실제 데이터가 이를 따른다.
|
|
507
|
-
(실측: 예시 프로젝트 88개 필드 중 이 규칙으로 산문 복귀한 문단 1개.)
|
|
508
|
-
|
|
509
|
-
> **이 규칙은 지우기 쉽다.** 코드만 보면 "왜 조사 목록이 여기 있지?" 싶다.
|
|
510
|
-
> 그래서 위 실패 예를 이 문서에 남긴다. 근거 없는 규칙은 다음 사람이 되돌린다.
|
|
511
|
-
|
|
512
|
-
### 5.3 규칙 (3) — `(3)(4)` 병합
|
|
513
|
-
|
|
514
|
-
```python
|
|
515
|
-
if not body: # "(3)(4)" — 다음 번호와 한 덩어리
|
|
516
|
-
pending.append(m.group(1))
|
|
517
|
-
continue
|
|
518
|
-
items.append(('·'.join(pending + [m.group(1)]), body))
|
|
519
|
-
pending = []
|
|
520
|
-
...
|
|
521
|
-
if pending and items: # 끝에 매달린 번호는 마지막 항목에
|
|
522
|
-
items[-1] = ('·'.join([items[-1][0]] + pending), items[-1][1])
|
|
523
|
-
```
|
|
524
|
-
|
|
525
|
-
마커 사이 본문이 비면 **다음 번호와 하나의 항목**으로 합치고 라벨을 `3·4`로 만든다.
|
|
526
|
-
|
|
527
|
-
**예시 프로젝트 실측 라벨 분포:** `1×5, 2×5, 3×3, 4×1, 3·4×1` — 병합 라벨이 실제로 렌더된다.
|
|
250
|
+
| 강조 박스 항목 | `kind:"note"` 절의 `paragraphs[]` |
|
|
251
|
+
| 이 문서 읽는 법 | `guide[].a[]` |
|
|
252
|
+
| PART 가 답하는 질문 | `parts[].questions[]` |
|
|
253
|
+
| 확인 요청 | `open_items[]` |
|
|
528
254
|
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
| 본문 없는 마커는 다음 마커와 병합 | 원문 `"(3)(4) 두 건은 …"`은 **두 항목을 함께 가리키는 하나의 진술**이므로 라벨도 하나여야 한다 | `(3)`이 본문 없는 빈 `<li>`로 나오고 내용이 전부 `(4)`에 들어간다. 독자는 "3번은 왜 비었지?"를 의심한다 |
|
|
532
|
-
| 끝에 매달린 `pending`을 마지막 항목에 붙임 | `"… (5)(6)"`처럼 문단이 번호로 끝나는 경우의 방어 | 마지막 번호가 조용히 증발한다 |
|
|
533
|
-
|
|
534
|
-
> **⚠ 렌더 주의(현행 CSS 확인):** `report.css`의 `ol.enum li`는 첫 칸이 `23px` 고정이고
|
|
535
|
-
> `.n`도 `width:23px; height:23px`다. `3·4`까지는 들어가지만 `3·4·5`는 넘칠 수 있다.
|
|
536
|
-
> 세 개 이상을 한 라벨로 묶어야 할 상황이면 그건 문장을 다시 쓸 신호다.
|
|
537
|
-
|
|
538
|
-
### 5.4 규칙 (4) — 두 문장 단위 분절
|
|
539
|
-
|
|
540
|
-
```python
|
|
541
|
-
SENT = re.compile(r'(?<=다\.)\s+|(?<=요\.)\s+|(?<=오\.)\s+')
|
|
542
|
-
...
|
|
543
|
-
sents = [x.strip() for x in SENT.split(text) if x.strip()]
|
|
544
|
-
if len(text) > 180 and len(sents) > 2:
|
|
545
|
-
...두 문장씩 <p> 로 묶는다...
|
|
546
|
-
return f'<p>{link(e(text))}</p>'
|
|
547
|
-
```
|
|
255
|
+
건의 다섯 칸에 갈래가 넷 이상 생기면 **그건 한 건이 아니라 여러 건임.**
|
|
256
|
+
건을 쪼개고 `§` 로 서로 가리키게 함.
|
|
548
257
|
|
|
549
|
-
|
|
550
|
-
|---|---|---|
|
|
551
|
-
| 문장 경계를 마침표가 아니라 **한국어 종결어미 + 마침표**로 잡는다 (`다.` `요.` `오.`) | 마침표만 쓰면 `"0.52초"`, `"2026.08.10"`, `"5.9MB"` 같은 숫자·날짜에서 오분절한다. 한국어 서면체 종결은 이 셋으로 거의 닫히므로 lookbehind로 두면 **오분절률이 사실상 0** | 날짜·소수점마다 문단이 갈라져 문서가 부서진다 |
|
|
552
|
-
| lookbehind 뒤에 `\s+`를 요구 | 문단 끝 마침표에서는 안 끊긴다 | 마지막에 빈 문단이 생긴다 |
|
|
553
|
-
| **`전체 길이 > 180자` AND `문장 3개 이상`** 두 조건을 AND로 | 길이만 보면 한 문장짜리 긴 문장을 쪼갤 수 없어 무의미하게 통과한다. 문장 수만 보면 짧은 문장 3개(예: 확인 방법 문구)까지 3문단으로 쪼개져 **파편처럼 보인다** | 둘 중 하나만 쓰면 반대쪽 실패가 그대로 남는다 |
|
|
554
|
-
| **2문장씩** 묶는다 | 1문장/문단은 개조식으로 보여 보고서 톤이 깨지고, 3문장 이상은 다시 덩어리가 된다 | 리듬이 무너진다 |
|
|
555
|
-
| 나머지 홀수 문장은 마지막 문단에 붙인다 | 1문장짜리 꼬리 문단을 만들지 않는다 | 끝에 외톨이 문단이 생긴다 |
|
|
258
|
+
---
|
|
556
259
|
|
|
557
|
-
|
|
260
|
+
## 4. 고객 언어 치환
|
|
558
261
|
|
|
559
|
-
|
|
560
|
-
<p>사전상담 진입 시 해당 고객 정보의 실제 주인이 로그인한 계정인지 확인하도록 바꿨습니다. 주인이 없는 고객 정보는 번호가 일치하는 계정에 연결되고, 이미 다른 계정의 정보라면 번호가 같아도 진입이 차단됩니다.</p>
|
|
561
|
-
<p>이 수정은 운영 환경에 반영된 것을 확인했습니다. 함께 전체 계정을 조사한 결과, 사용 중인 계정 78개 중 전화번호가 겹치는 경우는 2건(계정 5개)뿐이고 모두 주인이 정해져 있으며, 주인이 없는 고객 정보는 없었습니다.</p>
|
|
562
|
-
```
|
|
262
|
+
### 4.1 ⚠ `text_fix` 는 관보 경로에서 돌지 않는다
|
|
563
263
|
|
|
564
|
-
|
|
565
|
-
단위와 맞아떨어진 것은 우연이 아니다 — **조치 서술이 대개 "변경 → 검증" 두 박자로 쓰이기 때문**이다.
|
|
264
|
+
**SKILL.md 와 예시 데이터가 아직 반대로 적고 있는 지점이라 먼저 적음.**
|
|
566
265
|
|
|
567
|
-
|
|
266
|
+
`build.py:516-522` 가 치환을 적용함.
|
|
267
|
+
그런데 그 코드는 `build_gazette()` 가 돌아간 **뒤**의 옛 경로에 있음(`build.py:464-465` 에서 갈림).
|
|
268
|
+
`build_gazette()` 는 `text_fix` 를 한 번도 읽지 않음.
|
|
568
269
|
|
|
569
|
-
|
|
270
|
+
실측(예시 데이터에 `{"from":"조회 색인","to":"ZZZTESTZZZ"}` 를 넣고 렌더).
|
|
570
271
|
|
|
571
272
|
```
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
어느 경로든: e() 이스케이프 → link() 적용
|
|
273
|
+
ZZZTESTZZZ 0회
|
|
274
|
+
조회 색인 1회 ← 원문 그대로 살아남음
|
|
575
275
|
```
|
|
576
276
|
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
| `e()`(`html.escape(..., quote=True)`)를 **먼저**, `link()`를 **나중에** | `link()`가 생성한 `<a>` 태그는 이스케이프 대상이 아니다 | 순서를 바꾸면 생성한 `<a href="#s3-1">`가 통째로 이스케이프되어 **태그 소스가 화면에 그대로 보인다** |
|
|
580
|
-
|
|
581
|
-
### 5.6 청킹을 적용하지 **않는** 자리
|
|
582
|
-
|
|
583
|
-
`summary`(01 요약)와 `open_items`(05 확인 요청), `lede`, `cta`, `note.paragraphs`는
|
|
584
|
-
`chunk()`를 거치지 않고 `link(e(...))`만 통과한다.
|
|
585
|
-
|
|
586
|
-
| 규칙 | 근거 | 어기면 생기는 일 |
|
|
587
|
-
|---|---|---|
|
|
588
|
-
| 이미 구조화된 텍스트에는 기계 분절을 걸지 않는다 | 요약은 `lead`(한 문장 단언) + `body`(부연)로 **집필 단계에서 이미 구조화**되어 있다 | 구조 위에 구조를 얹으면 리듬이 깨진다. `lead`가 두 동강 나 단언의 힘이 사라진다 |
|
|
589
|
-
|
|
590
|
-
> **원칙: 청킹은 "구조 없는 산문"에만 쓰는 도구다.** 입력이 이미 구조를 갖고 있으면 그 구조를 신뢰한다.
|
|
591
|
-
|
|
592
|
-
---
|
|
593
|
-
|
|
594
|
-
## 6. `text_fix` — 고객 언어 치환
|
|
595
|
-
|
|
596
|
-
### 6.1 형식과 적용 범위
|
|
597
|
-
|
|
598
|
-
```json
|
|
599
|
-
"text_fix": [
|
|
600
|
-
{"from": "예약 정보가 없어도 저장이 정상 처리되도록 저장 경로 6곳을 수정했고,",
|
|
601
|
-
"to": "예약 정보가 없어도 저장이 정상 처리되도록 저장이 일어나는 모든 경로를 수정했고,"}
|
|
602
|
-
]
|
|
603
|
-
```
|
|
277
|
+
MCP 발행 스키마에도 `text_fix` 칸이 없음(`mcp/genetics.mjs`).
|
|
278
|
+
서버 화이트리스트는 통과시키지만(`report_publish.php:45`) 렌더러가 안 읽으므로 결과는 같음.
|
|
604
279
|
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
`
|
|
280
|
+
> **귀결: 고객 언어 치환은 이제 원고 단계에서 사람이 한다.**
|
|
281
|
+
> `cases.json` 에 처음부터 고객 언어로 적음.
|
|
282
|
+
> 예시 데이터에 `text_fix` 가 없는 것은 그래서임 — 본문이 이미 치환된 상태로 쓰였음.
|
|
283
|
+
> 어딘가에 남아 있는 `text_fix` 를 보면 **그 문서는 치환이 안 된 것**으로 의심할 것.
|
|
608
284
|
|
|
609
|
-
|
|
610
|
-
때문이다. 어떤 내부 용어를 어떤 고객 언어로 바꿨는지가 그 프로젝트의 판단이고, 다음 회차에
|
|
611
|
-
그대로 재사용된다.
|
|
285
|
+
### 4.2 치환 유형 — 원고를 쓰면서 적용한다
|
|
612
286
|
|
|
613
|
-
|
|
287
|
+
내부 기록을 그대로 옮기면 걸리는 자리 열 가지임.
|
|
288
|
+
**전부 개조식으로 적음** — 6장의 어휘 게이트가 경어체를 0건으로 요구함.
|
|
614
289
|
|
|
615
|
-
|
|
|
616
|
-
|---|---|---|
|
|
617
|
-
| **내부 티켓 번호·트래커 이름** | 문서에 절대 싣지 않음 | `forbid` 정규식 → **빌드 실패** |
|
|
618
|
-
| **개발 배포 서술** | "배포" → "운영 반영", 스토어 빌드 → 앱 업데이트 | `text_fix` + `forbid`에 `운영 배포`·`스토어 빌드` |
|
|
619
|
-
| **내부 검증 산출물** | 코드 규모·테스트 건수 등 수치 제거, 보장 범위 서술로 | `text_fix` + `forbid`에 `자동 검증 \d건` |
|
|
620
|
-
| **설계 검토 이력** | 채택되지 않은 대안 논의는 **통째로 삭제** | `text_fix`의 `"to": ""` |
|
|
621
|
-
|
|
622
|
-
### 6.3 치환 유형 분류표
|
|
623
|
-
|
|
624
|
-
모든 예시는 예시 프로젝트 `report.json`의 `text_fix`에서 그대로 인용했다.
|
|
625
|
-
|
|
626
|
-
| # | 유형 | 변환 방향 | 왜 | 실제 예 (from → to) |
|
|
290
|
+
| # | 유형 | 방향 | 왜 | 예 (내부 → 고객) |
|
|
627
291
|
|---|---|---|---|---|
|
|
628
|
-
|
|
|
629
|
-
|
|
|
630
|
-
|
|
|
631
|
-
|
|
|
632
|
-
|
|
|
633
|
-
|
|
|
634
|
-
|
|
|
635
|
-
|
|
|
636
|
-
|
|
|
637
|
-
|
|
|
638
|
-
|
|
639
|
-
### 6.4 허용/금지의 경계선
|
|
640
|
-
|
|
641
|
-
| 금지 | 허용 | 왜 |
|
|
642
|
-
|---|---|---|
|
|
643
|
-
| `별도 티켓` (`forbid`에 등재) | `별도 건` / `별건` | **"건"은 고객도 세는 단위이고, "티켓"은 우리 도구의 이름이다.** 실제로 예시 프로젝트 3.3건 `cause`에 `"(3)(4)는 원인이 달라 별도 건으로 분리했습니다."`가 그대로 살아 있다 |
|
|
292
|
+
| A | 검수 환경·계정 지칭어 | 조직 내부 명칭 → **용도**로 지칭 | 고객에게 「테스트 매장」은 **자기 매장 중 하나**로 읽힘 | `테스트 매장의 테스트 고객에 올려` → `검수용 매장·고객으로 올려` |
|
|
293
|
+
| B | 배포 이벤트 → 상태 서술 | 「배포했다」 → 「운영에 반영됨」. ISO 날짜 → 한국어 날짜 | 고객은 릴리스 절차를 모르고 알 필요도 없음. 알아야 할 것은 「지금 내 쪽에 적용돼 있나」뿐 | `2026-08-09 운영 배포를 마쳤고` → `2026년 8월 9일 운영 환경에 반영함` |
|
|
294
|
+
| C | 티켓 분리 서술 → **문서 내 절 참조** | 「별도 티켓으로 뺐다」 → 「§5.1 에 정리함」 | 티켓은 고객이 볼 수 없는 곳임. 같은 문서 안의 좌표로 바꾸면 **「빠진 것」이 「다른 데 적힌 것」이 됨** | `별도 티켓으로 분리해 처리함` → `§5.1 에 따로 정리함` |
|
|
295
|
+
| D | 내부 작업 규모 수치 제거 | 코드·테스트 카운트 → **보장 범위** 서술 | 「6곳」은 고객이 검증할 수 없는 수치라 신뢰를 못 만들고 **「왜 6곳뿐?」이라는 불안만** 만듦 | `저장 경로 6곳을 수정함` → `저장이 일어나는 모든 경로를 수정함` |
|
|
296
|
+
| E | 배포 채널 용어 → 사용자 체감 사건 | 스토어 빌드·버전 → 「앱 업데이트」 | 현장 인원의 세계에서 일어나는 사건은 「앱이 업데이트된다」뿐임 | `다음 스토어 빌드부터 적용됨` → `다음 앱 업데이트부터 적용됨` |
|
|
297
|
+
| F | 기술 명사 → 관찰 가능한 대상 | 쿠키·세션·업체 → 화면에서 보이는 것 | 독자가 그 명사를 **눈으로 확인할 수 없으면** 문장이 검증 불가능해짐 | `세션이 잠깐 되살아나던` → `로그인 상태가 잠깐 되살아나던` |
|
|
298
|
+
| G | 증거의 출처를 내부 도구에서 지움 | 「서버 응답으로 확인」 → 「직접 확인」 | 확인했다는 **사실**이 요점이고, 어느 계층에서 봤는지는 내부 사정 | `서버 응답으로 직접 확인함` → `직접 확인함` |
|
|
299
|
+
| H | 한 문장 두 사실 → 두 문장 재편 | 복문 해체 | 3.3 의 문장 길이 규칙과 같은 뿌리임 | `같은 배포에 …(이전에는 잠시 남아 보임)이 함께 포함됐음` → `함께 반영된 수정이 하나 더 있음. 녹음을 삭제하면 일지 상세에서도 즉시 사라짐.` |
|
|
300
|
+
| I | 내부 관용어 → 중립 어휘 | 팀 은어 정규화 | 같은 개념을 두 이름으로 부르면 독자가 **다른 것으로** 읽음 | `자동 정리 방식` → `일괄 정리 방식` |
|
|
301
|
+
| J | **삭제** | 설계 검토 이력·내부 잔여 정리는 서술 대상이 아님 | 채택되지 않은 대안은 **고객의 판단·행동을 바꾸지 않음.** 문서에 남으면 「아직 안 끝났다」로만 읽힘 | `처음 검토안 중 …은 제외함` → (통째로 삭제) |
|
|
644
302
|
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
303
|
+
**경계선 하나.**
|
|
304
|
+
`별도 티켓` 은 금지, `별건`·`별도 건` 은 허용.
|
|
305
|
+
「건」은 고객도 세는 단위이고 「티켓」은 우리 도구의 이름임.
|
|
648
306
|
|
|
649
|
-
###
|
|
307
|
+
### 4.3 `forbid` — 로컬 렌더에서만 산다
|
|
650
308
|
|
|
651
|
-
|
|
309
|
+
`forbid` 는 산출물 전체 문자열에 거는 금칙어 정규식 목록임.
|
|
310
|
+
`guard()` 가 **파일을 쓰기 전에** 검사하고, 걸리면 빌드가 죽음(`build.py:394-396`).
|
|
652
311
|
|
|
653
|
-
|
|
312
|
+
실측(예시에 `"forbid": ["조회 색인"]` 을 넣고 렌더).
|
|
654
313
|
|
|
655
|
-
```
|
|
656
|
-
|
|
657
|
-
{"from": "별도 건으로 분리해 처리 중입니다.", "to": "§4.5에 따로 정리했습니다."}
|
|
314
|
+
```
|
|
315
|
+
AssertionError: 금지 표현 유출: 조회 색인 → ['조회 색인']
|
|
658
316
|
```
|
|
659
317
|
|
|
660
|
-
|
|
661
|
-
|
|
318
|
+
| 경로 | `forbid` 가 도는가 |
|
|
319
|
+
|---|---|
|
|
320
|
+
| `python3 assets/render.py report.json` | **돈다.** 위 실측 |
|
|
321
|
+
| MCP 발행(`publish_report`·`update_report`) | **안 돈다.** `REPORT_CLIENT_KEYS` 에 없어 서버가 입구에서 깎아 냄(`report_publish.php:31-67`·주석 76행) |
|
|
662
322
|
|
|
663
|
-
|
|
323
|
+
> **귀결: 포털 발행만 하는 문서에는 금칙어 관문이 없다.**
|
|
324
|
+
> 그 자리를 메우는 것은 `dry_run` 의 `warnings` 와 사람의 눈임.
|
|
325
|
+
> 로컬 렌더를 한 번 돌려 `forbid` 를 통과시키는 것이 지금으로서는 유일한 자동 방어임.
|
|
664
326
|
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
327
|
+
한국어 금칙어의 함정 하나.
|
|
328
|
+
**한국어에는 낱말 경계가 없음** — `머지` 가 「**나**머지 절은」을 잡음.
|
|
329
|
+
`\b` 도 전방탐색도 안 들음(뒤에 공백이 오므로).
|
|
330
|
+
**앞**을 막아야 함: `(?<!나)머지`.
|
|
668
331
|
|
|
669
|
-
|
|
670
|
-
**치환의 연쇄가 새 결함을 만든다**는 실례.
|
|
332
|
+
### 4.4 치환 규칙 ↔ 금칙어 1:1 짝짓기
|
|
671
333
|
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
| 완전 일치 치환은 공백·개행 변형에 **무방비**하고, 빗나가도 **조용하다** | 8장의 `forbid`가 반드시 짝으로 있어야 한다 |
|
|
675
|
-
| 치환의 연쇄가 새 결함(이중어)을 만든다 | 치환 규칙 순서에 의존하지 말고, 최종 산출물을 사람이 한 번 읽는다 |
|
|
676
|
-
| 빌드가 규칙별 발화 횟수를 세지 않는다 | 사문 규칙과 빗나간 규칙을 구분할 수 없다. 규칙을 지울 때는 손으로 확인한다 |
|
|
677
|
-
|
|
678
|
-
### 6.6 ★ 가장 이식 가치가 높은 패턴 — 치환 규칙 ↔ `forbid` 1:1 짝짓기
|
|
334
|
+
치환을 사람이 하게 되면서 **빗나감이 더 조용해졌음.**
|
|
335
|
+
그래서 짝짓기가 더 중요해짐.
|
|
679
336
|
|
|
680
337
|
| `forbid` 패턴 | 짝지어진 방어 대상 |
|
|
681
338
|
|---|---|
|
|
682
|
-
|
|
|
683
|
-
|
|
|
684
|
-
| `운영 배포` |
|
|
685
|
-
| `별도 티켓` |
|
|
686
|
-
| `자동 검증 \d건` |
|
|
687
|
-
| (별도 등재 없음 — `assert '§' not in doc`) | `link()`를 안 타는 텍스트가 생김 |
|
|
688
|
-
|
|
689
|
-
**논리:** 완전 일치 치환은 원문이 한 글자만 바뀌어도 **조용히** 실패한다(6.5의 `\n` 사례가 실증).
|
|
690
|
-
그래서 **치환 규칙을 추가할 때마다, 그 규칙이 없앴어야 할 금칙어를 `forbid`에 함께 등록한다.**
|
|
691
|
-
치환이 빗나가면 금칙어가 살아남고, **빌드가 그 자리에서 죽는다.**
|
|
339
|
+
| 티켓 번호 숫자 패턴 | 번호가 어디선가 새어 들어옴 |
|
|
340
|
+
| 트래커 이름 | 이름 자체가 새어 들어옴 |
|
|
341
|
+
| `운영 배포` | 유형 **B** 를 빠뜨림 |
|
|
342
|
+
| `별도 티켓` | 유형 **C** 를 빠뜨림 |
|
|
343
|
+
| `자동 검증 \d건` | 유형 **D** 를 빠뜨림 |
|
|
692
344
|
|
|
693
|
-
> **규칙:
|
|
694
|
-
|
|
695
|
-
`forbid`는 `text_fix`가 적용되지 않는 자리(`summary`, `open_items`, `lede`, 건 `title`)도
|
|
696
|
-
포함해 **산출물 전체 문자열**을 검사한다. 그래서 요약을 사람이 새로 쓰다가 "운영 배포"를
|
|
697
|
-
적어도 빌드가 잡는다.
|
|
345
|
+
> **규칙: 치환 유형을 하나 정할 때마다, 그 유형이 없앴어야 할 낱말을 `forbid` 에 함께 등록한다.**
|
|
346
|
+
> 짝이 없는 치환 규칙은 방어되지 않음.
|
|
698
347
|
|
|
699
348
|
---
|
|
700
349
|
|
|
701
|
-
##
|
|
350
|
+
## 5. 제목 재작성
|
|
702
351
|
|
|
703
|
-
###
|
|
352
|
+
### 5.1 원칙
|
|
704
353
|
|
|
705
|
-
`cases[].title
|
|
706
|
-
|
|
354
|
+
`cases[].title` 은 **처음부터 보고서 표제로 씀.**
|
|
355
|
+
티켓 제목을 옮겨 놓고 나중에 고치는 자리가 아님 — 렌더러에 제목 덮어쓰기 표 같은 것은 없음.
|
|
707
356
|
|
|
708
|
-
**티켓 제목은
|
|
709
|
-
재사용하지 않고 전건 새로
|
|
357
|
+
**티켓 제목은 「찾기 위한 문자열」이고 보고서 표제는 「읽기 위한 문장」임.**
|
|
358
|
+
목적이 다르므로 재사용하지 않고 전건 새로 씀.
|
|
710
359
|
|
|
711
|
-
###
|
|
360
|
+
### 5.2 버리는 것 / 남기는 것
|
|
712
361
|
|
|
713
|
-
|
|
|
362
|
+
| 버림 | 이유 |
|
|
714
363
|
|---|---|
|
|
715
|
-
| 경로 구분자 `>` (`예약 >
|
|
364
|
+
| 경로 구분자 `>` (`예약 > 일지 작성 버그`) | 티켓 트리 좌표. 문서에는 절 번호가 이미 있음 |
|
|
716
365
|
| 일련번호 `#2` | 티켓 안에서만 뜻이 통하는 구분자 |
|
|
717
|
-
| 워크플로 태그 `[후속]` | **우리 팀의 작업 상태**이지 문제의 성질이
|
|
718
|
-
| 심각도 라벨 `(심각)` | 심각도는 **본문 증상이 스스로
|
|
719
|
-
| 불확실 표시 `?`
|
|
720
|
-
| 내부 기술어 (`RN 앱`·`토큰 부활
|
|
721
|
-
|
|
|
722
|
-
| 복수 증상 접속 `+` | 대표 증상 하나로
|
|
723
|
-
| 대시 `—` 대비 구조 |
|
|
724
|
-
| 외래어 (`리젝`) | `반려`로 |
|
|
725
|
-
|
|
726
|
-
| 남긴다 | 이유 |
|
|
727
|
-
|---|---|
|
|
728
|
-
| 영향의 크기를 알리는 괄호 부기 | 예시 프로젝트 실측 2개뿐: `(앱 심사 반려 사유)`, `(방침 확정)`. **"이건 왜 중요한가 / 왜 결함이 아닌가"를 제목만으로 알려야 할 때만** |
|
|
366
|
+
| 워크플로 태그 `[후속]` | **우리 팀의 작업 상태**이지 문제의 성질이 아님 |
|
|
367
|
+
| 심각도 라벨 `(심각)` | 심각도는 **본문 증상이 스스로 말해야 함.** 라벨은 독자의 판단을 대신해 버림 |
|
|
368
|
+
| 불확실 표시 `?` | 제보 시점의 추측. **보고서는 원인이 확정된 뒤에 쓰임** |
|
|
369
|
+
| 내부 기술어 (`RN 앱`·`토큰 부활 경합`·`세션`) | 독자 어휘가 아님 |
|
|
370
|
+
| 낱말 `버그`·`오류`·`이슈` · 외래어 `리젝` | 명사가 아니라 **증상 서술**로 대체. `리젝` 은 `반려` 로 |
|
|
371
|
+
| 복수 증상 접속 `+` | 대표 증상 하나로 좁힘 |
|
|
372
|
+
| 대시 `—` 대비 구조 | 어휘 게이트가 밀도로 잡음 (6장) |
|
|
729
373
|
|
|
730
|
-
|
|
374
|
+
**남기는 것은 하나뿐임** — 영향의 크기를 알리는 괄호 부기.
|
|
375
|
+
`(앱 심사 반려 사유)` 처럼 「이건 왜 중요한가」를 제목만으로 알려야 할 때만 씀.
|
|
731
376
|
|
|
732
|
-
|
|
733
|
-
**최종 자산에는 남기지 않는다**(3.2).
|
|
377
|
+
### 5.3 before / after
|
|
734
378
|
|
|
735
379
|
| before (티켓) | after (표제) | 버린 것 |
|
|
736
380
|
|---|---|---|
|
|
737
|
-
| `예약
|
|
738
|
-
|
|
|
739
|
-
|
|
|
740
|
-
| `AI 녹음 관련 버그 > 컨설팅일지 동기화 버그?` | `저장한 AI 상담이 컨설팅 일지에 나타나지 않던 문제` | `>` 경로, `버그` 2회, `?` 추측, `동기화` → **사용자 행위(저장) + 관찰 결과(나타나지 않음)** |
|
|
741
|
-
| `회원탈퇴해도 탈퇴가 안되고 로그인되는 이슈 (앱 심사 리젝 사유)` | `앱에서 회원탈퇴한 계정이 다시 로그인되던 문제 (앱 심사 반려 사유)` | `이슈`, 외래어 `리젝`. **괄호 부기는 유지** — 영향의 크기를 알리므로 |
|
|
742
|
-
| `[후속] RN 앱 토큰 부활 경합 — 탈퇴/로그아웃 직후 세션이 되살아남` | `로그아웃·탈퇴 직후 이전 로그인 상태로 되돌아가던 문제` | `[후속]`, `RN`·`토큰`·`경합`, `—` 대시, `세션` → `이전 로그인 상태` |
|
|
381
|
+
| `예약 > 컨설팅 일지 작성 버그 (심각)` | `같은 날 두 번째 예약의 일지에 이전 내용이 잘못 채워지던 상황` | `>` 경로 · `버그` · `(심각)` → **조건 + 현상** |
|
|
382
|
+
| `AI 녹음 관련 버그 > 일지 동기화 버그?` | `저장한 상담이 일지에 나타나지 않던 상황` | `>` · `버그` 2회 · `?` 추측 → **행위 + 관찰 결과** |
|
|
383
|
+
| `[후속] RN 앱 토큰 부활 경합 — 탈퇴 직후 세션이 되살아남` | `로그아웃·탈퇴 직후 이전 로그인 상태로 되돌아가던 상황` | `[후속]` · `RN`·`토큰`·`경합` · `—` 대시 · `세션` |
|
|
743
384
|
|
|
744
|
-
###
|
|
385
|
+
### 5.4 시제·어미 — 한 글자가 문서 인상을 뒤집는다
|
|
745
386
|
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
| 규칙 | 근거 | 어기면 생기는 일 |
|
|
387
|
+
| 규칙 | 근거 | 어기면 |
|
|
749
388
|
|---|---|---|
|
|
750
|
-
| 표제는 **과거형**(`…되던
|
|
751
|
-
|
|
|
752
|
-
|
|
|
753
|
-
|
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
같은 원인에서 갈라져 나온 건은 티켓 제목이 똑같아지기 쉽다. **제목만 보고 두 건이 다르다는 걸
|
|
758
|
-
알 수 있어야 한다.** 위 표 첫 줄이 그 사례다 — 두 건의 티켓 제목이 문자 그대로 동일했고,
|
|
759
|
-
각각의 **증상 실체**(`다른 고객의 정보가 열리던` / `사전상담이 열리지 않던`)로 다시 썼다.
|
|
389
|
+
| 표제는 **과거형**(`…되던 상황`)으로 씀 | 과거형이 「지금은 고쳐졌다」를 제목만으로 전달함. 티켓은 문제가 살아 있을 때 쓰이므로 현재형이지만, 보고서는 조치가 끝난 뒤 쓰이므로 과거형임 | 현재형(`…되는 문제`)은 **목차만 훑는 결정권자에게 미해결 목록으로 읽힘.** 다 고쳐 놓고 다 남은 것처럼 보이는 최악의 결과 |
|
|
390
|
+
| 어미는 **`…던 상황`** 을 기본으로 씀 | 개조식 전환 뒤의 표기임. 예시 두 건 모두 `…던 상황` 임 | `…던 문제` 도 통하지만 문서 안에서 섞어 쓰면 두 종류가 있는 것처럼 읽힘 |
|
|
391
|
+
| **결함이 아닌 항목은 다른 어미로 끝냄** | 방침 반영(`… 동작 제거 (방침 확정)`) · 예방적 정리(`… 연결 기록 정리`) | 전부 「문제/상황」으로 통일하면 **없던 결함을 만들어 냄.** 어미가 항목의 성격 분류자 역할을 함 |
|
|
392
|
+
| 명사구 종결은 **허용** | 목차로 훑는 항목이라 완전문으로 바꾸면 오히려 읽기 어려움 | 제목이 문장이 되어 목차가 장황해짐 |
|
|
393
|
+
| **요약의 `lead` 와 확인 요청의 `title` 은 완전문** | 여기가 판단이 일어나는 자리임 | `표본 부족` 같은 명사구는 심각도를 감춤 |
|
|
394
|
+
| 같은 원인에서 갈라진 건은 **증상 실체로 갈라 씀** | 티켓 제목이 똑같아지기 쉬움. 제목만 보고 두 건이 다르다는 걸 알 수 있어야 함 | `다른 고객의 정보가 열리던` / `사전상담이 열리지 않던` 처럼 안 가르면 중복으로 읽힘 |
|
|
760
395
|
|
|
761
396
|
---
|
|
762
397
|
|
|
763
|
-
##
|
|
398
|
+
## 6. 어휘·톤 게이트
|
|
764
399
|
|
|
765
|
-
|
|
400
|
+
폐기한 `qa.py` 의 카피 게이트를 회수한 자리임.
|
|
401
|
+
지금 이 검사를 실제로 도는 것은 `gazette-report` 의 `tone.mjs` 임.
|
|
402
|
+
아래는 그 검사기가 **무엇을 재는가**임 — 실행법은 그쪽 스킬에 있음.
|
|
766
403
|
|
|
767
|
-
|
|
404
|
+
### 6.1 S1 — 한 번만 나와도 고친다
|
|
768
405
|
|
|
769
|
-
| 검사 |
|
|
770
|
-
|---|---|---|
|
|
771
|
-
| 그룹 미배정 건 | `assert placed == len(cases)` | 오타 하나로 건이 문서에서 통째로 사라짐 |
|
|
772
|
-
| 팔레트 대비 미달 | `assert not bad` (`P.audit` 17쌍) | 읽을 수 없는 색 조합이 나감 |
|
|
773
|
-
| 원고 마커 잔존 | `assert '§' not in doc` | `link()`를 안 타는 텍스트가 생겨 `§`가 인쇄됨 |
|
|
774
|
-
| 금칙어 유출 | `for pat in cfg.get('forbid', []): assert not re.findall(pat, doc)` — `forbid`는 선택이라 없으면 이 검사가 통째로 비활성이다 | 티켓 번호·내부 용어가 고객본에 나감 |
|
|
775
|
-
| 미정의 등급 | (암묵) `GCLASS[c['grade']]` → `KeyError` | 등급 오타 |
|
|
776
|
-
| 필수 필드 누락 | (암묵) `c["symptom"]` 등 → `KeyError` | 스키마 위반 |
|
|
777
|
-
|
|
778
|
-
성공하면 이렇게 찍는다. **이 세 줄을 눈으로 확인한다.**
|
|
779
|
-
|
|
780
|
-
```
|
|
781
|
-
생성 · ../고객사-수정내역-20260810.html 523,464B
|
|
782
|
-
폰트 임베드 300,544B → base64 400,728B
|
|
783
|
-
건 18 · 등급 {'실데이터 대조': 4, '운영 환경 확인': 10, '재발 점검 적용': 1, '반영 확인': 3} · 확인요청 6
|
|
784
|
-
유출 점검: 통과
|
|
785
|
-
```
|
|
786
|
-
|
|
787
|
-
| 줄 | 확인할 것 |
|
|
788
|
-
|---|---|
|
|
789
|
-
| 폰트 | `폰트 임베드 생략`이 찍혔는데 `font.embed`를 끈 적이 없다면 파일 경로가 틀렸다 |
|
|
790
|
-
| 건 수 | `report.json`의 `stats`·`meta`·절 이름에 적은 숫자와 **손으로 대조한다** (8.3) |
|
|
791
|
-
| 등급 분포 | 특정 등급에 몰려 있으면 등급을 성의 없이 매긴 것이다 |
|
|
792
|
-
|
|
793
|
-
### 8.2 `qa.py`로 이어지는 것
|
|
794
|
-
|
|
795
|
-
빌드는 **원고 규칙**만 본다. 산출물 검사는 `qa.py`가 맡는다(항목 전체는 `references/qa.md`).
|
|
796
|
-
콘텐츠 파이프라인과 직결되는 것만 옮기면:
|
|
797
|
-
|
|
798
|
-
| 검사 | 잡는 것 |
|
|
406
|
+
| 검사 | 기준 |
|
|
799
407
|
|---|---|
|
|
800
|
-
|
|
|
801
|
-
|
|
|
802
|
-
| 죽은 절 참조 — `href="#sN-M"`가 실제 존재하는 `id`를 가리키는가 | 지운 건을 참조하는 `§`가 남음 |
|
|
803
|
-
| `§` 잔존 · `forbid` 패턴 | 빌드 assert와 **같은 검사를 산출물에서 한 번 더** |
|
|
804
|
-
| 카피 게이트 — 헤드라인 대시, AI 슬롭 어휘 8종 | 7.4의 제목 규칙 |
|
|
805
|
-
| 고객본 내보내기 시뮬레이션 | 편집기 레이어가 실제로 걷어내지는가 (아래) |
|
|
806
|
-
|
|
807
|
-
**예시 프로젝트 현재 상태: 통과 48 · 경고 1 · 실패 0.** 경고 1은 `report.css`에 남은
|
|
808
|
-
`rgba()` 리터럴 2건(표지 구분선, xref 밑줄)이다 — 팔레트와 대조할 수 없다는 경고이지 오류가 아니다.
|
|
408
|
+
| **경어체 잔존** | `습니다 · 십시오 · 합니다 · 입니다 · 됩니다 · 드립니다 · 주세요 · 하세요 · 해요` → **0건** |
|
|
409
|
+
| **한 문장 한 줄** | 문장 경계가 있는데 `<span class="sn">` 도 `<br>` 도 없는 블록 → **0개** |
|
|
809
410
|
|
|
810
|
-
|
|
411
|
+
개조식은 `~함 / ~했음 / ~필요 / ~한 상황` 임.
|
|
412
|
+
문체를 안 바꾸면 관보 문법 위에서 겉돎 — **낱말이 아니라 조판의 문제임.**
|
|
811
413
|
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
| 자리 | 예시 |
|
|
815
|
-
|---|---|
|
|
816
|
-
| `subtitle` | `"… 처리한 18건의 수정 내역과 …"` |
|
|
817
|
-
| `description` | `"… 18건의 수정 내역과 확인 요청 사항"` |
|
|
818
|
-
| `meta` | `["처리 건수", "18건 (접수 9 · 자체 발견 9)"]` |
|
|
819
|
-
| `stats[].value` | `"18"`, `"9"`, `"9"`, `"6"` |
|
|
820
|
-
| `sections[].name` | `"접수하신 9건"`, `"함께 발견해 처리한 9건"` |
|
|
821
|
-
| `sections[].lede` | `"18건 전부의 처리 결과와 …"` |
|
|
414
|
+
### 6.2 S2 — 늘어짐 지표 (천 자당 밀도)
|
|
822
415
|
|
|
823
|
-
|
|
824
|
-
빌드 로그의 `건 18 · … · 확인요청 6`을 위 자리들과 손으로 대조하는 것이 유일한 방어다.
|
|
825
|
-
건 수가 바뀌는 작업을 했다면 **이 목록을 체크리스트로 한 번 훑는다.**
|
|
416
|
+
3회 이상 나오면서 한도를 넘을 때만 지적됨.
|
|
826
417
|
|
|
827
|
-
|
|
418
|
+
| 지표 | 한도 | 처방 |
|
|
419
|
+
|---|---|---|
|
|
420
|
+
| 문두 접속사 (`또한`·`따라서`·`즉`·`나아가`·`아울러`·`게다가`·`더욱이`·`그리고`) | 1.5 | 70% 이상 제거. 논리 흐름은 문장 내용으로 표현 |
|
|
421
|
+
| 추측·관측 종결 (`보임`·`판단됨`·`여겨짐`·`듯함`) | 1.0 | 근거가 있으면 단정. 없으면 「확인하지 못함」으로 범위를 명시 |
|
|
422
|
+
| 이중 완곡 (`가능성이 있을 수`·`수도 있을`) | 0.2 | 완곡은 하나만 |
|
|
423
|
+
| 「이는 ~」 지시 (`이는 `·`이 점에서`·`이 말은`) | 1.0 | 앞 문장과 통합. 메타 진입 삭제 후 본 서술 직진 |
|
|
424
|
+
| 형식명사 결합 (`라는 점에 있`·`다는 것이`·`할 필요가 있`) | 1.0 | 구체 명사·동사로 치환 |
|
|
425
|
+
| 괄호 부연 (6자 이상) | 2.5 | 근거·출처 표기는 남기고 설명 부연은 본문화 |
|
|
426
|
+
| 낫표 `「」` | 5.0 | 용어 첫 등장 1회만. 이후는 평문 |
|
|
427
|
+
| 대시 `—` | 0.5 | 쉼표·괄호·별도 문장으로 분해 |
|
|
428
|
+
| 인라인 강조 (`<b>`·`<strong>`) | 5.0 | 굵게는 요지에만. 구조 라벨은 세지 않음 |
|
|
828
429
|
|
|
829
|
-
|
|
830
|
-
`<!-- REPORT-EDITOR:BEGIN --> … <!-- REPORT-EDITOR:END -->`로 감싸 출력한다.
|
|
831
|
-
`editor.js`의 **고객본 내보내기가 정확히 이 표식을 찾아 들어낸다.**
|
|
832
|
-
`qa.py`가 그 내보내기를 시뮬레이션해 잔존물(`data-rte-ui`, `data-report-edit`, …)을 검사한다.
|
|
430
|
+
### 6.3 게이트가 못 잡는 것 — 건수 리터럴
|
|
833
431
|
|
|
834
|
-
|
|
432
|
+
아래 자리에 적은 숫자는 **데이터에서 파생되지 않음.**
|
|
835
433
|
|
|
836
|
-
|
|
|
434
|
+
| 자리 | 예 |
|
|
837
435
|
|---|---|
|
|
838
|
-
|
|
|
839
|
-
|
|
|
840
|
-
|
|
|
436
|
+
| `subtitle` · `description` | `이번 회차에 처리한 2건과 …` |
|
|
437
|
+
| `meta` | `["처리 건수", "2건 (접수 1 · 자체 발견 1)"]` |
|
|
438
|
+
| `stats[].value` | `"2"` · `"1"` |
|
|
439
|
+
| `sections[].name` | `접수하신 1건` |
|
|
440
|
+
| `sections[].lede` · `notice.text` · `bar[].value` | `접수 1건 처리 완료` |
|
|
441
|
+
| `remain` | 생략하면 `open_items` 개수로 자동. **적으면 그 값이 이김** |
|
|
841
442
|
|
|
842
|
-
|
|
443
|
+
**건을 추가·삭제하면 이 숫자들이 조용히 거짓말을 함.**
|
|
444
|
+
렌더 로그의 `절 5 · 건 2 · 확인요청 1` 을 위 자리들과 손으로 대조하는 것이 유일한 방어임.
|
|
843
445
|
|
|
844
446
|
---
|
|
845
447
|
|
|
846
|
-
##
|
|
847
|
-
|
|
848
|
-
새 프로젝트를 시작할 때 순서대로.
|
|
849
|
-
|
|
850
|
-
1. **독자를 못 박는다** (0장). 누가 읽고, 읽고 나서 무엇을 하는가.
|
|
851
|
-
2. **등급 체계를 정한다** (2.3). 강한 순으로 선언하고, 가장 약한 등급의 설명에 한계를 적는다.
|
|
852
|
-
3. **`groups`와 `sections`를 정한다** (2.2, 4.1). `no`를 두 곳에서 일치시킨다.
|
|
853
|
-
4. **건을 채운다** (3장). `how_to_check`는 전건 필수라고 생각하고 채운다.
|
|
854
|
-
5. **제목을 전건 새로 쓴다** (7장). 과거형, 결함 아닌 건은 다른 어미.
|
|
855
|
-
6. **요약 4칸을 쓴다** (2.4). `lead`는 완전문.
|
|
856
|
-
7. **원고에 `§3.1` 참조를 직접 쓴다** (4.2).
|
|
857
|
-
8. **`text_fix`를 쓰면서 `forbid`를 같이 늘린다** (6.6). 짝이 없는 치환은 방어되지 않는다.
|
|
858
|
-
9. **빌드 → 로그 3줄 확인 → 건수 리터럴 대조**(8.3) **→ `qa.py` 실패 0 → 브라우저로 눈 검수.**
|
|
448
|
+
## 7. 이 문서가 책임지는 검수 항목
|
|
859
449
|
|
|
860
|
-
|
|
450
|
+
검수 절차 전체는 `references/process.md` 7단계가 원전임.
|
|
451
|
+
여기서 나온 규칙 중 **기계가 안 잡는 것**만 다시 적음.
|
|
861
452
|
|
|
862
|
-
|
|
|
453
|
+
| 항목 | 무엇을 대조하는가 |
|
|
863
454
|
|---|---|
|
|
864
|
-
|
|
|
865
|
-
|
|
|
866
|
-
|
|
|
867
|
-
|
|
|
868
|
-
| 제목 전건 재작성 + 과거형 어미 | 목차만 훑는 독자에게 상태를 전달 |
|
|
869
|
-
| `e()` → `link()` 순서 고정 | 순서를 바꾸면 태그가 화면에 보인다 |
|
|
870
|
-
| 구조 일관성 검사(각 요소 수 == 건 수) | 반쪽 렌더는 눈으로 못 찾는다 |
|
|
455
|
+
| 번호 정합 | `groups[].no` == 그 그룹을 펴는 `kind:"cases"` 절의 자리 (2.2) |
|
|
456
|
+
| 건수 리터럴 | 렌더 로그의 `절 N · 건 N · 확인요청 N` 을 6.3 의 자리들과 손으로 대조 |
|
|
457
|
+
| 치환 누락 | 4.2 의 열 유형을 원고에서 훑음. `text_fix` 는 안 도므로 사람이 유일한 관문임 |
|
|
458
|
+
| 표기 갈림 | 인라인 `§n.n` 을 쓴 자리가 있으면 `refs[]` 로 옮길 수 있는지 검토 (2.4) |
|
|
871
459
|
|
|
872
460
|
---
|
|
873
461
|
|
|
874
|
-
##
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
손 대조로 남겨 두었다.
|
|
888
|
-
5. **`ol.enum .n` 23px 고정폭에서 `3·4·5`가 실제로 넘치는지.** CSS 값으로는 넘칠 수 있는
|
|
889
|
-
조건이나 브라우저 렌더로 확인하지 않았다. 예시 프로젝트에는 `3·4`까지만 나왔다.
|
|
890
|
-
6. **`labels`로 덮을 수 없는 문구**(판정표의 `있음`/`—`, 범례의 `N건`). 한국어 외 언어로 낼
|
|
891
|
-
요구가 실제로 있을지 미정이라 `build.py`에 남겨 두었다.
|
|
462
|
+
## 8. 미확정 · 확인 못 한 것
|
|
463
|
+
|
|
464
|
+
1. **서버가 실제로 422 를 돌려주는지 런타임으로 확인하지 못함.**
|
|
465
|
+
`report_publish.php:157-287` 의 검증 코드는 읽었고, 어긋난 `kind`·없는 그룹·근거 없는 요약이 오류 목록에 담기는 것까지는 코드로 확인함.
|
|
466
|
+
응답 코드 자체는 확인 못 함.
|
|
467
|
+
2. **`dry_run` 의 `warnings` 가 렌더러의 ⚠ 와 같은 목록인지 확인 못 함.**
|
|
468
|
+
로컬 렌더의 ⚠ 는 실측했음.
|
|
469
|
+
3. **건수 리터럴의 자동화 여부.**
|
|
470
|
+
`{cases:count}` 같은 치환으로 바꿀 수 있으나 미치환 플레이스홀더 검사를 함께 설계해야 함.
|
|
471
|
+
지금은 손 대조로 남겨 둠.
|
|
472
|
+
4. **`assets/example/report.json` 의 `sections[].no`·`id` 는 지금 아무 일도 하지 않음.**
|
|
473
|
+
같은 파일의 `label` 이 실제로 찍히는 값임.
|
|
474
|
+
이 문서에 그 사실을 적었으나 예시 파일 자체는 고치지 않았음 — 이번 작업의 범위 밖임.
|