# AI Review

> 把一份產出送給「另一個模型」做二審，消化意見後才寫最終回報。附單檔 POSIX shell 腳本（預設走 Codex CLI，可用 AI_REVIEW_CMD 換成任何後端）與 code／copy／research 三份 rubric；偵測不到後端時明講「本次僅自審」而不是靜默跳過，也不會中斷你的流程。當使用者說「送二審」「跨模型 review」「找另一個模型看一下」「二審」「ai-review」時觸發。⚠️ 送出去的內容就是給第三方模型看，憑證與個資自己把關。 English triggers: "second opinion", "cross-model review", "have another model review this", "ai review".

- Skill: `tingyulu/ai-review` (Agent Skill, multi-file: 6 files)
- Install (CLI): `npx skillmds@latest add tingyulu/ai-review`
- Raw SKILL.md: https://api.skillmd.com/api/skills/tingyulu/ai-review/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: AI & ML
- Author: tingyulu (https://skillmd.com/u/tingyulu)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/tingyulu/ai-review

---


# /ai-review — 跨模型二審

把剛做完的東西（程式碼、文案、研究報告）交給**另一個模型家族**看一眼，
把它的意見**消化進**你的最終回報 —— 而不是自己審自己。

> 🤖 R2-D2 時刻：R2 與 C-3PO 吵了六集，但每次都是那個「跟你看法不同的傢伙」
> 補上你漏掉的那一半。自己檢查自己，只會確認自己本來就相信的事。

## 為什麼需要

自審有結構性上限：**判準是自己定的，推理自洽就過關**。同一個模型跑「再檢查一次」，
多半只會把原本的結論換句話說 —— 它沒有理由懷疑自己的前提。

換一個模型家族來看，它不受你的推理路徑約束，會抓到你抓不到的東西：
被自己判成「顯然沒問題」的假設、沒標來源的宣稱、會反轉結論的漏洞。

這支 skill 是 [`damage-report`](../damage-report/SKILL.md) 的**升級路徑**：
damage-report 是自審五問，接上 ai-review 就變成**自審＋異質視角**。

## 動作

1. **決定送什麼**：要被審的那份東西（檔案，或用 `-` 讀 stdin）。
   🔒 送出去就是給第三方模型看 —— 憑證、金鑰、個資、客戶資料**自己判斷不要送**。
2. **選 rubric**：`code`（程式碼）／`copy`（對外文案）／`research`（研究與決策文件），
   或直接給你自己的 rubric 檔路徑。
3. **跑**：

   ```bash
   <本skill目錄>/scripts/ai-review.sh <檔案> --rubric code --context "這東西要解什麼"
   ```

   常用選項：`--model`／`--effort low|medium|high`／`--strict`／`--soft-fail`／`--no-save`。

   ⚠️ **一律用完整路徑呼叫，不要打裸的 `ai-review`** —— `ai-review` 是個很容易撞名的名字，
   你的 `PATH` 上可能已經有另一支同名執行檔（別的工具、你自己寫的、或這支 skill 的私人變體）。
   打裸命令名會叫到那一支，而且它可能**看起來也在做二審**，你不會發現叫錯了。
4. **看狀態**：除用法錯誤（exit 1，訊息在 stderr）外，stdout 最後一行一定是 `AI_REVIEW_STATUS: <狀態>`。
   - `ok` → 進第 5 步。
   - `skipped_*` → **本次沒有二審**（沒裝／沒登入）；腳本已在畫面上印好逐步引導，
     照著做或跳過都行。**回報時明講「本次僅自審」**，不要假裝審過。
   - `failed_*` → 後端出錯（額度／網路／政策／版本／空回覆），原始錯誤已印在畫面上。
5. **消化，不要轉述**：把二審意見**併進**你原本的回報（哪幾條採納、哪幾條不採納與理由），
   🚫 不要另開一段「二審說……」把責任外包給它 —— 判斷還是你的。

## 狀態與退出碼

**設計原則：狀態走 stdout，退出碼只分「真失敗」。**
沒裝二審後端是**預期中的降級**，不是錯誤 —— 若它回非零，在 `set -e`、skill runner
或 `$(…)` 裡會直接中止上層流程，「正常降級」根本輪不到人處理。

| 狀態 | 意思 | 退出碼 |
|---|---|---|
| `ok` | 有拿到意見（＝後端回了非空輸出；意見對不對，靠你消化判斷） | 0 |
| `skipped_not_installed` | 找不到後端 CLI | 0（`--strict` 下 3） |
| `skipped_not_logged_in` | 有裝但看起來沒登入 | 0（`--strict` 下 3） |
| `failed_quota` | 額度／頻率限制 | 2 |
| `failed_network` | 網路／連線 | 2 |
| `failed_policy` | 地區或組織政策擋下 | 2 |
| `failed_version` | 後端 CLI 版本不相容 | 2 |
| `failed_empty` | 後端回空內容 | 2 |
| `failed_unknown` | 無法歸類 | 2 |
| （不印狀態） | **用法錯誤**：沒給來源／缺 `--rubric`／`--effort` 值不合法／一次給兩份來源／`--strict` 與 `--soft-fail` 併用 | 1 |

兩個退出碼開關，方向相反、都是**你主動要求**才生效：

- `--strict`：把 `skipped_*` 變成 3，讓 CI 能把「沒有二審」當成失敗擋下來。
- `--soft-fail`：把 `failed_*` 也變成 0。當二審是加分項、**絕不能擋住主流程**時用。
  ⚠️ 用了它就等於放棄「靠退出碼判斷」——呼叫端**必須自己解析 stdout 最後一行的狀態**，
  否則你會得到一個「看起來成功、其實沒審到」的靜默結果。
  （兩個開關方向相反，**不能同時用**，同時給會直接報錯。）

預設值（skipped=0、failed=2）的取捨：「沒有後端」是預期中的降級，不該吵；
「後端拒絕了你」是真的有事發生，預設吵一下比較誠實。哪種對你比較好，你自己決定。

## 模型：刻意不釘死

腳本不指定模型，吃後端 CLI 自己的預設 —— **釘死會過期，而且猜不到你的方案有哪些模型**。
代價是：若後端的預設模型不在你的方案內，你會撞到 `failed_quota`（後端各家訊息不同，
也可能落在 `failed_policy`／`failed_unknown`）。這時用 `--model` 指定你有的模型即可。
⚠️ 免費方案的可用模型與額度，官方文件並未逐項載明 —— **本專案沒有在免費帳號上實測過**，
不保證開箱即用。

⚠️ **狀態分類是比對後端 stderr 文字的啟發式，不是後端官方保證的介面** ——
CLI 升版就可能失準。所以失敗時後端的 stderr 與 stdout **各印尾 20 行**，別只信標籤。
⚠️ 那是尾段不是全文（原因若在開頭會被截掉），而且後端回顯可能含 token 或你送審的片段 ——
別把它無腦貼進公開的 CI log。
「沒裝」與「沒登入」刻意分成兩種：新手最常卡在第二種，而若訊息說「找不到 codex」，
他會再裝一次然後更困惑。

## 成本與前提（誠實版）

- **不是零依賴**：需要一個二審後端（預設 Codex CLI）＋ POSIX shell ＋ `mktemp`／`date`
  ＋可連外的網路。誠實說法是「**除後端 CLI 與 POSIX shell 外，無額外套件依賴**」——
  不需 npm 套件、不需 brew formula、不需自備 API key。
- **每次要花時間與額度**：一次審閱約 1–3 分鐘，吃的是**你自己**的後端帳號額度。
- 官方方案表把 Codex 列在各方案內（含免費方案），**但官方的用量限制表並未列出免費方案的
  可用模型與額度**，且能不能跑起來還要看地區、帳號狀態與組織政策 ——
  「方案表上有」不等於「人人都能用」。查證原文與日期見
  [docs/AI_REVIEW_SOURCES.md](../../docs/AI_REVIEW_SOURCES.md)，**引用前自己重查**。

## 🚦 鐵則

- 🔒 **資料界線自己把關**：送出去就是給第三方模型看，憑證／個資／客戶資料不要送。
- ✅ **消化，不轉述**：意見要併進你的回報並說明採納與否，不是貼一段「它說……」。
- 🚫 **沒審到就明講**：`skipped_*` 時回報寫「本次僅自審」，**絕不可靜默略過**。
- ⚠️ **這是 best-effort，不是強制機制**：觸發器只是文字 —— 忘記跑不會有人發現。
  三個已知漏洞照實講：① **無收據**（沒有機制證明某次工作真的送審過）
  ② **TOCTOU**（送審後又改東西，審的是舊版）③ **「本次不送」是無限制 bypass**。
  想要強制，得在 CI／hook 這種工具層做，光靠 skill 文字辦不到。
- 📝 **二審在寫回報之前跑**，不是回報寫完再補一段。

## 進階：換掉後端（不綁單一廠商）

`AI_REVIEW_CMD` 收 stdin 的 prompt、吐 stdout 的意見，設了就不走 codex：

```bash
AI_REVIEW_CMD='ollama run llama3' ./scripts/ai-review.sh draft.md --rubric copy
```

後端跑不起來（找不到命令、沒有執行權限）或它自己說沒登入時，一樣走 `skipped_*`＋exit 0，
不會中斷你的流程。

⚠️ 三個代價講在前面：
① 自帶後端的品質不可控，而 rubric 的措辭是為「另一個模型家族」寫的；
② `AI_REVIEW_CMD` 是**整條 shell 命令**（用 `sh -c` 執行），不是單純的執行檔路徑 ——
別讓不可信的來源（外部 `.env`、CI 變數）決定它的值；
③ 若你的後端把錯誤訊息印到 **stdout** 而且回 exit 0，本工具分不出那是意見還是錯誤頁，
只會用長度給你一個「偏短」的警告 —— 自訂後端時自己看一眼輸出。
（後端回**非零**時不受此限：stderr 與 stdout 都會納入分類並照印。）
另外兩個環境變數：`AI_REVIEW_DIR`（落檔目錄，預設 `./.ai-reviews`）、
`AI_REVIEW_RUBRICS`（自訂 rubric 目錄）。
⚠️ 落檔會把**送審內容的審閱結果**留在磁碟上。檔案以 `600` 建立（只有你讀得到），
但目錄本身仍受你的 `umask` 影響，而且 `.gitignore` 只擋 git、不擋本機其他工具。
不想留就加 `--no-save`。

## 進階：自己的 rubric

`--rubric` 除了三個內建名稱，也吃檔案路徑。rubric 就是純文字提問清單 ——
照 `rubrics/*.md` 的樣子寫：**結論先行、逐項編號、要求具體到可直接動手**，
最後一行寫明用什麼語言回答。

## 實測範圍（過期請重驗）

⚠️ 「POSIX shell」是寫法上的目標，不是可移植性的保證：腳本仍用到 `trap … EXIT`、
`dirname --`／`basename --`／`cp --` 這類**規格沒有硬性保證**的行為。
真正的依據是下面實測過的組合，不是規格推論。

**你可以自己驗一次，別只信這段文字**：

```bash
sh <本skill目錄>/tests/matrix.sh          # 加 SH=bash 可指定用哪個 shell 跑受測腳本
```

41 項行為測試，**不燒任何額度**（後端全用 stub 模擬）、**不弄髒你的目錄**（產出寫在暫存區、
跑完自動清掉），全過回 exit 0，可直接放進 CI。缺 `python3`+`pyyaml` 時只會略過其中一項。
矩陣開頭會自動 `unset` 外層可能 export 過的 `AI_REVIEW_CMD` 等變數——你平常把後端指到別處
也不會污染測試結果。

腳本本體在 macOS 上以 `sh`／`dash`／`bash`／`ksh`／`zsh` 各跑過這份矩陣
（狀態分類、`--strict`／`--soft-fail`、可插拔後端、`set -e`＋`$(…)`＋wrapper 呼叫鏈、
路徑含空白、落檔目錄不可寫、stdin 來源、用法錯誤、同秒並發落檔、特殊檔名），
並以真實 Codex CLI 驗過 `ok` 路徑。
Linux 由 CI（ubuntu-latest）跑同一份矩陣；**Windows 未實測**，**免費方案帳號也未實測** —— 沒驗過的一律別當保證。

