讓 AI Agent 接手舊專案前,我先寫了哪些驗收條件

以 Hexo 遷移到 Astro 6 的真實工作為例,拆解如何替 AI Agent 定義範圍、權限 gate、可驗證產物與停止條件,避免只得到一個看起來正常的新首頁。

這次把停更多年的 Hexo 部落格搬到 Astro 6,我確實讓 AI Agent 參與了大量工作。但我沒有只給一句「幫我把 Hexo 改成 Astro,畫面做漂亮一點」。

舊專案最危險的地方通常不是新功能做不出來,而是 Agent 很快做出一個能 build 的新網站,同時讓 139 個舊網址、搜尋引擎 canonical、Disqus identity 和一張拿不到的圖片悄悄消失。

先把任務改寫成可以失敗的規格

如果規格只有「完成遷移」,Agent 很容易把「新首頁能打開」當成成功。這次的目標文件反而先寫出大量會讓任務判定失敗的條件:

scope:
  source: Hexo 3.8
  target: Astro 6.x
  published_posts: 35
  local_posts: 36
  legacy_html_paths: 139

must_preserve:
  - URL 大小寫
  - canonical 與 og:url
  - Disqus identifier
  - 原始發布與更新時間
  - 一張失效圖片的真實狀態

must_not_do:
  - 公開本機獨有 draft
  - 捏造失效圖片
  - 搬移失效 UA Analytics
  - 未經同意 push 或部署

這段規格的功能不是把 Prompt 寫得很長,而是替後面的 checker 提供輸入。若 legacy_html_paths 寫成 139,最後就必須得到 139 / 139;少一個不能用「大部分正常」帶過。

把權限拆成 gate,不要一次交出去

程式能不能修改、分支能不能建立,以及網站能不能正式上線,是三種不同權限。

這次的流程分成兩個明確 gate:

  1. 第一次批准只允許建立 migration/astro-6,並在該分支完成 13 個可建置提交。
  2. 第二次批准才可能包含 push、merge、切換 GitHub Pages Source 與部署。

第一個 gate 通過,不代表第二個也自動通過。即使 workflow 已經寫好,本機驗證全部成功,Agent 仍必須停在 cutover 報告,不能因為「剩最後一步」就自行部署。

這種設計適合所有有外部副作用的 Agent 工作。可以把權限拆成:

讀取與盤點 → 本機修改 → 建立隔離分支 → 外部寫入 → 正式切換

每一段都需要不同的驗收證據。Git commit 是可回看的本機狀態;push、雲端設定與 production deploy 則會影響其他人,不能混成同一個「請幫我完成」。

先做 routing spike,再大量搬內容

Astro 支援 build.format: 'preserve',看起來正好能保留 .html 文章與目錄型 index.html。但「文件說支援」和「這個舊站的所有路徑都能保留」仍然是兩件事。

正式搬移 36 篇 Markdown 前,Agent 先建立最小 routing spike,實際 build 出:

  • /Hexo/why-choose-hexo.html
  • /about/index.html
  • /tags/Hexo-searchdb/index.html
  • /tags/hexo-searchdb/index.html

這裡很快遇到一個容易漏掉的問題:macOS 常見的大小寫不敏感檔案系統,會把後兩個路徑視為同一個位置。Astro 顯示 141 條 route 都成功生成,但實體目錄只剩 139 個 HTML,Pagefind 也少索引一頁。

如果只看 build exit code,這次測試會被判定成功。最後是改到大小寫敏感的 APFS volume 重建,才同時看到 141 個 HTML 與 36 個 Pagefind 索引頁。

這個例子說明 Agent 的驗收不能只問「指令有沒有成功」,還要問「成功後的世界是不是我們要的狀態」。

把大任務切成每一步都能 build 的狀態

遷移依照下面順序拆成 13 個 commit:

  1. 凍結 Hexo inventory。
  2. 建立 Astro 6 基礎。
  3. 定義 Content Collection schema。
  4. 匯入 Markdown。
  5. 保留文章路由。
  6. 重建 archive 與 taxonomy。
  7. 加入可替換的設計基礎。
  8. 搬移圖片。
  9. 恢復 Disqus 與中文搜尋。
  10. 補上 SEO、feeds 與相容端點。
  11. 讓舊 service worker 安全退役。
  12. 加入 parity tests。
  13. 準備仍未啟用的 Pages workflow。

每個 commit 都必須能 build,不能先提交已知失敗狀態,再期待下一個 commit 修好。這項限制會迫使 Agent 在每一步留下可理解的邊界,也讓人工審閱時不用跨越三、四個 commit 才看懂某個錯誤為什麼暫時存在。

Prompt 不能取代 checker

我可以在目標裡寫「不要弄壞舊網址」,但真正有用的是 check-legacy-urls.mjs。我也可以寫「文章內容要完整」,但最後仍要逐篇比對:

  • 標題、日期、分類與標籤。
  • 純文字長度與 heading。
  • link、image、iframe 與 code block 數量。
  • canonical、og:url 與 Disqus identity。

最後的遷移基準包含:

驗收結果
Legacy HTML139 / 139 exact case
公開文章結構35 / 35
Internal references4,543
Fragment references275
圖片 variants152 / 152
靜態 accessibility141 / 141
Secret findings0
Playwright桌機與手機 8 種頁面

Prompt 負責說明意圖,checker 負責拒絕不符合意圖的產物。兩者缺一不可。

不確定的事情要留下,不要補成完整故事

Agent 很擅長把缺口補成讀起來合理的內容,但 migration 有幾種缺口不能補:

  • 一張 Facebook CDN 圖片已經失效,沒有可驗證備份。
  • SlideShare 在 headless browser 可能顯示供應商 fallback。
  • Astro 6 的 dependency audit 有一組 moderate 問題,但修復版本屬於 Astro 7 major upgrade。
  • 本機獨有的 2021 文章沒有公開紀錄。

這些項目最後分別被標成來源失效、外部服務限制、dependency exception 與 draft,而不是生成一張替代圖片、宣稱 iframe 完全正常、強制 major upgrade,或推測那篇文章原本就應該公開。

對 Agent 來說,「不知道」也必須是一種合法輸出。否則規格寫得越完整,它越可能為了填滿所有欄位而開始猜測。

我現在會怎麼設計 Agent 任務

經過這次遷移,我會先替較大的 Agent 工作準備六項內容:

  1. Source of truth:哪些檔案、線上狀態或 API 回應可以當成事實。
  2. Non-goals:哪些相鄰問題本次明確不處理。
  3. Permission gates:本機修改、外部寫入與 production 變更分別何時允許。
  4. Acceptance checks:每一項要求如何產生 pass/fail。
  5. Reversible path:失敗時保留哪個 branch、artifact 或備份。
  6. Unknowns report:無法取得與需要人工決定的內容放在哪裡。

AI Agent 能加快盤點、產生腳本、搬移內容與重複驗證,但它不應替我們決定什麼可以消失,也不應因為測試大多數通過,就把剩下的差異當成雜訊。

讓 Agent 接手舊專案之前,我現在最先寫的不是「請使用哪個框架」,而是「什麼證據能證明它沒有把舊世界弄丟」。

Discussion

文章留言

留言服務會連線到 Disqus;只有在你選擇載入後才會建立外部連線。