@raftlabs/raftstack 1.0.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/skills/backend/SKILL.md +802 -0
- package/.claude/skills/code-quality/SKILL.md +318 -0
- package/.claude/skills/database/SKILL.md +465 -0
- package/.claude/skills/react/SKILL.md +418 -0
- package/.claude/skills/seo/SKILL.md +446 -0
- package/LICENSE +21 -0
- package/README.md +291 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +2009 -0
- package/dist/cli.js.map +1 -0
- package/package.json +69 -0
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: code-quality
|
|
3
|
+
description: Use when writing or reviewing code, creating functions, naming variables, structuring files, or when code feels messy, hard to read, or overly complex
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Code Quality
|
|
7
|
+
|
|
8
|
+
## Overview
|
|
9
|
+
|
|
10
|
+
Clean code reveals intent and minimizes complexity. Every function should do one thing, have a clear name, and fit on one screen.
|
|
11
|
+
|
|
12
|
+
## When to Use
|
|
13
|
+
|
|
14
|
+
- Writing any new function or module
|
|
15
|
+
- Reviewing code for clarity
|
|
16
|
+
- Refactoring messy code
|
|
17
|
+
- Under time pressure (especially then)
|
|
18
|
+
|
|
19
|
+
**When NOT to use:** Quick prototypes explicitly marked as throwaway (rare).
|
|
20
|
+
|
|
21
|
+
## The Iron Rules
|
|
22
|
+
|
|
23
|
+
### 1. Function Length: Max 30 Lines
|
|
24
|
+
|
|
25
|
+
Functions over 30 lines are doing too much. Extract helpers.
|
|
26
|
+
|
|
27
|
+
**Exception:** Orchestration functions (calling other functions, minimal logic) may reach 50 lines. If you have conditionals or loops, max is 30.
|
|
28
|
+
|
|
29
|
+
**Cyclomatic Complexity:** Max 10 branches per function. Count: `if`, `else`, `case`, `&&`, `||`, `?:`, `catch`.
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
// ❌ BAD: 100+ line processOrder doing everything
|
|
33
|
+
async function processOrder(order: Order) {
|
|
34
|
+
// validation...
|
|
35
|
+
// pricing...
|
|
36
|
+
// inventory...
|
|
37
|
+
// payment...
|
|
38
|
+
// email...
|
|
39
|
+
// analytics...
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// ✅ GOOD: Orchestrator with extracted concerns
|
|
43
|
+
async function processOrder(order: Order) {
|
|
44
|
+
const validation = validateOrder(order);
|
|
45
|
+
if (!validation.valid) return failure(validation.error);
|
|
46
|
+
|
|
47
|
+
const pricing = calculateOrderTotal(order);
|
|
48
|
+
const reservation = await reserveInventory(order);
|
|
49
|
+
|
|
50
|
+
const payment = await processPayment(order, pricing.total);
|
|
51
|
+
if (!payment.success) {
|
|
52
|
+
await releaseInventory(reservation);
|
|
53
|
+
return failure(payment.error);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
await sendConfirmationEmail(order, pricing);
|
|
57
|
+
await recordAnalytics(order, pricing);
|
|
58
|
+
|
|
59
|
+
return success(order.id, pricing.total);
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### 2. No Magic Numbers
|
|
64
|
+
|
|
65
|
+
Every number with meaning needs a name.
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
// ❌ BAD: What do these numbers mean?
|
|
69
|
+
if (totalItems >= 10) {
|
|
70
|
+
total = total * 0.90;
|
|
71
|
+
}
|
|
72
|
+
if (password.length < 8) { }
|
|
73
|
+
|
|
74
|
+
// ✅ GOOD: Self-documenting
|
|
75
|
+
const BULK_DISCOUNT_THRESHOLD = 10;
|
|
76
|
+
const BULK_DISCOUNT_RATE = 0.10;
|
|
77
|
+
const MIN_PASSWORD_LENGTH = 8;
|
|
78
|
+
|
|
79
|
+
if (totalItems >= BULK_DISCOUNT_THRESHOLD) {
|
|
80
|
+
total = total * (1 - BULK_DISCOUNT_RATE);
|
|
81
|
+
}
|
|
82
|
+
if (password.length < MIN_PASSWORD_LENGTH) { }
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### 3. DRY: Extract Repeated Logic
|
|
86
|
+
|
|
87
|
+
If you copy-paste, extract.
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
// ❌ BAD: Rollback logic repeated 5 times
|
|
91
|
+
if (!paymentInfo) {
|
|
92
|
+
for (const item of order.items) {
|
|
93
|
+
inventoryDb[item.productId] += item.quantity;
|
|
94
|
+
}
|
|
95
|
+
delete reservedInventory[order.id];
|
|
96
|
+
return { success: false, error: 'Payment required' };
|
|
97
|
+
}
|
|
98
|
+
// ...same rollback code repeated 4 more times...
|
|
99
|
+
|
|
100
|
+
// ✅ GOOD: Extracted once
|
|
101
|
+
function rollbackInventory(orderId: string, items: OrderItem[]) {
|
|
102
|
+
for (const item of items) {
|
|
103
|
+
inventoryDb[item.productId] += item.quantity;
|
|
104
|
+
}
|
|
105
|
+
delete reservedInventory[orderId];
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Then use: rollbackInventory(order.id, order.items);
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### 4. Single Responsibility
|
|
112
|
+
|
|
113
|
+
One function = one reason to change.
|
|
114
|
+
|
|
115
|
+
| Bad | Good |
|
|
116
|
+
|-----|------|
|
|
117
|
+
| `validateAndProcessOrder()` | `validateOrder()` + `processOrder()` |
|
|
118
|
+
| `fetchDataAndRender()` | `fetchData()` + `renderData()` |
|
|
119
|
+
| `parseAndValidateAndSave()` | `parse()` + `validate()` + `save()` |
|
|
120
|
+
|
|
121
|
+
### 5. Naming Conventions
|
|
122
|
+
|
|
123
|
+
| Type | Convention | Examples |
|
|
124
|
+
|------|------------|----------|
|
|
125
|
+
| Functions | camelCase, verb-first | `validateEmail()`, `calculateTotal()` |
|
|
126
|
+
| Booleans | `is`, `has`, `should`, `can` | `isValid`, `hasPermission`, `shouldRetry` |
|
|
127
|
+
| Constants | SCREAMING_SNAKE_CASE | `MAX_RETRIES`, `API_TIMEOUT_MS` |
|
|
128
|
+
| Classes/Types | PascalCase | `OrderProcessor`, `ValidationResult` |
|
|
129
|
+
|
|
130
|
+
### 6. Comments: WHY, Not WHAT
|
|
131
|
+
|
|
132
|
+
```typescript
|
|
133
|
+
// ❌ BAD: Describes what code does (obvious)
|
|
134
|
+
// Check if user is admin
|
|
135
|
+
if (user.role === 'admin') { }
|
|
136
|
+
|
|
137
|
+
// ✅ GOOD: Explains why (not obvious)
|
|
138
|
+
// Admins bypass rate limiting per security policy SEC-2024-001
|
|
139
|
+
if (user.role === 'admin') { }
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## Automated Enforcement
|
|
143
|
+
|
|
144
|
+
### ESLint Configuration
|
|
145
|
+
|
|
146
|
+
Enforce code quality with automated tools:
|
|
147
|
+
|
|
148
|
+
```javascript
|
|
149
|
+
// .eslintrc.js
|
|
150
|
+
module.exports = {
|
|
151
|
+
rules: {
|
|
152
|
+
// Max function length
|
|
153
|
+
'max-lines-per-function': ['error', {
|
|
154
|
+
max: 30,
|
|
155
|
+
skipBlankLines: true,
|
|
156
|
+
skipComments: true,
|
|
157
|
+
}],
|
|
158
|
+
|
|
159
|
+
// Cyclomatic complexity
|
|
160
|
+
'complexity': ['error', { max: 10 }],
|
|
161
|
+
|
|
162
|
+
// Max file length
|
|
163
|
+
'max-lines': ['error', {
|
|
164
|
+
max: 300,
|
|
165
|
+
skipBlankLines: true,
|
|
166
|
+
skipComments: true,
|
|
167
|
+
}],
|
|
168
|
+
|
|
169
|
+
// Max function params
|
|
170
|
+
'max-params': ['error', 3],
|
|
171
|
+
|
|
172
|
+
// Max nested callbacks
|
|
173
|
+
'max-nested-callbacks': ['error', 2],
|
|
174
|
+
|
|
175
|
+
// No magic numbers
|
|
176
|
+
'no-magic-numbers': ['error', {
|
|
177
|
+
ignore: [0, 1, -1],
|
|
178
|
+
ignoreArrayIndexes: true,
|
|
179
|
+
}],
|
|
180
|
+
},
|
|
181
|
+
};
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### TypeScript-Specific Patterns
|
|
185
|
+
|
|
186
|
+
```typescript
|
|
187
|
+
// ✅ GOOD: Type-safe options object (not 5 params)
|
|
188
|
+
interface CreateUserOptions {
|
|
189
|
+
email: string;
|
|
190
|
+
firstName: string;
|
|
191
|
+
lastName: string;
|
|
192
|
+
role?: UserRole;
|
|
193
|
+
sendWelcomeEmail?: boolean;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function createUser(options: CreateUserOptions): User {
|
|
197
|
+
// Single param, clear structure
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// ✅ GOOD: Discriminated union for type-safe control flow
|
|
201
|
+
type Result<T> =
|
|
202
|
+
| { success: true; data: T }
|
|
203
|
+
| { success: false; error: string };
|
|
204
|
+
|
|
205
|
+
function processOrder(order: Order): Result<OrderConfirmation> {
|
|
206
|
+
if (!isValid(order)) {
|
|
207
|
+
return { success: false, error: 'Invalid order' };
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const confirmation = executeOrder(order);
|
|
211
|
+
return { success: true, data: confirmation };
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// ✅ GOOD: Branded types for type safety
|
|
215
|
+
type UserId = string & { readonly __brand: 'UserId' };
|
|
216
|
+
type OrderId = string & { readonly __brand: 'OrderId' };
|
|
217
|
+
|
|
218
|
+
function getUser(id: UserId): User { /* ... */ }
|
|
219
|
+
|
|
220
|
+
// Won't compile - prevents mixing up IDs
|
|
221
|
+
const orderId: OrderId = '123' as OrderId;
|
|
222
|
+
getUser(orderId); // Type error!
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
## Testing Strategy
|
|
226
|
+
|
|
227
|
+
```typescript
|
|
228
|
+
// Test function length and complexity
|
|
229
|
+
describe('Code Quality', () => {
|
|
230
|
+
it('functions stay under 30 lines', () => {
|
|
231
|
+
const functionSource = processOrder.toString();
|
|
232
|
+
const lines = functionSource.split('\n').filter(l => l.trim()).length;
|
|
233
|
+
expect(lines).toBeLessThanOrEqual(30);
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
it('maintains low cyclomatic complexity', () => {
|
|
237
|
+
// Use eslint-plugin-complexity or similar
|
|
238
|
+
const complexity = calculateComplexity(processOrder);
|
|
239
|
+
expect(complexity).toBeLessThanOrEqual(10);
|
|
240
|
+
});
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
// Test extracted helpers
|
|
244
|
+
describe('Helper Functions', () => {
|
|
245
|
+
it('validateOrder handles invalid input', () => {
|
|
246
|
+
const result = validateOrder({ items: [] });
|
|
247
|
+
expect(result.valid).toBe(false);
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
it('calculateOrderTotal sums items correctly', () => {
|
|
251
|
+
const total = calculateOrderTotal(mockOrder);
|
|
252
|
+
expect(total).toBe(99.99);
|
|
253
|
+
});
|
|
254
|
+
});
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
## Quick Reference
|
|
258
|
+
|
|
259
|
+
| Smell | Fix | ESLint Rule |
|
|
260
|
+
|-------|-----|-------------|
|
|
261
|
+
| Function > 30 lines | Extract helpers | `max-lines-per-function` |
|
|
262
|
+
| Cyclomatic complexity > 10 | Extract conditionals | `complexity` |
|
|
263
|
+
| Repeated code block | Extract function | Manual review |
|
|
264
|
+
| Magic number | Named constant | `no-magic-numbers` |
|
|
265
|
+
| `validateAndProcess()` | Split into two functions | Manual review |
|
|
266
|
+
| Nested callbacks > 2 levels | Extract or use async/await | `max-nested-callbacks` |
|
|
267
|
+
| Parameter list > 3 | Use options object | `max-params` |
|
|
268
|
+
|
|
269
|
+
## Red Flags - STOP and Refactor
|
|
270
|
+
|
|
271
|
+
These thoughts mean you're about to write bad code:
|
|
272
|
+
|
|
273
|
+
| Thought | Reality |
|
|
274
|
+
|---------|---------|
|
|
275
|
+
| "It's faster to write it all in one function" | It's faster to read small functions. Write for the reader. |
|
|
276
|
+
| "We'll refactor later" | Later never comes. Write it right the first time. |
|
|
277
|
+
| "It's just prototype code" | Prototypes become production. No excuse. |
|
|
278
|
+
| "The deadline is tight" | Bad code slows you down MORE. Clean code is faster. |
|
|
279
|
+
| "I'll add helpers if it gets complex" | It's already complex. Extract NOW. |
|
|
280
|
+
| "This is a special case" | There are no special cases for quality. |
|
|
281
|
+
| "It's only 35 lines, close enough" | The limit exists for a reason. Extract a helper. |
|
|
282
|
+
| "The user specifically asked for one function" | Push back. Explain why splitting is better. |
|
|
283
|
+
| "I need all this context in one place" | That's what orchestrator functions are for. |
|
|
284
|
+
|
|
285
|
+
## Pressure Response
|
|
286
|
+
|
|
287
|
+
When someone says "just make it work fast":
|
|
288
|
+
|
|
289
|
+
1. **Small functions ARE faster** - easier to debug, test, modify
|
|
290
|
+
2. **Tech debt has interest** - every shortcut costs 10x later
|
|
291
|
+
3. **Extract as you go** - takes 30 seconds, saves hours
|
|
292
|
+
|
|
293
|
+
**Violating code quality under pressure is violating code quality.**
|
|
294
|
+
|
|
295
|
+
## References
|
|
296
|
+
|
|
297
|
+
- [ESLint Complexity Rule](https://eslint.org/docs/latest/rules/complexity) - Cyclomatic complexity enforcement
|
|
298
|
+
- [ESLintCC](https://eslintcc.github.io/) - Complexity measurement tool
|
|
299
|
+
- [TypeScript Handbook](https://www.typescriptlang.org/docs/handbook/) - Advanced type patterns
|
|
300
|
+
- [Clean Code (Martin)](https://www.amazon.com/Clean-Code-Handbook-Software-Craftsmanship/dp/0132350882) - Function length rationale
|
|
301
|
+
|
|
302
|
+
**Version Notes:**
|
|
303
|
+
- ESLint 9+: Flat config format, enhanced rule options
|
|
304
|
+
- TypeScript 5+: Improved discriminated union narrowing
|
|
305
|
+
- Cyclomatic complexity: Default threshold 20, recommended 10
|
|
306
|
+
|
|
307
|
+
## Common Mistakes
|
|
308
|
+
|
|
309
|
+
| Mistake | Impact | Fix |
|
|
310
|
+
|---------|--------|-----|
|
|
311
|
+
| God function | Untestable, unreadable | Max 30 lines, single responsibility |
|
|
312
|
+
| Copy-paste code | Bugs multiply | Extract shared logic |
|
|
313
|
+
| Cryptic names | Confusion | Descriptive, verb-first names |
|
|
314
|
+
| No constants | Magic numbers everywhere | SCREAMING_SNAKE_CASE for all config |
|
|
315
|
+
| No ESLint enforcement | Quality drifts over time | Add `complexity` and `max-lines-per-function` rules |
|
|
316
|
+
| 5+ function parameters | Hard to call, hard to test | Use options object pattern |
|
|
317
|
+
| Comments describe WHAT | Redundant, unmaintained | Comment WHY, not WHAT |
|
|
318
|
+
| Mixing ID types (string) | Runtime bugs | Use branded types for type safety |
|