---
name: dep-upgrade
description: 統一 dep 升級入口 — 依輸入分兩個 mode。(1) 在 consumer cwd 無參數觸發 → § Outdated batch mode：跑 pnpm outdated 後逐 pkg 派 codex 在 worktree 升版 + 驗證 + commit + 失敗升 high research。(2) 在 clade home 給 release URL / `<pkg>@<ver>` / 純 pkg name → § Fleet mode：跨 consumer 對單一 pkg sweep，自動 fetch + 解析 BC、用 dep-fleet-scan 掃命中 consumer、用 codebase-memory-mcp 定位 callsites、並行 fan-out 長駐 subagent。Use when 使用者說「升 deps」、「pnpm outdated 處理」、「處理過期套件」、「跟上 release」、「跨 consumer 升 X」、「全部 consumer 升 @xxx」、「無腦升 @xxx」、「升級套件」。**不適用於**單套件 ad-hoc 升（`pnpm add <pkg>@latest` 更快）、framework major migration（Nuxt 3→4 / Next 14→15 需專屬 plan）。Fleet mode **MUST run from clade home (`~/offline/clade`)**，受 [[clade-role-and-todo-discipline]] § upstream-driven dep migration carve-out 約束。
---
<!-- 🔒 LOCKED — managed by clade · auto-generated by sync-to-agents; edit source in .claude/ then re-run sync -->

# dep-upgrade — 統一 dep 升級入口

兩種升級需求一個 skill 涵蓋：

- **Outdated batch**：一個 consumer 累積很久沒升、`pnpm outdated` 一坨要批升
- **Fleet sweep**：看到 upstream release、想跨 consumer 跟上同一個版本

skill 開頭依輸入分流，**不要記兩個 skill 名**。

## Step 0 — Mode dispatcher（最先讀）

依**輸入**跟 **cwd** 分流：

| 觸發 | cwd 預期 | Mode | 跳到 |
| --- | --- | --- | --- |
| 純命令 `/dep-upgrade`（無參數） | consumer root（有 `package.json` + lockfile） | **Outdated batch** | § Outdated · Step O.1 |
| 給 GitHub release URL（`https://github.com/.../releases/tag/v<ver>`） | clade home | **Fleet** | § Fleet · Step F.1 |
| 給 `<pkg>@<ver>` / `<pkg> v<ver>` / `<pkg> <ver>` | clade home | **Fleet** | § Fleet · Step F.1 |
| 給純 pkg name（「升 @nuxt/ui」、「無腦升 X」） | clade home | **Fleet + Discovery** | § Fleet · Step F.1 |
| 無參數但 cwd = clade home | — | STOP + 問意圖 | 見下方 § Disambiguation |

### Disambiguation（cwd 與 input 不對）

- 在 consumer 給 pkg name → 問「你想 (A) 只對這個 consumer 升 (走 Outdated batch 但鎖單套件)、還是 (B) 跨 6 consumer sweep (要 `cd ~/offline/clade` 再跑)？」
- 在 clade 無參數 → 問「你想 (A) sweep 哪個 pkg？或 (B) 你其實要進某個 consumer 跑 `pnpm outdated`？」
- 在 worktree（cwd 含 `-wt/`） + 無參數 → 視為已在 Outdated batch 中段，跳 Step O.0、直接續跑 Step O.2

**MUST 等 user 拍板**，**NEVER** 主線自選 mode。

## 共用基礎（兩個 mode 都用到）

| 基礎 | 出處 |
| --- | --- |
| Worktree gate | [[worktree-default]] §1，wt-helper 開 / merge-back |
| Codex 派工模板 | `vendor/snippets/codex-upgrade-prompts/{medium,high}.md`（authoring source），SKILL.md § Codex prompt templates 是 plugin cache 副本 |
| Codex watch protocol | [[agent-routing.codex-watch-protocol]] |
| Selective stage on main | 一律 `git add package.json <lockfile>` + 額外指定檔，**NEVER** `git add -A` |
| Commit msg（commitlint-aware） | subagent / codex 端讀 consumer commitlint config 後生 compliant msg |

---

# § Outdated mode — Single consumer, multi-pkg batch

把「升 deps」這個機械活全程委派給 codex 跑：主線只負責 plan / dispatch / watch / 摘要，**不**自己改 `package.json`、不自己跑 `pnpm install`。每個 package 一個 codex 派工、一個 commit，失敗自動升 reasoning effort + 加上 web search / GitHub Issues 研究。

## 何時用 / 不適用（Outdated mode）

**適用**：
- `pnpm outdated` / `npm outdated` 跑出一堆 patch + minor + major，想逐套件穩穩升
- 不確定哪些套件會 break，想要明確的 per-package commit boundary 方便 bisect
- 大版號跳躍想讓 codex 自己去查 release notes / migration guide

**不適用**：
- 單一套件、單行升版（`pnpm add foo@latest` 自己跑更快）
- 整個 framework migration（Nuxt / Next / React major bump 需要專屬的 migration plan，不是逐套件 loop 能處理）
- monorepo 跨 workspace catalog 升版（先用 [[evlog-catalogs]] 派工方式處理 catalog）

**Changelog-aware sub-mode**：兩種觸發路徑：
- **Outdated pre-scan**（Step O.1.5）：主線用 `dep-fleet-discover.mjs` + `gh release view` 拿 changelog → 分類為 `bugfix` / `adaptation` / `feature` → 依分類決定 codex prompt 是否帶 `<changelog-block>`。
- **Fleet brief**：被 § Fleet mode subagent 呼叫時，跳過 Step O.1（target / version 由 fleet brief 指定）、Step O.2.1 的 prompt 內嵌 BC clauses + callsites。詳見 § Codex prompt templates · Changelog-block 填充。

## Step O.0 — Worktree gate（[[worktree-default]] §1）

升 deps 會改 tracked code（`package.json` / lockfile / 必要時 source code），**MUST** 在 session worktree 內跑，不在 main working tree 直接動。

**進入 worktree 的兩條路**：

1. 主線目前已在 worktree（cwd 名含 `-wt/`）→ 跳過 Step O.0、繼續 Step O.1
2. 主線在 main → 跑 `/wt upgrade-deps-<YYYYMMDD>` ad-hoc Form-1（不對應 spectra change）。`wt-helper add` 會走 `--baseline-strategy stash` 把 main dirty 保留，fork 出 worktree 後主線 `cd` 進去

**禁止**直接在 main working tree 跑這個 mode — 升爆掉一條 package 整個 main 都會卡，bisect / rollback 成本爆增。

## Step O.1 — Detect PM + 收 outdated + 讀 deps 分類

1. **偵測 package manager**（看 lockfile）：

   ```bash
   ls -1 pnpm-lock.yaml package-lock.json yarn.lock bun.lockb 2>/dev/null | head -1
   ```

   找到的對應 `pnpm` / `npm` / `yarn` / `bun`。**沒有 lockfile** → STOP，回覆「找不到 lockfile，無法判定 package manager」。

2. **拉 outdated**：

   ```bash
   pnpm outdated           # 或 npm outdated / yarn outdated
   ```

   > pnpm `--format=json` 在 monorepo workspace 會回多行 NDJSON 不是純 JSON，直接讀人類格式 + 主線自己 parse 比較穩。

3. **讀 package.json 把每個 outdated 套件分類為 deps / devDeps**（**P0，下 prompt 必用**）：

   ```bash
   python3 -c "
   import json
   p = json.load(open('package.json'))
   for pkg in <outdated_pkg_list>:
       if pkg in p.get('dependencies', {}): print(f'{pkg}  dep')
       elif pkg in p.get('devDependencies', {}): print(f'{pkg}  devDep')
   "
   ```

   後續 prompt builder 依此結果決定 `pnpm add <pkg>` 還是 `pnpm add -D <pkg>` — **NEVER** 預設 `-D`，否則會把 dependencies 套件靜默搬到 devDependencies（實證踩坑：TDMS 第一次 run wrangler 被誤搬，commit 後才發現）。

4. **分類版號差距**（從低風險到高風險升）：
   - **patch**（`1.2.3 → 1.2.4`）：通常安全
   - **minor**（`1.2.3 → 1.3.0`）：should be safe 但偶爾有 regression
   - **major**（`1.x → 2.x`）：高機率 breaking change

5. **`@types/*` 特殊封頂（MUST 在分類後立刻套用）**：

   `@types/<lib>` 的 major 版號**跟著對應 runtime / lib 版本走**，不是越新越好。**MUST** 把每個 `@types/*` 套件的 target version 上限封頂為 project 當前對應版本的 major：
   - `@types/node` → ≤ project Node major（讀 `.nvmrc` / `.tool-versions` / `package.json` `engines.node` / `Dockerfile` 任一可靠來源；都查不到才問 user）
   - `@types/react` → ≤ `dependencies.react` 的 major
   - 其餘 `@types/<lib>` 同理跟 `<lib>` 自身 major

   若 `outdated` 給的 Latest 超過上限，target 改成「上限 major 內最新」（例：Node 20 環境 + `@types/node` Latest 是 22.x → target 鎖 `^20.x` 最新 minor/patch，**不**升 22）。Step O.1.5.5 回報給 user 的計畫表 **MUST** 在這些 `@types/*` 條目後標註「(capped to Node N / react N)」讓 user 看到封頂邏輯有作用。

   **NEVER** 為了「升到最新」把 `@types/*` 推超過對應 runtime / lib — 型別會漂移：typecheck 用的是 newer types，但 runtime 跑的是舊版 API，bug 表現是「TS 說沒問題、prod 卻炸」這種最難 debug 的型別。

6. **進入 Step O.1.5 Changelog pre-scan**（見下方）。pre-scan 完成後才回報增強版計畫給使用者。

## Step O.1.5 — Changelog pre-scan（主線直接做）

在 O.1 收完 outdated 清單 + deps 分類 + 版號差距 + `@types/*` 封頂後，**升版前**先理解每個 package 的 release 性質，依此決定升版策略。主線直接完成（不派 codex），零額外 overhead。

### O.1.5.1 — Batch discover release URLs

對每個 outdated package，跑 `dep-fleet-discover.mjs` 拿 release URL：

```bash
node ~/offline/clade/vendor/scripts/dep-fleet-discover.mjs --pkg "<pkg>" --version "<to>"
```

- 可並行跑（獨立、唯讀）— 多個 Bash tool call 同一 message
- 記錄每個 package 的 `release_url` + `source` + `repo_url`
- `source: "none"` 的 package 標記分類為 `unknown`，skip O.1.5.2–O.1.5.4
- **Skip `@types/*`**：型別定義的 changelog 對分類無意義 → 直接標 `bugfix`

### O.1.5.2 — Batch fetch release bodies

對 `source != "none"` 的 package，fetch release body：

```bash
gh release view v<to> --repo <owner>/<repo> --json body -q .body > /tmp/dep-prescan-<pkg-slug>.md
```

- `gh` 失敗（private repo / rate limit / 無 release）→ 若 `source: "changelog_md"`，用 WebFetch 拿 changelog URL → 否則標 `unknown`
- 寫出的檔可在 O.2.1 codex prompt builder 復用

### O.1.5.3 — 主線分類（三類 + 混合）

主線讀所有 fetch 到的 release body，對每個 package 判定分類標籤：

| 訊號 | 標籤 |
| --- | --- |
| release body 全是 bug fix / patch notes / perf / internal refactor，無 user-facing API 變更 | `bugfix` |
| 有 breaking changes / deprecations / renamed APIs / 移除的 API / signature 變更 / config schema 變更 | `adaptation` |
| 有重大新 feature（新 component / 新 API / 新 capability），consumer 可能受益 | `feature` |
| changelog 不可得（O.1.5.1 `source: "none"` 或 O.1.5.2 fetch 失敗） | `unknown` |

**混合判定**（一個 release 可有多個標籤）：
- BC + feature → 同時標 `adaptation` + `feature`（升版時修 BC，完成後 HANDOFF 記 feature）
- 只有 deprecation（尚未 remove）→ `adaptation`（趁這次改掉，不等到 remove 才爆）
- 小 feature（如新增一個 option、default off、不影響現有行為）→ 仍歸 `bugfix`（不值得 HANDOFF entry）
- 重大 feature 但無 BC → 純 `feature`（升版走 bugfix 路徑，HANDOFF 記 feature）

**`adaptation` 時提取 BC 結構化資料**（復用 Fleet mode F.2.2 schema）：

```jsonc
{
  "breaking_changes": [
    {
      "category": "rename | removal | signature | config-schema | peer-bump",
      "description": "<一句話人話>",
      "affected_apis": ["<symbol>"],
      "before": "<code snippet>",
      "after": "<code snippet>"
    }
  ],
  "deprecations": [{ "api": "<name>", "replacement": "<name>" }]
}
```

- `affected_apis` 必須是**可搜尋符號**（函式名 / component 名 / config key），不要寫人話
- `before` / `after` 是可貼上的 code snippet，給 codex 看

**`feature` 時提取 feature 摘要**：

```jsonc
{
  "notable_features": [
    { "description": "<feature 描述>", "opt_in": true }
  ]
}
```

### O.1.5.4 — Callsite quick-scan（`adaptation` only）

對標記 `adaptation` 的 package，用 `rg` 快掃每個 BC 的 `affected_apis`：

```bash
rg -n "<symbol>" --type-add 'vue:*.vue' --type vue --type ts .
```

- 記錄 `file:line` 組合，後續 O.2.1 填入 `<changelog-block>` 的 callsites 段
- 過濾 docs-only 命中（`.md` / `README` / `docs/`）— 不是 runtime callsite
- 比 Fleet mode 的 codebase-memory-mcp 輕量（單 consumer、不需 index）
- **callsite = 0**：不刪該 BC（仍可能 transitive 影響），prompt 內提示 `callsites: 0（codex 自行 grep 確認）`

### O.1.5.5 — 增強版計畫回報

```
偵測到 <PM>，outdated 共 <N> 個（changelog pre-scan 完成）：

### 🟢 Bug fix only — <a> 個（順升）

| Package | from → to | 摘要 |
| --- | --- | --- |

### 🟡 需要適配 — <b> 個（有 BC / deprecation）

| Package | from → to | BC 摘要 | callsite 數 |
| --- | --- | --- | --- |

### 🔵 有新 Feature — <c> 個（升版 + HANDOFF 記 feature 適配計畫）

| Package | from → to | Feature 摘要 |
| --- | --- | --- |

### ⚪ Changelog 不可得 — <d> 個（走現有 medium → high fallback）

| Package | from → to |
| --- | --- |

建議升級順序：🟢 → ⚪ → 🟡 → 🔵（先簡單的、再需要適配的、最後有 feature 的）

要排除任何套件不升嗎？要調整任何分類嗎？
```

使用者可：
- 指定排除清單（如 `vue` lockstep with `nuxt`，先不動）
- 調整分類（如「bar 不用 adaptation，降成 bugfix」）
- **MUST 等使用者確認**才進 Step O.2 — 不要自決定

> Smoke test 建議：第一次跑這個 mode on 一個新 consumer 時，先挑 1 個最低風險 🟢 patch 跑 smoke，驗證 worktree fork + codex dispatch + commit boundary 都對，再批次推剩下的。

## Step O.2 — 逐套件 codex 派工 loop（category-aware）

每個 package 走以下子流程，**一個 package 一個 codex 派工**、**一個 commit**。Step O.1.5 的分類決定 prompt 填充策略：

| 分類 | `<changelog-block>` | `<plan-first-block>` | 工作範圍 | 驗證 |
| --- | --- | --- | --- | --- |
| `bugfix` | 空（現有行為） | patch 空 / minor 填 | `package.json` + lockfile only | typecheck（patch）/ +build（minor） |
| `adaptation` | **填入** BC + callsites（復用 Fleet 的 changelog-block 格式） | 必填 | `package.json` + lockfile + callsite 檔 | typecheck + build + 相關 test |
| `feature`（無 BC） | 填入 feature 摘要（informational） | 依版號差距 | `package.json` + lockfile only | 同 bugfix |
| `feature` + `adaptation` | **填入** BC + callsites + feature 摘要 | 必填 | 同 adaptation | 同 adaptation |
| `unknown` | 空（現有行為） | 依版號差距 | `package.json` + lockfile | 同 bugfix |

### O.2.1 寫 prompt 到 `/tmp/codex-upgrade-<pkg>-prompt.md`

用 § Codex prompt templates · § A medium 模板。**MUST** 內含：
- `[DELEGATED-BY-CLAUDE-CODE]` marker（第一行，per [[agent-routing.codex-watch-protocol]] § Runtime Gate）
- 目標 package 名 + current version → target version + **正確的 install flag**
- Git Baseline 段（per codex-watch-protocol § Git Baseline；列當前 worktree 內所有 main fork 過來的 in-flight 變更 path，**不要列死**——每個 consumer / 每次 fork 都不同，主線跑 `git status --porcelain` 動態抓）
- Commit Authorization 段（per codex-watch-protocol § Commit Authorization；message format `🧹 chore: wt upgrade-<pkg>-<from>→<to>`，subagent 端需讀 commitlint config 調整）
- 失敗時的回報格式

**`<changelog-block>` 填充**（`adaptation` / `feature+adaptation` 才填，其他留空）：完全復用 § Codex prompt templates · Changelog-block 填充格式，callsites 來源為 O.1.5.4 的 `rg` 結果（而非 Fleet mode 的 codebase-memory-mcp）。`adaptation` 的 codex 工作範圍擴大到 callsite 檔：Commit Authorization 加 `git add <callsite-files>`。

**`feature`（無 BC）的 `<changelog-block>` 填充**：只含 feature 摘要段（informational），**不**含 callsites 或「動手範圍」段 — 告知 codex 這個版本有新功能但升版只需 bump，不必改 source code。

**Plan-first 條件化**（DRY + 降 token）：
- `bugfix` + patch：**MAY 省略** Plan-first 硬指令，prompt 直接列「install → typecheck → commit」固定三步
- `bugfix` + minor / `unknown` + minor：**MUST** 加 Plan-first
- `adaptation` / `feature+adaptation` / major（任何分類）：**MUST** 加 Plan-first

**驗證步驟**（依分類 + 升版類型，取嚴格者）：
- `bugfix` + patch：`pnpm install`（隱式跑） + `pnpm typecheck` 0 errors
- `bugfix` + minor / `unknown`：typecheck + 如有 build script 跑一次 + 相關 test
- `adaptation`（任何版號差距）：typecheck + build + 相關 test
- major（任何分類）：typecheck + build + 全 test + codex 自己決定要不要 smoke test

### O.2.2 Dispatch background bash（medium）

```bash
cd <worktree-path> && codex exec \
  --model gpt-5.5 \
  --dangerously-bypass-approvals-and-sandbox \
  --skip-git-repo-check \
  -c model_reasoning_effort=medium \
  < /tmp/codex-upgrade-<pkg>-prompt.md 2>&1
```

`run_in_background=true`、立刻 `ScheduleWakeup(180, ...)` 啟動 [[agent-routing.codex-watch-protocol]] § Codex Watch Protocol。

**Wakeup 雜訊接受限制**：ScheduleWakeup 沒有 cancel API；package 已完成 + task-notification 已到後，先前排的 wakeup 仍會觸發 1–2 次「請 watch jobId X」prompt。主線收到時做兩件事：(1) 用 jobId 對齊當前 package（已完成的 jobId 直接忽略）；(2) 不為已完成的 package 再次 reschedule。

### O.2.3 收到 `<task-notification status=completed>` 後判定

| 訊號 | 判定 | 下一步 |
| --- | --- | --- |
| `PHASE_RESULT: SUCCESS` + worktree 多了一個 `🧹 chore: wt upgrade-<pkg>-...` commit | 成功 | 記錄到摘要、進下一 package |
| `PHASE_RESULT: FAILURE` + codex 自報原因 | 失敗 → 進 O.2.4 升 high research | 不馬上問使用者；先讓 codex high 自己研究 |
| Plan section 缺 / commit message format 不符 / scope drift | codex 漏跑硬指令 | 不升 high；直接 request_user_input [重派 medium / 升 high / 跳過 / 中止] |
| `fetch failed` / sandbox 拒絕 / 互動 prompt 卡住 | 環境問題 | per watch protocol 「介入觸發」，request_user_input |

**絕不**在 O.2.3 替 codex 修檔（會破壞 per-package commit boundary）。要修就 reset worktree commit 後重派。

### O.2.4 升 high research（medium 失敗自動觸發）

寫 prompt 到 `/tmp/codex-upgrade-<pkg>-research-prompt.md`，用 § Codex prompt templates · § B high 模板。**MUST** 內含：

- `[DELEGATED-BY-CLAUDE-CODE]` marker
- Medium 派工的失敗 tail（≤ 50 行）+ codex 自報的失敗原因
- 明確指示走研究模式：先 **github** plugin 查 `<pkg>` repo 的 issues / releases / changelog，再 **agent-browser** / web search 查官方 migration guide
- 研究完才動手改檔
- 一樣的 Git Baseline / Commit Authorization 硬指令
- Commit message format `🧹 chore: wt upgrade-<pkg>-<from>→<to> (researched <issue-url-slug>)`

Dispatch（同 O.2.2 但 `model_reasoning_effort=high`）+ watch（high 跑得更久，wakeup 間隔仍守 ≤ 180s）。

### O.2.5 high 仍失敗 → request_user_input

```
<pkg> medium + high research 都失敗。

Medium 失敗原因：<一句話>
High research 失敗原因 + 已查到的線索：<一句話>
完整 codex 輸出在 /tmp/codex-upgrade-<pkg>-research-prompt.md

要怎麼處理？
[1] 跳過 <pkg>（記錄到摘要的 SKIPPED 區，繼續下個 package）
[2] 中止整個 upgrade loop（保留 worktree 供手動處理）
[3] 主線接手手動升（會破壞 per-package commit boundary，僅在套件很小時建議）
```

**禁止**主線自己決定跳過或中止 — 必須使用者選。

## Step O.3 — Auto-merge-back + selective stage（主線自主完成）

所有 package codex 派工跑完（成功 / 跳過 / 中止皆同）後，**MUST** 自主把 worktree merge-back 進 main，**NEVER** 把這步當「下一步」丟給 user 自己跑（per [[worktree-default]] §5「Skill-owned worktree lifecycle」）。

### O.3.1 主線 cd 回 main consumer root

```bash
MAIN_PATH=$(git worktree list --porcelain | head -1 | awk '{print $2}')
cd "$MAIN_PATH"
```

從此往後所有 O.3 命令都在 main 跑。

### O.3.2 Merge-back（含 baseline blocker 自動處理）

```bash
node scripts/wt-helper.mjs merge-back <slug> --auto-stash
```

| 訊號 | 處理 |
| --- | --- |
| `merge-back: <slug> absorbed into main` | 成功 → 進 O.3.3 |
| `merge-back: <slug> absorbed into main (blockers stashed as wt-merge-block/<slug>/<ISO>)` | 成功 + main 端有 stash 紀錄 → 進 O.3.3，O.3.4 摘要末段提醒 user 用 `node scripts/stash-reconcile.mjs --slug <slug> --interactive` 收尾 |
| `merge-back blocked: worktree '<wt-path>' has N uncommitted edit(s)` | Pre-fork baseline 殘留 → 進 O.3.2.a 自動清理 |
| `merge-back blocked: <N> file(s) in main's working tree would be overwritten` | 上面 `--auto-stash` 應已涵蓋；若仍出現是 race condition → STOP + 報 user |
| `error: merge conflict in <files>` / `pre-sync` / `squash` conflict | Worktree 保留，wt-helper 內部已 abort + 救 stash → STOP + 報 user（不主線自決） |

#### O.3.2.a Pre-fork baseline blocker 自動清理

```bash
WT_PATH="$(dirname "$MAIN_PATH")/$(basename "$MAIN_PATH")-wt/<slug>"
for path in <parsed-blocker-paths>; do
  git -C "$WT_PATH" checkout HEAD -- "$path"
done
node scripts/wt-helper.mjs merge-back <slug> --auto-stash
```

**安全性論證**：IDENTICAL baseline → data 在 main HEAD 還在；DIVERGED baseline → `refs/wt-baseline/<slug>/<ISO>` pinned ref 是安全網（per [[worktree-default]] §1）；merge-back 只搬「branch commits 的 diff」，main 上別 session 的 dirty 完全不會被踩。

**NEVER**：`git stash` 在 worktree 內收 baseline / `git add` baseline 進 worktree branch / 用 `--include-worktree-wip`。

#### O.3.2.b 真衝突（pre-sync / squash conflict）

wt-helper 內部已 abort + 救 stash + 保留 worktree。主線 STOP 整個流程 + request_user_input 三選項：手動解 conflict / 放棄 upgrade / user 自己看狀態。

### O.3.2.c Sync node_modules on main

merge-back 只搬 tracked 檔（`package.json` / lockfile / `pnpm-workspace.yaml`）；`node_modules/` 是 gitignored，main 端仍停留在升版前的舊狀態。**MUST** 跑 `pnpm install`（或對應 PM 的 install）同步，否則後續 `pnpm outdated` / typecheck / test 都看到 stale 版本。

```bash
<PM> install
```

這不違反「NEVER 主線自己跑 `pnpm install`」禁令 — 該禁令指的是升版階段（Step O.2）不該由主線做 `pnpm add`；這裡是 post-merge-back setup chore（per [[worktree-default]] §1.x 的 `pnpm install` 自動代勞清單）。

### O.3.3 Selective stage on main

```bash
git reset HEAD                                        # 清掉 index（並行 session 的 WIP 退回 unstaged）
git add package.json <lockfile-path>                  # 只 stage upgrade 真的動的檔
```

`<lockfile-path>` 對應 PM：`pnpm-lock.yaml` / `package-lock.json` / `yarn.lock` / `bun.lockb`。

**NEVER** `git add -A` / `git add .`。

**`adaptation` 額外 stage**：codex 有改 callsite 檔時（import path 重寫 / API rename），那些檔也在 squash 後落在 main working tree。**MUST** 也加進 `git add`，但 stage 前 `git status` 印給 user。

### O.3.5 — Feature HANDOFF entries（`feature` 分類 only）

對每個在 Step O.1.5 分類含 `feature` 標籤的 package，append entry 到 consumer 的 `HANDOFF.md`：

```markdown
## <pkg> <to> 新 feature 適配

**Discovered**: <YYYY-MM-DD>
**Source**: <release_url>
**Upgraded in**: dep-upgrade session <date>

新增功能：
- <feature description 1>
- <feature description 2>

建議適配方式：（待規劃）
```

- **MUST** 把 `HANDOFF.md` 加進 selective stage：`git add HANDOFF.md`
- 若 consumer 沒有 `HANDOFF.md` → skip（不替 consumer 建檔）
- O.3.6 摘要的 🔵 區段列出寫了哪些 HANDOFF entries

### O.3.6 摘要彙報

```markdown
## dep-upgrade · outdated 摘要（<YYYY-MM-DD HH:MM>）

**Worktree**：`<wt-path>` → ✅ absorbed into main + cleaned
**Branch**：`session/<date>-upgrade-deps-<slug>` → ✅ removed
**Package manager**：`<PM>`
**總計**：N 套件（M 成功、K 升 high 後成功、S 跳過、F 失敗）

### 🟢 Bug fix — 一次成功

| Package | <from> | <to> | Commit |
| --- | --- | --- | --- |

### 🟡 Adaptation — 成功套用 BC

| Package | <from> | <to> | BC 摘要 | callsite 改動 | Commit |
| --- | --- | --- | --- | --- | --- |

### 🔵 Feature — 已升版 + HANDOFF 已記

| Package | <from> | <to> | Feature 摘要 | HANDOFF entry |
| --- | --- | --- | --- | --- |

### ⚠️ 升 high research 後成功（K）

| Package | <from> | <to> | BC 摘要 | Commit |
| --- | --- | --- | --- | --- |

### ⏭️ 跳過（S）

| Package | <from> | <to> | 跳過原因 |
| --- | --- | --- | --- |

### 📦 Main 端狀態

- Staged：`package.json`、`<lockfile-path>`（+ callsite 檔 + `HANDOFF.md` 若有）
- Unstaged：並行 session 原有 WIP 保留
- 並行 session 的 staged WIP（若有）已退回 unstaged

### 下一步（user 拍板）

走 `/commit` 收尾。**NEVER** 主線自動跑 `/commit` — 留 commit 時機 / message / sign-off 給 user。
```

### 例外：user 在 Step O.0 之前明確說「不要 land」/「先看一下」

跳過 O.3.1–O.3.3，只跑 O.3.4 摘要 + 把「下一步」改寫成手動指令。判定詞例：「先別 merge-back」「保留 worktree」。**NEVER** 主動延遲 — 預設一律自動 land。

---

# § Fleet mode — Single pkg, multi-consumer sweep

把「user 貼出一個 release URL → 6 個 consumer 全部升版 + 套用 BC 修正 + 各自 commit」這個工作流全程編排化。Clade 主線負責 fetch / 解析 / 掃描 / 編排 / 聚合；每個命中 consumer 一個長駐 subagent 在自己 worktree 內呼叫 § Outdated mode 的 changelog-aware 子流程。

## 何時用 / 不適用（Fleet mode）

**適用**：
- 使用者貼出 GitHub release URL（例：`https://github.com/nuxt/ui/releases/tag/v4.8.0`），想跨 consumer 升版
- 想用同一個 changelog 在多個 consumer 套用同樣的 BC 修正（rename / removal / signature / config schema）
- 想要「全部 OK 才一起 land」的統一 push gate

**不適用**：
- **單 consumer** ad-hoc 升版（直接在該 consumer 走 § Outdated mode 或 `pnpm add` 更快）
- **Framework major migration**（Nuxt 3→4、Next 14→15）— 需專屬 migration plan
- **無 changelog / changelog 不公開**的內部套件升版
- 一次 sweep 想升**多個套件**（per [[clade-role-and-todo-discipline]] § carve-out「一個套件 × 一個 target version」）

## Step F.1 — Preflight clade-only + 解析觸發 input

### F.1.1 確認 cwd 是 clade home

```bash
pwd                                          # MUST end with /offline/clade
test -f registry/consumers.json -a -f consumers.local
```

- cwd 不是 clade home → STOP，回 user「Fleet mode 必須從 clade home 跑 (`cd ~/offline/clade`)，因為要讀 `consumers.local` + 跨 consumer 編排」
- `consumers.local` 缺 → STOP，回 user「`consumers.local` 不存在，請先建好本機 consumer 路徑清單再跑」

**為什麼 clade-only**：Fleet mode 對「**所有** consumer」做改動，需要 `consumers.local` 的 ground truth；在某個 consumer 端跑會把當前 consumer 自己也納入 sweep 範圍但又無法掃其他 consumer，編排錯亂。

### F.1.2 解析觸發 input

使用者可能用以下四種形式觸發：

| 觸發形式 | 解析方法 |
| --- | --- |
| GitHub release URL（`https://github.com/<owner>/<repo>/releases/tag/v<ver>`） | 直接記 release_url + 從 release notes 找 `pnpm add` / `npm install` 範例驗證 npm pkg name（repo name 跟 npm pkg 可能不同，例：`nuxt/ui` repo → `@nuxt/ui` pkg） |
| `<pkg>@<ver>` 形式（例：`@nuxt/ui@4.8.0`） | pkg + version 直接拆；release_url 從 npm registry discover（見下方） |
| `<pkg> v<ver>` / `<pkg> <ver>` 空白分隔（例：「升 @nuxt/ui v4.8.0」） | 同上 |
| **純套件名（無腦升級，例：「升 @nuxt/ui」、「全 consumer 升 @nuxt/ui 到最新」）** | discover latest + release URL（見下方） |

### F.1.3 Discovery — 無腦升級自動補欄位

當缺 `target_version` 或缺 `release_url` 時，跑：

```bash
node vendor/scripts/dep-fleet-discover.mjs --pkg "<pkg>" [--version <ver>]
```

輸出 JSON：

```json
{
  "pkg": "@nuxt/ui",
  "latest_version": "4.8.0",
  "target_version": "4.8.0",
  "release_url": "https://github.com/nuxt/ui/releases/tag/v4.8.0",
  "repo_url": "https://github.com/nuxt/ui",
  "homepage": "https://ui.nuxt.com",
  "source": "github_release"
}
```

Discovery 內部 fallback chain：

1. `npm view <pkg> dist-tags.latest` 拿 latest version（無 --version 時）
2. `npm view <pkg> repository.url` 拿 repo URL
3. 試 `https://github.com/<owner>/<repo>/releases/tag/v<ver>` 200 OK
4. 失敗 → 試 `releases/tag/<pkg>@<ver>`（monorepo 慣例）
5. 仍失敗 → fallback raw `CHANGELOG.md`
6. 全失敗 → STOP，回 user「找不到 changelog，請手動貼 release URL」

**無腦升級觸發判定**：user 訊息含 pkg name 但沒帶 `@<ver>` / `v<ver>` / 數字版號 → 直接走 discovery，**NEVER** 反問「target 版本是？」（user 已經明說要升、且預期 latest）。

**MUST** 在進入 Step F.2 之前把以下五個值都拍板：`<pkg>` / `<target_version>` / `<release_url>` / `<pkg-slug>`（檔名安全字串，例：`@nuxt/ui` → `nuxt-ui`） / 觸發語境（user 原話）。

## Step F.2 — Fetch + 解析 changelog

### F.2.1 Fetch release notes

```bash
gh release view v<target_version> --repo <owner>/<repo> --json body,name,tagName,publishedAt > /tmp/dep-fleet-release-<pkg-slug>.json
```

`gh` 認證已配置時 quota 寬鬆；不行才退到 WebFetch。**禁止** scrape release notes 之外的東西（issues / discussions / PRs 是 Step F.5 high-research subagent 的工作）。

### F.2.2 LLM 解析 release notes → 結構化

把 release body 丟給 LLM 解析（這個 mode 本身就是 LLM 跑，直接讀檔做解析即可），輸出**統一格式**：

```jsonc
{
  "pkg": "@nuxt/ui",
  "to_version": "4.8.0",
  "release_url": "https://github.com/nuxt/ui/releases/tag/v4.8.0",
  "breaking_changes": [
    {
      "category": "rename | removal | signature | config-schema | peer-bump",
      "description": "<一句話人話>",
      "affected_apis": ["<symbol1>"],
      "before": "<code snippet>",
      "after": "<code snippet>"
    }
  ],
  "features": [{ "description": "...", "opt_in": true }],
  "deprecations": [{ "api": "<name>", "replacement": "<name>" }]
}
```

**解析守則**：

- `category` 必填、必須是上面列的五個之一；不確定就用 `signature`
- `affected_apis` 是要拿去 codebase-memory-mcp `search_graph` 的字串 — 必須是**真實的可搜尋符號**，不要寫人話
- `before` / `after` 給 codex 看的範例，**MUST** 是可貼上的 code snippet
- 找不到任何 BC → 全部 `breaking_changes: []`，跳到 Step F.3.5「無 BC fast path」

寫到 `/tmp/dep-fleet-changelog-<pkg-slug>.json`。

### F.2.3 Carve-out 條件自查

**MUST** 自查 [[clade-role-and-todo-discipline]] § upstream-driven dep migration 的 5 條允許條件：

- ✅ 每條 BC 在 release notes 找得到對應 clause
- ✅ Sweep 範圍只有「一個套件 × 一個 target version」
- ✅ Sweep 不夾帶 clade 自行發想的 refactor
- ✅ Sweep 不夾帶 unrelated 套件升版
- ✅ Worktree gate 由 § Outdated mode Step O.0 保證
- ✅ 若 sweep 會重寫 hit consumer `package.json` 的 `test` / `lint` / `typecheck` script（換工具 / 改命令）：原本有 `.clade/bin/clade-gate run <gate> --` 前綴的，**MUST** 只換 `--` 後面的內層命令、保留前綴（per [[test-scripts]] § MUST：重寫已包 clade-gate 的 script 時保留前綴）。主線在 land 前 `git -C <consumer-path> show <commit_sha> -- package.json` 抽查前綴沒被整行覆蓋

任一條不滿足 → STOP，回 user 解釋哪條沒過。

> **實證**：2026-06-09 一次 `migrate lint from oxlint to vp lint across consumers` sweep 對 6 consumer 整行覆蓋 lint script，把 clade-gate 前綴全弄掉（TD-152 5 天前才採用）→ instrumentation 退化。此條 + [[test-scripts]] 的 MUST 是補這個缺口。

## Step F.3 — Fleet scan 命中 consumer

```bash
node vendor/scripts/dep-fleet-scan.mjs --pkg "<pkg>" --target "<target_version>" > /tmp/dep-fleet-scan-<pkg-slug>.json
```

挑出 `found: true` 的當作 hit consumer 名單。

**Skip 條件**（hit 但不該 sweep 的 consumer）：

- `business_activity: "paused"` → skip + 列入 plan 顯示為「skipped: paused」
- `version_gap: "same"` → skip + 列入 plan 顯示為「already at target」
- `version_gap: "unknown"` 且 manifest 是 `catalog:` 但 catalog 解析不到 → request_user_input 問是否 skip

### F.3.x — 0-sweep fast path（無事可做就早早結束）

算完 `hit_consumers - skipped`：

- **= 0**：所有 hit consumer 都符合 skip 條件 → **跳過 Step F.4-F.8**，直接輸出「No-op summary」並結束：

  ```
  ## dep-upgrade · fleet no-op（<YYYY-MM-DD HH:MM>）

  Target: <pkg>@<target_version>
  Release: <release_url>
  Sweep 命中：0 個 consumer 需要動

  | consumer | manifest | skip reason |
  | --- | --- | --- |
  | <id> | <spec> | already at target | paused | catalog unresolved |

  無事可做。
  ```

  **NEVER** 在這條 fast path 跑 changelog 解析 / callsite scan / plan gate — 這些只在有 sweep 需求時才有意義。

- **≥ 1**：照原流程進 Step F.4 callsite 預掃。

**Idempotency 設計**：「無腦升級」連跑兩次第二次必走 0-sweep fast path（第一次升完所有 active consumer 都到 latest，第二次掃出 gap=same）。

## Step F.4 — Callsite 預掃（codebase-memory-mcp）

對每個 hit consumer × 每條 BC 的 `affected_apis[]`：

### F.4.1 確認 consumer 已 indexed

**MCP project name 必須 normalize**：codebase-memory-mcp 用 dash-separated 形式，**不是** absolute path。轉換規則：去掉開頭 `/`、把剩下的 `/` 換 `-`。

```
/Users/charles/offline/perno → Users-charles-offline-perno
/Users/charles/offline/nuxt-edge-agentic-rag → Users-charles-offline-nuxt-edge-agentic-rag
```

每個 consumer 查 index 狀態：

```
mcp__codebase-memory-mcp__index_status(project="Users-charles-offline-<consumer-basename>")
```

若 not indexed → 跑 `mcp__codebase-memory-mcp__index_repository(repo_path=<consumer_path>, mode="fast")`（這個 tool 收 absolute path，**輸出** 才是 normalize 後的 project name）。

### F.4.2 搜 callsite

對每個 `<symbol>` 跑（**project 用 normalize 後形式**）：

```
mcp__codebase-memory-mcp__search_graph(name_pattern=<symbol>, project="Users-charles-offline-<consumer-basename>", path_filter="^(app|server|components|composables|utils|pages|layouts)/")
mcp__codebase-memory-mcp__search_code(pattern=<symbol>, project="Users-charles-offline-<consumer-basename>", path_filter="\\.(vue|ts|tsx|js)$")
```

**過濾 docs-only 命中**：MCP 可能命中 `.agents/skills/<name>/references/*.md`、`docs/`、`README.md` 等檔。這些**不是 runtime callsite**，歸到「docs 提及」分類，不進 BC code mod 範圍。

**Fallback**：MCP 沒找到 → grep `<consumer_path>` 一次（`rg -n "<symbol>" --type vue --type ts <consumer_path>`）。

**Callsite 0 個的 BC**：不刪該條 BC（仍可能影響 transitive 用法），但 brief 內 callsites 為空、prompt 內提示「callsites: 0（請 codex 自行 grep 確認）」。

### F.4.3 寫 per-consumer brief 到 `/tmp/dep-fleet-brief-<pkg-slug>-<consumer-id>.json`

每個 hit consumer 一份 brief，schema：

```json
{
  "pkg": "@nuxt/ui",
  "from_version": "^4.7.1",
  "to_version": "4.8.0",
  "consumer_path": "/Users/charles/offline/perno",
  "consumer_id": "perno",
  "workflow_model": "trunk-based",
  "release_url": "https://github.com/nuxt/ui/releases/tag/v4.8.0",
  "field": "dependencies",
  "dep_or_devdep": "dep",
  "version_gap": "minor",
  "breaking_changes": [...],
  "features": [...],
  "deprecations": [...],
  "callsites": [{ "file": "app/components/Foo.vue", "line": 42, "symbol": "UInputMenu" }]
}
```

### F.4.5 無 BC fast path

若 Step F.2.2 拿到 `breaking_changes: []`：brief 仍寫，但 `breaking_changes` / `callsites` 都為空、subagent 端等同跑 Outdated mode Step O.2 minor / patch 流程。

## Step F.5 — 一次性 plan gate（request_user_input）

把 Step F.2-F.4 的結果整理成一張 plan table 給 user 拍板：

```
## dep-upgrade · fleet plan

Target: <pkg>@<target_version>
Release: <release_url>
BC clauses: <N> 條
Features: <M> 條
Deprecations: <K> 條

### Hit consumers (將 sweep)

| consumer | from | gap | manifest | callsites |
| --- | --- | --- | --- | --- |

### Skipped consumers

| consumer | reason |
| --- | --- |
```

request_user_input 提供四個選項：

```
[1] 全部 sweep（並行 fan-out N 個 subagent）
[2] 先挑一個試（指定 consumer_id 跑 smoke，回報後再決定剩下）
[3] 改 BC 解析（user 想增刪某條 BC clause）→ 回 Step F.2.2
[4] 中止
```

**MUST 等 user 選**，**NEVER** 主線自決定全部 sweep（即使 plan 看起來很安全）。

## Step F.6 — 並行 fan-out 長駐 subagent

per [[parallel-fanout]]，每個 hit consumer 一個**長駐**（name 參數）subagent，**thin brief**（3–5K）只包含：

- Brief JSON 檔路徑：`/tmp/dep-fleet-brief-<pkg-slug>-<consumer-id>.json`（subagent 自己 Read）
- Consumer worktree 還沒開時的指令：`cd <consumer_path> && node scripts/wt-helper.mjs add upgrade-<pkg-slug>-<YYYYMMDD> --baseline-strategy stash`
- 跑 § Outdated mode changelog-aware 子流程的指示
- 回報格式

### F.6.1 Subagent brief template

```markdown
# Task: 跑 dep-upgrade § Outdated mode changelog-aware 對 <consumer_id> 升級 <pkg>

你是 dep-upgrade orchestrator 派的長駐 subagent，負責 **<consumer_id>** 這一個 consumer 的升版。

## Brief

Brief JSON：`/tmp/dep-fleet-brief-<pkg-slug>-<consumer-id>.json`
請 Read 這個檔。

## 工作流程

1. `cd <consumer_path>`
2. 開 worktree：`node scripts/wt-helper.mjs add upgrade-<pkg-slug>-<YYYYMMDD> --baseline-strategy stash`
3. 跑 dep-upgrade § Outdated mode changelog-aware 子流程：
   - 讀 `~/offline/clade/plugins/hub-core/skills/dep-upgrade/SKILL.md` § Outdated mode + § Codex prompt templates
   - 跳過 Step O.1（target / version 由 brief 取）
   - 跑 Step O.2.1：用 § A medium 模板 + brief 內 BC 渲染 `<changelog-block>` + brief 內 callsites
   - 跑 Step O.2.2：codex dispatch（medium）
   - Watch codex per [[agent-routing.codex-watch-protocol]]
   - 失敗 → 升 high（用 § B 模板）
   - 高失敗 → 不要 request_user_input，直接 STOP + 回報 orchestrator
4. 跑 Step O.3 merge-back + selective stage（`package.json` + lockfile + callsite 改動檔）
5. **NEVER push、NEVER /commit**：worktree 已 merge-back 後 main 端 staged 狀態保留，回報 orchestrator
6. **產生 commit msg（commitlint-aware）**：
   - 先 read consumer 的 commitlint 設定（`commitlint.config.{js,ts,mjs,cjs}` / `.commitlintrc.*` / `package.json` 內 `commitlint`）
   - 偵測限制：`type-enum` 允許清單、自定 `subject-has-chinese` plugin、`body-max-line-length` / `header-max-length`
   - 生 commit msg 必 **同時通過** worktree branch 跟 consumer main 的 commit-msg hook
   - 範例：agentic-rag 用 `🧹 chore: 升級 @nuxt/ui ^4.7.0 → 4.8.0`；perno 可用 `⬆️ chore: upgrade @nuxt/ui ^4.7.1 → 4.8.0`
7. 回報 stdout 結尾：
   \`\`\`
   FLEET_SUBAGENT_RESULT: SUCCESS | PARTIAL | FAILURE
   CONSUMER_ID: <consumer_id>
   STAGED_FILES: <comma-separated paths>
   COMMIT_PLAN_MSG: <你在 step 6 生的、worktree 已驗證過 hook 的 msg verbatim>
   CODEX_FINDINGS: <codex high-research 找到的關鍵 URL / issue，若有>
   FAILURE_DETAIL: <若 FAILURE / PARTIAL，一段 ≤ 10 行的失敗描述>
   \`\`\`
   **MUST** `COMMIT_PLAN_MSG` 是你在 worktree 上 **真正用過** 的 msg（hook 已驗證），orchestrator 會 verbatim 用這條去 commit main。

## Git Baseline / 禁止

- worktree 內 main fork 過來的 in-flight 變更：**不要動**
- 工作範圍：`package.json` + lockfile + brief.callsites 列到的檔案
- ❌ `git push` / `git commit` on main / `/commit` skill
- ❌ 動 brief.callsites 範圍外的 source code
- ❌ request_user_input（subagent 不直接跟 user 對話）

## Long-running

orchestrator 會用 SendMessage 跟你續跑 phase。請保持 session 狀態、不要主動結束。
```

### F.6.2 同時送多個 spawn_agent 工具 call

**MUST** 一個 message 內塞所有 hit consumer 的 spawn_agent 工具 call（per [[parallel-fanout]]）；每個有獨立 `name`（建議：`fleet-<consumer-id>`）方便後續 SendMessage。

## Step F.7 — 主線 watch + 聚合

### F.7.1 收到 subagent 完成通知

每個 subagent 完成會送 `<task-notification>`。依 `FLEET_SUBAGENT_RESULT` 分流：

| 結果 | 處理 |
| --- | --- |
| SUCCESS | 記錄到 summary、worktree 已 merge-back、main staged |
| PARTIAL | 升版成功但有部分 callsite 沒套用 BC → 列入「需 user 確認」區 |
| FAILURE | 升版失敗 → 列入「未 land」區，worktree 保留供 user 手動處理 |

### F.7.2 等所有 subagent 完成

不要在中間 push 任何 consumer。**MUST** 等到全部 subagent 回報才進 Step F.8。

## Step F.8 — Fleet push gate + 摘要

### F.8.1 顯示聚合 plan

```
## dep-upgrade · fleet 聚合結果

| consumer | 狀態 | staged files | commit msg | wt path |
```

### F.8.2 request_user_input fleet push gate

```
[1] 全部 OK 的 consumer 一起 commit + push
[2] 我要先 review 一兩個 consumer 的 diff
[3] 只 push 某幾個 consumer（指定 consumer_id）
[4] 都先停下，我手動 review 後再決定（保留 staged 狀態）
```

**MUST 等 user 選**。**NEVER** 主線自決定全部 push。

### F.8.3 按 user 選擇執行

**Race 警示**：subagent 在 Step F.6 結束時把 upgrade staged-on-main，到 orchestrator 在 Step F.8 跑 commit 之間的時間窗，consumer 端**並行 session** 可能跑 `git add -A` / `git reset` 把 staged 區掃掉、或把 upgrade staged 內容夾帶進別人的 commit。**MUST** 在 commit 前 verify staged 集合：

```bash
# 預期 staged：subagent 回報的 STAGED_FILES
git -C <consumer_path> diff --cached --name-only > /tmp/dep-fleet-actual-staged-<consumer-id>.txt
diff <expected-staged-list> /tmp/dep-fleet-actual-staged-<consumer-id>.txt
```

- 若一致 → 正常 commit + push
- 若 staged 被擴大（多了不是 subagent 動的檔）→ STOP 該 consumer，列入摘要 ⚠️ 區「staged 被並行 session 擴大」+ 不 push
- 若 staged 被縮小或消失 → 查 `git log -p package.json` 確認升版是否已落地：已落地 → 列入摘要 ✅ Pushed 區，標記「by-other-session race」；未落地 → ❌ Failed 區、需重跑

**選 [1]**：對每個 SUCCESS consumer：

```bash
cd <consumer_path>
git status --short                                      # race verify
git commit -m "<COMMIT_PLAN_MSG>"                        # **MUST** 用 subagent 回報的 msg verbatim
git push origin <current_branch>                         # trunk-based 都是 push main
```

- **選 [2]/[4]**：列出 `git diff --staged` 指令給 user，**NEVER** 替 user 跑 commit
- **選 [3]**：對指定 consumer_id 跑 commit + push，其餘留 staged

### F.8.4 摘要彙報

```markdown
## dep-upgrade · fleet 摘要（<YYYY-MM-DD HH:MM>）

**Sweep**：`<pkg>` `<from>` → `<target_version>`
**Release**：<release_url>
**BC clauses**：<N> 條套用、<M> 條無 callsite

### ✅ Pushed (P) / ⏸️ Staged but not pushed (S) / ❌ Failed (F) / ⏭️ Skipped (K)

[tables...]
```

---

# § Codex prompt templates（兩 mode 共享）

> **Authoring source**：`~/offline/clade/vendor/snippets/codex-upgrade-prompts/{medium,high}.md`（clade-only，不散播）。下方 § A § B inline 是 plugin cache 副本，**改其中一處時兩邊都要同步**。未來會由 TD-129 dispatch script 機械化渲染。

## § A — Medium 派工 prompt（per-package）

主線 / subagent 在生 prompt 時用 substitution：
- `<pkg>` / `<from>` / `<to>` / `<wt-path>` / `<branch>` / `<PM>` / `<lockfile>`：每個 package 不同
- `<install-flag>`：依 deps/devDeps 偵測（Outdated mode Step O.1.3）或 brief 的 `dep_or_devdep`（Fleet mode），`dep` 用空字串、`devDep` 用 `-D`
- `<baseline-paths>`：跑 `cd <wt-path> && git status --porcelain` 動態抓 unstaged + untracked
- `<plan-first-block>`：patch 升版時填空字串、minor/major 時填下方 Plan-first 區段
- `<verification-steps>`：依升版類型填 typecheck（patch）/ typecheck + build（minor）/ typecheck + build + test（major）
- `<changelog-block>`：**changelog-aware mode 才填**（Fleet mode 從 brief 渲染），非 changelog 模式留空白

模板：

```markdown
[DELEGATED-BY-CLAUDE-CODE]

# Task: 升級 <pkg> 從 <from> 到 <to>

你在 worktree `<wt-path>`（branch `<branch>`）跑。Package manager 是 `<PM>`。

<changelog-block>

<plan-first-block>

## Git Baseline

worktree 內這些 path 是 main fork 過來的 in-flight 變更，**不要動**：

<baseline-paths>

你的工作範圍**只動**：`package.json` + `<lockfile>`。

## 升版步驟

1. `<PM> add <install-flag> <pkg>@<to>`
<verification-steps>
N. 全綠後 commit

## Commit Authorization

**允許**：
- Selective stage：`git add package.json <lockfile>`
- Commit：`git commit -m "🧹 chore: wt upgrade-<pkg>-<from>→<to>"`（emoji-conventional commitlint 合規）

**禁止**：
- `git add -A` / `git add .`
- `--no-verify`（per `rules/core/commit.md` hard rule）
- `git push` / `git stash` / `git commit --amend`
- 修改 view 層檔（`.vue` / `.tsx` / `.jsx` / `app/pages/` 等）— 升 deps 不該動 view
- 動 Git Baseline 列的 in-flight 檔案

## 回報格式（MUST，stdout 結尾輸出）

成功：
`​`​`
PHASE_RESULT: SUCCESS
COMMIT: <sha>
FILES_CHANGED: package.json, <lockfile>
VERIFICATION: <依驗證步驟回報>
`​`​`

失敗：
`​`​`
PHASE_RESULT: FAILURE
STAGE_FAILED: <install | typecheck | build | test>
ERROR_TAIL:
<≤ 30 行 error message>
HYPOTHESIS: <一句話猜為什麼炸>
SUGGESTED_NEXT: <要不要升 high research / 要查什麼 issue / changelog>
`​`​`

失敗時**不要**自己 commit、不要強過 fail、不要刪 / revert lockfile。
```

**`<plan-first-block>` 填充**（minor / major 才填，patch 留空）：

```markdown
## Plan-first（MUST）

在動任何 Edit / Write / Bash 寫入動作之前，先在 stdout 輸出 `## Plan` section：
- 預期要改的檔案
- 預期的驗證指令
- 預期影響範圍

Plan 寫完**立刻**繼續執行，不要等確認。
```

**`<verification-steps>` 填充**：

- Patch：`2. <PM> typecheck → 0 errors`
- Minor：`2. <PM> typecheck → 0 errors\n3. <PM> build → 成功（若有 build script）`
- Major：`2. <PM> typecheck → 0 errors\n3. <PM> build → 成功\n4. <PM> test 相關測試 → 全綠`

**`<changelog-block>` 填充**（Changelog-aware mode 才填，非 changelog 模式留空白）：

```markdown
## Changelog（orchestrator 預先研究，不用再 web search）

Release: <release_url>

### Breaking changes

- **<category>**: <description>
  Before: `<before>`
  After: `<after>`
  Affected APIs: <affected_apis joined>

### Callsites in this consumer（orchestrator 預先掃過）

- `<file>:<line>` 使用 `<symbol>`

### 動手範圍

除了 `package.json` + `<lockfile>` 之外，**可以**改上面 callsites 列到的檔案來套用 BC 修正。**NEVER** 改 callsites 清單外的其他 source code（即使「順手很合理」也不行 — 那是 unrelated refactor）。
```

## § B — High research 派工 prompt（escalation）

```markdown
[DELEGATED-BY-CLAUDE-CODE]

# Task: 升級 <pkg> 從 <from> 到 <to>（research mode）

Medium 已經失敗一次。失敗 tail：

\`\`\`
<medium-failure-tail>
\`\`\`

Codex 自報原因：<medium-hypothesis>

## 你的工作流程

**Phase R（Research，MUST 先做）**：
1. 用 **github** plugin 查 `<pkg>` 的 GitHub repo：
   - releases / tags / changelog → 找 `<from>` → `<to>` 之間的 breaking changes
   - issues 用關鍵字搜失敗的 error message
   - migration guide / upgrade guide pull request
2. 用 **agent-browser** 或 web search：
   - `<pkg> migration guide <to>` / `<pkg> breaking changes <to>`
   - 套件官方 docs site
3. 把研究結果濃縮成 `## Research Findings` section 輸出（≤ 20 行）

**Phase P（Plan，研究完才寫）**：

依 Research Findings 寫 `## Plan` section：要改哪些 source code 檔、預期驗證步驟、預期影響範圍。

**Phase I（Implement）**：

跟 medium 派工一樣（install → typecheck → build → test → commit），但 commit message 改成：

\`\`\`
🧹 chore: wt upgrade-<pkg>-<from>→<to> (researched <最關鍵的 issue/release URL slug>)
\`\`\`

## Git Baseline / Commit Authorization

同 medium 派工，不重述。

## 回報格式

成功時 stdout 結尾：
\`\`\`
PHASE_RESULT: SUCCESS
COMMIT: <sha>
RESEARCH_KEY_FINDINGS:
- <一行 breaking change 摘要>
RESEARCH_URLS:
- <release URL>
VERIFICATION: typecheck PASS, build PASS, test PASS
\`\`\`

失敗時：
\`\`\`
PHASE_RESULT: FAILURE
STAGE_FAILED: <stage>
ERROR_TAIL:
<≤ 30 行>
RESEARCH_FINDINGS_SO_FAR:
<線索>
WHY_STUCK: <一句話為什麼即使查到資訊也卡住>
\`\`\`
```

---

# 禁止事項（兩個 mode）

**通用**：

- **NEVER** 在 main working tree 跑（無 worktree gate）— 兩個 mode 都受此規約
- **NEVER** 主線自己改 `package.json` 或在升版階段（Step O.2）跑 `pnpm add` / `pnpm install`（升版全程委派給 codex / subagent）。**例外**：Step O.3.2.c post-merge-back `pnpm install` 是 setup chore，不是升版動作
- **NEVER** medium 失敗就直接問使用者 — 必須先自動升 high research
- **NEVER** high 也失敗就主線自己接手 — 必須 request_user_input 讓使用者選
- **NEVER** 把 merge-back 當「下一步」丟給 user 自己跑（per [[worktree-default]] §5）
- **NEVER** 把 `[DELEGATED-BY-CLAUDE-CODE]` marker 漏掉 — codex 端有 Runtime Gate 會擋
- **NEVER** 派 codex 時把 sandbox 換成 `read-only` / `workspace-write`（會擋 MCP）
- **NEVER** `git add -A` / `git add .` 在 main — 一律 selective stage

**Outdated mode 限定**：

- **NEVER** 一次派多 package 並行（破壞 per-package commit boundary，bisect 失效）
- **NEVER** 自動跑 `/commit` 收尾 — 留 commit 時機 / message / sign-off 給 user

**Fleet mode 限定**：

- **NEVER** 不在 clade home 跑（Step F.1.1 preflight）
- **NEVER** 一次 sweep 多個套件 / 多個 release（per carve-out「一個套件 × 一個 target version」）
- **NEVER** 在 BC 解析階段帶入 release notes 沒提到的修正（要找得到對應 clause 才能進 brief）
- **NEVER** subagent 直接 push 或 /commit — 集中 gate 在 Step F.8
- **NEVER** 跳過 Step F.5 user gate / Step F.8.2 fleet push gate（即使 plan / 聚合結果看起來很安全）
- **NEVER** 在 Step F.6 fan-out 後忘了等所有 subagent 完成就進 Step F.8
- **NEVER** 對 `business_activity: "paused"` 的 consumer 動手（即使 found: true）
- **NEVER** 主線替 subagent 改 worktree 內檔
- **NEVER** 把 unrelated 套件 / refactor / cleanup 搭便車進來（per carve-out 紅線）

# 相關規約

- [[clade-role-and-todo-discipline]] § upstream-driven dep migration — Fleet mode carve-out 條件
- [[worktree-default]] §1, §5 — worktree gate + skill-owned lifecycle
- [[parallel-fanout]] — Fleet mode 長駐 subagent + thin brief 規約
- [[agent-routing.codex-watch-protocol]] — codex 派工 + watch + Runtime Gate marker
- [[commit]] — Outdated mode main 端 selective stage 後的 `/commit` 收尾流程
