@massu/core 0.1.1 → 0.4.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 (151) hide show
  1. package/commands/_shared-preamble.md +76 -0
  2. package/commands/massu-audit-deps.md +211 -0
  3. package/commands/massu-changelog.md +174 -0
  4. package/commands/massu-cleanup.md +315 -0
  5. package/commands/massu-commit.md +481 -0
  6. package/commands/massu-create-plan.md +752 -0
  7. package/commands/massu-dead-code.md +131 -0
  8. package/commands/massu-debug.md +484 -0
  9. package/commands/massu-deploy.md +91 -0
  10. package/commands/massu-deps.md +374 -0
  11. package/commands/massu-doc-gen.md +279 -0
  12. package/commands/massu-docs.md +364 -0
  13. package/commands/massu-estimate.md +313 -0
  14. package/commands/massu-golden-path.md +973 -0
  15. package/commands/massu-guide.md +167 -0
  16. package/commands/massu-hotfix.md +480 -0
  17. package/commands/massu-loop-playwright.md +837 -0
  18. package/commands/massu-loop.md +775 -0
  19. package/commands/massu-new-feature.md +511 -0
  20. package/commands/massu-parity.md +214 -0
  21. package/commands/massu-plan.md +456 -0
  22. package/commands/massu-push-light.md +207 -0
  23. package/commands/massu-push.md +434 -0
  24. package/commands/massu-refactor.md +410 -0
  25. package/commands/massu-release.md +363 -0
  26. package/commands/massu-review.md +238 -0
  27. package/commands/massu-simplify.md +281 -0
  28. package/commands/massu-status.md +278 -0
  29. package/commands/massu-tdd.md +201 -0
  30. package/commands/massu-test.md +516 -0
  31. package/commands/massu-verify-playwright.md +281 -0
  32. package/commands/massu-verify.md +667 -0
  33. package/dist/cli.js +7772 -3140
  34. package/dist/hooks/cost-tracker.js +103 -40
  35. package/dist/hooks/post-edit-context.js +74 -8
  36. package/dist/hooks/post-tool-use.js +268 -106
  37. package/dist/hooks/pre-compact.js +167 -43
  38. package/dist/hooks/pre-delete-check.js +159 -42
  39. package/dist/hooks/quality-event.js +103 -40
  40. package/dist/hooks/security-gate.js +29 -0
  41. package/dist/hooks/session-end.js +143 -84
  42. package/dist/hooks/session-start.js +186 -49
  43. package/dist/hooks/user-prompt.js +189 -43
  44. package/package.json +10 -15
  45. package/src/adr-generator.ts +9 -2
  46. package/src/analytics.ts +9 -3
  47. package/src/audit-trail.ts +10 -3
  48. package/src/backfill-sessions.ts +5 -4
  49. package/src/cli.ts +6 -0
  50. package/src/cloud-sync.ts +14 -18
  51. package/src/commands/doctor.ts +193 -6
  52. package/src/commands/init.ts +230 -5
  53. package/src/commands/install-commands.ts +137 -0
  54. package/src/config.ts +68 -2
  55. package/src/cost-tracker.ts +11 -6
  56. package/src/db.ts +115 -2
  57. package/src/dependency-scorer.ts +9 -2
  58. package/src/docs-tools.ts +21 -16
  59. package/src/hooks/post-edit-context.ts +4 -4
  60. package/src/hooks/post-tool-use.ts +130 -0
  61. package/src/hooks/pre-compact.ts +23 -1
  62. package/src/hooks/pre-delete-check.ts +92 -4
  63. package/src/hooks/security-gate.ts +32 -0
  64. package/src/hooks/session-end.ts +3 -3
  65. package/src/hooks/session-start.ts +99 -6
  66. package/src/hooks/user-prompt.ts +46 -1
  67. package/src/import-resolver.ts +2 -1
  68. package/src/knowledge-db.ts +169 -0
  69. package/src/knowledge-indexer.ts +704 -0
  70. package/src/knowledge-tools.ts +1413 -0
  71. package/src/license.ts +482 -0
  72. package/src/memory-db.ts +1364 -23
  73. package/src/memory-tools.ts +14 -15
  74. package/src/observability-tools.ts +13 -2
  75. package/src/observation-extractor.ts +11 -4
  76. package/src/page-deps.ts +3 -2
  77. package/src/prompt-analyzer.ts +9 -2
  78. package/src/python/coupling-detector.ts +124 -0
  79. package/src/python/domain-enforcer.ts +83 -0
  80. package/src/python/impact-analyzer.ts +95 -0
  81. package/src/python/import-parser.ts +244 -0
  82. package/src/python/import-resolver.ts +135 -0
  83. package/src/python/migration-indexer.ts +115 -0
  84. package/src/python/migration-parser.ts +332 -0
  85. package/src/python/model-indexer.ts +70 -0
  86. package/src/python/model-parser.ts +279 -0
  87. package/src/python/route-indexer.ts +58 -0
  88. package/src/python/route-parser.ts +317 -0
  89. package/src/python-tools.ts +629 -0
  90. package/src/regression-detector.ts +9 -3
  91. package/src/security-scorer.ts +9 -2
  92. package/src/sentinel-db.ts +45 -89
  93. package/src/sentinel-tools.ts +8 -11
  94. package/src/server.ts +29 -7
  95. package/src/session-archiver.ts +4 -5
  96. package/src/team-knowledge.ts +9 -2
  97. package/src/tools.ts +1032 -44
  98. package/src/validate-features-runner.ts +0 -1
  99. package/src/validation-engine.ts +9 -2
  100. package/README.md +0 -40
  101. package/dist/server.js +0 -7008
  102. package/src/__tests__/adr-generator.test.ts +0 -260
  103. package/src/__tests__/analytics.test.ts +0 -282
  104. package/src/__tests__/audit-trail.test.ts +0 -382
  105. package/src/__tests__/backfill-sessions.test.ts +0 -690
  106. package/src/__tests__/cli.test.ts +0 -290
  107. package/src/__tests__/cloud-sync.test.ts +0 -261
  108. package/src/__tests__/config-sections.test.ts +0 -359
  109. package/src/__tests__/config.test.ts +0 -732
  110. package/src/__tests__/cost-tracker.test.ts +0 -348
  111. package/src/__tests__/db.test.ts +0 -177
  112. package/src/__tests__/dependency-scorer.test.ts +0 -325
  113. package/src/__tests__/docs-integration.test.ts +0 -178
  114. package/src/__tests__/docs-tools.test.ts +0 -199
  115. package/src/__tests__/domains.test.ts +0 -236
  116. package/src/__tests__/hooks.test.ts +0 -221
  117. package/src/__tests__/import-resolver.test.ts +0 -95
  118. package/src/__tests__/integration/path-traversal.test.ts +0 -134
  119. package/src/__tests__/integration/pricing-consistency.test.ts +0 -88
  120. package/src/__tests__/integration/tool-registration.test.ts +0 -146
  121. package/src/__tests__/memory-db.test.ts +0 -404
  122. package/src/__tests__/memory-enhancements.test.ts +0 -316
  123. package/src/__tests__/memory-tools.test.ts +0 -199
  124. package/src/__tests__/middleware-tree.test.ts +0 -177
  125. package/src/__tests__/observability-tools.test.ts +0 -595
  126. package/src/__tests__/observability.test.ts +0 -437
  127. package/src/__tests__/observation-extractor.test.ts +0 -167
  128. package/src/__tests__/page-deps.test.ts +0 -60
  129. package/src/__tests__/prompt-analyzer.test.ts +0 -298
  130. package/src/__tests__/regression-detector.test.ts +0 -295
  131. package/src/__tests__/rules.test.ts +0 -87
  132. package/src/__tests__/schema-mapper.test.ts +0 -29
  133. package/src/__tests__/security-scorer.test.ts +0 -238
  134. package/src/__tests__/security-utils.test.ts +0 -175
  135. package/src/__tests__/sentinel-db.test.ts +0 -491
  136. package/src/__tests__/sentinel-scanner.test.ts +0 -750
  137. package/src/__tests__/sentinel-tools.test.ts +0 -324
  138. package/src/__tests__/sentinel-types.test.ts +0 -750
  139. package/src/__tests__/server.test.ts +0 -452
  140. package/src/__tests__/session-archiver.test.ts +0 -524
  141. package/src/__tests__/session-state-generator.test.ts +0 -900
  142. package/src/__tests__/team-knowledge.test.ts +0 -327
  143. package/src/__tests__/tools.test.ts +0 -340
  144. package/src/__tests__/transcript-parser.test.ts +0 -195
  145. package/src/__tests__/trpc-index.test.ts +0 -25
  146. package/src/__tests__/validate-features-runner.test.ts +0 -517
  147. package/src/__tests__/validation-engine.test.ts +0 -300
  148. package/src/core-tools.ts +0 -685
  149. package/src/memory-queries.ts +0 -804
  150. package/src/memory-schema.ts +0 -546
  151. 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
- });