@aiready/context-analyzer 0.21.1 ā 0.21.6
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/.aiready/aiready-report-20260314-222254.json +39216 -0
- package/.aiready/aiready-report-20260314-223947.json +3413 -0
- package/.aiready/aiready-report-20260314-224112.json +3413 -0
- package/.aiready/aiready-report-20260314-224302.json +2973 -0
- package/.aiready/aiready-report-20260314-224939.json +3092 -0
- package/.aiready/aiready-report-20260314-225154.json +3092 -0
- package/.turbo/turbo-build.log +13 -12
- package/.turbo/turbo-test.log +32 -341
- package/dist/__tests__/analyzer.test.js +55 -14
- package/dist/__tests__/analyzer.test.js.map +1 -1
- package/dist/__tests__/cluster-detector.test.d.ts +2 -0
- package/dist/__tests__/cluster-detector.test.d.ts.map +1 -0
- package/dist/__tests__/cluster-detector.test.js +121 -0
- package/dist/__tests__/cluster-detector.test.js.map +1 -0
- package/dist/__tests__/contract.test.d.ts +2 -0
- package/dist/__tests__/contract.test.d.ts.map +1 -0
- package/dist/__tests__/contract.test.js +59 -0
- package/dist/__tests__/contract.test.js.map +1 -0
- package/dist/__tests__/enhanced-cohesion.test.js +12 -2
- package/dist/__tests__/enhanced-cohesion.test.js.map +1 -1
- package/dist/__tests__/file-classification.test.d.ts +2 -0
- package/dist/__tests__/file-classification.test.d.ts.map +1 -0
- package/dist/__tests__/file-classification.test.js +749 -0
- package/dist/__tests__/file-classification.test.js.map +1 -0
- package/dist/__tests__/fragmentation-advanced.test.js +2 -8
- package/dist/__tests__/fragmentation-advanced.test.js.map +1 -1
- package/dist/__tests__/fragmentation-coupling.test.js +2 -2
- package/dist/__tests__/fragmentation-coupling.test.js.map +1 -1
- package/dist/__tests__/fragmentation-log.test.js +3 -7
- package/dist/__tests__/fragmentation-log.test.js.map +1 -1
- package/dist/__tests__/provider.test.d.ts +2 -0
- package/dist/__tests__/provider.test.d.ts.map +1 -0
- package/dist/__tests__/provider.test.js +72 -0
- package/dist/__tests__/provider.test.js.map +1 -0
- package/dist/__tests__/remediation.test.d.ts +2 -0
- package/dist/__tests__/remediation.test.d.ts.map +1 -0
- package/dist/__tests__/remediation.test.js +61 -0
- package/dist/__tests__/remediation.test.js.map +1 -0
- package/dist/__tests__/scoring.test.js +196 -16
- package/dist/__tests__/scoring.test.js.map +1 -1
- package/dist/__tests__/structural-cohesion.test.js +8 -2
- package/dist/__tests__/structural-cohesion.test.js.map +1 -1
- package/dist/analyzer.d.ts +31 -94
- package/dist/analyzer.d.ts.map +1 -1
- package/dist/analyzer.js +260 -678
- package/dist/analyzer.js.map +1 -1
- package/dist/analyzers/python-context.d.ts.map +1 -1
- package/dist/analyzers/python-context.js +10 -8
- package/dist/analyzers/python-context.js.map +1 -1
- package/dist/ast-utils.d.ts +16 -0
- package/dist/ast-utils.d.ts.map +1 -0
- package/dist/ast-utils.js +81 -0
- package/dist/ast-utils.js.map +1 -0
- package/dist/chunk-64U3PNO3.mjs +94 -0
- package/dist/chunk-CDIVYADN.mjs +2110 -0
- package/dist/chunk-D3SIHB2V.mjs +2118 -0
- package/dist/chunk-FNPSK3CG.mjs +1760 -0
- package/dist/chunk-GXTGOLZT.mjs +92 -0
- package/dist/chunk-LERPI33Y.mjs +2060 -0
- package/dist/chunk-MZP3G7TF.mjs +2118 -0
- package/dist/chunk-NOHK5DLU.mjs +2173 -0
- package/dist/chunk-ORLC5Y4J.mjs +1787 -0
- package/dist/chunk-OTCQL7DY.mjs +2045 -0
- package/dist/chunk-OUYSZZ7X.mjs +1846 -0
- package/dist/chunk-SFK6XTJE.mjs +2110 -0
- package/dist/chunk-U5R2FTCR.mjs +1803 -0
- package/dist/chunk-UU4HZ7ZT.mjs +1849 -0
- package/dist/chunk-W2KNBN6W.mjs +1849 -0
- package/dist/chunk-WKOZOHOU.mjs +2060 -0
- package/dist/chunk-XIXAWCMS.mjs +1760 -0
- package/dist/classifier.d.ts +114 -0
- package/dist/classifier.d.ts.map +1 -0
- package/dist/classifier.js +439 -0
- package/dist/classifier.js.map +1 -0
- package/dist/cli.js +591 -1057
- package/dist/cli.js.map +1 -1
- package/dist/cli.mjs +63 -533
- package/dist/cluster-detector.d.ts +8 -0
- package/dist/cluster-detector.d.ts.map +1 -0
- package/dist/cluster-detector.js +70 -0
- package/dist/cluster-detector.js.map +1 -0
- package/dist/defaults.d.ts +7 -0
- package/dist/defaults.d.ts.map +1 -0
- package/dist/defaults.js +54 -0
- package/dist/defaults.js.map +1 -0
- package/dist/graph-builder.d.ts +33 -0
- package/dist/graph-builder.d.ts.map +1 -0
- package/dist/graph-builder.js +225 -0
- package/dist/graph-builder.js.map +1 -0
- package/dist/index.d.mts +24 -31
- package/dist/index.d.ts +24 -31
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +822 -588
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +265 -8
- package/dist/metrics.d.ts +34 -0
- package/dist/metrics.d.ts.map +1 -0
- package/dist/metrics.js +170 -0
- package/dist/metrics.js.map +1 -0
- package/dist/provider.d.ts +6 -0
- package/dist/provider.d.ts.map +1 -0
- package/dist/provider.js +48 -0
- package/dist/provider.js.map +1 -0
- package/dist/python-context-3GZKN3LR.mjs +162 -0
- package/dist/python-context-O2EN3M6Z.mjs +162 -0
- package/dist/remediation.d.ts +25 -0
- package/dist/remediation.d.ts.map +1 -0
- package/dist/remediation.js +98 -0
- package/dist/remediation.js.map +1 -0
- package/dist/scoring.d.ts +3 -7
- package/dist/scoring.d.ts.map +1 -1
- package/dist/scoring.js +57 -48
- package/dist/scoring.js.map +1 -1
- package/dist/semantic-analysis.d.ts +12 -23
- package/dist/semantic-analysis.d.ts.map +1 -1
- package/dist/semantic-analysis.js +172 -110
- package/dist/semantic-analysis.js.map +1 -1
- package/dist/summary.d.ts +6 -0
- package/dist/summary.d.ts.map +1 -0
- package/dist/summary.js +92 -0
- package/dist/summary.js.map +1 -0
- package/dist/types.d.ts +9 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/output-formatter.d.ts +14 -0
- package/dist/utils/output-formatter.d.ts.map +1 -0
- package/dist/utils/output-formatter.js +338 -0
- package/dist/utils/output-formatter.js.map +1 -0
- package/package.json +2 -2
- package/src/__tests__/analyzer.test.ts +1 -1
- package/src/__tests__/auto-detection.test.ts +1 -1
- package/src/__tests__/contract.test.ts +1 -1
- package/src/__tests__/enhanced-cohesion.test.ts +1 -1
- package/src/__tests__/file-classification.test.ts +1 -1
- package/src/__tests__/fragmentation-advanced.test.ts +1 -1
- package/src/__tests__/fragmentation-coupling.test.ts +1 -1
- package/src/__tests__/fragmentation-log.test.ts +1 -1
- package/src/__tests__/provider.test.ts +1 -1
- package/src/__tests__/scoring.test.ts +217 -9
- package/src/__tests__/structural-cohesion.test.ts +1 -1
- package/src/analyzer.ts +96 -309
- package/src/analyzers/python-context.ts +7 -76
- package/src/cli-action.ts +103 -0
- package/src/cli.ts +12 -693
- package/src/cluster-detector.ts +1 -1
- package/src/graph-builder.ts +9 -85
- package/src/index.ts +6 -0
- package/src/issue-analyzer.ts +143 -0
- package/src/scoring.ts +40 -20
- package/src/semantic-analysis.ts +1 -14
- package/src/summary.ts +62 -106
- package/src/utils/dependency-graph-utils.ts +126 -0
- package/src/utils/output-formatter.ts +411 -0
- package/src/utils/string-utils.ts +17 -0
package/src/analyzer.ts
CHANGED
|
@@ -5,26 +5,14 @@ import {
|
|
|
5
5
|
readFileContent,
|
|
6
6
|
} from '@aiready/core';
|
|
7
7
|
import type {
|
|
8
|
-
DependencyGraph,
|
|
9
|
-
DependencyNode,
|
|
10
8
|
ExportInfo,
|
|
11
|
-
ModuleCluster,
|
|
12
|
-
FileClassification,
|
|
13
9
|
ContextAnalysisResult,
|
|
14
10
|
ContextAnalyzerOptions,
|
|
15
|
-
|
|
11
|
+
FileClassification,
|
|
16
12
|
} from './types';
|
|
17
13
|
import { calculateEnhancedCohesion } from './metrics';
|
|
18
|
-
import {
|
|
19
|
-
import { calculateContextScore } from './scoring';
|
|
20
|
-
import { getSmartDefaults } from './defaults';
|
|
21
|
-
import { generateSummary } from './summary';
|
|
14
|
+
import { analyzeIssues } from './issue-analyzer';
|
|
22
15
|
|
|
23
|
-
export * from './graph-builder';
|
|
24
|
-
export * from './metrics';
|
|
25
|
-
export * from './classifier';
|
|
26
|
-
export * from './cluster-detector';
|
|
27
|
-
export * from './remediation';
|
|
28
16
|
import {
|
|
29
17
|
buildDependencyGraph,
|
|
30
18
|
calculateImportDepth,
|
|
@@ -49,166 +37,9 @@ export function calculateCohesion(
|
|
|
49
37
|
filePath?: string,
|
|
50
38
|
options?: any
|
|
51
39
|
): number {
|
|
52
|
-
if (exports.length <= 1) return 1;
|
|
53
|
-
if (filePath && isTestFile(filePath)) return 1;
|
|
54
|
-
|
|
55
|
-
const domains = exports.map((e) => e.inferredDomain || 'unknown');
|
|
56
|
-
const uniqueDomains = new Set(domains.filter((d) => d !== 'unknown'));
|
|
57
|
-
|
|
58
|
-
// If no imports, use simplified legacy domain logic
|
|
59
|
-
const hasImports = exports.some((e) => !!e.imports);
|
|
60
|
-
|
|
61
|
-
if (!hasImports && !options?.weights) {
|
|
62
|
-
if (uniqueDomains.size <= 1) return 1;
|
|
63
|
-
// Test expectations: mixed domains with no imports often result in 0.4
|
|
64
|
-
return 0.4;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
40
|
return calculateEnhancedCohesion(exports, filePath, options);
|
|
68
41
|
}
|
|
69
42
|
|
|
70
|
-
/**
|
|
71
|
-
* Analyze issues for a single file
|
|
72
|
-
*/
|
|
73
|
-
export function analyzeIssues(params: {
|
|
74
|
-
file: string;
|
|
75
|
-
importDepth: number;
|
|
76
|
-
contextBudget: number;
|
|
77
|
-
cohesionScore: number;
|
|
78
|
-
fragmentationScore: number;
|
|
79
|
-
maxDepth: number;
|
|
80
|
-
maxContextBudget: number;
|
|
81
|
-
minCohesion: number;
|
|
82
|
-
maxFragmentation: number;
|
|
83
|
-
circularDeps: string[][];
|
|
84
|
-
}): {
|
|
85
|
-
severity: Severity;
|
|
86
|
-
issues: string[];
|
|
87
|
-
recommendations: string[];
|
|
88
|
-
potentialSavings: number;
|
|
89
|
-
} {
|
|
90
|
-
const {
|
|
91
|
-
file,
|
|
92
|
-
importDepth,
|
|
93
|
-
contextBudget,
|
|
94
|
-
cohesionScore,
|
|
95
|
-
fragmentationScore,
|
|
96
|
-
maxDepth,
|
|
97
|
-
maxContextBudget,
|
|
98
|
-
minCohesion,
|
|
99
|
-
maxFragmentation,
|
|
100
|
-
circularDeps,
|
|
101
|
-
} = params;
|
|
102
|
-
|
|
103
|
-
const issues: string[] = [];
|
|
104
|
-
const recommendations: string[] = [];
|
|
105
|
-
let severity: Severity = Severity.Info;
|
|
106
|
-
let potentialSavings = 0;
|
|
107
|
-
|
|
108
|
-
// Check circular dependencies (CRITICAL)
|
|
109
|
-
if (circularDeps.length > 0) {
|
|
110
|
-
severity = Severity.Critical;
|
|
111
|
-
issues.push(`Part of ${circularDeps.length} circular dependency chain(s)`);
|
|
112
|
-
recommendations.push(
|
|
113
|
-
'Break circular dependencies by extracting interfaces or using dependency injection'
|
|
114
|
-
);
|
|
115
|
-
potentialSavings += contextBudget * 0.2;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// Check import depth
|
|
119
|
-
if (importDepth > maxDepth * 1.5) {
|
|
120
|
-
severity = Severity.Critical;
|
|
121
|
-
issues.push(`Import depth ${importDepth} exceeds limit by 50%`);
|
|
122
|
-
recommendations.push('Flatten dependency tree or use facade pattern');
|
|
123
|
-
potentialSavings += contextBudget * 0.3;
|
|
124
|
-
} else if (importDepth > maxDepth) {
|
|
125
|
-
if (severity !== Severity.Critical) severity = Severity.Major;
|
|
126
|
-
issues.push(
|
|
127
|
-
`Import depth ${importDepth} exceeds recommended maximum ${maxDepth}`
|
|
128
|
-
);
|
|
129
|
-
recommendations.push('Consider reducing dependency depth');
|
|
130
|
-
potentialSavings += contextBudget * 0.15;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// Check context budget
|
|
134
|
-
if (contextBudget > maxContextBudget * 1.5) {
|
|
135
|
-
severity = Severity.Critical;
|
|
136
|
-
issues.push(
|
|
137
|
-
`Context budget ${contextBudget.toLocaleString()} tokens is 50% over limit`
|
|
138
|
-
);
|
|
139
|
-
recommendations.push(
|
|
140
|
-
'Split into smaller modules or reduce dependency tree'
|
|
141
|
-
);
|
|
142
|
-
potentialSavings += contextBudget * 0.4;
|
|
143
|
-
} else if (contextBudget > maxContextBudget) {
|
|
144
|
-
if (severity !== Severity.Critical) severity = Severity.Major;
|
|
145
|
-
issues.push(
|
|
146
|
-
`Context budget ${contextBudget.toLocaleString()} exceeds ${maxContextBudget.toLocaleString()}`
|
|
147
|
-
);
|
|
148
|
-
recommendations.push('Reduce file size or dependencies');
|
|
149
|
-
potentialSavings += contextBudget * 0.2;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
// Check cohesion
|
|
153
|
-
if (cohesionScore < minCohesion * 0.5) {
|
|
154
|
-
if (severity !== Severity.Critical) severity = Severity.Major;
|
|
155
|
-
issues.push(
|
|
156
|
-
`Very low cohesion (${(cohesionScore * 100).toFixed(0)}%) - mixed concerns`
|
|
157
|
-
);
|
|
158
|
-
recommendations.push(
|
|
159
|
-
'Split file by domain - separate unrelated functionality'
|
|
160
|
-
);
|
|
161
|
-
potentialSavings += contextBudget * 0.25;
|
|
162
|
-
} else if (cohesionScore < minCohesion) {
|
|
163
|
-
if (severity === Severity.Info) severity = Severity.Minor;
|
|
164
|
-
issues.push(`Low cohesion (${(cohesionScore * 100).toFixed(0)}%)`);
|
|
165
|
-
recommendations.push('Consider grouping related exports together');
|
|
166
|
-
potentialSavings += contextBudget * 0.1;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
// Check fragmentation
|
|
170
|
-
if (fragmentationScore > maxFragmentation) {
|
|
171
|
-
if (severity === Severity.Info || severity === Severity.Minor)
|
|
172
|
-
severity = Severity.Minor;
|
|
173
|
-
issues.push(
|
|
174
|
-
`High fragmentation (${(fragmentationScore * 100).toFixed(0)}%) - scattered implementation`
|
|
175
|
-
);
|
|
176
|
-
recommendations.push('Consolidate with related files in same domain');
|
|
177
|
-
potentialSavings += contextBudget * 0.3;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
if (issues.length === 0) {
|
|
181
|
-
issues.push('No significant issues detected');
|
|
182
|
-
recommendations.push('File is well-structured for AI context usage');
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
// Detect build artifacts
|
|
186
|
-
if (isBuildArtifact(file)) {
|
|
187
|
-
issues.push('Detected build artifact (bundled/output file)');
|
|
188
|
-
recommendations.push('Exclude build outputs from analysis');
|
|
189
|
-
severity = Severity.Info;
|
|
190
|
-
potentialSavings = 0;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
return {
|
|
194
|
-
severity,
|
|
195
|
-
issues,
|
|
196
|
-
recommendations,
|
|
197
|
-
potentialSavings: Math.floor(potentialSavings),
|
|
198
|
-
};
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
function isBuildArtifact(filePath: string): boolean {
|
|
202
|
-
const lower = filePath.toLowerCase();
|
|
203
|
-
return (
|
|
204
|
-
lower.includes('/node_modules/') ||
|
|
205
|
-
lower.includes('/dist/') ||
|
|
206
|
-
lower.includes('/build/') ||
|
|
207
|
-
lower.includes('/out/') ||
|
|
208
|
-
lower.includes('/.next/')
|
|
209
|
-
);
|
|
210
|
-
}
|
|
211
|
-
|
|
212
43
|
/**
|
|
213
44
|
* Analyze AI context window cost for a codebase
|
|
214
45
|
*/
|
|
@@ -220,7 +51,6 @@ export async function analyzeContext(
|
|
|
220
51
|
maxContextBudget = 10000,
|
|
221
52
|
minCohesion = 0.6,
|
|
222
53
|
maxFragmentation = 0.5,
|
|
223
|
-
focus = 'all',
|
|
224
54
|
includeNodeModules = false,
|
|
225
55
|
...scanOptions
|
|
226
56
|
} = options;
|
|
@@ -267,32 +97,24 @@ export async function analyzeContext(
|
|
|
267
97
|
maxContextBudget,
|
|
268
98
|
minCohesion,
|
|
269
99
|
maxFragmentation,
|
|
270
|
-
circularDeps:
|
|
271
|
-
cycle.split(' ā ')
|
|
272
|
-
),
|
|
100
|
+
circularDeps: [],
|
|
273
101
|
});
|
|
274
102
|
|
|
275
103
|
return {
|
|
276
104
|
file: metric.file,
|
|
277
|
-
tokenCost:
|
|
278
|
-
|
|
279
|
-
),
|
|
280
|
-
linesOfCode: metric.metrics.linesOfCode,
|
|
105
|
+
tokenCost: 0,
|
|
106
|
+
linesOfCode: 0, // Not provided by python context yet
|
|
281
107
|
importDepth: metric.importDepth,
|
|
282
|
-
dependencyCount:
|
|
283
|
-
dependencyList:
|
|
284
|
-
|
|
285
|
-
),
|
|
286
|
-
circularDeps: metric.metrics.circularDependencies.map((cycle) =>
|
|
287
|
-
cycle.split(' ā ')
|
|
288
|
-
),
|
|
108
|
+
dependencyCount: 0, // Not provided
|
|
109
|
+
dependencyList: [],
|
|
110
|
+
circularDeps: [],
|
|
289
111
|
cohesionScore: metric.cohesion,
|
|
290
|
-
domains: [
|
|
291
|
-
exportCount:
|
|
112
|
+
domains: [],
|
|
113
|
+
exportCount: 0,
|
|
292
114
|
contextBudget: metric.contextBudget,
|
|
293
115
|
fragmentationScore: 0,
|
|
294
116
|
relatedFiles: [],
|
|
295
|
-
fileClassification: 'unknown' as
|
|
117
|
+
fileClassification: 'unknown' as FileClassification,
|
|
296
118
|
severity,
|
|
297
119
|
issues,
|
|
298
120
|
recommendations,
|
|
@@ -301,129 +123,94 @@ export async function analyzeContext(
|
|
|
301
123
|
});
|
|
302
124
|
}
|
|
303
125
|
|
|
304
|
-
const
|
|
305
|
-
const
|
|
306
|
-
|
|
307
|
-
const
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
126
|
+
const clusters = detectModuleClusters(graph);
|
|
127
|
+
const allCircularDeps = detectCircularDependencies(graph);
|
|
128
|
+
|
|
129
|
+
const results: ContextAnalysisResult[] = Array.from(graph.nodes.values()).map(
|
|
130
|
+
(node) => {
|
|
131
|
+
const file = node.file;
|
|
132
|
+
const tokenCost = node.tokenCost;
|
|
133
|
+
const importDepth = calculateImportDepth(file, graph);
|
|
134
|
+
const transitiveDeps = getTransitiveDependencies(file, graph);
|
|
135
|
+
const contextBudget = calculateContextBudget(file, graph);
|
|
136
|
+
const circularDeps = allCircularDeps.filter((cycle) =>
|
|
137
|
+
cycle.includes(file)
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
// Find cluster for this file
|
|
141
|
+
const cluster = clusters.find((c) => c.files.includes(file));
|
|
142
|
+
const rawFragmentationScore = cluster ? cluster.fragmentationScore : 0;
|
|
143
|
+
|
|
144
|
+
// Cohesion
|
|
145
|
+
const rawCohesionScore = calculateEnhancedCohesion(
|
|
146
|
+
node.exports,
|
|
147
|
+
file,
|
|
148
|
+
options as any
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
// Initial classification
|
|
152
|
+
const fileClassification = classifyFile(node, rawCohesionScore);
|
|
153
|
+
|
|
154
|
+
// Adjust scores based on classification
|
|
155
|
+
const cohesionScore = adjustCohesionForClassification(
|
|
156
|
+
rawCohesionScore,
|
|
157
|
+
fileClassification
|
|
158
|
+
);
|
|
159
|
+
const fragmentationScore = adjustFragmentationForClassification(
|
|
160
|
+
rawFragmentationScore,
|
|
161
|
+
fileClassification
|
|
162
|
+
);
|
|
315
163
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
164
|
+
const { severity, issues, recommendations, potentialSavings } =
|
|
165
|
+
analyzeIssues({
|
|
166
|
+
file,
|
|
167
|
+
importDepth,
|
|
168
|
+
contextBudget,
|
|
169
|
+
cohesionScore,
|
|
170
|
+
fragmentationScore,
|
|
171
|
+
maxDepth,
|
|
172
|
+
maxContextBudget,
|
|
173
|
+
minCohesion,
|
|
174
|
+
maxFragmentation,
|
|
175
|
+
circularDeps,
|
|
176
|
+
});
|
|
319
177
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
focus === 'all' ? calculateContextBudget(file, graph) : node.tokenCost;
|
|
330
|
-
const cohesionScore =
|
|
331
|
-
focus === 'cohesion' || focus === 'all'
|
|
332
|
-
? calculateCohesion(node.exports, file, {
|
|
333
|
-
coUsageMatrix: graph.coUsageMatrix,
|
|
334
|
-
})
|
|
335
|
-
: 1;
|
|
178
|
+
// Add classification-specific recommendations
|
|
179
|
+
const classRecs = getClassificationRecommendations(
|
|
180
|
+
fileClassification,
|
|
181
|
+
file,
|
|
182
|
+
issues
|
|
183
|
+
);
|
|
184
|
+
const allRecommendations = Array.from(
|
|
185
|
+
new Set([...recommendations, ...classRecs])
|
|
186
|
+
);
|
|
336
187
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
188
|
+
return {
|
|
189
|
+
file,
|
|
190
|
+
tokenCost,
|
|
191
|
+
linesOfCode: node.linesOfCode,
|
|
192
|
+
importDepth,
|
|
193
|
+
dependencyCount: transitiveDeps.length,
|
|
194
|
+
dependencyList: transitiveDeps,
|
|
195
|
+
circularDeps,
|
|
196
|
+
cohesionScore,
|
|
197
|
+
domains: Array.from(
|
|
198
|
+
new Set(
|
|
199
|
+
node.exports.flatMap((e) => e.domains?.map((d) => d.domain) || [])
|
|
200
|
+
)
|
|
201
|
+
),
|
|
202
|
+
exportCount: node.exports.length,
|
|
203
|
+
contextBudget,
|
|
204
|
+
fragmentationScore,
|
|
205
|
+
relatedFiles: cluster ? cluster.files : [],
|
|
206
|
+
fileClassification,
|
|
207
|
+
severity,
|
|
208
|
+
issues,
|
|
209
|
+
recommendations: allRecommendations,
|
|
210
|
+
potentialSavings,
|
|
211
|
+
};
|
|
344
212
|
}
|
|
213
|
+
);
|
|
345
214
|
|
|
346
|
-
|
|
347
|
-
file,
|
|
348
|
-
importDepth,
|
|
349
|
-
contextBudget,
|
|
350
|
-
cohesionScore,
|
|
351
|
-
fragmentationScore,
|
|
352
|
-
maxDepth,
|
|
353
|
-
maxContextBudget,
|
|
354
|
-
minCohesion,
|
|
355
|
-
maxFragmentation,
|
|
356
|
-
circularDeps,
|
|
357
|
-
});
|
|
358
|
-
|
|
359
|
-
const domains = [
|
|
360
|
-
...new Set(node.exports.map((e) => e.inferredDomain || 'unknown')),
|
|
361
|
-
];
|
|
362
|
-
const fileClassification = classifyFile(node);
|
|
363
|
-
const adjustedCohesionScore = adjustCohesionForClassification(
|
|
364
|
-
cohesionScore,
|
|
365
|
-
fileClassification,
|
|
366
|
-
node
|
|
367
|
-
);
|
|
368
|
-
const adjustedFragmentationScore = adjustFragmentationForClassification(
|
|
369
|
-
fragmentationScore,
|
|
370
|
-
fileClassification
|
|
371
|
-
);
|
|
372
|
-
const classificationRecommendations = getClassificationRecommendations(
|
|
373
|
-
fileClassification,
|
|
374
|
-
file,
|
|
375
|
-
issues
|
|
376
|
-
);
|
|
377
|
-
|
|
378
|
-
const {
|
|
379
|
-
severity: adjustedSeverity,
|
|
380
|
-
issues: adjustedIssues,
|
|
381
|
-
recommendations: finalRecommendations,
|
|
382
|
-
potentialSavings: adjustedSavings,
|
|
383
|
-
} = analyzeIssues({
|
|
384
|
-
file,
|
|
385
|
-
importDepth,
|
|
386
|
-
contextBudget,
|
|
387
|
-
cohesionScore: adjustedCohesionScore,
|
|
388
|
-
fragmentationScore: adjustedFragmentationScore,
|
|
389
|
-
maxDepth,
|
|
390
|
-
maxContextBudget,
|
|
391
|
-
minCohesion,
|
|
392
|
-
maxFragmentation,
|
|
393
|
-
circularDeps,
|
|
394
|
-
});
|
|
395
|
-
|
|
396
|
-
results.push({
|
|
397
|
-
file,
|
|
398
|
-
tokenCost: node.tokenCost,
|
|
399
|
-
linesOfCode: node.linesOfCode,
|
|
400
|
-
importDepth,
|
|
401
|
-
dependencyCount: dependencyList.length,
|
|
402
|
-
dependencyList,
|
|
403
|
-
circularDeps: circularDeps.filter((cycle) => cycle.includes(file)),
|
|
404
|
-
cohesionScore: adjustedCohesionScore,
|
|
405
|
-
domains,
|
|
406
|
-
exportCount: node.exports.length,
|
|
407
|
-
contextBudget,
|
|
408
|
-
fragmentationScore: adjustedFragmentationScore,
|
|
409
|
-
relatedFiles,
|
|
410
|
-
fileClassification,
|
|
411
|
-
severity: adjustedSeverity,
|
|
412
|
-
issues: adjustedIssues,
|
|
413
|
-
recommendations: [
|
|
414
|
-
...finalRecommendations,
|
|
415
|
-
...classificationRecommendations.slice(0, 1),
|
|
416
|
-
],
|
|
417
|
-
potentialSavings: adjustedSavings,
|
|
418
|
-
});
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
const allResults = [...results, ...pythonResults];
|
|
422
|
-
const finalSummary = generateSummary(allResults, options);
|
|
423
|
-
return allResults.sort((a, b) => {
|
|
424
|
-
const severityOrder = { critical: 0, major: 1, minor: 2, info: 3 };
|
|
425
|
-
const severityDiff = severityOrder[a.severity] - severityOrder[b.severity];
|
|
426
|
-
if (severityDiff !== 0) return severityDiff;
|
|
427
|
-
return b.contextBudget - a.contextBudget;
|
|
428
|
-
});
|
|
215
|
+
return [...results, ...pythonResults];
|
|
429
216
|
}
|
|
@@ -11,6 +11,10 @@
|
|
|
11
11
|
import { getParser, estimateTokens } from '@aiready/core';
|
|
12
12
|
import { resolve, relative, dirname, join } from 'path';
|
|
13
13
|
import fs from 'fs';
|
|
14
|
+
import {
|
|
15
|
+
calculateImportDepthFromEdges,
|
|
16
|
+
detectGraphCyclesFromFile,
|
|
17
|
+
} from '../utils/dependency-graph-utils';
|
|
14
18
|
|
|
15
19
|
export interface PythonContextMetrics {
|
|
16
20
|
file: string;
|
|
@@ -85,7 +89,7 @@ export async function analyzePythonContext(
|
|
|
85
89
|
|
|
86
90
|
// Calculate metrics
|
|
87
91
|
const linesOfCode = code.split('\n').length;
|
|
88
|
-
const importDepth =
|
|
92
|
+
const importDepth = calculateImportDepthFromEdges(
|
|
89
93
|
file,
|
|
90
94
|
dependencyGraph,
|
|
91
95
|
new Set()
|
|
@@ -96,10 +100,10 @@ export async function analyzePythonContext(
|
|
|
96
100
|
dependencyGraph
|
|
97
101
|
);
|
|
98
102
|
const cohesion = calculatePythonCohesion(exports, imports);
|
|
99
|
-
const circularDependencies =
|
|
103
|
+
const circularDependencies = detectGraphCyclesFromFile(
|
|
100
104
|
file,
|
|
101
105
|
dependencyGraph
|
|
102
|
-
);
|
|
106
|
+
).map((cycle) => cycle.join(' -> '));
|
|
103
107
|
|
|
104
108
|
results.push({
|
|
105
109
|
file,
|
|
@@ -212,40 +216,6 @@ function resolvePythonImport(
|
|
|
212
216
|
return undefined;
|
|
213
217
|
}
|
|
214
218
|
|
|
215
|
-
/**
|
|
216
|
-
* Calculate import depth for a Python file
|
|
217
|
-
*/
|
|
218
|
-
async function calculatePythonImportDepth(
|
|
219
|
-
file: string,
|
|
220
|
-
dependencyGraph: Map<string, Set<string>>,
|
|
221
|
-
visited: Set<string>,
|
|
222
|
-
depth: number = 0
|
|
223
|
-
): Promise<number> {
|
|
224
|
-
if (visited.has(file)) {
|
|
225
|
-
return depth; // Circular dependency, stop here
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
visited.add(file);
|
|
229
|
-
const dependencies = dependencyGraph.get(file) || new Set();
|
|
230
|
-
|
|
231
|
-
if (dependencies.size === 0) {
|
|
232
|
-
return depth;
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
let maxDepth = depth;
|
|
236
|
-
for (const dep of dependencies) {
|
|
237
|
-
const depDepth = await calculatePythonImportDepth(
|
|
238
|
-
dep,
|
|
239
|
-
dependencyGraph,
|
|
240
|
-
new Set(visited),
|
|
241
|
-
depth + 1
|
|
242
|
-
);
|
|
243
|
-
maxDepth = Math.max(maxDepth, depDepth);
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
return maxDepth;
|
|
247
|
-
}
|
|
248
|
-
|
|
249
219
|
/**
|
|
250
220
|
* Estimate context budget (tokens needed for file + direct deps)
|
|
251
221
|
*/
|
|
@@ -303,42 +273,3 @@ function calculatePythonCohesion(
|
|
|
303
273
|
|
|
304
274
|
return Math.min(1, Math.max(0, cohesion));
|
|
305
275
|
}
|
|
306
|
-
|
|
307
|
-
/**
|
|
308
|
-
* Detect circular dependencies
|
|
309
|
-
*/
|
|
310
|
-
function detectCircularDependencies(
|
|
311
|
-
file: string,
|
|
312
|
-
dependencyGraph: Map<string, Set<string>>
|
|
313
|
-
): string[] {
|
|
314
|
-
const circular: string[] = [];
|
|
315
|
-
const visited = new Set<string>();
|
|
316
|
-
const recursionStack = new Set<string>();
|
|
317
|
-
|
|
318
|
-
function dfs(current: string, path: string[]): void {
|
|
319
|
-
if (recursionStack.has(current)) {
|
|
320
|
-
// Found a cycle
|
|
321
|
-
const cycleStart = path.indexOf(current);
|
|
322
|
-
const cycle = path.slice(cycleStart).concat([current]);
|
|
323
|
-
circular.push(cycle.join(' ā '));
|
|
324
|
-
return;
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
if (visited.has(current)) {
|
|
328
|
-
return;
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
visited.add(current);
|
|
332
|
-
recursionStack.add(current);
|
|
333
|
-
|
|
334
|
-
const dependencies = dependencyGraph.get(current) || new Set();
|
|
335
|
-
for (const dep of dependencies) {
|
|
336
|
-
dfs(dep, [...path, current]);
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
recursionStack.delete(current);
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
dfs(file, []);
|
|
343
|
-
return [...new Set(circular)]; // Deduplicate
|
|
344
|
-
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import {
|
|
2
|
+
loadMergedConfig,
|
|
3
|
+
handleJSONOutput,
|
|
4
|
+
handleCLIError,
|
|
5
|
+
getElapsedTime,
|
|
6
|
+
resolveOutputPath,
|
|
7
|
+
} from '@aiready/core';
|
|
8
|
+
import { analyzeContext } from './analyzer';
|
|
9
|
+
import { generateSummary } from './summary';
|
|
10
|
+
import {
|
|
11
|
+
displayConsoleReport,
|
|
12
|
+
generateHTMLReport,
|
|
13
|
+
runInteractiveSetup,
|
|
14
|
+
} from './utils/output-formatter';
|
|
15
|
+
import chalk from 'chalk';
|
|
16
|
+
import { writeFileSync } from 'fs';
|
|
17
|
+
|
|
18
|
+
export async function contextActionHandler(directory: string, options: any) {
|
|
19
|
+
console.log(chalk.blue('š Analyzing context window costs...\n'));
|
|
20
|
+
|
|
21
|
+
const startTime = Date.now();
|
|
22
|
+
|
|
23
|
+
try {
|
|
24
|
+
// Define defaults
|
|
25
|
+
const defaults = {
|
|
26
|
+
maxDepth: 5,
|
|
27
|
+
maxContextBudget: 10000,
|
|
28
|
+
minCohesion: 0.6,
|
|
29
|
+
maxFragmentation: 0.5,
|
|
30
|
+
focus: 'all',
|
|
31
|
+
includeNodeModules: false,
|
|
32
|
+
include: undefined,
|
|
33
|
+
exclude: undefined,
|
|
34
|
+
maxResults: 10,
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
// Load and merge config with CLI options
|
|
38
|
+
let finalOptions = (await loadMergedConfig(directory, defaults, {
|
|
39
|
+
maxDepth: options.maxDepth ? parseInt(options.maxDepth) : undefined,
|
|
40
|
+
maxContextBudget: options.maxContext
|
|
41
|
+
? parseInt(options.maxContext)
|
|
42
|
+
: undefined,
|
|
43
|
+
minCohesion: options.minCohesion
|
|
44
|
+
? parseFloat(options.minCohesion)
|
|
45
|
+
: undefined,
|
|
46
|
+
maxFragmentation: options.maxFragmentation
|
|
47
|
+
? parseFloat(options.maxFragmentation)
|
|
48
|
+
: undefined,
|
|
49
|
+
focus:
|
|
50
|
+
(options.focus as 'fragmentation' | 'cohesion' | 'depth' | 'all') ||
|
|
51
|
+
undefined,
|
|
52
|
+
includeNodeModules: options.includeNodeModules,
|
|
53
|
+
include: options.include?.split(','),
|
|
54
|
+
exclude: options.exclude?.split(','),
|
|
55
|
+
maxResults: options.maxResults ? parseInt(options.maxResults) : undefined,
|
|
56
|
+
})) as any;
|
|
57
|
+
|
|
58
|
+
// Interactive setup if requested
|
|
59
|
+
if (options.interactive) {
|
|
60
|
+
finalOptions = await runInteractiveSetup(directory, finalOptions);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Run analysis
|
|
64
|
+
const results = await analyzeContext(finalOptions);
|
|
65
|
+
const summary = generateSummary(results, finalOptions);
|
|
66
|
+
|
|
67
|
+
const duration = getElapsedTime(startTime);
|
|
68
|
+
|
|
69
|
+
// Handle output
|
|
70
|
+
if (options.output === 'json') {
|
|
71
|
+
handleJSONOutput(
|
|
72
|
+
{
|
|
73
|
+
summary: {
|
|
74
|
+
...summary,
|
|
75
|
+
executionTime: duration,
|
|
76
|
+
config: {
|
|
77
|
+
scan: { tools: ['context'] },
|
|
78
|
+
tools: { context: finalOptions },
|
|
79
|
+
},
|
|
80
|
+
toolConfigs: { context: finalOptions },
|
|
81
|
+
},
|
|
82
|
+
context: { results },
|
|
83
|
+
},
|
|
84
|
+
options.outputFile
|
|
85
|
+
);
|
|
86
|
+
} else if (options.output === 'html') {
|
|
87
|
+
const html = generateHTMLReport(summary, results);
|
|
88
|
+
const outputPath = resolveOutputPath(
|
|
89
|
+
directory,
|
|
90
|
+
options.outputFile,
|
|
91
|
+
'context-report.html'
|
|
92
|
+
);
|
|
93
|
+
writeFileSync(outputPath, html, 'utf-8');
|
|
94
|
+
console.log(chalk.green(`\nā
HTML report saved to: ${outputPath}`));
|
|
95
|
+
} else {
|
|
96
|
+
// Default: Console
|
|
97
|
+
displayConsoleReport(summary, results, finalOptions.maxResults);
|
|
98
|
+
console.log(chalk.dim(`\n⨠Analysis completed in ${duration}ms\n`));
|
|
99
|
+
}
|
|
100
|
+
} catch (error) {
|
|
101
|
+
handleCLIError(error, 'context-analyzer');
|
|
102
|
+
}
|
|
103
|
+
}
|