关于 penpot-design-to-code-review
面向设计工程师的只读 Penpot 技能,可将选中的设计与提供的组件代码对照,并生成结构化偏差报告。没有代码时,可改为检查设计与现有系统令牌的一致性。
主要功能
- 01对照 Penpot 选区与组件代码
- 02检查结构、样式和绑定令牌
- 03导出设计图像用于比对
- 04分类并报告设计与代码偏差
- 05保持画布不变
适用场景
- 01核对 React 或 Vue 组件与 Penpot 选区
- 02检查令牌、间距、字体和状态差异
- 03无代码来源时审查设计令牌
MCP 相关
使用 Penpot MCP 工具检查选区和令牌、提取标记与样式,并导出形状用于视觉比对。不会修改画布。
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ penpot/ penpot-ai-kit --skill penpot-design-to-code-review安装后在 Claude Code 中输入/penpot-design-to-code-review确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/01-extract-markup-style.md02-mapping-to-source.md03-storybook-compare.md04-drift-report.mdscripts/exportForCompare.jsextractMarkupStyle.jsgenerateDriftReport.js详细介绍
penpot-design-to-code-review — 弥合设计↔代码之间的 READ 方向鸿沟
1. 标题 + 工作原理
此技能读取 Penpot 中的选区,并将其与工程师实现的代码进行比较,然后报告偏差。所有变更都通过 execute_code 执行;使用 export_shape 进行视觉验证;使用 penpotUtils.shapeStructure 读取结构(完整工具接口:shared/penpot-mcp-tool-reference.md)。它使用 penpotUtils.tokenOverview 读取令牌,通过 penpot.generateMarkup(shapes, opts) 和 penpot.generateStyle(shapes, opts) 提取设计的 HTML/CSS(两者都通过 execute_code 调用),并使用单独的 export_shape 工具生成视觉图像以供并排比较。此技能默认对画布为只读(mode: suggest)——绝不修改设计;它只生成报告,并且仅在明确请求时提出协调步骤。代码侧(React/Vue 等组件文件,或已渲染的 Storybook story/其 DOM+CSS)由用户或其他 MCP(文件系统、Storybook、浏览器工具)提供。如果没有可用的代码来源,此技能会优雅降级,改为将选区与设计系统令牌进行比较。
2. 最重要的一条规则
绝不要一次性完成审查,也绝不要在审查期间进行修改。 提取 → 规范化 → 映射 → 比较差异 → 报告;每一步使用一次独立的逻辑 execute_code 调用,并在各阶段之间进行读取或调用 export_shape。一次性拼凑出的偏差报告无法验证,而且几乎总会误判令牌名称。这是一个 suggest 模式的技能:不会触碰画布。你只需生成一份结构化的 DRIFT 报告;如需协调修改,则交由可写入的技能(令牌相关工作交给 penpot-foundations,结构重建交给 penpot-rename-layers 或 penpot-build-from-code)处理,并遵循各自的检查点。
3. Penpot MCP 工具参考
完整接口:参见 shared/penpot-mcp-tool-reference.md。此技能主要使用以下调用:
| 需求 | 调用(除非另有说明,否则在 execute_code 内) |
|---|---|
| 解析选区 | penpot.selection(形状数组) |
| 读取选区的对象树 | penpotUtils.shapeStructure(shape, maxDepth) |
| 读取活动令牌(名称 → 解析后的值) | penpotUtils.tokenOverview() |
| 根据原始值查找令牌 | penpotUtils.findTokensByName(name) / findTokenByName(name) |
| 生成设计的 HTML | penpot.generateMarkup(shapes, opts) |
| 生成设计的 CSS | penpot.generateStyle(shapes, opts) |
| 检查形状绑定的令牌 | shape.tokens(属性 → 令牌的映射) |
| 检查原始样式值 | shape.fills、shape.strokes、shape.shadows、shape.borderRadius,以及通过 shape.width/height/bounds 获取的几何信息 |
| 生成比较图像 | export_shape(独立的 MCP 工具,不是 JS)——每个状态画板调用一次 |
依赖任何不熟悉的签名之前,请使用 penpot_api_info 验证(例如,penpot_api_info("Page", "generateMarkup")、penpot_api_info("Shape", "tokens"))。
4. 插件 API 要点
注意事项编号对应 shared/plugin-api-gotchas.md。以下是本技能需要特别注意的事项:
generateMarkup/generateStyle接受的是形状数组,而不是单个形状,并且接受一个opts对象。传入选项前,请通过penpot_api_info("Penpot", "generateMarkup")/("Penpot", "generateStyle")确认准确的opts键名——猜错选项名称只会静默地产生默认输出,让你误以为「没有偏差」。直接传入penpot.selection(它已经是数组)。- #1 样式数组中的各项不可变——可以读取(
shape.fills[0].fillColor);绝不要尝试在此处「修复」填充——本技能不会进行修改。 - #5
width/height/parentX/parentY为只读属性——读取几何信息以进行差异比较;此处不要调用resize()/setParentXY()。 - #2 应用令牌是异步的(约 100 ms)——读取时无关紧要,但如果协调交接流程应用了令牌,不要在同一次调用中读回它(避免错误地判定失败)。
shape.tokens是哪个设计令牌绑定到哪个属性的权威映射。优先使用它,而不是根据原始十六进制值猜测令牌。没有绑定令牌的原始值本身就是一项偏差发现(「设计使用了硬编码值,而非令牌」)。generateStyle输出的是设计所理解的 CSS——它不知道你代码中的类名或变量。将其输出视为设计的意图,然后在比较差异前对双方进行规范化(见references/04)。不要直接比较原始 CSS 字符串。- 验证,不要猜测。 当
generateMarkup/generateStyle返回意外结果时,首先应怀疑opts键名错误或参数不是数组——用penpot_api_info重新确认,不要换个猜法再试。
5. 令牌感知型简报约定
开始提取任何内容前,先将请求重述为一份约定。以资深设计工程师的身份行事,将设计文件视为权威规范,绝不接受存在令牌时仍使用硬编码值。
- 上下文——审查对象是什么(单个组件、一组状态、屏幕区域),以及面向谁(哪个应用/库,代码使用哪种框架)。
- 目标——单一且具体:*「确认
src/Button.tsx中的<Button variant=primary size=md>与 Penpot 选区一致,包括悬停/禁用状态。」*不要只说「审查设计」。 - 输入 —
- Penpot 选区(解析它;若为空,请用户选择对象或指定画板);
- 代码源:文件路径/粘贴的组件代码,或 Storybook 故事(URL 或渲染后的 DOM+CSS),或明确指定无;
- 当前使用的令牌集(
tokenOverview()),以及语义令牌应解析到哪个层级。
- 约束——明确各维度由哪一方作为权威来源(通常:视觉令牌、间距、状态以设计为准;行为/无障碍语义以代码为准);仅使用 4px 网格上的令牌值;本技能禁止修改画布。
- 验收标准(量化) —
- 每项设计属性都归类为
match/drift/code-only/design-only; - 每项颜色/间距/圆角/字体偏差都注明涉及的令牌(或标注「未绑定令牌」);
- 颜色偏差报告十六进制级差异,并在适用时报告 WCAG 对比度影响;
- 间距偏差报告 px 差值,以及该值是否符合 4px 网格;
- 状态覆盖情况以矩阵形式报告(设计状态 × 代码状态),并明确列出缺口;
- 根据 §8 的分级标准为每项发现标注严重程度(Critical / Major / Minor)。
- 每项设计属性都归类为
6. 强制工作流
阶段 0 — 发现(只读)
- 目标: 在查看任何样式之前,了解正在审查的内容以及现有的令牌。
- 步骤:
high_level_overview(每个会话调用一次)。execute_code:解析penpot.selection;如果为空,则停止并请用户选择组件/画板。返回 id、名称、类型和数量。execute_code:对每个顶层选中图形调用penpotUtils.shapeStructure(sel[0], 4)— 捕获结构树(名称、类型、嵌套关系)。execute_code:调用penpotUtils.tokenOverview()— 捕获令牌集、名称和解析后的值。- 与用户确认代码来源(文件路径 / 粘贴的代码 / Storybook / 无)。
- 退出标准: 能够说出设计的结构、令牌以及代码来源(或确认没有代码来源)。
- ✋ 检查点: 展示结构摘要 + 令牌清单 + 用于对比的代码来源。请用户确认范围,以及各个维度以哪一方为准。
阶段 1 — 从设计中提取标记和样式
- 目标: 将所选内容转换为规范化的 HTML + CSS + 令牌映射。
- 步骤: 使用
scripts/extractMarkupStyle.js— 调用一次execute_code,运行penpot.generateMarkup(sel, opts)、penpot.generateStyle(sel, opts),遍历每个节点的shape.tokens,并返回{ markup, style, perNode:[{id,name,type,tokens,rawFills,rawSpacing,radius,typography}] }。 - 退出标准: 已获取设计的 HTML、CSS 和逐节点令牌/原始值映射。
- ✋ 检查点: 展示提取出的标记 + 样式(精简版)和令牌映射。确认后再进行映射。
阶段 2 — 获取对比图像
- 目标: 提供一张图像,供工程师与代码渲染结果进行目视对比。
- 步骤: 运行
scripts/exportForCompare.js,收集图形 id、边界和元数据;然后调用export_shapeMCP 工具,使用'selection'(或已捕获的 id),并设置format: png。对于多状态组件,分别导出每个状态画板。请工程师提供代码/故事的渲染截图(如有可用的浏览器 MCP,也可以通过它获取)。 - 退出标准: 已获取设计图像,并与对应的代码渲染结果配对。
- ✋ 检查点: 并排展示;让工程师指出明显的视觉差异,以便确定优先级。
阶段 3 — 映射设计 ↔ 源代码
- 目标: 确定设计节点/令牌/状态与代码元素/变量/属性之间的对应关系。参见
references/02。 - 步骤: 解析提供的代码(属性、CSS/变量用法、状态类/变体);建立映射表
designNode → codeElement、designToken → codeVar、designState → codeState。如果代码来源是 Storybook 故事,则根据故事的 args/controls 推导状态(参见references/03)。如果没有代码来源: 仅将设计令牌映射到设计系统令牌集,并跳过代码列(平稳降级)。 - 退出标准: 映射表中的每个设计属性都关联了对应的代码项,或标记为未映射。
- ✋ 检查点: 展示映射表;在进行差异比对前修正错误映射。
阶段 4 — 比对并生成 DRIFT 报告
- 目标: 交付成果。
- 步骤: 运行
references/04中的比对逻辑,使用scripts/generateDriftReport.js汇总(结构化输出),为每项发现分类(match/drift/code-only/design-only),指定严重程度,并为每项不匹配提出协调建议。对于任何影响对比度的颜色差异,注明 WCAG 编号。 - 退出标准: 报告完整涵盖令牌、结构、间距、排版和状态,并包含严重程度和协调建议。
- ✋ 检查点: 展示报告。如果用户希望应用修复,则交接给适当的写入技能 — 不要在此处进行修改。
7. 关键规则
- 对画布只读。 此技能绝不会调用任何会产生变更的方法。对齐工作会移交给下游技能,并受其自身检查点约束。
- 绝不直接比较原始 CSS 字符串。 比较差异前,先规范化两侧(单位转换为 px、颜色转换为小写十六进制、展开简写属性、对属性排序)。参见
references/04。 - 没有绑定令牌的硬编码值属于问题,即使其视觉效果与代码一致。将其报告为
Minor级设计规范问题(“值未使用令牌”)。 - 每条视觉问题都要注明令牌名称。 先读取
shape.tokens;只有未绑定令牌时,才通过tokenOverview()匹配原始十六进制颜色。 - 间距差异需注明 px 数值和网格对齐情况。 任何非 4 的倍数的值都会被标记,无论它是否与代码一致。
- 信息不足时降级处理,不要猜测。 没有代码来源时,将设计系统令牌作为比较依据,并在报告标题中说明;绝不捏造代码侧信息。
- 每个逻辑步骤只调用一次
execute_code,并按顺序执行,在各阶段之间设置检查点。绝不批量执行提取、映射和差异比较。 - 传入选项前,使用
penpot_api_info确认generateMarkup/generateStyle的opts。 - 进行任何提取前,确认选择非空; 空选择会静默地产生空标记。
- 状态覆盖情况应以矩阵呈现,而非勾选框。 报告每个(设计状态 × 代码状态)单元格。
8. 领域架构
DRIFT 报告架构(即 generateDriftReport.js 返回并由你渲染的内容):
{
runId, scope, codeSource: "file" | "storybook" | "none",
authoritative: { visual: "design", behavior: "code", ... },
summary: { match, drift, codeOnly, designOnly, critical, major, minor },
findings: [
{
id, // stable, e.g. "drift-color-01"
dimension: "color" | "spacing" | "typography" | "radius" | "structure" | "state",
designNode, codeElement, // either may be null
designValue, codeValue, // normalized
token, // bound design token name or null
status: "match" | "drift" | "design-only" | "code-only",
delta, // px delta, hex delta, contrast delta, etc.
grid4px, // boolean | null (spacing only)
wcag, // criterion string if contrast-impacting, else null
severity: "Critical" | "Major" | "Minor",
reconciliation // concrete, one-line suggested fix + which side to change
}
],
stateMatrix: { design: [...], code: [...], cells: [{ state, design: bool, code: bool }] }
}
此结构在 shared/report-schemas/drift-report.schema.json 中有正式定义;需包含派生的顶层 drift 计数(= summary.drift + summary.designOnly + summary.codeOnly),code-to-penpot-sync 工作流会根据此值分支处理。
严重级别标准:
| 严重级别 | 定义 |
|---|---|
| Critical | 影响功能或无障碍体验的对等性破坏:交互元素在设计中存在某个状态,但代码中不存在(反之亦然);颜色差异导致对比度低于 WCAG 1.4.3 AA(4.5:1 / 3:1)。 |
| Major | 用户会注意到的明显视觉不匹配:绑定了错误的语义令牌、间距偏差 ≥ 8px、字号/字重不匹配、缺少结构元素(图标、标签)。 |
| Minor | 规范性或难以察觉的问题:值与设计匹配但未使用令牌、间距差异 ≤ 4px、顺序/命名差异、可选的装饰性偏差。 |
比较的维度: 令牌使用情况(使用哪个令牌、已绑定还是硬编码)、结构(节点树与 DOM 树)、间距(4px 网格上的内边距/间隙/外边距)、排版(字体系列/字号/字重/行高/字间距)、圆角/边框,以及状态覆盖情况。
9. 模式与策略
参见 shared/modes-and-policies.md。此技能默认采用 suggest 模式,并且始终保持该模式——它是一项审计。它不会自动对画布进行任何应用操作。此技能没有安全应用范围,因为它不会进行任何变更;其“输出”就是报告。如果用户要求修复差异,此技能会明确移交处理:令牌替换 → penpot-foundations(apply-with-review)、图层命名 → penpot-rename-layers、结构/几何变更 → penpot-build-from-code。每项对齐操作都会作为提案提交,并注明事实来源一侧;未经下游技能的检查点确认,不会进行任何变更。
10. 状态管理
参见 shared/state-management.md。RUN_ID slug 示例:d2c-2026-06-05-a。通过 storage 写入的账本键(此技能对画布为只读,因此除非用户选择持久化报告,否则不会写入 setSharedPluginData):
storage.run.scope— 已解析的选区 ID/名称。storage.run.tokens— 缓存的tokenOverview()结果。storage.run.extracted— Phase 1 中的{ markup, style, perNode }。storage.run.mapping— Phase 3 中的设计↔代码映射表。storage.run.report— 最终的 DRIFT 报告对象。
**恢复:**重新读取 storage.run;如果不存在(新会话),则重新运行 Phase 0 发现,并基于 shapeStructure + tokenOverview 重新推导,而不是信任过期报告。只有在用户希望将报告持久化到文件中以便交接时,才可选择将报告同步到 setSharedPluginData(NS, "${RUN_ID}.driftReport", JSON.stringify(report))。
11. 用户检查点
| 阶段之后 | 展示的产物 | 要询问的内容 |
|---|---|---|
| 0 发现 | 结构摘要、令牌清单、已识别的代码来源 | “确认范围,以及哪些维度以哪一侧为准。” |
| 1 提取 | 精简后的标记 + 样式、逐节点令牌映射 | “这是否符合你预期设计中声明的内容?” |
| 2 视觉 | export_shape PNG(s) 与代码渲染结果配对展示 | “标出明显的视觉差异,以便确定优先级。” |
| 3 映射 | 设计↔代码映射表 | “更正映射错误的节点/令牌/状态。” |
| 4 报告 | 完整的 DRIFT 报告(摘要 + 发现 + 状态矩阵) | “请批准。是否要将协调工作交接给写入技能?” |
12. 命名约定
参见 shared/naming-conventions.md。此技能会读取名称;它依赖语义化名称(button、label、icon、h1–h6)将设计节点映射到 DOM 元素。自动生成的名称(Rectangle 12、Board)会降低映射质量——如果选区中这类名称很多,建议先运行 penpot-rename-layers,并在报告中注明结构差异判断的置信度较低。令牌会根据 shared/tokens-schema.json 中的点号表示法分类体系进行匹配;令牌的 type 字符串使用 Penpot 的精确拼写(color、spacing、borderRadius、fontSizes 等)。代码变量(CSS 自定义属性、JS 主题键)会在映射前规范化为该点号表示法(例如 --color-action-primary-bg ↔ color.action.primary.bg)。
13. 反合理化对照表
| LLM 给出的借口 | 为什么不对 | 确定性应对措施 |
|---|---|---|
| “CSS 字符串不一样,所以出现了偏差。” | generateStyle 输出 16px,代码输出 1rem;#FFF 与 #ffffff;简写与完整写法。直接比较原始字符串会产生无用结果。 | 先规范化两边(单位→px,颜色→小写 6 位十六进制,展开简写,按属性排序)。只比较规范化后的映射。 |
| “十六进制值与代码一致,所以此属性通过。” | 十六进制值匹配,但未绑定令牌仍会破坏主题化和治理——它在深色模式下不会切换,而且不会发出提示。 | 读取 shape.tokens。如果未绑定令牌,即使十六进制值匹配,也要报告 Minor 级别的“值未使用令牌化”问题。 |
| “没有提供 Storybook/代码,所以我无法审查。” | 此技能必须能够优雅降级。 | 切换到设计系统令牌模式:将所选内容与 tokenOverview() 进行比较,在报告头部设置 codeSource: "none",并仅报告设计侧问题。 |
“generateStyle 返回的内容几乎为空——设计几乎没有样式。” | 更可能是你传入了单个形状,而不是数组,或使用了错误的 opts 键。 | 重新检查:传入 penpot.selection(一个数组);使用 penpot_api_info 验证 opts。重新提取后再得出“没有偏差”的结论。 |
| “我顺手在 Penpot 里修复令牌不匹配吧。” | 这是一个 suggest 模式的只读技能;进行修改会违反约定,并跳过后续检查点。 | 不要修改。将协调方案作为提议输出,并移交给 penpot-foundations/penpot-build-from-code。 |
| “两边间距都是 18px,所以匹配——通过。” | 18 不符合 4px 网格;与代码匹配并不代表数值正确。 | 无论是否与代码一致,都要将所有不是 4 的倍数的间距标记为问题(grid4px:false);建议使用最接近的网格令牌。 |
14. 辅助代码片段
解析并汇总所选内容(阶段 0):
const sel = penpot.selection || [];
return sel.length === 0
? { empty: true, hint: "Select the component/board to review, then re-run." }
: { count: sel.length, items: sel.map(s => ({ id: s.id, name: s.name, type: s.type })) };
读取节点上绑定的令牌与原始值(阶段 1):
const s = penpotUtils.findShapeById("REPLACE-ME-shape-id");
const tokens = s.tokens ? Object.fromEntries(Object.entries(s.tokens)) : {};
const fill = Array.isArray(s.fills) && s.fills[0] ? s.fills[0].fillColor : null;
return { id: s.id, name: s.name, boundTokens: tokens, rawFillColor: fill, radius: s.borderRadius ?? null };
未绑定令牌时,将原始十六进制值与现有令牌匹配(阶段 4 备用方案):
const overview = penpotUtils.tokenOverview();
const target = "REPLACE-ME-#0066ff".toLowerCase();
// scan resolved color tokens for an exact value match
const hit = (overview.tokens || []).find(t =>
t.type === "color" && String(t.resolvedValue).toLowerCase() === target);
return { rawValue: target, suggestedToken: hit ? hit.name : null };
15. 参考资源
penpot_api_info("Penpot", "generateMarkup")和("Penpot", "generateStyle")— 在阶段 1 前确认opts键。penpot_api_info("Shape", "tokens")— 确认绑定令牌映射的结构。penpot_api_info("Page", "shapeStructure")/penpotUtils— 读取结构。shared/penpot-mcp-tool-reference.md— 工具接口和全局变量。shared/plugin-api-gotchas.md— 不可变数组、异步令牌、只读几何信息。shared/tokens-schema.json— 令牌类型字符串、层级、4px 网格规则。- W3C WCAG 1.4.3(对比度),用于提升颜色偏差的严重程度。
16. 支持文件
参考资料(渐进式披露——在相应阶段运行时加载)
| 文件 | 阅读时机 | 内容 |
|---|---|---|
references/01-extract-markup-style.md | 阶段 1 | generateMarkup/generateStyle 用法、opts、读取 shape.tokens 和原始值、构建逐节点映射 |
references/02-mapping-to-source.md | 阶段 3 | 解析提供的组件代码、将设计节点映射到 DOM、将令牌映射到 CSS 变量/主题键、将状态映射到 props/类 |
references/03-storybook-compare.md | 阶段 3(Storybook 场景) | 从 story 参数/控件推导状态,捕获渲染后的 DOM 和 CSS,仅提供 URL 时的优雅降级 |
references/04-drift-report.md | 阶段 4 | 规范化规则、差异算法、严重程度标准、报告呈现、协调措辞 |
脚本(粘贴到 execute_code 调用中;替换占位符)
| 脚本 | 用途 |
|---|---|
scripts/extractMarkupStyle.js | 阶段 1 — 为所选内容生成 generateMarkup + generateStyle + 每个节点的令牌/原始值映射 |
scripts/exportForCompare.js | 阶段 2 — 收集形状 ID、边界和元数据,以便调用方调用 export_shape MCP 工具 |
scripts/generateDriftReport.js | 阶段 4 — 根据提取的设计数据和映射后的代码数据,组装结构化 DRIFT 报告 |
规则文件路径。 在原生安装中,shared/… 和 policies/… 在此软件包内解析(由安装程序随附);在 Claude Code 插件安装中,它们位于插件根目录 — ${CLAUDE_PLUGIN_ROOT}/shared/…,相对于此文件向上两级。