@aiready/context-analyzer 0.4.3 → 0.4.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +10 -10
- package/README.md +48 -0
- package/dist/chunk-45P4RDYP.mjs +607 -0
- package/dist/cli.js +32 -1
- package/dist/cli.mjs +30 -1
- package/dist/index.d.mts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +18 -16
- package/dist/index.mjs +1 -1
- package/package.json +2 -2
- package/src/cli.ts +42 -0
- package/src/index.ts +30 -20
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
|
|
2
2
|
|
|
3
|
-
> @aiready/context-analyzer@0.4.
|
|
3
|
+
> @aiready/context-analyzer@0.4.5 build /Users/pengcao/projects/aiready/packages/context-analyzer
|
|
4
4
|
> tsup src/index.ts src/cli.ts --format cjs,esm --dts
|
|
5
5
|
|
|
6
6
|
[34mCLI[39m Building entry: src/cli.ts, src/index.ts
|
|
@@ -9,16 +9,16 @@
|
|
|
9
9
|
[34mCLI[39m Target: es2020
|
|
10
10
|
[34mCJS[39m Build start
|
|
11
11
|
[34mESM[39m Build start
|
|
12
|
-
[
|
|
12
|
+
[32mCJS[39m [1mdist/index.js [22m[32m20.38 KB[39m
|
|
13
|
+
[32mCJS[39m [1mdist/cli.js [22m[32m39.03 KB[39m
|
|
14
|
+
[32mCJS[39m ⚡️ Build success in 58ms
|
|
15
|
+
[32mESM[39m [1mdist/chunk-45P4RDYP.mjs [22m[32m19.24 KB[39m
|
|
16
|
+
[32mESM[39m [1mdist/cli.mjs [22m[32m18.45 KB[39m
|
|
13
17
|
[32mESM[39m [1mdist/index.mjs [22m[32m164.00 B[39m
|
|
14
|
-
[32mESM[39m
|
|
15
|
-
[32mESM[39m ⚡️ Build success in 16ms
|
|
16
|
-
[32mCJS[39m [1mdist/cli.js [22m[32m36.36 KB[39m
|
|
17
|
-
[32mCJS[39m [1mdist/index.js [22m[32m20.26 KB[39m
|
|
18
|
-
[32mCJS[39m ⚡️ Build success in 16ms
|
|
18
|
+
[32mESM[39m ⚡️ Build success in 58ms
|
|
19
19
|
DTS Build start
|
|
20
|
-
DTS ⚡️ Build success in
|
|
20
|
+
DTS ⚡️ Build success in 543ms
|
|
21
21
|
DTS dist/cli.d.ts 20.00 B
|
|
22
|
-
DTS dist/index.d.ts 2.
|
|
22
|
+
DTS dist/index.d.ts 2.44 KB
|
|
23
23
|
DTS dist/cli.d.mts 20.00 B
|
|
24
|
-
DTS dist/index.d.mts 2.
|
|
24
|
+
DTS dist/index.d.mts 2.44 KB
|
package/README.md
CHANGED
|
@@ -84,6 +84,54 @@ aiready-context ./src --output json --output-file custom-report.json
|
|
|
84
84
|
|
|
85
85
|
> **📁 Output Files:** By default, all output files are saved to the `.aiready/` directory in your project root. You can override this with `--output-file`.
|
|
86
86
|
|
|
87
|
+
## 🎛️ Tuning Guide
|
|
88
|
+
|
|
89
|
+
**Smart defaults automatically adjust based on your repository size** to show ~10 most serious issues.
|
|
90
|
+
|
|
91
|
+
### Getting More/Fewer Results
|
|
92
|
+
|
|
93
|
+
**Want to catch MORE potential issues?** (More sensitive, shows smaller problems)
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
# Lower thresholds to be more strict:
|
|
97
|
+
aiready-context ./src --max-depth 3 --max-context 5000 --min-cohesion 0.7 --max-fragmentation 0.4
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
**Want to see FEWER issues?** (Less noise, focus on critical problems only)
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
# Raise thresholds to be more lenient:
|
|
104
|
+
aiready-context ./src --max-depth 10 --max-context 30000 --min-cohesion 0.4 --max-fragmentation 0.8
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Threshold Parameters Explained
|
|
108
|
+
|
|
109
|
+
| Parameter | Default (Auto) | Lower = More Strict | Higher = Less Strict |
|
|
110
|
+
|-----------|---------------|-------------------|---------------------|
|
|
111
|
+
| `--max-depth` | 4-10* | Catches shallower imports | Only very deep chains |
|
|
112
|
+
| `--max-context` | 8k-40k* | Catches smaller files | Only huge files |
|
|
113
|
+
| `--min-cohesion` | 0.35-0.5* | Stricter about mixed concerns | More lenient |
|
|
114
|
+
| `--max-fragmentation` | 0.5-0.8* | Catches less scattered code | Only severely scattered |
|
|
115
|
+
|
|
116
|
+
\* Auto-adjusted based on your repository size (100 files vs 2000+ files)
|
|
117
|
+
|
|
118
|
+
### Common Tuning Scenarios
|
|
119
|
+
|
|
120
|
+
**Small codebase getting too many warnings?**
|
|
121
|
+
```bash
|
|
122
|
+
aiready-context ./src --max-depth 6 --min-cohesion 0.5
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
**Large codebase showing too few issues?**
|
|
126
|
+
```bash
|
|
127
|
+
aiready-context ./src --max-depth 5 --max-context 15000
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
**Focus on critical issues only:**
|
|
131
|
+
```bash
|
|
132
|
+
aiready-context ./src --max-depth 8 --max-context 25000 --min-cohesion 0.3
|
|
133
|
+
```
|
|
134
|
+
|
|
87
135
|
# Generate HTML report
|
|
88
136
|
aiready-context ./src --output html --output-file report.html
|
|
89
137
|
|
|
@@ -0,0 +1,607 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import { scanFiles, readFileContent } from "@aiready/core";
|
|
3
|
+
|
|
4
|
+
// src/analyzer.ts
|
|
5
|
+
import { estimateTokens } from "@aiready/core";
|
|
6
|
+
function buildDependencyGraph(files) {
|
|
7
|
+
const nodes = /* @__PURE__ */ new Map();
|
|
8
|
+
const edges = /* @__PURE__ */ new Map();
|
|
9
|
+
for (const { file, content } of files) {
|
|
10
|
+
const imports = extractImportsFromContent(content);
|
|
11
|
+
const exports = extractExports(content);
|
|
12
|
+
const tokenCost = estimateTokens(content);
|
|
13
|
+
const linesOfCode = content.split("\n").length;
|
|
14
|
+
nodes.set(file, {
|
|
15
|
+
file,
|
|
16
|
+
imports,
|
|
17
|
+
exports,
|
|
18
|
+
tokenCost,
|
|
19
|
+
linesOfCode
|
|
20
|
+
});
|
|
21
|
+
edges.set(file, new Set(imports));
|
|
22
|
+
}
|
|
23
|
+
return { nodes, edges };
|
|
24
|
+
}
|
|
25
|
+
function extractImportsFromContent(content) {
|
|
26
|
+
const imports = [];
|
|
27
|
+
const patterns = [
|
|
28
|
+
/import\s+.*?\s+from\s+['"](.+?)['"]/g,
|
|
29
|
+
// import ... from '...'
|
|
30
|
+
/import\s+['"](.+?)['"]/g,
|
|
31
|
+
// import '...'
|
|
32
|
+
/require\(['"](.+?)['"]\)/g
|
|
33
|
+
// require('...')
|
|
34
|
+
];
|
|
35
|
+
for (const pattern of patterns) {
|
|
36
|
+
let match;
|
|
37
|
+
while ((match = pattern.exec(content)) !== null) {
|
|
38
|
+
const importPath = match[1];
|
|
39
|
+
if (importPath && !importPath.startsWith("@") && !importPath.startsWith("node:")) {
|
|
40
|
+
imports.push(importPath);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return [...new Set(imports)];
|
|
45
|
+
}
|
|
46
|
+
function calculateImportDepth(file, graph, visited = /* @__PURE__ */ new Set(), depth = 0) {
|
|
47
|
+
if (visited.has(file)) {
|
|
48
|
+
return depth;
|
|
49
|
+
}
|
|
50
|
+
const dependencies = graph.edges.get(file);
|
|
51
|
+
if (!dependencies || dependencies.size === 0) {
|
|
52
|
+
return depth;
|
|
53
|
+
}
|
|
54
|
+
visited.add(file);
|
|
55
|
+
let maxDepth = depth;
|
|
56
|
+
for (const dep of dependencies) {
|
|
57
|
+
const depDepth = calculateImportDepth(dep, graph, visited, depth + 1);
|
|
58
|
+
maxDepth = Math.max(maxDepth, depDepth);
|
|
59
|
+
}
|
|
60
|
+
visited.delete(file);
|
|
61
|
+
return maxDepth;
|
|
62
|
+
}
|
|
63
|
+
function getTransitiveDependencies(file, graph, visited = /* @__PURE__ */ new Set()) {
|
|
64
|
+
if (visited.has(file)) {
|
|
65
|
+
return [];
|
|
66
|
+
}
|
|
67
|
+
visited.add(file);
|
|
68
|
+
const dependencies = graph.edges.get(file);
|
|
69
|
+
if (!dependencies || dependencies.size === 0) {
|
|
70
|
+
return [];
|
|
71
|
+
}
|
|
72
|
+
const allDeps = [];
|
|
73
|
+
for (const dep of dependencies) {
|
|
74
|
+
allDeps.push(dep);
|
|
75
|
+
allDeps.push(...getTransitiveDependencies(dep, graph, visited));
|
|
76
|
+
}
|
|
77
|
+
return [...new Set(allDeps)];
|
|
78
|
+
}
|
|
79
|
+
function calculateContextBudget(file, graph) {
|
|
80
|
+
const node = graph.nodes.get(file);
|
|
81
|
+
if (!node) return 0;
|
|
82
|
+
let totalTokens = node.tokenCost;
|
|
83
|
+
const deps = getTransitiveDependencies(file, graph);
|
|
84
|
+
for (const dep of deps) {
|
|
85
|
+
const depNode = graph.nodes.get(dep);
|
|
86
|
+
if (depNode) {
|
|
87
|
+
totalTokens += depNode.tokenCost;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return totalTokens;
|
|
91
|
+
}
|
|
92
|
+
function detectCircularDependencies(graph) {
|
|
93
|
+
const cycles = [];
|
|
94
|
+
const visited = /* @__PURE__ */ new Set();
|
|
95
|
+
const recursionStack = /* @__PURE__ */ new Set();
|
|
96
|
+
function dfs(file, path) {
|
|
97
|
+
if (recursionStack.has(file)) {
|
|
98
|
+
const cycleStart = path.indexOf(file);
|
|
99
|
+
if (cycleStart !== -1) {
|
|
100
|
+
cycles.push([...path.slice(cycleStart), file]);
|
|
101
|
+
}
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
if (visited.has(file)) {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
visited.add(file);
|
|
108
|
+
recursionStack.add(file);
|
|
109
|
+
path.push(file);
|
|
110
|
+
const dependencies = graph.edges.get(file);
|
|
111
|
+
if (dependencies) {
|
|
112
|
+
for (const dep of dependencies) {
|
|
113
|
+
dfs(dep, [...path]);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
recursionStack.delete(file);
|
|
117
|
+
}
|
|
118
|
+
for (const file of graph.nodes.keys()) {
|
|
119
|
+
if (!visited.has(file)) {
|
|
120
|
+
dfs(file, []);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return cycles;
|
|
124
|
+
}
|
|
125
|
+
function calculateCohesion(exports) {
|
|
126
|
+
if (exports.length === 0) return 1;
|
|
127
|
+
if (exports.length === 1) return 1;
|
|
128
|
+
const domains = exports.map((e) => e.inferredDomain || "unknown");
|
|
129
|
+
const domainCounts = /* @__PURE__ */ new Map();
|
|
130
|
+
for (const domain of domains) {
|
|
131
|
+
domainCounts.set(domain, (domainCounts.get(domain) || 0) + 1);
|
|
132
|
+
}
|
|
133
|
+
const total = domains.length;
|
|
134
|
+
let entropy = 0;
|
|
135
|
+
for (const count of domainCounts.values()) {
|
|
136
|
+
const p = count / total;
|
|
137
|
+
if (p > 0) {
|
|
138
|
+
entropy -= p * Math.log2(p);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
const maxEntropy = Math.log2(total);
|
|
142
|
+
return maxEntropy > 0 ? 1 - entropy / maxEntropy : 1;
|
|
143
|
+
}
|
|
144
|
+
function calculateFragmentation(files, domain) {
|
|
145
|
+
if (files.length <= 1) return 0;
|
|
146
|
+
const directories = new Set(files.map((f) => f.split("/").slice(0, -1).join("/")));
|
|
147
|
+
return (directories.size - 1) / (files.length - 1);
|
|
148
|
+
}
|
|
149
|
+
function detectModuleClusters(graph) {
|
|
150
|
+
const domainMap = /* @__PURE__ */ new Map();
|
|
151
|
+
for (const [file, node] of graph.nodes.entries()) {
|
|
152
|
+
const domains = node.exports.map((e) => e.inferredDomain || "unknown");
|
|
153
|
+
const primaryDomain = domains[0] || "unknown";
|
|
154
|
+
if (!domainMap.has(primaryDomain)) {
|
|
155
|
+
domainMap.set(primaryDomain, []);
|
|
156
|
+
}
|
|
157
|
+
domainMap.get(primaryDomain).push(file);
|
|
158
|
+
}
|
|
159
|
+
const clusters = [];
|
|
160
|
+
for (const [domain, files] of domainMap.entries()) {
|
|
161
|
+
if (files.length < 2) continue;
|
|
162
|
+
const totalTokens = files.reduce((sum, file) => {
|
|
163
|
+
const node = graph.nodes.get(file);
|
|
164
|
+
return sum + (node?.tokenCost || 0);
|
|
165
|
+
}, 0);
|
|
166
|
+
const fragmentationScore = calculateFragmentation(files, domain);
|
|
167
|
+
const avgCohesion = files.reduce((sum, file) => {
|
|
168
|
+
const node = graph.nodes.get(file);
|
|
169
|
+
return sum + (node ? calculateCohesion(node.exports) : 0);
|
|
170
|
+
}, 0) / files.length;
|
|
171
|
+
const targetFiles = Math.max(1, Math.ceil(files.length / 3));
|
|
172
|
+
const consolidationPlan = generateConsolidationPlan(
|
|
173
|
+
domain,
|
|
174
|
+
files,
|
|
175
|
+
targetFiles
|
|
176
|
+
);
|
|
177
|
+
clusters.push({
|
|
178
|
+
domain,
|
|
179
|
+
files,
|
|
180
|
+
totalTokens,
|
|
181
|
+
fragmentationScore,
|
|
182
|
+
avgCohesion,
|
|
183
|
+
suggestedStructure: {
|
|
184
|
+
targetFiles,
|
|
185
|
+
consolidationPlan
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
return clusters.sort((a, b) => b.fragmentationScore - a.fragmentationScore);
|
|
190
|
+
}
|
|
191
|
+
function extractExports(content) {
|
|
192
|
+
const exports = [];
|
|
193
|
+
const patterns = [
|
|
194
|
+
/export\s+function\s+(\w+)/g,
|
|
195
|
+
/export\s+class\s+(\w+)/g,
|
|
196
|
+
/export\s+const\s+(\w+)/g,
|
|
197
|
+
/export\s+type\s+(\w+)/g,
|
|
198
|
+
/export\s+interface\s+(\w+)/g,
|
|
199
|
+
/export\s+default/g
|
|
200
|
+
];
|
|
201
|
+
const types = [
|
|
202
|
+
"function",
|
|
203
|
+
"class",
|
|
204
|
+
"const",
|
|
205
|
+
"type",
|
|
206
|
+
"interface",
|
|
207
|
+
"default"
|
|
208
|
+
];
|
|
209
|
+
patterns.forEach((pattern, index) => {
|
|
210
|
+
let match;
|
|
211
|
+
while ((match = pattern.exec(content)) !== null) {
|
|
212
|
+
const name = match[1] || "default";
|
|
213
|
+
const type = types[index];
|
|
214
|
+
const inferredDomain = inferDomain(name);
|
|
215
|
+
exports.push({ name, type, inferredDomain });
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
return exports;
|
|
219
|
+
}
|
|
220
|
+
function inferDomain(name) {
|
|
221
|
+
const lower = name.toLowerCase();
|
|
222
|
+
const domainKeywords = [
|
|
223
|
+
"user",
|
|
224
|
+
"auth",
|
|
225
|
+
"order",
|
|
226
|
+
"product",
|
|
227
|
+
"payment",
|
|
228
|
+
"cart",
|
|
229
|
+
"invoice",
|
|
230
|
+
"customer",
|
|
231
|
+
"admin",
|
|
232
|
+
"api",
|
|
233
|
+
"util",
|
|
234
|
+
"helper",
|
|
235
|
+
"config",
|
|
236
|
+
"service",
|
|
237
|
+
"repository",
|
|
238
|
+
"controller",
|
|
239
|
+
"model",
|
|
240
|
+
"view"
|
|
241
|
+
];
|
|
242
|
+
for (const keyword of domainKeywords) {
|
|
243
|
+
if (lower.includes(keyword)) {
|
|
244
|
+
return keyword;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
return "unknown";
|
|
248
|
+
}
|
|
249
|
+
function generateConsolidationPlan(domain, files, targetFiles) {
|
|
250
|
+
const plan = [];
|
|
251
|
+
if (files.length <= targetFiles) {
|
|
252
|
+
return [`No consolidation needed for ${domain}`];
|
|
253
|
+
}
|
|
254
|
+
plan.push(
|
|
255
|
+
`Consolidate ${files.length} ${domain} files into ${targetFiles} cohesive file(s):`
|
|
256
|
+
);
|
|
257
|
+
const dirGroups = /* @__PURE__ */ new Map();
|
|
258
|
+
for (const file of files) {
|
|
259
|
+
const dir = file.split("/").slice(0, -1).join("/");
|
|
260
|
+
if (!dirGroups.has(dir)) {
|
|
261
|
+
dirGroups.set(dir, []);
|
|
262
|
+
}
|
|
263
|
+
dirGroups.get(dir).push(file);
|
|
264
|
+
}
|
|
265
|
+
plan.push(`1. Create unified ${domain} module file`);
|
|
266
|
+
plan.push(
|
|
267
|
+
`2. Move related functionality from ${files.length} scattered files`
|
|
268
|
+
);
|
|
269
|
+
plan.push(`3. Update imports in dependent files`);
|
|
270
|
+
plan.push(
|
|
271
|
+
`4. Remove old files after consolidation (verify with tests first)`
|
|
272
|
+
);
|
|
273
|
+
return plan;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// src/index.ts
|
|
277
|
+
async function getSmartDefaults(directory, userOptions) {
|
|
278
|
+
const files = await scanFiles({
|
|
279
|
+
rootDir: directory,
|
|
280
|
+
include: userOptions.include,
|
|
281
|
+
exclude: userOptions.exclude
|
|
282
|
+
});
|
|
283
|
+
const estimatedBlocks = files.length;
|
|
284
|
+
let maxDepth;
|
|
285
|
+
let maxContextBudget;
|
|
286
|
+
let minCohesion;
|
|
287
|
+
let maxFragmentation;
|
|
288
|
+
if (estimatedBlocks < 100) {
|
|
289
|
+
maxDepth = 4;
|
|
290
|
+
maxContextBudget = 8e3;
|
|
291
|
+
minCohesion = 0.5;
|
|
292
|
+
maxFragmentation = 0.5;
|
|
293
|
+
} else if (estimatedBlocks < 500) {
|
|
294
|
+
maxDepth = 5;
|
|
295
|
+
maxContextBudget = 15e3;
|
|
296
|
+
minCohesion = 0.45;
|
|
297
|
+
maxFragmentation = 0.6;
|
|
298
|
+
} else if (estimatedBlocks < 2e3) {
|
|
299
|
+
maxDepth = 7;
|
|
300
|
+
maxContextBudget = 25e3;
|
|
301
|
+
minCohesion = 0.4;
|
|
302
|
+
maxFragmentation = 0.7;
|
|
303
|
+
} else {
|
|
304
|
+
maxDepth = 10;
|
|
305
|
+
maxContextBudget = 4e4;
|
|
306
|
+
minCohesion = 0.35;
|
|
307
|
+
maxFragmentation = 0.8;
|
|
308
|
+
}
|
|
309
|
+
return {
|
|
310
|
+
maxDepth,
|
|
311
|
+
maxContextBudget,
|
|
312
|
+
minCohesion,
|
|
313
|
+
maxFragmentation,
|
|
314
|
+
focus: "all",
|
|
315
|
+
includeNodeModules: false,
|
|
316
|
+
rootDir: userOptions.rootDir || directory,
|
|
317
|
+
include: userOptions.include,
|
|
318
|
+
exclude: userOptions.exclude
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
async function analyzeContext(options) {
|
|
322
|
+
const {
|
|
323
|
+
maxDepth = 5,
|
|
324
|
+
maxContextBudget = 1e4,
|
|
325
|
+
minCohesion = 0.6,
|
|
326
|
+
maxFragmentation = 0.5,
|
|
327
|
+
focus = "all",
|
|
328
|
+
includeNodeModules = false,
|
|
329
|
+
...scanOptions
|
|
330
|
+
} = options;
|
|
331
|
+
const files = await scanFiles({
|
|
332
|
+
...scanOptions,
|
|
333
|
+
exclude: includeNodeModules ? scanOptions.exclude : [...scanOptions.exclude || [], "**/node_modules/**"]
|
|
334
|
+
});
|
|
335
|
+
const fileContents = await Promise.all(
|
|
336
|
+
files.map(async (file) => ({
|
|
337
|
+
file,
|
|
338
|
+
content: await readFileContent(file)
|
|
339
|
+
}))
|
|
340
|
+
);
|
|
341
|
+
const graph = buildDependencyGraph(fileContents);
|
|
342
|
+
const circularDeps = detectCircularDependencies(graph);
|
|
343
|
+
const clusters = detectModuleClusters(graph);
|
|
344
|
+
const fragmentationMap = /* @__PURE__ */ new Map();
|
|
345
|
+
for (const cluster of clusters) {
|
|
346
|
+
for (const file of cluster.files) {
|
|
347
|
+
fragmentationMap.set(file, cluster.fragmentationScore);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
const results = [];
|
|
351
|
+
for (const { file } of fileContents) {
|
|
352
|
+
const node = graph.nodes.get(file);
|
|
353
|
+
if (!node) continue;
|
|
354
|
+
const importDepth = focus === "depth" || focus === "all" ? calculateImportDepth(file, graph) : 0;
|
|
355
|
+
const dependencyList = focus === "depth" || focus === "all" ? getTransitiveDependencies(file, graph) : [];
|
|
356
|
+
const contextBudget = focus === "all" ? calculateContextBudget(file, graph) : node.tokenCost;
|
|
357
|
+
const cohesionScore = focus === "cohesion" || focus === "all" ? calculateCohesion(node.exports) : 1;
|
|
358
|
+
const fragmentationScore = fragmentationMap.get(file) || 0;
|
|
359
|
+
const relatedFiles = [];
|
|
360
|
+
for (const cluster of clusters) {
|
|
361
|
+
if (cluster.files.includes(file)) {
|
|
362
|
+
relatedFiles.push(...cluster.files.filter((f) => f !== file));
|
|
363
|
+
break;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
const { severity, issues, recommendations, potentialSavings } = analyzeIssues({
|
|
367
|
+
file,
|
|
368
|
+
importDepth,
|
|
369
|
+
contextBudget,
|
|
370
|
+
cohesionScore,
|
|
371
|
+
fragmentationScore,
|
|
372
|
+
maxDepth,
|
|
373
|
+
maxContextBudget,
|
|
374
|
+
minCohesion,
|
|
375
|
+
maxFragmentation,
|
|
376
|
+
circularDeps
|
|
377
|
+
});
|
|
378
|
+
const domains = [
|
|
379
|
+
...new Set(node.exports.map((e) => e.inferredDomain || "unknown"))
|
|
380
|
+
];
|
|
381
|
+
results.push({
|
|
382
|
+
file,
|
|
383
|
+
tokenCost: node.tokenCost,
|
|
384
|
+
linesOfCode: node.linesOfCode,
|
|
385
|
+
importDepth,
|
|
386
|
+
dependencyCount: dependencyList.length,
|
|
387
|
+
dependencyList,
|
|
388
|
+
circularDeps: circularDeps.filter((cycle) => cycle.includes(file)),
|
|
389
|
+
cohesionScore,
|
|
390
|
+
domains,
|
|
391
|
+
exportCount: node.exports.length,
|
|
392
|
+
contextBudget,
|
|
393
|
+
fragmentationScore,
|
|
394
|
+
relatedFiles,
|
|
395
|
+
severity,
|
|
396
|
+
issues,
|
|
397
|
+
recommendations,
|
|
398
|
+
potentialSavings
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
const issuesOnly = results.filter((r) => r.severity !== "info");
|
|
402
|
+
const sorted = issuesOnly.sort((a, b) => {
|
|
403
|
+
const severityOrder = { critical: 0, major: 1, minor: 2, info: 3 };
|
|
404
|
+
const severityDiff = severityOrder[a.severity] - severityOrder[b.severity];
|
|
405
|
+
if (severityDiff !== 0) return severityDiff;
|
|
406
|
+
return b.contextBudget - a.contextBudget;
|
|
407
|
+
});
|
|
408
|
+
return sorted.length > 0 ? sorted : results;
|
|
409
|
+
}
|
|
410
|
+
function generateSummary(results) {
|
|
411
|
+
if (results.length === 0) {
|
|
412
|
+
return {
|
|
413
|
+
totalFiles: 0,
|
|
414
|
+
totalTokens: 0,
|
|
415
|
+
avgContextBudget: 0,
|
|
416
|
+
maxContextBudget: 0,
|
|
417
|
+
avgImportDepth: 0,
|
|
418
|
+
maxImportDepth: 0,
|
|
419
|
+
deepFiles: [],
|
|
420
|
+
avgFragmentation: 0,
|
|
421
|
+
fragmentedModules: [],
|
|
422
|
+
avgCohesion: 0,
|
|
423
|
+
lowCohesionFiles: [],
|
|
424
|
+
criticalIssues: 0,
|
|
425
|
+
majorIssues: 0,
|
|
426
|
+
minorIssues: 0,
|
|
427
|
+
totalPotentialSavings: 0,
|
|
428
|
+
topExpensiveFiles: []
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
const totalFiles = results.length;
|
|
432
|
+
const totalTokens = results.reduce((sum, r) => sum + r.tokenCost, 0);
|
|
433
|
+
const totalContextBudget = results.reduce(
|
|
434
|
+
(sum, r) => sum + r.contextBudget,
|
|
435
|
+
0
|
|
436
|
+
);
|
|
437
|
+
const avgContextBudget = totalContextBudget / totalFiles;
|
|
438
|
+
const maxContextBudget = Math.max(...results.map((r) => r.contextBudget));
|
|
439
|
+
const avgImportDepth = results.reduce((sum, r) => sum + r.importDepth, 0) / totalFiles;
|
|
440
|
+
const maxImportDepth = Math.max(...results.map((r) => r.importDepth));
|
|
441
|
+
const deepFiles = results.filter((r) => r.importDepth >= 5).map((r) => ({ file: r.file, depth: r.importDepth })).sort((a, b) => b.depth - a.depth).slice(0, 10);
|
|
442
|
+
const avgFragmentation = results.reduce((sum, r) => sum + r.fragmentationScore, 0) / totalFiles;
|
|
443
|
+
const moduleMap = /* @__PURE__ */ new Map();
|
|
444
|
+
for (const result of results) {
|
|
445
|
+
for (const domain of result.domains) {
|
|
446
|
+
if (!moduleMap.has(domain)) {
|
|
447
|
+
moduleMap.set(domain, []);
|
|
448
|
+
}
|
|
449
|
+
moduleMap.get(domain).push(result);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
const fragmentedModules = [];
|
|
453
|
+
for (const [domain, files] of moduleMap.entries()) {
|
|
454
|
+
if (files.length < 2) continue;
|
|
455
|
+
const fragmentationScore = files.reduce((sum, f) => sum + f.fragmentationScore, 0) / files.length;
|
|
456
|
+
if (fragmentationScore < 0.3) continue;
|
|
457
|
+
const totalTokens2 = files.reduce((sum, f) => sum + f.tokenCost, 0);
|
|
458
|
+
const avgCohesion2 = files.reduce((sum, f) => sum + f.cohesionScore, 0) / files.length;
|
|
459
|
+
const targetFiles = Math.max(1, Math.ceil(files.length / 3));
|
|
460
|
+
fragmentedModules.push({
|
|
461
|
+
domain,
|
|
462
|
+
files: files.map((f) => f.file),
|
|
463
|
+
totalTokens: totalTokens2,
|
|
464
|
+
fragmentationScore,
|
|
465
|
+
avgCohesion: avgCohesion2,
|
|
466
|
+
suggestedStructure: {
|
|
467
|
+
targetFiles,
|
|
468
|
+
consolidationPlan: [
|
|
469
|
+
`Consolidate ${files.length} ${domain} files into ${targetFiles} cohesive file(s)`,
|
|
470
|
+
`Current token cost: ${totalTokens2.toLocaleString()}`,
|
|
471
|
+
`Estimated savings: ${Math.floor(totalTokens2 * 0.3).toLocaleString()} tokens (30%)`
|
|
472
|
+
]
|
|
473
|
+
}
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
fragmentedModules.sort((a, b) => b.fragmentationScore - a.fragmentationScore);
|
|
477
|
+
const avgCohesion = results.reduce((sum, r) => sum + r.cohesionScore, 0) / totalFiles;
|
|
478
|
+
const lowCohesionFiles = results.filter((r) => r.cohesionScore < 0.6).map((r) => ({ file: r.file, score: r.cohesionScore })).sort((a, b) => a.score - b.score).slice(0, 10);
|
|
479
|
+
const criticalIssues = results.filter((r) => r.severity === "critical").length;
|
|
480
|
+
const majorIssues = results.filter((r) => r.severity === "major").length;
|
|
481
|
+
const minorIssues = results.filter((r) => r.severity === "minor").length;
|
|
482
|
+
const totalPotentialSavings = results.reduce(
|
|
483
|
+
(sum, r) => sum + r.potentialSavings,
|
|
484
|
+
0
|
|
485
|
+
);
|
|
486
|
+
const topExpensiveFiles = results.sort((a, b) => b.contextBudget - a.contextBudget).slice(0, 10).map((r) => ({
|
|
487
|
+
file: r.file,
|
|
488
|
+
contextBudget: r.contextBudget,
|
|
489
|
+
severity: r.severity
|
|
490
|
+
}));
|
|
491
|
+
return {
|
|
492
|
+
totalFiles,
|
|
493
|
+
totalTokens,
|
|
494
|
+
avgContextBudget,
|
|
495
|
+
maxContextBudget,
|
|
496
|
+
avgImportDepth,
|
|
497
|
+
maxImportDepth,
|
|
498
|
+
deepFiles,
|
|
499
|
+
avgFragmentation,
|
|
500
|
+
fragmentedModules: fragmentedModules.slice(0, 10),
|
|
501
|
+
avgCohesion,
|
|
502
|
+
lowCohesionFiles,
|
|
503
|
+
criticalIssues,
|
|
504
|
+
majorIssues,
|
|
505
|
+
minorIssues,
|
|
506
|
+
totalPotentialSavings,
|
|
507
|
+
topExpensiveFiles
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
function analyzeIssues(params) {
|
|
511
|
+
const {
|
|
512
|
+
file,
|
|
513
|
+
importDepth,
|
|
514
|
+
contextBudget,
|
|
515
|
+
cohesionScore,
|
|
516
|
+
fragmentationScore,
|
|
517
|
+
maxDepth,
|
|
518
|
+
maxContextBudget,
|
|
519
|
+
minCohesion,
|
|
520
|
+
maxFragmentation,
|
|
521
|
+
circularDeps
|
|
522
|
+
} = params;
|
|
523
|
+
const issues = [];
|
|
524
|
+
const recommendations = [];
|
|
525
|
+
let severity = "info";
|
|
526
|
+
let potentialSavings = 0;
|
|
527
|
+
if (circularDeps.length > 0) {
|
|
528
|
+
severity = "critical";
|
|
529
|
+
issues.push(
|
|
530
|
+
`Part of ${circularDeps.length} circular dependency chain(s)`
|
|
531
|
+
);
|
|
532
|
+
recommendations.push("Break circular dependencies by extracting interfaces or using dependency injection");
|
|
533
|
+
potentialSavings += contextBudget * 0.2;
|
|
534
|
+
}
|
|
535
|
+
if (importDepth > maxDepth * 1.5) {
|
|
536
|
+
severity = severity === "critical" ? "critical" : "critical";
|
|
537
|
+
issues.push(`Import depth ${importDepth} exceeds limit by 50%`);
|
|
538
|
+
recommendations.push("Flatten dependency tree or use facade pattern");
|
|
539
|
+
potentialSavings += contextBudget * 0.3;
|
|
540
|
+
} else if (importDepth > maxDepth) {
|
|
541
|
+
severity = severity === "critical" ? "critical" : "major";
|
|
542
|
+
issues.push(`Import depth ${importDepth} exceeds recommended maximum ${maxDepth}`);
|
|
543
|
+
recommendations.push("Consider reducing dependency depth");
|
|
544
|
+
potentialSavings += contextBudget * 0.15;
|
|
545
|
+
}
|
|
546
|
+
if (contextBudget > maxContextBudget * 1.5) {
|
|
547
|
+
severity = severity === "critical" ? "critical" : "critical";
|
|
548
|
+
issues.push(`Context budget ${contextBudget.toLocaleString()} tokens is 50% over limit`);
|
|
549
|
+
recommendations.push("Split into smaller modules or reduce dependency tree");
|
|
550
|
+
potentialSavings += contextBudget * 0.4;
|
|
551
|
+
} else if (contextBudget > maxContextBudget) {
|
|
552
|
+
severity = severity === "critical" || severity === "major" ? severity : "major";
|
|
553
|
+
issues.push(`Context budget ${contextBudget.toLocaleString()} exceeds ${maxContextBudget.toLocaleString()}`);
|
|
554
|
+
recommendations.push("Reduce file size or dependencies");
|
|
555
|
+
potentialSavings += contextBudget * 0.2;
|
|
556
|
+
}
|
|
557
|
+
if (cohesionScore < minCohesion * 0.5) {
|
|
558
|
+
severity = severity === "critical" ? "critical" : "major";
|
|
559
|
+
issues.push(`Very low cohesion (${(cohesionScore * 100).toFixed(0)}%) - mixed concerns`);
|
|
560
|
+
recommendations.push("Split file by domain - separate unrelated functionality");
|
|
561
|
+
potentialSavings += contextBudget * 0.25;
|
|
562
|
+
} else if (cohesionScore < minCohesion) {
|
|
563
|
+
severity = severity === "critical" || severity === "major" ? severity : "minor";
|
|
564
|
+
issues.push(`Low cohesion (${(cohesionScore * 100).toFixed(0)}%)`);
|
|
565
|
+
recommendations.push("Consider grouping related exports together");
|
|
566
|
+
potentialSavings += contextBudget * 0.1;
|
|
567
|
+
}
|
|
568
|
+
if (fragmentationScore > maxFragmentation) {
|
|
569
|
+
severity = severity === "critical" || severity === "major" ? severity : "minor";
|
|
570
|
+
issues.push(`High fragmentation (${(fragmentationScore * 100).toFixed(0)}%) - scattered implementation`);
|
|
571
|
+
recommendations.push("Consolidate with related files in same domain");
|
|
572
|
+
potentialSavings += contextBudget * 0.3;
|
|
573
|
+
}
|
|
574
|
+
if (issues.length === 0) {
|
|
575
|
+
issues.push("No significant issues detected");
|
|
576
|
+
recommendations.push("File is well-structured for AI context usage");
|
|
577
|
+
}
|
|
578
|
+
if (isBuildArtifact(file)) {
|
|
579
|
+
issues.push("Detected build artifact (bundled/output file)");
|
|
580
|
+
recommendations.push("Exclude build outputs (e.g., cdk.out, dist, build, .next) from analysis");
|
|
581
|
+
severity = downgradeSeverity(severity);
|
|
582
|
+
potentialSavings = 0;
|
|
583
|
+
}
|
|
584
|
+
return { severity, issues, recommendations, potentialSavings: Math.floor(potentialSavings) };
|
|
585
|
+
}
|
|
586
|
+
function isBuildArtifact(filePath) {
|
|
587
|
+
const lower = filePath.toLowerCase();
|
|
588
|
+
return lower.includes("/node_modules/") || lower.includes("/dist/") || lower.includes("/build/") || lower.includes("/out/") || lower.includes("/output/") || lower.includes("/cdk.out/") || lower.includes("/.next/") || /\/asset\.[^/]+\//.test(lower);
|
|
589
|
+
}
|
|
590
|
+
function downgradeSeverity(s) {
|
|
591
|
+
switch (s) {
|
|
592
|
+
case "critical":
|
|
593
|
+
return "minor";
|
|
594
|
+
case "major":
|
|
595
|
+
return "minor";
|
|
596
|
+
case "minor":
|
|
597
|
+
return "info";
|
|
598
|
+
default:
|
|
599
|
+
return "info";
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
export {
|
|
604
|
+
getSmartDefaults,
|
|
605
|
+
analyzeContext,
|
|
606
|
+
generateSummary
|
|
607
|
+
};
|
package/dist/cli.js
CHANGED
|
@@ -382,12 +382,14 @@ async function analyzeContext(options) {
|
|
|
382
382
|
potentialSavings
|
|
383
383
|
});
|
|
384
384
|
}
|
|
385
|
-
|
|
385
|
+
const issuesOnly = results.filter((r) => r.severity !== "info");
|
|
386
|
+
const sorted = issuesOnly.sort((a, b) => {
|
|
386
387
|
const severityOrder = { critical: 0, major: 1, minor: 2, info: 3 };
|
|
387
388
|
const severityDiff = severityOrder[a.severity] - severityOrder[b.severity];
|
|
388
389
|
if (severityDiff !== 0) return severityDiff;
|
|
389
390
|
return b.contextBudget - a.contextBudget;
|
|
390
391
|
});
|
|
392
|
+
return sorted.length > 0 ? sorted : results;
|
|
391
393
|
}
|
|
392
394
|
function generateSummary(results) {
|
|
393
395
|
if (results.length === 0) {
|
|
@@ -667,11 +669,40 @@ program.name("aiready-context").description("Analyze AI context window cost and
|
|
|
667
669
|
return;
|
|
668
670
|
}
|
|
669
671
|
displayConsoleReport(summary, results, elapsedTime, finalOptions.maxResults);
|
|
672
|
+
displayTuningGuidance(results, finalOptions);
|
|
670
673
|
} catch (error) {
|
|
671
674
|
(0, import_core3.handleCLIError)(error, "Analysis");
|
|
672
675
|
}
|
|
673
676
|
});
|
|
674
677
|
program.parse();
|
|
678
|
+
function displayTuningGuidance(results, options) {
|
|
679
|
+
const issueCount = results.filter((r) => r.severity !== "info").length;
|
|
680
|
+
if (issueCount === 0) {
|
|
681
|
+
console.log(import_chalk.default.green("\n\u2728 No issues found! Your code is well-structured for AI context usage.\n"));
|
|
682
|
+
return;
|
|
683
|
+
}
|
|
684
|
+
console.log(import_chalk.default.cyan("\n\u2501".repeat(60)));
|
|
685
|
+
console.log(import_chalk.default.bold.white(" TUNING GUIDANCE"));
|
|
686
|
+
console.log(import_chalk.default.cyan("\u2501".repeat(60) + "\n"));
|
|
687
|
+
if (issueCount < 5) {
|
|
688
|
+
console.log(import_chalk.default.yellow("\u{1F4CA} Showing few issues. To catch more potential problems:\n"));
|
|
689
|
+
console.log(import_chalk.default.dim(" \u2022 Lower --max-depth (currently: " + options.maxDepth + ") to catch shallower import chains"));
|
|
690
|
+
console.log(import_chalk.default.dim(" \u2022 Lower --max-context (currently: " + options.maxContextBudget.toLocaleString() + ") to catch smaller files"));
|
|
691
|
+
console.log(import_chalk.default.dim(" \u2022 Raise --min-cohesion (currently: " + (options.minCohesion * 100).toFixed(0) + "%) to be stricter about mixed concerns"));
|
|
692
|
+
console.log(import_chalk.default.dim(" \u2022 Lower --max-fragmentation (currently: " + (options.maxFragmentation * 100).toFixed(0) + "%) to catch scattered code\n"));
|
|
693
|
+
} else if (issueCount > 20) {
|
|
694
|
+
console.log(import_chalk.default.yellow("\u{1F4CA} Showing many issues. To focus on most critical problems:\n"));
|
|
695
|
+
console.log(import_chalk.default.dim(" \u2022 Raise --max-depth (currently: " + options.maxDepth + ") to only catch very deep chains"));
|
|
696
|
+
console.log(import_chalk.default.dim(" \u2022 Raise --max-context (currently: " + options.maxContextBudget.toLocaleString() + ") to focus on largest files"));
|
|
697
|
+
console.log(import_chalk.default.dim(" \u2022 Lower --min-cohesion (currently: " + (options.minCohesion * 100).toFixed(0) + "%) to only flag severe mixed concerns"));
|
|
698
|
+
console.log(import_chalk.default.dim(" \u2022 Raise --max-fragmentation (currently: " + (options.maxFragmentation * 100).toFixed(0) + "%) to only flag highly scattered code\n"));
|
|
699
|
+
} else {
|
|
700
|
+
console.log(import_chalk.default.green("\u{1F4CA} Good balance of issues detected (showing " + issueCount + " issues)\n"));
|
|
701
|
+
console.log(import_chalk.default.dim(" \u{1F4A1} Tip: Adjust thresholds if needed:"));
|
|
702
|
+
console.log(import_chalk.default.dim(" aiready-context . --max-depth N --max-context N --min-cohesion 0.X\n"));
|
|
703
|
+
}
|
|
704
|
+
console.log(import_chalk.default.dim(" \u{1F4D6} See README for detailed tuning guide\n"));
|
|
705
|
+
}
|
|
675
706
|
function displayConsoleReport(summary, results, elapsedTime, maxResults = 10) {
|
|
676
707
|
const terminalWidth = process.stdout.columns || 80;
|
|
677
708
|
const dividerWidth = Math.min(60, terminalWidth - 2);
|
package/dist/cli.mjs
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import {
|
|
3
3
|
analyzeContext,
|
|
4
4
|
generateSummary
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-45P4RDYP.mjs";
|
|
6
6
|
|
|
7
7
|
// src/cli.ts
|
|
8
8
|
import { Command } from "commander";
|
|
@@ -90,11 +90,40 @@ program.name("aiready-context").description("Analyze AI context window cost and
|
|
|
90
90
|
return;
|
|
91
91
|
}
|
|
92
92
|
displayConsoleReport(summary, results, elapsedTime, finalOptions.maxResults);
|
|
93
|
+
displayTuningGuidance(results, finalOptions);
|
|
93
94
|
} catch (error) {
|
|
94
95
|
handleCLIError(error, "Analysis");
|
|
95
96
|
}
|
|
96
97
|
});
|
|
97
98
|
program.parse();
|
|
99
|
+
function displayTuningGuidance(results, options) {
|
|
100
|
+
const issueCount = results.filter((r) => r.severity !== "info").length;
|
|
101
|
+
if (issueCount === 0) {
|
|
102
|
+
console.log(chalk.green("\n\u2728 No issues found! Your code is well-structured for AI context usage.\n"));
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
console.log(chalk.cyan("\n\u2501".repeat(60)));
|
|
106
|
+
console.log(chalk.bold.white(" TUNING GUIDANCE"));
|
|
107
|
+
console.log(chalk.cyan("\u2501".repeat(60) + "\n"));
|
|
108
|
+
if (issueCount < 5) {
|
|
109
|
+
console.log(chalk.yellow("\u{1F4CA} Showing few issues. To catch more potential problems:\n"));
|
|
110
|
+
console.log(chalk.dim(" \u2022 Lower --max-depth (currently: " + options.maxDepth + ") to catch shallower import chains"));
|
|
111
|
+
console.log(chalk.dim(" \u2022 Lower --max-context (currently: " + options.maxContextBudget.toLocaleString() + ") to catch smaller files"));
|
|
112
|
+
console.log(chalk.dim(" \u2022 Raise --min-cohesion (currently: " + (options.minCohesion * 100).toFixed(0) + "%) to be stricter about mixed concerns"));
|
|
113
|
+
console.log(chalk.dim(" \u2022 Lower --max-fragmentation (currently: " + (options.maxFragmentation * 100).toFixed(0) + "%) to catch scattered code\n"));
|
|
114
|
+
} else if (issueCount > 20) {
|
|
115
|
+
console.log(chalk.yellow("\u{1F4CA} Showing many issues. To focus on most critical problems:\n"));
|
|
116
|
+
console.log(chalk.dim(" \u2022 Raise --max-depth (currently: " + options.maxDepth + ") to only catch very deep chains"));
|
|
117
|
+
console.log(chalk.dim(" \u2022 Raise --max-context (currently: " + options.maxContextBudget.toLocaleString() + ") to focus on largest files"));
|
|
118
|
+
console.log(chalk.dim(" \u2022 Lower --min-cohesion (currently: " + (options.minCohesion * 100).toFixed(0) + "%) to only flag severe mixed concerns"));
|
|
119
|
+
console.log(chalk.dim(" \u2022 Raise --max-fragmentation (currently: " + (options.maxFragmentation * 100).toFixed(0) + "%) to only flag highly scattered code\n"));
|
|
120
|
+
} else {
|
|
121
|
+
console.log(chalk.green("\u{1F4CA} Good balance of issues detected (showing " + issueCount + " issues)\n"));
|
|
122
|
+
console.log(chalk.dim(" \u{1F4A1} Tip: Adjust thresholds if needed:"));
|
|
123
|
+
console.log(chalk.dim(" aiready-context . --max-depth N --max-context N --min-cohesion 0.X\n"));
|
|
124
|
+
}
|
|
125
|
+
console.log(chalk.dim(" \u{1F4D6} See README for detailed tuning guide\n"));
|
|
126
|
+
}
|
|
98
127
|
function displayConsoleReport(summary, results, elapsedTime, maxResults = 10) {
|
|
99
128
|
const terminalWidth = process.stdout.columns || 80;
|
|
100
129
|
const dividerWidth = Math.min(60, terminalWidth - 2);
|
package/dist/index.d.mts
CHANGED
|
@@ -69,6 +69,7 @@ interface ContextSummary {
|
|
|
69
69
|
|
|
70
70
|
/**
|
|
71
71
|
* Generate smart defaults for context analysis based on repository size
|
|
72
|
+
* Automatically tunes thresholds to target ~10 most serious issues
|
|
72
73
|
*/
|
|
73
74
|
declare function getSmartDefaults(directory: string, userOptions: Partial<ContextAnalyzerOptions>): Promise<ContextAnalyzerOptions>;
|
|
74
75
|
/**
|
package/dist/index.d.ts
CHANGED
|
@@ -69,6 +69,7 @@ interface ContextSummary {
|
|
|
69
69
|
|
|
70
70
|
/**
|
|
71
71
|
* Generate smart defaults for context analysis based on repository size
|
|
72
|
+
* Automatically tunes thresholds to target ~10 most serious issues
|
|
72
73
|
*/
|
|
73
74
|
declare function getSmartDefaults(directory: string, userOptions: Partial<ContextAnalyzerOptions>): Promise<ContextAnalyzerOptions>;
|
|
74
75
|
/**
|
package/dist/index.js
CHANGED
|
@@ -312,25 +312,25 @@ async function getSmartDefaults(directory, userOptions) {
|
|
|
312
312
|
let minCohesion;
|
|
313
313
|
let maxFragmentation;
|
|
314
314
|
if (estimatedBlocks < 100) {
|
|
315
|
-
maxDepth = 3;
|
|
316
|
-
maxContextBudget = 5e3;
|
|
317
|
-
minCohesion = 0.7;
|
|
318
|
-
maxFragmentation = 0.3;
|
|
319
|
-
} else if (estimatedBlocks < 500) {
|
|
320
315
|
maxDepth = 4;
|
|
321
316
|
maxContextBudget = 8e3;
|
|
322
|
-
minCohesion = 0.
|
|
323
|
-
maxFragmentation = 0.4;
|
|
324
|
-
} else if (estimatedBlocks < 2e3) {
|
|
325
|
-
maxDepth = 5;
|
|
326
|
-
maxContextBudget = 12e3;
|
|
327
|
-
minCohesion = 0.6;
|
|
317
|
+
minCohesion = 0.5;
|
|
328
318
|
maxFragmentation = 0.5;
|
|
329
|
-
} else {
|
|
330
|
-
maxDepth =
|
|
331
|
-
maxContextBudget =
|
|
332
|
-
minCohesion = 0.
|
|
319
|
+
} else if (estimatedBlocks < 500) {
|
|
320
|
+
maxDepth = 5;
|
|
321
|
+
maxContextBudget = 15e3;
|
|
322
|
+
minCohesion = 0.45;
|
|
333
323
|
maxFragmentation = 0.6;
|
|
324
|
+
} else if (estimatedBlocks < 2e3) {
|
|
325
|
+
maxDepth = 7;
|
|
326
|
+
maxContextBudget = 25e3;
|
|
327
|
+
minCohesion = 0.4;
|
|
328
|
+
maxFragmentation = 0.7;
|
|
329
|
+
} else {
|
|
330
|
+
maxDepth = 10;
|
|
331
|
+
maxContextBudget = 4e4;
|
|
332
|
+
minCohesion = 0.35;
|
|
333
|
+
maxFragmentation = 0.8;
|
|
334
334
|
}
|
|
335
335
|
return {
|
|
336
336
|
maxDepth,
|
|
@@ -424,12 +424,14 @@ async function analyzeContext(options) {
|
|
|
424
424
|
potentialSavings
|
|
425
425
|
});
|
|
426
426
|
}
|
|
427
|
-
|
|
427
|
+
const issuesOnly = results.filter((r) => r.severity !== "info");
|
|
428
|
+
const sorted = issuesOnly.sort((a, b) => {
|
|
428
429
|
const severityOrder = { critical: 0, major: 1, minor: 2, info: 3 };
|
|
429
430
|
const severityDiff = severityOrder[a.severity] - severityOrder[b.severity];
|
|
430
431
|
if (severityDiff !== 0) return severityDiff;
|
|
431
432
|
return b.contextBudget - a.contextBudget;
|
|
432
433
|
});
|
|
434
|
+
return sorted.length > 0 ? sorted : results;
|
|
433
435
|
}
|
|
434
436
|
function generateSummary(results) {
|
|
435
437
|
if (results.length === 0) {
|
package/dist/index.mjs
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aiready/context-analyzer",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.5",
|
|
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",
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
"commander": "^12.1.0",
|
|
51
51
|
"chalk": "^5.3.0",
|
|
52
52
|
"prompts": "^2.4.2",
|
|
53
|
-
"@aiready/core": "0.5.
|
|
53
|
+
"@aiready/core": "0.5.5"
|
|
54
54
|
},
|
|
55
55
|
"devDependencies": {
|
|
56
56
|
"@types/node": "^22.10.2",
|
package/src/cli.ts
CHANGED
|
@@ -121,6 +121,9 @@ program
|
|
|
121
121
|
|
|
122
122
|
// Console output
|
|
123
123
|
displayConsoleReport(summary, results, elapsedTime, finalOptions.maxResults);
|
|
124
|
+
|
|
125
|
+
// Show tuning guidance after results
|
|
126
|
+
displayTuningGuidance(results, finalOptions);
|
|
124
127
|
} catch (error) {
|
|
125
128
|
handleCLIError(error, 'Analysis');
|
|
126
129
|
}
|
|
@@ -128,6 +131,45 @@ program
|
|
|
128
131
|
|
|
129
132
|
program.parse();
|
|
130
133
|
|
|
134
|
+
/**
|
|
135
|
+
* Display tuning guidance to help users adjust thresholds
|
|
136
|
+
*/
|
|
137
|
+
function displayTuningGuidance(
|
|
138
|
+
results: Awaited<ReturnType<typeof analyzeContext>>,
|
|
139
|
+
options: any
|
|
140
|
+
): void {
|
|
141
|
+
const issueCount = results.filter(r => r.severity !== 'info').length;
|
|
142
|
+
|
|
143
|
+
if (issueCount === 0) {
|
|
144
|
+
console.log(chalk.green('\n✨ No issues found! Your code is well-structured for AI context usage.\n'));
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
console.log(chalk.cyan('\n━'.repeat(60)));
|
|
149
|
+
console.log(chalk.bold.white(' TUNING GUIDANCE'));
|
|
150
|
+
console.log(chalk.cyan('━'.repeat(60) + '\n'));
|
|
151
|
+
|
|
152
|
+
if (issueCount < 5) {
|
|
153
|
+
console.log(chalk.yellow('📊 Showing few issues. To catch more potential problems:\n'));
|
|
154
|
+
console.log(chalk.dim(' • Lower --max-depth (currently: ' + options.maxDepth + ') to catch shallower import chains'));
|
|
155
|
+
console.log(chalk.dim(' • Lower --max-context (currently: ' + options.maxContextBudget.toLocaleString() + ') to catch smaller files'));
|
|
156
|
+
console.log(chalk.dim(' • Raise --min-cohesion (currently: ' + (options.minCohesion * 100).toFixed(0) + '%) to be stricter about mixed concerns'));
|
|
157
|
+
console.log(chalk.dim(' • Lower --max-fragmentation (currently: ' + (options.maxFragmentation * 100).toFixed(0) + '%) to catch scattered code\n'));
|
|
158
|
+
} else if (issueCount > 20) {
|
|
159
|
+
console.log(chalk.yellow('📊 Showing many issues. To focus on most critical problems:\n'));
|
|
160
|
+
console.log(chalk.dim(' • Raise --max-depth (currently: ' + options.maxDepth + ') to only catch very deep chains'));
|
|
161
|
+
console.log(chalk.dim(' • Raise --max-context (currently: ' + options.maxContextBudget.toLocaleString() + ') to focus on largest files'));
|
|
162
|
+
console.log(chalk.dim(' • Lower --min-cohesion (currently: ' + (options.minCohesion * 100).toFixed(0) + '%) to only flag severe mixed concerns'));
|
|
163
|
+
console.log(chalk.dim(' • Raise --max-fragmentation (currently: ' + (options.maxFragmentation * 100).toFixed(0) + '%) to only flag highly scattered code\n'));
|
|
164
|
+
} else {
|
|
165
|
+
console.log(chalk.green('📊 Good balance of issues detected (showing ' + issueCount + ' issues)\n'));
|
|
166
|
+
console.log(chalk.dim(' 💡 Tip: Adjust thresholds if needed:'));
|
|
167
|
+
console.log(chalk.dim(' aiready-context . --max-depth N --max-context N --min-cohesion 0.X\n'));
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
console.log(chalk.dim(' 📖 See README for detailed tuning guide\n'));
|
|
171
|
+
}
|
|
172
|
+
|
|
131
173
|
/**
|
|
132
174
|
* Display formatted console report
|
|
133
175
|
*/
|
package/src/index.ts
CHANGED
|
@@ -21,6 +21,7 @@ export type { ContextAnalyzerOptions, ContextAnalysisResult, ContextSummary, Mod
|
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
23
|
* Generate smart defaults for context analysis based on repository size
|
|
24
|
+
* Automatically tunes thresholds to target ~10 most serious issues
|
|
24
25
|
*/
|
|
25
26
|
async function getSmartDefaults(
|
|
26
27
|
directory: string,
|
|
@@ -36,35 +37,37 @@ async function getSmartDefaults(
|
|
|
36
37
|
const estimatedBlocks = files.length;
|
|
37
38
|
|
|
38
39
|
// Smart defaults based on repository size
|
|
40
|
+
// Adjusted to be stricter (higher thresholds) to catch only serious issues
|
|
41
|
+
// This targets ~10 most critical issues instead of showing all files
|
|
39
42
|
let maxDepth: number;
|
|
40
43
|
let maxContextBudget: number;
|
|
41
44
|
let minCohesion: number;
|
|
42
45
|
let maxFragmentation: number;
|
|
43
46
|
|
|
44
47
|
if (estimatedBlocks < 100) {
|
|
45
|
-
// Small project
|
|
46
|
-
maxDepth = 3;
|
|
47
|
-
maxContextBudget = 5000;
|
|
48
|
-
minCohesion = 0.7;
|
|
49
|
-
maxFragmentation = 0.3;
|
|
50
|
-
} else if (estimatedBlocks < 500) {
|
|
51
|
-
// Medium project
|
|
48
|
+
// Small project - be more lenient
|
|
52
49
|
maxDepth = 4;
|
|
53
50
|
maxContextBudget = 8000;
|
|
54
|
-
minCohesion = 0.
|
|
55
|
-
maxFragmentation = 0.4;
|
|
56
|
-
} else if (estimatedBlocks < 2000) {
|
|
57
|
-
// Large project
|
|
58
|
-
maxDepth = 5;
|
|
59
|
-
maxContextBudget = 12000;
|
|
60
|
-
minCohesion = 0.6;
|
|
51
|
+
minCohesion = 0.5;
|
|
61
52
|
maxFragmentation = 0.5;
|
|
62
|
-
} else {
|
|
63
|
-
//
|
|
64
|
-
maxDepth =
|
|
65
|
-
maxContextBudget =
|
|
66
|
-
minCohesion = 0.
|
|
53
|
+
} else if (estimatedBlocks < 500) {
|
|
54
|
+
// Medium project - moderate strictness
|
|
55
|
+
maxDepth = 5;
|
|
56
|
+
maxContextBudget = 15000;
|
|
57
|
+
minCohesion = 0.45;
|
|
67
58
|
maxFragmentation = 0.6;
|
|
59
|
+
} else if (estimatedBlocks < 2000) {
|
|
60
|
+
// Large project - stricter to focus on worst issues
|
|
61
|
+
maxDepth = 7;
|
|
62
|
+
maxContextBudget = 25000;
|
|
63
|
+
minCohesion = 0.4;
|
|
64
|
+
maxFragmentation = 0.7;
|
|
65
|
+
} else {
|
|
66
|
+
// Enterprise project - very strict to show only critical issues
|
|
67
|
+
maxDepth = 10;
|
|
68
|
+
maxContextBudget = 40000;
|
|
69
|
+
minCohesion = 0.35;
|
|
70
|
+
maxFragmentation = 0.8;
|
|
68
71
|
}
|
|
69
72
|
|
|
70
73
|
return {
|
|
@@ -205,13 +208,20 @@ export async function analyzeContext(
|
|
|
205
208
|
});
|
|
206
209
|
}
|
|
207
210
|
|
|
211
|
+
// Filter to only files with actual issues (not just info)
|
|
212
|
+
// This reduces output noise and focuses on actionable problems
|
|
213
|
+
const issuesOnly = results.filter(r => r.severity !== 'info');
|
|
214
|
+
|
|
208
215
|
// Sort by severity and context budget
|
|
209
|
-
|
|
216
|
+
const sorted = issuesOnly.sort((a, b) => {
|
|
210
217
|
const severityOrder = { critical: 0, major: 1, minor: 2, info: 3 };
|
|
211
218
|
const severityDiff = severityOrder[a.severity] - severityOrder[b.severity];
|
|
212
219
|
if (severityDiff !== 0) return severityDiff;
|
|
213
220
|
return b.contextBudget - a.contextBudget;
|
|
214
221
|
});
|
|
222
|
+
|
|
223
|
+
// If we have issues, return them; otherwise return all results
|
|
224
|
+
return sorted.length > 0 ? sorted : results;
|
|
215
225
|
}
|
|
216
226
|
|
|
217
227
|
/**
|