研究33份公开review skill文件,总结出「定义审查范围→明确证据标准→设置触发条件→规定后续动作」的编写方法论。
像"彻底审查这段代码"这样的提示语隐藏了多个选择,因为没有人定义过什么叫"彻底"。它可能指运行时行为、架构、安全、产品意图,或者同时指所有这些。什么都要求,评审就容易变成一次代码 diff 的游览:一条命名意见、半个关于竞态条件的调查、一条泛泛的测试请求,最后对于这个变更是否应该合入没有任何明确意见。
在构建我们自己的评审 skill 时遇到了这个问题。写提示词比决定希望评审者注意到什么更容易——需要决定它应该信任什么证据、何时应该保持沉默、发现问题时应该做什么。
在大量书写自己的 skill 之前,我们先阅读了其他团队的评审 skill。
我们的研究语料覆盖了 33 份公开的 SKILL.md 文件及相关评审工作流,包括 Cursor、Sentry、Anthropic 和 .NET MAUI 维护的企业级方案,以及研究时仅有一颗 GitHub 星的各种独立项目。这个范围是刻意设定的:我们想要的是整个生态系统中如何定义评审的广泛横截面,而不仅仅是那些最显眼团队的答案。
Cursor 的 Thermo-Nuclear Code Quality Review 是我们的一个重要起点。Cursor 将严格的结构评审与第二层安全性和正确性评审分开。这种分离对我们很有用:追查具体 bug 的评审者应该有不同的行为模式,与挑战复杂设计的那一类评审者不同。
Matt Pocock 的评审 skill 和 Sentry 的紧凑策略引导我们形成了一条明确的策略:仓库标准和规范符合是两个独立的问题,而且最重要的是,风格偏好不应该阻塞一个 Pull Request。
Trail of Bits 和 .NET MAUI 的项目专属评审者展示了另一种有用的区分。专业人员可以深入做安全评审,而项目专属评审者可以读取完整文件、调用方、历史、之前的评审和 CI。
研究给我们留下了一个可以命名的设计问题,到我们编写 skill 时,我们已经清楚评审应该支持什么决策,以及应该从哪里寻找意图。我们还确定了使用哪些"车道"以及每条"车道"何时运行、什么样的证据才能赢得一条评论、严重程度如何影响作者,以及 skill 应该放在本地、CI 还是两者兼有。模型随后有了一份具体的简报来实现。
我们决定,我们的评审者应该像一位资深工程师那样行事——判断一个变更是否安全、是否足够连贯以至于可以合入。评审者让一组专业化的子评审者深入代码的不同方面,然后综合并去重他们的反馈。我们不仅仅是找 bug:代码可能孤立运行没问题,但仍然无法解决工单或满足客户请求、破坏调用方的假设、重复一个已存在的抽象,或者切断了保持仓库连贯性的模式。
一条车道是一个专注的子评审任务(如果环境支持委托,也是一个子代理)。每条车道接收相同的上下文,但要调查不同的问题。当环境支持委托时,各车道可以并行运行;否则就顺序运行。它们保持独立的笔记和历史,这样架构意见就不会淹没具体的运行时 bug,而 bug 追查也不会悄悄发明产品需求或锦上添花的功能。
我们主要在 TypeScript 和 JavaScript 中工作,横跨一组有明确观点的仓库。我们的 monorepo 共享模式和词汇,包括对"后端"、"前端"和"包"的一致理解。每个仓库还带有一个 AGENTS.md 文件,其中包含项目特定的规则。这给了我们一个一致的基线,而不必假装每个代码库都是相同的。
这个 skill 是可移植的,但它带有我们自身代码库的假设和偏见。这就是为什么我们解释它背后的决策,而不是发布一个 skill 然后指望它对每个人都有效。
在我们策略中,严格的 linter 是第一个评审步骤,这使得模型可以在评审路径中省略很多内容。单体仓库使用刻意严格的 linter,它由社区规则集和我们自己的一小部分规则构建而成。格式、import 顺序、命名约定和其他风格决策在评审者或模型将它们转化为评审评论之前就已经解决了。
当一个反复出现的坏模式可以被程序化识别时,我们宁愿让它成为一条规则。一个例子是新版模型喜欢添加的那些微小的单行包装辅助函数的泛滥。如果一个项目决定反对这种形态,一条精确的 lint 失败比在每个 Pull Request 上反复讨论同样的话题要便宜得多。
严格的 linting 还给编码 agent 一个程序化的自我检查:运行工具,收到文件名、行号和规则,然后修复失败再请求判断。模型从 lint 停止的地方开始:意图、爆炸半径、运行时行为、架构,以及需要仓库上下文的风险。
在一条车道甚至看到代码变更之前,我们会运行一个小的确定性发现过程。我们的本地上下文收集器建立评审范围、记录提供的或推断出的基准、列出变更和未跟踪的文件、总结 diff、找到适用的仓库文档,并且可以包含完整的 patch。
在 CI 中,一个 Pull Request 运行器从 PR 描述、提交、时间线、评审讨论和 diff 构建标准化的上下文。然后评审策略指示评审者将任何关联的工单或计划作为意图的额外来源进行检查。我们这个版本的上下文工程不怎么时髦——非常机械:给模型一个可靠的起点,而不是指望它记得如何去查找。
这个确定性过程建立了一个发现的下限。它无法计算语义上的爆炸半径,但它确保模型看到正确的文件、diff 和意图。各车道随后做语义工作并进行进一步调查。它们沿着变更的符号追踪到其调用方、被调用方、下游消费者、状态转换、外部契约和副作用。它们将结果行为与声明的意图、最接近的仓库规则和现有的并行实现进行比较。diff 显示编辑在哪里。周围的代码显示编辑可能改变什么。
在我们的运行中,这个上下文过程显著减少了输出方差和模型需要的发现循环数。评审在实践中也更快了,尽管我们没有将这个观察当作基准。
每条车道都接收那个上下文:
这个分离只有当每条车道使用相同的标准来决定什么值得一条评论时才有效。

如果没有一个共同的发现标准,各车道会很嘈杂。一个关注点必须由变更引入或暴露,指向一个由代码或仓库证据支持的具体机制,显示有意义的影响,并且给作者留下一个具体的修复或决定。工具自有的 lint 失败、个人喜好、推测性的未来工作、泛泛的测试需求,以及无关的旧问题都应该排除在外。
严重程度给每个保留的发现一个可操作的后果。skill 定义了 critical、high、low、question 和 hint 五级。在综合过程中,指令将保留的最高严重程度映射到评审正文中的一个文本裁决。然后运行器将该正文和经过验证的评论作为 GitHub COMMENT 评审提交。
这就是评审策略:什么证据算数、每条车道调查什么、什么被压制、以及一个发现意味着什么。它可以在没有我们 GitHub 基础设施的情况下运行。下一个问题是如何将这个判断转化为一个可重复的 PR 评审。
当评审可以在 Pull Request 之前与变更相遇时,评审策略就更有用了。我们希望同一个 skill 在本地编码 agent 会话、一次性沙箱、远程编码环境和最终评审我们 Pull Request 的专用 Docker 镜像中都可用。
多个编码 agent 理解这种格式,包括 GitHub Copilot、Codex、OpenCode 和 Claude Code。例如,GitHub Agent Skills 与 Copilot 的云 agent、CLI、代码评审和 IDE agent 模式配合使用。
Claude Code skills 使用相同的 SKILL.md 约定,Claude 的云会话可以加载提交到仓库的项目 skill。沙箱或我们自己的评审镜像可以安装同一个文件夹,而无需将策略翻译成另一种提示格式。
策略在这些环境中保持不变:确定性上下文发现、超越 diff 的评审、相同的车道、以及相同的发现标准。支持子代理的环境可以并行运行各车道;另一个环境可以顺序运行它们。
在本地,工程师可以在推送之前评审工作树、分阶段 diff 或分支。在远程 agent 或沙箱中,评审发生在 agent 编写代码的地方。当 Pull Request 落地时,我们的 Docker 运行器自动应用相同的策略,然后添加 PR 状态、行验证和受控发布。
作者可以在推送之前遇到评审策略,此时意图、架构和爆炸半径问题修复起来还很便宜。Pull Request 评审仍然是一次独立的通过,但它应用的是一个熟悉的策略,而不是一套出人意料的偏好。目的是减少避免不了的来回,而不是一次仪式性的第二遍检查。

一旦策略和运行器稳定下来,我们就能够在不改变评审本身的情况下比较模型。在实践中,当前的编码模型处理这项工作得心应手;它们擅长长的、横向的任务。评审跨越请求和仓库的几个部分,然后要求模型运行独立的调查、协调它们,并只报告有用的发现。
在我们的本地运行中,Claude Opus 和 SOL 表现很好。在日常使用中,我们也尝试过 MiniMax M3,以及最近的 DeepSeek V4 Flash。我们没有注意到质量下降大到需要为每次评审都使用高级模型。这是我们自身 Pull Request 的运营印象,不是受控基准,另一个代码库可能得出不同的答案。
保持这些变量稳定让失败更具揭示性:我们可以看到哪个模型错过了上下文、夸大了严重程度或产生了噪音。
旧的 Pull Request 比精心打磨的演示是更好的评估案例,因为团队已经知道评审者应该找到什么、不应该找到什么。我们有多年的 Pull Request 包含 bug、误报和静默案例,还有好的和差的评论、回复和裁决——全都来自后来使用这个 skill 的同一个团队。我们很幸运有足够大的语料库,可以在构建和塑造评审 skill 的同时用自己的偏见对自己的工作进行基准测试。
如果你也能这样做,从团队理解的评审争议开始:一个某人发现的真正 bug、一个浪费时间的误报、一个遗漏的需求、一条有书面上下文记录的架构决策、一次重试失败,以及一个沉默才是正确结果的干净变更。每个案例都需要预期的发现和预期的非发现。否则评审者可以通过评论所有内容来提高其表面召回率。
skill 和运行器需要独立的记分卡。对于 skill,看正确的车道是否找到了问题、证据是否支持它、严重程度是否与后果匹配、多条车道是否产生了同一条评论、以及建议的行动是否可用。多次运行同一个案例,使上下文遗漏和输出方差变得可见。像追踪发现的 bug 一样仔细地追踪误报和有用的沉默。
对于运行器,检查机械故障:无效的锚点、错误的 diff 端、重复的评论、格式错误的回复、丢失的上下文、错误的裁决映射、超时和发布错误。运行时、token 使用量和成本也属于这里。一个贴在错误行上的出色发现仍然是一个破损的评审。

这也是比较模型的时机。使用相同的案例集、策略、上下文和运行器。不要只看单一的平均分:哪条车道失败了、哪个模型夸大了严重程度、哪个重复了发现、哪个花了更多时间阅读而没有改善评审?我们只有在看完这些结果后才比较价格。
我们在过去 Pull Request 的一组很大的 rubric 上运行,包括知名库的公开 PR 和我们自己仓库的私有 PR。我们在几个模型上用相同的策略运行相同的案例,然后比较车道覆盖率、证据、严重程度、噪音、锚点、运行时和成本。结果足够清晰,可以指导我们在接下来几周 dogfooding 的模型选择。
保持这个测试工具。当 skill、运行器或模型发生变化时,重新运行相同的案例以捕捉回归并确认改进。
在连续几周每天数十个 Pull Request 上使用新 skill 之后,我们对它很满意。它有用的发现往往比 bug 更广:变更了契约的调用方、diff 隐藏的下游效应、遗漏了工单的实现,或者切断了仓库其余部分模式的新形状。各车道给每次调查足够的空间来找到证据,而不会把最终评审变成评论礼花。在我们自己的运行中最令人惊讶的是,更小、更便宜的模型仍然能捕捉到前沿模型在高强度努力下遗漏的问题。
运行器仍然是一个原型。我们正在 dogfooding 它以了解它在哪里失败、模型选择如何改变经济学,以及在我们自身使用之外是否存在有用的 SaaS 产品。我们还没有承诺产品路线图。现在,它留在我们自己的 Pull Request 上,同时我们继续收集失败案例。
把严格的 linting 放在第一位,不要把工具自有的发现放到 AI 评审中。
把严格的 linting 放在第一位,不要把工具自有的发现放到 AI 评审中。
通过确定性的上下文过程收集范围、意图、适用规则和精确的 diff。
通过确定性的上下文过程收集范围、意图、适用规则和精确的 diff。
沿着调用方、被调用方、下游效应和已建立的仓库模式追踪变更。
沿着调用方、被调用方、下游效应和已建立的仓库模式追踪变更。
将独立的失败模式拆分到有共同发现标准的有焦点的车道中。
将独立的失败模式拆分到有共同发现标准的有焦点的车道中。
设定发现标准、压制内容和严重程度后果。
设定发现标准、压制内容和严重程度后果。
在推送前运行相同的策略,Pull Request 落地时再次运行。
在推送前运行相同的策略,Pull Request 落地时再次运行。
在已知的 bug、误报、静默案例和跨模型的相同 Pull Request 上评估。
在已知的 bug、误报、静默案例和跨模型的相同 Pull Request 上评估。
npx skills add we-are-singular/skills --skill singular-code-review

我们的评审 skill 采用 MIT 许可,GitHub 运行器原型公开可供检查。从上面的决策开始,借用适合的部分,改变其余的。