@su-record/vibe 2.8.35 → 2.8.37

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,21 +1,31 @@
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 — Figma Design to Code
8
+ # vibe.figma — Visual Puzzle Assembly
9
+
10
+ ## 핵심 원칙
11
+
12
+ ```
13
+ 스크린샷이 정답이다. Figma 데이터는 재료일 뿐이다.
14
+
15
+ ❌ Figma 트리 구조를 HTML로 변환하지 않는다 (이 방식은 실패한다)
16
+ ✅ 스크린샷을 보고 "무엇을 만들어야 하는지" 파악한다
17
+ ✅ Figma 데이터(이미지, 색상, 수치)를 정확한 재료로 사용한다
18
+ ✅ 사람 개발자처럼: 디자인 보고 → 에셋 받고 → 만들면서 비교
19
+ ```
9
20
 
10
21
  ## 금지 사항
11
22
 
12
23
  ```
13
- CSS로 이미지 재현 (삼각형/원/gradient로 나무/눈사람/배경 그리기)
24
+ Figma 레이어 트리를 그대로 div 구조로 변환
25
+ ❌ CSS로 이미지 재현 (gradient/shape으로 그림 그리기)
14
26
  ❌ 이미지 다운로드 없이 코드 생성 진행
15
- ❌ placeholder / 빈 template / 빈 src="" 남기기
16
- CSS 값을 추정 (참조 코드의 Tailwind 클래스에 정확한 값이 있음)
17
- ❌ 브라우저 기본 스타일(검은색 16px)로 보이는 텍스트
18
- ❌ 핵심 에셋만 다운로드 (const img... 전부 다운로드)
27
+ ❌ placeholder / 빈 src="" 남기기
28
+ 색상·크기를 추정 (재료함에 정확한 값이 있음)
19
29
  ❌ 컴포넌트 파일 안에 <style> 블록 / 인라인 style=""
20
30
  ✅ 외부 SCSS 파일에만 스타일 작성
21
31
  ```
@@ -25,9 +35,10 @@ tier: standard
25
35
  ```
26
36
  /vibe.figma
27
37
  → Phase 0: Setup (스택 감지, 디렉토리 생성)
28
- → Phase 1: Storyboard (선택 레이아웃+컴포넌트 구성 + 기능 주석)
29
- → Phase 2: Design (최소 뷰포트 퍼스트 브레이크포인트별 반복)
30
- → Phase 3: Verification (Grep 체크 + 스크린샷 비교)
38
+ → Phase 1: Storyboard (스토리보드 레이아웃 + 컴포넌트 + 기능 정의)
39
+ → Phase 2: 재료 확보 (디자인 URL스크린샷 + 이미지 + CSS + 텍스트)
40
+ → Phase 3: 퍼즐 조립 (스크린샷 보면서 Phase 1 컴포넌트에 디자인 입히기)
41
+ → Phase 4: 검증 루프 (빌드 → 스크린샷 → 비교 → 수정 → 반복)
31
42
  ```
32
43
 
33
44
  ---
@@ -52,6 +63,100 @@ tier: standard
52
63
  - components/{feature}/
53
64
  - public/images/{feature}/ (또는 static/images/{feature}/)
54
65
  - styles/{feature}/ (layout/, components/ 하위)
66
+
67
+ 5. 기존 컴포넌트 스캔 + 인덱싱:
68
+ - Glob "components/**/*.vue" or "components/**/*.tsx"
69
+ - 재사용 가능한 컴포넌트 목록 수집 (GNB, Footer, Button 등)
70
+
71
+ 5-1. 컴포넌트 상세 인덱싱 (50개 이하 스캔):
72
+ 대상 파일 수집 (우선순위 순서, 합산 50개 이내):
73
+ barrel file index.ts → components/ui/ → components/common/ → components/shared/ → 나머지
74
+ 각 디렉토리 내에서는 1-depth 파일만 (하위 재귀 탐색 안 함)
75
+
76
+ 각 파일에 대해 2단계 추출:
77
+ a. Grep: defineProps|interface Props|<slot|@description|class=|className=
78
+ → 시작 줄 번호 탐색
79
+ b. Read: 시작줄 ~ +30줄 추출
80
+ Vue/Svelte template+script 분리 파일: 첫 300줄 전체 Read
81
+ barrel file (index.ts): 전체 Read
82
+
83
+ 추출 항목:
84
+ - Props/Interface: defineProps<{...}> 또는 interface Props {...} 에서 prop 이름+타입
85
+ ※ 외부 타입 참조 시 (defineProps<ButtonProps>()) → types 인덱스에서 교차 참조
86
+ - Slots: <slot> 또는 {children} 패턴
87
+ - 스타일 클래스: class="..." 또는 className={styles.xxx} 최상위 요소의 클래스명
88
+ - 용도 힌트: JSDoc @description 또는 파일 첫 번째 주석
89
+
90
+ 5-2. 인덱스 결과 저장:
91
+ 저장 경로: /tmp/{feature}/component-index.json
92
+ [
93
+ { name: "BaseButton",
94
+ path: "components/common/BaseButton.vue",
95
+ props: [{ name: "label", type: "string" }, { name: "variant", type: "'primary'|'secondary'" }],
96
+ slots: ["default"],
97
+ classes: ["btn", "btn--primary"],
98
+ description: "공통 버튼 컴포넌트" },
99
+ ...
100
+ ]
101
+
102
+ 컨텍스트 관리:
103
+ - 컴포넌트 20개 이하: 전체 인덱스를 프롬프트에 포함
104
+ - 컴포넌트 20개 초과: 이름+설명+축약 props(이름만, 타입 생략)만 포함
105
+ 매칭 후보 발견 시 해당 파일 Read로 상세 확인
106
+ - classes 필드는 요약에서 제외 (매칭 시 파일 Read로 확인)
107
+
108
+ 5-3. Hooks/Types/Constants 인덱싱:
109
+ 추가 스캔 대상:
110
+ - Composables/Hooks: composables/**/*.ts, hooks/**/*.ts
111
+ → export 함수명 + 파라미터 + 반환 타입
112
+ - 타입 정의: types/**/*.ts, types.ts
113
+ → export interface/type 이름 + 최상위 필드
114
+ - 상수: constants/**/*.ts
115
+ → export const 이름 + 값 (또는 타입)
116
+
117
+ 저장: /tmp/{feature}/context-index.json (component-index와 별도)
118
+ 컨텍스트 관리:
119
+ - 항목 30개 이하: 전체 인덱스를 프롬프트에 포함
120
+ - 항목 30개 초과: 이름+핵심 시그니처만 요약, 상세 필요 시 파일 Read로 지연 조회
121
+
122
+ 타임아웃: 파일당 Read 최대 300줄, 전체 인덱싱 2분 이내 완료
123
+
124
+ 6. 기존 디자인 토큰 스캔:
125
+
126
+ SCSS 토큰:
127
+ Glob: **/_variables.scss, **/_tokens.scss, **/_colors.scss, **/variables.scss
128
+ → 패턴: $변수명: 값; 추출
129
+ → 결과: { name: "$color-primary", value: "#3b82f6", source: "styles/_variables.scss" }
130
+
131
+ CSS Variables:
132
+ Glob: **/global.css, **/variables.css, **/root.css, **/app.css
133
+ Grep: "--[a-zA-Z].*:" 패턴
134
+ → 결과: { name: "--color-primary", value: "#3b82f6", source: "styles/global.css" }
135
+
136
+ Tailwind:
137
+ tailwind.config.{js,ts,mjs} 존재 시 Read
138
+ → theme.colors, theme.extend.colors 에서 커스텀 색상 추출
139
+ → theme.spacing, theme.fontSize 에서 커스텀 값 추출
140
+ → Tailwind v4: global CSS에서 @theme 디렉티브 내 CSS variable도 스캔
141
+ → 결과: { name: "blue-500", value: "#3b82f6", type: "tailwind", category: "color" }
142
+
143
+ CSS-in-JS:
144
+ Glob: **/theme.{ts,js}, **/tokens.{ts,js}, **/design-tokens.{ts,js}
145
+ → export 객체에서 color/spacing/typography 키 추출
146
+
147
+ 통합 결과 → /tmp/{feature}/project-tokens.json:
148
+ {
149
+ colors: [{ name, value, source }],
150
+ spacing: [{ name, value, source }],
151
+ typography: [{ name, value, source }],
152
+ other: [{ name, value, source }]
153
+ }
154
+
155
+ 토큰 소스 우선순위 (복수 시스템 공존 시):
156
+ Tailwind > CSS Variables > SCSS > CSS-in-JS
157
+
158
+ 성능: 100개 파일 기준 5초 이내 완료
159
+ 파싱 실패 시: 해당 파일 스킵 + 경고 로그 (전체 중단하지 않음)
55
160
  ```
56
161
 
57
162
  ---
@@ -86,7 +191,7 @@ URL에서 fileKey, nodeId 추출
86
191
  1순위: SPEC (기능 정의서) — 1개
87
192
  2순위: CONFIG (해상도) — 1개
88
193
  3순위: PAGE 중 메인 섹션만 (3.1, 3.2, 3.3, 3.4, 3.5, 3.6)
89
- 하위 케이스(3.1.1, 3.2.1 등)는 건너뜀 — Phase 2에서 필요 시 참조
194
+ 하위 케이스(3.1.1, 3.2.1 등)는 건너뜀
90
195
  4순위: SHARED (공통 요소, Popup) — 필요 시
91
196
 
92
197
  높이 1500px 이상 프레임:
@@ -139,7 +244,7 @@ URL에서 fileKey, nodeId 추출
139
244
  - 목 데이터 (빈 배열 금지, 3~7개 아이템)
140
245
  - 이벤트 핸들러 stub (body는 // TODO:)
141
246
 
142
- <style> 블록 없음 — 스타일은 Phase 2에서 외부 파일로.
247
+ <style> 블록 없음 — 스타일은 Phase 3에서 외부 파일로.
143
248
 
144
249
  3. 공통 컴포넌트 (SHARED에서 파악):
145
250
  → 프로젝트에 이미 있으면 import 재사용
@@ -165,7 +270,112 @@ Phase 1 완료 조건:
165
270
  □ 빌드 성공
166
271
 
167
272
  빈 화면 = Phase 1 미완성. Phase 2로 넘어가지 않는다.
168
- 스타일/이미지는 없어도 됨 — Phase 2에서 채움.
273
+ 스타일/이미지는 없어도 됨 — Phase 3에서 채움.
274
+ ```
275
+
276
+ ---
277
+
278
+ ## Phase 2: 재료 확보
279
+
280
+ Phase 1 컴포넌트가 준비된 상태에서, 디자인 URL로 시각 재료를 수집한다.
281
+
282
+ 사용자에게 질문한다:
283
+ - question: "디자인 Figma URL을 입력해주세요. 여러 페이지는 줄바꿈으로 구분."
284
+ - options 제공 금지 — 자유 텍스트 입력만 허용
285
+
286
+ ### 멀티 프레임 URL 검증
287
+
288
+ ```
289
+ URL 유효성 검증:
290
+ - 모든 URL에서 fileKey 추출 → 동일한 fileKey인지 확인
291
+ - 서로 다른 fileKey 발견 시 에러: "멀티 프레임은 동일 Figma 파일 내 다른 프레임만 지원합니다"
292
+ - node-id 파라미터 누락 시 해당 URL 에러 보고
293
+ - 최대 5개 URL까지 지원
294
+
295
+ URL 개수에 따른 처리:
296
+ 1개: 기존 방식 (변경 없음, 아래 2-1 그대로)
297
+ 2개 이상: 멀티 프레임 모드 활성화 (2-1-multi 참조)
298
+ ```
299
+
300
+ ### 2-1. 디자인 재료 추출 (단일 URL)
301
+
302
+ ```
303
+ URL에서 fileKey, nodeId 추출
304
+
305
+ 1단계 — 전체 스크린샷 (정답 사진):
306
+ node "[FIGMA_SCRIPT]" screenshot {fileKey} {nodeId} --out=/tmp/{feature}/full-screenshot.png
307
+
308
+ 2단계 — 전체 트리 + CSS (수치 재료):
309
+ node "[FIGMA_SCRIPT]" tree {fileKey} {nodeId} --depth=10
310
+ → /tmp/{feature}/tree.json 에 저장
311
+
312
+ 3단계 — 전체 이미지 다운로드 (시각 재료):
313
+ node "[FIGMA_SCRIPT]" images {fileKey} {nodeId} --out=/tmp/{feature}/images/ --depth=10
314
+ → 모든 이미지 에셋 확보. 누락 0건, 0byte 0건.
315
+
316
+ 4단계 — 섹션별 스크린샷 (부분 정답):
317
+ 트리의 1depth 자식 프레임 각각:
318
+ node "[FIGMA_SCRIPT]" screenshot {fileKey} {child.nodeId} --out=/tmp/{feature}/sections/{child.name}.png
319
+ ```
320
+
321
+ ### 2-1-multi. 멀티 프레임 재료 확보 (2개 이상 URL)
322
+
323
+ ```
324
+ 각 URL에 대해 순차 추출 (Figma API rate limit: 요청 간 500ms 간격):
325
+ URL 1 → /tmp/{feature}/frame-1/ (full-screenshot, tree, images, sections)
326
+ URL 2 → /tmp/{feature}/frame-2/ (URL 1 완료 후 500ms 대기)
327
+ URL 3 → /tmp/{feature}/frame-3/ (URL 2 완료 후 500ms 대기)
328
+ ※ 추출 완료 후 후처리(섹션 분할 등)는 병렬 가능
329
+
330
+ 부분 실패 처리:
331
+ - 개별 URL 추출 실패 시 해당 frame만 건너뛰고 나머지 진행
332
+ - 실패한 frame은 사용자에게 즉시 보고: "frame-{N} 추출 실패: {에러 사유}" (API 에러, 권한 부족, 잘못된 nodeId 등)
333
+ - 성공한 frame ≥ 2: Phase 2.5 계속 진행
334
+ - 정확히 1개만 성공: 단일 프레임 모드로 폴백 (Phase 2.5 스킵)
335
+ - 0개 성공: 전체 실패 보고
336
+
337
+ 결과:
338
+ /tmp/{feature}/
339
+ ├── frame-1/ ← 메인 페이지
340
+ │ ├── full-screenshot.png
341
+ │ ├── tree.json
342
+ │ ├── images/
343
+ │ └── sections/
344
+ ├── frame-2/ ← 서브 페이지 1
345
+ │ └── ...
346
+ ├── frame-3/ ← 서브 페이지 2
347
+ │ └── ...
348
+ └── shared/ ← 공통 분석 결과 (Phase 2.5에서 생성)
349
+ ```
350
+
351
+ ### 2-2. 재료함 정리
352
+
353
+ ```
354
+ Phase 2 완료 시 /tmp/{feature}/ 에 다음이 준비되어야 함:
355
+
356
+ 단일 URL:
357
+ /tmp/{feature}/
358
+ ├── full-screenshot.png ← 전체 정답 사진
359
+ ├── tree.json ← 노드 트리 + CSS 수치
360
+ ├── images/ ← 모든 이미지 에셋
361
+ │ ├── hero-bg.png
362
+ │ ├── hero-title.png
363
+ │ ├── card-item-1.png
364
+ │ └── ...
365
+ └── sections/ ← 섹션별 정답 사진
366
+ ├── hero.png
367
+ ├── daily-checkin.png
368
+ ├── playtime-mission.png
369
+ └── ...
370
+
371
+ 멀티 URL: 위 2-1-multi 결과 구조 참조
372
+
373
+ 재료 목록 (material-inventory):
374
+ - 이미지: 파일명 + 크기 + 용도 추정 (BG/icon/title/decoration)
375
+ - 색상: tree.json에서 추출한 모든 고유 색상값
376
+ - 폰트: 사용된 font-family, size, weight 목록
377
+ - 텍스트: 모든 TEXT 노드의 characters 값
378
+ - 간격: padding, gap, margin 사용 빈도 높은 값
169
379
  ```
170
380
 
171
381
  ### 스케일 팩터
@@ -181,145 +391,477 @@ Phase 1 완료 조건:
181
391
 
182
392
  ---
183
393
 
184
- ## Phase 2: Design
394
+ ## Phase 2.5: 공통 패턴 분석 (멀티 프레임 전용)
185
395
 
186
- Phase 1 컴포넌트에 **이미지 + 스타일**을 입힌다.
187
- 모바일 퍼스트. base = 최소 뷰포트, @media (min-width:)로 확장.
396
+ **URL 2개 이상일 때만 실행. 단일 URL이면 Phase 3으로 건너뜀.**
397
+ **프레임 공통 요소를 식별하여 공유 컴포넌트로 추출한다.**
188
398
 
189
- 사용자에게 질문한다:
190
- - question: "베이스 디자인(모바일) Figma URL을 입력해주세요."
191
- - options 제공 금지 자유 텍스트 입력만 허용
399
+ ```
400
+ 1. 시각 비교 프레임의 스크린샷을 순차 Read:
401
+ - frame-1/full-screenshot.png frame-2/full-screenshot.png ...
402
+ - 시각적으로 반복되는 요소 식별:
403
+ 상단 영역 (GNB/Header), 하단 영역 (Footer),
404
+ 카드 패턴, 버튼 스타일, 섹션 레이아웃
405
+
406
+ 2. 구조 비교 — 각 tree.json의 1depth 자식 비교:
407
+ - 동일한 name 또는 prefix 일치 (예: "GNB", "Header", "Footer", "Nav")
408
+ - 동일한 size (width와 height 모두 ±10% 이내): 같은 컴포넌트 후보
409
+ - 동일한 CSS 패턴: 같은 스타일 사용 (색상, 폰트, 레이아웃)
410
+
411
+ 3. 공통 컴포넌트 후보 목록:
412
+ shared-components:
413
+ - name: GNB
414
+ frames: [frame-1, frame-2, frame-3]
415
+ consistency: 100% (모든 프레임에서 동일)
416
+ action: 공유 컴포넌트로 1회만 생성
417
+ - name: Footer
418
+ frames: [frame-1, frame-3]
419
+ consistency: 67% (2/3 프레임)
420
+ action: 공유 컴포넌트 + frame-2는 다른 Footer
421
+ - name: CardItem
422
+ frames: [frame-1, frame-2]
423
+ size-match: 95%
424
+ action: 공유 컴포넌트 (props로 변형)
425
+
426
+ 4. 공통 토큰 추출:
427
+ - 모든 프레임의 색상 팔레트 합집합 → 공유 _tokens.scss
428
+ - 모든 프레임의 폰트 목록 합집합
429
+ - 모든 프레임의 간격 패턴 합집합
430
+ → 프레임별 고유 값만 프레임 로컬 토큰으로 분리
431
+ ```
192
432
 
193
- → 2-1 → 2-2 섹션 루프 실행.
433
+ ---
194
434
 
195
- 완료 다시 질문:
196
- - question: "다음 브레이크포인트 디자인 URL을 입력해주세요. (없으면 '없음')"
197
- → URL 입력 시: 2-3 반응형 추가 후 다시 질문
198
- → "없음" 응답 시: Phase 3으로
435
+ ## Phase 3: 퍼즐 조립
199
436
 
200
- ### 2-1. SCSS Setup + 등록 (첫 섹션 전)
437
+ **Phase 1에서 만든 컴포넌트에 Phase 2의 재료로 디자인을 입힌다.**
438
+ **스크린샷을 보면서 퍼즐을 맞추듯 조립한다.**
439
+ **첫 섹션(Hero) 단독 완료 후 나머지 섹션 병렬 진행.**
201
440
 
441
+ **멀티 프레임 모드 시 조립 순서 변경:**
442
+ ```
443
+ 멀티 프레임 Phase 3 순서:
444
+
445
+ 1단계: 공유 컴포넌트 먼저 생성
446
+ - shared-components 목록의 컴포넌트를 components/shared/에 생성
447
+ - 가장 일관적인 프레임(shared-components 매칭 수가 가장 많은 프레임) 기준으로 조립
448
+ - 프레임별 변형은 props로 처리
449
+
450
+ 2단계: 프레임별 고유 섹션 조립
451
+ - frame-1 고유 섹션: 공유 컴포넌트 import + 고유 섹션 신규 생성
452
+ - frame-2 고유 섹션: 동일
453
+ - 각 프레임의 페이지 파일에서 공유 + 고유 컴포넌트 배치
454
+
455
+ 3단계: 스타일 구조
456
+ styles/{feature}/
457
+ ├── _tokens.scss ← 공유 토큰 (합집합)
458
+ ├── shared/ ← 공유 컴포넌트 스타일
459
+ │ ├── layout/_gnb.scss
460
+ │ └── components/_gnb.scss
461
+ ├── frame-1/ ← 메인 고유 스타일
462
+ │ ├── layout/
463
+ │ └── components/
464
+ └── frame-2/
465
+
466
+ 단일 URL: 기존 방식 그대로 (위 멀티 프레임 순서 무시)
202
467
  ```
203
- SCSS 파일 기본 내용 Write:
468
+
469
+ ### 3-0. SCSS Setup + 등록 (첫 섹션 전)
470
+
471
+ ```
472
+ Phase 1에서 생성한 빈 SCSS 파일에 기본 내용 Write:
204
473
  styles/{feature}/index.scss ← @import 진입점
205
- styles/{feature}/_tokens.scss ← 파일 (섹션마다 채움)
474
+ styles/{feature}/_tokens.scss ← 재료함에서 추출한 디자인 토큰
206
475
  styles/{feature}/_mixins.scss ← breakpoint mixin
207
- styles/{feature}/_base.scss ← 루트 클래스 (.winterPcbang 등)
208
- styles/{feature}/layout/ ← 디렉토리
209
- styles/{feature}/components/ ← 디렉토리
210
-
211
- 스타일 등록 (BLOCKING 미등록 섹션 루프 진행 금지):
476
+ styles/{feature}/_base.scss ← 루트 클래스
477
+
478
+ 토큰 매핑 (기존 토큰 우선 사용):
479
+ 1. /tmp/{feature}/project-tokens.json 을 Read로 로드
480
+ 2. Figma 재료함의 값에 대해 project-tokens에서 동일 검색:
481
+ - 색상: hex 정규화 후 완전 일치 (Figma RGBA 0-1 → hex, 3자리→6자리, 대소문자 무시)
482
+ ※ alpha < 1: 8자리 hex (#RRGGBBAA) 또는 rgba() 함수로 변환
483
+ - 간격: px 값 완전 일치 (rem→px: 1rem=16px)
484
+ - 폰트: family 이름 포함 매칭
485
+ 3. 매칭됨 → 기존 토큰 참조:
486
+ - SCSS: @use 'path' 로 기존 파일 import, $변수명 사용
487
+ - Tailwind: 해당 유틸리티 클래스 사용 (bg-blue-500)
488
+ - CSS Variables: var(--color-primary) 사용
489
+ 4. 매칭 안 됨 → 새 토큰 생성 (기존 방식)
490
+
491
+ _tokens.scss 구조:
492
+ // ─── 기존 토큰 참조 ────────────────────
493
+ @use '../../styles/variables' as v;
494
+
495
+ // ─── 매핑 (기존 토큰 → 피처 시맨틱) ────
496
+ $color-bg-primary: v.$color-navy; // 기존 재사용
497
+ $color-text-primary: v.$color-white; // 기존 재사용
498
+
499
+ // ─── 새 토큰 (매칭 안 된 값만) ─────────
500
+ $color-accent-gold: #ffd700; // 새 값
501
+ $space-section-gap: 42px; // 새 값
502
+
503
+ 매핑 결과 보고: "토큰 매핑: 12/18 매칭 (67%), 6개 새 토큰 생성"
504
+
505
+ 기존 토큰 파일 수정 금지 — 참조만 함
506
+ 같은 값의 토큰 중복 생성 금지
507
+ 새 토큰은 피처 스코프 네이밍 ($feature-color-xxx)
508
+
509
+ project-tokens.json 없는 경우 (기존 토큰 없음):
510
+ 기존 방식으로 전체 생성:
511
+ - 색상 → primitive ($color-navy: #0a1628) + semantic ($color-bg-primary)
512
+ - 폰트 → $font-pretendard, $font-size-md: 16px
513
+ - 간격 → $space-sm: 8px, $space-md: 16px
514
+
515
+ 스타일 등록 (BLOCKING):
212
516
  Grep "{feature}/index.scss" → 이미 등록되어 있으면 건너뜀.
517
+ 없으면 프로젝트 방식에 맞게 등록.
518
+ ```
213
519
 
214
- 신규 프로젝트 (--new):
215
- 루트 페이지 파일에서 직접 로드:
216
- pages/{feature}.vue → <style lang="scss" src="~/assets/scss/{feature}/index.scss" />
217
- app/{feature}/page.tsx → import '~/styles/{feature}/index.scss'
520
+ ### 3-0.5. 컴포넌트 매칭 (각 섹션 조립 전)
218
521
 
219
- ■ 기존 프로젝트 (업데이트):
220
- Grep "\.scss\|\.css" in nuxt.config.*/next.config.*/vite.config.*/main.ts
221
- → 기존 방식과 동일하게 등록
522
+ ```
523
+ /tmp/{feature}/component-index.json Read로 로드한다.
524
+
525
+ 각 섹션 스크린샷 분석 후, 기존 컴포넌트 매칭:
526
+
527
+ 1. 스크린샷에서 식별된 UI 요소를 component-index와 대조
528
+ 2. 매칭 판정 기준:
529
+ - 필수: name/role 유사성 (시각적 역할 일치)
530
+ 버튼 → BaseButton, 네비게이션 → GNB, 카드 → Card, 모달 → Modal
531
+ - 보조: props 호환성 (필요 props가 기존 컴포넌트에 존재)
532
+ - 보조: slots 호환성 (필요 slot이 존재)
533
+ - 불일치: props/slots 50% 미만 호환이면 매칭 거부 → 새로 생성
534
+ 3. 매칭된 컴포넌트: import하여 props 전달 (내부 수정 금지)
535
+ 4. 매칭 안 됨: 새로 생성 (강제 재사용 금지)
536
+
537
+ 재사용 시 스타일 충돌 방지:
538
+ ✅ 기존 컴포넌트 import하여 사용
539
+ ✅ props로 variant/size 등 커스터마이즈
540
+ ✅ 래퍼 클래스로 position/size만 오버라이드
541
+ ❌ 기존 컴포넌트 내부 스타일 수정 금지
542
+ ❌ 90% 유사한데 새로 만들기 금지
543
+ ```
544
+
545
+ ### 3-1. 섹션 조립 프로세스
222
546
 
223
- 검증: Grep "{feature}/index.scss" in 프로젝트 전체 → 0건이면 실패
547
+ ```
548
+ 각 섹션에 대해:
549
+
550
+ 1. 정답 확인 — 섹션 스크린샷을 Read로 본다
551
+ /tmp/{feature}/sections/{section}.png
552
+ → "이 화면처럼 만들어야 한다"
553
+
554
+ 2. 재료 확인 — 이 섹션에 필요한 재료 목록 확인
555
+ - 이미지: /tmp/{feature}/images/ 에서 해당 파일들
556
+ - CSS 수치: tree.json에서 해당 노드의 정확한 값
557
+ - 텍스트: TEXT 노드의 characters
558
+
559
+ 3. Phase 1 컴포넌트에 디자인 입히기
560
+ a. 이미지 복사: images/ → static/images/{feature}/
561
+ b. template 업데이트:
562
+ - Phase 1의 기능 요소(v-for, @click, v-if, $emit) 보존
563
+ - 스크린샷을 보고 시각 구조에 맞게 HTML 재구성
564
+ - Figma 레이어 구조를 HTML로 변환하지 않음 (시각 기반)
565
+ - 재료함의 정확한 이미지 경로 사용
566
+ - 재료함의 정확한 텍스트 사용
567
+ c. script 보존:
568
+ - Phase 1의 JSDoc, 인터페이스, 핸들러 유지
569
+ d. SCSS 작성: 재료함의 정확한 CSS 수치 사용 (scaleFactor 적용)
570
+ - layout/_{section}.scss ← 포지션, 사이즈, 플렉스
571
+ - components/_{section}.scss ← 폰트, 색상, 보더
572
+ - _tokens.scss에 새 토큰 추가
573
+
574
+ 4. 즉시 검증 — 스크린샷과 비교
575
+ - 이미지 빠진 거 없나?
576
+ - 텍스트 빠진 거 없나?
577
+ - 레이아웃 구조가 스크린샷과 맞나?
578
+ - Phase 1의 기능 요소가 보존되었나?
224
579
  ```
225
580
 
226
- ### 2-2. 섹션 루프
581
+ ### 3-2. 코드 작성 규칙
582
+
583
+ ```
584
+ 컴포넌트 (Vue 예시):
585
+ <template>
586
+ 스크린샷에서 보이는 시각 구조대로 작성.
587
+ Figma 레이어 구조 무시.
588
+ Phase 1의 기능 요소(v-for, @click, v-if) 보존.
589
+ 시맨틱 HTML 사용 (<section>, <h2>, <ul>, <button>).
590
+ 이미지 경로: /images/{feature}/파일명.png
591
+ 텍스트: tree.json의 TEXT 노드 characters 값 그대로.
592
+
593
+ <script setup>
594
+ Phase 1의 JSDoc + 인터페이스 + 핸들러 보존.
595
+ 새로운 데이터/상태 추가 시 기존과 병합.
596
+
597
+ <style> 블록 없음 — 외부 SCSS만.
598
+
599
+ SCSS (vibe.figma.convert 참조):
600
+ layout/ → position, display, flex, width, height, padding, gap
601
+ components/ → font, color, border, shadow, opacity
602
+ 모든 수치는 재료함의 정확한 값 × scaleFactor.
603
+ 추정 금지 — 값이 없으면 tree.json에서 다시 찾는다.
604
+ ```
227
605
 
228
- **섹션별로 a→d 순서는 지키되, 섹션 간 병렬 처리 허용.**
229
- **단, 첫 번째 섹션(Hero)은 단독 완료 후 나머지를 병렬로.**
606
+ ### 반응형 (추가 URL)
230
607
 
231
- #### a. render 실행 (BLOCKING)
608
+ ```
609
+ 완료 후 질문: "다음 브레이크포인트 디자인 URL을 입력해주세요. (없으면 '없음')"
232
610
 
611
+ URL 있으면:
612
+ 1. 새 URL에서 재료 확보 (Phase 1 반복)
613
+ 2. 새 스크린샷과 기존 코드 비교
614
+ 3. @media (min-width: $bp-desktop) 오버라이드만 추가
615
+ 4. 기존 모바일 코드 삭제 금지
233
616
  ```
234
- # [FIGMA_SCRIPT] = ~/.vibe/hooks/scripts/figma-extract.js
235
- node "[FIGMA_SCRIPT]" render {fileKey} {섹션.nodeId} --out=/tmp/{feature}-{section}/ --depth=10
236
617
 
237
- 한 번 호출로 아래 파일이 생성됨:
238
- /tmp/{feature}-{section}/
239
- ├── {section}.html ← HTML 구조 (class명 포함)
240
- ├── {section}.scss ← 전체 SCSS (모든 CSS 속성)
241
- ├── {section}.json ← 원본 트리 JSON
242
- ├── {section}-screenshot.png ← 섹션 스크린샷 (시각 기준점)
243
- └── images/
244
- ├── {section}-bg-composite.png ← 복합 BG 합성 이미지
245
- ├── {section}-title.png ← 이름 있는 이미지
246
- └── ...
618
+ ---
619
+
620
+ ## Phase 3.5: 컴파일 게이트
247
621
 
248
- 이미지는 노드 name 기반 파일명 (해시 아님).
249
- 복합 BG(3개+ 하위 레이어)는 자동으로 합성 스크린샷 생성.
250
- 인스턴스 내부 자식도 depth로 전부 조회.
622
+ **Phase 3 퍼즐 조립 완료 후, 브라우저 검증 전에 컴파일 성공을 보장한다.**
623
+ **컴파일 에러는 스킵 불가 반드시 수정 또는 사용자 보고.**
624
+ **Phase 3.5 실패 Phase 4 진행 불가 (hard gate).**
625
+
626
+ ```
627
+ 자동 반복: 컴파일 성공까지. 최대 3라운드.
251
628
  ```
252
629
 
253
- #### b. 생성물 적용
630
+ ### 3.5-0. 베이스라인 캡처 (Phase 3 변경 전)
254
631
 
255
632
  ```
256
- render 출력물을 프로젝트에 적용:
633
+ Phase 3 시작 전에 기존 프로젝트의 에러를 캡처:
634
+ 1. 타입 체크 베이스라인: (3.5-1에서 선택한 동일 명령 사용) > /tmp/{feature}/baseline-typecheck.txt 2>&1
635
+ 2. 빌드 베이스라인: npm run build > /tmp/{feature}/baseline-build.txt 2>&1
257
636
 
258
- 1. 이미지 복사:
259
- /tmp/{feature}-{section}/images/* static/images/{feature}/
637
+ Phase 3.5에서는 baseline에 없는 **새로 발생한 에러만** 수정 대상.
638
+ baseline에 존재하던 에러는 무시하고 별도 보고 ("기존 에러 {N}개 유지").
639
+ vibe.figma가 생성/수정한 파일 외의 에러는 자동 수정 금지.
640
+ ```
260
641
 
261
- 2. SCSS 적용:
262
- {section}.scss를 읽고 scaleFactor 적용하여 프로젝트 SCSS에 Write:
263
- styles/{feature}/layout/_{section}.scss ← position, display, flex, width, height
264
- styles/{feature}/components/_{section}.scss ← font, color, border, shadow
265
- styles/{feature}/_tokens.scss ← 새 값 추가 (primitive/semantic, vibe.figma.convert 참조)
266
- index.scss에 새 섹션 @import 추가.
642
+ ### 3.5-1. TypeScript 컴파일 체크
267
643
 
268
- 3. template 업데이트:
269
- {section}.html을 읽고 Phase 1 컴포넌트에 반영:
270
- - HTML 구조를 프로젝트 스택으로 변환 (class 유지)
271
- - 이미지 경로를 static/images/{feature}/ 교체
272
- - Phase 1 기능 요소(v-for, @click, v-if, $emit) 재배치
273
- - script(JSDoc, 인터페이스, 핸들러) 보존
644
+ ```
645
+ 1. 프로젝트 타입 체커 감지 → 실행:
646
+ - package.json scripts에 type-check 또는 typecheck 존재 → npm run type-check 사용
647
+ - vue-tsc 설치 확인 (Vue 프로젝트) → npx vue-tsc --noEmit 2>&1
648
+ - svelte-check 설치 확인 (Svelte 프로젝트) → npx svelte-check 2>&1
649
+ - 해당 없음 → fallback: npx tsc --noEmit 2>&1
650
+ → 에러 0개: PASS → 다음 단계
651
+ → 에러 있음: 에러 메시지 파싱 → 자동 수정
652
+
653
+ 2. 에러 파싱:
654
+ 각 에러에서 추출: 파일 경로, 줄 번호, 에러 코드, 메시지
655
+ 예: "src/components/Hero.tsx(15,3): error TS2322: Type 'string' is not assignable to type 'number'"
656
+
657
+ 3. 자동 수정 (에러 유형별):
658
+ - TS2322 (타입 불일치): prop 타입을 올바르게 수정
659
+ - TS2304 (이름 없음): import 추가
660
+ - TS2339 (프로퍼티 없음): interface에 프로퍼티 추가
661
+ - TS7006 (암시적 any): 타입 어노테이션 추가
662
+ - 기타: Read로 해당 파일+줄 확인 → 컨텍스트 기반 수정
663
+ ```
664
+
665
+ ### 3.5-2. 빌드 체크
274
666
 
275
- 4. 스크린샷 참조:
276
- {section}-screenshot.png과 비교하면서 작업
667
+ ```
668
+ 1. npm run build 실행:
669
+ Bash: npm run build 2>&1
670
+ → 성공: PASS → 다음 단계
671
+ → 실패: 에러 메시지 파싱 → 자동 수정
672
+ → 타임아웃: 최대 120초 (초과 시 해당 라운드 실패 처리)
673
+
674
+ 2. 일반적 빌드 에러 처리:
675
+ - SCSS 컴파일 에러: 변수명/import 오류 수정
676
+ - Module not found: import 경로 수정 (.js 확장자 등)
677
+ - ESLint 에러 (--max-warnings 초과): 자동 수정 가능한 것 처리
277
678
  ```
278
679
 
279
- #### c. 섹션 검증
680
+ ### 3.5-3. dev 서버 시작 확인
280
681
 
281
682
  ```
282
- Grep 체크:
283
- 'src=""' in 컴포넌트 파일0건
284
- "<style" in 컴포넌트 파일 0건
683
+ 1. dev 서버 시작 + PID 캡처:
684
+ Bash: npm run dev & echo $! DEV_PID 저장
685
+ localhost 포트 자동 감지: npm run dev stdout에서 localhost:\d+ 또는 port \d+ 파싱
686
+ 감지 실패 시 기본값 3000, 5173, 4173 순서 시도
687
+ → 포트 폴링 (3초 간격, 최대 30초 대기)
688
+ → 성공: Phase 4 진행 (Phase 4 완료 후 정리)
689
+ → 실패: kill $DEV_PID → 에러 로그 확인 → 수정 → 재시도
690
+
691
+ 2. 프로세스 정리 규칙:
692
+ - Phase 4 완료 또는 3라운드 실패 시 반드시 정리
693
+ - 정리 순서: kill $DEV_PID → 3초 대기 → kill -9 $DEV_PID (응답 없으면)
694
+ - lsof -i :{port} -t 로 포트 점유 프로세스 확인 후 추가 정리
695
+ ※ spawned child process만 대상 — 관련 없는 프로세스 kill 금지
696
+ - interrupt 시에도 cleanup 보장
697
+ ```
698
+
699
+ ### 3.5-4. 수정 루프
700
+
701
+ ```
702
+ 라운드 1~3:
703
+ 1. tsc → build → dev 순서로 체크
704
+ 2. 첫 번째 실패 단계의 에러 수정
705
+ 3. 수정 후 해당 단계부터 재체크
706
+ 4. 모든 단계 통과: Phase 4 진행
707
+
708
+ 라운드 종료 조건:
709
+ - 3라운드 후 실패: 에러 목록 + 시도한 수정을 사용자에게 보고
710
+ - 같은 에러 반복: 해당 에러 스킵 불가 → 사용자 보고 (컴파일 에러는 스킵 불가)
711
+
712
+ 컴파일 게이트 결과 보고:
713
+
714
+ ✅ 통과:
715
+ "Phase 3.5: 컴파일 게이트 PASS (라운드 {N})"
716
+ - tsc: 0 errors
717
+ - build: success
718
+ - dev server: running on localhost:{port}
719
+
720
+ ❌ 실패 (3라운드 후):
721
+ "Phase 3.5: 컴파일 게이트 FAIL"
722
+ - 남은 에러 목록 (파일, 줄, 메시지)
723
+ - 시도한 수정 내역
724
+ - 사용자 수동 수정 필요
725
+ → Phase 4 진행하지 않음
726
+ ```
727
+
728
+ ---
285
729
 
286
- Read 체크:
287
- □ 외부 SCSS 파일에 font-size, color 존재
288
- □ 이미지 파일 존재 + 0byte 없음
730
+ ## Phase 4: 검증 루프
289
731
 
290
- 스크린샷 비교:
291
- {section}-screenshot.png vs dev 서버 주요 차이 없음
732
+ **Puppeteer + CDP로 실제 렌더링 결과를 확인하며 자동 수정한다.**
733
+ **사람이 브라우저 보면서 고치는 것과 동일한 루프.**
734
+ **인프라: `src/infra/lib/browser/` (범용 UI 검증 도구)**
292
735
 
293
- 실패 → 수정 → 재검증
736
+ ```
737
+ 자동 반복: P1=0 될 때까지. 최대 3라운드.
294
738
  ```
295
739
 
296
- ### 2-3. 반응형 (두 번째 URL부터)
740
+ ### 4-0. 환경 준비
297
741
 
298
742
  ```
299
- 번째 이후 URL: 기존 스타일 유지 + @media 추가만.
743
+ 1. dev 서버 시작:
744
+ Phase 3.5에서 이미 시작된 dev 서버 사용 (재시작 불필요)
745
+ → localhost:{port} 확인
746
+
747
+ 2. Puppeteer 브라우저 시작:
748
+ import { launchBrowser, openPage } from 'src/infra/lib/browser'
749
+ const browser = await launchBrowser({ headless: true })
750
+ const page = await openPage(browser, 'http://localhost:3000/{feature}', {
751
+ width: 480, // 모바일 퍼스트 (또는 타겟 뷰포트)
752
+ height: 960,
753
+ })
754
+ ```
755
+
756
+ ### 4-1. 렌더링 스크린샷 vs Figma 스크린샷
300
757
 
301
- 같은 값 → 유지
302
- 다른 px @media (min-width: $bp-desktop) 오버라이드
303
- 다른 레이아웃 → @media 블록 추가
304
- 다른 배경 이미지 → @media 이미지 분기
305
- 기존 코드/스타일 삭제 금지
758
+ ```
759
+ import { captureScreenshot, compareScreenshots } from 'src/infra/lib/browser'
760
+
761
+ 섹션에 대해:
762
+ 1. 렌더링 결과 스크린샷 캡처:
763
+ await captureScreenshot(page, {
764
+ outPath: '/tmp/{feature}/rendered-{section}.png',
765
+ selector: '.{section}Section', // Phase 1에서 만든 클래스
766
+ })
767
+
768
+ 2. Figma 원본과 픽셀 비교:
769
+ const diff = await compareScreenshots(
770
+ '/tmp/{feature}/sections/{section}.png', // Figma 원본
771
+ '/tmp/{feature}/rendered-{section}.png', // 렌더링 결과
772
+ '/tmp/{feature}/diff-{section}.png', // 차이 시각화
773
+ )
774
+
775
+ 3. diff 이미지를 Read로 확인:
776
+ → 빨간색 영역 = 차이 나는 부분
777
+ → diffRatio > 0.1 이면 P1 이슈
306
778
  ```
307
779
 
308
- ---
780
+ ### 4-2. CSS 수치 정밀 비교
781
+
782
+ ```
783
+ import { getComputedStyles, compareStyles, diffsToIssues } from 'src/infra/lib/browser'
784
+
785
+ 각 섹션의 주요 요소에 대해:
786
+ 1. 렌더링된 computed CSS 추출:
787
+ const actual = await getComputedStyles(page, '.heroTitle', [
788
+ 'font-size', 'color', 'width', 'height', 'padding', 'margin',
789
+ 'background-color', 'border-radius', 'gap',
790
+ ])
791
+
792
+ 2. Figma 재료함의 기대값과 비교:
793
+ // tree.json에서 해당 노드의 CSS 수치 (scaleFactor 적용 후)
794
+ const expected = { 'font-size': '16px', 'color': '#ffffff', 'width': '465px' }
795
+ const diffs = compareStyles(expected, actual)
796
+
797
+ 3. 차이 → 이슈 변환:
798
+ const issues = diffsToIssues(diffs)
799
+ → delta > 4px: P1 (레이아웃 영향)
800
+ → delta ≤ 4px: P2 (미세 차이)
801
+ ```
802
+
803
+ ### 4-3. 이미지·텍스트 누락 체크
804
+
805
+ ```
806
+ import { extractImages, extractTextContent } from 'src/infra/lib/browser'
807
+
808
+ 1. 이미지 로드 상태 확인:
809
+ const images = await extractImages(page)
810
+ images.filter(img => !img.loaded)
811
+ → 로드 실패 이미지 = P1 (이미지 누락)
812
+ → src="" 이미지 = P1 (빈 경로)
813
+
814
+ 2. 텍스트 콘텐츠 확인:
815
+ const texts = await extractTextContent(page)
816
+ → 재료함의 TEXT 노드 characters와 대조
817
+ → 누락된 텍스트 = P1
818
+ ```
819
+
820
+ ### 4-4. 자동 수정 루프
821
+
822
+ ```
823
+ 라운드 1~3:
824
+ 1. 4-1 ~ 4-3 실행 → 이슈 목록 수집
825
+ 2. P1 이슈 우선 수정:
826
+ - 이미지 누락 → 이미지 경로 확인, static/ 에 파일 존재 확인
827
+ - 레이아웃 다름 → 스크린샷 diff 이미지 + computed CSS로 원인 파악
828
+ - 텍스트 누락 → 재료함의 정확한 텍스트 삽입
829
+ - CSS 수치 틀림 → 재료함(tree.json)의 정확한 값으로 교체
830
+ ⚠️ 추정으로 수정하지 않는다. 반드시 재료함 참조.
831
+ 3. 수정 후 컴파일 재검증:
832
+ Bash: npx tsc --noEmit 2>&1 (또는 3.5-1에서 선택한 타입 체커)
833
+ → 시각 수정이 타입 에러를 유발하면 즉시 타입 에러 수정 후 진행
834
+ 4. 페이지 리로드 → 다시 캡처 → 비교
835
+ 5. P1=0 이면 종료
836
+
837
+ 라운드 종료 조건:
838
+ - P1=0: 성공 → 브라우저 종료, 결과 보고
839
+ - 3라운드 후 P1 남음: TODO 목록으로 사용자에게 보고
840
+ - 같은 이슈가 반복: 해당 이슈 스킵, 다음 이슈로
841
+
842
+ 결과 보고:
843
+ - 수정한 파일 목록
844
+ - 남은 P2 이슈 목록 (선택적 수정)
845
+ - 최종 diff 스크린샷 경로
846
+ ```
847
+
848
+ ### 4-5. 반응형 검증 (추가 뷰포트)
849
+
850
+ ```
851
+ 모바일 검증 완료 후, 추가 브레이크포인트가 있으면:
852
+
853
+ await page.setViewport({ width: 1920, height: 1080 })
854
+ await page.reload({ waitUntil: 'networkidle0' })
855
+
856
+ → 데스크탑 Figma 스크린샷과 동일한 4-1 ~ 4-4 루프 반복
857
+ ```
309
858
 
310
- ## Phase 3: Verification
859
+ ### 4-6. 브라우저 정리
311
860
 
312
861
  ```
313
- Grep 체크:
314
- □ "<style" in components/{feature}/ → 0건
315
- □ 'src=""' in components/{feature}/ → 0건
316
- □ Glob: images/{feature}/ → 이미지 파일 존재
862
+ import { closeBrowser } from 'src/infra/lib/browser'
317
863
 
318
- 시각 검증:
319
- 섹션 스크린샷:
320
- node "[FIGMA_SCRIPT]" screenshot {fileKey} {nodeId} --out=/tmp/{section}.png
321
- → dev 서버/preview와 비교
322
- P1 (필수): 이미지 누락, 레이아웃 구조 다름, 텍스트 스타일 미적용
323
- P2 (권장): 미세 간격, 미세 색상 차이
324
- → P1 수정 → 재검증 (P1=0 될 때까지)
864
+ 검증 완료 후:
865
+ await closeBrowser()
866
+ dev 서버 종료 (필요 시)
325
867
  ```