@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,629 @@
1
+ # TypeScript Testing Standards
2
+
3
+ **Note:** For universal testing principles, see `shared/testing-principles.md`. This document covers TypeScript and Jest-specific practices.
4
+
5
+ ## Testing Framework
6
+
7
+ We use **Jest** with **ts-jest** for TypeScript projects.
8
+
9
+ ### Setup
10
+
11
+ ```bash
12
+ npm install --save-dev jest ts-jest @types/jest
13
+ npx ts-jest config:init
14
+ ```
15
+
16
+ ### Configuration
17
+
18
+ ```javascript
19
+ // jest.config.js
20
+ module.exports = {
21
+ preset: 'ts-jest',
22
+ testEnvironment: 'node',
23
+ roots: ['<rootDir>/tests'],
24
+ testMatch: ['**/*.test.ts'],
25
+ collectCoverageFrom: [
26
+ 'src/**/*.ts',
27
+ '!src/**/*.d.ts',
28
+ '!src/**/*.test.ts'
29
+ ],
30
+ coverageThreshold: {
31
+ global: {
32
+ branches: 80,
33
+ functions: 80,
34
+ lines: 80,
35
+ statements: 80
36
+ }
37
+ }
38
+ };
39
+ ```
40
+
41
+ ## Test Structure
42
+
43
+ ### File Naming
44
+
45
+ ```
46
+ tests/
47
+ ├── unit/
48
+ │ ├── domain/
49
+ │ │ └── services/
50
+ │ │ └── ProductService.test.ts
51
+ │ └── application/
52
+ │ └── services/
53
+ │ └── OrderService.test.ts
54
+ └── integration/
55
+ └── repositories/
56
+ └── ProductRepository.integration.test.ts
57
+ ```
58
+
59
+ ### Describe/It Blocks
60
+
61
+ ```typescript
62
+ describe('ProductService', () => {
63
+ describe('getProductById', () => {
64
+ it('should return product when it exists', async () => {
65
+ // Test implementation
66
+ });
67
+
68
+ it('should throw ProductNotFoundError when product does not exist', async () => {
69
+ // Test implementation
70
+ });
71
+
72
+ it('should log warning when product not found', async () => {
73
+ // Test implementation
74
+ });
75
+ });
76
+
77
+ describe('createProduct', () => {
78
+ it('should create product with valid data', async () => {
79
+ // Test implementation
80
+ });
81
+
82
+ it('should throw ValidationError for invalid data', async () => {
83
+ // Test implementation
84
+ });
85
+ });
86
+ });
87
+ ```
88
+
89
+ ## Unit Testing
90
+
91
+ ### Testing Services with Mocked Dependencies
92
+
93
+ ```typescript
94
+ import { ProductService } from '@/application/services/ProductService';
95
+ import { IProductRepository } from '@/domain/interfaces/IProductRepository';
96
+ import { ILogger } from '@/domain/interfaces/ILogger';
97
+ import { ProductNotFoundError } from '@/domain/errors/ProductNotFoundError';
98
+
99
+ describe('ProductService', () => {
100
+ let service: ProductService;
101
+ let mockProductRepository: jest.Mocked<IProductRepository>;
102
+ let mockLogger: jest.Mocked<ILogger>;
103
+
104
+ beforeEach(() => {
105
+ // Create mocks
106
+ mockProductRepository = {
107
+ getById: jest.fn(),
108
+ save: jest.fn(),
109
+ delete: jest.fn(),
110
+ findByCategory: jest.fn()
111
+ } as jest.Mocked<IProductRepository>;
112
+
113
+ mockLogger = {
114
+ info: jest.fn(),
115
+ warn: jest.fn(),
116
+ error: jest.fn()
117
+ } as jest.Mocked<ILogger>;
118
+
119
+ // Create service with mocked dependencies
120
+ service = new ProductService(mockProductRepository, mockLogger);
121
+ });
122
+
123
+ afterEach(() => {
124
+ jest.clearAllMocks();
125
+ });
126
+
127
+ describe('getProduct', () => {
128
+ it('should return product when it exists', async () => {
129
+ // Arrange
130
+ const mockProduct = {
131
+ id: '123',
132
+ name: 'Test Product',
133
+ price: 99.99
134
+ };
135
+ mockProductRepository.getById.mockResolvedValue(mockProduct);
136
+
137
+ // Act
138
+ const result = await service.getProduct('123');
139
+
140
+ // Assert
141
+ expect(result).toEqual(mockProduct);
142
+ expect(mockProductRepository.getById).toHaveBeenCalledWith('123');
143
+ expect(mockProductRepository.getById).toHaveBeenCalledTimes(1);
144
+ expect(mockLogger.info).toHaveBeenCalledWith(
145
+ 'Product retrieved',
146
+ { productId: '123' }
147
+ );
148
+ });
149
+
150
+ it('should throw ProductNotFoundError when product does not exist', async () => {
151
+ // Arrange
152
+ mockProductRepository.getById.mockResolvedValue(null);
153
+
154
+ // Act & Assert
155
+ await expect(service.getProduct('999')).rejects.toThrow(ProductNotFoundError);
156
+ expect(mockProductRepository.getById).toHaveBeenCalledWith('999');
157
+ expect(mockLogger.warn).toHaveBeenCalledWith(
158
+ 'Product not found',
159
+ { productId: '999' }
160
+ );
161
+ });
162
+ });
163
+ });
164
+ ```
165
+
166
+ ### Testing with Type-Safe Mocks
167
+
168
+ ```typescript
169
+ // Helper to create type-safe mocks
170
+ function createMock<T>(): jest.Mocked<T> {
171
+ return {} as jest.Mocked<T>;
172
+ }
173
+
174
+ // Usage
175
+ const mockRepository = createMock<IProductRepository>();
176
+ mockRepository.getById = jest.fn().mockResolvedValue(mockProduct);
177
+ ```
178
+
179
+ ## Integration Testing
180
+
181
+ ### Testing Repositories
182
+
183
+ ```typescript
184
+ import { ProductRepository } from '@/infrastructure/repositories/ProductRepository';
185
+ import { IDatabaseService } from '@/infrastructure/interfaces/IDatabaseService';
186
+ import { IProduct } from '@/domain/entities/IProduct';
187
+
188
+ describe('ProductRepository Integration', () => {
189
+ let repository: ProductRepository;
190
+ let db: IDatabaseService;
191
+
192
+ beforeAll(async () => {
193
+ // Set up test database
194
+ db = await createTestDatabase();
195
+ repository = new ProductRepository(db);
196
+ });
197
+
198
+ afterAll(async () => {
199
+ // Clean up
200
+ await db.disconnect();
201
+ });
202
+
203
+ beforeEach(async () => {
204
+ // Clear database before each test
205
+ await db.clearCollection('products');
206
+ });
207
+
208
+ describe('save and getById', () => {
209
+ it('should save and retrieve product', async () => {
210
+ // Arrange
211
+ const product: IProduct = {
212
+ id: '123',
213
+ name: 'Test Product',
214
+ price: 99.99,
215
+ categoryId: 'cat1'
216
+ };
217
+
218
+ // Act
219
+ await repository.save(product);
220
+ const retrieved = await repository.getById('123');
221
+
222
+ // Assert
223
+ expect(retrieved).toEqual(product);
224
+ });
225
+ });
226
+
227
+ describe('findByCategory', () => {
228
+ it('should return products in category', async () => {
229
+ // Arrange
230
+ const products: IProduct[] = [
231
+ { id: '1', name: 'Product 1', price: 10, categoryId: 'cat1' },
232
+ { id: '2', name: 'Product 2', price: 20, categoryId: 'cat1' },
233
+ { id: '3', name: 'Product 3', price: 30, categoryId: 'cat2' }
234
+ ];
235
+
236
+ for (const product of products) {
237
+ await repository.save(product);
238
+ }
239
+
240
+ // Act
241
+ const results = await repository.findByCategory('cat1');
242
+
243
+ // Assert
244
+ expect(results).toHaveLength(2);
245
+ expect(results.map(p => p.id)).toEqual(['1', '2']);
246
+ });
247
+ });
248
+ });
249
+ ```
250
+
251
+ ## Assertions
252
+
253
+ ### Jest Matchers
254
+
255
+ ```typescript
256
+ // Equality
257
+ expect(value).toBe(expected); // Strict equality (===)
258
+ expect(value).toEqual(expected); // Deep equality
259
+ expect(value).toStrictEqual(expected); // Strict deep equality
260
+
261
+ // Truthiness
262
+ expect(value).toBeTruthy();
263
+ expect(value).toBeFalsy();
264
+ expect(value).toBeNull();
265
+ expect(value).toBeUndefined();
266
+ expect(value).toBeDefined();
267
+
268
+ // Numbers
269
+ expect(value).toBeGreaterThan(3);
270
+ expect(value).toBeGreaterThanOrEqual(3);
271
+ expect(value).toBeLessThan(5);
272
+ expect(value).toBeLessThanOrEqual(5);
273
+ expect(value).toBeCloseTo(0.3, 5); // Floating point
274
+
275
+ // Strings
276
+ expect(value).toMatch(/pattern/);
277
+ expect(value).toContain('substring');
278
+
279
+ // Arrays
280
+ expect(array).toContain(item);
281
+ expect(array).toHaveLength(3);
282
+ expect(array).toContainEqual(object);
283
+
284
+ // Objects
285
+ expect(obj).toHaveProperty('key');
286
+ expect(obj).toHaveProperty('key', value);
287
+ expect(obj).toMatchObject({ key: value });
288
+
289
+ // Exceptions
290
+ expect(() => fn()).toThrow();
291
+ expect(() => fn()).toThrow(Error);
292
+ expect(() => fn()).toThrow('error message');
293
+ expect(async () => await fn()).rejects.toThrow();
294
+
295
+ // Mock functions
296
+ expect(mockFn).toHaveBeenCalled();
297
+ expect(mockFn).toHaveBeenCalledTimes(1);
298
+ expect(mockFn).toHaveBeenCalledWith(arg1, arg2);
299
+ expect(mockFn).toHaveBeenLastCalledWith(arg1, arg2);
300
+ expect(mockFn).toHaveReturnedWith(value);
301
+ ```
302
+
303
+ ## Mocking
304
+
305
+ ### Mock Functions
306
+
307
+ ```typescript
308
+ // Create mock function
309
+ const mockFn = jest.fn();
310
+
311
+ // Mock return value
312
+ mockFn.mockReturnValue(42);
313
+ mockFn.mockReturnValueOnce(42);
314
+
315
+ // Mock resolved value (Promise)
316
+ mockFn.mockResolvedValue(data);
317
+ mockFn.mockResolvedValueOnce(data);
318
+
319
+ // Mock rejected value (Promise)
320
+ mockFn.mockRejectedValue(new Error('Failed'));
321
+ mockFn.mockRejectedValueOnce(new Error('Failed'));
322
+
323
+ // Mock implementation
324
+ mockFn.mockImplementation((arg) => arg * 2);
325
+ mockFn.mockImplementationOnce((arg) => arg * 2);
326
+
327
+ // Check calls
328
+ expect(mockFn).toHaveBeenCalled();
329
+ expect(mockFn).toHaveBeenCalledWith(arg1, arg2);
330
+ expect(mockFn.mock.calls).toHaveLength(2);
331
+ expect(mockFn.mock.calls[0][0]).toBe(arg1);
332
+ expect(mockFn.mock.results[0].value).toBe(returnValue);
333
+ ```
334
+
335
+ ### Mocking Modules
336
+
337
+ ```typescript
338
+ // Mock entire module
339
+ jest.mock('@/infrastructure/database/FirebaseService');
340
+
341
+ // Mock specific function
342
+ jest.mock('@/utils/helpers', () => ({
343
+ generateId: jest.fn(() => 'test-id'),
344
+ formatDate: jest.fn((date) => date.toISOString())
345
+ }));
346
+
347
+ // Partial mock (keep some real implementations)
348
+ jest.mock('@/utils/helpers', () => ({
349
+ ...jest.requireActual('@/utils/helpers'),
350
+ generateId: jest.fn(() => 'test-id')
351
+ }));
352
+ ```
353
+
354
+ ### Spy on Methods
355
+
356
+ ```typescript
357
+ // Spy on method
358
+ const spy = jest.spyOn(object, 'method');
359
+
360
+ // Spy and mock implementation
361
+ const spy = jest.spyOn(object, 'method').mockImplementation(() => 'mocked');
362
+
363
+ // Restore original implementation
364
+ spy.mockRestore();
365
+ ```
366
+
367
+ ## Test Data
368
+
369
+ ### Fixtures
370
+
371
+ ```typescript
372
+ // tests/fixtures/products.ts
373
+ export const mockProduct: IProduct = {
374
+ id: '123',
375
+ name: 'Test Product',
376
+ price: 99.99,
377
+ categoryId: 'cat1',
378
+ createdAt: new Date('2024-01-01'),
379
+ updatedAt: new Date('2024-01-01')
380
+ };
381
+
382
+ export const mockProducts: IProduct[] = [
383
+ mockProduct,
384
+ {
385
+ id: '456',
386
+ name: 'Another Product',
387
+ price: 49.99,
388
+ categoryId: 'cat2',
389
+ createdAt: new Date('2024-01-02'),
390
+ updatedAt: new Date('2024-01-02')
391
+ }
392
+ ];
393
+ ```
394
+
395
+ ### Test Builders
396
+
397
+ ```typescript
398
+ // tests/builders/ProductBuilder.ts
399
+ export class ProductBuilder {
400
+ private product: Partial<IProduct> = {
401
+ id: '123',
402
+ name: 'Test Product',
403
+ price: 10.00,
404
+ categoryId: 'cat1'
405
+ };
406
+
407
+ withId(id: string): ProductBuilder {
408
+ this.product.id = id;
409
+ return this;
410
+ }
411
+
412
+ withName(name: string): ProductBuilder {
413
+ this.product.name = name;
414
+ return this;
415
+ }
416
+
417
+ withPrice(price: number): ProductBuilder {
418
+ this.product.price = price;
419
+ return this;
420
+ }
421
+
422
+ build(): IProduct {
423
+ return this.product as IProduct;
424
+ }
425
+ }
426
+
427
+ // Usage in tests
428
+ const product = new ProductBuilder()
429
+ .withId('custom-id')
430
+ .withName('Custom Product')
431
+ .withPrice(99.99)
432
+ .build();
433
+ ```
434
+
435
+ ## Async Testing
436
+
437
+ ### Testing Promises
438
+
439
+ ```typescript
440
+ it('should handle async operations', async () => {
441
+ // Using async/await
442
+ const result = await service.getData();
443
+ expect(result).toBeDefined();
444
+ });
445
+
446
+ it('should handle promise rejections', async () => {
447
+ // Expect rejection
448
+ await expect(service.failingMethod()).rejects.toThrow(Error);
449
+ await expect(service.failingMethod()).rejects.toThrow('Specific message');
450
+ });
451
+ ```
452
+
453
+ ### Testing Callbacks
454
+
455
+ ```typescript
456
+ it('should handle callbacks', (done) => {
457
+ service.methodWithCallback((error, result) => {
458
+ expect(error).toBeNull();
459
+ expect(result).toBeDefined();
460
+ done();
461
+ });
462
+ });
463
+ ```
464
+
465
+ ## Coverage
466
+
467
+ ### Running Coverage
468
+
469
+ ```bash
470
+ # Run tests with coverage
471
+ npm test -- --coverage
472
+
473
+ # Coverage for specific files
474
+ npm test -- --coverage --collectCoverageFrom='src/services/**/*.ts'
475
+
476
+ # Watch mode
477
+ npm test -- --watch
478
+
479
+ # Update snapshots
480
+ npm test -- -u
481
+ ```
482
+
483
+ ### Coverage Reports
484
+
485
+ ```bash
486
+ # HTML report
487
+ npm test -- --coverage --coverageReporters=html
488
+ open coverage/index.html
489
+
490
+ # Text report
491
+ npm test -- --coverage --coverageReporters=text
492
+
493
+ # LCOV for CI
494
+ npm test -- --coverage --coverageReporters=lcov
495
+ ```
496
+
497
+ ## Best Practices
498
+
499
+ ### Do's
500
+
501
+ - ✅ Use `async/await` for async tests
502
+ - ✅ Clear mocks between tests (`afterEach`)
503
+ - ✅ Use type-safe mocks
504
+ - ✅ Test error cases
505
+ - ✅ Use descriptive test names
506
+ - ✅ Arrange-Act-Assert pattern
507
+ - ✅ One concept per test
508
+ - ✅ Use builders for complex objects
509
+
510
+ ### Don'ts
511
+
512
+ - ❌ Don't use `any` in tests
513
+ - ❌ Don't test implementation details
514
+ - ❌ Don't share state between tests
515
+ - ❌ Don't mock everything
516
+ - ❌ Don't ignore failing tests
517
+ - ❌ Don't skip assertions
518
+ - ❌ Don't test third-party code
519
+
520
+ ## Example: Complete Test Suite
521
+
522
+ ```typescript
523
+ import { OrderService } from '@/application/services/OrderService';
524
+ import { IOrderRepository } from '@/domain/interfaces/IOrderRepository';
525
+ import { IProductRepository } from '@/domain/interfaces/IProductRepository';
526
+ import { ILogger } from '@/domain/interfaces/ILogger';
527
+ import { OrderBuilder } from '@/tests/builders/OrderBuilder';
528
+ import { ProductBuilder } from '@/tests/builders/ProductBuilder';
529
+
530
+ describe('OrderService', () => {
531
+ let service: OrderService;
532
+ let mockOrderRepository: jest.Mocked<IOrderRepository>;
533
+ let mockProductRepository: jest.Mocked<IProductRepository>;
534
+ let mockLogger: jest.Mocked<ILogger>;
535
+
536
+ beforeEach(() => {
537
+ mockOrderRepository = {
538
+ save: jest.fn(),
539
+ getById: jest.fn(),
540
+ findByUser: jest.fn()
541
+ } as jest.Mocked<IOrderRepository>;
542
+
543
+ mockProductRepository = {
544
+ getById: jest.fn(),
545
+ findByIds: jest.fn()
546
+ } as jest.Mocked<IProductRepository>;
547
+
548
+ mockLogger = {
549
+ info: jest.fn(),
550
+ warn: jest.fn(),
551
+ error: jest.fn()
552
+ } as jest.Mocked<ILogger>;
553
+
554
+ service = new OrderService(
555
+ mockOrderRepository,
556
+ mockProductRepository,
557
+ mockLogger
558
+ );
559
+ });
560
+
561
+ afterEach(() => {
562
+ jest.clearAllMocks();
563
+ });
564
+
565
+ describe('createOrder', () => {
566
+ it('should create order with valid data', async () => {
567
+ // Arrange
568
+ const products = [
569
+ new ProductBuilder().withId('p1').withPrice(10).build(),
570
+ new ProductBuilder().withId('p2').withPrice(20).build()
571
+ ];
572
+
573
+ mockProductRepository.findByIds.mockResolvedValue(products);
574
+ mockOrderRepository.save.mockResolvedValue(undefined);
575
+
576
+ const orderData = {
577
+ userId: 'user1',
578
+ productIds: ['p1', 'p2']
579
+ };
580
+
581
+ // Act
582
+ const order = await service.createOrder(orderData);
583
+
584
+ // Assert
585
+ expect(order).toBeDefined();
586
+ expect(order.userId).toBe('user1');
587
+ expect(order.total).toBe(30);
588
+ expect(mockProductRepository.findByIds).toHaveBeenCalledWith(['p1', 'p2']);
589
+ expect(mockOrderRepository.save).toHaveBeenCalledWith(
590
+ expect.objectContaining({
591
+ userId: 'user1',
592
+ total: 30
593
+ })
594
+ );
595
+ expect(mockLogger.info).toHaveBeenCalledWith(
596
+ 'Order created',
597
+ expect.objectContaining({ orderId: order.id })
598
+ );
599
+ });
600
+
601
+ it('should throw ValidationError for empty product list', async () => {
602
+ // Arrange
603
+ const orderData = {
604
+ userId: 'user1',
605
+ productIds: []
606
+ };
607
+
608
+ // Act & Assert
609
+ await expect(service.createOrder(orderData)).rejects.toThrow(ValidationError);
610
+ expect(mockOrderRepository.save).not.toHaveBeenCalled();
611
+ });
612
+ });
613
+ });
614
+ ```
615
+
616
+ ## Checklist
617
+
618
+ - [ ] Tests use TypeScript with proper types
619
+ - [ ] Mocks are type-safe (`jest.Mocked<T>`)
620
+ - [ ] async/await used for async tests
621
+ - [ ] Arrange-Act-Assert pattern followed
622
+ - [ ] Mocks cleared between tests
623
+ - [ ] Error cases tested
624
+ - [ ] Test names are descriptive
625
+ - [ ] Coverage meets thresholds (80%+)
626
+ - [ ] No `any` types in tests
627
+ - [ ] Test data uses builders or fixtures
628
+
629
+ For universal testing principles, see `shared/testing-principles.md`.