跳到主要內容
resoluna.

我怎麼跟 AI 工作

我負責判斷,AI 負責實作

每個專案的分工都一樣:我定義問題、列限制、做取捨、驗收結果;Claude Code 把決定寫成程式。這不是「叫 AI 寫一個 App」,而是我先想清楚要什麼、不要什麼,再把規格交出去。AI 寫得很快,所以瓶頸從來不在程式,而在判斷。

需求來自現場,不來自想像中的使用者

地球日六頂思考帽工具的第一版是每人固定一頂帽子。帶完第一個班,我看到沒分到帽子的學生在放空,才把它改成全組開放作答加 Pass 機制。我盡量在真實使用者面前看產品被使用,然後把觀察整理成下一輪的需求。

有標準答案的問題,不交給模型自由發揮

做動物園 LINE Bot 時,票價、公休日這種有標準答案的問題,一開始全交給 GPT 回答,結果它會自由發揮。後來改成程式先用規則比對,GPT 只負責把答案說得自然。能用規則算的就用規則算,模型留給真正開放的問題。

先定義要看什麼,再讓模型去看

課後用 AI 分析學生答案時,我沒有讓模型「幫我看看寫得好不好」,而是先定義四個維度:黃帽有沒有引用白帽的數據、綠帽有沒有回應黑帽的困難、藍帽是具體行動還是口號、整體是否連貫。模型的輸出品質,取決於你有沒有先把問題問清楚。

每個決策都留下紀錄

背景、當時的選項、最後的決定、理由,以及「未來什麼情況下會改」。這些紀錄一方面讓 AI 下次接手時有脈絡,一方面也是我自己回頭檢查判斷的依據。站上的決策總覽就是從這些紀錄整理出來的。

人機合作回放

每一筆都是真實發生過的:我判斷了什麼、AI 做了什麼、結果如何。

  1. 動物園課程小幫手 LINE Bot ·環境地雷:本機能跑 ≠ 建置得過

    我判斷了什麼

    本機 python app.py 正常,推上去 Cloud Build 卻失敗。我把建置 log 的錯誤段落貼給 AI,並要求先搞清楚「是誰跟誰衝突」,不要整個環境打掉重練。

    AI 做了什麼

    Cursor 定位出 numpy==1.24.0 與 seaborn 的硬衝突,把版本改成範圍(>=1.22.4,!=1.24.0,<2);另一個 aenum 舊版帶 Python 2 語法的編譯錯誤,則在 Dockerfile 的 pip 加上 --no-compile。

    結果

    Cloud Build 通過,bot 首次在 Cloud Run 上線。同一個 aenum 坑四個月後在 setup.sh 又踩一次,同樣用 --no-compile 補上。

    一句話 套件版本開範圍而不是釘死;錯誤先讀訊息,再動環境 →

  2. 動物園課程小幫手 LINE Bot ·架構轉捩點:把判斷權從 GPT 收回來

    我判斷了什麼

    我發現「週六有什麼課」這種可以驗證對錯的問題,GPT 會抓錯星期、漏課程,而前兩天的 commit 都在改 prompt 跟它講道理。我決定不再補 prompt,要求改成 Python 先依星期篩出正確課程,GPT 只負責把篩好的資料講得自然。

    AI 做了什麼

    AI 助理(Cursor)加入 detect_query_weekday / matches_weekday 做星期比對與預篩選,接著補上篩選失敗時的退路,並把票價、公休、開放時間一併整理成 CSV,建立 query_router 作為統一分流入口。

    結果

    有標準答案的查詢不再經過 GPT 判斷;這個原則後來套用到整個專案,包括多人票價試算(GPT 只抽身份、Python 算總價)與環教時數直回原文。

    一句話 有標準答案的問題不該讓模型自由發揮 →

  3. 國文作業查詢系統 ·從成績表裡切出可以公開的那一塊

    我判斷了什麼

    原表是平時成績表。我決定只拿繳交與訂正欄位、畫面只顯示座號、不要登入,而且要能立刻發佈給三個班。

    AI 做了什麼

    Gemini 依這些條件產出 Apps Script:doGet 回一頁網頁、解析三層表頭、只認四種作業項目,以及手機優先的卡片介面。

    結果

    立即上線,三個班的學生、導師與任課教師都在用。

    一句話 先講清楚哪些資料不能出去,再講要做什麼 →

  4. 地球日 × 六頂思考帽 ·修正同步狀態 bug

    我判斷了什麼

    改成全組開放作答後,學生端的「儲存」按鈕被鎖死。我把這個症狀交給 Claude 處理。

    AI 做了什麼

    Claude 追到 subscribeHatReadiness 在 open-to-all 模式下的判斷問題,以及 hatKey 的 race condition,一併修正。

    結果

    開放參與模式下的儲存按鈕恢復正常,修正寫進 commit 紀錄。

    一句話 流程規則一改,同步狀態的假設就要重新驗一次 →

  5. 地球日 × 六頂思考帽 ·把教學目標變成介面

    我判斷了什麼

    我要的是「綠帽回應黑帽提出的困難」,所以要求綠帽作答時直接看得到同組黑帽寫了什麼。

    AI 做了什麼

    Claude 在綠帽頁新增即時訂閱的「本組黑帽的困難點」區塊,組員一存檔就更新。

    結果

    黑→綠的回應從靠記憶變成有畫面可對照,之後也成為課後分析的一個維度。

    一句話 想評量什麼,就先在介面上讓學生做得到 →

  6. 地球日 × 六頂思考帽 ·依課堂觀察改產品規則

    我判斷了什麼

    帶班後我決定放棄「一人一頂帽子」,改成全組作答、可 Pass;白帽要拆成具體欄位,老師後台也要能直接改學生答案。

    AI 做了什麼

    Claude 修復作答按鈕、把白帽改成三個欄位、實作 Pass 機制,並在後台加入作答編輯功能。

    結果

    流程變成每個人每頂帽子都有事做;不想答的欄位有明確出口,而不是空著卡住。

    一句話 原型的角色設計要讓真實課堂來檢驗 →

  7. 地球日 × 六頂思考帽 ·課後 AI 分析

    我判斷了什麼

    我定義分析框架:不判斷是不是 AI 寫的,而是看白→黃有沒有引用事實、黑→綠有沒有回應困難、藍帽是不是具體行動。

    AI 做了什麼

    Claude 寫了 analyze-708.js:從 Firestore 讀資料,用程式先算好填寫率、藍帽一致性、行動分布,再把統計與原文交給 Azure OpenAI 做質性分析。

    結果

    產出 708 班 28 筆作答的分析報告,含各組連貫度與三項教學建議。

    一句話 數字交給程式算,模型只負責解讀 →

  8. MinutePad 1分鐘便條紙 ·把 Phase 4 的 AI 提前,但只做一件事

    我判斷了什麼

    設計文件把 AI 排在 12~24 個月後,內容包含艾森豪矩陣分類和歷史模式分析。我判斷 Pro 需要一個現在就能賣的理由,但沒有使用資料就做矩陣分類是空談。所以把 AI 提前到 v1.0,範圍縮成「幫我把今天沒排的事排進時間」,而且每一筆建議都要我按確認。

    AI 做了什麼

    Claude 寫了 AIAssistView 與 suggestSchedule:最多選 5 筆,把現在時刻、已佔用時段、不可安排時段餵給模型,要求只回 HH:mm 逐行;解析失敗的那筆顯示「無法取得建議時間」並提供「安排至明日」。

    結果

    AI 排程成為 Pro 主賣點(免費每日 3 次),艾森豪欄位保留未用(DECISIONS D017、D020)。

    一句話 AI 功能先做一件做得準、可驗證的事,再談聰明 →

  9. MinutePad 1分鐘便條紙 ·Widget 打勾要同步回 App

    我判斷了什麼

    我要求桌面 Widget 可以直接打勾完成,而且一定要同步回 App,但不想讓 Widget 這個獨立 process 直接去寫 Core Data 資料庫。

    AI 做了什麼

    Claude 用 AppIntent(ToggleTaskIntent)把被點擊的任務 ID 寫進 App Group 的待處理佇列,Widget 端先樂觀顯示;App 回到前景時讀佇列、寫入 Core Data、清空,再把最新清單推回 Widget。

    結果

    Widget 與 App 之間只靠 App Group UserDefaults 單向推送 + 一個佇列,沒有多 process 寫同一個 store 的風險(DECISIONS D023)。

    一句話 跨 process 的同步,先決定誰是唯一的寫入者 →

  10. 動物園課程小幫手 LINE Bot ·正式環境 21 小時的 worker 當機循環

    我判斷了什麼

    接上流量上限功能後,Cloud Run 的 log 開始出現大量 gunicorn WORKER TIMEOUT 與 SIGKILL,時間點跟那次部署完全吻合。我把 log 與時間線交給 Claude Code,要求找出跟這次改動的關聯,而不是只把逾時秒數調大。

    AI 做了什麼

    Claude Code 發現 rate_limiter、interest_store、conversation_memory 各自建立 Firestore client,同一則訊息最多初始化三次;改成 process 內共用單例,並把 gunicorn timeout 從 30 秒拉到 60 秒當安全網。

    結果

    當機循環停止;修法的理由寫進 firestore_client.py 的 docstring,之後接手的人看得到為什麼。

    一句話 先找跟改動相關的根因,再加安全網 →

  11. 動物園課程小幫手 LINE Bot ·自建盲測 skill:讓不知道答案的 agent 來提問

    我判斷了什麼

    人工寫的測試案例一定偏向「我知道系統答得出來的題目」。我設計了一個 Claude Code skill:由我給人設(陪輪椅長輩的家屬、安排校外教學的老師),實際提問交給一個完全沒讀過 data/ 的子 agent,並規定它先想完全部題目再開始問、問完才分類,避免被前面的回答帶著走。

    AI 做了什麼

    Claude Code 以隔離的子 agent 對正式環境跑了兩輪共 35 題,從 Cloud Logging 撈回完整回覆並分成四類;主 agent 把人設逐字記進 persona-log,並在第二輪發現「撈 log 只抓到第一行」的方法瑕疵後,把正確的兩步驟撈法寫回 skill。

    結果

    第一輪在第 6 題就撞到每人 5 則的永久訊息上限,當天提高到 20;第二輪找出「休息」被誤判成公休、多人票價流程卡住不放兩個問題,隔天修正。三個修正都不是事先設計的測試案例會測到的。

    一句話 出題的人不能知道答案;把測試方法本身做成可重複的工具 →

  12. 動物園課程小幫手 LINE Bot ·管線靜默失敗三週後的存活檢查

    我判斷了什麼

    課表每日自動更新的管線設計成「遇到問題就跳過、明天再試」,結果 9 月中之後連續四次沒更新,直到 10 月初我自己察覺正式環境已經用過期課表回答了三週。我要求加一個告警,但不要用「超過 7 天沒更新就叫」這種每個月都會誤報的規則。

    AI 做了什麼

    Claude Code 寫了 check_pipeline_liveness.py:以「courses_meta.json 的月份是否落後本月」為訊號,再用每月 1~7 號的合理等待窗口區分正常等待與真正故障;退出碼一律 0,只回報不攔截,避免再多一個會靜默擋掉管線的環節。另加了排程前的認證預檢。

    結果

    每次排程執行第一件事就是跑存活檢查並寫進報告第一行;跳過本身不是問題,跳過而沒人知道才是。

    一句話 fail-safe 只做一半等於沒做:安全地跳過還要有人知道 →

  13. MinutePad 1分鐘便條紙 ·用 CLAUDE.md 畫出 AI 的工作邊界

    我判斷了什麼

    程式碼裡出現了「CBT 小行動建議」和使用習慣分析的雛形。我判斷這些屬於另一個產品(智慧行動記事本),不是 MinutePad 的「操作簡單、省事」定位。我把定位、功能範圍、Pro 分界和「不屬於本專案的功能」寫進 CLAUDE.md,讓它成為給 AI 看的 PRD。

    AI 做了什麼

    Claude Code 每次 session 先讀 CLAUDE.md,之後的開發都在範圍內;殘留的 suggest(for:) 與 AISuggestionCache 保留為沒有 UI 入口的程式碼,等確認後刪除。

    結果

    v1.0 範圍穩定:AI 只做「重排今日時間」,CBT 與習慣分析正式移出(DECISIONS D025)。

    一句話 給 AI 的需求文件,先寫「不做什麼」比寫「要做什麼」更有效 →

  14. MinutePad 1分鐘便條紙 ·AI 走了捷徑:金鑰寫死在原始碼

    我判斷了什麼

    規劃上 AI 排程要經過 Azure Functions 中轉層保護金鑰(D006)。實作時 Claude 為了讓功能跑起來,直接從 App 打 Azure OpenAI,把金鑰寫進 Secrets.swift。我在把專案納入 git 之前做安全檢查抓到這件事,列出三個方案(ignore 檔案 / xcconfig 注入 / 中轉層),裁決先用 xcconfig,中轉層排進上架前待辦。

    AI 做了什麼

    Claude 把金鑰移到不進 git 的 Secrets.xcconfig,經 Base.xcconfig 的可選 include 注入 Info.plist,Secrets.swift 改為讀取;沒有 xcconfig 也能編譯,AI 功能會明確回報未設定。兩種情境都用 xcodebuild 驗證。

    結果

    原始碼與 git history 內無金鑰;被讀過的舊金鑰列為需重發。中轉層列為上架前的已知技術債(DECISIONS D019、D026)。

    一句話 AI 會為了「先跑起來」跨過安全邊界,邊界要靠流程上的檢查點守住,不能靠它自律 →

  15. MinutePad 1分鐘便條紙 ·被 Xcode 推翻的工具決策

    我判斷了什麼

    D012 決定用 XcodeGen 管專案檔。但我在 Xcode 裡直接新增了 Widget target 和簽章設定,project.yml 從此落後,沒有人回頭補。整理文件時我選擇承認現實:pbxproj 以 Xcode 為準,並把「不要執行 xcodegen generate」寫成決策。

    AI 做了什麼

    Claude 在補記 DECISIONS 時發現 project.yml 沒有 Widget target、bundle ID 還是佔位值,寫成 D015 並在 README 的開發環境設定裡警告。

    結果

    文件與實際工作流一致;新加入的人不會因為跑 xcodegen 把 Widget target 砍掉(DECISIONS D015)。

    一句話 決策紀錄要能被推翻,而且推翻也要記下來 →

  16. 國文作業查詢系統 ·納入版控:先證明這就是線上那份

    我判斷了什麼

    原始碼只在 Apps Script 編輯器裡,而我的 Google 帳號下有好幾個長得像的專案。我決定以「線上網址正在跑的那個」為準,而不是以專案名稱為準。

    AI 做了什麼

    Claude Code 用 clasp 列出每個候選專案的部署 ID 與線上網址比對,再把線上部署版本與 HEAD 逐檔 diff,確認完全相同後才 clone 進 repo。

    結果

    repo 內的 src/ 與線上版本完全相同,部署網址未變。

    一句話 納入版控的第一步不是 commit,是證明你 commit 的就是線上跑的那份 →

  17. 國文作業查詢系統 ·隱私檢視:畫面沒顯示,不代表沒送出去

    我判斷了什麼

    我要求檢查程式裡有沒有寫死學生資料。聽完「按 F12 就看得到全班姓名」的說明後,我決定移除姓名欄位,並要求用同一個部署更新,網址不能變。

    AI 做了什麼

    Claude Code 確認沒有寫死資料,但指出 getStudentData 回傳的 JSON 含姓名欄;依規則先回報、不擅改,待我決定後才改一行並以 clasp deploy -i 更新同一個部署。

    結果

    回傳資料不再含姓名;畫面與網址都沒有變。

    一句話 審資料外洩要看回傳的 payload,不是看畫面 →