awesome-python PR 评审自动化:review-prs 技能的五步 Agent 工作流解析
awesome-python 是一个刻意做成"精选清单"(shortlist)而非"目录"(catalog)的开源项目:每个用例(Use Case)只保留最显然的 3 个选择和至多 2 个挑战者,条目上限硬性卡在 5 个。这意味着绝大多数 PR 会被拒绝,而评审流程必须同时做到机械化(格式、重复、活跃度)与编辑化(是否配得上这个 slot)。本仓库在 .claude/skills/review-prs/SKILL.md 中把整套 PR 评审流程沉淀为一个 Claude Code 技能(Skill):给定 PR 队列,Agent 按 Fetch → Screen → Judge → Act → Report 五个步骤,把开放队列中的每个 PR 收敛到三种终态之一——合并(merged)、关闭(closed)或明确搁置(parked/kept open)。读完后你能掌握:如何把"规则文档 + Agent 工作流"解耦设计成一个可复用的维护型 Agent 技能,以及其中每一步的 gh 命令、委托调用和人工确认点是怎么落地的。
技能定位:规则与工作流的解耦
技能文件采用标准的 SKILL.md 结构,YAML frontmatter 定义了三个字段:
- name:
review-prs,技能标识; - description:说明触发条件——维护者要求"评审 PR、处理 PR 队列、或判断某个特定 PR 是否该合并"时激活,并明确了工作流骨架:"从 diff 筛查 → 委托 audit-the-list 做准入判断 → 在 GitHub 上合并或关闭";
- argument-hint:
[PR numbers],即技能可接收具体 PR 编号作为参数,不传则处理整个队列。
文档开头一句话点出了整个设计的核心原则:
规则住在 CONTRIBUTING.md(Quality Requirements、Admission、Review Process、Automatic Rejection)里——这个技能是应用这些规则的工作流,不是规则的第二份拷贝。
这是一种典型的"单一事实源"设计:准入规则只维护在 CONTRIBUTING.md 中,技能文件只描述执行过程,避免规则在两个地方漂移。这也与 CLAUDE.md 的入口规则呼应——"每次增删条目(包括直接提交,不只是 PR 评审)都要应用 CONTRIBUTING.md 的规则"。
理解这个工作流之前,需要先理解它所执行的那套规则,这也是后续每一步筛查和判断的依据。
前置知识:工作流依赖的规则底座
CONTRIBUTING.md 定义了三块核心规则,review-prs 的每个步骤都在消费它们:
质量要求(Quality Requirements)——所有提交必须同时满足五条:
- Serves Python Developers:Python 开发者在 Python 工作中实际使用它。实现语言和打包方式无关——uv 和 ty 是 Rust 写的,agent 技能包是 markdown,都属于清单范围;一个没人用于 Python 工作的纯 Python 项目则不属于。
- Active:最近 12 个月内有提交;
- Stable:生产可用,非 alpha/beta/experimental;
- Documented:有清晰的 README,含示例和用例;
- Established:仓库至少存在 1 个月。
准入规则(Admission)——定义"用例"与容量上限:
- **Use Case(用例)**是清单结构定义的:每个 Subcategory(子分类)是一个用例;没有子分类的 Section 整体是一个用例。结构变更(新 section、新 subcategory、拆分过大的用例)只能由维护者执行——条目 PR 永远不能创建它所需要的子分类;
- 每个用例至多 3 个 Obvious Choices(资深开发者脱口而出的选择)+ 2 个 Challengers(尚非显然但可信的继任者,准入需要采纳趋势证据而非单纯人气);硬性上限 5 个条目/用例;
- Displacement(置换):用例满员后唯一的进入方式——PR 必须点名它要替换的条目,并论证新项目做得更糟的那位的活儿更好。一进一出;
- Evidence(证据):准入由维护者编辑判断拍板,主要参考 PyPI 下载量而非 GitHub star;维护者判断最终生效。
自动拒绝规则(Automatic Rejection)——以下情况 PR 会被直接关闭:
- 一个 PR 添加多个项目;
- 用例已满员且 PR 没有 Displacement 论证;
- PR 自建 section/subcategory 并填充它;
- 同一组织/作者跨一或多个 PR 的协调式自我推广;
- 与现有条目或近期已关闭 PR 重复;
- PR 描述为空或占位;
- 分类不当;项目已归档/废弃(12 个月以上无提交);
- 无文档或用例不清晰;仓库存在不足 1 个月。
这套规则的由来背景可以参见 docs/adr/0001-shortlist-not-catalog.md:清单从"三车道模型"(Industry Standard / Rising Star / Hidden Gem)改造为"明显选项精选清单",术语体系(Use Case、Obvious Choice、Challenger、Displacement、Split 等)定义在 CONTEXT.md 中。
步骤一 Fetch:拉取队列与 diff
技能的参数指定具体 PR 时只处理这些 PR;否则取整个队列:
gh pr list --repo vinta/awesome-python --limit 10 \
--json number,title,author,url,body,files,mergeable,mergeStateStatus
字段选择很讲究:mergeable 和 mergeStateStatus 直接决定后续走哪条合并路径(见步骤四),files 用于判断 PR 性质,body 用于空描述检查。随后并行拉取所有 diff:
gh pr diff <number> --repo vinta/awesome-python
拉到 diff 后要做的第一件事是分拣(sort the batch),规则是:
- 新增条目的 PR 继续走流程;
- 其他一切(typo 修复、website 改动、文档改动)超出技能范围——不动它们,只报告到 "needs human"(需人工处理)清单。
这里有一个刻意的设计:needs-human 的 PR 每次运行都会再次冒出来(resurface),直到有人类动手处理。"Done when every PR is sorted and has its diff"——完成标准是每个 PR 都已分拣且都有 diff。
步骤二 Screen:无判断力的机械筛查
筛查阶段应用 Automatic Rejection 规则中那些 diff 和 PR 元数据能直接回答、不需要编辑判断的部分。其中一条规则需要额外查询——"近期已关闭的重复 PR":
gh pr list --repo vinta/awesome-python --state closed \
--search "<project name>" --limit 10
即按项目名搜索最近关闭的 PR,命中则按"重复"关闭。
被筛掉的 PR 直接跳到步骤四,作为一次 close 处理,理由就是它违反的那条规则。
对每个存活 PR,技能还要求把目标用例对照当前 README 重新解析一遍,理由很实际:diff 的上下文行显示的是 PR 写作时的基线代码,而从提交到评审,README 可能已经变了——PR 作者写入时用例没满,评审时可能已经满员。这里有一个衔接设计:存在合并冲突的 PR 被标记后带入 Merge 分支,由步骤四就地吸收解决,而不是在筛查阶段就丢弃。
完成标准:每个存活 PR 都能说出自己的目标用例(section — subcategory)。
步骤三 Judge:按用例分组并委托准入判断
机械筛查无法回答"这个项目配不配得上这个 slot",这一步把工作委托给另一个技能。
首先按目标用例分组:为同一用例提出条目的多个 PR 竞争的是同一批 slot,所以它们必须一起评审(ride one invocation),避免各自判断时互相 unaware。
对每个分组,以如下形状的参数调用 audit-the-list 技能(定义见 .claude/skills/audit-the-list/SKILL.md):
"Judge proposed entries <names, each with its PR number> for the <section — subcategory> use case. Evidence and Verdicts steps only; report the verdicts back. No preview page, no README changes, no commits."
注意参数中的三重约束:
- 只执行 audit-the-list 的 Evidence 和 Verdicts 两个阶段(拉取下载量、仓库状态、PyPI 元数据等证据,然后逐条给裁决);
- 不要预览页面、不改 README、不提交——因为 PR 评审场景下裁决对象是"拟议条目"而非现有 README 内容;
- 把裁决报告回来。
每个 PR 的裁决是 merge 或 close,必须基于拉取到的证据。当用例已满员时,一个 merge 裁决必须点名被挤出的条目。还有一种第三种结果:没有当前用例能容纳的条目是"结构问题"——它带着证据被带入 Act 阶段,由维护者决定:新建子分类并合并、关闭、还是继续挂着。
完成标准:每个存活 PR 都持有带理由的裁决。
步骤四 Act:关闭与合并两条执行臂
裁决由维护者逐条确认后执行,分为两条臂:
Close 臂:带草案评论的人工确认
对每个要关闭的 PR,技能通过 AskUserQuestion 呈现起草好的关闭评论——评论写明理由并链接 CONTRIBUTING.md。交互细节有两点值得注意:
- 每次调用最多批量 4 个 PR,并维护一个"哪些裁决已问过"的清单;
- 维护者的回答经常是自定义文本,而那段文本本身就是决定(可能改评论措辞,也可能改变裁决)。
AskUserQuestion 提供三个选项:用这条评论关闭 / 不带评论关闭 / 保持开放。确认后的命令:
gh pr close <number> --repo vinta/awesome-python --comment "<comment>"
# 或无评论直接关闭
gh pr close <number> --repo vinta/awesome-python
Merge 臂:干净合并与本地冲突解决
无冲突的 PR 直接:
gh pr merge <number> --repo vinta/awesome-python --merge
有冲突的 PR 走本地合并,保留贡献者署名(GitHub 仍会标记 PR 为已合并):
git fetch origin pull/<number>/head
git merge FETCH_HEAD # 使用标准信息 "Merge pull request #<number> from <owner>/<headRef>"
解决冲突的原则是把条目放到正确的位置——这本身就是一次编辑判断,而非机械解冲突。
无论哪条路径,push 前都要按 CONTRIBUTING 规则对账(reconcile)目标 section,然后跑测试并提交:
- 移除裁决中被挤出的条目;
- 修正新条目的显示名(应为 PyPI 包名,便于
pip install直接复制)和 Entry Ordering 位置(obvious choices 在前按 PyPI 月下载量降序,challengers 在后;stdlib 模块在用例内最前;无下载信号的条目在其层级内按字母序垫底——位置即标记,条目文本里没有 tier 标记); - 运行
make test(对应 Makefile 中的uv run pytest website/tests/ -v); - 提交。
这里有一条明确的责任划分:"add-only diff 却需要挤出旧条目"是正常情况——移除是评审这一步的职责,不是贡献者的职责。 贡献者只提交自己的条目,评审侧负责一进一出中的"出一"。
步骤五 Report:终态核对与汇报
最后产出一张汇总表:PR 编号、裁决、已执行的动作,外加 needs-human 清单。完成标准是一条封闭性检查:
每个拉取到的 PR 都恰好结束于一种状态——已合并(且 section 已对账)、已关闭、或保持开放(kept open、needs-human、或结构问题待决)。
"kept open 的 PR 会在下一次运行时再次冒出来——这正是它的目的。"技能不追求一次性清空队列,而追求每个 PR 状态可追踪:被搁置的 PR 是显式状态而非遗漏。
设计要点:这个工作流做对了什么
从技能文件与配套规则文档的结构看,这套设计有几个可迁移到任何"维护型 Agent 技能"的要点:
- 规则与过程解耦:SKILL.md 只写"做什么、按什么顺序、何时委托、何时停下来问人";"什么算合格、什么算重复"全部指向 CONTRIBUTING.md。改规则不需要改技能,技能升级也不会产生第二份规则拷贝。
- 机械判断与编辑判断分层:Screen 阶段只做元数据可回答的检查(空描述、重复、多条目 PR),Judge 阶段才引入需要证据拉取的编辑判断,且通过委托复用 audit-the-list 技能已有的证据管线(ClickPy 下载量扫描、
gh api仓库状态、PyPI 元数据校验)而不是重写一遍。 - 委托时收紧作用域:调用 audit-the-list 的参数明确限定"Evidence and Verdicts steps only",并用三个否定句封住副作用(no preview page, no README changes, no commits)——跨技能调用时显式裁剪对方流程,避免审计技能默认的"维护者预览 + 提交"路径被误触发。
- 人工是裁决点而非橡皮图章:Close 臂的评论由人确认且人的自定义文本即决定;Merge 臂的冲突解决、结构问题(新建子分类)都留给人;维护者的裁决"最终生效"(与 audit-the-list 中"their verdicts are final"一致)。
- 幂等的失败重放:needs-human 与 kept-open 的 PR 每轮重跑都会重新出现,队列永远可重入——Agent 运行中断或人类拖延都不导致 PR 丢失。
相关文件与运行方式
这个技能是 .claude/skills/ 下三个技能之一,围绕同一套规则文档协作:
| 文件 | 职责 |
|---|---|
| .claude/skills/review-prs/SKILL.md | 本文主题:PR 队列的评审工作流 |
| .claude/skills/audit-the-list/SKILL.md | 对 README 现有条目做周期性审计(被 review-prs 委托做证据与裁决) |
| .claude/skills/preview-verdicts/SKILL.md | 生成维护者交互式 keep/drop 预览页(review-prs 场景下明确不调用它) |
配套的事实源:CONTRIBUTING.md(规则)、CONTEXT.md(术语表)、CLAUDE.md 与 AGENTS.md(保持同步的 Agent 入口规则)、docs/adr/0001-shortlist-not-catalog.md(精选清单定位的决策记录)。
运行前提:仓库内已安装 Claude Code(技能通过 .claude/skills/ 目录自动发现),并已配置 gh CLI(含 PR 读写权限)与 git。技能在维护者要求"review PRs / process the PR queue / 判断某 PR 是否该合并"时触发,可传 PR 编号参数指定范围。仓库本地测试入口为 make test(等价于 uv run pytest website/tests/ -v),合并路径在 push 前依赖它通过。
需要说明的适用边界:本文描述的是仓库当前 .claude/skills/review-prs/SKILL.md 的内容;技能中的 --repo vinta/awesome-python 是上游仓库标识,fork 使用时应替换为对应仓库;技能本身不改动 README.md 的条目规则——若准入规则(上限数值、tier 定义等)调整,以 CONTRIBUTING.md 为准,技能流程无需变更。
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 StartedRust0622
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