首页
/ Graphify 跨仓库知识图谱:GitHub 仓库克隆与多仓库图合并(clone / merge-graphs)实战指南

Graphify 跨仓库知识图谱:GitHub 仓库克隆与多仓库图合并(clone / merge-graphs)实战指南

2026-09-06 13:52:14作者:伍希望

本文基于 graphify 官方技能参考文档 graphify/skills/claw/references/github-and-merge.md,完整讲解 graphify 处理两类"多来源"输入的标准流程:一是用户传入一个或多个 GitHub 仓库 URL,二是用户指定多个本地子目录(monorepo 或多服务布局)需要合并进同一张知识图谱。读完后,你将掌握 graphify clone 的缓存与复用机制、graphify extract 的输出位置差异、graphify merge-graphs 的节点前缀/社区偏移/超边合并等底层实现细节,并知道合并完成后如何直接走 graphify query 快速路径。

适用场景:什么时候加载这份参考

该参考文档是 graphify 多平台技能(Claw 及其他平台同构)的按需加载片段,其触发条件在文档开头写得很明确:

  • 用户传入了一个或多个 https://github.com/... 形式的 URL;
  • 用户命名了若干本地子目录,要求合并进一张图(monorepo、多服务布局)。

也就是说,它覆盖三种典型拓扑:单仓库多远程仓库多本地子目录。后文按此三种场景展开,并在每个场景之后附上仓库源码中的实现证据。

Step 0:graphify clone —— 克隆 GitHub 仓库

单仓库

LOCAL_PATH=$(graphify clone <github-url> [--branch <branch>])
# Use LOCAL_PATH as the target for all subsequent steps

graphify clone 的最终行为是把本地路径打印到 stdout,因此可以直接用 $(...) 捕获,作为后续所有步骤(extract、query 等)的目标路径。

多仓库(跨仓库图)

# Clone each repo, run the full pipeline on each, then merge
graphify clone <url1>   # → ~/.graphify/repos/<owner1>/<repo1>
graphify clone <url2>   # → ~/.graphify/repos/<owner2>/<repo2>
# Run /graphify on each local path to produce their graph.json files
# Then merge:
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

要点(原文档明确给出的约定):

  • 克隆目标目录固定为 ~/.graphify/repos/<owner>/<repo>
  • 重复运行同一 URL 时复用已有克隆(走 git pull 而非重新 clone);
  • 合并图中每个节点都带有 repo 属性,可按来源仓库过滤。

源码印证:_clone_repo 的真实行为

上述每一条约定都能在 graphify/cli.py_clone_repo() 中找到对应实现:

  • URL 归一化:若 URL 不以 .git 结尾会自动补上(git_url = url + ".git"),最终传给 git 的是补全后的地址;
  • 仓库识别:用正则 github\.com://([^/]+?) 从 URL 提取 owner/repo,不识别的地址直接报错退出(error: not a recognised GitHub URL)——即 clone 只接受 GitHub 地址;
  • 缓存复用:默认目标是 Path.home() / ".graphify" / "repos" / owner / repo。若该目录已存在,则执行 git -C <dest> pull(带 --branch 时为 git pull origin -- <branch>),并打印 Repo already cloned at ... - pulling latest...;否则执行 git clone --depth 1 [--branch <branch>] -- <git_url> <dest>,即浅克隆(depth 1),只拉取最新快照;
  • branch 安全校验:以 - 开头的 branch 名会被直接拒绝,防止参数注入式误用;
  • --out 覆盖:CLI 分发层(graphify/cli.py)支持 graphify clone <github-url> [--branch <branch>] [--out <dir>],显式传入 --out 时克隆目录可脱离 ~/.graphify/repos 缓存体系。

clone 命令最后执行 print(local_path),这解释了为什么参考文档中要用 LOCAL_PATH=$(graphify clone ...) 捕获输出:stdout 上的最后一行就是可供后续步骤直接引用的本地路径。

多个本地子目录:为什么必须绕过技能流水线直接走 CLI

这是该参考文档中最容易踩坑的一段。原文档给出的解释是:

The skill pipeline writes all intermediate and final outputs to graphify-out/ in the current working directory. Running the skill on each subfolder separately will clobber the same output dir.

也就是说,/graphify 技能流水线把所有中间产物和最终 graph.json 都写到当前工作目录下的 graphify-out/。如果在同一目录下对每个子目录分别跑技能流水线,后面的运行会覆盖前面的输出。正确做法是直接用 CLI 对每个子目录执行 graphify extract——CLI 会把 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
# Add --backend gemini|kimi|openai|deepseek|claude-cli depending on which API key you have set

# Then merge at the project root:
graphify merge-graphs \
  ./core/graphify-out/graph.json \
  ./service/graphify-out/graph.json \
  ./platform/graphify-out/graph.json \
  --out graphify-out/graph.json

关于 --backend 参数:extract 会按你实际配置了哪个 API key 选择 LLM 后端。参考文档给出的取值集合是 gemini|kimi|openai|deepseek|claude-cli;从 CLI 自身的用法提示(graphify/cli.py)看,graphify extract--backend 还支持 claude|openai|deepseek|ollama 等选项,二者可结合本地环境变量按需选用。若只关心确定性 AST 结果而不需要 LLM 语义标注,也可以不带 --backend 走 code-only 路径。

merge-graphs 底层机制:前缀、标签、社区偏移与超边

graphify merge-graphs <graph1.json> <graph2.json> [...] [--out merged.json] 要求至少两个输入图--out 缺省时默认写到 graphify-out/merged-graph.json。这段处理逻辑位于 graphify/cli.py,它远不止一次简单的 nx.compose,而是一系列针对"多来源图合并"痛点的防御性处理:

1. 输入归一化:类型、方向、旧格式兼容

  • 有向/无向、多重/简单图混合输入:不同提取路径在不同时间写出的 per-repo graph.jsondirected / multigraph 标志可能不一致,而 nx.compose 要求所有输入同类型。merge 处理器会先把所有输入规范化为无向简单图(跨仓库合并视图本身就是无向的),避免崩溃(对应测试 tests/test_merge_graphs_cli.pytest_merge_graphs_mixed_directed_and_multigraph);
  • 边方向保护node_link_graph 以无向方式加载时会丢失方向。merge 在加载前为每条 link 暂存 _src / _tgt 方向标记,写出时再还原为 source / target,保证 imports_from 等关系不会被翻转或变成自环(测试 test_merge_graphs_preserves_import_edge_direction 专门锁定了这一行为);
  • 旧格式兼容:若输入用 edges 而非 links 作为边键(更旧的运行产物),会先做键名归一化再加载;
  • 超边双槽位读取graph.json 的超边可能存放在顶层 hyperedges 键,也可能嵌套在 graph.hyperedges 槽位中。node_link_graph 只还原后者,所以 merge 会对只带顶层键的输入做兜底回填。

2. 节点前缀与 repo 标签:distinct_repo_tags + prefix_graph_for_global

参考文档说"每个节点带 repo 属性可按来源过滤",其实现链条是:

  • distinct_repo_tags()graphify/build.py)为每个输入图生成唯一且可读的仓库标签,取 graphify-out 的父目录名(即仓库目录名)。关键防御是:朴素目录名在多个输入间会撞车——例如 src/graphify-outfrontend/src/graphify-out 都会得到标签 src,若不加处理,两边同名的裸节点(如后端的 app.js 与前端的 App.jsx)会被 nx.compose 静默合并成同一实体,凭空造出跨运行时的边。发现碰撞后标签会加宽为 父目录_目录名,仍有重复再追加序号后缀,确保两两不同(测试 test_merge_graphs_same_named_repo_dirs_do_not_collapsetest_distinct_repo_tags_unit 覆盖此行为);
  • prefix_graph_for_global()graphify/build.py)拿到标签后,把该图所有节点 ID 改写为 repo_tag::原ID(label 保持不变用于展示),并在每个节点上写入:
    • repo:仓库标签——这就是参考文档所说"可按来源过滤"的直接依据;
    • local_id:原始 ID,便于回溯;
    • 边的 _src / _tgt 同步改写为新 ID。

3. 社区 ID 偏移:防止跨仓库社区融合

每个输入图的社区编号都从 0 开始,若原样带入合并图,仓库 A 的 community 0 与仓库 B 的 community 0 会撞号,聚合社区视图会把两个无关社区融合成一个元节点。merge 因此对每个输入做累加偏移:第一个输入保持原 ID(offset 0),其后每个输入的偏移量 = 前面所有输入的最大社区 ID + 1;原始 ID 保留在节点的 local_community 属性中。测试 test_merge_graphs_offsets_communities_so_repos_do_not_fuse 验证了"alpha 社区 {0,1} 与 beta 偏移后社区 {2,3,4} 互不相交",并且同一输入顺序下两次合并产出字节级一致的结果(test_merge_graphs_community_offset_is_byte_reproducible)。

4. 超边(hyperedge)的收集、改写与去重

nx.compose 合并图属性用的是 dict.update,会覆盖先前输入累积的超边列表——若不做处理,最终只剩最后一个输入的超边。merge 的实现是:逐输入收集"已加前缀的超边"到统一列表,compose 完成后再清空 merged.graph["hyperedges"],通过 attach_hyperedges 从干净状态按 id 去重并整体挂载;同时超边成员 ID 在 prefix_graph_for_global 内被同一张 relabel 表改写为前缀后 ID,超边自身 ID 也加前缀以防同名超边跨仓库碰撞。输出时顶层与 graph 嵌套两个槽位都写入,与其他 writer 保持同一"双槽位"形状(测试 test_merge_graphs_carries_hyperedges_from_all_inputstest_merge_graphs_hyperedges_dedup_on_shared_prefixed_id 锁定该契约)。

5. 跨仓库同名类型链接:same_type_as

合并后,两个仓库各自声明的同一契约类型会成为两个不相连的节点(因为所有 ID 都带前缀)。在消息总线式代码库中,这恰恰是最有价值的跳转点:生产者在仓库 A 引用 SyncProductUpsertToSearchEvent,消费者在仓库 B 实现 IConsumer<SyncProductUpsertToSearchEvent>,合并图却没有路径从一方走到另一方。

为此,merge 会调用 link_shared_type_declarations()graphify/cross_repo_types.py):把**同时满足"有 namespace + 有 label + 带 repo 标签"**的类型声明节点按 (namespace, label) 分组,若某组横跨至少两个仓库,则组内不同仓库的每对节点之间补一条 relation="same_type_as" 的边(confidence="INFERRED", confidence_score=0.9)。模块 docstring 解释了为什么要求 namespace:仅同名不够,否则两个恰好取了相同短名的类也会被错误关联——文档给出的实测例子是一对 .NET 服务(分别声明 1440 与 262 个类型),"namespace+name" 匹配只产生 7 对,全部是真正共享的 EventManager.Models.*Event 契约。设计取舍也很明确:只加边、不并节点,因为两个仓库持有的契约副本可能已经漂移,合并节点会掩盖这种漂移,而一条链接既能支撑跨仓库遍历,又保留各自成员与来源信息。

6. 规模上限

每个输入图在加载前都要通过 _enforce_graph_size_cap_or_exit(),合并驱动的节点上限为 100,000graphify/cli.py_MERGE_MAX_NODES = 100_000);超限会中止并报错,避免生成不可查询的巨型合并图。

合并之后的快速路径:直接 query,不再重复提取

参考文档的最后一句定义了合并产物 graphify-out/graph.json 存在后的运行模式:

Once graphify-out/graph.json exists, the fast path above takes over: any codebase question runs graphify query directly on the merged graph — no re-extraction, no size gate.

含义是:合并图一旦落盘,后续任何代码库问题都直接对该合并图执行 graphify query(BFS 默认 / --dfs 追踪链路等遍历模式见同目录的 graphify/skills/claw/references/query.md),不再触发重新提取,也不受"超大图需先增量"这类前置门限约束。对跨仓库场景,这意味着一次 extract + merge 的投入可以换成长期可复用的查询资产:按 repo 属性过滤单仓库子图、沿 imports_from / same_type_as 边做跨仓库溯源,都发生在同一份 JSON 上。

实操要点与适用边界

结合参考文档与源码实现,实操时建议记住以下约束:

场景 正确做法 依据
传入 GitHub URL graphify clone,捕获 stdout 的 LOCAL_PATH 作为后续目标 graphify/cli.py
重复克隆同一 URL 无需手动管理,自动走 git pull 复用 ~/.graphify/repos/<owner>/<repo> 缓存 graphify/cli.py
monorepo / 多服务子目录 每个子目录单独 graphify extract <subdir>不要在根目录对每个子目录重复跑技能流水线(会互相覆盖 CWD 下的 graphify-out/ 参考文档原文
需要 LLM 语义标注 按已配置的 API key 追加 --backend(如 geminiopenaideepseekclaude-cli 等) 参考文档 + graphify/cli.py
合并两个以上图 graphify merge-graphs 要求 ≥2 个输入;--out 缺省为 graphify-out/merged-graph.json graphify/cli.py
单输入图规模 超过 10 万节点会在加载前中止 graphify/cli.py
同名仓库目录 无需担心,标签自动加宽(父目录_目录名)或追加序号保证唯一 graphify/build.py

适用前提与限制:graphify clone 只识别 GitHub 地址(正则限定 github.com[:/]/<owner>/<repo>),其他托管平台需自行先克隆到本地、再走 extract + merge-graphs 流程;合并图被规范化为无向简单图,输入中的有向性通过 _src/_tgt 标记在序列化层还原,而非保留为有向图结构;社区偏移量按输入顺序分配,固定顺序下结果字节级可复现,但改变输入顺序可能导致社区编号变化。

参考路径汇总

登录后查看全文
热门项目推荐
相关项目推荐