工作原理
从 diff 到带行号的审查结果,整条审查流水线。
ocra 结合了两种经过验证的设计:按领域分工的专项审查员加一个负责协调的裁决者,以及把所有不能出错的步骤交给确定性的工程代码。
Ingest → Select → Triage → Bundle → Matrix → Execute → Anchor → Filter → Verify → Judge → Report| 阶段 | 做什么 |
|---|---|
| Ingest | 通过代码托管平台适配器读取改动和仓库规范。 |
| Select | 逐个文件决定:审查,或者排除并记录原因(二进制、密钥、生成代码、过大……)。 |
| Triage | 根据改动规模和 auth/、PasswordHasher.cs、CI workflow 这类敏感路径,给出风险档位(trivial、lite、full)。 |
| Bundle | 把相关文件分到一组。改动少时合成一组;改动多时由 light 模型按文件编号分组。没有这一步的分组时,20 个文件以内每个文件一个任务,更多时按目录分组。 |
| Matrix | 根据风险档位和每个审查员的适用范围,决定哪个审查员审查哪个分组(例如只看代码的审查员不会收到文档)。被跳过的组合会写进报告。 |
| Execute | 每个计划好的(分组,审查员)组合作为一个隔离的 agent 任务运行,只有只读工具。 |
| Anchor | 把每条问题引用的代码解析成准确的行号。 |
| Filter | 去掉仓库记忆里已接受的问题和审查者驳回的问题,并与同一 PR 的上一次评审对比;这一步在任何模型调用之前完成,不为这些问题花钱。 |
| Verify | 由 standard 档位的模型,对照每个文件的 diff 和问题附近的代码逐条核查。只有当代码能证明某条问题是错的,它才会被丢弃;拿不准的保留,核查失败也保留。被丢弃的问题和原因会写进报告。 |
| Judge | 一次 top 档位的调用看到所有问题:合并不同审查员对同一根因的报告,丢弃臆测和吹毛求疵,校准严重程度,并写一段简短总结。每一项调整都会在报告里附上原因。 |
结论(verdict)
结论由一套固定规则根据最终的问题列表决定,而不是由模型决定,因此相同的问题总会得到相同的结论:
| 问题 | 结论 |
|---|---|
| 没有问题 | approved |
| 只有 suggestion,或少于三个 warning | approved_with_comments |
| 三个及以上 warning,或未经核查确认的 critical | minor_issues |
| 任何经核查确认的 critical | significant_concerns |
每条问题都会标明核查(Verify)的结果:已确认、不确定,或未核查(核查被跳过、调用失败或预算不足)。未经确认的 critical 照常显示为 critical 并标为未核实,但结论最高只到 minor_issues,一个模型未经核查的判断不能单独拦下改动。如果本该核查、却因调用失败或预算不足没能核查某条 critical,这次运行算作不完整(退出码 3),不会被当成通过;配置了 verify: false 时不算。Judge 既不能丢弃、也不能降级已确认的 critical;这类尝试会被忽略,并记入警告。
如果 Judge 被关闭、没有配置 top 模型或调用失败,问题会不经裁决直接报告,结论规则照样适用。
结论只是参考意见,不是安全门禁。流水线里的每个模型都会读到被审查的改动,改动里埋下的文字可能诱导审查员漏报问题,或说服 Judge 丢掉问题。ocra 用来辅助审查者,不能替代必需的人工审查和安全检查。
模式
默认模式偏重准确率。--ultra 用成本换召回率:
| 默认 | --ultra | |
|---|---|---|
| 审查员 | 按风险档位和适用范围 | 所有审查员在所有档位都运行,包括通过 reviewers.<id>.minTier 设置的档位(超出审查员适用范围的文件仍会跳过,enabled: false 仍然生效) |
| 采样 | 每个审查员、每个分组跑一次 | 跑两次,按 fingerprint 合并 |
| 计划 | 只针对大分组(5 个文件及以上,或 diff 超过 40,000 字符) | 每个任务都有。先用一次简短的模型调用列出优先要查的地方;失败时任务照常运行 |
| 调用方 | 审查员自己搜索 | 把分组里定义或改动的符号在分组之外的用法提供给审查员 |
| Judge | 丢掉臆测和吹毛求疵 | 保留它们并标为低置信度,不计入结论 |
| 成本 | 基准 | 约两倍或更多,外加每个任务一次计划调用 |
两种模式下的核查(Verify)完全一样。
审查员与工具
审查员是一个带有专门提示词的 agent,有自己的规则,以及告诉 Matrix 在哪里值得运行的适用范围:
| 审查员 | 关注 | 运行档位 | 跳过 |
|---|---|---|---|
correctness | 逻辑错误、契约被破坏、错误处理 | 所有档位 | 无 |
security | 能被不可信输入触达、可被利用的问题:注入、授权、密钥、加密、不安全的解析、CI | lite 和 full | 文档、测试 |
performance | 重要路径上可度量的性能退化:复杂度、N+1、阻塞操作、内存 | lite 和 full | 文档、测试、配置数据、CI workflow |
docs | 被这次改动弄错的文档:被删除或改名的参数、配置项、接口或默认值,而 README、文档或帮助文字还在描述旧的(用 light 模型) | lite 和 full | 测试 |
agents-md | AGENTS.md 里被这次改动弄错的内容:命令、目录结构、模块职责、需要的工具和环境变量(用 light 模型;仅当仓库有 AGENTS.md 时运行) | lite 和 full | 测试 |
每个审查员只看自己的领域,并且明确知道哪些不该报。所有审查员都只能读:read_file、read_diff 和 code_search 返回的都是被审查的那个版本(在区间或单个 commit 模式下是对应的 commit,而不是你的工作区),问题通过 report_finding 提交。这些工具的返回有上限(每行 2,000 字符、每次 50,000 字符、每个文件最多读 2 MB),压缩过的打包文件不会塞满模型的上下文。它不能修改文件、执行命令或访问网络。
行号定位
模型给出的行号并不可靠,所以让模型引用代码,由 ocra 来解析:
- 在该文件改动过的代码段里做规范化匹配;
- 在整个文件里匹配;
- 在其他改动文件里匹配(模型写错了文件),只接受整行引用,而且只能在一处找到;
- 把问题和该文件的 diff 交给 light 模型,让它指出对应的代码行,再像普通引用一样去匹配(每条没匹配上的问题一次小调用);
- 以上都不行,问题就挂在它自己的文件上,作为文件级问题。
如果问题最终落到审查员分组之外的文件上,会被丢弃并记一条警告:那个审查员并没有看到这个文件。
优先按整行匹配。引用只是某一行的一部分时,只有长度不少于 12 个字符、并且只有一行包含它,才算匹配;否则像 err 这样的片段会落到第一个碰巧包含它的行上。如果一段引用同样适合多个位置(比如三行一样的 return err;),就不挑具体的行:问题显示在文件级别,并计入 anchoring.ambiguous。JSON 报告(--format json、会话的 report.json)会给出整次运行的 anchoring:每种方式(hunk、file、cross_file、relocated、file_level)锚定了多少问题、其中有歧义的数量,以及重新定位调用了几次。
稳定性
- 单个任务和整次运行都有超时,即使模型不再响应也照样生效。某个任务的 agent 如果五分钟没有任何进展(没有新的步骤、文字或工具调用),会提前停止,交给模型链里的下一个模型。
- 某个任务失败不会导致整次运行失败,它负责的文件会被标记为
failed。 - 每个层级都有模型降级链,每个模型都有熔断器(反复失败后打开,冷却后放行一次试探)。
- 遇到限流会等待:供应商要求短时间后重试(最多 90 秒)时,所有任务都会暂停使用这个模型,等完再重试。如果限流标明是按天计算、等待时间更长或没有给出,或者连续第四次被限流,这个模型在本次运行里就不再使用,ocra 也就不再发送注定被拒的请求。
- 每个审查 agent 最多执行 30 步,因为每一步都会把整个对话重新发送一遍;大多数任务在 15 步左右就结束。
- 审查 agent 如果没做完就停下(没有发出完成信号、没有给出结论、步数也没用完),会在同一个会话里被提示一次,把还没审的文件审完;进度行会写"resumed after stopping early"。