@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/dist/index.js
CHANGED
|
@@ -43,7 +43,12 @@ async function analyzePythonContext(files, rootDir) {
|
|
|
43
43
|
return results;
|
|
44
44
|
}
|
|
45
45
|
const pythonFiles = files.filter((f) => f.toLowerCase().endsWith(".py"));
|
|
46
|
-
|
|
46
|
+
void import_path.relative;
|
|
47
|
+
void import_path.join;
|
|
48
|
+
const dependencyGraph = await buildPythonDependencyGraph(
|
|
49
|
+
pythonFiles,
|
|
50
|
+
rootDir
|
|
51
|
+
);
|
|
47
52
|
for (const file of pythonFiles) {
|
|
48
53
|
try {
|
|
49
54
|
const code = await import_fs.default.promises.readFile(file, "utf-8");
|
|
@@ -59,10 +64,21 @@ async function analyzePythonContext(files, rootDir) {
|
|
|
59
64
|
type: exp.type
|
|
60
65
|
}));
|
|
61
66
|
const linesOfCode = code.split("\n").length;
|
|
62
|
-
const importDepth = await calculatePythonImportDepth(
|
|
63
|
-
|
|
67
|
+
const importDepth = await calculatePythonImportDepth(
|
|
68
|
+
file,
|
|
69
|
+
dependencyGraph,
|
|
70
|
+
/* @__PURE__ */ new Set()
|
|
71
|
+
);
|
|
72
|
+
const contextBudget = estimateContextBudget(
|
|
73
|
+
code,
|
|
74
|
+
imports,
|
|
75
|
+
dependencyGraph
|
|
76
|
+
);
|
|
64
77
|
const cohesion = calculatePythonCohesion(exports2, imports);
|
|
65
|
-
const circularDependencies = detectCircularDependencies2(
|
|
78
|
+
const circularDependencies = detectCircularDependencies2(
|
|
79
|
+
file,
|
|
80
|
+
dependencyGraph
|
|
81
|
+
);
|
|
66
82
|
results.push({
|
|
67
83
|
file,
|
|
68
84
|
importDepth,
|
|
@@ -100,6 +116,7 @@ async function buildPythonDependencyGraph(files, rootDir) {
|
|
|
100
116
|
}
|
|
101
117
|
graph.set(file, dependencies);
|
|
102
118
|
} catch (error) {
|
|
119
|
+
void error;
|
|
103
120
|
}
|
|
104
121
|
}
|
|
105
122
|
return graph;
|
|
@@ -408,8 +425,12 @@ function findConsolidationCandidates(graph, coUsageMatrix, typeGraph, minCoUsage
|
|
|
408
425
|
if (coUsageCount < minCoUsage) continue;
|
|
409
426
|
const nodeB = graph.nodes.get(fileB);
|
|
410
427
|
if (!nodeB) continue;
|
|
411
|
-
const typesA = new Set(
|
|
412
|
-
|
|
428
|
+
const typesA = new Set(
|
|
429
|
+
nodeA.exports.flatMap((e) => e.typeReferences || [])
|
|
430
|
+
);
|
|
431
|
+
const typesB = new Set(
|
|
432
|
+
nodeB.exports.flatMap((e) => e.typeReferences || [])
|
|
433
|
+
);
|
|
413
434
|
const sharedTypes = Array.from(typesA).filter((t) => typesB.has(t));
|
|
414
435
|
if (sharedTypes.length >= minSharedTypes) {
|
|
415
436
|
const strength = coUsageCount / 10 + sharedTypes.length / 5;
|
|
@@ -437,7 +458,26 @@ function extractDomainKeywordsFromPaths(files) {
|
|
|
437
458
|
const folderNames = /* @__PURE__ */ new Set();
|
|
438
459
|
for (const { file } of files) {
|
|
439
460
|
const segments = file.split("/");
|
|
440
|
-
const skipFolders = /* @__PURE__ */ new Set([
|
|
461
|
+
const skipFolders = /* @__PURE__ */ new Set([
|
|
462
|
+
"src",
|
|
463
|
+
"lib",
|
|
464
|
+
"dist",
|
|
465
|
+
"build",
|
|
466
|
+
"node_modules",
|
|
467
|
+
"test",
|
|
468
|
+
"tests",
|
|
469
|
+
"__tests__",
|
|
470
|
+
"spec",
|
|
471
|
+
"e2e",
|
|
472
|
+
"scripts",
|
|
473
|
+
"components",
|
|
474
|
+
"utils",
|
|
475
|
+
"helpers",
|
|
476
|
+
"util",
|
|
477
|
+
"helper",
|
|
478
|
+
"api",
|
|
479
|
+
"apis"
|
|
480
|
+
]);
|
|
441
481
|
for (const segment of segments) {
|
|
442
482
|
const normalized = segment.toLowerCase();
|
|
443
483
|
if (normalized && !skipFolders.has(normalized) && !normalized.includes(".")) {
|
|
@@ -473,9 +513,15 @@ function buildDependencyGraph(files) {
|
|
|
473
513
|
const nodes = /* @__PURE__ */ new Map();
|
|
474
514
|
const edges = /* @__PURE__ */ new Map();
|
|
475
515
|
const autoDetectedKeywords = extractDomainKeywordsFromPaths(files);
|
|
516
|
+
void import_core.calculateImportSimilarity;
|
|
476
517
|
for (const { file, content } of files) {
|
|
477
518
|
const imports = extractImportsFromContent(content);
|
|
478
|
-
const exports2 = extractExportsWithAST(
|
|
519
|
+
const exports2 = extractExportsWithAST(
|
|
520
|
+
content,
|
|
521
|
+
file,
|
|
522
|
+
{ domainKeywords: autoDetectedKeywords },
|
|
523
|
+
imports
|
|
524
|
+
);
|
|
479
525
|
const tokenCost = (0, import_core.estimateTokens)(content);
|
|
480
526
|
const linesOfCode = content.split("\n").length;
|
|
481
527
|
nodes.set(file, {
|
|
@@ -619,7 +665,9 @@ function isTestFile(filePath) {
|
|
|
619
665
|
}
|
|
620
666
|
function calculateFragmentation(files, domain, options) {
|
|
621
667
|
if (files.length <= 1) return 0;
|
|
622
|
-
const directories = new Set(
|
|
668
|
+
const directories = new Set(
|
|
669
|
+
files.map((f) => f.split("/").slice(0, -1).join("/"))
|
|
670
|
+
);
|
|
623
671
|
const uniqueDirs = directories.size;
|
|
624
672
|
if (options?.useLogScale) {
|
|
625
673
|
if (uniqueDirs <= 1) return 0;
|
|
@@ -692,7 +740,9 @@ function detectModuleClusters(graph, options) {
|
|
|
692
740
|
const node = graph.nodes.get(file);
|
|
693
741
|
return sum + (node?.tokenCost || 0);
|
|
694
742
|
}, 0);
|
|
695
|
-
const baseFragmentation = calculateFragmentation(files, domain, {
|
|
743
|
+
const baseFragmentation = calculateFragmentation(files, domain, {
|
|
744
|
+
useLogScale: !!options?.useLogScale
|
|
745
|
+
});
|
|
696
746
|
let importSimilarityTotal = 0;
|
|
697
747
|
let importComparisons = 0;
|
|
698
748
|
for (let i = 0; i < files.length; i++) {
|
|
@@ -713,7 +763,9 @@ function detectModuleClusters(graph, options) {
|
|
|
713
763
|
const directoryDistance = calculateDirectoryDistance(files);
|
|
714
764
|
const avgCohesion = files.reduce((sum, file) => {
|
|
715
765
|
const node = graph.nodes.get(file);
|
|
716
|
-
return sum + (node ? calculateCohesion(node.exports, file, {
|
|
766
|
+
return sum + (node ? calculateCohesion(node.exports, file, {
|
|
767
|
+
coUsageMatrix: graph.coUsageMatrix
|
|
768
|
+
}) : 0);
|
|
717
769
|
}, 0) / files.length;
|
|
718
770
|
const targetFiles = Math.max(1, Math.ceil(files.length / 3));
|
|
719
771
|
const consolidationPlan = generateConsolidationPlan(
|
|
@@ -761,7 +813,12 @@ function extractExports(content, filePath, domainOptions, fileImports) {
|
|
|
761
813
|
while ((match = pattern.exec(content)) !== null) {
|
|
762
814
|
const name = match[1] || "default";
|
|
763
815
|
const type = types[index];
|
|
764
|
-
const inferredDomain = inferDomain(
|
|
816
|
+
const inferredDomain = inferDomain(
|
|
817
|
+
name,
|
|
818
|
+
filePath,
|
|
819
|
+
domainOptions,
|
|
820
|
+
fileImports
|
|
821
|
+
);
|
|
765
822
|
exports2.push({ name, type, inferredDomain });
|
|
766
823
|
}
|
|
767
824
|
});
|
|
@@ -869,11 +926,17 @@ function extractExportsWithAST(content, filePath, domainOptions, fileImports) {
|
|
|
869
926
|
return astExports.map((exp) => ({
|
|
870
927
|
name: exp.name,
|
|
871
928
|
type: exp.type,
|
|
872
|
-
inferredDomain: inferDomain(
|
|
929
|
+
inferredDomain: inferDomain(
|
|
930
|
+
exp.name,
|
|
931
|
+
filePath,
|
|
932
|
+
domainOptions,
|
|
933
|
+
fileImports
|
|
934
|
+
),
|
|
873
935
|
imports: exp.imports,
|
|
874
936
|
dependencies: exp.dependencies
|
|
875
937
|
}));
|
|
876
938
|
} catch (error) {
|
|
939
|
+
void error;
|
|
877
940
|
return extractExports(content, filePath, domainOptions, fileImports);
|
|
878
941
|
}
|
|
879
942
|
}
|
|
@@ -888,15 +951,24 @@ function calculateEnhancedCohesion(exports2, filePath, options) {
|
|
|
888
951
|
const importCohesion = hasImportData ? calculateImportBasedCohesion(exports2) : void 0;
|
|
889
952
|
const coUsageMatrix = options?.coUsageMatrix;
|
|
890
953
|
const structuralCohesion = filePath && coUsageMatrix ? calculateStructuralCohesionFromCoUsage(filePath, coUsageMatrix) : void 0;
|
|
891
|
-
const defaultWeights = {
|
|
954
|
+
const defaultWeights = {
|
|
955
|
+
importBased: 0.5,
|
|
956
|
+
structural: 0.3,
|
|
957
|
+
domainBased: 0.2
|
|
958
|
+
};
|
|
892
959
|
const weights = { ...defaultWeights, ...options?.weights || {} };
|
|
893
960
|
const signals = [];
|
|
894
|
-
if (importCohesion !== void 0)
|
|
895
|
-
|
|
961
|
+
if (importCohesion !== void 0)
|
|
962
|
+
signals.push({ score: importCohesion, weight: weights.importBased });
|
|
963
|
+
if (structuralCohesion !== void 0)
|
|
964
|
+
signals.push({ score: structuralCohesion, weight: weights.structural });
|
|
896
965
|
signals.push({ score: domainCohesion, weight: weights.domainBased });
|
|
897
966
|
const totalWeight = signals.reduce((s, el) => s + el.weight, 0);
|
|
898
967
|
if (totalWeight === 0) return domainCohesion;
|
|
899
|
-
const combined = signals.reduce(
|
|
968
|
+
const combined = signals.reduce(
|
|
969
|
+
(sum, el) => sum + el.score * (el.weight / totalWeight),
|
|
970
|
+
0
|
|
971
|
+
);
|
|
900
972
|
return combined;
|
|
901
973
|
}
|
|
902
974
|
function calculateStructuralCohesionFromCoUsage(file, coUsageMatrix) {
|
|
@@ -919,7 +991,9 @@ function calculateStructuralCohesionFromCoUsage(file, coUsageMatrix) {
|
|
|
919
991
|
return maxEntropy > 0 ? 1 - entropy / maxEntropy : 1;
|
|
920
992
|
}
|
|
921
993
|
function calculateImportBasedCohesion(exports2) {
|
|
922
|
-
const exportsWithImports = exports2.filter(
|
|
994
|
+
const exportsWithImports = exports2.filter(
|
|
995
|
+
(e) => e.imports && e.imports.length > 0
|
|
996
|
+
);
|
|
923
997
|
if (exportsWithImports.length < 2) {
|
|
924
998
|
return 1;
|
|
925
999
|
}
|
|
@@ -964,6 +1038,8 @@ function calculateDomainCohesion(exports2) {
|
|
|
964
1038
|
}
|
|
965
1039
|
function classifyFile(node, cohesionScore, domains) {
|
|
966
1040
|
const { exports: exports2, imports, linesOfCode, file } = node;
|
|
1041
|
+
void imports;
|
|
1042
|
+
void linesOfCode;
|
|
967
1043
|
if (isBarrelExport(node)) {
|
|
968
1044
|
return "barrel-export";
|
|
969
1045
|
}
|
|
@@ -1042,8 +1118,12 @@ function isTypeDefinitionFile(node) {
|
|
|
1042
1118
|
const isTypesFile = fileName?.includes("types") || fileName?.includes(".d.ts") || fileName === "types.ts" || fileName === "interfaces.ts";
|
|
1043
1119
|
const lowerPath = file.toLowerCase();
|
|
1044
1120
|
const isTypesPath = lowerPath.includes("/types/") || lowerPath.includes("/typings/") || lowerPath.includes("/@types/") || lowerPath.startsWith("types/") || lowerPath.startsWith("typings/");
|
|
1045
|
-
const typeExports = exports2.filter(
|
|
1046
|
-
|
|
1121
|
+
const typeExports = exports2.filter(
|
|
1122
|
+
(e) => e.type === "type" || e.type === "interface"
|
|
1123
|
+
);
|
|
1124
|
+
const runtimeExports = exports2.filter(
|
|
1125
|
+
(e) => e.type === "function" || e.type === "class" || e.type === "const"
|
|
1126
|
+
);
|
|
1047
1127
|
const mostlyTypes = exports2.length > 0 && typeExports.length > runtimeExports.length && typeExports.length / exports2.length > 0.7;
|
|
1048
1128
|
const pureTypeFile = exports2.length > 0 && typeExports.length === exports2.length;
|
|
1049
1129
|
const emptyOrReExportInTypesDir = isTypesPath && exports2.length === 0;
|
|
@@ -1224,12 +1304,7 @@ function isLambdaHandler(node) {
|
|
|
1224
1304
|
function isServiceFile(node) {
|
|
1225
1305
|
const { file, exports: exports2 } = node;
|
|
1226
1306
|
const fileName = file.split("/").pop()?.toLowerCase();
|
|
1227
|
-
const servicePatterns = [
|
|
1228
|
-
"service",
|
|
1229
|
-
".service.",
|
|
1230
|
-
"-service.",
|
|
1231
|
-
"_service."
|
|
1232
|
-
];
|
|
1307
|
+
const servicePatterns = ["service", ".service.", "-service.", "_service."];
|
|
1233
1308
|
const isServiceName = servicePatterns.some(
|
|
1234
1309
|
(pattern) => fileName?.includes(pattern)
|
|
1235
1310
|
);
|
|
@@ -1337,7 +1412,15 @@ function isNextJsPage(node) {
|
|
|
1337
1412
|
}
|
|
1338
1413
|
const exportNames = exports2.map((e) => e.name.toLowerCase());
|
|
1339
1414
|
const hasDefaultExport = exports2.some((e) => e.type === "default");
|
|
1340
|
-
const nextJsExports = [
|
|
1415
|
+
const nextJsExports = [
|
|
1416
|
+
"metadata",
|
|
1417
|
+
"generatemetadata",
|
|
1418
|
+
"faqjsonld",
|
|
1419
|
+
"jsonld",
|
|
1420
|
+
"icon",
|
|
1421
|
+
"viewport",
|
|
1422
|
+
"dynamic"
|
|
1423
|
+
];
|
|
1341
1424
|
const hasNextJsExports = exportNames.some(
|
|
1342
1425
|
(name) => nextJsExports.includes(name) || name.includes("jsonld")
|
|
1343
1426
|
);
|
|
@@ -1411,13 +1494,44 @@ function hasRelatedExportNames(exportNames) {
|
|
|
1411
1494
|
const stems = /* @__PURE__ */ new Set();
|
|
1412
1495
|
const domains = /* @__PURE__ */ new Set();
|
|
1413
1496
|
for (const name of exportNames) {
|
|
1414
|
-
const verbs = [
|
|
1497
|
+
const verbs = [
|
|
1498
|
+
"get",
|
|
1499
|
+
"set",
|
|
1500
|
+
"create",
|
|
1501
|
+
"update",
|
|
1502
|
+
"delete",
|
|
1503
|
+
"fetch",
|
|
1504
|
+
"save",
|
|
1505
|
+
"load",
|
|
1506
|
+
"parse",
|
|
1507
|
+
"format",
|
|
1508
|
+
"validate",
|
|
1509
|
+
"convert",
|
|
1510
|
+
"transform",
|
|
1511
|
+
"build",
|
|
1512
|
+
"generate",
|
|
1513
|
+
"render",
|
|
1514
|
+
"send",
|
|
1515
|
+
"receive"
|
|
1516
|
+
];
|
|
1415
1517
|
for (const verb of verbs) {
|
|
1416
1518
|
if (name.startsWith(verb) && name.length > verb.length) {
|
|
1417
1519
|
stems.add(name.slice(verb.length).toLowerCase());
|
|
1418
1520
|
}
|
|
1419
1521
|
}
|
|
1420
|
-
const domainPatterns = [
|
|
1522
|
+
const domainPatterns = [
|
|
1523
|
+
"user",
|
|
1524
|
+
"order",
|
|
1525
|
+
"product",
|
|
1526
|
+
"session",
|
|
1527
|
+
"email",
|
|
1528
|
+
"file",
|
|
1529
|
+
"db",
|
|
1530
|
+
"s3",
|
|
1531
|
+
"dynamo",
|
|
1532
|
+
"api",
|
|
1533
|
+
"config"
|
|
1534
|
+
];
|
|
1421
1535
|
for (const domain of domainPatterns) {
|
|
1422
1536
|
if (name.includes(domain)) {
|
|
1423
1537
|
domains.add(domain);
|
|
@@ -1475,11 +1589,15 @@ function hasRelatedExportNames(exportNames) {
|
|
|
1475
1589
|
"all"
|
|
1476
1590
|
]);
|
|
1477
1591
|
const singularize2 = (w) => w.endsWith("s") && w.length > 3 ? w.slice(0, -1) : w;
|
|
1478
|
-
return new Set(
|
|
1592
|
+
return new Set(
|
|
1593
|
+
tokens.filter((t) => !skip.has(t) && t.length > 2).map(singularize2)
|
|
1594
|
+
);
|
|
1479
1595
|
});
|
|
1480
1596
|
if (nounSets.length >= 2 && nounSets.every((s) => s.size > 0)) {
|
|
1481
1597
|
const [first, ...rest] = nounSets;
|
|
1482
|
-
const commonNouns = Array.from(first).filter(
|
|
1598
|
+
const commonNouns = Array.from(first).filter(
|
|
1599
|
+
(n) => rest.every((s) => s.has(n))
|
|
1600
|
+
);
|
|
1483
1601
|
if (commonNouns.length > 0) return true;
|
|
1484
1602
|
}
|
|
1485
1603
|
return false;
|
|
@@ -1624,7 +1742,10 @@ function calculateContextScore(summary, costConfig) {
|
|
|
1624
1742
|
}
|
|
1625
1743
|
const recommendations = [];
|
|
1626
1744
|
if (avgContextBudget > 1e4) {
|
|
1627
|
-
const estimatedImpact = Math.min(
|
|
1745
|
+
const estimatedImpact = Math.min(
|
|
1746
|
+
15,
|
|
1747
|
+
Math.round((avgContextBudget - 1e4) / 1e3)
|
|
1748
|
+
);
|
|
1628
1749
|
recommendations.push({
|
|
1629
1750
|
action: "Reduce file dependencies to lower context requirements",
|
|
1630
1751
|
estimatedImpact,
|
|
@@ -1640,7 +1761,10 @@ function calculateContextScore(summary, costConfig) {
|
|
|
1640
1761
|
});
|
|
1641
1762
|
}
|
|
1642
1763
|
if (avgFragmentation > 0.5) {
|
|
1643
|
-
const estimatedImpact = Math.min(
|
|
1764
|
+
const estimatedImpact = Math.min(
|
|
1765
|
+
12,
|
|
1766
|
+
Math.round((avgFragmentation - 0.5) * 40)
|
|
1767
|
+
);
|
|
1644
1768
|
recommendations.push({
|
|
1645
1769
|
action: "Consolidate related code into cohesive modules",
|
|
1646
1770
|
estimatedImpact,
|
|
@@ -1742,21 +1866,29 @@ async function analyzeContext(options) {
|
|
|
1742
1866
|
// Only add node_modules to exclude if includeNodeModules is false
|
|
1743
1867
|
// The DEFAULT_EXCLUDE already includes node_modules, so this is only needed
|
|
1744
1868
|
// if user overrides the default exclude list
|
|
1745
|
-
exclude: includeNodeModules && scanOptions.exclude ? scanOptions.exclude.filter(
|
|
1869
|
+
exclude: includeNodeModules && scanOptions.exclude ? scanOptions.exclude.filter(
|
|
1870
|
+
(pattern) => pattern !== "**/node_modules/**"
|
|
1871
|
+
) : scanOptions.exclude
|
|
1746
1872
|
});
|
|
1747
1873
|
const pythonFiles = files.filter((f) => f.toLowerCase().endsWith(".py"));
|
|
1748
1874
|
const tsJsFiles = files.filter((f) => !f.toLowerCase().endsWith(".py"));
|
|
1875
|
+
void tsJsFiles;
|
|
1749
1876
|
const fileContents = await Promise.all(
|
|
1750
1877
|
files.map(async (file) => ({
|
|
1751
1878
|
file,
|
|
1752
1879
|
content: await (0, import_core4.readFileContent)(file)
|
|
1753
1880
|
}))
|
|
1754
1881
|
);
|
|
1755
|
-
const graph = buildDependencyGraph(
|
|
1882
|
+
const graph = buildDependencyGraph(
|
|
1883
|
+
fileContents.filter((f) => !f.file.toLowerCase().endsWith(".py"))
|
|
1884
|
+
);
|
|
1756
1885
|
let pythonResults = [];
|
|
1757
1886
|
if (pythonFiles.length > 0) {
|
|
1758
1887
|
const { analyzePythonContext: analyzePythonContext2 } = await Promise.resolve().then(() => (init_python_context(), python_context_exports));
|
|
1759
|
-
const pythonMetrics = await analyzePythonContext2(
|
|
1888
|
+
const pythonMetrics = await analyzePythonContext2(
|
|
1889
|
+
pythonFiles,
|
|
1890
|
+
scanOptions.rootDir || options.rootDir || "."
|
|
1891
|
+
);
|
|
1760
1892
|
pythonResults = pythonMetrics.map((metric) => {
|
|
1761
1893
|
const { severity, issues, recommendations, potentialSavings } = analyzeIssues({
|
|
1762
1894
|
file: metric.file,
|
|
@@ -1769,17 +1901,25 @@ async function analyzeContext(options) {
|
|
|
1769
1901
|
maxContextBudget,
|
|
1770
1902
|
minCohesion,
|
|
1771
1903
|
maxFragmentation,
|
|
1772
|
-
circularDeps: metric.metrics.circularDependencies.map(
|
|
1904
|
+
circularDeps: metric.metrics.circularDependencies.map(
|
|
1905
|
+
(cycle) => cycle.split(" \u2192 ")
|
|
1906
|
+
)
|
|
1773
1907
|
});
|
|
1774
1908
|
return {
|
|
1775
1909
|
file: metric.file,
|
|
1776
|
-
tokenCost: Math.floor(
|
|
1910
|
+
tokenCost: Math.floor(
|
|
1911
|
+
metric.contextBudget / (1 + metric.imports.length || 1)
|
|
1912
|
+
),
|
|
1777
1913
|
// Estimate
|
|
1778
1914
|
linesOfCode: metric.metrics.linesOfCode,
|
|
1779
1915
|
importDepth: metric.importDepth,
|
|
1780
1916
|
dependencyCount: metric.imports.length,
|
|
1781
|
-
dependencyList: metric.imports.map(
|
|
1782
|
-
|
|
1917
|
+
dependencyList: metric.imports.map(
|
|
1918
|
+
(imp) => imp.resolvedPath || imp.source
|
|
1919
|
+
),
|
|
1920
|
+
circularDeps: metric.metrics.circularDependencies.map(
|
|
1921
|
+
(cycle) => cycle.split(" \u2192 ")
|
|
1922
|
+
),
|
|
1783
1923
|
cohesionScore: metric.cohesion,
|
|
1784
1924
|
domains: ["python"],
|
|
1785
1925
|
// Generic for now
|
|
@@ -1812,7 +1952,9 @@ async function analyzeContext(options) {
|
|
|
1812
1952
|
const importDepth = focus === "depth" || focus === "all" ? calculateImportDepth(file, graph) : 0;
|
|
1813
1953
|
const dependencyList = focus === "depth" || focus === "all" ? getTransitiveDependencies(file, graph) : [];
|
|
1814
1954
|
const contextBudget = focus === "all" ? calculateContextBudget(file, graph) : node.tokenCost;
|
|
1815
|
-
const cohesionScore = focus === "cohesion" || focus === "all" ? calculateCohesion(node.exports, file, {
|
|
1955
|
+
const cohesionScore = focus === "cohesion" || focus === "all" ? calculateCohesion(node.exports, file, {
|
|
1956
|
+
coUsageMatrix: graph.coUsageMatrix
|
|
1957
|
+
}) : 1;
|
|
1816
1958
|
const fragmentationScore = fragmentationMap.get(file) || 0;
|
|
1817
1959
|
const relatedFiles = [];
|
|
1818
1960
|
for (const cluster of clusters) {
|
|
@@ -1833,6 +1975,10 @@ async function analyzeContext(options) {
|
|
|
1833
1975
|
maxFragmentation,
|
|
1834
1976
|
circularDeps
|
|
1835
1977
|
});
|
|
1978
|
+
void severity;
|
|
1979
|
+
void issues;
|
|
1980
|
+
void recommendations;
|
|
1981
|
+
void potentialSavings;
|
|
1836
1982
|
const domains = [
|
|
1837
1983
|
...new Set(node.exports.map((e) => e.inferredDomain || "unknown"))
|
|
1838
1984
|
];
|
|
@@ -1887,7 +2033,10 @@ async function analyzeContext(options) {
|
|
|
1887
2033
|
fileClassification,
|
|
1888
2034
|
severity: adjustedSeverity,
|
|
1889
2035
|
issues: adjustedIssues,
|
|
1890
|
-
recommendations: [
|
|
2036
|
+
recommendations: [
|
|
2037
|
+
...finalRecommendations,
|
|
2038
|
+
...classificationRecommendations.slice(0, 1)
|
|
2039
|
+
],
|
|
1891
2040
|
potentialSavings: adjustedSavings
|
|
1892
2041
|
});
|
|
1893
2042
|
}
|
|
@@ -1966,7 +2115,10 @@ function generateSummary(results) {
|
|
|
1966
2115
|
let importPairs = 0;
|
|
1967
2116
|
for (let i = 0; i < files.length; i++) {
|
|
1968
2117
|
for (let j = i + 1; j < files.length; j++) {
|
|
1969
|
-
importSimTotal += jaccard2(
|
|
2118
|
+
importSimTotal += jaccard2(
|
|
2119
|
+
files[i].dependencyList || [],
|
|
2120
|
+
files[j].dependencyList || []
|
|
2121
|
+
);
|
|
1970
2122
|
importPairs++;
|
|
1971
2123
|
}
|
|
1972
2124
|
}
|
|
@@ -1993,7 +2145,9 @@ function generateSummary(results) {
|
|
|
1993
2145
|
fragmentedModules.sort((a, b) => b.fragmentationScore - a.fragmentationScore);
|
|
1994
2146
|
const avgCohesion = results.reduce((sum, r) => sum + r.cohesionScore, 0) / totalFiles;
|
|
1995
2147
|
const lowCohesionFiles = results.filter((r) => r.cohesionScore < 0.6).map((r) => ({ file: r.file, score: r.cohesionScore })).sort((a, b) => a.score - b.score).slice(0, 10);
|
|
1996
|
-
const criticalIssues = results.filter(
|
|
2148
|
+
const criticalIssues = results.filter(
|
|
2149
|
+
(r) => r.severity === "critical"
|
|
2150
|
+
).length;
|
|
1997
2151
|
const majorIssues = results.filter((r) => r.severity === "major").length;
|
|
1998
2152
|
const minorIssues = results.filter((r) => r.severity === "minor").length;
|
|
1999
2153
|
const totalPotentialSavings = results.reduce(
|
|
@@ -2043,10 +2197,10 @@ function analyzeIssues(params) {
|
|
|
2043
2197
|
let potentialSavings = 0;
|
|
2044
2198
|
if (circularDeps.length > 0) {
|
|
2045
2199
|
severity = "critical";
|
|
2046
|
-
issues.push(
|
|
2047
|
-
|
|
2200
|
+
issues.push(`Part of ${circularDeps.length} circular dependency chain(s)`);
|
|
2201
|
+
recommendations.push(
|
|
2202
|
+
"Break circular dependencies by extracting interfaces or using dependency injection"
|
|
2048
2203
|
);
|
|
2049
|
-
recommendations.push("Break circular dependencies by extracting interfaces or using dependency injection");
|
|
2050
2204
|
potentialSavings += contextBudget * 0.2;
|
|
2051
2205
|
}
|
|
2052
2206
|
if (importDepth > maxDepth * 1.5) {
|
|
@@ -2056,25 +2210,37 @@ function analyzeIssues(params) {
|
|
|
2056
2210
|
potentialSavings += contextBudget * 0.3;
|
|
2057
2211
|
} else if (importDepth > maxDepth) {
|
|
2058
2212
|
severity = severity === "critical" ? "critical" : "major";
|
|
2059
|
-
issues.push(
|
|
2213
|
+
issues.push(
|
|
2214
|
+
`Import depth ${importDepth} exceeds recommended maximum ${maxDepth}`
|
|
2215
|
+
);
|
|
2060
2216
|
recommendations.push("Consider reducing dependency depth");
|
|
2061
2217
|
potentialSavings += contextBudget * 0.15;
|
|
2062
2218
|
}
|
|
2063
2219
|
if (contextBudget > maxContextBudget * 1.5) {
|
|
2064
2220
|
severity = severity === "critical" ? "critical" : "critical";
|
|
2065
|
-
issues.push(
|
|
2066
|
-
|
|
2221
|
+
issues.push(
|
|
2222
|
+
`Context budget ${contextBudget.toLocaleString()} tokens is 50% over limit`
|
|
2223
|
+
);
|
|
2224
|
+
recommendations.push(
|
|
2225
|
+
"Split into smaller modules or reduce dependency tree"
|
|
2226
|
+
);
|
|
2067
2227
|
potentialSavings += contextBudget * 0.4;
|
|
2068
2228
|
} else if (contextBudget > maxContextBudget) {
|
|
2069
2229
|
severity = severity === "critical" || severity === "major" ? severity : "major";
|
|
2070
|
-
issues.push(
|
|
2230
|
+
issues.push(
|
|
2231
|
+
`Context budget ${contextBudget.toLocaleString()} exceeds ${maxContextBudget.toLocaleString()}`
|
|
2232
|
+
);
|
|
2071
2233
|
recommendations.push("Reduce file size or dependencies");
|
|
2072
2234
|
potentialSavings += contextBudget * 0.2;
|
|
2073
2235
|
}
|
|
2074
2236
|
if (cohesionScore < minCohesion * 0.5) {
|
|
2075
2237
|
severity = severity === "critical" ? "critical" : "major";
|
|
2076
|
-
issues.push(
|
|
2077
|
-
|
|
2238
|
+
issues.push(
|
|
2239
|
+
`Very low cohesion (${(cohesionScore * 100).toFixed(0)}%) - mixed concerns`
|
|
2240
|
+
);
|
|
2241
|
+
recommendations.push(
|
|
2242
|
+
"Split file by domain - separate unrelated functionality"
|
|
2243
|
+
);
|
|
2078
2244
|
potentialSavings += contextBudget * 0.25;
|
|
2079
2245
|
} else if (cohesionScore < minCohesion) {
|
|
2080
2246
|
severity = severity === "critical" || severity === "major" ? severity : "minor";
|
|
@@ -2084,7 +2250,9 @@ function analyzeIssues(params) {
|
|
|
2084
2250
|
}
|
|
2085
2251
|
if (fragmentationScore > maxFragmentation) {
|
|
2086
2252
|
severity = severity === "critical" || severity === "major" ? severity : "minor";
|
|
2087
|
-
issues.push(
|
|
2253
|
+
issues.push(
|
|
2254
|
+
`High fragmentation (${(fragmentationScore * 100).toFixed(0)}%) - scattered implementation`
|
|
2255
|
+
);
|
|
2088
2256
|
recommendations.push("Consolidate with related files in same domain");
|
|
2089
2257
|
potentialSavings += contextBudget * 0.3;
|
|
2090
2258
|
}
|
|
@@ -2094,11 +2262,18 @@ function analyzeIssues(params) {
|
|
|
2094
2262
|
}
|
|
2095
2263
|
if (isBuildArtifact(file)) {
|
|
2096
2264
|
issues.push("Detected build artifact (bundled/output file)");
|
|
2097
|
-
recommendations.push(
|
|
2265
|
+
recommendations.push(
|
|
2266
|
+
"Exclude build outputs (e.g., cdk.out, dist, build, .next) from analysis"
|
|
2267
|
+
);
|
|
2098
2268
|
severity = downgradeSeverity(severity);
|
|
2099
2269
|
potentialSavings = 0;
|
|
2100
2270
|
}
|
|
2101
|
-
return {
|
|
2271
|
+
return {
|
|
2272
|
+
severity,
|
|
2273
|
+
issues,
|
|
2274
|
+
recommendations,
|
|
2275
|
+
potentialSavings: Math.floor(potentialSavings)
|
|
2276
|
+
};
|
|
2102
2277
|
}
|
|
2103
2278
|
function isBuildArtifact(filePath) {
|
|
2104
2279
|
const lower = filePath.toLowerCase();
|