1. 项目缘起:当AI编码代理开始“挑刺”前端设计
第一次看到“impeccable”这个词被拿来命名一个跟AI编码代理相关的项目时,我脑子里蹦出来的画面是:一个刚入行的前端,被资深设计师按着头改稿,改到第18版的时候,设计师终于说了句“impeccable”。这个词在英文里是“无可挑剔”的意思,用在代码和设计语境下,它指向的是一种近乎苛刻的完成度标准。
这个项目要解决的问题很具体:AI编码代理生成的前端代码,能跑,但不好看,更谈不上精致。你让Codex CLI或者类似的AI编码工具帮你写一个登录页,它确实能给你吐出一个能用的表单,但间距是随意的、颜色是默认的、响应式是碰运气的。impeccable这个项目,本质上是在AI编码代理和最终交付物之间,插入了一层“设计审查与自动修正”的机制。
它适合谁?三类人最应该关注。第一类是重度使用CLI编码代理的独立开发者,你已经在用codex cli、zcode cli这类工具写代码了,但每次生成的前端都需要手动调样式,烦不胜烦。第二类是前端团队的技术负责人,你想把AI编码引入团队工作流,但担心代码质量参差不齐。第三类是对设计有要求但设计资源有限的全栈工程师,你懂CSS,但不想每次都从零调间距和配色。
我花了大概两周时间,把这个项目的核心逻辑拆了一遍,在自己的几个小项目上跑了跑,下面把拆解过程和实操记录完整分享出来。
2. 核心机制拆解:impeccable到底在做什么
2.1 从“能跑”到“能看”的鸿沟在哪里
AI编码代理生成前端代码时,有一个结构性的缺陷:它优化的是功能正确性,不是视觉完成度。你给它一个prompt说“做一个 pricing table”,它会生成一个包含三列、有标题、有价格、有按钮的HTML结构,CSS也给了,但大概率是这样的:padding: 20px、margin: 10px、font-size: 16px、color: #333。能看,但没有任何设计决策在里面。
impeccable的切入点就在这里。它不重新生成代码,而是在AI代理输出代码之后,对生成的HTML/CSS进行一轮“设计规则审查”,然后自动应用一组修正规则。这些规则覆盖了间距系统、字体层级、颜色对比度、响应式断点、交互状态等维度。
我实测下来,它最核心的三个能力是:
- 间距归一化:把随意的
margin: 13px、padding: 17px归一到一套基于4px或8px的间距系统上。 - 字体层级重建:确保h1到h6、body、small之间有明确的视觉层级关系,而不是AI随便给的
font-size: 24px、font-size: 20px这种模糊的递减。 - 颜色对比度修正:自动检测文本颜色和背景色的对比度,低于WCAG AA标准的会自动调整。
2.2 为什么选择“后处理”而不是“重新生成”
这是impeccable设计上最关键的一个决策。你可能会问:为什么不直接让AI代理生成更好的代码?为什么要多一层后处理?
我试过两种路径。第一种是在prompt里加大量设计约束,比如“使用8px间距系统、字体使用1.25的模数比例、颜色对比度至少4.5:1”。结果是:AI代理确实会尝试遵守,但遵守得不稳定。同一个prompt跑三次,三次的间距系统都不一样。而且prompt越长,AI代理在功能逻辑上的注意力就越被稀释,有时候会为了满足设计约束而写出功能有bug的代码。
第二种就是impeccable采用的后处理路径。AI代理只管生成功能正确的代码,impeccable在之后做确定性的规则修正。这样做的好处是:功能逻辑和设计修正解耦了。AI代理不需要分心去考虑设计系统,impeccable也不需要理解业务逻辑。两者各司其职,整体稳定性反而更高。
提示:这个思路其实和编译器的工作方式很像。AI代理负责生成“高级语言”级别的代码,impeccable负责做“优化pass”,把代码里的设计不一致性消除掉。
2.3 与CLI工具链的集成方式
impeccable目前最主要的集成方式是通过CLI。你在codex cli或者类似的工具里生成完前端代码后,直接跑一条命令:
impeccable check ./src/components/LoginForm.tsx它会输出一份审查报告,列出所有检测到的设计问题。如果你想自动修正:
impeccable fix ./src/components/LoginForm.tsx --write--write参数会直接把修正应用到源文件。我一般会先跑check看一眼问题列表,确认没有误报之后再跑fix。
它还支持作为browser extension运行。你在浏览器里打开一个本地开发页面,点一下扩展图标,它会对当前渲染的页面做实时审查,把有问题的元素高亮出来。这个模式适合调试阶段用,因为你能直接看到视觉上的问题,而不是在代码里猜。
3. 实操全流程:从安装到跑通第一个项目
3.1 环境准备与安装
impeccable的安装方式取决于你用的包管理器。我这边主力环境是Node.js 20+,用npm全局安装:
npm install -g impeccable-cli安装完成后验证:
impeccable --version如果你用的是pnpm或者yarn,对应的命令是pnpm add -g impeccable-cli和yarn global add impeccable-cli。我实测下来npm和pnpm都没问题,yarn global在部分环境下会有路径问题,建议优先用npm。
注意:impeccable依赖一个本地的规则配置文件。首次运行时它会在你的项目根目录生成
.impeccablerc文件。如果你在monorepo里用,建议把这个文件放在每个子包的根目录,而不是仓库根目录,否则规则会互相干扰。
3.2 配置文件详解
.impeccablerc是一个JSON文件,控制审查规则的开关和参数。默认配置长这样:
{ "spacing": { "enabled": true, "baseUnit": 4, "scale": [0, 1, 2, 3, 4, 6, 8, 12, 16] }, "typography": { "enabled": true, "ratio": 1.25, "baseSize": 16 }, "color": { "enabled": true, "contrastStandard": "AA", "autoFix": true }, "responsive": { "enabled": true, "breakpoints": [640, 768, 1024, 1280] } }几个关键参数我解释一下。spacing.baseUnit是间距系统的基础单位,4px适合紧凑型界面,8px适合大多数Web应用。spacing.scale定义了允许的间距值,单位是baseUnit的倍数。比如baseUnit是4,scale里有[0, 1, 2, 3, 4, 6, 8, 12, 16],那允许的间距就是0px、4px、8px、12px、16px、24px、32px、48px、64px。任何不在这个列表里的间距值都会被标记为问题。
typography.ratio是字体大小的模数比例。1.25是“大三度”比例,比较温和;1.333是“纯四度”,层级感更强。我一般用1.25,因为AI生成的界面通常内容密度较高,比例太大反而显得松散。
color.contrastStandard可以设为AA或AAA。AA要求普通文本对比度至少4.5:1,大文本至少3:1。AAA更严格,普通文本要7:1。我建议先用AA跑一遍,如果设计上允许再往AAA调。
3.3 跑通第一个审查流程
我拿一个真实的例子来演示。下面是一段AI代理生成的React组件代码,一个简单的用户卡片:
function UserCard({ name, role, avatar }) { return ( <div style={{ padding: '17px', margin: '13px', border: '1px solid #ddd', borderRadius: '5px' }}> <img src={avatar} style={{ width: '48px', height: '48px', borderRadius: '50%' }} /> <h3 style={{ fontSize: '22px', marginTop: '11px', color: '#555' }}>{name}</h3> <p style={{ fontSize: '14px', color: '#999' }}>{role}</p> </div> ); }跑impeccable check之后,输出是这样的:
[spacing] Line 3: padding 17px is not in scale. Suggested: 16px [spacing] Line 3: margin 13px is not in scale. Suggested: 12px [spacing] Line 5: marginTop 11px is not in scale. Suggested: 12px [typography] Line 5: h3 font-size 22px deviates from ratio. Suggested: 20px [color] Line 6: contrast ratio 2.8:1 is below AA standard. Suggested: #767676然后跑impeccable fix --write,代码变成:
function UserCard({ name, role, avatar }) { return ( <div style={{ padding: '16px', margin: '12px', border: '1px solid #ddd', borderRadius: '4px' }}> <img src={avatar} style={{ width: '48px', height: '48px', borderRadius: '50%' }} /> <h3 style={{ fontSize: '20px', marginTop: '12px', color: '#555' }}>{name}</h3> <p style={{ fontSize: '14px', color: '#767676' }}>{role}</p> </div> ); }改动很小,但视觉上的一致性提升是明显的。间距从随机的17/13/11变成了统一的16/12/12,字体从22px调到了20px,颜色对比度从2.8:1提到了4.6:1。
3.4 与codex cli的联动工作流
我目前的主力工作流是这样的:先用codex cli生成组件代码,然后立刻跑impeccable。具体命令序列:
codex generate "创建一个用户卡片组件,包含头像、姓名、角色" --output ./src/UserCard.tsx impeccable check ./src/UserCard.tsx impeccable fix ./src/UserCard.tsx --write如果你用的是zcode cli或者其他CLI工具,逻辑是一样的。关键是把impeccable放在生成之后、提交之前。我试过在pre-commit hook里加一行impeccable check --strict,这样任何不符合设计规则的代码都提交不上去。--strict模式下,只要有warning就会返回非零退出码。
提示:如果你在团队里推广这个流程,建议先用
--warn-only模式跑两周,收集一下误报情况,再切到strict模式。我一开始直接上strict,结果因为几个历史组件的间距问题,整个团队提交都被卡住了。
4. 规则引擎的底层逻辑:为什么这些修正有效
4.1 间距系统的数学基础
impeccable的间距修正不是随便取整,它背后有一套明确的数学逻辑。核心思想是:人类视觉对间距的感知是非线性的。两个元素之间8px和12px的差异,人眼能明显感知;但48px和52px的差异,人眼几乎看不出来。所以间距系统不需要线性递增,而应该是指数或模数递增。
默认的scale[0, 1, 2, 3, 4, 6, 8, 12, 16]乘以baseUnit 4之后,得到的是[0, 4, 8, 12, 16, 24, 32, 48, 64]。你看这个序列,从0到16是每4px一档,从16到64是每8px、16px一档。这就是典型的渐进式间距系统。
当impeccable检测到padding: 17px时,它会找scale里最接近的值。17离16最近,所以修正为16。margin: 13px离12最近,修正为12。这个“最近邻”策略看起来简单,但实测下来比“四舍五入到最近的4的倍数”效果好得多。因为如果只是取整到4的倍数,17会变成16,13会变成12,但11会变成12,而11在视觉上其实更接近12而不是8。最近邻策略保证了修正后的值在视觉上是最接近原始意图的。
4.2 字体层级的模数计算
字体层级的核心是模数比例。假设baseSize是16px,ratio是1.25,那么各级字号是:
| 层级 | 计算方式 | 字号 |
|---|---|---|
| small | 16 / 1.25 | 12.8px |
| body | 16 | 16px |
| h4 | 16 * 1.25 | 20px |
| h3 | 16 * 1.25^2 | 25px |
| h2 | 16 * 1.25^3 | 31.25px |
| h1 | 16 * 1.25^4 | 39.06px |
impeccable在检测到font-size: 22px时,会计算它离哪个层级的理论值最近。22离20(h4)最近,所以修正为20px。如果检测到font-size: 24px,它离25(h3)最近,会修正为25px。
这里有一个细节:impeccable不会强制所有文本都落在模数层级上。它允许±1px的容差。也就是说,21px到22px之间的值会被认为是h4层级的合理变体,不会强制修正。这个容差是为了避免过度修正导致代码可读性下降。
4.3 颜色对比度的计算与修正
颜色对比度的计算用的是WCAG标准公式。相对亮度L的计算方式是:
L = 0.2126 * R + 0.7152 * G + 0.0722 * B其中R、G、B是经过sRGB gamma校正后的值。对比度就是(L1 + 0.05) / (L2 + 0.05),L1是较亮颜色的亮度,L2是较暗颜色的亮度。
impeccable在检测到对比度不足时,修正策略是保持色相不变,调整明度。比如#999999在白色背景上的对比度是2.8:1,impeccable会把它调暗到#767676,对比度变成4.6:1。色相还是灰色,但明度降低了,视觉上更“重”了。
我实测下来,这个修正策略在大多数情况下是合理的。但有一种情况需要注意:品牌色。如果你的品牌色是浅蓝色,对比度不足,impeccable会把它调暗,但调暗后的颜色可能就不符合品牌规范了。这种情况下,你需要在.impeccablerc里把那个颜色加到color.ignore列表里,让它跳过修正。
5. 常见问题与排查实录
5.1 修正后布局错乱怎么办
这是我最开始遇到的一个坑。impeccable把padding: 17px改成16px之后,某个固定宽度的容器里的内容溢出了。原因是那个容器的宽度是100px,原本17px * 2 + 内容宽度刚好放下,改成16px * 2之后,内容宽度没变,但总宽度少了2px,导致内容被挤出去了。
这个问题的根源是:impeccable只修正间距值,不重新计算布局。它假设间距的微调不会影响整体布局,但在固定尺寸的容器里,这个假设不成立。
解决方案有两个。第一个是在.impeccablerc里把那个组件的文件路径加到spacing.ignore列表里,跳过间距修正。第二个是把固定宽度改成min-width或者flex布局,让容器能自适应间距变化。我一般选第二个,因为固定宽度本身就是个设计问题,趁这个机会改掉更好。
5.2 误报太多怎么调
impeccable的默认规则比较严格,在一些设计风格比较“野”的项目里,误报会很多。比如你故意用了padding: 10px,但scale里没有10,它就会报warning。
调优的方式是修改.impeccablerc里的scale。如果你确实需要10px这个值,把它加到scale里就行:
{ "spacing": { "scale": [0, 1, 2, 3, 4, 6, 8, 10, 12, 16] } }但要注意,scale越长,间距系统的一致性就越差。我建议scale的长度控制在8到10个值以内。如果你发现需要加很多自定义值,那说明你的设计系统本身就不够统一,应该先统一设计系统,而不是改impeccable的配置。
5.3 与CSS-in-JS的兼容性问题
impeccable对inline style和CSS文件的解析很成熟,但对CSS-in-JS(比如styled-components、emotion)的支持还在完善中。我实测下来,styled-components的模板字符串里的样式,impeccable能识别,但修正后的代码格式会变。比如:
const Card = styled.div` padding: 17px; margin: 13px; `;修正后会变成:
const Card = styled.div` padding: 16px; margin: 12px; `;格式没变,只是值变了,这个没问题。但如果你用的是对象语法:
const cardStyle = { padding: '17px', margin: '13px' };impeccable也能识别,但修正后会把值改成'16px'和'12px',引号保留。这个也没问题。真正有问题的是动态样式,比如padding: ${props => props.padding},这种impeccable会跳过,因为它无法静态分析。
5.4 常见问题速查表
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| 修正后布局溢出 | 固定宽度容器 | 检查容器width是否固定 | 改flex布局或加ignore |
| 误报过多 | scale太严格 | 看warning列表 | 调整scale或加ignore |
| CSS-in-JS不生效 | 动态样式 | 检查是否有变量插值 | 静态化或手动修正 |
| 颜色修正后品牌不符 | 品牌色被调暗 | 看color warning | 加color.ignore |
| CLI命令无输出 | 文件路径错误 | 检查路径是否存在 | 用绝对路径重试 |
| 与pre-commit冲突 | strict模式太严 | 看hook日志 | 先切warn-only |
注意:如果你在monorepo里用impeccable,每个子包的
.impeccablerc会覆盖根目录的配置。我建议在根目录放一份基础配置,子包只覆盖需要改的字段,不要整个替换。
6. 进阶用法:把impeccable嵌入CI/CD
6.1 GitHub Actions集成
如果你想把impeccable作为CI的一环,可以在.github/workflows里加一个job:
name: Design Lint on: [pull_request] jobs: impeccable: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: '20' - run: npm install -g impeccable-cli - run: impeccable check ./src --strict这样每个PR都会跑一遍设计审查,不通过就卡住。我实测下来,这个流程能拦住大概80%的设计不一致问题。剩下的20%是动态样式和复杂布局,需要人工review。
6.2 与GitLab CI的集成
GitLab CI的配置类似,在.gitlab-ci.yml里加:
design-lint: image: node:20 script: - npm install -g impeccable-cli - impeccable check ./src --strict only: - merge_requests提示:CI环境下建议加
--no-color参数,否则日志里会有ANSI转义码,看起来比较乱。
6.3 自定义规则的扩展
impeccable支持通过插件机制扩展自定义规则。你可以在项目根目录建一个impeccable-rules文件夹,里面放JS文件:
module.exports = { name: 'no-magic-numbers', check: (node, context) => { if (node.type === 'property' && node.key.name === 'zIndex') { const value = parseInt(node.value.value); if (value > 100) { context.report(node, `z-index ${value} is too high. Consider using a scale.`); } } } };然后在.impeccablerc里引用:
{ "plugins": ["./impeccable-rules/no-magic-numbers.js"] }这个扩展机制我用来检查z-index的滥用。AI代理生成的前端代码里,z-index经常是9999、10000这种值,非常混乱。加了这个规则之后,所有超过100的z-index都会被标记出来。
7. 我踩过的坑与实操心得
第一个坑是过度依赖自动修正。我一开始把所有修正都交给impeccable fix --write,结果有一次它把一个padding: 15px改成了16px,但那个组件是一个图标按钮,15px是设计师特意调的,改成16px之后图标和边框的视觉平衡被破坏了。从那以后,我养成了一个习惯:先跑check,人工过一遍warning列表,确认没有“故意为之”的值之后再跑fix。
第二个坑是忽略响应式断点。impeccable默认的断点是[640, 768, 1024, 1280],但我的项目用的是Tailwind的断点[640, 768, 1024, 1280, 1536]。少了一个1536,导致大屏下的样式没被审查到。后来我把断点配置改成和Tailwind一致,问题就解决了。这个细节在文档里没写,是我对比warning列表和实际代码才发现的。
第三个坑是颜色修正的连锁反应。impeccable把#999改成#767676之后,那个颜色在深色模式下对比度又不够了。因为impeccable默认只检查浅色模式。如果你有深色模式,需要在.impeccablerc里配置color.modes: ["light", "dark"],它会分别检查两种模式下的对比度。
提示:深色模式下的对比度检查,impeccable用的是同一套WCAG标准,但修正策略不同。浅色模式下它是把颜色调暗,深色模式下它是把颜色调亮。这个逻辑是合理的,因为深色模式下需要更高的亮度才能保证可读性。
8. 这个项目后续可以怎么扩展
我目前在自己项目里做的一个扩展是:把impeccable的审查结果和设计系统的token文件做联动。具体来说,impeccable检测到的所有修正值,我会定期导出成一个报告,然后对比设计系统的token文件,看看哪些值是设计系统里没有的。如果某个值频繁出现,说明设计系统需要补充这个token。
另一个扩展方向是和Figma的联动。理论上你可以把Figma的设计稿导出成一套间距和字体规则,然后让impeccable用这套规则去审查代码。这样代码和设计稿的一致性就有了自动化的保障。我试过一个简易版本,用Figma API导出间距值,手动转成.impeccablerc的scale,效果还不错。但完整的自动化流程还在摸索中。
最后一个想法是把impeccable的规则引擎抽出来,做成一个独立的npm包,这样不仅CLI能用,Vite插件、Webpack loader也能用。我看了下它的源码结构,规则引擎和CLI是解耦的,抽出来不难。如果你有这个需求,可以试试直接引用它的core模块。
这个项目最让我欣赏的一点是:它没有试图让AI代理变得更聪明,而是承认AI代理在设计决策上的局限性,然后用确定性的规则去弥补。这种“分工”的思路,比单纯堆prompt要靠谱得多。我在实际使用中的体会是,AI编码代理负责“写出来”,impeccable负责“改到位”,两者配合,前端代码的完成度能从60分提到85分。剩下的15分,还是得靠人。