@aiready/cli 0.10.6 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.mjs CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  __require,
4
4
  analyzeUnified,
5
5
  scoreUnified
6
- } from "./chunk-VQCWYJYJ.mjs";
6
+ } from "./chunk-N56YAZVN.mjs";
7
7
 
8
8
  // src/cli.ts
9
9
  import { Command } from "commander";
@@ -19,18 +19,14 @@ import {
19
19
  loadMergedConfig,
20
20
  handleJSONOutput,
21
21
  handleCLIError as handleCLIError2,
22
- getElapsedTime,
23
22
  resolveOutputPath,
24
23
  formatScore,
25
- formatToolScore,
26
24
  calculateTokenBudget,
27
25
  estimateCostFromBudget,
28
26
  getModelPreset,
29
27
  getRating,
30
- getRatingDisplay,
31
28
  getRepoMetadata,
32
29
  Severity,
33
- IssueType,
34
30
  ToolName
35
31
  } from "@aiready/core";
36
32
 
@@ -151,12 +147,6 @@ function generateMarkdownReport(report, elapsedTime) {
151
147
  }
152
148
  return markdown;
153
149
  }
154
- function truncateArray(arr, cap = 8) {
155
- if (!Array.isArray(arr)) return "";
156
- const shown = arr.slice(0, cap).map((v) => String(v));
157
- const more = arr.length - shown.length;
158
- return shown.join(", ") + (more > 0 ? `, ... (+${more} more)` : "");
159
- }
160
150
 
161
151
  // src/commands/upload.ts
162
152
  import fs from "fs";
@@ -270,14 +260,14 @@ async function scanAction(directory, options) {
270
260
  try {
271
261
  const defaults = {
272
262
  tools: [
273
- "patterns",
274
- "context",
275
- "consistency",
263
+ "pattern-detect",
264
+ "context-analyzer",
265
+ "naming-consistency",
276
266
  "ai-signal-clarity",
277
267
  "agent-grounding",
278
- "testability",
268
+ "testability-index",
279
269
  "doc-drift",
280
- "deps-health",
270
+ "dependency-health",
281
271
  "change-amplification"
282
272
  ],
283
273
  include: void 0,
@@ -287,35 +277,37 @@ async function scanAction(directory, options) {
287
277
  file: void 0
288
278
  }
289
279
  };
290
- let profileTools = options.tools ? options.tools.split(",").map((t) => {
291
- const tool = t.trim();
292
- if (tool === "hallucination" || tool === "hallucination-risk")
293
- return "aiSignalClarity";
294
- return tool;
295
- }) : void 0;
280
+ let profileTools = options.tools ? options.tools.split(",").map((t) => t.trim()) : void 0;
296
281
  if (options.profile) {
297
282
  switch (options.profile.toLowerCase()) {
298
283
  case "agentic":
299
284
  profileTools = [
300
- "ai-signal-clarity",
301
- "agent-grounding",
302
- "testability"
285
+ ToolName.AiSignalClarity,
286
+ ToolName.AgentGrounding,
287
+ ToolName.TestabilityIndex
303
288
  ];
304
289
  break;
305
290
  case "cost":
306
- profileTools = ["patterns", "context"];
291
+ profileTools = [ToolName.PatternDetect, ToolName.ContextAnalyzer];
307
292
  break;
308
293
  case "security":
309
- profileTools = ["consistency", "testability"];
294
+ profileTools = [
295
+ ToolName.NamingConsistency,
296
+ ToolName.TestabilityIndex
297
+ ];
310
298
  break;
311
299
  case "onboarding":
312
- profileTools = ["context", "consistency", "agent-grounding"];
300
+ profileTools = [
301
+ ToolName.ContextAnalyzer,
302
+ ToolName.NamingConsistency,
303
+ ToolName.AgentGrounding
304
+ ];
313
305
  break;
314
306
  default:
315
307
  console.log(
316
308
  chalk3.yellow(
317
309
  `
318
- \u26A0\uFE0F Unknown profile '${options.profile}'. Using specified tools or defaults.`
310
+ \u26A0\uFE0F Unknown profile '${options.profile}'. Using defaults.`
319
311
  )
320
312
  );
321
313
  }
@@ -324,16 +316,14 @@ async function scanAction(directory, options) {
324
316
  include: options.include?.split(","),
325
317
  exclude: options.exclude?.split(",")
326
318
  };
327
- if (profileTools) {
328
- cliOverrides.tools = profileTools;
329
- }
319
+ if (profileTools) cliOverrides.tools = profileTools;
330
320
  const baseOptions = await loadMergedConfig(
331
321
  resolvedDir,
332
322
  defaults,
333
323
  cliOverrides
334
324
  );
335
325
  let finalOptions = { ...baseOptions };
336
- if (baseOptions.tools.includes("patterns")) {
326
+ if (baseOptions.tools.includes(ToolName.PatternDetect) || baseOptions.tools.includes("patterns")) {
337
327
  const { getSmartDefaults } = await import("@aiready/pattern-detect");
338
328
  const patternSmartDefaults = await getSmartDefaults(
339
329
  resolvedDir,
@@ -348,256 +338,21 @@ async function scanAction(directory, options) {
348
338
  console.log(chalk3.cyan("\n=== AIReady Run Preview ==="));
349
339
  console.log(
350
340
  chalk3.white("Tools to run:"),
351
- (finalOptions.tools || ["patterns", "context", "consistency"]).join(", ")
341
+ (finalOptions.tools || []).join(", ")
352
342
  );
353
- console.log(chalk3.white("Will use settings from config and defaults."));
354
- console.log(chalk3.white("\nGeneral settings:"));
355
- if (finalOptions.rootDir)
356
- console.log(` rootDir: ${chalk3.bold(String(finalOptions.rootDir))}`);
357
- if (finalOptions.include)
358
- console.log(
359
- ` include: ${chalk3.bold(truncateArray(finalOptions.include, 6))}`
360
- );
361
- if (finalOptions.exclude)
362
- console.log(
363
- ` exclude: ${chalk3.bold(truncateArray(finalOptions.exclude, 6))}`
364
- );
365
- if (finalOptions["pattern-detect"] || finalOptions.minSimilarity) {
366
- const patternDetectConfig = finalOptions["pattern-detect"] || {
367
- minSimilarity: finalOptions.minSimilarity,
368
- minLines: finalOptions.minLines,
369
- approx: finalOptions.approx,
370
- minSharedTokens: finalOptions.minSharedTokens,
371
- maxCandidatesPerBlock: finalOptions.maxCandidatesPerBlock,
372
- batchSize: finalOptions.batchSize,
373
- streamResults: finalOptions.streamResults,
374
- severity: finalOptions.severity,
375
- includeTests: finalOptions.includeTests
376
- };
377
- console.log(chalk3.white("\nPattern-detect settings:"));
378
- console.log(
379
- ` minSimilarity: ${chalk3.bold(patternDetectConfig.minSimilarity ?? "default")}`
380
- );
381
- console.log(
382
- ` minLines: ${chalk3.bold(patternDetectConfig.minLines ?? "default")}`
383
- );
384
- if (patternDetectConfig.approx !== void 0)
385
- console.log(
386
- ` approx: ${chalk3.bold(String(patternDetectConfig.approx))}`
387
- );
388
- if (patternDetectConfig.minSharedTokens !== void 0)
389
- console.log(
390
- ` minSharedTokens: ${chalk3.bold(String(patternDetectConfig.minSharedTokens))}`
391
- );
392
- if (patternDetectConfig.maxCandidatesPerBlock !== void 0)
393
- console.log(
394
- ` maxCandidatesPerBlock: ${chalk3.bold(String(patternDetectConfig.maxCandidatesPerBlock))}`
395
- );
396
- if (patternDetectConfig.batchSize !== void 0)
397
- console.log(
398
- ` batchSize: ${chalk3.bold(String(patternDetectConfig.batchSize))}`
399
- );
400
- if (patternDetectConfig.streamResults !== void 0)
401
- console.log(
402
- ` streamResults: ${chalk3.bold(String(patternDetectConfig.streamResults))}`
403
- );
404
- if (patternDetectConfig.severity !== void 0)
405
- console.log(
406
- ` severity: ${chalk3.bold(String(patternDetectConfig.severity))}`
407
- );
408
- if (patternDetectConfig.includeTests !== void 0)
409
- console.log(
410
- ` includeTests: ${chalk3.bold(String(patternDetectConfig.includeTests))}`
411
- );
412
- }
413
- if (finalOptions["context-analyzer"] || finalOptions.maxDepth) {
414
- const ca = finalOptions["context-analyzer"] || {
415
- maxDepth: finalOptions.maxDepth,
416
- maxContextBudget: finalOptions.maxContextBudget,
417
- minCohesion: finalOptions.minCohesion,
418
- maxFragmentation: finalOptions.maxFragmentation,
419
- includeNodeModules: finalOptions.includeNodeModules
420
- };
421
- console.log(chalk3.white("\nContext-analyzer settings:"));
422
- console.log(` maxDepth: ${chalk3.bold(ca.maxDepth ?? "default")}`);
423
- console.log(
424
- ` maxContextBudget: ${chalk3.bold(ca.maxContextBudget ?? "default")}`
425
- );
426
- if (ca.minCohesion !== void 0)
427
- console.log(` minCohesion: ${chalk3.bold(String(ca.minCohesion))}`);
428
- if (ca.maxFragmentation !== void 0)
429
- console.log(
430
- ` maxFragmentation: ${chalk3.bold(String(ca.maxFragmentation))}`
431
- );
432
- if (ca.includeNodeModules !== void 0)
433
- console.log(
434
- ` includeNodeModules: ${chalk3.bold(String(ca.includeNodeModules))}`
435
- );
436
- }
437
- if (finalOptions.consistency) {
438
- const c = finalOptions.consistency;
439
- console.log(chalk3.white("\nConsistency settings:"));
440
- console.log(
441
- ` checkNaming: ${chalk3.bold(String(c.checkNaming ?? true))}`
442
- );
443
- console.log(
444
- ` checkPatterns: ${chalk3.bold(String(c.checkPatterns ?? true))}`
445
- );
446
- console.log(
447
- ` checkArchitecture: ${chalk3.bold(String(c.checkArchitecture ?? false))}`
448
- );
449
- if (c.minSeverity)
450
- console.log(` minSeverity: ${chalk3.bold(c.minSeverity)}`);
451
- if (c.acceptedAbbreviations)
452
- console.log(
453
- ` acceptedAbbreviations: ${chalk3.bold(truncateArray(c.acceptedAbbreviations, 8))}`
454
- );
455
- if (c.shortWords)
456
- console.log(
457
- ` shortWords: ${chalk3.bold(truncateArray(c.shortWords, 8))}`
458
- );
459
- }
460
- console.log(chalk3.white("\nStarting analysis..."));
461
343
  const progressCallback = (event) => {
462
344
  console.log(chalk3.cyan(`
463
345
  --- ${event.tool.toUpperCase()} RESULTS ---`));
464
- try {
465
- if (event.tool === "patterns") {
466
- const pr = event.data;
346
+ const res = event.data;
347
+ if (res.summary) {
348
+ if (res.summary.totalIssues !== void 0)
349
+ console.log(` Issues found: ${chalk3.bold(res.summary.totalIssues)}`);
350
+ if (res.summary.score !== void 0)
351
+ console.log(` Tool Score: ${chalk3.bold(res.summary.score)}/100`);
352
+ if (res.summary.totalFiles !== void 0)
467
353
  console.log(
468
- ` Duplicate patterns: ${chalk3.bold(String(pr.duplicates?.length || 0))}`
354
+ ` Files analyzed: ${chalk3.bold(res.summary.totalFiles)}`
469
355
  );
470
- console.log(
471
- ` Files with pattern issues: ${chalk3.bold(String(pr.results?.length || 0))}`
472
- );
473
- if (pr.duplicates && pr.duplicates.length > 0) {
474
- pr.duplicates.slice(0, 5).forEach((d, i) => {
475
- console.log(
476
- ` ${i + 1}. ${d.file1.split("/").pop()} \u2194 ${d.file2.split("/").pop()} (sim=${(d.similarity * 100).toFixed(1)}%)`
477
- );
478
- });
479
- }
480
- if (pr.results && pr.results.length > 0) {
481
- console.log(` Top files with pattern issues:`);
482
- const sortedByIssues = [...pr.results].sort(
483
- (a, b) => (b.issues?.length || 0) - (a.issues?.length || 0)
484
- );
485
- sortedByIssues.slice(0, 5).forEach((r, i) => {
486
- console.log(
487
- ` ${i + 1}. ${r.fileName.split("/").pop()} - ${r.issues.length} issue(s)`
488
- );
489
- });
490
- }
491
- if (pr.groups && pr.groups.length >= 0) {
492
- console.log(
493
- ` \u2705 Grouped ${chalk3.bold(String(pr.duplicates?.length || 0))} duplicates into ${chalk3.bold(String(pr.groups.length))} file pairs`
494
- );
495
- }
496
- if (pr.clusters && pr.clusters.length >= 0) {
497
- console.log(
498
- ` \u2705 Created ${chalk3.bold(String(pr.clusters.length))} refactor clusters`
499
- );
500
- pr.clusters.slice(0, 3).forEach((cl, idx) => {
501
- const files = (cl.files || []).map((f) => f.path.split("/").pop()).join(", ");
502
- console.log(
503
- ` ${idx + 1}. ${files} (${cl.tokenCost || "n/a"} tokens)`
504
- );
505
- });
506
- }
507
- } else if (event.tool === "context") {
508
- const cr = event.data;
509
- console.log(
510
- ` Context issues found: ${chalk3.bold(String(cr.length || 0))}`
511
- );
512
- cr.slice(0, 5).forEach((c, i) => {
513
- const msg = c.message ? ` - ${c.message}` : "";
514
- console.log(
515
- ` ${i + 1}. ${c.file} (${c.severity || "n/a"})${msg}`
516
- );
517
- });
518
- } else if (event.tool === "consistency") {
519
- const rep = event.data;
520
- console.log(
521
- ` Consistency totalIssues: ${chalk3.bold(String(rep.summary?.totalIssues || 0))}`
522
- );
523
- if (rep.results && rep.results.length > 0) {
524
- const fileMap = /* @__PURE__ */ new Map();
525
- rep.results.forEach((r) => {
526
- (r.issues || []).forEach((issue) => {
527
- const file = issue.location?.file || r.file || "unknown";
528
- if (!fileMap.has(file)) fileMap.set(file, []);
529
- fileMap.get(file).push(issue);
530
- });
531
- });
532
- const files = Array.from(fileMap.entries()).sort(
533
- (a, b) => b[1].length - a[1].length
534
- );
535
- const topFiles = files.slice(0, 10);
536
- topFiles.forEach(([file, issues], idx) => {
537
- const counts = issues.reduce(
538
- (acc, it) => {
539
- const s = (it.severity || Severity.Info).toLowerCase();
540
- acc[s] = (acc[s] || 0) + 1;
541
- return acc;
542
- },
543
- {}
544
- );
545
- const sample = issues.find(
546
- (it) => it.severity === Severity.Critical || it.severity === Severity.Major
547
- ) || issues[0];
548
- const sampleMsg = sample ? ` \u2014 ${sample.message}` : "";
549
- console.log(
550
- ` ${idx + 1}. ${file} \u2014 ${issues.length} issue(s) (critical:${counts[Severity.Critical] || 0} major:${counts[Severity.Major] || 0} minor:${counts[Severity.Minor] || 0} info:${counts[Severity.Info] || 0})${sampleMsg}`
551
- );
552
- });
553
- const remaining = files.length - topFiles.length;
554
- if (remaining > 0) {
555
- console.log(
556
- chalk3.dim(
557
- ` ... and ${remaining} more files with issues (use --output json for full details)`
558
- )
559
- );
560
- }
561
- }
562
- } else if (event.tool === "doc-drift") {
563
- const dr = event.data;
564
- console.log(
565
- ` Issues found: ${chalk3.bold(String(dr.issues?.length || 0))}`
566
- );
567
- if (dr.rawData) {
568
- console.log(
569
- ` Signature Mismatches: ${chalk3.bold(dr.rawData.outdatedComments || 0)}`
570
- );
571
- console.log(
572
- ` Undocumented Complexity: ${chalk3.bold(dr.rawData.undocumentedComplexity || 0)}`
573
- );
574
- }
575
- } else if (event.tool === "deps-health") {
576
- const dr = event.data;
577
- console.log(
578
- ` Packages Analyzed: ${chalk3.bold(String(dr.summary?.packagesAnalyzed || 0))}`
579
- );
580
- if (dr.rawData) {
581
- console.log(
582
- ` Deprecated Packages: ${chalk3.bold(dr.rawData.deprecatedPackages || 0)}`
583
- );
584
- console.log(
585
- ` AI Cutoff Skew Score: ${chalk3.bold(dr.rawData.trainingCutoffSkew?.toFixed(1) || 0)}`
586
- );
587
- }
588
- } else if (event.tool === "change-amplification" || event.tool === "changeAmplification") {
589
- const dr = event.data;
590
- console.log(
591
- ` Coupling issues: ${chalk3.bold(String(dr.issues?.length || 0))}`
592
- );
593
- if (dr.summary) {
594
- console.log(
595
- ` Complexity Score: ${chalk3.bold(dr.summary.score || 0)}/100`
596
- );
597
- }
598
- }
599
- } catch (err) {
600
- void err;
601
356
  }
602
357
  };
603
358
  const results = await analyzeUnified({
@@ -607,44 +362,14 @@ async function scanAction(directory, options) {
607
362
  process.stdout.write(
608
363
  `\r\x1B[K [${processed}/${total}] ${message}...`
609
364
  );
610
- if (processed === total) {
611
- process.stdout.write("\n");
612
- }
365
+ if (processed === total) process.stdout.write("\n");
613
366
  },
614
367
  suppressToolConfig: true
615
368
  });
616
369
  console.log(chalk3.cyan("\n=== AIReady Run Summary ==="));
617
- console.log(
618
- chalk3.white("Tools run:"),
619
- (finalOptions.tools || ["patterns", "context", "consistency"]).join(", ")
620
- );
621
- console.log(chalk3.cyan("\nResults summary:"));
622
370
  console.log(
623
371
  ` Total issues (all tools): ${chalk3.bold(String(results.summary.totalIssues || 0))}`
624
372
  );
625
- if (results[ToolName.PatternDetect]) {
626
- console.log(
627
- ` Duplicate patterns found: ${chalk3.bold(String(results[ToolName.PatternDetect].duplicates?.length || 0))}`
628
- );
629
- console.log(
630
- ` Pattern files with issues: ${chalk3.bold(String(results[ToolName.PatternDetect].results.length || 0))}`
631
- );
632
- }
633
- if (results[ToolName.ContextAnalyzer])
634
- console.log(
635
- ` Context issues: ${chalk3.bold(String(results[ToolName.ContextAnalyzer].results.length || 0))}`
636
- );
637
- if (results[ToolName.NamingConsistency])
638
- console.log(
639
- ` Consistency issues: ${chalk3.bold(String(results[ToolName.NamingConsistency].summary?.totalIssues || 0))}`
640
- );
641
- if (results[ToolName.ChangeAmplification])
642
- console.log(
643
- ` Change amplification: ${chalk3.bold(String(results[ToolName.ChangeAmplification].summary?.score || 0))}/100`
644
- );
645
- console.log(chalk3.cyan("===========================\n"));
646
- const elapsedTime = getElapsedTime(startTime);
647
- void elapsedTime;
648
373
  let scoringResult;
649
374
  if (options.score || finalOptions.scoring?.showBreakdown) {
650
375
  scoringResult = await scoreUnified(results, finalOptions);
@@ -652,55 +377,34 @@ async function scanAction(directory, options) {
652
377
  console.log(` ${formatScore(scoringResult)}`);
653
378
  if (options.compareTo) {
654
379
  try {
655
- const prevReportStr = readFileSync2(
656
- resolvePath3(process.cwd(), options.compareTo),
657
- "utf8"
380
+ const prevReport = JSON.parse(
381
+ readFileSync2(resolvePath3(process.cwd(), options.compareTo), "utf8")
658
382
  );
659
- const prevReport = JSON.parse(prevReportStr);
660
- const prevScore = prevReport.scoring?.score || prevReport.scoring?.overallScore;
383
+ const prevScore = prevReport.scoring?.overall || prevReport.scoring?.score;
661
384
  if (typeof prevScore === "number") {
662
385
  const diff = scoringResult.overall - prevScore;
663
386
  const diffStr = diff > 0 ? `+${diff}` : String(diff);
664
- console.log();
665
- if (diff > 0) {
387
+ if (diff > 0)
666
388
  console.log(
667
389
  chalk3.green(
668
390
  ` \u{1F4C8} Trend: ${diffStr} compared to ${options.compareTo} (${prevScore} \u2192 ${scoringResult.overall})`
669
391
  )
670
392
  );
671
- } else if (diff < 0) {
393
+ else if (diff < 0)
672
394
  console.log(
673
395
  chalk3.red(
674
396
  ` \u{1F4C9} Trend: ${diffStr} compared to ${options.compareTo} (${prevScore} \u2192 ${scoringResult.overall})`
675
397
  )
676
398
  );
677
- } else {
399
+ else
678
400
  console.log(
679
401
  chalk3.blue(
680
- ` \u2796 Trend: No change compared to ${options.compareTo} (${prevScore} \u2192 ${scoringResult.overall})`
402
+ ` \u2796 Trend: No change (${prevScore} \u2192 ${scoringResult.overall})`
681
403
  )
682
404
  );
683
- }
684
- scoringResult.trend = {
685
- previousScore: prevScore,
686
- difference: diff
687
- };
688
- } else {
689
- console.log(
690
- chalk3.yellow(
691
- `
692
- \u26A0\uFE0F Previous report at ${options.compareTo} does not contain an overall score.`
693
- )
694
- );
695
405
  }
696
406
  } catch (e) {
697
407
  void e;
698
- console.log(
699
- chalk3.yellow(
700
- `
701
- \u26A0\uFE0F Could not read or parse previous report at ${options.compareTo}.`
702
- )
703
- );
704
408
  }
705
409
  }
706
410
  const totalWastedDuplication = (scoringResult.breakdown || []).reduce(
@@ -714,7 +418,8 @@ async function scanAction(directory, options) {
714
418
  const totalContext = Math.max(
715
419
  ...(scoringResult.breakdown || []).map(
716
420
  (s) => s.tokenBudget?.totalContextTokens || 0
717
- )
421
+ ),
422
+ 0
718
423
  );
719
424
  if (totalContext > 0) {
720
425
  const unifiedBudget = calculateTokenBudget({
@@ -725,35 +430,17 @@ async function scanAction(directory, options) {
725
430
  chattiness: 0
726
431
  }
727
432
  });
728
- const targetModel = options.model || "claude-4.6";
729
- const modelPreset = getModelPreset(targetModel);
433
+ const modelPreset = getModelPreset(options.model || "claude-4.6");
730
434
  const costEstimate = estimateCostFromBudget(unifiedBudget, modelPreset);
731
- const barWidth = 20;
732
- const filled = Math.round(unifiedBudget.efficiencyRatio * barWidth);
733
- const bar = chalk3.green("\u2588".repeat(filled)) + chalk3.dim("\u2591".repeat(barWidth - filled));
734
- console.log(chalk3.bold("\n\u{1F4CA} AI Token Budget Analysis (v0.13)"));
735
- console.log(
736
- ` Efficiency: [${bar}] ${(unifiedBudget.efficiencyRatio * 100).toFixed(0)}%`
737
- );
738
- console.log(
739
- ` Total Context: ${chalk3.bold(unifiedBudget.totalContextTokens.toLocaleString())} tokens`
740
- );
435
+ console.log(chalk3.bold("\n\u{1F4CA} AI Token Budget Analysis"));
741
436
  console.log(
742
- ` Wasted Tokens: ${chalk3.red(unifiedBudget.wastedTokens.total.toLocaleString())} (${(unifiedBudget.wastedTokens.total / unifiedBudget.totalContextTokens * 100).toFixed(1)}%)`
437
+ ` Efficiency: ${(unifiedBudget.efficiencyRatio * 100).toFixed(0)}%`
743
438
  );
744
- console.log(` Waste Breakdown:`);
745
439
  console.log(
746
- ` \u2022 Duplication: ${unifiedBudget.wastedTokens.bySource.duplication.toLocaleString()} tokens`
440
+ ` Wasted Tokens: ${chalk3.red(unifiedBudget.wastedTokens.total.toLocaleString())}`
747
441
  );
748
442
  console.log(
749
- ` \u2022 Fragmentation: ${unifiedBudget.wastedTokens.bySource.fragmentation.toLocaleString()} tokens`
750
- );
751
- console.log(
752
- ` Potential Savings: ${chalk3.green(unifiedBudget.potentialRetrievableTokens.toLocaleString())} tokens retrievable`
753
- );
754
- console.log(
755
- `
756
- Est. Monthly Cost (${modelPreset.name}): ${chalk3.bold("$" + costEstimate.total)} [range: $${costEstimate.range[0]}-$${costEstimate.range[1]}]`
443
+ ` Est. Monthly Cost (${modelPreset.name}): ${chalk3.bold("$" + costEstimate.total)}`
757
444
  );
758
445
  scoringResult.tokenBudget = unifiedBudget;
759
446
  scoringResult.costEstimate = {
@@ -761,85 +448,31 @@ async function scanAction(directory, options) {
761
448
  ...costEstimate
762
449
  };
763
450
  }
764
- if (scoringResult.breakdown && scoringResult.breakdown.length > 0) {
451
+ if (scoringResult.breakdown) {
765
452
  console.log(chalk3.bold("\nTool breakdown:"));
766
453
  scoringResult.breakdown.forEach((tool) => {
767
454
  const rating = getRating(tool.score);
768
- const rd = getRatingDisplay(rating);
769
- console.log(
770
- ` - ${tool.toolName}: ${tool.score}/100 (${rating}) ${rd.emoji}`
771
- );
455
+ console.log(` - ${tool.toolName}: ${tool.score}/100 (${rating})`);
772
456
  });
773
- console.log();
774
- if (finalOptions.scoring?.showBreakdown) {
775
- console.log(chalk3.bold("Detailed tool breakdown:"));
776
- scoringResult.breakdown.forEach((tool) => {
777
- console.log(formatToolScore(tool));
778
- });
779
- console.log();
780
- }
781
457
  }
782
458
  }
783
459
  const mapToUnifiedReport = (res, scoring) => {
784
460
  const allResults = [];
785
- let totalFilesSet = /* @__PURE__ */ new Set();
461
+ const totalFilesSet = /* @__PURE__ */ new Set();
786
462
  let criticalCount = 0;
787
463
  let majorCount = 0;
788
- const collect = (spokeRes, defaultType = IssueType.AiSignalClarity) => {
464
+ res.summary.toolsRun.forEach((toolId) => {
465
+ const spokeRes = res[toolId];
789
466
  if (!spokeRes || !spokeRes.results) return;
790
467
  spokeRes.results.forEach((r) => {
791
- const fileName = r.fileName || r.file || "unknown";
792
- totalFilesSet.add(fileName);
793
- const normalizedResult = {
794
- fileName,
795
- issues: [],
796
- metrics: r.metrics || { tokenCost: r.tokenCost || 0 }
797
- };
798
- if (r.issues && Array.isArray(r.issues)) {
799
- r.issues.forEach((i) => {
800
- const normalizedIssue = typeof i === "string" ? {
801
- type: defaultType,
802
- severity: r.severity || Severity.Info,
803
- message: i,
804
- location: { file: fileName, line: 1 }
805
- } : {
806
- type: i.type || defaultType,
807
- severity: i.severity || r.severity || Severity.Info,
808
- message: i.message || String(i),
809
- location: i.location || { file: fileName, line: 1 },
810
- suggestion: i.suggestion
811
- };
812
- if (normalizedIssue.severity === Severity.Critical || normalizedIssue.severity === "critical")
813
- criticalCount++;
814
- if (normalizedIssue.severity === Severity.Major || normalizedIssue.severity === "major")
815
- majorCount++;
816
- normalizedResult.issues.push(normalizedIssue);
817
- });
818
- } else if (r.severity) {
819
- const normalizedIssue = {
820
- type: defaultType,
821
- severity: r.severity,
822
- message: r.message || "General issue",
823
- location: { file: fileName, line: 1 }
824
- };
825
- if (normalizedIssue.severity === Severity.Critical || normalizedIssue.severity === "critical")
826
- criticalCount++;
827
- if (normalizedIssue.severity === Severity.Major || normalizedIssue.severity === "major")
828
- majorCount++;
829
- normalizedResult.issues.push(normalizedIssue);
830
- }
831
- allResults.push(normalizedResult);
468
+ totalFilesSet.add(r.fileName);
469
+ allResults.push(r);
470
+ r.issues?.forEach((i) => {
471
+ if (i.severity === Severity.Critical) criticalCount++;
472
+ if (i.severity === Severity.Major) majorCount++;
473
+ });
832
474
  });
833
- };
834
- collect(res[ToolName.PatternDetect], IssueType.DuplicatePattern);
835
- collect(res[ToolName.ContextAnalyzer], IssueType.ContextFragmentation);
836
- collect(res[ToolName.NamingConsistency], IssueType.NamingInconsistency);
837
- collect(res[ToolName.DocDrift], IssueType.DocDrift);
838
- collect(res[ToolName.DependencyHealth], IssueType.DependencyHealth);
839
- collect(res[ToolName.AiSignalClarity], IssueType.AiSignalClarity);
840
- collect(res[ToolName.AgentGrounding], IssueType.AgentNavigationFailure);
841
- collect(res[ToolName.TestabilityIndex], IssueType.LowTestability);
842
- collect(res[ToolName.ChangeAmplification], IssueType.ChangeAmplification);
475
+ });
843
476
  return {
844
477
  ...res,
845
478
  results: allResults,
@@ -852,199 +485,70 @@ async function scanAction(directory, options) {
852
485
  scoring
853
486
  };
854
487
  };
488
+ const outputData = {
489
+ ...mapToUnifiedReport(results, scoringResult),
490
+ repository: repoMetadata
491
+ };
855
492
  const outputFormat = options.output || finalOptions.output?.format || "console";
856
- const userOutputFile = options.outputFile || finalOptions.output?.file;
493
+ const outputPath = resolveOutputPath(
494
+ options.outputFile || finalOptions.output?.file,
495
+ `aiready-report-${getReportTimestamp()}.json`,
496
+ resolvedDir
497
+ );
857
498
  if (outputFormat === "json") {
858
- const timestamp = getReportTimestamp();
859
- const defaultFilename = `aiready-report-${timestamp}.json`;
860
- const outputPath = resolveOutputPath(
861
- userOutputFile,
862
- defaultFilename,
863
- resolvedDir
864
- );
865
- const outputData = {
866
- ...mapToUnifiedReport(results, scoringResult),
867
- repository: repoMetadata
868
- };
869
499
  handleJSONOutput(
870
500
  outputData,
871
501
  outputPath,
872
502
  `\u2705 Report saved to ${outputPath}`
873
503
  );
874
- if (options.upload) {
875
- console.log(chalk3.blue("\n\u{1F4E4} Automatic upload triggered..."));
876
- await uploadAction(outputPath, {
877
- apiKey: options.apiKey,
878
- server: options.server
879
- });
880
- }
881
- await warnIfGraphCapExceeded(outputData, resolvedDir);
882
504
  } else {
883
- const timestamp = getReportTimestamp();
884
- const defaultFilename = `aiready-report-${timestamp}.json`;
885
- const outputPath = resolveOutputPath(
886
- userOutputFile,
887
- defaultFilename,
888
- resolvedDir
889
- );
890
- const outputData = {
891
- ...mapToUnifiedReport(results, scoringResult),
892
- repository: repoMetadata
893
- };
894
505
  try {
895
506
  writeFileSync(outputPath, JSON.stringify(outputData, null, 2));
896
507
  console.log(chalk3.dim(`\u2705 Report auto-persisted to ${outputPath}`));
897
- if (options.upload) {
898
- console.log(chalk3.blue("\n\u{1F4E4} Automatic upload triggered..."));
899
- await uploadAction(outputPath, {
900
- apiKey: options.apiKey,
901
- server: options.server
902
- });
903
- }
904
- await warnIfGraphCapExceeded(outputData, resolvedDir);
905
508
  } catch (err) {
906
509
  void err;
907
510
  }
908
511
  }
909
- const isCI = options.ci || process.env.CI === "true" || process.env.GITHUB_ACTIONS === "true";
512
+ if (options.upload) {
513
+ await uploadAction(outputPath, {
514
+ apiKey: options.apiKey,
515
+ server: options.server
516
+ });
517
+ }
518
+ await warnIfGraphCapExceeded(outputData, resolvedDir);
519
+ const isCI = options.ci || process.env.CI === "true";
910
520
  if (isCI && scoringResult) {
911
521
  const threshold = options.threshold ? parseInt(options.threshold) : void 0;
912
522
  const failOnLevel = options.failOn || "critical";
913
- if (process.env.GITHUB_ACTIONS === "true") {
914
- console.log(`
915
- ::group::AI Readiness Score`);
916
- console.log(`score=${scoringResult.overall}`);
917
- if (scoringResult.breakdown) {
918
- scoringResult.breakdown.forEach((tool) => {
919
- console.log(`${tool.toolName}=${tool.score}`);
920
- });
921
- }
922
- console.log("::endgroup::");
923
- if (threshold && scoringResult.overall < threshold) {
924
- console.log(
925
- `::error::AI Readiness Score ${scoringResult.overall} is below threshold ${threshold}`
926
- );
927
- } else if (threshold) {
928
- console.log(
929
- `::notice::AI Readiness Score: ${scoringResult.overall}/100 (threshold: ${threshold})`
930
- );
931
- }
932
- if (results[ToolName.PatternDetect]) {
933
- const criticalPatterns = results[ToolName.PatternDetect].results.flatMap(
934
- (p) => p.issues.filter((i) => i.severity === Severity.Critical)
935
- );
936
- criticalPatterns.slice(0, 10).forEach((issue) => {
937
- console.log(
938
- `::warning file=${issue.location?.file || "unknown"},line=${issue.location?.line || 1}::${issue.message}`
939
- );
940
- });
941
- }
942
- }
943
523
  let shouldFail = false;
944
524
  let failReason = "";
945
525
  if (threshold && scoringResult.overall < threshold) {
946
526
  shouldFail = true;
947
- failReason = `AI Readiness Score ${scoringResult.overall} is below threshold ${threshold}`;
527
+ failReason = `Score ${scoringResult.overall} < threshold ${threshold}`;
948
528
  }
529
+ const report = mapToUnifiedReport(results, scoringResult);
949
530
  if (failOnLevel !== "none") {
950
- const severityLevels = { critical: 4, major: 3, minor: 2, any: 1 };
951
- const minSeverity = severityLevels[failOnLevel] || 4;
952
- let criticalCount = 0;
953
- let majorCount = 0;
954
- if (results[ToolName.PatternDetect]) {
955
- results[ToolName.PatternDetect].results.forEach((p) => {
956
- p.issues.forEach((i) => {
957
- if (i.severity === Severity.Critical) criticalCount++;
958
- if (i.severity === Severity.Major) majorCount++;
959
- });
960
- });
961
- }
962
- if (results[ToolName.ContextAnalyzer]) {
963
- results[ToolName.ContextAnalyzer].results.forEach((c) => {
964
- if (c.severity === Severity.Critical) criticalCount++;
965
- if (c.severity === Severity.Major) majorCount++;
966
- });
967
- }
968
- if (results[ToolName.NamingConsistency]) {
969
- results[ToolName.NamingConsistency].results.forEach((r) => {
970
- r.issues?.forEach((i) => {
971
- if (i.severity === Severity.Critical) criticalCount++;
972
- if (i.severity === Severity.Major) majorCount++;
973
- });
974
- });
975
- }
976
- if (minSeverity >= 4 && criticalCount > 0) {
531
+ if (failOnLevel === "critical" && report.summary.criticalIssues > 0) {
977
532
  shouldFail = true;
978
- failReason = `Found ${criticalCount} critical issues`;
979
- } else if (minSeverity >= 3 && criticalCount + majorCount > 0) {
533
+ failReason = `Found ${report.summary.criticalIssues} critical issues`;
534
+ } else if (failOnLevel === "major" && report.summary.criticalIssues + report.summary.majorIssues > 0) {
980
535
  shouldFail = true;
981
- failReason = `Found ${criticalCount} critical and ${majorCount} major issues`;
536
+ failReason = `Found ${report.summary.criticalIssues} critical and ${report.summary.majorIssues} major issues`;
982
537
  }
983
538
  }
984
539
  if (shouldFail) {
985
- console.log(chalk3.red("\n\u{1F6AB} PR BLOCKED: AI Readiness Check Failed"));
986
- console.log(chalk3.red(` Reason: ${failReason}`));
987
- console.log(chalk3.dim("\n Remediation steps:"));
988
- console.log(
989
- chalk3.dim(" 1. Run `aiready scan` locally to see detailed issues")
990
- );
991
- console.log(chalk3.dim(" 2. Fix the critical issues before merging"));
992
- console.log(
993
- chalk3.dim(
994
- " 3. Consider upgrading to Team plan for historical tracking: https://getaiready.dev/pricing"
995
- )
996
- );
540
+ console.log(chalk3.red(`
541
+ \u{1F6AB} PR BLOCKED: ${failReason}`));
997
542
  process.exit(1);
998
543
  } else {
999
- console.log(chalk3.green("\n\u2705 PR PASSED: AI Readiness Check"));
1000
- if (threshold) {
1001
- console.log(
1002
- chalk3.green(
1003
- ` Score: ${scoringResult.overall}/100 (threshold: ${threshold})`
1004
- )
1005
- );
1006
- }
1007
- console.log(
1008
- chalk3.dim(
1009
- "\n \u{1F4A1} Track historical trends: https://getaiready.dev \u2014 Team plan $99/mo"
1010
- )
1011
- );
544
+ console.log(chalk3.green("\n\u2705 PR PASSED"));
1012
545
  }
1013
546
  }
1014
547
  } catch (error) {
1015
548
  handleCLIError2(error, "Analysis");
1016
549
  }
1017
550
  }
1018
- var scanHelpText = `
1019
- EXAMPLES:
1020
- $ aiready scan # Analyze all tools
1021
- $ aiready scan --tools patterns,context # Skip consistency
1022
- $ aiready scan --profile agentic # Optimize for AI agent execution
1023
- $ aiready scan --profile security # Optimize for secure coding (testability)
1024
- $ aiready scan --compare-to prev-report.json # Compare trends against previous run
1025
- $ aiready scan --score --threshold 75 # CI/CD with threshold
1026
- $ aiready scan --ci --threshold 70 # GitHub Actions gatekeeper
1027
- $ aiready scan --ci --fail-on major # Fail on major+ issues
1028
- $ aiready scan --output json --output-file report.json
1029
- $ aiready scan --upload --api-key ar_... # Automatic platform upload
1030
- $ aiready scan --upload --server custom-url.com # Upload to custom platform
1031
-
1032
- PROFILES:
1033
- agentic: aiSignalClarity, grounding, testability
1034
- cost: patterns, context
1035
- security: consistency, testability
1036
- onboarding: context, consistency, grounding
1037
-
1038
- CI/CD INTEGRATION (Gatekeeper Mode):
1039
- Use --ci for GitHub Actions integration:
1040
- - Outputs GitHub Actions annotations for PR checks
1041
- - Fails with exit code 1 if threshold not met
1042
- - Shows clear "blocked" message with remediation steps
1043
-
1044
- Example GitHub Actions workflow:
1045
- - name: AI Readiness Check
1046
- run: aiready scan --ci --threshold 70
1047
- `;
551
+ var scanHelpText = `...`;
1048
552
 
1049
553
  // src/commands/patterns.ts
1050
554
  import chalk4 from "chalk";