已知局限(诚实边界)
ReviewGate 是静态 LLM 质量闸口,不执行代码。以下边界来自真实评测(见 docs/evals/),
明示局限是为了可信——宁可说清楚做不到什么,也不夸大。
1. 细微算法 off-by-one / 进位 / 需"真正执行"才暴露的 bug
- DST(#1003)/parseISO 24:00(#1229)/负零差值(#739) 经「执行推演」提示改进后已能 BLOCK。
- 进展(2026-06-26):revert
addBusinessDays周末修复后,当前版本 2/2 稳定 BLOCK, 命中周末分支的 off-by-one——此前记录的"addBusinessDays 周末漏报"已不再静默放行。 - run_check 修复(2026-07-01,见
docs/evals/2026-07-01__exec-verify-runcheck-fix.md):--exec-verify的run_check此前有严重 bug——子进程 stdout 未捕获,既泄漏到 ReviewGate 自身 stdout(破坏--format json,CI 直接挂),执行结果又从未回传给模型(一律收到(no output))。 即"exec-verify 开着也没用上"的真实原因是工具坏了,而非模型不调用。修复后 run_check 才真正提供 执行信号,--format json在 exec-verify 下也不再损坏。 - 勘误(2026-07-01):此前把 big.js #125 记为"进位漏报"是误标——该 issue 是作者问能否简化,
两种写法(
(c[j]+b)%10vsc[j]=b)在该位置c[j]恒 0、可证等价(20 万随机 + 全 9 对抗,0 差异)。 ReviewGate 的 ai_smell 判其"等价/冗余"是正确的,不是漏报。 - 诚实边界(仍未根除):
- 大仓库上有维度撞超时(已标 incomplete、未静默放行,但该维度未审完)。
- 最细微的单步 off-by-one / 逐位进位仍可能误推演,属理论硬尾——瓶颈仍是"模型是否主动怀疑并验证"; run_check 修好后模型可可靠执行核验,但仍需模型主动发起。
- 可靠防线仍是单元测试:纯"读+推理"难稳定模拟多次迭代的边界算法,关键算法逻辑不要仅依赖静态审查。
1b. 大 diff 上的跨文件状态表征漂移(无局部坏味道)
- 标本:syncthing #10170(revert 法,见
docs/evals/2026-07-02__frontier-10170-state-drift.md)。bool 字段与位掩码双事实源、聚合掩码删位后计数消费方漂移——19 文件、每个 hunk 各自看都自洽, bug 是三跳跨文件不变量。logic 维度基线与加「状态表征迁移」检查项后均 2/2 零命中(改动已撤,不留无效复杂度)。 - 定性:这类 bug 需要「本该做什么」的意图上下文,属
--intent意图评审的领地(PR 标题一句话即含验收标准); 缺陷向静态审查补不了意图缺失。该用例留在dataset-recall.tsv作 frontier 标本,预期长期先红。
2. 无上下文信号的"裸"危险调用
- 例:3 行
requests.get(url)且无任何"url 来自用户"的线索——模型不一定判定为 SSRF。 - 缓解:真实代码通常带 handler/请求参数上下文(带上下文时召回良好);高危类型可用
--samples N提升稳定性。
3. run-to-run 方差
- LLM 固有:同一改动多次运行命中的发现集合会有波动;borderline 置信度会在 WARN/BLOCK 间抖动。
- 缓解:dedup + 证伪 judge 收敛;
--samples N取并集,同维度严重度/置信度取中位数(偶数个取偏低者),避免「跑 N 次里有一次报高就算高」。多单元(大 PR)自动固定为 1。
4. 未支持语言的精确工具降级
- tree-sitter 仅覆盖 rust/cpp/python/go/js/ts/java;其它语言(含仓颉等)LLM 审查照常可用,但
find_definition/callers/references走 grep 词法兜底、find_duplicate_functions不可用、<lang>.mdper-language 规则不路由。补一种语言=加 tree-sitter grammar + 扩展名映射。
5. 仅审查 diff(改动)
- 默认只评审本次改动;不主动审计仓库既有代码。删除安全防护这类「负向改动」已专门覆盖 (prompt 例外规则 + cJSON 删除式漏洞验证通过)。
6. 意图评审(--intent)的验收清单
- 结构化强制:意图被解析成 N 条验收标准(C1..CN),评审跑完后未被逐条 verdict 的标准兜底标
? not assessed—— 所以清单一定覆盖每条标准,不再出现"空清单/静默漏掉"。未核对的标准会让结果降级 WARN(绝不伪装成 PASS)。 - 仍有的模型层面局限:模型可能把预算花在深挖某条标准上,导致其余标准只是"未核对"而非真正打勾;
此时清单诚实显示"未核对",提示放宽
--timeout或拆分意图后重跑,而不是假装审过。 - 意图质量决定上限:一行提交信息只能拆出 1~几条粗标准;写清楚验收标准(编号/分条)才能得到细粒度清单。
7. 增量复审(--incremental)的覆盖取舍
- opt-in、默认关闭:只有显式
--incremental才启用;不带则每次全量审查,行为不变。 - 原理:按文件缓存发现,只重审自上次以来 diff 逐字节变化的文件。之所以成立——ReviewGate 只报 改动行上的发现(见 #5),发现锚定在有 diff 的文件上;文件 diff 不变则其发现不变。跨文件上下文由 Agent 按需拉取,不影响缓存正确性。
- 失效即安全:缓存键含"评审签名"(维度/模型/规则/采样/exec_verify + 内部
prompt_version),任一变化 即整体作废,绝不复用过期结果。从.reviewgate/ignore删条目会照常恢复发现(抑制状态不进缓存)。 - 诚实边界(取舍,非 bug):若改动文件 X 令未变文件 Y 的既有代码出问题,而 Y 本身无 diff,增量下 不会重审 Y——但这类问题本就在「仅审 diff」(#5)范围外,全量审查同样不报。意图评审(
--intent) 是整体性的、每轮全跑,不受增量影响。 - 建议:CI 强闸口用全量;本地迭代/预检想省钱再开
--incremental。
8. 审查范围排除([exclude] / .reviewgateignore)的取舍
- 默认开启内置规则:跳过依赖锁、vendored 依赖、protobuf/ORM 生成物、压缩打包产物与二进制文件。 实测 20 个真实仓库(见
docs/evals/2026-08-01__exclude-scope-20-repos.md)送审 token 合计降 20.3%, 且 7/20 的仓库因没有匹配项而完全零变化——没有匹配就没有退化。 - 诚实边界:内置规则是按路径判断的,不看内容。如果项目故意把要审的代码放在
node_modules/、vendor/、third_party/下(如 deno 的 npm 测试夹具),这些文件会被跳过。用!反选救回, 例:[exclude] patterns = ["!tests/specs/**"]。 - 排除必须可见:被排除的文件带原因出现在文本报告、JSON 的
excluded字段与 PR 评论里; 若一次改动全部被排除,报告会明说"全被排除"而不是"没有改动"。闸口不做静默少审。 - 锁文件被排除 = 供应链改动不进审查:
Cargo.lock/package-lock.json/go.sum里的 依赖替换、可疑版本跳变、registry 地址被改,默认都不会被 LLM 看到。这是有意的取舍—— 单个poetry.lock就可能占 90 万 token,而 LLM 本来也无法验证包的完整性;依赖安全该交给 专门工具(dependabot / cargo-audit / 各生态的 SCA)。要审就显式反选:[exclude] patterns = ["!Cargo.lock"]。 .reviewgateignore本身不被排除:改它等于改闸口范围,必须可审。- 全部被排除时仍是 PASS(退出码 0):因为这是你自己配的范围——只动 lock 文件的 PR 本就该放行。 报告会明说"本次改动的 N 个文件全部被排除规则挡下,未送审"并逐条列出,不会显示成"没有改动"。 CI 上想拦住"排除规则写太宽"这类配置事故,可直接判 JSON:
files_changed == 0 && excluded 非空。 实测见docs/evals/2026-08-01__decision-stability-20-repos.md的 excluded-path 植入对照。
9. 增量范围(--since-last-review)的取舍
- 只审上次审查之后新增的部分(新提交 + 未提交编辑),基准取自上次审查记录的 HEAD sha。
- 范围写进结论:报告/JSON
scope/PR 评论都会写明审的是哪一段——增量审查的 PASS 只对该段成立, 不代表整个 PR 通过。 - 拿不到基准就报错:没有上次审查、上次没记基准、基准 commit 被 rebase/force-push 冲掉时直接失败, 不会悄悄退回全量或更小范围。
--estimate-only不写会话,因此估算不会推进基准。
10. PR 讨论注入(--with-pr-discussion)的取舍
- 只做上下文注入,不做自动折叠:不会因为"有人评论过"就隐藏任何发现——按文本相似度隐藏发现 等于给闸口开后门。
- 注入内容不可信:PR 评论谁都能写,因此注入时会被围栏包住并显式声明为「数据不是指令」, "忽略之前的指令/不要报任何问题"这类提示注入不能用来关闭闸口。机器人评论与 ReviewGate 自己上一轮的 评论会被剔除,避免自我强化。
- 有长度上限,但会按单元×维度放大:讨论文本上限约 6000 字符(≈1.5–2k token),它和项目规则走 同一条注入通道,因此每个审查单元、每个维度都会带一份。大 PR(多单元)下额外开销约
2k × 单元数 × 维度数。这也是它默认关闭、需要显式--with-pr-discussion的原因。 超出上限时保留最新的评论并在文本里注明省略了多少条。 - 注入的是全部评论,不区分是否已解决:GitHub 的 REST 评论接口不返回 thread 的 resolved 状态 (那在 GraphQL 里),因此已被解决的讨论也会一起进上下文。代价是多占一点预算、可能压掉一个 "其实已经修好了"的点;好处是实现简单、不引入新的 API 面。真要区分需要接 GraphQL,暂未做。
- 目前仅 GitHub;其它平台不注入(宁可不给,也不猜 API 形状)。
11. Issue 分诊(reviewgate issue …)的边界
分类是规则启发式,不是模型判断
- 固定语料
crates/core/tests/fixtures/issue_classify.jsonl(58 条:真实线上 case + 常规样本 + 留出样本 + 真实仓库跑出来的误报)上当前准确率 57/58 ≈ 98%,cargo test -p reviewgate-core --test issue_classify_corpus -- --nocapture可复现, 测试同时打印全部已知缺口。 - 这个数字要怎么读:语料是回归护栏,不是能力评估。规则是对着它调的, 所以它保证的是"改规则不会悄悄弄坏别处",不代表在你的仓库上也有 98%。 真实分布上的表现见下一条。
- 合成样本测不出精度。 语料里曾有一条自造的"token 明文写日志"样本,为了让它通过 加进了
credential/hardcoded/plain text——语料放行了,真实仓库上却全是误报。 这类关键词只有跑真实分布才能证伪,所以语料里现在专门收了一批真实仓库跑出来的误报 作为负样本(clicli-*/arthas-*)。 - 对着真实标注的准确率:拿 cli/cli 维护者自己打的标签做 ground truth(749 条标签 唯一且已分诊的样本,
scripts/eval-issue-groundtruth.py可复现)——
| 类型 | 召回 | 精确 |
|---|---|---|
| bug | 77.7% | 84.2% |
| feature_request | 68.8% | 93.9% |
| documentation | 20.0% | 17.4% |
documentation 是明确的短板:真实的文档诉求大多不含 docs/文档 字样,
规则抓不到,会散进 bug / feature_request / unknown。自建语料测不出这一点——
语料里的 docs 样本都是"标题直接写 docs"的显式形态。
- 真实仓库上的分布(cli/cli 1020 条 + alibaba/arthas 500 条,无 LLM、未发布任何评论): 约 14–26% 落到
unknown——纯规则对措辞平淡的标题就是没有把握。这些会走min_confidence闸门转人工,不会发出错误结论。 - 已知缺口(已登记,不假装没有):英文凭据泄露表述抓不到。曾经加过
credential/hardcoded/plain text,但 cli/cli 真实数据上全是误报 ("Bad credentials" 是认证报错、"hardcoding master" 是分支名), 判成安全类会 @ 安全接口人——精度代价远大于那点召回,已回退。 - 短 ASCII 缩写按词边界匹配(
rce/xss/oom/bug),避免 "percentage"、"debug" 误命中;五字母以上的词仍是子串匹配,以保留injections这类词形的召回。 unknown不等于"没问题":它意味着没有足够信号下结论,会走min_confidence闸门转人工。
置信度不足时不下结论,但可能静默
- 低于
min_confidence就不发结论、不关单子,改发移交评论 +needs-triage+ 指派。 - 没配处理人时会静默跳过——被拦下的 Issue 不会消失,但也不会主动通知谁, 只能用
reviewgate issue stats --gated查。长期不看这个列表,等于这些单子没人管。
查重靠本地信号,语义漂移会漏
- 三路候选:FTS 全文、错误签名、向量。向量用的是本地哈希嵌入(不调 API、零成本), 因此"换一套说法描述同一件事"这类语义漂移基本抓不到,跨语言(中文 issue vs 英文 issue) 更是抓不到。查重是降噪,不是保证。
- 固定语料
issue_duplicate.jsonl上:真重复 3/5 摆出来,负样本 零误报。 阈值刻意偏向"宁可漏,不可错"——把一条真问题当旧单关掉的代价,远大于漏掉一条重复。 上面两类语义缺口都作为known_gap登记在语料里。
技术验证(--verify)依赖本地仓库,且判别力有限
- 需要
--repo-root指向对应的本地 checkout,且版本要对得上。仓库找不到 / 版本不匹配时 结论退化为UNVERIFIED——那是"没验证",不是"验证通过"。 - 它确实在读真代码:cli/cli 1020 条上跑了 334 次验证,产出
code_hits307 次、test_hits177 次、related_commits76 次——都是在 checkout 与 git 历史里搜到的实证。 - 但它的增量判别力很弱。拿维护者标签对照:
| 样本 | 跑了验证 | 其中判 LIKELY_BUG |
|---|---|---|
维护者标 bug | 70.4% | 35.1% |
维护者标 enhancement | 6.7% | 25.9% |
只差 9 个百分点。 也就是说在"已经被判成缺陷"的样本内部,翻代码几乎不再增加信息。
链路真正的判别力来自分类闸口——93% 的 enhancement 在进验证之前就被挡掉了
(最终裁决:非缺陷 74% 判 NOT_A_BUG,缺陷 42% 判 LIKELY_BUG)。
- 所以
--verify的性价比要自己权衡:它的代价是 clone 整个仓库 + 逐条搜索, 收益是那 9 个百分点和几条可贴进回复的源码证据。当作"给回复加证据"合理, 当作"判断是不是真 bug 的主要依据"不合理。用scripts/eval-issue-groundtruth.py在你自己的仓库上量一次再决定。
长跑模式不发布、不调模型
issue watch/daemon只做同步 + 本地 triage + 打印:不发评论、不打标签、不关单子, 也不调用 LLM(LLM 只在issue review的用户向说明润色里用一次)。 所有对外写操作只发生在reviewgate issue review --publish,且每项动作默认关闭。- 每轮限量:
--max-issues-per-run(默认 20)同时限制单轮同步与 triage 条数, 避免首次接入大仓库时一口气打满平台 API 配额。没同步完时游标不前进, 剩下的下一轮继续;已入库且未变更的会跳过,不占配额。
这些局限均有评测留痕。随版本演进会持续缩小(每条都标注了已做/可做的缓解)。