@aiready/context-analyzer 0.19.18 → 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 +25 -25
- 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/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_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
|
}
|
|
@@ -247,8 +247,11 @@ var import_commander = require("commander");
|
|
|
247
247
|
// src/index.ts
|
|
248
248
|
var import_core10 = require("@aiready/core");
|
|
249
249
|
|
|
250
|
+
// src/provider.ts
|
|
251
|
+
var import_core8 = require("@aiready/core");
|
|
252
|
+
|
|
250
253
|
// src/analyzer.ts
|
|
251
|
-
var
|
|
254
|
+
var import_core6 = require("@aiready/core");
|
|
252
255
|
|
|
253
256
|
// src/metrics.ts
|
|
254
257
|
var import_core2 = require("@aiready/core");
|
|
@@ -610,8 +613,143 @@ function calculateDirectoryDistance(files) {
|
|
|
610
613
|
return comparisons > 0 ? totalNormalized / comparisons : 0;
|
|
611
614
|
}
|
|
612
615
|
|
|
613
|
-
// src/
|
|
616
|
+
// src/summary.ts
|
|
614
617
|
var import_core3 = require("@aiready/core");
|
|
618
|
+
function generateSummary(results, options) {
|
|
619
|
+
const config = options ? Object.fromEntries(
|
|
620
|
+
Object.entries(options).filter(
|
|
621
|
+
([key]) => !import_core3.GLOBAL_SCAN_OPTIONS.includes(key) || key === "rootDir"
|
|
622
|
+
)
|
|
623
|
+
) : void 0;
|
|
624
|
+
if (results.length === 0) {
|
|
625
|
+
return {
|
|
626
|
+
totalFiles: 0,
|
|
627
|
+
totalTokens: 0,
|
|
628
|
+
avgContextBudget: 0,
|
|
629
|
+
maxContextBudget: 0,
|
|
630
|
+
avgImportDepth: 0,
|
|
631
|
+
maxImportDepth: 0,
|
|
632
|
+
deepFiles: [],
|
|
633
|
+
avgFragmentation: 0,
|
|
634
|
+
fragmentedModules: [],
|
|
635
|
+
avgCohesion: 0,
|
|
636
|
+
lowCohesionFiles: [],
|
|
637
|
+
criticalIssues: 0,
|
|
638
|
+
majorIssues: 0,
|
|
639
|
+
minorIssues: 0,
|
|
640
|
+
totalPotentialSavings: 0,
|
|
641
|
+
topExpensiveFiles: [],
|
|
642
|
+
config
|
|
643
|
+
};
|
|
644
|
+
}
|
|
645
|
+
const totalFiles = results.length;
|
|
646
|
+
const totalTokens = results.reduce((sum, r) => sum + r.tokenCost, 0);
|
|
647
|
+
const totalContextBudget = results.reduce(
|
|
648
|
+
(sum, r) => sum + r.contextBudget,
|
|
649
|
+
0
|
|
650
|
+
);
|
|
651
|
+
const avgContextBudget = totalContextBudget / totalFiles;
|
|
652
|
+
const maxContextBudget = Math.max(...results.map((r) => r.contextBudget));
|
|
653
|
+
const avgImportDepth = results.reduce((sum, r) => sum + r.importDepth, 0) / totalFiles;
|
|
654
|
+
const maxImportDepth = Math.max(...results.map((r) => r.importDepth));
|
|
655
|
+
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);
|
|
656
|
+
const avgFragmentation = results.reduce((sum, r) => sum + r.fragmentationScore, 0) / totalFiles;
|
|
657
|
+
const moduleMap = /* @__PURE__ */ new Map();
|
|
658
|
+
for (const result of results) {
|
|
659
|
+
for (const domain of result.domains) {
|
|
660
|
+
if (!moduleMap.has(domain)) moduleMap.set(domain, []);
|
|
661
|
+
moduleMap.get(domain).push(result);
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
const fragmentedModules = [];
|
|
665
|
+
for (const [domain, files] of moduleMap.entries()) {
|
|
666
|
+
let jaccard2 = function(a, b) {
|
|
667
|
+
const s1 = new Set(a || []);
|
|
668
|
+
const s2 = new Set(b || []);
|
|
669
|
+
if (s1.size === 0 && s2.size === 0) return 0;
|
|
670
|
+
const inter = new Set([...s1].filter((x) => s2.has(x)));
|
|
671
|
+
const uni = /* @__PURE__ */ new Set([...s1, ...s2]);
|
|
672
|
+
return uni.size === 0 ? 0 : inter.size / uni.size;
|
|
673
|
+
};
|
|
674
|
+
var jaccard = jaccard2;
|
|
675
|
+
if (files.length < 2) continue;
|
|
676
|
+
const fragmentationScore = files.reduce((sum, f) => sum + f.fragmentationScore, 0) / files.length;
|
|
677
|
+
if (fragmentationScore < 0.3) continue;
|
|
678
|
+
const totalTokens2 = files.reduce((sum, f) => sum + f.tokenCost, 0);
|
|
679
|
+
const avgCohesion2 = files.reduce((sum, f) => sum + f.cohesionScore, 0) / files.length;
|
|
680
|
+
const targetFiles = Math.max(1, Math.ceil(files.length / 3));
|
|
681
|
+
const filePaths = files.map((f) => f.file);
|
|
682
|
+
const pathEntropy = calculatePathEntropy(filePaths);
|
|
683
|
+
const directoryDistance = calculateDirectoryDistance(filePaths);
|
|
684
|
+
let importSimTotal = 0;
|
|
685
|
+
let importPairs = 0;
|
|
686
|
+
for (let i = 0; i < files.length; i++) {
|
|
687
|
+
for (let j = i + 1; j < files.length; j++) {
|
|
688
|
+
importSimTotal += jaccard2(
|
|
689
|
+
files[i].dependencyList || [],
|
|
690
|
+
files[j].dependencyList || []
|
|
691
|
+
);
|
|
692
|
+
importPairs++;
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
const importCohesion = importPairs > 0 ? importSimTotal / importPairs : 0;
|
|
696
|
+
fragmentedModules.push({
|
|
697
|
+
domain,
|
|
698
|
+
files: files.map((f) => f.file),
|
|
699
|
+
totalTokens: totalTokens2,
|
|
700
|
+
fragmentationScore,
|
|
701
|
+
avgCohesion: avgCohesion2,
|
|
702
|
+
importCohesion,
|
|
703
|
+
pathEntropy,
|
|
704
|
+
directoryDistance,
|
|
705
|
+
suggestedStructure: {
|
|
706
|
+
targetFiles,
|
|
707
|
+
consolidationPlan: [
|
|
708
|
+
`Consolidate ${files.length} files across ${new Set(files.map((f) => f.file.split("/").slice(0, -1).join("/"))).size} directories`,
|
|
709
|
+
`Target ~${targetFiles} core modules to reduce context switching`
|
|
710
|
+
]
|
|
711
|
+
}
|
|
712
|
+
});
|
|
713
|
+
}
|
|
714
|
+
const avgCohesion = results.reduce((sum, r) => sum + r.cohesionScore, 0) / totalFiles;
|
|
715
|
+
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);
|
|
716
|
+
const criticalIssues = results.filter(
|
|
717
|
+
(r) => r.severity === "critical"
|
|
718
|
+
).length;
|
|
719
|
+
const majorIssues = results.filter((r) => r.severity === "major").length;
|
|
720
|
+
const minorIssues = results.filter((r) => r.severity === "minor").length;
|
|
721
|
+
const totalPotentialSavings = results.reduce(
|
|
722
|
+
(sum, r) => sum + r.potentialSavings,
|
|
723
|
+
0
|
|
724
|
+
);
|
|
725
|
+
const topExpensiveFiles = results.sort((a, b) => b.contextBudget - a.contextBudget).slice(0, 10).map((r) => ({
|
|
726
|
+
file: r.file,
|
|
727
|
+
contextBudget: r.contextBudget,
|
|
728
|
+
severity: r.severity
|
|
729
|
+
}));
|
|
730
|
+
return {
|
|
731
|
+
totalFiles,
|
|
732
|
+
totalTokens,
|
|
733
|
+
avgContextBudget,
|
|
734
|
+
maxContextBudget,
|
|
735
|
+
avgImportDepth,
|
|
736
|
+
maxImportDepth,
|
|
737
|
+
deepFiles,
|
|
738
|
+
avgFragmentation,
|
|
739
|
+
fragmentedModules,
|
|
740
|
+
avgCohesion,
|
|
741
|
+
lowCohesionFiles,
|
|
742
|
+
criticalIssues,
|
|
743
|
+
majorIssues,
|
|
744
|
+
minorIssues,
|
|
745
|
+
totalPotentialSavings,
|
|
746
|
+
topExpensiveFiles,
|
|
747
|
+
config
|
|
748
|
+
};
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
// src/graph-builder.ts
|
|
752
|
+
var import_core4 = require("@aiready/core");
|
|
615
753
|
function extractDomainKeywordsFromPaths(files) {
|
|
616
754
|
const folderNames = /* @__PURE__ */ new Set();
|
|
617
755
|
for (const { file } of files) {
|
|
@@ -663,7 +801,7 @@ function buildDependencyGraph(files, options) {
|
|
|
663
801
|
const edges = /* @__PURE__ */ new Map();
|
|
664
802
|
const autoDetectedKeywords = options?.domainKeywords ?? extractDomainKeywordsFromPaths(files);
|
|
665
803
|
for (const { file, content } of files) {
|
|
666
|
-
const { imports: astImports } = (0,
|
|
804
|
+
const { imports: astImports } = (0, import_core4.parseFileExports)(content, file);
|
|
667
805
|
const importSources = astImports.map((i) => i.source);
|
|
668
806
|
const exports2 = extractExportsWithAST(
|
|
669
807
|
content,
|
|
@@ -671,7 +809,7 @@ function buildDependencyGraph(files, options) {
|
|
|
671
809
|
{ domainKeywords: autoDetectedKeywords },
|
|
672
810
|
importSources
|
|
673
811
|
);
|
|
674
|
-
const tokenCost = (0,
|
|
812
|
+
const tokenCost = (0, import_core4.estimateTokens)(content);
|
|
675
813
|
const linesOfCode = content.split("\n").length;
|
|
676
814
|
nodes.set(file, {
|
|
677
815
|
file,
|
|
@@ -778,48 +916,62 @@ function detectCircularDependencies(graph) {
|
|
|
778
916
|
}
|
|
779
917
|
|
|
780
918
|
// src/classifier.ts
|
|
919
|
+
var Classification = {
|
|
920
|
+
BARREL: "barrel-export",
|
|
921
|
+
TYPE_DEFINITION: "type-definition",
|
|
922
|
+
NEXTJS_PAGE: "nextjs-page",
|
|
923
|
+
LAMBDA_HANDLER: "lambda-handler",
|
|
924
|
+
SERVICE: "service-file",
|
|
925
|
+
EMAIL_TEMPLATE: "email-template",
|
|
926
|
+
PARSER: "parser-file",
|
|
927
|
+
COHESIVE_MODULE: "cohesive-module",
|
|
928
|
+
UTILITY_MODULE: "utility-module",
|
|
929
|
+
MIXED_CONCERNS: "mixed-concerns",
|
|
930
|
+
UNKNOWN: "unknown"
|
|
931
|
+
};
|
|
781
932
|
function classifyFile(node, cohesionScore = 1, domains = []) {
|
|
782
933
|
if (isBarrelExport(node)) {
|
|
783
|
-
return
|
|
934
|
+
return Classification.BARREL;
|
|
784
935
|
}
|
|
785
936
|
if (isTypeDefinition(node)) {
|
|
786
|
-
return
|
|
937
|
+
return Classification.TYPE_DEFINITION;
|
|
787
938
|
}
|
|
788
939
|
if (isNextJsPage(node)) {
|
|
789
|
-
return
|
|
940
|
+
return Classification.NEXTJS_PAGE;
|
|
790
941
|
}
|
|
791
942
|
if (isLambdaHandler(node)) {
|
|
792
|
-
return
|
|
943
|
+
return Classification.LAMBDA_HANDLER;
|
|
793
944
|
}
|
|
794
945
|
if (isServiceFile(node)) {
|
|
795
|
-
return
|
|
946
|
+
return Classification.SERVICE;
|
|
796
947
|
}
|
|
797
948
|
if (isEmailTemplate(node)) {
|
|
798
|
-
return
|
|
949
|
+
return Classification.EMAIL_TEMPLATE;
|
|
799
950
|
}
|
|
800
951
|
if (isParserFile(node)) {
|
|
801
|
-
return
|
|
952
|
+
return Classification.PARSER;
|
|
802
953
|
}
|
|
803
954
|
if (isSessionFile(node)) {
|
|
804
|
-
if (cohesionScore >= 0.25 && domains.length <= 1)
|
|
805
|
-
|
|
955
|
+
if (cohesionScore >= 0.25 && domains.length <= 1)
|
|
956
|
+
return Classification.COHESIVE_MODULE;
|
|
957
|
+
return Classification.UTILITY_MODULE;
|
|
806
958
|
}
|
|
807
959
|
if (isUtilityModule(node)) {
|
|
808
|
-
return
|
|
960
|
+
return Classification.UTILITY_MODULE;
|
|
809
961
|
}
|
|
810
962
|
if (isConfigFile(node)) {
|
|
811
|
-
return
|
|
963
|
+
return Classification.COHESIVE_MODULE;
|
|
812
964
|
}
|
|
813
965
|
if (domains.length <= 1 && domains[0] !== "unknown") {
|
|
814
|
-
return
|
|
966
|
+
return Classification.COHESIVE_MODULE;
|
|
815
967
|
}
|
|
816
968
|
if (domains.length > 1 && cohesionScore < 0.4) {
|
|
817
|
-
return
|
|
969
|
+
return Classification.MIXED_CONCERNS;
|
|
818
970
|
}
|
|
819
971
|
if (cohesionScore >= 0.7) {
|
|
820
|
-
return
|
|
972
|
+
return Classification.COHESIVE_MODULE;
|
|
821
973
|
}
|
|
822
|
-
return
|
|
974
|
+
return Classification.UNKNOWN;
|
|
823
975
|
}
|
|
824
976
|
function isBarrelExport(node) {
|
|
825
977
|
const { file, exports: exports2 } = node;
|
|
@@ -982,13 +1134,13 @@ function isNextJsPage(node) {
|
|
|
982
1134
|
}
|
|
983
1135
|
function adjustCohesionForClassification(baseCohesion, classification, node) {
|
|
984
1136
|
switch (classification) {
|
|
985
|
-
case
|
|
1137
|
+
case Classification.BARREL:
|
|
986
1138
|
return 1;
|
|
987
|
-
case
|
|
1139
|
+
case Classification.TYPE_DEFINITION:
|
|
988
1140
|
return 1;
|
|
989
|
-
case
|
|
1141
|
+
case Classification.NEXTJS_PAGE:
|
|
990
1142
|
return 1;
|
|
991
|
-
case
|
|
1143
|
+
case Classification.UTILITY_MODULE: {
|
|
992
1144
|
if (node && hasRelatedExportNames(
|
|
993
1145
|
(node.exports || []).map((e) => e.name.toLowerCase())
|
|
994
1146
|
)) {
|
|
@@ -996,17 +1148,17 @@ function adjustCohesionForClassification(baseCohesion, classification, node) {
|
|
|
996
1148
|
}
|
|
997
1149
|
return Math.max(0.75, Math.min(1, baseCohesion + 0.35));
|
|
998
1150
|
}
|
|
999
|
-
case
|
|
1151
|
+
case Classification.SERVICE:
|
|
1000
1152
|
return Math.max(0.72, Math.min(1, baseCohesion + 0.3));
|
|
1001
|
-
case
|
|
1153
|
+
case Classification.LAMBDA_HANDLER:
|
|
1002
1154
|
return Math.max(0.75, Math.min(1, baseCohesion + 0.35));
|
|
1003
|
-
case
|
|
1155
|
+
case Classification.EMAIL_TEMPLATE:
|
|
1004
1156
|
return Math.max(0.72, Math.min(1, baseCohesion + 0.3));
|
|
1005
|
-
case
|
|
1157
|
+
case Classification.PARSER:
|
|
1006
1158
|
return Math.max(0.7, Math.min(1, baseCohesion + 0.3));
|
|
1007
|
-
case
|
|
1159
|
+
case Classification.COHESIVE_MODULE:
|
|
1008
1160
|
return Math.max(baseCohesion, 0.7);
|
|
1009
|
-
case
|
|
1161
|
+
case Classification.MIXED_CONCERNS:
|
|
1010
1162
|
return baseCohesion;
|
|
1011
1163
|
default:
|
|
1012
1164
|
return Math.min(1, baseCohesion + 0.1);
|
|
@@ -1055,20 +1207,20 @@ function hasRelatedExportNames(exportNames) {
|
|
|
1055
1207
|
}
|
|
1056
1208
|
function adjustFragmentationForClassification(baseFragmentation, classification) {
|
|
1057
1209
|
switch (classification) {
|
|
1058
|
-
case
|
|
1210
|
+
case Classification.BARREL:
|
|
1059
1211
|
return 0;
|
|
1060
|
-
case
|
|
1212
|
+
case Classification.TYPE_DEFINITION:
|
|
1061
1213
|
return 0;
|
|
1062
|
-
case
|
|
1063
|
-
case
|
|
1064
|
-
case
|
|
1065
|
-
case
|
|
1066
|
-
case
|
|
1067
|
-
case
|
|
1214
|
+
case Classification.UTILITY_MODULE:
|
|
1215
|
+
case Classification.SERVICE:
|
|
1216
|
+
case Classification.LAMBDA_HANDLER:
|
|
1217
|
+
case Classification.EMAIL_TEMPLATE:
|
|
1218
|
+
case Classification.PARSER:
|
|
1219
|
+
case Classification.NEXTJS_PAGE:
|
|
1068
1220
|
return baseFragmentation * 0.2;
|
|
1069
|
-
case
|
|
1221
|
+
case Classification.COHESIVE_MODULE:
|
|
1070
1222
|
return baseFragmentation * 0.3;
|
|
1071
|
-
case
|
|
1223
|
+
case Classification.MIXED_CONCERNS:
|
|
1072
1224
|
return baseFragmentation;
|
|
1073
1225
|
default:
|
|
1074
1226
|
return baseFragmentation * 0.7;
|
|
@@ -1234,10 +1386,10 @@ function analyzeIssues(params) {
|
|
|
1234
1386
|
} = params;
|
|
1235
1387
|
const issues = [];
|
|
1236
1388
|
const recommendations = [];
|
|
1237
|
-
let severity =
|
|
1389
|
+
let severity = import_core6.Severity.Info;
|
|
1238
1390
|
let potentialSavings = 0;
|
|
1239
1391
|
if (circularDeps.length > 0) {
|
|
1240
|
-
severity =
|
|
1392
|
+
severity = import_core6.Severity.Critical;
|
|
1241
1393
|
issues.push(`Part of ${circularDeps.length} circular dependency chain(s)`);
|
|
1242
1394
|
recommendations.push(
|
|
1243
1395
|
"Break circular dependencies by extracting interfaces or using dependency injection"
|
|
@@ -1245,12 +1397,12 @@ function analyzeIssues(params) {
|
|
|
1245
1397
|
potentialSavings += contextBudget * 0.2;
|
|
1246
1398
|
}
|
|
1247
1399
|
if (importDepth > maxDepth * 1.5) {
|
|
1248
|
-
severity =
|
|
1400
|
+
severity = import_core6.Severity.Critical;
|
|
1249
1401
|
issues.push(`Import depth ${importDepth} exceeds limit by 50%`);
|
|
1250
1402
|
recommendations.push("Flatten dependency tree or use facade pattern");
|
|
1251
1403
|
potentialSavings += contextBudget * 0.3;
|
|
1252
1404
|
} else if (importDepth > maxDepth) {
|
|
1253
|
-
if (severity !==
|
|
1405
|
+
if (severity !== import_core6.Severity.Critical) severity = import_core6.Severity.Major;
|
|
1254
1406
|
issues.push(
|
|
1255
1407
|
`Import depth ${importDepth} exceeds recommended maximum ${maxDepth}`
|
|
1256
1408
|
);
|
|
@@ -1258,7 +1410,7 @@ function analyzeIssues(params) {
|
|
|
1258
1410
|
potentialSavings += contextBudget * 0.15;
|
|
1259
1411
|
}
|
|
1260
1412
|
if (contextBudget > maxContextBudget * 1.5) {
|
|
1261
|
-
severity =
|
|
1413
|
+
severity = import_core6.Severity.Critical;
|
|
1262
1414
|
issues.push(
|
|
1263
1415
|
`Context budget ${contextBudget.toLocaleString()} tokens is 50% over limit`
|
|
1264
1416
|
);
|
|
@@ -1267,7 +1419,7 @@ function analyzeIssues(params) {
|
|
|
1267
1419
|
);
|
|
1268
1420
|
potentialSavings += contextBudget * 0.4;
|
|
1269
1421
|
} else if (contextBudget > maxContextBudget) {
|
|
1270
|
-
if (severity !==
|
|
1422
|
+
if (severity !== import_core6.Severity.Critical) severity = import_core6.Severity.Major;
|
|
1271
1423
|
issues.push(
|
|
1272
1424
|
`Context budget ${contextBudget.toLocaleString()} exceeds ${maxContextBudget.toLocaleString()}`
|
|
1273
1425
|
);
|
|
@@ -1275,7 +1427,7 @@ function analyzeIssues(params) {
|
|
|
1275
1427
|
potentialSavings += contextBudget * 0.2;
|
|
1276
1428
|
}
|
|
1277
1429
|
if (cohesionScore < minCohesion * 0.5) {
|
|
1278
|
-
if (severity !==
|
|
1430
|
+
if (severity !== import_core6.Severity.Critical) severity = import_core6.Severity.Major;
|
|
1279
1431
|
issues.push(
|
|
1280
1432
|
`Very low cohesion (${(cohesionScore * 100).toFixed(0)}%) - mixed concerns`
|
|
1281
1433
|
);
|
|
@@ -1284,14 +1436,14 @@ function analyzeIssues(params) {
|
|
|
1284
1436
|
);
|
|
1285
1437
|
potentialSavings += contextBudget * 0.25;
|
|
1286
1438
|
} else if (cohesionScore < minCohesion) {
|
|
1287
|
-
if (severity ===
|
|
1439
|
+
if (severity === import_core6.Severity.Info) severity = import_core6.Severity.Minor;
|
|
1288
1440
|
issues.push(`Low cohesion (${(cohesionScore * 100).toFixed(0)}%)`);
|
|
1289
1441
|
recommendations.push("Consider grouping related exports together");
|
|
1290
1442
|
potentialSavings += contextBudget * 0.1;
|
|
1291
1443
|
}
|
|
1292
1444
|
if (fragmentationScore > maxFragmentation) {
|
|
1293
|
-
if (severity ===
|
|
1294
|
-
severity =
|
|
1445
|
+
if (severity === import_core6.Severity.Info || severity === import_core6.Severity.Minor)
|
|
1446
|
+
severity = import_core6.Severity.Minor;
|
|
1295
1447
|
issues.push(
|
|
1296
1448
|
`High fragmentation (${(fragmentationScore * 100).toFixed(0)}%) - scattered implementation`
|
|
1297
1449
|
);
|
|
@@ -1305,7 +1457,7 @@ function analyzeIssues(params) {
|
|
|
1305
1457
|
if (isBuildArtifact(file)) {
|
|
1306
1458
|
issues.push("Detected build artifact (bundled/output file)");
|
|
1307
1459
|
recommendations.push("Exclude build outputs from analysis");
|
|
1308
|
-
severity =
|
|
1460
|
+
severity = import_core6.Severity.Info;
|
|
1309
1461
|
potentialSavings = 0;
|
|
1310
1462
|
}
|
|
1311
1463
|
return {
|
|
@@ -1319,147 +1471,192 @@ function isBuildArtifact(filePath) {
|
|
|
1319
1471
|
const lower = filePath.toLowerCase();
|
|
1320
1472
|
return lower.includes("/node_modules/") || lower.includes("/dist/") || lower.includes("/build/") || lower.includes("/out/") || lower.includes("/.next/");
|
|
1321
1473
|
}
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
criticalIssues: 0,
|
|
1345
|
-
majorIssues: 0,
|
|
1346
|
-
minorIssues: 0,
|
|
1347
|
-
totalPotentialSavings: 0,
|
|
1348
|
-
topExpensiveFiles: [],
|
|
1349
|
-
config
|
|
1350
|
-
};
|
|
1351
|
-
}
|
|
1352
|
-
const totalFiles = results.length;
|
|
1353
|
-
const totalTokens = results.reduce((sum, r) => sum + r.tokenCost, 0);
|
|
1354
|
-
const totalContextBudget = results.reduce(
|
|
1355
|
-
(sum, r) => sum + r.contextBudget,
|
|
1356
|
-
0
|
|
1474
|
+
async function analyzeContext(options) {
|
|
1475
|
+
const {
|
|
1476
|
+
maxDepth = 5,
|
|
1477
|
+
maxContextBudget = 1e4,
|
|
1478
|
+
minCohesion = 0.6,
|
|
1479
|
+
maxFragmentation = 0.5,
|
|
1480
|
+
focus = "all",
|
|
1481
|
+
includeNodeModules = false,
|
|
1482
|
+
...scanOptions
|
|
1483
|
+
} = options;
|
|
1484
|
+
const files = await (0, import_core6.scanFiles)({
|
|
1485
|
+
...scanOptions,
|
|
1486
|
+
exclude: includeNodeModules && scanOptions.exclude ? scanOptions.exclude.filter(
|
|
1487
|
+
(pattern) => pattern !== "**/node_modules/**"
|
|
1488
|
+
) : scanOptions.exclude
|
|
1489
|
+
});
|
|
1490
|
+
const pythonFiles = files.filter((f) => f.toLowerCase().endsWith(".py"));
|
|
1491
|
+
const fileContents = await Promise.all(
|
|
1492
|
+
files.map(async (file) => ({
|
|
1493
|
+
file,
|
|
1494
|
+
content: await (0, import_core6.readFileContent)(file)
|
|
1495
|
+
}))
|
|
1357
1496
|
);
|
|
1358
|
-
const
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1497
|
+
const graph = buildDependencyGraph(
|
|
1498
|
+
fileContents.filter((f) => !f.file.toLowerCase().endsWith(".py"))
|
|
1499
|
+
);
|
|
1500
|
+
let pythonResults = [];
|
|
1501
|
+
if (pythonFiles.length > 0) {
|
|
1502
|
+
const { analyzePythonContext: analyzePythonContext2 } = await Promise.resolve().then(() => (init_python_context(), python_context_exports));
|
|
1503
|
+
const pythonMetrics = await analyzePythonContext2(
|
|
1504
|
+
pythonFiles,
|
|
1505
|
+
scanOptions.rootDir || options.rootDir || "."
|
|
1506
|
+
);
|
|
1507
|
+
pythonResults = pythonMetrics.map((metric) => {
|
|
1508
|
+
const { severity, issues, recommendations, potentialSavings } = analyzeIssues({
|
|
1509
|
+
file: metric.file,
|
|
1510
|
+
importDepth: metric.importDepth,
|
|
1511
|
+
contextBudget: metric.contextBudget,
|
|
1512
|
+
cohesionScore: metric.cohesion,
|
|
1513
|
+
fragmentationScore: 0,
|
|
1514
|
+
maxDepth,
|
|
1515
|
+
maxContextBudget,
|
|
1516
|
+
minCohesion,
|
|
1517
|
+
maxFragmentation,
|
|
1518
|
+
circularDeps: metric.metrics.circularDependencies.map(
|
|
1519
|
+
(cycle) => cycle.split(" \u2192 ")
|
|
1520
|
+
)
|
|
1521
|
+
});
|
|
1522
|
+
return {
|
|
1523
|
+
file: metric.file,
|
|
1524
|
+
tokenCost: Math.floor(
|
|
1525
|
+
metric.contextBudget / (1 + metric.imports.length || 1)
|
|
1526
|
+
),
|
|
1527
|
+
linesOfCode: metric.metrics.linesOfCode,
|
|
1528
|
+
importDepth: metric.importDepth,
|
|
1529
|
+
dependencyCount: metric.imports.length,
|
|
1530
|
+
dependencyList: metric.imports.map(
|
|
1531
|
+
(imp) => imp.resolvedPath || imp.source
|
|
1532
|
+
),
|
|
1533
|
+
circularDeps: metric.metrics.circularDependencies.map(
|
|
1534
|
+
(cycle) => cycle.split(" \u2192 ")
|
|
1535
|
+
),
|
|
1536
|
+
cohesionScore: metric.cohesion,
|
|
1537
|
+
domains: ["python"],
|
|
1538
|
+
exportCount: metric.exports.length,
|
|
1539
|
+
contextBudget: metric.contextBudget,
|
|
1540
|
+
fragmentationScore: 0,
|
|
1541
|
+
relatedFiles: [],
|
|
1542
|
+
fileClassification: "unknown",
|
|
1543
|
+
severity,
|
|
1544
|
+
issues,
|
|
1545
|
+
recommendations,
|
|
1546
|
+
potentialSavings
|
|
1547
|
+
};
|
|
1548
|
+
});
|
|
1549
|
+
}
|
|
1550
|
+
const circularDeps = detectCircularDependencies(graph);
|
|
1551
|
+
const useLogScale = files.length >= 500;
|
|
1552
|
+
const clusters = detectModuleClusters(graph, { useLogScale });
|
|
1553
|
+
const fragmentationMap = /* @__PURE__ */ new Map();
|
|
1554
|
+
for (const cluster of clusters) {
|
|
1555
|
+
for (const file of cluster.files) {
|
|
1556
|
+
fragmentationMap.set(file, cluster.fragmentationScore);
|
|
1557
|
+
}
|
|
1558
|
+
}
|
|
1559
|
+
const results = [];
|
|
1560
|
+
for (const { file } of fileContents) {
|
|
1561
|
+
const node = graph.nodes.get(file);
|
|
1562
|
+
if (!node) continue;
|
|
1563
|
+
const importDepth = focus === "depth" || focus === "all" ? calculateImportDepth(file, graph) : 0;
|
|
1564
|
+
const dependencyList = focus === "depth" || focus === "all" ? getTransitiveDependencies(file, graph) : [];
|
|
1565
|
+
const contextBudget = focus === "all" ? calculateContextBudget(file, graph) : node.tokenCost;
|
|
1566
|
+
const cohesionScore = focus === "cohesion" || focus === "all" ? calculateCohesion(node.exports, file, {
|
|
1567
|
+
coUsageMatrix: graph.coUsageMatrix
|
|
1568
|
+
}) : 1;
|
|
1569
|
+
const fragmentationScore = fragmentationMap.get(file) || 0;
|
|
1570
|
+
const relatedFiles = [];
|
|
1571
|
+
for (const cluster of clusters) {
|
|
1572
|
+
if (cluster.files.includes(file)) {
|
|
1573
|
+
relatedFiles.push(...cluster.files.filter((f) => f !== file));
|
|
1574
|
+
break;
|
|
1400
1575
|
}
|
|
1401
1576
|
}
|
|
1402
|
-
const
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1577
|
+
const { issues } = analyzeIssues({
|
|
1578
|
+
file,
|
|
1579
|
+
importDepth,
|
|
1580
|
+
contextBudget,
|
|
1581
|
+
cohesionScore,
|
|
1407
1582
|
fragmentationScore,
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1583
|
+
maxDepth,
|
|
1584
|
+
maxContextBudget,
|
|
1585
|
+
minCohesion,
|
|
1586
|
+
maxFragmentation,
|
|
1587
|
+
circularDeps
|
|
1588
|
+
});
|
|
1589
|
+
const domains = [
|
|
1590
|
+
...new Set(node.exports.map((e) => e.inferredDomain || "unknown"))
|
|
1591
|
+
];
|
|
1592
|
+
const fileClassification = classifyFile(node);
|
|
1593
|
+
const adjustedCohesionScore = adjustCohesionForClassification(
|
|
1594
|
+
cohesionScore,
|
|
1595
|
+
fileClassification,
|
|
1596
|
+
node
|
|
1597
|
+
);
|
|
1598
|
+
const adjustedFragmentationScore = adjustFragmentationForClassification(
|
|
1599
|
+
fragmentationScore,
|
|
1600
|
+
fileClassification
|
|
1601
|
+
);
|
|
1602
|
+
const classificationRecommendations = getClassificationRecommendations(
|
|
1603
|
+
fileClassification,
|
|
1604
|
+
file,
|
|
1605
|
+
issues
|
|
1606
|
+
);
|
|
1607
|
+
const {
|
|
1608
|
+
severity: adjustedSeverity,
|
|
1609
|
+
issues: adjustedIssues,
|
|
1610
|
+
recommendations: finalRecommendations,
|
|
1611
|
+
potentialSavings: adjustedSavings
|
|
1612
|
+
} = analyzeIssues({
|
|
1613
|
+
file,
|
|
1614
|
+
importDepth,
|
|
1615
|
+
contextBudget,
|
|
1616
|
+
cohesionScore: adjustedCohesionScore,
|
|
1617
|
+
fragmentationScore: adjustedFragmentationScore,
|
|
1618
|
+
maxDepth,
|
|
1619
|
+
maxContextBudget,
|
|
1620
|
+
minCohesion,
|
|
1621
|
+
maxFragmentation,
|
|
1622
|
+
circularDeps
|
|
1623
|
+
});
|
|
1624
|
+
results.push({
|
|
1625
|
+
file,
|
|
1626
|
+
tokenCost: node.tokenCost,
|
|
1627
|
+
linesOfCode: node.linesOfCode,
|
|
1628
|
+
importDepth,
|
|
1629
|
+
dependencyCount: dependencyList.length,
|
|
1630
|
+
dependencyList,
|
|
1631
|
+
circularDeps: circularDeps.filter((cycle) => cycle.includes(file)),
|
|
1632
|
+
cohesionScore: adjustedCohesionScore,
|
|
1633
|
+
domains,
|
|
1634
|
+
exportCount: node.exports.length,
|
|
1635
|
+
contextBudget,
|
|
1636
|
+
fragmentationScore: adjustedFragmentationScore,
|
|
1637
|
+
relatedFiles,
|
|
1638
|
+
fileClassification,
|
|
1639
|
+
severity: adjustedSeverity,
|
|
1640
|
+
issues: adjustedIssues,
|
|
1641
|
+
recommendations: [
|
|
1642
|
+
...finalRecommendations,
|
|
1643
|
+
...classificationRecommendations.slice(0, 1)
|
|
1644
|
+
],
|
|
1645
|
+
potentialSavings: adjustedSavings
|
|
1419
1646
|
});
|
|
1420
1647
|
}
|
|
1421
|
-
const
|
|
1422
|
-
const
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
(sum, r) => sum + r.potentialSavings,
|
|
1430
|
-
0
|
|
1431
|
-
);
|
|
1432
|
-
const topExpensiveFiles = results.sort((a, b) => b.contextBudget - a.contextBudget).slice(0, 10).map((r) => ({
|
|
1433
|
-
file: r.file,
|
|
1434
|
-
contextBudget: r.contextBudget,
|
|
1435
|
-
severity: r.severity
|
|
1436
|
-
}));
|
|
1437
|
-
return {
|
|
1438
|
-
totalFiles,
|
|
1439
|
-
totalTokens,
|
|
1440
|
-
avgContextBudget,
|
|
1441
|
-
maxContextBudget,
|
|
1442
|
-
avgImportDepth,
|
|
1443
|
-
maxImportDepth,
|
|
1444
|
-
deepFiles,
|
|
1445
|
-
avgFragmentation,
|
|
1446
|
-
fragmentedModules,
|
|
1447
|
-
avgCohesion,
|
|
1448
|
-
lowCohesionFiles,
|
|
1449
|
-
criticalIssues,
|
|
1450
|
-
majorIssues,
|
|
1451
|
-
minorIssues,
|
|
1452
|
-
totalPotentialSavings,
|
|
1453
|
-
topExpensiveFiles,
|
|
1454
|
-
config
|
|
1455
|
-
};
|
|
1648
|
+
const allResults = [...results, ...pythonResults];
|
|
1649
|
+
const finalSummary = generateSummary(allResults, options);
|
|
1650
|
+
return allResults.sort((a, b) => {
|
|
1651
|
+
const severityOrder = { critical: 0, major: 1, minor: 2, info: 3 };
|
|
1652
|
+
const severityDiff = severityOrder[a.severity] - severityOrder[b.severity];
|
|
1653
|
+
if (severityDiff !== 0) return severityDiff;
|
|
1654
|
+
return b.contextBudget - a.contextBudget;
|
|
1655
|
+
});
|
|
1456
1656
|
}
|
|
1457
1657
|
|
|
1458
|
-
// src/provider.ts
|
|
1459
|
-
var import_core7 = require("@aiready/core");
|
|
1460
|
-
|
|
1461
1658
|
// src/scoring.ts
|
|
1462
|
-
var
|
|
1659
|
+
var import_core7 = require("@aiready/core");
|
|
1463
1660
|
function calculateContextScore(summary, costConfig) {
|
|
1464
1661
|
const {
|
|
1465
1662
|
avgContextBudget,
|
|
@@ -1555,8 +1752,8 @@ function calculateContextScore(summary, costConfig) {
|
|
|
1555
1752
|
priority: "high"
|
|
1556
1753
|
});
|
|
1557
1754
|
}
|
|
1558
|
-
const cfg = { ...
|
|
1559
|
-
const estimatedMonthlyCost = (0,
|
|
1755
|
+
const cfg = { ...import_core7.DEFAULT_COST_CONFIG, ...costConfig };
|
|
1756
|
+
const estimatedMonthlyCost = (0, import_core7.calculateMonthlyCost)(
|
|
1560
1757
|
avgContextBudget * (summary.totalFiles || 1),
|
|
1561
1758
|
cfg
|
|
1562
1759
|
);
|
|
@@ -1564,9 +1761,9 @@ function calculateContextScore(summary, costConfig) {
|
|
|
1564
1761
|
...Array(criticalIssues).fill({ severity: "critical" }),
|
|
1565
1762
|
...Array(majorIssues).fill({ severity: "major" })
|
|
1566
1763
|
];
|
|
1567
|
-
const productivityImpact = (0,
|
|
1764
|
+
const productivityImpact = (0, import_core7.calculateProductivityImpact)(issues);
|
|
1568
1765
|
return {
|
|
1569
|
-
toolName:
|
|
1766
|
+
toolName: import_core7.ToolName.ContextAnalyzer,
|
|
1570
1767
|
score,
|
|
1571
1768
|
rawMetrics: {
|
|
1572
1769
|
avgContextBudget: Math.round(avgContextBudget),
|
|
@@ -1586,7 +1783,7 @@ function calculateContextScore(summary, costConfig) {
|
|
|
1586
1783
|
|
|
1587
1784
|
// src/provider.ts
|
|
1588
1785
|
var ContextAnalyzerProvider = {
|
|
1589
|
-
id:
|
|
1786
|
+
id: import_core8.ToolName.ContextAnalyzer,
|
|
1590
1787
|
alias: ["context", "fragmentation", "budget"],
|
|
1591
1788
|
async analyze(options) {
|
|
1592
1789
|
const results = await analyzeContext(options);
|
|
@@ -1595,7 +1792,7 @@ var ContextAnalyzerProvider = {
|
|
|
1595
1792
|
(r) => ({
|
|
1596
1793
|
fileName: r.file,
|
|
1597
1794
|
issues: r.issues.map((msg) => ({
|
|
1598
|
-
type:
|
|
1795
|
+
type: import_core8.IssueType.ContextFragmentation,
|
|
1599
1796
|
severity: r.severity,
|
|
1600
1797
|
message: msg,
|
|
1601
1798
|
location: { file: r.file, line: 1 },
|
|
@@ -1608,13 +1805,13 @@ var ContextAnalyzerProvider = {
|
|
|
1608
1805
|
}
|
|
1609
1806
|
})
|
|
1610
1807
|
);
|
|
1611
|
-
return
|
|
1808
|
+
return import_core8.SpokeOutputSchema.parse({
|
|
1612
1809
|
results: normalizedResults,
|
|
1613
1810
|
summary: {
|
|
1614
1811
|
...summary
|
|
1615
1812
|
},
|
|
1616
1813
|
metadata: {
|
|
1617
|
-
toolName:
|
|
1814
|
+
toolName: import_core8.ToolName.ContextAnalyzer,
|
|
1618
1815
|
version: "0.17.5",
|
|
1619
1816
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1620
1817
|
}
|
|
@@ -1628,193 +1825,10 @@ var ContextAnalyzerProvider = {
|
|
|
1628
1825
|
};
|
|
1629
1826
|
|
|
1630
1827
|
// src/defaults.ts
|
|
1631
|
-
var
|
|
1828
|
+
var import_core9 = require("@aiready/core");
|
|
1632
1829
|
|
|
1633
1830
|
// src/index.ts
|
|
1634
1831
|
import_core10.ToolRegistry.register(ContextAnalyzerProvider);
|
|
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_core10.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_core10.readFileContent)(file)
|
|
1656
|
-
}))
|
|
1657
|
-
);
|
|
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;
|
|
1736
|
-
}
|
|
1737
|
-
}
|
|
1738
|
-
const { issues } = analyzeIssues({
|
|
1739
|
-
file,
|
|
1740
|
-
importDepth,
|
|
1741
|
-
contextBudget,
|
|
1742
|
-
cohesionScore,
|
|
1743
|
-
fragmentationScore,
|
|
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
|
|
1807
|
-
});
|
|
1808
|
-
}
|
|
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
|
-
});
|
|
1817
|
-
}
|
|
1818
1832
|
|
|
1819
1833
|
// src/cli.ts
|
|
1820
1834
|
var import_chalk = __toESM(require("chalk"));
|