DriftGuard: 코드가 조용히 문서를 망가뜨릴 때 이를 포착하는 방법
요약
코드 변경 시 문서의 코드 스니펫이 동기화되지 않는 문제를 해결하기 위한 도구인 DriftGuard를 소개합니다. 스냅샷과 체크 메커니즘을 통해 SDK 시그니처 변경과 문서 간의 불일치를 자동으로 탐지합니다.
핵심 포인트
- 스냅샷 기능을 통해 코드와 문서 스니펫의 기준점을 캡처하고 관리함
- PR 단계에서 ts-morph를 활용해 문서 내 코드 블록의 컴파일 가능 여부를 검증
- CI 워크플로우에 통합하여 코드와 문서 간의 드리프트 현상을 방지
- 도그푸딩을 통해 복잡한 타입 추론으로 인한 디프(diff) 문제를 식별함
만약 여러분이 SDK 업데이트를 배포하고 3주 뒤에 "README 예제가 작동하지 않습니다"라는 GitHub Issue를 받아본 적이 있다면, 이 문제를 공감하실 것입니다.
코드는 변경되었지만, 문서는 변경되지 않았습니다. 사용자들은 깨진 코드 스니펫(snippets)을 마주하게 됩니다. 여러분은 화가 난 메시지를 통해서야 이 사실을 알게 됩니다.
저는 Web3 SDK에서 이런 현상을 계속 목격했기 때문에 DriftGuard를 만들었습니다. 컨트랙트(contracts)는 업그레이드되고 TypeScript SDK는 진화하지만
-
Snapshot (스냅샷) —
driftguard snapshot을 한 번 실행합니다. 이는 컨트랙트(contracts), SDK export, 그리고 문서 스니펫(doc snippets)을 정규화된 JSON 파일로 캡처합니다. 이 파일을 커밋(commit)하세요. -
Check (체크) — 모든 PR(Pull Request)에서
driftguard check가 동일한 형태를 다시 계산하고 커밋된 스냅샷과 비교(diff)합니다. 차이점은 탐지 결과(findings)가 됩니다.
스냅샷은 승인 메커니즘 역할을 합니다. 만약 의도적으로 SDK 시그니처(signature)를 변경했다면,driftguard snapshot을 다시 실행하여 기준점(baseline)을 업데이트하면 됩니다. 그러면 다음 체크는 통과됩니다.
문서 스니펫(doc snippets)의 경우, 검증(validation) 단계가 매우 흥미롭습니다. README에 포함된 각 TypeScript 코드 블록은 경로 매핑된 ts-morph Project를 사용하여 현재 SDK를 대상으로 컴파일됩니다. 만약 SDK의 시그니처가 변경되면, 이전 형태를 호출하는 스니펫은 컴파일에 실패하며, 체크는 마크다운(markdown) 파일 내 해당 스니펫이 있는 줄에서 실패하게 됩니다.
Quick start (빠른 시작)
npm install --save-dev @driftguardjs/cli
npx driftguard init # 초기 설정 파일을 작성합니다 (프로젝트 내 요소를 자동 감지)
npx driftguard snapshot # 기준점(baseline)을 캡처합니다
git add .driftguard/snapshot.json driftguard.config.ts
git commit -m "add driftguard"
CI 워크플로우에 추가하기:
- uses: mahimathacker/driftguard@v0
with:
mode: check
이것이 설정의 전부입니다.
이를 만들며 배운 점
도그푸딩(Dogfooding, 자사 제품 직접 사용)은 그 무엇도 잡아내지 못하는 버그를 잡아냅니다. DriftGuard를 자신의 레포지토리(repo)에 처음 실행했을 때, 1,500자 길이의 읽을 수 없는 디프(diff)가 생성되었습니다. Zod 스키마(schemas)가 거대한 추론된 타입(inferred types)으로 확장되고 있었기 때문입니다. 코드는 정확했습니다. 하지만 출력 결과는 PR 코멘트에서 사용할 수 없는 수준이었습니다. 어떤 테스트로도 이를 잡아낼 수 없었을 것입니다. 실제 사용자가 느끼는 것을 체감하기 위해 도구를 자기 자신에게 직접 실행해 보아야 했습니다.
복잡한 라이브러리를 번들링(bundling)하는 것이 항상 성공하는 것은 아닙니다. GitHub Action을 단일 JavaScript 파일로 번들링하려고 두 시간을 허비했습니다. 이 도구가 사용하는 두 라이브러리(ts-morph와 jiti)는 어떤 번들러도 깔끔하게 패키징할 수 없는 런타임 동적 로딩(runtime dynamic loading)을 수행합니다. 결국 저는 포기하고, 액션(action)이 실행 시점에 npm에서 CLI를 설치하도록 만들었습니다. 더 단순한 접근 방식이 결국 옳은 방법이었음이 밝혀졌습니다.
AI가 워크플로우를 형성한 방식:
AI는 특히 두 가지 측면에서 매우 빨랐습니다:
후회로 남기 전의 아키텍처 결정(Architecture decisions) — 문서 스니펫(doc snippets)에 대해 줄 번호(line numbers, 위에 문단을 추가하면 깨짐) 대신 콘텐츠 해시 ID(content-hash IDs)를 사용하기로 한 것, 기본적으로 관대한 스냅샷(lenient-by-default snapshots, 사용자가 모든 깨진 예시를 먼저 수정하지 않고도 도구를 채택할 수 있도록 함), 디프(diff) 출력에서 추론된 타입(inferred types) 대신 구문 타입(syntactic types)을 사용하기로 한 것.
CI 출력 진단 — 테스트 PR(Pull Request)이 예상대로 주석(annotations)을 렌더링하지 않았을 때, AI는 즉시 "GitHub은 절대 경로가 아닌 저장소 상대 경로(repo-relative paths)를 요구합니다"라고 지적했습니다. 그 사실 하나만으로 문서를 읽는 데 드는 한 시간을 아낄 수 있었습니다.
AI가 결정하지 않은 것: 언제 다듬기를 멈추고 배포할지, 언제 번들링(bundling) 경로를 포기할지, 무엇이 v0.1.0 차단 요소(blocker)이고 무엇이 v0.1.x 개선 항목(polish item)인지에 대한 판단. 그러한 판단(judgment calls)은 저의 몫으로 남았습니다.
위치
npm: https://www.npmjs.com/package/@driftguardjs/cli
GitHub: https://github.com/mahimathacker/driftguard
DevRel Uni Cohort 7의 일환으로 제작되었습니다. 로드맵의 다음 단계는 다음과 같습니다: Solidity 스니펫 검증, 깨진 스니펫에 대한 AI 제안 수정, 그리고 코딩 에이전트(coding agents)가 변경을 수행하기 전에 "X를 변경하면 무엇이 깨질까?"라고 질의할 수 있도록 하는 MCP 서버 구축입니다.
여러분의 SDK에 직접 시도해 보신다면, 무엇이 가장 먼저 깨지는지 꼭 듣고 싶습니다. 이슈(issues)나 DM 모두 환영합니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기