@aiready/context-analyzer 0.21.22 → 0.21.24

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 (78) hide show
  1. package/.turbo/turbo-build.log +26 -25
  2. package/.turbo/turbo-lint.log +5 -5
  3. package/.turbo/turbo-test.log +91 -41
  4. package/coverage/clover.xml +2615 -1242
  5. package/coverage/coverage-final.json +30 -13
  6. package/coverage/dist/chunk-64U3PNO3.mjs.html +367 -0
  7. package/coverage/dist/chunk-J3MUOWHC.mjs.html +5326 -0
  8. package/coverage/dist/index.html +146 -0
  9. package/coverage/{classifier.ts.html → dist/index.mjs.html} +537 -912
  10. package/coverage/index.html +84 -189
  11. package/coverage/src/analyzer.ts.html +88 -0
  12. package/coverage/src/analyzers/index.html +116 -0
  13. package/coverage/src/analyzers/python-context.ts.html +910 -0
  14. package/coverage/{ast-utils.ts.html → src/ast-utils.ts.html} +84 -54
  15. package/coverage/src/classifier.ts.html +892 -0
  16. package/coverage/src/classify/classification-patterns.ts.html +307 -0
  17. package/coverage/src/classify/file-classifiers.ts.html +973 -0
  18. package/coverage/src/classify/index.html +131 -0
  19. package/coverage/{cluster-detector.ts.html → src/cluster-detector.ts.html} +154 -91
  20. package/coverage/{defaults.ts.html → src/defaults.ts.html} +74 -65
  21. package/coverage/{graph-builder.ts.html → src/graph-builder.ts.html} +268 -229
  22. package/coverage/src/index.html +341 -0
  23. package/coverage/{index.ts.html → src/index.ts.html} +70 -13
  24. package/coverage/{scoring.ts.html → src/issue-analyzer.ts.html} +201 -261
  25. package/coverage/src/mapper.ts.html +439 -0
  26. package/coverage/{metrics.ts.html → src/metrics.ts.html} +201 -132
  27. package/coverage/src/orchestrator.ts.html +493 -0
  28. package/coverage/{provider.ts.html → src/provider.ts.html} +21 -21
  29. package/coverage/{remediation.ts.html → src/remediation.ts.html} +112 -52
  30. package/coverage/src/report/console-report.ts.html +415 -0
  31. package/coverage/src/report/html-report.ts.html +361 -0
  32. package/coverage/src/report/index.html +146 -0
  33. package/coverage/src/report/interactive-setup.ts.html +373 -0
  34. package/coverage/src/scoring.ts.html +895 -0
  35. package/coverage/src/semantic/co-usage.ts.html +340 -0
  36. package/coverage/src/semantic/consolidation.ts.html +223 -0
  37. package/coverage/src/semantic/domain-inference.ts.html +859 -0
  38. package/coverage/src/semantic/index.html +161 -0
  39. package/coverage/src/semantic/type-graph.ts.html +163 -0
  40. package/coverage/{summary.ts.html → src/summary.ts.html} +155 -275
  41. package/coverage/{types.ts.html → src/types.ts.html} +133 -31
  42. package/coverage/src/utils/dependency-graph-utils.ts.html +463 -0
  43. package/coverage/src/utils/index.html +131 -0
  44. package/coverage/src/utils/string-utils.ts.html +148 -0
  45. package/dist/chunk-AMPK6SWS.mjs +1754 -0
  46. package/dist/chunk-BHCRDEE4.mjs +1745 -0
  47. package/dist/chunk-IKRP7ECY.mjs +1754 -0
  48. package/dist/chunk-J3MUOWHC.mjs +1747 -0
  49. package/dist/chunk-TWWPY7FD.mjs +1754 -0
  50. package/dist/chunk-Z5WY6A4P.mjs +1754 -0
  51. package/dist/cli.js +77 -185
  52. package/dist/cli.mjs +1 -1
  53. package/dist/index.d.mts +1 -1
  54. package/dist/index.d.ts +1 -1
  55. package/dist/index.js +73 -181
  56. package/dist/index.mjs +1 -1
  57. package/dist/python-context-BWDC4E5Z.mjs +162 -0
  58. package/package.json +3 -3
  59. package/src/__tests__/analyzer.test.ts +14 -14
  60. package/src/__tests__/auto-detection.test.ts +16 -16
  61. package/src/__tests__/consolidation.test.ts +247 -0
  62. package/src/__tests__/defaults.test.ts +121 -0
  63. package/src/__tests__/domain-inference.test.ts +420 -0
  64. package/src/__tests__/fragmentation-coupling.test.ts +4 -4
  65. package/src/__tests__/issue-analyzer.test.ts +155 -0
  66. package/src/__tests__/orchestrator.test.ts +143 -0
  67. package/src/__tests__/python-context.test.ts +100 -0
  68. package/src/__tests__/report/console-report.test.ts +292 -0
  69. package/src/__tests__/report/html-report.test.ts +243 -0
  70. package/src/__tests__/scoring.test.ts +17 -11
  71. package/src/analyzers/python-context.ts +2 -2
  72. package/src/ast-utils.ts +3 -3
  73. package/src/graph-builder.ts +4 -4
  74. package/src/mapper.ts +6 -0
  75. package/src/orchestrator.ts +1 -1
  76. package/src/report/html-report.ts +73 -181
  77. package/coverage/analyzer.ts.html +0 -1369
  78. package/coverage/semantic-analysis.ts.html +0 -1201
package/dist/index.js CHANGED
@@ -130,7 +130,7 @@ __export(python_context_exports, {
130
130
  });
131
131
  async function analyzePythonContext(files, rootDir) {
132
132
  const results = [];
133
- const parser = (0, import_core5.getParser)("dummy.py");
133
+ const parser = await (0, import_core5.getParser)("dummy.py");
134
134
  if (!parser) {
135
135
  console.warn("Python parser not available");
136
136
  return results;
@@ -194,7 +194,7 @@ async function analyzePythonContext(files, rootDir) {
194
194
  }
195
195
  async function buildPythonDependencyGraph(files, rootDir) {
196
196
  const graph = /* @__PURE__ */ new Map();
197
- const parser = (0, import_core5.getParser)("dummy.py");
197
+ const parser = await (0, import_core5.getParser)("dummy.py");
198
198
  if (!parser) return graph;
199
199
  for (const file of files) {
200
200
  try {
@@ -352,7 +352,7 @@ __export(index_exports, {
352
352
  runInteractiveSetup: () => runInteractiveSetup
353
353
  });
354
354
  module.exports = __toCommonJS(index_exports);
355
- var import_core11 = require("@aiready/core");
355
+ var import_core12 = require("@aiready/core");
356
356
 
357
357
  // src/provider.ts
358
358
  var import_core9 = require("@aiready/core");
@@ -552,9 +552,9 @@ function inferDomain(name, filePath, domainOptions, fileImports) {
552
552
  }
553
553
 
554
554
  // src/ast-utils.ts
555
- function extractExportsWithAST(content, filePath, domainOptions, fileImports) {
555
+ async function extractExportsWithAST(content, filePath, domainOptions, fileImports) {
556
556
  try {
557
- const { exports: astExports } = (0, import_core.parseFileExports)(content, filePath);
557
+ const { exports: astExports } = await (0, import_core.parseFileExports)(content, filePath);
558
558
  if (astExports.length === 0 && !isTestFile(filePath)) {
559
559
  return extractExports(content, filePath, domainOptions, fileImports);
560
560
  }
@@ -952,16 +952,16 @@ function extractDomainKeywordsFromPaths(files) {
952
952
  }
953
953
  return Array.from(folderNames);
954
954
  }
955
- function buildDependencyGraph(files, options) {
955
+ async function buildDependencyGraph(files, options) {
956
956
  const nodes = /* @__PURE__ */ new Map();
957
957
  const edges = /* @__PURE__ */ new Map();
958
958
  const autoDetectedKeywords = options?.domainKeywords ?? extractDomainKeywordsFromPaths(files);
959
959
  const allFilePaths = new Set(files.map((f) => f.file));
960
960
  for (const { file, content } of files) {
961
- const { imports: astImports } = (0, import_core4.parseFileExports)(content, file);
961
+ const { imports: astImports } = await (0, import_core4.parseFileExports)(content, file);
962
962
  const resolvedImports = astImports.map((i) => resolveImport(i.source, file, allFilePaths)).filter((path) => path !== null);
963
963
  const importSources = astImports.map((i) => i.source);
964
- const exports2 = extractExportsWithAST(
964
+ const exports2 = await extractExportsWithAST(
965
965
  content,
966
966
  file,
967
967
  { domainKeywords: autoDetectedKeywords },
@@ -1687,7 +1687,7 @@ async function analyzeContext(options) {
1687
1687
  content: await (0, import_core6.readFileContent)(file)
1688
1688
  }))
1689
1689
  );
1690
- const graph = buildDependencyGraph(
1690
+ const graph = await buildDependencyGraph(
1691
1691
  fileContents.filter((f) => !f.file.toLowerCase().endsWith(".py"))
1692
1692
  );
1693
1693
  let pythonResults = [];
@@ -2204,182 +2204,74 @@ function displayConsoleReport(summary, results, maxResults = 10) {
2204
2204
  }
2205
2205
 
2206
2206
  // src/report/html-report.ts
2207
+ var import_core11 = require("@aiready/core");
2207
2208
  function generateHTMLReport(summary, results) {
2208
2209
  const totalIssues = summary.criticalIssues + summary.majorIssues + summary.minorIssues;
2209
2210
  void results;
2210
- return `<!DOCTYPE html>
2211
- <html lang="en">
2212
- <head>
2213
- <meta charset="UTF-8">
2214
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
2215
- <title>aiready Context Analysis Report</title>
2216
- <style>
2217
- body {
2218
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
2219
- line-height: 1.6;
2220
- color: #333;
2221
- max-width: 1200px;
2222
- margin: 0 auto;
2223
- padding: 20px;
2224
- background-color: #f5f5f5;
2225
- }
2226
- h1, h2, h3 { color: #2c3e50; }
2227
- .header {
2228
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
2229
- color: white;
2230
- padding: 30px;
2231
- border-radius: 8px;
2232
- margin-bottom: 30px;
2233
- }
2234
- .summary {
2235
- display: grid;
2236
- grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
2237
- gap: 20px;
2238
- margin-bottom: 30px;
2239
- }
2240
- .card {
2241
- background: white;
2242
- padding: 20px;
2243
- border-radius: 8px;
2244
- box-shadow: 0 2px 4px rgba(0,0,0,0.1);
2245
- }
2246
- .metric {
2247
- font-size: 2em;
2248
- font-weight: bold;
2249
- color: #667eea;
2250
- }
2251
- .label {
2252
- color: #666;
2253
- font-size: 0.9em;
2254
- margin-top: 5px;
2255
- }
2256
- .issue-critical { color: #e74c3c; }
2257
- .issue-major { color: #f39c12; }
2258
- .issue-minor { color: #3498db; }
2259
- table {
2260
- width: 100%;
2261
- border-collapse: collapse;
2262
- background: white;
2263
- border-radius: 8px;
2264
- overflow: hidden;
2265
- box-shadow: 0 2px 4px rgba(0,0,0,0.1);
2266
- }
2267
- th, td {
2268
- padding: 12px;
2269
- text-align: left;
2270
- border-bottom: 1px solid #eee;
2271
- }
2272
- th {
2273
- background-color: #667eea;
2274
- color: white;
2275
- font-weight: 600;
2276
- }
2277
- tr:hover { background-color: #f8f9fa; }
2278
- .footer {
2279
- text-align: center;
2280
- margin-top: 40px;
2281
- padding: 20px;
2282
- color: #666;
2283
- font-size: 0.9em;
2211
+ const head = (0, import_core11.generateReportHead)("AIReady Context Analysis Report");
2212
+ const stats = (0, import_core11.generateStatCards)([
2213
+ { value: summary.totalFiles, label: "Files Analyzed" },
2214
+ { value: summary.totalTokens.toLocaleString(), label: "Total Tokens" },
2215
+ { value: summary.avgContextBudget.toFixed(0), label: "Avg Context Budget" },
2216
+ {
2217
+ value: totalIssues,
2218
+ label: "Total Issues",
2219
+ color: totalIssues > 0 ? "#f39c12" : void 0
2284
2220
  }
2285
- </style>
2286
- </head>
2221
+ ]);
2222
+ const hero = (0, import_core11.generateReportHero)(
2223
+ "\u{1F50D} AIReady Context Analysis Report",
2224
+ `Generated on ${(/* @__PURE__ */ new Date()).toLocaleString()}`
2225
+ );
2226
+ let body = `${hero}
2227
+ ${stats}`;
2228
+ if (totalIssues > 0) {
2229
+ body += (0, import_core11.generateIssueSummary)(
2230
+ summary.criticalIssues,
2231
+ summary.majorIssues,
2232
+ summary.minorIssues,
2233
+ summary.totalPotentialSavings
2234
+ );
2235
+ }
2236
+ if (summary.fragmentedModules.length > 0) {
2237
+ const fragmentedRows = summary.fragmentedModules.map((m) => [
2238
+ m.domain,
2239
+ String(m.files.length),
2240
+ `${(m.fragmentationScore * 100).toFixed(0)}%`,
2241
+ m.totalTokens.toLocaleString()
2242
+ ]);
2243
+ body += (0, import_core11.wrapInCard)(
2244
+ (0, import_core11.generateTable)({
2245
+ headers: ["Domain", "Files", "Fragmentation", "Token Cost"],
2246
+ rows: fragmentedRows
2247
+ }),
2248
+ "\u{1F9E9} Fragmented Modules"
2249
+ );
2250
+ }
2251
+ if (summary.topExpensiveFiles.length > 0) {
2252
+ const expensiveRows = summary.topExpensiveFiles.map((f) => [
2253
+ f.file,
2254
+ `${f.contextBudget.toLocaleString()} tokens`,
2255
+ `<span class="issue-${f.severity}">${f.severity.toUpperCase()}</span>`
2256
+ ]);
2257
+ body += (0, import_core11.wrapInCard)(
2258
+ (0, import_core11.generateTable)({
2259
+ headers: ["File", "Context Budget", "Severity"],
2260
+ rows: expensiveRows
2261
+ }),
2262
+ "\u{1F4B8} Most Expensive Files"
2263
+ );
2264
+ }
2265
+ const footer = (0, import_core11.generateReportFooter)({
2266
+ title: "Context Analysis Report",
2267
+ packageName: "context-analyzer",
2268
+ packageUrl: "https://github.com/caopengau/aiready-context-analyzer",
2269
+ bugUrl: "https://github.com/caopengau/aiready-context-analyzer/issues"
2270
+ });
2271
+ return `${head}
2287
2272
  <body>
2288
- <div class="header">
2289
- <h1>\u{1F50D} AIReady Context Analysis Report</h1>
2290
- <p>Generated on ${(/* @__PURE__ */ new Date()).toLocaleString()}</p>
2291
- </div>
2292
-
2293
- <div class="summary">
2294
- <div class="card">
2295
- <div class="metric">${summary.totalFiles}</div>
2296
- <div class="label">Files Analyzed</div>
2297
- </div>
2298
- <div class="card">
2299
- <div class="metric">${summary.totalTokens.toLocaleString()}</div>
2300
- <div class="label">Total Tokens</div>
2301
- </div>
2302
- <div class="card">
2303
- <div class="metric">${summary.avgContextBudget.toFixed(0)}</div>
2304
- <div class="label">Avg Context Budget</div>
2305
- </div>
2306
- <div class="card">
2307
- <div class="metric ${totalIssues > 0 ? "issue-major" : ""}">${totalIssues}</div>
2308
- <div class="label">Total Issues</div>
2309
- </div>
2310
- </div>
2311
-
2312
- ${totalIssues > 0 ? `
2313
- <div class="card" style="margin-bottom: 30px;">
2314
- <h2>\u26A0\uFE0F Issues Summary</h2>
2315
- <p>
2316
- <span class="issue-critical">\u{1F534} Critical: ${summary.criticalIssues}</span> &nbsp;
2317
- <span class="issue-major">\u{1F7E1} Major: ${summary.majorIssues}</span> &nbsp;
2318
- <span class="issue-minor">\u{1F535} Minor: ${summary.minorIssues}</span>
2319
- </p>
2320
- <p><strong>Potential Savings:</strong> ${summary.totalPotentialSavings.toLocaleString()} tokens</p>
2321
- </div>
2322
- ` : ""}
2323
-
2324
- ${summary.fragmentedModules.length > 0 ? `
2325
- <div class="card" style="margin-bottom: 30px;">
2326
- <h2>\u{1F9E9} Fragmented Modules</h2>
2327
- <table>
2328
- <thead>
2329
- <tr>
2330
- <th>Domain</th>
2331
- <th>Files</th>
2332
- <th>Fragmentation</th>
2333
- <th>Token Cost</th>
2334
- </tr>
2335
- </thead>
2336
- <tbody>
2337
- ${summary.fragmentedModules.map(
2338
- (m) => `
2339
- <tr>
2340
- <td>${m.domain}</td>
2341
- <td>${m.files.length}</td>
2342
- <td>${(m.fragmentationScore * 100).toFixed(0)}%</td>
2343
- <td>${m.totalTokens.toLocaleString()}</td>
2344
- </tr>
2345
- `
2346
- ).join("")}
2347
- </tbody>
2348
- </table>
2349
- </div>
2350
- ` : ""}
2351
-
2352
- ${summary.topExpensiveFiles.length > 0 ? `
2353
- <div class="card" style="margin-bottom: 30px;">
2354
- <h2>\u{1F4B8} Most Expensive Files</h2>
2355
- <table>
2356
- <thead>
2357
- <tr>
2358
- <th>File</th>
2359
- <th>Context Budget</th>
2360
- <th>Severity</th>
2361
- </tr>
2362
- </thead>
2363
- <tbody>
2364
- ${summary.topExpensiveFiles.map(
2365
- (f) => `
2366
- <tr>
2367
- <td>${f.file}</td>
2368
- <td>${f.contextBudget.toLocaleString()} tokens</td>
2369
- <td class="issue-${f.severity}">${f.severity.toUpperCase()}</td>
2370
- </tr>
2371
- `
2372
- ).join("")}
2373
- </tbody>
2374
- </table>
2375
- </div>
2376
- ` : ""}
2377
-
2378
- <div class="footer">
2379
- <p>Generated by <strong>@aiready/context-analyzer</strong></p>
2380
- <p>Like AIReady? <a href="https://github.com/caopengau/aiready-context-analyzer">Star us on GitHub</a></p>
2381
- <p>Found a bug? <a href="https://github.com/caopengau/aiready-context-analyzer/issues">Report it here</a></p>
2382
- </div>
2273
+ ${body}
2274
+ ${footer}
2383
2275
  </body>
2384
2276
  </html>`;
2385
2277
  }
@@ -2458,7 +2350,7 @@ async function runInteractiveSetup(directory, current) {
2458
2350
  }
2459
2351
 
2460
2352
  // src/index.ts
2461
- import_core11.ToolRegistry.register(ContextAnalyzerProvider);
2353
+ import_core12.ToolRegistry.register(ContextAnalyzerProvider);
2462
2354
  // Annotate the CommonJS export names for ESM import in node:
2463
2355
  0 && (module.exports = {
2464
2356
  BARREL_EXPORT_MIN_EXPORTS,
package/dist/index.mjs CHANGED
@@ -52,7 +52,7 @@ import {
52
52
  isTypeDefinition,
53
53
  isUtilityModule,
54
54
  runInteractiveSetup
55
- } from "./chunk-CBWM3EK5.mjs";
55
+ } from "./chunk-Z5WY6A4P.mjs";
56
56
  import "./chunk-64U3PNO3.mjs";
57
57
 
58
58
  // src/index.ts
@@ -0,0 +1,162 @@
1
+ import {
2
+ calculateImportDepthFromEdges,
3
+ detectGraphCyclesFromFile
4
+ } from "./chunk-64U3PNO3.mjs";
5
+
6
+ // src/analyzers/python-context.ts
7
+ import { getParser, estimateTokens } from "@aiready/core";
8
+ import { resolve, relative, dirname, join } from "path";
9
+ import fs from "fs";
10
+ async function analyzePythonContext(files, rootDir) {
11
+ const results = [];
12
+ const parser = await getParser("dummy.py");
13
+ if (!parser) {
14
+ console.warn("Python parser not available");
15
+ return results;
16
+ }
17
+ const pythonFiles = files.filter((f) => f.toLowerCase().endsWith(".py"));
18
+ void relative;
19
+ void join;
20
+ const dependencyGraph = await buildPythonDependencyGraph(
21
+ pythonFiles,
22
+ rootDir
23
+ );
24
+ for (const file of pythonFiles) {
25
+ try {
26
+ const code = await fs.promises.readFile(file, "utf-8");
27
+ const result = parser.parse(code, file);
28
+ const imports = result.imports.map((imp) => ({
29
+ source: imp.source,
30
+ specifiers: imp.specifiers,
31
+ isRelative: imp.source.startsWith("."),
32
+ resolvedPath: resolvePythonImport(file, imp.source, rootDir)
33
+ }));
34
+ const exports = result.exports.map((exp) => ({
35
+ name: exp.name,
36
+ type: exp.type
37
+ }));
38
+ const linesOfCode = code.split("\n").length;
39
+ const importDepth = calculateImportDepthFromEdges(
40
+ file,
41
+ dependencyGraph,
42
+ /* @__PURE__ */ new Set()
43
+ );
44
+ const contextBudget = estimateContextBudget(
45
+ code,
46
+ imports,
47
+ dependencyGraph
48
+ );
49
+ const cohesion = calculatePythonCohesion(exports, imports);
50
+ const circularDependencies = detectGraphCyclesFromFile(
51
+ file,
52
+ dependencyGraph
53
+ ).map((cycle) => cycle.join(" -> "));
54
+ results.push({
55
+ file,
56
+ importDepth,
57
+ contextBudget,
58
+ cohesion,
59
+ imports,
60
+ exports,
61
+ metrics: {
62
+ linesOfCode,
63
+ importCount: imports.length,
64
+ exportCount: exports.length,
65
+ circularDependencies
66
+ }
67
+ });
68
+ } catch (error) {
69
+ console.warn(`Failed to analyze ${file}:`, error);
70
+ }
71
+ }
72
+ return results;
73
+ }
74
+ async function buildPythonDependencyGraph(files, rootDir) {
75
+ const graph = /* @__PURE__ */ new Map();
76
+ const parser = await getParser("dummy.py");
77
+ if (!parser) return graph;
78
+ for (const file of files) {
79
+ try {
80
+ const code = await fs.promises.readFile(file, "utf-8");
81
+ const result = parser.parse(code, file);
82
+ const dependencies = /* @__PURE__ */ new Set();
83
+ for (const imp of result.imports) {
84
+ const resolved = resolvePythonImport(file, imp.source, rootDir);
85
+ if (resolved && files.includes(resolved)) {
86
+ dependencies.add(resolved);
87
+ }
88
+ }
89
+ graph.set(file, dependencies);
90
+ } catch (error) {
91
+ void error;
92
+ }
93
+ }
94
+ return graph;
95
+ }
96
+ function resolvePythonImport(fromFile, importPath, rootDir) {
97
+ const dir = dirname(fromFile);
98
+ if (importPath.startsWith(".")) {
99
+ const parts = importPath.split(".");
100
+ let upCount = 0;
101
+ while (parts[0] === "") {
102
+ upCount++;
103
+ parts.shift();
104
+ }
105
+ let targetDir = dir;
106
+ for (let i = 0; i < upCount - 1; i++) {
107
+ targetDir = dirname(targetDir);
108
+ }
109
+ const modulePath = parts.join("/");
110
+ const possiblePaths = [
111
+ resolve(targetDir, `${modulePath}.py`),
112
+ resolve(targetDir, modulePath, "__init__.py")
113
+ ];
114
+ for (const path of possiblePaths) {
115
+ if (fs.existsSync(path)) {
116
+ return path;
117
+ }
118
+ }
119
+ } else {
120
+ const modulePath = importPath.replace(/\./g, "/");
121
+ const possiblePaths = [
122
+ resolve(rootDir, `${modulePath}.py`),
123
+ resolve(rootDir, modulePath, "__init__.py")
124
+ ];
125
+ for (const path of possiblePaths) {
126
+ if (fs.existsSync(path)) {
127
+ return path;
128
+ }
129
+ }
130
+ }
131
+ return void 0;
132
+ }
133
+ function estimateContextBudget(code, imports, dependencyGraph) {
134
+ void dependencyGraph;
135
+ let budget = estimateTokens(code);
136
+ const avgTokensPerDep = 500;
137
+ budget += imports.length * avgTokensPerDep;
138
+ return budget;
139
+ }
140
+ function calculatePythonCohesion(exports, imports) {
141
+ if (exports.length === 0) return 1;
142
+ const exportCount = exports.length;
143
+ const importCount = imports.length;
144
+ let cohesion = 1;
145
+ if (exportCount > 10) {
146
+ cohesion *= 0.6;
147
+ } else if (exportCount > 5) {
148
+ cohesion *= 0.8;
149
+ }
150
+ if (exportCount > 0) {
151
+ const ratio = importCount / exportCount;
152
+ if (ratio > 2) {
153
+ cohesion *= 1.1;
154
+ } else if (ratio < 0.5) {
155
+ cohesion *= 0.9;
156
+ }
157
+ }
158
+ return Math.min(1, Math.max(0, cohesion));
159
+ }
160
+ export {
161
+ analyzePythonContext
162
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aiready/context-analyzer",
3
- "version": "0.21.22",
3
+ "version": "0.21.24",
4
4
  "description": "AI context window cost analysis - detect fragmented code, deep import chains, and expensive context budgets",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -49,7 +49,7 @@
49
49
  "commander": "^14.0.0",
50
50
  "chalk": "^5.3.0",
51
51
  "prompts": "^2.4.2",
52
- "@aiready/core": "0.23.19"
52
+ "@aiready/core": "0.23.21"
53
53
  },
54
54
  "devDependencies": {
55
55
  "@types/node": "^24.0.0",
@@ -65,7 +65,7 @@
65
65
  "scripts": {
66
66
  "build": "tsup src/index.ts src/cli.ts --format cjs,esm --dts",
67
67
  "dev": "tsup src/index.ts src/cli.ts --format cjs,esm --dts --watch",
68
- "test": "vitest run",
68
+ "test": "vitest run --exclude \"**/dist/**\"",
69
69
  "lint": "eslint src",
70
70
  "clean": "rm -rf dist",
71
71
  "release": "pnpm build && pnpm publish --no-git-checks"
@@ -10,7 +10,7 @@ import {
10
10
  } from '../index';
11
11
 
12
12
  describe('buildDependencyGraph', () => {
13
- it('should build a basic dependency graph', () => {
13
+ it('should build a basic dependency graph', async () => {
14
14
  const files = [
15
15
  {
16
16
  file: 'a.ts',
@@ -27,7 +27,7 @@ export const b = a + 1;
27
27
  },
28
28
  ];
29
29
 
30
- const graph = buildDependencyGraph(files);
30
+ const graph = await buildDependencyGraph(files);
31
31
 
32
32
  expect(graph.nodes.size).toBe(2);
33
33
  expect(graph.edges.get('b.ts')?.has('a.ts')).toBe(true);
@@ -35,7 +35,7 @@ export const b = a + 1;
35
35
  });
36
36
 
37
37
  describe('calculateImportDepth', () => {
38
- it('should calculate import depth correctly', () => {
38
+ it('should calculate import depth correctly', async () => {
39
39
  const files = [
40
40
  { file: 'a.ts', content: 'export const a = 1;' },
41
41
  {
@@ -48,14 +48,14 @@ describe('calculateImportDepth', () => {
48
48
  },
49
49
  ];
50
50
 
51
- const graph = buildDependencyGraph(files);
51
+ const graph = await buildDependencyGraph(files);
52
52
 
53
53
  expect(calculateImportDepth('a.ts', graph)).toBe(0);
54
54
  expect(calculateImportDepth('b.ts', graph)).toBe(1);
55
55
  expect(calculateImportDepth('c.ts', graph)).toBe(2);
56
56
  });
57
57
 
58
- it('should handle circular dependencies gracefully', () => {
58
+ it('should handle circular dependencies gracefully', async () => {
59
59
  const files = [
60
60
  {
61
61
  file: 'a.ts',
@@ -67,7 +67,7 @@ describe('calculateImportDepth', () => {
67
67
  },
68
68
  ];
69
69
 
70
- const graph = buildDependencyGraph(files);
70
+ const graph = await buildDependencyGraph(files);
71
71
 
72
72
  // Should not infinite loop
73
73
  const depth = calculateImportDepth('a.ts', graph);
@@ -77,7 +77,7 @@ describe('calculateImportDepth', () => {
77
77
  });
78
78
 
79
79
  describe('getTransitiveDependencies', () => {
80
- it('should get all transitive dependencies', () => {
80
+ it('should get all transitive dependencies', async () => {
81
81
  const files = [
82
82
  { file: 'a.ts', content: 'export const a = 1;' },
83
83
  {
@@ -90,7 +90,7 @@ describe('getTransitiveDependencies', () => {
90
90
  },
91
91
  ];
92
92
 
93
- const graph = buildDependencyGraph(files);
93
+ const graph = await buildDependencyGraph(files);
94
94
  const deps = getTransitiveDependencies('c.ts', graph);
95
95
 
96
96
  expect(deps).toContain('b.ts');
@@ -100,7 +100,7 @@ describe('getTransitiveDependencies', () => {
100
100
  });
101
101
 
102
102
  describe('calculateContextBudget', () => {
103
- it('should calculate total token cost including dependencies', () => {
103
+ it('should calculate total token cost including dependencies', async () => {
104
104
  const files = [
105
105
  { file: 'a.ts', content: 'export const a = 1;'.repeat(10) }, // ~40 tokens
106
106
  {
@@ -109,7 +109,7 @@ describe('calculateContextBudget', () => {
109
109
  }, // ~60 tokens
110
110
  ];
111
111
 
112
- const graph = buildDependencyGraph(files);
112
+ const graph = await buildDependencyGraph(files);
113
113
  const budget = calculateContextBudget('b.ts', graph);
114
114
 
115
115
  // Should include both files' tokens
@@ -118,7 +118,7 @@ describe('calculateContextBudget', () => {
118
118
  });
119
119
 
120
120
  describe('detectCircularDependencies', () => {
121
- it('should detect circular dependencies', () => {
121
+ it('should detect circular dependencies', async () => {
122
122
  const files = [
123
123
  {
124
124
  file: 'a.ts',
@@ -130,13 +130,13 @@ describe('detectCircularDependencies', () => {
130
130
  },
131
131
  ];
132
132
 
133
- const graph = buildDependencyGraph(files);
133
+ const graph = await buildDependencyGraph(files);
134
134
  const cycles = detectCircularDependencies(graph);
135
135
 
136
136
  expect(cycles.length).toBeGreaterThan(0);
137
137
  });
138
138
 
139
- it('should return empty for no circular dependencies', () => {
139
+ it('should return empty for no circular dependencies', async () => {
140
140
  const files = [
141
141
  { file: 'a.ts', content: 'export const a = 1;' },
142
142
  {
@@ -145,7 +145,7 @@ describe('detectCircularDependencies', () => {
145
145
  },
146
146
  ];
147
147
 
148
- const graph = buildDependencyGraph(files);
148
+ const graph = await buildDependencyGraph(files);
149
149
  const cycles = detectCircularDependencies(graph);
150
150
 
151
151
  expect(cycles.length).toBe(0);