Skip to content

문서 기여 규칙 (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.ymlexclude_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: docs/features/tool/tool-development-guide.md
source_commit: eec9ccd
---
필드
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 에 선언한다.

structure_exempt: list-items
structure_exempt_reason: "정본의 3항 목록이 영어에서는 관용적으로 2항이 된다"
  • 값은 쉼표로 구분한 축 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 의 면제 기록은 이 규칙이 아니라 그 절을 따른다 — 경계 앞 본문은 고치지 않고, 틀린 곳은 경계 뒤 절에 적는다.


관련 문서