用 CLI 给 AI 代码变更做脆弱性体检
fragility 是零依赖 TypeScript CLI,可分析 Git 差异或补丁文件,对测试浅薄等四类脆弱性信号评分并输出 CI 判定。工具无需模型调用、API 密钥或服务端,支持 JSON 输出。
AI 深度解读
AI 把代码写快了,团队却未必能同样快地理解它。作者遇到过一次 1,199 行的改动:测试全绿,评审也走完了,两周后团队仍说不清其中一半在干什么。问题卡在了理解和评审这一关。
他因此做了一个叫 fragility 的 TypeScript CLI,用四类信号给待合并的 diff 打分,并输出 CI 判定。它的价值是把一些容易被忽略的风险变成可检查的数字;但文章中的高低分来自合成样例,还不能证明它能准确预测真实项目的故障。
四个分数,想拦住什么样的改动
fragility 是零依赖工具,不需要 API key、服务器或模型调用,全部在本地运行。它检查的是你交给它的 diff,不是整个仓库的质量。
四类信号分别对应四个问题:
- 测试浅薄程度:改动行有没有测试断言支撑,重点看 orphan lines,也就是工具认定没有测试覆盖的行。
- 过度抽象:统计改动函数的调用深度、参数数量和分支数量,估计理解这些代码要付出多少脑力。
- 理解债务:观察改动中有多少代码来自后续无人再触碰的提交,以及代码合并速度。AI 标记从完整提交消息中识别,包括
Co-Authored-By尾注。 - 评审瓶颈:把 diff 大小映射到问题检出率曲线,再把每日合并行数与默认
400 lines/day比较。
它用可计算的代理指标,提示哪些改动可能超出团队的理解能力。
下面三条命令分别用于检查最近一次提交、读取 diff 并输出 JSON,以及查看评审指标的解释:
fragility audit --base HEAD~1
fragility audit --diff pr.diff --format json
fragility explain review
注意第一行的 --base HEAD~1:比较基准决定了检查范围,接入 PR 时要选对基准。
每项信号和综合分都在 0–100 之间。虽然叫 FRAGILITY SCORE,文中的例子却是高分更健康。判定有 ROBUST、WATCH、FRAGILE 三档;退出码列出了 0 表示稳健、1 表示脆弱、2 表示输入错误,文章没有单独讲清 WATCH 对应什么退出行为。
20 分与 96 分,证明了多少东西
作者用一个预置风险的合成样例演示工具:模拟 1,199 行 AI 风格改动,得到 FRAGILE 20/100,退出码为 1。其中 orphan lines 为 1,169/1,169,占 100%,评审速度为每天 1,225 行。另一个小型健康样例得到 ROBUST 96/100,退出码为 0。
另一个脆弱 diff 文件的信号是 10/45/85,综合分 47/100,退出码 1;加入 LCOV 覆盖率文件后,综合分降到 43。健康 diff 则显示 100/100/100。
这里有两个空白:为什么 1,199 行改动只统计了 1,169 行,以及为什么四类信号在单文件示例里只列出三个分数,原文都没有解释。不能自行补齐口径。
这些结果说明工具能识别作者植入的风险,也说明覆盖率输入会改变判断。但作者没有提供真实项目上的误报率、漏报率或缺陷对照数据,不能据此认定 20 分的 PR 一定比 47 分更容易出事故。
真正有启发的是那次漏检
工具第一版只扫描提交标题里的 AI 标记。然而,常见的 Co-Authored-By 尾注位于提交正文。结果,一个按这种方式标记的提交被报成 0% orphan lines,拿到 WATCH,通过了原本应该拦住它的检查。
测试为什么没发现?因为所有测试都把 AI 标记写在标题里。
修复后,工具读取完整提交消息,并补了更贴近实际的样例:正文尾注、深调用链,再配一个薄弱测试。这个场景得到 59/100 FRAGILE,退出码为 1。
测试全绿,只能证明代码满足了测试写下来的假设。
这对日常 AI 编程很有参考价值。让模型实现功能,再围绕同一套理解生成测试,很可能把相同的盲点复制两遍。作者这次修正的关键,是把真实提交格式带进测试,而不是单纯增加用例数量。
我的判断
我认同把评审压力纳入合并前检查。覆盖率不能回答「这次改动是否大到没人看得懂」,调用深度和合并速度也确实能提供额外线索。
但综合分仍是启发式判断。断言统计使用正则,会少算表驱动测试和动态测试;AI 标记只能说明提交消息声称使用了 AI,不能证明代码由谁编写。检出率曲线来自已发表 PR 研究的群体数据,400 lines/day 也是可配置的经验默认值,不是每个团队的实测上限。
作者强调 orphan-line 数量可核验,并提到 git blame。这里需要区分:git blame 能追溯代码行的提交归属,不能单独证明某行被测试覆盖,更不能证明断言有效。追溯证据与测试证据仍要结合看。
替代工具也各有位置。按作者的比较,只关心改动行覆盖率,可以用 patchcov 或 Target PR coverage;gapix 在 AST 层面检查断言,比正则计数更深入。CodeRabbit、Qodo 提供云端 LLM 评审,LinearB 关注团队工程指标。fragility 的特点是离线、免费、确定性执行,并把多个信号放进同一条 CI 命令。至于哪种方式更能减少缺陷,文章没有对照数据,仍属于作者的经验判断。
能拿来做什么
- 给 AI 生成的大型 PR 增加一次审计,先观察分项与 orphan lines,再决定是否拆分改动、补测试或安排熟悉模块的人评审,不必立即把综合分设为硬门禁。
- 已经产出 LCOV 的前端或 Node 项目,可以把覆盖率文件加入检查;本文没有给出对应参数,接入时需要查工具说明,不能凭空拼命令。
- 为提交解析、文件格式等功能补真实输入样例,尤其检查模型生成的测试是否重复了实现里的假设。
Co-Authored-By位于正文,就是一个具体提醒。 - 按原文发布时的状态,工具需要从源码安装,
npm publish尚待完成。可以先跑仓库附带的预置样例和演示记录,复核分数,再评估是否适合自己的仓库。
以下是原文给出的源码构建与运行步骤:
git clone https://github.com/techdevfun/fragility.git
cd fragility
npm install
npm run build
node dist/src/cli.js audit --base HEAD~1
注意最后一行:它审计的是当前仓库相对 HEAD~1 的改动;复现样例时还需要按仓库说明选择对应输入。