@aiready/context-analyzer 0.19.17 → 0.19.21
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/.turbo/turbo-build.log +10 -10
- package/.turbo/turbo-lint.log +2 -22
- package/.turbo/turbo-test.log +23 -23
- package/coverage/analyzer.ts.html +1369 -0
- package/coverage/ast-utils.ts.html +382 -0
- package/coverage/base.css +224 -0
- package/coverage/block-navigation.js +87 -0
- package/coverage/classifier.ts.html +1771 -0
- package/coverage/clover.xml +1245 -0
- package/coverage/cluster-detector.ts.html +385 -0
- package/coverage/coverage-final.json +15 -0
- package/coverage/defaults.ts.html +262 -0
- package/coverage/favicon.png +0 -0
- package/coverage/graph-builder.ts.html +859 -0
- package/coverage/index.html +311 -0
- package/coverage/index.ts.html +124 -0
- package/coverage/metrics.ts.html +748 -0
- package/coverage/prettify.css +1 -0
- package/coverage/prettify.js +2 -0
- package/coverage/provider.ts.html +283 -0
- package/coverage/remediation.ts.html +502 -0
- package/coverage/scoring.ts.html +619 -0
- package/coverage/semantic-analysis.ts.html +1201 -0
- package/coverage/sort-arrow-sprite.png +0 -0
- package/coverage/sorter.js +210 -0
- package/coverage/summary.ts.html +625 -0
- package/coverage/types.ts.html +571 -0
- package/dist/chunk-736QSHJP.mjs +1807 -0
- package/dist/chunk-CCBNKQYB.mjs +1812 -0
- package/dist/chunk-JUHHOSHG.mjs +1808 -0
- package/dist/cli.js +393 -379
- package/dist/cli.mjs +1 -1
- package/dist/index.d.mts +65 -6
- package/dist/index.d.ts +65 -6
- package/dist/index.js +396 -380
- package/dist/index.mjs +3 -1
- package/package.json +2 -2
- package/src/__tests__/cluster-detector.test.ts +138 -0
- package/src/__tests__/provider.test.ts +78 -0
- package/src/__tests__/remediation.test.ts +94 -0
- package/src/analyzer.ts +244 -1
- package/src/classifier.ts +100 -35
- package/src/index.ts +1 -242
- package/src/provider.ts +2 -1
package/src/classifier.ts
CHANGED
|
@@ -1,7 +1,29 @@
|
|
|
1
1
|
import type { DependencyNode, FileClassification } from './types';
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Constants for file classifications to avoid magic strings
|
|
5
|
+
*/
|
|
6
|
+
export const Classification = {
|
|
7
|
+
BARREL: 'barrel-export' as const,
|
|
8
|
+
TYPE_DEFINITION: 'type-definition' as const,
|
|
9
|
+
NEXTJS_PAGE: 'nextjs-page' as const,
|
|
10
|
+
LAMBDA_HANDLER: 'lambda-handler' as const,
|
|
11
|
+
SERVICE: 'service-file' as const,
|
|
12
|
+
EMAIL_TEMPLATE: 'email-template' as const,
|
|
13
|
+
PARSER: 'parser-file' as const,
|
|
14
|
+
COHESIVE_MODULE: 'cohesive-module' as const,
|
|
15
|
+
UTILITY_MODULE: 'utility-module' as const,
|
|
16
|
+
MIXED_CONCERNS: 'mixed-concerns' as const,
|
|
17
|
+
UNKNOWN: 'unknown' as const,
|
|
18
|
+
};
|
|
19
|
+
|
|
3
20
|
/**
|
|
4
21
|
* Classify a file into a specific type for better analysis context
|
|
22
|
+
*
|
|
23
|
+
* @param node The dependency node representing the file
|
|
24
|
+
* @param cohesionScore The calculated cohesion score for the file
|
|
25
|
+
* @param domains The detected domains/concerns for the file
|
|
26
|
+
* @returns The determined file classification
|
|
5
27
|
*/
|
|
6
28
|
export function classifyFile(
|
|
7
29
|
node: DependencyNode,
|
|
@@ -10,74 +32,78 @@ export function classifyFile(
|
|
|
10
32
|
): FileClassification {
|
|
11
33
|
// 1. Detect barrel exports (primarily re-exports)
|
|
12
34
|
if (isBarrelExport(node)) {
|
|
13
|
-
return
|
|
35
|
+
return Classification.BARREL;
|
|
14
36
|
}
|
|
15
37
|
|
|
16
38
|
// 2. Detect type definition files
|
|
17
39
|
if (isTypeDefinition(node)) {
|
|
18
|
-
return
|
|
40
|
+
return Classification.TYPE_DEFINITION;
|
|
19
41
|
}
|
|
20
42
|
|
|
21
43
|
// 3. Detect Next.js App Router pages
|
|
22
44
|
if (isNextJsPage(node)) {
|
|
23
|
-
return
|
|
45
|
+
return Classification.NEXTJS_PAGE;
|
|
24
46
|
}
|
|
25
47
|
|
|
26
48
|
// 4. Detect Lambda handlers
|
|
27
49
|
if (isLambdaHandler(node)) {
|
|
28
|
-
return
|
|
50
|
+
return Classification.LAMBDA_HANDLER;
|
|
29
51
|
}
|
|
30
52
|
|
|
31
53
|
// 5. Detect Service files
|
|
32
54
|
if (isServiceFile(node)) {
|
|
33
|
-
return
|
|
55
|
+
return Classification.SERVICE;
|
|
34
56
|
}
|
|
35
57
|
|
|
36
58
|
// 6. Detect Email templates
|
|
37
59
|
if (isEmailTemplate(node)) {
|
|
38
|
-
return
|
|
60
|
+
return Classification.EMAIL_TEMPLATE;
|
|
39
61
|
}
|
|
40
62
|
|
|
41
63
|
// 7. Detect Parser/Transformer files
|
|
42
64
|
if (isParserFile(node)) {
|
|
43
|
-
return
|
|
65
|
+
return Classification.PARSER;
|
|
44
66
|
}
|
|
45
67
|
|
|
46
68
|
// 8. Detect Session/State management files
|
|
47
69
|
if (isSessionFile(node)) {
|
|
48
70
|
// If it has high cohesion, it's a cohesive module
|
|
49
|
-
if (cohesionScore >= 0.25 && domains.length <= 1)
|
|
50
|
-
|
|
71
|
+
if (cohesionScore >= 0.25 && domains.length <= 1)
|
|
72
|
+
return Classification.COHESIVE_MODULE;
|
|
73
|
+
return Classification.UTILITY_MODULE; // Group with utility for now
|
|
51
74
|
}
|
|
52
75
|
|
|
53
76
|
// 9. Detect Utility modules (multi-domain but functional purpose)
|
|
54
77
|
if (isUtilityModule(node)) {
|
|
55
|
-
return
|
|
78
|
+
return Classification.UTILITY_MODULE;
|
|
56
79
|
}
|
|
57
80
|
|
|
58
81
|
// 10. Detect Config/Schema files
|
|
59
82
|
if (isConfigFile(node)) {
|
|
60
|
-
return
|
|
83
|
+
return Classification.COHESIVE_MODULE;
|
|
61
84
|
}
|
|
62
85
|
|
|
63
86
|
// Cohesion and Domain heuristics
|
|
64
87
|
if (domains.length <= 1 && domains[0] !== 'unknown') {
|
|
65
|
-
return
|
|
88
|
+
return Classification.COHESIVE_MODULE;
|
|
66
89
|
}
|
|
67
90
|
|
|
68
91
|
if (domains.length > 1 && cohesionScore < 0.4) {
|
|
69
|
-
return
|
|
92
|
+
return Classification.MIXED_CONCERNS;
|
|
70
93
|
}
|
|
71
94
|
|
|
72
95
|
if (cohesionScore >= 0.7) {
|
|
73
|
-
return
|
|
96
|
+
return Classification.COHESIVE_MODULE;
|
|
74
97
|
}
|
|
75
98
|
|
|
76
|
-
return
|
|
99
|
+
return Classification.UNKNOWN;
|
|
77
100
|
}
|
|
78
101
|
|
|
79
102
|
/**
|
|
80
103
|
* Detect if a file is a barrel export (index.ts)
|
|
104
|
+
*
|
|
105
|
+
* @param node The dependency node to check
|
|
106
|
+
* @returns True if the file appears to be a barrel export
|
|
81
107
|
*/
|
|
82
108
|
export function isBarrelExport(node: DependencyNode): boolean {
|
|
83
109
|
const { file, exports } = node;
|
|
@@ -106,6 +132,9 @@ export function isBarrelExport(node: DependencyNode): boolean {
|
|
|
106
132
|
|
|
107
133
|
/**
|
|
108
134
|
* Detect if a file is primarily type definitions
|
|
135
|
+
*
|
|
136
|
+
* @param node The dependency node to check
|
|
137
|
+
* @returns True if the file appears to be primarily types
|
|
109
138
|
*/
|
|
110
139
|
export function isTypeDefinition(node: DependencyNode): boolean {
|
|
111
140
|
const { file } = node;
|
|
@@ -132,6 +161,9 @@ export function isTypeDefinition(node: DependencyNode): boolean {
|
|
|
132
161
|
|
|
133
162
|
/**
|
|
134
163
|
* Detect if a file is a utility module
|
|
164
|
+
*
|
|
165
|
+
* @param node The dependency node to check
|
|
166
|
+
* @returns True if the file appears to be a utility module
|
|
135
167
|
*/
|
|
136
168
|
export function isUtilityModule(node: DependencyNode): boolean {
|
|
137
169
|
const { file } = node;
|
|
@@ -155,6 +187,9 @@ export function isUtilityModule(node: DependencyNode): boolean {
|
|
|
155
187
|
|
|
156
188
|
/**
|
|
157
189
|
* Detect if a file is a Lambda/API handler
|
|
190
|
+
*
|
|
191
|
+
* @param node The dependency node to check
|
|
192
|
+
* @returns True if the file appears to be a Lambda handler
|
|
158
193
|
*/
|
|
159
194
|
export function isLambdaHandler(node: DependencyNode): boolean {
|
|
160
195
|
const { file, exports } = node;
|
|
@@ -191,6 +226,9 @@ export function isLambdaHandler(node: DependencyNode): boolean {
|
|
|
191
226
|
|
|
192
227
|
/**
|
|
193
228
|
* Detect if a file is a service file
|
|
229
|
+
*
|
|
230
|
+
* @param node The dependency node to check
|
|
231
|
+
* @returns True if the file appears to be a service file
|
|
194
232
|
*/
|
|
195
233
|
export function isServiceFile(node: DependencyNode): boolean {
|
|
196
234
|
const { file, exports } = node;
|
|
@@ -217,6 +255,9 @@ export function isServiceFile(node: DependencyNode): boolean {
|
|
|
217
255
|
|
|
218
256
|
/**
|
|
219
257
|
* Detect if a file is an email template/layout
|
|
258
|
+
*
|
|
259
|
+
* @param node The dependency node to check
|
|
260
|
+
* @returns True if the file appears to be an email template
|
|
220
261
|
*/
|
|
221
262
|
export function isEmailTemplate(node: DependencyNode): boolean {
|
|
222
263
|
const { file, exports } = node;
|
|
@@ -254,6 +295,9 @@ export function isEmailTemplate(node: DependencyNode): boolean {
|
|
|
254
295
|
|
|
255
296
|
/**
|
|
256
297
|
* Detect if a file is a parser/transformer
|
|
298
|
+
*
|
|
299
|
+
* @param node The dependency node to check
|
|
300
|
+
* @returns True if the file appears to be a parser
|
|
257
301
|
*/
|
|
258
302
|
export function isParserFile(node: DependencyNode): boolean {
|
|
259
303
|
const { file, exports } = node;
|
|
@@ -290,6 +334,9 @@ export function isParserFile(node: DependencyNode): boolean {
|
|
|
290
334
|
|
|
291
335
|
/**
|
|
292
336
|
* Detect if a file is a session/state management file
|
|
337
|
+
*
|
|
338
|
+
* @param node The dependency node to check
|
|
339
|
+
* @returns True if the file appears to be a session/state file
|
|
293
340
|
*/
|
|
294
341
|
export function isSessionFile(node: DependencyNode): boolean {
|
|
295
342
|
const { file, exports } = node;
|
|
@@ -315,6 +362,9 @@ export function isSessionFile(node: DependencyNode): boolean {
|
|
|
315
362
|
|
|
316
363
|
/**
|
|
317
364
|
* Detect if a file is a configuration or schema file
|
|
365
|
+
*
|
|
366
|
+
* @param node The dependency node to check
|
|
367
|
+
* @returns True if the file appears to be a config file
|
|
318
368
|
*/
|
|
319
369
|
export function isConfigFile(node: DependencyNode): boolean {
|
|
320
370
|
const { file, exports } = node;
|
|
@@ -348,6 +398,9 @@ export function isConfigFile(node: DependencyNode): boolean {
|
|
|
348
398
|
|
|
349
399
|
/**
|
|
350
400
|
* Detect if a file is a Next.js App Router page
|
|
401
|
+
*
|
|
402
|
+
* @param node The dependency node to check
|
|
403
|
+
* @returns True if the file appears to be a Next.js page
|
|
351
404
|
*/
|
|
352
405
|
export function isNextJsPage(node: DependencyNode): boolean {
|
|
353
406
|
const { file, exports } = node;
|
|
@@ -377,6 +430,11 @@ export function isNextJsPage(node: DependencyNode): boolean {
|
|
|
377
430
|
|
|
378
431
|
/**
|
|
379
432
|
* Adjust cohesion score based on file classification
|
|
433
|
+
*
|
|
434
|
+
* @param baseCohesion The initial cohesion score
|
|
435
|
+
* @param classification The file classification
|
|
436
|
+
* @param node Optional dependency node for further context
|
|
437
|
+
* @returns The adjusted cohesion score
|
|
380
438
|
*/
|
|
381
439
|
export function adjustCohesionForClassification(
|
|
382
440
|
baseCohesion: number,
|
|
@@ -384,13 +442,13 @@ export function adjustCohesionForClassification(
|
|
|
384
442
|
node?: DependencyNode
|
|
385
443
|
): number {
|
|
386
444
|
switch (classification) {
|
|
387
|
-
case
|
|
445
|
+
case Classification.BARREL:
|
|
388
446
|
return 1;
|
|
389
|
-
case
|
|
447
|
+
case Classification.TYPE_DEFINITION:
|
|
390
448
|
return 1;
|
|
391
|
-
case
|
|
449
|
+
case Classification.NEXTJS_PAGE:
|
|
392
450
|
return 1;
|
|
393
|
-
case
|
|
451
|
+
case Classification.UTILITY_MODULE: {
|
|
394
452
|
if (
|
|
395
453
|
node &&
|
|
396
454
|
hasRelatedExportNames(
|
|
@@ -401,17 +459,17 @@ export function adjustCohesionForClassification(
|
|
|
401
459
|
}
|
|
402
460
|
return Math.max(0.75, Math.min(1, baseCohesion + 0.35));
|
|
403
461
|
}
|
|
404
|
-
case
|
|
462
|
+
case Classification.SERVICE:
|
|
405
463
|
return Math.max(0.72, Math.min(1, baseCohesion + 0.3));
|
|
406
|
-
case
|
|
464
|
+
case Classification.LAMBDA_HANDLER:
|
|
407
465
|
return Math.max(0.75, Math.min(1, baseCohesion + 0.35));
|
|
408
|
-
case
|
|
466
|
+
case Classification.EMAIL_TEMPLATE:
|
|
409
467
|
return Math.max(0.72, Math.min(1, baseCohesion + 0.3));
|
|
410
|
-
case
|
|
468
|
+
case Classification.PARSER:
|
|
411
469
|
return Math.max(0.7, Math.min(1, baseCohesion + 0.3));
|
|
412
|
-
case
|
|
470
|
+
case Classification.COHESIVE_MODULE:
|
|
413
471
|
return Math.max(baseCohesion, 0.7);
|
|
414
|
-
case
|
|
472
|
+
case Classification.MIXED_CONCERNS:
|
|
415
473
|
return baseCohesion;
|
|
416
474
|
default:
|
|
417
475
|
return Math.min(1, baseCohesion + 0.1);
|
|
@@ -420,6 +478,9 @@ export function adjustCohesionForClassification(
|
|
|
420
478
|
|
|
421
479
|
/**
|
|
422
480
|
* Check if export names suggest related functionality
|
|
481
|
+
*
|
|
482
|
+
* @param exportNames List of exported names
|
|
483
|
+
* @returns True if names appear related
|
|
423
484
|
*/
|
|
424
485
|
function hasRelatedExportNames(exportNames: string[]): boolean {
|
|
425
486
|
if (exportNames.length < 2) return true;
|
|
@@ -470,26 +531,30 @@ function hasRelatedExportNames(exportNames: string[]): boolean {
|
|
|
470
531
|
|
|
471
532
|
/**
|
|
472
533
|
* Adjust fragmentation score based on file classification
|
|
534
|
+
*
|
|
535
|
+
* @param baseFragmentation The initial fragmentation score
|
|
536
|
+
* @param classification The file classification
|
|
537
|
+
* @returns The adjusted fragmentation score
|
|
473
538
|
*/
|
|
474
539
|
export function adjustFragmentationForClassification(
|
|
475
540
|
baseFragmentation: number,
|
|
476
541
|
classification: FileClassification
|
|
477
542
|
): number {
|
|
478
543
|
switch (classification) {
|
|
479
|
-
case
|
|
544
|
+
case Classification.BARREL:
|
|
480
545
|
return 0;
|
|
481
|
-
case
|
|
546
|
+
case Classification.TYPE_DEFINITION:
|
|
482
547
|
return 0;
|
|
483
|
-
case
|
|
484
|
-
case
|
|
485
|
-
case
|
|
486
|
-
case
|
|
487
|
-
case
|
|
488
|
-
case
|
|
548
|
+
case Classification.UTILITY_MODULE:
|
|
549
|
+
case Classification.SERVICE:
|
|
550
|
+
case Classification.LAMBDA_HANDLER:
|
|
551
|
+
case Classification.EMAIL_TEMPLATE:
|
|
552
|
+
case Classification.PARSER:
|
|
553
|
+
case Classification.NEXTJS_PAGE:
|
|
489
554
|
return baseFragmentation * 0.2;
|
|
490
|
-
case
|
|
555
|
+
case Classification.COHESIVE_MODULE:
|
|
491
556
|
return baseFragmentation * 0.3;
|
|
492
|
-
case
|
|
557
|
+
case Classification.MIXED_CONCERNS:
|
|
493
558
|
return baseFragmentation;
|
|
494
559
|
default:
|
|
495
560
|
return baseFragmentation * 0.7;
|
package/src/index.ts
CHANGED
|
@@ -1,27 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
buildDependencyGraph,
|
|
4
|
-
calculateImportDepth,
|
|
5
|
-
getTransitiveDependencies,
|
|
6
|
-
calculateContextBudget,
|
|
7
|
-
detectCircularDependencies,
|
|
8
|
-
calculateCohesion,
|
|
9
|
-
detectModuleClusters,
|
|
10
|
-
classifyFile,
|
|
11
|
-
adjustCohesionForClassification,
|
|
12
|
-
adjustFragmentationForClassification,
|
|
13
|
-
getClassificationRecommendations,
|
|
14
|
-
analyzeIssues,
|
|
15
|
-
} from './analyzer';
|
|
16
|
-
import { calculateContextScore } from './scoring';
|
|
17
|
-
import { getSmartDefaults } from './defaults';
|
|
18
|
-
import { generateSummary } from './summary';
|
|
1
|
+
import { ToolRegistry } from '@aiready/core';
|
|
19
2
|
import { ContextAnalyzerProvider } from './provider';
|
|
20
|
-
import type {
|
|
21
|
-
ContextAnalyzerOptions,
|
|
22
|
-
ContextAnalysisResult,
|
|
23
|
-
ContextSummary,
|
|
24
|
-
} from './types';
|
|
25
3
|
|
|
26
4
|
// Register with global registry
|
|
27
5
|
ToolRegistry.register(ContextAnalyzerProvider);
|
|
@@ -33,222 +11,3 @@ export * from './summary';
|
|
|
33
11
|
export * from './types';
|
|
34
12
|
export * from './semantic-analysis';
|
|
35
13
|
export { ContextAnalyzerProvider };
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Analyze AI context window cost for a codebase
|
|
39
|
-
*/
|
|
40
|
-
export async function analyzeContext(
|
|
41
|
-
options: ContextAnalyzerOptions
|
|
42
|
-
): Promise<ContextAnalysisResult[]> {
|
|
43
|
-
const {
|
|
44
|
-
maxDepth = 5,
|
|
45
|
-
maxContextBudget = 10000,
|
|
46
|
-
minCohesion = 0.6,
|
|
47
|
-
maxFragmentation = 0.5,
|
|
48
|
-
focus = 'all',
|
|
49
|
-
includeNodeModules = false,
|
|
50
|
-
...scanOptions
|
|
51
|
-
} = options;
|
|
52
|
-
|
|
53
|
-
const files = await scanFiles({
|
|
54
|
-
...scanOptions,
|
|
55
|
-
exclude:
|
|
56
|
-
includeNodeModules && scanOptions.exclude
|
|
57
|
-
? scanOptions.exclude.filter(
|
|
58
|
-
(pattern) => pattern !== '**/node_modules/**'
|
|
59
|
-
)
|
|
60
|
-
: scanOptions.exclude,
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
const pythonFiles = files.filter((f) => f.toLowerCase().endsWith('.py'));
|
|
64
|
-
const fileContents = await Promise.all(
|
|
65
|
-
files.map(async (file) => ({
|
|
66
|
-
file,
|
|
67
|
-
content: await readFileContent(file),
|
|
68
|
-
}))
|
|
69
|
-
);
|
|
70
|
-
|
|
71
|
-
const graph = buildDependencyGraph(
|
|
72
|
-
fileContents.filter((f) => !f.file.toLowerCase().endsWith('.py'))
|
|
73
|
-
);
|
|
74
|
-
|
|
75
|
-
let pythonResults: ContextAnalysisResult[] = [];
|
|
76
|
-
if (pythonFiles.length > 0) {
|
|
77
|
-
const { analyzePythonContext } = await import('./analyzers/python-context');
|
|
78
|
-
const pythonMetrics = await analyzePythonContext(
|
|
79
|
-
pythonFiles,
|
|
80
|
-
scanOptions.rootDir || options.rootDir || '.'
|
|
81
|
-
);
|
|
82
|
-
|
|
83
|
-
pythonResults = pythonMetrics.map((metric) => {
|
|
84
|
-
const { severity, issues, recommendations, potentialSavings } =
|
|
85
|
-
analyzeIssues({
|
|
86
|
-
file: metric.file,
|
|
87
|
-
importDepth: metric.importDepth,
|
|
88
|
-
contextBudget: metric.contextBudget,
|
|
89
|
-
cohesionScore: metric.cohesion,
|
|
90
|
-
fragmentationScore: 0,
|
|
91
|
-
maxDepth,
|
|
92
|
-
maxContextBudget,
|
|
93
|
-
minCohesion,
|
|
94
|
-
maxFragmentation,
|
|
95
|
-
circularDeps: metric.metrics.circularDependencies.map((cycle) =>
|
|
96
|
-
cycle.split(' → ')
|
|
97
|
-
),
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
return {
|
|
101
|
-
file: metric.file,
|
|
102
|
-
tokenCost: Math.floor(
|
|
103
|
-
metric.contextBudget / (1 + metric.imports.length || 1)
|
|
104
|
-
),
|
|
105
|
-
linesOfCode: metric.metrics.linesOfCode,
|
|
106
|
-
importDepth: metric.importDepth,
|
|
107
|
-
dependencyCount: metric.imports.length,
|
|
108
|
-
dependencyList: metric.imports.map(
|
|
109
|
-
(imp) => imp.resolvedPath || imp.source
|
|
110
|
-
),
|
|
111
|
-
circularDeps: metric.metrics.circularDependencies.map((cycle) =>
|
|
112
|
-
cycle.split(' → ')
|
|
113
|
-
),
|
|
114
|
-
cohesionScore: metric.cohesion,
|
|
115
|
-
domains: ['python'],
|
|
116
|
-
exportCount: metric.exports.length,
|
|
117
|
-
contextBudget: metric.contextBudget,
|
|
118
|
-
fragmentationScore: 0,
|
|
119
|
-
relatedFiles: [],
|
|
120
|
-
fileClassification: 'unknown' as const,
|
|
121
|
-
severity,
|
|
122
|
-
issues,
|
|
123
|
-
recommendations,
|
|
124
|
-
potentialSavings,
|
|
125
|
-
};
|
|
126
|
-
});
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
const circularDeps = detectCircularDependencies(graph);
|
|
130
|
-
const useLogScale = files.length >= 500;
|
|
131
|
-
const clusters = detectModuleClusters(graph, { useLogScale });
|
|
132
|
-
const fragmentationMap = new Map<string, number>();
|
|
133
|
-
for (const cluster of clusters) {
|
|
134
|
-
for (const file of cluster.files) {
|
|
135
|
-
fragmentationMap.set(file, cluster.fragmentationScore);
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
const results: ContextAnalysisResult[] = [];
|
|
140
|
-
|
|
141
|
-
for (const { file } of fileContents) {
|
|
142
|
-
const node = graph.nodes.get(file);
|
|
143
|
-
if (!node) continue;
|
|
144
|
-
|
|
145
|
-
const importDepth =
|
|
146
|
-
focus === 'depth' || focus === 'all'
|
|
147
|
-
? calculateImportDepth(file, graph)
|
|
148
|
-
: 0;
|
|
149
|
-
const dependencyList =
|
|
150
|
-
focus === 'depth' || focus === 'all'
|
|
151
|
-
? getTransitiveDependencies(file, graph)
|
|
152
|
-
: [];
|
|
153
|
-
const contextBudget =
|
|
154
|
-
focus === 'all' ? calculateContextBudget(file, graph) : node.tokenCost;
|
|
155
|
-
const cohesionScore =
|
|
156
|
-
focus === 'cohesion' || focus === 'all'
|
|
157
|
-
? calculateCohesion(node.exports, file, {
|
|
158
|
-
coUsageMatrix: graph.coUsageMatrix,
|
|
159
|
-
})
|
|
160
|
-
: 1;
|
|
161
|
-
|
|
162
|
-
const fragmentationScore = fragmentationMap.get(file) || 0;
|
|
163
|
-
const relatedFiles: string[] = [];
|
|
164
|
-
for (const cluster of clusters) {
|
|
165
|
-
if (cluster.files.includes(file)) {
|
|
166
|
-
relatedFiles.push(...cluster.files.filter((f) => f !== file));
|
|
167
|
-
break;
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
const { issues } = analyzeIssues({
|
|
172
|
-
file,
|
|
173
|
-
importDepth,
|
|
174
|
-
contextBudget,
|
|
175
|
-
cohesionScore,
|
|
176
|
-
fragmentationScore,
|
|
177
|
-
maxDepth,
|
|
178
|
-
maxContextBudget,
|
|
179
|
-
minCohesion,
|
|
180
|
-
maxFragmentation,
|
|
181
|
-
circularDeps,
|
|
182
|
-
});
|
|
183
|
-
|
|
184
|
-
const domains = [
|
|
185
|
-
...new Set(node.exports.map((e) => e.inferredDomain || 'unknown')),
|
|
186
|
-
];
|
|
187
|
-
const fileClassification = classifyFile(node);
|
|
188
|
-
const adjustedCohesionScore = adjustCohesionForClassification(
|
|
189
|
-
cohesionScore,
|
|
190
|
-
fileClassification,
|
|
191
|
-
node
|
|
192
|
-
);
|
|
193
|
-
const adjustedFragmentationScore = adjustFragmentationForClassification(
|
|
194
|
-
fragmentationScore,
|
|
195
|
-
fileClassification
|
|
196
|
-
);
|
|
197
|
-
const classificationRecommendations = getClassificationRecommendations(
|
|
198
|
-
fileClassification,
|
|
199
|
-
file,
|
|
200
|
-
issues
|
|
201
|
-
);
|
|
202
|
-
|
|
203
|
-
const {
|
|
204
|
-
severity: adjustedSeverity,
|
|
205
|
-
issues: adjustedIssues,
|
|
206
|
-
recommendations: finalRecommendations,
|
|
207
|
-
potentialSavings: adjustedSavings,
|
|
208
|
-
} = analyzeIssues({
|
|
209
|
-
file,
|
|
210
|
-
importDepth,
|
|
211
|
-
contextBudget,
|
|
212
|
-
cohesionScore: adjustedCohesionScore,
|
|
213
|
-
fragmentationScore: adjustedFragmentationScore,
|
|
214
|
-
maxDepth,
|
|
215
|
-
maxContextBudget,
|
|
216
|
-
minCohesion,
|
|
217
|
-
maxFragmentation,
|
|
218
|
-
circularDeps,
|
|
219
|
-
});
|
|
220
|
-
|
|
221
|
-
results.push({
|
|
222
|
-
file,
|
|
223
|
-
tokenCost: node.tokenCost,
|
|
224
|
-
linesOfCode: node.linesOfCode,
|
|
225
|
-
importDepth,
|
|
226
|
-
dependencyCount: dependencyList.length,
|
|
227
|
-
dependencyList,
|
|
228
|
-
circularDeps: circularDeps.filter((cycle) => cycle.includes(file)),
|
|
229
|
-
cohesionScore: adjustedCohesionScore,
|
|
230
|
-
domains,
|
|
231
|
-
exportCount: node.exports.length,
|
|
232
|
-
contextBudget,
|
|
233
|
-
fragmentationScore: adjustedFragmentationScore,
|
|
234
|
-
relatedFiles,
|
|
235
|
-
fileClassification,
|
|
236
|
-
severity: adjustedSeverity,
|
|
237
|
-
issues: adjustedIssues,
|
|
238
|
-
recommendations: [
|
|
239
|
-
...finalRecommendations,
|
|
240
|
-
...classificationRecommendations.slice(0, 1),
|
|
241
|
-
],
|
|
242
|
-
potentialSavings: adjustedSavings,
|
|
243
|
-
});
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
const allResults = [...results, ...pythonResults];
|
|
247
|
-
const finalSummary = generateSummary(allResults, options);
|
|
248
|
-
return allResults.sort((a, b) => {
|
|
249
|
-
const severityOrder = { critical: 0, major: 1, minor: 2, info: 3 };
|
|
250
|
-
const severityDiff = severityOrder[a.severity] - severityOrder[b.severity];
|
|
251
|
-
if (severityDiff !== 0) return severityDiff;
|
|
252
|
-
return b.contextBudget - a.contextBudget;
|
|
253
|
-
});
|
|
254
|
-
}
|
package/src/provider.ts
CHANGED
|
@@ -9,7 +9,8 @@ import {
|
|
|
9
9
|
IssueType,
|
|
10
10
|
SpokeOutputSchema,
|
|
11
11
|
} from '@aiready/core';
|
|
12
|
-
import { analyzeContext
|
|
12
|
+
import { analyzeContext } from './analyzer';
|
|
13
|
+
import { generateSummary } from './summary';
|
|
13
14
|
import { calculateContextScore } from './scoring';
|
|
14
15
|
import { ContextAnalyzerOptions, ContextAnalysisResult } from './types';
|
|
15
16
|
|