Graphify 跨仓库知识图谱:GitHub 仓库克隆与多仓库图合并(clone / merge-graphs)实战指南
本文基于 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.json,directed/multigraph标志可能不一致,而nx.compose要求所有输入同类型。merge 处理器会先把所有输入规范化为无向简单图(跨仓库合并视图本身就是无向的),避免崩溃(对应测试 tests/test_merge_graphs_cli.py 中test_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-out与frontend/src/graphify-out都会得到标签src,若不加处理,两边同名的裸节点(如后端的app.js与前端的App.jsx)会被nx.compose静默合并成同一实体,凭空造出跨运行时的边。发现碰撞后标签会加宽为父目录_目录名,仍有重复再追加序号后缀,确保两两不同(测试test_merge_graphs_same_named_repo_dirs_do_not_collapse与test_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_inputs、test_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,000(graphify/cli.py:_MERGE_MAX_NODES = 100_000);超限会中止并报错,避免生成不可查询的巨型合并图。
合并之后的快速路径:直接 query,不再重复提取
参考文档的最后一句定义了合并产物 graphify-out/graph.json 存在后的运行模式:
Once
graphify-out/graph.jsonexists, the fast path above takes over: any codebase question runsgraphify querydirectly 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(如 gemini、openai、deepseek、claude-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 标记在序列化层还原,而非保留为有向图结构;社区偏移量按输入顺序分配,固定顺序下结果字节级可复现,但改变输入顺序可能导致社区编号变化。
参考路径汇总
- 主题参考文档:graphify/skills/claw/references/github-and-merge.md
- clone 实现与 CLI 分发:graphify/cli.py、graphify/cli.py
- merge-graphs 处理逻辑:graphify/cli.py
- 前缀与仓库标签:graphify/build.py
- 跨仓库同名类型链接:graphify/cross_repo_types.py
- 合并行为测试:tests/test_merge_graphs_cli.py
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 StartedRust0624
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