graphify 跨仓库图谱实战:clone、多仓库 graph.json 合并与 repo 溯源机制详解
【免费下载链接】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 的官方 skill 参考文档 github-and-merge.md 展开,讲清两个高频场景的完整操作流程:一是用graphify clone把 GitHub 仓库拉到本地缓存并建立知识图谱,二是用graphify merge-graphs把多个仓库、多个本地子目录的graph.json合并成一张可查询的跨仓库图谱。读完本文,你可以独立完成多仓库/多服务代码库的统一图谱构建,并理解合并过程中节点前缀、repo属性、社区 ID 偏移、跨仓库共享类型链接等底层机制,从而正确使用graphify query对合并图进行查询。
适用场景:何时加载这条参考流程
原参考文档的触发条件很明确:当用户给出一条或多条https://github.com/...URL,或者点名要把若干本地子目录合并进一张图时,走本文的流程。它覆盖三种输入形态:
- 单个 GitHub 仓库:clone 后作为后续所有步骤的目标路径;
- 多个 GitHub 仓库:分别 clone、分别跑完整流水线、最后合并成一张跨仓库图;
- 多个本地子目录(monorepo 或多服务布局):分别对每个子目录抽取,再在项目根合并。
三种形态最终都收敛到同一个产物:一个(或多个)graph.json加一次merge-graphs合并,之后所有代码问题都直接对合并图执行graphify query。
Step 1:graphify clone —— 克隆 GitHub 仓库并复用本地缓存
单仓库克隆
LOCAL_PATH=$(graphify clone <github-url> [--branch <branch>]) # 用 LOCAL_PATH 作为后续所有步骤的目标路径clone子命令的完整用法为:
Usage: graphify clone <github-url> [--branch <branch>] [--out <dir>]各参数含义:
| 参数 | 作用 |
|---|---|
<github-url> | GitHub 仓库地址,https://github.com/<owner>/<repo>,可带或不带.git后缀 |
--branch <branch> | 指定分支;命令会校验分支名不能以-开头(防止被误解析为 git 选项) |
--out <dir> | 自定义克隆目标目录;缺省时使用全局缓存目录~/.graphify/repos/<owner>/<repo> |
命令执行成功后会把本地路径打印到 stdout,所以上面可以用LOCAL_PATH=$(...)直接捕获。
源码级实现:为什么重复执行不会重新 clone
clone的实现在 cli.py 的 _clone_repo,命令分发在 cli.py 的 clone 分支。从源码可以确认以下行为:
- URL 归一化:末尾的
/会被剥掉,没有.git后缀的地址会自动补上.git再用于git clone; - owner/repo 提取:用正则
github\.com://([^/]+?)解析出owner和repo,无法识别的 URL 直接报错退出; - 浅克隆:首次克隆使用
git clone --depth 1,只取最新一个 commit,下载量最小; - 缓存复用:默认落盘位置是
Path.home() / ".graphify" / "repos" / owner / repo。如果目标目录已存在,则不重新 clone,而是执行git -C <dest> pull(带--branch时追加origin -- <branch>)更新到最新,并向 stderr 输出 warning 而非直接失败; - 失败即退出:
git clone失败会打印完整 stderr 并以退出码 1 结束,便于脚本判断。
这意味着在 CI 或自动化流水线里反复对同一 URL 执行graphify clone是幂等的:首次浅克隆、后续增量 pull,缓存目录可以长期保留。
多仓库克隆:为跨仓库图谱做准备
参考文档给出的多仓库模式:
# 分别 clone 每个仓库,对每个仓库跑完整流水线,然后合并 graphify clone <url1> # → ~/.graphify/repos/<owner1>/<repo1> graphify clone <url2> # → ~/.graphify/repos/<owner2>/<repo2> # 对每个本地路径运行 /graphify 流水线,各自产出 graph.json # 然后合并: graphify merge-graphs \ ~/.graphify/repos/<owner1>/<repo1>/graphify-out/graph.json \ ~/.graphify/repos/<owner2>/<repo2>/graphify-out/graph.json \ --out graphify-out/cross-repo-graph.json这里的要点:
- 每个 clone 出来的仓库独立走一遍完整抽取流水线,各自在仓库根生成
graphify-out/graph.json; merge-graphs接收任意多个输入图(至少 2 个),--out指定合并产物路径;- 合并图中的每个节点都会带一个
repo属性,标明它来自哪个仓库,因此可以在后续查询、过滤、可视化中按来源仓库筛选。这一点在源码中对应 build.py 的 prefix_graph_for_global:每个节点 ID 被重写为<repo_tag>::<原ID>,同时写入repo属性,并保留local_id属性以便恢复合并前的原始 ID。
Step 2:多个本地子目录的合并(monorepo / 多服务布局)
这是参考文档里专门强调的一个坑:skill 流水线会把所有中间产物和最终产物写到当前工作目录下的graphify-out/。如果在 monorepo 根目录上分别对./core/、./service/、./platform/各跑一次 skill,三次的输出会互相覆盖(clobber 同一个输出目录)。
正确的做法是直接调用 CLI 的extract子命令逐子目录执行——CLI 会把graphify-out/写到被扫描路径内部(对应main.py 的 help 文本:--out DIR ... writes <DIR>/graphify-out/),各子目录互不干扰:
graphify extract ./core/ # → ./core/graphify-out/graph.json graphify extract ./service/ # → ./service/graphify-out/graph.json graphify extract ./platform/ # → ./platform/graphify-out/graph.json # 根据你实际配置了哪家的 API key,追加 --backend gemini|kimi|openai|deepseek|claude-cli然后在项目根执行合并:
graphify merge-graphs \ ./core/graphify-out/graph.json \ ./service/graphify-out/graph.json \ ./platform/graphify-out/graph.json \ --out graphify-out/graph.json参考文档最后指出:一旦graphify-out/graph.json存在,后续所有代码问题都可以直接对这个合并图跑graphify query——无需重新抽取,也不受体积门控(size gate)影响。
merge-graphs 底层机制:合并到底做了哪些事
这一节基于 cli.py 中 merge-graphs 的完整实现 与配套模块,说明合并命令的实际行为,帮助你预判输出与排查异常。
输入校验与体积保护
- 至少需要 2 个输入文件,否则打印用法并退出 1;
- 每个输入在解析前都会经过 _enforce_graph_size_cap_or_exit 的文件体积检查(委托
graphify.security.check_graph_file_size_cap),超大文件会被拒绝; - 合并完成后还有一道 10 万节点上限(
_MERGE_MAX_NODES = 100_000),超限直接中止合并。
输入图格式的容错
由于各仓库的graph.json可能由不同时期的抽取路径写出,实现做了多重兼容:
- edges/links 键归一:老版本可能用
edges存边,新版本写links,加载前会把edges映射为links(对应注释中引用的 #738); - 边方向保留:
node_link_graph会以无向图加载,丢失持久化的方向,因此在加载前给每条 link 注入_src/_tgt标记(#2261),写出时再恢复为真正的source/target; - hyperedges 双槽位:
graph.hyperedges与顶层hyperedges两个槽位都可能存放超边,加载时若图属性里缺失会回退读顶层键(#2484/#2485),写出时两个槽位都落盘,保证新旧读方都能拿到; - 图类型归一:
nx.compose要求所有输入图类型一致,而 DiGraph、Graph、MultiGraph 混用会直接崩溃(#1606)。合并前会把每个输入统一转成无向简单图nx.Graph——跨仓库合并视图本身就是无向的。tests/test_merge_graphs_cli.py 专门用 directed/multigraph/普通 Graph 三个混合输入验证了这一点:合并成功、输出为directed: false, multigraph: false,且三方节点全部保留。
仓库标签(repo tag):防止同名实体被静默合并
这是合并流程中最关键的正确性设计。每个输入图的所有节点 ID 都会被加前缀<repo_tag>::,标签由 distinct_repo_tags 生成:
- 朴素取法是用
graphify-out的父目录名作为 tag,但src/graphify-out和frontend/src/graphify-out会同时得到src——两个仓库里同名的节点(比如后端app.js与前端App.jsx都叫app)会共享src::app这个 ID,被nx.compose静默合并成一个节点,凭空制造出跨运行时边(#1729); - 因此当标签发生碰撞时,实现会先用
<父目录>_<目录名>拓宽(如frontend_src),仍不唯一则追加序号后缀,保证任何两个输入图的前缀绝不重叠,并向 stderr 打印note: repo dir names collide; using distinct tags: ...提示。 - tests/test_merge_graphs_cli.py 用
src/与frontend/src/两个同目录名输入验证了两个::app节点都存活。
标签本身即节点上的repo属性值——这就是参考文档所说"每个节点携带repo属性可按来源过滤"的具体落地。此外 build.py 还提供 prune_repo_from_graph,可原地移除某个repo标签下的全部节点,方便在合并图上做来源裁剪。
社区 ID 偏移:避免社区视图被"熔"成一个
每个输入图自己的社区编号都从 0 开始。若原样合并,两个仓库的 0 号社区会撞号,聚类的社区视图会把不相关的社区熔成一个大元节点(#3014)。合并实现为每个输入依次分配community_offset:第一个输入保持原编号(offset 0),后续输入的每个节点community值加上偏移量,并把原值记入local_community属性,使各仓库自身分区仍可从节点属性中还原(见 build.py prefix_graph_for_global 的文档字符串)。
跨仓库共享类型链接:same_type_as 边
跨仓库图中最有价值的"跳板"往往在契约类型上:一个仓库里的生产者引用事件类型SyncProductUpsertToSearchEvent,另一个仓库的消费者实现IConsumer<...>,但两边节点都带了 repo 前缀,默认是互不相连的两个节点。
为此合并流程末尾会调用 cross_repo_types.py 的 link_shared_type_declarations(#3007):
- 候选节点是"带
source_file、namespace 和 label 的类型声明节点"; - 只有当同一
(namespace, label)的声明跨越至少两个仓库时才成组,组内跨仓库的节点两两相连; - 添加的是边而非节点合并——
relation="same_type_as"、context="cross_repo"、confidence="INFERRED"、confidence_score=0.9。刻意保留两侧各自漂移的副本,让遍历可以跨界,同时不掩盖两个仓库契约可能已经不同步的事实; - 要求 namespace 相同是为了排除"仅仅短名撞车"的假匹配。该模块文档字符串引用了一组真实语料:一对共 1702 个声明类型的 .NET 服务里,namespace+name 匹配只产生 7 对边,全部是真正共享的
EventManager.Models.*Event契约。
合并成功时的输出形如:
linked 7 type declaration(s) shared across repos Merged 2 graphs -> 15320 nodes, 48211 edges Written to: graphify-out/cross-repo-graph.json验证与测试依据
- 混合图类型合并:tests/test_merge_graphs_cli.py 通过子进程真实调用
python -m graphify merge-graphs,验证 directed/undirected/multigraph 混合输入可正常合并; - 同名仓库目录碰撞:同文件后半段验证
src::app与frontend_src::app两个节点并存不被折叠; - 跨仓库共享类型:
graphify/cross_repo_types.py的注释与 tests 目录中的对应测试 覆盖了same_type_as边的生成条件; - 输出原子性:合并结果通过
graphify.paths.write_json_atomic原子落盘,避免中途崩溃留下半截 JSON。
使用边界与注意事项
- 环境依赖:
clone依赖本机git;无法识别非 GitHub 的 URL(正则只匹配github.com[:/]<owner>/<repo>); - 浅克隆限制:
--depth 1只保留一个 commit,历史无关;需要历史或子模块的场景可考虑--out到自定义目录后自行git fetch; - 合并体积上限:合并图超过 10 万节点会中止,单输入文件体积也受安全上限约束,超大单体仓库应先评估规模;
- 输出位置约定:skill 流程输出在当前工作目录的
graphify-out/,CLIextract输出在被扫描目录内部的graphify-out/,多子目录场景务必用后者避免互相覆盖; - 合并图是无向视图:合并会把输入归一为无向简单图,跨仓库视图里边的方向依赖持久化时的
_src/_tgt标记恢复; - 查询快速路径:合并图生成后,
graphify query直接以其为输入即可,无需再走抽取与体积门控。
关键路径速查
| 内容 | 路径 |
|---|---|
| 本文的参考文档(kilo skill 版) | github-and-merge.md |
| clone 子命令实现 | graphify/cli.py |
| merge-graphs 子命令实现 | graphify/cli.py |
| 节点前缀 / repo 属性 / 社区偏移 | graphify/build.py |
| 跨仓库共享类型链接 | graphify/cross_repo_types.py |
| 合并行为测试 | tests/test_merge_graphs_cli.py |
【免费下载链接】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),仅供参考