@aiready/context-analyzer 0.9.31 → 0.9.34

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
@@ -46,8 +46,7 @@ async function analyzePythonContext(files, rootDir) {
46
46
  const dependencyGraph = await buildPythonDependencyGraph(pythonFiles, rootDir);
47
47
  for (const file of pythonFiles) {
48
48
  try {
49
- const fs = await import("fs");
50
- const code = await fs.promises.readFile(file, "utf-8");
49
+ const code = await import_fs.default.promises.readFile(file, "utf-8");
51
50
  const result = parser.parse(code, file);
52
51
  const imports = result.imports.map((imp) => ({
53
52
  source: imp.source,
@@ -90,8 +89,7 @@ async function buildPythonDependencyGraph(files, rootDir) {
90
89
  if (!parser) return graph;
91
90
  for (const file of files) {
92
91
  try {
93
- const fs = await import("fs");
94
- const code = await fs.promises.readFile(file, "utf-8");
92
+ const code = await import_fs.default.promises.readFile(file, "utf-8");
95
93
  const result = parser.parse(code, file);
96
94
  const dependencies = /* @__PURE__ */ new Set();
97
95
  for (const imp of result.imports) {
@@ -124,9 +122,8 @@ function resolvePythonImport(fromFile, importPath, rootDir) {
124
122
  (0, import_path.resolve)(targetDir, `${modulePath}.py`),
125
123
  (0, import_path.resolve)(targetDir, modulePath, "__init__.py")
126
124
  ];
127
- const fs = require("fs");
128
125
  for (const path of possiblePaths) {
129
- if (fs.existsSync(path)) {
126
+ if (import_fs.default.existsSync(path)) {
130
127
  return path;
131
128
  }
132
129
  }
@@ -136,9 +133,8 @@ function resolvePythonImport(fromFile, importPath, rootDir) {
136
133
  (0, import_path.resolve)(rootDir, `${modulePath}.py`),
137
134
  (0, import_path.resolve)(rootDir, modulePath, "__init__.py")
138
135
  ];
139
- const fs = require("fs");
140
136
  for (const path of possiblePaths) {
141
- if (fs.existsSync(path)) {
137
+ if (import_fs.default.existsSync(path)) {
142
138
  return path;
143
139
  }
144
140
  }
@@ -217,12 +213,13 @@ function detectCircularDependencies2(file, dependencyGraph) {
217
213
  dfs(file, []);
218
214
  return [...new Set(circular)];
219
215
  }
220
- var import_core3, import_path;
216
+ var import_core3, import_path, import_fs;
221
217
  var init_python_context = __esm({
222
218
  "src/analyzers/python-context.ts"() {
223
219
  "use strict";
224
220
  import_core3 = require("@aiready/core");
225
221
  import_path = require("path");
222
+ import_fs = __toESM(require("fs"));
226
223
  }
227
224
  });
228
225
 
@@ -1887,7 +1884,7 @@ function downgradeSeverity(s) {
1887
1884
 
1888
1885
  // src/cli.ts
1889
1886
  var import_chalk = __toESM(require("chalk"));
1890
- var import_fs = require("fs");
1887
+ var import_fs2 = require("fs");
1891
1888
  var import_path2 = require("path");
1892
1889
  var import_core5 = require("@aiready/core");
1893
1890
  var import_prompts = __toESM(require("prompts"));
@@ -1961,10 +1958,10 @@ program.name("aiready-context").description("Analyze AI context window cost and
1961
1958
  directory
1962
1959
  );
1963
1960
  const dir = (0, import_path2.dirname)(outputPath);
1964
- if (!(0, import_fs.existsSync)(dir)) {
1965
- (0, import_fs.mkdirSync)(dir, { recursive: true });
1961
+ if (!(0, import_fs2.existsSync)(dir)) {
1962
+ (0, import_fs2.mkdirSync)(dir, { recursive: true });
1966
1963
  }
1967
- (0, import_fs.writeFileSync)(outputPath, html);
1964
+ (0, import_fs2.writeFileSync)(outputPath, html);
1968
1965
  console.log(import_chalk.default.green(`
1969
1966
  \u2713 HTML report saved to ${outputPath}`));
1970
1967
  return;
@@ -2314,15 +2311,15 @@ async function runInteractiveSetup(directory, current) {
2314
2311
  console.log(import_chalk.default.yellow("\u{1F9ED} Interactive mode: let\u2019s tailor the analysis."));
2315
2312
  const pkgPath = (0, import_path2.join)(directory, "package.json");
2316
2313
  let deps = {};
2317
- if ((0, import_fs.existsSync)(pkgPath)) {
2314
+ if ((0, import_fs2.existsSync)(pkgPath)) {
2318
2315
  try {
2319
- const pkg = JSON.parse((0, import_fs.readFileSync)(pkgPath, "utf-8"));
2316
+ const pkg = JSON.parse((0, import_fs2.readFileSync)(pkgPath, "utf-8"));
2320
2317
  deps = { ...pkg.dependencies || {}, ...pkg.devDependencies || {} };
2321
2318
  } catch {
2322
2319
  }
2323
2320
  }
2324
- const hasNextJs = (0, import_fs.existsSync)((0, import_path2.join)(directory, ".next")) || !!deps["next"];
2325
- const hasCDK = (0, import_fs.existsSync)((0, import_path2.join)(directory, "cdk.out")) || !!deps["aws-cdk-lib"] || Object.keys(deps).some((d) => d.startsWith("@aws-cdk/"));
2321
+ const hasNextJs = (0, import_fs2.existsSync)((0, import_path2.join)(directory, ".next")) || !!deps["next"];
2322
+ const hasCDK = (0, import_fs2.existsSync)((0, import_path2.join)(directory, "cdk.out")) || !!deps["aws-cdk-lib"] || Object.keys(deps).some((d) => d.startsWith("@aws-cdk/"));
2326
2323
  const recommendedExcludes = new Set(current.exclude || []);
2327
2324
  if (hasNextJs && !Array.from(recommendedExcludes).some((p) => p.includes(".next"))) {
2328
2325
  recommendedExcludes.add("**/.next/**");
package/dist/cli.mjs CHANGED
@@ -2,8 +2,7 @@
2
2
  import {
3
3
  analyzeContext,
4
4
  generateSummary
5
- } from "./chunk-M64RHH4D.mjs";
6
- import "./chunk-Y6FXYEAI.mjs";
5
+ } from "./chunk-EVX2W2BK.mjs";
7
6
 
8
7
  // src/cli.ts
9
8
  import { Command } from "commander";
package/dist/index.js CHANGED
@@ -46,8 +46,7 @@ async function analyzePythonContext(files, rootDir) {
46
46
  const dependencyGraph = await buildPythonDependencyGraph(pythonFiles, rootDir);
47
47
  for (const file of pythonFiles) {
48
48
  try {
49
- const fs = await import("fs");
50
- const code = await fs.promises.readFile(file, "utf-8");
49
+ const code = await import_fs.default.promises.readFile(file, "utf-8");
51
50
  const result = parser.parse(code, file);
52
51
  const imports = result.imports.map((imp) => ({
53
52
  source: imp.source,
@@ -90,8 +89,7 @@ async function buildPythonDependencyGraph(files, rootDir) {
90
89
  if (!parser) return graph;
91
90
  for (const file of files) {
92
91
  try {
93
- const fs = await import("fs");
94
- const code = await fs.promises.readFile(file, "utf-8");
92
+ const code = await import_fs.default.promises.readFile(file, "utf-8");
95
93
  const result = parser.parse(code, file);
96
94
  const dependencies = /* @__PURE__ */ new Set();
97
95
  for (const imp of result.imports) {
@@ -124,9 +122,8 @@ function resolvePythonImport(fromFile, importPath, rootDir) {
124
122
  (0, import_path.resolve)(targetDir, `${modulePath}.py`),
125
123
  (0, import_path.resolve)(targetDir, modulePath, "__init__.py")
126
124
  ];
127
- const fs = require("fs");
128
125
  for (const path of possiblePaths) {
129
- if (fs.existsSync(path)) {
126
+ if (import_fs.default.existsSync(path)) {
130
127
  return path;
131
128
  }
132
129
  }
@@ -136,9 +133,8 @@ function resolvePythonImport(fromFile, importPath, rootDir) {
136
133
  (0, import_path.resolve)(rootDir, `${modulePath}.py`),
137
134
  (0, import_path.resolve)(rootDir, modulePath, "__init__.py")
138
135
  ];
139
- const fs = require("fs");
140
136
  for (const path of possiblePaths) {
141
- if (fs.existsSync(path)) {
137
+ if (import_fs.default.existsSync(path)) {
142
138
  return path;
143
139
  }
144
140
  }
@@ -217,12 +213,13 @@ function detectCircularDependencies2(file, dependencyGraph) {
217
213
  dfs(file, []);
218
214
  return [...new Set(circular)];
219
215
  }
220
- var import_core3, import_path;
216
+ var import_core3, import_path, import_fs;
221
217
  var init_python_context = __esm({
222
218
  "src/analyzers/python-context.ts"() {
223
219
  "use strict";
224
220
  import_core3 = require("@aiready/core");
225
221
  import_path = require("path");
222
+ import_fs = __toESM(require("fs"));
226
223
  }
227
224
  });
228
225
 
package/dist/index.mjs CHANGED
@@ -12,8 +12,7 @@ import {
12
12
  getCoUsageData,
13
13
  getSmartDefaults,
14
14
  inferDomainFromSemantics
15
- } from "./chunk-M64RHH4D.mjs";
16
- import "./chunk-Y6FXYEAI.mjs";
15
+ } from "./chunk-EVX2W2BK.mjs";
17
16
  export {
18
17
  adjustFragmentationForClassification,
19
18
  analyzeContext,
@@ -0,0 +1,185 @@
1
+ // src/analyzers/python-context.ts
2
+ import { getParser, estimateTokens } from "@aiready/core";
3
+ import { resolve, dirname } from "path";
4
+ import fs from "fs";
5
+ async function analyzePythonContext(files, rootDir) {
6
+ const results = [];
7
+ const parser = getParser("dummy.py");
8
+ if (!parser) {
9
+ console.warn("Python parser not available");
10
+ return results;
11
+ }
12
+ const pythonFiles = files.filter((f) => f.toLowerCase().endsWith(".py"));
13
+ const dependencyGraph = await buildPythonDependencyGraph(pythonFiles, rootDir);
14
+ for (const file of pythonFiles) {
15
+ try {
16
+ const code = await fs.promises.readFile(file, "utf-8");
17
+ const result = parser.parse(code, file);
18
+ const imports = result.imports.map((imp) => ({
19
+ source: imp.source,
20
+ specifiers: imp.specifiers,
21
+ isRelative: imp.source.startsWith("."),
22
+ resolvedPath: resolvePythonImport(file, imp.source, rootDir)
23
+ }));
24
+ const exports = result.exports.map((exp) => ({
25
+ name: exp.name,
26
+ type: exp.type
27
+ }));
28
+ const linesOfCode = code.split("\n").length;
29
+ const importDepth = await calculatePythonImportDepth(file, dependencyGraph, /* @__PURE__ */ new Set());
30
+ const contextBudget = estimateContextBudget(code, imports, dependencyGraph);
31
+ const cohesion = calculatePythonCohesion(exports, imports);
32
+ const circularDependencies = detectCircularDependencies(file, dependencyGraph);
33
+ results.push({
34
+ file,
35
+ importDepth,
36
+ contextBudget,
37
+ cohesion,
38
+ imports,
39
+ exports,
40
+ metrics: {
41
+ linesOfCode,
42
+ importCount: imports.length,
43
+ exportCount: exports.length,
44
+ circularDependencies
45
+ }
46
+ });
47
+ } catch (error) {
48
+ console.warn(`Failed to analyze ${file}:`, error);
49
+ }
50
+ }
51
+ return results;
52
+ }
53
+ async function buildPythonDependencyGraph(files, rootDir) {
54
+ const graph = /* @__PURE__ */ new Map();
55
+ const parser = getParser("dummy.py");
56
+ if (!parser) return graph;
57
+ for (const file of files) {
58
+ try {
59
+ const code = await fs.promises.readFile(file, "utf-8");
60
+ const result = parser.parse(code, file);
61
+ const dependencies = /* @__PURE__ */ new Set();
62
+ for (const imp of result.imports) {
63
+ const resolved = resolvePythonImport(file, imp.source, rootDir);
64
+ if (resolved && files.includes(resolved)) {
65
+ dependencies.add(resolved);
66
+ }
67
+ }
68
+ graph.set(file, dependencies);
69
+ } catch (error) {
70
+ }
71
+ }
72
+ return graph;
73
+ }
74
+ function resolvePythonImport(fromFile, importPath, rootDir) {
75
+ const dir = dirname(fromFile);
76
+ if (importPath.startsWith(".")) {
77
+ const parts = importPath.split(".");
78
+ let upCount = 0;
79
+ while (parts[0] === "") {
80
+ upCount++;
81
+ parts.shift();
82
+ }
83
+ let targetDir = dir;
84
+ for (let i = 0; i < upCount - 1; i++) {
85
+ targetDir = dirname(targetDir);
86
+ }
87
+ const modulePath = parts.join("/");
88
+ const possiblePaths = [
89
+ resolve(targetDir, `${modulePath}.py`),
90
+ resolve(targetDir, modulePath, "__init__.py")
91
+ ];
92
+ for (const path of possiblePaths) {
93
+ if (fs.existsSync(path)) {
94
+ return path;
95
+ }
96
+ }
97
+ } else {
98
+ const modulePath = importPath.replace(/\./g, "/");
99
+ const possiblePaths = [
100
+ resolve(rootDir, `${modulePath}.py`),
101
+ resolve(rootDir, modulePath, "__init__.py")
102
+ ];
103
+ for (const path of possiblePaths) {
104
+ if (fs.existsSync(path)) {
105
+ return path;
106
+ }
107
+ }
108
+ }
109
+ return void 0;
110
+ }
111
+ async function calculatePythonImportDepth(file, dependencyGraph, visited, depth = 0) {
112
+ if (visited.has(file)) {
113
+ return depth;
114
+ }
115
+ visited.add(file);
116
+ const dependencies = dependencyGraph.get(file) || /* @__PURE__ */ new Set();
117
+ if (dependencies.size === 0) {
118
+ return depth;
119
+ }
120
+ let maxDepth = depth;
121
+ for (const dep of dependencies) {
122
+ const depDepth = await calculatePythonImportDepth(
123
+ dep,
124
+ dependencyGraph,
125
+ new Set(visited),
126
+ depth + 1
127
+ );
128
+ maxDepth = Math.max(maxDepth, depDepth);
129
+ }
130
+ return maxDepth;
131
+ }
132
+ function estimateContextBudget(code, imports, dependencyGraph) {
133
+ let budget = estimateTokens(code);
134
+ const avgTokensPerDep = 500;
135
+ budget += imports.length * avgTokensPerDep;
136
+ return budget;
137
+ }
138
+ function calculatePythonCohesion(exports, imports) {
139
+ if (exports.length === 0) return 1;
140
+ const exportCount = exports.length;
141
+ const importCount = imports.length;
142
+ let cohesion = 1;
143
+ if (exportCount > 10) {
144
+ cohesion *= 0.6;
145
+ } else if (exportCount > 5) {
146
+ cohesion *= 0.8;
147
+ }
148
+ if (exportCount > 0) {
149
+ const ratio = importCount / exportCount;
150
+ if (ratio > 2) {
151
+ cohesion *= 1.1;
152
+ } else if (ratio < 0.5) {
153
+ cohesion *= 0.9;
154
+ }
155
+ }
156
+ return Math.min(1, Math.max(0, cohesion));
157
+ }
158
+ function detectCircularDependencies(file, dependencyGraph) {
159
+ const circular = [];
160
+ const visited = /* @__PURE__ */ new Set();
161
+ const recursionStack = /* @__PURE__ */ new Set();
162
+ function dfs(current, path) {
163
+ if (recursionStack.has(current)) {
164
+ const cycleStart = path.indexOf(current);
165
+ const cycle = path.slice(cycleStart).concat([current]);
166
+ circular.push(cycle.join(" \u2192 "));
167
+ return;
168
+ }
169
+ if (visited.has(current)) {
170
+ return;
171
+ }
172
+ visited.add(current);
173
+ recursionStack.add(current);
174
+ const dependencies = dependencyGraph.get(current) || /* @__PURE__ */ new Set();
175
+ for (const dep of dependencies) {
176
+ dfs(dep, [...path, current]);
177
+ }
178
+ recursionStack.delete(current);
179
+ }
180
+ dfs(file, []);
181
+ return [...new Set(circular)];
182
+ }
183
+ export {
184
+ analyzePythonContext
185
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aiready/context-analyzer",
3
- "version": "0.9.31",
3
+ "version": "0.9.34",
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.9.28"
52
+ "@aiready/core": "0.9.31"
53
53
  },
54
54
  "devDependencies": {
55
55
  "@types/node": "^24.0.0",
@@ -10,6 +10,7 @@
10
10
 
11
11
  import { getParser, estimateTokens } from '@aiready/core';
12
12
  import { resolve, relative, dirname, join } from 'path';
13
+ import fs from 'fs';
13
14
 
14
15
  export interface PythonContextMetrics {
15
16
  file: string;
@@ -54,13 +55,12 @@ export async function analyzePythonContext(
54
55
  }
55
56
 
56
57
  const pythonFiles = files.filter(f => f.toLowerCase().endsWith('.py'));
57
-
58
+
58
59
  // Build dependency graph first
59
60
  const dependencyGraph = await buildPythonDependencyGraph(pythonFiles, rootDir);
60
61
 
61
62
  for (const file of pythonFiles) {
62
63
  try {
63
- const fs = await import('fs');
64
64
  const code = await fs.promises.readFile(file, 'utf-8');
65
65
  const result = parser.parse(code, file);
66
66
 
@@ -119,7 +119,6 @@ async function buildPythonDependencyGraph(
119
119
 
120
120
  for (const file of files) {
121
121
  try {
122
- const fs = await import('fs');
123
122
  const code = await fs.promises.readFile(file, 'utf-8');
124
123
  const result = parser.parse(code, file);
125
124
 
@@ -167,7 +166,6 @@ function resolvePythonImport(fromFile: string, importPath: string, rootDir: stri
167
166
  resolve(targetDir, modulePath, '__init__.py'),
168
167
  ];
169
168
 
170
- const fs = require('fs');
171
169
  for (const path of possiblePaths) {
172
170
  if (fs.existsSync(path)) {
173
171
  return path;
@@ -181,7 +179,6 @@ function resolvePythonImport(fromFile: string, importPath: string, rootDir: stri
181
179
  resolve(rootDir, modulePath, '__init__.py'),
182
180
  ];
183
181
 
184
- const fs = require('fs');
185
182
  for (const path of possiblePaths) {
186
183
  if (fs.existsSync(path)) {
187
184
  return path;