你两年前就禁掉了 componentWillMount,ESLint 也标记它,代码库里早就零使用。Cursor 生成一个新组件,悄悄又用了 componentWillMount。你指出 lint 报错,AI 说”好的我马上修”,改完那个文件。三个 prompt 之后,在另一个完全无关的组件里,componentWillMount 又冒出来了。AI 看得到 lint 规则,也表示同意,然后在跨轮次时直接忘掉。同样的剧情在 var、ESM 项目里的 require()、React 16 风格的 useEffect 清理逻辑、pg.Pool.connect 回调而非 Promise 上一遍遍上演。模式很清楚:AI 训练数据里仍然充斥着废弃形式,lint 反馈只在当前轮次有效,你的护栏没有触达真正需要的地方。
最快的修法(先做这个): 把禁令写进 AI 每一轮都会读的项目规则文件,并让 ESLint 变成硬性 error 卡口。Cursor 用 .cursor/rules/conventions.mdc,frontmatter 设 alwaysApply: true(旧的单文件 .cursorrules 已废弃,截至 2026 年 6 月在 Agent 模式下会被静默忽略);Claude Code 用 CLAUDE.md。然后在 pre-commit hook 里强制 eslint . --max-warnings 0,这样只要废弃写法还在,构建就不可能通过。对话里的纠正不会持久,文件才会。
常见原因
按真实会话中出现频次排序。
1. AI 训练时见到的大量代码都还没废弃这个 API
GitHub 上公开代码里大部分还是旧写法。模型见过一百万次 componentWillMount,只见过一千次 componentDidMount,概率自然偏向旧的。
如何识别:在与你修过的位置毫无关联的其他文件里,同一个废弃写法反复出现。
2. lint 反馈只覆盖单次轮次
Cursor 或 Claude Code 读到 lint 输出,把那一行改掉,接着进入下一个任务——规则并没有进入长期记忆。
如何识别:同一条 lint 规则跨会话反复触发;“修复”只在同一对话内生效。
3. 项目层没有规则告诉 AI 什么是被禁的
你的项目规则文件(.cursor/rules/*.mdc、CLAUDE.md 或 AGENTS.md)里压根没提到这条废弃,AI 没有任何信号说明这个代码库比公网更严格。2026 年有个很常见的变体:你把规则写在了根目录那个旧的 .cursorrules 里,但 Cursor 的 Agent 模式已经不再加载它,规则形同虚设。
如何识别:查规则文件——如果废弃模式没出现在里面,AI 就完全没有理由避开它。如果你唯一的规则文件就是 .cursorrules,那本身就是 bug:把它迁移到 .cursor/rules/。
4. ESLint 把规则设为 “warn” 而不是 “error”
规则存在,但只 warn 不 error。AI 跑完 lint 看到”通过了”,其实是被警告淹没了。
如何识别:eslint --max-warnings 0 失败,但单跑 eslint 退出码是 0。
5. 废弃是项目自定义规则,AI 无从知晓
你用自定义规则禁了生产代码里的 console.log。AI 要等到跑 lint 才知道有这条规则,即便看到了也未必理解为什么禁。
如何识别:lint 输出里的规则名,在 AI 的解释里从来没被提到过。
6. 库升级后悄悄废弃了旧写法
你把 pg 从 v7 升到 v8,回调写法被废弃。AI 训练数据 v7 和 v8 各占一半,挑哪个全看”感觉”。
如何识别:npm ls <package> 显示是较新的主版本;废弃写法对应的是上一个主版本。
你属于哪一种
| 症状 | 最可能的原因 | 最快的修法 |
|---|---|---|
| 废弃写法在全新的、无关的文件里冒出来 | #1 训练偏向 / #3 没有项目规则 | 在 .cursor/rules/*.mdc(alwaysApply: true)或 CLAUDE.md 里加禁令 |
| 规则写下来了,但 Cursor Agent 模式仍然无视 | #3,用的是旧 .cursorrules | 把规则搬进 .cursor/rules/conventions.mdc |
eslint 退出码是 0,但废弃写法明明在那 | #4 规则停在 warn 或被禁用 | 把规则设为 "error";跑 eslint . --max-warnings 0 |
| 修复在对话里管用,新会话又坏 | #2 lint 反馈只覆盖单轮 | 把 lint 做成 pre-commit 卡口,而不是对话提醒 |
| lint 报了一个 AI 从没提过的规则名 | #5 自定义/项目专属规则 | 在规则文件里写明规则及其原因 |
| 旧写法在依赖升级后立刻出现 | #6 库升级 | npm ls <pkg>;在 src/ 上跑 codemod |
开始之前
- 把当前在意的所有废弃模式列清楚,写下来:“我们永远不用 X,总是用 Y”。
- 跑一遍
eslint . --max-warnings 0,确认你的规则确实按 error 而非 warning 在执行。 - 确认 AI 真正读的是哪个规则文件:Cursor Agent 模式读
.cursor/rules/*.mdc,Claude Code 读CLAUDE.md,跨工具标准是AGENTS.md。注意你是不是还在依赖旧的根目录.cursorrules(截至 2026 年 6 月,Cursor Agent 模式已不再读它)。 - 抓住一个 AI 重新引入废弃写法的实例,保存文件名和前后 prompt。
需要收集的信息
- 废弃模式(如
componentWillMount、var、require()、回调 API)。 - 替代写法(正确做法)。
- 应该触发的 lint 规则名(
react/no-deprecated、no-var等)。 - 该规则在
eslint.config.js(flat config)中的启用状态与严重度。ESLint 10(2026 年 2 月发布)已彻底移除旧的.eslintrc系列格式。 .cursor/rules/*.mdc/CLAUDE.md/AGENTS.md的内容。- 如果废弃和库相关,库的版本号。
分步修复
按从快速见效到长期策略排序。
第 1 步:把 lint 规则改成硬性 error
用 flat config 的 eslint.config.js。ESLint 10(2026 年 2 月)已移除对 .eslintrc.* 的支持,所以如果你还在用 .eslintrc,要么是被钉在 ESLint 9(2026-08-06 停止维护),要么配置压根没被读取:
// eslint.config.js
export default [
{
rules: {
"react/no-deprecated": "error",
"no-var": "error",
"import/no-commonjs": "error",
},
},
];
然后在 CI 和本地都强制零警告——注意要显式带上 . 目标,flat config 需要它:
eslint . --max-warnings 0
这样 AI 那句”我跑了 lint,通过了”就真的会在废弃写法上失败。(warn 级别的规则不会改变退出码 0,AI 仍然以为自己干完了——所以严重度必须是 error。)
第 2 步:把禁止项写进 AI 真正会读的规则文件
格式取决于工具,而写错文件正是禁令触达不到模型的头号原因:
- Cursor Agent 模式:建
.cursor/rules/conventions.mdc。根目录的.cursorrules出于向后兼容仍然存在,但截至 2026 年 6 月在 Agent 模式下会被忽略,别再依赖它。.mdc文件需要 frontmatter,设alwaysApply: true让规则每一轮都加载,而不是只在 Cursor 判断”相关”时才加载。 - Claude Code:用仓库根目录的
CLAUDE.md(Claude Code 也读AGENTS.md)。 - 跨工具(Codex、Copilot、Gemini CLI 等):用
AGENTS.md,这是 28+ 个 agent 都读的共享标准。
Cursor 规则文件长这样——顶部那段 frontmatter 是必需的:
---
description: Banned legacy patterns this codebase rejects
globs: src/**/*.{ts,tsx,js,jsx}
alwaysApply: true
---
## Banned patterns (do NOT generate)
- componentWillMount, componentWillReceiveProps, componentWillUpdate
-> use componentDidMount + useEffect equivalents
- var -> use const, let
- require(), module.exports -> use ESM import / export
- pg client callback API -> use the promise / async-await form
- console.log in src/ outside of src/lib/logger.ts
If you generate any of the above, the lint check WILL fail.
Verify your code does not contain these patterns before responding.
CLAUDE.md / AGENTS.md 用同样的正文,纯 Markdown 即可(不需要 frontmatter)。这能把禁止项注入到 AI 每一轮的工作上下文里。
第 3 步:用正例告诉 AI 正确写法长什么样
加一份 docs/conventions.md,左右对照,然后在规则文件里引用它:
# Banned vs preferred
## React lifecycle
WRONG:
componentWillMount() { this.fetch(); }
RIGHT:
useEffect(() => { fetch(); }, []);
## Module system
WRONG:
const x = require("foo");
RIGHT:
import x from "foo";
具体对照比抽象禁令有效得多——AI 会模仿 RIGHT 那边的样式。
第 4 步:把 lint 放进 pre-commit hook
package.json:
{
"scripts": {
"lint": "eslint . --max-warnings 0"
},
"lint-staged": {
"*.{ts,tsx,js,jsx}": "eslint --max-warnings 0"
}
}
配 husky / lefthook 等 pre-commit hook。提交时的强制校验 AI 绕不过去。
第 5 步:能 --fix 的就自动修
很多废弃规则自带 autofix(no-var、prefer-const、prefer-arrow-callback 等):
eslint . --fix
每次 AI 生成完跑一遍,免去人工抓回归的认知负担。
第 6 步:大范围迁移用 codemod
回调转 Promise、class 转 hooks、CommonJS 转 ESM 这种大动作,用 jscodeshift 或 ts-morph:
npx jscodeshift -t ./codemods/cb-to-promise.js src/
codemod 跑完后,代码库里废弃写法的样本归零——AI 对你代码库做 RAG 时,只看得到新写法,自然会模仿新风格。
验证
eslint . --max-warnings 0在整个仓库零警告退出。- 让 AI 在 3 个不同 prompt 下生成新文件——没有一个再引入废弃写法。
grep -r "<deprecated-pattern>" src/全仓零命中。- 故意提交一段废弃写法,pre-commit hook 用清晰错误信息拦下。
长期预防
- 在
.cursor/rules/*.mdc/CLAUDE.md/AGENTS.md里维护”deprecations”小节,每次升级主版本依赖时回顾。 - ESLint、TSC、自定义 AST linter 对废弃 API 统一设为 error——warning 是 AI 会忽略的训练噪声。
- 每条禁令都配上正确替代写法,不要光禁不教。AI 对”这样写”远比对”别那样写”敏感。
- 定期
grep -r "<deprecated>" src/清理漏网之鱼;AI 工具非常忠实地模仿仓库现状。 - 升级主版本时,专门拉一个 PR 用 codemod 把废弃 API 在整个仓库扫一遍。混合状态既迷惑人也迷惑 AI。
- 简要写下每条废弃的”为什么”。AI 对”componentWillMount 在并发模式下不安全”的锚定效果,远好于一条干巴巴的禁令。
常见误区
- 把规则停在 “warn” 级别。AI 看到 lint 通过就以为自己干完了。
- 把禁令写在根目录
.cursorrules里,还以为 Cursor 会读。Agent 模式无视它,搬到.cursor/rules/*.mdc。 - 升级到 ESLint 10 后还留着
.eslintrc——它已不再被读取,你所有规则都不会触发。迁移到eslint.config.js。 - 禁令只写进自己的笔记,而不是 AI 真正会读的规则文件。
- 在一个文件里修了废弃写法,却没 grep 整个仓库——通常还埋着 5 到 10 处。
- 以为 AI 会从本次对话内的纠正里”学到”,于是不写规则文件。跨会话不持久,只对同一对话的后续轮次有效。
- 同一个文件里新旧风格混杂。AI 会挑离它最近的那种风格沿用下去。
相关问题见 AI 建议了过时的依赖、AI 引入的 TypeScript 错误、Claude Code 缺失项目上下文。权威格式参见 Cursor Rules 官方文档 和 ESLint 配置文件文档。
FAQ
Q:我一直在纠正它,它一直犯同样的错,是我哪里没做对吗?
纠正只在当前对话有效。把规则搬到 .cursor/rules/*.mdc(Cursor)或 CLAUDE.md(Claude Code),新会话才会继承。对话内提醒只是短期记忆。
Q:我的 .cursorrules 文件里已经写了禁令,为什么 Cursor 还是无视?
截至 2026 年 6 月,根目录那个旧的 .cursorrules 已废弃,在 Cursor 的 Agent 模式下不会被加载——只要切到 agent,整个文件就停止生效。把它迁移到 .cursor/rules/conventions.mdc,frontmatter 设 alwaysApply: true。你可以为更老的非 agent 流程保留 .cursorrules,但别再依赖它。
Q:lint 规则已经启用了,AI 还是生成废弃写法,为什么?
AI 是”先生成 → 再跑 lint → 再修当前轮次的文件”的流程。如果你在 lint 跑之前就接受了代码,或之后才手动 lint,废弃形式就溜走了。把 lint 做成 pre-commit 硬卡口。
Q:我直接把废弃 API 从库里删掉行不行?
如果库由你掌控,这是最干净的做法。第三方库就不行了,只能靠规则文件 + lint 强制。
Q:怎么让 AI 主动推荐现代写法,而不是只是不用旧的?
每条禁令都配上一个具体的 RIGHT 示例。AI 模仿正面样本的能力远强于尊重抽象禁令。