낯선 레포에서 헤매지 않는 법
강민석
2026년 3월 4일8분 분량
배경
김캐디의 예약 서버는 Django(Python)로 작성된 API 서버와 NestJS(TypeScript)로 작성된 Booking 서버, 두 개의 코드베이스로 나뉘어 있습니다. 앱 개발자인 제가 서버 로직을 파악해야 할 때가 종종 있는데, 이게 쉽지 않습니다. 함수명도 모르고, 어떤 파일에 있는지도 모르고, 심지어 검색할 키워드조차 감이 안 잡힐 때가 많죠.
“예약 확정 로직이 어디 있지?”라는 질문에 grep을 돌려봐야 수백 개의 결과가 쏟아질 뿐입니다. 그렇다고 서버 개발자를 매번 붙잡고 물어볼 수도 없는 노릇이고요.
AI 코딩 에이전트가 이런 상황을 도와줄 수 있지만, Claude Code의 기본 검색은 grep 기반이라 한계가 분명합니다. 정확한 키워드를 알아야만 원하는 코드를 찾을 수 있고, 키워드를 모른 채 탐색하면 에이전트가 이 파일 저 파일을 헤매면서 시간과 토큰을 대량 소비합니다. 무엇보다, 찾고자 하는 정보가 누락될 가능성이 높습니다.
만약 “예약이 확정되는 로직”이라는 의미 자체로 코드를 검색할 수 있다면 어떨까요? 이것이 바로 Semantic Search가 해결하는 문제입니다.
Semantic Search란?
Semantic Search(의미 기반 검색)는 단순 문자열 매칭이 아닌, 텍스트의 의미를 이해하여 검색하는 방식입니다.
동작 원리는 다음과 같습니다:
인덱싱 (최초 1회):
1. 코드베이스의 모든 파일을 AST(구문 트리) 기반으로 함수, 클래스 등 논리적 단위로 나눈다
2. 각 청크를 임베딩 모델을 통해 벡터(고차원 숫자 배열)로 변환한다
3. 변환된 벡터와 원본 텍스트를 벡터 데이터베이스에 함께 저장한다
검색 (매 질문마다):
4. 질문도 같은 방식으로 벡터로 변환한다
5. 하이브리드 검색을 수행한다 — 벡터 유사도(의미 매칭)와 BM25(키워드 매칭)를 동시에 실행하고, 두 결과를 결합하여 최종 순위를 매긴다
“confirm”이라는 정확한 단어를 몰라도, “예약이 확정되는 곳”이라고 질문하면 관련 코드를 찾아줍니다. 한국어로 질문해도 영어 코드에서 답을 찾을 수 있죠.
grep 검색 vs Semantic Search
| grep (문자열 매칭) | Semantic Search (하이브리드 매칭) | |
|---|---|---|
| 검색 방식 | 정확한 키워드 필요 | 자연어로 의도를 설명 |
| 매칭 방식 | 문자열 일치만 | 벡터 유사도 + BM25 키워드 결합 |
| 한국어 질문 | 불가능 | 가능 |
| 검색 범위 | 키워드가 포함된 라인만 | 의미적으로 관련된 코드 전체 |
| 누락 가능성 | 높음 (키워드 불일치 시) | 낮음 (의미 + 키워드 이중 매칭) |
왜 Claude Code에 필요한가?
Cursor 같은 AI 에디터를 써보신 분이라면 코드베이스 인덱싱 기능이 익숙할 것입니다. 프로젝트를 열면 자동으로 코드를 인덱싱하고, 이후 AI가 코드베이스 전체를 맥락으로 활용해 정확한 답변을 해줍니다.
반면 Claude Code는 CLI 기반의 강력한 에이전트이지만, 코드 검색은 grep과 파일 탐색에 의존합니다. 구체적으로 살펴보면, Claude Code가 코드를 탐색하는 과정은 다음과 같습니다:
- Grep/Glob으로 키워드 검색 — 사용자의 질문에서 키워드를 추출해 문자열 검색을 수행
- 파일 열기(Read) — 검색 결과에서 관련 있어 보이는 파일을 하나씩 열어 내용을 확인
- 반복 탐색 — 파일 내용에서 새로운 단서를 발견하면 다시 grep → Read를 반복
- Explore 에이전트 활용 — 복잡한 탐색의 경우 서브 에이전트를 띄워 위 과정을 위임
이 방식은 키워드를 정확히 알 때는 빠르지만, “이런 기능이 어디에 구현되어 있지?”라는 탐색형 질문에는 비효율적입니다. 에이전트가 수십 번의 grep과 파일 읽기를 반복하면서 토큰을 소모하고, 그럼에도 관련 코드를 놓칠 수 있습니다.
다행히 Claude Code는 MCP(Model Context Protocol)를 지원하기에, Semantic Search 기능을 직접 붙일 수 있습니다. 설정 한 번이면 됩니다.
환경 설정
전체 구성
- MCP 서버: Claude Context — 코드를 임베딩하고 Semantic Search를 수행하는 MCP 서버
- 벡터 데이터베이스: Milvus — 코드 임베딩을 저장하고 유사도 검색을 수행 (Docker로 로컬 실행)
- 임베딩 모델:
gemini-embedding-001— Google의 임베딩 모델 (API 키 필요, 무료 티어 사용 가능)
Ollama를 사용하면 API 비용 없이 완전히 로컬에서 동작하는 임베딩도 가능합니다. nomic-embed-text v1.5 모델이 성능과 속도 면에서 균형이 좋습니다.bashollama pull nomic-embed-text:v1.5
1단계: Milvus 실행
Docker Desktop이 설치되어 있다면, Milvus를 로컬에서 간단히 띄울 수 있습니다.
2단계: MCP 설정
프로젝트 루트의 .mcp.json에 다음 설정을 추가합니다.
{
"mcpServers": {
"claude-context": {
"type": "stdio",
"command": "npx",
"args": ["@zilliz/claude-context-mcp@0.1.3"],
"env": {
"EMBEDDING_PROVIDER": "Gemini",
"EMBEDDING_MODEL": "gemini-embedding-001",
"GEMINI_API_KEY": "...",
"MILVUS_ADDRESS": "http://localhost:19530"
}
}
}
}설정이 완료되면 Claude Code에서 claude-context MCP를 사용할 수 있게 됩니다.
3단계: 인덱싱
Claude Code에서 “이 코드베이스를 인덱싱해줘”라고 요청하면, claude-context가 코드를 함수·클래스 단위의 청크로 나누고, 임베딩 벡터와 원본 텍스트를 함께 Milvus에 저장합니다.
인덱싱 최적화: 불필요한 파일 제외
대규모 프로젝트에서는 인덱싱 대상을 잘 선별하는 것이 중요합니다. 예를 들어, Apple의 Swift 레포지토리를 인덱싱한다고 해봅시다.
전체 약 30,000개 파일 중 test/와 validation-test/ 디렉토리만 해도 24,653개(82%)를 차지합니다. 이런 파일들은 실제 구현 로직이 아니므로 제외하는 것이 합리적입니다.
ignorePatterns 옵션으로 제외 패턴을 지정하면 핵심 소스 코드만 인덱싱됩니다.
결과적으로 25,770개 파일이 3,775개로 약 85% 감소했고, 107,867개의 청크로 인덱싱이 완료되었습니다.
실전 비교: 같은 질문, 다른 결과
이론만으로는 와닿지 않으니, 실제로 김캐디 서버 코드베이스에서 같은 질문을 두 가지 방식으로 해보겠습니다.
질문: “예약이 실제로 확정 상태로 바뀌는 로직은 어디인가?”
이 질문이 까다로운 이유가 있습니다. 김캐디 서버에서 “확정”은 단순한 하나의 상태가 아닙니다. CONFIRMED는 “접수됨”을 의미하고, 실제 최종 확정은 SUCCESS 상태입니다. 이 도메인 지식 없이 코드를 탐색하면 쉽게 오해가 생길 수 있는 구조죠.
방식 1: Semantic Search (claude-context)
❯ 예약이 실제로 확정 상태로 바뀌는 로직은 어디인가? claude-context를 사용하도록. Claude가 search_code를 통해 의미 기반으로 관련 코드를 찾아냈습니다. 4번의 검색만으로 Python(API 서버)과 TypeScript(Booking 서버) 양쪽 모두에서 예약 확정 로직을 정확히 짚어냈고, 전체 흐름까지 정리해주었습니다.
결과 요약:
[사용자 예약 요청]
├─ 즉시확정 매장 → API 서버에서 바로 state="success"
└─ 일반 예약 → state="confirmed" (접수 상태)
→ SQS Queue로 Booking 서버에 전달
→ 사장님에게 알림 → 확정/실패 응답
→ updateBookingState()로 state="success"/"failed" 변경
→ API 서버 콜백으로 DB 반영- 소요 시간: 40초
- 토큰 사용량: 45k
- 도구 호출: 4회
- 즉시확정, 사장님 확정, 강제 처리 등 모든 경로를 커버
confirmed는 “접수”이고 실제 확정은success라는 점까지 정확히 구분
방식 2: 기본 검색 (grep 기반)
❯ 예약이 실제로 확정 상태로 바뀌는 로직은 어디인가?Claude가 Explore 에이전트를 띄워 코드베이스를 탐색했습니다. 서브 에이전트가 51번의 도구 호출로 파일을 하나하나 열어보는 과정을 거쳤습니다.
결과 자체는 나쁘지 않았지만 두 가지 문제가 있었습니다:
CONFIRMED(접수)와SUCCESS(확정)를 혼동하여, “예약 확정” 로직이라고 하면서 실제로는 “접수” 로직을 주로 설명- Django 서버 쪽 로직이 누락되어 TypeScript Booking 서버 중심의 불완전한 답변
- 소요 시간: 3분 18초
- 토큰 사용량: 98.8k
- 도구 호출: 51회
서버 개발자의 피드백
두 결과를 저희팀의 서버 개발자 신두평님에게 보여주고 검증을 요청했습니다.
“예약 확정이 success가 맞는건데 (방식 2는) confirmed로 오인했네요. confirmed는 접수입니다.
접수에 대한 내용만 있고, 장고 서버 내용이 없어요. 틀린 내용은 아니지만, 일부만 있는 것 같습니다.”
방식 1(Semantic Search)의 결과에 대해서는 별다른 지적 없이 핵심 로직을 정확히 파악했다는 피드백을 받았습니다.
비교 정리
| Semantic Search (claude-context) | 기본 검색 (grep) | |
|---|---|---|
| 소요 시간 | 40초 | 3분 18초 (5배) |
| 토큰 사용량 | 45k | 98.8k (2.2배) |
| 도구 호출 수 | 4회 | 51회 (12.75배) |
| 정확도 | 핵심 로직 정확히 파악 | 일부 개념 혼동, 누락 존재 |
| 범위 | Python + TypeScript 양쪽 커버 | TypeScript 중심, Django 누락 |
시간은 5배, 토큰은 2배 이상 절약하면서 오히려 더 정확한 결과를 얻었습니다.
한 가지 눈에 띄는 점이 있습니다. 방식 1에서 "claude-context를 사용하도록"이라고 직접 지시해야 했다는 것입니다. MCP 서버를 설치했다고 Claude가 알아서 활용하지는 않습니다. 이 문제를 포함해, 실제 사용에서 마주치는 불편함들을 플러그인으로 해결한 이야기를 이어가겠습니다.
활용 팁 — Semantic Search 플러그인으로 자동화하기
앞서 보여드린 비교에서 Semantic Search의 효과는 확인했지만, 실제로 쓰다 보면 매번 “claude-context를 사용해”라고 지시하거나, 검색 전략을 수동으로 결정해야 하는 번거로움이 있습니다. 이 문제를 해결하기 위해 Semantic Search 플러그인을 만들었습니다.
Claude Code는 플러그인 시스템을 통해 기능을 확장할 수 있습니다. 플러그인 안에 스킬(Skill)을 정의하면, Claude가 특정 상황에서 자동으로 해당 스킬의 절차를 따르게 됩니다. 예를 들어 “코드 탐색 시 이런 순서로 검색하라”는 규칙을 스킬로 만들어두면, 매번 수동으로 지시하지 않아도 됩니다.
플러그인이 해결하는 문제
claude-context MCP 서버를 설치했다고 끝이 아닙니다. 실제로 써보면 이런 문제들을 마주치게 됩니다:
- Claude가 Semantic Search 대신 익숙한 grep부터 사용하려 함
- 시맨틱 검색 결과에서 발견한 식별자를 grep으로 추적해야 하는데, 매번 수동으로 지시해야 함
- 코드 수정 시 영향 범위 분석을 빠뜨리기 쉬움
- 멀티 코드베이스 환경에서 한쪽만 검색하고 넘어가는 경우가 많음
플러그인은 이런 판단과 절차를 스킬로 정의해서, Claude가 자동으로 따르게 합니다.
검색 도구 자동 조합
플러그인의 핵심은 Semantic Search와 Grep을 질문 유형에 따라 자동으로 조합하는 것입니다.
| 질문 유형 | 먼저 사용 | 이후 보완 |
|---|---|---|
| “예약 확정 로직이 어디 있지?” (개념적 질문) | Semantic Search | 발견된 식별자를 Grep으로 추적 |
confirmBooking 함수의 호출처 (정확한 이름) | Grep | Semantic Search로 간접 호출 탐색 |
| “인증 미들웨어에서 토큰 검증” (개념 + 키워드) | 둘 다 병렬 실행 | 결과를 교차 검증 |
예를 들어, “예약 확정 로직”을 검색하면 플러그인은 이렇게 동작합니다:
- Semantic Search로 관련 코드를 찾아
BookingState.SUCCESS,forceConfirmBooking같은 식별자를 발견 - 발견된 식별자를 Grep으로 추적해 모든 사용처를 파악
- 멀티 코드베이스 환경이라면 모든 코드베이스를 병렬로 검색
CLAUDE.md에 검색 지시를 넣어두는 것과 비교하면, 플러그인은 “언제 어떤 도구를 어떤 순서로 쓸지”까지 Claude에게 가르치는 셈입니다.
코드 수정 시 안전망
단순 탐색뿐 아니라, 코드를 수정할 때의 워크플로우도 플러그인이 가이드합니다.
수정 전:
1. Semantic Search로 관련 로직 전체를 파악
2. Grep으로 수정 대상의 모든 참조를 카운트
3. Semantic Search로 기존 컨벤션을 확인
수정 후:
4. Grep으로 변경된 식별자를 다시 검색해, 수정 전 카운트와 비교
5. 누락된 곳이 없는지 자동으로 검증
“enum 값을 하나 추가했는데 switch문 하나를 빠뜨렸다” 같은 실수를 구조적으로 방지합니다.
검색 쿼리 작성 팁
플러그인이 전략을 자동화해주지만, Semantic Search의 성능은 여전히 쿼리 품질에 달려 있습니다.
좋은 쿼리 예시:
- "예약 확정 상태 변경 success booking state update" — 한국어 의도 + 영어 키워드를 섞으면 검색 범위가 넓어집니다
- "사용자 인증 토큰 검증 middleware authentication" — 도메인 개념과 기술 용어를 함께 사용
피해야 할 쿼리:
- "confirm" — 너무 짧으면 의미 벡터가 모호해져서 grep보다 나을 게 없습니다
- "버그 수정" — 너무 일반적인 표현은 관련 없는 코드까지 반환됩니다
limit 조절하기
search_code의 limit 파라미터로 반환 결과 수를 조절할 수 있습니다.
- 탐색 초기:
limit: 15~20으로 넓게 검색해서 전체 그림 파악 - 특정 로직 확인:
limit: 5~10으로 좁혀서 핵심 코드에 집중
임베딩 모델 선택지
이 글에서는 gemini-embedding-001을 사용했지만, claude-context는 여러 임베딩 모델을 지원합니다.
| 모델 | 방식 | 비용 | 특징 |
|---|---|---|---|
gemini-embedding-001 | API | 무료 티어 있음 | 빠르고 품질 좋음, 이 글에서 사용 |
nomic-embed-text:v1.5 | 로컬 (Ollama) | 무료 | 768차원, 코드 검색에 적합 |
text-embedding-3-small | API (OpenAI) | 유료 | claude-context에서 지원 |
voyage-code-3 | API (VoyageAI) | 유료 | 코드 특화 임베딩, 높은 정확도 |
개인적으로는 빠른 설정과 무료 사용이 가능한 gemini-embedding-001로 시작하는 것을 추천합니다.
결론
Semantic Search를 도입한 후 체감하는 가장 큰 변화는, 낯선 코드베이스 앞에서 느끼던 막막함이 사라졌다는 것입니다. 서버 로직이 궁금하면 한국어로 질문하고, 40초 안에 정확한 답변을 받을 수 있게 되었습니다.
물론 한계도 있습니다. 대규모 코드베이스일수록 초기 인덱싱에 시간이 걸리고, Milvus를 Docker로 실행하므로 로컬 리소스를 일정 부분 사용합니다. 다만 인덱싱은 한 번만 해두면 이후에는 증분 업데이트만 하면 됩니다.
앞으로의 가능성
현재는 로컬에서 벡터 데이터베이스를 운영하고 있지만, 이를 클라우드로 올리면 팀 단위로 활용할 수 있습니다. 한 명이 인덱싱해두면 팀 전체가 바로 사용 가능하고, 새로 합류한 팀원도 이미 구축된 벡터 데이터베이스에 연결하기만 하면 됩니다.
Claude Context는 Zilliz Cloud를 지원하므로 이를 활용하거나, 레포를 fork하여 다른 클라우드 벡터 데이터베이스로 교체할 수도 있습니다.
낯선 코드베이스 앞에서 막막함을 느끼고 있다면, Semantic Search를 한번 시도해 보세요. “이 코드 어디 있지?”라는 질문에 대한 답을 찾는 시간이 확연히 줄어들 것입니다.