graphify 跨仓库知识图谱实战:GitHub 仓库克隆与 merge-graphs 合并流程
本篇技术指南基于 graphify 的 Droid 技能参考文档 github-and-merge.md,讲解当用户给出一个或多个 GitHub 仓库 URL、或需要把多个本地子目录合并成一张知识图谱时的完整操作流程:从 graphify clone 克隆(含分支选择与本地缓存复用)、对每个仓库/子目录独立运行提取管线,到用 graphify merge-graphs 生成可跨仓库遍历的合并图谱,并结合 cli.py 与 build.py 的源码实现,说明节点 repo 前缀、社区 ID 偏移、跨仓库同型连接等底层机制。
一、适用场景与整体流程
graphify/skills/droid/references/github-and-merge.md 这份参考文档的触发条件非常明确:当用户传入了一个或多个 https://github.com/... URL,或者点名要合并多个本地子文件夹时,Agent 才加载该流程。它覆盖三类典型场景:
- 单个 GitHub 仓库:克隆到本地缓存,然后以本地路径为目标运行后续所有步骤;
- 多个 GitHub 仓库(跨仓库图谱):分别克隆、分别跑完整管线、最后合并为一张带来源标记的图;
- 多个本地子文件夹(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-out 和 frontend/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 个输入图,否则打印用法并退出;合并前对每个输入做体量上限检查;
edges与links键归一化:兼容旧版本写入的edges键;同时保留_src/_tgt方向标记,合并完成后按标记恢复真实边方向(无向node_link_graph会丢失方向);- 图类型归一化:不同输入可能一个是
MultiGraph(AST-only 运行)一个是Graph(完整 LLM 运行),统一转换为无向Graph再nx.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/ 的父目录名——core、service、platform,三个标签天然不同,不会触发 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 直接问答的跨仓库知识图谱。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00