首页
/ graphify 实战:用 clone 与 merge-graphs 构建跨仓库知识图谱

graphify 实战:用 clone 与 merge-graphs 构建跨仓库知识图谱

2026-09-06 12:55:41作者:侯霆垣

本文围绕 graphify 的 /graphify skill 参考文档 GitHub clone and cross-repo merge 展开,讲解如何用 graphify clone 拉取远程仓库、用 graphify merge-graphs 把多个仓库或多个本地子目录的知识图谱合并成一张可查询的跨仓库图谱,并结合 cli.pybuild.py 的源码,说明节点前缀、社区 ID 偏移、同名目录冲突等合并细节的真实实现,读完即可独立完成"多仓库 / monorepo 图谱合并 + 合并后查询"的完整工作流。

适用场景

该参考文档对应两类典型输入:

  • 用户给出一个或多个 https://github.com/... URL,需要先克隆再建图;
  • 用户指定多个本地子目录(monorepo 或 multi-service 布局),希望把它们合并进同一张图。

两种场景最终都落在同一条路径上:先为每个代码库单独产出 graph.json,再用 merge-graphs 合并,之后所有代码库问题直接对合并图执行 graphify query——无需重新抽取,也没有体积门槛(fast path)。

Step 0:用 graphify clone 克隆 GitHub 仓库

单仓库

LOCAL_PATH=$(graphify clone <github-url> [--branch <branch>])
# 后续所有步骤都以 LOCAL_PATH 作为扫描目标

多仓库(跨仓库图谱)

# 分别克隆,各自跑完整 pipeline,再合并
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

默认克隆目录是 ~/.graphify/repos/<owner>/<repo>,重复运行时直接复用已有克隆。源码中 clone 子命令的完整签名是:

graphify clone <github-url> [--branch <branch>] [--out <dir>]

源码实现:浅克隆 + 缓存复用

实现位于 cli.py 的 _clone_repo,几个关键行为可以从源码直接确认:

  • URL 规范化:自动补全 .git 后缀,并用正则从 URL 中解析出 ownerrepo;不是可识别的 GitHub URL 会直接报错退出。
  • 浅克隆:首次克隆执行 git clone --depth 1,指定分支时追加 --branch <branch>,以控制缓存体积;分支名以 - 开头会被拒绝,避免被误认为选项。
  • 缓存复用:目标目录已存在时,不再克隆,而是执行 git pull(带分支时为 git pull origin -- <branch>),pull 失败只打警告不中断流程;不存在时执行浅克隆,失败则退出并输出 git 的 stderr。
  • 自定义目录--out <dir> 可覆盖默认的 ~/.graphify/repos/<owner>/<repo> 位置。
  • 命令最后会把本地路径打印到 stdout,所以文档里 LOCAL_PATH=$(graphify clone ...) 的 shell 捕获写法成立,供后续步骤直接引用。

跨仓库合并:merge-graphs 的工作方式

merge-graphs 接收两个及以上的 graph.json 路径,可选 --out 指定输出位置,实现位于 cli.py。除了把几张图拼在一起,它还处理了若干跨仓库合并特有的"脏活",这些都是从源码注释和对应测试中确认的行为:

1. 节点 ID 加仓库前缀,并记录 repo 属性

合并时每个输入图都会经过 build.py 的 prefix_graph_for_global

  • 所有节点 ID 被改写为 repo_tag::<原ID>,边及其方向属性 _src/_tgt 同步重写;
  • 每个节点写入 repo 属性(值为该图的 repo tag),这正是文档所说"每个节点携带 repo 属性,可按来源过滤"的实现来源;
  • 原 ID 保留在 local_id 属性中,方便回溯;label 保持不变用于展示。

2. 同名目录冲突:repo tag 去重

若两张图各自位于同名目录下(例如 src/graphify-outfrontend/src/graphify-out 朴素取目录名都得到 src),前缀相同的不同节点会被 nx.compose 悄悄合并、产生虚假的跨仓库边(上游 issue #1729)。distinct_repo_tags 专门解决这个问题:标签冲突时先用"父目录_目录名"展宽(如 frontend_src),再追加序号后缀保证唯一,并打印一行 note 提示。回归测试见 test_merge_graphs_cli.pytest_merge_graphs_same_named_repo_dirs_do_not_collapse

3. 社区 ID 偏移,避免跨仓库社区互相吞并

每个输入图的 community 都是从 0 开始编号的,直接携带合并会让不同仓库的同号社区在聚合视图里融成一个元节点(#3014)。合并循环按输入顺序给每个图施加递增的 community_offset:第一个图偏移为 0,之后每个图的偏移取"当前最大值 + 1"。原社区 ID 保存在节点的 local_community 属性中,见 prefix_graph_for_global

4. 输入类型归一化、方向与超边保持

  • 各仓库的 graph.json 可能由不同抽取路径在不同时间写出,directed / multigraph 标志不一致会导致 nx.compose 崩溃(#1606);合并器把每个输入统一归一化为无向简单 Graph,测试 test_merge_graphs_mixed_directed_and_multigraph 用 DiGraph、Graph、MultiGraph 三个混合输入验证了这一点。
  • 加载时会把旧格式 edges 键归一化为 links(#738),并用 _src/_tgt 标记恢复原始边方向(#2261),写出前再还原回 source/target
  • 超边(hyperedges)在 compose 前被逐图收集、统一前缀重写(成员 ID 与超边 ID 都加 repo 前缀,见 build.py),合并后重新以"取并集 + 按 ID 去重"的方式挂回,避免只有最后一个输入的超边存活(#2484/#2485),并以双槽位(graph.hyperedges 与顶层 hyperedges)原子写出,保持与常规 graph.json 一致的形状。

5. 跨仓库共享类型的接线

合并后还会执行 cross_repo_types.pylink_shared_type_declarations:由于所有节点 ID 都带仓库前缀,两个仓库各自声明的同一契约类型会表现为两个互不相连的节点。该步骤对"namespace 与 name 都相同、且分属不同 repo"的类型声明之间添加 same_type_as 边——只加边、不合并节点,这样跨仓库遍历可以跨越边界,而两个可能已经发生漂移的副本仍各自保留成员与来源。模块 docstring 给出了一组实测数字:一对声明了 1440 与 262 个类型的 .NET 服务中,namespace+name 匹配只产生 7 对,全部是共享的 EventManager.Models.*Event 契约,说明该匹配相当保守。合并完成时终端会输出类似:

  linked 7 type declaration(s) shared across repos
Merged 2 graphs -> N nodes, M edges
Written to: graphify-out/cross-repo-graph.json

6. 参数速查

命令 参数 说明
graphify clone <url> --branch <b> 克隆指定分支;已有缓存时转为 git pull origin -- <b>
--out <dir> 覆盖默认克隆目录 ~/.graphify/repos/<owner>/<repo>
graphify merge-graphs g1 g2 [...] --out <file> 合并输出路径;不指定时默认为 graphify-out/merged-graph.json
位置参数 至少 2 个 graph.json,否则打印用法并退出;每个输入都会做体积上限检查

monorepo / 多服务本地子目录的合并方式

文档特别指出一个坑:/graphify skill 流水线把所有中间产物与最终产物写到当前工作目录graphify-out/。如果先 cd core/cd service/ 分别跑 skill,第二次运行会直接覆盖第一次的输出(clobber 同一个目录)。

正确做法是改用 CLI 逐个子目录抽取——graphify extract 会把 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
# --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

注意这条路径与"多 GitHub 仓库"路径共享同一个合并器,因此上述前缀、社区偏移、类型接线等机制同样生效;由于各子目录名字天然不同,distinct_repo_tags 取到的标签也自然可区分。

合并之后的快速路径:直接查询

一旦 graphify-out/graph.json(跨仓库合并图)存在,后续任何代码库问题都走 fast path:直接对合并图执行 graphify query,不需要重新抽取,也不受体积门槛限制。日常可用的几个动作:

  • 按来源过滤:合并图每个节点带 repo 属性,可据此只关注某一仓库/子目录的节点;节点上的 local_id 可还原该节点在原始图中的 ID。
  • 跨仓库追溯:共享契约类型之间有 same_type_as 边,从生产者仓库的节点可以一步跳到消费者仓库中的同型声明。
  • 再合并/扩展:新增一个子仓库时,只需对其单独 graphify extract,再用 merge-graphs 把旧合并图与新图重新合并即可。

小结

这条参考文档给出的是 graphify 处理"不止一个代码库"场景的标准配方:clone(浅克隆 + 目录缓存 + pull 复用)负责把远程仓库落到 ~/.graphify/repos/<owner>/<repo>;对每个库独立跑 pipeline 得到各自 graph.jsonmerge-graphs 负责合并,并在底层完成节点 ID 前缀化(repo 属性、local_id 回溯)、repo tag 去重、社区 ID 偏移、图类型归一化、边方向恢复、超边并集重建与跨仓库共享类型接线。本地多子目录则用 graphify extract <dir>/ 规避 skill 流水线的固定输出目录冲突。合并图落盘后,graphify query 的 fast path 让所有跨仓库问题无需再抽取即可回答。相关实现与回归测试可分别参见 cli.pybuild.pycross_repo_types.py 以及 test_merge_graphs_cli.pytest_cross_repo_shared_types.py

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