背景
graphify 預設每次 extract 都針對單一目錄建一份圖譜。但實務上有兩種場景需要「跨圖譜查詢」:
- monorepo 多服務:
services/auth/、services/billing/、services/export/各自獨立維護,想看「auth 的verify()跟 billing 的charge()是怎麼連起來的」 - 多 repo 跨專案:上游 SDK 是獨立 repo、下游服務也是獨立 repo,想看「SDK 的 client 升級會炸到下游哪些 callers」
把全部 source 丟進一次 extract 不是好辦法。子專案各有自己的 .graphifyignore、版本節奏、CI pipeline,混在一起會失去邊界。graphify merge-graphs 給你「分別建圖、需要時合併」的工作流:每個子專案維護自己的圖譜,要做橫向分析時才 merge 成一份總圖。
💡 與範例 002 差異:002 是把整個 monorepo 一次
extract .建成單一圖譜。本範例處理的是「子專案各自獨立建圖、再合併」的場景,適合子專案有不同建圖設定或不同 repo 的情況。
適用情境
- monorepo 內子專案有各自的
.graphifyignore、建圖頻率、或需走語意層 vs AST-only 的差異 - 跨 repo 場景:SDK repo + 應用 repo,想看 client 升級的下游影響
- 微服務架構:每個服務獨立 repo,但想分析 service-to-service 的呼叫關係
- 想做「跨服務死碼分析」或「跨服務 god node 識別」
完整流程
步驟 1:分別為子專案建圖
情境:monorepo 內有 services/auth/、services/billing/、services/export/ 三個子專案,各自有獨立 setup。
cd ~/team-monorepo
# 子專案 1:auth(純 AST,不需要語意)
cd services/auth
graphify extract . --code-only --output graphify-out/auth.json
# 子專案 2:billing(含 docs,需要 LLM 語意層)
cd ../billing
export ANTHROPIC_API_KEY=sk-ant-...
graphify extract . --output graphify-out/billing.json
# 子專案 3:export(純 AST)
cd ../export
graphify extract . --code-only --output graphify-out/export.json
每個子專案輸出獨立的 graph.json。各自維護新鮮度(hook / update / watch)。
步驟 2:合併三份子圖譜
回到 monorepo 根目錄,把三份合在一起:
cd ~/team-monorepo
graphify merge-graphs \
services/auth/graphify-out/auth.json \
services/billing/graphify-out/billing.json \
services/export/graphify-out/export.json \
--out graphify-out/merged.json
merge-graphs 做三件事:
- 讀進每份子圖的節點跟邊
- 對相同 ID 的節點取聯集(node id 相同就合併屬性)
- 寫出合併後的 JSON
輸出類似:
Merging 3 graphs...
services/auth/graphify-out/auth.json (412 nodes, 1024 edges)
services/billing/graphify-out/billing.json (687 nodes, 1543 edges)
services/export/graphify-out/export.json (298 nodes, 612 edges)
Resolving shared nodes...
Shared node IDs: 23 (across auth & billing)
Shared node IDs: 8 (across billing & export)
→ merged as unified nodes, attributes combined
Output written: graphify-out/merged.json
Total nodes: 1366
Total edges: 3151
(union of all subgraphs; shared nodes deduplicated)
步驟 3:對合併圖做橫向查詢
合併圖的重點價值在「跨子專案的查詢」。把 merged.json 餵給 graphify query:
graphify query --graph graphify-out/merged.json "auth 的 verify() 怎麼連到 billing 的 charge()?"
graphify 在合併圖上跑 BFS,找到跨子專案的路徑:
Subgraph (5 nodes, 6 edges):
AuthMiddleware.verify() [auth]
--calls--> RequestContext.current_user() [auth]
--calls--> InvoiceService.for_user() [billing]
--calls--> InvoiceService.charge() [billing]
--calls--> PaymentGateway.charge() [billing]
跨子專案的呼叫鏈一次拉出來。這是單獨看任一份子圖譜看不到的視角。
步驟 4:跨服務的 god node 識別
合併圖的好處是 god node(高連結數節點)計算更準。單獨看 auth 圖譜,RequestContext.current_user() 可能 in-degree 只有 4,不算 god node。合併後 in-degree 跳到 18,立刻浮現為 god node:
head -50 graphify-out/GRAPH_REPORT.md
合併後的報告會列出跨服務的 god node,是重購的高價值目標。
步驟 5:把合併自動化進 CI
子專案各自 push 後,CI 自動重建子圖、再合併成總圖:
.github/workflows/merge-graphs.yml:
name: Merge Subproject Graphs
on:
push:
branches: [main]
paths:
- 'services/**'
jobs:
merge:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install graphify
run: pipx install graphifyy
- name: Build subgraphs
run: |
for svc in services/auth services/billing services/export; do
(cd "$svc" && graphify extract . --code-only --output graphify-out/sub.json)
done
- name: Merge
run: |
graphify merge-graphs \
services/auth/graphify-out/sub.json \
services/billing/graphify-out/sub.json \
services/export/graphify-out/sub.json \
--out graphify-out/merged.json
- name: Commit merged graph
run: |
git add graphify-out/merged.json
git commit -m "chore(graphify): update merged graph" || echo "no changes"
git push
main branch 永遠帶著最新的合併圖。reviewer 跟新人都看得到跨服務視角。
步驟 6:用 HTTP MCP server 共用合併圖
把合併圖開成團隊共用的 MCP server:
python -m graphify.serve graphify-out/merged.json \
--transport http \
--host 0.0.0.0 \
--api-key "$TEAM_GRAPHIFY_KEY"
每人的 IDE 指向同一個 URL,助理用同一份合併圖回答問題,避免每人各自重建。
真實輸出範例
$ graphify merge-graphs \
services/auth/graphify-out/auth.json \
services/billing/graphify-out/billing.json \
services/export/graphify-out/export.json \
--out graphify-out/merged.json
Merging 3 graphs...
services/auth/graphify-out/auth.json (412 nodes, 1024 edges)
services/billing/graphify-out/billing.json (687 nodes, 1543 edges)
services/export/graphify-out/export.json (298 nodes, 612 edges)
Resolving shared nodes...
Shared node IDs: 23 (across auth & billing)
Shared node IDs: 8 (across billing & export)
→ merged as unified nodes, attributes combined
Output written: graphify-out/merged.json
Total nodes: 1366
Total edges: 3151
$ graphify query --graph graphify-out/merged.json "auth verify 跟 billing charge 怎麼連?"
Subgraph (5 nodes, 6 edges):
AuthMiddleware.verify() [auth]
--calls--> RequestContext.current_user() [auth]
--calls--> InvoiceService.for_user() [billing]
--calls--> InvoiceService.charge() [billing]
--calls--> PaymentGateway.charge() [billing]
對比傳統做法
| 任務 | 一次 extract . 整個 monorepo |
分別建圖 + merge-graphs |
|---|---|---|
子專案 .graphifyignore |
只能設一個 | 各自獨立設定 |
| 子專案建圖頻率 | 全部綁在一起 | 各自控制(純 AST vs 語意層) |
| 跨 repo 場景 | 不適用(路徑不連續) | 各 repo 建圖、再合併 |
| 跨服務查詢 | 預設就有 | merge 後才有 |
| CI 成本 | 一次全量 | 子專案 incremental、CI 平行 |
| 子專案獨立分析 | 不容易(邊界模糊) | 子圖譜可單獨查詢 |
merge-graphs 不是「取代」一次 extract .,而是補它。子專案獨立性強、建圖設定差異大、或跨 repo 時,merge-graphs 才是對的工具。單純 monorepo 還是建議直接 extract .。
失敗處理
| 情境 | 排查 |
|---|---|
| 合併後節點數比預期少很多 | 子圖譜之間的節點 ID 衝突被誤判為同一個。檢查子圖譜的 ID 命名規則(例如是否含路徑前綴) |
| 合併圖查不到跨服務路徑 | 子圖譜之間的 shared node ID 不存在,呼叫鏈在中間斷掉。確認中介層(例如 shared SDK)也被建進至少一份子圖 |
| merge-graphs 報 JSON parse error | 某份子圖譜是舊版 graphify 建的、格式不相容。重跑該子專案的 extract |
| 合併圖太大、查詢慢 | 節點超過 10 萬時 query 會變慢。考慮只合併需要的子圖,或用 --code-only 減少語意節點 |
| CI 合併後圖譜跟本地對不上 | 本地 hook 跑的是子圖 incremental、CI 跑的是全量。统一流程:CI 每次都從子圖全量重建再合併 |
| 共享節點屬性不一致 | 兩份子圖對同一個節點有不同屬性。merge 採「後寫入覆蓋」或「屬性聯集」,看 graphify 版本;檢查合併後的 GRAPH_REPORT.md 確認 |
內部連結
下一步
- 用 diagnose multigraph 診斷 same-endpoint 風險(合併多圖後的節點衝突診斷)
- 大型 monorepo 重購(合併圖譜作為重購分析的基礎)
關鍵學習點
- merge-graphs 不是預設工作流,是「子專案獨立性強、跨服務查詢需求」場景的專用工具。單純 monorepo 用一次
extract .就好。 - 共享節點 ID 是合併成敗的關鍵。子專案之間共用的 SDK、型別、介面要先確認 ID 命名一致,否則合併會出現幽靈節點或斷鏈。
- 合併圖的價值在「跨服務 god node」跟「跨服務呼叫鏈」。這兩個視角是單獨子圖譜給不出來的。
- CI 自動化合併 + HTTP MCP server 共用,是大型團隊把 merge-graphs 變成日常工具的標準組合。
- merge 後的 merged.json 也要 commit 進 repo 或開 MCP server,否則每人各自合一次、結果不一致。
v0.9.54:跨 repo 不只連型別,還能補 member call
從 v0.9.49 起,merge-graphs 會對同 namespace + name 的跨 repo shared type 建立 same_type_as;v0.9.54 再往前一步:如果某個 member call 的 receiver type 在另一個 repo,而且「跨 repo type + method」只有唯一候選,merge 時可以補出該呼叫邊。
例如 SDK repo:
namespace Shared.Payments;
public sealed class PaymentClient
{
public Task ChargeAsync(decimal amount) => ...;
}
App repo:
await _paymentClient.ChargeAsync(order.Total);
兩邊分開建圖後再 merge-graphs,新版有機會把 app 的 ChargeAsync 呼叫連到 SDK 的真實方法。若存在兩個不歧義不足的候選,Graphify 會保守地不連,避免生成漂亮但錯誤的跨 repo path。
這也表示舊版「只靠 shared node ID 是否相同」的理解已不完整;現在還有 fully-qualified type identity 與 merge-time resolver 參與判斷。