首页
/ Understand-Anything /understand-figma:把 Figma 设计文件变成可交互的设计知识图谱

Understand-Anything /understand-figma:把 Figma 设计文件变成可交互的设计知识图谱

2026-09-06 14:02:33作者:戚魁泉Nursing

/understand-figma 是 Understand-Anything 插件中面向设计资产的专用技能:它通过 Figma REST API 拉取设计文件,用确定性解析器抽取页面、屏幕、组件、组件集、实例与设计令牌,再借助 LLM 子代理补充语义摘要,最终合并出一份 kind:"design" 的知识图谱并挂到现有 Dashboard 上。读完全文,你将掌握该技能的四阶段流水线(Pre-flight / Fetch & Parse / Analyze / Merge & Launch)、增量扫描与缩略图刷新机制、scan-manifest.json 的数据结构,以及 mergeDesignGraph 如何生成图层(layers)与导览(tour)的源码级实现。

一、技能定位与前置条件

/understand-figma 的完整行为规范定义在 SKILL.md。与完全离线的 /understand(分析本地代码库)不同,这个技能会向 api.figma.com 发起出站请求,属于在线技能。执行前需要满足以下前置条件:

  • FIGMA_TOKEN 环境变量:一个 Figma 个人访问令牌(personal access token),在 Figma 账号设置页面创建后通过 export FIGMA_TOKEN=<token> 注入。缺少该变量时,技能会终止并提示用户先配置令牌。
  • 运行时要求:Node ≥ 22,pnpm ≥ 10。

技能文档中还有一条明确的安全约束,值得单独强调:令牌只从环境变量读取,只出现在 X-Figma-Token 请求头中,绝不能被写入图谱、meta.json、日志或任何中间文件。这条约束在源码中得到了一致的落实——FigmaApiSourceget 方法将令牌固定放入请求头,注释直接写明 "Token travels only in the header — never in the URL, never logged"(令牌只走请求头,不进 URL,不打日志)。

二、Phase 0 —— 预检(Pre-flight)

预检阶段完成四件事,全部为确定性操作:

  1. 解析参数:从 $ARGUMENTS 中取出 Figma URL 或裸文件 key(非 flag 参数),以及可选的 --language <lang> 参数。

  2. 解析数据目录 $UA_DIR:这是 Understand-Anything 的兼容层逻辑——

    UA_DIR="$PROJECT_ROOT/$([ -d "$PROJECT_ROOT/.understand-anything" ] && echo .understand-anything || echo .ua)"
    

    即旧版 .understand-anything/ 目录若已存在则优先复用以保持向后兼容,否则使用新的 .ua/ 目录。由于每个阶段可能运行在全新的 shell 中,文档要求像 $PROJECT_ROOT 一样把 $UA_DIR 向后传递,必要时用同一行命令重新解析。脚本侧的实现与之一致,见 figma-scan.mjs 中的 uaDir 函数,注释也注明这是"镜像 core 的 resolveUaDir 逻辑"。

  3. 确保 core 已构建:解析 PLUGIN_ROOT 后检查 packages/core/dist/figma/index.js 是否存在;缺失时执行:

    cd "$PLUGIN_ROOT" && (pnpm install --frozen-lockfile 2>/dev/null || pnpm install) && pnpm --filter @understand-anything/core build
    

    技能脚本正是依赖该构建产物——figma-scan.mjs 首行就从 @understand-anything/core/figma 导入 parseFileKey、FigmaApiSource、parseDocument、extractTokens、applyScreenThumbnails

  4. 创建中间目录mkdir -p $UA_DIR/intermediate,后续各阶段的中间产物(manifest、分批分析结果)都落在这里。

三、Phase 1 —— 拉取与解析(确定性阶段)

预检通过后运行捆绑的扫描脚本:

FIGMA_TOKEN="$FIGMA_TOKEN" node <SKILL_DIR>/figma-scan.mjs "$PROJECT_ROOT" "<url-or-key>"

脚本成功后写入 $UA_DIR/intermediate/scan-manifest.json 并打印各节点类型计数,技能要求把这些计数转述给用户;若脚本非零退出,则转述 stderr 并停止。下面按源码顺序拆解这条流水线。

3.1 文件 key 解析与 API 访问层

parseFileKey 支持两种输入:从 figma.com/file/...figma.com/design/... URL 中用正则提取 key,或直接接受纯字母数字的裸 key;无法解析时抛出带原始输入的清晰错误。

FigmaApiSource 封装了三个 Figma REST 端点,全部以 https://api.figma.com/v1 为基址:

方法 端点 用途
fetchDocument() GET /files/{fileKey} 获取完整文档树(根 DOCUMENT,其子节点为各页面 CANVAS)
fetchStyles() GET /files/{fileKey}/styles 获取已发布样式(design tokens 的来源)
renderImages(nodeIds) GET /images/{fileKey}?ids=...&format=png&scale=1 批量渲染屏幕缩略图

请求失败时统一抛出 Figma API {path} failed: {status} 错误。构造函数在未设置 FIGMA_TOKEN 时抛出带创建指引的友好错误,与 SKILL.md 的预检要求呼应。

3.2 基于版本号的增量跳过与缩略图就地刷新

figma-scan.mjs 在解析前会读取已有 meta.json 中的 figmaVersion,与本次拉取文档的 version 字段比对。若两者相同且未设置强制重建环境变量,则打印 UP_TO_DATE 并退出——此时技能按 SKILL.md 的要求向用户报告"该 Figma 文件版本的设计图谱已是最新"并停止;若要强制全量重建,需在环境中设置 UNDERSTAND_FIGMA_FORCE=1 后重跑。

这个跳过路径有一个关键细节:直接跳过并不完全等于什么都不做。Figma 的图像渲染 URL 是预签名 URL,数小时后过期,而屏幕缩略图 URL 已持久化在 knowledge-graph.json 中。因此 refreshThumbnailsInPlace 会在 UP_TO_DATE 路径上重新渲染所有 screen 节点对应的缩略图,并就地打补丁回写已有图谱,避免 Dashboard 侧栏出现失效图片;整个过程是尽力而为(best-effort),任何异常都不会让增量路径失败。

3.3 文档树解析:六种节点与四种结构边

parseDocument 把 Figma 文档树翻译为图谱节点与边,规则如下:

  • CANVAS → page:文档根节点的每个 CANVAS 子节点成为一个页面节点,id 形如 page:{figmaId}
  • FRAME → screen:页面下的顶层 FRAME 成为屏幕节点,并把 absoluteBoundingBox 的宽高记入 figmaMeta.dimensions
  • COMPONENT → componentCOMPONENT_SET → componentSet:页面顶层组件与组件集;组件集内的每个 COMPONENT 变体还会产生 variant_of 边(权重 0.9)连回组件集。
  • INSTANCE(深度读取):屏幕节点采用"浅节点集、深读取"策略——collectInstances 递归遍历屏幕子树,收集所有 INSTANCE,建立 contains 边连到所属屏幕;若实例引用了主组件(componentId),则追加 instance_of 边(权重 0.8)。此处有个容易混淆的点:figmaMeta.componentKey 存的是文档 components 映射表里全局发布的 GUID key,而 instance_of 边指向的是文件内局部 node id,两者语义不同(源码注释专门说明了这一点)。
  • SECTION:v1 阶段直接拍平(flatten),把 SECTION 的子节点按页面子节点处理,SECTION 本身不建模。
  • 其他顶层类型:v1 忽略。

边权重约定为:contains = 1.0,variant_of = 0.9,instance_of = 0.8。所有节点初始 summary 是名称占位符,complexity"simple",等待 Phase 2 的 LLM 充实。该映射逻辑有完整测试覆盖,parse-document.test.ts 用一个包含 Onboarding 页面、Login 屏幕、Button 组件集的双页文档,验证了全部节点 id 与四类边的生成,以及 componentKey 记录的是发布 GUID 而非局部 id。

3.4 设计令牌抽取:只收已发布样式

extractTokens 实现设计令牌的确定性抽取,有两个刻意的边界设计:

  1. 令牌节点只来自已发布样式(published styles),即 fetchStyles() 返回的 meta.styles。这是一个有界的集合,防止未发布的本地样式把节点数撑爆。样式类型通过映射表归一为令牌种类:FILL → colorTEXT → typeEFFECT → effectGRID → grid,未知类型回落到 color。令牌 id 形如 token:{kind}:{name-slug}(名称经小写、非字母数字转连字符的 slug 化处理)。
  2. uses_token 边的归属策略:Figma 中样式通常应用在深层叶子图层(TEXT/RECTANGLE 等)而非浅层结构节点上。解析器在遍历文档树时维护"最近结构祖先"(screen/component/componentSet/instance/page),把样式使用归属到该祖先,确保真实的消费者关系不丢失。文件内局部样式 id(如 "2:1")还会通过文档顶层 styles 映射桥接到已发布的 style key,再与令牌节点匹配;每条 (消费者, 令牌) 使用关系去重后以权重 0.5 的 uses_token 边输出。

3.5 屏幕缩略图与 scan-manifest.json

结构解析完成后,脚本只对 screen 类型的节点批量预取 PNG 缩略图(format=png&scale=1),通过 applyScreenThumbnails 把预签名 URL 写入对应节点的 figmaMeta.thumbnailUrl;缩略图是可选能力,渲染失败不会让扫描失败。

最终写入 scan-manifest.json 的结构为:

{
  "project": {
    "name": "(Figma 文档名)",
    "languages": ["figma"],
    "frameworks": [],
    "description": "Figma design file: <文档名>",
    "analyzedAt": "(ISO 时间戳)",
    "gitCommitHash": ""
  },
  "fileKey": "(文件 key)",
  "figmaVersion": "(文档 version)",
  "nodes": [ "…结构节点 + 令牌节点…", ],
  "edges": [ "…contains / instance_of / variant_of / uses_token…", ]
}

控制台输出形如 Figma scan: N pages, M screens, K components, S sets, I instances, T tokens,技能要求把这组计数转述给用户。

四、Phase 2 —— LLM 语义充实

读取 scan-manifest.json 后,把节点按约 15 个一批、尽量按页面聚合地分批,每批派发一个基于 design-analyzer 定义的子代理。派发给子代理的输入包含:该批节点(idtypenamefigmaMeta、子节点名称、令牌使用情况)、全部现有节点 id 列表(供 related 边精确引用)、中间目录 $INTERMEDIATE_DIR,以及用于输出命名的批号。若用户提供了 --language,还需追加 $LANGUAGE_DIRECTIVE(复用 /understand 的语言指令文本)。子代理将结果写入 analysis-batch-<N>.json

design-analyzer 的职责边界被定义得非常严格,这是整个技能"确定性打底、LLM 只做增量"设计哲学的核心:

  • 只做语义层:为每个节点产出 1–2 句 summary(说明该屏幕/组件用来做什么,而非描述像素;令牌则说明其角色,如"用于 CTA 的主品牌色")和 2–5 个小写 tags(功能域、角色、状态,如 authentryctaempty-state)。
  • 允许但从严:仅可输出保守的 related 边,且只限于名称/结构明显同属一个功能流的情况(如同一 onboarding 流程的两个屏幕);一批约 15 个节点,预期产出约 15 条充实与 0–8 条 related 边。
  • 禁止项:不得输出任何 page/screen/component/componentSet/instance/token 节点(它们已存在),不得重发结构边(containsinstance_ofvariant_ofuses_token),related 边必须使用精确的现有 id。

输出格式示例(子代理实际写入的文件内容):

{
  "nodes": [
    { "id": "screen:1:1", "summary": "The sign-in screen where returning users authenticate.", "tags": ["auth", "entry"] }
  ],
  "edges": [
    { "source": "screen:1:1", "target": "screen:1:5", "type": "related", "direction": "forward", "weight": 0.5, "description": "Both part of the sign-in flow" }
  ]
}

并发与容错方面,SKILL.md 规定最多 5 批并发;某批失败时仅记录警告并继续——因为确定性 manifest 本身就是扎实的基础,LLM 批是增强层而非依赖项。

五、Phase 3 —— 合并(Merge)

合并阶段运行捆绑脚本:

node <SKILL_DIR>/figma-merge.mjs "$PROJECT_ROOT"

figma-merge.mjs 读取 scan-manifest.json 与所有匹配 analysis-batch-*.json 的分析文件,调用 core 包的 mergeDesignGraph 完成合并,成功后写出 knowledge-graph.jsonmeta.json,并把统计信息(节点数、边数、图层数、tour 步骤数)与非 auto-corrected 级别的校验问题打印出来。mergeDesignGraph 的内部流程分五步:

  1. 索引 manifest 节点(克隆以便就地充实)。
  2. 应用 LLM 充实:按 id 把批分析中的 summary/tags 补丁回对应节点——不在 manifest 中的 id 直接忽略,从机制上兜住了子代理"不得发明节点"的规则;分析批的 edgesrelated 边)则追加到边列表。
  3. 构建图层(layers):基于 contains 边建立父子映射,对每个节点沿父链向上(带环保护)找到所属 page;component/componentSet/token 三类节点不随页面,而是统一收进一个 Design System 图层;找不到页面归属的节点落入 layer:unscoped。每个页面生成 layer:{pageId} 图层并附页面名描述。
  4. 构建导览(tour):Design System 图层排第一步,随后按页面顺序每页一步;每步 nodeIds 截取前 8 个以保持视图可读。
  5. 组装 + 校验 + 重挂 kind:组装出 { version: "1.0.0", kind: "design", project, nodes, edges, layers, tour },走统一的 validateGraph 校验(该函数会剥掉 kind 字段),校验通过后把 kind: "design" 重新挂回。

这些行为都有单元测试佐证:merge.test.ts 验证了产出图谱的 kinddesign、屏幕归入其页面图层而组件/令牌归入 Design System 图层、充实按 id 精确回填、tour 首步为 Design System。

合并脚本写出的 meta.json 也值得注意:

{
  "lastAnalyzedAt": "(ISO 时间戳)",
  "gitCommitHash": "",
  "figmaVersion": "(来自 manifest 的 figmaVersion)",
  "version": "1.0.0",
  "analyzerizedFiles": null
}

其中 figmaVersion 正是 Phase 1 增量跳过的比对依据,形成"扫描写版本 → 下次扫描比版本 → 版本合并进 meta"的闭环(meta.jsonanalyzerizedFiles 字段实为 analyzedFiles,取值是最终节点总数)。

六、Phase 4 —— 保存与启动

合并成功后:

  1. 清理中间文件(保留 manifest)

    INTER="$UA_DIR/intermediate"
    find "$INTER" -mindepth 1 -maxdepth 1 -not -name 'scan-manifest.json' -exec rm -rf {} +
    

    保留 scan-manifest.json 是有意的:它是确定性结构层的快照,analysis-batch-*.json 等 LLM 中间产物则可安全清除。

  2. 汇报摘要:项目名、各节点类型计数、各类型边计数、图层、tour 步骤数,以及最终图谱路径 $UA_DIR/knowledge-graph.json

  3. 自动启动 Dashboard:通过调用 /understand-dashboard 技能把设计图谱渲染出来。Dashboard 对设计图谱的原生支持体现在 NodeInfo.tsxFigmaThumbnail 组件——当节点带有 figmaMeta.thumbnailUrl 时,节点信息面板会渲染屏幕预览图;设计边类型(instance_of/variant_of/uses_token)则回落到自动生成的可读标签。

七、Schema 层如何为"design"图谱让路

设计图谱能与代码库图谱、知识图谱共用同一套 schema 与 Dashboard,靠的是 core 包 schema 中的按 kind 分派的别名表。在 schema.ts 中:

  • kind 字段的枚举为 codebase | knowledge | design
  • DESIGN_NODE_TYPE_ALIASES 仅在 kind === "design" 时生效,把 frame/artboardscreencanvaspagecomponent_set/variant_set/componentsetcomponentSetdesign_token/styletoken 等别名归一为规范节点类型;
  • 对应的 DESIGN_EDGE_TYPE_ALIASESinstantiatesinstance_ofvariantvariant_ofstyled_by/applies_tokenuses_token
  • 反向亦有 NON_DESIGN_NODE_TYPE_ALIASES/NON_DESIGN_EDGE_TYPE_ALIASES:非 design 图谱里 page 归一为 articleinstance_of 归一为 exemplifies——注释解释了原因:sanitizer 无法区分"wiki 页面"和"Figma 页面",由图谱的 kind 决定哪张别名表生效。

这套机制保证同一套 validateGraph 管道可以安全地承载三种图谱类型,而 Figma 语义(page/screen/token)不会泄漏污染代码库或知识图谱。

八、适用边界与注意事项

从源码与测试的边界设定,可以归纳出当前版本(v1)的明确限制,实际使用时应了解:

  • 在线依赖:需要有效的 FIGMA_TOKEN 且能访问 api.figma.com;这与 /understand 的全离线定位不同。
  • v1 解析范围:SECTION 只拍平不建模,页面下其他顶层类型被忽略;令牌只收录已发布样式,未发布的本地样式不会成为令牌节点。
  • 缩略图时效:屏幕缩略图 URL 是预签名 URL,数小时后过期;重新运行技能会走 UP_TO_DATE 路径自动刷新(见 3.2 节),但不重跑时旧图可能失效。
  • LLM 批失败不阻塞:个别批次失败只降级该批节点的语义质量,结构层完整不受影响。
  • 强制重建:文件版本未变但希望全量重建时,设置 UNDERSTAND_FIGMA_FORCE=1

小结

/understand-figma 的设计可以概括为"确定性解析打底、LLM 语义增强、统一 schema 收口":figma-scan.mjsparseDocument + extractTokens 产出可复现的 manifest,design-analyzer 子代理在严格约束下只做摘要、标签与保守 related 边,mergeDesignGraph 按页面与 Design System 生成图层和导览,最终产出与代码库图谱同构、可在同一个 Dashboard 中浏览的 kind:"design" 图谱。对维护者而言,最值得关注的扩展点是 parse-document.ts 的类型分派(handlePageChild)与 tokens.ts 的样式归属逻辑——它们决定了哪些 Figma 结构会进入图谱;对使用者而言,掌握 FIGMA_TOKEN 配置、UNDERSTAND_FIGMA_FORCE=1 强制重建与 UP_TO_DATE 增量路径这三点,就足以日常运转这条流水线。

登录后查看全文
热门项目推荐
相关项目推荐