iT邦幫忙

2026 iThome 鐵人賽

DAY 16
1
Claude AI

盡信 Claude,不如無 Code — 心法與全端實戰系列 第 16 篇

Day 16 裝一個 AI 工具之前,怎麼知道它動了什麼

  • 分享至 

  • xImage
  •  

昨天接了一支別人的 MCP server,問題是「怎麼接」。今天往前一步:裝之前,你怎麼知道它會動你什麼?

現在的 AI 工具很少只是一支程式。它的 install 會偵測你裝了哪些 coding agent,然後替每一家寫 MCP 設定、注入 rules、放 skill、掛 hook —— 一個指令,散到 repo 和家目錄幾十個地方。而你看得到的只有三樣:README、--dry-run、裝完之後的 git status。前兩樣是工具自己說的,第三樣只看得到 repo 裡沒被 ignore 的那一塊。

今天拿 code-review-graph(以下簡稱 crg)當受試者 —— 一個把 codebase 掃成圖、讓 agent 少讀檔的工具,三萬多星,MIT 授權,怎麼裝已經很多人寫過。這篇不教怎麼裝它,是拿它示範:一個認真、誠實的工具裝下去之後,靠那三樣你能知道多少。 答案是一半不到。


方法:在副本上裝一次,前後拍快照

Day 4 用過 checksum 前後比對,但那次只比 ~/.claude 一個目錄。安裝程式寫的地方多得多,所以這次網要撒大,而且先在副本上裝,不碰你真的 repo 和家目錄:

# 在要裝的 repo 根目錄執行
LAB=$(mktemp -d)
git clone -q --no-hardlinks . "$LAB/repo"               # 1. 在副本上裝,不碰你的 repo
mkdir -p "$LAB/home"/{.codex,.copilot,.gemini/antigravity,.config/opencode}
touch "$LAB/home/.codex/config.toml"                   # 2. 假 HOME:只放你機器上真的有的工具目錄

snap() { (cd "$LAB" && find repo home repo/.git/hooks -path repo/.git -prune -o -type f -print0 \
          | sort -z | xargs -0 shasum) > "$LAB/$1.txt"; }  # 3. 快照:repo 含被 ignore 的、.git/hooks、假 HOME

CRG=$(uvx --from code-review-graph==2.3.8 which code-review-graph)   # 先用真 HOME 把工具抓好
cd "$LAB/repo"
snap 0-before
HOME="$LAB/home" "$CRG" install --dry-run > "$LAB/dry-run.txt"
HOME="$LAB/home" "$CRG" install -y        > "$LAB/install.txt"
snap 1-installed
git status --short > "$LAB/git-status.txt"
HOME="$LAB/home" "$CRG" uninstall -y      > "$LAB/uninstall.txt"
snap 2-uninstalled

diff "$LAB/0-before.txt" "$LAB/1-installed.txt"   | grep -c '^>'   # 4. 裝了動了幾個
diff "$LAB/0-before.txt" "$LAB/2-uninstalled.txt"                  # 5. 移除後還差什麼

四個設計各有原因:

  • repo 用 clone 的副本:裝壞了整個丟掉。被 ignore 的本機檔(例如你自己的 .claude/settings.json)不會跟著 clone 過來,要看它怎麼跟既有設定合併,就自己 cp 進去。
  • 假 HOME:HOME 指到一個空目錄,裡面只建你機器上真的有的工具目錄。安裝程式靠這些目錄判斷「你有裝 Codex」,所以它會照真實情況寫,只是寫進假的家。crg 是 Python,Path.home() 讀的就是 $HOME;我翻過它的原始碼,家目錄以外的路徑(/Applications 之類)只讀不寫。換成別的工具要自己確認這一點 —— 會寫 /usr/local、或不看 $HOME 的工具,假 HOME 攔不到,那種就直接開 VM。
  • 快照要包含 .git/hooks 和被 ignore 的檔:為什麼,下一節就看到。
  • dry-run、真裝、移除各拍一次:三次對照,才分得出「它說的」「它做的」「它收回的」。

我在 apple/container 上跑,工具先抓好之後,整段 3 秒。

它說的,和它做的

dry-run 說 18 個,實際動了 39 個

--dry-run 說 真的跑之後
MCP 設定 8 個寫入目標 8 個 ✅
指令注入 9 個檔 9 個 ✅
.gitignore 會加一行 加了 ✅
skills 沒提 3 個平台 × 4 支 = 12 個 SKILL.md
hooks 沒提 6 處(含 ~/.codex/hooks.json)
git hook 沒提 .git/hooks/pre-commit
OpenCode 外掛 沒提 ~/.config/opencode/plugins/crg-plugin.ts
備份檔 沒提 .gemini/settings.json.bak

--dry-run 漏掉的,除了一個備份檔,全是會改變 agent 行為、或會自己執行的那一類 —— skill 是 agent 會讀進去照做的指示,hook 是每次編輯後自動跑的指令,pre-commit 是每次 commit 前跑的腳本,OpenCode 外掛是一支會被載入執行的 TypeScript。它預告了設定檔,沒預告行為層的東西。加起來 39 個檔 —— 37 個新增、2 個改寫:

repo 內      33 個新檔(含 .git/hooks/pre-commit) + .gitignore 改 1 行
家目錄       4 個新檔 + ~/.codex/config.toml 加 6 行

git status 看不到的,大多是會自己跑的那一半

裝完最常見的檢查是 git status。它列了 15 行:

 M .gitignore
?? .codebuddy/
?? .cursorrules
?? .gemini/
?? .github/instructions/
?? .kiro/
?? .mcp.json
?? .qoder/
?? .windsurfrules
?? AGENTS.md
?? CLAUDE.md
?? CODEBUDDY.md
?? GEMINI.md
?? QODER.md
?? opencode.jsonc

看起來很完整。對照快照,它沒列出來的是這些:

位置 內容 為什麼看不到
.claude/settings.json、.claude/skills/ 4 支 Claude Code 的 hook 與 skill .claude/ 在這個 repo 的 .gitignore 裡
.vscode/mcp.json VS Code Copilot 的 MCP 設定 .vscode/ 同上
.git/hooks/pre-commit 每次 commit 前會跑的腳本 .git/ 從來不在 git status 裡
家目錄 5 個檔 全域 MCP 設定、全域 hook、OpenCode 外掛 不在 repo 裡

git status 看得到的是 rules 檔和 MCP 設定;看不到的是 Claude Code 的 hook 和 skill、pre-commit、全域設定 —— 會自己跑起來的,大多在看不到的那一邊。 這不是 crg 故意藏,是 .claude/ 本來就常被 ignore(Day 11 我自己的 repo 也是)。但結果一樣:只看 git status 的人,會以為它「就多了幾個 md 檔」。

兩個全域寫入

~/.codex/config.toml 裡有一行寫死的 cwd:

[mcp_servers.code-review-graph]
command = "uvx"
args = ["code-review-graph", "serve"]
cwd = "/Volumes/.../Github/container"    # ← 我當時所在的那個 repo
type = "stdio"

我在 A 專案裡裝了它,全域的 Codex 設定就永遠指向 A 專案。之後在 B 專案開 Codex,它問到的圖還是 A 的。

~/.codex/hooks.json 是全域的,matcher 是 Write|Edit|Bash:

"PostToolUse": [{ "matcher": "Write|Edit|Bash",
  "hooks": [{ "command": "... code-review-graph update --skip-flows || true" }] }]

一次針對單一 repo 的 install,換來一個在你每一個專案、每一次編輯之後都會跑的 hook。

移除之後,沒有回到原點

uninstall -y 移除 25 個路徑、編輯 14 個共用檔,全域搜尋 code-review-graph 字樣零命中。以一個裝了 39 個地方的工具來說,這算誠實。

但比對快照,它沒有回到裝之前的狀態:

~/.codex/hooks.json                    留下 {  }          ← 裝之前不存在
~/.copilot/mcp-config.json             留下 {"mcpServers":{}}
~/.gemini/antigravity/mcp_config.json  留下 {"mcpServers":{}}
~/.codex/config.toml                   多一個空行
repo 內                                .claude/settings.json .vscode/mcp.json .mcp.json opencode.jsonc
                                       .codebuddy/ .gemini/ .qoder/ 仍在

它清掉了自己寫的內容,但留下了自己建的容器。 我逐一驗過,每一個都是合法的空設定;但其中 .claude/settings.json 和 .vscode/mcp.json,git status 一樣看不到 —— 「乾淨移除」和「回到原狀」之間的差,只有快照看得見。

已經有設定的時候:加得很小心,拿走的時候弄壞了

上面是空目錄。真實的機器上,.claude/settings.json、.mcp.json 多半本來就有你自己的東西 —— 第一次裝看它新增什麼,第二次要看它敢碰你什麼。

我在副本和假 HOME 裡先放好十個「自己的」設定檔,再重跑一次:.mcp.json 裡有一個 my-db server,.claude/settings.json 裡有 allow npm test、deny git push 和一個自己的 hook,CLAUDE.md 有兩條規則,家目錄的 Codex、Copilot 設定也各有東西。

install 很客氣。 十個檔全部是合併,沒有一個被覆蓋:你的 server、hook、權限規則都還在,它的東西加在旁邊;CLAUDE.md 在文末追加一段。要掛 hook 的 JSON,還先存了一份 .bak。

uninstall 大多也收得乾淨 —— CLAUDE.md、.gitignore 逐字還原,MCP 設定內容還原。只有一個檔例外,.claude/settings.json 的結尾變成這樣:

    "PreToolUse": [ … 你自己的 hook … ],

  }

它把自己的 hook 拿掉之後,留下一個多餘的逗號。這在嚴格的 JSON 裡是語法錯誤,而 Claude Code 讀這個檔用的就是嚴格 JSON。同一份內容,我對照跑了兩次:

uninstall 留下的版本 只拿掉那個逗號
你自己的 hook 沒跑 有跑
deny: Bash(git push:*) 失效,git push --dry-run 直接執行 擋下來
Claude Code 有沒有提示 claude -p 沒有;互動模式啟動時跳 Settings Error —

互動模式下,Claude Code 一啟動就跳出 Settings Error,說「有錯的檔整份略過」,讓你選修、退出,或不帶這些設定繼續。但 claude -p、排程、CI 這些不互動的跑法,一聲不吭。

裝了再移除,你設定的「禁止 push」就悄悄沒了,至少在沒人看著的那些跑法裡。檔案還在,內容看起來也都對,只是整份不再生效;它又在 .claude/ 裡,git status 看不到。原因在它的原始碼:uninstall 改完會自己驗證一次,但驗證用的是會自動忽略多餘逗號的 JSONC 規則 —— 它驗過了,Claude Code 讀不了。2.3.9 也一樣重現,我回報給作者了(#1068)。

這種事空目錄測不出來。所以快照要多一個前提:副本裡要放一份你自己的設定。

真的要裝,怎麼裝

量完之後,真的要裝的時候我會這樣做:

  1. 先找縮小範圍的旗標。 crg 有 install --platform claude-code。9/13 我在 VM 裡用它裝了三次,家目錄 23 個設定檔前後零差異 —— 上面那些全域寫入,全是「偵測到你有 Codex、Copilot」才觸發的。多平台的工具,裝之前先找有沒有類似的旗標。
  2. 在副本 + 假 HOME 預演一次,把 diff 留著。 之後要移除,你手上有一份「它應該收回什麼」的清單,殘留一眼就對得出來。
  3. 要長期跑、又碰得到你的家目錄的,裝進 VM(Day 4 第三級),主機一個檔都不給它碰。
  4. 移除之後,確認你自己的設定檔還讀得起來。 python3 -m json.tool .claude/settings.json 一行就夠 —— 互動模式會跳 Settings Error,但 claude -p 和排程不會。

還有一件快照看不到的事:crg 自己宣告 8 個相依,裝完落地 74 個套件。其中 opentelemetry 是 MCP SDK 的傳遞相依;uncalled-for 聽起來可疑,查了是個正經的 async DI 套件。兩個都不是問題,但我是查了才知道不是問題。 快照管的是它寫了哪些檔,管不到它帶進來的程式碼 —— 那是另一層。同樣管不到的還有它跑了什麼:連了哪裡、起了哪些程序;shasum 只比內容,連檔案權限被改都看不出來。快照回答的是「它寫了什麼」,不是「它做了什麼」。

另一半:它有沒有做到它說的

知道它動了什麼,只回答了「裝了會怎樣」。另一半是「裝了值不值得」,這裡只講結論,實測細節在參考資料。

README 主打 ~82x median per-question token reduction(range 38x–528x,實驗當時的數字;9/13 重查已改成 ~65x)。它的分母是 naive full-corpus tokenization,也就是把整個 codebase 塞進去 —— 作者自己在旁邊註明:

upper bound no real agent actually pays

沒有 agent 真的那樣讀檔。Day 2 我引過 graphify 的「查一次 29k、整包塞進去 403k」,用的也是同一個分母,那個數字一樣要打這個折。

它最核心的承諾是「改這一行會炸到哪」。我拿 apple/container 三個真實 commit(改 3、4、20 個檔)驗了兩件事:

  • 它自己的 impact,三次都回 0 個受影響,還附一句 this 0 is a real absence。但同一張圖用 MCP 的 callers_of 問,中、大兩個 commit 分別回 3 個、4 個呼叫者,跟 git grep 一致 —— 邊在圖裡,impact 沒走到。
  • 有它、沒它各讓 Claude review 三次。 小改動有它多花 56%,找到沒它漏掉的兩個生產呼叫者;中改動兩邊一樣好,有它多花 44%;大改動沒它 30 輪用完沒有答案,有它 25 輪交出完整 review,還抓到一個真的回歸(同一個檔對 apiserver ping 了兩次),反而便宜 19%。逐字稿裡,有它的三次都是先問圖、再用 Grep 自己核 —— 功勞一半是圖,一半是它注入 CLAUDE.md 的「先問圖、再對原始碼」流程。

所以就這三個 commit 看,我的答案是:大改動的 review 值得,日常不值得;而那套流程,不裝它也能寫進自己的 CLAUDE.md。


總結

裝一個 AI 工具,你直接看得到的三樣東西,今天各漏了一塊:README 的倍數,分母不見了;--dry-run,漏了會改變行為、會自己執行的那一類;git status,看不到 .claude/、.git/hooks 和家目錄。後兩個洞疊在同一個地方 —— 會自己跑起來的東西。

所以方法只有一個:別問它動了什麼,自己拍快照。在副本上、用假 HOME、dry-run/install/uninstall 各一次,副本裡放一份你自己的設定。crg 那次,工具抓好之後整段 3 秒;方法本身換哪個工具都能用。

crg 不是反例。它 MIT、有 dry-run、有 uninstall,install 會合併、不覆蓋你的設定。正因為它已經是好的那一種,只靠它自己說的還是看不到一半 —— 連它的 uninstall 都會在你的設定檔留下一個讓規則失效的逗號。其他工具就更不用說了。

這一篇留下的心法:

工具說它要動什麼(dry-run)、說它清了什麼(uninstall),都要拿快照對過。git status 看不到的地方,正好是會自己跑起來的地方。

明天:一條我以為寫在 CLAUDE.md 裡三個月的規則,去查 git 才發現那三個月它連寫都沒寫 —— 所以這次把它寫成 hook,擋在寫入之前。


參考資料

  • tirth8205/code-review-graph:github.com/tirth8205/code-review-graph(第一次查證 2026-07-27:26,608 stars、README 主打 ~82x;2026-09-13 重查:31.4k stars、README 已改為 ~65x、range 36x–376x;建立於 2026-02-26,MIT)。baseline 的註記與限制出自該 repo 的 README
  • install 足跡:2026-09-25 主機,code-review-graph 2.3.8(uvx)、apple/container @ d6de5694,腳本即本文那段,原樣跑過;HOME 指到只建了 .codex/、.gemini/antigravity/、.copilot/、.config/opencode/ 的暫存目錄。9/10 在主機真 HOME 裝過一次(當時做了 checksum 快照、實驗後 uninstall 並逐檔比對還原),家目錄的寫入與殘留跟假 HOME 這次一致
  • 已有設定時的合併與移除:2026-09-26,同樣是副本 + 假 HOME,2.3.8 與 2.3.9 各跑一次;預先放 10 個自己的設定檔;壞掉與修好的 settings.json 各用 claude -p --allowedTools Bash(Claude Code 2.1.282)驗 hook 與 deny;主機家目錄前後零差異;同一組對照在一台全域設定沒有任何權限規則的乾淨 VM(Claude Code 2.1.283)重跑,結果相同;互動模式另外開一次壞檔,啟動即跳 Settings Error(2.1.283)
  • --platform claude-code 與六次 review:2026-09-13,VirtualBuddy VM(macOS 26.6.2、Claude Code 2.1.270、uv 0.12.13);三個 worktree 各 install -y --platform claude-code + build;claude -p --output-format json --max-turns 30;真值用主機的 git show 與 git grep
  • 延伸閱讀:讓 Claude 先看懂你的專案 — 用 Graphify 把 codebase 掃成一張知識圖

上一篇
Day 15 接一個 MCP server 的完整流程:從 .mcp.json 到它第一次撞牆
下一篇
Day 17 Hook:規則寫下來不夠,要擋在寫入之前
系列文
盡信 Claude,不如無 Code — 心法與全端實戰 共 18 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言