Review Inbox — 批次 Review 待審 PR
找出 team 內需要自己 first review、re-approve、或 re-review 的 PR,批次執行 review, 並依來源回 Slack 通知。
Contract
此 skill 只處理多 PR discovery + batch review orchestration。單一 PR URL 轉 review-pr;
「我的 PR」approval 狀態轉 request-pr-review。
支援三個來源:
| Source | Use when |
|---|---|
| Slack | 預設;掃 PR channel 最近訊息中的 PR URLs——含 thread 回覆,不只 top-level |
| GitHub 條件掃描 | 一律與 Slack 取聯集:我投過票而 head 已推進的 open PR,不靠任何人說話 |
| Thread | 使用者提供 Slack thread URL 並要求 review |
| Label | 使用者明確提到 need review label |
不得 review 自己的 PR。不得對 waiting_for_author PR 重複 review。
Review inbox 屬 reviewer-side read-only lane;它可以 advisory,但對 awaiting_re_review、
mergeable_ready、unsupported_mutation、changes_requested 的解釋必須沿用 shared PR state。
它不得把 batch review 結果升格成 author-side completion / release authority;所有
「可 merge / 已修完 / 可 release」語句只能轉述 shared state,不可自行推論。
Reference Loading
| Situation | Load |
|---|---|
| Any run | context-budget-contract.md, review-inbox-discovery-flow.md, stale-approval-detection.md, workspace-config.yaml |
| Batch review execution | review-inbox-batch-review-flow.md, .claude/skills/review-inbox/dispatch-context-bundle.md |
| Slack notification | review-inbox-slack-reporting.md, slack-message-format.md, ../review-pr/references/github-slack-user-mapping.md, scripts/validate-language-policy.sh |
每張 PR 交給一個 sub-agent 執行。 用哪一種 agent、幾個並行、先跑哪一張,由執行的人
當下判斷——這一支提供判斷需要的事實(姊妹單關係、規模、風險等級、授權狀態),不提供結論。
它不指名任何一種 agent 型別,要求與禁止都不指名:一支 scope: universal 的 skill 指名一個
它不 ship 的東西,換一個環境就不成立。
主 session 不讀完整 diff。 這一條與怎麼派無關,它由 context-budget-contract.md 直接
規定,任何 sub-agent 都滿足它。以前這件事由一條「禁用 general-purpose sub-agent」的規定
代理,那條規定引用的證據從來沒有產出過(DP-575);代理拿掉了,被代理的沒有。
Batch review dispatch 由 main session 讀 dispatch-context-bundle.md 一次,把它 inline 注入
每個 review packet。Packet 自帶執行 review 需要的全部內容;另外附上延伸參考的路徑,
要不要讀、讀多少由 sub-agent 自己判斷。
Flow
讀 workspace config 與 defaults,取得 GitHub org、PR channel、approval threshold。
解析 mode:Thread 優先,其次 explicit Label,其餘走 Slack。
取得 current GitHub username,作為 exclude author 與 review-status 判定依據。
依 discovery reference 產生 candidates JSON;派工前照〈Scan Freshness〉重核一次,60 秒從那次重核起算。 Slack channel scan 使用 MCP 時指定 detailed output——
concise不輸出=== Message from與Message TS:這兩個 marker,parser 會找不到 message header 而 靜默回傳 0 個 URL(stderr 只印一行 WARN,離場碼仍然是 0),而那跟「channel 真的空」 分不開;fallback CLI 的--oldest可接受 Slack timestamp 或 ISO date/datetime。 產出 candidates 之前要先過scripts/review-inbox-discovery-probe.sh。 它非零就停在 那裡、把 marker 與說明回報出來——不要宣告空的收件匣,也不要靜默改走 label scan。 它現在除了「讀不讀得懂」也問「讀完了沒」:時間窗翻到底了嗎、窗內有新回覆的 thread 讀進來了嗎。--window-seconds在 channel 模式必填,值是這一趟宣告的回溯時間窗。 4b. Slack 那條做完,再跑一次 GitHub 條件掃描並取聯集:scripts/scan-my-stale-reviews.sh --my-user <u> --org <org> --merge-with <Slack 的 candidates>。 頻道掃描的前提是有人說話,這一條沒有這個前提——2026-09-04 兩輪 discovery 都空手, 而同一時間有五顆 PR 擋在我方舊票上、作者早就推了修正。將 candidates JSON 經
annotate-review-candidates.pyenrich,補上 sister PR cluster metadata 與model_tiersemantic class。Slack mapping 若含root_ticket_key,cluster 必須優先使用 root ticket;若沒有 umbrella ticket 但同一 Slack root message 有可辨識 topic,使用root_topic_key;最後才 fallback 到每張 PR 自己的 ticket。鍵相同不等於同一批改動。 同一個 repo 的兩顆 PR 要進同一個 cluster,還要量得到改動 交集——共用一個檔案,而且該檔在 base 那一側的 hunk 區塊重疊。量不到就兩顆都走完整 review:sibling 走的是 lead summary 的六條判準,判錯的代價是一顆 PR 只被半審過,而判成 standalone 的代價只是多花一次 review。跨 repo 的兩顆不套這條,因為那裡量不到交集, 而「同一件事在三個 repo 各開一顆」正是 sister PR 要服務的形狀。每一顆都帶著一句
cluster_reason說出憑什麼——它分得出這一顆的交集是量到的(same_repo_overlap)還是 量不到而放行的(cross_repo_key_only)。這條規則來自一次真跑的收件匣:同一則 thread 裡有兩張不同單的 PR,同一個 repo、 同一個 controller 檔案,而區塊零重疊。舊的判準讓後面那顆走 sibling,於是它只被 lead 的六條判準看過,漏掉一條 must-fix。
若 candidates 為空而且 probe 回的是
POLARIS_DISCOVERY_LEGITIMATE_EMPTY,回報目前 沒有需要 review 的 PR 並停止。probe 沒過的空清單不是空的收件匣,是來源壞掉了。顯示排序後清單,然後全部進入 review。不問「先看哪一批」、不問「要不要送」、不因為 張數多或 diff 大自行縮小範圍。 這一支只在兩種情況停下來,而且要說出是哪一種:來源 拿不到,或是需要授權而授權不存在。
用
build-review-prompt.sh產生 review packets + manifest。人已經授權送出時,把授權 一起傳進去(--authorized-by與--authorization-quote),packet 才有一條到得了執行 那一層的路——沒有這兩個參數時 packet 明講「未授權」,sub-agent 產出 payload 但不送出。 蒐證放在 sub-agent 那一層:主 session 的 per-PR 預算不會被 diff 灌爆,而讀 diff 以外 的檔案(追消費端、對照姊妹 repo、實跑驗證)只有在那一層做得到。執行 per-PR review packets,一張都不留。每張 PR 的 head 與完整 diff 在那張 PR 的 review 開始的那一刻才取,計畫時不整批預取——理由與量到的形狀寫在
review-inbox-batch-review-flow.md的〈什麼時候取那份 diff〉。Prompt 必須使用 deterministic handbook resolver 列出已存在的 project handbook paths,空清單時明確標示 no project handbook。Prompt 必須要求執行者先讀 changed-file names,再依 diff size sampling;existing inline comments 的完整 body 不進主 session,reviewer envelope 內 讀得到。Cluster lead 先跑完整 review;cluster sibling 使用 sibling-diff mode 與small_fastmodel class hint(那是一個事實,不是一道指令——adapter 認不認得由執行的人判斷),不確定時標記needs_standard_review——但兩端行為對不上 本身就是一個發現,不是只是一個要標記的例外。收斂結果,依來源模式發 Slack summary 或 thread replies。跟 review 一樣做完就回, 不在這裡再問一次要不要送。
跑
measure-review-inbox-session.sh產生 telemetry JSON,並用polaris-learnings.sh add --type telemetry --tag review-inbox寫入metadata.review_inbox_run。若無法取得完整 transcript,仍需用 line-count proxy 記錄 candidate count、reviewed count 與 artifact volume。在對話中回報每個 PR 的 review result、approve status 與 telemetry run_id。
Write And Notification Rules
- Review body 與 inline comments 由
review-pr流程負責。 - Slack message 送出前必須通過
scripts/validate-language-policy.sh。 - Slack mode 不發 channel-wide summary;只回覆原始 PR threads。
- Label mode 發一則 channel summary。
- Thread mode 回覆指定 thread。
- Thread replies 不設
reply_broadcast: true。
Completion
輸出 reviewed count、APPROVE / REQUEST_CHANGES / COMMENT counts、Slack notification count、 任何 blocked PR 的原因,以及 telemetry run_id。Telemetry 可用:
POLARIS_WORKSPACE_ROOT={workspace_root} .claude/skills/review-inbox/scripts/polaris-learnings.sh query --type telemetry --tag review-inbox
查到 metadata.review_inbox_run.main_session_input_tokens。