Readable Docs from AI Markdown
AI에게 설계 문서나 조사 기록을 맡기면 분량은 금방 차지만 사람이 읽을 순서는 비어 있는 경우가 많습니다. 직접 만들어 공개한 정적 문서 도구 for-humanity로 AI가 HTML로 만든 업무 설계 문서와 개인 프로젝트의 핸드북을 Markdown으로 옮겼습니다. 그때 정한 원본 보존 순서, 읽는 구조를 입히는 기준, 생성된 문서의 검증 방법을 도구 0.21.0 기준으로 정리합니다.
AI 문서에서 반복해서 본 문제
옮기기 전에 업무 설계 문서 HTML 5개와 핸드북 HTML 11개를 다시 읽었습니다. 두 묶음 모두 표가 많았지만 같은 종류의 표는 아니었습니다.
| 항목 | 업무 설계 문서 | 핸드북 |
|---|---|---|
| 표 | 121개 | 170개 |
| 칸 안에 목록, 문단, 코드가 든 표 | 99개 | 0개 |
| 가장 넓은 표의 열 수 | 10 | 14 |
| 원본 이미지 | 23개 | 0개 |
설계 문서의 표는 비교보다 상세 설명을 담는 데 쓰였습니다. 한 칸에 문단과 코드가 들어간 10열 표는 본문 폭을 넓혀도 읽히지 않습니다. 표의 개수만 보고 새 표현 부품이 필요하다고 판단할 수 없었던 이유입니다.
내용에서도 같은 문제가 반복됐습니다.
- 같은 사실이 화면 설명, 질문 카드, 변경 기록에 서로 다른 상태로 반복됐습니다. 보기 좋은 문서도 이렇게 되면 잘못 읽힙니다.
- 자동 요약은 계약, 숫자, 예외를 쉽게 잃었습니다. 정리는 옮긴 뒤에 해야 했습니다.
- 정보가 작은 화면 캡처 안에만 있으면 모바일과 보조 기술에서 읽을 수 없습니다.
- 큰 절마다 파일을 나누면 사이드바가 길어집니다. 첫 변환에서 설계 문서를 절별 60개 파일로 나눴다가 화면 문서와 구조 문서의 내부 제목으로 다시 합쳤습니다. 핸드북 두 개도 18개 절별 파일을 각 문서 안의 제목으로 되돌렸습니다.
원본은 고치지 않고 보존부터 합니다
AI 문서를 옮길 때 가장 먼저 정한 원칙은 원본을 고치지 않는 것이었습니다. 옮기는 순서는 다음과 같습니다.
- 원본 목록, SHA256, 이미지, 앵커, 표의 열 수와 병합 셀을 기록합니다.
- 원본은 그대로 두고 목적지의 문서 경계와 부모 관계, 정본 위치를 설계합니다.
- 첫 변환에서는 현재, 제안, 미확인 상태와 코드, 계약을 보존합니다. 반복된 개정 기록도 이 단계에서는 줄이지 않습니다.
- 원본과 결과를 문장과 값 단위로 대조합니다. 빠진 것은 원문을 다시 읽어 복원합니다.
- 최신 동작만 추린 편집본은 별도 작업으로 만듭니다.
대조는 정규화한 글자 비교로 했습니다. 실제 이전에서는 코드 블록 110개와 본문, 목록, 표 칸 5,631개를 원문과 맞춰 봤습니다. 아래는 같은 방식을 Markdown 사이에 적용하도록 다시 쓴 예시입니다.
import { createHash } from 'node:crypto'
import { readFileSync } from 'node:fs'
// 링크 주소, 기호, 공백을 지우고 글자만 비교합니다
const normalize = (text: string) =>
text.replace(/\]\([^)]*\)/g, ']').replace(/[\s`*_>#[\]-]/g, '').toLowerCase()
// 줄과 표 칸을 비교 단위로 씁니다. 표를 문단으로 풀어도 칸 단위로 찾을 수 있습니다
const units = (text: string) =>
text.split(/\n|\|/).map(normalize).filter((unit) => unit.length > 1)
export const compareUnits = (sourcePath: string, targetPaths: string[]) => {
const source = readFileSync(sourcePath, 'utf8')
const target = normalize(targetPaths.map((path) => readFileSync(path, 'utf8')).join('\n'))
const all = units(source)
return {
sha256: createHash('sha256').update(source).digest('hex'),
units: all.length,
missing: all.filter((unit) => !target.includes(unit)),
}
}missing에는 지운 예외 문장뿐 아니라 이름을 바꾼 제목과 열 이름도 함께 나옵니다. 그래서 목록을 사람이 한 번 걸러야 합니다. 이 검사는 글자가 남았는지를 볼 뿐이고 문장이 맞는지, 업무 규칙이 빠짐없이 옮겨졌는지는 증명하지 못합니다.
문법도 원본을 해치지 않는 쪽을 골랐습니다. for-humanity는 일반 Markdown에 frontmatter를 붙인 파일을 읽습니다. 설계 문서 전용 타입을 두지 않기로 했고 .mdx 파일도 Markdown으로 읽으며 JSX는 실행하지 않습니다. 추가 문법은 :::note, :::details, ::part 정도라 도구 없이 저장소에서 열어도 문서가 읽힙니다. AI가 만든 HTML 프로토타입의 상자와 대화상자를 모두 부품으로 만들지 않고 제목, 목록, 비교표, 그림, 명시적 링크로 먼저 옮겼습니다.
사이드바, 제목, 표로 읽는 순서를 만듭니다
읽는 구조는 세 가지로 입힙니다. 사이드바는 문서의 경계를, 제목은 문서 안의 순서를, 표는 같은 기준의 비교를 맡습니다.
사이드바는 frontmatter의 group과 parent로 만듭니다. group은 클릭할 수 없는 분류 경로이고 parent는 같은 분류 안에서 문서를 다른 문서 아래에 놓습니다.
---
name: Upload screen
label: 업로드 화면의 상태, 질문, API와 검증 조건
group: [Projects, Design]
parent: ./overview
order: 20
---flowchart TB
G["group: Projects / Design"] --> O["overview: 범위와 핵심 결정"]
O --> A["upload screen: 상태, 질문, API"]
O --> B["list screen: 상태, 질문, API"]
O --> R["공통 참조: 오류 코드"]문서를 나누는 기준은 본문 폭이 아니라 독자입니다. 한 화면을 이해하는 데 필요한 구성, 상태, 질문, API, 검증은 한 문서의 ##와 ### 제목으로 잇습니다. 다른 독자, 다른 작업, 여러 문서가 함께 찾아볼 공통 참조만 하위 문서로 나눕니다. 제목은 페이지 목차가 되므로 번호보다 안정적인 이름을 붙입니다.
표는 내용의 모양을 보고 고릅니다.
| 내용 | 쓰는 표현 | 피하는 표현 |
|---|---|---|
| 같은 기준의 짧은 비교 | 2~5열 표 | 칸 안에 문단과 목록을 압축 |
| 항목마다 긴 이유와 조건 | 제목과 문단, 목록 | 숫자 표처럼 보이는 큰 행렬 |
| API 요청, 응답, 오류 | 항목별 제목과 코드, 짧은 표 | 요청과 오류를 한 표에 모두 넣기 |
| 확인할 질문 | ID, 상태, 선택지, 근거를 적은 절 | 번호만 보고 의미를 추측 |
| 화면의 위치 | 설명을 붙인 그림 | 작은 그림 속 글씨에만 의존 |
5열은 렌더러의 제한이 아니라 보수적인 분류 기준입니다. 짧은 6열 표는 읽히고 긴 3열 표는 읽히지 않을 수 있습니다. 실제 이전에서는 원본 표 291개 중 단순 비교 175개를 표로 남기고 116개는 열 이름을 제목 삼은 설명으로 풀었습니다. 병합 셀은 공통 열의 제목을 함께 적거나 공유 값을 행마다 다시 적어 관계를 지켰습니다. 넓은 표와 코드는 상자 안에서만 가로로 스크롤되게 하고 페이지 전체가 밀리지 않는지 좁은 화면에서 확인합니다.
AI에게 줄 작성 규칙
AI에게 형식 하나를 강제하지 않았습니다. 대신 독자가 찾을 질문, 근거의 종류, 현재 사실과 제안과 미확인 항목의 구분, 읽는 순서를 먼저 정하게 했습니다. 목적에 따라 앞에 둘 것과 뒤에서 찾을 것이 다릅니다.
| 목적 | 앞에서 읽을 것 | 뒤에서 찾을 것 |
|---|---|---|
| 설계 | 범위, 핵심 결정, 남은 질문, 화면 흐름 | 항목별 계약, 상태, 검증 조건 |
| 참조 | 지원 범위, 버전, 정본 | 값, 오류, 예외, 짧은 예, 출처 |
| 조사 | 확인할 질문, 관찰, 결론과 한계 | 자료, 재현 절차, 미확인 항목 |
| 검증 기록 | 무엇을 어디까지 확인했는가 | 환경, 명령, 관찰, 확인하지 않은 것 |
이 규칙은 저장소의 에이전트 지침 파일이 작성 가이드를 가리키는 방식으로 연결할 수 있습니다. 사용자 저장소의 지침 전체를 덮어쓰지 않고 문서 작성에 필요한 부분만 잇습니다. 언어와 출처 우선순위는 각 프로젝트가 정합니다.
## Writing documents
- Start with the question the reader needs answered and the scope in one sentence.
- Mark each claim as current fact, decision, proposal or unverified.
- Do not report a successful render as a verified behavior.
- Keep numbers, exceptions, sources and IDs from the source.
- Keep one canonical place per fact and link to it explicitly.
- Prefer headings, lists and short tables before inventing new syntax.
- Put unimplemented directives only inside code blocks.
- Follow docs/writing.md for terms and tone.체크박스와 상태 색은 수행 증거를 대신하지 않습니다. 질문에 답이 없다는 이유로 추천안을 채택 상태로 바꾸지 않게 한 것도 같은 이유입니다.
생성된 문서를 빌드에서 검증합니다
AI가 문서를 한꺼번에 고치면 사람이 모든 링크와 frontmatter를 읽어 볼 수 없습니다. 그래서 검사는 빌드가 맡게 했습니다. 0.21.0에서 빌드를 멈추는 입력은 다음과 같습니다.
- frontmatter의
name,label,group이 없거나 형식이 틀리면 스키마 검사가 파일과 필드를 알려 주고 실패합니다. - 같은 ID가 되는 파일 두 개, 없는
parent, 순환하는parent, 다른group의parent는 실패합니다. - 공개 문서가 내부 문서나 내부 경로를 참조하면 파일과 줄 번호를 알려 주고 실패합니다.
- 끝 표시가 없는 코드 스니펫 영역, 이미지가 아닌 파일을 넣은 이미지 문법도 실패합니다.
없는 문서를 가리키는 링크는 다릅니다. 파일과 줄 번호를 경고로 보여 주지만 종료 코드는 0입니다. 작성 중에는 편하지만 배포 전에는 놓치기 쉬우므로 경고도 실패로 다루는 감싸기 스크립트를 둡니다.
import { spawnSync } from 'node:child_process'
const result = spawnSync('pnpm', ['exec', 'for-humanity', 'build'], { encoding: 'utf8' })
const output = `${result.stdout}${result.stderr}`
process.stdout.write(output)
// 터미널에서는 " WARN ", CI에서는 "[warn]"으로 찍힙니다
const warnings = output.split('\n').filter((line) => /\sWARN\s|\[warn\]/.test(line))
if (result.status !== 0 || warnings.length > 0) {
console.error(`build failed: exit ${result.status}, ${warnings.length} warning(s)`)
process.exit(1)
}도구가 모르는 규칙은 저장소 쪽 검사로 보탭니다. 이 사이트는 빌드 뒤에 별도 스크립트로 가운뎃점, 용어 통일표, 연결어미 뒤 쉼표, 공개 범위의 비공개 명칭을 찾습니다. 빌드는 홈과 공개 문서만 검사하므로 내부 문서의 frontmatter 누락도 이 스크립트가 봅니다.
개발 서버도 검증 대상이었습니다. AI가 문서를 대량으로 고치는 동안 0.3.0 개발 서버는 전체 렌더를 겹쳐 돌리다 메모리 부족으로 멈췄습니다. 0.4.1에서 연속 저장을 합치고 갱신을 하나씩 처리하도록 바꾼 뒤 35회 연속 저장, 늦게 추가한 이미지, 잘못된 문서의 복구를 실제 개발 서버에서 확인했습니다.
자동 검사가 증명하지 못하는 범위도 적어 둡니다. 렌더링이 성공했다고 문서의 주장이 맞는 것은 아닙니다. 마지막에는 사람이 다음 질문으로 읽습니다.
- 처음 읽는 사람이 현재 상태와 다음 판단을 찾을 수 있는가
- 비교가 필요한 표와 항목별 설명을 구분했는가
- 원본의 수치, 조건, 예외, 출처, 미확인 상태가 남아 있는가
- 정본과 요약이 같은 상태를 가리키는가
- 구현되지 않은 문법이나 가상의 API를 실제 기능처럼 소개하지 않았는가