codesyncer 1.1.0 → 2.0.1
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.ko.md +96 -23
- package/README.md +96 -23
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +120 -393
- package/dist/commands/init.js.map +1 -1
- package/dist/templates/en/comment_guide.md +460 -202
- package/dist/templates/en/setup_guide.md +296 -0
- package/dist/templates/ko/comment_guide.md +461 -203
- package/dist/templates/ko/setup_guide.md +296 -0
- package/package.json +1 -1
- package/src/templates/en/comment_guide.md +460 -202
- package/src/templates/en/setup_guide.md +296 -0
- package/src/templates/ko/comment_guide.md +461 -203
- package/src/templates/ko/setup_guide.md +296 -0
|
@@ -1,324 +1,582 @@
|
|
|
1
1
|
# Comment Writing Guide
|
|
2
2
|
|
|
3
|
-
> **
|
|
3
|
+
> **Manage All Context with Comments** - Code is the documentation
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
##
|
|
7
|
+
## 🎯 Core Principle
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
**Record all decisions and context directly in code.**
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
11
|
+
- ❌ Write quality standards in separate docs → AI can't read
|
|
12
|
+
- ✅ Explain quality standards in code comments → Permanent record
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## 📋 Comment Tag System (10 Tags)
|
|
17
|
+
|
|
18
|
+
### Basic Tags (5)
|
|
19
|
+
|
|
20
|
+
| Tag | Purpose | Required Info |
|
|
21
|
+
|-----|---------|--------------|
|
|
22
|
+
| `@codesyncer-inference` | Inference + rationale | "What" + "Why" |
|
|
23
|
+
| `@codesyncer-decision` | Decision made | [Date] + reason |
|
|
24
|
+
| `@codesyncer-todo` | Needs confirmation | Specific task |
|
|
25
|
+
| `@codesyncer-context` | Business context | Domain knowledge |
|
|
26
|
+
| `@codesyncer-rule` | Special rule | Exception case |
|
|
27
|
+
|
|
28
|
+
### Extended Tags (5) - Complete Context Preservation
|
|
29
|
+
|
|
30
|
+
| Tag | Purpose | When to Use |
|
|
31
|
+
|-----|---------|-------------|
|
|
32
|
+
| `@codesyncer-why` | Detailed explanation | When code alone isn't clear |
|
|
33
|
+
| `@codesyncer-tradeoff` | Pros and cons | When there are trade-offs |
|
|
34
|
+
| `@codesyncer-alternative` | Other options | When alternatives considered |
|
|
35
|
+
| `@codesyncer-pattern` | Pattern name | Reusable pattern |
|
|
36
|
+
| `@codesyncer-reference` | Reference link | External docs/issues |
|
|
18
37
|
|
|
19
38
|
### Legacy Compatibility
|
|
20
39
|
|
|
21
|
-
Existing `@claude-*` tags are fully compatible:
|
|
22
40
|
```typescript
|
|
23
|
-
@claude
|
|
24
|
-
@claude-inference = @codesyncer-inference
|
|
25
|
-
@claude-decision = @codesyncer-decision
|
|
26
|
-
@claude-todo = @codesyncer-todo
|
|
27
|
-
@claude-context = @codesyncer-context
|
|
41
|
+
@claude-* = @codesyncer-* // Legacy tags fully compatible
|
|
28
42
|
```
|
|
29
43
|
|
|
30
44
|
---
|
|
31
45
|
|
|
32
|
-
##
|
|
33
|
-
|
|
34
|
-
### 1. 📄 File Level (JSDoc)
|
|
46
|
+
## 💡 Real Examples: All Context in Comments
|
|
35
47
|
|
|
36
|
-
|
|
48
|
+
### 1️⃣ Quality Standards in Comments
|
|
37
49
|
|
|
38
50
|
```typescript
|
|
39
51
|
/**
|
|
40
|
-
*
|
|
52
|
+
* Payment processing service
|
|
41
53
|
*
|
|
42
|
-
* @codesyncer-context
|
|
43
|
-
* @codesyncer-rule
|
|
44
|
-
* @
|
|
45
|
-
*
|
|
54
|
+
* @codesyncer-context Real-time card payment (PG: Stripe)
|
|
55
|
+
* @codesyncer-rule All amounts as integers (avoid decimal errors)
|
|
56
|
+
* @codesyncer-pattern Transaction Script (simple payments don't need domain model)
|
|
57
|
+
*
|
|
58
|
+
* Quality standards:
|
|
59
|
+
* - Timeout: 30s (PG recommendation)
|
|
60
|
+
* - Retry: 3 times (idempotency required)
|
|
61
|
+
* - Logging: Record all payment attempts
|
|
62
|
+
* - Error handling: User-friendly messages
|
|
46
63
|
*/
|
|
47
|
-
|
|
64
|
+
export class PaymentService {
|
|
65
|
+
/**
|
|
66
|
+
* Process payment
|
|
67
|
+
*
|
|
68
|
+
* @codesyncer-why Synchronous implementation (need immediate confirmation)
|
|
69
|
+
* @codesyncer-tradeoff Sync: Fast feedback | Async: Higher throughput
|
|
70
|
+
* @codesyncer-decision [2024-11-12] Chose sync (UX priority)
|
|
71
|
+
*/
|
|
72
|
+
async processPayment(
|
|
73
|
+
amount: number,
|
|
74
|
+
cardToken: string
|
|
75
|
+
): Promise<PaymentResult> {
|
|
76
|
+
// @codesyncer-inference: Minimum 100 (PG policy)
|
|
77
|
+
if (amount < 100) {
|
|
78
|
+
throw new ValidationError('Minimum payment is 100');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// @codesyncer-why: Generate idempotency key (prevent duplicate charges)
|
|
82
|
+
const idempotencyKey = this.generateIdempotencyKey(amount, cardToken);
|
|
83
|
+
|
|
84
|
+
// @codesyncer-pattern: Retry with Exponential Backoff
|
|
85
|
+
return await this.retryWithBackoff(async () => {
|
|
86
|
+
return await stripe.charge({
|
|
87
|
+
amount,
|
|
88
|
+
source: cardToken,
|
|
89
|
+
idempotencyKey
|
|
90
|
+
});
|
|
91
|
+
}, {
|
|
92
|
+
maxRetries: 3,
|
|
93
|
+
initialDelay: 1000
|
|
94
|
+
});
|
|
95
|
+
}
|
|
48
96
|
|
|
49
|
-
|
|
97
|
+
/**
|
|
98
|
+
* @codesyncer-pattern Exponential Backoff
|
|
99
|
+
* @codesyncer-reference https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/
|
|
100
|
+
*/
|
|
101
|
+
private async retryWithBackoff<T>(
|
|
102
|
+
fn: () => Promise<T>,
|
|
103
|
+
options: RetryOptions
|
|
104
|
+
): Promise<T> {
|
|
105
|
+
// ... implementation
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
```
|
|
50
109
|
|
|
51
|
-
|
|
110
|
+
### 2️⃣ Complex Business Logic Explanation
|
|
52
111
|
|
|
53
|
-
```
|
|
112
|
+
```typescript
|
|
54
113
|
/**
|
|
55
|
-
*
|
|
114
|
+
* Discount calculator
|
|
56
115
|
*
|
|
57
|
-
* @codesyncer-context
|
|
58
|
-
*
|
|
59
|
-
*
|
|
116
|
+
* @codesyncer-context Compound discount policy (stackable)
|
|
117
|
+
* - Member tier discount: 5-15%
|
|
118
|
+
* - Coupon discount: Fixed amount or percentage
|
|
119
|
+
* - Promotion discount: Conditional
|
|
120
|
+
*
|
|
121
|
+
* @codesyncer-decision [2024-11-10] Fixed discount order (marketing agreement)
|
|
122
|
+
* 1. Member tier discount
|
|
123
|
+
* 2. Coupon discount
|
|
124
|
+
* 3. Promotion discount
|
|
125
|
+
*
|
|
126
|
+
* @codesyncer-why Order matters (final amount differs)
|
|
127
|
+
* @codesyncer-alternative Sum discounts then apply → Rejected (complex cases)
|
|
60
128
|
*/
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
129
|
+
function calculateFinalPrice(
|
|
130
|
+
basePrice: number,
|
|
131
|
+
user: User,
|
|
132
|
+
coupon?: Coupon,
|
|
133
|
+
promotion?: Promotion
|
|
134
|
+
): number {
|
|
135
|
+
// @codesyncer-context: Save all intermediate calculations (refund tracking)
|
|
136
|
+
const breakdown: PriceBreakdown = {
|
|
137
|
+
basePrice,
|
|
138
|
+
discounts: []
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
let currentPrice = basePrice;
|
|
142
|
+
|
|
143
|
+
// Step 1: Member tier discount
|
|
144
|
+
// @codesyncer-inference: GOLD 15%, SILVER 10%, BRONZE 5% (common pattern)
|
|
145
|
+
const memberDiscount = this.calculateMemberDiscount(user.tier);
|
|
146
|
+
if (memberDiscount > 0) {
|
|
147
|
+
currentPrice -= memberDiscount;
|
|
148
|
+
breakdown.discounts.push({
|
|
149
|
+
type: 'MEMBER',
|
|
150
|
+
amount: memberDiscount
|
|
151
|
+
});
|
|
152
|
+
}
|
|
69
153
|
|
|
70
|
-
|
|
71
|
-
// @codesyncer-
|
|
72
|
-
|
|
154
|
+
// Step 2: Coupon discount
|
|
155
|
+
// @codesyncer-rule: Apply coupon to discounted price (important!)
|
|
156
|
+
if (coupon) {
|
|
157
|
+
const couponDiscount = this.applyCoupon(currentPrice, coupon);
|
|
158
|
+
currentPrice -= couponDiscount;
|
|
159
|
+
breakdown.discounts.push({
|
|
160
|
+
type: 'COUPON',
|
|
161
|
+
amount: couponDiscount,
|
|
162
|
+
couponId: coupon.id
|
|
163
|
+
});
|
|
164
|
+
}
|
|
73
165
|
|
|
74
|
-
//
|
|
75
|
-
|
|
166
|
+
// Step 3: Promotion discount
|
|
167
|
+
// @codesyncer-todo: Confirm promotion stacking policy
|
|
168
|
+
if (promotion) {
|
|
169
|
+
const promoDiscount = this.applyPromotion(currentPrice, promotion);
|
|
170
|
+
currentPrice -= promoDiscount;
|
|
171
|
+
breakdown.discounts.push({
|
|
172
|
+
type: 'PROMOTION',
|
|
173
|
+
amount: promoDiscount,
|
|
174
|
+
promotionId: promotion.id
|
|
175
|
+
});
|
|
176
|
+
}
|
|
76
177
|
|
|
77
|
-
// @codesyncer-
|
|
78
|
-
|
|
79
|
-
// @codesyncer-inference: Using deleted_at flag (for recovery feature)
|
|
80
|
-
return db.update(id, { deleted_at: new Date() });
|
|
178
|
+
// @codesyncer-rule: Final amount must be non-negative
|
|
179
|
+
return Math.max(0, currentPrice);
|
|
81
180
|
}
|
|
82
|
-
|
|
83
|
-
const maxRetry = 3; // @codesyncer-inference: 3 retries (stability)
|
|
84
181
|
```
|
|
85
182
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
## ✅ Good Comment Examples
|
|
89
|
-
|
|
90
|
-
### Example 1: Business Logic
|
|
183
|
+
### 3️⃣ Performance Optimization Record
|
|
91
184
|
|
|
92
|
-
```
|
|
185
|
+
```typescript
|
|
93
186
|
/**
|
|
94
|
-
*
|
|
187
|
+
* Order list API
|
|
95
188
|
*
|
|
96
|
-
* @codesyncer-context
|
|
97
|
-
* -
|
|
98
|
-
* - Under $300: $30
|
|
99
|
-
* - Remote areas: +$30
|
|
189
|
+
* @codesyncer-context Heavy users have 100k+ orders (performance issue)
|
|
190
|
+
* @codesyncer-decision [2024-11-12] Pagination + Index + Caching
|
|
100
191
|
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
192
|
+
* Performance goals:
|
|
193
|
+
* - Response time: < 500ms (P95)
|
|
194
|
+
* - Concurrent users: 1000 TPS
|
|
195
|
+
* - Cache hit rate: > 80%
|
|
103
196
|
*/
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
197
|
+
export class OrderController {
|
|
198
|
+
/**
|
|
199
|
+
* @codesyncer-pattern Cursor-based Pagination
|
|
200
|
+
* @codesyncer-why Offset pagination gets slower with depth (OFFSET 10000)
|
|
201
|
+
* @codesyncer-tradeoff Cursor: Fast | Offset: Page numbers
|
|
202
|
+
* @codesyncer-alternative Offset pagination → Test showed P95 3s (rejected)
|
|
203
|
+
* @codesyncer-reference https://use-the-index-luke.com/no-offset
|
|
204
|
+
*/
|
|
205
|
+
async getOrders(userId: string, cursor?: string, limit = 20) {
|
|
206
|
+
// @codesyncer-inference: Redis cache 5min (real-time vs performance)
|
|
207
|
+
const cacheKey = `orders:${userId}:${cursor}`;
|
|
208
|
+
const cached = await redis.get(cacheKey);
|
|
209
|
+
if (cached) {
|
|
210
|
+
return JSON.parse(cached);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// @codesyncer-pattern: Index Hint
|
|
214
|
+
// @codesyncer-why Force use of userId + createdAt compound index
|
|
215
|
+
const orders = await db.query(`
|
|
216
|
+
SELECT /*+ INDEX(orders idx_user_created) */
|
|
217
|
+
id, total, status, created_at
|
|
218
|
+
FROM orders
|
|
219
|
+
WHERE user_id = ?
|
|
220
|
+
${cursor ? 'AND created_at < ?' : ''}
|
|
221
|
+
ORDER BY created_at DESC
|
|
222
|
+
LIMIT ?
|
|
223
|
+
`, cursor ? [userId, cursor, limit] : [userId, limit]);
|
|
224
|
+
|
|
225
|
+
const result = {
|
|
226
|
+
data: orders,
|
|
227
|
+
nextCursor: orders.length === limit
|
|
228
|
+
? orders[orders.length - 1].created_at
|
|
229
|
+
: null
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
// @codesyncer-inference: 5min TTL (orders rarely change)
|
|
233
|
+
await redis.setex(cacheKey, 300, JSON.stringify(result));
|
|
234
|
+
|
|
235
|
+
return result;
|
|
116
236
|
}
|
|
117
|
-
|
|
118
|
-
const baseFee = BASIC_FEE;
|
|
119
|
-
const extraFee = EXTRA_FEE_REGIONS.includes(region) ? 3000 : 0;
|
|
120
|
-
|
|
121
|
-
return baseFee + extraFee;
|
|
122
237
|
}
|
|
123
238
|
```
|
|
124
239
|
|
|
125
|
-
###
|
|
240
|
+
### 4️⃣ Security Requirements
|
|
126
241
|
|
|
127
|
-
```
|
|
242
|
+
```typescript
|
|
128
243
|
/**
|
|
129
|
-
*
|
|
244
|
+
* Authentication middleware
|
|
245
|
+
*
|
|
246
|
+
* @codesyncer-context Financial service (security first)
|
|
247
|
+
* @codesyncer-rule OWASP Top 10 compliance required
|
|
130
248
|
*
|
|
131
|
-
*
|
|
132
|
-
*
|
|
249
|
+
* Security checklist:
|
|
250
|
+
* ✅ SQL Injection prevention (Prepared statements)
|
|
251
|
+
* ✅ XSS prevention (CSP headers)
|
|
252
|
+
* ✅ CSRF prevention (Token validation)
|
|
253
|
+
* ✅ Rate limiting (100 req/min)
|
|
254
|
+
* ✅ No sensitive info in logs
|
|
133
255
|
*/
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
256
|
+
export async function authenticate(req: Request, res: Response, next: NextFunction) {
|
|
257
|
+
try {
|
|
258
|
+
// @codesyncer-rule: Token from httpOnly cookie only (XSS prevention)
|
|
259
|
+
const token = req.cookies.access_token;
|
|
260
|
+
|
|
261
|
+
if (!token) {
|
|
262
|
+
// @codesyncer-why: 401 vs 403 distinction (security best practice)
|
|
263
|
+
// 401: Not authenticated | 403: Not authorized
|
|
264
|
+
return res.status(401).json({ error: 'Authentication required' });
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// @codesyncer-decision [2024-11-12] Session over JWT (more secure)
|
|
268
|
+
// @codesyncer-tradeoff JWT: Stateless | Session: Revocable
|
|
269
|
+
const session = await sessionStore.get(token);
|
|
270
|
+
|
|
271
|
+
if (!session) {
|
|
272
|
+
// @codesyncer-why: Minimal error messages (reduce info leakage)
|
|
273
|
+
return res.status(401).json({ error: 'Invalid token' });
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// @codesyncer-pattern: Session Rotation
|
|
277
|
+
// @codesyncer-reference: OWASP Session Management Cheat Sheet
|
|
278
|
+
if (session.shouldRotate()) {
|
|
279
|
+
const newToken = await sessionStore.rotate(session.id);
|
|
280
|
+
res.cookie('access_token', newToken, {
|
|
281
|
+
httpOnly: true,
|
|
282
|
+
secure: true,
|
|
283
|
+
sameSite: 'strict'
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
req.user = session.user;
|
|
288
|
+
next();
|
|
289
|
+
|
|
290
|
+
} catch (error) {
|
|
291
|
+
// @codesyncer-rule: Never log sensitive information
|
|
292
|
+
logger.error('Authentication error', {
|
|
293
|
+
// ❌ Never: token, password, email
|
|
294
|
+
ip: req.ip,
|
|
295
|
+
userAgent: req.get('user-agent')
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
return res.status(500).json({ error: 'Internal server error' });
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
```
|
|
139
302
|
|
|
140
|
-
|
|
141
|
-
passwordHash: string;
|
|
303
|
+
### 5️⃣ Error Handling Strategy
|
|
142
304
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
305
|
+
```typescript
|
|
306
|
+
/**
|
|
307
|
+
* External API wrapper
|
|
308
|
+
*
|
|
309
|
+
* @codesyncer-context External service unstable (95% SLA)
|
|
310
|
+
* @codesyncer-pattern Circuit Breaker + Retry + Timeout
|
|
311
|
+
* @codesyncer-reference Netflix Hystrix pattern
|
|
312
|
+
*
|
|
313
|
+
* Error handling strategy:
|
|
314
|
+
* - Timeout: 30s
|
|
315
|
+
* - Retry: 3 times (Exponential Backoff)
|
|
316
|
+
* - Circuit Breaker: Open after 5 failures
|
|
317
|
+
* - Fallback: Return cached data
|
|
318
|
+
*/
|
|
319
|
+
export class ExternalApiClient {
|
|
320
|
+
private circuitBreaker = new CircuitBreaker({
|
|
321
|
+
failureThreshold: 5,
|
|
322
|
+
resetTimeout: 60000
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* @codesyncer-why Handle all errors in one place (consistency)
|
|
327
|
+
* @codesyncer-alternative Try-catch per call → Too much duplication
|
|
328
|
+
*/
|
|
329
|
+
async call<T>(
|
|
330
|
+
endpoint: string,
|
|
331
|
+
options?: RequestOptions
|
|
332
|
+
): Promise<Result<T>> {
|
|
333
|
+
// @codesyncer-pattern: Circuit Breaker
|
|
334
|
+
if (this.circuitBreaker.isOpen()) {
|
|
335
|
+
logger.warn('Circuit breaker is open', { endpoint });
|
|
336
|
+
return this.getFallback<T>(endpoint);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
try {
|
|
340
|
+
// @codesyncer-inference: 30s timeout (external API recommendation)
|
|
341
|
+
const response = await this.retryWithTimeout(
|
|
342
|
+
() => fetch(endpoint, options),
|
|
343
|
+
{ timeout: 30000, maxRetries: 3 }
|
|
344
|
+
);
|
|
345
|
+
|
|
346
|
+
this.circuitBreaker.recordSuccess();
|
|
347
|
+
return Result.ok(response.data);
|
|
348
|
+
|
|
349
|
+
} catch (error) {
|
|
350
|
+
this.circuitBreaker.recordFailure();
|
|
351
|
+
|
|
352
|
+
// @codesyncer-pattern: Error Classification
|
|
353
|
+
if (error instanceof TimeoutError) {
|
|
354
|
+
logger.warn('API timeout', { endpoint, duration: error.duration });
|
|
355
|
+
return this.getFallback<T>(endpoint);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
if (error instanceof NetworkError) {
|
|
359
|
+
logger.error('Network error', { endpoint, error });
|
|
360
|
+
return this.getFallback<T>(endpoint);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// @codesyncer-why: Propagate unexpected errors (handle upstream)
|
|
364
|
+
throw error;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
146
367
|
|
|
147
|
-
|
|
148
|
-
|
|
368
|
+
/**
|
|
369
|
+
* @codesyncer-pattern: Fallback with Stale Cache
|
|
370
|
+
* @codesyncer-why Stale data better than no data
|
|
371
|
+
*/
|
|
372
|
+
private async getFallback<T>(endpoint: string): Promise<Result<T>> {
|
|
373
|
+
const staleData = await cache.getStale<T>(endpoint);
|
|
374
|
+
if (staleData) {
|
|
375
|
+
logger.info('Returning stale cache', { endpoint });
|
|
376
|
+
return Result.ok(staleData, { isStale: true });
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
return Result.error('Service unavailable');
|
|
380
|
+
}
|
|
149
381
|
}
|
|
150
382
|
```
|
|
151
383
|
|
|
152
|
-
###
|
|
384
|
+
### 6️⃣ Test Strategy Documentation
|
|
153
385
|
|
|
154
|
-
```
|
|
386
|
+
```typescript
|
|
155
387
|
/**
|
|
156
|
-
*
|
|
388
|
+
* Payment service tests
|
|
157
389
|
*
|
|
158
|
-
* @codesyncer-context
|
|
159
|
-
*
|
|
160
|
-
*
|
|
390
|
+
* @codesyncer-context Payment is critical path (zero bugs tolerated)
|
|
391
|
+
*
|
|
392
|
+
* Test strategy:
|
|
393
|
+
* - Unit: All public methods
|
|
394
|
+
* - Integration: PG API calls (Mocked)
|
|
395
|
+
* - E2E: Full payment flow (Staging)
|
|
396
|
+
* - Coverage goal: 95%+
|
|
397
|
+
*
|
|
398
|
+
* @codesyncer-rule Payment changes require QA approval
|
|
161
399
|
*/
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
400
|
+
describe('PaymentService', () => {
|
|
401
|
+
describe('processPayment', () => {
|
|
402
|
+
/**
|
|
403
|
+
* @codesyncer-pattern: AAA (Arrange-Act-Assert)
|
|
404
|
+
* @codesyncer-why Test readability and maintainability
|
|
405
|
+
*/
|
|
406
|
+
it('should process payment successfully', async () => {
|
|
407
|
+
// Arrange: Setup test data
|
|
408
|
+
const service = new PaymentService();
|
|
409
|
+
const amount = 10000;
|
|
410
|
+
const cardToken = 'tok_test_1234';
|
|
411
|
+
|
|
412
|
+
// @codesyncer-inference: Mock PG API (prevent actual charges)
|
|
413
|
+
const mockStripe = jest.spyOn(stripe, 'charge')
|
|
414
|
+
.mockResolvedValue({ id: 'ch_1234', status: 'succeeded' });
|
|
415
|
+
|
|
416
|
+
// Act: Execute
|
|
417
|
+
const result = await service.processPayment(amount, cardToken);
|
|
418
|
+
|
|
419
|
+
// Assert: Verify
|
|
420
|
+
expect(result.isSuccess).toBe(true);
|
|
421
|
+
expect(result.data.status).toBe('succeeded');
|
|
422
|
+
|
|
423
|
+
// @codesyncer-why: Verify call parameters (ensure correct values)
|
|
424
|
+
expect(mockStripe).toHaveBeenCalledWith({
|
|
425
|
+
amount,
|
|
426
|
+
source: cardToken,
|
|
427
|
+
idempotencyKey: expect.any(String)
|
|
428
|
+
});
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* @codesyncer-pattern: Edge Case Testing
|
|
433
|
+
* @codesyncer-why Bugs often occur at boundaries
|
|
434
|
+
*/
|
|
435
|
+
it('should reject payment below minimum amount', async () => {
|
|
436
|
+
const service = new PaymentService();
|
|
437
|
+
|
|
438
|
+
// @codesyncer-context: Minimum 100 (PG policy)
|
|
439
|
+
await expect(
|
|
440
|
+
service.processPayment(99, 'tok_test')
|
|
441
|
+
).rejects.toThrow('Minimum payment is 100');
|
|
442
|
+
});
|
|
443
|
+
});
|
|
444
|
+
});
|
|
175
445
|
```
|
|
176
446
|
|
|
177
447
|
---
|
|
178
448
|
|
|
179
|
-
##
|
|
449
|
+
## 🎯 Comment Writing Principles
|
|
180
450
|
|
|
181
|
-
###
|
|
451
|
+
### ✅ DO (Best Practices)
|
|
182
452
|
|
|
183
|
-
```
|
|
184
|
-
//
|
|
185
|
-
// @codesyncer-inference:
|
|
186
|
-
const
|
|
453
|
+
```typescript
|
|
454
|
+
// ✅ Specific reason and rationale
|
|
455
|
+
// @codesyncer-inference: Page size 20 (user research, <3 scrolls)
|
|
456
|
+
const PAGE_SIZE = 20;
|
|
187
457
|
|
|
188
|
-
//
|
|
189
|
-
// @codesyncer-decision:
|
|
190
|
-
const API_URL = '/api/new';
|
|
458
|
+
// ✅ Date and context
|
|
459
|
+
// @codesyncer-decision: [2024-11-12] PostgreSQL (complex queries + ACID)
|
|
191
460
|
|
|
192
|
-
//
|
|
193
|
-
// @codesyncer-
|
|
194
|
-
function doSomething() {}
|
|
461
|
+
// ✅ Trade-offs explicit
|
|
462
|
+
// @codesyncer-tradeoff: Caching +50% perf, +20% memory
|
|
195
463
|
|
|
196
|
-
//
|
|
197
|
-
// @codesyncer-
|
|
198
|
-
const IMPORTANT_VALUE = 42;
|
|
199
|
-
```
|
|
464
|
+
// ✅ Record alternatives
|
|
465
|
+
// @codesyncer-alternative: MongoDB → Rejected (schema changes frequent)
|
|
200
466
|
|
|
201
|
-
|
|
467
|
+
// ✅ Pattern name (reusable)
|
|
468
|
+
// @codesyncer-pattern: Repository Pattern (data access abstraction)
|
|
469
|
+
```
|
|
202
470
|
|
|
203
|
-
|
|
204
|
-
// ✅ Specific rationale
|
|
205
|
-
// @codesyncer-inference: Default 10 (typical retry wait time)
|
|
206
|
-
const RETRY_DELAY = 10;
|
|
471
|
+
### ❌ DON'T (Anti-patterns)
|
|
207
472
|
|
|
208
|
-
|
|
209
|
-
//
|
|
210
|
-
|
|
473
|
+
```typescript
|
|
474
|
+
// ❌ Too vague
|
|
475
|
+
// @codesyncer-inference: Did this
|
|
476
|
+
const value = 10;
|
|
211
477
|
|
|
212
|
-
//
|
|
213
|
-
// @codesyncer-
|
|
214
|
-
function
|
|
478
|
+
// ❌ Repeats code
|
|
479
|
+
// @codesyncer-context: Create user // Code already says this
|
|
480
|
+
function createUser() {}
|
|
215
481
|
|
|
216
|
-
//
|
|
217
|
-
// @codesyncer-
|
|
218
|
-
const
|
|
482
|
+
// ❌ No rationale
|
|
483
|
+
// @codesyncer-decision: Changed
|
|
484
|
+
const API_URL = '/new';
|
|
219
485
|
```
|
|
220
486
|
|
|
221
487
|
---
|
|
222
488
|
|
|
223
489
|
## 🔍 Comment Search
|
|
224
490
|
|
|
225
|
-
###
|
|
491
|
+
### Project-wide Search
|
|
226
492
|
|
|
227
493
|
```bash
|
|
228
494
|
# Find all inferences
|
|
229
495
|
grep -r "@codesyncer-inference" ./src
|
|
230
496
|
|
|
231
|
-
#
|
|
497
|
+
# Find TODOs
|
|
232
498
|
grep -r "@codesyncer-todo" ./src
|
|
233
499
|
|
|
234
|
-
#
|
|
500
|
+
# Find decisions
|
|
235
501
|
grep -r "@codesyncer-decision" ./src
|
|
236
502
|
|
|
237
|
-
#
|
|
238
|
-
grep -r "@codesyncer-
|
|
503
|
+
# Find patterns (reuse)
|
|
504
|
+
grep -r "@codesyncer-pattern" ./src
|
|
239
505
|
|
|
240
|
-
#
|
|
241
|
-
grep -r "@codesyncer-
|
|
506
|
+
# Find specific pattern
|
|
507
|
+
grep -r "@codesyncer-pattern.*Retry" ./src
|
|
242
508
|
```
|
|
243
509
|
|
|
244
510
|
### VS Code Search
|
|
245
511
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
512
|
+
```
|
|
513
|
+
Cmd/Ctrl + Shift + F
|
|
514
|
+
→ @codesyncer-todo
|
|
515
|
+
→ src/**/*.{ts,tsx,js,jsx}
|
|
516
|
+
```
|
|
249
517
|
|
|
250
518
|
---
|
|
251
519
|
|
|
252
520
|
## 📊 Comment Statistics
|
|
253
521
|
|
|
254
|
-
ARCHITECTURE.md
|
|
522
|
+
Auto-aggregated in ARCHITECTURE.md:
|
|
255
523
|
|
|
256
524
|
```markdown
|
|
257
525
|
## Comment Tag Statistics
|
|
258
526
|
- @codesyncer-inference: 45
|
|
259
527
|
- @codesyncer-decision: 12
|
|
260
|
-
- @codesyncer-
|
|
261
|
-
- @codesyncer-
|
|
262
|
-
- @codesyncer-context: 15
|
|
528
|
+
- @codesyncer-pattern: 8
|
|
529
|
+
- @codesyncer-todo: 3
|
|
263
530
|
```
|
|
264
531
|
|
|
265
|
-
|
|
532
|
+
Command: `"update stats"`
|
|
266
533
|
|
|
267
534
|
---
|
|
268
535
|
|
|
269
|
-
## 💡
|
|
536
|
+
## 💡 Why Comments Replace Documentation
|
|
270
537
|
|
|
271
|
-
###
|
|
272
|
-
|
|
273
|
-
```tsx
|
|
274
|
-
// ❌ @codesyncer-inference: Using useState
|
|
275
|
-
// ✅ @codesyncer-inference: Using useState (simple local state, Zustand unnecessary)
|
|
538
|
+
### Problems with Separate Docs
|
|
276
539
|
```
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
540
|
+
❌ Separate documentation
|
|
541
|
+
→ AI can't read
|
|
542
|
+
→ Code and docs diverge
|
|
543
|
+
→ Docs never updated
|
|
544
|
+
|
|
545
|
+
❌ Long guide documents
|
|
546
|
+
→ Exceeds AI context
|
|
547
|
+
→ Not actually applied
|
|
548
|
+
→ Forgotten
|
|
283
549
|
```
|
|
284
550
|
|
|
285
|
-
###
|
|
286
|
-
|
|
287
|
-
```tsx
|
|
288
|
-
// ❌ @codesyncer-todo: Needs fix
|
|
289
|
-
// ✅ @codesyncer-todo: Add error boundary (fallback UI for API failures)
|
|
551
|
+
### Benefits of Comment-based Approach
|
|
290
552
|
```
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
```tsx
|
|
302
|
-
// ❌ @codesyncer-rule: Using TypeScript (this is obvious)
|
|
303
|
-
// ✅ @codesyncer-rule: This file only allows any type (external library has no types)
|
|
553
|
+
✅ Record directly in code
|
|
554
|
+
→ Permanent preservation
|
|
555
|
+
→ Git version control
|
|
556
|
+
→ Always in sync with code
|
|
557
|
+
|
|
558
|
+
✅ Only where needed
|
|
559
|
+
→ Context efficient
|
|
560
|
+
→ Searchable
|
|
561
|
+
→ AI actually references
|
|
304
562
|
```
|
|
305
563
|
|
|
306
564
|
---
|
|
307
565
|
|
|
308
566
|
## 🎯 Checklist
|
|
309
567
|
|
|
310
|
-
After writing code
|
|
568
|
+
After writing code:
|
|
311
569
|
|
|
312
|
-
- [ ]
|
|
313
|
-
- [ ]
|
|
314
|
-
- [ ]
|
|
315
|
-
- [ ]
|
|
316
|
-
- [ ]
|
|
317
|
-
- [ ]
|
|
570
|
+
- [ ] All inferences have `@codesyncer-inference` + rationale
|
|
571
|
+
- [ ] Decisions have `@codesyncer-decision` + [date] + reason
|
|
572
|
+
- [ ] Trade-offs marked with `@codesyncer-tradeoff`
|
|
573
|
+
- [ ] Reusable patterns tagged `@codesyncer-pattern`
|
|
574
|
+
- [ ] Items needing confirmation have `@codesyncer-todo`
|
|
575
|
+
- [ ] Complex logic explained with `@codesyncer-why`
|
|
318
576
|
|
|
319
577
|
---
|
|
320
578
|
|
|
321
|
-
**Version**:
|
|
579
|
+
**Version**: 2.0.0
|
|
322
580
|
**Last Updated**: [TODAY]
|
|
323
581
|
|
|
324
|
-
*
|
|
582
|
+
*Comments are the documentation. Record all context in code.*
|