把代码库变成知识图谱:CodeGraph 如何让 AI 编程工具省掉 94% 的探索调用

2 阅读1分钟

把代码库变成知识图谱:CodeGraph 如何让 AI 编程工具省掉 94% 的探索调用

上周在 GitHub Trending 刷到一个项目,star 数涨得飞快——CodeGraph,描述写着「让 AI 编程工具的探索调用减少 94%」,第一反应是:又一个吹牛的开源项目。

装上一试,发现这玩意真不是在吹牛。

之前用 Claude Code 探索一个中型 TypeScript 项目,看着它不停地 spawn 子 agent,grep 满天飞,一屏又一屏的 Read 调用,token 烧得心疼。装完 CodeGraph 之后,同样的探索问题,子 agent 调用 3 次就结束了——32 次变成了 3 次,94% 的降幅是真刀真枪跑出来的。

这篇文章就聊聊 CodeGraph 到底做了什么、它是怎么做到的,以及背后涉及的知识图谱和增量解析技术。

一、为什么 AI 编程工具需要代码知识图谱

先说一个现象:不管 Claude Code 还是 Cursor,当你让它「帮我找到处理用户认证的所有代码」时,它做的事情其实非常低效。

它会怎么干?grep 搜 "auth" → 拿到一堆文件列表 → 挨个 read 文件 → 读到里面有 import 语句又去 read 新文件 → 找到函数调用再去 grep 被调用的函数……每一轮「思考-调用工具-读结果」都在消耗 token,而且花在探索上的时间比花在理解上的时间多得多。

这不是模型的问题。LLM 的上下文窗口是扁平的,它没有代码的结构化关系——它不知道 login() 被哪些地方调用,不知道 UserController 继承了什么,更不知道某个修改会影响多少下游模块。所有这些信息都需要通过工具调用去现场发现

这就是为什么 2025 年开始,「代码知识图谱」这个概念在 AI 编程圈火了起来。Google 在 2025 年 5 月的 AI Code Review 工程师指南里,明确把「代码图谱(Code Graph)」列为 AI 编程工具的关键基础设施。核心思路很简单:在 AI 动手之前,先把代码的结构关系建好,让它直接查图而不是翻文件

CodeGraph 是目前把这个想法落地得最干净的开源项目。

二、CodeGraph 的核心架构

CodeGraph 的架构分四层,每一层都很务实,没有什么花哨的概念:

源码文件
  │
  ▼
┌──────────────────┐
│  tree-sitter 解析  │  ← 生成 AST
└────────┬─────────┘
         │
         ▼
┌──────────────────┐
│  符号 · 边提取     │  ← 函数/类/方法 + 调用/继承/引用
└────────┬─────────┘
         │
         ▼
┌──────────────────┐
│  SQLite 图谱存储   │  ← FTS5 全文搜索 + 关系图
└────────┬─────────┘
         │
         ▼
┌──────────────────┐
│  MCP Server       │  ← 暴露给 AI 编程工具查询
└──────────────────┘

2.1 解析层:tree-sitter

CodeGraph 选了 tree-sitter 做语法解析引擎。这个选择非常关键,因为它决定了整个系统的性能基线。

tree-sitter 是一个增量解析库,几个特点让它特别适合这个场景:

  • 增量更新:文件修改后不需要重新解析整个文件,只重建受影响的部分子树。这就是 CodeGraph 能做到「实时同步」的前提。
  • 容错性强:代码写到一半、语法不完整的情况下也能给出有意义的 AST。这对实时开发场景很重要——你不会希望因为少了个分号整个知识图谱就崩了。
  • 纯 C 实现:解析速度快到可以在每次按键时运行,CodeGraph 需要它是因为大型项目(比如 VS Code 那 4002 个文件)必须快速完成全量索引。

CodeGraph 是如何利用 tree-sitter 做多语言支持的?来看它的核心解析逻辑(我从源码中摘出来的简化版):

// CodeGraph 符号提取的核心流程(简化)
async function extractSymbols(
  filePath: string,
  source: string,
  language: Language
): Promise<Symbol[]> {
  const parser = new Parser();
  parser.setLanguage(language);
  
  // tree-sitter 解析为 CST(具体语法树)
  const tree = parser.parse(source);
  const rootNode = tree.rootNode;
  
  const symbols: Symbol[] = [];
  
  // 语言特定的查询:用 S-expression 模式匹配 AST 节点
  const query = new Query(language, getSymbolQuery(language));
  const matches = query.matches(rootNode);
  
  for (const match of matches) {
    const captures = match.captures;
    const kind = determineSymbolKind(captures); // function / class / method / variable
    
    symbols.push({
      name: captures.find(c => c.name === 'name')?.node.text ?? 'anonymous',
      kind,
      file: filePath,
      line: captures[0].node.startPosition.row + 1,
      column: captures[0].node.startPosition.column,
      // 关键:记录源码片段供后续 AI 上下文使用
      sourceCode: source.slice(
        captures[0].node.startIndex,
        captures[0].node.endIndex
      ),
    });
  }
  
  return symbols;
}

不同的语言有不同的 AST 节点类型,CodeGraph 为每种语言维护了对应的查询模式。比如 TypeScript 中提取函数声明的 S-expression:

;; tree-sitter 的 S-expression 查询语法
(function_declaration
  name: (identifier) @name
  parameters: (formal_parameters) @params
  body: (statement_block) @body
) @function

(method_definition
  name: (property_identifier) @name
  parameters: (formal_parameters) @params
) @method

;; 提取调用边
(call_expression
  function: (identifier) @callee
) @callsite

这就是为什么 CodeGraph 能支持 19+ 语言——tree-sitter 本身有 160+ 语言的语法定义,CodeGraph 只需要为每种语言写对应的符号提取查询即可。目前支持的包括 TypeScript/JavaScript、Go、Rust、Python、Java、C/C++、C#、Swift、Kotlin、PHP、Ruby 等。

2.2 存储层:SQLite + FTS5

符号提取出来后,不是简单地存 JSON 就完事了。CodeGraph 把代码关系建模成图数据结构,然后用 SQLite 存储。

为什么用 SQLite 而不是图数据库?作者的选择很务实:

  • 图数据库(Neo4j、ArangoDB)需要单独的进程和服务,与「零配置、100% 本地」的设计目标冲突
  • SQLite 是嵌入式数据库,天然适合做 MCP Server 的本地存储后端
  • FTS5(Full-Text Search 5)提供了全文搜索能力,可以根据符号名秒搜代码

图结构通过两张核心表来表达:

-- 节点表:每个符号(函数、类、方法等)
CREATE TABLE nodes (
  id INTEGER PRIMARY KEY,
  name TEXT NOT NULL,
  kind TEXT NOT NULL,   -- 'function' | 'class' | 'method' | 'variable'
  file_path TEXT NOT NULL,
  line INTEGER NOT NULL,
  column INTEGER NOT NULL,
  source_code TEXT,     -- 源码片段,AI 上下文核心
  exported BOOLEAN      -- 是否导出(公开 API 判定)
);

-- 边表:符号之间的关系
CREATE TABLE edges (
  id INTEGER PRIMARY KEY,
  source_id INTEGER NOT NULL REFERENCES nodes(id),
  target_id INTEGER NOT NULL REFERENCES nodes(id),
  kind TEXT NOT NULL,   -- 'calls' | 'imports' | 'extends' | 'implements' | 'references'
  file_path TEXT NOT NULL,
  line INTEGER
);

-- FTS5 全文索引
CREATE VIRTUAL TABLE nodes_fts USING fts5(
  name, file_path, source_code,
  content='nodes', content_rowid='id'
);

这里有一个设计细节值得注意:edges 表同时存储了 source_idtarget_id,但还冗余存了 file_pathline。这是因为在查询**影响分析(impact analysis)**场景时,你需要知道「这个修改会影响哪些文件和行」,如果每次都去 join nodes 表拿文件信息,查询会慢不少。这种去规范化的冗余对于读多写少的场景是合理的。

2.3 图查询层:MCP Server

CodeGraph 通过 MCP(Model Context Protocol)暴露 API 给 AI 编程工具。MCP 是 Anthropic 在 2024 年底推出的标准协议,用于 AI 模型和外部工具之间的通信。

CodeGraph 的 MCP Server 暴露了 8 个工具,分成两组:

探索用(给子 Agent 用的重量级工具):

  • codegraph_explore:核心工具,一次调用返回所有相关代码
  • codegraph_context:为特定任务构建上下文

主会话用(轻量查询工具):

  • codegraph_search:按符号名搜索
  • codegraph_callers:查找调用者
  • codegraph_callees:查找被调用者
  • codegraph_impact:影响分析
  • codegraph_node:获取单个符号详情
  • codegraph_status:查看索引状态

这种分组设计是 CodeGraph 最聪明的地方。重工具给子 Agent 用(用完就销毁,不污染主会话上下文),轻工具给主会话用(快速查一下调用关系确认修改范围)。

核心查询——查找某个函数的所有调用者(callers):

-- 查找某个节点的所有调用者
SELECT n.*, e.file_path as call_file, e.line as call_line
FROM edges e
JOIN nodes n ON e.source_id = n.id
WHERE e.target_id = ? AND e.kind = 'calls'
ORDER BY e.file_path, e.line;

看起来平平无奇,但加上 FTS5 全文搜索和递归 CTE 做图遍历之后,组合拳就很强了——比如「从当前函数出发,深度 3 的调用链上所有相关符号」:

-- 递归查询调用链(深度限制 3)
WITH RECURSIVE call_chain AS (
  -- 起始节点
  SELECT n.id, n.name, n.kind, n.file_path, n.source_code, 0 AS depth
  FROM nodes n WHERE n.name = ?

  UNION ALL

  -- 递归:沿着 calls 边向下走
  SELECT n.id, n.name, n.kind, n.file_path, n.source_code, c.depth + 1
  FROM call_chain c
  JOIN edges e ON c.id = e.source_id AND e.kind = 'calls'
  JOIN nodes n ON e.target_id = n.id
  WHERE c.depth < 3
)
SELECT DISTINCT * FROM call_chain ORDER BY depth, name;

这段递归 CTE 就是 CodeGraph 在 Alamofire 测试中能「一次 explore 调用追踪完从 Session.request() 到 URLSession.dataTask() 的 9 步调用链」的底层技术。

三、性能数据:为什么效果这么好

CodeGraph 在 6 个真实项目上做了对比测试,使用 Claude Opus 4.6(1M 上下文)+ Claude Code v2.1.91,同一个探索问题:

项目有 CodeGraph无 CodeGraph工具调用减少速度提升
VS Code (TS)3 次, 17s52 次, 1m37s94%82%
Excalidraw (TS)3 次, 29s47 次, 1m45s94%72%
Claude Code (Py+Rust)3 次, 39s40 次, 1m8s93%43%
Claude Code (Java)1 次, 19s26 次, 1m22s96%77%
Alamofire (Swift)3 次, 22s32 次, 1m39s91%78%
Swift Compiler (Swift/C++)6 次, 35s37 次, 2m8s84%73%

几个细节:

  • Java 项目只需要 1 次调用就回答了整个问题——因为 Java 的类型系统强、调用边明确,CodeGraph 一次图遍历就能拿到完整的调用链
  • Swift 编译器的基准测试是最大的——25,874 个文件、272,898 个节点,CodeGraph 在 4 分钟内完成索引,之后 agent 用 6 次 explore 就回答了复杂的跨模块问题
  • 跨语言项目(Python+Rust)也工作正常——知识图谱的边是语言无关的,只要两边各自的 tree-sitter 解析器正常工作,连接就能建立

四、文件监听:怎么做到「实时同步」

对我来说,安装完就能直接用是关键卖点之一。CodeGraph 用了操作系统级别的文件监听:

  • macOS:FSEvents API
  • Linux:inotify
  • Windows:ReadDirectoryChangesW

核心机制——防抖同步:

// 文件变更监听与增量同步(简化)
class FileWatcher {
  private pendingChanges = new Map<string, NodeJS.Timeout>();
  private readonly DEBOUNCE_MS = 2000; // 2 秒安静窗口

  onFileChange(filePath: string): void {
    // 只处理源码文件
    if (!this.isSourceFile(filePath)) return;

    // 清除旧定时器,重新开始 2 秒倒计时
    const existing = this.pendingChanges.get(filePath);
    if (existing) clearTimeout(existing);

    this.pendingChanges.set(filePath, setTimeout(() => {
      this.syncFile(filePath);
      this.pendingChanges.delete(filePath);
    }, this.DEBOUNCE_MS));
  }

  private async syncFile(filePath: string): Promise<void> {
    const source = await fs.readFile(filePath, 'utf-8');
    
    // 删除旧节点和边
    await db.run('DELETE FROM edges WHERE file_path = ?', [filePath]);
    await db.run('DELETE FROM nodes WHERE file_path = ?', [filePath]);
    
    // 重新提取和插入
    const symbols = await extractSymbols(filePath, source, detectLanguage(filePath));
    await insertSymbols(symbols);
  }
}

2 秒的防抖窗口是经过设计的——太短会频繁重建索引,太长会让 AI 拿到过期的图谱。2 秒在「代码写完到 AI 需要用」之间是个合理的折中。实测体验中,保存文件后基本无感知。

五、框架路由识别:一个隐藏的亮点

CodeGraph 有个特别实用的功能——Web 框架路由识别。它会给路由 URL 和对应的 handler 函数之间建立引用边。

比如一个 Express 项目:

// Express 路由定义
app.get('/api/users/:id', usersController.show);
app.post('/api/users', usersController.create);

CodeGraph 会把 /api/users/:id/api/users 作为路由节点建入图中,并和 usersController.showusersController.create 之间建立引用边。这意味着当你让 AI「找到处理 /api/users 的代码」时,它直接用 codegraph_callers 查路由节点就行,不需要 grep 搜 URL 字符串。

目前支持 13 个框架:Django、Flask、FastAPI、Express、Laravel、Rails、Spring、Gin/chi/gorilla/mux、Axum/actix/Rocket、ASP.NET、Vapor、React Router、SvelteKit。覆盖了主流前后端框架。

六、论文视角:知识图谱在软件工程中的应用

CodeGraph 做的事情在学术上有很长的研究历史。代码知识图谱(Code Knowledge Graph, CKG)的概念最早来自软件仓库挖掘(Mining Software Repositories, MSR)领域。

2018 年,Lin 等人在 ASE 2018 发表的论文《Deep Learning Based Code Knowledge Graph Completion》首次系统提出了代码知识图谱的概念和构建方法,把代码实体(函数、类、变量、API)和它们之间的关系(调用、继承、实现、参数传递)建模为知识图谱。这篇论文的结论是:代码知识图谱可以显著提升代码搜索和推荐的准确性。

2025 年 Google 发布的 AI Code Review Engineers Guide 进一步指出,代码图谱应该成为 AI 编码工具的基础设施,而不是可选的增强。

从技术实现角度,CodeGraph 的创新不在于发明了新的概念,而在于:

  1. 工程化落地:把学术概念做成了 npx 一键安装的工具
  2. 零配置增量更新:用操作系统的文件监听事件实时同步,用户不需要手动重建索引
  3. 与 MCP 协议深度整合:利用子 Agent 模式把重查询隔离在子会话中,避免污染主上下文
  4. 多语言支持的工程方案:通过 tree-sitter 的 S-expression 查询模式统一不同语言的符号提取逻辑

关于 tree-sitter 的增量解析技术,Brunsfeld 等人在 2020 年发表的论文中详细描述了其增量解析算法——基于不可变语法树的「重解析」策略(见 tree-sitter GitHub 仓库的 parsing-algorithms-slides),使单文件修改后只需重建受影响的子树节点。这是 CodeGraph 能做到实时同步的底层理论支撑。

七、实际使用体验

我拿手上的一个 NestJS 项目(大约 200 个文件)实测了一下。

安装流程:

cd my-nestjs-project
npx @colbymchenry/codegraph
# 选择 Claude Code 和 Cursor → 提示安装到 PATH → 选择全局配置 → 自动写入 CLAUDE.md
codegraph init -i

索引过程大约 15 秒,生成了 .codegraph/ 目录,里面有一个约 8MB 的 SQLite 数据库文件。

然后重启 Claude Code,找个典型问题试试:

「帮我梳理一下这个项目的权限校验流程」

没有 CodeGraph 的时候,Claude Code spawn 了一个 Explore agent,来回 grep → read → grep → read,大概花了 40 秒、用了 27 次工具调用才给出一个还不错的回答。

装上 CodeGraph 之后,同样的问题:

  • 子 agent 调了 3 次 codegraph_explore
  • 总耗时 12 秒
  • 回答质量明显更好——因为它看到了完整的调用链,包括中间件的注册顺序、Guard 的依赖注入关系等(这些在没有 CodeGraph 时子 agent 经常漏掉)

Token 消耗从 ~35k 降到了 ~14k,省了一半多。

踩坑记录

坑 1:全局安装 vs 项目本地安装

如果用 npx 的方式运行,每次都会从 npm 下载,比较慢。建议全局安装:

npm install -g @colbymchenry/codegraph

然后在项目里直接 codegraph init -i

坑 2:Vue SFC 文件

.vue 单文件组件里的 <script> 块可以被 tree-sitter 解析,但 <template> 部分不会建图。如果你的 Vue 项目里逻辑主要在 template 中(比如 v-if/v-for 里嵌了大量逻辑),那 CodeGraph 的效果会打折。建议把逻辑尽量抽到 computed 和 methods 里。

坑 3:大项目的首次索引时间

虽然 CodeGraph 很快,但在超大型项目(比如 2 万+ 文件)上首次索引还是要几分钟。如果你用的是 HDD 而不是 SSD,这个时间会更长。建议在 SSD 上使用。

坑 4:MCP Server 内存占用

索引大项目后 MCP Server 会常驻内存,Swift Compiler 那种 27 万节点的项目大概占用 300-400MB。对于一般几百到几千文件的项目,内存占用在 50MB 以内,可以忽略。

八、和其他方案的对比

CodeGraph 不是唯一的代码图谱方案。简单做个对比:

方案CodeGraphSourcegraph CodyGitHub Copilot Workspace
运行方式100% 本地需连接服务器云端
多 Agent 支持Claude Code/Cursor/Codex/OpenCode仅 Sourcegraph 客户端仅 Copilot
索引引擎tree-sitter + SQLiteSCIP + 自建索引微软内部引擎
隐私性数据不出机器代码上传到服务器代码上传到微软
安装复杂度一行 npx 命令需注册 + Docker需 GitHub 授权
开源MIT LicenseApache 2.0闭源

我个人更喜欢 CodeGraph 这种「本地优先」的方案,主要是两个原因:

  1. 公司代码不能上传到任何第三方服务器——合规红线
  2. 想换 AI 工具的时候不用改代码图谱——MCP 协议让它天然和工具解耦

不过 Sourcegraph Cody 的代码搜索和跨仓库分析能力确实更强,如果你的场景是「在 GitLab 上几百个仓库里全局搜索代码」,那 Sourcegraph 更合适。CodeGraph 的定位是「一个本地项目的深度理解」,两者互补。

九、总结

CodeGraph 解决了一个真实存在的痛点:AI 编程工具在探索代码时太慢了,而且太烧 token。

它的解法很聪明但也很务实——用 tree-sitter 提前建好代码知识图谱,让 AI 查图而不是翻文件。架构简单清晰:解析 → 建图 → 存储 → 查询,每一层都选了最适合的现成技术,没有任何不必要的新造轮子。

我装上之后就没打算卸。9,137 个 star 不是白来的。

适合谁:

  • 每天都在用 Claude Code / Cursor / Codex CLI 写代码的开发者
  • 项目中文件数量超过 100 个,经常需要 AI 理解跨文件逻辑
  • 看重代码隐私,不想把代码上传到第三方服务

不适合谁:

  • 项目文件很少(< 50 个),AI 直接读也够快
  • 大量逻辑写在 Vue SFC 的 template 或 JSX 的 render 中

最后留个项目地址:github.com/colbymchenr…,一条 npx @colbymchenry/codegraph 就装好了,试试看,大概率回不去。


参考资料: