@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
package/src/index.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { scanFiles, readFileContent } from '@aiready/core';
|
|
2
|
-
import type { ScanOptions } from '@aiready/core';
|
|
3
2
|
import {
|
|
4
3
|
buildDependencyGraph,
|
|
5
4
|
calculateImportDepth,
|
|
@@ -37,11 +36,13 @@ import {
|
|
|
37
36
|
getCoUsageData,
|
|
38
37
|
findConsolidationCandidates,
|
|
39
38
|
} from './semantic-analysis';
|
|
39
|
+
// Reference optional imports used by future heuristics to avoid lint warnings
|
|
40
|
+
void calculateFragmentation;
|
|
40
41
|
|
|
41
|
-
export type {
|
|
42
|
-
ContextAnalyzerOptions,
|
|
43
|
-
ContextAnalysisResult,
|
|
44
|
-
ContextSummary,
|
|
42
|
+
export type {
|
|
43
|
+
ContextAnalyzerOptions,
|
|
44
|
+
ContextAnalysisResult,
|
|
45
|
+
ContextSummary,
|
|
45
46
|
ModuleCluster,
|
|
46
47
|
DomainAssignment,
|
|
47
48
|
DomainSignals,
|
|
@@ -50,10 +51,7 @@ export type {
|
|
|
50
51
|
FileClassification,
|
|
51
52
|
};
|
|
52
53
|
|
|
53
|
-
export {
|
|
54
|
-
classifyFile,
|
|
55
|
-
adjustFragmentationForClassification,
|
|
56
|
-
};
|
|
54
|
+
export { classifyFile, adjustFragmentationForClassification };
|
|
57
55
|
|
|
58
56
|
export {
|
|
59
57
|
buildCoUsageMatrix,
|
|
@@ -152,14 +150,18 @@ export async function analyzeContext(
|
|
|
152
150
|
// Only add node_modules to exclude if includeNodeModules is false
|
|
153
151
|
// The DEFAULT_EXCLUDE already includes node_modules, so this is only needed
|
|
154
152
|
// if user overrides the default exclude list
|
|
155
|
-
exclude:
|
|
156
|
-
|
|
157
|
-
|
|
153
|
+
exclude:
|
|
154
|
+
includeNodeModules && scanOptions.exclude
|
|
155
|
+
? scanOptions.exclude.filter(
|
|
156
|
+
(pattern) => pattern !== '**/node_modules/**'
|
|
157
|
+
)
|
|
158
|
+
: scanOptions.exclude,
|
|
158
159
|
});
|
|
159
160
|
|
|
160
161
|
// Separate files by language
|
|
161
|
-
const pythonFiles = files.filter(f => f.toLowerCase().endsWith('.py'));
|
|
162
|
-
const tsJsFiles = files.filter(f => !f.toLowerCase().endsWith('.py'));
|
|
162
|
+
const pythonFiles = files.filter((f) => f.toLowerCase().endsWith('.py'));
|
|
163
|
+
const tsJsFiles = files.filter((f) => !f.toLowerCase().endsWith('.py'));
|
|
164
|
+
void tsJsFiles;
|
|
163
165
|
|
|
164
166
|
// Read all file contents
|
|
165
167
|
const fileContents = await Promise.all(
|
|
@@ -170,37 +172,51 @@ export async function analyzeContext(
|
|
|
170
172
|
);
|
|
171
173
|
|
|
172
174
|
// Build dependency graph (TS/JS)
|
|
173
|
-
const graph = buildDependencyGraph(
|
|
175
|
+
const graph = buildDependencyGraph(
|
|
176
|
+
fileContents.filter((f) => !f.file.toLowerCase().endsWith('.py'))
|
|
177
|
+
);
|
|
174
178
|
|
|
175
179
|
// Analyze Python files separately (if any)
|
|
176
180
|
let pythonResults: ContextAnalysisResult[] = [];
|
|
177
181
|
if (pythonFiles.length > 0) {
|
|
178
182
|
const { analyzePythonContext } = await import('./analyzers/python-context');
|
|
179
|
-
const pythonMetrics = await analyzePythonContext(
|
|
180
|
-
|
|
183
|
+
const pythonMetrics = await analyzePythonContext(
|
|
184
|
+
pythonFiles,
|
|
185
|
+
scanOptions.rootDir || options.rootDir || '.'
|
|
186
|
+
);
|
|
187
|
+
|
|
181
188
|
// Convert Python metrics to ContextAnalysisResult format
|
|
182
|
-
pythonResults = pythonMetrics.map(metric => {
|
|
183
|
-
const { severity, issues, recommendations, potentialSavings } =
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
189
|
+
pythonResults = pythonMetrics.map((metric) => {
|
|
190
|
+
const { severity, issues, recommendations, potentialSavings } =
|
|
191
|
+
analyzeIssues({
|
|
192
|
+
file: metric.file,
|
|
193
|
+
importDepth: metric.importDepth,
|
|
194
|
+
contextBudget: metric.contextBudget,
|
|
195
|
+
cohesionScore: metric.cohesion,
|
|
196
|
+
fragmentationScore: 0, // Python analyzer doesn't calculate fragmentation yet
|
|
197
|
+
maxDepth,
|
|
198
|
+
maxContextBudget,
|
|
199
|
+
minCohesion,
|
|
200
|
+
maxFragmentation,
|
|
201
|
+
circularDeps: metric.metrics.circularDependencies.map((cycle) =>
|
|
202
|
+
cycle.split(' → ')
|
|
203
|
+
),
|
|
204
|
+
});
|
|
195
205
|
|
|
196
206
|
return {
|
|
197
207
|
file: metric.file,
|
|
198
|
-
tokenCost: Math.floor(
|
|
208
|
+
tokenCost: Math.floor(
|
|
209
|
+
metric.contextBudget / (1 + metric.imports.length || 1)
|
|
210
|
+
), // Estimate
|
|
199
211
|
linesOfCode: metric.metrics.linesOfCode,
|
|
200
212
|
importDepth: metric.importDepth,
|
|
201
213
|
dependencyCount: metric.imports.length,
|
|
202
|
-
dependencyList: metric.imports.map(
|
|
203
|
-
|
|
214
|
+
dependencyList: metric.imports.map(
|
|
215
|
+
(imp) => imp.resolvedPath || imp.source
|
|
216
|
+
),
|
|
217
|
+
circularDeps: metric.metrics.circularDependencies.map((cycle) =>
|
|
218
|
+
cycle.split(' → ')
|
|
219
|
+
),
|
|
204
220
|
cohesionScore: metric.cohesion,
|
|
205
221
|
domains: ['python'], // Generic for now
|
|
206
222
|
exportCount: metric.exports.length,
|
|
@@ -253,7 +269,9 @@ export async function analyzeContext(
|
|
|
253
269
|
|
|
254
270
|
const cohesionScore =
|
|
255
271
|
focus === 'cohesion' || focus === 'all'
|
|
256
|
-
? calculateCohesion(node.exports, file, {
|
|
272
|
+
? calculateCohesion(node.exports, file, {
|
|
273
|
+
coUsageMatrix: graph.coUsageMatrix,
|
|
274
|
+
})
|
|
257
275
|
: 1;
|
|
258
276
|
|
|
259
277
|
const fragmentationScore = fragmentationMap.get(file) || 0;
|
|
@@ -281,6 +299,11 @@ export async function analyzeContext(
|
|
|
281
299
|
maxFragmentation,
|
|
282
300
|
circularDeps,
|
|
283
301
|
});
|
|
302
|
+
// Some returned fields are not needed for the Python mapping here
|
|
303
|
+
void severity;
|
|
304
|
+
void issues;
|
|
305
|
+
void recommendations;
|
|
306
|
+
void potentialSavings;
|
|
284
307
|
|
|
285
308
|
// Get domains from exports
|
|
286
309
|
const domains = [
|
|
@@ -289,14 +312,14 @@ export async function analyzeContext(
|
|
|
289
312
|
|
|
290
313
|
// Classify the file to help distinguish real issues from false positives
|
|
291
314
|
const fileClassification = classifyFile(node, cohesionScore, domains);
|
|
292
|
-
|
|
315
|
+
|
|
293
316
|
// Adjust cohesion based on classification (utility/service/handler files get boosted)
|
|
294
317
|
const adjustedCohesionScore = adjustCohesionForClassification(
|
|
295
318
|
cohesionScore,
|
|
296
319
|
fileClassification,
|
|
297
320
|
node
|
|
298
321
|
);
|
|
299
|
-
|
|
322
|
+
|
|
300
323
|
// Adjust fragmentation based on classification
|
|
301
324
|
const adjustedFragmentationScore = adjustFragmentationForClassification(
|
|
302
325
|
fragmentationScore,
|
|
@@ -346,7 +369,10 @@ export async function analyzeContext(
|
|
|
346
369
|
fileClassification,
|
|
347
370
|
severity: adjustedSeverity,
|
|
348
371
|
issues: adjustedIssues,
|
|
349
|
-
recommendations: [
|
|
372
|
+
recommendations: [
|
|
373
|
+
...finalRecommendations,
|
|
374
|
+
...classificationRecommendations.slice(0, 1),
|
|
375
|
+
],
|
|
350
376
|
potentialSavings: adjustedSavings,
|
|
351
377
|
});
|
|
352
378
|
}
|
|
@@ -457,7 +483,10 @@ export function generateSummary(
|
|
|
457
483
|
let importPairs = 0;
|
|
458
484
|
for (let i = 0; i < files.length; i++) {
|
|
459
485
|
for (let j = i + 1; j < files.length; j++) {
|
|
460
|
-
importSimTotal += jaccard(
|
|
486
|
+
importSimTotal += jaccard(
|
|
487
|
+
files[i].dependencyList || [],
|
|
488
|
+
files[j].dependencyList || []
|
|
489
|
+
);
|
|
461
490
|
importPairs++;
|
|
462
491
|
}
|
|
463
492
|
}
|
|
@@ -495,7 +524,9 @@ export function generateSummary(
|
|
|
495
524
|
.sort((a, b) => a.score - b.score)
|
|
496
525
|
.slice(0, 10);
|
|
497
526
|
|
|
498
|
-
const criticalIssues = results.filter(
|
|
527
|
+
const criticalIssues = results.filter(
|
|
528
|
+
(r) => r.severity === 'critical'
|
|
529
|
+
).length;
|
|
499
530
|
const majorIssues = results.filter((r) => r.severity === 'major').length;
|
|
500
531
|
const minorIssues = results.filter((r) => r.severity === 'minor').length;
|
|
501
532
|
|
|
@@ -574,10 +605,10 @@ function analyzeIssues(params: {
|
|
|
574
605
|
// Check circular dependencies (CRITICAL)
|
|
575
606
|
if (circularDeps.length > 0) {
|
|
576
607
|
severity = 'critical';
|
|
577
|
-
issues.push(
|
|
578
|
-
|
|
608
|
+
issues.push(`Part of ${circularDeps.length} circular dependency chain(s)`);
|
|
609
|
+
recommendations.push(
|
|
610
|
+
'Break circular dependencies by extracting interfaces or using dependency injection'
|
|
579
611
|
);
|
|
580
|
-
recommendations.push('Break circular dependencies by extracting interfaces or using dependency injection');
|
|
581
612
|
potentialSavings += contextBudget * 0.2; // Estimate 20% savings
|
|
582
613
|
}
|
|
583
614
|
|
|
@@ -589,7 +620,9 @@ function analyzeIssues(params: {
|
|
|
589
620
|
potentialSavings += contextBudget * 0.3; // Estimate 30% savings
|
|
590
621
|
} else if (importDepth > maxDepth) {
|
|
591
622
|
severity = severity === 'critical' ? 'critical' : 'major';
|
|
592
|
-
issues.push(
|
|
623
|
+
issues.push(
|
|
624
|
+
`Import depth ${importDepth} exceeds recommended maximum ${maxDepth}`
|
|
625
|
+
);
|
|
593
626
|
recommendations.push('Consider reducing dependency depth');
|
|
594
627
|
potentialSavings += contextBudget * 0.15;
|
|
595
628
|
}
|
|
@@ -597,12 +630,19 @@ function analyzeIssues(params: {
|
|
|
597
630
|
// Check context budget
|
|
598
631
|
if (contextBudget > maxContextBudget * 1.5) {
|
|
599
632
|
severity = severity === 'critical' ? 'critical' : 'critical';
|
|
600
|
-
issues.push(
|
|
601
|
-
|
|
633
|
+
issues.push(
|
|
634
|
+
`Context budget ${contextBudget.toLocaleString()} tokens is 50% over limit`
|
|
635
|
+
);
|
|
636
|
+
recommendations.push(
|
|
637
|
+
'Split into smaller modules or reduce dependency tree'
|
|
638
|
+
);
|
|
602
639
|
potentialSavings += contextBudget * 0.4; // Significant savings possible
|
|
603
640
|
} else if (contextBudget > maxContextBudget) {
|
|
604
|
-
severity =
|
|
605
|
-
|
|
641
|
+
severity =
|
|
642
|
+
severity === 'critical' || severity === 'major' ? severity : 'major';
|
|
643
|
+
issues.push(
|
|
644
|
+
`Context budget ${contextBudget.toLocaleString()} exceeds ${maxContextBudget.toLocaleString()}`
|
|
645
|
+
);
|
|
606
646
|
recommendations.push('Reduce file size or dependencies');
|
|
607
647
|
potentialSavings += contextBudget * 0.2;
|
|
608
648
|
}
|
|
@@ -610,11 +650,16 @@ function analyzeIssues(params: {
|
|
|
610
650
|
// Check cohesion
|
|
611
651
|
if (cohesionScore < minCohesion * 0.5) {
|
|
612
652
|
severity = severity === 'critical' ? 'critical' : 'major';
|
|
613
|
-
issues.push(
|
|
614
|
-
|
|
653
|
+
issues.push(
|
|
654
|
+
`Very low cohesion (${(cohesionScore * 100).toFixed(0)}%) - mixed concerns`
|
|
655
|
+
);
|
|
656
|
+
recommendations.push(
|
|
657
|
+
'Split file by domain - separate unrelated functionality'
|
|
658
|
+
);
|
|
615
659
|
potentialSavings += contextBudget * 0.25;
|
|
616
660
|
} else if (cohesionScore < minCohesion) {
|
|
617
|
-
severity =
|
|
661
|
+
severity =
|
|
662
|
+
severity === 'critical' || severity === 'major' ? severity : 'minor';
|
|
618
663
|
issues.push(`Low cohesion (${(cohesionScore * 100).toFixed(0)}%)`);
|
|
619
664
|
recommendations.push('Consider grouping related exports together');
|
|
620
665
|
potentialSavings += contextBudget * 0.1;
|
|
@@ -622,8 +667,11 @@ function analyzeIssues(params: {
|
|
|
622
667
|
|
|
623
668
|
// Check fragmentation
|
|
624
669
|
if (fragmentationScore > maxFragmentation) {
|
|
625
|
-
severity =
|
|
626
|
-
|
|
670
|
+
severity =
|
|
671
|
+
severity === 'critical' || severity === 'major' ? severity : 'minor';
|
|
672
|
+
issues.push(
|
|
673
|
+
`High fragmentation (${(fragmentationScore * 100).toFixed(0)}%) - scattered implementation`
|
|
674
|
+
);
|
|
627
675
|
recommendations.push('Consolidate with related files in same domain');
|
|
628
676
|
potentialSavings += contextBudget * 0.3;
|
|
629
677
|
}
|
|
@@ -636,13 +684,20 @@ function analyzeIssues(params: {
|
|
|
636
684
|
// Detect build artifacts and downgrade severity to reduce noise
|
|
637
685
|
if (isBuildArtifact(file)) {
|
|
638
686
|
issues.push('Detected build artifact (bundled/output file)');
|
|
639
|
-
recommendations.push(
|
|
687
|
+
recommendations.push(
|
|
688
|
+
'Exclude build outputs (e.g., cdk.out, dist, build, .next) from analysis'
|
|
689
|
+
);
|
|
640
690
|
severity = downgradeSeverity(severity);
|
|
641
691
|
// Build artifacts do not represent actionable savings
|
|
642
692
|
potentialSavings = 0;
|
|
643
693
|
}
|
|
644
694
|
|
|
645
|
-
return {
|
|
695
|
+
return {
|
|
696
|
+
severity,
|
|
697
|
+
issues,
|
|
698
|
+
recommendations,
|
|
699
|
+
potentialSavings: Math.floor(potentialSavings),
|
|
700
|
+
};
|
|
646
701
|
}
|
|
647
702
|
|
|
648
703
|
export { getSmartDefaults };
|
|
@@ -664,7 +719,9 @@ function isBuildArtifact(filePath: string): boolean {
|
|
|
664
719
|
);
|
|
665
720
|
}
|
|
666
721
|
|
|
667
|
-
function downgradeSeverity(
|
|
722
|
+
function downgradeSeverity(
|
|
723
|
+
s: ContextAnalysisResult['severity']
|
|
724
|
+
): ContextAnalysisResult['severity'] {
|
|
668
725
|
switch (s) {
|
|
669
726
|
case 'critical':
|
|
670
727
|
return 'minor';
|
package/src/scoring.ts
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
import {
|
|
2
|
-
calculateMonthlyCost,
|
|
1
|
+
import {
|
|
2
|
+
calculateMonthlyCost,
|
|
3
3
|
calculateProductivityImpact,
|
|
4
4
|
DEFAULT_COST_CONFIG,
|
|
5
|
-
type CostConfig
|
|
5
|
+
type CostConfig,
|
|
6
6
|
} from '@aiready/core';
|
|
7
7
|
import type { ToolScoringOutput } from '@aiready/core';
|
|
8
8
|
import type { ContextSummary } from './types';
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* Calculate AI Readiness Score for context efficiency (0-100)
|
|
12
|
-
*
|
|
12
|
+
*
|
|
13
13
|
* Based on:
|
|
14
14
|
* - Average context budget (tokens needed to understand files)
|
|
15
15
|
* - Import depth (dependency chain length)
|
|
16
16
|
* - Fragmentation score (code organization)
|
|
17
17
|
* - Critical/major issues
|
|
18
|
-
*
|
|
18
|
+
*
|
|
19
19
|
* Includes business value metrics:
|
|
20
20
|
* - Estimated monthly cost of context waste
|
|
21
21
|
* - Estimated developer hours to fix
|
|
@@ -33,66 +33,70 @@ export function calculateContextScore(
|
|
|
33
33
|
criticalIssues,
|
|
34
34
|
majorIssues,
|
|
35
35
|
} = summary;
|
|
36
|
-
|
|
36
|
+
|
|
37
37
|
// Context budget scoring (40% weight in final score)
|
|
38
38
|
// Ideal: <5000 tokens avg = 100
|
|
39
39
|
// Acceptable: 5000-10000 = 90-70
|
|
40
40
|
// High: 10000-20000 = 70-40
|
|
41
41
|
// Critical: >20000 = <40
|
|
42
|
-
const budgetScore =
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
42
|
+
const budgetScore =
|
|
43
|
+
avgContextBudget < 5000
|
|
44
|
+
? 100
|
|
45
|
+
: Math.max(0, 100 - (avgContextBudget - 5000) / 150);
|
|
46
|
+
|
|
46
47
|
// Import depth scoring (30% weight)
|
|
47
48
|
// Ideal: <5 avg = 100
|
|
48
49
|
// Acceptable: 5-8 = 80-60
|
|
49
50
|
// Deep: >8 = <60
|
|
50
|
-
const depthScore =
|
|
51
|
-
? 100
|
|
52
|
-
|
|
53
|
-
|
|
51
|
+
const depthScore =
|
|
52
|
+
avgImportDepth < 5 ? 100 : Math.max(0, 100 - (avgImportDepth - 5) * 10);
|
|
53
|
+
|
|
54
54
|
// Fragmentation scoring (30% weight)
|
|
55
55
|
// Well-organized: <0.3 = 100
|
|
56
56
|
// Moderate: 0.3-0.5 = 80-60
|
|
57
57
|
// Fragmented: >0.5 = <60
|
|
58
|
-
const fragmentationScore =
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
58
|
+
const fragmentationScore =
|
|
59
|
+
avgFragmentation < 0.3
|
|
60
|
+
? 100
|
|
61
|
+
: Math.max(0, 100 - (avgFragmentation - 0.3) * 200);
|
|
62
|
+
|
|
62
63
|
// Issue penalties
|
|
63
64
|
const criticalPenalty = criticalIssues * 10;
|
|
64
65
|
const majorPenalty = majorIssues * 3;
|
|
65
|
-
|
|
66
|
+
|
|
66
67
|
// Max budget penalty (if any single file is extreme)
|
|
67
|
-
const maxBudgetPenalty =
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
68
|
+
const maxBudgetPenalty =
|
|
69
|
+
maxContextBudget > 15000
|
|
70
|
+
? Math.min(20, (maxContextBudget - 15000) / 500)
|
|
71
|
+
: 0;
|
|
72
|
+
|
|
71
73
|
// Weighted average of subscores
|
|
72
|
-
const rawScore =
|
|
73
|
-
|
|
74
|
-
|
|
74
|
+
const rawScore =
|
|
75
|
+
budgetScore * 0.4 + depthScore * 0.3 + fragmentationScore * 0.3;
|
|
76
|
+
const finalScore =
|
|
77
|
+
rawScore - criticalPenalty - majorPenalty - maxBudgetPenalty;
|
|
78
|
+
|
|
75
79
|
const score = Math.max(0, Math.min(100, Math.round(finalScore)));
|
|
76
|
-
|
|
80
|
+
|
|
77
81
|
// Build factors array
|
|
78
82
|
const factors = [
|
|
79
83
|
{
|
|
80
84
|
name: 'Context Budget',
|
|
81
|
-
impact: Math.round(
|
|
85
|
+
impact: Math.round(budgetScore * 0.4 - 40),
|
|
82
86
|
description: `Avg ${Math.round(avgContextBudget)} tokens per file ${avgContextBudget < 5000 ? '(excellent)' : avgContextBudget < 10000 ? '(acceptable)' : '(high)'}`,
|
|
83
87
|
},
|
|
84
88
|
{
|
|
85
89
|
name: 'Import Depth',
|
|
86
|
-
impact: Math.round(
|
|
90
|
+
impact: Math.round(depthScore * 0.3 - 30),
|
|
87
91
|
description: `Avg ${avgImportDepth.toFixed(1)} levels ${avgImportDepth < 5 ? '(excellent)' : avgImportDepth < 8 ? '(acceptable)' : '(deep)'}`,
|
|
88
92
|
},
|
|
89
93
|
{
|
|
90
94
|
name: 'Fragmentation',
|
|
91
|
-
impact: Math.round(
|
|
95
|
+
impact: Math.round(fragmentationScore * 0.3 - 30),
|
|
92
96
|
description: `${(avgFragmentation * 100).toFixed(0)}% fragmentation ${avgFragmentation < 0.3 ? '(well-organized)' : avgFragmentation < 0.5 ? '(moderate)' : '(high)'}`,
|
|
93
97
|
},
|
|
94
98
|
];
|
|
95
|
-
|
|
99
|
+
|
|
96
100
|
if (criticalIssues > 0) {
|
|
97
101
|
factors.push({
|
|
98
102
|
name: 'Critical Issues',
|
|
@@ -100,7 +104,7 @@ export function calculateContextScore(
|
|
|
100
104
|
description: `${criticalIssues} critical context issue${criticalIssues > 1 ? 's' : ''}`,
|
|
101
105
|
});
|
|
102
106
|
}
|
|
103
|
-
|
|
107
|
+
|
|
104
108
|
if (majorIssues > 0) {
|
|
105
109
|
factors.push({
|
|
106
110
|
name: 'Major Issues',
|
|
@@ -108,7 +112,7 @@ export function calculateContextScore(
|
|
|
108
112
|
description: `${majorIssues} major context issue${majorIssues > 1 ? 's' : ''}`,
|
|
109
113
|
});
|
|
110
114
|
}
|
|
111
|
-
|
|
115
|
+
|
|
112
116
|
if (maxBudgetPenalty > 0) {
|
|
113
117
|
factors.push({
|
|
114
118
|
name: 'Extreme File Detected',
|
|
@@ -116,19 +120,22 @@ export function calculateContextScore(
|
|
|
116
120
|
description: `One file requires ${Math.round(maxContextBudget)} tokens (very high)`,
|
|
117
121
|
});
|
|
118
122
|
}
|
|
119
|
-
|
|
123
|
+
|
|
120
124
|
// Generate recommendations
|
|
121
125
|
const recommendations: ToolScoringOutput['recommendations'] = [];
|
|
122
|
-
|
|
126
|
+
|
|
123
127
|
if (avgContextBudget > 10000) {
|
|
124
|
-
const estimatedImpact = Math.min(
|
|
128
|
+
const estimatedImpact = Math.min(
|
|
129
|
+
15,
|
|
130
|
+
Math.round((avgContextBudget - 10000) / 1000)
|
|
131
|
+
);
|
|
125
132
|
recommendations.push({
|
|
126
133
|
action: 'Reduce file dependencies to lower context requirements',
|
|
127
134
|
estimatedImpact,
|
|
128
135
|
priority: 'high',
|
|
129
136
|
});
|
|
130
137
|
}
|
|
131
|
-
|
|
138
|
+
|
|
132
139
|
if (avgImportDepth > 8) {
|
|
133
140
|
const estimatedImpact = Math.min(10, Math.round((avgImportDepth - 8) * 2));
|
|
134
141
|
recommendations.push({
|
|
@@ -137,16 +144,19 @@ export function calculateContextScore(
|
|
|
137
144
|
priority: avgImportDepth > 10 ? 'high' : 'medium',
|
|
138
145
|
});
|
|
139
146
|
}
|
|
140
|
-
|
|
147
|
+
|
|
141
148
|
if (avgFragmentation > 0.5) {
|
|
142
|
-
const estimatedImpact = Math.min(
|
|
149
|
+
const estimatedImpact = Math.min(
|
|
150
|
+
12,
|
|
151
|
+
Math.round((avgFragmentation - 0.5) * 40)
|
|
152
|
+
);
|
|
143
153
|
recommendations.push({
|
|
144
154
|
action: 'Consolidate related code into cohesive modules',
|
|
145
155
|
estimatedImpact,
|
|
146
156
|
priority: 'medium',
|
|
147
157
|
});
|
|
148
158
|
}
|
|
149
|
-
|
|
159
|
+
|
|
150
160
|
if (maxContextBudget > 20000) {
|
|
151
161
|
recommendations.push({
|
|
152
162
|
action: `Split large file (${Math.round(maxContextBudget)} tokens) into smaller modules`,
|
|
@@ -154,20 +164,20 @@ export function calculateContextScore(
|
|
|
154
164
|
priority: 'high',
|
|
155
165
|
});
|
|
156
166
|
}
|
|
157
|
-
|
|
167
|
+
|
|
158
168
|
// Calculate business value metrics
|
|
159
169
|
const cfg = { ...DEFAULT_COST_CONFIG, ...costConfig };
|
|
160
170
|
// Total context budget across all files
|
|
161
171
|
const totalContextBudget = avgContextBudget * summary.totalFiles;
|
|
162
172
|
const estimatedMonthlyCost = calculateMonthlyCost(totalContextBudget, cfg);
|
|
163
|
-
|
|
173
|
+
|
|
164
174
|
// Convert issues to format for productivity calculation
|
|
165
175
|
const issues = [
|
|
166
176
|
...Array(criticalIssues).fill({ severity: 'critical' as const }),
|
|
167
177
|
...Array(majorIssues).fill({ severity: 'major' as const }),
|
|
168
178
|
];
|
|
169
179
|
const productivityImpact = calculateProductivityImpact(issues);
|
|
170
|
-
|
|
180
|
+
|
|
171
181
|
return {
|
|
172
182
|
toolName: 'context-analyzer',
|
|
173
183
|
score,
|