mcpField
P
pr-babysit

pr-babysit

github.com/kirkchen测试与质量0423 次安装8 周前更新
代码仓库

关于 pr-babysit

用于持续跟进 GitHub PR 或 GitLab MR 的技能,直至 CI 通过且可执行的审查意见得到处理。它会分类评论、提交针对性修复、在相关讨论串中回复,并检查重复或由修复引入的问题。

主要功能

  1. 01根据仓库 remote 识别 GitHub 和 GitLab
  2. 02分类审查意见与失败检查
  3. 03以 conventional commits 提交针对性修复
  4. 04发布回复并确认其位于讨论串内
  5. 05轮询 CI 并运行自反馈循环关卡

适用场景

  1. 01处理开放 PR 或 MR 中可执行的评论
  2. 02修改代码前诊断失败的 CI 检查
  3. 03停止处理反复修复后仍未解决的问题

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/kirkchen/cadence --skill pr-babysit

安装后在 Claude Code 中输入/pr-babysit确认已加载。

详细介绍

pr-babysit

持续跟进 PR/MR,直到 CI 全部通过且所有有效的评审反馈都得到处理。支持 GitHub PR(通过 gh)和 GitLab MR(通过 glab)——根据 git remote get-url origin 自动检测(github.com → gh;gitlab.com / 自托管 GitLab → glab)。

参数

$ARGUMENTS — 可接受:

  • 为空 → 当前分支的开放 PR/MR
  • 一个数字 → 当前仓库中对应 IID 的 PR/MR
  • 一个 URL → 从中解析 owner/repo + IID

如果当前分支匹配到多个 PR/MR,则停止并询问用户要处理哪一个。

回复语言

发布到 PR/MR 讨论串中的回复正文——即每个回复模板锚点后面的 <what changed> / <reason> / <evidence> 内容,以及 Wontfix Template 字段中的正文——使用 PR/MR 描述的主要语言。其他所有内容均使用英语:锚点短语本身、Wontfix Template 字段标签、常规提交前缀、race 元标签、P-code / 严重程度 / 理由标记(与 pr-review 的 Output Language 中的规范集合相同)。

如果 PR 描述缺少实质性正文,则依次回退到关联 issue 正文,再回退到英语。

终端输出(第 6 步运行报告、Gate A / Gate B 审计消息、不可见发现提示)保持英语——这些内容会发送到调度器会话,而非 PR。

循环

1. 快照

获取:PR/MR 元数据 + head SHA、所有检查 / 流水线作业、所有评审评论、所有普通评论、所有评审讨论串 / 讨论(含已解决状态)、当前用户登录名。

对于此 PR 中你之前回复过的每个讨论串,缓存 {file path, rule code or primary keyword, your reply summary}——供第 2 步去重使用。

按内容筛选,而非按作者筛选:

  • 丢弃正文仅包含 CI 状态行的评论(构建通过/失败、部署事件、“流水线已成功”)。这些是噪声。
  • 保留包含可操作信号(Suggestion / Warning / Critical / Issue / quality gate / failed / 行级评审备注)的任何评论——即使来自机器人账号。AI 评审机器人、SonarQube、Snyk 属于_内容_机器人,而非噪声机器人。
  • 丢弃你之前的回复和已解决的讨论串。
2. 分类

硬性门槛——不可见发现:如果某项检查失败,但实际发现列表位于 CLI 无法访问的外部仪表板(SonarQube、Snyk、DataDog 测试报告等——无令牌、无可访问的 API 端点),则立即停止并请用户粘贴这些发现。不要在本地复现,也不要把“猜测”的发现当作完整周期来处理。不要先处理不相关的反馈,而让不可见发现悬而未决。根因诊断以能够看到发现为前提;看不到时,必须首先触发此门槛。

跨轮去重——对于每条新评论,检查第 1 步的缓存:

  • 文件相同且规则代码(例如 CA1031)相同,或主关键词相同,且对应的讨论串已回复过 → 视为重复。用一行回复链接到较早的讨论串,不要重新实现或解释。
  • 同一问题在尝试修复后仍连续 3 轮存在 → 升级为 needs-user-input(机器人陷入停滞;需要用户来打破僵局)。

反馈——将剩余的未解决评论分类:

  • 有效 — bug、安全问题、逻辑错误、明确可执行的建议
  • 讨论 — 含义模糊、可能误读了源码、设计权衡、范围不明确 → 不要自主回复,不要实现——收集起来交由用户处理
  • 超出范围 — 明显不属于此 PR 声明的目标范围 → 收集起来交由用户处理

检查——对于每项失败的检查:通过 CLI 获取失败日志,并在尝试修复前诊断根因(没有明确原因就不要打补丁)。区分真实失败与偶发失败;仅在有证据表明是偶发失败时才重试。如果失败日志不包含实际发现,则触发上述不可见发现门槛。

3. 处理(仅限有效问题和真实故障)

对于每项:

  1. 实施修复。
  2. 提交小型提交,遵循 Conventional Commits 格式,每个提交只包含一项逻辑更改。类型速查:行为变更 → fix;不改变行为的结构调整/可读性改进(包括 lint 抑制)→ refactor;非源代码内容(CI、husky、工具)→ chore;纯文档 → docs。
  3. 在原始评论/讨论串中回复(使用下方模板表)。
  4. 确认回复已发布在讨论串内,而非顶层评论中(参见下方“回复端点”)。

各平台的回复端点——端点不匹配会产生孤立的顶层评论:

操作GitHubGitLab
回复审查讨论串POST /repos/{O}/{R}/pulls/{id}/comments with in_reply_to_idPOST /projects/:id/merge_requests/{iid}/discussions/{disc_id}/notes
新建顶层评论POST /repos/{O}/{R}/issues/{id}/commentsPOST /projects/:id/merge_requests/{iid}/notes

发布回复后,获取讨论/审查讨论串并确认你的评论位于讨论串内(评论数 ≥ 2,且包含你的用户名)。如果评论发到了顶层 → 删除它,并通过正确的端点重试。

回复模板——根据情况选择:

情况模板
已采纳并修复Addressed in <SHA> — <what changed>.
有意为之的设计,不作更改Deliberate design — <reason>. <spec or codebase ref>.
此 PR 中较早的回复已处理相同问题Same as the earlier <topic> thread — <link>.
Bot 的前提有误,不予修复Won't fix — premise doesn't hold. <evidence: file:line / spec section>.

“有意为之”/“不予修复”模板旨在保持语气中立、以证据为依据——没有模板时,回复往往会变成防御性辩解或实现细节堆砌。

锚点短语保留英文;仅根据 PR 描述所用的语言调整每个锚点后面的正文。参见回复语言。

Lint/警告抑制——任何 #pragma、// eslint-disable、# noqa、@SuppressWarnings 等内容都必须包含:

  • (a) 同一行内的理由注释;以及
  • (b) 对规范章节的引用,或现有代码库中因相同原因使用相同抑制方式的先例(file:line)。

若 (a) 或 (b) 任一项不可用 → 不要抑制,而应重构。适用 (b) 时,在提交信息中注明先例 file:line。

硬性规则:

  • 不得对已推送的提交使用 --amend
  • 不得 --force-push
  • 除非审查者明确要求,否则不要将 GitLab 讨论标记为已解决
  • 不得在未回复的情况下关闭任何审查者讨论串
  • 对同一项修复连续失败 3 次 → 停止,记录失败原因及需要质疑的假设,并交还用户处理(遵循全局 CLAUDE.md)
4. 推送并等待

执行 git push。轮询 CI,直到进入终态(GitHub:gh pr checks --watch;GitLab:轮询 head_pipeline.status,直到成功/失败/已取消)。

4.1 记录 prior_fix_range

步骤 3 的修复提交合入且步骤 4 已推送后,记录涵盖本轮修复的 SHA 范围。此范围是以下两个下游使用方的权威事实来源:

  1. 下一轮的 pr-review 调用——将其作为 prior_fix_range 输入传入,以便 pr-review 的增量模式能够应用丢弃信号(B),识别自身引入的表面
  2. 下文步骤 4.5 的 Gate B——使用同一范围和同一行级归因机制
# After step 4 push, before invoking the next pr-review iter:
FIRST_FIX_SHA=$(git log --format='%H' "$PREV_HEAD..HEAD" | tail -1)   # oldest fix in this iter
LAST_FIX_SHA=$(git rev-parse HEAD)                                    # newest fix in this iter
PRIOR_FIX_RANGE="${FIRST_FIX_SHA}^..${LAST_FIX_SHA}"

将 PRIOR_FIX_RANGE(以及作为下一轮 $PREV_HEAD 的 $LAST_FIX_SHA)保存到 babysit 状态文件或会话环境中。如果本轮只推送了一个提交,则 FIRST_FIX_SHA == LAST_FIX_SHA,范围将简化为 <sha>^..<sha>。

如果本轮未推送任何提交(仅重新运行 CI)→ 无需记录修复范围;下一轮跳过 Gate B 的自身引入检查,但仍照常运行 Gate A。

为何不在 Gate B 执行时再计算:在推送时计算可确保范围精确对应解决第 (N-1) 轮问题的提交。在 Gate B 执行时才计算,可能会包含用户在两轮之间手动编辑分支时产生的无关提交。

4.5 自反馈循环门控

推送本轮修复并等待 CI 通过后,在返回步骤 1 之前,运行两个子门控,以捕获不同的自反馈故障模式。如果没有这两个门控,自动化审查器与自动化 babysitter 搭配时,可能会连续 N 轮追查测试卫生问题(Gate A),或追查层层叠加的竞态表面(Gate B)。

两个门控都会解析此 PR 上 pr-review 的行内评论:

gh api repos/$OWNER/$REPO/pulls/$N/comments \
  --jq '[.[] | select(.body | contains("").j),
          race_meta: (.body | capture("\\[window=(?<w>[^,]+), damage=(?<d>[^,]+), recovery=(?<r>[^\\]]+)\\]") // null)}]'

仅保留自上一轮 HEAD SHA 之后创建的问题(即本轮引入的新问题)。

Gate A:收益递减(仅涉及卫生问题的轮次)

触发条件:以下条件全部满足:

  • 本轮有 ≥1 个新的 pr-review 问题
  • 没有任何新问题的 justification ∈ {Reachable, Precedent, Asymmetric, Historical}
  • 所有新问题的 justification=Hygiene(或缺失——缺失时视为 Hygiene)

操作:停止自动循环,跳过步骤 5 的常规判断,跳转到步骤 6,并执行:

Status: needs-user-input (diminishing returns)

This iter's pr-review surfaced only hygiene findings — no Reachable / Precedent /
Asymmetric / Historical justification on any new finding.

Hygiene followups (N):
  <list — id, slug, file:line, one-line failure mode>

Continuing the loop will likely surface more hygiene from the same code paths.

Your call:
  (s) ship — open a single follow-up issue collecting the hygiene items, mark PR ready-to-merge
  (p) polish — keep looping (override the gate for this round)
  (r) re-review-full — challenge whether the self-loop missed anything (force `mode=full` on next pr-review)
Gate B:收敛审计(竞态叠加轮次)

用于捕获以下故障模式:第 (N-1) 轮的修复引入新的竞态/状态转换表面,审查器将其标记为 Reachable 问题,下一轮的修复又引入另一个竞态表面,如此无穷无尽。Gate A 无法捕获此情况——这些问题带有 justification=Reachable,单独来看都成立;只有在聚类层面才能看出其发散趋势。

prior_fix_range:使用步骤 4.1中记录的范围。该范围与传给 pr-review 增量模式调用的范围相同,因此 Gate B 的自身引入检查和 pr-review 的丢弃信号(B)依据的是完全相同的证据。如果步骤 4.1 未记录任何内容(第 N-1 轮未推送提交),则不触发 Gate B——没有第 (N-1) 轮的修复表面可供收敛审计。

触发条件:以下条件全部满足:

  • iter ≥ 3(前两轮属于正常审查节奏,不视为发散)
  • 本轮有 ≥2 个新问题引用 prior_fix_range 内的 file:line——即批评第 (N-1) 轮新添加的表面
  • 其中 ≥2 个问题属于竞态类别——按以下条件的逻辑或进行检测:
    • (i) 带有 pr-review 竞态类别元数据要求所规定的 [window=..., damage=..., recovery=...] 元数据,或
    • (ii) slug/类别关键词匹配以下任一项:race | TOCTOU | concurren | sweep | lifecycle | state-transition | debounce | claim | lease | fence | stale | orphan | race-window,或
    • (iii) \bwindow=(即使完整元数据格式错误,也能匹配元数据标签前缀),或 atomic.*race | race.*atomic(要求两者同时出现,以避免误匹配数据库事务中的 atomic 和前端视口中的 window) 关键词设计说明:有意排除单独出现的 window 和单独出现的 atomic——它们会在速率限制器、视口和数据库事务正确性相关注释中产生误报。TOCTOU 是规范的安全竞态术语,与绕过 meta-tag 路径的 Codex 发现相匹配。debounce / claim / lease / fence 涵盖分布式锁定词汇;stale / orphan 涵盖清理竞态描述。

如何验证 prior_fix_range 中的 file:line:

git diff --name-only $prior_fix_range                  # files touched
git diff -U0 $prior_fix_range -- <file>                # line-level attribution

操作:停止自动循环,针对该集群运行 Convergence Audit。对于每个竞态类别的发现,应用 Wontfix 模板 的五步决策:

  1. 时间窗口:估算竞态操作之间的毫秒/秒/分钟/小时数(如果存在 meta tag,则使用它)
  2. 损害:分类为 data-loss | deadlock | inconsistency | latency | marginal
  3. 非对称检查:失效模式是否涉及安全、数据完整性或计费?
  4. 缓解成本:建议的修复是否会引入新的竞态面?
  5. 恢复路径:容错能力/下一个 webhook/清理器是否能覆盖此竞态?

每项发现的审计判定:

判定适用情况
modify(非对称)理由属于非对称情形(安全/数据丢失/数据完整性/计费)→ 始终修改,无论缓解成本如何
modify(损害门槛)damage 值为 data-loss / deadlock / inconsistency → 即使理由不正式属于非对称情形,也要修改。这些损害类别不存在可接受的“容错”答复
modify(安全修复)非非对称情形,且 damage ∈ {latency, marginal},但缓解措施不会引入新的竞态面 → 修改(不存在竞态中的竞态风险)
wontfix-with-template非非对称情形 + damage ∈ {latency, marginal} + recovery=has + 缓解措施会引入新的竞态面 → 使用 Wontfix 模板回复。必须满足全部五项条件;缺少任何一项 → 回退为修改
defer-followup问题合理,但解决需要基础设施(例如真实数据库测试、模式迁移、新的后台任务),应归入后续问题

向用户报告:

Status: convergence-audit (race-of-race detected)

iter (N-1) fix surface attracted N race-class findings this iter (cluster):
  <id> <slug> @ <file:line>  window=<w> damage=<d> recovery=<r>
  ...

Audit verdict per finding:
  <id>: modify    — <reason: Asymmetric / mitigation safe / etc>
  <id>: wontfix   — <five-field summary from Wontfix Template>
  <id>: defer     — <followup issue suggestion>

Your call:
  (a) accept all verdicts (post wontfix replies via template, address modify items, open defer issues)
  (m) modify a specific verdict — say which finding-id and target verdict
  (s) ship — accept all wontfix + defer as-is, mark PR ready-to-merge
  (p) override audit — treat as normal iter, loop back to step 1

以下情况不会触发 Gate B:

  • 集群包含任何非对称发现——非对称情形(安全/数据丢失/数据完整性/计费)会像 pr-review 的丢弃信号(B)一样绕过收敛退出机制。应展示并修改
  • iter < 3——早期迭代属于正常审查节奏
  • 缺少竞态类别 meta,且没有 slug/category 关键词匹配——这能使门槛仅针对实际竞态领域;非竞态收敛(例如命名争论)会回退到 Gate A 或正常流程

原理:Gate A 捕获所有内容都属于卫生问题的迭代;Gate B 捕获多个各自有效的竞态发现聚集在新引入的表面上的迭代。二者共同覆盖两种主要的自反馈失效模式,同时不会压制真正的非对称发现或第三方信号(没有 pr-review 元数据的 Codex / SonarQube / Snyk 发现会绕过这两个门,并通过正常的第 2 步去重 + 3 轮升级流程)。

4.6 Wontfix 模板

供步骤 4.5 Gate B(收敛审计)使用,也可作为手动回复模板,用于那些修改后会引入新的竞争面的问题,例如竞态、状态、清扫和原子操作类问题。

五个字段均为最低必填项。缺少任何一项 → 问题就应当修改,而非标记为 wontfix。

Wontfix — deliberate trade-off.

Race window: <ms / s / min / hr> between <op A> and <op B>.
Precondition: <only fires when X is in Y state for N+ time>
Damage if race fires: <not data-loss / not deadlock / only X happens N seconds earlier than ideal>
Recovery path: <new event / cron sweeper / next webhook covers it; user-visible behavior unchanged>

Asymmetric check: <not security / not data-loss / not data-integrity / not billing>
Mitigation cost: <atomic re-check / two-step merge into transaction is doable, but introduces new race-of-race surface at X>

Acknowledged as known trade-off; fault tolerance covers genuinely <abandoned / stranded / dropped> class.
Tracking: <if needed, opened followup issue X>

字段含义:

  • 竞态窗口 — 具体的时间估算,不能只写“很短”。紧凑 CAS 用 ms,清扫周期之间的间隔用 min,cron 生命周期用 hr。审查者需要知道具体量级才能做判断。
  • 前置条件 — 竞态要产生影响,系统必须已处于什么状态。如果前置条件很少出现或系统已处于降级状态,则可以接受该竞态。
  • 损害 — 具体描述用户或数据会出现什么情况,不能只写“可能会有问题”。如果你无法用一行描述损害,这个问题可能实际上并非可达。
  • 恢复路径 — 必须指出具体机制(下一个 webhook / 清扫器运行 / cron / 容错重试)。“大概没事”不算恢复路径。
  • 非对称性检查 — 明确声明该问题不涉及安全性、数据完整性或计费。非对称性问题不可标记为 Wontfix —— 应当修改。
  • 缓解成本 — 指出拟议修复会引入的新竞态面。“竞态的竞态”是关键论据。

参考示例:PR #148 中的 sweepAbandonedTasklessThreads 两次 UPDATE 竞态 —— Codex 标记“在放弃排队事件之前重新检查线程状态”;两个清扫 UPDATE 之间的竞态窗口只有几毫秒,前置条件是线程已搁置至少 1 小时,损害为 marginal(已搁置的事件只会比理想情况提前几秒进入终态),恢复路径是新的 webhook 会触发重新激活门控。最终发布了 Wontfix,PR 也已合并。

不适用情况:

  • 五个字段中有任何一项无法如实填写 → 问题确实存在,应当修改。Wontfix 模板只适用于修改会引入同等或更严重竞态面的特定情况,不是通用的拒绝模板。
  • 开发阶段自我审查场景(代码作者和判断理由推理者之间没有独立会话):不要根据主会话的记忆填写这些字段。Babysit 通常在与代码作者分开的会话中运行,这正是 Wontfix 模板能够安全使用的原因 —— babysit 会话此前未对设计作出承诺,因此可以如实推理损害 / 恢复 / 缓解成本。在开发阶段的自我审查循环中(同一会话既编写代码,又推理问题),作者叙事偏差会不断叠加 —— 在对 6 个 LLM 测试的各种框定条件中,无缺陷框定导致的检测率下降最明显(Mitropoulos 等人,衡量并利用 LLM 辅助安全代码审查中的上下文偏差,arXiv:2603.18740)。暂停流程,并选择 (a) 将判断交给独立会话,或 (b) 使用新启动的判断子 Agent,要求它根据代码独立推导 damage / recovery / mitigation cost,而不是依据问题对象中的字段。Deriver 模式的判断子 Agent 尚未开发为技能 —— 在此之前,将开发阶段的 wontfix 判断视为建议,并告知用户。
5. 做出决定
  • ✅ 所有检查均通过,且所有有效反馈均已处理 → 报告(步骤 6)
  • 🟡 新增评论 / 检查状态在周期中发生变化 → 返回步骤 1
  • 🔴 达到 3 次失败停止门槛、不可见问题门槛、去重 3 轮升级门槛,或确实需要人工判断 → 报告,并标记为 blocked / needs-user-input
6. 报告(运行结束时执行,不自动合并)
PR/MR: <link>
Status: ready-to-merge | needs-user-input | blocked
Checks: <green>/<total>
Addressed (this run): <list of SHA → comment ref + one-liner>

Awaiting your decision:
  Discuss (I did NOT reply): <list with comment text + my read of the ambiguity>
  Out-of-scope: <list>  → open follow-up issues for any of these? (y/N per item)

Blockers (if any): <description + what I tried>

Next command: gh pr merge --squash <id>   # or: glab mr merge <id>

报告后,如果存在范围外事项,只询问一次:哪些事项需要创建后续问题?只创建用户选定的事项(gh issue create / glab issue create),并编辑每条 MR/PR 评论中的报告回复,添加新问题的链接。

未经询问,我绝不会做的事

  • 根据 Discuss 项目回复、关闭或实施更改——列出这些项目,然后停止。
  • 未先与用户确认清单,就为 Out-of-scope 项目创建后续问题。
  • 合并 PR/MR。即使所有检查都已通过,也只报告可以合并,并让用户自行执行合并。
  • 强制推送、修改已推送的提交、跳过钩子(--no-verify)或绕过签名。
  • 无休止地循环——如果一轮没有产生新工作,也没有解决任何问题,就停止并报告。

评论

支持 Markdown