@aiready/cli 0.9.35 → 0.9.38

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.js CHANGED
@@ -25,11 +25,13 @@ 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_fs4 = require("fs");
28
+ var import_fs5 = require("fs");
29
29
  var import_path7 = require("path");
30
+ var import_url = require("url");
30
31
 
31
32
  // src/commands/scan.ts
32
33
  var import_chalk2 = __toESM(require("chalk"));
34
+ var import_fs2 = require("fs");
33
35
  var import_path2 = require("path");
34
36
  var import_core = require("@aiready/core");
35
37
 
@@ -115,6 +117,71 @@ async function analyzeUnified(options) {
115
117
  result.consistency = report;
116
118
  result.summary.totalIssues += report.summary.totalIssues;
117
119
  }
120
+ if (tools.includes("doc-drift")) {
121
+ const { analyzeDocDrift } = await import("@aiready/doc-drift");
122
+ const report = await analyzeDocDrift({
123
+ rootDir: options.rootDir,
124
+ include: options.include,
125
+ exclude: options.exclude
126
+ });
127
+ if (options.progressCallback) {
128
+ options.progressCallback({ tool: "doc-drift", data: report });
129
+ }
130
+ result.docDrift = report;
131
+ result.summary.totalIssues += report.issues?.length || 0;
132
+ }
133
+ if (tools.includes("deps-health")) {
134
+ const { analyzeDeps } = await import("@aiready/deps");
135
+ const report = await analyzeDeps({
136
+ rootDir: options.rootDir,
137
+ include: options.include,
138
+ exclude: options.exclude
139
+ });
140
+ if (options.progressCallback) {
141
+ options.progressCallback({ tool: "deps-health", data: report });
142
+ }
143
+ result.deps = report;
144
+ result.summary.totalIssues += report.issues?.length || 0;
145
+ }
146
+ if (tools.includes("hallucination")) {
147
+ const { analyzeHallucinationRisk } = await import("@aiready/hallucination-risk");
148
+ const report = await analyzeHallucinationRisk({
149
+ rootDir: options.rootDir,
150
+ include: options.include,
151
+ exclude: options.exclude
152
+ });
153
+ if (options.progressCallback) {
154
+ options.progressCallback({ tool: "hallucination", data: report });
155
+ }
156
+ result.hallucination = report;
157
+ result.summary.totalIssues += report.results?.reduce((sum, r) => sum + (r.issues?.length || 0), 0) || 0;
158
+ }
159
+ if (tools.includes("grounding")) {
160
+ const { analyzeAgentGrounding } = await import("@aiready/agent-grounding");
161
+ const report = await analyzeAgentGrounding({
162
+ rootDir: options.rootDir,
163
+ include: options.include,
164
+ exclude: options.exclude
165
+ });
166
+ if (options.progressCallback) {
167
+ options.progressCallback({ tool: "grounding", data: report });
168
+ }
169
+ result.grounding = report;
170
+ result.summary.totalIssues += report.issues?.length || 0;
171
+ }
172
+ if (tools.includes("testability")) {
173
+ const { analyzeTestability } = await import("@aiready/testability");
174
+ const report = await analyzeTestability({
175
+ rootDir: options.rootDir,
176
+ include: options.include,
177
+ exclude: options.exclude
178
+ });
179
+ if (options.progressCallback) {
180
+ options.progressCallback({ tool: "testability", data: report });
181
+ }
182
+ result.testability = report;
183
+ result.summary.totalIssues += report.issues?.length || 0;
184
+ }
118
185
  result.summary.executionTime = Date.now() - startTime;
119
186
  return result;
120
187
  }
@@ -145,7 +212,7 @@ function findLatestScanReport(dirPath) {
145
212
  }
146
213
  function warnIfGraphCapExceeded(report, dirPath) {
147
214
  try {
148
- const { loadConfig } = require("@aiready/core");
215
+ const { loadConfig: loadConfig4 } = require("@aiready/core");
149
216
  let graphConfig = { maxNodes: 400, maxEdges: 600 };
150
217
  const configPath = (0, import_path.resolve)(dirPath, "aiready.json");
151
218
  if ((0, import_fs.existsSync)(configPath)) {
@@ -231,7 +298,7 @@ async function scanAction(directory, options) {
231
298
  const resolvedDir = (0, import_path2.resolve)(process.cwd(), directory || ".");
232
299
  try {
233
300
  const defaults = {
234
- tools: ["patterns", "context", "consistency"],
301
+ tools: ["patterns", "context", "consistency", "hallucination", "grounding", "testability", "doc-drift", "deps-health"],
235
302
  include: void 0,
236
303
  exclude: void 0,
237
304
  output: {
@@ -239,8 +306,28 @@ async function scanAction(directory, options) {
239
306
  file: void 0
240
307
  }
241
308
  };
309
+ let profileTools = options.tools ? options.tools.split(",").map((t) => t.trim()) : void 0;
310
+ if (options.profile) {
311
+ switch (options.profile.toLowerCase()) {
312
+ case "agentic":
313
+ profileTools = ["hallucination", "grounding", "testability"];
314
+ break;
315
+ case "cost":
316
+ profileTools = ["patterns", "context"];
317
+ break;
318
+ case "security":
319
+ profileTools = ["consistency", "testability"];
320
+ break;
321
+ case "onboarding":
322
+ profileTools = ["context", "consistency", "grounding"];
323
+ break;
324
+ default:
325
+ console.log(import_chalk2.default.yellow(`
326
+ \u26A0\uFE0F Unknown profile '${options.profile}'. Using specified tools or defaults.`));
327
+ }
328
+ }
242
329
  const baseOptions = await (0, import_core.loadMergedConfig)(resolvedDir, defaults, {
243
- tools: options.tools ? options.tools.split(",").map((t) => t.trim()) : void 0,
330
+ tools: profileTools,
244
331
  include: options.include?.split(","),
245
332
  exclude: options.exclude?.split(",")
246
333
  });
@@ -372,6 +459,20 @@ async function scanAction(directory, options) {
372
459
  console.log(import_chalk2.default.dim(` ... and ${remaining} more files with issues (use --output json for full details)`));
373
460
  }
374
461
  }
462
+ } else if (event.tool === "doc-drift") {
463
+ const dr = event.data;
464
+ console.log(` Issues found: ${import_chalk2.default.bold(String(dr.issues?.length || 0))}`);
465
+ if (dr.rawData) {
466
+ console.log(` Signature Mismatches: ${import_chalk2.default.bold(dr.rawData.outdatedComments || 0)}`);
467
+ console.log(` Undocumented Complexity: ${import_chalk2.default.bold(dr.rawData.undocumentedComplexity || 0)}`);
468
+ }
469
+ } else if (event.tool === "deps-health") {
470
+ const dr = event.data;
471
+ console.log(` Packages Analyzed: ${import_chalk2.default.bold(String(dr.summary?.packagesAnalyzed || 0))}`);
472
+ if (dr.rawData) {
473
+ console.log(` Deprecated Packages: ${import_chalk2.default.bold(dr.rawData.deprecatedPackages || 0)}`);
474
+ console.log(` AI Cutoff Skew Score: ${import_chalk2.default.bold(dr.rawData.trainingCutoffSkew?.toFixed(1) || 0)}`);
475
+ }
375
476
  }
376
477
  } catch (err) {
377
478
  }
@@ -417,11 +518,82 @@ async function scanAction(directory, options) {
417
518
  } catch (err) {
418
519
  }
419
520
  }
521
+ if (results.hallucination) {
522
+ const { calculateHallucinationScore } = await import("@aiready/hallucination-risk");
523
+ try {
524
+ const hrScore = calculateHallucinationScore(results.hallucination);
525
+ toolScores.set("hallucination-risk", hrScore);
526
+ } catch (err) {
527
+ }
528
+ }
529
+ if (results.grounding) {
530
+ const { calculateGroundingScore } = await import("@aiready/agent-grounding");
531
+ try {
532
+ const agScore = calculateGroundingScore(results.grounding);
533
+ toolScores.set("agent-grounding", agScore);
534
+ } catch (err) {
535
+ }
536
+ }
537
+ if (results.testability) {
538
+ const { calculateTestabilityScore } = await import("@aiready/testability");
539
+ try {
540
+ const tbScore = calculateTestabilityScore(results.testability);
541
+ toolScores.set("testability", tbScore);
542
+ } catch (err) {
543
+ }
544
+ }
545
+ if (results.docDrift) {
546
+ toolScores.set("doc-drift", {
547
+ toolName: "doc-drift",
548
+ score: results.docDrift.summary.score,
549
+ rawMetrics: results.docDrift.rawData,
550
+ factors: [],
551
+ recommendations: results.docDrift.recommendations.map((action) => ({ action, estimatedImpact: 5, priority: "medium" }))
552
+ });
553
+ }
554
+ if (results.deps) {
555
+ toolScores.set("dependency-health", {
556
+ toolName: "dependency-health",
557
+ score: results.deps.summary.score,
558
+ rawMetrics: results.deps.rawData,
559
+ factors: [],
560
+ recommendations: results.deps.recommendations.map((action) => ({ action, estimatedImpact: 5, priority: "medium" }))
561
+ });
562
+ }
420
563
  const cliWeights = (0, import_core.parseWeightString)(options.weights);
421
564
  if (toolScores.size > 0) {
422
565
  scoringResult = (0, import_core.calculateOverallScore)(toolScores, finalOptions, cliWeights.size ? cliWeights : void 0);
423
566
  console.log(import_chalk2.default.bold("\n\u{1F4CA} AI Readiness Overall Score"));
424
567
  console.log(` ${(0, import_core.formatScore)(scoringResult)}`);
568
+ if (options.compareTo) {
569
+ try {
570
+ const prevReportStr = (0, import_fs2.readFileSync)((0, import_path2.resolve)(process.cwd(), options.compareTo), "utf8");
571
+ const prevReport = JSON.parse(prevReportStr);
572
+ const prevScore = prevReport.scoring?.score || prevReport.scoring?.overallScore;
573
+ if (typeof prevScore === "number") {
574
+ const diff = scoringResult.overall - prevScore;
575
+ const diffStr = diff > 0 ? `+${diff}` : String(diff);
576
+ console.log();
577
+ if (diff > 0) {
578
+ console.log(import_chalk2.default.green(` \u{1F4C8} Trend: ${diffStr} compared to ${options.compareTo} (${prevScore} \u2192 ${scoringResult.overall})`));
579
+ } else if (diff < 0) {
580
+ console.log(import_chalk2.default.red(` \u{1F4C9} Trend: ${diffStr} compared to ${options.compareTo} (${prevScore} \u2192 ${scoringResult.overall})`));
581
+ } else {
582
+ console.log(import_chalk2.default.blue(` \u2796 Trend: No change compared to ${options.compareTo} (${prevScore} \u2192 ${scoringResult.overall})`));
583
+ }
584
+ scoringResult.trend = {
585
+ previousScore: prevScore,
586
+ difference: diff
587
+ };
588
+ } else {
589
+ console.log(import_chalk2.default.yellow(`
590
+ \u26A0\uFE0F Previous report at ${options.compareTo} does not contain an overall score.`));
591
+ }
592
+ } catch (e) {
593
+ console.log(import_chalk2.default.yellow(`
594
+ \u26A0\uFE0F Could not read or parse previous report at ${options.compareTo}.`));
595
+ }
596
+ }
425
597
  if (scoringResult.breakdown && scoringResult.breakdown.length > 0) {
426
598
  console.log(import_chalk2.default.bold("\nTool breakdown:"));
427
599
  scoringResult.breakdown.forEach((tool) => {
@@ -457,17 +629,17 @@ async function scanAction(directory, options) {
457
629
  if (process.env.GITHUB_ACTIONS === "true") {
458
630
  console.log(`
459
631
  ::group::AI Readiness Score`);
460
- console.log(`score=${scoringResult.overallScore}`);
632
+ console.log(`score=${scoringResult.overall}`);
461
633
  if (scoringResult.breakdown) {
462
634
  scoringResult.breakdown.forEach((tool) => {
463
635
  console.log(`${tool.toolName}=${tool.score}`);
464
636
  });
465
637
  }
466
638
  console.log("::endgroup::");
467
- if (threshold && scoringResult.overallScore < threshold) {
468
- console.log(`::error::AI Readiness Score ${scoringResult.overallScore} is below threshold ${threshold}`);
639
+ if (threshold && scoringResult.overall < threshold) {
640
+ console.log(`::error::AI Readiness Score ${scoringResult.overall} is below threshold ${threshold}`);
469
641
  } else if (threshold) {
470
- console.log(`::notice::AI Readiness Score: ${scoringResult.overallScore}/100 (threshold: ${threshold})`);
642
+ console.log(`::notice::AI Readiness Score: ${scoringResult.overall}/100 (threshold: ${threshold})`);
471
643
  }
472
644
  if (results.patterns) {
473
645
  const criticalPatterns = results.patterns.flatMap(
@@ -480,9 +652,9 @@ async function scanAction(directory, options) {
480
652
  }
481
653
  let shouldFail = false;
482
654
  let failReason = "";
483
- if (threshold && scoringResult.overallScore < threshold) {
655
+ if (threshold && scoringResult.overall < threshold) {
484
656
  shouldFail = true;
485
- failReason = `AI Readiness Score ${scoringResult.overallScore} is below threshold ${threshold}`;
657
+ failReason = `AI Readiness Score ${scoringResult.overall} is below threshold ${threshold}`;
486
658
  }
487
659
  if (failOnLevel !== "none") {
488
660
  const severityLevels = { critical: 4, major: 3, minor: 2, any: 1 };
@@ -530,7 +702,7 @@ async function scanAction(directory, options) {
530
702
  } else {
531
703
  console.log(import_chalk2.default.green("\n\u2705 PR PASSED: AI Readiness Check"));
532
704
  if (threshold) {
533
- console.log(import_chalk2.default.green(` Score: ${scoringResult.overallScore}/100 (threshold: ${threshold})`));
705
+ console.log(import_chalk2.default.green(` Score: ${scoringResult.overall}/100 (threshold: ${threshold})`));
534
706
  }
535
707
  console.log(import_chalk2.default.dim("\n \u{1F4A1} Track historical trends: https://getaiready.dev \u2014 Team plan $99/mo"));
536
708
  }
@@ -543,11 +715,20 @@ var scanHelpText = `
543
715
  EXAMPLES:
544
716
  $ aiready scan # Analyze all tools
545
717
  $ aiready scan --tools patterns,context # Skip consistency
718
+ $ aiready scan --profile agentic # Optimize for AI agent execution
719
+ $ aiready scan --profile security # Optimize for secure coding (testability)
720
+ $ aiready scan --compare-to prev-report.json # Compare trends against previous run
546
721
  $ aiready scan --score --threshold 75 # CI/CD with threshold
547
722
  $ aiready scan --ci --threshold 70 # GitHub Actions gatekeeper
548
723
  $ aiready scan --ci --fail-on major # Fail on major+ issues
549
724
  $ aiready scan --output json --output-file report.json
550
725
 
726
+ PROFILES:
727
+ agentic: hallucination, grounding, testability
728
+ cost: patterns, context
729
+ security: consistency, testability
730
+ onboarding: context, consistency, grounding
731
+
551
732
  CI/CD INTEGRATION (Gatekeeper Mode):
552
733
  Use --ci for GitHub Actions integration:
553
734
  - Outputs GitHub Actions annotations for PR checks
@@ -820,7 +1001,7 @@ async function contextAction(directory, options) {
820
1001
 
821
1002
  // src/commands/consistency.ts
822
1003
  var import_chalk5 = __toESM(require("chalk"));
823
- var import_fs2 = require("fs");
1004
+ var import_fs3 = require("fs");
824
1005
  var import_path5 = require("path");
825
1006
  var import_core4 = require("@aiready/core");
826
1007
  async function consistencyAction(directory, options) {
@@ -878,7 +1059,7 @@ async function consistencyAction(directory, options) {
878
1059
  `aiready-report-${getReportTimestamp()}.md`,
879
1060
  resolvedDir
880
1061
  );
881
- (0, import_fs2.writeFileSync)(outputPath, markdown);
1062
+ (0, import_fs3.writeFileSync)(outputPath, markdown);
882
1063
  console.log(import_chalk5.default.green(`\u2705 Report saved to ${outputPath}`));
883
1064
  } else {
884
1065
  console.log(import_chalk5.default.bold("\n\u{1F4CA} Summary\n"));
@@ -965,7 +1146,7 @@ async function consistencyAction(directory, options) {
965
1146
 
966
1147
  // src/commands/visualize.ts
967
1148
  var import_chalk6 = __toESM(require("chalk"));
968
- var import_fs3 = require("fs");
1149
+ var import_fs4 = require("fs");
969
1150
  var import_path6 = require("path");
970
1151
  var import_child_process = require("child_process");
971
1152
  var import_core5 = require("@aiready/core");
@@ -974,7 +1155,7 @@ async function visualizeAction(directory, options) {
974
1155
  try {
975
1156
  const dirPath = (0, import_path6.resolve)(process.cwd(), directory || ".");
976
1157
  let reportPath = options.report ? (0, import_path6.resolve)(dirPath, options.report) : null;
977
- if (!reportPath || !(0, import_fs3.existsSync)(reportPath)) {
1158
+ if (!reportPath || !(0, import_fs4.existsSync)(reportPath)) {
978
1159
  const latestScan = findLatestScanReport(dirPath);
979
1160
  if (latestScan) {
980
1161
  reportPath = latestScan;
@@ -990,13 +1171,13 @@ Or specify a custom report:
990
1171
  return;
991
1172
  }
992
1173
  }
993
- const raw = (0, import_fs3.readFileSync)(reportPath, "utf8");
1174
+ const raw = (0, import_fs4.readFileSync)(reportPath, "utf8");
994
1175
  const report = JSON.parse(raw);
995
1176
  const configPath = (0, import_path6.resolve)(dirPath, "aiready.json");
996
1177
  let graphConfig = { maxNodes: 400, maxEdges: 600 };
997
- if ((0, import_fs3.existsSync)(configPath)) {
1178
+ if ((0, import_fs4.existsSync)(configPath)) {
998
1179
  try {
999
- const rawConfig = JSON.parse((0, import_fs3.readFileSync)(configPath, "utf8"));
1180
+ const rawConfig = JSON.parse((0, import_fs4.readFileSync)(configPath, "utf8"));
1000
1181
  if (rawConfig.visualizer?.graph) {
1001
1182
  graphConfig = {
1002
1183
  maxNodes: rawConfig.visualizer.graph.maxNodes ?? graphConfig.maxNodes,
@@ -1018,7 +1199,7 @@ Or specify a custom report:
1018
1199
  const monorepoWebDir = (0, import_path6.resolve)(dirPath, "packages/visualizer");
1019
1200
  let webDir = "";
1020
1201
  let visualizerAvailable = false;
1021
- if ((0, import_fs3.existsSync)(monorepoWebDir)) {
1202
+ if ((0, import_fs4.existsSync)(monorepoWebDir)) {
1022
1203
  webDir = monorepoWebDir;
1023
1204
  visualizerAvailable = true;
1024
1205
  } else {
@@ -1034,7 +1215,7 @@ Or specify a custom report:
1034
1215
  currentDir = parent;
1035
1216
  }
1036
1217
  for (const location of nodemodulesLocations) {
1037
- if ((0, import_fs3.existsSync)(location) && (0, import_fs3.existsSync)((0, import_path6.resolve)(location, "package.json"))) {
1218
+ if ((0, import_fs4.existsSync)(location) && (0, import_fs4.existsSync)((0, import_path6.resolve)(location, "package.json"))) {
1038
1219
  webDir = location;
1039
1220
  visualizerAvailable = true;
1040
1221
  break;
@@ -1049,14 +1230,14 @@ Or specify a custom report:
1049
1230
  }
1050
1231
  }
1051
1232
  }
1052
- const webViteConfigExists = webDir && (0, import_fs3.existsSync)((0, import_path6.resolve)(webDir, "web", "vite.config.ts"));
1233
+ const webViteConfigExists = webDir && (0, import_fs4.existsSync)((0, import_path6.resolve)(webDir, "web", "vite.config.ts"));
1053
1234
  if (visualizerAvailable && webViteConfigExists) {
1054
1235
  const spawnCwd = webDir;
1055
1236
  const { watch } = await import("fs");
1056
1237
  const copyReportToViz = () => {
1057
1238
  try {
1058
1239
  const destPath = (0, import_path6.resolve)(spawnCwd, "web", "report-data.json");
1059
- (0, import_fs3.copyFileSync)(reportPath, destPath);
1240
+ (0, import_fs4.copyFileSync)(reportPath, destPath);
1060
1241
  console.log(`\u{1F4CB} Report synced to ${destPath}`);
1061
1242
  } catch (e) {
1062
1243
  console.error("Failed to sync report:", e);
@@ -1104,7 +1285,7 @@ Or specify a custom report:
1104
1285
  const html = (0, import_core6.generateHTML)(graph);
1105
1286
  const defaultOutput = "visualization.html";
1106
1287
  const outPath = (0, import_path6.resolve)(dirPath, options.output || defaultOutput);
1107
- (0, import_fs3.writeFileSync)(outPath, html, "utf8");
1288
+ (0, import_fs4.writeFileSync)(outPath, html, "utf8");
1108
1289
  console.log(import_chalk6.default.green(`\u2705 Visualization written to: ${outPath}`));
1109
1290
  if (options.open || options.serve) {
1110
1291
  const opener = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
@@ -1176,8 +1357,25 @@ NOTES:
1176
1357
  - Same options as 'visualize'. Use --serve to host the static HTML, or --dev for live reload.
1177
1358
  `;
1178
1359
 
1360
+ // src/commands/hallucination-risk.ts
1361
+ var import_chalk7 = __toESM(require("chalk"));
1362
+ var import_core7 = require("@aiready/core");
1363
+
1364
+ // src/commands/agent-grounding.ts
1365
+ var import_chalk8 = __toESM(require("chalk"));
1366
+ var import_core8 = require("@aiready/core");
1367
+
1368
+ // src/commands/testability.ts
1369
+ var import_chalk9 = __toESM(require("chalk"));
1370
+ var import_core9 = require("@aiready/core");
1371
+
1179
1372
  // src/cli.ts
1180
- var packageJson = JSON.parse((0, import_fs4.readFileSync)((0, import_path7.join)(__dirname, "../package.json"), "utf8"));
1373
+ var import_meta = {};
1374
+ var getDirname = () => {
1375
+ if (typeof __dirname !== "undefined") return __dirname;
1376
+ return (0, import_path7.dirname)((0, import_url.fileURLToPath)(import_meta.url));
1377
+ };
1378
+ var packageJson = JSON.parse((0, import_fs5.readFileSync)((0, import_path7.join)(getDirname(), "../package.json"), "utf8"));
1181
1379
  var program = new import_commander.Command();
1182
1380
  program.name("aiready").description("AIReady - Assess and improve AI-readiness of codebases").version(packageJson.version).addHelpText("after", `
1183
1381
  AI READINESS SCORING:
@@ -1215,7 +1413,7 @@ VERSION: ${packageJson.version}
1215
1413
  DOCUMENTATION: https://aiready.dev/docs/cli
1216
1414
  GITHUB: https://github.com/caopengau/aiready-cli
1217
1415
  LANDING: https://github.com/caopengau/aiready-landing`);
1218
- program.command("scan").description("Run comprehensive AI-readiness analysis (patterns + context + consistency)").argument("[directory]", "Directory to analyze", ".").option("-t, --tools <tools>", "Tools to run (comma-separated: patterns,context,consistency)", "patterns,context,consistency").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", "json").option("--output-file <path>", "Output file path (for json)").option("--no-score", "Disable calculating AI Readiness Score (enabled by default)").option("--weights <weights>", "Custom scoring weights (patterns:40,context:35,consistency:25)").option("--threshold <score>", "Fail CI/CD if score below threshold (0-100)").option("--ci", "CI mode: GitHub Actions annotations, no colors, fail on threshold").option("--fail-on <level>", "Fail on issues: critical, major, any", "critical").addHelpText("after", scanHelpText).action(async (directory, options) => {
1416
+ program.command("scan").description("Run comprehensive AI-readiness analysis (patterns + context + consistency)").argument("[directory]", "Directory to analyze", ".").option("-t, --tools <tools>", "Tools to run (comma-separated: patterns,context,consistency,hallucination,grounding,testability)").option("--profile <type>", "Scan profile to use (agentic, cost, security, onboarding)").option("--compare-to <path>", "Compare results against a previous AIReady report JSON").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", "json").option("--output-file <path>", "Output file path (for json)").option("--no-score", "Disable calculating AI Readiness Score (enabled by default)").option("--weights <weights>", "Custom scoring weights").option("--threshold <score>", "Fail CI/CD if score below threshold (0-100)").option("--ci", "CI mode: GitHub Actions annotations, no colors, fail on threshold").option("--fail-on <level>", "Fail on issues: critical, major, any", "critical").addHelpText("after", scanHelpText).action(async (directory, options) => {
1219
1417
  await scanAction(directory, options);
1220
1418
  });
1221
1419
  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("--max-candidates <number>", "Maximum candidates per block (performance tuning)").option("--min-shared-tokens <number>", "Minimum shared tokens for candidates (performance tuning)").option("--full-scan", "Disable smart defaults for comprehensive analysis (slower)").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 for patterns (0-100)").addHelpText("after", patternsHelpText).action(async (directory, options) => {