@aiready/core 0.23.23 → 0.23.24

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/index.mjs CHANGED
@@ -52,13 +52,13 @@ import {
52
52
  getToolWeight,
53
53
  normalizeToolName,
54
54
  parseWeightString
55
- } from "./chunk-E55RNGGK.mjs";
55
+ } from "./chunk-G4Z45SMK.mjs";
56
56
  import {
57
57
  TypeScriptParser
58
58
  } from "./chunk-UTCRW3N7.mjs";
59
59
  import {
60
60
  PythonParser
61
- } from "./chunk-LZHO636W.mjs";
61
+ } from "./chunk-SM6INS52.mjs";
62
62
  import {
63
63
  JavaParser
64
64
  } from "./chunk-SWZOT67M.mjs";
@@ -391,14 +391,14 @@ async function scanFiles(options) {
391
391
  ignoreFromFile = [];
392
392
  }
393
393
  }
394
- const TEST_PATTERNS = [
394
+ const TEST_PATTERNS2 = [
395
395
  "**/*.test.*",
396
396
  "**/*.spec.*",
397
397
  "**/__tests__/**",
398
398
  "**/test/**",
399
399
  "**/tests/**"
400
400
  ];
401
- const baseExclude = options.includeTests ? DEFAULT_EXCLUDE.filter((p) => !TEST_PATTERNS.includes(p)) : DEFAULT_EXCLUDE;
401
+ const baseExclude = options.includeTests ? DEFAULT_EXCLUDE.filter((p) => !TEST_PATTERNS2.includes(p)) : DEFAULT_EXCLUDE;
402
402
  const finalExclude = [
403
403
  .../* @__PURE__ */ new Set([...exclude || [], ...ignoreFromFile, ...baseExclude])
404
404
  ];
@@ -460,14 +460,14 @@ async function scanEntries(options) {
460
460
  ignoreFromFile = [];
461
461
  }
462
462
  }
463
- const TEST_PATTERNS = [
463
+ const TEST_PATTERNS2 = [
464
464
  "**/*.test.*",
465
465
  "**/*.spec.*",
466
466
  "**/__tests__/**",
467
467
  "**/test/**",
468
468
  "**/tests/**"
469
469
  ];
470
- const baseExclude = includeTests ? DEFAULT_EXCLUDE.filter((p) => !TEST_PATTERNS.includes(p)) : DEFAULT_EXCLUDE;
470
+ const baseExclude = includeTests ? DEFAULT_EXCLUDE.filter((p) => !TEST_PATTERNS2.includes(p)) : DEFAULT_EXCLUDE;
471
471
  const finalExclude = [
472
472
  .../* @__PURE__ */ new Set([...exclude || [], ...ignoreFromFile, ...baseExclude])
473
473
  ];
@@ -522,7 +522,7 @@ function isSourceFile(filePath) {
522
522
  return ["ts", "tsx", "js", "jsx", "py", "java", "go", "rs"].includes(ext);
523
523
  }
524
524
 
525
- // src/utils/cli-helpers.ts
525
+ // src/utils/fs-utils.ts
526
526
  import {
527
527
  writeFileSync,
528
528
  mkdirSync,
@@ -531,23 +531,12 @@ import {
531
531
  statSync
532
532
  } from "fs";
533
533
  import { join as join2, dirname as dirname2, resolve as resolvePath } from "path";
534
- import chalk from "chalk";
535
534
  function ensureDir(path) {
536
535
  const dir = dirname2(path);
537
536
  if (!existsSync2(dir)) {
538
537
  mkdirSync(dir, { recursive: true });
539
538
  }
540
539
  }
541
- function normalizeSeverity(s) {
542
- if (!s) return null;
543
- const lower = s.toLowerCase();
544
- if (["critical", "high-risk", "blind-risk"].includes(lower))
545
- return "critical" /* Critical */;
546
- if (["major", "moderate-risk"].includes(lower)) return "major" /* Major */;
547
- if (["minor", "safe"].includes(lower)) return "minor" /* Minor */;
548
- if (lower === "info") return "info" /* Info */;
549
- return null;
550
- }
551
540
  function getFilesByPattern(dir, pattern) {
552
541
  if (!existsSync2(dir)) return [];
553
542
  try {
@@ -574,16 +563,6 @@ function resolveOutputPath(userPath, defaultFilename, workingDir = process.cwd()
574
563
  ensureDir(outputPath);
575
564
  return outputPath;
576
565
  }
577
- async function loadMergedConfig(directory, defaults, cliOptions) {
578
- const config = await loadConfig(directory);
579
- const mergedConfig = mergeConfigWithDefaults(config, defaults);
580
- const result = {
581
- ...mergedConfig,
582
- ...cliOptions,
583
- rootDir: directory
584
- };
585
- return result;
586
- }
587
566
  function handleJSONOutput(data, outputFile, successMessage) {
588
567
  if (outputFile) {
589
568
  ensureDir(outputFile);
@@ -593,63 +572,54 @@ function handleJSONOutput(data, outputFile, successMessage) {
593
572
  console.log(JSON.stringify(data, null, 2));
594
573
  }
595
574
  }
596
- function getTerminalDivider(color = chalk.cyan, maxWidth = 60) {
597
- const terminalWidth = process.stdout.columns || 80;
598
- const dividerWidth = Math.min(maxWidth, terminalWidth - 2);
599
- return color("\u2501".repeat(dividerWidth));
600
- }
601
- function printTerminalHeader(title, color = chalk.cyan) {
602
- const divider = getTerminalDivider(color);
603
- console.log(divider);
604
- console.log(chalk.bold.white(` ${title.toUpperCase()}`));
605
- console.log(divider + "\n");
606
- }
607
- function handleCLIError(error, commandName) {
608
- console.error(`\u274C ${commandName} failed:`, error);
609
- process.exit(1);
610
- }
611
- function getElapsedTime(startTime) {
612
- return ((Date.now() - startTime) / 1e3).toFixed(2);
613
- }
614
- function getScoreBar(val) {
615
- const clamped = Math.max(0, Math.min(100, val));
616
- return "\u2588".repeat(Math.round(clamped / 10)).padEnd(10, "\u2591");
617
- }
618
- function getSafetyIcon(rating) {
619
- switch (rating) {
620
- case "safe":
621
- return "\u2705";
622
- case "moderate-risk":
623
- return "\u26A0\uFE0F ";
624
- case "high-risk":
625
- return "\u{1F534}";
626
- case "blind-risk":
627
- return "\u{1F480}";
628
- default:
629
- return "\u2753";
575
+ function findLatestReport(dirPath) {
576
+ const aireadyDir = resolvePath(dirPath, ".aiready");
577
+ let files = getFilesByPattern(aireadyDir, /^aiready-report-.*\.json$/);
578
+ if (files.length === 0) {
579
+ files = getFilesByPattern(aireadyDir, /^aiready-scan-.*\.json$/);
630
580
  }
631
- }
632
- function emitProgress(processed, total, toolId, message, onProgress, throttleCount = 50) {
633
- if (!onProgress) return;
634
- if (processed % throttleCount === 0 || processed === total) {
635
- onProgress(processed, total, `${message} (${processed}/${total})`);
581
+ if (files.length === 0) {
582
+ return null;
636
583
  }
584
+ const sortedFiles = files.map((f) => ({
585
+ name: f,
586
+ path: resolvePath(aireadyDir, f),
587
+ mtime: statSync(resolvePath(aireadyDir, f)).mtime
588
+ })).sort((a, b) => b.mtime.getTime() - a.mtime.getTime());
589
+ return sortedFiles[0].path;
637
590
  }
638
- function getSeverityColor(severity, chalkInstance = chalk) {
639
- const normalized = normalizeSeverity(severity);
640
- switch (normalized) {
641
- case "critical" /* Critical */:
642
- return chalkInstance.red;
643
- case "major" /* Major */:
644
- return chalkInstance.yellow;
645
- case "minor" /* Minor */:
646
- return chalkInstance.green;
647
- case "info" /* Info */:
648
- return chalkInstance.blue;
649
- default:
650
- return chalkInstance.white;
591
+ function findLatestScanReport(scanReportsDir, reportFilePrefix) {
592
+ try {
593
+ const prefixRegex = new RegExp(`^${reportFilePrefix}\\d+\\.json$`);
594
+ const reportFiles = getFilesByPattern(scanReportsDir, prefixRegex);
595
+ if (reportFiles.length === 0) return null;
596
+ reportFiles.sort((a, b) => {
597
+ const idA = parseInt(a.match(/\d+/)?.[0] || "0", 10);
598
+ const idB = parseInt(b.match(/\d+/)?.[0] || "0", 10);
599
+ return idB - idA;
600
+ });
601
+ return join2(scanReportsDir, reportFiles[0]);
602
+ } catch {
603
+ console.error("Error while finding latest scan report");
604
+ return null;
651
605
  }
652
606
  }
607
+
608
+ // src/utils/terminal-utils.ts
609
+ import chalk2 from "chalk";
610
+
611
+ // src/utils/severity-utils.ts
612
+ import chalk from "chalk";
613
+ function normalizeSeverity(s) {
614
+ if (!s) return null;
615
+ const lower = s.toLowerCase();
616
+ if (["critical", "high-risk", "blind-risk"].includes(lower))
617
+ return "critical" /* Critical */;
618
+ if (["major", "moderate-risk"].includes(lower)) return "major" /* Major */;
619
+ if (["minor", "safe"].includes(lower)) return "minor" /* Minor */;
620
+ if (lower === "info") return "info" /* Info */;
621
+ return null;
622
+ }
653
623
  function getSeverityValue(s) {
654
624
  const normalized = normalizeSeverity(s);
655
625
  switch (normalized) {
@@ -668,23 +638,6 @@ function getSeverityValue(s) {
668
638
  function getSeverityLevel(s) {
669
639
  return getSeverityValue(s);
670
640
  }
671
- function getSeverityBadge(severity, chalkInstance = chalk) {
672
- const val = getSeverityValue(
673
- typeof severity === "string" ? severity : severity
674
- );
675
- switch (val) {
676
- case 4:
677
- return chalkInstance.bgRed.white.bold(" CRITICAL ");
678
- case 3:
679
- return chalkInstance.bgYellow.black.bold(" MAJOR ");
680
- case 2:
681
- return chalkInstance.bgBlue.white.bold(" MINOR ");
682
- case 1:
683
- return chalkInstance.bgCyan.black(" INFO ");
684
- default:
685
- return chalkInstance.bgCyan.black(" INFO ");
686
- }
687
- }
688
641
  function getSeverityEnum(s) {
689
642
  const level = getSeverityLevel(s);
690
643
  switch (level) {
@@ -698,39 +651,125 @@ function getSeverityEnum(s) {
698
651
  return "info";
699
652
  }
700
653
  }
701
- function findLatestReport(dirPath) {
702
- const aireadyDir = resolvePath(dirPath, ".aiready");
703
- let files = getFilesByPattern(aireadyDir, /^aiready-report-.*\.json$/);
704
- if (files.length === 0) {
705
- files = getFilesByPattern(aireadyDir, /^aiready-scan-.*\.json$/);
654
+ function getSeverityColor(severity, chalkInstance = chalk) {
655
+ const normalized = normalizeSeverity(severity);
656
+ switch (normalized) {
657
+ case "critical" /* Critical */:
658
+ return chalkInstance.red;
659
+ case "major" /* Major */:
660
+ return chalkInstance.yellow;
661
+ case "minor" /* Minor */:
662
+ return chalkInstance.green;
663
+ case "info" /* Info */:
664
+ return chalkInstance.blue;
665
+ default:
666
+ return chalkInstance.white;
706
667
  }
707
- if (files.length === 0) {
708
- return null;
668
+ }
669
+ function getSeverityBadge(severity, chalkInstance = chalk) {
670
+ const normalized = normalizeSeverity(severity);
671
+ switch (normalized) {
672
+ case "critical" /* Critical */:
673
+ return chalkInstance.bgRed.white.bold(" CRITICAL ");
674
+ case "major" /* Major */:
675
+ return chalkInstance.bgYellow.black.bold(" MAJOR ");
676
+ case "minor" /* Minor */:
677
+ return chalkInstance.bgGreen.black.bold(" MINOR ");
678
+ case "info" /* Info */:
679
+ return chalkInstance.bgBlue.white.bold(" INFO ");
680
+ default:
681
+ return chalkInstance.bgCyan.black(" UNKNOWN ");
709
682
  }
710
- const sortedFiles = files.map((f) => ({
711
- name: f,
712
- path: resolvePath(aireadyDir, f),
713
- mtime: statSync(resolvePath(aireadyDir, f)).mtime
714
- })).sort((a, b) => b.mtime.getTime() - a.mtime.getTime());
715
- return sortedFiles[0].path;
716
683
  }
717
- function findLatestScanReport(scanReportsDir, reportFilePrefix) {
718
- try {
719
- const prefixRegex = new RegExp(`^${reportFilePrefix}\\d+\\.json$`);
720
- const reportFiles = getFilesByPattern(scanReportsDir, prefixRegex);
721
- if (reportFiles.length === 0) return null;
722
- reportFiles.sort((a, b) => {
723
- const idA = parseInt(a.match(/\d+/)?.[0] || "0", 10);
724
- const idB = parseInt(b.match(/\d+/)?.[0] || "0", 10);
725
- return idB - idA;
726
- });
727
- return join2(scanReportsDir, reportFiles[0]);
728
- } catch {
729
- console.error("Error while finding latest scan report");
730
- return null;
684
+ function getSeverityLabel(severity) {
685
+ const labels = {
686
+ ["critical" /* Critical */]: "\u{1F534} CRITICAL",
687
+ ["major" /* Major */]: "\u{1F7E1} MAJOR",
688
+ ["minor" /* Minor */]: "\u{1F535} MINOR",
689
+ ["info" /* Info */]: "\u2139\uFE0F INFO"
690
+ };
691
+ return labels[severity];
692
+ }
693
+ function filterBySeverity(items, minSeverity) {
694
+ const severityOrder = [
695
+ "info" /* Info */,
696
+ "minor" /* Minor */,
697
+ "major" /* Major */,
698
+ "critical" /* Critical */
699
+ ];
700
+ const minIndex = severityOrder.indexOf(minSeverity);
701
+ if (minIndex === -1) return items;
702
+ return items.filter((item) => {
703
+ const itemIndex = severityOrder.indexOf(item.severity);
704
+ return itemIndex >= minIndex;
705
+ });
706
+ }
707
+
708
+ // src/utils/terminal-utils.ts
709
+ function getSafetyIcon(rating) {
710
+ switch (rating) {
711
+ case "safe":
712
+ return "\u2705";
713
+ case "moderate-risk":
714
+ return "\u26A0\uFE0F ";
715
+ case "high-risk":
716
+ return "\u{1F534}";
717
+ case "blind-risk":
718
+ return "\u{1F480}";
719
+ default:
720
+ return "\u2753";
731
721
  }
732
722
  }
733
723
 
724
+ // src/utils/terminal-ui.ts
725
+ import chalk3 from "chalk";
726
+ function printTerminalHeader(title, colorFn = chalk3.cyan.bold, width = 80) {
727
+ const divider = "\u2501".repeat(width);
728
+ console.log(colorFn(`
729
+ ${divider}`));
730
+ console.log(colorFn(` ${title.toUpperCase()}`));
731
+ console.log(colorFn(`${divider}
732
+ `));
733
+ }
734
+ function getTerminalDivider(colorFn = chalk3.gray, width = 80) {
735
+ return colorFn("\u2501".repeat(width));
736
+ }
737
+ function getScoreBar(score, width = 10) {
738
+ const normalized = Math.max(0, Math.min(100, score));
739
+ const solid = Math.round(normalized / 100 * width);
740
+ const empty = width - solid;
741
+ return "\u2588".repeat(solid) + "\u2591".repeat(empty);
742
+ }
743
+
744
+ // src/utils/progress-utils.ts
745
+ function emitProgress(processed, total, toolId, message, onProgress, throttleCount = 50) {
746
+ if (!onProgress) return;
747
+ if (processed % throttleCount === 0 || processed === total) {
748
+ onProgress(processed, total, `${message} (${processed}/${total})`);
749
+ }
750
+ }
751
+
752
+ // src/utils/cli-utils.ts
753
+ function getElapsedTime(startTime) {
754
+ return ((Date.now() - startTime) / 1e3).toFixed(2);
755
+ }
756
+ function handleCLIError(error, commandName) {
757
+ console.error(`\u274C ${commandName} failed:`, error);
758
+ process.exit(1);
759
+ }
760
+
761
+ // src/utils/cli-helpers.ts
762
+ async function loadMergedConfig(directory, defaults, cliOptions) {
763
+ const config = await loadConfig(directory);
764
+ const mergedConfig = mergeConfigWithDefaults(config, defaults);
765
+ const result = {
766
+ ...mergedConfig,
767
+ ...cliOptions,
768
+ rootDir: directory
769
+ };
770
+ return result;
771
+ }
772
+
734
773
  // src/utils/cli-action-helpers.ts
735
774
  import { resolve as resolvePath2 } from "path";
736
775
  function getReportTimestamp() {
@@ -873,7 +912,7 @@ var ParserFactory = class _ParserFactory {
873
912
  return new TypeScriptParser2();
874
913
  });
875
914
  this.registerLazyParser("python" /* Python */, async () => {
876
- const { PythonParser: PythonParser2 } = await import("./python-parser-FNFK2473.mjs");
915
+ const { PythonParser: PythonParser2 } = await import("./python-parser-SJ3LFZFJ.mjs");
877
916
  return new PythonParser2();
878
917
  });
879
918
  this.registerLazyParser("java" /* Java */, async () => {
@@ -1318,6 +1357,9 @@ function mergeConfigWithDefaults(userConfig, defaults) {
1318
1357
  if (userConfig.scan.include) mergedConfig.include = userConfig.scan.include;
1319
1358
  if (userConfig.scan.exclude) mergedConfig.exclude = userConfig.scan.exclude;
1320
1359
  }
1360
+ if (userConfig.threshold !== void 0)
1361
+ mergedConfig.threshold = userConfig.threshold;
1362
+ if (userConfig.failOn !== void 0) mergedConfig.failOn = userConfig.failOn;
1321
1363
  if (userConfig.tools) {
1322
1364
  if (!mergedConfig.toolConfigs) mergedConfig.toolConfigs = {};
1323
1365
  for (const [toolName, toolConfig] of Object.entries(userConfig.tools)) {
@@ -1335,114 +1377,148 @@ function mergeConfigWithDefaults(userConfig, defaults) {
1335
1377
  return mergedConfig;
1336
1378
  }
1337
1379
 
1380
+ // src/utils/report-styles.ts
1381
+ var REPORT_STYLES = `
1382
+ body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; line-height: 1.6; color: #333; max-width: 1200px; margin: 0 auto; padding: 2rem; background-color: #f9f9f9; }
1383
+ h1, h2, h3 { color: #1a1a1a; border-bottom: 2px solid #eaeaea; padding-bottom: 0.5rem; }
1384
+ .card { background: white; padding: 1.5rem; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.05); margin-bottom: 2rem; border: 1px solid #eaeaea; }
1385
+ .stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 1rem; margin-bottom: 2rem; }
1386
+ .stat-card { background: #fff; padding: 1rem; border-radius: 6px; text-align: center; border: 1px solid #eaeaea; }
1387
+ .stat-value { font-size: 1.8rem; font-weight: bold; color: #2563eb; }
1388
+ .stat-label { font-size: 0.875rem; color: #666; text-transform: uppercase; }
1389
+ .hero { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 30px; border-radius: 8px; margin-bottom: 30px; }
1390
+ .hero h1 { border: none; color: white; margin: 0; }
1391
+ .hero p { margin: 10px 0 0 0; opacity: 0.9; }
1392
+ table { width: 100%; border-collapse: collapse; margin-top: 1rem; background: white; border-radius: 4px; overflow: hidden; }
1393
+ th, td { text-align: left; padding: 0.875rem 1rem; border-bottom: 1px solid #eaeaea; }
1394
+ th { background-color: #f8fafc; font-weight: 600; color: #475569; }
1395
+ tr:last-child td { border-bottom: none; }
1396
+ .critical { color: #dc2626; font-weight: bold; }
1397
+ .major { color: #ea580c; font-weight: bold; }
1398
+ .minor { color: #2563eb; }
1399
+ .issue-critical { color: #dc2626; font-weight: bold; text-transform: uppercase; }
1400
+ .issue-major { color: #ea580c; font-weight: bold; text-transform: uppercase; }
1401
+ .issue-minor { color: #2563eb; font-weight: bold; text-transform: uppercase; }
1402
+ code { background: #f1f5f9; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.875rem; color: #334155; }
1403
+ .footer { margin-top: 4rem; text-align: center; color: #94a3b8; font-size: 0.875rem; }
1404
+ a { color: #2563eb; text-decoration: none; }
1405
+ a:hover { text-decoration: underline; }
1406
+ `;
1407
+
1338
1408
  // src/utils/report-formatters.ts
1339
- function generateReportHead(title) {
1409
+ function tag(name, content = "", attrs = {}) {
1410
+ const attrStr = Object.entries(attrs).map(([k, v]) => ` ${k}="${v}"`).join("");
1411
+ return `<${name}${attrStr}>${content}</${name}>`;
1412
+ }
1413
+ function generateReportHead(title, styles = REPORT_STYLES) {
1340
1414
  return `<!DOCTYPE html>
1341
1415
  <html lang="en">
1342
1416
  <head>
1343
1417
  <meta charset="UTF-8">
1344
1418
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
1345
- <title>${title}</title>
1346
- <style>
1347
- body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; line-height: 1.6; color: #333; max-width: 1200px; margin: 0 auto; padding: 2rem; background-color: #f9f9f9; }
1348
- h1, h2, h3 { color: #1a1a1a; border-bottom: 2px solid #eaeaea; padding-bottom: 0.5rem; }
1349
- .card { background: white; padding: 1.5rem; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.05); margin-bottom: 2rem; border: 1px solid #eaeaea; }
1350
- .stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 1rem; margin-bottom: 2rem; }
1351
- .stat-card { background: #fff; padding: 1rem; border-radius: 6px; text-align: center; border: 1px solid #eaeaea; }
1352
- .stat-value { font-size: 1.8rem; font-weight: bold; color: #2563eb; }
1353
- .stat-label { font-size: 0.875rem; color: #666; text-transform: uppercase; }
1354
- .hero { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 30px; border-radius: 8px; margin-bottom: 30px; }
1355
- .hero h1 { border: none; color: white; margin: 0; }
1356
- .hero p { margin: 10px 0 0 0; opacity: 0.9; }
1357
- table { width: 100%; border-collapse: collapse; margin-top: 1rem; background: white; border-radius: 4px; overflow: hidden; }
1358
- th, td { text-align: left; padding: 0.875rem 1rem; border-bottom: 1px solid #eaeaea; }
1359
- th { background-color: #f8fafc; font-weight: 600; color: #475569; }
1360
- tr:last-child td { border-bottom: none; }
1361
- .critical { color: #dc2626; font-weight: bold; }
1362
- .major { color: #ea580c; font-weight: bold; }
1363
- .minor { color: #2563eb; }
1364
- .issue-critical { color: #dc2626; font-weight: bold; text-transform: uppercase; }
1365
- .issue-major { color: #ea580c; font-weight: bold; text-transform: uppercase; }
1366
- .issue-minor { color: #2563eb; font-weight: bold; text-transform: uppercase; }
1367
- code { background: #f1f5f9; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.875rem; color: #334155; }
1368
- .footer { margin-top: 4rem; text-align: center; color: #94a3b8; font-size: 0.875rem; }
1369
- a { color: #2563eb; text-decoration: none; }
1370
- a:hover { text-decoration: underline; }
1371
- </style>
1419
+ ${tag("title", title)}
1420
+ ${tag("style", styles)}
1372
1421
  </head>`;
1373
1422
  }
1374
1423
  function generateReportHero(title, subtitle) {
1375
- const subtitleHtml = subtitle ? `<p>${subtitle}</p>` : "";
1376
- return `<div class="hero">
1377
- <h1>${title}</h1>
1378
- ${subtitleHtml}
1379
- </div>`;
1424
+ return tag("div", tag("h1", title) + (subtitle ? tag("p", subtitle) : ""), {
1425
+ class: "hero"
1426
+ });
1380
1427
  }
1381
1428
  function generateStatCards(cards) {
1382
1429
  const cardsHtml = cards.map(
1383
- (card) => `
1384
- <div class="stat-card">
1385
- <div class="stat-value"${card.color ? ` style="color: ${card.color}"` : ""}>${card.value}</div>
1386
- <div class="stat-label">${card.label}</div>
1387
- </div>`
1430
+ (c) => tag(
1431
+ "div",
1432
+ tag("div", String(c.value), {
1433
+ class: "stat-value",
1434
+ ...c.color ? { style: `color: ${c.color}` } : {}
1435
+ }) + tag("div", c.label, { class: "stat-label" }),
1436
+ { class: "stat-card" }
1437
+ )
1388
1438
  ).join("");
1389
- return `<div class="stats">${cardsHtml}</div>`;
1390
- }
1391
- function generateScoreCard(value, label) {
1392
- return `<div class="stat-card" style="margin-bottom: 2rem;">
1393
- <div class="stat-label">${label}</div>
1394
- <div class="stat-value">${value}</div>
1395
- </div>`;
1439
+ return tag("div", cardsHtml, { class: "stats" });
1396
1440
  }
1397
1441
  function generateTable(config) {
1398
- const headersHtml = config.headers.map((h) => `<th>${h}</th>`).join("");
1399
- const rowsHtml = config.rows.map((row) => `<tr>${row.map((cell) => `<td>${cell}</td>`).join("")}</tr>`).join("");
1400
- return `<table>
1401
- <thead><tr>${headersHtml}</tr></thead>
1402
- <tbody>${rowsHtml}</tbody>
1403
- </table>`;
1404
- }
1405
- function generateIssueSummary(critical, major, minor, potentialSavings) {
1406
- const savingsHtml = potentialSavings ? `<p><strong>Potential Savings:</strong> ${potentialSavings.toLocaleString()} tokens</p>` : "";
1407
- return `<div class="card" style="margin-bottom: 30px;">
1408
- <h2>\u26A0\uFE0F Issues Summary</h2>
1409
- <p>
1410
- <span class="critical">\u{1F534} Critical: ${critical}</span> &nbsp;
1411
- <span class="major">\u{1F7E1} Major: ${major}</span> &nbsp;
1412
- <span class="minor">\u{1F535} Minor: ${minor}</span>
1413
- </p>
1414
- ${savingsHtml}
1415
- </div>`;
1442
+ const head = tag(
1443
+ "thead",
1444
+ tag("tr", config.headers.map((h) => tag("th", h)).join(""))
1445
+ );
1446
+ const body = tag(
1447
+ "tbody",
1448
+ config.rows.map(
1449
+ (row) => tag("tr", row.map((cell) => tag("td", cell)).join(""))
1450
+ ).join("")
1451
+ );
1452
+ return tag("table", head + body);
1453
+ }
1454
+ function generateIssueSummary(crit, maj, min, savings) {
1455
+ const details = [
1456
+ tag("span", `\u{1F534} Critical: ${crit}`, { class: "critical" }),
1457
+ tag("span", `\u{1F7E1} Major: ${maj}`, { class: "major" }),
1458
+ tag("span", `\u{1F535} Minor: ${min}`, { class: "minor" })
1459
+ ].join(" &nbsp; ");
1460
+ const savingsHtml = savings ? tag(
1461
+ "p",
1462
+ tag("strong", "Potential Savings: ") + savings.toLocaleString() + " tokens"
1463
+ ) : "";
1464
+ return tag(
1465
+ "div",
1466
+ tag("h2", "\u26A0\uFE0F Issues Summary") + tag("p", details) + savingsHtml,
1467
+ { class: "card", style: "margin-bottom: 30px;" }
1468
+ );
1416
1469
  }
1417
1470
  function generateReportFooter(options) {
1418
- const versionText = options.version ? ` v${options.version}` : "";
1471
+ const version = options.version ? ` v${options.version}` : "";
1419
1472
  const links = [];
1420
- if (options.packageUrl) {
1421
- links.push(`<a href="${options.packageUrl}">Star us on GitHub</a>`);
1422
- }
1423
- if (options.bugUrl) {
1424
- links.push(`<a href="${options.bugUrl}">Report it here</a>`);
1425
- }
1426
- const linksHtml = links.length ? links.map((l) => `<p>Like AIReady? ${l}</p>`).join("\n ") : "";
1427
- return `<div class="footer">
1428
- <p>Generated by <strong>@aiready/${options.packageName}</strong>${versionText}</p>
1429
- ${linksHtml}
1430
- </div>`;
1473
+ if (options.packageUrl)
1474
+ links.push(
1475
+ tag(
1476
+ "p",
1477
+ `Like AIReady? ${tag("a", "Star us on GitHub", { href: options.packageUrl })}`
1478
+ )
1479
+ );
1480
+ if (options.bugUrl)
1481
+ links.push(
1482
+ tag(
1483
+ "p",
1484
+ `Like AIReady? ${tag("a", "Report it here", { href: options.bugUrl })}`
1485
+ )
1486
+ );
1487
+ return tag(
1488
+ "div",
1489
+ tag(
1490
+ "p",
1491
+ `Generated by ${tag("strong", "@aiready/" + options.packageName)}` + version
1492
+ ) + links.join(""),
1493
+ { class: "footer" }
1494
+ );
1431
1495
  }
1432
1496
  function wrapInCard(content, title) {
1433
- const titleHtml = title ? `<h2>${title}</h2>` : "";
1434
- return `<div class="card">
1435
- ${titleHtml}
1436
- ${content}
1437
- </div>`;
1438
- }
1439
- function generateCompleteReport(options, bodyContent) {
1440
- return `${generateReportHead(options.title)}
1441
- <body>
1442
- ${bodyContent}
1443
- ${generateReportFooter(options)}
1444
- </body>
1445
- </html>`;
1497
+ return tag("div", (title ? tag("h2", title) : "") + content, {
1498
+ class: "card"
1499
+ });
1500
+ }
1501
+ function generateCompleteReport(options, body) {
1502
+ return generateReportHead(options.title) + tag("body", body + generateReportFooter(options));
1503
+ }
1504
+ function generateStandardHtmlReport(options, stats, sections, score) {
1505
+ const hero = generateReportHero(
1506
+ `${options.emoji || "\u{1F50D}"} AIReady ${options.title}`,
1507
+ `Generated on ${(/* @__PURE__ */ new Date()).toLocaleString()}`
1508
+ );
1509
+ const scoreCard = score ? tag(
1510
+ "div",
1511
+ tag("div", String(score.value), { class: "score-value" }) + tag("div", score.label, { class: "score-label" }),
1512
+ { class: "score-card" }
1513
+ ) : "";
1514
+ const statsCards = generateStatCards(stats);
1515
+ const bodyContent = `
1516
+ ${hero}
1517
+ ${scoreCard}
1518
+ ${statsCards}
1519
+ ${sections.map((s) => wrapInCard(s.content, s.title)).join("\n")}
1520
+ `;
1521
+ return generateCompleteReport(options, bodyContent);
1446
1522
  }
1447
1523
 
1448
1524
  // src/utils/scoring-helpers.ts
@@ -1539,12 +1615,12 @@ function calculateHeuristicConfidence(similarity, tokens, lines) {
1539
1615
  }
1540
1616
 
1541
1617
  // src/utils/cli-factory.ts
1542
- import chalk2 from "chalk";
1618
+ import chalk4 from "chalk";
1543
1619
  function createStandardProgressCallback(toolName) {
1544
1620
  return (processed, total, message) => {
1545
1621
  const percent = Math.round(processed / Math.max(1, total) * 100);
1546
1622
  process.stdout.write(
1547
- `\r\x1B[K [${toolName}] ${chalk2.cyan(`${percent}%`)} ${message}`
1623
+ `\r\x1B[K [${toolName}] ${chalk4.cyan(`${percent}%`)} ${message}`
1548
1624
  );
1549
1625
  if (processed === total) {
1550
1626
  process.stdout.write("\n");
@@ -1552,12 +1628,12 @@ function createStandardProgressCallback(toolName) {
1552
1628
  };
1553
1629
  }
1554
1630
  function formatStandardCliResult(toolName, score, issuesCount) {
1555
- const scoreColor = score >= 75 ? chalk2.green : score >= 50 ? chalk2.yellow : chalk2.red;
1631
+ const scoreColor = score >= 75 ? chalk4.green : score >= 50 ? chalk4.yellow : chalk4.red;
1556
1632
  console.log(`
1557
- ${chalk2.bold(toolName.toUpperCase())} Analysis Complete`);
1633
+ ${chalk4.bold(toolName.toUpperCase())} Analysis Complete`);
1558
1634
  console.log(` Overall Score: ${scoreColor(score)}/100`);
1559
1635
  console.log(
1560
- ` Issues Found: ${issuesCount > 0 ? chalk2.red(issuesCount) : chalk2.green("None")}`
1636
+ ` Issues Found: ${issuesCount > 0 ? chalk4.red(issuesCount) : chalk4.green("None")}`
1561
1637
  );
1562
1638
  }
1563
1639
  async function runStandardCliAction(toolName, action) {
@@ -1566,7 +1642,7 @@ async function runStandardCliAction(toolName, action) {
1566
1642
  formatStandardCliResult(toolName, score, issuesCount);
1567
1643
  } catch (error) {
1568
1644
  console.error(
1569
- chalk2.red(`
1645
+ chalk4.red(`
1570
1646
  \u274C [${toolName}] critical error: ${error.message}`)
1571
1647
  );
1572
1648
  process.exit(1);
@@ -1574,13 +1650,13 @@ async function runStandardCliAction(toolName, action) {
1574
1650
  }
1575
1651
 
1576
1652
  // src/utils/reporting.ts
1577
- import chalk3 from "chalk";
1653
+ import chalk5 from "chalk";
1578
1654
  function getScoreColor(score) {
1579
- if (score >= 85) return chalk3.green;
1580
- if (score >= 70) return chalk3.cyan;
1581
- if (score >= 50) return chalk3.yellow;
1582
- if (score >= 30) return chalk3.red;
1583
- return chalk3.bgRed.white;
1655
+ if (score >= 85) return chalk5.green;
1656
+ if (score >= 70) return chalk5.cyan;
1657
+ if (score >= 50) return chalk5.yellow;
1658
+ if (score >= 30) return chalk5.red;
1659
+ return chalk5.bgRed.white;
1584
1660
  }
1585
1661
  function displayStandardConsoleReport(data) {
1586
1662
  const {
@@ -1595,27 +1671,27 @@ function displayStandardConsoleReport(data) {
1595
1671
  noIssuesMessage = "\u2728 No issues found!",
1596
1672
  safetyRating
1597
1673
  } = data;
1598
- console.log(chalk3.bold(`
1674
+ console.log(chalk5.bold(`
1599
1675
  ${title}
1600
1676
  `));
1601
1677
  if (safetyRating) {
1602
1678
  if (safetyRating === "blind-risk" || safetyRating === "\u{1F480} blind-risk") {
1603
1679
  console.log(
1604
- chalk3.bgRed.white.bold(
1680
+ chalk5.bgRed.white.bold(
1605
1681
  " \u{1F480} BLIND RISK \u2014 NO TESTS DETECTED. AI-GENERATED CHANGES CANNOT BE VERIFIED. "
1606
1682
  )
1607
1683
  );
1608
1684
  console.log();
1609
1685
  } else if (safetyRating === "high-risk" || safetyRating === "\u{1F534} high-risk") {
1610
1686
  console.log(
1611
- chalk3.red.bold(
1687
+ chalk5.red.bold(
1612
1688
  ` \u{1F534} HIGH RISK \u2014 Insufficient test coverage. AI changes may introduce silent bugs.`
1613
1689
  )
1614
1690
  );
1615
1691
  console.log();
1616
1692
  }
1617
1693
  }
1618
- const safetyColor = safetyRating ? getSeverityColor(safetyRating, chalk3) : getScoreColor(score);
1694
+ const safetyColor = safetyRating ? getSeverityColor(safetyRating, chalk5) : getScoreColor(score);
1619
1695
  if (safetyRating) {
1620
1696
  console.log(
1621
1697
  `AI Change Safety: ${safetyColor(`${getSafetyIcon(safetyRating)} ${safetyRating.toUpperCase()}`)}`
@@ -1625,12 +1701,12 @@ ${title}
1625
1701
  `Score: ${getScoreColor(score)(score + "/100")} (${rating.toUpperCase()})`
1626
1702
  );
1627
1703
  if (stats.length > 0) {
1628
- const statsStr = stats.map((s) => `${s.label}: ${chalk3.cyan(s.value)}`).join(" ");
1704
+ const statsStr = stats.map((s) => `${s.label}: ${chalk5.cyan(s.value)}`).join(" ");
1629
1705
  console.log(statsStr);
1630
1706
  }
1631
- console.log(`Analysis Time: ${chalk3.gray(elapsedTime + "s")}
1707
+ console.log(`Analysis Time: ${chalk5.gray(elapsedTime + "s")}
1632
1708
  `);
1633
- console.log(chalk3.bold("\u{1F4D0} Dimension Scores\n"));
1709
+ console.log(chalk5.bold("\u{1F4D0} Dimension Scores\n"));
1634
1710
  for (const dim of dimensions) {
1635
1711
  const color = getScoreColor(dim.value);
1636
1712
  console.log(
@@ -1638,24 +1714,24 @@ ${title}
1638
1714
  );
1639
1715
  }
1640
1716
  if (issues.length > 0) {
1641
- console.log(chalk3.bold("\n\u26A0\uFE0F Issues\n"));
1717
+ console.log(chalk5.bold("\n\u26A0\uFE0F Issues\n"));
1642
1718
  for (const issue of issues) {
1643
- const sev = getSeverityColor(issue.severity, chalk3);
1719
+ const sev = getSeverityColor(issue.severity, chalk5);
1644
1720
  console.log(`${sev(issue.severity.toUpperCase())} ${issue.message}`);
1645
1721
  if (issue.suggestion) {
1646
1722
  console.log(
1647
- ` ${chalk3.dim("\u2192")} ${chalk3.italic(issue.suggestion)}`
1723
+ ` ${chalk5.dim("\u2192")} ${chalk5.italic(issue.suggestion)}`
1648
1724
  );
1649
1725
  }
1650
1726
  console.log();
1651
1727
  }
1652
1728
  } else {
1653
- console.log(chalk3.green(`
1729
+ console.log(chalk5.green(`
1654
1730
  ${noIssuesMessage}
1655
1731
  `));
1656
1732
  }
1657
1733
  if (recommendations.length > 0) {
1658
- console.log(chalk3.bold("\u{1F4A1} Recommendations\n"));
1734
+ console.log(chalk5.bold("\u{1F4A1} Recommendations\n"));
1659
1735
  recommendations.forEach((rec, i) => {
1660
1736
  console.log(`${i + 1}. ${rec}`);
1661
1737
  });
@@ -2636,7 +2712,17 @@ function calculateAiSignalClarity(params) {
2636
2712
  rating: "minimal",
2637
2713
  signals: [],
2638
2714
  topRisk: "No symbols to analyze",
2639
- recommendations: []
2715
+ recommendations: [],
2716
+ dimensions: {
2717
+ overloadingScore: 100,
2718
+ magicLiteralScore: 100,
2719
+ booleanTrapScore: 100,
2720
+ implicitSideEffectScore: 100,
2721
+ deepCallbackScore: 100,
2722
+ ambiguityScore: 100,
2723
+ documentationScore: 100,
2724
+ sizeScore: 100
2725
+ }
2640
2726
  };
2641
2727
  }
2642
2728
  const overloadRatio = overloadedSymbols / Math.max(1, totalSymbols);
@@ -2749,7 +2835,23 @@ function calculateAiSignalClarity(params) {
2749
2835
  rating,
2750
2836
  signals: signals.filter((s) => s.count > 0),
2751
2837
  topRisk,
2752
- recommendations
2838
+ recommendations,
2839
+ dimensions: {
2840
+ overloadingScore: Math.max(0, 100 - overloadSignal.riskContribution * 5),
2841
+ magicLiteralScore: Math.max(0, 100 - magicSignal.riskContribution * 6.6),
2842
+ booleanTrapScore: Math.max(0, 100 - trapSignal.riskContribution * 6.6),
2843
+ implicitSideEffectScore: Math.max(
2844
+ 0,
2845
+ 100 - sideEffectSignal.riskContribution * 10
2846
+ ),
2847
+ deepCallbackScore: Math.max(
2848
+ 0,
2849
+ 100 - callbackSignal.riskContribution * 10
2850
+ ),
2851
+ ambiguityScore: Math.max(0, 100 - ambiguousSignal.riskContribution * 20),
2852
+ documentationScore: Math.max(0, 100 - undocSignal.riskContribution * 20),
2853
+ sizeScore: Math.max(0, 100 - largeFileSignal.riskContribution * 4)
2854
+ }
2753
2855
  };
2754
2856
  }
2755
2857
 
@@ -3352,6 +3454,104 @@ function emitIssuesAsAnnotations(issues) {
3352
3454
  });
3353
3455
  });
3354
3456
  }
3457
+
3458
+ // src/utils/analysis-orchestrator.ts
3459
+ async function runBatchAnalysis(items, label, toolId, onProgress, processFn, onResult) {
3460
+ let processed = 0;
3461
+ for (const item of items) {
3462
+ processed++;
3463
+ emitProgress(processed, items.length, toolId, label, onProgress);
3464
+ const result = await processFn(item);
3465
+ onResult(result);
3466
+ }
3467
+ }
3468
+
3469
+ // src/utils/spoke-cli-helpers.ts
3470
+ import chalk6 from "chalk";
3471
+ async function executeSpokeCli(name, description, options, analyzeFn) {
3472
+ console.log(chalk6.cyan(`Analyzing ${description.toLowerCase()}...`));
3473
+ try {
3474
+ const report = await analyzeFn({
3475
+ rootDir: process.cwd(),
3476
+ ...options
3477
+ });
3478
+ console.log(chalk6.bold(`
3479
+ ${name} Analysis Results:`));
3480
+ console.log(
3481
+ `Rating: ${report.summary.rating.toUpperCase()} (Score: ${report.summary.score})`
3482
+ );
3483
+ if (report.issues && report.issues.length > 0) {
3484
+ console.log(chalk6.red(`
3485
+ Found ${report.issues.length} issues.`));
3486
+ } else {
3487
+ console.log(chalk6.green("\nNo issues detected."));
3488
+ }
3489
+ return report;
3490
+ } catch (err) {
3491
+ console.error(chalk6.red(`Error during ${name} analysis: ${err.message}`));
3492
+ process.exit(1);
3493
+ }
3494
+ }
3495
+
3496
+ // src/utils/test-utils.ts
3497
+ import { readFileSync as readFileSync3, existsSync as existsSync5 } from "fs";
3498
+ import { join as join5 } from "path";
3499
+ var TEST_PATTERNS = [
3500
+ /\.(test|spec)\.(ts|tsx|js|jsx)$/,
3501
+ /_test\.go$/,
3502
+ /test_.*\.py$/,
3503
+ /.*_test\.py$/,
3504
+ /.*Test\.java$/,
3505
+ /.*Tests\.cs$/,
3506
+ /__tests__\//,
3507
+ /\/tests?\//,
3508
+ /\/e2e\//,
3509
+ /\/fixtures\//
3510
+ ];
3511
+ function isTestFile(filePath, extraPatterns) {
3512
+ if (TEST_PATTERNS.some((p) => p.test(filePath))) return true;
3513
+ if (extraPatterns) return extraPatterns.some((p) => filePath.includes(p));
3514
+ return false;
3515
+ }
3516
+ function detectTestFramework(rootDir) {
3517
+ const manifests = [
3518
+ {
3519
+ file: "package.json",
3520
+ deps: [
3521
+ "jest",
3522
+ "vitest",
3523
+ "mocha",
3524
+ "jasmine",
3525
+ "ava",
3526
+ "tap",
3527
+ "playwright",
3528
+ "cypress"
3529
+ ]
3530
+ },
3531
+ { file: "requirements.txt", deps: ["pytest", "unittest", "nose"] },
3532
+ { file: "pyproject.toml", deps: ["pytest"] },
3533
+ { file: "pom.xml", deps: ["junit", "testng"] },
3534
+ { file: "build.gradle", deps: ["junit", "testng"] },
3535
+ { file: "go.mod", deps: ["testing"] }
3536
+ // go testing is built-in
3537
+ ];
3538
+ for (const m of manifests) {
3539
+ const p = join5(rootDir, m.file);
3540
+ if (existsSync5(p)) {
3541
+ if (m.file === "go.mod") return true;
3542
+ try {
3543
+ const content = readFileSync3(p, "utf-8");
3544
+ if (m.deps.some((d) => content.includes(d))) return true;
3545
+ } catch {
3546
+ }
3547
+ }
3548
+ }
3549
+ return false;
3550
+ }
3551
+ function isBuildArtifact(filePath) {
3552
+ const lower = filePath.toLowerCase();
3553
+ return lower.includes("/node_modules/") || lower.includes("/dist/") || lower.includes("/build/") || lower.includes("/out/") || lower.includes("/.next/") || lower.includes("/target/") || lower.includes("/bin/");
3554
+ }
3355
3555
  export {
3356
3556
  AIReadyConfigSchema,
3357
3557
  AnalysisResultSchema,
@@ -3396,6 +3596,7 @@ export {
3396
3596
  SeveritySchema,
3397
3597
  SpokeOutputSchema,
3398
3598
  SpokeSummarySchema,
3599
+ TEST_PATTERNS,
3399
3600
  TOOL_NAME_MAP,
3400
3601
  ToolName,
3401
3602
  ToolNameSchema,
@@ -3435,14 +3636,18 @@ export {
3435
3636
  clearHistory,
3436
3637
  createProvider,
3437
3638
  createStandardProgressCallback,
3639
+ detectTestFramework,
3438
3640
  displayStandardConsoleReport,
3439
3641
  emitAnnotation,
3440
3642
  emitIssuesAsAnnotations,
3441
3643
  emitProgress,
3644
+ ensureDir,
3442
3645
  estimateCostFromBudget,
3443
3646
  estimateTokens,
3647
+ executeSpokeCli,
3444
3648
  exportHistory,
3445
3649
  extractCodeBlocks,
3650
+ filterBySeverity,
3446
3651
  findLatestReport,
3447
3652
  findLatestScanReport,
3448
3653
  formatAcceptanceRate,
@@ -3458,13 +3663,14 @@ export {
3458
3663
  generateReportFooter,
3459
3664
  generateReportHead,
3460
3665
  generateReportHero,
3461
- generateScoreCard,
3666
+ generateStandardHtmlReport,
3462
3667
  generateStatCards,
3463
3668
  generateTable,
3464
3669
  generateValueChain,
3465
3670
  getElapsedTime,
3466
3671
  getFileCommitTimestamps,
3467
3672
  getFileExtension,
3673
+ getFilesByPattern,
3468
3674
  getHistorySummary,
3469
3675
  getLineRangeLastModifiedCached,
3470
3676
  getModelPreset,
@@ -3487,6 +3693,7 @@ export {
3487
3693
  getSeverityBadge,
3488
3694
  getSeverityColor,
3489
3695
  getSeverityEnum,
3696
+ getSeverityLabel,
3490
3697
  getSeverityLevel,
3491
3698
  getSeverityValue,
3492
3699
  getSupportedLanguages,
@@ -3501,8 +3708,10 @@ export {
3501
3708
  inferPatternType,
3502
3709
  initTreeSitter,
3503
3710
  initializeParsers,
3711
+ isBuildArtifact,
3504
3712
  isFileSupported,
3505
3713
  isSourceFile,
3714
+ isTestFile,
3506
3715
  loadConfig,
3507
3716
  loadMergedConfig,
3508
3717
  loadScoreHistory,
@@ -3510,6 +3719,7 @@ export {
3510
3719
  normalizeAnalysisResult,
3511
3720
  normalizeIssue,
3512
3721
  normalizeMetrics,
3722
+ normalizeSeverity,
3513
3723
  normalizeSpokeOutput,
3514
3724
  normalizeToolName,
3515
3725
  parseFileExports,
@@ -3520,6 +3730,7 @@ export {
3520
3730
  readFileContent,
3521
3731
  resolveOutputFormat,
3522
3732
  resolveOutputPath,
3733
+ runBatchAnalysis,
3523
3734
  runStandardCliAction,
3524
3735
  saveScoreEntry,
3525
3736
  scanEntries,