핸드오프: API 문서 · 멀티레포 · AI 친화 그래프

ADR-0001로 문서 시스템의 뼈대(docs-as-code 하이브리드)는 확정됐다. 이 문서는 그 위에서 ① API를 어디서·어떻게 관리할지, ② 레포가 여러 개일 때 문서를 어떻게 나눌지, ③ “노드 그래프”가 AI 친화에 실제로 뭘 의미하는지를 논의한 결과와, 이제부터 할 일을 정리한다. 실제 사용을 시작하기 전의 실행 계획서다. 아직 열린 질문(특히 계층 설계)이 있어 초안 상태로 둔다.

1. 이번 논의의 출발 질문

  • 지금 폴더 구조가 AI 친화적인가?
  • 백엔드와 프론트엔드가 같은 맥락을 공유하는 자리를 만들고 싶다. Swagger를 따로 관리하는 게 맞나?
  • “어느 화면에서 쓰는지, 몇 버전까지 쓰는지, 이 엔드포인트의 엣지케이스는 뭐였는지, request/response는 왜 이런지” — Swagger가 못 담는 것들을 링크·태그·노드로 관리하고 싶다.

2. 확정된 결정

2.1 레포 구성: 3레포 hub-and-spoke

  • FE 레포, BE 레포, team-docs 레포(허브, 이 저장소) 세 개.
  • 허브: FE·BE가 공유하는 지속적 지식 — 시스템 ADR, PRD, API 맥락, 회고, runbook.
  • 스포크: 각 코드 레포 안의 /docs구현 심화 문서를 둔다(그 서비스의 캐싱 동작, 내부 모듈 설계, 레포-로컬 ADR 등).
  • 스포크 문서는 코드와 같은 PR로 이동한다 → “코드 PR에 문서 포함” 규칙이 스포크에서는 실제로 작동한다(허브에서는 다른 레포라 불가).

2.2 Swagger/OpenAPI는 코드에서 생성, 문서에는 복붙하지 않는다

  • OpenAPI 스펙 = 계약(“무엇”). 코드에서 생성하고 BE 레포에 둔다. 빠르게 변하는 기계 진실.
  • 문서에 스키마를 복사하는 순간 문서 부패가 시작된다 → 링크만 건다.

2.3 Swagger가 못 담는 층 = 허브의 docs/api/

  • request/response의 이유, 운영에서 본 엣지케이스, gotcha, 어느 화면이 쓰는지(태그), PRD·설계·ADR·이슈 링크.

  • 이건 FE도 BE도 아닌 중립 지대여야 하므로 허브에 둔다. BE 레포에 두면 FE가 챙기지 않는다.

  • 한 API 문서 = 하나의 노드. frontmatter 태그 + 본문 링크가 엣지가 된다.

    ---
    type: api
    endpoint: POST /orders
    domain: order
    version: v2
    status: stable            # draft | stable | deprecated
    owner: backend
    screens: [checkout, cart-review]   # 힌트(코드가 진실)
    spec: <BE OpenAPI URL>             # 생성된 스펙 링크
    related:
      - ../product/checkout-prd.md
      - ../design/order-service.md
      - ../adr/0007-order-idempotency.md
    ---

2.4 AI에게 “그래프”는 마크다운 링크 그 자체다

  • AI는 .md[텍스트](경로) 링크와 frontmatter related:를 읽고 파일을 따라간다. 이게 엣지다.
  • Quartz의 그래프 뷰는 같은 링크를 사람 눈에 그림으로 그려주는 눈요기일 뿐. AI는 그림이 필요 없다.
  • 따라서 크로스레포 Quartz aggregate 그래프는 AI 목적에는 불필요하다. 비개발자가 사이트로 볼 때만 하면 된다.

2.5 계층과 노드는 동시에 성립한다

  • 계층 = 폴더(docs/adr/, docs/api/ …) + 파일명 규칙. AI가 경로로 찾아다니는 “지도”.
  • 노드/그래프 = 링크 + related:. 계층을 가로지르는 관계.
  • AI는 오히려 계층(경로)을 더 많이 쓴다. “적당히 계층이 있는 그래프”라는 방향이 맞다.

2.6 이슈 vs 문서

  • 문서 = 영구 지식(“이 엔드포인트는 동시요청 시 idempotency key 필수”). AI가 읽는다.
  • 이슈 = 일시적 작업(“이거 5xx 남, 고쳐야”). 닫히면 묻히고 AI가 읽기 어렵다.
  • 영구 API 지식은 문서에, 진행 중 작업은 이슈에, 서로 링크한다.

2.7 (선택) 컨트랙트 퍼스트

  • OpenAPI 스펙 파일을 PR로 편집하고 FE·BE가 그 diff를 함께 리뷰하면, 스펙 자체가 “같은 맥락”의 공유물이 된다.
  • FE·BE 정렬에는 가장 강력하지만 규율 비용이 있다. 도입 여부는 미결.

3. 트레이드오프 요약 (이번 논의분)

결정얻는 것감수하는 것
3레포 hub-and-spoke심화 문서가 코드와 함께 이동, 허브는 중립 공유지문서가 3곳에 분산 → 중복·링크 깨짐 위험 증가
Swagger 분리(코드에서 생성)문서 부패 없음, FE·BE 공통 참조스펙과 맥락이 두 곳(링크로 연결)
API 맥락은 허브 docs/api/Swagger가 못 담는 “왜/엣지케이스”를 그래프로손으로 유지 → 맥락 있는 것만 써야 함
크로스레포 그래프는 Quartz에서만사람 읽기 사이트에서 전체 연결편집 시점 Obsidian 백링크는 레포를 못 넘음
AI 친화 = 링크 텍스트별도 인프라 없이 즉시 성립related:를 실제로 채워야 의미 있음

4. 열린 질문 — 2026-07-24 전부 배정 완료

규칙: 모든 열린 질문은 세 버킷 중 하나 — 지금 결정 / 디폴트 채택+재검토 트리거 / 트리거까지 연기. “언젠가 정하자” 버킷은 없다.

  1. 계층 — ✅ 결정: 6절 안 그대로 채택.
  2. API 문서 단위 — ✅ 결정: 리소스 단위 평면 파일. 리소스 10개 초과 시 도메인 폴더 승격.
  3. 화면·버전 매핑 — 디폴트: screens:는 힌트, 코드가 진실. 힌트 오류로 실제 사고 발생 시 재검토.
  4. 컨트랙트 퍼스트 — ✅ 결정: 진짜 스펙 퍼스트 도입. ADR-0002 참고 (2.2절 “코드에서 생성”은 검증용으로 강등 — 충돌 해소).
  5. Quartz 접근 제어 — ✅ 결정(2026-07-24, 사용자 배포 지시로 트리거 앞당김): Cloudflare Pages + Cloudflare Access. GitHub Pages는 org free 플랜에서 private 불가라 기각. 파이프라인은 quartz-deploy.yml — 시크릿 미설정 시 배포 스킵 게이트로 잠금 전 노출 차단. 남은 1회 작업(시크릿 등록·Access 정책)은 quartz.md.
  6. Relay→repo 동기화 — 종결. 규칙 이미 있음(주 30분 초과 시 자동화).

추가로 발견·결정된 것 (기존 목록에 없던 것):

  1. 기존 스포크 문서 triage — ✅ 결정: 맥락은 허브 docs/api/로, 계약은 openapi.yaml로 분해 이동, 원위치엔 링크. as-built·구현 심화는 스포크 유지. FE 레포의 backend-*-spec.md 6개와 양쪽 중복 live-studio.md가 대상.
  2. 허브 git 레포화 — ✅ 결정: Tapplee/tapple-docs로 생성·push (마스터 지도 URL의 전제).

5. 다음 할 일 (우선순위)

실제 사용 시작 전에 뼈대만 있고 알맹이(링크·내용)가 비어 있다. “친화적일 구조”를 “친화적인 상태”로 바꾸는 작업.

  • P0. 루트 CLAUDE.md 채우기 — 완료: 코드 레포용 placeholder를 버리고 허브(지식 지도)형으로 재작성. FE/BE CLAUDE.md도 신규 생성(마스터 지도 3종 성립 — 단 허브 URL은 push 후 유효).
  • P0. related: 채우는 습관 — 빈 related: []는 엣지 0. 문서 작성 시 관련 문서를 실제로 링크.
  • P1. 정적 인덱스docs/index.md 생성 완료 (손 유지).
  • P1. 3레포 마스터 지도 — 각 레포 CLAUDE.md가 서로를 URL로 가리킨다(“시스템 결정=허브 <URL>, FE 심화=<URL>”). 3레포 AI 친화의 핵심. 현재 FE/BE에 CLAUDE.md 자체가 없음 — 신규 생성 필요.
  • P1. docs/api/ 신설 — 완료: api.md + 공개 명함 기능 3종 초안(public-card·nfc-card·analytics, as-built 기준).
  • P1. 스펙 퍼스트 후속ADR-0002: springdoc 시드 커밋, CI diff 검증, CODEOWNERS. (BE 레포 작업)
  • P1. 기존 스포크 문서 triage — 허브 측 완료 — API 맥락 5종(public-card·nfc-card·business-card·analytics·auth). admin 문서 2종은 내부 도구라 스포크 유지. 2026-07-24 triage 전체 완료: openapi.yaml 시드 후 FE의 backend-* 스펙 전부 해체 — business-card 계약은 스펙+허브로 분해, self-claim 2종은 BE docs/로 이사, csp 지시서는 as-built로 대체. FE docs/에는 FE 구현 심화(라우팅·배포·온보딩)와 이관 스텁만 남음.
  • P2. 자잘한 버그 픽스 — meeting.md 추가, relay.md 자기참조 수정, CONVENTIONS 예시 링크 수정 완료. 남은 것: status 어휘 3분열(문서 트랙/ADR 트랙 차이 명시) — 미처리.
  • P2. *.original.md 처리 — 원본 복원(롤백) 완료. 압축본 폐기.

6. 계층 설계 초안 (미결 — 여기가 핵심 고민)

6.1 두 축으로 나눈다

  • 1축: 문서 유형 = 폴더 (이미 있음). “이게 뭐냐”로 찾는다.
  • 2축: 도메인/기능 = frontmatter 태그(domain:). 유형을 가로지른다. “결제 관련 전부”로 찾는다.

폴더는 유형, 태그는 도메인. 이러면 “ADR 전부”(폴더)와 “결제 관련 전부”(도메인 태그) 둘 다 조회된다.

6.2 허브 폴더 구조 (제안)

docs/
├── product/     기획 (PRD) — Relay 공유
├── meetings/    회의록 — Relay 공유
├── design/      설계
├── adr/         결정 (append-only)
├── api/         API 맥락 (신규)
├── runbook/     운영
├── postmortem/  회고
├── templates/
├── assets/
└── setup/

6.3 API 문서 단위 (제안: 리소스 단위로 시작)

단위평가
엔드포인트POST /orders 하나당 문서너무 세밀. 수백 개 → 관리 불가
리소스order.md, payment.md적당. 한 리소스의 엔드포인트 묶음 + 공통 맥락
도메인주문 도메인 전체 하나너무 큼. 한 문서에 여러 목적 섞임
  • 추천: docs/api/에 리소스 단위 평면 파일로 시작(order.md, payment.md).
  • 원칙: 맥락이 있을 때만 문서가 존재한다. 엣지케이스나 비자명한 계약 결정이 있는 API만. 평범한 CRUD는 OpenAPI 항목으로 충분.
  • 리소스가 많아지면 도메인 폴더(docs/api/order/…)로 승격.

6.4 결정됨 (2026-07-24)

  • 6.3 단위 확정: 리소스 평면 파일로 시작, 10개 초과 시 도메인 폴더 승격.
  • domain: 태그는 자라는 대로. 현재 사용 중: card, nfc, analytics.

부록 A. 논의 경과 — 어떻게 여기 도달했나

시간 순으로, 특히 생각이 바뀐 지점을 남긴다. 결론만 보면 왜 그렇게 정했는지 6개월 뒤에 알 수 없기 때문이다.

  1. 출발 — “이 구조가 AI 친화적인가? BE·FE가 같은 맥락을 공유하는 자리를 원하는데 Swagger를 따로 관리해야 하나?”

  2. 1차 진단 — 구조는 이미 상당히 AI 친화적이다(위키링크 금지, frontmatter, ADR에 “왜” 보존). 단 함정 두 개: ① Dataview 인덱스는 Obsidian에서만 렌더링돼 AI·GitHub·기본 Quartz에는 안 보인다, ② AI 진입점인 CLAUDE.md가 빈 껍데기다. Swagger는 코드에서 생성하고 링크만 걸면 된다. 그리고 큰 질문을 던졌다 — docs가 코드와 같은 레포인가 별도인가? 별도면 “코드 PR에 문서 포함” 규칙이 성립하지 않는다.

  3. 요구 구체화 — 사용자가 원한 것: 어느 화면에서 쓰는지, 몇 버전까지 쓰는지, 이 엔드포인트의 엣지케이스, request/response의 이유를 태그·그래프로 관리. “Swagger가 이런 건 못 담잖아.”

  4. 세 층 분리 — 맞다. Swagger(“무엇”) / docs(“왜·맥락”) / **이슈(“일시적 작업”)**로 나누면 애매함이 사라진다. 함정 하나 못박음: 화면·버전 사용 매핑은 소비자→생산자 관계라 손으로 유지하면 즉시 썩는다. 맥락이 있는 API만 문서화한다.

  5. 레포 토폴로지 확정 — 사용자: FE·BE·team-docs 세 개의 별도 레포로 간다. 각 코드 레포에도 심화 문서를 둔다.

  6. hub-and-spoke로 정리 — 스포크(코드 레포 심화 문서)는 코드 PR과 함께 이동하니 좋다. 냉정한 사실: Obsidian 백링크/그래프는 레포 경계를 못 넘는다. 크로스레포 그래프는 Quartz aggregate 빌드(읽기 시점)에서만 산다. 최우선 규칙 = 한 사실은 한 레포에만(3레포에서 중복 위험이 3배).

  7. “심화 문서도 링크 걸 수 있으면 좋은데 안 되겠지?” — 된다, 조건부다. 클릭되는 링크는 공짜(전체 URL), 그래프 엣지·백링크는 Quartz aggregate 셋업 1회면 된다. 편집 시점 통합 그래프는 접어라.

  8. 핵심 전환 — 사용자가 방향을 다시 잡아줬다: “나는 사람 보기용 그래프가 아니라 AI 친화 때문에 노드를 생각한 거다. 노드로 연결됐지만 적당히 계층이 있는 구조.” → 여기서 내가 그동안 Quartz 그래프(사람용)를 과하게 얘기하고 있었음을 정정했다. AI에게 그래프는 마크다운 링크 텍스트 그 자체다. Quartz 그래프 뷰는 눈요기일 뿐이고, 크로스레포 aggregate는 AI 목적에는 불필요하다. 계층(폴더)과 노드(링크)는 동시에 성립한다. → 이 정정으로 크로스레포 그래프 엔지니어링이라는 큰 작업이 범위에서 빠졌다.

  9. 합의 — “md로 이미 AI 친화, 크로스레포 Quartz는 사람용 nice-to-have, 그럼 다 굿?” → 방향·뼈대는 굿. 다만 지금은 뼈대만 있고 알맹이(related: 링크·본문·CLAUDE.md·정적 인덱스·마스터 지도)가 비어서 “친화적일 구조”이지 “친화적인 상태”는 아니다. 실행은 이제 시작.

세션 중 곁가지: 문서 15개를 caveman 스타일로 압축 실험했으나 절감 1%(대부분 frontmatter·코드블록·표라 압축 여지 없음)에 그쳐 무의미로 판정. 원본은 *.original.md로 백업. 롤백 대기.


다음 갱신: 계층(6절)이 확정되면 이 문서 상태를 업데이트하고, docs/api/ 실물과 마스터 지도를 만든다.