@aiready/context-analyzer 0.21.22 → 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 +26 -25
- package/.turbo/turbo-lint.log +5 -5
- package/.turbo/turbo-test.log +91 -41
- package/coverage/clover.xml +2615 -1242
- package/coverage/coverage-final.json +30 -13
- package/coverage/dist/chunk-64U3PNO3.mjs.html +367 -0
- package/coverage/dist/chunk-J3MUOWHC.mjs.html +5326 -0
- package/coverage/dist/index.html +146 -0
- package/coverage/{classifier.ts.html → dist/index.mjs.html} +537 -912
- package/coverage/index.html +84 -189
- package/coverage/src/analyzer.ts.html +88 -0
- package/coverage/src/analyzers/index.html +116 -0
- package/coverage/src/analyzers/python-context.ts.html +910 -0
- package/coverage/{ast-utils.ts.html → src/ast-utils.ts.html} +84 -54
- package/coverage/src/classifier.ts.html +892 -0
- package/coverage/src/classify/classification-patterns.ts.html +307 -0
- package/coverage/src/classify/file-classifiers.ts.html +973 -0
- package/coverage/src/classify/index.html +131 -0
- package/coverage/{cluster-detector.ts.html → src/cluster-detector.ts.html} +154 -91
- package/coverage/{defaults.ts.html → src/defaults.ts.html} +74 -65
- package/coverage/{graph-builder.ts.html → src/graph-builder.ts.html} +268 -229
- package/coverage/src/index.html +341 -0
- package/coverage/{index.ts.html → src/index.ts.html} +70 -13
- package/coverage/{scoring.ts.html → src/issue-analyzer.ts.html} +201 -261
- package/coverage/src/mapper.ts.html +439 -0
- package/coverage/{metrics.ts.html → src/metrics.ts.html} +201 -132
- package/coverage/src/orchestrator.ts.html +493 -0
- package/coverage/{provider.ts.html → src/provider.ts.html} +21 -21
- package/coverage/{remediation.ts.html → src/remediation.ts.html} +112 -52
- package/coverage/src/report/console-report.ts.html +415 -0
- package/coverage/src/report/html-report.ts.html +361 -0
- package/coverage/src/report/index.html +146 -0
- package/coverage/src/report/interactive-setup.ts.html +373 -0
- package/coverage/src/scoring.ts.html +895 -0
- package/coverage/src/semantic/co-usage.ts.html +340 -0
- package/coverage/src/semantic/consolidation.ts.html +223 -0
- package/coverage/src/semantic/domain-inference.ts.html +859 -0
- package/coverage/src/semantic/index.html +161 -0
- package/coverage/src/semantic/type-graph.ts.html +163 -0
- package/coverage/{summary.ts.html → src/summary.ts.html} +155 -275
- package/coverage/{types.ts.html → src/types.ts.html} +133 -31
- package/coverage/src/utils/dependency-graph-utils.ts.html +463 -0
- package/coverage/src/utils/index.html +131 -0
- package/coverage/src/utils/string-utils.ts.html +148 -0
- package/dist/chunk-AMPK6SWS.mjs +1754 -0
- package/dist/chunk-BHCRDEE4.mjs +1745 -0
- package/dist/chunk-IKRP7ECY.mjs +1754 -0
- package/dist/chunk-J3MUOWHC.mjs +1747 -0
- package/dist/chunk-TWWPY7FD.mjs +1754 -0
- package/dist/chunk-Z5WY6A4P.mjs +1754 -0
- package/dist/cli.js +77 -185
- package/dist/cli.mjs +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +73 -181
- 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__/consolidation.test.ts +247 -0
- package/src/__tests__/defaults.test.ts +121 -0
- package/src/__tests__/domain-inference.test.ts +420 -0
- package/src/__tests__/fragmentation-coupling.test.ts +4 -4
- package/src/__tests__/issue-analyzer.test.ts +155 -0
- package/src/__tests__/orchestrator.test.ts +143 -0
- package/src/__tests__/python-context.test.ts +100 -0
- package/src/__tests__/report/console-report.test.ts +292 -0
- package/src/__tests__/report/html-report.test.ts +243 -0
- 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 +73 -181
- package/coverage/analyzer.ts.html +0 -1369
- package/coverage/semantic-analysis.ts.html +0 -1201
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 {
|
|
@@ -352,7 +352,7 @@ __export(index_exports, {
|
|
|
352
352
|
runInteractiveSetup: () => runInteractiveSetup
|
|
353
353
|
});
|
|
354
354
|
module.exports = __toCommonJS(index_exports);
|
|
355
|
-
var
|
|
355
|
+
var import_core12 = require("@aiready/core");
|
|
356
356
|
|
|
357
357
|
// src/provider.ts
|
|
358
358
|
var import_core9 = require("@aiready/core");
|
|
@@ -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 = [];
|
|
@@ -2204,182 +2204,74 @@ function displayConsoleReport(summary, results, maxResults = 10) {
|
|
|
2204
2204
|
}
|
|
2205
2205
|
|
|
2206
2206
|
// src/report/html-report.ts
|
|
2207
|
+
var import_core11 = require("@aiready/core");
|
|
2207
2208
|
function generateHTMLReport(summary, results) {
|
|
2208
2209
|
const totalIssues = summary.criticalIssues + summary.majorIssues + summary.minorIssues;
|
|
2209
2210
|
void results;
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
line-height: 1.6;
|
|
2220
|
-
color: #333;
|
|
2221
|
-
max-width: 1200px;
|
|
2222
|
-
margin: 0 auto;
|
|
2223
|
-
padding: 20px;
|
|
2224
|
-
background-color: #f5f5f5;
|
|
2225
|
-
}
|
|
2226
|
-
h1, h2, h3 { color: #2c3e50; }
|
|
2227
|
-
.header {
|
|
2228
|
-
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
2229
|
-
color: white;
|
|
2230
|
-
padding: 30px;
|
|
2231
|
-
border-radius: 8px;
|
|
2232
|
-
margin-bottom: 30px;
|
|
2233
|
-
}
|
|
2234
|
-
.summary {
|
|
2235
|
-
display: grid;
|
|
2236
|
-
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
|
2237
|
-
gap: 20px;
|
|
2238
|
-
margin-bottom: 30px;
|
|
2239
|
-
}
|
|
2240
|
-
.card {
|
|
2241
|
-
background: white;
|
|
2242
|
-
padding: 20px;
|
|
2243
|
-
border-radius: 8px;
|
|
2244
|
-
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
2245
|
-
}
|
|
2246
|
-
.metric {
|
|
2247
|
-
font-size: 2em;
|
|
2248
|
-
font-weight: bold;
|
|
2249
|
-
color: #667eea;
|
|
2250
|
-
}
|
|
2251
|
-
.label {
|
|
2252
|
-
color: #666;
|
|
2253
|
-
font-size: 0.9em;
|
|
2254
|
-
margin-top: 5px;
|
|
2255
|
-
}
|
|
2256
|
-
.issue-critical { color: #e74c3c; }
|
|
2257
|
-
.issue-major { color: #f39c12; }
|
|
2258
|
-
.issue-minor { color: #3498db; }
|
|
2259
|
-
table {
|
|
2260
|
-
width: 100%;
|
|
2261
|
-
border-collapse: collapse;
|
|
2262
|
-
background: white;
|
|
2263
|
-
border-radius: 8px;
|
|
2264
|
-
overflow: hidden;
|
|
2265
|
-
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
2266
|
-
}
|
|
2267
|
-
th, td {
|
|
2268
|
-
padding: 12px;
|
|
2269
|
-
text-align: left;
|
|
2270
|
-
border-bottom: 1px solid #eee;
|
|
2271
|
-
}
|
|
2272
|
-
th {
|
|
2273
|
-
background-color: #667eea;
|
|
2274
|
-
color: white;
|
|
2275
|
-
font-weight: 600;
|
|
2276
|
-
}
|
|
2277
|
-
tr:hover { background-color: #f8f9fa; }
|
|
2278
|
-
.footer {
|
|
2279
|
-
text-align: center;
|
|
2280
|
-
margin-top: 40px;
|
|
2281
|
-
padding: 20px;
|
|
2282
|
-
color: #666;
|
|
2283
|
-
font-size: 0.9em;
|
|
2211
|
+
const head = (0, import_core11.generateReportHead)("AIReady Context Analysis Report");
|
|
2212
|
+
const stats = (0, import_core11.generateStatCards)([
|
|
2213
|
+
{ value: summary.totalFiles, label: "Files Analyzed" },
|
|
2214
|
+
{ value: summary.totalTokens.toLocaleString(), label: "Total Tokens" },
|
|
2215
|
+
{ value: summary.avgContextBudget.toFixed(0), label: "Avg Context Budget" },
|
|
2216
|
+
{
|
|
2217
|
+
value: totalIssues,
|
|
2218
|
+
label: "Total Issues",
|
|
2219
|
+
color: totalIssues > 0 ? "#f39c12" : void 0
|
|
2284
2220
|
}
|
|
2285
|
-
|
|
2286
|
-
|
|
2221
|
+
]);
|
|
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}
|
|
2227
|
+
${stats}`;
|
|
2228
|
+
if (totalIssues > 0) {
|
|
2229
|
+
body += (0, import_core11.generateIssueSummary)(
|
|
2230
|
+
summary.criticalIssues,
|
|
2231
|
+
summary.majorIssues,
|
|
2232
|
+
summary.minorIssues,
|
|
2233
|
+
summary.totalPotentialSavings
|
|
2234
|
+
);
|
|
2235
|
+
}
|
|
2236
|
+
if (summary.fragmentedModules.length > 0) {
|
|
2237
|
+
const fragmentedRows = summary.fragmentedModules.map((m) => [
|
|
2238
|
+
m.domain,
|
|
2239
|
+
String(m.files.length),
|
|
2240
|
+
`${(m.fragmentationScore * 100).toFixed(0)}%`,
|
|
2241
|
+
m.totalTokens.toLocaleString()
|
|
2242
|
+
]);
|
|
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
|
+
);
|
|
2250
|
+
}
|
|
2251
|
+
if (summary.topExpensiveFiles.length > 0) {
|
|
2252
|
+
const expensiveRows = summary.topExpensiveFiles.map((f) => [
|
|
2253
|
+
f.file,
|
|
2254
|
+
`${f.contextBudget.toLocaleString()} tokens`,
|
|
2255
|
+
`<span class="issue-${f.severity}">${f.severity.toUpperCase()}</span>`
|
|
2256
|
+
]);
|
|
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
|
+
);
|
|
2264
|
+
}
|
|
2265
|
+
const footer = (0, import_core11.generateReportFooter)({
|
|
2266
|
+
title: "Context Analysis Report",
|
|
2267
|
+
packageName: "context-analyzer",
|
|
2268
|
+
packageUrl: "https://github.com/caopengau/aiready-context-analyzer",
|
|
2269
|
+
bugUrl: "https://github.com/caopengau/aiready-context-analyzer/issues"
|
|
2270
|
+
});
|
|
2271
|
+
return `${head}
|
|
2287
2272
|
<body>
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
<p>Generated on ${(/* @__PURE__ */ new Date()).toLocaleString()}</p>
|
|
2291
|
-
</div>
|
|
2292
|
-
|
|
2293
|
-
<div class="summary">
|
|
2294
|
-
<div class="card">
|
|
2295
|
-
<div class="metric">${summary.totalFiles}</div>
|
|
2296
|
-
<div class="label">Files Analyzed</div>
|
|
2297
|
-
</div>
|
|
2298
|
-
<div class="card">
|
|
2299
|
-
<div class="metric">${summary.totalTokens.toLocaleString()}</div>
|
|
2300
|
-
<div class="label">Total Tokens</div>
|
|
2301
|
-
</div>
|
|
2302
|
-
<div class="card">
|
|
2303
|
-
<div class="metric">${summary.avgContextBudget.toFixed(0)}</div>
|
|
2304
|
-
<div class="label">Avg Context Budget</div>
|
|
2305
|
-
</div>
|
|
2306
|
-
<div class="card">
|
|
2307
|
-
<div class="metric ${totalIssues > 0 ? "issue-major" : ""}">${totalIssues}</div>
|
|
2308
|
-
<div class="label">Total Issues</div>
|
|
2309
|
-
</div>
|
|
2310
|
-
</div>
|
|
2311
|
-
|
|
2312
|
-
${totalIssues > 0 ? `
|
|
2313
|
-
<div class="card" style="margin-bottom: 30px;">
|
|
2314
|
-
<h2>\u26A0\uFE0F Issues Summary</h2>
|
|
2315
|
-
<p>
|
|
2316
|
-
<span class="issue-critical">\u{1F534} Critical: ${summary.criticalIssues}</span>
|
|
2317
|
-
<span class="issue-major">\u{1F7E1} Major: ${summary.majorIssues}</span>
|
|
2318
|
-
<span class="issue-minor">\u{1F535} Minor: ${summary.minorIssues}</span>
|
|
2319
|
-
</p>
|
|
2320
|
-
<p><strong>Potential Savings:</strong> ${summary.totalPotentialSavings.toLocaleString()} tokens</p>
|
|
2321
|
-
</div>
|
|
2322
|
-
` : ""}
|
|
2323
|
-
|
|
2324
|
-
${summary.fragmentedModules.length > 0 ? `
|
|
2325
|
-
<div class="card" style="margin-bottom: 30px;">
|
|
2326
|
-
<h2>\u{1F9E9} Fragmented Modules</h2>
|
|
2327
|
-
<table>
|
|
2328
|
-
<thead>
|
|
2329
|
-
<tr>
|
|
2330
|
-
<th>Domain</th>
|
|
2331
|
-
<th>Files</th>
|
|
2332
|
-
<th>Fragmentation</th>
|
|
2333
|
-
<th>Token Cost</th>
|
|
2334
|
-
</tr>
|
|
2335
|
-
</thead>
|
|
2336
|
-
<tbody>
|
|
2337
|
-
${summary.fragmentedModules.map(
|
|
2338
|
-
(m) => `
|
|
2339
|
-
<tr>
|
|
2340
|
-
<td>${m.domain}</td>
|
|
2341
|
-
<td>${m.files.length}</td>
|
|
2342
|
-
<td>${(m.fragmentationScore * 100).toFixed(0)}%</td>
|
|
2343
|
-
<td>${m.totalTokens.toLocaleString()}</td>
|
|
2344
|
-
</tr>
|
|
2345
|
-
`
|
|
2346
|
-
).join("")}
|
|
2347
|
-
</tbody>
|
|
2348
|
-
</table>
|
|
2349
|
-
</div>
|
|
2350
|
-
` : ""}
|
|
2351
|
-
|
|
2352
|
-
${summary.topExpensiveFiles.length > 0 ? `
|
|
2353
|
-
<div class="card" style="margin-bottom: 30px;">
|
|
2354
|
-
<h2>\u{1F4B8} Most Expensive Files</h2>
|
|
2355
|
-
<table>
|
|
2356
|
-
<thead>
|
|
2357
|
-
<tr>
|
|
2358
|
-
<th>File</th>
|
|
2359
|
-
<th>Context Budget</th>
|
|
2360
|
-
<th>Severity</th>
|
|
2361
|
-
</tr>
|
|
2362
|
-
</thead>
|
|
2363
|
-
<tbody>
|
|
2364
|
-
${summary.topExpensiveFiles.map(
|
|
2365
|
-
(f) => `
|
|
2366
|
-
<tr>
|
|
2367
|
-
<td>${f.file}</td>
|
|
2368
|
-
<td>${f.contextBudget.toLocaleString()} tokens</td>
|
|
2369
|
-
<td class="issue-${f.severity}">${f.severity.toUpperCase()}</td>
|
|
2370
|
-
</tr>
|
|
2371
|
-
`
|
|
2372
|
-
).join("")}
|
|
2373
|
-
</tbody>
|
|
2374
|
-
</table>
|
|
2375
|
-
</div>
|
|
2376
|
-
` : ""}
|
|
2377
|
-
|
|
2378
|
-
<div class="footer">
|
|
2379
|
-
<p>Generated by <strong>@aiready/context-analyzer</strong></p>
|
|
2380
|
-
<p>Like AIReady? <a href="https://github.com/caopengau/aiready-context-analyzer">Star us on GitHub</a></p>
|
|
2381
|
-
<p>Found a bug? <a href="https://github.com/caopengau/aiready-context-analyzer/issues">Report it here</a></p>
|
|
2382
|
-
</div>
|
|
2273
|
+
${body}
|
|
2274
|
+
${footer}
|
|
2383
2275
|
</body>
|
|
2384
2276
|
</html>`;
|
|
2385
2277
|
}
|
|
@@ -2458,7 +2350,7 @@ async function runInteractiveSetup(directory, current) {
|
|
|
2458
2350
|
}
|
|
2459
2351
|
|
|
2460
2352
|
// src/index.ts
|
|
2461
|
-
|
|
2353
|
+
import_core12.ToolRegistry.register(ContextAnalyzerProvider);
|
|
2462
2354
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2463
2355
|
0 && (module.exports = {
|
|
2464
2356
|
BARREL_EXPORT_MIN_EXPORTS,
|
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);
|