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.
Scanned 6/6/2026
Install via CLI
openskills install YuDefine/nuxt-supabase-starter---
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"
---
<!-- 🔒 LOCKED — managed by clade · auto-generated by sync-to-agents; edit source in .claude/ then re-run sync -->
# /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:
```bash
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
# linked worktree: .git/worktrees/<slug>/.. → main repo's .git dir
# so dirname(GIT_COMMON_DIR) 是 main worktree path(main 與 linked 都成立)
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、不做下一步推薦:
1. **盤點當前 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/<date>-*.md / spectra change / dirty file 就是當前 session 在動的,那才算當前 session 工作 —— 由對話脈絡決定歸屬,不是由檔案存在決定。
2. **逐項分類升級**(依 `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`) |
| 純放棄 | 直接刪 |
3. **寫入**:依分類 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>`)
4. **清理 session-tasks**:所有未完項升級完成後 → 只 `mv` / 刪「當前 session 自己開的」`tasks/<date>-*.md`(依 `rules/core/session-tasks.md`「NEVER 動別人的 tasks 檔」)。若當前 session 從頭到尾沒開 tasks 檔,跳過此步。
5. **Worktree & Stash audit**:跑 **Step 3 共用 audit block**(見下文)。Mode A 為「靜默寫入」—— audit 段寫進 HANDOFF.md,但**不**在 chat 訊息輸出 audit 全文或摘要(避免雜訊干擾當前 session 交接收尾)。
6. **回報**:一句話總結升級數量(如「升級 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
```bash
node ~/offline/clade/vendor/scripts/handoff-drift-scan.mjs --json 2>/dev/null
```
讀回的 JSON `handoffHealth` 欄位:
- `warnings` array 為空 → HANDOFF 健康,跳 2B.1c
- `warnings` 非空 → **MUST** 進 2B.1b
JSON 範例:
```json
{
"handoffHealth": {
"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 ..." }
]
}
}
```
#### 2B.1b Rotate plan(超標時必跑)
依 `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 路線執行時):
- Archive 檔開頭沿用 clade `docs/archives/` 既有 pattern:
```markdown
# <YYYY-MM> Handoff Narrative
> 來源:`HANDOFF.md`(rotate by /handoff Mode B 2B.1)
> 本檔保留已完成 dated session narrative,月 bucket append-only
```
- 同月 archive 已存在 → append 新 section(不重建檔頭)
- HANDOFF.md 同步刪除對應 section(**MUST** 在 Edit / Write 前 verify section title + startLine 對得上 audit 報的 stats)
#### 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)
跑 review-gui `--scan` 拿即時 bucket 資訊;outstanding 推薦(§2B.2 / §2B.3 / §2B.4 / §2B.5)**MUST** 引用 scan 結果而非從 `HANDOFF.md` 既有 narrative 或 `tasks.md` leaf count 推測 review-gui bucket 與 ready 狀態。
```bash
cd ~/offline/clade && node vendor/scripts/review-gui.mts --scan 2>/dev/null > "/tmp/review-gui-scan-$$.json"
```
把對應 consumer 的 active changes(filter `consumerId` = 當前 consumer)依 bucket 寫入 `$MAIN_WT_PATH/HANDOFF.md` 新段:
```markdown
## 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
- `healthCheckNeeded` → Pre-Review Data Readiness pattern 命中
- `malformed` → tasks.md 解析失敗
```
每跑一次 audit **整段覆寫**(不是 append)— scan 是 snapshot,stale audit content 應該被新 snapshot 替換。
**判定 review-gui readiness 的 SoT**:scan output `output.ready` / `output.notReady` 與 `output.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**:
| 失敗情境 | 處理 |
| --- | --- |
| clade home 不存在 / 不可達 | 寫 `## Review-gui Readiness` 段含 `_(scan unavailable: <reason>)_`,並警告主線「outstanding 推薦無 review:ui 即時資訊,請避免推薦 review:ui flow」 |
| `review-gui.mts` 報 error(type checked node version etc.) | 同上,把 stderr 前 5 行貼進該段 |
| scan 跑成功但回空 list | 寫 `_(scan returned 0 changes — repo possibly fresh)_` |
### 2B.2 盤點剩餘 outstanding
從以下來源蒐集 outstanding 工作:
- 整理後的 `HANDOFF.md`
- `docs/tech-debt.md` 未解決的 TD-NNN
- `openspec/ROADMAP.md` `## Next Moves`
- 任何已 archive 但留下 follow-up 註記的 change
每條 outstanding 抓三件資料:
- 標題(一句話)
- 涉及檔案 / module / consumer
- 依賴關係(依賴誰、誰依賴它)
### 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** 含 dry-run signal(blocker / uncommitted / baseline ref)— Step 3.1 audit 已記錄,照搬即可
- **NEVER** 推薦「review:ui」/「ready 區可點 OK」/「最快 deliverable 用 review:ui 收尾」相關 next move 而未先跑 §2B.1.7 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 |
## 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
```bash
node "$MAIN_WT_PATH/vendor/scripts/wt-helper.mjs" list --json 2>/dev/null
git -C "$MAIN_WT_PATH" worktree list --porcelain 2>/dev/null
```
#### 3.1a 每條 wt 跑 merge-back safety signal(v1.14+ hard rule)
對 wt-helper list 回的每條 `mergedToMain: false` worktree,**MUST** 跑 3 條 signal command 才能判定 kind:
```bash
# blocker count(dry-run merge-back 偵測 main blockers)
BLOCKERS=$(node "$MAIN_WT_PATH/vendor/scripts/wt-helper.mjs" merge-back <slug> --dry-run 2>&1 | grep -cE "blocked|conflict")
# uncommitted count(wt working tree + staged)
UNCOMMITTED=$(git -C <wt-path> status --porcelain 2>/dev/null | wc -l | tr -d ' ')
# pinned baseline ref present?
BASELINE_REF=$(git -C "$MAIN_WT_PATH" for-each-ref "refs/wt-baseline/<slug>/" --format='%(refname)' 2>/dev/null | head -1)
```
由這 3 條 signal 推導 `mergeBackSafety`:
| 條件 | mergeBackSafety | 對應動作 |
| --- | --- | --- |
| `BLOCKERS == 0` + `UNCOMMITTED == 0` | `landable` | safe to merge-back |
| `BLOCKERS > 0` 或 `UNCOMMITTED > 0`,且 `BASELINE_REF` 存在 | `ptb-recoverable` | merge-back / rescue path 都 OK(pinned ref 是救援保險絲) |
| `BLOCKERS > 0` 或 `UNCOMMITTED ≥ 100`,且 `BASELINE_REF` 不存在 | `ptb-unsafe` | **禁止 dispatch /spectra-archive**;走 Step 2B.4.5 PTB-unsafe 快速分流 |
#### 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 |
額外掃 `git worktree list --porcelain`:若有 linked worktree 不在 wt-helper list 結果裡(即不在 `~/offline/<consumer>-wt/<slug>/` 規約路徑),加 `unmanaged` 條目 → `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
```bash
node "$MAIN_WT_PATH/vendor/scripts/stash-reconcile.mjs" --include-all --json 2>/dev/null
```
對 `entries[*]` **每一筆**寫入 audit 段(不再過濾 archived-only 或 stale>7d;user 要求「所有 stash 都有狀況與下一步建議」):
- ref(`stash@{N}`)
- kind(`namespace.kind`,無 namespace 時為 `unknown`)
- slug(`namespace.slug` 或 `(unknown)`)
- 下一步建議(`recommendation.action` — `apply` / `view diff first` / `drop` / `manual review`)
- 理由(`recommendation.reason`)
若 `stash-reconcile` 回傳 `{ "entries": [] }`,audit 段 stash 子節寫 `No stashes.`(仍保留節標題)。
### 3.3 寫入 HANDOFF.md
寫到 `$MAIN_WT_PATH/HANDOFF.md` `## Worktree & Stash Audit` 段(不存在就建)。每跑一次 audit **整段覆寫**(不是 append,避免重複累積)。格式:
```markdown
## 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 已寫入並在訊息摘要一行 + 盤點訊息 + `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 執行
No comments yet. Be the first to comment!