
PM 在 JIRA卡上寫了兩句:「已付款的訂單取消時,要提出退款要求;同一張訂單再取消一次,不能重複提出。」
工程師的直覺動作很簡單:打開 Claude Code,貼上這兩句,說「照這個改」。Day 8 我們查對了該改的位置;今天的問題是,位置對了、需求也寫了,現在可以開工了嗎?
這是第二幕「AI 開發實戰」的第四篇:Day 6 約好怎樣算完成、Day 7 交理由、Day 8 查位置,今天站在動手前的最後一步。我決定先照直覺做一次,看它會交回什麼。本文用同一份教學程式做三組唯讀演示,差別只在給它看多少材料:只給程式(只有 src/、tests/)、加呼叫端(多了 API)、給完整規格(多了規格與決策文件)。三組使用不同材料與提示,並非同一輪對話逐步補齊;下面看它們各自查到了什麼,還留下哪些決定。
案例還是那份訂單取消的教學程式:一個 Cancel 函式、三個布林欄位(是否出貨、是否付款、是否取消)、三條 v1 測試。第一組「只給程式」:我只給 Claude Code 兩樣東西,PM 那兩句話,以及 src/ 與 tests/ 兩個資料夾。用 --tools Read,Grep,Glob 讓它只能讀,要求「先不要改程式,告訴我要改哪裡、改成什麼、哪些測試要改」。
十九秒、五個回合,它交回這份計畫。重點是先擋住重複取消,再把舊測試的預期改成新規則:
要改的檔案:src/Domain/Cancellation.cs
- 第 16–21 行:新增判斷,若訂單已經是 Cancelled == true,直接回傳原 order、
RefundRequested: false(不可重複提出退款)。
- 未取消、未出貨的情況下取消時,RefundRequested 改成 order.Paid。
- 同步更新第 16–19 行的註解,移除「已付款不提出退款」「待確認」等舊敘述。
要改的檔案:tests/DomainTests/Program.cs
- 第 13 行(v1-3):斷言改成「已付款取消要提出退款」,RefundRequested 應為 true。
- 新增測試 v1-4:已付款、已取消的訂單再取消一次 → RefundRequested == false。
需要我接著實作這些修改嗎?
看起來可以開工了。兩句需求它都接住了:已付款要退、重複取消不重複退。它還自己讀到程式註解裡那句「已取消的訂單再次取消屬於未確認行為」,知道舊測試 v1-3 的預期要反過來。以 Domain 這一層來說,這份計畫是對的。
同一個提示我又跑了兩次。三次的 Domain 計畫都對;後兩次多了一段第一次沒有的話:「目前程式碼原本明確標註『已付款取消不要求退款』是已定案的 decision,這次需求與其衝突,建議先跟 PM 確認是否正式推翻該決策,再動工。」它從程式註解讀到舊決定,自己停下來。但三次都沒有問一件事:這個旗標算完之後會流到哪裡。這組材料沒有 API。這三次都沒有主動追問下游;我不能把「它沒問」當成「沒有影響」。
那份計畫沒有錯,但它回答不了三件事,因為答案不在它讀到的檔案裡。
| 它看不到的 | 問題 | 缺了會怎樣 |
|---|---|---|
| 呼叫端 | RefundRequested 算完之後,誰拿去用? |
旗標會進 API 回應、通知、日誌;改了語意卻沒查下游,下游照舊解讀。三次都沒問,檔案不在它手上 |
| 決定的來源 | 舊測試「已付款不退款」是 v1 Owner 當時的決定;這次推翻它,誰確認的? | 把舊決定當成 bug 修掉,沒人知道規則換過。三次裡兩次它從註解讀到、停下來問;一次直接寫進計畫 |
| 範圍外的提案 | 同資料夾有一份通知契約,狀態是 proposed,要一起做嗎? | 順手做進去,一張票變成兩張 |
PM 的兩句話交代了方向,但這三樣不在裡面。下一組就把其中一樣補給它:API 程式。

下面三節照這張圖由左到右走。每欄中段是它多問出來的,最下面那格是它在這組栽的跟頭。
第二組「加呼叫端」換用一份刻意留白的教學草案 spec.md,搭配 Domain、測試與 API 程式(src/Api/Program.cs);這組沒有提供決策紀錄與通知提案。草案已列出重複取消、併發與通知三項未決問題。我用了 Matt Pocock 的「grill me」訪談方法(本篇採用的 skill 名稱為 grilling),讓 Claude 先查程式,再追問需要我決定的事。這個 skill 是放進 plugin 的提問方法,不是 Claude Code 內建功能;本篇只實跑第一輪提問,尚未完成多輪訪談。它讀完程式之後,沒有直接給計畫,而是先列現況、再提出四個問題。第一題是這樣寫的:
對已經
Cancelled=true的付款訂單再次呼叫/orders/{id}/cancel,Program.cs:73-74判定為 idempotent,不會發通知、不會更新 store。但 API 回應仍會帶result.RefundRequested。
(a) 一律回傳 false(語意:這次呼叫沒有新動作……);
(b) 依訂單當下Paid狀態計算(可能造成refund_requested: true但實際沒發通知的不一致觀感);
(c) 回傳上一次真正取消時的歷史結果(需要OrderStore增加歷史欄位……)。
這題正是「只給程式」那組漏掉的:第二次取消時,這個欄位想表達的是「本次動作」還是「訂單歷史」? 第一組已經規劃了重複取消的判斷,但沒有查 API 如何使用結果;看到 API 把旗標透傳進回應與通知,才知道同一個值在兩處會被讀成不同意思。

依 API 第 69–89 行重繪。左邊是 Domain 算出的值,右邊是它去的三個地方:回應與日誌兩條路都到,通知只走 ok 那條。下方兩種讀法,PM 的兩句話沒說是哪一種。
PM 沒有回答這件事,Claude 也沒有替 PM 決定,它把選項列出來,標明建議,然後停在「尚未取得負責人回覆」。
另外三題:第二題問併發取消(兩個請求同時打同一張單,讀取、判斷、寫回不是原子操作)要不要納入這次,它建議另開票;第三題問通知,refund_requested 欄位已經在通知 payload 裡,下游收到 true 就夠了嗎,還是要新的通知種類?它自己補了一句:「這點程式碼看不出來,需負責人告知或另外調查下游服務。」第四題問舊測試 v1-3 是否視為過時,這題的建議需要我核對,後面再看。
第二組還有一個支線:同一批材料,換成直接要它給修改計畫,交付會不一樣嗎?我用乾淨副本各跑三次。提示與設定也不同,因此比較的是整套用法,不能單獨歸因於 skill:
| 觀察 | 先查再問+grilling | 直接要修改計畫 |
|---|---|---|
| 交回什麼 | 現況掃描+3 到 4 個問題,每題附選項,停下來等人 | 修改計畫:改哪幾行、改成什麼、哪些測試要改 |
| 重複取消那件事 | 三次都列為第一或第二題,要人選 | 三次都看到了,寫在「邊界事項/給 RD 的提醒/本次不處理」 |
| 有沒有替人決定 | 三次都寫「我不代為拍板」 | 一次用 !order.Cancelled && order.Paid 保住舊行為,等於默默選了一邊 |
直接要修改計畫時,其中一份是這樣寫的:
改完後,重複取消一筆已付款訂單,API 仍會回
refund_requested: true,即使沒有真的轉態、也不會觸發通知。spec.md 第 6 行把「重複取消應回傳什麼」列為未決,所以這次不用修,但建議在 PR 描述或交接紀錄裡點出來。
兩種做法都看見了問題。一邊交回選項等人回答,另一邊把問題留在修改計畫的提醒裡。對我有用的差別,是發現之後有沒有把決定交給負責人。 這次訪談把草案裡的未決事項接到程式位置與具體選項;接下來仍要由人確認,才能決定依賴它的設計。
第三組「給完整規格」:把 specs/rules-v2.md 與兩份決策紀錄一起給它,補的是規格層的差異。這段只看兩件事:兩條新規則撞在哪一格,以及哪份文件不屬於這張票。它把既有測試要反轉這件事標成「規格要求,不是我的推論」,並找出兩條規則的交會點:
兩條規則的交會點只有一種輸入組合:未出貨、已取消、已付款(rules-v2.md:25 SC-07)。若實作先判斷
Paid再判斷Cancelled……「已取消+已付款」的訂單再次取消時會被誤判成RefundRequested=true。現有測試只驗到未取消輸入下的三種組合。
它也讀出那份通知提案內部自相矛盾:notification-contract-v2.1.md 第 16 行寫重複取消「回 200/ok=false 或 409」,decisions-v2.1.md 第 6 列卻決定「200 而非 409」。這不在這張票的範圍,但值得回報。
查完之後,還有三個結論不能直接接受:兩處把材料缺漏推論成事實,另一處把可行寫法當成唯一要求。
verify.py),八格裡,「提前返回」與「組合條件」兩種寫法結果完全相同;只有「只把 false 換成 order.Paid、不看 Cancelled」那種寫法會在其中一格分歧,就是 SC-07:未出貨、已取消、已付款,再取消一次時它會回 true。「只給程式」那組交回的計畫有先判 Cancelled,不是這種寫法;這格是給只想改一行的人看的。規格要的是這張表的結果,不是分支順序。decisions.md 第 3 列,材料裡沒有這份檔案,它就建議把舊測試「視為過時產物」;不載 skill 的對照跑用 Glob 確認檔案不在,判成「失效引用」。沒附進材料不等於原決定不存在,這格我改成「來源待補」。這裡有兩種需要人核對的跳躍:沒看到的,被說成不存在;可行的寫法,被說成非這樣不可。延續 Day 8,每個結論要附來源,還要核對來源是否真的支持這個判斷。
官方 Best practices 寫得很直接:先探索、再規劃、然後才寫程式。今天做的就是前兩步。互動模式按 Shift+Tab 進 plan mode,它只能讀檔回答;批次用 --tools Read,Grep,Glob,從工具表拿掉 Write、Edit、Bash。兩種都是把「先不要改」交給工具,不靠它自律。提示可以直接用這份,把檔名換成自己的:
先不要改程式。請讀 [本次需求]、[現有程式]、[現有測試]、[呼叫端]。
列出:目前行為、新要求,以及兩者的差異,每項附檔案位置。
找出這次修改會影響誰:誰呼叫這個函式、結果被寫到哪裡。
舊測試的預期若與新需求相反,指出是規格改了還是程式原本就錯。
需要人決定的事列成問題,附選項;不要替我決定,也不要把提案當已接受需求。
未提供的檔案不能推論成不存在。
這份提示是上圖三欄的合體:第一行補材料、中間三行要它追影響並列出待決、最後一行守住每欄最下面那格。它不是只改一個欄位的對照提示。
想試訪談做法,可以使用配套材料裡已備好的 plugin:
lab-intake 目錄,確認有 spec.md、src/、tests/ 與 interview-plugin/。claude --plugin-dir ./interview-plugin --permission-mode plan。/intake-interview:grilling,請它讀 spec.md、src/、tests/,先查程式再提出問題與選項。預期拿到的是附程式位置的待答問題;收到之後,先核對位置與推論,再由負責人回答。這還不是開工核准。
把前面的查證與待決事項收成這張交接卡,每列接一個動作、一個人,並標明那一格是 Claude 說的還是我整理的:
| 開工卡 | 內容 | 來源 | 誰接 |
|---|---|---|---|
| 要改 | 已付款首次取消的退款旗標;同步反轉舊測試 v1-3 的預期 | Claude 三次計畫皆有 | RD |
| 要補驗收 | 已取消且已付款再取消、已出貨且已付款;現有三條測試都沒餵過「已取消」的輸入 | Claude「給完整規格」那輪指出 | RD |
| 保持不變 | 已出貨不可取消;三個型別簽名 | 規格明文,Claude 照抄 | RD |
| 不納入 | 真實金流、通知契約、API 回應碼改版 | 我劃的線;Claude 曾把通知矛盾列為待回報 | 另開票 |
| 待決 | 重複取消時旗標代表本次動作還是歷史?下游怎麼讀這個欄位? | Claude 的 Q1、Q3,我改成問句 | PM/業務 Owner;下游 Owner |
「待決」那兩格擋的是設計,不是全部工作。 查程式、追資料流、比較方案可以先做;方案依賴未決規則的地方,先標明假設。這是讀者可以帶走的範本,附件裡有空白版。
開工卡整理好了,放進團隊使用時,還有一件事要約好:交出去之前,誰先確認哪些內容。
每位 PM 的產品想法可以不同,但不能讓 RD 每次重新猜,哪些需求已定案、哪些只是提案、例外要找誰決定。AI 讓文件與程式產出更快,這些沒講清楚的地方,也可能更快堆到下一個人身上。
Tobi Lütke 談到的「AI 垃圾手榴彈」(Slop Grenade),就是把未經核對的 AI 產出交出去,讓別人接手查錯與補洞。訪談方的原始說明說的正是這種工作轉嫁。PM 直接丟一份沒核對的 AI spec 會如此,RD 把連程式都沒查過的問題整包丟回 PM,也一樣。
所以這張卡的用法是兩邊各認領一欄:「來源」欄讓 RD 說明哪句是查到的、哪句是整理的,「誰接」欄讓 PM 知道自己被指名在哪幾列。約定的是欄位,不是態度。
還有一個動作別漏掉:問完之後,答案不能只留在聊天裡。Day 6 的 intent.md 這時派得上用場—為什麼做、期待改善什麼、有哪些限制寫回它;確認的行為與驗收條件寫回 spec.md;待決的留在卡上,接著才由 RD 整理實作的 plan.md。官方 SDLC playbook 也是這個順序。這是建議的接續流程,本篇實跑沒有做到這一步。開工卡本身只是這些資料的交接摘要,指出依據在哪、哪些待決、由誰確認,不必再複製一套規格;團隊若用 Jira 管需求,正式來源與版本就留在工單上。
自己產出得快,還不等於整段工作走得快;下一個人接得住,才有機會一起往前。
兩句需求,Claude 十九秒就交回一份 Domain 層正確的計畫,還主動問要不要接著實作。三組演示補的是三種材料:給它 API,它才問得出旗標代表本次動作還是訂單歷史;給它規格與決策文件,它才找得到兩條規則撞在 SC-07 那一格。開不了工的不是它,是那三樣它看不到的東西。
需求是開工的起點;開工卡才是設計討論的起點。
這張卡上的「待決」明天要面對:只改一個欄位,Claude 要先查哪些地方?這份訂單取消程式會一路用到 Day 16,從規格做到 PR。
參考資料:
本文實作說明
885e521),規則、決策與通知提案為教學設定;退款旗標是記憶體標記,不連付款服務。教學 Owner 不代表公司業務核准。| 實跑 | 材料 | 參數 | 回合/秒/費用估值 | 原件 |
|---|---|---|---|---|
| 只給程式(×3) | PM 兩句話+src/、tests/ 副本(無 API、無規格) |
--safe-mode、--tools Read,Grep,Glob |
5/19/0.03;4/17/0.03;6/22/0.04 | day09-scope-lab/runs/r0-naive-nospec、-2、-3;計畫引第一次,「跟 PM 確認是否推翻」引第二次 |
| (同提示,原資料夾) | 含 specs/ |
同上 | 7/15/0.05 | r0-naive:它自己 Glob 到 rules-v2.md 照著寫;正文採用上一列 |
| 加呼叫端(×3) | API 程式、刻意留白的 PM 草案 spec.md、src/、tests/ |
工具 Read/Grep/Glob/Skill,本機 plugin 載入固定版本 grilling |
8/81/0.24;11/60/0.19;8/68/0.18 | day09-intake-lab/runs/interview-01、-02、-03;正文 Q1 引第一次;只驗第一輪問題,沒有真人回答 |
| 加呼叫端,直接要計畫(×3) | 同上一列材料的乾淨副本 | --safe-mode、--tools Read,Grep,Glob,要它給修改計畫 |
7/45/0.09;8/58/0.11;8/52/0.10 | day09-intake-lab/runs/noskill-02、-03、-04;引文取第一次(noskill-01 因工作目錄含前次輸出作廢,保留並標記) |
| 給完整規格 | rules-v2.md、兩份決策、通知提案(無 API) |
同「只給程式」;第二輪附材料來源說明與第一輪報告 | 8/93/0.11;13/134/0.20 | day09-scope-lab/runs/r1、r2;八組布林核對見 verify.py |
所有實跑用 claude -p、sonnet、medium effort;費用為 CLI 估值(美元)。驅動端:「加呼叫端」與「給完整規格」由 Codex 執行 CLI,其餘由本文作者的 Claude Code session 執行;驅動端只是按下執行的角色,被評的都是 Claude Code 的輸出。
官方文件
--tools、--permission-mode plan、--output-format stream-json。借鏡
實作材料
days/day09/lab-scope(唯讀實跑紀錄、規格與程式快照、八組布林核對)、days/day09/lab-intake(訪談實跑與 plugin)、templates/change-scope-card.md(開工卡空白版)。閱讀既有紀錄不需呼叫模型;重跑 runner 會消耗用量,輸出不保證相同。