@champpaba/claude-agent-kit 2.7.0 → 2.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/CLAUDE.md +49 -0
- package/.claude/commands/csetup.md +364 -38
- package/.claude/commands/cview.md +364 -364
- package/.claude/contexts/design/accessibility.md +611 -611
- package/.claude/contexts/design/layout.md +400 -400
- package/.claude/contexts/design/responsive.md +551 -551
- package/.claude/contexts/design/shadows.md +522 -522
- package/.claude/contexts/design/typography.md +465 -465
- package/.claude/contexts/domain/README.md +164 -164
- package/.claude/contexts/patterns/agent-coordination.md +388 -388
- package/.claude/contexts/patterns/development-principles.md +513 -513
- package/.claude/contexts/patterns/error-handling.md +478 -478
- package/.claude/contexts/patterns/logging.md +424 -424
- package/.claude/contexts/patterns/tdd-classification.md +516 -516
- package/.claude/contexts/patterns/testing.md +413 -413
- package/.claude/lib/tdd-classifier.md +345 -345
- package/.claude/lib/validation-gates.md +484 -484
- package/.claude/settings.local.json +42 -42
- package/.claude/templates/context-template.md +45 -45
- package/.claude/templates/flags-template.json +42 -42
- package/.claude/templates/phases-sections/accessibility-test.md +17 -17
- package/.claude/templates/phases-sections/api-design.md +37 -37
- package/.claude/templates/phases-sections/backend-tests.md +16 -16
- package/.claude/templates/phases-sections/backend.md +37 -37
- package/.claude/templates/phases-sections/business-logic-validation.md +16 -16
- package/.claude/templates/phases-sections/component-tests.md +17 -17
- package/.claude/templates/phases-sections/contract-backend.md +16 -16
- package/.claude/templates/phases-sections/contract-frontend.md +16 -16
- package/.claude/templates/phases-sections/database.md +35 -35
- package/.claude/templates/phases-sections/e2e-tests.md +16 -16
- package/.claude/templates/phases-sections/fix-implementation.md +17 -17
- package/.claude/templates/phases-sections/frontend-integration.md +18 -18
- package/.claude/templates/phases-sections/manual-flow-test.md +15 -15
- package/.claude/templates/phases-sections/manual-ux-test.md +16 -16
- package/.claude/templates/phases-sections/refactor-implementation.md +17 -17
- package/.claude/templates/phases-sections/refactor.md +16 -16
- package/.claude/templates/phases-sections/regression-tests.md +15 -15
- package/.claude/templates/phases-sections/responsive-test.md +16 -16
- package/.claude/templates/phases-sections/script-implementation.md +43 -43
- package/.claude/templates/phases-sections/test-coverage.md +16 -16
- package/.claude/templates/phases-sections/user-approval.md +14 -14
- package/LICENSE +21 -21
- package/README.md +25 -0
- package/package.json +8 -4
|
@@ -1,513 +1,513 @@
|
|
|
1
|
-
# Development Principles
|
|
2
|
-
|
|
3
|
-
**Core Philosophy:** Build software that is simple, maintainable, observable, and resilient.
|
|
4
|
-
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
## KISS (Keep It Simple, Stupid)
|
|
8
|
-
|
|
9
|
-
Simplicity should be a key goal in design. Choose straightforward solutions over complex ones.
|
|
10
|
-
|
|
11
|
-
### Examples
|
|
12
|
-
|
|
13
|
-
**❌ Complex:**
|
|
14
|
-
```typescript
|
|
15
|
-
// Over-engineered abstraction
|
|
16
|
-
class UserRepositoryFactory {
|
|
17
|
-
createRepository(type: 'sql' | 'nosql') {
|
|
18
|
-
return type === 'sql'
|
|
19
|
-
? new SQLUserRepository(new DatabaseConnection())
|
|
20
|
-
: new NoSQLUserRepository(new MongoConnection())
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
**✅ Simple:**
|
|
26
|
-
```typescript
|
|
27
|
-
// Direct, readable
|
|
28
|
-
import { prisma } from '@/lib/db'
|
|
29
|
-
|
|
30
|
-
export async function getUser(id: string) {
|
|
31
|
-
return prisma.user.findUnique({ where: { id } })
|
|
32
|
-
}
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
---
|
|
36
|
-
|
|
37
|
-
## YAGNI (You Aren't Gonna Need It)
|
|
38
|
-
|
|
39
|
-
Avoid building functionality on speculation. Implement features only when needed.
|
|
40
|
-
|
|
41
|
-
### When to Apply
|
|
42
|
-
|
|
43
|
-
**❌ Don't Build:**
|
|
44
|
-
- "We might need this someday"
|
|
45
|
-
- Premature optimization
|
|
46
|
-
- Unused abstractions
|
|
47
|
-
- Speculative features
|
|
48
|
-
|
|
49
|
-
**✅ Build Only:**
|
|
50
|
-
- Features with clear requirements
|
|
51
|
-
- Immediate user needs
|
|
52
|
-
- Proven performance bottlenecks
|
|
53
|
-
|
|
54
|
-
### Example
|
|
55
|
-
|
|
56
|
-
```typescript
|
|
57
|
-
// ❌ YAGNI Violation - Building for future maybe-needs
|
|
58
|
-
interface User {
|
|
59
|
-
id: string
|
|
60
|
-
email: string
|
|
61
|
-
name: string
|
|
62
|
-
preferences?: UserPreferences // Not needed yet
|
|
63
|
-
settings?: UserSettings // Not needed yet
|
|
64
|
-
metadata?: Record<string, unknown> // "Just in case"
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// ✅ YAGNI Compliant - Build what you need now
|
|
68
|
-
interface User {
|
|
69
|
-
id: string
|
|
70
|
-
email: string
|
|
71
|
-
name: string
|
|
72
|
-
}
|
|
73
|
-
// Add fields when requirements are clear
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
---
|
|
77
|
-
|
|
78
|
-
## SOLID Principles
|
|
79
|
-
|
|
80
|
-
### 1. Single Responsibility Principle (SRP)
|
|
81
|
-
|
|
82
|
-
Each module/class/function should have ONE reason to change.
|
|
83
|
-
|
|
84
|
-
**❌ Violates SRP:**
|
|
85
|
-
```typescript
|
|
86
|
-
class UserService {
|
|
87
|
-
createUser(data: UserData) { /* ... */ }
|
|
88
|
-
sendWelcomeEmail(user: User) { /* ... */ }
|
|
89
|
-
logActivity(action: string) { /* ... */ }
|
|
90
|
-
validateEmail(email: string) { /* ... */ }
|
|
91
|
-
}
|
|
92
|
-
```
|
|
93
|
-
|
|
94
|
-
**✅ Follows SRP:**
|
|
95
|
-
```typescript
|
|
96
|
-
// lib/services/user-service.ts
|
|
97
|
-
class UserService {
|
|
98
|
-
createUser(data: UserData) { /* ... */ }
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// lib/services/email-service.ts
|
|
102
|
-
class EmailService {
|
|
103
|
-
sendWelcomeEmail(user: User) { /* ... */ }
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// lib/logger.ts
|
|
107
|
-
export const logger = { /* ... */ }
|
|
108
|
-
|
|
109
|
-
// lib/validators.ts
|
|
110
|
-
export function validateEmail(email: string) { /* ... */ }
|
|
111
|
-
```
|
|
112
|
-
|
|
113
|
-
---
|
|
114
|
-
|
|
115
|
-
### 2. Open/Closed Principle (OCP)
|
|
116
|
-
|
|
117
|
-
Software entities should be **open for extension** but **closed for modification**.
|
|
118
|
-
|
|
119
|
-
**❌ Violates OCP:**
|
|
120
|
-
```typescript
|
|
121
|
-
function calculateDiscount(user: User, order: Order) {
|
|
122
|
-
if (user.type === 'regular') {
|
|
123
|
-
return order.total * 0.05
|
|
124
|
-
} else if (user.type === 'premium') {
|
|
125
|
-
return order.total * 0.10
|
|
126
|
-
} else if (user.type === 'vip') {
|
|
127
|
-
return order.total * 0.20
|
|
128
|
-
}
|
|
129
|
-
// Adding new user types requires modifying this function
|
|
130
|
-
}
|
|
131
|
-
```
|
|
132
|
-
|
|
133
|
-
**✅ Follows OCP:**
|
|
134
|
-
```typescript
|
|
135
|
-
interface DiscountStrategy {
|
|
136
|
-
calculate(order: Order): number
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
class RegularDiscount implements DiscountStrategy {
|
|
140
|
-
calculate(order: Order) { return order.total * 0.05 }
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
class PremiumDiscount implements DiscountStrategy {
|
|
144
|
-
calculate(order: Order) { return order.total * 0.10 }
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
class VIPDiscount implements DiscountStrategy {
|
|
148
|
-
calculate(order: Order) { return order.total * 0.20 }
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
// Adding new discount types = create new class (no modification needed)
|
|
152
|
-
function calculateDiscount(strategy: DiscountStrategy, order: Order) {
|
|
153
|
-
return strategy.calculate(order)
|
|
154
|
-
}
|
|
155
|
-
```
|
|
156
|
-
|
|
157
|
-
---
|
|
158
|
-
|
|
159
|
-
### 3. Liskov Substitution Principle (LSP)
|
|
160
|
-
|
|
161
|
-
Subtypes must be substitutable for their base types without altering correctness.
|
|
162
|
-
|
|
163
|
-
**❌ Violates LSP:**
|
|
164
|
-
```typescript
|
|
165
|
-
class Bird {
|
|
166
|
-
fly() { console.log('Flying') }
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
class Penguin extends Bird {
|
|
170
|
-
fly() { throw new Error('Penguins cannot fly') } // Breaks substitution
|
|
171
|
-
}
|
|
172
|
-
```
|
|
173
|
-
|
|
174
|
-
**✅ Follows LSP:**
|
|
175
|
-
```typescript
|
|
176
|
-
interface Bird {
|
|
177
|
-
move(): void
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
class FlyingBird implements Bird {
|
|
181
|
-
move() { console.log('Flying') }
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
class Penguin implements Bird {
|
|
185
|
-
move() { console.log('Swimming') }
|
|
186
|
-
}
|
|
187
|
-
```
|
|
188
|
-
|
|
189
|
-
---
|
|
190
|
-
|
|
191
|
-
### 4. Interface Segregation Principle (ISP)
|
|
192
|
-
|
|
193
|
-
Clients should not depend on interfaces they don't use.
|
|
194
|
-
|
|
195
|
-
**❌ Violates ISP:**
|
|
196
|
-
```typescript
|
|
197
|
-
interface Worker {
|
|
198
|
-
work(): void
|
|
199
|
-
eat(): void
|
|
200
|
-
sleep(): void
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
class Robot implements Worker {
|
|
204
|
-
work() { /* ... */ }
|
|
205
|
-
eat() { throw new Error('Robots do not eat') } // Forced to implement
|
|
206
|
-
sleep() { throw new Error('Robots do not sleep') } // Forced to implement
|
|
207
|
-
}
|
|
208
|
-
```
|
|
209
|
-
|
|
210
|
-
**✅ Follows ISP:**
|
|
211
|
-
```typescript
|
|
212
|
-
interface Workable {
|
|
213
|
-
work(): void
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
interface Eatable {
|
|
217
|
-
eat(): void
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
interface Sleepable {
|
|
221
|
-
sleep(): void
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
class Human implements Workable, Eatable, Sleepable {
|
|
225
|
-
work() { /* ... */ }
|
|
226
|
-
eat() { /* ... */ }
|
|
227
|
-
sleep() { /* ... */ }
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
class Robot implements Workable {
|
|
231
|
-
work() { /* ... */ }
|
|
232
|
-
}
|
|
233
|
-
```
|
|
234
|
-
|
|
235
|
-
---
|
|
236
|
-
|
|
237
|
-
### 5. Dependency Inversion Principle (DIP)
|
|
238
|
-
|
|
239
|
-
High-level modules should not depend on low-level modules. Both should depend on abstractions.
|
|
240
|
-
|
|
241
|
-
**❌ Violates DIP:**
|
|
242
|
-
```typescript
|
|
243
|
-
class MySQLDatabase {
|
|
244
|
-
query(sql: string) { /* ... */ }
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
class UserService {
|
|
248
|
-
private db = new MySQLDatabase() // Direct dependency on low-level module
|
|
249
|
-
|
|
250
|
-
getUser(id: string) {
|
|
251
|
-
return this.db.query(`SELECT * FROM users WHERE id = ${id}`)
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
```
|
|
255
|
-
|
|
256
|
-
**✅ Follows DIP:**
|
|
257
|
-
```typescript
|
|
258
|
-
interface Database {
|
|
259
|
-
query(sql: string): Promise<unknown>
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
class MySQLDatabase implements Database {
|
|
263
|
-
query(sql: string) { /* ... */ }
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
class PostgreSQLDatabase implements Database {
|
|
267
|
-
query(sql: string) { /* ... */ }
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
class UserService {
|
|
271
|
-
constructor(private db: Database) {} // Depends on abstraction
|
|
272
|
-
|
|
273
|
-
getUser(id: string) {
|
|
274
|
-
return this.db.query(`SELECT * FROM users WHERE id = ${id}`)
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
// Usage
|
|
279
|
-
const userService = new UserService(new MySQLDatabase())
|
|
280
|
-
// Easy to swap: new UserService(new PostgreSQLDatabase())
|
|
281
|
-
```
|
|
282
|
-
|
|
283
|
-
---
|
|
284
|
-
|
|
285
|
-
## Fail Fast
|
|
286
|
-
|
|
287
|
-
Detect errors early and raise exceptions immediately when issues occur.
|
|
288
|
-
|
|
289
|
-
### When to Fail Fast
|
|
290
|
-
|
|
291
|
-
**✅ Fail immediately on:**
|
|
292
|
-
- Invalid input
|
|
293
|
-
- Missing required configuration
|
|
294
|
-
- Broken dependencies
|
|
295
|
-
- Database connection failures
|
|
296
|
-
- Authentication failures
|
|
297
|
-
|
|
298
|
-
**❌ Don't fail fast on:**
|
|
299
|
-
- Transient network errors (retry instead)
|
|
300
|
-
- User input errors (validate gracefully)
|
|
301
|
-
- Optional features
|
|
302
|
-
|
|
303
|
-
### Examples
|
|
304
|
-
|
|
305
|
-
**❌ Silent Failure:**
|
|
306
|
-
```typescript
|
|
307
|
-
function divide(a: number, b: number) {
|
|
308
|
-
if (b === 0) {
|
|
309
|
-
return 0 // Silent failure - wrong!
|
|
310
|
-
}
|
|
311
|
-
return a / b
|
|
312
|
-
}
|
|
313
|
-
```
|
|
314
|
-
|
|
315
|
-
**✅ Fail Fast:**
|
|
316
|
-
```typescript
|
|
317
|
-
function divide(a: number, b: number) {
|
|
318
|
-
if (b === 0) {
|
|
319
|
-
throw new Error('Division by zero') // Immediate failure
|
|
320
|
-
}
|
|
321
|
-
return a / b
|
|
322
|
-
}
|
|
323
|
-
```
|
|
324
|
-
|
|
325
|
-
**✅ Fail Fast with Validation:**
|
|
326
|
-
```typescript
|
|
327
|
-
import { z } from 'zod'
|
|
328
|
-
|
|
329
|
-
const userSchema = z.object({
|
|
330
|
-
email: z.string().email(),
|
|
331
|
-
age: z.number().min(18)
|
|
332
|
-
})
|
|
333
|
-
|
|
334
|
-
export async function createUser(data: unknown) {
|
|
335
|
-
// Fail fast if data is invalid
|
|
336
|
-
const validated = userSchema.parse(data) // Throws on invalid input
|
|
337
|
-
|
|
338
|
-
// Continue with valid data
|
|
339
|
-
return prisma.user.create({ data: validated })
|
|
340
|
-
}
|
|
341
|
-
```
|
|
342
|
-
|
|
343
|
-
---
|
|
344
|
-
|
|
345
|
-
## Observability First
|
|
346
|
-
|
|
347
|
-
Every significant action must be observable through logging.
|
|
348
|
-
|
|
349
|
-
**Critical Rule:** If an action happens without logs, it's invisible in production.
|
|
350
|
-
|
|
351
|
-
### What to Log
|
|
352
|
-
|
|
353
|
-
| Event Type | Log Level |
|
|
354
|
-
|------------|-----------|
|
|
355
|
-
| API Route Entry/Exit | INFO |
|
|
356
|
-
| Database Operations | INFO |
|
|
357
|
-
| External API Calls | INFO |
|
|
358
|
-
| User Actions | INFO |
|
|
359
|
-
| Errors & Exceptions | ERROR |
|
|
360
|
-
| State Changes | DEBUG |
|
|
361
|
-
|
|
362
|
-
**See `patterns/logging.md` for complete patterns.**
|
|
363
|
-
|
|
364
|
-
---
|
|
365
|
-
|
|
366
|
-
## DRY (Don't Repeat Yourself)
|
|
367
|
-
|
|
368
|
-
Every piece of knowledge should have a single, authoritative representation.
|
|
369
|
-
|
|
370
|
-
**❌ Violates DRY:**
|
|
371
|
-
```typescript
|
|
372
|
-
// File 1
|
|
373
|
-
const MAX_FILE_SIZE = 5 * 1024 * 1024 // 5MB
|
|
374
|
-
|
|
375
|
-
// File 2
|
|
376
|
-
const MAX_FILE_SIZE = 5 * 1024 * 1024 // 5MB (duplicate!)
|
|
377
|
-
|
|
378
|
-
// File 3
|
|
379
|
-
if (file.size > 5 * 1024 * 1024) { /* ... */ } // Hardcoded (duplicate!)
|
|
380
|
-
```
|
|
381
|
-
|
|
382
|
-
**✅ Follows DRY:**
|
|
383
|
-
```typescript
|
|
384
|
-
// lib/constants.ts
|
|
385
|
-
export const MAX_FILE_SIZE = 5 * 1024 * 1024 // Single source of truth
|
|
386
|
-
|
|
387
|
-
// File 1
|
|
388
|
-
import { MAX_FILE_SIZE } from '@/lib/constants'
|
|
389
|
-
|
|
390
|
-
// File 2
|
|
391
|
-
import { MAX_FILE_SIZE } from '@/lib/constants'
|
|
392
|
-
|
|
393
|
-
// File 3
|
|
394
|
-
import { MAX_FILE_SIZE } from '@/lib/constants'
|
|
395
|
-
if (file.size > MAX_FILE_SIZE) { /* ... */ }
|
|
396
|
-
```
|
|
397
|
-
|
|
398
|
-
---
|
|
399
|
-
|
|
400
|
-
## Separation of Concerns
|
|
401
|
-
|
|
402
|
-
Different concerns should be handled by different modules.
|
|
403
|
-
|
|
404
|
-
**Example: Next.js API Route**
|
|
405
|
-
|
|
406
|
-
**❌ Mixed Concerns:**
|
|
407
|
-
```typescript
|
|
408
|
-
export async function POST(request: NextRequest) {
|
|
409
|
-
const body = await request.json()
|
|
410
|
-
|
|
411
|
-
// Validation mixed with business logic
|
|
412
|
-
if (!body.email || !body.email.includes('@')) {
|
|
413
|
-
return NextResponse.json({ error: 'Invalid email' }, { status: 400 })
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
// Database logic mixed with API logic
|
|
417
|
-
const user = await prisma.user.create({
|
|
418
|
-
data: { email: body.email, name: body.name }
|
|
419
|
-
})
|
|
420
|
-
|
|
421
|
-
// Email sending mixed with everything else
|
|
422
|
-
await fetch('https://api.sendgrid.com/send', {
|
|
423
|
-
method: 'POST',
|
|
424
|
-
body: JSON.stringify({ to: user.email, subject: 'Welcome' })
|
|
425
|
-
})
|
|
426
|
-
|
|
427
|
-
return NextResponse.json(user)
|
|
428
|
-
}
|
|
429
|
-
```
|
|
430
|
-
|
|
431
|
-
**✅ Separated Concerns:**
|
|
432
|
-
```typescript
|
|
433
|
-
// app/api/users/route.ts (API Layer)
|
|
434
|
-
export async function POST(request: NextRequest) {
|
|
435
|
-
const body = await request.json()
|
|
436
|
-
const validated = validateUserInput(body) // Validation layer
|
|
437
|
-
const user = await userService.createUser(validated) // Business layer
|
|
438
|
-
await emailService.sendWelcome(user) // Email layer
|
|
439
|
-
return NextResponse.json(user)
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
// lib/validators/user.ts (Validation Layer)
|
|
443
|
-
export function validateUserInput(data: unknown) {
|
|
444
|
-
return userSchema.parse(data)
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
// lib/services/user-service.ts (Business Layer)
|
|
448
|
-
export const userService = {
|
|
449
|
-
async createUser(data: ValidatedUserData) {
|
|
450
|
-
return prisma.user.create({ data })
|
|
451
|
-
}
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
// lib/services/email-service.ts (Email Layer)
|
|
455
|
-
export const emailService = {
|
|
456
|
-
async sendWelcome(user: User) {
|
|
457
|
-
await sendEmail({ to: user.email, subject: 'Welcome' })
|
|
458
|
-
}
|
|
459
|
-
}
|
|
460
|
-
```
|
|
461
|
-
|
|
462
|
-
---
|
|
463
|
-
|
|
464
|
-
## Principle of Least Surprise
|
|
465
|
-
|
|
466
|
-
Code should behave in a way that minimizes surprise for other developers.
|
|
467
|
-
|
|
468
|
-
**❌ Surprising:**
|
|
469
|
-
```typescript
|
|
470
|
-
// Function name suggests it only reads, but it also writes
|
|
471
|
-
function getUser(id: string) {
|
|
472
|
-
const user = db.findUser(id)
|
|
473
|
-
db.updateLastAccessed(id) // Surprise! Side effect!
|
|
474
|
-
return user
|
|
475
|
-
}
|
|
476
|
-
```
|
|
477
|
-
|
|
478
|
-
**✅ Expected:**
|
|
479
|
-
```typescript
|
|
480
|
-
// Clear function names, no surprises
|
|
481
|
-
function getUser(id: string) {
|
|
482
|
-
return db.findUser(id)
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
function getUserAndUpdateAccess(id: string) {
|
|
486
|
-
const user = db.findUser(id)
|
|
487
|
-
db.updateLastAccessed(id) // Expected from function name
|
|
488
|
-
return user
|
|
489
|
-
}
|
|
490
|
-
```
|
|
491
|
-
|
|
492
|
-
---
|
|
493
|
-
|
|
494
|
-
## Quick Reference
|
|
495
|
-
|
|
496
|
-
| Principle | Summary |
|
|
497
|
-
|-----------|---------|
|
|
498
|
-
| **KISS** | Choose simple solutions over complex ones |
|
|
499
|
-
| **YAGNI** | Build only what you need now |
|
|
500
|
-
| **SRP** | One responsibility per module |
|
|
501
|
-
| **OCP** | Open for extension, closed for modification |
|
|
502
|
-
| **LSP** | Subtypes must be substitutable |
|
|
503
|
-
| **ISP** | Don't force clients to depend on unused methods |
|
|
504
|
-
| **DIP** | Depend on abstractions, not concretions |
|
|
505
|
-
| **Fail Fast** | Detect and raise errors immediately |
|
|
506
|
-
| **Observability** | Log everything that matters |
|
|
507
|
-
| **DRY** | Single source of truth for all knowledge |
|
|
508
|
-
| **Separation** | Different concerns in different modules |
|
|
509
|
-
| **Least Surprise** | Code should behave as expected |
|
|
510
|
-
|
|
511
|
-
---
|
|
512
|
-
|
|
513
|
-
**💡 Remember:** Good principles lead to maintainable code!
|
|
1
|
+
# Development Principles
|
|
2
|
+
|
|
3
|
+
**Core Philosophy:** Build software that is simple, maintainable, observable, and resilient.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## KISS (Keep It Simple, Stupid)
|
|
8
|
+
|
|
9
|
+
Simplicity should be a key goal in design. Choose straightforward solutions over complex ones.
|
|
10
|
+
|
|
11
|
+
### Examples
|
|
12
|
+
|
|
13
|
+
**❌ Complex:**
|
|
14
|
+
```typescript
|
|
15
|
+
// Over-engineered abstraction
|
|
16
|
+
class UserRepositoryFactory {
|
|
17
|
+
createRepository(type: 'sql' | 'nosql') {
|
|
18
|
+
return type === 'sql'
|
|
19
|
+
? new SQLUserRepository(new DatabaseConnection())
|
|
20
|
+
: new NoSQLUserRepository(new MongoConnection())
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
**✅ Simple:**
|
|
26
|
+
```typescript
|
|
27
|
+
// Direct, readable
|
|
28
|
+
import { prisma } from '@/lib/db'
|
|
29
|
+
|
|
30
|
+
export async function getUser(id: string) {
|
|
31
|
+
return prisma.user.findUnique({ where: { id } })
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## YAGNI (You Aren't Gonna Need It)
|
|
38
|
+
|
|
39
|
+
Avoid building functionality on speculation. Implement features only when needed.
|
|
40
|
+
|
|
41
|
+
### When to Apply
|
|
42
|
+
|
|
43
|
+
**❌ Don't Build:**
|
|
44
|
+
- "We might need this someday"
|
|
45
|
+
- Premature optimization
|
|
46
|
+
- Unused abstractions
|
|
47
|
+
- Speculative features
|
|
48
|
+
|
|
49
|
+
**✅ Build Only:**
|
|
50
|
+
- Features with clear requirements
|
|
51
|
+
- Immediate user needs
|
|
52
|
+
- Proven performance bottlenecks
|
|
53
|
+
|
|
54
|
+
### Example
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
// ❌ YAGNI Violation - Building for future maybe-needs
|
|
58
|
+
interface User {
|
|
59
|
+
id: string
|
|
60
|
+
email: string
|
|
61
|
+
name: string
|
|
62
|
+
preferences?: UserPreferences // Not needed yet
|
|
63
|
+
settings?: UserSettings // Not needed yet
|
|
64
|
+
metadata?: Record<string, unknown> // "Just in case"
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// ✅ YAGNI Compliant - Build what you need now
|
|
68
|
+
interface User {
|
|
69
|
+
id: string
|
|
70
|
+
email: string
|
|
71
|
+
name: string
|
|
72
|
+
}
|
|
73
|
+
// Add fields when requirements are clear
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## SOLID Principles
|
|
79
|
+
|
|
80
|
+
### 1. Single Responsibility Principle (SRP)
|
|
81
|
+
|
|
82
|
+
Each module/class/function should have ONE reason to change.
|
|
83
|
+
|
|
84
|
+
**❌ Violates SRP:**
|
|
85
|
+
```typescript
|
|
86
|
+
class UserService {
|
|
87
|
+
createUser(data: UserData) { /* ... */ }
|
|
88
|
+
sendWelcomeEmail(user: User) { /* ... */ }
|
|
89
|
+
logActivity(action: string) { /* ... */ }
|
|
90
|
+
validateEmail(email: string) { /* ... */ }
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
**✅ Follows SRP:**
|
|
95
|
+
```typescript
|
|
96
|
+
// lib/services/user-service.ts
|
|
97
|
+
class UserService {
|
|
98
|
+
createUser(data: UserData) { /* ... */ }
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// lib/services/email-service.ts
|
|
102
|
+
class EmailService {
|
|
103
|
+
sendWelcomeEmail(user: User) { /* ... */ }
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// lib/logger.ts
|
|
107
|
+
export const logger = { /* ... */ }
|
|
108
|
+
|
|
109
|
+
// lib/validators.ts
|
|
110
|
+
export function validateEmail(email: string) { /* ... */ }
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
### 2. Open/Closed Principle (OCP)
|
|
116
|
+
|
|
117
|
+
Software entities should be **open for extension** but **closed for modification**.
|
|
118
|
+
|
|
119
|
+
**❌ Violates OCP:**
|
|
120
|
+
```typescript
|
|
121
|
+
function calculateDiscount(user: User, order: Order) {
|
|
122
|
+
if (user.type === 'regular') {
|
|
123
|
+
return order.total * 0.05
|
|
124
|
+
} else if (user.type === 'premium') {
|
|
125
|
+
return order.total * 0.10
|
|
126
|
+
} else if (user.type === 'vip') {
|
|
127
|
+
return order.total * 0.20
|
|
128
|
+
}
|
|
129
|
+
// Adding new user types requires modifying this function
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
**✅ Follows OCP:**
|
|
134
|
+
```typescript
|
|
135
|
+
interface DiscountStrategy {
|
|
136
|
+
calculate(order: Order): number
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
class RegularDiscount implements DiscountStrategy {
|
|
140
|
+
calculate(order: Order) { return order.total * 0.05 }
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
class PremiumDiscount implements DiscountStrategy {
|
|
144
|
+
calculate(order: Order) { return order.total * 0.10 }
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
class VIPDiscount implements DiscountStrategy {
|
|
148
|
+
calculate(order: Order) { return order.total * 0.20 }
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Adding new discount types = create new class (no modification needed)
|
|
152
|
+
function calculateDiscount(strategy: DiscountStrategy, order: Order) {
|
|
153
|
+
return strategy.calculate(order)
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
### 3. Liskov Substitution Principle (LSP)
|
|
160
|
+
|
|
161
|
+
Subtypes must be substitutable for their base types without altering correctness.
|
|
162
|
+
|
|
163
|
+
**❌ Violates LSP:**
|
|
164
|
+
```typescript
|
|
165
|
+
class Bird {
|
|
166
|
+
fly() { console.log('Flying') }
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
class Penguin extends Bird {
|
|
170
|
+
fly() { throw new Error('Penguins cannot fly') } // Breaks substitution
|
|
171
|
+
}
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
**✅ Follows LSP:**
|
|
175
|
+
```typescript
|
|
176
|
+
interface Bird {
|
|
177
|
+
move(): void
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
class FlyingBird implements Bird {
|
|
181
|
+
move() { console.log('Flying') }
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
class Penguin implements Bird {
|
|
185
|
+
move() { console.log('Swimming') }
|
|
186
|
+
}
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
### 4. Interface Segregation Principle (ISP)
|
|
192
|
+
|
|
193
|
+
Clients should not depend on interfaces they don't use.
|
|
194
|
+
|
|
195
|
+
**❌ Violates ISP:**
|
|
196
|
+
```typescript
|
|
197
|
+
interface Worker {
|
|
198
|
+
work(): void
|
|
199
|
+
eat(): void
|
|
200
|
+
sleep(): void
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
class Robot implements Worker {
|
|
204
|
+
work() { /* ... */ }
|
|
205
|
+
eat() { throw new Error('Robots do not eat') } // Forced to implement
|
|
206
|
+
sleep() { throw new Error('Robots do not sleep') } // Forced to implement
|
|
207
|
+
}
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
**✅ Follows ISP:**
|
|
211
|
+
```typescript
|
|
212
|
+
interface Workable {
|
|
213
|
+
work(): void
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
interface Eatable {
|
|
217
|
+
eat(): void
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
interface Sleepable {
|
|
221
|
+
sleep(): void
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
class Human implements Workable, Eatable, Sleepable {
|
|
225
|
+
work() { /* ... */ }
|
|
226
|
+
eat() { /* ... */ }
|
|
227
|
+
sleep() { /* ... */ }
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
class Robot implements Workable {
|
|
231
|
+
work() { /* ... */ }
|
|
232
|
+
}
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
---
|
|
236
|
+
|
|
237
|
+
### 5. Dependency Inversion Principle (DIP)
|
|
238
|
+
|
|
239
|
+
High-level modules should not depend on low-level modules. Both should depend on abstractions.
|
|
240
|
+
|
|
241
|
+
**❌ Violates DIP:**
|
|
242
|
+
```typescript
|
|
243
|
+
class MySQLDatabase {
|
|
244
|
+
query(sql: string) { /* ... */ }
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
class UserService {
|
|
248
|
+
private db = new MySQLDatabase() // Direct dependency on low-level module
|
|
249
|
+
|
|
250
|
+
getUser(id: string) {
|
|
251
|
+
return this.db.query(`SELECT * FROM users WHERE id = ${id}`)
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
**✅ Follows DIP:**
|
|
257
|
+
```typescript
|
|
258
|
+
interface Database {
|
|
259
|
+
query(sql: string): Promise<unknown>
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
class MySQLDatabase implements Database {
|
|
263
|
+
query(sql: string) { /* ... */ }
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
class PostgreSQLDatabase implements Database {
|
|
267
|
+
query(sql: string) { /* ... */ }
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
class UserService {
|
|
271
|
+
constructor(private db: Database) {} // Depends on abstraction
|
|
272
|
+
|
|
273
|
+
getUser(id: string) {
|
|
274
|
+
return this.db.query(`SELECT * FROM users WHERE id = ${id}`)
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Usage
|
|
279
|
+
const userService = new UserService(new MySQLDatabase())
|
|
280
|
+
// Easy to swap: new UserService(new PostgreSQLDatabase())
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
---
|
|
284
|
+
|
|
285
|
+
## Fail Fast
|
|
286
|
+
|
|
287
|
+
Detect errors early and raise exceptions immediately when issues occur.
|
|
288
|
+
|
|
289
|
+
### When to Fail Fast
|
|
290
|
+
|
|
291
|
+
**✅ Fail immediately on:**
|
|
292
|
+
- Invalid input
|
|
293
|
+
- Missing required configuration
|
|
294
|
+
- Broken dependencies
|
|
295
|
+
- Database connection failures
|
|
296
|
+
- Authentication failures
|
|
297
|
+
|
|
298
|
+
**❌ Don't fail fast on:**
|
|
299
|
+
- Transient network errors (retry instead)
|
|
300
|
+
- User input errors (validate gracefully)
|
|
301
|
+
- Optional features
|
|
302
|
+
|
|
303
|
+
### Examples
|
|
304
|
+
|
|
305
|
+
**❌ Silent Failure:**
|
|
306
|
+
```typescript
|
|
307
|
+
function divide(a: number, b: number) {
|
|
308
|
+
if (b === 0) {
|
|
309
|
+
return 0 // Silent failure - wrong!
|
|
310
|
+
}
|
|
311
|
+
return a / b
|
|
312
|
+
}
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
**✅ Fail Fast:**
|
|
316
|
+
```typescript
|
|
317
|
+
function divide(a: number, b: number) {
|
|
318
|
+
if (b === 0) {
|
|
319
|
+
throw new Error('Division by zero') // Immediate failure
|
|
320
|
+
}
|
|
321
|
+
return a / b
|
|
322
|
+
}
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
**✅ Fail Fast with Validation:**
|
|
326
|
+
```typescript
|
|
327
|
+
import { z } from 'zod'
|
|
328
|
+
|
|
329
|
+
const userSchema = z.object({
|
|
330
|
+
email: z.string().email(),
|
|
331
|
+
age: z.number().min(18)
|
|
332
|
+
})
|
|
333
|
+
|
|
334
|
+
export async function createUser(data: unknown) {
|
|
335
|
+
// Fail fast if data is invalid
|
|
336
|
+
const validated = userSchema.parse(data) // Throws on invalid input
|
|
337
|
+
|
|
338
|
+
// Continue with valid data
|
|
339
|
+
return prisma.user.create({ data: validated })
|
|
340
|
+
}
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
---
|
|
344
|
+
|
|
345
|
+
## Observability First
|
|
346
|
+
|
|
347
|
+
Every significant action must be observable through logging.
|
|
348
|
+
|
|
349
|
+
**Critical Rule:** If an action happens without logs, it's invisible in production.
|
|
350
|
+
|
|
351
|
+
### What to Log
|
|
352
|
+
|
|
353
|
+
| Event Type | Log Level |
|
|
354
|
+
|------------|-----------|
|
|
355
|
+
| API Route Entry/Exit | INFO |
|
|
356
|
+
| Database Operations | INFO |
|
|
357
|
+
| External API Calls | INFO |
|
|
358
|
+
| User Actions | INFO |
|
|
359
|
+
| Errors & Exceptions | ERROR |
|
|
360
|
+
| State Changes | DEBUG |
|
|
361
|
+
|
|
362
|
+
**See `patterns/logging.md` for complete patterns.**
|
|
363
|
+
|
|
364
|
+
---
|
|
365
|
+
|
|
366
|
+
## DRY (Don't Repeat Yourself)
|
|
367
|
+
|
|
368
|
+
Every piece of knowledge should have a single, authoritative representation.
|
|
369
|
+
|
|
370
|
+
**❌ Violates DRY:**
|
|
371
|
+
```typescript
|
|
372
|
+
// File 1
|
|
373
|
+
const MAX_FILE_SIZE = 5 * 1024 * 1024 // 5MB
|
|
374
|
+
|
|
375
|
+
// File 2
|
|
376
|
+
const MAX_FILE_SIZE = 5 * 1024 * 1024 // 5MB (duplicate!)
|
|
377
|
+
|
|
378
|
+
// File 3
|
|
379
|
+
if (file.size > 5 * 1024 * 1024) { /* ... */ } // Hardcoded (duplicate!)
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
**✅ Follows DRY:**
|
|
383
|
+
```typescript
|
|
384
|
+
// lib/constants.ts
|
|
385
|
+
export const MAX_FILE_SIZE = 5 * 1024 * 1024 // Single source of truth
|
|
386
|
+
|
|
387
|
+
// File 1
|
|
388
|
+
import { MAX_FILE_SIZE } from '@/lib/constants'
|
|
389
|
+
|
|
390
|
+
// File 2
|
|
391
|
+
import { MAX_FILE_SIZE } from '@/lib/constants'
|
|
392
|
+
|
|
393
|
+
// File 3
|
|
394
|
+
import { MAX_FILE_SIZE } from '@/lib/constants'
|
|
395
|
+
if (file.size > MAX_FILE_SIZE) { /* ... */ }
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
---
|
|
399
|
+
|
|
400
|
+
## Separation of Concerns
|
|
401
|
+
|
|
402
|
+
Different concerns should be handled by different modules.
|
|
403
|
+
|
|
404
|
+
**Example: Next.js API Route**
|
|
405
|
+
|
|
406
|
+
**❌ Mixed Concerns:**
|
|
407
|
+
```typescript
|
|
408
|
+
export async function POST(request: NextRequest) {
|
|
409
|
+
const body = await request.json()
|
|
410
|
+
|
|
411
|
+
// Validation mixed with business logic
|
|
412
|
+
if (!body.email || !body.email.includes('@')) {
|
|
413
|
+
return NextResponse.json({ error: 'Invalid email' }, { status: 400 })
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// Database logic mixed with API logic
|
|
417
|
+
const user = await prisma.user.create({
|
|
418
|
+
data: { email: body.email, name: body.name }
|
|
419
|
+
})
|
|
420
|
+
|
|
421
|
+
// Email sending mixed with everything else
|
|
422
|
+
await fetch('https://api.sendgrid.com/send', {
|
|
423
|
+
method: 'POST',
|
|
424
|
+
body: JSON.stringify({ to: user.email, subject: 'Welcome' })
|
|
425
|
+
})
|
|
426
|
+
|
|
427
|
+
return NextResponse.json(user)
|
|
428
|
+
}
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
**✅ Separated Concerns:**
|
|
432
|
+
```typescript
|
|
433
|
+
// app/api/users/route.ts (API Layer)
|
|
434
|
+
export async function POST(request: NextRequest) {
|
|
435
|
+
const body = await request.json()
|
|
436
|
+
const validated = validateUserInput(body) // Validation layer
|
|
437
|
+
const user = await userService.createUser(validated) // Business layer
|
|
438
|
+
await emailService.sendWelcome(user) // Email layer
|
|
439
|
+
return NextResponse.json(user)
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// lib/validators/user.ts (Validation Layer)
|
|
443
|
+
export function validateUserInput(data: unknown) {
|
|
444
|
+
return userSchema.parse(data)
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// lib/services/user-service.ts (Business Layer)
|
|
448
|
+
export const userService = {
|
|
449
|
+
async createUser(data: ValidatedUserData) {
|
|
450
|
+
return prisma.user.create({ data })
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// lib/services/email-service.ts (Email Layer)
|
|
455
|
+
export const emailService = {
|
|
456
|
+
async sendWelcome(user: User) {
|
|
457
|
+
await sendEmail({ to: user.email, subject: 'Welcome' })
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
---
|
|
463
|
+
|
|
464
|
+
## Principle of Least Surprise
|
|
465
|
+
|
|
466
|
+
Code should behave in a way that minimizes surprise for other developers.
|
|
467
|
+
|
|
468
|
+
**❌ Surprising:**
|
|
469
|
+
```typescript
|
|
470
|
+
// Function name suggests it only reads, but it also writes
|
|
471
|
+
function getUser(id: string) {
|
|
472
|
+
const user = db.findUser(id)
|
|
473
|
+
db.updateLastAccessed(id) // Surprise! Side effect!
|
|
474
|
+
return user
|
|
475
|
+
}
|
|
476
|
+
```
|
|
477
|
+
|
|
478
|
+
**✅ Expected:**
|
|
479
|
+
```typescript
|
|
480
|
+
// Clear function names, no surprises
|
|
481
|
+
function getUser(id: string) {
|
|
482
|
+
return db.findUser(id)
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
function getUserAndUpdateAccess(id: string) {
|
|
486
|
+
const user = db.findUser(id)
|
|
487
|
+
db.updateLastAccessed(id) // Expected from function name
|
|
488
|
+
return user
|
|
489
|
+
}
|
|
490
|
+
```
|
|
491
|
+
|
|
492
|
+
---
|
|
493
|
+
|
|
494
|
+
## Quick Reference
|
|
495
|
+
|
|
496
|
+
| Principle | Summary |
|
|
497
|
+
|-----------|---------|
|
|
498
|
+
| **KISS** | Choose simple solutions over complex ones |
|
|
499
|
+
| **YAGNI** | Build only what you need now |
|
|
500
|
+
| **SRP** | One responsibility per module |
|
|
501
|
+
| **OCP** | Open for extension, closed for modification |
|
|
502
|
+
| **LSP** | Subtypes must be substitutable |
|
|
503
|
+
| **ISP** | Don't force clients to depend on unused methods |
|
|
504
|
+
| **DIP** | Depend on abstractions, not concretions |
|
|
505
|
+
| **Fail Fast** | Detect and raise errors immediately |
|
|
506
|
+
| **Observability** | Log everything that matters |
|
|
507
|
+
| **DRY** | Single source of truth for all knowledge |
|
|
508
|
+
| **Separation** | Different concerns in different modules |
|
|
509
|
+
| **Least Surprise** | Code should behave as expected |
|
|
510
|
+
|
|
511
|
+
---
|
|
512
|
+
|
|
513
|
+
**💡 Remember:** Good principles lead to maintainable code!
|