内容
# scip-code-kg
从 [SCIP](https://github.com/scip-code/scip) 索引中构建一个**可查询的代码知识图谱**,并通过 **MCP** 为 AI 编程代理提供服务。
有关当前架构、模式、构建管道、图语义、查询算法、发布协议和已知边界的详细描述,请参阅 [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md)(中文)。
该管道处理编译器级别的 SCIP 索引(Java 通过 `scip-java`,C++ 通过 `scip-clang`),将它们物化为**每个版本自包含的 SQLite 数据库** —— 符号、精确引用、派生调用图、继承关系、跨语言 RPC/JNI 桥、每个源文件的 zlib 快照、消息模板索引,以及可选的每个符号的更改历史 —— 并通过一个 stdio MCP 服务器公开十个查询工具,该服务器可以**同时托管多个版本**(版本是查询参数,而不是部署拓扑)。代理需要的所有内容,以便回答 *"谁调用此函数?"*、*"如果我更改此函数会发生什么?"*、*"哪个代码产生此错误行?"* 或 *"此 C++ 函数如何到达该 Java 实现?"*,都成为毫秒级索引的 SQL 查询。
该项目已在 [StarRocks](https://github.com/StarRocks/starrocks) 代码库(130 万行 Java 前端 + 90 万行 C++ 后端 + JNI 扩展)上进行了实战测试:**48.4k 符号,6.4M 出现,255 万调用边,约 250 个跨语言桥边,32k 消息模板每个版本**,在几分钟内构建完成(索引生成除外),为一个版本提供服务。
## 为什么选择 SCIP 而不是 tree-sitter 启发式?
基于 tree-sitter 的图工具在词法上解析名称:重载、模板和虚拟调度会产生猜测的边带有置信度。SCIP 索引来自真正的编译器(`javac` 插件 / Clang 前端),因此:
- 每个引用都指向编译器解析的**确切重载**(`log(String)` vs `log(String, Throwable)` 是不同的符号);
- 接口/虚拟调度通过 `is_implementation` 关系显式建模,可按需扩展;
- 相同的 protobuf 格式涵盖每种语言,因此 Java 前端和 C++ 后端可以放在**一个图谱中,具有一个 ID 空间**。
## 架构
```
scip-java (Java FE) ─┐
scip-clang (C++ BE) ─┼─► scip_loader.py ─► kg-<ver>-<sha>.db (自包含)
scip-java (Java ext) ─┘ │
│ 后处理,均为幂等:
├─ be_kg_patch.py C++ 函数体范围通过
│ tree-sitter(scip-clang 不发出)
├─ thrift_bridges.sql 声明式 Java↔C++ RPC 桥
├─ bridge_patch.py 词法 thrift + JNI + brpc 桥
├─ kg_enrich.sql 指标、文件/目录依赖、全文索引
├─ snapshot_text.py zlib 源代码快照到数据库
├─ extract_messages.py 错误/日志模板索引
└─ kg_changes.py (可选)每个符号的 PR 历史
│
kg_mcp_server.py (stdio MCP,10 个工具,
多版本:KG_DIR 扫描)
```
主要且默认的部署是**端到端 SQLite**:每个索引版本一个自包含的 `.db`,无需运行的服务,通过递归 CTE 进行事务查询。自设计 v2 以来,数据库嵌入了自己的源代码文本(`file_text`),因此服务机器**无需 checkout,无需 git,无需网络** —— 服务器扫描 `KG_DIR` 以获取 `kg*.db`,每个数据库都由其 `snapshot_meta.version_label` 键控,并且每个工具都接受 `version=`。
## 快速开始
### 1. 一次性设置
```bash
python3 -m venv venv && ./venv/bin/pip install -r requirements.txt
./venv/bin/python -m grpc_tools.protoc --proto_path=. --python_out=. scip.proto
```
### 2. 生成 SCIP 索引
```bash
cd $REPO/fe && scip-java index # 使用真正的 Maven 构建
cd $REPO && scip-clang --compdb-path=be/build/compile_commands.json \
--index-output-path=index-be.scip -j $(nproc)
```
`scip-clang` 需要编译数据库:使用 `-DCMAKE_EXPORT_COMPILE_COMMANDS=ON` 配置 C++ 构建。
### 3. 构建图谱
```bash
./build_kg.sh /path/to/repo # 完整管道,或:
SKIP_INDEX=1 ./build_kg.sh /path/to/repo # 重用现有的 index.scip 文件(~2 分钟)
```
输出:`kg-<branch>-<sha>.db`。每个阶段都写入 `<final>.db.building`;然后 `kg_publish.py` 验证(`PRAGMA quick_check`,所需表,非空图谱),将清单戳入 `snapshot_meta`(`schema_version`,`artifact_id = <label>@<full_sha>`,`builder_git_sha`,`input_hashes`,`build_status=ready`)并发布,伴随原子同目录重命名。**发布的工件是不可变的**:现有最终版本被拒绝(`REPLACE=1` 用于故意重建),而已服务的数据库的丰富(新的 `kg_changes.py` 范围,丰富重运行)通过 `tools/republish_enrich.sh` 进行 —— 复制、丰富、验证、原子替换,然后重启服务器会话(`tools/kg_health.sh` 检查舰队)。重建是完整的,而不是增量的 —— `scip-java` 和 `scip-clang` 均不支持增量索引(夜间 CI 或按需)。
### 4. 为代理提供服务
```bash
claude mcp add code-kg \
-e KG_DIR=/path/to/dir-with-kg-dbs \
-- /path/to/venv/bin/python /path/to/kg_mcp_server.py
# 或单个数据库:-e KG_DB=/path/to/kg-<ver>-<sha>.db
```
一个服务器托管 `KG_DIR` 下的所有 `kg*.db`(惰性只读连接;`graph_info` 不带参数列出舰队)。工件身份为 `label@<full_commit_sha>`,因此同一分支的两个提交可以并排托管;`version=` 接受裸标签,只要它唯一,否则 `label@shaprefix`(或裸唯一 sha 前缀)—— 模糊标签返回候选项而不是猜测。验证失败的数据库(缺少 v3 丰富表,不支持的 `schema_version`,`build_status` ≠ ready)在启动时被跳过,并在 stderr 上显示原因。每个工具都接受 `version=`,除了 `diff_symbol`,它接受 `version_a=`/`version_b=`。服务器使用 stdio,因此当客户端在同一台机器上运行时,不涉及网络。要从远程客户端使用它,请通过 `ssh -T` 隧道传输相同的命令。**不要**将其暴露为未经身份验证的 HTTP 端点。
## MCP 工具
| 工具 | 答案 |
|---|---|
| `find_symbol` | 模糊全文搜索按名称(按词分割 camelCase 查询自动连接);返回 `{status, matches, total}`,带有 id、种类和签名 |
| `neighbors` | 一个符号的直接关系 — `relation=callers\|callees\|references\|implementations`;调用者/被调用者边**包括跨语言桥** |
| `impact` | 调用 ∪ 桥上的深度有界反向传递闭包;种子实现接口以覆盖虚拟调度 |
| `read_definition` | 函数/类体的完整源代码,精确到索引的提交 |
| `graph_info` | 快照出处(提交、索引器、表大小);使用 `dir_prefix=` 目录级依赖图 |
| `analyze_stack` | 一次性崩溃堆栈解析:粘贴原始堆栈文本(C++/Java,OCR 噪音容忍)—— 每个可识别的帧返回符号 id + 位置 + 定义源代码 + 自快照以来更改它的 PR;运行时噪音被跳过,未匹配的行被列出,没有被静默丢弃 |
| `trace_path` | 两个符号之间的具体调用路径(s)通过调用 ∪ 桥 ∪ 接口调度(JDK 函数接口排除 - 它们将所有内容 wormhole 到所有内容;调度方向根据类型级超类型闭包验证);16 跳 FE→BE 路径通过 brpc 桥在 ~0.7 秒内解析;`detail=source` 将每个跳点的调用站点窗口和两个端点体在字节预算下内联 |
| `symbol_history` | 索引快照后哪些 PR 更改了符号 - 来自 `kg_changes.py` 丰富:一个累积的 `git diff --unified=0`,其旧侧行号是确切的快照坐标,与 `def_ranges` 间隔连接;通过 `git log -L`/`-G` 进行每个 PR 的归属 |
| `find_message` | 症状 → 代码入口:粘贴原始错误/日志行(填充值,glog/log4j 前缀,OCR 噪音全部正常);嵌入式 `file.cpp:123` 直接解析,否则基于 `messages` 索引的标准化模板匹配返回每个直接生产者及其源函数和内联源代码;无法解析的动态文本标记为 `partial`/`miss`,永远不会为空 |
| `diff_symbol` | 跨版本结构化答案“是否在 X 中仍然存在该 bug”:解析 `version_a` 上的符号,在 `version_b` 上用包版本无关的 SCIP 描述符(前端符号字符串中的工件版本在版本之间更改)匹配其对应项,并返回统一的体差异 — 或 `identical:true`,或诚实的未找到具有最近候选者的结果 |
表面故意很小(十个工具),输出故意简洁 — 紧凑的 JSON,合格的短名称而不是完整的 SCIP 符号字符串,默认限制很小,原生批处理(每个调用最多 10 个以逗号分隔的 id/分号分隔的查询)。每个工具返回的每个字节在会话的每个后续回合中都会重新发送到模型,因此输出大小主导图工具的实际成本;查询延迟(~1 毫秒)永远不会这样做。预合并工具名称(`find_callers`、`find_references`,...)在 CLI 模式下仍然作为别名工作。
内置防护:预期结果是成功形状的 `status` 载荷(`ok|partial|ambiguous|not_found|invalid_argument|unsupported`)而不是传输错误,因此代理永远不会看到错误墙;模糊名称返回带有 ID 的结构化候选项而不是在重载中猜测;列表响应带有 `returned/total/truncated`;`read_definition` 强制执行 ~48KB 响应预算,以 JSON 项目粒度为单位;外部和无位置符号(stdlib,依赖项 jar)被降级;桥边永远不会被 `neighbors` 调用边限制;测试文件从路径遍历和影响中排除;每个调用都被审计日志记录(`mcp_calls.jsonl`)带有版本、延迟、输出大小和答案质量信号,用于事后分析。
> **提示:** 一致的评估显示,除非提示,否则代理会低估图工具。在项目说明中添加一行,例如:*“对于调用图、影响或跨语言问题,请优先使用 `code-kg` 工具而不是 grep。”*
## 调用图的推导
SCIP 没有原生的“调用”关系。边在构建时通过一个区间连接:引用出现落在定义的可封闭范围内,是来自该定义的调用 - **innermost-wins** 归属:当函数体嵌套时(方法-local 类在测试助手内,局部函数对象在函数内),只有最小的包含体是调用者;相等跨度绑定全部保留,因为 C++ 模板实例化在 N 个符号下共享一个源代码体,每个体必须保留其出边或路径在实例化边界处中断。
```sql
INSERT INTO calls (caller_id, callee_id, file_id, line)
SELECT DISTINCT caller_id, callee_id, file_id, line FROM (
SELECT dr.symbol_id AS caller_id, o.symbol_id AS callee_id,
o.file_id AS file_id, o.start_line AS line,
RANK() OVER (PARTITION BY o.rowid
ORDER BY dr.end_line - dr.start_line) AS rn
FROM occurrences o
JOIN def_ranges dr ON dr.file_id = o.file_id
AND o.start_line BETWEEN dr.start_line AND dr.end_line
JOIN symbols callee ON callee.id = o.symbol_id AND callee.symbol LIKE '%).'
JOIN symbols caller ON caller.id = dr.symbol_id AND caller.symbol LIKE '%).'
WHERE (o.roles & 1) = 0 AND dr.symbol_id <> o.symbol_id
) WHERE rn = 1;
```
`def_ranges` 来自 SCIP `enclosing_range`(如果可用)来自 scip-java,否则来自 tree-sitter 函数定义节点(scip-clang 不发出)- 无论如何语法正确。查询时查找只是普通的索引扫描;事务查询(`impact`)是递归 CTE。
查询必须处理的另一个细微差别:SCIP 在**两个方向上发出方法重写关系行,具有相同的标志**,因此“哪一侧是接口”通过父类的类型级(`is_reference = 0`)超类型闭包恢复 - 该实现保留接口的 `implemented_by` 列表,接口的 impact 中保留实现,并阻止路径从实现跳转到接口 → 实施 → 兄弟实现。
## 跨语言桥
Java 和 C++ 永远不会相互引用符号,因此图谱自然会分成不相连的组件。`bridges` 表(`src`,`dst`,`kind`,`evidence`)将它们重新连接:
- **`thrift`** - 声明式:双方生成的代码从相同的 IDL 下降,因此 `(service, method)` 名称相等,将 Java `Svc#Client#m()` 连接到 C++ `SvcIf#m()` 等,反之亦然。仅当目标侧有实际(非生成,非测试)实现时发出。
- **`brpc`** - 声明式:现代 FE→BE 执行路径(protobuf RPC)对双方索引不可见 - FE 使用 jprotobuf 注解存根,BE 实现 `PInternalServiceImplBase`/`LakeServiceImpl`。通过方法名称连接:FE camelCase 减去 `Async` 后缀等于 BE 蛇形案例(`execPlanFragmentAsync` ↔ `exec_plan_fragment`)。
- **`thrift-lex`** - 词法:scip-clang 不发出 `ClientConnection<T>::operator->`(主要的 RPC 习惯用法)的引用,因此扫描器匹配 `client->method(` 站点,验证方法与服务接口表,并将调用归属到封闭函数。
- **`jni`** - 词法:C++ 中的 Java 类名字符串字面量(如 `FindClass` 样式)与相应的 Java 类符号链接。
Thrift/JNI 扫描器专门针对 StarRocks RPC 习惯用法,但模式 - 通过生成的存根上的声明式名称连接加上对索引器遗漏的习惯用法的狭窄词法传递 - 转移到任何多语言代码库。查询端,遍历简单地使用 `calls UNION ALL bridges`。
## 数据库模式
核心表(由 `scip_loader.py` 编写):
| 表 | 内容 |
|---|---|
| `meta` | 索引器名称/版本,项目根,**索引提交 SHA** |
| `files` | 路径,语言 |
| `symbols` | SCIP 符号字符串,显示名称,种类,父节点(包含),定义站点,文档,签名,`is_external` |
| `occurrences` | 每个定义/引用的确切行/列和 SCIP 角色位掩码 |
| `def_ranges` | 每个定义的完整体范围(调用图的骨架) |
| `relationships` | `is_implementation` / `is_reference` / `is_type_definition` / `is_definition`(唯一索引;摄入时重复数据删除) |
| `calls` | 派生的调用者 → 被调用者边与调用站点位置 |
| `bridges` | 跨语言边与种类和证据 |
派生表(`kg_enrich.sql`,~10 秒内重建):`symbol_metrics`(扇入/出/引用计数),`file_deps`,`dir_deps`,`symbols_fts`(FTS5 名称搜索)。
自包含和丰富表:
| 表 | 编写者 | 内容 |
|---|---|---|
| `file_text` | `snapshot_text.py` | 每个文件的 zlib 块在构建时 + 分辨率存储库相对路径(`read_definition`/`analyze_stack`/`find_message` 读取此文件,而不是文件系统) |
| `snapshot_meta` | `snapshot_text.py` | `version_label`(注册键),快照时间戳 |
| `messages` (+`messages_fts`) | `extract_messages.py` | 每个日志/错误/异常模板与种类、接收器、严重性、源函数;通过 `occurrences` 展开命名常量 |
| `symbol_changes` | `kg_changes.py` | 每个符号的 PR 归属超过 `<kg-commit>..<newer-release>` 范围(一个端点必须是索引提交;向前范围追加版本) |
## 已知限制 & 设计笔记
1. **仅完整重建.** 没有 SCIP 索引器支持增量更新;每个提交工件模型都采用这种方式。
2. **scip-clang ≤ 0.4.0 补丁**,两者都补丁:没有 `enclosing_range`(上游问题 #323 关闭 *not planned*;PR #504 未合并)→ tree-sitter 补丁;`operator->` 成员调用的引用缺失 → 词法桥接传递 |
3. **支持两种 SCIP 范围编码** - 旧的 `repeated int32` 和新的 `typed_range`/`typed_enclosing_range` oneofs(typed wins)- 因为当前的 scip-java 只发出 typed 形式 |
4. **`SELECT DISTINCT` 在调用连接中是负载承载的**:scip-clang 发出重复的定义出现,否则将产生约 25% 的重复边。关系行在摄入时额外去重,并在唯一索引下进行去重,以便回归失败而不是静默重复。 |
5. **SCIP `local N` 符号在所有地方都被跳过** - 它们按规范是文档作用域的;全局 intern 会合并无关的匿名类到奇异节点 (`tools/purge_local_symbols.py` 改造旧数据库)|
6. **静态超近似值,按设计**。 接口方法的调用者被报告为每个实现的潜在调用者 - 影响分析的正确偏差。 |
7. **词法桥和消息提取是启发式的。** 每个桥行都有一个 `evidence` 字符串,每个消息行都有一个接收器;无法解析的动态消息表现为 `partial`/`miss`,而不是制造的来源。 |
8. `probe_index.py` / `probe_bridge.py` 健全性检查任何新索引:封闭范围覆盖,符号方案,关系计数,桥锚。
## 可选:分析仓库导出
为了进行团队范围的分析(无效代码扫描、依赖关系仪表盘),可以将相同的图导出到OLAP仓库:`kg_ddl.sql` + `export_kg.py` + `load_sr.sh` 通过Stream Load加载到StarRocks集群中。这是严格可选的,并且次于SQLite制品 — 注意大多数OLAP引擎缺乏递归CTE,因此其中的传递查询需要有界自连接或客户端循环。
## 仓库布局
```
build_kg.sh 协调器:索引 → 加载 → 补丁 → 桥梁 → 丰富 → 快照 → 消息 → 发布
kg_publish.py 验证(快速检查/表格/计数)+ 清单戳记 + 原子发布
scip_loader.py SCIP protobuf → SQLite(流式 mmap 解析器,纯 ANSI INSERT)
be_kg_patch.py C++ def_ranges 通过 tree-sitter + 调用重新物化
thrift_bridges.sql 声明式 thrift 桥梁
bridge_patch.py 词法 thrift + JNI + brpc 桥梁
kg_enrich.sql 指标 / 文件依赖 / 目录依赖 / 全文搜索
snapshot_text.py zlib 源快照到数据库(自包含)
extract_messages.py 日志/错误/异常模板索引(查找消息后端)
kg_changes.py 按符号的 PR 历史记录 <kg-commit>..<release> (符号历史后端)
kg_mcp_server.py 标准输入输出 MCP 服务器(10 个工具,多版本通过 KG_DIR)
test_kg_mcp.py 协议级烟雾测试(驱动服务器作为真正的 MCP 客户端)
probe_index.py 新索引兼容性探测
probe_bridge.py 桥梁锚点探测
tools/ build_version_kg.sh(历史版本一次性),republish_enrich.sh
(丰富 SERVED 数据库:复制 → 丰富 → 验证 → 原子替换),
kg_health.sh(发布后舰队检查),gen_compile_commands.sh,
purge_local_symbols.py + rematerialize_calls.py(旧数据库改造),kg-ab
eval/ A/B 评估框架:cases/,run_eval.sh,score.py,anatomy.py
skills/code-kg/ 代理面向的技能卡(路由 + 成本纪律)
docs/ 设计笔记(v2 自包含多版本)
kg_ddl.sql 可选仓库 DDL
export_kg.py 可选仓库导出(TSV + JSONL)
load_sr.sh 可选仓库 Stream Load
scip.proto 来自 scip-code/scip(Apache-2.0),为了解析稳定性而固定
```
## 致谢
- [SCIP](https://github.com/scip-code/scip) 及其索引器 ([scip-java](https://github.com/scip-code/scip-java),[scip-clang](https://github.com/sourcegraph/scip-clang)) — Apache-2.0。
- 调用图派生通过封闭范围连接遵循 Meta 的 Glean 和其他 SCIP 消费者的做法。
MCP 配置
以下是此 MCP Server 的配置信息,可直接复制到 Cursor 或其他 MCP 客户端中使用。
mcp.json
连接信息
猜你喜欢
暂无推荐服务器