@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/.turbo/turbo-build.log +10 -11
- package/.turbo/turbo-test.log +18 -18
- package/README.md +32 -950
- package/dist/chunk-EVX2W2BK.mjs +1896 -0
- package/dist/cli.js +14 -17
- package/dist/cli.mjs +1 -2
- package/dist/index.js +6 -9
- package/dist/index.mjs +1 -2
- package/dist/python-context-PAETRLDY.mjs +185 -0
- package/package.json +2 -2
- package/src/analyzers/python-context.ts +2 -5
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
|
|
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
|
|
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 (
|
|
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 (
|
|
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
|
|
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,
|
|
1965
|
-
(0,
|
|
1961
|
+
if (!(0, import_fs2.existsSync)(dir)) {
|
|
1962
|
+
(0, import_fs2.mkdirSync)(dir, { recursive: true });
|
|
1966
1963
|
}
|
|
1967
|
-
(0,
|
|
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,
|
|
2314
|
+
if ((0, import_fs2.existsSync)(pkgPath)) {
|
|
2318
2315
|
try {
|
|
2319
|
-
const pkg = JSON.parse((0,
|
|
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,
|
|
2325
|
-
const hasCDK = (0,
|
|
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
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
|
|
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
|
|
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 (
|
|
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 (
|
|
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
|
@@ -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.
|
|
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.
|
|
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;
|