@su-record/vibe 2.7.6 → 2.7.9

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 (69) hide show
  1. package/dist/cli/commands/init.d.ts +10 -0
  2. package/dist/cli/commands/init.d.ts.map +1 -1
  3. package/dist/cli/commands/init.js +78 -2
  4. package/dist/cli/commands/init.js.map +1 -1
  5. package/dist/cli/commands/update.d.ts.map +1 -1
  6. package/dist/cli/commands/update.js +17 -2
  7. package/dist/cli/commands/update.js.map +1 -1
  8. package/dist/cli/postinstall/codex-agents.d.ts +12 -0
  9. package/dist/cli/postinstall/codex-agents.d.ts.map +1 -0
  10. package/dist/cli/postinstall/codex-agents.js +51 -0
  11. package/dist/cli/postinstall/codex-agents.js.map +1 -0
  12. package/dist/cli/postinstall/codex-instruction.d.ts +10 -0
  13. package/dist/cli/postinstall/codex-instruction.d.ts.map +1 -0
  14. package/dist/cli/postinstall/codex-instruction.js +56 -0
  15. package/dist/cli/postinstall/codex-instruction.js.map +1 -0
  16. package/dist/cli/postinstall/constants.d.ts.map +1 -1
  17. package/dist/cli/postinstall/constants.js +1 -0
  18. package/dist/cli/postinstall/constants.js.map +1 -1
  19. package/dist/cli/postinstall/gemini-agents.d.ts +12 -0
  20. package/dist/cli/postinstall/gemini-agents.d.ts.map +1 -0
  21. package/dist/cli/postinstall/gemini-agents.js +80 -0
  22. package/dist/cli/postinstall/gemini-agents.js.map +1 -0
  23. package/dist/cli/postinstall/gemini-instruction.d.ts +10 -0
  24. package/dist/cli/postinstall/gemini-instruction.d.ts.map +1 -0
  25. package/dist/cli/postinstall/gemini-instruction.js +59 -0
  26. package/dist/cli/postinstall/gemini-instruction.js.map +1 -0
  27. package/dist/cli/postinstall/index.d.ts +4 -0
  28. package/dist/cli/postinstall/index.d.ts.map +1 -1
  29. package/dist/cli/postinstall/index.js +4 -0
  30. package/dist/cli/postinstall/index.js.map +1 -1
  31. package/dist/cli/postinstall/main.d.ts.map +1 -1
  32. package/dist/cli/postinstall/main.js +34 -1
  33. package/dist/cli/postinstall/main.js.map +1 -1
  34. package/dist/cli/postinstall.d.ts +1 -1
  35. package/dist/cli/postinstall.d.ts.map +1 -1
  36. package/dist/cli/postinstall.js +1 -1
  37. package/dist/cli/postinstall.js.map +1 -1
  38. package/dist/cli/setup/ProjectSetup.d.ts +15 -0
  39. package/dist/cli/setup/ProjectSetup.d.ts.map +1 -1
  40. package/dist/cli/setup/ProjectSetup.js +159 -0
  41. package/dist/cli/setup/ProjectSetup.js.map +1 -1
  42. package/dist/cli/setup.d.ts +1 -1
  43. package/dist/cli/setup.d.ts.map +1 -1
  44. package/dist/cli/setup.js +1 -1
  45. package/dist/cli/setup.js.map +1 -1
  46. package/dist/cli/utils/cli-detector.d.ts +25 -0
  47. package/dist/cli/utils/cli-detector.d.ts.map +1 -0
  48. package/dist/cli/utils/cli-detector.js +55 -0
  49. package/dist/cli/utils/cli-detector.js.map +1 -0
  50. package/hooks/gemini-hooks.json +73 -0
  51. package/package.json +1 -1
  52. package/skills/agents-md/SKILL.md +120 -0
  53. package/skills/brand-assets/SKILL.md +8 -0
  54. package/skills/characterization-test/SKILL.md +4 -0
  55. package/skills/commerce-patterns/SKILL.md +36 -338
  56. package/skills/commit-push-pr/SKILL.md +21 -64
  57. package/skills/core-capabilities/SKILL.md +26 -142
  58. package/skills/e2e-commerce/SKILL.md +37 -284
  59. package/skills/frontend-design/SKILL.md +12 -31
  60. package/skills/git-worktree/SKILL.md +34 -146
  61. package/skills/handoff/SKILL.md +8 -0
  62. package/skills/parallel-research/SKILL.md +7 -0
  63. package/skills/priority-todos/SKILL.md +34 -213
  64. package/skills/seo-checklist/SKILL.md +38 -225
  65. package/skills/tool-fallback/SKILL.md +53 -143
  66. package/skills/typescript-advanced-types/SKILL.md +30 -685
  67. package/skills/ui-ux-pro-max/SKILL.md +40 -220
  68. package/skills/vercel-react-best-practices/SKILL.md +38 -283
  69. package/skills/video-production/SKILL.md +35 -206
@@ -0,0 +1,73 @@
1
+ {
2
+ "hooks": {
3
+ "SessionStart": [
4
+ {
5
+ "command": "VIBE_CLI=gemini node {{VIBE_PATH}}/hooks/scripts/session-start.js"
6
+ }
7
+ ],
8
+ "BeforeTool": [
9
+ {
10
+ "tool_name": "shell",
11
+ "command": "VIBE_CLI=gemini node {{VIBE_PATH}}/hooks/scripts/sentinel-guard.js Bash"
12
+ },
13
+ {
14
+ "tool_name": "shell",
15
+ "command": "VIBE_CLI=gemini node {{VIBE_PATH}}/hooks/scripts/pre-tool-guard.js Bash"
16
+ },
17
+ {
18
+ "tool_name": "edit",
19
+ "command": "VIBE_CLI=gemini node {{VIBE_PATH}}/hooks/scripts/sentinel-guard.js Edit"
20
+ },
21
+ {
22
+ "tool_name": "edit",
23
+ "command": "VIBE_CLI=gemini node {{VIBE_PATH}}/hooks/scripts/pre-tool-guard.js Edit"
24
+ },
25
+ {
26
+ "tool_name": "write_file",
27
+ "command": "VIBE_CLI=gemini node {{VIBE_PATH}}/hooks/scripts/sentinel-guard.js Write"
28
+ },
29
+ {
30
+ "tool_name": "write_file",
31
+ "command": "VIBE_CLI=gemini node {{VIBE_PATH}}/hooks/scripts/pre-tool-guard.js Write"
32
+ }
33
+ ],
34
+ "AfterTool": [
35
+ {
36
+ "tool_name": "edit",
37
+ "command": "VIBE_CLI=gemini node {{VIBE_PATH}}/hooks/scripts/code-check.js"
38
+ },
39
+ {
40
+ "tool_name": "write_file",
41
+ "command": "VIBE_CLI=gemini node {{VIBE_PATH}}/hooks/scripts/code-check.js"
42
+ },
43
+ {
44
+ "tool_name": "edit",
45
+ "command": "VIBE_CLI=gemini node {{VIBE_PATH}}/hooks/scripts/post-edit.js"
46
+ }
47
+ ],
48
+ "BeforeAgent": [
49
+ {
50
+ "command": "VIBE_CLI=gemini node {{VIBE_PATH}}/hooks/scripts/prompt-dispatcher.js"
51
+ }
52
+ ],
53
+ "Notification": [
54
+ {
55
+ "matcher": "context_window_80",
56
+ "command": "VIBE_CLI=gemini node {{VIBE_PATH}}/hooks/scripts/context-save.js medium"
57
+ },
58
+ {
59
+ "matcher": "context_window_90",
60
+ "command": "VIBE_CLI=gemini node {{VIBE_PATH}}/hooks/scripts/context-save.js high"
61
+ },
62
+ {
63
+ "matcher": "context_window_95",
64
+ "command": "VIBE_CLI=gemini node {{VIBE_PATH}}/hooks/scripts/context-save.js critical"
65
+ }
66
+ ],
67
+ "SessionEnd": [
68
+ {
69
+ "command": "VIBE_CLI=gemini node {{VIBE_PATH}}/hooks/scripts/stop-notify.js"
70
+ }
71
+ ]
72
+ }
73
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@su-record/vibe",
3
- "version": "2.7.6",
3
+ "version": "2.7.9",
4
4
  "description": "AI Coding Framework for Claude Code — 49 agents, 41+ tools, multi-LLM orchestration",
5
5
  "type": "module",
6
6
  "main": "dist/cli/index.js",
@@ -0,0 +1,120 @@
1
+ ---
2
+ name: agents-md
3
+ description: "Optimize AGENTS.md / CLAUDE.md by removing discoverable info and keeping only gotchas. Based on Addy Osmani's AGENTS.md principles. Activates on agents.md, claude.md, context file optimization."
4
+ triggers: [agents.md, claude.md, context file, optimize agents, optimize claude]
5
+ priority: 50
6
+ ---
7
+
8
+ # agents-md — Context File Optimizer
9
+
10
+ AGENTS.md / CLAUDE.md를 최적화한다.
11
+ 근거: https://addyosmani.com/blog/agents-md/
12
+
13
+ ## 핵심 원칙
14
+
15
+ **한 줄 테스트**: "에이전트가 코드를 읽어서 스스로 알 수 있는가?" → Yes면 삭제.
16
+
17
+ ## Step 1: 대상 파일 찾기
18
+
19
+ 프로젝트 루트에서 다음 파일을 탐색한다:
20
+
21
+ ```
22
+ Glob: pattern="AGENTS.md"
23
+ Glob: pattern="CLAUDE.md"
24
+ Glob: pattern=".cursorrules"
25
+ Glob: pattern=".github/copilot-instructions.md"
26
+ Glob: pattern=".windsurfrules"
27
+ ```
28
+
29
+ 대상 파일이 없으면 새로 생성할지 사용자에게 확인한다.
30
+
31
+ ## Step 2: 현재 내용 분류
32
+
33
+ 파일의 각 항목을 다음 기준으로 분류한다:
34
+
35
+ ### 삭제 (Discoverable)
36
+
37
+ 에이전트가 코드 탐색으로 발견 가능한 정보:
38
+
39
+ | 유형 | 예시 | 발견 경로 |
40
+ |------|------|----------|
41
+ | 디렉토리 구조 | "src/에 컴포넌트가 있다" | `ls`, `Glob` |
42
+ | 기술 스택 | "React + TypeScript 사용" | `package.json`, 파일 확장자 |
43
+ | Phase/진행 테이블 | "Phase 1 ✅, Phase 2 ✅..." | 이력일 뿐, 행동 지침 아님 |
44
+ | 빌드/테스트 커맨드 | "npm test로 테스트 실행" | `package.json` scripts |
45
+ | API 엔드포인트 목록 | "POST /api/users" | 라우터 코드 |
46
+ | 기능별 상세 설명 | "Phase 3에서 서킷브레이커 구현" | 해당 코드 읽으면 파악 가능 |
47
+ | 아키텍처 다이어그램 | ASCII 박스 다이어그램 | 개념적 설명, 실수 방지 효과 없음 |
48
+
49
+ ### 유지 (Non-discoverable)
50
+
51
+ 에이전트가 코드만으로 알 수 없는 함정과 규칙:
52
+
53
+ | 유형 | 예시 |
54
+ |------|------|
55
+ | 런타임 함정 | "Bun이다, Node가 아니다" (package.json에 명시 안 됨) |
56
+ | 금지 패턴 | "require() 쓰지 말 것", "React 패턴 금지" |
57
+ | SSOT 위치 | "model-registry.ts만 수정할 것, 하드코딩 금지" |
58
+ | 불변 순서/규칙 | "우선순위: A → B → C, 이 순서 변경 금지" |
59
+ | 재시도/폴백 제한 | "최대 2회, 3회 이상 금지" |
60
+ | 도구 선택 | "Zod만 사용, joi/yup 금지" |
61
+ | 네이밍 컨벤션 | 비표준 패턴 (표준이면 삭제) |
62
+ | 프로젝트 한 줄 소개 | 코드만으로 목적 파악 어려운 경우 |
63
+ | 언어/응답 지시 | "한글로 답변" 같은 선호 |
64
+
65
+ ### 앵커링 주의
66
+
67
+ 기술 이름을 언급하면 에이전트가 해당 기술로 편향된다. "무엇을 쓰지 말라"는 유용하지만, "우리는 X를 쓴다"는 코드에서 이미 보인다면 불필요.
68
+
69
+ ## Step 3: 재구성
70
+
71
+ 다음 구조로 재작성한다:
72
+
73
+ ```markdown
74
+ # {프로젝트명} — {한 줄 소개}
75
+
76
+ {프로젝트가 무엇을 하는지 1-2문장. 코드만으로 목적 파악이 어려운 경우에만.}
77
+
78
+ # Gotchas
79
+
80
+ - **{함정 제목}.** {구체적 금지/규칙 설명}.
81
+ - ...
82
+
83
+ # Naming
84
+
85
+ {비표준 네이밍 패턴이 있을 때만. 표준(camelCase 등)이면 생략.}
86
+ ```
87
+
88
+ 규칙:
89
+ - 섹션은 최대 3개 (소개, Gotchas, Naming)
90
+ - Gotchas 항목은 각각 **볼드 제목 + 구체적 do/don't**
91
+ - "~를 사용한다" 보다 "~를 쓰지 말 것"이 더 유용
92
+ - 전체 50줄 이내 목표
93
+
94
+ ## Step 4: CLAUDE.md 분리 (해당 시)
95
+
96
+ CLAUDE.md가 존재하면 Claude 전용 지시만 남긴다:
97
+
98
+ ```markdown
99
+ {Claude 전용 지시. 예: "답변은 반드시 한글로 답변할 것."}
100
+ ```
101
+
102
+ 공통 규칙은 AGENTS.md에 둔다. Claude Code는 둘 다 읽는다.
103
+
104
+ ## Step 5: 결과 보고
105
+
106
+ ```markdown
107
+ ## AGENTS.md 최적화 결과
108
+
109
+ | 지표 | Before | After |
110
+ |------|--------|-------|
111
+ | 줄 수 | N | N |
112
+ | 삭제 항목 | - | N개 (discoverable) |
113
+ | 유지 항목 | - | N개 (gotchas) |
114
+
115
+ ### 삭제된 항목
116
+ - {항목}: {삭제 이유}
117
+
118
+ ### 유지/추가된 항목
119
+ - {항목}: {유지 이유}
120
+ ```
@@ -136,3 +136,11 @@ public/
136
136
  android-chrome-512x512.png
137
137
  site.webmanifest
138
138
  ```
139
+
140
+ ## Done Criteria (K4)
141
+
142
+ - [ ] All required sizes generated (16x16 through 512x512)
143
+ - [ ] Icons work at small sizes (recognizable at 16x16)
144
+ - [ ] No text/letters in icon (illegible at small sizes)
145
+ - [ ] `site.webmanifest` updated with icon paths
146
+ - [ ] Fallback generated if Gemini API unavailable
@@ -11,6 +11,10 @@ priority: 65
11
11
 
12
12
  Lock existing behavior with snapshot/characterization tests before modifying code. This prevents regressions in legacy, complex, or unfamiliar codebases.
13
13
 
14
+ ## Pre-check (K1)
15
+
16
+ > Are you modifying existing code with uncertain behavior? If the code is new (you just wrote it), well-tested, or trivially simple, skip characterization tests and write regular unit tests instead.
17
+
14
18
  ## When to Use
15
19
 
16
20
  | Scenario | Signal |
@@ -4,358 +4,56 @@ description: "E-commerce domain patterns - cart, payment, inventory with transac
4
4
  triggers: [commerce, ecommerce, cart, payment, checkout, inventory, stock, order, pg, toss, stripe]
5
5
  priority: 70
6
6
  ---
7
- # Commerce Patterns Skill
8
7
 
9
- E-commerce domain patterns for reliable transactions.
8
+ # Commerce Patterns
10
9
 
11
- ## When to Use
10
+ ## Pre-check (K1)
12
11
 
13
- - Shopping cart implementation
14
- - Payment integration (PG, Stripe, Toss)
15
- - Inventory/stock management
16
- - Order processing systems
12
+ > Is this an e-commerce transaction flow? If building simple CRUD without payment/stock management, this skill is not needed.
17
13
 
18
- ## Core Patterns
14
+ ## Gotchas & Traps
19
15
 
20
- ### 1. Cart (Shopping Cart)
16
+ These are the non-obvious failure modes LLMs typically miss:
21
17
 
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);
18
+ ### Payment
254
19
 
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]);
20
+ | Trap | Consequence | Prevention |
21
+ |------|-------------|------------|
22
+ | No idempotency key | User double-clicks → charged twice | `idempotencyKey: order_${orderId}_${timestamp}` on every payment request |
23
+ | Webhook not idempotent | Retry delivers duplicate events | Check `eventId` before processing, store processed events |
24
+ | Missing webhook signature verification | Attacker forges payment confirmation | Always verify HMAC signature before processing |
25
+ | No payment state machine | Order stuck in limbo | `PENDING → PROCESSING → AUTHORIZED → CAPTURED → COMPLETED` (+ FAILED, CANCELED, REFUNDED) |
262
26
 
263
- await this.reservationRepository.update(reservation.id, {
264
- status: 'RELEASED',
265
- });
266
- }
267
- }
268
- }
269
- }
270
- ```
27
+ ### Inventory
271
28
 
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]);
29
+ | Trap | Consequence | Prevention |
30
+ |------|-------------|------------|
31
+ | Non-atomic stock decrement | Race condition → negative stock | `UPDATE SET stock = stock - $1 WHERE stock >= $1` (atomic with check) |
32
+ | No reservation TTL | Stock locked forever on abandoned checkout | 15-min reservation + scheduled cleanup job |
33
+ | Commit without reservation | Stock sold twice | Two-phase: RESERVE (checkout start) → COMMIT (payment success) / RELEASE (failure) |
34
+ | Optimistic lock without retry | Fails silently under contention | Use `version` column or `FOR UPDATE` for high-contention products |
280
35
 
281
- // Option 2: Pessimistic Locking (for high contention)
282
- await db.query(`
283
- SELECT * FROM products WHERE id = $1 FOR UPDATE
284
- `, [productId]);
36
+ ### Cart & Pricing
285
37
 
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
- ```
38
+ | Trap | Consequence | Prevention |
39
+ |------|-------------|------------|
40
+ | No price snapshot | Price changes between add and checkout | Store `price` at add time, revalidate at checkout entry |
41
+ | No guest→user cart merge | Guest loses cart on login | Merge by `sessionId` on login, user cart takes priority for duplicates |
42
+ | No cart expiration | Abandoned carts accumulate forever | TTL-based cleanup (e.g., 7 days) |
294
43
 
295
- ## Order Flow (Complete)
44
+ ## Checkout Flow (Reference)
296
45
 
297
46
  ```
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
- └─────────────────────────────────────────────────────────────┘
47
+ Cart Validation → Order Creation (PENDING) → Stock Reservation (15min) → Payment
48
+ ├─ Success → Order PAID, Stock COMMIT, Send Confirmation
49
+ └─ Failure → Order FAILED, Stock RELEASE
319
50
  ```
320
51
 
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:
52
+ ## Done Criteria (K4)
335
53
 
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
54
+ - [ ] Idempotency key on all payment requests
55
+ - [ ] Webhook handler is idempotent (dedup by eventId)
56
+ - [ ] Stock updates are atomic (SQL-level check)
57
+ - [ ] Two-phase reservation implemented with TTL
58
+ - [ ] Prices snapshot at cart add, revalidated at checkout
59
+ - [ ] Payment state machine covers all transitions