AI 编程设计 — 为什么每个 AI 都做出同一个 UI。
任何一个让 Claude Code、Cursor、Windsurf、GitHub Copilot、Cline 或 Continue 生成过 landing page 的人,都见过那种结果:Inter 被拉到 90px 当显示字、紫到蓝的渐变铺底、横向并排三张一模一样的卡,Hero 下面那行 「Build something amazing」。问题不在工具,问题在所有这些工具都是用同一批模板训练出来的。ux-skill 用 145 条确定性正则规则,在 CI 上拦下这些默认产物。
每个 AI 都会复刻的八个视觉默认值
我们把所有指纹都整理在了 AI 设计指纹分类 里。下面这八个,几乎在每个 Hero 里都出现:
- 把 Inter 当显示字 — Inter 是为屏幕小字号易读性而设计的,被拉到 90px 当大标题用,正好和它的设计意图反着来。
- 紫到蓝的渐变 —
#7C3AED → #3B82F6这一族,用作第一屏的背景底色。 - 横向三张同款卡片 — 图标、单词标题、一句话副标题,重复三遍。
- 全员 fade-in-up — 每个 section 的每个直接子元素都套同一个动画,角色完全不区分。
- 所有内容居中 — Hero、卡片、表单,全在视觉中轴上。
- 泛泛的文案 — 「Build something amazing」「Beautiful experiences」「Revolutionizing X」,一个产品相关的名词都没有。
- 占位图 URL — 用通用占位图代替真实的产品截图。
- 扁平设计上压重阴影 — 「要醒目」和「扁平最潮」这两种本能撞在一起,得到一种说不清的立体感。
单独看每个默认值都不是错。问题是这八个同时出现在一张页面上时,任何人都能一眼认出「这是 AI 写的」。一个,你可以耸耸肩放过去;在同一个 above-the-fold 里凑齐四个,你甚至能猜出是哪个工具生成的。
为什么 prompt 不够用
每个团队的第一反应都是给 prompt 加约束:「别用默认渐变」「换一款真正的显示字」「别再放三张同款卡」。这一条消息内有效;到下一条,引导被稀释,上下文窗口变大,指纹又回来了。靠 prompt 引导是概率性的,而且随对话长度衰减。
我们真正需要的是确定性的兜底 — 一道在模型 写完之后 跑、回路里不再夹一个 LLM 的检查。这正是 ux-skill 填上的那块缺口:推荐器在前面收窄模型的选项,linter 在后面把漏过来的默认值打回去。
不要用随机性对抗随机性
「那就让第二个 LLM 来给第一个 LLM 打分」这个想法每隔一阵就冒出来。但 LLM 给的分数本身也是随机的:它取决于评分用的 prompt、few-shot 示例、当天用到的模型版本。CI 需要的是每次都给同一个答案的那一层。这正是正则的唯一长处,而它足够。
145 条正则、不调 LLM、跑在 CI 上
linter 扫描所有 *.tsx、*.jsx、*.vue、*.svelte、*.astro、*.css、*.scss、*.html 文件,用 i 和 m 两个 flag 套上 145 条正则,输出 JSON,带规则 ID、严重度、文件、行号、列号、片段、建议修复。
# 本地 lint,默认拦截 high+critical $ uxskill lint [OK] Scanned 142 files in 412ms · 0 findings at threshold high # 子目录 + JSON $ uxskill lint apps/web/src --json | jq '.summary' { "critical": 0, "high": 4, "medium": 11, "low": 3, "total": 18 }
0 次 LLM 调用。一个 200 文件的 Next.js 仓库平均扫描时间冷启动 380ms,热启动 90ms。完整的规则说明在 正则 linter 指南 里。
真实 Hero 的前后对比
下面是一段从 Next.js 起始模板里截出来的简化 Hero,会命中三条规则;下方是改写后能以 exit 0 通过的版本。同样的转化意图,完全不同的指纹。
// 改写前 — 命中 3 条 high <section className="bg-gradient-to-br from-purple-500 via-violet-500 to-blue-500 py-32"> <h1 className="font-['Inter'] text-7xl leading-none"> Build something amazing. </h1> <div className="grid grid-cols-3 gap-6"> <Card icon="Zap" title="Fast" /> <Card icon="Shield" title="Safe" /> <Card icon="Heart" title="Loved" /> </div> </section> // 改写后 — 0 命中 <section className="bg-stone-50 py-28"> <h1 className="font-['Fraunces'] text-6xl leading-[1.04] tracking-tight"> 你们 TMS 一直没交付的 那一层货运路由。 </h1> <div className="grid grid-cols-12 gap-6 mt-16"> <Card className="col-span-7" title="货运合并" /> <Card className="col-span-5" title="承运人排名" /> </div> </section>
改写前命中三条规则:Inter 用在 text-7xl、AI 标志性渐变、三张同款卡的三列网格。改写后用 Fraunces (显示字) 配 Inter (正文),底色变成平的浅米色,网格换成 7-5 非对称布局。意图没变,指纹消失了。
推荐器抬高地板。Linter 压住天花板。两者缺一不可。
给 Cursor 配一套真正能用的设计系统
Cursor 是目前 AI 编程栈里最受欢迎的 composer 体验,但它本身对设计系统没有任何内置认知。Cascade 直接继承模型的默认值。ux-skill 在 Cursor 上的接入分两层:一层是 .cursorrules 文件,负责编辑器内的指令;另一层是 MCP 服务器,负责结构化工具调用。
# Cursor 接入步骤 $ pip install uxskill $ uxskill init --target cursor $ uxskill mcp-config --target cursor # 生成的文件: # .cursorrules ← 51 条规则片段,编辑器内生效 # ~/.cursor/mcp.json ← 14 个 MCP 工具注册 # tokens.css ← OKLCH 调色板、字号刻度 # MASTER.md ← 设计系统单一事实来源
接好之后,Cursor Composer 可以直接调用 14 个 ux-skill 工具:discover_brief、recommend_design、lint_codebase、get_brand_spec 等等。设计决策不再只活在聊天里,而是落到仓库里的真实文件 — 之后每一次 Cursor 会话也都跟着这些文件走。
三条安装路径,一个引擎
同一个 Python 包覆盖兼容矩阵里的 17 个 IDE。挑离你的编辑器最近的那条路:
| 编辑器 | 路径 | 命令 |
|---|---|---|
| Claude Code | 插件市场 | /plugin install ux@ux-skill |
| Cursor | .cursorrules + MCP | uxskill init --target cursor |
| Windsurf | .windsurfrules + MCP | uxskill init --target windsurf |
| GitHub Copilot | copilot-instructions.md | uxskill init --target copilot |
| Cline / Continue | MCP 服务器 | uxskill mcp-config --target cline |
| JetBrains AI Assistant | .junie/guidelines.md | uxskill init --target jetbrains |
| Zed | MCP stdio | uxskill init --target zed |
| Codex CLI / Aider | 直接命令行 | pip install uxskill |
所有路径背后都是同一组:1,182 条目录条目、145 条规则、131 条品牌规范、22 条命令、每次发版都通过的 75 个测试。
规则只接得住结构,接不住品味。
linter 永远不会报「这一段节奏不对」「这句文案冷冰冰的」。那是人来做的事,或者是评审用的 LLM 来做的事。但凡能用字面 token、模式、形状表达的指纹,正则都能 稳稳地 接住 — 这恰恰是 AI 默认行为里的绝大部分,因为模型每次都跑回同一种产物。
我们故意不在 CI 上跑 LLM 评委。如果你的标准是「看起来像设计师做的」,那就是 code review,不是 lint。ux-skill 提供 ux critique 来做这一步 — 但只在编辑器里按需运行,不进 CI。