关于 babysit-pr
babysit-pr 是处理 GitHub PR 和 GitLab MR 审查反馈的技能,覆盖核实问题到关闭未解决讨论。它会验证发现、修复阻塞项,并在回复前发布更改,以证据结束每条讨论。
主要功能
- 01收集行内与顶层审查
- 02对照代码核实发现
- 03修复阻塞项并推送
- 04回复并解决讨论串
- 05按提交跟踪审查轮次
适用场景
- 01整理每次推送后的机器人反馈
- 02清理未答复的审查讨论
- 03回复审查者前验证修复
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ amelnagdy/ review-skills --skill babysit-pr安装后在 Claude Code 中输入/babysit-pr确认已加载。
附带文件scripts/threads.sh
详细介绍
跟进 PR
目标:将一个拉取请求(GitHub)或合并请求(GitLab)从“刚刚开启”跟进到“没有任何待回复事项”,无需人工守着页面反复刷新。下文中的“PR”指两者之一。
审查机器人是以差异行为锚点进行采样的。每次推送都会开启新一轮审查,一个地方的修复可能会引发相邻位置的评论。如果放任不管,PR 就会积累无人解决、只得到部分回复的讨论串,而第三轮发现的真正错误也会被第一轮和第二轮的细枝末节淹没。你的职责是阅读每一条发现,判断哪些属实,修复阻塞问题,并用文字跟进每一个事项,直至闭环。
你知道如何使用 gh(GitHub)、glab(GitLab)和 git。下面只介绍这个工作流需要的判断,以及几个容易出错的 API 调用。采集脚本会根据当前工作目录的 git 远程源选择代码托管平台;在两者上,它返回的内容结构相同,并带有一个 capabilities 区块,列出该平台无法告知你的信息。
最重要的三条规则
先核实,再相信。机器人的严重程度标记只是未经实际运行验证的猜测。每条发现都应视为需要对照代码核实的主张,包括 P1。机器人经常判断正确(这正是值得运行这个工作流的原因),但它们也常常会自信地判断错误,未经审查就照做可能引入缺陷。在同意或反对之前,先阅读实际的代码路径。
每个讨论串都要回复。只有说明了修复、驳回或延期的理由,并解决讨论串,相关发现才算关闭。沉默会让下一个打开 PR 的人以为问题“被忽略了”,也会导致真正的缺陷被遗漏。
先发布,再回复。“已修复”的回复只有在远程分支包含该修复后才属实。如果修复还只在本地,绝不要发布已确认的回复,也不要解决对应讨论串。驳回则无需推送。提供证据后立即回复并解决。
收集本轮审查结果
发现会出现在两个不同的位置,只查看其中一个位置会悄无声息地漏掉一半内容。这是跟进工作流出错最常见的原因:
- 行内审查讨论串。debate-review 和 Codex 会在这里发布发现(Codex 会在原本只有模板内容的审查正文中附上带有 P1/P2 标记的行内评论;正文看起来为空并不能说明没有内容)。每个讨论串都有一个
thread_id(用于解决讨论串)和一个reply_to(用于在讨论串中回复)。在 GitHub 上,这些是 GraphQL 审查讨论串;在 GitLab 上,它们是讨论。 - 顶层审查正文。Greptile 会在这里总结,Codex 有时会发布编号列表,debate-review 则会发布本轮总结。这些没有可解决的讨论串;每轮应针对它们发布一条 PR 评论。在 GitHub 上,它们是审查对象;在 GitLab 上,它们是普通备注。
内置脚本会通过一次调用返回两类结果,并已完成关联(<skill-dir> 是存放此 SKILL.md 的文件夹):
"<skill-dir>/scripts/threads.sh" <N> > /tmp/pr-<N>-round-<k>.json
不要只看过滤后的数量,务必同时查看未过滤的数量。在对采集结果使用任何 jq 过滤器之前,先打印原始总数(jq '{threads: (.threads|length), reviews: (.reviews|length)}')并进行对照。如果过滤器排除了 100% 的项目,在根据实际数据结构核实字段名称之前,应先假定过滤器有问题(jq '.threads[0] | keys')。jq 使用拼写错误的字段进行筛选时会静默地返回空结果;基于这种结果得出的“干净的一轮审查”,可能会让一条 P1 在其上方被发布为合并门槛评论。这种情况确实发生过。GitHub 工具出错时会返回较少的数据,而不是报错;因此还要遵守分页规则。
切勿描述你未获取的对象。如果针对特定 ID 的查询返回空结果,这就是停止信号。请说“我看不到它”,然后用其他方式获取(gh api .../reviews/<id>);绝不要叙述它可能包含的内容。另一个相关陷阱:每条内联讨论串回复都会被包装在一个零字节的 COMMENTED 评审对象中,因此,监视器的“新评审”事件可能只是回复包装对象,而非新一轮评审。threads.sh 的 .reviews 不包含这些包装对象,因此,事件中的评审 ID 如果在收集结果中缺失,表示“包装对象”,而不是“已消失”。
将它与上一轮的文件进行差异比较,查看哪些内容确实是新出现的。讨论串中的 outdated: true 表示其下方的代码行已移动。该问题可能已经修复,因此在投入本轮处理之前,请先对照当前代码核查。如果你已经处理过的讨论串的 comment_count 增加,表示有机器人在其中跟进了。
这位评审者看过当前推送了吗?只相信明确标注 SHA 的字段。在 GitHub 上,每条评审都有 commit_id;请将其与 head 比较。对于任一代码托管平台上的 debate-review,轮次正文中的 debate_head 就是其评审的 SHA。在 GitLab 上,其他评审者的备注不带 SHA(capabilities.review_commit_id: false);备注时间晚于你的推送,并不能证明它评审过该次推送,因此请报告“覆盖情况未知”,不要猜测。
两类作者算作评审者。第一类是机器人:收集结果中的 author_bot: true。在 GitHub 上,这来自 API 自身的作者类型,可靠性较高(常见的是 chatgpt-codex-connector 和 greptile-apps;不要硬编码白名单)。在 GitLab 上,API 有时才会提供这一信息,因此 author_bot 可能为 null;请将 null 视为未知,查看讨论串,并在报告中说明无法确认。第二类是首条评论带有 `` 标记的任何讨论串。debate-review 使用用户自己的账号发布,因此作者是 PR 作者(author_is_pr_author: true),但该讨论串仍属于评审者讨论串。收集结果会将这些标记为 debate_review: true,并从标记中解析出 debate_id、debate_status 和 debate_severity;其轮次正文会显示在 .reviews 中,其中包含 debate_head(所评审的 SHA)以及 debate_agreed / debate_contested。请像处理其他机器人讨论串一样处理它们。PR 作者的其他任何内容,以及任何未带该标记的人类评论,均不在自主修复范围内。请将其告知用户。
机器人通常会在推送后 5 到 10 分钟发帖;差异较大时会更久。不要频繁轮询;请在后台等待,同时自行审查差异。只有当你预期的每位评审者都已针对当前头部 SHA 发帖,或在各自的等待时限结束后被标记为不可用,才算一轮“完成”。评审者不可用,不应阻止你收集反馈或处理已发帖者的意见。请说明这一缺口,而不要报告 PR 没有问题。
按实际影响分类,而不是按徽章分类
验证问题后,应按后果分类,而不是按机器人附加的标签分类。
阻塞性问题,即会导致缺陷上线或阻止合并的问题:
- 修改后的代码中存在真实错误、行为错误或边界情况处理失常
- 存在安全、授权、数据完整性或数据丢失风险
- 违反了变更自身声明的契约、验收标准或规范
- 存在迁移或架构风险
- 检查失败或出现新的不稳定情况
非阻塞性问题,即问题确实存在,但不会导致任何故障上线:命名、结构、文档、测试方面的小问题、微小 性能问题、“考虑抽取此部分”以及风格偏好。
如果问题确实模棱两可,在阅读足够多的代码、确认可以降级之前,先将其视为阻塞性问题。这种不对称是有意为之。谨慎修复只需几分钟,漏掉一个 P1 问题却会导致生产环境故障。
带有 debate_status: contested 的辩论审查讨论串表示两个模型审查后意见不一。主审查者在反驳之后仍坚持该问题,评论中斜体的最后一行则说明了质疑内容。这是一个已有明确反方论据的主张,并不意味着主张较弱。应以同样的方式进行验证,并在回复中说明代码支持哪一方,以及原因。
自主修复阻塞性问题
不要停下来询问阻塞性问题。验证、修复、推送、持续观察,并报告所做的工作。
- 尽可能先复现。修复前探测失败、修复后通过,才能区分真正的修复和看似合理的修改。这对你起初持不同意见的问题尤其重要。判断错误的代价在这些问题上最高。
- 每轮推送一次,而不是每个问题推送一次。每次推送都会开启新一轮机器人审查,因此逐个问题推送会让你不得不经历更多轮次。
- 推送前运行仓库自带的检查关卡。修复导致测试套件失败,会多耗费一整轮。
- 如果已安装匹配的防护技能(来自 guard-skills 的 clean-code-guard、test-guard、wp-guard、woo-guard),请在推送前用它检查你的修复。这些防护工具能捕捉在审查压力下快速修复容易引发的问题。
- 如果你不同意某项问题,就拿出证据。驳回问题是合理且常见的,但回复必须提供证据:相关代码路径、已有的防护措施,或能明确验证行为的测试。“这样没问题”不算驳回。
发布,然后回复,最后解决
一轮中一次性处理所有审查意见,不要逐条处理:核实所有内容,在可行时复现已确认的阻塞问题,全部修复,运行门禁,然后只提交并推送一次,并确认远程 SHA。只有完成这些步骤后,才处理后续事项:
- 已确认:回复并注明修复提交,然后解决。
- 驳回:回复并提供具体证据,然后解决。无需推送;随时可以关闭这些事项。
- 延后:创建商定的问题单,回复并附上链接,然后解决。
用户批准的非阻塞修复应并入下一次汇总推送,绝不能为其单独推送。不存在可豁免复审的推送:每次推送,包括最终仅修改文档或细枝末节的推送,都必须在合并前经过合并门禁审查者的一轮干净审查(参见“合并前”)。如果发布或验证失败,请保持讨论串未解决,并报告阻塞问题。
请直接在发现问题的讨论串中回复。新建顶层评论会让原讨论串保持未解决,并迫使读者手动关联上下文。使用 harvest 中的 reply_to 回复,使用 thread_id 解决问题。在 GitHub 上,这两者是不同的标识符(REST 评论 ID 和 GraphQL 讨论串 ID);在 GitLab 上,两者都是讨论 ID。
GitHub:
gh api --method POST "repos/<owner>/<repo>/pulls/<N>/comments/<reply_to>/replies" \
-f body="$(cat /tmp/reply.md)"
gh api graphql -f query='mutation($t:ID!){
resolveReviewThread(input:{threadId:$t}){ thread{ isResolved } } }' -F t="<thread_id>"
GitLab(<project> 是经过 URL 编码的 group/path,--hostname 填写你的实例):
glab api --hostname <host> --method POST "projects/<project>/merge_requests/<N>/discussions/<reply_to>/notes" \
--raw-field "body=$(cat /tmp/reply.md)"
glab api --hostname <host> --method PUT "projects/<project>/merge_requests/<N>/discussions/<thread_id>" \
-F resolved=true
GitLab 的回复和解决调用摘自 GitLab API 文档,尚未在此技能中针对实际运行的实例进行过测试。首次使用时,请检查响应;如果任一调用失败,请停止并报告,不要尝试其他变体。
署名。每条回复开头都要说明撰写回复的模型,以及它代表谁发言,以免读者猜测是否有人类参与判断。请署上你自己的模型名称;此技能不绑定特定模型。回复代表的人是发布该回复的账户所有者。每个会话获取一次姓名:在 GitHub 上运行 gh api user -q '.name // .login',或在 GitLab 上运行 glab api user --hostname <host> | jq -r '.name // .username',然后重复使用:
我是 <model-slug>,代表 <user> 发言。
接着给出结论,然后简要提供证据:
I am <model-slug> writing on behalf of <user>.
Confirmed and fixed in `a1b2c3d`. You were right that `occurrence_time` was never
compared against `evidence.event_time`, so a mapping could bind proof from a
different occurrence. Reproduced with a failing test first
(`test_binds_proof_to_mapped_occurrence`), then fixed the composition check.
I am <model-slug> writing on behalf of <user>.
Declining this one. The nil case you describe is already unreachable. `resolve()`
returns early at `handlers.py:88` whenever the session is unset, which is the only
path that reaches this line. Leaving the behavior as-is.
只解决确实已关闭的事项:已修复并推送、提供证据后驳回,或已创建问题单并延后。切勿解决尚未回答其问题的讨论串。标记为已解决意味着问题已处理完毕;虚假声称已解决比保持讨论串未解决更糟。
非阻塞问题:每轮集中征询一次
不要逐条打断用户,也不要擅自决定。每轮在处理完阻塞问题后,将所有非阻塞问题汇总为一个列表,每项附上建议(现在修复、创建问题单或驳回),并让用户选择:
PR #123 第 2 轮审查。1 个阻塞问题已修复并推送(
a1b2c3d)。还有 3 个非阻塞问题:
- debate-review:提取
test_foo.py中重复的 fixture。建议创建问题单;涉及本次更改范围之外的文件- Codex:
Counter比较可以直接使用==。建议现在修复,只需改一行- Greptile:新增的 helper 缺少文档字符串。建议现在修复,改动很小 下一次推送修复 2 和 3,为 1 创建问题单?
无论用户如何决定,都要像处理其他审查意见一样关闭每个讨论串。所有延后的事项都要创建真正的问题单,并提供足够的上下文,以便几个月后仍可采取行动:附上指向讨论串的链接、文件路径,以及延后的原因。不能只写个标题。
在固定预算内重新触发
一次调用会获取初始审查结果,之后最多进行两次合并修复推送和两轮重新审查,除非用户明确要求继续。每次推送后,你都要自行启动下一轮。具体方式取决于审查者,因为它们有三种不同的触发方式:
-
debate-review 是本地脚本,不是机器人,适用于两种代码托管平台。运行后,它会在你等待时完成整个审查,并在发布审查结果后退出。无需提及,也无需轮询:
node "<debate-review skill-dir>/scripts/review-pr.mjs" <pr-url>(
<debate-review skill-dir>是该技能的安装位置;标准安装路径为~/.agents/skills/debate-review。)在后台运行它,继续处理其他工作,并在命令退出时立即获取结果。它会输出审查 URL;退出代码为 3 表示当前 head 已经审查过。每次运行只审查一个 head sha,因此推送后运行总会产生新一轮审查。一次运行需要 10 到 20 分钟。 -
Codex 是 GitHub 应用(没有对应的 GitLab 版本)。在 PR 评论中提及
@codex review,然后等待。它会在 8 到 15 分钟后回复,审查运行时的当前 head。请检查其审查结果中的commit_id,确认它涵盖了你的推送后再采信。 -
Greptile 等类似机器人会自行在每次推送后重新审查。不要主动触发它们;等结果出现后再处理其中的问题。
如果你正在等待机器人,最多等待到其通常响应时间之后 10 分钟。如果它没有响应或受到速率限制,将该审查者标记为不可用;不要等冷却期结束。唯一例外是在合并门禁处的合并门禁审查者,它没有超时限制(见“合并前”)。即使最后一次推送只涉及测试、文档或细微调整,也要进行一轮审查。如果最后一次推送未经审查,下面的合并门禁就毫无意义。
每次合并修复推送都运行一次仓库要求的门禁;同一 SHA 已通过的门禁绝不要重复运行。如果用户说“停止”“够了”或“把现有内容推上去”,请取消正在进行的轮询和耗时较长的门禁,运行能迅速完成的最小相关检查,发布安全的工作,说明尚未完成的门禁,并且不要触发新一轮审查。
每轮改动都应减少。如果第三轮与第一轮规模相当,说明存在系统性问题。应指出这一点,而不是继续反复处理。同类问题反复出现,通常意味着修复针对的是症状而非根因,值得明确说明。
达到预算上限时,停止并交接剩余问题、未解决的讨论、最后一次已审查的 SHA,以及不可用的审查者。绝不要把未经审查的 head 描述为干净。
合并前
合并门槛是:仓库的主要审查者针对确切的合并候选版本,也就是最终的 head sha,明确给出一轮无问题的审查。具体由哪位审查者负责,取决于仓库。
如果已安装 Codex,请在最终推送后提及 @codex review,并等待其回复。无问题的一轮审查,是 Codex 明确表示没有发现问题(“no findings”“good job”),且针对的是最终 head。尚未收到回复不算通过。Codex 会在推送后 8 到 15 分钟回复,在此时间段内合并,可能导致真正的问题在合并几分钟后才被发现。
如果由 debate-review 负责审查(在 GitLab 上通常只有它),请针对最终 head 运行它。无问题的一轮审查必须同时满足以下三点:.reviews 中存在该轮审查正文,且 debate_head 等于最终 head sha;debate_agreed 和 debate_contested 均为零;没有未解决的审查线程。如果正文缺失(运行可能在发布行内评论后失败),就说明尚未达到门槛;应重新运行,不要自行推断。
无论哪种情况,发现问题都意味着需要新一轮审查,而不是合并。若远超通常的等待时限仍无回复,应告知用户,而不是视为已批准。
提出合并前,重新获取并阅读 PR 的最新评论。监视器进入静默期后可能会漏掉迟到的一轮审查,而你上次检查后发布的评论,恰恰可能就是被合并时忽略的那条。
然后询问用户是否要合并。绝不要擅自合并。请汇报:运行了几轮审查、已修复的问题及其 SHA、驳回了哪些发现及原因、提交了哪些问题、仍未解决的线程数(最好为零),以及 CI 状态。是否合并由用户决定;此前的一切工作由你负责。
何时停止并提出问题
有些情况不该由你埋头处理:
- 机器人的发现是正确的,但要求的改动远超本 PR 的范围。
- 两个机器人针对同一行给出相互矛盾的结论,且代码、测试和明确说明的契约都无法解决矛盾。
- 重试后同一问题再次出现。第一次复现时,应重新验证根因,并在修复预算内再尝试一次;第二次复现则意味着你对 bug 的判断有误。
- 人工审查者的评论,始终如此。
- CI 因基础设施而非代码原因失败。
- 两轮修复预算已用尽。
- 用户要求停止、推送当前工作,或结束持续跟进循环。