Skill 管不住代码风格时:把项目约定写成 ESLint 护栏
Skill 很适合写项目约定:目录怎么分、组件怎么拆、哪些写法要避开、改代码前要看哪些文件。问题是,Skill 只是提示,它不能真的拦住 Agent 下一次继续写偏。
反复出现、能从源码稳定判断的偏差,可以做成 ESLint 规则:编辑器直接标出位置,pnpm lint 和 CI 再提供统一检查。需要跨文件、资源或构建产物才能判断的问题,则交给专项脚本。这样 review 可以讨论例外与取舍,不必每次重新解释同一条约定。
这批规则后来已经整理成 @sheng/eslint-plugin。规则分组、在线示例、接入方式和选项边界统一放在 GitHub Pages 文档站;想查某条规则会报什么、怎么配置、适不适合当前项目,直接看规则列表。
约定应该进入 Lint,还是继续留在文档、review 或专项脚本里,取决于它能否被稳定判断,以及判断需要读取哪些信息。
规则不是为了统一审美
项目约定型规则和 Prettier 的边界不同,也不能只来自个人口味。一条约定值得变成 ESLint,通常要满足一个条件:它反复制造了可预期的维护成本。
适合规则化的问题大致有三类:
- 会破坏工具链。代码看起来能运行,但会让 IDE、静态扫描、翻译平台、类型检查或构建链路失去真实信息。
- 会把反馈推迟到运行时。源码阶段已经能判断风险,却要等 dev server、浏览器控制台、构建日志或线上页面才暴露。
- 会增加重复 review 成本。团队已经形成稳定约定,每次 Agent 或开发者写偏时,review 都在重复解释同一套口径。
这些规则让问题更早、更靠近代码现场出现。能在编辑器里出现波浪线的问题,不应该长期留到 PR review 里靠人提醒。
先确定这是谁的项目约定
这些默认写法表达的是我主导项目时的工程习惯。维护别人的项目,先看贡献指南、项目规则和当前模块已有的稳定范式;例如 composable 一直叫 useXxx.ts,就沿用它。没有约定,或者同一职责里 useXxx.ts 与 use-xxx.ts 混用时,再对本轮涉及的代码采用自己的默认,不为统一偏好批量改名。
风格规则应当能按项目选择、关闭和划定目录范围。关闭文件命名偏好,不应连带让 SSR 或作用域检查漏掉合法文件名。框架允许的写法也不能只因为偏好不同,就被解释成语法错误。
Skill、Lint 和脚本各管一层
博客适合解释经验从哪里来、机制为什么如此、在哪些条件下成立。Skill 和 AGENTS.md 适合把经验变成动手时可执行的指导:为什么这么拆目录、某类组件应该先看哪些文件、哪些场景要避开某种写法。它们能帮助 Agent 在动手前建立上下文,也能让人快速理解项目偏好。
ESLint 适合检查那些可以从 AST、作用域、文件路径或少量项目配置里稳定判断的问题。比如某个调用参数必须是静态值,某类 import 的目标文件必须真实存在,某种表达式结构已经稳定被团队认定为噪音。开发者可以直接根据代码旁边的提示判断问题,不必先读完整套方法论。
样式源码里的 selector、declaration 和 at-rule 由 Stylelint 读取;Vue 模板 class 与 JS 字符串继续由 ESLint 或相应工具处理,不能把两者当成相同输入。
脚本检查适合更重的边界:需要读文件系统、解析资源文件、检查构建产物,或者跨 Vue、CSS、SVG、JSON 多种文件一起判断。把这类问题硬塞进 ESLint,往往会让规则又慢又难配置。脚本可以跑在 pnpm check、CI 或发布前流程里,作为 ESLint 的补充。
review 仍然保留最终判断。Lint 能指出结构风险,但不能替开发者理解全部业务上下文。尤其是项目风格型规则,命中后应该问「这里是不是确实破坏了约定」,而不是机械追求零提示。
一个经验可以同时需要背景文章、执行指导和自动检查,但不用强求每项都有三份。科普、工具操作、纯格式化或只能运行态确认的内容,可以保留适合的产物。判断的是职责是否覆盖,而不是数量是否凑齐。
从 warn 开始
项目约定型规则第一版大多适合用 warn。旧代码可能已有历史例外,规则也需要观察误报;先确认它能准确指出问题,再决定哪些目录应当阻断提交。
确认误报很少、例外已经能通过 options 或局部注释表达后,再考虑把部分规则升成 error。适合升成 error 的通常是确定会坏的问题:运行时值拿不到、资源文件不存在、编译器宏不会被识别、生产构建会失败。这类问题继续用 warn,错误就可能要等到运行或构建时才真正阻止流程。
warn 默认不等于阻断 CI;实际是否失败,还取决于 --max-warnings=0 等命令参数。规则实现、消费者启用和流水线阻断要分别确认。
严重级别也可以按场景拆开。开发目录先 warn,新代码目录先 error;迁移期只扫某个 feature,稳定后再扩大范围。规则本身要配置化,把函数名、alias、忽略路径和历史例外交给项目配置,不要写死在规则源码里。
规则文档放在规则旁边
具体规则会持续变化。如果博客同时维护完整规则、示例和源码包,文章会越来越像规则手册,也容易在实现更新后过期。
这几类内容分别维护:
- 博客讲方法论:为什么 Skill 管不住代码风格,什么时候需要把约定做成护栏。
- 文档站讲规则:每条规则为什么需要、会提示什么、推荐写法是什么、有哪些配置和边界。
- 源码仓库承载实现:规则源码、测试、在线 demo、发布版本和维护入口都跟着
@sheng/eslint-plugin走。
@sheng/eslint-plugin 的使用指南里保留了接入方式和 parser 配置;规则列表按场景分组,规则页里有可编辑示例和诊断回显。以后如果某条规则需要展开背景,优先单独写一篇对应规则的文章,再从规则页和方法论文章互相链接。
当前规则 demo 和源码由文档站及插件仓库提供,文章里不再内嵌;早期源码包仍作为历史快照留在站点资源里。接入新项目时,应该优先使用当前维护的 npm 包和文档站。
什么时候不要写规则
不是每条 review 意见都适合变成 ESLint。
如果一个判断依赖业务语义、需求背景或设计稿取舍,规则很容易变成噪音。比如某个组件是否应该拆分、某段逻辑是否该抽 composable、某个命名是不是足够表达业务,这些问题可以写进 Skill、架构文档或 review checklist,不一定要让 AST 去猜。
规则如果需要维护大量项目私有例外,也要先停一下。例外比正例还多,通常说明约定本身还没稳定,或者规则边界太贪心;先收窄到最确定的场景,比一开始追求覆盖所有坏味道更可靠。
如果问题只发生过一次,也不急着写规则。可以先把 review 口径写进 Skill,观察 Agent 后面会不会重复犯。重复出现、能稳定描述、能写出最小错误示例,再沉淀成 Lint 护栏。
总结
项目约定反复偏离、会产生可预期的维护成本,且能从源码稳定判断时,才值得做成自动检查。Skill 和 AGENTS.md 交代背景与执行指导,ESLint 检查 AST、作用域和依赖,Stylelint 处理样式源码,更重的跨文件、资源和产物关系交给专项脚本;业务取舍与合理例外仍需 review。
规则也要服从项目归属和迁移阶段。先用 warn 观察误报,把名称、路径和例外交给配置,再将确定会坏且已整理的范围升为 error,并确认流水线实际如何阻断。具体实现与示例跟随插件和文档站维护,博客保留选择检查方式的方法,避免多处复制后逐渐过期。