Initial commit
This commit is contained in:
1028
skills/api-best-practices/SKILL.md
Normal file
1028
skills/api-best-practices/SKILL.md
Normal file
File diff suppressed because it is too large
Load Diff
1092
skills/bmad-methodology/skill.md
Normal file
1092
skills/bmad-methodology/skill.md
Normal file
File diff suppressed because it is too large
Load Diff
1074
skills/code-quality-standards/SKILL.md
Normal file
1074
skills/code-quality-standards/SKILL.md
Normal file
File diff suppressed because it is too large
Load Diff
773
skills/debugging-methodology/SKILL.md
Normal file
773
skills/debugging-methodology/SKILL.md
Normal file
@@ -0,0 +1,773 @@
|
||||
---
|
||||
name: debugging-methodology
|
||||
description: Scientific debugging methodology including hypothesis-driven debugging, bug reproduction, binary search debugging, stack trace analysis, logging strategies, and root cause analysis. Use when debugging errors, analyzing stack traces, investigating bugs, or troubleshooting performance issues.
|
||||
---
|
||||
|
||||
# Debugging Methodology
|
||||
|
||||
This skill provides comprehensive guidance for systematically debugging issues using scientific methods and proven techniques.
|
||||
|
||||
## Scientific Debugging Method
|
||||
|
||||
### The Scientific Approach
|
||||
|
||||
**1. Observe**: Gather information about the bug
|
||||
**2. Hypothesize**: Form theories about the cause
|
||||
**3. Test**: Design experiments to test hypotheses
|
||||
**4. Analyze**: Evaluate results
|
||||
**5. Conclude**: Fix the bug or refine hypothesis
|
||||
|
||||
### Example: Debugging a Login Issue
|
||||
|
||||
```typescript
|
||||
// Bug: Users cannot log in
|
||||
|
||||
// 1. OBSERVE
|
||||
// - Error message: "Invalid credentials"
|
||||
// - Happens for all users
|
||||
// - Started after last deployment
|
||||
// - Logs show: "bcrypt compare failed"
|
||||
|
||||
// 2. HYPOTHESIZE
|
||||
// Hypothesis 1: Password comparison logic is broken
|
||||
// Hypothesis 2: Database passwords corrupted
|
||||
// Hypothesis 3: Bcrypt library updated with breaking change
|
||||
|
||||
// 3. TEST
|
||||
// Test 1: Check if bcrypt library version changed
|
||||
const packageLock = await fs.readFile('package-lock.json');
|
||||
// Result: bcrypt upgraded from 5.0.0 to 6.0.0
|
||||
|
||||
// Test 2: Check bcrypt changelog
|
||||
// Result: v6.0.0 changed default salt rounds
|
||||
|
||||
// Test 3: Verify password hashing
|
||||
const testPassword = 'password123';
|
||||
const oldHash = '$2b$10$...'; // From database
|
||||
const newHash = await bcrypt.hash(testPassword, 10);
|
||||
console.log(await bcrypt.compare(testPassword, oldHash)); // false
|
||||
console.log(await bcrypt.compare(testPassword, newHash)); // true
|
||||
|
||||
// 4. ANALYZE
|
||||
// Old hashes use $2b$ format, new version uses $2a$ format
|
||||
// Incompatible hash formats
|
||||
|
||||
// 5. CONCLUDE
|
||||
// Rollback bcrypt to 5.x or migrate all password hashes
|
||||
```
|
||||
|
||||
## Reproducing Bugs Consistently
|
||||
|
||||
### Creating Minimal Reproduction
|
||||
|
||||
```typescript
|
||||
// Original bug report: "App crashes when clicking submit"
|
||||
|
||||
// Step 1: Remove unrelated code
|
||||
// ❌ BAD - Too much noise
|
||||
function handleSubmit() {
|
||||
validateForm();
|
||||
checkPermissions();
|
||||
logAnalytics();
|
||||
sendToServer();
|
||||
updateUI();
|
||||
showNotification();
|
||||
// Which one causes the crash?
|
||||
}
|
||||
|
||||
// ✅ GOOD - Minimal reproduction
|
||||
function handleSubmit() {
|
||||
// Removed: validateForm, checkPermissions, logAnalytics, updateUI, showNotification
|
||||
// Bug still occurs with just:
|
||||
sendToServer();
|
||||
// Root cause: sendToServer crashes with undefined data
|
||||
}
|
||||
```
|
||||
|
||||
### Reproducing Race Conditions
|
||||
|
||||
```typescript
|
||||
// Bug: Intermittent "Cannot read property of undefined"
|
||||
|
||||
// Make race condition reproducible with delays
|
||||
async function fetchUserData() {
|
||||
const user = await fetchUser();
|
||||
// Add artificial delay to make race condition consistent
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
return user.profile; // Sometimes undefined
|
||||
}
|
||||
|
||||
// Once reproducible, investigate:
|
||||
// - Are multiple requests racing?
|
||||
// - Is data being cleared too early?
|
||||
// - Are promises resolving out of order?
|
||||
```
|
||||
|
||||
### Creating Test Cases
|
||||
|
||||
```typescript
|
||||
// Once bug is reproducible, create failing test
|
||||
describe('Login', () => {
|
||||
test('should authenticate user with valid credentials', async () => {
|
||||
const user = await db.user.create({
|
||||
email: 'test@example.com',
|
||||
password: await bcrypt.hash('password123', 10),
|
||||
});
|
||||
|
||||
const result = await login('test@example.com', 'password123');
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.user.email).toBe('test@example.com');
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
## Binary Search Debugging
|
||||
|
||||
### Finding the Breaking Commit
|
||||
|
||||
```bash
|
||||
# Use git bisect to find the commit that introduced the bug
|
||||
|
||||
# Start bisect
|
||||
git bisect start
|
||||
|
||||
# Mark current commit as bad (has the bug)
|
||||
git bisect bad
|
||||
|
||||
# Mark a known good commit (before bug appeared)
|
||||
git bisect good v1.2.0
|
||||
|
||||
# Git will checkout a commit in the middle
|
||||
# Test if bug exists, then mark:
|
||||
git bisect bad # Bug exists in this commit
|
||||
# or
|
||||
git bisect good # Bug doesn't exist in this commit
|
||||
|
||||
# Repeat until git identifies the breaking commit
|
||||
# Git will output: "abc123 is the first bad commit"
|
||||
|
||||
# End bisect session
|
||||
git bisect reset
|
||||
```
|
||||
|
||||
### Automated Bisect
|
||||
|
||||
```bash
|
||||
# Create test script that exits 0 (pass) or 1 (fail)
|
||||
# test.sh
|
||||
#!/bin/bash
|
||||
npm test 2>&1 | grep -q "Login test failed"
|
||||
if [ $? -eq 0 ]; then
|
||||
exit 1 # Bug found
|
||||
else
|
||||
exit 0 # Bug not found
|
||||
fi
|
||||
|
||||
# Run automated bisect
|
||||
git bisect start HEAD v1.2.0
|
||||
git bisect run ./test.sh
|
||||
|
||||
# Git will automatically find the breaking commit
|
||||
```
|
||||
|
||||
### Binary Search in Code
|
||||
|
||||
```typescript
|
||||
// Bug: Function returns wrong result for large arrays
|
||||
|
||||
function processArray(arr: number[]): number {
|
||||
// 100 lines of code
|
||||
// Which line causes the bug?
|
||||
}
|
||||
|
||||
// Binary search approach:
|
||||
// 1. Comment out second half
|
||||
function processArray(arr: number[]): number {
|
||||
// Lines 1-50
|
||||
// Lines 51-100 (commented out)
|
||||
}
|
||||
// If bug disappears: Bug is in lines 51-100
|
||||
// If bug persists: Bug is in lines 1-50
|
||||
|
||||
// 2. Repeat on the problematic half
|
||||
// Continue until you isolate the buggy line
|
||||
```
|
||||
|
||||
## Stack Trace Analysis
|
||||
|
||||
### Reading Stack Traces
|
||||
|
||||
```
|
||||
Error: Cannot read property 'name' of undefined
|
||||
at getUserName (/app/src/user.ts:42:20)
|
||||
at formatUserProfile (/app/src/profile.ts:15:25)
|
||||
at handleRequest (/app/src/api.ts:89:30)
|
||||
at Layer.handle [as handle_request] (/app/node_modules/express/lib/router/layer.js:95:5)
|
||||
```
|
||||
|
||||
**Analysis**:
|
||||
1. **Error type**: TypeError - trying to access property on undefined
|
||||
2. **Error message**: "Cannot read property 'name' of undefined"
|
||||
3. **Origin**: `getUserName` function at line 42
|
||||
4. **Call chain**: api.ts → profile.ts → user.ts
|
||||
5. **Root cause location**: user.ts:42
|
||||
|
||||
### Investigating the Stack Trace
|
||||
|
||||
```typescript
|
||||
// user.ts:42
|
||||
function getUserName(userId: string): string {
|
||||
const user = cache.get(userId);
|
||||
return user.name; // ← Line 42: user is undefined
|
||||
}
|
||||
|
||||
// Why is user undefined?
|
||||
// 1. Check cache.get implementation
|
||||
// 2. Check if userId is valid
|
||||
// 3. Check if user exists in cache
|
||||
|
||||
// Add defensive check:
|
||||
function getUserName(userId: string): string {
|
||||
const user = cache.get(userId);
|
||||
if (!user) {
|
||||
throw new Error(`User not found in cache: ${userId}`);
|
||||
}
|
||||
return user.name;
|
||||
}
|
||||
```
|
||||
|
||||
### Source Maps for Production
|
||||
|
||||
```javascript
|
||||
// Enable source maps in production
|
||||
// webpack.config.js
|
||||
module.exports = {
|
||||
devtool: 'source-map',
|
||||
// This generates .map files for production debugging
|
||||
};
|
||||
|
||||
// View original TypeScript code in production errors
|
||||
// Instead of:
|
||||
// at r (/app/bundle.js:1:23456)
|
||||
// You see:
|
||||
// at getUserName (/app/src/user.ts:42:20)
|
||||
```
|
||||
|
||||
## Logging Strategies
|
||||
|
||||
### Strategic Log Placement
|
||||
|
||||
```typescript
|
||||
// ✅ GOOD - Log at key decision points
|
||||
async function processOrder(order: Order) {
|
||||
logger.info('Processing order', { orderId: order.id, items: order.items.length });
|
||||
|
||||
try {
|
||||
// Log before critical operations
|
||||
logger.debug('Validating order', { orderId: order.id });
|
||||
await validateOrder(order);
|
||||
|
||||
logger.debug('Processing payment', { orderId: order.id, amount: order.total });
|
||||
const payment = await processPayment(order);
|
||||
|
||||
logger.info('Order processed successfully', {
|
||||
orderId: order.id,
|
||||
paymentId: payment.id,
|
||||
duration: Date.now() - startTime,
|
||||
});
|
||||
|
||||
return payment;
|
||||
} catch (error) {
|
||||
// Log errors with context
|
||||
logger.error('Order processing failed', {
|
||||
orderId: order.id,
|
||||
error: error.message,
|
||||
stack: error.stack,
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Structured Logging
|
||||
|
||||
```typescript
|
||||
import winston from 'winston';
|
||||
|
||||
const logger = winston.createLogger({
|
||||
level: process.env.LOG_LEVEL || 'info',
|
||||
format: winston.format.combine(
|
||||
winston.format.timestamp(),
|
||||
winston.format.errors({ stack: true }),
|
||||
winston.format.json()
|
||||
),
|
||||
defaultMeta: {
|
||||
service: 'order-service',
|
||||
version: process.env.APP_VERSION,
|
||||
},
|
||||
transports: [
|
||||
new winston.transports.File({ filename: 'error.log', level: 'error' }),
|
||||
new winston.transports.File({ filename: 'combined.log' }),
|
||||
],
|
||||
});
|
||||
|
||||
// Output:
|
||||
// {
|
||||
// "timestamp": "2025-10-16T10:30:00.000Z",
|
||||
// "level": "error",
|
||||
// "message": "Order processing failed",
|
||||
// "orderId": "order_123",
|
||||
// "error": "Payment declined",
|
||||
// "service": "order-service",
|
||||
// "version": "1.2.3"
|
||||
// }
|
||||
```
|
||||
|
||||
### Log Levels
|
||||
|
||||
```typescript
|
||||
// Use appropriate log levels
|
||||
logger.debug('Detailed debug information'); // Development only
|
||||
logger.info('Normal operation'); // Informational
|
||||
logger.warn('Warning but not an error'); // Potential issues
|
||||
logger.error('Error occurred'); // Errors that need attention
|
||||
logger.fatal('Critical failure'); // System-wide failures
|
||||
|
||||
// Set log level by environment
|
||||
const logLevel = {
|
||||
development: 'debug',
|
||||
staging: 'info',
|
||||
production: 'warn',
|
||||
}[process.env.NODE_ENV];
|
||||
```
|
||||
|
||||
## Debugging Tools
|
||||
|
||||
### Using Debuggers
|
||||
|
||||
```typescript
|
||||
// Set breakpoints in VS Code
|
||||
function calculateTotal(items: Item[]): number {
|
||||
let total = 0;
|
||||
for (const item of items) {
|
||||
debugger; // Execution pauses here
|
||||
total += item.price * item.quantity;
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
// Or use VS Code launch.json
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Debug Tests",
|
||||
"program": "${workspaceFolder}/node_modules/.bin/jest",
|
||||
"args": ["--runInBand"],
|
||||
"console": "integratedTerminal"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Node.js Built-in Debugger
|
||||
|
||||
```bash
|
||||
# Start Node with inspector
|
||||
node --inspect index.js
|
||||
|
||||
# Open Chrome DevTools
|
||||
# Navigate to: chrome://inspect
|
||||
# Click "inspect" on your Node.js process
|
||||
|
||||
# Or use Node's built-in debugger
|
||||
node inspect index.js
|
||||
> cont # Continue
|
||||
> next # Step over
|
||||
> step # Step into
|
||||
> out # Step out
|
||||
> repl # Enter REPL to inspect variables
|
||||
```
|
||||
|
||||
### Memory Profiling
|
||||
|
||||
```typescript
|
||||
// Detect memory leaks
|
||||
import v8 from 'v8';
|
||||
import fs from 'fs';
|
||||
|
||||
// Take heap snapshot
|
||||
function takeHeapSnapshot(filename: string) {
|
||||
const snapshot = v8.writeHeapSnapshot(filename);
|
||||
console.log(`Heap snapshot written to ${snapshot}`);
|
||||
}
|
||||
|
||||
// Compare snapshots to find leaks
|
||||
takeHeapSnapshot('before.heapsnapshot');
|
||||
// ... run code that might leak
|
||||
takeHeapSnapshot('after.heapsnapshot');
|
||||
|
||||
// Analyze in Chrome DevTools:
|
||||
// 1. Open DevTools → Memory tab
|
||||
// 2. Load snapshot
|
||||
// 3. Compare snapshots
|
||||
// 4. Look for objects that grew significantly
|
||||
```
|
||||
|
||||
## Performance Profiling
|
||||
|
||||
### CPU Profiling
|
||||
|
||||
```typescript
|
||||
// Profile function execution time
|
||||
console.time('processLargeArray');
|
||||
processLargeArray(data);
|
||||
console.timeEnd('processLargeArray');
|
||||
// Output: processLargeArray: 1234.567ms
|
||||
|
||||
// More detailed profiling
|
||||
import { performance } from 'perf_hooks';
|
||||
|
||||
const start = performance.now();
|
||||
processLargeArray(data);
|
||||
const end = performance.now();
|
||||
console.log(`Execution time: ${end - start}ms`);
|
||||
```
|
||||
|
||||
### Node.js Profiler
|
||||
|
||||
```bash
|
||||
# Generate CPU profile
|
||||
node --prof index.js
|
||||
|
||||
# Process profile data
|
||||
node --prof-process isolate-0x*.log > profile.txt
|
||||
|
||||
# Analyze profile.txt to find slow functions
|
||||
```
|
||||
|
||||
### Chrome DevTools Performance
|
||||
|
||||
```typescript
|
||||
// Add performance marks
|
||||
performance.mark('start-data-processing');
|
||||
processData(data);
|
||||
performance.mark('end-data-processing');
|
||||
|
||||
performance.measure(
|
||||
'data-processing',
|
||||
'start-data-processing',
|
||||
'end-data-processing'
|
||||
);
|
||||
|
||||
const measure = performance.getEntriesByName('data-processing')[0];
|
||||
console.log(`Data processing took ${measure.duration}ms`);
|
||||
```
|
||||
|
||||
## Network Debugging
|
||||
|
||||
### HTTP Request Logging
|
||||
|
||||
```typescript
|
||||
import axios from 'axios';
|
||||
|
||||
// Add request/response interceptors
|
||||
axios.interceptors.request.use(
|
||||
(config) => {
|
||||
console.log('Request:', {
|
||||
method: config.method,
|
||||
url: config.url,
|
||||
headers: config.headers,
|
||||
data: config.data,
|
||||
});
|
||||
return config;
|
||||
},
|
||||
(error) => {
|
||||
console.error('Request error:', error);
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
axios.interceptors.response.use(
|
||||
(response) => {
|
||||
console.log('Response:', {
|
||||
status: response.status,
|
||||
headers: response.headers,
|
||||
data: response.data,
|
||||
});
|
||||
return response;
|
||||
},
|
||||
(error) => {
|
||||
console.error('Response error:', {
|
||||
status: error.response?.status,
|
||||
data: error.response?.data,
|
||||
message: error.message,
|
||||
});
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
### Debugging CORS Issues
|
||||
|
||||
```typescript
|
||||
// Enable detailed CORS logging
|
||||
import cors from 'cors';
|
||||
|
||||
const corsOptions = {
|
||||
origin: (origin, callback) => {
|
||||
console.log('CORS request from origin:', origin);
|
||||
|
||||
const allowedOrigins = ['https://app.example.com'];
|
||||
if (!origin || allowedOrigins.includes(origin)) {
|
||||
callback(null, true);
|
||||
} else {
|
||||
console.log('CORS blocked:', origin);
|
||||
callback(new Error('Not allowed by CORS'));
|
||||
}
|
||||
},
|
||||
credentials: true,
|
||||
};
|
||||
|
||||
app.use(cors(corsOptions));
|
||||
```
|
||||
|
||||
## Race Condition Detection
|
||||
|
||||
### Using Promises Correctly
|
||||
|
||||
```typescript
|
||||
// ❌ BAD - Race condition
|
||||
let userData = null;
|
||||
|
||||
async function loadUser() {
|
||||
userData = await fetchUser(); // Takes 100ms
|
||||
}
|
||||
|
||||
async function displayUser() {
|
||||
console.log(userData.name); // May be null if loadUser not finished
|
||||
}
|
||||
|
||||
loadUser();
|
||||
displayUser(); // Race condition!
|
||||
|
||||
// ✅ GOOD - Wait for promise
|
||||
async function main() {
|
||||
await loadUser();
|
||||
await displayUser(); // userData guaranteed to be loaded
|
||||
}
|
||||
```
|
||||
|
||||
### Detecting Concurrent Modifications
|
||||
|
||||
```typescript
|
||||
// Detect race conditions with version numbers
|
||||
interface Document {
|
||||
id: string;
|
||||
content: string;
|
||||
version: number;
|
||||
}
|
||||
|
||||
async function updateDocument(doc: Document) {
|
||||
// Read current version
|
||||
const current = await db.document.findUnique({
|
||||
where: { id: doc.id },
|
||||
});
|
||||
|
||||
// Check if version matches
|
||||
if (current.version !== doc.version) {
|
||||
throw new Error('Document was modified by another user');
|
||||
}
|
||||
|
||||
// Update with incremented version
|
||||
await db.document.update({
|
||||
where: { id: doc.id, version: doc.version },
|
||||
data: {
|
||||
content: doc.content,
|
||||
version: doc.version + 1,
|
||||
},
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
## Common Bug Patterns
|
||||
|
||||
### Off-by-One Errors
|
||||
|
||||
```typescript
|
||||
// ❌ BAD - Off by one
|
||||
const arr = [1, 2, 3, 4, 5];
|
||||
for (let i = 0; i <= arr.length; i++) {
|
||||
console.log(arr[i]); // Last iteration: undefined
|
||||
}
|
||||
|
||||
// ✅ GOOD
|
||||
for (let i = 0; i < arr.length; i++) {
|
||||
console.log(arr[i]);
|
||||
}
|
||||
```
|
||||
|
||||
### Null/Undefined Issues
|
||||
|
||||
```typescript
|
||||
// ❌ BAD - No null check
|
||||
function getUserName(user: User): string {
|
||||
return user.profile.name; // Crashes if user or profile is null
|
||||
}
|
||||
|
||||
// ✅ GOOD - Defensive checks
|
||||
function getUserName(user: User | null): string {
|
||||
if (!user) {
|
||||
return 'Unknown';
|
||||
}
|
||||
if (!user.profile) {
|
||||
return 'No profile';
|
||||
}
|
||||
return user.profile.name;
|
||||
}
|
||||
|
||||
// ✅ BETTER - Optional chaining
|
||||
function getUserName(user: User | null): string {
|
||||
return user?.profile?.name ?? 'Unknown';
|
||||
}
|
||||
```
|
||||
|
||||
### Async/Await Pitfalls
|
||||
|
||||
```typescript
|
||||
// ❌ BAD - Forgot await
|
||||
async function getUser(id: string) {
|
||||
const user = fetchUser(id); // Missing await!
|
||||
return user.name; // user is a Promise, not the actual user
|
||||
}
|
||||
|
||||
// ✅ GOOD
|
||||
async function getUser(id: string) {
|
||||
const user = await fetchUser(id);
|
||||
return user.name;
|
||||
}
|
||||
|
||||
// ❌ BAD - Sequential when parallel is possible
|
||||
async function loadData() {
|
||||
const users = await fetchUsers(); // 1 second
|
||||
const posts = await fetchPosts(); // 1 second
|
||||
const comments = await fetchComments(); // 1 second
|
||||
// Total: 3 seconds
|
||||
}
|
||||
|
||||
// ✅ GOOD - Parallel execution
|
||||
async function loadData() {
|
||||
const [users, posts, comments] = await Promise.all([
|
||||
fetchUsers(),
|
||||
fetchPosts(),
|
||||
fetchComments(),
|
||||
]);
|
||||
// Total: 1 second
|
||||
}
|
||||
```
|
||||
|
||||
## Root Cause Analysis (5 Whys)
|
||||
|
||||
### The 5 Whys Technique
|
||||
|
||||
```
|
||||
Problem: Application crashed in production
|
||||
|
||||
Why? Memory leak caused out-of-memory error
|
||||
Why? Array of user sessions kept growing
|
||||
Why? Sessions weren't being cleaned up
|
||||
Why? Cleanup function wasn't being called
|
||||
Why? Event listener for cleanup was never registered
|
||||
Root Cause: Missing initialization code in new deployment script
|
||||
```
|
||||
|
||||
### RCA Template
|
||||
|
||||
```markdown
|
||||
## Root Cause Analysis
|
||||
|
||||
**Date**: 2025-10-16
|
||||
**Incident**: API downtime (30 minutes)
|
||||
|
||||
### Timeline
|
||||
- 10:00 - Deployment started
|
||||
- 10:15 - First error reports
|
||||
- 10:20 - Incident declared
|
||||
- 10:25 - Rollback initiated
|
||||
- 10:30 - Service restored
|
||||
|
||||
### Impact
|
||||
- 500 users affected
|
||||
- 10% of API requests failed
|
||||
- $5,000 estimated revenue loss
|
||||
|
||||
### Root Cause
|
||||
Database connection pool exhausted due to missing connection cleanup in new feature code.
|
||||
|
||||
### 5 Whys
|
||||
1. Why did the API fail? → Database connections exhausted
|
||||
2. Why were connections exhausted? → Connections not returned to pool
|
||||
3. Why weren't connections returned? → Missing finally block in new code
|
||||
4. Why was the finally block missing? → Code review missed it
|
||||
5. Why did code review miss it? → No automated check for connection cleanup
|
||||
|
||||
### Immediate Actions Taken
|
||||
- Rolled back deployment
|
||||
- Manually closed leaked connections
|
||||
- Service restored
|
||||
|
||||
### Preventive Measures
|
||||
1. Add linter rule to detect missing finally blocks
|
||||
2. Add integration test for connection cleanup
|
||||
3. Update code review checklist
|
||||
4. Add monitoring for connection pool usage
|
||||
|
||||
### Lessons Learned
|
||||
- Need better monitoring of connection pool metrics
|
||||
- Database connection patterns should be abstracted
|
||||
- Code review process needs improvement
|
||||
```
|
||||
|
||||
## Debugging Checklist
|
||||
|
||||
**Before Debugging**:
|
||||
- [ ] Can you reproduce the bug consistently?
|
||||
- [ ] Do you have a minimal reproduction case?
|
||||
- [ ] Have you checked recent changes (git log)?
|
||||
- [ ] Have you read error messages carefully?
|
||||
- [ ] Have you checked logs?
|
||||
|
||||
**During Debugging**:
|
||||
- [ ] Are you using scientific method (hypothesis-driven)?
|
||||
- [ ] Have you added strategic logging?
|
||||
- [ ] Are you using a debugger effectively?
|
||||
- [ ] Have you isolated the problem area?
|
||||
- [ ] Have you considered race conditions?
|
||||
|
||||
**After Fixing**:
|
||||
- [ ] Does the fix address root cause (not just symptoms)?
|
||||
- [ ] Have you added tests to prevent regression?
|
||||
- [ ] Have you documented the fix?
|
||||
- [ ] Have you conducted root cause analysis?
|
||||
- [ ] Have you shared learnings with team?
|
||||
|
||||
## When to Use This Skill
|
||||
|
||||
Use this skill when:
|
||||
- Debugging production issues
|
||||
- Investigating bug reports
|
||||
- Analyzing error logs
|
||||
- Troubleshooting performance problems
|
||||
- Finding memory leaks
|
||||
- Resolving race conditions
|
||||
- Conducting post-mortems
|
||||
- Training team on debugging
|
||||
- Improving debugging processes
|
||||
- Setting up debugging tools
|
||||
|
||||
---
|
||||
|
||||
**Remember**: Debugging is detective work. Be systematic, stay curious, and always document what you learn. The bug you fix today will teach you how to prevent similar bugs tomorrow.
|
||||
1083
skills/devops-patterns/SKILL.md
Normal file
1083
skills/devops-patterns/SKILL.md
Normal file
File diff suppressed because it is too large
Load Diff
922
skills/frontend-patterns/SKILL.md
Normal file
922
skills/frontend-patterns/SKILL.md
Normal file
@@ -0,0 +1,922 @@
|
||||
---
|
||||
name: frontend-patterns
|
||||
description: Modern frontend architecture patterns for React, Next.js, and TypeScript including component composition, state management, performance optimization, accessibility, and responsive design. Use when building UI components, implementing frontend features, optimizing performance, or working with React/Next.js applications.
|
||||
---
|
||||
|
||||
# Frontend Development Patterns
|
||||
|
||||
This skill provides comprehensive guidance for modern frontend development using React, Next.js, TypeScript, and related technologies.
|
||||
|
||||
## Component Architecture
|
||||
|
||||
### Component Composition Patterns
|
||||
|
||||
**Container/Presentational Pattern**:
|
||||
```typescript
|
||||
// Presentational component (pure, reusable)
|
||||
interface UserCardProps {
|
||||
name: string;
|
||||
email: string;
|
||||
avatar: string;
|
||||
onEdit: () => void;
|
||||
}
|
||||
|
||||
function UserCard({ name, email, avatar, onEdit }: UserCardProps) {
|
||||
return (
|
||||
<div className="user-card">
|
||||
<img src={avatar} alt={name} />
|
||||
<h3>{name}</h3>
|
||||
<p>{email}</p>
|
||||
<button onClick={onEdit}>Edit</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Container component (handles logic, state, data fetching)
|
||||
function UserCardContainer({ userId }: { userId: string }) {
|
||||
const { data: user, isLoading } = useUser(userId);
|
||||
const { mutate: updateUser } = useUpdateUser();
|
||||
|
||||
if (isLoading) return <Skeleton />;
|
||||
if (!user) return <NotFound />;
|
||||
|
||||
return <UserCard {...user} onEdit={() => updateUser(user.id)} />;
|
||||
}
|
||||
```
|
||||
|
||||
**Compound Components Pattern**:
|
||||
```typescript
|
||||
// Flexible, composable API
|
||||
<Tabs defaultValue="profile">
|
||||
<TabsList>
|
||||
<TabsTrigger value="profile">Profile</TabsTrigger>
|
||||
<TabsTrigger value="settings">Settings</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent value="profile">
|
||||
<ProfileForm />
|
||||
</TabsContent>
|
||||
<TabsContent value="settings">
|
||||
<SettingsForm />
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
```
|
||||
|
||||
### Component Organization
|
||||
|
||||
```
|
||||
components/
|
||||
├── ui/ # Primitive components (buttons, inputs)
|
||||
│ ├── button.tsx
|
||||
│ ├── input.tsx
|
||||
│ └── card.tsx
|
||||
├── forms/ # Form components
|
||||
│ ├── login-form.tsx
|
||||
│ └── register-form.tsx
|
||||
├── features/ # Feature-specific components
|
||||
│ ├── user-profile/
|
||||
│ │ ├── profile-header.tsx
|
||||
│ │ ├── profile-stats.tsx
|
||||
│ │ └── index.ts
|
||||
│ └── dashboard/
|
||||
│ ├── dashboard-grid.tsx
|
||||
│ └── dashboard-card.tsx
|
||||
└── layouts/ # Layout components
|
||||
├── main-layout.tsx
|
||||
└── auth-layout.tsx
|
||||
```
|
||||
|
||||
## State Management
|
||||
|
||||
### Local State (useState)
|
||||
|
||||
Use for:
|
||||
- Component-specific UI state
|
||||
- Form inputs
|
||||
- Toggles, modals
|
||||
|
||||
```typescript
|
||||
function SearchBar() {
|
||||
const [query, setQuery] = useState('');
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<input
|
||||
value={query}
|
||||
onChange={(e) => setQuery(e.target.value)}
|
||||
/>
|
||||
{isOpen && <SearchResults query={query} />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Global State (Zustand)
|
||||
|
||||
Use for:
|
||||
- User authentication state
|
||||
- Theme preferences
|
||||
- Shopping cart
|
||||
- Cross-component shared state
|
||||
|
||||
```typescript
|
||||
import create from 'zustand';
|
||||
|
||||
interface UserStore {
|
||||
user: User | null;
|
||||
setUser: (user: User) => void;
|
||||
logout: () => void;
|
||||
}
|
||||
|
||||
export const useUserStore = create<UserStore>((set) => ({
|
||||
user: null,
|
||||
setUser: (user) => set({ user }),
|
||||
logout: () => set({ user: null }),
|
||||
}));
|
||||
|
||||
// Usage
|
||||
function Header() {
|
||||
const user = useUserStore((state) => state.user);
|
||||
const logout = useUserStore((state) => state.logout);
|
||||
|
||||
return <div>{user ? user.name : 'Guest'}</div>;
|
||||
}
|
||||
```
|
||||
|
||||
### Server State (React Query / TanStack Query)
|
||||
|
||||
Use for:
|
||||
- API data fetching
|
||||
- Caching API responses
|
||||
- Optimistic updates
|
||||
- Background refetching
|
||||
|
||||
```typescript
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
|
||||
// Fetch data
|
||||
function UserProfile({ userId }: { userId: string }) {
|
||||
const { data, isLoading, error } = useQuery({
|
||||
queryKey: ['user', userId],
|
||||
queryFn: () => fetchUser(userId),
|
||||
staleTime: 5 * 60 * 1000, // 5 minutes
|
||||
});
|
||||
|
||||
if (isLoading) return <Skeleton />;
|
||||
if (error) return <Error error={error} />;
|
||||
|
||||
return <div>{data.name}</div>;
|
||||
}
|
||||
|
||||
// Mutations with optimistic updates
|
||||
function useUpdateUser() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: (user: User) => api.updateUser(user),
|
||||
onMutate: async (newUser) => {
|
||||
// Cancel outgoing refetches
|
||||
await queryClient.cancelQueries({ queryKey: ['user', newUser.id] });
|
||||
|
||||
// Snapshot previous value
|
||||
const previous = queryClient.getQueryData(['user', newUser.id]);
|
||||
|
||||
// Optimistically update
|
||||
queryClient.setQueryData(['user', newUser.id], newUser);
|
||||
|
||||
return { previous };
|
||||
},
|
||||
onError: (err, newUser, context) => {
|
||||
// Rollback on error
|
||||
queryClient.setQueryData(['user', newUser.id], context?.previous);
|
||||
},
|
||||
onSettled: (newUser) => {
|
||||
// Refetch after mutation
|
||||
queryClient.invalidateQueries({ queryKey: ['user', newUser.id] });
|
||||
},
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
## Performance Optimization
|
||||
|
||||
### 1. Memoization
|
||||
|
||||
**useMemo** (expensive calculations):
|
||||
```typescript
|
||||
function ProductList({ products }: { products: Product[] }) {
|
||||
const sortedProducts = useMemo(
|
||||
() => products.sort((a, b) => b.price - a.price),
|
||||
[products]
|
||||
);
|
||||
|
||||
return <div>{sortedProducts.map(...)}</div>;
|
||||
}
|
||||
```
|
||||
|
||||
**useCallback** (prevent re-renders):
|
||||
```typescript
|
||||
function Parent() {
|
||||
const [count, setCount] = useState(0);
|
||||
|
||||
// ✅ Memoized - Child won't re-render unless count changes
|
||||
const handleClick = useCallback(() => {
|
||||
setCount(c => c + 1);
|
||||
}, []);
|
||||
|
||||
return <Child onClick={handleClick} />;
|
||||
}
|
||||
|
||||
const Child = memo(function Child({ onClick }: { onClick: () => void }) {
|
||||
console.log('Child rendered');
|
||||
return <button onClick={onClick}>Click</button>;
|
||||
});
|
||||
```
|
||||
|
||||
**React.memo** (prevent component re-renders):
|
||||
```typescript
|
||||
const ExpensiveComponent = memo(function ExpensiveComponent({ data }) {
|
||||
// Only re-renders if data changes
|
||||
return <div>{/* expensive rendering */}</div>;
|
||||
});
|
||||
```
|
||||
|
||||
### 2. Code Splitting
|
||||
|
||||
**Route-based splitting** (Next.js automatic):
|
||||
```typescript
|
||||
// app/dashboard/page.tsx - automatically code split
|
||||
export default function DashboardPage() {
|
||||
return <Dashboard />;
|
||||
}
|
||||
```
|
||||
|
||||
**Component-level splitting**:
|
||||
```typescript
|
||||
import dynamic from 'next/dynamic';
|
||||
|
||||
const HeavyChart = dynamic(() => import('@/components/heavy-chart'), {
|
||||
loading: () => <Skeleton />,
|
||||
ssr: false, // Don't render on server
|
||||
});
|
||||
|
||||
function Analytics() {
|
||||
return <HeavyChart data={chartData} />;
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Image Optimization
|
||||
|
||||
```typescript
|
||||
import Image from 'next/image';
|
||||
|
||||
// ✅ Optimized - Next.js Image component
|
||||
<Image
|
||||
src="/hero.jpg"
|
||||
alt="Hero image"
|
||||
width={800}
|
||||
height={600}
|
||||
priority // Load immediately for LCP
|
||||
placeholder="blur"
|
||||
blurDataURL="data:image/..."
|
||||
/>
|
||||
|
||||
// ❌ Not optimized
|
||||
<img src="/hero.jpg" alt="Hero" />
|
||||
```
|
||||
|
||||
### 4. Lazy Loading
|
||||
|
||||
```typescript
|
||||
import { lazy, Suspense } from 'react';
|
||||
|
||||
const Comments = lazy(() => import('./comments'));
|
||||
|
||||
function Post() {
|
||||
return (
|
||||
<div>
|
||||
<PostContent />
|
||||
<Suspense fallback={<CommentsSkeleton />}>
|
||||
<Comments postId={postId} />
|
||||
</Suspense>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### 5. Virtual Scrolling
|
||||
|
||||
```typescript
|
||||
import { useVirtualizer } from '@tanstack/react-virtual';
|
||||
|
||||
function VirtualList({ items }: { items: Item[] }) {
|
||||
const parentRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const virtualizer = useVirtualizer({
|
||||
count: items.length,
|
||||
getScrollElement: () => parentRef.current,
|
||||
estimateSize: () => 50,
|
||||
});
|
||||
|
||||
return (
|
||||
<div ref={parentRef} style={{ height: '400px', overflow: 'auto' }}>
|
||||
<div style={{ height: `${virtualizer.getTotalSize()}px` }}>
|
||||
{virtualizer.getVirtualItems().map((virtualRow) => (
|
||||
<div
|
||||
key={virtualRow.index}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: '100%',
|
||||
height: `${virtualRow.size}px`,
|
||||
transform: `translateY(${virtualRow.start}px)`,
|
||||
}}
|
||||
>
|
||||
{items[virtualRow.index].name}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Accessibility (a11y)
|
||||
|
||||
### Semantic HTML
|
||||
|
||||
```typescript
|
||||
// ✅ Semantic
|
||||
<nav>
|
||||
<ul>
|
||||
<li><a href="/home">Home</a></li>
|
||||
<li><a href="/about">About</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
// ❌ Non-semantic
|
||||
<div>
|
||||
<div>
|
||||
<div onClick={goHome}>Home</div>
|
||||
<div onClick={goAbout}>About</div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
### ARIA Attributes
|
||||
|
||||
```typescript
|
||||
<button
|
||||
aria-label="Close dialog"
|
||||
aria-expanded={isOpen}
|
||||
aria-controls="dialog-content"
|
||||
onClick={toggle}
|
||||
>
|
||||
<X aria-hidden="true" />
|
||||
</button>
|
||||
|
||||
<div
|
||||
id="dialog-content"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-labelledby="dialog-title"
|
||||
>
|
||||
<h2 id="dialog-title">Dialog Title</h2>
|
||||
{content}
|
||||
</div>
|
||||
```
|
||||
|
||||
### Keyboard Navigation
|
||||
|
||||
```typescript
|
||||
function Dropdown() {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [focusedIndex, setFocusedIndex] = useState(0);
|
||||
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
switch (e.key) {
|
||||
case 'ArrowDown':
|
||||
e.preventDefault();
|
||||
setFocusedIndex((i) => Math.min(i + 1, items.length - 1));
|
||||
break;
|
||||
case 'ArrowUp':
|
||||
e.preventDefault();
|
||||
setFocusedIndex((i) => Math.max(i - 1, 0));
|
||||
break;
|
||||
case 'Enter':
|
||||
selectItem(items[focusedIndex]);
|
||||
break;
|
||||
case 'Escape':
|
||||
setIsOpen(false);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div onKeyDown={handleKeyDown} role="combobox">
|
||||
{/* dropdown content */}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Focus Management
|
||||
|
||||
```typescript
|
||||
import { useRef, useEffect } from 'react';
|
||||
|
||||
function Modal({ isOpen, onClose }: ModalProps) {
|
||||
const closeButtonRef = useRef<HTMLButtonElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
// Focus close button when modal opens
|
||||
closeButtonRef.current?.focus();
|
||||
|
||||
// Trap focus within modal
|
||||
const handleTab = (e: KeyboardEvent) => {
|
||||
// Implement focus trap logic
|
||||
};
|
||||
|
||||
document.addEventListener('keydown', handleTab);
|
||||
return () => document.removeEventListener('keydown', handleTab);
|
||||
}
|
||||
}, [isOpen]);
|
||||
|
||||
if (!isOpen) return null;
|
||||
|
||||
return (
|
||||
<div role="dialog" aria-modal="true">
|
||||
<button ref={closeButtonRef} onClick={onClose}>
|
||||
Close
|
||||
</button>
|
||||
{content}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Form Patterns
|
||||
|
||||
### Controlled Forms with Validation
|
||||
|
||||
```typescript
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import * as z from 'zod';
|
||||
|
||||
const schema = z.object({
|
||||
email: z.string().email('Invalid email address'),
|
||||
password: z.string().min(8, 'Password must be at least 8 characters'),
|
||||
age: z.number().min(18, 'Must be 18 or older'),
|
||||
});
|
||||
|
||||
type FormData = z.infer<typeof schema>;
|
||||
|
||||
function RegistrationForm() {
|
||||
const { register, handleSubmit, formState: { errors, isSubmitting } } = useForm<FormData>({
|
||||
resolver: zodResolver(schema),
|
||||
});
|
||||
|
||||
const onSubmit = async (data: FormData) => {
|
||||
await api.register(data);
|
||||
};
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<div>
|
||||
<input {...register('email')} type="email" />
|
||||
{errors.email && <span>{errors.email.message}</span>}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<input {...register('password')} type="password" />
|
||||
{errors.password && <span>{errors.password.message}</span>}
|
||||
</div>
|
||||
|
||||
<button type="submit" disabled={isSubmitting}>
|
||||
{isSubmitting ? 'Submitting...' : 'Submit'}
|
||||
</button>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Form State Management
|
||||
|
||||
```typescript
|
||||
// Optimistic updates
|
||||
const { mutate } = useMutation({
|
||||
mutationFn: updateUser,
|
||||
onMutate: async (newData) => {
|
||||
// Cancel outgoing queries
|
||||
await queryClient.cancelQueries({ queryKey: ['user', userId] });
|
||||
|
||||
// Snapshot previous
|
||||
const previous = queryClient.getQueryData(['user', userId]);
|
||||
|
||||
// Optimistically update UI
|
||||
queryClient.setQueryData(['user', userId], newData);
|
||||
|
||||
return { previous };
|
||||
},
|
||||
onError: (err, newData, context) => {
|
||||
// Rollback on error
|
||||
queryClient.setQueryData(['user', userId], context?.previous);
|
||||
toast.error('Update failed');
|
||||
},
|
||||
onSuccess: () => {
|
||||
toast.success('Updated successfully');
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
### Error Boundaries
|
||||
|
||||
```typescript
|
||||
import { Component, ReactNode } from 'react';
|
||||
|
||||
interface Props {
|
||||
children: ReactNode;
|
||||
fallback?: ReactNode;
|
||||
}
|
||||
|
||||
interface State {
|
||||
hasError: boolean;
|
||||
error?: Error;
|
||||
}
|
||||
|
||||
class ErrorBoundary extends Component<Props, State> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
this.state = { hasError: false };
|
||||
}
|
||||
|
||||
static getDerivedStateFromError(error: Error): State {
|
||||
return { hasError: true, error };
|
||||
}
|
||||
|
||||
componentDidCatch(error: Error, errorInfo: any) {
|
||||
console.error('Error boundary caught:', error, errorInfo);
|
||||
// Log to error tracking service
|
||||
logErrorToService(error, errorInfo);
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.hasError) {
|
||||
return this.props.fallback || (
|
||||
<div>
|
||||
<h2>Something went wrong</h2>
|
||||
<button onClick={() => this.setState({ hasError: false })}>
|
||||
Try again
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
|
||||
// Usage
|
||||
<ErrorBoundary fallback={<ErrorFallback />}>
|
||||
<App />
|
||||
</ErrorBoundary>
|
||||
```
|
||||
|
||||
### Async Error Handling
|
||||
|
||||
```typescript
|
||||
function DataComponent() {
|
||||
const { data, error, isError, isLoading } = useQuery({
|
||||
queryKey: ['data'],
|
||||
queryFn: fetchData,
|
||||
retry: 3,
|
||||
retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 30000),
|
||||
});
|
||||
|
||||
if (isLoading) return <Skeleton />;
|
||||
if (isError) return <ErrorDisplay error={error} />;
|
||||
|
||||
return <DisplayData data={data} />;
|
||||
}
|
||||
```
|
||||
|
||||
## Responsive Design
|
||||
|
||||
### Mobile-First Approach
|
||||
|
||||
```typescript
|
||||
// Tailwind CSS (mobile-first)
|
||||
<div className="
|
||||
w-full /* Full width on mobile */
|
||||
md:w-1/2 /* Half width on tablets */
|
||||
lg:w-1/3 /* Third width on desktop */
|
||||
p-4 /* Padding 16px */
|
||||
md:p-6 /* Padding 24px on tablets+ */
|
||||
">
|
||||
Content
|
||||
</div>
|
||||
```
|
||||
|
||||
### Responsive Hooks
|
||||
|
||||
```typescript
|
||||
import { useMediaQuery } from '@/hooks/use-media-query';
|
||||
|
||||
function ResponsiveLayout() {
|
||||
const isMobile = useMediaQuery('(max-width: 768px)');
|
||||
const isTablet = useMediaQuery('(min-width: 769px) and (max-width: 1024px)');
|
||||
const isDesktop = useMediaQuery('(min-width: 1025px)');
|
||||
|
||||
if (isMobile) return <MobileLayout />;
|
||||
if (isTablet) return <TabletLayout />;
|
||||
return <DesktopLayout />;
|
||||
}
|
||||
```
|
||||
|
||||
## Data Fetching Strategies
|
||||
|
||||
### Server Components (Next.js 14+)
|
||||
|
||||
```typescript
|
||||
// app/users/page.tsx - Server Component
|
||||
async function UsersPage() {
|
||||
// Fetched on server
|
||||
const users = await db.user.findMany();
|
||||
|
||||
return <UserList users={users} />;
|
||||
}
|
||||
```
|
||||
|
||||
### Client Components with React Query
|
||||
|
||||
```typescript
|
||||
'use client';
|
||||
|
||||
function UserList() {
|
||||
const { data: users, isLoading } = useQuery({
|
||||
queryKey: ['users'],
|
||||
queryFn: fetchUsers,
|
||||
});
|
||||
|
||||
if (isLoading) return <UsersLoading />;
|
||||
|
||||
return <div>{users.map(user => <UserCard key={user.id} {...user} />)}</div>;
|
||||
}
|
||||
```
|
||||
|
||||
### Parallel Data Fetching
|
||||
|
||||
```typescript
|
||||
function Dashboard() {
|
||||
const { data: user } = useQuery({ queryKey: ['user'], queryFn: fetchUser });
|
||||
const { data: stats } = useQuery({ queryKey: ['stats'], queryFn: fetchStats });
|
||||
const { data: posts } = useQuery({ queryKey: ['posts'], queryFn: fetchPosts });
|
||||
|
||||
// All three queries run in parallel
|
||||
return <div>...</div>;
|
||||
}
|
||||
```
|
||||
|
||||
### Dependent Queries
|
||||
|
||||
```typescript
|
||||
function UserPosts({ userId }: { userId: string }) {
|
||||
const { data: user } = useQuery({
|
||||
queryKey: ['user', userId],
|
||||
queryFn: () => fetchUser(userId),
|
||||
});
|
||||
|
||||
const { data: posts } = useQuery({
|
||||
queryKey: ['posts', user?.id],
|
||||
queryFn: () => fetchUserPosts(user!.id),
|
||||
enabled: !!user, // Only fetch after user is loaded
|
||||
});
|
||||
|
||||
return <div>...</div>;
|
||||
}
|
||||
```
|
||||
|
||||
## TypeScript Patterns
|
||||
|
||||
### Prop Types
|
||||
|
||||
```typescript
|
||||
// Basic props
|
||||
interface ButtonProps {
|
||||
children: ReactNode;
|
||||
onClick: () => void;
|
||||
variant?: 'primary' | 'secondary';
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
// Props with generic
|
||||
interface ListProps<T> {
|
||||
items: T[];
|
||||
renderItem: (item: T) => ReactNode;
|
||||
keyExtractor: (item: T) => string;
|
||||
}
|
||||
|
||||
// Props extending HTML attributes
|
||||
interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
|
||||
label: string;
|
||||
error?: string;
|
||||
}
|
||||
```
|
||||
|
||||
### Type-Safe API Responses
|
||||
|
||||
```typescript
|
||||
// API response types
|
||||
interface ApiResponse<T> {
|
||||
data: T;
|
||||
error?: never;
|
||||
}
|
||||
|
||||
interface ApiError {
|
||||
data?: never;
|
||||
error: {
|
||||
code: string;
|
||||
message: string;
|
||||
};
|
||||
}
|
||||
|
||||
type ApiResult<T> = ApiResponse<T> | ApiError;
|
||||
|
||||
// Usage
|
||||
async function fetchUser(id: string): Promise<ApiResult<User>> {
|
||||
const response = await fetch(`/api/users/${id}`);
|
||||
return response.json();
|
||||
}
|
||||
```
|
||||
|
||||
## Testing Patterns
|
||||
|
||||
### Component Testing
|
||||
|
||||
```typescript
|
||||
import { render, screen, fireEvent } from '@testing-library/react';
|
||||
import { LoginForm } from './login-form';
|
||||
|
||||
test('submits form with email and password', async () => {
|
||||
const onSubmit = jest.fn();
|
||||
|
||||
render(<LoginForm onSubmit={onSubmit} />);
|
||||
|
||||
fireEvent.change(screen.getByLabelText('Email'), {
|
||||
target: { value: 'test@example.com' },
|
||||
});
|
||||
|
||||
fireEvent.change(screen.getByLabelText('Password'), {
|
||||
target: { value: 'password123' },
|
||||
});
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Login' }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(onSubmit).toHaveBeenCalledWith({
|
||||
email: 'test@example.com',
|
||||
password: 'password123',
|
||||
});
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### Mock API Calls
|
||||
|
||||
```typescript
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
import { rest } from 'msw';
|
||||
import { setupServer } from 'msw/node';
|
||||
|
||||
const server = setupServer(
|
||||
rest.get('/api/users/:id', (req, res, ctx) => {
|
||||
return res(ctx.json({
|
||||
id: req.params.id,
|
||||
name: 'Test User',
|
||||
email: 'test@example.com',
|
||||
}));
|
||||
})
|
||||
);
|
||||
|
||||
beforeAll(() => server.listen());
|
||||
afterEach(() => server.resetHandlers());
|
||||
afterAll(() => server.close());
|
||||
|
||||
test('displays user data', async () => {
|
||||
const queryClient = new QueryClient();
|
||||
|
||||
render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<UserProfile userId="123" />
|
||||
</QueryClientProvider>
|
||||
);
|
||||
|
||||
expect(await screen.findByText('Test User')).toBeInTheDocument();
|
||||
});
|
||||
```
|
||||
|
||||
## Build Optimization
|
||||
|
||||
### Bundle Analysis
|
||||
|
||||
```bash
|
||||
# Next.js bundle analyzer
|
||||
npm install @next/bundle-analyzer
|
||||
|
||||
# next.config.js
|
||||
const withBundleAnalyzer = require('@next/bundle-analyzer')({
|
||||
enabled: process.env.ANALYZE === 'true',
|
||||
});
|
||||
|
||||
module.exports = withBundleAnalyzer({
|
||||
// config
|
||||
});
|
||||
|
||||
# Run analysis
|
||||
ANALYZE=true npm run build
|
||||
```
|
||||
|
||||
### Tree Shaking
|
||||
|
||||
```typescript
|
||||
// ✅ Named imports (tree-shakeable)
|
||||
import { Button } from '@/components/ui/button';
|
||||
|
||||
// ❌ Namespace import (includes everything)
|
||||
import * as UI from '@/components/ui';
|
||||
```
|
||||
|
||||
### Dynamic Imports
|
||||
|
||||
```typescript
|
||||
// Import only when needed
|
||||
async function handleExport() {
|
||||
const { exportToPDF } = await import('@/lib/pdf-export');
|
||||
await exportToPDF(data);
|
||||
}
|
||||
```
|
||||
|
||||
## Common Frontend Mistakes to Avoid
|
||||
|
||||
1. **Prop drilling**: Use Context or state management library instead
|
||||
2. **Unnecessary re-renders**: Use memo, useMemo, useCallback appropriately
|
||||
3. **Missing loading states**: Always show loading indicators
|
||||
4. **No error boundaries**: Catch errors before they break the app
|
||||
5. **Inline functions in JSX**: Causes re-renders, use useCallback
|
||||
6. **Large bundle sizes**: Code split and lazy load
|
||||
7. **Missing alt text**: All images need descriptive alt text
|
||||
8. **Inaccessible forms**: Use proper labels and ARIA
|
||||
9. **Console.log in production**: Remove or use proper logging
|
||||
10. **Mixing server and client code**: Know Next.js boundaries
|
||||
|
||||
## Performance Metrics (Core Web Vitals)
|
||||
|
||||
### LCP (Largest Contentful Paint)
|
||||
**Target**: < 2.5 seconds
|
||||
|
||||
**Optimize**:
|
||||
- Preload critical images
|
||||
- Use Next.js Image component
|
||||
- Minimize render-blocking resources
|
||||
- Use CDN for assets
|
||||
|
||||
### FID (First Input Delay)
|
||||
**Target**: < 100 milliseconds
|
||||
|
||||
**Optimize**:
|
||||
- Minimize JavaScript execution
|
||||
- Code split large bundles
|
||||
- Use web workers for heavy computation
|
||||
- Defer non-critical JavaScript
|
||||
|
||||
### CLS (Cumulative Layout Shift)
|
||||
**Target**: < 0.1
|
||||
|
||||
**Optimize**:
|
||||
- Set explicit width/height on images
|
||||
- Reserve space for ads/embeds
|
||||
- Avoid inserting content above existing content
|
||||
- Use CSS transforms instead of layout properties
|
||||
|
||||
## When to Use This Skill
|
||||
|
||||
Use this skill when:
|
||||
- Building React or Next.js components
|
||||
- Implementing frontend features
|
||||
- Optimizing frontend performance
|
||||
- Debugging rendering issues
|
||||
- Setting up state management
|
||||
- Implementing forms
|
||||
- Ensuring accessibility
|
||||
- Working with responsive design
|
||||
- Fetching and caching data
|
||||
- Testing frontend code
|
||||
|
||||
---
|
||||
|
||||
**Remember**: Modern frontend development is about creating fast, accessible, and delightful user experiences. Follow these patterns to build UIs that users love.
|
||||
883
skills/project-planning/SKILL.md
Normal file
883
skills/project-planning/SKILL.md
Normal file
@@ -0,0 +1,883 @@
|
||||
---
|
||||
name: project-planning
|
||||
description: Project planning methodologies including work breakdown structure, task estimation, dependency management, risk assessment, sprint planning, and stakeholder communication. Use when breaking down projects, estimating work, planning sprints, or managing dependencies.
|
||||
---
|
||||
|
||||
# Project Planning
|
||||
|
||||
This skill provides comprehensive guidance for planning and managing software development projects effectively.
|
||||
|
||||
## Work Breakdown Structure (WBS)
|
||||
|
||||
### Breaking Down Large Projects
|
||||
|
||||
```markdown
|
||||
Project: E-commerce Platform
|
||||
├── 1. User Management
|
||||
│ ├── 1.1 Authentication
|
||||
│ │ ├── 1.1.1 Email/Password Login
|
||||
│ │ ├── 1.1.2 Social Login (Google, Facebook)
|
||||
│ │ └── 1.1.3 Password Reset
|
||||
│ ├── 1.2 User Profiles
|
||||
│ │ ├── 1.2.1 Profile Creation
|
||||
│ │ ├── 1.2.2 Profile Editing
|
||||
│ │ └── 1.2.3 Avatar Upload
|
||||
│ └── 1.3 Role Management
|
||||
│ ├── 1.3.1 Admin Role
|
||||
│ ├── 1.3.2 Customer Role
|
||||
│ └── 1.3.3 Vendor Role
|
||||
├── 2. Product Catalog
|
||||
│ ├── 2.1 Product Listings
|
||||
│ ├── 2.2 Product Details
|
||||
│ ├── 2.3 Product Search
|
||||
│ └── 2.4 Product Categories
|
||||
├── 3. Shopping Cart
|
||||
│ ├── 3.1 Add to Cart
|
||||
│ ├── 3.2 Update Quantities
|
||||
│ ├── 3.3 Remove Items
|
||||
│ └── 3.4 Cart Persistence
|
||||
├── 4. Checkout
|
||||
│ ├── 4.1 Shipping Address
|
||||
│ ├── 4.2 Payment Processing
|
||||
│ ├── 4.3 Order Confirmation
|
||||
│ └── 4.4 Email Notifications
|
||||
└── 5. Order Management
|
||||
├── 5.1 Order History
|
||||
├── 5.2 Order Tracking
|
||||
└── 5.3 Order Cancellation
|
||||
```
|
||||
|
||||
### WBS Best Practices
|
||||
|
||||
**1. Start with deliverables, not activities**
|
||||
```markdown
|
||||
❌ Wrong (activities):
|
||||
- Write code
|
||||
- Test features
|
||||
- Deploy
|
||||
|
||||
✅ Right (deliverables):
|
||||
- User Authentication System
|
||||
- Product Search Feature
|
||||
- Payment Integration
|
||||
```
|
||||
|
||||
**2. Use the 8/80 rule**
|
||||
- No task should take less than 8 hours (too granular)
|
||||
- No task should take more than 80 hours (too large)
|
||||
- Sweet spot: 1-5 days per task
|
||||
|
||||
**3. Break down until you can estimate**
|
||||
```markdown
|
||||
❌ Too vague:
|
||||
- Build API (? days)
|
||||
|
||||
✅ Specific:
|
||||
- Design API endpoints (1 day)
|
||||
- Implement authentication (2 days)
|
||||
- Create CRUD operations (3 days)
|
||||
- Write API documentation (1 day)
|
||||
- Add rate limiting (1 day)
|
||||
Total: 8 days
|
||||
```
|
||||
|
||||
## Task Estimation
|
||||
|
||||
### Story Points
|
||||
|
||||
**Fibonacci Scale**: 1, 2, 3, 5, 8, 13, 21
|
||||
|
||||
```markdown
|
||||
1 point - Trivial
|
||||
- Update documentation
|
||||
- Fix typo
|
||||
- Change button color
|
||||
|
||||
2 points - Simple
|
||||
- Add validation to form field
|
||||
- Create simple API endpoint
|
||||
- Write unit tests for existing function
|
||||
|
||||
3 points - Moderate
|
||||
- Implement login form
|
||||
- Add pagination to list
|
||||
- Create database migration
|
||||
|
||||
5 points - Complex
|
||||
- Build user profile page
|
||||
- Implement search functionality
|
||||
- Add email notifications
|
||||
|
||||
8 points - Very Complex
|
||||
- Build payment integration
|
||||
- Implement complex reporting
|
||||
- Create admin dashboard
|
||||
|
||||
13 points - Epic
|
||||
- Build entire authentication system
|
||||
- Create complete checkout flow
|
||||
(Should be broken down further)
|
||||
```
|
||||
|
||||
### T-Shirt Sizing
|
||||
|
||||
**Quick estimation for early planning**:
|
||||
|
||||
```markdown
|
||||
XS (1-2 days)
|
||||
- Bug fixes
|
||||
- Minor UI updates
|
||||
- Documentation updates
|
||||
|
||||
S (3-5 days)
|
||||
- Small features
|
||||
- Simple integrations
|
||||
- Basic CRUD operations
|
||||
|
||||
M (1-2 weeks)
|
||||
- Medium features
|
||||
- Standard integrations
|
||||
- Multiple related stories
|
||||
|
||||
L (2-4 weeks)
|
||||
- Large features
|
||||
- Complex integrations
|
||||
- Multiple epics
|
||||
|
||||
XL (1-2 months)
|
||||
- Major features
|
||||
- System redesigns
|
||||
(Should be broken down into smaller pieces)
|
||||
```
|
||||
|
||||
### Planning Poker
|
||||
|
||||
**Collaborative estimation process**:
|
||||
|
||||
```markdown
|
||||
1. Product Owner presents user story
|
||||
2. Team asks clarifying questions
|
||||
3. Each member privately selects estimate
|
||||
4. All reveal simultaneously
|
||||
5. Discuss differences (especially highest/lowest)
|
||||
6. Re-estimate if needed
|
||||
7. Reach consensus
|
||||
|
||||
Example Session:
|
||||
Story: "As a user, I want to reset my password"
|
||||
|
||||
Round 1 Estimates: 2, 3, 3, 8, 3
|
||||
Discussion: Why 8?
|
||||
- "What about email templates?"
|
||||
- "What if SMTP fails?"
|
||||
- "Need password strength validation"
|
||||
|
||||
Round 2 Estimates: 5, 5, 5, 5, 5
|
||||
Consensus: 5 points
|
||||
```
|
||||
|
||||
### Estimation Accuracy
|
||||
|
||||
**Cone of Uncertainty**:
|
||||
```
|
||||
Project Start: ±100% accuracy
|
||||
Requirements: ±50% accuracy
|
||||
Design: ±25% accuracy
|
||||
Development: ±10% accuracy
|
||||
Testing: ±5% accuracy
|
||||
```
|
||||
|
||||
**Build in buffer**:
|
||||
```markdown
|
||||
Optimistic: 3 days
|
||||
Realistic: 5 days
|
||||
Pessimistic: 8 days
|
||||
|
||||
Formula: (Optimistic + 4×Realistic + Pessimistic) ÷ 6
|
||||
Buffer: (3 + 4×5 + 8) ÷ 6 = 5.2 days
|
||||
|
||||
Use 6 days for planning
|
||||
```
|
||||
|
||||
## Dependency Identification
|
||||
|
||||
### Types of Dependencies
|
||||
|
||||
```markdown
|
||||
1. Finish-to-Start (FS) - Most common
|
||||
Task B cannot start until Task A finishes
|
||||
Example: Design → Development
|
||||
|
||||
2. Start-to-Start (SS)
|
||||
Task B cannot start until Task A starts
|
||||
Example: Development → Code Review (parallel)
|
||||
|
||||
3. Finish-to-Finish (FF)
|
||||
Task B cannot finish until Task A finishes
|
||||
Example: Development → Testing (testing continues)
|
||||
|
||||
4. Start-to-Finish (SF) - Rare
|
||||
Task B cannot finish until Task A starts
|
||||
Example: Old system → New system (overlap)
|
||||
```
|
||||
|
||||
### Dependency Mapping
|
||||
|
||||
```markdown
|
||||
Task Dependencies:
|
||||
|
||||
1. Database Schema Design
|
||||
└─► 2. API Development (FS)
|
||||
├─► 3. Frontend Development (FS)
|
||||
└─► 4. Integration Tests (SS)
|
||||
└─► 5. End-to-End Tests (FS)
|
||||
|
||||
2. API Development
|
||||
└─► 6. API Documentation (SS)
|
||||
|
||||
3. Frontend Development
|
||||
└─► 7. UI/UX Review (FF)
|
||||
|
||||
Critical Path: 1 → 2 → 3 → 5
|
||||
(Longest path through dependencies)
|
||||
```
|
||||
|
||||
### Managing Dependencies
|
||||
|
||||
```typescript
|
||||
// Dependency tracking in code
|
||||
interface Task {
|
||||
id: string;
|
||||
name: string;
|
||||
dependencies: string[]; // IDs of tasks that must complete first
|
||||
status: 'pending' | 'in-progress' | 'completed' | 'blocked';
|
||||
}
|
||||
|
||||
const tasks: Task[] = [
|
||||
{
|
||||
id: 'db-schema',
|
||||
name: 'Design database schema',
|
||||
dependencies: [],
|
||||
status: 'completed',
|
||||
},
|
||||
{
|
||||
id: 'api-dev',
|
||||
name: 'Develop API',
|
||||
dependencies: ['db-schema'],
|
||||
status: 'in-progress',
|
||||
},
|
||||
{
|
||||
id: 'frontend-dev',
|
||||
name: 'Develop frontend',
|
||||
dependencies: ['api-dev'],
|
||||
status: 'blocked', // Waiting for API
|
||||
},
|
||||
];
|
||||
|
||||
function canStartTask(taskId: string): boolean {
|
||||
const task = tasks.find(t => t.id === taskId);
|
||||
if (!task) return false;
|
||||
|
||||
// Check if all dependencies are completed
|
||||
return task.dependencies.every(depId => {
|
||||
const dep = tasks.find(t => t.id === depId);
|
||||
return dep?.status === 'completed';
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
## Critical Path Analysis
|
||||
|
||||
### Finding the Critical Path
|
||||
|
||||
```markdown
|
||||
Project: Launch Marketing Website
|
||||
|
||||
Tasks:
|
||||
A. Design mockups (3 days)
|
||||
B. Develop frontend (5 days) - depends on A
|
||||
C. Write content (4 days) - independent
|
||||
D. Set up hosting (1 day) - independent
|
||||
E. Deploy website (1 day) - depends on B, C, D
|
||||
F. Test website (2 days) - depends on E
|
||||
|
||||
Path 1: A → B → E → F = 3 + 5 + 1 + 2 = 11 days
|
||||
Path 2: C → E → F = 4 + 1 + 2 = 7 days
|
||||
Path 3: D → E → F = 1 + 1 + 2 = 4 days
|
||||
|
||||
Critical Path: A → B → E → F (11 days)
|
||||
(Any delay in these tasks delays the entire project)
|
||||
```
|
||||
|
||||
### Managing Critical Path
|
||||
|
||||
```markdown
|
||||
Strategies:
|
||||
|
||||
1. Fast-track critical tasks
|
||||
- Assign best developers
|
||||
- Remove blockers immediately
|
||||
- Daily status checks
|
||||
|
||||
2. Crash critical tasks (add resources)
|
||||
- Pair programming
|
||||
- Additional team members
|
||||
- Overtime (carefully)
|
||||
|
||||
3. Parallelize where possible
|
||||
- Content writing during development
|
||||
- Documentation during testing
|
||||
|
||||
4. Monitor closely
|
||||
- Daily updates on critical path
|
||||
- Early warning of delays
|
||||
- Quick decision-making
|
||||
```
|
||||
|
||||
## Risk Assessment and Mitigation
|
||||
|
||||
### Risk Matrix
|
||||
|
||||
```markdown
|
||||
Impact vs Probability:
|
||||
|
||||
Low Medium High
|
||||
High Monitor Mitigate Immediate Action
|
||||
Medium Accept Monitor Mitigate
|
||||
Low Accept Accept Monitor
|
||||
|
||||
Example Risks:
|
||||
|
||||
1. API Integration Delays (High Impact, Medium Probability)
|
||||
→ Mitigate: Start integration early, have backup plan
|
||||
|
||||
2. Key Developer Leaves (High Impact, Low Probability)
|
||||
→ Monitor: Document knowledge, cross-train team
|
||||
|
||||
3. Library Deprecated (Medium Impact, Low Probability)
|
||||
→ Accept: Will address if it happens
|
||||
```
|
||||
|
||||
### Risk Register
|
||||
|
||||
```markdown
|
||||
| ID | Risk | Impact | Prob | Status | Mitigation |
|
||||
|----|------|--------|------|--------|------------|
|
||||
| R1 | Third-party API unreliable | High | Medium | Active | Build fallback, cache responses |
|
||||
| R2 | Database performance issues | High | Low | Monitor | Load testing, optimization plan |
|
||||
| R3 | Requirements change | Medium | High | Active | Weekly stakeholder sync, flexible architecture |
|
||||
| R4 | Security vulnerability | High | Low | Monitor | Security audits, dependency scanning |
|
||||
| R5 | Team member unavailable | Medium | Medium | Active | Documentation, knowledge sharing |
|
||||
```
|
||||
|
||||
### Risk Mitigation Strategies
|
||||
|
||||
```markdown
|
||||
1. Avoidance - Eliminate risk
|
||||
Risk: Untested technology
|
||||
Action: Use proven technology stack
|
||||
|
||||
2. Reduction - Decrease likelihood/impact
|
||||
Risk: Integration failures
|
||||
Action: Early integration testing, CI/CD
|
||||
|
||||
3. Transfer - Share risk
|
||||
Risk: Infrastructure failure
|
||||
Action: Use cloud provider with SLA
|
||||
|
||||
4. Acceptance - Accept risk
|
||||
Risk: Minor UI inconsistencies
|
||||
Action: Document and fix in future release
|
||||
```
|
||||
|
||||
## Milestone Planning
|
||||
|
||||
### Setting Milestones
|
||||
|
||||
```markdown
|
||||
Project Timeline: 12 weeks
|
||||
|
||||
Week 2: M1 - Requirements Complete
|
||||
- All user stories defined
|
||||
- Mockups approved
|
||||
- Technical design ready
|
||||
✓ Milestone met when: PRD signed off
|
||||
|
||||
Week 4: M2 - Foundation Complete
|
||||
- Database schema implemented
|
||||
- Authentication working
|
||||
- Basic API endpoints created
|
||||
✓ Milestone met when: Users can log in
|
||||
|
||||
Week 7: M3 - Core Features Complete
|
||||
- All CRUD operations working
|
||||
- Main user flows implemented
|
||||
- Integration tests passing
|
||||
✓ Milestone met when: Alpha testing can begin
|
||||
|
||||
Week 10: M4 - Feature Complete
|
||||
- All features implemented
|
||||
- Bug fixes complete
|
||||
- Documentation written
|
||||
✓ Milestone met when: Beta testing ready
|
||||
|
||||
Week 12: M5 - Launch
|
||||
- Production deployment
|
||||
- Monitoring in place
|
||||
- Support processes ready
|
||||
✓ Milestone met when: Live to users
|
||||
```
|
||||
|
||||
## Sprint Planning
|
||||
|
||||
### Sprint Structure (2-week sprint)
|
||||
|
||||
```markdown
|
||||
Day 1 - Monday: Sprint Planning
|
||||
- Review backlog
|
||||
- Estimate stories
|
||||
- Commit to sprint goal
|
||||
|
||||
Days 2-9: Development
|
||||
- Daily standups
|
||||
- Development work
|
||||
- Code reviews
|
||||
- Testing
|
||||
|
||||
Day 10 - Friday Week 2: Sprint Review & Retrospective
|
||||
- Demo completed work
|
||||
- Discuss what went well/poorly
|
||||
- Plan improvements
|
||||
```
|
||||
|
||||
### Sprint Planning Meeting
|
||||
|
||||
```markdown
|
||||
Agenda (2 hours):
|
||||
|
||||
Part 1: Sprint Goal (30 min)
|
||||
- Review product roadmap
|
||||
- Define sprint goal
|
||||
- Identify high-priority items
|
||||
|
||||
Example Sprint Goal:
|
||||
"Enable users to browse and search products"
|
||||
|
||||
Part 2: Story Selection (60 min)
|
||||
- Review top backlog items
|
||||
- Estimate stories
|
||||
- Check capacity
|
||||
- Commit to stories
|
||||
|
||||
Team Capacity:
|
||||
- 5 developers × 8 days × 6 hours = 240 hours
|
||||
- Velocity: 40 story points per sprint
|
||||
- Buffer: 20% for bugs/meetings = 32 points
|
||||
|
||||
Selected Stories:
|
||||
- Product list page (5 pts)
|
||||
- Product search (8 pts)
|
||||
- Product filters (8 pts)
|
||||
- Product pagination (3 pts)
|
||||
- Product sort (3 pts)
|
||||
- Bug fixes (5 pts)
|
||||
Total: 32 points
|
||||
|
||||
Part 3: Task Breakdown (30 min)
|
||||
- Break stories into tasks
|
||||
- Identify blockers
|
||||
- Assign initial tasks
|
||||
```
|
||||
|
||||
## Capacity Planning
|
||||
|
||||
### Calculating Team Capacity
|
||||
|
||||
```markdown
|
||||
Team: 5 Developers
|
||||
Sprint: 2 weeks (10 working days)
|
||||
|
||||
Available Hours:
|
||||
5 developers × 10 days × 8 hours = 400 hours
|
||||
|
||||
Subtract Non-Dev Time:
|
||||
- Meetings: 2 hours/day × 10 days × 5 people = 100 hours
|
||||
- Code reviews: 1 hour/day × 10 days × 5 people = 50 hours
|
||||
- Planning/retro: 4 hours × 5 people = 20 hours
|
||||
|
||||
Actual Development Time:
|
||||
400 - 100 - 50 - 20 = 230 hours
|
||||
|
||||
Story Points:
|
||||
If 1 point ≈ 6 hours
|
||||
Capacity: 230 ÷ 6 ≈ 38 points
|
||||
|
||||
Add 20% buffer: 30 points safe commitment
|
||||
```
|
||||
|
||||
### Handling Vacation and Absences
|
||||
|
||||
```markdown
|
||||
Team Capacity with Absences:
|
||||
|
||||
Regular Capacity: 40 points
|
||||
|
||||
Developer A: Out entire sprint (-8 points)
|
||||
Developer B: Out 3 days (-5 points)
|
||||
Holiday: 1 day for everyone (-8 points)
|
||||
|
||||
Adjusted Capacity:
|
||||
40 - 8 - 5 - 8 = 19 points
|
||||
|
||||
Plan accordingly:
|
||||
- Smaller sprint goal
|
||||
- Fewer stories
|
||||
- Focus on high priority
|
||||
- Avoid risky work
|
||||
```
|
||||
|
||||
## Burndown Charts
|
||||
|
||||
### Creating Burndown Charts
|
||||
|
||||
```markdown
|
||||
Sprint Burndown:
|
||||
|
||||
Day | Remaining Points | Ideal Burn
|
||||
----|------------------|------------
|
||||
0 | 40 | 40
|
||||
1 | 38 | 36
|
||||
2 | 35 | 32
|
||||
3 | 32 | 28
|
||||
4 | 28 | 24
|
||||
5 | 28 | 20 ← Weekend
|
||||
6 | 28 | 16 ← Weekend
|
||||
7 | 25 | 12
|
||||
8 | 20 | 8
|
||||
9 | 12 | 4
|
||||
10 | 0 | 0
|
||||
|
||||
Ideal line: Straight from start to finish
|
||||
Actual line: Based on completed work
|
||||
|
||||
Analysis:
|
||||
- Days 3-6: Slow progress (blocker?)
|
||||
- Day 7: Back on track
|
||||
- Day 9: Ahead of schedule
|
||||
```
|
||||
|
||||
### Interpreting Burndown Trends
|
||||
|
||||
```markdown
|
||||
Scenarios:
|
||||
|
||||
1. Line below ideal
|
||||
→ Ahead of schedule
|
||||
→ May have underestimated
|
||||
→ Consider pulling in more work
|
||||
|
||||
2. Line above ideal
|
||||
→ Behind schedule
|
||||
→ May have overcommitted
|
||||
→ Identify blockers
|
||||
→ Consider removing stories
|
||||
|
||||
3. Flat line
|
||||
→ No progress
|
||||
→ Blocker or team unavailable
|
||||
→ Immediate intervention needed
|
||||
|
||||
4. Increasing line
|
||||
→ Scope creep
|
||||
→ Stories added mid-sprint
|
||||
→ Review sprint boundaries
|
||||
```
|
||||
|
||||
## Velocity Tracking
|
||||
|
||||
### Measuring Velocity
|
||||
|
||||
```markdown
|
||||
Historical Velocity:
|
||||
|
||||
Sprint 1: 28 points completed
|
||||
Sprint 2: 32 points completed
|
||||
Sprint 3: 30 points completed
|
||||
Sprint 4: 35 points completed
|
||||
Sprint 5: 33 points completed
|
||||
|
||||
Average Velocity: (28+32+30+35+33) ÷ 5 = 31.6 points
|
||||
|
||||
Use for Planning:
|
||||
- Conservative: 28 points (lowest recent)
|
||||
- Realistic: 32 points (average)
|
||||
- Optimistic: 35 points (highest recent)
|
||||
|
||||
Recommend: Use 32 points for next sprint
|
||||
```
|
||||
|
||||
### Velocity Trends
|
||||
|
||||
```markdown
|
||||
Improving Velocity:
|
||||
Sprint 1: 20 → Sprint 2: 25 → Sprint 3: 30
|
||||
- Team learning
|
||||
- Process improvements
|
||||
- Good trend
|
||||
|
||||
Declining Velocity:
|
||||
Sprint 1: 35 → Sprint 2: 30 → Sprint 3: 25
|
||||
- Technical debt accumulating
|
||||
- Team burnout
|
||||
- Need intervention
|
||||
|
||||
Stable Velocity:
|
||||
Sprint 1: 30 → Sprint 2: 31 → Sprint 3: 29
|
||||
- Sustainable pace
|
||||
- Predictable
|
||||
- Ideal state
|
||||
```
|
||||
|
||||
## Agile Ceremonies
|
||||
|
||||
### Daily Standup (15 minutes)
|
||||
|
||||
```markdown
|
||||
Format: Each person answers:
|
||||
1. What did I complete yesterday?
|
||||
2. What will I work on today?
|
||||
3. What blockers do I have?
|
||||
|
||||
Example:
|
||||
"Yesterday I completed the login form.
|
||||
Today I'll start on the password reset flow.
|
||||
I'm blocked on the email template approval."
|
||||
|
||||
Anti-patterns:
|
||||
❌ Status reports to manager
|
||||
❌ Problem-solving discussions
|
||||
❌ More than 15 minutes
|
||||
|
||||
Best practices:
|
||||
✓ Same time, same place
|
||||
✓ Everyone participates
|
||||
✓ Park detailed discussions
|
||||
✓ Update task board
|
||||
```
|
||||
|
||||
### Sprint Review (1 hour)
|
||||
|
||||
```markdown
|
||||
Agenda:
|
||||
1. Demo completed work (40 min)
|
||||
- Show working software
|
||||
- Get stakeholder feedback
|
||||
- Note requested changes
|
||||
|
||||
2. Review sprint metrics (10 min)
|
||||
- Velocity
|
||||
- Completed vs planned
|
||||
- Quality metrics
|
||||
|
||||
3. Update product backlog (10 min)
|
||||
- Adjust priorities
|
||||
- Add new items
|
||||
- Remove obsolete items
|
||||
|
||||
Tips:
|
||||
- Focus on working software
|
||||
- No PowerPoint presentations
|
||||
- Encourage feedback
|
||||
- Keep it informal
|
||||
```
|
||||
|
||||
### Sprint Retrospective (1 hour)
|
||||
|
||||
```markdown
|
||||
Format: What went well / What to improve / Action items
|
||||
|
||||
Example:
|
||||
|
||||
What Went Well:
|
||||
✓ Completed all planned stories
|
||||
✓ Good collaboration on complex feature
|
||||
✓ Improved code review process
|
||||
|
||||
What to Improve:
|
||||
⚠ Too many meetings interrupted flow
|
||||
⚠ Test environment was unstable
|
||||
⚠ Requirements unclear on story X
|
||||
|
||||
Action Items:
|
||||
1. Block "focus time" 2-4pm daily (Owner: Scrum Master)
|
||||
2. Fix test environment stability (Owner: DevOps)
|
||||
3. Refine stories with PO before sprint (Owner: Team Lead)
|
||||
|
||||
Follow-up:
|
||||
- Review action items at next retro
|
||||
- Track completion
|
||||
- Celebrate improvements
|
||||
```
|
||||
|
||||
## Stakeholder Communication
|
||||
|
||||
### Status Reports
|
||||
|
||||
```markdown
|
||||
Weekly Status Report - Week of Oct 16, 2025
|
||||
|
||||
Sprint Progress:
|
||||
- Completed: 18/32 points (56%)
|
||||
- On Track: Yes
|
||||
- Sprint Goal: Enable product browsing
|
||||
|
||||
Completed This Week:
|
||||
✓ Product list page with pagination
|
||||
✓ Basic search functionality
|
||||
✓ Product filters (category, price)
|
||||
|
||||
In Progress:
|
||||
• Advanced search with autocomplete (90% done)
|
||||
• Product sort options (started today)
|
||||
|
||||
Upcoming Next Week:
|
||||
○ Complete remaining search features
|
||||
○ Begin product detail page
|
||||
○ Integration testing
|
||||
|
||||
Blockers/Risks:
|
||||
⚠ Designer out sick - UI reviews delayed 1 day
|
||||
⚠ Third-party API slow - investigating alternatives
|
||||
|
||||
Metrics:
|
||||
- Velocity: 32 points/sprint (stable)
|
||||
- Bug count: 3 (all low priority)
|
||||
- Test coverage: 85%
|
||||
|
||||
Next Milestone:
|
||||
M3 - Core Features (Week 7) - On track
|
||||
```
|
||||
|
||||
### Stakeholder Matrix
|
||||
|
||||
```markdown
|
||||
| Stakeholder | Role | Interest | Influence | Communication |
|
||||
|-------------|------|----------|-----------|---------------|
|
||||
| CEO | Sponsor | High | High | Monthly exec summary |
|
||||
| Product Manager | Owner | High | High | Daily collaboration |
|
||||
| Engineering Manager | Lead | High | High | Daily standup |
|
||||
| Marketing Director | User | Medium | Medium | Weekly demo |
|
||||
| Customer Support | User | Medium | Low | Sprint review |
|
||||
| End Users | Consumer | High | Low | Beta feedback |
|
||||
```
|
||||
|
||||
## Project Tracking Tools
|
||||
|
||||
### Issue/Task Management
|
||||
|
||||
```markdown
|
||||
GitHub Issues / Jira / Linear:
|
||||
|
||||
Epic: User Authentication
|
||||
├── Story: Email/Password Login (8 pts)
|
||||
│ ├── Task: Design login form
|
||||
│ ├── Task: Implement API endpoint
|
||||
│ ├── Task: Add validation
|
||||
│ └── Task: Write tests
|
||||
├── Story: Social Login (5 pts)
|
||||
└── Story: Password Reset (5 pts)
|
||||
|
||||
Labels:
|
||||
- Priority: P0 (Critical), P1 (High), P2 (Normal), P3 (Low)
|
||||
- Type: Feature, Bug, Tech Debt, Documentation
|
||||
- Status: Todo, In Progress, In Review, Done
|
||||
- Component: Frontend, Backend, Database, DevOps
|
||||
```
|
||||
|
||||
### Documentation
|
||||
|
||||
```markdown
|
||||
Essential Project Documents:
|
||||
|
||||
1. Product Requirements Document (PRD)
|
||||
- Features and requirements
|
||||
- User stories
|
||||
- Acceptance criteria
|
||||
|
||||
2. Technical Design Document
|
||||
- Architecture
|
||||
- Technology choices
|
||||
- API design
|
||||
|
||||
3. Project Charter
|
||||
- Goals and objectives
|
||||
- Scope
|
||||
- Timeline
|
||||
- Resources
|
||||
|
||||
4. Risk Register
|
||||
- Identified risks
|
||||
- Mitigation plans
|
||||
- Status
|
||||
|
||||
5. Sprint Plans
|
||||
- Sprint goals
|
||||
- Committed stories
|
||||
- Capacity
|
||||
```
|
||||
|
||||
## Planning Checklist
|
||||
|
||||
**Project Initiation**:
|
||||
- [ ] Define project goals and objectives
|
||||
- [ ] Identify stakeholders
|
||||
- [ ] Create project charter
|
||||
- [ ] Define scope and requirements
|
||||
- [ ] Estimate timeline and budget
|
||||
|
||||
**Planning Phase**:
|
||||
- [ ] Create work breakdown structure
|
||||
- [ ] Estimate tasks
|
||||
- [ ] Identify dependencies
|
||||
- [ ] Assess risks
|
||||
- [ ] Define milestones
|
||||
- [ ] Allocate resources
|
||||
|
||||
**Sprint Planning**:
|
||||
- [ ] Review and refine backlog
|
||||
- [ ] Define sprint goal
|
||||
- [ ] Estimate stories
|
||||
- [ ] Check team capacity
|
||||
- [ ] Commit to sprint backlog
|
||||
- [ ] Break down into tasks
|
||||
|
||||
**During Execution**:
|
||||
- [ ] Track progress daily
|
||||
- [ ] Update burndown chart
|
||||
- [ ] Address blockers immediately
|
||||
- [ ] Communicate with stakeholders
|
||||
- [ ] Adjust plan as needed
|
||||
|
||||
**Sprint Close**:
|
||||
- [ ] Demo completed work
|
||||
- [ ] Conduct retrospective
|
||||
- [ ] Update velocity metrics
|
||||
- [ ] Plan next sprint
|
||||
|
||||
## When to Use This Skill
|
||||
|
||||
Use this skill when:
|
||||
- Starting new projects
|
||||
- Breaking down large initiatives
|
||||
- Estimating work effort
|
||||
- Planning sprints
|
||||
- Managing dependencies
|
||||
- Assessing risks
|
||||
- Tracking progress
|
||||
- Communicating with stakeholders
|
||||
- Running agile ceremonies
|
||||
- Improving team processes
|
||||
|
||||
---
|
||||
|
||||
**Remember**: Plans are useless, but planning is essential. Stay flexible, communicate often, and adjust course based on reality. The goal is not perfect adherence to the plan, but successfully delivering value to users.
|
||||
1012
skills/security-checklist/SKILL.md
Normal file
1012
skills/security-checklist/SKILL.md
Normal file
File diff suppressed because it is too large
Load Diff
912
skills/technical-writing/SKILL.md
Normal file
912
skills/technical-writing/SKILL.md
Normal file
@@ -0,0 +1,912 @@
|
||||
---
|
||||
name: technical-writing
|
||||
description: Technical writing best practices including documentation structure, clear writing principles, API documentation, tutorials, changelogs, and markdown formatting. Use when writing documentation, creating READMEs, documenting APIs, or writing tutorials.
|
||||
---
|
||||
|
||||
# Technical Writing
|
||||
|
||||
This skill provides comprehensive guidance for creating clear, effective technical documentation that helps users and developers.
|
||||
|
||||
## Documentation Structure
|
||||
|
||||
### The Four Types of Documentation
|
||||
|
||||
**1. Tutorials** (Learning-oriented)
|
||||
- Goal: Help beginners learn
|
||||
- Format: Step-by-step lessons
|
||||
- Example: "Build your first API"
|
||||
|
||||
**2. How-to Guides** (Problem-oriented)
|
||||
- Goal: Solve specific problems
|
||||
- Format: Numbered steps
|
||||
- Example: "How to deploy to production"
|
||||
|
||||
**3. Reference** (Information-oriented)
|
||||
- Goal: Provide detailed information
|
||||
- Format: Systematic descriptions
|
||||
- Example: API reference, configuration options
|
||||
|
||||
**4. Explanation** (Understanding-oriented)
|
||||
- Goal: Clarify concepts
|
||||
- Format: Discursive explanations
|
||||
- Example: Architecture decisions, design patterns
|
||||
|
||||
### README Structure
|
||||
|
||||
```markdown
|
||||
# Project Name
|
||||
|
||||
Brief description of what the project does (1-2 sentences).
|
||||
|
||||
[](link)
|
||||
[](link)
|
||||
[](link)
|
||||
|
||||
## Features
|
||||
|
||||
- Feature 1
|
||||
- Feature 2
|
||||
- Feature 3
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
# Installation
|
||||
npm install project-name
|
||||
|
||||
# Usage
|
||||
npx project-name init
|
||||
```
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Node.js 18+
|
||||
- PostgreSQL 14+
|
||||
- Redis 7+
|
||||
|
||||
## Installation
|
||||
|
||||
### Using npm
|
||||
|
||||
```bash
|
||||
npm install project-name
|
||||
```
|
||||
|
||||
### Using yarn
|
||||
|
||||
```bash
|
||||
yarn add project-name
|
||||
```
|
||||
|
||||
### From source
|
||||
|
||||
```bash
|
||||
git clone https://github.com/user/project.git
|
||||
cd project
|
||||
npm install
|
||||
npm run build
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
Create a `.env` file:
|
||||
|
||||
```env
|
||||
DATABASE_URL=postgresql://user:password@localhost:5432/db
|
||||
API_KEY=your_api_key
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Example
|
||||
|
||||
```typescript
|
||||
import { createClient } from 'project-name';
|
||||
|
||||
const client = createClient({
|
||||
apiKey: process.env.API_KEY,
|
||||
});
|
||||
|
||||
const result = await client.doSomething();
|
||||
console.log(result);
|
||||
```
|
||||
|
||||
### Advanced Example
|
||||
|
||||
[More complex example with explanations]
|
||||
|
||||
## API Reference
|
||||
|
||||
See [API.md](./API.md) for complete API documentation.
|
||||
|
||||
## Contributing
|
||||
|
||||
See [CONTRIBUTING.md](./CONTRIBUTING.md) for guidelines.
|
||||
|
||||
## License
|
||||
|
||||
MIT © [Author Name]
|
||||
|
||||
## Support
|
||||
|
||||
- Documentation: https://docs.example.com
|
||||
- Issues: https://github.com/user/project/issues
|
||||
- Discussions: https://github.com/user/project/discussions
|
||||
```
|
||||
|
||||
## Clear Writing Principles
|
||||
|
||||
### Use Active Voice
|
||||
|
||||
```markdown
|
||||
❌ Passive: The data is validated by the function.
|
||||
✅ Active: The function validates the data.
|
||||
|
||||
❌ Passive: Errors should be handled by your application.
|
||||
✅ Active: Your application should handle errors.
|
||||
```
|
||||
|
||||
### Use Simple Language
|
||||
|
||||
```markdown
|
||||
❌ Complex: Utilize the aforementioned methodology to instantiate a novel instance.
|
||||
✅ Simple: Use this method to create a new instance.
|
||||
|
||||
❌ Jargon: Leverage our SDK to synergize with the API ecosystem.
|
||||
✅ Clear: Use our SDK to connect to the API.
|
||||
```
|
||||
|
||||
### Be Concise
|
||||
|
||||
```markdown
|
||||
❌ Wordy: In order to be able to successfully complete the installation process,
|
||||
you will need to make sure that you have Node.js version 18 or higher installed
|
||||
on your system.
|
||||
✅ Concise: Install Node.js 18 or higher.
|
||||
|
||||
❌ Redundant: The function returns back a response.
|
||||
✅ Concise: The function returns a response.
|
||||
```
|
||||
|
||||
### Use Consistent Terminology
|
||||
|
||||
```markdown
|
||||
❌ Inconsistent:
|
||||
- Create a user
|
||||
- Add an account
|
||||
- Register a member
|
||||
(All referring to the same action)
|
||||
|
||||
✅ Consistent:
|
||||
- Create a user
|
||||
- Update a user
|
||||
- Delete a user
|
||||
```
|
||||
|
||||
## Code Example Best Practices
|
||||
|
||||
### Complete, Runnable Examples
|
||||
|
||||
```typescript
|
||||
// ❌ BAD - Incomplete example
|
||||
user.save();
|
||||
|
||||
// ✅ GOOD - Complete example
|
||||
import { User } from './models';
|
||||
|
||||
async function createUser() {
|
||||
const user = new User({
|
||||
email: 'user@example.com',
|
||||
name: 'John Doe',
|
||||
});
|
||||
|
||||
await user.save();
|
||||
console.log('User created:', user.id);
|
||||
}
|
||||
|
||||
createUser();
|
||||
```
|
||||
|
||||
### Show Expected Output
|
||||
|
||||
```typescript
|
||||
// Calculate fibonacci number
|
||||
function fibonacci(n: number): number {
|
||||
if (n <= 1) return n;
|
||||
return fibonacci(n - 1) + fibonacci(n - 2);
|
||||
}
|
||||
|
||||
console.log(fibonacci(10));
|
||||
// Output: 55
|
||||
```
|
||||
|
||||
### Highlight Important Parts
|
||||
|
||||
```typescript
|
||||
// Authenticate user with JWT
|
||||
app.post('/api/auth/login', async (req, res) => {
|
||||
const { email, password } = req.body;
|
||||
|
||||
const user = await User.findOne({ email });
|
||||
if (!user) {
|
||||
return res.status(401).json({ error: 'Invalid credentials' });
|
||||
}
|
||||
|
||||
// 👇 Important: Always use bcrypt for password comparison
|
||||
const isValid = await bcrypt.compare(password, user.passwordHash);
|
||||
if (!isValid) {
|
||||
return res.status(401).json({ error: 'Invalid credentials' });
|
||||
}
|
||||
|
||||
const token = generateToken(user);
|
||||
res.json({ token });
|
||||
});
|
||||
```
|
||||
|
||||
### Provide Context
|
||||
|
||||
```typescript
|
||||
// ❌ BAD - No context
|
||||
await client.query('SELECT * FROM users');
|
||||
|
||||
// ✅ GOOD - With context
|
||||
// Fetch all active users who logged in within the last 30 days
|
||||
const activeUsers = await client.query(`
|
||||
SELECT id, email, name, last_login
|
||||
FROM users
|
||||
WHERE status = 'active'
|
||||
AND last_login > NOW() - INTERVAL '30 days'
|
||||
ORDER BY last_login DESC
|
||||
`);
|
||||
```
|
||||
|
||||
## Tutorial Structure
|
||||
|
||||
### Learning Progression
|
||||
|
||||
**1. Introduction** (2-3 sentences)
|
||||
- What will users learn?
|
||||
- Why is it useful?
|
||||
|
||||
**2. Prerequisites**
|
||||
- Required knowledge
|
||||
- Required tools
|
||||
- Time estimate
|
||||
|
||||
**3. Step-by-Step Instructions**
|
||||
- Number each step
|
||||
- One concept per step
|
||||
- Show results after each step
|
||||
|
||||
**4. Next Steps**
|
||||
- Links to related tutorials
|
||||
- Advanced topics
|
||||
- Additional resources
|
||||
|
||||
### Tutorial Example
|
||||
|
||||
```markdown
|
||||
# Building a REST API with Express
|
||||
|
||||
In this tutorial, you'll build a REST API for managing a todo list.
|
||||
You'll learn how to create routes, handle requests, and connect to a database.
|
||||
|
||||
**Time**: 30 minutes
|
||||
**Level**: Beginner
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Node.js 18+ installed
|
||||
- Basic JavaScript knowledge
|
||||
- Code editor (VS Code recommended)
|
||||
|
||||
## Step 1: Set Up Project
|
||||
|
||||
Create a new project directory and initialize npm:
|
||||
|
||||
```bash
|
||||
mkdir todo-api
|
||||
cd todo-api
|
||||
npm init -y
|
||||
```
|
||||
|
||||
Install Express:
|
||||
|
||||
```bash
|
||||
npm install express
|
||||
```
|
||||
|
||||
You should see `express` added to your `package.json`.
|
||||
|
||||
## Step 2: Create Basic Server
|
||||
|
||||
Create `index.js`:
|
||||
|
||||
```javascript
|
||||
const express = require('express');
|
||||
const app = express();
|
||||
|
||||
app.get('/', (req, res) => {
|
||||
res.json({ message: 'Hello, World!' });
|
||||
});
|
||||
|
||||
const PORT = 3000;
|
||||
app.listen(PORT, () => {
|
||||
console.log(`Server running on http://localhost:${PORT}`);
|
||||
});
|
||||
```
|
||||
|
||||
Run the server:
|
||||
|
||||
```bash
|
||||
node index.js
|
||||
```
|
||||
|
||||
Visit http://localhost:3000 in your browser. You should see:
|
||||
```json
|
||||
{ "message": "Hello, World!" }
|
||||
```
|
||||
|
||||
## Step 3: Add Todo Routes
|
||||
|
||||
[Continue with more steps...]
|
||||
|
||||
## What You Learned
|
||||
|
||||
- How to set up an Express server
|
||||
- How to create REST API routes
|
||||
- How to connect to a database
|
||||
|
||||
## Next Steps
|
||||
|
||||
- [Authentication with JWT](./auth-tutorial.md)
|
||||
- [Deploy to Production](./deploy-guide.md)
|
||||
- [API Best Practices](./api-best-practices.md)
|
||||
```
|
||||
|
||||
## API Documentation Patterns
|
||||
|
||||
### Endpoint Documentation
|
||||
|
||||
```markdown
|
||||
## Create User
|
||||
|
||||
Creates a new user account.
|
||||
|
||||
**Endpoint**: `POST /api/v1/users`
|
||||
|
||||
**Authentication**: Not required
|
||||
|
||||
**Request Body**:
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| email | string | Yes | User's email address (must be valid) |
|
||||
| password | string | Yes | Password (min 8 characters) |
|
||||
| name | string | Yes | User's full name (max 100 characters) |
|
||||
|
||||
**Example Request**:
|
||||
|
||||
```bash
|
||||
curl -X POST https://api.example.com/v1/users \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"email": "user@example.com",
|
||||
"password": "SecurePass123",
|
||||
"name": "John Doe"
|
||||
}'
|
||||
```
|
||||
|
||||
**Success Response** (201 Created):
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "user_abc123",
|
||||
"email": "user@example.com",
|
||||
"name": "John Doe",
|
||||
"createdAt": "2025-10-16T10:30:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
**Error Responses**:
|
||||
|
||||
**400 Bad Request** - Invalid input:
|
||||
```json
|
||||
{
|
||||
"error": {
|
||||
"code": "VALIDATION_ERROR",
|
||||
"message": "Invalid email address",
|
||||
"field": "email"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**409 Conflict** - Email already exists:
|
||||
```json
|
||||
{
|
||||
"error": {
|
||||
"code": "EMAIL_EXISTS",
|
||||
"message": "Email address already registered"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Rate Limit**: 5 requests per minute
|
||||
```
|
||||
|
||||
### Function/Method Documentation
|
||||
|
||||
```typescript
|
||||
/**
|
||||
* Calculates the total price of items including tax.
|
||||
*
|
||||
* @param items - Array of items to calculate total for
|
||||
* @param taxRate - Tax rate as decimal (e.g., 0.08 for 8%)
|
||||
* @returns Total price including tax
|
||||
*
|
||||
* @throws {Error} If items array is empty
|
||||
* @throws {Error} If taxRate is negative
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const items = [
|
||||
* { price: 10, quantity: 2 },
|
||||
* { price: 15, quantity: 1 }
|
||||
* ];
|
||||
* const total = calculateTotal(items, 0.08);
|
||||
* console.log(total); // 37.80
|
||||
* ```
|
||||
*/
|
||||
function calculateTotal(
|
||||
items: Array<{ price: number; quantity: number }>,
|
||||
taxRate: number
|
||||
): number {
|
||||
if (items.length === 0) {
|
||||
throw new Error('Items array cannot be empty');
|
||||
}
|
||||
if (taxRate < 0) {
|
||||
throw new Error('Tax rate cannot be negative');
|
||||
}
|
||||
|
||||
const subtotal = items.reduce(
|
||||
(sum, item) => sum + item.price * item.quantity,
|
||||
0
|
||||
);
|
||||
return subtotal * (1 + taxRate);
|
||||
}
|
||||
```
|
||||
|
||||
## Changelog Best Practices
|
||||
|
||||
### Keep a Changelog Format
|
||||
|
||||
```markdown
|
||||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/).
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
- New feature X for Y use case
|
||||
|
||||
### Changed
|
||||
- Improved performance of Z operation
|
||||
|
||||
### Fixed
|
||||
- Fixed bug where A caused B
|
||||
|
||||
## [2.1.0] - 2025-10-16
|
||||
|
||||
### Added
|
||||
- User profile avatars (#123)
|
||||
- Email notification settings (#125)
|
||||
- Two-factor authentication support (#130)
|
||||
|
||||
### Changed
|
||||
- Updated UI for settings page (#124)
|
||||
- Improved API response times by 40% (#128)
|
||||
|
||||
### Deprecated
|
||||
- `oldFunction()` will be removed in v3.0 - use `newFunction()` instead
|
||||
|
||||
### Fixed
|
||||
- Fixed memory leak in session management (#126)
|
||||
- Corrected timezone handling in reports (#129)
|
||||
|
||||
### Security
|
||||
- Updated dependencies to patch security vulnerabilities (#127)
|
||||
|
||||
## [2.0.0] - 2025-09-01
|
||||
|
||||
### Added
|
||||
- Complete redesign of dashboard
|
||||
- GraphQL API support
|
||||
|
||||
### Changed
|
||||
- **BREAKING**: Renamed `create_user` to `createUser` for consistency
|
||||
- **BREAKING**: Changed date format from `DD/MM/YYYY` to ISO 8601
|
||||
|
||||
### Removed
|
||||
- **BREAKING**: Removed deprecated v1 API endpoints
|
||||
|
||||
[Unreleased]: https://github.com/user/project/compare/v2.1.0...HEAD
|
||||
[2.1.0]: https://github.com/user/project/compare/v2.0.0...v2.1.0
|
||||
[2.0.0]: https://github.com/user/project/releases/tag/v2.0.0
|
||||
```
|
||||
|
||||
### Version Numbering
|
||||
|
||||
**Semantic Versioning (MAJOR.MINOR.PATCH)**:
|
||||
- **MAJOR**: Breaking changes (2.0.0 → 3.0.0)
|
||||
- **MINOR**: New features, backwards compatible (2.0.0 → 2.1.0)
|
||||
- **PATCH**: Bug fixes, backwards compatible (2.0.0 → 2.0.1)
|
||||
|
||||
## Markdown Formatting
|
||||
|
||||
### Headers
|
||||
|
||||
```markdown
|
||||
# H1 - Main title
|
||||
## H2 - Section
|
||||
### H3 - Subsection
|
||||
#### H4 - Sub-subsection
|
||||
```
|
||||
|
||||
### Emphasis
|
||||
|
||||
```markdown
|
||||
**Bold text** or __bold__
|
||||
*Italic text* or _italic_
|
||||
***Bold and italic***
|
||||
~~Strikethrough~~
|
||||
`Inline code`
|
||||
```
|
||||
|
||||
### Lists
|
||||
|
||||
```markdown
|
||||
Unordered list:
|
||||
- Item 1
|
||||
- Item 2
|
||||
- Nested item
|
||||
- Another nested item
|
||||
- Item 3
|
||||
|
||||
Ordered list:
|
||||
1. First item
|
||||
2. Second item
|
||||
1. Nested item
|
||||
2. Another nested item
|
||||
3. Third item
|
||||
|
||||
Task list:
|
||||
- [x] Completed task
|
||||
- [ ] Incomplete task
|
||||
```
|
||||
|
||||
### Links and Images
|
||||
|
||||
```markdown
|
||||
[Link text](https://example.com)
|
||||
[Link with title](https://example.com "Title text")
|
||||
|
||||

|
||||

|
||||
```
|
||||
|
||||
### Code Blocks
|
||||
|
||||
````markdown
|
||||
Inline code: `const x = 5;`
|
||||
|
||||
Code block:
|
||||
```javascript
|
||||
function greet(name) {
|
||||
console.log(`Hello, ${name}!`);
|
||||
}
|
||||
```
|
||||
|
||||
With line highlighting:
|
||||
```javascript {2}
|
||||
function greet(name) {
|
||||
console.log(`Hello, ${name}!`); // This line is highlighted
|
||||
}
|
||||
```
|
||||
````
|
||||
|
||||
### Tables
|
||||
|
||||
```markdown
|
||||
| Column 1 | Column 2 | Column 3 |
|
||||
|----------|----------|----------|
|
||||
| Row 1 | Data | More |
|
||||
| Row 2 | Data | More |
|
||||
|
||||
Alignment:
|
||||
| Left | Center | Right |
|
||||
|:-----|:------:|------:|
|
||||
| L | C | R |
|
||||
```
|
||||
|
||||
### Blockquotes
|
||||
|
||||
```markdown
|
||||
> Single line quote
|
||||
|
||||
> Multi-line
|
||||
> quote with
|
||||
> several lines
|
||||
|
||||
> **Note**: Important information
|
||||
```
|
||||
|
||||
### Admonitions
|
||||
|
||||
```markdown
|
||||
> **⚠️ Warning**: This action cannot be undone.
|
||||
|
||||
> **💡 Tip**: Use keyboard shortcuts to speed up your workflow.
|
||||
|
||||
> **🚨 Danger**: Never commit secrets to version control.
|
||||
|
||||
> **ℹ️ Info**: This feature requires Node.js 18+.
|
||||
```
|
||||
|
||||
## Diagrams and Visuals
|
||||
|
||||
### When to Use Diagrams
|
||||
|
||||
**Use diagrams for**:
|
||||
- System architecture
|
||||
- Data flow
|
||||
- Process flows
|
||||
- Component relationships
|
||||
- Complex concepts
|
||||
|
||||
**Don't use diagrams for**:
|
||||
- Simple concepts (text is better)
|
||||
- Things that change frequently
|
||||
- Content that can be code
|
||||
|
||||
### Mermaid Diagrams
|
||||
|
||||
````markdown
|
||||
```mermaid
|
||||
graph TD
|
||||
A[User Request] --> B{Authenticated?}
|
||||
B -->|Yes| C[Process Request]
|
||||
B -->|No| D[Return 401]
|
||||
C --> E[Return Response]
|
||||
```
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
Client->>API: POST /users
|
||||
API->>Database: INSERT user
|
||||
Database-->>API: User created
|
||||
API->>Email: Send welcome email
|
||||
API-->>Client: 201 Created
|
||||
```
|
||||
````
|
||||
|
||||
### ASCII Diagrams
|
||||
|
||||
```markdown
|
||||
┌─────────────┐ ┌──────────────┐ ┌──────────┐
|
||||
│ Client │─────▶│ API Server │─────▶│ Database │
|
||||
│ (Browser) │◀─────│ (Express) │◀─────│ (Postgres)│
|
||||
└─────────────┘ └──────────────┘ └──────────┘
|
||||
```
|
||||
|
||||
## Progressive Disclosure
|
||||
|
||||
### Start Simple, Add Details
|
||||
|
||||
```markdown
|
||||
## Installation
|
||||
|
||||
Install via npm:
|
||||
|
||||
```bash
|
||||
npm install package-name
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>Advanced installation options</summary>
|
||||
|
||||
### Install from source
|
||||
|
||||
```bash
|
||||
git clone https://github.com/user/package.git
|
||||
cd package
|
||||
npm install
|
||||
npm run build
|
||||
npm link
|
||||
```
|
||||
|
||||
### Install specific version
|
||||
|
||||
```bash
|
||||
npm install package-name@2.1.0
|
||||
```
|
||||
|
||||
### Install with peer dependencies
|
||||
|
||||
```bash
|
||||
npm install package-name react react-dom
|
||||
```
|
||||
</details>
|
||||
```
|
||||
|
||||
### Organize by Skill Level
|
||||
|
||||
```markdown
|
||||
## Quick Start (Beginner)
|
||||
|
||||
Get up and running in 5 minutes:
|
||||
|
||||
[Simple example]
|
||||
|
||||
## Advanced Usage
|
||||
|
||||
For experienced users:
|
||||
|
||||
[Complex example]
|
||||
|
||||
## Expert Topics
|
||||
|
||||
Deep dive into internals:
|
||||
|
||||
[Very advanced example]
|
||||
```
|
||||
|
||||
## User-Focused Language
|
||||
|
||||
### Address the Reader
|
||||
|
||||
```markdown
|
||||
❌ Impersonal: The configuration file should be updated.
|
||||
✅ Personal: Update your configuration file.
|
||||
|
||||
❌ Distant: One must install the dependencies.
|
||||
✅ Direct: Install the dependencies.
|
||||
```
|
||||
|
||||
### Use "You" Not "We"
|
||||
|
||||
```markdown
|
||||
❌ We: Now we'll create a new user.
|
||||
✅ You: Now you'll create a new user.
|
||||
|
||||
❌ We: We recommend using TypeScript.
|
||||
✅ You: We recommend you use TypeScript.
|
||||
```
|
||||
|
||||
### Be Helpful
|
||||
|
||||
```markdown
|
||||
❌ Vague: An error occurred.
|
||||
✅ Helpful: Connection failed. Check your network and try again.
|
||||
|
||||
❌ Blaming: You entered invalid data.
|
||||
✅ Helpful: The email field requires a valid email address (e.g., user@example.com).
|
||||
```
|
||||
|
||||
## Avoiding Jargon
|
||||
|
||||
### Define Technical Terms
|
||||
|
||||
```markdown
|
||||
❌ Assumes knowledge:
|
||||
"Use the ORM to query the RDBMS."
|
||||
|
||||
✅ Explains terms:
|
||||
"Use the ORM (Object-Relational Mapping tool) to query the database.
|
||||
An ORM lets you interact with your database using code instead of SQL."
|
||||
```
|
||||
|
||||
### Use Common Words
|
||||
|
||||
```markdown
|
||||
❌ Technical jargon:
|
||||
"Leverage the API to facilitate data ingestion."
|
||||
|
||||
✅ Plain English:
|
||||
"Use the API to import data."
|
||||
```
|
||||
|
||||
## Version Documentation
|
||||
|
||||
### Document Version Changes
|
||||
|
||||
```markdown
|
||||
## Version Compatibility
|
||||
|
||||
| Version | Node.js | Features |
|
||||
|---------|---------|----------|
|
||||
| 3.x | 18+ | Full feature set |
|
||||
| 2.x | 16+ | Legacy API (deprecated) |
|
||||
| 1.x | 14+ | No longer supported |
|
||||
|
||||
## Upgrading from 2.x to 3.x
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
**1. Renamed functions**
|
||||
|
||||
```typescript
|
||||
// v2.x
|
||||
import { create_user } from 'package';
|
||||
|
||||
// v3.x
|
||||
import { createUser } from 'package';
|
||||
```
|
||||
|
||||
**2. Changed date format**
|
||||
|
||||
Dates now use ISO 8601 format:
|
||||
- Old: `01/15/2025`
|
||||
- New: `2025-01-15T00:00:00Z`
|
||||
|
||||
### Migration Guide
|
||||
|
||||
1. Update imports:
|
||||
```bash
|
||||
# Run this command to update your code
|
||||
npx package-migrate-v3
|
||||
```
|
||||
|
||||
2. Update date handling:
|
||||
```typescript
|
||||
// Before
|
||||
const date = '01/15/2025';
|
||||
|
||||
// After
|
||||
const date = '2025-01-15T00:00:00Z';
|
||||
```
|
||||
|
||||
3. Test thoroughly before deploying.
|
||||
```
|
||||
|
||||
## Documentation Checklist
|
||||
|
||||
**Before Writing**:
|
||||
- [ ] Who is the audience (beginner/intermediate/expert)?
|
||||
- [ ] What do they need to accomplish?
|
||||
- [ ] What do they already know?
|
||||
|
||||
**While Writing**:
|
||||
- [ ] Use active voice
|
||||
- [ ] Use simple language
|
||||
- [ ] Be concise
|
||||
- [ ] Provide examples
|
||||
- [ ] Show expected output
|
||||
|
||||
**After Writing**:
|
||||
- [ ] Read it aloud
|
||||
- [ ] Have someone else review it
|
||||
- [ ] Test all code examples
|
||||
- [ ] Check all links
|
||||
- [ ] Spell check
|
||||
|
||||
## When to Use This Skill
|
||||
|
||||
Use this skill when:
|
||||
- Writing project READMEs
|
||||
- Creating API documentation
|
||||
- Writing tutorials
|
||||
- Documenting code
|
||||
- Creating user guides
|
||||
- Writing changelogs
|
||||
- Contributing to open source
|
||||
- Creating internal documentation
|
||||
- Writing blog posts about technical topics
|
||||
- Training others on technical writing
|
||||
|
||||
---
|
||||
|
||||
**Remember**: Good documentation is empathetic. Always write for the person reading your docs at 2 AM who just wants to get their code working. Be clear, be helpful, and be kind.
|
||||
909
skills/testing-strategy/SKILL.md
Normal file
909
skills/testing-strategy/SKILL.md
Normal file
@@ -0,0 +1,909 @@
|
||||
---
|
||||
name: testing-strategy
|
||||
description: Comprehensive testing strategies including test pyramid, TDD methodology, testing patterns, coverage goals, and CI/CD integration. Use when writing tests, implementing TDD, reviewing test coverage, debugging test failures, or setting up testing infrastructure.
|
||||
---
|
||||
|
||||
# Testing Strategy
|
||||
|
||||
This skill provides comprehensive guidance for implementing effective testing strategies across your entire application stack.
|
||||
|
||||
## Test Pyramid
|
||||
|
||||
### The Testing Hierarchy
|
||||
|
||||
```
|
||||
/\
|
||||
/ \
|
||||
/E2E \ 10% - End-to-End Tests (slowest, most expensive)
|
||||
/______\
|
||||
/ \
|
||||
/Integration\ 20% - Integration Tests (medium speed/cost)
|
||||
/____________\
|
||||
/ \
|
||||
/ Unit Tests \ 70% - Unit Tests (fast, cheap, focused)
|
||||
/__________________\
|
||||
```
|
||||
|
||||
**Rationale**:
|
||||
- **70% Unit Tests**: Fast, isolated, catch bugs early
|
||||
- **20% Integration Tests**: Test component interactions
|
||||
- **10% E2E Tests**: Test critical user journeys
|
||||
|
||||
### Why This Distribution?
|
||||
|
||||
**Unit tests are cheap**:
|
||||
- Run in milliseconds
|
||||
- No external dependencies
|
||||
- Easy to debug
|
||||
- High code coverage per test
|
||||
|
||||
**Integration tests are moderate**:
|
||||
- Test real interactions
|
||||
- Catch integration bugs
|
||||
- Slower than unit tests
|
||||
- More complex setup
|
||||
|
||||
**E2E tests are expensive**:
|
||||
- Test entire system
|
||||
- Catch UX issues
|
||||
- Very slow (seconds/minutes)
|
||||
- Brittle and hard to maintain
|
||||
|
||||
## TDD (Test-Driven Development)
|
||||
|
||||
### Red-Green-Refactor Cycle
|
||||
|
||||
**1. Red - Write a failing test**:
|
||||
```typescript
|
||||
describe('Calculator', () => {
|
||||
test('adds two numbers', () => {
|
||||
const calculator = new Calculator();
|
||||
expect(calculator.add(2, 3)).toBe(5); // FAILS - method doesn't exist
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
**2. Green - Write minimal code to pass**:
|
||||
```typescript
|
||||
class Calculator {
|
||||
add(a: number, b: number): number {
|
||||
return a + b; // Simplest implementation
|
||||
}
|
||||
}
|
||||
// Test now PASSES
|
||||
```
|
||||
|
||||
**3. Refactor - Improve the code**:
|
||||
```typescript
|
||||
class Calculator {
|
||||
add(a: number, b: number): number {
|
||||
// Add validation
|
||||
if (!Number.isFinite(a) || !Number.isFinite(b)) {
|
||||
throw new Error('Arguments must be finite numbers');
|
||||
}
|
||||
return a + b;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### TDD Benefits
|
||||
|
||||
**Design benefits**:
|
||||
- Forces you to think about API before implementation
|
||||
- Leads to more testable, modular code
|
||||
- Encourages SOLID principles
|
||||
|
||||
**Quality benefits**:
|
||||
- 100% test coverage by design
|
||||
- Catches bugs immediately
|
||||
- Provides living documentation
|
||||
|
||||
**Workflow benefits**:
|
||||
- Clear next step (make test pass)
|
||||
- Confidence when refactoring
|
||||
- Prevents over-engineering
|
||||
|
||||
## Arrange-Act-Assert Pattern
|
||||
|
||||
### The AAA Pattern
|
||||
|
||||
Every test should follow this structure:
|
||||
|
||||
```typescript
|
||||
test('user registration creates account and sends welcome email', async () => {
|
||||
// ARRANGE - Set up test conditions
|
||||
const userData = {
|
||||
email: 'test@example.com',
|
||||
password: 'SecurePass123',
|
||||
name: 'Test User',
|
||||
};
|
||||
const mockEmailService = jest.fn();
|
||||
const userService = new UserService(mockEmailService);
|
||||
|
||||
// ACT - Execute the behavior being tested
|
||||
const result = await userService.register(userData);
|
||||
|
||||
// ASSERT - Verify the outcome
|
||||
expect(result.id).toBeDefined();
|
||||
expect(result.email).toBe(userData.email);
|
||||
expect(mockEmailService).toHaveBeenCalledWith({
|
||||
to: userData.email,
|
||||
subject: 'Welcome!',
|
||||
template: 'welcome',
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### Why AAA?
|
||||
|
||||
- **Clear structure**: Easy to understand what's being tested
|
||||
- **Consistent**: All tests follow same pattern
|
||||
- **Maintainable**: Easy to modify and debug
|
||||
|
||||
## Mocking Strategies
|
||||
|
||||
### When to Mock
|
||||
|
||||
**✅ DO mock**:
|
||||
- External APIs
|
||||
- Databases
|
||||
- File system operations
|
||||
- Time/dates
|
||||
- Random number generators
|
||||
- Network requests
|
||||
- Third-party services
|
||||
|
||||
```typescript
|
||||
// Mock external API
|
||||
jest.mock('axios');
|
||||
|
||||
test('fetches user data from API', async () => {
|
||||
const mockData = { id: 1, name: 'John' };
|
||||
(axios.get as jest.Mock).mockResolvedValue({ data: mockData });
|
||||
|
||||
const user = await fetchUser(1);
|
||||
|
||||
expect(user).toEqual(mockData);
|
||||
});
|
||||
```
|
||||
|
||||
### When NOT to Mock
|
||||
|
||||
**❌ DON'T mock**:
|
||||
- Pure functions (test them directly)
|
||||
- Simple utility functions
|
||||
- Domain logic
|
||||
- Value objects
|
||||
- Internal implementation details
|
||||
|
||||
```typescript
|
||||
// ❌ BAD - Over-mocking
|
||||
test('validates email', () => {
|
||||
const validator = new EmailValidator();
|
||||
jest.spyOn(validator, 'isValid').mockReturnValue(true);
|
||||
expect(validator.isValid('test@example.com')).toBe(true);
|
||||
// This test is useless - you're testing the mock, not the code
|
||||
});
|
||||
|
||||
// ✅ GOOD - Test real implementation
|
||||
test('validates email', () => {
|
||||
const validator = new EmailValidator();
|
||||
expect(validator.isValid('test@example.com')).toBe(true);
|
||||
expect(validator.isValid('invalid')).toBe(false);
|
||||
});
|
||||
```
|
||||
|
||||
### Mocking Patterns
|
||||
|
||||
**Stub** (return predetermined values):
|
||||
```typescript
|
||||
const mockDatabase = {
|
||||
findUser: jest.fn().mockResolvedValue({ id: 1, name: 'John' }),
|
||||
saveUser: jest.fn().mockResolvedValue(true),
|
||||
};
|
||||
```
|
||||
|
||||
**Spy** (track calls, use real implementation):
|
||||
```typescript
|
||||
const emailService = new EmailService();
|
||||
const sendSpy = jest.spyOn(emailService, 'send');
|
||||
|
||||
await emailService.send('test@example.com', 'Hello');
|
||||
|
||||
expect(sendSpy).toHaveBeenCalledTimes(1);
|
||||
expect(sendSpy).toHaveBeenCalledWith('test@example.com', 'Hello');
|
||||
```
|
||||
|
||||
**Fake** (lightweight implementation):
|
||||
```typescript
|
||||
class FakeDatabase {
|
||||
private data = new Map();
|
||||
|
||||
async save(key: string, value: any) {
|
||||
this.data.set(key, value);
|
||||
}
|
||||
|
||||
async get(key: string) {
|
||||
return this.data.get(key);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Test Coverage Goals
|
||||
|
||||
### Coverage Metrics
|
||||
|
||||
**Line Coverage**: Percentage of code lines executed
|
||||
- **Target**: 80-90% for critical paths
|
||||
|
||||
**Branch Coverage**: Percentage of if/else branches tested
|
||||
- **Target**: 80%+ (more important than line coverage)
|
||||
|
||||
**Function Coverage**: Percentage of functions called
|
||||
- **Target**: 90%+
|
||||
|
||||
**Statement Coverage**: Percentage of statements executed
|
||||
- **Target**: 80%+
|
||||
|
||||
### Coverage Configuration
|
||||
|
||||
```json
|
||||
// package.json
|
||||
{
|
||||
"jest": {
|
||||
"collectCoverage": true,
|
||||
"coverageThreshold": {
|
||||
"global": {
|
||||
"branches": 80,
|
||||
"functions": 90,
|
||||
"lines": 80,
|
||||
"statements": 80
|
||||
},
|
||||
"./src/critical/": {
|
||||
"branches": 95,
|
||||
"functions": 95,
|
||||
"lines": 95,
|
||||
"statements": 95
|
||||
}
|
||||
},
|
||||
"coveragePathIgnorePatterns": [
|
||||
"/node_modules/",
|
||||
"/tests/",
|
||||
"/migrations/",
|
||||
"/.config.ts$/"
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### What to Prioritize
|
||||
|
||||
**High priority** (aim for 95%+ coverage):
|
||||
- Business logic
|
||||
- Security-critical code
|
||||
- Payment/billing code
|
||||
- Data validation
|
||||
- Authentication/authorization
|
||||
|
||||
**Medium priority** (aim for 80%+ coverage):
|
||||
- API endpoints
|
||||
- Database queries
|
||||
- Utility functions
|
||||
- Error handling
|
||||
|
||||
**Low priority** (optional coverage):
|
||||
- UI components (use integration tests instead)
|
||||
- Configuration files
|
||||
- Type definitions
|
||||
- Third-party library wrappers
|
||||
|
||||
## Integration Testing
|
||||
|
||||
### Database Integration Tests
|
||||
|
||||
```typescript
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
|
||||
describe('UserRepository', () => {
|
||||
let prisma: PrismaClient;
|
||||
let repository: UserRepository;
|
||||
|
||||
beforeAll(async () => {
|
||||
// Use test database
|
||||
prisma = new PrismaClient({
|
||||
datasources: { db: { url: process.env.TEST_DATABASE_URL } },
|
||||
});
|
||||
repository = new UserRepository(prisma);
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
// Clean database before each test
|
||||
await prisma.user.deleteMany();
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await prisma.$disconnect();
|
||||
});
|
||||
|
||||
test('creates user and retrieves by email', async () => {
|
||||
// ARRANGE
|
||||
const userData = {
|
||||
email: 'test@example.com',
|
||||
name: 'Test User',
|
||||
password: 'hashed_password',
|
||||
};
|
||||
|
||||
// ACT
|
||||
const created = await repository.create(userData);
|
||||
const retrieved = await repository.findByEmail(userData.email);
|
||||
|
||||
// ASSERT
|
||||
expect(retrieved).toBeDefined();
|
||||
expect(retrieved?.id).toBe(created.id);
|
||||
expect(retrieved?.email).toBe(userData.email);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### API Integration Tests
|
||||
|
||||
```typescript
|
||||
import request from 'supertest';
|
||||
import { app } from '../src/app';
|
||||
|
||||
describe('User API', () => {
|
||||
test('POST /api/users creates user and returns 201', async () => {
|
||||
const response = await request(app)
|
||||
.post('/api/users')
|
||||
.send({
|
||||
email: 'test@example.com',
|
||||
password: 'SecurePass123',
|
||||
name: 'Test User',
|
||||
})
|
||||
.expect(201);
|
||||
|
||||
expect(response.body).toMatchObject({
|
||||
email: 'test@example.com',
|
||||
name: 'Test User',
|
||||
});
|
||||
expect(response.body.password).toBeUndefined(); // Never return password
|
||||
});
|
||||
|
||||
test('POST /api/users returns 400 for invalid email', async () => {
|
||||
const response = await request(app)
|
||||
.post('/api/users')
|
||||
.send({
|
||||
email: 'invalid-email',
|
||||
password: 'SecurePass123',
|
||||
name: 'Test User',
|
||||
})
|
||||
.expect(400);
|
||||
|
||||
expect(response.body.error.code).toBe('VALIDATION_ERROR');
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### Service Integration Tests
|
||||
|
||||
```typescript
|
||||
describe('OrderService Integration', () => {
|
||||
test('complete order flow', async () => {
|
||||
// Create order
|
||||
const order = await orderService.create({
|
||||
userId: 'user_123',
|
||||
items: [{ productId: 'prod_1', quantity: 2 }],
|
||||
});
|
||||
|
||||
// Process payment
|
||||
const payment = await paymentService.process({
|
||||
orderId: order.id,
|
||||
amount: order.total,
|
||||
});
|
||||
|
||||
// Verify inventory updated
|
||||
const product = await inventoryService.getProduct('prod_1');
|
||||
expect(product.stock).toBe(originalStock - 2);
|
||||
|
||||
// Verify order status updated
|
||||
const updatedOrder = await orderService.getById(order.id);
|
||||
expect(updatedOrder.status).toBe('paid');
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
## E2E Testing
|
||||
|
||||
### Playwright Setup
|
||||
|
||||
```typescript
|
||||
// playwright.config.ts
|
||||
import { defineConfig } from '@playwright/test';
|
||||
|
||||
export default defineConfig({
|
||||
testDir: './e2e',
|
||||
fullyParallel: true,
|
||||
retries: process.env.CI ? 2 : 0,
|
||||
workers: process.env.CI ? 1 : undefined,
|
||||
use: {
|
||||
baseURL: 'http://localhost:3000',
|
||||
trace: 'on-first-retry',
|
||||
screenshot: 'only-on-failure',
|
||||
},
|
||||
projects: [
|
||||
{ name: 'chromium', use: { browserName: 'chromium' } },
|
||||
{ name: 'firefox', use: { browserName: 'firefox' } },
|
||||
{ name: 'webkit', use: { browserName: 'webkit' } },
|
||||
],
|
||||
});
|
||||
```
|
||||
|
||||
### E2E Test Example
|
||||
|
||||
```typescript
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test.describe('User Registration Flow', () => {
|
||||
test('user can register and login', async ({ page }) => {
|
||||
// Navigate to registration page
|
||||
await page.goto('/register');
|
||||
|
||||
// Fill registration form
|
||||
await page.fill('[name="email"]', 'test@example.com');
|
||||
await page.fill('[name="password"]', 'SecurePass123');
|
||||
await page.fill('[name="confirmPassword"]', 'SecurePass123');
|
||||
await page.fill('[name="name"]', 'Test User');
|
||||
|
||||
// Submit form
|
||||
await page.click('button[type="submit"]');
|
||||
|
||||
// Wait for redirect to dashboard
|
||||
await page.waitForURL('/dashboard');
|
||||
|
||||
// Verify welcome message
|
||||
await expect(page.locator('h1')).toContainText('Welcome, Test User');
|
||||
});
|
||||
|
||||
test('shows validation errors for invalid input', async ({ page }) => {
|
||||
await page.goto('/register');
|
||||
|
||||
await page.fill('[name="email"]', 'invalid-email');
|
||||
await page.fill('[name="password"]', '123'); // Too short
|
||||
|
||||
await page.click('button[type="submit"]');
|
||||
|
||||
// Verify error messages displayed
|
||||
await expect(page.locator('[data-testid="email-error"]'))
|
||||
.toContainText('Invalid email');
|
||||
await expect(page.locator('[data-testid="password-error"]'))
|
||||
.toContainText('at least 8 characters');
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### Critical E2E Scenarios
|
||||
|
||||
Test these critical user journeys:
|
||||
- User registration and login
|
||||
- Checkout and payment flow
|
||||
- Password reset
|
||||
- Profile updates
|
||||
- Critical business workflows
|
||||
|
||||
## Performance Testing
|
||||
|
||||
### Load Testing with k6
|
||||
|
||||
```javascript
|
||||
import http from 'k6/http';
|
||||
import { check, sleep } from 'k6';
|
||||
|
||||
export const options = {
|
||||
stages: [
|
||||
{ duration: '30s', target: 20 }, // Ramp up to 20 users
|
||||
{ duration: '1m', target: 20 }, // Stay at 20 users
|
||||
{ duration: '30s', target: 100 }, // Ramp up to 100 users
|
||||
{ duration: '1m', target: 100 }, // Stay at 100 users
|
||||
{ duration: '30s', target: 0 }, // Ramp down to 0 users
|
||||
],
|
||||
thresholds: {
|
||||
http_req_duration: ['p(95)<500'], // 95% of requests under 500ms
|
||||
http_req_failed: ['rate<0.01'], // Less than 1% error rate
|
||||
},
|
||||
};
|
||||
|
||||
export default function() {
|
||||
const response = http.get('https://api.example.com/users');
|
||||
|
||||
check(response, {
|
||||
'status is 200': (r) => r.status === 200,
|
||||
'response time < 500ms': (r) => r.timings.duration < 500,
|
||||
});
|
||||
|
||||
sleep(1);
|
||||
}
|
||||
```
|
||||
|
||||
### Benchmark Testing
|
||||
|
||||
```typescript
|
||||
import { performance } from 'perf_hooks';
|
||||
|
||||
describe('Performance Benchmarks', () => {
|
||||
test('database query completes in under 100ms', async () => {
|
||||
const start = performance.now();
|
||||
|
||||
await database.query('SELECT * FROM users WHERE email = ?', ['test@example.com']);
|
||||
|
||||
const duration = performance.now() - start;
|
||||
expect(duration).toBeLessThan(100);
|
||||
});
|
||||
|
||||
test('API endpoint responds in under 200ms', async () => {
|
||||
const start = performance.now();
|
||||
|
||||
await request(app).get('/api/users/123');
|
||||
|
||||
const duration = performance.now() - start;
|
||||
expect(duration).toBeLessThan(200);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
## Flaky Test Prevention
|
||||
|
||||
### Common Causes of Flaky Tests
|
||||
|
||||
**1. Race Conditions**:
|
||||
```typescript
|
||||
// ❌ BAD - Race condition
|
||||
test('displays data', async () => {
|
||||
fetchData();
|
||||
expect(screen.getByText('Data loaded')).toBeInTheDocument();
|
||||
// Fails intermittently if fetchData takes longer than expected
|
||||
});
|
||||
|
||||
// ✅ GOOD - Wait for async operation
|
||||
test('displays data', async () => {
|
||||
fetchData();
|
||||
await screen.findByText('Data loaded'); // Waits up to 1 second
|
||||
});
|
||||
```
|
||||
|
||||
**2. Time Dependencies**:
|
||||
```typescript
|
||||
// ❌ BAD - Depends on current time
|
||||
test('shows message for new users', () => {
|
||||
const user = { createdAt: new Date() };
|
||||
expect(isNewUser(user)).toBe(true);
|
||||
// Fails if test runs slowly
|
||||
});
|
||||
|
||||
// ✅ GOOD - Mock time
|
||||
test('shows message for new users', () => {
|
||||
jest.useFakeTimers();
|
||||
jest.setSystemTime(new Date('2025-10-16'));
|
||||
|
||||
const user = { createdAt: new Date('2025-10-15') };
|
||||
expect(isNewUser(user)).toBe(true);
|
||||
|
||||
jest.useRealTimers();
|
||||
});
|
||||
```
|
||||
|
||||
**3. Shared State**:
|
||||
```typescript
|
||||
// ❌ BAD - Tests share state
|
||||
let counter = 0;
|
||||
|
||||
test('increments counter', () => {
|
||||
counter++;
|
||||
expect(counter).toBe(1);
|
||||
});
|
||||
|
||||
test('increments counter again', () => {
|
||||
counter++;
|
||||
expect(counter).toBe(1); // Fails if first test ran
|
||||
});
|
||||
|
||||
// ✅ GOOD - Isolated state
|
||||
test('increments counter', () => {
|
||||
const counter = new Counter();
|
||||
counter.increment();
|
||||
expect(counter.value).toBe(1);
|
||||
});
|
||||
```
|
||||
|
||||
### Flaky Test Best Practices
|
||||
|
||||
1. **Always clean up after tests**:
|
||||
```typescript
|
||||
afterEach(async () => {
|
||||
await database.truncate();
|
||||
jest.clearAllMocks();
|
||||
jest.useRealTimers();
|
||||
});
|
||||
```
|
||||
|
||||
2. **Use explicit waits, not delays**:
|
||||
```typescript
|
||||
// ❌ BAD
|
||||
await sleep(1000);
|
||||
|
||||
// ✅ GOOD
|
||||
await waitFor(() => expect(element).toBeInTheDocument());
|
||||
```
|
||||
|
||||
3. **Isolate test data**:
|
||||
```typescript
|
||||
test('creates user', async () => {
|
||||
const uniqueEmail = `test-${Date.now()}@example.com`;
|
||||
const user = await createUser({ email: uniqueEmail });
|
||||
expect(user.email).toBe(uniqueEmail);
|
||||
});
|
||||
```
|
||||
|
||||
## Test Data Management
|
||||
|
||||
### Test Fixtures
|
||||
|
||||
```typescript
|
||||
// fixtures/users.ts
|
||||
export const testUsers = {
|
||||
admin: {
|
||||
email: 'admin@example.com',
|
||||
password: 'AdminPass123',
|
||||
role: 'admin',
|
||||
},
|
||||
regular: {
|
||||
email: 'user@example.com',
|
||||
password: 'UserPass123',
|
||||
role: 'user',
|
||||
},
|
||||
};
|
||||
|
||||
// Usage in tests
|
||||
import { testUsers } from './fixtures/users';
|
||||
|
||||
test('admin can delete users', async () => {
|
||||
const admin = await createUser(testUsers.admin);
|
||||
// Test admin functionality
|
||||
});
|
||||
```
|
||||
|
||||
### Factory Pattern
|
||||
|
||||
```typescript
|
||||
class UserFactory {
|
||||
static create(overrides = {}) {
|
||||
return {
|
||||
id: faker.datatype.uuid(),
|
||||
email: faker.internet.email(),
|
||||
name: faker.name.fullName(),
|
||||
createdAt: new Date(),
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
static createMany(count: number, overrides = {}) {
|
||||
return Array.from({ length: count }, () => this.create(overrides));
|
||||
}
|
||||
}
|
||||
|
||||
// Usage
|
||||
test('displays user list', () => {
|
||||
const users = UserFactory.createMany(5);
|
||||
render(<UserList users={users} />);
|
||||
expect(screen.getAllByRole('listitem')).toHaveLength(5);
|
||||
});
|
||||
```
|
||||
|
||||
### Database Seeding
|
||||
|
||||
```typescript
|
||||
// seeds/test-seed.ts
|
||||
export async function seedTestDatabase() {
|
||||
// Create admin user
|
||||
const admin = await prisma.user.create({
|
||||
data: { email: 'admin@test.com', role: 'admin' },
|
||||
});
|
||||
|
||||
// Create test products
|
||||
const products = await Promise.all([
|
||||
prisma.product.create({ data: { name: 'Product 1', price: 10 } }),
|
||||
prisma.product.create({ data: { name: 'Product 2', price: 20 } }),
|
||||
]);
|
||||
|
||||
return { admin, products };
|
||||
}
|
||||
|
||||
// Usage
|
||||
beforeEach(async () => {
|
||||
await prisma.$executeRaw`TRUNCATE TABLE users CASCADE`;
|
||||
const { admin, products } = await seedTestDatabase();
|
||||
});
|
||||
```
|
||||
|
||||
## CI/CD Integration
|
||||
|
||||
### GitHub Actions Configuration
|
||||
|
||||
```yaml
|
||||
# .github/workflows/test.yml
|
||||
name: Tests
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main, develop]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:15
|
||||
env:
|
||||
POSTGRES_PASSWORD: postgres
|
||||
options: >-
|
||||
--health-cmd pg_isready
|
||||
--health-interval 10s
|
||||
--health-timeout 5s
|
||||
--health-retries 5
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '18'
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Run linter
|
||||
run: npm run lint
|
||||
|
||||
- name: Run type check
|
||||
run: npm run type-check
|
||||
|
||||
- name: Run unit tests
|
||||
run: npm run test:unit
|
||||
|
||||
- name: Run integration tests
|
||||
run: npm run test:integration
|
||||
env:
|
||||
DATABASE_URL: postgresql://postgres:postgres@localhost:5432/test
|
||||
|
||||
- name: Run E2E tests
|
||||
run: npm run test:e2e
|
||||
|
||||
- name: Upload coverage
|
||||
uses: codecov/codecov-action@v3
|
||||
with:
|
||||
files: ./coverage/coverage-final.json
|
||||
fail_ci_if_error: true
|
||||
```
|
||||
|
||||
### Test Scripts Organization
|
||||
|
||||
```json
|
||||
// package.json
|
||||
{
|
||||
"scripts": {
|
||||
"test": "npm run test:unit && npm run test:integration && npm run test:e2e",
|
||||
"test:unit": "jest --testPathPattern=\\.test\\.ts$",
|
||||
"test:integration": "jest --testPathPattern=\\.integration\\.ts$",
|
||||
"test:e2e": "playwright test",
|
||||
"test:watch": "jest --watch",
|
||||
"test:coverage": "jest --coverage",
|
||||
"test:ci": "jest --ci --coverage --maxWorkers=2"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Test Performance in CI
|
||||
|
||||
**Parallel execution**:
|
||||
```yaml
|
||||
jobs:
|
||||
test:
|
||||
strategy:
|
||||
matrix:
|
||||
shard: [1, 2, 3, 4]
|
||||
steps:
|
||||
- run: npm test -- --shard=${{ matrix.shard }}/4
|
||||
```
|
||||
|
||||
**Cache dependencies**:
|
||||
```yaml
|
||||
- uses: actions/cache@v3
|
||||
with:
|
||||
path: ~/.npm
|
||||
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
|
||||
```
|
||||
|
||||
## Test Organization
|
||||
|
||||
### File Structure
|
||||
|
||||
```
|
||||
tests/
|
||||
├── unit/ # Fast, isolated tests
|
||||
│ ├── services/
|
||||
│ │ ├── user-service.test.ts
|
||||
│ │ └── order-service.test.ts
|
||||
│ └── utils/
|
||||
│ ├── validator.test.ts
|
||||
│ └── formatter.test.ts
|
||||
├── integration/ # Database, API tests
|
||||
│ ├── api/
|
||||
│ │ ├── users.integration.ts
|
||||
│ │ └── orders.integration.ts
|
||||
│ └── database/
|
||||
│ └── repository.integration.ts
|
||||
├── e2e/ # End-to-end tests
|
||||
│ ├── auth.spec.ts
|
||||
│ ├── checkout.spec.ts
|
||||
│ └── profile.spec.ts
|
||||
├── fixtures/ # Test data
|
||||
│ ├── users.ts
|
||||
│ └── products.ts
|
||||
└── helpers/ # Test utilities
|
||||
├── setup.ts
|
||||
└── factories.ts
|
||||
```
|
||||
|
||||
### Test Naming Conventions
|
||||
|
||||
```typescript
|
||||
// Pattern: describe('Component/Function', () => test('should...when...'))
|
||||
|
||||
describe('UserService', () => {
|
||||
describe('register', () => {
|
||||
test('should create user when valid data provided', async () => {
|
||||
// Test implementation
|
||||
});
|
||||
|
||||
test('should throw error when email already exists', async () => {
|
||||
// Test implementation
|
||||
});
|
||||
|
||||
test('should hash password before saving', async () => {
|
||||
// Test implementation
|
||||
});
|
||||
});
|
||||
|
||||
describe('login', () => {
|
||||
test('should return token when credentials are valid', async () => {
|
||||
// Test implementation
|
||||
});
|
||||
|
||||
test('should throw error when password is incorrect', async () => {
|
||||
// Test implementation
|
||||
});
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
## When to Use This Skill
|
||||
|
||||
Use this skill when:
|
||||
- Setting up testing infrastructure
|
||||
- Writing unit, integration, or E2E tests
|
||||
- Implementing TDD methodology
|
||||
- Reviewing test coverage
|
||||
- Debugging flaky tests
|
||||
- Optimizing test performance
|
||||
- Configuring CI/CD pipelines
|
||||
- Establishing testing standards
|
||||
- Training team on testing practices
|
||||
- Improving code quality through testing
|
||||
|
||||
---
|
||||
|
||||
**Remember**: Good tests give you confidence to refactor, catch bugs early, and serve as living documentation. Invest in your test suite and it will pay dividends throughout the project lifecycle.
|
||||
Reference in New Issue
Block a user