GitHub 分类整理
分类整理仓库的未解决 GitHub Issue 和拉取请求。可选地先处理准备好的 PR(合并通过的机器人 PR 和维护者批准的 PR,审查从未审查过的 PR),然后关闭已解决的 Issue(附上注释,引用解决该 Issue 的 PR 或提交),确保 Issue 及其待修复的 PR 互相引用,并为所有未完成的 Issue 分配一个仅限本地的优先级和变更规模评估。
何时使用
- 当用户运行
/github-triage来整理或审查仓库的未解决 Issue 和拉取请求时。 - 当 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 # 当前工作目录是否是一个 git 仓库?
git remote -v # 列举所有远程仓库
确定远程仓库中不同的 GitHub 托管仓库的集合。当远程仓库的 URL 主机是 github.com,且为以下任一形式时,该远程仓库即为 GitHub 托管:
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:收集 Issue 和上下文
# 未解决的 Issue(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 链接要关闭的 Issue——通过 PR 正文中的任何 GitHub 关闭关键字(close/closes/closed、fix/fixes/fixed、resolve/resolves/resolved),或通过手动 UI 链接填充。这是可用的最强信号。对于它未涵盖的 Issue,还要在默认分支上搜索提交消息。从所选仓库权威性地解析默认分支(不从本地 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:分类未解决的拉取请求(可选)
在 Issue 之前处理 PR:在这里合并准备好的 PR 意味着 Issue 阶段中的“已解决”检查会看到这些合并刚刚落地的工作。
如果没有未解决的 PR,则跳过此阶段。否则汇总未解决的 PR 并询问用户是否现在处理 PR(AskUserQuestion:处理 PR / 跳过到 Issue)。如果他们跳过,直接进入 Issue 分类。
根据每个未解决 PR 的审查、CI 和合并状态对其分类。下面的字段形状是 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是可以的,绝不能阻塞(例如,对依赖项更新的 CodeQL 运行报告NEUTRAL——一个通过的 Dependabot PR 使用NEUTRALCodeQL 检查是CLEAN)。空汇总表示没有 CI——一种不同的状态,绝不能视为准备就绪。mergeStateStatus == "CLEAN"已经反映了必需检查的状态;使用汇总来捕获失败/挂起的非必需检查。 - 机器人/自动 =
author.is_bot == true。根据author.login匹配自动合并允许列表,标准化掉gh的渲染——去掉前导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 # 位置参数仓库,不是 -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 # 期望 "MERGED" 后再移动到下一个 PR
如果 PR 不再准备就绪(包括 mergeable == "UNKNOWN")、合并未落地、或任何必需检查未通过,则停止并报告——绝不强制、使用 --admin、--auto 或跳过检查。对于机器人 PR,在把关中公开依赖项和版本跳转(例如主要版本更新),以便用户可以在有上下文的情况下决定。
审查子代理(从未审查过的 PR):当用户选择加入时,为每个 PR 生成一个Agent——全部在一条助手消息中,以便它们并行运行。每个子代理审查一个 PR 的差异并返回结构化审查;将每个写入工作目录下的 github-pr-<number>-review.md(覆盖该 PR 的任何先前文件)。审查是只读的,从不发布到 GitHub。有关子代理提示、审查量表和文件格式,请参阅 references/reviewing-prs.md。
在任何合并之后,重新获取已合并的 PR 列表(阶段 1 的 --state merged 查询),以便 Issue 阶段可以检测这些合并解决的 Issue。
阶段 3:分类每个未解决的 Issue
将每个未解决的 Issue 排序到恰好一个桶中。
桶 A——已解决(工作已落地,Issue 仍保持开放)。 需要具体证据;优先采用相互印证而不是单个弱信号:
- 一个合并的 PR 在
closingIssuesReferences中列出了该 Issue,或者其标题/正文与关闭关键字一起引用了#N。(最强。) - 默认分支上的一个提交使用关闭关键字引用了
#N。 - Issue 要求的行为在当前代码中明显存在——通过使用 Grep/Read 阅读相关代码来验证,不要假设。一个不同或部分实现并不解决该 Issue。
→ 拟议的写入操作:关闭该 Issue,并附上注释,指明解决该问题的 PR/提交。
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:为未解决的 Issue 评分(仅限本地)
优先级——Critical / High / Medium / Low。权衡:
- 影响/严重性——安全、数据丢失、崩溃或正确性错误排名高于增强功能;文档/美观排名最低。现有标签(
security、bug、crash、regression)是强信号。 - 范围——影响多少用户/工作流程。
- 信号——反应(👍)、重复报告、带有持续活动的年龄。
- 紧迫性——阻塞发布、有截止日期或有活动的回归。
变更规模——工作量代理,以 size/* T 恤桶的形式表示,来自估计的变更总行数(添加行 + 删除行,忽略生成/供应商文件),使用 Kubernetes/Prow 的阈值。