@aiready/context-analyzer 0.21.5 → 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 (149) 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 +26 -24
  8. package/.turbo/turbo-test.log +41 -119
  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-SFK6XTJE.mjs +2110 -0
  65. package/dist/chunk-U5R2FTCR.mjs +1803 -0
  66. package/dist/chunk-UU4HZ7ZT.mjs +1849 -0
  67. package/dist/chunk-WKOZOHOU.mjs +2060 -0
  68. package/dist/chunk-XIXAWCMS.mjs +1760 -0
  69. package/dist/classifier.d.ts +114 -0
  70. package/dist/classifier.d.ts.map +1 -0
  71. package/dist/classifier.js +439 -0
  72. package/dist/classifier.js.map +1 -0
  73. package/dist/cli.js +590 -1071
  74. package/dist/cli.js.map +1 -1
  75. package/dist/cli.mjs +63 -533
  76. package/dist/cluster-detector.d.ts +8 -0
  77. package/dist/cluster-detector.d.ts.map +1 -0
  78. package/dist/cluster-detector.js +70 -0
  79. package/dist/cluster-detector.js.map +1 -0
  80. package/dist/defaults.d.ts +7 -0
  81. package/dist/defaults.d.ts.map +1 -0
  82. package/dist/defaults.js +54 -0
  83. package/dist/defaults.js.map +1 -0
  84. package/dist/graph-builder.d.ts +33 -0
  85. package/dist/graph-builder.d.ts.map +1 -0
  86. package/dist/graph-builder.js +225 -0
  87. package/dist/graph-builder.js.map +1 -0
  88. package/dist/index.d.mts +24 -31
  89. package/dist/index.d.ts +24 -31
  90. package/dist/index.d.ts.map +1 -1
  91. package/dist/index.js +788 -569
  92. package/dist/index.js.map +1 -1
  93. package/dist/index.mjs +265 -8
  94. package/dist/metrics.d.ts +34 -0
  95. package/dist/metrics.d.ts.map +1 -0
  96. package/dist/metrics.js +170 -0
  97. package/dist/metrics.js.map +1 -0
  98. package/dist/provider.d.ts +6 -0
  99. package/dist/provider.d.ts.map +1 -0
  100. package/dist/provider.js +48 -0
  101. package/dist/provider.js.map +1 -0
  102. package/dist/python-context-3GZKN3LR.mjs +162 -0
  103. package/dist/python-context-O2EN3M6Z.mjs +162 -0
  104. package/dist/remediation.d.ts +25 -0
  105. package/dist/remediation.d.ts.map +1 -0
  106. package/dist/remediation.js +98 -0
  107. package/dist/remediation.js.map +1 -0
  108. package/dist/scoring.d.ts +3 -7
  109. package/dist/scoring.d.ts.map +1 -1
  110. package/dist/scoring.js +57 -48
  111. package/dist/scoring.js.map +1 -1
  112. package/dist/semantic-analysis.d.ts +12 -23
  113. package/dist/semantic-analysis.d.ts.map +1 -1
  114. package/dist/semantic-analysis.js +172 -110
  115. package/dist/semantic-analysis.js.map +1 -1
  116. package/dist/summary.d.ts +6 -0
  117. package/dist/summary.d.ts.map +1 -0
  118. package/dist/summary.js +92 -0
  119. package/dist/summary.js.map +1 -0
  120. package/dist/types.d.ts +9 -2
  121. package/dist/types.d.ts.map +1 -1
  122. package/dist/utils/output-formatter.d.ts +14 -0
  123. package/dist/utils/output-formatter.d.ts.map +1 -0
  124. package/dist/utils/output-formatter.js +338 -0
  125. package/dist/utils/output-formatter.js.map +1 -0
  126. package/package.json +2 -2
  127. package/src/__tests__/analyzer.test.ts +1 -1
  128. package/src/__tests__/auto-detection.test.ts +1 -1
  129. package/src/__tests__/contract.test.ts +1 -1
  130. package/src/__tests__/enhanced-cohesion.test.ts +1 -1
  131. package/src/__tests__/file-classification.test.ts +1 -1
  132. package/src/__tests__/fragmentation-advanced.test.ts +1 -1
  133. package/src/__tests__/fragmentation-coupling.test.ts +1 -1
  134. package/src/__tests__/fragmentation-log.test.ts +1 -1
  135. package/src/__tests__/provider.test.ts +1 -1
  136. package/src/__tests__/structural-cohesion.test.ts +1 -1
  137. package/src/analyzer.ts +96 -309
  138. package/src/analyzers/python-context.ts +7 -76
  139. package/src/cli-action.ts +103 -0
  140. package/src/cli.ts +12 -693
  141. package/src/cluster-detector.ts +1 -1
  142. package/src/graph-builder.ts +9 -85
  143. package/src/index.ts +6 -0
  144. package/src/issue-analyzer.ts +143 -0
  145. package/src/semantic-analysis.ts +1 -14
  146. package/src/summary.ts +62 -106
  147. package/src/utils/dependency-graph-utils.ts +126 -0
  148. package/src/utils/output-formatter.ts +411 -0
  149. package/src/utils/string-utils.ts +17 -0
package/dist/index.js CHANGED
@@ -30,6 +30,99 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
30
30
  ));
31
31
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
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,6 +285,7 @@ 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
 
@@ -249,7 +297,6 @@ __export(index_exports, {
249
297
  adjustCohesionForClassification: () => adjustCohesionForClassification,
250
298
  adjustFragmentationForClassification: () => adjustFragmentationForClassification,
251
299
  analyzeContext: () => analyzeContext,
252
- analyzeIssues: () => analyzeIssues,
253
300
  buildCoUsageMatrix: () => buildCoUsageMatrix,
254
301
  buildDependencyGraph: () => buildDependencyGraph,
255
302
  buildTypeGraph: () => buildTypeGraph,
@@ -266,10 +313,12 @@ __export(index_exports, {
266
313
  classifyFile: () => classifyFile,
267
314
  detectCircularDependencies: () => detectCircularDependencies,
268
315
  detectModuleClusters: () => detectModuleClusters,
316
+ displayConsoleReport: () => displayConsoleReport,
269
317
  extractDomainKeywordsFromPaths: () => extractDomainKeywordsFromPaths,
270
318
  extractExports: () => extractExports,
271
319
  findConsolidationCandidates: () => findConsolidationCandidates,
272
320
  findSemanticClusters: () => findSemanticClusters,
321
+ generateHTMLReport: () => generateHTMLReport,
273
322
  generateSummary: () => generateSummary,
274
323
  getClassificationRecommendations: () => getClassificationRecommendations,
275
324
  getCoUsageData: () => getCoUsageData,
@@ -288,13 +337,14 @@ __export(index_exports, {
288
337
  isSessionFile: () => isSessionFile,
289
338
  isTypeDefinition: () => isTypeDefinition,
290
339
  isUtilityModule: () => isUtilityModule,
291
- mapScoreToRating: () => mapScoreToRating
340
+ mapScoreToRating: () => mapScoreToRating,
341
+ runInteractiveSetup: () => runInteractiveSetup
292
342
  });
293
343
  module.exports = __toCommonJS(index_exports);
294
- var import_core10 = require("@aiready/core");
344
+ var import_core11 = require("@aiready/core");
295
345
 
296
346
  // src/provider.ts
297
- var import_core8 = require("@aiready/core");
347
+ var import_core9 = require("@aiready/core");
298
348
 
299
349
  // src/analyzer.ts
300
350
  var import_core6 = require("@aiready/core");
@@ -305,6 +355,21 @@ var import_core2 = require("@aiready/core");
305
355
  // src/ast-utils.ts
306
356
  var import_core = require("@aiready/core");
307
357
 
358
+ // src/utils/string-utils.ts
359
+ function singularize(word) {
360
+ const irregulars = {
361
+ people: "person",
362
+ children: "child",
363
+ men: "man",
364
+ women: "woman"
365
+ };
366
+ if (irregulars[word]) return irregulars[word];
367
+ if (word.endsWith("ies")) return word.slice(0, -3) + "y";
368
+ if (word.endsWith("ses")) return word.slice(0, -2);
369
+ if (word.endsWith("s") && word.length > 3) return word.slice(0, -1);
370
+ return word;
371
+ }
372
+
308
373
  // src/semantic-analysis.ts
309
374
  function buildCoUsageMatrix(graph) {
310
375
  const coUsageMatrix = /* @__PURE__ */ new Map();
@@ -524,19 +589,6 @@ function inferDomain(name, filePath, domainOptions, fileImports) {
524
589
  }
525
590
  return "unknown";
526
591
  }
527
- function singularize(word) {
528
- const irregulars = {
529
- people: "person",
530
- children: "child",
531
- men: "man",
532
- women: "woman"
533
- };
534
- if (irregulars[word]) return irregulars[word];
535
- if (word.endsWith("ies")) return word.slice(0, -3) + "y";
536
- if (word.endsWith("ses")) return word.slice(0, -2);
537
- if (word.endsWith("s") && word.length > 3) return word.slice(0, -1);
538
- return word;
539
- }
540
592
  function getCoUsageData(file, coUsageMatrix) {
541
593
  return {
542
594
  file,
@@ -729,143 +781,112 @@ function calculateDirectoryDistance(files) {
729
781
  return comparisons > 0 ? totalNormalized / comparisons : 0;
730
782
  }
731
783
 
732
- // src/summary.ts
784
+ // src/issue-analyzer.ts
733
785
  var import_core3 = require("@aiready/core");
734
- function generateSummary(results, options) {
735
- const config = options ? Object.fromEntries(
736
- Object.entries(options).filter(
737
- ([key]) => !import_core3.GLOBAL_SCAN_OPTIONS.includes(key) || key === "rootDir"
738
- )
739
- ) : void 0;
740
- if (results.length === 0) {
741
- return {
742
- totalFiles: 0,
743
- totalTokens: 0,
744
- avgContextBudget: 0,
745
- maxContextBudget: 0,
746
- avgImportDepth: 0,
747
- maxImportDepth: 0,
748
- deepFiles: [],
749
- avgFragmentation: 0,
750
- fragmentedModules: [],
751
- avgCohesion: 0,
752
- lowCohesionFiles: [],
753
- criticalIssues: 0,
754
- majorIssues: 0,
755
- minorIssues: 0,
756
- totalPotentialSavings: 0,
757
- topExpensiveFiles: [],
758
- config
759
- };
786
+ function analyzeIssues(params) {
787
+ const {
788
+ file,
789
+ importDepth,
790
+ contextBudget,
791
+ cohesionScore,
792
+ fragmentationScore,
793
+ maxDepth,
794
+ maxContextBudget,
795
+ minCohesion,
796
+ maxFragmentation,
797
+ circularDeps
798
+ } = params;
799
+ const issues = [];
800
+ const recommendations = [];
801
+ let severity = import_core3.Severity.Info;
802
+ let potentialSavings = 0;
803
+ if (circularDeps.length > 0) {
804
+ severity = import_core3.Severity.Critical;
805
+ issues.push(`Part of ${circularDeps.length} circular dependency chain(s)`);
806
+ recommendations.push(
807
+ "Break circular dependencies by extracting interfaces or using dependency injection"
808
+ );
809
+ potentialSavings += contextBudget * 0.2;
760
810
  }
761
- const totalFiles = results.length;
762
- const totalTokens = results.reduce((sum, r) => sum + r.tokenCost, 0);
763
- const totalContextBudget = results.reduce(
764
- (sum, r) => sum + r.contextBudget,
765
- 0
766
- );
767
- const avgContextBudget = totalContextBudget / totalFiles;
768
- const maxContextBudget = Math.max(...results.map((r) => r.contextBudget));
769
- const avgImportDepth = results.reduce((sum, r) => sum + r.importDepth, 0) / totalFiles;
770
- const maxImportDepth = Math.max(...results.map((r) => r.importDepth));
771
- 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);
772
- const avgFragmentation = results.reduce((sum, r) => sum + r.fragmentationScore, 0) / totalFiles;
773
- const moduleMap = /* @__PURE__ */ new Map();
774
- for (const result of results) {
775
- for (const domain of result.domains) {
776
- if (!moduleMap.has(domain)) moduleMap.set(domain, []);
777
- moduleMap.get(domain).push(result);
778
- }
811
+ if (importDepth > maxDepth * 1.5) {
812
+ severity = import_core3.Severity.Critical;
813
+ issues.push(`Import depth ${importDepth} exceeds limit by 50%`);
814
+ recommendations.push("Flatten dependency tree or use facade pattern");
815
+ potentialSavings += contextBudget * 0.3;
816
+ } else if (importDepth > maxDepth) {
817
+ if (severity !== import_core3.Severity.Critical) severity = import_core3.Severity.Major;
818
+ issues.push(
819
+ `Import depth ${importDepth} exceeds recommended maximum ${maxDepth}`
820
+ );
821
+ recommendations.push("Consider reducing dependency depth");
822
+ potentialSavings += contextBudget * 0.15;
779
823
  }
780
- const fragmentedModules = [];
781
- for (const [domain, files] of moduleMap.entries()) {
782
- let jaccard2 = function(a, b) {
783
- const s1 = new Set(a || []);
784
- const s2 = new Set(b || []);
785
- if (s1.size === 0 && s2.size === 0) return 0;
786
- const inter = new Set([...s1].filter((x) => s2.has(x)));
787
- const uni = /* @__PURE__ */ new Set([...s1, ...s2]);
788
- return uni.size === 0 ? 0 : inter.size / uni.size;
789
- };
790
- var jaccard = jaccard2;
791
- if (files.length < 2) continue;
792
- const fragmentationScore = files.reduce((sum, f) => sum + f.fragmentationScore, 0) / files.length;
793
- if (fragmentationScore < 0.3) continue;
794
- const totalTokens2 = files.reduce((sum, f) => sum + f.tokenCost, 0);
795
- const avgCohesion2 = files.reduce((sum, f) => sum + f.cohesionScore, 0) / files.length;
796
- const targetFiles = Math.max(1, Math.ceil(files.length / 3));
797
- const filePaths = files.map((f) => f.file);
798
- const pathEntropy = calculatePathEntropy(filePaths);
799
- const directoryDistance = calculateDirectoryDistance(filePaths);
800
- let importSimTotal = 0;
801
- let importPairs = 0;
802
- for (let i = 0; i < files.length; i++) {
803
- for (let j = i + 1; j < files.length; j++) {
804
- importSimTotal += jaccard2(
805
- files[i].dependencyList || [],
806
- files[j].dependencyList || []
807
- );
808
- importPairs++;
809
- }
810
- }
811
- const importCohesion = importPairs > 0 ? importSimTotal / importPairs : 0;
812
- fragmentedModules.push({
813
- domain,
814
- files: files.map((f) => f.file),
815
- totalTokens: totalTokens2,
816
- fragmentationScore,
817
- avgCohesion: avgCohesion2,
818
- importCohesion,
819
- pathEntropy,
820
- directoryDistance,
821
- suggestedStructure: {
822
- targetFiles,
823
- consolidationPlan: [
824
- `Consolidate ${files.length} files across ${new Set(files.map((f) => f.file.split("/").slice(0, -1).join("/"))).size} directories`,
825
- `Target ~${targetFiles} core modules to reduce context switching`
826
- ]
827
- }
828
- });
824
+ if (contextBudget > maxContextBudget * 1.5) {
825
+ severity = import_core3.Severity.Critical;
826
+ issues.push(
827
+ `Context budget ${contextBudget.toLocaleString()} tokens is 50% over limit`
828
+ );
829
+ recommendations.push(
830
+ "Split into smaller modules or reduce dependency tree"
831
+ );
832
+ potentialSavings += contextBudget * 0.4;
833
+ } else if (contextBudget > maxContextBudget) {
834
+ if (severity !== import_core3.Severity.Critical) severity = import_core3.Severity.Major;
835
+ issues.push(
836
+ `Context budget ${contextBudget.toLocaleString()} exceeds ${maxContextBudget.toLocaleString()}`
837
+ );
838
+ recommendations.push("Reduce file size or dependencies");
839
+ potentialSavings += contextBudget * 0.2;
840
+ }
841
+ if (cohesionScore < minCohesion * 0.5) {
842
+ if (severity !== import_core3.Severity.Critical) severity = import_core3.Severity.Major;
843
+ issues.push(
844
+ `Very low cohesion (${(cohesionScore * 100).toFixed(0)}%) - mixed concerns`
845
+ );
846
+ recommendations.push(
847
+ "Split file by domain - separate unrelated functionality"
848
+ );
849
+ potentialSavings += contextBudget * 0.25;
850
+ } else if (cohesionScore < minCohesion) {
851
+ if (severity === import_core3.Severity.Info) severity = import_core3.Severity.Minor;
852
+ issues.push(`Low cohesion (${(cohesionScore * 100).toFixed(0)}%)`);
853
+ recommendations.push("Consider grouping related exports together");
854
+ potentialSavings += contextBudget * 0.1;
855
+ }
856
+ if (fragmentationScore > maxFragmentation) {
857
+ if (severity === import_core3.Severity.Info || severity === import_core3.Severity.Minor)
858
+ severity = import_core3.Severity.Minor;
859
+ issues.push(
860
+ `High fragmentation (${(fragmentationScore * 100).toFixed(0)}%) - scattered implementation`
861
+ );
862
+ recommendations.push("Consolidate with related files in same domain");
863
+ potentialSavings += contextBudget * 0.3;
864
+ }
865
+ if (issues.length === 0) {
866
+ issues.push("No significant issues detected");
867
+ recommendations.push("File is well-structured for AI context usage");
868
+ }
869
+ if (isBuildArtifact(file)) {
870
+ issues.push("Detected build artifact (bundled/output file)");
871
+ recommendations.push("Exclude build outputs from analysis");
872
+ severity = import_core3.Severity.Info;
873
+ potentialSavings = 0;
829
874
  }
830
- const avgCohesion = results.reduce((sum, r) => sum + r.cohesionScore, 0) / totalFiles;
831
- 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);
832
- const criticalIssues = results.filter(
833
- (r) => r.severity === "critical"
834
- ).length;
835
- const majorIssues = results.filter((r) => r.severity === "major").length;
836
- const minorIssues = results.filter((r) => r.severity === "minor").length;
837
- const totalPotentialSavings = results.reduce(
838
- (sum, r) => sum + r.potentialSavings,
839
- 0
840
- );
841
- const topExpensiveFiles = results.sort((a, b) => b.contextBudget - a.contextBudget).slice(0, 10).map((r) => ({
842
- file: r.file,
843
- contextBudget: r.contextBudget,
844
- severity: r.severity
845
- }));
846
875
  return {
847
- totalFiles,
848
- totalTokens,
849
- avgContextBudget,
850
- maxContextBudget,
851
- avgImportDepth,
852
- maxImportDepth,
853
- deepFiles,
854
- avgFragmentation,
855
- fragmentedModules,
856
- avgCohesion,
857
- lowCohesionFiles,
858
- criticalIssues,
859
- majorIssues,
860
- minorIssues,
861
- totalPotentialSavings,
862
- topExpensiveFiles,
863
- config
876
+ severity,
877
+ issues,
878
+ recommendations,
879
+ potentialSavings: Math.floor(potentialSavings)
864
880
  };
865
881
  }
882
+ function isBuildArtifact(filePath) {
883
+ const lower = filePath.toLowerCase();
884
+ return lower.includes("/node_modules/") || lower.includes("/dist/") || lower.includes("/build/") || lower.includes("/out/") || lower.includes("/.next/");
885
+ }
866
886
 
867
887
  // src/graph-builder.ts
868
888
  var import_core4 = require("@aiready/core");
889
+ init_dependency_graph_utils();
869
890
  var import_path = require("path");
870
891
  function resolveImport(source, importingFile, allFiles) {
871
892
  if (!source.startsWith(".") && !source.startsWith("/")) {
@@ -913,25 +934,12 @@ function extractDomainKeywordsFromPaths(files) {
913
934
  for (const segment of segments) {
914
935
  const normalized = segment.toLowerCase();
915
936
  if (normalized && !skipFolders.has(normalized) && !normalized.includes(".")) {
916
- folderNames.add(singularize2(normalized));
937
+ folderNames.add(singularize(normalized));
917
938
  }
918
939
  }
919
940
  }
920
941
  return Array.from(folderNames);
921
942
  }
922
- function singularize2(word) {
923
- const irregulars = {
924
- people: "person",
925
- children: "child",
926
- men: "man",
927
- women: "woman"
928
- };
929
- if (irregulars[word]) return irregulars[word];
930
- if (word.endsWith("ies")) return word.slice(0, -3) + "y";
931
- if (word.endsWith("ses")) return word.slice(0, -2);
932
- if (word.endsWith("s") && word.length > 3) return word.slice(0, -1);
933
- return word;
934
- }
935
943
  function buildDependencyGraph(files, options) {
936
944
  const nodes = /* @__PURE__ */ new Map();
937
945
  const edges = /* @__PURE__ */ new Map();
@@ -982,31 +990,10 @@ function buildDependencyGraph(files, options) {
982
990
  return graph;
983
991
  }
984
992
  function calculateImportDepth(file, graph, visited = /* @__PURE__ */ new Set(), depth = 0) {
985
- if (visited.has(file)) return depth;
986
- const dependencies = graph.edges.get(file);
987
- if (!dependencies || dependencies.size === 0) return depth;
988
- visited.add(file);
989
- let maxDepth = depth;
990
- for (const dep of dependencies) {
991
- maxDepth = Math.max(
992
- maxDepth,
993
- calculateImportDepth(dep, graph, visited, depth + 1)
994
- );
995
- }
996
- visited.delete(file);
997
- return maxDepth;
993
+ return calculateImportDepthFromEdges(file, graph.edges, visited, depth);
998
994
  }
999
995
  function getTransitiveDependencies(file, graph, visited = /* @__PURE__ */ new Set()) {
1000
- if (visited.has(file)) return [];
1001
- visited.add(file);
1002
- const dependencies = graph.edges.get(file);
1003
- if (!dependencies || dependencies.size === 0) return [];
1004
- const allDeps = [];
1005
- for (const dep of dependencies) {
1006
- allDeps.push(dep);
1007
- allDeps.push(...getTransitiveDependencies(dep, graph, visited));
1008
- }
1009
- return [...new Set(allDeps)];
996
+ return getTransitiveDependenciesFromEdges(file, graph.edges, visited);
1010
997
  }
1011
998
  function calculateContextBudget(file, graph) {
1012
999
  const node = graph.nodes.get(file);
@@ -1022,35 +1009,79 @@ function calculateContextBudget(file, graph) {
1022
1009
  return totalTokens;
1023
1010
  }
1024
1011
  function detectCircularDependencies(graph) {
1025
- const cycles = [];
1026
- const visited = /* @__PURE__ */ new Set();
1027
- const recursionStack = /* @__PURE__ */ new Set();
1028
- function dfs(file, path) {
1029
- if (recursionStack.has(file)) {
1030
- const cycleStart = path.indexOf(file);
1031
- if (cycleStart !== -1) {
1032
- cycles.push([...path.slice(cycleStart), file]);
1033
- }
1034
- return;
1012
+ return detectGraphCycles(graph.edges);
1013
+ }
1014
+
1015
+ // src/cluster-detector.ts
1016
+ function detectModuleClusters(graph, options) {
1017
+ const domainMap = /* @__PURE__ */ new Map();
1018
+ for (const [file, node] of graph.nodes.entries()) {
1019
+ const primaryDomain = node.exports[0]?.inferredDomain || "unknown";
1020
+ if (!domainMap.has(primaryDomain)) {
1021
+ domainMap.set(primaryDomain, []);
1035
1022
  }
1036
- if (visited.has(file)) return;
1037
- visited.add(file);
1038
- recursionStack.add(file);
1039
- path.push(file);
1040
- const dependencies = graph.edges.get(file);
1041
- if (dependencies) {
1042
- for (const dep of dependencies) {
1043
- dfs(dep, [...path]);
1023
+ domainMap.get(primaryDomain).push(file);
1024
+ }
1025
+ const clusters = [];
1026
+ for (const [domain, files] of domainMap.entries()) {
1027
+ if (files.length < 2 || domain === "unknown") continue;
1028
+ const totalTokens = files.reduce((sum, file) => {
1029
+ const node = graph.nodes.get(file);
1030
+ return sum + (node?.tokenCost || 0);
1031
+ }, 0);
1032
+ let sharedImportRatio = 0;
1033
+ if (files.length >= 2) {
1034
+ const allImportSets = files.map(
1035
+ (f) => new Set(graph.nodes.get(f)?.imports || [])
1036
+ );
1037
+ let intersection = new Set(allImportSets[0]);
1038
+ const union = new Set(allImportSets[0]);
1039
+ for (let i = 1; i < allImportSets.length; i++) {
1040
+ const nextSet = allImportSets[i];
1041
+ intersection = new Set([...intersection].filter((x) => nextSet.has(x)));
1042
+ for (const x of nextSet) union.add(x);
1044
1043
  }
1044
+ sharedImportRatio = union.size > 0 ? intersection.size / union.size : 0;
1045
1045
  }
1046
- recursionStack.delete(file);
1046
+ const fragmentation = calculateFragmentation(files, domain, {
1047
+ ...options,
1048
+ sharedImportRatio
1049
+ });
1050
+ let totalCohesion = 0;
1051
+ files.forEach((f) => {
1052
+ const node = graph.nodes.get(f);
1053
+ if (node) totalCohesion += calculateEnhancedCohesion(node.exports);
1054
+ });
1055
+ const avgCohesion = totalCohesion / files.length;
1056
+ clusters.push({
1057
+ domain,
1058
+ files,
1059
+ totalTokens,
1060
+ fragmentationScore: fragmentation,
1061
+ avgCohesion,
1062
+ suggestedStructure: generateSuggestedStructure(
1063
+ files,
1064
+ totalTokens,
1065
+ fragmentation
1066
+ )
1067
+ });
1047
1068
  }
1048
- for (const file of graph.nodes.keys()) {
1049
- if (!visited.has(file)) {
1050
- dfs(file, []);
1051
- }
1069
+ return clusters;
1070
+ }
1071
+ function generateSuggestedStructure(files, tokens, fragmentation) {
1072
+ const targetFiles = Math.max(1, Math.ceil(tokens / 1e4));
1073
+ const plan = [];
1074
+ if (fragmentation > 0.5) {
1075
+ plan.push(
1076
+ `Consolidate ${files.length} files scattered across multiple directories into ${targetFiles} core module(s)`
1077
+ );
1052
1078
  }
1053
- return cycles;
1079
+ if (tokens > 2e4) {
1080
+ plan.push(
1081
+ `Domain logic is very large (${Math.round(tokens / 1e3)}k tokens). Ensure clear sub-domain boundaries.`
1082
+ );
1083
+ }
1084
+ return { targetFiles, consolidationPlan: plan };
1054
1085
  }
1055
1086
 
1056
1087
  // src/classifier.ts
@@ -1365,78 +1396,6 @@ function adjustFragmentationForClassification(baseFragmentation, classification)
1365
1396
  }
1366
1397
  }
1367
1398
 
1368
- // src/cluster-detector.ts
1369
- function detectModuleClusters(graph, options) {
1370
- const domainMap = /* @__PURE__ */ new Map();
1371
- for (const [file, node] of graph.nodes.entries()) {
1372
- const primaryDomain = node.exports[0]?.inferredDomain || "unknown";
1373
- if (!domainMap.has(primaryDomain)) {
1374
- domainMap.set(primaryDomain, []);
1375
- }
1376
- domainMap.get(primaryDomain).push(file);
1377
- }
1378
- const clusters = [];
1379
- for (const [domain, files] of domainMap.entries()) {
1380
- if (files.length < 2 || domain === "unknown") continue;
1381
- const totalTokens = files.reduce((sum, file) => {
1382
- const node = graph.nodes.get(file);
1383
- return sum + (node?.tokenCost || 0);
1384
- }, 0);
1385
- let sharedImportRatio = 0;
1386
- if (files.length >= 2) {
1387
- const allImportSets = files.map(
1388
- (f) => new Set(graph.nodes.get(f)?.imports || [])
1389
- );
1390
- let intersection = new Set(allImportSets[0]);
1391
- let union = new Set(allImportSets[0]);
1392
- for (let i = 1; i < allImportSets.length; i++) {
1393
- const nextSet = allImportSets[i];
1394
- intersection = new Set([...intersection].filter((x) => nextSet.has(x)));
1395
- for (const x of nextSet) union.add(x);
1396
- }
1397
- sharedImportRatio = union.size > 0 ? intersection.size / union.size : 0;
1398
- }
1399
- const fragmentation = calculateFragmentation(files, domain, {
1400
- ...options,
1401
- sharedImportRatio
1402
- });
1403
- let totalCohesion = 0;
1404
- files.forEach((f) => {
1405
- const node = graph.nodes.get(f);
1406
- if (node) totalCohesion += calculateEnhancedCohesion(node.exports);
1407
- });
1408
- const avgCohesion = totalCohesion / files.length;
1409
- clusters.push({
1410
- domain,
1411
- files,
1412
- totalTokens,
1413
- fragmentationScore: fragmentation,
1414
- avgCohesion,
1415
- suggestedStructure: generateSuggestedStructure(
1416
- files,
1417
- totalTokens,
1418
- fragmentation
1419
- )
1420
- });
1421
- }
1422
- return clusters;
1423
- }
1424
- function generateSuggestedStructure(files, tokens, fragmentation) {
1425
- const targetFiles = Math.max(1, Math.ceil(tokens / 1e4));
1426
- const plan = [];
1427
- if (fragmentation > 0.5) {
1428
- plan.push(
1429
- `Consolidate ${files.length} files scattered across multiple directories into ${targetFiles} core module(s)`
1430
- );
1431
- }
1432
- if (tokens > 2e4) {
1433
- plan.push(
1434
- `Domain logic is very large (${Math.round(tokens / 1e3)}k tokens). Ensure clear sub-domain boundaries.`
1435
- );
1436
- }
1437
- return { targetFiles, consolidationPlan: plan };
1438
- }
1439
-
1440
1399
  // src/remediation.ts
1441
1400
  function getClassificationRecommendations(classification, file, issues) {
1442
1401
  switch (classification) {
@@ -1543,124 +1502,14 @@ function getGeneralRecommendations(metrics, thresholds) {
1543
1502
 
1544
1503
  // src/analyzer.ts
1545
1504
  function calculateCohesion(exports2, filePath, options) {
1546
- if (exports2.length <= 1) return 1;
1547
- if (filePath && isTestFile(filePath)) return 1;
1548
- const domains = exports2.map((e) => e.inferredDomain || "unknown");
1549
- const uniqueDomains = new Set(domains.filter((d) => d !== "unknown"));
1550
- const hasImports = exports2.some((e) => !!e.imports);
1551
- if (!hasImports && !options?.weights) {
1552
- if (uniqueDomains.size <= 1) return 1;
1553
- return 0.4;
1554
- }
1555
1505
  return calculateEnhancedCohesion(exports2, filePath, options);
1556
1506
  }
1557
- function analyzeIssues(params) {
1558
- const {
1559
- file,
1560
- importDepth,
1561
- contextBudget,
1562
- cohesionScore,
1563
- fragmentationScore,
1564
- maxDepth,
1565
- maxContextBudget,
1566
- minCohesion,
1567
- maxFragmentation,
1568
- circularDeps
1569
- } = params;
1570
- const issues = [];
1571
- const recommendations = [];
1572
- let severity = import_core6.Severity.Info;
1573
- let potentialSavings = 0;
1574
- if (circularDeps.length > 0) {
1575
- severity = import_core6.Severity.Critical;
1576
- issues.push(`Part of ${circularDeps.length} circular dependency chain(s)`);
1577
- recommendations.push(
1578
- "Break circular dependencies by extracting interfaces or using dependency injection"
1579
- );
1580
- potentialSavings += contextBudget * 0.2;
1581
- }
1582
- if (importDepth > maxDepth * 1.5) {
1583
- severity = import_core6.Severity.Critical;
1584
- issues.push(`Import depth ${importDepth} exceeds limit by 50%`);
1585
- recommendations.push("Flatten dependency tree or use facade pattern");
1586
- potentialSavings += contextBudget * 0.3;
1587
- } else if (importDepth > maxDepth) {
1588
- if (severity !== import_core6.Severity.Critical) severity = import_core6.Severity.Major;
1589
- issues.push(
1590
- `Import depth ${importDepth} exceeds recommended maximum ${maxDepth}`
1591
- );
1592
- recommendations.push("Consider reducing dependency depth");
1593
- potentialSavings += contextBudget * 0.15;
1594
- }
1595
- if (contextBudget > maxContextBudget * 1.5) {
1596
- severity = import_core6.Severity.Critical;
1597
- issues.push(
1598
- `Context budget ${contextBudget.toLocaleString()} tokens is 50% over limit`
1599
- );
1600
- recommendations.push(
1601
- "Split into smaller modules or reduce dependency tree"
1602
- );
1603
- potentialSavings += contextBudget * 0.4;
1604
- } else if (contextBudget > maxContextBudget) {
1605
- if (severity !== import_core6.Severity.Critical) severity = import_core6.Severity.Major;
1606
- issues.push(
1607
- `Context budget ${contextBudget.toLocaleString()} exceeds ${maxContextBudget.toLocaleString()}`
1608
- );
1609
- recommendations.push("Reduce file size or dependencies");
1610
- potentialSavings += contextBudget * 0.2;
1611
- }
1612
- if (cohesionScore < minCohesion * 0.5) {
1613
- if (severity !== import_core6.Severity.Critical) severity = import_core6.Severity.Major;
1614
- issues.push(
1615
- `Very low cohesion (${(cohesionScore * 100).toFixed(0)}%) - mixed concerns`
1616
- );
1617
- recommendations.push(
1618
- "Split file by domain - separate unrelated functionality"
1619
- );
1620
- potentialSavings += contextBudget * 0.25;
1621
- } else if (cohesionScore < minCohesion) {
1622
- if (severity === import_core6.Severity.Info) severity = import_core6.Severity.Minor;
1623
- issues.push(`Low cohesion (${(cohesionScore * 100).toFixed(0)}%)`);
1624
- recommendations.push("Consider grouping related exports together");
1625
- potentialSavings += contextBudget * 0.1;
1626
- }
1627
- if (fragmentationScore > maxFragmentation) {
1628
- if (severity === import_core6.Severity.Info || severity === import_core6.Severity.Minor)
1629
- severity = import_core6.Severity.Minor;
1630
- issues.push(
1631
- `High fragmentation (${(fragmentationScore * 100).toFixed(0)}%) - scattered implementation`
1632
- );
1633
- recommendations.push("Consolidate with related files in same domain");
1634
- potentialSavings += contextBudget * 0.3;
1635
- }
1636
- if (issues.length === 0) {
1637
- issues.push("No significant issues detected");
1638
- recommendations.push("File is well-structured for AI context usage");
1639
- }
1640
- if (isBuildArtifact(file)) {
1641
- issues.push("Detected build artifact (bundled/output file)");
1642
- recommendations.push("Exclude build outputs from analysis");
1643
- severity = import_core6.Severity.Info;
1644
- potentialSavings = 0;
1645
- }
1646
- return {
1647
- severity,
1648
- issues,
1649
- recommendations,
1650
- potentialSavings: Math.floor(potentialSavings)
1651
- };
1652
- }
1653
- function isBuildArtifact(filePath) {
1654
- const lower = filePath.toLowerCase();
1655
- return lower.includes("/node_modules/") || lower.includes("/dist/") || lower.includes("/build/") || lower.includes("/out/") || lower.includes("/.next/");
1656
- }
1657
1507
  async function analyzeContext(options) {
1658
1508
  const {
1659
1509
  maxDepth = 5,
1660
1510
  maxContextBudget = 1e4,
1661
1511
  minCohesion = 0.6,
1662
1512
  maxFragmentation = 0.5,
1663
- focus = "all",
1664
1513
  includeNodeModules = false,
1665
1514
  ...scanOptions
1666
1515
  } = options;
@@ -1698,27 +1547,21 @@ async function analyzeContext(options) {
1698
1547
  maxContextBudget,
1699
1548
  minCohesion,
1700
1549
  maxFragmentation,
1701
- circularDeps: metric.metrics.circularDependencies.map(
1702
- (cycle) => cycle.split(" \u2192 ")
1703
- )
1550
+ circularDeps: []
1704
1551
  });
1705
1552
  return {
1706
1553
  file: metric.file,
1707
- tokenCost: Math.floor(
1708
- metric.contextBudget / (1 + metric.imports.length || 1)
1709
- ),
1710
- linesOfCode: metric.metrics.linesOfCode,
1554
+ tokenCost: 0,
1555
+ linesOfCode: 0,
1556
+ // Not provided by python context yet
1711
1557
  importDepth: metric.importDepth,
1712
- dependencyCount: metric.imports.length,
1713
- dependencyList: metric.imports.map(
1714
- (imp) => imp.resolvedPath || imp.source
1715
- ),
1716
- circularDeps: metric.metrics.circularDependencies.map(
1717
- (cycle) => cycle.split(" \u2192 ")
1718
- ),
1558
+ dependencyCount: 0,
1559
+ // Not provided
1560
+ dependencyList: [],
1561
+ circularDeps: [],
1719
1562
  cohesionScore: metric.cohesion,
1720
- domains: ["python"],
1721
- exportCount: metric.exports.length,
1563
+ domains: [],
1564
+ exportCount: 0,
1722
1565
  contextBudget: metric.contextBudget,
1723
1566
  fragmentationScore: 0,
1724
1567
  relatedFiles: [],
@@ -1730,116 +1573,169 @@ async function analyzeContext(options) {
1730
1573
  };
1731
1574
  });
1732
1575
  }
1733
- const circularDeps = detectCircularDependencies(graph);
1734
- const useLogScale = files.length >= 500;
1735
- const clusters = detectModuleClusters(graph, { useLogScale });
1736
- const fragmentationMap = /* @__PURE__ */ new Map();
1737
- for (const cluster of clusters) {
1738
- for (const file of cluster.files) {
1739
- fragmentationMap.set(file, cluster.fragmentationScore);
1576
+ const clusters = detectModuleClusters(graph);
1577
+ const allCircularDeps = detectCircularDependencies(graph);
1578
+ const results = Array.from(graph.nodes.values()).map(
1579
+ (node) => {
1580
+ const file = node.file;
1581
+ const tokenCost = node.tokenCost;
1582
+ const importDepth = calculateImportDepth(file, graph);
1583
+ const transitiveDeps = getTransitiveDependencies(file, graph);
1584
+ const contextBudget = calculateContextBudget(file, graph);
1585
+ const circularDeps = allCircularDeps.filter(
1586
+ (cycle) => cycle.includes(file)
1587
+ );
1588
+ const cluster = clusters.find((c) => c.files.includes(file));
1589
+ const rawFragmentationScore = cluster ? cluster.fragmentationScore : 0;
1590
+ const rawCohesionScore = calculateEnhancedCohesion(
1591
+ node.exports,
1592
+ file,
1593
+ options
1594
+ );
1595
+ const fileClassification = classifyFile(node, rawCohesionScore);
1596
+ const cohesionScore = adjustCohesionForClassification(
1597
+ rawCohesionScore,
1598
+ fileClassification
1599
+ );
1600
+ const fragmentationScore = adjustFragmentationForClassification(
1601
+ rawFragmentationScore,
1602
+ fileClassification
1603
+ );
1604
+ const { severity, issues, recommendations, potentialSavings } = analyzeIssues({
1605
+ file,
1606
+ importDepth,
1607
+ contextBudget,
1608
+ cohesionScore,
1609
+ fragmentationScore,
1610
+ maxDepth,
1611
+ maxContextBudget,
1612
+ minCohesion,
1613
+ maxFragmentation,
1614
+ circularDeps
1615
+ });
1616
+ const classRecs = getClassificationRecommendations(
1617
+ fileClassification,
1618
+ file,
1619
+ issues
1620
+ );
1621
+ const allRecommendations = Array.from(
1622
+ /* @__PURE__ */ new Set([...recommendations, ...classRecs])
1623
+ );
1624
+ return {
1625
+ file,
1626
+ tokenCost,
1627
+ linesOfCode: node.linesOfCode,
1628
+ importDepth,
1629
+ dependencyCount: transitiveDeps.length,
1630
+ dependencyList: transitiveDeps,
1631
+ circularDeps,
1632
+ cohesionScore,
1633
+ domains: Array.from(
1634
+ new Set(
1635
+ node.exports.flatMap((e) => e.domains?.map((d) => d.domain) || [])
1636
+ )
1637
+ ),
1638
+ exportCount: node.exports.length,
1639
+ contextBudget,
1640
+ fragmentationScore,
1641
+ relatedFiles: cluster ? cluster.files : [],
1642
+ fileClassification,
1643
+ severity,
1644
+ issues,
1645
+ recommendations: allRecommendations,
1646
+ potentialSavings
1647
+ };
1740
1648
  }
1741
- }
1742
- const results = [];
1743
- for (const { file } of fileContents) {
1744
- const node = graph.nodes.get(file);
1745
- if (!node) continue;
1746
- const importDepth = focus === "depth" || focus === "all" ? calculateImportDepth(file, graph) : 0;
1747
- const dependencyList = focus === "depth" || focus === "all" ? getTransitiveDependencies(file, graph) : [];
1748
- const contextBudget = focus === "all" ? calculateContextBudget(file, graph) : node.tokenCost;
1749
- const cohesionScore = focus === "cohesion" || focus === "all" ? calculateCohesion(node.exports, file, {
1750
- coUsageMatrix: graph.coUsageMatrix
1751
- }) : 1;
1752
- const fragmentationScore = fragmentationMap.get(file) || 0;
1753
- const relatedFiles = [];
1754
- for (const cluster of clusters) {
1755
- if (cluster.files.includes(file)) {
1756
- relatedFiles.push(...cluster.files.filter((f) => f !== file));
1757
- break;
1758
- }
1649
+ );
1650
+ return [...results, ...pythonResults];
1651
+ }
1652
+
1653
+ // src/summary.ts
1654
+ var import_core7 = require("@aiready/core");
1655
+ function generateSummary(results, options = {}) {
1656
+ const config = options ? Object.fromEntries(
1657
+ Object.entries(options).filter(
1658
+ ([key]) => !import_core7.GLOBAL_SCAN_OPTIONS.includes(key) || key === "rootDir"
1659
+ )
1660
+ ) : {};
1661
+ const totalFiles = results.length;
1662
+ const totalTokens = results.reduce((sum, r) => sum + r.tokenCost, 0);
1663
+ const avgContextBudget = totalFiles > 0 ? results.reduce((sum, r) => sum + r.contextBudget, 0) / totalFiles : 0;
1664
+ const deepFiles = results.filter((r) => r.importDepth > 5).map((r) => ({ file: r.file, depth: r.importDepth }));
1665
+ const maxImportDepth = Math.max(0, ...results.map((r) => r.importDepth));
1666
+ const moduleMap = /* @__PURE__ */ new Map();
1667
+ results.forEach((r) => {
1668
+ const parts = r.file.split("/");
1669
+ let domain = "root";
1670
+ if (parts.length > 2) {
1671
+ domain = parts.slice(0, 2).join("/");
1672
+ }
1673
+ if (!moduleMap.has(domain)) moduleMap.set(domain, []);
1674
+ moduleMap.get(domain).push(r);
1675
+ });
1676
+ const fragmentedModules = [];
1677
+ moduleMap.forEach((files, domain) => {
1678
+ const clusterTokens = files.reduce((sum, f) => sum + f.tokenCost, 0);
1679
+ const filePaths = files.map((f) => f.file);
1680
+ const avgEntropy = calculatePathEntropy(filePaths);
1681
+ const fragmentationScore = Math.min(1, avgEntropy * (files.length / 10));
1682
+ if (fragmentationScore > 0.4) {
1683
+ fragmentedModules.push({
1684
+ domain,
1685
+ files: filePaths,
1686
+ fragmentationScore,
1687
+ totalTokens: clusterTokens,
1688
+ avgCohesion: files.reduce((sum, f) => sum + f.cohesionScore, 0) / files.length,
1689
+ suggestedStructure: {
1690
+ targetFiles: Math.ceil(files.length / 2),
1691
+ consolidationPlan: [
1692
+ `Consolidate ${files.length} files in ${domain} into fewer modules`
1693
+ ]
1694
+ }
1695
+ });
1759
1696
  }
1760
- const { issues } = analyzeIssues({
1761
- file,
1762
- importDepth,
1763
- contextBudget,
1764
- cohesionScore,
1765
- fragmentationScore,
1766
- maxDepth,
1767
- maxContextBudget,
1768
- minCohesion,
1769
- maxFragmentation,
1770
- circularDeps
1771
- });
1772
- const domains = [
1773
- ...new Set(node.exports.map((e) => e.inferredDomain || "unknown"))
1774
- ];
1775
- const fileClassification = classifyFile(node);
1776
- const adjustedCohesionScore = adjustCohesionForClassification(
1777
- cohesionScore,
1778
- fileClassification,
1779
- node
1780
- );
1781
- const adjustedFragmentationScore = adjustFragmentationForClassification(
1782
- fragmentationScore,
1783
- fileClassification
1784
- );
1785
- const classificationRecommendations = getClassificationRecommendations(
1786
- fileClassification,
1787
- file,
1788
- issues
1789
- );
1790
- const {
1791
- severity: adjustedSeverity,
1792
- issues: adjustedIssues,
1793
- recommendations: finalRecommendations,
1794
- potentialSavings: adjustedSavings
1795
- } = analyzeIssues({
1796
- file,
1797
- importDepth,
1798
- contextBudget,
1799
- cohesionScore: adjustedCohesionScore,
1800
- fragmentationScore: adjustedFragmentationScore,
1801
- maxDepth,
1802
- maxContextBudget,
1803
- minCohesion,
1804
- maxFragmentation,
1805
- circularDeps
1806
- });
1807
- results.push({
1808
- file,
1809
- tokenCost: node.tokenCost,
1810
- linesOfCode: node.linesOfCode,
1811
- importDepth,
1812
- dependencyCount: dependencyList.length,
1813
- dependencyList,
1814
- circularDeps: circularDeps.filter((cycle) => cycle.includes(file)),
1815
- cohesionScore: adjustedCohesionScore,
1816
- domains,
1817
- exportCount: node.exports.length,
1818
- contextBudget,
1819
- fragmentationScore: adjustedFragmentationScore,
1820
- relatedFiles,
1821
- fileClassification,
1822
- severity: adjustedSeverity,
1823
- issues: adjustedIssues,
1824
- recommendations: [
1825
- ...finalRecommendations,
1826
- ...classificationRecommendations.slice(0, 1)
1827
- ],
1828
- potentialSavings: adjustedSavings
1829
- });
1830
- }
1831
- const allResults = [...results, ...pythonResults];
1832
- const finalSummary = generateSummary(allResults, options);
1833
- return allResults.sort((a, b) => {
1834
- const severityOrder = { critical: 0, major: 1, minor: 2, info: 3 };
1835
- const severityDiff = severityOrder[a.severity] - severityOrder[b.severity];
1836
- if (severityDiff !== 0) return severityDiff;
1837
- return b.contextBudget - a.contextBudget;
1838
1697
  });
1698
+ fragmentedModules.sort((a, b) => b.fragmentationScore - a.fragmentationScore);
1699
+ const avgFragmentation = fragmentedModules.length > 0 ? fragmentedModules.reduce((sum, m) => sum + m.fragmentationScore, 0) / fragmentedModules.length : 0;
1700
+ const avgCohesion = results.reduce((sum, r) => sum + r.cohesionScore, 0) / (totalFiles || 1);
1701
+ const lowCohesionFiles = results.filter((r) => r.cohesionScore < 0.4).map((r) => ({ file: r.file, score: r.cohesionScore }));
1702
+ const criticalIssues = results.filter(
1703
+ (r) => r.severity === "critical"
1704
+ ).length;
1705
+ const majorIssues = results.filter((r) => r.severity === "major").length;
1706
+ const minorIssues = results.filter((r) => r.severity === "minor").length;
1707
+ const totalPotentialSavings = results.reduce(
1708
+ (sum, r) => sum + (r.potentialSavings || 0),
1709
+ 0
1710
+ );
1711
+ const topExpensiveFiles = [...results].sort((a, b) => b.contextBudget - a.contextBudget).slice(0, 10).map((r) => ({
1712
+ file: r.file,
1713
+ contextBudget: r.contextBudget,
1714
+ severity: r.severity
1715
+ }));
1716
+ return {
1717
+ totalFiles,
1718
+ totalTokens,
1719
+ avgContextBudget,
1720
+ maxContextBudget: Math.max(0, ...results.map((r) => r.contextBudget)),
1721
+ avgImportDepth: results.reduce((sum, r) => sum + r.importDepth, 0) / (totalFiles || 1),
1722
+ maxImportDepth,
1723
+ deepFiles,
1724
+ avgFragmentation,
1725
+ fragmentedModules,
1726
+ avgCohesion,
1727
+ lowCohesionFiles,
1728
+ criticalIssues,
1729
+ majorIssues,
1730
+ minorIssues,
1731
+ totalPotentialSavings,
1732
+ topExpensiveFiles,
1733
+ config
1734
+ };
1839
1735
  }
1840
1736
 
1841
1737
  // src/scoring.ts
1842
- var import_core7 = require("@aiready/core");
1738
+ var import_core8 = require("@aiready/core");
1843
1739
  function calculateContextScore(summary, costConfig) {
1844
1740
  const {
1845
1741
  avgContextBudget,
@@ -1950,8 +1846,8 @@ function calculateContextScore(summary, costConfig) {
1950
1846
  priority: "high"
1951
1847
  });
1952
1848
  }
1953
- const cfg = { ...import_core7.DEFAULT_COST_CONFIG, ...costConfig };
1954
- const estimatedMonthlyCost = (0, import_core7.calculateMonthlyCost)(
1849
+ const cfg = { ...import_core8.DEFAULT_COST_CONFIG, ...costConfig };
1850
+ const estimatedMonthlyCost = (0, import_core8.calculateMonthlyCost)(
1955
1851
  avgContextBudget * (totalFiles || 1),
1956
1852
  cfg
1957
1853
  );
@@ -1959,9 +1855,9 @@ function calculateContextScore(summary, costConfig) {
1959
1855
  ...Array(criticalIssues).fill({ severity: "critical" }),
1960
1856
  ...Array(majorIssues).fill({ severity: "major" })
1961
1857
  ];
1962
- const productivityImpact = (0, import_core7.calculateProductivityImpact)(issues);
1858
+ const productivityImpact = (0, import_core8.calculateProductivityImpact)(issues);
1963
1859
  return {
1964
- toolName: import_core7.ToolName.ContextAnalyzer,
1860
+ toolName: import_core8.ToolName.ContextAnalyzer,
1965
1861
  score,
1966
1862
  rawMetrics: {
1967
1863
  avgContextBudget: Math.round(avgContextBudget),
@@ -1988,7 +1884,7 @@ function mapScoreToRating(score) {
1988
1884
 
1989
1885
  // src/provider.ts
1990
1886
  var ContextAnalyzerProvider = {
1991
- id: import_core8.ToolName.ContextAnalyzer,
1887
+ id: import_core9.ToolName.ContextAnalyzer,
1992
1888
  alias: ["context", "fragmentation", "budget"],
1993
1889
  async analyze(options) {
1994
1890
  const results = await analyzeContext(options);
@@ -1997,7 +1893,7 @@ var ContextAnalyzerProvider = {
1997
1893
  (r) => ({
1998
1894
  fileName: r.file,
1999
1895
  issues: r.issues.map((msg) => ({
2000
- type: import_core8.IssueType.ContextFragmentation,
1896
+ type: import_core9.IssueType.ContextFragmentation,
2001
1897
  severity: r.severity,
2002
1898
  message: msg,
2003
1899
  location: { file: r.file, line: 1 },
@@ -2010,13 +1906,13 @@ var ContextAnalyzerProvider = {
2010
1906
  }
2011
1907
  })
2012
1908
  );
2013
- return import_core8.SpokeOutputSchema.parse({
1909
+ return import_core9.SpokeOutputSchema.parse({
2014
1910
  results: normalizedResults,
2015
1911
  summary: {
2016
1912
  ...summary
2017
1913
  },
2018
1914
  metadata: {
2019
- toolName: import_core8.ToolName.ContextAnalyzer,
1915
+ toolName: import_core9.ToolName.ContextAnalyzer,
2020
1916
  version: "0.17.5",
2021
1917
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
2022
1918
  }
@@ -2030,9 +1926,9 @@ var ContextAnalyzerProvider = {
2030
1926
  };
2031
1927
 
2032
1928
  // src/defaults.ts
2033
- var import_core9 = require("@aiready/core");
1929
+ var import_core10 = require("@aiready/core");
2034
1930
  async function getSmartDefaults(directory, userOptions) {
2035
- const files = await (0, import_core9.scanFiles)({
1931
+ const files = await (0, import_core10.scanFiles)({
2036
1932
  rootDir: directory,
2037
1933
  include: userOptions.include,
2038
1934
  exclude: userOptions.exclude
@@ -2076,8 +1972,329 @@ async function getSmartDefaults(directory, userOptions) {
2076
1972
  };
2077
1973
  }
2078
1974
 
1975
+ // src/utils/output-formatter.ts
1976
+ var import_chalk = __toESM(require("chalk"));
1977
+ var import_fs2 = require("fs");
1978
+ var import_path3 = require("path");
1979
+ var import_prompts = __toESM(require("prompts"));
1980
+ function displayConsoleReport(summary, results, maxResults = 10) {
1981
+ 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";
1982
+ const totalIssues = summary.criticalIssues + summary.majorIssues + summary.minorIssues;
1983
+ console.log(import_chalk.default.bold("\u{1F4CA} Context Analysis Summary:\n"));
1984
+ console.log(` \u2022 Total Files: ${import_chalk.default.cyan(summary.totalFiles)}`);
1985
+ console.log(
1986
+ ` \u2022 Total Tokens: ${import_chalk.default.cyan(summary.totalTokens.toLocaleString())}`
1987
+ );
1988
+ console.log(
1989
+ ` \u2022 Avg Budget: ${import_chalk.default.cyan(summary.avgContextBudget.toFixed(0))} tokens`
1990
+ );
1991
+ console.log(
1992
+ ` \u2022 Potential Saving: ${import_chalk.default.green(summary.totalPotentialSavings.toLocaleString())} tokens`
1993
+ );
1994
+ console.log();
1995
+ if (totalIssues > 0) {
1996
+ console.log(import_chalk.default.bold("\u26A0\uFE0F Issues Detected:\n"));
1997
+ console.log(` \u2022 ${import_chalk.default.red("\u{1F534} Critical:")} ${summary.criticalIssues}`);
1998
+ console.log(` \u2022 ${import_chalk.default.yellow("\u{1F7E1} Major:")} ${summary.majorIssues}`);
1999
+ console.log(` \u2022 ${import_chalk.default.blue("\u{1F535} Minor:")} ${summary.minorIssues}`);
2000
+ console.log();
2001
+ } else {
2002
+ console.log(import_chalk.default.green("\u2705 No significant context issues detected!\n"));
2003
+ }
2004
+ if (summary.fragmentedModules.length > 0) {
2005
+ console.log(import_chalk.default.bold("\u{1F9E9} Top Fragmented Modules:\n"));
2006
+ summary.fragmentedModules.slice(0, maxResults).forEach((mod) => {
2007
+ const scoreColor = mod.fragmentationScore > 0.7 ? import_chalk.default.red : mod.fragmentationScore > 0.4 ? import_chalk.default.yellow : import_chalk.default.green;
2008
+ console.log(
2009
+ ` ${scoreColor("\u25A0")} ${import_chalk.default.white(mod.domain)} ${import_chalk.default.dim(`(${mod.files.length} files, ${(mod.fragmentationScore * 100).toFixed(0)}% frag)`)}`
2010
+ );
2011
+ });
2012
+ console.log();
2013
+ }
2014
+ if (summary.topExpensiveFiles.length > 0) {
2015
+ console.log(import_chalk.default.bold("\u{1F4B8} Most Expensive Files (Context Budget):\n"));
2016
+ summary.topExpensiveFiles.slice(0, maxResults).forEach((item) => {
2017
+ const fileName = item.file.split("/").slice(-2).join("/");
2018
+ const severityColor = item.severity === "critical" ? import_chalk.default.red : item.severity === "major" ? import_chalk.default.yellow : import_chalk.default.blue;
2019
+ console.log(
2020
+ ` ${severityColor("\u25CF")} ${import_chalk.default.white(fileName)} ${import_chalk.default.dim(`- ${item.contextBudget.toLocaleString()} tokens`)}`
2021
+ );
2022
+ });
2023
+ console.log();
2024
+ }
2025
+ if (totalIssues > 0) {
2026
+ console.log(import_chalk.default.bold("\u{1F4A1} Top Recommendations:\n"));
2027
+ const topFiles = results.filter((r) => r.severity === "critical" || r.severity === "major").slice(0, 3);
2028
+ topFiles.forEach((result, index) => {
2029
+ const fileName = result.file.split("/").slice(-2).join("/");
2030
+ console.log(import_chalk.default.cyan(` ${index + 1}. ${fileName}`));
2031
+ result.recommendations.slice(0, 2).forEach((rec) => {
2032
+ console.log(import_chalk.default.dim(` \u2022 ${rec}`));
2033
+ });
2034
+ });
2035
+ console.log();
2036
+ }
2037
+ console.log(import_chalk.default.cyan(divider));
2038
+ console.log(
2039
+ import_chalk.default.dim(
2040
+ "\n\u2B50 Like aiready? Star us on GitHub: https://github.com/caopengau/aiready-context-analyzer"
2041
+ )
2042
+ );
2043
+ console.log(
2044
+ import_chalk.default.dim(
2045
+ "\u{1F41B} Found a bug? Report it: https://github.com/caopengau/aiready-context-analyzer/issues\n"
2046
+ )
2047
+ );
2048
+ }
2049
+ function generateHTMLReport(summary, results) {
2050
+ const totalIssues = summary.criticalIssues + summary.majorIssues + summary.minorIssues;
2051
+ void results;
2052
+ return `<!DOCTYPE html>
2053
+ <html lang="en">
2054
+ <head>
2055
+ <meta charset="UTF-8">
2056
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
2057
+ <title>aiready Context Analysis Report</title>
2058
+ <style>
2059
+ body {
2060
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
2061
+ line-height: 1.6;
2062
+ color: #333;
2063
+ max-width: 1200px;
2064
+ margin: 0 auto;
2065
+ padding: 20px;
2066
+ background-color: #f5f5f5;
2067
+ }
2068
+ h1, h2, h3 { color: #2c3e50; }
2069
+ .header {
2070
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
2071
+ color: white;
2072
+ padding: 30px;
2073
+ border-radius: 8px;
2074
+ margin-bottom: 30px;
2075
+ }
2076
+ .summary {
2077
+ display: grid;
2078
+ grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
2079
+ gap: 20px;
2080
+ margin-bottom: 30px;
2081
+ }
2082
+ .card {
2083
+ background: white;
2084
+ padding: 20px;
2085
+ border-radius: 8px;
2086
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
2087
+ }
2088
+ .metric {
2089
+ font-size: 2em;
2090
+ font-weight: bold;
2091
+ color: #667eea;
2092
+ }
2093
+ .label {
2094
+ color: #666;
2095
+ font-size: 0.9em;
2096
+ margin-top: 5px;
2097
+ }
2098
+ .issue-critical { color: #e74c3c; }
2099
+ .issue-major { color: #f39c12; }
2100
+ .issue-minor { color: #3498db; }
2101
+ table {
2102
+ width: 100%;
2103
+ border-collapse: collapse;
2104
+ background: white;
2105
+ border-radius: 8px;
2106
+ overflow: hidden;
2107
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
2108
+ }
2109
+ th, td {
2110
+ padding: 12px;
2111
+ text-align: left;
2112
+ border-bottom: 1px solid #eee;
2113
+ }
2114
+ th {
2115
+ background-color: #667eea;
2116
+ color: white;
2117
+ font-weight: 600;
2118
+ }
2119
+ tr:hover { background-color: #f8f9fa; }
2120
+ .footer {
2121
+ text-align: center;
2122
+ margin-top: 40px;
2123
+ padding: 20px;
2124
+ color: #666;
2125
+ font-size: 0.9em;
2126
+ }
2127
+ </style>
2128
+ </head>
2129
+ <body>
2130
+ <div class="header">
2131
+ <h1>\u{1F50D} AIReady Context Analysis Report</h1>
2132
+ <p>Generated on ${(/* @__PURE__ */ new Date()).toLocaleString()}</p>
2133
+ </div>
2134
+
2135
+ <div class="summary">
2136
+ <div class="card">
2137
+ <div class="metric">${summary.totalFiles}</div>
2138
+ <div class="label">Files Analyzed</div>
2139
+ </div>
2140
+ <div class="card">
2141
+ <div class="metric">${summary.totalTokens.toLocaleString()}</div>
2142
+ <div class="label">Total Tokens</div>
2143
+ </div>
2144
+ <div class="card">
2145
+ <div class="metric">${summary.avgContextBudget.toFixed(0)}</div>
2146
+ <div class="label">Avg Context Budget</div>
2147
+ </div>
2148
+ <div class="card">
2149
+ <div class="metric ${totalIssues > 0 ? "issue-major" : ""}">${totalIssues}</div>
2150
+ <div class="label">Total Issues</div>
2151
+ </div>
2152
+ </div>
2153
+
2154
+ ${totalIssues > 0 ? `
2155
+ <div class="card" style="margin-bottom: 30px;">
2156
+ <h2>\u26A0\uFE0F Issues Summary</h2>
2157
+ <p>
2158
+ <span class="issue-critical">\u{1F534} Critical: ${summary.criticalIssues}</span> &nbsp;
2159
+ <span class="issue-major">\u{1F7E1} Major: ${summary.majorIssues}</span> &nbsp;
2160
+ <span class="issue-minor">\u{1F535} Minor: ${summary.minorIssues}</span>
2161
+ </p>
2162
+ <p><strong>Potential Savings:</strong> ${summary.totalPotentialSavings.toLocaleString()} tokens</p>
2163
+ </div>
2164
+ ` : ""}
2165
+
2166
+ ${summary.fragmentedModules.length > 0 ? `
2167
+ <div class="card" style="margin-bottom: 30px;">
2168
+ <h2>\u{1F9E9} Fragmented Modules</h2>
2169
+ <table>
2170
+ <thead>
2171
+ <tr>
2172
+ <th>Domain</th>
2173
+ <th>Files</th>
2174
+ <th>Fragmentation</th>
2175
+ <th>Token Cost</th>
2176
+ </tr>
2177
+ </thead>
2178
+ <tbody>
2179
+ ${summary.fragmentedModules.map(
2180
+ (m) => `
2181
+ <tr>
2182
+ <td>${m.domain}</td>
2183
+ <td>${m.files.length}</td>
2184
+ <td>${(m.fragmentationScore * 100).toFixed(0)}%</td>
2185
+ <td>${m.totalTokens.toLocaleString()}</td>
2186
+ </tr>
2187
+ `
2188
+ ).join("")}
2189
+ </tbody>
2190
+ </table>
2191
+ </div>
2192
+ ` : ""}
2193
+
2194
+ ${summary.topExpensiveFiles.length > 0 ? `
2195
+ <div class="card" style="margin-bottom: 30px;">
2196
+ <h2>\u{1F4B8} Most Expensive Files</h2>
2197
+ <table>
2198
+ <thead>
2199
+ <tr>
2200
+ <th>File</th>
2201
+ <th>Context Budget</th>
2202
+ <th>Severity</th>
2203
+ </tr>
2204
+ </thead>
2205
+ <tbody>
2206
+ ${summary.topExpensiveFiles.map(
2207
+ (f) => `
2208
+ <tr>
2209
+ <td>${f.file}</td>
2210
+ <td>${f.contextBudget.toLocaleString()} tokens</td>
2211
+ <td class="issue-${f.severity}">${f.severity.toUpperCase()}</td>
2212
+ </tr>
2213
+ `
2214
+ ).join("")}
2215
+ </tbody>
2216
+ </table>
2217
+ </div>
2218
+ ` : ""}
2219
+
2220
+ <div class="footer">
2221
+ <p>Generated by <strong>@aiready/context-analyzer</strong></p>
2222
+ <p>Like AIReady? <a href="https://github.com/caopengau/aiready-context-analyzer">Star us on GitHub</a></p>
2223
+ <p>Found a bug? <a href="https://github.com/caopengau/aiready-context-analyzer/issues">Report it here</a></p>
2224
+ </div>
2225
+ </body>
2226
+ </html>`;
2227
+ }
2228
+ async function runInteractiveSetup(directory, current) {
2229
+ console.log(import_chalk.default.yellow("\u{1F9ED} Interactive mode: let\u2019s tailor the analysis."));
2230
+ const pkgPath = (0, import_path3.join)(directory, "package.json");
2231
+ let deps = {};
2232
+ if ((0, import_fs2.existsSync)(pkgPath)) {
2233
+ try {
2234
+ const pkg = JSON.parse((0, import_fs2.readFileSync)(pkgPath, "utf-8"));
2235
+ deps = { ...pkg.dependencies || {}, ...pkg.devDependencies || {} };
2236
+ } catch (e) {
2237
+ void e;
2238
+ }
2239
+ }
2240
+ const hasNextJs = (0, import_fs2.existsSync)((0, import_path3.join)(directory, ".next")) || !!deps["next"];
2241
+ const hasCDK = (0, import_fs2.existsSync)((0, import_path3.join)(directory, "cdk.out")) || !!deps["aws-cdk-lib"] || Object.keys(deps).some((d) => d.startsWith("@aws-cdk/"));
2242
+ const recommendedExcludes = new Set(current.exclude || []);
2243
+ if (hasNextJs && !Array.from(recommendedExcludes).some((p) => p.includes(".next"))) {
2244
+ recommendedExcludes.add("**/.next/**");
2245
+ }
2246
+ if (hasCDK && !Array.from(recommendedExcludes).some((p) => p.includes("cdk.out"))) {
2247
+ recommendedExcludes.add("**/cdk.out/**");
2248
+ }
2249
+ const { applyExcludes } = await (0, import_prompts.default)({
2250
+ type: "toggle",
2251
+ name: "applyExcludes",
2252
+ message: `Detected ${hasNextJs ? "Next.js " : ""}${hasCDK ? "AWS CDK " : ""}frameworks. Apply recommended excludes?`,
2253
+ initial: true,
2254
+ active: "yes",
2255
+ inactive: "no"
2256
+ });
2257
+ const nextOptions = { ...current };
2258
+ if (applyExcludes) {
2259
+ nextOptions.exclude = Array.from(recommendedExcludes);
2260
+ }
2261
+ const { focusArea } = await (0, import_prompts.default)({
2262
+ type: "select",
2263
+ name: "focusArea",
2264
+ message: "Which areas to focus?",
2265
+ choices: [
2266
+ { title: "Frontend (web app)", value: "frontend" },
2267
+ { title: "Backend (API/infra)", value: "backend" },
2268
+ { title: "Both", value: "both" }
2269
+ ],
2270
+ initial: 2
2271
+ });
2272
+ if (focusArea === "frontend") {
2273
+ nextOptions.include = ["**/*.{ts,tsx,js,jsx}"];
2274
+ nextOptions.exclude = Array.from(
2275
+ /* @__PURE__ */ new Set([
2276
+ ...nextOptions.exclude || [],
2277
+ "**/cdk.out/**",
2278
+ "**/infra/**",
2279
+ "**/server/**",
2280
+ "**/backend/**"
2281
+ ])
2282
+ );
2283
+ } else if (focusArea === "backend") {
2284
+ nextOptions.include = [
2285
+ "**/api/**",
2286
+ "**/server/**",
2287
+ "**/backend/**",
2288
+ "**/infra/**",
2289
+ "**/*.{ts,js,py,java}"
2290
+ ];
2291
+ }
2292
+ console.log(import_chalk.default.green("\u2713 Interactive configuration applied."));
2293
+ return nextOptions;
2294
+ }
2295
+
2079
2296
  // src/index.ts
2080
- import_core10.ToolRegistry.register(ContextAnalyzerProvider);
2297
+ import_core11.ToolRegistry.register(ContextAnalyzerProvider);
2081
2298
  // Annotate the CommonJS export names for ESM import in node:
2082
2299
  0 && (module.exports = {
2083
2300
  Classification,
@@ -2085,7 +2302,6 @@ import_core10.ToolRegistry.register(ContextAnalyzerProvider);
2085
2302
  adjustCohesionForClassification,
2086
2303
  adjustFragmentationForClassification,
2087
2304
  analyzeContext,
2088
- analyzeIssues,
2089
2305
  buildCoUsageMatrix,
2090
2306
  buildDependencyGraph,
2091
2307
  buildTypeGraph,
@@ -2102,10 +2318,12 @@ import_core10.ToolRegistry.register(ContextAnalyzerProvider);
2102
2318
  classifyFile,
2103
2319
  detectCircularDependencies,
2104
2320
  detectModuleClusters,
2321
+ displayConsoleReport,
2105
2322
  extractDomainKeywordsFromPaths,
2106
2323
  extractExports,
2107
2324
  findConsolidationCandidates,
2108
2325
  findSemanticClusters,
2326
+ generateHTMLReport,
2109
2327
  generateSummary,
2110
2328
  getClassificationRecommendations,
2111
2329
  getCoUsageData,
@@ -2124,5 +2342,6 @@ import_core10.ToolRegistry.register(ContextAnalyzerProvider);
2124
2342
  isSessionFile,
2125
2343
  isTypeDefinition,
2126
2344
  isUtilityModule,
2127
- mapScoreToRating
2345
+ mapScoreToRating,
2346
+ runInteractiveSetup
2128
2347
  });