昨天讓 Plan Mode 對著一句需求提了七條假設、問了一題,然後說「沒有了」。今天換一個專門做這件事的工具,這次把規格直接交給它拷問 —— 看它是問得更狠,還是問出一樣的洞。
工具是 Matt Pocock 的 /grill-me。他那個 skills repo 七個多月從零到二十六萬星,而 /grill-me 是裡面很具代表性的一支:一個「relentless interview」,把你的計畫問到沒東西可問為止。
/grill-me 本身只有 7 行,做一件事:呼叫另一支叫 grilling 的 skill。真正定義拷問規則的是 grilling,一共 28 行,核心規則有四條:
第 2 條和第 4 條,就是它跟 Plan Mode 的差別:Plan Mode 的「假設」是計畫的附錄,列完七條就交計畫;grilling 的問題本身就是產出,交計畫之前要先問到沒得問。
另外還有一支 /grill-with-docs:同樣的拷問,邊問邊產 glossary 和 ADR。先跑 /grill-me,後面再用它跑一次。
乾淨 clone,放在原 repo 之外的目錄(Day 4 講的那個鄰居的洞),rm -rf .git、拿掉 devlog/(裡面有昨天 Plan Mode 的三版計畫,留著它就有答案可抄)。規格與非目標這次留著:昨天是讓它從一句需求問起,今天是讓它拷問寫好的規格,兩篇驗的東西不同。tests/overdue.test.js 那支紅測試也在 —— 它是 repo 裡唯一的一支測試,拷問過程中它也會讀。
需要的四支 skill 照 commit 抓進 .claude/skills/,然後:
claude -p "/grill-me 我要實作借書與還書這個 feature。規格在 docs/spec.md,非目標在 docs/non-goals.md,硬規則在 CLAUDE.md。請拷問這份規格。" \
--permission-mode default --allowedTools "Read,Glob,Grep,Skill,Agent"
之後每一輪 --resume 接回,把我的答案原文貼給它。紀律跟昨天一樣:規格有的照規格答,沒有的當場決定並記下來。
| 輪 | 它給的 | 秒 / $ |
|---|---|---|
| 1 | 讀完七個檔,開場一句判斷,再 Q1–Q11 | 158 / 0.48 |
| 2 | Q12–Q21,其中一題是追問我上一輪的答案 | 143 / 0.40 |
| 3 | 自己定了閘門的 API,Q22–Q24,整棵 24 個節點的設計樹 | 74 / 0.21 |
| 4 | 「Frontier 清空」;接下來只改 spec、寫 tests/README.md,CLAUDE.md 一字不動、不寫 src/ |
8 / 0.08 |
合計 24 題、$1.18、六分多鐘。跟昨天 Plan Mode 的三輪 $1.32 比:問題是七條假設的三倍多,錢還少 —— 因為它不寫計畫,只問。
它讀完規格、非目標、CLAUDE.md、紅測試,第一段不是問題,是判斷:
規格在「規則」層寫得很硬,但在「怎麼落地」層有幾個洞,而且有兩條規則彼此有張力。
張力是 Q2:規格要 books.status 存成欄位、走白名單、有 CHECK;又要「有未歸還紀錄 ⇔ on_loan」被測試證明。這等於要維護兩份真相 —— 一致性得靠每次借還都在同一個交易裡同步動兩張表。它給三個選項、建議第三個:兩者都存,partial unique index 當第二道防線,「任何時候兩者不一致就是 bug,測試直接驗這條不變量」。這件事我寫規格時沒想過,昨天 Plan Mode 也沒提 —— 它的計畫做的就是第三個選項(兩張表同一交易、partial unique index 也有),做對了,但沒告訴我這裡有張力。
第一輪 11 題,每題附建議答案。挑三題:
Q3:硬規則 2 說「不准在別處寫 status = '...'」,但 UPDATE books SET status = 'on_loan' 這行 SQL 本身就是寫 status。它提出落地法 —— 字面值只出現在 book-state.js,SQL 全部參數化,靜態檢查 grep status\s*=\s*' 只允許那一檔 —— 然後補一句:這是硬規則 2 唯一講得通的落地法;否則就得寫 trigger,而那會讓實作比題目本身還大。
Q5:規格決定 4「一人同一本書只能一筆未歸還」,在一書一冊之下是多餘的 —— 書已經 on_loan,同一人再借本來就會被擋,這條沒有獨立的失敗路徑。它還指出:規格「為什麼」那欄寫的是「讓同一人重複送出不污染併發測試」,那是測試設計的理由,不是業務理由。這個問題我寫規格時沒發現。
Q8:GET /api/users/:id/borrows 未歸還那段要不要帶即時算的 overdueDays?它建議不帶(「要帶就是第二個真相」)。我答「帶」 —— 兩段用同一支純函式,它不是第二個真相,而是同一個快照在不同 now 下的讀數。
然後第二輪它追問了(Q14):history 那段的 overdueDays 讀存的欄位還是重算?如果重算,還書時押進去的 overdue_days 就變成「只寫不讀」。它說:「你的論點我同意,但 history 的 now 就是 returned_at,那個讀數已經在還書當下押進去了;再算一次不是錯,是多一個可以分岔的地方。」我改採它的:history 讀存的,active 現算。
24 題我照它的建議答了 23 題,唯一不同的那題被它追問後也改回去了。這不代表它每題都對,而是每題都先把選項和取捨排好,我只要判斷「這個場合適不適用」。
第二輪的十題沒有一題是第一輪能問的。Q12 問併發測試的閘門插在哪 —— 這題只有在 Q1 選了「測試用 Node 內建 node:sqlite、抽一層 DB 介面」之後才存在。因為 node:sqlite 是同步的,SQL 不可能交錯,閘門必須插在應用層的 await 點。它建議包住 adapter 的 batch(),N 個請求到齊才放行,src/ 零改動;並且說要先證明它會紅:對照組是一版「先 SELECT 再 UPDATE 不帶 WHERE」的 naive 借書,同一個閘門下應該出現 N 個 201 —— 「沒有紅過的閘門等於沒有閘門。」第三輪 Q22 又把這件事推一步:naive 對照組不要紅過就刪,留成永久測試,守的是閘門本身有沒有偵測 TOCTOU 的能力。
第四輪:「Frontier 清空。整棵樹 24 個節點全部有人裁決,沒有任何一個是我默默假設的。」
/grill-with-docs,邊問邊把檔寫出來/grill-with-docs 本體只有一行:「Call the Skill tool twice, for grilling and domain-modeling.」拷問流程一模一樣,多掛的 domain-modeling(74 行)管三件事:你用了模糊的詞、或跟 glossary 打架的詞,它當場攔;詞一定案就寫進 CONTEXT.md(純 glossary,明講不准放實作細節);ADR 要三個條件同時成立才開 —— 難回頭、沒脈絡看不懂、真的有取捨 —— 少一個就不寫。
同一份規格、同一個起點再跑一次,這次 --allowedTools 多給 Write,Edit。五輪(四輪問、一輪摘要)、33 題、$2.10、十一分鐘。
它多問了什麼。 第一輪的 Q13 是 /grill-me 那次沒有的題型:規格裡 POST /api/borrow、borrow_records 用 borrow,loan_policies、on_loan 用 loan,中文又有「借閱」「借書」「借期」—— 要寫 glossary 得選一個。它建議中文正名「借閱」、英文以 borrow 為主,on_loan 和 loan_policies 標成「契約遺留」、不再新造。第三輪 CONTEXT.md 寫出來 67 行,每個詞都附一行 Avoid:書不准叫副本、copy;讀者不准叫 patron、reader、會員;到期時刻不准叫 deadline、到期日。Q5 那條「一人一書多餘」也進了 glossary,條目就叫「單冊版的推論規則」。
ADR 的閘門真的有在擋。 它開了兩份:測試跑 node:sqlite 不跑 D1;借還兩筆寫入放同一個 batch、第二句用 WHERE changes() = 1 綁住第一句。然後明講三件不開:錯誤碼表、欄位命名、overdue_days 存不存 —— 「都是規格延伸或容易改的,不夠格」。第二份 ADR 那招是 /grill-me 那次沒出現的:上次的設計是輸家靠 unique index 拋錯、整批回滾;這次它自己把這個設計推翻 —— route 得解析錯誤訊息,而 D1 跟 node:sqlite 的錯誤格式不一樣 —— 改成正常路徑零例外,並在 ADR 裡承認「changes() 只在 node:sqlite 驗過,D1 上是假設」。
還有一個坑值得記:第二輪它要寫 CONTEXT.md 被 -p 擋下,理由是「敏感檔案」。查了才知道是路徑不是檔名 —— 我把乾淨 clone 放在 ~/.claude/ 底下的暫存目錄,那整棵在非互動 session 裡都不給寫。搬出來、--resume 接回同一個 session 就好了。
33 題比 24 題多,但同一個模型跑兩次本來就不會一樣,這 9 題不能全部算在 domain-modeling 頭上;能確定是它帶來的,是 Q13 那種詞彙題,和拷問結束時桌上多出來的三份檔。
同一份需求、同一天、兩種問法:
| Plan Mode(Day 7) | /grill-me |
|
|---|---|---|
| 給它什麼 | 一句需求(規格拿掉) | 規格 + 非目標 + 硬規則 + 紅測試 |
| 輪 / 題 | 3 / 七條假設 + 1 題 | 4 / 24 |
| 成本 | $1.32 | $1.18 |
| 產出 | 197 行計畫 | 一棵設計樹(不寫計畫) |
| 抓到規格自己的問題 | 0(它猜錯三處,是它沒讀到規格) | 兩條規則的張力、決定 4 多餘、硬規則 2 的字面衝突 |
| 兩邊都碰到 | overdueDays 要不要帶進 list、loan_policies 存快照、要不要開 src/db/ |
|
| 只有它問到 | —— | 併發閘門插哪、naive 對照組先紅再留成永久測試、D1 batch() 不能分支怎麼守、status code 表、時間字串怎麼比、GET 用 LEFT JOIN 一次查完 |
| 它沒問的 | 兩條 GET 的細節、一人一書(我補的) |
非目標一條沒踩;也沒問任何「要不要加什麼」 |
最後一列是我覺得最重要的:24 題裡,沒有一題是在問「要不要加 X」。它問的全是「你寫的這些怎麼同時成立」。非目標清單把「加東西」的門關了,它就只往裡面問。
另一個差別是方向。Plan Mode 從一句需求往外推,問的是「我該做哪些」;grilling 從寫好的規格往裡鑽,問的是「這些對不對得起來」。所以它會問到規格自己的問題 —— 兩份真相、多餘的規則、寫得太硬落不了地的規則 —— 這些問題 Plan Mode 不會主動問:一方面它沒讀規格,另一方面它的目標是交出計畫,不是找出規格本身的問題。
它沒有比 Plan Mode 聰明。同一個模型,同一份規格,差別不在模型,而在那 28 行規則:一輪問整個 frontier、每題附建議、事實自己查、問到空為止。Plan Mode 七條假設就交計畫,是因為它的目標是計畫;grilling 問了 24 題,是因為它的目標是問完。
但「問完」有價值的前提,是你答得出來。24 題我答了不到半小時,因為大部分是照規格念、少數當場決定。如果規格不存在,這 24 題會變成 24 個要你現場想的設計決定 —— 那就不是拷問,是它在替你設計。順序還是 Day 7 那句:先寫你的,再讓它問。
/grill-me 是 28 行 prompt,做的事只有一件:不給計畫,只給問題,問到沒得問。四輪 24 題,$1.18,抓到我規格裡兩條規則的張力、一條多餘的規則、一條寫得太硬、字面上落不了地的硬規則 —— 三件事昨天 Plan Mode 都沒提:前兩件是它沒讀規格;第三件它的計畫其實用了同一套落地法,只是沒說這條規則字面上有衝突。
兩個工具不衝突。需求還是一句話的時候,Plan Mode 從它往外推;規格寫好了,grilling 往裡鑽。兩邊真正有東西可問的前提,都是你先寫出東西。要邊問邊留檔,換 /grill-with-docs:同樣的拷問,多一份 glossary,和幾份它認為夠格的 ADR。
這一篇留下的心法:
拷問的價值不在問了幾題,而在問的全是「你寫的這些怎麼同時成立」—— 而不是「要不要多做什麼」。後者靠非目標清單擋,前者靠有人肯問到底。
明天:測試是給 AI 的護欄,也是驗收標準。先寫一個會紅的測試,再讓它動手。
/grill-me、grilling、/grill-with-docs、domain-modeling;本文用 commit 959a8e9,2026-09-17):github.com/mattpocock/skills —— grilling 28 行原文:SKILL.md
CONTEXT.md 與兩份 ADR 在 produced/)docs/spec.md、docs/non-goals.md、CLAUDE.md
AskUserQuestion 訪談你、寫 SPEC.md;2026-09-17 查