@aiready/cli 0.15.1 → 0.15.3

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 (3) hide show
  1. package/dist/cli.js +186 -274
  2. package/dist/cli.mjs +175 -267
  3. 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"));
@@ -428,7 +396,7 @@ async function executeToolAction(directory, options, config) {
428
396
  const elapsedTime = (0, import_core5.getElapsedTime)(startTime);
429
397
  const summary = generateSummary(results);
430
398
  let toolScore;
431
- if (options.score && calculateScore) {
399
+ if ((options.score || finalOptions.score) && calculateScore) {
432
400
  const resultsAny = results;
433
401
  const scoreData = resultsAny.duplicates || resultsAny.issues || results;
434
402
  const filesCount = resultsAny.length || resultsAny.summary?.filesAnalyzed || resultsAny.summary?.totalFiles;
@@ -1170,6 +1138,38 @@ ${import_chalk6.default.bold("CI/CD Integration:")}
1170
1138
  Use --threshold and --fail-on to use AIReady as a quality gate in your CI pipelines.
1171
1139
  When running in GitHub Actions, it will automatically emit annotations for found issues.
1172
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
+ }
1173
1173
 
1174
1174
  // src/commands/init.ts
1175
1175
  var import_fs5 = require("fs");
@@ -1449,33 +1449,6 @@ function renderSafetyRating(safety) {
1449
1449
  ` AI Change Safety: ${color(`${icon} ${safety.toUpperCase()}`)}`
1450
1450
  );
1451
1451
  }
1452
- function renderIssueSummaryBlock(summary) {
1453
- const total = (summary.criticalIssues || 0) + (summary.majorIssues || 0) + (summary.minorIssues || 0);
1454
- if (total === 0) {
1455
- console.log(import_chalk8.default.green("\u2705 No significant issues found!\n"));
1456
- return;
1457
- }
1458
- console.log(import_chalk8.default.bold("\u26A0\uFE0F Issues Found:\n"));
1459
- if (summary.criticalIssues > 0)
1460
- console.log(
1461
- import_chalk8.default.red(` \u{1F534} Critical: ${import_chalk8.default.bold(summary.criticalIssues)}`)
1462
- );
1463
- if (summary.majorIssues > 0)
1464
- console.log(
1465
- import_chalk8.default.yellow(` \u{1F7E1} Major: ${import_chalk8.default.bold(summary.majorIssues)}`)
1466
- );
1467
- if (summary.minorIssues > 0)
1468
- console.log(import_chalk8.default.blue(` \u{1F535} Minor: ${import_chalk8.default.bold(summary.minorIssues)}`));
1469
- if (summary.totalPotentialSavings) {
1470
- console.log(
1471
- import_chalk8.default.green(
1472
- `
1473
- \u{1F4A1} Potential savings: ${import_chalk8.default.bold(summary.totalPotentialSavings.toLocaleString())} tokens
1474
- `
1475
- )
1476
- );
1477
- }
1478
- }
1479
1452
  function renderSubSection(title) {
1480
1453
  console.log("\n" + (0, import_core12.getTerminalDivider)());
1481
1454
  console.log(import_chalk8.default.bold.white(` ${title.toUpperCase()}`));
@@ -1492,6 +1465,36 @@ function renderToolScoreFooter(score) {
1492
1465
  }
1493
1466
 
1494
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
+ }
1495
1498
  async function patternsAction(directory, options) {
1496
1499
  return await executeToolAction(directory, options, {
1497
1500
  toolName: "pattern-detect",
@@ -1571,21 +1574,30 @@ async function patternsAction(directory, options) {
1571
1574
  }
1572
1575
  });
1573
1576
  }
1574
- var PATTERNS_HELP_TEXT = `
1575
- EXAMPLES:
1576
- $ aiready patterns # Default analysis
1577
- $ aiready patterns --similarity 0.6 # Stricter matching
1578
- $ aiready patterns --min-lines 10 # Larger patterns only
1579
- `;
1580
1577
 
1581
1578
  // src/commands/context.ts
1582
1579
  var import_chalk10 = __toESM(require("chalk"));
1583
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
+ }
1584
1596
  async function contextAction(directory, options) {
1585
1597
  return await executeToolAction(directory, options, {
1586
1598
  toolName: "context-analyzer",
1587
1599
  label: "Context analysis",
1588
- emoji: "\u{1F9E0}",
1600
+ emoji: "\u{1F9E9}",
1589
1601
  defaults: {
1590
1602
  maxDepth: 5,
1591
1603
  maxContextBudget: 1e4,
@@ -1597,11 +1609,6 @@ async function contextAction(directory, options) {
1597
1609
  maxDepth: opts.maxDepth ? parseInt(opts.maxDepth) : void 0,
1598
1610
  maxContextBudget: opts.maxContext ? parseInt(opts.maxContext) : void 0
1599
1611
  }),
1600
- preAnalyze: async (resolvedDir, baseOptions) => {
1601
- const { getSmartDefaults } = await import("@aiready/context-analyzer");
1602
- const smartDefaults = await getSmartDefaults(resolvedDir, baseOptions);
1603
- return { ...smartDefaults, ...baseOptions };
1604
- },
1605
1612
  importTool: async () => {
1606
1613
  const { analyzeContext, generateSummary, calculateContextScore } = await import("@aiready/context-analyzer");
1607
1614
  return {
@@ -1610,45 +1617,40 @@ async function contextAction(directory, options) {
1610
1617
  calculateScore: calculateContextScore
1611
1618
  };
1612
1619
  },
1613
- renderConsole: ({ summary, elapsedTime, score }) => {
1620
+ renderConsole: ({ results: _results, summary, elapsedTime, score }) => {
1614
1621
  (0, import_core14.printTerminalHeader)("CONTEXT ANALYSIS SUMMARY");
1615
1622
  console.log(
1616
- 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)}`)
1617
1624
  );
1618
1625
  console.log(
1619
1626
  import_chalk10.default.white(
1620
- `\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())}`
1621
1628
  )
1622
1629
  );
1623
1630
  console.log(
1624
- import_chalk10.default.yellow(
1625
- `\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`
1626
1633
  )
1627
1634
  );
1628
1635
  console.log(
1629
- import_chalk10.default.white(`\u23F1 Analysis time: ${import_chalk10.default.bold(elapsedTime + "s")}
1630
- `)
1636
+ import_chalk10.default.gray(`\u23F1 Analysis time: ${import_chalk10.default.bold(elapsedTime + "s")}`)
1631
1637
  );
1632
- renderIssueSummaryBlock(summary);
1633
- if (summary.deepFiles && summary.deepFiles.length > 0) {
1634
- renderSubSection("Deep Import Chains");
1635
- summary.deepFiles.slice(0, 10).forEach((item) => {
1636
- 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;
1637
1642
  console.log(
1638
- ` ${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`
1639
1644
  );
1640
1645
  });
1641
1646
  }
1642
- if (summary.fragmentedModules && summary.fragmentedModules.length > 0) {
1643
- renderSubSection("Fragmented Modules");
1644
- summary.fragmentedModules.slice(0, 10).forEach((module2) => {
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;
1645
1652
  console.log(
1646
- ` ${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`)}`
1647
- );
1648
- console.log(
1649
- import_chalk10.default.dim(
1650
- ` Token cost: ${module2.totalTokens.toLocaleString()}, Cohesion: ${(module2.avgCohesion * 100).toFixed(0)}%`
1651
- )
1653
+ ` ${icon} ${color(item.severity.toUpperCase())}: ${import_chalk10.default.white(item.file.split("/").pop())} ${import_chalk10.default.dim(`(${item.contextBudget.toLocaleString()} tokens)`)}`
1652
1654
  );
1653
1655
  });
1654
1656
  }
@@ -1659,17 +1661,35 @@ async function contextAction(directory, options) {
1659
1661
 
1660
1662
  // src/commands/consistency.ts
1661
1663
  var import_chalk11 = __toESM(require("chalk"));
1662
- var import_fs6 = require("fs");
1663
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
+ }
1664
1684
  async function consistencyAction(directory, options) {
1665
1685
  return await executeToolAction(directory, options, {
1666
1686
  toolName: "naming-consistency",
1667
1687
  label: "Consistency analysis",
1668
- emoji: "\u{1F50D}",
1688
+ emoji: "\u{1F4CF}",
1669
1689
  defaults: {
1670
- checkNaming: true,
1671
- checkPatterns: true,
1672
- minSeverity: "info",
1690
+ checkNaming: options.naming !== false,
1691
+ checkPatterns: options.patterns !== false,
1692
+ minSeverity: options.minSeverity || "info",
1673
1693
  include: void 0,
1674
1694
  exclude: void 0,
1675
1695
  output: { format: "console", file: void 0 }
@@ -1680,119 +1700,68 @@ async function consistencyAction(directory, options) {
1680
1700
  minSeverity: opts.minSeverity
1681
1701
  }),
1682
1702
  importTool: async () => {
1683
- const { analyzeConsistency, calculateConsistencyScore } = await import("@aiready/consistency");
1703
+ const { analyzeConsistency, generateSummary, calculateConsistencyScore } = await import("@aiready/consistency");
1684
1704
  return {
1685
- analyze: analyzeConsistency,
1686
- generateSummary: (report) => report.summary,
1687
- calculateScore: (summary, _resultsCount) => {
1688
- return calculateConsistencyScore(
1689
- summary.results?.flatMap((r) => r.issues) ?? [],
1690
- summary.summary.filesAnalyzed
1691
- );
1692
- }
1705
+ analyze: async (opts) => {
1706
+ const report = await analyzeConsistency(opts);
1707
+ return report;
1708
+ },
1709
+ generateSummary,
1710
+ calculateScore: calculateConsistencyScore
1693
1711
  };
1694
1712
  },
1695
- renderConsole: ({ results, summary, elapsedTime, score, finalOptions }) => {
1696
- const report = results;
1697
- const { format: outputFormat, file: userOutputFile } = (0, import_core15.resolveOutputFormat)(options, finalOptions);
1698
- if (outputFormat === "markdown") {
1699
- const markdown = generateMarkdownReport(report, elapsedTime);
1700
- const outputPath = (0, import_core15.resolveOutputPath)(
1701
- userOutputFile,
1702
- `aiready-report-${(0, import_core2.getReportTimestamp)()}.md`,
1703
- directory
1704
- );
1705
- (0, import_fs6.writeFileSync)(outputPath, markdown);
1706
- console.log(import_chalk11.default.green(`\u2705 Report saved to ${outputPath}`));
1707
- return;
1708
- }
1709
- console.log(import_chalk11.default.bold("\n\u{1F4CA} Summary\n"));
1710
- console.log(`Files Analyzed: ${import_chalk11.default.cyan(summary.filesAnalyzed)}`);
1711
- console.log(`Total Issues: ${import_chalk11.default.yellow(summary.totalIssues)}`);
1712
- console.log(` Naming: ${import_chalk11.default.yellow(summary.namingIssues)}`);
1713
- console.log(` Patterns: ${import_chalk11.default.yellow(summary.patternIssues)}`);
1713
+ renderConsole: ({ results: report, summary, elapsedTime, score }) => {
1714
+ (0, import_core15.printTerminalHeader)("CONSISTENCY ANALYSIS SUMMARY");
1714
1715
  console.log(
1715
- ` Architecture: ${import_chalk11.default.yellow(summary.architectureIssues ?? 0)}`
1716
+ import_chalk11.default.white(`\u{1F4C1} Files analyzed: ${import_chalk11.default.bold(summary.filesAnalyzed)}`)
1716
1717
  );
1717
- console.log(`Analysis Time: ${import_chalk11.default.gray(elapsedTime + "s")}
1718
- `);
1719
- if (summary.totalIssues === 0) {
1720
- console.log(
1721
- import_chalk11.default.green(
1722
- "\u2728 No consistency issues found! Your codebase is well-maintained.\n"
1723
- )
1724
- );
1725
- } else {
1726
- const namingResults = report.results.filter(
1727
- (r) => r.issues.some((i) => i.category === "naming")
1728
- );
1729
- const patternResults = report.results.filter(
1730
- (r) => r.issues.some((i) => i.category === "patterns")
1731
- );
1732
- if (namingResults.length > 0) {
1733
- console.log(import_chalk11.default.bold("\u{1F3F7}\uFE0F Naming Issues\n"));
1734
- let shown = 0;
1735
- for (const namingFileResult of namingResults) {
1736
- if (shown >= 5) break;
1737
- for (const issue of namingFileResult.issues) {
1738
- if (shown >= 5) break;
1739
- 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;
1740
- console.log(
1741
- `${severityColor(issue.severity.toUpperCase())} ${import_chalk11.default.dim(`${issue.location.file}:${issue.location.line}`)}`
1742
- );
1743
- console.log(` ${issue.message}`);
1744
- if (issue.suggestion) {
1745
- console.log(
1746
- ` ${import_chalk11.default.dim("\u2192")} ${import_chalk11.default.italic(issue.suggestion)}`
1747
- );
1748
- }
1749
- console.log();
1750
- shown++;
1751
- }
1752
- }
1753
- }
1754
- if (patternResults.length > 0) {
1755
- console.log(import_chalk11.default.bold("\u{1F504} Pattern Issues\n"));
1756
- let shown = 0;
1757
- for (const patternFileResult of patternResults) {
1758
- if (shown >= 5) break;
1759
- for (const issue of patternFileResult.issues) {
1760
- if (shown >= 5) break;
1761
- 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;
1762
- console.log(
1763
- `${severityColor(issue.severity.toUpperCase())} ${import_chalk11.default.dim(`${issue.location.file}:${issue.location.line}`)}`
1764
- );
1765
- console.log(` ${issue.message}`);
1766
- if (issue.suggestion) {
1767
- console.log(
1768
- ` ${import_chalk11.default.dim("\u2192")} ${import_chalk11.default.italic(issue.suggestion)}`
1769
- );
1770
- }
1771
- console.log();
1772
- shown++;
1773
- }
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}`));
1774
1749
  }
1775
- }
1776
- if (report.recommendations?.length > 0) {
1777
- console.log(import_chalk11.default.bold("\u{1F4A1} Recommendations\n"));
1778
- report.recommendations.forEach((rec, i) => {
1779
- console.log(`${i + 1}. ${rec}`);
1780
- });
1781
1750
  console.log();
1782
- }
1783
- }
1784
- if (score) {
1785
- console.log(import_chalk11.default.bold("\n\u{1F4CA} AI Readiness Score (Consistency)\n"));
1786
- console.log((0, import_core15.formatToolScore)(score));
1787
- console.log();
1751
+ });
1752
+ } else {
1753
+ console.log(
1754
+ import_chalk11.default.green("\n\u2728 Great! No consistency issues detected.\n")
1755
+ );
1788
1756
  }
1757
+ renderToolScoreFooter(score);
1789
1758
  }
1790
1759
  });
1791
1760
  }
1792
1761
 
1793
1762
  // src/commands/visualize.ts
1794
1763
  var import_chalk12 = __toESM(require("chalk"));
1795
- var import_fs7 = require("fs");
1764
+ var import_fs6 = require("fs");
1796
1765
  var import_path6 = require("path");
1797
1766
  var import_child_process = require("child_process");
1798
1767
  var import_core16 = require("@aiready/core");
@@ -1801,7 +1770,7 @@ async function visualizeAction(directory, options) {
1801
1770
  try {
1802
1771
  const dirPath = (0, import_path6.resolve)(process.cwd(), directory ?? ".");
1803
1772
  let reportPath = options.report ? (0, import_path6.resolve)(dirPath, options.report) : null;
1804
- if (!reportPath || !(0, import_fs7.existsSync)(reportPath)) {
1773
+ if (!reportPath || !(0, import_fs6.existsSync)(reportPath)) {
1805
1774
  const latestScan = (0, import_core17.findLatestReport)(dirPath);
1806
1775
  if (latestScan) {
1807
1776
  reportPath = latestScan;
@@ -1823,13 +1792,13 @@ Or specify a custom report:
1823
1792
  return;
1824
1793
  }
1825
1794
  }
1826
- const raw = (0, import_fs7.readFileSync)(reportPath, "utf8");
1795
+ const raw = (0, import_fs6.readFileSync)(reportPath, "utf8");
1827
1796
  const report = JSON.parse(raw);
1828
1797
  const configPath = (0, import_path6.resolve)(dirPath, "aiready.json");
1829
1798
  let graphConfig = { maxNodes: 400, maxEdges: 600 };
1830
- if ((0, import_fs7.existsSync)(configPath)) {
1799
+ if ((0, import_fs6.existsSync)(configPath)) {
1831
1800
  try {
1832
- const rawConfig = JSON.parse((0, import_fs7.readFileSync)(configPath, "utf8"));
1801
+ const rawConfig = JSON.parse((0, import_fs6.readFileSync)(configPath, "utf8"));
1833
1802
  if (rawConfig.visualizer?.graph) {
1834
1803
  graphConfig = {
1835
1804
  maxNodes: rawConfig.visualizer.graph.maxNodes ?? graphConfig.maxNodes,
@@ -1852,7 +1821,7 @@ Or specify a custom report:
1852
1821
  const localWebDir = (0, import_path6.resolve)(dirPath, "packages/visualizer");
1853
1822
  let webDir = "";
1854
1823
  let visualizerAvailable = false;
1855
- if ((0, import_fs7.existsSync)(localWebDir)) {
1824
+ if ((0, import_fs6.existsSync)(localWebDir)) {
1856
1825
  webDir = localWebDir;
1857
1826
  visualizerAvailable = true;
1858
1827
  } else {
@@ -1875,7 +1844,7 @@ Or specify a custom report:
1875
1844
  currentDir = parent;
1876
1845
  }
1877
1846
  for (const location of nodemodulesLocations) {
1878
- 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"))) {
1879
1848
  webDir = location;
1880
1849
  visualizerAvailable = true;
1881
1850
  break;
@@ -1891,14 +1860,14 @@ Or specify a custom report:
1891
1860
  }
1892
1861
  }
1893
1862
  }
1894
- 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"));
1895
1864
  if (visualizerAvailable && webViteConfigExists) {
1896
1865
  const spawnCwd = webDir;
1897
1866
  const { watch } = await import("fs");
1898
1867
  const copyReportToViz = () => {
1899
1868
  try {
1900
1869
  const destPath = (0, import_path6.resolve)(spawnCwd, "web", "report-data.json");
1901
- (0, import_fs7.copyFileSync)(reportPath, destPath);
1870
+ (0, import_fs6.copyFileSync)(reportPath, destPath);
1902
1871
  console.log(`\u{1F4CB} Report synced to ${destPath}`);
1903
1872
  } catch (e) {
1904
1873
  console.error("Failed to sync report:", e);
@@ -1962,7 +1931,7 @@ Or specify a custom report:
1962
1931
  const html = (0, import_core17.generateHTML)(graph);
1963
1932
  const defaultOutput = "visualization.html";
1964
1933
  const outPath = (0, import_path6.resolve)(dirPath, options.output ?? defaultOutput);
1965
- (0, import_fs7.writeFileSync)(outPath, html, "utf8");
1934
+ (0, import_fs6.writeFileSync)(outPath, html, "utf8");
1966
1935
  console.log(import_chalk12.default.green(`\u2705 Visualization written to: ${outPath}`));
1967
1936
  if (options.open || options.serve) {
1968
1937
  const opener = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
@@ -2245,7 +2214,7 @@ EXAMPLES:
2245
2214
  // src/commands/remediate.ts
2246
2215
  var import_chalk18 = __toESM(require("chalk"));
2247
2216
  var import_path7 = require("path");
2248
- var import_fs8 = require("fs");
2217
+ var import_fs7 = require("fs");
2249
2218
  var import_core18 = require("@aiready/core");
2250
2219
  async function remediateAction(directory, options) {
2251
2220
  const resolvedDir = (0, import_path7.resolve)(process.cwd(), directory || ".");
@@ -2255,15 +2224,15 @@ async function remediateAction(directory, options) {
2255
2224
  let reportPath = options.report;
2256
2225
  if (!reportPath) {
2257
2226
  const aireadyDir = (0, import_path7.resolve)(resolvedDir, ".aiready");
2258
- if ((0, import_fs8.existsSync)(aireadyDir)) {
2259
- 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();
2260
2229
  if (files.length > 0) {
2261
2230
  reportPath = (0, import_path7.resolve)(aireadyDir, files[0]);
2262
2231
  console.log(import_chalk18.default.dim(`\u{1F4C2} Using latest report: ${files[0]}`));
2263
2232
  }
2264
2233
  }
2265
2234
  }
2266
- if (!reportPath || !(0, import_fs8.existsSync)(reportPath)) {
2235
+ if (!reportPath || !(0, import_fs7.existsSync)(reportPath)) {
2267
2236
  console.log(import_chalk18.default.yellow("\n\u26A0\uFE0F No AIReady report found."));
2268
2237
  console.log(
2269
2238
  import_chalk18.default.dim(
@@ -2340,7 +2309,7 @@ var getDirname = () => {
2340
2309
  return (0, import_path8.dirname)((0, import_url.fileURLToPath)(import_meta.url));
2341
2310
  };
2342
2311
  var packageJson = JSON.parse(
2343
- (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")
2344
2313
  );
2345
2314
  var program = new import_commander.Command();
2346
2315
  program.name("aiready").description("AIReady - Assess and improve AI-readiness of codebases").version(packageJson.version).addHelpText(
@@ -2369,42 +2338,15 @@ CONFIGURATION:
2369
2338
  Config files (searched upward): aiready.json, .aiready.json, aiready.config.*
2370
2339
  CLI options override config file settings
2371
2340
 
2372
- Example aiready.json:
2373
- {
2374
- "scan": { "exclude": ["**/dist/**", "**/node_modules/**"] },
2375
- "tools": {
2376
- "pattern-detect": { "minSimilarity": 0.5 },
2377
- "context-analyzer": { "maxContextBudget": 15000 }
2378
- },
2379
- "output": { "format": "json", "directory": ".aiready" }
2380
- }
2381
-
2382
2341
  VERSION: ${packageJson.version}
2383
2342
  DOCUMENTATION: https://aiready.dev/docs/cli
2384
2343
  GITHUB: https://github.com/caopengau/aiready-cli
2385
2344
  LANDING: https://github.com/caopengau/aiready-landing`
2386
2345
  );
2387
- program.command("scan").description(
2388
- "Run comprehensive AI-readiness analysis (patterns + context + consistency)"
2389
- ).argument("[directory]", "Directory to analyze", ".").option(
2390
- "-t, --tools <tools>",
2391
- "Tools to run (comma-separated: patterns,context,consistency,doc-drift,deps-health,aiSignalClarity,grounding,testability,changeAmplification)"
2392
- ).option(
2393
- "--profile <type>",
2394
- "Scan profile to use (agentic, cost, logic, ui, security, onboarding)"
2395
- ).option(
2396
- "--compare-to <path>",
2397
- "Compare results against a previous AIReady report JSON"
2398
- ).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(
2399
- "--ci",
2400
- "CI mode: GitHub Actions annotations, no colors, fail on threshold"
2401
- ).option(
2402
- "--fail-on <level>",
2403
- "Fail on issues: critical, major, any",
2404
- "critical"
2405
- ).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) => {
2406
- await scanAction(directory, options);
2407
- });
2346
+ defineScanCommand(program);
2347
+ definePatternsCommand(program);
2348
+ defineContextCommand(program);
2349
+ defineConsistencyCommand(program);
2408
2350
  program.command("init").description("Generate a default configuration (aiready.json)").option("-f, --force", "Overwrite existing configuration file").option(
2409
2351
  "--js",
2410
2352
  "Generate configuration as a JavaScript file (aiready.config.js)"
@@ -2412,36 +2354,6 @@ program.command("init").description("Generate a default configuration (aiready.j
2412
2354
  const format = options.js ? "js" : "json";
2413
2355
  await initAction({ force: options.force, format, full: options.full });
2414
2356
  });
2415
- 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(
2416
- "--max-candidates <number>",
2417
- "Maximum candidates per block (performance tuning)"
2418
- ).option(
2419
- "--min-shared-tokens <number>",
2420
- "Minimum shared tokens for candidates (performance tuning)"
2421
- ).option(
2422
- "--full-scan",
2423
- "Disable smart defaults for comprehensive analysis (slower)"
2424
- ).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) => {
2425
- await patternsAction(directory, options);
2426
- });
2427
- 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(
2428
- "--max-context <number>",
2429
- "Maximum acceptable context budget (tokens)",
2430
- "10000"
2431
- ).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) => {
2432
- await contextAction(directory, options);
2433
- });
2434
- 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(
2435
- "--min-severity <level>",
2436
- "Minimum severity: info|minor|major|critical",
2437
- "info"
2438
- ).option("--include <patterns>", "File patterns to include (comma-separated)").option("--exclude <patterns>", "File patterns to exclude (comma-separated)").option(
2439
- "-o, --output <format>",
2440
- "Output format: console, json, markdown",
2441
- "console"
2442
- ).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) => {
2443
- await consistencyAction(directory, options);
2444
- });
2445
2357
  program.command("visualise").description("Alias for visualize (British spelling)").argument("[directory]", "Directory to analyze", ".").option(
2446
2358
  "--report <path>",
2447
2359
  "Report path (auto-detects latest .aiready/aiready-report-*.json if not provided)"