@aiready/context-analyzer 0.21.6 → 0.21.8
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 +11 -11
- package/.turbo/turbo-lint.log +5 -6
- package/.turbo/turbo-test.log +27 -27
- package/dist/chunk-2HE27YEV.mjs +1739 -0
- package/dist/chunk-D25B5LZR.mjs +1739 -0
- package/dist/chunk-KDUUZQBK.mjs +1692 -0
- package/dist/chunk-KWIS5FQP.mjs +1739 -0
- package/dist/chunk-RRB2C34Q.mjs +1738 -0
- package/dist/chunk-XTAXUNQN.mjs +1742 -0
- package/dist/cli.js +136 -144
- package/dist/cli.mjs +1 -1
- package/dist/index.d.mts +72 -78
- package/dist/index.d.ts +72 -78
- package/dist/index.js +137 -169
- package/dist/index.mjs +4 -27
- package/package.json +2 -2
- package/src/analyzer.ts +17 -9
- package/src/ast-utils.ts +2 -2
- package/src/classifier.ts +13 -328
- package/src/cli-action.ts +7 -0
- package/src/cli.ts +0 -17
- package/src/cluster-detector.ts +27 -23
- package/src/defaults.ts +3 -0
- package/src/graph-builder.ts +1 -6
- package/src/heuristics.ts +216 -0
- package/src/issue-analyzer.ts +15 -0
- package/src/metrics.ts +9 -0
- package/src/scoring.ts +3 -5
- package/src/semantic-analysis.ts +7 -0
- package/src/summary.ts +1 -1
- package/src/types.ts +52 -20
- package/src/utils/string-utils.ts +6 -2
package/dist/cli.js
CHANGED
|
@@ -542,7 +542,7 @@ function extractExportsWithAST(content, filePath, domainOptions, fileImports) {
|
|
|
542
542
|
dependencies: exp.dependencies,
|
|
543
543
|
typeReferences: exp.typeReferences
|
|
544
544
|
}));
|
|
545
|
-
} catch
|
|
545
|
+
} catch {
|
|
546
546
|
return extractExports(content, filePath, domainOptions, fileImports);
|
|
547
547
|
}
|
|
548
548
|
}
|
|
@@ -879,6 +879,21 @@ function detectModuleClusters(graph, options) {
|
|
|
879
879
|
domainMap.get(primaryDomain).push(file);
|
|
880
880
|
}
|
|
881
881
|
const clusters = [];
|
|
882
|
+
const generateSuggestedStructure = (files, tokens, fragmentation) => {
|
|
883
|
+
const targetFiles = Math.max(1, Math.ceil(tokens / 1e4));
|
|
884
|
+
const plan = [];
|
|
885
|
+
if (fragmentation > 0.5) {
|
|
886
|
+
plan.push(
|
|
887
|
+
`Consolidate ${files.length} files scattered across multiple directories into ${targetFiles} core module(s)`
|
|
888
|
+
);
|
|
889
|
+
}
|
|
890
|
+
if (tokens > 2e4) {
|
|
891
|
+
plan.push(
|
|
892
|
+
`Domain logic is very large (${Math.round(tokens / 1e3)}k tokens). Ensure clear sub-domain boundaries.`
|
|
893
|
+
);
|
|
894
|
+
}
|
|
895
|
+
return { targetFiles, consolidationPlan: plan };
|
|
896
|
+
};
|
|
882
897
|
for (const [domain, files] of domainMap.entries()) {
|
|
883
898
|
if (files.length < 2 || domain === "unknown") continue;
|
|
884
899
|
const totalTokens = files.reduce((sum, file) => {
|
|
@@ -924,87 +939,15 @@ function detectModuleClusters(graph, options) {
|
|
|
924
939
|
}
|
|
925
940
|
return clusters;
|
|
926
941
|
}
|
|
927
|
-
function generateSuggestedStructure(files, tokens, fragmentation) {
|
|
928
|
-
const targetFiles = Math.max(1, Math.ceil(tokens / 1e4));
|
|
929
|
-
const plan = [];
|
|
930
|
-
if (fragmentation > 0.5) {
|
|
931
|
-
plan.push(
|
|
932
|
-
`Consolidate ${files.length} files scattered across multiple directories into ${targetFiles} core module(s)`
|
|
933
|
-
);
|
|
934
|
-
}
|
|
935
|
-
if (tokens > 2e4) {
|
|
936
|
-
plan.push(
|
|
937
|
-
`Domain logic is very large (${Math.round(tokens / 1e3)}k tokens). Ensure clear sub-domain boundaries.`
|
|
938
|
-
);
|
|
939
|
-
}
|
|
940
|
-
return { targetFiles, consolidationPlan: plan };
|
|
941
|
-
}
|
|
942
942
|
|
|
943
|
-
// src/
|
|
944
|
-
var Classification = {
|
|
945
|
-
BARREL: "barrel-export",
|
|
946
|
-
TYPE_DEFINITION: "type-definition",
|
|
947
|
-
NEXTJS_PAGE: "nextjs-page",
|
|
948
|
-
LAMBDA_HANDLER: "lambda-handler",
|
|
949
|
-
SERVICE: "service-file",
|
|
950
|
-
EMAIL_TEMPLATE: "email-template",
|
|
951
|
-
PARSER: "parser-file",
|
|
952
|
-
COHESIVE_MODULE: "cohesive-module",
|
|
953
|
-
UTILITY_MODULE: "utility-module",
|
|
954
|
-
MIXED_CONCERNS: "mixed-concerns",
|
|
955
|
-
UNKNOWN: "unknown"
|
|
956
|
-
};
|
|
957
|
-
function classifyFile(node, cohesionScore = 1, domains = []) {
|
|
958
|
-
if (isBarrelExport(node)) {
|
|
959
|
-
return Classification.BARREL;
|
|
960
|
-
}
|
|
961
|
-
if (isTypeDefinition(node)) {
|
|
962
|
-
return Classification.TYPE_DEFINITION;
|
|
963
|
-
}
|
|
964
|
-
if (isNextJsPage(node)) {
|
|
965
|
-
return Classification.NEXTJS_PAGE;
|
|
966
|
-
}
|
|
967
|
-
if (isLambdaHandler(node)) {
|
|
968
|
-
return Classification.LAMBDA_HANDLER;
|
|
969
|
-
}
|
|
970
|
-
if (isServiceFile(node)) {
|
|
971
|
-
return Classification.SERVICE;
|
|
972
|
-
}
|
|
973
|
-
if (isEmailTemplate(node)) {
|
|
974
|
-
return Classification.EMAIL_TEMPLATE;
|
|
975
|
-
}
|
|
976
|
-
if (isParserFile(node)) {
|
|
977
|
-
return Classification.PARSER;
|
|
978
|
-
}
|
|
979
|
-
if (isSessionFile(node)) {
|
|
980
|
-
if (cohesionScore >= 0.25 && domains.length <= 1)
|
|
981
|
-
return Classification.COHESIVE_MODULE;
|
|
982
|
-
return Classification.UTILITY_MODULE;
|
|
983
|
-
}
|
|
984
|
-
if (isUtilityModule(node)) {
|
|
985
|
-
return Classification.UTILITY_MODULE;
|
|
986
|
-
}
|
|
987
|
-
if (isConfigFile(node)) {
|
|
988
|
-
return Classification.COHESIVE_MODULE;
|
|
989
|
-
}
|
|
990
|
-
if (domains.length <= 1 && domains[0] !== "unknown") {
|
|
991
|
-
return Classification.COHESIVE_MODULE;
|
|
992
|
-
}
|
|
993
|
-
if (domains.length > 1 && cohesionScore < 0.4) {
|
|
994
|
-
return Classification.MIXED_CONCERNS;
|
|
995
|
-
}
|
|
996
|
-
if (cohesionScore >= 0.7) {
|
|
997
|
-
return Classification.COHESIVE_MODULE;
|
|
998
|
-
}
|
|
999
|
-
return Classification.UNKNOWN;
|
|
1000
|
-
}
|
|
943
|
+
// src/heuristics.ts
|
|
1001
944
|
function isBarrelExport(node) {
|
|
1002
945
|
const { file, exports: exports2 } = node;
|
|
1003
946
|
const fileName = file.split("/").pop()?.toLowerCase();
|
|
1004
947
|
const isIndexFile = fileName === "index.ts" || fileName === "index.js";
|
|
1005
948
|
const isSmallAndManyExports = node.tokenCost < 1e3 && (exports2 || []).length > 5;
|
|
1006
949
|
const isReexportPattern = (exports2 || []).length >= 5 && (exports2 || []).every(
|
|
1007
|
-
(e) =>
|
|
950
|
+
(e) => ["const", "function", "type", "interface"].includes(e.type)
|
|
1008
951
|
);
|
|
1009
952
|
return !!isIndexFile || !!isSmallAndManyExports || !!isReexportPattern;
|
|
1010
953
|
}
|
|
@@ -1014,20 +957,19 @@ function isTypeDefinition(node) {
|
|
|
1014
957
|
const nodeExports = node.exports || [];
|
|
1015
958
|
const hasExports = nodeExports.length > 0;
|
|
1016
959
|
const areAllTypes = hasExports && nodeExports.every((e) => e.type === "type" || e.type === "interface");
|
|
1017
|
-
const
|
|
1018
|
-
|
|
1019
|
-
return allTypes || isTypePath && hasExports;
|
|
960
|
+
const isTypePath = /\/(types|interfaces|models)\//i.test(file);
|
|
961
|
+
return !!areAllTypes || isTypePath && hasExports;
|
|
1020
962
|
}
|
|
1021
963
|
function isUtilityModule(node) {
|
|
1022
964
|
const { file } = node;
|
|
1023
|
-
const isUtilPath =
|
|
1024
|
-
const fileName = file.split("/").pop()?.toLowerCase();
|
|
1025
|
-
const isUtilName =
|
|
1026
|
-
return
|
|
965
|
+
const isUtilPath = /\/(utils|helpers|util|helper)\//i.test(file);
|
|
966
|
+
const fileName = file.split("/").pop()?.toLowerCase() || "";
|
|
967
|
+
const isUtilName = /(utils\.|helpers\.|util\.|helper\.)/i.test(fileName);
|
|
968
|
+
return isUtilPath || isUtilName;
|
|
1027
969
|
}
|
|
1028
970
|
function isLambdaHandler(node) {
|
|
1029
971
|
const { file, exports: exports2 } = node;
|
|
1030
|
-
const fileName = file.split("/").pop()?.toLowerCase();
|
|
972
|
+
const fileName = file.split("/").pop()?.toLowerCase() || "";
|
|
1031
973
|
const handlerPatterns = [
|
|
1032
974
|
"handler",
|
|
1033
975
|
".handler.",
|
|
@@ -1036,33 +978,29 @@ function isLambdaHandler(node) {
|
|
|
1036
978
|
".lambda.",
|
|
1037
979
|
"-lambda."
|
|
1038
980
|
];
|
|
1039
|
-
const isHandlerName = handlerPatterns.some(
|
|
1040
|
-
|
|
1041
|
-
);
|
|
1042
|
-
const isHandlerPath = file.toLowerCase().includes("/handlers/") || file.toLowerCase().includes("/lambdas/") || file.toLowerCase().includes("/lambda/") || file.toLowerCase().includes("/functions/");
|
|
981
|
+
const isHandlerName = handlerPatterns.some((p) => fileName.includes(p));
|
|
982
|
+
const isHandlerPath = /\/(handlers|lambdas|lambda|functions)\//i.test(file);
|
|
1043
983
|
const hasHandlerExport = (exports2 || []).some(
|
|
1044
|
-
(e) =>
|
|
984
|
+
(e) => ["handler", "main", "lambdahandler"].includes(e.name.toLowerCase()) || e.name.toLowerCase().endsWith("handler")
|
|
1045
985
|
);
|
|
1046
|
-
return
|
|
986
|
+
return isHandlerName || isHandlerPath || hasHandlerExport;
|
|
1047
987
|
}
|
|
1048
988
|
function isServiceFile(node) {
|
|
1049
989
|
const { file, exports: exports2 } = node;
|
|
1050
|
-
const fileName = file.split("/").pop()?.toLowerCase();
|
|
990
|
+
const fileName = file.split("/").pop()?.toLowerCase() || "";
|
|
1051
991
|
const servicePatterns = ["service", ".service.", "-service.", "_service."];
|
|
1052
|
-
const isServiceName = servicePatterns.some(
|
|
1053
|
-
(pattern) => fileName?.includes(pattern)
|
|
1054
|
-
);
|
|
992
|
+
const isServiceName = servicePatterns.some((p) => fileName.includes(p));
|
|
1055
993
|
const isServicePath = file.toLowerCase().includes("/services/");
|
|
1056
994
|
const hasServiceNamedExport = (exports2 || []).some(
|
|
1057
|
-
(e) => e.name.toLowerCase().includes("service")
|
|
995
|
+
(e) => e.name.toLowerCase().includes("service")
|
|
1058
996
|
);
|
|
1059
997
|
const hasClassExport = (exports2 || []).some((e) => e.type === "class");
|
|
1060
|
-
return
|
|
998
|
+
return isServiceName || isServicePath || hasServiceNamedExport && hasClassExport;
|
|
1061
999
|
}
|
|
1062
1000
|
function isEmailTemplate(node) {
|
|
1063
1001
|
const { file, exports: exports2 } = node;
|
|
1064
|
-
const fileName = file.split("/").pop()?.toLowerCase();
|
|
1065
|
-
const
|
|
1002
|
+
const fileName = file.split("/").pop()?.toLowerCase() || "";
|
|
1003
|
+
const emailPatterns = [
|
|
1066
1004
|
"-email-",
|
|
1067
1005
|
".email.",
|
|
1068
1006
|
"_email_",
|
|
@@ -1072,78 +1010,51 @@ function isEmailTemplate(node) {
|
|
|
1072
1010
|
"-mail.",
|
|
1073
1011
|
".mail."
|
|
1074
1012
|
];
|
|
1075
|
-
const
|
|
1076
|
-
|
|
1077
|
-
);
|
|
1078
|
-
const isEmailPath = file.toLowerCase().includes("/emails/") || file.toLowerCase().includes("/mail/") || file.toLowerCase().includes("/notifications/");
|
|
1013
|
+
const isEmailName = emailPatterns.some((p) => fileName.includes(p));
|
|
1014
|
+
const isEmailPath = /\/(emails|mail|notifications)\//i.test(file);
|
|
1079
1015
|
const hasTemplateFunction = (exports2 || []).some(
|
|
1080
|
-
(e) => e.type === "function" && (e.name.toLowerCase().startsWith("render") || e.name.toLowerCase().startsWith("generate")
|
|
1016
|
+
(e) => e.type === "function" && (e.name.toLowerCase().startsWith("render") || e.name.toLowerCase().startsWith("generate"))
|
|
1081
1017
|
);
|
|
1082
|
-
return
|
|
1018
|
+
return isEmailPath || isEmailName || hasTemplateFunction;
|
|
1083
1019
|
}
|
|
1084
1020
|
function isParserFile(node) {
|
|
1085
1021
|
const { file, exports: exports2 } = node;
|
|
1086
|
-
const fileName = file.split("/").pop()?.toLowerCase();
|
|
1022
|
+
const fileName = file.split("/").pop()?.toLowerCase() || "";
|
|
1087
1023
|
const parserPatterns = [
|
|
1088
1024
|
"parser",
|
|
1089
1025
|
".parser.",
|
|
1090
1026
|
"-parser.",
|
|
1091
1027
|
"_parser.",
|
|
1092
1028
|
"transform",
|
|
1093
|
-
".transform.",
|
|
1094
1029
|
"converter",
|
|
1095
1030
|
"mapper",
|
|
1096
1031
|
"serializer"
|
|
1097
1032
|
];
|
|
1098
|
-
const isParserName = parserPatterns.some(
|
|
1099
|
-
|
|
1100
|
-
);
|
|
1101
|
-
const isParserPath = file.toLowerCase().includes("/parsers/") || file.toLowerCase().includes("/transformers/");
|
|
1033
|
+
const isParserName = parserPatterns.some((p) => fileName.includes(p));
|
|
1034
|
+
const isParserPath = /\/(parsers|transformers)\//i.test(file);
|
|
1102
1035
|
const hasParseFunction = (exports2 || []).some(
|
|
1103
|
-
(e) => e.type === "function" && (e.name.toLowerCase().startsWith("parse") || e.name.toLowerCase().startsWith("transform")
|
|
1036
|
+
(e) => e.type === "function" && (e.name.toLowerCase().startsWith("parse") || e.name.toLowerCase().startsWith("transform"))
|
|
1104
1037
|
);
|
|
1105
|
-
return
|
|
1038
|
+
return isParserName || isParserPath || hasParseFunction;
|
|
1106
1039
|
}
|
|
1107
1040
|
function isSessionFile(node) {
|
|
1108
1041
|
const { file, exports: exports2 } = node;
|
|
1109
|
-
const fileName = file.split("/").pop()?.toLowerCase();
|
|
1042
|
+
const fileName = file.split("/").pop()?.toLowerCase() || "";
|
|
1110
1043
|
const sessionPatterns = ["session", "state", "context", "store"];
|
|
1111
|
-
const isSessionName = sessionPatterns.some(
|
|
1112
|
-
|
|
1113
|
-
);
|
|
1114
|
-
const isSessionPath = file.toLowerCase().includes("/sessions/") || file.toLowerCase().includes("/state/");
|
|
1044
|
+
const isSessionName = sessionPatterns.some((p) => fileName.includes(p));
|
|
1045
|
+
const isSessionPath = /\/(sessions|state)\//i.test(file);
|
|
1115
1046
|
const hasSessionExport = (exports2 || []).some(
|
|
1116
|
-
(e) =>
|
|
1117
|
-
);
|
|
1118
|
-
return !!isSessionName || !!isSessionPath || !!hasSessionExport;
|
|
1119
|
-
}
|
|
1120
|
-
function isConfigFile(node) {
|
|
1121
|
-
const { file, exports: exports2 } = node;
|
|
1122
|
-
const lowerPath = file.toLowerCase();
|
|
1123
|
-
const fileName = file.split("/").pop()?.toLowerCase();
|
|
1124
|
-
const configPatterns = [
|
|
1125
|
-
".config.",
|
|
1126
|
-
"tsconfig",
|
|
1127
|
-
"jest.config",
|
|
1128
|
-
"package.json",
|
|
1129
|
-
"aiready.json",
|
|
1130
|
-
"next.config",
|
|
1131
|
-
"sst.config"
|
|
1132
|
-
];
|
|
1133
|
-
const isConfigName = configPatterns.some((p) => fileName?.includes(p));
|
|
1134
|
-
const isConfigPath = lowerPath.includes("/config/") || lowerPath.includes("/settings/") || lowerPath.includes("/schemas/");
|
|
1135
|
-
const hasSchemaExports = (exports2 || []).some(
|
|
1136
|
-
(e) => e.name.toLowerCase().includes("schema") || e.name.toLowerCase().includes("config") || e.name.toLowerCase().includes("setting")
|
|
1047
|
+
(e) => ["session", "state", "store"].some((p) => e.name.toLowerCase().includes(p))
|
|
1137
1048
|
);
|
|
1138
|
-
return
|
|
1049
|
+
return isSessionName || isSessionPath || hasSessionExport;
|
|
1139
1050
|
}
|
|
1140
1051
|
function isNextJsPage(node) {
|
|
1141
1052
|
const { file, exports: exports2 } = node;
|
|
1142
1053
|
const lowerPath = file.toLowerCase();
|
|
1143
|
-
const fileName = file.split("/").pop()?.toLowerCase();
|
|
1054
|
+
const fileName = file.split("/").pop()?.toLowerCase() || "";
|
|
1144
1055
|
const isInAppDir = lowerPath.includes("/app/") || lowerPath.startsWith("app/");
|
|
1145
|
-
|
|
1146
|
-
|
|
1056
|
+
if (!isInAppDir || fileName !== "page.tsx" && fileName !== "page.ts")
|
|
1057
|
+
return false;
|
|
1147
1058
|
const hasDefaultExport = (exports2 || []).some((e) => e.type === "default");
|
|
1148
1059
|
const nextJsExports = [
|
|
1149
1060
|
"metadata",
|
|
@@ -1152,10 +1063,91 @@ function isNextJsPage(node) {
|
|
|
1152
1063
|
"jsonld",
|
|
1153
1064
|
"icon"
|
|
1154
1065
|
];
|
|
1155
|
-
const
|
|
1066
|
+
const hasNextJsExport = (exports2 || []).some(
|
|
1156
1067
|
(e) => nextJsExports.includes(e.name.toLowerCase())
|
|
1157
1068
|
);
|
|
1158
|
-
return
|
|
1069
|
+
return hasDefaultExport || hasNextJsExport;
|
|
1070
|
+
}
|
|
1071
|
+
function isConfigFile(node) {
|
|
1072
|
+
const { file, exports: exports2 } = node;
|
|
1073
|
+
const lowerPath = file.toLowerCase();
|
|
1074
|
+
const fileName = file.split("/").pop()?.toLowerCase() || "";
|
|
1075
|
+
const configPatterns = [
|
|
1076
|
+
".config.",
|
|
1077
|
+
"tsconfig",
|
|
1078
|
+
"jest.config",
|
|
1079
|
+
"package.json",
|
|
1080
|
+
"aiready.json",
|
|
1081
|
+
"next.config",
|
|
1082
|
+
"sst.config"
|
|
1083
|
+
];
|
|
1084
|
+
const isConfigName = configPatterns.some((p) => fileName.includes(p));
|
|
1085
|
+
const isConfigPath = /\/(config|settings|schemas)\//i.test(lowerPath);
|
|
1086
|
+
const hasSchemaExport = (exports2 || []).some(
|
|
1087
|
+
(e) => ["schema", "config", "setting"].some(
|
|
1088
|
+
(p) => e.name.toLowerCase().includes(p)
|
|
1089
|
+
)
|
|
1090
|
+
);
|
|
1091
|
+
return isConfigName || isConfigPath || hasSchemaExport;
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
// src/classifier.ts
|
|
1095
|
+
var Classification = {
|
|
1096
|
+
BARREL: "barrel-export",
|
|
1097
|
+
TYPE_DEFINITION: "type-definition",
|
|
1098
|
+
NEXTJS_PAGE: "nextjs-page",
|
|
1099
|
+
LAMBDA_HANDLER: "lambda-handler",
|
|
1100
|
+
SERVICE: "service-file",
|
|
1101
|
+
EMAIL_TEMPLATE: "email-template",
|
|
1102
|
+
PARSER: "parser-file",
|
|
1103
|
+
COHESIVE_MODULE: "cohesive-module",
|
|
1104
|
+
UTILITY_MODULE: "utility-module",
|
|
1105
|
+
MIXED_CONCERNS: "mixed-concerns",
|
|
1106
|
+
UNKNOWN: "unknown"
|
|
1107
|
+
};
|
|
1108
|
+
function classifyFile(node, cohesionScore = 1, domains = []) {
|
|
1109
|
+
if (isBarrelExport(node)) {
|
|
1110
|
+
return Classification.BARREL;
|
|
1111
|
+
}
|
|
1112
|
+
if (isTypeDefinition(node)) {
|
|
1113
|
+
return Classification.TYPE_DEFINITION;
|
|
1114
|
+
}
|
|
1115
|
+
if (isNextJsPage(node)) {
|
|
1116
|
+
return Classification.NEXTJS_PAGE;
|
|
1117
|
+
}
|
|
1118
|
+
if (isLambdaHandler(node)) {
|
|
1119
|
+
return Classification.LAMBDA_HANDLER;
|
|
1120
|
+
}
|
|
1121
|
+
if (isServiceFile(node)) {
|
|
1122
|
+
return Classification.SERVICE;
|
|
1123
|
+
}
|
|
1124
|
+
if (isEmailTemplate(node)) {
|
|
1125
|
+
return Classification.EMAIL_TEMPLATE;
|
|
1126
|
+
}
|
|
1127
|
+
if (isParserFile(node)) {
|
|
1128
|
+
return Classification.PARSER;
|
|
1129
|
+
}
|
|
1130
|
+
if (isSessionFile(node)) {
|
|
1131
|
+
if (cohesionScore >= 0.25 && domains.length <= 1)
|
|
1132
|
+
return Classification.COHESIVE_MODULE;
|
|
1133
|
+
return Classification.UTILITY_MODULE;
|
|
1134
|
+
}
|
|
1135
|
+
if (isUtilityModule(node)) {
|
|
1136
|
+
return Classification.UTILITY_MODULE;
|
|
1137
|
+
}
|
|
1138
|
+
if (isConfigFile(node)) {
|
|
1139
|
+
return Classification.COHESIVE_MODULE;
|
|
1140
|
+
}
|
|
1141
|
+
if (domains.length <= 1 && domains[0] !== "unknown") {
|
|
1142
|
+
return Classification.COHESIVE_MODULE;
|
|
1143
|
+
}
|
|
1144
|
+
if (domains.length > 1 && cohesionScore < 0.4) {
|
|
1145
|
+
return Classification.MIXED_CONCERNS;
|
|
1146
|
+
}
|
|
1147
|
+
if (cohesionScore >= 0.7) {
|
|
1148
|
+
return Classification.COHESIVE_MODULE;
|
|
1149
|
+
}
|
|
1150
|
+
return Classification.UNKNOWN;
|
|
1159
1151
|
}
|
|
1160
1152
|
function adjustCohesionForClassification(baseCohesion, classification, node) {
|
|
1161
1153
|
switch (classification) {
|
|
@@ -1315,7 +1307,7 @@ function getClassificationRecommendations(classification, file, issues) {
|
|
|
1315
1307
|
async function analyzeContext(options) {
|
|
1316
1308
|
const {
|
|
1317
1309
|
maxDepth = 5,
|
|
1318
|
-
maxContextBudget =
|
|
1310
|
+
maxContextBudget = 25e3,
|
|
1319
1311
|
minCohesion = 0.6,
|
|
1320
1312
|
maxFragmentation = 0.5,
|
|
1321
1313
|
includeNodeModules = false,
|
package/dist/cli.mjs
CHANGED
package/dist/index.d.mts
CHANGED
|
@@ -1,36 +1,68 @@
|
|
|
1
|
-
import { ToolProvider, Severity, ScanOptions, CostConfig, ToolScoringOutput } from '@aiready/core';
|
|
1
|
+
import { ToolProvider, Severity, ScanOptions, FileContent, CostConfig, ToolScoringOutput } from '@aiready/core';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Context Analyzer Tool Provider
|
|
5
5
|
*/
|
|
6
6
|
declare const ContextAnalyzerProvider: ToolProvider;
|
|
7
7
|
|
|
8
|
+
/**
|
|
9
|
+
* Options for the Context Analyzer tool.
|
|
10
|
+
* Controls thresholds for import depth, context budget, and cohesion.
|
|
11
|
+
*/
|
|
8
12
|
interface ContextAnalyzerOptions extends ScanOptions {
|
|
13
|
+
/** Maximum acceptable import depth (default: 5) */
|
|
9
14
|
maxDepth?: number;
|
|
15
|
+
/** Maximum acceptable token budget for a single context (default: 25000) */
|
|
10
16
|
maxContextBudget?: number;
|
|
17
|
+
/** Minimum acceptable cohesion score between 0 and 1 (default: 0.6) */
|
|
11
18
|
minCohesion?: number;
|
|
19
|
+
/** Maximum acceptable fragmentation score between 0 and 1 (default: 0.5) */
|
|
12
20
|
maxFragmentation?: number;
|
|
21
|
+
/** Analysis focus area: fragmentation, cohesion, depth, or all (default: 'all') */
|
|
13
22
|
focus?: 'fragmentation' | 'cohesion' | 'depth' | 'all';
|
|
23
|
+
/** Whether to include node_modules in the analysis (default: false) */
|
|
14
24
|
includeNodeModules?: boolean;
|
|
15
25
|
}
|
|
26
|
+
/**
|
|
27
|
+
* The result of a context analysis for a single file or module.
|
|
28
|
+
* Includes metrics for tokens, dependencies, cohesion, and AI impact.
|
|
29
|
+
*/
|
|
16
30
|
interface ContextAnalysisResult {
|
|
31
|
+
/** The file path being analyzed */
|
|
17
32
|
file: string;
|
|
33
|
+
/** Total number of tokens in this file */
|
|
18
34
|
tokenCost: number;
|
|
35
|
+
/** Total lines of code in the file */
|
|
19
36
|
linesOfCode: number;
|
|
37
|
+
/** Maximum depth of the import tree for this file */
|
|
20
38
|
importDepth: number;
|
|
39
|
+
/** Total number of transitive dependencies */
|
|
21
40
|
dependencyCount: number;
|
|
41
|
+
/** List of all files in the dependency tree */
|
|
22
42
|
dependencyList: string[];
|
|
43
|
+
/** Detected circular dependency chains */
|
|
23
44
|
circularDeps: string[][];
|
|
45
|
+
/** Cohesion score from 0 to 1 (1 is perfect cohesion) */
|
|
24
46
|
cohesionScore: number;
|
|
47
|
+
/** Detected domain categories for the module */
|
|
25
48
|
domains: string[];
|
|
49
|
+
/** Number of exported symbols */
|
|
26
50
|
exportCount: number;
|
|
51
|
+
/** Total tokens required to understand this file and all its dependencies */
|
|
27
52
|
contextBudget: number;
|
|
53
|
+
/** Fragmentation score from 0 to 1 (0 is well-grouped) */
|
|
28
54
|
fragmentationScore: number;
|
|
55
|
+
/** List of files that should be loaded together for full context */
|
|
29
56
|
relatedFiles: string[];
|
|
57
|
+
/** The semantic classification of the file (e.g. 'barrel-export', 'service-file') */
|
|
30
58
|
fileClassification: FileClassification;
|
|
59
|
+
/** Overall severity of identified issues */
|
|
31
60
|
severity: Severity | 'critical' | 'major' | 'minor' | 'info';
|
|
61
|
+
/** List of specific structural problems found */
|
|
32
62
|
issues: string[];
|
|
63
|
+
/** Actionable suggestions for improving context readiness */
|
|
33
64
|
recommendations: string[];
|
|
65
|
+
/** Estimated tokens that could be saved by following recommendations */
|
|
34
66
|
potentialSavings: number;
|
|
35
67
|
}
|
|
36
68
|
/**
|
|
@@ -129,19 +161,28 @@ interface TypeDependency {
|
|
|
129
161
|
}
|
|
130
162
|
|
|
131
163
|
/**
|
|
132
|
-
* Calculate cohesion score (how related are exports in a file)
|
|
133
|
-
* Legacy wrapper for backward compatibility with exact test expectations
|
|
164
|
+
* Calculate cohesion score (how related are exports in a file).
|
|
165
|
+
* Legacy wrapper for backward compatibility with exact test expectations.
|
|
166
|
+
*
|
|
167
|
+
* @param exports - List of exported symbols
|
|
168
|
+
* @param filePath - Path to the file being analyzed
|
|
169
|
+
* @param options - Additional options for cohesion calculation
|
|
170
|
+
* @returns Cohesion score between 0 and 1
|
|
134
171
|
*/
|
|
135
172
|
declare function calculateCohesion(exports: ExportInfo[], filePath?: string, options?: any): number;
|
|
136
173
|
/**
|
|
137
174
|
* Analyze AI context window cost for a codebase
|
|
138
175
|
*/
|
|
176
|
+
/**
|
|
177
|
+
* Performs deep context analysis of a project.
|
|
178
|
+
* Scans files, builds a dependency graph, calculates context budgets,
|
|
179
|
+
* and identifies structural issues like high fragmentation or depth.
|
|
180
|
+
*
|
|
181
|
+
* @param options - Analysis parameters including root directory and focus areas
|
|
182
|
+
* @returns Comprehensive analysis results with metrics and identified issues
|
|
183
|
+
*/
|
|
139
184
|
declare function analyzeContext(options: ContextAnalyzerOptions): Promise<ContextAnalysisResult[]>;
|
|
140
185
|
|
|
141
|
-
interface FileContent {
|
|
142
|
-
file: string;
|
|
143
|
-
content: string;
|
|
144
|
-
}
|
|
145
186
|
/**
|
|
146
187
|
* Auto-detect domain keywords from workspace folder structure
|
|
147
188
|
*/
|
|
@@ -172,6 +213,15 @@ declare function detectCircularDependencies(graph: DependencyGraph): string[][];
|
|
|
172
213
|
/**
|
|
173
214
|
* Calculate cohesion score (how related are exports in a file)
|
|
174
215
|
*/
|
|
216
|
+
/**
|
|
217
|
+
* Calculates a cohesion score (0-1) for a module based on its exports,
|
|
218
|
+
* shared imports, and internal structure. High cohesion indicates
|
|
219
|
+
* a well-focused module that is easy for AI models to reason about.
|
|
220
|
+
*
|
|
221
|
+
* @param exports - Exported symbols and their metadata
|
|
222
|
+
* @param imports - Imported symbols and their sources
|
|
223
|
+
* @returns Cohesion score between 0 and 1
|
|
224
|
+
*/
|
|
175
225
|
declare function calculateEnhancedCohesion(exports: ExportInfo[], filePath?: string, options?: {
|
|
176
226
|
coUsageMatrix?: Map<string, Map<string, number>>;
|
|
177
227
|
weights?: {
|
|
@@ -227,76 +277,6 @@ declare const Classification: {
|
|
|
227
277
|
* @returns The determined file classification
|
|
228
278
|
*/
|
|
229
279
|
declare function classifyFile(node: DependencyNode, cohesionScore?: number, domains?: string[]): FileClassification;
|
|
230
|
-
/**
|
|
231
|
-
* Detect if a file is a barrel export (index.ts)
|
|
232
|
-
*
|
|
233
|
-
* @param node The dependency node to check
|
|
234
|
-
* @returns True if the file appears to be a barrel export
|
|
235
|
-
*/
|
|
236
|
-
declare function isBarrelExport(node: DependencyNode): boolean;
|
|
237
|
-
/**
|
|
238
|
-
* Detect if a file is primarily type definitions
|
|
239
|
-
*
|
|
240
|
-
* @param node The dependency node to check
|
|
241
|
-
* @returns True if the file appears to be primarily types
|
|
242
|
-
*/
|
|
243
|
-
declare function isTypeDefinition(node: DependencyNode): boolean;
|
|
244
|
-
/**
|
|
245
|
-
* Detect if a file is a utility module
|
|
246
|
-
*
|
|
247
|
-
* @param node The dependency node to check
|
|
248
|
-
* @returns True if the file appears to be a utility module
|
|
249
|
-
*/
|
|
250
|
-
declare function isUtilityModule(node: DependencyNode): boolean;
|
|
251
|
-
/**
|
|
252
|
-
* Detect if a file is a Lambda/API handler
|
|
253
|
-
*
|
|
254
|
-
* @param node The dependency node to check
|
|
255
|
-
* @returns True if the file appears to be a Lambda handler
|
|
256
|
-
*/
|
|
257
|
-
declare function isLambdaHandler(node: DependencyNode): boolean;
|
|
258
|
-
/**
|
|
259
|
-
* Detect if a file is a service file
|
|
260
|
-
*
|
|
261
|
-
* @param node The dependency node to check
|
|
262
|
-
* @returns True if the file appears to be a service file
|
|
263
|
-
*/
|
|
264
|
-
declare function isServiceFile(node: DependencyNode): boolean;
|
|
265
|
-
/**
|
|
266
|
-
* Detect if a file is an email template/layout
|
|
267
|
-
*
|
|
268
|
-
* @param node The dependency node to check
|
|
269
|
-
* @returns True if the file appears to be an email template
|
|
270
|
-
*/
|
|
271
|
-
declare function isEmailTemplate(node: DependencyNode): boolean;
|
|
272
|
-
/**
|
|
273
|
-
* Detect if a file is a parser/transformer
|
|
274
|
-
*
|
|
275
|
-
* @param node The dependency node to check
|
|
276
|
-
* @returns True if the file appears to be a parser
|
|
277
|
-
*/
|
|
278
|
-
declare function isParserFile(node: DependencyNode): boolean;
|
|
279
|
-
/**
|
|
280
|
-
* Detect if a file is a session/state management file
|
|
281
|
-
*
|
|
282
|
-
* @param node The dependency node to check
|
|
283
|
-
* @returns True if the file appears to be a session/state file
|
|
284
|
-
*/
|
|
285
|
-
declare function isSessionFile(node: DependencyNode): boolean;
|
|
286
|
-
/**
|
|
287
|
-
* Detect if a file is a configuration or schema file
|
|
288
|
-
*
|
|
289
|
-
* @param node The dependency node to check
|
|
290
|
-
* @returns True if the file appears to be a config file
|
|
291
|
-
*/
|
|
292
|
-
declare function isConfigFile(node: DependencyNode): boolean;
|
|
293
|
-
/**
|
|
294
|
-
* Detect if a file is a Next.js App Router page
|
|
295
|
-
*
|
|
296
|
-
* @param node The dependency node to check
|
|
297
|
-
* @returns True if the file appears to be a Next.js page
|
|
298
|
-
*/
|
|
299
|
-
declare function isNextJsPage(node: DependencyNode): boolean;
|
|
300
280
|
/**
|
|
301
281
|
* Adjust cohesion score based on file classification
|
|
302
282
|
*
|
|
@@ -317,6 +297,10 @@ declare function adjustFragmentationForClassification(baseFragmentation: number,
|
|
|
317
297
|
|
|
318
298
|
/**
|
|
319
299
|
* Group files by domain to detect module clusters
|
|
300
|
+
* @param graph - The dependency graph to analyze
|
|
301
|
+
* @param options - Optional configuration options
|
|
302
|
+
* @param options.useLogScale - Whether to use logarithmic scaling for calculations
|
|
303
|
+
* @returns Array of module clusters
|
|
320
304
|
*/
|
|
321
305
|
declare function detectModuleClusters(graph: DependencyGraph, options?: {
|
|
322
306
|
useLogScale?: boolean;
|
|
@@ -355,6 +339,9 @@ declare function mapScoreToRating(score: number): string;
|
|
|
355
339
|
/**
|
|
356
340
|
* Generate smart defaults for context analysis based on repository size
|
|
357
341
|
* Automatically tunes thresholds to target ~10 most serious issues
|
|
342
|
+
* @param directory - The root directory to analyze
|
|
343
|
+
* @param userOptions - Partial user-provided options to merge with defaults
|
|
344
|
+
* @returns Complete ContextAnalyzerOptions with smart defaults
|
|
358
345
|
*/
|
|
359
346
|
declare function getSmartDefaults(directory: string, userOptions: Partial<ContextAnalyzerOptions>): Promise<ContextAnalyzerOptions>;
|
|
360
347
|
|
|
@@ -365,14 +352,21 @@ declare function generateSummary(results: ContextAnalysisResult[], options?: any
|
|
|
365
352
|
|
|
366
353
|
/**
|
|
367
354
|
* Build co-usage matrix: track which files are imported together
|
|
355
|
+
* @param graph - The dependency graph to analyze
|
|
356
|
+
* @returns Map of file to co-usage counts
|
|
368
357
|
*/
|
|
369
358
|
declare function buildCoUsageMatrix(graph: DependencyGraph): Map<string, Map<string, number>>;
|
|
370
359
|
/**
|
|
371
360
|
* Extract type dependencies from AST exports
|
|
361
|
+
* @param graph - The dependency graph to analyze
|
|
362
|
+
* @returns Map of type references to files that use them
|
|
372
363
|
*/
|
|
373
364
|
declare function buildTypeGraph(graph: DependencyGraph): Map<string, Set<string>>;
|
|
374
365
|
/**
|
|
375
366
|
* Find semantic clusters using co-usage patterns
|
|
367
|
+
* @param coUsageMatrix - The co-usage matrix from buildCoUsageMatrix
|
|
368
|
+
* @param minCoUsage - Minimum co-usage count to consider (default: 3)
|
|
369
|
+
* @returns Map of cluster representative files to their cluster members
|
|
376
370
|
*/
|
|
377
371
|
declare function findSemanticClusters(coUsageMatrix: Map<string, Map<string, number>>, minCoUsage?: number): Map<string, string[]>;
|
|
378
372
|
/**
|
|
@@ -408,4 +402,4 @@ declare function generateHTMLReport(summary: ReturnType<typeof generateSummary>,
|
|
|
408
402
|
*/
|
|
409
403
|
declare function runInteractiveSetup(directory: string, current: any): Promise<any>;
|
|
410
404
|
|
|
411
|
-
export { Classification, type CoUsageData, type ContextAnalysisResult, type ContextAnalyzerOptions, ContextAnalyzerProvider, type ContextSummary, type DependencyGraph, type DependencyNode, type DomainAssignment, type DomainSignals, type ExportInfo, type FileClassification, type ModuleCluster, type TypeDependency, adjustCohesionForClassification, adjustFragmentationForClassification, analyzeContext, buildCoUsageMatrix, buildDependencyGraph, buildTypeGraph, calculateCohesion, calculateContextBudget, calculateContextScore, calculateDirectoryDistance, calculateDomainConfidence, calculateEnhancedCohesion, calculateFragmentation, calculateImportDepth, calculatePathEntropy, calculateStructuralCohesionFromCoUsage, classifyFile, detectCircularDependencies, detectModuleClusters, displayConsoleReport, extractDomainKeywordsFromPaths, extractExports, findConsolidationCandidates, findSemanticClusters, generateHTMLReport, generateSummary, getClassificationRecommendations, getCoUsageData, getGeneralRecommendations, getSmartDefaults, getTransitiveDependencies, inferDomain, inferDomainFromSemantics,
|
|
405
|
+
export { Classification, type CoUsageData, type ContextAnalysisResult, type ContextAnalyzerOptions, ContextAnalyzerProvider, type ContextSummary, type DependencyGraph, type DependencyNode, type DomainAssignment, type DomainSignals, type ExportInfo, type FileClassification, type ModuleCluster, type TypeDependency, adjustCohesionForClassification, adjustFragmentationForClassification, analyzeContext, buildCoUsageMatrix, buildDependencyGraph, buildTypeGraph, calculateCohesion, calculateContextBudget, calculateContextScore, calculateDirectoryDistance, calculateDomainConfidence, calculateEnhancedCohesion, calculateFragmentation, calculateImportDepth, calculatePathEntropy, calculateStructuralCohesionFromCoUsage, classifyFile, detectCircularDependencies, detectModuleClusters, displayConsoleReport, extractDomainKeywordsFromPaths, extractExports, findConsolidationCandidates, findSemanticClusters, generateHTMLReport, generateSummary, getClassificationRecommendations, getCoUsageData, getGeneralRecommendations, getSmartDefaults, getTransitiveDependencies, inferDomain, inferDomainFromSemantics, mapScoreToRating, runInteractiveSetup };
|