@aiready/context-analyzer 0.19.17 → 0.19.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +10 -10
- package/.turbo/turbo-lint.log +2 -22
- package/.turbo/turbo-test.log +23 -23
- package/coverage/analyzer.ts.html +1369 -0
- package/coverage/ast-utils.ts.html +382 -0
- package/coverage/base.css +224 -0
- package/coverage/block-navigation.js +87 -0
- package/coverage/classifier.ts.html +1771 -0
- package/coverage/clover.xml +1245 -0
- package/coverage/cluster-detector.ts.html +385 -0
- package/coverage/coverage-final.json +15 -0
- package/coverage/defaults.ts.html +262 -0
- package/coverage/favicon.png +0 -0
- package/coverage/graph-builder.ts.html +859 -0
- package/coverage/index.html +311 -0
- package/coverage/index.ts.html +124 -0
- package/coverage/metrics.ts.html +748 -0
- package/coverage/prettify.css +1 -0
- package/coverage/prettify.js +2 -0
- package/coverage/provider.ts.html +283 -0
- package/coverage/remediation.ts.html +502 -0
- package/coverage/scoring.ts.html +619 -0
- package/coverage/semantic-analysis.ts.html +1201 -0
- package/coverage/sort-arrow-sprite.png +0 -0
- package/coverage/sorter.js +210 -0
- package/coverage/summary.ts.html +625 -0
- package/coverage/types.ts.html +571 -0
- package/dist/chunk-736QSHJP.mjs +1807 -0
- package/dist/chunk-CCBNKQYB.mjs +1812 -0
- package/dist/chunk-JUHHOSHG.mjs +1808 -0
- package/dist/cli.js +393 -379
- package/dist/cli.mjs +1 -1
- package/dist/index.d.mts +65 -6
- package/dist/index.d.ts +65 -6
- package/dist/index.js +396 -380
- package/dist/index.mjs +3 -1
- package/package.json +2 -2
- package/src/__tests__/cluster-detector.test.ts +138 -0
- package/src/__tests__/provider.test.ts +78 -0
- package/src/__tests__/remediation.test.ts +94 -0
- package/src/analyzer.ts +244 -1
- package/src/classifier.ts +100 -35
- package/src/index.ts +1 -242
- package/src/provider.ts +2 -1
package/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_core5.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_core5.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_core5.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_core5, 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_core5 = require("@aiready/core");
|
|
239
239
|
import_path = require("path");
|
|
240
240
|
import_fs = __toESM(require("fs"));
|
|
241
241
|
}
|
|
@@ -244,6 +244,7 @@ var init_python_context = __esm({
|
|
|
244
244
|
// src/index.ts
|
|
245
245
|
var index_exports = {};
|
|
246
246
|
__export(index_exports, {
|
|
247
|
+
Classification: () => Classification,
|
|
247
248
|
ContextAnalyzerProvider: () => ContextAnalyzerProvider,
|
|
248
249
|
adjustCohesionForClassification: () => adjustCohesionForClassification,
|
|
249
250
|
adjustFragmentationForClassification: () => adjustFragmentationForClassification,
|
|
@@ -292,8 +293,11 @@ __export(index_exports, {
|
|
|
292
293
|
module.exports = __toCommonJS(index_exports);
|
|
293
294
|
var import_core10 = require("@aiready/core");
|
|
294
295
|
|
|
296
|
+
// src/provider.ts
|
|
297
|
+
var import_core8 = require("@aiready/core");
|
|
298
|
+
|
|
295
299
|
// src/analyzer.ts
|
|
296
|
-
var
|
|
300
|
+
var import_core6 = require("@aiready/core");
|
|
297
301
|
|
|
298
302
|
// src/metrics.ts
|
|
299
303
|
var import_core2 = require("@aiready/core");
|
|
@@ -725,8 +729,143 @@ function calculateDirectoryDistance(files) {
|
|
|
725
729
|
return comparisons > 0 ? totalNormalized / comparisons : 0;
|
|
726
730
|
}
|
|
727
731
|
|
|
728
|
-
// src/
|
|
732
|
+
// src/summary.ts
|
|
729
733
|
var import_core3 = require("@aiready/core");
|
|
734
|
+
function generateSummary(results, options) {
|
|
735
|
+
const config = options ? Object.fromEntries(
|
|
736
|
+
Object.entries(options).filter(
|
|
737
|
+
([key]) => !import_core3.GLOBAL_SCAN_OPTIONS.includes(key) || key === "rootDir"
|
|
738
|
+
)
|
|
739
|
+
) : void 0;
|
|
740
|
+
if (results.length === 0) {
|
|
741
|
+
return {
|
|
742
|
+
totalFiles: 0,
|
|
743
|
+
totalTokens: 0,
|
|
744
|
+
avgContextBudget: 0,
|
|
745
|
+
maxContextBudget: 0,
|
|
746
|
+
avgImportDepth: 0,
|
|
747
|
+
maxImportDepth: 0,
|
|
748
|
+
deepFiles: [],
|
|
749
|
+
avgFragmentation: 0,
|
|
750
|
+
fragmentedModules: [],
|
|
751
|
+
avgCohesion: 0,
|
|
752
|
+
lowCohesionFiles: [],
|
|
753
|
+
criticalIssues: 0,
|
|
754
|
+
majorIssues: 0,
|
|
755
|
+
minorIssues: 0,
|
|
756
|
+
totalPotentialSavings: 0,
|
|
757
|
+
topExpensiveFiles: [],
|
|
758
|
+
config
|
|
759
|
+
};
|
|
760
|
+
}
|
|
761
|
+
const totalFiles = results.length;
|
|
762
|
+
const totalTokens = results.reduce((sum, r) => sum + r.tokenCost, 0);
|
|
763
|
+
const totalContextBudget = results.reduce(
|
|
764
|
+
(sum, r) => sum + r.contextBudget,
|
|
765
|
+
0
|
|
766
|
+
);
|
|
767
|
+
const avgContextBudget = totalContextBudget / totalFiles;
|
|
768
|
+
const maxContextBudget = Math.max(...results.map((r) => r.contextBudget));
|
|
769
|
+
const avgImportDepth = results.reduce((sum, r) => sum + r.importDepth, 0) / totalFiles;
|
|
770
|
+
const maxImportDepth = Math.max(...results.map((r) => r.importDepth));
|
|
771
|
+
const deepFiles = results.filter((r) => r.importDepth >= 5).map((r) => ({ file: r.file, depth: r.importDepth })).sort((a, b) => b.depth - a.depth).slice(0, 10);
|
|
772
|
+
const avgFragmentation = results.reduce((sum, r) => sum + r.fragmentationScore, 0) / totalFiles;
|
|
773
|
+
const moduleMap = /* @__PURE__ */ new Map();
|
|
774
|
+
for (const result of results) {
|
|
775
|
+
for (const domain of result.domains) {
|
|
776
|
+
if (!moduleMap.has(domain)) moduleMap.set(domain, []);
|
|
777
|
+
moduleMap.get(domain).push(result);
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
const fragmentedModules = [];
|
|
781
|
+
for (const [domain, files] of moduleMap.entries()) {
|
|
782
|
+
let jaccard2 = function(a, b) {
|
|
783
|
+
const s1 = new Set(a || []);
|
|
784
|
+
const s2 = new Set(b || []);
|
|
785
|
+
if (s1.size === 0 && s2.size === 0) return 0;
|
|
786
|
+
const inter = new Set([...s1].filter((x) => s2.has(x)));
|
|
787
|
+
const uni = /* @__PURE__ */ new Set([...s1, ...s2]);
|
|
788
|
+
return uni.size === 0 ? 0 : inter.size / uni.size;
|
|
789
|
+
};
|
|
790
|
+
var jaccard = jaccard2;
|
|
791
|
+
if (files.length < 2) continue;
|
|
792
|
+
const fragmentationScore = files.reduce((sum, f) => sum + f.fragmentationScore, 0) / files.length;
|
|
793
|
+
if (fragmentationScore < 0.3) continue;
|
|
794
|
+
const totalTokens2 = files.reduce((sum, f) => sum + f.tokenCost, 0);
|
|
795
|
+
const avgCohesion2 = files.reduce((sum, f) => sum + f.cohesionScore, 0) / files.length;
|
|
796
|
+
const targetFiles = Math.max(1, Math.ceil(files.length / 3));
|
|
797
|
+
const filePaths = files.map((f) => f.file);
|
|
798
|
+
const pathEntropy = calculatePathEntropy(filePaths);
|
|
799
|
+
const directoryDistance = calculateDirectoryDistance(filePaths);
|
|
800
|
+
let importSimTotal = 0;
|
|
801
|
+
let importPairs = 0;
|
|
802
|
+
for (let i = 0; i < files.length; i++) {
|
|
803
|
+
for (let j = i + 1; j < files.length; j++) {
|
|
804
|
+
importSimTotal += jaccard2(
|
|
805
|
+
files[i].dependencyList || [],
|
|
806
|
+
files[j].dependencyList || []
|
|
807
|
+
);
|
|
808
|
+
importPairs++;
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
const importCohesion = importPairs > 0 ? importSimTotal / importPairs : 0;
|
|
812
|
+
fragmentedModules.push({
|
|
813
|
+
domain,
|
|
814
|
+
files: files.map((f) => f.file),
|
|
815
|
+
totalTokens: totalTokens2,
|
|
816
|
+
fragmentationScore,
|
|
817
|
+
avgCohesion: avgCohesion2,
|
|
818
|
+
importCohesion,
|
|
819
|
+
pathEntropy,
|
|
820
|
+
directoryDistance,
|
|
821
|
+
suggestedStructure: {
|
|
822
|
+
targetFiles,
|
|
823
|
+
consolidationPlan: [
|
|
824
|
+
`Consolidate ${files.length} files across ${new Set(files.map((f) => f.file.split("/").slice(0, -1).join("/"))).size} directories`,
|
|
825
|
+
`Target ~${targetFiles} core modules to reduce context switching`
|
|
826
|
+
]
|
|
827
|
+
}
|
|
828
|
+
});
|
|
829
|
+
}
|
|
830
|
+
const avgCohesion = results.reduce((sum, r) => sum + r.cohesionScore, 0) / totalFiles;
|
|
831
|
+
const lowCohesionFiles = results.filter((r) => r.cohesionScore < 0.4).map((r) => ({ file: r.file, score: r.cohesionScore })).sort((a, b) => a.score - b.score).slice(0, 10);
|
|
832
|
+
const criticalIssues = results.filter(
|
|
833
|
+
(r) => r.severity === "critical"
|
|
834
|
+
).length;
|
|
835
|
+
const majorIssues = results.filter((r) => r.severity === "major").length;
|
|
836
|
+
const minorIssues = results.filter((r) => r.severity === "minor").length;
|
|
837
|
+
const totalPotentialSavings = results.reduce(
|
|
838
|
+
(sum, r) => sum + r.potentialSavings,
|
|
839
|
+
0
|
|
840
|
+
);
|
|
841
|
+
const topExpensiveFiles = results.sort((a, b) => b.contextBudget - a.contextBudget).slice(0, 10).map((r) => ({
|
|
842
|
+
file: r.file,
|
|
843
|
+
contextBudget: r.contextBudget,
|
|
844
|
+
severity: r.severity
|
|
845
|
+
}));
|
|
846
|
+
return {
|
|
847
|
+
totalFiles,
|
|
848
|
+
totalTokens,
|
|
849
|
+
avgContextBudget,
|
|
850
|
+
maxContextBudget,
|
|
851
|
+
avgImportDepth,
|
|
852
|
+
maxImportDepth,
|
|
853
|
+
deepFiles,
|
|
854
|
+
avgFragmentation,
|
|
855
|
+
fragmentedModules,
|
|
856
|
+
avgCohesion,
|
|
857
|
+
lowCohesionFiles,
|
|
858
|
+
criticalIssues,
|
|
859
|
+
majorIssues,
|
|
860
|
+
minorIssues,
|
|
861
|
+
totalPotentialSavings,
|
|
862
|
+
topExpensiveFiles,
|
|
863
|
+
config
|
|
864
|
+
};
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
// src/graph-builder.ts
|
|
868
|
+
var import_core4 = require("@aiready/core");
|
|
730
869
|
function extractDomainKeywordsFromPaths(files) {
|
|
731
870
|
const folderNames = /* @__PURE__ */ new Set();
|
|
732
871
|
for (const { file } of files) {
|
|
@@ -778,7 +917,7 @@ function buildDependencyGraph(files, options) {
|
|
|
778
917
|
const edges = /* @__PURE__ */ new Map();
|
|
779
918
|
const autoDetectedKeywords = options?.domainKeywords ?? extractDomainKeywordsFromPaths(files);
|
|
780
919
|
for (const { file, content } of files) {
|
|
781
|
-
const { imports: astImports } = (0,
|
|
920
|
+
const { imports: astImports } = (0, import_core4.parseFileExports)(content, file);
|
|
782
921
|
const importSources = astImports.map((i) => i.source);
|
|
783
922
|
const exports2 = extractExportsWithAST(
|
|
784
923
|
content,
|
|
@@ -786,7 +925,7 @@ function buildDependencyGraph(files, options) {
|
|
|
786
925
|
{ domainKeywords: autoDetectedKeywords },
|
|
787
926
|
importSources
|
|
788
927
|
);
|
|
789
|
-
const tokenCost = (0,
|
|
928
|
+
const tokenCost = (0, import_core4.estimateTokens)(content);
|
|
790
929
|
const linesOfCode = content.split("\n").length;
|
|
791
930
|
nodes.set(file, {
|
|
792
931
|
file,
|
|
@@ -893,48 +1032,62 @@ function detectCircularDependencies(graph) {
|
|
|
893
1032
|
}
|
|
894
1033
|
|
|
895
1034
|
// src/classifier.ts
|
|
1035
|
+
var Classification = {
|
|
1036
|
+
BARREL: "barrel-export",
|
|
1037
|
+
TYPE_DEFINITION: "type-definition",
|
|
1038
|
+
NEXTJS_PAGE: "nextjs-page",
|
|
1039
|
+
LAMBDA_HANDLER: "lambda-handler",
|
|
1040
|
+
SERVICE: "service-file",
|
|
1041
|
+
EMAIL_TEMPLATE: "email-template",
|
|
1042
|
+
PARSER: "parser-file",
|
|
1043
|
+
COHESIVE_MODULE: "cohesive-module",
|
|
1044
|
+
UTILITY_MODULE: "utility-module",
|
|
1045
|
+
MIXED_CONCERNS: "mixed-concerns",
|
|
1046
|
+
UNKNOWN: "unknown"
|
|
1047
|
+
};
|
|
896
1048
|
function classifyFile(node, cohesionScore = 1, domains = []) {
|
|
897
1049
|
if (isBarrelExport(node)) {
|
|
898
|
-
return
|
|
1050
|
+
return Classification.BARREL;
|
|
899
1051
|
}
|
|
900
1052
|
if (isTypeDefinition(node)) {
|
|
901
|
-
return
|
|
1053
|
+
return Classification.TYPE_DEFINITION;
|
|
902
1054
|
}
|
|
903
1055
|
if (isNextJsPage(node)) {
|
|
904
|
-
return
|
|
1056
|
+
return Classification.NEXTJS_PAGE;
|
|
905
1057
|
}
|
|
906
1058
|
if (isLambdaHandler(node)) {
|
|
907
|
-
return
|
|
1059
|
+
return Classification.LAMBDA_HANDLER;
|
|
908
1060
|
}
|
|
909
1061
|
if (isServiceFile(node)) {
|
|
910
|
-
return
|
|
1062
|
+
return Classification.SERVICE;
|
|
911
1063
|
}
|
|
912
1064
|
if (isEmailTemplate(node)) {
|
|
913
|
-
return
|
|
1065
|
+
return Classification.EMAIL_TEMPLATE;
|
|
914
1066
|
}
|
|
915
1067
|
if (isParserFile(node)) {
|
|
916
|
-
return
|
|
1068
|
+
return Classification.PARSER;
|
|
917
1069
|
}
|
|
918
1070
|
if (isSessionFile(node)) {
|
|
919
|
-
if (cohesionScore >= 0.25 && domains.length <= 1)
|
|
920
|
-
|
|
1071
|
+
if (cohesionScore >= 0.25 && domains.length <= 1)
|
|
1072
|
+
return Classification.COHESIVE_MODULE;
|
|
1073
|
+
return Classification.UTILITY_MODULE;
|
|
921
1074
|
}
|
|
922
1075
|
if (isUtilityModule(node)) {
|
|
923
|
-
return
|
|
1076
|
+
return Classification.UTILITY_MODULE;
|
|
924
1077
|
}
|
|
925
1078
|
if (isConfigFile(node)) {
|
|
926
|
-
return
|
|
1079
|
+
return Classification.COHESIVE_MODULE;
|
|
927
1080
|
}
|
|
928
1081
|
if (domains.length <= 1 && domains[0] !== "unknown") {
|
|
929
|
-
return
|
|
1082
|
+
return Classification.COHESIVE_MODULE;
|
|
930
1083
|
}
|
|
931
1084
|
if (domains.length > 1 && cohesionScore < 0.4) {
|
|
932
|
-
return
|
|
1085
|
+
return Classification.MIXED_CONCERNS;
|
|
933
1086
|
}
|
|
934
1087
|
if (cohesionScore >= 0.7) {
|
|
935
|
-
return
|
|
1088
|
+
return Classification.COHESIVE_MODULE;
|
|
936
1089
|
}
|
|
937
|
-
return
|
|
1090
|
+
return Classification.UNKNOWN;
|
|
938
1091
|
}
|
|
939
1092
|
function isBarrelExport(node) {
|
|
940
1093
|
const { file, exports: exports2 } = node;
|
|
@@ -1097,13 +1250,13 @@ function isNextJsPage(node) {
|
|
|
1097
1250
|
}
|
|
1098
1251
|
function adjustCohesionForClassification(baseCohesion, classification, node) {
|
|
1099
1252
|
switch (classification) {
|
|
1100
|
-
case
|
|
1253
|
+
case Classification.BARREL:
|
|
1101
1254
|
return 1;
|
|
1102
|
-
case
|
|
1255
|
+
case Classification.TYPE_DEFINITION:
|
|
1103
1256
|
return 1;
|
|
1104
|
-
case
|
|
1257
|
+
case Classification.NEXTJS_PAGE:
|
|
1105
1258
|
return 1;
|
|
1106
|
-
case
|
|
1259
|
+
case Classification.UTILITY_MODULE: {
|
|
1107
1260
|
if (node && hasRelatedExportNames(
|
|
1108
1261
|
(node.exports || []).map((e) => e.name.toLowerCase())
|
|
1109
1262
|
)) {
|
|
@@ -1111,17 +1264,17 @@ function adjustCohesionForClassification(baseCohesion, classification, node) {
|
|
|
1111
1264
|
}
|
|
1112
1265
|
return Math.max(0.75, Math.min(1, baseCohesion + 0.35));
|
|
1113
1266
|
}
|
|
1114
|
-
case
|
|
1267
|
+
case Classification.SERVICE:
|
|
1115
1268
|
return Math.max(0.72, Math.min(1, baseCohesion + 0.3));
|
|
1116
|
-
case
|
|
1269
|
+
case Classification.LAMBDA_HANDLER:
|
|
1117
1270
|
return Math.max(0.75, Math.min(1, baseCohesion + 0.35));
|
|
1118
|
-
case
|
|
1271
|
+
case Classification.EMAIL_TEMPLATE:
|
|
1119
1272
|
return Math.max(0.72, Math.min(1, baseCohesion + 0.3));
|
|
1120
|
-
case
|
|
1273
|
+
case Classification.PARSER:
|
|
1121
1274
|
return Math.max(0.7, Math.min(1, baseCohesion + 0.3));
|
|
1122
|
-
case
|
|
1275
|
+
case Classification.COHESIVE_MODULE:
|
|
1123
1276
|
return Math.max(baseCohesion, 0.7);
|
|
1124
|
-
case
|
|
1277
|
+
case Classification.MIXED_CONCERNS:
|
|
1125
1278
|
return baseCohesion;
|
|
1126
1279
|
default:
|
|
1127
1280
|
return Math.min(1, baseCohesion + 0.1);
|
|
@@ -1170,20 +1323,20 @@ function hasRelatedExportNames(exportNames) {
|
|
|
1170
1323
|
}
|
|
1171
1324
|
function adjustFragmentationForClassification(baseFragmentation, classification) {
|
|
1172
1325
|
switch (classification) {
|
|
1173
|
-
case
|
|
1326
|
+
case Classification.BARREL:
|
|
1174
1327
|
return 0;
|
|
1175
|
-
case
|
|
1328
|
+
case Classification.TYPE_DEFINITION:
|
|
1176
1329
|
return 0;
|
|
1177
|
-
case
|
|
1178
|
-
case
|
|
1179
|
-
case
|
|
1180
|
-
case
|
|
1181
|
-
case
|
|
1182
|
-
case
|
|
1330
|
+
case Classification.UTILITY_MODULE:
|
|
1331
|
+
case Classification.SERVICE:
|
|
1332
|
+
case Classification.LAMBDA_HANDLER:
|
|
1333
|
+
case Classification.EMAIL_TEMPLATE:
|
|
1334
|
+
case Classification.PARSER:
|
|
1335
|
+
case Classification.NEXTJS_PAGE:
|
|
1183
1336
|
return baseFragmentation * 0.2;
|
|
1184
|
-
case
|
|
1337
|
+
case Classification.COHESIVE_MODULE:
|
|
1185
1338
|
return baseFragmentation * 0.3;
|
|
1186
|
-
case
|
|
1339
|
+
case Classification.MIXED_CONCERNS:
|
|
1187
1340
|
return baseFragmentation;
|
|
1188
1341
|
default:
|
|
1189
1342
|
return baseFragmentation * 0.7;
|
|
@@ -1394,10 +1547,10 @@ function analyzeIssues(params) {
|
|
|
1394
1547
|
} = params;
|
|
1395
1548
|
const issues = [];
|
|
1396
1549
|
const recommendations = [];
|
|
1397
|
-
let severity =
|
|
1550
|
+
let severity = import_core6.Severity.Info;
|
|
1398
1551
|
let potentialSavings = 0;
|
|
1399
1552
|
if (circularDeps.length > 0) {
|
|
1400
|
-
severity =
|
|
1553
|
+
severity = import_core6.Severity.Critical;
|
|
1401
1554
|
issues.push(`Part of ${circularDeps.length} circular dependency chain(s)`);
|
|
1402
1555
|
recommendations.push(
|
|
1403
1556
|
"Break circular dependencies by extracting interfaces or using dependency injection"
|
|
@@ -1405,12 +1558,12 @@ function analyzeIssues(params) {
|
|
|
1405
1558
|
potentialSavings += contextBudget * 0.2;
|
|
1406
1559
|
}
|
|
1407
1560
|
if (importDepth > maxDepth * 1.5) {
|
|
1408
|
-
severity =
|
|
1561
|
+
severity = import_core6.Severity.Critical;
|
|
1409
1562
|
issues.push(`Import depth ${importDepth} exceeds limit by 50%`);
|
|
1410
1563
|
recommendations.push("Flatten dependency tree or use facade pattern");
|
|
1411
1564
|
potentialSavings += contextBudget * 0.3;
|
|
1412
1565
|
} else if (importDepth > maxDepth) {
|
|
1413
|
-
if (severity !==
|
|
1566
|
+
if (severity !== import_core6.Severity.Critical) severity = import_core6.Severity.Major;
|
|
1414
1567
|
issues.push(
|
|
1415
1568
|
`Import depth ${importDepth} exceeds recommended maximum ${maxDepth}`
|
|
1416
1569
|
);
|
|
@@ -1418,7 +1571,7 @@ function analyzeIssues(params) {
|
|
|
1418
1571
|
potentialSavings += contextBudget * 0.15;
|
|
1419
1572
|
}
|
|
1420
1573
|
if (contextBudget > maxContextBudget * 1.5) {
|
|
1421
|
-
severity =
|
|
1574
|
+
severity = import_core6.Severity.Critical;
|
|
1422
1575
|
issues.push(
|
|
1423
1576
|
`Context budget ${contextBudget.toLocaleString()} tokens is 50% over limit`
|
|
1424
1577
|
);
|
|
@@ -1427,7 +1580,7 @@ function analyzeIssues(params) {
|
|
|
1427
1580
|
);
|
|
1428
1581
|
potentialSavings += contextBudget * 0.4;
|
|
1429
1582
|
} else if (contextBudget > maxContextBudget) {
|
|
1430
|
-
if (severity !==
|
|
1583
|
+
if (severity !== import_core6.Severity.Critical) severity = import_core6.Severity.Major;
|
|
1431
1584
|
issues.push(
|
|
1432
1585
|
`Context budget ${contextBudget.toLocaleString()} exceeds ${maxContextBudget.toLocaleString()}`
|
|
1433
1586
|
);
|
|
@@ -1435,7 +1588,7 @@ function analyzeIssues(params) {
|
|
|
1435
1588
|
potentialSavings += contextBudget * 0.2;
|
|
1436
1589
|
}
|
|
1437
1590
|
if (cohesionScore < minCohesion * 0.5) {
|
|
1438
|
-
if (severity !==
|
|
1591
|
+
if (severity !== import_core6.Severity.Critical) severity = import_core6.Severity.Major;
|
|
1439
1592
|
issues.push(
|
|
1440
1593
|
`Very low cohesion (${(cohesionScore * 100).toFixed(0)}%) - mixed concerns`
|
|
1441
1594
|
);
|
|
@@ -1444,14 +1597,14 @@ function analyzeIssues(params) {
|
|
|
1444
1597
|
);
|
|
1445
1598
|
potentialSavings += contextBudget * 0.25;
|
|
1446
1599
|
} else if (cohesionScore < minCohesion) {
|
|
1447
|
-
if (severity ===
|
|
1600
|
+
if (severity === import_core6.Severity.Info) severity = import_core6.Severity.Minor;
|
|
1448
1601
|
issues.push(`Low cohesion (${(cohesionScore * 100).toFixed(0)}%)`);
|
|
1449
1602
|
recommendations.push("Consider grouping related exports together");
|
|
1450
1603
|
potentialSavings += contextBudget * 0.1;
|
|
1451
1604
|
}
|
|
1452
1605
|
if (fragmentationScore > maxFragmentation) {
|
|
1453
|
-
if (severity ===
|
|
1454
|
-
severity =
|
|
1606
|
+
if (severity === import_core6.Severity.Info || severity === import_core6.Severity.Minor)
|
|
1607
|
+
severity = import_core6.Severity.Minor;
|
|
1455
1608
|
issues.push(
|
|
1456
1609
|
`High fragmentation (${(fragmentationScore * 100).toFixed(0)}%) - scattered implementation`
|
|
1457
1610
|
);
|
|
@@ -1465,7 +1618,7 @@ function analyzeIssues(params) {
|
|
|
1465
1618
|
if (isBuildArtifact(file)) {
|
|
1466
1619
|
issues.push("Detected build artifact (bundled/output file)");
|
|
1467
1620
|
recommendations.push("Exclude build outputs from analysis");
|
|
1468
|
-
severity =
|
|
1621
|
+
severity = import_core6.Severity.Info;
|
|
1469
1622
|
potentialSavings = 0;
|
|
1470
1623
|
}
|
|
1471
1624
|
return {
|
|
@@ -1479,147 +1632,192 @@ function isBuildArtifact(filePath) {
|
|
|
1479
1632
|
const lower = filePath.toLowerCase();
|
|
1480
1633
|
return lower.includes("/node_modules/") || lower.includes("/dist/") || lower.includes("/build/") || lower.includes("/out/") || lower.includes("/.next/");
|
|
1481
1634
|
}
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
criticalIssues: 0,
|
|
1505
|
-
majorIssues: 0,
|
|
1506
|
-
minorIssues: 0,
|
|
1507
|
-
totalPotentialSavings: 0,
|
|
1508
|
-
topExpensiveFiles: [],
|
|
1509
|
-
config
|
|
1510
|
-
};
|
|
1511
|
-
}
|
|
1512
|
-
const totalFiles = results.length;
|
|
1513
|
-
const totalTokens = results.reduce((sum, r) => sum + r.tokenCost, 0);
|
|
1514
|
-
const totalContextBudget = results.reduce(
|
|
1515
|
-
(sum, r) => sum + r.contextBudget,
|
|
1516
|
-
0
|
|
1635
|
+
async function analyzeContext(options) {
|
|
1636
|
+
const {
|
|
1637
|
+
maxDepth = 5,
|
|
1638
|
+
maxContextBudget = 1e4,
|
|
1639
|
+
minCohesion = 0.6,
|
|
1640
|
+
maxFragmentation = 0.5,
|
|
1641
|
+
focus = "all",
|
|
1642
|
+
includeNodeModules = false,
|
|
1643
|
+
...scanOptions
|
|
1644
|
+
} = options;
|
|
1645
|
+
const files = await (0, import_core6.scanFiles)({
|
|
1646
|
+
...scanOptions,
|
|
1647
|
+
exclude: includeNodeModules && scanOptions.exclude ? scanOptions.exclude.filter(
|
|
1648
|
+
(pattern) => pattern !== "**/node_modules/**"
|
|
1649
|
+
) : scanOptions.exclude
|
|
1650
|
+
});
|
|
1651
|
+
const pythonFiles = files.filter((f) => f.toLowerCase().endsWith(".py"));
|
|
1652
|
+
const fileContents = await Promise.all(
|
|
1653
|
+
files.map(async (file) => ({
|
|
1654
|
+
file,
|
|
1655
|
+
content: await (0, import_core6.readFileContent)(file)
|
|
1656
|
+
}))
|
|
1517
1657
|
);
|
|
1518
|
-
const
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1658
|
+
const graph = buildDependencyGraph(
|
|
1659
|
+
fileContents.filter((f) => !f.file.toLowerCase().endsWith(".py"))
|
|
1660
|
+
);
|
|
1661
|
+
let pythonResults = [];
|
|
1662
|
+
if (pythonFiles.length > 0) {
|
|
1663
|
+
const { analyzePythonContext: analyzePythonContext2 } = await Promise.resolve().then(() => (init_python_context(), python_context_exports));
|
|
1664
|
+
const pythonMetrics = await analyzePythonContext2(
|
|
1665
|
+
pythonFiles,
|
|
1666
|
+
scanOptions.rootDir || options.rootDir || "."
|
|
1667
|
+
);
|
|
1668
|
+
pythonResults = pythonMetrics.map((metric) => {
|
|
1669
|
+
const { severity, issues, recommendations, potentialSavings } = analyzeIssues({
|
|
1670
|
+
file: metric.file,
|
|
1671
|
+
importDepth: metric.importDepth,
|
|
1672
|
+
contextBudget: metric.contextBudget,
|
|
1673
|
+
cohesionScore: metric.cohesion,
|
|
1674
|
+
fragmentationScore: 0,
|
|
1675
|
+
maxDepth,
|
|
1676
|
+
maxContextBudget,
|
|
1677
|
+
minCohesion,
|
|
1678
|
+
maxFragmentation,
|
|
1679
|
+
circularDeps: metric.metrics.circularDependencies.map(
|
|
1680
|
+
(cycle) => cycle.split(" \u2192 ")
|
|
1681
|
+
)
|
|
1682
|
+
});
|
|
1683
|
+
return {
|
|
1684
|
+
file: metric.file,
|
|
1685
|
+
tokenCost: Math.floor(
|
|
1686
|
+
metric.contextBudget / (1 + metric.imports.length || 1)
|
|
1687
|
+
),
|
|
1688
|
+
linesOfCode: metric.metrics.linesOfCode,
|
|
1689
|
+
importDepth: metric.importDepth,
|
|
1690
|
+
dependencyCount: metric.imports.length,
|
|
1691
|
+
dependencyList: metric.imports.map(
|
|
1692
|
+
(imp) => imp.resolvedPath || imp.source
|
|
1693
|
+
),
|
|
1694
|
+
circularDeps: metric.metrics.circularDependencies.map(
|
|
1695
|
+
(cycle) => cycle.split(" \u2192 ")
|
|
1696
|
+
),
|
|
1697
|
+
cohesionScore: metric.cohesion,
|
|
1698
|
+
domains: ["python"],
|
|
1699
|
+
exportCount: metric.exports.length,
|
|
1700
|
+
contextBudget: metric.contextBudget,
|
|
1701
|
+
fragmentationScore: 0,
|
|
1702
|
+
relatedFiles: [],
|
|
1703
|
+
fileClassification: "unknown",
|
|
1704
|
+
severity,
|
|
1705
|
+
issues,
|
|
1706
|
+
recommendations,
|
|
1707
|
+
potentialSavings
|
|
1708
|
+
};
|
|
1709
|
+
});
|
|
1710
|
+
}
|
|
1711
|
+
const circularDeps = detectCircularDependencies(graph);
|
|
1712
|
+
const useLogScale = files.length >= 500;
|
|
1713
|
+
const clusters = detectModuleClusters(graph, { useLogScale });
|
|
1714
|
+
const fragmentationMap = /* @__PURE__ */ new Map();
|
|
1715
|
+
for (const cluster of clusters) {
|
|
1716
|
+
for (const file of cluster.files) {
|
|
1717
|
+
fragmentationMap.set(file, cluster.fragmentationScore);
|
|
1718
|
+
}
|
|
1719
|
+
}
|
|
1720
|
+
const results = [];
|
|
1721
|
+
for (const { file } of fileContents) {
|
|
1722
|
+
const node = graph.nodes.get(file);
|
|
1723
|
+
if (!node) continue;
|
|
1724
|
+
const importDepth = focus === "depth" || focus === "all" ? calculateImportDepth(file, graph) : 0;
|
|
1725
|
+
const dependencyList = focus === "depth" || focus === "all" ? getTransitiveDependencies(file, graph) : [];
|
|
1726
|
+
const contextBudget = focus === "all" ? calculateContextBudget(file, graph) : node.tokenCost;
|
|
1727
|
+
const cohesionScore = focus === "cohesion" || focus === "all" ? calculateCohesion(node.exports, file, {
|
|
1728
|
+
coUsageMatrix: graph.coUsageMatrix
|
|
1729
|
+
}) : 1;
|
|
1730
|
+
const fragmentationScore = fragmentationMap.get(file) || 0;
|
|
1731
|
+
const relatedFiles = [];
|
|
1732
|
+
for (const cluster of clusters) {
|
|
1733
|
+
if (cluster.files.includes(file)) {
|
|
1734
|
+
relatedFiles.push(...cluster.files.filter((f) => f !== file));
|
|
1735
|
+
break;
|
|
1560
1736
|
}
|
|
1561
1737
|
}
|
|
1562
|
-
const
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1738
|
+
const { issues } = analyzeIssues({
|
|
1739
|
+
file,
|
|
1740
|
+
importDepth,
|
|
1741
|
+
contextBudget,
|
|
1742
|
+
cohesionScore,
|
|
1567
1743
|
fragmentationScore,
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1744
|
+
maxDepth,
|
|
1745
|
+
maxContextBudget,
|
|
1746
|
+
minCohesion,
|
|
1747
|
+
maxFragmentation,
|
|
1748
|
+
circularDeps
|
|
1749
|
+
});
|
|
1750
|
+
const domains = [
|
|
1751
|
+
...new Set(node.exports.map((e) => e.inferredDomain || "unknown"))
|
|
1752
|
+
];
|
|
1753
|
+
const fileClassification = classifyFile(node);
|
|
1754
|
+
const adjustedCohesionScore = adjustCohesionForClassification(
|
|
1755
|
+
cohesionScore,
|
|
1756
|
+
fileClassification,
|
|
1757
|
+
node
|
|
1758
|
+
);
|
|
1759
|
+
const adjustedFragmentationScore = adjustFragmentationForClassification(
|
|
1760
|
+
fragmentationScore,
|
|
1761
|
+
fileClassification
|
|
1762
|
+
);
|
|
1763
|
+
const classificationRecommendations = getClassificationRecommendations(
|
|
1764
|
+
fileClassification,
|
|
1765
|
+
file,
|
|
1766
|
+
issues
|
|
1767
|
+
);
|
|
1768
|
+
const {
|
|
1769
|
+
severity: adjustedSeverity,
|
|
1770
|
+
issues: adjustedIssues,
|
|
1771
|
+
recommendations: finalRecommendations,
|
|
1772
|
+
potentialSavings: adjustedSavings
|
|
1773
|
+
} = analyzeIssues({
|
|
1774
|
+
file,
|
|
1775
|
+
importDepth,
|
|
1776
|
+
contextBudget,
|
|
1777
|
+
cohesionScore: adjustedCohesionScore,
|
|
1778
|
+
fragmentationScore: adjustedFragmentationScore,
|
|
1779
|
+
maxDepth,
|
|
1780
|
+
maxContextBudget,
|
|
1781
|
+
minCohesion,
|
|
1782
|
+
maxFragmentation,
|
|
1783
|
+
circularDeps
|
|
1784
|
+
});
|
|
1785
|
+
results.push({
|
|
1786
|
+
file,
|
|
1787
|
+
tokenCost: node.tokenCost,
|
|
1788
|
+
linesOfCode: node.linesOfCode,
|
|
1789
|
+
importDepth,
|
|
1790
|
+
dependencyCount: dependencyList.length,
|
|
1791
|
+
dependencyList,
|
|
1792
|
+
circularDeps: circularDeps.filter((cycle) => cycle.includes(file)),
|
|
1793
|
+
cohesionScore: adjustedCohesionScore,
|
|
1794
|
+
domains,
|
|
1795
|
+
exportCount: node.exports.length,
|
|
1796
|
+
contextBudget,
|
|
1797
|
+
fragmentationScore: adjustedFragmentationScore,
|
|
1798
|
+
relatedFiles,
|
|
1799
|
+
fileClassification,
|
|
1800
|
+
severity: adjustedSeverity,
|
|
1801
|
+
issues: adjustedIssues,
|
|
1802
|
+
recommendations: [
|
|
1803
|
+
...finalRecommendations,
|
|
1804
|
+
...classificationRecommendations.slice(0, 1)
|
|
1805
|
+
],
|
|
1806
|
+
potentialSavings: adjustedSavings
|
|
1579
1807
|
});
|
|
1580
1808
|
}
|
|
1581
|
-
const
|
|
1582
|
-
const
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
(sum, r) => sum + r.potentialSavings,
|
|
1590
|
-
0
|
|
1591
|
-
);
|
|
1592
|
-
const topExpensiveFiles = results.sort((a, b) => b.contextBudget - a.contextBudget).slice(0, 10).map((r) => ({
|
|
1593
|
-
file: r.file,
|
|
1594
|
-
contextBudget: r.contextBudget,
|
|
1595
|
-
severity: r.severity
|
|
1596
|
-
}));
|
|
1597
|
-
return {
|
|
1598
|
-
totalFiles,
|
|
1599
|
-
totalTokens,
|
|
1600
|
-
avgContextBudget,
|
|
1601
|
-
maxContextBudget,
|
|
1602
|
-
avgImportDepth,
|
|
1603
|
-
maxImportDepth,
|
|
1604
|
-
deepFiles,
|
|
1605
|
-
avgFragmentation,
|
|
1606
|
-
fragmentedModules,
|
|
1607
|
-
avgCohesion,
|
|
1608
|
-
lowCohesionFiles,
|
|
1609
|
-
criticalIssues,
|
|
1610
|
-
majorIssues,
|
|
1611
|
-
minorIssues,
|
|
1612
|
-
totalPotentialSavings,
|
|
1613
|
-
topExpensiveFiles,
|
|
1614
|
-
config
|
|
1615
|
-
};
|
|
1809
|
+
const allResults = [...results, ...pythonResults];
|
|
1810
|
+
const finalSummary = generateSummary(allResults, options);
|
|
1811
|
+
return allResults.sort((a, b) => {
|
|
1812
|
+
const severityOrder = { critical: 0, major: 1, minor: 2, info: 3 };
|
|
1813
|
+
const severityDiff = severityOrder[a.severity] - severityOrder[b.severity];
|
|
1814
|
+
if (severityDiff !== 0) return severityDiff;
|
|
1815
|
+
return b.contextBudget - a.contextBudget;
|
|
1816
|
+
});
|
|
1616
1817
|
}
|
|
1617
1818
|
|
|
1618
|
-
// src/provider.ts
|
|
1619
|
-
var import_core7 = require("@aiready/core");
|
|
1620
|
-
|
|
1621
1819
|
// src/scoring.ts
|
|
1622
|
-
var
|
|
1820
|
+
var import_core7 = require("@aiready/core");
|
|
1623
1821
|
function calculateContextScore(summary, costConfig) {
|
|
1624
1822
|
const {
|
|
1625
1823
|
avgContextBudget,
|
|
@@ -1715,8 +1913,8 @@ function calculateContextScore(summary, costConfig) {
|
|
|
1715
1913
|
priority: "high"
|
|
1716
1914
|
});
|
|
1717
1915
|
}
|
|
1718
|
-
const cfg = { ...
|
|
1719
|
-
const estimatedMonthlyCost = (0,
|
|
1916
|
+
const cfg = { ...import_core7.DEFAULT_COST_CONFIG, ...costConfig };
|
|
1917
|
+
const estimatedMonthlyCost = (0, import_core7.calculateMonthlyCost)(
|
|
1720
1918
|
avgContextBudget * (summary.totalFiles || 1),
|
|
1721
1919
|
cfg
|
|
1722
1920
|
);
|
|
@@ -1724,9 +1922,9 @@ function calculateContextScore(summary, costConfig) {
|
|
|
1724
1922
|
...Array(criticalIssues).fill({ severity: "critical" }),
|
|
1725
1923
|
...Array(majorIssues).fill({ severity: "major" })
|
|
1726
1924
|
];
|
|
1727
|
-
const productivityImpact = (0,
|
|
1925
|
+
const productivityImpact = (0, import_core7.calculateProductivityImpact)(issues);
|
|
1728
1926
|
return {
|
|
1729
|
-
toolName:
|
|
1927
|
+
toolName: import_core7.ToolName.ContextAnalyzer,
|
|
1730
1928
|
score,
|
|
1731
1929
|
rawMetrics: {
|
|
1732
1930
|
avgContextBudget: Math.round(avgContextBudget),
|
|
@@ -1753,7 +1951,7 @@ function mapScoreToRating(score) {
|
|
|
1753
1951
|
|
|
1754
1952
|
// src/provider.ts
|
|
1755
1953
|
var ContextAnalyzerProvider = {
|
|
1756
|
-
id:
|
|
1954
|
+
id: import_core8.ToolName.ContextAnalyzer,
|
|
1757
1955
|
alias: ["context", "fragmentation", "budget"],
|
|
1758
1956
|
async analyze(options) {
|
|
1759
1957
|
const results = await analyzeContext(options);
|
|
@@ -1762,7 +1960,7 @@ var ContextAnalyzerProvider = {
|
|
|
1762
1960
|
(r) => ({
|
|
1763
1961
|
fileName: r.file,
|
|
1764
1962
|
issues: r.issues.map((msg) => ({
|
|
1765
|
-
type:
|
|
1963
|
+
type: import_core8.IssueType.ContextFragmentation,
|
|
1766
1964
|
severity: r.severity,
|
|
1767
1965
|
message: msg,
|
|
1768
1966
|
location: { file: r.file, line: 1 },
|
|
@@ -1775,13 +1973,13 @@ var ContextAnalyzerProvider = {
|
|
|
1775
1973
|
}
|
|
1776
1974
|
})
|
|
1777
1975
|
);
|
|
1778
|
-
return
|
|
1976
|
+
return import_core8.SpokeOutputSchema.parse({
|
|
1779
1977
|
results: normalizedResults,
|
|
1780
1978
|
summary: {
|
|
1781
1979
|
...summary
|
|
1782
1980
|
},
|
|
1783
1981
|
metadata: {
|
|
1784
|
-
toolName:
|
|
1982
|
+
toolName: import_core8.ToolName.ContextAnalyzer,
|
|
1785
1983
|
version: "0.17.5",
|
|
1786
1984
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1787
1985
|
}
|
|
@@ -1795,9 +1993,9 @@ var ContextAnalyzerProvider = {
|
|
|
1795
1993
|
};
|
|
1796
1994
|
|
|
1797
1995
|
// src/defaults.ts
|
|
1798
|
-
var
|
|
1996
|
+
var import_core9 = require("@aiready/core");
|
|
1799
1997
|
async function getSmartDefaults(directory, userOptions) {
|
|
1800
|
-
const files = await (0,
|
|
1998
|
+
const files = await (0, import_core9.scanFiles)({
|
|
1801
1999
|
rootDir: directory,
|
|
1802
2000
|
include: userOptions.include,
|
|
1803
2001
|
exclude: userOptions.exclude
|
|
@@ -1843,191 +2041,9 @@ async function getSmartDefaults(directory, userOptions) {
|
|
|
1843
2041
|
|
|
1844
2042
|
// src/index.ts
|
|
1845
2043
|
import_core10.ToolRegistry.register(ContextAnalyzerProvider);
|
|
1846
|
-
async function analyzeContext(options) {
|
|
1847
|
-
const {
|
|
1848
|
-
maxDepth = 5,
|
|
1849
|
-
maxContextBudget = 1e4,
|
|
1850
|
-
minCohesion = 0.6,
|
|
1851
|
-
maxFragmentation = 0.5,
|
|
1852
|
-
focus = "all",
|
|
1853
|
-
includeNodeModules = false,
|
|
1854
|
-
...scanOptions
|
|
1855
|
-
} = options;
|
|
1856
|
-
const files = await (0, import_core10.scanFiles)({
|
|
1857
|
-
...scanOptions,
|
|
1858
|
-
exclude: includeNodeModules && scanOptions.exclude ? scanOptions.exclude.filter(
|
|
1859
|
-
(pattern) => pattern !== "**/node_modules/**"
|
|
1860
|
-
) : scanOptions.exclude
|
|
1861
|
-
});
|
|
1862
|
-
const pythonFiles = files.filter((f) => f.toLowerCase().endsWith(".py"));
|
|
1863
|
-
const fileContents = await Promise.all(
|
|
1864
|
-
files.map(async (file) => ({
|
|
1865
|
-
file,
|
|
1866
|
-
content: await (0, import_core10.readFileContent)(file)
|
|
1867
|
-
}))
|
|
1868
|
-
);
|
|
1869
|
-
const graph = buildDependencyGraph(
|
|
1870
|
-
fileContents.filter((f) => !f.file.toLowerCase().endsWith(".py"))
|
|
1871
|
-
);
|
|
1872
|
-
let pythonResults = [];
|
|
1873
|
-
if (pythonFiles.length > 0) {
|
|
1874
|
-
const { analyzePythonContext: analyzePythonContext2 } = await Promise.resolve().then(() => (init_python_context(), python_context_exports));
|
|
1875
|
-
const pythonMetrics = await analyzePythonContext2(
|
|
1876
|
-
pythonFiles,
|
|
1877
|
-
scanOptions.rootDir || options.rootDir || "."
|
|
1878
|
-
);
|
|
1879
|
-
pythonResults = pythonMetrics.map((metric) => {
|
|
1880
|
-
const { severity, issues, recommendations, potentialSavings } = analyzeIssues({
|
|
1881
|
-
file: metric.file,
|
|
1882
|
-
importDepth: metric.importDepth,
|
|
1883
|
-
contextBudget: metric.contextBudget,
|
|
1884
|
-
cohesionScore: metric.cohesion,
|
|
1885
|
-
fragmentationScore: 0,
|
|
1886
|
-
maxDepth,
|
|
1887
|
-
maxContextBudget,
|
|
1888
|
-
minCohesion,
|
|
1889
|
-
maxFragmentation,
|
|
1890
|
-
circularDeps: metric.metrics.circularDependencies.map(
|
|
1891
|
-
(cycle) => cycle.split(" \u2192 ")
|
|
1892
|
-
)
|
|
1893
|
-
});
|
|
1894
|
-
return {
|
|
1895
|
-
file: metric.file,
|
|
1896
|
-
tokenCost: Math.floor(
|
|
1897
|
-
metric.contextBudget / (1 + metric.imports.length || 1)
|
|
1898
|
-
),
|
|
1899
|
-
linesOfCode: metric.metrics.linesOfCode,
|
|
1900
|
-
importDepth: metric.importDepth,
|
|
1901
|
-
dependencyCount: metric.imports.length,
|
|
1902
|
-
dependencyList: metric.imports.map(
|
|
1903
|
-
(imp) => imp.resolvedPath || imp.source
|
|
1904
|
-
),
|
|
1905
|
-
circularDeps: metric.metrics.circularDependencies.map(
|
|
1906
|
-
(cycle) => cycle.split(" \u2192 ")
|
|
1907
|
-
),
|
|
1908
|
-
cohesionScore: metric.cohesion,
|
|
1909
|
-
domains: ["python"],
|
|
1910
|
-
exportCount: metric.exports.length,
|
|
1911
|
-
contextBudget: metric.contextBudget,
|
|
1912
|
-
fragmentationScore: 0,
|
|
1913
|
-
relatedFiles: [],
|
|
1914
|
-
fileClassification: "unknown",
|
|
1915
|
-
severity,
|
|
1916
|
-
issues,
|
|
1917
|
-
recommendations,
|
|
1918
|
-
potentialSavings
|
|
1919
|
-
};
|
|
1920
|
-
});
|
|
1921
|
-
}
|
|
1922
|
-
const circularDeps = detectCircularDependencies(graph);
|
|
1923
|
-
const useLogScale = files.length >= 500;
|
|
1924
|
-
const clusters = detectModuleClusters(graph, { useLogScale });
|
|
1925
|
-
const fragmentationMap = /* @__PURE__ */ new Map();
|
|
1926
|
-
for (const cluster of clusters) {
|
|
1927
|
-
for (const file of cluster.files) {
|
|
1928
|
-
fragmentationMap.set(file, cluster.fragmentationScore);
|
|
1929
|
-
}
|
|
1930
|
-
}
|
|
1931
|
-
const results = [];
|
|
1932
|
-
for (const { file } of fileContents) {
|
|
1933
|
-
const node = graph.nodes.get(file);
|
|
1934
|
-
if (!node) continue;
|
|
1935
|
-
const importDepth = focus === "depth" || focus === "all" ? calculateImportDepth(file, graph) : 0;
|
|
1936
|
-
const dependencyList = focus === "depth" || focus === "all" ? getTransitiveDependencies(file, graph) : [];
|
|
1937
|
-
const contextBudget = focus === "all" ? calculateContextBudget(file, graph) : node.tokenCost;
|
|
1938
|
-
const cohesionScore = focus === "cohesion" || focus === "all" ? calculateCohesion(node.exports, file, {
|
|
1939
|
-
coUsageMatrix: graph.coUsageMatrix
|
|
1940
|
-
}) : 1;
|
|
1941
|
-
const fragmentationScore = fragmentationMap.get(file) || 0;
|
|
1942
|
-
const relatedFiles = [];
|
|
1943
|
-
for (const cluster of clusters) {
|
|
1944
|
-
if (cluster.files.includes(file)) {
|
|
1945
|
-
relatedFiles.push(...cluster.files.filter((f) => f !== file));
|
|
1946
|
-
break;
|
|
1947
|
-
}
|
|
1948
|
-
}
|
|
1949
|
-
const { issues } = analyzeIssues({
|
|
1950
|
-
file,
|
|
1951
|
-
importDepth,
|
|
1952
|
-
contextBudget,
|
|
1953
|
-
cohesionScore,
|
|
1954
|
-
fragmentationScore,
|
|
1955
|
-
maxDepth,
|
|
1956
|
-
maxContextBudget,
|
|
1957
|
-
minCohesion,
|
|
1958
|
-
maxFragmentation,
|
|
1959
|
-
circularDeps
|
|
1960
|
-
});
|
|
1961
|
-
const domains = [
|
|
1962
|
-
...new Set(node.exports.map((e) => e.inferredDomain || "unknown"))
|
|
1963
|
-
];
|
|
1964
|
-
const fileClassification = classifyFile(node);
|
|
1965
|
-
const adjustedCohesionScore = adjustCohesionForClassification(
|
|
1966
|
-
cohesionScore,
|
|
1967
|
-
fileClassification,
|
|
1968
|
-
node
|
|
1969
|
-
);
|
|
1970
|
-
const adjustedFragmentationScore = adjustFragmentationForClassification(
|
|
1971
|
-
fragmentationScore,
|
|
1972
|
-
fileClassification
|
|
1973
|
-
);
|
|
1974
|
-
const classificationRecommendations = getClassificationRecommendations(
|
|
1975
|
-
fileClassification,
|
|
1976
|
-
file,
|
|
1977
|
-
issues
|
|
1978
|
-
);
|
|
1979
|
-
const {
|
|
1980
|
-
severity: adjustedSeverity,
|
|
1981
|
-
issues: adjustedIssues,
|
|
1982
|
-
recommendations: finalRecommendations,
|
|
1983
|
-
potentialSavings: adjustedSavings
|
|
1984
|
-
} = analyzeIssues({
|
|
1985
|
-
file,
|
|
1986
|
-
importDepth,
|
|
1987
|
-
contextBudget,
|
|
1988
|
-
cohesionScore: adjustedCohesionScore,
|
|
1989
|
-
fragmentationScore: adjustedFragmentationScore,
|
|
1990
|
-
maxDepth,
|
|
1991
|
-
maxContextBudget,
|
|
1992
|
-
minCohesion,
|
|
1993
|
-
maxFragmentation,
|
|
1994
|
-
circularDeps
|
|
1995
|
-
});
|
|
1996
|
-
results.push({
|
|
1997
|
-
file,
|
|
1998
|
-
tokenCost: node.tokenCost,
|
|
1999
|
-
linesOfCode: node.linesOfCode,
|
|
2000
|
-
importDepth,
|
|
2001
|
-
dependencyCount: dependencyList.length,
|
|
2002
|
-
dependencyList,
|
|
2003
|
-
circularDeps: circularDeps.filter((cycle) => cycle.includes(file)),
|
|
2004
|
-
cohesionScore: adjustedCohesionScore,
|
|
2005
|
-
domains,
|
|
2006
|
-
exportCount: node.exports.length,
|
|
2007
|
-
contextBudget,
|
|
2008
|
-
fragmentationScore: adjustedFragmentationScore,
|
|
2009
|
-
relatedFiles,
|
|
2010
|
-
fileClassification,
|
|
2011
|
-
severity: adjustedSeverity,
|
|
2012
|
-
issues: adjustedIssues,
|
|
2013
|
-
recommendations: [
|
|
2014
|
-
...finalRecommendations,
|
|
2015
|
-
...classificationRecommendations.slice(0, 1)
|
|
2016
|
-
],
|
|
2017
|
-
potentialSavings: adjustedSavings
|
|
2018
|
-
});
|
|
2019
|
-
}
|
|
2020
|
-
const allResults = [...results, ...pythonResults];
|
|
2021
|
-
const finalSummary = generateSummary(allResults, options);
|
|
2022
|
-
return allResults.sort((a, b) => {
|
|
2023
|
-
const severityOrder = { critical: 0, major: 1, minor: 2, info: 3 };
|
|
2024
|
-
const severityDiff = severityOrder[a.severity] - severityOrder[b.severity];
|
|
2025
|
-
if (severityDiff !== 0) return severityDiff;
|
|
2026
|
-
return b.contextBudget - a.contextBudget;
|
|
2027
|
-
});
|
|
2028
|
-
}
|
|
2029
2044
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2030
2045
|
0 && (module.exports = {
|
|
2046
|
+
Classification,
|
|
2031
2047
|
ContextAnalyzerProvider,
|
|
2032
2048
|
adjustCohesionForClassification,
|
|
2033
2049
|
adjustFragmentationForClassification,
|