graphify跨文件符号解析全解:从import语句到calls边的旅程
【免费下载链接】graphifyTurn any codebase, with its docs, SQL schemas, configs, and PDFs, into a queryable knowledge graph. A /graphify skill for Claude Code, Cursor, Codex, and Gemini CLI: local deterministic AST parsing, every edge explained, no vector store.项目地址: https://gitcode.com/GitHub_Trending/graph/graphify
graphify是一个把代码库变成知识图谱的开源工具:它对代码做本地 AST 解析(不依赖 LLM、不用向量库),把每个import语句和函数调用解析成图谱里的imports边与calls边。本文带你走一遍 graphify 的跨文件符号解析机制——一条裸调用如何从源码出发,最终变成一条带置信度标签的calls边。
30秒上手:graphify是什么?
graphify 通过/graphify技能接入 Claude Code、Cursor、Codex、Gemini CLI 等 AI 编程助手。安装只需两条命令:
uv tool install graphifyy # 安装 CLI(或 pipx install graphifyy) graphify install # 把技能注册到你的 AI 助手然后在 AI 助手里输入/graphify .,得到三个产物:
graph.html— 浏览器里可点击、可搜索的交互图谱GRAPH_REPORT.md— 高亮报告:关键概念、 surprising 连接、建议问题graph.json— 完整图谱,随时查询,无需重读源码
它的第一遍处理(Pass 1)完全本地运行:tree-sitter 解析代码,提取类、函数、导入和调用关系,不花一分钱 token。整个过程详见 docs/how-it-works.md。
旅程第一站:import 语句变成图谱的 imports 边
代码解析阶段,每个文件的import/from ... import ...语句会被确定性提取,生成文件级imports/imports_from边,置信度直接标记为EXTRACTED(源码中明确存在,confidence 恒为 1.0)。
这些边不只是"文件依赖"记录,它们还是后续解析calls边的关键证据——这正是"从 import 语句到 calls 边"这条旅程的核心线索。
旅程第二站:裸调用如何被保守解析成 calls 边
单文件解析时,graphify 先把每个调用点记为raw_calls(只记录被调用的名字、调用者和位置,先不判断它指向哪个文件)。所有文件解析完毕后,跨文件解析才开始。核心逻辑在 graphify/symbol_resolution.py 中:
保守标签索引:宁可少连,不错连
build_label_index 先把所有代码节点建索引(标签归一化:去括号、去点、转小写),且只允许file_type == "code"的节点参与匹配——文档标题恰好和函数同名时,绝不会被误认为调用目标。
然后 resolve_cross_file_raw_calls 对每个裸调用按规则处理:
| 情况 | 处理 | 结果 |
|---|---|---|
| 同名候选恰好 1 个 | 直接连线 | INFERRED边,置信度 0.8 |
| 同名候选多个 | 走消歧:优先排除测试目录 mock,再看路径邻近度 | 唯一幸存才连线,否则跳过 |
成员调用(obj.method()) | 跳过,交给语言专属解析器 | — |
这种"保守"哲学是 graphify 图谱可信的来源:没有把握的连接宁可不建,而不是猜一个。
import 证据引导:把 INFERRED 升级为 EXTRACTED
如果调用名来自显式导入,解析会更精确。parse_python_import_aliases 会解析文件顶层的from helper import transform as tx形式(刻意忽略函数内局部导入——作用域信息不足以安全匹配),再结合(模块名, 符号名)二维索引定位目标。
命中后,resolve_python_import_guided_calls 发出的calls边置信度直接是EXTRACTED(1.0)——因为 import 语句本身就是白纸黑字的证据。TypeScript/JS 的import { foo }、Kotlin 的 FQN 导入同理,导入边越明确,calls边越可信。
旅程第三站:成员调用交给语言专属解析器
ClassName.method()这类带接收者的调用,裸标签索引无法处理(同名方法太多),graphify 用注册器模式解决:
graphify/resolver_registry.py 定义了LanguageResolver(名称 + 文件后缀 + 解析函数),run_language_resolvers 按"语料里出现了哪些后缀"决定激活哪些解析器,且单个解析器出错只记警告、不中断整体构建。
在 graphify/extract.py 尾部,各语言依次注册自己的一等公民:
python_member_calls、typescript_member_calls、ruby_member_callsswift_member_calls、csharp_member_calls、java_member_callscpp_member_calls、objc_member_calls、pascal_inherited_calls、kotlin_qualified_calls
想为新语言加跨文件解析?只需注册一个LanguageResolver,不用改动提取主流程——这就是注册器模式的扩展点设计。
如何验证解析质量:explain 与 path
图谱建好后不用读文件,直接问图谱(真实输出示例):
$ graphify explain "APIRouter" Node: APIRouter Source: routing.py L2210 Degree: 47 Connections (47): --> RequestValidationError [uses] [INFERRED] <-- __init__.py [imports] [EXTRACTED] $ graphify path "FastAPI" "ModelField" Shortest path (3 hops): FastAPI --uses--> DefaultPlaceholder <--references-- get_request_handler() --references--> ModelField每条边都带标签,你能一眼分清"源码里明写的"和"graphify 推理出来的"——这个置信度体系(EXTRACTED/INFERRED/AMBIGUOUS三档 + 离散分数评分标准)完整说明见 docs/how-it-works.md。
小结:一次确定性的符号之旅 🧭
| 阶段 | 做了什么 | 边的置信度 |
|---|---|---|
| AST 提取 | import 语句 → imports 边 | EXTRACTED (1.0) |
| import 引导解析 | 导入证据绑定裸调用 → calls 边 | EXTRACTED (1.0) |
| 保守标签解析 | 唯一候选 → calls 边 | INFERRED (0.8) |
| 语言解析器 | 成员调用按类型/框架约定解析 | 视证据而定 |
graphify 的跨文件符号解析没有魔法:本地 AST 保证确定性,import 证据保证可信度,保守原则保证不胡连。最终你得到的是一个每条边都能解释来源的知识图谱——查问题、追调用路径、理解陌生代码库,都从这里开始。
【免费下载链接】graphifyTurn any codebase, with its docs, SQL schemas, configs, and PDFs, into a queryable knowledge graph. A /graphify skill for Claude Code, Cursor, Codex, and Gemini CLI: local deterministic AST parsing, every edge explained, no vector store.项目地址: https://gitcode.com/GitHub_Trending/graph/graphify
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考