@aiready/context-analyzer 0.9.34 → 0.9.36

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/cli.js CHANGED
@@ -43,7 +43,12 @@ async function analyzePythonContext(files, rootDir) {
43
43
  return results;
44
44
  }
45
45
  const pythonFiles = files.filter((f) => f.toLowerCase().endsWith(".py"));
46
- const dependencyGraph = await buildPythonDependencyGraph(pythonFiles, rootDir);
46
+ void import_path.relative;
47
+ void import_path.join;
48
+ const dependencyGraph = await buildPythonDependencyGraph(
49
+ pythonFiles,
50
+ rootDir
51
+ );
47
52
  for (const file of pythonFiles) {
48
53
  try {
49
54
  const code = await import_fs.default.promises.readFile(file, "utf-8");
@@ -59,10 +64,21 @@ async function analyzePythonContext(files, rootDir) {
59
64
  type: exp.type
60
65
  }));
61
66
  const linesOfCode = code.split("\n").length;
62
- const importDepth = await calculatePythonImportDepth(file, dependencyGraph, /* @__PURE__ */ new Set());
63
- const contextBudget = estimateContextBudget(code, imports, dependencyGraph);
67
+ const importDepth = await calculatePythonImportDepth(
68
+ file,
69
+ dependencyGraph,
70
+ /* @__PURE__ */ new Set()
71
+ );
72
+ const contextBudget = estimateContextBudget(
73
+ code,
74
+ imports,
75
+ dependencyGraph
76
+ );
64
77
  const cohesion = calculatePythonCohesion(exports2, imports);
65
- const circularDependencies = detectCircularDependencies2(file, dependencyGraph);
78
+ const circularDependencies = detectCircularDependencies2(
79
+ file,
80
+ dependencyGraph
81
+ );
66
82
  results.push({
67
83
  file,
68
84
  importDepth,
@@ -100,6 +116,7 @@ async function buildPythonDependencyGraph(files, rootDir) {
100
116
  }
101
117
  graph.set(file, dependencies);
102
118
  } catch (error) {
119
+ void error;
103
120
  }
104
121
  }
105
122
  return graph;
@@ -362,7 +379,26 @@ function extractDomainKeywordsFromPaths(files) {
362
379
  const folderNames = /* @__PURE__ */ new Set();
363
380
  for (const { file } of files) {
364
381
  const segments = file.split("/");
365
- const skipFolders = /* @__PURE__ */ new Set(["src", "lib", "dist", "build", "node_modules", "test", "tests", "__tests__", "spec", "e2e", "scripts", "components", "utils", "helpers", "util", "helper", "api", "apis"]);
382
+ const skipFolders = /* @__PURE__ */ new Set([
383
+ "src",
384
+ "lib",
385
+ "dist",
386
+ "build",
387
+ "node_modules",
388
+ "test",
389
+ "tests",
390
+ "__tests__",
391
+ "spec",
392
+ "e2e",
393
+ "scripts",
394
+ "components",
395
+ "utils",
396
+ "helpers",
397
+ "util",
398
+ "helper",
399
+ "api",
400
+ "apis"
401
+ ]);
366
402
  for (const segment of segments) {
367
403
  const normalized = segment.toLowerCase();
368
404
  if (normalized && !skipFolders.has(normalized) && !normalized.includes(".")) {
@@ -398,9 +434,15 @@ function buildDependencyGraph(files) {
398
434
  const nodes = /* @__PURE__ */ new Map();
399
435
  const edges = /* @__PURE__ */ new Map();
400
436
  const autoDetectedKeywords = extractDomainKeywordsFromPaths(files);
437
+ void import_core.calculateImportSimilarity;
401
438
  for (const { file, content } of files) {
402
439
  const imports = extractImportsFromContent(content);
403
- const exports2 = extractExportsWithAST(content, file, { domainKeywords: autoDetectedKeywords }, imports);
440
+ const exports2 = extractExportsWithAST(
441
+ content,
442
+ file,
443
+ { domainKeywords: autoDetectedKeywords },
444
+ imports
445
+ );
404
446
  const tokenCost = (0, import_core.estimateTokens)(content);
405
447
  const linesOfCode = content.split("\n").length;
406
448
  nodes.set(file, {
@@ -544,7 +586,9 @@ function isTestFile(filePath) {
544
586
  }
545
587
  function calculateFragmentation(files, domain, options) {
546
588
  if (files.length <= 1) return 0;
547
- const directories = new Set(files.map((f) => f.split("/").slice(0, -1).join("/")));
589
+ const directories = new Set(
590
+ files.map((f) => f.split("/").slice(0, -1).join("/"))
591
+ );
548
592
  const uniqueDirs = directories.size;
549
593
  if (options?.useLogScale) {
550
594
  if (uniqueDirs <= 1) return 0;
@@ -617,7 +661,9 @@ function detectModuleClusters(graph, options) {
617
661
  const node = graph.nodes.get(file);
618
662
  return sum + (node?.tokenCost || 0);
619
663
  }, 0);
620
- const baseFragmentation = calculateFragmentation(files, domain, { useLogScale: !!options?.useLogScale });
664
+ const baseFragmentation = calculateFragmentation(files, domain, {
665
+ useLogScale: !!options?.useLogScale
666
+ });
621
667
  let importSimilarityTotal = 0;
622
668
  let importComparisons = 0;
623
669
  for (let i = 0; i < files.length; i++) {
@@ -638,7 +684,9 @@ function detectModuleClusters(graph, options) {
638
684
  const directoryDistance = calculateDirectoryDistance(files);
639
685
  const avgCohesion = files.reduce((sum, file) => {
640
686
  const node = graph.nodes.get(file);
641
- return sum + (node ? calculateCohesion(node.exports, file, { coUsageMatrix: graph.coUsageMatrix }) : 0);
687
+ return sum + (node ? calculateCohesion(node.exports, file, {
688
+ coUsageMatrix: graph.coUsageMatrix
689
+ }) : 0);
642
690
  }, 0) / files.length;
643
691
  const targetFiles = Math.max(1, Math.ceil(files.length / 3));
644
692
  const consolidationPlan = generateConsolidationPlan(
@@ -686,7 +734,12 @@ function extractExports(content, filePath, domainOptions, fileImports) {
686
734
  while ((match = pattern.exec(content)) !== null) {
687
735
  const name = match[1] || "default";
688
736
  const type = types[index];
689
- const inferredDomain = inferDomain(name, filePath, domainOptions, fileImports);
737
+ const inferredDomain = inferDomain(
738
+ name,
739
+ filePath,
740
+ domainOptions,
741
+ fileImports
742
+ );
690
743
  exports2.push({ name, type, inferredDomain });
691
744
  }
692
745
  });
@@ -794,11 +847,17 @@ function extractExportsWithAST(content, filePath, domainOptions, fileImports) {
794
847
  return astExports.map((exp) => ({
795
848
  name: exp.name,
796
849
  type: exp.type,
797
- inferredDomain: inferDomain(exp.name, filePath, domainOptions, fileImports),
850
+ inferredDomain: inferDomain(
851
+ exp.name,
852
+ filePath,
853
+ domainOptions,
854
+ fileImports
855
+ ),
798
856
  imports: exp.imports,
799
857
  dependencies: exp.dependencies
800
858
  }));
801
859
  } catch (error) {
860
+ void error;
802
861
  return extractExports(content, filePath, domainOptions, fileImports);
803
862
  }
804
863
  }
@@ -813,15 +872,24 @@ function calculateEnhancedCohesion(exports2, filePath, options) {
813
872
  const importCohesion = hasImportData ? calculateImportBasedCohesion(exports2) : void 0;
814
873
  const coUsageMatrix = options?.coUsageMatrix;
815
874
  const structuralCohesion = filePath && coUsageMatrix ? calculateStructuralCohesionFromCoUsage(filePath, coUsageMatrix) : void 0;
816
- const defaultWeights = { importBased: 0.5, structural: 0.3, domainBased: 0.2 };
875
+ const defaultWeights = {
876
+ importBased: 0.5,
877
+ structural: 0.3,
878
+ domainBased: 0.2
879
+ };
817
880
  const weights = { ...defaultWeights, ...options?.weights || {} };
818
881
  const signals = [];
819
- if (importCohesion !== void 0) signals.push({ score: importCohesion, weight: weights.importBased });
820
- if (structuralCohesion !== void 0) signals.push({ score: structuralCohesion, weight: weights.structural });
882
+ if (importCohesion !== void 0)
883
+ signals.push({ score: importCohesion, weight: weights.importBased });
884
+ if (structuralCohesion !== void 0)
885
+ signals.push({ score: structuralCohesion, weight: weights.structural });
821
886
  signals.push({ score: domainCohesion, weight: weights.domainBased });
822
887
  const totalWeight = signals.reduce((s, el) => s + el.weight, 0);
823
888
  if (totalWeight === 0) return domainCohesion;
824
- const combined = signals.reduce((sum, el) => sum + el.score * (el.weight / totalWeight), 0);
889
+ const combined = signals.reduce(
890
+ (sum, el) => sum + el.score * (el.weight / totalWeight),
891
+ 0
892
+ );
825
893
  return combined;
826
894
  }
827
895
  function calculateStructuralCohesionFromCoUsage(file, coUsageMatrix) {
@@ -844,7 +912,9 @@ function calculateStructuralCohesionFromCoUsage(file, coUsageMatrix) {
844
912
  return maxEntropy > 0 ? 1 - entropy / maxEntropy : 1;
845
913
  }
846
914
  function calculateImportBasedCohesion(exports2) {
847
- const exportsWithImports = exports2.filter((e) => e.imports && e.imports.length > 0);
915
+ const exportsWithImports = exports2.filter(
916
+ (e) => e.imports && e.imports.length > 0
917
+ );
848
918
  if (exportsWithImports.length < 2) {
849
919
  return 1;
850
920
  }
@@ -889,6 +959,8 @@ function calculateDomainCohesion(exports2) {
889
959
  }
890
960
  function classifyFile(node, cohesionScore, domains) {
891
961
  const { exports: exports2, imports, linesOfCode, file } = node;
962
+ void imports;
963
+ void linesOfCode;
892
964
  if (isBarrelExport(node)) {
893
965
  return "barrel-export";
894
966
  }
@@ -967,8 +1039,12 @@ function isTypeDefinitionFile(node) {
967
1039
  const isTypesFile = fileName?.includes("types") || fileName?.includes(".d.ts") || fileName === "types.ts" || fileName === "interfaces.ts";
968
1040
  const lowerPath = file.toLowerCase();
969
1041
  const isTypesPath = lowerPath.includes("/types/") || lowerPath.includes("/typings/") || lowerPath.includes("/@types/") || lowerPath.startsWith("types/") || lowerPath.startsWith("typings/");
970
- const typeExports = exports2.filter((e) => e.type === "type" || e.type === "interface");
971
- const runtimeExports = exports2.filter((e) => e.type === "function" || e.type === "class" || e.type === "const");
1042
+ const typeExports = exports2.filter(
1043
+ (e) => e.type === "type" || e.type === "interface"
1044
+ );
1045
+ const runtimeExports = exports2.filter(
1046
+ (e) => e.type === "function" || e.type === "class" || e.type === "const"
1047
+ );
972
1048
  const mostlyTypes = exports2.length > 0 && typeExports.length > runtimeExports.length && typeExports.length / exports2.length > 0.7;
973
1049
  const pureTypeFile = exports2.length > 0 && typeExports.length === exports2.length;
974
1050
  const emptyOrReExportInTypesDir = isTypesPath && exports2.length === 0;
@@ -1149,12 +1225,7 @@ function isLambdaHandler(node) {
1149
1225
  function isServiceFile(node) {
1150
1226
  const { file, exports: exports2 } = node;
1151
1227
  const fileName = file.split("/").pop()?.toLowerCase();
1152
- const servicePatterns = [
1153
- "service",
1154
- ".service.",
1155
- "-service.",
1156
- "_service."
1157
- ];
1228
+ const servicePatterns = ["service", ".service.", "-service.", "_service."];
1158
1229
  const isServiceName = servicePatterns.some(
1159
1230
  (pattern) => fileName?.includes(pattern)
1160
1231
  );
@@ -1262,7 +1333,15 @@ function isNextJsPage(node) {
1262
1333
  }
1263
1334
  const exportNames = exports2.map((e) => e.name.toLowerCase());
1264
1335
  const hasDefaultExport = exports2.some((e) => e.type === "default");
1265
- const nextJsExports = ["metadata", "generatemetadata", "faqjsonld", "jsonld", "icon", "viewport", "dynamic"];
1336
+ const nextJsExports = [
1337
+ "metadata",
1338
+ "generatemetadata",
1339
+ "faqjsonld",
1340
+ "jsonld",
1341
+ "icon",
1342
+ "viewport",
1343
+ "dynamic"
1344
+ ];
1266
1345
  const hasNextJsExports = exportNames.some(
1267
1346
  (name) => nextJsExports.includes(name) || name.includes("jsonld")
1268
1347
  );
@@ -1336,13 +1415,44 @@ function hasRelatedExportNames(exportNames) {
1336
1415
  const stems = /* @__PURE__ */ new Set();
1337
1416
  const domains = /* @__PURE__ */ new Set();
1338
1417
  for (const name of exportNames) {
1339
- const verbs = ["get", "set", "create", "update", "delete", "fetch", "save", "load", "parse", "format", "validate", "convert", "transform", "build", "generate", "render", "send", "receive"];
1418
+ const verbs = [
1419
+ "get",
1420
+ "set",
1421
+ "create",
1422
+ "update",
1423
+ "delete",
1424
+ "fetch",
1425
+ "save",
1426
+ "load",
1427
+ "parse",
1428
+ "format",
1429
+ "validate",
1430
+ "convert",
1431
+ "transform",
1432
+ "build",
1433
+ "generate",
1434
+ "render",
1435
+ "send",
1436
+ "receive"
1437
+ ];
1340
1438
  for (const verb of verbs) {
1341
1439
  if (name.startsWith(verb) && name.length > verb.length) {
1342
1440
  stems.add(name.slice(verb.length).toLowerCase());
1343
1441
  }
1344
1442
  }
1345
- const domainPatterns = ["user", "order", "product", "session", "email", "file", "db", "s3", "dynamo", "api", "config"];
1443
+ const domainPatterns = [
1444
+ "user",
1445
+ "order",
1446
+ "product",
1447
+ "session",
1448
+ "email",
1449
+ "file",
1450
+ "db",
1451
+ "s3",
1452
+ "dynamo",
1453
+ "api",
1454
+ "config"
1455
+ ];
1346
1456
  for (const domain of domainPatterns) {
1347
1457
  if (name.includes(domain)) {
1348
1458
  domains.add(domain);
@@ -1400,11 +1510,15 @@ function hasRelatedExportNames(exportNames) {
1400
1510
  "all"
1401
1511
  ]);
1402
1512
  const singularize2 = (w) => w.endsWith("s") && w.length > 3 ? w.slice(0, -1) : w;
1403
- return new Set(tokens.filter((t) => !skip.has(t) && t.length > 2).map(singularize2));
1513
+ return new Set(
1514
+ tokens.filter((t) => !skip.has(t) && t.length > 2).map(singularize2)
1515
+ );
1404
1516
  });
1405
1517
  if (nounSets.length >= 2 && nounSets.every((s) => s.size > 0)) {
1406
1518
  const [first, ...rest] = nounSets;
1407
- const commonNouns = Array.from(first).filter((n) => rest.every((s) => s.has(n)));
1519
+ const commonNouns = Array.from(first).filter(
1520
+ (n) => rest.every((s) => s.has(n))
1521
+ );
1408
1522
  if (commonNouns.length > 0) return true;
1409
1523
  }
1410
1524
  return false;
@@ -1507,21 +1621,29 @@ async function analyzeContext(options) {
1507
1621
  // Only add node_modules to exclude if includeNodeModules is false
1508
1622
  // The DEFAULT_EXCLUDE already includes node_modules, so this is only needed
1509
1623
  // if user overrides the default exclude list
1510
- exclude: includeNodeModules && scanOptions.exclude ? scanOptions.exclude.filter((pattern) => pattern !== "**/node_modules/**") : scanOptions.exclude
1624
+ exclude: includeNodeModules && scanOptions.exclude ? scanOptions.exclude.filter(
1625
+ (pattern) => pattern !== "**/node_modules/**"
1626
+ ) : scanOptions.exclude
1511
1627
  });
1512
1628
  const pythonFiles = files.filter((f) => f.toLowerCase().endsWith(".py"));
1513
1629
  const tsJsFiles = files.filter((f) => !f.toLowerCase().endsWith(".py"));
1630
+ void tsJsFiles;
1514
1631
  const fileContents = await Promise.all(
1515
1632
  files.map(async (file) => ({
1516
1633
  file,
1517
1634
  content: await (0, import_core4.readFileContent)(file)
1518
1635
  }))
1519
1636
  );
1520
- const graph = buildDependencyGraph(fileContents.filter((f) => !f.file.toLowerCase().endsWith(".py")));
1637
+ const graph = buildDependencyGraph(
1638
+ fileContents.filter((f) => !f.file.toLowerCase().endsWith(".py"))
1639
+ );
1521
1640
  let pythonResults = [];
1522
1641
  if (pythonFiles.length > 0) {
1523
1642
  const { analyzePythonContext: analyzePythonContext2 } = await Promise.resolve().then(() => (init_python_context(), python_context_exports));
1524
- const pythonMetrics = await analyzePythonContext2(pythonFiles, scanOptions.rootDir || options.rootDir || ".");
1643
+ const pythonMetrics = await analyzePythonContext2(
1644
+ pythonFiles,
1645
+ scanOptions.rootDir || options.rootDir || "."
1646
+ );
1525
1647
  pythonResults = pythonMetrics.map((metric) => {
1526
1648
  const { severity, issues, recommendations, potentialSavings } = analyzeIssues({
1527
1649
  file: metric.file,
@@ -1534,17 +1656,25 @@ async function analyzeContext(options) {
1534
1656
  maxContextBudget,
1535
1657
  minCohesion,
1536
1658
  maxFragmentation,
1537
- circularDeps: metric.metrics.circularDependencies.map((cycle) => cycle.split(" \u2192 "))
1659
+ circularDeps: metric.metrics.circularDependencies.map(
1660
+ (cycle) => cycle.split(" \u2192 ")
1661
+ )
1538
1662
  });
1539
1663
  return {
1540
1664
  file: metric.file,
1541
- tokenCost: Math.floor(metric.contextBudget / (1 + metric.imports.length || 1)),
1665
+ tokenCost: Math.floor(
1666
+ metric.contextBudget / (1 + metric.imports.length || 1)
1667
+ ),
1542
1668
  // Estimate
1543
1669
  linesOfCode: metric.metrics.linesOfCode,
1544
1670
  importDepth: metric.importDepth,
1545
1671
  dependencyCount: metric.imports.length,
1546
- dependencyList: metric.imports.map((imp) => imp.resolvedPath || imp.source),
1547
- circularDeps: metric.metrics.circularDependencies.map((cycle) => cycle.split(" \u2192 ")),
1672
+ dependencyList: metric.imports.map(
1673
+ (imp) => imp.resolvedPath || imp.source
1674
+ ),
1675
+ circularDeps: metric.metrics.circularDependencies.map(
1676
+ (cycle) => cycle.split(" \u2192 ")
1677
+ ),
1548
1678
  cohesionScore: metric.cohesion,
1549
1679
  domains: ["python"],
1550
1680
  // Generic for now
@@ -1577,7 +1707,9 @@ async function analyzeContext(options) {
1577
1707
  const importDepth = focus === "depth" || focus === "all" ? calculateImportDepth(file, graph) : 0;
1578
1708
  const dependencyList = focus === "depth" || focus === "all" ? getTransitiveDependencies(file, graph) : [];
1579
1709
  const contextBudget = focus === "all" ? calculateContextBudget(file, graph) : node.tokenCost;
1580
- const cohesionScore = focus === "cohesion" || focus === "all" ? calculateCohesion(node.exports, file, { coUsageMatrix: graph.coUsageMatrix }) : 1;
1710
+ const cohesionScore = focus === "cohesion" || focus === "all" ? calculateCohesion(node.exports, file, {
1711
+ coUsageMatrix: graph.coUsageMatrix
1712
+ }) : 1;
1581
1713
  const fragmentationScore = fragmentationMap.get(file) || 0;
1582
1714
  const relatedFiles = [];
1583
1715
  for (const cluster of clusters) {
@@ -1598,6 +1730,10 @@ async function analyzeContext(options) {
1598
1730
  maxFragmentation,
1599
1731
  circularDeps
1600
1732
  });
1733
+ void severity;
1734
+ void issues;
1735
+ void recommendations;
1736
+ void potentialSavings;
1601
1737
  const domains = [
1602
1738
  ...new Set(node.exports.map((e) => e.inferredDomain || "unknown"))
1603
1739
  ];
@@ -1652,7 +1788,10 @@ async function analyzeContext(options) {
1652
1788
  fileClassification,
1653
1789
  severity: adjustedSeverity,
1654
1790
  issues: adjustedIssues,
1655
- recommendations: [...finalRecommendations, ...classificationRecommendations.slice(0, 1)],
1791
+ recommendations: [
1792
+ ...finalRecommendations,
1793
+ ...classificationRecommendations.slice(0, 1)
1794
+ ],
1656
1795
  potentialSavings: adjustedSavings
1657
1796
  });
1658
1797
  }
@@ -1731,7 +1870,10 @@ function generateSummary(results) {
1731
1870
  let importPairs = 0;
1732
1871
  for (let i = 0; i < files.length; i++) {
1733
1872
  for (let j = i + 1; j < files.length; j++) {
1734
- importSimTotal += jaccard2(files[i].dependencyList || [], files[j].dependencyList || []);
1873
+ importSimTotal += jaccard2(
1874
+ files[i].dependencyList || [],
1875
+ files[j].dependencyList || []
1876
+ );
1735
1877
  importPairs++;
1736
1878
  }
1737
1879
  }
@@ -1758,7 +1900,9 @@ function generateSummary(results) {
1758
1900
  fragmentedModules.sort((a, b) => b.fragmentationScore - a.fragmentationScore);
1759
1901
  const avgCohesion = results.reduce((sum, r) => sum + r.cohesionScore, 0) / totalFiles;
1760
1902
  const lowCohesionFiles = results.filter((r) => r.cohesionScore < 0.6).map((r) => ({ file: r.file, score: r.cohesionScore })).sort((a, b) => a.score - b.score).slice(0, 10);
1761
- const criticalIssues = results.filter((r) => r.severity === "critical").length;
1903
+ const criticalIssues = results.filter(
1904
+ (r) => r.severity === "critical"
1905
+ ).length;
1762
1906
  const majorIssues = results.filter((r) => r.severity === "major").length;
1763
1907
  const minorIssues = results.filter((r) => r.severity === "minor").length;
1764
1908
  const totalPotentialSavings = results.reduce(
@@ -1808,10 +1952,10 @@ function analyzeIssues(params) {
1808
1952
  let potentialSavings = 0;
1809
1953
  if (circularDeps.length > 0) {
1810
1954
  severity = "critical";
1811
- issues.push(
1812
- `Part of ${circularDeps.length} circular dependency chain(s)`
1955
+ issues.push(`Part of ${circularDeps.length} circular dependency chain(s)`);
1956
+ recommendations.push(
1957
+ "Break circular dependencies by extracting interfaces or using dependency injection"
1813
1958
  );
1814
- recommendations.push("Break circular dependencies by extracting interfaces or using dependency injection");
1815
1959
  potentialSavings += contextBudget * 0.2;
1816
1960
  }
1817
1961
  if (importDepth > maxDepth * 1.5) {
@@ -1821,25 +1965,37 @@ function analyzeIssues(params) {
1821
1965
  potentialSavings += contextBudget * 0.3;
1822
1966
  } else if (importDepth > maxDepth) {
1823
1967
  severity = severity === "critical" ? "critical" : "major";
1824
- issues.push(`Import depth ${importDepth} exceeds recommended maximum ${maxDepth}`);
1968
+ issues.push(
1969
+ `Import depth ${importDepth} exceeds recommended maximum ${maxDepth}`
1970
+ );
1825
1971
  recommendations.push("Consider reducing dependency depth");
1826
1972
  potentialSavings += contextBudget * 0.15;
1827
1973
  }
1828
1974
  if (contextBudget > maxContextBudget * 1.5) {
1829
1975
  severity = severity === "critical" ? "critical" : "critical";
1830
- issues.push(`Context budget ${contextBudget.toLocaleString()} tokens is 50% over limit`);
1831
- recommendations.push("Split into smaller modules or reduce dependency tree");
1976
+ issues.push(
1977
+ `Context budget ${contextBudget.toLocaleString()} tokens is 50% over limit`
1978
+ );
1979
+ recommendations.push(
1980
+ "Split into smaller modules or reduce dependency tree"
1981
+ );
1832
1982
  potentialSavings += contextBudget * 0.4;
1833
1983
  } else if (contextBudget > maxContextBudget) {
1834
1984
  severity = severity === "critical" || severity === "major" ? severity : "major";
1835
- issues.push(`Context budget ${contextBudget.toLocaleString()} exceeds ${maxContextBudget.toLocaleString()}`);
1985
+ issues.push(
1986
+ `Context budget ${contextBudget.toLocaleString()} exceeds ${maxContextBudget.toLocaleString()}`
1987
+ );
1836
1988
  recommendations.push("Reduce file size or dependencies");
1837
1989
  potentialSavings += contextBudget * 0.2;
1838
1990
  }
1839
1991
  if (cohesionScore < minCohesion * 0.5) {
1840
1992
  severity = severity === "critical" ? "critical" : "major";
1841
- issues.push(`Very low cohesion (${(cohesionScore * 100).toFixed(0)}%) - mixed concerns`);
1842
- recommendations.push("Split file by domain - separate unrelated functionality");
1993
+ issues.push(
1994
+ `Very low cohesion (${(cohesionScore * 100).toFixed(0)}%) - mixed concerns`
1995
+ );
1996
+ recommendations.push(
1997
+ "Split file by domain - separate unrelated functionality"
1998
+ );
1843
1999
  potentialSavings += contextBudget * 0.25;
1844
2000
  } else if (cohesionScore < minCohesion) {
1845
2001
  severity = severity === "critical" || severity === "major" ? severity : "minor";
@@ -1849,7 +2005,9 @@ function analyzeIssues(params) {
1849
2005
  }
1850
2006
  if (fragmentationScore > maxFragmentation) {
1851
2007
  severity = severity === "critical" || severity === "major" ? severity : "minor";
1852
- issues.push(`High fragmentation (${(fragmentationScore * 100).toFixed(0)}%) - scattered implementation`);
2008
+ issues.push(
2009
+ `High fragmentation (${(fragmentationScore * 100).toFixed(0)}%) - scattered implementation`
2010
+ );
1853
2011
  recommendations.push("Consolidate with related files in same domain");
1854
2012
  potentialSavings += contextBudget * 0.3;
1855
2013
  }
@@ -1859,11 +2017,18 @@ function analyzeIssues(params) {
1859
2017
  }
1860
2018
  if (isBuildArtifact(file)) {
1861
2019
  issues.push("Detected build artifact (bundled/output file)");
1862
- recommendations.push("Exclude build outputs (e.g., cdk.out, dist, build, .next) from analysis");
2020
+ recommendations.push(
2021
+ "Exclude build outputs (e.g., cdk.out, dist, build, .next) from analysis"
2022
+ );
1863
2023
  severity = downgradeSeverity(severity);
1864
2024
  potentialSavings = 0;
1865
2025
  }
1866
- return { severity, issues, recommendations, potentialSavings: Math.floor(potentialSavings) };
2026
+ return {
2027
+ severity,
2028
+ issues,
2029
+ recommendations,
2030
+ potentialSavings: Math.floor(potentialSavings)
2031
+ };
1867
2032
  }
1868
2033
  function isBuildArtifact(filePath) {
1869
2034
  const lower = filePath.toLowerCase();
@@ -1889,7 +2054,10 @@ var import_path2 = require("path");
1889
2054
  var import_core5 = require("@aiready/core");
1890
2055
  var import_prompts = __toESM(require("prompts"));
1891
2056
  var program = new import_commander.Command();
1892
- program.name("aiready-context").description("Analyze AI context window cost and code structure").version("0.1.0").addHelpText("after", "\nCONFIGURATION:\n Supports config files: aiready.json, aiready.config.json, .aiready.json, .aireadyrc.json, aiready.config.js, .aireadyrc.js\n CLI options override config file settings").argument("<directory>", "Directory to analyze").option("--max-depth <number>", "Maximum acceptable import depth").option(
2057
+ program.name("aiready-context").description("Analyze AI context window cost and code structure").version("0.1.0").addHelpText(
2058
+ "after",
2059
+ "\nCONFIGURATION:\n Supports config files: aiready.json, aiready.config.json, .aiready.json, .aireadyrc.json, aiready.config.js, .aireadyrc.js\n CLI options override config file settings"
2060
+ ).argument("<directory>", "Directory to analyze").option("--max-depth <number>", "Maximum acceptable import depth").option(
1893
2061
  "--max-context <number>",
1894
2062
  "Maximum acceptable context budget (tokens)"
1895
2063
  ).option("--min-cohesion <number>", "Minimum acceptable cohesion score (0-1)").option(
@@ -1898,11 +2066,17 @@ program.name("aiready-context").description("Analyze AI context window cost and
1898
2066
  ).option(
1899
2067
  "--focus <type>",
1900
2068
  "Analysis focus: fragmentation, cohesion, depth, all"
1901
- ).option("--include-node-modules", "Include node_modules in analysis").option("--include <patterns>", "File patterns to include (comma-separated)").option("--exclude <patterns>", "File patterns to exclude (comma-separated)").option("--max-results <number>", "Maximum number of results to show in console output").option(
2069
+ ).option("--include-node-modules", "Include node_modules in analysis").option("--include <patterns>", "File patterns to include (comma-separated)").option("--exclude <patterns>", "File patterns to exclude (comma-separated)").option(
2070
+ "--max-results <number>",
2071
+ "Maximum number of results to show in console output"
2072
+ ).option(
1902
2073
  "-o, --output <format>",
1903
2074
  "Output format: console, json, html",
1904
2075
  "console"
1905
- ).option("--output-file <path>", "Output file path (for json/html)").option("--interactive", "Run interactive setup to suggest excludes and focus areas").action(async (directory, options) => {
2076
+ ).option("--output-file <path>", "Output file path (for json/html)").option(
2077
+ "--interactive",
2078
+ "Run interactive setup to suggest excludes and focus areas"
2079
+ ).action(async (directory, options) => {
1906
2080
  console.log(import_chalk.default.blue("\u{1F50D} Analyzing context window costs...\n"));
1907
2081
  const startTime = Date.now();
1908
2082
  try {
@@ -1946,8 +2120,12 @@ program.name("aiready-context").description("Analyze AI context window cost and
1946
2120
  `context-report-${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}.json`,
1947
2121
  directory
1948
2122
  );
1949
- (0, import_core5.handleJSONOutput)(jsonOutput, outputPath, `
1950
- \u2713 JSON report saved to ${outputPath}`);
2123
+ (0, import_core5.handleJSONOutput)(
2124
+ jsonOutput,
2125
+ outputPath,
2126
+ `
2127
+ \u2713 JSON report saved to ${outputPath}`
2128
+ );
1951
2129
  return;
1952
2130
  }
1953
2131
  if (options.output === "html") {
@@ -1966,7 +2144,12 @@ program.name("aiready-context").description("Analyze AI context window cost and
1966
2144
  \u2713 HTML report saved to ${outputPath}`));
1967
2145
  return;
1968
2146
  }
1969
- displayConsoleReport(summary, results, elapsedTime, finalOptions.maxResults);
2147
+ displayConsoleReport(
2148
+ summary,
2149
+ results,
2150
+ elapsedTime,
2151
+ finalOptions.maxResults
2152
+ );
1970
2153
  displayTuningGuidance(results, finalOptions);
1971
2154
  } catch (error) {
1972
2155
  (0, import_core5.handleCLIError)(error, "Analysis");
@@ -1976,28 +2159,80 @@ program.parse();
1976
2159
  function displayTuningGuidance(results, options) {
1977
2160
  const issueCount = results.filter((r) => r.severity !== "info").length;
1978
2161
  if (issueCount === 0) {
1979
- console.log(import_chalk.default.green("\n\u2728 No optimization opportunities found! Your code is well-structured for AI context usage.\n"));
2162
+ console.log(
2163
+ import_chalk.default.green(
2164
+ "\n\u2728 No optimization opportunities found! Your code is well-structured for AI context usage.\n"
2165
+ )
2166
+ );
1980
2167
  return;
1981
2168
  }
1982
2169
  console.log(import_chalk.default.cyan("\n\u2501".repeat(60)));
1983
2170
  console.log(import_chalk.default.bold.white(" TUNING GUIDANCE"));
1984
2171
  console.log(import_chalk.default.cyan("\u2501".repeat(60) + "\n"));
1985
2172
  if (issueCount < 5) {
1986
- console.log(import_chalk.default.yellow("\u{1F4CA} Showing few optimization opportunities. To find more areas to improve:\n"));
1987
- console.log(import_chalk.default.dim(" \u2022 Lower --max-depth (currently: " + options.maxDepth + ") to catch shallower import chains"));
1988
- console.log(import_chalk.default.dim(" \u2022 Lower --max-context (currently: " + options.maxContextBudget.toLocaleString() + ") to catch smaller files"));
1989
- console.log(import_chalk.default.dim(" \u2022 Raise --min-cohesion (currently: " + (options.minCohesion * 100).toFixed(0) + "%) to be stricter about mixed concerns"));
1990
- console.log(import_chalk.default.dim(" \u2022 Lower --max-fragmentation (currently: " + (options.maxFragmentation * 100).toFixed(0) + "%) to catch scattered code\n"));
2173
+ console.log(
2174
+ import_chalk.default.yellow(
2175
+ "\u{1F4CA} Showing few optimization opportunities. To find more areas to improve:\n"
2176
+ )
2177
+ );
2178
+ console.log(
2179
+ import_chalk.default.dim(
2180
+ " \u2022 Lower --max-depth (currently: " + options.maxDepth + ") to catch shallower import chains"
2181
+ )
2182
+ );
2183
+ console.log(
2184
+ import_chalk.default.dim(
2185
+ " \u2022 Lower --max-context (currently: " + options.maxContextBudget.toLocaleString() + ") to catch smaller files"
2186
+ )
2187
+ );
2188
+ console.log(
2189
+ import_chalk.default.dim(
2190
+ " \u2022 Raise --min-cohesion (currently: " + (options.minCohesion * 100).toFixed(0) + "%) to be stricter about mixed concerns"
2191
+ )
2192
+ );
2193
+ console.log(
2194
+ import_chalk.default.dim(
2195
+ " \u2022 Lower --max-fragmentation (currently: " + (options.maxFragmentation * 100).toFixed(0) + "%) to catch scattered code\n"
2196
+ )
2197
+ );
1991
2198
  } else if (issueCount > 20) {
1992
- console.log(import_chalk.default.yellow("\u{1F4CA} Showing many opportunities. To focus on highest-impact areas:\n"));
1993
- console.log(import_chalk.default.dim(" \u2022 Raise --max-depth (currently: " + options.maxDepth + ") to only catch very deep chains"));
1994
- console.log(import_chalk.default.dim(" \u2022 Raise --max-context (currently: " + options.maxContextBudget.toLocaleString() + ") to focus on largest files"));
1995
- console.log(import_chalk.default.dim(" \u2022 Lower --min-cohesion (currently: " + (options.minCohesion * 100).toFixed(0) + "%) to only flag severe mixed concerns"));
1996
- console.log(import_chalk.default.dim(" \u2022 Raise --max-fragmentation (currently: " + (options.maxFragmentation * 100).toFixed(0) + "%) to only flag highly scattered code\n"));
2199
+ console.log(
2200
+ import_chalk.default.yellow(
2201
+ "\u{1F4CA} Showing many opportunities. To focus on highest-impact areas:\n"
2202
+ )
2203
+ );
2204
+ console.log(
2205
+ import_chalk.default.dim(
2206
+ " \u2022 Raise --max-depth (currently: " + options.maxDepth + ") to only catch very deep chains"
2207
+ )
2208
+ );
2209
+ console.log(
2210
+ import_chalk.default.dim(
2211
+ " \u2022 Raise --max-context (currently: " + options.maxContextBudget.toLocaleString() + ") to focus on largest files"
2212
+ )
2213
+ );
2214
+ console.log(
2215
+ import_chalk.default.dim(
2216
+ " \u2022 Lower --min-cohesion (currently: " + (options.minCohesion * 100).toFixed(0) + "%) to only flag severe mixed concerns"
2217
+ )
2218
+ );
2219
+ console.log(
2220
+ import_chalk.default.dim(
2221
+ " \u2022 Raise --max-fragmentation (currently: " + (options.maxFragmentation * 100).toFixed(0) + "%) to only flag highly scattered code\n"
2222
+ )
2223
+ );
1997
2224
  } else {
1998
- console.log(import_chalk.default.green("\u{1F4CA} Good balance of optimization opportunities (showing " + issueCount + " areas)\n"));
2225
+ console.log(
2226
+ import_chalk.default.green(
2227
+ "\u{1F4CA} Good balance of optimization opportunities (showing " + issueCount + " areas)\n"
2228
+ )
2229
+ );
1999
2230
  console.log(import_chalk.default.dim(" \u{1F4A1} Tip: Adjust thresholds if needed:"));
2000
- console.log(import_chalk.default.dim(" aiready-context . --max-depth N --max-context N --min-cohesion 0.X\n"));
2231
+ console.log(
2232
+ import_chalk.default.dim(
2233
+ " aiready-context . --max-depth N --max-context N --min-cohesion 0.X\n"
2234
+ )
2235
+ );
2001
2236
  }
2002
2237
  console.log(import_chalk.default.dim(" \u{1F4D6} See README for detailed tuning guide\n"));
2003
2238
  }
@@ -2008,9 +2243,13 @@ function displayConsoleReport(summary, results, elapsedTime, maxResults = 10) {
2008
2243
  console.log(import_chalk.default.cyan(divider));
2009
2244
  console.log(import_chalk.default.bold.white(" CONTEXT ANALYSIS SUMMARY"));
2010
2245
  console.log(import_chalk.default.cyan(divider) + "\n");
2011
- console.log(import_chalk.default.white(`\u{1F4C1} Files analyzed: ${import_chalk.default.bold(summary.totalFiles)}`));
2012
2246
  console.log(
2013
- import_chalk.default.white(`\u{1F4CA} Total tokens: ${import_chalk.default.bold(summary.totalTokens.toLocaleString())}`)
2247
+ import_chalk.default.white(`\u{1F4C1} Files analyzed: ${import_chalk.default.bold(summary.totalFiles)}`)
2248
+ );
2249
+ console.log(
2250
+ import_chalk.default.white(
2251
+ `\u{1F4CA} Total tokens: ${import_chalk.default.bold(summary.totalTokens.toLocaleString())}`
2252
+ )
2014
2253
  );
2015
2254
  console.log(
2016
2255
  import_chalk.default.yellow(
@@ -2035,7 +2274,9 @@ function displayConsoleReport(summary, results, elapsedTime, maxResults = 10) {
2035
2274
  );
2036
2275
  }
2037
2276
  if (summary.minorIssues > 0) {
2038
- console.log(import_chalk.default.blue(` \u{1F535} Minor: ${import_chalk.default.bold(summary.minorIssues)}`));
2277
+ console.log(
2278
+ import_chalk.default.blue(` \u{1F535} Minor: ${import_chalk.default.bold(summary.minorIssues)}`)
2279
+ );
2039
2280
  }
2040
2281
  console.log(
2041
2282
  import_chalk.default.green(
@@ -2130,11 +2371,14 @@ function displayConsoleReport(summary, results, elapsedTime, maxResults = 10) {
2130
2371
  )
2131
2372
  );
2132
2373
  console.log(
2133
- import_chalk.default.dim("\u{1F41B} Found a bug? Report it: https://github.com/caopengau/aiready-context-analyzer/issues\n")
2374
+ import_chalk.default.dim(
2375
+ "\u{1F41B} Found a bug? Report it: https://github.com/caopengau/aiready-context-analyzer/issues\n"
2376
+ )
2134
2377
  );
2135
2378
  }
2136
2379
  function generateHTMLReport(summary, results) {
2137
2380
  const totalIssues = summary.criticalIssues + summary.majorIssues + summary.minorIssues;
2381
+ void results;
2138
2382
  return `<!DOCTYPE html>
2139
2383
  <html lang="en">
2140
2384
  <head>
@@ -2262,14 +2506,16 @@ function generateHTMLReport(summary, results) {
2262
2506
  </tr>
2263
2507
  </thead>
2264
2508
  <tbody>
2265
- ${summary.fragmentedModules.map((m) => `
2509
+ ${summary.fragmentedModules.map(
2510
+ (m) => `
2266
2511
  <tr>
2267
2512
  <td>${m.domain}</td>
2268
2513
  <td>${m.files.length}</td>
2269
2514
  <td>${(m.fragmentationScore * 100).toFixed(0)}%</td>
2270
2515
  <td>${m.totalTokens.toLocaleString()}</td>
2271
2516
  </tr>
2272
- `).join("")}
2517
+ `
2518
+ ).join("")}
2273
2519
  </tbody>
2274
2520
  </table>
2275
2521
  </div>
@@ -2287,13 +2533,15 @@ function generateHTMLReport(summary, results) {
2287
2533
  </tr>
2288
2534
  </thead>
2289
2535
  <tbody>
2290
- ${summary.topExpensiveFiles.map((f) => `
2536
+ ${summary.topExpensiveFiles.map(
2537
+ (f) => `
2291
2538
  <tr>
2292
2539
  <td>${f.file}</td>
2293
2540
  <td>${f.contextBudget.toLocaleString()} tokens</td>
2294
2541
  <td class="issue-${f.severity}">${f.severity.toUpperCase()}</td>
2295
2542
  </tr>
2296
- `).join("")}
2543
+ `
2544
+ ).join("")}
2297
2545
  </tbody>
2298
2546
  </table>
2299
2547
  </div>
@@ -2315,7 +2563,8 @@ async function runInteractiveSetup(directory, current) {
2315
2563
  try {
2316
2564
  const pkg = JSON.parse((0, import_fs2.readFileSync)(pkgPath, "utf-8"));
2317
2565
  deps = { ...pkg.dependencies || {}, ...pkg.devDependencies || {} };
2318
- } catch {
2566
+ } catch (e) {
2567
+ void e;
2319
2568
  }
2320
2569
  }
2321
2570
  const hasNextJs = (0, import_fs2.existsSync)((0, import_path2.join)(directory, ".next")) || !!deps["next"];
@@ -2335,7 +2584,7 @@ async function runInteractiveSetup(directory, current) {
2335
2584
  active: "yes",
2336
2585
  inactive: "no"
2337
2586
  });
2338
- let nextOptions = { ...current };
2587
+ const nextOptions = { ...current };
2339
2588
  if (applyExcludes) {
2340
2589
  nextOptions.exclude = Array.from(recommendedExcludes);
2341
2590
  }
@@ -2352,9 +2601,23 @@ async function runInteractiveSetup(directory, current) {
2352
2601
  });
2353
2602
  if (focusArea === "frontend") {
2354
2603
  nextOptions.include = ["**/*.{ts,tsx,js,jsx}"];
2355
- nextOptions.exclude = Array.from(/* @__PURE__ */ new Set([...nextOptions.exclude || [], "**/cdk.out/**", "**/infra/**", "**/server/**", "**/backend/**"]));
2604
+ nextOptions.exclude = Array.from(
2605
+ /* @__PURE__ */ new Set([
2606
+ ...nextOptions.exclude || [],
2607
+ "**/cdk.out/**",
2608
+ "**/infra/**",
2609
+ "**/server/**",
2610
+ "**/backend/**"
2611
+ ])
2612
+ );
2356
2613
  } else if (focusArea === "backend") {
2357
- nextOptions.include = ["**/api/**", "**/server/**", "**/backend/**", "**/infra/**", "**/*.{ts,js,py,java}"];
2614
+ nextOptions.include = [
2615
+ "**/api/**",
2616
+ "**/server/**",
2617
+ "**/backend/**",
2618
+ "**/infra/**",
2619
+ "**/*.{ts,js,py,java}"
2620
+ ];
2358
2621
  }
2359
2622
  console.log(import_chalk.default.green("\u2713 Interactive configuration applied."));
2360
2623
  return nextOptions;