claude-mem `babysit` 技能实战:用 Claude Code 看护 PR 直到合并就绪
这篇文章围绕 claude-mem 仓库中 plugin/skills/babysit/SKILL.md 展开。它是一份面向 Agent(如 Claude Code / Codex)的专用技能(Skill)定义:当用户要求"看护 / babysit / 持续盯一个 PR 直到可合并"时,Agent 会按该技能中的工作流与 GitHub CLI 命令,轮询 checks、过滤未解决 review threads、修复真实问题并持续验证,直到 PR 真正"干净"。读完本文,你将掌握 babysit 技能的完整决策规则、可直接复制的 gh / GraphQL / jq 命令序列,以及它在本仓库中配套的源码级实现(scripts/pr-babysit-status.ts 与测试 tests/scripts/pr-babysit-status.test.ts)。
babysit 是什么:一次技能型"PR 看护闭环"
在 claude-mem 仓库中,技能存放在 plugin/skills/ 目录,每个技能目录内以 SKILL.md 声明自身能力。babysit 技能的文件头通过 YAML frontmatter 暴露给宿主 Agent:
---
name: babysit
description: Watch a pull request or review cycle until it is ready to merge. Use when asked to babysit, monitor, or keep checking PR comments, reviews, and CI until all actionable issues are resolved.
---
这段描述本身就是技能路由的触发条件:当用户提出 "babysit"、"monitor"、"keep checking PR comments / reviews / CI until resolved" 等意图时,Agent 应加载本技能。技能的核心纪律是一条硬性约定:
Stay with the PR until it is actually clean. Do not stop after one check pass if comments or review threads are still unresolved.
也就是说,单次检查通过不等于任务完成——只要仍有未解决的评论或 review threads,就必须继续留在循环里,这正是与普通"看一眼 PR 状态"式指令的本质区别。从仓库版本史看,该技能于 claude-mem@12.7.1 正式打包发布,并配套了"低噪声的 PR babysit 状态查看助手"(见 CHANGELOG.md 相关条目),12.7.x 系列将插件技能总数推进到 12 个。
核心工作流:七步直到干净
SKILL.md 给出了明确的循环流程,可归纳为如下七步:
- 定位目标:确认 PR 编号、分支名与目标基分支(base branch)。
- 初检:确认 PR 不是 draft,检查 mergeability、CI checks、review decision、普通评论与 review threads。
- 等待检查:以实际可行的间隔轮询 pending checks,默认 30–60 秒,除非用户指定其他节奏。
- 消化评论:读取新评论与未解决 review threads;机器人(bot)摘要可作参考,但必须对照代码核实其"可操作结论"是否属实。
- 修复并推进:对真实问题做聚焦提交(focused commits)、跑相关测试/构建、推送,然后回到第 2 步继续。
- 解决陈旧线程:只有确认代码或生成产物已真正回应评论之后,才 resolve 相应 review thread。
- 收尾判定:仅在以下条件同时满足时停止——checks 全部通过或按预期跳过、review decision 可接受、无剩余可操作评论、无未解决 review threads。
这条循环的关键词是"验证闭环":评论 → 修复 → 推送 → 重检,任何中间态都不算完成。
GitHub CLI 粗检:gh pr view 的 JSON 字段
技能的粗检命令直接封装在 scripts/pr-babysit-status.ts 的 fetchPr 中,与 SKILL.md 给出一致的字段集合:
gh pr view <number> --json \
number,state,isDraft,mergeable,mergeStateStatus,reviewDecision,headRefOid,statusCheckRollup,url
各字段用途如下:
| 字段 | 含义与看护判断 |
|---|---|
number |
PR 编号 |
state |
OPEN / MERGED / CLOSED,决定了是否还要继续盯 |
isDraft |
若为 true,第 2 步就应排除:draft 不计入可合并目标 |
mergeable |
GitHub 计算的可合并性(MERGEABLE / CONFLICTING / UNKNOWN) |
mergeStateStatus |
更细粒度:BLOCKED、BEHIND、DIRTY、CLEAN 等,反映是否还需更新分支 |
reviewDecision |
APPROVED / CHANGES_REQUESTED / REVIEW_REQUIRED |
headRefOid |
头部 commit 的完整 SHA,后续所有"是否过期"判断都以它为锚点 |
statusCheckRollup |
CI 检查汇总 |
url |
PR 链接,用于最终报告 |
headRefOid 极其关键:判断一条 review 是否针对"当前 head"、机器人评论是否已过时,都依赖把评论/评审的 commit_id 与它比对。脚本里 currentHeadReviews 正是用 review.commit_id === headSha 来过滤"当前 head 上的评审"(scripts/pr-babysit-status.ts)。
精确获取仓库身份与未解决线程
粗检通过后,需要精确数据。SKILL.md 先演示如何解析仓库 owner/name:
repo_json=$(gh repo view --json owner,name)
owner=$(jq -r '.owner.login // .owner.name' <<<"$repo_json")
repo=$(jq -r '.name' <<<"$repo_json")
随后用 GraphQL 拉取未解决 review threads。查询按 100 条分页,第一页不传 cursor,之后用上一页 endCursor 通过 -f cursor="$cursor" 继续,直到 hasNextPage 为 false:
gh api graphql \
-f query='query($owner:String!,$repo:String!,$number:Int!,$cursor:String){repository(owner:$owner,name:$repo){pullRequest(number:$number){reviewThreads(first:100,after:$cursor){pageInfo{hasNextPage endCursor}nodes{id,isResolved,isOutdated,path,line,comments(last:1){nodes{author{login},body,createdAt,url}}}}}}}' \
-f owner="$owner" -f repo="$repo" -F number=<number>
注意 -F number=<number> 使用 -F(非 -f),保证数字以 Int! 类型传入。每个 thread 节点只取该线程最后一条评论(comments(last:1)),因为线程的最新状态才代表当前诉求。
多线程分页:一个可复制的 bash 看护循环
当 PR 可能存在大量 review threads 时,SKILL.md 给出了完整的分页循环,用 jq 的 @tsv 输出便于人/机器阅读的紧凑行。这段逻辑与脚本中对 bot 评论的"低噪声提取"哲学一致——只看未解决、未过时的线程,把 Markdown 折叠为单行摘要:
thread_query='query($owner:String!,$repo:String!,$number:Int!,$cursor:String){repository(owner:$owner,name:$repo){pullRequest(number:$number){reviewThreads(first:100,after:$cursor){pageInfo{hasNextPage endCursor}nodes{id,isResolved,isOutdated,path,line,comments(last:1){nodes{author{login},body,createdAt,url}}}}}}}'
cursor_args=()
while :; do
page=$(gh api graphql -f query="$thread_query" -f owner="$owner" -f repo="$repo" -F number=<number> "${cursor_args[@]}")
printf '%s\n' "$page" | jq -r '.data.repository.pullRequest.reviewThreads.nodes[]
| select(.isResolved==false)
| [.id,.path,(.line//""),(.isOutdated|tostring),(.comments.nodes[-1].author.login//""),(.comments.nodes[-1].body|gsub("\n";" ")|.[0:240])]
| @tsv'
jq -e '.data.repository.pullRequest.reviewThreads.pageInfo.hasNextPage' >/dev/null <<<"$page" || break
cursor=$(jq -r '.data.repository.pullRequest.reviewThreads.pageInfo.endCursor' <<<"$page")
cursor_args=(-f cursor="$cursor")
done
拆解这段脚本:
- 输出列依次为 thread
id、文件path、line(为空则//""兜底)、isOutdated布尔、最后评论作者 login、最后评论正文(换行折叠为空格并截断 240 字符)。 select(.isResolved==false)只保留未解决线程,是看护的主战场。- 分页靠
jq -e判定hasNextPage:表达式结果非真时退出码非 0,|| break跳出循环;为真则取endCursor追加-f cursor继续下一页。
如果不需要循环、只想"过滤一次",SKILL.md 提供等价的单条 jq 过滤片段,逻辑与上述循环内完全一致。
只有验证通过才 resolve:正确关闭线程
resolve 一个 review thread 是有副作用(修改仓库评审状态)的写操作,因此 SKILL.md 将其严格限制在"修复已获验证"之后。所用 mutation:
gh api graphql \
-f query='mutation($threadId:ID!){resolveReviewThread(input:{threadId:$threadId}){thread{id,isResolved}}}' \
-f threadId=<thread-id>
执行后返回的 thread.isResolved 可作幂等确认。把"解决陈旧线程"列为独立于修复的第 6 步,本身就是防呆设计:它防止 Agent 在只看到"评论存在"而未核对代码时就随手勾掉线程。
机器人评论处理:参考但不盲信
SKILL.md 对机器人评论给出了明确姿态:bot 摘要有用,但可操作结论必须对照代码验证。对应实现见 scripts/pr-babysit-status.ts:
- 通过
BOT_LOGIN_PATTERN = /(coderabbit|greptile)/i识别常见代码评审机器人; - 只取
commit_id === headSha(当前 head)上的 bot 评审与评论,排除"Addressed in commit"这类已被处理的文本; extractActionableHints只抽取机器人总结中的可操作要点,例如**Actionable comments posted: N**、形如- Line 10: ...的列表项、首个加粗结论等,丢弃<details>、纯 Markdown 渲染噪声与超链接,把每段压缩成 ≤4 条、每条 ≤140 字符的提示(scripts/pr-babysit-status.ts)。
测试 tests/scripts/pr-babysit-status.test.ts 验证了这一"低噪声提取"行为:测试夹具保证能取出 "Actionable comments posted: 2" 与逐行建议,同时断言不会把 <details> 里的 "Prompt for all review comments" 这类说明文字混进结果。
运行规则与收尾报告
SKILL.md 的 Operating Rules 给出看护期间与收尾时的行为准则:
- 长检查期间保持 watcher 运行,不要因为一次轮询无果就退出。
- 生成产物参与分发时,resolve 评论前必须验证源码与生成产物一致(例如"源码改了但忘记重新生成锁定文件"正是常见翻车点)。
- bot 报告指向过期代码时,先确认该线程是
isOutdated,还是已在新 head 中被处理。 - 最终报告前做一次全新扫描:PR 状态、未解决线程、近期评论、本地
git status都要重看一遍,避免报告基于陈旧快照。
收尾报告必须携带具体证据而不是泛泛结论:
- 最新 commit SHA(即
headRefOid); - 检查项名称与结果;
- 剩余未解决线程数量;
- 实际运行过的测试;
- 是否有未触碰的本地脏文件(dirty local files)需要告知用户。
这一点与脚本的输出结构吻合:main 会打印 PR 概要(含 head/base SHA、mergeable、mergeStateStatus、reviewDecision)、按 bucket 分组的当前 head checks、基分支保护规则摘要,以及"可操作 bot 提示"列表(scripts/pr-babysit-status.ts)。
分支保护意识:理解"必须绿"与"可接受跳过"的边界
工作流第 7 步提到 "checks are passing or intentionally skipped"——要正确判断一个 check 该不该等,需要知道基分支到底强制了哪些检查。脚本提供了这一补充视角:
fetchBranchProtection读取repos/{owner}/{repo}/branches/{branch}/protection,失败则返回undefined(可能无保护或无权访问);summarizeProtection将保护规则翻译成人类可读清单:必过检查数量与strict属性、需要几个 approval、是否 dismiss stale reviews、是否要求 code owner review、是否 last-push 后需重新 approve、是否强制 conversation resolution、是否要求签名提交、是否对 admin 生效、是否允许 force push(scripts/pr-babysit-status.ts)。
对应测试验证了"缺少部分字段时不报错"的容错设计(tests/scripts/pr-babysit-status.test.ts)。看护 Agent 结合这套信息,就能判断某个失败/待跑检查是必须等待的硬门槛,还是可安全视为"跳过即可"。
把技能落地到日常:配套 CLI 助手的用法
babysit 是技能(指导 Agent 行为),而 scripts/pr-babysit-status.ts 是把"快照当前 PR 状态"固化为 CLI 的伴生工具,二者互补:技能负责决策与循环,脚本负责一次性、低噪声地把状态摊开给 Agent 阅读。仓库通过 package.json 暴露了快捷命令:
npm run pr:status # 解析当前分支对应的 PR
npm run pr:status -- <number> # 指定 PR 编号
底层是 Bun 脚本(bun scripts/pr-babysit-status.ts)。直接运行时约等于执行一次工作流第 2 步的自动化快照,主要输出:
- PR 元信息(标题、URL、head/base、draft/mergeable/mergeStateStatus/reviewDecision);
- 按
fail / pending / pass / skipping / cancel分组、且仅针对当前 head 的 checks; - 基分支保护规则摘要;
- 当前 head 上的 review 记录(作者、时间、state);
- 从 bot review 与行内评论中提炼的可操作 hints。
使用前置条件同样在 checkPrerequisites 中固化:必须位于 git 仓库内、gh 可用且已完成 gh auth login,否则脚本会直接报错退出(scripts/pr-babysit-status.ts)。
小结:把"再看一眼"升级为"验证闭环"
babysit 技能的技术价值不在单条命令,而在于把 PR 看护拆成一个可持续、可验证、带退出条件的状态机:粗检(gh pr view)→ 精确扫描(GraphQL 未解决线程 + 分页)→ 有节制地写状态(验证后 resolve)→ 携带证据收尾。它与 scripts/pr-babysit-status.ts、tests/scripts/pr-babysit-status.test.ts 共同构成了 claude-mem 仓库中一套可复用、可测试的 PR 看护范式——既可作为 Claude Code / Codex 的加载型技能使用,也可当作 pr:status CLI 纳入你自己的 CI 或日常 review 流程。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0624
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00