@massu/core 0.1.1 → 0.1.2

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 (87) hide show
  1. package/README.md +2 -2
  2. package/dist/hooks/cost-tracker.js +23 -35
  3. package/dist/hooks/post-edit-context.js +2 -2
  4. package/dist/hooks/post-tool-use.js +43 -58
  5. package/dist/hooks/pre-compact.js +23 -38
  6. package/dist/hooks/pre-delete-check.js +18 -31
  7. package/dist/hooks/quality-event.js +23 -35
  8. package/dist/hooks/session-end.js +62 -78
  9. package/dist/hooks/session-start.js +33 -42
  10. package/dist/hooks/user-prompt.js +23 -38
  11. package/package.json +8 -14
  12. package/src/adr-generator.ts +9 -2
  13. package/src/analytics.ts +9 -3
  14. package/src/audit-trail.ts +10 -3
  15. package/src/cloud-sync.ts +14 -18
  16. package/src/commands/init.ts +1 -5
  17. package/src/cost-tracker.ts +11 -6
  18. package/src/dependency-scorer.ts +9 -2
  19. package/src/docs-tools.ts +13 -10
  20. package/src/hooks/post-edit-context.ts +3 -3
  21. package/src/hooks/session-end.ts +3 -3
  22. package/src/hooks/session-start.ts +2 -2
  23. package/src/memory-db.ts +1351 -23
  24. package/src/memory-tools.ts +14 -15
  25. package/src/observability-tools.ts +13 -2
  26. package/src/prompt-analyzer.ts +9 -2
  27. package/src/regression-detector.ts +9 -3
  28. package/src/security-scorer.ts +9 -2
  29. package/src/sentinel-db.ts +43 -88
  30. package/src/sentinel-tools.ts +8 -11
  31. package/src/server.ts +1 -2
  32. package/src/team-knowledge.ts +9 -2
  33. package/src/tools.ts +771 -35
  34. package/src/validate-features-runner.ts +0 -1
  35. package/src/validation-engine.ts +9 -2
  36. package/dist/cli.js +0 -7890
  37. package/dist/server.js +0 -7008
  38. package/src/__tests__/adr-generator.test.ts +0 -260
  39. package/src/__tests__/analytics.test.ts +0 -282
  40. package/src/__tests__/audit-trail.test.ts +0 -382
  41. package/src/__tests__/backfill-sessions.test.ts +0 -690
  42. package/src/__tests__/cli.test.ts +0 -290
  43. package/src/__tests__/cloud-sync.test.ts +0 -261
  44. package/src/__tests__/config-sections.test.ts +0 -359
  45. package/src/__tests__/config.test.ts +0 -732
  46. package/src/__tests__/cost-tracker.test.ts +0 -348
  47. package/src/__tests__/db.test.ts +0 -177
  48. package/src/__tests__/dependency-scorer.test.ts +0 -325
  49. package/src/__tests__/docs-integration.test.ts +0 -178
  50. package/src/__tests__/docs-tools.test.ts +0 -199
  51. package/src/__tests__/domains.test.ts +0 -236
  52. package/src/__tests__/hooks.test.ts +0 -221
  53. package/src/__tests__/import-resolver.test.ts +0 -95
  54. package/src/__tests__/integration/path-traversal.test.ts +0 -134
  55. package/src/__tests__/integration/pricing-consistency.test.ts +0 -88
  56. package/src/__tests__/integration/tool-registration.test.ts +0 -146
  57. package/src/__tests__/memory-db.test.ts +0 -404
  58. package/src/__tests__/memory-enhancements.test.ts +0 -316
  59. package/src/__tests__/memory-tools.test.ts +0 -199
  60. package/src/__tests__/middleware-tree.test.ts +0 -177
  61. package/src/__tests__/observability-tools.test.ts +0 -595
  62. package/src/__tests__/observability.test.ts +0 -437
  63. package/src/__tests__/observation-extractor.test.ts +0 -167
  64. package/src/__tests__/page-deps.test.ts +0 -60
  65. package/src/__tests__/prompt-analyzer.test.ts +0 -298
  66. package/src/__tests__/regression-detector.test.ts +0 -295
  67. package/src/__tests__/rules.test.ts +0 -87
  68. package/src/__tests__/schema-mapper.test.ts +0 -29
  69. package/src/__tests__/security-scorer.test.ts +0 -238
  70. package/src/__tests__/security-utils.test.ts +0 -175
  71. package/src/__tests__/sentinel-db.test.ts +0 -491
  72. package/src/__tests__/sentinel-scanner.test.ts +0 -750
  73. package/src/__tests__/sentinel-tools.test.ts +0 -324
  74. package/src/__tests__/sentinel-types.test.ts +0 -750
  75. package/src/__tests__/server.test.ts +0 -452
  76. package/src/__tests__/session-archiver.test.ts +0 -524
  77. package/src/__tests__/session-state-generator.test.ts +0 -900
  78. package/src/__tests__/team-knowledge.test.ts +0 -327
  79. package/src/__tests__/tools.test.ts +0 -340
  80. package/src/__tests__/transcript-parser.test.ts +0 -195
  81. package/src/__tests__/trpc-index.test.ts +0 -25
  82. package/src/__tests__/validate-features-runner.test.ts +0 -517
  83. package/src/__tests__/validation-engine.test.ts +0 -300
  84. package/src/core-tools.ts +0 -685
  85. package/src/memory-queries.ts +0 -804
  86. package/src/memory-schema.ts +0 -546
  87. package/src/tool-helpers.ts +0 -41
@@ -1,750 +0,0 @@
1
- // Copyright (c) 2026 Massu. All rights reserved.
2
- // Licensed under BSL 1.1 - see LICENSE file for details.
3
-
4
- import { describe, it, expect } from 'vitest';
5
- import type {
6
- Feature,
7
- FeatureInput,
8
- FeatureComponent,
9
- FeatureProcedure,
10
- FeaturePage,
11
- FeatureDep,
12
- FeatureChangeLog,
13
- FeatureWithCounts,
14
- FeatureDetail,
15
- ImpactItem,
16
- ImpactReport,
17
- ValidationItem,
18
- ValidationReport,
19
- ParityItem,
20
- ParityReport,
21
- FeatureStatus,
22
- FeaturePriority,
23
- ComponentRole,
24
- DependencyType,
25
- ChangeType,
26
- } from '../sentinel-types.ts';
27
-
28
- // ============================================================
29
- // Sentinel Types Tests
30
- // Verifies type shapes compile and runtime objects conform.
31
- // ============================================================
32
-
33
- // ------------------------------------
34
- // Type guard helpers (runtime checks)
35
- // ------------------------------------
36
-
37
- function isFeatureStatus(value: unknown): value is FeatureStatus {
38
- return (
39
- typeof value === 'string' &&
40
- ['planned', 'active', 'deprecated', 'removed'].includes(value)
41
- );
42
- }
43
-
44
- function isFeaturePriority(value: unknown): value is FeaturePriority {
45
- return (
46
- typeof value === 'string' &&
47
- ['critical', 'standard', 'nice-to-have'].includes(value)
48
- );
49
- }
50
-
51
- function isComponentRole(value: unknown): value is ComponentRole {
52
- return (
53
- typeof value === 'string' &&
54
- ['implementation', 'ui', 'data', 'utility'].includes(value)
55
- );
56
- }
57
-
58
- function isDependencyType(value: unknown): value is DependencyType {
59
- return (
60
- typeof value === 'string' &&
61
- ['requires', 'enhances', 'replaces'].includes(value)
62
- );
63
- }
64
-
65
- function isChangeType(value: unknown): value is ChangeType {
66
- return (
67
- typeof value === 'string' &&
68
- ['created', 'updated', 'deprecated', 'removed', 'restored'].includes(value)
69
- );
70
- }
71
-
72
- function isFeature(obj: unknown): obj is Feature {
73
- if (typeof obj !== 'object' || obj === null) return false;
74
- const f = obj as Record<string, unknown>;
75
- return (
76
- typeof f.id === 'number' &&
77
- typeof f.feature_key === 'string' &&
78
- typeof f.domain === 'string' &&
79
- (f.subdomain === null || typeof f.subdomain === 'string') &&
80
- typeof f.title === 'string' &&
81
- (f.description === null || typeof f.description === 'string') &&
82
- isFeatureStatus(f.status) &&
83
- isFeaturePriority(f.priority) &&
84
- Array.isArray(f.portal_scope) &&
85
- typeof f.created_at === 'string' &&
86
- typeof f.updated_at === 'string' &&
87
- (f.removed_at === null || typeof f.removed_at === 'string') &&
88
- (f.removed_reason === null || typeof f.removed_reason === 'string')
89
- );
90
- }
91
-
92
- // ------------------------------------
93
- // Type literal union tests
94
- // ------------------------------------
95
-
96
- describe('FeatureStatus union type', () => {
97
- it('accepts all valid status values', () => {
98
- const statuses: FeatureStatus[] = ['planned', 'active', 'deprecated', 'removed'];
99
- expect(statuses).toHaveLength(4);
100
- for (const s of statuses) {
101
- expect(isFeatureStatus(s)).toBe(true);
102
- }
103
- });
104
-
105
- it('rejects invalid status values at runtime', () => {
106
- expect(isFeatureStatus('unknown')).toBe(false);
107
- expect(isFeatureStatus('')).toBe(false);
108
- expect(isFeatureStatus(null)).toBe(false);
109
- expect(isFeatureStatus(42)).toBe(false);
110
- });
111
- });
112
-
113
- describe('FeaturePriority union type', () => {
114
- it('accepts all valid priority values', () => {
115
- const priorities: FeaturePriority[] = ['critical', 'standard', 'nice-to-have'];
116
- expect(priorities).toHaveLength(3);
117
- for (const p of priorities) {
118
- expect(isFeaturePriority(p)).toBe(true);
119
- }
120
- });
121
-
122
- it('rejects invalid priority values at runtime', () => {
123
- expect(isFeaturePriority('low')).toBe(false);
124
- expect(isFeaturePriority('high')).toBe(false);
125
- expect(isFeaturePriority(null)).toBe(false);
126
- });
127
- });
128
-
129
- describe('ComponentRole union type', () => {
130
- it('accepts all valid role values', () => {
131
- const roles: ComponentRole[] = ['implementation', 'ui', 'data', 'utility'];
132
- expect(roles).toHaveLength(4);
133
- for (const r of roles) {
134
- expect(isComponentRole(r)).toBe(true);
135
- }
136
- });
137
-
138
- it('rejects invalid role values at runtime', () => {
139
- expect(isComponentRole('service')).toBe(false);
140
- expect(isComponentRole('')).toBe(false);
141
- });
142
- });
143
-
144
- describe('DependencyType union type', () => {
145
- it('accepts all valid dependency types', () => {
146
- const types: DependencyType[] = ['requires', 'enhances', 'replaces'];
147
- expect(types).toHaveLength(3);
148
- for (const t of types) {
149
- expect(isDependencyType(t)).toBe(true);
150
- }
151
- });
152
-
153
- it('rejects invalid dependency types at runtime', () => {
154
- expect(isDependencyType('uses')).toBe(false);
155
- expect(isDependencyType(null)).toBe(false);
156
- });
157
- });
158
-
159
- describe('ChangeType union type', () => {
160
- it('accepts all valid change types', () => {
161
- const types: ChangeType[] = ['created', 'updated', 'deprecated', 'removed', 'restored'];
162
- expect(types).toHaveLength(5);
163
- for (const t of types) {
164
- expect(isChangeType(t)).toBe(true);
165
- }
166
- });
167
-
168
- it('rejects invalid change types at runtime', () => {
169
- expect(isChangeType('deleted')).toBe(false);
170
- expect(isChangeType('modified')).toBe(false);
171
- expect(isChangeType(null)).toBe(false);
172
- });
173
- });
174
-
175
- // ------------------------------------
176
- // Interface conformance tests
177
- // ------------------------------------
178
-
179
- describe('Feature interface', () => {
180
- it('accepts a fully populated Feature object', () => {
181
- const feature: Feature = {
182
- id: 1,
183
- feature_key: 'auth.login',
184
- domain: 'auth',
185
- subdomain: 'session',
186
- title: 'User Login',
187
- description: 'Handles user authentication',
188
- status: 'active',
189
- priority: 'critical',
190
- portal_scope: ['internal', 'external'],
191
- created_at: '2026-01-01T00:00:00Z',
192
- updated_at: '2026-01-15T00:00:00Z',
193
- removed_at: null,
194
- removed_reason: null,
195
- };
196
-
197
- expect(isFeature(feature)).toBe(true);
198
- expect(feature.id).toBe(1);
199
- expect(feature.feature_key).toBe('auth.login');
200
- expect(feature.portal_scope).toEqual(['internal', 'external']);
201
- expect(feature.removed_at).toBeNull();
202
- });
203
-
204
- it('accepts a Feature with null optional fields', () => {
205
- const feature: Feature = {
206
- id: 2,
207
- feature_key: 'product.list',
208
- domain: 'product',
209
- subdomain: null,
210
- title: 'Product List',
211
- description: null,
212
- status: 'deprecated',
213
- priority: 'standard',
214
- portal_scope: [],
215
- created_at: '2026-01-01T00:00:00Z',
216
- updated_at: '2026-01-01T00:00:00Z',
217
- removed_at: '2026-02-01T00:00:00Z',
218
- removed_reason: 'Feature deprecated in v2',
219
- };
220
-
221
- expect(isFeature(feature)).toBe(true);
222
- expect(feature.subdomain).toBeNull();
223
- expect(feature.description).toBeNull();
224
- expect(feature.removed_at).toBe('2026-02-01T00:00:00Z');
225
- });
226
- });
227
-
228
- describe('FeatureInput interface', () => {
229
- it('accepts a minimal FeatureInput (required fields only)', () => {
230
- const input: FeatureInput = {
231
- feature_key: 'auth.logout',
232
- domain: 'auth',
233
- title: 'User Logout',
234
- };
235
-
236
- expect(input.feature_key).toBe('auth.logout');
237
- expect(input.domain).toBe('auth');
238
- expect(input.title).toBe('User Logout');
239
- expect(input.subdomain).toBeUndefined();
240
- expect(input.status).toBeUndefined();
241
- });
242
-
243
- it('accepts a fully populated FeatureInput', () => {
244
- const input: FeatureInput = {
245
- feature_key: 'product.search',
246
- domain: 'product',
247
- subdomain: 'catalog',
248
- title: 'Product Search',
249
- description: 'Full-text search for products',
250
- status: 'active',
251
- priority: 'standard',
252
- portal_scope: ['internal'],
253
- };
254
-
255
- expect(input.subdomain).toBe('catalog');
256
- expect(input.status).toBe('active');
257
- expect(input.portal_scope).toEqual(['internal']);
258
- });
259
- });
260
-
261
- describe('FeatureComponent interface', () => {
262
- it('accepts a valid FeatureComponent object', () => {
263
- const component: FeatureComponent = {
264
- id: 10,
265
- feature_id: 1,
266
- component_file: 'src/components/Login.tsx',
267
- component_name: 'LoginForm',
268
- role: 'ui',
269
- is_primary: true,
270
- };
271
-
272
- expect(component.id).toBe(10);
273
- expect(component.feature_id).toBe(1);
274
- expect(component.component_file).toBe('src/components/Login.tsx');
275
- expect(isComponentRole(component.role)).toBe(true);
276
- expect(component.is_primary).toBe(true);
277
- });
278
-
279
- it('accepts a FeatureComponent with null component_name', () => {
280
- const component: FeatureComponent = {
281
- id: 11,
282
- feature_id: 2,
283
- component_file: 'src/utils/auth-helpers.ts',
284
- component_name: null,
285
- role: 'utility',
286
- is_primary: false,
287
- };
288
-
289
- expect(component.component_name).toBeNull();
290
- expect(component.is_primary).toBe(false);
291
- });
292
- });
293
-
294
- describe('FeatureProcedure interface', () => {
295
- it('accepts a valid FeatureProcedure object', () => {
296
- const procedure: FeatureProcedure = {
297
- id: 20,
298
- feature_id: 1,
299
- router_name: 'auth',
300
- procedure_name: 'login',
301
- procedure_type: 'mutation',
302
- };
303
-
304
- expect(procedure.router_name).toBe('auth');
305
- expect(procedure.procedure_name).toBe('login');
306
- expect(procedure.procedure_type).toBe('mutation');
307
- });
308
-
309
- it('accepts a FeatureProcedure with null procedure_type', () => {
310
- const procedure: FeatureProcedure = {
311
- id: 21,
312
- feature_id: 1,
313
- router_name: 'auth',
314
- procedure_name: 'getSession',
315
- procedure_type: null,
316
- };
317
-
318
- expect(procedure.procedure_type).toBeNull();
319
- });
320
- });
321
-
322
- describe('FeaturePage interface', () => {
323
- it('accepts a valid FeaturePage object', () => {
324
- const page: FeaturePage = {
325
- id: 30,
326
- feature_id: 1,
327
- page_route: '/login',
328
- portal: 'external',
329
- };
330
-
331
- expect(page.page_route).toBe('/login');
332
- expect(page.portal).toBe('external');
333
- });
334
-
335
- it('accepts a FeaturePage with null portal', () => {
336
- const page: FeaturePage = {
337
- id: 31,
338
- feature_id: 1,
339
- page_route: '/admin/dashboard',
340
- portal: null,
341
- };
342
-
343
- expect(page.portal).toBeNull();
344
- });
345
- });
346
-
347
- describe('FeatureDep interface', () => {
348
- it('accepts a valid FeatureDep object', () => {
349
- const dep: FeatureDep = {
350
- id: 40,
351
- feature_id: 2,
352
- depends_on_feature_id: 1,
353
- dependency_type: 'requires',
354
- };
355
-
356
- expect(dep.feature_id).toBe(2);
357
- expect(dep.depends_on_feature_id).toBe(1);
358
- expect(isDependencyType(dep.dependency_type)).toBe(true);
359
- });
360
-
361
- it('accepts all dependency types in FeatureDep', () => {
362
- const types: DependencyType[] = ['requires', 'enhances', 'replaces'];
363
- for (const depType of types) {
364
- const dep: FeatureDep = {
365
- id: 1,
366
- feature_id: 1,
367
- depends_on_feature_id: 2,
368
- dependency_type: depType,
369
- };
370
- expect(isDependencyType(dep.dependency_type)).toBe(true);
371
- }
372
- });
373
- });
374
-
375
- describe('FeatureChangeLog interface', () => {
376
- it('accepts a valid FeatureChangeLog object', () => {
377
- const log: FeatureChangeLog = {
378
- id: 50,
379
- feature_id: 1,
380
- change_type: 'created',
381
- changed_by: 'scanner',
382
- change_detail: 'Auto-discovered feature',
383
- commit_hash: 'abc123def456',
384
- created_at: '2026-01-01T00:00:00Z',
385
- };
386
-
387
- expect(isChangeType(log.change_type)).toBe(true);
388
- expect(log.changed_by).toBe('scanner');
389
- });
390
-
391
- it('accepts a FeatureChangeLog with all nullable fields null', () => {
392
- const log: FeatureChangeLog = {
393
- id: 51,
394
- feature_id: 1,
395
- change_type: 'updated',
396
- changed_by: null,
397
- change_detail: null,
398
- commit_hash: null,
399
- created_at: '2026-02-01T00:00:00Z',
400
- };
401
-
402
- expect(log.changed_by).toBeNull();
403
- expect(log.change_detail).toBeNull();
404
- expect(log.commit_hash).toBeNull();
405
- });
406
- });
407
-
408
- describe('FeatureWithCounts interface', () => {
409
- it('extends Feature with count fields', () => {
410
- const featureWithCounts: FeatureWithCounts = {
411
- id: 1,
412
- feature_key: 'auth.login',
413
- domain: 'auth',
414
- subdomain: null,
415
- title: 'User Login',
416
- description: null,
417
- status: 'active',
418
- priority: 'critical',
419
- portal_scope: [],
420
- created_at: '2026-01-01T00:00:00Z',
421
- updated_at: '2026-01-01T00:00:00Z',
422
- removed_at: null,
423
- removed_reason: null,
424
- component_count: 3,
425
- procedure_count: 2,
426
- page_count: 1,
427
- };
428
-
429
- expect(isFeature(featureWithCounts)).toBe(true);
430
- expect(featureWithCounts.component_count).toBe(3);
431
- expect(featureWithCounts.procedure_count).toBe(2);
432
- expect(featureWithCounts.page_count).toBe(1);
433
- });
434
-
435
- it('accepts zero counts', () => {
436
- const featureWithCounts: FeatureWithCounts = {
437
- id: 2,
438
- feature_key: 'empty.feature',
439
- domain: 'test',
440
- subdomain: null,
441
- title: 'Empty Feature',
442
- description: null,
443
- status: 'planned',
444
- priority: 'nice-to-have',
445
- portal_scope: [],
446
- created_at: '2026-01-01T00:00:00Z',
447
- updated_at: '2026-01-01T00:00:00Z',
448
- removed_at: null,
449
- removed_reason: null,
450
- component_count: 0,
451
- procedure_count: 0,
452
- page_count: 0,
453
- };
454
-
455
- expect(featureWithCounts.component_count).toBe(0);
456
- expect(featureWithCounts.procedure_count).toBe(0);
457
- expect(featureWithCounts.page_count).toBe(0);
458
- });
459
- });
460
-
461
- describe('FeatureDetail interface', () => {
462
- it('extends Feature with related entity arrays', () => {
463
- const detail: FeatureDetail = {
464
- id: 1,
465
- feature_key: 'auth.login',
466
- domain: 'auth',
467
- subdomain: null,
468
- title: 'User Login',
469
- description: null,
470
- status: 'active',
471
- priority: 'critical',
472
- portal_scope: [],
473
- created_at: '2026-01-01T00:00:00Z',
474
- updated_at: '2026-01-01T00:00:00Z',
475
- removed_at: null,
476
- removed_reason: null,
477
- components: [
478
- {
479
- id: 1,
480
- feature_id: 1,
481
- component_file: 'src/Login.tsx',
482
- component_name: 'Login',
483
- role: 'ui',
484
- is_primary: true,
485
- },
486
- ],
487
- procedures: [
488
- {
489
- id: 1,
490
- feature_id: 1,
491
- router_name: 'auth',
492
- procedure_name: 'login',
493
- procedure_type: 'mutation',
494
- },
495
- ],
496
- pages: [
497
- {
498
- id: 1,
499
- feature_id: 1,
500
- page_route: '/login',
501
- portal: 'external',
502
- },
503
- ],
504
- dependencies: [
505
- {
506
- id: 1,
507
- feature_id: 1,
508
- depends_on_feature_id: 2,
509
- dependency_type: 'requires',
510
- },
511
- ],
512
- changelog: [
513
- {
514
- id: 1,
515
- feature_id: 1,
516
- change_type: 'created',
517
- changed_by: 'scanner',
518
- change_detail: null,
519
- commit_hash: null,
520
- created_at: '2026-01-01T00:00:00Z',
521
- },
522
- ],
523
- };
524
-
525
- expect(isFeature(detail)).toBe(true);
526
- expect(detail.components).toHaveLength(1);
527
- expect(detail.procedures).toHaveLength(1);
528
- expect(detail.pages).toHaveLength(1);
529
- expect(detail.dependencies).toHaveLength(1);
530
- expect(detail.changelog).toHaveLength(1);
531
- });
532
-
533
- it('accepts empty arrays for all related entities', () => {
534
- const detail: FeatureDetail = {
535
- id: 2,
536
- feature_key: 'new.feature',
537
- domain: 'test',
538
- subdomain: null,
539
- title: 'New Feature',
540
- description: null,
541
- status: 'planned',
542
- priority: 'standard',
543
- portal_scope: [],
544
- created_at: '2026-01-01T00:00:00Z',
545
- updated_at: '2026-01-01T00:00:00Z',
546
- removed_at: null,
547
- removed_reason: null,
548
- components: [],
549
- procedures: [],
550
- pages: [],
551
- dependencies: [],
552
- changelog: [],
553
- };
554
-
555
- expect(detail.components).toHaveLength(0);
556
- expect(detail.procedures).toHaveLength(0);
557
- expect(detail.dependencies).toHaveLength(0);
558
- });
559
- });
560
-
561
- describe('ImpactItem interface', () => {
562
- it('accepts a valid ImpactItem with orphaned status', () => {
563
- const baseFeature: Feature = {
564
- id: 1,
565
- feature_key: 'auth.login',
566
- domain: 'auth',
567
- subdomain: null,
568
- title: 'User Login',
569
- description: null,
570
- status: 'active',
571
- priority: 'critical',
572
- portal_scope: [],
573
- created_at: '2026-01-01T00:00:00Z',
574
- updated_at: '2026-01-01T00:00:00Z',
575
- removed_at: null,
576
- removed_reason: null,
577
- };
578
-
579
- const item: ImpactItem = {
580
- feature: baseFeature,
581
- affected_files: ['src/Login.tsx'],
582
- remaining_files: [],
583
- status: 'orphaned',
584
- };
585
-
586
- expect(item.status).toBe('orphaned');
587
- expect(item.affected_files).toHaveLength(1);
588
- expect(item.remaining_files).toHaveLength(0);
589
- });
590
-
591
- it('accepts all ImpactItem status values', () => {
592
- const statuses: ImpactItem['status'][] = ['orphaned', 'degraded', 'unaffected'];
593
- for (const status of statuses) {
594
- expect(['orphaned', 'degraded', 'unaffected'].includes(status)).toBe(true);
595
- }
596
- });
597
- });
598
-
599
- describe('ImpactReport interface', () => {
600
- it('accepts a valid ImpactReport', () => {
601
- const report: ImpactReport = {
602
- files_analyzed: ['src/Login.tsx', 'src/helpers.ts'],
603
- orphaned: [],
604
- degraded: [],
605
- unaffected: [],
606
- blocked: false,
607
- block_reason: null,
608
- };
609
-
610
- expect(report.files_analyzed).toHaveLength(2);
611
- expect(report.blocked).toBe(false);
612
- expect(report.block_reason).toBeNull();
613
- });
614
-
615
- it('accepts a blocked ImpactReport', () => {
616
- const report: ImpactReport = {
617
- files_analyzed: ['src/Login.tsx'],
618
- orphaned: [],
619
- degraded: [],
620
- unaffected: [],
621
- blocked: true,
622
- block_reason: 'Critical feature auth.login is orphaned',
623
- };
624
-
625
- expect(report.blocked).toBe(true);
626
- expect(report.block_reason).toBe('Critical feature auth.login is orphaned');
627
- });
628
- });
629
-
630
- describe('ValidationItem interface', () => {
631
- it('accepts a valid ValidationItem', () => {
632
- const baseFeature: Feature = {
633
- id: 1,
634
- feature_key: 'auth.login',
635
- domain: 'auth',
636
- subdomain: null,
637
- title: 'User Login',
638
- description: null,
639
- status: 'active',
640
- priority: 'critical',
641
- portal_scope: [],
642
- created_at: '2026-01-01T00:00:00Z',
643
- updated_at: '2026-01-01T00:00:00Z',
644
- removed_at: null,
645
- removed_reason: null,
646
- };
647
-
648
- const item: ValidationItem = {
649
- feature: baseFeature,
650
- missing_components: ['src/Login.tsx'],
651
- missing_procedures: [{ router: 'auth', procedure: 'login' }],
652
- missing_pages: ['/login'],
653
- status: 'degraded',
654
- };
655
-
656
- expect(item.status).toBe('degraded');
657
- expect(item.missing_components).toHaveLength(1);
658
- expect(item.missing_procedures[0]).toEqual({ router: 'auth', procedure: 'login' });
659
- });
660
-
661
- it('accepts all ValidationItem status values', () => {
662
- const statuses: ValidationItem['status'][] = ['alive', 'orphaned', 'degraded'];
663
- for (const status of statuses) {
664
- expect(['alive', 'orphaned', 'degraded'].includes(status)).toBe(true);
665
- }
666
- });
667
- });
668
-
669
- describe('ValidationReport interface', () => {
670
- it('accepts a valid ValidationReport', () => {
671
- const report: ValidationReport = {
672
- alive: 5,
673
- orphaned: 1,
674
- degraded: 2,
675
- details: [],
676
- };
677
-
678
- expect(report.alive).toBe(5);
679
- expect(report.orphaned).toBe(1);
680
- expect(report.degraded).toBe(2);
681
- expect(Array.isArray(report.details)).toBe(true);
682
- });
683
-
684
- it('accepts a zero-count ValidationReport', () => {
685
- const report: ValidationReport = {
686
- alive: 0,
687
- orphaned: 0,
688
- degraded: 0,
689
- details: [],
690
- };
691
-
692
- expect(report.alive + report.orphaned + report.degraded).toBe(0);
693
- });
694
- });
695
-
696
- describe('ParityItem interface', () => {
697
- it('accepts all ParityItem status values', () => {
698
- const statuses: ParityItem['status'][] = ['DONE', 'GAP', 'NEW'];
699
- for (const status of statuses) {
700
- const item: ParityItem = {
701
- feature_key: 'test.feature',
702
- title: 'Test Feature',
703
- status,
704
- old_files: ['src/old.ts'],
705
- new_files: ['src/new.ts'],
706
- };
707
- expect(['DONE', 'GAP', 'NEW'].includes(item.status)).toBe(true);
708
- }
709
- });
710
-
711
- it('accepts a ParityItem with empty file arrays', () => {
712
- const item: ParityItem = {
713
- feature_key: 'new.feature',
714
- title: 'New Feature',
715
- status: 'NEW',
716
- old_files: [],
717
- new_files: ['src/new-feature.ts'],
718
- };
719
-
720
- expect(item.old_files).toHaveLength(0);
721
- expect(item.new_files).toHaveLength(1);
722
- });
723
- });
724
-
725
- describe('ParityReport interface', () => {
726
- it('accepts a valid ParityReport', () => {
727
- const report: ParityReport = {
728
- done: [],
729
- gaps: [],
730
- new_features: [],
731
- parity_percentage: 100,
732
- };
733
-
734
- expect(report.parity_percentage).toBe(100);
735
- expect(Array.isArray(report.done)).toBe(true);
736
- expect(Array.isArray(report.gaps)).toBe(true);
737
- expect(Array.isArray(report.new_features)).toBe(true);
738
- });
739
-
740
- it('accepts a partial parity percentage', () => {
741
- const report: ParityReport = {
742
- done: [],
743
- gaps: [],
744
- new_features: [],
745
- parity_percentage: 66.67,
746
- };
747
-
748
- expect(report.parity_percentage).toBeCloseTo(66.67);
749
- });
750
- });