Rebut
分类处理拉取请求上的 AI 代码审查评论,并严谨地回复。根据实际代码验证每条发现,修复真实问题,以证据反驳误报,并在每个线程中回复,标记为自动化分类。人类仍然点击“解决”。
这是人工审查的对应部分,而非其副本。AI 审查者(如 CodeRabbit 或 Gemini Code Assist)会发布多得多的发现,混合真实 bug 和误报,并且对仓库不可见。他们不知道你的仓库的本地配置、约定或工具,因此会自信地将对仓库正确的模式标记为 bug。机器人的严重性不等于其正确性:一个 CRITICAL 可能是误报,而一个 nit 可能隐藏一个真正的 bug。
为什么 AI 审查者需要自己的分类
- 对仓库不可见。 机器人不会加载你仓库的本地配置——linter、formatter 或类型检查器配置,无论你用的什么技术栈——或你的内部规则。它会将你的工具要求的模式标记为 bug。真实案例(一个 ESLint 仓库):审查者将
meta.defaultOptions标记为 CRITICAL“错误”,但该仓库的require-meta-default-optionslint 规则强制要求它。应用该建议会破坏构建。盲目应用和盲目驳回一样危险。 - 高误报率。 许多发现是推测性或仅限样式的。有些是人类遗漏的真实 bug。任务是凭证据区分它们,而非凭感觉。
- 自信地错误。 严重性标签是启发式的。像对待 nit 一样验证
CRITICAL。
当审查者是人类时
对于每个审查者,机器人或人类,流程相同:根据当前代码验证 → 修复或用证据反驳 → 在线程中回复并附上归属行 → 证明覆盖率。人类贡献者或审查者也属于分类循环:当有人要求更改时,修复并回复,使用相同的严谨性和人类在合并时读取的相同审计跟踪。只有语气会改变,因为人类了解仓库,发布更少且信号更强的发现,并且有面孔,粗鲁的反驳可能会威胁到他们。因此当审查者是人类时:
- 放弃机器人框架。 不要将他们的审查视为对仓库不可见、高误报率的噪音;通常不是。
- 协作,而非简短反驳。 验证,然后修复正确的,并用技术推理反驳错误的。绝不要表演性同意,也绝不要对同事进行简短的反驳“误报”;用非简短的技术论证语气陈述理由。
- 亲自发布回复,并附上归属行。 你自主分类人类线程,与机器人线程相同:通过分类账户发布,以归属行打开,使读者看到的是自动化分类而非维护者自己的同行审查。将评论与维护者断开连接的行正是让自动发布到人类线程变得安全的因素。覆盖率也计数人类线程(步骤 5.5),因此未回复的人类发现会保持轮次打开。
以下步骤适用于机器人和人类;归属标记和自动回复对两者都使用。唯一的按审查者调整是上述语气。
前置检查
"${CLAUDE_SKILL_DIR}/scripts/preflight.sh"
preflight.sh 确认 gh 已认证并且仓库有 GitHub 远程。如果缺少先决条件,它会在 stderr 上打印修复方法并以非零退出;传递该信息并停止。识别 PR:[pr-number] 参数,否则是当前分支的 PR(gh pr view --json number)。
步骤 1:收集每条发现
"${CLAUDE_SKILL_DIR}/scripts/gather-review.sh" <pr>
gather-review.sh 打印一个 REVIEWS 块,其中包含每条审查的作者、状态和正文,以及一个 COMMENTS 块,其中包含每条内联评论的作者、path:line、id=<id> 和正文,通过 gh 的内置 --jq 实现,因此不会发明或丢弃任何内容。列出每条发现及其作者、path:line、回复所需的评论 id 以及正文。包含顶层审查摘要和每条内联评论。不遗漏任何一条。将每个线程的作者标记为机器人(可通过收集输出中显示的 [bot] 登录后缀识别)或人类:两者都遵循步骤 4-6,并且人类线程还需额外进行“当审查者是人类时”中的语气调整。
让轮次稳定后再分类。 审查者异步触发:第二个机器人通常比第一个晚几分钟才发布其审查波次,因此 PR 更新后立即运行的 gather 只会捕获到目前为止已发布的审查者。第一次 gather 后,使用 gh pr checks <pr> --watch || true 让轮次稳定:它会阻塞直到 PR 的检查完成,并且在交互式和 headless 模式下行为相同,而这是异步审查者发布的窗口(|| true 使无检查 PR 的非零退出不被视为失败)。然后重新运行 gather-review.sh;如果发现了新发现,则轮次仍在到达,因此再次 watch 并重新 gather。(如果 PR 没有检查,--watch 会立即返回;并且仅限审查的机器人可能根本不注册任何检查运行,因此在这两种情况下,暂停片刻并无论检查状态如何都重新 gather。下面跨轮次的自动化才是针对在此之后才发布的审查者的真正后盾。)只有在重新 gather 没有新增内容后才开始分类。这将多审查者轮次处理为一次分类传递,而不是从审查者 A 的发现开始而审查者 B 仍在发布过程中,这迫使进行第二次完整传递。这个限制使其不会无限等待;最后一节中的跨轮次自动化是针对技能退出后发布的审查者的持久解决方案。
步骤 2:验证每条发现(此技能的核心)
对于每条发现,根据当前代码回答两个问题,而不是根据机器人的框架:
- 问题能否复现? 按照
${CLAUDE_SKILL_DIR}/references/debugging.md所述复现它:在相应行打开文件,跟踪逻辑,构造机器人描述的输入,并检查实际行为。如果是 bug,它必须是可演示的。 - 建议对这个仓库来说正确吗? 运行仓库定义的相关门控(其 linter、类型检查器或测试,存在哪个就运行哪个)。检查文档化的约定。通过机器人但未通过仓库门控的修复在这里是错误的。
一次只更改一件事并重新验证。不要批量处理。
步骤 3:分类
| 裁决 | 含义 | 操作 |
|---|---|---|
| FIX | 真实,可复现 | 修复它(步骤 4) |
| REFUTE | 针对此仓库的误报 | 回复附上证明其不成立的证据 |
| INTENTIONAL | 机器人无法看到的故意选择 | 回复解释意图 |
| MINOR | 美观性,非门控性问题 | 注明;根据维护者的标准修复或跳过 |
步骤 4:修复有效的发现
- 最小更改,仅限于发现范围。对于 bug,先编写失败的测试,观察其失败,修复,观察其通过。
- 运行仓库的完整门控。红色门控会阻止修复。
- 将整个轮次放到一次提交中并推送,绝不对每条发现单独推送。 像步骤 2 中那样一次验证和修复每条发现,但将所有修复暂存并一起提交,然后推送一次,使用维护者的账户,即拥有 PR 并发布回复的同一个账户。对每条发现推送一次会将一次审查周期变成多次:每次推送会重新运行 CI 并重新触发每个机器人,因此五次修复变成五次 CI 运行和五次重新审查波次。每轮一次提交就是一次 CI 运行和一次重新审查波次。捕获该单一 SHA;步骤 5 中的每条修复回复都引用它。如果该推送因非快进而被拒绝(因为远程分支在轮次期间移动了——版本管理机器人的 bump 或 PR“更新分支”合并添加了提交),恢复,不要停止:
git fetch然后git rebase origin/<branch>,重新运行门控,并再次推送,绝不用--force;rebase 会重写提交,因此重新捕获新的 SHA 并在步骤 5 的回复中引用它,而不是过时的 rebase 前的那个。
步骤 5:在每个线程中回复
回复原始评论,以便它与原始线程内联,绝不是顶层 PR 评论。
每条回复都以相同的归属行开头,对每个线程,无一例外。 你通过维护者的 gh token 发布,因此 GitHub 会显示他们的头像,读者会认为他们写了它。归属行命名了代理,并将评论与维护者断开连接,因此自动化分类永远不会被误认为是维护者自己的审查:
🤖 Automated triage by Claude Code, posted through the maintainer's account, not a personal review.
当仓库有机器人或机器账户时,改为以该身份发布,以便作者本身是非个人的;归属行是个人 token 的备用方案。
将正文写入文件。归属行使正文变成多行,这会破坏内联的 -f body="..." 引用。一个修复回复:
> 🤖 Automated triage by Claude Code, posted through the maintainer's account, not a personal review.
**rebut** fixed in <sha>: <关于更改的一行说明>。
一个反驳说明证据:
> 🤖 Automated triage by Claude Code, posted through the maintainer's account, not a personal review.
**rebut** not changing this: <具体理由,例如仓库的 lint 规则要求它;门控通过>。
使用 post-reply.sh 发布每条回复,一次一条发现:
"${CLAUDE_SKILL_DIR}/scripts/post-reply.sh" <pr> <comment_id> /tmp/reply.md
post-reply.sh 在 GitHub 的二级速率限制下控制发送节奏,然后通过从 API 响应中读取创建的回复 ID 来确认它已持久化,并在 post 被限速时使用退避重试。它打印回复 ID 并在成功时退出 0;退出 1 表示回复未发布(不要继续,修复并重新运行),退出 2 是参数错误。不要手动在循环中调用原始 gh api .../replies:背靠背的 post 会触发速率限制并消失,这正是此脚本旨在防止的失败。一条发现,一次 post-reply.sh 调用,按顺序。
仅回复,绝不解决。 通过 API 发布回复;绝不要点击“解决对话”。绿色的“解决”按钮属于人类审查者,他们用它来验证此技能的工作:他们阅读每个线程,确认存在回复,确认存在摘要评论,然后才解决。如果技能自己解决,人类无法判断是代理还是他们自己关闭了线程,这正是导致他们重新检查或重新运行分类的混淆。此技能的完整输出是回复加上一条摘要评论;“解决”按钮保持属于他们。
不留任何空白线程。 步骤 1 中收集的每条发现都会获得回复:FIX 及其 SHA,或 REFUTE / INTENTIONAL / MINOR 及其原因。没有回复的发现,无论来自机器人还是人类,都与未经分类的工作无法区分:审查者无法确定技能是否运行过,因此他们会重新调用它,导致轮次循环。沉默从来不是裁决。即使不同意发现,仍然需要陈述理由,绝不要留空线程。
步骤 5.5:断言覆盖率,重新验证 CI,然后捕获你的修复推送所引发的轮次
在总结之前:
-
断言覆盖率(始终)。 发布后,重新查询 PR 并确认每条发现,无论是机器人还是人类的,都从分类账户获得了回复:
"${CLAUDE_SKILL_DIR}/scripts/verify-coverage.sh" <pr>它为每条发现打印一行(
answered/MISSING),然后是answered N / M。退出 0 表示每条发现都有回复(轮次已覆盖);退出 1 列出了每条MISSING发现,因此使用post-reply.sh重新发布那些,然后重新运行直到它报告answered N / N;退出 2 是读取/参数错误,这不是覆盖率问题,因此输出它。此运行无论裁决如何,包括所有发现都是 REFUTE 且未推送任何修复的轮次:一个被丢弃的反驳回复与一个被丢弃的修复回复一样不可见。在任何发现是MISSING的情况下,绝不要继续到步骤 6。 -
重新验证 CI。 当检查仍在结算时,使用
gh pr checks <pr> --watch等待,然后使用"${CLAUDE_SKILL_DIR}/scripts/ci-status.sh" <pr>进行分类:ci: green(退出 0)表示清除;ci: failing(退出 1)会阻塞,因此调试它;ci: none(退出 3)表示未配置 CI,这不是失败,因此注明它并继续,与 implement 技能处理它的方式完全相同;ci: pending(退出 4)表示重新运行--watch,然后重新探测;读取错误(退出 2)不是“green”,因此输出它。用自己的眼睛确认绿色;绝不在没有脚本证据的情况下断言它。如果步骤 4 因为每条发现都是 REFUTE 或 INTENTIONAL 而没有改变任何内容,那么就没有新的推送,因此这个 CI 重新验证和下面的轮次捕获没有需要重新触发的内容;上面的覆盖率断言仍然运行。 -
捕获新的轮次。 修复推送自然会触发 CI 和审查者。在覆盖率和 CI 验证之后,但在任何总结之前,通过重新运行
gather-review.sh来检查修复是否导致了新的评审。如果有新的发现,则轮次尚未完成:从步骤 2 开始再次处理它们,保持相同的回复和相同的提交组(如果新发现需要修复,则git commit --amend;如果不需要,则推送已经在那里)。绝不要进行两次推送——目标是每轮一次推送,而不是每轮一次且仅一次的集合。无新增内容意味着轮次稳定,可以总结。这解决了以下问题:在上次 gather 结束后才发布发现但出现在ci_status.sh轮询窗口中的审查者;更新后重新触发的机器人;以及在技能发布回复期间到达的深度评估。