@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/dist/index.js CHANGED
@@ -966,24 +966,50 @@ function calculateDomainCohesion(exports2) {
966
966
  return maxEntropy > 0 ? 1 - entropy / maxEntropy : 1;
967
967
  }
968
968
  function classifyFile(node, cohesionScore, domains) {
969
- const { exports: exports2, imports, linesOfCode } = node;
969
+ const { exports: exports2, imports, linesOfCode, file } = node;
970
970
  if (isBarrelExport(node)) {
971
971
  return "barrel-export";
972
972
  }
973
973
  if (isTypeDefinitionFile(node)) {
974
974
  return "type-definition";
975
975
  }
976
+ if (isConfigOrSchemaFile(node)) {
977
+ return "cohesive-module";
978
+ }
979
+ if (isLambdaHandler(node)) {
980
+ return "lambda-handler";
981
+ }
982
+ if (isEmailTemplate(node)) {
983
+ return "email-template";
984
+ }
985
+ if (isParserFile(node)) {
986
+ return "parser-file";
987
+ }
988
+ if (isServiceFile(node)) {
989
+ return "service-file";
990
+ }
991
+ if (isSessionFile(node)) {
992
+ return "cohesive-module";
993
+ }
994
+ if (isNextJsPage(node)) {
995
+ return "nextjs-page";
996
+ }
997
+ if (isUtilityFile(node)) {
998
+ return "utility-module";
999
+ }
976
1000
  const uniqueDomains = domains.filter((d) => d !== "unknown");
977
1001
  const hasSingleDomain = uniqueDomains.length <= 1;
978
- const hasHighCohesion = cohesionScore >= 0.7;
979
- if (hasSingleDomain && hasHighCohesion) {
1002
+ if (hasSingleDomain) {
980
1003
  return "cohesive-module";
981
1004
  }
982
1005
  const hasMultipleDomains = uniqueDomains.length > 1;
983
- const hasLowCohesion = cohesionScore < 0.5;
984
- if (hasMultipleDomains || hasLowCohesion) {
1006
+ const hasLowCohesion = cohesionScore < 0.4;
1007
+ if (hasMultipleDomains && hasLowCohesion) {
985
1008
  return "mixed-concerns";
986
1009
  }
1010
+ if (cohesionScore >= 0.5) {
1011
+ return "cohesive-module";
1012
+ }
987
1013
  return "unknown";
988
1014
  }
989
1015
  function isBarrelExport(node) {
@@ -1008,10 +1034,300 @@ function isTypeDefinitionFile(node) {
1008
1034
  const { file, exports: exports2 } = node;
1009
1035
  const fileName = file.split("/").pop()?.toLowerCase();
1010
1036
  const isTypesFile = fileName?.includes("types") || fileName?.includes(".d.ts") || fileName === "types.ts" || fileName === "interfaces.ts";
1037
+ const lowerPath = file.toLowerCase();
1038
+ const isTypesPath = lowerPath.includes("/types/") || lowerPath.includes("/typings/") || lowerPath.includes("/@types/") || lowerPath.startsWith("types/") || lowerPath.startsWith("typings/");
1011
1039
  const typeExports = exports2.filter((e) => e.type === "type" || e.type === "interface");
1012
1040
  const runtimeExports = exports2.filter((e) => e.type === "function" || e.type === "class" || e.type === "const");
1013
1041
  const mostlyTypes = exports2.length > 0 && typeExports.length > runtimeExports.length && typeExports.length / exports2.length > 0.7;
1014
- return isTypesFile || mostlyTypes;
1042
+ const pureTypeFile = exports2.length > 0 && typeExports.length === exports2.length;
1043
+ const emptyOrReExportInTypesDir = isTypesPath && exports2.length === 0;
1044
+ return isTypesFile || isTypesPath || mostlyTypes || pureTypeFile || emptyOrReExportInTypesDir;
1045
+ }
1046
+ function isConfigOrSchemaFile(node) {
1047
+ const { file, exports: exports2 } = node;
1048
+ const fileName = file.split("/").pop()?.toLowerCase();
1049
+ const configPatterns = [
1050
+ "config",
1051
+ "schema",
1052
+ "settings",
1053
+ "options",
1054
+ "constants",
1055
+ "env",
1056
+ "environment",
1057
+ ".config.",
1058
+ "-config.",
1059
+ "_config."
1060
+ ];
1061
+ const isConfigName = configPatterns.some(
1062
+ (pattern) => fileName?.includes(pattern) || fileName?.startsWith(pattern) || fileName?.endsWith(`${pattern}.ts`)
1063
+ );
1064
+ const isConfigPath = file.toLowerCase().includes("/config/") || file.toLowerCase().includes("/schemas/") || file.toLowerCase().includes("/settings/");
1065
+ const hasSchemaExports = exports2.some(
1066
+ (e) => e.name.toLowerCase().includes("table") || e.name.toLowerCase().includes("schema") || e.name.toLowerCase().includes("config") || e.name.toLowerCase().includes("setting")
1067
+ );
1068
+ return isConfigName || isConfigPath || hasSchemaExports;
1069
+ }
1070
+ function isUtilityFile(node) {
1071
+ const { file, exports: exports2 } = node;
1072
+ const fileName = file.split("/").pop()?.toLowerCase();
1073
+ const utilityPatterns = [
1074
+ "util",
1075
+ "utility",
1076
+ "utilities",
1077
+ "helper",
1078
+ "helpers",
1079
+ "common",
1080
+ "shared",
1081
+ "toolbox",
1082
+ "toolkit",
1083
+ ".util.",
1084
+ "-util.",
1085
+ "_util.",
1086
+ "-utils.",
1087
+ ".utils."
1088
+ ];
1089
+ const isUtilityName = utilityPatterns.some(
1090
+ (pattern) => fileName?.includes(pattern)
1091
+ );
1092
+ 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");
1093
+ const hasManySmallExportsInUtilityContext = exports2.length >= 3 && exports2.every((e) => e.type === "function" || e.type === "const") && (isUtilityName || isUtilityPath);
1094
+ return isUtilityName || isUtilityPath || hasManySmallExportsInUtilityContext;
1095
+ }
1096
+ function isLambdaHandler(node) {
1097
+ const { file, exports: exports2 } = node;
1098
+ const fileName = file.split("/").pop()?.toLowerCase();
1099
+ const handlerPatterns = [
1100
+ "handler",
1101
+ ".handler.",
1102
+ "-handler.",
1103
+ "lambda",
1104
+ ".lambda.",
1105
+ "-lambda."
1106
+ ];
1107
+ const isHandlerName = handlerPatterns.some(
1108
+ (pattern) => fileName?.includes(pattern)
1109
+ );
1110
+ const isHandlerPath = file.toLowerCase().includes("/handlers/") || file.toLowerCase().includes("/lambdas/") || file.toLowerCase().includes("/functions/");
1111
+ const hasHandlerExport = exports2.some(
1112
+ (e) => e.name.toLowerCase() === "handler" || e.name.toLowerCase() === "main" || e.name.toLowerCase() === "lambdahandler" || e.name.toLowerCase().endsWith("handler")
1113
+ );
1114
+ const hasSingleEntryInHandlerContext = exports2.length === 1 && (exports2[0].type === "function" || exports2[0].name === "default") && (isHandlerPath || isHandlerName);
1115
+ return isHandlerName || isHandlerPath || hasHandlerExport || hasSingleEntryInHandlerContext;
1116
+ }
1117
+ function isServiceFile(node) {
1118
+ const { file, exports: exports2 } = node;
1119
+ const fileName = file.split("/").pop()?.toLowerCase();
1120
+ const servicePatterns = [
1121
+ "service",
1122
+ ".service.",
1123
+ "-service.",
1124
+ "_service."
1125
+ ];
1126
+ const isServiceName = servicePatterns.some(
1127
+ (pattern) => fileName?.includes(pattern)
1128
+ );
1129
+ const isServicePath = file.toLowerCase().includes("/services/");
1130
+ const hasServiceNamedExport = exports2.some(
1131
+ (e) => e.name.toLowerCase().includes("service") || e.name.toLowerCase().endsWith("service")
1132
+ );
1133
+ const hasClassExport = exports2.some((e) => e.type === "class");
1134
+ return isServiceName || isServicePath || hasServiceNamedExport && hasClassExport;
1135
+ }
1136
+ function isEmailTemplate(node) {
1137
+ const { file, exports: exports2 } = node;
1138
+ const fileName = file.split("/").pop()?.toLowerCase();
1139
+ const emailTemplatePatterns = [
1140
+ "-email-",
1141
+ ".email.",
1142
+ "_email_",
1143
+ "-template",
1144
+ ".template.",
1145
+ "_template",
1146
+ "-mail.",
1147
+ ".mail."
1148
+ ];
1149
+ const isEmailTemplateName = emailTemplatePatterns.some(
1150
+ (pattern) => fileName?.includes(pattern)
1151
+ );
1152
+ const isSpecificTemplateName = fileName?.includes("receipt") || fileName?.includes("invoice-email") || fileName?.includes("welcome-email") || fileName?.includes("notification-email") || fileName?.includes("writer") && fileName.includes("receipt");
1153
+ const isEmailPath = file.toLowerCase().includes("/emails/") || file.toLowerCase().includes("/mail/") || file.toLowerCase().includes("/notifications/");
1154
+ const hasTemplateFunction = exports2.some(
1155
+ (e) => e.type === "function" && (e.name.toLowerCase().startsWith("render") || e.name.toLowerCase().startsWith("generate") || e.name.toLowerCase().includes("template") && e.name.toLowerCase().includes("email"))
1156
+ );
1157
+ const hasEmailExport = exports2.some(
1158
+ (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"
1159
+ );
1160
+ return isEmailPath || isEmailTemplateName || isSpecificTemplateName || hasTemplateFunction && hasEmailExport;
1161
+ }
1162
+ function isParserFile(node) {
1163
+ const { file, exports: exports2 } = node;
1164
+ const fileName = file.split("/").pop()?.toLowerCase();
1165
+ const parserPatterns = [
1166
+ "parser",
1167
+ ".parser.",
1168
+ "-parser.",
1169
+ "_parser.",
1170
+ "transform",
1171
+ ".transform.",
1172
+ "-transform.",
1173
+ "converter",
1174
+ ".converter.",
1175
+ "-converter.",
1176
+ "mapper",
1177
+ ".mapper.",
1178
+ "-mapper.",
1179
+ "serializer",
1180
+ ".serializer.",
1181
+ "deterministic"
1182
+ // For base-parser-deterministic.ts pattern
1183
+ ];
1184
+ const isParserName = parserPatterns.some(
1185
+ (pattern) => fileName?.includes(pattern)
1186
+ );
1187
+ const isParserPath = file.toLowerCase().includes("/parsers/") || file.toLowerCase().includes("/transformers/") || file.toLowerCase().includes("/converters/") || file.toLowerCase().includes("/mappers/");
1188
+ const hasParserExport = exports2.some(
1189
+ (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")
1190
+ );
1191
+ const hasParseFunction = exports2.some(
1192
+ (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"))
1193
+ );
1194
+ return isParserName || isParserPath || hasParserExport || hasParseFunction;
1195
+ }
1196
+ function isSessionFile(node) {
1197
+ const { file, exports: exports2 } = node;
1198
+ const fileName = file.split("/").pop()?.toLowerCase();
1199
+ const sessionPatterns = [
1200
+ "session",
1201
+ ".session.",
1202
+ "-session.",
1203
+ "state",
1204
+ ".state.",
1205
+ "-state.",
1206
+ "context",
1207
+ ".context.",
1208
+ "-context.",
1209
+ "store",
1210
+ ".store.",
1211
+ "-store."
1212
+ ];
1213
+ const isSessionName = sessionPatterns.some(
1214
+ (pattern) => fileName?.includes(pattern)
1215
+ );
1216
+ const isSessionPath = file.toLowerCase().includes("/sessions/") || file.toLowerCase().includes("/state/") || file.toLowerCase().includes("/context/") || file.toLowerCase().includes("/store/");
1217
+ const hasSessionExport = exports2.some(
1218
+ (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")
1219
+ );
1220
+ return isSessionName || isSessionPath || hasSessionExport;
1221
+ }
1222
+ function isNextJsPage(node) {
1223
+ const { file, exports: exports2 } = node;
1224
+ const lowerPath = file.toLowerCase();
1225
+ const fileName = file.split("/").pop()?.toLowerCase();
1226
+ const isInAppDir = lowerPath.includes("/app/") || lowerPath.startsWith("app/");
1227
+ const isPageFile = fileName === "page.tsx" || fileName === "page.ts";
1228
+ if (!isInAppDir || !isPageFile) {
1229
+ return false;
1230
+ }
1231
+ const exportNames = exports2.map((e) => e.name.toLowerCase());
1232
+ const hasDefaultExport = exports2.some((e) => e.type === "default");
1233
+ const nextJsExports = ["metadata", "generatemetadata", "faqjsonld", "jsonld", "icon", "viewport", "dynamic"];
1234
+ const hasNextJsExports = exportNames.some(
1235
+ (name) => nextJsExports.includes(name) || name.includes("jsonld")
1236
+ );
1237
+ return hasDefaultExport || hasNextJsExports;
1238
+ }
1239
+ function adjustCohesionForClassification(baseCohesion, classification, node) {
1240
+ switch (classification) {
1241
+ case "barrel-export":
1242
+ return 1;
1243
+ case "type-definition":
1244
+ return 1;
1245
+ case "utility-module": {
1246
+ if (node) {
1247
+ const exportNames = node.exports.map((e) => e.name.toLowerCase());
1248
+ const hasRelatedNames = hasRelatedExportNames(exportNames);
1249
+ if (hasRelatedNames) {
1250
+ return Math.min(1, baseCohesion + 0.45);
1251
+ }
1252
+ }
1253
+ return Math.min(1, baseCohesion + 0.35);
1254
+ }
1255
+ case "service-file": {
1256
+ if (node?.exports.some((e) => e.type === "class")) {
1257
+ return Math.min(1, baseCohesion + 0.4);
1258
+ }
1259
+ return Math.min(1, baseCohesion + 0.3);
1260
+ }
1261
+ case "lambda-handler": {
1262
+ if (node) {
1263
+ const hasSingleEntry = node.exports.length === 1 || node.exports.some((e) => e.name.toLowerCase() === "handler");
1264
+ if (hasSingleEntry) {
1265
+ return Math.min(1, baseCohesion + 0.45);
1266
+ }
1267
+ }
1268
+ return Math.min(1, baseCohesion + 0.35);
1269
+ }
1270
+ case "email-template": {
1271
+ if (node) {
1272
+ const hasTemplateFunc = node.exports.some(
1273
+ (e) => e.name.toLowerCase().includes("render") || e.name.toLowerCase().includes("generate") || e.name.toLowerCase().includes("template")
1274
+ );
1275
+ if (hasTemplateFunc) {
1276
+ return Math.min(1, baseCohesion + 0.4);
1277
+ }
1278
+ }
1279
+ return Math.min(1, baseCohesion + 0.3);
1280
+ }
1281
+ case "parser-file": {
1282
+ if (node) {
1283
+ const hasParseFunc = node.exports.some(
1284
+ (e) => e.name.toLowerCase().startsWith("parse") || e.name.toLowerCase().startsWith("transform") || e.name.toLowerCase().startsWith("convert")
1285
+ );
1286
+ if (hasParseFunc) {
1287
+ return Math.min(1, baseCohesion + 0.4);
1288
+ }
1289
+ }
1290
+ return Math.min(1, baseCohesion + 0.3);
1291
+ }
1292
+ case "nextjs-page":
1293
+ return 1;
1294
+ case "cohesive-module":
1295
+ return Math.max(baseCohesion, 0.7);
1296
+ case "mixed-concerns":
1297
+ return baseCohesion;
1298
+ default:
1299
+ return Math.min(1, baseCohesion + 0.1);
1300
+ }
1301
+ }
1302
+ function hasRelatedExportNames(exportNames) {
1303
+ if (exportNames.length < 2) return true;
1304
+ const stems = /* @__PURE__ */ new Set();
1305
+ const domains = /* @__PURE__ */ new Set();
1306
+ for (const name of exportNames) {
1307
+ const verbs = ["get", "set", "create", "update", "delete", "fetch", "save", "load", "parse", "format", "validate", "convert", "transform", "build", "generate", "render", "send", "receive"];
1308
+ for (const verb of verbs) {
1309
+ if (name.startsWith(verb) && name.length > verb.length) {
1310
+ stems.add(name.slice(verb.length).toLowerCase());
1311
+ }
1312
+ }
1313
+ const domainPatterns = ["user", "order", "product", "session", "email", "file", "db", "s3", "dynamo", "api", "config"];
1314
+ for (const domain of domainPatterns) {
1315
+ if (name.includes(domain)) {
1316
+ domains.add(domain);
1317
+ }
1318
+ }
1319
+ }
1320
+ if (stems.size === 1 && exportNames.length >= 2) return true;
1321
+ if (domains.size === 1 && exportNames.length >= 2) return true;
1322
+ const prefixes = exportNames.map((name) => {
1323
+ const match = name.match(/^([a-z]+)/);
1324
+ return match ? match[1] : "";
1325
+ }).filter((p) => p.length >= 3);
1326
+ if (prefixes.length >= 2) {
1327
+ const uniquePrefixes = new Set(prefixes);
1328
+ if (uniquePrefixes.size === 1) return true;
1329
+ }
1330
+ return false;
1015
1331
  }
1016
1332
  function adjustFragmentationForClassification(baseFragmentation, classification) {
1017
1333
  switch (classification) {
@@ -1019,6 +1335,13 @@ function adjustFragmentationForClassification(baseFragmentation, classification)
1019
1335
  return 0;
1020
1336
  case "type-definition":
1021
1337
  return 0;
1338
+ case "utility-module":
1339
+ case "service-file":
1340
+ case "lambda-handler":
1341
+ case "email-template":
1342
+ case "parser-file":
1343
+ case "nextjs-page":
1344
+ return baseFragmentation * 0.2;
1022
1345
  case "cohesive-module":
1023
1346
  return baseFragmentation * 0.3;
1024
1347
  case "mixed-concerns":
@@ -1044,6 +1367,36 @@ function getClassificationRecommendations(classification, file, issues) {
1044
1367
  "Module has good cohesion despite its size",
1045
1368
  "Consider documenting the module boundaries for AI assistants"
1046
1369
  ];
1370
+ case "utility-module":
1371
+ return [
1372
+ "Utility module detected - multiple domains are acceptable here",
1373
+ "Consider grouping related utilities by prefix or domain for better discoverability"
1374
+ ];
1375
+ case "service-file":
1376
+ return [
1377
+ "Service file detected - orchestration of multiple dependencies is expected",
1378
+ "Consider documenting service boundaries and dependencies"
1379
+ ];
1380
+ case "lambda-handler":
1381
+ return [
1382
+ "Lambda handler detected - coordination of services is expected",
1383
+ "Ensure handler has clear single responsibility"
1384
+ ];
1385
+ case "email-template":
1386
+ return [
1387
+ "Email template detected - references multiple domains for rendering",
1388
+ "Template structure is cohesive by design"
1389
+ ];
1390
+ case "parser-file":
1391
+ return [
1392
+ "Parser/transformer file detected - handles multiple data sources",
1393
+ "Consider documenting input/output schemas"
1394
+ ];
1395
+ case "nextjs-page":
1396
+ return [
1397
+ "Next.js App Router page detected - metadata/JSON-LD/component pattern is cohesive",
1398
+ "Multiple exports (metadata, faqJsonLd, default) serve single page purpose"
1399
+ ];
1047
1400
  case "mixed-concerns":
1048
1401
  return [
1049
1402
  "Consider splitting this file by domain",
@@ -1317,6 +1670,11 @@ async function analyzeContext(options) {
1317
1670
  ...new Set(node.exports.map((e) => e.inferredDomain || "unknown"))
1318
1671
  ];
1319
1672
  const fileClassification = classifyFile(node, cohesionScore, domains);
1673
+ const adjustedCohesionScore = adjustCohesionForClassification(
1674
+ cohesionScore,
1675
+ fileClassification,
1676
+ node
1677
+ );
1320
1678
  const adjustedFragmentationScore = adjustFragmentationForClassification(
1321
1679
  fragmentationScore,
1322
1680
  fileClassification
@@ -1335,7 +1693,8 @@ async function analyzeContext(options) {
1335
1693
  file,
1336
1694
  importDepth,
1337
1695
  contextBudget,
1338
- cohesionScore,
1696
+ cohesionScore: adjustedCohesionScore,
1697
+ // Use adjusted cohesion
1339
1698
  fragmentationScore: adjustedFragmentationScore,
1340
1699
  maxDepth,
1341
1700
  maxContextBudget,
@@ -1351,7 +1710,8 @@ async function analyzeContext(options) {
1351
1710
  dependencyCount: dependencyList.length,
1352
1711
  dependencyList,
1353
1712
  circularDeps: circularDeps.filter((cycle) => cycle.includes(file)),
1354
- cohesionScore,
1713
+ cohesionScore: adjustedCohesionScore,
1714
+ // Report adjusted cohesion
1355
1715
  domains,
1356
1716
  exportCount: node.exports.length,
1357
1717
  contextBudget,
package/dist/index.mjs CHANGED
@@ -12,7 +12,7 @@ import {
12
12
  getCoUsageData,
13
13
  getSmartDefaults,
14
14
  inferDomainFromSemantics
15
- } from "./chunk-MBE4AQP5.mjs";
15
+ } from "./chunk-PJD4VCIH.mjs";
16
16
  import "./chunk-Y6FXYEAI.mjs";
17
17
  export {
18
18
  adjustFragmentationForClassification,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aiready/context-analyzer",
3
- "version": "0.9.23",
3
+ "version": "0.9.26",
4
4
  "description": "AI context window cost analysis - detect fragmented code, deep import chains, and expensive context budgets",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",