@haposoft/cafekit 0.3.1 → 0.3.5
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 +3 -3
- package/bin/install.js +1 -0
- package/package.json +1 -1
- package/src/antigravity/workflows/impact-analysis-output-example.md +313 -0
- package/src/antigravity/workflows/impact-analysis.md +735 -0
- package/src/claude/migration-manifest.json +2 -1
- package/src/common/skills/impact-analysis/SKILL.md +271 -0
- package/src/common/skills/impact-analysis/references/change-detection.md +270 -0
- package/src/common/skills/impact-analysis/references/dependency-scouting.md +337 -0
- package/src/common/skills/impact-analysis/references/edge-case-identification.md +439 -0
- package/src/common/skills/impact-analysis/references/industry-techniques.md +695 -0
- package/src/common/skills/impact-analysis/references/practical-techniques-guide.md +753 -0
- package/src/common/skills/impact-analysis/references/project-detection.md +704 -0
- package/src/common/skills/impact-analysis/references/react-native-customization.md +508 -0
- package/src/common/skills/impact-analysis/references/report-template.md +604 -0
- package/src/common/skills/impact-analysis/references/test-scenario-generation.md +459 -0
- package/src/common/skills/impact-analysis/scripts/README.md +476 -0
- package/src/common/skills/impact-analysis/scripts/ast-analyze.js +403 -0
- package/src/common/skills/impact-analysis/scripts/calculate-risk.js +475 -0
- package/src/common/skills/impact-analysis/scripts/find-dependencies.sh +202 -0
- package/src/common/skills/impact-analysis/scripts/run-analysis.sh +312 -0
|
@@ -0,0 +1,439 @@
|
|
|
1
|
+
# Edge Case Identification - Xác Định Edge Cases
|
|
2
|
+
|
|
3
|
+
Phương pháp phát hiện và phân tích edge cases để tránh bugs tiềm ẩn.
|
|
4
|
+
|
|
5
|
+
## Edge Case Categories
|
|
6
|
+
|
|
7
|
+
### 1. Data Flow Issues
|
|
8
|
+
|
|
9
|
+
#### Null/Undefined Handling
|
|
10
|
+
|
|
11
|
+
```typescript
|
|
12
|
+
// ❌ Dangerous
|
|
13
|
+
function getUser(id: string) {
|
|
14
|
+
const user = users.find(u => u.id === id);
|
|
15
|
+
return user.name; // Crash if user not found
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// ✅ Safe
|
|
19
|
+
function getUser(id: string) {
|
|
20
|
+
const user = users.find(u => u.id === id);
|
|
21
|
+
return user?.name ?? 'Unknown';
|
|
22
|
+
}
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
**Check:**
|
|
26
|
+
- [ ] Có check null/undefined trước khi access?
|
|
27
|
+
- [ ] Optional chaining (`?.`) được sử dụng?
|
|
28
|
+
- [ ] Nullish coalescing (`??`) cho default values?
|
|
29
|
+
- [ ] Type guards cho union types?
|
|
30
|
+
|
|
31
|
+
#### Empty Collections
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
// ❌ Assumes array has items
|
|
35
|
+
const firstUser = users[0].name;
|
|
36
|
+
|
|
37
|
+
// ✅ Handles empty array
|
|
38
|
+
const firstUser = users.length > 0 ? users[0].name : null;
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
**Check:**
|
|
42
|
+
- [ ] Array.length check trước khi access index?
|
|
43
|
+
- [ ] Empty array handling trong map/filter/reduce?
|
|
44
|
+
- [ ] Object.keys() check trước khi iterate?
|
|
45
|
+
|
|
46
|
+
#### Type Coercion
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
// ❌ Implicit coercion
|
|
50
|
+
if (value) { } // false for 0, '', false, null, undefined
|
|
51
|
+
|
|
52
|
+
// ✅ Explicit check
|
|
53
|
+
if (value !== null && value !== undefined) { }
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
**Check:**
|
|
57
|
+
- [ ] Strict equality (`===`) thay vì loose (`==`)?
|
|
58
|
+
- [ ] Explicit type checks cho boolean logic?
|
|
59
|
+
- [ ] Number validation (isNaN, isFinite)?
|
|
60
|
+
|
|
61
|
+
### 2. Boundary Conditions
|
|
62
|
+
|
|
63
|
+
#### String Length
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
66
|
+
// Edge cases
|
|
67
|
+
const inputs = [
|
|
68
|
+
'', // Empty
|
|
69
|
+
'a', // Single char
|
|
70
|
+
'x'.repeat(1000), // Very long
|
|
71
|
+
'🎉', // Unicode/emoji
|
|
72
|
+
'hello\nworld', // Newlines
|
|
73
|
+
' spaces ' // Leading/trailing spaces
|
|
74
|
+
];
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
**Check:**
|
|
78
|
+
- [ ] Min/max length validation?
|
|
79
|
+
- [ ] Trim whitespace?
|
|
80
|
+
- [ ] Unicode/emoji handling?
|
|
81
|
+
- [ ] Special characters escaped?
|
|
82
|
+
|
|
83
|
+
#### Numeric Ranges
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
// Edge cases
|
|
87
|
+
const numbers = [
|
|
88
|
+
0, // Zero
|
|
89
|
+
-1, // Negative
|
|
90
|
+
Number.MAX_SAFE_INTEGER, // Max
|
|
91
|
+
Number.MIN_SAFE_INTEGER, // Min
|
|
92
|
+
0.1 + 0.2, // Float precision
|
|
93
|
+
Infinity, // Infinity
|
|
94
|
+
NaN // Not a number
|
|
95
|
+
];
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
**Check:**
|
|
99
|
+
- [ ] Min/max bounds validation?
|
|
100
|
+
- [ ] Zero handling (division by zero)?
|
|
101
|
+
- [ ] Negative number handling?
|
|
102
|
+
- [ ] Float precision issues?
|
|
103
|
+
- [ ] Infinity/NaN checks?
|
|
104
|
+
|
|
105
|
+
#### Date/Time
|
|
106
|
+
|
|
107
|
+
```typescript
|
|
108
|
+
// Edge cases
|
|
109
|
+
const dates = [
|
|
110
|
+
new Date('2024-02-29'), // Leap year
|
|
111
|
+
new Date('2024-12-31'), // Year boundary
|
|
112
|
+
new Date('invalid'), // Invalid date
|
|
113
|
+
new Date(0), // Unix epoch
|
|
114
|
+
new Date('2024-03-10T02:30:00'), // DST transition
|
|
115
|
+
];
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
**Check:**
|
|
119
|
+
- [ ] Timezone handling?
|
|
120
|
+
- [ ] Leap year logic?
|
|
121
|
+
- [ ] Date validation?
|
|
122
|
+
- [ ] DST transitions?
|
|
123
|
+
- [ ] Date range limits?
|
|
124
|
+
|
|
125
|
+
### 3. Error Scenarios
|
|
126
|
+
|
|
127
|
+
#### Network Failures
|
|
128
|
+
|
|
129
|
+
```typescript
|
|
130
|
+
// ❌ No error handling
|
|
131
|
+
const data = await fetch('/api/users');
|
|
132
|
+
return data.json();
|
|
133
|
+
|
|
134
|
+
// ✅ Comprehensive error handling
|
|
135
|
+
try {
|
|
136
|
+
const response = await fetch('/api/users');
|
|
137
|
+
if (!response.ok) {
|
|
138
|
+
throw new Error(`HTTP ${response.status}`);
|
|
139
|
+
}
|
|
140
|
+
return await response.json();
|
|
141
|
+
} catch (error) {
|
|
142
|
+
if (error instanceof TypeError) {
|
|
143
|
+
// Network error
|
|
144
|
+
} else {
|
|
145
|
+
// Other errors
|
|
146
|
+
}
|
|
147
|
+
throw error;
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
**Check:**
|
|
152
|
+
- [ ] Network timeout handling?
|
|
153
|
+
- [ ] HTTP error status handling?
|
|
154
|
+
- [ ] Retry logic for transient failures?
|
|
155
|
+
- [ ] Offline mode support?
|
|
156
|
+
|
|
157
|
+
#### Database Errors
|
|
158
|
+
|
|
159
|
+
```typescript
|
|
160
|
+
// ❌ No transaction
|
|
161
|
+
await prisma.user.create({ data: userData });
|
|
162
|
+
await prisma.profile.create({ data: profileData });
|
|
163
|
+
|
|
164
|
+
// ✅ Transaction with rollback
|
|
165
|
+
await prisma.$transaction(async (tx) => {
|
|
166
|
+
await tx.user.create({ data: userData });
|
|
167
|
+
await tx.profile.create({ data: profileData });
|
|
168
|
+
});
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
**Check:**
|
|
172
|
+
- [ ] Transaction boundaries?
|
|
173
|
+
- [ ] Rollback on error?
|
|
174
|
+
- [ ] Unique constraint violations?
|
|
175
|
+
- [ ] Foreign key violations?
|
|
176
|
+
- [ ] Connection pool exhaustion?
|
|
177
|
+
|
|
178
|
+
#### Permission Denied
|
|
179
|
+
|
|
180
|
+
```typescript
|
|
181
|
+
// ❌ Assumes authorized
|
|
182
|
+
function deleteUser(userId: string) {
|
|
183
|
+
return prisma.user.delete({ where: { id: userId } });
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// ✅ Authorization check
|
|
187
|
+
function deleteUser(userId: string, currentUserId: string) {
|
|
188
|
+
if (userId !== currentUserId && !isAdmin(currentUserId)) {
|
|
189
|
+
throw new UnauthorizedError();
|
|
190
|
+
}
|
|
191
|
+
return prisma.user.delete({ where: { id: userId } });
|
|
192
|
+
}
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
**Check:**
|
|
196
|
+
- [ ] Authentication check?
|
|
197
|
+
- [ ] Authorization check?
|
|
198
|
+
- [ ] Role-based access control?
|
|
199
|
+
- [ ] Resource ownership validation?
|
|
200
|
+
|
|
201
|
+
### 4. Race Conditions
|
|
202
|
+
|
|
203
|
+
#### Concurrent Requests
|
|
204
|
+
|
|
205
|
+
```typescript
|
|
206
|
+
// ❌ Race condition
|
|
207
|
+
let counter = 0;
|
|
208
|
+
async function increment() {
|
|
209
|
+
const current = counter;
|
|
210
|
+
await delay(10);
|
|
211
|
+
counter = current + 1; // Lost updates!
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// ✅ Atomic operation
|
|
215
|
+
async function increment() {
|
|
216
|
+
await prisma.counter.update({
|
|
217
|
+
where: { id: 1 },
|
|
218
|
+
data: { value: { increment: 1 } }
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
**Check:**
|
|
224
|
+
- [ ] Atomic database operations?
|
|
225
|
+
- [ ] Optimistic locking?
|
|
226
|
+
- [ ] Idempotency keys?
|
|
227
|
+
- [ ] Debouncing/throttling?
|
|
228
|
+
|
|
229
|
+
#### State Mutations
|
|
230
|
+
|
|
231
|
+
```typescript
|
|
232
|
+
// ❌ Shared mutable state
|
|
233
|
+
const state = { count: 0 };
|
|
234
|
+
function increment() {
|
|
235
|
+
state.count++; // Race condition
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// ✅ Immutable updates
|
|
239
|
+
function increment(state) {
|
|
240
|
+
return { ...state, count: state.count + 1 };
|
|
241
|
+
}
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
**Check:**
|
|
245
|
+
- [ ] Immutable data structures?
|
|
246
|
+
- [ ] Pure functions?
|
|
247
|
+
- [ ] State management (Redux/Zustand)?
|
|
248
|
+
- [ ] React concurrent mode safe?
|
|
249
|
+
|
|
250
|
+
### 5. Integration Points
|
|
251
|
+
|
|
252
|
+
#### API Contract Changes
|
|
253
|
+
|
|
254
|
+
```typescript
|
|
255
|
+
// Old API response
|
|
256
|
+
{ "user": { "name": "John" } }
|
|
257
|
+
|
|
258
|
+
// New API response (breaking change!)
|
|
259
|
+
{ "data": { "user": { "name": "John" } } }
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
**Check:**
|
|
263
|
+
- [ ] Backward compatibility?
|
|
264
|
+
- [ ] API versioning?
|
|
265
|
+
- [ ] Response schema validation?
|
|
266
|
+
- [ ] Graceful degradation?
|
|
267
|
+
|
|
268
|
+
#### Event Handlers
|
|
269
|
+
|
|
270
|
+
```typescript
|
|
271
|
+
// ❌ Missing cleanup
|
|
272
|
+
useEffect(() => {
|
|
273
|
+
window.addEventListener('resize', handleResize);
|
|
274
|
+
}, []);
|
|
275
|
+
|
|
276
|
+
// ✅ Cleanup on unmount
|
|
277
|
+
useEffect(() => {
|
|
278
|
+
window.addEventListener('resize', handleResize);
|
|
279
|
+
return () => window.removeEventListener('resize', handleResize);
|
|
280
|
+
}, []);
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
**Check:**
|
|
284
|
+
- [ ] Event listener cleanup?
|
|
285
|
+
- [ ] useEffect dependencies correct?
|
|
286
|
+
- [ ] Memory leaks prevented?
|
|
287
|
+
- [ ] Stale closures avoided?
|
|
288
|
+
|
|
289
|
+
## Edge Case Checklist
|
|
290
|
+
|
|
291
|
+
### For Every Change
|
|
292
|
+
|
|
293
|
+
```markdown
|
|
294
|
+
## Data Flow
|
|
295
|
+
- [ ] Null/undefined handling
|
|
296
|
+
- [ ] Empty collections
|
|
297
|
+
- [ ] Type coercion issues
|
|
298
|
+
|
|
299
|
+
## Boundaries
|
|
300
|
+
- [ ] String length (empty, very long, unicode)
|
|
301
|
+
- [ ] Numeric ranges (0, negative, max, float)
|
|
302
|
+
- [ ] Date/time (timezone, leap year, DST)
|
|
303
|
+
|
|
304
|
+
## Errors
|
|
305
|
+
- [ ] Network failures
|
|
306
|
+
- [ ] Database errors
|
|
307
|
+
- [ ] Permission denied
|
|
308
|
+
- [ ] Validation errors
|
|
309
|
+
|
|
310
|
+
## Concurrency
|
|
311
|
+
- [ ] Race conditions
|
|
312
|
+
- [ ] State mutations
|
|
313
|
+
- [ ] Atomic operations
|
|
314
|
+
|
|
315
|
+
## Integration
|
|
316
|
+
- [ ] API contract changes
|
|
317
|
+
- [ ] Event handler cleanup
|
|
318
|
+
- [ ] Dependency updates
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
## Automated Detection
|
|
322
|
+
|
|
323
|
+
### Static Analysis
|
|
324
|
+
|
|
325
|
+
```bash
|
|
326
|
+
# TypeScript strict mode
|
|
327
|
+
tsc --strict --noEmit
|
|
328
|
+
|
|
329
|
+
# ESLint rules
|
|
330
|
+
eslint --rule 'no-unsafe-optional-chaining: error'
|
|
331
|
+
eslint --rule 'no-unsafe-member-access: error'
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
### Runtime Checks
|
|
335
|
+
|
|
336
|
+
```typescript
|
|
337
|
+
// Add assertions
|
|
338
|
+
function divide(a: number, b: number) {
|
|
339
|
+
if (b === 0) throw new Error('Division by zero');
|
|
340
|
+
if (!Number.isFinite(a) || !Number.isFinite(b)) {
|
|
341
|
+
throw new Error('Invalid number');
|
|
342
|
+
}
|
|
343
|
+
return a / b;
|
|
344
|
+
}
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
## Edge Case Patterns
|
|
348
|
+
|
|
349
|
+
### Pattern 1: Guard Clauses
|
|
350
|
+
|
|
351
|
+
```typescript
|
|
352
|
+
function processUser(user: User | null) {
|
|
353
|
+
// Early returns for edge cases
|
|
354
|
+
if (!user) return null;
|
|
355
|
+
if (!user.email) return null;
|
|
356
|
+
if (user.deleted) return null;
|
|
357
|
+
|
|
358
|
+
// Main logic
|
|
359
|
+
return transformUser(user);
|
|
360
|
+
}
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
### Pattern 2: Default Values
|
|
364
|
+
|
|
365
|
+
```typescript
|
|
366
|
+
function getConfig(options: Partial<Config> = {}) {
|
|
367
|
+
return {
|
|
368
|
+
timeout: options.timeout ?? 5000,
|
|
369
|
+
retries: options.retries ?? 3,
|
|
370
|
+
cache: options.cache ?? true,
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
### Pattern 3: Validation Layer
|
|
376
|
+
|
|
377
|
+
```typescript
|
|
378
|
+
function createUser(data: unknown) {
|
|
379
|
+
// Validate at boundary
|
|
380
|
+
const validated = UserSchema.parse(data);
|
|
381
|
+
|
|
382
|
+
// Safe to use
|
|
383
|
+
return prisma.user.create({ data: validated });
|
|
384
|
+
}
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
## Output Format
|
|
388
|
+
|
|
389
|
+
```json
|
|
390
|
+
{
|
|
391
|
+
"edgeCases": [
|
|
392
|
+
{
|
|
393
|
+
"category": "data-flow",
|
|
394
|
+
"issue": "Null pointer in user.profile.avatar",
|
|
395
|
+
"location": "src/components/Avatar.tsx:15",
|
|
396
|
+
"severity": "high",
|
|
397
|
+
"recommendation": "Add optional chaining: user?.profile?.avatar"
|
|
398
|
+
},
|
|
399
|
+
{
|
|
400
|
+
"category": "boundary",
|
|
401
|
+
"issue": "No max length validation on bio field",
|
|
402
|
+
"location": "src/api/users.ts:45",
|
|
403
|
+
"severity": "medium",
|
|
404
|
+
"recommendation": "Add validation: bio.length <= 500"
|
|
405
|
+
},
|
|
406
|
+
{
|
|
407
|
+
"category": "error",
|
|
408
|
+
"issue": "Network error not handled",
|
|
409
|
+
"location": "src/services/api.ts:23",
|
|
410
|
+
"severity": "high",
|
|
411
|
+
"recommendation": "Add try-catch with retry logic"
|
|
412
|
+
}
|
|
413
|
+
]
|
|
414
|
+
}
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
## Integration with Testing
|
|
418
|
+
|
|
419
|
+
Edge cases identified → Test scenarios:
|
|
420
|
+
|
|
421
|
+
```typescript
|
|
422
|
+
describe('User API', () => {
|
|
423
|
+
// From edge case analysis
|
|
424
|
+
it('handles null user gracefully', () => {
|
|
425
|
+
expect(getUser(null)).toBe(null);
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
it('validates email length', () => {
|
|
429
|
+
const longEmail = 'a'.repeat(300) + '@test.com';
|
|
430
|
+
expect(() => createUser({ email: longEmail }))
|
|
431
|
+
.toThrow('Email too long');
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
it('handles network timeout', async () => {
|
|
435
|
+
mockFetch.mockRejectedValue(new Error('timeout'));
|
|
436
|
+
await expect(fetchUsers()).rejects.toThrow();
|
|
437
|
+
});
|
|
438
|
+
});
|
|
439
|
+
```
|