ux-skill v3.0 — 我们发布了 The Brain。品牌规范现在是训练数据,不是模板。
v2 从目录里挑,v3 从目录里蒸馏。同样的 160 份品牌规范,角色完全不同。原来的推荐器会对 1,182 条目录打分,返回最匹配的那一条。新的合成器把这些条目当作词汇来读,从一份 brief 编译出一种崭新的设计语言。每次调用,返回的都是那次调用之前不曾存在过的系统。
不是模板。
用一段话讲清这次转向
v2 是一个推荐器。你给它一份 brief,它从精心整理的目录里 — 84 种风格、176 套调色板、160 份品牌规范、70 组字体搭配 — 返回最相近的一条。输出永远是数据库里的某一行。v3 保留了整份目录,但把它的角色翻转了:这 1,182 条条目现在是引擎用来蒸馏的词汇。一份 brief 不再是去命中一行,而是被编译成 7 个轴的数值,这些数值再合成出全新的 token。目录教会引擎"通透 + 企业"是什么形状;引擎据此生成一个全新的、未曾出现过的"通透 + 企业"系统。同样的数据,完全不同的角色。
v2 里坏掉的、v3 修好的东西
三个真实的 bug,加上一个设计上的空洞。bug 不是那种喧哗的,而是安静的那种 — 引擎一直在返回看起来合理的输出,我们也一直在发布。直到坐下来写升级说明、被一次外部 review 逼着诚实地看一眼,我们才意识到。
1. 靠文件系统顺序的"伪"决定论
推荐器流水线里有几处排序,会按相似度给候选打分,用 Python 的 os.listdir 返回顺序来打破并列。macOS 上是字母序,Linux 上是文件系统日志里的插入顺序。新克隆和老克隆,打平后的结果就可能反过来。我们一直说引擎是确定性的。它在分数不打平的范围内确实是确定性的,一旦打平,就变成"操作系统恰好记得的那个顺序"。
v3 给每一次排序都加上了明确的并列处理 — 按品牌 id 的字母序。同一份 brief、同一组轴、同一份输出,跨机器、跨文件系统、跨 Python 版本都相同。有一个测试会在三个临时目录里把合成器跑 200 次,断言输出按字节完全一致。
2. 轴的冲突全靠"碰巧"解决
在 v2 中,如果 brief 同时含有两个相反方向的标签 — 比如 dense 和 corporate — 推荐器的打分会被某个隐藏系数偏向其中一边,输出就会朝那一边倾斜。没有文档化的规则。事后能解释结果,事前没法预测。
v3 把这件事抬到了一个轴的相互作用矩阵里。每一处文档化的冲突都有明示的解决方式和理由。密度和正式度争抢 spacing — 密集型 brief 让密度赢(彭博风),通透型 brief 让正式度赢(奢华风)。这套矩阵有测试,可读,提交在仓库里。
3. 评估器在批改合成器自己的作业
这一个最难为情。能发现还是因为 ChatGPT 在 v2.1 规范的外部 review 里指出来的 — 他们叫它自我参照漂移 (self-referential drift)。流程看上去很干净:合成器从 brief 编译出一套系统,然后 score_tone_match 评估这个系统和 brief 的语气是不是一致。问题在于,score_tone_match 用的是和合成器一模一样的逻辑,从 brief 推出一组轴,再把合成器的轴和自己推出的轴比对。比较的两边都过同一个函数。当然分数会很高。合成器是用自己的评分标准在批改自己。
v3 把两边解耦了。score_tone_match 现在通过一条独立的、绝不会碰到合成器轴逻辑的映射,直接把合成结果和 brief 的原始语气标签 — warm、bold、minimal 这些字符串 — 做对比。两条路径再也抄不到对方了。这一观察归功于外部 review,我们在此公开承认。
4. 决策日志只写、从不读
v2 在每次 recommend 之后会向 .ux/decisions.jsonl 追加一行,但没有任何东西会再把它读回来。它只是我们自己调试用的日志,并不是反馈信号。推荐器第 1 次调用和第 1,000 次调用行为完全一样。没有学习,仓库有记忆,引擎选择忽略。
v3 把这个回路闭合了。账本有锁定的 schema (_v: 1),推荐器按同一个 (industry, ui_type) 桶里的过往胜者给候选重排,只有分数够高 + 用户真的发布了的决策才算进重排。脑终于长了眼睛看自己的历史。第 6 节是完整说明。
七轴合成器
v3 的中心是一个决定性函数,它把一份 brief 映射到 7 个数值轴,再把这 7 个轴映射到一套完整的设计系统。轴没什么神秘 — 每个都是一个有文档化值域的标准化标量,指向一组特定的 token。在轴上跑(而不是从目录里挑一行)的意义在于:空间里任何组合都可以触达,包括目录里实际品牌从未用过的组合。目录定义空间的形状,合成器可以落到其中任何位置。
| 轴 | 衡量什么 | 映射到 |
|---|---|---|
| warmth | 调色板的色温 — 从冷调炭黑到暖调赭色 | 调色板色相族,强调色饱和度曲线 |
| contrast | 视觉响度 — quiet / balanced / loud | 排版比例 (1.200 / 1.250 / 1.333)、阴影深度、强调色强度 |
| density | 单屏信息密度 — airy 到 dense | spacing 基数 (4/8/12/16px)、行高倍率 |
| geometry | 边缘人格 — sharp / balanced / soft | radius 尺度 (2/8/18px)、转角拓扑、描边粗细 |
| formality | 语气定位 — playful / balanced / corporate | 字重曲线、字距、从 caption 到 hero 的跨度 |
| motion | 动效预算 — restrained / balanced / cinematic | 持续时间梯度 (120/240/420ms)、缓动家族、滚动行为 |
| type_personality | 字体的声音 — humanist / neutral / geometric / expressive | display 字体家族、字重梯度、斜体策略 |
Python 的入口足够小,能塞进一个示例里。传一份 brief,得到一个合成系统,里面挂着轴的数值、选好的调色板、字体设置、spacing 尺度、radius 尺度、动效预设。
# 从一份 brief 到一个系统,一次调用 from engine.synthesizer import synthesize brief = Brief( industry="fintech-payments", tone=["bold", "serious"], audience=["merchants", "developers"], ) sys = synthesize(brief) # sys.mode == "pure_synthesis" # sys.axes == {warmth: 0.32, contrast: 0.72, density: 0.58, ...} # sys.palette, sys.type, sys.spacing, sys.radius, sys.motion
同样的 brief 进来,同样的轴出来,同样的 token 出来。永远如此。合成器没有随机种子,因为没有可种的随机。
三种模式,一套判定逻辑
合成器根据 brief 自动路由到三种模式之一。大多数用法都不需要手动选模式 — brief 的形状自己决定。逻辑刻意简单:是否指定了参考品牌、是否打了 strict 标志,这两件事就决定了路径。
100% 指定品牌
reference_brands=[stripe] 加 strict=True,直接返回 Stripe 规范的原样。不解读、不混合、不合成。适合品牌已存在、规范有权威、brief 只想要 token 的场景。当你手上有一份 Figma 文件和一套写好的系统、需要不即兴的代码时,走这条路。
以品牌为锚,按 brief 的轴做调适
reference_brands=[stripe] 但不带 strict=True,返回 70% 的 Stripe token 与按轴距挑选出的 4 个姐妹品牌 30% 的轴差融合。输出仍然一眼就是 Stripe,只是朝 brief 的语气微微弯曲 — 给消费者功能时是更俏皮一点的 Stripe,给企业级仪表盘时是更正式一点的 Stripe。锚定品牌在所有冲突里都胜出。
未指定品牌 — 每次调用都是新的语言
brief 里没有 reference_brands 时,合成器从 brief 给 7 个轴打分,在目录里按轴距找到 8 个最近的范例,蒸馏它们共有的结构,产出全新的 palette + type + spacing + radius + motion 一整套。不同 brief 落在同一空间的不同区域,每个输出都内部自洽、且具有可识别的自我。
从 CLI 看,三条路径是这样的:
# strict — 100% 指定品牌 $ uxskill synthesize --brand stripe --strict # anchor — 70/30 $ uxskill synthesize --brand stripe # pure synthesis — 无品牌 $ uxskill synthesize --industry fintech-payments --tone bold
一个合成器,一套判定逻辑,三种输出人格。用户不切换实现 — brief 自己挑路径。
轴的相互作用矩阵
轴会争抢同一批 token。density 想要紧凑的 spacing,formality 在奢华语境里想要更宽裕的 spacing。geometry 为编辑感想要锐利的转角,formality 在金融仪表盘里想要柔和的转角。在 v2 里,谁的系数恰好更大谁就赢。在 v3 里,每一个文档化的冲突都有声明的结果,以及它指向的设计流派 — 名字都列出来了,你读了之后想反对完全可以。
四个典型案例,都作为测试 fixture 提交了:
密度赢。彭博学派的答案。当 brief 要求在企业语境里塞密集的数据仪表盘,信息密度就是最高的美德 — 正式度在"密度下的可读性"面前让位。
正式度赢。奢华金融的答案。airy + corporate 是给高净值银行、私人财富、高管仪表盘的 brief — 元素之间的空气,本身就是"严肃"的信号。
几何赢。NYT 学派的答案。sharp + corporate 是编辑型 brief — 直角加上 2px 的微小 radius,读起来是机构感、深思熟虑、对开报纸式的语气。
两个轴方向一致。Glossier 学派的答案。柔和几何加俏皮语气会收敛到宽裕的圆角 — 18px 是矩形开始读作"鹅卵石"的阈值。
为什么这件事要紧:v2 里,同一份 brief 这个月可能落 4px,下个月可能落 12px,看打分时哪个系数赢。v3 里,dense + corporate 永远是 4px,airy + corporate 永远是 12px。规则可读、可测、可争论。如果你不认同某个结论,可以对矩阵开 issue,讨论的是品味,而不是某个藏起来的常量。
脑会学习
这才是这次发布对项目而言真正新的部分:引擎会从你本地的决策里学习。机制小、确定、完全离线。没有遥测、没有账号、不会上云。你的安装从你的安装里学。不同的仓库会长出不同的脑。
账本
每一次 /ux-recommend 和 /ux-synthesize 调用,都会向 .ux/decisions.jsonl 写入一行。schema 锁定为 _v: 1:
{
"_v": 1,
"ts": "2026-05-28T14:22:09Z",
"frame": { "industry": "fintech-payments", "ui_type": "dashboard", ... },
"system": { "style_id": "editorial-calm-dark", "palette_id": "charcoal-amber", ... },
"axes": { "warmth": 0.31, "contrast": 0.72, ... },
"lint_score": 88,
"user_accepted": true
}
重排
下一次调用时,推荐器会按 (industry, ui_type) 给历史记录分组。每个候选只要命中同桶里的一条先验,就拿到 +5 的加成。只有 lint_score >= 80 且 user_accepted = true 的先验才会被计算 — 被拒绝的输出不会进入学习。在任何重排发生之前,同一桶里至少需要 3 条合格先验,否则引擎按冷启动跑,行为和全新安装完全一致。
承诺
这里能给出的承诺很窄、值得说清楚。确定性保留。同一份 brief + 同一份账本永远产生同一份输出。+5 加成在任何 tie-break 之前应用,品牌 id 字母序的规则仍在下层把所有打平拆开。冷启动安全。新仓库的行为与其他所有新仓库相同。学习是本地的。你的决策从不离开你的机器。
你随时可以查看自己的安装学到了什么:
$ uxskill stats --html [OK] Wrote .ux/stats.html [OK] Open in browser to see your install's learned priors
这份 HTML 面板会显示每个桶下的决策数量、平均 lint 分、最常出现的调色板、轴的分布。这就是自我学习的可见证据 — 随着你不断发布,你的安装的"品味轮廓"会随时间变得更浓。没有任何东西在服务器上。一切都在你的仓库里:明文 JSONL 和生成的 HTML 视图。
/ux-evolve 自动循环
v3 的新命令是 /ux-evolve。直到现在,打磨循环都是手动的:跑 lint、读结果、修、再 lint。Evolve 把这个循环关掉了。你把一份产物和一个目标分给它,它会跑 lint、应用 6 个幂等的打磨 pass、再 lint,要么停在目标、要么停在 plateau、要么停在 5 轮上限。
一轮的结构:
- Lint — 跨 A11y、内容、质量、排版的 145 条确定性正则规则。返回一个 0-100 的分数和一份 finding 列表。
- Polish — 6 个 pass,优先修复高严重度 finding。幂等:对已经打磨过的产物再跑一遍是 no-op。
- Re-lint — 给打磨后的产物重新打分。
- Decide — 新分数 ≥ 90,停;距上一轮的 delta 小于 5(plateau),停;到第 5 轮,停;否则继续循环。
质量门禁硬卡在 65。最终分数低于 65,引擎拒绝发布,除非传入 --force。理由是:65 以下的输出已经是能被识别出来的 AI slop,我们不允许自己的引擎安静地输出 slop。用户可以覆盖,但覆盖是显式的,并且会写进决策账本。
一个具体例子:一份金融科技仪表盘脚手架第一次 lint 拿了 72 分 — 大部分是焦点状态缺失、几个低对比度标签、一个"用 Inter 做 display"的 finding。第 1 轮把焦点状态打磨到 81 分,第 2 轮把对比度修到 86 分,第 3 轮把 Inter 换成 brief 实际指定的 display 字体并重新平衡 caption 尺寸 — 91 分,目标到达,循环结束。三轮,无拒绝,发布。
我们没有做的事(以及原因)
v2.1 极端化版本的规范里,有几样东西没进 v3。诚实地说出哪些没做、为什么没做,是讨论 scope 唯一靠谱的方式。
不让 LLM 进入回路
极端化方案里有一个由 LLM 判定的"主观美学轴" — "问问模型这是不是编辑风"。我们出于原则拒绝了。引擎的全部要点就是确定性:同一份 brief、同一份输出、明天早上不会有惊喜。一旦 LLM 进入合成器,每次调用都不可复现。v3 调用 LLM 零次。合成器是纯 Python 走 JSON,linter 是正则,评估器是规则驱动。
不做多候选遗传变异
方案里还有一个遗传算法步骤 — 每次调用产出 N 个候选,变异、打分、返回最优。我们在分支里试过,输出反而更差了,因为变异引入了噪声,打磨循环还要再清掉。v3 选择单候选合成 + 打磨循环。一个候选,六个幂等 pass。结果更好,代码面更小。
不改命令名
方案里把 /ux-recommend 改成 /generate:ui,把 /ux-polish 改成 /mutate:ui。我们保留了已有的全部 22 条命令的原名,把 /ux-evolve 作为第 23 条加进来。我们不希望一次从 v2 到 v3 的升级,把任何人的肌肉记忆或 shell alias 弄坏。
不"为了无穷空间烧掉目录"
最极端的版本认为目录是负担 — 合成器能独立到达整个设计空间,1,182 条条目应该删掉。我们把每一条都留下了。目录是教会合成器空间形状的东西 — 没有样本,轴就没有锚,输出就开始漂。v3 把目录当词汇来读,v3 需要这套词汇。
这些是品味判断,优先级不同的人会反对,没问题。把我们没做的事、为什么没做记录下来,这是我们对项目的本分。
v3 时的数字
22 条已有的斜杠命令名字保持不动,新增的第 23 条是 /ux-evolve。15 个 MCP 工具保持不动,新增三个 — ux_synthesize、ux_decisions_query、ux_decisions_stats — 给想驱动合成器、或想查询脑学到了什么的 agent 使用。目录和 linter 不动。17 个 IDE 的安装器不动。17 份本地化首页和 README 也不变。v3 在表层是加法,在内核是改写。
v2 是从目录里挑的推荐器。v3 是从目录里蒸馏的编译器。同样的数据,在做相反的工作。
60 秒安装
v3 走和 v2 一样的三条发布路径 — pip、npm、IDE 插件市场。init 步骤会自动检测你的 IDE,把正确的配置文件写到正确的位置。synthesize 步骤接受一份 brief 或一组 industry + tone,产出一套完整的设计系统到当前仓库的 .ux/last-system.json。
$ pip install uxskill $ uxskill init # 自动检测你的 IDE $ uxskill synthesize --industry fintech-payments --tone bold [OK] Mode: pure_synthesis [OK] Axes: warmth=0.31, contrast=0.74, density=0.58, geometry=0.42, ... [OK] Wrote .ux/last-system.json (palette, type, spacing, radius, motion) [OK] Wrote .ux/decisions.jsonl (1 entry, schema _v: 1)
所有支持的 IDE 都是同一条安装路径:Claude Code、Cursor、Windsurf、GitHub Copilot、Gemini CLI、Codex、Kiro、Cline、Continue、Aider、Zed、JetBrains AI、Pieces、Tabby、Tabnine、CodeWhisperer、Roo Cline。MCP stdio 服务器是所有 IDE 的唯一真源。
v3 是地基,不是终点。
七轴合成器是我们已经放出证据的工作。第 8、第 9 个轴 (saturation_strategy、surface_depth) 草案在,未发布 — 它们会让 dark / light 分流和渐变策略更深一层。决策账本目前只被推荐器的重排消费,我们预计 v3.1 会让评估器和 lint 打分也开始读它。我们拒掉的那份极端化方案并没有消失 — 当我们能证明某些点确实有帮助时,它们会在以后慢慢进来。
如果你发现某份 brief 的输出让你不满意,或者矩阵里某个结论和你的品味打架,请开 issue。矩阵在仓库里,测试在仓库里,脑也在你的仓库里。从现在起,争论都发生在一个你可以读得到的基底上。
这对你意味着什么变化
在 v2 的人:什么都不会坏。你已经在用的 22 条命令行为不变。lint 还是一样的速度。MCP 服务器用原名暴露 18 个工具里的 15 个,再加三个新工具。如果你升级后从不调用 /ux-synthesize 或 /ux-evolve,你的日常工作流毫无变化。
一直在等引擎从推荐走到生成的人:v3 就是这一步。合成器是新的中心,/ux-recommend 还在,而且现在会读你的账本来重排;/ux-evolve 关上了打磨循环。品牌规范是训练数据,不是模板。同样的 1,182 条条目,在做完全不同的工作。
我们把 v3 叫做 The Brain,是因为做出来的就是这个东西 — 一个带闭合反馈回路的、受约束的生成式设计编译器。完全离线,LLM 调用 0 次。同 brief 同轴同输出,异 brief 异区域,无穷输出。你的安装从你的仓库里学,不同仓库长出不同的脑。整个东西是 MIT 协议、普通机器上 sub-300ms、pip 可装。
找一份你真正在意的 brief 跑一下,看返回的是什么。如果输出和你的品味打架,开 issue — 冲突进矩阵,规则会被命名,下一次安装就会表现得更好。这就是脑的复利方式。