@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/.turbo/turbo-build.log +11 -11
- package/.turbo/turbo-test.log +45 -58
- package/dist/chunk-AMPK6SWS.mjs +1754 -0
- package/dist/chunk-BHCRDEE4.mjs +1745 -0
- package/dist/chunk-IKRP7ECY.mjs +1754 -0
- package/dist/chunk-TWWPY7FD.mjs +1754 -0
- package/dist/chunk-Z5WY6A4P.mjs +1754 -0
- package/dist/cli.js +33 -29
- package/dist/cli.mjs +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +33 -29
- package/dist/index.mjs +1 -1
- package/dist/python-context-BWDC4E5Z.mjs +162 -0
- package/package.json +3 -3
- package/src/__tests__/analyzer.test.ts +14 -14
- package/src/__tests__/auto-detection.test.ts +16 -16
- package/src/__tests__/fragmentation-coupling.test.ts +4 -4
- package/src/__tests__/python-context.test.ts +4 -2
- package/src/__tests__/report/html-report.test.ts +14 -3
- package/src/__tests__/scoring.test.ts +17 -11
- package/src/analyzers/python-context.ts +2 -2
- package/src/ast-utils.ts +3 -3
- package/src/graph-builder.ts +4 -4
- package/src/mapper.ts +6 -0
- package/src/orchestrator.ts +1 -1
- package/src/report/html-report.ts +29 -21
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
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
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 +=
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
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 +=
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
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 +=
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
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
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
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
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 +=
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
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 +=
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
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 +=
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
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
|
@@ -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.
|
|
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.
|
|
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);
|