@aiready/context-analyzer 0.4.2 → 0.4.4

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.
@@ -1,6 +1,6 @@
1
1
 
2
2
  
3
- > @aiready/context-analyzer@0.4.2 build /Users/pengcao/projects/aiready/packages/context-analyzer
3
+ > @aiready/context-analyzer@0.4.4 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
  CLI Building entry: src/cli.ts, src/index.ts
@@ -9,16 +9,16 @@
9
9
  CLI Target: es2020
10
10
  CJS Build start
11
11
  ESM Build start
12
- ESM dist/cli.mjs 15.74 KB
12
+ ESM dist/cli.mjs 18.45 KB
13
13
  ESM dist/index.mjs 164.00 B
14
- ESM dist/chunk-HDFXSEFW.mjs 19.11 KB
15
- ESM ⚡️ Build success in 33ms
16
- CJS dist/cli.js 35.91 KB
17
- CJS dist/index.js 20.26 KB
18
- CJS ⚡️ Build success in 33ms
14
+ ESM dist/chunk-45P4RDYP.mjs 19.24 KB
15
+ ESM ⚡️ Build success in 22ms
16
+ CJS dist/index.js 20.38 KB
17
+ CJS dist/cli.js 39.03 KB
18
+ CJS ⚡️ Build success in 22ms
19
19
  DTS Build start
20
- DTS ⚡️ Build success in 646ms
20
+ DTS ⚡️ Build success in 608ms
21
21
  DTS dist/cli.d.ts 20.00 B
22
- DTS dist/index.d.ts 2.37 KB
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.37 KB
24
+ DTS dist/index.d.mts 2.44 KB
package/README.md CHANGED
@@ -75,8 +75,62 @@ aiready-context ./src --focus depth
75
75
  # Set thresholds
76
76
  aiready-context ./src --max-depth 5 --max-context 10000 --min-cohesion 0.6
77
77
 
78
- # Export to JSON
79
- aiready-context ./src --output json --output-file report.json
78
+ # Export to JSON (saved to .aiready/ by default)
79
+ aiready-context ./src --output json
80
+
81
+ # Or specify custom path
82
+ aiready-context ./src --output json --output-file custom-report.json
83
+ ```
84
+
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
+
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
+ ```
80
134
 
81
135
  # Generate HTML report
82
136
  aiready-context ./src --output html --output-file report.html
@@ -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
- return results.sort((a, b) => {
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) {
@@ -641,24 +643,66 @@ program.name("aiready-context").description("Analyze AI context window cost and
641
643
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
642
644
  analysisTime: elapsedTime
643
645
  };
644
- (0, import_core3.handleJSONOutput)(jsonOutput, options.outputFile, `
645
- \u2713 JSON report saved to ${options.outputFile}`);
646
+ const outputPath = (0, import_core3.resolveOutputPath)(
647
+ options.outputFile,
648
+ `context-report-${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}.json`,
649
+ directory
650
+ );
651
+ (0, import_core3.handleJSONOutput)(jsonOutput, outputPath, `
652
+ \u2713 JSON report saved to ${outputPath}`);
646
653
  return;
647
654
  }
648
655
  if (options.output === "html") {
649
656
  const html = generateHTMLReport(summary, results);
650
- const outputPath = options.outputFile || (0, import_path.join)(process.cwd(), "context-report.html");
657
+ const outputPath = (0, import_core3.resolveOutputPath)(
658
+ options.outputFile,
659
+ `context-report-${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}.html`,
660
+ directory
661
+ );
662
+ const dir = (0, import_path.dirname)(outputPath);
663
+ if (!(0, import_fs.existsSync)(dir)) {
664
+ (0, import_fs.mkdirSync)(dir, { recursive: true });
665
+ }
651
666
  (0, import_fs.writeFileSync)(outputPath, html);
652
667
  console.log(import_chalk.default.green(`
653
668
  \u2713 HTML report saved to ${outputPath}`));
654
669
  return;
655
670
  }
656
671
  displayConsoleReport(summary, results, elapsedTime, finalOptions.maxResults);
672
+ displayTuningGuidance(results, finalOptions);
657
673
  } catch (error) {
658
674
  (0, import_core3.handleCLIError)(error, "Analysis");
659
675
  }
660
676
  });
661
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
+ }
662
706
  function displayConsoleReport(summary, results, elapsedTime, maxResults = 10) {
663
707
  const terminalWidth = process.stdout.columns || 80;
664
708
  const dividerWidth = Math.min(60, terminalWidth - 2);
package/dist/cli.mjs CHANGED
@@ -2,14 +2,14 @@
2
2
  import {
3
3
  analyzeContext,
4
4
  generateSummary
5
- } from "./chunk-HDFXSEFW.mjs";
5
+ } from "./chunk-45P4RDYP.mjs";
6
6
 
7
7
  // src/cli.ts
8
8
  import { Command } from "commander";
9
9
  import chalk from "chalk";
10
- import { writeFileSync, existsSync, readFileSync } from "fs";
11
- import { join } from "path";
12
- import { loadMergedConfig, handleJSONOutput, handleCLIError, getElapsedTime } from "@aiready/core";
10
+ import { writeFileSync, existsSync, readFileSync, mkdirSync } from "fs";
11
+ import { join, dirname } from "path";
12
+ import { loadMergedConfig, handleJSONOutput, handleCLIError, getElapsedTime, resolveOutputPath } from "@aiready/core";
13
13
  import prompts from "prompts";
14
14
  var program = new Command();
15
15
  program.name("aiready-context").description("Analyze AI context window cost and code structure").version("0.1.0").addHelpText("after", "\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").argument("<directory>", "Directory to analyze").option("--max-depth <number>", "Maximum acceptable import depth").option(
@@ -64,24 +64,66 @@ program.name("aiready-context").description("Analyze AI context window cost and
64
64
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
65
65
  analysisTime: elapsedTime
66
66
  };
67
- handleJSONOutput(jsonOutput, options.outputFile, `
68
- \u2713 JSON report saved to ${options.outputFile}`);
67
+ const outputPath = resolveOutputPath(
68
+ options.outputFile,
69
+ `context-report-${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}.json`,
70
+ directory
71
+ );
72
+ handleJSONOutput(jsonOutput, outputPath, `
73
+ \u2713 JSON report saved to ${outputPath}`);
69
74
  return;
70
75
  }
71
76
  if (options.output === "html") {
72
77
  const html = generateHTMLReport(summary, results);
73
- const outputPath = options.outputFile || join(process.cwd(), "context-report.html");
78
+ const outputPath = resolveOutputPath(
79
+ options.outputFile,
80
+ `context-report-${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}.html`,
81
+ directory
82
+ );
83
+ const dir = dirname(outputPath);
84
+ if (!existsSync(dir)) {
85
+ mkdirSync(dir, { recursive: true });
86
+ }
74
87
  writeFileSync(outputPath, html);
75
88
  console.log(chalk.green(`
76
89
  \u2713 HTML report saved to ${outputPath}`));
77
90
  return;
78
91
  }
79
92
  displayConsoleReport(summary, results, elapsedTime, finalOptions.maxResults);
93
+ displayTuningGuidance(results, finalOptions);
80
94
  } catch (error) {
81
95
  handleCLIError(error, "Analysis");
82
96
  }
83
97
  });
84
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
+ }
85
127
  function displayConsoleReport(summary, results, elapsedTime, maxResults = 10) {
86
128
  const terminalWidth = process.stdout.columns || 80;
87
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.65;
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 = 6;
331
- maxContextBudget = 2e4;
332
- minCohesion = 0.55;
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
- return results.sort((a, b) => {
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
@@ -2,7 +2,7 @@ import {
2
2
  analyzeContext,
3
3
  generateSummary,
4
4
  getSmartDefaults
5
- } from "./chunk-HDFXSEFW.mjs";
5
+ } from "./chunk-45P4RDYP.mjs";
6
6
  export {
7
7
  analyzeContext,
8
8
  generateSummary,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aiready/context-analyzer",
3
- "version": "0.4.2",
3
+ "version": "0.4.4",
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.1"
53
+ "@aiready/core": "0.5.4"
54
54
  },
55
55
  "devDependencies": {
56
56
  "@types/node": "^22.10.2",
package/src/cli.ts CHANGED
@@ -3,9 +3,9 @@
3
3
  import { Command } from 'commander';
4
4
  import { analyzeContext, generateSummary } from './index';
5
5
  import chalk from 'chalk';
6
- import { writeFileSync, existsSync, readFileSync } from 'fs';
7
- import { join } from 'path';
8
- import { loadMergedConfig, handleJSONOutput, handleCLIError, getElapsedTime } from '@aiready/core';
6
+ import { writeFileSync, existsSync, readFileSync, mkdirSync } from 'fs';
7
+ import { join, dirname } from 'path';
8
+ import { loadMergedConfig, handleJSONOutput, handleCLIError, getElapsedTime, resolveOutputPath } from '@aiready/core';
9
9
  import prompts from 'prompts';
10
10
 
11
11
  const program = new Command();
@@ -91,14 +91,29 @@ program
91
91
  analysisTime: elapsedTime,
92
92
  };
93
93
 
94
- handleJSONOutput(jsonOutput, options.outputFile, `\n✓ JSON report saved to ${options.outputFile}`);
94
+ const outputPath = resolveOutputPath(
95
+ options.outputFile,
96
+ `context-report-${new Date().toISOString().split('T')[0]}.json`,
97
+ directory
98
+ );
99
+
100
+ handleJSONOutput(jsonOutput, outputPath, `\n✓ JSON report saved to ${outputPath}`);
95
101
  return;
96
102
  }
97
103
 
98
104
  if (options.output === 'html') {
99
105
  const html = generateHTMLReport(summary, results);
100
- const outputPath =
101
- options.outputFile || join(process.cwd(), 'context-report.html');
106
+ const outputPath = resolveOutputPath(
107
+ options.outputFile,
108
+ `context-report-${new Date().toISOString().split('T')[0]}.html`,
109
+ directory
110
+ );
111
+
112
+ const dir = dirname(outputPath);
113
+ if (!existsSync(dir)) {
114
+ mkdirSync(dir, { recursive: true });
115
+ }
116
+
102
117
  writeFileSync(outputPath, html);
103
118
  console.log(chalk.green(`\n✓ HTML report saved to ${outputPath}`));
104
119
  return;
@@ -106,6 +121,9 @@ program
106
121
 
107
122
  // Console output
108
123
  displayConsoleReport(summary, results, elapsedTime, finalOptions.maxResults);
124
+
125
+ // Show tuning guidance after results
126
+ displayTuningGuidance(results, finalOptions);
109
127
  } catch (error) {
110
128
  handleCLIError(error, 'Analysis');
111
129
  }
@@ -113,6 +131,45 @@ program
113
131
 
114
132
  program.parse();
115
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
+
116
173
  /**
117
174
  * Display formatted console report
118
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.65;
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
- // Enterprise project
64
- maxDepth = 6;
65
- maxContextBudget = 20000;
66
- minCohesion = 0.55;
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
- return results.sort((a, b) => {
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
  /**