cli.py(4044 行)· __main__.pygraphify 的 CLI 分成兩層:__main__.py 的 main() / _run_cli() 是殼(處理 encoding、broken pipe、help、版本),真正的指令分派在 cli.py 的 dispatch_command()。而 install/uninstall 家族在 0.8.x 被拆到 install.py(所以 __main__.py 有一大串 re-export)。
main() 的 docstring 講了一個很實際的問題(issue #1807):當 graphify query … | head 這種用法,下游 reader(head)提早關掉 stdout,Python 會在上層 flush 時拋 BrokenPipeError 並以 255 結束——CI 或 agent harness 會把一次成功的查詢誤判成失敗。
解法:
_run_cli() 包在 try 裡,先對 stdout/stderr 做 reconfigure(encoding="utf-8")(跨平台 UTF-8)。-h/--help/-?` 印出完整的 subcommand 清單。BrokenPipeError 或 Windows 的 OSError(EINVAL/EPIPE),呼叫 _silence_broken_pipe()——把 stdout 導到 devnull 後 sys.exit(0)。| head 失敗會讓所有 wrapper 壞掉。段落式說明:_check_skill_version 比對安裝的 skill 版本戳記與套件版本。特別處理兩種情形:skill 比套件新(那不能叫使用者 graphify install,會 downgrade skill——要升級套件,issue #1568)、references/ sidecar 遺失(提示 repair)。_version_tuple 把版本字串解析成可比較的整數 tuple,非數字 segment 退化成 0。
這是一個巨大的 if/elif 分派(4044 行的檔案,多半是它)。每個 subcommand 的處理模式大致是:
sys.argv 的旗標(用 --flag value 或 --flag=value 兩種形式)。graph.json(_default_graph_path(),預設 graphify-out/graph.json)。graphify.serve / graphify.cluster / graphify.analyze…)或直接操作圖。--json)。幾個值得注意的指令類別:
| 類別 | 指令 | 對應模組 |
|---|---|---|
| 建圖 | extract / update / cluster-only / label | detect / extract / build / cluster / llm |
| 查詢 | query / path / explain / affected / god-nodes | serve(query 引擎) |
| 進料 | add / clone / merge-graphs | ingest / clone |
| 維運 | watch / check-update / hook / benchmark | watch / hooks |
| 輸出 | export callflow-html / tree | callflow_html / tree_html |
| 記憶 | save-result / reflect | reflect |
| 進階 | provider / global / merge-driver / diagnose | llm / global_graph / build |
這是 always-on 機制的核心:當 Claude Code / Gemini 的 PreToolUse hook 觸發時,會執行 graphify hook-guard。它判斷目前的搜尋/讀檔是否該被「推一把」——如果圖存在且不是 strict 模式,回傳一個 additionalContext 提示(_SEARCH_NUDGE / _READ_NUDGE)引導助手先 graphify query;strict 模式(_hook_strict_enabled)則會擋住第一次原始讀檔並重導到圖。
為什麼搬進 Python subcommand?舊 hook 是內嵌 bash 的 python -c "…" 一行流,Windows cmd/PowerShell 解析不了(issue #522)。改成 graphify hook-guard subcommand 後,sh / cmd / PowerShell 都能跑。
graphify clone)。段落式說明:graphify clone <github-url> 的實作,把 repo clone 到 ~/.graphify/repos/<owner>/<repo>(或 --out),預設用 repo 的預設分支(--branch 可覆寫),最後印出路徑供 skill 的 Step 0 使用。
段落式說明:
_StageTimer — --timing flag 的實作,用 perf_counter 量每個 pipeline stage 的牆鐘時間。_enforce_graph_size_cap_or_exit — 載入圖前檢查 512 MiB 上限(與 security 對應)。_default_graph_path — 預設 graphify-out/graph.json 的解析(尊重 GRAPHIFY_OUT 環境變數)。_stale_graph_sources / _prune_graph_json_sources — 檢查/剪除過期來源(更新時)。install/uninstall 為什麼被拆到 install.py。指令的行為詳解在 文件教學 與 README 的 full command reference。前面是「看懂 CLI 兩層結構」;這次是把 CLI 當 CI 元件:你的 CI pipeline 要在每次 PR 跑一連串 graphify 指令,且「任何一個失敗都要有明確訊息」。
graphify extract . 建圖;接 graphify report 或檢查 built_at_commit 確認圖新鮮。graphify --check-skill-version(或對應旗標)確認 skill 與套件版本一致——避免 CI 環境裝了舊 skill 配新套件。graphify hook-guard 模擬 agent 搜尋前的行為——驗證 always-on 機制在 CI 環境(可能沒裝 GUI、Windows runner)也能跑。graphify benchmark graphify-out/graph.json 對比 README 數字,建立「準確度回歸門檻」。graphify diagnose(或 validate 路徑)檢查圖檔大小上限(512 MiB)與 schema。graphify export --svg --graphml 產出 CI artifact,附在 PR 上供人工檢視。--json 輸出可被下游客器解析。更重要的是 broken pipe 處理:graphify query … | head 在 CI 裡會遇到,處理得當才不會把成功誤判成失敗。cli 頁那些「小工具」其實藏著產品思維:
graphify query | head 時下游提早關閉 stdout,Python 在 flush 時拋 BrokenPipeError 並以 255 結束——CI/agent harness 會誤判。解法是 _silence_broken_pipe():把 stdout 導到 devnull 後 sys.exit(0)。這是一個「懂 Unix 慣例」的相容層。python -c "…",Windows cmd/PowerShell 解析不了。改成 graphify hook-guard subcommand 後,sh/cmd/PowerShell 都能跑——「跨平台」不是口號,是實作決策。__main__.py 只剩 re-export。職責單一讓測試可以只 mock 一部分。_run_cli() 開場對 stdout/stderr reconfigure(encoding="utf-8")——跨平台中文輸出(含本站)不亂碼。graphify query 走過的路徑就是建圖時驗證過的路徑」。其實 query 是對 graph.json 的即時檢索——如果圖是用舊語料建的、或增量更新漏了某些檔,query 會「正確地」回傳正確圖裡的錯誤答案。最常見的是「改了檔案但忘了 graphify update」:graph.json 停在舊 commit,query 卻照常回應。CLI 不會提醒你圖過期——把「檢查 built_at_commit」寫進你的工作流程,而不是期待工具自動發現。| 症狀 | 可能原因 | 解決方案 |
|---|---|---|
| CI 把成功查詢判成失敗(exit 255) | 下游 pipe 關閉造成 BrokenPipeError 未處理 | 確認版本含 _silence_broken_pipe(issue #1807);升級 graphify |
| Windows runner 上 hook 不執行 | 舊版 hook 是 bash 一行流,cmd/PowerShell 解析不了 | 改用 graphify hook-guard subcommand 形式(issue #522) |
| query 回答與最新程式碼不符 | 圖過期:改了檔但沒 graphify update | 檢查 built_at_commit;養成修改後 update 的習慣(AGENTS.md 規則) |
--json 輸出在 CI 解析失敗 | stderr 混入警告(skill 版本提示等)污染了 stdout | 確認警告走 stderr;CI 只吃 stdout;或先跑 --check-skill-version 淨化環境 |
| skill 版本比套件新、install 被拒 | skill 更新領先 pip 套件(issue #1568) | 升級套件而非降級 skill;確認 references/ 側車存在 |
head -1 截斷,退出碼 0 會隱藏「輸出被截斷」的事實。你怎麼在「尊重 pipe 慣例」與「誠實報告」之間取捨?graphify fresh,輸入 repo 根,輸出「圖是否落後 HEAD」並給 exit code。列出它的判斷規則與邊界案例(未 commit 的變更算不算?)。