@aiready/context-analyzer 0.21.23 → 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.
package/dist/cli.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 {
@@ -490,9 +490,9 @@ function inferDomain(name, filePath, domainOptions, fileImports) {
490
490
  }
491
491
 
492
492
  // src/ast-utils.ts
493
- function extractExportsWithAST(content, filePath, domainOptions, fileImports) {
493
+ async function extractExportsWithAST(content, filePath, domainOptions, fileImports) {
494
494
  try {
495
- const { exports: astExports } = (0, import_core.parseFileExports)(content, filePath);
495
+ const { exports: astExports } = await (0, import_core.parseFileExports)(content, filePath);
496
496
  if (astExports.length === 0 && !isTestFile(filePath)) {
497
497
  return extractExports(content, filePath, domainOptions, fileImports);
498
498
  }
@@ -824,16 +824,16 @@ function extractDomainKeywordsFromPaths(files) {
824
824
  }
825
825
  return Array.from(folderNames);
826
826
  }
827
- function buildDependencyGraph(files, options) {
827
+ async function buildDependencyGraph(files, options) {
828
828
  const nodes = /* @__PURE__ */ new Map();
829
829
  const edges = /* @__PURE__ */ new Map();
830
830
  const autoDetectedKeywords = options?.domainKeywords ?? extractDomainKeywordsFromPaths(files);
831
831
  const allFilePaths = new Set(files.map((f) => f.file));
832
832
  for (const { file, content } of files) {
833
- const { imports: astImports } = (0, import_core4.parseFileExports)(content, file);
833
+ const { imports: astImports } = await (0, import_core4.parseFileExports)(content, file);
834
834
  const resolvedImports = astImports.map((i) => resolveImport(i.source, file, allFilePaths)).filter((path) => path !== null);
835
835
  const importSources = astImports.map((i) => i.source);
836
- const exports2 = extractExportsWithAST(
836
+ const exports2 = await extractExportsWithAST(
837
837
  content,
838
838
  file,
839
839
  { domainKeywords: autoDetectedKeywords },
@@ -1511,7 +1511,7 @@ async function analyzeContext(options) {
1511
1511
  content: await (0, import_core6.readFileContent)(file)
1512
1512
  }))
1513
1513
  );
1514
- const graph = buildDependencyGraph(
1514
+ const graph = await buildDependencyGraph(
1515
1515
  fileContents.filter((f) => !f.file.toLowerCase().endsWith(".py"))
1516
1516
  );
1517
1517
  let pythonResults = [];
@@ -1741,21 +1741,19 @@ function generateHTMLReport(summary, results) {
1741
1741
  color: totalIssues > 0 ? "#f39c12" : void 0
1742
1742
  }
1743
1743
  ]);
1744
- let body = `<div style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 30px; border-radius: 8px; margin-bottom: 30px;">
1745
- <h1 style="border: none; color: white; margin: 0;">\u{1F50D} AIReady Context Analysis Report</h1>
1746
- <p style="margin: 10px 0 0 0;">Generated on ${(/* @__PURE__ */ new Date()).toLocaleString()}</p>
1747
- </div>
1744
+ const hero = (0, import_core8.generateReportHero)(
1745
+ "\u{1F50D} AIReady Context Analysis Report",
1746
+ `Generated on ${(/* @__PURE__ */ new Date()).toLocaleString()}`
1747
+ );
1748
+ let body = `${hero}
1748
1749
  ${stats}`;
1749
1750
  if (totalIssues > 0) {
1750
- body += `<div class="card" style="margin-bottom: 30px;">
1751
- <h2>\u26A0\uFE0F Issues Summary</h2>
1752
- <p>
1753
- <span class="critical">\u{1F534} Critical: ${summary.criticalIssues}</span> &nbsp;
1754
- <span class="major">\u{1F7E1} Major: ${summary.majorIssues}</span> &nbsp;
1755
- <span class="minor">\u{1F535} Minor: ${summary.minorIssues}</span>
1756
- </p>
1757
- <p><strong>Potential Savings:</strong> ${summary.totalPotentialSavings.toLocaleString()} tokens</p>
1758
- </div>`;
1751
+ body += (0, import_core8.generateIssueSummary)(
1752
+ summary.criticalIssues,
1753
+ summary.majorIssues,
1754
+ summary.minorIssues,
1755
+ summary.totalPotentialSavings
1756
+ );
1759
1757
  }
1760
1758
  if (summary.fragmentedModules.length > 0) {
1761
1759
  const fragmentedRows = summary.fragmentedModules.map((m) => [
@@ -1764,10 +1762,13 @@ ${stats}`;
1764
1762
  `${(m.fragmentationScore * 100).toFixed(0)}%`,
1765
1763
  m.totalTokens.toLocaleString()
1766
1764
  ]);
1767
- body += `<div class="card" style="margin-bottom: 30px;">
1768
- <h2>\u{1F9E9} Fragmented Modules</h2>
1769
- ${(0, import_core8.generateTable)({ headers: ["Domain", "Files", "Fragmentation", "Token Cost"], rows: fragmentedRows })}
1770
- </div>`;
1765
+ body += (0, import_core8.wrapInCard)(
1766
+ (0, import_core8.generateTable)({
1767
+ headers: ["Domain", "Files", "Fragmentation", "Token Cost"],
1768
+ rows: fragmentedRows
1769
+ }),
1770
+ "\u{1F9E9} Fragmented Modules"
1771
+ );
1771
1772
  }
1772
1773
  if (summary.topExpensiveFiles.length > 0) {
1773
1774
  const expensiveRows = summary.topExpensiveFiles.map((f) => [
@@ -1775,10 +1776,13 @@ ${stats}`;
1775
1776
  `${f.contextBudget.toLocaleString()} tokens`,
1776
1777
  `<span class="issue-${f.severity}">${f.severity.toUpperCase()}</span>`
1777
1778
  ]);
1778
- body += `<div class="card" style="margin-bottom: 30px;">
1779
- <h2>\u{1F4B8} Most Expensive Files</h2>
1780
- ${(0, import_core8.generateTable)({ headers: ["File", "Context Budget", "Severity"], rows: expensiveRows })}
1781
- </div>`;
1779
+ body += (0, import_core8.wrapInCard)(
1780
+ (0, import_core8.generateTable)({
1781
+ headers: ["File", "Context Budget", "Severity"],
1782
+ rows: expensiveRows
1783
+ }),
1784
+ "\u{1F4B8} Most Expensive Files"
1785
+ );
1782
1786
  }
1783
1787
  const footer = (0, import_core8.generateReportFooter)({
1784
1788
  title: "Context Analysis Report",
package/dist/cli.mjs CHANGED
@@ -5,7 +5,7 @@ import {
5
5
  generateHTMLReport,
6
6
  generateSummary,
7
7
  runInteractiveSetup
8
- } from "./chunk-J3MUOWHC.mjs";
8
+ } from "./chunk-Z5WY6A4P.mjs";
9
9
  import "./chunk-64U3PNO3.mjs";
10
10
 
11
11
  // src/cli.ts
package/dist/index.d.mts CHANGED
@@ -196,7 +196,7 @@ declare function extractDomainKeywordsFromPaths(files: FileContent[]): string[];
196
196
  */
197
197
  declare function buildDependencyGraph(files: FileContent[], options?: {
198
198
  domainKeywords?: string[];
199
- }): DependencyGraph;
199
+ }): Promise<DependencyGraph>;
200
200
  /**
201
201
  * Calculate the maximum depth of the import tree for a specific file.
202
202
  *
package/dist/index.d.ts CHANGED
@@ -196,7 +196,7 @@ declare function extractDomainKeywordsFromPaths(files: FileContent[]): string[];
196
196
  */
197
197
  declare function buildDependencyGraph(files: FileContent[], options?: {
198
198
  domainKeywords?: string[];
199
- }): DependencyGraph;
199
+ }): Promise<DependencyGraph>;
200
200
  /**
201
201
  * Calculate the maximum depth of the import tree for a specific file.
202
202
  *
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 {
@@ -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 = [];
@@ -2219,21 +2219,19 @@ function generateHTMLReport(summary, results) {
2219
2219
  color: totalIssues > 0 ? "#f39c12" : void 0
2220
2220
  }
2221
2221
  ]);
2222
- let body = `<div style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 30px; border-radius: 8px; margin-bottom: 30px;">
2223
- <h1 style="border: none; color: white; margin: 0;">\u{1F50D} AIReady Context Analysis Report</h1>
2224
- <p style="margin: 10px 0 0 0;">Generated on ${(/* @__PURE__ */ new Date()).toLocaleString()}</p>
2225
- </div>
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}
2226
2227
  ${stats}`;
2227
2228
  if (totalIssues > 0) {
2228
- body += `<div class="card" style="margin-bottom: 30px;">
2229
- <h2>\u26A0\uFE0F Issues Summary</h2>
2230
- <p>
2231
- <span class="critical">\u{1F534} Critical: ${summary.criticalIssues}</span> &nbsp;
2232
- <span class="major">\u{1F7E1} Major: ${summary.majorIssues}</span> &nbsp;
2233
- <span class="minor">\u{1F535} Minor: ${summary.minorIssues}</span>
2234
- </p>
2235
- <p><strong>Potential Savings:</strong> ${summary.totalPotentialSavings.toLocaleString()} tokens</p>
2236
- </div>`;
2229
+ body += (0, import_core11.generateIssueSummary)(
2230
+ summary.criticalIssues,
2231
+ summary.majorIssues,
2232
+ summary.minorIssues,
2233
+ summary.totalPotentialSavings
2234
+ );
2237
2235
  }
2238
2236
  if (summary.fragmentedModules.length > 0) {
2239
2237
  const fragmentedRows = summary.fragmentedModules.map((m) => [
@@ -2242,10 +2240,13 @@ ${stats}`;
2242
2240
  `${(m.fragmentationScore * 100).toFixed(0)}%`,
2243
2241
  m.totalTokens.toLocaleString()
2244
2242
  ]);
2245
- body += `<div class="card" style="margin-bottom: 30px;">
2246
- <h2>\u{1F9E9} Fragmented Modules</h2>
2247
- ${(0, import_core11.generateTable)({ headers: ["Domain", "Files", "Fragmentation", "Token Cost"], rows: fragmentedRows })}
2248
- </div>`;
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
+ );
2249
2250
  }
2250
2251
  if (summary.topExpensiveFiles.length > 0) {
2251
2252
  const expensiveRows = summary.topExpensiveFiles.map((f) => [
@@ -2253,10 +2254,13 @@ ${stats}`;
2253
2254
  `${f.contextBudget.toLocaleString()} tokens`,
2254
2255
  `<span class="issue-${f.severity}">${f.severity.toUpperCase()}</span>`
2255
2256
  ]);
2256
- body += `<div class="card" style="margin-bottom: 30px;">
2257
- <h2>\u{1F4B8} Most Expensive Files</h2>
2258
- ${(0, import_core11.generateTable)({ headers: ["File", "Context Budget", "Severity"], rows: expensiveRows })}
2259
- </div>`;
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
+ );
2260
2264
  }
2261
2265
  const footer = (0, import_core11.generateReportFooter)({
2262
2266
  title: "Context Analysis Report",
package/dist/index.mjs CHANGED
@@ -52,7 +52,7 @@ import {
52
52
  isTypeDefinition,
53
53
  isUtilityModule,
54
54
  runInteractiveSetup
55
- } from "./chunk-J3MUOWHC.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.23",
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.20"
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);