GitHub Triage 是做什麼的?
對儲存庫的開放 GitHub Issues 和 Pull Requests 進行分類。可選擇先清理好準備好的 PR(合併通過的機器人 PR 和維護者批准的 PR,審查從未被審查的 PR),然後關閉已經解決的 Issue(留言引用解決它們的 PR 或提交),確保 Issue 與其待合併的修復 PR 相互關聯,並為所有仍然未完成的 Issue 指定一個本地專用的優先級和變更規模估算。
何時使用
- 當用戶執行
/github-triage來整理或審查儲存庫的開放 Issue 和 PR 時。 - 當 Issue 待辦事項列表已失焦:已解決的工作仍保持開放、修復已合併但未關閉其 Issue、或正在進行的 PR 從未連結到其 Issue。
- 當準備好的 PR 已堆積(通過的依賴更新、已批准且綠色的 PR)或 PR 處於未審查狀態。
何時不要使用
- 不要自動調用。此技能會執行不可逆的 GitHub 寫入操作(合併 PR、關閉 Issue、發布留言),且僅在明確調用時運行。
- 不要用於在 GitHub 上套用優先級/工作量 標籤。優先級和規模僅在本地展示,絕不會發布(請參閱安全規則)。
- 不要取代人類最終的合併決策——每次合併都會提議獲得明確批准,絕不會自主執行。
核心原則
- 寫入操作有門控。 首先計算完整的分類結果,呈現每個提議的寫入操作(包括合併)供審查,在用戶批准前不執行任何操作。
- 關閉前要有證據。 沒有具體、可引用的理由(一個已合併的 PR 或預設分支上的一個提交),絕不關閉 Issue。當證據薄弱或不明確時,讓 Issue 保持未完成並標記為待審查。
- 優先級和規模保持本地。 它們是供用戶使用的內部規劃輔助工具,絕不會寫入 GitHub。
工作流程
階段 0:選擇目標儲存庫
gh auth status # 確認 gh 已認證
git rev-parse --is-inside-work-tree # PWD 是否為 git 儲存庫?
git remote -v # 列舉遠端
確定遠端中不同的 GitHub 託管儲存庫集合。
一個遠端是 GitHub 託管的,當其 URL 主機為 github.com,並符合以下任何一種形式:
https://github.com/OWNER/REPO(.git)[email protected]:OWNER/REPO(.git)ssh://[email protected]/OWNER/REPO(.git)
將每個標準化為 OWNER/REPO 並去除重複(一個 fork 設定可能讓 origin 和 upstream 指向不同的 GitHub 儲存庫;指向同一個 OWNER/REPO 的多個遠端只算一次)。
選擇規則:
| 情況 | 操作 |
|---|---|
| 恰好一個不同的 GitHub 儲存庫 | 將其用作預設——不要提示。 |
| 不是 git 儲存庫,或零個 GitHub 遠端 | 詢問用戶要分類的 OWNER/REPO。 |
| 多於一個不同的 GitHub 儲存庫 | 使用 AskUserQuestion 讓用戶選擇哪個 OWNER/REPO。 |
GitHub Enterprise 主機無法可靠地自動偵測。如果用戶在 GHE 實例上工作,請索要
OWNER/REPO,並讓他們設定GH_HOST/ 使用gh設定的主機。在繼續之前向用戶確認解析出的儲存庫。
將結果儲存為 REPO="OWNER/REPO",並將 -R "$REPO" 傳遞給每次 gh 調用。
在使用前根據 ^[A-Za-z0-9._-]+/[A-Za-z0-9._-]+$ 進行驗證,這樣格式錯誤或惡意的遠端 URL 就不會流入命令。
階段 1:收集 Issues 和上下文
# 開放 Issues(gh issue list 預設排除 PR)
gh issue list -R "$REPO" --state open --limit 1000 \
--json number,title,body,labels,assignees,comments,reactionGroups,createdAt,updatedAt,url
# 開放 PR——「待定修復」(Issue 階段)和 PR 分類(階段 2)的候選
gh pr list -R "$REPO" --state open --limit 1000 \
--json number,title,body,author,isDraft,reviewDecision,latestReviews,\
mergeable,mergeStateStatus,statusCheckRollup,labels,createdAt,headRefName,url,closingIssuesReferences
# 最近合併的 PR——「已解決」的候選
gh pr list -R "$REPO" --state merged --limit 300 \
--json number,title,body,mergedAt,url,closingIssuesReferences
closingIssuesReferences 列出一個 PR 連結以關閉的 Issues——由 PR 主體中的任何 GitHub 關閉關鍵字(close/closes/closed、fix/fixes/fixed、resolve/resolves/resolved)或手動 UI 連結填充。這是最強的信號。對於它未涵蓋的 Issues,也要搜尋預設分支上的提交訊息。從選定的儲存庫(不是本地 origin/HEAD,它可能未設定或指向錯誤的遠端)權威地解析預設分支:
default_branch=$(gh repo view "$REPO" --json defaultBranchRef --jq .defaultBranchRef.name)
# 錨定 Issue 編號,這樣 #12 就不會匹配到 #120、#123 等
git log --oneline "origin/$default_branch" \
| grep -iE "(close|fix|resolve)[sd]? +#<N>([^0-9]|$)"
階段 2:分類開放的 Pull Requests(可選)
在 Issues 之前處理 PR:在此合併準備好的 PR 意味著 Issue 階段中的「已解決」檢查會看到這些合併剛剛完成的工作。
如果沒有開放的 PR,跳過此階段。否則,總結開放的 PR 並詢問用戶是否現在處理 PR(AskUserQuestion:處理 PR / 跳到 Issues)。如果他們跳過,直接進行 Issue 分類。
根據審查、CI 和合併狀態對每個開放的 PR 進行分類。以下字段形狀是 gh pr ... --json 實際返回的——依賴它們,而不是直覺:
- 準備好合併 =
mergeable == "MERGEABLE"且mergeStateStatus == "CLEAN"且 CI 沒有阻塞(如下)。CLEAN是 GitHub 伺服器端的「無衝突、不落後、非草稿、必要檢查通過」的判定——任何其他mergeStateStatus(BEHIND、UNSTABLE、BLOCKED、DIRTY、DRAFT……)都是沒有準備好。將mergeable == "UNKNOWN"(GitHub 懶惰地重新計算可合併性,尤其是在另一次合併之後)視為沒有準備好——短暫重新輪詢或跳過;絕不要在此時合併。 - CI 沒有阻塞——掃描
statusCheckRollup(以__typename為鍵)並僅在真正問題上拒絕該 PR:一個硬性失敗(一個conclusion為FAILURE/CANCELLED/TIMED_OUT/ACTION_REQUIRED/STARTUP_FAILURE/STALE的CheckRun,或一個state為FAILURE/ERROR的StatusContext),或任何仍在運行的東西(一個status為QUEUED/IN_PROGRESS/WAITING/PENDING的CheckRun,或一個state為PENDING/EXPECTED的StatusContext——等待,還不能合併)。SUCCESS、NEUTRAL和SKIPPED是可以的且不得阻塞(例如,一個在依賴更新上報告NEUTRAL的 CodeQL 運行——一個綠色的 Dependabot PR 是CLEAN且帶有NEUTRAL的 CodeQL 檢查)。一個空的 rollup 表示沒有 CI——這是一個不同的狀態,絕不能視為準備好。mergeStateStatus == "CLEAN"已經反映了必要檢查的狀態;使用 rollup 來捕捉失敗/待定的非必要檢查。 - 機器人/自動化 =
author.is_bot == true。在標準化掉gh的渲染後,根據author.login匹配自動合併允許清單——去除前導的app/和尾隨的[bot](gh 在某些版本中將 Dependabot 返回為app/dependabot,在其他版本中返回為dependabot[bot];將兩者都標準化為dependabot)。 預設允許清單中的:dependabot、renovate,加上用戶命名的任何其他。來自未在允許清單中之機器人的通過 PR 會被報告,但不提供合併選項。 - 維護者批准 =
latestReviews有一個state == "APPROVED"的條目,其authorAssociation為OWNER/MEMBER/COLLABORATOR,且其author.login不是 PR 作者。不要僅使用reviewDecision == "APPROVED":它是由分支保護驅動的,並且在沒有必要審查規則的儲存庫上為null,因此它既過度信任(無法確認寫入權限)又錯過了真正的批准。 - 從未被審查 =
latestReviews沒有來自 PR 作者以外任何人的APPROVED/CHANGES_REQUESTED條目(一個 fork「審查已禁用」的機器人留言不算審查)。
| 類別 | 條件 | 提供的操作 |
|---|---|---|
| 可合併的機器人 PR | 允許清單中的機器人 + 準備好 + 非草稿 | 提供增量、按順序合併 |
| 已批准且準備好 | 維護者批准 + 準備好 + 非草稿 | 提示合併 |
| 從未被審查 | 非機器人 + 只有作者審查過(或無審查)+ 非草稿 | 提供生成一個審查子代理 |
| 需要修改 | 草稿、硬性 CI 失敗、待定 CI、衝突、落後、請求修改、或未準備好的機器人 PR | 僅報告——不提供操作 |
(「準備好」已經包含了 CI 未阻塞。審查子代理適用於人類編寫的 PR——一個未準備好的機器人依賴更新被報告為「需要修改」,而不被審查。)
透過 AskUserQuestion 呈現已分類的 PR 並提供適用的操作。
增量、按順序合併(機器人 PR 和已批准且準備好的 PR):首先確認合併集和合併方法(合併是不可逆的),然後一次一個、從最舊開始合併。選擇一個儲存庫允許的方法,如果不允許則失敗關閉:
gh repo view "$REPO" --json mergeCommitAllowed,squashMergeAllowed,rebaseMergeAllowed # 位置參數 repository,不是 -R
對於每個 PR,按順序在合併前立即重新驗證(每次合併後狀態都會變化——一個合併的 PR 可能使下一個 BEHIND、衝突或正在重新計算),同步合併,然後確認它已合併再繼續:
gh pr view <N> -R "$REPO" --json isDraft,reviewDecision,mergeable,mergeStateStatus,statusCheckRollup
# 僅在仍然準備好時繼續:非草稿、mergeable == MERGEABLE、mergeStateStatus == CLEAN、CI 沒有阻塞
gh pr merge <N> -R "$REPO" --<method> # 絕不使用 --auto,絕不使用 --admin
gh pr view <N> -R "$REPO" --json state # 在移動到下一個 PR 前預期 "MERGED"
如果一個 PR 不再準備好(包括 mergeable == "UNKNOWN")、合併未成功、或任何必要檢查不是綠色,則停止並報告——絕不強制、--admin、--auto 或跳過檢查。對於機器人 PR,在關卡中呈現依賴項和版本跳轉(例如主要版本更新),以便用戶在上下文中決定。
審查子代理(從未被審查的 PR):當用戶選擇加入時,為每個 PR 生成一個代理——全部在單個助手訊息中,以便它們並行運行。每個子代理審查一個 PR 的 diff,並返回結構化審查;將每個寫入到工作目錄中的 github-pr-<number>-review.md(覆蓋該 PR 的任何先前檔案)。審查是唯讀的,絕不會發布到 GitHub。請參閱 references/reviewing-prs.md 了解子代理提示、審查標準和檔案格式。
在任何合併之後,重新獲取已合併的 PR 列表(階段 1 的 --state merged 查詢),以便 Issue 階段可以偵測這些合併所解決的 Issues。
階段 3:分類每個開放的 Issue
將每個開放的 Issue 恰好排序到一個桶中。
桶 A — 已經解決(工作已完成,Issue 保持開放)。 需要具體證據;相對於單一弱信號,更傾向於相互印證:
- 一個已合併的 PR 在
closingIssuesReferences中列出了該 Issue,或者其標題/主體引用#N旁帶有關閉關鍵字。(最強。) - 預設分支上的一個提交引用
#N並帶有關閉關鍵字。 - Issue 請求的行為在當前程式碼中明顯存在——透過使用 Grep/Read 閱讀相關程式碼來驗證,不要假設。不同的或部分實作不會解決 Issue。
→ 提議的寫入:用命名解決的 PR/提交的留言關閉 Issue。
gh issue close <N> -R "$REPO" \
-c "由 #<PR>(<簡短原因>)解決。由於變更現在已在 $default_branch 上,因此關閉。"
桶 B — 待定 PR 將解決它(一個開放的 PR 處理了該 Issue)。
透過任一方向偵測:開放 PR 的 closingIssuesReferences 包括該 Issue;Issue 主體/留言連結了該 PR;或一個開放 PR 明確修復了 Issue 描述相同的內容。目標是 Issue 和 PR 相互引用——只填補真正的空白,並優先選擇非破壞性寫入:
- 任一方向沒有引用 → 在缺少引用的一方發布一個指向性留言(非破壞性)。提及
#<PR>/#<N>會創建一個 GitHub 鏡像到另一個時間線的交叉引用。gh issue comment <N> -R "$REPO" -b "#<PR> 中有一個修復正在進行中。" - 至少一個方向已連結 → 記錄「已連結——無需操作」。
除非用戶明確希望 PR 在合併時自動關閉 Issue,否則不要編輯 PR 主體。留言建立了一個引用,但不會觸發自動關閉——只有 PR 主體中的關閉關鍵字或提交訊息會。當用戶選擇加入時,絕不要破壞描述:在編輯前立即重新獲取主體,並通過 stdin 傳遞它,這樣不受信任的 PR 文字就不會穿過 shell 插值字串:
body=$(gh pr view <PR> -R "$REPO" --json body --jq .body)
printf '%s\n\nCloses #%s\n' "$body" "<N>" | gh pr edit <PR> -R "$REPO" --body-file -
不要重複已經存在的連結。
桶 C — 未完成(無解決方案,無待定 PR)。 指定一個,僅限本地,優先級和變更規模估算(下一個部分)。
階段 4:為未完成的 Issues 評分(僅限本地)
優先級 — Critical / High / Medium / Low。權衡:
- 影響/嚴重性 — 安全、資料丟失、崩潰或正確性錯誤排名高於增強;文件/美觀排名最低。現有標籤(
security、bug、crash、regression)是很強的信號。 - 範圍 — 影響了多少用戶/工作流程。
- 信號 — 反應(👍)、重複報告、持續活躍的年齡。
- 緊急性 — 阻塞了一個發布、有截止日期、或有一個活躍的回歸。
變更規模 — 工作量代理,表示為一個 size/* T 恤大小分類,來自估計的總變更行數(新增 + 刪除,忽略生成的/供應商提供的檔案),使用 Kubernetes/Prow 閾值:
- xs — 1–9 行
- s — 10–49 行
- m — 50–299 行
- l — 300–999 行
- xl — 1000+ 行
優先選擇保守的估計:如果範圍跨越桶邊界,報告範圍並選擇較大的桶以安全。從分析 Issue 的文字開始(「它只是改變一個字串」→ xs),然後檢查受影響的檔案/模組以驗證或調整。\n```