문서 기여 규칙 (Documentation Guide)¶
문서를 새로 쓰거나 · 옮기거나 · 번역할 때 보는 문서다. 무엇을 읽을지 찾는 중이라면 여기가 아니라
docs/README.md 로 간다.
이 규칙들은 원래 docs/README.md 안에 있었다. 그 페이지가 사이트의 첫 화면이기도 해서, 처음 온
사람이 "AIMON 이 무엇인가" 대신 "체크박스는 plan/ 에만 둔다" 를 먼저 읽게 되어 갈라냈다.
1. 어디에 둘까¶
새 문서를 추가할 때 막히면, 누가 읽는가가 아니라 무엇에 대한 문서인가로 정한다.
| 문서의 내용 | 위치 |
|---|---|
| AIMON 전체를 조망하는 것 (기능 목록, 용어, 수명 규칙) | overview/ |
| 처음 붙이는 절차 | getting-started/ |
| 특정 기능의 사용·개발·운영 방법 | features/<기능>/ |
| 설계 결정의 근거 / 기각한 대안 | design/<도메인>/ — 도메인은 features/ 와 같은 이름 |
| 의식적으로 보류한 설계 항목 | design/backlog/ |
| 끝난 작업이 남기고 간 열린 항목 | backlog/ — 열림/닫힘의 정본 |
| 여러 PR에 걸친 작업의 진행 상태 / 다음 할 일 | plan/ |
| 외부 명세나 패턴 인용 | references/ |
| 버전 업그레이드 절차, 그리고 개명·동결 이름 조회표 | migration/ |
| 프로젝트 자체의 운영 (원칙·릴리스·품질·이 문서) | project/ |
IMPORTANT: 같은 기능의 "개발자용"과 "운영자용" 문서를 서로 다른 디렉토리로 가르지 않는다.
둘 다 features/<기능>/ 에 둔다 — 한 기능을 붙이는 사람은 대개 둘 다 읽는다.
기능 디렉토리를 새로 만들었다면 features/README.md 색인과
overview/features.md 카탈로그 양쪽에 반영한다.
2. 사이트에 게시되지 않는 디렉토리¶
docs/ 아래 두 디렉토리는 저장소에는 있지만 문서 사이트에는 빌드되지 않는다.
mkdocs.yml 의 exclude_docs 가 그것을 정한다.
| 디렉토리 | 왜 빼는가 |
|---|---|
backlog/ |
우리 팀의 작업 목록이다. 사이트에 오는 사람이 찾는 것은 "무엇을 쓸 수 있나" 이지 "무엇이 아직 안 됐나" 가 아니다 |
plan/ |
진행 추적 문서. 끝나면 지워지는 것을 게시하면 사이트에 유령 페이지가 남는다 |
지우는 것이 아니라 게시하지 않는 것이다. 두 디렉토리는 GitHub 에서 그대로 열리고, 기여자는 계속
읽고 고친다. 사이트 페이지에서 그쪽을 가리키는 상대 링크는
scripts/mkdocs_github_links.py 가 렌더 시점에 GitHub URL 로
바꾸므로, 링크가 죽지 않는다 — 소스에는 상대 경로 그대로 쓴다.
design/backlog/ 는 빠지지 않는다. 그것은 작업 목록이 아니라 "왜 아직 만들지 않았는가" 의 설계
근거이고, design/ 의 나머지와 같은 성격이다. 그래서 패턴을 /backlog/ 로 적는다 — 앞의 슬래시가
없으면 gitignore 문법상 모든 깊이의 backlog/ 가 걸려서 그것까지 함께 사라진다.
새로 뺄 디렉토리가 생기면 exclude_docs 에 한 줄 더하는 것으로 끝난다. 링크 처리는 그 훅이 같은
목록을 읽어서 따라온다.
3. design/ · plan/ · backlog/ 를 가르는 기준¶
3.1 design/ 과 plan/¶
같은 작업에 대해 두 문서가 생길 수 있다. 나누는 기준은 무엇이 문서를 갱신시키는가다.
design/ |
plan/ |
|
|---|---|---|
| 담는 것 | 왜 이렇게 하기로 했는가 — 분류·구조·결정의 근거 | 어디까지 했고 다음에 무엇을 하는가 |
| 갱신 시점 | 결정이 바뀔 때만 | 작업이 진척될 때마다 |
| 수명 | 영구 (구현 여부는 첫머리 Status 한 줄이 말한다) |
작업 종료 시 삭제 |
체크박스·"미착수/완료" 같은 상태 표기는 plan/ 에만 둔다. 설계 문서가 진행률을 들고 있으면 근거를
읽으러 온 사람이 낡은 상태 표를 먼저 만나게 된다. 반대로 계획 문서는 근거를 복사하지 말고 설계 문서를
링크한다.
plan/ 디렉토리는 진행 중인 계획이 있을 때만 존재한다. 끝나면 문서를 지우고, 남길 가치가 있던
근거는 design 문서나 규칙 문서로 옮긴다 — OrcaAgentRuntime 통합 테스트 계층은
design/agent-execution/integration-test-layers.md
로, turn 용어 정리는 overview/glossary.md §4 의 규칙으로 옮겨 갔다.
3.2 backlog/ 와 design/backlog/¶
이름이 겹치지만 담는 것이 다르다.
design/backlog/ |
backlog/ |
|
|---|---|---|
| 담는 것 | 설계 자체를 보류한 것 — 형태는 확정됐지만 소비자가 없어 만들지 않았다 | 구현하고 남은 것 — 만들었는데 어떤 항목을 뒤로 미뤘다 |
| 단위 | 문서 하나 = 보류한 설계 하나 | 문서 하나 = 끝난 작업 하나가 남긴 항목 전부 |
| 나오는 시점 | 설계 중 | 작업 종료 시 |
IMPORTANT: 무엇이 열려 있는지의 정본은 backlog/ 다. 설계 문서의 우선순위 표(P0/P1/P2, 미해결 U)는
설계 시점의 기록으로 동결되어 있으므로, 거기서 취소선이 없다고 열려 있는 것이 아니다. 이 규칙이
필요하다는 증거는 첫 등록 때 바로 나왔다 — 스타터 설계 문서의 표는 이미 배선된 항목 하나를 여전히
열린 것처럼 싣고 있었다. 자세한 것은 backlog/README.md.
backlog/ 는 plan/ 과도 다르다. 계획 문서는 진행 중인 작업의 다음 할 일을 담고 끝나면 지우지만,
백로그 항목은 작업이 끝난 뒤에 생겨서 누가 집어 갈 때까지 남는다. 그래서 "상태 표기는 plan/ 에만"
규칙의 예외이며, 대신 담는 상태를 열림/닫힘 하나로 제한한다 — 진행률이 들어가는 순간 지워지지 않는
plan/ 이 된다.
4. 링크 규칙¶
- 문서 간 링크는 상대 경로로 쓴다.
- 문서를 옮길 때는 그 문서를 가리키는 모든 링크를 함께 고친다. 상대 경로는 문자열 치환으로 고칠 수 없다 — 옛 디렉토리 기준으로 해석 → 새 위치로 매핑 → 새 디렉토리 기준으로 다시 상대화해야 한다.
- Java Javadoc이나
CLAUDE.md에서 문서를 가리킬 때는 리포지토리 루트 기준 경로 (docs/features/tool/tool-development-guide.md)를 쓴다. docs/밖(소스 파일,CHANGELOG.md)을 가리키는 상대 링크도 그대로 쓴다. 사이트 빌드 때scripts/mkdocs_github_links.py가 GitHub URL 로 바꾼다.- 링크는 자동으로 검사된다 —
python3 scripts/check-doc-links.py가 경로와#앵커를 둘 다 본다. CI 의docs-links잡이 같은 것을 돌린다.
제목을 고치면 앵커가 바뀐다. 문서 안의 목차와 다른 문서의 #fragment 를 같은 PR 에서 다시 겨눈다.
5. 번역 규칙¶
정본은 한국어다. 영어 문서는 번역이며, 접미사로 구분한다 — 정본 파일은 한 칸도 움직이지 않는다.
docs/features/tool/tool-development-guide.md ← 한국어 정본 (경로 불변)
docs/features/tool/tool-development-guide.en.md ← 영어 번역
이 접미사가 사이트의 URL 을 정한다.
| URL | 내용 |
|---|---|
/ |
한국어 — 무접미사 파일 |
/en/ |
영어 — .en.md 파일 |
번역이 없는 문서는 /en/ 에서 404 가 아니라 한국어 정본이 그대로 나온다. 그래서 번역을 한
디렉토리씩 진행해도 사이트는 늘 온전하다 — 한 번에 다 번역해야 할 이유가 없다는 뜻이다.
루트가 한국어인 것은 취향이 아니라 접미사 방식의 귀결이다. 무접미사 파일은 기본 로케일의 것이고
기본 로케일은 언제나 루트에 빌드되므로, 영어를 루트로 두려면 정본 전체에 .ko 를 붙여야 한다.
5.1 무엇을 번역하는가 — 디렉토리로 정한다¶
문서마다 판단하지 않는다. 번역 대상은 디렉토리로 못박혀 있고, 이 표가 그 경계다.
| 디렉토리 | 상태 | 이유 |
|---|---|---|
docs/README.md |
대상 | 사이트 진입점 |
overview/ |
대상 | 이 프로젝트가 무엇인지 묻는 사람이 처음 여는 곳. 용어·수명이 다른 번역의 기준이 된다 |
getting-started/ |
대상 | 붙여 보려는 사람의 경로 |
features/ |
대상 | 기능을 쓰려는 사람의 경로 |
project/ |
아직 아님 | 대상으로 승격 가능. 지금 미룬 것은 우선순위이지 성격이 아니다 |
references/ |
아직 아님 | 같음. 외부 명세와의 대조표라 수요가 생기면 앞당긴다 |
migration/ |
아직 아님 | 같음. 다만 새 마이그레이션 문서가 나오는 시점이 자연스러운 승격 시점이다 |
design/ |
대상 아님 | 설계 근거의 기록. 기여자의 진입 경로가 아니고, 가장 자주 바뀐다 |
backlog/ |
대상 아님 | 같은 이유. 밖에서 오는 문은 GitHub Issues 다 |
plan/ |
대상 아님 | 끝나면 지워지는 문서다 |
가운데 세 줄과 아래 세 줄은 다른 말을 한다. "아직 아님" 은 미룬 것이고 승격에 새 근거가 필요 없다 — 이 표의 상태만 바꾸면 된다. "대상 아님" 은 번역하지 않기로 결정한 것이고, 뒤집으려면 그 결정을 먼저 뒤집어야 한다.
경계를 옮기고 싶으면 이 표를 먼저 고친다. 표에 없는 디렉토리를 번역하면 다음 사람이 "여기는 왜 번역이 있고 저기는 없나" 를 매번 다시 판단하게 된다.
그리고 승격은 열린 백로그 항목 하나를 깨운다 —
../backlog/translation-tooling-open-items.md 의 T-1
(번역 구조 일치 규칙에 강제 장치가 없다). 쌍이 늘면 손으로 맞추는 비용도 함께 늘기 때문이다.
5.2 리포지토리 루트는 방향이 반대다¶
docs/ 아래의 정본은 한국어지만, 루트의 README.md · CONTRIBUTING.md · SECURITY.md ·
CODE_OF_CONDUCT.md · MAINTAINERS.md 는 영어가 정본이다. GitHub 이 그 파일들을 먼저 보여
주고, 그것을 여는 사람이 아직 이 프로젝트의 언어를 모르기 때문이다.
따라서 그쪽의 번역 파일은 .ko.md 이고, translated_from 이 가리키는 방향도 반대다.
접미사 규약은 양쪽에서 같다 — 접미사가 붙은 쪽이 번역이다.
5.3 번역 파일에는 frontmatter 를 붙인다¶
번역 파일에만 붙인다. 정본은 건드리지 않는다 — 정본에 메타데이터를 붙이면 번역이 없는 문서에도 번역 관리 부담이 생긴다.
| 필드 | 값 |
|---|---|
translated_from |
정본 파일의 리포지토리 루트 기준 경로 |
source_commit |
번역이 따라간 정본의 마지막 커밋 SHA (짧은 형식) |
source_commit 이 있어야 정본만 바뀌고 번역이 안 따라온 상태를 사람이 아니라 도구가 판정할 수
있다. 판정은 간단하다 — 그 SHA 이후로 translated_from 경로에 커밋이 있으면 번역이 낡은 것이다.
번역을 갱신할 때는 본문과 source_commit 을 같은 커밋에서 함께 고친다. 따로 고치면 이 필드는
"마지막으로 누군가 신경 쓴 시점" 이라는 다른 뜻이 되어 버린다.
source_commit 에 적을 것은 이번 수정 직전의 정본 커밋이다 (자기 커밋 SHA 는 미리 알 수 없다).
python3 scripts/check-translation-staleness.py 가 뒤처진 번역을 보고하며, 정본과 번역을 함께 건드린
커밋은 건너뛰므로 이 한 커밋의 지연은 낡음으로 세지 않는다.
그 SHA 가 해석되지 않으면 CI 가 실패한다. 낡음(stale)과 해석 불가(unresolvable)는 다른 것이다 — 전자는 검사가 답을 냈고 그 답이 나쁜 것이고, 후자는 답이 없는 것이다. 낡음이 빌드를 실패시키지 않는 이유(번역 지연이 정본 수정을 막으면 안 된다)는 해석 불가에는 적용되지 않는다. 그쪽에서 요구하는 것은 번역이 아니라 존재하는 SHA 한 줄이고, 실패시키지 않으면 초록 빌드 뒤에 무기한 남는다 — 실제로 오픈소스 전환 스쿼시가 32건 중 19건의 SHA 를 한 번에 없앴고, 두 발견이 exit code 를 공유한 탓에 그 상태로 계속 초록이었다.
히스토리 재작성으로 적어 둔 SHA 가 사라졌다면, 정본이 번역한 그 상태로 담겨 있는 가장 오래된 커밋을 겨눈다. 그리고 겨누기 전에 정말 그 상태인지 확인한다 — 어긋난 번역에 붙은 해석 가능한 SHA 는 해석 불가보다 나쁘다. 침묵은 모른다고 말하지만 그것은 안다고 거짓말한다.
그 마지막 문장이 더 이상 전부 참은 아니다 — check-translation-structure.py 가 그중 일부를 잡기
때문이다. 쌍이 같은 시점이라고 주장하는데 구조가 어긋나 있으면 그 검사가 실패시킨다. 그래서
source_commit 을 아무 커밋에나 겨누는 것은 이제 초록으로 통과하지 않는다 — 다만 그 검사가 보는 것은
모양이므로, 모양만 맞고 내용이 다른 번역은 여전히 사람만 잡을 수 있다.
5.3.1 구조를 맞출 수 없을 때 — 축 단위 예외¶
축 하나가 정당하게 어긋난다면 그 번역본 frontmatter 에 선언한다.
- 값은 쉼표로 구분한 축 id 문자열이다(
headings·fences·table-rows·list-items·quote-blocks). YAML 목록으로 적으면 안 된다 — mkdocs 는 리스트로 읽고 스크립트는 문자열로 읽어 둘째 항목이 조용히 사라진다 - 이유는 큰따옴표로 감싼다. 감싸지 않고 콜론·백틱·대괄호를 쓰면 mkdocs 가 frontmatter 를 통째로
버리고 그것을 본문으로 발행하는데,
mkdocs build --strict는 그때도 초록이다 - 이유 없는 예외도, 예외 없는 이유도 실패한다. 둘 다 아무것도 끄지 않으면서 껐다고 읽히는 줄이다
- 예외는 만료된다. 축이 다시 일치하면 검사가 그 줄을 지우라고 한다
- 같은 축에 예외가 셋 이상 쌓이면 틀린 것은 그 파일들이 아니라 그 축이다 —
../design/documentation/translation-structure-check.md§4.3
5.4 번역본 안의 상대 링크는 번역이 있는 것만 접미사로 가리킨다¶
번역본이 foo.md 를 가리킬지 foo.en.md 를 가리킬지는 사이트에서 차이가 없다 — i18n 플러그인이
둘을 같은 페이지로 해석하고, 번역이 없는 문서는 404 가 아니라 한국어 정본을 내보낸다.
차이는 GitHub 에서 난다. GitHub 은 플러그인 없이 파일을 그대로 열기 때문에 .en.md 는 그 파일이
있을 때만 맞고, 없으면 404 다. 따라서 규칙은 하나로 정리된다.
| 대상의 번역 | 링크에 쓸 것 |
|---|---|
| 있다 | foo.en.md — 양쪽 다 맞다 |
| 아직 없다 | foo.md — 사이트는 한국어로 대체하고, GitHub 은 열린다 |
배치가 하나 끝나면 앞 배치들의 링크 중 이 규칙에 새로 걸리는 것이 생긴다.
python3 scripts/upgrade-translation-links.py 가 그것만 골라 올린다 — 손으로 찾지 않는다.
5.5 용어¶
한 단어를 여러 가지로 옮기는 것을 막는 표가 따로 있다 —
translation-glossary.md. 번역 전에 §1(번역하지 않는 것)과
§2(turn/iteration/execution)는 반드시 읽는다. 표에 없는 단어를 새로 정했다면 같은 PR 에서
그 표에 추가한다.
6. 문서를 고치기 전에 돌려 볼 것¶
pip install -r docs-requirements.txt
mkdocs serve # http://127.0.0.1:8000 에서 미리보기
mkdocs build --strict # CI 가 돌리는 것과 같다 — 링크 경고가 실패가 된다
python3 scripts/check-doc-links.py # 경로 + 앵커
python3 scripts/check-backlog-registers.py # 등록부의 중복 ID · 제목과 색인의 건수
python3 scripts/check-translation-staleness.py # 뒤처진 번역
python3 scripts/check-translation-structure.py # 정본과 어긋난 구조
앞의 둘은 CI 의 docs-links 잡에서 돈다 — 둘 다 텍스트만 읽으므로 얕은 클론으로 충분하다.
뒤의 둘은 CI 의 translations 잡에서 함께 돈다. 둘 다 전체 이력을 필요로 하므로(얕은 클론에서는
실패시키는 대신 보고한다) 그 잡만 fetch-depth: 0 으로 체크아웃한다.
7. 틀린 기록을 고칠 때 — 제자리 수정과 대체¶
CHANGELOG.md 항목처럼 쓰인 시점을 기록하는 문장이 틀렸다고 드러나면, 고치는 모양은 그 문장이 무엇을
잘못 말했는가로 정한다.
| 그 문장은 | 고치는 모양 |
|---|---|
| 쓰일 때부터 그 트리를 잘못 서술했다 | 제자리에서 고친다 |
| 쓰일 때는 맞았고, 뒤의 변경이 사실을 바꿨다 | 사실을 바꾼 변경의 항목에 대체 문장을 둔다 ("This supersedes …") — 옛 문장이 "전", 새 항목이 "후" 다 |
대체 문장은 사실을 바꾼 변경이 있을 때만 속할 자리가 있다. 바뀐 것이 없는데 대체 문장을 쓰면 기록의 정정이
변경의 모양을 입는다. 그리고 틀린 문장이 아직 릴리스되지 않은 절([Unreleased])에 있으면, 대체는 틀린 문장과
그 정정을 같은 릴리스 노트에 함께 싣고 틀린 문장을 읽는 독자는 정정을 만나지 못한다.
design/README.md §3.4 의 면제 기록은 이 규칙이 아니라 그 절을 따른다 — 경계 앞 본문은
고치지 않고, 틀린 곳은 경계 뒤 절에 적는다.
관련 문서¶
docs/README.md— 문서 사이트의 첫 화면. 무엇을 읽을지 찾는 곳translation-glossary.md— 번역 용어표CONTRIBUTING.md— 기여 절차 전반 (한국어)design/README.md— 설계 문서 색인