codecruise 0.1.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.
Files changed (129) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +111 -0
  3. package/bin/codecruise.js +68 -0
  4. package/config/CLAUDE.md +107 -0
  5. package/config/agents/analyst.md +48 -0
  6. package/config/agents/architect-reviewer.md +161 -0
  7. package/config/agents/architect.md +119 -0
  8. package/config/agents/critic.md +63 -0
  9. package/config/agents/developer.md +96 -0
  10. package/config/agents/devops.md +81 -0
  11. package/config/agents/orchestrator.md +91 -0
  12. package/config/agents/planner.md +139 -0
  13. package/config/agents/retro.md +52 -0
  14. package/config/agents/reviewer.md +101 -0
  15. package/config/agents/security-reviewer.md +57 -0
  16. package/config/agents/stack/expo/AGENT.md +473 -0
  17. package/config/agents/stack/expo/rules/critical.md +427 -0
  18. package/config/agents/stack/expo/rules/native.md +455 -0
  19. package/config/agents/stack/expo/rules/navigation.md +445 -0
  20. package/config/agents/stack/expo/rules/performance.md +415 -0
  21. package/config/agents/stack/fastify/AGENT.md +397 -0
  22. package/config/agents/stack/fastify/rules/api-design.md +283 -0
  23. package/config/agents/stack/fastify/rules/critical.md +232 -0
  24. package/config/agents/stack/fastify/rules/queues.md +303 -0
  25. package/config/agents/stack/fastify/rules/security.md +384 -0
  26. package/config/agents/stack/index.yaml +48 -0
  27. package/config/agents/stack/nextjs/AGENT.md +421 -0
  28. package/config/agents/stack/nextjs/rules/components.md +413 -0
  29. package/config/agents/stack/nextjs/rules/critical.md +391 -0
  30. package/config/agents/stack/nextjs/rules/performance.md +403 -0
  31. package/config/agents/stack/nextjs/rules/styling.md +334 -0
  32. package/config/agents/stack/shared-ts/AGENT.md +384 -0
  33. package/config/agents/stack/shared-ts/rules/critical.md +315 -0
  34. package/config/agents/stack/shared-ts/rules/patterns.md +384 -0
  35. package/config/agents/stack/shared-ts/rules/zod.md +427 -0
  36. package/config/agents/tester.md +79 -0
  37. package/config/commands/architect-discuss.md +366 -0
  38. package/config/commands/architect-list.md +160 -0
  39. package/config/commands/architect-review.md +111 -0
  40. package/config/commands/architect.md +118 -0
  41. package/config/commands/compact.md +118 -0
  42. package/config/commands/companion.md +279 -0
  43. package/config/commands/dashboard.md +152 -0
  44. package/config/commands/doctor.md +227 -0
  45. package/config/commands/dogfood-report.md +101 -0
  46. package/config/commands/flags/run-autonomous.md +110 -0
  47. package/config/commands/flags/run-pause.md +80 -0
  48. package/config/commands/ingest.md +173 -0
  49. package/config/commands/init.md +128 -0
  50. package/config/commands/metrics.md +87 -0
  51. package/config/commands/parallel.md +320 -0
  52. package/config/commands/pause.md +55 -0
  53. package/config/commands/plan-review.md +130 -0
  54. package/config/commands/plan.md +216 -0
  55. package/config/commands/production-check.md +308 -0
  56. package/config/commands/refine.md +323 -0
  57. package/config/commands/resume.md +72 -0
  58. package/config/commands/retro.md +121 -0
  59. package/config/commands/retry.md +75 -0
  60. package/config/commands/role.md +310 -0
  61. package/config/commands/run.md +417 -0
  62. package/config/commands/scope.md +85 -0
  63. package/config/commands/setup-permissions.md +104 -0
  64. package/config/commands/skip.md +75 -0
  65. package/config/commands/spec-forge.md +213 -0
  66. package/config/commands/spec-help.md +194 -0
  67. package/config/commands/spec-patch.md +342 -0
  68. package/config/commands/spec-resolve.md +110 -0
  69. package/config/commands/spec-review.md +153 -0
  70. package/config/commands/status.md +114 -0
  71. package/config/commands/sync.md +131 -0
  72. package/config/commands/task.md +138 -0
  73. package/config/commands/verify.md +124 -0
  74. package/config/hooks/README.md +632 -0
  75. package/config/hooks/activity-log.sh +187 -0
  76. package/config/hooks/anti-rationalize.sh +52 -0
  77. package/config/hooks/capture-verification.sh +112 -0
  78. package/config/hooks/collect-metrics.sh +135 -0
  79. package/config/hooks/enforce-file-scope.sh +75 -0
  80. package/config/hooks/enforce-state-machine.sh +161 -0
  81. package/config/hooks/enforce-tdd.sh +180 -0
  82. package/config/hooks/format.sh +40 -0
  83. package/config/hooks/lib/activity-helpers.sh +162 -0
  84. package/config/hooks/lib/read-settings.sh +71 -0
  85. package/config/hooks/load-context-skills.sh +95 -0
  86. package/config/hooks/notify.sh +81 -0
  87. package/config/hooks/pre-commit.sample +35 -0
  88. package/config/hooks/protect-files.sh +63 -0
  89. package/config/hooks/track-agents.sh +41 -0
  90. package/config/hooks/track-commands.sh +37 -0
  91. package/config/hooks/track-enforcement.sh +44 -0
  92. package/config/hooks/track-ooda.sh +77 -0
  93. package/config/hooks/validate-commit-msg.sh +35 -0
  94. package/config/hooks/validate-plan.sh +213 -0
  95. package/config/hooks/verify-criteria.sh +46 -0
  96. package/config/hooks/verify-todo-completion.sh +140 -0
  97. package/config/rules/comments.md +25 -0
  98. package/config/rules/decision-rules.md +308 -0
  99. package/config/rules/hygiene.md +247 -0
  100. package/config/rules/pattern-detection.md +372 -0
  101. package/config/rules/profiles.md +193 -0
  102. package/config/rules/recovery.md +83 -0
  103. package/config/rules/scope-detection.md +213 -0
  104. package/config/rules/standards.md +127 -0
  105. package/config/rules/workflow.md +121 -0
  106. package/config/schemas.md +767 -0
  107. package/config/settings.json +195 -0
  108. package/config/skills/backend/SKILL.md +734 -0
  109. package/config/skills/database/SKILL.md +426 -0
  110. package/config/skills/frontend/SKILL.md +434 -0
  111. package/config/skills/git/SKILL.md +396 -0
  112. package/config/skills/index.yaml +36 -0
  113. package/config/skills/observability/SKILL.md +430 -0
  114. package/config/skills/package-dev/SKILL.md +498 -0
  115. package/config/skills/performance/SKILL.md +378 -0
  116. package/config/skills/resilience/SKILL.md +573 -0
  117. package/config/skills/testing/SKILL.md +398 -0
  118. package/config/skills/testing-patterns/SKILL.md +276 -0
  119. package/config/skills/typescript/SKILL.md +152 -0
  120. package/config/templates/CLAUDE.md +70 -0
  121. package/config/templates/README.md +117 -0
  122. package/config/templates/steering/adr-template.md +102 -0
  123. package/config/templates/steering/product.md +60 -0
  124. package/config/templates/steering/rfc-template.md +159 -0
  125. package/config/templates/steering/structure.md +146 -0
  126. package/config/templates/steering/tech.md +85 -0
  127. package/package.json +40 -0
  128. package/src/install.js +163 -0
  129. package/src/report.js +310 -0
@@ -0,0 +1,315 @@
1
+ # Critical Rules - TypeScript
2
+
3
+ Must-follow rules for type safety. Violations block PR merge.
4
+
5
+ ---
6
+
7
+ ## Strict Mode
8
+
9
+ ### TS-001: Enable all strict flags
10
+
11
+ ```json
12
+ // tsconfig.json - REQUIRED settings
13
+ {
14
+ "compilerOptions": {
15
+ "strict": true,
16
+ "noUncheckedIndexedAccess": true,
17
+ "noImplicitOverride": true,
18
+ "forceConsistentCasingInFileNames": true,
19
+ "exactOptionalPropertyTypes": true
20
+ }
21
+ }
22
+ ```
23
+
24
+ ### TS-002: Never use `any`
25
+
26
+ ```typescript
27
+ // BAD
28
+ function process(data: any) { ... }
29
+ const result = response as any;
30
+
31
+ // GOOD
32
+ function process(data: unknown) {
33
+ if (isValidData(data)) { ... }
34
+ }
35
+
36
+ // If truly unknown, use unknown and narrow
37
+ function parseJson(text: string): unknown {
38
+ return JSON.parse(text);
39
+ }
40
+ ```
41
+
42
+ ### TS-003: Never use non-null assertion carelessly
43
+
44
+ ```typescript
45
+ // BAD - crashes if undefined
46
+ const user = users.find(u => u.id === id)!;
47
+ const name = user.name!;
48
+
49
+ // GOOD - handle undefined
50
+ const user = users.find(u => u.id === id);
51
+ if (!user) throw new NotFoundError('User');
52
+
53
+ // Or with nullish coalescing
54
+ const name = user.name ?? 'Unknown';
55
+ ```
56
+
57
+ ---
58
+
59
+ ## Type Safety
60
+
61
+ ### TS-004: Prefer explicit return types for public APIs
62
+
63
+ ```typescript
64
+ // BAD - inferred types can change unexpectedly
65
+ export function getUser(id: string) {
66
+ return db.user.findUnique({ where: { id } });
67
+ }
68
+
69
+ // GOOD - explicit contract
70
+ export function getUser(id: string): Promise<User | null> {
71
+ return db.user.findUnique({ where: { id } });
72
+ }
73
+ ```
74
+
75
+ ### TS-005: Use const assertions for literals
76
+
77
+ ```typescript
78
+ // BAD - type is string[]
79
+ const statuses = ['pending', 'active', 'done'];
80
+
81
+ // GOOD - type is readonly ['pending', 'active', 'done']
82
+ const statuses = ['pending', 'active', 'done'] as const;
83
+
84
+ // Useful for type derivation
85
+ type Status = typeof statuses[number]; // 'pending' | 'active' | 'done'
86
+ ```
87
+
88
+ ### TS-006: Avoid type assertions except for narrowing
89
+
90
+ ```typescript
91
+ // BAD - bypasses type checking
92
+ const user = response as User;
93
+
94
+ // GOOD - validate first
95
+ const user = userSchema.parse(response);
96
+
97
+ // OK - narrowing after check
98
+ if (isUser(response)) {
99
+ const user = response; // Type is narrowed
100
+ }
101
+
102
+ // OK - when TypeScript can't infer
103
+ const element = document.getElementById('root') as HTMLDivElement | null;
104
+ ```
105
+
106
+ ---
107
+
108
+ ## Null Safety
109
+
110
+ ### TS-007: Handle all nullable values
111
+
112
+ ```typescript
113
+ // BAD - might be undefined
114
+ const items = data.items.map(i => i.name);
115
+
116
+ // GOOD - check first
117
+ const items = data.items?.map(i => i.name) ?? [];
118
+
119
+ // Or throw if required
120
+ if (!data.items) throw new Error('Items required');
121
+ const items = data.items.map(i => i.name);
122
+ ```
123
+
124
+ ### TS-008: Use noUncheckedIndexedAccess
125
+
126
+ ```typescript
127
+ // With noUncheckedIndexedAccess: true
128
+
129
+ const arr = [1, 2, 3];
130
+ const first = arr[0]; // Type is number | undefined
131
+
132
+ // Must handle
133
+ if (first !== undefined) {
134
+ console.log(first + 1);
135
+ }
136
+
137
+ // Or use at() with check
138
+ const last = arr.at(-1);
139
+ if (last !== undefined) { ... }
140
+ ```
141
+
142
+ ### TS-009: Distinguish null vs undefined
143
+
144
+ ```typescript
145
+ // null = intentionally empty
146
+ // undefined = not set
147
+
148
+ interface User {
149
+ name: string;
150
+ nickname: string | null; // User chose no nickname
151
+ deletedAt?: Date; // Not deleted = undefined
152
+ }
153
+
154
+ // Check appropriately
155
+ if (user.nickname === null) {
156
+ // User explicitly has no nickname
157
+ }
158
+
159
+ if (user.deletedAt === undefined) {
160
+ // User is not deleted
161
+ }
162
+ ```
163
+
164
+ ---
165
+
166
+ ## Imports & Exports
167
+
168
+ ### TS-010: Use type imports for types
169
+
170
+ ```typescript
171
+ // BAD - imports at runtime
172
+ import { User, UserService } from './user';
173
+
174
+ // GOOD - type-only imports
175
+ import type { User } from './user';
176
+ import { UserService } from './user';
177
+
178
+ // Or combined
179
+ import { UserService, type User } from './user';
180
+ ```
181
+
182
+ ### TS-011: Export types alongside values
183
+
184
+ ```typescript
185
+ // BAD - types not exported
186
+ const userSchema = z.object({ ... });
187
+ // User type only available internally
188
+
189
+ // GOOD - export both
190
+ export const userSchema = z.object({ ... });
191
+ export type User = z.infer<typeof userSchema>;
192
+ ```
193
+
194
+ ### TS-012: Use barrel exports carefully
195
+
196
+ ```typescript
197
+ // index.ts
198
+ export { userSchema, type User } from './user';
199
+ export { orderSchema, type Order } from './order';
200
+
201
+ // Avoid re-exporting everything
202
+ // BAD - pulls in everything
203
+ export * from './user';
204
+ export * from './order';
205
+
206
+ // GOOD - explicit exports
207
+ export { User, userSchema, createUser } from './user';
208
+ ```
209
+
210
+ ---
211
+
212
+ ## Async Safety
213
+
214
+ ### TS-013: Always handle Promise rejections
215
+
216
+ ```typescript
217
+ // BAD - unhandled rejection
218
+ someAsyncFunction();
219
+
220
+ // GOOD - handle or await
221
+ await someAsyncFunction();
222
+
223
+ // Or with catch
224
+ someAsyncFunction().catch(handleError);
225
+
226
+ // In event handlers
227
+ button.addEventListener('click', async () => {
228
+ try {
229
+ await handleClick();
230
+ } catch (error) {
231
+ showError(error);
232
+ }
233
+ });
234
+ ```
235
+
236
+ ### TS-014: Type async function returns
237
+
238
+ ```typescript
239
+ // BAD
240
+ async function fetchUser(id: string) {
241
+ const response = await fetch(`/users/${id}`);
242
+ return response.json();
243
+ }
244
+
245
+ // GOOD
246
+ async function fetchUser(id: string): Promise<User> {
247
+ const response = await fetch(`/users/${id}`);
248
+ const data = await response.json();
249
+ return userSchema.parse(data);
250
+ }
251
+ ```
252
+
253
+ ---
254
+
255
+ ## Error Handling
256
+
257
+ ### TS-015: Use typed errors
258
+
259
+ ```typescript
260
+ // Define error types
261
+ class AppError extends Error {
262
+ constructor(
263
+ message: string,
264
+ public code: string,
265
+ public statusCode: number = 500
266
+ ) {
267
+ super(message);
268
+ this.name = 'AppError';
269
+ }
270
+ }
271
+
272
+ class NotFoundError extends AppError {
273
+ constructor(resource: string, id?: string) {
274
+ super(
275
+ id ? `${resource} ${id} not found` : `${resource} not found`,
276
+ 'NOT_FOUND',
277
+ 404
278
+ );
279
+ }
280
+ }
281
+
282
+ class ValidationError extends AppError {
283
+ constructor(
284
+ message: string,
285
+ public details: Array<{ field: string; message: string }>
286
+ ) {
287
+ super(message, 'VALIDATION_ERROR', 400);
288
+ }
289
+ }
290
+ ```
291
+
292
+ ### TS-016: Narrow unknown errors
293
+
294
+ ```typescript
295
+ // BAD
296
+ try { ... } catch (e) {
297
+ console.log(e.message); // e is unknown
298
+ }
299
+
300
+ // GOOD
301
+ try { ... } catch (e) {
302
+ if (e instanceof Error) {
303
+ console.log(e.message);
304
+ } else {
305
+ console.log('Unknown error', e);
306
+ }
307
+ }
308
+
309
+ // Or use a helper
310
+ function getErrorMessage(error: unknown): string {
311
+ if (error instanceof Error) return error.message;
312
+ if (typeof error === 'string') return error;
313
+ return 'Unknown error';
314
+ }
315
+ ```
@@ -0,0 +1,384 @@
1
+ # TypeScript Patterns
2
+
3
+ Common patterns for clean, type-safe code.
4
+
5
+ ---
6
+
7
+ ## Utility Types
8
+
9
+ ### PAT-001: Use built-in utility types
10
+
11
+ ```typescript
12
+ // Partial - all properties optional
13
+ type UpdateUser = Partial<User>;
14
+
15
+ // Required - all properties required
16
+ type CompleteUser = Required<User>;
17
+
18
+ // Pick - subset of properties
19
+ type UserCredentials = Pick<User, 'email' | 'password'>;
20
+
21
+ // Omit - exclude properties
22
+ type PublicUser = Omit<User, 'password' | 'hashedPassword'>;
23
+
24
+ // Record - object with known keys
25
+ type StatusMap = Record<OrderStatus, string>;
26
+
27
+ // Readonly - immutable
28
+ type FrozenConfig = Readonly<Config>;
29
+ ```
30
+
31
+ ### PAT-002: Create custom utility types
32
+
33
+ ```typescript
34
+ // Make specific keys optional
35
+ type PartialBy<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
36
+ type CreateUser = PartialBy<User, 'id' | 'createdAt'>;
37
+
38
+ // Make specific keys required
39
+ type RequiredBy<T, K extends keyof T> = Omit<T, K> & Required<Pick<T, K>>;
40
+ type UserWithEmail = RequiredBy<PartialUser, 'email'>;
41
+
42
+ // Deep partial
43
+ type DeepPartial<T> = {
44
+ [P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
45
+ };
46
+ ```
47
+
48
+ ### PAT-003: Extract types from existing code
49
+
50
+ ```typescript
51
+ // From function return
52
+ type UserResult = Awaited<ReturnType<typeof getUser>>;
53
+
54
+ // From array element
55
+ type OrderItem = Order['items'][number];
56
+
57
+ // From object values
58
+ const STATUS = { pending: 1, active: 2, done: 3 } as const;
59
+ type StatusKey = keyof typeof STATUS; // 'pending' | 'active' | 'done'
60
+ type StatusValue = typeof STATUS[StatusKey]; // 1 | 2 | 3
61
+
62
+ // From Zod schema
63
+ type User = z.infer<typeof userSchema>;
64
+ ```
65
+
66
+ ---
67
+
68
+ ## Discriminated Unions
69
+
70
+ ### PAT-004: Model state with discriminated unions
71
+
72
+ ```typescript
73
+ // Instead of optional flags
74
+ // BAD
75
+ interface Request {
76
+ status: 'loading' | 'success' | 'error';
77
+ data?: User;
78
+ error?: Error;
79
+ isLoading?: boolean;
80
+ }
81
+
82
+ // GOOD - discriminated union
83
+ type RequestState<T> =
84
+ | { status: 'idle' }
85
+ | { status: 'loading' }
86
+ | { status: 'success'; data: T }
87
+ | { status: 'error'; error: Error };
88
+
89
+ // Usage with exhaustive checking
90
+ function render(state: RequestState<User>) {
91
+ switch (state.status) {
92
+ case 'idle':
93
+ return <Placeholder />;
94
+ case 'loading':
95
+ return <Spinner />;
96
+ case 'success':
97
+ return <UserCard user={state.data} />;
98
+ case 'error':
99
+ return <Error message={state.error.message} />;
100
+ }
101
+ }
102
+ ```
103
+
104
+ ### PAT-005: Use never for exhaustive checks
105
+
106
+ ```typescript
107
+ function assertNever(value: never): never {
108
+ throw new Error(`Unexpected value: ${value}`);
109
+ }
110
+
111
+ type Shape = { kind: 'circle'; radius: number } | { kind: 'square'; side: number };
112
+
113
+ function getArea(shape: Shape): number {
114
+ switch (shape.kind) {
115
+ case 'circle':
116
+ return Math.PI * shape.radius ** 2;
117
+ case 'square':
118
+ return shape.side ** 2;
119
+ default:
120
+ return assertNever(shape); // Compile error if new shape added
121
+ }
122
+ }
123
+ ```
124
+
125
+ ---
126
+
127
+ ## Type Guards
128
+
129
+ ### PAT-006: Create custom type guards
130
+
131
+ ```typescript
132
+ // Simple type guard
133
+ function isString(value: unknown): value is string {
134
+ return typeof value === 'string';
135
+ }
136
+
137
+ // Object type guard
138
+ function isUser(value: unknown): value is User {
139
+ return (
140
+ typeof value === 'object' &&
141
+ value !== null &&
142
+ 'id' in value &&
143
+ 'email' in value
144
+ );
145
+ }
146
+
147
+ // With Zod (preferred)
148
+ function isUser(value: unknown): value is User {
149
+ return userSchema.safeParse(value).success;
150
+ }
151
+
152
+ // Array type guard
153
+ function isStringArray(value: unknown): value is string[] {
154
+ return Array.isArray(value) && value.every(isString);
155
+ }
156
+ ```
157
+
158
+ ### PAT-007: Use assertion functions
159
+
160
+ ```typescript
161
+ function assertDefined<T>(
162
+ value: T | undefined | null,
163
+ message?: string
164
+ ): asserts value is T {
165
+ if (value === undefined || value === null) {
166
+ throw new Error(message ?? 'Value is not defined');
167
+ }
168
+ }
169
+
170
+ // Usage
171
+ const user = users.find(u => u.id === id);
172
+ assertDefined(user, `User ${id} not found`);
173
+ // user is now User (not User | undefined)
174
+ ```
175
+
176
+ ---
177
+
178
+ ## Generics
179
+
180
+ ### PAT-008: Use generics for reusable types
181
+
182
+ ```typescript
183
+ // Generic response wrapper
184
+ interface ApiResponse<T> {
185
+ data: T;
186
+ timestamp: Date;
187
+ }
188
+
189
+ // Generic result type
190
+ type Result<T, E = Error> =
191
+ | { ok: true; value: T }
192
+ | { ok: false; error: E };
193
+
194
+ // Generic with constraints
195
+ function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
196
+ return obj[key];
197
+ }
198
+
199
+ // Generic with default
200
+ interface Paginated<T, Meta = { page: number; total: number }> {
201
+ items: T[];
202
+ meta: Meta;
203
+ }
204
+ ```
205
+
206
+ ### PAT-009: Constrain generics appropriately
207
+
208
+ ```typescript
209
+ // Require specific shape
210
+ function clone<T extends object>(obj: T): T {
211
+ return { ...obj };
212
+ }
213
+
214
+ // Require specific properties
215
+ function getId<T extends { id: string }>(item: T): string {
216
+ return item.id;
217
+ }
218
+
219
+ // Multiple constraints
220
+ function merge<T extends object, U extends object>(a: T, b: U): T & U {
221
+ return { ...a, ...b };
222
+ }
223
+ ```
224
+
225
+ ---
226
+
227
+ ## Async Patterns
228
+
229
+ ### PAT-010: Type async iterators
230
+
231
+ ```typescript
232
+ async function* paginate<T>(
233
+ fetcher: (page: number) => Promise<{ items: T[]; hasMore: boolean }>
234
+ ): AsyncGenerator<T[], void, unknown> {
235
+ let page = 1;
236
+ let hasMore = true;
237
+
238
+ while (hasMore) {
239
+ const result = await fetcher(page);
240
+ yield result.items;
241
+ hasMore = result.hasMore;
242
+ page++;
243
+ }
244
+ }
245
+
246
+ // Usage
247
+ for await (const batch of paginate(fetchUsers)) {
248
+ await processUsers(batch);
249
+ }
250
+ ```
251
+
252
+ ### PAT-011: Use AbortSignal for cancellation
253
+
254
+ ```typescript
255
+ async function fetchWithTimeout<T>(
256
+ url: string,
257
+ options: { timeout: number; signal?: AbortSignal }
258
+ ): Promise<T> {
259
+ const controller = new AbortController();
260
+ const timeoutId = setTimeout(() => controller.abort(), options.timeout);
261
+
262
+ // Combine signals
263
+ options.signal?.addEventListener('abort', () => controller.abort());
264
+
265
+ try {
266
+ const response = await fetch(url, { signal: controller.signal });
267
+ return response.json();
268
+ } finally {
269
+ clearTimeout(timeoutId);
270
+ }
271
+ }
272
+ ```
273
+
274
+ ---
275
+
276
+ ## Object Patterns
277
+
278
+ ### PAT-012: Use satisfies for type checking with inference
279
+
280
+ ```typescript
281
+ // satisfies checks type but preserves literal types
282
+ const config = {
283
+ port: 3000,
284
+ host: 'localhost',
285
+ debug: true,
286
+ } satisfies Config;
287
+
288
+ // config.port is number, not widened
289
+ // But still validated against Config shape
290
+
291
+ // Useful for route definitions
292
+ const routes = {
293
+ home: '/',
294
+ users: '/users',
295
+ userDetail: '/users/:id',
296
+ } satisfies Record<string, string>;
297
+
298
+ // routes.home is '/', not string
299
+ ```
300
+
301
+ ### PAT-013: Immutable updates
302
+
303
+ ```typescript
304
+ // Spread for shallow copies
305
+ const updated = { ...user, name: 'New Name' };
306
+
307
+ // For arrays
308
+ const added = [...items, newItem];
309
+ const removed = items.filter(i => i.id !== id);
310
+ const replaced = items.map(i => i.id === id ? updated : i);
311
+
312
+ // For nested updates
313
+ const nested = {
314
+ ...state,
315
+ user: {
316
+ ...state.user,
317
+ profile: {
318
+ ...state.user.profile,
319
+ name: 'New Name',
320
+ },
321
+ },
322
+ };
323
+
324
+ // Or use immer for complex updates
325
+ import { produce } from 'immer';
326
+ const nested = produce(state, draft => {
327
+ draft.user.profile.name = 'New Name';
328
+ });
329
+ ```
330
+
331
+ ---
332
+
333
+ ## Module Patterns
334
+
335
+ ### PAT-014: Use namespace for grouping
336
+
337
+ ```typescript
338
+ // Group related functions
339
+ export const User = {
340
+ create: (data: CreateUserData): User => ({ ... }),
341
+ validate: (user: unknown): user is User => userSchema.safeParse(user).success,
342
+ format: (user: User): string => `${user.name} <${user.email}>`,
343
+ } as const;
344
+
345
+ // Usage
346
+ const user = User.create({ name: 'John', email: 'john@example.com' });
347
+ if (User.validate(data)) { ... }
348
+ ```
349
+
350
+ ### PAT-015: Builder pattern for complex objects
351
+
352
+ ```typescript
353
+ class QueryBuilder<T> {
354
+ private filters: Filter[] = [];
355
+ private sortBy?: string;
356
+ private limit?: number;
357
+
358
+ where(filter: Filter): this {
359
+ this.filters.push(filter);
360
+ return this;
361
+ }
362
+
363
+ orderBy(field: keyof T): this {
364
+ this.sortBy = String(field);
365
+ return this;
366
+ }
367
+
368
+ take(count: number): this {
369
+ this.limit = count;
370
+ return this;
371
+ }
372
+
373
+ build(): Query<T> {
374
+ return { filters: this.filters, sortBy: this.sortBy, limit: this.limit };
375
+ }
376
+ }
377
+
378
+ // Usage
379
+ const query = new QueryBuilder<User>()
380
+ .where({ field: 'status', op: 'eq', value: 'active' })
381
+ .orderBy('createdAt')
382
+ .take(10)
383
+ .build();
384
+ ```