@aiready/context-analyzer 0.22.15 → 0.22.17

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 (47) hide show
  1. package/.turbo/turbo-build.log +18 -18
  2. package/.turbo/turbo-format-check.log +1 -1
  3. package/.turbo/turbo-lint.log +1 -1
  4. package/.turbo/turbo-test.log +408 -30
  5. package/.turbo/turbo-type-check.log +1 -1
  6. package/dist/chunk-2LEHY2GV.mjs +1287 -0
  7. package/dist/chunk-P2ZQGQAO.mjs +1282 -0
  8. package/dist/chunk-QGI23DBA.mjs +1282 -0
  9. package/dist/chunk-QTB4KYCX.mjs +1260 -0
  10. package/dist/chunk-RQ5BQLT6.mjs +102 -0
  11. package/dist/chunk-VYFHSGV6.mjs +1283 -0
  12. package/dist/chunk-WLXLBWDU.mjs +96 -0
  13. package/dist/chunk-XDYPMFCH.mjs +1250 -0
  14. package/dist/cli-action-332WE54N.mjs +95 -0
  15. package/dist/cli-action-7QXG7LHS.mjs +95 -0
  16. package/dist/cli-action-BIX6TYXF.mjs +95 -0
  17. package/dist/cli-action-BUGVCH44.mjs +95 -0
  18. package/dist/cli-action-RO24U52W.mjs +95 -0
  19. package/dist/cli-action-WAZ5KM6X.mjs +95 -0
  20. package/dist/cli-action-XDKINE2R.mjs +95 -0
  21. package/dist/cli-action-Y6VATXMV.mjs +95 -0
  22. package/dist/cli.js +75 -27
  23. package/dist/cli.mjs +1 -1
  24. package/dist/index.d.mts +4 -2
  25. package/dist/index.d.ts +4 -2
  26. package/dist/index.js +71 -25
  27. package/dist/index.mjs +3 -3
  28. package/dist/orchestrator-2KQNMO2L.mjs +10 -0
  29. package/dist/orchestrator-66ZVNOLR.mjs +10 -0
  30. package/dist/orchestrator-KM2OJPZD.mjs +10 -0
  31. package/dist/orchestrator-MKDZPRBA.mjs +10 -0
  32. package/dist/orchestrator-QSHWWBWS.mjs +10 -0
  33. package/dist/orchestrator-WFQPMNSD.mjs +10 -0
  34. package/dist/python-context-H2OLC5JN.mjs +162 -0
  35. package/dist/python-context-OBP7JD5P.mjs +162 -0
  36. package/package.json +13 -10
  37. package/src/__tests__/analyzer.test.ts +4 -3
  38. package/src/__tests__/issue-analyzer.test.ts +4 -2
  39. package/src/classify/file-classifiers.ts +14 -13
  40. package/src/cli-action.ts +6 -3
  41. package/src/graph-builder.ts +43 -8
  42. package/src/issue-analyzer.ts +19 -7
  43. package/src/orchestrator.ts +6 -4
  44. package/src/semantic/domain-inference.ts +1 -1
  45. package/src/types.ts +2 -0
  46. package/src/utils/dependency-graph-utils.ts +22 -13
  47. package/tsconfig.json +2 -1
@@ -0,0 +1,162 @@
1
+ import {
2
+ calculateImportDepthFromEdges,
3
+ detectGraphCyclesFromFile
4
+ } from "./chunk-WLXLBWDU.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
+ };
@@ -0,0 +1,162 @@
1
+ import {
2
+ calculateImportDepthFromEdges,
3
+ detectGraphCyclesFromFile
4
+ } from "./chunk-RQ5BQLT6.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.22.15",
3
+ "version": "0.22.17",
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",
@@ -46,18 +46,18 @@
46
46
  "url": "https://github.com/getaiready/aiready-context-analyzer/issues"
47
47
  },
48
48
  "dependencies": {
49
- "commander": "^14.0.0",
50
- "chalk": "^5.3.0",
49
+ "chalk": "^5.6.2",
50
+ "commander": "^14.0.3",
51
51
  "prompts": "^2.4.2",
52
- "@aiready/core": "0.24.16"
52
+ "@aiready/core": "0.24.20"
53
53
  },
54
54
  "devDependencies": {
55
- "@types/node": "^24.0.0",
55
+ "@types/node": "^24.12.2",
56
56
  "@types/prompts": "^2.4.9",
57
- "tsup": "^8.3.5",
58
- "typescript": "^5.7.3",
59
- "vitest": "^4.0.0",
60
- "eslint": "^10.0.0"
57
+ "eslint": "^10.2.0",
58
+ "tsup": "^8.5.1",
59
+ "typescript": "^6.0.2",
60
+ "vitest": "^4.1.2"
61
61
  },
62
62
  "engines": {
63
63
  "node": ">=18.0.0"
@@ -70,6 +70,9 @@
70
70
  "clean": "rm -rf dist",
71
71
  "release": "pnpm build && pnpm publish --no-git-checks",
72
72
  "type-check": "tsc --noEmit",
73
- "format-check": "prettier --check . --ignore-path ../../.prettierignore"
73
+ "format-check": "prettier --check . --ignore-path ../../.prettierignore",
74
+ "format": "prettier --write . --ignore-path ../../.prettierignore",
75
+ "lint:fix": "eslint . --fix",
76
+ "test:coverage": "vitest run --coverage"
74
77
  }
75
78
  }
@@ -93,9 +93,10 @@ describe('getTransitiveDependencies', () => {
93
93
  const graph = await buildDependencyGraph(files);
94
94
  const deps = getTransitiveDependencies('c.ts', graph);
95
95
 
96
- expect(deps).toContain('b.ts');
97
- expect(deps).toContain('a.ts');
98
- expect(deps.length).toBe(2);
96
+ const depKeys = Array.from(deps.keys());
97
+ expect(depKeys).toContain('b.ts');
98
+ expect(depKeys).toContain('a.ts');
99
+ expect(deps.size).toBe(2);
99
100
  });
100
101
  });
101
102
 
@@ -4,9 +4,11 @@ import { Severity } from '@aiready/core';
4
4
 
5
5
  describe('analyzeIssues', () => {
6
6
  const baseParams = {
7
- file: 'src/test.ts',
7
+ file: 'test.ts',
8
8
  importDepth: 2,
9
- contextBudget: 10000,
9
+ tokenCost: 1000,
10
+ contextBudget: 5000,
11
+
10
12
  cohesionScore: 0.8,
11
13
  fragmentationScore: 0.3,
12
14
  maxDepth: 5,
@@ -1,4 +1,5 @@
1
1
  import { DependencyNode } from '../types';
2
+ import type { ExportInfo } from '@aiready/core';
2
3
  import {
3
4
  BARREL_EXPORT_MIN_EXPORTS,
4
5
  BARREL_EXPORT_TOKEN_LIMIT,
@@ -23,7 +24,7 @@ export function isBoilerplateBarrel(node: DependencyNode): boolean {
23
24
  if (!exports || exports.length === 0) return false;
24
25
 
25
26
  // 1. Must be purely re-exports
26
- const isPurelyReexports = exports.every((exp: any) => !!exp.source);
27
+ const isPurelyReexports = exports.every((exp: ExportInfo) => !!exp.source);
27
28
  if (!isPurelyReexports) return false;
28
29
 
29
30
  // 2. Must be low local token cost (no actual logic)
@@ -31,7 +32,7 @@ export function isBoilerplateBarrel(node: DependencyNode): boolean {
31
32
 
32
33
  // 3. Detect "Architectural Theater"
33
34
  // If it re-exports everything from exactly ONE source, it's a pass-through
34
- const sources = new Set(exports.map((exp: any) => exp.source));
35
+ const sources = new Set(exports.map((exp: ExportInfo) => exp.source));
35
36
 
36
37
  // Pattern: export * from '../actual'
37
38
  const isSingleSourcePassThrough = sources.size === 1;
@@ -63,7 +64,7 @@ export function isBarrelExport(node: DependencyNode): boolean {
63
64
 
64
65
  const isReexportPattern =
65
66
  (exports || []).length >= BARREL_EXPORT_MIN_EXPORTS &&
66
- (exports || []).every((exp: any) =>
67
+ (exports || []).every((exp: ExportInfo) =>
67
68
  ['const', 'function', 'type', 'interface'].includes(exp.type)
68
69
  );
69
70
 
@@ -86,7 +87,7 @@ export function isTypeDefinition(node: DependencyNode): boolean {
86
87
  const areAllTypes =
87
88
  hasExports &&
88
89
  nodeExports.every(
89
- (exp: any) => exp.type === 'type' || exp.type === 'interface'
90
+ (exp: ExportInfo) => exp.type === 'type' || exp.type === 'interface'
90
91
  );
91
92
 
92
93
  const isTypePath = /\/(types|interfaces|models)\//i.test(file);
@@ -124,7 +125,7 @@ export function isLambdaHandler(node: DependencyNode): boolean {
124
125
  );
125
126
  const isHandlerPath = /\/(handlers|lambdas|lambda|functions)\//i.test(file);
126
127
  const hasHandlerExport = (exports || []).some(
127
- (exp: any) =>
128
+ (exp: ExportInfo) =>
128
129
  ['handler', 'main', 'lambdahandler'].includes(exp.name.toLowerCase()) ||
129
130
  exp.name.toLowerCase().endsWith('handler')
130
131
  );
@@ -146,11 +147,11 @@ export function isServiceFile(node: DependencyNode): boolean {
146
147
  fileName.includes(pattern)
147
148
  );
148
149
  const isServicePath = file.toLowerCase().includes('/services/');
149
- const hasServiceNamedExport = (exports || []).some((exp: any) =>
150
+ const hasServiceNamedExport = (exports || []).some((exp: ExportInfo) =>
150
151
  exp.name.toLowerCase().includes('service')
151
152
  );
152
153
  const hasClassExport = (exports || []).some(
153
- (exp: any) => exp.type === 'class'
154
+ (exp: ExportInfo) => exp.type === 'class'
154
155
  );
155
156
  return (
156
157
  isServiceName || isServicePath || (hasServiceNamedExport && hasClassExport)
@@ -173,7 +174,7 @@ export function isEmailTemplate(node: DependencyNode): boolean {
173
174
  );
174
175
  const isEmailPath = /\/(emails|mail|notifications)\//i.test(file);
175
176
  const hasTemplateFunction = (exports || []).some(
176
- (exp: any) =>
177
+ (exp: ExportInfo) =>
177
178
  exp.type === 'function' &&
178
179
  (exp.name.toLowerCase().startsWith('render') ||
179
180
  exp.name.toLowerCase().startsWith('generate'))
@@ -197,7 +198,7 @@ export function isParserFile(node: DependencyNode): boolean {
197
198
  );
198
199
  const isParserPath = /\/(parsers|transformers)\//i.test(file);
199
200
  const hasParseFunction = (exports || []).some(
200
- (exp: any) =>
201
+ (exp: ExportInfo) =>
201
202
  exp.type === 'function' &&
202
203
  (exp.name.toLowerCase().startsWith('parse') ||
203
204
  exp.name.toLowerCase().startsWith('transform'))
@@ -220,7 +221,7 @@ export function isSessionFile(node: DependencyNode): boolean {
220
221
  fileName.includes(pattern)
221
222
  );
222
223
  const isSessionPath = /\/(sessions|state)\//i.test(file);
223
- const hasSessionExport = (exports || []).some((exp: any) =>
224
+ const hasSessionExport = (exports || []).some((exp: ExportInfo) =>
224
225
  ['session', 'state', 'store'].some((pattern: string) =>
225
226
  exp.name.toLowerCase().includes(pattern)
226
227
  )
@@ -246,10 +247,10 @@ export function isNextJsPage(node: DependencyNode): boolean {
246
247
  return false;
247
248
 
248
249
  const hasDefaultExport = (exports || []).some(
249
- (exp: any) => exp.type === 'default'
250
+ (exp: ExportInfo) => exp.type === 'default'
250
251
  );
251
252
 
252
- const hasNextJsExport = (exports || []).some((exp: any) =>
253
+ const hasNextJsExport = (exports || []).some((exp: ExportInfo) =>
253
254
  NEXTJS_METADATA_EXPORTS.includes(exp.name.toLowerCase())
254
255
  );
255
256
 
@@ -272,7 +273,7 @@ export function isConfigFile(node: DependencyNode): boolean {
272
273
  fileName.includes(pattern)
273
274
  );
274
275
  const isConfigPath = /\/(config|settings|schemas)\//i.test(lowerPath);
275
- const hasSchemaExport = (exports || []).some((exp: any) =>
276
+ const hasSchemaExport = (exports || []).some((exp: ExportInfo) =>
276
277
  ['schema', 'config', 'setting'].some((pattern: string) =>
277
278
  exp.name.toLowerCase().includes(pattern)
278
279
  )
package/src/cli-action.ts CHANGED
@@ -7,6 +7,7 @@ import {
7
7
  } from '@aiready/core';
8
8
  import { analyzeContext } from './orchestrator';
9
9
  import { generateSummary } from './summary';
10
+ import type { ContextAnalyzerOptions } from './types';
10
11
  import chalk from 'chalk';
11
12
  import { writeFileSync } from 'fs';
12
13
 
@@ -28,7 +29,7 @@ export async function contextActionHandler(directory: string, options: any) {
28
29
  try {
29
30
  // Define defaults
30
31
  const defaults = {
31
- maxDepth: 5,
32
+ maxDepth: 10,
32
33
  maxContextBudget: 10000,
33
34
  minCohesion: 0.6,
34
35
  maxFragmentation: 0.5,
@@ -68,7 +69,9 @@ export async function contextActionHandler(directory: string, options: any) {
68
69
  }
69
70
 
70
71
  // Run analysis
71
- const results = await analyzeContext(finalOptions as any);
72
+ const results = await analyzeContext(
73
+ finalOptions as ContextAnalyzerOptions
74
+ );
72
75
  const summary = generateSummary(results, finalOptions);
73
76
 
74
77
  const duration = getElapsedTime(startTime);
@@ -104,7 +107,7 @@ export async function contextActionHandler(directory: string, options: any) {
104
107
  } else {
105
108
  // Default: Console (Dynamic Import)
106
109
  const { displayConsoleReport } = await import('./report/console-report');
107
- displayConsoleReport(summary, results, (finalOptions as any).maxResults);
110
+ displayConsoleReport(summary, results, finalOptions.maxResults);
108
111
  console.log(chalk.dim(`\n✨ Analysis completed in ${duration}ms\n`));
109
112
  }
110
113
  } catch (error) {
@@ -23,6 +23,35 @@ function resolveImport(
23
23
  // If it's not a relative import, we treat it as an external dependency for now
24
24
  // (unless it's an absolute path that exists in our set)
25
25
  if (!source.startsWith('.') && !source.startsWith('/')) {
26
+ // Ignore standard libraries and external packages we don't control
27
+ const externalIgnores = [
28
+ 'react',
29
+ 'next',
30
+ 'lucide-react',
31
+ 'framer-motion',
32
+ '@aws-sdk',
33
+ 'stripe',
34
+ 'clsx',
35
+ 'tailwind-merge',
36
+ 'zod',
37
+ 'commander',
38
+ 'chalk',
39
+ 'fs',
40
+ 'path',
41
+ 'util',
42
+ 'child_process',
43
+ 'os',
44
+ 'crypto',
45
+ ];
46
+
47
+ if (
48
+ externalIgnores.some(
49
+ (pkg) => source === pkg || source.startsWith(`${pkg}/`)
50
+ )
51
+ ) {
52
+ return null;
53
+ }
54
+
26
55
  // Handle monorepo package imports (@aiready/*)
27
56
  if (source.startsWith('@aiready/')) {
28
57
  const pkgName = source.split('/')[1];
@@ -144,12 +173,15 @@ export async function buildDependencyGraph(
144
173
  // 1. Get high-fidelity AST-based imports & exports
145
174
  const { imports: astImports } = await parseFileExports(content, file);
146
175
 
147
- // 2. Resolve imports to absolute paths in the graph
148
- const resolvedImports = astImports
176
+ // 2. Filter out type-only imports (they don't create runtime dependencies)
177
+ const runtimeImports = astImports.filter((i) => !i.isTypeOnly);
178
+
179
+ // 3. Resolve imports to absolute paths in the graph
180
+ const resolvedImports = runtimeImports
149
181
  .map((i) => resolveImport(i.source, file, allFilePaths))
150
182
  .filter((path): path is string => path !== null);
151
183
 
152
- const importSources = astImports.map((i) => i.source);
184
+ const importSources = runtimeImports.map((i) => i.source);
153
185
 
154
186
  // 3. Wrap with platform-specific metadata (v0.11+)
155
187
  const exports = await extractExportsWithAST(
@@ -233,7 +265,7 @@ export function getTransitiveDependencies(
233
265
  file: string,
234
266
  graph: DependencyGraph,
235
267
  visited = new Set<string>()
236
- ): string[] {
268
+ ): Map<string, number> {
237
269
  return getTransitiveDependenciesFromEdges(file, graph.edges, visited);
238
270
  }
239
271
 
@@ -242,7 +274,7 @@ export function getTransitiveDependencies(
242
274
  *
243
275
  * @param file - File path to calculate budget for.
244
276
  * @param graph - The dependency graph.
245
- * @returns Total token count including recursive dependencies.
277
+ * @returns Total token count including recursive dependencies (discounted by depth).
246
278
  */
247
279
  export function calculateContextBudget(
248
280
  file: string,
@@ -254,14 +286,17 @@ export function calculateContextBudget(
254
286
  let totalTokens = node.tokenCost;
255
287
  const deps = getTransitiveDependencies(file, graph);
256
288
 
257
- for (const dep of deps) {
289
+ for (const [dep, depth] of deps.entries()) {
258
290
  const depNode = graph.nodes.get(dep);
259
291
  if (depNode) {
260
- totalTokens += depNode.tokenCost;
292
+ // Discount token cost by depth (20% reduction per level)
293
+ // This prevents "barrel file" false positives where a facade pulls in the entire project
294
+ const discountFactor = Math.pow(0.8, depth - 1);
295
+ totalTokens += depNode.tokenCost * discountFactor;
261
296
  }
262
297
  }
263
298
 
264
- return totalTokens;
299
+ return Math.round(totalTokens);
265
300
  }
266
301
 
267
302
  /**