---
title: "AI 에이전트가 grep으로 코드를 더듬는 비용, CodeGraph로 걷어내기"
description: "AI 코딩 에이전트가 텍스트 검색에 의존하며 겪는 비효율을 해결하는 CodeGraph를 소개합니다. 코드베이스를 로컬 SQLite 기반 지식 그래프로 미리 인덱싱하여, 에이전트가 호출 관계와 영향 범위를 한 번의 호출로 파악하게 함으로써 탐색 시간을 획기적으로 줄이고 보안까지 챙기는 최적의 코딩 환경을 구축해 보세요"
date: 2026-06-24
updated: 2026-06-24T14:09:24.048Z
tags: [codegraph, ai-agent, claude-code, mcp, 개발도구]
canonical: https://blog.wooncloud.com/posts/codegraph-code-graph-for-ai-agents
---

![CodeGraph — 코드 지식 그래프](/images/posts/codegraph-code-graph-for-ai-agents/d244877d-6aac-4ae4-bc9a-badcf39b400f.webp)

AI 코딩 에이전트를 며칠만 써 보면 한 가지 패턴이 눈에 들어온다. 무언가를 시키면 본격적인 작업에 들어가기 전에 한참을 "탐색"한다. `grep` 한 번, 파일 하나 읽고, 거기서 나온 심볼을 다시 `grep`, 또 파일을 읽는다. 정작 코드를 고치기도 전에 컨텍스트 창의 절반이 파일 덤프로 차 있다. 사람이 IDE에서 "정의로 이동" 한 번이면 끝낼 일을, 에이전트는 텍스트 검색으로 더듬더듬 재구성하고 있는 셈이다.

[CodeGraph](https://github.com/colbymchenry/codegraph)는 이 탐색 단계를 통째로 들어내려는 도구다. 코드베이스를 미리 한 번 파싱해서 심볼과 그 관계를 그래프로 만들어 두고, 에이전트가 물어보면 "필요한 소스 + 그게 누구를 부르고 누가 그걸 부르는지"를 한 번의 호출로 돌려준다. grep/read 루프 수십 번이 explore 한두 번으로 줄어든다. 아래 내용은 v1.1.0을 한 프로젝트에 붙여서 한동안 써 본 기록이다.

## grep으로 코드를 "읽는" 게 왜 비싼가

LLM 에이전트가 코드를 이해하는 기본 동작은 결국 텍스트 검색이다. `requirePermission`이 어디서 쓰이는지 알고 싶으면 그 문자열을 grep하고, 매칭된 파일들을 열어서 읽고, 그중 또 호출 관계를 따라가려면 다시 grep을 돈다. 문제가 몇 가지 겹친다.

- **호출 관계를 텍스트로는 못 따라간다.** 콜백, 동적 디스패치, JSX 자식으로 넘어가는 흐름은 문자열 매칭으로 안 잡힌다. 에이전트는 추측하거나, 더 많은 파일을 읽거나, 그냥 놓친다.
- **토큰이 샌다.** 함수 하나만 보면 되는데 매칭된 파일 전체를 읽어 들인다. 컨텍스트는 한정 자원인데 관련 없는 코드로 채워진다.
- **느리다.** grep → read → grep 은 본질적으로 직렬이다. 매 라운드가 왕복 한 번이고, 실제 작업은 그 뒤에 시작된다.

핵심은, 이 "구조 파악"이 매번 새로 할 필요가 없는 일이라는 점이다. 호출 그래프는 코드가 바뀔 때만 바뀐다. 그렇다면 한 번 계산해서 저장해 두고 재사용하면 된다. 그게 CodeGraph가 하는 일이다.

## CodeGraph가 하는 일

한 줄로 줄이면, 코드베이스의 지식 그래프를 로컬 SQLite에 미리 만들어 두고 에이전트에 MCP 도구로 물려주는 인덱서다. README의 표현을 빌리면 "surgical context · fewer tool calls · faster answers · 100% local"인데, 마지막 항목이 개인적으로 가장 컸다. 데이터가 기계 밖으로 안 나간다. API 키도, 외부 서비스도 없고 SQLite 파일 하나가 전부다. 회사 코드를 다루는 입장에선 이게 채택 가능 여부를 가른다.

인덱싱 파이프라인 자체는 단순하다.

![CodeGraph 인덱싱 파이프라인](/images/posts/codegraph-code-graph-for-ai-agents/11c0e914-6052-4756-982e-753a749c177d.webp)

tree-sitter가 소스를 AST로 파싱하고, 언어별 쿼리로 노드(함수·클래스·메서드·라우트)와 엣지(호출·import·상속)를 뽑아 로컬 SQLite에 넣는다. 검색은 FTS5(전문 검색)로 깔린다. 파일 하나에 갇히지 않도록 참조를 교차 해석하는 게 포인트다. 호출은 정의로, import는 원본으로, 상속 체인을 파일 경계 너머로 잇는다. 그 뒤로는 OS 네이티브 파일 워처(macOS면 FSEvents)가 변경을 감지해 디바운스 후 자동으로 sync하므로, 한 번 init하면 코딩하는 동안 그래프가 알아서 따라온다.

지원 언어도 넉넉하다. TypeScript/Python/Go/Rust/Java/C#/Swift/Kotlin 등 20개 이상이고, Django·Flask·Express·Rails 같은 웹 프레임워크 17종의 라우팅을 인식한다. 라우트가 노드 종류로 따로 잡히는 게 백엔드 코드에선 은근히 유용했다(뒤에 status 출력의 `route` 항목으로 다시 나온다).

## 실제로 붙여 보기

설치는 세 단계다. 스크립트 한 줄로 깔고, 에이전트에 등록하고, 프로젝트마다 인덱스를 만든다.

```bash
curl -fsSL https://raw.githubusercontent.com/colbymchenry/codegraph/main/install.sh | sh
codegraph install   # Claude Code, Cursor, Codex 등에 MCP 서버 등록
codegraph init      # 현재 프로젝트 인덱싱
```

`init`이 끝나면 `.codegraph/codegraph.db`가 생긴다. 상태를 찍어 보면 그래프 규모가 한눈에 보인다. 아래는 내가 인덱싱한 중간 규모 TS/JS 프로젝트의 실제 수치다.

```text
CodeGraph Status

Index Statistics:
  Files:     586
  Nodes:     6,625
  Edges:     21,989
  DB Size:   20.19 MB
  Backend:   node:sqlite — built-in (full WAL)

Nodes by Kind:
  import     2,637
  function   1,596
  constant     768
  file         582
  interface    494
  route        151
  ...
```

586개 파일에서 심볼 6,625개와 엣지 21,989개를 뽑아 20MB짜리 SQLite 하나에 담았다. 이 20MB가 에이전트가 grep으로 매번 재구성하던 구조 정보 전부다.

### explore — 보통 이거 하나면 끝난다

가장 자주 쓰는 명령이다(MCP에선 `codegraph_explore` 도구로 노출된다). 자연어 질문이나 심볼 이름을 던지면 세 덩어리를 돌려준다. 연관 심볼의 verbatim 소스, 호출 관계, 그리고 블라스트 반경이다. 출력은 대략 이렇게 생겼다(내용은 설명용으로 단순화했다).

```text
$ codegraph explore "how does the permission check work"

Found 23 symbols across 9 files.

Blast radius — what depends on these (update/verify before editing)
- permissionToApi (.../mappers.ts:84) — 7 callers in permissions.service,
  files.service, folders.service; tests: core-extended.test
- insertOwnerPermission (.../permissions.repo.ts:75) — 2 callers; ⚠️ no covering tests

Relationships
  calls:  GET /:id/permissions → listPermissions
          addPermission → permissionToApi

Source Code   (줄번호 붙은 실제 on-disk 소스 — Read 한 것과 byte-for-byte 동일)

  server/auth/permission.ts — requirePermission (function)
  131  export function requirePermission(role: Role = "viewer") {
  132    return async (req, res, next) => {
  133      const granted = await resolveRole(req.user, req.params.id)
  134      if (!hasRole(granted, role)) return res.status(403).end()
  135      next()
  136    }
  137  }
```

세 가지가 한 응답에 같이 온다는 게 핵심이다. "이 코드"만 주는 게 아니라 이걸 고치면 누가 깨지는지(블라스트 반경), 그리고 테스트가 걸려 있는지까지 같이 알려준다. grep으로는 `⚠️ no covering tests` 같은 건 절대 못 뽑는다. 게다가 Source Code가 줄번호 붙은 실제 디스크 내용이라, 받은 그대로 바로 편집에 쓸 수 있다. 에이전트 입장에선 "여러 파일을 읽어 보겠습니다" 단계가 통째로 사라지는 셈이다.

### impact — 고치기 전에 폭을 본다

전체 그림이 아니라 심볼 하나의 영향 범위만 보고 싶을 때가 있다.

```bash
codegraph node requirePermission   # 한 심볼의 소스 + 호출자/피호출자
codegraph callers makeId           # 이 함수를 부르는 곳 전부
codegraph impact makeId            # 이걸 바꾸면 영향받는 코드(테스트 포함)
```

이 중 `impact`에 손이 제일 자주 갔다. 공용 ID 생성 헬퍼 하나를 손보려다 `codegraph impact makeId`를 쳤더니 "66 affected symbols"가 떴다. 업로드 서비스, 컨트롤러, 그리고 내가 깜빡했을 게 분명한 테스트 파일까지 파일별로 쭉 나왔다. 헬퍼 하나가 이렇게 멀리 뻗어 있는 줄은 그래프를 보기 전엔 몰랐다. 변경 폭을 눈에 넣고 편집을 시작하는 것과, 고친 다음 무엇이 깨졌는지 뒤늦게 아는 것은 차이가 크다.

### 에이전트에 물리기

MCP 등록은 `codegraph install`이 알아서 해 주지만, 프로젝트에 박아 두고 싶으면 설정 파일 한 조각이면 된다. Claude Code 기준 `.mcp.json`은 이렇게 생겼다.

```json
{
  "mcpServers": {
    "codegraph": {
      "type": "stdio",
      "command": "codegraph",
      "args": ["serve", "--mcp"]
    }
  }
}
```

여기에 "코드 읽기 전에 codegraph부터 써라"는 한 줄을 에이전트 지침에 넣어 두니, 작업 전에 파일을 우르르 열어 보던 습관이 눈에 띄게 줄었다.

## git worktree를 쓴다면 — 한 번 걸린 함정

`.codegraph/`는 기본적으로 gitignore된다(인덱스는 빌드 산출물이니 당연하다). 그런데 `git worktree`를 즐겨 쓴다면 여기서 발이 한 번 걸린다. worktree로 새 작업 디렉터리를 만들면 untracked인 `.codegraph/`가 따라오지 않는다. 정작 편집이 일어나는 워크트리에서 그래프가 깜깜이가 되는 것이다.

해결은 간단하다. MCP 설정(`.mcp.json`)은 추적 파일로 커밋해 모든 워크트리에 전파하고, 워크트리에 인덱스가 없으면 자동으로 만들게 훅을 건다. Claude Code라면 `SessionStart` 훅으로:

```bash
command -v codegraph >/dev/null 2>&1 \
  && [ ! -d ".codegraph" ] \
  && ( codegraph init . >/dev/null 2>&1 & ) ; true
```

세션 시작 시 `.codegraph/`가 없으면(=새 워크트리) 백그라운드로 그 브랜치 기준 인덱스를 새로 뜬다. codegraph가 안 깔린 환경에선 조용히 no-op이라 안전하다. 워크트리당 20MB쯤 더 쓰지만 `git worktree remove`할 때 같이 정리된다. 막 작성한 코드까지 정확히 반영하려면 결국 워크트리별 인덱스가 맞다.

## 그래서, 쓸 만한가

README가 내건 벤치마크는 7개 코드베이스에서 "tool call 58% 감소 · 22% 빠름 · 파일 읽기 거의 0"이고, 절약 폭은 코드베이스 규모에 따라 다르다고 솔직하게 적어 두었다. 나는 이 수치를 재현해 보진 않았다. 어디까지나 정성적 인상이지만, 에이전트가 작업 전에 파일을 끌어모으던 습관이 줄고 호출 관계 질문에 답이 한 번에 나온다는 점은 분명했다.

한계도 있다.

- 인덱스는 쓰기보다 약 1초 늦다. 파일 워처가 디바운스 후 sync하므로, 방금 저장한 코드를 곧바로 explore하면 직전 상태가 잡힐 수 있다. 보통은 무시할 만하다.
- 그래프가 답하는 건 "구조"지 "의도"가 아니다. 누가 누구를 부르는지는 정확하지만, 왜 그렇게 짰는지는 여전히 코드와 주석을 읽어야 한다.
- 동적 디스패치는 정적 호출만큼 깔끔하진 않다. 다만 콜백·런타임 디스패치 일부를 따라가 주므로, 적어도 grep보다는 확실히 더 잡는다.

붙이고 나서 달라진 건 단순하다. 에이전트한테 뭘 시키면 더는 파일부터 열지 않는다. 호출 관계를 물으면 여러 파일을 읽어 보겠다는 말 없이 답이 바로 나오고, 헬퍼 하나를 고치기 전에 영향 범위가 먼저 손에 잡힌다. 회사 코드라 데이터가 기계 밖으로 안 나가는 것도 마음에 들었다. `codegraph init` 한 번 치는 값이 이 정도면 싸다.
