1. 项目缘起:为什么我们需要一个叫“impeccable”的东西
第一次看到“impeccable”这个词,是在一个前端团队的内部讨论里。有人甩出一句话:“我们的代码 review 标准就一个词,impeccable。”当时我以为他在装,后来发现他是认真的。这个词的意思是“无可挑剔的、完美的”,放在 AI coding agents 和 frontend design 的语境下,它指向一个非常具体的痛点:AI 写出来的前端代码,能跑,但不够“干净”。
你可能已经用过各种 CLI 工具来驱动 AI 写代码,比如 codex cli、zcode cli、trae cli 这些。它们确实能把自然语言变成可运行的代码,但问题在于——生成的前端代码往往带着一股“AI 味”:class 命名随意、组件拆分混乱、样式和逻辑耦合、响应式断点拍脑袋定、可访问性基本靠运气。你让 AI 改一个按钮颜色,它可能顺手把你整个布局重构了。这种“能跑但不可维护”的代码,在真实项目里就是技术债的加速器。
“impeccable”这个项目,就是冲着这个问题来的。它不是一个全新的 AI 模型,也不是一个替代 codex cli 的工具,而是一套约束层和检查层——你可以把它理解成给 AI coding agents 戴上的“紧箍咒”。它通过 CLI 和 browser extension 两种形态,在 AI 生成前端代码的过程中和之后,施加一套严格的、可配置的“无可挑剔”标准。说白了,它让 AI 写出来的前端代码,从“能跑”变成“能进 code review 而不被骂”。
适合谁来参考?三类人:一是正在用 AI coding agents 做前端开发、但被代码质量困扰的工程师;二是团队里负责制定代码规范、想把这套规范“喂”给 AI 的人;三是对 CLI 工具链和 browser extension 开发感兴趣、想看看怎么把两者打通的技术爱好者。如果你只是偶尔让 AI 写个 demo,这个项目可能有点重;但如果你真的在用 AI 写要上线的产品,那这套东西值得你花时间研究。
2. 核心设计思路:把“无可挑剔”拆成可执行的规则
2.1 为什么是“约束”而不是“生成”
市面上大多数 AI coding 工具的思路是“增强生成”——让模型更聪明、更懂你的意图。但 impeccable 走的是另一条路:它假设模型已经足够聪明,问题出在缺乏约束。这个判断基于一个很朴素的观察:你用 codex cli 生成一个 React 组件,模型知道怎么写 useState、怎么写 useEffect,但它不知道你们团队规定“所有异步逻辑必须抽成 custom hook”“样式必须用 CSS Modules 而不是 inline style”“组件文件超过 150 行必须拆分”。这些规则不在模型的训练数据里,也不在你的 prompt 里,所以它不会遵守。
impeccable 的做法是,把这些规则从“口头约定”变成“机器可读的配置”。它定义了一套规则描述格式,你可以用 YAML 或 JSON 写清楚:哪些模式是禁止的、哪些结构是必须的、哪些命名规范是强制的。然后 CLI 在 AI 生成代码后立即扫描,browser extension 在你在浏览器里预览时实时标注。这样,AI 的“生成”和 impeccable 的“约束”形成闭环——生成、检查、反馈、修正。
2.2 CLI 与 browser extension 的分工逻辑
为什么同时做 CLI 和 browser extension?这不是为了炫技,而是因为前端开发的检查场景天然分两段。
CLI 负责的是代码层面的静态检查。你在终端里跑impeccable check ./src,它扫描你的文件,输出一份报告:哪些文件违反了规则、违反了什么规则、严重程度如何。这个环节适合集成到 CI 里,也适合在 AI 生成代码后立刻跑一遍。它的优势是快、可脚本化、能处理整个项目。
Browser extension 负责的是运行时和视觉层面的检查。有些问题静态扫描发现不了:比如某个按钮在 375px 宽度下文字溢出了、某个颜色对比度不满足 WCAG 标准、某个交互元素没有 focus 状态。这些必须在真实渲染环境里才能看到。extension 注入一个检查面板,你在浏览器里打开页面,它实时标注问题,并且能直接定位到对应的 DOM 节点和源文件行号。
两者共享同一套规则配置。你在 CLI 里定义的规则,extension 会自动读取。这样你不需要维护两份标准,改一处,两端同步生效。
2.3 规则引擎的抽象层次
impeccable 的规则引擎分了三个抽象层次,这个设计很关键,直接决定了它的灵活性和可维护性。
第一层是语法层规则,基于 AST 做检查。比如“禁止使用any类型”“禁止在 JSX 里写内联箭头函数”“import 顺序必须按字母排序”。这层规则用 TypeScript 的 compiler API 或者 Babel parser 实现,检查速度快,误报率低。
第二层是结构层规则,基于文件组织和依赖关系做检查。比如“components 目录下的文件不能直接 import pages 目录”“每个组件必须有一个同名的 .test.tsx 文件”“工具函数必须放在 utils 目录且不能有副作用”。这层需要构建依赖图,稍微重一点,但能发现架构层面的问题。
第三层是语义层规则,基于命名和注释做检查。比如“布尔变量必须以 is/has/can 开头”“事件处理函数必须以 handle 开头”“复杂逻辑必须有注释说明为什么而不是做什么”。这层最“软”,但恰恰是团队规范里最常被提及、最难自动化的部分。impeccable 用正则加启发式规则来实现,允许一定的误报,但可以通过配置文件调低敏感度。
注意:语义层规则不要设得太激进。我见过一个团队把“所有函数必须有 JSDoc”设成 error 级别,结果 AI 生成的代码里全是废话注释,反而降低了可读性。建议语义层规则默认用 warning,只在关键路径上用 error。
3. 核心细节解析:规则配置与检查机制
3.1 规则配置文件的写法
impeccable 的配置文件叫impeccable.config.yaml,放在项目根目录。一个典型的配置长这样:
version: 1 rules: syntax: no-any: severity: error message: "禁止使用 any 类型,请用 unknown 或具体类型" no-inline-arrow-in-jsx: severity: warning message: "JSX 内联箭头函数会导致不必要的重渲染" import-order: severity: error groups: - ["react", "react-dom"] - ["@/components", "@/hooks"] - ["@/utils", "@/lib"] - ["^[./]"] structure: component-file-length: severity: warning maxLines: 150 test-file-required: severity: error pattern: "src/components/**/*.tsx" testPattern: "src/components/**/*.test.tsx" semantic: boolean-prefix: severity: warning prefixes: ["is", "has", "can", "should"] handler-prefix: severity: warning prefixes: ["handle", "on"]这个配置的写法有几个设计考量。第一,severity 分 error、warning、info 三级,对应不同的退出码,方便 CI 集成。第二,每条规则都有 message,这个 message 会直接显示给开发者,所以措辞要具体、可操作,不要写“代码不规范”这种废话。第三,规则按 syntax、structure、semantic 分组,对应前面说的三层抽象,这样配置文件的组织结构和引擎实现是对应的,改起来不容易乱。
3.2 CLI 的检查流程与输出格式
CLI 的检查流程分四步:解析配置、构建 AST 和依赖图、逐规则执行、汇总输出。
第一步解析配置时,impeccable 会做 schema 校验,如果配置写错了(比如 severity 写成了err),会直接报错并指出行号。这个细节很重要,因为配置文件是团队共享的,一个人写错会导致所有人的检查结果异常。
第二步构建 AST 和依赖图是性能瓶颈。对于大型项目,全量解析可能要几秒钟。impeccable 做了增量缓存:把每个文件的 AST 和 hash 存到.impeccable/cache目录,下次检查时只重新解析改动的文件。实测下来,一个 500 文件的项目,首次检查约 4 秒,增量检查约 0.3 秒。
第三步逐规则执行时,syntax 层规则并行跑,structure 和 semantic 层串行跑(因为依赖图构建有顺序要求)。每条规则的执行结果包含:文件路径、行号、列号、规则 ID、severity、message。
第四步输出支持三种格式:--format pretty给人看,带颜色和代码片段;--format json给机器看,方便集成到其他工具;--format sarif给 GitHub Code Scanning 用。我一般本地开发用 pretty,CI 里用 sarif,这样 PR 里能直接看到标注。
3.3 Browser extension 的注入机制
Browser extension 的核心是 content script 注入。它做了几件静态检查做不到的事。
第一是实时 DOM 扫描。extension 在页面加载完成后,遍历 DOM 树,检查每个元素的属性、样式、可访问性。比如检查所有<img>是否有 alt 属性、所有按钮是否有可访问名称、所有表单输入是否有 label 关联。这些检查在静态代码里也能做一部分,但动态渲染的组件(比如根据数据生成的列表)只有运行时才能看到。
第二是响应式断点检查。extension 提供一个断点切换器,你可以在 320px、375px、768px、1024px、1440px 几个常用宽度之间快速切换,每切换一次就重新扫描布局问题。它会检测水平溢出、文字截断、元素重叠、点击区域过小(小于 44x44px)等问题。
第三是源文件映射。这是最实用的功能。extension 通过 source map 把 DOM 节点映射回源文件的行号,你在面板里点一个问题,它直接打开编辑器的对应位置。这个功能依赖构建工具生成 source map,所以在开发模式下才生效。
提示:extension 的源文件映射需要你的构建工具开启
devtool: 'source-map'或等价配置。Vite 默认开启,Webpack 需要手动配。如果映射不准,先检查 source map 是否完整。
3.4 规则冲突的处理策略
规则多了必然会冲突。比如你设了“组件文件不超过 150 行”,又设了“每个组件必须包含完整的 PropTypes 定义”,一个复杂组件的 PropTypes 可能就占 50 行,加上逻辑很容易超 150 行。这时候 AI 会陷入两难:拆组件违反 PropTypes 规则,不拆违反长度规则。
impeccable 的处理策略是优先级加豁免机制。每条规则可以设priority,数字越小优先级越高。当两条规则冲突时,高优先级的规则生效,低优先级的规则降级为 info 并在报告里标注“因规则冲突被降级”。另外,你可以在文件头部加注释/* impeccable-disable component-file-length */来临时豁免某条规则,但豁免必须带原因,否则 CI 会报错。
这个设计背后的逻辑是:规则是为人服务的,不是人为规则服务。当规则之间打架时,应该让人来做最终判断,而不是让工具卡死。但豁免必须留痕,这样 code review 时能看到“为什么这里破例了”。
4. 实操过程:从零搭建 impeccable 检查流水线
4.1 环境准备与 CLI 安装
impeccable 的 CLI 通过 npm 分发,安装命令很直接:
npm install -D impeccable-cli # 或者用 pnpm pnpm add -D impeccable-cli安装完成后,在 package.json 里加两个 script:
{ "scripts": { "check": "impeccable check ./src", "check:ci": "impeccable check ./src --format sarif --output impeccable.sarif" } }这里有个细节:impeccable 依赖 TypeScript 和 Babel parser,但它把它们作为 peer dependencies,不强制安装。如果你的项目已经用了 TypeScript,它会复用你项目里的版本;如果没有,它会提示你安装。这样做是为了避免版本冲突——我见过太多工具因为锁死了 parser 版本,导致和项目里的 TypeScript 版本打架。
初始化配置文件:
npx impeccable init这个命令会生成一个带注释的impeccable.config.yaml,里面预置了一套“推荐规则”。我建议不要直接用推荐规则,而是先跑一遍npx impeccable check ./src --dry-run,看看你的项目当前有多少违规,再根据实际情况调整规则。一上来就开所有规则,报告会多到你看不完。
4.2 规则调优的实操记录
我拿一个真实项目做了调优,记录如下。
项目背景:一个用 React + TypeScript + Vite 的中后台系统,约 200 个组件文件,团队 5 人,之前没有强制代码规范。
第一轮,用推荐规则跑,结果:error 127 个,warning 892 个,info 2100 个。error 主要集中在no-any(58 个)和import-order(41 个)。warning 主要是component-file-length(203 个)和boolean-prefix(300 多个)。
第二轮,调整策略。no-any保留 error,因为 any 确实会传染。import-order降为 warning,因为自动修复成本高,而且不影响运行。component-file-length的 maxLines 从 150 调到 250,因为中后台的表格页面天然就长,强行拆反而增加复杂度。boolean-prefix降为 info,因为改变量名涉及面太广,先记录不阻塞。
第三轮,加了两条自定义规则。一条是no-direct-dom-query,禁止在组件里直接用document.querySelector,必须用 ref。另一条是api-call-in-effect,检查 useEffect 里是否直接调了 API 而没有清理函数。这两条规则是我们团队踩过坑之后加的,推荐规则里没有。
调优后的结果:error 降到 12 个,warning 约 200 个,info 约 500 个。error 都是必须改的,warning 排期改,info 有空再看。这个梯度是合理的,不会让团队产生“规范太多干脆摆烂”的心态。
4.3 与 AI coding agents 的集成方式
impeccable 和 AI coding agents 的集成有两种模式:事后检查和事前约束。
事后检查最简单:你用 codex cli 或 zcode cli 生成代码,保存文件,然后跑npm run check。如果有 error,把错误信息复制回给 AI,让它修正。这个循环可以手动做,也可以写个脚本自动做。
事前约束稍微复杂一点,但效果更好。思路是把 impeccable 的规则摘要注入到 AI 的 system prompt 里。比如你可以在调用 codex cli 时加一段:
codex cli --system-prompt "你生成的前端代码必须遵守以下规则: 1. 禁止使用 any 类型 2. 组件文件不超过 250 行 3. 布尔变量以 is/has/can 开头 4. import 顺序:react 相关、内部组件、工具函数、相对路径 完整规则见 impeccable.config.yaml"这样 AI 在生成时就会尽量遵守,减少事后修正的轮次。实测下来,事前约束能让 error 数量减少约 60%,但不可能降到零,因为 AI 对规则的理解偶尔会偏差。所以事后检查仍然必要。
注意:不要把整个配置文件塞进 system prompt,太长了会挤占上下文窗口。只摘录 error 级别的规则,warning 和 info 让 AI 自己判断。
4.4 CI 流水线的配置
CI 集成用 GitHub Actions 举例:
name: impeccable-check on: [pull_request] jobs: check: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: 20 - run: npm ci - run: npm run check:ci - uses: github/codeql-action/upload-sarif@v3 with: sarif_file: impeccable.sarif这个配置的关键是最后一步,把 sarif 文件上传到 GitHub,这样 PR 的 Files changed 页面会直接标注出违规行。开发者不用点开 CI 日志,在 diff 里就能看到问题。
有个坑要注意:impeccable 的退出码设计是——有 error 返回 1,只有 warning 返回 0,只有 info 返回 0。所以 CI 里npm run check:ci会因为 error 而失败,这是预期的。如果你想让 warning 也阻塞合并,加--fail-on-warning参数。但我建议不要这么做,warning 太多会让人麻木,反而忽略了 error。
5. 常见问题与排查技巧实录
5.1 检查结果与预期不符怎么办
最常见的问题是“我明明改了,为什么还报错”。排查顺序如下。
先看缓存。impeccable 的增量缓存偶尔会抽风,尤其是你用了 git 切换分支之后。删掉.impeccable/cache目录,重新跑一次。如果问题消失,那就是缓存问题,可以在 CI 里加--no-cache参数避免。
再看配置合并。impeccable 支持多层配置:项目根目录的impeccable.config.yaml、子目录的impeccable.config.yaml、以及package.json里的impeccable字段。子目录配置会覆盖根配置的同名规则。如果你在子目录里改了规则但没生效,检查一下是不是被根配置覆盖了。
最后看规则作用域。每条规则可以设include和excludeglob。比如test-file-required规则默认 exclude 了*.test.tsx本身,否则会要求测试文件也有测试文件,无限递归。如果你发现某条规则没生效,检查它的 include/exclude 是否覆盖了你的文件。
5.2 Browser extension 不显示检查面板
这个问题通常有三个原因。
第一,extension 没有权限注入当前页面。检查 extension 的 manifest 里content_scripts的 matches 是否包含你的开发服务器地址。默认配置是http://localhost:*/*和http://127.0.0.1:*/*,如果你用的是自定义域名或者 HTTPS,需要手动加。
第二,页面加载时机问题。extension 默认在document_idle时注入,但如果你的页面是 SPA,路由切换后新渲染的组件不会被自动扫描。这时候需要手动点 extension 面板里的“重新扫描”按钮,或者在代码里调用window.__impeccable_rescan()。
第三,source map 缺失导致面板显示但无法定位。前面提过,需要构建工具开启 source map。如果你用的是 Vite,检查vite.config.ts里build.sourcemap是否为 true。开发模式下 Vite 默认开启,但生产构建默认关闭。
5.3 规则误报的降噪技巧
误报是静态检查的宿命,但可以降噪。
对于 syntax 层规则,误报通常来自第三方库的类型定义。比如no-any会报 node_modules 里的 any,但那些你改不了。解决办法是在配置里加exclude: ["node_modules/**"],impeccable 默认已经排除了,但如果你手动改了 include,记得把 node_modules 加回去。
对于 structure 层规则,误报通常来自动态导入和懒加载。比如test-file-required会报懒加载的组件没有测试文件,但那些组件可能是临时页面。解决办法是用注释豁免:在文件头部加/* impeccable-disable test-file-required: 临时页面,后续补测试 */。
对于 semantic 层规则,误报最多。比如boolean-prefix会把loading这种状态变量报出来,但loading其实是布尔值。解决办法是维护一个白名单,把常见的布尔状态名加进去:loading、visible、disabled、checked、selected。这个白名单可以放在配置文件的semantic.boolean-prefix.allow字段里。
5.4 性能优化与大型项目适配
项目大了之后,检查速度会成为问题。几个优化手段。
第一,用--changed-only参数只检查 git 改动的文件。这个参数在本地开发时特别有用,CI 里还是全量检查。
第二,把 syntax 层规则和 structure/semantic 层规则分开跑。syntax 层快,可以每次保存都跑;structure/semantic 层慢,可以只在 pre-commit 或 CI 里跑。impeccable 支持--layer syntax参数来只跑某一层。
第三,对于 monorepo,每个 package 单独跑检查,而不是在根目录跑全量。impeccable 会自动识别 workspace 结构,但你需要给每个 package 单独配impeccable.config.yaml,否则根配置的规则会应用到所有 package,可能不合适。
第四,如果项目超过 2000 个文件,考虑把 structure 层规则关掉,只保留 syntax 和 semantic。structure 层的依赖图构建在超大项目上可能耗时超过 10 秒,性价比不高。
5.5 常见问题速查表
| 问题现象 | 可能原因 | 排查步骤 | 解决方案 |
|---|---|---|---|
| 改了代码还报旧错误 | 增量缓存未更新 | 删除.impeccable/cache重跑 | CI 加--no-cache |
| 子目录规则不生效 | 被根配置覆盖 | 检查配置合并顺序 | 子目录配置加extends: false |
| extension 面板不显示 | 权限或注入时机 | 检查 manifest matches | 手动加开发服务器地址 |
| 无法定位源文件 | source map 缺失 | 检查构建配置 | 开启build.sourcemap |
| 误报太多 | 规则太激进 | 看报告里的规则 ID | 降级 severity 或加白名单 |
| 检查太慢 | 全量解析 | 看耗时分布 | 用--changed-only或分层跑 |
| CI 不阻塞合并 | 退出码为 0 | 检查是否有 error | 加--fail-on-warning或修 error |
| 规则冲突 | 优先级未设 | 看报告里的降级标注 | 设 priority 或加豁免注释 |
6. 规则设计的经验与踩坑记录
6.1 规则不是越多越好
我一开始很兴奋,给项目加了 40 多条规则,结果 AI 生成的代码频繁触发 error,修正循环从 2 轮变成 5 轮,开发效率反而下降了。后来砍到 15 条,只保留真正影响可维护性的规则,效率才回来。
判断一条规则该不该留,问三个问题:这条规则能防止什么类型的 bug?违反它的成本有多高?自动修复的难度有多大?如果一条规则既不能防 bug,违反成本又低,修复还麻烦,那就别加。比如“函数参数不超过 3 个”这种规则,听起来很整洁,但实际项目里经常需要传 4 个参数,强行拆成对象反而增加复杂度。
6.2 规则要跟着项目阶段走
项目初期,规则应该宽松,重点是快速迭代。这个阶段只保留no-any和no-unused-vars这种基础规则就够了。项目中期,规则逐渐收紧,加上结构层规则,防止架构腐化。项目后期或者维护期,规则可以最严,因为这时候改动少,有足够时间修正。
我见过一个团队在项目第一天就开了所有规则,结果前两周全在改规范问题,功能没写几个。这是本末倒置。规则是服务于交付的,不是反过来。
6.3 让 AI 参与规则制定
这是个反直觉的做法,但有效。你可以把 impeccable 的报告喂给 AI,让它分析哪些规则误报最多、哪些规则修复成本最高,然后给出调整建议。AI 对模式识别很擅长,它能发现你忽略的规律。比如它可能告诉你:“import-order规则在 80% 的情况下都是自动修复的,但剩下的 20% 需要手动调整,建议把自动修复打开,手动部分降为 warning。”这种建议人很难快速得出,但 AI 可以。
6.4 规则的文档化
每条规则都应该有文档,说明:规则的目的、违反后的后果、如何修复、有没有例外情况。这个文档不用很长,三五句话就行,但要写在配置文件旁边,方便查阅。impeccable 支持在配置里加description字段,这个字段会显示在报告里。我建议把修复方法也写进去,这样开发者看到报告就知道怎么改,不用去翻文档。
提示:规则的
description字段支持 Markdown,你可以放代码示例。比如description: "禁止使用 any。修复方式:\n```ts\n// 错误\nconst data: any = fetch()\n// 正确\nconst data: unknown = fetch()\n```"。这样报告里直接显示修复示例,效率很高。
7. 后续可以扩展的方向
这套东西跑通之后,我试过几个扩展方向,有的成了,有的还在折腾。
一个是规则的市场化。把团队沉淀的规则打包成 preset,不同项目可以复用。比如“React 中后台 preset”“Vue 移动端 preset”“Node CLI preset”。这样新项目初始化时直接选 preset,不用从零配。impeccable 的配置文件格式支持extends字段,可以继承 preset 再覆盖,实现起来不难。
另一个是和 code review 工具的深度集成。现在 impeccable 的报告是独立的,如果能把 error 直接变成 PR 的 review comment,开发者就不用切换工具了。GitHub API 支持创建 review comment,理论上可行,但需要处理行号映射和去重,还在试验阶段。
还有一个是规则的自学习。让 impeccable 观察团队实际合并的代码,自动发现“大家普遍遵守但没写成规则”的模式,然后建议加规则。这个方向很有意思,但需要大量数据,小团队可能跑不起来。
最后分享一个小技巧:如果你在用 codex cli 或 trae cli 这类工具,可以把 impeccable 的检查命令配成它们的 post-hook。这样每次 AI 生成完代码,自动跑检查,有问题直接反馈给 AI 修正,形成一个自动闭环。我试过这个配置,能把人工介入的轮次从平均 3 轮降到 1 轮,省下来的时间很可观。