关于 opencode-review
opencode-review 是一项技能,可在提交拉取请求前,对本地 Git 变更运行无头评审委员会。它并行执行专项评审,由主席汇总报告,并支持按文件类型应用检查清单及可选的事实核查。
主要功能
- 01并行执行 SWE 与架构评审
- 02主席汇总评审结果
- 03按文件类型匹配检查清单
- 04可选的基于差异事实核查
- 05评审提交与工作区变更
- 06主席失败时提供备用报告
适用场景
- 01在创建拉取请求前查找问题
- 02评审同时含提交和未提交变更的分支
- 03针对变更文件类型聚焦评审
- 04核实评审发现是否与差异内容矛盾
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ shyuan/ skills --skill opencode-review安装后在 Claude Code 中输入/opencode-review确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
prompts/architect.mdchair.mdfactcheck.mdswe.mdprompts/rules/arkts.mdATTRIBUTION.mdbuild_gradle.mdc.mdcargo_toml.mdcpp.mddefault.mdgo.mdjava.mdjson.mdkotlin.mdLICENSEmapper_dao_xml.mdpackage_json.mdpom_xml.mdproperties.mdrust.mdts_js_tsx_jsx.mdyaml.mdscripts/run-review.sh详细介绍
OpenCode PR 前审查
针对本地差异运行无头多模型审查委员会,并返回主席汇总的报告,帮助你在创建 PR 前修复问题。
自包含:此技能包含所需的一切。它按模型驱动 opencode
(opencode run --pure --model <id>),将每位审查者的角色设定从 prompts/*.md
嵌入消息中——它不依赖用户的 opencode.jsonc 中定义任何 Agent。(这也避开了 opencode 的一个陷阱:作为顶层 --agent 运行调用 mode:subagent Agent 时会自我复制并引发 fork 炸弹;按 --model 驱动则完全不使用 Agent。)
**你(Claude Code)是负责人/编排者。**脚本会并行调用两名成员,然后将两份报告交给主席,最后运行一个可选的事实核查阶段:
- SWE — 正确性/Bug/安全性(
prompts/swe.md,默认模型 Muse Spark Contributor) - Architect — 设计/架构(
prompts/architect.md,默认模型 GLM Flash) - Chair — 去重 + 验证 + 补全遗漏 → 最终报告(
prompts/chair.md,默认模型 Qwen Flash) - Fact-check(可选,阶段 3)— 仅剔除可由差异直接证伪的发现
(
prompts/factcheck.md,默认模型 DeepSeek V4 Flash)
每个席位都使用其系列中的经济档,并采用高推理强度——参见 为何选择这些模型。
按文件类型划分的审查清单(移植自 Alibaba 的 open-code-review)。在并行调用前,
脚本会将每个变更文件映射到 prompts/rules/ 下的重点清单(第一个匹配项优先:
*.java→java.md、*.{ts,tsx,js,jsx}→ts_js_tsx_jsx.md、pom.xml→pom_xml.md、
*mapper*.xml→mapper_dao_xml.md,……否则使用 default.md)。所有匹配清单的并集会附加到两位成员的角色设定中,因此每个模型都会将注意力集中在此差异中这些文件类型真正重要的方面——规则匹配的精准性与多模型的盲点多样性相结合。参见 prompts/rules/ATTRIBUTION.md(Apache-2.0)。
此技能不会修改用户的 OpenCode 配置,也不会运行任何 gh 命令——
因为此时还没有 PR。
何时运行
代码编写完成后、创建拉取请求前。变更可以是:
- (a) 尚未提交(工作树和/或暂存区中,包括未跟踪文件),或
- (b) 已提交到尚无 PR 的功能分支上,或
- 两者同时存在——分支上有提交,工作树中还有更多改动。
(c) PR 已创建后不属于此技能的适用范围(该场景会在 OpenCode
TUI 中交互运行,并使用 gh)。脚本仍会检测这种情况——参见下文的已推送分支警告——以便误操作运行时会明确提示,而不是悄悄审查错误内容。
如何运行
在仓库根目录下使用 bash 调用随附脚本(这样脚本才能读取 git
差异)。脚本位于此技能目录下的 scripts/ 中:
bash scripts/run-review.sh
不带参数时,脚本会自动检测阶段。“领先基线的提交数”和“脏工作树”会分别衡量,因为两者经常同时成立:
| 领先基线的提交数 | 未提交的工作 | 审查内容 |
|---|---|---|
| — | 是 | (a) 工作树 |
| 是 | — | (b) base...HEAD |
| 是 | 是 | (a+b) 两者的并集 |
| — | — | 无;以状态码 0 退出 |
并集的情况才是关键。如果把两者当成二选一:工作树若有改动就审查工作树,否则审查分支,那么一个包含真正工作成果、同时还多出一个无关未跟踪文件的分支,就会只审查那个无关文件,跳过所有提交,并记录一条看似正确的范围说明。基线为 origin/HEAD,否则为 main/master。
脚本会记录所选阶段,包括未纳入审查的内容,这样调用错误会清晰可见,而不会悄悄影响审查范围:
[opencode-review] stage : (a+b) branch 'x' vs 'main'
[opencode-review] 3 commits + 2 uncommitted files, BOTH included
如果分支看起来已经到达远程端——它有上游分支,或者存在
refs/remotes/<remote>/<branch>(因为直接执行 git push origin <branch> 不会设置上游分支)——运行时会记录警告,提示可能已经有 PR,而当前使用的是 PR 前审查流程。
提示词本身从未说明是否存在 PR,无论正向还是反向。过去,只要未设置上游分支,就会断言*“目前没有拉取请求”*,但恰恰在上述情况下,这一说法是错的。扩大检查范围也无法解决这一类问题——远程跟踪引用可能已过时,而从另一台机器推送、却从未在此处获取的分支,在本地完全没有痕迹——因此,除非运行 gh,否则本地仓库无法确定这一点。所以,与其让说法更准确,不如直接删掉;真正传达指令的那部分(“仅根据本地 git 状态进行审查;不要运行任何 gh 命令”)始终成立,也足以满足所有模型的需要。现在,推送信号仅用于向操作者发出警告,在这里误报没有任何代价。
若要指定某个对象——当两侧都存在时,这也是有意审查其中一侧的方式,因此没有单独的阶段覆盖开关:
bash scripts/run-review.sh main # diff current branch vs main
bash scripts/run-review.sh <commit-sha> # one commit
bash scripts/run-review.sh "" # force: uncommitted changes only
可选的环境覆盖项:
OPENCODE_REVIEW_PROVIDER=<id>— 通过上一级的提供方访问默认模型,例如omniroute→omniroute/opencode-go/glm-5.3-flash。如果你使用路由器接入多个 OpenCode 套餐,这项设置会将一次运行中的四次调用(其中两次并发)分散到各个套餐,而不是集中在同一个账户上。未设置时(默认),直接使用这些 ID,不作更改。此设置仅适用于四个默认模型——下方显式指定的*_MODEL始终是完整 ID;如果希望通过路由器访问,请在其中写明提供方。注意:配置为通用 OpenAI 兼容提供方的路由器不在 models.dev 中,因此 opencode 不知道其模型的上下文和输出限制;如果 diff 超出限制,就会显示提供方错误,而不是事先警告。如果遇到这种情况,请在opencode.jsonc中为这些模型声明limit。OPENCODE_REVIEW_SWE_MODEL/OPENCODE_REVIEW_ARCH_MODEL/OPENCODE_REVIEW_CHAIR_MODEL— 覆盖评审委员会使用的模型(默认值:opencode-go/muse-spark-1.3-contributor、opencode-go/glm-5.3-flash、opencode-go/qwen3.8-flash)。OPENCODE_REVIEW_SWE_VARIANT/_ARCH_VARIANT/_CHAIR_VARIANT/_FACTCHECK_VARIANT— 每个席位使用的推理强度,通过--variant传递给 opencode。默认值依次为xhigh、max、xhigh、max。推理强度名称因模型而异——它们来自各模型自己的reasoning_options,且可选范围不同(glm/deepseek的上限为max,muse/qwen的上限为xhigh)——因此,默认变体只对为其选择的模型有效。为某个席位指定自定义模型时,该席位的变体会被清除,除非你也指定了变体;将变体设为""可不传递任何变体。OPENCODE_REVIEW_MODEL=<id>— 跳过评审委员会,使用单个模型运行(采用 SWE 人设)。OPENCODE_REVIEW_VARIANT=<effort>—OPENCODE_REVIEW_MODEL的变体。没有默认值:模型由调用方选择,因此此处无法确定其推理强度范围。OPENCODE_REVIEW_AGENT=<name>— 应急选项:通过--agent运行单个预配置的 opencode Agent(必须为mode:primary)。OPENCODE_REVIEW_RULES=0— 禁用文件类型检查清单注入(默认启用)。OPENCODE_REVIEW_DEP_DIRS=<dir>[:<dir>…]— 文件工具可读取的额外依赖源目录(会自动检测 Go 的GOMODCACHE和$CARGO_HOME/registry)。OPENCODE_REVIEW_FACTCHECK=0— 禁用第 3 阶段的事实核查步骤(默认启用)。OPENCODE_REVIEW_FACTCHECK_MODEL=<id>— 事实核查步骤使用的模型(默认opencode-go/deepseek-v4-flash;与主席模型相互独立)。OPENCODE_REVIEW_FACTCHECK_DIFF_MAX=<bytes>— 第 3 阶段消息中内联的 diff 长度上限,超出后会截断(默认值200000)。事实核查器会直接收到 diff,而不是收到要求其获取 diff 的指令,因为它只能删除与 diff 相矛盾的发现。截断时会在文本中加入标记提示,而不会静默截断——该步骤必须知道 diff 不完整,才能保留涉及其无法查看部分的发现。如果你怀疑相关发现位于被截断的尾部,请调高此值。OPENCODE_REVIEW_TIMEOUT=<seconds>— 每个模型的硬超时(默认值900)。OPENCODE_REVIEW_STAGGER=<seconds>— 两个并行成员启动之间的延迟(默认值3),用于避免 opencode 在会话初始化时出现“database is locked”的启动竞态。
每个阶段都使用 --format json 运行,因此脚本捕获的是 opencode 的事件流——每行一个 JSON 对象——而不是渲染后的转录文本。主席汇总的报告会打印到标准输出,位于 ===== … REVIEW … ===== 和 ===== END OF REVIEW ===== 标记之间。只有脚本自身的进度/错误行(以 [opencode-review] 为前缀)会输出到标准错误。如果主席运行失败或超时,将打印两名成员的报告作为备用结果,确保你仍能获得可执行的信息。
阶段的报告仅由其 text 事件构成,不包含其他内容。 事件流在源头就区分开了渲染后的转录文本所合并成的那些无法区分的行:
| 事件 | 内容 |
|---|---|
text | 模型自己的话——这就是报告 |
tool_use | 工具生成的每个字节,包括工具的错误 |
step_finish | 模型停止的 reason:stop、tool-calls、…… |
因此,工具输出不可能被误认为是正文:它永远不会与正文共用一个通道。每位成员读取的完整差异、每次 bash 调用被拒绝时转储的 OPENCODE_PERMISSION 规则集、拼错的命令产生的 shell 错误——这些全都是 tool_use,都不可能是候选报告。
这一设计取代了要求各角色输出 <<<REVIEW-REPORT>>> 分隔标记的做法,也取代了从渲染后的输出中推断结构的去噪器,以及标记上每次运行都不同的随机数。在实际运行中,这种设计两方面都出了问题。模型调查了 30–70 KB 后停止,却没有输出任何带分隔标记的内容,于是触发了回退逻辑,并转发状态机最后看到的内容:一次运行把 150 字节的 shell 错误消息当作 SWE 报告交给了主持者,另一次则把 99 字节的模型开场旁白当作架构师报告交了上去——日志都记录为 ok, but the report was not fenced,看起来像格式问题,而不是这根本不是审查报告。读取 text 事件后,这类情况就不可能发生了。随机数也因此不再需要:它原本存在,只是因为分隔标记无论出现在哪里都具有权威性,而成员可以 cat 仓库文件,因此被审查的代码库可以伪造标记;现在,文件内容按设计就是工具输出。
读取事件流需要 jq(首选)或 python3。程序会在启动时、发起任何模型调用之前检查,因此没有这两者的机器会立即失败,而不是等运行四次之后才失败。
阶段以 0 状态退出,但没有写出报告时,日志和交给主持者的消息都会报告 NO REPORT PRODUCED,而不是 ok——这样 prompts/chair.md 中“注明缺席的成员”的规则才能生效,并且本次运行的退出状态会是非零。没有产出任何内容的运行不再会看起来像成功。
事件流还会说明它为何没有产出内容,这些情况各不相同,值得分别说明:
[opencode-review] WARN: swe: stopped after 14 tool calls without writing anything (reason=stop).
这是一个读取了差异、进行了调查,却在结束本轮时没有写出报告的模型——不是模型从未运行,也不是超时。这正是 issue #33 中记录的故障:同一次运行中有两位成员各自独立地出现了这种情况。脚本会检测并报告该情况;目前不会采取干预措施。
提供商拒绝请求时会报告其实际原因,并立即结束本次运行。 请求彻底失败时,opencode 会发出一个 {"type":"error"} 事件——例如模型 ID 错误、身份验证失败、配额不足——该事件包含提供商自己的描述:
[opencode-review] WARN: swe: provider error: Monthly usage limit reached. Resets in 1 day.
[opencode-review] ERROR: both members failed with a provider error; not launching the chair or fact-check.
[opencode-review] ERROR: retrying against this provider will not help — switch provider … or wait for the limit to reset.
不会因此跳过任何阶段;本次运行会通过退出状态报告该错误。在 --format json 模式下,阶段被拒绝后大约一秒就会结束,因此提前放弃并无可挽回的损失——issue #30 记录的约 45 分钟,源于旧的输出格式让进程一直运行却没有内容可说;#34 将所有阶段切换为事件流后,这个问题便解决了。(事实核查从未造成这部分耗时:只有主持者产出报告时才会运行。)
跳过主持者也可能白白让整个输出落空,因为错误事件无法区分*“此提供商的配额已用尽”和“这两个模型 ID 都不正确”*——而在第二种情况下,使用有效模型的主持者本可以正常工作。
退出状态:0 表示正常完成,1 表示某个阶段失败,3 表示某个阶段失败,且提供商有该模型,但出现了提供商错误。仅此而已——3 并不能告诉你是否应该重试,而且这是有意为之:
Monthly usage limit reached. Resets in 1 day. -> rerunning cannot help
Provider rate limit exceeded -> rerunning may well help
Inference is temporarily unavailable -> rerunning may well help
这三种情况都会产生相同的事件。能区分它们的消息来自提供商,并会显示在 WARN 行上——因此,状态用于分类,消息用于提供建议。
两个条件决定 3 是否成立,而且都能通过证据判断。必须有错误事件,以此区分提供方故障和模型只是停止了。每个失败的模型还必须列在 opencode models 中:不存在的模型 ID 也会产生相同的通用错误 UnknownError,但其原因是本地配置问题,diag : 行已经说明了这一点(1)。如果无法获取模型列表,则两种情况都不作判定,结果保持为 1。
3 表示运行完成了所有阶段——不会跳过任何阶段来得出这一结果。
如果没有成员,stdout 上的最后一条内容会说明这一点,并且位于报告标记之间:
**DEGRADED: 2 of 2 members produced no report.** Nothing above is a committee finding —
it is a solo judgment by the chair model, which read the diff itself.
主席确实会在自己的前言中注明成员缺席,但扫读的读者很容易错过这条信息——而没有成员参与的主席报告,只是一个模型的观点披上了委员会的外衣。这正是设计要避免的情况。
当某个阶段失败时,日志还会说明模型是否可用。 opencode 在模型 ID 无效时不会提供任何模型信息——opencode run --model does-not-exist/at-all 会输出一条简单的 UnknownError: "Unexpected server error",其中从未提及该 ID——因此,脚本原本会将每个阶段都报告为 NO REPORT PRODUCED,却没有任何线索指出原因。这是人们在没有默认提供方的机器上运行此技能时最先遇到的问题。因此,脚本仅在失败路径中,才会通过 opencode models 检查失败阶段所用的模型:
[opencode-review] diag : NOT available in this OpenCode setup: opencode-go/glm-5.3-flash …
[opencode-review] diag : that alone accounts for an empty report — opencode reports only a
generic server error for an unusable model id, never naming it.
[opencode-review] diag : name models you do have via OPENCODE_REVIEW_{SWE,ARCH,CHAIR,…}_MODEL,
or set OPENCODE_REVIEW_PROVIDER=<id> if they sit behind a router.
如果所有模型都存在,脚本也会说明这一点——这就排除了最可能的原因,并提示你查看保留的事件。opencode models 耗时约 7 秒,因此只会在已经发生故障后运行,每次运行最多一次;运行顺利时不会付出这项开销。
只要某个阶段没有生成报告,其原始事件流——以及由于不是 JSON 而单独捕获的 stderr——就会复制到每次运行专用的 mktemp -d 目录中(权限模式为 700),并记录该目录路径。脚本写入的其他内容都存放在私有工作目录中,并会在退出时删除;空白阶段不会输出任何内容。如果运行结果报告某个阶段为空白,请读取该文件:tool_use 事件会准确显示模型停止时正在做什么。
如何处理报告
- 阅读 stdout 上两个标记之间的报告。主席会先列出已确认的高优先级问题,然后是供参考的条目,最后是已驳回的条目(并说明原因)。
- 向用户简要总结实质性发现——除非用户要求,否则不要把整份报告贴回去。
- 修复实质性问题:正确性、安全性和实际的设计问题。除非用户有要求,否则不要为了纯粹的风格小问题反复折腾。
- 如果你决定跳过某个已标记的条目,请用一行说明决定及原因。
- 提出在修复后重新运行此技能,以确认在提交 PR 前问题已解决。
前置条件
只有运行失败时才检查这些项——并先检查 diag : 行,因为其中会报告最常见的原因:
-
opencode位于PATH中,且所选模型已完成身份验证(与 TUI 使用相同的提供商)。 只需要模型访问权限——opencode.jsonc中不必存在任何委员会 Agent。 默认配置使用opencode-go/*模型,这意味着你需要 OpenCode Go 套餐;如果没有该套餐,请将OPENCODE_REVIEW_{SWE,ARCH,CHAIR,FACTCHECK}_MODEL指向opencode models中的模型,或者在相同模型位于路由器后方时设置OPENCODE_REVIEW_PROVIDER。 任一操作都会清除对应席位的推理强度默认值,因为强度名称因模型而异。 -
opencode版本足够新,支持run --variant。如果版本不够新,运行结果会在最后一行说明,无论成功还是失败都会如此:[opencode-review] WARN: this 'opencode' has no 'run --variant' flag. The reasoning effort logged above as '@...' was therefore NOT applied — each stage ran at its model's default effort, whatever the log line said.在顺利运行时报告这一点正是目的所在。解析器若拒绝未知标志,就会导致每个阶段同时失败,且错误显而易见;危险的情况是程序忽略该标志,此时每个阶段都会以默认强度成功,而日志却声称使用了
@xhigh——报告实际上悄悄变差,却宣称并非如此。这项检查耗时约 6 秒,并在后台与第 1 阶段并行运行,因此顺利运行不会增加耗时;未传入 variant 的运行则根本不会启动它。检查只报告二进制程序的行为,不会将任何特定故障归咎于它。 -
jq或python3位于PATH中——其中一个会读取 opencode 的 JSON 事件。启动时会进行检查,因此缺少读取器时会立即显示相应消息并失败,而不会表现为四个空阶段。 -
此技能目录中存在角色文件(随技能一同提供):
prompts/swe.md、prompts/architect.md、prompts/chair.md、prompts/factcheck.md,以及prompts/rules/下的文件类型检查清单。 -
当前工作目录位于 git 仓库内。
为什么选择这些模型
每个席位都使用其原先所用系列的低价档模型,并通过推理强度弥补模型降档带来的能力损失,而不是改用更大的模型。一次委员会运行要调用四次,其中两次会读取完整差异;旗舰档的定价让常规的 PR 前检查变得像罕见操作一样昂贵。每百万个输入/输出词元的价格:
| 席位 | 原模型 | 现模型 | 强度 |
|---|---|---|---|
| SWE | kimi-k3 — 3.00 / 15.00 | muse-spark-1.3-contributor — 0.10 / 0.20 | xhigh |
| Architect | glm-5.3 — 1.40 / 4.40 | glm-5.3-flash — 0.075 / 0.25 | max |
| Chair | qwen3.8-max — 2.00 / 6.00 | qwen3.8-flash — 0.15 / 0.47 | xhigh |
| Fact-check | deepseek-v4-pro — 0.66 / 1.98 | deepseek-v4-flash — 0.22 / 0.66 | max |
席位实际受限的两项指标在更换模型后仍保持不变,这使得此次调整只是替换,而不是在能力类型上降级。每个替代模型都保留了约 1M 的上下文窗口——这是 SWE 席位的限制,因为它要读取完整差异、规则检查清单,以及通过文件工具打开的所有内容;如果运行超出窗口,就会按全部内容计费,却不返回报告——并且保留了**≥131k 的输出上限**,这是 Chair 的限制,因为它要输出汇总报告,而 64k 的上限曾导致报告被截断。
这些都不是经过实测的质量结论,而是有意作出的成本/质量权衡。如果委员会的评审不够深入,优先为 Chair 增加投入——它是提升精度的关键,也是负责驳回成员不可靠条目的阶段;下方的事实核查环节只能删除它放行内容的一个子集。先通过 OPENCODE_REVIEW_CHAIR_MODEL 恢复 qwen3.8-max,再调整其他三个模型,并记得同时设置 OPENCODE_REVIEW_CHAIR_VARIANT。
为什么运行配置采用这种方式
- 由
--model驱动(而非--agent):自包含(不依赖配置),并避免mode:subagent作为顶层模式导致的自复制炸弹。 --pure会跳过外部插件,因此启动更精简,也不会有端口冲突。--format json让报告能够被还原:模型的文字、工具输出和停止原因会作为独立的类型化事件传入,而不是作为渲染后记录中的多行内容,事后还得再辨别。stderr 会被捕获到单独的文件,而不是合并,因为它不是 JSON,否则会将无法解析的行插入事件流。OPENCODE_PERMISSION仅对本次运行设置——它会与已保存的配置合并,而不是替换配置,也不会修改已保存的配置。edit会被拒绝,而 bash 采用默认拒绝,仅允许只读命令(git 读取,以及cat/head/tail/wc/ls/grep/rg)。末尾用于拒绝 Shell 元字符的模式(;、|、&、>、反引号、$(、<(、换行符)会覆盖允许规则,因此获准的命令前缀无法夹带串联命令、通过管道传入 Shell,或执行重定向写入。待审查的差异内容对审查模型来说是不可信输入,因此这既能加固提示词注入防护,也能让(可能尚未提交的)工作树保持只读;不过,glob 匹配意味着这只是纵深防御,并非硬性沙箱。question和doom_loop会被拒绝,这样运行就不会因等待无头模式下永远不会到来的输入而停滞。- 运行期间没有网络,也不能委派任务。
webfetch和websearch会被拒绝,task、skill和lsp也一样。真正关键的是webfetch:此前它未设置,而 opencode 对它的默认行为是允许,因此审查者在读取差异时拥有不受限制的出站网络访问权限,而本段也说明该差异内容是不可信的——这使注入文本可以将仓库内容发送到外部,且没有 bash 所具备的主机或路径限制。没有任何角色设定要求访问网络。 - 每个
PermissionConfig键都明确设置了处理方式,而非依赖默认值。“未设置”并不意味着统一的默认行为,不能一概而论:在同一次无头运行中,未设置的external_directory会自动拒绝,而未设置的webfetch则会允许。拒绝:edit、question、doom_loop、webfetch、websearch、task、skill、lsp。结构化设置:bash、external_directory。有意交由用户配置决定的项目:read、glob、grep、list、todowrite——将它们明确设为允许,会覆盖用户有意施加的限制,而这些正是审查者的实际工作。拒绝工具是免费的,不像拒绝 bash 模式那样有代价:被拒绝的工具会从模型的工具集中移除,而非在调用时遭拒,因此不会消耗轮次。 - 边界的实际含义。 bash 模式与路径无关——
head *会匹配head /anywhere——因此允许列表限制的是命令,而非路径:只要写成 Shell 命令,就一直可以读取仓库外的文件。文件工具则由external_directory单独管控;对于未列出的路径,其默认行为是“询问”,也就是在无头模式下自动拒绝。因此,过去访问同一文件的两种途径会有不同结果,导致成员在被拒绝的Read调用上浪费轮次(#24)。现在,脚本会允许文件工具读取它发现的依赖缓存中的文件(Go 模块缓存、$CARGO_HOME/registry,以及OPENCODE_REVIEW_DEP_DIRS中的所有路径),让关键场景下的便捷读取方式能够正常工作:如果差异中的断言体现了某个依赖的契约,那么该依赖的源码本身就是审查问题的一部分。这里只设置允许规则,从不进行一概拒绝——因为"*":"deny"也会覆盖 opencode 自身的条目和用户配置的条目,两者会合并。Go 路径会从GOMODCACHE/GOPATH/go env文件中解析,而不是通过运行go env获取:在任何权限集建立之前,脚本必须先从仓库根目录运行 PATH 解析出的git和opencode,但不应为了可选的便利功能扩大权限。 - 这些角色设定会预先说明边界,而不是让模型通过碰壁才发现:不执行测试或构建(否则会运行不可信差异中的代码)、说明哪些内容可读以及如何读取、不重试遭拒的调用;并且,由于有两次成员曾在调用遭拒且未写入任何内容的情况下结束运行,所以无论如何都要生成报告,并注明哪些内容无法验证。
GIT_PAGER=cat/PAGER=cat可阻止 git 打开分页器,避免它在非 TTY 环境中挂起。- 两名成员并行运行,然后主席运行一次,最后可选的事实核查运行一次——因此大型差异可能需要几分钟。每次模型运行都有独立的超时设置;某个成员挂起不会阻塞其他成员、主席或事实核查。
- 事实核查环节只会移除误报:如果发生任何失败、超时或输出为空,就会原样输出主席的报告;被移除的条目会透明列出(而非悄悄删除),以便你推翻该判断。
事实核查环节能捕捉和不能捕捉的问题
事实核查环节(第 3 阶段)是一种刻意收窄范围且安全的精度过滤器,而不是通用的噪声消除器。其覆盖边界已通过受控测试和真实提交的 A/B 测试得到实证确认:
- 它只针对 diff 内部可证伪的主张——即提供的 diff 本身直接与之矛盾的发现(例如,diff 显示有空值检查,却声称“没有空值检查”;diff 显示变量被返回,却声称“变量未使用”)。在一个植入错误正例的测试样例中,它将已呈现发现的精确率从约 60% 提升到 100%,且召回率没有损失。
- 它有意不处理关于 diff 之外代码的主张。 审查成员可能通过工具读取了其他文件;事实核查器只能看到 diff,因此按照“证伪,而非验证”的规则,它会保留任何无法直接反驳的内容。在一次真实 A/B 测试中,一个能力较弱的负责人过度审查了其他文件(
agent.go、llm_cmd.go),所有这类条目都被保留了下来——而这些条目后来证明是真实的相邻问题,因此保留它们是正确的。 - 后果——它在真实场景中的命中率较低,这是设计使然。 上游的两种影响通常会让它无内容可删:(1)能力较强的负责人已在综合评审期间排除了站不住脚的条目;(2)真实 diff 中的大多数委员会噪声都来自对 diff 外内容的过度审查,超出了本环节的范围。应将事实核查视为一种低成本的最后防线,用于拦截负责人放过的、与 diff 矛盾的主张,而非主要的精度提升手段。主要手段是使用强大的负责人模型。
- 如果你确实还想捕捉对 diff 外内容的过度审查,就需要向事实核查器开放仓库读取权限——这会牺牲“仅检查 diff ⇒ 永远不会过度删减”的安全保证,并增加时间和令牌开销。当前设计有意保留这项安全保证。