docs: apply Korean prose contract to README

This commit is contained in:
DongHyeonka
2026-07-29 18:44:50 +09:00
parent a066b89a7a
commit 89e09cbc20
6 changed files with 250 additions and 43 deletions
+81 -41
View File
@@ -1,15 +1,15 @@
# ClariDoc Harness 0.2.0
ClariDoc은 기술 블로그와 기술 문서를 계획·작성·검토·수정하는 멀티 모델 하네스다. 처음 `brief`와 프로젝트 문서를 넣으면 바로 글부터 쓰지 않는다. 로컬 문서 저장소에서 근거를 찾고, 문서 유형에 맞춰 독자가 문제와 선택을 따라갈 순서를 먼저 잡는다. 그다음 Codex, Claude, Google Antigravity가 계획과 작성, 검토와 수정을 나누어 맡다.
저는 ClariDoc을 만들면서 모델이 `brief`와 프로젝트 문서를 받자마자 글부터 쓰지 않게 했습니다. 먼저 로컬 문서 저장소에서 근거를 찾고, 문서 유형에 맞춰 독자가 문제와 선택을 따라갈 순서를 잡습니다. 그다음 Codex, Claude, Google Antigravity가 계획과 작성, 검토와 수정을 나누어 맡습니다. ClariDoc은 이 과정을 연결하는 멀티 모델 하네스입니다.
이 과정에서는 두 가지를 끝까지 지다.
이 과정에서는 두 가지를 끝까지 지킵니다.
1. **근거 추적 정보와 독자용 글을 분리다.** source ID, repository path, access date, prompt tag는 `provenance.md``evidence-map.json`에만 남다.
2. **기술 선택은 이유 없이 선언할 수 없다.** “의도적으로 사용한다”, “허용했다”, “금지했다”라고 썼다면 제약, 선택 이유, 대안, 수용 비용, 가드레일까지 이어져야 다.
1. **근거 추적 정보와 독자용 글을 분리합니다.** source ID, repository path, access date, prompt tag는 `provenance.md``evidence-map.json`에만 남깁니다.
2. **기술 선택은 이유 없이 선언하지 않습니다.** “의도적으로 사용한다”, “허용했다”, “금지했다”라고 썼다면 제약, 선택 이유, 대안, 수용 비용, 가드레일까지 이어져야 합니다.
## 해결하려는 실패
최종 문서에서 다음 문장이 보이면 ClariDoc은 실패로 처리다.
제가 최종 문서에서 막으려 한 것은 다음과 같은 문장입니다. ClariDoc은 이 문장들이 남아 있으면 실패로 처리합니다.
```text
예시는 2026-07-23 기준이다.
@@ -18,9 +18,9 @@ Retries can increase load ... [S1]
application-core는 Spring DI와 SLF4J를 의도적으로 사용한다.
```
처음 세 문장에는 독자가 볼 필요가 없는 작성 과정과 provenance가 섞여 있다. 마지막 문장은 Spring DI를 선택했다는 사실만 있고 **왜 선택했는지**, **무슨 대안을 검토했는지**, **어떤 비용을 감수했는지**, **어디까지 허용했는지**는 알 수 없다.
처음 세 문장에는 독자가 볼 필요가 없는 작성 과정과 provenance가 섞여 있습니다. 마지막 문장은 Spring DI를 선택했다는 사실만 있고 **왜 선택했는지**, **무슨 대안을 검토했는지**, **어떤 비용을 감수했는지**, **어디까지 허용했는지**는 알 수 없습니다.
그래서 ClariDoc 0.2.0은 독자가 읽을 내용과 근거를 추적할 때 필요한 기록을 서로 다른 파일에 남다.
그래서 저는 독자가 읽을 내용과 근거를 추적할 때 필요한 기록을 서로 다른 파일에 남기도록 ClariDoc 0.2.0을 구성했습니다.
```text
reader-facing document.md
@@ -70,7 +70,7 @@ brief.json
## 기술 블로그의 기본 논리 구조
`technical_blog`는 다음 순서를 기본 계약으로 사용한다.
저는 `technical_blog`의 기본 계약을 다음 순서로 정했습니다.
1. **구체적인 문제 장면**: 어떤 상황과 비용이 있었는가
2. **제약**: 단순한 해법을 막은 조건은 무엇인가
@@ -81,13 +81,14 @@ brief.json
7. **트레이드오프**: 얻은 것, 잃은 것, 적용하지 않을 조건은 무엇인가
8. **결론**: 다른 환경에서도 가져갈 판단은 무엇인가
우아한형제들 기술 블로그를 조사하면서 저는 여러 문제 해결 글이 `팀과 시스템의 상황 → 구체적인 문제와 비용 → 검토한 접근 → 선택과 구현 → 검증과 한계` 순서로 이어지는 것을 확인했다. 여기에 독자와 메시지, 개요와 문단 흐름을 다룬 개발자 글쓰기 자료를 더해 위 순서를 만들었다. 우아한형제들의 공식 편집 규정을 그대로 옮긴 것은 아니다. 어떤 글을 조사했고 어디까지 해석했는지는 [`research/FOUNDATIONS.md`](research/FOUNDATIONS.md)에 기록했다.
우아한형제들 기술 블로그를 조사하면서 저는 여러 문제 해결 글이 `팀과 시스템의 상황 → 구체적인 문제와 비용 → 검토한 접근 → 선택과 구현 → 검증과 한계` 순서로 이어지는 것을 확인했습니다. 여기에 독자와 메시지, 개요와 문단 흐름을 다룬 개발자 글쓰기 자료를 더해 위 순서를 만들었습니다. 우아한형제들의 공식 편집 규정을 그대로 옮긴 것은 아니다. 어떤 글을 조사했고 어디까지 해석했는지는 [`research/FOUNDATIONS.md`](research/FOUNDATIONS.md)에 기록했습니다.
## 지원 문서 유형
| `document_type` | 기본 독자 과업 | 필수 논리 축 |
|---|---|---|
| `technical_blog` | 문제와 설계 판단 이해 | 문제 → 제약 → 대안 → 선택 이유 → 메커니즘 → 검증 → 비용 → 판단 |
| `readme` | 프로젝트 가치 이해와 첫 실행 | 문제/가치 → 원칙 → 흐름 → 설치 → 최소 실행 → 설정 → 검증 → 한계 |
| `tutorial` | 따라 하며 결과와 개념 학습 | 결과 → 준비 → 경로 → 단계 → 체크포인트 → 검증 → 다음 학습 |
| `how_to` | 특정 작업을 안전하게 완료 | 적용 조건 → 사전 조건 → 절차 → 확인 → 롤백 → 문제 해결 |
| `explanation` | 개념과 인과 관계 이해 | 질문/답 → 익숙한 기준 → 모델 → 메커니즘 → 예시 → 대안 → 한계 |
@@ -97,7 +98,7 @@ brief.json
## 설치
Python 3.10 이상이 필요하다. core runtime은 외부 Python package에 의존하지 않다.
제가 core runtime의 기준으로 삼은 버전은 Python 3.10 이상입니다. core runtime은 외부 Python package에 의존하지 않습니다.
```bash
python3 -m venv .venv
@@ -105,7 +106,7 @@ python3 -m venv .venv
python -m pip install -e .
```
Antigravity provider를 사용할 때만 선택 의존성을 설치다.
Antigravity provider를 사용할 때만 선택 의존성을 설치합니다.
```bash
python -m pip install -e '.[antigravity]'
@@ -113,7 +114,7 @@ python -m pip install -e '.[antigravity]'
## 로컬 문서 저장소를 근거로 사용하기
검색기 기본으로 이 경로를 훑는다.
저는 프로젝트 문서와 결정 이력이 섞이지 않도록 검색기 기본 경로를 다음과 같이 나눴습니다.
```text
wiki/projects
@@ -133,7 +134,7 @@ claridoc run \
--output .run/application-core-live
```
검색 결과만 먼저 확인할 수도 있다.
검색 결과만 먼저 확인할 수도 있습니다.
```bash
claridoc collect \
@@ -144,7 +145,7 @@ claridoc collect \
--output .run/application-core-sources.json
```
검색기는 먼저 Markdown 문서를 heading 단위로 나다. 그런 다음 BM25 계열 점수에 source type과 status, decision/rationale 용어의 가중치를 더해 관련 chunk를 고다. Source pack에는 절대 경로를 넣지 않고 저장소를 기준으로 한 상대 경로만 남다.
검색기는 먼저 Markdown 문서를 heading 단위로 나눕니다. 그런 다음 BM25 계열 점수에 source type과 status, decision/rationale 용어의 가중치를 더해 관련 chunk를 고릅니다. Source pack에는 절대 경로를 넣지 않고 저장소를 기준으로 한 상대 경로만 남깁니다.
### Source hierarchy
@@ -156,11 +157,11 @@ claridoc collect \
| `official-doc` | vendor·protocol·표준 동작 | 프로젝트가 실제 채택했다는 증거는 아님 |
| `company-tech-blog` | 선례와 경험 보고 | 보편 법칙으로 일반화하지 않음 |
검색 결과 같은 기술 이름이 나온다고 바로 선택의 근거로 쓰지는 않다. Planner는 이유와 대안, 제약과 비용을 실제로 설명하는 chunk를 결정 섹션에 먼저 배치다. 그런 근거를 찾지 못하면 모델이 이유를 만들어 내지 않고 주장을 좁히거나 빼도록 다.
제가 검색 결과를 검토할 때 같은 기술 이름이 나온다는 이유만으로 선택의 근거로 쓰지는 않습니다. Planner는 이유와 대안, 제약과 비용을 실제로 설명하는 chunk를 결정 섹션에 먼저 배치합니다. 그런 근거를 찾지 못하면 모델이 이유를 만들어 내지 않고 주장을 좁히거나 빼도록 합니다.
## 독자용 인용 정책
독자에게 출처를 어떻게 보여 줄지는 `brief.json``constraints.citation_style`에서 정다.
독자에게 출처를 어떻게 보여 줄지는 `brief.json``constraints.citation_style`에서 정합니다.
| 값 | 독자용 문서 | 내부 sidecar |
|---|---|---|
@@ -169,27 +170,27 @@ claridoc collect \
| `inline_link` | 자연스러운 공개 링크 | 내부 provenance도 보존 |
| `source_id` | `[SOURCE_ID]` 형식 허용 | 내부 provenance도 보존 |
기술 블로그에서 기본값 `hidden`을 선택하면 독자용 문서에는 출처 표시가 나오지 않다. `[S1]`, `Labc123...`, `raw/branch-notes/...`, “제공된 근거 팩” 같은 문자열이 남아 있으면 lint가 error로 잡다.
저는 기술 블로그 기본값 `hidden`으로 두었습니다. 이 값을 선택하면 독자용 문서에는 출처 표시가 나오지 않습니다. `[S1]`, `Labc123...`, `raw/branch-notes/...`, “제공된 근거 팩” 같은 문자열이 남아 있으면 lint가 error로 잡습니다.
## 날짜 정책
날짜와 버전을 본문에 표시할지는 `constraints.date_policy`에서 정다.
날짜와 버전을 본문에 표시할지는 `constraints.date_policy`에서 정합니다.
- `only_when_material`: 버전·날짜가 동작, 호환성, 재현성에 영향을 줄 때만 본문에 표시
- `always`: 제공된 version context를 자연스럽게 표시
- `never`: 날짜·버전 context를 독자용 글에 표시하지 않음
Source의 `accessed`는 독자에게 보여 주지 않고 내부 provenance에만 남다. 그래서 “예시는 2026-07-23 기준이다”처럼 접근 날짜만 알리는 문장은 기본 정책에서 error 또는 warning이 다.
Source의 `accessed`는 독자에게 보여 주지 않고 내부 provenance에만 남깁니다. 그래서 “예시는 2026-07-23 기준이다”처럼 접근 날짜만 알리는 문장은 기본 정책에서 error 또는 warning이 됩니다.
## 선택 이유 계약
문서에 다음 한 문장만 있다면 선택 이유가 빠진 것다.
제가 기술 선택을 검토할 때 다음 한 문장만 남아 있다면 선택 이유가 빠진 것으로 판단합니다.
```text
application-core는 Spring DI를 의도적으로 사용한다.
```
이 한 문장만으로는 왜 Spring DI를 허용했는지 알 수 없다. ClariDoc은 기술 선택을 설명할 때 적어도 아래 내용을 함께 요구다.
이 한 문장만으로는 왜 Spring DI를 허용했는지 알 수 없습니다. ClariDoc은 기술 선택을 설명할 때 적어도 아래 내용을 함께 요구합니다.
```text
context / constraint
@@ -200,7 +201,7 @@ context / constraint
→ guardrail or boundary
```
실제 문장으로 옮기면 다음과 같다.
실제 문장으로 옮기면 다음과 같습니다.
```text
application-core는 use case를 component scanning으로 등록하기 위해
@@ -214,7 +215,7 @@ Spring DI까지 제거하면 use case마다 @Configuration에서 bean을 수동
spring-tx, Spring Web, JPA는 금지하고 Gradle과 ArchUnit으로 검사한다.
```
이렇게 쓰면 Spring DI의 장점뿐 아니라 검토한 대안과 감수한 비용, 의존성이 번지지 않게 막은 범위까지 함께 확인할 수 있다.
이렇게 쓰면 Spring DI의 장점뿐 아니라 검토한 대안과 감수한 비용, 의존성이 번지지 않게 막은 범위까지 함께 확인할 수 있습니다.
## 포함된 `application-core` 예시
@@ -224,11 +225,11 @@ spring-tx, Spring Web, JPA는 금지하고 Gradle과 ArchUnit으로 검사한다
- brief: [`examples/briefs/application-core-spring-di-blog.json`](examples/briefs/application-core-spring-di-blog.json)
- 최소 로컬 corpus: [`examples/corpus/llm-wiki-mini/`](examples/corpus/llm-wiki-mini/)
예시 글은 Spring DI 허용 이유를 수동 bean 등록 비용과 연결다. `spring-tx`·Spring Web·JPA 금지, `TransactionPort`, Gradle/ArchUnit 검사, reflection 우회 한계까지 설명다. corpus에서 명시적인 선택 이유를 확보하지 못한 SLF4J는 독자용 글에서 언급하지 않다.
제가 포함한 예시 글은 Spring DI 허용 이유를 수동 bean 등록 비용과 연결합니다. `spring-tx`·Spring Web·JPA 금지, `TransactionPort`, Gradle/ArchUnit 검사, reflection 우회 한계까지 설명합니다. corpus에서 명시적인 선택 이유를 확보하지 못한 SLF4J는 독자용 글에서 언급하지 않습니다.
## Provider 역할
기본 multi-agent 예제에서는 다음과 같이 작업을 나눈다.
저는 기본 multi-agent 예제의 작업을 다음과 같이 나눴습니다.
| 역할 | provider | 책임 |
|---|---|---|
@@ -242,17 +243,17 @@ spring-tx, Spring Web, JPA는 금지하고 Gradle과 ArchUnit으로 검사한다
| operations reviewer | Antigravity | 절차·안전·검증·롤백 검사 |
| reviser | Claude | blocker/error 수정 |
실행하기 전에는 각 provider가 설치되어 있고 인증할 수 있는지 먼저 확인다.
실행하기 전에는 각 provider가 설치되어 있고 인증할 수 있는지 먼저 확인합니다.
```bash
claridoc doctor --config config/pipeline.multi-agent.example.json
```
자세한 통합 계약은 [`docs/PROVIDERS.md`](docs/PROVIDERS.md)를 참조다.
자세한 통합 계약은 [`docs/PROVIDERS.md`](docs/PROVIDERS.md)를 참조합니다.
## Mock 실행
Mock을 실행하면 외부 모델을 부르지 않고도 파이프라인 연결과 artifact 생성을 확인할 수 있다.
제가 Mock을 둔 이유는 외부 모델을 부르지 않고도 파이프라인 연결과 artifact 생성을 확인하기 위해서입니다.
```bash
claridoc run \
@@ -262,7 +263,7 @@ claridoc run \
--output .run/retry-policy-mock
```
Mock은 source excerpt를 글에 복사하지 않다. 실행 결과가 PASS여도 문장이 잘 쓰였다는 뜻은 아니다. 여기서 확인할 수 있는 것은 구조와 계약, 파이프라인 fixture가 연결됐다는 점까지다.
Mock은 source excerpt를 글에 복사하지 않습니다. 실행 결과가 PASS여도 문장이 잘 쓰였다는 뜻은 아니다. 여기서 확인할 수 있는 것은 구조와 계약, 파이프라인 fixture가 연결됐다는 점까지입니다.
## 명령어
@@ -276,7 +277,7 @@ claridoc run --brief BRIEF [--sources SOURCES] [--source-root ROOT] [--config PI
claridoc doctor --config PIPELINE [--json]
```
`validate`, `outline`, `lint`, `run`은 local corpus 옵션을 공유다.
`validate`, `outline`, `lint`, `run`은 local corpus 옵션을 공유합니다.
```text
--source-root ROOT
@@ -285,9 +286,46 @@ claridoc doctor --config PIPELINE [--json]
--source-max-per-file N
```
## 한국어 기술 블로그와 README 문체 계약
처음에는 prompt에 문체 지침을 한 번 넣으면 초안과 수정본이 같은 형식을 유지할 것으로 생각했습니다. 그런데 일부 문단만 고친 뒤 다른 절에서 `한다`, `있다`, `아니다` 같은 종결이 다시 나타나거나, `저는` 한 문장만 추가하고 경험의 흐름을 검증하지 않는 경우가 있었습니다. 그래서 저는 한국어 독자용 본문의 문체를 prompt 지침이 아니라 작성·리뷰·수정·lint·품질 게이트가 함께 확인하는 계약으로 만들었습니다.
이 계약은 모든 한국어 `readme`에 자동으로 적용합니다. 한국어 `technical_blog`에서는 `style_profile``auto`, `woowahan_tech_blog_ko`, `korean_problem_solving_blog`일 때 적용합니다. tutorial, how-to, reference, troubleshooting, explanation, design document에는 1인칭 회고 규칙을 자동으로 적용하지 않습니다.
- `STYLE002`는 제목, 표, 인용문, 코드와 명령 출력을 제외한 독자용 본문에 일반 서술형 종결이 섞이면 blocker로 처리합니다.
- `STYLE003`은 첫 문단에 `저는/제가` 경험 맥락이 없거나, 실질적인 H2 절의 절반 미만에서 실제 관찰·행동·결정을 1인칭으로 연결하면 blocker로 처리합니다.
- model reviewer는 1인칭이 장식이 아니라 근거가 있는 경험인지, 낯선 용어를 처음 필요한 곳에서 설명했는지, 대조 문장이 실제 구성요소와 동작 차이를 말하는지 확인합니다.
- reviser는 개별 지적을 고친 뒤 문서 전체를 다시 검사합니다. 한 문단을 고쳤다는 사실만으로 완료하지 않습니다.
README용 brief 예제는 [`examples/briefs/claridoc-readme.json`](examples/briefs/claridoc-readme.json)에 있습니다. 핵심 설정은 다음과 같습니다.
```json
{
"document_type": "readme",
"language": "ko-KR",
"constraints": {
"citation_style": "hidden",
"style_profile": "auto"
}
}
```
예제 brief와 근거 입력이 런타임 계약을 만족하는지 확인하고, 결정적 README 목차를 생성하려면 다음 명령을 실행합니다.
```bash
claridoc validate \
--brief examples/briefs/claridoc-readme.json \
--sources examples/sources/retry-policy-sources.json
claridoc outline \
--brief examples/briefs/claridoc-readme.json \
--sources examples/sources/retry-policy-sources.json \
--output .run/claridoc-readme-outline.json
```
## 결정적 lint
주요 검사:
제가 model reviewer와 별도로 둔 결정적 lint의 주요 검사는 다음과 같습니다.
- 정확히 하나의 H1과 필수 H2의 존재·중복·순서
- 기술 블로그가 prompt contract가 아니라 구체적 문제에서 시작하는지
@@ -299,11 +337,13 @@ claridoc doctor --config PIPELINE [--json]
- decision section에 rationale evidence가 배치되지 않은 경우 (`RAT003`)
- 코드 fence, heading depth, 문단·문장 밀도
- 한국어 기술 블로그에서 `첫 번째/두 번째/세 번째 + 추상 분류명`이 가까운 문단에 반복되는 문장 scaffolding (`STYLE001`)
- 한국어 기술 블로그와 README 본문의 `합니다/했습니다` 종결 일관성 (`STYLE002`)
- 첫 문단과 주요 절의 근거 있는 `저는/제가` 경험 맥락 (`STYLE003`)
- 절차의 사전 조건, 단계, 검증, 롤백
- 파괴적 명령 주변의 영향 경고, checkpoint, verification
- 금지 주장과 미해결 TODO
Lint를 통과했다고 문장의 의미까지 맞는 것은 아니다. Lint가 정해진 규칙을 검사한 뒤에도 모델 reviewer와 프로젝트 소유자가 내용을 다시 확인해야 다.
Lint를 통과했다고 문장의 의미까지 맞는 것은 아니다. Lint가 정해진 규칙을 검사한 뒤에도 model reviewer와 프로젝트 소유자가 내용을 다시 확인해야 합니다.
## 산출물
@@ -334,7 +374,7 @@ run-dir/
└── manifest.json
```
`manifest.json`은 자신을 제외한 모든 artifact의 크기와 SHA-256을 기록다.
`manifest.json`은 자신을 제외한 모든 artifact의 크기와 SHA-256을 기록합니다.
## 검증
@@ -342,15 +382,15 @@ run-dir/
bash scripts/verify.sh
```
이 명령 unit/integration test부터 Python 3.10 grammar parse, JSON과 JSON Schema, Markdown local link, local corpus retrieval, golden example lint를 차례로 확인다. 이어서 Mock end-to-end, provenance sidecar, manifest 재검산, wheel build/install smoke test까지 실행다. 최신 결과는 [`verification/TEST_REPORT.md`](verification/TEST_REPORT.md)에서 확인할 수 있다.
저는 이 명령으로 unit/integration test부터 Python 3.10 grammar parse, JSON과 JSON Schema, Markdown local link, local corpus retrieval, golden example lint를 차례로 확인합니다. 이어서 Mock end-to-end, provenance sidecar, manifest 재검산, wheel build/install smoke test까지 실행합니다. 최신 결과는 [`verification/TEST_REPORT.md`](verification/TEST_REPORT.md)에서 확인할 수 있습니다.
## 한계
- 로컬 corpus 검색은 lexical ranking다. 의미가 유사하지만 단어가 다른 근거는 놓칠 수 있다.
- source chunk가 검색됐다고 그 내용을 바로 본문에 쓸 수 있는 것은 아니다. status와 governing source를 함께 확인해야 다.
- LLM reviewer의 합의는 진실의 증명이 아니다.
- 실제 코드 예시, command, 운영 수치, 보안 주장은 대상 시스템에서 별도로 검증해야 다.
- provider binary, SDK, 인증, quota, model ID는 실행 환경마다 다다.
- Mock 실행은 문서 품질을 증명하지 않다.
- 제가 현재 구현에서 사용한 로컬 corpus 검색은 lexical ranking입니다. 의미가 유사하지만 단어가 다른 근거는 놓칠 수 있습니다.
- source chunk가 검색됐다고 그 내용을 바로 본문에 쓸 수 있는 것은 아니다. status와 governing source를 함께 확인해야 합니다.
- LLM reviewer의 합의는 진실의 증명이 아니다.
- 실제 코드 예시, command, 운영 수치, 보안 주장은 대상 시스템에서 별도로 검증해야 합니다.
- provider binary, SDK, 인증, quota, model ID는 실행 환경마다 다릅니다.
- Mock 실행은 문서 품질을 증명하지 않습니다.
위협 모델과 prompt-injection 경계는 [`docs/SECURITY.md`](docs/SECURITY.md)에 정리했다.
위협 모델과 prompt-injection 경계는 [`docs/SECURITY.md`](docs/SECURITY.md)에 정리했습니다.