首页
/ graphify 跨仓库知识图谱实战:GitHub 仓库克隆与 merge-graphs 合并流程

graphify 跨仓库知识图谱实战:GitHub 仓库克隆与 merge-graphs 合并流程

2026-09-06 15:16:24作者:咎竹峻Karen

本篇技术指南基于 graphify 的 Droid 技能参考文档 github-and-merge.md,讲解当用户给出一个或多个 GitHub 仓库 URL、或需要把多个本地子目录合并成一张知识图谱时的完整操作流程:从 graphify clone 克隆(含分支选择与本地缓存复用)、对每个仓库/子目录独立运行提取管线,到用 graphify merge-graphs 生成可跨仓库遍历的合并图谱,并结合 cli.pybuild.py 的源码实现,说明节点 repo 前缀、社区 ID 偏移、跨仓库同型连接等底层机制。

一、适用场景与整体流程

graphify/skills/droid/references/github-and-merge.md 这份参考文档的触发条件非常明确:当用户传入了一个或多个 https://github.com/... URL,或者点名要合并多个本地子文件夹时,Agent 才加载该流程。它覆盖三类典型场景:

  1. 单个 GitHub 仓库:克隆到本地缓存,然后以本地路径为目标运行后续所有步骤;
  2. 多个 GitHub 仓库(跨仓库图谱):分别克隆、分别跑完整管线、最后合并为一张带来源标记的图;
  3. 多个本地子文件夹(monorepo / 多服务布局):绕过会互相覆盖输出目录的 skill 管线,直接用 CLI 对每个子目录提取,再在项目根合并。

三类场景最终殊途同归:一旦 graphify-out/graph.json 合并产物存在,任何针对代码库的问题都直接跑 graphify query 查合并图——不再重新提取,也不再触发体量门禁(原文档称之为 "fast path")。

二、Step 0:克隆 GitHub 仓库(仅在给定 GitHub URL 时执行)

2.1 单仓库克隆

文档给出的第一步命令:

LOCAL_PATH=$(graphify clone <github-url> [--branch <branch>])
# 后续所有步骤都使用 LOCAL_PATH 作为目标路径

graphify clone 的完整用法在 cli.py 中定义,还支持一个文档未展开的参数 --out <dir> 用于自定义克隆目录。

2.2 源码级行为解析:浅克隆、缓存复用与分支校验

文档说"Graphify 克隆到 ~/.graphify/repos/<owner>/<repo>,重复运行时复用已有克隆"。这一行为由 cli.py 中的 _clone_repo 函数 实现,源码确认了四个细节:

  • URL 规范化:自动补全/剥离末尾的 .git,并用正则提取 owner/repo;无法识别为非 GitHub 的 URL 会直接报错退出;
  • 首次克隆使用浅克隆:执行 git clone --depth 1,只拉取最新提交,减小体积;若指定了 --branch,则追加 --branch <branch>
  • 重复运行复用缓存:目标目录 ~/.graphify/repos/<owner>/<repo> 已存在时,不重新克隆,而是执行 git pull(带分支时为 git pull origin -- <branch>);pull 失败仅打印警告,不会中断流程;
  • 分支名安全校验:以 - 开头的 --branch 取值会被拒绝(防止被解析为选项注入 git 参数)。

命令最终把本地路径打印到 stdout,这正是文档中 LOCAL_PATH=$(...) 命令替换能捕获路径的原因。

三、多仓库合并:生成跨仓库图谱

文档给出的多仓库流程:

# 每个仓库都克隆、跑完整管线,然后合并
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-out/graph.json,合并时再交给 merge-graphs。文档强调合并图中每个节点都携带 repo 属性,因此可以按来源仓库过滤。

3.1 节点 ID 前缀化:repo 属性从哪来

这一点的实现在 build.py 的 prefix_graph_for_global 中,合并流程对每个输入图做如下处理:

  • 所有节点 ID 被重写为 <repo_tag>::<原节点ID> 形式,显示用的 label 保持不变
  • 每个节点新增 repo 属性(即 repo_tag),并新增 local_id 属性保存原始 ID 以便还原;
  • 边上的 _src/_tgt 方向标记同步重写为新前缀 ID;
  • 超边(hyperedges)的成员 ID 和超边自身的 id 也一并前缀化,避免不同仓库同名超边合并后冲突。

正是这套前缀化机制保证了两个仓库里恰好同名的类/函数不会因为 ID 相同而被 nx.compose 悄悄合并成同一个节点。

3.2 仓库标签去重:避免同名目录导致节点碰撞

repo_tag 不能简单取 graphify-out/ 的父目录名。build.py 的 distinct_repo_tags 函数 专门处理这个陷阱:src/graphify-outfrontend/src/graphify-out 的朴素标签都叫 src,若两图共用 src:: 前缀,两个毫不相关的实体(比如后端 src/app.js 和前端 App.jsx 提取出的同名 app 节点)会被静默合并。源码策略是:检测到碰撞时把标签扩展为 <父目录名>_<目录名>(例如 frontend_src),仍重复则追加序号后缀 -2-3……,保证任意两个输入图绝不共享前缀。合并时若发生标签扩展,CLI 会打印一条 note: repo dir names collide; using distinct tags: ... 提示。

3.3 社区 ID 空间隔离:防止聚合视图把不相关社区熔成一片

每个输入图的社区编号都从 0 开始,直接合并会让"仓库 A 的社区 0"和"仓库 B 的社区 0"碰撞,聚合社区视图会把它们熔成同一个元节点。cli.py 的 merge-graphs 分支 通过 community_offset 机制解决:第一个输入保留原始社区 ID(偏移 0),后续输入的每个社区 ID 加上递增偏移量,原始值保存在 local_community 属性中,从而保持各仓库自身的社区划分独立。

3.4 跨仓库同型声明自动连接

前缀化让每个节点都"带上户口",但也切断了跨仓库的语义联系:生产者仓库引用 SyncProductUpsertToSearchEvent,消费者仓库实现 IConsumer<SyncProductUpsertToSearchEvent>,合并图里两侧命名相同却互不相通。为此,合并流程在 compose 之后调用 cross_repo_types.py 的 link_shared_type_declarations

  • 只针对带 namespace 和来源文件的类型声明节点,要求 (namespace, 类型名) 完全一致且分属至少两个仓库;
  • 满足条件的一组节点两两之间添加一条 same_type_as 边(context="cross_repo"confidence="INFERRED"confidence_score=0.9);
  • 只加边、不合并节点——两个仓库可能各持有一份已经漂移的契约副本,合并节点会掩盖这种漂移,而一条边既允许遍历跨越仓库边界,又让每侧保留自己的成员、文件与来源信息。

CLI 会打印 linked N type declaration(s) shared across repos 报告连接数量。

3.5 其余合并细节(源码可确认的健壮性处理)

除上述主线外,merge-graphs 命令实现 还处理了几类输入不一致问题,了解它们有助于排查合并失败:

  • 至少 2 个输入图,否则打印用法并退出;合并前对每个输入做体量上限检查;
  • edgeslinks 键归一化:兼容旧版本写入的 edges 键;同时保留 _src/_tgt 方向标记,合并完成后按标记恢复真实边方向(无向 node_link_graph 会丢失方向);
  • 图类型归一化:不同输入可能一个是 MultiGraph(AST-only 运行)一个是 Graph(完整 LLM 运行),统一转换为无向 Graphnx.compose,否则 compose 会因类型不一致抛错;
  • 超边并集nx.compose 用 dict.update 合并图属性会互相覆盖,实现改为先收集所有输入的前缀化超边,compose 后再统一按 ID 去重挂回;
  • 原子写出:合并结果经 node_link_data 序列化后,用 paths.write_json_atomic 原子写入 --out 指定路径(默认 graphify-out/merged-graph.json),并打印 Merged N graphs -> X nodes, Y edges 摘要。

四、多个本地子文件夹:monorepo / 多服务布局

文档对这一场景给出了一个重要的踩坑提醒:skill 管线会把所有中间产物和最终产物写到当前工作目录下的 graphify-out/。如果对多个子文件夹分别跑 skill 管线,会互相覆盖同一个输出目录。正确做法是直接调用 CLI 对每个子目录提取——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
# 根据你配置了哪个 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

4.1 graphify extract 参数速查

文档提示按 API key 配置追加 --backend。结合 cli.py 中 extract 子命令的完整用法行graphify extract 的实际参数面比文档示例更宽,常用项包括:

参数 作用
<path> 要提取的路径;省略时默认为当前目录
--backend gemini|kimi|claude|openai|deepseek|ollama 语义提取的 LLM 后端(文档列出的 claude-cli 对应 CLI 用法行中的 claude 系后端,以实际安装环境为准)
--model M 指定模型
--mode deep 深度提取模式
--out DIR / --output DIR 覆盖输出目录
--code-only 仅做代码 AST 提取,不调用 LLM
--no-dedup 跳过实体去重(同时启用 build_merge 的收缩守卫)
--no-cluster 跳过社区聚类
--no-gitignore 不按 .gitignore 过滤
--max-workers N / --token-budget N / --max-concurrency N / --api-timeout S 性能与并发调优旋钮
--allow-partial 允许部分失败后继续
--timing 打印各阶段耗时

cli.py 注释 可以看到,extract 是一条无头(headless)全管线:detect → 代码 AST 提取 → 文档/论文/图片的 LLM 语义提取 → merge → build → cluster → 写出产物,适用于 CI 和脚本场景,不依赖 Agent 子代理环境。

4.2 为什么必须用 CLI 而不是 skill 管线

对照第二节的 GitHub 流程就能理解这个设计差异:GitHub 多仓库场景中,各仓库位于 ~/.graphify/repos/<owner>/<repo>彼此独立的路径,各自跑 /graphify 互不干扰;而 monorepo 的多个子目录都从同一个项目根发起 skill 调用,输出目录 graphify-out/ 相同,后跑的先覆盖前者。CLI 的 extract 以扫描路径为基准放置输出,天然隔离了 ./core/./service/./platform/ 三套产物,随后一次 merge-graphs 把它们收拢为根目录下的 graphify-out/graph.json。注意这里的 repo 标签正是取自 graphify-out/ 的父目录名——coreserviceplatform,三个标签天然不同,不会触发 3.2 节的碰撞兜底。

五、合并完成后的快速查询路径

文档的收尾结论值得单独强调:一旦 graphify-out/graph.json(合并产物)存在,"fast path" 接管——任何代码库问题都直接对合并图运行 graphify query,不重新提取、不做体量门禁。

这与 graphify 的查询模型一致:graphify query 针对的是已写盘的 graph.json 静态产物,合并图里跨仓库的 same_type_as 边、按 repo 属性可过滤的节点,都让它成为真正的"跨仓库知识图谱"入口。仓库内 worked/ 目录下也保留了若干已构建的图谱示例(如 mixed-corpus 的 graph.json),可作为了解 graph.json 结构的参考。

六、流程小结

场景 步骤 产物位置
单 GitHub 仓库 graphify clone <url> [--branch b] → 以 LOCAL_PATH 跑完整管线 ~/.graphify/repos/<owner>/<repo>/graphify-out/graph.json
多 GitHub 仓库 分别 clone + 分别跑管线 → graphify merge-graphs 合并 graphify-out/cross-repo-graph.json--out 指定)
多本地子目录 每个子目录 graphify extract ./xxx/ → 根目录 graphify merge-graphs 合并 ./xxx/graphify-out/graph.json → 根 graphify-out/graph.json

三个场景的共同点:先独立建图,再统一合并。合并时 graphify 通过节点 ID 前缀化(repo 属性)、社区 ID 偏移、跨仓库同型连接三道机制,让多来源实体在一张图里既互不串扰、又能被遍历跨越,最终产出一张可以用 graphify query 直接问答的跨仓库知识图谱。

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