Verify Finite
Finite 有兩個操作介面,大多數功能需要兩者協同運作。代理透過位於 /mcp 的 MCP 工具編輯設計(包含六個 doc6 工具;call 驅動位於 /mcp/classic 的經典工具)。使用者則在瀏覽器中觀看:設計清單位於 /,畫布位於 /design/?id=<designId>。一個證明過程使用 call 驅動代理端,並在頁面中檢查使用者端的結果,反之亦然(用於評論)。
測試框架是 e2e/headless.mjs。.agents/skills/finite-headless/SKILL.md 是其命令參考(包含能進入 Shadow Root、沙盒 iframe、--agent、Python 的選擇器)。請在首次驅動前閱讀一次。此技能增加了隔離性、一個健檢工具(doctor)、一個證據資料夾、前後對比執行和功能對應表。
以下每個命令都從 checkout 根目錄執行:
V=.agents/skills/verify-finite/scripts/verify.sh
Shell 變數無法在不同工具呼叫之間保留。請在每次呼叫的開頭重複 V=... 和 RUN=...。
步驟
- 挑選功能。 在
features/README.md的「修改會影響哪些功能」表格中,找出你的修改所觸及的功能。 - 啟動 一個你程式碼的執行實例。如果是錯誤修復,也請同時啟動一個修復前程式碼的執行實例(請參閱「前後對比」)。
- 健檢 每個執行實例,直到它只輸出
ok行。 - 驅動 所選每個功能檔案中的每個進入點,並在過程中儲存證據。
- 清理 你啟動的每個執行實例,包括失敗的。
- 報告 使用「報告」章節中的格式。
當所選每個功能的每個進入點都被證明、有理由地被跳過、或失敗並附有證據,且 $V list 顯示沒有正在執行的項目時,你就完成了。
啟動
$V launch # 建構伺服器、finite.wasm 和 Web 應用程式,然後啟動它們
$V launch --no-build # 重複使用最後一次建構的結果;如果程式碼在建立後有變更則會拒絕
$V launch --width 390 --height 844 # 手機尺寸的頁面(預設為 1440 × 900)
$V launch --at main # 另一個提交的執行實例,位於一個臨時 checkout 中(請參閱「前後對比」)
它會輸出 RUN=<run>。請保留此 ID:所有其他命令都需要它。冷啟動建構大約需要 2 分鐘,而無需建構的執行只需幾秒鐘。請將 Bash 超時設置為 600000。--no-build 僅在無需建構時節省幾秒鐘,因此若有疑慮,請不要使用此選項。
當啟動完成並輸出 Finite at http://127.0.0.1:<port> 時即表示就緒。框架會等待 /api/health 並載入設計清單,然後才會輸出該行。資料夾最初是空的,沒有種子資料、遷移或登入。使用者以「Guest」身份發布評論,代理以「Agent」身份發布,除非你傳遞 --agent <name>。
每次執行都是一個獨立的會話(verify-<run>),擁有自己的空閒連接埠、臨時資料夾和 Chrome 設定檔,因此執行實例可以並行運行。切勿驅動你未啟動的 Finite。vp run dev 會從 crates/server/data 提供使用者的真實設計,桌面應用程式也會提供使用者自己的資料。健檢工具會對連接到他人伺服器的會話報告失敗。
執行實例會在空閒 30 分鐘後自行停止。之後,doctor 會失敗:請清理並重新啟動。
健檢
$V doctor $RUN
它僅讀取狀態,每行輸出 ok 或 FAIL。它檢查五件事:
- 會話是否存活並擁有私有的
finite-headless-data-*資料夾。 /api/health是否回應ok並包含package.json中的版本。- 正在運行的建構版本是否與程式碼匹配。它比較建構輸入(
crates/、apps/web/、框架、鎖定檔,包括未追蹤的檔案)的雜湊值與建構該執行實例時的程式碼雜湊值。對文件、技能或測試的編輯不計入。 - 是否存在 Chrome、Chromium 或 Edge 以供
get_screenshot使用。 - 頁面是否回應。
然後它會列印自上次呼叫以來的頁面主控台輸出。請在首次執行時、遇到任何意外情況後,以及在擷取最終證明之前執行它。如果發生 FAIL,請清理並重新啟動,不要繼續驅動。在你修改程式碼後,建構檢查會按設計意圖失敗:請清理並重新啟動。
驅動
$V h $RUN <command> ... 對此執行實例執行任何 e2e/headless.mjs 命令。你最常使用的命令:
$V h $RUN call create_design '{"name":"Verify"}' # 代理端:輸出 designId "<id>"
$V h $RUN call create_artboard @input.json # JSON 內嵌、@file 或 - 用於標準輸入
$V h $RUN design <designId> # 使用者端:打開畫布,等待圖板
$V h $RUN snapshot --selector main # 無障礙樹,使用者看到的內容
$V h $RUN click 'button[title="Comment (C)"]'
$V h $RUN click '[data-artboard] >>> h1' --shift # Shift-點擊
$V h $RUN upload 'input[type=file]' card.html # 選取檔案,如同作業系統的選取器
$V h $RUN api GET /api/designs/<designId> # 儲存的設計,包含評論
使用這些穩定的代稱。它們是應用程式的無障礙名稱和標題:
| 位置 | 代稱 |
|---|---|
| 設計清單 | 標題 Designs,按鈕 New design,搜尋框 Search designs by name,每個設計卡片一個 link |
| 畫布頂部欄 | 連結 ← Designs,button[title="CSS variables every artboard uses, in each mode"] (Tokens),button[title="Every comment, open and resolved"] (Comments,其名稱中包含開啟計數),Import,Export,每個在場代理的一個晶片 |
| 畫布工具 | 工具列 Tools:button[title="Pointer (V)"],button[title="Comment (C)"];按鍵 v、c、Shift+1(適應畫面),=/-(縮放) |
| 評論框 | 文字框 Add a comment,button[title="Change your name"],[data-selection-count],按鈕 Send,名為 Comment: <first message> 的圖釘 |
| 評論串 | 名為 Comment thread 的 [role=dialog]:按鈕 Delete、Resolve/Reopen、Close、Edit(在你的最新訊息上),文字框 Reply |
| 面板 | 帶有標籤頁 Open N / Resolved N 的 aside[aria-label=Comments];帶有 form[aria-label="Add a token"] 的 section[aria-label=Tokens];nav[aria-label=Pages];[role=menu][aria-label=Export];帶有 status 的 aside[aria-label=Import] |
| 圖板內容 | [data-artboard="<artboardId>"] >>> <css>;代理的 data-name 屬性會保留在 DOM 中 |
優先使用這些代稱而非座標。座標僅用於沙盒化(腳本)圖板,選擇器無法觸及這些圖板。click 接受 CSS 而非文字,因此沒有標題的按鈕透過位置選取:[role=dialog] button --nth 1,可使用 query 列出順序。
文字輸入有兩個陷阱。type 會附加到欄位現有內容之後,而 key Meta+a 無法透過 CDP 選取文字。要取代欄位中的文字,請在欄位聚焦時執行 $V h $RUN eval "document.execCommand('selectAll')",然後使用 type。
背景呼叫
wait_for_comments 會阻塞直到有人發表評論,因此它必須在你驅動頁面時同時執行:
$V bg $RUN 02-wait call wait_for_comments '{"designId":"<D>","timeoutSeconds":30}'
# ...驅動頁面...
$V await $RUN 02-wait 30 # 阻塞直到呼叫返回,然後輸出至 .verify/<run>/02-wait.txt
使用 await 讀取結果,切勿直接讀取檔案:該檔案僅在呼叫返回後才會出現。背景呼叫在不同工具呼叫之間持續存在。
證據
所有內容都會儲存到 checkout 目錄下的 .verify/<run>/。.gitignore 已涵蓋它,清理時會保留它。
$V save $RUN 01-comment-posted snapshot --selector main # 命令、其輸出和結束代碼
$V shot $RUN 02-canvas # .verify/<run>/02-canvas.png
$V shot $RUN 03-panel --selector 'aside[aria-label=Comments]'
save 接受任何無頭命令,包括 call,並寫入 <name>.txt,其中包含命令、輸出和 [exit N]。為名稱編號,以便執行實例的順序易於閱讀。在使用每個 PNG 之前,請使用「讀取」工具檢查它。get_screenshot 圖像和任何未指定路徑的頁面截圖都會存放在工作階段的臨時資料夾中。清理時會將它們複製到 .verify/<run>/tool-images/,伺服器日誌則複製到 .verify/<run>/server.log。你製作作為測試輸入的檔案,例如要匯入的檔案,也應放置在 .verify/<run>/ 中。
一個有效的證明滿足以下標準:
- 真實路徑。 使用者的操作需透過頁面(點擊、按鍵、輸入、上傳),而代理的操作需透過 MCP
call。僅使用 REST API 來讀取儲存的狀態。透過它寫入會繞過你要證明的 UI。 - 操作與結果。 儲存執行操作的步驟以及操作後的狀態,而不僅僅是最後的螢幕截圖。
- 副作用。 顯示變更已被儲存以及顯示。
api GET /api/designs/<id>會傳回伺服器儲存的meta、nodes和comments,而代理的讀取工具(get_comments、get_tokens、get_tree_summary)則顯示下一個代理將看到的內容。 - 即時功能的雙向性。 代理的編輯必須無需重新載入即可顯示在頁面中。使用者的評論必須送達等待中的代理。
- 無模擬。 執行實例使用真實的伺服器、真實的瀏覽器和真實的 MCP 會話。
- 乾淨的主控台。 以
$V save $RUN <n>-console console結束。其中的異常或失敗請求會破壞證明。
前後對比
當同一個操作過程在舊程式碼上失敗而在新程式碼上成功時,即證明錯誤已修復。同時執行兩者:
$V launch --at main # RUN=<before>:main 分支的臨時 checkout,已安裝和建構(約一分鐘)
$V launch # RUN=<after>:此 checkout
在兩個執行實例中驅動相同的步驟,使用相同的證據名稱。「修復前」的執行必須顯示錯誤。如果沒有,則你的操作過程未觸及錯誤,而「修復後」的執行無法證明修復了任何東西。--at 接受任何 git 引用:一個分支、標籤、提交,或當你的修復尚未提交時的 HEAD。健檢工具會命名執行實例建構時所使用的引用和提交。清理時會移除臨時 checkout 並保留其證據。
清理
$V cleanup $RUN
它會停止此執行實例的守護行程。守護行程會停止它產生的伺服器(發送 SIGTERM,因此設計會先儲存)和它啟動的 Chrome,然後刪除它們的臨時資料和設定檔。接著,清理會將伺服器日誌和工具圖像複製到證據資料夾,移除 --at 執行實例的臨時 checkout,並列出它保留的內容。它不會觸及其他任何東西,因此其他會話、vp run dev 和桌面應用程式可以繼續運行。
$V list 會列印此 checkout 中仍在運行或遺留了 checkout 的執行實例。對每個執行實例使用 $V cleanup <run> 進行清理,切勿使用 pkill。$V prune [days] 會刪除早於指定天數(預設為 7 天)的已清理執行實例的證據。切勿清理你即將報告其證據的執行實例。
報告
最後,為你驅動的每個功能提供一個區塊:
<功能 ID> 透過 <進入點>: proven | skipped | failed
證據: .verify/<run>/<檔案>
原因(跳過或失敗): <你運行的命令以及缺少或錯誤的內容>
然後提供執行 ID、每個執行實例的健檢輸出,以及對於修復,提供顯示錯誤的「修復前」執行證據。即使另一個進入點證明了相同的行為,也應將跳過的進入點報告為 skipped。
輔助程式
scripts/verify.sh 接受以下命令:launch、doctor、h、save、shot、bg、await、cleanup、list 和 prune。不帶參數執行它以查看其參數。它是 e2e/headless.mjs 的一個薄封裝層:會話是 verify-<run>,證據則存放在 .verify/<run>/。