Theme / v0.9.56

Graphify

Codebase 知識圖譜

實戰範例

實戰範例 004:用 graphify query 做跨檔案依賴追蹤

把『這個 function 被誰用到』、『改這支檔會炸到哪』的跨檔調查,從 grep 一整路昇級成一行自然語言查詢

背景

維護過 mid-size codebase 的人一定遇過這些問題:

  • getUserRole() 到底被哪些地方呼叫?」
  • 「如果我改 auth middleware 的回傳值,會不會炸到 billing?」
  • 「這個看似沒人用的 util 是不是死碼(dead code)?」

傳統做法是 grep -r "getUserRole" .,然後在 50 個命中裡人工過濾:哪些是註解、哪些是同名變數、哪些才是真正的呼叫點。跨好幾個目錄之後,你還要在腦海裡把這些命中串起來。

graphify 的 query 把這件苦差事交給圖譜。你的問題打到 graphify query/graphify query,graphify 在已建好的 JSON 圖譜上做 BFS(廣度優先搜尋,Breadth-First Search)展開子圖,回傳真正相關的節點跟邊。搜尋結果不再只是字串命中,而是有方向、有型別的依賴關係。

💡 範例 001 示範過 graphify query 的開放式問答用法。本範例聚焦在依賴追蹤這個更結構化的場景:怎麼把開放式 query 接到「跨檔影響分析」的工作流。


適用情境

  • 想重購或刪除某個 function / class,要先盤點所有呼叫點
  • 接手 legacy code,需要建立「更動 A 會牽動 B、C、D」的心智模型
  • code review 時,懷疑 PR 提的影響範圍不完整,想快速驗證
  • 找死碼候選清單(節點 in-degree 為 0 且非 entry point)

完整流程

步驟 1:確保圖譜是新鮮的

依賴追蹤前提是圖譜要反映現況。如果還沒建過:

cd ~/my-project

uv tool install graphifyy && graphify install
graphify extract . --code-only

--code-only 只跑 tree-sitter AST 解析,不需要 API key,30 秒內建完中型 codebase。如果之前已建過,先確認是新鮮的:

graphify hook status

如果 hook 沒裝、或最近 commit 沒觸發重建,先補一次:

graphify update

update 只重抽變動檔,比全量 extract 快很多。

步驟 2:用開放式 query 找入口

假設你想追的是 getUserRole。第一個 query 先問「它在哪」:

graphify query "getUserRole 是在哪裡定義的?"

graphify 挑關鍵字、過濾 stop-words、BFS 找子圖,回傳類似:

Subgraph (4 nodes, 5 edges):
  [DEF]  getUserRole()           src/auth/permissions.py:42
  [DEF]  UserRole                src/auth/permissions.py:12
  [REF]  get_request_user()      src/middleware/auth.py:88
  [REF]  check_permission()      src/auth/policy.py:23

[DEF] 是定義點,[REF] 是參考點。這已經比 grep 淨多了:grep 會連字串字面量、註解、同名欄位全部撈進來。

步驟 3:反過來問「誰用到它」

接著問反向依賴(downstream callers):

graphify query "哪些地方依賴 getUserRole 的回傳值?"

graphify 沿 CALLS / REFERENCES 邊走,回傳子圖。看到的可能是:

Subgraph (6 nodes, 8 edges):
  getUserRole() --calls--> canAccessAdmin()
  getUserRole() --calls--> getDashboardView()
  canAccessAdmin() --calls--> AdminController.index()
  getDashboardView() --calls--> DashboardController.show()

從子圖你直接看出:改 getUserRole 的回傳型別會炸到 AdminControllerDashboardController 這兩個 god node。

步驟 4:在助理內用 slash command 收斂

如果你在 Claude Code / Cursor 內,把 query 包成 slash command 讓助理直接消費子圖:

/graphify query "如果我改 UserRole 的 enum 值,哪些地方會被影響?"

助理拿到子圖後,會結合程式碼上下文回答:「影響 12 個檔,最嚴重的是 AdminPolicy.enforce()DashboardController.show(),兩者都直接 switch 這個 enum。」

slash command 跟 CLI 的差別:CLI 把子圖印給你看,slash command 把子圖餵給助理做進一步推論。兩者底層都打 graphify query,只是出口不同。

步驟 5:搭配 path 確認關鍵路徑

子圖很大、看不完時,把焦點轉到 path 指令:

graphify path "getUserRole" "AdminController"

path 兩個端點之間走 BFS 找最短路徑,回傳明確的邊序列:

Shortest path (3 hops):
  getUserRole() --calls--> canAccessAdmin()
  canAccessAdmin() --calls--> requireAdminRole()
  requireAdminRole() --calls--> AdminController.index()

這就是「為什麼改 A 會牽到 B」的具體呼叫鏈。完整用法見範例 005

步驟 6:把發現寫進 PR 描述

依賴追蹤的結果要落地成可分享的文件。把步驟 3、5 的子圖與路徑貼進 PR description:

## Impact analysis

`getUserRole` 的 downstream callers(graphify query 結果):

- canAccessAdmin() → AdminController.index()
- getDashboardView() → DashboardController.show()

最短路徑(graphify path):
getUserRole → canAccessAdmin → requireAdminRole → AdminController.index()

本 PR 只改回傳註解,不動 enum 值,所以下游不影響。

reviewer 看到具體路徑,不用自己再 grep 一輪。


真實輸出範例

$ graphify query "getUserRole 被誰呼叫?"
Subgraph (6 nodes, 8 edges):
  [DEF]  getUserRole()              src/auth/permissions.py:42
  [REF]  canAccessAdmin()           src/auth/policy.py:23
  [REF]  getDashboardView()         src/dashboard/views.py:15
  [REF]  exportUserCsv()            src/export/csv.py:88
  [REF]  requireAdminRole()         src/middleware/admin_guard.py:12
  [REF]  audit_log()                src/audit/log.py:34

$ graphify query "改 UserRole enum 會炸到哪些 god node?"
Subgraph (5 nodes, 6 edges):
  UserRole (enum)
    → getUserRole()           [DEFINES]
    → canAccessAdmin()        [CALLS UserRole.admin]
    → AdminController.index() [CALLS, god node]
    → DashboardController.show() [CALLS, god node]

$ graphify path "getUserRole" "AdminController"
Shortest path (3 hops):
  getUserRole() --calls--> canAccessAdmin()
  canAccessAdmin() --calls--> requireAdminRole()
  requireAdminRole() --calls--> AdminController.index()

對比傳統做法

任務 grep + 讀檔 graphify query
找所有呼叫點 grep -r "getUserRole" 然後人工過濾字串命中 graphify query 直接回 CALLS 邊
反向依賴(誰用我) 反向 grep,再從 import 推論 query 反向走邊,一次到位
找 god node 關聯 看不出來,要靠 import 拓樸 子圖直接標 god node 標籤
死碼候選 grep 不到 ≠ 沒人用 看 in-degree = 0 且非 entry point
跨檔影響範圍 跨檔搜尋再人工拼接 query + path 一氣呵成

graphify query 真正的優勢不在「找字串」,而在回傳結構化的子圖,有方向(CALLS / REFERENCES / DEFINES)、有標籤(god node、community)。


失敗處理

情境 排查
query 回空子圖 換關鍵字:graphify 過濾 stop-words,純中文虛詞會被丟掉。用具體的 function / class 名稱
query 回太多無關節點 收斂問句:加「定義」、「呼叫」、「實作」這類動詞,graphify 會優先走對應的邊類型
結果跟現況對不上 圖譜過時。跑 graphify update,或確認 graphify hook status 顯示 hook 跑過
助理拿到 slash command 卻說「沒有 graphify」 還沒 graphify install。先 graphify claude install 或對應平台的 install
query 命中字串字面量而非真正呼叫 AST 邊帶信心標籤,看 [EXTRACTED] vs [INFERRED]--code-only 結果只有 EXTRACTED,最可靠

內部連結


下一步

  1. 用 path / path-explain 解釋為何 A 影響 B(更深入的影響鏈分析)
  2. 大型 monorepo 重購(依賴追蹤之後的重購實戰)

關鍵學習點

  • graphify query 不是「進階版 grep」,而是「在結構化圖譜上做 BFS」。回傳的是有方向、有標籤的子圖。
  • 依賴追蹤的標準三步:先 query 找入口,再 query 反向找下游 callers,最後 path 確認關鍵路徑。
  • query 結果的好壞取決於問句的關鍵字。用具體的 function / class 名稱,加「定義」「呼叫」「實作」這類動詞收斂。
  • 圖譜新鮮度是前提。沒裝 hook、或最近沒 update,查到的依賴就是過時的,會誤導重購決策。