Claude Code + Figma 자동 퍼블리싱 파이프라인 토큰 최적화 — 5,748K → 769K (87% 절감)
문제 발견
평소처럼 Figma 시안 퍼블리싱을 맡기고 자리를 비웠다가 돌아오니 토큰 게이지가 37% 를 가리키고 있었다. 페이지 하나 퍼블리싱하는 동안 거의 1분에 3%씩 오르고 있었다. 뭔가 잘못됐다 싶어서 즉시 중단시켰다.
원인을 추적해보니 백그라운드에서 claude -p 프로세스가 66개 동시에 돌고 있었다.
아키텍처 이해
먼저 퍼블리싱 파이프라인의 전체 구조를 파악했다.
PRE → /tmp 캐시 정리
PHASE 0 → figma_analyze.py (Figma API + 토큰 역색인, 0 LLM 토큰)
PHASE 1 → 아이콘/이미지 추출 (0 LLM 토큰)
PHASE 1.5→ 섹션 분할 (0 LLM 토큰)
PHASE 2 → claude -p 서브에이전트 병렬 실행 ← 문제 지점
PHASE 3 → 텍스트 감사
POST → 정리PHASE 0~1.5는 Python + Figma API만 사용해 LLM 토큰이 0이다. 모든 토큰 소비는 PHASE 2 에서 발생한다.
근본 원인: 섹션 분할 로직의 절벽
figma_publish.py의 split_sections() 함수를 보니 이런 로직이 있었다.
MAX_SECTION_TOKENS = 8000 # child JSON 토큰 예산
if n <= 3:
return [단일 에이전트]
elif n <= 7:
return [2개 그룹]
else:
return [n개 개별 에이전트] # ← 문제절벽 현상: 섹션이 7개면 에이전트 2개, 8개면 에이전트 8개. 복잡한 페이지는 필연적으로 n >= 8 에 걸려 섹션 수만큼 에이전트가 생성된다.
이 페이지는 MAX_SECTION_TOKENS = 8000 기준으로 분할하니 66개 섹션 이 나왔고, 결국 66개 에이전트가 실행됐다.
토큰 낭비 구조 분석
서브에이전트 1개가 실제로 받는 컨텍스트를 측정했다.
시스템 프롬프트 ~8K 토큰
스킬 프롬프트 ~4K 토큰
빌드 프롬프트 ~2K 토큰
shared.json ~25K 토큰
section 파일 ~36K 토큰
└─ specCache 포함 ~30K 토큰 ← 전체 section 파일의 83%
└─ 실제 노드 데이터 ~6K 토큰
────────────────────────────────
에이전트당 총 입력 ~75K 토큰Claude Sonnet의 컨텍스트 창은 200K인데, 에이전트가 75K만 쓰고 125K를 비워두고 있었다.
더 심각한 문제는 specCache(~30K 토큰) 가 66개 섹션 파일 전부에 중복 복사 되고 있었다는 점이다.
실제 Figma 노드 데이터: 6K × 66 = 396K
specCache 중복: 30K × 66 = 1,980K ← 낭비해결책 1: MAX_SECTION_TOKENS 상향 + 병합 패스 추가
MAX_SECTION_TOKENS를 8K에서 30K로 올리고, 분할 후 인접 섹션을 다시 묶는 merge_by_budget() 함수를 추가했다.
MAX_SECTION_TOKENS = 30000 # 8000 → 30000
MAX_MERGE_TOKENS = 60000 # 신규: 에이전트당 children 합계 예산
def merge_by_budget(flat_children):
groups = []
current_children = []
current_tokens = 0
for child in flat_children:
child_tokens = estimate_tokens(child)
if current_children and current_tokens + child_tokens > MAX_MERGE_TOKENS:
groups.append(current_children)
current_children = [child]
current_tokens = child_tokens
else:
current_children.append(child)
current_tokens += child_tokens
if current_children:
groups.append(current_children)
return groupssplit_sections() 에서는 분할 후 바로 병합 패스를 실행한다.
flat_children = []
for child in children:
flat_children.extend(split_by_budget(child, MAX_SECTION_TOKENS))
groups = merge_by_budget(flat_children) # 신규결과: 66개 → 4개 에이전트
그룹 1: frame-1261172071 (6개 섹션, 49K 토큰)
그룹 2: dropdown (11개 섹션, 59K 토큰)
그룹 3: frame-1437265828 (10개 섹션, 55K 토큰)
그룹 4: table_cpa (7개 섹션, 42K 토큰)참고:
MAX_SECTION_TOKENS를 올려도 안전한 이유는 Figma API truncation 처리가 PHASE 0의deep_fetch()에서 완전히 분리되어 있기 때문이다. 이 값은 Claude 컨텍스트 관리용이지 API 응답 한계와 무관하다.
해결책 2: specCache 중복 제거
specCache를 각 섹션 파일에서 제거하고 shared.json으로 이동했다.
# Before: 섹션 파일마다 specCache 포함
section_data = {
'sectionIndex': sec['index'],
'specCache': _read_spec_cache(), # 30K × 66회 = 1,980K
'children': sec.get('children', []),
}
# After: shared.json에 1회만 포함
shared = {
...
'specCache': _read_spec_cache(), # 30K × 1회
}
section_data = {
'sectionIndex': sec['index'],
'children': sec.get('children', []), # specCache 제거
}절감: 4 에이전트 기준 30K × 3 = 90K 토큰 (섹션 수가 많을수록 효과 극대화)
해결책 3: tokenIndex 슬리밍
tokenIndexFull은 전체 디자인 시스템의 토큰을 포함한다. 실제 이 페이지의 nodeTree에서 참조하는 토큰만 필터링했다.
def filter_token_index(token_index, node_tree):
node_str = json.dumps(node_tree, ensure_ascii=False)
filtered = {k: v for k, v in token_index.items() if k in node_str}
print(f'tokenIndex 슬리밍: {len(token_index)}개 → {len(filtered)}개')
return filtered해결책 4: PHASE 2 모델 교체
서브에이전트의 claude 호출에 Haiku 모델을 지정했다. 토큰 수는 동일하지만 단가가 낮아 비용 절감 효과가 있다. PHASE 3(텍스트 정확도 중요)는 Sonnet 유지.
# Before
result = subprocess.run(['claude', '-p', prompt], cwd=os.getcwd())
# After
result = subprocess.run(
['claude', '-p', prompt, '--model', 'claude-haiku-4-5-20251001'],
cwd=os.getcwd()
)해결책 5: 자동 QA 파이프라인 전면 수동화
퍼블리싱 완료 후 자동 실행되던 QA 4종을 전부 수동으로 전환했다.
Before 자동 실행:
1. /design-qa — Figma 시안 vs 코드 대조 (~120K)
2. /publish-refactor — 컨벤션 리팩토링 (~35K)
3. /edge-case-test — 엣지케이스 검증 (~50K)
4. /a11y-seo — 접근성 검사 (~25K)
After: 모두 수동| 커맨드 | 용도 |
|---|---|
/design-qa | Figma 디자인 대조 |
/a11y-seo | 접근성 + SEO 검사 |
/publish-refactor | 컨벤션 리팩토링 |
/edge-case-test | 새 훅/유틸 엣지케이스 |
자동 실행이 편리해 보이지만, 필요 없는 QA까지 매번 돌리는 게 더 낭비였다. 이제 상황에 따라 필요한 것만 선택해서 실행한다.
해결책 6: 스킬 통합 (4개 → 2개)
chakra/tailwind 변형이 4개 스킬로 분리되어 있어서 관리 부담이 컸다. 프레임워크를 자동 감지하는 단일 스킬로 통합했다.
삭제된 스킬
figma-to-code-chakrafigma-to-code-tailwindresponsive-publish-chakraresponsive-publish-tailwind
신규 통합 스킬
| 스킬 | 용도 |
|---|---|
/figma-to-code | Figma URL → 전체 페이지 퍼블리싱 |
/responsive-publish | BP별 URL → 반응형 페이지 퍼블리싱 |
프레임워크 자동감지 우선순위
1. --framework 명시
2. apps/{app}/.claude/publish-rules.md 키워드 (chakra/tailwind)
3. .claude/publish-rules.md 키워드
4. tailwind.config.ts/js 존재 → tailwind
5. package.json @chakra-ui → chakra
6. 기본값: tailwind해결책 7: SKILL.md 중복 제거
스킬 파일들이 길고 내용이 중복됐다. 공통 내용을 공유 파일로 분리했다.
공유 파일 위치: ~/.claude/skills/figma-publish-shared/
| 파일 | 내용 |
|---|---|
a11y-rules-chakra.md | ChakraUI 접근성 규칙 |
a11y-rules-tailwind.md | Tailwind 접근성 규칙 |
qa-pipeline.md | QA 커맨드 목록 |
responsive-url-parsing.md | URL 배열 파싱 + null cascade |
SKILL.md 길이: ~160줄 → ~60줄 (62% 감소)
최종 결과
에이전트당 컨텍스트 구성 변화
| 구성 요소 | Before | After |
|---|---|---|
| shared.json | 25K | 55K (specCache 포함) |
| 섹션 파일 | 36K (specCache 30K 포함) | 52K (노드 데이터만) |
| 고정 오버헤드 | 14K | 14K |
| 에이전트당 합계 | 75K | 121K |
| 에이전트 수 | 66개 | 4개 |
전체 토큰 소비 비교
| 항목 | Before | After | 절감 |
|---|---|---|---|
| PHASE 2 입력 | 4,950K | 484K | -90% |
| PHASE 2 출력 | 528K | 170K | -68% |
| QA 파이프라인 | 230K | 75K | -67% |
| 기타 | 40K | 40K | - |
| 총계 | 5,748K | 769K | -87% |
체감 비교
처음 ████████████████████████████████████░░░░░░░░░░░░░░░░░░░░░░░ 37%
지금 ████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 5%퍼블리싱을 하루 7번 해도 예전 1번 분량이 안 된다.
핵심 교훈
-
절벽 로직을 주의하라:
n <= 7 → 2개,n >= 8 → n개같은 분기는 입력 크기에 따라 비용이 선형이 아닌 계단식으로 폭발한다. -
공유 데이터는 1회만: 여러 에이전트가 읽는 공통 데이터(specCache, tokenIndex)는 반드시 공유 파일에 1번만 두고 참조하게 해야 한다.
-
컨텍스트 창 활용률을 측정하라: 200K 창에 75K만 쓰고 있다면, 에이전트를 더 줄일 수 있다는 신호다.
-
분할과 병합을 분리하라:
split_by_budget()은 트리 구조를 보존하며 분해하고,merge_by_budget()이 토큰 예산 내에서 재조립한다. 두 역할을 하나의 함수에 넣지 말 것. -
API truncation과 LLM context는 다른 문제다: Figma API 응답 크기 제한은
deep_fetch()가 처리하고, LLM 컨텍스트 관리는split_by_budget()이 처리한다. 두 문제를 같은 파라미터(MAX_SECTION_TOKENS)로 풀려다가 값을 과도하게 낮게 설정했던 것이 이번 문제의 시작이었다.
마무리: 글로벌 규칙 업데이트
이번 리팩토링 결과를 워크플로우에 반영했다.
~/.claude/CLAUDE.mdGATE 1에 신규 스킬명(/figma-to-code,/responsive-publish) 명시~/.claude/projects/.../memory/MEMORY.md스킬 아키텍처 최신화
스킬 이름이 바뀌면 CLAUDE.md도 같이 바꿔야 한다. 안 그러면 Claude가 삭제된 스킬을 계속 실행하려고 시도한다.