chore: 문서를 작성할 때 한국어의 표현 작성 스킬 추가 및 1인칭 관점의 글 작성 검증 테스트 추가

This commit is contained in:
DongHyeonka
2026-07-29 16:48:03 +09:00
parent c39406bbdd
commit 41501b5d06
520 changed files with 95494 additions and 2231 deletions
+280 -220
View File
@@ -1,230 +1,309 @@
# ClariDoc Harness
# ClariDoc Harness 0.2.0
ClariDoc은 기술 블로그와 기술 문서를 **독자의 질문 순서가 드러나는 논리 구조**로 계획·작성·검토·수정하는 멀티 에이전트 하네스다. 단순 프롬프트 템플릿이 아니라 다음을 코드로 강제한다.
ClariDoc은 기술 블로그와 기술 문서를 계획·작성·검토·수정하는 멀티 모델 하네스다. 처음 `brief`와 프로젝트 문서를 넣으면 바로 글부터 쓰지 않는다. 로컬 문서 저장소에서 근거를 찾고, 문서 유형에 맞춰 독자가 문제와 선택을 따라갈 순서를 먼저 잡는다. 그다음 Codex, Claude, Google Antigravity가 계획과 작성, 검토와 수정을 나누어 맡는다.
- 문서 유형별 정보 구조 계약
- 독자·목표·선행지식·범위·비범위가 포함된 작성 브리프
- 출처별 사실 단위를 분리한 근거 팩
- Codex, Claude, Google Antigravity 제공자 어댑터
- 논리·독자·근거·운영 관점의 독립 리뷰
- Markdown 구조, 절차 안전성, 인용, 버전 맥락을 검사하는 결정적 린터
- 점수, blocker/error 한도, 수정 횟수를 포함한 품질 게이트
- 각 단계의 원문 응답, 보고서, 실행 이벤트, SHA-256 매니페스트
이 과정에서는 두 가지를 끝까지 지킨다.
## 핵심 설계
1. **근거 추적 정보와 독자용 글을 분리한다.** source ID, repository path, access date, prompt tag는 `provenance.md``evidence-map.json`에만 남는다.
2. **기술 선택은 이유 없이 선언할 수 없다.** “의도적으로 사용한다”, “허용했다”, “금지했다”라고 썼다면 제약, 선택 이유, 대안, 수용 비용, 가드레일까지 이어져야 한다.
## 해결하려는 실패
최종 문서에서 다음 문장이 보이면 ClariDoc은 실패로 처리한다.
```text
brief.json + sources.json
[문서 유형별 구조 계약]
│ planner: Codex
[질문 기반 outline.json]
│ writer: Claude
[draft.md]
├── 결정적 린터
├── 논리 리뷰: Codex
├── 독자 리뷰: Claude
├── 근거 리뷰: Antigravity
└── 운영 리뷰: Antigravity
[품질 게이트] ── 실패 ──> reviser: Claude ──> 재검사
│ 통과 또는 수정 한도 도달
final/document.md + quality-report.md + manifest.json
예시는 2026-07-23 기준이다.
Retries can increase load ... [S1]
제공된 근거 팩은 다음 사실을 확인 대상으로 제시한다.
application-core는 Spring DI와 SLF4J를 의도적으로 사용한다.
```
모델이 자유롭게 목차부터 만들게 두지 않는다. 먼저 코드가 문서 유형별 필수 질문과 순서를 정하고, planner는 제목·전환·근거 배치를 정교화하되 필수 intent를 삭제하거나 재배열할 수 없다. 모델 출력이 구조 계약을 위반하면 planner 단계는 결정적 기본 구조로 폴백한다.
처음 세 문장에는 독자가 볼 필요가 없는 작성 과정과 provenance가 섞여 있다. 마지막 문장은 Spring DI를 선택했다는 사실만 있고 **왜 선택했는지**, **무슨 대안을 검토했는지**, **어떤 비용을 감수했는지**, **어디까지 허용했는지**는 알 수 없다.
그래서 ClariDoc 0.2.0은 독자가 읽을 내용과 근거를 추적할 때 필요한 기록을 서로 다른 파일에 남긴다.
```text
reader-facing document.md
└─ 문제, 제약, 대안, 선택 이유, 동작, 검증, 트레이드오프만 노출
internal provenance.md / evidence-map.json
└─ source ID, 원본 경로, heading, line range, status, claim/decision ID 보존
```
## 전체 흐름
```text
brief.json
+ manual sources.json (선택)
+ local documentation repository
[local corpus collector]
canonical project / concept / branch note /
official docs / company tech blogs를 chunk 검색
[문서 유형별 구조 계약]
│ planner: Codex
질문 기반 outline + decision requirements
│ writer: Claude
reader-facing draft
┌──────────┼──────────┐
│ │ │
deterministic logic/ reader/editor/
linter decision evidence/operations
│ reviews reviews
└──────────┼──────────┘
quality gate
실패 │ │ 통과
▼ ▼
reviser: Claude
document.md + quality-report.md
provenance.md + evidence-map.json + manifest.json
```
## 기술 블로그의 기본 논리 구조
`technical_blog`는 다음 순서를 기본 계약으로 사용한다.
1. **구체적인 문제 장면**: 어떤 상황과 비용이 있었는가
2. **제약**: 단순한 해법을 막은 조건은 무엇인가
3. **선택지**: 어떤 대안과 실패한 시도를 검토했는가
4. **결정 이유**: 왜 골랐고, 무엇을 포기했으며, 어떤 경계를 지켰는가
5. **메커니즘**: 실제 모듈·인터페이스·제어 흐름에 어떻게 반영됐는가
6. **검증**: 어떤 테스트·빌드 규칙·관측값이 무엇을 증명하는가
7. **트레이드오프**: 얻은 것, 잃은 것, 적용하지 않을 조건은 무엇인가
8. **결론**: 다른 환경에서도 가져갈 판단은 무엇인가
우아한형제들 기술 블로그를 조사하면서 저는 여러 문제 해결 글이 `팀과 시스템의 상황 → 구체적인 문제와 비용 → 검토한 접근 → 선택과 구현 → 검증과 한계` 순서로 이어지는 것을 확인했다. 여기에 독자와 메시지, 개요와 문단 흐름을 다룬 개발자 글쓰기 자료를 더해 위 순서를 만들었다. 우아한형제들의 공식 편집 규정을 그대로 옮긴 것은 아니다. 어떤 글을 조사했고 어디까지 해석했는지는 [`research/FOUNDATIONS.md`](research/FOUNDATIONS.md)에 기록했다.
## 지원 문서 유형
| `document_type` | 독자 요구 | 기본 논리 축 |
| `document_type` | 기본 독자 과업 | 필수 논리 축 |
|---|---|---|
| `technical_blog` | 문제와 설계 판단 이해 | 결론맥락/제약 → 멘털 모델 → 메커니즘 → 예시 → 검증 → 트레이드오프행동 |
| `tutorial` | 안내를 따라 학습·완성 | 결과 → 준비 → 전체 경로 → 단계 → 체크포인트 → 최종 검증 → 다음 학습 |
| `how_to` | 특정 작업을 안전하게 완료 | 목표/적용 조건 → 사전 조건 → 절차 → 확인 → 롤백 → 문제 해결 |
| `explanation` | 개념과 원리를 이해 | 질문/답 → 익숙한 기준 → 모델 → 인과 과정 → 예시 → 대안 → 한계 → 실무 의미 |
| `reference` | 정확한 사실을 빠르게 조회 | 범위/버전 → 구문 → 필드 → 동작 → 오류 → 최소 예시 → 관련 항목 |
| `troubleshooting` | 증상에서 원인복구로 이동 | 증상 → 영향 → 안전 → 최소 진단 → 원인 분기 → 조치 → 복구 확인 → 예방 |
| `design_doc` | 대안을 비교하고 결정 승인 | 결정 → 문제 → 목표/비목표 → 제약 → 대안 → 선택아키텍처 → 실패 → 롤아웃 → 관측 → 위험 |
| `technical_blog` | 문제와 설계 판단 이해 | 문제 → 제약 → 대안 → 선택 이유 → 메커니즘 → 검증 → 비용판단 |
| `tutorial` | 따라 하며 결과와 개념 학습 | 결과 → 준비 → 경로 → 단계 → 체크포인트 → 검증 → 다음 학습 |
| `how_to` | 특정 작업을 안전하게 완료 | 적용 조건 → 사전 조건 → 절차 → 확인 → 롤백 → 문제 해결 |
| `explanation` | 개념과 인과 관계 이해 | 질문/답 → 익숙한 기준 → 모델 → 메커니즘 → 예시 → 대안 → 한계 |
| `reference` | 정확한 항목 조회 | 범위 → 구문 → 필드 → 동작 → 오류 → 최소 예시 → 관련 항목 |
| `troubleshooting` | 증상에서 원인·복구로 이동 | 증상 → 영향 → 안전 → 진단 → 원인 → 조치 → 복구 → 예방 |
| `design_doc` | 대안을 비교하고 결정 승인 | 요 → 문제 → 목표 → 제약 → 대안 → 결정구조 → 실패 → 배포 → 관측 → 위험 |
상세 근거는 [`research/FOUNDATIONS.md`](research/FOUNDATIONS.md), 구현 규칙은 [`docs/LOGIC_MODEL.md`](docs/LOGIC_MODEL.md)에 정리되어 있다.
## 설치
## 빠른 실행: 외부 모델 없이 전체 흐름 검증
요구 사항은 Python 3.10 이상이다. 핵심 패키지는 외부 Python 의존성이 없다.
Python 3.10 이상이 필요하다. core runtime은 외부 Python package에 의존하지 않는다.
```bash
cd claridoc-harness
python3 -m venv .venv
. .venv/bin/activate
python -m pip install -e .
claridoc run \
--brief examples/briefs/retry-policy-blog.json \
--sources examples/sources/retry-policy-sources.json \
--config config/pipeline.mock.json \
--output .run/retry-policy
```
또는 저장소에서 바로 실행한다.
```bash
bash scripts/run-demo.sh
```
Mock 제공자는 **파이프라인·계약·린터·보고서 재현용**이다. 언어 모델 품질을 증명하지 않으며, 생성 점수도 외부 모델 평가값이 아니라 테스트용 결정적 값이다.
## Codex + Claude + Antigravity 실행
예제 역할 배치는 다음과 같다.
- Codex: 구조 planner와 논리 reviewer
- Claude: primary writer, reader reviewer, reviser
- Antigravity: evidence reviewer와 operations reviewer
먼저 각 도구를 설치하고 인증한 뒤 진단한다.
```bash
claridoc doctor --config config/pipeline.multi-agent.example.json
```
Antigravity SDK 어댑터를 사용할 때는 선택 의존성을 설치한다.
Antigravity provider를 사용할 때만 선택 의존성을 설치한다.
```bash
python -m pip install -e '.[antigravity]'
```
실행:
## 로컬 문서 저장소를 근거로 사용하기
검색기는 기본으로 이 경로를 훑는다.
```text
wiki/projects
wiki/concepts
raw/branch-notes
raw/official-docs
raw/company-tech-blogs
```
프로젝트 문서 저장소를 직접 지정할 때:
```bash
claridoc run \
--brief examples/briefs/application-core-spring-di-blog.json \
--source-root /path/to/local-document-repository \
--config config/pipeline.multi-agent.example.json \
--output .run/application-core-live
```
검색 결과만 먼저 확인할 수도 있다.
```bash
claridoc collect \
--root /path/to/local-document-repository \
--query 'application-core Spring DI 선택 이유 대안 비용 가드레일' \
--query 'TransactionPort spring-tx 금지 ArchUnit 검증' \
--top-k 24 \
--output .run/application-core-sources.json
```
검색기는 먼저 Markdown 문서를 heading 단위로 나눈다. 그런 다음 BM25 계열 점수에 source type과 status, decision/rationale 용어의 가중치를 더해 관련 chunk를 고른다. Source pack에는 절대 경로를 넣지 않고 저장소를 기준으로 한 상대 경로만 남긴다.
### Source hierarchy
| source type | 주 용도 | 주의점 |
|---|---|---|
| `canonical-project` | 현재 프로젝트의 검증된 상태 | 현재 상태의 우선 근거 |
| `canonical-concept` | 재사용 가능한 개념 | 프로젝트 구현 사실과 구분 |
| `branch-note` | 선택 배경, 대안, 결정 이력, 로컬 검증 | status를 보존하고 현재 canonical과 충돌 여부 확인 |
| `official-doc` | vendor·protocol·표준 동작 | 프로젝트가 실제 채택했다는 증거는 아님 |
| `company-tech-blog` | 선례와 경험 보고 | 보편 법칙으로 일반화하지 않음 |
검색 결과에 같은 기술 이름이 나온다고 바로 선택의 근거로 쓰지는 않는다. Planner는 이유와 대안, 제약과 비용을 실제로 설명하는 chunk를 결정 섹션에 먼저 배치한다. 그런 근거를 찾지 못하면 모델이 이유를 만들어 내지 않고 주장을 좁히거나 빼도록 한다.
## 독자용 인용 정책
독자에게 출처를 어떻게 보여 줄지는 `brief.json``constraints.citation_style`에서 정한다.
| 값 | 독자용 문서 | 내부 sidecar |
|---|---|---|
| `hidden` | source ID, URL, path, access date를 표시하지 않음 | 전체 provenance 보존 |
| `footnote` | 공개 가능한 Markdown footnote | 내부 provenance도 보존 |
| `inline_link` | 자연스러운 공개 링크 | 내부 provenance도 보존 |
| `source_id` | `[SOURCE_ID]` 형식 허용 | 내부 provenance도 보존 |
기술 블로그에서 기본값인 `hidden`을 선택하면 독자용 문서에는 출처 표시가 나오지 않는다. `[S1]`, `Labc123...`, `raw/branch-notes/...`, “제공된 근거 팩” 같은 문자열이 남아 있으면 lint가 error로 잡는다.
## 날짜 정책
날짜와 버전을 본문에 표시할지는 `constraints.date_policy`에서 정한다.
- `only_when_material`: 버전·날짜가 동작, 호환성, 재현성에 영향을 줄 때만 본문에 표시
- `always`: 제공된 version context를 자연스럽게 표시
- `never`: 날짜·버전 context를 독자용 글에 표시하지 않음
Source의 `accessed`는 독자에게 보여 주지 않고 내부 provenance에만 남긴다. 그래서 “예시는 2026-07-23 기준이다”처럼 접근 날짜만 알리는 문장은 기본 정책에서 error 또는 warning이 된다.
## 선택 이유 계약
문서에 다음 한 문장만 있다면 선택 이유가 빠진 것이다.
```text
application-core는 Spring DI를 의도적으로 사용한다.
```
이 한 문장만으로는 왜 Spring DI를 허용했는지 알 수 없다. ClariDoc은 기술 선택을 설명할 때 적어도 아래 내용을 함께 요구한다.
```text
context / constraint
→ chosen option
→ why it was chosen
→ realistic alternative
→ accepted cost
→ guardrail or boundary
```
실제 문장으로 옮기면 다음과 같다.
```text
application-core는 use case를 component scanning으로 등록하기 위해
@Service와 @Component를 허용했다.
Spring DI까지 제거하면 use case마다 @Configuration에서 bean을 수동 등록해야 해
조립 코드가 빠르게 늘어나기 때문이다.
대신 application-core가 spring-context와 spring-beans에 의존하는 비용을 수용한다.
그 비용이 transaction·transport·persistence 의존으로 번지지 않도록
spring-tx, Spring Web, JPA는 금지하고 Gradle과 ArchUnit으로 검사한다.
```
이렇게 쓰면 Spring DI의 장점뿐 아니라 검토한 대안과 감수한 비용, 의존성이 번지지 않게 막은 범위까지 함께 확인할 수 있다.
## 포함된 `application-core` 예시
- 독자용 완성 예시: [`examples/golden/application-core-spring-di-boundary.md`](examples/golden/application-core-spring-di-boundary.md)
- 내부 provenance 예시: [`examples/golden/application-core-spring-di-boundary.provenance.md`](examples/golden/application-core-spring-di-boundary.provenance.md)
- machine-readable evidence map: [`examples/golden/application-core-spring-di-boundary.evidence-map.json`](examples/golden/application-core-spring-di-boundary.evidence-map.json)
- 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는 독자용 글에서 언급하지 않는다.
## Provider 역할
기본 multi-agent 예제에서는 다음과 같이 작업을 나눈다.
| 역할 | provider | 책임 |
|---|---|---|
| planner | Codex | 구조 계약 정교화, evidence allocation |
| writer | Claude | 독자용 완성 초안 |
| logic reviewer | Codex | 인과·전제·결론 검사 |
| decision reviewer | Codex | 선택 이유·대안·비용·가드레일 검사 |
| reader reviewer | Claude | 독자 맥락·인지 부하·정보 누락 검사 |
| editor reviewer | Claude | 도입·문단 초점·전환·반복·상투적 LLM 문구 검사 |
| evidence reviewer | Antigravity | source fit·status·과장 검사 |
| operations reviewer | Antigravity | 절차·안전·검증·롤백 검사 |
| reviser | Claude | blocker/error 수정 |
실행하기 전에는 각 provider가 설치되어 있고 인증할 수 있는지 먼저 확인한다.
```bash
claridoc doctor --config config/pipeline.multi-agent.example.json
```
자세한 통합 계약은 [`docs/PROVIDERS.md`](docs/PROVIDERS.md)를 참조한다.
## Mock 실행
Mock을 실행하면 외부 모델을 부르지 않고도 파이프라인 연결과 artifact 생성을 확인할 수 있다.
```bash
claridoc run \
--brief examples/briefs/retry-policy-blog.json \
--sources examples/sources/retry-policy-sources.json \
--config config/pipeline.multi-agent.example.json \
--output .run/retry-policy-live
--config config/pipeline.mock.json \
--output .run/retry-policy-mock
```
기본 호출 방식은 다음과 같다.
| 제공자 | 기본 통합 | 안전 기본값 |
|---|---|---|
| Codex | `codex exec`에 프롬프트를 stdin으로 전달하고 마지막 메시지를 파일로 수집 | `--sandbox read-only`, Git 저장소 검사 생략 가능 |
| Claude | `claude -p --output-format text`와 piped task | 파일 변경을 요구하지 않는 출력 전용 프롬프트 |
| Antigravity | `google.antigravity.Agent` + `LocalAgentConfig` | SDK 설정을 명시적으로 전달; 하네스 자체는 도구 실행을 요청하지 않음 |
조직별 래퍼가 있으면 provider의 `options.command` 또는 `options.extra_args`를 사용한다. 자세한 내용은 [`docs/PROVIDERS.md`](docs/PROVIDERS.md)를 참조한다.
## 입력 계약
### `brief.json`
브리프는 문서 주제보다 **독자가 왜 읽는지**를 더 엄격하게 정의한다.
```json
{
"title": "API 재시도는 횟수가 아니라 부하 예산으로 설계한다",
"document_type": "technical_blog",
"language": "ko-KR",
"audience": {
"roles": ["백엔드 개발자"],
"prior_knowledge": ["HTTP와 타임아웃의 기본 개념"],
"needs": ["재시도 정책의 판단 기준"]
},
"reader_goal": "장애를 증폭하지 않는 재시도 정책을 설계한다",
"core_message": "재시도는 실패 중인 의존성에 보내는 추가 부하 예산이다.",
"scope": ["동기 HTTP 클라이언트 재시도"],
"non_scope": ["메시지 큐 전달 보장 전체"],
"prerequisites": ["로그와 지표를 조회할 수 있음"],
"required_topics": ["멱등성", "백오프", "지터", "한도", "검증"],
"constraints": {
"target_words": 1200,
"tone": "직접적이고 검증 가능한 문체",
"version_context": "HTTP 의미론은 RFC 9110, 2026-07-23 기준",
"max_heading_depth": 3,
"require_citations": true,
"allow_external_knowledge": false
},
"forbidden_claims": ["재시도는 항상 안전하다"],
"metadata": {"risk": "high"}
}
```
`allow_external_knowledge: false`일 때 모델은 근거 팩 밖의 외부 사실을 추가하지 않도록 지시받는다. 논리 설명과 명시적인 가상 예시는 가능하지만 측정값·버전·사건·API를 지어낼 수 없다.
### `sources.json`
근거 팩은 URL 목록이 아니라 **출처가 실제로 지지하는 사실의 최소 단위**를 제공한다.
```json
{
"sources": [
{
"id": "S1",
"title": "Authoritative source title",
"url": "https://example.com/source",
"publisher": "Publisher",
"accessed": "2026-07-23",
"facts": ["This source explicitly supports this fact."],
"notes": "Allowed use and limitations"
}
]
}
```
모델은 문서에서 `[S1]`처럼 인용한다. 린터는 존재하지 않는 ID, 근거 팩이 비었는데 인용이 필수인 경우, 출처가 있는데 하나도 사용하지 않은 경우를 검사한다. 하네스는 URL 내용을 자동으로 신뢰하거나 실행하지 않는다.
스키마는 [`schemas/`](schemas/)에 있다.
명시적인 pipeline JSON은 `planner`, `writer`, `reviewers`, `reviser`를 모두 포함해야 하며 reviewer는 최소 한 명이어야 한다. reviewer role은 중복될 수 없고, 모델 리뷰는 9개 고정 평가 차원과 허용된 severity만 반환해야 한다. 일부 역할에 Mock을 섞으면 합성 점수가 실제 모델 평가처럼 보이지 않도록 실행 경고가 자동으로 남는다.
Mock은 source excerpt를 글에 복사하지 않는다. 실행 결과가 PASS여도 문장이 잘 쓰였다는 뜻은 아니다. 여기서 확인할 수 있는 것은 구조와 계약, 파이프라인 fixture가 연결됐다는 점까지다.
## 명령어
```text
claridoc init [directory] [--force]
claridoc validate --brief BRIEF [--sources SOURCES]
claridoc outline --brief BRIEF [--sources SOURCES] [--output OUTLINE]
claridoc lint DOCUMENT --brief BRIEF [--sources SOURCES] [--json] [--output REPORT]
claridoc run --brief BRIEF [--sources SOURCES] [--config PIPELINE] --output RUN_DIR
claridoc collect --root ROOT --query QUERY [--query QUERY] --output SOURCES
claridoc validate --brief BRIEF [--sources SOURCES] [--source-root ROOT]
claridoc outline --brief BRIEF [--sources SOURCES] [--source-root ROOT] [--output OUTLINE]
claridoc lint DOCUMENT --brief BRIEF [--sources SOURCES] [--source-root ROOT] [--json]
claridoc run --brief BRIEF [--sources SOURCES] [--source-root ROOT] [--config PIPELINE] --output RUN_DIR
claridoc doctor --config PIPELINE [--json]
```
`claridoc init`은 시작용 브리프, 근거 팩, Mock 설정을 만든다.
## 품질 게이트
기본 복합 점수는 다음과 같다.
`validate`, `outline`, `lint`, `run`은 local corpus 옵션을 공유한다.
```text
composite = deterministic_lint × 0.4 + model_review_mean × 0.6
--source-root ROOT
--source-include RELATIVE_DIR # 반복 가능
--source-top-k N
--source-max-per-file N
```
점수만으로 통과시키지 않는다. 다음을 동시에 확인한다.
## 결정적 lint
- 최소 복합 점수
- blocker 최대 개수
- error 최대 개수
- 최대 수정 라운드
주요 검사:
결정적 린터의 주요 검사:
- 정확히 하나의 H1과 필수 H2의 존재·중복·순서
- 기술 블로그가 prompt contract가 아니라 구체적 문제에서 시작하는지
- “제공된 근거 팩”, prompt tag, section-planning narration 누출
- hidden citation 모드에서 source ID와 repository path 누출
- access-date/example-date boilerplate
- 기술 선택 선언 뒤 이유 누락 (`RAT001`)
- 대안·수용 비용·가드레일 누락 (`RAT002`)
- decision section에 rationale evidence가 배치되지 않은 경우 (`RAT003`)
- 코드 fence, heading depth, 문단·문장 밀도
- 한국어 기술 블로그에서 `첫 번째/두 번째/세 번째 + 추상 분류명`이 가까운 문단에 반복되는 문장 scaffolding (`STYLE001`)
- 절차의 사전 조건, 단계, 검증, 롤백
- 파괴적 명령 주변의 영향 경고, checkpoint, verification
- 금지 주장과 미해결 TODO
- H1 개수와 제목, heading level skip, 중복·일반적 제목
- 문서 유형 계약의 필수 H2 존재와 순서
- 오프닝의 독자 목표·핵심 메시지·비범위 노출
- 과도하게 긴 문단과 문장, 한 문단에 과도한 문장 수
- 절차 문서의 번호 단계·사전 조건·검증·롤백
- 기술 블로그/설명의 예시와 트레이드오프
- 닫히지 않은 코드 fence와 언어 태그
- 출처 ID, 인용 부재, 숫자·버전형 주장에 대한 근거 표식
- TODO/TBD/FIXME, 금지 주장
- 파괴적 명령 주변의 경고·백업·복구 경로
- 버전/날짜 맥락과 목표 길이
린터는 휴리스틱이다. 문장의 참·거짓과 실제 코드 동작을 보증하지 않는다. 이 부분은 출처 검증, 코드 테스트, 도메인 소유자 리뷰로 보완해야 한다.
Lint를 통과했다고 문장의 의미까지 맞는 것은 아니다. Lint가 정해진 규칙을 검사한 뒤에도 모델 reviewer와 프로젝트 소유자가 내용을 다시 확인해야 한다.
## 산출물
@@ -239,58 +318,39 @@ run-dir/
│ ├── 02-outline.json
│ ├── 02-outline.md
│ └── 03-writer.raw.txt
├── rounds/
── round-01/
├── draft.md
├── lint.json
├── lint.md
├── review-*.json
│ ├── review-*.raw.txt
│ └── quality-gate.json
├── rounds/round-*/
── draft.md
├── lint.json
│ ├── lint.md
├── review-*.json
└── quality-gate.json
├── final/
│ ├── document.md
── quality-report.md
│ ├── document.md # 독자용
── quality-report.md
│ ├── provenance.md # 내부용
│ └── evidence-map.json # 내부용
├── provider-events.jsonl
├── run.json
└── manifest.json
```
`manifest.json`은 자신을 제외한 산출물의 바이트 크기와 SHA-256을 기록한다. 모델 프롬프트에는 소스·브리프가 신뢰되지 않은 데이터라는 경계를 반복해서 넣으며, 원문 응답을 보존해 사후 감사를 가능하게 한다.
`manifest.json`은 자신을 제외한 모든 artifact의 크기와 SHA-256을 기록한다.
## 테스트와 재현 검증
```bash
bash scripts/test.sh
```
전체 배포 전 검증은 다음 한 명령으로 수행한다.
## 검증
```bash
bash scripts/verify.sh
```
이 명령은 단위·통합 테스트, Python 3.10 문법 호환 파싱, JSON 구문, 로컬 Markdown 링크, 입력 계약, Mock 종단 간 실행, 합성 점수 경고, 산출물 SHA-256 매니페스트를 검사한다.
이 명령은 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)에서 확인할 수 있다.
테스트 범위에는 계약 파싱, 7개 문서 유형 구조, 구조 병합 실패 조건, 리뷰 스키마 우회 차단, 필수 H2 중복, 임의 source ID, 파괴적 명령 안전 통제, reviewer artifact 경로 격리, Codex/Claude 가짜 실행 파일, Antigravity 가짜 SDK, 수정 한도, 전체 Mock 파이프라인, CLI 초기화가 포함된다. 실행 시점의 상세 결과와 실제 외부 provider 미검증 범위는 [`verification/TEST_REPORT.md`](verification/TEST_REPORT.md)에 기록한다.
## 한계
## Agent Skills
- 로컬 corpus 검색은 lexical ranking이다. 의미가 유사하지만 단어가 다른 근거는 놓칠 수 있다.
- source chunk가 검색됐다고 그 내용을 바로 본문에 쓸 수 있는 것은 아니다. status와 governing source를 함께 확인해야 한다.
- LLM reviewer의 합의는 진실의 증명이 아니다.
- 실제 코드 예시, command, 운영 수치, 보안 주장은 대상 시스템에서 별도로 검증해야 한다.
- provider binary, SDK, 인증, quota, model ID는 실행 환경마다 다르다.
- Mock 실행은 문서 품질을 증명하지 않는다.
저장소에는 동일한 작성 규칙을 에이전트가 직접 발견할 수 있도록 스킬을 포함한다.
- Codex / Antigravity: `.agents/skills/technical-document-author/SKILL.md`
- Claude Code: `.claude/skills/technical-document-author/SKILL.md`
- 저장소 전역 규칙: `AGENTS.md`, `CLAUDE.md`
스킬은 하네스를 우회해 자유 형식으로 글을 쓰지 않고, 브리프 → 근거 팩 → outline 계약 → 작성 → lint/review → gate 순서를 따르도록 지시한다.
## 보안과 한계
- 제공자 인증 토큰을 구성 파일에 저장하지 않는다. 각 CLI/SDK의 인증 메커니즘을 사용한다.
- `options.command`는 신뢰된 로컬 설정으로 취급한다. 외부 입력을 그대로 command에 넣지 않는다.
- Codex 기본 sandbox는 read-only다. 하네스 자체는 모델에게 shell 실행이나 파일 수정을 요구하지 않는다.
- 브리프·근거 팩·초안 내부의 지시문은 데이터로 취급하도록 모든 단계에서 명시한다. 다만 LLM prompt injection을 수학적으로 제거할 수는 없다.
- URL 접근, 사실 수집, 링크 상태 확인은 이 버전의 core pipeline에 포함하지 않는다. 입력 근거 팩의 진실성은 작성자가 책임진다.
- 실제 코드 예시, 명령, API, 보안·법률·의료·재무 내용은 해당 분야 검증을 별도로 거쳐야 한다.
- Mock PASS는 배선과 규칙이 동작했다는 의미이며 외부 모델이 좋은 문서를 작성했다는 증거가 아니다.
자세한 위협 모델은 [`docs/SECURITY.md`](docs/SECURITY.md)를 참조한다.
위협 모델과 prompt-injection 경계는 [`docs/SECURITY.md`](docs/SECURITY.md)에 정리했다.