iT邦幫忙

2026 iThome 鐵人賽

DAY 6
0

模組二|工程底座與可驗收的 AI(Day 5–9)

昨天講座標系那條規則只寫了一句話就撐住 44 個檔案。今天把那句話所在的檔案整份打開。

這個 repo 的根目錄有兩份給 AI 讀的合約:AGENTS.md 69 行CLAUDE.md 139 行。兩份都是我自己寫的,內容幾乎不重疊,維護軌跡差得更遠——AGENTS.md 在 08-06 03:36 進 repo 之後再也沒有被改過,全歷史只有一個 commit;CLAUDE.md 從專案的第一個 commit 就存在,前後 10 個 commit 碰過它,從 68 行長到 139 行。

結論先講:一份合約有沒有用,看它有沒有把「不准做什麼」寫成具名清單。形容詞式的規則(「保持風格一致」「不要過度耦合」)在 AI 這裡等於沒寫,因為它無法判定自己有沒有違反。


問題的形狀:每一輪對話都是第一次

用 AI 寫程式最反直覺的一點:它不會記得你上次講過什麼。你昨天花十分鐘解釋為什麼這個專案不用 TypeScript,今天開新對話,它照樣可能給你一個 .ts 檔。

這不是模型笨。這是無狀態。而無狀態的系統要靠什麼維持一致性——把狀態外部化,放進它每次都會讀的地方。

不同工具讀不同檔名:Codex 讀 AGENTS.md,Claude Code 讀 CLAUDE.md。檔名是慣例,內容的結構是共通的


合約的五個區塊

AGENTS.md 全檔只有五個 ## 標題。以下把 69 行壓成骨架,ProjectCommands 兩段是原文,另外三段的內容用括號標示:

## Project
This repository is a JavaScript browser game named `save-the-dog-web`.
Core stack: Vite / PixiJS 8 / Matter.js 0.20 / Howler.js / Vitest / Playwright
Do not introduce TypeScript or a frontend UI framework such as React, Vue,
Svelte, or Angular.

## Commands
Before finishing a code task, run:
- `npm run lint`
- `npm run test:unit -- --run`
- `npm run build`
When a task changes rendering, browser behavior, or SVG assets, also run:
- `npm run assets:validate`
- `npm run test:e2e`

## SVG Rules        (八條 must + 一條 never,見下節)
## Engineering Rules(七條 bullet)
## Definition Of Done
Report: files added/changed/deleted, commands executed, lint/test/validation/
build results, remaining manual review items.
Do not declare a task complete when required checks fail.

五塊各自防一件事:

區塊 不寫會發生什麼
Project AI 引進不該有的框架,或用它熟悉的技術棧改寫你的
Commands 交出來的東西根本不能建置
SVG Rules / Engineering Rules 每一輪的寫法都不一樣
Definition Of Done 只跟你說「做好了」,你得自己去查它做了什麼

最後一塊常被當成客套話,我認為它是這五塊裡投報率最高的。Definition Of Done 要求的四欄——動了哪些檔、跑了哪些指令、四項檢查各自的結果、還剩下哪些需要人工目視——把「我做好了」這句話拆成四個可以被反駁的欄位。少了這一塊,你收到的回報會是一段流暢的自然語言摘要,而你得自己去 git status 和 CI 裡把真相挖出來。順帶一提,整份 AGENTS.md 沒有一句在教怎麼寫程式,六十九行全部是邊界與交付格式。

還有一個實務細節值得單獨講:合約裡的指令必須真的能跑。 AGENTS.md 點名六個 npm script(linttest:unitbuildassets:validatetest:e2epreview),package.jsonscripts 一共七項,六個全在裡面【實查】。寫了一個不存在的指令,AI 跑失敗之後最可能的行為是跳過它然後宣告完成——你等於自己給了它一個豁免。


29 分鐘之間,這份合約變精確了

這個專案有一份很少見的對照組:PRD.md 的〈Codex 專案規範〉一節裡,內嵌了一份 64 行的 AGENTS.md 草稿PRD.md:190-255 的程式碼區塊)。那份草稿在專案的第一個 commit 就存在(b4c2c62,03:07,當時檔名還是 deep-research-report.md,03:12 才更名為 PRD.md),實際的 AGENTS.md29 分鐘後1dd9971(03:36)。兩份長度差不多,內容的可判定性差很多。

草稿寫的 實際寫的 差在哪
Do not introduce TypeScript or a frontend UI framework. ...such as React, Vue, Svelte, or Angular. 從類別變成具名清單
contain semantic group IDs contain all required semantic <g id="..."> groups from docs/asset-inventory.md 從形容詞變成「去哪張表查」
use only colors defined in docs/art-style.md 同上,另加 plus none and currentColor 補例外,否則合法檔案會被誤判
avoid complex filters unless explicitly requested 整條刪掉 「複雜」判定不了
Never replace... 列 5 項 10 項 見下節
Do not directly couple scenes to Matter.js internals. keep synchronization one-way from Matter bodies to Pixi display objects 從「不要耦合」變成「同步只准往一個方向」
(無) Game logic uses the 750 x 1334 internal coordinate system from PRD.md. 新增

第一列是整張表最值錢的一列。「不要引進前端框架」是人話,AI 讀得懂,但你沒辦法用它寫出一條檢查。改成點名 React、Vue、Svelte、Angular 之後,它就能一比一地變成 eslint.config.js:22-50no-restricted-imports——pathsreactreact-domvuesveltepatterns@angular/*跟合約那句話的清單完全對應【實查】。同一段的「不引進 TypeScript」也一樣,變成 35 行的 scripts/check-no-typescript.js,掛在 npm run lint 的第一段。

精確不是為了嚴格,是為了讓規則有機會離開文件、變成程式。


Never 那一段

https://ithelp.ithome.com.tw/upload/images/20260811/201834797AJNWMYKYZ.png

我把那十扇看起來都很合理的小門,一扇一扇栓起來

AGENTS.md## SVG Rules 全段是這樣(:33-48):

## SVG Rules

SVG asset requirements are enforced by `scripts/validate-svg.js` and defined by
`docs/art-style.md` plus `docs/asset-inventory.md`.

Every SVG must:

- contain `xmlns="http://www.w3.org/2000/svg"`
- contain the exact requested `viewBox`
- use `preserveAspectRatio="xMidYMid meet"`
- contain all required semantic `<g id="...">` groups from `docs/asset-inventory.md`
- use only colors defined in `docs/art-style.md`, plus `none` and `currentColor`
- keep all visible geometry inside the `viewBox`
- keep at least 6% safe padding on all sides
- avoid text elements and non-ASCII visual characters

Never replace a requested SVG with emoji, Unicode symbols, CSS shapes, remote
URLs, Base64 images, `image` / `script` / `foreignObject` elements, event
attributes, or external fonts.

最後那一句,是從草稿到定稿改動幅度最大的一處:草稿列 5 項,定稿列 10 項。它列的十樣東西不是隨便想的,是卡住時的十條捷徑

捷徑 它為什麼看起來合理
塞一個 emoji 「反正是佔位圖,之後再換」
內嵌一張 Base64 PNG 「畫不出漸層,但至少畫面出得來」
引用一個遠端字型或圖片 URL 「這個圖示網路上有現成的」
<rect> + <circle> 拼形狀 「這就是 SVG 啊,沒有違規」
<text> 當圖示 「文字也是視覺元素」

每一條單看都能自圓其說,合起來就是專案風格徹底崩掉。這就是「不准做什麼」比「要做什麼」重要的原因:正面表述只描述了一個目標,反面表述封掉的是達不到目標時的所有出口。

需要誠實標一件事:這十項裡面,有一項到今天都沒有自動檢查在擋,而且我認為它天生就擋不住。是哪一項、為什麼,Day 8 那篇會用驗證器的原始碼講完。


CLAUDE.md 管的是另一件事:不要順手改回去

https://ithelp.ithome.com.tw/upload/images/20260811/20183479EsgryM1K0y.png

這根柱子是我故意歪的,所以我得把後果一起掛上去

如果 AGENTS.md 是「進門守則」,CLAUDE.md 就是「這棟房子為什麼有些地方長得很怪」。

它的比重可以量:全檔 18 個「不要」、6 個「不得」、3 個「不可」、12 個「必須」【實測:grep -oE '不得|不要|必須|不可' CLAUDE.md | sort | uniq -c】。而這些「不要」大部分不是禁止犯錯,是禁止把一個刻意的決定改回直覺版本

PhysicsManager 預設開啟 enableSleeping不要關掉:靜止的防線會被 Matter 的位置解算持續灌回能量而永遠搖擺(實測 10 秒漂移 ±250 px)⋯⋯

節流用 wall clock 是刻意的例外。 專案的通則是時間走 updateFixed⋯⋯AudioSystem 裡有註解說明,不要「順手修正」成固定步長。

難度靠開口(寬度、深度、位置、數量)、洞內障礙與畫線上限推進,不要調快蜜蜂

沒達到全勝就改關卡幾何,不要放寬測試

這一類條目的共同形狀是:「規則 + 違反它會發生的具體現象」。單寫「不要關掉 enableSleeping」,下一輪 AI(或三個月後的我)看到 sleeping 造成的怪行為,會覺得關掉很合理。加上「10 秒漂移 ±250 px」,那條路就被關掉了。

這也解釋了為什麼兩份檔案的維護節奏差這麼多:AGENTS.md 寫的是開工前就知道的事,所以寫完一次就定了;CLAUDE.md 寫的是撞到之後才知道的事,所以它跟著專案長。可以逐個 commit 量給你看:04:58 時它是 76 行,13:22 變 90 行、14:58 變 108 行、15:45 變 122 行、18:40 定在 139 行。63 行是玩法開始實作之後才補進去的,而那 63 行幾乎全是「這裡踩過,別再踩」。

代價也一起攤開:沒有人在維護 AGENTS.md,所以它現在有一條已經過期的條款。 :58 寫著「Keep foundation changes free of gameplay initialization until the render foundation change」——那個「render foundation change」在 08-06 04:08 的 314a7b4 就完成了,這條規則從那之後就沒有作用,但它還躺在合約裡。:57 的「Random gameplay behavior must use the project seeded random utility once it exists」也一樣,那個「once it exists」的條件早就滿足了。

合約會過期,而過期的合約比沒有合約更難察覺,因為它看起來還在生效。


合約管不到的地方

最後講一句誠實話,這句話是明天之後兩篇的入口。

寫在合約裡但沒有自動檢查的規則,我沒有辦法量它的違反率——這個專案沒有留下被擋下產出的紀錄,我不會憑印象編一個數字。我能說的是可查的那一面:AGENTS.md 裡的規則,有些能一比一變成 lint 規則或驗證器規則(今天示範了兩條),有些一條都寫不出來。這兩類的分界線在哪裡、佔比各是多少,是 Day 8 開頭與 Day 29 收尾的主題,今天不展開。

只留一句:合約管的是「AI 知道該做什麼」,檢查器管的是「AI 做錯了會被擋下」。只有前者,等於只有交通號誌沒有測速照相。


帶走什麼

合約的可執行性,等於它裡面具名清單的比例。凡是用形容詞寫的規則,都只是給模型的背景資訊,不是約束。

三件今天就能做的事:

  1. 把「不要 X 類的東西」改寫成「不要 A、B、C、D」。 具名之後你才有機會把它變成 lint 規則;沒具名之前它只是一句願望。
  2. 每一條「不要」後面補上違反的後果。 沒有後果的禁令,會在下一個人覺得合理的時候被撤銷。
  3. 給合約一個複查日。 帶時間條件的條款(「until X」「once Y exists」)會在條件滿足後靜靜失效,而檔案不會告訴你。

明天 Day 7 換一個更具體的場景:三十幾張 SVG 要分好幾批交給 AI 生成,怎麼讓它們看起來像同一個人畫的?答案跟今天同一條線——先訂命名。這個專案的做法是三層約束加一套母版制,而其中最反直覺的一層,目的不是為了讓素材好編輯,是為了讓「風格一致」這件事變成可以被程式檢查的東西。


本篇數字的快照時間:2026-08-07 12:35(+0800),對應 commit 5aa3705。專案仍在開發中,量體數字會變動;引用的每一項都可以用本文提到的檔案路徑與行號自行對照。
可玩網址https://save-the-dog-web.vercel.app/原始碼https://github.com/HarryFan/save-the-dog-web

參考資料

如果你卡在語法

深入原理


上一篇
Day 5|先決定座標系,再寫任何一行
下一篇
Day 7|先訂命名,再叫 AI 畫圖
系列文
一條線救一隻狗:我用 PixiJS、Matter.js 和一條有閘門的 AI 產線做完一款網頁小遊戲8
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言