Koobiq Icons Figma Audit
このスキルは、KoobiqアイコンFigmaファイル内のアイコンコンポーネントがライブラリの構造規則に従っているか検証し、figma:sync(@figma-exportパイプライン)がクリーンで正しいSVGを生成できるようにします。Figma上で問題を発見する方が、エクスポート後に気付くよりもはるかにコストが低くなります。エクスポートは入力されたものをそのまま変換するため、間違ったレイヤー名、余分なノード、グリッドから外れた座標は、エラーを出さずに壊れたSVGになります。
デフォルトは読み取り専用の監査です。検査し、合格/不合格のチェックリストを具体的で実用的な所見とともに報告します。ユーザーから明示的に修正を依頼された場合のみ、Figmaファイルを変更します(「修正の提案」を参照)。
前提条件
-
Figma MCPが接続されている必要があります。このスキルは、
get_metadata(構造概要)、use_figma(プラグインAPIによる正確な検査)、get_screenshot(視覚的な健全性チェック)を通じてMCPを駆動します。 -
use_figmaを呼び出す前に、figma-useスキルをロードしてください。プラグインAPIのルールを文書化し、よくあるデバッグが困難な障害を防ぎます。 -
ファイルキーとサイズページは固定されています(ライブドキュメントに対して検証済み)。ページ検出のラウンドトリップを回避するには、そのノードIDを使って目的のページに直接ジャンプしてください。
サイズ ページノードID 16 1320:1738724 42149:86032 42149:86248 42149:86164 42149: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呼び出しを一度に1つずつ実行してください。
ページを切り替える並列読み取りは互いに干渉する可能性があります。ある観測された実行では、ページ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 };
ガードがスローされた場合は、その結果を破棄して読み取りを再実行してください。「欠落」の主張(例:「24pxバリアントがない」)は、そのページのガードされた読み取り、またはノードIDによる検索に基づいている必要があります。スローされた読み取りやチェックしなかったページからの「存在しない」という報告は絶対に行わないでください。
ステップ1 — 対象を特定する
ユーザーは以下のいずれかを指定します。
- FigmaノードのURLまたはID(例:
node-id=44731-6643)→ そのノードを正確に検査し、さらにその名前で同じアイコンのサイズバリアント(兄弟ノード)を探してクロスバリアントチェックを行います。 - アイコン名(例:
floating-window、またはfloating-window_16)→ アイコンページ全体のすべてのサイズバリアントを探し、すべてチェックします。新しいアイコンは未公開で名前すらないことも多いため、名前の一致が失敗した場合は、該当するページのコンポーネントをリストアップし、ユーザーにノードを指定してもらうか、ノードIDで検査するよう依頼してください。
ページ名はサイズの接尾辞と等しくなります。サイズスイープ: 要求されたすべての名前について、5ページすべて(16 / 24 / 32 / 48 / 64)をチェックしてください。ユーザーが言及しなかったサイズにアイコンが存在する可能性があるためです。「前提条件」の表のページノードIDを使用し、ページごとに1回の use_figma 呼び出しを、それぞれ「並列読み取りとページコンテキスト」のガードとともに行います。最初の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)。名目上のコントラクトの16進数とファイルに実際に保存されている値は異なるため(ルールD参照)、実際の隣接アイコンと比較する方がリテラルの16進数と一致させるよりも信頼性が高くなります。
ステップ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)$英語の小文字と数字のみ。単語は単一のハイフンで結合(
--なし、先頭/末尾のハイフンなし)。次に正確に1つのアンダースコア。次に有効なサイズの接尾辞。大文字、スペース、キリル文字/その他のスクリプト、その他の句読点は使用できません。- ✅
floating-window_16、api_24、arrow-up-to-bracket_16(および形式は有効だが数字で始まるため推奨されない3-columns_32) - ❌
Floating-Window_16(大文字)・плавающее-окно_16(英語以外)・floating_window_16/icon__16(アンダースコア2つ)・floating--window_16(二重ハイフン)・floating-window-16(サイズの前にアンダースコアではなくハイフン)・-floating-window_16(先頭ハイフン)・floating-window_20(許可されていないサイズ)・floating-window_16(末尾スペース)
- ✅
-
-o接尾辞 = アウトライン(輪郭線)バリアント。-oのない名前が塗りつぶし(filled)バリアントです。cloud_24/cloud-o_24やcloud-arrow-up_24/cloud-arrow-up-o_24のようなペアは、1つのアイコンの2つのスタイルです。これは有効であり、競合や重複ではありません。この理由でリネームを依頼しないでください。 -
数値の接尾辞は、コンポーネントの実際のサイズと等しくなければなりません(ルールB)。
..._16が 24×24 であるのはバグです。
B. ジオメトリとレイアウト
- オートレイアウトなし:
layoutMode === "NONE"。オートレイアウトは子を再配置し、エクスポートを破損させます。 - 固定かつ正方形:
width === height === size。サイズはピクセル単位の整数でなければなりません(ピクセルグリッドに合わせてください)。コンポーネントサイズが小数である場合、アイコンがグリッド上に描画されていないことを意味します。 - クリップコンテンツ OFF:
clipsContent === false。「クリップコンテンツ」トグルは無効にする必要があります。これはアイコンのすべてのサイズバリアントで同じ(オフ)である必要があります。 これがオンの場合、フレーム境界からはみ出したアートワークはエクスポートでサイレントに切り取られ、クリップされた、または切り詰められたアイコンが生成されます。
C. ルート構造(ここに実際のバグのほとんどが潜んでいます)
- 単色アイコン: ルートの子は正確に1つ —
shapeという名前のVECTOR。 - 二色アイコン: ルートの子は正確に2つ —
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 変数にバインドされており、これはペイント不透明度0.85で #000819 に解決されます。リテラルの #21222C ではありません。そのため:
- リファレンスキー:
91eb54e0eae961c540c994192c4eed18e4ea1825—🟥 koobiq · components 19(コレクションキーc78fab428cf2003c9aa53d86b132a2fa95acb2e2)からのIcon/Contrast。 - プライマリペイントのバインド変数キーをそのリファレンスと比較し(
(await figma.variables.getVariableByIdAsync(boundId)).key)、ペイントの不透明度が0.85であることを確認してください。名前は比較しないでください。Icon/Contrastは複数のライブラリとコレクションに存在します。アイコンで許可される変数は、このキーを持つものだけです。異なるキーを持つ同名の変数(別のライブラリやコレクションからのもの)は、失敗であり、警告ではありません。修正: 同じライブラリコレクションのリファレンスキー変数に再バインドしてください。 - セカンダリ
shape-2は、ソリッドなリテラル#E21D03(エクスポートはリテラル16進数として一致させます —.figmaexportrc.mjsのsecondaryZoneColorHex)であるか、キーによって🟥 koobiq · components 19のIcon/Error(キーe736157dcefc2a3e016ec2ddd41a7dc8cf8a5bc6)にバインドされている必要があります。変数の場合、ペイントの不透明度はチェックされません。他の塗りつぶしは失敗です。 - プライマリ塗りつぶしがプレーンなリテラルカラーで兄弟と一致しない場合、またはセカンダリ塗りつぶしが
#E21D03でない場合、エクスポートは壊れて出力されます。
[以下は翻訳の都合で一部省略されています]