iT邦幫忙

2026 iThome 鐵人賽

DAY 14
0
Vibe Coding

神隊友.swift:30 天 Vibe Coding 打造育兒 iOS App 系列 第 14 篇

[Day 14] 讓 AI 自己跑一整天:規則寫成文件、腳本通過才算、兩個 AI 互審

  • 分享至 

  • xImage
  •  

Day 14

文章同步發表在我的個人 Blog

讓 AI 自己跑一整天:規則寫成文件、腳本通過才算、兩個 AI 互審

讓 AI 自己跑一整天,你怎麼知道它做出來的東西是對的?

昨天決定第一版重做,今天講重做的版本是怎麼做出來的。

先講結果:從 9/27 凌晨 3 點到 9/28 凌晨 1 點,新專案累積了 51 個 commit。第一個里程碑(專案骨架、設計系統)9 項做完 8 項,第二個里程碑(邀請加入、首頁、行事曆、通知、雙人同步)14 項做完 13 項,剩下的兩項都要拿實機才能做。

重做的版本:行事曆

做法有三個:規則寫成文件,Claude Code 和 Codex 照同一份做;AI 說做完不算,檢查腳本通過才算;Claude Code 寫的交給 Codex 審,Codex 寫的交給 Claude Code 審。審查抓到的問題裡,有一個跟 Swift 的取消機制有關:叫一個工作停下來,它不一定會停,第 5 節用程式碼說明。

1. 動手前先寫好的文件

Day 8 介紹過 AGENTS.md:Claude Code 和 Codex 讀同一份規則,CLAUDE.md 只匯入它再補幾行。重做的版本沿用這個做法。為了趕進度,前兩個里程碑的規格直接寫在文件裡;之後的新功能,會照 Day 7 的流程用 Spectra 開規格。

除了 AGENTS.md,這次多了四份文件:

檔案 用途 誰能改
docs/acceptance.md 驗收條件,每一條都有編號 只有我
docs/DESIGN.md 顏色、字級、間距、元件、畫面構圖 只有我
BACKLOG.md 任務清單,每一項標好由誰做 AI 打勾
docs/QUESTIONS.md AI 有疑問、需要我決定或動手的事 雙方

DESIGN.md 規定的顏色和元件,做成一頁只有開發版才看得到的元件展示頁。第一個里程碑的設計審查,Codex 看的就是這一頁的截圖:

元件展示頁

AGENTS.md 的禁止事項寫明「違反任一條,這一項就不算完成」,例如:不能出現任何計分或次數統計、不能改需求文件、不能刪除或弱化測試、不能加入需要付費開發者帳號的功能。

驗收條件有編號,測試名稱就能對回去。例如 test_AC_SYNC_02_cardAndAcknowledgementSyncWithinFiveSeconds,看名字就知道它驗的是 AC-SYNC-02:一支手機新增卡片、按「知道了」,另一支 5 秒內都要看到。

2. AI 說做完不算,檢查腳本通過才算

每一項都要跑同一個腳本 ./scripts/verify.sh,最後一行印出 VERIFY: PASS 才算完成。腳本的主要內容:

xcodegen generate                                   # 1. regenerate the Xcode project
swift test --package-path Packages/TeammateKit      # 2. pure Swift logic, seconds
firebase emulators:exec --only auth,firestore \
  "./scripts/_integration.sh"                       # 3. rules tests + xcodebuild test
./scripts/scans-selftest.sh                         # 4. prove the scans still catch violations
./scripts/scans.sh .                                # 5. static scans
echo "VERIFY: PASS"

任何一步失敗,就印出 VERIFY: FAIL 加上原因,後面的步驟不跑。

  • 第 1 步:Xcode 的專案檔由 project.yml 產生,AI 不能直接改專案檔
  • 第 2 步:資料模型、「對方已知」、通知排程這些不依賴畫面的邏輯,48 個測試,幾秒鐘跑完
  • 第 3 步:在本機啟動 Firebase Emulator(模擬 Firebase 的資料庫和登入,不連雲端),先跑 79 個權限規則測試(誰能讀寫哪些資料),再跑 116 個 App 測試,包含模擬兩支手機同步和 UI 測試
  • 第 4、5 步:用 grep 找違反規則的寫法,例如計分相關的字、畫面上寫死的顏色、需要付費帳號的功能。第 4 步先檢查掃描本身:放違規和合法的假檔案(共 46 個案例),違規的一定要判為失敗、合法的一定要通過。不這樣做的話,掃描規則寫錯、永遠抓不到東西,也會被當成通過

3. 誰做什麼

AGENTS.md 寫了分工,BACKLOG.md 每一項標 [cc](Claude Code)或 [codex]。這兩天實際的分工:

角色 誰 這兩天實際做了什麼
主 Builder Claude Code 專案骨架、Firestore 資料層、雙人同步測試、通知、把各功能接起來
平行開發 Claude Code 的 subagent 設計元件庫、首頁、首次啟動、行事曆、權限規則,各自在獨立的 git worktree 裡做,完成後由主 Builder 合併
副 Builder Codex 4 個純邏輯項目:資料模型、「對方已知」的規則、每週重複行程的展開、通知排程
Reviewer Codex 每個里程碑結束時唯讀審查程式和畫面截圖
Adversary Codex 挑戰高風險的設計:同步、權限規則、背景通知

Codex 的項目刻意只放不依賴畫面的純 Swift 邏輯。它在自己的 worktree 裡只要 swift test 幾秒鐘就能驗證,不需要模擬器;Claude Code 讀它的 diff,對照驗收條件,確認測試真的在驗證行為,才合併。

交叉的規則是:誰寫的程式,就由另一方審。

4. 交叉審查抓到什麼

Codex 的審查結果分三級:P0 必須修、P1 應該修、P2 之後再說。兩個里程碑下來,Codex 做了 7 份審查,沒有 P0,但有不少 P1。下面是 Codex 以 Adversary 角色(專挑設計假設的漏洞)審第二個里程碑的結果,列了 8 條 P1:

Codex 對第二個里程碑的 Adversary 審查

挑其中三個:

背景刷新設了 25 秒期限,但不一定守得住。 細節在第 5 節。

通知重排可能讓已刪除的提醒復活。 每次資料同步完成,App 就重新排一次本地通知。如果上一輪還停在 await 等系統回應,新的一輪已經先做完,上一輪回來後用舊資料寫入,已刪除行程的提醒就又排回去了。修法是同一時間只跑一輪;中途進來的變更只記住最新的,這一輪做完再照最新的排一次。

首頁沒有顯示每週重複的行程。 行事曆會把每週六的游泳課逐週列出來,首頁卻只看行程第一次的日期;游泳課如果從上週開始,這週的首頁就看不到。

反過來,Claude Code 也抓到 Codex 的問題。Codex 寫的 4 個項目合併前都沒有退回,但寫 Day 13 時對照文章,發現 Codex 寫的通知排程遇到內容有變更的通知,是先移除、再用同一個 identifier 加回去。這就是 Day 13 第 6 節寫的那件事:iOS 移除待送通知是非同步執行的,可能等新的加好之後才移除,把剛加的那則也刪掉。Claude Code 改成用同一個 identifier 直接覆蓋,再交給 Codex 審。

Claude Code 也會抓自己 subagent 的問題。寫權限規則的 subagent 訂了一條「更新卡片時,最後修改者必須是自己」。對照「對方已知」的規則時發現:對方按「知道了」只寫入已知的時間,不算修改內容,最後修改者還是原本的人,所以會被這條規則擋掉。於是請它補了一條「只確認」的更新路徑,並加上測試。

5. 叫它停,它不一定會停:Swift 的取消是合作式的

背景刷新是 iOS 在背景把 App 叫醒一下,讓它拉新資料。能跑多久由系統決定,超過就會被停掉,所以要自己設期限。

背景刷新時同時做兩件事:向伺服器拉最新的卡片,以及倒數 25 秒。哪一件先完成就用哪一件的結果;倒數先到,就要取消還沒回來的網路請求。一開始的寫法是用 task group(同時跑幾個子工作,等它們都結束)讓兩件事賽跑:

await withTaskGroup(of: [Card]?.self) { group in
    group.addTask { try? await fetch() }                          // pull latest cards
    group.addTask { try? await Task.sleep(for: timeout); return nil }
    let first = await group.next() ?? nil                         // whichever finishes first
    group.cancelAll()                                             // only a request
    return first                                                  // group still waits for fetch
}

看起來時間到就會返回,問題是 cancelAll() 只是告訴網路請求「該停了」,不會強制停掉。要不要停、什麼時候停,由那個工作自己檢查、自己決定。官方文件是這樣寫的:

Swift concurrency uses a cooperative cancellation model. Each task checks whether it has been canceled at the appropriate points in its execution, and responds to cancellation appropriately.

就像跟正在講電話的同伴說「該走了」,他什麼時候掛電話由他決定,你只能等。task group 也一樣,要等所有子工作結束才會返回;網路請求如果卡住、一直沒去檢查取消,整個背景刷新就跟著卡住,超過 iOS 給的時間。

修法是不等它。withCheckedContinuation 讓我們自己決定這個 await 什麼時候結束:網路請求和倒數各跑一個 Task,先到的一方呼叫 resume 交出結果。時間先到就直接返回空結果;網路請求一樣會收到取消,但不必等它結束。

await withCheckedContinuation { continuation in
    let work = Task {
        let value = await operation()
        if gate.claim() { continuation.resume(returning: value) }   // first one wins
    }
    Task {
        try? await Task.sleep(until: deadline, clock: .continuous)
        if gate.claim() {
            work.cancel()                                           // still ask it to stop
            continuation.resume(returning: nil)                     // return now
        }
    }
}

continuation 只能 resume 一次,重複 resume 會讓 App 直接當掉。gate 是一個加鎖的旗標,兩個 Task 都要先搶到它才能 resume,所以只會有一方成功。晚到的結果回來時旗標已經被拿走,結果直接丟掉,不會拿去排通知或改徽章。

測試也照這個情況寫:假的資料來源故意不理會取消,1.5 秒後才回來;期限設 200 毫秒,要求 0.8 秒內返回,而且等晚到的結果回來之後,確認沒有發通知、沒有改徽章。

6. 過程中踩到的坑

  • Codex 在背景卡住:codex exec 在背景執行時會一直等標準輸入結束,沒有任何輸出。在指令後面加上 </dev/null(給它空的輸入)就好了。
  • subagent 串流逾時:兩個做畫面的 subagent 跑到一半被系統判定停滯而中止,進度都還在各自的 worktree 裡,叫同一個 subagent 接著做就完成了。
  • 雙人同步測試的時序:「對方覆寫同一張卡」的測試偶爾失敗,原因是對方還沒收到原本那張,就先寫入,被權限規則當成替別人建立卡片而拒絕。實際使用時一定是先看到才會改,所以測試改成先等對方收到再操作。

7. 留給我決定的事

AI 遇到需求寫得不清楚、或需要取捨的地方,不自己決定,寫進 QUESTIONS.md:

編號 問題 目前
A-001 燈號的 Dark 顏色只寫「提亮 10%」,沒有色碼 先照 AI 的解讀做,等我給色碼
A-002 6 位數邀請碼可能被猜中,要接受風險,還是花錢用雲端函式擋 接受風險:24 小時失效、只能用一次、滿兩人就不能再加入
A-003 主按鈕白字的對比不到無障礙標準,改深色字可以嗎 維持深色字
A-004 目前用匿名登入:把 App 刪掉再重裝會變成新的身分,家庭已經滿兩人就加不回去 先維持匿名,之後改成 Google 登入
A-005 設計審查的 4 條 P1 都只影響最大字級,例如浮動按鈕會蓋住內容 先延後修

這幾題都是取捨,不是寫程式就能回答,所以留給我決定。

8. 目前的狀態

項目 結果
commit 51 個
第一個里程碑 9 項做完 8 項,剩 1 項要實機測試
第二個里程碑 14 項做完 13 項,剩裝到兩支手機
驗收 VERIFY: PASS:共用邏輯 48、權限規則 79、App 116 個測試
Codex 審查 7 份,沒有 P0;設計的 4 條 P1 延後

還沒做的部分:

  • 建立正式的 Firebase 專案、裝到兩支手機
  • 燈號的 Dark 色碼(A-001)
  • 大字級的 4 條設計修正(A-005)
  • 改成 Google 登入(A-004,要新增套件,得先寫決策紀錄)

明天

Day 15 建立 Firebase 專案、兩支 iPhone 開啟開發者模式、裝到手機使用。

參考資源

官方文件


上一篇
[Day 13] 投票第一週結果:說出口還不夠,所以第一版重做
下一篇
[Day 15] Security Rules 擋得住 App,擋不住管理者:建 Firebase 正式專案踩到的三件事
系列文
神隊友.swift:30 天 Vibe Coding 打造育兒 iOS App 共 15 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言