본문으로 건너뛰기

Claude Code + Figma 자동 퍼블리싱 파이프라인 토큰 최적화 — 5,748K → 769K (87% 절감)

·11 min read

문제 발견

평소처럼 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.pysplit_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 groups

split_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-qaFigma 디자인 대조
/a11y-seo접근성 + SEO 검사
/publish-refactor컨벤션 리팩토링
/edge-case-test새 훅/유틸 엣지케이스

자동 실행이 편리해 보이지만, 필요 없는 QA까지 매번 돌리는 게 더 낭비였다. 이제 상황에 따라 필요한 것만 선택해서 실행한다.


해결책 6: 스킬 통합 (4개 → 2개)

chakra/tailwind 변형이 4개 스킬로 분리되어 있어서 관리 부담이 컸다. 프레임워크를 자동 감지하는 단일 스킬로 통합했다.

삭제된 스킬

  • figma-to-code-chakra
  • figma-to-code-tailwind
  • responsive-publish-chakra
  • responsive-publish-tailwind

신규 통합 스킬

스킬용도
/figma-to-codeFigma URL → 전체 페이지 퍼블리싱
/responsive-publishBP별 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.mdChakraUI 접근성 규칙
a11y-rules-tailwind.mdTailwind 접근성 규칙
qa-pipeline.mdQA 커맨드 목록
responsive-url-parsing.mdURL 배열 파싱 + null cascade

SKILL.md 길이: ~160줄 → ~60줄 (62% 감소)


최종 결과

에이전트당 컨텍스트 구성 변화

구성 요소BeforeAfter
shared.json25K55K (specCache 포함)
섹션 파일36K (specCache 30K 포함)52K (노드 데이터만)
고정 오버헤드14K14K
에이전트당 합계75K121K
에이전트 수66개4개

전체 토큰 소비 비교

항목BeforeAfter절감
PHASE 2 입력4,950K484K-90%
PHASE 2 출력528K170K-68%
QA 파이프라인230K75K-67%
기타40K40K-
총계5,748K769K-87%

체감 비교

처음  ████████████████████████████████████░░░░░░░░░░░░░░░░░░░░░░░  37%
지금  ████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░   5%

퍼블리싱을 하루 7번 해도 예전 1번 분량이 안 된다.


핵심 교훈

  1. 절벽 로직을 주의하라: n <= 7 → 2개, n >= 8 → n개 같은 분기는 입력 크기에 따라 비용이 선형이 아닌 계단식으로 폭발한다.

  2. 공유 데이터는 1회만: 여러 에이전트가 읽는 공통 데이터(specCache, tokenIndex)는 반드시 공유 파일에 1번만 두고 참조하게 해야 한다.

  3. 컨텍스트 창 활용률을 측정하라: 200K 창에 75K만 쓰고 있다면, 에이전트를 더 줄일 수 있다는 신호다.

  4. 분할과 병합을 분리하라: split_by_budget()은 트리 구조를 보존하며 분해하고, merge_by_budget()이 토큰 예산 내에서 재조립한다. 두 역할을 하나의 함수에 넣지 말 것.

  5. API truncation과 LLM context는 다른 문제다: Figma API 응답 크기 제한은 deep_fetch()가 처리하고, LLM 컨텍스트 관리는 split_by_budget()이 처리한다. 두 문제를 같은 파라미터(MAX_SECTION_TOKENS)로 풀려다가 값을 과도하게 낮게 설정했던 것이 이번 문제의 시작이었다.


마무리: 글로벌 규칙 업데이트

이번 리팩토링 결과를 워크플로우에 반영했다.

  • ~/.claude/CLAUDE.md GATE 1에 신규 스킬명(/figma-to-code, /responsive-publish) 명시
  • ~/.claude/projects/.../memory/MEMORY.md 스킬 아키텍처 최신화

스킬 이름이 바뀌면 CLAUDE.md도 같이 바꿔야 한다. 안 그러면 Claude가 삭제된 스킬을 계속 실행하려고 시도한다.