首页
/ graphify 跨仓库知识图谱实战:graphify clone 与 merge-graphs 从克隆到合并的完整链路

graphify 跨仓库知识图谱实战:graphify clone 与 merge-graphs 从克隆到合并的完整链路

2026-09-06 17:11:18作者:卓炯娓

当你手头有多个服务仓库、多个 monorepo 子目录,甚至只是想基于 GitHub URL 直接对陌生项目做结构分析时,graphify 的 /graphify skill 提供了一条完整的"克隆 → 抽取 → 合并 → 查询"链路。本文以 graphify/skills/opencode/references/github-and-merge.md 这份官方参考文档为主线,逐步拆解 graphify clonegraphify merge-graphs 两条命令的用法与边界,并结合 graphify/cli.pygraphify/build.py 的源码实现说明合并过程中的节点前缀、社区 ID 偏移与跨仓库类型关联等底层机制,读完你可以独立完成跨仓库知识图谱的构建与检索。

何时加载这条链路

参考文档开宗明义:当用户传入一个或多个 https://github.com/... 形式的 URL,或者指定了多个本地子目录要合并成一张图时,才需要走这条 reference。它覆盖两类典型场景:

  1. 跨仓库(cross-repo)图谱:每个服务一个独立仓库,各跑一遍完整 pipeline 再合并;
  2. 多子目录(monorepo 或 multi-service 布局)图谱:同一个仓库内有若干独立可扫描的子目录。

两类场景最终都收敛到同一个产物:一份合并后的 graph.json,之后任何代码结构问题都直接对合并图执行 graphify query无需重新抽取,也不受体积门限约束(原文档称之为 fast path)。

Step 0:用 graphify clone 克隆 GitHub 仓库

只有当输入是 GitHub URL 时才需要这一步。

单仓库

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

clone 子命令的完整用法形如:

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

命令成功时会在最后一行打印本地路径(源码中 print(local_path)),因此可以直接用 $(...) 捕获后作为后续 pipeline 的目标路径。

多仓库(cross-repo 图谱)

# 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

源码印证:clone 的四个关键行为

结合 graphify/cli.py_clone_repo 的实现,文档中"clone into ~/.graphify/repos/<owner>/<repo> and reuses existing clones on repeat runs"这句话可以展开为四个确定行为:

  1. URL 归一化与校验:自动补全/剥离 .git 后缀,再用正则从 URL 中提取 owner/repo;不是 GitHub URL 会直接报错退出(error: not a recognised GitHub URL)。
  2. 浅克隆:首次克隆执行 git clone --depth 1,指定 --branch 时附加 --branch <branch> 参数,只取单个 commit,体积最小。
  3. 复用已有克隆:若目标目录(默认 ~/.graphify/repos/<owner>/<repo>,可用 --out 覆盖)已存在,则改跑 git -C <dest> pull(带 branch 时是 pull origin -- <branch>),而不是重新克隆——这就是"重复运行复用已有克隆"的实现来源。
  4. 输出可脚本化:最后一行统一打印 Ready at: <dest> 后返回 dest,供 shell 变量捕获。

一个值得注意的细节:--branch 的值若以 - 开头会被判定为非法分支名并退出,这是对选项解析误判的防御。

多个本地子目录:用 CLI 的 extract 代替 skill pipeline

参考文档特别强调了一个容易踩的坑:skill pipeline 会把所有中间与最终产物写到当前工作目录下的 graphify-out/。如果对着每个子目录各跑一次 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
# 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 参数:当需要对非代码内容(文档、笔记等)做 LLM 语义增强时,按你手头配置的 API key 选择对应后端;纯 AST 结构抽取则不需要。

merge-graphs 做了什么:前缀、标签与冲突消解

graphify merge-graphs 接收多个 graph.json 路径与一个 --out 输出路径。它的实现位于 graphify/cli.py,配合 graphify/build.py 中的两个工具函数,完成了五件关键的事。

1. 每个节点打上 repo 前缀与 repo 属性

prefix_graph_for_globalgraphify/build.py#L2005-L2057)把输入图的每个节点 ID 重写为 <repo_tag>::<原 ID>

  • 显示用的 label 保持不变;
  • 原 ID 存入 local_id 属性,便于回溯;
  • 每个节点都会写入 repo 属性——这正是参考文档所说"Each node in the merged graph carries a repo attribute so you can filter by origin"的落点,后续按来源仓库过滤、或删除某一仓库的所有节点(prune_repo_from_graph)都依赖这个属性;
  • 边的 _src/_tgt 方向标记与超边(hyperedge)成员 ID 会同步重写到前缀形态,超边 ID 本身也加前缀,防止不同仓库同名超边冲突。

2. 仓库标签自动消歧

repo tag 的默认取值是 graphify-out 的父目录名。但 src/graphify-outfrontend/src/graphify-out 都会得到 tag src,两个同名 tag 会把无关实体悄悄合并成同一节点(源码注释中标注为 #1729)。distinct_repo_tagsgraphify/build.py#L2060-L2085)的消解策略是:先检测冲突,冲突时把标签加宽为"父目录名_目录名"(如 frontend_src),仍重复则追加 -2-3 索引后缀,保证任意两张输入图的标签唯一。标签冲突时 CLI 会在终端打印一行提示:note: repo dir names collide; using distinct tags: ...

3. 社区 ID 偏移

每张输入图都从 0 开始编号自己的 community,若原样带入合并图,不同仓库的 community 0 会撞号,聚合社区视图会把不相干的社区融合成一个元节点(#3014)。merge 处理因此按输入顺序维护一个 community_offset:第一张图保持原 ID,其后每张图的 community 被平移到共享 ID 空间,原始编号保留在 local_community 属性中。

4. 混合图类型归一化

不同 extract 路径在不同时期写出的 graph.json,其 directed / multigraph 标志未必一致,而 nx.compose 要求所有输入图同型。实现里会把 DiGraph / MultiGraph / MultiDiGraph 一律归一为普通无向 Graph(合并出的跨仓库视图本来就以无向方式消费),避免 All graphs must be directed or undirected 崩溃(#1606)。tests/test_merge_graphs_cli.py 用一个"DiGraph + Graph + MultiGraph"三合一输入验证了这条归一化路径,并断言输出 directedfalsemultigraphfalse 且三张图的节点全部存活;同一测试文件还覆盖了"同名仓库目录不得折叠"(#1729)场景:

def test_merge_graphs_same_named_repo_dirs_do_not_collapse(tmp_path):
    # #1729: two graphs under a same-named repo dir (src/graphify-out and
    # frontend/src/graphify-out both → tag "src") share the `src::` prefix, ...
    app_nodes = [n for n in data["nodes"] if n["id"].endswith("::app")]
    assert len(app_nodes) == 2

5. 跨仓库同名类型自动连线

消息总线型架构里最有价值的"一跳"往往跨仓库:producer 在一个仓库引用 SyncProductUpsertToSearchEvent,consumer 在另一个仓库实现 IConsumer<SyncProductUpsertToSearchEvent>。由于所有节点 ID 都带仓库前缀,这两个同名类型声明在合并图里默认互不相连。

link_shared_type_declarationsgraphify/cross_repo_types.py)在 compose 之后补上这层连线:把"namespace + 类型名"相同、且分属至少两个不同仓库的类型声明两两之间加一条 same_type_as 边(confidence=INFERREDconfidence_score=0.9context=cross_repo)。两个设计取舍值得注意:

  • 要求 namespace 完全相同:只凭短类名相同就连线会把无关类型误连(源码 docstring 提到在一对 .NET 服务上,namespace+name 匹配产生 7 对,全部是共享的事件契约,零误报);
  • 只加边、不合并节点:两个仓库可能持有发生漂移(drift)的契约副本,合并节点会掩盖这种漂移,而连线既允许遍历跨越仓库边界,又让两侧各自保留自己的成员、文件与来源信息。

触发时 CLI 会打印 linked N type declaration(s) shared across repos

此外,merge 过程还会对超边做"先收集、后重挂"的并集处理——nx.compose 合并图属性时会用 dict.update 覆盖,若放任不管只有最后一张输入图的超边能存活(#2484),因此实现里先收集每张图前缀化后的超边、compose 完成后再统一去重挂回,且同时写入顶层与 graph 内两个存储槽位,与 to_json 的双槽形态保持一致。最终结果经原子写入落到 --out 指定路径,并打印 Merged N graphs -> X nodes, Y edges

合并之后:query 走 fast path

按参考文档的收尾描述,一旦 graphify-out/graph.json 存在,后续所有代码库问题都直接对合并图执行 graphify query——不再重新抽取,也不再有体积门限(size gate)拦截。也就是说,跨仓库构建的一次性成本换来的是后续持续的结构检索能力;当某个仓库更新后,重新 git pull 该仓库(clone 命令对已存在克隆会自动走 pull)、重新 extract、重新 merge-graphs 即可刷新合并图。

小结:一条可复制的跨仓库工作流

步骤 命令 产物/效果
克隆仓库 graphify clone <url> [--branch b] ~/.graphify/repos/<owner>/<repo>(已有克隆则 git pull)
抽取(子目录) graphify extract ./core/ [--backend ...] ./core/graphify-out/graph.json(产物落在扫描路径内部,互不覆盖)
合并 graphify merge-graphs a/graph.json b/graph.json --out graphify-out/graph.json 节点带 repo 属性与 <tag>:: 前缀,同名类型自动加 same_type_as
查询 graphify query ... 直接在合并图上执行,无重新抽取、无体积门限

这套链路的关键工程保障都来自源码层:distinct_repo_tags 防止同名目录静默合并无关节点,社区 ID 偏移防止跨仓库社区撞号,same_type_as 边让遍历能够跨越仓库边界。测试用例(如 tests/test_merge_graphs_cli.py)对这些边界场景均有回归覆盖,可以作为阅读实现时的最佳入口。

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