| name | handoff |
| description | Session 交接管理。雙模:(A) 當前 chat session 有 in-progress 工作時,只做交接寫入(升級未完項到 HANDOFF.md / tech-debt / ROADMAP / spectra change)。(B) 當前 chat session 沒有要交辦的時,整理現有 HANDOFF.md + 評估剩餘 outstanding 工作適合串行還是並行,推薦並讓使用者用 request_user_input 選擇下一步。「Session」指當前 chat session,**不是** working tree / git state — user 並行多 session 工作,git 髒污可能來自別 session。Use when user types /handoff. |
| license | MIT |
| metadata | {"author":"clade","version":"1.0"} |
/handoff
雙模 session 交接管理:模式由「當前是否有未交辦工作」自動決定。
Step 1 — 偵測模式
「Session」=當前這個 chat session,不是 working tree / git state / 檔案系統狀態。User 經常並行多開 AI Agent session 工作,所以 git status 髒污、tasks/<date>-*.md 內 unchecked 項、active spectra change 的 unchecked tasks 都可能來自別的 session,不能拿來判斷當前 session 是否有未交辦工作。
Mode A — 當前 chat session 有未交辦工作(任一條成立即 Mode A):
TaskList 顯示當前 session 任何 in_progress 或 pending task(TaskList 是 per-session 工具狀態,可信)
- 當前 chat 對話脈絡明顯顯示 user 正在 mid-task(我剛在做某事還沒收尾、user 剛交辦一個多步驟工作做到一半)
- Stop hook 攔住但 acceptance 未滿足 + 處於 [[worktree-default]] §8 死鎖(cwd 在 main + main 已 dirty)且當前 session 已自評不適合走 §7 分支 A(context 不寬裕 / 剩餘 work 不小 / 無法 selective stash)
Mode B — 當前 chat session 沒有要交辦的:以上皆否(即使 working tree 髒、tasks/ 有別 session 的 unchecked、spectra changes 有別 session 的 active work,都仍是 Mode B —— 那些屬於別 session 的責任)。
禁止訊號(這些都不算「當前 session」狀態):
- ❌
git status --short 有 dirty file
- ❌
tasks/<YYYY-MM-DD-HHMM>-*.md 存在或有 unchecked 項
- ❌
openspec/changes/<name>/tasks.md 有 unchecked 項
- ❌
HANDOFF.md 有 In Progress 段落
宣布偵測結果一句話:「偵測到 Mode A(理由:當前 session TaskList 有 N 個 in-progress / 對話脈絡顯示 mid-task on X)」或「偵測到 Mode B(當前 session 清空)」。
Step 1.5 — 路徑解析 invariant(Mode A / B 共用)
HANDOFF.md / docs/tech-debt.md / openspec/ROADMAP.md 是「跨 change 全局狀態」,不該 per-worktree 分裂。/handoff 若在 linked worktree 內跑、寫到 cwd-相對的 HANDOFF.md,得等 squash merge-back 才出現在 main,下一 session 接手會看到舊版。
MUST 在進入 Step 2A / 2B 寫入動作前先解析 main worktree absolute path:
GIT_COMMON_DIR="$(git rev-parse --path-format=absolute --git-common-dir 2>/dev/null)"
if [ -z "$GIT_COMMON_DIR" ]; then
echo "warn: not inside a git repo; falling back to cwd for HANDOFF writes" >&2
MAIN_WT_PATH="$(pwd)"
else
MAIN_WT_PATH="$(dirname "$GIT_COMMON_DIR")"
fi
實際操作:所有 HANDOFF.md / docs/tech-debt.md / openspec/ROADMAP.md / docs/archives/<yyyy-mm>-<topic>.md 寫入路徑都用 $MAIN_WT_PATH/<rel> 絕對路徑(Edit / Write tool 的 file_path 參數);禁止用 cwd-相對路徑寫這幾個檔。其餘檔案(.claude/rules/local/*.md 讀取、tasks/<date>-*.md 清理)保持 cwd 相對行為。
Why:git rev-parse --path-format=absolute --git-common-dir 在 main worktree 回 .../.git,在 linked worktree 回 .../.git/worktrees/<slug>;兩者的 dirname 就是 main worktree path(main 自己 / linked 的 main)。git stash list 跟 git worktree list 都是 repo-wide(refs/stash 與 worktree 索引共享所有 worktree),所以 Step 3 audit 的讀取階段無關當前 cwd,但寫入 HANDOFF.md 仍 MUST 用 $MAIN_WT_PATH/HANDOFF.md。
Step 2A — Mode A 流程(只做交接寫入)
只做以下,不做 reorganize、不做下一步推薦:
-
盤點當前 session 未完項(只從 per-session 來源蒐集):
TaskList 取當前 session 所有未 completed task
- 當前 chat 對話脈絡(我剛在做、user 剛交辦但沒做完的工作)
NEVER 把以下當「當前 session 未完項」(這些屬於別 session 或檔案系統狀態,不是當前 chat 在做的事):
- ❌
tasks/<date>-*.md 既有 unchecked 項
- ❌ active spectra change 既有 unchecked tasks
- ❌
git status dirty 檔案
例外:若當前 chat 對話脈絡明確指向某個 tasks/-*.md / spectra change / dirty file 就是當前 session 在動的,那才算當前 session 工作 —— 由對話脈絡決定歸屬,不是由檔案存在決定。
-
逐項分類升級(依 rules/core/session-tasks.md 升級路徑表):
| 未完項類型 | 升級到 |
|---|
| 下一 session 要立刻接手的 in-progress 工作 | HANDOFF.md ## In Progress section |
| 被 blocker 卡住(缺權限 / 缺決策 / 等外部) | HANDOFF.md ## Blocked |
| 等待外部 signal(合約 / ramp 日期 / 第三方 API ready) | docs/tech-debt.md 建 TD-NNN |
| 未來才做、可排優先序 | openspec/ROADMAP.md ## Next Moves |
| 規模膨脹(要動 spec / design review / 跨多檔) | 新 spectra change(先 /spectra-propose) |
| 純放棄 | 直接刪 |
-
寫入:依分類 Edit / Write 對應檔案,path MUST 用 Step 1.5 解析出的 $MAIN_WT_PATH/<rel> 絕對路徑(即使當前 cwd 在 linked worktree)。HANDOFF.md ## In Progress 條目 MUST 含:
- change / task 名稱
- 主要檔案路徑(讓接手者直接跳)
- 目前做到哪裡 / 還剩什麼
- 已踩過的坑(避免下一 session 重踩)
- 若來自 [[worktree-default]] §8 死鎖:額外加 Stop hook 攔點摘要、missing acceptance criterion、改過檔案的 selective stash ref(若有,例
stash@{0}: <slug>-handoff)、下一 session 接手指引(直接從 main 跑 /<next-skill> <change-name>,apply / ingest / debug 內建 worktree dispatch;若是 archive,直接從 main 跑 /spectra-archive <change-name>)
-
清理 session-tasks:所有未完項升級完成後 → 只 mv / 刪「當前 session 自己開的」tasks/<date>-*.md(依 rules/core/session-tasks.md「NEVER 動別人的 tasks 檔」)。若當前 session 從頭到尾沒開 tasks 檔,跳過此步。
-
Worktree & Stash audit:跑 Step 3 共用 audit block(見下文)。Mode A 為「靜默寫入」—— audit 段寫進 HANDOFF.md,但不在 chat 訊息輸出 audit 全文或摘要(避免雜訊干擾當前 session 交接收尾)。
-
回報:一句話總結升級數量(如「升級 3 到 HANDOFF / 1 到 tech-debt / 砍 2」)。禁止追加「下一步建議」或「要不要繼續做 X」。Audit 因為靜默不出現在回報;user 想看走 HANDOFF.md。
Step 2B — Mode B 流程(整理 + 推薦)
2B.0 Session-end pitfall sweep(呼叫 /oops Mode C — from hub-maintenance-full plugin;無此 plugin 時跳過整段並繼續 2B.1)
在動 HANDOFF.md 前,先回顧當前 chat session transcript 掃 missed lessons。觸發訊號:
- user 糾正 Claude 的訊號(「不對」「不是這樣」「不要這樣做」「重做」「應該先 X」)
- session 中解過的 cryptic runtime error 或 stack trace
- 升 npm 套件大版 / 動 evlog / Supabase RLS / Cloudflare Workers config / nuxt-security / Better Auth / supabase-js 過程中發現的非預期行為
- 跨 consumer 散播某 fix 過程中發現新的 contract 變更
對每個 candidate MUST 判斷分流:
| Candidate 等級 | 動作 |
|---|
符合 /oops Mode B 四條件齊備(root cause / detection / fix / prevention) | dispatch /oops 走完整 Mode B pipeline 寫進 ~/offline/clade/docs/pitfalls/ |
| 個人偏好 / 跨專案沿用的行為更正(user 糾正用詞、強調某做法) | dispatch /oops Mode B 輕量降級 → 寫 auto-memory feedback type |
| 只給當前 repo 的 self-improvement lesson | dispatch /oops Mode B 輕量降級 → 寫 <consumer>/tasks/lessons.md |
| 一次性 typo / 純業務邏輯 bug / 純設計問題 | 跳過(不該成為 pitfall 也不該佔 memory 槽位) |
若 sweep 為空(無 candidate)→ 一句話宣告「無 missed lesson」繼續 2B.1。
禁止行為:
- ❌ 把 sweep candidate 一次塞給 user 讓他選哪些要記 — 主動分流後直接 dispatch,user 看結果
- ❌ 把 candidate 暫存到 HANDOFF.md
outstanding 段 — sweep 是 session 內 cleanup,不該變成跨 session 待辦
- ❌ 強推 candidate 升級到 pitfall — 不符四條件就降級或跳過,不硬塞
2B.1 HANDOFF.md Health Gate(hard step)
跑 audit → 若超標走 rotate plan → 再走既有 reorganize。三 sub-step 都跑完才能進 2B.1.5。
2B.1a Audit
node ~/offline/clade/vendor/scripts/handoff-scan.mjs --json 2>/dev/null
一次涵蓋四段機械掃描:Health Gate(本 sub-step)+ review-gui readiness(§2B.1.7)+ worktree/stash audit(Step 3)+ tech-debt hygiene(§2B.1.8)。輸出四個 section(healthGate / reviewGuiReadiness / worktreeStash / techDebtHygiene),每 section 含 checks({name, status: pass|warn|fail|n/a, detail})與 raw(原始事實)。同一次輸出四處共用,不必重跑;對 status=warn/fail/n/a 的項目做判讀與後續動作。
本 sub-step 讀 healthGate 段:
checks 全部 status=pass → HANDOFF 健康,跳 2B.1c
- 有 warn / n/a(含
rotate-plan 標 n/a needs-judgment)→ MUST 進 2B.1b
- 有 fail(scanner 自身炸掉,detail 含原因)→ 對 user 回報卡點,不假裝 audit 已過
JSON 範例(節錄):
{
"healthGate": {
"checks": [
{ "name": "handoff-size", "status": "warn", "detail": "64.2 KB (threshold 30 KB)" },
{ "name": "rotate-plan", "status": "n/a", "detail": "needs-judgment: 2 warning(s) → 進 2B.1b rotate plan ..." }
],
"raw": {
"sizeKb": 64.2,
"lines": 707,
"thresholds": { "max_kb": 30, "max_lines": 400, "narrative_age_days": 3, "active_age_days": 14 },
"sectionStats": [
{ "title": "...", "kind": "active|baseline|narrative", "date": "2026-05-22", "ageDays": 4, "startLine": 8 }
],
"warnings": [
{ "drift": "handoff-size-exceeded", "message": "HANDOFF.md is 64.2 KB ..." }
]
}
},
"reviewGuiReadiness": { "checks": ["..."], "raw": { "counts": {}, "entries": ["..."] } },
"worktreeStash": { "checks": ["..."], "raw": { "worktrees": ["..."], "stashes": ["..."], "orphanSidecars": ["..."] } },
"techDebtHygiene": { "checks": ["..."], "raw": { "total": 0, "openCount": 0, "closedCount": 0, "closedLines": 0, "stale": ["..."], "aging": ["..."], "closed": ["..."] } }
}
2B.1b Rotate plan(超標時必跑)
依 healthGate.raw.sectionStats[].kind 分組:
| kind | 處置 |
|---|
active | 留 HANDOFF |
baseline | 留 HANDOFF(標記為覆寫式段,下次 audit 同位置應仍存在但內容已更新) |
narrative | rotate candidate — 按 date 的 YYYY-MM 分桶,搬到 docs/archives/<YYYY-MM>-handoff-narrative.md(append-only) |
例外情境:
- 0 narrative 但 size/lines 仍超標(clade 自家常見:baseline section 過度累積到 30+ 條)→ rotate plan 不自動搬,改產出「baseline 拆檔建議清單」:哪幾個 baseline section 該拆到
docs/archives/<YYYY-MM>-<topic>.md / docs/decisions/<topic>.md / docs/solutions/<topic>.md,依 baseline section 主題判斷。user 拍板後手動執行。
- narrative dated section 跨多月 → 按月 group,每月一個 archive bucket。
- ambiguous section(kind = baseline 但 title 是 dated;或 active/narrative 邊界不清)→ 保守留 HANDOFF + 在 chat 訊息列出,等下次 Mode B 重判。
用 request_user_input 把 plan 呈給 user(terminal options):
- (A) 套用 rotate plan:把 N narrative section(共 K KB)搬到
docs/archives/<YYYY-MM>-handoff-narrative.md,HANDOFF.md 移除對應段
- (B) 跳過此次 rotate(next session 再判,warning 仍會在 SessionStart surface)
- (C) 手動編輯 HANDOFF.md,跳過自動 rotate(user 自己接手)
寫入規約(A 路線執行時):
2B.1c Reorganize(既有 2B.1 行為,保留)
讀整理過的 HANDOFF.md,逐段再判一輪:
| 內容類型 | 動作 |
|---|
| 與當前 SoT 矛盾(版本過時、檔案已不存在) | 修正或刪除 |
| 重複條目(同一事在 HANDOFF / tech-debt / ROADMAP 都有) | 留最該的位置,其他刪 |
寫法違反當前專案規則(如 clade 自治區內 consumer 自治區工作 violation) | 依規則重寫或刪除 |
| 仍 valid 的稽核 baseline 表 / outstanding follow-up | 保留 |
## Deferred discuss items 段(含 <!-- deferred-begin:...:... --> markers) | 保留、禁動:由 /spectra-archive Resume mode 獨自 maintain(依 marker 增刪 entry),/handoff 不可改寫、reorder、合併或刪除任何 entry |
MUST 載入 .claude/rules/local/*.md 內所有自治區規則。若有 clade-role-and-todo-discipline.md 之類 local rule 限定 HANDOFF 寫法,整理時必須遵守。
寫入 HANDOFF.md 與 archive 檔的路徑 MUST 用 Step 1.5 解析出的 $MAIN_WT_PATH/HANDOFF.md / $MAIN_WT_PATH/docs/archives/<YYYY-MM>-handoff-narrative.md / $MAIN_WT_PATH/docs/archives/<YYYY-MM>-<topic>.md,不用 cwd 相對。
2B.1.5 Worktree & Stash 稽核
跑 Step 3 共用 audit block(見下文)。Mode B 完成 audit 後,在 chat 訊息加一行摘要:「Audit: N 個 worktree / M 個 stash 寫進 HANDOFF.md ## Worktree & Stash Audit 段」。具體判定邏輯不在此重複,避免兩處規約走 drift。
2B.1.7 Review-gui readiness scan(hard rule)
讀 §2B.1a 那次 handoff-scan.mjs --json 輸出的 reviewGuiReadiness 段(script 內部已從 clade home 代跑 headless review-gui.mts --scan 並 filter consumerId = 當前 consumer,raw.entries 即當前 consumer 的 active changes)。本 sub-step 前尚未跑過 scan 時補跑:
node ~/offline/clade/vendor/scripts/handoff-scan.mjs --json 2>/dev/null
Outstanding 推薦(§2B.2 / §2B.3 / §2B.4 / §2B.5)MUST 引用 scan 結果而非從 HANDOFF.md 既有 narrative 或 tasks.md leaf count 推測 review-gui bucket 與 ready 狀態。
把 raw.entries 依 bucket 寫入 $MAIN_WT_PATH/HANDOFF.md 新段:
## Review-gui Readiness
_Updated: <YYYY-MM-DD> /hub-core:handoff Mode B — clade <version> scan_
### ✅ Ready (N)
- `<changeKey>` | pending=N/total | userActionPending=K
- (空時寫 `_(none)_`)
### ⚠ notReady (M)
- `<changeKey>` | bucket=`<bucket>` | pending=N/total | userActionPending=K
- bucket meaning hint:
- `feedbackGiven` → 有 verify pending / issued feedback,需 agent 處理 evidence
- `awaitArchiveWalkthrough` → 純 `[discuss]` 待 `/spectra-archive` Step 2.5 walkthrough
- `readyForEvidence` → apply 已完成但 evidence missing
- `applyInProgress` → impl 未達 APPLY_COMPLETE_THRESHOLD
- `applyBlocked` → impl 卡 `@apply-blocked` 外部 blocker(master 統計排除,但 **MUST 走 §2B.2.5 主動 triage**,不可 silently drop)
- `awaitingUserDecision` → Claude 已標 `(awaiting-user-decision:)` 交還 user(master 排除,同樣走 §2B.2.5 triage)
- `healthCheckNeeded` → Pre-Review Data Readiness pattern 命中
- `malformed` → tasks.md 解析失敗
master 排除 ≠ 不寫入 / 不 triage:applyBlocked / awaitingUserDecision 雖不計入 ready/notReady master count,仍 MUST 寫進 ### ⚠ notReady 段(附 bucket),並在 §2B.2.5 主動抽 blocker 原因。NEVER 因「master 排除」就從 HANDOFF / outstanding 中省略。
每跑一次 audit 整段覆寫(不是 append)— scan 是 snapshot,stale audit content 應該被新 snapshot 替換。
判定 review-gui readiness 的 SoT:handoff-scan 輸出 reviewGuiReadiness.raw(counts + entries[].bucket;底層即 review-gui --scan 的 ready / notReady / buckets)。tasks.md leaf count / spectra DB <done>/<total> 數字 / HANDOFF.md 既有 narrative 都不是 SoT — 它們是不同維度的真相(leaf count 不解析 evidence annotation / kind marker;spectra DB 不考慮 cross-wt 與 evidence;既有 narrative 是上次 session 的 stale snapshot)。
Mode A 跑時不執行本 sub-step — Mode A 是「靜默寫入交接」,scan 為 outstanding 推薦服務,Mode A 沒推薦階段。
scan 失敗 fallback(reviewGuiReadiness.checks 的 review-gui-scan check status=fail 時,detail 已含失敗原因 + stderr 前 5 行):
| 失敗情境 | 處理 |
|---|
| clade home 不存在 / 不可達 | 寫 ## Review-gui Readiness 段含 _(scan unavailable: <reason>)_,並警告主線「outstanding 推薦無 review:ui 即時資訊,請避免推薦 review:ui flow」 |
review-gui.mts 報 error(type checked node version etc.) | 同上,把 check detail 內的 stderr 行貼進該段 |
scan 跑成功但回空 list(review-gui-changes check detail 標 0 changes) | 寫 _(scan returned 0 changes — repo possibly fresh)_ |
2B.2 盤點剩餘 outstanding
從以下來源蒐集 outstanding 工作。所有 active item 一律列入盤點並推薦處理 — drift scan 的 active-section-stale(14d)是 escalation threshold,不是 grace period;未超過 14d 的 active item 同樣 MUST 列入 outstanding,不得因「尚未觸發 stale signal」而省略或降低優先序。
- 整理後的
HANDOFF.md
docs/tech-debt.md 未解決的 TD-NNN — 優先序分三層,MUST 依此排序,NEVER 平鋪混在一起(這是「堆積然後忘記」的根因):
- stale(
techDebtHygiene.raw.stale[],>60d 無 Last reviewed)— 最高優先,discAge 越大越前。每條 MUST 附三選一(做掉 / wontfix / stamp Last reviewed),但 stamp Last reviewed 列為最後選項,不推薦
- aging(
techDebtHygiene.raw.aging[],>30d 含被 snooze 的)— 第二優先,discAge 越大越前。每條 MUST 主動追問 blocker:「什麼卡關?能現在推進嗎?」。對 snoozed: true 的項目明確指出「已 stamp Last reviewed 但仍未解決 — 不應再延期」
- 其他 open TD — 按
Discovered 排序,正常列入 outstanding
openspec/ROADMAP.md ## Next Moves
- 任何已 archive 但留下 follow-up 註記的 change
每條 outstanding 抓三件資料:
- 標題(一句話)
- 涉及檔案 / module / consumer
- 依賴關係(依賴誰、誰依賴它)
2B.2.5 applyBlocked / awaitingUserDecision bucket 主動 triage(hard rule)
核心命題:applyBlocked / awaitingUserDecision 是 master 排除 bucket,但排除的只是 ready 統計,不是主線的責任。§2B.1.7 scan 抓到這兩類 change 時,MUST 對每一條主動 triage,NEVER 只寫進 ### ⚠ notReady 就 silently drop、等 user 主動問才處理。此步對齊 [[goal-mode]] §「applyInProgress 不是 user-bound」的同一 spirit:blocked bucket 不等於「主線無事可做」。
對每條 applyBlocked / awaitingUserDecision change MUST 做三件事:
- 抽 blocker 原因:Read 該 change 的
tasks.md,grep @apply-blocked[<reason>] / (awaiting-user-decision:<reason>),逐條列出每個 blocked phase 的具體 reason(不是一句「blocked」帶過)。MUST 到 change 目錄實抽,NEVER 從 bucket 名或 HANDOFF 既有 narrative 推測原因。
- 辨識 startable 子集(最關鍵):一條 change 落
applyBlocked bucket 只代表它含至少一個 @apply-blocked phase,不代表整條無事可做。MUST 判斷 tasks.md 是否有未 blocked、可現在開工的 phase / task(典型:上游條件已解封但整條仍被 blocked marker 拖著)。有 startable 子集 → 依 [[goal-mode]] 規約提供 dispatch 選項(/wt /spectra-apply <change> 只做 unblocked phases),NEVER 因整條標 applyBlocked 就當 user-bound 擱置。
- 端出具體 user 決策:把 blocker reason 中真正需 user / owner 拍板的具體題目(例:「work-order grain 二選一:
receiving_scans+process_tracking vs work_reports」)逐條列進 outstanding,讓 user 當場能答,NEVER 只寫「等 owner 拍板」這種無法行動的模糊句。同時分辨哪些 blocker 是外部依賴(等 A 端 contract / 等別 change 先完成)— 這類才真的擱置,但仍 MUST 明列在等什麼。
triage 結果併入 §2B.2 outstanding 清單(與 HANDOFF / tech-debt / ROADMAP 來源並列),進 §2B.3 serial/parallel 評估、§2B.4 推薦。
分類對照:
| blocker 類型 | 判定 | outstanding 處置 |
|---|
| 有 startable 子集 | tasks.md 有未 blocked phase 可現在做 | 列 outstanding + 提供 /wt /spectra-apply dispatch 選項(只做 unblocked phases) |
| 需 user/owner 內部決策 | @apply-blocked[需 owner 拍板: X] 類 | 列 outstanding + 端出具體決策題讓 user 當場答 |
| 等外部依賴 | 等 A 端 contract / 等別 change 先完成 | 列 outstanding + 明列在等什麼 signal(對齊 [[goal-mode]] @apply-blocked 僅限真外部 blocker) |
NEVER:
- ❌ scan 抓到 applyBlocked change 卻不 Read 其 tasks.md 抽 blocker 原因
- ❌ 把「含 blocked phase」等同「整條無 startable 工作」→ 漏掉可現在 dispatch 的子集
- ❌ 只寫「等 owner 拍板 / 卡外部」而不端出具體決策題或具體等待 signal
- ❌ 因 master 統計排除就把這兩類 bucket 從 outstanding / request_user_input 選項中省略
為什麼這條 rule 存在(2026-07-06 TDMS 實證):/handoff Mode B 對 3 條 applyBlocked 的 ai-* change 只寫進 notReady 段就結束,未抽 blocker 原因、未辨識 ai-mcp-server 其實 Phase 1-7.2 已解封可現在開工、未端出唯一需 user 拍板的 work-order grain 決策。user 被迫主動追問才拿到這些資訊 — 主動 triage 本應是 Mode B 內建職責。
2B.3 Serial vs Parallel 評估
對每條 outstanding 套 rubric:
Serial 訊號(任一成立 → serial):
- 同檔 / 同 module 內順序改動
- 同一 spectra change 內 phase 間有依賴(phase B 依賴 phase A 落地)
- 共享 mutex 資源:DB migration、單一 config 檔、單一 secret rotation
- 後一步的設計需要前一步的結果(探索結論決定後續方向)
Parallel 訊號(全成立 → parallel candidate):
- 動到的檔案 / module / consumer 不重疊
- 沒有 phase 依賴(各自獨立完工)
- 無共享 mutex 資源
- 可獨立驗證(各自有 acceptance criteria)
若 Parallel candidate,MUST 套用 thin-brief 長駐 subagent 模式(避免 fresh subagent fan-out 冷載 N 倍 repo context):
- 主線預先用 codebase-memory-mcp(
search_graph / trace_path / get_code_snippet)定位每條 outstanding 的檔案路徑 + 符號 + 依賴,把結果寫進 brief
- 一條 outstanding 配一個長駐 named subagent;後續 phase 推進MUST 用
SendMessage({to: name}) 續跑,NEVER 為同一條 outstanding 的下一個 phase 重開新 subagent
- Thin brief(3–5K 具體指示:檔案路徑、規則條目、驗收標準),禁止冷載整份 repo / AGENTS.md / rules
- 不同 outstanding 的長駐 subagent 可同時跑(多個
Agent tool call 放同一訊息)
2B.4 推薦 + request_user_input
寫一段「outstanding 盤點 + serial/parallel 推薦」訊息:
Outstanding(N 條):
1. <標題> — <涉及範圍> — <serial/parallel 判定>
2. ...
推薦執行模式:<serial | parallel | mixed>
理由:<rubric 命中哪幾條>
接著用 request_user_input 問 user 選擇:
- Option 1: 推薦的執行模式 + 起手 outstanding(label 標
(Recommended))
- Option 2-3: 替代方案(如「先做 outstanding #2」/「mixed: 先 serial #1 再 parallel #2-#3」)
- Option 4(optional): 「都先不做,session 收工」
禁止行為(依 user AGENTS.md「不要把工作往後放」+ clade-role-and-todo-discipline.md「Session 結尾自查」+ rules/core/handoff.md § Outstanding writing hygiene):
- 推薦清單裡放「N 週後再回頭做」/「排程 /schedule 在 X 天後」
- 推薦清單裡放當前主線「無法完整 own」的工作(consumer 自治區工作 / user 必須親自操作的外部系統指令)— 此 ban 不因
clade-role-and-todo-discipline.md § user-explicit cross-boundary authorization carve-out 而鬆綁;該 carve-out 只解鎖「user 已明確發起」的當下跨界行為,不解鎖 session 結尾主動推薦 consumer 動作
- 用「block production」「最高優先」包裝其他自治區工作
- 推薦的 Option 1 不該是「都不做」(除非真的盤點為空)
mergeBackSafety: ptb-unsafe wt 不可列為 Option 1 (Recommended);可列為 Option 但 label 強制標 ⚠ PTB unsafe、描述明列 PTB 風險,禁止包裝為「最快 deliverable」「safe to land」「ready to merge」這類沒 signal 支撐的斷言
- 對任何 wt 推薦 next move 時,描述 MUST 含 safety signal(blocker / uncommitted / baseline ref)— Step 3.1 audit(handoff-scan
worktreeStash)已記錄,照搬即可
- NEVER 推薦「review:ui」/「ready 區可點 OK」/「最快 deliverable 用 review:ui 收尾」相關 next move 而未先跑 §2B.1.7 readiness scan(handoff-scan 內含 review-gui
--scan)+ 引用 ## Review-gui Readiness 段的 scan 結果。Scan 後 change 落 feedbackGiven / awaitArchiveWalkthrough / readyForEvidence 等 bucket 時,描述 MUST 反映該 bucket 的真實 user action(不是「點 OK 收尾」) — 例:feedbackGiven 推薦語應為「補 evidence annotation 後 user 在 review GUI 點 OK」、awaitArchiveWalkthrough 推薦語應為「跑 /spectra-archive <change> 觸發 Step 2.5 walkthrough」
- NEVER 從
HANDOFF.md 既有「Outstanding」段、tasks.md leaf [x] / [ ] count、或 spectra list CLI 進度數字推測 review-gui bucket 或 ready 狀態 — 三類資料維度都跟 reviewBucketForChange() 不同,scan output 才是 SoT
2B.4.5 PTB-unsafe wt 的快速分流(v1.14+)
對 Step 3.1 audit 判為 mergeBackSafety: ptb-unsafe 的 wt,MUST request_user_input 直接給 3 個 terminal 選項,禁止 inspect 子選項作為主推薦:
| 選項 | 動作 | 風險 |
|---|
| Commit baseline 全收 → merge-back | cd <wt> && git commit -m "baseline: <slug> pre-fork drift catch-up (N paths)" 後 wt-helper merge-back | 可能把跨 session WIP 一起 commit 進 main;commit message 含混 |
| Abandon wt | wt-helper cleanup <slug> --force --force-discard-unland --force-discard-uncommitted | 永久遺失所有 wt 工作(commits + uncommitted);user MUST 明確接受風險 |
| Defer | 不動 wt 原狀,記進 HANDOFF.md outstanding,下次 session 或專門 chat 處理 | 工作仍卡在 wt,main 看不到 |
Inspect 路徑只作為附加可選(Option 4),描述需強調「inspect 不會新增可行動方案,3 個 terminal 解仍是這 3 個」,避免 user 誤選後燒 token 跑完 inspect 還是回到 commit / abandon / defer。
為什麼:PTB-unsafe 的本質是「無 baseline ref + 大量 uncommitted」,任何 deep inspect 都無法把這轉成 safe-to-merge 狀態 — 解路就是 3 條 terminal 選擇。預先固化選項 = 把分支變成 reflex,省 user 多輪 round-trip。
2B.5 接續 dispatch(user 選定 outstanding 後)
User 透過 request_user_input 選定下一步 outstanding(含明確的 next-skill 與 change-name / argument)後,MUST 依下表透過 Skill tool 內呼對應的入口,不要輸出「請執行 cd ... && claude ...」oneliner 讓 user 另開 terminal。
| Next-skill 類型 | Dispatch 行為 |
|---|
/spectra-archive <change-name> | 直接 透過 Skill tool 內呼 /spectra-archive <change-name>,不建 worktree。Archive 是 main-bound 例外,per [[worktree-default]] §1 |
/spectra-apply / /spectra-ingest / /spectra-debug(要寫 tracked file 的 spectra-* skill) | 透過 Skill tool 內呼 /wt <slug>: /<next-skill> <change-name>,由 /wt 建 worktree + dispatch subagent 跑 next-skill + squash 回 main + cleanup(per [[wt]] Form 3)。Parent session cwd 不動 |
/spectra-ask、其他 read-only / 探索 skill | 直接 透過 Skill tool 內呼(無需 worktree) |
/spectra-propose / /spectra-discuss | 直接 透過 Skill tool 內呼(propose / discuss 階段純寫 openspec/changes/<new>/ 內新檔,不碰既有 tracked file,與[[worktree-default]] §1 的 worktree 邊界相容) |
| 不在表上的 skill | 評估後決定:若不寫 tracked file 直接 dispatch;若會寫則包進 /wt <slug>: /<next-skill> 走 worktree |
判定條件:
- 觸發此 dispatch path MUST 全部成立:當前 chat session 剛跑完 Mode B、user 已選定下一步
- Mode B 的寫入動作(§2B.1 / §2B.1.5)透過 Step 1.5 的
$MAIN_WT_PATH 已落到 main worktree absolute path,與 cwd 無關
- 若 cwd 不在 main worktree(user 在 linked worktree session 跑了
/handoff)→ dispatch 階段 MUST 在內呼 /wt / next-skill 前先 cd "$MAIN_WT_PATH" 切換工作目錄,dispatch 完成後不必還原(session 已收尾交接)。直接 dispatch 純 read-only / propose / discuss 類 skill 不寫 tracked file 時可省略此切換
Slug 解析:/wt <slug>: /<next-skill> <change-name> 的 <slug> 由 change-name 直接帶入(wt-helper 自動 normalize per [[worktree-default]] §3)。
Parent cwd 不動 invariant:/wt Form 3 內部用 subagent 進 worktree 跑 next-skill,主線(當前 chat session)cwd 全程在 main worktree,per [[worktree-default]] §1。先前 wt-relax-for-archive-and-handoff change 引入的 --dispatch-from-handoff flag 已移除,禁止在 args 內帶此 flag。
Review:ui dispatch scope rule:pnpm review flow dispatch 前 MUST 引用 §2B.1.7 scan 結果確認該 change 落 ready bucket 或對應 user-actionable bucket。三類非 ready bucket 走不同入口(NEVER 一律推 review:ui):
| Scan bucket | 真實下一步 | 入口 |
|---|
ready | user 在 review GUI 點 OK / Issue / Skip | cd ~/offline/clade && pnpm review + deep-link |
feedbackGiven | agent 先補 verify-* annotation evidence;user 後續在 review GUI 點 OK | 主線跑 verify channel(per manual-review.md Step 8a),補 annotation 後 → review GUI |
awaitArchiveWalkthrough | 跑 /spectra-archive Step 2.5 walkthrough,純 [discuss] items 由 Claude evidence-based 討論後勾 | /spectra-archive <change-name> |
readyForEvidence | agent 補 verify-* annotation(同 feedbackGiven);scan 顯示 evidenceMissing list 含具體 item | 主線跑 verify channel |
applyInProgress | 繼續 /spectra-apply 完成 impl phase | /spectra-apply <change-name>(per §2B.5 dispatch table 走 /wt) |
healthCheckNeeded | 修 Pre-Review Data Readiness violation(模糊指代 / 缺 sample / 缺 step);通常走 /spectra-ingest | /spectra-ingest <change-name> |
malformed | 修 tasks.md 解析問題(kind marker / #N schema);通常 grep + 手動修 | 主線直接 Edit |
2B.1.8 Tech-debt hygiene scan(hard rule — 防 tech-debt.md 堆積)
讀 §2B.1a 那次 handoff-scan.mjs --json 輸出的 techDebtHygiene 段(掃當前 consumer 自家 docs/tech-debt.md,與 clade SoT 無關)。本 sub-step 前未跑過 scan 時補跑同一指令。
兩條訊號 MUST 各自處置,NEVER 只看一條:
| 訊號 | check | 意義 | 處置 |
|---|
| staleOpen | tech-debt-stale:<TD-NNN>(warn) | open/pending TD 的 Discovered > 60d 且無 ### Resolution / 近期 Last reviewed — 「開了就忘」候選 | 列進 §2B.2 outstanding 並標記為最高優先(age 越大越前)。推薦 user 三選一:做掉 + 補 ### Resolution / 改 Status: wontfix + 理由 / 加 **Last reviewed**: <today> 重置 SLA。NEVER 默默放回清單尾巴 |
| aging | tech-debt-aging:<TD-NNN>(warn) | open/pending TD 的 Discovered > 30d,含被 Last reviewed snooze 的 — 「正在老化」候選 | 列進 §2B.2 outstanding(排在 stale 之後、一般項目之前)。MUST 主動追問 user 卡關原因(見 § anti-snooze)。對 snoozed: true 的項目明確指出 Last reviewed 不等於解決 — 「已 stamp Last reviewed 但仍無 Resolution,應推進或 wontfix」 |
| closedBloat | tech-debt-closed-bloat(warn,closed TD ≥ 門檻時觸發) | done/resolved/wontfix 的 closed TD 仍躺 docs/tech-debt.md 主檔,每次讀檔佔 token | 產出 rotate 建議:把 closed TD 搬到 $MAIN_WT_PATH/docs/archives/tech-debt-closed-<YYYY-MM>.md(append-only,編號不重用 — audit-tech-debt-hygiene.mjs Invariant 1 archive-aware),主檔移除對應段。用 request_user_input 讓 user 拍板(同 §2B.1b rotate plan 模式:A 套用 rotate / B 跳過 / C 手動),user 選 A 才動檔。例外保留:raw.closed[] 內 status 帶 re-activation 條件的(如 wontfix-until-signal、*-until-*)MUST 從 rotate 候選排除並在訊息標註「保留主檔以維持 trigger 可見性」— 這類項雖被 isClosedStatus(clade 共用 SoT)歸 closed,但搬到 archive 會丟失等訊號再啟動的 trigger |
Anti-snooze(防無限延期):Last reviewed 只是「我知道這條存在」的確認,不等於已在推進。以下情境 MUST 主動追問 user 而非默許延期:
- aging TD 帶
snoozed: true(有 Last reviewed 但無 Resolution)— 「這條 TD 已 N 天,上次 review 是 M 天前但仍未解決。什麼卡關?能現在做掉嗎?還是應該 wontfix?」
- stale TD(> 60d)— 已超過 SLA,NEVER 推薦「加 Last reviewed 重置」作為預設選項(只作為三選一的最後項,前兩項是做掉 / wontfix)
- 同一條 TD 如果在
docs/tech-debt.md body 明確寫了 blocker(如「需人工確認」「等外部 API」「需客戶拍板」),MUST 在 outstanding 盤點時引用該 blocker 並問 user:「blocker 解了嗎?能推進嗎?」
判定 SoT:techDebtHygiene.raw(stale[] 含 discAge / lineNo;aging[] 含 discAge / reviewAge / snoozed;closed[] 含 status / lines;closedCount / closedLines)。NEVER 從 docs/tech-debt.md 既有 narrative 或目測推測 — scan output 才是 SoT。
Mode A 跑時不執行本 sub-step — Mode A 是「靜默寫入交接」,本 scan 為 §2B.2 outstanding 盤點與 rotate 推薦服務,Mode A 無推薦階段。
scan 失敗 / 無檔 fallback:techDebtHygiene.checks 出現 tech-debt check status=pass detail=「docs/tech-debt.md 不存在」→ 該 consumer 無 tech-debt 追蹤,跳過本段不報錯。
Step 3 — Worktree & Stash 稽核(共用 block,Mode A / B 都會 invoke)
目的:把所有 linked worktree + stash 的當前狀態 + 下一步建議寫進 HANDOFF.md ## Worktree & Stash Audit 段,避免歷史包袱累積。讀取 + 寫入摘要,不執行 drop / cleanup / merge-back。
3.1 Worktree audit
讀 handoff-scan.mjs --json 輸出的 worktreeStash 段(Mode B 在 §2B.1a 已跑過 → 直接共用該輸出;Mode A 沒經過 2B.1 → 在此跑):
node ~/offline/clade/vendor/scripts/handoff-scan.mjs --json 2>/dev/null
worktreeStash.raw.worktrees[] 每條已含 wt-helper list 欄位(slug / branch / path / daysOld / mergedToMain)+ kind 判定(kind / nextStep);script 另掃 git worktree list --porcelain,非 session/* branch 的 worktree 列進 raw.unmanagedWorktrees。
3.1a 每條 wt 的 merge-back safety signal(v1.14+ hard rule)
對每條 mergedToMain: false worktree,script 已以純讀方式蒐集 3 條 signal(不跑 merge-back --dry-run — 該入口會清 index.lock 屬寫入;blockers 改用等價唯讀邏輯:branch diff files ∩ main dirty paths,即 wt-helper detectMergeBlockers 演算法):
blockers — main 端會被 merge-back 踩到的檔案數
uncommitted — wt working tree + staged 的 dirty 行數
baselineRef — refs/wt-baseline/<slug>/ pinned ref(無則 null)
並由 3 條 signal 推導 mergeBackSafety(判讀與處置仍照下表):
| 條件 | mergeBackSafety | 對應動作 |
|---|
blockers == 0 + uncommitted == 0 | landable | safe to merge-back |
blockers > 0 或 uncommitted > 0,且 baselineRef 存在 | ptb-recoverable | merge-back / rescue path 都 OK(pinned ref 是救援保險絲) |
blockers > 0 或 uncommitted ≥ 100,且 baselineRef 不存在 | ptb-unsafe | 禁止 dispatch /spectra-archive;走 Step 2B.4.5 PTB-unsafe 快速分流 |
表未覆蓋區(blockers == 0、uncommitted 1–99、無 baselineRef) | unclassified(check 標 n/a needs-judgment) | LLM 看 raw.worktrees[] 的 signal 自行判讀(小量 WIP 通常先 commit 進 wt 再 merge-back) |
3.1b Kind 判定表(與 mergeBackSafety 正交)
| 條件 | kind | 下一步建議 |
|---|
mergedToMain: true | merged | cleanup — node vendor/scripts/wt-helper.mjs cleanup <slug> |
mergedToMain: false + openspec/changes/archive/<slug>/ 存在 | archived-change | verify-then-cleanup — change 已 archive 但 branch 未 merged-into-main,先 git log -1 <branch> 檢視 commits 是否已含在 archive squash;若是 → wt-helper cleanup <slug> |
mergedToMain: false + openspec/changes/<slug>/ 仍 active + daysOld > 7 | active-stale | merge-back-or-resume — 依 mergeBackSafety 分流(landable → 直接 merge-back;ptb-* → Step 2B.4.5) |
mergedToMain: false + change 仍 active + daysOld <= 7 | active-fresh | keep — 在用中;若需 land 仍依 mergeBackSafety 分流 |
mergedToMain: false + openspec/changes/<slug>/ 跟 archive/<slug>/ 都不在 | orphan | verify-then-cleanup — 孤兒 worktree,git log <branch> 檢視內容再決定 cleanup |
script 已額外掃 git worktree list --porcelain:linked worktree 不在 wt-helper list 結果裡(即不在 ~/offline/<consumer>-wt/<slug>/ 規約路徑)→ 列進 raw.unmanagedWorktrees,對應 check 標 n/a → manual review(非規約 worktree,user 自管,audit 只記不建議動)。
audit 寫進 HANDOFF.md 時每條 wt 後綴 (mergeBackSafety: <landable|ptb-recoverable|ptb-unsafe>, blockers=N, uncommitted=K, baselineRef=<yes|no>),讓下次 /handoff 不用重跑 signal 就看得到 ground truth。
3.2 Stash audit
讀同一次 handoff-scan 輸出的 worktreeStash.raw.stashes[](script 內部代跑 stash-reconcile.mjs --include-all --json,並對 .spectra/stash-meta-*.json sidecar 做雙向比對)。
對 raw.stashes[*] 每一筆寫入 audit 段(不過濾 archived-only 或 stale>7d;user 要求「所有 stash 都有狀況與下一步建議」):
- ref(
stash@{N})
- kind(無 namespace 時為
unknown)
- slug(無則
(unknown))
- 下一步建議(
action — apply / view-diff / drop / manual review;apply / drop 的 check 標 warn,其餘標 n/a needs-judgment)
- 理由(
reason;無 sidecar 的 stash detail 已標 owner unknown)
raw.orphanSidecars[*](sidecar 在、stash 不在 = stale metadata)也逐筆寫入 stash 子節(check 已標 warn + 可刪指令)。
若 raw.stashes 為空,audit 段 stash 子節寫 No stashes.(仍保留節標題)。
3.3 寫入 HANDOFF.md
寫到 $MAIN_WT_PATH/HANDOFF.md ## Worktree & Stash Audit 段(不存在就建)。每跑一次 audit 整段覆寫(不是 append,避免重複累積)。格式:
## Worktree & Stash Audit
_Updated: <YYYY-MM-DD>_
### Worktrees (N)
- `<slug>` (`<branch>`) — **<kind>** — <下一步建議>
- `<path>` (last activity <Nd> ago)
若 0 條:`No linked worktrees.`
### Stashes (M)
- `stash@{0}` (`<kind>`, slug=`<slug>`) — **<action>** — <reason>
若 0 條:`No stashes.`
3.4 禁止行為
- ❌ 自動跑
git stash drop / git worktree remove / wt-helper cleanup / wt-helper merge-back —— Step 3 只寫 audit 段,user 自行抉擇是否動作(可跑 stash-reconcile --interactive 或 wt-helper cleanup <slug>)
- ❌ 把 audit 條目改寫進
## In Progress / ## Blocked 段 —— audit 是「待清紀錄」,不是 in-progress 工作
- ❌ Mode A 跑時在 chat 訊息輸出 audit 全文或摘要 —— 完全靜默寫入 HANDOFF.md(避免雜訊干擾交接收尾)
- ❌ 偵測到無 worktree + 無 stash 就跳過整段 —— 仍要寫「## Worktree & Stash Audit」段,內含
No linked worktrees. + No stashes.,讓接手 session 能確認 audit 已跑過、結果為空
Output contract
- Mode A:成功 = HANDOFF.md / tech-debt / ROADMAP 有對應寫入 + tasks 檔已清 + Step 3 audit 已靜默寫入 HANDOFF.md
## Worktree & Stash Audit 段;訊息只含升級摘要(不含 audit)
- Mode B:成功 = 2B.0 pitfall sweep 已執行(dispatch
/oops 或宣告「無 missed lesson」)+ HANDOFF.md 已整理 + 2B.1.5 → Step 3 audit 已寫入並在訊息摘要一行 + 2B.1.7 scan 抓到的 applyBlocked / awaitingUserDecision change 已走 2B.2.5 主動 triage(抽 blocker 原因 + 辨識 startable 子集 + 端出具體 user 決策,NEVER silently drop)+ 2B.1.8 tech-debt hygiene 已讀(staleOpen 排進 outstanding 最高優先 + aging 排第二優先並主動追問 blocker + closedBloat 達門檻時走 request_user_input rotate 拍板)+ 盤點訊息 + request_user_input 已發出讓 user 選 + user 選定後 2B.5 dispatch 已完成(直接 dispatch 或內呼 /wt <slug>: /<next-skill> <change-name>)
- 失敗 / blocked:明確說明卡點,不假裝完成
與其他 skill 的銜接
/spectra-commit — Mode A 升級 spectra change WIP 時,commit 用此 skill 走 selective stage
/spectra-propose — Mode A「規模膨脹」分類升級時,後續開新 change 入口
/spectra-apply — Mode B request_user_input user 選定起手 active change 後的執行入口
/oops — Mode B 2B.0 sweep missed lessons 時的 dispatch 目標(pitfall / memory / lessons.md 三層分流;from hub-maintenance-full plugin,不在 starter consumer 內安裝)
subagent-dev — Mode B request_user_input user 選 parallel 後,subagent fan-out 由此 skill 執行