
코드베이스를 grep으로 뒤지는 것에 지쳐 trelix를 만들었습니다
요약
기존 grep 방식의 한계를 극복하기 위해 개발된 오픈 소스 코드 인텔리전스 엔진 trelix를 소개합니다. Tree-sitter와 하이브리드 검색 방식을 사용하여 코드의 구조적 관계를 파악하고 자연어 질문에 답할 수 있습니다.
핵심 포인트
- grep은 코드의 구조적 관계(AST, 호출 엣지 등)를 파악하지 못하는 한계가 있음
- trelix는 Tree-sitter를 활용해 코드베이스를 인덱싱하고 심볼을 임베딩함
- BM25, 벡터 검색, 콜 그래프를 결합한 하이브리드 검색 방식 제공
- 오프라인 작동 및 API 키가 필요 없는 로컬 기반 엔진
새로운 팀에서 대부분의 시간을 인증(authentication)이 작동하는 위치를 찾기 위해 80,000줄의 코드를 grep으로 뒤지며 보냈습니다.
4시간. 세 명의 팀원이 방해함. 이해하지 못하는 파일들 사이에서 12번의 막다른 길. 코드는 괜찮았습니다. 잘 작성되었고, 잘 조직되어 있었으며, 적절하게 문서화되어 있었습니다. 문제는 도구였습니다. 저는 텍스트 검색 문제가 아닌 것을 이해하기 위해 grep을 사용하고 있었습니다. 코드에는 구조가 있습니다: 호출 엣지(call edges), 임포트 체인(import chains), 타입 계층 구조(type hierarchies), AST 관계(AST relationships). Grep은 이 모든 것을 무시합니다.
그날의 경험이 머릿속을 떠나지 않았습니다. 다른 팀, 다른 코드베이스, 다른 언어에서도 똑같은 패턴에 계속 부딪혔습니다. 새로운 프로젝트에 합류하거나 6개월 만에 프로젝트로 돌아올 때마다, 처음 며칠은 고고학 작업과 같았습니다. 수동으로 호출을 추적하고, 쿼리할 수 있어야 했던 컨텍스트(context)를 재구성하는 일 말입니다.
이를 해결하기 위해 trelix를 만들었습니다. trelix는 Tree-sitter를 사용하여 모든 리포지토리를 인덱싱하고, 모든 심볼(symbol)을 임베딩(embed)하며, hybrid BM25 + vector + call-graph 검색을 사용하여 자연어 질문에 답하는 오픈 소스 코드 인텔리전스 엔진(code intelligence engine)입니다. 오프라인에서 작동하며, API 키가 필요 없고, 인프라 구축도 필요 없습니다.
pip install "trelix[local]"
trelix index ./my-repo
trelix ask ./my-repo "how does the authentication middleware work?"
코드 검색의 문제점
코드를 이해하기 위해 우리가 사용하는 도구들 — 에디터(editors), grep, ctags, 언어 서버(language servers) — 는 코드를 작성하기 위해 설계된 것이지, 대규모로 이해하기 위해 설계된 것이 아닙니다. 이 도구들은 이미 알고 있는 목적지로 이동하는 데는 탁월합니다. 하지만 정답을 모르는 상태에서 "요청 라이프사이클(request lifecycle)이 엔드 투 엔드로 어떻게 작동하나요?" 또는 "이 함수를 호출하는 것은 무엇이며, 그 호출자는 무엇에 의존하나요?"와 같은 질문에 답하는 데는 취약합니다.
grep의 근본적인 한계는 코드베이스를 하나의 문서 코퍼스 (document corpus)로 취급한다는 점입니다. grep은 문자열을 찾습니다. 하지만 코드는 문서 코퍼스가 아니라 그래프 (graph)입니다. 함수는 다른 함수를 호출합니다. 모듈은 다른 모듈을 임포트 (import)합니다. 클래스는 다른 클래스를 상속 (extend)합니다. "인증이 어떻게 작동하나요?"라고 물었을 때, 그 답은 하나의 파일이나 몇 개의 파일이 아닙니다. 그것은 의미론적 진입점 (semantic entry point)에서 시작하여 엣지 (edges)를 따라가며 관련 컨텍스트 (context)를 수집하는 그래프 순회 (traversal)의 결과입니다.
벡터 검색 (Vector search)은 이 문제의 일부를 해결합니다. 의미론적 유사성 (semantic similarity)을 통해 정확한 토큰 (tokens)을 모르더라도 올바른 파일에 더 가깝게 접근할 수 있습니다. 하지만 순수 벡터 검색은 구조적 관계를 놓칩니다. UserRepository.get_by_token()이 항상 AuthMiddleware.verify()에 의해 호출되고, 이 함수는 다시 모든 보호된 라우트 핸들러 (protected route handler)에 의해 호출된다는 사실을 알지 못합니다. 그것은 임베딩 (embedding) 지식이 아니라 호출 그래프 (call-graph) 지식입니다.
trelix는 이 두 가지를 모두 사용합니다.
trelix가 하는 일
trelix는 모든 리포지토리 (repository)를 단일 SQLite 파일(.trelix/index.db)로 인덱싱한 다음, 그에 대한 질문에 답합니다.
인덱스에는 다음 내용이 포함됩니다: Tree-sitter를 통해 추출된 모든 심볼 (symbols) (함수, 클래스, 메서드, 그 본문 및 라인 범위); 심볼과 파일 간의 호출 엣지 (call edges) 및 임포트 엣지 (import edges); sqlite-vec의 HNSW 벡터와 FTS5 BM25를 결합한 하이브리드 검색 인덱스; 그리고 v2.1.0부터는 위의 모든 것을 순회 가능한 NetworkX 그래프로 통합하는 코드 프로퍼티 그래프 (Code Property Graph)가 포함됩니다.
trelix ask ./repo "explain how authentication works"와 같은 쿼리는 3단계 적응형 라우터 (3-tier adaptive router)를 거칩니다:
Tier 1 (Direct) — "X는 무엇인가" 또는 "X를 정의하라"와 같은 단순한 사실적 패턴의 경우, trelix는 검색 (retrieval) 과정을 완전히 건너뛰고 LLM에서 직접 답변합니다. 불필요한 왕복 (round-trips)을 방지합니다.
Tier 2 (8-intent) — 대부분의 코드 쿼리에 대해, trelix는 의도 (intent)를 8가지 카테고리(symbol_lookup, feature_flow, dependency_map, blast_radius 등) 중 하나로 분류하고 적절한 검색 전략을 실행합니다.
Tier 3 (Multi-step) — "요청 라이프사이클을 처음부터 끝까지 설명해줘"와 같은 복잡한 쿼리의 경우, 질문을 2~3개의 하위 쿼리(sub-queries)로 분해하고, 각 쿼리를 독립적으로 실행한 뒤 결과를 병합합니다.
모든 활성 검색 단계(retrieval legs)의 결과는 LLM 합성을 위한 컨텍스트 윈도우(context window)에 조립되기 전, 상호 순위 융합 (Reciprocal Rank Fusion, k=60)을 통해 결합됩니다.
실제 작동 방식
인덱싱 파이프라인은 네 가지 단계로 실행됩니다:
Phase 1 (Parse) — Tree-sitter가 모든 파일을 탐색하며 소스, 라인 범위(line spans), AST 구조와 함께 심볼(symbols)을 추출합니다. ThreadPoolExecutor를 통해 병렬로 실행됩니다.
Phase 2 (Write) — 심볼과 청크(chunks)가 SQLite에 기록됩니다. 파일 간의 parent_id 관계가 해결됩니다.
Phase 3 (Embed) — 모든 청크는 4개의 동시 API 호출 배치 단위로 비동기적으로 임베딩(embedded)됩니다. 로컬 프로바이더(sentence-transformers, API 키 불필요)를 사용하면 이 과정은 완전히 오프라인으로 실행됩니다.
Phase 4 (Resolve) — 파일 간 호출 엣지(call edges)는 3단계 우선순위 전략으로 해결됩니다: 정규화된 이름(qualified name) 우선, 그다음 type_hint+name, 마지막으로 이름만 사용하는 폴백(fallback) 순입니다. 이는 이름만 매칭하는 방식에 비해 파일 간 잘못된 엣지(false-positive cross-file edges)를 약 40% 줄여줍니다.
그 결과, 벡터, BM25, 호출 그래프(call graph), 임포트 그래프(import graph), 심볼, 증분 업데이트를 위한 파일 해시 등 모든 것을 포함하는 단일 .trelix/index.db 파일이 생성됩니다.
인프라 제로, 강력한 성능
이는 의도적인 설계 결정이었으며, 제가 계속해서 되돌아오는 지점이기도 합니다.
대부분의 코드 인텔리전스(code intelligence) 도구는 벡터 데이터베이스, 관계형 데이터베이스, 그리고 종종 별도의 API 서버를 실행해야 합니다. 근본적으로 로컬 개발자 도구인 것을 감안하면 유지 관리해야 할 인프라가 너무 많습니다. trelix의 기본 설정은 HNSW 벡터 검색을 위한 sqlite-vec과 BM25를 위한 FTS5를 사용하는 단일 SQLite 파일입니다. 외부 인프라가 전혀 필요 없습니다. 인터넷 연결이 없는 노트북에서도 작동합니다.
확장이 필요한 경우: 10만 개 이상의 청크(chunk)를 위한 LanceDB 백엔드(ARM/Apple Silicon에서 벡터 삽입 속도 3~5배 향상), 또는 멀티 리포지토리(multi-repo) 공유 컬렉션을 포함한 50만 개 이상의 청크 배포를 위한 Qdrant를 사용할 수 있습니다. 하지만 기본 설정만으로도 대부분의 코드베이스를 처리할 수 있으며, 대부분의 개발자는 전환할 필요가 없을 것입니다.
# 기본 설정 (sqlite) — 최대 약 10만 개 청크
trelix index ./my-repo
...
비스트 모드(Beast Mode): 7가지 검색 단계 (Retrieval Legs)
기본 설정(BM25 + 벡터 + grep + 호출 그래프(call graph))은 대부분의 질문을 잘 처리합니다. 하지만 trelix에는 더 높은 재현율(recall)이나 더 정교한 쿼리 처리가 필요할 때 활성화할 수 있는 5가지 추가 검색 단계가 있습니다.
5단계: 파일 요약 시맨틱 검색 (File-summary semantic search) — RAPTOR 방식(arXiv:2401.18059)을 따릅니다. 인덱싱 시점에 trelix는 모든 파일에 대한 LLM 요약을 생성하고 해당 요약들을 별도로 임베딩(embedding)합니다. 이 방식은 "이 코드베이스를 설명해줘" 또는 "결제 처리를 담당하는 파일은 무엇인가요?"와 같이 답변이 심볼(symbol) 수준이 아닌 파일 수준에서 이루어지는 질문에 특히 효과적입니다.
6단계: SPLADE-Code — 학습된 희소 검색(learned sparse retrieval)을 통한 희소(sparse)+밀집(dense) 하이브리드 방식입니다. SPLADE는 쿼리를 희소 고차원 토큰 벡터로 인코딩하여, BM25와 밀집 벡터 검색(dense vector search)을 모두 보완하는 방식으로 정확한 일치(exact match)를 넘어 어휘를 확장합니다.
7단계: 다중 입도 (Multi-granularity) — 코드 블록(block)과 문장(statement) 수준에서 동시에 인덱싱합니다. 어떤 쿼리는 전체 함수 본문(function body)을 통해 더 잘 답변될 수 있고, 다른 쿼리는 단일 문장을 통해 더 잘 답변될 수 있습니다. 인덱스에 두 가지 입도를 모두 갖추면 정밀한 질문에 대한 재현율(recall)이 향상됩니다.
또한 쿼리 측면의 향상 기능들이 있습니다: HyDE (가상의 코드 답변을 ANN 쿼리 벡터로 생성하여 추상적인 질문에 대한 재현율(recall)을 향상시킴), FLARE (신뢰도 기반 재검색 — 합성(synthesis) 단계에서 불확실성이 나타나면, trelix는 답변을 확정하기 전에 다시 쿼리함), 그리고 v2.2.0부터 도입된 **에이전트 방식의 ReAct 루프(agentic ReAct loop)**로, 자기 수정(self-correction)을 포함하여 다회차의 검색(retrieve)→관찰(observe)→재검색(re-retrieve) 과정을 수행합니다.
# 모든 기능 활성화
TRELIX_RETRIEVAL_AGENTIC=true \
TRELIX_GRAPH_SEARCH_ENABLED=true \
...
내가 가장 자랑스럽게 생각하는 기능들
GitHub PR 리뷰. 이는 v2.4.0 기능이며 가장 많이 사용되는 기능 중 하나가 되었습니다. trelix review --pr owner/repo#42 명령은 GitHub에서 PR diff를 가져오고, 변경된 각 덩어리(hunk)에 대한 코드베이스 컨텍스트를 검색하며, LLM 리뷰를 실행합니다. 또한 --post-comments 옵션을 사용하면 발견된 내용을 단일 배치 리뷰 댓글로 다시 게시할 수 있습니다. 핵심 통찰은 주변 코드베이스를 이해하지 못한 채 diff를 리뷰하는 것은 한 번도 읽어본 적 없는 문장을 교정하는 것과 같다는 점입니다.
trelix review --pr sairam0424/trelix#42
trelix review --pr sairam0424/trelix#42 --post-comments
연합 검색 (Federated search). trelix search-all "query"는 ThreadPoolExecutor를 통해 등록된 모든 리포지토리에 병렬로 분산하여 검색을 수행하고 RRF(Reciprocal Rank Fusion) 방식으로 결과를 병합합니다. trelix watch-all을 사용하면 단 한 번의 watchfiles.awatch() 호출로 등록된 모든 리포지토리를 동시에 감시합니다. FederatedRetriever의 TTL 캐시는 일반적인 디버깅 세션의 쿼리 패턴에 대해 약 90%의 히트율(hit rate)을 제공합니다.
trelix federation add api ./services/api
trelix federation add web ./services/web
trelix search-all "JWT validation"
...
MCP 통합. 단 한 번의 명령으로 trelix를 Claude Code, Cursor, Windsurf, 그리고 Continue.dev 내부에서 사용할 수 있습니다:
pip install trelix-mcp
claude mcp add trelix -- trelix-mcp
그다음 Claude Code 내부에서 다음과 같이 입력합니다: "/path/to/repo에 있는 내 저장소를 인덱싱하고, 인증(authentication)이 어떻게 작동하는지 찾아줘".
이것을 만들며 놀랐던 점
가장 어려운 부분이 임베딩 (embedding) 및 검색 (retrieval) 아키텍처일 것이라고 예상했습니다. 하지만 그렇지 않았습니다. 가장 어려운 부분은 시스템이 유용할 만큼 충분히 주관적(opinionated)이면서도, 특이한 코드베이스에서 작동이 깨질 정도로 과하게 주관적이지 않게 만드는 것이었습니다.
콜 그래프 리졸버 (call-graph resolver)가 가장 대표적인 사례였습니다. 첫 번째 버전은 파일 간 호출 엣지 (call edges)에 대해 이름만 매칭하는 방식을 사용했습니다 — 즉, 파일 A의 login()이 파일 B의 login()을 호출한다고 판단하는 식입니다. 이는 약 40%의 거짓 양성 (false-positive) 엣지를 포함하는 밀도가 높고 노이즈가 많은 그래프를 생성했습니다. 해결책은 3단계 우선순위 리졸루션 (resolution) 전략이었습니다: 먼저 정규화된 이름 (qualified name)을 시도하고 (가장 정밀하지만 재현율이 낮음), 그다음 타입 힌트 (type hint) + 이름을 시도하며 (중간 정도의 정밀도), 마지막으로 이름만 사용하는 방식을 폴백 (fallback)으로 사용하는 것입니다. 이를 통해 전체 타입 어노테이션 (type annotations)이 없는 코드베이스에서도 재현율 (recall)을 유지하면서 거짓 양성을 크게 줄일 수 있었습니다.
또 다른 놀라운 점은 의미론적 임베딩 (semantic embeddings)보다 구조적 메타데이터 (structural metadata)에서 훨씬 더 많은 가치가 나온다는 것이었습니다. 콜 그래프 (call graph), 임포트 그래프 (import graph), 그리고 타입 계층 구조 (type hierarchy)는 trelix의 답변을 코드 파일에 대한 벡터 검색 (vector search)과 질적으로 다르게 만드는 핵심 요소입니다. 의미론적 유사성 (semantic similarity)은 당신을 올바른 근처 영역으로 데려다주지만, 그래프 순회 (graph traversal)는 당신을 올바른 정답으로 데려다줍니다.
여전히 불확실한 점
3단계 쿼리 라우터 (query router)는 제가 테스트한 쿼리들에 대해서는 잘 작동합니다. 하지만 그래프 순회 비용이 커지는 매우 큰 코드베이스 (수백만 줄 단위)에서도 잘 작동할지는 확신이 서지 않습니다. 현재 구현은 BFS 깊이를 2로 제한하고 있는데, 이는 대개 적절하지만 가끔 중요한 연결을 놓치기도 합니다. 적응형 깊이 (adaptive depth)를 위한 적절한 휴리스틱 (heuristics)을 여전히 찾아가는 중입니다.
또한 GraphRAG의 맵리듀스 (map-reduce) 임계값 (threshold)을 여전히 조정하고 있습니다. 현재 기본값 (결과가 20개 초과 또는 8k 토큰 초과 시 활성화)은 보수적입니다. 어떤 쿼리 유형에 대해서는 너무 성급하게 활성화되고, 다른 유형에 대해서는 충분히 빠르게 활성화되지 않습니다. 이것이 제가 실무에서 주시하고 있는 주요 검색 파라미터 (retrieval parameter)입니다.
사용해 보기
# 오프라인 — API 키 불필요
pip install "trelix[local]"
trelix index ./your-repo
...
모든 것은 MIT 라이선스이며, PyPI 및 github.com/sairam0424/trelix에서 확인할 수 있습니다. 전체 문서는 리포지토리의 README에 포함되어 있으며, 7개의 검색 다리 (retrieval legs)를 한 번에 모두 사용하고 싶을 경우를 위한 비스트 모드 (beast-mode) 활성화 블록도 포함되어 있습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기
