@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.
package/README.md CHANGED
@@ -6,7 +6,7 @@
6
6
  [![TypeScript](https://img.shields.io/badge/TypeScript-5.5+-blue)](https://www.typescriptlang.org/)
7
7
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
8
8
 
9
- **One install adds 56 agents, 36 skills, multi-LLM orchestration, and automated quality gates to your AI coding workflow.**
9
+ **One install adds 56 agents, 45 skills, multi-LLM orchestration, and automated quality gates to your AI coding workflow.**
10
10
 
11
11
  Works with Claude Code, Codex, Cursor, and Gemini CLI.
12
12
 
@@ -131,13 +131,17 @@ Event Content, Event Image, Event Speaker, Event Ops, Event Comms, Event Schedul
131
131
 
132
132
  ---
133
133
 
134
- ## Skills (36)
134
+ ## Skills (45)
135
135
 
136
- Domain-specific skill modules auto-installed based on detected stack.
136
+ Domain-specific skill modules auto-installed based on detected stack. Classified into 3 tiers to prevent context overload.
137
137
 
138
- **Core (15):** Core Capabilities, Parallel Research, Commit Push PR, Git Worktree, Handoff, Priority Todos, Tool Fallback, Context7, Tech Debt, Characterization Test, Agents MD, Claude MD Guide, Exec Plan, Arch Guard, Capability Loop
138
+ **Core (4):** Tech Debt, Characterization Test, Arch Guard, Exec Plan
139
139
 
140
- **Design (7):** Frontend Design, UI/UX Pro Max, Design Teach, Design Audit, Design Critique, Design Polish, Design Normalize
140
+ **Standard (11):** Parallel Research, Handoff, Priority Todos, Agents MD, Claude MD Guide, Capability Loop, Design Teach, Vibe Figma, Vibe Figma Extract, Vibe Figma Convert, Vibe Docs
141
+
142
+ **Optional (4):** Commit Push PR, Git Worktree, Tool Fallback, Context7
143
+
144
+ **Design (8):** UI/UX Pro Max, Design Audit, Design Critique, Design Polish, Design Normalize, Design Distill, Brand Assets, SEO Checklist
141
145
 
142
146
  **Domain (3):** Commerce Patterns, E2E Commerce, Video Production
143
147
 
@@ -145,7 +149,9 @@ Domain-specific skill modules auto-installed based on detected stack.
145
149
 
146
150
  **Event (3):** Event Planning, Event Comms, Event Ops
147
151
 
148
- **Stack-Specific (5):** TypeScript Advanced Types, Vercel React Best Practices, SEO Checklist, Brand Assets, Design Distill
152
+ **Stack-Specific (2):** TypeScript Advanced Types, Vercel React Best Practices
153
+
154
+ **Figma Pipeline (7):** Figma Rules, Figma Pipeline, Figma Frame, Figma Style, Figma Analyze, Figma Consolidate, Figma Codegen
149
155
 
150
156
  ### External Skills (skills.sh)
151
157
 
@@ -262,7 +268,7 @@ const runner = skills.resolve('review');
262
268
 
263
269
  ---
264
270
 
265
- ## Hooks (16 scripts)
271
+ ## Hooks (21 scripts)
266
272
 
267
273
  | Event | Script | Role |
268
274
  |-------|--------|------|
@@ -272,9 +278,11 @@ const runner = skills.resolve('review');
272
278
  | PostToolUse | `post-edit.js` | Git index update |
273
279
  | UserPromptSubmit | `prompt-dispatcher.js` | Command routing |
274
280
  | UserPromptSubmit | `keyword-detector.js` | Magic keyword detection |
281
+ | UserPromptSubmit | `llm-orchestrate.js` | Multi-LLM dispatch |
275
282
  | Notification | `context-save.js` | Auto-save at 80/90/95% context |
283
+ | Notification | `stop-notify.js` | Session end notification |
276
284
 
277
- Additional: `llm-orchestrate.js`, `codex-review-gate.js`, `codex-detect.js`, `sentinel-guard.js`, `skill-injector.js`, `evolution-engine.js`, `hud-status.js`, `stop-notify.js`
285
+ Additional: `codex-review-gate.js`, `codex-detect.js`, `sentinel-guard.js`, `skill-injector.js`, `evolution-engine.js`, `hud-status.js`, `auto-commit.js`, `auto-format.js`, `auto-test.js`, `command-log.js`, `pr-test-gate.js`, `figma-extract.js`
278
286
 
279
287
  ---
280
288
 
@@ -397,6 +405,10 @@ vibe figma status|logout # Token management
397
405
  vibe telegram setup|chat|status
398
406
  vibe slack setup|channel|status
399
407
 
408
+ # Diagnostics
409
+ vibe config show # Unified config view (global + project)
410
+ vibe stats [--week|--quality] # Usage telemetry summary
411
+
400
412
  # Other
401
413
  vibe env import [path] # Migrate .env → config.json
402
414
  vibe help / version
@@ -0,0 +1,26 @@
1
+ /**
2
+ * UI 캡처 — 스크린샷, DOM 추출, computed CSS 조회
3
+ *
4
+ * CDP(Chrome DevTools Protocol)를 통해 렌더링된 결과를 정밀하게 추출.
5
+ */
6
+ import type { CaptureScreenshotOptions, ElementComputedStyle } from './types.js';
7
+ /** 페이지 스크린샷 캡처 */
8
+ export declare function captureScreenshot(page: unknown, options: CaptureScreenshotOptions): Promise<string>;
9
+ /** 요소의 computed CSS + bounding box 추출 */
10
+ export declare function getComputedStyles(page: unknown, selector: string, properties: string[]): Promise<ElementComputedStyle | null>;
11
+ /** 페이지의 모든 매칭 요소에서 computed CSS 일괄 추출 */
12
+ export declare function getComputedStylesBatch(page: unknown, selector: string, properties: string[]): Promise<ElementComputedStyle[]>;
13
+ /** 페이지의 모든 텍스트 콘텐츠 추출 */
14
+ export declare function extractTextContent(page: unknown, selector?: string): Promise<Array<{
15
+ selector: string;
16
+ text: string;
17
+ }>>;
18
+ /** 페이지의 모든 이미지 src + 로드 상태 확인 */
19
+ export declare function extractImages(page: unknown): Promise<Array<{
20
+ src: string;
21
+ alt: string;
22
+ loaded: boolean;
23
+ width: number;
24
+ height: number;
25
+ }>>;
26
+ //# sourceMappingURL=capture.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"capture.d.ts","sourceRoot":"","sources":["../../../../src/infra/lib/browser/capture.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EACV,wBAAwB,EACxB,oBAAoB,EACrB,MAAM,YAAY,CAAC;AAEpB,kBAAkB;AAClB,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,OAAO,EACb,OAAO,EAAE,wBAAwB,GAChC,OAAO,CAAC,MAAM,CAAC,CAyBjB;AAED,yCAAyC;AACzC,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,OAAO,EACb,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAAE,GACnB,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC,CAmCtC;AAED,yCAAyC;AACzC,wBAAsB,sBAAsB,CAC1C,IAAI,EAAE,OAAO,EACb,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAAE,GACnB,OAAO,CAAC,oBAAoB,EAAE,CAAC,CAkCjC;AAED,yBAAyB;AACzB,wBAAsB,kBAAkB,CACtC,IAAI,EAAE,OAAO,EACb,QAAQ,CAAC,EAAE,MAAM,GAChB,OAAO,CAAC,KAAK,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC,CAoBpD;AAED,iCAAiC;AACjC,wBAAsB,aAAa,CACjC,IAAI,EAAE,OAAO,GACZ,OAAO,CAAC,KAAK,CAAC;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC,CAiB9F"}
@@ -0,0 +1,115 @@
1
+ /**
2
+ * UI 캡처 — 스크린샷, DOM 추출, computed CSS 조회
3
+ *
4
+ * CDP(Chrome DevTools Protocol)를 통해 렌더링된 결과를 정밀하게 추출.
5
+ */
6
+ /** 페이지 스크린샷 캡처 */
7
+ export async function captureScreenshot(page, options) {
8
+ const p = page;
9
+ if (options.selector) {
10
+ const element = await p.$(options.selector);
11
+ if (!element) {
12
+ throw new Error(`Element not found: ${options.selector}`);
13
+ }
14
+ const el = element;
15
+ await el.screenshot({
16
+ path: options.outPath,
17
+ type: options.format ?? 'png',
18
+ });
19
+ }
20
+ else {
21
+ await p.screenshot({
22
+ path: options.outPath,
23
+ fullPage: options.fullPage !== false,
24
+ type: options.format ?? 'png',
25
+ });
26
+ }
27
+ return options.outPath;
28
+ }
29
+ /** 요소의 computed CSS + bounding box 추출 */
30
+ export async function getComputedStyles(page, selector, properties) {
31
+ const p = page;
32
+ const result = await p.evaluate((...args) => {
33
+ const sel = args[0];
34
+ const props = args[1];
35
+ const el = document.querySelector(sel);
36
+ if (!el)
37
+ return null;
38
+ const computed = window.getComputedStyle(el);
39
+ const styles = {};
40
+ for (const prop of props) {
41
+ styles[prop] = computed.getPropertyValue(prop);
42
+ }
43
+ const rect = el.getBoundingClientRect();
44
+ return {
45
+ selector: sel,
46
+ styles,
47
+ box: {
48
+ x: Math.round(rect.x),
49
+ y: Math.round(rect.y),
50
+ width: Math.round(rect.width),
51
+ height: Math.round(rect.height),
52
+ },
53
+ };
54
+ }, selector, properties);
55
+ return result;
56
+ }
57
+ /** 페이지의 모든 매칭 요소에서 computed CSS 일괄 추출 */
58
+ export async function getComputedStylesBatch(page, selector, properties) {
59
+ const p = page;
60
+ const results = await p.evaluate((...args) => {
61
+ const sel = args[0];
62
+ const props = args[1];
63
+ const elements = document.querySelectorAll(sel);
64
+ return Array.from(elements).map((el, idx) => {
65
+ const computed = window.getComputedStyle(el);
66
+ const styles = {};
67
+ for (const prop of props) {
68
+ styles[prop] = computed.getPropertyValue(prop);
69
+ }
70
+ const rect = el.getBoundingClientRect();
71
+ return {
72
+ selector: `${sel}:nth-child(${idx + 1})`,
73
+ styles,
74
+ box: {
75
+ x: Math.round(rect.x),
76
+ y: Math.round(rect.y),
77
+ width: Math.round(rect.width),
78
+ height: Math.round(rect.height),
79
+ },
80
+ };
81
+ });
82
+ }, selector, properties);
83
+ return results;
84
+ }
85
+ /** 페이지의 모든 텍스트 콘텐츠 추출 */
86
+ export async function extractTextContent(page, selector) {
87
+ const p = page;
88
+ const results = await p.evaluate((...args) => {
89
+ const sel = args[0];
90
+ const elements = document.querySelectorAll(sel);
91
+ return Array.from(elements)
92
+ .filter(el => el.textContent?.trim())
93
+ .map((el, idx) => ({
94
+ selector: `${sel}:nth-child(${idx + 1})`,
95
+ text: el.textContent?.trim() ?? '',
96
+ }));
97
+ }, selector ?? 'h1, h2, h3, h4, h5, h6, p, span, a, button, li, label');
98
+ return results;
99
+ }
100
+ /** 페이지의 모든 이미지 src + 로드 상태 확인 */
101
+ export async function extractImages(page) {
102
+ const p = page;
103
+ const results = await p.evaluate(() => {
104
+ const images = document.querySelectorAll('img');
105
+ return Array.from(images).map(img => ({
106
+ src: img.src,
107
+ alt: img.alt,
108
+ loaded: img.complete && img.naturalWidth > 0,
109
+ width: img.naturalWidth,
110
+ height: img.naturalHeight,
111
+ }));
112
+ });
113
+ return results;
114
+ }
115
+ //# sourceMappingURL=capture.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"capture.js","sourceRoot":"","sources":["../../../../src/infra/lib/browser/capture.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * UI 비교 — 스크린샷 diff, CSS 수치 비교
3
+ *
4
+ * Figma 원본 스크린샷 vs 렌더링 결과 비교,
5
+ * Figma CSS 수치 vs computed CSS 비교.
6
+ */
7
+ import type { ElementComputedStyle, ScreenshotDiff, StyleDiff, VerificationIssue } from './types.js';
8
+ /**
9
+ * 두 PNG 파일의 픽셀 단위 비교
10
+ *
11
+ * 정밀한 비교를 위해 pixelmatch 사용 (optional dependency).
12
+ * 없으면 파일 크기 기반 근사 비교로 폴백.
13
+ */
14
+ export declare function compareScreenshots(expectedPath: string, actualPath: string, diffOutputPath?: string): Promise<ScreenshotDiff>;
15
+ /** CSS 수치 비교 — Figma 기대값 vs 실제 렌더링 값 */
16
+ export declare function compareStyles(expected: Record<string, string>, actual: ElementComputedStyle): StyleDiff[];
17
+ /** StyleDiff → VerificationIssue 변환 */
18
+ export declare function diffsToIssues(diffs: StyleDiff[]): VerificationIssue[];
19
+ //# sourceMappingURL=compare.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compare.d.ts","sourceRoot":"","sources":["../../../../src/infra/lib/browser/compare.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EACV,oBAAoB,EACpB,cAAc,EACd,SAAS,EACT,iBAAiB,EAClB,MAAM,YAAY,CAAC;AAEpB;;;;;GAKG;AACH,wBAAsB,kBAAkB,CACtC,YAAY,EAAE,MAAM,EACpB,UAAU,EAAE,MAAM,EAClB,cAAc,CAAC,EAAE,MAAM,GACtB,OAAO,CAAC,cAAc,CAAC,CAiDzB;AAsBD,wCAAwC;AACxC,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAChC,MAAM,EAAE,oBAAoB,GAC3B,SAAS,EAAE,CA6Bb;AA2BD,uCAAuC;AACvC,wBAAgB,aAAa,CAAC,KAAK,EAAE,SAAS,EAAE,GAAG,iBAAiB,EAAE,CAerE"}
@@ -0,0 +1,133 @@
1
+ /**
2
+ * UI 비교 — 스크린샷 diff, CSS 수치 비교
3
+ *
4
+ * Figma 원본 스크린샷 vs 렌더링 결과 비교,
5
+ * Figma CSS 수치 vs computed CSS 비교.
6
+ */
7
+ import { readFileSync } from 'node:fs';
8
+ /**
9
+ * 두 PNG 파일의 픽셀 단위 비교
10
+ *
11
+ * 정밀한 비교를 위해 pixelmatch 사용 (optional dependency).
12
+ * 없으면 파일 크기 기반 근사 비교로 폴백.
13
+ */
14
+ export async function compareScreenshots(expectedPath, actualPath, diffOutputPath) {
15
+ try {
16
+ const { default: pixelmatch } = await import('pixelmatch');
17
+ const { PNG } = await import('pngjs');
18
+ const img1 = PNG.sync.read(readFileSync(expectedPath));
19
+ const img2 = PNG.sync.read(readFileSync(actualPath));
20
+ const width = Math.max(img1.width, img2.width);
21
+ const height = Math.max(img1.height, img2.height);
22
+ // 사이즈 맞추기 (작은 쪽을 확장)
23
+ const canvas1 = createCanvas(img1, width, height);
24
+ const canvas2 = createCanvas(img2, width, height);
25
+ const diffData = new Uint8Array(width * height * 4);
26
+ const diffPixels = pixelmatch(canvas1, canvas2, diffData, width, height, { threshold: 0.1 });
27
+ const totalPixels = width * height;
28
+ if (diffOutputPath) {
29
+ const diffImg = new PNG({ width, height });
30
+ diffImg.data = Buffer.from(diffData);
31
+ const { writeFileSync } = await import('node:fs');
32
+ writeFileSync(diffOutputPath, PNG.sync.write(diffImg));
33
+ }
34
+ return {
35
+ diffRatio: diffPixels / totalPixels,
36
+ diffPixels,
37
+ diffImagePath: diffOutputPath,
38
+ totalPixels,
39
+ };
40
+ }
41
+ catch {
42
+ // pixelmatch/pngjs 미설치 시 파일 크기 기반 근사 비교
43
+ const buf1 = readFileSync(expectedPath);
44
+ const buf2 = readFileSync(actualPath);
45
+ const sizeDiff = Math.abs(buf1.length - buf2.length);
46
+ const maxSize = Math.max(buf1.length, buf2.length);
47
+ return {
48
+ diffRatio: sizeDiff / maxSize,
49
+ diffPixels: -1,
50
+ totalPixels: -1,
51
+ };
52
+ }
53
+ }
54
+ /** 캔버스 크기 통일 (투명 배경으로 확장) */
55
+ function createCanvas(img, width, height) {
56
+ const data = new Uint8Array(width * height * 4);
57
+ for (let y = 0; y < img.height; y++) {
58
+ for (let x = 0; x < img.width; x++) {
59
+ const srcIdx = (y * img.width + x) * 4;
60
+ const dstIdx = (y * width + x) * 4;
61
+ data[dstIdx] = img.data[srcIdx];
62
+ data[dstIdx + 1] = img.data[srcIdx + 1];
63
+ data[dstIdx + 2] = img.data[srcIdx + 2];
64
+ data[dstIdx + 3] = img.data[srcIdx + 3];
65
+ }
66
+ }
67
+ return data;
68
+ }
69
+ /** CSS 수치 비교 — Figma 기대값 vs 실제 렌더링 값 */
70
+ export function compareStyles(expected, actual) {
71
+ const diffs = [];
72
+ for (const [property, expectedValue] of Object.entries(expected)) {
73
+ const actualValue = actual.styles[property];
74
+ if (!actualValue)
75
+ continue;
76
+ const normalizedExpected = normalizeCSS(property, expectedValue);
77
+ const normalizedActual = normalizeCSS(property, actualValue);
78
+ if (normalizedExpected !== normalizedActual) {
79
+ const diff = {
80
+ selector: actual.selector,
81
+ property,
82
+ expected: expectedValue,
83
+ actual: actualValue,
84
+ };
85
+ const expectedNum = parseFloat(expectedValue);
86
+ const actualNum = parseFloat(actualValue);
87
+ if (!isNaN(expectedNum) && !isNaN(actualNum)) {
88
+ diff.delta = Math.abs(expectedNum - actualNum);
89
+ }
90
+ diffs.push(diff);
91
+ }
92
+ }
93
+ return diffs;
94
+ }
95
+ /** CSS 값 정규화 (비교용) */
96
+ function normalizeCSS(property, value) {
97
+ let v = value.trim().toLowerCase();
98
+ // rgb → hex 변환
99
+ const rgbMatch = v.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/);
100
+ if (rgbMatch) {
101
+ const [, r, g, b] = rgbMatch;
102
+ v = '#' + [r, g, b].map(c => parseInt(c, 10).toString(16).padStart(2, '0')).join('');
103
+ }
104
+ // px 값 반올림
105
+ if (v.endsWith('px')) {
106
+ const num = parseFloat(v);
107
+ if (!isNaN(num))
108
+ v = Math.round(num) + 'px';
109
+ }
110
+ // color 속성의 특수 처리
111
+ if (property === 'color' || property.includes('color')) {
112
+ if (v === 'transparent')
113
+ v = 'rgba(0, 0, 0, 0)';
114
+ }
115
+ return v;
116
+ }
117
+ /** StyleDiff → VerificationIssue 변환 */
118
+ export function diffsToIssues(diffs) {
119
+ return diffs.map(diff => {
120
+ const isPx = diff.delta !== undefined;
121
+ const isLarge = isPx && diff.delta > 4;
122
+ return {
123
+ severity: isLarge ? 'P1' : 'P2',
124
+ type: 'style-diff',
125
+ target: diff.selector,
126
+ message: `${diff.property}: expected ${diff.expected}, got ${diff.actual}` +
127
+ (diff.delta !== undefined ? ` (delta: ${diff.delta}px)` : ''),
128
+ expected: diff.expected,
129
+ actual: diff.actual,
130
+ };
131
+ });
132
+ }
133
+ //# sourceMappingURL=compare.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compare.js","sourceRoot":"","sources":["../../../../src/infra/lib/browser/compare.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ export { launchBrowser, openPage, closeBrowser, getBrowser } from './launch.js';
2
+ export { captureScreenshot, getComputedStyles, getComputedStylesBatch, extractTextContent, extractImages } from './capture.js';
3
+ export { compareScreenshots, compareStyles, diffsToIssues } from './compare.js';
4
+ export type { BrowserLaunchOptions, CaptureScreenshotOptions, ElementComputedStyle, StyleDiff, ScreenshotDiff, VerificationIssue, VerificationReport, } from './types.js';
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/infra/lib/browser/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAChF,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,sBAAsB,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC/H,OAAO,EAAE,kBAAkB,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAChF,YAAY,EACV,oBAAoB,EACpB,wBAAwB,EACxB,oBAAoB,EACpB,SAAS,EACT,cAAc,EACd,iBAAiB,EACjB,kBAAkB,GACnB,MAAM,YAAY,CAAC"}
@@ -0,0 +1,4 @@
1
+ export { launchBrowser, openPage, closeBrowser, getBrowser } from './launch.js';
2
+ export { captureScreenshot, getComputedStyles, getComputedStylesBatch, extractTextContent, extractImages } from './capture.js';
3
+ export { compareScreenshots, compareStyles, diffsToIssues } from './compare.js';
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/infra/lib/browser/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAChF,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,sBAAsB,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC/H,OAAO,EAAE,kBAAkB,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Puppeteer 브라우저 관리
3
+ *
4
+ * headless Chrome 런치, 페이지 관리, 정리.
5
+ * 싱글턴 패턴으로 세션 내 브라우저 재사용.
6
+ */
7
+ import type { BrowserLaunchOptions } from './types.js';
8
+ /** headless Chrome 브라우저 시작 */
9
+ export declare function launchBrowser(options?: BrowserLaunchOptions): Promise<unknown>;
10
+ /** 새 페이지 열고 URL 로드 */
11
+ export declare function openPage(browser: unknown, url: string, viewport?: {
12
+ width: number;
13
+ height: number;
14
+ }): Promise<unknown>;
15
+ /** 브라우저 종료 */
16
+ export declare function closeBrowser(): Promise<void>;
17
+ /** 현재 브라우저 인스턴스 반환 (없으면 null) */
18
+ export declare function getBrowser(): unknown;
19
+ //# sourceMappingURL=launch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"launch.d.ts","sourceRoot":"","sources":["../../../../src/infra/lib/browser/launch.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAoBvD,8BAA8B;AAC9B,wBAAsB,aAAa,CAAC,OAAO,GAAE,oBAAyB,GAAG,OAAO,CAAC,OAAO,CAAC,CAiBxF;AAED,sBAAsB;AACtB,wBAAsB,QAAQ,CAC5B,OAAO,EAAE,OAAO,EAChB,GAAG,EAAE,MAAM,EACX,QAAQ,CAAC,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAC3C,OAAO,CAAC,OAAO,CAAC,CAWlB;AAED,cAAc;AACd,wBAAsB,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC,CAMlD;AAED,iCAAiC;AACjC,wBAAgB,UAAU,IAAI,OAAO,CAEpC"}
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Puppeteer 브라우저 관리
3
+ *
4
+ * headless Chrome 런치, 페이지 관리, 정리.
5
+ * 싱글턴 패턴으로 세션 내 브라우저 재사용.
6
+ */
7
+ const DEFAULT_VIEWPORT = { width: 1920, height: 1080 };
8
+ let browserInstance = null;
9
+ /** Puppeteer 동적 import (optional dependency) */
10
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- puppeteer is optional peer dep
11
+ async function loadPuppeteer() {
12
+ try {
13
+ // @ts-expect-error -- puppeteer is optional peer dependency, may not have type declarations
14
+ return await import('puppeteer');
15
+ }
16
+ catch {
17
+ throw new Error('puppeteer is not installed. Run: npm install puppeteer\n' +
18
+ 'Required for UI verification (vibe.figma Phase 4, design-audit, etc.)');
19
+ }
20
+ }
21
+ /** headless Chrome 브라우저 시작 */
22
+ export async function launchBrowser(options = {}) {
23
+ if (browserInstance)
24
+ return browserInstance;
25
+ const puppeteer = await loadPuppeteer();
26
+ const browser = await puppeteer.launch({
27
+ headless: options.headless !== false,
28
+ args: [
29
+ '--no-sandbox',
30
+ '--disable-setuid-sandbox',
31
+ '--disable-dev-shm-usage',
32
+ '--disable-gpu',
33
+ ],
34
+ ...(options.executablePath ? { executablePath: options.executablePath } : {}),
35
+ });
36
+ browserInstance = browser;
37
+ return browser;
38
+ }
39
+ /** 새 페이지 열고 URL 로드 */
40
+ export async function openPage(browser, url, viewport) {
41
+ const b = browser;
42
+ const page = await b.newPage();
43
+ await page.setViewport(viewport ?? DEFAULT_VIEWPORT);
44
+ await page.goto(url, { waitUntil: 'networkidle0' });
45
+ return page;
46
+ }
47
+ /** 브라우저 종료 */
48
+ export async function closeBrowser() {
49
+ if (!browserInstance)
50
+ return;
51
+ const b = browserInstance;
52
+ await b.close();
53
+ browserInstance = null;
54
+ }
55
+ /** 현재 브라우저 인스턴스 반환 (없으면 null) */
56
+ export function getBrowser() {
57
+ return browserInstance;
58
+ }
59
+ //# sourceMappingURL=launch.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"launch.js","sourceRoot":"","sources":["../../../../src/infra/lib/browser/launch.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,MAAM,gBAAgB,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;AAEvD,IAAI,eAAe,GAAY,IAAI,CAAC;AAEpC,gDAAgD;AAChD,gGAAgG;AAChG,KAAK,UAAU,aAAa;IAC1B,IAAI,CAAC;QACH,4FAA4F;QAC5F,OAAO,MAAM,MAAM,CAAC,WAAW,CAAwB,CAAC;IAC1D,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,KAAK,CACb,0DAA0D;YAC1D,uEAAuE,CACxE,CAAC;IACJ,CAAC;AACH,CAAC;AAED,8BAA8B;AAC9B,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,UAAgC,EAAE;IACpE,IAAI,eAAe;QAAE,OAAO,eAAe,CAAC;IAE5C,MAAM,SAAS,GAAG,MAAM,aAAa,EAAE,CAAC;IACxC,MAAM,OAAO,GAAG,MAAM,SAAS,CAAC,MAAM,CAAC;QACrC,QAAQ,EAAE,OAAO,CAAC,QAAQ,KAAK,KAAK;QACpC,IAAI,EAAE;YACJ,cAAc;YACd,0BAA0B;YAC1B,yBAAyB;YACzB,eAAe;SAChB;QACD,GAAG,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,OAAO,CAAC,cAAc,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC9E,CAAC,CAAC;IAEH,eAAe,GAAG,OAAO,CAAC;IAC1B,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,sBAAsB;AACtB,MAAM,CAAC,KAAK,UAAU,QAAQ,CAC5B,OAAgB,EAChB,GAAW,EACX,QAA4C;IAE5C,MAAM,CAAC,GAAG,OAA8C,CAAC;IACzD,MAAM,IAAI,GAAG,MAAM,CAAC,CAAC,OAAO,EAG3B,CAAC;IAEF,MAAM,IAAI,CAAC,WAAW,CAAC,QAAQ,IAAI,gBAAgB,CAAC,CAAC;IACrD,MAAM,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,cAAc,EAAE,CAAC,CAAC;IAEpD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,cAAc;AACd,MAAM,CAAC,KAAK,UAAU,YAAY;IAChC,IAAI,CAAC,eAAe;QAAE,OAAO;IAE7B,MAAM,CAAC,GAAG,eAAiD,CAAC;IAC5D,MAAM,CAAC,CAAC,KAAK,EAAE,CAAC;IAChB,eAAe,GAAG,IAAI,CAAC;AACzB,CAAC;AAED,iCAAiC;AACjC,MAAM,UAAU,UAAU;IACxB,OAAO,eAAe,CAAC;AACzB,CAAC"}
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Browser UI 검증 인프라 타입 정의
3
+ *
4
+ * Puppeteer + CDP 기반 범용 UI 검증 도구.
5
+ * vibe.figma Phase 4, design-audit, design-polish 등에서 공통 사용.
6
+ */
7
+ /** 브라우저 런치 옵션 */
8
+ export interface BrowserLaunchOptions {
9
+ /** headless 모드 (기본: true) */
10
+ headless?: boolean;
11
+ /** 뷰포트 크기 */
12
+ viewport?: {
13
+ width: number;
14
+ height: number;
15
+ };
16
+ /** dev 서버 URL (예: http://localhost:3000) */
17
+ url?: string;
18
+ /** Chrome 실행 경로 (미지정 시 Puppeteer 번들 사용) */
19
+ executablePath?: string;
20
+ }
21
+ /** 스크린샷 옵션 */
22
+ export interface CaptureScreenshotOptions {
23
+ /** 저장 경로 */
24
+ outPath: string;
25
+ /** 특정 CSS selector만 캡처 (미지정 시 전체 페이지) */
26
+ selector?: string;
27
+ /** fullPage 스크롤 캡처 (기본: true) */
28
+ fullPage?: boolean;
29
+ /** 이미지 포맷 (기본: png) */
30
+ format?: 'png' | 'jpeg' | 'webp';
31
+ }
32
+ /** DOM 요소의 computed CSS */
33
+ export interface ElementComputedStyle {
34
+ /** CSS selector */
35
+ selector: string;
36
+ /** computed style key-value */
37
+ styles: Record<string, string>;
38
+ /** bounding box */
39
+ box: {
40
+ x: number;
41
+ y: number;
42
+ width: number;
43
+ height: number;
44
+ };
45
+ }
46
+ /** CSS 수치 비교 결과 */
47
+ export interface StyleDiff {
48
+ selector: string;
49
+ property: string;
50
+ expected: string;
51
+ actual: string;
52
+ /** px 차이 (숫자 비교 가능한 속성만) */
53
+ delta?: number;
54
+ }
55
+ /** 스크린샷 비교 결과 */
56
+ export interface ScreenshotDiff {
57
+ /** 차이 비율 (0.0 ~ 1.0) */
58
+ diffRatio: number;
59
+ /** 차이 픽셀 수 */
60
+ diffPixels: number;
61
+ /** diff 이미지 저장 경로 */
62
+ diffImagePath?: string;
63
+ /** 전체 픽셀 수 */
64
+ totalPixels: number;
65
+ }
66
+ /** UI 검증 결과 항목 */
67
+ export interface VerificationIssue {
68
+ /** P1=필수, P2=권장 */
69
+ severity: 'P1' | 'P2';
70
+ /** 이슈 유형 */
71
+ type: 'missing-image' | 'layout-mismatch' | 'style-diff' | 'text-mismatch' | 'a11y';
72
+ /** 대상 selector 또는 영역 */
73
+ target: string;
74
+ /** 이슈 설명 */
75
+ message: string;
76
+ /** 기대값 */
77
+ expected?: string;
78
+ /** 실제값 */
79
+ actual?: string;
80
+ }
81
+ /** UI 검증 리포트 */
82
+ export interface VerificationReport {
83
+ /** 검증 대상 URL */
84
+ url: string;
85
+ /** 뷰포트 */
86
+ viewport: {
87
+ width: number;
88
+ height: number;
89
+ };
90
+ /** 총 이슈 수 */
91
+ totalIssues: number;
92
+ /** P1 이슈 수 */
93
+ p1Count: number;
94
+ /** P2 이슈 수 */
95
+ p2Count: number;
96
+ /** 이슈 목록 */
97
+ issues: VerificationIssue[];
98
+ /** 스크린샷 diff (있으면) */
99
+ screenshotDiff?: ScreenshotDiff;
100
+ /** 타임스탬프 */
101
+ timestamp: string;
102
+ }
103
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/infra/lib/browser/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,iBAAiB;AACjB,MAAM,WAAW,oBAAoB;IACnC,6BAA6B;IAC7B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,aAAa;IACb,QAAQ,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAC7C,4CAA4C;IAC5C,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,2CAA2C;IAC3C,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,cAAc;AACd,MAAM,WAAW,wBAAwB;IACvC,YAAY;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,yCAAyC;IACzC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,iCAAiC;IACjC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,uBAAuB;IACvB,MAAM,CAAC,EAAE,KAAK,GAAG,MAAM,GAAG,MAAM,CAAC;CAClC;AAED,2BAA2B;AAC3B,MAAM,WAAW,oBAAoB;IACnC,mBAAmB;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,+BAA+B;IAC/B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,mBAAmB;IACnB,GAAG,EAAE;QACH,CAAC,EAAE,MAAM,CAAC;QACV,CAAC,EAAE,MAAM,CAAC;QACV,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,MAAM,CAAC;KAChB,CAAC;CACH;AAED,mBAAmB;AACnB,MAAM,WAAW,SAAS;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,4BAA4B;IAC5B,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,iBAAiB;AACjB,MAAM,WAAW,cAAc;IAC7B,wBAAwB;IACxB,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,qBAAqB;IACrB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,cAAc;IACd,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,kBAAkB;AAClB,MAAM,WAAW,iBAAiB;IAChC,mBAAmB;IACnB,QAAQ,EAAE,IAAI,GAAG,IAAI,CAAC;IACtB,YAAY;IACZ,IAAI,EAAE,eAAe,GAAG,iBAAiB,GAAG,YAAY,GAAG,eAAe,GAAG,MAAM,CAAC;IACpF,wBAAwB;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,YAAY;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU;IACV,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU;IACV,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,gBAAgB;AAChB,MAAM,WAAW,kBAAkB;IACjC,gBAAgB;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,UAAU;IACV,QAAQ,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAC5C,aAAa;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY;IACZ,MAAM,EAAE,iBAAiB,EAAE,CAAC;IAC5B,sBAAsB;IACtB,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC,YAAY;IACZ,SAAS,EAAE,MAAM,CAAC;CACnB"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Browser UI 검증 인프라 타입 정의
3
+ *
4
+ * Puppeteer + CDP 기반 범용 UI 검증 도구.
5
+ * vibe.figma Phase 4, design-audit, design-polish 등에서 공통 사용.
6
+ */
7
+ export {};
8
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../../src/infra/lib/browser/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG"}