驗證個人作品集網站 是做什麼的?
狀態:啟動、檢查、清理和第一批流程已通過實際環境驗證 (2026-10-02,測試於 82ec737,使用 chrome-devtools-axi,在 Apple M3 Pro 上使用真實 WebGL2)。覆蓋範圍是部分的:每個 features/*.md 檔案中的 Last live proof: 行會確切說明已運行和未運行的內容;若該行顯示 none,表示該功能從未被驅動過。此處沒有任何東西是被「驗證」過的,因為這個檔案僅是對此的描述。
僅手動觸發 (使用者規則,2026-10-02):此技能和 PStack 僅在使用者明確要求驗證時運行。提交、推送、建立 PR 或啟用某種模式不會啟動它,且此處的任何內容都不得連接到 git hook、package 生命週期腳本、CI 任務或排程。frontmatter 設定了 disable-model-invocation: true,因此代理程式永遠不會自行啟動它。請手動啟動:Claude Code /verify;當 Codex 發現 .agents/skills/verify 時,Codex $verify;任何其他代理程式:請閱讀此檔案並明確執行以下步驟。自動觸發器審計於 2026-10-02,沒有任何觸發器會執行瀏覽器:.husky/pre-commit (lint-staged,配置 .lintstagedrc:eslint 和 prettier 檢查暫存檔案)、.husky/commit-msg (commitlint)、package.json prepare (husky)、.github/workflows/ci.yml (型別檢查、lint、fmt:check、vitest、建置、預算)。test:e2e 和 visual-baseline 僅為手動腳本。此簽出目錄的 core.hooksPath 指向儲存庫外部的 firstmate hooks 目錄,而非 .husky。
瀏覽器使用僅在明確的驗證運行時才被授權 (2026-10-02 的運行已由管理者授權,取代了先前的瀏覽器使用禁令)。Drive 配方被編寫為使用者操作和可觀察的結果,因此它們可以與 chrome-devtools-axi 或任何其他瀏覽器技能一起運行。
每一個未解決的問題、假設和實際驗證缺口都記錄在一個文件中:docs/verification/verification-issues.md。功能檔案會連結到它,並且不會重複其內容。在驅動任何內容之前,請先閱讀 features/README.md 以了解功能地圖。
網站概觀
一個 Next.js 16 應用程式 (App Router、proxy.ts、靜態預渲染、React Compiler)。11 個線上版本共享一個內容模型和一組路由;訪客的版本決定了外觀。代理程式將 /<path> 重寫為 /f/<edition>/<path>;在 hr_flavor cookie 包含一個有效的版本 ID 之前,/ 會顯示選擇器 (/flavors)。NEXT_PUBLIC_FLAVOR=<id> 會將建置結果固定到一個版本。版本列表位於 flavors/registry.ts。每個版本的路由列表為 content/site.ts 中的 pages,加上 /owner、/projects/<slug>、/lab/<slug>、/ask/<slug>、/ask/page/<n>、/md/* 鏡像路由、/llms.txt、/sitemap.xml、/robots.txt。
這是一個包含破壞性變更的 Next.js。在修改應用程式程式碼前,請先閱讀 node_modules/next/dist/docs/ 中相應的指南 (儲存庫的 AGENTS.md 有說明)。
現有測試框架 (請重用,勿重複)
playwright.config.ts 和 e2e/*.spec.ts 已經涵蓋了大多數基本的殼層行為。驗證運行會驅動實際線上網站來測試它們無法證明的部分,並引用它們來證明已證明的部分。切勿將 e2e 斷言複製到功能檔案中作為第二個測試套件。
| 現有測試覆蓋範圍 | 位置 | 證明內容 |
|---|---|---|
| 建置輸出已預先渲染 | e2e/static-routes.spec.ts (專案 build) | 沒有公開路由是按需渲染的,沒有基於時間的重新驗證 |
| 最小的特定套件 | home、navigation、ask、lab、motion、preferences、print、disclosure、command-menu、link-preview、markdown 規格,專案 desktop 和 mobile | 僅限最小版本 (cookie hr_flavor=minimal) |
| Drawing Set 特定套件 | e2e/drawing-set.spec.ts,專案 desktop-drawing-set、mobile-drawing-set | 工作區索引、指令選單、Customize 持久化、/now 記錄、在 768 寬度下無溢出 |
| 另外九個版本的共享殼層 | editions.spec.ts、a11y.spec.ts、command-dialog.spec.ts,專案 desktop-<id> 和 mobile-<id> | 每個路由都返回 200 且包含一個 h1、版本 404、指令選單、Customize 主題、頁首主題切換、在 light/dark 模式和減弱動畫效果下的 axe 無障礙檢查、指令轉盤和鍵盤合約 |
這些套件使用 bun run test:e2e 運行,並會自行啟動 bun run build && bun run start -p 3020 (在 CI 環境之外使用 reuseExistingServer: true,因此會靜默重用任何已在 3020 埠號上執行的程序)。它們是瀏覽器自動化測試:僅在要求時運行,使用一個 worker,並確認 3020 埠號是空閒的。它們並未在 2026-10-02 運行。它們的通過或失敗並不能證明此功能地圖標記為缺口的任何內容 (請參閱問題記錄)。
現有的非瀏覽器門禁:bun run lint、bun run type-check、bun run test (vitest)、bun run fmt:check、bun run check:identity、bun run budget (在建置之後)。允許運行它們,但它們無法說明 UI 行為。
啟動
每個任務運行一個伺服器,使用任務特定的埠號,從正式建置結果啟動,因為這是 e2e 配置和 static-routes.spec.ts 所假設的 (next dev 不屬於此計畫的一部分)。
.agents/skills/verify/scripts/doctor.sh # 環境和樹狀結構健康檢查,無需伺服器
.agents/skills/verify/scripts/serve.sh start 3071 # 建置、啟動、等待就緒、寫入 PID 檔案
.agents/skills/verify/scripts/doctor.sh 3071 # 現在也檢查正在運行的實例
serve.sh start 會執行以下操作:如果埠號已被佔用則拒絕運行,執行 bun run build 和 bun run start -p <port>,同時不設定任何 Sanity、ask 和 flavor 變數 (讓網站渲染捆綁的備用內容,不進行 CMS 呼叫),將伺服器 PID 記錄在 $RUN_DIR/server.pid 中,並等待直至 GET /flavors 返回 200。埠號 3000 和 3020 被拒絕 (3020 屬於 Playwright 配置)。建置會覆寫 .next;請勿在一個簽出目錄中啟動兩個運行。
就緒表示滿足以下所有條件:serve.sh 印出了 ready、GET /flavors 返回 200,且帶有 cookie hr_flavor=minimal 的 GET / 返回 200 並包含一個 <h1>。請勿驅動任何未滿足這三個條件的實例。
卸載:.agents/skills/verify/scripts/serve.sh stop 3071 (請參閱清理)。
檢查
.agents/skills/verify/scripts/doctor.sh [port] 是唯讀的。它回答「此實例是否值得驅動,以及是否安全」。當以下任何條件成立時,它會失敗 (exit 1):
- 在儲存庫根目錄中存在
.env、.env.local、.env.development*、.env.production*檔案 (Next 會載入 Sanity 或 ask 金鑰,驅動程式可能會寫入真實資料集)。 - 在呼叫 shell 中設置了任何
NEXT_PUBLIC_SANITY_PROJECT_ID、SANITY_API_READ_TOKEN、SANITY_API_WRITE_TOKEN、SANITY_REVALIDATE_SECRET、ASK_COOKIE_SECRET、ASK_OWNER_PASSPHRASE、NEXT_PUBLIC_FLAVOR。 HEAD不是運行所聲明的提交,或者樹狀結構是骯髒的 (它會印出兩者)。- 如果指定了埠號:該埠號不被
$RUN_DIR/server.pid中的 PID 所擁有,/flavors不是 200,/api/visits不是 503 (必須是 503:這正是備用建置用來證明訪客計數器無法寫入 CMS 的方式),或者帶有hr_flavor=minimal的/沒有<h1>。
當任何情況看起來不對勁時,請先運行它。
運行必須聲明的前提條件
- 內容:使用捆綁的備用內容 (
content/fallback/),這是預設值。針對已配置的 Sanity 專案運行是一個不同的運行,需要擁有者批准、一個可丟棄的資料集和一個唯讀 token;請參閱 features/cms-prerequisites.md。任何運行不得寫入 CMS、變更擁有者內容、部署或接觸憑證。 - 視口:桌面 1440x900 和手機 (Playwright 的「Pixel 7」設定檔:觸控、粗略指針、無懸停),這是 e2e 配置使用的兩個視口。淺色和深色模式。動畫開啟和
prefers-reduced-motion: reduce。 - 儲存:從空的設定檔開始每次的版本驅動,僅帶有 cookie
hr_flavor=<id>(e2estorageState的做法)。切勿使用共享的已認證瀏覽器工作階段。 - 場景層級:場景層級遵循 WebGL2、
Save-Data、prefers-reduced-data、deviceMemory和指針 (lib/scene/tier.ts)。記錄每個場景證明時的瀏覽器層級訊號。沒有 GPU 的無頭瀏覽器只能證明 T0 和靜態圖,而不是 3D 內容。
驅動
瀏覽器:使用 chrome-devtools-axi,並帶有其自己的工作階段、埠號和隔離的設定檔;絕不自動連接到使用者的 Chrome。已驗證的設定 (2026-10-02):
export CHROME_DEVTOOLS_AXI_SESSION=<unique-task-name> CHROME_DEVTOOLS_AXI_PORT=<free port> CHROME_DEVTOOLS_AXI_AUTO_CONNECT=0
export CHROME_DEVTOOLS_AXI_MCP_PATH="$(npm prefix -g)/lib/node_modules/chrome-devtools-mcp/build/src/bin/chrome-devtools-mcp.js" # 避免 npx 啟動
# 真實 WebGL2;無頭模式只能證明靜態圖
export CHROME_DEVTOOLS_AXI_HEADED=1 CHROME_DEVTOOLS_AXI_CHROME_ARGS="--enable-gpu --ignore-gpu-blocklist"
chrome-devtools-axi emulate --viewport "1440x900x2" # 桌面;對於 Pixel 7 設定檔使用 "412x915x2.625,mobile,touch"
chrome-devtools-axi emulate --color-scheme dark|light
# 減弱動畫效果:重新啟動 bridge (`chrome-devtools-axi stop`),並在 CHROME_ARGS 後加上 --force-prefers-reduced-motion
chrome-devtools-axi run <<'EOF' ... EOF # 腳本化的全面掃描;最後停止 bridge
來自運行的工具注意事項:在 run 腳本中,page.wait(ms) 會拋出「fn is not a function」錯誤,因此請使用 await new Promise(r=>setTimeout(r,ms)) 來暫停;process.env 在那裡不可用,請將值替換到腳本文字中;無法從 document.cookie 清除 hr_flavor,因此請透過開啟 /?flavor=<id> 來選擇版本;emulate 沒有減弱動畫效果的標誌;在 Press 的 visually hidden Paper 單選按鈕上的指針點擊會超時 (鍵盤的 ArrowRight 可以運作,WEB-H8);此 shell 的 zshz hook 在 cd 時會返回非零值,因此請使用絕對路徑。features/ 下的每個功能檔案都列出了操作、穩定的 handle(s) 和可觀察的最終狀態。handle 的偏好順序為:ARIA role 和 accessible name、路由路徑、html[data-theme]、html[data-motion]、html[data-scene]、[data-scene-poster]、每個版本的 hr.*.prefs localStorage key (e2e/support/site.ts 中的 prefsKeyFor)。不使用座標。
證明標準:
- 操作真實的路由和控件,而不是 store setter。優先使用可見控件而非
localStorage編輯;僅使用儲存來設定功能檔案指定的起始狀態。 - 擷取操作及其後的狀態:UI 的 ARIA 快照和螢幕截圖、HTTP 檢查的回應狀態和標頭、每次驅動的控制台和失敗請求記錄。
- 通過的單元測試或 e2e 規格並不能證明未執行過的流程。僅在真實流程運行之後,才將功能標記為
Last live proof:,並包含日期、提交和證據路徑。 - 將無法達到的路徑回報為嘗試的步驟和未滿足的前提條件。不要將跳過的入口點透過另一個入口點報告為「已驗證」。
證據
目錄:$EVIDENCE_DIR,預設為 $HOME/.verify-evidence/website/<run-id>/,其中 <run-id> 是 <UTC 時間戳>-<short HEAD>。佈局:doctor.txt、server.log (從 $RUN_DIR 複製)、然後是 <feature-id>/<step>.aria.txt、<step>.png、console.txt、network.txt。驅動完成後,在功能檔案的 Last live proof: 行中新增一行 (日期、提交、證據路徑),並在問題記錄中新增一行。在執行 stop 之前,將 $RUN_DIR/server.log 複製到證據目錄中,因為 stop 會刪除 $RUN_DIR。證據的生命週期比清理長,永遠不會被此技能刪除。
清理
.agents/skills/verify/scripts/serve.sh stop <port> 只殺死 $RUN_DIR/server.pid 中的 PID (以及它啟動的子程序),然後刪除 $RUN_DIR。它永遠不會按程序名稱殺死程序,也永遠不會觸及不是它啟動的程序。它不會動 .next (一個簽出目錄已經忽略的建置成品) 和 $EVIDENCE_DIR。如果 PID 檔案遺失,它會報告此情況並且不會殺死任何程序。在失敗的運行之後,請在重試前運行此步驟,以確保埠號不會被佔用。
$RUN_DIR 是 ${TMPDIR:-/tmp}/website-verify-<port>;它僅包含 PID 檔案和伺服器日誌。
輔助腳本
scripts/doctor.sh [port]:上述的唯讀檢查。exit 0 表示健康,exit 1 表示不值得驅動。scripts/serve.sh start <port>和scripts/serve.sh stop <port>:上述的啟動和卸載腳本。
這兩個腳本都是可執行的,並且僅使用 bash、curl、lsof、git 和 bun。