양방향 링크 시스템 회고: 프로젝트 스케일에 따른 도구화 결정

같은 양방향 마커 시스템을 세 개 프로젝트에 적용했고, 각각 다른 선택을 했습니다. CI 검증까지 확장한 프로젝트, 45파일 일괄 적용한 프로젝트, 그리고 27개 마커로 멈춘 프로젝트. 스케일이 어떻게 도구화 결정을 가르는지에 관한 회고입니다.

양방향 링크 시스템 회고: 프로젝트 스케일에 따른 도구화 결정

1. 이 글의 위치

이 글은 양방향 링크 시스템 시리즈의 네 번째 글이자 마지막 회고입니다.

  1. AI 코딩 어시스턴트 시대의 문서화: 양방향 링크 시스템 구축기@handbook / @code 도입
  2. 양방향 링크 시스템을 테스트 커버리지에 확장하기: @tested / @covers 마커 패턴 — ghost-mcp에서 CI 자동 검증 스크립트까지 확장
  3. 소스와 테스트를 grep 한 줄로 연결하기: @tested / @covers 양방향 마커 시스템 — Obsidian 플러그인에서 45파일 규모로 실전 적용
  4. 양방향 링크 시스템 회고: 프로젝트 스케일에 따른 도구화 결정 ← 현재 글

앞의 세 글을 읽은 독자라면 "이 시스템은 계속 확장되어 왔다" 는 인상을 받으실 겁니다. 마커 2종(@handbook / @code) → 4종(+@tested / @covers) → CI 자동 검증 → grep 툴킷. 시리즈 첫 글에는 이런 "다음 단계"를 적어두었습니다.

- [ ] IDE 플러그인 개발 (VSCode)
- [ ] CI 링크 검증 파이프라인 구축
- [ ] 마커 자동완성 지원
- [ ] 문서 생성 자동화

그리고 시간이 지난 지금, 세 개 프로젝트에 같은 시스템을 적용했지만 각 프로젝트의 결정이 달랐습니다.

  • ghost-mcp: CI 검증 파이프라인 구축 ✅ (2편 참고)
  • Obsidian 플러그인 (Auto Note Importer): 45파일 일괄 적용 + @vitest/coverage-v8 통합 ✅ (3편 참고)
  • my-webapp: 27개 마커만, 자동화 전부 생략 ⚪ (이 글)

이 글은 세 번째 프로젝트에서의 결정을 기록합니다. "왜 똑같은 시스템인데 여기서는 확장하지 않았는가", 그리고 그 결정이 스케일의 함수였다는 관찰입니다.


2. 세 프로젝트, 세 결정

2.1 ghost-mcp — CI 검증까지 확장

ghost-mcp는 Ghost 블로그를 MCP(Model Context Protocol) 서버로 감싸는 도구입니다. 규모가 커질수록 "어느 테스트가 어느 도구를 검증하는가" 의 추적이 복잡해졌고, 수동 grep으로는 drift가 자주 생겼습니다. 그래서 2편에서 공개한 CI validation 스크립트를 GitHub Actions에 묶었습니다. PR이 들어올 때마다 @tested@covers 양방향성이 자동 검증됩니다.

2.2 Obsidian 플러그인 — 45파일 일괄 적용

Auto Note Importer는 Airtable을 Obsidian vault로 동기화하는 플러그인입니다. 21개 소스 + 22개 테스트 파일 규모에서 "이 소스의 테스트는 뭐지?" 검색이 일상화되었고, 3편에 기록한 45파일 일괄 마커 적용 PR + @vitest/coverage-v8 dev dependency 추가로 결론이 났습니다. CI 검증은 아직 수동 grep이지만, 45파일 규모의 선언적 커버리지 맵이 있으면 리팩토링 영향 범위를 빠르게 파악할 수 있습니다.

2.3 my-webapp — 아무 도구도 추가 안 함

그리고 세 번째 프로젝트, my-webapp입니다. 인코그니토 모드용 크롬 확장으로, 구조는 이렇게 생겼습니다.

  • extension/ — Vanilla JS Chrome 확장 (Manifest V3)
  • server/src/ — TypeScript 기반 Cloudflare Workers 라이선스 서버

두 하위 시스템이 있고, 이중 스택(JS + TS)을 하나의 ENGINEERING_HANDBOOK이 덮습니다. 1편의 마커 시스템을 도입했고, 현재 마커 개수를 세어 보면:

$ grep -rn "@handbook" extension/ server/src/ | wc -l
27

27개. CI 검증 스크립트? 없습니다. IDE 플러그인? 없습니다. @tested / @covers 확장? 없습니다. 자동화 도구? 없습니다. 아무것도 추가하지 않았습니다.

그런데 시스템은 지금까지 멀쩡히 작동 중입니다. AI 코딩 어시스턴트(주로 Claude Code)와의 협업에서 문서↔코드 연결이 자연스럽게 일어나고, 그 사이 drift 버그는 한 번도 발견되지 않았습니다.


3. 왜 my-webapp에선 아무것도 필요 없었나

3.1 27개는 "사람 머리" 스케일

27개는 한 화면에 들어오는 수입니다. grep -rn "@handbook" 결과가 터미널 한 페이지에 전부 표시됩니다. 사람이 전체 구조를 한 번에 scan 가능하고, 시각적으로 "이 파일군은 §2, 이 파일군은 §4, 이 파일군은 §5"라는 군집이 보입니다.

Obsidian 플러그인(45 파일)은 이 한계를 넘어 grep 출력이 한 화면을 벗어나기 시작하는 지점이었습니다. ghost-mcp는 MCP 도구가 늘어나면서 1:N 관계가 복잡해졌습니다. my-webapp은 아직 그 임계점에 없습니다.

3.2 이중 스택에서 한 섹션 체계 공유

my-webapp이 특이한 점은 두 개의 독립된 코드베이스가 한 handbook을 공유한다는 것입니다.

  • extension/ — Vanilla JS, Chrome 확장 MV3
  • server/src/ — TypeScript, Hono + Cloudflare Workers

서로 다른 언어, 다른 런타임, 다른 빌드 파이프라인입니다. 그런데 @handbook 마커가 가리키는 docs/ENGINEERING_HANDBOOK.md하나입니다. 섹션 번호가 영역 좌표계 역할을 합니다.

## 1. 아키텍처
  1.2 Incognito Split Mode       ← extension/background.js가 참조
  1.3 메시지 통신                 ← extension/shared/api.js가 참조
## 2. 모듈 패턴 (확장 측)
## 3. 스토리지 (확장 측)
## 4. 보안 (확장 측)
## 5. 서버 패턴 (서버 측)
  5.1 Hono + Workers 구조         ← server/src/index.ts가 참조
  5.2 Zod + D1 쿼리               ← server/src/routes/api.ts가 참조
  5.3 HMAC 검증                   ← server/src/routes/webhook.ts가 참조
## 6. i18n (확장 측)
## 7. UI 컴포넌트 (확장 측)

§1~§4, §6~§7은 확장. §5는 서버. Cross-stack 질문("라이선스 검증 플로우는?")이 한 문서 안에서 답변됩니다. 섹션 번호로 물리적 경계를 나누고 개념적 연속성을 유지하는 구조.

이 구조에서 CI 검증 같은 자동화를 도입하면 언어별 toolchain을 두 벌 세팅해야 합니다. Vanilla JS는 eslint/node script, TypeScript는 tsc + vitest. 각각 CI job이 필요합니다. 27개 마커 관리하려고 이 복잡도를 더할 이유가 없었습니다.

3.3 JSDoc 포기 — Vanilla JS에는 한 줄 주석

1편의 예시는 JSDoc 블록 안에 마커를 쓰는 것이었습니다. my-webapp에서는 이 스타일을 포기했습니다. 대신 파일 최상단에 한 줄 주석을 씁니다.

// extension/shared/crypto.js
// @handbook 4.1-pbkdf2-hashing
// @handbook 4.4-decoy-space-encryption
/**
 * Crypto — AES-GCM encryption/decryption + PBKDF2 key derivation
 */
const Crypto = (function () {
  'use strict';
  // ...
})();

이유는 세 가지:

  1. Vanilla JS에 JSDoc은 과함. TypeScript 프로젝트라면 JSDoc이 타입 추론에도 쓰이지만, Vanilla JS에선 마커를 위해 JSDoc 컨벤션을 강요할 이유가 없습니다.
  2. 파일 단위 마커가 자연스러움. 개별 함수마다 다는 것보다 "이 파일은 이 섹션을 구현한다"가 실제 작업 경험과 더 잘 맞습니다.
  3. TypeScript 파일에도 동일 스타일 적용. server/src/routes/webhook.ts 최상단에도 // @handbook 5.3-hmac-verification 한 줄. JS와 TS가 같은 스타일을 공유해서 grep 패턴이 균일합니다.

3.4 검증의 주체가 바뀌었다 — CI가 아니라 AI

CI 검증을 만들지 않은 가장 깊은 이유는 검증의 주체가 바뀌었기 때문입니다. 1편에서 상상한 검증은 이런 것이었습니다.

[Git push] → [CI 파이프라인] → [링크 유효성 검사] → [깨진 링크 있으면 PR 블록]

그런데 실제로 운영하면서 drift 버그가 잡힌 경로는 이런 식이었습니다.

[코드 수정 요청] → [Claude가 파일 읽음]
                    ↓
                 "이 파일에 @handbook 4.4 마커가 있네요. 섹션을 먼저 확인할게요"
                    ↓
                 (섹션 읽고 모순 발견)
                    ↓
                 "§4.4에 적힌 티어 분포와 코드의 실제 분포가 다릅니다. 어느 쪽이 정답인가요?"

AI가 검증을 주도합니다. CI가 아니라 수정 순간에. 그리고 이 검증은 "마커가 문법적으로 유효한가"를 넘어 "마커가 가리키는 섹션의 내용이 실제 코드와 일치하는가" 까지 체크합니다. 의미 수준의 검증입니다.

CI로 이걸 구현하려면 LLM을 CI 안에 넣어야 합니다. 그런데 이미 작업 순간에 LLM이 옆에 있습니다. 같은 검증을 두 번 할 이유가 없습니다.

이 관찰은 my-webapp에만 적용되는 건 아닙니다. ghost-mcp와 Obsidian 플러그인에서도 AI 어시스턴트가 같은 역할을 합니다. 다만 그쪽 프로젝트는 스케일이 임계점을 넘어서 CI 자동 검증이 추가 방어선으로 가치가 있었습니다. my-webapp은 그 지점을 아직 안 넘었습니다.

3.5 CLAUDE.md의 라우팅 테이블

1편에는 없던 새 아이디어 하나 — 프로젝트 루트의 CLAUDE.md"개념 → 섹션 → 파일" 3열 매핑표를 넣었습니다.

## Engineering Handbook

| 찾는 것 | HANDBOOK 섹션 |
|---------|--------------|
| Incognito split | 1.2 |
| 모듈 패턴 (IIFE/전역) | 2.1, 2.2 |
| Storage 라우팅 | 3.1 |
| 보안 (PIN, 검증) | 4.1-4.3 |
| 디코이 스페이스 암호화/패딩 | 4.4 |
| 서버 API/Webhook | 5 |
| i18n | 6 |
| UI 컴포넌트 | 7 |

| 패턴 | 참고 파일 |
|------|----------|
| IIFE 모듈 | `extension/shared/confirm-modal.js` |
| 페이지 스크립트 | `extension/popup/popup.js` |
| Hono API 라우트 | `server/src/routes/api.ts` |
| Webhook 핸들러 | `server/src/routes/webhook.ts` |

이 표가 AI에게 "개념 → 섹션 → 레퍼런스 파일" 의 지름길을 제공합니다. grep으로 "incognito"를 찾는 것보다 빠르고 정확합니다. 이 표를 업데이트한 건 두 번뿐입니다. 유지보수 비용이 거의 0입니다.

CI 검증 같은 코드 자산보다 CLAUDE.md 같은 컨벤션 자산이 이 규모의 프로젝트에는 더 효율적입니다.


4. 스케일에 따른 결정 매트릭스

세 프로젝트의 선택을 표로 정리하면 패턴이 보입니다.

항목 my-webapp Obsidian 플러그인 ghost-mcp
마커 수 27개 66개 (38 @tested + 28 @covers) 계속 증가 중
스택 JS + TS 이중 TS 단일 TS 단일
테스트 복잡도 E2E 없음, 수동 QA 위주 Unit + View + E2E(CDP) Unit + 통합 + MCP 프로토콜
@tested/@covers 확장 ❌ 미도입 ✅ 도입 ✅ 도입
CI 자동 검증 ⚠️ 수동 grep ✅ GitHub Actions
@vitest/coverage-v8
IDE 플러그인
CLAUDE.md 라우팅 테이블 ✅ (고유)

그리고 이 표에서 일반화 가능한 임계점이 관찰됩니다.

4.1 임계점 1 — "grep 한 화면"

30~40개 마커 이하면 grep 결과가 한 화면에 들어옵니다. 이 범위에서는 사람이 전체 상태를 scan 가능하고, 도구화의 이득이 유지비용보다 작습니다. 이 임계점을 넘어가면 (Obsidian 플러그인 같은 60+개) find, comm, awk 조합 같은 쿼리 패턴이 필요해집니다.

4.2 임계점 2 — "E2E 테스트의 존재"

E2E 테스트가 있는 프로젝트(Obsidian 플러그인의 CDP 기반 E2E, ghost-mcp의 MCP 프로토콜 통합)는 한 테스트 파일이 여러 소스 파일을 커버하는 N:M 관계가 생깁니다. 이 지점에서 @tested/@covers가 큰 가치를 발휘합니다. E2E가 없고 단위 테스트 + 수동 QA로 충분한 프로젝트는 마커 확장이 불필요합니다.

4.3 임계점 3 — "언어 단일성"

TypeScript 단일 스택이면 CI 검증 스크립트 하나가 전체를 커버합니다. my-webapp처럼 JS + TS 이중 스택이면 하나의 검증 도구로 양쪽을 다루기 어렵습니다. 언어별로 toolchain이 달라서 CI job이 중복됩니다. 이 비용이 27개 마커 관리 이득을 넘어섭니다.

4.4 임계점 4 — "1인 vs 다인"

1인 프로젝트는 정체성 유지가 쉽습니다. 마커 스타일, 섹션 번호 안정성 같은 것이 자연스럽게 지켜집니다. 컨트리뷰터가 여러 명이면 일관성을 CI로 강제해야 drift를 막을 수 있습니다. my-webapp(1인) vs Obsidian 플러그인(외부 PR 받음)의 차이입니다.


5. YAGNI의 AI 어시스턴트 시대 버전

"You Aren't Gonna Need It"(YAGNI)는 과잉 엔지니어링을 경계하는 고전 원칙입니다. 이 세 프로젝트의 경험은 YAGNI의 새 버전을 시사합니다.

AI 코딩 어시스턴트 시대에는, 과거에 자동화가 필요했던 많은 것이 "수정 순간의 AI 판단"으로 대체됩니다. 도구를 만들기 전에, AI가 이미 그 역할을 커버하는지 먼저 확인하세요. 그리고 프로젝트 스케일의 임계점을 측정하세요.

이 원칙은 모든 도구를 거부하자는 뜻이 아닙니다. 수치가 임계점을 넘거나, AI가 놓치는 고유한 영역이 있을 때만 도구를 만들자는 뜻입니다. ghost-mcp와 Obsidian 플러그인은 임계점을 넘어 자동화가 정당화됐고, my-webapp은 아직입니다.

5.1 "도구를 만들지 말아야 하는" 신호

다음 질문에 하나라도 "예" 면 도구 만들기 연기:

  • 다뤄야 할 항목 수가 40개 미만인가?
  • 매 변경마다 AI 코딩 어시스턴트가 관여하는가?
  • 검증이 문법 수준이 아니라 의미 수준에서 필요한가?
  • 도구 없이도 grep + 사람 스캔으로 감당 가능한가?
  • 도구 자체의 유지보수 부담이 해결하려는 문제보다 커질 가능성이 있는가?
  • 1인 프로젝트인가?

5.2 "그럼 도구를 언제 만드나"

반대로, 다음 신호가 있으면 도구를 실제로 만들어야 합니다.

  • 항목 수가 수십 개를 넘어 grep 출력이 읽히지 않음
  • E2E 테스트 같은 N:M 관계가 존재함
  • 같은 실수가 3회 이상 반복됨
  • 컨트리뷰터가 여러 명
  • AI가 반복적으로 놓치는 고유한 패턴이 드러남

조건이 만족되면 작게 시작하세요. 완벽한 도구보다 150줄짜리 TypeScript 스크립트(2편 참고)가 낫습니다.


6. FAQ

Q1. 세 프로젝트 중 가장 적절한 스케일은 어느 것인가요?

A. "가장 적절한" 스케일은 없습니다. 각 프로젝트의 요구가 다르기 때문에 선택이 달라진 것뿐입니다. my-webapp은 사이드 프로젝트라 스케일이 작고, ghost-mcp는 오픈소스로 확장 중이라 자동화가 필요하고, Obsidian 플러그인은 데이터 동기화라는 복잡한 도메인 때문에 E2E가 필수입니다. 똑같은 원칙을 각 맥락에 맞게 적용한 것입니다.

Q2. 미래에 my-webapp이 커지면 뒤늦게 CI 검증을 추가해야 하나요?

A. 그때가 되면 추가하면 됩니다. 도구는 나중에 만들어도 늦지 않습니다. 마커는 이미 코드에 박혀 있으므로, CI 스크립트만 추가하면 됩니다. "지금 필요 없는데 미래를 위해 미리 만든다"는 정확히 YAGNI가 경계하는 사고방식입니다.

Q3. 1편에서 "다음 단계"로 적은 항목이 전부 안 이뤄졌는데, 계획이 잘못된 건가요?

A. 계획이 아니라 가능성 목록이었다는 게 정확한 표현입니다. 그 시점에 "나중에 할 수도 있는 것들"을 열거했고, 실제로 필요해진 프로젝트에서는 구현됐습니다(ghost-mcp의 CI 검증). 필요해지지 않은 프로젝트에서는 안 만들어졌습니다(my-webapp의 IDE 플러그인). "계획을 안 지킨" 게 아니라 "계획이 조건부였다" 가 맞는 해석입니다.

Q4. AI 코딩 어시스턴트 없이도 이 시스템이 작동할까요?

A. 부분적으로. 마커 자체는 독립적으로 가치가 있습니다 — 인간 개발자가 grep으로 관련 코드/문서를 찾는 데 쓸 수 있습니다. 하지만 drift 방지와 의미 수준 검증은 AI의 기여가 큽니다. AI 없는 팀이라면 CI 검증 같은 정적 도구가 더 일찍 필요해집니다 — 즉 임계점이 낮아집니다.

Q5. 섹션 번호를 영구 불변으로 유지하는 게 지저분해지지 않나요?

A. 지저분해집니다. 인정합니다. 처음 설계한 구조에 "뒤에 붙이기"만 하면 논리적으로 §2 뒤에 와야 할 내용이 §7에 들어가는 상황이 생깁니다. 그런데 이걸 감수합니다. 깔끔한 구조 < 안정된 참조입니다. 독자가 한 번 섞인 순서에 익숙해지면 문제가 되지 않고, 참조 안정성은 지속적 가치입니다. 27개 마커를 리넘버링할 유혹이 있었지만 비용을 계산해보고 포기했습니다.

Q6. 이 회고를 다른 프로젝트에도 적용할 수 있나요?

A. "도구를 미리 만들지 말고, 임계점에 도달할 때까지 단순함을 유지하라"는 원칙은 일반화 가능합니다. 하지만 구체적인 숫자(27개, "한 화면")는 프로젝트마다 다릅니다. 본인 프로젝트의 성장 속도를 측정하고 그에 맞게 판단하세요. 각 프로젝트마다 고유한 임계점이 있습니다.


7. 참고 자료


8. 마무리

1편을 쓰고 가장 두려웠던 건 "자동화 없이는 망가질 것" 이라는 생각이었습니다. 시간이 지난 지금, 세 개 프로젝트에서 다르게 답이 나왔습니다. 두 곳은 자동화가 필요했고 만들었습니다. 한 곳은 필요 없었고 만들지 않았습니다. 세 선택 모두 맞았습니다.

  • 실패해도 비용이 낮은 시스템은 자동화가 필요 없습니다
  • 비용이 올라가거나 N:M 복잡도가 생기면 자동화를 해야 합니다
  • 임계점은 숫자가 아니라 상황의 함수입니다

이 원칙이 지금까지의 핵심 교훈입니다. 다음에 "나중에 커지면 어쩌지?" 라는 걱정이 들 때, 먼저 해야 할 일은 현재 스케일의 측정실패 비용의 평가입니다. 그 둘이 모두 낮으면, 도구는 나중에 만들어도 됩니다. 그리고 그때도 만들 필요가 없을 수 있습니다 — 다른 프로젝트가 이미 만든 도구를 그대로 가져오면 되니까요.

시리즈 목차:

  1. AI 코딩 어시스턴트 시대의 문서화: 양방향 링크 시스템 구축기
  2. 양방향 링크 시스템을 테스트 커버리지에 확장하기: @tested / @covers 마커 패턴
  3. 소스와 테스트를 grep 한 줄로 연결하기: @tested / @covers 양방향 마커 시스템
  4. 양방향 링크 시스템 회고: 프로젝트 스케일에 따른 도구화 결정 ← 현재 글