Chorus v0.19.0:给 AI Reviewer 立规矩

一轮 AI Review 可以这样消耗时间:先建议给已有错误处理的函数补错误处理,再要求重做已经批准的设计,最后把一个真实的越权问题夹在几条命名建议中间。

开发 Agent 修完,第二轮 Review 又来了。新评论里没有那个越权问题。它重新验证过了吗?只是没看到?还是这轮注意力换了地方?人和 Agent 都得回去翻上一轮评论,猜这次的 PASS 到底意味着什么。

Chorus v0.19.0 给三个 reviewer 补上了一套更明确的工作规则:按职责审查,用证据说明问题,给每条发现一个固定身份,并在复审时交代它的去向。

三次审查,得各自回答一个问题

Chorus 原本就有 Proposal reviewer、Task reviewer 和整个功能完成后的代码总审。开发 Agent 写完不算结束,还要由独立 reviewer 检查,再进入修复和复审。v0.18.0 也已经加入了意图对齐,要求 reviewer 回看人类最初的 Idea、澄清回答和评论。

这套流程有一条从需求到代码的线:

阶段要回答的问题
Proposal review这份方案和任务拆分,能不能完成用户真正要的东西?
Task review这个任务交出来的代码,是否满足要求、有没有具体缺陷?
整体代码审查各个任务拼起来之后,接口、权限和完整功能是否仍然成立?

分成三次审查,就需要把职责写清楚。否则 Proposal reviewer 会在还没写代码时追问每一个实现细节,Task reviewer 会反复讨论已批准的架构,代码总审又把每个任务查过的命名问题重讲一遍。

而另一头也有风险:任务里的权限检查漏了,Task reviewer 觉得“安全归总审管”;总审觉得每个任务已经验收,重点只看接缝。分了工,问题却落进了分工之间。

这一版同时收紧了这两端:明确什么不该报,也明确什么不能推给下一关。

Cloudflare 的经验:要教 Reviewer 忽略什么

这次改造的一个直接参考,是 Ryan Skidmore 在 Cloudflare 博客写的 《Orchestrating AI Code Review at scale》。

他们最初也试过把 diff 塞给模型,让它找 bug。原文描述的结果很具体:模糊建议、幻觉出来的语法错误,以及对已经处理错误的函数建议“增加错误处理”。后来,他们围绕 OpenCode 做了一套 CI 审查编排,按需要启动多个专职 reviewer,由协调者汇总。

读这篇文章,容易先注意到最多七个专职 reviewer、不同档位的模型、熔断和回退。但对 Chorus 这次改动最有帮助的,是他们给 reviewer 写的 What NOT to Flag。

以安全审查为例,Cloudflare 要求它关注可利用或有具体危险的问题,同时排除需要极不现实前提的理论风险、主防线已经足够时的额外防御建议,以及这次改动没有影响的旧代码问题。

发现交上来之后,还要经过协调者的一轮判断:合并重复问题,调整分类,过滤臆测和与项目约定冲突的建议。不确定时,协调者会继续读源码核实。

这意味着一条模型意见在成为开发者的待办之前,要经过几次判断:属于本次范围吗?有证据吗?别的 reviewer 是否已经报过?严重到需要阻止合并吗?增加 reviewer 的同时,也得有人控制它们带来的额外工作。

原文报告,他们在首个 30 天完成了 131,246 次审查,平均每次约 1.2 条发现,中位耗时 3 分 39 秒,平均成本 1.19 美元。这些数据说明了系统的运行规模和输出密度,单凭发现数量还不能判断准确率。对 Chorus 而言,值得借鉴的是产生这些结果的审查规则,效果仍然需要在自己的工作流里测量。

把“不该报什么”写进三个 Reviewer

Cloudflare 按安全、性能、文档等专业领域划分 reviewer。Chorus 已经按需求、任务、整体交付分工,因此这次沿着现有三个阶段写各自的 DO / NOT-DO 清单。

Proposal reviewer 关注方案能否成立。 需求有没有对应的人类意图,验收标准是否可验证,任务依赖有没有环,是否漏了跨任务的集成检查。措辞、标题顺序和“我更喜欢另一种架构”,不应该拖住一个本来可行的方案。实现细节留给任务阶段判断。

Task reviewer 关注这个任务实际交付的代码。 它不能重新争论已经批准的设计,也不能拿另一个任务负责的缺口来阻塞当前任务。可如果当前任务自己写出了权限漏洞,就应当在这一关指出来。

代码总审关注组合之后的问题。 一个任务返回的字段,另一个任务是否按同样的契约读取?单独看都合理的权限处理,拼起来有没有漏口?每个任务的测试都通过了,完整路径是否仍有空白?这时再逐行重复任务审查,只会淹没这些需要整体视角才能发现的问题。

三者共有的一条规则很朴素:报告“缺失”之前,先查证它不存在。 说“没有这个工具函数”,就先搜索仓库;说“缺少这个文件”,就先检查路径,并在结论里说明查过什么。

证据也要与判断对应。查询里确实缺了租户条件,可以引用代码和行号说明缺陷;声称“这里会出现竞态”,就要说明怎样的执行顺序会触发它。不能因为没法运行程序就放过源码里明确的漏洞,也不能把一个想象中的运行结果写成事实。

严重问题写清楚,小建议有上限

检查原有 reviewer 时,我们发现了一个很直接的矛盾:代码总审要求 BLOCKER 给出命令、输出、预期和实际结果,同时又要求总输出不超过 1000 个字符。其他 reviewer 也有不同长度的总量限制。

只要有几个真实问题,这两个要求就很难同时满足。把输出挤短,最容易挤掉的恰恰是开发 Agent 修复时需要的证据。

v0.19.0 删除了三个 reviewer 的总输出字符上限,改成按内容控制:

  • BLOCKER 保留完整证据。 需要多少内容才能说清楚,就写多少。
  • 首轮最多提出 5 条 NOTE。 超出的按相关性舍弃。
  • 第二轮起不再增加新的 NOTE。 复审集中处理已有发现,避免修完一批又来一批小建议。
  • 旧发现的逐条回执不受 5 条限制。 不能为了省篇幅,把之前的问题漏掉。

BLOCKER 是需要解决的阻塞项,NOTE 是不阻塞的建议。两类发现都有用,但不应在同一轮修复中获得一样的优先级。

没有再提,不能算修好了

Cloudflare 原文的复审机制同样值得细看。

新提交到来时,协调者会拿到上次的完整审查评论、此前发布的行内发现及其解决状态。未修复的问题必须再次报告;已修复的问题从新输出中省略,并由 MCP 服务自动关闭对应讨论。开发者的回复也会进入判断。

这里有一个前提:它有行内讨论和状态处理作为支撑。Chorus 目前的审查结论主要通过评论交换,因此这次把跨轮追踪写成了明确的评论规则。

每条发现都有一个稳定 ID:

B1-tenant-scope-missing
N1-unclear-helper-name

B 表示 BLOCKER,N 表示 NOTE,数字是首次报告的轮次。第一轮发现的问题,即使到了第三轮,仍然叫 B1-tenant-scope-missing。

从第二轮起,reviewer 必须逐条回应此前每个 BLOCKER 和 NOTE,只能使用三种状态:

状态含义
fixed本轮重新检查,确认已修复,并给出检查依据
still-open本轮重新检查,问题仍然存在
not-verifiable本轮无法核实,说明缺少什么条件

例如,下面是一段示意性的复审记录:

Prior findings:
- B1-tenant-scope-missing: fixed
  重跑跨租户访问测试,确认其他租户的记录不会返回。
- B1-error-swallowed: not-verifiable
  本轮缺少测试数据库,无法重跑此前的失败路径。
- N1-unclear-helper-name: still-open
  重新查看函数定义,命名未变。

VERDICT: FAIL

第一项关闭了,第二项还没有证明修好,第三项只是建议。即使开发 Agent 说“都改了”,reviewer 也要分别给出判断。

这次保留了 PASS、PASS WITH NOTES、FAIL 三种结论。旧 BLOCKER 仍然存在,或者无法重新验证,都得判 FAIL;未解决的 NOTE 最多得到 PASS WITH NOTES,不会因为拖了几轮就升级成阻塞。

这里有一个明确代价:代码可能真的修好了,但本轮环境无法验证,仍会得到 FAIL。我们接受需要补一次验证或交给人类处理的成本。复审评论至少要准确表达“已经证明了什么”,不能把“现在查不了”写成“已经修好”。

AC 全绿,代码也可能有问题

降低噪声的同时,这一版还补强了 Task reviewer 的默认质量检查。

验收标准(AC)通常在代码出现之前写好。它可以要求“用户能保存设置”,却很难提前穷举实现中所有可能出现的错误。保存失败后仍然显示成功,或者读取设置时漏了账号范围,都不能因为没有一条 AC 原样写出它,就被放过去。

现在,Task reviewer 默认检查五类问题:

  • 当前任务代码中的明确错误,即使没有对应的 AC。
  • 重复实现平台、已有依赖或仓库工具已经提供的能力。报告时必须指出现成能力在哪里。
  • 当前任务自己引入的安全缺陷,包括权限、租户隔离和注入问题。
  • 即使实现写错也不会失败的测试。
  • 必要操作失败后被吞掉,或失败路径仍然报告成功。

这些规则也需要边界。测试用了 mock,不等于它无效;如果验收标准就是“回调只执行一次”,检查调用次数正好能验证它。一个明确设计为 best-effort 的遥测操作,失败已经记录,本来就不要求传播给调用方,也不该被误报成“吞错误”。

同样,“这里可以更简洁”和“仓库里已有同样的函数,却重新写了一份”是不同的判断。前者可能只是偏好,后者需要给出已有函数的位置。要阻塞任务,reviewer 得指得出具体缺陷,并提出足够解决它的小改动。

这次改造,落在 Reviewer 的工作规则上

Cloudflare 的文章还覆盖了风险分层、按 reviewer 路由模型、超时重试、运行观测,以及 AGENTS.md 是否随项目变化更新。那些机制服务于它大规模运行 CI 审查的场景。

Chorus v0.19.0 先把重点放在现有 reviewer 的判断和交接方式上。稳定 ID、逐条回执、范围边界和默认质量要求,都写进了 reviewer 指令;Kiro 的任务 reviewer 和代码总审也补上了受限的只读 shell 检查能力。

这套规则覆盖 Claude Code、Codex、OpenClaw、Kiro、Pi、dsh,以及独立 skill,一共 21 份 reviewer 定义。仓库增加了对应的一致性测试,检查每份定义都带上这些规则,避免改了一端、漏了另一端。它验证的是指令是否齐全,模型在真实审查中能否持续遵守,还需要继续观察。

当前的 finding ID 和状态仍然保存在评论文本里,结论由编排 Agent 按工作流读取和使用。这次没有加入服务端强制审批门禁,也没有建立能认证每次 reviewer 调用来源的专用审查记录。规则先落在现有协作流程中,后续若要让平台确定性地校验来源、审查版本和状态迁移,需要独立设计。

这一版也修复了 Pi worker 和 reviewer 在后台派发时的工具访问、Agent 描述解析,以及 npm 上传成功后仍因等待 registry 传播而拖住发布的问题。六种插件和四个 npm 包统一升级到 0.19.0。

回到开头那轮 Review:越权问题应该有明确证据,有固定 ID,修复后有一条重新验证过的回执。命名建议可以保留为 NOTE,却不能把交付拖进下一轮无休止的挑刺。

开发 Agent 接到的每一条阻塞项,都应该知道为什么要修;接手的人看到一个 PASS,也应该能追到之前的问题是怎样关闭的。这是这一版要让 reviewer 做好的事。


升级

npm install -g @chorus-aidlc/chorus@0.19.0
chorus agents add

更新所用 Agent 的 Chorus 插件并重启,让新的 reviewer 规则生效。

延伸阅读:Cloudflare 原文、Chorus reviewer 改造 PR #573、GitHub Release v0.19.0。