Community编程与开发github.com

rmnturov/koobiq-icons-figma-audit

验证 Figma 图标组件在从 Koobiq 图标文件中导出之前是否组织正确。检查节点类型、名称格式、大小、布局、根 shape/shape-2 结构、缩放约束、填充颜色约定和描述元数据,并验证同一图标的不同尺寸变体是否相互一致。

koobiq-icons-figma-audit 是什么?

此技能用于审计 Koobiq 图标库中 Figma 图标组件的结构和约定合规性,确保通过 `figma:sync` 导出为 SVG 时正确无误。它支持所有主流 AI Agent 平台(如 Claude Code、Cursor、Codex),通过 Figma MCP 进行只读检查,并可选地执行修复。技能的核心是一套详细的规则,涵盖:节点类型必须是 COMPONENT、名称格式(小写英文、连字符连接、尺寸后缀)、几何尺寸(方形、无自动布局、无剪裁)、根结构(单色 icon 只有一个 shape 节点,双色 icon 有 shape 和 shape-2)、约束(SCALE)、填充颜色约定(主色绑定到 Icon/Contrast 变量,次色固定为 #E21D03 或绑定到 Icon/Error 变量),以及描述元数据。此外,它还会横向比较同一图标在 16/24/32/48/64 五种尺寸下的变体一致性,并检查名称中是否包含 `.` 或 `_` 等内部导出前缀。技能通过严格的页面上下文保护(page node id + guard)避免并行读取时的数据串扰,并在无法访问文件时提示用户检查 Figma 令牌、文件权限或提供正确链接。

兼容平台~Claude Code~Codex CLI~Cursor
Part ofMobile
npx skills add https://github.com/rmnturov/koobiq-icons-figma-audit-skill/tree/HEAD

Installed? Explore more 编程与开发 skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

在你喜欢的 AI 中提问

打开一个已预加载此 Agent Skill 的新对话。

文档

Koobiq Icons Figma Audit

此技能验证 Koobiq 图标 Figma 文件中的图标 组件 是否符合库的结构约定,确保 figma:sync(@figma-export 管道)能够生成干净、正确的 SVG。在 Figma 中发现问题远比在导出后发现问题成本更低,因为导出会静默地将任何找到的内容进行转换——错误的图层名称、多余的节点或偏离网格的坐标都会变成有问题的 SVG,且没有错误信息。

默认情况下,这是一个 只读审计:检查,然后报告清晰的通过/失败清单以及具体、可操作的结果。仅在用户明确要求修复时修改 Figma 文件(参见“提供修复”)。

先决条件

  • 必须连接 Figma MCP。此技能通过 get_metadata(结构概览)、use_figma(通过插件 API 进行精确检查)和 get_screenshot(视觉完整性检查)驱动它。

  • 在每次调用 use_figma 之前,请加载 figma-use 技能——它记录了插件 API 规则并防止常见、难以调试的失败。

  • 文件键和尺寸页面是固定的(已验证与实时文档一致)。要跳过页面发现往返,可以直接按节点 ID 跳转到页面:

    尺寸页面节点 ID
    161320:17387
    2442149:860
    3242149:862
    4842149:861
    6442149:863

    文件键:WvUarBj8tO256vML4PkBLl。每个页面名称就是尺寸后缀;名为 foo_24 的图标位于页面 24 上。文档: https://www.figma.com/design/WvUarBj8tO256vML4PkBLl/%F0%9F%9F%A5-koobiq-%C2%B7-icons?node-id=1320-17387

    • 此文件键是唯一真理来源——它与 config.mjs 中的键匹配。packages/icons/README.md 中的“绘制图标”链接指向一个 不同的、错误的文件——忽略它,信任上面的键。
    • 不带 nodeId 的 get_metadata 只列出第一页(ICONS,其中不包含图标组件)——它 不会 枚举尺寸页面。不要将其用于页面发现;请使用上面的节点 ID。

如果无法访问文件

如果 Figma 调用失败——身份验证/权限错误、“节点不存在”、文件为空或意外、或 MCP 无响应——请停止操作并向用户展示问题,而不是猜测。告诉他们哪里失败了,并请他们执行以下一项操作:

  • 提供不同的/正确的 Figma 链接(config.mjs 中的文件键可能已过时,或者他们粘贴了另一个文件或分支的链接),或
  • 重新检查仓库 .env 中的 FIGMA_TOKEN(figma-export / MCP 访问令牌可能丢失、过期或吊销),或
  • 确认他们的 Figma 帐户有权访问该文件(这是一个私有设计文件——他们可能需要被邀请或使用关联了有权限帐户的令牌)。

不要根据假设编造审计结果;检查只有在针对实时文件时才有意义。

并行读取与页面上下文

Figma 读取调用可以并行运行,但仅限于 任务中第一次 Figma 编辑之前。任何编辑开始后,请一次只运行一个 Figma 调用。

切换页面的并行读取可能会相互干扰。在一次观察到的运行中,针对页面 24 的读取返回了页面 16 的数据(相同组件数量,但名称错误),审计报告了 24px 变体缺失。保护每次切换页面的读取:

// 在每次切换页面的读取中使用。仅返回属于 PAGE_ID 的节点的数据。
const PAGE_ID = "42149:860";   // 上表中的页面节点 ID
const NAMES = ["foo_24"];       // 要查找的组件名称
const page = await figma.getNodeByIdAsync(PAGE_ID);
await figma.setCurrentPageAsync(page);
if (figma.currentPage.id !== PAGE_ID) throw new Error("读取前页面上下文不匹配");
const pageOf = (n) => { let p = n; while (p && p.type !== "PAGE") p = p.parent; return p; };
const hits = figma.currentPage
  .findAllWithCriteria({ types: ["COMPONENT", "COMPONENT_SET"] })
  .filter((n) => NAMES.includes(n.name) && pageOf(n) && pageOf(n).id === PAGE_ID)
  .map((n) => ({ name: n.name, id: n.id, w: n.width, h: n.height }));
if (figma.currentPage.id !== PAGE_ID) throw new Error("读取期间页面上下文已更改");
return { pageId: PAGE_ID, hits };

如果 guard 抛出异常,请丢弃该结果并重新运行读取。任何“缺失”声明(例如,“没有 24px 变体”)必须基于对该页面的受保护读取,或基于按节点 ID 的查找。永远不要从抛出异常的读取或未检查的页面中报告缺失。

步骤 1 — 解析目标

用户可以给你以下任何一种:

  • 一个 Figma 节点 URL 或 ID(例如 node-id=44731-6643)→ 精确检查该节点,然后按名称查找其兄弟尺寸变体以进行跨变体检查。
  • 一个图标名称(例如 floating-window 或 floating-window_16)→ 在图标页面中查找所有尺寸变体并全部检查。新图标通常是 未发布的,甚至可能是未命名的,因此如果名称匹配失败,请在相关页面上列出组件并让用户指向该节点,或按节点 ID 检查。

页面名称等于尺寸后缀。尺寸扫描: 检查所有五个页面(16 / 24 / 32 / 48 / 64)中的每个请求名称,因为图标可能存在于用户未提及的尺寸中。使用先决条件表中的页面节点 ID,每个页面一次 use_figma 调用,每次调用都带有“并行读取与页面上下文”中的 guard。仅在 Figma 编辑之前并行运行它们。将结果作为表格报告(名称 × 尺寸 → 节点 ID 或缺失)。

步骤 2 — 检查

运行此只读脚本(调整 targets/页面 ID)。它将返回以下规则所需的所有内容。同时获取一个 get_screenshot 以便你可以确认字形实际渲染。

// 粘贴到 use_figma (skillNames: "figma-use")。设置 PAGE_ID 和 NAMES。
// 页面节点 ID:16=1320:17387  24=42149:860  32=42149:862  48=42149:861  64=42149:863
const PAGE_ID = "1320:17387";      // 页面名称是尺寸,例如 "16"
const NAMES = ["floating-window_16"]; // 在此页面上要审计的组件名称
figma.skipInvisibleInstanceChildren = false; // false:规则 G 也必须看到隐藏图层
const page = await figma.getNodeByIdAsync(PAGE_ID);
await figma.setCurrentPageAsync(page);
if (figma.currentPage.id !== PAGE_ID) throw new Error("读取前页面上下文不匹配");

const toHex = (c) => {
  const h = (n) => Math.round(n * 255).toString(16).padStart(2, "0").toUpperCase();
  return "#" + h(c.r) + h(c.g) + h(c.b);
};
const describeFill = (n) => {
  if (!n.fills || n.fills === figma.mixed) return n.fills === figma.mixed ? "MIXED" : null;
  return n.fills.filter((f) => f.visible !== false).map((f) => ({
    type: f.type,
    hex: f.type === "SOLID" ? toHex(f.color) : undefined,
    opacity: f.opacity,
    boundVar: f.boundVariables && f.boundVariables.color ? f.boundVariables.color.id : null,
  }));
};

const pageOf = (n) => { let p = n; while (p && p.type !== "PAGE") p = p.parent; return p; };
const all = page.findAllWithCriteria({ types: ["COMPONENT", "COMPONENT_SET"] })
  .filter((n) => pageOf(n) && pageOf(n).id === PAGE_ID);
const report = [];
for (const name of NAMES) {
  const c = all.find((x) => x.name === name);
  if (!c) { report.push({ name, found: false }); continue; }
  report.push({
    name: c.name, id: c.id, type: c.type,
    width: c.width, height: c.height, x: c.x, y: c.y,
    layoutMode: c.layoutMode,
    clipsContent: c.clipsContent,
    description: c.description,
    children: c.children.map((ch) => ({
      name: ch.name, type: ch.type,
      w: ch.width, h: ch.height, x: ch.x, y: ch.y,
      constraints: ch.constraints,
      fills: describeFill(ch),
      styles: { fill: ch.fillStyleId, stroke: ch.strokeStyleId, effect: ch.effectStyleId },
      boundVariableFields: ch.boundVariables ? Object.keys(ch.boundVariables) : [],
    })),
  });
}
if (figma.currentPage.id !== PAGE_ID) throw new Error("读取期间页面上下文已更改");
return report;

为了可靠地验证 颜色约定,还以相同方式读取一个已知正确的同尺寸兄弟图标(例如 list_16),并比较主形状的绑定变量 键(规则 D)。名义上的约定十六进制值与文件中实际存储的值不同(参见规则 D),因此与真实邻居进行比较比匹配字面十六进制值更值得信赖。

步骤 3 — 对照规则进行检查

每项报告为 ✅ 通过 / ❌ 失败,并附上观察到的值以及失败时应为的值。

A. 节点类型和名称

  • 节点是一个 COMPONENT(或是尺寸变体的 COMPONENT_SET),而不是 FRAME、GROUP 或 INSTANCE。只有组件才能导出。

  • 首字符——先检查此项,它会改变名称的含义:

    • 以 . 或 _ 开头 → 一个 内部组件,有意不导出。 导出的 .figmaexportrc.mjs 中的 filterComponent 会丢弃任何匹配 ^[._] 的名称。这是设计使然,不是错误——不要将其作为有问题的图标进行审计。仅当用户期望此节点导出时提出(那么修复方法是重命名,去掉前导的 ./_)。
    • 以 - 开头 → 无效,不允许。 标记为失败。
    • 以 数字 开头 → 允许但 不鼓励。 它仍然会导出(例如 3-columns_32),但名称最好以英文字母开头——建议用户重命名。软性建议,不是阻塞项。
    • 以 英文小写字母 开头 → 首选情况。
  • 名称格式 — 可导出的图标名称必须匹配此正则表达式:

    ^[a-z0-9]+(-[a-z0-9]+)*_(16|24|32|48|64)$
    

    仅限英文小写字母和数字;单词由 单个 连字符连接(无 --,无前导/尾随连字符);然后 恰好一个 下划线;然后是有效的尺寸后缀。无大写字母、空格、西里尔字母/其他文字或其他标点符号。

    • ✅ floating-window_16、api_24、arrow-up-to-bracket_16(以及,格式有效但数字不鼓励的 3-columns_32)
    • ❌ Floating-Window_16(大写)· плавающее-окно_16(非英文)· floating_window_16 / icon__16(两个下划线)· floating--window_16(双连字符)· floating-window-16(尺寸前用连字符代替下划线)· -floating-window_16(前导连字符)· floating-window_20(尺寸不在允许集合中)· floating-window_16 (尾随空格)
  • -o 后缀 = 轮廓变体。 没有 -o 的名称是填充变体。像 cloud_24 / cloud-o_24 或 cloud-arrow-up_24 / cloud-arrow-up-o_24 这样的对是一个图标的两种样式。这是有效的,不是冲突或重复;不要因此要求重命名。

  • 数字后缀必须等于组件的实际尺寸(规则 B)。一个 ..._16 却是 24×24 的是个错误。

B. 几何和布局

  • 无自动布局:layoutMode === "NONE"。自动布局会重新排列子元素并破坏导出。
  • 固定且为方形:width === height === size。尺寸必须是整数像素(像素网格对齐);分数组件尺寸意味着图标不是在网格上绘制的。
  • 关闭剪裁内容:clipsContent === false。“剪裁内容”切换开关必须禁用。对于图标的所有尺寸变体,它必须相同(关闭)。 当它开启时,任何超出框架边界的图稿会在导出时被静默裁剪,产生一个被剪裁或截断的图标。

C. 根结构(这是大多数真实错误隐藏的地方)

  • 单色图标:恰好 一个 根子元素——一个名为 shape 的 VECTOR。
  • 双色图标:恰好 两个 根子元素——shape(主要)和 shape-2(次要)。没有其他元素。
  • 组件根目录下没有其他节点:没有多余的框架、组、矩形、切片、重复的向量或隐藏的辅助图层。每个多余节点都会出现在导出的 SVG 中。
  • 每个形状的 约束在两个轴上都是 SCALE(horizontal: "SCALE", vertical: "SCALE"),以便图稿随框架缩放。
    • 严重性:警告(卫生),不是导出阻塞项。 约束控制组件在 Figma 中的调整大小方式,而不是导出的路径数据。与其他批准的 Figma 编辑一起修复它们,因为实例和调整大小依赖于它们。
  • 理想情况下,每个形状都是一个单一的扁平路径(参见仓库 README “绘制图标”步骤:轮廓、扁平化、非零填充规则)。将多个子路径或未扁平的组标记为警告。

D. 填充颜色约定

约定(仓库 README “SVG 颜色区域指南”):

  • 主要区域 shape → 黑色 #21222C。导出会移除其 fill,以便 SVG 继承 currentColor。
  • 次要区域 shape-2 → 红色 #E21D03。导出会将其重写为 fill="currentColor"。

实际情况:在生产文件中,主填充绑定到共享的 Icon/Contrast 变量,该变量 解析为 #000819,不透明度为 0.85 paint opacity——不是 字面量的 #21222C。所以:

  • 参考键:91eb54e0eae961c540c994192c4eed18e4ea1825 — 来自 🟥 koobiq · components 19(集合键 c78fab428cf2003c9aa53d86b132a2fa95bacb2e2)的 Icon/Contrast。
  • 将主填充的绑定变量 键 与该参考值进行比较 ((await figma.variables.getVariableByIdAsync(boundId)).key),并检查填充不透明度是否为 0.85。不要比较名称:Icon/Contrast 存在于多个库和集合中。图标中唯一允许的 变量 就是这个,通过键识别。一个同名但键不同的变量(来自另一个库或集合)是一个 失败,而不是警告。修复:重新绑定到来自同一库集合的参考键变量。
  • 次要的 shape-2 必须是纯色字面量 #E21D03(导出将其作为字面量十六进制进行匹配——.figmaexportrc.mjs 中的 secondaryZoneColorHex),或者通过键绑定到来自 🟥 koobiq · components 19 的 Icon/Error(键 e736157dcefc2a3e016ec2ddd41a7dc8cf8a5bc6)。对于变量情况,不检查填充不透明度。任何其他填充都会失败。
  • 一个不与任何兄弟匹配的纯色字面量主填充,或一个不是 #E21D03 的次要填充,都会在导出时出现问题...

...

相关技能