@aiready/context-analyzer 0.9.34 → 0.9.36
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.
- package/.github/FUNDING.yml +2 -2
- package/.turbo/turbo-build.log +9 -9
- package/.turbo/turbo-test.log +19 -19
- package/CONTRIBUTING.md +10 -2
- package/dist/chunk-7LUSCLGR.mjs +2058 -0
- package/dist/cli.js +346 -83
- package/dist/cli.mjs +137 -33
- package/dist/index.js +231 -56
- package/dist/index.mjs +1 -1
- package/dist/python-context-GOH747QU.mjs +202 -0
- package/package.json +2 -2
- package/src/__tests__/analyzer.test.ts +69 -17
- package/src/__tests__/auto-detection.test.ts +1 -1
- package/src/__tests__/enhanced-cohesion.test.ts +19 -7
- package/src/__tests__/file-classification.test.ts +188 -53
- package/src/__tests__/fragmentation-advanced.test.ts +2 -11
- package/src/__tests__/fragmentation-coupling.test.ts +8 -2
- package/src/__tests__/fragmentation-log.test.ts +9 -9
- package/src/__tests__/scoring.test.ts +19 -7
- package/src/__tests__/structural-cohesion.test.ts +33 -21
- package/src/analyzer.ts +724 -376
- package/src/analyzers/python-context.ts +33 -10
- package/src/cli.ts +223 -59
- package/src/index.ts +112 -55
- package/src/scoring.ts +53 -43
- package/src/semantic-analysis.ts +73 -55
- package/src/types.ts +12 -13
|
@@ -30,7 +30,11 @@ describe('file classification', () => {
|
|
|
30
30
|
linesOfCode: 20, // Sparse code
|
|
31
31
|
});
|
|
32
32
|
|
|
33
|
-
const classification = classifyFile(node, 0.5, [
|
|
33
|
+
const classification = classifyFile(node, 0.5, [
|
|
34
|
+
'module1',
|
|
35
|
+
'module2',
|
|
36
|
+
'module3',
|
|
37
|
+
]);
|
|
34
38
|
expect(classification).toBe('barrel-export');
|
|
35
39
|
});
|
|
36
40
|
|
|
@@ -46,7 +50,11 @@ describe('file classification', () => {
|
|
|
46
50
|
linesOfCode: 100,
|
|
47
51
|
});
|
|
48
52
|
|
|
49
|
-
const classification = classifyFile(node, 0.5, [
|
|
53
|
+
const classification = classifyFile(node, 0.5, [
|
|
54
|
+
'user',
|
|
55
|
+
'order',
|
|
56
|
+
'product',
|
|
57
|
+
]);
|
|
50
58
|
expect(classification).toBe('type-definition');
|
|
51
59
|
});
|
|
52
60
|
|
|
@@ -54,7 +62,11 @@ describe('file classification', () => {
|
|
|
54
62
|
const node = createNode({
|
|
55
63
|
file: 'shared/src/types/audit/parser.ts',
|
|
56
64
|
exports: [
|
|
57
|
-
{
|
|
65
|
+
{
|
|
66
|
+
name: 'AuditParserConfig',
|
|
67
|
+
type: 'interface',
|
|
68
|
+
inferredDomain: 'audit',
|
|
69
|
+
},
|
|
58
70
|
{ name: 'ParseResult', type: 'type', inferredDomain: 'parse' },
|
|
59
71
|
],
|
|
60
72
|
linesOfCode: 50,
|
|
@@ -69,7 +81,11 @@ describe('file classification', () => {
|
|
|
69
81
|
file: 'shared/src/types/audit/status.ts',
|
|
70
82
|
exports: [
|
|
71
83
|
{ name: 'AuditStatus', type: 'type', inferredDomain: 'audit' },
|
|
72
|
-
{
|
|
84
|
+
{
|
|
85
|
+
name: 'StatusMapping',
|
|
86
|
+
type: 'interface',
|
|
87
|
+
inferredDomain: 'status',
|
|
88
|
+
},
|
|
73
89
|
],
|
|
74
90
|
linesOfCode: 30,
|
|
75
91
|
});
|
|
@@ -82,8 +98,16 @@ describe('file classification', () => {
|
|
|
82
98
|
const node = createNode({
|
|
83
99
|
file: 'src/models/user-models.ts', // NOT in /types/ but only type exports
|
|
84
100
|
exports: [
|
|
85
|
-
{
|
|
86
|
-
|
|
101
|
+
{
|
|
102
|
+
name: 'UserCreateInput',
|
|
103
|
+
type: 'interface',
|
|
104
|
+
inferredDomain: 'user',
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
name: 'UserUpdateInput',
|
|
108
|
+
type: 'interface',
|
|
109
|
+
inferredDomain: 'user',
|
|
110
|
+
},
|
|
87
111
|
{ name: 'UserFilter', type: 'type', inferredDomain: 'user' },
|
|
88
112
|
],
|
|
89
113
|
linesOfCode: 80,
|
|
@@ -122,7 +146,12 @@ describe('file classification', () => {
|
|
|
122
146
|
linesOfCode: 384,
|
|
123
147
|
});
|
|
124
148
|
|
|
125
|
-
const classification = classifyFile(node, 0.3, [
|
|
149
|
+
const classification = classifyFile(node, 0.3, [
|
|
150
|
+
'audit',
|
|
151
|
+
'job',
|
|
152
|
+
'order',
|
|
153
|
+
'doc',
|
|
154
|
+
]);
|
|
126
155
|
expect(classification).toBe('mixed-concerns');
|
|
127
156
|
});
|
|
128
157
|
|
|
@@ -140,7 +169,11 @@ describe('file classification', () => {
|
|
|
140
169
|
|
|
141
170
|
// Multiple domains + very low cohesion (< 0.4) = mixed concerns
|
|
142
171
|
// Note: NOT in /utils/ or /helpers/ or /services/ path
|
|
143
|
-
const classification = classifyFile(node, 0.3, [
|
|
172
|
+
const classification = classifyFile(node, 0.3, [
|
|
173
|
+
'date',
|
|
174
|
+
'report',
|
|
175
|
+
'audit',
|
|
176
|
+
]);
|
|
144
177
|
expect(classification).toBe('mixed-concerns');
|
|
145
178
|
});
|
|
146
179
|
|
|
@@ -200,17 +233,26 @@ describe('file classification', () => {
|
|
|
200
233
|
});
|
|
201
234
|
|
|
202
235
|
it('should return 0 fragmentation for type definitions', () => {
|
|
203
|
-
const result = adjustFragmentationForClassification(
|
|
236
|
+
const result = adjustFragmentationForClassification(
|
|
237
|
+
0.9,
|
|
238
|
+
'type-definition'
|
|
239
|
+
);
|
|
204
240
|
expect(result).toBe(0);
|
|
205
241
|
});
|
|
206
242
|
|
|
207
243
|
it('should reduce fragmentation by 70% for cohesive modules', () => {
|
|
208
|
-
const result = adjustFragmentationForClassification(
|
|
244
|
+
const result = adjustFragmentationForClassification(
|
|
245
|
+
0.6,
|
|
246
|
+
'cohesive-module'
|
|
247
|
+
);
|
|
209
248
|
expect(result).toBeCloseTo(0.18, 2); // 0.6 * 0.3
|
|
210
249
|
});
|
|
211
250
|
|
|
212
251
|
it('should keep full fragmentation for mixed concerns', () => {
|
|
213
|
-
const result = adjustFragmentationForClassification(
|
|
252
|
+
const result = adjustFragmentationForClassification(
|
|
253
|
+
0.7,
|
|
254
|
+
'mixed-concerns'
|
|
255
|
+
);
|
|
214
256
|
expect(result).toBe(0.7);
|
|
215
257
|
});
|
|
216
258
|
|
|
@@ -227,7 +269,9 @@ describe('file classification', () => {
|
|
|
227
269
|
'src/index.ts',
|
|
228
270
|
['High fragmentation']
|
|
229
271
|
);
|
|
230
|
-
expect(recommendations).toContain(
|
|
272
|
+
expect(recommendations).toContain(
|
|
273
|
+
'Barrel export file detected - multiple domains are expected here'
|
|
274
|
+
);
|
|
231
275
|
});
|
|
232
276
|
|
|
233
277
|
it('should provide type definition recommendations', () => {
|
|
@@ -236,7 +280,9 @@ describe('file classification', () => {
|
|
|
236
280
|
'src/types.ts',
|
|
237
281
|
['High fragmentation']
|
|
238
282
|
);
|
|
239
|
-
expect(recommendations).toContain(
|
|
283
|
+
expect(recommendations).toContain(
|
|
284
|
+
'Type definition file - centralized types improve consistency'
|
|
285
|
+
);
|
|
240
286
|
});
|
|
241
287
|
|
|
242
288
|
it('should provide cohesive module recommendations', () => {
|
|
@@ -245,7 +291,9 @@ describe('file classification', () => {
|
|
|
245
291
|
'src/calculator.ts',
|
|
246
292
|
[]
|
|
247
293
|
);
|
|
248
|
-
expect(recommendations).toContain(
|
|
294
|
+
expect(recommendations).toContain(
|
|
295
|
+
'Module has good cohesion despite its size'
|
|
296
|
+
);
|
|
249
297
|
});
|
|
250
298
|
|
|
251
299
|
it('should provide mixed concerns recommendations', () => {
|
|
@@ -254,7 +302,9 @@ describe('file classification', () => {
|
|
|
254
302
|
'src/audit.ts',
|
|
255
303
|
['Multiple domains detected']
|
|
256
304
|
);
|
|
257
|
-
expect(recommendations).toContain(
|
|
305
|
+
expect(recommendations).toContain(
|
|
306
|
+
'Consider splitting this file by domain'
|
|
307
|
+
);
|
|
258
308
|
});
|
|
259
309
|
});
|
|
260
310
|
|
|
@@ -262,7 +312,13 @@ describe('file classification', () => {
|
|
|
262
312
|
it('should detect barrel export even for non-index files with re-export patterns', () => {
|
|
263
313
|
const node = createNode({
|
|
264
314
|
file: 'src/exports.ts',
|
|
265
|
-
imports: [
|
|
315
|
+
imports: [
|
|
316
|
+
'../module1',
|
|
317
|
+
'../module2',
|
|
318
|
+
'../module3',
|
|
319
|
+
'../module4',
|
|
320
|
+
'../module5',
|
|
321
|
+
],
|
|
266
322
|
exports: [
|
|
267
323
|
{ name: 'a', type: 'function' },
|
|
268
324
|
{ name: 'b', type: 'function' },
|
|
@@ -281,9 +337,7 @@ describe('file classification', () => {
|
|
|
281
337
|
const node = createNode({
|
|
282
338
|
file: 'src/components/Calculator.tsx', // NOT an index file
|
|
283
339
|
imports: ['react', '../hooks', '../utils'],
|
|
284
|
-
exports: [
|
|
285
|
-
{ name: 'Calculator', type: 'function' },
|
|
286
|
-
],
|
|
340
|
+
exports: [{ name: 'Calculator', type: 'function' }],
|
|
287
341
|
linesOfCode: 346, // Substantial code
|
|
288
342
|
});
|
|
289
343
|
|
|
@@ -313,7 +367,11 @@ describe('file classification', () => {
|
|
|
313
367
|
const node = createNode({
|
|
314
368
|
file: 'src/api/process.ts',
|
|
315
369
|
exports: [
|
|
316
|
-
{
|
|
370
|
+
{
|
|
371
|
+
name: 'processHandler',
|
|
372
|
+
type: 'function',
|
|
373
|
+
inferredDomain: 'process',
|
|
374
|
+
},
|
|
317
375
|
],
|
|
318
376
|
imports: ['../services/queue'],
|
|
319
377
|
linesOfCode: 80,
|
|
@@ -363,7 +421,11 @@ describe('file classification', () => {
|
|
|
363
421
|
linesOfCode: 200,
|
|
364
422
|
});
|
|
365
423
|
|
|
366
|
-
const classification = classifyFile(node, 0.07, [
|
|
424
|
+
const classification = classifyFile(node, 0.07, [
|
|
425
|
+
'email',
|
|
426
|
+
'smtp',
|
|
427
|
+
'templates',
|
|
428
|
+
]);
|
|
367
429
|
expect(classification).toBe('service-file');
|
|
368
430
|
});
|
|
369
431
|
|
|
@@ -371,8 +433,16 @@ describe('file classification', () => {
|
|
|
371
433
|
const node = createNode({
|
|
372
434
|
file: 'src/services/notification.ts',
|
|
373
435
|
exports: [
|
|
374
|
-
{
|
|
375
|
-
|
|
436
|
+
{
|
|
437
|
+
name: 'sendNotification',
|
|
438
|
+
type: 'function',
|
|
439
|
+
inferredDomain: 'notification',
|
|
440
|
+
},
|
|
441
|
+
{
|
|
442
|
+
name: 'queueNotification',
|
|
443
|
+
type: 'function',
|
|
444
|
+
inferredDomain: 'notification',
|
|
445
|
+
},
|
|
376
446
|
],
|
|
377
447
|
imports: ['../db', '../email'],
|
|
378
448
|
linesOfCode: 120,
|
|
@@ -402,13 +472,21 @@ describe('file classification', () => {
|
|
|
402
472
|
const node = createNode({
|
|
403
473
|
file: 'src/emails/receipt-writer.ts',
|
|
404
474
|
exports: [
|
|
405
|
-
{
|
|
475
|
+
{
|
|
476
|
+
name: 'generateReceipt',
|
|
477
|
+
type: 'function',
|
|
478
|
+
inferredDomain: 'receipt',
|
|
479
|
+
},
|
|
406
480
|
],
|
|
407
481
|
imports: ['../templates/base', '../services/db'],
|
|
408
482
|
linesOfCode: 150,
|
|
409
483
|
});
|
|
410
484
|
|
|
411
|
-
const classification = classifyFile(node, 0.08, [
|
|
485
|
+
const classification = classifyFile(node, 0.08, [
|
|
486
|
+
'receipt',
|
|
487
|
+
'templates',
|
|
488
|
+
'db',
|
|
489
|
+
]);
|
|
412
490
|
expect(classification).toBe('email-template');
|
|
413
491
|
});
|
|
414
492
|
|
|
@@ -416,7 +494,11 @@ describe('file classification', () => {
|
|
|
416
494
|
const node = createNode({
|
|
417
495
|
file: 'src/emails/welcome.ts',
|
|
418
496
|
exports: [
|
|
419
|
-
{
|
|
497
|
+
{
|
|
498
|
+
name: 'renderWelcomeEmail',
|
|
499
|
+
type: 'function',
|
|
500
|
+
inferredDomain: 'email',
|
|
501
|
+
},
|
|
420
502
|
],
|
|
421
503
|
imports: ['../templates/layout'],
|
|
422
504
|
linesOfCode: 80,
|
|
@@ -430,13 +512,17 @@ describe('file classification', () => {
|
|
|
430
512
|
const node = createNode({
|
|
431
513
|
file: 'src/templates/invoice-template.ts',
|
|
432
514
|
exports: [
|
|
433
|
-
{
|
|
515
|
+
{
|
|
516
|
+
name: 'renderInvoice',
|
|
517
|
+
type: 'function',
|
|
518
|
+
inferredDomain: 'invoice',
|
|
519
|
+
},
|
|
434
520
|
],
|
|
435
521
|
imports: ['../services/pdf'],
|
|
436
522
|
linesOfCode: 100,
|
|
437
523
|
});
|
|
438
524
|
|
|
439
|
-
const classification = classifyFile(node, 0.
|
|
525
|
+
const classification = classifyFile(node, 0.2, ['invoice']);
|
|
440
526
|
expect(classification).toBe('email-template');
|
|
441
527
|
});
|
|
442
528
|
});
|
|
@@ -446,8 +532,16 @@ describe('file classification', () => {
|
|
|
446
532
|
const node = createNode({
|
|
447
533
|
file: 'src/parsers/base-parser-deterministic.ts',
|
|
448
534
|
exports: [
|
|
449
|
-
{
|
|
450
|
-
|
|
535
|
+
{
|
|
536
|
+
name: 'parseDeterministic',
|
|
537
|
+
type: 'function',
|
|
538
|
+
inferredDomain: 'parse',
|
|
539
|
+
},
|
|
540
|
+
{
|
|
541
|
+
name: 'parseNonDeterministic',
|
|
542
|
+
type: 'function',
|
|
543
|
+
inferredDomain: 'parse',
|
|
544
|
+
},
|
|
451
545
|
],
|
|
452
546
|
imports: ['../utils/transform'],
|
|
453
547
|
linesOfCode: 120,
|
|
@@ -476,14 +570,22 @@ describe('file classification', () => {
|
|
|
476
570
|
const node = createNode({
|
|
477
571
|
file: 'src/converters/xml-converter.ts',
|
|
478
572
|
exports: [
|
|
479
|
-
{
|
|
480
|
-
|
|
573
|
+
{
|
|
574
|
+
name: 'convertXmlToJson',
|
|
575
|
+
type: 'function',
|
|
576
|
+
inferredDomain: 'xml',
|
|
577
|
+
},
|
|
578
|
+
{
|
|
579
|
+
name: 'convertJsonToXml',
|
|
580
|
+
type: 'function',
|
|
581
|
+
inferredDomain: 'xml',
|
|
582
|
+
},
|
|
481
583
|
],
|
|
482
584
|
imports: ['xml2js'],
|
|
483
585
|
linesOfCode: 60,
|
|
484
586
|
});
|
|
485
587
|
|
|
486
|
-
const classification = classifyFile(node, 0.
|
|
588
|
+
const classification = classifyFile(node, 0.3, ['xml']);
|
|
487
589
|
expect(classification).toBe('parser-file');
|
|
488
590
|
});
|
|
489
591
|
});
|
|
@@ -532,7 +634,7 @@ describe('file classification', () => {
|
|
|
532
634
|
linesOfCode: 50,
|
|
533
635
|
});
|
|
534
636
|
|
|
535
|
-
const classification = classifyFile(node, 0.
|
|
637
|
+
const classification = classifyFile(node, 0.3, ['date']);
|
|
536
638
|
expect(classification).toBe('utility-module');
|
|
537
639
|
});
|
|
538
640
|
});
|
|
@@ -542,9 +644,17 @@ describe('file classification', () => {
|
|
|
542
644
|
const node = createNode({
|
|
543
645
|
file: 'src/session.ts',
|
|
544
646
|
exports: [
|
|
545
|
-
{
|
|
647
|
+
{
|
|
648
|
+
name: 'createSession',
|
|
649
|
+
type: 'function',
|
|
650
|
+
inferredDomain: 'session',
|
|
651
|
+
},
|
|
546
652
|
{ name: 'getSession', type: 'function', inferredDomain: 'session' },
|
|
547
|
-
{
|
|
653
|
+
{
|
|
654
|
+
name: 'destroySession',
|
|
655
|
+
type: 'function',
|
|
656
|
+
inferredDomain: 'session',
|
|
657
|
+
},
|
|
548
658
|
],
|
|
549
659
|
imports: ['../db', '../auth'],
|
|
550
660
|
linesOfCode: 100,
|
|
@@ -615,7 +725,11 @@ describe('file classification', () => {
|
|
|
615
725
|
});
|
|
616
726
|
|
|
617
727
|
// Related names (all Item operations) should get higher boost
|
|
618
|
-
const result = adjustCohesionForClassification(
|
|
728
|
+
const result = adjustCohesionForClassification(
|
|
729
|
+
0.21,
|
|
730
|
+
'utility-module',
|
|
731
|
+
node
|
|
732
|
+
);
|
|
619
733
|
expect(result).toBeGreaterThan(0.5); // Significant boost for related names
|
|
620
734
|
});
|
|
621
735
|
|
|
@@ -630,7 +744,11 @@ describe('file classification', () => {
|
|
|
630
744
|
});
|
|
631
745
|
|
|
632
746
|
// Single entry point should get higher boost
|
|
633
|
-
const result = adjustCohesionForClassification(
|
|
747
|
+
const result = adjustCohesionForClassification(
|
|
748
|
+
0.22,
|
|
749
|
+
'lambda-handler',
|
|
750
|
+
node
|
|
751
|
+
);
|
|
634
752
|
expect(result).toBeGreaterThan(0.5); // Significant boost for single entry
|
|
635
753
|
});
|
|
636
754
|
|
|
@@ -645,7 +763,11 @@ describe('file classification', () => {
|
|
|
645
763
|
});
|
|
646
764
|
|
|
647
765
|
// Class-based service should get higher boost
|
|
648
|
-
const result = adjustCohesionForClassification(
|
|
766
|
+
const result = adjustCohesionForClassification(
|
|
767
|
+
0.15,
|
|
768
|
+
'service-file',
|
|
769
|
+
node
|
|
770
|
+
);
|
|
649
771
|
expect(result).toBeGreaterThan(0.45); // Significant boost for class-based
|
|
650
772
|
});
|
|
651
773
|
});
|
|
@@ -657,7 +779,9 @@ describe('file classification', () => {
|
|
|
657
779
|
'src/utils/helpers.ts',
|
|
658
780
|
['Low cohesion']
|
|
659
781
|
);
|
|
660
|
-
expect(recommendations).toContain(
|
|
782
|
+
expect(recommendations).toContain(
|
|
783
|
+
'Utility module detected - multiple domains are acceptable here'
|
|
784
|
+
);
|
|
661
785
|
});
|
|
662
786
|
|
|
663
787
|
it('should provide service-file recommendations', () => {
|
|
@@ -666,7 +790,9 @@ describe('file classification', () => {
|
|
|
666
790
|
'src/services/email.ts',
|
|
667
791
|
['Multiple domains']
|
|
668
792
|
);
|
|
669
|
-
expect(recommendations).toContain(
|
|
793
|
+
expect(recommendations).toContain(
|
|
794
|
+
'Service file detected - orchestration of multiple dependencies is expected'
|
|
795
|
+
);
|
|
670
796
|
});
|
|
671
797
|
|
|
672
798
|
it('should provide lambda-handler recommendations', () => {
|
|
@@ -675,7 +801,9 @@ describe('file classification', () => {
|
|
|
675
801
|
'src/handlers/process.ts',
|
|
676
802
|
['Low cohesion']
|
|
677
803
|
);
|
|
678
|
-
expect(recommendations).toContain(
|
|
804
|
+
expect(recommendations).toContain(
|
|
805
|
+
'Lambda handler detected - coordination of services is expected'
|
|
806
|
+
);
|
|
679
807
|
});
|
|
680
808
|
|
|
681
809
|
it('should provide email-template recommendations', () => {
|
|
@@ -684,7 +812,9 @@ describe('file classification', () => {
|
|
|
684
812
|
'src/emails/receipt.ts',
|
|
685
813
|
['Multiple domains']
|
|
686
814
|
);
|
|
687
|
-
expect(recommendations).toContain(
|
|
815
|
+
expect(recommendations).toContain(
|
|
816
|
+
'Email template detected - references multiple domains for rendering'
|
|
817
|
+
);
|
|
688
818
|
});
|
|
689
819
|
|
|
690
820
|
it('should provide parser-file recommendations', () => {
|
|
@@ -693,7 +823,9 @@ describe('file classification', () => {
|
|
|
693
823
|
'src/parsers/data.ts',
|
|
694
824
|
['Multiple domains']
|
|
695
825
|
);
|
|
696
|
-
expect(recommendations).toContain(
|
|
826
|
+
expect(recommendations).toContain(
|
|
827
|
+
'Parser/transformer file detected - handles multiple data sources'
|
|
828
|
+
);
|
|
697
829
|
});
|
|
698
830
|
});
|
|
699
831
|
|
|
@@ -711,7 +843,12 @@ describe('file classification', () => {
|
|
|
711
843
|
linesOfCode: 208,
|
|
712
844
|
});
|
|
713
845
|
|
|
714
|
-
const classification = classifyFile(node, 0.25, [
|
|
846
|
+
const classification = classifyFile(node, 0.25, [
|
|
847
|
+
'seo',
|
|
848
|
+
'jsonld',
|
|
849
|
+
'page',
|
|
850
|
+
'ui',
|
|
851
|
+
]);
|
|
715
852
|
expect(classification).toBe('nextjs-page');
|
|
716
853
|
});
|
|
717
854
|
|
|
@@ -727,16 +864,14 @@ describe('file classification', () => {
|
|
|
727
864
|
linesOfCode: 204,
|
|
728
865
|
});
|
|
729
866
|
|
|
730
|
-
const classification = classifyFile(node, 0.
|
|
867
|
+
const classification = classifyFile(node, 0.3, ['seo', 'jsonld', 'page']);
|
|
731
868
|
expect(classification).toBe('nextjs-page');
|
|
732
869
|
});
|
|
733
870
|
|
|
734
871
|
it('should not classify non-page.tsx files in /app/ as nextjs-page', () => {
|
|
735
872
|
const node = createNode({
|
|
736
873
|
file: 'app/components/Header.tsx',
|
|
737
|
-
exports: [
|
|
738
|
-
{ name: 'Header', type: 'function', inferredDomain: 'ui' },
|
|
739
|
-
],
|
|
874
|
+
exports: [{ name: 'Header', type: 'function', inferredDomain: 'ui' }],
|
|
740
875
|
imports: ['react'],
|
|
741
876
|
linesOfCode: 50,
|
|
742
877
|
});
|
|
@@ -748,9 +883,7 @@ describe('file classification', () => {
|
|
|
748
883
|
it('should not classify page.tsx files outside /app/ as nextjs-page', () => {
|
|
749
884
|
const node = createNode({
|
|
750
885
|
file: 'src/pages/page.tsx', // Pages Router, not App Router
|
|
751
|
-
exports: [
|
|
752
|
-
{ name: 'default', type: 'default', inferredDomain: 'page' },
|
|
753
|
-
],
|
|
886
|
+
exports: [{ name: 'default', type: 'default', inferredDomain: 'page' }],
|
|
754
887
|
imports: ['react'],
|
|
755
888
|
linesOfCode: 100,
|
|
756
889
|
});
|
|
@@ -796,7 +929,9 @@ describe('file classification', () => {
|
|
|
796
929
|
'app/calculator/page.tsx',
|
|
797
930
|
['Low cohesion']
|
|
798
931
|
);
|
|
799
|
-
expect(recommendations).toContain(
|
|
932
|
+
expect(recommendations).toContain(
|
|
933
|
+
'Next.js App Router page detected - metadata/JSON-LD/component pattern is cohesive'
|
|
934
|
+
);
|
|
800
935
|
});
|
|
801
936
|
});
|
|
802
937
|
});
|
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import {
|
|
3
|
-
calculatePathEntropy,
|
|
4
|
-
calculateDirectoryDistance,
|
|
5
|
-
} from '../analyzer';
|
|
2
|
+
import { calculatePathEntropy, calculateDirectoryDistance } from '../analyzer';
|
|
6
3
|
|
|
7
4
|
describe('advanced fragmentation heuristics', () => {
|
|
8
5
|
it('path entropy is low when most files are in one directory', () => {
|
|
@@ -25,13 +22,7 @@ describe('advanced fragmentation heuristics', () => {
|
|
|
25
22
|
});
|
|
26
23
|
|
|
27
24
|
it('path entropy is high when files are evenly distributed', () => {
|
|
28
|
-
const files = [
|
|
29
|
-
'a/f1.ts',
|
|
30
|
-
'b/f2.ts',
|
|
31
|
-
'c/f3.ts',
|
|
32
|
-
'd/f4.ts',
|
|
33
|
-
'e/f5.ts',
|
|
34
|
-
];
|
|
25
|
+
const files = ['a/f1.ts', 'b/f2.ts', 'c/f3.ts', 'd/f4.ts', 'e/f5.ts'];
|
|
35
26
|
|
|
36
27
|
const entropy = calculatePathEntropy(files);
|
|
37
28
|
expect(entropy).toBeGreaterThan(0.8);
|
|
@@ -27,7 +27,10 @@ describe('fragmentation coupling discount', () => {
|
|
|
27
27
|
const cluster = clusters.find((c) => c.domain === 'billing');
|
|
28
28
|
expect(cluster).toBeDefined();
|
|
29
29
|
|
|
30
|
-
const base = calculateFragmentation(
|
|
30
|
+
const base = calculateFragmentation(
|
|
31
|
+
files.map((f) => f.file),
|
|
32
|
+
'billing'
|
|
33
|
+
);
|
|
31
34
|
// With no import similarity the coupling discount should be 0 -> fragmentation unchanged
|
|
32
35
|
expect(cluster!.fragmentationScore).toBeCloseTo(base, 6);
|
|
33
36
|
});
|
|
@@ -53,7 +56,10 @@ describe('fragmentation coupling discount', () => {
|
|
|
53
56
|
const cluster = clusters.find((c) => c.domain === 'billing');
|
|
54
57
|
expect(cluster).toBeDefined();
|
|
55
58
|
|
|
56
|
-
const base = calculateFragmentation(
|
|
59
|
+
const base = calculateFragmentation(
|
|
60
|
+
files.map((f) => f.file),
|
|
61
|
+
'billing'
|
|
62
|
+
);
|
|
57
63
|
const expected = base * 0.8; // full cohesion => 20% discount
|
|
58
64
|
|
|
59
65
|
// Allow small FP tolerance
|
|
@@ -5,16 +5,13 @@ describe('calculateFragmentation (log scale option)', () => {
|
|
|
5
5
|
it('returns 0 for single file regardless of option', () => {
|
|
6
6
|
const files = ['src/user/user.ts'];
|
|
7
7
|
expect(calculateFragmentation(files, 'user')).toBe(0);
|
|
8
|
-
expect(calculateFragmentation(files, 'user', { useLogScale: true })).toBe(
|
|
8
|
+
expect(calculateFragmentation(files, 'user', { useLogScale: true })).toBe(
|
|
9
|
+
0
|
|
10
|
+
);
|
|
9
11
|
});
|
|
10
12
|
|
|
11
13
|
it('matches linear formula when not using log scale', () => {
|
|
12
|
-
const files = [
|
|
13
|
-
'a/one.ts',
|
|
14
|
-
'b/two.ts',
|
|
15
|
-
'c/three.ts',
|
|
16
|
-
'd/four.ts',
|
|
17
|
-
];
|
|
14
|
+
const files = ['a/one.ts', 'b/two.ts', 'c/three.ts', 'd/four.ts'];
|
|
18
15
|
|
|
19
16
|
const uniqueDirs = 4;
|
|
20
17
|
const linear = (uniqueDirs - 1) / (files.length - 1);
|
|
@@ -30,9 +27,12 @@ describe('calculateFragmentation (log scale option)', () => {
|
|
|
30
27
|
'tools/x/e.ts',
|
|
31
28
|
];
|
|
32
29
|
|
|
33
|
-
const dirs = new Set(files.map((f) => f.split('/').slice(0, -1).join('/')))
|
|
30
|
+
const dirs = new Set(files.map((f) => f.split('/').slice(0, -1).join('/')))
|
|
31
|
+
.size;
|
|
34
32
|
const expected = Math.log(dirs) / Math.log(files.length);
|
|
35
33
|
|
|
36
|
-
expect(
|
|
34
|
+
expect(
|
|
35
|
+
calculateFragmentation(files, 'domain', { useLogScale: true })
|
|
36
|
+
).toBeCloseTo(expected, 6);
|
|
37
37
|
});
|
|
38
38
|
});
|
|
@@ -38,7 +38,9 @@ describe('Context Scoring', () => {
|
|
|
38
38
|
const result = calculateContextScore(summary);
|
|
39
39
|
|
|
40
40
|
expect(result.score).toBeLessThan(70);
|
|
41
|
-
expect(result.factors.some(f => f.name === 'Context Budget')).toBe(
|
|
41
|
+
expect(result.factors.some((f) => f.name === 'Context Budget')).toBe(
|
|
42
|
+
true
|
|
43
|
+
);
|
|
42
44
|
expect(result.recommendations.length).toBeGreaterThan(0);
|
|
43
45
|
});
|
|
44
46
|
|
|
@@ -58,8 +60,10 @@ describe('Context Scoring', () => {
|
|
|
58
60
|
|
|
59
61
|
// With depth=12: depthScore=30, rawScore=(100*0.4)+(30*0.3)+(100*0.3)=79
|
|
60
62
|
expect(result.score).toBe(79);
|
|
61
|
-
expect(result.factors.some(f => f.name === 'Import Depth')).toBe(true);
|
|
62
|
-
expect(
|
|
63
|
+
expect(result.factors.some((f) => f.name === 'Import Depth')).toBe(true);
|
|
64
|
+
expect(
|
|
65
|
+
result.recommendations.some((r) => r.action.includes('import chains'))
|
|
66
|
+
).toBe(true);
|
|
63
67
|
});
|
|
64
68
|
|
|
65
69
|
it('should penalize high fragmentation', () => {
|
|
@@ -77,7 +81,7 @@ describe('Context Scoring', () => {
|
|
|
77
81
|
const result = calculateContextScore(summary);
|
|
78
82
|
|
|
79
83
|
expect(result.score).toBeLessThan(80); // Adjusted threshold
|
|
80
|
-
expect(result.factors.some(f => f.name === 'Fragmentation')).toBe(true);
|
|
84
|
+
expect(result.factors.some((f) => f.name === 'Fragmentation')).toBe(true);
|
|
81
85
|
});
|
|
82
86
|
|
|
83
87
|
it('should apply critical issue penalties', () => {
|
|
@@ -96,7 +100,9 @@ describe('Context Scoring', () => {
|
|
|
96
100
|
|
|
97
101
|
// Perfect subscores=100, minus 5*10=50 critical penalty = 50
|
|
98
102
|
expect(result.score).toBe(50);
|
|
99
|
-
expect(result.factors.some(f => f.name === 'Critical Issues')).toBe(
|
|
103
|
+
expect(result.factors.some((f) => f.name === 'Critical Issues')).toBe(
|
|
104
|
+
true
|
|
105
|
+
);
|
|
100
106
|
});
|
|
101
107
|
|
|
102
108
|
it('should handle extreme max budget penalty', () => {
|
|
@@ -113,8 +119,14 @@ describe('Context Scoring', () => {
|
|
|
113
119
|
|
|
114
120
|
const result = calculateContextScore(summary);
|
|
115
121
|
|
|
116
|
-
expect(
|
|
117
|
-
|
|
122
|
+
expect(
|
|
123
|
+
result.factors.some((f) => f.name === 'Extreme File Detected')
|
|
124
|
+
).toBe(true);
|
|
125
|
+
expect(
|
|
126
|
+
result.recommendations.some((r) =>
|
|
127
|
+
r.action.includes('Split large file')
|
|
128
|
+
)
|
|
129
|
+
).toBe(true);
|
|
118
130
|
});
|
|
119
131
|
|
|
120
132
|
it('should combine multiple penalties correctly', () => {
|