MetaLint: Easy-to-Hard Generalization for Code Linting
Atharva Naik, Lawanya Baghel, Dhakshin Govindarajan, Darsh Agrawal, Yiqing Xie, Daniel Fried, Carolyn Rose
cs.SE, cs.CL, cs.LG
2025-07-16
卡内基梅隆把lint做成指令跟随,只用Ruff合成数据。Qwen3-4B在人工PEP难规则上检测F值从25.9%升至70.4%,召回最高,定位26.7%对齐o3-mini。
现有 LLM linter 大多把一套固定最佳实践写进参数,训练时当分类标签用。规则会变:语言升级、库改 API、安全标准更新,模型就会对过时写法误报,对新规则漏报。静态规则集已经出现过对过时写法过拟合。
难度还分层。Ruff 的 S104 到 S108 这类「疑似硬编码密钥」几乎看表面就能抓;PEP 506 建议安全场景用 secrets.choice,别用 random.choice,但不是每次 random.choice 都不安全,必须看意图。规则引擎覆盖不到这类上下文依赖的硬规则。现有公开评测要么用 linter 自动造数,要么数据不公开。
CMU 的 MetaLint 把每条最佳实践当成一条自然语言任务:输入里给规则说明加正反例,模型只判这一条,输出违规行号的 JSON;没有违规必须写 NO VIOLATIONS FOUND,乱报别的规则会扣分。规则从参数里拿出来,测试时换说明书就能换规则,不用重训。
监督全部来自现成 linter。Python 用 Ruff(800 多条规则里抽 50 条)在 The Stack 上跑,得到约 5.3 万条、正负约 1:3;Java 用 PMD 的 269 条规则做出 9.68 万条,再用 tree-sitter 按 8 个 JEP 写查询得到 12.73 万条。说明书从官方文档刮下来。
训练分两段:
SFT 容易把训练规则背下来。DPO 强迫模型在同一条说明书下把行号标准,泛化主要出在这一步。CoT 版走同一流程,只保留解析后完全正确(奖励等于 1)的思维链。
评测另做了一套人工 PEP benchmark:15 条 Ruff 覆盖不到的硬规则,从 Stack v2 启发式召回再人工核,536 条、违规和无违规约 52 比 48,标注一致性 Cohen's κ=0.95。训练文件和评测文件按内容哈希去重。
在 Ruff 合成测试集上,Qwen3-4B 无 CoT 的检测 F 值从 35.4% 到 SFT 的 45.0%,再被 DPO 拉到 78.7%;定位 F 值从 14.4% 到 66.1%。拆开看,SFT 几乎只涨 in-domain(检测 F 加 61.1 个百分点),far transfer 只加 1.4;DPO 在 far transfer 上再加 40.3 个百分点到 77.9%。CoT 底盘已经高,DPO 的边际更小。
Java 上同一套流程成立。Llama-3.2-3B 在 PMD 域内检测 F 从 1.3% 到 DPO 后 58.9%;PMD 训完去测从未见过的 JEP,检测 F 到 61.8%、定位 37.2%。
硬规则才是主场。Qwen3-4B 在 PEP benchmark 上:
| 设置 | 检测 F | 检测召回 | 定位 F |
| Qwen3-4B 底模 | 25.9% | 17.2% | 8.8% |
| +SFT | 13.8% | 8.2% | 2.9% |
| +SFT+RS-DPO | 70.4% | 70.4% | 25.0% |
| CoT 底模 | 53.5% | 39.9% | 18.8% |
| CoT+RS-DPO | 64.7% | 49.6% | 26.7% |
SFT 在硬规则上掉点,说明它在背 Ruff。跳过 SFT 直接 DPO,格式崩了,分数接近零。把 5 万条 Ruff 样本的说明书全换成「找代码质量问题」、评测时仍按 PEP 种类计分,检测和定位 F 都是 0.00%。说明书是迁移机制,堆数据量推不过去。
跟更大模型比:无 CoT 的 4B 检测召回全场最高,70.4%,检测 F 70.4% 贴近 o3-mini 的 70.7%,低于 GPT-4o 的 77.2% 和 GPT-4.1 的 75.5%。定位 F 26.7%(CoT)对齐 o3-mini 的 27.1%,明显低于 GPT-4.1 的 46.5%。代价是误报:无 CoT 在 257 条无违规样本上 FPR 24.5%,CoT 降到 5.1%;GPT-4o 是 9.7% 误报、67.9% 召回。
PEP 536 条上,无 CoT MetaLint 比 GPT-5 便宜约 175 倍。Flask 仓库 83 个文件乘 15 条 PEP,CoT 版比 Claude Code 里的 Sonnet 4.6 和 Opus 4.7 便宜 9.5 到 21 倍,成本随文件-规则对数线性,不会跟对话上下文一起涨。
给 agent 流水线塞一个专职 critic,是这篇能落地的位置:lint 几乎不需要仓库上下文,也不用跑代码。4B 可单卡自托管,测试时换自然语言说明书就能加新规则,不必为每条 PEP 重训。
这是一次干净的 easy-to-hard 证据,不是又一个「微调让 linter 分数变高」。代价也很清楚:定位仍弱,无 CoT 高召回绑着 24.5% 误报。当专职扫描器、用人审误报,划得来;当唯一门禁,还早。
附录自己列了:PEP 文本可能进过预训练,用底模对照能隔离这次训练的增益,隔离不了污染;没试过用教师模型给无 CoT 模型灌思维链;自举生成 CoT 容易直接抄提示里的 hint;偏好优化只做了 DPO,没试 PPO 或 GRPO;没有按抽象程度排课程;Python 和 Java 分开训,没有跨语言;只做检测,没有补丁和重构;评测按单条规则出题,工业场景要批处理多规则,文中没做。
另外几处读起来发虚。难规则只有 15 条 PEP、536 例,泛化叙事建立在这个窄分布上。无违规样本的 FPR 已经说明高召回很贵,论文把 FPR 写成可通过 DPO 里无违规比例调节的工作点,但没给出生产阈值下的完整 precision-recall 曲线。Java 的「未见过」是 PMD 和 JEP 互转,两边说明书风格差很大,JEP 转到 PMD 比反向弱,多样性叙事比跨语义迁移更稳。定位 F 卡在 25% 到 27%,跟「能当 linter」还有距离。