@aiready/cli 0.15.0 → 0.15.2

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.
Files changed (4) hide show
  1. package/README.md +10 -8
  2. package/dist/cli.js +194 -276
  3. package/dist/cli.mjs +183 -269
  4. package/package.json +13 -13
package/dist/cli.js CHANGED
@@ -25,7 +25,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
25
25
 
26
26
  // src/cli.ts
27
27
  var import_commander = require("commander");
28
- var import_fs9 = require("fs");
28
+ var import_fs8 = require("fs");
29
29
  var import_path8 = require("path");
30
30
  var import_url = require("url");
31
31
 
@@ -96,38 +96,6 @@ async function warnIfGraphCapExceeded(report, dirPath) {
96
96
  void err;
97
97
  }
98
98
  }
99
- function generateMarkdownReport(report, elapsedTime) {
100
- let markdown = `# Consistency Analysis Report
101
-
102
- `;
103
- markdown += `**Generated:** ${(/* @__PURE__ */ new Date()).toISOString()}
104
- `;
105
- markdown += `**Analysis Time:** ${elapsedTime}s
106
-
107
- `;
108
- markdown += `## Summary
109
-
110
- `;
111
- markdown += `- **Files Analyzed:** ${report.summary.filesAnalyzed}
112
- `;
113
- markdown += `- **Total Issues:** ${report.summary.totalIssues}
114
- `;
115
- markdown += ` - Naming: ${report.summary.namingIssues}
116
- `;
117
- markdown += ` - Patterns: ${report.summary.patternIssues}
118
-
119
- `;
120
- if (report.recommendations.length > 0) {
121
- markdown += `## Recommendations
122
-
123
- `;
124
- report.recommendations.forEach((rec, i) => {
125
- markdown += `${i + 1}. ${rec}
126
- `;
127
- });
128
- }
129
- return markdown;
130
- }
131
99
 
132
100
  // src/commands/report-formatter.ts
133
101
  var import_chalk2 = __toESM(require("chalk"));
@@ -471,7 +439,8 @@ function getProfileTools(profile) {
471
439
  return [
472
440
  import_core5.ToolName.AiSignalClarity,
473
441
  import_core5.ToolName.AgentGrounding,
474
- import_core5.ToolName.TestabilityIndex
442
+ import_core5.ToolName.TestabilityIndex,
443
+ import_core5.ToolName.ContractEnforcement
475
444
  ];
476
445
  case "cost":
477
446
  return [import_core5.ToolName.PatternDetect, import_core5.ToolName.ContextAnalyzer];
@@ -481,7 +450,8 @@ function getProfileTools(profile) {
481
450
  import_core5.ToolName.NamingConsistency,
482
451
  import_core5.ToolName.ContextAnalyzer,
483
452
  import_core5.ToolName.PatternDetect,
484
- import_core5.ToolName.ChangeAmplification
453
+ import_core5.ToolName.ChangeAmplification,
454
+ import_core5.ToolName.ContractEnforcement
485
455
  ];
486
456
  case "ui":
487
457
  return [
@@ -492,7 +462,11 @@ function getProfileTools(profile) {
492
462
  import_core5.ToolName.AiSignalClarity
493
463
  ];
494
464
  case "security":
495
- return [import_core5.ToolName.NamingConsistency, import_core5.ToolName.TestabilityIndex];
465
+ return [
466
+ import_core5.ToolName.NamingConsistency,
467
+ import_core5.ToolName.TestabilityIndex,
468
+ import_core5.ToolName.ContractEnforcement
469
+ ];
496
470
  case "onboarding":
497
471
  return [
498
472
  import_core5.ToolName.ContextAnalyzer,
@@ -1164,6 +1138,38 @@ ${import_chalk6.default.bold("CI/CD Integration:")}
1164
1138
  Use --threshold and --fail-on to use AIReady as a quality gate in your CI pipelines.
1165
1139
  When running in GitHub Actions, it will automatically emit annotations for found issues.
1166
1140
  `;
1141
+ function defineScanCommand(program2) {
1142
+ program2.command("scan").description(
1143
+ "Run comprehensive AI-readiness analysis (patterns + context + consistency)"
1144
+ ).argument("[directory]", "Directory to analyze", ".").option(
1145
+ "-t, --tools <tools>",
1146
+ "Tools to run (comma-separated: patterns,context,consistency,doc-drift,deps-health,aiSignalClarity,grounding,testability,changeAmplification)"
1147
+ ).option(
1148
+ "--profile <type>",
1149
+ "Scan profile to use (agentic, cost, logic, ui, security, onboarding)"
1150
+ ).option(
1151
+ "--compare-to <path>",
1152
+ "Compare results against a previous AIReady report JSON"
1153
+ ).option(
1154
+ "--include <patterns>",
1155
+ "File patterns to include (comma-separated)"
1156
+ ).option(
1157
+ "--exclude <patterns>",
1158
+ "File patterns to exclude (comma-separated)"
1159
+ ).option("-o, --output <format>", "Output format: console, json", "console").option("--output-file <path>", "Output file path (for json)").option("--score", "Calculate and display AI Readiness Score (0-100)").option("--no-score", "Disable calculating AI Readiness Score").option("--weights <weights>", "Custom scoring weights").option(
1160
+ "--threshold <score>",
1161
+ "Fail CI/CD if score below threshold (0-100)"
1162
+ ).option(
1163
+ "--ci",
1164
+ "CI mode: GitHub Actions annotations, no colors, fail on threshold"
1165
+ ).option(
1166
+ "--fail-on <level>",
1167
+ "Fail on issues: critical, major, any",
1168
+ "critical"
1169
+ ).option("--api-key <key>", "Platform API key for automatic upload").option("--upload", "Automatically upload results to the platform").option("--server <url>", "Custom platform URL").addHelpText("after", SCAN_HELP_TEXT).action(async (directory, options) => {
1170
+ await scanAction(directory, options);
1171
+ });
1172
+ }
1167
1173
 
1168
1174
  // src/commands/init.ts
1169
1175
  var import_fs5 = require("fs");
@@ -1443,33 +1449,6 @@ function renderSafetyRating(safety) {
1443
1449
  ` AI Change Safety: ${color(`${icon} ${safety.toUpperCase()}`)}`
1444
1450
  );
1445
1451
  }
1446
- function renderIssueSummaryBlock(summary) {
1447
- const total = (summary.criticalIssues || 0) + (summary.majorIssues || 0) + (summary.minorIssues || 0);
1448
- if (total === 0) {
1449
- console.log(import_chalk8.default.green("\u2705 No significant issues found!\n"));
1450
- return;
1451
- }
1452
- console.log(import_chalk8.default.bold("\u26A0\uFE0F Issues Found:\n"));
1453
- if (summary.criticalIssues > 0)
1454
- console.log(
1455
- import_chalk8.default.red(` \u{1F534} Critical: ${import_chalk8.default.bold(summary.criticalIssues)}`)
1456
- );
1457
- if (summary.majorIssues > 0)
1458
- console.log(
1459
- import_chalk8.default.yellow(` \u{1F7E1} Major: ${import_chalk8.default.bold(summary.majorIssues)}`)
1460
- );
1461
- if (summary.minorIssues > 0)
1462
- console.log(import_chalk8.default.blue(` \u{1F535} Minor: ${import_chalk8.default.bold(summary.minorIssues)}`));
1463
- if (summary.totalPotentialSavings) {
1464
- console.log(
1465
- import_chalk8.default.green(
1466
- `
1467
- \u{1F4A1} Potential savings: ${import_chalk8.default.bold(summary.totalPotentialSavings.toLocaleString())} tokens
1468
- `
1469
- )
1470
- );
1471
- }
1472
- }
1473
1452
  function renderSubSection(title) {
1474
1453
  console.log("\n" + (0, import_core12.getTerminalDivider)());
1475
1454
  console.log(import_chalk8.default.bold.white(` ${title.toUpperCase()}`));
@@ -1486,6 +1465,36 @@ function renderToolScoreFooter(score) {
1486
1465
  }
1487
1466
 
1488
1467
  // src/commands/patterns.ts
1468
+ var PATTERNS_HELP_TEXT = `
1469
+ EXAMPLES:
1470
+ $ aiready patterns # Default analysis
1471
+ $ aiready patterns --similarity 0.6 # Stricter matching
1472
+ $ aiready patterns --min-lines 10 # Larger patterns only
1473
+ `;
1474
+ function definePatternsCommand(program2) {
1475
+ program2.command("patterns").description("Detect duplicate code patterns that confuse AI models").argument("[directory]", "Directory to analyze", ".").option(
1476
+ "-s, --similarity <number>",
1477
+ "Minimum similarity score (0-1)",
1478
+ "0.40"
1479
+ ).option("-l, --min-lines <number>", "Minimum lines to consider", "5").option(
1480
+ "--max-candidates <number>",
1481
+ "Maximum candidates per block (performance tuning)"
1482
+ ).option(
1483
+ "--min-shared-tokens <number>",
1484
+ "Minimum shared tokens for candidates (performance tuning)"
1485
+ ).option(
1486
+ "--full-scan",
1487
+ "Disable smart defaults for comprehensive analysis (slower)"
1488
+ ).option(
1489
+ "--include <patterns>",
1490
+ "File patterns to include (comma-separated)"
1491
+ ).option(
1492
+ "--exclude <patterns>",
1493
+ "File patterns to exclude (comma-separated)"
1494
+ ).option("-o, --output <format>", "Output format: console, json", "console").option("--output-file <path>", "Output file path (for json)").option("--score", "Calculate and display AI Readiness Score (0-100)").option("--no-score", "Disable calculating AI Readiness Score").addHelpText("after", PATTERNS_HELP_TEXT).action(async (directory, options) => {
1495
+ await patternsAction(directory, options);
1496
+ });
1497
+ }
1489
1498
  async function patternsAction(directory, options) {
1490
1499
  return await executeToolAction(directory, options, {
1491
1500
  toolName: "pattern-detect",
@@ -1565,21 +1574,30 @@ async function patternsAction(directory, options) {
1565
1574
  }
1566
1575
  });
1567
1576
  }
1568
- var PATTERNS_HELP_TEXT = `
1569
- EXAMPLES:
1570
- $ aiready patterns # Default analysis
1571
- $ aiready patterns --similarity 0.6 # Stricter matching
1572
- $ aiready patterns --min-lines 10 # Larger patterns only
1573
- `;
1574
1577
 
1575
1578
  // src/commands/context.ts
1576
1579
  var import_chalk10 = __toESM(require("chalk"));
1577
1580
  var import_core14 = require("@aiready/core");
1581
+ function defineContextCommand(program2) {
1582
+ program2.command("context").description("Analyze context window costs and dependency fragmentation").argument("[directory]", "Directory to analyze", ".").option("--max-depth <number>", "Maximum acceptable import depth", "5").option(
1583
+ "--max-context <number>",
1584
+ "Maximum acceptable context budget (tokens)",
1585
+ "10000"
1586
+ ).option(
1587
+ "--include <patterns>",
1588
+ "File patterns to include (comma-separated)"
1589
+ ).option(
1590
+ "--exclude <patterns>",
1591
+ "File patterns to exclude (comma-separated)"
1592
+ ).option("-o, --output <format>", "Output format: console, json", "console").option("--output-file <path>", "Output file path (for json)").option("--score", "Calculate and display AI Readiness Score (0-100)", true).option("--no-score", "Disable calculating AI Readiness Score").action(async (directory, options) => {
1593
+ await contextAction(directory, options);
1594
+ });
1595
+ }
1578
1596
  async function contextAction(directory, options) {
1579
1597
  return await executeToolAction(directory, options, {
1580
1598
  toolName: "context-analyzer",
1581
1599
  label: "Context analysis",
1582
- emoji: "\u{1F9E0}",
1600
+ emoji: "\u{1F9E9}",
1583
1601
  defaults: {
1584
1602
  maxDepth: 5,
1585
1603
  maxContextBudget: 1e4,
@@ -1591,11 +1609,6 @@ async function contextAction(directory, options) {
1591
1609
  maxDepth: opts.maxDepth ? parseInt(opts.maxDepth) : void 0,
1592
1610
  maxContextBudget: opts.maxContext ? parseInt(opts.maxContext) : void 0
1593
1611
  }),
1594
- preAnalyze: async (resolvedDir, baseOptions) => {
1595
- const { getSmartDefaults } = await import("@aiready/context-analyzer");
1596
- const smartDefaults = await getSmartDefaults(resolvedDir, baseOptions);
1597
- return { ...smartDefaults, ...baseOptions };
1598
- },
1599
1612
  importTool: async () => {
1600
1613
  const { analyzeContext, generateSummary, calculateContextScore } = await import("@aiready/context-analyzer");
1601
1614
  return {
@@ -1604,45 +1617,40 @@ async function contextAction(directory, options) {
1604
1617
  calculateScore: calculateContextScore
1605
1618
  };
1606
1619
  },
1607
- renderConsole: ({ summary, elapsedTime, score }) => {
1620
+ renderConsole: ({ results: _results, summary, elapsedTime, score }) => {
1608
1621
  (0, import_core14.printTerminalHeader)("CONTEXT ANALYSIS SUMMARY");
1609
1622
  console.log(
1610
- import_chalk10.default.white(`\u{1F4C1} Files analyzed: ${import_chalk10.default.bold(summary.totalFiles)}`)
1623
+ import_chalk10.default.white(`\u{1F4C1} Total files: ${import_chalk10.default.bold(summary.totalFiles)}`)
1611
1624
  );
1612
1625
  console.log(
1613
1626
  import_chalk10.default.white(
1614
- `\u{1F4CA} Total tokens: ${import_chalk10.default.bold(summary.totalTokens.toLocaleString())}`
1627
+ `\u{1F4B8} Total tokens (context budget): ${import_chalk10.default.bold(summary.totalTokens.toLocaleString())}`
1615
1628
  )
1616
1629
  );
1617
1630
  console.log(
1618
- import_chalk10.default.yellow(
1619
- `\u{1F4B0} Avg context budget: ${import_chalk10.default.bold(summary.avgContextBudget.toFixed(0))} tokens/file`
1631
+ import_chalk10.default.cyan(
1632
+ `\u{1F4CA} Average context budget: ${import_chalk10.default.bold(summary.avgContextBudget.toFixed(0))} tokens`
1620
1633
  )
1621
1634
  );
1622
1635
  console.log(
1623
- import_chalk10.default.white(`\u23F1 Analysis time: ${import_chalk10.default.bold(elapsedTime + "s")}
1624
- `)
1636
+ import_chalk10.default.gray(`\u23F1 Analysis time: ${import_chalk10.default.bold(elapsedTime + "s")}`)
1625
1637
  );
1626
- renderIssueSummaryBlock(summary);
1627
- if (summary.deepFiles && summary.deepFiles.length > 0) {
1628
- renderSubSection("Deep Import Chains");
1629
- summary.deepFiles.slice(0, 10).forEach((item) => {
1630
- const fileName = item.file.split("/").slice(-2).join("/");
1638
+ if (summary.fragmentedModules.length > 0) {
1639
+ renderSubSection("Top Fragmented Modules");
1640
+ summary.fragmentedModules.slice(0, 5).forEach((mod) => {
1641
+ const scoreColor = mod.fragmentationScore > 0.7 ? import_chalk10.default.red : mod.fragmentationScore > 0.4 ? import_chalk10.default.yellow : import_chalk10.default.green;
1631
1642
  console.log(
1632
- ` ${import_chalk10.default.cyan("\u2192")} ${import_chalk10.default.white(fileName)} ${import_chalk10.default.dim(`(depth: ${item.depth})`)}`
1643
+ ` ${scoreColor("\u25A0")} ${import_chalk10.default.white(mod.domain.padEnd(20))} ${import_chalk10.default.bold((mod.fragmentationScore * 100).toFixed(0) + "%")} fragmentation`
1633
1644
  );
1634
1645
  });
1635
1646
  }
1636
- if (summary.fragmentedModules && summary.fragmentedModules.length > 0) {
1637
- renderSubSection("Fragmented Modules");
1638
- summary.fragmentedModules.slice(0, 10).forEach((module2) => {
1639
- console.log(
1640
- ` ${import_chalk10.default.yellow("\u25CF")} ${import_chalk10.default.white(module2.domain)} - ${import_chalk10.default.dim(`${module2.files.length} files, ${(module2.fragmentationScore * 100).toFixed(0)}% scattered`)}`
1641
- );
1647
+ if (summary.topExpensiveFiles.length > 0) {
1648
+ renderSubSection("Top Context-Expensive Files");
1649
+ summary.topExpensiveFiles.slice(0, 5).forEach((item) => {
1650
+ const icon = item.severity === "critical" ? "\u{1F534}" : item.severity === "major" ? "\u{1F7E1}" : "\u{1F535}";
1651
+ const color = item.severity === "critical" ? import_chalk10.default.red : item.severity === "major" ? import_chalk10.default.yellow : import_chalk10.default.blue;
1642
1652
  console.log(
1643
- import_chalk10.default.dim(
1644
- ` Token cost: ${module2.totalTokens.toLocaleString()}, Cohesion: ${(module2.avgCohesion * 100).toFixed(0)}%`
1645
- )
1653
+ ` ${icon} ${color(item.severity.toUpperCase())}: ${import_chalk10.default.white(item.file.split("/").pop())} ${import_chalk10.default.dim(`(${item.contextBudget.toLocaleString()} tokens)`)}`
1646
1654
  );
1647
1655
  });
1648
1656
  }
@@ -1653,17 +1661,35 @@ async function contextAction(directory, options) {
1653
1661
 
1654
1662
  // src/commands/consistency.ts
1655
1663
  var import_chalk11 = __toESM(require("chalk"));
1656
- var import_fs6 = require("fs");
1657
1664
  var import_core15 = require("@aiready/core");
1665
+ function defineConsistencyCommand(program2) {
1666
+ program2.command("consistency").description("Check naming conventions and architectural consistency").argument("[directory]", "Directory to analyze", ".").option("--naming", "Check naming conventions (default: true)").option("--no-naming", "Skip naming analysis").option("--patterns", "Check code patterns (default: true)").option("--no-patterns", "Skip pattern analysis").option(
1667
+ "--min-severity <level>",
1668
+ "Minimum severity: info|minor|major|critical",
1669
+ "info"
1670
+ ).option(
1671
+ "--include <patterns>",
1672
+ "File patterns to include (comma-separated)"
1673
+ ).option(
1674
+ "--exclude <patterns>",
1675
+ "File patterns to exclude (comma-separated)"
1676
+ ).option(
1677
+ "-o, --output <format>",
1678
+ "Output format: console, json, markdown",
1679
+ "console"
1680
+ ).option("--output-file <path>", "Output file path (for json/markdown)").option("--score", "Calculate and display AI Readiness Score (0-100)", true).option("--no-score", "Disable calculating AI Readiness Score").action(async (directory, options) => {
1681
+ await consistencyAction(directory, options);
1682
+ });
1683
+ }
1658
1684
  async function consistencyAction(directory, options) {
1659
1685
  return await executeToolAction(directory, options, {
1660
1686
  toolName: "naming-consistency",
1661
1687
  label: "Consistency analysis",
1662
- emoji: "\u{1F50D}",
1688
+ emoji: "\u{1F4CF}",
1663
1689
  defaults: {
1664
- checkNaming: true,
1665
- checkPatterns: true,
1666
- minSeverity: "info",
1690
+ checkNaming: options.naming !== false,
1691
+ checkPatterns: options.patterns !== false,
1692
+ minSeverity: options.minSeverity || "info",
1667
1693
  include: void 0,
1668
1694
  exclude: void 0,
1669
1695
  output: { format: "console", file: void 0 }
@@ -1674,119 +1700,68 @@ async function consistencyAction(directory, options) {
1674
1700
  minSeverity: opts.minSeverity
1675
1701
  }),
1676
1702
  importTool: async () => {
1677
- const { analyzeConsistency, calculateConsistencyScore } = await import("@aiready/consistency");
1703
+ const { analyzeConsistency, generateSummary, calculateConsistencyScore } = await import("@aiready/consistency");
1678
1704
  return {
1679
- analyze: analyzeConsistency,
1680
- generateSummary: (report) => report.summary,
1681
- calculateScore: (summary, _resultsCount) => {
1682
- return calculateConsistencyScore(
1683
- summary.results?.flatMap((r) => r.issues) ?? [],
1684
- summary.summary.filesAnalyzed
1685
- );
1686
- }
1705
+ analyze: async (opts) => {
1706
+ const report = await analyzeConsistency(opts);
1707
+ return report;
1708
+ },
1709
+ generateSummary,
1710
+ calculateScore: calculateConsistencyScore
1687
1711
  };
1688
1712
  },
1689
- renderConsole: ({ results, summary, elapsedTime, score, finalOptions }) => {
1690
- const report = results;
1691
- const { format: outputFormat, file: userOutputFile } = (0, import_core15.resolveOutputFormat)(options, finalOptions);
1692
- if (outputFormat === "markdown") {
1693
- const markdown = generateMarkdownReport(report, elapsedTime);
1694
- const outputPath = (0, import_core15.resolveOutputPath)(
1695
- userOutputFile,
1696
- `aiready-report-${(0, import_core2.getReportTimestamp)()}.md`,
1697
- directory
1698
- );
1699
- (0, import_fs6.writeFileSync)(outputPath, markdown);
1700
- console.log(import_chalk11.default.green(`\u2705 Report saved to ${outputPath}`));
1701
- return;
1702
- }
1703
- console.log(import_chalk11.default.bold("\n\u{1F4CA} Summary\n"));
1704
- console.log(`Files Analyzed: ${import_chalk11.default.cyan(summary.filesAnalyzed)}`);
1705
- console.log(`Total Issues: ${import_chalk11.default.yellow(summary.totalIssues)}`);
1706
- console.log(` Naming: ${import_chalk11.default.yellow(summary.namingIssues)}`);
1707
- console.log(` Patterns: ${import_chalk11.default.yellow(summary.patternIssues)}`);
1713
+ renderConsole: ({ results: report, summary, elapsedTime, score }) => {
1714
+ (0, import_core15.printTerminalHeader)("CONSISTENCY ANALYSIS SUMMARY");
1708
1715
  console.log(
1709
- ` Architecture: ${import_chalk11.default.yellow(summary.architectureIssues ?? 0)}`
1716
+ import_chalk11.default.white(`\u{1F4C1} Files analyzed: ${import_chalk11.default.bold(summary.filesAnalyzed)}`)
1710
1717
  );
1711
- console.log(`Analysis Time: ${import_chalk11.default.gray(elapsedTime + "s")}
1712
- `);
1713
- if (summary.totalIssues === 0) {
1714
- console.log(
1715
- import_chalk11.default.green(
1716
- "\u2728 No consistency issues found! Your codebase is well-maintained.\n"
1717
- )
1718
- );
1719
- } else {
1720
- const namingResults = report.results.filter(
1721
- (r) => r.issues.some((i) => i.category === "naming")
1722
- );
1723
- const patternResults = report.results.filter(
1724
- (r) => r.issues.some((i) => i.category === "patterns")
1725
- );
1726
- if (namingResults.length > 0) {
1727
- console.log(import_chalk11.default.bold("\u{1F3F7}\uFE0F Naming Issues\n"));
1728
- let shown = 0;
1729
- for (const namingFileResult of namingResults) {
1730
- if (shown >= 5) break;
1731
- for (const issue of namingFileResult.issues) {
1732
- if (shown >= 5) break;
1733
- const severityColor = issue.severity === "critical" ? import_chalk11.default.red : issue.severity === "major" ? import_chalk11.default.yellow : issue.severity === "minor" ? import_chalk11.default.blue : import_chalk11.default.gray;
1734
- console.log(
1735
- `${severityColor(issue.severity.toUpperCase())} ${import_chalk11.default.dim(`${issue.location.file}:${issue.location.line}`)}`
1736
- );
1737
- console.log(` ${issue.message}`);
1738
- if (issue.suggestion) {
1739
- console.log(
1740
- ` ${import_chalk11.default.dim("\u2192")} ${import_chalk11.default.italic(issue.suggestion)}`
1741
- );
1742
- }
1743
- console.log();
1744
- shown++;
1745
- }
1746
- }
1747
- }
1748
- if (patternResults.length > 0) {
1749
- console.log(import_chalk11.default.bold("\u{1F504} Pattern Issues\n"));
1750
- let shown = 0;
1751
- for (const patternFileResult of patternResults) {
1752
- if (shown >= 5) break;
1753
- for (const issue of patternFileResult.issues) {
1754
- if (shown >= 5) break;
1755
- const severityColor = issue.severity === "critical" ? import_chalk11.default.red : issue.severity === "major" ? import_chalk11.default.yellow : issue.severity === "minor" ? import_chalk11.default.blue : import_chalk11.default.gray;
1756
- console.log(
1757
- `${severityColor(issue.severity.toUpperCase())} ${import_chalk11.default.dim(`${issue.location.file}:${issue.location.line}`)}`
1758
- );
1759
- console.log(` ${issue.message}`);
1760
- if (issue.suggestion) {
1761
- console.log(
1762
- ` ${import_chalk11.default.dim("\u2192")} ${import_chalk11.default.italic(issue.suggestion)}`
1763
- );
1764
- }
1765
- console.log();
1766
- shown++;
1767
- }
1718
+ console.log(
1719
+ import_chalk11.default.white(`\u26A0 Total issues: ${import_chalk11.default.bold(summary.totalIssues)}`)
1720
+ );
1721
+ console.log(
1722
+ import_chalk11.default.gray(`\u23F1 Analysis time: ${import_chalk11.default.bold(elapsedTime + "s")}`)
1723
+ );
1724
+ if (summary.totalIssues > 0 && report.results) {
1725
+ renderSubSection("Issues Breakdown");
1726
+ const sortedIssues = [...report.results].flatMap(
1727
+ (file) => (file.issues || []).map((issue) => ({
1728
+ ...issue,
1729
+ file: file.fileName
1730
+ }))
1731
+ ).sort((a, b) => {
1732
+ const levels = {
1733
+ critical: 4,
1734
+ major: 3,
1735
+ minor: 2,
1736
+ info: 1
1737
+ };
1738
+ return (levels[b.severity] || 0) - (levels[a.severity] || 0);
1739
+ }).slice(0, 10);
1740
+ sortedIssues.forEach((issue) => {
1741
+ const icon = issue.severity === "critical" ? "\u{1F534}" : issue.severity === "major" ? "\u{1F7E1}" : "\u{1F535}";
1742
+ const color = issue.severity === "critical" ? import_chalk11.default.red : issue.severity === "major" ? import_chalk11.default.yellow : import_chalk11.default.blue;
1743
+ console.log(
1744
+ ` ${icon} ${color(issue.severity.toUpperCase())}: ${import_chalk11.default.white(issue.file)}${issue.line ? `:${issue.line}` : ""}`
1745
+ );
1746
+ console.log(` ${issue.message}`);
1747
+ if (issue.suggestion) {
1748
+ console.log(import_chalk11.default.dim(` \u{1F4A1} ${issue.suggestion}`));
1768
1749
  }
1769
- }
1770
- if (report.recommendations?.length > 0) {
1771
- console.log(import_chalk11.default.bold("\u{1F4A1} Recommendations\n"));
1772
- report.recommendations.forEach((rec, i) => {
1773
- console.log(`${i + 1}. ${rec}`);
1774
- });
1775
1750
  console.log();
1776
- }
1777
- }
1778
- if (score) {
1779
- console.log(import_chalk11.default.bold("\n\u{1F4CA} AI Readiness Score (Consistency)\n"));
1780
- console.log((0, import_core15.formatToolScore)(score));
1781
- console.log();
1751
+ });
1752
+ } else {
1753
+ console.log(
1754
+ import_chalk11.default.green("\n\u2728 Great! No consistency issues detected.\n")
1755
+ );
1782
1756
  }
1757
+ renderToolScoreFooter(score);
1783
1758
  }
1784
1759
  });
1785
1760
  }
1786
1761
 
1787
1762
  // src/commands/visualize.ts
1788
1763
  var import_chalk12 = __toESM(require("chalk"));
1789
- var import_fs7 = require("fs");
1764
+ var import_fs6 = require("fs");
1790
1765
  var import_path6 = require("path");
1791
1766
  var import_child_process = require("child_process");
1792
1767
  var import_core16 = require("@aiready/core");
@@ -1795,7 +1770,7 @@ async function visualizeAction(directory, options) {
1795
1770
  try {
1796
1771
  const dirPath = (0, import_path6.resolve)(process.cwd(), directory ?? ".");
1797
1772
  let reportPath = options.report ? (0, import_path6.resolve)(dirPath, options.report) : null;
1798
- if (!reportPath || !(0, import_fs7.existsSync)(reportPath)) {
1773
+ if (!reportPath || !(0, import_fs6.existsSync)(reportPath)) {
1799
1774
  const latestScan = (0, import_core17.findLatestReport)(dirPath);
1800
1775
  if (latestScan) {
1801
1776
  reportPath = latestScan;
@@ -1817,13 +1792,13 @@ Or specify a custom report:
1817
1792
  return;
1818
1793
  }
1819
1794
  }
1820
- const raw = (0, import_fs7.readFileSync)(reportPath, "utf8");
1795
+ const raw = (0, import_fs6.readFileSync)(reportPath, "utf8");
1821
1796
  const report = JSON.parse(raw);
1822
1797
  const configPath = (0, import_path6.resolve)(dirPath, "aiready.json");
1823
1798
  let graphConfig = { maxNodes: 400, maxEdges: 600 };
1824
- if ((0, import_fs7.existsSync)(configPath)) {
1799
+ if ((0, import_fs6.existsSync)(configPath)) {
1825
1800
  try {
1826
- const rawConfig = JSON.parse((0, import_fs7.readFileSync)(configPath, "utf8"));
1801
+ const rawConfig = JSON.parse((0, import_fs6.readFileSync)(configPath, "utf8"));
1827
1802
  if (rawConfig.visualizer?.graph) {
1828
1803
  graphConfig = {
1829
1804
  maxNodes: rawConfig.visualizer.graph.maxNodes ?? graphConfig.maxNodes,
@@ -1846,7 +1821,7 @@ Or specify a custom report:
1846
1821
  const localWebDir = (0, import_path6.resolve)(dirPath, "packages/visualizer");
1847
1822
  let webDir = "";
1848
1823
  let visualizerAvailable = false;
1849
- if ((0, import_fs7.existsSync)(localWebDir)) {
1824
+ if ((0, import_fs6.existsSync)(localWebDir)) {
1850
1825
  webDir = localWebDir;
1851
1826
  visualizerAvailable = true;
1852
1827
  } else {
@@ -1869,7 +1844,7 @@ Or specify a custom report:
1869
1844
  currentDir = parent;
1870
1845
  }
1871
1846
  for (const location of nodemodulesLocations) {
1872
- if ((0, import_fs7.existsSync)(location) && (0, import_fs7.existsSync)((0, import_path6.resolve)(location, "package.json"))) {
1847
+ if ((0, import_fs6.existsSync)(location) && (0, import_fs6.existsSync)((0, import_path6.resolve)(location, "package.json"))) {
1873
1848
  webDir = location;
1874
1849
  visualizerAvailable = true;
1875
1850
  break;
@@ -1885,14 +1860,14 @@ Or specify a custom report:
1885
1860
  }
1886
1861
  }
1887
1862
  }
1888
- const webViteConfigExists = webDir && (0, import_fs7.existsSync)((0, import_path6.resolve)(webDir, "web", "vite.config.ts"));
1863
+ const webViteConfigExists = webDir && (0, import_fs6.existsSync)((0, import_path6.resolve)(webDir, "web", "vite.config.ts"));
1889
1864
  if (visualizerAvailable && webViteConfigExists) {
1890
1865
  const spawnCwd = webDir;
1891
1866
  const { watch } = await import("fs");
1892
1867
  const copyReportToViz = () => {
1893
1868
  try {
1894
1869
  const destPath = (0, import_path6.resolve)(spawnCwd, "web", "report-data.json");
1895
- (0, import_fs7.copyFileSync)(reportPath, destPath);
1870
+ (0, import_fs6.copyFileSync)(reportPath, destPath);
1896
1871
  console.log(`\u{1F4CB} Report synced to ${destPath}`);
1897
1872
  } catch (e) {
1898
1873
  console.error("Failed to sync report:", e);
@@ -1956,7 +1931,7 @@ Or specify a custom report:
1956
1931
  const html = (0, import_core17.generateHTML)(graph);
1957
1932
  const defaultOutput = "visualization.html";
1958
1933
  const outPath = (0, import_path6.resolve)(dirPath, options.output ?? defaultOutput);
1959
- (0, import_fs7.writeFileSync)(outPath, html, "utf8");
1934
+ (0, import_fs6.writeFileSync)(outPath, html, "utf8");
1960
1935
  console.log(import_chalk12.default.green(`\u2705 Visualization written to: ${outPath}`));
1961
1936
  if (options.open || options.serve) {
1962
1937
  const opener = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
@@ -2239,7 +2214,7 @@ EXAMPLES:
2239
2214
  // src/commands/remediate.ts
2240
2215
  var import_chalk18 = __toESM(require("chalk"));
2241
2216
  var import_path7 = require("path");
2242
- var import_fs8 = require("fs");
2217
+ var import_fs7 = require("fs");
2243
2218
  var import_core18 = require("@aiready/core");
2244
2219
  async function remediateAction(directory, options) {
2245
2220
  const resolvedDir = (0, import_path7.resolve)(process.cwd(), directory || ".");
@@ -2249,15 +2224,15 @@ async function remediateAction(directory, options) {
2249
2224
  let reportPath = options.report;
2250
2225
  if (!reportPath) {
2251
2226
  const aireadyDir = (0, import_path7.resolve)(resolvedDir, ".aiready");
2252
- if ((0, import_fs8.existsSync)(aireadyDir)) {
2253
- const files = (0, import_fs8.readdirSync)(aireadyDir).filter((f) => f.startsWith("aiready-report-") && f.endsWith(".json")).sort().reverse();
2227
+ if ((0, import_fs7.existsSync)(aireadyDir)) {
2228
+ const files = (0, import_fs7.readdirSync)(aireadyDir).filter((f) => f.startsWith("aiready-report-") && f.endsWith(".json")).sort().reverse();
2254
2229
  if (files.length > 0) {
2255
2230
  reportPath = (0, import_path7.resolve)(aireadyDir, files[0]);
2256
2231
  console.log(import_chalk18.default.dim(`\u{1F4C2} Using latest report: ${files[0]}`));
2257
2232
  }
2258
2233
  }
2259
2234
  }
2260
- if (!reportPath || !(0, import_fs8.existsSync)(reportPath)) {
2235
+ if (!reportPath || !(0, import_fs7.existsSync)(reportPath)) {
2261
2236
  console.log(import_chalk18.default.yellow("\n\u26A0\uFE0F No AIReady report found."));
2262
2237
  console.log(
2263
2238
  import_chalk18.default.dim(
@@ -2334,7 +2309,7 @@ var getDirname = () => {
2334
2309
  return (0, import_path8.dirname)((0, import_url.fileURLToPath)(import_meta.url));
2335
2310
  };
2336
2311
  var packageJson = JSON.parse(
2337
- (0, import_fs9.readFileSync)((0, import_path8.join)(getDirname(), "../package.json"), "utf8")
2312
+ (0, import_fs8.readFileSync)((0, import_path8.join)(getDirname(), "../package.json"), "utf8")
2338
2313
  );
2339
2314
  var program = new import_commander.Command();
2340
2315
  program.name("aiready").description("AIReady - Assess and improve AI-readiness of codebases").version(packageJson.version).addHelpText(
@@ -2363,42 +2338,15 @@ CONFIGURATION:
2363
2338
  Config files (searched upward): aiready.json, .aiready.json, aiready.config.*
2364
2339
  CLI options override config file settings
2365
2340
 
2366
- Example aiready.json:
2367
- {
2368
- "scan": { "exclude": ["**/dist/**", "**/node_modules/**"] },
2369
- "tools": {
2370
- "pattern-detect": { "minSimilarity": 0.5 },
2371
- "context-analyzer": { "maxContextBudget": 15000 }
2372
- },
2373
- "output": { "format": "json", "directory": ".aiready" }
2374
- }
2375
-
2376
2341
  VERSION: ${packageJson.version}
2377
2342
  DOCUMENTATION: https://aiready.dev/docs/cli
2378
2343
  GITHUB: https://github.com/caopengau/aiready-cli
2379
2344
  LANDING: https://github.com/caopengau/aiready-landing`
2380
2345
  );
2381
- program.command("scan").description(
2382
- "Run comprehensive AI-readiness analysis (patterns + context + consistency)"
2383
- ).argument("[directory]", "Directory to analyze", ".").option(
2384
- "-t, --tools <tools>",
2385
- "Tools to run (comma-separated: patterns,context,consistency,doc-drift,deps-health,aiSignalClarity,grounding,testability,changeAmplification)"
2386
- ).option(
2387
- "--profile <type>",
2388
- "Scan profile to use (agentic, cost, logic, ui, security, onboarding)"
2389
- ).option(
2390
- "--compare-to <path>",
2391
- "Compare results against a previous AIReady report JSON"
2392
- ).option("--include <patterns>", "File patterns to include (comma-separated)").option("--exclude <patterns>", "File patterns to exclude (comma-separated)").option("-o, --output <format>", "Output format: console, json", "console").option("--output-file <path>", "Output file path (for json)").option("--score", "Calculate and display AI Readiness Score (0-100)").option("--no-score", "Disable calculating AI Readiness Score").option("--weights <weights>", "Custom scoring weights").option("--threshold <score>", "Fail CI/CD if score below threshold (0-100)").option(
2393
- "--ci",
2394
- "CI mode: GitHub Actions annotations, no colors, fail on threshold"
2395
- ).option(
2396
- "--fail-on <level>",
2397
- "Fail on issues: critical, major, any",
2398
- "critical"
2399
- ).option("--api-key <key>", "Platform API key for automatic upload").option("--upload", "Automatically upload results to the platform").option("--server <url>", "Custom platform URL").addHelpText("after", SCAN_HELP_TEXT).action(async (directory, options) => {
2400
- await scanAction(directory, options);
2401
- });
2346
+ defineScanCommand(program);
2347
+ definePatternsCommand(program);
2348
+ defineContextCommand(program);
2349
+ defineConsistencyCommand(program);
2402
2350
  program.command("init").description("Generate a default configuration (aiready.json)").option("-f, --force", "Overwrite existing configuration file").option(
2403
2351
  "--js",
2404
2352
  "Generate configuration as a JavaScript file (aiready.config.js)"
@@ -2406,36 +2354,6 @@ program.command("init").description("Generate a default configuration (aiready.j
2406
2354
  const format = options.js ? "js" : "json";
2407
2355
  await initAction({ force: options.force, format, full: options.full });
2408
2356
  });
2409
- program.command("patterns").description("Detect duplicate code patterns that confuse AI models").argument("[directory]", "Directory to analyze", ".").option("-s, --similarity <number>", "Minimum similarity score (0-1)", "0.40").option("-l, --min-lines <number>", "Minimum lines to consider", "5").option(
2410
- "--max-candidates <number>",
2411
- "Maximum candidates per block (performance tuning)"
2412
- ).option(
2413
- "--min-shared-tokens <number>",
2414
- "Minimum shared tokens for candidates (performance tuning)"
2415
- ).option(
2416
- "--full-scan",
2417
- "Disable smart defaults for comprehensive analysis (slower)"
2418
- ).option("--include <patterns>", "File patterns to include (comma-separated)").option("--exclude <patterns>", "File patterns to exclude (comma-separated)").option("-o, --output <format>", "Output format: console, json", "console").option("--output-file <path>", "Output file path (for json)").option("--score", "Calculate and display AI Readiness Score (0-100)").option("--no-score", "Disable calculating AI Readiness Score").addHelpText("after", PATTERNS_HELP_TEXT).action(async (directory, options) => {
2419
- await patternsAction(directory, options);
2420
- });
2421
- program.command("context").description("Analyze context window costs and dependency fragmentation").argument("[directory]", "Directory to analyze", ".").option("--max-depth <number>", "Maximum acceptable import depth", "5").option(
2422
- "--max-context <number>",
2423
- "Maximum acceptable context budget (tokens)",
2424
- "10000"
2425
- ).option("--include <patterns>", "File patterns to include (comma-separated)").option("--exclude <patterns>", "File patterns to exclude (comma-separated)").option("-o, --output <format>", "Output format: console, json", "console").option("--output-file <path>", "Output file path (for json)").option("--score", "Calculate and display AI Readiness Score (0-100)", true).option("--no-score", "Disable calculating AI Readiness Score").action(async (directory, options) => {
2426
- await contextAction(directory, options);
2427
- });
2428
- program.command("consistency").description("Check naming conventions and architectural consistency").argument("[directory]", "Directory to analyze", ".").option("--naming", "Check naming conventions (default: true)").option("--no-naming", "Skip naming analysis").option("--patterns", "Check code patterns (default: true)").option("--no-patterns", "Skip pattern analysis").option(
2429
- "--min-severity <level>",
2430
- "Minimum severity: info|minor|major|critical",
2431
- "info"
2432
- ).option("--include <patterns>", "File patterns to include (comma-separated)").option("--exclude <patterns>", "File patterns to exclude (comma-separated)").option(
2433
- "-o, --output <format>",
2434
- "Output format: console, json, markdown",
2435
- "console"
2436
- ).option("--output-file <path>", "Output file path (for json/markdown)").option("--score", "Calculate and display AI Readiness Score (0-100)", true).option("--no-score", "Disable calculating AI Readiness Score").action(async (directory, options) => {
2437
- await consistencyAction(directory, options);
2438
- });
2439
2357
  program.command("visualise").description("Alias for visualize (British spelling)").argument("[directory]", "Directory to analyze", ".").option(
2440
2358
  "--report <path>",
2441
2359
  "Report path (auto-detects latest .aiready/aiready-report-*.json if not provided)"