@aiready/context-analyzer 0.17.0 → 0.17.3
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-test.log +20 -20
- package/dist/chunk-7VK3XTSH.mjs +1756 -0
- package/dist/cli.js +31 -27
- package/dist/cli.mjs +1 -1
- package/dist/index.d.mts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +28 -24
- package/dist/index.mjs +1 -1
- package/package.json +2 -2
- package/src/__tests__/contract.test.ts +40 -3
- package/src/analyzer.ts +13 -12
- package/src/types.ts +2 -2
package/dist/cli.js
CHANGED
|
@@ -37,7 +37,7 @@ __export(python_context_exports, {
|
|
|
37
37
|
});
|
|
38
38
|
async function analyzePythonContext(files, rootDir) {
|
|
39
39
|
const results = [];
|
|
40
|
-
const parser = (0,
|
|
40
|
+
const parser = (0, import_core7.getParser)("dummy.py");
|
|
41
41
|
if (!parser) {
|
|
42
42
|
console.warn("Python parser not available");
|
|
43
43
|
return results;
|
|
@@ -101,7 +101,7 @@ async function analyzePythonContext(files, rootDir) {
|
|
|
101
101
|
}
|
|
102
102
|
async function buildPythonDependencyGraph(files, rootDir) {
|
|
103
103
|
const graph = /* @__PURE__ */ new Map();
|
|
104
|
-
const parser = (0,
|
|
104
|
+
const parser = (0, import_core7.getParser)("dummy.py");
|
|
105
105
|
if (!parser) return graph;
|
|
106
106
|
for (const file of files) {
|
|
107
107
|
try {
|
|
@@ -181,7 +181,7 @@ async function calculatePythonImportDepth(file, dependencyGraph, visited, depth
|
|
|
181
181
|
}
|
|
182
182
|
function estimateContextBudget(code, imports, dependencyGraph) {
|
|
183
183
|
void dependencyGraph;
|
|
184
|
-
let budget = (0,
|
|
184
|
+
let budget = (0, import_core7.estimateTokens)(code);
|
|
185
185
|
const avgTokensPerDep = 500;
|
|
186
186
|
budget += imports.length * avgTokensPerDep;
|
|
187
187
|
return budget;
|
|
@@ -231,11 +231,11 @@ function detectCircularDependencies2(file, dependencyGraph) {
|
|
|
231
231
|
dfs(file, []);
|
|
232
232
|
return [...new Set(circular)];
|
|
233
233
|
}
|
|
234
|
-
var
|
|
234
|
+
var import_core7, import_path, import_fs;
|
|
235
235
|
var init_python_context = __esm({
|
|
236
236
|
"src/analyzers/python-context.ts"() {
|
|
237
237
|
"use strict";
|
|
238
|
-
|
|
238
|
+
import_core7 = require("@aiready/core");
|
|
239
239
|
import_path = require("path");
|
|
240
240
|
import_fs = __toESM(require("fs"));
|
|
241
241
|
}
|
|
@@ -245,7 +245,10 @@ var init_python_context = __esm({
|
|
|
245
245
|
var import_commander = require("commander");
|
|
246
246
|
|
|
247
247
|
// src/index.ts
|
|
248
|
-
var
|
|
248
|
+
var import_core8 = require("@aiready/core");
|
|
249
|
+
|
|
250
|
+
// src/analyzer.ts
|
|
251
|
+
var import_core4 = require("@aiready/core");
|
|
249
252
|
|
|
250
253
|
// src/metrics.ts
|
|
251
254
|
var import_core2 = require("@aiready/core");
|
|
@@ -1255,10 +1258,10 @@ function analyzeIssues(params) {
|
|
|
1255
1258
|
} = params;
|
|
1256
1259
|
const issues = [];
|
|
1257
1260
|
const recommendations = [];
|
|
1258
|
-
let severity =
|
|
1261
|
+
let severity = import_core4.Severity.Info;
|
|
1259
1262
|
let potentialSavings = 0;
|
|
1260
1263
|
if (circularDeps.length > 0) {
|
|
1261
|
-
severity =
|
|
1264
|
+
severity = import_core4.Severity.Critical;
|
|
1262
1265
|
issues.push(`Part of ${circularDeps.length} circular dependency chain(s)`);
|
|
1263
1266
|
recommendations.push(
|
|
1264
1267
|
"Break circular dependencies by extracting interfaces or using dependency injection"
|
|
@@ -1266,12 +1269,12 @@ function analyzeIssues(params) {
|
|
|
1266
1269
|
potentialSavings += contextBudget * 0.2;
|
|
1267
1270
|
}
|
|
1268
1271
|
if (importDepth > maxDepth * 1.5) {
|
|
1269
|
-
severity =
|
|
1272
|
+
severity = import_core4.Severity.Critical;
|
|
1270
1273
|
issues.push(`Import depth ${importDepth} exceeds limit by 50%`);
|
|
1271
1274
|
recommendations.push("Flatten dependency tree or use facade pattern");
|
|
1272
1275
|
potentialSavings += contextBudget * 0.3;
|
|
1273
1276
|
} else if (importDepth > maxDepth) {
|
|
1274
|
-
if (severity !==
|
|
1277
|
+
if (severity !== import_core4.Severity.Critical) severity = import_core4.Severity.Major;
|
|
1275
1278
|
issues.push(
|
|
1276
1279
|
`Import depth ${importDepth} exceeds recommended maximum ${maxDepth}`
|
|
1277
1280
|
);
|
|
@@ -1279,7 +1282,7 @@ function analyzeIssues(params) {
|
|
|
1279
1282
|
potentialSavings += contextBudget * 0.15;
|
|
1280
1283
|
}
|
|
1281
1284
|
if (contextBudget > maxContextBudget * 1.5) {
|
|
1282
|
-
severity =
|
|
1285
|
+
severity = import_core4.Severity.Critical;
|
|
1283
1286
|
issues.push(
|
|
1284
1287
|
`Context budget ${contextBudget.toLocaleString()} tokens is 50% over limit`
|
|
1285
1288
|
);
|
|
@@ -1288,7 +1291,7 @@ function analyzeIssues(params) {
|
|
|
1288
1291
|
);
|
|
1289
1292
|
potentialSavings += contextBudget * 0.4;
|
|
1290
1293
|
} else if (contextBudget > maxContextBudget) {
|
|
1291
|
-
if (severity !==
|
|
1294
|
+
if (severity !== import_core4.Severity.Critical) severity = import_core4.Severity.Major;
|
|
1292
1295
|
issues.push(
|
|
1293
1296
|
`Context budget ${contextBudget.toLocaleString()} exceeds ${maxContextBudget.toLocaleString()}`
|
|
1294
1297
|
);
|
|
@@ -1296,7 +1299,7 @@ function analyzeIssues(params) {
|
|
|
1296
1299
|
potentialSavings += contextBudget * 0.2;
|
|
1297
1300
|
}
|
|
1298
1301
|
if (cohesionScore < minCohesion * 0.5) {
|
|
1299
|
-
if (severity !==
|
|
1302
|
+
if (severity !== import_core4.Severity.Critical) severity = import_core4.Severity.Major;
|
|
1300
1303
|
issues.push(
|
|
1301
1304
|
`Very low cohesion (${(cohesionScore * 100).toFixed(0)}%) - mixed concerns`
|
|
1302
1305
|
);
|
|
@@ -1305,13 +1308,14 @@ function analyzeIssues(params) {
|
|
|
1305
1308
|
);
|
|
1306
1309
|
potentialSavings += contextBudget * 0.25;
|
|
1307
1310
|
} else if (cohesionScore < minCohesion) {
|
|
1308
|
-
if (severity ===
|
|
1311
|
+
if (severity === import_core4.Severity.Info) severity = import_core4.Severity.Minor;
|
|
1309
1312
|
issues.push(`Low cohesion (${(cohesionScore * 100).toFixed(0)}%)`);
|
|
1310
1313
|
recommendations.push("Consider grouping related exports together");
|
|
1311
1314
|
potentialSavings += contextBudget * 0.1;
|
|
1312
1315
|
}
|
|
1313
1316
|
if (fragmentationScore > maxFragmentation) {
|
|
1314
|
-
if (severity ===
|
|
1317
|
+
if (severity === import_core4.Severity.Info || severity === import_core4.Severity.Minor)
|
|
1318
|
+
severity = import_core4.Severity.Minor;
|
|
1315
1319
|
issues.push(
|
|
1316
1320
|
`High fragmentation (${(fragmentationScore * 100).toFixed(0)}%) - scattered implementation`
|
|
1317
1321
|
);
|
|
@@ -1325,7 +1329,7 @@ function analyzeIssues(params) {
|
|
|
1325
1329
|
if (isBuildArtifact(file)) {
|
|
1326
1330
|
issues.push("Detected build artifact (bundled/output file)");
|
|
1327
1331
|
recommendations.push("Exclude build outputs from analysis");
|
|
1328
|
-
severity =
|
|
1332
|
+
severity = import_core4.Severity.Info;
|
|
1329
1333
|
potentialSavings = 0;
|
|
1330
1334
|
}
|
|
1331
1335
|
return {
|
|
@@ -1341,10 +1345,10 @@ function isBuildArtifact(filePath) {
|
|
|
1341
1345
|
}
|
|
1342
1346
|
|
|
1343
1347
|
// src/scoring.ts
|
|
1344
|
-
var
|
|
1348
|
+
var import_core5 = require("@aiready/core");
|
|
1345
1349
|
|
|
1346
1350
|
// src/defaults.ts
|
|
1347
|
-
var
|
|
1351
|
+
var import_core6 = require("@aiready/core");
|
|
1348
1352
|
|
|
1349
1353
|
// src/summary.ts
|
|
1350
1354
|
function generateSummary(results) {
|
|
@@ -1484,7 +1488,7 @@ async function analyzeContext(options) {
|
|
|
1484
1488
|
includeNodeModules = false,
|
|
1485
1489
|
...scanOptions
|
|
1486
1490
|
} = options;
|
|
1487
|
-
const files = await (0,
|
|
1491
|
+
const files = await (0, import_core8.scanFiles)({
|
|
1488
1492
|
...scanOptions,
|
|
1489
1493
|
exclude: includeNodeModules && scanOptions.exclude ? scanOptions.exclude.filter(
|
|
1490
1494
|
(pattern) => pattern !== "**/node_modules/**"
|
|
@@ -1494,7 +1498,7 @@ async function analyzeContext(options) {
|
|
|
1494
1498
|
const fileContents = await Promise.all(
|
|
1495
1499
|
files.map(async (file) => ({
|
|
1496
1500
|
file,
|
|
1497
|
-
content: await (0,
|
|
1501
|
+
content: await (0, import_core8.readFileContent)(file)
|
|
1498
1502
|
}))
|
|
1499
1503
|
);
|
|
1500
1504
|
const graph = buildDependencyGraph(
|
|
@@ -1661,7 +1665,7 @@ async function analyzeContext(options) {
|
|
|
1661
1665
|
var import_chalk = __toESM(require("chalk"));
|
|
1662
1666
|
var import_fs2 = require("fs");
|
|
1663
1667
|
var import_path2 = require("path");
|
|
1664
|
-
var
|
|
1668
|
+
var import_core9 = require("@aiready/core");
|
|
1665
1669
|
var import_prompts = __toESM(require("prompts"));
|
|
1666
1670
|
var program = new import_commander.Command();
|
|
1667
1671
|
program.name("aiready-context").description("Analyze AI context window cost and code structure").version("0.1.0").addHelpText(
|
|
@@ -1701,7 +1705,7 @@ program.name("aiready-context").description("Analyze AI context window cost and
|
|
|
1701
1705
|
exclude: void 0,
|
|
1702
1706
|
maxResults: 10
|
|
1703
1707
|
};
|
|
1704
|
-
let finalOptions = await (0,
|
|
1708
|
+
let finalOptions = await (0, import_core9.loadMergedConfig)(directory, defaults, {
|
|
1705
1709
|
maxDepth: options.maxDepth ? parseInt(options.maxDepth) : void 0,
|
|
1706
1710
|
maxContextBudget: options.maxContext ? parseInt(options.maxContext) : void 0,
|
|
1707
1711
|
minCohesion: options.minCohesion ? parseFloat(options.minCohesion) : void 0,
|
|
@@ -1716,7 +1720,7 @@ program.name("aiready-context").description("Analyze AI context window cost and
|
|
|
1716
1720
|
finalOptions = await runInteractiveSetup(directory, finalOptions);
|
|
1717
1721
|
}
|
|
1718
1722
|
const results = await analyzeContext(finalOptions);
|
|
1719
|
-
const elapsedTime = (0,
|
|
1723
|
+
const elapsedTime = (0, import_core9.getElapsedTime)(startTime);
|
|
1720
1724
|
const summary = generateSummary(results);
|
|
1721
1725
|
if (options.output === "json") {
|
|
1722
1726
|
const jsonOutput = {
|
|
@@ -1725,12 +1729,12 @@ program.name("aiready-context").description("Analyze AI context window cost and
|
|
|
1725
1729
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1726
1730
|
analysisTime: elapsedTime
|
|
1727
1731
|
};
|
|
1728
|
-
const outputPath = (0,
|
|
1732
|
+
const outputPath = (0, import_core9.resolveOutputPath)(
|
|
1729
1733
|
options.outputFile,
|
|
1730
1734
|
`context-report-${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}.json`,
|
|
1731
1735
|
directory
|
|
1732
1736
|
);
|
|
1733
|
-
(0,
|
|
1737
|
+
(0, import_core9.handleJSONOutput)(
|
|
1734
1738
|
jsonOutput,
|
|
1735
1739
|
outputPath,
|
|
1736
1740
|
`
|
|
@@ -1740,7 +1744,7 @@ program.name("aiready-context").description("Analyze AI context window cost and
|
|
|
1740
1744
|
}
|
|
1741
1745
|
if (options.output === "html") {
|
|
1742
1746
|
const html = generateHTMLReport(summary, results);
|
|
1743
|
-
const outputPath = (0,
|
|
1747
|
+
const outputPath = (0, import_core9.resolveOutputPath)(
|
|
1744
1748
|
options.outputFile,
|
|
1745
1749
|
`context-report-${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}.html`,
|
|
1746
1750
|
directory
|
|
@@ -1762,7 +1766,7 @@ program.name("aiready-context").description("Analyze AI context window cost and
|
|
|
1762
1766
|
);
|
|
1763
1767
|
displayTuningGuidance(results, finalOptions);
|
|
1764
1768
|
} catch (error) {
|
|
1765
|
-
(0,
|
|
1769
|
+
(0, import_core9.handleCLIError)(error, "Analysis");
|
|
1766
1770
|
}
|
|
1767
1771
|
});
|
|
1768
1772
|
program.parse();
|
package/dist/cli.mjs
CHANGED
package/dist/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ScanOptions, CostConfig, ToolScoringOutput } from '@aiready/core';
|
|
1
|
+
import { Severity, ScanOptions, CostConfig, ToolScoringOutput } from '@aiready/core';
|
|
2
2
|
|
|
3
3
|
interface ContextAnalyzerOptions extends ScanOptions {
|
|
4
4
|
maxDepth?: number;
|
|
@@ -23,7 +23,7 @@ interface ContextAnalysisResult {
|
|
|
23
23
|
fragmentationScore: number;
|
|
24
24
|
relatedFiles: string[];
|
|
25
25
|
fileClassification: FileClassification;
|
|
26
|
-
severity: 'critical' | 'major' | 'minor' | 'info';
|
|
26
|
+
severity: Severity | 'critical' | 'major' | 'minor' | 'info';
|
|
27
27
|
issues: string[];
|
|
28
28
|
recommendations: string[];
|
|
29
29
|
potentialSavings: number;
|
|
@@ -294,7 +294,7 @@ declare function analyzeIssues(params: {
|
|
|
294
294
|
maxFragmentation: number;
|
|
295
295
|
circularDeps: string[][];
|
|
296
296
|
}): {
|
|
297
|
-
severity:
|
|
297
|
+
severity: Severity;
|
|
298
298
|
issues: string[];
|
|
299
299
|
recommendations: string[];
|
|
300
300
|
potentialSavings: number;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ScanOptions, CostConfig, ToolScoringOutput } from '@aiready/core';
|
|
1
|
+
import { Severity, ScanOptions, CostConfig, ToolScoringOutput } from '@aiready/core';
|
|
2
2
|
|
|
3
3
|
interface ContextAnalyzerOptions extends ScanOptions {
|
|
4
4
|
maxDepth?: number;
|
|
@@ -23,7 +23,7 @@ interface ContextAnalysisResult {
|
|
|
23
23
|
fragmentationScore: number;
|
|
24
24
|
relatedFiles: string[];
|
|
25
25
|
fileClassification: FileClassification;
|
|
26
|
-
severity: 'critical' | 'major' | 'minor' | 'info';
|
|
26
|
+
severity: Severity | 'critical' | 'major' | 'minor' | 'info';
|
|
27
27
|
issues: string[];
|
|
28
28
|
recommendations: string[];
|
|
29
29
|
potentialSavings: number;
|
|
@@ -294,7 +294,7 @@ declare function analyzeIssues(params: {
|
|
|
294
294
|
maxFragmentation: number;
|
|
295
295
|
circularDeps: string[][];
|
|
296
296
|
}): {
|
|
297
|
-
severity:
|
|
297
|
+
severity: Severity;
|
|
298
298
|
issues: string[];
|
|
299
299
|
recommendations: string[];
|
|
300
300
|
potentialSavings: number;
|
package/dist/index.js
CHANGED
|
@@ -37,7 +37,7 @@ __export(python_context_exports, {
|
|
|
37
37
|
});
|
|
38
38
|
async function analyzePythonContext(files, rootDir) {
|
|
39
39
|
const results = [];
|
|
40
|
-
const parser = (0,
|
|
40
|
+
const parser = (0, import_core7.getParser)("dummy.py");
|
|
41
41
|
if (!parser) {
|
|
42
42
|
console.warn("Python parser not available");
|
|
43
43
|
return results;
|
|
@@ -101,7 +101,7 @@ async function analyzePythonContext(files, rootDir) {
|
|
|
101
101
|
}
|
|
102
102
|
async function buildPythonDependencyGraph(files, rootDir) {
|
|
103
103
|
const graph = /* @__PURE__ */ new Map();
|
|
104
|
-
const parser = (0,
|
|
104
|
+
const parser = (0, import_core7.getParser)("dummy.py");
|
|
105
105
|
if (!parser) return graph;
|
|
106
106
|
for (const file of files) {
|
|
107
107
|
try {
|
|
@@ -181,7 +181,7 @@ async function calculatePythonImportDepth(file, dependencyGraph, visited, depth
|
|
|
181
181
|
}
|
|
182
182
|
function estimateContextBudget(code, imports, dependencyGraph) {
|
|
183
183
|
void dependencyGraph;
|
|
184
|
-
let budget = (0,
|
|
184
|
+
let budget = (0, import_core7.estimateTokens)(code);
|
|
185
185
|
const avgTokensPerDep = 500;
|
|
186
186
|
budget += imports.length * avgTokensPerDep;
|
|
187
187
|
return budget;
|
|
@@ -231,11 +231,11 @@ function detectCircularDependencies2(file, dependencyGraph) {
|
|
|
231
231
|
dfs(file, []);
|
|
232
232
|
return [...new Set(circular)];
|
|
233
233
|
}
|
|
234
|
-
var
|
|
234
|
+
var import_core7, import_path, import_fs;
|
|
235
235
|
var init_python_context = __esm({
|
|
236
236
|
"src/analyzers/python-context.ts"() {
|
|
237
237
|
"use strict";
|
|
238
|
-
|
|
238
|
+
import_core7 = require("@aiready/core");
|
|
239
239
|
import_path = require("path");
|
|
240
240
|
import_fs = __toESM(require("fs"));
|
|
241
241
|
}
|
|
@@ -290,7 +290,10 @@ __export(index_exports, {
|
|
|
290
290
|
mapScoreToRating: () => mapScoreToRating
|
|
291
291
|
});
|
|
292
292
|
module.exports = __toCommonJS(index_exports);
|
|
293
|
-
var
|
|
293
|
+
var import_core8 = require("@aiready/core");
|
|
294
|
+
|
|
295
|
+
// src/analyzer.ts
|
|
296
|
+
var import_core4 = require("@aiready/core");
|
|
294
297
|
|
|
295
298
|
// src/metrics.ts
|
|
296
299
|
var import_core2 = require("@aiready/core");
|
|
@@ -1415,10 +1418,10 @@ function analyzeIssues(params) {
|
|
|
1415
1418
|
} = params;
|
|
1416
1419
|
const issues = [];
|
|
1417
1420
|
const recommendations = [];
|
|
1418
|
-
let severity =
|
|
1421
|
+
let severity = import_core4.Severity.Info;
|
|
1419
1422
|
let potentialSavings = 0;
|
|
1420
1423
|
if (circularDeps.length > 0) {
|
|
1421
|
-
severity =
|
|
1424
|
+
severity = import_core4.Severity.Critical;
|
|
1422
1425
|
issues.push(`Part of ${circularDeps.length} circular dependency chain(s)`);
|
|
1423
1426
|
recommendations.push(
|
|
1424
1427
|
"Break circular dependencies by extracting interfaces or using dependency injection"
|
|
@@ -1426,12 +1429,12 @@ function analyzeIssues(params) {
|
|
|
1426
1429
|
potentialSavings += contextBudget * 0.2;
|
|
1427
1430
|
}
|
|
1428
1431
|
if (importDepth > maxDepth * 1.5) {
|
|
1429
|
-
severity =
|
|
1432
|
+
severity = import_core4.Severity.Critical;
|
|
1430
1433
|
issues.push(`Import depth ${importDepth} exceeds limit by 50%`);
|
|
1431
1434
|
recommendations.push("Flatten dependency tree or use facade pattern");
|
|
1432
1435
|
potentialSavings += contextBudget * 0.3;
|
|
1433
1436
|
} else if (importDepth > maxDepth) {
|
|
1434
|
-
if (severity !==
|
|
1437
|
+
if (severity !== import_core4.Severity.Critical) severity = import_core4.Severity.Major;
|
|
1435
1438
|
issues.push(
|
|
1436
1439
|
`Import depth ${importDepth} exceeds recommended maximum ${maxDepth}`
|
|
1437
1440
|
);
|
|
@@ -1439,7 +1442,7 @@ function analyzeIssues(params) {
|
|
|
1439
1442
|
potentialSavings += contextBudget * 0.15;
|
|
1440
1443
|
}
|
|
1441
1444
|
if (contextBudget > maxContextBudget * 1.5) {
|
|
1442
|
-
severity =
|
|
1445
|
+
severity = import_core4.Severity.Critical;
|
|
1443
1446
|
issues.push(
|
|
1444
1447
|
`Context budget ${contextBudget.toLocaleString()} tokens is 50% over limit`
|
|
1445
1448
|
);
|
|
@@ -1448,7 +1451,7 @@ function analyzeIssues(params) {
|
|
|
1448
1451
|
);
|
|
1449
1452
|
potentialSavings += contextBudget * 0.4;
|
|
1450
1453
|
} else if (contextBudget > maxContextBudget) {
|
|
1451
|
-
if (severity !==
|
|
1454
|
+
if (severity !== import_core4.Severity.Critical) severity = import_core4.Severity.Major;
|
|
1452
1455
|
issues.push(
|
|
1453
1456
|
`Context budget ${contextBudget.toLocaleString()} exceeds ${maxContextBudget.toLocaleString()}`
|
|
1454
1457
|
);
|
|
@@ -1456,7 +1459,7 @@ function analyzeIssues(params) {
|
|
|
1456
1459
|
potentialSavings += contextBudget * 0.2;
|
|
1457
1460
|
}
|
|
1458
1461
|
if (cohesionScore < minCohesion * 0.5) {
|
|
1459
|
-
if (severity !==
|
|
1462
|
+
if (severity !== import_core4.Severity.Critical) severity = import_core4.Severity.Major;
|
|
1460
1463
|
issues.push(
|
|
1461
1464
|
`Very low cohesion (${(cohesionScore * 100).toFixed(0)}%) - mixed concerns`
|
|
1462
1465
|
);
|
|
@@ -1465,13 +1468,14 @@ function analyzeIssues(params) {
|
|
|
1465
1468
|
);
|
|
1466
1469
|
potentialSavings += contextBudget * 0.25;
|
|
1467
1470
|
} else if (cohesionScore < minCohesion) {
|
|
1468
|
-
if (severity ===
|
|
1471
|
+
if (severity === import_core4.Severity.Info) severity = import_core4.Severity.Minor;
|
|
1469
1472
|
issues.push(`Low cohesion (${(cohesionScore * 100).toFixed(0)}%)`);
|
|
1470
1473
|
recommendations.push("Consider grouping related exports together");
|
|
1471
1474
|
potentialSavings += contextBudget * 0.1;
|
|
1472
1475
|
}
|
|
1473
1476
|
if (fragmentationScore > maxFragmentation) {
|
|
1474
|
-
if (severity ===
|
|
1477
|
+
if (severity === import_core4.Severity.Info || severity === import_core4.Severity.Minor)
|
|
1478
|
+
severity = import_core4.Severity.Minor;
|
|
1475
1479
|
issues.push(
|
|
1476
1480
|
`High fragmentation (${(fragmentationScore * 100).toFixed(0)}%) - scattered implementation`
|
|
1477
1481
|
);
|
|
@@ -1485,7 +1489,7 @@ function analyzeIssues(params) {
|
|
|
1485
1489
|
if (isBuildArtifact(file)) {
|
|
1486
1490
|
issues.push("Detected build artifact (bundled/output file)");
|
|
1487
1491
|
recommendations.push("Exclude build outputs from analysis");
|
|
1488
|
-
severity =
|
|
1492
|
+
severity = import_core4.Severity.Info;
|
|
1489
1493
|
potentialSavings = 0;
|
|
1490
1494
|
}
|
|
1491
1495
|
return {
|
|
@@ -1501,7 +1505,7 @@ function isBuildArtifact(filePath) {
|
|
|
1501
1505
|
}
|
|
1502
1506
|
|
|
1503
1507
|
// src/scoring.ts
|
|
1504
|
-
var
|
|
1508
|
+
var import_core5 = require("@aiready/core");
|
|
1505
1509
|
function calculateContextScore(summary, costConfig) {
|
|
1506
1510
|
const {
|
|
1507
1511
|
avgContextBudget,
|
|
@@ -1597,8 +1601,8 @@ function calculateContextScore(summary, costConfig) {
|
|
|
1597
1601
|
priority: "high"
|
|
1598
1602
|
});
|
|
1599
1603
|
}
|
|
1600
|
-
const cfg = { ...
|
|
1601
|
-
const estimatedMonthlyCost = (0,
|
|
1604
|
+
const cfg = { ...import_core5.DEFAULT_COST_CONFIG, ...costConfig };
|
|
1605
|
+
const estimatedMonthlyCost = (0, import_core5.calculateMonthlyCost)(
|
|
1602
1606
|
avgContextBudget * (summary.totalFiles || 1),
|
|
1603
1607
|
cfg
|
|
1604
1608
|
);
|
|
@@ -1606,7 +1610,7 @@ function calculateContextScore(summary, costConfig) {
|
|
|
1606
1610
|
...Array(criticalIssues).fill({ severity: "critical" }),
|
|
1607
1611
|
...Array(majorIssues).fill({ severity: "major" })
|
|
1608
1612
|
];
|
|
1609
|
-
const productivityImpact = (0,
|
|
1613
|
+
const productivityImpact = (0, import_core5.calculateProductivityImpact)(issues);
|
|
1610
1614
|
return {
|
|
1611
1615
|
toolName: "context-analyzer",
|
|
1612
1616
|
score,
|
|
@@ -1634,9 +1638,9 @@ function mapScoreToRating(score) {
|
|
|
1634
1638
|
}
|
|
1635
1639
|
|
|
1636
1640
|
// src/defaults.ts
|
|
1637
|
-
var
|
|
1641
|
+
var import_core6 = require("@aiready/core");
|
|
1638
1642
|
async function getSmartDefaults(directory, userOptions) {
|
|
1639
|
-
const files = await (0,
|
|
1643
|
+
const files = await (0, import_core6.scanFiles)({
|
|
1640
1644
|
rootDir: directory,
|
|
1641
1645
|
include: userOptions.include,
|
|
1642
1646
|
exclude: userOptions.exclude
|
|
@@ -1818,7 +1822,7 @@ async function analyzeContext(options) {
|
|
|
1818
1822
|
includeNodeModules = false,
|
|
1819
1823
|
...scanOptions
|
|
1820
1824
|
} = options;
|
|
1821
|
-
const files = await (0,
|
|
1825
|
+
const files = await (0, import_core8.scanFiles)({
|
|
1822
1826
|
...scanOptions,
|
|
1823
1827
|
exclude: includeNodeModules && scanOptions.exclude ? scanOptions.exclude.filter(
|
|
1824
1828
|
(pattern) => pattern !== "**/node_modules/**"
|
|
@@ -1828,7 +1832,7 @@ async function analyzeContext(options) {
|
|
|
1828
1832
|
const fileContents = await Promise.all(
|
|
1829
1833
|
files.map(async (file) => ({
|
|
1830
1834
|
file,
|
|
1831
|
-
content: await (0,
|
|
1835
|
+
content: await (0, import_core8.readFileContent)(file)
|
|
1832
1836
|
}))
|
|
1833
1837
|
);
|
|
1834
1838
|
const graph = buildDependencyGraph(
|
package/dist/index.mjs
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aiready/context-analyzer",
|
|
3
|
-
"version": "0.17.
|
|
3
|
+
"version": "0.17.3",
|
|
4
4
|
"description": "AI context window cost analysis - detect fragmented code, deep import chains, and expensive context budgets",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
"commander": "^14.0.0",
|
|
50
50
|
"chalk": "^5.3.0",
|
|
51
51
|
"prompts": "^2.4.2",
|
|
52
|
-
"@aiready/core": "0.19.
|
|
52
|
+
"@aiready/core": "0.19.3"
|
|
53
53
|
},
|
|
54
54
|
"devDependencies": {
|
|
55
55
|
"@types/node": "^24.0.0",
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import { describe, it, expect, vi } from 'vitest';
|
|
2
2
|
import { analyzeContext, generateSummary } from '../index';
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
validateSpokeOutput,
|
|
5
|
+
SpokeOutputSchema,
|
|
6
|
+
Severity,
|
|
7
|
+
IssueType,
|
|
8
|
+
} from '@aiready/core';
|
|
4
9
|
|
|
5
10
|
// Mock core functions
|
|
6
11
|
vi.mock('@aiready/core', async (importOriginal) => {
|
|
@@ -22,17 +27,49 @@ describe('Context Analyzer Contract Validation', () => {
|
|
|
22
27
|
|
|
23
28
|
const summary = generateSummary(results);
|
|
24
29
|
|
|
30
|
+
// Context analyzer results need mapping to meet the strict AnalysisResult contract
|
|
31
|
+
// because it uses 'file' instead of 'fileName' and 'issues: string[]' instead of 'issues: Issue[]'
|
|
32
|
+
const mappedResults = results.map((r) => ({
|
|
33
|
+
fileName: r.file,
|
|
34
|
+
issues: r.issues.map((msg) => ({
|
|
35
|
+
type: IssueType.ContextFragmentation,
|
|
36
|
+
severity: r.severity as Severity,
|
|
37
|
+
message: msg,
|
|
38
|
+
location: { file: r.file, line: 1 },
|
|
39
|
+
})),
|
|
40
|
+
metrics: {
|
|
41
|
+
tokenCost: r.tokenCost,
|
|
42
|
+
complexityScore: r.cohesionScore * 100,
|
|
43
|
+
},
|
|
44
|
+
}));
|
|
45
|
+
|
|
25
46
|
const fullOutput = {
|
|
26
|
-
results,
|
|
47
|
+
results: mappedResults,
|
|
27
48
|
summary,
|
|
49
|
+
metadata: {
|
|
50
|
+
toolName: 'context-analyzer',
|
|
51
|
+
version: '0.1.0',
|
|
52
|
+
timestamp: new Date().toISOString(),
|
|
53
|
+
},
|
|
28
54
|
};
|
|
29
55
|
|
|
56
|
+
// 1. Legacy validation
|
|
30
57
|
const validation = validateSpokeOutput('context-analyzer', fullOutput);
|
|
31
58
|
|
|
32
59
|
if (!validation.valid) {
|
|
33
|
-
console.error('Contract Validation Errors:', validation.errors);
|
|
60
|
+
console.error('Contract Validation Errors (Legacy):', validation.errors);
|
|
34
61
|
}
|
|
35
62
|
|
|
36
63
|
expect(validation.valid).toBe(true);
|
|
64
|
+
|
|
65
|
+
// 2. Zod validation
|
|
66
|
+
const zodResult = SpokeOutputSchema.safeParse(fullOutput);
|
|
67
|
+
if (!zodResult.success) {
|
|
68
|
+
console.error(
|
|
69
|
+
'Contract Validation Errors (Zod):',
|
|
70
|
+
zodResult.error.format()
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
expect(zodResult.success).toBe(true);
|
|
37
74
|
});
|
|
38
75
|
});
|
package/src/analyzer.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { estimateTokens } from '@aiready/core';
|
|
1
|
+
import { estimateTokens, Severity } from '@aiready/core';
|
|
2
2
|
import type {
|
|
3
3
|
DependencyGraph,
|
|
4
4
|
DependencyNode,
|
|
@@ -58,7 +58,7 @@ export function analyzeIssues(params: {
|
|
|
58
58
|
maxFragmentation: number;
|
|
59
59
|
circularDeps: string[][];
|
|
60
60
|
}): {
|
|
61
|
-
severity:
|
|
61
|
+
severity: Severity;
|
|
62
62
|
issues: string[];
|
|
63
63
|
recommendations: string[];
|
|
64
64
|
potentialSavings: number;
|
|
@@ -78,12 +78,12 @@ export function analyzeIssues(params: {
|
|
|
78
78
|
|
|
79
79
|
const issues: string[] = [];
|
|
80
80
|
const recommendations: string[] = [];
|
|
81
|
-
let severity:
|
|
81
|
+
let severity: Severity = Severity.Info;
|
|
82
82
|
let potentialSavings = 0;
|
|
83
83
|
|
|
84
84
|
// Check circular dependencies (CRITICAL)
|
|
85
85
|
if (circularDeps.length > 0) {
|
|
86
|
-
severity =
|
|
86
|
+
severity = Severity.Critical;
|
|
87
87
|
issues.push(`Part of ${circularDeps.length} circular dependency chain(s)`);
|
|
88
88
|
recommendations.push(
|
|
89
89
|
'Break circular dependencies by extracting interfaces or using dependency injection'
|
|
@@ -93,12 +93,12 @@ export function analyzeIssues(params: {
|
|
|
93
93
|
|
|
94
94
|
// Check import depth
|
|
95
95
|
if (importDepth > maxDepth * 1.5) {
|
|
96
|
-
severity =
|
|
96
|
+
severity = Severity.Critical;
|
|
97
97
|
issues.push(`Import depth ${importDepth} exceeds limit by 50%`);
|
|
98
98
|
recommendations.push('Flatten dependency tree or use facade pattern');
|
|
99
99
|
potentialSavings += contextBudget * 0.3;
|
|
100
100
|
} else if (importDepth > maxDepth) {
|
|
101
|
-
if (severity !==
|
|
101
|
+
if (severity !== Severity.Critical) severity = Severity.Major;
|
|
102
102
|
issues.push(
|
|
103
103
|
`Import depth ${importDepth} exceeds recommended maximum ${maxDepth}`
|
|
104
104
|
);
|
|
@@ -108,7 +108,7 @@ export function analyzeIssues(params: {
|
|
|
108
108
|
|
|
109
109
|
// Check context budget
|
|
110
110
|
if (contextBudget > maxContextBudget * 1.5) {
|
|
111
|
-
severity =
|
|
111
|
+
severity = Severity.Critical;
|
|
112
112
|
issues.push(
|
|
113
113
|
`Context budget ${contextBudget.toLocaleString()} tokens is 50% over limit`
|
|
114
114
|
);
|
|
@@ -117,7 +117,7 @@ export function analyzeIssues(params: {
|
|
|
117
117
|
);
|
|
118
118
|
potentialSavings += contextBudget * 0.4;
|
|
119
119
|
} else if (contextBudget > maxContextBudget) {
|
|
120
|
-
if (severity !==
|
|
120
|
+
if (severity !== Severity.Critical) severity = Severity.Major;
|
|
121
121
|
issues.push(
|
|
122
122
|
`Context budget ${contextBudget.toLocaleString()} exceeds ${maxContextBudget.toLocaleString()}`
|
|
123
123
|
);
|
|
@@ -127,7 +127,7 @@ export function analyzeIssues(params: {
|
|
|
127
127
|
|
|
128
128
|
// Check cohesion
|
|
129
129
|
if (cohesionScore < minCohesion * 0.5) {
|
|
130
|
-
if (severity !==
|
|
130
|
+
if (severity !== Severity.Critical) severity = Severity.Major;
|
|
131
131
|
issues.push(
|
|
132
132
|
`Very low cohesion (${(cohesionScore * 100).toFixed(0)}%) - mixed concerns`
|
|
133
133
|
);
|
|
@@ -136,7 +136,7 @@ export function analyzeIssues(params: {
|
|
|
136
136
|
);
|
|
137
137
|
potentialSavings += contextBudget * 0.25;
|
|
138
138
|
} else if (cohesionScore < minCohesion) {
|
|
139
|
-
if (severity ===
|
|
139
|
+
if (severity === Severity.Info) severity = Severity.Minor;
|
|
140
140
|
issues.push(`Low cohesion (${(cohesionScore * 100).toFixed(0)}%)`);
|
|
141
141
|
recommendations.push('Consider grouping related exports together');
|
|
142
142
|
potentialSavings += contextBudget * 0.1;
|
|
@@ -144,7 +144,8 @@ export function analyzeIssues(params: {
|
|
|
144
144
|
|
|
145
145
|
// Check fragmentation
|
|
146
146
|
if (fragmentationScore > maxFragmentation) {
|
|
147
|
-
if (severity ===
|
|
147
|
+
if (severity === Severity.Info || severity === Severity.Minor)
|
|
148
|
+
severity = Severity.Minor;
|
|
148
149
|
issues.push(
|
|
149
150
|
`High fragmentation (${(fragmentationScore * 100).toFixed(0)}%) - scattered implementation`
|
|
150
151
|
);
|
|
@@ -161,7 +162,7 @@ export function analyzeIssues(params: {
|
|
|
161
162
|
if (isBuildArtifact(file)) {
|
|
162
163
|
issues.push('Detected build artifact (bundled/output file)');
|
|
163
164
|
recommendations.push('Exclude build outputs from analysis');
|
|
164
|
-
severity =
|
|
165
|
+
severity = Severity.Info;
|
|
165
166
|
potentialSavings = 0;
|
|
166
167
|
}
|
|
167
168
|
|
package/src/types.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ScanOptions } from '@aiready/core';
|
|
1
|
+
import type { ScanOptions, Severity } from '@aiready/core';
|
|
2
2
|
|
|
3
3
|
export interface ContextAnalyzerOptions extends ScanOptions {
|
|
4
4
|
maxDepth?: number; // Maximum acceptable import depth, default 5
|
|
@@ -36,7 +36,7 @@ export interface ContextAnalysisResult {
|
|
|
36
36
|
fileClassification: FileClassification; // Type of file for analysis context
|
|
37
37
|
|
|
38
38
|
// Recommendations
|
|
39
|
-
severity: 'critical' | 'major' | 'minor' | 'info';
|
|
39
|
+
severity: Severity | 'critical' | 'major' | 'minor' | 'info';
|
|
40
40
|
issues: string[]; // List of specific problems
|
|
41
41
|
recommendations: string[]; // Actionable suggestions
|
|
42
42
|
potentialSavings: number; // Estimated token savings if fixed
|