con un clic
spectra-apply
Implement or resume tasks from a Spectra change
Instalar con Codex o Claude Copia este prompt, pégalo en Codex, Claude u otro asistente, y deja que revise la página de la skill y la instale por ti.
Menú
Implement or resume tasks from a Spectra change
Instalar con Codex o Claude Copia este prompt, pégalo en Codex, Claude u otro asistente, y deja que revise la página de la skill y la instale por ti.
Basado en la clasificación ocupacional SOC
Implement or resume tasks from a Spectra change
Archive a completed change
Have a focused discussion about a topic and reach a conclusion
Archive a completed change
Have a focused discussion about a topic and reach a conclusion
Orchestrate a task inside a fresh git worktree end-to-end. /wt TASK builds a worktree, auto-routes the executor (Claude subagent for UI work, Codex for non-UI coding/analysis/debug), and reports back. Override with --claude or --codex. Supports parallel multi-task via /wt A: ... B: .... Use when 使用者說「worktree」「開 wt」「並行做」,或需要在隔離環境跑 task 不影響主 working tree。
| name | spectra-apply |
| description | Implement or resume tasks from a Spectra change |
| effort | high |
| license | MIT |
| compatibility | Requires spectra CLI. |
| metadata | {"author":"spectra","version":"1.0","generatedBy":"Spectra"} |
Implement tasks from a Spectra change.
Ownership(clade fork;cross-phase matrix in
rules/core/spectra-workflow.md):apply 負責 code 正確性 + Class B UI view phase refactor invariant(Step 6c / Layer B:無 column 整欄 fallback + 0 個 4xx/5xx)+ review-rules 機械規則掃描(Step 6d:patterns.jsonmulti-line match,補 pre-commit hook 逐行 grep 漏抓的跨行 Vue props)+ Design Review data-sanity(Layer C:client param vs server schema bound)+ pre-handoff 5-維度 cross-check(Step 8a.6 / Layer E.1 主線 + E.2 codex)。不負責 user 主觀視覺 / UX 真人驗收(manual review / review-gui 管)。
Input: Optionally specify a change name (e.g., /spectra-apply add-auth). If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes.
Task tracking is file-based only. The tasks file's markdown checkboxes (- [ ] / - [x]) are the single source of truth for progress. Do NOT use any external task management system, built-in task tracker, or todo tool. When a task is done, edit the checkbox in the tasks file — that is the only way to record progress.
Prerequisites: This skill requires the spectra CLI. If any spectra command fails with "command not found" or similar, report the error and STOP.
Steps
Worktree gate(clade fork addition;not in upstream spectra)
Spectra-apply writes tracked product code, so per [[worktree-default]] §1 it MUST run in an isolated session worktree — multi-session 並行共用單一 working tree 會撞 staging / branch / WIP(見 worktree-default.md 開頭兩次真實事故)。Step 1 之前先 gate:
a. Resolve change name early(用 Step 1 同套規則 — argument > conversation context > spectra list)。Step 0 完成後 Step 1 可重用已解析的 name,不必再問。
b. 偵測 cwd:
git rev-parse --git-dir
/worktrees/(或 git rev-parse --git-common-dir ≠ git rev-parse --git-dir)→ cwd 已在某個 session worktree,通過,繼續 Step 1c. Pre-fork baseline guard + 自動建 worktree(idempotent):
Spectra-apply 走 commit-then-fork — 有 change context,把屬於這條 change 的 baseline 自動 commit 上 main 再 fork,避免 worktree 看不到 main 的 untracked / modified baseline([[worktree-default]] §1 Pre-fork baseline guard)。
c.1 — 偵測 main dirty:
node scripts/wt-helper.mjs detect-main-dirty --json
解析回傳 { modified, untracked, conflicted }:
c.2 — Scope filter(主線自己做,不靠 wt-helper):
把 dirty paths 分成 scope-in(屬於這條 change 的 baseline)vs scope-out(其他)。三來源 union:
.spectra/touched/<change-name>.json(若存在;spectra-commit 上次 sync 寫入)— 列出的 path 為 scope-inopenspec/changes/<change-name>/proposal.md + openspec/changes/<change-name>/specs/**/*.md,找 packages/ / server/ / app/ / supabase/ / scripts/ 等 module path 提及;任一 dirty path 是它們的子路徑或開頭命中 → scope-in其餘 dirty → scope-out。
c.3 — 三情境決策:
| 情境 | 行為 |
|---|---|
| scope-in 非空 + scope-out 為空 | 直接走 c.4,commit-then-fork |
| scope-in 非空 + scope-out 非空 | 印分類報告給 user(scope-in N 條 / scope-out N 條)後走 c.4,commit 只包 scope-in;scope-out 留在 main 不動 |
| scope-in 為空(無論 scope-out 為空或非空、無論三來源是否對得上) | 直接走 c.4 clean fork;若 scope-out 非空,印一行通知:main 有 <N> 條 dirty 不屬於本 change,已留在 main 不動,worktree 從 HEAD fork。NEVER STOP / AskUserQuestion / 要求 user 先 commit/stash —— worktree 隔離已處理 main WIP 對 apply 的影響;同檔衝突是 merge-back 時的事,不在 apply 範圍 |
c.4 — Fork(commit-then-fork 或 clean fork):
# 有 scope-in baseline 要 commit
node scripts/wt-helper.mjs add <change-name> \
--precheck-baseline <change-name> \
--baseline-strategy commit \
--baseline-scope-paths <comma-separated-scope-in-paths>
# 或:main clean / user 選 (b) cross-session 不動 dirty
node scripts/wt-helper.mjs add <change-name>
Helper 用 change name 當 slug,內部 normalize(lowercase / 空白轉 - / collapse 重複 -)。commit 策略時 helper 跑 selective stage(git add -- <scope-paths>,禁 git add -A)+ commit baseline: <change-name> pre-fork sync + fork。Helper 行為與失敗處理見 plugins/hub-core/skills/wt/SKILL.md。
若 helper fail with Worktree path already exists → slug 對應 worktree 已存在(前次 session 建過、未清掉),沿用即可,視為成功;用 node scripts/wt-helper.mjs list --json 抓既有 path。注意:既有 worktree 不會再跑 baseline guard,若 main 仍有屬於本 change 的 dirty baseline,必須 user 自己 commit 後 worktree 內 git pull 或 cherry-pick。
其他 helper 錯誤 → 報錯並 STOP,不要降級回「在 main 跑」。
c.5. Main-side unpark + commit-to-git(clade fork addition;critical data-safety guardrail,per docs/pitfalls/2026-05-22-agent-tool-subagent-worktree-bypass.md):
理由:spectra v3 spectra park 把 artifacts 從 disk 搬進 .git/spectra-app/spectra.db SQLite blob(不在 git tracked file);後續 spectra unpark 會 restore artifacts 到 cwd 的 worktree disk 並把 SQLite parked 條目刪除。若 unpark 在 Claude Code Agent tool dispatched subagent 的 ephemeral cwd(.claude/worktrees/agent-*/,session 結束 GC)跑 → artifacts 寫進去就被 GC 清掉、SQLite 也沒了 → 永久遺失(co-purchase 已撞,99 tasks + 5 specs + proposal 蒸發)。
因此 MUST 在 dispatch subagent 之前,由主線在 main worktree(或 Step 0c 剛 fork 出的 session worktree — 兩者都是 persistent disk,非 ephemeral)跑 unpark + commit-to-git,artifacts 落 git tracked file,subagent fork 出去後天然帶過、不再依賴 SQLite blob。
執行流程:
偵測是否 parked:
spectra list --parked --json | jq -r '.parked[]?' | grep -Fx "<change-name>"
主線在 main 跑 unpark(禁止在 subagent / ephemeral worktree 跑;本步驟發生在 dispatch 之前,主線 cwd 仍是 main):
spectra unpark "<change-name>"
Unpark 把 artifacts blob restore 到 main worktree disk 的 openspec/changes/<change-name>/。SQLite parked 條目被刪除(這是 unpark 的正常行為)。
selective stage + commit to git(讓 artifacts 進入 git tracked,不再依賴 SQLite):
git add openspec/changes/<change-name>/
git commit -m "📝 docs(spectra): unpark artifacts for <change-name> before apply"
禁止 git add -A / git add .(會撈到 main 上其他 user WIP);禁止 --no-verify(per rules/core/commit.md hard rule)。
若 Step 0c 已 fork session worktree:主線在 main 跑完 unpark + commit 後,worktree 是基於 main HEAD fork 的(在 Step 0c.4 建好),尚未看到剛剛 commit 的 artifacts。MUST 在 worktree 內同步:
git -C <worktree-absolute-path> pull --ff-only
或等價的 git -C <wt> fetch && git -C <wt> reset --hard origin/main(視 consumer workflow_model 而定)。Worktree 拿到 artifacts 之後 subagent dispatch 才看得到。
若 Step 0c 跑 commit-then-fork(c.4 已 commit baseline):unpark 的 commit 是 main 上繼 baseline 之後的新 commit;worktree 需要 sync 到 main 最新 HEAD 才看得到 artifacts,方法同 step 4。
Failure handling:
spectra unpark 失敗(SQLite blob corrupt / change name typo)→ STOP,回報 error,不要 dispatch subagent;user 解掉 unpark issue 再重試 /spectra-applygit commit 失敗(pre-commit hook fail / no changes to commit)→
no changes to commit:artifacts 已在 git,視為成功,繼續git pull --ff-only 失敗(worktree 有 commit 跟 main 衝突)→ 罕見情境(worktree 是 fresh fork from main,理論上 ff 安全);STOP,回報並讓 user 手動 syncNEVER:
spectra unpark(Agent tool 的 cwd 是 ephemeral .claude/worktrees/agent-*/,unpark 寫的 artifacts 會被 session GC 清掉 → permanent data loss)git add -A / git add . stage artifacts — 會把 main 上其他 user WIP 一起 commitSkill tool 或 Agent tool 委派此步給 subagent — 必須主線自己跑(subagent 的 cwd 不可信)c.6. Environment Readiness Check(clade fork addition;per docs/pitfalls/2026-06-28-spectra-apply-dispatches-unready-change.md):
理由:dispatch subagent 後到 e2e / verify 階段才發現 DB 未 sync / dev server 指向 main / auth route 壞掉,每道牆浪費 5-15 分鐘。三項全在 dispatch 前 30 秒可驗出。
MUST 在 dispatch 前依序跑以下三項。任一紅燈 → 自動修(不問 user)或 STOP 回報:
DB migration sync(self-hosted Supabase consumer only — 讀 consumer-meta db-runtime):
# 比較 worktree migration 數量 vs dev LXC
LOCAL_COUNT=$(ls <worktree>/supabase/migrations/*.sql 2>/dev/null | wc -l)
REMOTE_COUNT=$(cd <worktree> && pnpm supabase:sync --dry-run 2>&1 | grep -oP '\d+ local' | grep -oP '\d+')
cd <worktree> && pnpm supabase:sync && pnpm db:resetsupabase:sync --dry-run 不支援 → fallback 直接跑 pnpm supabase:sync(idempotent)db-topology-invariant 規則:dev DB 是共享實例,reset 前 SHOULD 檢查沒有其他 active session 在用(node scripts/claim-helper.mjs list);若有其他 claim → warn user 但仍 proceed(apply 的 DB sync 優先於 claim collision 的低機率風險)Dev server cwd alignment(有 singleton dev server 的 consumer — 讀 scripts/singleton.mjs 存在性):
# 檢查 port 3000 上的 process cwd 是否對齊 worktree
DEV_PID=$(lsof -i :3000 -t 2>/dev/null | head -1)
if [ -n "$DEV_PID" ]; then
DEV_CWD=$(lsof -p "$DEV_PID" -a -d cwd -F n 2>/dev/null | grep ^n | cut -c2-)
if [ "$DEV_CWD" != "<worktree-absolute-path>" ]; then
echo "dev server cwd mismatch: $DEV_CWD != <worktree>"
# 自動修:kill + 從 worktree 重啟
cd <worktree> && pnpm dev:kill && pnpm dev:agent
fi
fi
pnpm dev:kill && cd <wt> && pnpm dev:agentAuth route smoke test(有 __test-login / _dev-login route 的 consumer):
HTTP_CODE=$(curl -sS -o /dev/null -w '%{http_code}' \
"http://127.0.0.1:3000/auth/__test-login?role=admin&email=admin@example.com" 2>/dev/null)
302 → pass(auth works)404 → dev server 可能從 main 跑(code 沒 fix)或 route 不存在 → 已在 Step c.6.2 修正 dev server cwd;若仍 404 → STOP 回報「auth route broken, check isLoopbackRequest」x-dev-login-token header 跑 smoke test — 這樣會繞過 loopback detection,隱藏底層問題全綠:印一行 ✅ Environment readiness: DB synced / dev server aligned / auth OK 繼續 Step 0d。
NEVER:
d. Internally dispatch via /wt Form 3:
Invoke the Skill tool with /wt <change-name>: /spectra-apply <change-name> (Form 3 per plugins/hub-core/skills/wt/SKILL.md). /wt orchestrates the worktree lifecycle (reuses the one prepared in Step 0c) and spawns a subagent that runs Step 1+ inside it. Subagent reports completion or structured failure back through /wt's normal channel; parent cwd stays on main throughout.
Wait for the dispatched skill to return, surface its report to the user, and STOP — do not re-enter Step 1 in the parent session.
Fallback — if the Skill tool / /wt dispatch is unavailable in this environment (rare degradation; e.g., minimal runtime without skill support), emit a status-only message:
Worktree at <worktree-absolute-path> ready; please run `/spectra-apply <change-name>` from inside it manually.
No cd … && claude oneliner under any branch. <worktree-absolute-path> 從 wt-helper 輸出抓;<change-name> 是 Step 0a 已解析的 name.
e. Bypass 條件:使用者明確訊息含「不要 worktree」「在 main 跑」「我知道風險」等字眼時,跳過 Step 0 直接 Step 1。禁止 agent 自行判斷略過(包括 user 跑 /spectra-apply 本身不算明確 bypass — 那只是 invocation,不是 worktree 偏好)。
Select the change
If a name is provided, use it. Otherwise:
spectra list --json AND spectra list --parked --json to get all available changes (including parked ones). Parked changes should be annotated with "(parked)" in the selection list. Use the AskUserQuestion tool to let the user selectAlways announce: "Using change: " and how to override (e.g., /spectra-apply <other>).
Then invoke /rename <name> (Claude Code built-in slash command) to rename this session after the change — makes concurrent change sessions easy to identify in the session list. If the SlashCommand tool is unavailable in this environment, skip silently.
Check status to understand the schema
spectra status --change "<name>" --json
If the command fails: show the error and STOP.
If the command succeeds, check whether the change is parked (status can succeed even for parked changes):
spectra list --parked --json
Look for the change name in the parked array of the JSON output.
If the change IS in the parked list (it's parked):
clade fork data-safety guard(per docs/pitfalls/2026-05-22-agent-tool-subagent-worktree-bypass.md):在 Step 0c.5 規約之下,主線理應已在 dispatch 之前跑過 unpark + commit-to-git。本路徑能命中表示 Step 0c.5 被跳過(罕見:cwd 已在 worktree、Bypass 條件、或主線 skill 邏輯被覆寫)。
Detect cwd:
git rev-parse --show-toplevel
git rev-parse --git-dir
若 cwd 看起來像 ephemeral agent worktree(git-dir 路徑含 .claude/worktrees/agent- 片段)→ STOP,回報:
⚠ spectra unpark must run on main worktree or persistent session worktree, NOT inside Agent tool dispatched subagent.
This subagent's cwd is `.claude/worktrees/agent-*/`, which Claude Code will GC at session end.
Running unpark here would write artifacts to a path that disappears → permanent data loss
(see docs/pitfalls/2026-05-22-agent-tool-subagent-worktree-bypass.md).
Action: cancel this run; from main session run `/spectra-apply <change>` which will execute
Step 0c.5 main-side unpark + commit-to-git before dispatching the subagent.
NEVER 自行嘗試 unpark / 用 AskUserQuestion 給「強制 unpark」選項 — 沒有合法的「在 subagent 內 unpark」路徑。
若 cwd 在 main / <consumer>-wt/<slug>/ 等 persistent worktree → 繼續以下 fallback 流程:
Inform the user that this change is currently parked(暫存). Use the AskUserQuestion tool to ask whether to continue. Two options:
If the user chooses to continue:
spectra unpark "<name>"
Post-unpark commit(clade fork addition;防 SQLite-only state):unpark 把 artifacts restore 到 cwd worktree disk,SQLite parked 條目被刪。MUST 立刻 commit 到 git,避免下次 session 又需重做:
git add openspec/changes/<name>/
git commit -m "📝 docs(spectra): unpark artifacts for <name> before apply"
禁止 git add -A;commit 失敗(hook reject / no changes)視同 Step 0c.5 同名情境處理(no changes = 視為成功;hook fail = STOP)。
Then mark it as in-progress:
spectra in-progress add "<name>"
This is a silent operation — do not show the output to the user.
Then re-run spectra status --change "<name>" --json and continue normally.
If there is no AskUserQuestion tool available (non-Claude-Code environment):
Inform the user that this change is currently parked(暫存)and ask via plain text whether to unpark and continue, or cancel.
Wait for the user's response. If the user confirms, run spectra unpark "<name>" + post-unpark commit + spectra in-progress add "<name>", and continue normally.
If the change is NOT in the parked list: mark it as in-progress and proceed normally.
spectra in-progress add "<name>"
This is a silent operation — do not show the output to the user.
Parse the JSON to understand:
schemaName: The workflow being used (e.g., "spec-driven")2.5. Stash Reconcile (clade fork; not in upstream spectra)
Scan namespaced stashes related to this change before starting work. Catches resume scenarios where the previous session's WIP got auto-stashed by wt-helper / propagate / clade-publish and never reapplied — without this, apply will run on a clean baseline while real WIP rots in stash.
node scripts/stash-reconcile.mjs --slug "<change-name>" --jsonentries.length === 0, continue silently to Step 3.⚠ Stash Reconcile: N entries match slug '<change>', then use AskUserQuestion:
ref, namespace.kind, createdAt, file list, and recommendation.action/recommendation.reason; then re-ask the same questionrecommendation.action === "apply", run git stash apply <ref> (safety contract: NEVER pop / drop here; the stash entries stay intact). Then continue to Step 3.--no-reconcile (or said "不要掃 stash" / "skip reconcile" when invoking the skill), skip this step and print Stash reconcile: skipped (user --no-reconcile).stash-reconcile.mjs exits non-zero or JSON parse fails, print the error and continue to Step 3 (reconcile is advisory — do NOT block apply).Get apply instructions
spectra instructions apply --change "<name>" --json
This returns:
Handle states:
state: "blocked" (missing artifacts): show message, suggest using /spectra-propose to create the change artifacts firststate: "all_done": congratulate, suggest archive3b. Preflight check
If the apply instructions JSON includes a preflight field, act on its status:
"clean": silently continue — no output needed.
"warnings": display a brief summary, then continue automatically:
⚠ Preflight warnings:
- Drifted files (modified after change was created): <list paths>
- Change is <N> days old
Continuing...
Only show the lines that are relevant (skip drifted if none, skip staleness if not stale).
"critical": display missing files with their source artifact, then use the AskUserQuestion tool to ask the user:
⚠ Preflight: missing files detected
- <path> (referenced in <source artifact>)
- ...
These files are referenced in the change artifacts but no longer exist on disk.
Options: "Continue anyway" / "Stop" If the user chooses "Stop", end the workflow.
If there is no AskUserQuestion tool available: Display the same information as plain text and ask whether to continue or stop. Wait for the user's response.
If the preflight field is absent (blocked or all_done states), skip this step.
3c. Artifact quality check
Run spectra analyze <change-name> --json to check cross-artifact consistency (Coverage, Consistency, Ambiguity, Gaps).
Zero findings: silently continue.
Warning/Suggestion only: display a one-line summary (e.g., "⚠ Artifact analysis: 2 warnings found") and continue automatically.
Critical findings: display each Critical finding (summary + location + recommendation), then use the AskUserQuestion tool:
If there is no AskUserQuestion tool available, present options as plain text and wait for the user's response.
3d. Drift dormancy check (passive trigger for stale changes)
When the change has been dormant for more than 5 days AND the change directory has had zero commits in the past 3 days, surface a drift report before tasks begin — the change is likely out-of-sync with the current codebase.
Detect dormancy from .openspec.yaml created and git log -1 --format=%at -- docs/specs/changes/<name>/:
spectra drift <change-name>, display the report, then use the AskUserQuestion tool:
/spectra-ingest <change-name> to update artifacts, then resumeThe trigger is guidance only — it MUST NOT block apply from proceeding when the user chooses to continue. Hard-blocking on dormancy would punish legitimate "I came back after a long weekend" cases.
(Threshold reasoning: AI-assisted commits are daily-cadence. ≥5 days dormant + ≥3 days no commit ≈ genuine stagnation, not normal pacing.)
If there is no AskUserQuestion tool available, present options as plain text and wait for the user's response.
Read context files
Read the files listed in contextFiles from the apply instructions output.
The files depend on the schema being used:
Check project preferences
Read .spectra.yaml in the project root.
If tdd: true is set, apply TDD discipline throughout implementation:
spectra instructions --skill tdd, then follow the Red-Green-Refactor cycleIf audit: true is set, apply sharp-edges discipline throughout implementation:
spectra instructions --skill audit, follow the discipline checklist (not the standalone 3-agent workflow)If parallel_tasks: true is set, check whether consecutive pending tasks have [P] markers (format: - [ ] [P] Task description). You SHALL dispatch consecutive [P] tasks as parallel agents. Only fall back to sequential when tasks have a data dependency (one task's output is another's input) or when tasks modify overlapping regions of the same file. Targeting the same file alone is NOT a reason to skip parallel dispatch — if the modified regions are disjoint, dispatch in parallel. If the environment does not support parallel execution, ignore [P] markers and execute tasks sequentially.
Show current progress
Display:
6a. Residency Classify + Record(機械前置,MUST — 任何 phase dispatch 決策前)
Per agent-routing.md § Orchestration Residency。Residency 規則上線 6 天 audit 實證:eligible(純非-view + tasks.md 定稿)change 採用率 1/3 — 2 條被主線自做、0 dispatch。「主線自行判斷 residency」已證實不可靠,classify + record 改為機械步驟。每一條 change 開工都要 classify + record,不是只有看起來像純後端的那條。
MUST 跑 classifier 拿 verdict(開工後、動任何 phase 之前):
node ~/offline/clade/vendor/scripts/residency-classify.mjs classify --change openspec/changes/<change>
stdout JSON:{verdict: "codex-primary" | "claude-primary", phases: [...]}。
MUST 立刻 record decision(決定實際 executor 後、第一個 dispatch / 第一個 Edit 之前):
node ~/offline/clade/vendor/scripts/residency-classify.mjs record \
--consumer-path . --change <change> \
--verdict <classifier verdict> --executor <codex|claude> [--reason <text>]
codex-primary 而決定 executor=claude → --reason 必填(record 入口會擋)record --verdict codex-primary --executor codex<consumer>/.spectra/residency-ledger.jsonl依 verdict 走對應路徑:verdict=codex-primary 且 executor=codex → 走 agent-routing.md § Orchestration Residency 的 change 粒度單次 dispatch + notification-only,不要落到 Step 6b 逐 phase 派工;verdict=claude-primary(或帶正當 --reason 留主線)→ 續走 Step 6b Phase Dispatch。
後果(機械強制,同 Check 7 / E.1 先例):archive-gate.sh Check 8 會機械驗 residency record 存在 — 缺 record → archive 被擋 exit 2。正當例外(user 明確指示主線自做等)在 tasks.md 加 bypass marker <!-- residency-decision: intentional, reason: ... -->。
6b. Phase Dispatch Decision(per agent-routing.md)
Before implementing tasks, decide dispatch model per phase(## N. <phase> section in tasks.md):
## N. phase sections/design improve / /impeccable audit / /impeccable * / review-screenshot / /design *
→ 主線 Claude Opus 4.6 high 自己做,永不派 codex
→ Design skill is Claude Code first-class; codex tooling weak in this domain.vue / .tsx / .jsx / app/pages/ / app/components/ / pages/ / components/ / views/ / layouts/ / .css / .scss / Tailwind class 變動,且該 phase 沒有摻入非 view 的 frontend / backend 工作(store / hook / API client / type / util / migration / API server)
→ 主線 Claude Opus 4.6 high 自己做,永不派 codex
→ UI view 層的視覺 / 互動 / a11y 細節需要與 Design skill 緊耦合;frontend 但非 view 的工作(store / hook / API client / type / util)不在此範圍,走 C 類[x],或 git history 顯示 phase 內檔案已被改):
phase `<N>. <title>` 同時混雜 UI view 與非 UI 工作,違反新版 Phase Dispatch 規則。
請改跑 `/spectra-ingest <change-name>` 把 UI view tasks 與其他 tasks 切成獨立 phase 後再 `/spectra-apply`。
NEVER 主線自行修改 tasks.md phase 結構 — 該交給 /spectra-ingest,避免 propose / apply / ingest 邊界混淆medium effort — schema drift / cross-file refactor / enum exhaustiveness require high minimum。Step 8a 系列收集工作允許 mediumCodex phase dispatch template(C 類專用,per agent-routing.md 「Codex 派工的標準流程」+「Spectra Apply Phase Dispatch」):
Write prompt to /tmp/codex-spectra-apply-<change>-phase-<N>-prompt.md,內容固定包含:
[DELEGATED-BY-CLAUDE-CODE]
請執行本 repo 的 spectra-apply phase <N>(<phase-title>)的全部 tasks。
Change: <change-name>
Phase: <N>. <phase-title>
Tasks(請依序完成並用 `spectra task done <change> <task-id>` 標記):
<每個 task 的編號 + 描述,從 tasks.md 抓>
Worktree workaround(clade TD-015 / spectra ≤2.3.1):
你在 session worktree 內跑 `spectra task done` 時,`.spectra/touched/` 會正確寫到當前 worktree ✅,
但 tasks.md 的 `[ ] → [x]` 翻轉可能寫到 Claude Code system-managed agent worktree(`<consumer>/.claude/worktrees/agent-*/`),
導致**當前 worktree 的 tasks.md 沒翻**。每跑完一次 `spectra task done`:
1. `git -C $(pwd) diff -- openspec/changes/<change>/tasks.md` 確認當前 worktree 看得到 `[ ] → [x]`
2. 若 diff 空 → 手動 Edit tasks.md 把對應行 `- [ ] <task-id>` 改成 `- [x] <task-id>`
3. **NEVER** 動 `<consumer>/.claude/worktrees/agent-*/` 內任何檔(harness 自管,session 結束會 GC)
Plan-first(**MUST**,per `.claude/rules/agent-routing.md` Plan-first 條目):
在動任何 Edit / Write / Bash 寫入動作之前,先在 stdout 最開頭輸出一段 `## Plan` section,包含:
- **要動的具體檔案**(每條一行的相對路徑;對應到 phase <N> 內每個 task 的預期落點)
- **每個檔案打算做什麼變動**(一句話 — 例如 schema 加哪欄 / API 加哪 endpoint / store 加哪個 action / migration 寫什麼)
- **預期影響範圍**(typecheck / 哪些 unit test 會被觸發 / 是否需要 migration / runtime 行為改變)
- **task → 檔案對應表**(每個 task ID 對應到哪些檔案,若某 task 不需要改檔請標 `(no file change — verification only)`)
Plan 寫完後**立刻**繼續執行,**不要**停下來等確認。Plan 是事前公開思路給主線 cross-check,不是 review gate;主線會用 plan vs. `git diff` 對齊抓「漏做的 task」與「踩到 view 層」這類 drift。
讀取以下檔案了解上下文:
- openspec/changes/<change-name>/proposal.md
- openspec/changes/<change-name>/design.md
- openspec/changes/<change-name>/specs/*/spec.md
- openspec/changes/<change-name>/tasks.md
- .claude/rules/(相關 rule,例如 server-api / pinia-store / supabase-* / development)
View-layer guard(**MUST**):
禁止修改 view 層檔案:
- 副檔名:`.vue` / `.tsx` / `.jsx` / `.css` / `.scss`
- 目錄:`app/pages/` / `app/components/` / `pages/` / `components/` / `views/` / `layouts/`
若 task 需要 view 層改動,回報 "view layer change required, defer to main thread" 並跳過該 task(不要勾 checkbox),主線會自己處理。
Commit Authorization(**MUST**,per `.claude/rules/agent-routing.codex-watch-protocol.md` § Commit Authorization):
完成 phase <N> 全部 tasks 後,**MUST** 在 worktree 內 commit 一次(一 phase 一 commit):
1. **Commit 前 self-check(任一條命中即 abort、NEVER commit)**:
- View-layer drift:
git diff --staged --name-only | grep -E '\.vue$|\.tsx$|\.jsx$|\.css$|\.scss$|app/(pages|components|layouts)/|^(pages|components|layouts|views)/'
命中 → 回報 "view layer drift: <files>" 並中止
- Scope discipline:
git diff --staged --name-only
對比本 phase 預期落點 — 超出範圍 → 回報 "scope drift: <files>" 並中止
2. **Selective stage**:`git add -- <each scoped file path>` — **禁止** `git add -A` / `git add .`(會撈到 baseline)
3. **Commit**:
git commit -m "🧹 chore: wt <change-name>-phase-<N> — <一行說明>"
- **MUST** 用 `🧹 chore: wt <change-name>-phase-<N>` format(emoji-conventional commitlint 合規;主線用 `git log main..HEAD` 對齊 phase)
- **禁止** `--no-verify`(per `rules/core/commit.md` hard rule,hook 擋住代表 phase 內容有問題,必須修而非繞)
仍禁止:`git push` / `git stash`(中途)/ `git commit --amend` / `/commit` / `/spectra-commit` / 跨 phase 混 commit。
Acceptance:所有 phase <N> 的 tasks 完成、checkbox 已勾、相關 typecheck / unit test 通過、phase commit 已在 worktree 內成立、`git log main..HEAD` 顯示 `🧹 chore: wt <change>-phase-<N> — ...`。
不要動 phase <N> 以外的 tasks。不要碰 ## Design Review 區塊(主線會自己做)。
不要呼叫 /spectra-archive。
Background bash:
cd <consumer-repo-root> && codex exec \
--model gpt-5.6-sol \
--dangerously-bypass-approvals-and-sandbox \
--skip-git-repo-check \
-c model_reasoning_effort=high \
< /tmp/codex-spectra-apply-<change>-phase-<N>-prompt.md 2>&1
Inform user briefly + start Codex Watch Protocol(見 agent-routing.md)
After <task-notification status=completed> — codex 已在 worktree 自 commit per § Commit Authorization:
[x]git -C <wt> log main..HEAD --oneline — confirm exactly one new commit per dispatched phase, format 🧹 chore: wt <change>-phase-<N> — .... Multiple commits per phase / missing commit / format mismatch → AskUserQuestion: [1] 主線 squash codex 的 multiple commits / [2] git -C <wt> reset --soft main 退 staging 重派 / [3] 中止git -C <wt> diff main..HEAD --name-only -- '*.vue' '*.tsx' '*.jsx' '*.css' '*.scss' 'app/pages/**' 'app/components/**' 'app/layouts/**' 'pages/**' 'components/**' 'layouts/**' 'views/**'(codex 自驗應已 abort,此處再驗保險)。若有任何 view 層檔案被 codex 動過 → AskUserQuestion: [1] git -C <wt> reset --soft main 退 staging + 主線剔除 view 改動 + 重派 codex / [2] 接受並由主線自己重跑該 view phase / [3] 中止git -C <wt> diff main..HEAD --name-only vs prompt 內 phase scope 宣告。超出範圍 → AskUserQuestion 處理pnpm typecheck (or equivalent), relevant testsMove to next phase (re-classify and dispatch or self-execute)
After ALL C 類 phases complete → 主線自己執行所有 A、B 類 phases(Design Review / UI view),用 /design improve, /impeccable skills, /impeccable audit, review-screenshot 等 Claude Code first-class 工具
Design Review 期間 MUST 跑 Layer C data-sanity(clade fork addition):對本 change 觸及的 paginated query + lookup-resolved column 跑 node <clade-vendor>/scripts/audit-data-sanity.mjs --consumer-path . --files <touched> --json。exit 1 status:"fail"(PARAM_BOUNDARY,Critical)→ 主線 root-cause 修(client literal 超 server zod bound,如 perPage:200 vs max(100)),NEVER 帶病進 handoff。詳見 /data-sanity skill。
6c. Refactor Invariant Check(clade fork addition;Layer B of pre-handoff quality gates;not in upstream spectra)
理由:a UI-view refactor MUST NOT change observable behavior. perno app-status-badge-extraction(2026-05-24)做 UBadge → AppStatusBadge refactor,但 attendance/amendments.vue 的 useEmployeeListQuery({ perPage: 200 }) 違反 schema max(100) → API 400 → employeeNameMap empty → 員工 column 整列「-」。Refactor「component substitute + typecheck pass」判定通過,但 page runtime 已壞 — design review / verify:ui / manual review 全沒攔,user 親眼才抓到。Step 6c 是針對這條失效鏈的 mechanical gate。
觸發範圍:每個 Class B(UI view)phase 由主線在 Step 7 實作完成後、該 phase commit / 標 tasks done 之前,跑一次。Class A / Class C phase 不觸發(Class C 已由 codex view-layer guard 擋住 view 改動;Class A 是純設計審查)。Phase 內 touched files 沒有 .vue list/table page → script 自動 skip(exit 0),不需主線預判。
執行流程:
取得 dev server(per rules/core/proactive-skills.md § Dev Server Auto-Spawn):若本 session 尚未起 dev server,scan free port 3001–3050(避開 3000)run_in_background 起,記下 URL;已起則重用。
收集本 phase touched view files:git -C <worktree> diff main..HEAD --name-only -- '*.vue'(或本 phase commit 的 .vue 變更),組成 comma-separated list。
跑 check(從 clade central 呼叫,<clade-vendor> 解析為 ~/offline/clade/vendor,與 Step 8a.4 codex-dispatch 同慣例):
node <clade-vendor>/scripts/refactor-invariant-check.mjs \
--consumer-path . \
--dev-server-url http://localhost:<port> \
--files <comma-separated-touched-vue-paths> \
--change <change-name> \
--json
解析 exit code + JSON:
status: "pass" / "skip" → 通過,繼續該 phase 的 commit / 標 done。status: "fail"(含 uniform-column 或 network finding)→ MUST block phase complete:主線自己 root-cause(典型:client query param literal 違反 server zod schema max/min → 4xx → lookup map empty → column 全 fallback)。NEVER 標 phase done、NEVER 寫「等 user 在 manual review 抓」、NEVER 把整列 fallback rationalize 成「sample-bearing verification deferred」。修完 re-run 至 pass 才繼續。harness-error finding(agent-browser 起不來 / dev server 連不上)→ advisory,不 block(exit 仍 0)。主線一行告知 user「refactor-invariant-check 因 未能驗證 ,建議手動 sanity check」,繼續流程。False positive 出口:某 column 真的 intentionally 全空(例「備註」大多 row 空)→ 在該 .vue template 加 <!-- @ui-invariant-allow-empty[<column-header>] --> 註解,re-run 確認 suppressed。NEVER 用 marker 掩蓋真壞掉的 column(lookup-resolved column 全 fallback 是 bug,不是 optional)。
Phase 1 為 model-driven(SKILL.md 指示);Phase 3 會把本 check 升級成 archive-gate.sh hard gate(master plan 3.1)。
6d. Review Rules Check(clade fork addition;not in upstream spectra)
理由:vendor/review-rules/patterns.json 定義的機械可檢規則(如 ubadge-size-ban、overlay-width-class)在 pre-commit hook 有逐行 grep 的 fallback,但跨行 Vue template props(如 <UBadge\n size="xs"\n/>)在 hook 首次落地前會漏抓。在 apply 階段加 multi-line 整檔掃描是 defense-in-depth。perno line-notification-system(2026-06-26)的 UBadge size="xs" 穿過 pre-commit hook 上線即為實證。
觸發範圍:每個 Class B(UI view)phase 由主線在 Step 6c 之後、該 phase commit / 標 tasks done 之前,跑一次。Class A / Class C phase 不觸發。Phase 內 touched files 沒有 .vue → skip。
執行流程:
收集本 phase touched .vue files:git -C <worktree> diff main..HEAD --name-only -- '*.vue',若空 → skip。
跑 review-rules multi-line scan:
node -e "
const fs = require('fs');
const data = JSON.parse(fs.readFileSync('<consumer>/vendor/review-rules/patterns.json', 'utf8'));
const rules = data.rules.filter(r => r.fileGlob === '*.vue');
const files = process.argv.slice(1);
const tagRe = /<[A-Z][A-Za-z]*(?:\s|\n)(?:[^>]|\n)*?\/?>/g;
let hasError = false;
for (const rule of rules) {
const re = new RegExp(rule.pattern);
const exRe = rule.excludePattern ? new RegExp(rule.excludePattern) : null;
for (const file of files) {
const content = fs.readFileSync(file, 'utf8');
// multi-line: 展平 Vue tag 區塊再 match
let m; tagRe.lastIndex = 0;
while ((m = tagRe.exec(content)) !== null) {
const flat = m[0].replace(/\n\s*/g, ' ');
if (re.test(flat) && !(exRe && exRe.test(flat))) {
const line = content.slice(0, m.index).split('\n').length;
process.stderr.write('[' + rule.id + '] ' + file + ':' + line + ' ' + rule.message + '\n');
if (rule.severity === 'error') hasError = true;
}
}
// single-line fallback for non-tag patterns
const lines = content.split('\n');
for (let i = 0; i < lines.length; i++) {
if (re.test(lines[i]) && !(exRe && exRe.test(lines[i]))) {
// 避免 tag-extracted 重複命中
if (/<[A-Z]/.test(lines[i])) continue;
process.stderr.write('[' + rule.id + '] ' + file + ':' + (i+1) + ' ' + rule.message + '\n');
if (rule.severity === 'error') hasError = true;
}
}
}
}
process.exit(hasError ? 1 : 0);
" <touched-vue-files...>
解析 exit code:
[rule-id] 修正(如 size="xs" → 移除 size prop)。修完 re-run 至 exit 0 才繼續。Implement tasks (loop until done or blocked)
Reminder: Track progress by editing checkboxes in the tasks file only. Do not use any built-in task tracker.
Dispatch reminder: For each phase, follow Step 6b's three-way classification:
/spectra-ingestFor each pending task:
Show which task is being worked on
Re-read the sections of design and spec files that are relevant to this task's scope — do not rely on memory from earlier in the conversation, as context may have been compressed
Read the Implementation Contract for this task before editing any source file. If design.md exists and contains an ## Implementation Contract section (or contract content under another heading the design uses), read the part of it that covers this task's scope. The contract names the observable behavior, interface or data shape, failure modes, acceptance criteria, and scope boundaries you must satisfy. Treat the contract as the durable handoff — it is what the task will be measured against, regardless of who started the change.
Detect unclear or path-only tasks before writing code. A task is unclear if it:
foo.rs", "update bar.svelte") with no behavior, contract, or verification target;Before writing code, check:
##### Example: blocks, use them as concrete test cases:
Make the code changes required
Keep changes minimal and focused
Verify before marking done — re-read the task description from the tasks file AND the relevant Implementation Contract content from design.md. For each requirement stated in the task description and each contract item that covers this task's scope, confirm it is addressed by your changes. Confirm the verification target named by the task (test name, CLI invocation, analyzer check, or manual assertion) actually passes. If any contract item, task requirement, or verification target is missing or failing, implement/fix it now. Do not mark the task complete until every part of the description is covered and the contract for this task is satisfied.
Mark task complete by running: spectra task done --change "<name>" <task-id>
This command marks the checkbox in tasks.md AND records which files were modified for this task.
Worktree workaround (clade TD-015 / spectra ≤2.3.1): when running inside a session worktree (path <consumer>-wt/<slug>/), spectra task done writes .spectra/touched/<change>.json to the current worktree ✅ but its tasks.md checkbox flip can land in the Claude Code system-managed agent worktree (<consumer>/.claude/worktrees/agent-*/) instead. Workaround:
spectra task done, MUST verify git -C $(pwd) diff -- openspec/changes/<change>/tasks.md shows the [ ] → [x] flip in the current worktree.- [ ] <task-id> to - [x] <task-id> on the matching line). The .spectra/touched/ write already happened, so this is a UI-only sync.<consumer>/.claude/worktrees/agent-*/; that's Claude Code harness state — let it GC at session end.Continue to next task
Parallel task dispatch: When consecutive [P]-marked tasks are found and parallel_tasks: true is configured (see Step 5), dispatch them as parallel agents in a single message. If any [P] task fails, pause and report.
Pause if:
| What You're Thinking | What You Should Do |
|---|---|
| "This task looks done, I'll mark it complete" | Re-read the task description first. Check whether your diff covers every part of it. Incomplete tasks marked done are the #1 source of rework |
| "This task is trivial, I don't need to re-read the design" | Re-read. Context compression loses details. 30s of reading saves 30min of rework |
| "I already know how this works, skip the code search" | Search anyway. Someone may have added a utility since you last looked |
| "The test is obvious, I'll add it after implementation" | If TDD is enabled, test first. If not, still write it before marking done |
| "This is just a small refactor, no test needed" | Small refactors are how regressions sneak in. Write the test |
| "The artifact says X but Y makes more sense" | Pause and suggest updating the artifact. Don't silently deviate |
| "I'll fix this other thing I noticed while I'm here" | Finish current task first. Address the other thing separately |
| "The example values are just illustrations, I'll pick better ones" | Use the spec example values exactly. They were chosen deliberately |
Final check
After completing all tasks, re-run:
spectra instructions apply --change "<name>" --json
Confirm state: "all_done". If not, review remaining tasks and complete them.
8.5. Notion ticket status sync — 進行中(clade fork addition — per [[spectra-notion-coupling]])
Skip-condition(任一成立即 silent skip):consumer-meta notion.ticketWorkflow !== true;或本 change 的 proposal.md 頂部無 > **Notion ticket**: 連結。不適用時不要硬湊 ticket。
兩條件都成立時,MUST(impl 已 all_done = /spectra-apply 真的開工過,符合 REFERENCE.md §3 → 進行中 觸發):
proposal.md 頂部抓 ticket page_id,從 consumer-meta notion.dataSourceId 抓 data source。notion-fetch collection://<dataSourceId> 重撈 schema 校對 property key(中文 + 全形空格 + >=,憑記憶必錯)。未開始 / 需確認 → 依 ~/.claude/skills/_notion-tdms-board/REFERENCE.md §3 授權表推 → 進行中;已是 進行中 / 驗收中 → no-op。驗收中(需 git tag,archive → /commit 發版後才有;見 spectra-archive Step 8 + [[spectra-notion-coupling]])。驗收中→完成 等)或 發布日期 / 驗收日期 / 名稱 / 驗收完成 欄位。8a. Verify Channel Pass(Step 8b 前 hard gate)
Model allocation:Step 8a 全部(含 8a.5 / 8a.6 / 8a.7)的收集工作(跑 verify channel、截圖、hook、sweep)由 codex GPT-5.6-sol medium 執行;截圖收集完成後,由 codex GPT-5.6-sol xhigh 分析每張截圖是否匹配對應要求(防止亂截圖搪塞)。
Read tasks.md ## 人工檢查 找未勾 [verify:e2e] / [verify:api] / [verify:ui] / [verify:<a>+<b>] / deprecated [verify:auto] items。MUST 先處理完所有 verify channels 才進 Step 8b。
Skip-condition:## 人工檢查 沒任何未勾 verify:* item → 直接跳 Step 8b。
Cookbook 與範本入口:vendor/snippets/verify-channels/README.md。
Pre-verify baseline check(dispatch 前必做):
主線先 grep / read dev-login route:
find server packages -path '*/server/routes/auth/_dev-login.get.ts' -o -path '*/server/routes/auth/__test-login.get.ts' 2>/dev/null
依 channel 補查:
[verify:e2e]:Playwright config + e2e/fixtures/index.ts style three-role fixture 必須存在(propose 階段 post-propose-manual-review-check.sh 已對「標 verify:e2e 但 repo 無 e2e infra」印 warn-only advisory;apply 此處為 hard baseline gate,defense in depth)[verify:api]:__test-login 或等價 session bypass route 必須存在[verify:ui]:supabase/seed.sql 或專案等價 seed file 必須存在缺 baseline → 先判別該 item 是否真的需要此 channel(per TD-176):
[verify:ui] / API round-trip → [verify:api] / 使用者互動 round-trip「建立/編輯/輸入/點/存」→ [review:ui])→ MUST reclassify marker(不是補 infra);判別依 manual-review.evidence.md Kind 分類指引。Baseline-exists-but-functional-gap 自接路徑(hard rule,clade fork addition — per [[pitfall-verify-evidence-handoff-instead-of-self-collect]]):
Baseline 確認存在但功能性缺(dev-login route 不接 fixture user UUID / 受測 endpoint 需要 role 不符 / seed identifier 對應不到 dev-login allow-list / curl 401 因 cookie missing 等),主線 / subagent MUST 依序嘗試以下 self-collect path,全部失敗才寫 deferred annotation:
(a)(b) 執行者 — 預設派背景 codex:
(a)(b) 兩層預設派背景 codex 執行,主線不 foreground 自跑:
node ~/offline/clade/vendor/scripts/codex-dispatch.mjs \
--template ~/offline/clade/vendor/snippets/codex-offload/templates/self-collect-evidence.template.md \
--var <key>=<value> ...(依 template 變數表填:change name、dev-login route 路徑、fixture UUID、port、table 等) \
--label 8a-self-collect-<change> --effort medium
(背景跑、stdout 單一 JSON evidence;exit 0=ok / 2=(a)(b) 皆業務 fail / 3=機械故障 / 4=quota。exit 2 → 主線依序降到 (c)(d),不重派同一 brief;exit 3/4 → 機械故障,主線 fallback foreground 自跑 (a)(b) 再續 chain。)
codex-dispatch-screenshot-verify.mjs,不改走本 dispatcher(a) 擴 dev-login route allow-list(首選;最持久的治根):
server/routes/auth/_dev-login.get.ts(或 __test-login.get.ts、其他等價 dev-only signin endpoint)dev_user_id query 接受)curl -i 'http://localhost:<port>/auth/_dev-login?user_id=<fixture-uuid>' 驗證 session cookie 可 mint(b) service_role direct DB query 證 data shape(escape hatch;HTTP 路徑無法搭起時):
@supabase/supabase-js service_role client(或對應 server 端 service_role 連線)直連 DB 跑 SELECT 證明 endpoint 期待回傳的 data shape 正確(verified-api: <ISO-8601> direct-db-shape table=<table> rows=<n> sha=<sha256-12chars>)
(c) 主線自起 dev server + agent-browser self-login(OAuth 已設好時):
run_in_background 起 dev server(d) 派 screenshot-review codex(mode: verify):
四層全失敗才寫 deferred + handoff user。Annotation MUST 註明已嘗試 path 與失敗原因(避免 user 重複試同樣 path):
(deferred: tried (a) dev-login route 不接 fixture UUID(route 限 E2E test user only)/ (b) service_role 不適用(需驗 RLS)/ (c) OAuth provider unreachable in dev / (d) screenshot-review fail with <reason>。剩需 user 親自跑)
完整 recipe + 適用 / 不適用情境見 vendor/snippets/verify-channels/main-self-collect-fallback-chain.md。
執行流程:
解析未勾 verify items 並依 kinds 分類
[verify:e2e] / [verify:api] / [verify:ui] 依該 channel 執行。e2e → api → ui 順序逐 channel 執行。[verify:auto] MUST resolution as [verify:api+ui];同時記錄 deprecation warning,後續 archive-gate 也會 warn。[verify:e2e] channel — 主線自己寫 Playwright spec
Copy/adapt vendor/snippets/verify-channels/e2e-spec.template.ts。
Spec path MUST 是 e2e/verify/<change>/<topic>.spec.ts。
跑:
pnpm test:e2e:verify <change>
Spec pass 後,MUST 先確認 Playwright trace zip 真的有產出(ls -1 test-results/**/trace.zip 或對應 reporter output 路徑),再 Edit tasks.md 寫:
(verified-e2e: <ISO-8601> spec=e2e/verify/<change>/<topic>.spec.ts trace=<trace-path>)
Trace zip 抓不到(playwright.config 沒開 trace: 'on' / per-test 沒 test.use({ trace: 'on' }))→ 視同 blocker,保留 [ ],寫 (issue: trace not captured — enable trace recording in playwright.config or per-test);NEVER 寫缺 trace= 的降級 annotation(archive-gate 會擋住、review-gui 會印 malformed warning)。
Spec fail → 保留 [ ],寫 (issue: <spec failure summary>) 或回報 blocker;NEVER 寫 (verified-e2e:)。
[verify:api] channel — 主線自己跑 HTTP round-trip
Copy/adapt vendor/snippets/verify-channels/api-roundtrip.template.sh 或直接用 curl / ofetch 跑等價 request。
通過後,主線 Edit tasks.md 寫:
(verified-api: <ISO-8601> <METHOD> <URL> <STATUS>[ body=<sha256-12chars>])
Request fail / status 不符 → 保留 [ ],寫 (issue: <METHOD URL expected/actual>) 或回報 blocker;NEVER 寫 (verified-api:)。
[verify:ui] channel — 派 verify mode(UI only)
Model allocation:截圖收集由 codex GPT-5.6-sol medium 執行;收集完成後由 codex GPT-5.6-sol xhigh 分析每張截圖是否匹配對應 item 要求(防止亂截圖搪塞——收集與判斷分開、同 model 不同 effort,收集便宜跑快、判斷用最高推理力)。
Runtime 選擇(default codex;Claude subagent fallback):
command -v codex 存在且 env CLADE_FORCE_CLAUDE_SCREENSHOT 未設 → 呼叫 node <clade-vendor>/scripts/codex-dispatch-screenshot-verify.mjs --change <name> --consumer-path . --dev-server-url <url> --items-json <items.json>(dispatcher 內部以 medium effort 收集截圖)。Dispatcher 跑完 stdout 印 JSON 摘要({"runtime":"codex","change":...,"items":[...],"audit_exit_code":N,"progress_json":"...","review_md":"..."}),主線解析該 JSON 後進入 Screenshot Match Analysis gate(見下方 §)。Codex 任一 item status 不是 PASS 時 → 保留 [ ] + 寫 issue / blocker(業務結果,NEVER fallback Claude — 同一 brief 在 Claude 也會撞同樣業務問題)screenshot-review subagent(brief copy/adapt 自 vendor/snippets/verify-channels/ui-final-state-brief.template.md):
command -v codex 不存在CLADE_FORCE_CLAUDE_SCREENSHOT=1 強制退場(debug / 退場用)screenshot-review.md)反 bypass(hard rule — 2026-06-11 audit 實證):
verify:ui evidence 來取代本步 dispatcher — 2026-06-11 audit 實證:05-29 dispatcher 修復後 147 條 (verified-ui:) annotation 0 次走 codex、92 個 session 全部走此 bypass 形狀(從未進入本分支)、0 次機械故障 fallback 記錄。需要 verify:ui evidence 的唯一入口是 node ~/offline/clade/vendor/scripts/codex-dispatch-screenshot-verify.mjscommand -v codex 不存在 / dispatcher exit≠0 且 stdout 無 parseable JSON;env CLADE_FORCE_CLAUDE_SCREENSHOT=1 為 user 明確設定的 debug 退場,不在此限),且 MUST 在 tasks.md 對應 item 留 UNCERTAIN(dispatcher-error) 痕跡 — 無此痕跡的 Claude 自拍 evidence 視為違規(audit 以 annotation × dispatcher 記錄比對抓)共用規約:
Brief MUST 提供 change name、dev server URL、每個 item 的 known URL、ready_signal(structured,見下)、預期 screenshot path。
主線 MUST 為每個 assertion-bearing verify:ui item 建 ready_signal:從 item 描述的具體可斷言短語抽機械可判 signal(text / text_all / text_any / selector / regex / min_rows),放進 --items-json 的 ready_signal 欄。agent capture 前 poll 它命中才拍、拍後 cross-check 它仍在才算 PASS(見 manual-review.data-readiness.md § [verify:ui] ready_signal 契約 + screenshot-review Verify Mode step 2-4)。理由:頁面 async query 資料在 wait_for_load() 之後才填,無 signal 時 agent 只能盲拍 → 拍到空殼(per TDMS monitoring-slot 2026-05-30 incident)。
建不出 ready_signal(描述只有「畫面正常」「顯示資料」等模糊語、無具體斷言點)→ NEVER 硬 dispatch;依 manual-review.data-readiness.md § signal-less 分流 reclassify(純主觀視覺 → [review:ui];需互動才出現 → [verify:e2e] / [verify:api])。既有未帶 signal 的 grandfather item → agent 走 generic-settle fallback(不可當 PASS 充分條件)。
Agent scope MUST 限於 open known URL + readiness gate poll(≤15s 等 ready_signal)+ final-state screenshot + post-capture cross-check + DOM observation。
Agent NEVER 做 mutation / form fill / click sequences / multi-role login switching / seed repair。
PASS 後,主線 Edit tasks.md 寫:
(verified-ui: <ISO-8601> screenshot=screenshots/local/<change>/#<id>-final.png[ dom=<obs>])
FAIL / UNCERTAIN → 保留 [ ],寫 issue 或回報 blocker;NEVER 寫 (verified-ui:)。
Screenshot Match Analysis gate(截圖收集完成後 xhigh 分析):
Dispatcher 收集完所有截圖後(JSON 摘要已拿到),MUST 對每個 status === "PASS" 的 item 派 codex GPT-5.6-sol xhigh 做截圖 vs 要求匹配分析:
codex exec \
--model gpt-5.6-sol \
--dangerously-bypass-approvals-and-sandbox \
--skip-git-repo-check \
-c model_reasoning_effort=xhigh \
< /tmp/codex-screenshot-match-analysis-<change>-prompt.md 2>&1
Prompt 內容固定包含:
[DELEGATED-BY-CLAUDE-CODE]
你是截圖匹配分析器。對以下每張截圖,判斷它是否真正匹配對應的 verify:ui item 要求。
Change: <change-name>
Items to analyze:
<對每個 PASS item 列出>
- #<id>
要求描述: <item description from tasks.md>
ready_signal: <the ready_signal that was used>
截圖路徑: screenshots/local/<change>/#<id>-final.png
DOM observation: <from dispatcher JSON>
請逐一分析每張截圖:
1. 讀取截圖檔案
2. 比對 item 描述的具體要求(預期看到的 UI 元素、文字、排序、badge、狀態)
3. 比對 ready_signal 宣告的條件是否在截圖中可見
4. 判定 MATCH / MISMATCH / UNCERTAIN
輸出 JSON:
{"items": [{"id": <N>, "verdict": "MATCH"|"MISMATCH"|"UNCERTAIN", "reason": "<一句話>"}]}
判定標準:
- MATCH: 截圖明確顯示 item 描述要求的 UI 元素 / 文字 / 狀態
- MISMATCH: 截圖是空白頁 / 錯誤頁 / 顯示內容與要求無關 / 關鍵元素缺失
- UNCERTAIN: 無法確定(截圖模糊 / 部分匹配)
MISMATCH 和 UNCERTAIN 都視為需要重新處理。
分析結果處理:
(verified-ui:) annotation[ ],寫 (issue: screenshot-match-analysis: <reason>);主線重派 codex medium 重拍該 item(最多 2 輪),重拍後再跑一次 xhigh 分析NEVER 跳過 Screenshot Match Analysis 直接寫 (verified-ui:) annotation — 收集與判斷分離是防搪塞的核心機制。
Brief 範例:
mode: verify
Channel: verify:ui
Change: <change-name>
Dev server URL: http://localhost:<port>
Items:
- #3 [verify:ui]
Description: /asset-loans 顯示 overdue badge + top-sort
Known URL: http://localhost:<port>/asset-loans
ready_signal:
text_any: ["逾期", "overdue"]
selector: "[data-testid=asset-loan-overdue-badge]"
min_rows: 1
Screenshot path: screenshots/local/<change-name>/#3-final.png
Scope:
- Open the known URL, wait for load, **poll ready_signal until present (≤15s)**, capture final-state screenshot, **post-capture cross-check ready_signal still present**, record DOM observation.
- Do NOT click, fill forms, submit mutations, switch roles, repair seed, or patch network.
Multi-marker completion semantics
(verified-e2e:) / (verified-api:) / (verified-ui:),順序 MUST 是 e2e → api → ui。verify:ui / review:ui 時,呼叫 review-gui auto-check helper autoCheckCompletedAutomaticItems(...),自動 flip [x]。verify:ui 或 review:ui 時,checkbox MUST 保持 [ ],等 user 在 review GUI 確認。Deprecated [verify:auto] alias
[verify:api+ui]。[verify:auto];若 Step 8a 碰到它,只做 backward-compatible execution 並保留 deprecation warning。Exit
autoCheckCompletedAutomaticItems(...) 讓 review-gui helper 自動勾 [x]。verify:ui 的 items 保持未勾,進 Step 8b 由 user GUI 確認 visual evidence。Guardrails:
[verify:e2e] / [verify:api] automatic-only items;annotation pass 後 helper 自動 done。[verify:ui] 的 item 代勾 [x];final-state screenshot 需要 user eye。(verified-<channel>:) annotation。verify:e2e 或 verify:api。8a.5. Manual-Review Pattern Re-check (clade fork addition — pre-handoff ## 人工檢查 hygiene gate before Step 8b)
## 人工檢查 items can drift during Step 7 implementation phases — impl-phase tasks may surface new manual-review items, edit existing ones inline, or paste internal jargon (DB column names / capability flag names / spec heading slugs) into descriptions while the impl context is fresh. Re-run the same enforcement hook that /spectra-propose Step 3a uses, so jargon leakage / abstract reference / missing URL etc. doesn't reach the GUI handoff or get baked into the archive history:
bash scripts/spectra-advanced/post-propose-manual-review-check.sh <change-name>
Exit 2 = pattern findings (any of ABSTRACT_REFERENCE / CARD_WITHOUT_UID / UI_ITEM_NO_URL / MULTI_STEP_NOT_SCOPED / REVIEW_UI_BACKEND_ROUNDTRIP / INTERNAL_JARGON_LEAKAGE). Main thread SHALL Edit tasks.md directly to fix findings inline per hook stdout remediation guidance — do NOT round-trip to codex (slow). Reference: vendor/snippets/manual-review-enforcement/patterns.json + rules/core/manual-review.data-readiness.md.
Legitimate false positive (e.g., 真機掃 SMS 無 dev replay endpoint, sample inline value weekly_target=5000) → add @no-manual-review-check[<reason>] trailing marker per manual-review.md「@no-manual-review-check Marker」, re-run hook to confirm bypass recognized, then proceed.
Hook exits 0 → proceed to Step 8b silently. Defense in depth: primary catches are propose / ingest / archive — apply Step 8a.5 specifically catches drift introduced during impl phases that bypass all three.
8a.6. Pre-Manual-Review Self-Analysis (clade fork addition — Layer E.1 of pre-handoff quality gates; not in upstream spectra)
The user must not be the first to discover trivial UX/data defects in the GUI. perno app-status-badge-extraction(2026-05-24)handed 9 fabricated (verified-ui:) annotations + an all-「-」員工 column straight to the user because nothing between Step 8a and the GUI re-checked the change. Step 8a.6 is that re-check.
Model allocation:E.1 五維分析由 codex GPT-5.6-sol medium 收集 + codex GPT-5.6-sol xhigh 判定。E.2 cross-model opinion 由 codex GPT-5.6-sol xhigh 執行。
MUST before Step 8b handoff, 派 codex GPT-5.6-sol medium 跑 5-dimension 收集(template 見下),再由 codex GPT-5.6-sol xhigh 對收集結果做 5-dimension 判定:
E.1 收集階段(codex GPT-5.6-sol medium):
codex exec \
--model gpt-5.6-sol \
--dangerously-bypass-approvals-and-sandbox \
--skip-git-repo-check \
-c model_reasoning_effort=medium \
< /tmp/codex-8a6-e1-collect-<change>-prompt.md 2>&1
Prompt 基於 ~/offline/clade/vendor/snippets/pre-handoff-cross-check/main-self-analysis.template.md,要求 codex 走全 5 dimensions(D1 task↔render / D2 evidence↔dom fab guard / D3 list↔fallback / D4 api contract boundary / D5 error tail),對每個 dimension 收集客觀 evidence(讀截圖、讀 DOM observation、讀 annotation、讀 git diff、讀 API response),輸出 JSON:{"dimensions": [{"id":"D1","evidence":"...","raw_data":"..."}, ...]} — 收集階段不做 PASS/FAIL 判定。
E.1 判定階段(codex GPT-5.6-sol xhigh):
codex exec \
--model gpt-5.6-sol \
--dangerously-bypass-approvals-and-sandbox \
--skip-git-repo-check \
-c model_reasoning_effort=xhigh \
< /tmp/codex-8a6-e1-judge-<change>-prompt.md 2>&1
Prompt 給上一階段收集的 5-dimension evidence JSON,要求對每個 dimension 判定 PASS / FAIL / N/A + 判定理由。輸出 JSON:{"dimensions": [{"id":"D1","verdict":"PASS"|"FAIL"|"N/A","reason":"..."}, ...]} 。
E.1 結果處理(主線):
解析判定 JSON,寫 finding report(每個 dimension explicit verdict + evidence)。
For each FAIL: edit ## 人工檢查 item to append (issue: <summary + where>); D2 fabrication findings additionally strip the bad (verified-ui:) annotation and restore [ ].
No finding report written → NO Step 8b handoff. This is the gate.
Record the E.1 verdict(telemetry-only,fail-open):
node <clade-vendor>/scripts/pre-handoff-ledger.mjs record \
--consumer-path . --change <change-name> --layer E.1 \
--status <pass|fail> \
--findings-json '[{"dimension":"D2","severity":"critical"}, ...]'
--status fail 當任一 dimension FAIL,否則 pass;--findings-json 列每個 FAIL 的 {dimension, severity}(無 FAIL 給 [])。此 step append-only + fail-open,NEVER 因 ledger 寫入失敗而中斷 handoff。此 E.1 record 現由 archive-gate.sh Check 7(Pre-handoff Verdict Presence)機械強制存在 — 缺 E.1 record → archive 被擋 exit 2(fail-open 僅限 ledger 檔尚不存在的 pre-propagation consumer)。
Layer E.2 — codex cross-model second opinion(clade fork addition;Phase 2):E.1 之後 MUST 再派 codex GPT-5.6-sol xhigh 對同 5 dimension 做獨立 cross-check(E.1 收集 + 判定都是同 model 同 session,E.2 另起一個 session 獨立審——不同 session 各自推理,防止 E.1 session 內的 rationalization 傳染):
node <clade-vendor>/scripts/codex-dispatch-pre-handoff-check.mjs \
--change <change-name> --consumer-path . \
--tasks-file openspec/changes/<change-name>/tasks.md \
--screenshots-dir screenshots/local/<change-name>
{"layer":"E.2","runtime":"codex","status":"pass"|"fail","findings":[{dimension,severity,evidence,suggested_fix}]}。FAIL → 對應 item 寫 (issue: <dimension>: <evidence>) annotation(去重;D2 fabrication 同樣 strip 假 (verified-ui:) + restore [ ])。status:"error" + fallback:"claude-subagent"(codex 不在 / 無 parseable JSON)→ 改派一個 Claude subagent 用 main-self-analysis.template.md 同 5 dimension 做 cross-check(NEVER 憑記憶補;NEVER 跳過 cross-check 直接 handoff)。Level: Phase 2 仍為 warning / soft-gate — E.1 + E.2 都跑、findings 必寫成 (issue:)annotation 讓 user 在 review-gui 看到,但不hard-block workflow(user 在 GUI 拍板)。Phase 3.1 才把「zero unresolved findings」整進 archive-gate.sh 成 hard gate。每次 E.1/E.2 verdict 已落 <consumer>/.spectra/pre-handoff-ledger.jsonl(telemetry,gitignored);Phase 3.1 升 hard gate 的 soak 評估跑 node <clade-vendor>/scripts/pre-handoff-ledger.mjs report --all-consumers(出 fire-rate / by-dimension / E.1↔E.2 agreement)。
Reuse Step 6c / Layer C: D3 / D5 是 refactor-invariant-check.mjs(Layer B)偵測的;D4 是 audit-data-sanity.mjs(Layer C)偵測的。已跑過就 cite 結果,不必重跑。
8a.7. Screenshot Staleness Sweep + Auto-reshoot (clade fork addition — mechanical gate before Step 8b handoff)
理由:Step 8a verify:ui 拍完截圖後,後續步驟(seed fix、allow-empty marker、pattern re-check fix、E.2 finding fix 等)常產生新 commit 碰到 .vue / seed / config 檔。這些 commit 讓先前的截圖 mtime < last UI commit → review-gui 顯示 ⚠ 截圖過時。User 必須手動告訴 Claude session 重拍 — 這個 relay 不該是 user 的事。本 step 把「重拍 stale」從 behavioral rule 升級成 mechanical gate。
觸發條件:## 人工檢查 含至少一個 [verify:ui] item 且 screenshots/local/<change>/ 目錄存在。否則 silent skip。
執行流程:
跑 audit:
node --experimental-strip-types ~/offline/clade/vendor/scripts/audit-screenshot-staleness.mts \
--repo <consumer-or-worktree-path> --change <change-name> --json
解析 JSON output 的 stale 和 legacy arrays。
LEGACY 清理:刪掉 legacy array 內所有無 #N 前綴的舊圖(rm 即可;它們不配對任何 item)。
STALE 重拍(codex GPT-5.6-sol medium):對 stale array 內每個 item:
## 人工檢查 找到對應 #N item 的 URL + ready_signalcodex-dispatch-screenshot-verify.mjs 重拍該張截圖重跑 audit 確認 0 stale:
node --experimental-strip-types ~/offline/clade/vendor/scripts/audit-screenshot-staleness.mts \
--repo <consumer-or-worktree-path> --change <change-name> --json
stale array 長度 MUST 為 0 才進 Step 8b。若仍有 stale → 重拍對應項(最多 2 輪)。
Commit 更新截圖:selective git add -f 重拍的檔案 + git commit。
Skip 條件:
screenshots/local/<change>/ 目錄(純 backend change)## 人工檢查 無 [verify:ui] itemNEVER:
8b. Manual review handoff
MUST 先跑 bash scripts/spectra-advanced/pre-handoff-readiness-check.sh <change-name>。Exit 2 = NOT READY — 修完 blockers 再重跑直到 exit 0。Exit 0 才進 handoff message。
When tasks.md still contains unchecked items in the ## 人工檢查 section (typical at this point — implementation tasks [x] but manual-review items [ ]), MUST hand off to the local manual-review GUI rather than walking through items inline in chat.
Pre-handoff evidence-missing self-collect(hard rule,clade fork addition — per [[pitfall-verify-evidence-handoff-instead-of-self-collect]]):
走 review-gui handoff message 之前,MUST 對每個 ## 人工檢查 未勾且帶 [verify:*] marker 的 item(含 [verify:e2e] / [verify:api] / [verify:ui] / verify multi-marker)跑:
(issue: pre-handoff self-collect 無法解析 item description;need clarification),跳該 item)deferred 回來時若沒附「已嘗試 (a)(b)(c)(d)」紀錄 → 主線 MUST 自己再跑一輪 (a)(b)(c)(d),NEVER 把 subagent 的 deferred 直接 forward 給 user)(verified-e2e:) / (verified-api:) / (verified-ui:) annotation(review-gui auto-check helper 會自動勾 [x])[ ] + 寫 (deferred: tried (a)(b)(c)(d), <reason>; 需 user 親自跑) annotation,註明已嘗試 path 避免 user 重複試跑完一輪後仍有 evidence-missing items → 才走以下 DEFAULT path review-gui handoff message。
Default flow = 「主線已自跑一輪 self-collect、剩下真需要 user 拍板(真機 / 視覺主觀 / production 授權 / OAuth-only path 不可達)的才 surface」。 NEVER 在主線未嘗試 self-collect 一輪的情況下丟整批 evidence-missing 給 user 自己點 review-gui「📋 補 evidence prompt」按鈕(per [[manual-review]] § review-gui 補 evidence prompt 路徑分類:補 prompt 是 fallback 不是 default)。
Mechanical readiness gate(per [[review-gui-surface]] MUST 9):handoff message 之前 MUST 跑 script 確認 bucket=ready:
node ~/offline/clade/vendor/scripts/check-review-readiness.mjs \
--repo . --change <change-name>
bucket + blocking 數據,auto-triage(dispatch fix / self-collect / triage)後重跑 scriptNEVER 自判 bucket、NEVER 跳過 script — Claude 自判已 9 次證明不可靠。
Implementation 完成。Step 8a 已處理 verify channels:automatic
[verify:e2e]/[verify:api]items 已寫 annotation 並自動完成;含[verify:ui]/[review:ui]的<N>項仍待你確認。請在 clade home(~/offline/clade)執行pnpm review開本地 GUI 驗收(review-gui 從 clade home 跑會自動聚合所有 consumer + worktree change;consumer 端直接跑會被 clade-only guard 擋下;pnpm reviewdev mode default ON,改 review-gui source 自動 reload):cd ~/offline/clade pnpm review
GUI 啟動後直接打開:
http://127.0.0.1:5174/review/:
例 co-purchase 的 mvp-financial-layer-bootstrap:
http://127.0.0.1:5174/review/co-purchase:mvp-financial-layer-bootstrap
GUI 會自動配對
screenshots/local/<change-name>/#<N>-*.png、conflict-aware 寫回 tasks.md、對[verify:e2e]/[verify:api]automatic-only items 自動勾[x]、對[verify:ui]/[review:ui]items 顯示 evidence 等你 OK / Issue / Skip。完成後回報,我繼續 Step 9 status。
rules/core/proactive-skills.md § Inline Review-GUI Deep-Link):訊息 MUST 含 http://127.0.0.1:5174/review/<consumer-id>:<change-name> 完整 URL(cross-consumer mode 預設啟用,沒 <consumer-id>: prefix 會 fallback 到 clade mainEntry → API 404;<consumer-id> 從 ~/offline/clade/registry/consumers.json 對應 entry 抓)。NEVER 寫「請在 worktree root 執行」或「請在 main consumer root 執行」當預設措辭——review-gui (vendor/scripts/review-gui.mts listSourceRoots) 從 clade home 跑時偵測 vendor/scripts/review-gui.mts + consumers.local 雙標記 → 進 cross-consumer mode,自動聚合所有 consumer + worktree change;consumer 端跑會被 preflightCladeOnly guard 擋下、退出 exit 2。NEVER 列 dev server URL(http://localhost:3040/admin/...)當替代——review-gui 內部已有 final-state screenshot + evidence。若 review 過程發現需要 fresh screenshot 或 user 想 sanity check,MUST 由 agent 自起 dev server(per rules/core/proactive-skills.md § Dev Server Auto-Spawn:scan free port 3001–3050、避開 3000、run_in_background、回報 URL + shellId),NEVER 叫 user cd worktree 跑 pnpm dev。5174 是 vendor/scripts/review-gui.mts DEFAULT_PORT,找不到時會 fallback 到 5174-5194,由 GUI startup banner 告知 user,主線不必猜。AskUserQuestion chat dialog walking items one-by-one — it burns tokens, ignores the screenshot pool, and contradicts rules/core/manual-review.md 標準流程.Fallback to chat-based confirmation only when:
review:ui script (offer to run pnpm hub:check or propagate from clade first)Once manual review is complete (all ## 人工檢查 items resolved with user confirmation), proceed to Step 9.
On completion or pause, show status
Display:
Output During Implementation
## Implementing: <change-name> (schema: <schema-name>)
Working on task 3/7: <task description>
[...implementation happening...]
✓ Task complete
Working on task 4/7: <task description>
[...implementation happening...]
✓ Task complete
Completion evidence gate(clade fork addition — 輸出「Implementation Complete」前 MUST 逐格自查;每格附「實跑命令+輸出摘尾」,貼不出證據=該格未完成,不准宣告完成):
spectra instructions apply --change "<name>" --json 回傳 state: "all_done"(貼該欄位輸出行)三格證據放進下方 Output On Completion 的 ### Evidence 段。
Output On Completion
## Implementation Complete
**Change:** <change-name>
**Schema:** <schema-name>
**Progress:** 7/7 tasks complete ✓
### Completed This Session
- [x] Task 1
- [x] Task 2
...
### Evidence
- spectra state: `"state": "all_done"`(<實跑輸出行>)
- checks: <typecheck/test/lint 命令+輸出摘尾>
- annotations: <tasks.md 其中一條 (verified-*) annotation 行>
All tasks complete! You can archive this change with `/spectra-archive`.
Output On Pause (Issue Encountered)
## Implementation Paused
**Change:** <change-name>
**Schema:** <schema-name>
**Progress:** 4/7 tasks complete
### Issue Encountered
<description of the issue>
**Options:**
1. <option 1>
2. <option 2>
3. Other approach
What would you like to do?
Guardrails
/spectra-commit + user 決策處理。唯一合法 STOP 是 unmerged conflict(wt-helper 拒絕 fork)或 helper 本身錯誤;user-decision-needed pause NEVER。agent-routing.md):
medium effort — use high minimum。Step 8a 系列的收集工作允許 medium(見 Step 8a Model allocation)