# 代码库图谱 · 用 tree-sitter 建一张符号索引

> grok 用 tree-sitter 给五种语言建了一张作用域符号图（Bloop 风格），支撑跳转定义、查引用。这一章看它怎么解析、建什么节点与边、以及那个专为省内存设计的字符串驻留器。

- 作者：David（道雾轩）
- 专栏：Grok Build 源码解读（https://daiw.org/manual/grok-build.md）
- 最后更新：2026-09-15
- 原文：https://daiw.org/manual/grok-build/codebase-graph-build
- 转载与引用：请注明出处并附原文链接（https://daiw.org/about/copyright）

# 代码库图谱 · 用 tree-sitter 建一张符号索引

`xai-codebase-graph` 是一张 **Bloop 风格的作用域符号图**：用 tree-sitter 解析代码，建出「符号在哪定义、被谁引用」的索引，支撑编辑器级的跳转定义 / 查引用。这一章看它怎么建。

## 五种语言，靠 tree-sitter 查询

它支持 Rust / TypeScript / JavaScript / Go / Python 五种语言。每种语言一个 `TSLanguageConfig`，核心是一段 **tree-sitter S 表达式查询**，约定捕获名：`@name.definition.<kind>`(定义)、`@name.reference.<kind>`(引用)，外加别名对。`<kind>`(function/struct/const……)对应命名空间，用于「引用→定义」的命名空间感知匹配。

<Callout type="info">
  `LanguageRegistry` 有个 `compute_query_hash()`——对所有语言的查询字符串算哈希。查询一改，哈希变，缓存自动失效重建。**把「规则」纳入缓存版本**是个好习惯：否则你改了 query 却读到旧索引，查出来的结果对不上。
</Callout>

## 两种图：每文件 vs 跨文件

- **`ScopeGraph`**(每文件)：一张 `petgraph` 图，节点是 `Scope/Def/Import/Ref`，边是 `RefToDef/DefToScope……`。插入引用时从最紧作用域往根走(`ScopeStack`)，按名字 + 命名空间匹配定义。这是经典的作用域图解析。
- **`ScopeGraphIndex`**(跨文件，**真正的索引**)：扁平结构，`definitions: 符号ID → [(路径ID, 行)]`、`references: ...`、别名、文件元信息。这才是持久化、被查询的东西。

一个性能取舍：索引构建走**快路径 `extract_symbols_fast`**——**跳过建图**，直接返回定义/引用/别名列表(预计算好每个捕获是 def 还是 ref，避免热循环里反复比字符串，“快 2–3 倍”)。完整的 `ScopeGraph` **不持久化**，需要时才重建。

## 字符串驻留器：为省内存而生

大仓库里符号名、路径重复度极高。`StringInterner`(`interner.rs`)是个内存优化的关键：一个字节 arena + `offsets: Vec<(u32, u16)>` + 用预哈希的 `nohash_hasher` 做查找，`StringId(u32)` 代替字符串到处传。定义/引用表里存的都是 `StringId`，行号用 `u32`(8 字节/条，而非 16)。7 月版源码的注释把目标写得很直白：**把大仓库的索引从 ~1GB 压到 ~100MB**(新代码删掉了这句注释，数据结构没变)。

## 构建流程：并行两阶段

`IndexBuilder::build_fast`:

1. **收文件**：先试 `git2` 拿 tracked 文件(快)，不行再用 `ignore` 并行 walk(尊重 `.gitignore`)，按支持的扩展名过滤。
2. **阶段一(并行)**：`rayon` 线程池，`par_chunks(100)` 逐文件 `process_file_fast`——线程本地缓存 parser/query、5MB 大小闸、8KB 前缀查二进制、解析、抽符号。
3. **阶段二(串行合并)**：归并进单个 `ScopeGraphIndex`(单一 interner)。按 5000 文件/批控制峰值内存。
4. `compact()` 收缩。

持久化用**自定义二进制格式**(魔数 `SGIX`)——手写小端读写 interner arena、defs、refs、别名。识别不了魔数就当遗留、回退重建。

## 和《从 LLM 到 Coding Agent》对照

那本没有代码库图谱——它超出了「最小 agent」的范围。grok 这套(tree-sitter 多语言 + 作用域图 + 驻留器 + 并行构建 + 自定义序列化)是一个独立、完整的**符号索引引擎**。不过它有个出人意料的真相——**它其实不喂给模型**。下一章专门讲这件事。

---

上一篇：[Git 集成](https://daiw.org/manual/grok-build/git-integration) · 下一篇：[图谱的真实用途](https://daiw.org/manual/grok-build/codebase-graph-use)
