@tonycasey/lisa 0.5.13

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 (48) hide show
  1. package/README.md +42 -0
  2. package/dist/cli.js +390 -0
  3. package/dist/lib/interfaces/IDockerClient.js +2 -0
  4. package/dist/lib/interfaces/IMcpClient.js +2 -0
  5. package/dist/lib/interfaces/IServices.js +2 -0
  6. package/dist/lib/interfaces/ITemplateCopier.js +2 -0
  7. package/dist/lib/mcp.js +35 -0
  8. package/dist/lib/services.js +57 -0
  9. package/dist/package.json +36 -0
  10. package/dist/templates/agents/.sample.env +12 -0
  11. package/dist/templates/agents/docs/STORAGE_SETUP.md +161 -0
  12. package/dist/templates/agents/skills/common/group-id.js +193 -0
  13. package/dist/templates/agents/skills/init-review/SKILL.md +119 -0
  14. package/dist/templates/agents/skills/init-review/scripts/ai-enrich.js +258 -0
  15. package/dist/templates/agents/skills/init-review/scripts/init-review.js +769 -0
  16. package/dist/templates/agents/skills/lisa/SKILL.md +92 -0
  17. package/dist/templates/agents/skills/lisa/cache/.gitkeep +0 -0
  18. package/dist/templates/agents/skills/lisa/scripts/storage.js +374 -0
  19. package/dist/templates/agents/skills/memory/SKILL.md +31 -0
  20. package/dist/templates/agents/skills/memory/scripts/memory.js +533 -0
  21. package/dist/templates/agents/skills/prompt/SKILL.md +19 -0
  22. package/dist/templates/agents/skills/prompt/scripts/prompt.js +184 -0
  23. package/dist/templates/agents/skills/tasks/SKILL.md +31 -0
  24. package/dist/templates/agents/skills/tasks/scripts/tasks.js +489 -0
  25. package/dist/templates/claude/config.js +40 -0
  26. package/dist/templates/claude/hooks/README.md +158 -0
  27. package/dist/templates/claude/hooks/common/complexity-rater.js +290 -0
  28. package/dist/templates/claude/hooks/common/context.js +263 -0
  29. package/dist/templates/claude/hooks/common/group-id.js +188 -0
  30. package/dist/templates/claude/hooks/common/mcp-client.js +131 -0
  31. package/dist/templates/claude/hooks/common/transcript-parser.js +256 -0
  32. package/dist/templates/claude/hooks/common/zep-client.js +175 -0
  33. package/dist/templates/claude/hooks/session-start.js +401 -0
  34. package/dist/templates/claude/hooks/session-stop-worker.js +341 -0
  35. package/dist/templates/claude/hooks/session-stop.js +122 -0
  36. package/dist/templates/claude/hooks/user-prompt-submit.js +256 -0
  37. package/dist/templates/claude/settings.json +46 -0
  38. package/dist/templates/docker/.env.lisa.example +17 -0
  39. package/dist/templates/docker/docker-compose.graphiti.yml +45 -0
  40. package/dist/templates/rules/shared/clean-architecture.md +333 -0
  41. package/dist/templates/rules/shared/code-quality-rules.md +469 -0
  42. package/dist/templates/rules/shared/git-rules.md +64 -0
  43. package/dist/templates/rules/shared/testing-principles.md +469 -0
  44. package/dist/templates/rules/typescript/coding-standards.md +751 -0
  45. package/dist/templates/rules/typescript/testing.md +629 -0
  46. package/dist/templates/rules/typescript/typescript-config-guide.md +465 -0
  47. package/package.json +64 -0
  48. package/scripts/postinstall.js +710 -0
@@ -0,0 +1,751 @@
1
+ # TypeScript Coding Standards
2
+
3
+ **Note:** This document covers TypeScript-specific conventions. For universal architecture principles, see `shared/clean-architecture.md`.
4
+
5
+ ---
6
+
7
+ ## 📘 IMPORTANT: TypeScript Configuration
8
+
9
+ **Before writing TypeScript code, understand your project's configuration:**
10
+
11
+ See **[TypeScript Configuration Guide](./typescript-config-guide.md)** for:
12
+ - How to configure `tsconfig.json` for development vs. production
13
+ - Common configuration issues and solutions (300+ errors resolved)
14
+ - When to use relaxed vs. strict settings
15
+ - Monorepo configuration best practices
16
+ - When to use `@ts-nocheck` for examples and tests
17
+
18
+ **Key takeaways:**
19
+ - Start with **relaxed settings** during active development (`strict: false`)
20
+ - Enable **strict mode** when stabilizing for production
21
+ - Use separate `tsconfig.test.json` for tests
22
+ - Explicitly exclude examples, tests, and demo files
23
+
24
+ ---
25
+
26
+ ## 🚨 CRITICAL: Strict Null Checks
27
+
28
+ ### ALWAYS Handle Undefined/Null Values
29
+
30
+ **TypeScript strict mode will cause compile errors if you don't explicitly handle `undefined` and `null`.**
31
+
32
+ This is the #1 source of TypeScript errors. Every value that might be undefined MUST be checked before use.
33
+
34
+ #### Common Errors and Fixes
35
+
36
+ **❌ ERROR: TS2532 "Object is possibly 'undefined'"**
37
+
38
+ ```typescript
39
+ // ❌ BAD - Will cause TS2532 error
40
+ function processUser(user: User | undefined) {
41
+ return user.name; // ERROR: Object is possibly 'undefined'
42
+ }
43
+
44
+ // ✅ FIX #1 - Null check
45
+ function processUser(user: User | undefined) {
46
+ if (!user) {
47
+ throw new UserNotFoundError();
48
+ }
49
+ return user.name; // ✅ TypeScript knows user is defined here
50
+ }
51
+
52
+ // ✅ FIX #2 - Optional chaining
53
+ function processUser(user: User | undefined): string | undefined {
54
+ return user?.name; // Returns undefined if user is undefined
55
+ }
56
+
57
+ // ✅ FIX #3 - Nullish coalescing
58
+ function processUser(user: User | undefined): string {
59
+ return user?.name ?? 'Unknown'; // Returns 'Unknown' if undefined
60
+ }
61
+
62
+ // ✅ FIX #4 - Type guard
63
+ function processUser(user: User | undefined): string {
64
+ if (user === undefined) {
65
+ return 'Unknown';
66
+ }
67
+ return user.name; // ✅ user is User here
68
+ }
69
+ ```
70
+
71
+ **❌ ERROR: TS2345 "Type 'X | undefined' is not assignable to parameter of type 'X'"**
72
+
73
+ ```typescript
74
+ // ❌ BAD - Passing possibly undefined to function expecting defined value
75
+ function sendEmail(email: string) { /* ... */ }
76
+
77
+ const user: User | undefined = await getUser(id);
78
+ sendEmail(user.email); // ERROR: user is possibly undefined
79
+
80
+ // ✅ FIX #1 - Check before passing
81
+ const user = await getUser(id);
82
+ if (!user) {
83
+ throw new UserNotFoundError(id);
84
+ }
85
+ sendEmail(user.email); // ✅ TypeScript knows user is defined
86
+
87
+ // ✅ FIX #2 - Early return
88
+ const user = await getUser(id);
89
+ if (!user) return;
90
+ sendEmail(user.email); // ✅ TypeScript knows user is defined
91
+
92
+ // ✅ FIX #3 - Guard clause
93
+ const user = await getUser(id);
94
+ if (user === undefined) {
95
+ throw new UserNotFoundError(id);
96
+ }
97
+ sendEmail(user.email); // ✅ TypeScript knows user is User
98
+ ```
99
+
100
+ #### Array Operations
101
+
102
+ **❌ BAD - .find() returns undefined**
103
+ ```typescript
104
+ const users: User[] = [/* ... */];
105
+ const user = users.find(u => u.id === targetId);
106
+ processUser(user); // ERROR: user might be undefined
107
+
108
+ // ✅ GOOD - Check result
109
+ const user = users.find(u => u.id === targetId);
110
+ if (!user) {
111
+ throw new UserNotFoundError(targetId);
112
+ }
113
+ processUser(user); // ✅ user is User
114
+ ```
115
+
116
+ #### Object Property Access
117
+
118
+ **❌ BAD - Accessing nested properties**
119
+ ```typescript
120
+ const name = user.profile.name; // ERROR: profile might be undefined
121
+
122
+ // ✅ GOOD - Optional chaining
123
+ const name = user.profile?.name; // Returns undefined if profile is undefined
124
+ const name = user.profile?.name ?? 'Unknown'; // Default value
125
+ ```
126
+
127
+ #### Function Return Values
128
+
129
+ **When repository methods return `T | null`, ALWAYS check the result:**
130
+
131
+ ```typescript
132
+ // Repository method signature
133
+ interface IUserRepository {
134
+ getById(id: string): Promise<User | null>; // Can return null!
135
+ }
136
+
137
+ // ❌ BAD - Not checking for null
138
+ async function getUser(id: string): Promise<User> {
139
+ const user = await userRepository.getById(id);
140
+ return user; // ERROR: Type 'User | null' is not assignable to 'User'
141
+ }
142
+
143
+ // ✅ GOOD - Check and throw
144
+ async function getUser(id: string): Promise<User> {
145
+ const user = await userRepository.getById(id);
146
+ if (!user) {
147
+ throw new UserNotFoundError(id);
148
+ }
149
+ return user; // ✅ TypeScript knows user is User, not null
150
+ }
151
+
152
+ // ✅ GOOD - Return nullable type
153
+ async function getUser(id: string): Promise<User | null> {
154
+ return await userRepository.getById(id); // Explicitly nullable return
155
+ }
156
+ ```
157
+
158
+ ### MANDATORY Checks for Every Potentially Undefined Value
159
+
160
+ **Before using ANY value that might be undefined, you MUST:**
161
+
162
+ 1. **Check if it exists**: `if (!value) { ... }`
163
+ 2. **Use optional chaining**: `value?.property`
164
+ 3. **Provide a default**: `value ?? defaultValue`
165
+ 4. **Use a type guard**: `if (value === undefined) { ... }`
166
+
167
+ **No exceptions. TypeScript will not compile otherwise.**
168
+
169
+ ## Type Safety
170
+
171
+ ### Strict Mode
172
+
173
+ **ALWAYS use TypeScript strict mode:**
174
+
175
+ ```json
176
+ // tsconfig.json
177
+ {
178
+ "compilerOptions": {
179
+ "strict": true,
180
+ "noImplicitAny": true,
181
+ "strictNullChecks": true,
182
+ "strictFunctionTypes": true,
183
+ "strictPropertyInitialization": true
184
+ }
185
+ }
186
+ ```
187
+
188
+ ### Never Use `any`
189
+
190
+ **NEVER use `any` type** - it defeats the purpose of TypeScript.
191
+
192
+ ```typescript
193
+ // ❌ BAD
194
+ function processData(data: any): any {
195
+ return data.something;
196
+ }
197
+
198
+ // ✅ GOOD
199
+ function processData(data: ProductData): ProcessedData {
200
+ return {
201
+ id: data.id,
202
+ name: data.name
203
+ };
204
+ }
205
+ ```
206
+
207
+ **Alternatives to `any`:**
208
+ - `unknown` - for truly unknown types (with type guards)
209
+ - Generic types - `<T>` for reusable code
210
+ - Union types - `string | number`
211
+ - Specific interfaces - define the shape
212
+
213
+ ### Explicit Return Types
214
+
215
+ Always specify return types for public functions:
216
+
217
+ ```typescript
218
+ // ❌ BAD - inferred return type
219
+ export function calculateTotal(items) {
220
+ return items.reduce((sum, item) => sum + item.price, 0);
221
+ }
222
+
223
+ // ✅ GOOD - explicit return type
224
+ export function calculateTotal(items: CartItem[]): number {
225
+ return items.reduce((sum, item) => sum + item.price, 0);
226
+ }
227
+ ```
228
+
229
+ ### Type vs Interface
230
+
231
+ **Prefer `interface` for object shapes:**
232
+
233
+ ```typescript
234
+ // ✅ GOOD - interface for objects
235
+ export interface IProduct {
236
+ id: string;
237
+ name: string;
238
+ price: number;
239
+ }
240
+ ```
241
+
242
+ **Use `type` for:**
243
+ - Unions: `type Status = 'active' | 'inactive'`
244
+ - Intersections: `type Combined = TypeA & TypeB`
245
+ - Mapped types: `type Readonly<T> = { readonly [P in keyof T]: T[P] }`
246
+ - Tuples: `type Point = [number, number]`
247
+
248
+ ## Naming Conventions
249
+
250
+ ### Interfaces
251
+
252
+ **Prefix interfaces with `I`:**
253
+
254
+ ```typescript
255
+ // ✅ GOOD
256
+ export interface IProductService {
257
+ getById(id: string): Promise<IProduct>;
258
+ }
259
+
260
+ export interface IProductRepository {
261
+ save(product: IProduct): Promise<void>;
262
+ }
263
+
264
+ // ❌ BAD
265
+ export interface ProductService { ... } // Missing 'I' prefix
266
+ ```
267
+
268
+ ### Classes
269
+
270
+ **PascalCase for classes:**
271
+
272
+ ```typescript
273
+ // ✅ GOOD
274
+ export class ProductService implements IProductService {
275
+ // ...
276
+ }
277
+
278
+ export class ProductRepository implements IProductRepository {
279
+ // ...
280
+ }
281
+ ```
282
+
283
+ ### Files
284
+
285
+ **File names must match the primary export:**
286
+
287
+ ```typescript
288
+ // File: IProductRepository.ts
289
+ export interface IProductRepository { ... }
290
+
291
+ // File: ProductRepository.ts
292
+ export class ProductRepository implements IProductRepository { ... }
293
+
294
+ // File: ProductService.ts
295
+ export class ProductService implements IProductService { ... }
296
+ ```
297
+
298
+ ### Constants
299
+
300
+ **UPPER_SNAKE_CASE for constants:**
301
+
302
+ ```typescript
303
+ export const MAX_RETRY_ATTEMPTS = 3;
304
+ export const DEFAULT_TIMEOUT_MS = 5000;
305
+ export const API_BASE_URL = 'https://api.example.com';
306
+ ```
307
+
308
+ ### Variables and Functions
309
+
310
+ **camelCase for variables and functions:**
311
+
312
+ ```typescript
313
+ const productList: IProduct[] = [];
314
+ const userId: string = '123';
315
+
316
+ function calculateDiscount(price: number): number {
317
+ return price * 0.1;
318
+ }
319
+
320
+ async function fetchUserData(id: string): Promise<IUser> {
321
+ // ...
322
+ }
323
+ ```
324
+
325
+ ## File Organization
326
+
327
+ ### One Interface Per File
328
+
329
+ Each interface gets its own file:
330
+
331
+ ```typescript
332
+ // ✅ GOOD
333
+ // File: IProductService.ts
334
+ export interface IProductService {
335
+ getById(id: string): Promise<IProduct>;
336
+ }
337
+
338
+ // File: IOrderService.ts
339
+ export interface IOrderService {
340
+ create(order: IOrder): Promise<string>;
341
+ }
342
+ ```
343
+
344
+ ```typescript
345
+ // ❌ BAD - multiple interfaces in one file
346
+ // File: Services.ts
347
+ export interface IProductService { ... }
348
+ export interface IOrderService { ... }
349
+ export interface IUserService { ... }
350
+ ```
351
+
352
+ **Exception:** Small, related interfaces can be together:
353
+
354
+ ```typescript
355
+ // File: IPaginationTypes.ts
356
+ export interface IPaginationRequest {
357
+ page: number;
358
+ pageSize: number;
359
+ }
360
+
361
+ export interface IPaginationResponse<T> {
362
+ items: T[];
363
+ total: number;
364
+ page: number;
365
+ }
366
+ ```
367
+
368
+ ### Index Files for Clean Imports
369
+
370
+ Use `AgentEnums.ts` files to export from directories:
371
+
372
+ ```typescript
373
+ // File: src/domain/interfaces/AgentEnums.ts
374
+ export { IProductRepository } from './IProductRepository';
375
+ export { IOrderRepository } from './IOrderRepository';
376
+ export { IUserRepository } from './IUserRepository';
377
+
378
+ // Now you can import as:
379
+ import { IProductRepository, IOrderRepository } from '@/domain/interfaces';
380
+ ```
381
+
382
+ ## Clean Architecture Implementation
383
+
384
+ ### Repository Interface (Domain)
385
+
386
+ ```typescript
387
+ // File: src/domain/interfaces/IProductRepository.ts
388
+ import { IProduct } from '../entities/IProduct';
389
+
390
+ export interface IProductRepository {
391
+ getById(id: string): Promise<IProduct | null>;
392
+ save(product: IProduct): Promise<void>;
393
+ delete(id: string): Promise<void>;
394
+ findByCategory(categoryId: string): Promise<IProduct[]>;
395
+ }
396
+ ```
397
+
398
+ ### Repository Implementation (Infrastructure)
399
+
400
+ ```typescript
401
+ // File: src/infrastructure/repositories/ProductRepository.ts
402
+ import { IProductRepository } from '@/domain/interfaces/IProductRepository';
403
+ import { IProduct } from '@/domain/entities/IProduct';
404
+ import { IDatabaseService } from '../interfaces/IDatabaseService';
405
+ import { ProductNotFoundError } from '@/domain/errors/ProductNotFoundError';
406
+
407
+ export class ProductRepository implements IProductRepository {
408
+ constructor(private readonly db: IDatabaseService) {}
409
+
410
+ async getById(id: string): Promise<IProduct | null> {
411
+ try {
412
+ const doc = await this.db.readDocDataAtPath(`products/${id}`);
413
+ return doc ? (doc as IProduct) : null;
414
+ } catch (error) {
415
+ throw new RepositoryError(`Failed to fetch product: ${id}`, { originalError: error });
416
+ }
417
+ }
418
+
419
+ async save(product: IProduct): Promise<void> {
420
+ await this.db.writeDocDataAtPath(`products/${product.id}`, product);
421
+ }
422
+
423
+ async delete(id: string): Promise<void> {
424
+ await this.db.deleteDocAtPath(`products/${id}`);
425
+ }
426
+
427
+ async findByCategory(categoryId: string): Promise<IProduct[]> {
428
+ const docs = await this.db.queryCollection('products', {
429
+ where: ['categoryId', '==', categoryId]
430
+ });
431
+ return docs as IProduct[];
432
+ }
433
+ }
434
+ ```
435
+
436
+ ### Service (Application)
437
+
438
+ ```typescript
439
+ // File: src/application/services/ProductService.ts
440
+ import { IProductService } from '../interfaces/IProductService';
441
+ import { IProductRepository } from '@/domain/interfaces/IProductRepository';
442
+ import { IProduct } from '@/domain/entities/IProduct';
443
+ import { ProductNotFoundError } from '@/domain/errors/ProductNotFoundError';
444
+
445
+ export class ProductService implements IProductService {
446
+ constructor(
447
+ private readonly productRepository: IProductRepository,
448
+ private readonly logger: ILogger
449
+ ) {}
450
+
451
+ async getProduct(id: string): Promise<IProduct> {
452
+ const product = await this.productRepository.getById(id);
453
+
454
+ if (!product) {
455
+ this.logger.warn('Product not found', { productId: id });
456
+ throw new ProductNotFoundError(id);
457
+ }
458
+
459
+ this.logger.info('Product retrieved', { productId: id });
460
+ return product;
461
+ }
462
+
463
+ async createProduct(data: CreateProductData): Promise<IProduct> {
464
+ // Validation
465
+ this.validateProductData(data);
466
+
467
+ // Create entity
468
+ const product: IProduct = {
469
+ id: generateId(),
470
+ ...data,
471
+ createdAt: new Date(),
472
+ updatedAt: new Date()
473
+ };
474
+
475
+ // Save
476
+ await this.productRepository.save(product);
477
+
478
+ this.logger.info('Product created', { productId: product.id });
479
+ return product;
480
+ }
481
+
482
+ private validateProductData(data: CreateProductData): void {
483
+ if (!data.name || data.name.trim().length === 0) {
484
+ throw new ValidationError('Product name is required');
485
+ }
486
+ if (data.price <= 0) {
487
+ throw new ValidationError('Product price must be positive');
488
+ }
489
+ }
490
+ }
491
+ ```
492
+
493
+ ## Constructor Injection
494
+
495
+ **ALWAYS use constructor injection for dependencies:**
496
+
497
+ ```typescript
498
+ // ✅ GOOD - Constructor injection
499
+ export class OrderService implements IOrderService {
500
+ constructor(
501
+ private readonly orderRepository: IOrderRepository,
502
+ private readonly productRepository: IProductRepository,
503
+ private readonly logger: ILogger
504
+ ) {}
505
+
506
+ async createOrder(data: CreateOrderData): Promise<IOrder> {
507
+ // Use injected dependencies
508
+ const products = await this.productRepository.findByIds(data.productIds);
509
+ // ...
510
+ }
511
+ }
512
+
513
+ // ❌ BAD - Direct instantiation
514
+ export class OrderService implements IOrderService {
515
+ async createOrder(data: CreateOrderData): Promise<IOrder> {
516
+ const orderRepo = new OrderRepository(); // Hard to test!
517
+ // ...
518
+ }
519
+ }
520
+ ```
521
+
522
+ ### Readonly Dependencies
523
+
524
+ Mark injected dependencies as `readonly`:
525
+
526
+ ```typescript
527
+ constructor(
528
+ private readonly orderRepository: IOrderRepository, // ✅ readonly
529
+ private readonly logger: ILogger // ✅ readonly
530
+ ) {}
531
+ ```
532
+
533
+ This prevents accidental reassignment.
534
+
535
+ ## Error Handling
536
+
537
+ ### Domain Errors
538
+
539
+ ```typescript
540
+ // File: src/domain/errors/DomainError.ts
541
+ export class DomainError extends Error {
542
+ constructor(
543
+ message: string,
544
+ public readonly statusCode: number = 500,
545
+ public readonly data?: Record<string, unknown>
546
+ ) {
547
+ super(message);
548
+ this.name = this.constructor.name;
549
+ Error.captureStackTrace(this, this.constructor);
550
+ }
551
+ }
552
+
553
+ // File: src/domain/errors/ProductNotFoundError.ts
554
+ export class ProductNotFoundError extends DomainError {
555
+ constructor(productId: string) {
556
+ super(
557
+ `Product not found: ${productId}`,
558
+ 404,
559
+ { productId }
560
+ );
561
+ }
562
+ }
563
+ ```
564
+
565
+ ### Try-Catch with Proper Types
566
+
567
+ ```typescript
568
+ // ✅ GOOD - Proper error handling
569
+ async function fetchProduct(id: string): Promise<IProduct> {
570
+ try {
571
+ const product = await productRepository.getById(id);
572
+ if (!product) {
573
+ throw new ProductNotFoundError(id);
574
+ }
575
+ return product;
576
+ } catch (error) {
577
+ if (error instanceof ProductNotFoundError) {
578
+ throw error; // Re-throw domain errors
579
+ }
580
+
581
+ // Transform unknown errors
582
+ logger.error('Unexpected error fetching product', {
583
+ error,
584
+ productId: id
585
+ });
586
+
587
+ throw new ServiceError(
588
+ 'Failed to fetch product',
589
+ { productId: id, originalError: error }
590
+ );
591
+ }
592
+ }
593
+ ```
594
+
595
+ ## Async/Await
596
+
597
+ **ALWAYS use async/await over raw Promises:**
598
+
599
+ ```typescript
600
+ // ✅ GOOD - async/await
601
+ async function getUser(id: string): Promise<IUser> {
602
+ const user = await userRepository.getById(id);
603
+ return user;
604
+ }
605
+
606
+ // ❌ BAD - raw promises
607
+ function getUser(id: string): Promise<IUser> {
608
+ return userRepository.getById(id).then(user => {
609
+ return user;
610
+ });
611
+ }
612
+ ```
613
+
614
+ ### Parallel Operations
615
+
616
+ Use `Promise.all()` for parallel async operations:
617
+
618
+ ```typescript
619
+ // ✅ GOOD - parallel execution
620
+ async function getUserWithOrders(userId: string): Promise<UserWithOrders> {
621
+ const [user, orders] = await Promise.all([
622
+ userRepository.getById(userId),
623
+ orderRepository.findByUser(userId)
624
+ ]);
625
+
626
+ return { user, orders };
627
+ }
628
+
629
+ // ❌ BAD - sequential (slower)
630
+ async function getUserWithOrders(userId: string): Promise<UserWithOrders> {
631
+ const user = await userRepository.getById(userId);
632
+ const orders = await orderRepository.findByUser(userId);
633
+ return { user, orders };
634
+ }
635
+ ```
636
+
637
+ ## Generics
638
+
639
+ Use generics for reusable, type-safe code:
640
+
641
+ ```typescript
642
+ // Generic repository interface
643
+ export interface IRepository<T> {
644
+ getById(id: string): Promise<T | null>;
645
+ save(entity: T): Promise<void>;
646
+ delete(id: string): Promise<void>;
647
+ }
648
+
649
+ // Specific implementations
650
+ export interface IProductRepository extends IRepository<IProduct> {
651
+ findByCategory(categoryId: string): Promise<IProduct[]>;
652
+ }
653
+
654
+ // Generic function
655
+ function findById<T extends { id: string }>(
656
+ items: T[],
657
+ id: string
658
+ ): T | undefined {
659
+ return items.find(item => item.id === id);
660
+ }
661
+ ```
662
+
663
+ ## Utility Types
664
+
665
+ Use TypeScript utility types:
666
+
667
+ ```typescript
668
+ // Make all properties optional
669
+ type PartialProduct = Partial<IProduct>;
670
+
671
+ // Make all properties required
672
+ type RequiredProduct = Required<IProduct>;
673
+
674
+ // Make all properties readonly
675
+ type ReadonlyProduct = Readonly<IProduct>;
676
+
677
+ // Pick specific properties
678
+ type ProductSummary = Pick<IProduct, 'id' | 'name' | 'price'>;
679
+
680
+ // Omit specific properties
681
+ type ProductWithoutId = Omit<IProduct, 'id'>;
682
+
683
+ // Extract return type of function
684
+ type ProductData = ReturnType<typeof getProduct>;
685
+
686
+ // Extract parameters of function
687
+ type GetProductParams = Parameters<typeof getProduct>;
688
+ ```
689
+
690
+ ## Code Quality Checklist
691
+
692
+ - [ ] TypeScript strict mode enabled
693
+ - [ ] No `any` types used
694
+ - [ ] Explicit return types on public functions
695
+ - [ ] Interfaces prefixed with 'I'
696
+ - [ ] File names match primary export
697
+ - [ ] One interface per file
698
+ - [ ] Constructor injection used
699
+ - [ ] Dependencies marked as readonly
700
+ - [ ] Proper error handling with domain errors
701
+ - [ ] async/await preferred over raw promises
702
+ - [ ] Generics used where appropriate
703
+ - [ ] Index files for clean imports
704
+
705
+ ## Tooling
706
+
707
+ ### Recommended Tools
708
+
709
+ - **Compiler**: `tsc` - TypeScript compiler
710
+ - **Linter**: `ESLint` with TypeScript plugin
711
+ - **Formatter**: `Prettier`
712
+ - **Testing**: `Jest` with `ts-jest`
713
+ - **Build**: `tsc` or bundlers (webpack, rollup, esbuild)
714
+
715
+ ### ESLint Configuration
716
+
717
+ ```javascript
718
+ // .eslintrc.js
719
+ module.exports = {
720
+ parser: '@typescript-eslint/parser',
721
+ plugins: ['@typescript-eslint'],
722
+ extends: [
723
+ 'eslint:recommended',
724
+ 'plugin:@typescript-eslint/recommended',
725
+ ],
726
+ rules: {
727
+ '@typescript-eslint/no-explicit-any': 'error',
728
+ '@typescript-eslint/explicit-function-return-type': 'warn',
729
+ '@typescript-eslint/no-unused-vars': 'error',
730
+ }
731
+ };
732
+ ```
733
+
734
+ ### Pre-commit Checks
735
+
736
+ ```bash
737
+ # Run TypeScript compiler
738
+ npx tsc --noEmit
739
+
740
+ # Run linter
741
+ npx eslint "src/**/*.ts"
742
+
743
+ # Run formatter
744
+ npx prettier --check "src/**/*.ts"
745
+
746
+ # Run tests
747
+ npm test
748
+ ```
749
+
750
+ For testing specifics, see `languages/typescript/testing.md`.
751
+ For universal architecture principles, see `shared/clean-architecture.md`.