Files
document-haness/README.md
T

20 KiB

ClariDoc Harness 0.2.0

저는 ClariDoc을 만들면서 모델이 brief와 프로젝트 문서를 받자마자 글부터 쓰지 않게 했습니다. 먼저 로컬 문서 저장소에서 근거를 찾고, 문서 유형에 맞춰 독자가 문제와 선택을 따라갈 순서를 잡습니다. 그다음 Codex, Claude, Google Antigravity가 계획과 작성, 검토와 수정을 나누어 맡습니다. ClariDoc은 이 과정을 연결하는 멀티 모델 하네스입니다.

이 과정에서는 두 가지를 끝까지 지킵니다.

  1. 근거 추적 정보와 독자용 글을 분리합니다. source ID, repository path, access date, prompt tag는 provenance.mdevidence-map.json에만 남깁니다.
  2. 기술 선택은 이유 없이 선언하지 않습니다. “의도적으로 사용한다”, “허용했다”, “금지했다”라고 썼다면 제약, 선택 이유, 대안, 수용 비용, 가드레일까지 이어져야 합니다.

해결하려는 실패

제가 최종 문서에서 막으려 한 것은 다음과 같은 문장입니다. ClariDoc은 이 문장들이 남아 있으면 실패로 처리합니다.

예시는 2026-07-23 기준이다.
Retries can increase load ... [S1]
제공된 근거 팩은 다음 사실을 확인 대상으로 제시한다.
application-core는 Spring DI와 SLF4J를 의도적으로 사용한다.

처음 세 문장에는 독자가 볼 필요가 없는 작성 과정과 provenance가 섞여 있습니다. 마지막 문장은 Spring DI를 선택했다는 사실만 있고 왜 선택했는지, 무슨 대안을 검토했는지, 어떤 비용을 감수했는지, 어디까지 허용했는지는 알 수 없습니다.

그래서 저는 독자가 읽을 내용과 근거를 추적할 때 필요한 기록을 서로 다른 파일에 남기도록 ClariDoc 0.2.0을 구성했습니다.

reader-facing document.md
  └─ 문제, 제약, 대안, 선택 이유, 동작, 검증, 트레이드오프만 노출

internal provenance.md / evidence-map.json
  └─ source ID, 원본 경로, heading, line range, status, claim/decision ID 보존

전체 흐름

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에 기록했습니다.

지원 문서 유형

document_type 기본 독자 과업 필수 논리 축
technical_blog 문제와 설계 판단 이해 문제 → 제약 → 대안 → 선택 이유 → 메커니즘 → 검증 → 비용 → 판단
readme 프로젝트 가치 이해와 첫 실행 문제/가치 → 원칙 → 흐름 → 설치 → 최소 실행 → 설정 → 검증 → 한계
tutorial 따라 하며 결과와 개념 학습 결과 → 준비 → 경로 → 단계 → 체크포인트 → 검증 → 다음 학습
how_to 특정 작업을 안전하게 완료 적용 조건 → 사전 조건 → 절차 → 확인 → 롤백 → 문제 해결
explanation 개념과 인과 관계 이해 질문/답 → 익숙한 기준 → 모델 → 메커니즘 → 예시 → 대안 → 한계
reference 정확한 항목 조회 범위 → 구문 → 필드 → 동작 → 오류 → 최소 예시 → 관련 항목
troubleshooting 증상에서 원인·복구로 이동 증상 → 영향 → 안전 → 진단 → 원인 → 조치 → 복구 → 예방
design_doc 대안을 비교하고 결정 승인 요약 → 문제 → 목표 → 제약 → 대안 → 결정 → 구조 → 실패 → 배포 → 관측 → 위험

설치

제가 core runtime의 기준으로 삼은 버전은 Python 3.10 이상입니다. core runtime은 외부 Python package에 의존하지 않습니다.

python3 -m venv .venv
. .venv/bin/activate
python -m pip install -e .

Antigravity provider를 사용할 때만 선택 의존성을 설치합니다.

python -m pip install -e '.[antigravity]'

로컬 문서 저장소를 근거로 사용하기

저는 프로젝트 문서와 결정 이력이 섞이지 않도록 검색기의 기본 경로를 다음과 같이 나눴습니다.

wiki/projects
wiki/concepts
raw/branch-notes
raw/official-docs
raw/company-tech-blogs

프로젝트 문서 저장소를 직접 지정할 때:

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

검색 결과만 먼저 확인할 수도 있습니다.

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.jsonconstraints.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이 됩니다.

선택 이유 계약

제가 기술 선택을 검토할 때 다음 한 문장만 남아 있다면 선택 이유가 빠진 것으로 판단합니다.

application-core는 Spring DI를 의도적으로 사용한다.

이 한 문장만으로는 왜 Spring DI를 허용했는지 알 수 없습니다. ClariDoc은 기술 선택을 설명할 때 적어도 아래 내용을 함께 요구합니다.

context / constraint
  → chosen option
  → why it was chosen
  → realistic alternative
  → accepted cost
  → guardrail or boundary

실제 문장으로 옮기면 다음과 같습니다.

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 예시

제가 포함한 예시 글은 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가 설치되어 있고 인증할 수 있는지 먼저 확인합니다.

claridoc doctor --config config/pipeline.multi-agent.example.json

자세한 통합 계약은 docs/PROVIDERS.md를 참조합니다.

Mock 실행

제가 Mock을 둔 이유는 외부 모델을 부르지 않고도 파이프라인 연결과 artifact 생성을 확인하기 위해서입니다.

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-mock

Mock은 source excerpt를 글에 복사하지 않습니다. 실행 결과가 PASS여도 문장이 잘 쓰였다는 뜻은 아닙니다. 여기서 확인할 수 있는 것은 구조와 계약, 파이프라인 fixture가 연결됐다는 점까지입니다.

명령어

claridoc init [directory] [--force]
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]

validate, outline, lint, run은 local corpus 옵션을 공유합니다.

--source-root ROOT
--source-include RELATIVE_DIR     # 반복 가능
--source-top-k N
--source-max-per-file N

한국어 기술 블로그와 README 문체 계약

처음에는 prompt에 문체 지침을 한 번 넣으면 초안과 수정본이 같은 형식을 유지할 것으로 생각했습니다. 그런데 일부 문단만 고친 뒤 다른 절에서 한다, 있다, 아니다 같은 종결이 다시 나타나거나, 저는 한 문장만 추가하고 경험의 흐름을 검증하지 않는 경우가 있었습니다. 그래서 저는 한국어 독자용 본문의 문체를 prompt 지침이 아니라 작성·리뷰·수정·lint·품질 게이트가 함께 확인하는 계약으로 만들었습니다.

이 계약은 모든 한국어 readme에 자동으로 적용합니다. 한국어 technical_blog에서는 style_profileauto, 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에 있습니다. 핵심 설정은 다음과 같습니다.

{
  "document_type": "readme",
  "language": "ko-KR",
  "constraints": {
    "citation_style": "hidden",
    "style_profile": "auto"
  }
}

예제 brief와 근거 입력이 런타임 계약을 만족하는지 확인하고, 결정적 README 목차를 생성하려면 다음 명령을 실행합니다.

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가 아니라 구체적 문제에서 시작하는지
  • “제공된 근거 팩”, 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)
  • 한국어 기술 블로그와 README 본문의 합니다/했습니다 종결 일관성 (STYLE002)
  • 첫 문단과 주요 절의 근거 있는 저는/제가 경험 맥락 (STYLE003)
  • 절차의 사전 조건, 단계, 검증, 롤백
  • 파괴적 명령 주변의 영향 경고, checkpoint, verification
  • 금지 주장과 미해결 TODO

Lint를 통과했다고 문장의 의미까지 맞는 것은 아닙니다. Lint가 정해진 규칙을 검사한 뒤에도 model reviewer와 프로젝트 소유자가 내용을 다시 확인해야 합니다.

산출물

run-dir/
├── inputs/
│   ├── brief.normalized.json
│   ├── sources.normalized.json
│   └── pipeline.normalized.json
├── stages/
│   ├── 01-planner.raw.txt
│   ├── 02-outline.json
│   ├── 02-outline.md
│   └── 03-writer.raw.txt
├── rounds/round-*/
│   ├── draft.md
│   ├── lint.json
│   ├── lint.md
│   ├── review-*.json
│   └── quality-gate.json
├── final/
│   ├── document.md                 # 독자용
│   ├── quality-report.md
│   ├── provenance.md               # 내부용
│   └── evidence-map.json           # 내부용
├── provider-events.jsonl
├── run.json
└── manifest.json

manifest.json은 자신을 제외한 모든 artifact의 크기와 SHA-256을 기록합니다.

검증

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에서 확인할 수 있습니다.

한계

  • 제가 현재 구현에서 사용한 로컬 corpus 검색은 lexical ranking입니다. 의미가 유사하지만 단어가 다른 근거는 놓칠 수 있습니다.
  • source chunk가 검색됐다고 그 내용을 바로 본문에 쓸 수 있는 것은 아닙니다. status와 governing source를 함께 확인해야 합니다.
  • LLM reviewer의 합의는 진실의 증명이 아닙니다.
  • 실제 코드 예시, command, 운영 수치, 보안 주장은 대상 시스템에서 별도로 검증해야 합니다.
  • provider binary, SDK, 인증, quota, model ID는 실행 환경마다 다릅니다.
  • Mock 실행은 문서 품질을 증명하지 않습니다.

위협 모델과 prompt-injection 경계는 docs/SECURITY.md에 정리했습니다.