@aiready/context-analyzer 0.7.19 → 0.8.0

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.
@@ -0,0 +1,10 @@
1
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
2
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
3
+ }) : x)(function(x) {
4
+ if (typeof require !== "undefined") return require.apply(this, arguments);
5
+ throw Error('Dynamic require of "' + x + '" is not supported');
6
+ });
7
+
8
+ export {
9
+ __require
10
+ };
package/dist/cli.js CHANGED
@@ -6,6 +6,13 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
6
  var __getOwnPropNames = Object.getOwnPropertyNames;
7
7
  var __getProtoOf = Object.getPrototypeOf;
8
8
  var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __esm = (fn, res) => function __init() {
10
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
11
+ };
12
+ var __export = (target, all) => {
13
+ for (var name in all)
14
+ __defProp(target, name, { get: all[name], enumerable: true });
15
+ };
9
16
  var __copyProps = (to, from, except, desc) => {
10
17
  if (from && typeof from === "object" || typeof from === "function") {
11
18
  for (let key of __getOwnPropNames(from))
@@ -23,11 +30,207 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
23
30
  mod
24
31
  ));
25
32
 
33
+ // src/analyzers/python-context.ts
34
+ var python_context_exports = {};
35
+ __export(python_context_exports, {
36
+ analyzePythonContext: () => analyzePythonContext
37
+ });
38
+ async function analyzePythonContext(files, rootDir) {
39
+ const results = [];
40
+ const parser = (0, import_core2.getParser)("dummy.py");
41
+ if (!parser) {
42
+ console.warn("Python parser not available");
43
+ return results;
44
+ }
45
+ const pythonFiles = files.filter((f) => f.toLowerCase().endsWith(".py"));
46
+ const dependencyGraph = await buildPythonDependencyGraph(pythonFiles, rootDir);
47
+ for (const file of pythonFiles) {
48
+ try {
49
+ const fs = await import("fs");
50
+ const code = await fs.promises.readFile(file, "utf-8");
51
+ const result = parser.parse(code, file);
52
+ const imports = result.imports.map((imp) => ({
53
+ source: imp.source,
54
+ specifiers: imp.specifiers,
55
+ isRelative: imp.source.startsWith("."),
56
+ resolvedPath: resolvePythonImport(file, imp.source, rootDir)
57
+ }));
58
+ const exports2 = result.exports.map((exp) => ({
59
+ name: exp.name,
60
+ type: exp.type
61
+ }));
62
+ const linesOfCode = code.split("\n").length;
63
+ const importDepth = await calculatePythonImportDepth(file, dependencyGraph, /* @__PURE__ */ new Set());
64
+ const contextBudget = estimateContextBudget(code, imports, dependencyGraph);
65
+ const cohesion = calculatePythonCohesion(exports2, imports);
66
+ const circularDependencies = detectCircularDependencies2(file, dependencyGraph);
67
+ results.push({
68
+ file,
69
+ importDepth,
70
+ contextBudget,
71
+ cohesion,
72
+ imports,
73
+ exports: exports2,
74
+ metrics: {
75
+ linesOfCode,
76
+ importCount: imports.length,
77
+ exportCount: exports2.length,
78
+ circularDependencies
79
+ }
80
+ });
81
+ } catch (error) {
82
+ console.warn(`Failed to analyze ${file}:`, error);
83
+ }
84
+ }
85
+ return results;
86
+ }
87
+ async function buildPythonDependencyGraph(files, rootDir) {
88
+ const graph = /* @__PURE__ */ new Map();
89
+ const parser = (0, import_core2.getParser)("dummy.py");
90
+ if (!parser) return graph;
91
+ for (const file of files) {
92
+ try {
93
+ const fs = await import("fs");
94
+ const code = await fs.promises.readFile(file, "utf-8");
95
+ const result = parser.parse(code, file);
96
+ const dependencies = /* @__PURE__ */ new Set();
97
+ for (const imp of result.imports) {
98
+ const resolved = resolvePythonImport(file, imp.source, rootDir);
99
+ if (resolved && files.includes(resolved)) {
100
+ dependencies.add(resolved);
101
+ }
102
+ }
103
+ graph.set(file, dependencies);
104
+ } catch (error) {
105
+ }
106
+ }
107
+ return graph;
108
+ }
109
+ function resolvePythonImport(fromFile, importPath, rootDir) {
110
+ const dir = (0, import_path.dirname)(fromFile);
111
+ if (importPath.startsWith(".")) {
112
+ const parts = importPath.split(".");
113
+ let upCount = 0;
114
+ while (parts[0] === "") {
115
+ upCount++;
116
+ parts.shift();
117
+ }
118
+ let targetDir = dir;
119
+ for (let i = 0; i < upCount - 1; i++) {
120
+ targetDir = (0, import_path.dirname)(targetDir);
121
+ }
122
+ const modulePath = parts.join("/");
123
+ const possiblePaths = [
124
+ (0, import_path.resolve)(targetDir, `${modulePath}.py`),
125
+ (0, import_path.resolve)(targetDir, modulePath, "__init__.py")
126
+ ];
127
+ const fs = require("fs");
128
+ for (const path of possiblePaths) {
129
+ if (fs.existsSync(path)) {
130
+ return path;
131
+ }
132
+ }
133
+ } else {
134
+ const modulePath = importPath.replace(/\./g, "/");
135
+ const possiblePaths = [
136
+ (0, import_path.resolve)(rootDir, `${modulePath}.py`),
137
+ (0, import_path.resolve)(rootDir, modulePath, "__init__.py")
138
+ ];
139
+ const fs = require("fs");
140
+ for (const path of possiblePaths) {
141
+ if (fs.existsSync(path)) {
142
+ return path;
143
+ }
144
+ }
145
+ }
146
+ return void 0;
147
+ }
148
+ async function calculatePythonImportDepth(file, dependencyGraph, visited, depth = 0) {
149
+ if (visited.has(file)) {
150
+ return depth;
151
+ }
152
+ visited.add(file);
153
+ const dependencies = dependencyGraph.get(file) || /* @__PURE__ */ new Set();
154
+ if (dependencies.size === 0) {
155
+ return depth;
156
+ }
157
+ let maxDepth = depth;
158
+ for (const dep of dependencies) {
159
+ const depDepth = await calculatePythonImportDepth(
160
+ dep,
161
+ dependencyGraph,
162
+ new Set(visited),
163
+ depth + 1
164
+ );
165
+ maxDepth = Math.max(maxDepth, depDepth);
166
+ }
167
+ return maxDepth;
168
+ }
169
+ function estimateContextBudget(code, imports, dependencyGraph) {
170
+ let budget = (0, import_core2.estimateTokens)(code);
171
+ const avgTokensPerDep = 500;
172
+ budget += imports.length * avgTokensPerDep;
173
+ return budget;
174
+ }
175
+ function calculatePythonCohesion(exports2, imports) {
176
+ if (exports2.length === 0) return 1;
177
+ const exportCount = exports2.length;
178
+ const importCount = imports.length;
179
+ let cohesion = 1;
180
+ if (exportCount > 10) {
181
+ cohesion *= 0.6;
182
+ } else if (exportCount > 5) {
183
+ cohesion *= 0.8;
184
+ }
185
+ if (exportCount > 0) {
186
+ const ratio = importCount / exportCount;
187
+ if (ratio > 2) {
188
+ cohesion *= 1.1;
189
+ } else if (ratio < 0.5) {
190
+ cohesion *= 0.9;
191
+ }
192
+ }
193
+ return Math.min(1, Math.max(0, cohesion));
194
+ }
195
+ function detectCircularDependencies2(file, dependencyGraph) {
196
+ const circular = [];
197
+ const visited = /* @__PURE__ */ new Set();
198
+ const recursionStack = /* @__PURE__ */ new Set();
199
+ function dfs(current, path) {
200
+ if (recursionStack.has(current)) {
201
+ const cycleStart = path.indexOf(current);
202
+ const cycle = path.slice(cycleStart).concat([current]);
203
+ circular.push(cycle.join(" \u2192 "));
204
+ return;
205
+ }
206
+ if (visited.has(current)) {
207
+ return;
208
+ }
209
+ visited.add(current);
210
+ recursionStack.add(current);
211
+ const dependencies = dependencyGraph.get(current) || /* @__PURE__ */ new Set();
212
+ for (const dep of dependencies) {
213
+ dfs(dep, [...path, current]);
214
+ }
215
+ recursionStack.delete(current);
216
+ }
217
+ dfs(file, []);
218
+ return [...new Set(circular)];
219
+ }
220
+ var import_core2, import_path;
221
+ var init_python_context = __esm({
222
+ "src/analyzers/python-context.ts"() {
223
+ "use strict";
224
+ import_core2 = require("@aiready/core");
225
+ import_path = require("path");
226
+ }
227
+ });
228
+
26
229
  // src/cli.ts
27
230
  var import_commander = require("commander");
28
231
 
29
232
  // src/index.ts
30
- var import_core2 = require("@aiready/core");
233
+ var import_core3 = require("@aiready/core");
31
234
 
32
235
  // src/analyzer.ts
33
236
  var import_core = require("@aiready/core");
@@ -598,20 +801,63 @@ async function analyzeContext(options) {
598
801
  includeNodeModules = false,
599
802
  ...scanOptions
600
803
  } = options;
601
- const files = await (0, import_core2.scanFiles)({
804
+ const files = await (0, import_core3.scanFiles)({
602
805
  ...scanOptions,
603
806
  // Only add node_modules to exclude if includeNodeModules is false
604
807
  // The DEFAULT_EXCLUDE already includes node_modules, so this is only needed
605
808
  // if user overrides the default exclude list
606
809
  exclude: includeNodeModules && scanOptions.exclude ? scanOptions.exclude.filter((pattern) => pattern !== "**/node_modules/**") : scanOptions.exclude
607
810
  });
811
+ const pythonFiles = files.filter((f) => f.toLowerCase().endsWith(".py"));
812
+ const tsJsFiles = files.filter((f) => !f.toLowerCase().endsWith(".py"));
608
813
  const fileContents = await Promise.all(
609
814
  files.map(async (file) => ({
610
815
  file,
611
- content: await (0, import_core2.readFileContent)(file)
816
+ content: await (0, import_core3.readFileContent)(file)
612
817
  }))
613
818
  );
614
- const graph = buildDependencyGraph(fileContents);
819
+ const graph = buildDependencyGraph(fileContents.filter((f) => !f.file.toLowerCase().endsWith(".py")));
820
+ let pythonResults = [];
821
+ if (pythonFiles.length > 0) {
822
+ const { analyzePythonContext: analyzePythonContext2 } = await Promise.resolve().then(() => (init_python_context(), python_context_exports));
823
+ const pythonMetrics = await analyzePythonContext2(pythonFiles, scanOptions.rootDir || options.rootDir || ".");
824
+ pythonResults = pythonMetrics.map((metric) => {
825
+ const { severity, issues, recommendations, potentialSavings } = analyzeIssues({
826
+ file: metric.file,
827
+ importDepth: metric.importDepth,
828
+ contextBudget: metric.contextBudget,
829
+ cohesionScore: metric.cohesion,
830
+ fragmentationScore: 0,
831
+ // Python analyzer doesn't calculate fragmentation yet
832
+ maxDepth,
833
+ maxContextBudget,
834
+ minCohesion,
835
+ maxFragmentation,
836
+ circularDeps: metric.metrics.circularDependencies.map((cycle) => cycle.split(" \u2192 "))
837
+ });
838
+ return {
839
+ file: metric.file,
840
+ tokenCost: Math.floor(metric.contextBudget / (1 + metric.imports.length || 1)),
841
+ // Estimate
842
+ linesOfCode: metric.metrics.linesOfCode,
843
+ importDepth: metric.importDepth,
844
+ dependencyCount: metric.imports.length,
845
+ dependencyList: metric.imports.map((imp) => imp.resolvedPath || imp.source),
846
+ circularDeps: metric.metrics.circularDependencies.map((cycle) => cycle.split(" \u2192 ")),
847
+ cohesionScore: metric.cohesion,
848
+ domains: ["python"],
849
+ // Generic for now
850
+ exportCount: metric.exports.length,
851
+ contextBudget: metric.contextBudget,
852
+ fragmentationScore: 0,
853
+ relatedFiles: [],
854
+ severity,
855
+ issues,
856
+ recommendations,
857
+ potentialSavings
858
+ };
859
+ });
860
+ }
615
861
  const circularDeps = detectCircularDependencies(graph);
616
862
  const clusters = detectModuleClusters(graph);
617
863
  const fragmentationMap = /* @__PURE__ */ new Map();
@@ -671,7 +917,8 @@ async function analyzeContext(options) {
671
917
  potentialSavings
672
918
  });
673
919
  }
674
- const issuesOnly = results.filter((r) => r.severity !== "info");
920
+ const allResults = [...results, ...pythonResults];
921
+ const issuesOnly = allResults.filter((r) => r.severity !== "info");
675
922
  const sorted = issuesOnly.sort((a, b) => {
676
923
  const severityOrder = { critical: 0, major: 1, minor: 2, info: 3 };
677
924
  const severityDiff = severityOrder[a.severity] - severityOrder[b.severity];
@@ -876,8 +1123,8 @@ function downgradeSeverity(s) {
876
1123
  // src/cli.ts
877
1124
  var import_chalk = __toESM(require("chalk"));
878
1125
  var import_fs = require("fs");
879
- var import_path = require("path");
880
- var import_core3 = require("@aiready/core");
1126
+ var import_path2 = require("path");
1127
+ var import_core4 = require("@aiready/core");
881
1128
  var import_prompts = __toESM(require("prompts"));
882
1129
  var program = new import_commander.Command();
883
1130
  program.name("aiready-context").description("Analyze AI context window cost and code structure").version("0.1.0").addHelpText("after", "\nCONFIGURATION:\n Supports config files: aiready.json, aiready.config.json, .aiready.json, .aireadyrc.json, aiready.config.js, .aireadyrc.js\n CLI options override config file settings").argument("<directory>", "Directory to analyze").option("--max-depth <number>", "Maximum acceptable import depth").option(
@@ -908,7 +1155,7 @@ program.name("aiready-context").description("Analyze AI context window cost and
908
1155
  exclude: void 0,
909
1156
  maxResults: 10
910
1157
  };
911
- let finalOptions = await (0, import_core3.loadMergedConfig)(directory, defaults, {
1158
+ let finalOptions = await (0, import_core4.loadMergedConfig)(directory, defaults, {
912
1159
  maxDepth: options.maxDepth ? parseInt(options.maxDepth) : void 0,
913
1160
  maxContextBudget: options.maxContext ? parseInt(options.maxContext) : void 0,
914
1161
  minCohesion: options.minCohesion ? parseFloat(options.minCohesion) : void 0,
@@ -923,7 +1170,7 @@ program.name("aiready-context").description("Analyze AI context window cost and
923
1170
  finalOptions = await runInteractiveSetup(directory, finalOptions);
924
1171
  }
925
1172
  const results = await analyzeContext(finalOptions);
926
- const elapsedTime = (0, import_core3.getElapsedTime)(startTime);
1173
+ const elapsedTime = (0, import_core4.getElapsedTime)(startTime);
927
1174
  const summary = generateSummary(results);
928
1175
  if (options.output === "json") {
929
1176
  const jsonOutput = {
@@ -932,23 +1179,23 @@ program.name("aiready-context").description("Analyze AI context window cost and
932
1179
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
933
1180
  analysisTime: elapsedTime
934
1181
  };
935
- const outputPath = (0, import_core3.resolveOutputPath)(
1182
+ const outputPath = (0, import_core4.resolveOutputPath)(
936
1183
  options.outputFile,
937
1184
  `context-report-${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}.json`,
938
1185
  directory
939
1186
  );
940
- (0, import_core3.handleJSONOutput)(jsonOutput, outputPath, `
1187
+ (0, import_core4.handleJSONOutput)(jsonOutput, outputPath, `
941
1188
  \u2713 JSON report saved to ${outputPath}`);
942
1189
  return;
943
1190
  }
944
1191
  if (options.output === "html") {
945
1192
  const html = generateHTMLReport(summary, results);
946
- const outputPath = (0, import_core3.resolveOutputPath)(
1193
+ const outputPath = (0, import_core4.resolveOutputPath)(
947
1194
  options.outputFile,
948
1195
  `context-report-${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}.html`,
949
1196
  directory
950
1197
  );
951
- const dir = (0, import_path.dirname)(outputPath);
1198
+ const dir = (0, import_path2.dirname)(outputPath);
952
1199
  if (!(0, import_fs.existsSync)(dir)) {
953
1200
  (0, import_fs.mkdirSync)(dir, { recursive: true });
954
1201
  }
@@ -960,7 +1207,7 @@ program.name("aiready-context").description("Analyze AI context window cost and
960
1207
  displayConsoleReport(summary, results, elapsedTime, finalOptions.maxResults);
961
1208
  displayTuningGuidance(results, finalOptions);
962
1209
  } catch (error) {
963
- (0, import_core3.handleCLIError)(error, "Analysis");
1210
+ (0, import_core4.handleCLIError)(error, "Analysis");
964
1211
  }
965
1212
  });
966
1213
  program.parse();
@@ -1300,7 +1547,7 @@ function generateHTMLReport(summary, results) {
1300
1547
  }
1301
1548
  async function runInteractiveSetup(directory, current) {
1302
1549
  console.log(import_chalk.default.yellow("\u{1F9ED} Interactive mode: let\u2019s tailor the analysis."));
1303
- const pkgPath = (0, import_path.join)(directory, "package.json");
1550
+ const pkgPath = (0, import_path2.join)(directory, "package.json");
1304
1551
  let deps = {};
1305
1552
  if ((0, import_fs.existsSync)(pkgPath)) {
1306
1553
  try {
@@ -1309,8 +1556,8 @@ async function runInteractiveSetup(directory, current) {
1309
1556
  } catch {
1310
1557
  }
1311
1558
  }
1312
- const hasNextJs = (0, import_fs.existsSync)((0, import_path.join)(directory, ".next")) || !!deps["next"];
1313
- const hasCDK = (0, import_fs.existsSync)((0, import_path.join)(directory, "cdk.out")) || !!deps["aws-cdk-lib"] || Object.keys(deps).some((d) => d.startsWith("@aws-cdk/"));
1559
+ const hasNextJs = (0, import_fs.existsSync)((0, import_path2.join)(directory, ".next")) || !!deps["next"];
1560
+ 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/"));
1314
1561
  const recommendedExcludes = new Set(current.exclude || []);
1315
1562
  if (hasNextJs && !Array.from(recommendedExcludes).some((p) => p.includes(".next"))) {
1316
1563
  recommendedExcludes.add("**/.next/**");
package/dist/cli.mjs CHANGED
@@ -2,7 +2,8 @@
2
2
  import {
3
3
  analyzeContext,
4
4
  generateSummary
5
- } from "./chunk-G7PO3DNK.mjs";
5
+ } from "./chunk-JZ2SE4DB.mjs";
6
+ import "./chunk-Y6FXYEAI.mjs";
6
7
 
7
8
  // src/cli.ts
8
9
  import { Command } from "commander";