Koobiq 아이콘 Figma 감사 (Audit)
이 스킬은 Koobiq 아이콘 Figma 파일의 아이콘 컴포넌트가 라이브러리의 구조적 규칙을 따르는지 확인하여 figma:sync (@figma-export 파이프라인)이 깨끗하고 정확한 SVG를 생성하도록 합니다. Figma에서 문제를 찾는 것은 내보내기 후에 발견하는 것보다 훨씬 저렴합니다. 내보내기는 발견한 것을 자동으로 변환하기 때문입니다. 잘못된 레이어 이름, 추가 노드 또는 그리드에서 벗어난 좌표는 오류 없이 깨진 SVG가 됩니다.
기본적으로 읽기 전용 감사입니다: 검사한 다음 특정하고 실행 가능한 결과와 함께 명확한 합격/불합격 체크리스트를 보고합니다. 사용자가 명시적으로 수정을 요청하는 경우에만 Figma 파일을 변경합니다("수정 제안" 참조).
전제 조건
-
Figma MCP가 연결되어 있어야 합니다. 이 스킬은
get_metadata(구조 개요),use_figma(Plugin API를 통한 정밀 검사),get_screenshot(시각적 온전성 검사)을 통해 이를 구동합니다. -
use_figma호출 전에figma-use스킬을 로드하세요 — Plugin 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- 이 파일 키는 진실 공급원(source of truth)입니다 —
config.mjs와 일치합니다.packages/icons/README.md의 "아이콘 그리기" 링크는 다른, 잘못된 파일을 가리킵니다 — 무시하고 위 키를 신뢰하십시오. nodeId없이get_metadata를 호출하면 첫 번째 페이지(ICONS, 아이콘 컴포넌트 없음)만 나열합니다 — 크기 페이지를 열거하지 않습니다. 페이지 검색에 사용하지 말고 위의 노드 ID를 사용하십시오.
- 이 파일 키는 진실 공급원(source of truth)입니다 —
파일에 접근할 수 없는 경우
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 };
가드가 예외를 던지면 해당 결과를 폐기하고 읽기를 다시 실행하십시오. "누락" 주장(예: "24px 변형 없음")은 해당 페이지의 가드된 읽기 또는 노드 ID 조회에 근거해야 합니다. 예외를 던진 읽기 또는 확인하지 않은 페이지의 부재를 보고하지 마십시오.
1단계 — 대상 확인
사용자는 다음 중 하나를 제공할 수 있습니다:
- Figma 노드 URL 또는 ID (예:
node-id=44731-6643) → 해당 노드를 정확히 검사한 다음, 크로스-변형 확인을 위해 이름으로 해당 노드의 형제 크기 변형도 조회합니다. - 아이콘 이름 (예:
floating-window또는floating-window_16) → 모든 아이콘 페이지에서 모든 크기 변형을 찾아 모두 확인합니다. 새 아이콘은 종종 게시되지 않았고 이름조차 없을 수 있으므로, 이름 일치가 실패하면 관련 페이지의 컴포넌트를 나열하고 사용자에게 노드를 가리키거나 노드 ID로 검사하도록 요청하십시오.
페이지 이름은 크기 접미사와 같습니다. 크기 스윕(Size sweep): 요청된 모든 이름에 대해 다섯 페이지(16 / 24 / 32 / 48 / 64)를 모두 확인하십시오. 아이콘이 사용자가 언급하지 않은 크기로 존재할 수 있기 때문입니다. 전제 조건 표의 페이지 노드 ID를 사용하고 페이지당 하나의 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. 노드 유형 및 이름
-
노드는
FRAME,GROUP또는INSTANCE가 아닌COMPONENT(또는 크기 변형의COMPONENT_SET)여야 합니다. 컴포넌트만 내보냅니다. -
첫 문자 — 먼저 확인하십시오. 이름의 의미를 바꿉니다:
- **
.또는_**로 시작 → 내부 컴포넌트, 의도적으로 내보내지 않음. 내보내기의filterComponent가.figmaexportrc.mjs에서^[._]와 일치하는 이름을 삭제합니다. 이는 버그가 아니라 설계상입니다 — 깨진 아이콘으로 감사하지 마십시오. 사용자가 이 노드가 내보내지길 기대한 경우에만 제기하십시오(수정은 선행./_없이 이름을 바꾸는 것입니다). - **
-**로 시작 → 유효하지 않음, 허용되지 않음. 실패로 표시합니다. - 숫자로 시작 → 허용되지만 권장되지 않음. 여전히 내보내지만(예:
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접미사 = 외곽선(outline) 변형.-o가 없는 이름은 채워진(filled) 변형입니다.cloud_24/cloud-o_24또는cloud-arrow-up_24/cloud-arrow-up-o_24와 같은 쌍은 두 가지 스타일의 하나의 아이콘입니다. 이는 유효하며 충돌이나 중복이 아닙니다. 이 때문에 이름을 바꾸라고 요청하지 마십시오. -
숫자 접미사는 컴포넌트의 실제 크기와 같아야 합니다(규칙 B).
..._16이 24×24이면 버그입니다.
B. 지오메트리 및 레이아웃
- 자동 레이아웃 없음:
layoutMode === "NONE". 자동 레이아웃은 자식을 재배치하고 내보내기를 손상시킵니다. - 고정 및 정사각형:
width === height === 크기. 크기는 픽셀 그리드에 정렬된 정수 픽셀 수여야 합니다. 분수 컴포넌트 크기는 아이콘이 그리드에 그려지지 않았음을 의미합니다. - 클립 콘텐츠 OFF:
clipsContent === false. "클립 콘텐츠" 토글이 비활성화되어야 합니다. 아이콘의 모든 크기 변형에 대해 동일해야 합니다(꺼짐). 켜져 있으면 프레임 경계를 벗어나는 아트웍이 내보내기에서 자동으로 잘려 클리핑되거나 잘린 아이콘이 생성됩니다.
C. 루트 구조 (대부분의 실제 버그가 숨어 있는 곳)
- 단색 아이콘: 정확히 하나의 루트 자식 — 이름이 **
shape**인VECTOR. - 두 가지 색상 아이콘: 정확히 두 개의 루트 자식 —
shape(기본) 및shape-2(보조). 그 외에는 없습니다. - 컴포넌트 루트에 다른 노드 없음: 잘못된 프레임, 그룹, 사각형, 슬라이스, 중복 벡터 또는 숨겨진 도우미 레이어가 없습니다. 각 추가 노드는 내보낸 SVG에 포함됩니다.
- 각 모양의 제약 조건은 두 축 모두
SCALE(horizontal: "SCALE",vertical: "SCALE")이어야 아트웍이 프레임과 함께 크기 조정됩니다.- 심각도: 경고(위생), 내보내기 차단 아님. 제약 조건은 Figma에서 컴포넌트 크기 조정 방식을 제어하며 내보낸 경로 데이터는 제어하지 않습니다. 다른 승인된 Figma 편집과 함께 수정하십시오. 인스턴스와 크기 조정이 이에 의존하기 때문입니다.
- 이상적으로 각 모양은 단일 평면화된 경로여야 합니다(저장소 README "아이콘 그리기" 단계 참조: 외곽선, 평면화, Non-zero 채우기 규칙). 여러 하위 경로 또는 평면화되지 않은 그룹을 경고로 표시합니다.
D. 채우기 색상 계약
계약(저장소 README "SVG 색상 영역 가이드"):
- 기본 영역
shape→ 검정#21222C. 내보내기에서fill을 제거하여 SVG가currentColor를 상속하도록 합니다. - 보조 영역
shape-2→ 빨강#E21D03. 내보내기에서fill="currentColor"로 다시 씁니다.
실제 현실: 프로덕션 파일에서 기본 채우기는 공유 Icon/Contrast 변수에 바인딩되며, 이는 0.85 페인트 불투명도로 #000819로 해석됩니다 — 리터럴 #21222C가 아닙니다. 따라서:
- 참조 키:
91eb54e0eae961c540c994192c4eed18e4ea1825—🟥 koobiq · components 19의Icon/Contrast(컬렉션 키c78fab428cf2003c9aa53d86b132a2fa95acb2e2). - 기본 페인트의 바운드 변수 키를 해당 참조(
(await figma.variables.getVariableByIdAsync(boundId)).key)와 비교하고 페인트 불투명도가 0.85인지 확인하십시오. 이름을 비교하지 마십시오:Icon/Contrast는 여러 라이브러리와 컬렉션에 존재합니다. 아이콘에서 허용되는 유일한 변수는 키로 이 변수입니다. 다른 키를 가진 같은 이름의 변수(다른 라이브러리 또는 컬렉션에서)는 경고가 아닌 실패입니다. 수정: 동일한 라이브러리 컬렉션의 참조 키 변수에 다시 바인딩합니다. - 보조
shape-2는 단색 리터럴#E21D03이거나(내보내기가 리터럴 16진수로 일치시킴 —.figmaexportrc.mjs의secondaryZoneColorHex),🟥 koobiq · components 19의Icon/Error(키e736157dcefc2a3e016ec2ddd41a7dc8cf8a5bc6)에 키로 바인딩되어야 합니다. 변수 케이스의 경우 페인트 불투명도는 확인되지 않습니다. 다른 채우기는 실패합니다. - 형제와 일치하지 않는 일반 리터럴 색상인 기본 채우기 또는
#E21D03이 아닌 보조 채우기는 잘못된fill과 함께 내보내집니다.
</original_message>