@su-record/vibe 2.8.36 → 2.8.38

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,32 +1,34 @@
1
1
  ---
2
2
  name: vibe.figma
3
- description: Figma design to code — 시각 기반 퍼즐 조립 방식
3
+ description: Figma design to code — 트리 기반 구조적 코드 생성
4
4
  triggers: []
5
5
  tier: standard
6
6
  ---
7
7
 
8
- # vibe.figma — Visual Puzzle Assembly
8
+ # vibe.figma — Structural Code Generation
9
9
 
10
10
  ## 핵심 원칙
11
11
 
12
12
  ```
13
- 스크린샷이 정답이다. Figma 데이터는 재료일 뿐이다.
13
+ Figma 트리가 코드의 원천이다. 스크린샷은 검증용이다.
14
14
 
15
- Figma 트리 구조를 HTML로 변환하지 않는다 (이 방식은 실패한다)
16
- 스크린샷을 보고 "무엇을 만들어야 하는지" 파악한다
17
- Figma 데이터(이미지, 색상, 수치)를 정확한 재료로 사용한다
18
- 사람 개발자처럼: 디자인 보고 → 에셋 받고 → 만들면서 비교
15
+ Figma Auto Layout CSS Flexbox 1:1 기계적 매핑
16
+ Figma CSS 속성 SCSS 직접 변환 (추정 없음)
17
+ Claude는 시맨틱 판단만: 태그 선택, 컴포넌트 분리, 인터랙션
18
+ 스크린샷은 생성이 아닌 검증에만 사용
19
19
  ```
20
20
 
21
21
  ## 금지 사항
22
22
 
23
23
  ```
24
- Figma 레이어 트리를 그대로 div 구조로 변환
24
+ 스크린샷을 보고 CSS 추정 (범용 LLM의 약점)
25
+ ❌ Figma 레이어를 무분별하게 div soup로 변환
25
26
  ❌ CSS로 이미지 재현 (gradient/shape으로 그림 그리기)
26
27
  ❌ 이미지 다운로드 없이 코드 생성 진행
27
28
  ❌ placeholder / 빈 src="" 남기기
28
- 색상·크기를 추정 (재료함에 정확한 값이 있음)
29
+ tree.json에 없는 CSS 값을 추정하여 작성
29
30
  ❌ 컴포넌트 파일 안에 <style> 블록 / 인라인 style=""
31
+ ✅ tree.json의 CSS 속성을 SCSS에 직접 매핑
30
32
  ✅ 외부 SCSS 파일에만 스타일 작성
31
33
  ```
32
34
 
@@ -34,11 +36,12 @@ tier: standard
34
36
 
35
37
  ```
36
38
  /vibe.figma
37
- → Phase 0: Setup (스택 감지, 디렉토리 생성)
39
+ → Phase 0: Setup (스택 감지, 디렉토리 생성, 기존 자산 인덱싱)
38
40
  → Phase 1: Storyboard (스토리보드 → 레이아웃 + 컴포넌트 + 기능 정의)
39
- → Phase 2: 재료 확보 (디자인 URL → 스크린샷 + 이미지 + CSS + 텍스트)
40
- → Phase 3: 퍼즐 조립 (스크린샷 보면서 Phase 1 컴포넌트에 디자인 입히기)
41
- → Phase 4: 검증 루프 (빌드스크린샷비교 → 수정 → 반복)
41
+ → Phase 2: 재료 확보 (디자인 URL → 트리 + 이미지 + 스크린샷)
42
+ → Phase 3: 구조적 코드 생성 (트리 HTML+SCSS 매핑 + 시맨틱 보강)
43
+ → Phase 3.5: 컴파일 게이트 (tscbuilddev 확인)
44
+ → Phase 4: 시각 검증 루프 (렌더링 vs 스크린샷 비교 → 수정)
42
45
  ```
43
46
 
44
47
  ---
@@ -64,9 +67,99 @@ tier: standard
64
67
  - public/images/{feature}/ (또는 static/images/{feature}/)
65
68
  - styles/{feature}/ (layout/, components/ 하위)
66
69
 
67
- 5. 기존 컴포넌트 스캔:
70
+ 5. 기존 컴포넌트 스캔 + 인덱싱:
68
71
  - Glob "components/**/*.vue" or "components/**/*.tsx"
69
72
  - 재사용 가능한 컴포넌트 목록 수집 (GNB, Footer, Button 등)
73
+
74
+ 5-1. 컴포넌트 상세 인덱싱 (50개 이하 스캔):
75
+ 대상 파일 수집 (우선순위 순서, 합산 50개 이내):
76
+ barrel file index.ts → components/ui/ → components/common/ → components/shared/ → 나머지
77
+ 각 디렉토리 내에서는 1-depth 파일만 (하위 재귀 탐색 안 함)
78
+
79
+ 각 파일에 대해 2단계 추출:
80
+ a. Grep: defineProps|interface Props|<slot|@description|class=|className=
81
+ → 시작 줄 번호 탐색
82
+ b. Read: 시작줄 ~ +30줄 추출
83
+ Vue/Svelte template+script 분리 파일: 첫 300줄 전체 Read
84
+ barrel file (index.ts): 전체 Read
85
+
86
+ 추출 항목:
87
+ - Props/Interface: defineProps<{...}> 또는 interface Props {...} 에서 prop 이름+타입
88
+ ※ 외부 타입 참조 시 (defineProps<ButtonProps>()) → types 인덱스에서 교차 참조
89
+ - Slots: <slot> 또는 {children} 패턴
90
+ - 스타일 클래스: class="..." 또는 className={styles.xxx} 최상위 요소의 클래스명
91
+ - 용도 힌트: JSDoc @description 또는 파일 첫 번째 주석
92
+
93
+ 5-2. 인덱스 결과 저장:
94
+ 저장 경로: /tmp/{feature}/component-index.json
95
+ [
96
+ { name: "BaseButton",
97
+ path: "components/common/BaseButton.vue",
98
+ props: [{ name: "label", type: "string" }, { name: "variant", type: "'primary'|'secondary'" }],
99
+ slots: ["default"],
100
+ classes: ["btn", "btn--primary"],
101
+ description: "공통 버튼 컴포넌트" },
102
+ ...
103
+ ]
104
+
105
+ 컨텍스트 관리:
106
+ - 컴포넌트 20개 이하: 전체 인덱스를 프롬프트에 포함
107
+ - 컴포넌트 20개 초과: 이름+설명+축약 props(이름만, 타입 생략)만 포함
108
+ 매칭 후보 발견 시 해당 파일 Read로 상세 확인
109
+ - classes 필드는 요약에서 제외 (매칭 시 파일 Read로 확인)
110
+
111
+ 5-3. Hooks/Types/Constants 인덱싱:
112
+ 추가 스캔 대상:
113
+ - Composables/Hooks: composables/**/*.ts, hooks/**/*.ts
114
+ → export 함수명 + 파라미터 + 반환 타입
115
+ - 타입 정의: types/**/*.ts, types.ts
116
+ → export interface/type 이름 + 최상위 필드
117
+ - 상수: constants/**/*.ts
118
+ → export const 이름 + 값 (또는 타입)
119
+
120
+ 저장: /tmp/{feature}/context-index.json (component-index와 별도)
121
+ 컨텍스트 관리:
122
+ - 항목 30개 이하: 전체 인덱스를 프롬프트에 포함
123
+ - 항목 30개 초과: 이름+핵심 시그니처만 요약, 상세 필요 시 파일 Read로 지연 조회
124
+
125
+ 타임아웃: 파일당 Read 최대 300줄, 전체 인덱싱 2분 이내 완료
126
+
127
+ 6. 기존 디자인 토큰 스캔:
128
+
129
+ SCSS 토큰:
130
+ Glob: **/_variables.scss, **/_tokens.scss, **/_colors.scss, **/variables.scss
131
+ → 패턴: $변수명: 값; 추출
132
+ → 결과: { name: "$color-primary", value: "#3b82f6", source: "styles/_variables.scss" }
133
+
134
+ CSS Variables:
135
+ Glob: **/global.css, **/variables.css, **/root.css, **/app.css
136
+ Grep: "--[a-zA-Z].*:" 패턴
137
+ → 결과: { name: "--color-primary", value: "#3b82f6", source: "styles/global.css" }
138
+
139
+ Tailwind:
140
+ tailwind.config.{js,ts,mjs} 존재 시 Read
141
+ → theme.colors, theme.extend.colors 에서 커스텀 색상 추출
142
+ → theme.spacing, theme.fontSize 에서 커스텀 값 추출
143
+ → Tailwind v4: global CSS에서 @theme 디렉티브 내 CSS variable도 스캔
144
+ → 결과: { name: "blue-500", value: "#3b82f6", type: "tailwind", category: "color" }
145
+
146
+ CSS-in-JS:
147
+ Glob: **/theme.{ts,js}, **/tokens.{ts,js}, **/design-tokens.{ts,js}
148
+ → export 객체에서 color/spacing/typography 키 추출
149
+
150
+ 통합 결과 → /tmp/{feature}/project-tokens.json:
151
+ {
152
+ colors: [{ name, value, source }],
153
+ spacing: [{ name, value, source }],
154
+ typography: [{ name, value, source }],
155
+ other: [{ name, value, source }]
156
+ }
157
+
158
+ 토큰 소스 우선순위 (복수 시스템 공존 시):
159
+ Tailwind > CSS Variables > SCSS > CSS-in-JS
160
+
161
+ 성능: 100개 파일 기준 5초 이내 완료
162
+ 파싱 실패 시: 해당 파일 스킵 + 경고 로그 (전체 중단하지 않음)
70
163
  ```
71
164
 
72
165
  ---
@@ -152,8 +245,14 @@ URL에서 fileKey, nodeId 추출
152
245
  */
153
246
  - TypeScript 인터페이스
154
247
  - 목 데이터 (빈 배열 금지, 3~7개 아이템)
248
+ - 목 데이터의 image 필드: 실제 다운로드할 이미지 경로 사용 금지
249
+ → Phase 1에서는 이미지 경로를 '' (빈 문자열) 또는 placeholder 텍스트로 설정
250
+ → Phase 2에서 실제 이미지 다운로드 후 경로를 업데이트
251
+ → ❌ '/images/{feature}/token-100.png' (존재하지 않는 파일 참조 금지)
155
252
  - 이벤트 핸들러 stub (body는 // TODO:)
156
253
 
254
+ ❌ <client-only> 래핑 금지 — SSR hydration 실패 위험
255
+ (<client-only>는 window/document 직접 접근하는 컴포넌트에만 사용)
157
256
  <style> 블록 없음 — 스타일은 Phase 3에서 외부 파일로.
158
257
 
159
258
  3. 공통 컴포넌트 (SHARED에서 파악):
@@ -177,6 +276,8 @@ Phase 1 완료 조건:
177
276
  □ 클릭하면 핸들러가 실행된다
178
277
  □ 모든 컴포넌트에 [기능 정의] + [인터랙션] + [상태] JSDoc
179
278
  □ 빈 배열 0개, 빈 template 0개, <style> 블록 0개
279
+ □ <client-only> 전체 래핑 0개
280
+ □ 목 데이터의 image 필드에 존재하지 않는 파일 경로 0개
180
281
  □ 빌드 성공
181
282
 
182
283
  빈 화면 = Phase 1 미완성. Phase 2로 넘어가지 않는다.
@@ -190,10 +291,24 @@ Phase 1 완료 조건:
190
291
  Phase 1 컴포넌트가 준비된 상태에서, 디자인 URL로 시각 재료를 수집한다.
191
292
 
192
293
  사용자에게 질문한다:
193
- - question: "베이스 디자인(모바일) Figma URL을 입력해주세요."
294
+ - question: "디자인 Figma URL을 입력해주세요. 여러 페이지는 줄바꿈으로 구분."
194
295
  - options 제공 금지 — 자유 텍스트 입력만 허용
195
296
 
196
- ### 2-1. 디자인 재료 추출
297
+ ### 멀티 프레임 URL 검증
298
+
299
+ ```
300
+ URL 유효성 검증:
301
+ - 모든 URL에서 fileKey 추출 → 동일한 fileKey인지 확인
302
+ - 서로 다른 fileKey 발견 시 에러: "멀티 프레임은 동일 Figma 파일 내 다른 프레임만 지원합니다"
303
+ - node-id 파라미터 누락 시 해당 URL 에러 보고
304
+ - 최대 5개 URL까지 지원
305
+
306
+ URL 개수에 따른 처리:
307
+ 1개: 기존 방식 (변경 없음, 아래 2-1 그대로)
308
+ 2개 이상: 멀티 프레임 모드 활성화 (2-1-multi 참조)
309
+ ```
310
+
311
+ ### 2-1. 디자인 재료 추출 (단일 URL)
197
312
 
198
313
  ```
199
314
  URL에서 fileKey, nodeId 추출
@@ -209,16 +324,57 @@ URL에서 fileKey, nodeId 추출
209
324
  node "[FIGMA_SCRIPT]" images {fileKey} {nodeId} --out=/tmp/{feature}/images/ --depth=10
210
325
  → 모든 이미지 에셋 확보. 누락 0건, 0byte 0건.
211
326
 
327
+ 3.5단계 — 아이템/아이콘 노드 렌더링 (추가 시각 재료):
328
+ tree.json에서 INSTANCE/COMPONENT 타입 중 아이템 후보를 식별:
329
+ - name에 "item", "icon", "reward", "token", "coin", "badge" 포함
330
+ - 크기 50~300px 범위의 독립 요소
331
+ - fill 이미지가 없지만 시각적으로 의미 있는 노드
332
+ 해당 노드를 개별 렌더링:
333
+ node "[FIGMA_SCRIPT]" images {fileKey} {nodeId} --render --nodeIds={id1},{id2},... --out=/tmp/{feature}/images/
334
+ → 이미지 fill이 아닌 벡터/인스턴스 에셋도 PNG로 확보
335
+ → Phase 3에서 목 데이터의 image 경로에 연결
336
+
212
337
  4단계 — 섹션별 스크린샷 (부분 정답):
213
338
  트리의 1depth 자식 프레임 각각:
214
339
  node "[FIGMA_SCRIPT]" screenshot {fileKey} {child.nodeId} --out=/tmp/{feature}/sections/{child.name}.png
215
340
  ```
216
341
 
342
+ ### 2-1-multi. 멀티 프레임 재료 확보 (2개 이상 URL)
343
+
344
+ ```
345
+ 각 URL에 대해 순차 추출 (Figma API rate limit: 요청 간 500ms 간격):
346
+ URL 1 → /tmp/{feature}/frame-1/ (full-screenshot, tree, images, sections)
347
+ URL 2 → /tmp/{feature}/frame-2/ (URL 1 완료 후 500ms 대기)
348
+ URL 3 → /tmp/{feature}/frame-3/ (URL 2 완료 후 500ms 대기)
349
+ ※ 추출 완료 후 후처리(섹션 분할 등)는 병렬 가능
350
+
351
+ 부분 실패 처리:
352
+ - 개별 URL 추출 실패 시 해당 frame만 건너뛰고 나머지 진행
353
+ - 실패한 frame은 사용자에게 즉시 보고: "frame-{N} 추출 실패: {에러 사유}" (API 에러, 권한 부족, 잘못된 nodeId 등)
354
+ - 성공한 frame ≥ 2: Phase 2.5 계속 진행
355
+ - 정확히 1개만 성공: 단일 프레임 모드로 폴백 (Phase 2.5 스킵)
356
+ - 0개 성공: 전체 실패 보고
357
+
358
+ 결과:
359
+ /tmp/{feature}/
360
+ ├── frame-1/ ← 메인 페이지
361
+ │ ├── full-screenshot.png
362
+ │ ├── tree.json
363
+ │ ├── images/
364
+ │ └── sections/
365
+ ├── frame-2/ ← 서브 페이지 1
366
+ │ └── ...
367
+ ├── frame-3/ ← 서브 페이지 2
368
+ │ └── ...
369
+ └── shared/ ← 공통 분석 결과 (Phase 2.5에서 생성)
370
+ ```
371
+
217
372
  ### 2-2. 재료함 정리
218
373
 
219
374
  ```
220
375
  Phase 2 완료 시 /tmp/{feature}/ 에 다음이 준비되어야 함:
221
376
 
377
+ 단일 URL:
222
378
  /tmp/{feature}/
223
379
  ├── full-screenshot.png ← 전체 정답 사진
224
380
  ├── tree.json ← 노드 트리 + CSS 수치
@@ -233,6 +389,8 @@ Phase 2 완료 시 /tmp/{feature}/ 에 다음이 준비되어야 함:
233
389
  ├── playtime-mission.png
234
390
  └── ...
235
391
 
392
+ 멀티 URL: 위 2-1-multi 결과 구조 참조
393
+
236
394
  재료 목록 (material-inventory):
237
395
  - 이미지: 파일명 + 크기 + 용도 추정 (BG/icon/title/decoration)
238
396
  - 색상: tree.json에서 추출한 모든 고유 색상값
@@ -254,12 +412,81 @@ Phase 2 완료 시 /tmp/{feature}/ 에 다음이 준비되어야 함:
254
412
 
255
413
  ---
256
414
 
415
+ ## Phase 2.5: 공통 패턴 분석 (멀티 프레임 전용)
416
+
417
+ **URL 2개 이상일 때만 실행. 단일 URL이면 Phase 3으로 건너뜀.**
418
+ **프레임 간 공통 요소를 식별하여 공유 컴포넌트로 추출한다.**
419
+
420
+ ```
421
+ 1. 시각 비교 — 각 프레임의 스크린샷을 순차 Read:
422
+ - frame-1/full-screenshot.png → frame-2/full-screenshot.png → ...
423
+ - 시각적으로 반복되는 요소 식별:
424
+ 상단 영역 (GNB/Header), 하단 영역 (Footer),
425
+ 카드 패턴, 버튼 스타일, 섹션 레이아웃
426
+
427
+ 2. 구조 비교 — 각 tree.json의 1depth 자식 비교:
428
+ - 동일한 name 또는 prefix 일치 (예: "GNB", "Header", "Footer", "Nav")
429
+ - 동일한 size (width와 height 모두 ±10% 이내): 같은 컴포넌트 후보
430
+ - 동일한 CSS 패턴: 같은 스타일 사용 (색상, 폰트, 레이아웃)
431
+
432
+ 3. 공통 컴포넌트 후보 목록:
433
+ shared-components:
434
+ - name: GNB
435
+ frames: [frame-1, frame-2, frame-3]
436
+ consistency: 100% (모든 프레임에서 동일)
437
+ action: 공유 컴포넌트로 1회만 생성
438
+ - name: Footer
439
+ frames: [frame-1, frame-3]
440
+ consistency: 67% (2/3 프레임)
441
+ action: 공유 컴포넌트 + frame-2는 다른 Footer
442
+ - name: CardItem
443
+ frames: [frame-1, frame-2]
444
+ size-match: 95%
445
+ action: 공유 컴포넌트 (props로 변형)
446
+
447
+ 4. 공통 토큰 추출:
448
+ - 모든 프레임의 색상 팔레트 합집합 → 공유 _tokens.scss
449
+ - 모든 프레임의 폰트 목록 합집합
450
+ - 모든 프레임의 간격 패턴 합집합
451
+ → 프레임별 고유 값만 프레임 로컬 토큰으로 분리
452
+ ```
453
+
454
+ ---
455
+
257
456
  ## Phase 3: 퍼즐 조립
258
457
 
259
458
  **Phase 1에서 만든 컴포넌트에 Phase 2의 재료로 디자인을 입힌다.**
260
459
  **스크린샷을 보면서 퍼즐을 맞추듯 조립한다.**
261
460
  **첫 섹션(Hero) 단독 완료 후 나머지 섹션 병렬 진행.**
262
461
 
462
+ **멀티 프레임 모드 시 조립 순서 변경:**
463
+ ```
464
+ 멀티 프레임 Phase 3 순서:
465
+
466
+ 1단계: 공유 컴포넌트 먼저 생성
467
+ - shared-components 목록의 컴포넌트를 components/shared/에 생성
468
+ - 가장 일관적인 프레임(shared-components 매칭 수가 가장 많은 프레임) 기준으로 조립
469
+ - 프레임별 변형은 props로 처리
470
+
471
+ 2단계: 프레임별 고유 섹션 조립
472
+ - frame-1 고유 섹션: 공유 컴포넌트 import + 고유 섹션 신규 생성
473
+ - frame-2 고유 섹션: 동일
474
+ - 각 프레임의 페이지 파일에서 공유 + 고유 컴포넌트 배치
475
+
476
+ 3단계: 스타일 구조
477
+ styles/{feature}/
478
+ ├── _tokens.scss ← 공유 토큰 (합집합)
479
+ ├── shared/ ← 공유 컴포넌트 스타일
480
+ │ ├── layout/_gnb.scss
481
+ │ └── components/_gnb.scss
482
+ ├── frame-1/ ← 메인 고유 스타일
483
+ │ ├── layout/
484
+ │ └── components/
485
+ └── frame-2/
486
+
487
+ 단일 URL: 기존 방식 그대로 (위 멀티 프레임 순서 무시)
488
+ ```
489
+
263
490
  ### 3-0. SCSS Setup + 등록 (첫 섹션 전)
264
491
 
265
492
  ```
@@ -269,50 +496,107 @@ Phase 1에서 생성한 빈 SCSS 파일에 기본 내용 Write:
269
496
  styles/{feature}/_mixins.scss ← breakpoint mixin
270
497
  styles/{feature}/_base.scss ← 루트 클래스
271
498
 
272
- 토큰 추출 (tree.json의 CSS 수치에서):
273
- - 색상 → primitive ($color-navy: #0a1628) + semantic ($color-bg-primary)
274
- - 폰트 $font-pretendard, $font-size-md: 16px
275
- - 간격 $space-sm: 8px, $space-md: 16px
499
+ 토큰 매핑 (기존 토큰 우선 사용):
500
+ 1. /tmp/{feature}/project-tokens.json Read로 로드
501
+ 2. Figma 재료함의 값에 대해 project-tokens에서 동일 값 검색:
502
+ - 색상: hex 정규화 후 완전 일치 (Figma RGBA 0-1 → hex, 3자리→6자리, 대소문자 무시)
503
+ ※ alpha < 1: 8자리 hex (#RRGGBBAA) 또는 rgba() 함수로 변환
504
+ - 간격: px 값 완전 일치 (rem→px: 1rem=16px)
505
+ - 폰트: family 이름 포함 매칭
506
+ 3. 매칭됨 → 기존 토큰 참조:
507
+ - SCSS: @use 'path' 로 기존 파일 import, $변수명 사용
508
+ - Tailwind: 해당 유틸리티 클래스 사용 (bg-blue-500)
509
+ - CSS Variables: var(--color-primary) 사용
510
+ 4. 매칭 안 됨 → 새 토큰 생성 (기존 방식)
511
+
512
+ _tokens.scss 구조:
513
+ // ─── 기존 토큰 참조 ────────────────────
514
+ @use '../../styles/variables' as v;
515
+
516
+ // ─── 매핑 (기존 토큰 → 피처 시맨틱) ────
517
+ $color-bg-primary: v.$color-navy; // 기존 재사용
518
+ $color-text-primary: v.$color-white; // 기존 재사용
519
+
520
+ // ─── 새 토큰 (매칭 안 된 값만) ─────────
521
+ $color-accent-gold: #ffd700; // 새 값
522
+ $space-section-gap: 42px; // 새 값
523
+
524
+ 매핑 결과 보고: "토큰 매핑: 12/18 매칭 (67%), 6개 새 토큰 생성"
525
+
526
+ 기존 토큰 파일 수정 금지 — 참조만 함
527
+ 같은 값의 토큰 중복 생성 금지
528
+ 새 토큰은 피처 스코프 네이밍 ($feature-color-xxx)
529
+
530
+ project-tokens.json 없는 경우 (기존 토큰 없음):
531
+ 기존 방식으로 전체 생성:
532
+ - 색상 → primitive ($color-navy: #0a1628) + semantic ($color-bg-primary)
533
+ - 폰트 → $font-pretendard, $font-size-md: 16px
534
+ - 간격 → $space-sm: 8px, $space-md: 16px
276
535
 
277
536
  스타일 등록 (BLOCKING):
278
537
  Grep "{feature}/index.scss" → 이미 등록되어 있으면 건너뜀.
279
538
  없으면 프로젝트 방식에 맞게 등록.
280
539
  ```
281
540
 
282
- ### 3-1. 섹션 조립 프로세스
541
+ ### 3-0.5. 컴포넌트 매칭 (각 섹션 조립 전)
283
542
 
284
543
  ```
285
- 섹션에 대해:
544
+ /tmp/{feature}/component-index.json Read로 로드한다.
286
545
 
287
- 1. 정답 확인 섹션 스크린샷을 Read로 본다
288
- /tmp/{feature}/sections/{section}.png
289
- → "이 화면처럼 만들어야 한다"
546
+ 섹션 스크린샷 분석 후, 기존 컴포넌트 매칭:
290
547
 
291
- 2. 재료 확인 섹션에 필요한 재료 목록 확인
292
- - 이미지: /tmp/{feature}/images/ 에서 해당 파일들
293
- - CSS 수치: tree.json에서 해당 노드의 정확한 값
294
- - 텍스트: TEXT 노드의 characters
548
+ 1. 스크린샷에서 식별된 UI 요소를 component-index와 대조
549
+ 2. 매칭 판정 기준:
550
+ - 필수: name/role 유사성 (시각적 역할 일치)
551
+ 버튼 BaseButton, 네비게이션 → GNB, 카드 → Card, 모달 → Modal
552
+ - 보조: props 호환성 (필요 props가 기존 컴포넌트에 존재)
553
+ - 보조: slots 호환성 (필요 slot이 존재)
554
+ - 불일치: props/slots 50% 미만 호환이면 매칭 거부 → 새로 생성
555
+ 3. 매칭된 컴포넌트: import하여 props 전달 (내부 수정 금지)
556
+ 4. 매칭 안 됨: 새로 생성 (강제 재사용 금지)
295
557
 
296
- 3. Phase 1 컴포넌트에 디자인 입히기
558
+ 재사용 스타일 충돌 방지:
559
+ ✅ 기존 컴포넌트 import하여 사용
560
+ ✅ props로 variant/size 등 커스터마이즈
561
+ ✅ 래퍼 클래스로 position/size만 오버라이드
562
+ ❌ 기존 컴포넌트 내부 스타일 수정 금지
563
+ ❌ 90% 유사한데 새로 만들기 금지
564
+ ```
565
+
566
+ ### 3-1. 트리 기반 구조적 코드 생성
567
+
568
+ ```
569
+ 각 섹션에 대해 (vibe.figma.convert 참조):
570
+
571
+ 1. 트리 구조 읽기 — tree.json에서 해당 섹션 노드를 Read
572
+ → Auto Layout 속성(flex, gap, padding)이 코드의 기반
573
+ → 스크린샷은 검증용으로만 참조
574
+
575
+ 2. 기계적 매핑 (추정 없음):
297
576
  a. 이미지 복사: images/ → static/images/{feature}/
298
- b. template 업데이트:
299
- - Phase 1의 기능 요소(v-for, @click, v-if, $emit) 보존
300
- - 스크린샷을 보고 시각 구조에 맞게 HTML 재구성
301
- - Figma 레이어 구조를 HTML로 변환하지 않음 (시각 기반)
302
- - 재료함의 정확한 이미지 경로 사용
303
- - 재료함의 정확한 텍스트 사용
304
- c. script 보존:
305
- - Phase 1JSDoc, 인터페이스, 핸들러 유지
306
- d. SCSS 작성: 재료함의 정확한 CSS 수치 사용 (scaleFactor 적용)
307
- - layout/_{section}.scss 포지션, 사이즈, 플렉스
308
- - components/_{section}.scss 폰트, 색상, 보더
309
- - _tokens.scss에 새 토큰 추가
310
-
311
- 4. 즉시 검증 스크린샷과 비교
312
- - 이미지 빠진 없나?
313
- - 텍스트 빠진 거 없나?
314
- - 레이아웃 구조가 스크린샷과 맞나?
315
- - Phase 1의 기능 요소가 보존되었나?
577
+ b. 노드 → HTML 매핑:
578
+ - Auto Layout 있음 <div> + flex (direction/gap/padding 직접)
579
+ - Auto Layout 없음 <div> + position:relative (자식 absolute)
580
+ - TEXT 노드 <span> (Claude가 h2/p/button으로 승격)
581
+ - imageRef 있음 <img src="다운로드된 파일">
582
+ - 반복 패턴 (동일 구조 3+) → v-for
583
+ c. CSS 직접 매핑:
584
+ - node.css모든 속성을 SCSS에 1:1 매핑
585
+ - scaleFactor 적용 (px 값만)
586
+ - tree.json에 없는 CSS 값은 작성하지 않음
587
+ d. Phase 1의 JSDoc, 인터페이스, 핸들러 보존
588
+
589
+ 3. Claude 시맨틱 보강:
590
+ - div section/h2/p/button 태그 승격
591
+ - 컴포넌트 분리 + props 설계
592
+ - 접근성 (alt, aria)
593
+ - 인터랙션 (클릭, 상태)
594
+
595
+ 4. 자가 검증:
596
+ - template 클래스 ↔ SCSS 클래스 1:1 일치
597
+ - 모든 img src가 static/에 실제 존재
598
+ - Auto Layout 노드 → SCSS에 flex 속성 존재
599
+ - 스크린샷과 비교 (시각 확인)
316
600
  ```
317
601
 
318
602
  ### 3-2. 코드 작성 규칙
@@ -320,11 +604,10 @@ Phase 1에서 생성한 빈 SCSS 파일에 기본 내용 Write:
320
604
  ```
321
605
  컴포넌트 (Vue 예시):
322
606
  <template>
323
- 스크린샷에서 보이는 시각 구조대로 작성.
324
- Figma 레이어 구조 무시.
607
+ tree.json의 Auto Layout 구조를 HTML flex 레이아웃으로 직접 매핑.
608
+ Claude가 시맨틱 태그로 승격 (div → section/h2/p/button).
325
609
  Phase 1의 기능 요소(v-for, @click, v-if) 보존.
326
- 시맨틱 HTML 사용 (<section>, <h2>, <ul>, <button>).
327
- 이미지 경로: /images/{feature}/파일명.png
610
+ 이미지 경로: /images/{feature}/파일명.png (실제 파일 존재 확인)
328
611
  텍스트: tree.json의 TEXT 노드 characters 값 그대로.
329
612
 
330
613
  <script setup>
@@ -354,6 +637,116 @@ URL 있으면:
354
637
 
355
638
  ---
356
639
 
640
+ ## Phase 3.5: 컴파일 게이트
641
+
642
+ **Phase 3 퍼즐 조립 완료 후, 브라우저 검증 전에 컴파일 성공을 보장한다.**
643
+ **컴파일 에러는 스킵 불가 — 반드시 수정 또는 사용자 보고.**
644
+ **Phase 3.5 실패 시 Phase 4 진행 불가 (hard gate).**
645
+
646
+ ```
647
+ 자동 반복: 컴파일 성공까지. 최대 3라운드.
648
+ ```
649
+
650
+ ### 3.5-0. 베이스라인 캡처 (Phase 3 변경 전)
651
+
652
+ ```
653
+ Phase 3 시작 전에 기존 프로젝트의 에러를 캡처:
654
+ 1. 타입 체크 베이스라인: (3.5-1에서 선택한 동일 명령 사용) > /tmp/{feature}/baseline-typecheck.txt 2>&1
655
+ 2. 빌드 베이스라인: npm run build > /tmp/{feature}/baseline-build.txt 2>&1
656
+
657
+ Phase 3.5에서는 baseline에 없는 **새로 발생한 에러만** 수정 대상.
658
+ baseline에 존재하던 에러는 무시하고 별도 보고 ("기존 에러 {N}개 유지").
659
+ vibe.figma가 생성/수정한 파일 외의 에러는 자동 수정 금지.
660
+ ```
661
+
662
+ ### 3.5-1. TypeScript 컴파일 체크
663
+
664
+ ```
665
+ 1. 프로젝트 타입 체커 감지 → 실행:
666
+ - package.json scripts에 type-check 또는 typecheck 존재 → npm run type-check 사용
667
+ - vue-tsc 설치 확인 (Vue 프로젝트) → npx vue-tsc --noEmit 2>&1
668
+ - svelte-check 설치 확인 (Svelte 프로젝트) → npx svelte-check 2>&1
669
+ - 위 해당 없음 → fallback: npx tsc --noEmit 2>&1
670
+ → 에러 0개: PASS → 다음 단계
671
+ → 에러 있음: 에러 메시지 파싱 → 자동 수정
672
+
673
+ 2. 에러 파싱:
674
+ 각 에러에서 추출: 파일 경로, 줄 번호, 에러 코드, 메시지
675
+ 예: "src/components/Hero.tsx(15,3): error TS2322: Type 'string' is not assignable to type 'number'"
676
+
677
+ 3. 자동 수정 (에러 유형별):
678
+ - TS2322 (타입 불일치): prop 타입을 올바르게 수정
679
+ - TS2304 (이름 없음): import 추가
680
+ - TS2339 (프로퍼티 없음): interface에 프로퍼티 추가
681
+ - TS7006 (암시적 any): 타입 어노테이션 추가
682
+ - 기타: Read로 해당 파일+줄 확인 → 컨텍스트 기반 수정
683
+ ```
684
+
685
+ ### 3.5-2. 빌드 체크
686
+
687
+ ```
688
+ 1. npm run build 실행:
689
+ Bash: npm run build 2>&1
690
+ → 성공: PASS → 다음 단계
691
+ → 실패: 에러 메시지 파싱 → 자동 수정
692
+ → 타임아웃: 최대 120초 (초과 시 해당 라운드 실패 처리)
693
+
694
+ 2. 일반적 빌드 에러 처리:
695
+ - SCSS 컴파일 에러: 변수명/import 오류 수정
696
+ - Module not found: import 경로 수정 (.js 확장자 등)
697
+ - ESLint 에러 (--max-warnings 초과): 자동 수정 가능한 것 처리
698
+ ```
699
+
700
+ ### 3.5-3. dev 서버 시작 확인
701
+
702
+ ```
703
+ 1. dev 서버 시작 + PID 캡처:
704
+ Bash: npm run dev & echo $! → DEV_PID 저장
705
+ → localhost 포트 자동 감지: npm run dev stdout에서 localhost:\d+ 또는 port \d+ 파싱
706
+ 감지 실패 시 기본값 3000, 5173, 4173 순서 시도
707
+ → 포트 폴링 (3초 간격, 최대 30초 대기)
708
+ → 성공: Phase 4 진행 (Phase 4 완료 후 정리)
709
+ → 실패: kill $DEV_PID → 에러 로그 확인 → 수정 → 재시도
710
+
711
+ 2. 프로세스 정리 규칙:
712
+ - Phase 4 완료 또는 3라운드 실패 시 반드시 정리
713
+ - 정리 순서: kill $DEV_PID → 3초 대기 → kill -9 $DEV_PID (응답 없으면)
714
+ - lsof -i :{port} -t 로 포트 점유 프로세스 확인 후 추가 정리
715
+ ※ spawned child process만 대상 — 관련 없는 프로세스 kill 금지
716
+ - interrupt 시에도 cleanup 보장
717
+ ```
718
+
719
+ ### 3.5-4. 수정 루프
720
+
721
+ ```
722
+ 라운드 1~3:
723
+ 1. tsc → build → dev 순서로 체크
724
+ 2. 첫 번째 실패 단계의 에러 수정
725
+ 3. 수정 후 해당 단계부터 재체크
726
+ 4. 모든 단계 통과: Phase 4 진행
727
+
728
+ 라운드 종료 조건:
729
+ - 3라운드 후 실패: 에러 목록 + 시도한 수정을 사용자에게 보고
730
+ - 같은 에러 반복: 해당 에러 스킵 불가 → 사용자 보고 (컴파일 에러는 스킵 불가)
731
+
732
+ 컴파일 게이트 결과 보고:
733
+
734
+ ✅ 통과:
735
+ "Phase 3.5: 컴파일 게이트 PASS (라운드 {N})"
736
+ - tsc: 0 errors
737
+ - build: success
738
+ - dev server: running on localhost:{port}
739
+
740
+ ❌ 실패 (3라운드 후):
741
+ "Phase 3.5: 컴파일 게이트 FAIL"
742
+ - 남은 에러 목록 (파일, 줄, 메시지)
743
+ - 시도한 수정 내역
744
+ - 사용자 수동 수정 필요
745
+ → Phase 4 진행하지 않음
746
+ ```
747
+
748
+ ---
749
+
357
750
  ## Phase 4: 검증 루프
358
751
 
359
752
  **Puppeteer + CDP로 실제 렌더링 결과를 확인하며 자동 수정한다.**
@@ -368,8 +761,8 @@ URL 있으면:
368
761
 
369
762
  ```
370
763
  1. dev 서버 시작:
371
- npm run dev (또는 프로젝트 dev 명령)
372
- → localhost:3000 (또는 해당 포트) 확인
764
+ Phase 3.5에서 이미 시작된 dev 서버 사용 (재시작 불필요)
765
+ → localhost:{port} 확인
373
766
 
374
767
  2. Puppeteer 브라우저 시작:
375
768
  import { launchBrowser, openPage } from 'src/infra/lib/browser'
@@ -455,8 +848,11 @@ import { extractImages, extractTextContent } from 'src/infra/lib/browser'
455
848
  - 텍스트 누락 → 재료함의 정확한 텍스트 삽입
456
849
  - CSS 수치 틀림 → 재료함(tree.json)의 정확한 값으로 교체
457
850
  ⚠️ 추정으로 수정하지 않는다. 반드시 재료함 참조.
458
- 3. 수정 후 페이지 리로드 → 다시 캡처 → 비교
459
- 4. P1=0 이면 종료
851
+ 3. 수정 후 컴파일 재검증:
852
+ Bash: npx tsc --noEmit 2>&1 (또는 3.5-1에서 선택한 타입 체커)
853
+ → 시각 수정이 타입 에러를 유발하면 즉시 타입 에러 수정 후 진행
854
+ 4. 페이지 리로드 → 다시 캡처 → 비교
855
+ 5. P1=0 이면 종료
460
856
 
461
857
  라운드 종료 조건:
462
858
  - P1=0: 성공 → 브라우저 종료, 결과 보고
@@ -40,11 +40,11 @@ Breakpoint threshold: `@media (min-width: {{BP_PC}}px)`
40
40
 
41
41
  ## Sections
42
42
 
43
- | # | Section Name | Component File | Mode | Height (design) |
44
- |---|-------------|---------------|------|----------------|
45
- | 1 | {{SECTION_NAME}} | `components/{{FEATURE_KEY}}/{{ComponentName}}.vue` | {{MODE}} | {{HEIGHT}}px |
43
+ | # | Section Name | Component File | Tree Nodes | Height (design) |
44
+ |---|-------------|---------------|-----------|----------------|
45
+ | 1 | {{SECTION_NAME}} | `components/{{FEATURE_KEY}}/{{ComponentName}}.vue` | {{NODE_COUNT}} | {{HEIGHT}}px |
46
46
 
47
- **Mode key:** `normal` = external SCSS | `literal` = scoped CSS (non-standard layout)
47
+ **Generation:** tree.json HTML+SCSS 구조적 매핑 (external SCSS)
48
48
 
49
49
  ---
50
50