@aiready/context-analyzer 0.9.23 → 0.9.26
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-test.log +21 -24
- package/dist/chunk-HOUDVRG2.mjs +1422 -0
- package/dist/chunk-PJD4VCIH.mjs +1722 -0
- package/dist/chunk-XZ645X5U.mjs +1425 -0
- package/dist/cli.js +368 -8
- package/dist/cli.mjs +1 -1
- package/dist/index.d.mts +7 -1
- package/dist/index.d.ts +7 -1
- package/dist/index.js +368 -8
- package/dist/index.mjs +1 -1
- package/package.json +1 -1
- package/src/__tests__/file-classification.test.ts +596 -10
- package/src/analyzer.ts +651 -8
- package/src/index.ts +11 -3
- package/src/types.ts +6 -0
package/dist/cli.js
CHANGED
|
@@ -891,24 +891,50 @@ function calculateDomainCohesion(exports2) {
|
|
|
891
891
|
return maxEntropy > 0 ? 1 - entropy / maxEntropy : 1;
|
|
892
892
|
}
|
|
893
893
|
function classifyFile(node, cohesionScore, domains) {
|
|
894
|
-
const { exports: exports2, imports, linesOfCode } = node;
|
|
894
|
+
const { exports: exports2, imports, linesOfCode, file } = node;
|
|
895
895
|
if (isBarrelExport(node)) {
|
|
896
896
|
return "barrel-export";
|
|
897
897
|
}
|
|
898
898
|
if (isTypeDefinitionFile(node)) {
|
|
899
899
|
return "type-definition";
|
|
900
900
|
}
|
|
901
|
+
if (isConfigOrSchemaFile(node)) {
|
|
902
|
+
return "cohesive-module";
|
|
903
|
+
}
|
|
904
|
+
if (isLambdaHandler(node)) {
|
|
905
|
+
return "lambda-handler";
|
|
906
|
+
}
|
|
907
|
+
if (isEmailTemplate(node)) {
|
|
908
|
+
return "email-template";
|
|
909
|
+
}
|
|
910
|
+
if (isParserFile(node)) {
|
|
911
|
+
return "parser-file";
|
|
912
|
+
}
|
|
913
|
+
if (isServiceFile(node)) {
|
|
914
|
+
return "service-file";
|
|
915
|
+
}
|
|
916
|
+
if (isSessionFile(node)) {
|
|
917
|
+
return "cohesive-module";
|
|
918
|
+
}
|
|
919
|
+
if (isNextJsPage(node)) {
|
|
920
|
+
return "nextjs-page";
|
|
921
|
+
}
|
|
922
|
+
if (isUtilityFile(node)) {
|
|
923
|
+
return "utility-module";
|
|
924
|
+
}
|
|
901
925
|
const uniqueDomains = domains.filter((d) => d !== "unknown");
|
|
902
926
|
const hasSingleDomain = uniqueDomains.length <= 1;
|
|
903
|
-
|
|
904
|
-
if (hasSingleDomain && hasHighCohesion) {
|
|
927
|
+
if (hasSingleDomain) {
|
|
905
928
|
return "cohesive-module";
|
|
906
929
|
}
|
|
907
930
|
const hasMultipleDomains = uniqueDomains.length > 1;
|
|
908
|
-
const hasLowCohesion = cohesionScore < 0.
|
|
909
|
-
if (hasMultipleDomains
|
|
931
|
+
const hasLowCohesion = cohesionScore < 0.4;
|
|
932
|
+
if (hasMultipleDomains && hasLowCohesion) {
|
|
910
933
|
return "mixed-concerns";
|
|
911
934
|
}
|
|
935
|
+
if (cohesionScore >= 0.5) {
|
|
936
|
+
return "cohesive-module";
|
|
937
|
+
}
|
|
912
938
|
return "unknown";
|
|
913
939
|
}
|
|
914
940
|
function isBarrelExport(node) {
|
|
@@ -933,10 +959,300 @@ function isTypeDefinitionFile(node) {
|
|
|
933
959
|
const { file, exports: exports2 } = node;
|
|
934
960
|
const fileName = file.split("/").pop()?.toLowerCase();
|
|
935
961
|
const isTypesFile = fileName?.includes("types") || fileName?.includes(".d.ts") || fileName === "types.ts" || fileName === "interfaces.ts";
|
|
962
|
+
const lowerPath = file.toLowerCase();
|
|
963
|
+
const isTypesPath = lowerPath.includes("/types/") || lowerPath.includes("/typings/") || lowerPath.includes("/@types/") || lowerPath.startsWith("types/") || lowerPath.startsWith("typings/");
|
|
936
964
|
const typeExports = exports2.filter((e) => e.type === "type" || e.type === "interface");
|
|
937
965
|
const runtimeExports = exports2.filter((e) => e.type === "function" || e.type === "class" || e.type === "const");
|
|
938
966
|
const mostlyTypes = exports2.length > 0 && typeExports.length > runtimeExports.length && typeExports.length / exports2.length > 0.7;
|
|
939
|
-
|
|
967
|
+
const pureTypeFile = exports2.length > 0 && typeExports.length === exports2.length;
|
|
968
|
+
const emptyOrReExportInTypesDir = isTypesPath && exports2.length === 0;
|
|
969
|
+
return isTypesFile || isTypesPath || mostlyTypes || pureTypeFile || emptyOrReExportInTypesDir;
|
|
970
|
+
}
|
|
971
|
+
function isConfigOrSchemaFile(node) {
|
|
972
|
+
const { file, exports: exports2 } = node;
|
|
973
|
+
const fileName = file.split("/").pop()?.toLowerCase();
|
|
974
|
+
const configPatterns = [
|
|
975
|
+
"config",
|
|
976
|
+
"schema",
|
|
977
|
+
"settings",
|
|
978
|
+
"options",
|
|
979
|
+
"constants",
|
|
980
|
+
"env",
|
|
981
|
+
"environment",
|
|
982
|
+
".config.",
|
|
983
|
+
"-config.",
|
|
984
|
+
"_config."
|
|
985
|
+
];
|
|
986
|
+
const isConfigName = configPatterns.some(
|
|
987
|
+
(pattern) => fileName?.includes(pattern) || fileName?.startsWith(pattern) || fileName?.endsWith(`${pattern}.ts`)
|
|
988
|
+
);
|
|
989
|
+
const isConfigPath = file.toLowerCase().includes("/config/") || file.toLowerCase().includes("/schemas/") || file.toLowerCase().includes("/settings/");
|
|
990
|
+
const hasSchemaExports = exports2.some(
|
|
991
|
+
(e) => e.name.toLowerCase().includes("table") || e.name.toLowerCase().includes("schema") || e.name.toLowerCase().includes("config") || e.name.toLowerCase().includes("setting")
|
|
992
|
+
);
|
|
993
|
+
return isConfigName || isConfigPath || hasSchemaExports;
|
|
994
|
+
}
|
|
995
|
+
function isUtilityFile(node) {
|
|
996
|
+
const { file, exports: exports2 } = node;
|
|
997
|
+
const fileName = file.split("/").pop()?.toLowerCase();
|
|
998
|
+
const utilityPatterns = [
|
|
999
|
+
"util",
|
|
1000
|
+
"utility",
|
|
1001
|
+
"utilities",
|
|
1002
|
+
"helper",
|
|
1003
|
+
"helpers",
|
|
1004
|
+
"common",
|
|
1005
|
+
"shared",
|
|
1006
|
+
"toolbox",
|
|
1007
|
+
"toolkit",
|
|
1008
|
+
".util.",
|
|
1009
|
+
"-util.",
|
|
1010
|
+
"_util.",
|
|
1011
|
+
"-utils.",
|
|
1012
|
+
".utils."
|
|
1013
|
+
];
|
|
1014
|
+
const isUtilityName = utilityPatterns.some(
|
|
1015
|
+
(pattern) => fileName?.includes(pattern)
|
|
1016
|
+
);
|
|
1017
|
+
const isUtilityPath = file.toLowerCase().includes("/utils/") || file.toLowerCase().includes("/helpers/") || file.toLowerCase().includes("/common/") || file.toLowerCase().endsWith("-utils.ts") || file.toLowerCase().endsWith("-util.ts") || file.toLowerCase().endsWith("-helper.ts") || file.toLowerCase().endsWith("-helpers.ts");
|
|
1018
|
+
const hasManySmallExportsInUtilityContext = exports2.length >= 3 && exports2.every((e) => e.type === "function" || e.type === "const") && (isUtilityName || isUtilityPath);
|
|
1019
|
+
return isUtilityName || isUtilityPath || hasManySmallExportsInUtilityContext;
|
|
1020
|
+
}
|
|
1021
|
+
function isLambdaHandler(node) {
|
|
1022
|
+
const { file, exports: exports2 } = node;
|
|
1023
|
+
const fileName = file.split("/").pop()?.toLowerCase();
|
|
1024
|
+
const handlerPatterns = [
|
|
1025
|
+
"handler",
|
|
1026
|
+
".handler.",
|
|
1027
|
+
"-handler.",
|
|
1028
|
+
"lambda",
|
|
1029
|
+
".lambda.",
|
|
1030
|
+
"-lambda."
|
|
1031
|
+
];
|
|
1032
|
+
const isHandlerName = handlerPatterns.some(
|
|
1033
|
+
(pattern) => fileName?.includes(pattern)
|
|
1034
|
+
);
|
|
1035
|
+
const isHandlerPath = file.toLowerCase().includes("/handlers/") || file.toLowerCase().includes("/lambdas/") || file.toLowerCase().includes("/functions/");
|
|
1036
|
+
const hasHandlerExport = exports2.some(
|
|
1037
|
+
(e) => e.name.toLowerCase() === "handler" || e.name.toLowerCase() === "main" || e.name.toLowerCase() === "lambdahandler" || e.name.toLowerCase().endsWith("handler")
|
|
1038
|
+
);
|
|
1039
|
+
const hasSingleEntryInHandlerContext = exports2.length === 1 && (exports2[0].type === "function" || exports2[0].name === "default") && (isHandlerPath || isHandlerName);
|
|
1040
|
+
return isHandlerName || isHandlerPath || hasHandlerExport || hasSingleEntryInHandlerContext;
|
|
1041
|
+
}
|
|
1042
|
+
function isServiceFile(node) {
|
|
1043
|
+
const { file, exports: exports2 } = node;
|
|
1044
|
+
const fileName = file.split("/").pop()?.toLowerCase();
|
|
1045
|
+
const servicePatterns = [
|
|
1046
|
+
"service",
|
|
1047
|
+
".service.",
|
|
1048
|
+
"-service.",
|
|
1049
|
+
"_service."
|
|
1050
|
+
];
|
|
1051
|
+
const isServiceName = servicePatterns.some(
|
|
1052
|
+
(pattern) => fileName?.includes(pattern)
|
|
1053
|
+
);
|
|
1054
|
+
const isServicePath = file.toLowerCase().includes("/services/");
|
|
1055
|
+
const hasServiceNamedExport = exports2.some(
|
|
1056
|
+
(e) => e.name.toLowerCase().includes("service") || e.name.toLowerCase().endsWith("service")
|
|
1057
|
+
);
|
|
1058
|
+
const hasClassExport = exports2.some((e) => e.type === "class");
|
|
1059
|
+
return isServiceName || isServicePath || hasServiceNamedExport && hasClassExport;
|
|
1060
|
+
}
|
|
1061
|
+
function isEmailTemplate(node) {
|
|
1062
|
+
const { file, exports: exports2 } = node;
|
|
1063
|
+
const fileName = file.split("/").pop()?.toLowerCase();
|
|
1064
|
+
const emailTemplatePatterns = [
|
|
1065
|
+
"-email-",
|
|
1066
|
+
".email.",
|
|
1067
|
+
"_email_",
|
|
1068
|
+
"-template",
|
|
1069
|
+
".template.",
|
|
1070
|
+
"_template",
|
|
1071
|
+
"-mail.",
|
|
1072
|
+
".mail."
|
|
1073
|
+
];
|
|
1074
|
+
const isEmailTemplateName = emailTemplatePatterns.some(
|
|
1075
|
+
(pattern) => fileName?.includes(pattern)
|
|
1076
|
+
);
|
|
1077
|
+
const isSpecificTemplateName = fileName?.includes("receipt") || fileName?.includes("invoice-email") || fileName?.includes("welcome-email") || fileName?.includes("notification-email") || fileName?.includes("writer") && fileName.includes("receipt");
|
|
1078
|
+
const isEmailPath = file.toLowerCase().includes("/emails/") || file.toLowerCase().includes("/mail/") || file.toLowerCase().includes("/notifications/");
|
|
1079
|
+
const hasTemplateFunction = exports2.some(
|
|
1080
|
+
(e) => e.type === "function" && (e.name.toLowerCase().startsWith("render") || e.name.toLowerCase().startsWith("generate") || e.name.toLowerCase().includes("template") && e.name.toLowerCase().includes("email"))
|
|
1081
|
+
);
|
|
1082
|
+
const hasEmailExport = exports2.some(
|
|
1083
|
+
(e) => e.name.toLowerCase().includes("template") && e.type === "function" || e.name.toLowerCase().includes("render") && e.type === "function" || e.name.toLowerCase().includes("email") && e.type !== "class"
|
|
1084
|
+
);
|
|
1085
|
+
return isEmailPath || isEmailTemplateName || isSpecificTemplateName || hasTemplateFunction && hasEmailExport;
|
|
1086
|
+
}
|
|
1087
|
+
function isParserFile(node) {
|
|
1088
|
+
const { file, exports: exports2 } = node;
|
|
1089
|
+
const fileName = file.split("/").pop()?.toLowerCase();
|
|
1090
|
+
const parserPatterns = [
|
|
1091
|
+
"parser",
|
|
1092
|
+
".parser.",
|
|
1093
|
+
"-parser.",
|
|
1094
|
+
"_parser.",
|
|
1095
|
+
"transform",
|
|
1096
|
+
".transform.",
|
|
1097
|
+
"-transform.",
|
|
1098
|
+
"converter",
|
|
1099
|
+
".converter.",
|
|
1100
|
+
"-converter.",
|
|
1101
|
+
"mapper",
|
|
1102
|
+
".mapper.",
|
|
1103
|
+
"-mapper.",
|
|
1104
|
+
"serializer",
|
|
1105
|
+
".serializer.",
|
|
1106
|
+
"deterministic"
|
|
1107
|
+
// For base-parser-deterministic.ts pattern
|
|
1108
|
+
];
|
|
1109
|
+
const isParserName = parserPatterns.some(
|
|
1110
|
+
(pattern) => fileName?.includes(pattern)
|
|
1111
|
+
);
|
|
1112
|
+
const isParserPath = file.toLowerCase().includes("/parsers/") || file.toLowerCase().includes("/transformers/") || file.toLowerCase().includes("/converters/") || file.toLowerCase().includes("/mappers/");
|
|
1113
|
+
const hasParserExport = exports2.some(
|
|
1114
|
+
(e) => e.name.toLowerCase().includes("parse") || e.name.toLowerCase().includes("transform") || e.name.toLowerCase().includes("convert") || e.name.toLowerCase().includes("map") || e.name.toLowerCase().includes("serialize") || e.name.toLowerCase().includes("deserialize")
|
|
1115
|
+
);
|
|
1116
|
+
const hasParseFunction = exports2.some(
|
|
1117
|
+
(e) => e.type === "function" && (e.name.toLowerCase().startsWith("parse") || e.name.toLowerCase().startsWith("transform") || e.name.toLowerCase().startsWith("convert") || e.name.toLowerCase().startsWith("map") || e.name.toLowerCase().startsWith("extract"))
|
|
1118
|
+
);
|
|
1119
|
+
return isParserName || isParserPath || hasParserExport || hasParseFunction;
|
|
1120
|
+
}
|
|
1121
|
+
function isSessionFile(node) {
|
|
1122
|
+
const { file, exports: exports2 } = node;
|
|
1123
|
+
const fileName = file.split("/").pop()?.toLowerCase();
|
|
1124
|
+
const sessionPatterns = [
|
|
1125
|
+
"session",
|
|
1126
|
+
".session.",
|
|
1127
|
+
"-session.",
|
|
1128
|
+
"state",
|
|
1129
|
+
".state.",
|
|
1130
|
+
"-state.",
|
|
1131
|
+
"context",
|
|
1132
|
+
".context.",
|
|
1133
|
+
"-context.",
|
|
1134
|
+
"store",
|
|
1135
|
+
".store.",
|
|
1136
|
+
"-store."
|
|
1137
|
+
];
|
|
1138
|
+
const isSessionName = sessionPatterns.some(
|
|
1139
|
+
(pattern) => fileName?.includes(pattern)
|
|
1140
|
+
);
|
|
1141
|
+
const isSessionPath = file.toLowerCase().includes("/sessions/") || file.toLowerCase().includes("/state/") || file.toLowerCase().includes("/context/") || file.toLowerCase().includes("/store/");
|
|
1142
|
+
const hasSessionExport = exports2.some(
|
|
1143
|
+
(e) => e.name.toLowerCase().includes("session") || e.name.toLowerCase().includes("state") || e.name.toLowerCase().includes("context") || e.name.toLowerCase().includes("manager") || e.name.toLowerCase().includes("store")
|
|
1144
|
+
);
|
|
1145
|
+
return isSessionName || isSessionPath || hasSessionExport;
|
|
1146
|
+
}
|
|
1147
|
+
function isNextJsPage(node) {
|
|
1148
|
+
const { file, exports: exports2 } = node;
|
|
1149
|
+
const lowerPath = file.toLowerCase();
|
|
1150
|
+
const fileName = file.split("/").pop()?.toLowerCase();
|
|
1151
|
+
const isInAppDir = lowerPath.includes("/app/") || lowerPath.startsWith("app/");
|
|
1152
|
+
const isPageFile = fileName === "page.tsx" || fileName === "page.ts";
|
|
1153
|
+
if (!isInAppDir || !isPageFile) {
|
|
1154
|
+
return false;
|
|
1155
|
+
}
|
|
1156
|
+
const exportNames = exports2.map((e) => e.name.toLowerCase());
|
|
1157
|
+
const hasDefaultExport = exports2.some((e) => e.type === "default");
|
|
1158
|
+
const nextJsExports = ["metadata", "generatemetadata", "faqjsonld", "jsonld", "icon", "viewport", "dynamic"];
|
|
1159
|
+
const hasNextJsExports = exportNames.some(
|
|
1160
|
+
(name) => nextJsExports.includes(name) || name.includes("jsonld")
|
|
1161
|
+
);
|
|
1162
|
+
return hasDefaultExport || hasNextJsExports;
|
|
1163
|
+
}
|
|
1164
|
+
function adjustCohesionForClassification(baseCohesion, classification, node) {
|
|
1165
|
+
switch (classification) {
|
|
1166
|
+
case "barrel-export":
|
|
1167
|
+
return 1;
|
|
1168
|
+
case "type-definition":
|
|
1169
|
+
return 1;
|
|
1170
|
+
case "utility-module": {
|
|
1171
|
+
if (node) {
|
|
1172
|
+
const exportNames = node.exports.map((e) => e.name.toLowerCase());
|
|
1173
|
+
const hasRelatedNames = hasRelatedExportNames(exportNames);
|
|
1174
|
+
if (hasRelatedNames) {
|
|
1175
|
+
return Math.min(1, baseCohesion + 0.45);
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
1178
|
+
return Math.min(1, baseCohesion + 0.35);
|
|
1179
|
+
}
|
|
1180
|
+
case "service-file": {
|
|
1181
|
+
if (node?.exports.some((e) => e.type === "class")) {
|
|
1182
|
+
return Math.min(1, baseCohesion + 0.4);
|
|
1183
|
+
}
|
|
1184
|
+
return Math.min(1, baseCohesion + 0.3);
|
|
1185
|
+
}
|
|
1186
|
+
case "lambda-handler": {
|
|
1187
|
+
if (node) {
|
|
1188
|
+
const hasSingleEntry = node.exports.length === 1 || node.exports.some((e) => e.name.toLowerCase() === "handler");
|
|
1189
|
+
if (hasSingleEntry) {
|
|
1190
|
+
return Math.min(1, baseCohesion + 0.45);
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
return Math.min(1, baseCohesion + 0.35);
|
|
1194
|
+
}
|
|
1195
|
+
case "email-template": {
|
|
1196
|
+
if (node) {
|
|
1197
|
+
const hasTemplateFunc = node.exports.some(
|
|
1198
|
+
(e) => e.name.toLowerCase().includes("render") || e.name.toLowerCase().includes("generate") || e.name.toLowerCase().includes("template")
|
|
1199
|
+
);
|
|
1200
|
+
if (hasTemplateFunc) {
|
|
1201
|
+
return Math.min(1, baseCohesion + 0.4);
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
1204
|
+
return Math.min(1, baseCohesion + 0.3);
|
|
1205
|
+
}
|
|
1206
|
+
case "parser-file": {
|
|
1207
|
+
if (node) {
|
|
1208
|
+
const hasParseFunc = node.exports.some(
|
|
1209
|
+
(e) => e.name.toLowerCase().startsWith("parse") || e.name.toLowerCase().startsWith("transform") || e.name.toLowerCase().startsWith("convert")
|
|
1210
|
+
);
|
|
1211
|
+
if (hasParseFunc) {
|
|
1212
|
+
return Math.min(1, baseCohesion + 0.4);
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
return Math.min(1, baseCohesion + 0.3);
|
|
1216
|
+
}
|
|
1217
|
+
case "nextjs-page":
|
|
1218
|
+
return 1;
|
|
1219
|
+
case "cohesive-module":
|
|
1220
|
+
return Math.max(baseCohesion, 0.7);
|
|
1221
|
+
case "mixed-concerns":
|
|
1222
|
+
return baseCohesion;
|
|
1223
|
+
default:
|
|
1224
|
+
return Math.min(1, baseCohesion + 0.1);
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1227
|
+
function hasRelatedExportNames(exportNames) {
|
|
1228
|
+
if (exportNames.length < 2) return true;
|
|
1229
|
+
const stems = /* @__PURE__ */ new Set();
|
|
1230
|
+
const domains = /* @__PURE__ */ new Set();
|
|
1231
|
+
for (const name of exportNames) {
|
|
1232
|
+
const verbs = ["get", "set", "create", "update", "delete", "fetch", "save", "load", "parse", "format", "validate", "convert", "transform", "build", "generate", "render", "send", "receive"];
|
|
1233
|
+
for (const verb of verbs) {
|
|
1234
|
+
if (name.startsWith(verb) && name.length > verb.length) {
|
|
1235
|
+
stems.add(name.slice(verb.length).toLowerCase());
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
const domainPatterns = ["user", "order", "product", "session", "email", "file", "db", "s3", "dynamo", "api", "config"];
|
|
1239
|
+
for (const domain of domainPatterns) {
|
|
1240
|
+
if (name.includes(domain)) {
|
|
1241
|
+
domains.add(domain);
|
|
1242
|
+
}
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
1245
|
+
if (stems.size === 1 && exportNames.length >= 2) return true;
|
|
1246
|
+
if (domains.size === 1 && exportNames.length >= 2) return true;
|
|
1247
|
+
const prefixes = exportNames.map((name) => {
|
|
1248
|
+
const match = name.match(/^([a-z]+)/);
|
|
1249
|
+
return match ? match[1] : "";
|
|
1250
|
+
}).filter((p) => p.length >= 3);
|
|
1251
|
+
if (prefixes.length >= 2) {
|
|
1252
|
+
const uniquePrefixes = new Set(prefixes);
|
|
1253
|
+
if (uniquePrefixes.size === 1) return true;
|
|
1254
|
+
}
|
|
1255
|
+
return false;
|
|
940
1256
|
}
|
|
941
1257
|
function adjustFragmentationForClassification(baseFragmentation, classification) {
|
|
942
1258
|
switch (classification) {
|
|
@@ -944,6 +1260,13 @@ function adjustFragmentationForClassification(baseFragmentation, classification)
|
|
|
944
1260
|
return 0;
|
|
945
1261
|
case "type-definition":
|
|
946
1262
|
return 0;
|
|
1263
|
+
case "utility-module":
|
|
1264
|
+
case "service-file":
|
|
1265
|
+
case "lambda-handler":
|
|
1266
|
+
case "email-template":
|
|
1267
|
+
case "parser-file":
|
|
1268
|
+
case "nextjs-page":
|
|
1269
|
+
return baseFragmentation * 0.2;
|
|
947
1270
|
case "cohesive-module":
|
|
948
1271
|
return baseFragmentation * 0.3;
|
|
949
1272
|
case "mixed-concerns":
|
|
@@ -969,6 +1292,36 @@ function getClassificationRecommendations(classification, file, issues) {
|
|
|
969
1292
|
"Module has good cohesion despite its size",
|
|
970
1293
|
"Consider documenting the module boundaries for AI assistants"
|
|
971
1294
|
];
|
|
1295
|
+
case "utility-module":
|
|
1296
|
+
return [
|
|
1297
|
+
"Utility module detected - multiple domains are acceptable here",
|
|
1298
|
+
"Consider grouping related utilities by prefix or domain for better discoverability"
|
|
1299
|
+
];
|
|
1300
|
+
case "service-file":
|
|
1301
|
+
return [
|
|
1302
|
+
"Service file detected - orchestration of multiple dependencies is expected",
|
|
1303
|
+
"Consider documenting service boundaries and dependencies"
|
|
1304
|
+
];
|
|
1305
|
+
case "lambda-handler":
|
|
1306
|
+
return [
|
|
1307
|
+
"Lambda handler detected - coordination of services is expected",
|
|
1308
|
+
"Ensure handler has clear single responsibility"
|
|
1309
|
+
];
|
|
1310
|
+
case "email-template":
|
|
1311
|
+
return [
|
|
1312
|
+
"Email template detected - references multiple domains for rendering",
|
|
1313
|
+
"Template structure is cohesive by design"
|
|
1314
|
+
];
|
|
1315
|
+
case "parser-file":
|
|
1316
|
+
return [
|
|
1317
|
+
"Parser/transformer file detected - handles multiple data sources",
|
|
1318
|
+
"Consider documenting input/output schemas"
|
|
1319
|
+
];
|
|
1320
|
+
case "nextjs-page":
|
|
1321
|
+
return [
|
|
1322
|
+
"Next.js App Router page detected - metadata/JSON-LD/component pattern is cohesive",
|
|
1323
|
+
"Multiple exports (metadata, faqJsonLd, default) serve single page purpose"
|
|
1324
|
+
];
|
|
972
1325
|
case "mixed-concerns":
|
|
973
1326
|
return [
|
|
974
1327
|
"Consider splitting this file by domain",
|
|
@@ -1091,6 +1444,11 @@ async function analyzeContext(options) {
|
|
|
1091
1444
|
...new Set(node.exports.map((e) => e.inferredDomain || "unknown"))
|
|
1092
1445
|
];
|
|
1093
1446
|
const fileClassification = classifyFile(node, cohesionScore, domains);
|
|
1447
|
+
const adjustedCohesionScore = adjustCohesionForClassification(
|
|
1448
|
+
cohesionScore,
|
|
1449
|
+
fileClassification,
|
|
1450
|
+
node
|
|
1451
|
+
);
|
|
1094
1452
|
const adjustedFragmentationScore = adjustFragmentationForClassification(
|
|
1095
1453
|
fragmentationScore,
|
|
1096
1454
|
fileClassification
|
|
@@ -1109,7 +1467,8 @@ async function analyzeContext(options) {
|
|
|
1109
1467
|
file,
|
|
1110
1468
|
importDepth,
|
|
1111
1469
|
contextBudget,
|
|
1112
|
-
cohesionScore,
|
|
1470
|
+
cohesionScore: adjustedCohesionScore,
|
|
1471
|
+
// Use adjusted cohesion
|
|
1113
1472
|
fragmentationScore: adjustedFragmentationScore,
|
|
1114
1473
|
maxDepth,
|
|
1115
1474
|
maxContextBudget,
|
|
@@ -1125,7 +1484,8 @@ async function analyzeContext(options) {
|
|
|
1125
1484
|
dependencyCount: dependencyList.length,
|
|
1126
1485
|
dependencyList,
|
|
1127
1486
|
circularDeps: circularDeps.filter((cycle) => cycle.includes(file)),
|
|
1128
|
-
cohesionScore,
|
|
1487
|
+
cohesionScore: adjustedCohesionScore,
|
|
1488
|
+
// Report adjusted cohesion
|
|
1129
1489
|
domains,
|
|
1130
1490
|
exportCount: node.exports.length,
|
|
1131
1491
|
contextBudget,
|
package/dist/cli.mjs
CHANGED
package/dist/index.d.mts
CHANGED
|
@@ -32,7 +32,7 @@ interface ContextAnalysisResult {
|
|
|
32
32
|
* Classification of file type for analysis context
|
|
33
33
|
* Helps distinguish real issues from false positives
|
|
34
34
|
*/
|
|
35
|
-
type FileClassification = 'barrel-export' | 'type-definition' | 'cohesive-module' | 'mixed-concerns' | 'unknown';
|
|
35
|
+
type FileClassification = 'barrel-export' | 'type-definition' | 'cohesive-module' | 'utility-module' | 'service-file' | 'lambda-handler' | 'email-template' | 'parser-file' | 'nextjs-page' | 'mixed-concerns' | 'unknown';
|
|
36
36
|
interface ModuleCluster {
|
|
37
37
|
domain: string;
|
|
38
38
|
files: string[];
|
|
@@ -130,6 +130,11 @@ interface TypeDependency {
|
|
|
130
130
|
* - barrel-export: Re-exports from other modules (index.ts files)
|
|
131
131
|
* - type-definition: Primarily type/interface definitions
|
|
132
132
|
* - cohesive-module: Single domain, high cohesion (acceptable large files)
|
|
133
|
+
* - utility-module: Utility/helper files with cohesive purpose despite multi-domain
|
|
134
|
+
* - service-file: Service files orchestrating multiple dependencies
|
|
135
|
+
* - lambda-handler: Lambda/API handlers with single business purpose
|
|
136
|
+
* - email-template: Email templates/layouts with structural cohesion
|
|
137
|
+
* - parser-file: Parser/transformer files with single transformation purpose
|
|
133
138
|
* - mixed-concerns: Multiple domains, potential refactoring candidate
|
|
134
139
|
* - unknown: Unable to classify
|
|
135
140
|
*/
|
|
@@ -141,6 +146,7 @@ declare function classifyFile(node: DependencyNode, cohesionScore: number, domai
|
|
|
141
146
|
* - Ignoring fragmentation for barrel exports (they're meant to aggregate)
|
|
142
147
|
* - Ignoring fragmentation for type definitions (centralized types are good)
|
|
143
148
|
* - Reducing fragmentation for cohesive modules (large but focused is OK)
|
|
149
|
+
* - Reducing fragmentation for utility/service/handler/template files
|
|
144
150
|
*/
|
|
145
151
|
declare function adjustFragmentationForClassification(baseFragmentation: number, classification: FileClassification): number;
|
|
146
152
|
|
package/dist/index.d.ts
CHANGED
|
@@ -32,7 +32,7 @@ interface ContextAnalysisResult {
|
|
|
32
32
|
* Classification of file type for analysis context
|
|
33
33
|
* Helps distinguish real issues from false positives
|
|
34
34
|
*/
|
|
35
|
-
type FileClassification = 'barrel-export' | 'type-definition' | 'cohesive-module' | 'mixed-concerns' | 'unknown';
|
|
35
|
+
type FileClassification = 'barrel-export' | 'type-definition' | 'cohesive-module' | 'utility-module' | 'service-file' | 'lambda-handler' | 'email-template' | 'parser-file' | 'nextjs-page' | 'mixed-concerns' | 'unknown';
|
|
36
36
|
interface ModuleCluster {
|
|
37
37
|
domain: string;
|
|
38
38
|
files: string[];
|
|
@@ -130,6 +130,11 @@ interface TypeDependency {
|
|
|
130
130
|
* - barrel-export: Re-exports from other modules (index.ts files)
|
|
131
131
|
* - type-definition: Primarily type/interface definitions
|
|
132
132
|
* - cohesive-module: Single domain, high cohesion (acceptable large files)
|
|
133
|
+
* - utility-module: Utility/helper files with cohesive purpose despite multi-domain
|
|
134
|
+
* - service-file: Service files orchestrating multiple dependencies
|
|
135
|
+
* - lambda-handler: Lambda/API handlers with single business purpose
|
|
136
|
+
* - email-template: Email templates/layouts with structural cohesion
|
|
137
|
+
* - parser-file: Parser/transformer files with single transformation purpose
|
|
133
138
|
* - mixed-concerns: Multiple domains, potential refactoring candidate
|
|
134
139
|
* - unknown: Unable to classify
|
|
135
140
|
*/
|
|
@@ -141,6 +146,7 @@ declare function classifyFile(node: DependencyNode, cohesionScore: number, domai
|
|
|
141
146
|
* - Ignoring fragmentation for barrel exports (they're meant to aggregate)
|
|
142
147
|
* - Ignoring fragmentation for type definitions (centralized types are good)
|
|
143
148
|
* - Reducing fragmentation for cohesive modules (large but focused is OK)
|
|
149
|
+
* - Reducing fragmentation for utility/service/handler/template files
|
|
144
150
|
*/
|
|
145
151
|
declare function adjustFragmentationForClassification(baseFragmentation: number, classification: FileClassification): number;
|
|
146
152
|
|