先读懂代码, 再开口的代码审查。

ocra 把一次改动拆成几个聚焦的审查任务。每个 agent 只能读你的仓库,必须引用它说的那段代码,并被要求交代自己查过什么。多数运行只报一两条意见,不少运行一条也没有,这也是正常结果。

Node 22+,支持 OpenCode 能用的任何模型

ocra review — 示例输出
$ ocra review --from main
[ocra] Reviewing: Changes from main to HEAD
[ocra] 4 file(s) selected, 1 excluded · risk tier: full
[ocra] 2 bundle(s) (grouped)
[ocra] 4 review task(s), 2 reviewer/bundle pair(s) skipped
⋮
[ocra] security-1 completed in 63.2s · 2 finding(s)
[ocra] correctness-1 completed in 71.9s · 2 finding(s)
[ocra] Verified 3 finding(s), dropped 1 that the code disproves
⋮
Verdict: significant concerns
src/auth/session.ts
critical L42 Every session is treated as expired [verified] #b7d6c863
isExpired() compares expiresAt, stored in seconds, with Date.now() in milliseconds, so it is always true: loadSession() deletes every session and users are logged out right after signing in.
Suggestion: return session.expiresAt * 1000 < Date.now();
1 finding(s) (1 critical, 0 warning, 0 suggestion) · tokens: 315936 in (209152 cached), 9021 out, 8489 reasoning · $0.5134
01

运行 ocra review 时发生了什么

不能出错的步骤,都是普通的、有测试的代码。只有需要判断的地方才交给模型。

代码模型

  1. [00]

    只读值得读的文件

    selecttriage示例运行
    • src/auth/session.ts读取
    • src/auth/token.ts读取
    • src/api/login.ts读取
    • docs/auth.md读取
    • package-lock.json放到一边:generated
    档位triviallitefull涉及 auth/

    二进制、锁文件、第三方和生成的代码、媒体文件、疑似密钥和过大的 diff 会被放到一边,每个都记录原因。涉及 auth/、CI workflow 这类敏感路径,或超过 20 个文件,直接定为 full 档;否则按改动量分为 trivial、lite 或 full。

  2. [01]

    每组、每个审查员一个任务

    bundlematrix示例运行
    auth 代码3 个文件文档1 个文件correctnesssecurity跳过performance跳过
    4 个审查任务跳过 2 个:没有匹配的文件

    四个文件起,由轻量模型把该一起看的文件分成组;文件更少时用代码直接拆分。correctness 总会运行;security 和 performance 从 lite 档开始加入,并跳过文档和测试。被跳过的组合连同原因记录下来,--plan 能在调用模型之前列出全部任务。

  3. [02]

    每条意见都引用它说的代码

    reviewanchor示例运行
    agent · correctness
    • read_file开
    • read_diff开
    • code_search开
    • edit关
    • bash关
    • webfetch关
    引用return session.expiresAt < Date.now();在 diff 里匹配到src/auth/session.ts:42

    每组每个审查员一个隔离的 agent,用只读工具读取被审查的那个版本,最多执行 20 步,用引用代码的方式报告问题。ocra 先在 diff 里、再在整个文件里查找这段引用,定位到具体行;找不到时,这条意见只挂在文件上。行号从来不由模型决定。

  4. [03]

    只留下经得起重读的意见

    filterverifyjudgeverdict示例运行
    • Every session is treated as expired已确认critical · session.ts:42
    • Expiry compares seconds with milliseconds并入 #1
    • Token stays valid after logout被证伪
    • user may be undefined已被记忆接受
    结论:significant concerns1 条已核实的 critical

    团队已经记入 ocra 记忆的问题,在花钱让模型核查之前就被去掉。核查模型把每条问题和代码放在一起重读,去掉被代码证伪的;Judge 合并同一根因、去掉吹毛求疵的意见,但不能去掉或降级已确认的 critical。结论由代码计算:只有经核查确认的 critical 才能拦下改动。

一次示例运行,加速播放。流水线、输出和结论都来自 ocra 本身;模型的回答是为录制预先写好的。
02

刻意做出的几个取舍

每一条都有代价,我们认为值得。

  1. 01

    行号从来不由模型决定。

    模型不擅长行号,却很擅长引用。所以让它引用,由 ocra 去找行号。

  2. 02

    审查员被明确告知哪些别管。

    代码风格、没有依据的推测、没改动的代码,对每个审查员都不在范围内;每个审查员还有自己的排除清单,比如 correctness 不管缺少测试。一条都不报,也是合格的结果。

  3. 03

    agent 没有写权限。

    agent 能读文件、读 diff、在仓库里搜索。编辑、shell 和网络工具全部关闭,OpenCode 的其他内置工具也一并关闭。

  4. 04

    你自己的配置不进审查。

    在发出第一个请求之前,全局 OpenCode 配置、已安装的 skill 和指令文件都会被关掉,运行时也只拿到它需要的环境变量。

  5. 05

    每次尝试都有账单。

    每次审查尝试结束时,列出步数、工具调用、token 和花费;整次运行的合计还包括缓存 token 和每一次辅助调用。

  6. 06

    一个模型倒下,下一个接手。

    给每个层级配一串模型。遇到过载就换下一个;反复失败的模型会暂停一段时间,短暂限流会等待后重试,额度用尽的模型在本次运行里不再使用。

03

一条意见长什么样

PR 上的一条行内评论,信息刚好够你在几秒内决定修还是忽略。之后怎么处理,取决于代码和审查者。

src/auth/session.ts示例 PR
41 export function isExpired(session: Session): boolean {
42+ return session.expiresAt < Date.now();1
43 }

github-actionsbot

Every session is treated as expiredcriticalverifiedcorrectness2

isExpired() compares expiresAt, stored in seconds, with Date.now() in milliseconds, so it is always true: loadSession() deletes every session and users are logged out right after signing in.3

Suggestion: return session.expiresAt * 1000 < Date.now();4

示例 PR

一条意见长什么样

下一次 push 时,只要这段代码没变,这条问题就保持打开,即使没有审查员再报一次。同样的代码,结论不会变。

  1. 1agent 引用的那一行,由 ocra 在 diff 里定位
  2. 2严重程度、是否经过核查确认,以及是哪个审查员发现的
  3. 3用平实的话说明错在哪里
  4. 4有必要时,最小的修复
04

把团队规范写成插件

Git 和 GitHub 适配器、OpenCode 运行时,以及目前自带的三个审查员,本身也是插件。你写的插件用的是同一套接口:注册规则、审查员、工具或事件监听,并拿到只属于自己的设置。插件会执行代码,所以只在本地审查时加载;审查 PR 时,同样的规则写在 base 分支的 .ocra/rules.json 里。

  • 三个生命周期钩子,按固定顺序执行
  • 每个插件的设置单独校验
  • 注册冲突或越界注册时报错,并指出是哪个插件
阅读插件指南
tools/ocra-team-rules.mjs
export default {
  name: "team-rules",
  configure(ctx) {
    ctx.registerRules([{
      path: "services/**",
      rule: `${ctx.settings.team}: require idempotency keys`,
    }]);
  },
};
05

目前进展

  1. M1本地审查已完成

    CLI、文件选择、分组、行号定位、correctness 审查员、OpenCode 运行时、插件、评测工具。

  2. M2更多审查员已完成

    安全与性能审查员、风险档位、审查矩阵、逐条核查、最终裁决,以及固定的结论规则。

  3. M3GitHub已完成

    ocra review --pr 和 GitHub Action:行内评论、一条摘要评论;之后的 push 只复审改动过的部分,自动关闭已修复的评论串,并尊重人工驳回。目前在模拟的 GitHub API 上测试过,第一次真实 PR 运行放在 M7。

  4. M4加固已完成

    每个模型的熔断器、通过 https 共享的配置、审查记忆,以及追求召回率的 --ultra 模式。

  5. M5度量已完成

    一套标注了预期问题的黄金集,加上 AACR-Bench。第一批结果已经连同局限一起公开:样本小,只测了一个模型家族,召回率是短板。

    查看结果
  6. M6召回率暂停

    审查员报告所有能拿出证据的问题,由核查和 Judge 负责准确率;每次只改一处提示词,并逐一度量。在有模型额度逐一度量之前暂停。

  7. M7发布 v0.1已完成

    v0.1 已带 provenance 发布到 npm,一行命令就能安装。Action 已在两个仓库的真实 PR 上审查。

  8. M8不可信的 PR已完成

    一份威胁模型、一套为来自 fork 的 PR 设置门槛的工作流,以及一组对抗测试:在被审查的改动里埋入指令、链接和命令。

    阅读威胁模型
  9. M9覆盖面与可信度进行中

    GitLab Merge Request(GitLab.com 和自建实例);用于代码扫描的 SARIF;容器镜像;你自己的 OpenAI 兼容模型端点;安全策略。已随 0.2.0 发布;下一步是在真实的 GitLab 实例上验证。

06

拿一个你熟悉的仓库试试

它可以在你本机的任何 Git 仓库上运行,也可以通过 GitHub Action 审查 PR。模型供应商能看到的是:这次改动及其标题和描述、你的 AGENTS.md 和审查规则,以及 agent 在仓库里打开或搜索到的内容;看起来像密钥的文件和仓库以外的东西,它都看不到。

拿一个你熟悉的仓库试试
zsh
npm install -g @open-cr-agent/cli
export GEMINI_API_KEY=...
export OCRA_MODEL_TOP=google/gemini-3.1-pro-preview
export OCRA_MODEL_STANDARD=google/gemini-3.5-flash
export OCRA_MODEL_LIGHT=google/gemini-flash-lite-latest
cd your-repository && ocra review --from main