@aiready/context-analyzer 0.21.1 → 0.21.6

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.
Files changed (153) hide show
  1. package/.aiready/aiready-report-20260314-222254.json +39216 -0
  2. package/.aiready/aiready-report-20260314-223947.json +3413 -0
  3. package/.aiready/aiready-report-20260314-224112.json +3413 -0
  4. package/.aiready/aiready-report-20260314-224302.json +2973 -0
  5. package/.aiready/aiready-report-20260314-224939.json +3092 -0
  6. package/.aiready/aiready-report-20260314-225154.json +3092 -0
  7. package/.turbo/turbo-build.log +13 -12
  8. package/.turbo/turbo-test.log +32 -341
  9. package/dist/__tests__/analyzer.test.js +55 -14
  10. package/dist/__tests__/analyzer.test.js.map +1 -1
  11. package/dist/__tests__/cluster-detector.test.d.ts +2 -0
  12. package/dist/__tests__/cluster-detector.test.d.ts.map +1 -0
  13. package/dist/__tests__/cluster-detector.test.js +121 -0
  14. package/dist/__tests__/cluster-detector.test.js.map +1 -0
  15. package/dist/__tests__/contract.test.d.ts +2 -0
  16. package/dist/__tests__/contract.test.d.ts.map +1 -0
  17. package/dist/__tests__/contract.test.js +59 -0
  18. package/dist/__tests__/contract.test.js.map +1 -0
  19. package/dist/__tests__/enhanced-cohesion.test.js +12 -2
  20. package/dist/__tests__/enhanced-cohesion.test.js.map +1 -1
  21. package/dist/__tests__/file-classification.test.d.ts +2 -0
  22. package/dist/__tests__/file-classification.test.d.ts.map +1 -0
  23. package/dist/__tests__/file-classification.test.js +749 -0
  24. package/dist/__tests__/file-classification.test.js.map +1 -0
  25. package/dist/__tests__/fragmentation-advanced.test.js +2 -8
  26. package/dist/__tests__/fragmentation-advanced.test.js.map +1 -1
  27. package/dist/__tests__/fragmentation-coupling.test.js +2 -2
  28. package/dist/__tests__/fragmentation-coupling.test.js.map +1 -1
  29. package/dist/__tests__/fragmentation-log.test.js +3 -7
  30. package/dist/__tests__/fragmentation-log.test.js.map +1 -1
  31. package/dist/__tests__/provider.test.d.ts +2 -0
  32. package/dist/__tests__/provider.test.d.ts.map +1 -0
  33. package/dist/__tests__/provider.test.js +72 -0
  34. package/dist/__tests__/provider.test.js.map +1 -0
  35. package/dist/__tests__/remediation.test.d.ts +2 -0
  36. package/dist/__tests__/remediation.test.d.ts.map +1 -0
  37. package/dist/__tests__/remediation.test.js +61 -0
  38. package/dist/__tests__/remediation.test.js.map +1 -0
  39. package/dist/__tests__/scoring.test.js +196 -16
  40. package/dist/__tests__/scoring.test.js.map +1 -1
  41. package/dist/__tests__/structural-cohesion.test.js +8 -2
  42. package/dist/__tests__/structural-cohesion.test.js.map +1 -1
  43. package/dist/analyzer.d.ts +31 -94
  44. package/dist/analyzer.d.ts.map +1 -1
  45. package/dist/analyzer.js +260 -678
  46. package/dist/analyzer.js.map +1 -1
  47. package/dist/analyzers/python-context.d.ts.map +1 -1
  48. package/dist/analyzers/python-context.js +10 -8
  49. package/dist/analyzers/python-context.js.map +1 -1
  50. package/dist/ast-utils.d.ts +16 -0
  51. package/dist/ast-utils.d.ts.map +1 -0
  52. package/dist/ast-utils.js +81 -0
  53. package/dist/ast-utils.js.map +1 -0
  54. package/dist/chunk-64U3PNO3.mjs +94 -0
  55. package/dist/chunk-CDIVYADN.mjs +2110 -0
  56. package/dist/chunk-D3SIHB2V.mjs +2118 -0
  57. package/dist/chunk-FNPSK3CG.mjs +1760 -0
  58. package/dist/chunk-GXTGOLZT.mjs +92 -0
  59. package/dist/chunk-LERPI33Y.mjs +2060 -0
  60. package/dist/chunk-MZP3G7TF.mjs +2118 -0
  61. package/dist/chunk-NOHK5DLU.mjs +2173 -0
  62. package/dist/chunk-ORLC5Y4J.mjs +1787 -0
  63. package/dist/chunk-OTCQL7DY.mjs +2045 -0
  64. package/dist/chunk-OUYSZZ7X.mjs +1846 -0
  65. package/dist/chunk-SFK6XTJE.mjs +2110 -0
  66. package/dist/chunk-U5R2FTCR.mjs +1803 -0
  67. package/dist/chunk-UU4HZ7ZT.mjs +1849 -0
  68. package/dist/chunk-W2KNBN6W.mjs +1849 -0
  69. package/dist/chunk-WKOZOHOU.mjs +2060 -0
  70. package/dist/chunk-XIXAWCMS.mjs +1760 -0
  71. package/dist/classifier.d.ts +114 -0
  72. package/dist/classifier.d.ts.map +1 -0
  73. package/dist/classifier.js +439 -0
  74. package/dist/classifier.js.map +1 -0
  75. package/dist/cli.js +591 -1057
  76. package/dist/cli.js.map +1 -1
  77. package/dist/cli.mjs +63 -533
  78. package/dist/cluster-detector.d.ts +8 -0
  79. package/dist/cluster-detector.d.ts.map +1 -0
  80. package/dist/cluster-detector.js +70 -0
  81. package/dist/cluster-detector.js.map +1 -0
  82. package/dist/defaults.d.ts +7 -0
  83. package/dist/defaults.d.ts.map +1 -0
  84. package/dist/defaults.js +54 -0
  85. package/dist/defaults.js.map +1 -0
  86. package/dist/graph-builder.d.ts +33 -0
  87. package/dist/graph-builder.d.ts.map +1 -0
  88. package/dist/graph-builder.js +225 -0
  89. package/dist/graph-builder.js.map +1 -0
  90. package/dist/index.d.mts +24 -31
  91. package/dist/index.d.ts +24 -31
  92. package/dist/index.d.ts.map +1 -1
  93. package/dist/index.js +822 -588
  94. package/dist/index.js.map +1 -1
  95. package/dist/index.mjs +265 -8
  96. package/dist/metrics.d.ts +34 -0
  97. package/dist/metrics.d.ts.map +1 -0
  98. package/dist/metrics.js +170 -0
  99. package/dist/metrics.js.map +1 -0
  100. package/dist/provider.d.ts +6 -0
  101. package/dist/provider.d.ts.map +1 -0
  102. package/dist/provider.js +48 -0
  103. package/dist/provider.js.map +1 -0
  104. package/dist/python-context-3GZKN3LR.mjs +162 -0
  105. package/dist/python-context-O2EN3M6Z.mjs +162 -0
  106. package/dist/remediation.d.ts +25 -0
  107. package/dist/remediation.d.ts.map +1 -0
  108. package/dist/remediation.js +98 -0
  109. package/dist/remediation.js.map +1 -0
  110. package/dist/scoring.d.ts +3 -7
  111. package/dist/scoring.d.ts.map +1 -1
  112. package/dist/scoring.js +57 -48
  113. package/dist/scoring.js.map +1 -1
  114. package/dist/semantic-analysis.d.ts +12 -23
  115. package/dist/semantic-analysis.d.ts.map +1 -1
  116. package/dist/semantic-analysis.js +172 -110
  117. package/dist/semantic-analysis.js.map +1 -1
  118. package/dist/summary.d.ts +6 -0
  119. package/dist/summary.d.ts.map +1 -0
  120. package/dist/summary.js +92 -0
  121. package/dist/summary.js.map +1 -0
  122. package/dist/types.d.ts +9 -2
  123. package/dist/types.d.ts.map +1 -1
  124. package/dist/utils/output-formatter.d.ts +14 -0
  125. package/dist/utils/output-formatter.d.ts.map +1 -0
  126. package/dist/utils/output-formatter.js +338 -0
  127. package/dist/utils/output-formatter.js.map +1 -0
  128. package/package.json +2 -2
  129. package/src/__tests__/analyzer.test.ts +1 -1
  130. package/src/__tests__/auto-detection.test.ts +1 -1
  131. package/src/__tests__/contract.test.ts +1 -1
  132. package/src/__tests__/enhanced-cohesion.test.ts +1 -1
  133. package/src/__tests__/file-classification.test.ts +1 -1
  134. package/src/__tests__/fragmentation-advanced.test.ts +1 -1
  135. package/src/__tests__/fragmentation-coupling.test.ts +1 -1
  136. package/src/__tests__/fragmentation-log.test.ts +1 -1
  137. package/src/__tests__/provider.test.ts +1 -1
  138. package/src/__tests__/scoring.test.ts +217 -9
  139. package/src/__tests__/structural-cohesion.test.ts +1 -1
  140. package/src/analyzer.ts +96 -309
  141. package/src/analyzers/python-context.ts +7 -76
  142. package/src/cli-action.ts +103 -0
  143. package/src/cli.ts +12 -693
  144. package/src/cluster-detector.ts +1 -1
  145. package/src/graph-builder.ts +9 -85
  146. package/src/index.ts +6 -0
  147. package/src/issue-analyzer.ts +143 -0
  148. package/src/scoring.ts +40 -20
  149. package/src/semantic-analysis.ts +1 -14
  150. package/src/summary.ts +62 -106
  151. package/src/utils/dependency-graph-utils.ts +126 -0
  152. package/src/utils/output-formatter.ts +411 -0
  153. package/src/utils/string-utils.ts +17 -0
package/dist/cli.js CHANGED
@@ -30,6 +30,99 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
30
30
  mod
31
31
  ));
32
32
 
33
+ // src/utils/dependency-graph-utils.ts
34
+ function calculateImportDepthFromEdges(file, edges, visited = /* @__PURE__ */ new Set(), depth = 0) {
35
+ if (visited.has(file)) return depth;
36
+ const dependencies = edges.get(file);
37
+ if (!dependencies || dependencies.size === 0) return depth;
38
+ const nextVisited = new Set(visited);
39
+ nextVisited.add(file);
40
+ let maxDepth = depth;
41
+ for (const dep of dependencies) {
42
+ maxDepth = Math.max(
43
+ maxDepth,
44
+ calculateImportDepthFromEdges(dep, edges, nextVisited, depth + 1)
45
+ );
46
+ }
47
+ return maxDepth;
48
+ }
49
+ function getTransitiveDependenciesFromEdges(file, edges, visited = /* @__PURE__ */ new Set()) {
50
+ if (visited.has(file)) return [];
51
+ const nextVisited = new Set(visited);
52
+ nextVisited.add(file);
53
+ const dependencies = edges.get(file);
54
+ if (!dependencies || dependencies.size === 0) return [];
55
+ const allDeps = [];
56
+ for (const dep of dependencies) {
57
+ allDeps.push(dep);
58
+ allDeps.push(
59
+ ...getTransitiveDependenciesFromEdges(dep, edges, nextVisited)
60
+ );
61
+ }
62
+ return [...new Set(allDeps)];
63
+ }
64
+ function detectGraphCycles(edges) {
65
+ const cycles = [];
66
+ const visited = /* @__PURE__ */ new Set();
67
+ const recursionStack = /* @__PURE__ */ new Set();
68
+ function dfs(file, path) {
69
+ if (recursionStack.has(file)) {
70
+ const cycleStart = path.indexOf(file);
71
+ if (cycleStart !== -1) {
72
+ cycles.push([...path.slice(cycleStart), file]);
73
+ }
74
+ return;
75
+ }
76
+ if (visited.has(file)) return;
77
+ visited.add(file);
78
+ recursionStack.add(file);
79
+ const dependencies = edges.get(file);
80
+ if (dependencies) {
81
+ for (const dep of dependencies) {
82
+ dfs(dep, [...path, file]);
83
+ }
84
+ }
85
+ recursionStack.delete(file);
86
+ }
87
+ for (const file of edges.keys()) {
88
+ if (!visited.has(file)) {
89
+ dfs(file, []);
90
+ }
91
+ }
92
+ return cycles;
93
+ }
94
+ function detectGraphCyclesFromFile(file, edges) {
95
+ const cycles = [];
96
+ const visited = /* @__PURE__ */ new Set();
97
+ const recursionStack = /* @__PURE__ */ new Set();
98
+ function dfs(current, path) {
99
+ if (recursionStack.has(current)) {
100
+ const cycleStart = path.indexOf(current);
101
+ if (cycleStart !== -1) {
102
+ cycles.push([...path.slice(cycleStart), current]);
103
+ }
104
+ return;
105
+ }
106
+ if (visited.has(current)) return;
107
+ visited.add(current);
108
+ recursionStack.add(current);
109
+ const dependencies = edges.get(current);
110
+ if (dependencies) {
111
+ for (const dep of dependencies) {
112
+ dfs(dep, [...path, current]);
113
+ }
114
+ }
115
+ recursionStack.delete(current);
116
+ }
117
+ dfs(file, []);
118
+ return cycles;
119
+ }
120
+ var init_dependency_graph_utils = __esm({
121
+ "src/utils/dependency-graph-utils.ts"() {
122
+ "use strict";
123
+ }
124
+ });
125
+
33
126
  // src/analyzers/python-context.ts
34
127
  var python_context_exports = {};
35
128
  __export(python_context_exports, {
@@ -64,7 +157,7 @@ async function analyzePythonContext(files, rootDir) {
64
157
  type: exp.type
65
158
  }));
66
159
  const linesOfCode = code.split("\n").length;
67
- const importDepth = await calculatePythonImportDepth(
160
+ const importDepth = calculateImportDepthFromEdges(
68
161
  file,
69
162
  dependencyGraph,
70
163
  /* @__PURE__ */ new Set()
@@ -75,10 +168,10 @@ async function analyzePythonContext(files, rootDir) {
75
168
  dependencyGraph
76
169
  );
77
170
  const cohesion = calculatePythonCohesion(exports2, imports);
78
- const circularDependencies = detectCircularDependencies2(
171
+ const circularDependencies = detectGraphCyclesFromFile(
79
172
  file,
80
173
  dependencyGraph
81
- );
174
+ ).map((cycle) => cycle.join(" -> "));
82
175
  results.push({
83
176
  file,
84
177
  importDepth,
@@ -158,27 +251,6 @@ function resolvePythonImport(fromFile, importPath, rootDir) {
158
251
  }
159
252
  return void 0;
160
253
  }
161
- async function calculatePythonImportDepth(file, dependencyGraph, visited, depth = 0) {
162
- if (visited.has(file)) {
163
- return depth;
164
- }
165
- visited.add(file);
166
- const dependencies = dependencyGraph.get(file) || /* @__PURE__ */ new Set();
167
- if (dependencies.size === 0) {
168
- return depth;
169
- }
170
- let maxDepth = depth;
171
- for (const dep of dependencies) {
172
- const depDepth = await calculatePythonImportDepth(
173
- dep,
174
- dependencyGraph,
175
- new Set(visited),
176
- depth + 1
177
- );
178
- maxDepth = Math.max(maxDepth, depDepth);
179
- }
180
- return maxDepth;
181
- }
182
254
  function estimateContextBudget(code, imports, dependencyGraph) {
183
255
  void dependencyGraph;
184
256
  let budget = (0, import_core5.estimateTokens)(code);
@@ -206,31 +278,6 @@ function calculatePythonCohesion(exports2, imports) {
206
278
  }
207
279
  return Math.min(1, Math.max(0, cohesion));
208
280
  }
209
- function detectCircularDependencies2(file, dependencyGraph) {
210
- const circular = [];
211
- const visited = /* @__PURE__ */ new Set();
212
- const recursionStack = /* @__PURE__ */ new Set();
213
- function dfs(current, path) {
214
- if (recursionStack.has(current)) {
215
- const cycleStart = path.indexOf(current);
216
- const cycle = path.slice(cycleStart).concat([current]);
217
- circular.push(cycle.join(" \u2192 "));
218
- return;
219
- }
220
- if (visited.has(current)) {
221
- return;
222
- }
223
- visited.add(current);
224
- recursionStack.add(current);
225
- const dependencies = dependencyGraph.get(current) || /* @__PURE__ */ new Set();
226
- for (const dep of dependencies) {
227
- dfs(dep, [...path, current]);
228
- }
229
- recursionStack.delete(current);
230
- }
231
- dfs(file, []);
232
- return [...new Set(circular)];
233
- }
234
281
  var import_core5, import_path2, import_fs;
235
282
  var init_python_context = __esm({
236
283
  "src/analyzers/python-context.ts"() {
@@ -238,16 +285,14 @@ var init_python_context = __esm({
238
285
  import_core5 = require("@aiready/core");
239
286
  import_path2 = require("path");
240
287
  import_fs = __toESM(require("fs"));
288
+ init_dependency_graph_utils();
241
289
  }
242
290
  });
243
291
 
244
292
  // src/cli.ts
245
293
  var import_commander = require("commander");
246
294
 
247
- // src/index.ts
248
- var import_core10 = require("@aiready/core");
249
-
250
- // src/provider.ts
295
+ // src/cli-action.ts
251
296
  var import_core8 = require("@aiready/core");
252
297
 
253
298
  // src/analyzer.ts
@@ -259,6 +304,21 @@ var import_core2 = require("@aiready/core");
259
304
  // src/ast-utils.ts
260
305
  var import_core = require("@aiready/core");
261
306
 
307
+ // src/utils/string-utils.ts
308
+ function singularize(word) {
309
+ const irregulars = {
310
+ people: "person",
311
+ children: "child",
312
+ men: "man",
313
+ women: "woman"
314
+ };
315
+ if (irregulars[word]) return irregulars[word];
316
+ if (word.endsWith("ies")) return word.slice(0, -3) + "y";
317
+ if (word.endsWith("ses")) return word.slice(0, -2);
318
+ if (word.endsWith("s") && word.length > 3) return word.slice(0, -1);
319
+ return word;
320
+ }
321
+
262
322
  // src/semantic-analysis.ts
263
323
  function buildCoUsageMatrix(graph) {
264
324
  const coUsageMatrix = /* @__PURE__ */ new Map();
@@ -461,19 +521,6 @@ function inferDomain(name, filePath, domainOptions, fileImports) {
461
521
  }
462
522
  return "unknown";
463
523
  }
464
- function singularize(word) {
465
- const irregulars = {
466
- people: "person",
467
- children: "child",
468
- men: "man",
469
- women: "woman"
470
- };
471
- if (irregulars[word]) return irregulars[word];
472
- if (word.endsWith("ies")) return word.slice(0, -3) + "y";
473
- if (word.endsWith("ses")) return word.slice(0, -2);
474
- if (word.endsWith("s") && word.length > 3) return word.slice(0, -1);
475
- return word;
476
- }
477
524
 
478
525
  // src/ast-utils.ts
479
526
  function extractExportsWithAST(content, filePath, domainOptions, fileImports) {
@@ -589,167 +636,113 @@ function calculatePathEntropy(files) {
589
636
  const maxEntropy = Math.log2(counts.length);
590
637
  return maxEntropy > 0 ? entropy / maxEntropy : 0;
591
638
  }
592
- function calculateDirectoryDistance(files) {
593
- if (!files || files.length <= 1) return 0;
594
- const pathSegments = (p) => p.split("/").filter(Boolean);
595
- const commonAncestorDepth = (a, b) => {
596
- const minLen = Math.min(a.length, b.length);
597
- let i = 0;
598
- while (i < minLen && a[i] === b[i]) i++;
599
- return i;
600
- };
601
- let totalNormalized = 0;
602
- let comparisons = 0;
603
- for (let i = 0; i < files.length; i++) {
604
- for (let j = i + 1; j < files.length; j++) {
605
- const segA = pathSegments(files[i]);
606
- const segB = pathSegments(files[j]);
607
- const shared = commonAncestorDepth(segA, segB);
608
- const maxDepth = Math.max(segA.length, segB.length);
609
- totalNormalized += 1 - (maxDepth > 0 ? shared / maxDepth : 0);
610
- comparisons++;
611
- }
612
- }
613
- return comparisons > 0 ? totalNormalized / comparisons : 0;
614
- }
615
639
 
616
- // src/summary.ts
640
+ // src/issue-analyzer.ts
617
641
  var import_core3 = require("@aiready/core");
618
- function generateSummary(results, options) {
619
- const config = options ? Object.fromEntries(
620
- Object.entries(options).filter(
621
- ([key]) => !import_core3.GLOBAL_SCAN_OPTIONS.includes(key) || key === "rootDir"
622
- )
623
- ) : void 0;
624
- if (results.length === 0) {
625
- return {
626
- totalFiles: 0,
627
- totalTokens: 0,
628
- avgContextBudget: 0,
629
- maxContextBudget: 0,
630
- avgImportDepth: 0,
631
- maxImportDepth: 0,
632
- deepFiles: [],
633
- avgFragmentation: 0,
634
- fragmentedModules: [],
635
- avgCohesion: 0,
636
- lowCohesionFiles: [],
637
- criticalIssues: 0,
638
- majorIssues: 0,
639
- minorIssues: 0,
640
- totalPotentialSavings: 0,
641
- topExpensiveFiles: [],
642
- config
643
- };
642
+ function analyzeIssues(params) {
643
+ const {
644
+ file,
645
+ importDepth,
646
+ contextBudget,
647
+ cohesionScore,
648
+ fragmentationScore,
649
+ maxDepth,
650
+ maxContextBudget,
651
+ minCohesion,
652
+ maxFragmentation,
653
+ circularDeps
654
+ } = params;
655
+ const issues = [];
656
+ const recommendations = [];
657
+ let severity = import_core3.Severity.Info;
658
+ let potentialSavings = 0;
659
+ if (circularDeps.length > 0) {
660
+ severity = import_core3.Severity.Critical;
661
+ issues.push(`Part of ${circularDeps.length} circular dependency chain(s)`);
662
+ recommendations.push(
663
+ "Break circular dependencies by extracting interfaces or using dependency injection"
664
+ );
665
+ potentialSavings += contextBudget * 0.2;
644
666
  }
645
- const totalFiles = results.length;
646
- const totalTokens = results.reduce((sum, r) => sum + r.tokenCost, 0);
647
- const totalContextBudget = results.reduce(
648
- (sum, r) => sum + r.contextBudget,
649
- 0
650
- );
651
- const avgContextBudget = totalContextBudget / totalFiles;
652
- const maxContextBudget = Math.max(...results.map((r) => r.contextBudget));
653
- const avgImportDepth = results.reduce((sum, r) => sum + r.importDepth, 0) / totalFiles;
654
- const maxImportDepth = Math.max(...results.map((r) => r.importDepth));
655
- const deepFiles = results.filter((r) => r.importDepth >= 5).map((r) => ({ file: r.file, depth: r.importDepth })).sort((a, b) => b.depth - a.depth).slice(0, 10);
656
- const avgFragmentation = results.reduce((sum, r) => sum + r.fragmentationScore, 0) / totalFiles;
657
- const moduleMap = /* @__PURE__ */ new Map();
658
- for (const result of results) {
659
- for (const domain of result.domains) {
660
- if (!moduleMap.has(domain)) moduleMap.set(domain, []);
661
- moduleMap.get(domain).push(result);
662
- }
667
+ if (importDepth > maxDepth * 1.5) {
668
+ severity = import_core3.Severity.Critical;
669
+ issues.push(`Import depth ${importDepth} exceeds limit by 50%`);
670
+ recommendations.push("Flatten dependency tree or use facade pattern");
671
+ potentialSavings += contextBudget * 0.3;
672
+ } else if (importDepth > maxDepth) {
673
+ if (severity !== import_core3.Severity.Critical) severity = import_core3.Severity.Major;
674
+ issues.push(
675
+ `Import depth ${importDepth} exceeds recommended maximum ${maxDepth}`
676
+ );
677
+ recommendations.push("Consider reducing dependency depth");
678
+ potentialSavings += contextBudget * 0.15;
663
679
  }
664
- const fragmentedModules = [];
665
- for (const [domain, files] of moduleMap.entries()) {
666
- let jaccard2 = function(a, b) {
667
- const s1 = new Set(a || []);
668
- const s2 = new Set(b || []);
669
- if (s1.size === 0 && s2.size === 0) return 0;
670
- const inter = new Set([...s1].filter((x) => s2.has(x)));
671
- const uni = /* @__PURE__ */ new Set([...s1, ...s2]);
672
- return uni.size === 0 ? 0 : inter.size / uni.size;
673
- };
674
- var jaccard = jaccard2;
675
- if (files.length < 2) continue;
676
- const fragmentationScore = files.reduce((sum, f) => sum + f.fragmentationScore, 0) / files.length;
677
- if (fragmentationScore < 0.3) continue;
678
- const totalTokens2 = files.reduce((sum, f) => sum + f.tokenCost, 0);
679
- const avgCohesion2 = files.reduce((sum, f) => sum + f.cohesionScore, 0) / files.length;
680
- const targetFiles = Math.max(1, Math.ceil(files.length / 3));
681
- const filePaths = files.map((f) => f.file);
682
- const pathEntropy = calculatePathEntropy(filePaths);
683
- const directoryDistance = calculateDirectoryDistance(filePaths);
684
- let importSimTotal = 0;
685
- let importPairs = 0;
686
- for (let i = 0; i < files.length; i++) {
687
- for (let j = i + 1; j < files.length; j++) {
688
- importSimTotal += jaccard2(
689
- files[i].dependencyList || [],
690
- files[j].dependencyList || []
691
- );
692
- importPairs++;
693
- }
694
- }
695
- const importCohesion = importPairs > 0 ? importSimTotal / importPairs : 0;
696
- fragmentedModules.push({
697
- domain,
698
- files: files.map((f) => f.file),
699
- totalTokens: totalTokens2,
700
- fragmentationScore,
701
- avgCohesion: avgCohesion2,
702
- importCohesion,
703
- pathEntropy,
704
- directoryDistance,
705
- suggestedStructure: {
706
- targetFiles,
707
- consolidationPlan: [
708
- `Consolidate ${files.length} files across ${new Set(files.map((f) => f.file.split("/").slice(0, -1).join("/"))).size} directories`,
709
- `Target ~${targetFiles} core modules to reduce context switching`
710
- ]
711
- }
712
- });
680
+ if (contextBudget > maxContextBudget * 1.5) {
681
+ severity = import_core3.Severity.Critical;
682
+ issues.push(
683
+ `Context budget ${contextBudget.toLocaleString()} tokens is 50% over limit`
684
+ );
685
+ recommendations.push(
686
+ "Split into smaller modules or reduce dependency tree"
687
+ );
688
+ potentialSavings += contextBudget * 0.4;
689
+ } else if (contextBudget > maxContextBudget) {
690
+ if (severity !== import_core3.Severity.Critical) severity = import_core3.Severity.Major;
691
+ issues.push(
692
+ `Context budget ${contextBudget.toLocaleString()} exceeds ${maxContextBudget.toLocaleString()}`
693
+ );
694
+ recommendations.push("Reduce file size or dependencies");
695
+ potentialSavings += contextBudget * 0.2;
696
+ }
697
+ if (cohesionScore < minCohesion * 0.5) {
698
+ if (severity !== import_core3.Severity.Critical) severity = import_core3.Severity.Major;
699
+ issues.push(
700
+ `Very low cohesion (${(cohesionScore * 100).toFixed(0)}%) - mixed concerns`
701
+ );
702
+ recommendations.push(
703
+ "Split file by domain - separate unrelated functionality"
704
+ );
705
+ potentialSavings += contextBudget * 0.25;
706
+ } else if (cohesionScore < minCohesion) {
707
+ if (severity === import_core3.Severity.Info) severity = import_core3.Severity.Minor;
708
+ issues.push(`Low cohesion (${(cohesionScore * 100).toFixed(0)}%)`);
709
+ recommendations.push("Consider grouping related exports together");
710
+ potentialSavings += contextBudget * 0.1;
711
+ }
712
+ if (fragmentationScore > maxFragmentation) {
713
+ if (severity === import_core3.Severity.Info || severity === import_core3.Severity.Minor)
714
+ severity = import_core3.Severity.Minor;
715
+ issues.push(
716
+ `High fragmentation (${(fragmentationScore * 100).toFixed(0)}%) - scattered implementation`
717
+ );
718
+ recommendations.push("Consolidate with related files in same domain");
719
+ potentialSavings += contextBudget * 0.3;
720
+ }
721
+ if (issues.length === 0) {
722
+ issues.push("No significant issues detected");
723
+ recommendations.push("File is well-structured for AI context usage");
724
+ }
725
+ if (isBuildArtifact(file)) {
726
+ issues.push("Detected build artifact (bundled/output file)");
727
+ recommendations.push("Exclude build outputs from analysis");
728
+ severity = import_core3.Severity.Info;
729
+ potentialSavings = 0;
713
730
  }
714
- const avgCohesion = results.reduce((sum, r) => sum + r.cohesionScore, 0) / totalFiles;
715
- const lowCohesionFiles = results.filter((r) => r.cohesionScore < 0.4).map((r) => ({ file: r.file, score: r.cohesionScore })).sort((a, b) => a.score - b.score).slice(0, 10);
716
- const criticalIssues = results.filter(
717
- (r) => r.severity === "critical"
718
- ).length;
719
- const majorIssues = results.filter((r) => r.severity === "major").length;
720
- const minorIssues = results.filter((r) => r.severity === "minor").length;
721
- const totalPotentialSavings = results.reduce(
722
- (sum, r) => sum + r.potentialSavings,
723
- 0
724
- );
725
- const topExpensiveFiles = results.sort((a, b) => b.contextBudget - a.contextBudget).slice(0, 10).map((r) => ({
726
- file: r.file,
727
- contextBudget: r.contextBudget,
728
- severity: r.severity
729
- }));
730
731
  return {
731
- totalFiles,
732
- totalTokens,
733
- avgContextBudget,
734
- maxContextBudget,
735
- avgImportDepth,
736
- maxImportDepth,
737
- deepFiles,
738
- avgFragmentation,
739
- fragmentedModules,
740
- avgCohesion,
741
- lowCohesionFiles,
742
- criticalIssues,
743
- majorIssues,
744
- minorIssues,
745
- totalPotentialSavings,
746
- topExpensiveFiles,
747
- config
732
+ severity,
733
+ issues,
734
+ recommendations,
735
+ potentialSavings: Math.floor(potentialSavings)
748
736
  };
749
737
  }
750
-
751
- // src/graph-builder.ts
738
+ function isBuildArtifact(filePath) {
739
+ const lower = filePath.toLowerCase();
740
+ return lower.includes("/node_modules/") || lower.includes("/dist/") || lower.includes("/build/") || lower.includes("/out/") || lower.includes("/.next/");
741
+ }
742
+
743
+ // src/graph-builder.ts
752
744
  var import_core4 = require("@aiready/core");
745
+ init_dependency_graph_utils();
753
746
  var import_path = require("path");
754
747
  function resolveImport(source, importingFile, allFiles) {
755
748
  if (!source.startsWith(".") && !source.startsWith("/")) {
@@ -797,25 +790,12 @@ function extractDomainKeywordsFromPaths(files) {
797
790
  for (const segment of segments) {
798
791
  const normalized = segment.toLowerCase();
799
792
  if (normalized && !skipFolders.has(normalized) && !normalized.includes(".")) {
800
- folderNames.add(singularize2(normalized));
793
+ folderNames.add(singularize(normalized));
801
794
  }
802
795
  }
803
796
  }
804
797
  return Array.from(folderNames);
805
798
  }
806
- function singularize2(word) {
807
- const irregulars = {
808
- people: "person",
809
- children: "child",
810
- men: "man",
811
- women: "woman"
812
- };
813
- if (irregulars[word]) return irregulars[word];
814
- if (word.endsWith("ies")) return word.slice(0, -3) + "y";
815
- if (word.endsWith("ses")) return word.slice(0, -2);
816
- if (word.endsWith("s") && word.length > 3) return word.slice(0, -1);
817
- return word;
818
- }
819
799
  function buildDependencyGraph(files, options) {
820
800
  const nodes = /* @__PURE__ */ new Map();
821
801
  const edges = /* @__PURE__ */ new Map();
@@ -866,31 +846,10 @@ function buildDependencyGraph(files, options) {
866
846
  return graph;
867
847
  }
868
848
  function calculateImportDepth(file, graph, visited = /* @__PURE__ */ new Set(), depth = 0) {
869
- if (visited.has(file)) return depth;
870
- const dependencies = graph.edges.get(file);
871
- if (!dependencies || dependencies.size === 0) return depth;
872
- visited.add(file);
873
- let maxDepth = depth;
874
- for (const dep of dependencies) {
875
- maxDepth = Math.max(
876
- maxDepth,
877
- calculateImportDepth(dep, graph, visited, depth + 1)
878
- );
879
- }
880
- visited.delete(file);
881
- return maxDepth;
849
+ return calculateImportDepthFromEdges(file, graph.edges, visited, depth);
882
850
  }
883
851
  function getTransitiveDependencies(file, graph, visited = /* @__PURE__ */ new Set()) {
884
- if (visited.has(file)) return [];
885
- visited.add(file);
886
- const dependencies = graph.edges.get(file);
887
- if (!dependencies || dependencies.size === 0) return [];
888
- const allDeps = [];
889
- for (const dep of dependencies) {
890
- allDeps.push(dep);
891
- allDeps.push(...getTransitiveDependencies(dep, graph, visited));
892
- }
893
- return [...new Set(allDeps)];
852
+ return getTransitiveDependenciesFromEdges(file, graph.edges, visited);
894
853
  }
895
854
  function calculateContextBudget(file, graph) {
896
855
  const node = graph.nodes.get(file);
@@ -906,35 +865,79 @@ function calculateContextBudget(file, graph) {
906
865
  return totalTokens;
907
866
  }
908
867
  function detectCircularDependencies(graph) {
909
- const cycles = [];
910
- const visited = /* @__PURE__ */ new Set();
911
- const recursionStack = /* @__PURE__ */ new Set();
912
- function dfs(file, path) {
913
- if (recursionStack.has(file)) {
914
- const cycleStart = path.indexOf(file);
915
- if (cycleStart !== -1) {
916
- cycles.push([...path.slice(cycleStart), file]);
917
- }
918
- return;
868
+ return detectGraphCycles(graph.edges);
869
+ }
870
+
871
+ // src/cluster-detector.ts
872
+ function detectModuleClusters(graph, options) {
873
+ const domainMap = /* @__PURE__ */ new Map();
874
+ for (const [file, node] of graph.nodes.entries()) {
875
+ const primaryDomain = node.exports[0]?.inferredDomain || "unknown";
876
+ if (!domainMap.has(primaryDomain)) {
877
+ domainMap.set(primaryDomain, []);
919
878
  }
920
- if (visited.has(file)) return;
921
- visited.add(file);
922
- recursionStack.add(file);
923
- path.push(file);
924
- const dependencies = graph.edges.get(file);
925
- if (dependencies) {
926
- for (const dep of dependencies) {
927
- dfs(dep, [...path]);
879
+ domainMap.get(primaryDomain).push(file);
880
+ }
881
+ const clusters = [];
882
+ for (const [domain, files] of domainMap.entries()) {
883
+ if (files.length < 2 || domain === "unknown") continue;
884
+ const totalTokens = files.reduce((sum, file) => {
885
+ const node = graph.nodes.get(file);
886
+ return sum + (node?.tokenCost || 0);
887
+ }, 0);
888
+ let sharedImportRatio = 0;
889
+ if (files.length >= 2) {
890
+ const allImportSets = files.map(
891
+ (f) => new Set(graph.nodes.get(f)?.imports || [])
892
+ );
893
+ let intersection = new Set(allImportSets[0]);
894
+ const union = new Set(allImportSets[0]);
895
+ for (let i = 1; i < allImportSets.length; i++) {
896
+ const nextSet = allImportSets[i];
897
+ intersection = new Set([...intersection].filter((x) => nextSet.has(x)));
898
+ for (const x of nextSet) union.add(x);
928
899
  }
900
+ sharedImportRatio = union.size > 0 ? intersection.size / union.size : 0;
929
901
  }
930
- recursionStack.delete(file);
902
+ const fragmentation = calculateFragmentation(files, domain, {
903
+ ...options,
904
+ sharedImportRatio
905
+ });
906
+ let totalCohesion = 0;
907
+ files.forEach((f) => {
908
+ const node = graph.nodes.get(f);
909
+ if (node) totalCohesion += calculateEnhancedCohesion(node.exports);
910
+ });
911
+ const avgCohesion = totalCohesion / files.length;
912
+ clusters.push({
913
+ domain,
914
+ files,
915
+ totalTokens,
916
+ fragmentationScore: fragmentation,
917
+ avgCohesion,
918
+ suggestedStructure: generateSuggestedStructure(
919
+ files,
920
+ totalTokens,
921
+ fragmentation
922
+ )
923
+ });
931
924
  }
932
- for (const file of graph.nodes.keys()) {
933
- if (!visited.has(file)) {
934
- dfs(file, []);
935
- }
925
+ return clusters;
926
+ }
927
+ function generateSuggestedStructure(files, tokens, fragmentation) {
928
+ const targetFiles = Math.max(1, Math.ceil(tokens / 1e4));
929
+ const plan = [];
930
+ if (fragmentation > 0.5) {
931
+ plan.push(
932
+ `Consolidate ${files.length} files scattered across multiple directories into ${targetFiles} core module(s)`
933
+ );
936
934
  }
937
- return cycles;
935
+ if (tokens > 2e4) {
936
+ plan.push(
937
+ `Domain logic is very large (${Math.round(tokens / 1e3)}k tokens). Ensure clear sub-domain boundaries.`
938
+ );
939
+ }
940
+ return { targetFiles, consolidationPlan: plan };
938
941
  }
939
942
 
940
943
  // src/classifier.ts
@@ -1249,78 +1252,6 @@ function adjustFragmentationForClassification(baseFragmentation, classification)
1249
1252
  }
1250
1253
  }
1251
1254
 
1252
- // src/cluster-detector.ts
1253
- function detectModuleClusters(graph, options) {
1254
- const domainMap = /* @__PURE__ */ new Map();
1255
- for (const [file, node] of graph.nodes.entries()) {
1256
- const primaryDomain = node.exports[0]?.inferredDomain || "unknown";
1257
- if (!domainMap.has(primaryDomain)) {
1258
- domainMap.set(primaryDomain, []);
1259
- }
1260
- domainMap.get(primaryDomain).push(file);
1261
- }
1262
- const clusters = [];
1263
- for (const [domain, files] of domainMap.entries()) {
1264
- if (files.length < 2 || domain === "unknown") continue;
1265
- const totalTokens = files.reduce((sum, file) => {
1266
- const node = graph.nodes.get(file);
1267
- return sum + (node?.tokenCost || 0);
1268
- }, 0);
1269
- let sharedImportRatio = 0;
1270
- if (files.length >= 2) {
1271
- const allImportSets = files.map(
1272
- (f) => new Set(graph.nodes.get(f)?.imports || [])
1273
- );
1274
- let intersection = new Set(allImportSets[0]);
1275
- let union = new Set(allImportSets[0]);
1276
- for (let i = 1; i < allImportSets.length; i++) {
1277
- const nextSet = allImportSets[i];
1278
- intersection = new Set([...intersection].filter((x) => nextSet.has(x)));
1279
- for (const x of nextSet) union.add(x);
1280
- }
1281
- sharedImportRatio = union.size > 0 ? intersection.size / union.size : 0;
1282
- }
1283
- const fragmentation = calculateFragmentation(files, domain, {
1284
- ...options,
1285
- sharedImportRatio
1286
- });
1287
- let totalCohesion = 0;
1288
- files.forEach((f) => {
1289
- const node = graph.nodes.get(f);
1290
- if (node) totalCohesion += calculateEnhancedCohesion(node.exports);
1291
- });
1292
- const avgCohesion = totalCohesion / files.length;
1293
- clusters.push({
1294
- domain,
1295
- files,
1296
- totalTokens,
1297
- fragmentationScore: fragmentation,
1298
- avgCohesion,
1299
- suggestedStructure: generateSuggestedStructure(
1300
- files,
1301
- totalTokens,
1302
- fragmentation
1303
- )
1304
- });
1305
- }
1306
- return clusters;
1307
- }
1308
- function generateSuggestedStructure(files, tokens, fragmentation) {
1309
- const targetFiles = Math.max(1, Math.ceil(tokens / 1e4));
1310
- const plan = [];
1311
- if (fragmentation > 0.5) {
1312
- plan.push(
1313
- `Consolidate ${files.length} files scattered across multiple directories into ${targetFiles} core module(s)`
1314
- );
1315
- }
1316
- if (tokens > 2e4) {
1317
- plan.push(
1318
- `Domain logic is very large (${Math.round(tokens / 1e3)}k tokens). Ensure clear sub-domain boundaries.`
1319
- );
1320
- }
1321
- return { targetFiles, consolidationPlan: plan };
1322
- }
1323
-
1324
1255
  // src/remediation.ts
1325
1256
  function getClassificationRecommendations(classification, file, issues) {
1326
1257
  switch (classification) {
@@ -1381,150 +1312,37 @@ function getClassificationRecommendations(classification, file, issues) {
1381
1312
  }
1382
1313
 
1383
1314
  // src/analyzer.ts
1384
- function calculateCohesion(exports2, filePath, options) {
1385
- if (exports2.length <= 1) return 1;
1386
- if (filePath && isTestFile(filePath)) return 1;
1387
- const domains = exports2.map((e) => e.inferredDomain || "unknown");
1388
- const uniqueDomains = new Set(domains.filter((d) => d !== "unknown"));
1389
- const hasImports = exports2.some((e) => !!e.imports);
1390
- if (!hasImports && !options?.weights) {
1391
- if (uniqueDomains.size <= 1) return 1;
1392
- return 0.4;
1393
- }
1394
- return calculateEnhancedCohesion(exports2, filePath, options);
1395
- }
1396
- function analyzeIssues(params) {
1315
+ async function analyzeContext(options) {
1397
1316
  const {
1398
- file,
1399
- importDepth,
1400
- contextBudget,
1401
- cohesionScore,
1402
- fragmentationScore,
1403
- maxDepth,
1404
- maxContextBudget,
1405
- minCohesion,
1406
- maxFragmentation,
1407
- circularDeps
1408
- } = params;
1409
- const issues = [];
1410
- const recommendations = [];
1411
- let severity = import_core6.Severity.Info;
1412
- let potentialSavings = 0;
1413
- if (circularDeps.length > 0) {
1414
- severity = import_core6.Severity.Critical;
1415
- issues.push(`Part of ${circularDeps.length} circular dependency chain(s)`);
1416
- recommendations.push(
1417
- "Break circular dependencies by extracting interfaces or using dependency injection"
1418
- );
1419
- potentialSavings += contextBudget * 0.2;
1420
- }
1421
- if (importDepth > maxDepth * 1.5) {
1422
- severity = import_core6.Severity.Critical;
1423
- issues.push(`Import depth ${importDepth} exceeds limit by 50%`);
1424
- recommendations.push("Flatten dependency tree or use facade pattern");
1425
- potentialSavings += contextBudget * 0.3;
1426
- } else if (importDepth > maxDepth) {
1427
- if (severity !== import_core6.Severity.Critical) severity = import_core6.Severity.Major;
1428
- issues.push(
1429
- `Import depth ${importDepth} exceeds recommended maximum ${maxDepth}`
1430
- );
1431
- recommendations.push("Consider reducing dependency depth");
1432
- potentialSavings += contextBudget * 0.15;
1433
- }
1434
- if (contextBudget > maxContextBudget * 1.5) {
1435
- severity = import_core6.Severity.Critical;
1436
- issues.push(
1437
- `Context budget ${contextBudget.toLocaleString()} tokens is 50% over limit`
1438
- );
1439
- recommendations.push(
1440
- "Split into smaller modules or reduce dependency tree"
1441
- );
1442
- potentialSavings += contextBudget * 0.4;
1443
- } else if (contextBudget > maxContextBudget) {
1444
- if (severity !== import_core6.Severity.Critical) severity = import_core6.Severity.Major;
1445
- issues.push(
1446
- `Context budget ${contextBudget.toLocaleString()} exceeds ${maxContextBudget.toLocaleString()}`
1447
- );
1448
- recommendations.push("Reduce file size or dependencies");
1449
- potentialSavings += contextBudget * 0.2;
1450
- }
1451
- if (cohesionScore < minCohesion * 0.5) {
1452
- if (severity !== import_core6.Severity.Critical) severity = import_core6.Severity.Major;
1453
- issues.push(
1454
- `Very low cohesion (${(cohesionScore * 100).toFixed(0)}%) - mixed concerns`
1455
- );
1456
- recommendations.push(
1457
- "Split file by domain - separate unrelated functionality"
1458
- );
1459
- potentialSavings += contextBudget * 0.25;
1460
- } else if (cohesionScore < minCohesion) {
1461
- if (severity === import_core6.Severity.Info) severity = import_core6.Severity.Minor;
1462
- issues.push(`Low cohesion (${(cohesionScore * 100).toFixed(0)}%)`);
1463
- recommendations.push("Consider grouping related exports together");
1464
- potentialSavings += contextBudget * 0.1;
1465
- }
1466
- if (fragmentationScore > maxFragmentation) {
1467
- if (severity === import_core6.Severity.Info || severity === import_core6.Severity.Minor)
1468
- severity = import_core6.Severity.Minor;
1469
- issues.push(
1470
- `High fragmentation (${(fragmentationScore * 100).toFixed(0)}%) - scattered implementation`
1471
- );
1472
- recommendations.push("Consolidate with related files in same domain");
1473
- potentialSavings += contextBudget * 0.3;
1474
- }
1475
- if (issues.length === 0) {
1476
- issues.push("No significant issues detected");
1477
- recommendations.push("File is well-structured for AI context usage");
1478
- }
1479
- if (isBuildArtifact(file)) {
1480
- issues.push("Detected build artifact (bundled/output file)");
1481
- recommendations.push("Exclude build outputs from analysis");
1482
- severity = import_core6.Severity.Info;
1483
- potentialSavings = 0;
1484
- }
1485
- return {
1486
- severity,
1487
- issues,
1488
- recommendations,
1489
- potentialSavings: Math.floor(potentialSavings)
1490
- };
1491
- }
1492
- function isBuildArtifact(filePath) {
1493
- const lower = filePath.toLowerCase();
1494
- return lower.includes("/node_modules/") || lower.includes("/dist/") || lower.includes("/build/") || lower.includes("/out/") || lower.includes("/.next/");
1495
- }
1496
- async function analyzeContext(options) {
1497
- const {
1498
- maxDepth = 5,
1499
- maxContextBudget = 1e4,
1500
- minCohesion = 0.6,
1501
- maxFragmentation = 0.5,
1502
- focus = "all",
1503
- includeNodeModules = false,
1504
- ...scanOptions
1505
- } = options;
1506
- const files = await (0, import_core6.scanFiles)({
1507
- ...scanOptions,
1508
- exclude: includeNodeModules && scanOptions.exclude ? scanOptions.exclude.filter(
1509
- (pattern) => pattern !== "**/node_modules/**"
1510
- ) : scanOptions.exclude
1511
- });
1512
- const pythonFiles = files.filter((f) => f.toLowerCase().endsWith(".py"));
1513
- const fileContents = await Promise.all(
1514
- files.map(async (file) => ({
1515
- file,
1516
- content: await (0, import_core6.readFileContent)(file)
1517
- }))
1518
- );
1519
- const graph = buildDependencyGraph(
1520
- fileContents.filter((f) => !f.file.toLowerCase().endsWith(".py"))
1521
- );
1522
- let pythonResults = [];
1523
- if (pythonFiles.length > 0) {
1524
- const { analyzePythonContext: analyzePythonContext2 } = await Promise.resolve().then(() => (init_python_context(), python_context_exports));
1525
- const pythonMetrics = await analyzePythonContext2(
1526
- pythonFiles,
1527
- scanOptions.rootDir || options.rootDir || "."
1317
+ maxDepth = 5,
1318
+ maxContextBudget = 1e4,
1319
+ minCohesion = 0.6,
1320
+ maxFragmentation = 0.5,
1321
+ includeNodeModules = false,
1322
+ ...scanOptions
1323
+ } = options;
1324
+ const files = await (0, import_core6.scanFiles)({
1325
+ ...scanOptions,
1326
+ exclude: includeNodeModules && scanOptions.exclude ? scanOptions.exclude.filter(
1327
+ (pattern) => pattern !== "**/node_modules/**"
1328
+ ) : scanOptions.exclude
1329
+ });
1330
+ const pythonFiles = files.filter((f) => f.toLowerCase().endsWith(".py"));
1331
+ const fileContents = await Promise.all(
1332
+ files.map(async (file) => ({
1333
+ file,
1334
+ content: await (0, import_core6.readFileContent)(file)
1335
+ }))
1336
+ );
1337
+ const graph = buildDependencyGraph(
1338
+ fileContents.filter((f) => !f.file.toLowerCase().endsWith(".py"))
1339
+ );
1340
+ let pythonResults = [];
1341
+ if (pythonFiles.length > 0) {
1342
+ const { analyzePythonContext: analyzePythonContext2 } = await Promise.resolve().then(() => (init_python_context(), python_context_exports));
1343
+ const pythonMetrics = await analyzePythonContext2(
1344
+ pythonFiles,
1345
+ scanOptions.rootDir || options.rootDir || "."
1528
1346
  );
1529
1347
  pythonResults = pythonMetrics.map((metric) => {
1530
1348
  const { severity, issues, recommendations, potentialSavings } = analyzeIssues({
@@ -1537,27 +1355,21 @@ async function analyzeContext(options) {
1537
1355
  maxContextBudget,
1538
1356
  minCohesion,
1539
1357
  maxFragmentation,
1540
- circularDeps: metric.metrics.circularDependencies.map(
1541
- (cycle) => cycle.split(" \u2192 ")
1542
- )
1358
+ circularDeps: []
1543
1359
  });
1544
1360
  return {
1545
1361
  file: metric.file,
1546
- tokenCost: Math.floor(
1547
- metric.contextBudget / (1 + metric.imports.length || 1)
1548
- ),
1549
- linesOfCode: metric.metrics.linesOfCode,
1362
+ tokenCost: 0,
1363
+ linesOfCode: 0,
1364
+ // Not provided by python context yet
1550
1365
  importDepth: metric.importDepth,
1551
- dependencyCount: metric.imports.length,
1552
- dependencyList: metric.imports.map(
1553
- (imp) => imp.resolvedPath || imp.source
1554
- ),
1555
- circularDeps: metric.metrics.circularDependencies.map(
1556
- (cycle) => cycle.split(" \u2192 ")
1557
- ),
1366
+ dependencyCount: 0,
1367
+ // Not provided
1368
+ dependencyList: [],
1369
+ circularDeps: [],
1558
1370
  cohesionScore: metric.cohesion,
1559
- domains: ["python"],
1560
- exportCount: metric.exports.length,
1371
+ domains: [],
1372
+ exportCount: 0,
1561
1373
  contextBudget: metric.contextBudget,
1562
1374
  fragmentationScore: 0,
1563
1375
  relatedFiles: [],
@@ -1569,579 +1381,202 @@ async function analyzeContext(options) {
1569
1381
  };
1570
1382
  });
1571
1383
  }
1572
- const circularDeps = detectCircularDependencies(graph);
1573
- const useLogScale = files.length >= 500;
1574
- const clusters = detectModuleClusters(graph, { useLogScale });
1575
- const fragmentationMap = /* @__PURE__ */ new Map();
1576
- for (const cluster of clusters) {
1577
- for (const file of cluster.files) {
1578
- fragmentationMap.set(file, cluster.fragmentationScore);
1579
- }
1580
- }
1581
- const results = [];
1582
- for (const { file } of fileContents) {
1583
- const node = graph.nodes.get(file);
1584
- if (!node) continue;
1585
- const importDepth = focus === "depth" || focus === "all" ? calculateImportDepth(file, graph) : 0;
1586
- const dependencyList = focus === "depth" || focus === "all" ? getTransitiveDependencies(file, graph) : [];
1587
- const contextBudget = focus === "all" ? calculateContextBudget(file, graph) : node.tokenCost;
1588
- const cohesionScore = focus === "cohesion" || focus === "all" ? calculateCohesion(node.exports, file, {
1589
- coUsageMatrix: graph.coUsageMatrix
1590
- }) : 1;
1591
- const fragmentationScore = fragmentationMap.get(file) || 0;
1592
- const relatedFiles = [];
1593
- for (const cluster of clusters) {
1594
- if (cluster.files.includes(file)) {
1595
- relatedFiles.push(...cluster.files.filter((f) => f !== file));
1596
- break;
1597
- }
1384
+ const clusters = detectModuleClusters(graph);
1385
+ const allCircularDeps = detectCircularDependencies(graph);
1386
+ const results = Array.from(graph.nodes.values()).map(
1387
+ (node) => {
1388
+ const file = node.file;
1389
+ const tokenCost = node.tokenCost;
1390
+ const importDepth = calculateImportDepth(file, graph);
1391
+ const transitiveDeps = getTransitiveDependencies(file, graph);
1392
+ const contextBudget = calculateContextBudget(file, graph);
1393
+ const circularDeps = allCircularDeps.filter(
1394
+ (cycle) => cycle.includes(file)
1395
+ );
1396
+ const cluster = clusters.find((c) => c.files.includes(file));
1397
+ const rawFragmentationScore = cluster ? cluster.fragmentationScore : 0;
1398
+ const rawCohesionScore = calculateEnhancedCohesion(
1399
+ node.exports,
1400
+ file,
1401
+ options
1402
+ );
1403
+ const fileClassification = classifyFile(node, rawCohesionScore);
1404
+ const cohesionScore = adjustCohesionForClassification(
1405
+ rawCohesionScore,
1406
+ fileClassification
1407
+ );
1408
+ const fragmentationScore = adjustFragmentationForClassification(
1409
+ rawFragmentationScore,
1410
+ fileClassification
1411
+ );
1412
+ const { severity, issues, recommendations, potentialSavings } = analyzeIssues({
1413
+ file,
1414
+ importDepth,
1415
+ contextBudget,
1416
+ cohesionScore,
1417
+ fragmentationScore,
1418
+ maxDepth,
1419
+ maxContextBudget,
1420
+ minCohesion,
1421
+ maxFragmentation,
1422
+ circularDeps
1423
+ });
1424
+ const classRecs = getClassificationRecommendations(
1425
+ fileClassification,
1426
+ file,
1427
+ issues
1428
+ );
1429
+ const allRecommendations = Array.from(
1430
+ /* @__PURE__ */ new Set([...recommendations, ...classRecs])
1431
+ );
1432
+ return {
1433
+ file,
1434
+ tokenCost,
1435
+ linesOfCode: node.linesOfCode,
1436
+ importDepth,
1437
+ dependencyCount: transitiveDeps.length,
1438
+ dependencyList: transitiveDeps,
1439
+ circularDeps,
1440
+ cohesionScore,
1441
+ domains: Array.from(
1442
+ new Set(
1443
+ node.exports.flatMap((e) => e.domains?.map((d) => d.domain) || [])
1444
+ )
1445
+ ),
1446
+ exportCount: node.exports.length,
1447
+ contextBudget,
1448
+ fragmentationScore,
1449
+ relatedFiles: cluster ? cluster.files : [],
1450
+ fileClassification,
1451
+ severity,
1452
+ issues,
1453
+ recommendations: allRecommendations,
1454
+ potentialSavings
1455
+ };
1598
1456
  }
1599
- const { issues } = analyzeIssues({
1600
- file,
1601
- importDepth,
1602
- contextBudget,
1603
- cohesionScore,
1604
- fragmentationScore,
1605
- maxDepth,
1606
- maxContextBudget,
1607
- minCohesion,
1608
- maxFragmentation,
1609
- circularDeps
1610
- });
1611
- const domains = [
1612
- ...new Set(node.exports.map((e) => e.inferredDomain || "unknown"))
1613
- ];
1614
- const fileClassification = classifyFile(node);
1615
- const adjustedCohesionScore = adjustCohesionForClassification(
1616
- cohesionScore,
1617
- fileClassification,
1618
- node
1619
- );
1620
- const adjustedFragmentationScore = adjustFragmentationForClassification(
1621
- fragmentationScore,
1622
- fileClassification
1623
- );
1624
- const classificationRecommendations = getClassificationRecommendations(
1625
- fileClassification,
1626
- file,
1627
- issues
1628
- );
1629
- const {
1630
- severity: adjustedSeverity,
1631
- issues: adjustedIssues,
1632
- recommendations: finalRecommendations,
1633
- potentialSavings: adjustedSavings
1634
- } = analyzeIssues({
1635
- file,
1636
- importDepth,
1637
- contextBudget,
1638
- cohesionScore: adjustedCohesionScore,
1639
- fragmentationScore: adjustedFragmentationScore,
1640
- maxDepth,
1641
- maxContextBudget,
1642
- minCohesion,
1643
- maxFragmentation,
1644
- circularDeps
1645
- });
1646
- results.push({
1647
- file,
1648
- tokenCost: node.tokenCost,
1649
- linesOfCode: node.linesOfCode,
1650
- importDepth,
1651
- dependencyCount: dependencyList.length,
1652
- dependencyList,
1653
- circularDeps: circularDeps.filter((cycle) => cycle.includes(file)),
1654
- cohesionScore: adjustedCohesionScore,
1655
- domains,
1656
- exportCount: node.exports.length,
1657
- contextBudget,
1658
- fragmentationScore: adjustedFragmentationScore,
1659
- relatedFiles,
1660
- fileClassification,
1661
- severity: adjustedSeverity,
1662
- issues: adjustedIssues,
1663
- recommendations: [
1664
- ...finalRecommendations,
1665
- ...classificationRecommendations.slice(0, 1)
1666
- ],
1667
- potentialSavings: adjustedSavings
1668
- });
1669
- }
1670
- const allResults = [...results, ...pythonResults];
1671
- const finalSummary = generateSummary(allResults, options);
1672
- return allResults.sort((a, b) => {
1673
- const severityOrder = { critical: 0, major: 1, minor: 2, info: 3 };
1674
- const severityDiff = severityOrder[a.severity] - severityOrder[b.severity];
1675
- if (severityDiff !== 0) return severityDiff;
1676
- return b.contextBudget - a.contextBudget;
1677
- });
1457
+ );
1458
+ return [...results, ...pythonResults];
1678
1459
  }
1679
1460
 
1680
- // src/scoring.ts
1461
+ // src/summary.ts
1681
1462
  var import_core7 = require("@aiready/core");
1682
- function calculateContextScore(summary, costConfig) {
1683
- const {
1463
+ function generateSummary(results, options = {}) {
1464
+ const config = options ? Object.fromEntries(
1465
+ Object.entries(options).filter(
1466
+ ([key]) => !import_core7.GLOBAL_SCAN_OPTIONS.includes(key) || key === "rootDir"
1467
+ )
1468
+ ) : {};
1469
+ const totalFiles = results.length;
1470
+ const totalTokens = results.reduce((sum, r) => sum + r.tokenCost, 0);
1471
+ const avgContextBudget = totalFiles > 0 ? results.reduce((sum, r) => sum + r.contextBudget, 0) / totalFiles : 0;
1472
+ const deepFiles = results.filter((r) => r.importDepth > 5).map((r) => ({ file: r.file, depth: r.importDepth }));
1473
+ const maxImportDepth = Math.max(0, ...results.map((r) => r.importDepth));
1474
+ const moduleMap = /* @__PURE__ */ new Map();
1475
+ results.forEach((r) => {
1476
+ const parts = r.file.split("/");
1477
+ let domain = "root";
1478
+ if (parts.length > 2) {
1479
+ domain = parts.slice(0, 2).join("/");
1480
+ }
1481
+ if (!moduleMap.has(domain)) moduleMap.set(domain, []);
1482
+ moduleMap.get(domain).push(r);
1483
+ });
1484
+ const fragmentedModules = [];
1485
+ moduleMap.forEach((files, domain) => {
1486
+ const clusterTokens = files.reduce((sum, f) => sum + f.tokenCost, 0);
1487
+ const filePaths = files.map((f) => f.file);
1488
+ const avgEntropy = calculatePathEntropy(filePaths);
1489
+ const fragmentationScore = Math.min(1, avgEntropy * (files.length / 10));
1490
+ if (fragmentationScore > 0.4) {
1491
+ fragmentedModules.push({
1492
+ domain,
1493
+ files: filePaths,
1494
+ fragmentationScore,
1495
+ totalTokens: clusterTokens,
1496
+ avgCohesion: files.reduce((sum, f) => sum + f.cohesionScore, 0) / files.length,
1497
+ suggestedStructure: {
1498
+ targetFiles: Math.ceil(files.length / 2),
1499
+ consolidationPlan: [
1500
+ `Consolidate ${files.length} files in ${domain} into fewer modules`
1501
+ ]
1502
+ }
1503
+ });
1504
+ }
1505
+ });
1506
+ fragmentedModules.sort((a, b) => b.fragmentationScore - a.fragmentationScore);
1507
+ const avgFragmentation = fragmentedModules.length > 0 ? fragmentedModules.reduce((sum, m) => sum + m.fragmentationScore, 0) / fragmentedModules.length : 0;
1508
+ const avgCohesion = results.reduce((sum, r) => sum + r.cohesionScore, 0) / (totalFiles || 1);
1509
+ const lowCohesionFiles = results.filter((r) => r.cohesionScore < 0.4).map((r) => ({ file: r.file, score: r.cohesionScore }));
1510
+ const criticalIssues = results.filter(
1511
+ (r) => r.severity === "critical"
1512
+ ).length;
1513
+ const majorIssues = results.filter((r) => r.severity === "major").length;
1514
+ const minorIssues = results.filter((r) => r.severity === "minor").length;
1515
+ const totalPotentialSavings = results.reduce(
1516
+ (sum, r) => sum + (r.potentialSavings || 0),
1517
+ 0
1518
+ );
1519
+ const topExpensiveFiles = [...results].sort((a, b) => b.contextBudget - a.contextBudget).slice(0, 10).map((r) => ({
1520
+ file: r.file,
1521
+ contextBudget: r.contextBudget,
1522
+ severity: r.severity
1523
+ }));
1524
+ return {
1525
+ totalFiles,
1526
+ totalTokens,
1684
1527
  avgContextBudget,
1685
- maxContextBudget,
1686
- avgImportDepth,
1528
+ maxContextBudget: Math.max(0, ...results.map((r) => r.contextBudget)),
1529
+ avgImportDepth: results.reduce((sum, r) => sum + r.importDepth, 0) / (totalFiles || 1),
1687
1530
  maxImportDepth,
1531
+ deepFiles,
1688
1532
  avgFragmentation,
1533
+ fragmentedModules,
1534
+ avgCohesion,
1535
+ lowCohesionFiles,
1689
1536
  criticalIssues,
1690
- majorIssues
1691
- } = summary;
1692
- const budgetScore = avgContextBudget < 5e3 ? 100 : Math.max(0, 100 - (avgContextBudget - 5e3) / 150);
1693
- const depthScore = avgImportDepth < 5 ? 100 : Math.max(0, 100 - (avgImportDepth - 5) * 10);
1694
- const fragmentationScore = avgFragmentation < 0.3 ? 100 : Math.max(0, 100 - (avgFragmentation - 0.3) * 200);
1695
- const criticalPenalty = criticalIssues * 10;
1696
- const majorPenalty = majorIssues * 3;
1697
- const maxBudgetPenalty = maxContextBudget > 15e3 ? Math.min(20, (maxContextBudget - 15e3) / 500) : 0;
1698
- const rawScore = budgetScore * 0.4 + depthScore * 0.3 + fragmentationScore * 0.3;
1699
- const finalScore = rawScore - criticalPenalty - majorPenalty - maxBudgetPenalty;
1700
- const score = Math.max(0, Math.min(100, Math.round(finalScore)));
1701
- const factors = [
1702
- {
1703
- name: "Context Budget",
1704
- impact: Math.round(budgetScore * 0.4 - 40),
1705
- description: `Avg ${Math.round(avgContextBudget)} tokens per file ${avgContextBudget < 5e3 ? "(excellent)" : avgContextBudget < 1e4 ? "(acceptable)" : "(high)"}`
1706
- },
1707
- {
1708
- name: "Import Depth",
1709
- impact: Math.round(depthScore * 0.3 - 30),
1710
- description: `Avg ${avgImportDepth.toFixed(1)} levels ${avgImportDepth < 5 ? "(excellent)" : avgImportDepth < 8 ? "(acceptable)" : "(deep)"}`
1711
- },
1712
- {
1713
- name: "Fragmentation",
1714
- impact: Math.round(fragmentationScore * 0.3 - 30),
1715
- description: `${(avgFragmentation * 100).toFixed(0)}% fragmentation ${avgFragmentation < 0.3 ? "(well-organized)" : avgFragmentation < 0.5 ? "(moderate)" : "(high)"}`
1716
- }
1717
- ];
1718
- if (criticalIssues > 0) {
1719
- factors.push({
1720
- name: "Critical Issues",
1721
- impact: -criticalPenalty,
1722
- description: `${criticalIssues} critical context issue${criticalIssues > 1 ? "s" : ""}`
1723
- });
1724
- }
1725
- if (majorIssues > 0) {
1726
- factors.push({
1727
- name: "Major Issues",
1728
- impact: -majorPenalty,
1729
- description: `${majorIssues} major context issue${majorIssues > 1 ? "s" : ""}`
1730
- });
1731
- }
1732
- if (maxBudgetPenalty > 0) {
1733
- factors.push({
1734
- name: "Extreme File Detected",
1735
- impact: -Math.round(maxBudgetPenalty),
1736
- description: `One file requires ${Math.round(maxContextBudget)} tokens (very high)`
1737
- });
1738
- }
1739
- const recommendations = [];
1740
- if (avgContextBudget > 1e4) {
1741
- const estimatedImpact = Math.min(
1742
- 15,
1743
- Math.round((avgContextBudget - 1e4) / 1e3)
1744
- );
1745
- recommendations.push({
1746
- action: "Reduce file dependencies to lower context requirements",
1747
- estimatedImpact,
1748
- priority: "high"
1749
- });
1750
- }
1751
- if (avgImportDepth > 8) {
1752
- const estimatedImpact = Math.min(10, Math.round((avgImportDepth - 8) * 2));
1753
- recommendations.push({
1754
- action: "Flatten import chains to reduce depth",
1755
- estimatedImpact,
1756
- priority: avgImportDepth > 10 ? "high" : "medium"
1757
- });
1758
- }
1759
- if (avgFragmentation > 0.5) {
1760
- const estimatedImpact = Math.min(
1761
- 12,
1762
- Math.round((avgFragmentation - 0.5) * 40)
1763
- );
1764
- recommendations.push({
1765
- action: "Consolidate related code into cohesive modules",
1766
- estimatedImpact,
1767
- priority: "medium"
1768
- });
1769
- }
1770
- if (maxContextBudget > 2e4) {
1771
- recommendations.push({
1772
- action: `Split large file (${Math.round(maxContextBudget)} tokens) into smaller modules`,
1773
- estimatedImpact: 8,
1774
- priority: "high"
1775
- });
1776
- }
1777
- const cfg = { ...import_core7.DEFAULT_COST_CONFIG, ...costConfig };
1778
- const estimatedMonthlyCost = (0, import_core7.calculateMonthlyCost)(
1779
- avgContextBudget * (summary.totalFiles || 1),
1780
- cfg
1781
- );
1782
- const issues = [
1783
- ...Array(criticalIssues).fill({ severity: "critical" }),
1784
- ...Array(majorIssues).fill({ severity: "major" })
1785
- ];
1786
- const productivityImpact = (0, import_core7.calculateProductivityImpact)(issues);
1787
- return {
1788
- toolName: import_core7.ToolName.ContextAnalyzer,
1789
- score,
1790
- rawMetrics: {
1791
- avgContextBudget: Math.round(avgContextBudget),
1792
- maxContextBudget: Math.round(maxContextBudget),
1793
- avgImportDepth: Math.round(avgImportDepth * 10) / 10,
1794
- maxImportDepth,
1795
- avgFragmentation: Math.round(avgFragmentation * 100) / 100,
1796
- criticalIssues,
1797
- majorIssues,
1798
- estimatedMonthlyCost,
1799
- estimatedDeveloperHours: productivityImpact.totalHours
1800
- },
1801
- factors,
1802
- recommendations
1537
+ majorIssues,
1538
+ minorIssues,
1539
+ totalPotentialSavings,
1540
+ topExpensiveFiles,
1541
+ config
1803
1542
  };
1804
1543
  }
1805
1544
 
1806
- // src/provider.ts
1807
- var ContextAnalyzerProvider = {
1808
- id: import_core8.ToolName.ContextAnalyzer,
1809
- alias: ["context", "fragmentation", "budget"],
1810
- async analyze(options) {
1811
- const results = await analyzeContext(options);
1812
- const summary = generateSummary(results, options);
1813
- const normalizedResults = results.map(
1814
- (r) => ({
1815
- fileName: r.file,
1816
- issues: r.issues.map((msg) => ({
1817
- type: import_core8.IssueType.ContextFragmentation,
1818
- severity: r.severity,
1819
- message: msg,
1820
- location: { file: r.file, line: 1 },
1821
- suggestion: r.recommendations[0]
1822
- })),
1823
- metrics: {
1824
- tokenCost: r.tokenCost,
1825
- complexityScore: r.importDepth
1826
- // Map other context-specific metrics if needed
1827
- }
1828
- })
1829
- );
1830
- return import_core8.SpokeOutputSchema.parse({
1831
- results: normalizedResults,
1832
- summary: {
1833
- ...summary
1834
- },
1835
- metadata: {
1836
- toolName: import_core8.ToolName.ContextAnalyzer,
1837
- version: "0.17.5",
1838
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
1839
- }
1840
- });
1841
- },
1842
- score(output, options) {
1843
- const summary = output.summary;
1844
- return calculateContextScore(summary, options.costConfig);
1845
- },
1846
- defaultWeight: 19
1847
- };
1848
-
1849
- // src/defaults.ts
1850
- var import_core9 = require("@aiready/core");
1851
-
1852
- // src/index.ts
1853
- import_core10.ToolRegistry.register(ContextAnalyzerProvider);
1854
-
1855
- // src/cli.ts
1545
+ // src/utils/output-formatter.ts
1856
1546
  var import_chalk = __toESM(require("chalk"));
1857
1547
  var import_fs2 = require("fs");
1858
1548
  var import_path3 = require("path");
1859
- var import_core11 = require("@aiready/core");
1860
1549
  var import_prompts = __toESM(require("prompts"));
1861
- var program = new import_commander.Command();
1862
- program.name("aiready-context").description("Analyze AI context window cost and code structure").version("0.1.0").addHelpText(
1863
- "after",
1864
- "\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"
1865
- ).argument("<directory>", "Directory to analyze").option("--max-depth <number>", "Maximum acceptable import depth").option(
1866
- "--max-context <number>",
1867
- "Maximum acceptable context budget (tokens)"
1868
- ).option("--min-cohesion <number>", "Minimum acceptable cohesion score (0-1)").option(
1869
- "--max-fragmentation <number>",
1870
- "Maximum acceptable fragmentation (0-1)"
1871
- ).option(
1872
- "--focus <type>",
1873
- "Analysis focus: fragmentation, cohesion, depth, all"
1874
- ).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(
1875
- "--max-results <number>",
1876
- "Maximum number of results to show in console output"
1877
- ).option(
1878
- "-o, --output <format>",
1879
- "Output format: console, json, html",
1880
- "console"
1881
- ).option("--output-file <path>", "Output file path (for json/html)").option(
1882
- "--interactive",
1883
- "Run interactive setup to suggest excludes and focus areas"
1884
- ).action(async (directory, options) => {
1885
- console.log(import_chalk.default.blue("\u{1F50D} Analyzing context window costs...\n"));
1886
- const startTime = Date.now();
1887
- try {
1888
- const defaults = {
1889
- maxDepth: 5,
1890
- maxContextBudget: 1e4,
1891
- minCohesion: 0.6,
1892
- maxFragmentation: 0.5,
1893
- focus: "all",
1894
- includeNodeModules: false,
1895
- include: void 0,
1896
- exclude: void 0,
1897
- maxResults: 10
1898
- };
1899
- let finalOptions = await (0, import_core11.loadMergedConfig)(directory, defaults, {
1900
- maxDepth: options.maxDepth ? parseInt(options.maxDepth) : void 0,
1901
- maxContextBudget: options.maxContext ? parseInt(options.maxContext) : void 0,
1902
- minCohesion: options.minCohesion ? parseFloat(options.minCohesion) : void 0,
1903
- maxFragmentation: options.maxFragmentation ? parseFloat(options.maxFragmentation) : void 0,
1904
- focus: options.focus || void 0,
1905
- includeNodeModules: options.includeNodeModules,
1906
- include: options.include?.split(","),
1907
- exclude: options.exclude?.split(","),
1908
- maxResults: options.maxResults ? parseInt(options.maxResults) : void 0
1909
- });
1910
- if (options.interactive) {
1911
- finalOptions = await runInteractiveSetup(directory, finalOptions);
1912
- }
1913
- const results = await analyzeContext(finalOptions);
1914
- const elapsedTime = (0, import_core11.getElapsedTime)(startTime);
1915
- const summary = generateSummary(results);
1916
- if (options.output === "json") {
1917
- const jsonOutput = {
1918
- summary,
1919
- results,
1920
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1921
- analysisTime: elapsedTime
1922
- };
1923
- const outputPath = (0, import_core11.resolveOutputPath)(
1924
- options.outputFile,
1925
- `context-report-${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}.json`,
1926
- directory
1927
- );
1928
- (0, import_core11.handleJSONOutput)(
1929
- jsonOutput,
1930
- outputPath,
1931
- `
1932
- \u2713 JSON report saved to ${outputPath}`
1933
- );
1934
- return;
1935
- }
1936
- if (options.output === "html") {
1937
- const html = generateHTMLReport(summary, results);
1938
- const outputPath = (0, import_core11.resolveOutputPath)(
1939
- options.outputFile,
1940
- `context-report-${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}.html`,
1941
- directory
1942
- );
1943
- const dir = (0, import_path3.dirname)(outputPath);
1944
- if (!(0, import_fs2.existsSync)(dir)) {
1945
- (0, import_fs2.mkdirSync)(dir, { recursive: true });
1946
- }
1947
- (0, import_fs2.writeFileSync)(outputPath, html);
1948
- console.log(import_chalk.default.green(`
1949
- \u2713 HTML report saved to ${outputPath}`));
1950
- return;
1951
- }
1952
- displayConsoleReport(
1953
- summary,
1954
- results,
1955
- elapsedTime,
1956
- finalOptions.maxResults
1957
- );
1958
- displayTuningGuidance(results, finalOptions);
1959
- } catch (error) {
1960
- (0, import_core11.handleCLIError)(error, "Analysis");
1961
- }
1962
- });
1963
- program.parse();
1964
- function displayTuningGuidance(results, options) {
1965
- const issueCount = results.filter((r) => r.severity !== "info").length;
1966
- if (issueCount === 0) {
1967
- console.log(
1968
- import_chalk.default.green(
1969
- "\n\u2728 No optimization opportunities found! Your code is well-structured for AI context usage.\n"
1970
- )
1971
- );
1972
- return;
1973
- }
1974
- console.log(import_chalk.default.cyan("\n\u2501".repeat(60)));
1975
- console.log(import_chalk.default.bold.white(" TUNING GUIDANCE"));
1976
- console.log(import_chalk.default.cyan("\u2501".repeat(60) + "\n"));
1977
- if (issueCount < 5) {
1978
- console.log(
1979
- import_chalk.default.yellow(
1980
- "\u{1F4CA} Showing few optimization opportunities. To find more areas to improve:\n"
1981
- )
1982
- );
1983
- console.log(
1984
- import_chalk.default.dim(
1985
- " \u2022 Lower --max-depth (currently: " + options.maxDepth + ") to catch shallower import chains"
1986
- )
1987
- );
1988
- console.log(
1989
- import_chalk.default.dim(
1990
- " \u2022 Lower --max-context (currently: " + options.maxContextBudget.toLocaleString() + ") to catch smaller files"
1991
- )
1992
- );
1993
- console.log(
1994
- import_chalk.default.dim(
1995
- " \u2022 Raise --min-cohesion (currently: " + (options.minCohesion * 100).toFixed(0) + "%) to be stricter about mixed concerns"
1996
- )
1997
- );
1998
- console.log(
1999
- import_chalk.default.dim(
2000
- " \u2022 Lower --max-fragmentation (currently: " + (options.maxFragmentation * 100).toFixed(0) + "%) to catch scattered code\n"
2001
- )
2002
- );
2003
- } else if (issueCount > 20) {
2004
- console.log(
2005
- import_chalk.default.yellow(
2006
- "\u{1F4CA} Showing many opportunities. To focus on highest-impact areas:\n"
2007
- )
2008
- );
2009
- console.log(
2010
- import_chalk.default.dim(
2011
- " \u2022 Raise --max-depth (currently: " + options.maxDepth + ") to only catch very deep chains"
2012
- )
2013
- );
2014
- console.log(
2015
- import_chalk.default.dim(
2016
- " \u2022 Raise --max-context (currently: " + options.maxContextBudget.toLocaleString() + ") to focus on largest files"
2017
- )
2018
- );
2019
- console.log(
2020
- import_chalk.default.dim(
2021
- " \u2022 Lower --min-cohesion (currently: " + (options.minCohesion * 100).toFixed(0) + "%) to only flag severe mixed concerns"
2022
- )
2023
- );
2024
- console.log(
2025
- import_chalk.default.dim(
2026
- " \u2022 Raise --max-fragmentation (currently: " + (options.maxFragmentation * 100).toFixed(0) + "%) to only flag highly scattered code\n"
2027
- )
2028
- );
2029
- } else {
2030
- console.log(
2031
- import_chalk.default.green(
2032
- "\u{1F4CA} Good balance of optimization opportunities (showing " + issueCount + " areas)\n"
2033
- )
2034
- );
2035
- console.log(import_chalk.default.dim(" \u{1F4A1} Tip: Adjust thresholds if needed:"));
2036
- console.log(
2037
- import_chalk.default.dim(
2038
- " aiready-context . --max-depth N --max-context N --min-cohesion 0.X\n"
2039
- )
2040
- );
2041
- }
2042
- console.log(import_chalk.default.dim(" \u{1F4D6} See README for detailed tuning guide\n"));
2043
- }
2044
- function displayConsoleReport(summary, results, elapsedTime, maxResults = 10) {
2045
- const terminalWidth = process.stdout.columns || 80;
2046
- const dividerWidth = Math.min(60, terminalWidth - 2);
2047
- const divider = "\u2501".repeat(dividerWidth);
2048
- console.log(import_chalk.default.cyan(divider));
2049
- console.log(import_chalk.default.bold.white(" CONTEXT ANALYSIS SUMMARY"));
2050
- console.log(import_chalk.default.cyan(divider) + "\n");
1550
+ function displayConsoleReport(summary, results, maxResults = 10) {
1551
+ const divider = "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500";
1552
+ const totalIssues = summary.criticalIssues + summary.majorIssues + summary.minorIssues;
1553
+ console.log(import_chalk.default.bold("\u{1F4CA} Context Analysis Summary:\n"));
1554
+ console.log(` \u2022 Total Files: ${import_chalk.default.cyan(summary.totalFiles)}`);
2051
1555
  console.log(
2052
- import_chalk.default.white(`\u{1F4C1} Files analyzed: ${import_chalk.default.bold(summary.totalFiles)}`)
1556
+ ` \u2022 Total Tokens: ${import_chalk.default.cyan(summary.totalTokens.toLocaleString())}`
2053
1557
  );
2054
1558
  console.log(
2055
- import_chalk.default.white(
2056
- `\u{1F4CA} Total tokens: ${import_chalk.default.bold(summary.totalTokens.toLocaleString())}`
2057
- )
1559
+ ` \u2022 Avg Budget: ${import_chalk.default.cyan(summary.avgContextBudget.toFixed(0))} tokens`
2058
1560
  );
2059
1561
  console.log(
2060
- import_chalk.default.yellow(
2061
- `\u{1F4B0} Avg context budget: ${import_chalk.default.bold(summary.avgContextBudget.toFixed(0))} tokens/file`
2062
- )
1562
+ ` \u2022 Potential Saving: ${import_chalk.default.green(summary.totalPotentialSavings.toLocaleString())} tokens`
2063
1563
  );
2064
- console.log(
2065
- import_chalk.default.white(`\u23F1 Analysis time: ${import_chalk.default.bold(elapsedTime + "s")}
2066
- `)
2067
- );
2068
- const totalIssues = summary.criticalIssues + summary.majorIssues + summary.minorIssues;
1564
+ console.log();
2069
1565
  if (totalIssues > 0) {
2070
- console.log(import_chalk.default.bold("\u26A0\uFE0F Issues Found:\n"));
2071
- if (summary.criticalIssues > 0) {
2072
- console.log(
2073
- import_chalk.default.red(` \u{1F534} Critical: ${import_chalk.default.bold(summary.criticalIssues)}`)
2074
- );
2075
- }
2076
- if (summary.majorIssues > 0) {
2077
- console.log(
2078
- import_chalk.default.yellow(` \u{1F7E1} Major: ${import_chalk.default.bold(summary.majorIssues)}`)
2079
- );
2080
- }
2081
- if (summary.minorIssues > 0) {
2082
- console.log(
2083
- import_chalk.default.blue(` \u{1F535} Minor: ${import_chalk.default.bold(summary.minorIssues)}`)
2084
- );
2085
- }
2086
- console.log(
2087
- import_chalk.default.green(
2088
- `
2089
- \u{1F4A1} Potential savings: ${import_chalk.default.bold(summary.totalPotentialSavings.toLocaleString())} tokens
2090
- `
2091
- )
2092
- );
2093
- } else {
2094
- console.log(import_chalk.default.green("\u2705 No significant issues found!\n"));
2095
- }
2096
- if (summary.deepFiles.length > 0) {
2097
- console.log(import_chalk.default.bold("\u{1F4CF} Deep Import Chains:\n"));
2098
- console.log(
2099
- import_chalk.default.gray(` Average depth: ${summary.avgImportDepth.toFixed(1)}`)
2100
- );
2101
- console.log(import_chalk.default.gray(` Maximum depth: ${summary.maxImportDepth}
2102
- `));
2103
- summary.deepFiles.slice(0, maxResults).forEach((item) => {
2104
- const fileName = item.file.split("/").slice(-2).join("/");
2105
- console.log(
2106
- ` ${import_chalk.default.cyan("\u2192")} ${import_chalk.default.white(fileName)} ${import_chalk.default.dim(`(depth: ${item.depth})`)}`
2107
- );
2108
- });
1566
+ console.log(import_chalk.default.bold("\u26A0\uFE0F Issues Detected:\n"));
1567
+ console.log(` \u2022 ${import_chalk.default.red("\u{1F534} Critical:")} ${summary.criticalIssues}`);
1568
+ console.log(` \u2022 ${import_chalk.default.yellow("\u{1F7E1} Major:")} ${summary.majorIssues}`);
1569
+ console.log(` \u2022 ${import_chalk.default.blue("\u{1F535} Minor:")} ${summary.minorIssues}`);
2109
1570
  console.log();
1571
+ } else {
1572
+ console.log(import_chalk.default.green("\u2705 No significant context issues detected!\n"));
2110
1573
  }
2111
1574
  if (summary.fragmentedModules.length > 0) {
2112
- console.log(import_chalk.default.bold("\u{1F9E9} Fragmented Modules:\n"));
2113
- console.log(
2114
- import_chalk.default.gray(
2115
- ` Average fragmentation: ${(summary.avgFragmentation * 100).toFixed(0)}%
2116
- `
2117
- )
2118
- );
2119
- summary.fragmentedModules.slice(0, maxResults).forEach((module2) => {
2120
- console.log(
2121
- ` ${import_chalk.default.yellow("\u25CF")} ${import_chalk.default.white(module2.domain)} - ${import_chalk.default.dim(`${module2.files.length} files, ${(module2.fragmentationScore * 100).toFixed(0)}% scattered`)}`
2122
- );
2123
- console.log(
2124
- import_chalk.default.dim(
2125
- ` Token cost: ${module2.totalTokens.toLocaleString()}, Cohesion: ${(module2.avgCohesion * 100).toFixed(0)}%`
2126
- )
2127
- );
2128
- });
2129
- console.log();
2130
- }
2131
- if (summary.lowCohesionFiles.length > 0) {
2132
- console.log(import_chalk.default.bold("\u{1F500} Low Cohesion Files:\n"));
2133
- console.log(
2134
- import_chalk.default.gray(
2135
- ` Average cohesion: ${(summary.avgCohesion * 100).toFixed(0)}%
2136
- `
2137
- )
2138
- );
2139
- summary.lowCohesionFiles.slice(0, maxResults).forEach((item) => {
2140
- const fileName = item.file.split("/").slice(-2).join("/");
2141
- const scorePercent = (item.score * 100).toFixed(0);
2142
- const color = item.score < 0.4 ? import_chalk.default.red : import_chalk.default.yellow;
1575
+ console.log(import_chalk.default.bold("\u{1F9E9} Top Fragmented Modules:\n"));
1576
+ summary.fragmentedModules.slice(0, maxResults).forEach((mod) => {
1577
+ const scoreColor = mod.fragmentationScore > 0.7 ? import_chalk.default.red : mod.fragmentationScore > 0.4 ? import_chalk.default.yellow : import_chalk.default.green;
2143
1578
  console.log(
2144
- ` ${color("\u25CB")} ${import_chalk.default.white(fileName)} ${import_chalk.default.dim(`(${scorePercent}% cohesion)`)}`
1579
+ ` ${scoreColor("\u25A0")} ${import_chalk.default.white(mod.domain)} ${import_chalk.default.dim(`(${mod.files.length} files, ${(mod.fragmentationScore * 100).toFixed(0)}% frag)`)}`
2145
1580
  );
2146
1581
  });
2147
1582
  console.log();
@@ -2427,3 +1862,102 @@ async function runInteractiveSetup(directory, current) {
2427
1862
  console.log(import_chalk.default.green("\u2713 Interactive configuration applied."));
2428
1863
  return nextOptions;
2429
1864
  }
1865
+
1866
+ // src/cli-action.ts
1867
+ var import_chalk2 = __toESM(require("chalk"));
1868
+ var import_fs3 = require("fs");
1869
+ async function contextActionHandler(directory, options) {
1870
+ console.log(import_chalk2.default.blue("\u{1F50D} Analyzing context window costs...\n"));
1871
+ const startTime = Date.now();
1872
+ try {
1873
+ const defaults = {
1874
+ maxDepth: 5,
1875
+ maxContextBudget: 1e4,
1876
+ minCohesion: 0.6,
1877
+ maxFragmentation: 0.5,
1878
+ focus: "all",
1879
+ includeNodeModules: false,
1880
+ include: void 0,
1881
+ exclude: void 0,
1882
+ maxResults: 10
1883
+ };
1884
+ let finalOptions = await (0, import_core8.loadMergedConfig)(directory, defaults, {
1885
+ maxDepth: options.maxDepth ? parseInt(options.maxDepth) : void 0,
1886
+ maxContextBudget: options.maxContext ? parseInt(options.maxContext) : void 0,
1887
+ minCohesion: options.minCohesion ? parseFloat(options.minCohesion) : void 0,
1888
+ maxFragmentation: options.maxFragmentation ? parseFloat(options.maxFragmentation) : void 0,
1889
+ focus: options.focus || void 0,
1890
+ includeNodeModules: options.includeNodeModules,
1891
+ include: options.include?.split(","),
1892
+ exclude: options.exclude?.split(","),
1893
+ maxResults: options.maxResults ? parseInt(options.maxResults) : void 0
1894
+ });
1895
+ if (options.interactive) {
1896
+ finalOptions = await runInteractiveSetup(directory, finalOptions);
1897
+ }
1898
+ const results = await analyzeContext(finalOptions);
1899
+ const summary = generateSummary(results, finalOptions);
1900
+ const duration = (0, import_core8.getElapsedTime)(startTime);
1901
+ if (options.output === "json") {
1902
+ (0, import_core8.handleJSONOutput)(
1903
+ {
1904
+ summary: {
1905
+ ...summary,
1906
+ executionTime: duration,
1907
+ config: {
1908
+ scan: { tools: ["context"] },
1909
+ tools: { context: finalOptions }
1910
+ },
1911
+ toolConfigs: { context: finalOptions }
1912
+ },
1913
+ context: { results }
1914
+ },
1915
+ options.outputFile
1916
+ );
1917
+ } else if (options.output === "html") {
1918
+ const html = generateHTMLReport(summary, results);
1919
+ const outputPath = (0, import_core8.resolveOutputPath)(
1920
+ directory,
1921
+ options.outputFile,
1922
+ "context-report.html"
1923
+ );
1924
+ (0, import_fs3.writeFileSync)(outputPath, html, "utf-8");
1925
+ console.log(import_chalk2.default.green(`
1926
+ \u2705 HTML report saved to: ${outputPath}`));
1927
+ } else {
1928
+ displayConsoleReport(summary, results, finalOptions.maxResults);
1929
+ console.log(import_chalk2.default.dim(`
1930
+ \u2728 Analysis completed in ${duration}ms
1931
+ `));
1932
+ }
1933
+ } catch (error) {
1934
+ (0, import_core8.handleCLIError)(error, "context-analyzer");
1935
+ }
1936
+ }
1937
+
1938
+ // src/cli.ts
1939
+ var program = new import_commander.Command();
1940
+ program.name("aiready-context").description("Analyze AI context window cost and code structure").version("0.1.0").addHelpText(
1941
+ "after",
1942
+ "\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"
1943
+ ).argument("<directory>", "Directory to analyze").option("--max-depth <number>", "Maximum acceptable import depth").option(
1944
+ "--max-context <number>",
1945
+ "Maximum acceptable context budget (tokens)"
1946
+ ).option("--min-cohesion <number>", "Minimum acceptable cohesion score (0-1)").option(
1947
+ "--max-fragmentation <number>",
1948
+ "Maximum acceptable fragmentation (0-1)"
1949
+ ).option(
1950
+ "--focus <type>",
1951
+ "Analysis focus: fragmentation, cohesion, depth, all"
1952
+ ).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(
1953
+ "--max-results <number>",
1954
+ "Maximum number of results to show in console output"
1955
+ ).option(
1956
+ "-o, --output <format>",
1957
+ "Output format: console, json, html",
1958
+ "console"
1959
+ ).option("--output-file <path>", "Output file path (for json/html)").option(
1960
+ "--interactive",
1961
+ "Run interactive setup to suggest excludes and focus areas"
1962
+ ).action(contextActionHandler);
1963
+ program.parse(process.argv);