@su-record/vibe 2.5.12 → 2.5.13

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.
Files changed (108) hide show
  1. package/commands/vibe.analyze.md +3 -3
  2. package/commands/vibe.review.md +3 -3
  3. package/commands/vibe.run.md +75 -9
  4. package/commands/vibe.spec.md +7 -7
  5. package/commands/vibe.utils.md +62 -5
  6. package/dist/cli/setup/GlobalInstaller.d.ts +24 -0
  7. package/dist/cli/setup/GlobalInstaller.d.ts.map +1 -0
  8. package/dist/cli/setup/GlobalInstaller.js +130 -0
  9. package/dist/cli/setup/GlobalInstaller.js.map +1 -0
  10. package/dist/cli/setup/LanguageDetector.d.ts +16 -0
  11. package/dist/cli/setup/LanguageDetector.d.ts.map +1 -0
  12. package/dist/cli/setup/LanguageDetector.js +49 -0
  13. package/dist/cli/setup/LanguageDetector.js.map +1 -0
  14. package/dist/cli/setup/LegacyMigration.d.ts +25 -0
  15. package/dist/cli/setup/LegacyMigration.d.ts.map +1 -0
  16. package/dist/cli/setup/LegacyMigration.js +162 -0
  17. package/dist/cli/setup/LegacyMigration.js.map +1 -0
  18. package/dist/cli/setup/ProjectSetup.d.ts +30 -0
  19. package/dist/cli/setup/ProjectSetup.d.ts.map +1 -0
  20. package/dist/cli/setup/ProjectSetup.js +238 -0
  21. package/dist/cli/setup/ProjectSetup.js.map +1 -0
  22. package/dist/cli/setup/index.d.ts +14 -0
  23. package/dist/cli/setup/index.d.ts.map +1 -0
  24. package/dist/cli/setup/index.js +18 -0
  25. package/dist/cli/setup/index.js.map +1 -0
  26. package/dist/cli/setup.d.ts +10 -77
  27. package/dist/cli/setup.d.ts.map +1 -1
  28. package/dist/cli/setup.js +15 -592
  29. package/dist/cli/setup.js.map +1 -1
  30. package/dist/lib/llm/auth/ApiKeyManager.d.ts +21 -0
  31. package/dist/lib/llm/auth/ApiKeyManager.d.ts.map +1 -0
  32. package/dist/lib/llm/auth/ApiKeyManager.js +43 -0
  33. package/dist/lib/llm/auth/ApiKeyManager.js.map +1 -0
  34. package/dist/lib/llm/auth/ConfigManager.d.ts +29 -0
  35. package/dist/lib/llm/auth/ConfigManager.d.ts.map +1 -0
  36. package/dist/lib/llm/auth/ConfigManager.js +67 -0
  37. package/dist/lib/llm/auth/ConfigManager.js.map +1 -0
  38. package/dist/lib/llm/auth/index.d.ts +25 -0
  39. package/dist/lib/llm/auth/index.d.ts.map +1 -0
  40. package/dist/lib/llm/auth/index.js +83 -0
  41. package/dist/lib/llm/auth/index.js.map +1 -0
  42. package/dist/lib/llm/index.d.ts +10 -0
  43. package/dist/lib/llm/index.d.ts.map +1 -0
  44. package/dist/lib/llm/index.js +12 -0
  45. package/dist/lib/llm/index.js.map +1 -0
  46. package/dist/lib/llm/types.d.ts +96 -0
  47. package/dist/lib/llm/types.d.ts.map +1 -0
  48. package/dist/lib/llm/types.js +17 -0
  49. package/dist/lib/llm/types.js.map +1 -0
  50. package/dist/lib/llm/utils/index.d.ts +6 -0
  51. package/dist/lib/llm/utils/index.d.ts.map +1 -0
  52. package/dist/lib/llm/utils/index.js +6 -0
  53. package/dist/lib/llm/utils/index.js.map +1 -0
  54. package/dist/lib/llm/utils/retry.d.ts +25 -0
  55. package/dist/lib/llm/utils/retry.d.ts.map +1 -0
  56. package/dist/lib/llm/utils/retry.js +72 -0
  57. package/dist/lib/llm/utils/retry.js.map +1 -0
  58. package/dist/lib/llm/utils/stream.d.ts +13 -0
  59. package/dist/lib/llm/utils/stream.d.ts.map +1 -0
  60. package/dist/lib/llm/utils/stream.js +110 -0
  61. package/dist/lib/llm/utils/stream.js.map +1 -0
  62. package/dist/orchestrator/AgentExecutor.d.ts +23 -0
  63. package/dist/orchestrator/AgentExecutor.d.ts.map +1 -0
  64. package/dist/orchestrator/AgentExecutor.js +231 -0
  65. package/dist/orchestrator/AgentExecutor.js.map +1 -0
  66. package/dist/orchestrator/AgentManager.d.ts +73 -0
  67. package/dist/orchestrator/AgentManager.d.ts.map +1 -0
  68. package/dist/orchestrator/AgentManager.js +184 -0
  69. package/dist/orchestrator/AgentManager.js.map +1 -0
  70. package/dist/orchestrator/LLMCluster.d.ts +70 -0
  71. package/dist/orchestrator/LLMCluster.d.ts.map +1 -0
  72. package/dist/orchestrator/LLMCluster.js +91 -0
  73. package/dist/orchestrator/LLMCluster.js.map +1 -0
  74. package/dist/orchestrator/MultiLlmResearch.d.ts +27 -0
  75. package/dist/orchestrator/MultiLlmResearch.d.ts.map +1 -0
  76. package/dist/orchestrator/MultiLlmResearch.js +145 -0
  77. package/dist/orchestrator/MultiLlmResearch.js.map +1 -0
  78. package/dist/orchestrator/SessionStore.d.ts +41 -0
  79. package/dist/orchestrator/SessionStore.d.ts.map +1 -0
  80. package/dist/orchestrator/SessionStore.js +117 -0
  81. package/dist/orchestrator/SessionStore.js.map +1 -0
  82. package/dist/orchestrator/SmartRouter.d.ts +68 -0
  83. package/dist/orchestrator/SmartRouter.d.ts.map +1 -0
  84. package/dist/orchestrator/SmartRouter.js +256 -0
  85. package/dist/orchestrator/SmartRouter.js.map +1 -0
  86. package/dist/orchestrator/backgroundAgent.d.ts +10 -27
  87. package/dist/orchestrator/backgroundAgent.d.ts.map +1 -1
  88. package/dist/orchestrator/backgroundAgent.js +11 -345
  89. package/dist/orchestrator/backgroundAgent.js.map +1 -1
  90. package/dist/orchestrator/index.d.ts +3 -0
  91. package/dist/orchestrator/index.d.ts.map +1 -1
  92. package/dist/orchestrator/index.js +4 -0
  93. package/dist/orchestrator/index.js.map +1 -1
  94. package/dist/orchestrator/orchestrator.d.ts +19 -154
  95. package/dist/orchestrator/orchestrator.d.ts.map +1 -1
  96. package/dist/orchestrator/orchestrator.js +90 -514
  97. package/dist/orchestrator/orchestrator.js.map +1 -1
  98. package/dist/orchestrator/parallelResearch.d.ts +5 -12
  99. package/dist/orchestrator/parallelResearch.d.ts.map +1 -1
  100. package/dist/orchestrator/parallelResearch.js +10 -193
  101. package/dist/orchestrator/parallelResearch.js.map +1 -1
  102. package/hooks/scripts/generate-brand-assets.js +472 -0
  103. package/package.json +1 -1
  104. package/skills/brand-assets.md +141 -0
  105. package/skills/commerce-patterns.md +361 -0
  106. package/skills/e2e-commerce.md +304 -0
  107. package/skills/frontend-design.md +92 -0
  108. package/skills/seo-checklist.md +244 -0
@@ -0,0 +1,361 @@
1
+ ---
2
+ name: commerce-patterns
3
+ description: "E-commerce domain patterns - cart, payment, inventory with transaction safety"
4
+ triggers: [commerce, ecommerce, cart, payment, checkout, inventory, stock, order, pg, toss, stripe]
5
+ priority: 70
6
+ ---
7
+ # Commerce Patterns Skill
8
+
9
+ E-commerce domain patterns for reliable transactions.
10
+
11
+ ## When to Use
12
+
13
+ - Shopping cart implementation
14
+ - Payment integration (PG, Stripe, Toss)
15
+ - Inventory/stock management
16
+ - Order processing systems
17
+
18
+ ## Core Patterns
19
+
20
+ ### 1. Cart (Shopping Cart)
21
+
22
+ #### State Model
23
+ ```typescript
24
+ interface CartItem {
25
+ productId: string;
26
+ variantId?: string;
27
+ quantity: number;
28
+ price: number; // Snapshot at add time
29
+ originalPrice: number; // For comparison
30
+ addedAt: Date;
31
+ }
32
+
33
+ interface Cart {
34
+ id: string;
35
+ userId?: string; // null for guest
36
+ sessionId: string; // For guest merge
37
+ items: CartItem[];
38
+ couponCode?: string;
39
+ updatedAt: Date;
40
+ expiresAt: Date; // Cart expiration
41
+ }
42
+ ```
43
+
44
+ #### Key Patterns
45
+
46
+ | Pattern | Description |
47
+ |---------|-------------|
48
+ | **Guest → User Merge** | Merge localStorage cart on login |
49
+ | **Price Snapshot** | Store price at add time, revalidate at checkout |
50
+ | **Expiration** | Clear abandoned carts after N days |
51
+ | **Validation** | Check stock/price at checkout entry |
52
+
53
+ #### Implementation
54
+ ```typescript
55
+ class CartService {
56
+ async addItem(cartId: string, item: AddItemRequest): Promise<Cart> {
57
+ // 1. Validate product exists and in stock
58
+ const product = await this.productService.get(item.productId);
59
+ if (!product || product.stock < item.quantity) {
60
+ throw new OutOfStockError();
61
+ }
62
+
63
+ // 2. Snapshot current price
64
+ const cartItem: CartItem = {
65
+ ...item,
66
+ price: product.currentPrice,
67
+ originalPrice: product.originalPrice,
68
+ addedAt: new Date(),
69
+ };
70
+
71
+ // 3. Add or update quantity
72
+ return this.cartRepository.upsertItem(cartId, cartItem);
73
+ }
74
+
75
+ async mergeGuestCart(userId: string, sessionId: string): Promise<Cart> {
76
+ const guestCart = await this.cartRepository.findBySession(sessionId);
77
+ const userCart = await this.cartRepository.findByUser(userId);
78
+
79
+ if (!guestCart) return userCart;
80
+
81
+ // Merge: user cart takes priority for duplicates
82
+ const merged = this.mergeItems(userCart.items, guestCart.items);
83
+ await this.cartRepository.delete(guestCart.id);
84
+
85
+ return this.cartRepository.update(userCart.id, { items: merged });
86
+ }
87
+ }
88
+ ```
89
+
90
+ ### 2. Payment
91
+
92
+ #### State Machine
93
+ ```
94
+ PENDING → PROCESSING → AUTHORIZED → CAPTURED → COMPLETED
95
+ ↘ FAILED
96
+ ↘ CANCELED
97
+ COMPLETED → REFUND_REQUESTED → REFUNDED (partial/full)
98
+ ```
99
+
100
+ #### Idempotency Pattern (Critical)
101
+ ```typescript
102
+ interface PaymentRequest {
103
+ orderId: string;
104
+ amount: number;
105
+ currency: string;
106
+ idempotencyKey: string; // REQUIRED: `order_${orderId}_${timestamp}`
107
+ paymentMethod: PaymentMethod;
108
+ }
109
+
110
+ class PaymentService {
111
+ async processPayment(request: PaymentRequest): Promise<PaymentResult> {
112
+ // 1. Check idempotency - prevent duplicate charges
113
+ const existing = await this.paymentRepository.findByIdempotencyKey(
114
+ request.idempotencyKey
115
+ );
116
+ if (existing) {
117
+ return existing.result; // Return cached result
118
+ }
119
+
120
+ // 2. Create payment record (PENDING)
121
+ const payment = await this.paymentRepository.create({
122
+ ...request,
123
+ status: 'PENDING',
124
+ });
125
+
126
+ try {
127
+ // 3. Call PG adapter
128
+ const pgResult = await this.pgAdapter.authorize(request);
129
+
130
+ // 4. Update status
131
+ return this.paymentRepository.update(payment.id, {
132
+ status: pgResult.success ? 'AUTHORIZED' : 'FAILED',
133
+ pgTransactionId: pgResult.transactionId,
134
+ result: pgResult,
135
+ });
136
+ } catch (error) {
137
+ await this.paymentRepository.update(payment.id, {
138
+ status: 'FAILED',
139
+ error: error.message,
140
+ });
141
+ throw error;
142
+ }
143
+ }
144
+ }
145
+ ```
146
+
147
+ #### PG Adapter Pattern
148
+ ```typescript
149
+ interface PGAdapter {
150
+ authorize(request: PaymentRequest): Promise<PGResult>;
151
+ capture(transactionId: string): Promise<PGResult>;
152
+ cancel(transactionId: string): Promise<PGResult>;
153
+ refund(transactionId: string, amount?: number): Promise<PGResult>;
154
+ }
155
+
156
+ // Implementations
157
+ class TossPaymentsAdapter implements PGAdapter { /* ... */ }
158
+ class StripeAdapter implements PGAdapter { /* ... */ }
159
+ class PortOneAdapter implements PGAdapter { /* ... */ }
160
+ ```
161
+
162
+ #### Webhook Handling
163
+ ```typescript
164
+ class PaymentWebhookHandler {
165
+ async handle(event: WebhookEvent): Promise<void> {
166
+ // 1. Verify signature
167
+ if (!this.verifySignature(event)) {
168
+ throw new UnauthorizedError();
169
+ }
170
+
171
+ // 2. Idempotency check - process each event only once
172
+ const processed = await this.eventStore.find(event.id);
173
+ if (processed) {
174
+ return; // Already processed
175
+ }
176
+
177
+ // 3. Process event
178
+ await this.processEvent(event);
179
+
180
+ // 4. Mark as processed
181
+ await this.eventStore.save({
182
+ eventId: event.id,
183
+ processedAt: new Date(),
184
+ });
185
+ }
186
+ }
187
+ ```
188
+
189
+ ### 3. Inventory (Stock Management)
190
+
191
+ #### Reservation Pattern (Two-Phase)
192
+ ```typescript
193
+ interface StockReservation {
194
+ id: string;
195
+ productId: string;
196
+ quantity: number;
197
+ orderId: string;
198
+ status: 'RESERVED' | 'COMMITTED' | 'RELEASED';
199
+ expiresAt: Date; // Auto-release if not committed
200
+ createdAt: Date;
201
+ }
202
+
203
+ class InventoryService {
204
+ // Phase 1: Reserve stock (at checkout start)
205
+ async reserve(orderId: string, items: OrderItem[]): Promise<void> {
206
+ for (const item of items) {
207
+ // Atomic decrement with check
208
+ const result = await this.db.query(`
209
+ UPDATE products
210
+ SET reserved_stock = reserved_stock + $1
211
+ WHERE id = $2
212
+ AND (available_stock - reserved_stock) >= $1
213
+ RETURNING *
214
+ `, [item.quantity, item.productId]);
215
+
216
+ if (result.rowCount === 0) {
217
+ // Rollback previous reservations
218
+ await this.releaseAll(orderId);
219
+ throw new InsufficientStockError(item.productId);
220
+ }
221
+
222
+ await this.reservationRepository.create({
223
+ orderId,
224
+ productId: item.productId,
225
+ quantity: item.quantity,
226
+ status: 'RESERVED',
227
+ expiresAt: addMinutes(new Date(), 15), // 15min hold
228
+ });
229
+ }
230
+ }
231
+
232
+ // Phase 2: Commit stock (after payment success)
233
+ async commit(orderId: string): Promise<void> {
234
+ const reservations = await this.reservationRepository.findByOrder(orderId);
235
+
236
+ for (const reservation of reservations) {
237
+ await this.db.query(`
238
+ UPDATE products
239
+ SET
240
+ available_stock = available_stock - $1,
241
+ reserved_stock = reserved_stock - $1
242
+ WHERE id = $2
243
+ `, [reservation.quantity, reservation.productId]);
244
+
245
+ await this.reservationRepository.update(reservation.id, {
246
+ status: 'COMMITTED',
247
+ });
248
+ }
249
+ }
250
+
251
+ // Rollback: Release stock (payment failed or timeout)
252
+ async release(orderId: string): Promise<void> {
253
+ const reservations = await this.reservationRepository.findByOrder(orderId);
254
+
255
+ for (const reservation of reservations) {
256
+ if (reservation.status === 'RESERVED') {
257
+ await this.db.query(`
258
+ UPDATE products
259
+ SET reserved_stock = reserved_stock - $1
260
+ WHERE id = $2
261
+ `, [reservation.quantity, reservation.productId]);
262
+
263
+ await this.reservationRepository.update(reservation.id, {
264
+ status: 'RELEASED',
265
+ });
266
+ }
267
+ }
268
+ }
269
+ }
270
+ ```
271
+
272
+ #### Concurrency Control
273
+ ```typescript
274
+ // Option 1: Optimistic Locking
275
+ await db.query(`
276
+ UPDATE products
277
+ SET stock = stock - $1, version = version + 1
278
+ WHERE id = $2 AND version = $3 AND stock >= $1
279
+ `, [quantity, productId, expectedVersion]);
280
+
281
+ // Option 2: Pessimistic Locking (for high contention)
282
+ await db.query(`
283
+ SELECT * FROM products WHERE id = $1 FOR UPDATE
284
+ `, [productId]);
285
+
286
+ // Option 3: Redis Distributed Lock
287
+ const lock = await redlock.lock(`stock:${productId}`, 5000);
288
+ try {
289
+ // Update stock
290
+ } finally {
291
+ await lock.unlock();
292
+ }
293
+ ```
294
+
295
+ ## Order Flow (Complete)
296
+
297
+ ```
298
+ ┌─────────────────────────────────────────────────────────────┐
299
+ │ CHECKOUT FLOW │
300
+ ├─────────────────────────────────────────────────────────────┤
301
+ │ │
302
+ │ 1. Cart Validation │
303
+ │ └─ Revalidate prices, check stock availability │
304
+ │ │
305
+ │ 2. Order Creation (PENDING) │
306
+ │ └─ Generate orderId, snapshot cart │
307
+ │ │
308
+ │ 3. Stock Reservation │
309
+ │ └─ Reserve inventory (15min hold) │
310
+ │ │
311
+ │ 4. Payment Processing │
312
+ │ ├─ Success → Order PAID, Stock COMMIT │
313
+ │ └─ Failure → Order FAILED, Stock RELEASE │
314
+ │ │
315
+ │ 5. Order Confirmation │
316
+ │ └─ Send notification, trigger fulfillment │
317
+ │ │
318
+ └─────────────────────────────────────────────────────────────┘
319
+ ```
320
+
321
+ ## Common Bugs & Prevention
322
+
323
+ | Bug | Cause | Prevention |
324
+ |-----|-------|------------|
325
+ | **Duplicate payment** | User double-click, webhook retry | Idempotency key |
326
+ | **Negative stock** | Race condition | Atomic update with check |
327
+ | **Price mismatch** | Price changed during checkout | Snapshot + revalidation |
328
+ | **Orphan reservation** | Payment timeout without release | TTL + scheduled cleanup |
329
+ | **Lost webhook** | Network failure | Retry + idempotent handler |
330
+ | **Order state corruption** | Concurrent updates | State machine + versioning |
331
+
332
+ ## Integration with /vibe.run
333
+
334
+ During commerce feature implementation:
335
+
336
+ 1. **Phase 1**: Define domain models (Cart, Order, Payment, Inventory)
337
+ 2. **Phase 2**: Implement core services with patterns above
338
+ 3. **Phase 3**: Add PG adapter and webhook handlers
339
+ 4. **Phase 4**: Implement compensating transactions (Saga)
340
+ 5. **Phase 5**: E2E test critical flows
341
+
342
+ ## Checklist
343
+
344
+ ### Cart
345
+ - [ ] Guest/user cart merge on login
346
+ - [ ] Price snapshot at add time
347
+ - [ ] Stock validation at checkout
348
+ - [ ] Cart expiration cleanup
349
+
350
+ ### Payment
351
+ - [ ] Idempotency key on all requests
352
+ - [ ] Webhook signature verification
353
+ - [ ] Duplicate event handling
354
+ - [ ] Timeout/retry strategy
355
+ - [ ] Refund flow tested
356
+
357
+ ### Inventory
358
+ - [ ] Two-phase reservation (reserve → commit/release)
359
+ - [ ] Atomic stock updates
360
+ - [ ] Reservation TTL and cleanup job
361
+ - [ ] Concurrency control tested
@@ -0,0 +1,304 @@
1
+ ---
2
+ name: e2e-commerce
3
+ description: "E2E test scenarios for commerce checkout and payment flows"
4
+ triggers: [e2e commerce, checkout test, payment test, order flow test]
5
+ priority: 65
6
+ ---
7
+ # E2E Commerce Test Scenarios
8
+
9
+ Playwright-based E2E testing for commerce checkout flows.
10
+
11
+ ## When to Use
12
+
13
+ - After implementing checkout/payment features
14
+ - Before production deployment
15
+ - CI/CD pipeline quality gate
16
+ - Regression testing after changes
17
+
18
+ ## Test Scenarios
19
+
20
+ ### 1. Happy Path - Complete Checkout
21
+
22
+ ```typescript
23
+ // tests/e2e/checkout.spec.ts
24
+ import { test, expect } from '@playwright/test';
25
+
26
+ test.describe('Checkout Flow', () => {
27
+ test('complete purchase - happy path', async ({ page }) => {
28
+ // 1. Add to cart
29
+ await page.goto('/products/test-product');
30
+ await page.click('[data-testid="add-to-cart"]');
31
+ await expect(page.locator('[data-testid="cart-count"]')).toHaveText('1');
32
+
33
+ // 2. Go to cart
34
+ await page.click('[data-testid="cart-icon"]');
35
+ await expect(page).toHaveURL('/cart');
36
+
37
+ // 3. Proceed to checkout
38
+ await page.click('[data-testid="checkout-button"]');
39
+ await expect(page).toHaveURL('/checkout');
40
+
41
+ // 4. Fill shipping info
42
+ await page.fill('[name="name"]', 'Test User');
43
+ await page.fill('[name="phone"]', '010-1234-5678');
44
+ await page.fill('[name="address"]', 'Test Address 123');
45
+
46
+ // 5. Select payment method
47
+ await page.click('[data-testid="payment-card"]');
48
+
49
+ // 6. Complete payment (sandbox/mock)
50
+ await page.click('[data-testid="pay-button"]');
51
+
52
+ // 7. Verify order complete
53
+ await expect(page).toHaveURL(/\/orders\/\w+/);
54
+ await expect(page.locator('[data-testid="order-status"]')).toHaveText('결제 완료');
55
+ });
56
+ });
57
+ ```
58
+
59
+ ### 2. Stock Validation
60
+
61
+ ```typescript
62
+ test('prevent checkout when out of stock', async ({ page }) => {
63
+ // Setup: Product with stock = 1, another user reserves it
64
+ await page.goto('/products/low-stock-product');
65
+ await page.click('[data-testid="add-to-cart"]');
66
+ await page.goto('/checkout');
67
+
68
+ // Simulate stock depletion during checkout
69
+ await page.evaluate(async () => {
70
+ await fetch('/api/test/deplete-stock', { method: 'POST' });
71
+ });
72
+
73
+ // Attempt payment
74
+ await page.click('[data-testid="pay-button"]');
75
+
76
+ // Should show out of stock error
77
+ await expect(page.locator('[data-testid="error-message"]'))
78
+ .toContainText('재고가 부족합니다');
79
+ });
80
+ ```
81
+
82
+ ### 3. Payment Failure Handling
83
+
84
+ ```typescript
85
+ test('handle payment failure gracefully', async ({ page }) => {
86
+ await page.goto('/products/test-product');
87
+ await page.click('[data-testid="add-to-cart"]');
88
+ await page.goto('/checkout');
89
+
90
+ // Fill form
91
+ await page.fill('[name="name"]', 'Test User');
92
+ await page.fill('[name="address"]', 'Test Address');
93
+
94
+ // Use test card that triggers failure
95
+ await page.fill('[name="card-number"]', '4000000000000002'); // Decline card
96
+ await page.click('[data-testid="pay-button"]');
97
+
98
+ // Verify error handling
99
+ await expect(page.locator('[data-testid="payment-error"]'))
100
+ .toContainText('결제가 거절되었습니다');
101
+
102
+ // Stock should be released
103
+ await page.goto('/products/test-product');
104
+ await expect(page.locator('[data-testid="stock-status"]'))
105
+ .not.toContainText('품절');
106
+ });
107
+ ```
108
+
109
+ ### 4. Duplicate Payment Prevention
110
+
111
+ ```typescript
112
+ test('prevent duplicate payment on double click', async ({ page }) => {
113
+ await page.goto('/checkout');
114
+ // Fill checkout form...
115
+
116
+ // Double click pay button rapidly
117
+ const payButton = page.locator('[data-testid="pay-button"]');
118
+ await Promise.all([
119
+ payButton.click(),
120
+ payButton.click(),
121
+ ]);
122
+
123
+ // Wait for completion
124
+ await page.waitForURL(/\/orders\/\w+/);
125
+
126
+ // Verify only one order created
127
+ const orderId = page.url().split('/').pop();
128
+ const response = await page.request.get(`/api/orders?userId=test`);
129
+ const orders = await response.json();
130
+
131
+ expect(orders.filter(o => o.id === orderId)).toHaveLength(1);
132
+ });
133
+ ```
134
+
135
+ ### 5. Coupon Application
136
+
137
+ ```typescript
138
+ test('apply coupon and verify discount', async ({ page }) => {
139
+ await page.goto('/products/test-product'); // Price: 10,000
140
+ await page.click('[data-testid="add-to-cart"]');
141
+ await page.goto('/checkout');
142
+
143
+ // Original price
144
+ await expect(page.locator('[data-testid="total-price"]'))
145
+ .toHaveText('10,000원');
146
+
147
+ // Apply 10% coupon
148
+ await page.fill('[name="coupon"]', 'DISCOUNT10');
149
+ await page.click('[data-testid="apply-coupon"]');
150
+
151
+ // Verify discount applied
152
+ await expect(page.locator('[data-testid="discount-amount"]'))
153
+ .toHaveText('-1,000원');
154
+ await expect(page.locator('[data-testid="total-price"]'))
155
+ .toHaveText('9,000원');
156
+ });
157
+ ```
158
+
159
+ ### 6. Webhook Resilience
160
+
161
+ ```typescript
162
+ test('order completes even with webhook delay', async ({ page, request }) => {
163
+ // Configure webhook delay in test environment
164
+ await request.post('/api/test/configure-webhook', {
165
+ data: { delay: 5000 } // 5 second delay
166
+ });
167
+
168
+ // Complete checkout
169
+ await page.goto('/checkout');
170
+ // ... fill form
171
+ await page.click('[data-testid="pay-button"]');
172
+
173
+ // Should show processing state
174
+ await expect(page.locator('[data-testid="order-status"]'))
175
+ .toHaveText('처리 중');
176
+
177
+ // Wait for webhook
178
+ await page.waitForSelector('[data-testid="order-status"]:has-text("결제 완료")', {
179
+ timeout: 10000
180
+ });
181
+ });
182
+ ```
183
+
184
+ ## CLI Usage
185
+
186
+ ```bash
187
+ # Run all commerce e2e tests
188
+ /vibe.utils --e2e commerce
189
+
190
+ # Run specific scenario
191
+ /vibe.utils --e2e checkout-flow
192
+
193
+ # Run with visual recording
194
+ /vibe.utils --e2e commerce --record
195
+
196
+ # Run against staging
197
+ /vibe.utils --e2e commerce --env staging
198
+ ```
199
+
200
+ ## Test Environment Setup
201
+
202
+ ### Mock PG Server
203
+ ```typescript
204
+ // tests/mocks/pg-server.ts
205
+ import { setupServer } from 'msw/node';
206
+ import { http, HttpResponse } from 'msw';
207
+
208
+ export const pgMockServer = setupServer(
209
+ // Success response
210
+ http.post('/payments/authorize', () => {
211
+ return HttpResponse.json({
212
+ success: true,
213
+ transactionId: `txn_${Date.now()}`,
214
+ status: 'AUTHORIZED',
215
+ });
216
+ }),
217
+
218
+ // Failure simulation
219
+ http.post('/payments/authorize', ({ request }) => {
220
+ const body = request.json();
221
+ if (body.cardNumber === '4000000000000002') {
222
+ return HttpResponse.json({
223
+ success: false,
224
+ error: 'CARD_DECLINED',
225
+ }, { status: 400 });
226
+ }
227
+ }),
228
+ );
229
+ ```
230
+
231
+ ### Database Seeding
232
+ ```typescript
233
+ // tests/fixtures/commerce.ts
234
+ export async function seedCommerceData(db: Database) {
235
+ // Create test products
236
+ await db.products.createMany([
237
+ { id: 'test-product', name: 'Test Product', price: 10000, stock: 100 },
238
+ { id: 'low-stock', name: 'Low Stock', price: 5000, stock: 1 },
239
+ ]);
240
+
241
+ // Create test coupons
242
+ await db.coupons.create({
243
+ code: 'DISCOUNT10',
244
+ discountPercent: 10,
245
+ expiresAt: addDays(new Date(), 30),
246
+ });
247
+ }
248
+ ```
249
+
250
+ ## CI/CD Integration
251
+
252
+ ```yaml
253
+ # .github/workflows/e2e.yml
254
+ name: E2E Commerce Tests
255
+
256
+ on:
257
+ pull_request:
258
+ paths:
259
+ - 'src/checkout/**'
260
+ - 'src/payment/**'
261
+ - 'src/cart/**'
262
+
263
+ jobs:
264
+ e2e:
265
+ runs-on: ubuntu-latest
266
+ steps:
267
+ - uses: actions/checkout@v4
268
+
269
+ - name: Install dependencies
270
+ run: npm ci
271
+
272
+ - name: Start test server
273
+ run: npm run start:test &
274
+
275
+ - name: Run E2E tests
276
+ run: npx playwright test tests/e2e/commerce/
277
+
278
+ - name: Upload test results
279
+ if: failure()
280
+ uses: actions/upload-artifact@v4
281
+ with:
282
+ name: playwright-report
283
+ path: playwright-report/
284
+ ```
285
+
286
+ ## Quality Checklist
287
+
288
+ ### Must Pass (P0)
289
+ - [ ] Happy path checkout completes
290
+ - [ ] Payment failure releases stock
291
+ - [ ] Duplicate payment prevented
292
+ - [ ] Out of stock blocks checkout
293
+
294
+ ### Should Pass (P1)
295
+ - [ ] Coupon calculation correct
296
+ - [ ] Webhook retry handled
297
+ - [ ] Cart merge on login works
298
+ - [ ] Partial refund processed
299
+
300
+ ### Nice to Have (P2)
301
+ - [ ] Multiple payment methods
302
+ - [ ] Guest checkout flow
303
+ - [ ] Order cancellation
304
+ - [ ] Subscription renewal