IMS 的實作計畫放在 docs/plans/。Task 2 是「Models + JSCore 解析」,第一步就列出了解析功能要通過的測試。下面是其中的節錄:
func testParsesBundledSchedule() throws {
let parsed = try ScheduleParser.parse(
scheduleJS: fixture("schedule.bundle.js"),
correctionsJS: fixture("corrections.bundle.js"))
XCTAssertTrue(parsed.people.contains("DinDin"))
// 修正檔會改名 → 驗證修正有生效
XCTAssertFalse(parsed.schedule.contains { $0.person == "<舊名>" })
}
func testCorruptCorrectionsFallsBackToScheduleOnly() throws {
let parsed = try ScheduleParser.parse(
scheduleJS: fixture("schedule.bundle.js"),
correctionsJS: "this is not js ((")
XCTAssertTrue(parsed.people.contains("DinDin")) // 仍可解析
}
這段節錄依賴專案的測試資料與 fixture 輔助函式,使用時要對應實際資料;<舊名> 也是需要替換的示意值。
第二個測試把一個決策寫得很具體:修正檔無法解析時,仍要能從主檔取得資料。實作還沒開始,我就能先檢查這是不是想要的行為。
計畫要讓執行者知道,最後必須交出什麼證據。 只寫「完成解析功能」,AI 還得自行猜測錯誤時如何處理;列出測試後,預期行為就有了可執行的例子。符合它的實作可能不只一種,但不能任意改變這個結果。
我把實作計畫當成「外接記憶」,因為它有兩種讀者。
第一種是接手工作的 AI。在我的派工方式裡,每個 task 會交給一個獨立執行者,只提供相關的計畫、專案規則與決策。它不需要讀完整段聊天歷史,卻必須有足夠資訊完成工作。
因此,計畫不能只寫「照剛才說的做」或「類似 Task 3」。需要引用時,要指出具體檔案、段落或函式,讓執行者能找到依據。
第二種是幾個月後的自己。回頭維護時,我可能已經忘了為什麼 Widget 只讀快照、JavaScriptCore 必須留在 App。計畫和決策紀錄能把這些理由接回來。
這幾份文件各有分工:
| 文件 | 回答的問題 |
|---|---|
| Ticket/規格 | 要完成什麼?如何驗收? |
| 決策紀錄 | 為什麼這樣選?哪些替代方案已經考慮過? |
| 實作計畫 | 按什麼順序做?每一步改哪裡、怎麼驗證? |
文件可以互相引用,不必重複貼上所有內容。重要的是下一個讀者找得到答案。
我習慣在每個 task 裡放五個元素:
以 BlockBuilder 為例,它需要將指定人員的時段排序,再依規則合併成任務區段。計畫不能只寫「把相鄰任務合併」,還要交代同角色、休息時段與真正空檔的差別。
涉及前置 task 的型別,也要寫清楚依賴。如果 TaskBlock 尚未建立,就標明由哪個 task 定義,不能讓執行者自己猜介面。
我通常讓一個步驟只有一個可檢查的目的,例如補一個測試、確認失敗、完成一段邏輯。若一個 task 預計超過半小時,或包含好幾種互不相關的行為,就檢查能否拆小。時間是提醒,真正的判斷是中間出了問題,能不能快速定位。
AI 寫出計畫後,我會先讀測試名稱與斷言,再檢查架構和修改範圍。每個測試都問兩件事:
IMS 的任務合併測試包含同角色相鄰時段、不同角色、休息區段、真正空檔、重複資料、人員篩選、時區與排序。其中「休息時段可以被吸收」是我特別補的。
例如,同一個人 9:40–10:20 在外場,10:20–10:30 是排班表明列的休息,10:30–10:50 又回到同一角色。對這個 App 來說,這三段可以形成 9:40–10:50 的一個任務區段,避免同一段勤務被拆成多次提醒。
但如果中間根本沒有排班資料,只是一段真正的空檔,就不能一律合併。這個差別只靠「相鄰任務要合併」很難說清楚,用兩組測試就能分別表達。
測試不是完整需求的替代品;它能幫你檢查,文字需求是否已經轉成正確的判斷規則。
準備好 ticket 和 Day 8 的決策紀錄,請 AI 先列測試案例,再寫詳細步驟:
根據指定的 ticket 和 docs/decisions-log.md,撰寫實作計畫。
先讀 CLAUDE.md 與相關程式碼,確認既有架構與測試方式。
第一階段:
- 列出 task、依賴順序、測試名稱和關鍵斷言。
- 每個測試標出對應的完成條件。
- 沒有辦法用單元測試驗證的項目,列出其他驗證方式。
先交給我檢查,再寫完整計畫。
第二階段:
- 每個 task 列檔案、測試案例、實作範圍、完整驗證指令與成功條件。
- 邏輯任務附測試程式碼;所需型別與測試資料要有來源。
- 每個 task 預計不超過 8 個檔案,超過則說明拆分方式。
- 阻擋實作的未知事項單獨列出,不用 TBD 代替。
- 存到 docs/plans/<日期>-<名稱>.md,先不要開始實作。
檢查時,把案例逐一對回完成條件,補上遺漏的邊界情況。完整計畫寫好後,再看檔案範圍、依賴和驗證指令,確認另一個執行者能靠它開工。
核准後納入版本控制。後續若需求或決策改變,也要同步更新計畫,避免文件與實作各走各的。
計畫只有大標題。 「Task 1:做資料層」沒有交代輸入、輸出或驗證方式,執行者仍得做大量猜測。
Task 包得太大。 資料解析、通知和 UI 放在同一步,出問題時很難判斷是哪一層。依可驗證的行為拆分。
只審措辭,沒審斷言。 計畫文字再順,測試若期待了錯誤行為,實作仍會往錯的方向走。
關鍵值沒有來源。 日期、時區、URL、timeout 和上限,都要有規格或決策依據,不能用看起來合理的值填空。
計畫核准後就不再更新。 後續接手的人會依據舊文件工作。改了重要決策,就把新結論寫回去。
plan-task-template.md 是計畫的骨架。先填共用限制,再依需要複製 task 段落;範例中的佔位內容要在開工前補齊。
# <功能名稱>實作計畫
## 需求與決策來源
- Ticket:<路徑>
- 決策紀錄:<路徑與相關條目>
## 不可自行決定的事項
- 數值:<timeout、上限、間隔的來源>
- 日期/時區:<來源>
- URL/識別字:<來源>
- 架構限制:<哪些設計必須沿用>
### Task N:<名稱>
**依賴:** <前置 task 或既有型別>
**檔案:** 新增 `<path>`;修改 `<path>`;測試 `<path>`
**完成條件:** <對應 ticket 的哪一項>
**Step 1:失敗測試**
```swift
// 填入可執行的測試,包含具體輸入與預期結果。
```
**Step 2:** 執行測試,檢查實際失敗原因是否符合預期。
**Step 3:** <實作範圍、要沿用的函式與限制>
**Step 4:** 再跑測試,確認新行為通過、既有行為沒有退步。
**Step 5:** 交出 diff 和驗證結果,依約定審查後 commit。
**完整驗證指令:** <包含 scheme、執行環境與測試範圍>
**成功條件:** <預期執行的測試、通過結果、其他必要檢查>
**需另外驗證:** <真機、畫面或整合行為;無則註明>
派工時,要把共用限制和相關 task 一起交給執行者。文件寫在前面只是第一步,還要確保接手的人知道去哪裡讀。