@aiready/context-analyzer 0.21.1 → 0.21.6
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/.aiready/aiready-report-20260314-222254.json +39216 -0
- package/.aiready/aiready-report-20260314-223947.json +3413 -0
- package/.aiready/aiready-report-20260314-224112.json +3413 -0
- package/.aiready/aiready-report-20260314-224302.json +2973 -0
- package/.aiready/aiready-report-20260314-224939.json +3092 -0
- package/.aiready/aiready-report-20260314-225154.json +3092 -0
- package/.turbo/turbo-build.log +13 -12
- package/.turbo/turbo-test.log +32 -341
- package/dist/__tests__/analyzer.test.js +55 -14
- package/dist/__tests__/analyzer.test.js.map +1 -1
- package/dist/__tests__/cluster-detector.test.d.ts +2 -0
- package/dist/__tests__/cluster-detector.test.d.ts.map +1 -0
- package/dist/__tests__/cluster-detector.test.js +121 -0
- package/dist/__tests__/cluster-detector.test.js.map +1 -0
- package/dist/__tests__/contract.test.d.ts +2 -0
- package/dist/__tests__/contract.test.d.ts.map +1 -0
- package/dist/__tests__/contract.test.js +59 -0
- package/dist/__tests__/contract.test.js.map +1 -0
- package/dist/__tests__/enhanced-cohesion.test.js +12 -2
- package/dist/__tests__/enhanced-cohesion.test.js.map +1 -1
- package/dist/__tests__/file-classification.test.d.ts +2 -0
- package/dist/__tests__/file-classification.test.d.ts.map +1 -0
- package/dist/__tests__/file-classification.test.js +749 -0
- package/dist/__tests__/file-classification.test.js.map +1 -0
- package/dist/__tests__/fragmentation-advanced.test.js +2 -8
- package/dist/__tests__/fragmentation-advanced.test.js.map +1 -1
- package/dist/__tests__/fragmentation-coupling.test.js +2 -2
- package/dist/__tests__/fragmentation-coupling.test.js.map +1 -1
- package/dist/__tests__/fragmentation-log.test.js +3 -7
- package/dist/__tests__/fragmentation-log.test.js.map +1 -1
- package/dist/__tests__/provider.test.d.ts +2 -0
- package/dist/__tests__/provider.test.d.ts.map +1 -0
- package/dist/__tests__/provider.test.js +72 -0
- package/dist/__tests__/provider.test.js.map +1 -0
- package/dist/__tests__/remediation.test.d.ts +2 -0
- package/dist/__tests__/remediation.test.d.ts.map +1 -0
- package/dist/__tests__/remediation.test.js +61 -0
- package/dist/__tests__/remediation.test.js.map +1 -0
- package/dist/__tests__/scoring.test.js +196 -16
- package/dist/__tests__/scoring.test.js.map +1 -1
- package/dist/__tests__/structural-cohesion.test.js +8 -2
- package/dist/__tests__/structural-cohesion.test.js.map +1 -1
- package/dist/analyzer.d.ts +31 -94
- package/dist/analyzer.d.ts.map +1 -1
- package/dist/analyzer.js +260 -678
- package/dist/analyzer.js.map +1 -1
- package/dist/analyzers/python-context.d.ts.map +1 -1
- package/dist/analyzers/python-context.js +10 -8
- package/dist/analyzers/python-context.js.map +1 -1
- package/dist/ast-utils.d.ts +16 -0
- package/dist/ast-utils.d.ts.map +1 -0
- package/dist/ast-utils.js +81 -0
- package/dist/ast-utils.js.map +1 -0
- package/dist/chunk-64U3PNO3.mjs +94 -0
- package/dist/chunk-CDIVYADN.mjs +2110 -0
- package/dist/chunk-D3SIHB2V.mjs +2118 -0
- package/dist/chunk-FNPSK3CG.mjs +1760 -0
- package/dist/chunk-GXTGOLZT.mjs +92 -0
- package/dist/chunk-LERPI33Y.mjs +2060 -0
- package/dist/chunk-MZP3G7TF.mjs +2118 -0
- package/dist/chunk-NOHK5DLU.mjs +2173 -0
- package/dist/chunk-ORLC5Y4J.mjs +1787 -0
- package/dist/chunk-OTCQL7DY.mjs +2045 -0
- package/dist/chunk-OUYSZZ7X.mjs +1846 -0
- package/dist/chunk-SFK6XTJE.mjs +2110 -0
- package/dist/chunk-U5R2FTCR.mjs +1803 -0
- package/dist/chunk-UU4HZ7ZT.mjs +1849 -0
- package/dist/chunk-W2KNBN6W.mjs +1849 -0
- package/dist/chunk-WKOZOHOU.mjs +2060 -0
- package/dist/chunk-XIXAWCMS.mjs +1760 -0
- package/dist/classifier.d.ts +114 -0
- package/dist/classifier.d.ts.map +1 -0
- package/dist/classifier.js +439 -0
- package/dist/classifier.js.map +1 -0
- package/dist/cli.js +591 -1057
- package/dist/cli.js.map +1 -1
- package/dist/cli.mjs +63 -533
- package/dist/cluster-detector.d.ts +8 -0
- package/dist/cluster-detector.d.ts.map +1 -0
- package/dist/cluster-detector.js +70 -0
- package/dist/cluster-detector.js.map +1 -0
- package/dist/defaults.d.ts +7 -0
- package/dist/defaults.d.ts.map +1 -0
- package/dist/defaults.js +54 -0
- package/dist/defaults.js.map +1 -0
- package/dist/graph-builder.d.ts +33 -0
- package/dist/graph-builder.d.ts.map +1 -0
- package/dist/graph-builder.js +225 -0
- package/dist/graph-builder.js.map +1 -0
- package/dist/index.d.mts +24 -31
- package/dist/index.d.ts +24 -31
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +822 -588
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +265 -8
- package/dist/metrics.d.ts +34 -0
- package/dist/metrics.d.ts.map +1 -0
- package/dist/metrics.js +170 -0
- package/dist/metrics.js.map +1 -0
- package/dist/provider.d.ts +6 -0
- package/dist/provider.d.ts.map +1 -0
- package/dist/provider.js +48 -0
- package/dist/provider.js.map +1 -0
- package/dist/python-context-3GZKN3LR.mjs +162 -0
- package/dist/python-context-O2EN3M6Z.mjs +162 -0
- package/dist/remediation.d.ts +25 -0
- package/dist/remediation.d.ts.map +1 -0
- package/dist/remediation.js +98 -0
- package/dist/remediation.js.map +1 -0
- package/dist/scoring.d.ts +3 -7
- package/dist/scoring.d.ts.map +1 -1
- package/dist/scoring.js +57 -48
- package/dist/scoring.js.map +1 -1
- package/dist/semantic-analysis.d.ts +12 -23
- package/dist/semantic-analysis.d.ts.map +1 -1
- package/dist/semantic-analysis.js +172 -110
- package/dist/semantic-analysis.js.map +1 -1
- package/dist/summary.d.ts +6 -0
- package/dist/summary.d.ts.map +1 -0
- package/dist/summary.js +92 -0
- package/dist/summary.js.map +1 -0
- package/dist/types.d.ts +9 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/output-formatter.d.ts +14 -0
- package/dist/utils/output-formatter.d.ts.map +1 -0
- package/dist/utils/output-formatter.js +338 -0
- package/dist/utils/output-formatter.js.map +1 -0
- package/package.json +2 -2
- package/src/__tests__/analyzer.test.ts +1 -1
- package/src/__tests__/auto-detection.test.ts +1 -1
- package/src/__tests__/contract.test.ts +1 -1
- package/src/__tests__/enhanced-cohesion.test.ts +1 -1
- package/src/__tests__/file-classification.test.ts +1 -1
- package/src/__tests__/fragmentation-advanced.test.ts +1 -1
- package/src/__tests__/fragmentation-coupling.test.ts +1 -1
- package/src/__tests__/fragmentation-log.test.ts +1 -1
- package/src/__tests__/provider.test.ts +1 -1
- package/src/__tests__/scoring.test.ts +217 -9
- package/src/__tests__/structural-cohesion.test.ts +1 -1
- package/src/analyzer.ts +96 -309
- package/src/analyzers/python-context.ts +7 -76
- package/src/cli-action.ts +103 -0
- package/src/cli.ts +12 -693
- package/src/cluster-detector.ts +1 -1
- package/src/graph-builder.ts +9 -85
- package/src/index.ts +6 -0
- package/src/issue-analyzer.ts +143 -0
- package/src/scoring.ts +40 -20
- package/src/semantic-analysis.ts +1 -14
- package/src/summary.ts +62 -106
- package/src/utils/dependency-graph-utils.ts +126 -0
- package/src/utils/output-formatter.ts +411 -0
- package/src/utils/string-utils.ts +17 -0
package/dist/cli.js
CHANGED
|
@@ -30,6 +30,99 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
30
30
|
mod
|
|
31
31
|
));
|
|
32
32
|
|
|
33
|
+
// src/utils/dependency-graph-utils.ts
|
|
34
|
+
function calculateImportDepthFromEdges(file, edges, visited = /* @__PURE__ */ new Set(), depth = 0) {
|
|
35
|
+
if (visited.has(file)) return depth;
|
|
36
|
+
const dependencies = edges.get(file);
|
|
37
|
+
if (!dependencies || dependencies.size === 0) return depth;
|
|
38
|
+
const nextVisited = new Set(visited);
|
|
39
|
+
nextVisited.add(file);
|
|
40
|
+
let maxDepth = depth;
|
|
41
|
+
for (const dep of dependencies) {
|
|
42
|
+
maxDepth = Math.max(
|
|
43
|
+
maxDepth,
|
|
44
|
+
calculateImportDepthFromEdges(dep, edges, nextVisited, depth + 1)
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
return maxDepth;
|
|
48
|
+
}
|
|
49
|
+
function getTransitiveDependenciesFromEdges(file, edges, visited = /* @__PURE__ */ new Set()) {
|
|
50
|
+
if (visited.has(file)) return [];
|
|
51
|
+
const nextVisited = new Set(visited);
|
|
52
|
+
nextVisited.add(file);
|
|
53
|
+
const dependencies = edges.get(file);
|
|
54
|
+
if (!dependencies || dependencies.size === 0) return [];
|
|
55
|
+
const allDeps = [];
|
|
56
|
+
for (const dep of dependencies) {
|
|
57
|
+
allDeps.push(dep);
|
|
58
|
+
allDeps.push(
|
|
59
|
+
...getTransitiveDependenciesFromEdges(dep, edges, nextVisited)
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
return [...new Set(allDeps)];
|
|
63
|
+
}
|
|
64
|
+
function detectGraphCycles(edges) {
|
|
65
|
+
const cycles = [];
|
|
66
|
+
const visited = /* @__PURE__ */ new Set();
|
|
67
|
+
const recursionStack = /* @__PURE__ */ new Set();
|
|
68
|
+
function dfs(file, path) {
|
|
69
|
+
if (recursionStack.has(file)) {
|
|
70
|
+
const cycleStart = path.indexOf(file);
|
|
71
|
+
if (cycleStart !== -1) {
|
|
72
|
+
cycles.push([...path.slice(cycleStart), file]);
|
|
73
|
+
}
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
if (visited.has(file)) return;
|
|
77
|
+
visited.add(file);
|
|
78
|
+
recursionStack.add(file);
|
|
79
|
+
const dependencies = edges.get(file);
|
|
80
|
+
if (dependencies) {
|
|
81
|
+
for (const dep of dependencies) {
|
|
82
|
+
dfs(dep, [...path, file]);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
recursionStack.delete(file);
|
|
86
|
+
}
|
|
87
|
+
for (const file of edges.keys()) {
|
|
88
|
+
if (!visited.has(file)) {
|
|
89
|
+
dfs(file, []);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return cycles;
|
|
93
|
+
}
|
|
94
|
+
function detectGraphCyclesFromFile(file, edges) {
|
|
95
|
+
const cycles = [];
|
|
96
|
+
const visited = /* @__PURE__ */ new Set();
|
|
97
|
+
const recursionStack = /* @__PURE__ */ new Set();
|
|
98
|
+
function dfs(current, path) {
|
|
99
|
+
if (recursionStack.has(current)) {
|
|
100
|
+
const cycleStart = path.indexOf(current);
|
|
101
|
+
if (cycleStart !== -1) {
|
|
102
|
+
cycles.push([...path.slice(cycleStart), current]);
|
|
103
|
+
}
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
if (visited.has(current)) return;
|
|
107
|
+
visited.add(current);
|
|
108
|
+
recursionStack.add(current);
|
|
109
|
+
const dependencies = edges.get(current);
|
|
110
|
+
if (dependencies) {
|
|
111
|
+
for (const dep of dependencies) {
|
|
112
|
+
dfs(dep, [...path, current]);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
recursionStack.delete(current);
|
|
116
|
+
}
|
|
117
|
+
dfs(file, []);
|
|
118
|
+
return cycles;
|
|
119
|
+
}
|
|
120
|
+
var init_dependency_graph_utils = __esm({
|
|
121
|
+
"src/utils/dependency-graph-utils.ts"() {
|
|
122
|
+
"use strict";
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
|
|
33
126
|
// src/analyzers/python-context.ts
|
|
34
127
|
var python_context_exports = {};
|
|
35
128
|
__export(python_context_exports, {
|
|
@@ -64,7 +157,7 @@ async function analyzePythonContext(files, rootDir) {
|
|
|
64
157
|
type: exp.type
|
|
65
158
|
}));
|
|
66
159
|
const linesOfCode = code.split("\n").length;
|
|
67
|
-
const importDepth =
|
|
160
|
+
const importDepth = calculateImportDepthFromEdges(
|
|
68
161
|
file,
|
|
69
162
|
dependencyGraph,
|
|
70
163
|
/* @__PURE__ */ new Set()
|
|
@@ -75,10 +168,10 @@ async function analyzePythonContext(files, rootDir) {
|
|
|
75
168
|
dependencyGraph
|
|
76
169
|
);
|
|
77
170
|
const cohesion = calculatePythonCohesion(exports2, imports);
|
|
78
|
-
const circularDependencies =
|
|
171
|
+
const circularDependencies = detectGraphCyclesFromFile(
|
|
79
172
|
file,
|
|
80
173
|
dependencyGraph
|
|
81
|
-
);
|
|
174
|
+
).map((cycle) => cycle.join(" -> "));
|
|
82
175
|
results.push({
|
|
83
176
|
file,
|
|
84
177
|
importDepth,
|
|
@@ -158,27 +251,6 @@ function resolvePythonImport(fromFile, importPath, rootDir) {
|
|
|
158
251
|
}
|
|
159
252
|
return void 0;
|
|
160
253
|
}
|
|
161
|
-
async function calculatePythonImportDepth(file, dependencyGraph, visited, depth = 0) {
|
|
162
|
-
if (visited.has(file)) {
|
|
163
|
-
return depth;
|
|
164
|
-
}
|
|
165
|
-
visited.add(file);
|
|
166
|
-
const dependencies = dependencyGraph.get(file) || /* @__PURE__ */ new Set();
|
|
167
|
-
if (dependencies.size === 0) {
|
|
168
|
-
return depth;
|
|
169
|
-
}
|
|
170
|
-
let maxDepth = depth;
|
|
171
|
-
for (const dep of dependencies) {
|
|
172
|
-
const depDepth = await calculatePythonImportDepth(
|
|
173
|
-
dep,
|
|
174
|
-
dependencyGraph,
|
|
175
|
-
new Set(visited),
|
|
176
|
-
depth + 1
|
|
177
|
-
);
|
|
178
|
-
maxDepth = Math.max(maxDepth, depDepth);
|
|
179
|
-
}
|
|
180
|
-
return maxDepth;
|
|
181
|
-
}
|
|
182
254
|
function estimateContextBudget(code, imports, dependencyGraph) {
|
|
183
255
|
void dependencyGraph;
|
|
184
256
|
let budget = (0, import_core5.estimateTokens)(code);
|
|
@@ -206,31 +278,6 @@ function calculatePythonCohesion(exports2, imports) {
|
|
|
206
278
|
}
|
|
207
279
|
return Math.min(1, Math.max(0, cohesion));
|
|
208
280
|
}
|
|
209
|
-
function detectCircularDependencies2(file, dependencyGraph) {
|
|
210
|
-
const circular = [];
|
|
211
|
-
const visited = /* @__PURE__ */ new Set();
|
|
212
|
-
const recursionStack = /* @__PURE__ */ new Set();
|
|
213
|
-
function dfs(current, path) {
|
|
214
|
-
if (recursionStack.has(current)) {
|
|
215
|
-
const cycleStart = path.indexOf(current);
|
|
216
|
-
const cycle = path.slice(cycleStart).concat([current]);
|
|
217
|
-
circular.push(cycle.join(" \u2192 "));
|
|
218
|
-
return;
|
|
219
|
-
}
|
|
220
|
-
if (visited.has(current)) {
|
|
221
|
-
return;
|
|
222
|
-
}
|
|
223
|
-
visited.add(current);
|
|
224
|
-
recursionStack.add(current);
|
|
225
|
-
const dependencies = dependencyGraph.get(current) || /* @__PURE__ */ new Set();
|
|
226
|
-
for (const dep of dependencies) {
|
|
227
|
-
dfs(dep, [...path, current]);
|
|
228
|
-
}
|
|
229
|
-
recursionStack.delete(current);
|
|
230
|
-
}
|
|
231
|
-
dfs(file, []);
|
|
232
|
-
return [...new Set(circular)];
|
|
233
|
-
}
|
|
234
281
|
var import_core5, import_path2, import_fs;
|
|
235
282
|
var init_python_context = __esm({
|
|
236
283
|
"src/analyzers/python-context.ts"() {
|
|
@@ -238,16 +285,14 @@ var init_python_context = __esm({
|
|
|
238
285
|
import_core5 = require("@aiready/core");
|
|
239
286
|
import_path2 = require("path");
|
|
240
287
|
import_fs = __toESM(require("fs"));
|
|
288
|
+
init_dependency_graph_utils();
|
|
241
289
|
}
|
|
242
290
|
});
|
|
243
291
|
|
|
244
292
|
// src/cli.ts
|
|
245
293
|
var import_commander = require("commander");
|
|
246
294
|
|
|
247
|
-
// src/
|
|
248
|
-
var import_core10 = require("@aiready/core");
|
|
249
|
-
|
|
250
|
-
// src/provider.ts
|
|
295
|
+
// src/cli-action.ts
|
|
251
296
|
var import_core8 = require("@aiready/core");
|
|
252
297
|
|
|
253
298
|
// src/analyzer.ts
|
|
@@ -259,6 +304,21 @@ var import_core2 = require("@aiready/core");
|
|
|
259
304
|
// src/ast-utils.ts
|
|
260
305
|
var import_core = require("@aiready/core");
|
|
261
306
|
|
|
307
|
+
// src/utils/string-utils.ts
|
|
308
|
+
function singularize(word) {
|
|
309
|
+
const irregulars = {
|
|
310
|
+
people: "person",
|
|
311
|
+
children: "child",
|
|
312
|
+
men: "man",
|
|
313
|
+
women: "woman"
|
|
314
|
+
};
|
|
315
|
+
if (irregulars[word]) return irregulars[word];
|
|
316
|
+
if (word.endsWith("ies")) return word.slice(0, -3) + "y";
|
|
317
|
+
if (word.endsWith("ses")) return word.slice(0, -2);
|
|
318
|
+
if (word.endsWith("s") && word.length > 3) return word.slice(0, -1);
|
|
319
|
+
return word;
|
|
320
|
+
}
|
|
321
|
+
|
|
262
322
|
// src/semantic-analysis.ts
|
|
263
323
|
function buildCoUsageMatrix(graph) {
|
|
264
324
|
const coUsageMatrix = /* @__PURE__ */ new Map();
|
|
@@ -461,19 +521,6 @@ function inferDomain(name, filePath, domainOptions, fileImports) {
|
|
|
461
521
|
}
|
|
462
522
|
return "unknown";
|
|
463
523
|
}
|
|
464
|
-
function singularize(word) {
|
|
465
|
-
const irregulars = {
|
|
466
|
-
people: "person",
|
|
467
|
-
children: "child",
|
|
468
|
-
men: "man",
|
|
469
|
-
women: "woman"
|
|
470
|
-
};
|
|
471
|
-
if (irregulars[word]) return irregulars[word];
|
|
472
|
-
if (word.endsWith("ies")) return word.slice(0, -3) + "y";
|
|
473
|
-
if (word.endsWith("ses")) return word.slice(0, -2);
|
|
474
|
-
if (word.endsWith("s") && word.length > 3) return word.slice(0, -1);
|
|
475
|
-
return word;
|
|
476
|
-
}
|
|
477
524
|
|
|
478
525
|
// src/ast-utils.ts
|
|
479
526
|
function extractExportsWithAST(content, filePath, domainOptions, fileImports) {
|
|
@@ -589,167 +636,113 @@ function calculatePathEntropy(files) {
|
|
|
589
636
|
const maxEntropy = Math.log2(counts.length);
|
|
590
637
|
return maxEntropy > 0 ? entropy / maxEntropy : 0;
|
|
591
638
|
}
|
|
592
|
-
function calculateDirectoryDistance(files) {
|
|
593
|
-
if (!files || files.length <= 1) return 0;
|
|
594
|
-
const pathSegments = (p) => p.split("/").filter(Boolean);
|
|
595
|
-
const commonAncestorDepth = (a, b) => {
|
|
596
|
-
const minLen = Math.min(a.length, b.length);
|
|
597
|
-
let i = 0;
|
|
598
|
-
while (i < minLen && a[i] === b[i]) i++;
|
|
599
|
-
return i;
|
|
600
|
-
};
|
|
601
|
-
let totalNormalized = 0;
|
|
602
|
-
let comparisons = 0;
|
|
603
|
-
for (let i = 0; i < files.length; i++) {
|
|
604
|
-
for (let j = i + 1; j < files.length; j++) {
|
|
605
|
-
const segA = pathSegments(files[i]);
|
|
606
|
-
const segB = pathSegments(files[j]);
|
|
607
|
-
const shared = commonAncestorDepth(segA, segB);
|
|
608
|
-
const maxDepth = Math.max(segA.length, segB.length);
|
|
609
|
-
totalNormalized += 1 - (maxDepth > 0 ? shared / maxDepth : 0);
|
|
610
|
-
comparisons++;
|
|
611
|
-
}
|
|
612
|
-
}
|
|
613
|
-
return comparisons > 0 ? totalNormalized / comparisons : 0;
|
|
614
|
-
}
|
|
615
639
|
|
|
616
|
-
// src/
|
|
640
|
+
// src/issue-analyzer.ts
|
|
617
641
|
var import_core3 = require("@aiready/core");
|
|
618
|
-
function
|
|
619
|
-
const
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
config
|
|
643
|
-
};
|
|
642
|
+
function analyzeIssues(params) {
|
|
643
|
+
const {
|
|
644
|
+
file,
|
|
645
|
+
importDepth,
|
|
646
|
+
contextBudget,
|
|
647
|
+
cohesionScore,
|
|
648
|
+
fragmentationScore,
|
|
649
|
+
maxDepth,
|
|
650
|
+
maxContextBudget,
|
|
651
|
+
minCohesion,
|
|
652
|
+
maxFragmentation,
|
|
653
|
+
circularDeps
|
|
654
|
+
} = params;
|
|
655
|
+
const issues = [];
|
|
656
|
+
const recommendations = [];
|
|
657
|
+
let severity = import_core3.Severity.Info;
|
|
658
|
+
let potentialSavings = 0;
|
|
659
|
+
if (circularDeps.length > 0) {
|
|
660
|
+
severity = import_core3.Severity.Critical;
|
|
661
|
+
issues.push(`Part of ${circularDeps.length} circular dependency chain(s)`);
|
|
662
|
+
recommendations.push(
|
|
663
|
+
"Break circular dependencies by extracting interfaces or using dependency injection"
|
|
664
|
+
);
|
|
665
|
+
potentialSavings += contextBudget * 0.2;
|
|
644
666
|
}
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
(
|
|
649
|
-
0
|
|
650
|
-
)
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
const moduleMap = /* @__PURE__ */ new Map();
|
|
658
|
-
for (const result of results) {
|
|
659
|
-
for (const domain of result.domains) {
|
|
660
|
-
if (!moduleMap.has(domain)) moduleMap.set(domain, []);
|
|
661
|
-
moduleMap.get(domain).push(result);
|
|
662
|
-
}
|
|
667
|
+
if (importDepth > maxDepth * 1.5) {
|
|
668
|
+
severity = import_core3.Severity.Critical;
|
|
669
|
+
issues.push(`Import depth ${importDepth} exceeds limit by 50%`);
|
|
670
|
+
recommendations.push("Flatten dependency tree or use facade pattern");
|
|
671
|
+
potentialSavings += contextBudget * 0.3;
|
|
672
|
+
} else if (importDepth > maxDepth) {
|
|
673
|
+
if (severity !== import_core3.Severity.Critical) severity = import_core3.Severity.Major;
|
|
674
|
+
issues.push(
|
|
675
|
+
`Import depth ${importDepth} exceeds recommended maximum ${maxDepth}`
|
|
676
|
+
);
|
|
677
|
+
recommendations.push("Consider reducing dependency depth");
|
|
678
|
+
potentialSavings += contextBudget * 0.15;
|
|
663
679
|
}
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
fragmentationScore
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
680
|
+
if (contextBudget > maxContextBudget * 1.5) {
|
|
681
|
+
severity = import_core3.Severity.Critical;
|
|
682
|
+
issues.push(
|
|
683
|
+
`Context budget ${contextBudget.toLocaleString()} tokens is 50% over limit`
|
|
684
|
+
);
|
|
685
|
+
recommendations.push(
|
|
686
|
+
"Split into smaller modules or reduce dependency tree"
|
|
687
|
+
);
|
|
688
|
+
potentialSavings += contextBudget * 0.4;
|
|
689
|
+
} else if (contextBudget > maxContextBudget) {
|
|
690
|
+
if (severity !== import_core3.Severity.Critical) severity = import_core3.Severity.Major;
|
|
691
|
+
issues.push(
|
|
692
|
+
`Context budget ${contextBudget.toLocaleString()} exceeds ${maxContextBudget.toLocaleString()}`
|
|
693
|
+
);
|
|
694
|
+
recommendations.push("Reduce file size or dependencies");
|
|
695
|
+
potentialSavings += contextBudget * 0.2;
|
|
696
|
+
}
|
|
697
|
+
if (cohesionScore < minCohesion * 0.5) {
|
|
698
|
+
if (severity !== import_core3.Severity.Critical) severity = import_core3.Severity.Major;
|
|
699
|
+
issues.push(
|
|
700
|
+
`Very low cohesion (${(cohesionScore * 100).toFixed(0)}%) - mixed concerns`
|
|
701
|
+
);
|
|
702
|
+
recommendations.push(
|
|
703
|
+
"Split file by domain - separate unrelated functionality"
|
|
704
|
+
);
|
|
705
|
+
potentialSavings += contextBudget * 0.25;
|
|
706
|
+
} else if (cohesionScore < minCohesion) {
|
|
707
|
+
if (severity === import_core3.Severity.Info) severity = import_core3.Severity.Minor;
|
|
708
|
+
issues.push(`Low cohesion (${(cohesionScore * 100).toFixed(0)}%)`);
|
|
709
|
+
recommendations.push("Consider grouping related exports together");
|
|
710
|
+
potentialSavings += contextBudget * 0.1;
|
|
711
|
+
}
|
|
712
|
+
if (fragmentationScore > maxFragmentation) {
|
|
713
|
+
if (severity === import_core3.Severity.Info || severity === import_core3.Severity.Minor)
|
|
714
|
+
severity = import_core3.Severity.Minor;
|
|
715
|
+
issues.push(
|
|
716
|
+
`High fragmentation (${(fragmentationScore * 100).toFixed(0)}%) - scattered implementation`
|
|
717
|
+
);
|
|
718
|
+
recommendations.push("Consolidate with related files in same domain");
|
|
719
|
+
potentialSavings += contextBudget * 0.3;
|
|
720
|
+
}
|
|
721
|
+
if (issues.length === 0) {
|
|
722
|
+
issues.push("No significant issues detected");
|
|
723
|
+
recommendations.push("File is well-structured for AI context usage");
|
|
724
|
+
}
|
|
725
|
+
if (isBuildArtifact(file)) {
|
|
726
|
+
issues.push("Detected build artifact (bundled/output file)");
|
|
727
|
+
recommendations.push("Exclude build outputs from analysis");
|
|
728
|
+
severity = import_core3.Severity.Info;
|
|
729
|
+
potentialSavings = 0;
|
|
713
730
|
}
|
|
714
|
-
const avgCohesion = results.reduce((sum, r) => sum + r.cohesionScore, 0) / totalFiles;
|
|
715
|
-
const lowCohesionFiles = results.filter((r) => r.cohesionScore < 0.4).map((r) => ({ file: r.file, score: r.cohesionScore })).sort((a, b) => a.score - b.score).slice(0, 10);
|
|
716
|
-
const criticalIssues = results.filter(
|
|
717
|
-
(r) => r.severity === "critical"
|
|
718
|
-
).length;
|
|
719
|
-
const majorIssues = results.filter((r) => r.severity === "major").length;
|
|
720
|
-
const minorIssues = results.filter((r) => r.severity === "minor").length;
|
|
721
|
-
const totalPotentialSavings = results.reduce(
|
|
722
|
-
(sum, r) => sum + r.potentialSavings,
|
|
723
|
-
0
|
|
724
|
-
);
|
|
725
|
-
const topExpensiveFiles = results.sort((a, b) => b.contextBudget - a.contextBudget).slice(0, 10).map((r) => ({
|
|
726
|
-
file: r.file,
|
|
727
|
-
contextBudget: r.contextBudget,
|
|
728
|
-
severity: r.severity
|
|
729
|
-
}));
|
|
730
731
|
return {
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
avgImportDepth,
|
|
736
|
-
maxImportDepth,
|
|
737
|
-
deepFiles,
|
|
738
|
-
avgFragmentation,
|
|
739
|
-
fragmentedModules,
|
|
740
|
-
avgCohesion,
|
|
741
|
-
lowCohesionFiles,
|
|
742
|
-
criticalIssues,
|
|
743
|
-
majorIssues,
|
|
744
|
-
minorIssues,
|
|
745
|
-
totalPotentialSavings,
|
|
746
|
-
topExpensiveFiles,
|
|
747
|
-
config
|
|
732
|
+
severity,
|
|
733
|
+
issues,
|
|
734
|
+
recommendations,
|
|
735
|
+
potentialSavings: Math.floor(potentialSavings)
|
|
748
736
|
};
|
|
749
737
|
}
|
|
750
|
-
|
|
751
|
-
|
|
738
|
+
function isBuildArtifact(filePath) {
|
|
739
|
+
const lower = filePath.toLowerCase();
|
|
740
|
+
return lower.includes("/node_modules/") || lower.includes("/dist/") || lower.includes("/build/") || lower.includes("/out/") || lower.includes("/.next/");
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
// src/graph-builder.ts
|
|
752
744
|
var import_core4 = require("@aiready/core");
|
|
745
|
+
init_dependency_graph_utils();
|
|
753
746
|
var import_path = require("path");
|
|
754
747
|
function resolveImport(source, importingFile, allFiles) {
|
|
755
748
|
if (!source.startsWith(".") && !source.startsWith("/")) {
|
|
@@ -797,25 +790,12 @@ function extractDomainKeywordsFromPaths(files) {
|
|
|
797
790
|
for (const segment of segments) {
|
|
798
791
|
const normalized = segment.toLowerCase();
|
|
799
792
|
if (normalized && !skipFolders.has(normalized) && !normalized.includes(".")) {
|
|
800
|
-
folderNames.add(
|
|
793
|
+
folderNames.add(singularize(normalized));
|
|
801
794
|
}
|
|
802
795
|
}
|
|
803
796
|
}
|
|
804
797
|
return Array.from(folderNames);
|
|
805
798
|
}
|
|
806
|
-
function singularize2(word) {
|
|
807
|
-
const irregulars = {
|
|
808
|
-
people: "person",
|
|
809
|
-
children: "child",
|
|
810
|
-
men: "man",
|
|
811
|
-
women: "woman"
|
|
812
|
-
};
|
|
813
|
-
if (irregulars[word]) return irregulars[word];
|
|
814
|
-
if (word.endsWith("ies")) return word.slice(0, -3) + "y";
|
|
815
|
-
if (word.endsWith("ses")) return word.slice(0, -2);
|
|
816
|
-
if (word.endsWith("s") && word.length > 3) return word.slice(0, -1);
|
|
817
|
-
return word;
|
|
818
|
-
}
|
|
819
799
|
function buildDependencyGraph(files, options) {
|
|
820
800
|
const nodes = /* @__PURE__ */ new Map();
|
|
821
801
|
const edges = /* @__PURE__ */ new Map();
|
|
@@ -866,31 +846,10 @@ function buildDependencyGraph(files, options) {
|
|
|
866
846
|
return graph;
|
|
867
847
|
}
|
|
868
848
|
function calculateImportDepth(file, graph, visited = /* @__PURE__ */ new Set(), depth = 0) {
|
|
869
|
-
|
|
870
|
-
const dependencies = graph.edges.get(file);
|
|
871
|
-
if (!dependencies || dependencies.size === 0) return depth;
|
|
872
|
-
visited.add(file);
|
|
873
|
-
let maxDepth = depth;
|
|
874
|
-
for (const dep of dependencies) {
|
|
875
|
-
maxDepth = Math.max(
|
|
876
|
-
maxDepth,
|
|
877
|
-
calculateImportDepth(dep, graph, visited, depth + 1)
|
|
878
|
-
);
|
|
879
|
-
}
|
|
880
|
-
visited.delete(file);
|
|
881
|
-
return maxDepth;
|
|
849
|
+
return calculateImportDepthFromEdges(file, graph.edges, visited, depth);
|
|
882
850
|
}
|
|
883
851
|
function getTransitiveDependencies(file, graph, visited = /* @__PURE__ */ new Set()) {
|
|
884
|
-
|
|
885
|
-
visited.add(file);
|
|
886
|
-
const dependencies = graph.edges.get(file);
|
|
887
|
-
if (!dependencies || dependencies.size === 0) return [];
|
|
888
|
-
const allDeps = [];
|
|
889
|
-
for (const dep of dependencies) {
|
|
890
|
-
allDeps.push(dep);
|
|
891
|
-
allDeps.push(...getTransitiveDependencies(dep, graph, visited));
|
|
892
|
-
}
|
|
893
|
-
return [...new Set(allDeps)];
|
|
852
|
+
return getTransitiveDependenciesFromEdges(file, graph.edges, visited);
|
|
894
853
|
}
|
|
895
854
|
function calculateContextBudget(file, graph) {
|
|
896
855
|
const node = graph.nodes.get(file);
|
|
@@ -906,35 +865,79 @@ function calculateContextBudget(file, graph) {
|
|
|
906
865
|
return totalTokens;
|
|
907
866
|
}
|
|
908
867
|
function detectCircularDependencies(graph) {
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
868
|
+
return detectGraphCycles(graph.edges);
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
// src/cluster-detector.ts
|
|
872
|
+
function detectModuleClusters(graph, options) {
|
|
873
|
+
const domainMap = /* @__PURE__ */ new Map();
|
|
874
|
+
for (const [file, node] of graph.nodes.entries()) {
|
|
875
|
+
const primaryDomain = node.exports[0]?.inferredDomain || "unknown";
|
|
876
|
+
if (!domainMap.has(primaryDomain)) {
|
|
877
|
+
domainMap.set(primaryDomain, []);
|
|
919
878
|
}
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
879
|
+
domainMap.get(primaryDomain).push(file);
|
|
880
|
+
}
|
|
881
|
+
const clusters = [];
|
|
882
|
+
for (const [domain, files] of domainMap.entries()) {
|
|
883
|
+
if (files.length < 2 || domain === "unknown") continue;
|
|
884
|
+
const totalTokens = files.reduce((sum, file) => {
|
|
885
|
+
const node = graph.nodes.get(file);
|
|
886
|
+
return sum + (node?.tokenCost || 0);
|
|
887
|
+
}, 0);
|
|
888
|
+
let sharedImportRatio = 0;
|
|
889
|
+
if (files.length >= 2) {
|
|
890
|
+
const allImportSets = files.map(
|
|
891
|
+
(f) => new Set(graph.nodes.get(f)?.imports || [])
|
|
892
|
+
);
|
|
893
|
+
let intersection = new Set(allImportSets[0]);
|
|
894
|
+
const union = new Set(allImportSets[0]);
|
|
895
|
+
for (let i = 1; i < allImportSets.length; i++) {
|
|
896
|
+
const nextSet = allImportSets[i];
|
|
897
|
+
intersection = new Set([...intersection].filter((x) => nextSet.has(x)));
|
|
898
|
+
for (const x of nextSet) union.add(x);
|
|
928
899
|
}
|
|
900
|
+
sharedImportRatio = union.size > 0 ? intersection.size / union.size : 0;
|
|
929
901
|
}
|
|
930
|
-
|
|
902
|
+
const fragmentation = calculateFragmentation(files, domain, {
|
|
903
|
+
...options,
|
|
904
|
+
sharedImportRatio
|
|
905
|
+
});
|
|
906
|
+
let totalCohesion = 0;
|
|
907
|
+
files.forEach((f) => {
|
|
908
|
+
const node = graph.nodes.get(f);
|
|
909
|
+
if (node) totalCohesion += calculateEnhancedCohesion(node.exports);
|
|
910
|
+
});
|
|
911
|
+
const avgCohesion = totalCohesion / files.length;
|
|
912
|
+
clusters.push({
|
|
913
|
+
domain,
|
|
914
|
+
files,
|
|
915
|
+
totalTokens,
|
|
916
|
+
fragmentationScore: fragmentation,
|
|
917
|
+
avgCohesion,
|
|
918
|
+
suggestedStructure: generateSuggestedStructure(
|
|
919
|
+
files,
|
|
920
|
+
totalTokens,
|
|
921
|
+
fragmentation
|
|
922
|
+
)
|
|
923
|
+
});
|
|
931
924
|
}
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
925
|
+
return clusters;
|
|
926
|
+
}
|
|
927
|
+
function generateSuggestedStructure(files, tokens, fragmentation) {
|
|
928
|
+
const targetFiles = Math.max(1, Math.ceil(tokens / 1e4));
|
|
929
|
+
const plan = [];
|
|
930
|
+
if (fragmentation > 0.5) {
|
|
931
|
+
plan.push(
|
|
932
|
+
`Consolidate ${files.length} files scattered across multiple directories into ${targetFiles} core module(s)`
|
|
933
|
+
);
|
|
936
934
|
}
|
|
937
|
-
|
|
935
|
+
if (tokens > 2e4) {
|
|
936
|
+
plan.push(
|
|
937
|
+
`Domain logic is very large (${Math.round(tokens / 1e3)}k tokens). Ensure clear sub-domain boundaries.`
|
|
938
|
+
);
|
|
939
|
+
}
|
|
940
|
+
return { targetFiles, consolidationPlan: plan };
|
|
938
941
|
}
|
|
939
942
|
|
|
940
943
|
// src/classifier.ts
|
|
@@ -1249,78 +1252,6 @@ function adjustFragmentationForClassification(baseFragmentation, classification)
|
|
|
1249
1252
|
}
|
|
1250
1253
|
}
|
|
1251
1254
|
|
|
1252
|
-
// src/cluster-detector.ts
|
|
1253
|
-
function detectModuleClusters(graph, options) {
|
|
1254
|
-
const domainMap = /* @__PURE__ */ new Map();
|
|
1255
|
-
for (const [file, node] of graph.nodes.entries()) {
|
|
1256
|
-
const primaryDomain = node.exports[0]?.inferredDomain || "unknown";
|
|
1257
|
-
if (!domainMap.has(primaryDomain)) {
|
|
1258
|
-
domainMap.set(primaryDomain, []);
|
|
1259
|
-
}
|
|
1260
|
-
domainMap.get(primaryDomain).push(file);
|
|
1261
|
-
}
|
|
1262
|
-
const clusters = [];
|
|
1263
|
-
for (const [domain, files] of domainMap.entries()) {
|
|
1264
|
-
if (files.length < 2 || domain === "unknown") continue;
|
|
1265
|
-
const totalTokens = files.reduce((sum, file) => {
|
|
1266
|
-
const node = graph.nodes.get(file);
|
|
1267
|
-
return sum + (node?.tokenCost || 0);
|
|
1268
|
-
}, 0);
|
|
1269
|
-
let sharedImportRatio = 0;
|
|
1270
|
-
if (files.length >= 2) {
|
|
1271
|
-
const allImportSets = files.map(
|
|
1272
|
-
(f) => new Set(graph.nodes.get(f)?.imports || [])
|
|
1273
|
-
);
|
|
1274
|
-
let intersection = new Set(allImportSets[0]);
|
|
1275
|
-
let union = new Set(allImportSets[0]);
|
|
1276
|
-
for (let i = 1; i < allImportSets.length; i++) {
|
|
1277
|
-
const nextSet = allImportSets[i];
|
|
1278
|
-
intersection = new Set([...intersection].filter((x) => nextSet.has(x)));
|
|
1279
|
-
for (const x of nextSet) union.add(x);
|
|
1280
|
-
}
|
|
1281
|
-
sharedImportRatio = union.size > 0 ? intersection.size / union.size : 0;
|
|
1282
|
-
}
|
|
1283
|
-
const fragmentation = calculateFragmentation(files, domain, {
|
|
1284
|
-
...options,
|
|
1285
|
-
sharedImportRatio
|
|
1286
|
-
});
|
|
1287
|
-
let totalCohesion = 0;
|
|
1288
|
-
files.forEach((f) => {
|
|
1289
|
-
const node = graph.nodes.get(f);
|
|
1290
|
-
if (node) totalCohesion += calculateEnhancedCohesion(node.exports);
|
|
1291
|
-
});
|
|
1292
|
-
const avgCohesion = totalCohesion / files.length;
|
|
1293
|
-
clusters.push({
|
|
1294
|
-
domain,
|
|
1295
|
-
files,
|
|
1296
|
-
totalTokens,
|
|
1297
|
-
fragmentationScore: fragmentation,
|
|
1298
|
-
avgCohesion,
|
|
1299
|
-
suggestedStructure: generateSuggestedStructure(
|
|
1300
|
-
files,
|
|
1301
|
-
totalTokens,
|
|
1302
|
-
fragmentation
|
|
1303
|
-
)
|
|
1304
|
-
});
|
|
1305
|
-
}
|
|
1306
|
-
return clusters;
|
|
1307
|
-
}
|
|
1308
|
-
function generateSuggestedStructure(files, tokens, fragmentation) {
|
|
1309
|
-
const targetFiles = Math.max(1, Math.ceil(tokens / 1e4));
|
|
1310
|
-
const plan = [];
|
|
1311
|
-
if (fragmentation > 0.5) {
|
|
1312
|
-
plan.push(
|
|
1313
|
-
`Consolidate ${files.length} files scattered across multiple directories into ${targetFiles} core module(s)`
|
|
1314
|
-
);
|
|
1315
|
-
}
|
|
1316
|
-
if (tokens > 2e4) {
|
|
1317
|
-
plan.push(
|
|
1318
|
-
`Domain logic is very large (${Math.round(tokens / 1e3)}k tokens). Ensure clear sub-domain boundaries.`
|
|
1319
|
-
);
|
|
1320
|
-
}
|
|
1321
|
-
return { targetFiles, consolidationPlan: plan };
|
|
1322
|
-
}
|
|
1323
|
-
|
|
1324
1255
|
// src/remediation.ts
|
|
1325
1256
|
function getClassificationRecommendations(classification, file, issues) {
|
|
1326
1257
|
switch (classification) {
|
|
@@ -1381,150 +1312,37 @@ function getClassificationRecommendations(classification, file, issues) {
|
|
|
1381
1312
|
}
|
|
1382
1313
|
|
|
1383
1314
|
// src/analyzer.ts
|
|
1384
|
-
function
|
|
1385
|
-
if (exports2.length <= 1) return 1;
|
|
1386
|
-
if (filePath && isTestFile(filePath)) return 1;
|
|
1387
|
-
const domains = exports2.map((e) => e.inferredDomain || "unknown");
|
|
1388
|
-
const uniqueDomains = new Set(domains.filter((d) => d !== "unknown"));
|
|
1389
|
-
const hasImports = exports2.some((e) => !!e.imports);
|
|
1390
|
-
if (!hasImports && !options?.weights) {
|
|
1391
|
-
if (uniqueDomains.size <= 1) return 1;
|
|
1392
|
-
return 0.4;
|
|
1393
|
-
}
|
|
1394
|
-
return calculateEnhancedCohesion(exports2, filePath, options);
|
|
1395
|
-
}
|
|
1396
|
-
function analyzeIssues(params) {
|
|
1315
|
+
async function analyzeContext(options) {
|
|
1397
1316
|
const {
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
if (severity !== import_core6.Severity.Critical) severity = import_core6.Severity.Major;
|
|
1428
|
-
issues.push(
|
|
1429
|
-
`Import depth ${importDepth} exceeds recommended maximum ${maxDepth}`
|
|
1430
|
-
);
|
|
1431
|
-
recommendations.push("Consider reducing dependency depth");
|
|
1432
|
-
potentialSavings += contextBudget * 0.15;
|
|
1433
|
-
}
|
|
1434
|
-
if (contextBudget > maxContextBudget * 1.5) {
|
|
1435
|
-
severity = import_core6.Severity.Critical;
|
|
1436
|
-
issues.push(
|
|
1437
|
-
`Context budget ${contextBudget.toLocaleString()} tokens is 50% over limit`
|
|
1438
|
-
);
|
|
1439
|
-
recommendations.push(
|
|
1440
|
-
"Split into smaller modules or reduce dependency tree"
|
|
1441
|
-
);
|
|
1442
|
-
potentialSavings += contextBudget * 0.4;
|
|
1443
|
-
} else if (contextBudget > maxContextBudget) {
|
|
1444
|
-
if (severity !== import_core6.Severity.Critical) severity = import_core6.Severity.Major;
|
|
1445
|
-
issues.push(
|
|
1446
|
-
`Context budget ${contextBudget.toLocaleString()} exceeds ${maxContextBudget.toLocaleString()}`
|
|
1447
|
-
);
|
|
1448
|
-
recommendations.push("Reduce file size or dependencies");
|
|
1449
|
-
potentialSavings += contextBudget * 0.2;
|
|
1450
|
-
}
|
|
1451
|
-
if (cohesionScore < minCohesion * 0.5) {
|
|
1452
|
-
if (severity !== import_core6.Severity.Critical) severity = import_core6.Severity.Major;
|
|
1453
|
-
issues.push(
|
|
1454
|
-
`Very low cohesion (${(cohesionScore * 100).toFixed(0)}%) - mixed concerns`
|
|
1455
|
-
);
|
|
1456
|
-
recommendations.push(
|
|
1457
|
-
"Split file by domain - separate unrelated functionality"
|
|
1458
|
-
);
|
|
1459
|
-
potentialSavings += contextBudget * 0.25;
|
|
1460
|
-
} else if (cohesionScore < minCohesion) {
|
|
1461
|
-
if (severity === import_core6.Severity.Info) severity = import_core6.Severity.Minor;
|
|
1462
|
-
issues.push(`Low cohesion (${(cohesionScore * 100).toFixed(0)}%)`);
|
|
1463
|
-
recommendations.push("Consider grouping related exports together");
|
|
1464
|
-
potentialSavings += contextBudget * 0.1;
|
|
1465
|
-
}
|
|
1466
|
-
if (fragmentationScore > maxFragmentation) {
|
|
1467
|
-
if (severity === import_core6.Severity.Info || severity === import_core6.Severity.Minor)
|
|
1468
|
-
severity = import_core6.Severity.Minor;
|
|
1469
|
-
issues.push(
|
|
1470
|
-
`High fragmentation (${(fragmentationScore * 100).toFixed(0)}%) - scattered implementation`
|
|
1471
|
-
);
|
|
1472
|
-
recommendations.push("Consolidate with related files in same domain");
|
|
1473
|
-
potentialSavings += contextBudget * 0.3;
|
|
1474
|
-
}
|
|
1475
|
-
if (issues.length === 0) {
|
|
1476
|
-
issues.push("No significant issues detected");
|
|
1477
|
-
recommendations.push("File is well-structured for AI context usage");
|
|
1478
|
-
}
|
|
1479
|
-
if (isBuildArtifact(file)) {
|
|
1480
|
-
issues.push("Detected build artifact (bundled/output file)");
|
|
1481
|
-
recommendations.push("Exclude build outputs from analysis");
|
|
1482
|
-
severity = import_core6.Severity.Info;
|
|
1483
|
-
potentialSavings = 0;
|
|
1484
|
-
}
|
|
1485
|
-
return {
|
|
1486
|
-
severity,
|
|
1487
|
-
issues,
|
|
1488
|
-
recommendations,
|
|
1489
|
-
potentialSavings: Math.floor(potentialSavings)
|
|
1490
|
-
};
|
|
1491
|
-
}
|
|
1492
|
-
function isBuildArtifact(filePath) {
|
|
1493
|
-
const lower = filePath.toLowerCase();
|
|
1494
|
-
return lower.includes("/node_modules/") || lower.includes("/dist/") || lower.includes("/build/") || lower.includes("/out/") || lower.includes("/.next/");
|
|
1495
|
-
}
|
|
1496
|
-
async function analyzeContext(options) {
|
|
1497
|
-
const {
|
|
1498
|
-
maxDepth = 5,
|
|
1499
|
-
maxContextBudget = 1e4,
|
|
1500
|
-
minCohesion = 0.6,
|
|
1501
|
-
maxFragmentation = 0.5,
|
|
1502
|
-
focus = "all",
|
|
1503
|
-
includeNodeModules = false,
|
|
1504
|
-
...scanOptions
|
|
1505
|
-
} = options;
|
|
1506
|
-
const files = await (0, import_core6.scanFiles)({
|
|
1507
|
-
...scanOptions,
|
|
1508
|
-
exclude: includeNodeModules && scanOptions.exclude ? scanOptions.exclude.filter(
|
|
1509
|
-
(pattern) => pattern !== "**/node_modules/**"
|
|
1510
|
-
) : scanOptions.exclude
|
|
1511
|
-
});
|
|
1512
|
-
const pythonFiles = files.filter((f) => f.toLowerCase().endsWith(".py"));
|
|
1513
|
-
const fileContents = await Promise.all(
|
|
1514
|
-
files.map(async (file) => ({
|
|
1515
|
-
file,
|
|
1516
|
-
content: await (0, import_core6.readFileContent)(file)
|
|
1517
|
-
}))
|
|
1518
|
-
);
|
|
1519
|
-
const graph = buildDependencyGraph(
|
|
1520
|
-
fileContents.filter((f) => !f.file.toLowerCase().endsWith(".py"))
|
|
1521
|
-
);
|
|
1522
|
-
let pythonResults = [];
|
|
1523
|
-
if (pythonFiles.length > 0) {
|
|
1524
|
-
const { analyzePythonContext: analyzePythonContext2 } = await Promise.resolve().then(() => (init_python_context(), python_context_exports));
|
|
1525
|
-
const pythonMetrics = await analyzePythonContext2(
|
|
1526
|
-
pythonFiles,
|
|
1527
|
-
scanOptions.rootDir || options.rootDir || "."
|
|
1317
|
+
maxDepth = 5,
|
|
1318
|
+
maxContextBudget = 1e4,
|
|
1319
|
+
minCohesion = 0.6,
|
|
1320
|
+
maxFragmentation = 0.5,
|
|
1321
|
+
includeNodeModules = false,
|
|
1322
|
+
...scanOptions
|
|
1323
|
+
} = options;
|
|
1324
|
+
const files = await (0, import_core6.scanFiles)({
|
|
1325
|
+
...scanOptions,
|
|
1326
|
+
exclude: includeNodeModules && scanOptions.exclude ? scanOptions.exclude.filter(
|
|
1327
|
+
(pattern) => pattern !== "**/node_modules/**"
|
|
1328
|
+
) : scanOptions.exclude
|
|
1329
|
+
});
|
|
1330
|
+
const pythonFiles = files.filter((f) => f.toLowerCase().endsWith(".py"));
|
|
1331
|
+
const fileContents = await Promise.all(
|
|
1332
|
+
files.map(async (file) => ({
|
|
1333
|
+
file,
|
|
1334
|
+
content: await (0, import_core6.readFileContent)(file)
|
|
1335
|
+
}))
|
|
1336
|
+
);
|
|
1337
|
+
const graph = buildDependencyGraph(
|
|
1338
|
+
fileContents.filter((f) => !f.file.toLowerCase().endsWith(".py"))
|
|
1339
|
+
);
|
|
1340
|
+
let pythonResults = [];
|
|
1341
|
+
if (pythonFiles.length > 0) {
|
|
1342
|
+
const { analyzePythonContext: analyzePythonContext2 } = await Promise.resolve().then(() => (init_python_context(), python_context_exports));
|
|
1343
|
+
const pythonMetrics = await analyzePythonContext2(
|
|
1344
|
+
pythonFiles,
|
|
1345
|
+
scanOptions.rootDir || options.rootDir || "."
|
|
1528
1346
|
);
|
|
1529
1347
|
pythonResults = pythonMetrics.map((metric) => {
|
|
1530
1348
|
const { severity, issues, recommendations, potentialSavings } = analyzeIssues({
|
|
@@ -1537,27 +1355,21 @@ async function analyzeContext(options) {
|
|
|
1537
1355
|
maxContextBudget,
|
|
1538
1356
|
minCohesion,
|
|
1539
1357
|
maxFragmentation,
|
|
1540
|
-
circularDeps:
|
|
1541
|
-
(cycle) => cycle.split(" \u2192 ")
|
|
1542
|
-
)
|
|
1358
|
+
circularDeps: []
|
|
1543
1359
|
});
|
|
1544
1360
|
return {
|
|
1545
1361
|
file: metric.file,
|
|
1546
|
-
tokenCost:
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
linesOfCode: metric.metrics.linesOfCode,
|
|
1362
|
+
tokenCost: 0,
|
|
1363
|
+
linesOfCode: 0,
|
|
1364
|
+
// Not provided by python context yet
|
|
1550
1365
|
importDepth: metric.importDepth,
|
|
1551
|
-
dependencyCount:
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
circularDeps: metric.metrics.circularDependencies.map(
|
|
1556
|
-
(cycle) => cycle.split(" \u2192 ")
|
|
1557
|
-
),
|
|
1366
|
+
dependencyCount: 0,
|
|
1367
|
+
// Not provided
|
|
1368
|
+
dependencyList: [],
|
|
1369
|
+
circularDeps: [],
|
|
1558
1370
|
cohesionScore: metric.cohesion,
|
|
1559
|
-
domains: [
|
|
1560
|
-
exportCount:
|
|
1371
|
+
domains: [],
|
|
1372
|
+
exportCount: 0,
|
|
1561
1373
|
contextBudget: metric.contextBudget,
|
|
1562
1374
|
fragmentationScore: 0,
|
|
1563
1375
|
relatedFiles: [],
|
|
@@ -1569,579 +1381,202 @@ async function analyzeContext(options) {
|
|
|
1569
1381
|
};
|
|
1570
1382
|
});
|
|
1571
1383
|
}
|
|
1572
|
-
const
|
|
1573
|
-
const
|
|
1574
|
-
const
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1384
|
+
const clusters = detectModuleClusters(graph);
|
|
1385
|
+
const allCircularDeps = detectCircularDependencies(graph);
|
|
1386
|
+
const results = Array.from(graph.nodes.values()).map(
|
|
1387
|
+
(node) => {
|
|
1388
|
+
const file = node.file;
|
|
1389
|
+
const tokenCost = node.tokenCost;
|
|
1390
|
+
const importDepth = calculateImportDepth(file, graph);
|
|
1391
|
+
const transitiveDeps = getTransitiveDependencies(file, graph);
|
|
1392
|
+
const contextBudget = calculateContextBudget(file, graph);
|
|
1393
|
+
const circularDeps = allCircularDeps.filter(
|
|
1394
|
+
(cycle) => cycle.includes(file)
|
|
1395
|
+
);
|
|
1396
|
+
const cluster = clusters.find((c) => c.files.includes(file));
|
|
1397
|
+
const rawFragmentationScore = cluster ? cluster.fragmentationScore : 0;
|
|
1398
|
+
const rawCohesionScore = calculateEnhancedCohesion(
|
|
1399
|
+
node.exports,
|
|
1400
|
+
file,
|
|
1401
|
+
options
|
|
1402
|
+
);
|
|
1403
|
+
const fileClassification = classifyFile(node, rawCohesionScore);
|
|
1404
|
+
const cohesionScore = adjustCohesionForClassification(
|
|
1405
|
+
rawCohesionScore,
|
|
1406
|
+
fileClassification
|
|
1407
|
+
);
|
|
1408
|
+
const fragmentationScore = adjustFragmentationForClassification(
|
|
1409
|
+
rawFragmentationScore,
|
|
1410
|
+
fileClassification
|
|
1411
|
+
);
|
|
1412
|
+
const { severity, issues, recommendations, potentialSavings } = analyzeIssues({
|
|
1413
|
+
file,
|
|
1414
|
+
importDepth,
|
|
1415
|
+
contextBudget,
|
|
1416
|
+
cohesionScore,
|
|
1417
|
+
fragmentationScore,
|
|
1418
|
+
maxDepth,
|
|
1419
|
+
maxContextBudget,
|
|
1420
|
+
minCohesion,
|
|
1421
|
+
maxFragmentation,
|
|
1422
|
+
circularDeps
|
|
1423
|
+
});
|
|
1424
|
+
const classRecs = getClassificationRecommendations(
|
|
1425
|
+
fileClassification,
|
|
1426
|
+
file,
|
|
1427
|
+
issues
|
|
1428
|
+
);
|
|
1429
|
+
const allRecommendations = Array.from(
|
|
1430
|
+
/* @__PURE__ */ new Set([...recommendations, ...classRecs])
|
|
1431
|
+
);
|
|
1432
|
+
return {
|
|
1433
|
+
file,
|
|
1434
|
+
tokenCost,
|
|
1435
|
+
linesOfCode: node.linesOfCode,
|
|
1436
|
+
importDepth,
|
|
1437
|
+
dependencyCount: transitiveDeps.length,
|
|
1438
|
+
dependencyList: transitiveDeps,
|
|
1439
|
+
circularDeps,
|
|
1440
|
+
cohesionScore,
|
|
1441
|
+
domains: Array.from(
|
|
1442
|
+
new Set(
|
|
1443
|
+
node.exports.flatMap((e) => e.domains?.map((d) => d.domain) || [])
|
|
1444
|
+
)
|
|
1445
|
+
),
|
|
1446
|
+
exportCount: node.exports.length,
|
|
1447
|
+
contextBudget,
|
|
1448
|
+
fragmentationScore,
|
|
1449
|
+
relatedFiles: cluster ? cluster.files : [],
|
|
1450
|
+
fileClassification,
|
|
1451
|
+
severity,
|
|
1452
|
+
issues,
|
|
1453
|
+
recommendations: allRecommendations,
|
|
1454
|
+
potentialSavings
|
|
1455
|
+
};
|
|
1598
1456
|
}
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
importDepth,
|
|
1602
|
-
contextBudget,
|
|
1603
|
-
cohesionScore,
|
|
1604
|
-
fragmentationScore,
|
|
1605
|
-
maxDepth,
|
|
1606
|
-
maxContextBudget,
|
|
1607
|
-
minCohesion,
|
|
1608
|
-
maxFragmentation,
|
|
1609
|
-
circularDeps
|
|
1610
|
-
});
|
|
1611
|
-
const domains = [
|
|
1612
|
-
...new Set(node.exports.map((e) => e.inferredDomain || "unknown"))
|
|
1613
|
-
];
|
|
1614
|
-
const fileClassification = classifyFile(node);
|
|
1615
|
-
const adjustedCohesionScore = adjustCohesionForClassification(
|
|
1616
|
-
cohesionScore,
|
|
1617
|
-
fileClassification,
|
|
1618
|
-
node
|
|
1619
|
-
);
|
|
1620
|
-
const adjustedFragmentationScore = adjustFragmentationForClassification(
|
|
1621
|
-
fragmentationScore,
|
|
1622
|
-
fileClassification
|
|
1623
|
-
);
|
|
1624
|
-
const classificationRecommendations = getClassificationRecommendations(
|
|
1625
|
-
fileClassification,
|
|
1626
|
-
file,
|
|
1627
|
-
issues
|
|
1628
|
-
);
|
|
1629
|
-
const {
|
|
1630
|
-
severity: adjustedSeverity,
|
|
1631
|
-
issues: adjustedIssues,
|
|
1632
|
-
recommendations: finalRecommendations,
|
|
1633
|
-
potentialSavings: adjustedSavings
|
|
1634
|
-
} = analyzeIssues({
|
|
1635
|
-
file,
|
|
1636
|
-
importDepth,
|
|
1637
|
-
contextBudget,
|
|
1638
|
-
cohesionScore: adjustedCohesionScore,
|
|
1639
|
-
fragmentationScore: adjustedFragmentationScore,
|
|
1640
|
-
maxDepth,
|
|
1641
|
-
maxContextBudget,
|
|
1642
|
-
minCohesion,
|
|
1643
|
-
maxFragmentation,
|
|
1644
|
-
circularDeps
|
|
1645
|
-
});
|
|
1646
|
-
results.push({
|
|
1647
|
-
file,
|
|
1648
|
-
tokenCost: node.tokenCost,
|
|
1649
|
-
linesOfCode: node.linesOfCode,
|
|
1650
|
-
importDepth,
|
|
1651
|
-
dependencyCount: dependencyList.length,
|
|
1652
|
-
dependencyList,
|
|
1653
|
-
circularDeps: circularDeps.filter((cycle) => cycle.includes(file)),
|
|
1654
|
-
cohesionScore: adjustedCohesionScore,
|
|
1655
|
-
domains,
|
|
1656
|
-
exportCount: node.exports.length,
|
|
1657
|
-
contextBudget,
|
|
1658
|
-
fragmentationScore: adjustedFragmentationScore,
|
|
1659
|
-
relatedFiles,
|
|
1660
|
-
fileClassification,
|
|
1661
|
-
severity: adjustedSeverity,
|
|
1662
|
-
issues: adjustedIssues,
|
|
1663
|
-
recommendations: [
|
|
1664
|
-
...finalRecommendations,
|
|
1665
|
-
...classificationRecommendations.slice(0, 1)
|
|
1666
|
-
],
|
|
1667
|
-
potentialSavings: adjustedSavings
|
|
1668
|
-
});
|
|
1669
|
-
}
|
|
1670
|
-
const allResults = [...results, ...pythonResults];
|
|
1671
|
-
const finalSummary = generateSummary(allResults, options);
|
|
1672
|
-
return allResults.sort((a, b) => {
|
|
1673
|
-
const severityOrder = { critical: 0, major: 1, minor: 2, info: 3 };
|
|
1674
|
-
const severityDiff = severityOrder[a.severity] - severityOrder[b.severity];
|
|
1675
|
-
if (severityDiff !== 0) return severityDiff;
|
|
1676
|
-
return b.contextBudget - a.contextBudget;
|
|
1677
|
-
});
|
|
1457
|
+
);
|
|
1458
|
+
return [...results, ...pythonResults];
|
|
1678
1459
|
}
|
|
1679
1460
|
|
|
1680
|
-
// src/
|
|
1461
|
+
// src/summary.ts
|
|
1681
1462
|
var import_core7 = require("@aiready/core");
|
|
1682
|
-
function
|
|
1683
|
-
const
|
|
1463
|
+
function generateSummary(results, options = {}) {
|
|
1464
|
+
const config = options ? Object.fromEntries(
|
|
1465
|
+
Object.entries(options).filter(
|
|
1466
|
+
([key]) => !import_core7.GLOBAL_SCAN_OPTIONS.includes(key) || key === "rootDir"
|
|
1467
|
+
)
|
|
1468
|
+
) : {};
|
|
1469
|
+
const totalFiles = results.length;
|
|
1470
|
+
const totalTokens = results.reduce((sum, r) => sum + r.tokenCost, 0);
|
|
1471
|
+
const avgContextBudget = totalFiles > 0 ? results.reduce((sum, r) => sum + r.contextBudget, 0) / totalFiles : 0;
|
|
1472
|
+
const deepFiles = results.filter((r) => r.importDepth > 5).map((r) => ({ file: r.file, depth: r.importDepth }));
|
|
1473
|
+
const maxImportDepth = Math.max(0, ...results.map((r) => r.importDepth));
|
|
1474
|
+
const moduleMap = /* @__PURE__ */ new Map();
|
|
1475
|
+
results.forEach((r) => {
|
|
1476
|
+
const parts = r.file.split("/");
|
|
1477
|
+
let domain = "root";
|
|
1478
|
+
if (parts.length > 2) {
|
|
1479
|
+
domain = parts.slice(0, 2).join("/");
|
|
1480
|
+
}
|
|
1481
|
+
if (!moduleMap.has(domain)) moduleMap.set(domain, []);
|
|
1482
|
+
moduleMap.get(domain).push(r);
|
|
1483
|
+
});
|
|
1484
|
+
const fragmentedModules = [];
|
|
1485
|
+
moduleMap.forEach((files, domain) => {
|
|
1486
|
+
const clusterTokens = files.reduce((sum, f) => sum + f.tokenCost, 0);
|
|
1487
|
+
const filePaths = files.map((f) => f.file);
|
|
1488
|
+
const avgEntropy = calculatePathEntropy(filePaths);
|
|
1489
|
+
const fragmentationScore = Math.min(1, avgEntropy * (files.length / 10));
|
|
1490
|
+
if (fragmentationScore > 0.4) {
|
|
1491
|
+
fragmentedModules.push({
|
|
1492
|
+
domain,
|
|
1493
|
+
files: filePaths,
|
|
1494
|
+
fragmentationScore,
|
|
1495
|
+
totalTokens: clusterTokens,
|
|
1496
|
+
avgCohesion: files.reduce((sum, f) => sum + f.cohesionScore, 0) / files.length,
|
|
1497
|
+
suggestedStructure: {
|
|
1498
|
+
targetFiles: Math.ceil(files.length / 2),
|
|
1499
|
+
consolidationPlan: [
|
|
1500
|
+
`Consolidate ${files.length} files in ${domain} into fewer modules`
|
|
1501
|
+
]
|
|
1502
|
+
}
|
|
1503
|
+
});
|
|
1504
|
+
}
|
|
1505
|
+
});
|
|
1506
|
+
fragmentedModules.sort((a, b) => b.fragmentationScore - a.fragmentationScore);
|
|
1507
|
+
const avgFragmentation = fragmentedModules.length > 0 ? fragmentedModules.reduce((sum, m) => sum + m.fragmentationScore, 0) / fragmentedModules.length : 0;
|
|
1508
|
+
const avgCohesion = results.reduce((sum, r) => sum + r.cohesionScore, 0) / (totalFiles || 1);
|
|
1509
|
+
const lowCohesionFiles = results.filter((r) => r.cohesionScore < 0.4).map((r) => ({ file: r.file, score: r.cohesionScore }));
|
|
1510
|
+
const criticalIssues = results.filter(
|
|
1511
|
+
(r) => r.severity === "critical"
|
|
1512
|
+
).length;
|
|
1513
|
+
const majorIssues = results.filter((r) => r.severity === "major").length;
|
|
1514
|
+
const minorIssues = results.filter((r) => r.severity === "minor").length;
|
|
1515
|
+
const totalPotentialSavings = results.reduce(
|
|
1516
|
+
(sum, r) => sum + (r.potentialSavings || 0),
|
|
1517
|
+
0
|
|
1518
|
+
);
|
|
1519
|
+
const topExpensiveFiles = [...results].sort((a, b) => b.contextBudget - a.contextBudget).slice(0, 10).map((r) => ({
|
|
1520
|
+
file: r.file,
|
|
1521
|
+
contextBudget: r.contextBudget,
|
|
1522
|
+
severity: r.severity
|
|
1523
|
+
}));
|
|
1524
|
+
return {
|
|
1525
|
+
totalFiles,
|
|
1526
|
+
totalTokens,
|
|
1684
1527
|
avgContextBudget,
|
|
1685
|
-
maxContextBudget,
|
|
1686
|
-
avgImportDepth,
|
|
1528
|
+
maxContextBudget: Math.max(0, ...results.map((r) => r.contextBudget)),
|
|
1529
|
+
avgImportDepth: results.reduce((sum, r) => sum + r.importDepth, 0) / (totalFiles || 1),
|
|
1687
1530
|
maxImportDepth,
|
|
1531
|
+
deepFiles,
|
|
1688
1532
|
avgFragmentation,
|
|
1533
|
+
fragmentedModules,
|
|
1534
|
+
avgCohesion,
|
|
1535
|
+
lowCohesionFiles,
|
|
1689
1536
|
criticalIssues,
|
|
1690
|
-
majorIssues
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
const criticalPenalty = criticalIssues * 10;
|
|
1696
|
-
const majorPenalty = majorIssues * 3;
|
|
1697
|
-
const maxBudgetPenalty = maxContextBudget > 15e3 ? Math.min(20, (maxContextBudget - 15e3) / 500) : 0;
|
|
1698
|
-
const rawScore = budgetScore * 0.4 + depthScore * 0.3 + fragmentationScore * 0.3;
|
|
1699
|
-
const finalScore = rawScore - criticalPenalty - majorPenalty - maxBudgetPenalty;
|
|
1700
|
-
const score = Math.max(0, Math.min(100, Math.round(finalScore)));
|
|
1701
|
-
const factors = [
|
|
1702
|
-
{
|
|
1703
|
-
name: "Context Budget",
|
|
1704
|
-
impact: Math.round(budgetScore * 0.4 - 40),
|
|
1705
|
-
description: `Avg ${Math.round(avgContextBudget)} tokens per file ${avgContextBudget < 5e3 ? "(excellent)" : avgContextBudget < 1e4 ? "(acceptable)" : "(high)"}`
|
|
1706
|
-
},
|
|
1707
|
-
{
|
|
1708
|
-
name: "Import Depth",
|
|
1709
|
-
impact: Math.round(depthScore * 0.3 - 30),
|
|
1710
|
-
description: `Avg ${avgImportDepth.toFixed(1)} levels ${avgImportDepth < 5 ? "(excellent)" : avgImportDepth < 8 ? "(acceptable)" : "(deep)"}`
|
|
1711
|
-
},
|
|
1712
|
-
{
|
|
1713
|
-
name: "Fragmentation",
|
|
1714
|
-
impact: Math.round(fragmentationScore * 0.3 - 30),
|
|
1715
|
-
description: `${(avgFragmentation * 100).toFixed(0)}% fragmentation ${avgFragmentation < 0.3 ? "(well-organized)" : avgFragmentation < 0.5 ? "(moderate)" : "(high)"}`
|
|
1716
|
-
}
|
|
1717
|
-
];
|
|
1718
|
-
if (criticalIssues > 0) {
|
|
1719
|
-
factors.push({
|
|
1720
|
-
name: "Critical Issues",
|
|
1721
|
-
impact: -criticalPenalty,
|
|
1722
|
-
description: `${criticalIssues} critical context issue${criticalIssues > 1 ? "s" : ""}`
|
|
1723
|
-
});
|
|
1724
|
-
}
|
|
1725
|
-
if (majorIssues > 0) {
|
|
1726
|
-
factors.push({
|
|
1727
|
-
name: "Major Issues",
|
|
1728
|
-
impact: -majorPenalty,
|
|
1729
|
-
description: `${majorIssues} major context issue${majorIssues > 1 ? "s" : ""}`
|
|
1730
|
-
});
|
|
1731
|
-
}
|
|
1732
|
-
if (maxBudgetPenalty > 0) {
|
|
1733
|
-
factors.push({
|
|
1734
|
-
name: "Extreme File Detected",
|
|
1735
|
-
impact: -Math.round(maxBudgetPenalty),
|
|
1736
|
-
description: `One file requires ${Math.round(maxContextBudget)} tokens (very high)`
|
|
1737
|
-
});
|
|
1738
|
-
}
|
|
1739
|
-
const recommendations = [];
|
|
1740
|
-
if (avgContextBudget > 1e4) {
|
|
1741
|
-
const estimatedImpact = Math.min(
|
|
1742
|
-
15,
|
|
1743
|
-
Math.round((avgContextBudget - 1e4) / 1e3)
|
|
1744
|
-
);
|
|
1745
|
-
recommendations.push({
|
|
1746
|
-
action: "Reduce file dependencies to lower context requirements",
|
|
1747
|
-
estimatedImpact,
|
|
1748
|
-
priority: "high"
|
|
1749
|
-
});
|
|
1750
|
-
}
|
|
1751
|
-
if (avgImportDepth > 8) {
|
|
1752
|
-
const estimatedImpact = Math.min(10, Math.round((avgImportDepth - 8) * 2));
|
|
1753
|
-
recommendations.push({
|
|
1754
|
-
action: "Flatten import chains to reduce depth",
|
|
1755
|
-
estimatedImpact,
|
|
1756
|
-
priority: avgImportDepth > 10 ? "high" : "medium"
|
|
1757
|
-
});
|
|
1758
|
-
}
|
|
1759
|
-
if (avgFragmentation > 0.5) {
|
|
1760
|
-
const estimatedImpact = Math.min(
|
|
1761
|
-
12,
|
|
1762
|
-
Math.round((avgFragmentation - 0.5) * 40)
|
|
1763
|
-
);
|
|
1764
|
-
recommendations.push({
|
|
1765
|
-
action: "Consolidate related code into cohesive modules",
|
|
1766
|
-
estimatedImpact,
|
|
1767
|
-
priority: "medium"
|
|
1768
|
-
});
|
|
1769
|
-
}
|
|
1770
|
-
if (maxContextBudget > 2e4) {
|
|
1771
|
-
recommendations.push({
|
|
1772
|
-
action: `Split large file (${Math.round(maxContextBudget)} tokens) into smaller modules`,
|
|
1773
|
-
estimatedImpact: 8,
|
|
1774
|
-
priority: "high"
|
|
1775
|
-
});
|
|
1776
|
-
}
|
|
1777
|
-
const cfg = { ...import_core7.DEFAULT_COST_CONFIG, ...costConfig };
|
|
1778
|
-
const estimatedMonthlyCost = (0, import_core7.calculateMonthlyCost)(
|
|
1779
|
-
avgContextBudget * (summary.totalFiles || 1),
|
|
1780
|
-
cfg
|
|
1781
|
-
);
|
|
1782
|
-
const issues = [
|
|
1783
|
-
...Array(criticalIssues).fill({ severity: "critical" }),
|
|
1784
|
-
...Array(majorIssues).fill({ severity: "major" })
|
|
1785
|
-
];
|
|
1786
|
-
const productivityImpact = (0, import_core7.calculateProductivityImpact)(issues);
|
|
1787
|
-
return {
|
|
1788
|
-
toolName: import_core7.ToolName.ContextAnalyzer,
|
|
1789
|
-
score,
|
|
1790
|
-
rawMetrics: {
|
|
1791
|
-
avgContextBudget: Math.round(avgContextBudget),
|
|
1792
|
-
maxContextBudget: Math.round(maxContextBudget),
|
|
1793
|
-
avgImportDepth: Math.round(avgImportDepth * 10) / 10,
|
|
1794
|
-
maxImportDepth,
|
|
1795
|
-
avgFragmentation: Math.round(avgFragmentation * 100) / 100,
|
|
1796
|
-
criticalIssues,
|
|
1797
|
-
majorIssues,
|
|
1798
|
-
estimatedMonthlyCost,
|
|
1799
|
-
estimatedDeveloperHours: productivityImpact.totalHours
|
|
1800
|
-
},
|
|
1801
|
-
factors,
|
|
1802
|
-
recommendations
|
|
1537
|
+
majorIssues,
|
|
1538
|
+
minorIssues,
|
|
1539
|
+
totalPotentialSavings,
|
|
1540
|
+
topExpensiveFiles,
|
|
1541
|
+
config
|
|
1803
1542
|
};
|
|
1804
1543
|
}
|
|
1805
1544
|
|
|
1806
|
-
// src/
|
|
1807
|
-
var ContextAnalyzerProvider = {
|
|
1808
|
-
id: import_core8.ToolName.ContextAnalyzer,
|
|
1809
|
-
alias: ["context", "fragmentation", "budget"],
|
|
1810
|
-
async analyze(options) {
|
|
1811
|
-
const results = await analyzeContext(options);
|
|
1812
|
-
const summary = generateSummary(results, options);
|
|
1813
|
-
const normalizedResults = results.map(
|
|
1814
|
-
(r) => ({
|
|
1815
|
-
fileName: r.file,
|
|
1816
|
-
issues: r.issues.map((msg) => ({
|
|
1817
|
-
type: import_core8.IssueType.ContextFragmentation,
|
|
1818
|
-
severity: r.severity,
|
|
1819
|
-
message: msg,
|
|
1820
|
-
location: { file: r.file, line: 1 },
|
|
1821
|
-
suggestion: r.recommendations[0]
|
|
1822
|
-
})),
|
|
1823
|
-
metrics: {
|
|
1824
|
-
tokenCost: r.tokenCost,
|
|
1825
|
-
complexityScore: r.importDepth
|
|
1826
|
-
// Map other context-specific metrics if needed
|
|
1827
|
-
}
|
|
1828
|
-
})
|
|
1829
|
-
);
|
|
1830
|
-
return import_core8.SpokeOutputSchema.parse({
|
|
1831
|
-
results: normalizedResults,
|
|
1832
|
-
summary: {
|
|
1833
|
-
...summary
|
|
1834
|
-
},
|
|
1835
|
-
metadata: {
|
|
1836
|
-
toolName: import_core8.ToolName.ContextAnalyzer,
|
|
1837
|
-
version: "0.17.5",
|
|
1838
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1839
|
-
}
|
|
1840
|
-
});
|
|
1841
|
-
},
|
|
1842
|
-
score(output, options) {
|
|
1843
|
-
const summary = output.summary;
|
|
1844
|
-
return calculateContextScore(summary, options.costConfig);
|
|
1845
|
-
},
|
|
1846
|
-
defaultWeight: 19
|
|
1847
|
-
};
|
|
1848
|
-
|
|
1849
|
-
// src/defaults.ts
|
|
1850
|
-
var import_core9 = require("@aiready/core");
|
|
1851
|
-
|
|
1852
|
-
// src/index.ts
|
|
1853
|
-
import_core10.ToolRegistry.register(ContextAnalyzerProvider);
|
|
1854
|
-
|
|
1855
|
-
// src/cli.ts
|
|
1545
|
+
// src/utils/output-formatter.ts
|
|
1856
1546
|
var import_chalk = __toESM(require("chalk"));
|
|
1857
1547
|
var import_fs2 = require("fs");
|
|
1858
1548
|
var import_path3 = require("path");
|
|
1859
|
-
var import_core11 = require("@aiready/core");
|
|
1860
1549
|
var import_prompts = __toESM(require("prompts"));
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
"--max-context <number>",
|
|
1867
|
-
"Maximum acceptable context budget (tokens)"
|
|
1868
|
-
).option("--min-cohesion <number>", "Minimum acceptable cohesion score (0-1)").option(
|
|
1869
|
-
"--max-fragmentation <number>",
|
|
1870
|
-
"Maximum acceptable fragmentation (0-1)"
|
|
1871
|
-
).option(
|
|
1872
|
-
"--focus <type>",
|
|
1873
|
-
"Analysis focus: fragmentation, cohesion, depth, all"
|
|
1874
|
-
).option("--include-node-modules", "Include node_modules in analysis").option("--include <patterns>", "File patterns to include (comma-separated)").option("--exclude <patterns>", "File patterns to exclude (comma-separated)").option(
|
|
1875
|
-
"--max-results <number>",
|
|
1876
|
-
"Maximum number of results to show in console output"
|
|
1877
|
-
).option(
|
|
1878
|
-
"-o, --output <format>",
|
|
1879
|
-
"Output format: console, json, html",
|
|
1880
|
-
"console"
|
|
1881
|
-
).option("--output-file <path>", "Output file path (for json/html)").option(
|
|
1882
|
-
"--interactive",
|
|
1883
|
-
"Run interactive setup to suggest excludes and focus areas"
|
|
1884
|
-
).action(async (directory, options) => {
|
|
1885
|
-
console.log(import_chalk.default.blue("\u{1F50D} Analyzing context window costs...\n"));
|
|
1886
|
-
const startTime = Date.now();
|
|
1887
|
-
try {
|
|
1888
|
-
const defaults = {
|
|
1889
|
-
maxDepth: 5,
|
|
1890
|
-
maxContextBudget: 1e4,
|
|
1891
|
-
minCohesion: 0.6,
|
|
1892
|
-
maxFragmentation: 0.5,
|
|
1893
|
-
focus: "all",
|
|
1894
|
-
includeNodeModules: false,
|
|
1895
|
-
include: void 0,
|
|
1896
|
-
exclude: void 0,
|
|
1897
|
-
maxResults: 10
|
|
1898
|
-
};
|
|
1899
|
-
let finalOptions = await (0, import_core11.loadMergedConfig)(directory, defaults, {
|
|
1900
|
-
maxDepth: options.maxDepth ? parseInt(options.maxDepth) : void 0,
|
|
1901
|
-
maxContextBudget: options.maxContext ? parseInt(options.maxContext) : void 0,
|
|
1902
|
-
minCohesion: options.minCohesion ? parseFloat(options.minCohesion) : void 0,
|
|
1903
|
-
maxFragmentation: options.maxFragmentation ? parseFloat(options.maxFragmentation) : void 0,
|
|
1904
|
-
focus: options.focus || void 0,
|
|
1905
|
-
includeNodeModules: options.includeNodeModules,
|
|
1906
|
-
include: options.include?.split(","),
|
|
1907
|
-
exclude: options.exclude?.split(","),
|
|
1908
|
-
maxResults: options.maxResults ? parseInt(options.maxResults) : void 0
|
|
1909
|
-
});
|
|
1910
|
-
if (options.interactive) {
|
|
1911
|
-
finalOptions = await runInteractiveSetup(directory, finalOptions);
|
|
1912
|
-
}
|
|
1913
|
-
const results = await analyzeContext(finalOptions);
|
|
1914
|
-
const elapsedTime = (0, import_core11.getElapsedTime)(startTime);
|
|
1915
|
-
const summary = generateSummary(results);
|
|
1916
|
-
if (options.output === "json") {
|
|
1917
|
-
const jsonOutput = {
|
|
1918
|
-
summary,
|
|
1919
|
-
results,
|
|
1920
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1921
|
-
analysisTime: elapsedTime
|
|
1922
|
-
};
|
|
1923
|
-
const outputPath = (0, import_core11.resolveOutputPath)(
|
|
1924
|
-
options.outputFile,
|
|
1925
|
-
`context-report-${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}.json`,
|
|
1926
|
-
directory
|
|
1927
|
-
);
|
|
1928
|
-
(0, import_core11.handleJSONOutput)(
|
|
1929
|
-
jsonOutput,
|
|
1930
|
-
outputPath,
|
|
1931
|
-
`
|
|
1932
|
-
\u2713 JSON report saved to ${outputPath}`
|
|
1933
|
-
);
|
|
1934
|
-
return;
|
|
1935
|
-
}
|
|
1936
|
-
if (options.output === "html") {
|
|
1937
|
-
const html = generateHTMLReport(summary, results);
|
|
1938
|
-
const outputPath = (0, import_core11.resolveOutputPath)(
|
|
1939
|
-
options.outputFile,
|
|
1940
|
-
`context-report-${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}.html`,
|
|
1941
|
-
directory
|
|
1942
|
-
);
|
|
1943
|
-
const dir = (0, import_path3.dirname)(outputPath);
|
|
1944
|
-
if (!(0, import_fs2.existsSync)(dir)) {
|
|
1945
|
-
(0, import_fs2.mkdirSync)(dir, { recursive: true });
|
|
1946
|
-
}
|
|
1947
|
-
(0, import_fs2.writeFileSync)(outputPath, html);
|
|
1948
|
-
console.log(import_chalk.default.green(`
|
|
1949
|
-
\u2713 HTML report saved to ${outputPath}`));
|
|
1950
|
-
return;
|
|
1951
|
-
}
|
|
1952
|
-
displayConsoleReport(
|
|
1953
|
-
summary,
|
|
1954
|
-
results,
|
|
1955
|
-
elapsedTime,
|
|
1956
|
-
finalOptions.maxResults
|
|
1957
|
-
);
|
|
1958
|
-
displayTuningGuidance(results, finalOptions);
|
|
1959
|
-
} catch (error) {
|
|
1960
|
-
(0, import_core11.handleCLIError)(error, "Analysis");
|
|
1961
|
-
}
|
|
1962
|
-
});
|
|
1963
|
-
program.parse();
|
|
1964
|
-
function displayTuningGuidance(results, options) {
|
|
1965
|
-
const issueCount = results.filter((r) => r.severity !== "info").length;
|
|
1966
|
-
if (issueCount === 0) {
|
|
1967
|
-
console.log(
|
|
1968
|
-
import_chalk.default.green(
|
|
1969
|
-
"\n\u2728 No optimization opportunities found! Your code is well-structured for AI context usage.\n"
|
|
1970
|
-
)
|
|
1971
|
-
);
|
|
1972
|
-
return;
|
|
1973
|
-
}
|
|
1974
|
-
console.log(import_chalk.default.cyan("\n\u2501".repeat(60)));
|
|
1975
|
-
console.log(import_chalk.default.bold.white(" TUNING GUIDANCE"));
|
|
1976
|
-
console.log(import_chalk.default.cyan("\u2501".repeat(60) + "\n"));
|
|
1977
|
-
if (issueCount < 5) {
|
|
1978
|
-
console.log(
|
|
1979
|
-
import_chalk.default.yellow(
|
|
1980
|
-
"\u{1F4CA} Showing few optimization opportunities. To find more areas to improve:\n"
|
|
1981
|
-
)
|
|
1982
|
-
);
|
|
1983
|
-
console.log(
|
|
1984
|
-
import_chalk.default.dim(
|
|
1985
|
-
" \u2022 Lower --max-depth (currently: " + options.maxDepth + ") to catch shallower import chains"
|
|
1986
|
-
)
|
|
1987
|
-
);
|
|
1988
|
-
console.log(
|
|
1989
|
-
import_chalk.default.dim(
|
|
1990
|
-
" \u2022 Lower --max-context (currently: " + options.maxContextBudget.toLocaleString() + ") to catch smaller files"
|
|
1991
|
-
)
|
|
1992
|
-
);
|
|
1993
|
-
console.log(
|
|
1994
|
-
import_chalk.default.dim(
|
|
1995
|
-
" \u2022 Raise --min-cohesion (currently: " + (options.minCohesion * 100).toFixed(0) + "%) to be stricter about mixed concerns"
|
|
1996
|
-
)
|
|
1997
|
-
);
|
|
1998
|
-
console.log(
|
|
1999
|
-
import_chalk.default.dim(
|
|
2000
|
-
" \u2022 Lower --max-fragmentation (currently: " + (options.maxFragmentation * 100).toFixed(0) + "%) to catch scattered code\n"
|
|
2001
|
-
)
|
|
2002
|
-
);
|
|
2003
|
-
} else if (issueCount > 20) {
|
|
2004
|
-
console.log(
|
|
2005
|
-
import_chalk.default.yellow(
|
|
2006
|
-
"\u{1F4CA} Showing many opportunities. To focus on highest-impact areas:\n"
|
|
2007
|
-
)
|
|
2008
|
-
);
|
|
2009
|
-
console.log(
|
|
2010
|
-
import_chalk.default.dim(
|
|
2011
|
-
" \u2022 Raise --max-depth (currently: " + options.maxDepth + ") to only catch very deep chains"
|
|
2012
|
-
)
|
|
2013
|
-
);
|
|
2014
|
-
console.log(
|
|
2015
|
-
import_chalk.default.dim(
|
|
2016
|
-
" \u2022 Raise --max-context (currently: " + options.maxContextBudget.toLocaleString() + ") to focus on largest files"
|
|
2017
|
-
)
|
|
2018
|
-
);
|
|
2019
|
-
console.log(
|
|
2020
|
-
import_chalk.default.dim(
|
|
2021
|
-
" \u2022 Lower --min-cohesion (currently: " + (options.minCohesion * 100).toFixed(0) + "%) to only flag severe mixed concerns"
|
|
2022
|
-
)
|
|
2023
|
-
);
|
|
2024
|
-
console.log(
|
|
2025
|
-
import_chalk.default.dim(
|
|
2026
|
-
" \u2022 Raise --max-fragmentation (currently: " + (options.maxFragmentation * 100).toFixed(0) + "%) to only flag highly scattered code\n"
|
|
2027
|
-
)
|
|
2028
|
-
);
|
|
2029
|
-
} else {
|
|
2030
|
-
console.log(
|
|
2031
|
-
import_chalk.default.green(
|
|
2032
|
-
"\u{1F4CA} Good balance of optimization opportunities (showing " + issueCount + " areas)\n"
|
|
2033
|
-
)
|
|
2034
|
-
);
|
|
2035
|
-
console.log(import_chalk.default.dim(" \u{1F4A1} Tip: Adjust thresholds if needed:"));
|
|
2036
|
-
console.log(
|
|
2037
|
-
import_chalk.default.dim(
|
|
2038
|
-
" aiready-context . --max-depth N --max-context N --min-cohesion 0.X\n"
|
|
2039
|
-
)
|
|
2040
|
-
);
|
|
2041
|
-
}
|
|
2042
|
-
console.log(import_chalk.default.dim(" \u{1F4D6} See README for detailed tuning guide\n"));
|
|
2043
|
-
}
|
|
2044
|
-
function displayConsoleReport(summary, results, elapsedTime, maxResults = 10) {
|
|
2045
|
-
const terminalWidth = process.stdout.columns || 80;
|
|
2046
|
-
const dividerWidth = Math.min(60, terminalWidth - 2);
|
|
2047
|
-
const divider = "\u2501".repeat(dividerWidth);
|
|
2048
|
-
console.log(import_chalk.default.cyan(divider));
|
|
2049
|
-
console.log(import_chalk.default.bold.white(" CONTEXT ANALYSIS SUMMARY"));
|
|
2050
|
-
console.log(import_chalk.default.cyan(divider) + "\n");
|
|
1550
|
+
function displayConsoleReport(summary, results, maxResults = 10) {
|
|
1551
|
+
const divider = "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500";
|
|
1552
|
+
const totalIssues = summary.criticalIssues + summary.majorIssues + summary.minorIssues;
|
|
1553
|
+
console.log(import_chalk.default.bold("\u{1F4CA} Context Analysis Summary:\n"));
|
|
1554
|
+
console.log(` \u2022 Total Files: ${import_chalk.default.cyan(summary.totalFiles)}`);
|
|
2051
1555
|
console.log(
|
|
2052
|
-
|
|
1556
|
+
` \u2022 Total Tokens: ${import_chalk.default.cyan(summary.totalTokens.toLocaleString())}`
|
|
2053
1557
|
);
|
|
2054
1558
|
console.log(
|
|
2055
|
-
import_chalk.default.
|
|
2056
|
-
`\u{1F4CA} Total tokens: ${import_chalk.default.bold(summary.totalTokens.toLocaleString())}`
|
|
2057
|
-
)
|
|
1559
|
+
` \u2022 Avg Budget: ${import_chalk.default.cyan(summary.avgContextBudget.toFixed(0))} tokens`
|
|
2058
1560
|
);
|
|
2059
1561
|
console.log(
|
|
2060
|
-
import_chalk.default.
|
|
2061
|
-
`\u{1F4B0} Avg context budget: ${import_chalk.default.bold(summary.avgContextBudget.toFixed(0))} tokens/file`
|
|
2062
|
-
)
|
|
1562
|
+
` \u2022 Potential Saving: ${import_chalk.default.green(summary.totalPotentialSavings.toLocaleString())} tokens`
|
|
2063
1563
|
);
|
|
2064
|
-
console.log(
|
|
2065
|
-
import_chalk.default.white(`\u23F1 Analysis time: ${import_chalk.default.bold(elapsedTime + "s")}
|
|
2066
|
-
`)
|
|
2067
|
-
);
|
|
2068
|
-
const totalIssues = summary.criticalIssues + summary.majorIssues + summary.minorIssues;
|
|
1564
|
+
console.log();
|
|
2069
1565
|
if (totalIssues > 0) {
|
|
2070
|
-
console.log(import_chalk.default.bold("\u26A0\uFE0F
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
);
|
|
2075
|
-
}
|
|
2076
|
-
if (summary.majorIssues > 0) {
|
|
2077
|
-
console.log(
|
|
2078
|
-
import_chalk.default.yellow(` \u{1F7E1} Major: ${import_chalk.default.bold(summary.majorIssues)}`)
|
|
2079
|
-
);
|
|
2080
|
-
}
|
|
2081
|
-
if (summary.minorIssues > 0) {
|
|
2082
|
-
console.log(
|
|
2083
|
-
import_chalk.default.blue(` \u{1F535} Minor: ${import_chalk.default.bold(summary.minorIssues)}`)
|
|
2084
|
-
);
|
|
2085
|
-
}
|
|
2086
|
-
console.log(
|
|
2087
|
-
import_chalk.default.green(
|
|
2088
|
-
`
|
|
2089
|
-
\u{1F4A1} Potential savings: ${import_chalk.default.bold(summary.totalPotentialSavings.toLocaleString())} tokens
|
|
2090
|
-
`
|
|
2091
|
-
)
|
|
2092
|
-
);
|
|
2093
|
-
} else {
|
|
2094
|
-
console.log(import_chalk.default.green("\u2705 No significant issues found!\n"));
|
|
2095
|
-
}
|
|
2096
|
-
if (summary.deepFiles.length > 0) {
|
|
2097
|
-
console.log(import_chalk.default.bold("\u{1F4CF} Deep Import Chains:\n"));
|
|
2098
|
-
console.log(
|
|
2099
|
-
import_chalk.default.gray(` Average depth: ${summary.avgImportDepth.toFixed(1)}`)
|
|
2100
|
-
);
|
|
2101
|
-
console.log(import_chalk.default.gray(` Maximum depth: ${summary.maxImportDepth}
|
|
2102
|
-
`));
|
|
2103
|
-
summary.deepFiles.slice(0, maxResults).forEach((item) => {
|
|
2104
|
-
const fileName = item.file.split("/").slice(-2).join("/");
|
|
2105
|
-
console.log(
|
|
2106
|
-
` ${import_chalk.default.cyan("\u2192")} ${import_chalk.default.white(fileName)} ${import_chalk.default.dim(`(depth: ${item.depth})`)}`
|
|
2107
|
-
);
|
|
2108
|
-
});
|
|
1566
|
+
console.log(import_chalk.default.bold("\u26A0\uFE0F Issues Detected:\n"));
|
|
1567
|
+
console.log(` \u2022 ${import_chalk.default.red("\u{1F534} Critical:")} ${summary.criticalIssues}`);
|
|
1568
|
+
console.log(` \u2022 ${import_chalk.default.yellow("\u{1F7E1} Major:")} ${summary.majorIssues}`);
|
|
1569
|
+
console.log(` \u2022 ${import_chalk.default.blue("\u{1F535} Minor:")} ${summary.minorIssues}`);
|
|
2109
1570
|
console.log();
|
|
1571
|
+
} else {
|
|
1572
|
+
console.log(import_chalk.default.green("\u2705 No significant context issues detected!\n"));
|
|
2110
1573
|
}
|
|
2111
1574
|
if (summary.fragmentedModules.length > 0) {
|
|
2112
|
-
console.log(import_chalk.default.bold("\u{1F9E9} Fragmented Modules:\n"));
|
|
2113
|
-
|
|
2114
|
-
import_chalk.default.
|
|
2115
|
-
` Average fragmentation: ${(summary.avgFragmentation * 100).toFixed(0)}%
|
|
2116
|
-
`
|
|
2117
|
-
)
|
|
2118
|
-
);
|
|
2119
|
-
summary.fragmentedModules.slice(0, maxResults).forEach((module2) => {
|
|
2120
|
-
console.log(
|
|
2121
|
-
` ${import_chalk.default.yellow("\u25CF")} ${import_chalk.default.white(module2.domain)} - ${import_chalk.default.dim(`${module2.files.length} files, ${(module2.fragmentationScore * 100).toFixed(0)}% scattered`)}`
|
|
2122
|
-
);
|
|
2123
|
-
console.log(
|
|
2124
|
-
import_chalk.default.dim(
|
|
2125
|
-
` Token cost: ${module2.totalTokens.toLocaleString()}, Cohesion: ${(module2.avgCohesion * 100).toFixed(0)}%`
|
|
2126
|
-
)
|
|
2127
|
-
);
|
|
2128
|
-
});
|
|
2129
|
-
console.log();
|
|
2130
|
-
}
|
|
2131
|
-
if (summary.lowCohesionFiles.length > 0) {
|
|
2132
|
-
console.log(import_chalk.default.bold("\u{1F500} Low Cohesion Files:\n"));
|
|
2133
|
-
console.log(
|
|
2134
|
-
import_chalk.default.gray(
|
|
2135
|
-
` Average cohesion: ${(summary.avgCohesion * 100).toFixed(0)}%
|
|
2136
|
-
`
|
|
2137
|
-
)
|
|
2138
|
-
);
|
|
2139
|
-
summary.lowCohesionFiles.slice(0, maxResults).forEach((item) => {
|
|
2140
|
-
const fileName = item.file.split("/").slice(-2).join("/");
|
|
2141
|
-
const scorePercent = (item.score * 100).toFixed(0);
|
|
2142
|
-
const color = item.score < 0.4 ? import_chalk.default.red : import_chalk.default.yellow;
|
|
1575
|
+
console.log(import_chalk.default.bold("\u{1F9E9} Top Fragmented Modules:\n"));
|
|
1576
|
+
summary.fragmentedModules.slice(0, maxResults).forEach((mod) => {
|
|
1577
|
+
const scoreColor = mod.fragmentationScore > 0.7 ? import_chalk.default.red : mod.fragmentationScore > 0.4 ? import_chalk.default.yellow : import_chalk.default.green;
|
|
2143
1578
|
console.log(
|
|
2144
|
-
` ${
|
|
1579
|
+
` ${scoreColor("\u25A0")} ${import_chalk.default.white(mod.domain)} ${import_chalk.default.dim(`(${mod.files.length} files, ${(mod.fragmentationScore * 100).toFixed(0)}% frag)`)}`
|
|
2145
1580
|
);
|
|
2146
1581
|
});
|
|
2147
1582
|
console.log();
|
|
@@ -2427,3 +1862,102 @@ async function runInteractiveSetup(directory, current) {
|
|
|
2427
1862
|
console.log(import_chalk.default.green("\u2713 Interactive configuration applied."));
|
|
2428
1863
|
return nextOptions;
|
|
2429
1864
|
}
|
|
1865
|
+
|
|
1866
|
+
// src/cli-action.ts
|
|
1867
|
+
var import_chalk2 = __toESM(require("chalk"));
|
|
1868
|
+
var import_fs3 = require("fs");
|
|
1869
|
+
async function contextActionHandler(directory, options) {
|
|
1870
|
+
console.log(import_chalk2.default.blue("\u{1F50D} Analyzing context window costs...\n"));
|
|
1871
|
+
const startTime = Date.now();
|
|
1872
|
+
try {
|
|
1873
|
+
const defaults = {
|
|
1874
|
+
maxDepth: 5,
|
|
1875
|
+
maxContextBudget: 1e4,
|
|
1876
|
+
minCohesion: 0.6,
|
|
1877
|
+
maxFragmentation: 0.5,
|
|
1878
|
+
focus: "all",
|
|
1879
|
+
includeNodeModules: false,
|
|
1880
|
+
include: void 0,
|
|
1881
|
+
exclude: void 0,
|
|
1882
|
+
maxResults: 10
|
|
1883
|
+
};
|
|
1884
|
+
let finalOptions = await (0, import_core8.loadMergedConfig)(directory, defaults, {
|
|
1885
|
+
maxDepth: options.maxDepth ? parseInt(options.maxDepth) : void 0,
|
|
1886
|
+
maxContextBudget: options.maxContext ? parseInt(options.maxContext) : void 0,
|
|
1887
|
+
minCohesion: options.minCohesion ? parseFloat(options.minCohesion) : void 0,
|
|
1888
|
+
maxFragmentation: options.maxFragmentation ? parseFloat(options.maxFragmentation) : void 0,
|
|
1889
|
+
focus: options.focus || void 0,
|
|
1890
|
+
includeNodeModules: options.includeNodeModules,
|
|
1891
|
+
include: options.include?.split(","),
|
|
1892
|
+
exclude: options.exclude?.split(","),
|
|
1893
|
+
maxResults: options.maxResults ? parseInt(options.maxResults) : void 0
|
|
1894
|
+
});
|
|
1895
|
+
if (options.interactive) {
|
|
1896
|
+
finalOptions = await runInteractiveSetup(directory, finalOptions);
|
|
1897
|
+
}
|
|
1898
|
+
const results = await analyzeContext(finalOptions);
|
|
1899
|
+
const summary = generateSummary(results, finalOptions);
|
|
1900
|
+
const duration = (0, import_core8.getElapsedTime)(startTime);
|
|
1901
|
+
if (options.output === "json") {
|
|
1902
|
+
(0, import_core8.handleJSONOutput)(
|
|
1903
|
+
{
|
|
1904
|
+
summary: {
|
|
1905
|
+
...summary,
|
|
1906
|
+
executionTime: duration,
|
|
1907
|
+
config: {
|
|
1908
|
+
scan: { tools: ["context"] },
|
|
1909
|
+
tools: { context: finalOptions }
|
|
1910
|
+
},
|
|
1911
|
+
toolConfigs: { context: finalOptions }
|
|
1912
|
+
},
|
|
1913
|
+
context: { results }
|
|
1914
|
+
},
|
|
1915
|
+
options.outputFile
|
|
1916
|
+
);
|
|
1917
|
+
} else if (options.output === "html") {
|
|
1918
|
+
const html = generateHTMLReport(summary, results);
|
|
1919
|
+
const outputPath = (0, import_core8.resolveOutputPath)(
|
|
1920
|
+
directory,
|
|
1921
|
+
options.outputFile,
|
|
1922
|
+
"context-report.html"
|
|
1923
|
+
);
|
|
1924
|
+
(0, import_fs3.writeFileSync)(outputPath, html, "utf-8");
|
|
1925
|
+
console.log(import_chalk2.default.green(`
|
|
1926
|
+
\u2705 HTML report saved to: ${outputPath}`));
|
|
1927
|
+
} else {
|
|
1928
|
+
displayConsoleReport(summary, results, finalOptions.maxResults);
|
|
1929
|
+
console.log(import_chalk2.default.dim(`
|
|
1930
|
+
\u2728 Analysis completed in ${duration}ms
|
|
1931
|
+
`));
|
|
1932
|
+
}
|
|
1933
|
+
} catch (error) {
|
|
1934
|
+
(0, import_core8.handleCLIError)(error, "context-analyzer");
|
|
1935
|
+
}
|
|
1936
|
+
}
|
|
1937
|
+
|
|
1938
|
+
// src/cli.ts
|
|
1939
|
+
var program = new import_commander.Command();
|
|
1940
|
+
program.name("aiready-context").description("Analyze AI context window cost and code structure").version("0.1.0").addHelpText(
|
|
1941
|
+
"after",
|
|
1942
|
+
"\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"
|
|
1943
|
+
).argument("<directory>", "Directory to analyze").option("--max-depth <number>", "Maximum acceptable import depth").option(
|
|
1944
|
+
"--max-context <number>",
|
|
1945
|
+
"Maximum acceptable context budget (tokens)"
|
|
1946
|
+
).option("--min-cohesion <number>", "Minimum acceptable cohesion score (0-1)").option(
|
|
1947
|
+
"--max-fragmentation <number>",
|
|
1948
|
+
"Maximum acceptable fragmentation (0-1)"
|
|
1949
|
+
).option(
|
|
1950
|
+
"--focus <type>",
|
|
1951
|
+
"Analysis focus: fragmentation, cohesion, depth, all"
|
|
1952
|
+
).option("--include-node-modules", "Include node_modules in analysis").option("--include <patterns>", "File patterns to include (comma-separated)").option("--exclude <patterns>", "File patterns to exclude (comma-separated)").option(
|
|
1953
|
+
"--max-results <number>",
|
|
1954
|
+
"Maximum number of results to show in console output"
|
|
1955
|
+
).option(
|
|
1956
|
+
"-o, --output <format>",
|
|
1957
|
+
"Output format: console, json, html",
|
|
1958
|
+
"console"
|
|
1959
|
+
).option("--output-file <path>", "Output file path (for json/html)").option(
|
|
1960
|
+
"--interactive",
|
|
1961
|
+
"Run interactive setup to suggest excludes and focus areas"
|
|
1962
|
+
).action(contextActionHandler);
|
|
1963
|
+
program.parse(process.argv);
|