@aiready/context-analyzer 0.3.7 → 0.4.0

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/dist/cli.mjs CHANGED
@@ -2,14 +2,15 @@
2
2
  import {
3
3
  analyzeContext,
4
4
  generateSummary
5
- } from "./chunk-T6ZCOPPI.mjs";
5
+ } from "./chunk-HDFXSEFW.mjs";
6
6
 
7
7
  // src/cli.ts
8
8
  import { Command } from "commander";
9
9
  import chalk from "chalk";
10
- import { writeFileSync } from "fs";
10
+ import { writeFileSync, existsSync, readFileSync } from "fs";
11
11
  import { join } from "path";
12
12
  import { loadMergedConfig, handleJSONOutput, handleCLIError, getElapsedTime } from "@aiready/core";
13
+ import prompts from "prompts";
13
14
  var program = new Command();
14
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(
15
16
  "--max-context <number>",
@@ -24,7 +25,7 @@ program.name("aiready-context").description("Analyze AI context window cost and
24
25
  "-o, --output <format>",
25
26
  "Output format: console, json, html",
26
27
  "console"
27
- ).option("--output-file <path>", "Output file path (for json/html)").action(async (directory, options) => {
28
+ ).option("--output-file <path>", "Output file path (for json/html)").option("--interactive", "Run interactive setup to suggest excludes and focus areas").action(async (directory, options) => {
28
29
  console.log(chalk.blue("\u{1F50D} Analyzing context window costs...\n"));
29
30
  const startTime = Date.now();
30
31
  try {
@@ -39,7 +40,7 @@ program.name("aiready-context").description("Analyze AI context window cost and
39
40
  exclude: void 0,
40
41
  maxResults: 10
41
42
  };
42
- const finalOptions = loadMergedConfig(directory, defaults, {
43
+ let finalOptions = loadMergedConfig(directory, defaults, {
43
44
  maxDepth: options.maxDepth ? parseInt(options.maxDepth) : void 0,
44
45
  maxContextBudget: options.maxContext ? parseInt(options.maxContext) : void 0,
45
46
  minCohesion: options.minCohesion ? parseFloat(options.minCohesion) : void 0,
@@ -50,6 +51,9 @@ program.name("aiready-context").description("Analyze AI context window cost and
50
51
  exclude: options.exclude?.split(","),
51
52
  maxResults: options.maxResults ? parseInt(options.maxResults) : void 0
52
53
  });
54
+ if (options.interactive) {
55
+ finalOptions = await runInteractiveSetup(directory, finalOptions);
56
+ }
53
57
  const results = await analyzeContext(finalOptions);
54
58
  const elapsedTime = getElapsedTime(startTime);
55
59
  const summary = generateSummary(results);
@@ -384,3 +388,55 @@ function generateHTMLReport(summary, results) {
384
388
  </body>
385
389
  </html>`;
386
390
  }
391
+ async function runInteractiveSetup(directory, current) {
392
+ console.log(chalk.yellow("\u{1F9ED} Interactive mode: let\u2019s tailor the analysis."));
393
+ const pkgPath = join(directory, "package.json");
394
+ let deps = {};
395
+ if (existsSync(pkgPath)) {
396
+ try {
397
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
398
+ deps = { ...pkg.dependencies || {}, ...pkg.devDependencies || {} };
399
+ } catch {
400
+ }
401
+ }
402
+ const hasNextJs = existsSync(join(directory, ".next")) || !!deps["next"];
403
+ const hasCDK = existsSync(join(directory, "cdk.out")) || !!deps["aws-cdk-lib"] || Object.keys(deps).some((d) => d.startsWith("@aws-cdk/"));
404
+ const recommendedExcludes = new Set(current.exclude || []);
405
+ if (hasNextJs && !Array.from(recommendedExcludes).some((p) => p.includes(".next"))) {
406
+ recommendedExcludes.add("**/.next/**");
407
+ }
408
+ if (hasCDK && !Array.from(recommendedExcludes).some((p) => p.includes("cdk.out"))) {
409
+ recommendedExcludes.add("**/cdk.out/**");
410
+ }
411
+ const { applyExcludes } = await prompts({
412
+ type: "toggle",
413
+ name: "applyExcludes",
414
+ message: `Detected ${hasNextJs ? "Next.js " : ""}${hasCDK ? "AWS CDK " : ""}frameworks. Apply recommended excludes?`,
415
+ initial: true,
416
+ active: "yes",
417
+ inactive: "no"
418
+ });
419
+ let nextOptions = { ...current };
420
+ if (applyExcludes) {
421
+ nextOptions.exclude = Array.from(recommendedExcludes);
422
+ }
423
+ const { focusArea } = await prompts({
424
+ type: "select",
425
+ name: "focusArea",
426
+ message: "Which areas to focus?",
427
+ choices: [
428
+ { title: "Frontend (web app)", value: "frontend" },
429
+ { title: "Backend (API/infra)", value: "backend" },
430
+ { title: "Both", value: "both" }
431
+ ],
432
+ initial: 2
433
+ });
434
+ if (focusArea === "frontend") {
435
+ nextOptions.include = ["**/*.{ts,tsx,js,jsx}"];
436
+ nextOptions.exclude = Array.from(/* @__PURE__ */ new Set([...nextOptions.exclude || [], "**/cdk.out/**", "**/infra/**", "**/server/**", "**/backend/**"]));
437
+ } else if (focusArea === "backend") {
438
+ nextOptions.include = ["**/api/**", "**/server/**", "**/backend/**", "**/infra/**", "**/*.{ts,js,py,java}"];
439
+ }
440
+ console.log(chalk.green("\u2713 Interactive configuration applied."));
441
+ return nextOptions;
442
+ }
package/dist/index.d.mts CHANGED
@@ -67,6 +67,10 @@ interface ContextSummary {
67
67
  }>;
68
68
  }
69
69
 
70
+ /**
71
+ * Generate smart defaults for context analysis based on repository size
72
+ */
73
+ declare function getSmartDefaults(directory: string, userOptions: Partial<ContextAnalyzerOptions>): Promise<ContextAnalyzerOptions>;
70
74
  /**
71
75
  * Analyze AI context window cost for a codebase
72
76
  */
@@ -76,4 +80,4 @@ declare function analyzeContext(options: ContextAnalyzerOptions): Promise<Contex
76
80
  */
77
81
  declare function generateSummary(results: ContextAnalysisResult[]): ContextSummary;
78
82
 
79
- export { type ContextAnalysisResult, type ContextAnalyzerOptions, type ContextSummary, type ModuleCluster, analyzeContext, generateSummary };
83
+ export { type ContextAnalysisResult, type ContextAnalyzerOptions, type ContextSummary, type ModuleCluster, analyzeContext, generateSummary, getSmartDefaults };
package/dist/index.d.ts CHANGED
@@ -67,6 +67,10 @@ interface ContextSummary {
67
67
  }>;
68
68
  }
69
69
 
70
+ /**
71
+ * Generate smart defaults for context analysis based on repository size
72
+ */
73
+ declare function getSmartDefaults(directory: string, userOptions: Partial<ContextAnalyzerOptions>): Promise<ContextAnalyzerOptions>;
70
74
  /**
71
75
  * Analyze AI context window cost for a codebase
72
76
  */
@@ -76,4 +80,4 @@ declare function analyzeContext(options: ContextAnalyzerOptions): Promise<Contex
76
80
  */
77
81
  declare function generateSummary(results: ContextAnalysisResult[]): ContextSummary;
78
82
 
79
- export { type ContextAnalysisResult, type ContextAnalyzerOptions, type ContextSummary, type ModuleCluster, analyzeContext, generateSummary };
83
+ export { type ContextAnalysisResult, type ContextAnalyzerOptions, type ContextSummary, type ModuleCluster, analyzeContext, generateSummary, getSmartDefaults };
package/dist/index.js CHANGED
@@ -21,7 +21,8 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
23
  analyzeContext: () => analyzeContext,
24
- generateSummary: () => generateSummary
24
+ generateSummary: () => generateSummary,
25
+ getSmartDefaults: () => getSmartDefaults
25
26
  });
26
27
  module.exports = __toCommonJS(index_exports);
27
28
  var import_core2 = require("@aiready/core");
@@ -299,6 +300,50 @@ function generateConsolidationPlan(domain, files, targetFiles) {
299
300
  }
300
301
 
301
302
  // src/index.ts
303
+ async function getSmartDefaults(directory, userOptions) {
304
+ const files = await (0, import_core2.scanFiles)({
305
+ rootDir: directory,
306
+ include: userOptions.include,
307
+ exclude: userOptions.exclude
308
+ });
309
+ const estimatedBlocks = files.length;
310
+ let maxDepth;
311
+ let maxContextBudget;
312
+ let minCohesion;
313
+ let maxFragmentation;
314
+ if (estimatedBlocks < 100) {
315
+ maxDepth = 3;
316
+ maxContextBudget = 5e3;
317
+ minCohesion = 0.7;
318
+ maxFragmentation = 0.3;
319
+ } else if (estimatedBlocks < 500) {
320
+ maxDepth = 4;
321
+ 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;
328
+ maxFragmentation = 0.5;
329
+ } else {
330
+ maxDepth = 6;
331
+ maxContextBudget = 2e4;
332
+ minCohesion = 0.55;
333
+ maxFragmentation = 0.6;
334
+ }
335
+ return {
336
+ maxDepth,
337
+ maxContextBudget,
338
+ minCohesion,
339
+ maxFragmentation,
340
+ focus: "all",
341
+ includeNodeModules: false,
342
+ rootDir: userOptions.rootDir || directory,
343
+ include: userOptions.include,
344
+ exclude: userOptions.exclude
345
+ };
346
+ }
302
347
  async function analyzeContext(options) {
303
348
  const {
304
349
  maxDepth = 5,
@@ -554,10 +599,33 @@ function analyzeIssues(params) {
554
599
  issues.push("No significant issues detected");
555
600
  recommendations.push("File is well-structured for AI context usage");
556
601
  }
602
+ if (isBuildArtifact(file)) {
603
+ issues.push("Detected build artifact (bundled/output file)");
604
+ recommendations.push("Exclude build outputs (e.g., cdk.out, dist, build, .next) from analysis");
605
+ severity = downgradeSeverity(severity);
606
+ potentialSavings = 0;
607
+ }
557
608
  return { severity, issues, recommendations, potentialSavings: Math.floor(potentialSavings) };
558
609
  }
610
+ function isBuildArtifact(filePath) {
611
+ const lower = filePath.toLowerCase();
612
+ 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);
613
+ }
614
+ function downgradeSeverity(s) {
615
+ switch (s) {
616
+ case "critical":
617
+ return "minor";
618
+ case "major":
619
+ return "minor";
620
+ case "minor":
621
+ return "info";
622
+ default:
623
+ return "info";
624
+ }
625
+ }
559
626
  // Annotate the CommonJS export names for ESM import in node:
560
627
  0 && (module.exports = {
561
628
  analyzeContext,
562
- generateSummary
629
+ generateSummary,
630
+ getSmartDefaults
563
631
  });
package/dist/index.mjs CHANGED
@@ -1,8 +1,10 @@
1
1
  import {
2
2
  analyzeContext,
3
- generateSummary
4
- } from "./chunk-T6ZCOPPI.mjs";
3
+ generateSummary,
4
+ getSmartDefaults
5
+ } from "./chunk-HDFXSEFW.mjs";
5
6
  export {
6
7
  analyzeContext,
7
- generateSummary
8
+ generateSummary,
9
+ getSmartDefaults
8
10
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aiready/context-analyzer",
3
- "version": "0.3.7",
3
+ "version": "0.4.0",
4
4
  "description": "AI context window cost analysis - detect fragmented code, deep import chains, and expensive context budgets",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -49,10 +49,12 @@
49
49
  "dependencies": {
50
50
  "commander": "^12.1.0",
51
51
  "chalk": "^5.3.0",
52
- "@aiready/core": "0.3.6"
52
+ "prompts": "^2.4.2",
53
+ "@aiready/core": "0.5.0"
53
54
  },
54
55
  "devDependencies": {
55
56
  "@types/node": "^22.10.2",
57
+ "@types/prompts": "^2.4.9",
56
58
  "tsup": "^8.3.5",
57
59
  "typescript": "^5.7.3",
58
60
  "vitest": "^2.1.9",
package/src/cli.ts CHANGED
@@ -3,9 +3,10 @@
3
3
  import { Command } from 'commander';
4
4
  import { analyzeContext, generateSummary } from './index';
5
5
  import chalk from 'chalk';
6
- import { writeFileSync } from 'fs';
6
+ import { writeFileSync, existsSync, readFileSync } from 'fs';
7
7
  import { join } from 'path';
8
8
  import { loadMergedConfig, handleJSONOutput, handleCLIError, getElapsedTime } from '@aiready/core';
9
+ import prompts from 'prompts';
9
10
 
10
11
  const program = new Command();
11
12
 
@@ -39,6 +40,7 @@ program
39
40
  'console'
40
41
  )
41
42
  .option('--output-file <path>', 'Output file path (for json/html)')
43
+ .option('--interactive', 'Run interactive setup to suggest excludes and focus areas')
42
44
  .action(async (directory, options) => {
43
45
  console.log(chalk.blue('🔍 Analyzing context window costs...\n'));
44
46
 
@@ -59,7 +61,7 @@ program
59
61
  };
60
62
 
61
63
  // Load and merge config with CLI options
62
- const finalOptions = loadMergedConfig(directory, defaults, {
64
+ let finalOptions = loadMergedConfig(directory, defaults, {
63
65
  maxDepth: options.maxDepth ? parseInt(options.maxDepth) : undefined,
64
66
  maxContextBudget: options.maxContext ? parseInt(options.maxContext) : undefined,
65
67
  minCohesion: options.minCohesion ? parseFloat(options.minCohesion) : undefined,
@@ -71,6 +73,11 @@ program
71
73
  maxResults: options.maxResults ? parseInt(options.maxResults) : undefined,
72
74
  }) as any;
73
75
 
76
+ // Optional: interactive setup to refine options for first-time users
77
+ if (options.interactive) {
78
+ finalOptions = await runInteractiveSetup(directory, finalOptions);
79
+ }
80
+
74
81
  const results = await analyzeContext(finalOptions);
75
82
 
76
83
  const elapsedTime = getElapsedTime(startTime);
@@ -455,3 +462,66 @@ function generateHTMLReport(
455
462
  </body>
456
463
  </html>`;
457
464
  }
465
+
466
+ /**
467
+ * Interactive setup: detect common frameworks and suggest excludes & focus areas
468
+ */
469
+ async function runInteractiveSetup(directory: string, current: any): Promise<any> {
470
+ console.log(chalk.yellow('🧭 Interactive mode: let’s tailor the analysis.'));
471
+
472
+ const pkgPath = join(directory, 'package.json');
473
+ let deps: Record<string, string> = {};
474
+ if (existsSync(pkgPath)) {
475
+ try {
476
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
477
+ deps = { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) };
478
+ } catch {}
479
+ }
480
+
481
+ const hasNextJs = existsSync(join(directory, '.next')) || !!deps['next'];
482
+ const hasCDK = existsSync(join(directory, 'cdk.out')) || !!deps['aws-cdk-lib'] || Object.keys(deps).some(d => d.startsWith('@aws-cdk/'));
483
+
484
+ const recommendedExcludes = new Set<string>(current.exclude || []);
485
+ if (hasNextJs && !Array.from(recommendedExcludes).some((p) => p.includes('.next'))) {
486
+ recommendedExcludes.add('**/.next/**');
487
+ }
488
+ if (hasCDK && !Array.from(recommendedExcludes).some((p) => p.includes('cdk.out'))) {
489
+ recommendedExcludes.add('**/cdk.out/**');
490
+ }
491
+
492
+ const { applyExcludes } = await prompts({
493
+ type: 'toggle',
494
+ name: 'applyExcludes',
495
+ message: `Detected ${hasNextJs ? 'Next.js ' : ''}${hasCDK ? 'AWS CDK ' : ''}frameworks. Apply recommended excludes?`,
496
+ initial: true,
497
+ active: 'yes',
498
+ inactive: 'no',
499
+ });
500
+
501
+ let nextOptions = { ...current };
502
+ if (applyExcludes) {
503
+ nextOptions.exclude = Array.from(recommendedExcludes);
504
+ }
505
+
506
+ const { focusArea } = await prompts({
507
+ type: 'select',
508
+ name: 'focusArea',
509
+ message: 'Which areas to focus?',
510
+ choices: [
511
+ { title: 'Frontend (web app)', value: 'frontend' },
512
+ { title: 'Backend (API/infra)', value: 'backend' },
513
+ { title: 'Both', value: 'both' },
514
+ ],
515
+ initial: 2,
516
+ });
517
+
518
+ if (focusArea === 'frontend') {
519
+ nextOptions.include = ['**/*.{ts,tsx,js,jsx}'];
520
+ nextOptions.exclude = Array.from(new Set([...(nextOptions.exclude || []), '**/cdk.out/**', '**/infra/**', '**/server/**', '**/backend/**']));
521
+ } else if (focusArea === 'backend') {
522
+ nextOptions.include = ['**/api/**', '**/server/**', '**/backend/**', '**/infra/**', '**/*.{ts,js,py,java}'];
523
+ }
524
+
525
+ console.log(chalk.green('✓ Interactive configuration applied.'));
526
+ return nextOptions;
527
+ }
package/src/index.ts CHANGED
@@ -19,6 +19,67 @@ import type {
19
19
 
20
20
  export type { ContextAnalyzerOptions, ContextAnalysisResult, ContextSummary, ModuleCluster };
21
21
 
22
+ /**
23
+ * Generate smart defaults for context analysis based on repository size
24
+ */
25
+ async function getSmartDefaults(
26
+ directory: string,
27
+ userOptions: Partial<ContextAnalyzerOptions>
28
+ ): Promise<ContextAnalyzerOptions> {
29
+ // Estimate repository size by scanning files
30
+ const files = await scanFiles({
31
+ rootDir: directory,
32
+ include: userOptions.include,
33
+ exclude: userOptions.exclude,
34
+ });
35
+
36
+ const estimatedBlocks = files.length;
37
+
38
+ // Smart defaults based on repository size
39
+ let maxDepth: number;
40
+ let maxContextBudget: number;
41
+ let minCohesion: number;
42
+ let maxFragmentation: number;
43
+
44
+ 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
52
+ maxDepth = 4;
53
+ 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;
61
+ maxFragmentation = 0.5;
62
+ } else {
63
+ // Enterprise project
64
+ maxDepth = 6;
65
+ maxContextBudget = 20000;
66
+ minCohesion = 0.55;
67
+ maxFragmentation = 0.6;
68
+ }
69
+
70
+ return {
71
+ maxDepth,
72
+ maxContextBudget,
73
+ minCohesion,
74
+ maxFragmentation,
75
+ focus: 'all',
76
+ includeNodeModules: false,
77
+ rootDir: userOptions.rootDir || directory,
78
+ include: userOptions.include,
79
+ exclude: userOptions.exclude,
80
+ };
81
+ }
82
+
22
83
  /**
23
84
  * Analyze AI context window cost for a codebase
24
85
  */
@@ -392,5 +453,46 @@ function analyzeIssues(params: {
392
453
  recommendations.push('File is well-structured for AI context usage');
393
454
  }
394
455
 
456
+ // Detect build artifacts and downgrade severity to reduce noise
457
+ if (isBuildArtifact(file)) {
458
+ issues.push('Detected build artifact (bundled/output file)');
459
+ recommendations.push('Exclude build outputs (e.g., cdk.out, dist, build, .next) from analysis');
460
+ severity = downgradeSeverity(severity);
461
+ // Build artifacts do not represent actionable savings
462
+ potentialSavings = 0;
463
+ }
464
+
395
465
  return { severity, issues, recommendations, potentialSavings: Math.floor(potentialSavings) };
396
466
  }
467
+
468
+ export { getSmartDefaults };
469
+
470
+ /**
471
+ * Heuristic: identify common build artifact paths
472
+ */
473
+ function isBuildArtifact(filePath: string): boolean {
474
+ const lower = filePath.toLowerCase();
475
+ return (
476
+ lower.includes('/node_modules/') ||
477
+ lower.includes('/dist/') ||
478
+ lower.includes('/build/') ||
479
+ lower.includes('/out/') ||
480
+ lower.includes('/output/') ||
481
+ lower.includes('/cdk.out/') ||
482
+ lower.includes('/.next/') ||
483
+ /\/asset\.[^/]+\//.test(lower) // e.g., cdk.out/asset.*
484
+ );
485
+ }
486
+
487
+ function downgradeSeverity(s: ContextAnalysisResult['severity']): ContextAnalysisResult['severity'] {
488
+ switch (s) {
489
+ case 'critical':
490
+ return 'minor';
491
+ case 'major':
492
+ return 'minor';
493
+ case 'minor':
494
+ return 'info';
495
+ default:
496
+ return 'info';
497
+ }
498
+ }