系列:從現場踩坑到 AI 工具 — IT Diagnostic Agent 開發實錄
Phase 2 做完 JSON Import 功能之後,我覺得那個「匯入」按鈕的顏色不夠明顯。
匯入是一個會覆蓋現有內容的操作。我希望它看起來就跟旁邊那個「匯出」不一樣,讓人按下去之前會停一秒。
所以我跟 GSD 說:把匯入按鈕的顏色改掉。
它改了。回報完成。
我打開來看,看不出任何差別。
接下來的排查過程,我要完整寫出來,因為它比結論有意思。
我把畫面貼給 Claude,說按鈕顏色沒變。
Claude 分析了 Data Export 頁籤,指出有三個元素的樣式不一致:
它列了三個候選,分析得有條有理。
但沒有一個是我要問的東西。
我要的就是那個「匯入」按鈕,不是頁腳、不是標題。
這裡我學到一件事:我最初的描述不夠精確。 我說「按鈕顏色沒變」,但那個頁面上有好幾個按鈕。Claude 在資訊不足的情況下,選擇列出所有可能——這正好是我在 Day 02 和 Day 13 批評過的行為模式。
(回頭看很好笑:我花了三天論證「不要列舉所有可能性」,然後自己給了一個模糊的問題,換來一份三項清單。)
我澄清了:我說的是 Import 按鈕。
Claude 去看了 GSD 實際的改動,然後說:
GSD 把按鈕改成了 ghost / outline 樣式——透明背景加一圈 border。
而問題是:那個按鈕原本就長得差不多是那樣。所以「改了」和「沒改」在視覺上幾乎沒有差別。
GSD 沒有偷懶,它真的改了。改完的結果就是看不出來。
到這裡我們知道「改了什麼」,但還不知道「為什麼它會這樣改」。
而這一步,是我提供了關鍵素材才解開的。
因為 GSD 的設定裡,我把 commit planning docs 開成 Yes(Day 21 那張表的第三行)。
所以每一個 Phase 都有完整的規劃文件躺在 repo 裡。
我把 Phase 2 的規劃文件整份貼給 Claude。
然後真正的原因出現了。
在 02-CONTEXT.md 這份規格文件裡,有一條編號 D-03 的設計決策,內容明確寫著:
匯入按鈕使用 secondary / muted style(次要 / 低調樣式)
那是我自己在 Phase 2 規劃階段定下的。當時的想法大概是「匯入是次要功能,不要搶主要動作的視覺焦點」。
而關鍵在於 GSD 的運作方式:
GSD 在每一個執行週期開始前,都會重新讀取正典規格檔。
所以事情的順序是這樣:
規格檔 D-03:匯入按鈕 = 次要/低調樣式
↓
我在對話裡說:把匯入按鈕改明顯一點
↓
GSD 這一輪照做了(改成 outline,勉強算「改了」)
↓
下一個執行週期開始
↓
GSD 重讀規格檔,看到 D-03 說要低調
↓
我的對話指令被規格覆蓋
我在 chat 層下的指令,跟規格檔在打架。而規格檔永遠贏,因為它每一輪都會被重新讀取。
我原本的心智模型是:跟 agent 說話,就像跟人說話。 你告訴它一件事,它就記住了。
錯了。
Agent 有兩種記憶,而它們的權重完全不同:
| 對話層 | 規格層 | |
|---|---|---|
| 存在形式 | 這一輪的上下文 | repo 裡的文件 |
| 生命週期 | 這一輪結束就沒了 | 一直存在 |
| 每輪執行時 | 不一定在上下文裡 | 必定被讀取 |
| 衝突時誰贏 | 輸 | 贏 |
所以那條教訓可以寫成一句話:
要改行為,就改規格檔。不要在對話裡下指令。
對話層適合的是「這一次幫我做什麼」。
規格層適合的是「以後都要怎樣」。
我把一個「以後都要怎樣」的需求,下在了「這一次做什麼」的層級。難怪它撐不過一輪。
事情還沒完。
在診斷過程中,Claude 早先建議過:加一個 .btn-import 的 CSS class 來控制樣式。
後來它自己主動撤回了這個建議,理由是:
這違反了本專案的 inline-styles-only 限制——不新增 CSS class。
而那條限制,正是 Day 21 提到的、GSD 寫進 CONVENTIONS.md 裡的那條專案慣例。
我覺得這件事值得記錄,因為它展示了一個很具體的現象:
同一個 AI,在讀過專案慣例文件之後,會推翻自己在沒讀之前給的建議。
不是「AI 前後不一致」,是「資訊量改變了,結論就該改變」。
而這也反向證明了規格文件的價值——它不只約束 agent,也約束你和 AI 之間的討論。
02-CONTEXT.md 的 D-03 改成:
匯入按鈕使用 amber (#d97706) 警示色,語義上標示這是一個破壞性 / 覆寫操作
注意這裡的用詞:不是「改成橘色因為比較顯眼」,而是**「警示色,語義上標示破壞性操作」**。
理由要寫進規格,不只寫結論。 因為下一次有人(或有 agent)想改這個顏色時,他會看到理由,而理由比結論更能防止錯誤的修改。
在 btn-import-json 上直接套 inline style,符合專案慣例。
這是現在跑在線上的實際程式碼:
<button id="btn-import-json" onclick="importJSON()"
style="width:100%;margin-top:8px;padding:10px 0;
background:#d97706;color:#fff;
border:1px solid #b45309;border-radius:6px;
cursor:pointer;font-size:14px"></button>
#d97706 是琥珀色,#b45309 是更深的琥珀當邊框。inline style,沒有新增 class。
順序很重要:先改規格,再改程式碼。 反過來做的話,下一個執行週期又會被打回去。
核心 vs 外部
表面問題是「顏色不對」(外部)。核心問題是「我用錯了指令層級」。
如果只解決外部問題——手動把顏色改掉——那下一次同類問題還會發生,而且我不會知道為什麼。
可控 vs 不可控
我控制不了 GSD 每輪重讀規格的行為(那是它的設計)。我控制得了規格檔的內容。
把力氣花在可控的那一端,這是整件事的解法。
已成立 vs 假設
「跟 agent 說話它就會記住」是我的假設。
「規格檔每輪重讀,且優先於對話」是查證後成立的事實。
而讓假設變成事實的關鍵素材,是那份規劃文件。如果我當初 commit planning docs 選了 No,這個問題我大概到現在還在瞎猜。
一個設定選項,救了一次排查。
寫完之後我發現這個模式在別的地方也一樣:
CLAUDE.md
README、CONTRIBUTING.md、lint 設定凡是「每次都會被讀取的檔案」,都是規格層。凡是「這次講的話」,都是對話層。
而幾乎所有 agent 系統都是規格層優先。因為那正是規格層存在的目的——它就是為了不被單次對話推翻而設計的。
我一開始把這個特性當成 bug,實際上它是 feature。
這件事的起點是一個顏色,終點是一個關於指令層級的認知。
而中間的過程包含:我的問題描述不精確、Claude 診斷了兩次才對、Claude 推翻自己的建議、我提供規劃文件才解開真因。
沒有任何一方是全對的,但迴路收斂了。
我覺得這才是人機協作的真實樣子。不是「AI 一次給出完美答案」,而是兩邊各自補上對方缺的資訊,直到問題被解開。
Claude 缺的是專案規格。我缺的是對 agent 記憶層級的理解。
各補一塊,就走出來了。
明天預告: 那個琥珀色不是隨便選的——它是一整組決策原則在 UI 上的一次落地。而那組原則的來源,是我在回答 GSD 一連串提問時被迫想清楚的東西。
作者:Rich Chang | IT 基礎建設工程師 | 越南・柬埔寨・台灣