@aiready/context-analyzer 0.9.25 → 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
@@ -976,13 +976,30 @@ function classifyFile(node, cohesionScore, domains) {
976
976
  if (isConfigOrSchemaFile(node)) {
977
977
  return "cohesive-module";
978
978
  }
979
- const uniqueDomains = domains.filter((d) => d !== "unknown");
980
- const hasSingleDomain = uniqueDomains.length <= 1;
981
- const hasReasonableCohesion = cohesionScore >= 0.5;
982
- if (hasSingleDomain) {
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)) {
983
992
  return "cohesive-module";
984
993
  }
994
+ if (isNextJsPage(node)) {
995
+ return "nextjs-page";
996
+ }
985
997
  if (isUtilityFile(node)) {
998
+ return "utility-module";
999
+ }
1000
+ const uniqueDomains = domains.filter((d) => d !== "unknown");
1001
+ const hasSingleDomain = uniqueDomains.length <= 1;
1002
+ if (hasSingleDomain) {
986
1003
  return "cohesive-module";
987
1004
  }
988
1005
  const hasMultipleDomains = uniqueDomains.length > 1;
@@ -1017,10 +1034,14 @@ function isTypeDefinitionFile(node) {
1017
1034
  const { file, exports: exports2 } = node;
1018
1035
  const fileName = file.split("/").pop()?.toLowerCase();
1019
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/");
1020
1039
  const typeExports = exports2.filter((e) => e.type === "type" || e.type === "interface");
1021
1040
  const runtimeExports = exports2.filter((e) => e.type === "function" || e.type === "class" || e.type === "const");
1022
1041
  const mostlyTypes = exports2.length > 0 && typeExports.length > runtimeExports.length && typeExports.length / exports2.length > 0.7;
1023
- 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;
1024
1045
  }
1025
1046
  function isConfigOrSchemaFile(node) {
1026
1047
  const { file, exports: exports2 } = node;
@@ -1057,21 +1078,256 @@ function isUtilityFile(node) {
1057
1078
  "helpers",
1058
1079
  "common",
1059
1080
  "shared",
1060
- "lib",
1061
1081
  "toolbox",
1062
1082
  "toolkit",
1063
1083
  ".util.",
1064
1084
  "-util.",
1065
- "_util."
1085
+ "_util.",
1086
+ "-utils.",
1087
+ ".utils."
1066
1088
  ];
1067
1089
  const isUtilityName = utilityPatterns.some(
1068
1090
  (pattern) => fileName?.includes(pattern)
1069
1091
  );
1070
- const isUtilityPath = file.toLowerCase().includes("/utils/") || file.toLowerCase().includes("/helpers/") || file.toLowerCase().includes("/lib/") || file.toLowerCase().includes("/common/");
1071
- const hasManySmallExports = exports2.length >= 3 && exports2.every(
1072
- (e) => e.type === "function" || e.type === "const"
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")
1073
1236
  );
1074
- return isUtilityName || isUtilityPath || hasManySmallExports;
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;
1075
1331
  }
1076
1332
  function adjustFragmentationForClassification(baseFragmentation, classification) {
1077
1333
  switch (classification) {
@@ -1079,6 +1335,13 @@ function adjustFragmentationForClassification(baseFragmentation, classification)
1079
1335
  return 0;
1080
1336
  case "type-definition":
1081
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;
1082
1345
  case "cohesive-module":
1083
1346
  return baseFragmentation * 0.3;
1084
1347
  case "mixed-concerns":
@@ -1104,6 +1367,36 @@ function getClassificationRecommendations(classification, file, issues) {
1104
1367
  "Module has good cohesion despite its size",
1105
1368
  "Consider documenting the module boundaries for AI assistants"
1106
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
+ ];
1107
1400
  case "mixed-concerns":
1108
1401
  return [
1109
1402
  "Consider splitting this file by domain",
@@ -1377,6 +1670,11 @@ async function analyzeContext(options) {
1377
1670
  ...new Set(node.exports.map((e) => e.inferredDomain || "unknown"))
1378
1671
  ];
1379
1672
  const fileClassification = classifyFile(node, cohesionScore, domains);
1673
+ const adjustedCohesionScore = adjustCohesionForClassification(
1674
+ cohesionScore,
1675
+ fileClassification,
1676
+ node
1677
+ );
1380
1678
  const adjustedFragmentationScore = adjustFragmentationForClassification(
1381
1679
  fragmentationScore,
1382
1680
  fileClassification
@@ -1395,7 +1693,8 @@ async function analyzeContext(options) {
1395
1693
  file,
1396
1694
  importDepth,
1397
1695
  contextBudget,
1398
- cohesionScore,
1696
+ cohesionScore: adjustedCohesionScore,
1697
+ // Use adjusted cohesion
1399
1698
  fragmentationScore: adjustedFragmentationScore,
1400
1699
  maxDepth,
1401
1700
  maxContextBudget,
@@ -1411,7 +1710,8 @@ async function analyzeContext(options) {
1411
1710
  dependencyCount: dependencyList.length,
1412
1711
  dependencyList,
1413
1712
  circularDeps: circularDeps.filter((cycle) => cycle.includes(file)),
1414
- cohesionScore,
1713
+ cohesionScore: adjustedCohesionScore,
1714
+ // Report adjusted cohesion
1415
1715
  domains,
1416
1716
  exportCount: node.exports.length,
1417
1717
  contextBudget,
package/dist/index.mjs CHANGED
@@ -12,7 +12,7 @@ import {
12
12
  getCoUsageData,
13
13
  getSmartDefaults,
14
14
  inferDomainFromSemantics
15
- } from "./chunk-HOUDVRG2.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.25",
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",