范到發(fā)布:用SDD駕馭AI協(xié)作開發(fā)npm工具包的實(shí)踐)
老實(shí)說(shuō)我一開始對(duì)讓 AI 寫代碼這件事已經(jīng)有點(diǎn)審美疲勞了。不是不好用是太容易翻車需求說(shuō)一半AI 自由發(fā)揮一半改了三輪才發(fā)現(xiàn)最初的方向就偏了。直到我試著用 SDDSpec-Driven Development規(guī)范驅(qū)動(dòng)開發(fā)配合 AI 做一個(gè)小工具——一個(gè)用于文案排版的 npm 包整個(gè)協(xié)作方式才突然順了起來(lái)。寫規(guī)范、拆任務(wù)、生成代碼、跑測(cè)試、發(fā)布包每一步都有明確的驗(yàn)收標(biāo)準(zhǔn)AI 不再猜需求我也不用反復(fù)打補(bǔ)丁。這篇文章就把這套流程、踩過(guò)的坑、以及最終如何把包發(fā)到 npm 的完整過(guò)程記錄下來(lái)希望能給同樣在摸索 AI 協(xié)作開發(fā)的人一點(diǎn)參考。1. 把 AI 協(xié)作從聊天寫代碼升級(jí)成按規(guī)格施工1.1 對(duì)話式編程為什么經(jīng)常失控我以前用 AI 寫代碼的路徑基本是打開對(duì)話窗口描述一個(gè)功能等它吐出一大段實(shí)現(xiàn)然后復(fù)制到項(xiàng)目里跑。一開始確實(shí)驚艷但規(guī)模稍微上來(lái)就露餡了。比如我想做一個(gè)文本排版函數(shù)需求是把中英文之間加上空格AI 第一版只用一行正則替換。跑起來(lái)才發(fā)現(xiàn)問(wèn)題URL 里的AI被打上了空格代碼塊內(nèi)部的縮進(jìn)被清掉全角半角標(biāo)點(diǎn)混在一起時(shí)輸出完全不可控。問(wèn)題不在 AI而在需求本身。自然語(yǔ)言是有歧義的中英文之間到底指什么是指中文字符和 ASCII 字母之間還是單詞與單詞之間加空格要加在哪個(gè)位置遇到鏈接、代碼塊、引號(hào)嵌套時(shí)怎么處理如果這些邊界條件沒(méi)有定義AI 就只能根據(jù)概率給你猜一個(gè)答案。而這個(gè)猜的過(guò)程恰恰是不可控的根源。1.2 SDD 的六步閉環(huán)后來(lái)我在整理自己項(xiàng)目經(jīng)驗(yàn)時(shí)把以前做需求分析的習(xí)慣提煉成了一套流程恰好在社區(qū)里也看到不少人把它叫作 SDD。核心不是新概念就是把開發(fā)順序從先寫代碼再補(bǔ)測(cè)試變成先寫規(guī)范再寫實(shí)現(xiàn)。我個(gè)人實(shí)踐中固化下來(lái)的六步是定義目標(biāo)用一段話描述要解決的問(wèn)題不給任何實(shí)現(xiàn)方案。編寫規(guī)范把目標(biāo)拆成可驗(yàn)證的行為規(guī)則每個(gè)規(guī)則都附輸入、輸出示例和反例。拆解任務(wù)按依賴關(guān)系把規(guī)范拆成小任務(wù)每個(gè)任務(wù)有明確的驗(yàn)收標(biāo)準(zhǔn)。AI 實(shí)現(xiàn)把單個(gè)任務(wù)交給 AI帶上規(guī)范和測(cè)試用例讓它只做這一件事。人工審查跑測(cè)試、讀代碼、對(duì)照規(guī)范逐條確認(rèn)而不是直接信任輸出。收斂迭代把 review 中發(fā)現(xiàn)的新邊界情況變成新的規(guī)范條目回到第 2 步。這個(gè)流程放在 AI 協(xié)作里尤其順手因?yàn)?AI 最擅長(zhǎng)的是給定明確規(guī)則后生成實(shí)現(xiàn)而不是自己理解復(fù)雜模糊的業(yè)務(wù)場(chǎng)景。把業(yè)務(wù)判斷拿回人手里把模式識(shí)別交給 AI分工就清晰了。1.3 為什么拿排版工具來(lái)練手選排版工具當(dāng)實(shí)驗(yàn)品是因?yàn)樗妮斎胼敵鲎銐蛎鞔_非常適合驗(yàn)證 SDD 流程。我給你隨便丟一段中文廣告文案里面有中英文混排、全角半角標(biāo)點(diǎn)混用、多余空格排版規(guī)則是要把它們處理成統(tǒng)一風(fēng)格。這件事的規(guī)則看起來(lái)主觀但只要把規(guī)則逐條定義清楚就完全可以自動(dòng)化。它不像生成一個(gè)電商系統(tǒng)那樣宏大模糊也不像寫一個(gè)排序算法那樣沒(méi)有業(yè)務(wù)味道恰好處于 AI 能搞定和人類要拍板的中間地帶。于是就有了 typo-clean 這個(gè)包一個(gè)純 TypeScript 實(shí)現(xiàn)的文本排版函數(shù)庫(kù)附帶一個(gè)簡(jiǎn)單的 CLI專門處理中文文案里的空格、標(biāo)點(diǎn)、引號(hào)這類常見排版問(wèn)題。2. 排版包的規(guī)范拆解哪些規(guī)則可自動(dòng)化哪些必須放棄2.1 需求到規(guī)范的第一步先定邊界寫規(guī)范的第一步不是寫規(guī)則而是定義這工具不做什么。我給 typo-clean 定的邊界很明確它只處理純文本和 Markdown 文本不碰 PDF、不排版圖片、不做段落重排。這個(gè)邊界看著像廢話但非常關(guān)鍵。因?yàn)橐坏┻吔绮磺逦鶤I 在實(shí)現(xiàn)時(shí)就會(huì)自作主張往里面塞功能比如給你輸出一個(gè) HTML 解析器或者把換行全改成段落標(biāo)簽。邊界定完后才開始定義核心規(guī)則。我給整個(gè)工具定了 8 條初始規(guī)則全部圍繞中文文案排版規(guī)則編號(hào)規(guī)則名稱示例說(shuō)明R1中西文之間加空格AI賦能 → AI 賦能在 CJK 字符和 ASCII 字母/數(shù)字之間插入空格R2統(tǒng)一標(biāo)點(diǎn)形式你好,world → 你好world中文語(yǔ)境使用全角逗號(hào)、句號(hào)R3引號(hào)整理AI → AI中文語(yǔ)境使用彎引號(hào)但代碼塊內(nèi)不處理R4壓縮重復(fù)標(biāo)點(diǎn)太好了!!! → 太好了連續(xù)重復(fù)標(biāo)點(diǎn)只保留一個(gè)R5清理行尾空白你好 \n → 你好\n去除每行末尾多余空格R6數(shù)字與單位不拆5G網(wǎng)絡(luò) → 5G 網(wǎng)絡(luò)5G內(nèi)部不加空格但后面緊跟中文時(shí)加空格R7保護(hù)代碼塊\nconst a1\n 原樣保留代碼塊內(nèi)部不應(yīng)用任何規(guī)則R8保護(hù) URL訪問(wèn)https://example.com → 訪問(wèn) https://example.comURL 作為整體內(nèi)部不加空格這里最反直覺(jué)的一條是 R8。常規(guī)正則替換很容易把 URL 內(nèi)部的連字符、點(diǎn)號(hào)、斜杠都當(dāng)成邊界處理結(jié)果把鏈接搞得支離破碎。所以規(guī)范里必須明確URL 是不可分割的整體處理時(shí)先提取保護(hù)處理完再放回去。2.2 每條規(guī)則都得有正例和反例規(guī)范文檔里只有規(guī)則描述是不夠的AI 和人都會(huì)對(duì)自然語(yǔ)言產(chǎn)生不同理解。我給每條規(guī)則都配了至少一個(gè)正例和一個(gè)反例。正例說(shuō)明什么情況要處理反例說(shuō)明什么情況不要處理。R1 的反例就很有意思英文單詞內(nèi)部不能加空格Transformer 不能被處理成 Trans former再有就是 Markdown 標(biāo)題語(yǔ)法后不能加##AI 應(yīng)該變成 ## AIATX 標(biāo)題標(biāo)記和標(biāo)題文本之間需要空格但這種空格是 Markdown 結(jié)構(gòu)性空白不是排版規(guī)則該管的。我干脆在規(guī)范里寫死R1 只處理 CJK 字符和 ASCII 字母/數(shù)字相鄰的邊界不處理字符串內(nèi)部、不處理標(biāo)記符號(hào)。這樣一個(gè)一個(gè)摳出來(lái)規(guī)范文檔寫到第 35 條時(shí)已經(jīng)非常長(zhǎng)了但每條規(guī)則長(zhǎng)度只有一兩行示例。事實(shí)證明這部分投入非常值A(chǔ)I 后續(xù)生成的代碼幾乎不需要返工。2.3 從規(guī)范直接生成測(cè)試用例規(guī)范里的正例和反例直接就是測(cè)試用例。我把 spec.md 放在倉(cāng)庫(kù)根目錄用 Markdown 表格維護(hù)規(guī)則然后用一個(gè)腳本把這些示例對(duì)解析成 Vitest 測(cè)試。也就是說(shuō)改規(guī)范表格里的任何一行示例測(cè)試代碼就會(huì)自動(dòng)變化。這樣做的好處是規(guī)范永遠(yuǎn)和測(cè)試同步AI 拿到的驗(yàn)收標(biāo)準(zhǔn)就是測(cè)試本身不存在文檔說(shuō)一套測(cè)試測(cè)另一套的情況。3. 六步 SDD 實(shí)操記錄從規(guī)范文件到第一版提交3.1 目標(biāo)定義和任務(wù)拆解項(xiàng)目啟動(dòng)時(shí)我只寫了一段話放在 README 頂部目標(biāo)是提供一個(gè) npm 庫(kù)能讓開發(fā)者輸入一段中文/中英混排的文案輸出經(jīng)過(guò)統(tǒng)一排版規(guī)則的文本。用戶主要通過(guò)函數(shù)調(diào)用或簡(jiǎn)單 CLI 使用不需要圖形界面。排版規(guī)則以 spec.md 為準(zhǔn)。這段話后來(lái)基本沒(méi)有改過(guò)它給整個(gè)項(xiàng)目定住了風(fēng)向。AGENTS.md 里有一段更長(zhǎng)的描述但核心就是這幾句AI 每次讀上下文時(shí)先看到這個(gè)不會(huì)跑偏。任務(wù)拆解則是按照依賴順序核心格式化引擎輸入字符串輸出處理后的字符串內(nèi)部按規(guī)則順序執(zhí)行。保護(hù)機(jī)制先把 URL、代碼塊、行內(nèi)代碼提取出來(lái)處理完再還原。規(guī)則實(shí)現(xiàn)每 2-3 條規(guī)則一組一個(gè)任務(wù)一個(gè)分支。CLI 入口用 Node.js 解析命令行參數(shù)讀取文件或標(biāo)準(zhǔn)輸入。打包發(fā)布生成類型聲明配置 package.json 的入口文件驗(yàn)證發(fā)布結(jié)果。這個(gè)順序很重要。一開始執(zhí)行保護(hù)機(jī)制后面的規(guī)則就會(huì)在安全環(huán)境里工作極大降低正則誤傷概率。3.2 AI 實(shí)現(xiàn)階段一條任務(wù)一個(gè)合并請(qǐng)求到 AI 實(shí)現(xiàn)階段我不再讓它一口氣生成整個(gè)項(xiàng)目。每一個(gè)子任務(wù)拆出來(lái)時(shí)都已經(jīng)帶著對(duì)應(yīng)的測(cè)試文件。比如 R1 的實(shí)現(xiàn)任務(wù)我先提交了一個(gè)只有測(cè)試的文件測(cè)試?yán)飳懼鳤I賦能 → AI 賦能然后把 spec.md 片段一起提供給 AI讓它只去實(shí)現(xiàn) typoCleanR1 這個(gè)函數(shù)。AI 第一次給出的實(shí)現(xiàn)是function typoCleanR1(text: string): string { return text.replace(/([\u4e00-\u9fff])([A-Za-z0-9])/g, $1 $2) .replace(/([A-Za-z0-9])([\u4e00-\u9fff])/g, $1 $2); }這個(gè)版本看起來(lái)沒(méi)問(wèn)題但只過(guò)了最基本的測(cè)試。真正的問(wèn)題出現(xiàn)在組合場(chǎng)景當(dāng)字符串里同時(shí)包含 URL 和中文時(shí)比如訪問(wèn)https://example.com/AI助手它會(huì)先經(jīng)過(guò) R1把AI助手處理成AI 助手但 URL 里的com/AI也會(huì)被處理成com/ AI并不會(huì)因?yàn)閏om/后是A前面是m/是 ASCII 和斜杠相鄰R1 不會(huì)觸發(fā)??蓡?wèn)題出在別的地方如果 URL 里包含了形如zh-cn的段R1 的正則并不會(huì)觸碰連字符但后面 R8 保護(hù)邏輯還沒(méi)生效時(shí)R1 就已經(jīng)在里面改了不該改的東西。這個(gè)問(wèn)題的根源是規(guī)則之間的順序耦合。我最后決定在架構(gòu)層把所有保護(hù)邏輯提前先提取 URL 和代碼塊保留位置占位符再執(zhí)行 R1 到 R6最后還原。這個(gè)改動(dòng)既不是 AI 主動(dòng)提出來(lái)的也不是我拍腦袋拍出來(lái)的而是在 review 階段對(duì)照規(guī)范時(shí)發(fā)現(xiàn) R1 和 R8 存在沖突后做的設(shè)計(jì)決策。3.3 人工審查時(shí)我具體看什么很多人讓 AI 寫完代碼就直接合并最多跑一遍測(cè)試。但我的經(jīng)驗(yàn)是測(cè)試通過(guò)只說(shuō)明規(guī)則被滿足不代表實(shí)現(xiàn)方式是對(duì)的。我會(huì)重點(diǎn)看三個(gè)地方是否修改了規(guī)范之外的行為比如 AI 可能順手把換行符統(tǒng)一成 LF這在 Windows 環(huán)境下屬于越權(quán)必須禁止。正則是否有潛在的災(zāi)難性回溯排版工具可能跑在長(zhǎng)文本上一個(gè)復(fù)雜度很高的正則就能拖垮頁(yè)面。規(guī)則之間有沒(méi)有隱式依賴比如先替換標(biāo)點(diǎn)還是先加空格順序不同結(jié)果可能完全不同。這些審查點(diǎn)我在第一次使用 AI 時(shí)踩過(guò)后來(lái)全部寫進(jìn)了審查清單每次提交代碼時(shí)按清單過(guò)一遍效率很高。4. 最容易翻車的三處實(shí)現(xiàn)細(xì)節(jié)鏈接保護(hù)、成對(duì)符號(hào)和邊界空格4.1 預(yù)處理領(lǐng)先于一切規(guī)則typo-clean 的最終架構(gòu)和大多數(shù)人想象的一堆正則順序執(zhí)行不同。我采用了兩階段處理export function cleanTypo(text: string): string { const tokens: Token[] []; const content protectTokens(text, tokens); const cleaned applyRules(content); return restoreTokens(cleaned, tokens); }protectTokens 負(fù)責(zé)把所有代碼塊、行內(nèi)代碼、URL、以及不需要處理的占位符提取出來(lái)替換成不可見字符加索引的形式。applyRules 在安全文本上操作restoreTokens 最后把原始內(nèi)容塞回去。這個(gè)設(shè)計(jì)的直接收益是URL 內(nèi)部的字符永遠(yuǎn)不會(huì)被誤處理。URL 里出現(xiàn)的下劃線、斜杠、點(diǎn)號(hào)在保護(hù)階段就離開了規(guī)則作用域。這個(gè)方案其實(shí)不復(fù)雜代碼量也很小但是結(jié)構(gòu)化地解決了 AI 原生實(shí)現(xiàn)里最容易出現(xiàn)的問(wèn)題。后來(lái)我測(cè)試了 100 個(gè)真實(shí) URL 和 20 個(gè)代碼塊零誤傷。4.2 成對(duì)符號(hào)的狀態(tài)處理另一個(gè)翻車點(diǎn)是引號(hào)。中文排版規(guī)范里彎引號(hào)要成對(duì)出現(xiàn)左引號(hào)和右引號(hào)不能搞混。簡(jiǎn)單的正則替換會(huì)把所有直引號(hào)變成同一個(gè)方向的彎引號(hào)輸出像這是一個(gè) 測(cè)試 文本里的引號(hào)方向完全錯(cuò)亂。AI 在這個(gè)問(wèn)題上的第一版實(shí)現(xiàn)是text text.replace(//g, \u201C); // 全部變成左引號(hào)這版跑測(cè)試直接紅了因?yàn)橐?guī)范里寫了右引號(hào)對(duì)應(yīng)的用例。實(shí)現(xiàn)正確邏輯需要跟蹤狀態(tài)遇到一個(gè)引號(hào)時(shí)判斷它是左還是右取決于上一個(gè)引號(hào)的狀態(tài)、上下文是否為中文引用的開頭。我最后用了一個(gè)雙向狀態(tài)標(biāo)志來(lái)解決代碼大致是let open false; let result ; for (const ch of text) { if (ch ) { result open ? \u201D : \u201C; open !open; } else { result ch; } }這段代碼是 AI 生成的但我在審查時(shí)發(fā)現(xiàn)一個(gè)缺陷如果文本是從中間截?cái)嗟幕蛘咭?hào)數(shù)量本身是奇數(shù)會(huì)出現(xiàn)方向錯(cuò)位。于是我在規(guī)范里加了一條當(dāng)引號(hào)數(shù)量不配對(duì)時(shí)保留原始字符不強(qiáng)行轉(zhuǎn)換。這屬于典型的規(guī)范越細(xì)AI 輸出越穩(wěn)的例子。4.3 邊界空格不是越細(xì)越好加空格的規(guī)則里數(shù)字與單位不拆是個(gè)很微妙的邊界。規(guī)范定義數(shù)字緊隨英文字母時(shí)數(shù)字和字母之間不加空格但在數(shù)字單位英文字母整體和中文相鄰時(shí)整體作為單詞加空格。5G網(wǎng)絡(luò) → 5G 網(wǎng)絡(luò)但5G內(nèi)部不加空格。AI 在這里給出的正則方案是text.replace(/([0-9])([A-Za-z])/g, $1 $2)直接把5G拆成了5 G這是我一開始最擔(dān)心的誤傷。后來(lái)在規(guī)范里明確數(shù)字與字母之間是否存在空格依據(jù)字典表判斷并為常見的單位G、GB、KB、cm、mm、Hz 等建立了一張白名單。這個(gè)表不長(zhǎng)但解決了 AI 無(wú)法通過(guò)上下文準(zhǔn)確判斷的行業(yè)術(shù)語(yǔ)問(wèn)題。這里收獲很大排版規(guī)范有些部分需要精確到字符級(jí)別但也有些部分需要放到領(lǐng)域知識(shí)層面不是 AI 從文本模式中就能學(xué)出來(lái)的。5. 發(fā)布 npm 包的過(guò)程比寫代碼更折騰PowerShell、鏡像源和 files 字段5.1 Windows 環(huán)境下的 npm.ps1 執(zhí)行策略問(wèn)題寫完代碼只是萬(wàn)里長(zhǎng)征第一步發(fā)布 npm 包時(shí)我才發(fā)現(xiàn)本機(jī)環(huán)境一堆問(wèn)題。最常見的那個(gè)報(bào)錯(cuò)就是npm : 無(wú)法加載文件 C:\Program Files\nodejs\npm.ps1因?yàn)樵诖讼到y(tǒng)上禁止運(yùn)行腳本這是 PowerShell 的執(zhí)行策略限制了 .ps1 腳本運(yùn)行而 npm 的 Windows 安裝包默認(rèn)是通過(guò) npm.ps1 調(diào)用的。解決辦法不是關(guān)掉整個(gè)執(zhí)行策略而是只針對(duì)當(dāng)前用戶放開Set-ExecutionPolicy -Scope CurrentUser RemoteSigned這里要注意別順手寫成Set-ExecutionPolicy Unrestricted那會(huì)把機(jī)器置于不可控狀態(tài)。如果你在公司電腦上最好先和本地安全策略確認(rèn)一下避免影響其他腳本的權(quán)限審計(jì)。5.2 鏡像源過(guò)期導(dǎo)致證書報(bào)錯(cuò)發(fā)布前我想先跑一輪 npm install 驗(yàn)證依賴結(jié)果屏幕上出現(xiàn)了一堆npm ERR! code CERT_HAS_EXPIRED。仔細(xì)看錯(cuò)誤信息里面訪問(wèn)的是https://registry.npm.taobao.org/...。這其實(shí)是一個(gè)歷史遺留問(wèn)題早前我為了提速把 registry 切到了淘寶鏡像但老地址的證書已經(jīng)過(guò)期。新版鏡像源已經(jīng)遷移到了https://registry.npmmirror.com。排查方式很簡(jiǎn)單先看當(dāng)前 registrynpm config get registry然后統(tǒng)一改成新版鏡像源或官方源npm config set registry https://registry.npmmirror.com如果你用的是公司內(nèi)部 Nexus 倉(cāng)庫(kù)同理需要確認(rèn)證書鏈?zhǔn)欠裢暾?。這里給出的經(jīng)驗(yàn)和發(fā)不了包的關(guān)系很多人要等到發(fā)布時(shí)才能體會(huì)到。5.3 發(fā)布前先用 npm pack 檢查包內(nèi)容真正執(zhí)行npm publish之前我建議先跑一遍npm pack --dry-run這個(gè)命令會(huì)列出最終打到 npm 包里的所有文件。我在這一步發(fā)現(xiàn)了兩個(gè)問(wèn)題一是src/目錄也被打包進(jìn)去了而我并不想讓消費(fèi)者看到 TypeScript 源碼二是spec.md沒(méi)有被包含但我想讓使用者能查看規(guī)則說(shuō)明。解決方案是在 package.json 里顯式配置 files 字段files: [ dist, spec.md, README.md ]files 字段是白名單機(jī)制只要寫清楚就不會(huì)把任何多余文件帶進(jìn)去。同時(shí)確認(rèn)main指向 dist/index.jstypes指向 dist/index.d.ts。這一步不做好最容易出現(xiàn)的情況就是包發(fā)上去了別人import進(jìn)來(lái)卻是 undefined。5.4 發(fā)布后必須做的冒煙測(cè)試發(fā)布成功不代表萬(wàn)事大吉。我會(huì)在另一個(gè)干凈目錄里重新安裝一遍npm i -g typo-clean echo AI賦能 | typo-clean輸出正確后再用 Node 跑一次函數(shù)調(diào)用node -e const { cleanTypo } require(typo-clean); console.log(cleanTypo(AI賦能))兩次都通過(guò)才說(shuō)明這個(gè)包在真實(shí)環(huán)境里可用。這個(gè)流程看起來(lái)繁瑣但能篩掉一大半類型聲明挺好跑起來(lái)報(bào)錯(cuò)的尷尬情況。實(shí)測(cè)下來(lái)發(fā)布一個(gè)純 TypeScript 寫的工具包最容易出問(wèn)題的往往不是業(yè)務(wù)邏輯而是exports字段沒(méi)配好導(dǎo)致 ESM 和 CJS 環(huán)境下的加載行為不一致。6. 嘗到甜頭之后我現(xiàn)在怎么安排 AI 與人工的分工6.1 AI 負(fù)責(zé)模式識(shí)別人負(fù)責(zé)邊界定義這個(gè)項(xiàng)目做完后我最大的感觸是AI 的能力邊界不在代碼生成而在業(yè)務(wù)邊界的判斷。AI 可以寫出非常漂亮的實(shí)現(xiàn)但它不知道數(shù)字單位白名單該包含哪些詞不知道 URL 在中文排版里應(yīng)該整體保留更不知道彎引號(hào)不配對(duì)時(shí)就保留原樣這種面向異常的策略。這些邊界定義必須由人來(lái)完成而且越早寫進(jìn)規(guī)范后面返工越少。SDD 的價(jià)值就在于它逼著你在編碼之前把這些邊界想清楚。沒(méi)有這套流程時(shí)我可能寫完正則才開始想邊界結(jié)果被各種誤傷追著跑。有了規(guī)范之后AI 的實(shí)現(xiàn)質(zhì)量明顯提升review 的時(shí)間反而大幅縮短。6.2 我還在堅(jiān)持的幾條習(xí)慣項(xiàng)目結(jié)束后我把這套流程沉淀進(jìn)了自己的項(xiàng)目模板現(xiàn)在已經(jīng)成了固定習(xí)慣每個(gè)項(xiàng)目的根目錄都有一份 spec.md所有業(yè)務(wù)規(guī)則的唯一權(quán)威來(lái)源。任何 AI 產(chǎn)生的代碼變更必須對(duì)應(yīng)一個(gè)測(cè)試用例不能只有代碼沒(méi)有測(cè)試。規(guī)則的分裂或修改先改 spec再改代碼順序不能反。每次 AI 生成了意料之外的好方案我會(huì)回填進(jìn)規(guī)范作為參考實(shí)現(xiàn)知識(shí)庫(kù)越攢越多。這個(gè)習(xí)慣給我?guī)?lái)的直接變化是AI 生成代碼的采納率從原來(lái)的不到一半提高到了八成以上。剩下的兩成不是代碼質(zhì)量問(wèn)題而是業(yè)務(wù)邏輯本來(lái)就沒(méi)定義清楚。說(shuō)到底AI 并不是替你寫代碼而是替你執(zhí)行你已經(jīng)寫好的規(guī)范。規(guī)范寫得越清晰AI 協(xié)作的體驗(yàn)就越接近靠譜同事而不是猜謎選手。做 typo-clean 這個(gè)排版包只是個(gè)小項(xiàng)目但驗(yàn)證下來(lái)的這套協(xié)作范式我覺(jué)得值得每個(gè)人都試一次。