@aiready/core 0.23.23 → 0.24.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/index.js CHANGED
@@ -965,8 +965,8 @@ var init_python_parser = __esm({
965
965
  const fromMatch = line.match(fromImportRegex);
966
966
  if (fromMatch) {
967
967
  const module2 = fromMatch[1];
968
- const imports_str = fromMatch[2];
969
- if (imports_str.trim() === "*") {
968
+ const importsStr = fromMatch[2];
969
+ if (importsStr.trim() === "*") {
970
970
  imports.push({
971
971
  source: module2,
972
972
  specifiers: ["*"],
@@ -977,7 +977,7 @@ var init_python_parser = __esm({
977
977
  });
978
978
  return;
979
979
  }
980
- const specifiers = imports_str.split(",").map((s) => s.trim().split(" as ")[0]);
980
+ const specifiers = importsStr.split(",").map((s) => s.trim().split(" as ")[0]);
981
981
  imports.push({
982
982
  source: module2,
983
983
  specifiers,
@@ -1994,6 +1994,7 @@ __export(index_exports, {
1994
1994
  SeveritySchema: () => SeveritySchema,
1995
1995
  SpokeOutputSchema: () => SpokeOutputSchema,
1996
1996
  SpokeSummarySchema: () => SpokeSummarySchema,
1997
+ TEST_PATTERNS: () => TEST_PATTERNS,
1997
1998
  TOOL_NAME_MAP: () => TOOL_NAME_MAP,
1998
1999
  ToolName: () => ToolName,
1999
2000
  ToolNameSchema: () => ToolNameSchema,
@@ -2033,14 +2034,18 @@ __export(index_exports, {
2033
2034
  clearHistory: () => clearHistory,
2034
2035
  createProvider: () => createProvider,
2035
2036
  createStandardProgressCallback: () => createStandardProgressCallback,
2037
+ detectTestFramework: () => detectTestFramework,
2036
2038
  displayStandardConsoleReport: () => displayStandardConsoleReport,
2037
2039
  emitAnnotation: () => emitAnnotation,
2038
2040
  emitIssuesAsAnnotations: () => emitIssuesAsAnnotations,
2039
2041
  emitProgress: () => emitProgress,
2042
+ ensureDir: () => ensureDir,
2040
2043
  estimateCostFromBudget: () => estimateCostFromBudget,
2041
2044
  estimateTokens: () => estimateTokens,
2045
+ executeSpokeCli: () => executeSpokeCli,
2042
2046
  exportHistory: () => exportHistory,
2043
2047
  extractCodeBlocks: () => extractCodeBlocks,
2048
+ filterBySeverity: () => filterBySeverity,
2044
2049
  findLatestReport: () => findLatestReport,
2045
2050
  findLatestScanReport: () => findLatestScanReport,
2046
2051
  formatAcceptanceRate: () => formatAcceptanceRate,
@@ -2056,13 +2061,14 @@ __export(index_exports, {
2056
2061
  generateReportFooter: () => generateReportFooter,
2057
2062
  generateReportHead: () => generateReportHead,
2058
2063
  generateReportHero: () => generateReportHero,
2059
- generateScoreCard: () => generateScoreCard,
2064
+ generateStandardHtmlReport: () => generateStandardHtmlReport,
2060
2065
  generateStatCards: () => generateStatCards,
2061
2066
  generateTable: () => generateTable,
2062
2067
  generateValueChain: () => generateValueChain,
2063
2068
  getElapsedTime: () => getElapsedTime,
2064
2069
  getFileCommitTimestamps: () => getFileCommitTimestamps,
2065
2070
  getFileExtension: () => getFileExtension,
2071
+ getFilesByPattern: () => getFilesByPattern,
2066
2072
  getHistorySummary: () => getHistorySummary,
2067
2073
  getLineRangeLastModifiedCached: () => getLineRangeLastModifiedCached,
2068
2074
  getModelPreset: () => getModelPreset,
@@ -2085,6 +2091,7 @@ __export(index_exports, {
2085
2091
  getSeverityBadge: () => getSeverityBadge,
2086
2092
  getSeverityColor: () => getSeverityColor,
2087
2093
  getSeverityEnum: () => getSeverityEnum,
2094
+ getSeverityLabel: () => getSeverityLabel,
2088
2095
  getSeverityLevel: () => getSeverityLevel,
2089
2096
  getSeverityValue: () => getSeverityValue,
2090
2097
  getSupportedLanguages: () => getSupportedLanguages,
@@ -2099,8 +2106,10 @@ __export(index_exports, {
2099
2106
  inferPatternType: () => inferPatternType,
2100
2107
  initTreeSitter: () => initTreeSitter,
2101
2108
  initializeParsers: () => initializeParsers,
2109
+ isBuildArtifact: () => isBuildArtifact,
2102
2110
  isFileSupported: () => isFileSupported,
2103
2111
  isSourceFile: () => isSourceFile,
2112
+ isTestFile: () => isTestFile,
2104
2113
  loadConfig: () => loadConfig,
2105
2114
  loadMergedConfig: () => loadMergedConfig,
2106
2115
  loadScoreHistory: () => loadScoreHistory,
@@ -2108,6 +2117,7 @@ __export(index_exports, {
2108
2117
  normalizeAnalysisResult: () => normalizeAnalysisResult,
2109
2118
  normalizeIssue: () => normalizeIssue,
2110
2119
  normalizeMetrics: () => normalizeMetrics,
2120
+ normalizeSeverity: () => normalizeSeverity,
2111
2121
  normalizeSpokeOutput: () => normalizeSpokeOutput,
2112
2122
  normalizeToolName: () => normalizeToolName,
2113
2123
  parseFileExports: () => parseFileExports,
@@ -2118,6 +2128,7 @@ __export(index_exports, {
2118
2128
  readFileContent: () => readFileContent,
2119
2129
  resolveOutputFormat: () => resolveOutputFormat,
2120
2130
  resolveOutputPath: () => resolveOutputPath,
2131
+ runBatchAnalysis: () => runBatchAnalysis,
2121
2132
  runStandardCliAction: () => runStandardCliAction,
2122
2133
  saveScoreEntry: () => saveScoreEntry,
2123
2134
  scanEntries: () => scanEntries,
@@ -2212,6 +2223,7 @@ var ToolName = /* @__PURE__ */ ((ToolName2) => {
2212
2223
  ToolName2["PatternEntropy"] = "pattern-entropy";
2213
2224
  ToolName2["ConceptCohesion"] = "concept-cohesion";
2214
2225
  ToolName2["SemanticDistance"] = "semantic-distance";
2226
+ ToolName2["ContractEnforcement"] = "contract-enforcement";
2215
2227
  return ToolName2;
2216
2228
  })(ToolName || {});
2217
2229
  var ToolNameSchema = import_zod2.z.nativeEnum(ToolName);
@@ -2228,7 +2240,8 @@ var FRIENDLY_TOOL_NAMES = {
2228
2240
  ["cognitive-load" /* CognitiveLoad */]: "Cognitive Load",
2229
2241
  ["pattern-entropy" /* PatternEntropy */]: "Pattern Entropy",
2230
2242
  ["concept-cohesion" /* ConceptCohesion */]: "Concept Cohesion",
2231
- ["semantic-distance" /* SemanticDistance */]: "Semantic Distance"
2243
+ ["semantic-distance" /* SemanticDistance */]: "Semantic Distance",
2244
+ ["contract-enforcement" /* ContractEnforcement */]: "Contract Enforcement"
2232
2245
  };
2233
2246
  var IssueType = /* @__PURE__ */ ((IssueType2) => {
2234
2247
  IssueType2["DuplicatePattern"] = "duplicate-pattern";
@@ -2249,6 +2262,7 @@ var IssueType = /* @__PURE__ */ ((IssueType2) => {
2249
2262
  IssueType2["AgentNavigationFailure"] = "agent-navigation-failure";
2250
2263
  IssueType2["AmbiguousApi"] = "ambiguous-api";
2251
2264
  IssueType2["ChangeAmplification"] = "change-amplification";
2265
+ IssueType2["ContractGap"] = "contract-gap";
2252
2266
  return IssueType2;
2253
2267
  })(IssueType || {});
2254
2268
  var IssueTypeSchema = import_zod2.z.nativeEnum(IssueType);
@@ -2364,12 +2378,12 @@ var UnifiedReportSchema = import_zod6.z.object({
2364
2378
  // src/types/schemas/config.ts
2365
2379
  var import_zod7 = require("zod");
2366
2380
  var AIReadyConfigSchema = import_zod7.z.object({
2367
- /** Target score threshold (0-100) */
2368
- threshold: import_zod7.z.number().optional(),
2369
- /** Files or directories to include in scan */
2370
- include: import_zod7.z.array(import_zod7.z.string()).optional(),
2371
2381
  /** Files or directories to exclude from scan */
2372
2382
  exclude: import_zod7.z.array(import_zod7.z.string()).optional(),
2383
+ /** Fail CI/CD if score below threshold (0-100) */
2384
+ threshold: import_zod7.z.number().optional(),
2385
+ /** Fail on issues: critical, major, any */
2386
+ failOn: import_zod7.z.enum(["critical", "major", "any", "none"]).optional(),
2373
2387
  /** Scan-specific configuration */
2374
2388
  scan: import_zod7.z.object({
2375
2389
  include: import_zod7.z.array(import_zod7.z.string()).optional(),
@@ -2750,14 +2764,14 @@ async function scanFiles(options) {
2750
2764
  ignoreFromFile = [];
2751
2765
  }
2752
2766
  }
2753
- const TEST_PATTERNS = [
2767
+ const TEST_PATTERNS2 = [
2754
2768
  "**/*.test.*",
2755
2769
  "**/*.spec.*",
2756
2770
  "**/__tests__/**",
2757
2771
  "**/test/**",
2758
2772
  "**/tests/**"
2759
2773
  ];
2760
- const baseExclude = options.includeTests ? DEFAULT_EXCLUDE.filter((p) => !TEST_PATTERNS.includes(p)) : DEFAULT_EXCLUDE;
2774
+ const baseExclude = options.includeTests ? DEFAULT_EXCLUDE.filter((p) => !TEST_PATTERNS2.includes(p)) : DEFAULT_EXCLUDE;
2761
2775
  const finalExclude = [
2762
2776
  .../* @__PURE__ */ new Set([...exclude || [], ...ignoreFromFile, ...baseExclude])
2763
2777
  ];
@@ -2819,14 +2833,14 @@ async function scanEntries(options) {
2819
2833
  ignoreFromFile = [];
2820
2834
  }
2821
2835
  }
2822
- const TEST_PATTERNS = [
2836
+ const TEST_PATTERNS2 = [
2823
2837
  "**/*.test.*",
2824
2838
  "**/*.spec.*",
2825
2839
  "**/__tests__/**",
2826
2840
  "**/test/**",
2827
2841
  "**/tests/**"
2828
2842
  ];
2829
- const baseExclude = includeTests ? DEFAULT_EXCLUDE.filter((p) => !TEST_PATTERNS.includes(p)) : DEFAULT_EXCLUDE;
2843
+ const baseExclude = includeTests ? DEFAULT_EXCLUDE.filter((p) => !TEST_PATTERNS2.includes(p)) : DEFAULT_EXCLUDE;
2830
2844
  const finalExclude = [
2831
2845
  .../* @__PURE__ */ new Set([...exclude || [], ...ignoreFromFile, ...baseExclude])
2832
2846
  ];
@@ -2881,26 +2895,15 @@ function isSourceFile(filePath) {
2881
2895
  return ["ts", "tsx", "js", "jsx", "py", "java", "go", "rs"].includes(ext);
2882
2896
  }
2883
2897
 
2884
- // src/utils/cli-helpers.ts
2898
+ // src/utils/fs-utils.ts
2885
2899
  var import_fs2 = require("fs");
2886
2900
  var import_path2 = require("path");
2887
- var import_chalk = __toESM(require("chalk"));
2888
2901
  function ensureDir(path2) {
2889
2902
  const dir = (0, import_path2.dirname)(path2);
2890
2903
  if (!(0, import_fs2.existsSync)(dir)) {
2891
2904
  (0, import_fs2.mkdirSync)(dir, { recursive: true });
2892
2905
  }
2893
2906
  }
2894
- function normalizeSeverity(s) {
2895
- if (!s) return null;
2896
- const lower = s.toLowerCase();
2897
- if (["critical", "high-risk", "blind-risk"].includes(lower))
2898
- return "critical" /* Critical */;
2899
- if (["major", "moderate-risk"].includes(lower)) return "major" /* Major */;
2900
- if (["minor", "safe"].includes(lower)) return "minor" /* Minor */;
2901
- if (lower === "info") return "info" /* Info */;
2902
- return null;
2903
- }
2904
2907
  function getFilesByPattern(dir, pattern) {
2905
2908
  if (!(0, import_fs2.existsSync)(dir)) return [];
2906
2909
  try {
@@ -2927,16 +2930,6 @@ function resolveOutputPath(userPath, defaultFilename, workingDir = process.cwd()
2927
2930
  ensureDir(outputPath);
2928
2931
  return outputPath;
2929
2932
  }
2930
- async function loadMergedConfig(directory, defaults, cliOptions) {
2931
- const config = await loadConfig(directory);
2932
- const mergedConfig = mergeConfigWithDefaults(config, defaults);
2933
- const result = {
2934
- ...mergedConfig,
2935
- ...cliOptions,
2936
- rootDir: directory
2937
- };
2938
- return result;
2939
- }
2940
2933
  function handleJSONOutput(data, outputFile, successMessage) {
2941
2934
  if (outputFile) {
2942
2935
  ensureDir(outputFile);
@@ -2946,63 +2939,54 @@ function handleJSONOutput(data, outputFile, successMessage) {
2946
2939
  console.log(JSON.stringify(data, null, 2));
2947
2940
  }
2948
2941
  }
2949
- function getTerminalDivider(color = import_chalk.default.cyan, maxWidth = 60) {
2950
- const terminalWidth = process.stdout.columns || 80;
2951
- const dividerWidth = Math.min(maxWidth, terminalWidth - 2);
2952
- return color("\u2501".repeat(dividerWidth));
2953
- }
2954
- function printTerminalHeader(title, color = import_chalk.default.cyan) {
2955
- const divider = getTerminalDivider(color);
2956
- console.log(divider);
2957
- console.log(import_chalk.default.bold.white(` ${title.toUpperCase()}`));
2958
- console.log(divider + "\n");
2959
- }
2960
- function handleCLIError(error, commandName) {
2961
- console.error(`\u274C ${commandName} failed:`, error);
2962
- process.exit(1);
2963
- }
2964
- function getElapsedTime(startTime) {
2965
- return ((Date.now() - startTime) / 1e3).toFixed(2);
2966
- }
2967
- function getScoreBar(val) {
2968
- const clamped = Math.max(0, Math.min(100, val));
2969
- return "\u2588".repeat(Math.round(clamped / 10)).padEnd(10, "\u2591");
2970
- }
2971
- function getSafetyIcon(rating) {
2972
- switch (rating) {
2973
- case "safe":
2974
- return "\u2705";
2975
- case "moderate-risk":
2976
- return "\u26A0\uFE0F ";
2977
- case "high-risk":
2978
- return "\u{1F534}";
2979
- case "blind-risk":
2980
- return "\u{1F480}";
2981
- default:
2982
- return "\u2753";
2942
+ function findLatestReport(dirPath) {
2943
+ const aireadyDir = (0, import_path2.resolve)(dirPath, ".aiready");
2944
+ let files = getFilesByPattern(aireadyDir, /^aiready-report-.*\.json$/);
2945
+ if (files.length === 0) {
2946
+ files = getFilesByPattern(aireadyDir, /^aiready-scan-.*\.json$/);
2983
2947
  }
2984
- }
2985
- function emitProgress(processed, total, toolId, message, onProgress, throttleCount = 50) {
2986
- if (!onProgress) return;
2987
- if (processed % throttleCount === 0 || processed === total) {
2988
- onProgress(processed, total, `${message} (${processed}/${total})`);
2948
+ if (files.length === 0) {
2949
+ return null;
2989
2950
  }
2951
+ const sortedFiles = files.map((f) => ({
2952
+ name: f,
2953
+ path: (0, import_path2.resolve)(aireadyDir, f),
2954
+ mtime: (0, import_fs2.statSync)((0, import_path2.resolve)(aireadyDir, f)).mtime
2955
+ })).sort((a, b) => b.mtime.getTime() - a.mtime.getTime());
2956
+ return sortedFiles[0].path;
2990
2957
  }
2991
- function getSeverityColor(severity, chalkInstance = import_chalk.default) {
2992
- const normalized = normalizeSeverity(severity);
2993
- switch (normalized) {
2994
- case "critical" /* Critical */:
2995
- return chalkInstance.red;
2996
- case "major" /* Major */:
2997
- return chalkInstance.yellow;
2998
- case "minor" /* Minor */:
2999
- return chalkInstance.green;
3000
- case "info" /* Info */:
3001
- return chalkInstance.blue;
3002
- default:
3003
- return chalkInstance.white;
2958
+ function findLatestScanReport(scanReportsDir, reportFilePrefix) {
2959
+ try {
2960
+ const prefixRegex = new RegExp(`^${reportFilePrefix}\\d+\\.json$`);
2961
+ const reportFiles = getFilesByPattern(scanReportsDir, prefixRegex);
2962
+ if (reportFiles.length === 0) return null;
2963
+ reportFiles.sort((a, b) => {
2964
+ const idA = parseInt(a.match(/\d+/)?.[0] || "0", 10);
2965
+ const idB = parseInt(b.match(/\d+/)?.[0] || "0", 10);
2966
+ return idB - idA;
2967
+ });
2968
+ return (0, import_path2.join)(scanReportsDir, reportFiles[0]);
2969
+ } catch {
2970
+ console.error("Error while finding latest scan report");
2971
+ return null;
3004
2972
  }
3005
2973
  }
2974
+
2975
+ // src/utils/terminal-utils.ts
2976
+ var import_chalk2 = __toESM(require("chalk"));
2977
+
2978
+ // src/utils/severity-utils.ts
2979
+ var import_chalk = __toESM(require("chalk"));
2980
+ function normalizeSeverity(s) {
2981
+ if (!s) return null;
2982
+ const lower = s.toLowerCase();
2983
+ if (["critical", "high-risk", "blind-risk"].includes(lower))
2984
+ return "critical" /* Critical */;
2985
+ if (["major", "moderate-risk"].includes(lower)) return "major" /* Major */;
2986
+ if (["minor", "safe"].includes(lower)) return "minor" /* Minor */;
2987
+ if (lower === "info") return "info" /* Info */;
2988
+ return null;
2989
+ }
3006
2990
  function getSeverityValue(s) {
3007
2991
  const normalized = normalizeSeverity(s);
3008
2992
  switch (normalized) {
@@ -3021,23 +3005,6 @@ function getSeverityValue(s) {
3021
3005
  function getSeverityLevel(s) {
3022
3006
  return getSeverityValue(s);
3023
3007
  }
3024
- function getSeverityBadge(severity, chalkInstance = import_chalk.default) {
3025
- const val = getSeverityValue(
3026
- typeof severity === "string" ? severity : severity
3027
- );
3028
- switch (val) {
3029
- case 4:
3030
- return chalkInstance.bgRed.white.bold(" CRITICAL ");
3031
- case 3:
3032
- return chalkInstance.bgYellow.black.bold(" MAJOR ");
3033
- case 2:
3034
- return chalkInstance.bgBlue.white.bold(" MINOR ");
3035
- case 1:
3036
- return chalkInstance.bgCyan.black(" INFO ");
3037
- default:
3038
- return chalkInstance.bgCyan.black(" INFO ");
3039
- }
3040
- }
3041
3008
  function getSeverityEnum(s) {
3042
3009
  const level = getSeverityLevel(s);
3043
3010
  switch (level) {
@@ -3051,39 +3018,125 @@ function getSeverityEnum(s) {
3051
3018
  return "info";
3052
3019
  }
3053
3020
  }
3054
- function findLatestReport(dirPath) {
3055
- const aireadyDir = (0, import_path2.resolve)(dirPath, ".aiready");
3056
- let files = getFilesByPattern(aireadyDir, /^aiready-report-.*\.json$/);
3057
- if (files.length === 0) {
3058
- files = getFilesByPattern(aireadyDir, /^aiready-scan-.*\.json$/);
3021
+ function getSeverityColor(severity, chalkInstance = import_chalk.default) {
3022
+ const normalized = normalizeSeverity(severity);
3023
+ switch (normalized) {
3024
+ case "critical" /* Critical */:
3025
+ return chalkInstance.red;
3026
+ case "major" /* Major */:
3027
+ return chalkInstance.yellow;
3028
+ case "minor" /* Minor */:
3029
+ return chalkInstance.green;
3030
+ case "info" /* Info */:
3031
+ return chalkInstance.blue;
3032
+ default:
3033
+ return chalkInstance.white;
3059
3034
  }
3060
- if (files.length === 0) {
3061
- return null;
3035
+ }
3036
+ function getSeverityBadge(severity, chalkInstance = import_chalk.default) {
3037
+ const normalized = normalizeSeverity(severity);
3038
+ switch (normalized) {
3039
+ case "critical" /* Critical */:
3040
+ return chalkInstance.bgRed.white.bold(" CRITICAL ");
3041
+ case "major" /* Major */:
3042
+ return chalkInstance.bgYellow.black.bold(" MAJOR ");
3043
+ case "minor" /* Minor */:
3044
+ return chalkInstance.bgGreen.black.bold(" MINOR ");
3045
+ case "info" /* Info */:
3046
+ return chalkInstance.bgBlue.white.bold(" INFO ");
3047
+ default:
3048
+ return chalkInstance.bgCyan.black(" UNKNOWN ");
3062
3049
  }
3063
- const sortedFiles = files.map((f) => ({
3064
- name: f,
3065
- path: (0, import_path2.resolve)(aireadyDir, f),
3066
- mtime: (0, import_fs2.statSync)((0, import_path2.resolve)(aireadyDir, f)).mtime
3067
- })).sort((a, b) => b.mtime.getTime() - a.mtime.getTime());
3068
- return sortedFiles[0].path;
3069
3050
  }
3070
- function findLatestScanReport(scanReportsDir, reportFilePrefix) {
3071
- try {
3072
- const prefixRegex = new RegExp(`^${reportFilePrefix}\\d+\\.json$`);
3073
- const reportFiles = getFilesByPattern(scanReportsDir, prefixRegex);
3074
- if (reportFiles.length === 0) return null;
3075
- reportFiles.sort((a, b) => {
3076
- const idA = parseInt(a.match(/\d+/)?.[0] || "0", 10);
3077
- const idB = parseInt(b.match(/\d+/)?.[0] || "0", 10);
3078
- return idB - idA;
3079
- });
3080
- return (0, import_path2.join)(scanReportsDir, reportFiles[0]);
3081
- } catch {
3082
- console.error("Error while finding latest scan report");
3083
- return null;
3051
+ function getSeverityLabel(severity) {
3052
+ const labels = {
3053
+ ["critical" /* Critical */]: "\u{1F534} CRITICAL",
3054
+ ["major" /* Major */]: "\u{1F7E1} MAJOR",
3055
+ ["minor" /* Minor */]: "\u{1F535} MINOR",
3056
+ ["info" /* Info */]: "\u2139\uFE0F INFO"
3057
+ };
3058
+ return labels[severity];
3059
+ }
3060
+ function filterBySeverity(items, minSeverity) {
3061
+ const severityOrder = [
3062
+ "info" /* Info */,
3063
+ "minor" /* Minor */,
3064
+ "major" /* Major */,
3065
+ "critical" /* Critical */
3066
+ ];
3067
+ const minIndex = severityOrder.indexOf(minSeverity);
3068
+ if (minIndex === -1) return items;
3069
+ return items.filter((item) => {
3070
+ const itemIndex = severityOrder.indexOf(item.severity);
3071
+ return itemIndex >= minIndex;
3072
+ });
3073
+ }
3074
+
3075
+ // src/utils/terminal-utils.ts
3076
+ function getSafetyIcon(rating) {
3077
+ switch (rating) {
3078
+ case "safe":
3079
+ return "\u2705";
3080
+ case "moderate-risk":
3081
+ return "\u26A0\uFE0F ";
3082
+ case "high-risk":
3083
+ return "\u{1F534}";
3084
+ case "blind-risk":
3085
+ return "\u{1F480}";
3086
+ default:
3087
+ return "\u2753";
3088
+ }
3089
+ }
3090
+
3091
+ // src/utils/terminal-ui.ts
3092
+ var import_chalk3 = __toESM(require("chalk"));
3093
+ function printTerminalHeader(title, colorFn = import_chalk3.default.cyan.bold, width = 80) {
3094
+ const divider = "\u2501".repeat(width);
3095
+ console.log(colorFn(`
3096
+ ${divider}`));
3097
+ console.log(colorFn(` ${title.toUpperCase()}`));
3098
+ console.log(colorFn(`${divider}
3099
+ `));
3100
+ }
3101
+ function getTerminalDivider(colorFn = import_chalk3.default.gray, width = 80) {
3102
+ return colorFn("\u2501".repeat(width));
3103
+ }
3104
+ function getScoreBar(score, width = 10) {
3105
+ const normalized = Math.max(0, Math.min(100, score));
3106
+ const solid = Math.round(normalized / 100 * width);
3107
+ const empty = width - solid;
3108
+ return "\u2588".repeat(solid) + "\u2591".repeat(empty);
3109
+ }
3110
+
3111
+ // src/utils/progress-utils.ts
3112
+ function emitProgress(processed, total, toolId, message, onProgress, throttleCount = 50) {
3113
+ if (!onProgress) return;
3114
+ if (processed % throttleCount === 0 || processed === total) {
3115
+ onProgress(processed, total, `${message} (${processed}/${total})`);
3084
3116
  }
3085
3117
  }
3086
3118
 
3119
+ // src/utils/cli-utils.ts
3120
+ function getElapsedTime(startTime) {
3121
+ return ((Date.now() - startTime) / 1e3).toFixed(2);
3122
+ }
3123
+ function handleCLIError(error, commandName) {
3124
+ console.error(`\u274C ${commandName} failed:`, error);
3125
+ process.exit(1);
3126
+ }
3127
+
3128
+ // src/utils/cli-helpers.ts
3129
+ async function loadMergedConfig(directory, defaults, cliOptions) {
3130
+ const config = await loadConfig(directory);
3131
+ const mergedConfig = mergeConfigWithDefaults(config, defaults);
3132
+ const result = {
3133
+ ...mergedConfig,
3134
+ ...cliOptions,
3135
+ rootDir: directory
3136
+ };
3137
+ return result;
3138
+ }
3139
+
3087
3140
  // src/utils/cli-action-helpers.ts
3088
3141
  var import_path3 = require("path");
3089
3142
  function getReportTimestamp() {
@@ -3675,6 +3728,9 @@ function mergeConfigWithDefaults(userConfig, defaults) {
3675
3728
  if (userConfig.scan.include) mergedConfig.include = userConfig.scan.include;
3676
3729
  if (userConfig.scan.exclude) mergedConfig.exclude = userConfig.scan.exclude;
3677
3730
  }
3731
+ if (userConfig.threshold !== void 0)
3732
+ mergedConfig.threshold = userConfig.threshold;
3733
+ if (userConfig.failOn !== void 0) mergedConfig.failOn = userConfig.failOn;
3678
3734
  if (userConfig.tools) {
3679
3735
  if (!mergedConfig.toolConfigs) mergedConfig.toolConfigs = {};
3680
3736
  for (const [toolName, toolConfig] of Object.entries(userConfig.tools)) {
@@ -3858,114 +3914,148 @@ function generateHTML(graph) {
3858
3914
  </html>`;
3859
3915
  }
3860
3916
 
3917
+ // src/utils/report-styles.ts
3918
+ var REPORT_STYLES = `
3919
+ 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; }
3920
+ h1, h2, h3 { color: #1a1a1a; border-bottom: 2px solid #eaeaea; padding-bottom: 0.5rem; }
3921
+ .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; }
3922
+ .stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 1rem; margin-bottom: 2rem; }
3923
+ .stat-card { background: #fff; padding: 1rem; border-radius: 6px; text-align: center; border: 1px solid #eaeaea; }
3924
+ .stat-value { font-size: 1.8rem; font-weight: bold; color: #2563eb; }
3925
+ .stat-label { font-size: 0.875rem; color: #666; text-transform: uppercase; }
3926
+ .hero { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 30px; border-radius: 8px; margin-bottom: 30px; }
3927
+ .hero h1 { border: none; color: white; margin: 0; }
3928
+ .hero p { margin: 10px 0 0 0; opacity: 0.9; }
3929
+ table { width: 100%; border-collapse: collapse; margin-top: 1rem; background: white; border-radius: 4px; overflow: hidden; }
3930
+ th, td { text-align: left; padding: 0.875rem 1rem; border-bottom: 1px solid #eaeaea; }
3931
+ th { background-color: #f8fafc; font-weight: 600; color: #475569; }
3932
+ tr:last-child td { border-bottom: none; }
3933
+ .critical { color: #dc2626; font-weight: bold; }
3934
+ .major { color: #ea580c; font-weight: bold; }
3935
+ .minor { color: #2563eb; }
3936
+ .issue-critical { color: #dc2626; font-weight: bold; text-transform: uppercase; }
3937
+ .issue-major { color: #ea580c; font-weight: bold; text-transform: uppercase; }
3938
+ .issue-minor { color: #2563eb; font-weight: bold; text-transform: uppercase; }
3939
+ code { background: #f1f5f9; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.875rem; color: #334155; }
3940
+ .footer { margin-top: 4rem; text-align: center; color: #94a3b8; font-size: 0.875rem; }
3941
+ a { color: #2563eb; text-decoration: none; }
3942
+ a:hover { text-decoration: underline; }
3943
+ `;
3944
+
3861
3945
  // src/utils/report-formatters.ts
3862
- function generateReportHead(title) {
3946
+ function tag(name, content = "", attrs = {}) {
3947
+ const attrStr = Object.entries(attrs).map(([k, v]) => ` ${k}="${v}"`).join("");
3948
+ return `<${name}${attrStr}>${content}</${name}>`;
3949
+ }
3950
+ function generateReportHead(title, styles = REPORT_STYLES) {
3863
3951
  return `<!DOCTYPE html>
3864
3952
  <html lang="en">
3865
3953
  <head>
3866
3954
  <meta charset="UTF-8">
3867
3955
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
3868
- <title>${title}</title>
3869
- <style>
3870
- 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; }
3871
- h1, h2, h3 { color: #1a1a1a; border-bottom: 2px solid #eaeaea; padding-bottom: 0.5rem; }
3872
- .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; }
3873
- .stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 1rem; margin-bottom: 2rem; }
3874
- .stat-card { background: #fff; padding: 1rem; border-radius: 6px; text-align: center; border: 1px solid #eaeaea; }
3875
- .stat-value { font-size: 1.8rem; font-weight: bold; color: #2563eb; }
3876
- .stat-label { font-size: 0.875rem; color: #666; text-transform: uppercase; }
3877
- .hero { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 30px; border-radius: 8px; margin-bottom: 30px; }
3878
- .hero h1 { border: none; color: white; margin: 0; }
3879
- .hero p { margin: 10px 0 0 0; opacity: 0.9; }
3880
- table { width: 100%; border-collapse: collapse; margin-top: 1rem; background: white; border-radius: 4px; overflow: hidden; }
3881
- th, td { text-align: left; padding: 0.875rem 1rem; border-bottom: 1px solid #eaeaea; }
3882
- th { background-color: #f8fafc; font-weight: 600; color: #475569; }
3883
- tr:last-child td { border-bottom: none; }
3884
- .critical { color: #dc2626; font-weight: bold; }
3885
- .major { color: #ea580c; font-weight: bold; }
3886
- .minor { color: #2563eb; }
3887
- .issue-critical { color: #dc2626; font-weight: bold; text-transform: uppercase; }
3888
- .issue-major { color: #ea580c; font-weight: bold; text-transform: uppercase; }
3889
- .issue-minor { color: #2563eb; font-weight: bold; text-transform: uppercase; }
3890
- code { background: #f1f5f9; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.875rem; color: #334155; }
3891
- .footer { margin-top: 4rem; text-align: center; color: #94a3b8; font-size: 0.875rem; }
3892
- a { color: #2563eb; text-decoration: none; }
3893
- a:hover { text-decoration: underline; }
3894
- </style>
3956
+ ${tag("title", title)}
3957
+ ${tag("style", styles)}
3895
3958
  </head>`;
3896
3959
  }
3897
3960
  function generateReportHero(title, subtitle) {
3898
- const subtitleHtml = subtitle ? `<p>${subtitle}</p>` : "";
3899
- return `<div class="hero">
3900
- <h1>${title}</h1>
3901
- ${subtitleHtml}
3902
- </div>`;
3961
+ return tag("div", tag("h1", title) + (subtitle ? tag("p", subtitle) : ""), {
3962
+ class: "hero"
3963
+ });
3903
3964
  }
3904
3965
  function generateStatCards(cards) {
3905
3966
  const cardsHtml = cards.map(
3906
- (card) => `
3907
- <div class="stat-card">
3908
- <div class="stat-value"${card.color ? ` style="color: ${card.color}"` : ""}>${card.value}</div>
3909
- <div class="stat-label">${card.label}</div>
3910
- </div>`
3967
+ (c) => tag(
3968
+ "div",
3969
+ tag("div", String(c.value), {
3970
+ class: "stat-value",
3971
+ ...c.color ? { style: `color: ${c.color}` } : {}
3972
+ }) + tag("div", c.label, { class: "stat-label" }),
3973
+ { class: "stat-card" }
3974
+ )
3911
3975
  ).join("");
3912
- return `<div class="stats">${cardsHtml}</div>`;
3913
- }
3914
- function generateScoreCard(value, label) {
3915
- return `<div class="stat-card" style="margin-bottom: 2rem;">
3916
- <div class="stat-label">${label}</div>
3917
- <div class="stat-value">${value}</div>
3918
- </div>`;
3976
+ return tag("div", cardsHtml, { class: "stats" });
3919
3977
  }
3920
3978
  function generateTable(config) {
3921
- const headersHtml = config.headers.map((h) => `<th>${h}</th>`).join("");
3922
- const rowsHtml = config.rows.map((row) => `<tr>${row.map((cell) => `<td>${cell}</td>`).join("")}</tr>`).join("");
3923
- return `<table>
3924
- <thead><tr>${headersHtml}</tr></thead>
3925
- <tbody>${rowsHtml}</tbody>
3926
- </table>`;
3927
- }
3928
- function generateIssueSummary(critical, major, minor, potentialSavings) {
3929
- const savingsHtml = potentialSavings ? `<p><strong>Potential Savings:</strong> ${potentialSavings.toLocaleString()} tokens</p>` : "";
3930
- return `<div class="card" style="margin-bottom: 30px;">
3931
- <h2>\u26A0\uFE0F Issues Summary</h2>
3932
- <p>
3933
- <span class="critical">\u{1F534} Critical: ${critical}</span> &nbsp;
3934
- <span class="major">\u{1F7E1} Major: ${major}</span> &nbsp;
3935
- <span class="minor">\u{1F535} Minor: ${minor}</span>
3936
- </p>
3937
- ${savingsHtml}
3938
- </div>`;
3979
+ const head = tag(
3980
+ "thead",
3981
+ tag("tr", config.headers.map((h) => tag("th", h)).join(""))
3982
+ );
3983
+ const body = tag(
3984
+ "tbody",
3985
+ config.rows.map(
3986
+ (row) => tag("tr", row.map((cell) => tag("td", cell)).join(""))
3987
+ ).join("")
3988
+ );
3989
+ return tag("table", head + body);
3990
+ }
3991
+ function generateIssueSummary(crit, maj, min, savings) {
3992
+ const details = [
3993
+ tag("span", `\u{1F534} Critical: ${crit}`, { class: "critical" }),
3994
+ tag("span", `\u{1F7E1} Major: ${maj}`, { class: "major" }),
3995
+ tag("span", `\u{1F535} Minor: ${min}`, { class: "minor" })
3996
+ ].join(" &nbsp; ");
3997
+ const savingsHtml = savings ? tag(
3998
+ "p",
3999
+ tag("strong", "Potential Savings: ") + savings.toLocaleString() + " tokens"
4000
+ ) : "";
4001
+ return tag(
4002
+ "div",
4003
+ tag("h2", "\u26A0\uFE0F Issues Summary") + tag("p", details) + savingsHtml,
4004
+ { class: "card", style: "margin-bottom: 30px;" }
4005
+ );
3939
4006
  }
3940
4007
  function generateReportFooter(options) {
3941
- const versionText = options.version ? ` v${options.version}` : "";
4008
+ const version = options.version ? ` v${options.version}` : "";
3942
4009
  const links = [];
3943
- if (options.packageUrl) {
3944
- links.push(`<a href="${options.packageUrl}">Star us on GitHub</a>`);
3945
- }
3946
- if (options.bugUrl) {
3947
- links.push(`<a href="${options.bugUrl}">Report it here</a>`);
3948
- }
3949
- const linksHtml = links.length ? links.map((l) => `<p>Like AIReady? ${l}</p>`).join("\n ") : "";
3950
- return `<div class="footer">
3951
- <p>Generated by <strong>@aiready/${options.packageName}</strong>${versionText}</p>
3952
- ${linksHtml}
3953
- </div>`;
4010
+ if (options.packageUrl)
4011
+ links.push(
4012
+ tag(
4013
+ "p",
4014
+ `Like AIReady? ${tag("a", "Star us on GitHub", { href: options.packageUrl })}`
4015
+ )
4016
+ );
4017
+ if (options.bugUrl)
4018
+ links.push(
4019
+ tag(
4020
+ "p",
4021
+ `Like AIReady? ${tag("a", "Report it here", { href: options.bugUrl })}`
4022
+ )
4023
+ );
4024
+ return tag(
4025
+ "div",
4026
+ tag(
4027
+ "p",
4028
+ `Generated by ${tag("strong", "@aiready/" + options.packageName)}` + version
4029
+ ) + links.join(""),
4030
+ { class: "footer" }
4031
+ );
3954
4032
  }
3955
4033
  function wrapInCard(content, title) {
3956
- const titleHtml = title ? `<h2>${title}</h2>` : "";
3957
- return `<div class="card">
3958
- ${titleHtml}
3959
- ${content}
3960
- </div>`;
3961
- }
3962
- function generateCompleteReport(options, bodyContent) {
3963
- return `${generateReportHead(options.title)}
3964
- <body>
3965
- ${bodyContent}
3966
- ${generateReportFooter(options)}
3967
- </body>
3968
- </html>`;
4034
+ return tag("div", (title ? tag("h2", title) : "") + content, {
4035
+ class: "card"
4036
+ });
4037
+ }
4038
+ function generateCompleteReport(options, body) {
4039
+ return generateReportHead(options.title) + tag("body", body + generateReportFooter(options));
4040
+ }
4041
+ function generateStandardHtmlReport(options, stats, sections, score) {
4042
+ const hero = generateReportHero(
4043
+ `${options.emoji || "\u{1F50D}"} AIReady ${options.title}`,
4044
+ `Generated on ${(/* @__PURE__ */ new Date()).toLocaleString()}`
4045
+ );
4046
+ const scoreCard = score ? tag(
4047
+ "div",
4048
+ tag("div", String(score.value), { class: "score-value" }) + tag("div", score.label, { class: "score-label" }),
4049
+ { class: "score-card" }
4050
+ ) : "";
4051
+ const statsCards = generateStatCards(stats);
4052
+ const bodyContent = `
4053
+ ${hero}
4054
+ ${scoreCard}
4055
+ ${statsCards}
4056
+ ${sections.map((s) => wrapInCard(s.content, s.title)).join("\n")}
4057
+ `;
4058
+ return generateCompleteReport(options, bodyContent);
3969
4059
  }
3970
4060
 
3971
4061
  // src/utils/rating-helpers.ts
@@ -4065,7 +4155,8 @@ var DEFAULT_TOOL_WEIGHTS = {
4065
4155
  ["testability-index" /* TestabilityIndex */]: 10,
4066
4156
  ["doc-drift" /* DocDrift */]: 8,
4067
4157
  ["dependency-health" /* DependencyHealth */]: 6,
4068
- ["change-amplification" /* ChangeAmplification */]: 8
4158
+ ["change-amplification" /* ChangeAmplification */]: 8,
4159
+ ["contract-enforcement" /* ContractEnforcement */]: 10
4069
4160
  };
4070
4161
  var TOOL_NAME_MAP = {
4071
4162
  patterns: "pattern-detect" /* PatternDetect */,
@@ -4084,7 +4175,9 @@ var TOOL_NAME_MAP = {
4084
4175
  "deps-health": "dependency-health" /* DependencyHealth */,
4085
4176
  "dependency-health": "dependency-health" /* DependencyHealth */,
4086
4177
  "change-amp": "change-amplification" /* ChangeAmplification */,
4087
- "change-amplification": "change-amplification" /* ChangeAmplification */
4178
+ "change-amplification": "change-amplification" /* ChangeAmplification */,
4179
+ contract: "contract-enforcement" /* ContractEnforcement */,
4180
+ "contract-enforcement": "contract-enforcement" /* ContractEnforcement */
4088
4181
  };
4089
4182
  var ScoringProfile = /* @__PURE__ */ ((ScoringProfile2) => {
4090
4183
  ScoringProfile2["Default"] = "default";
@@ -4098,9 +4191,10 @@ var ScoringProfile = /* @__PURE__ */ ((ScoringProfile2) => {
4098
4191
  var SCORING_PROFILES = {
4099
4192
  ["default" /* Default */]: DEFAULT_TOOL_WEIGHTS,
4100
4193
  ["agentic" /* Agentic */]: {
4101
- ["ai-signal-clarity" /* AiSignalClarity */]: 30,
4102
- ["agent-grounding" /* AgentGrounding */]: 30,
4103
- ["testability-index" /* TestabilityIndex */]: 20,
4194
+ ["ai-signal-clarity" /* AiSignalClarity */]: 25,
4195
+ ["agent-grounding" /* AgentGrounding */]: 25,
4196
+ ["testability-index" /* TestabilityIndex */]: 15,
4197
+ ["contract-enforcement" /* ContractEnforcement */]: 15,
4104
4198
  ["context-analyzer" /* ContextAnalyzer */]: 10,
4105
4199
  ["naming-consistency" /* NamingConsistency */]: 10
4106
4200
  },
@@ -4125,8 +4219,9 @@ var SCORING_PROFILES = {
4125
4219
  ["dependency-health" /* DependencyHealth */]: 10
4126
4220
  },
4127
4221
  ["security" /* Security */]: {
4128
- ["naming-consistency" /* NamingConsistency */]: 40,
4129
- ["testability-index" /* TestabilityIndex */]: 30,
4222
+ ["naming-consistency" /* NamingConsistency */]: 30,
4223
+ ["testability-index" /* TestabilityIndex */]: 25,
4224
+ ["contract-enforcement" /* ContractEnforcement */]: 15,
4130
4225
  ["dependency-health" /* DependencyHealth */]: 20,
4131
4226
  ["context-analyzer" /* ContextAnalyzer */]: 10
4132
4227
  }
@@ -4387,12 +4482,12 @@ function calculateHeuristicConfidence(similarity, tokens, lines) {
4387
4482
  }
4388
4483
 
4389
4484
  // src/utils/cli-factory.ts
4390
- var import_chalk2 = __toESM(require("chalk"));
4485
+ var import_chalk4 = __toESM(require("chalk"));
4391
4486
  function createStandardProgressCallback(toolName) {
4392
4487
  return (processed, total, message) => {
4393
4488
  const percent = Math.round(processed / Math.max(1, total) * 100);
4394
4489
  process.stdout.write(
4395
- `\r\x1B[K [${toolName}] ${import_chalk2.default.cyan(`${percent}%`)} ${message}`
4490
+ `\r\x1B[K [${toolName}] ${import_chalk4.default.cyan(`${percent}%`)} ${message}`
4396
4491
  );
4397
4492
  if (processed === total) {
4398
4493
  process.stdout.write("\n");
@@ -4400,12 +4495,12 @@ function createStandardProgressCallback(toolName) {
4400
4495
  };
4401
4496
  }
4402
4497
  function formatStandardCliResult(toolName, score, issuesCount) {
4403
- const scoreColor = score >= 75 ? import_chalk2.default.green : score >= 50 ? import_chalk2.default.yellow : import_chalk2.default.red;
4498
+ const scoreColor = score >= 75 ? import_chalk4.default.green : score >= 50 ? import_chalk4.default.yellow : import_chalk4.default.red;
4404
4499
  console.log(`
4405
- ${import_chalk2.default.bold(toolName.toUpperCase())} Analysis Complete`);
4500
+ ${import_chalk4.default.bold(toolName.toUpperCase())} Analysis Complete`);
4406
4501
  console.log(` Overall Score: ${scoreColor(score)}/100`);
4407
4502
  console.log(
4408
- ` Issues Found: ${issuesCount > 0 ? import_chalk2.default.red(issuesCount) : import_chalk2.default.green("None")}`
4503
+ ` Issues Found: ${issuesCount > 0 ? import_chalk4.default.red(issuesCount) : import_chalk4.default.green("None")}`
4409
4504
  );
4410
4505
  }
4411
4506
  async function runStandardCliAction(toolName, action) {
@@ -4414,7 +4509,7 @@ async function runStandardCliAction(toolName, action) {
4414
4509
  formatStandardCliResult(toolName, score, issuesCount);
4415
4510
  } catch (error) {
4416
4511
  console.error(
4417
- import_chalk2.default.red(`
4512
+ import_chalk4.default.red(`
4418
4513
  \u274C [${toolName}] critical error: ${error.message}`)
4419
4514
  );
4420
4515
  process.exit(1);
@@ -4422,13 +4517,13 @@ async function runStandardCliAction(toolName, action) {
4422
4517
  }
4423
4518
 
4424
4519
  // src/utils/reporting.ts
4425
- var import_chalk3 = __toESM(require("chalk"));
4520
+ var import_chalk5 = __toESM(require("chalk"));
4426
4521
  function getScoreColor(score) {
4427
- if (score >= 85) return import_chalk3.default.green;
4428
- if (score >= 70) return import_chalk3.default.cyan;
4429
- if (score >= 50) return import_chalk3.default.yellow;
4430
- if (score >= 30) return import_chalk3.default.red;
4431
- return import_chalk3.default.bgRed.white;
4522
+ if (score >= 85) return import_chalk5.default.green;
4523
+ if (score >= 70) return import_chalk5.default.cyan;
4524
+ if (score >= 50) return import_chalk5.default.yellow;
4525
+ if (score >= 30) return import_chalk5.default.red;
4526
+ return import_chalk5.default.bgRed.white;
4432
4527
  }
4433
4528
  function displayStandardConsoleReport(data) {
4434
4529
  const {
@@ -4443,27 +4538,27 @@ function displayStandardConsoleReport(data) {
4443
4538
  noIssuesMessage = "\u2728 No issues found!",
4444
4539
  safetyRating
4445
4540
  } = data;
4446
- console.log(import_chalk3.default.bold(`
4541
+ console.log(import_chalk5.default.bold(`
4447
4542
  ${title}
4448
4543
  `));
4449
4544
  if (safetyRating) {
4450
4545
  if (safetyRating === "blind-risk" || safetyRating === "\u{1F480} blind-risk") {
4451
4546
  console.log(
4452
- import_chalk3.default.bgRed.white.bold(
4547
+ import_chalk5.default.bgRed.white.bold(
4453
4548
  " \u{1F480} BLIND RISK \u2014 NO TESTS DETECTED. AI-GENERATED CHANGES CANNOT BE VERIFIED. "
4454
4549
  )
4455
4550
  );
4456
4551
  console.log();
4457
4552
  } else if (safetyRating === "high-risk" || safetyRating === "\u{1F534} high-risk") {
4458
4553
  console.log(
4459
- import_chalk3.default.red.bold(
4554
+ import_chalk5.default.red.bold(
4460
4555
  ` \u{1F534} HIGH RISK \u2014 Insufficient test coverage. AI changes may introduce silent bugs.`
4461
4556
  )
4462
4557
  );
4463
4558
  console.log();
4464
4559
  }
4465
4560
  }
4466
- const safetyColor = safetyRating ? getSeverityColor(safetyRating, import_chalk3.default) : getScoreColor(score);
4561
+ const safetyColor = safetyRating ? getSeverityColor(safetyRating, import_chalk5.default) : getScoreColor(score);
4467
4562
  if (safetyRating) {
4468
4563
  console.log(
4469
4564
  `AI Change Safety: ${safetyColor(`${getSafetyIcon(safetyRating)} ${safetyRating.toUpperCase()}`)}`
@@ -4473,12 +4568,12 @@ ${title}
4473
4568
  `Score: ${getScoreColor(score)(score + "/100")} (${rating.toUpperCase()})`
4474
4569
  );
4475
4570
  if (stats.length > 0) {
4476
- const statsStr = stats.map((s) => `${s.label}: ${import_chalk3.default.cyan(s.value)}`).join(" ");
4571
+ const statsStr = stats.map((s) => `${s.label}: ${import_chalk5.default.cyan(s.value)}`).join(" ");
4477
4572
  console.log(statsStr);
4478
4573
  }
4479
- console.log(`Analysis Time: ${import_chalk3.default.gray(elapsedTime + "s")}
4574
+ console.log(`Analysis Time: ${import_chalk5.default.gray(elapsedTime + "s")}
4480
4575
  `);
4481
- console.log(import_chalk3.default.bold("\u{1F4D0} Dimension Scores\n"));
4576
+ console.log(import_chalk5.default.bold("\u{1F4D0} Dimension Scores\n"));
4482
4577
  for (const dim of dimensions) {
4483
4578
  const color = getScoreColor(dim.value);
4484
4579
  console.log(
@@ -4486,24 +4581,24 @@ ${title}
4486
4581
  );
4487
4582
  }
4488
4583
  if (issues.length > 0) {
4489
- console.log(import_chalk3.default.bold("\n\u26A0\uFE0F Issues\n"));
4584
+ console.log(import_chalk5.default.bold("\n\u26A0\uFE0F Issues\n"));
4490
4585
  for (const issue of issues) {
4491
- const sev = getSeverityColor(issue.severity, import_chalk3.default);
4586
+ const sev = getSeverityColor(issue.severity, import_chalk5.default);
4492
4587
  console.log(`${sev(issue.severity.toUpperCase())} ${issue.message}`);
4493
4588
  if (issue.suggestion) {
4494
4589
  console.log(
4495
- ` ${import_chalk3.default.dim("\u2192")} ${import_chalk3.default.italic(issue.suggestion)}`
4590
+ ` ${import_chalk5.default.dim("\u2192")} ${import_chalk5.default.italic(issue.suggestion)}`
4496
4591
  );
4497
4592
  }
4498
4593
  console.log();
4499
4594
  }
4500
4595
  } else {
4501
- console.log(import_chalk3.default.green(`
4596
+ console.log(import_chalk5.default.green(`
4502
4597
  ${noIssuesMessage}
4503
4598
  `));
4504
4599
  }
4505
4600
  if (recommendations.length > 0) {
4506
- console.log(import_chalk3.default.bold("\u{1F4A1} Recommendations\n"));
4601
+ console.log(import_chalk5.default.bold("\u{1F4A1} Recommendations\n"));
4507
4602
  recommendations.forEach((rec, i) => {
4508
4603
  console.log(`${i + 1}. ${rec}`);
4509
4604
  });
@@ -5492,7 +5587,17 @@ function calculateAiSignalClarity(params) {
5492
5587
  rating: "minimal",
5493
5588
  signals: [],
5494
5589
  topRisk: "No symbols to analyze",
5495
- recommendations: []
5590
+ recommendations: [],
5591
+ dimensions: {
5592
+ overloadingScore: 100,
5593
+ magicLiteralScore: 100,
5594
+ booleanTrapScore: 100,
5595
+ implicitSideEffectScore: 100,
5596
+ deepCallbackScore: 100,
5597
+ ambiguityScore: 100,
5598
+ documentationScore: 100,
5599
+ sizeScore: 100
5600
+ }
5496
5601
  };
5497
5602
  }
5498
5603
  const overloadRatio = overloadedSymbols / Math.max(1, totalSymbols);
@@ -5605,7 +5710,23 @@ function calculateAiSignalClarity(params) {
5605
5710
  rating,
5606
5711
  signals: signals.filter((s) => s.count > 0),
5607
5712
  topRisk,
5608
- recommendations
5713
+ recommendations,
5714
+ dimensions: {
5715
+ overloadingScore: Math.max(0, 100 - overloadSignal.riskContribution * 5),
5716
+ magicLiteralScore: Math.max(0, 100 - magicSignal.riskContribution * 6.6),
5717
+ booleanTrapScore: Math.max(0, 100 - trapSignal.riskContribution * 6.6),
5718
+ implicitSideEffectScore: Math.max(
5719
+ 0,
5720
+ 100 - sideEffectSignal.riskContribution * 10
5721
+ ),
5722
+ deepCallbackScore: Math.max(
5723
+ 0,
5724
+ 100 - callbackSignal.riskContribution * 10
5725
+ ),
5726
+ ambiguityScore: Math.max(0, 100 - ambiguousSignal.riskContribution * 20),
5727
+ documentationScore: Math.max(0, 100 - undocSignal.riskContribution * 20),
5728
+ sizeScore: Math.max(0, 100 - largeFileSignal.riskContribution * 4)
5729
+ }
5609
5730
  };
5610
5731
  }
5611
5732
 
@@ -6208,6 +6329,104 @@ function emitIssuesAsAnnotations(issues) {
6208
6329
  });
6209
6330
  });
6210
6331
  }
6332
+
6333
+ // src/utils/analysis-orchestrator.ts
6334
+ async function runBatchAnalysis(items, label, toolId, onProgress, processFn, onResult) {
6335
+ let processed = 0;
6336
+ for (const item of items) {
6337
+ processed++;
6338
+ emitProgress(processed, items.length, toolId, label, onProgress);
6339
+ const result = await processFn(item);
6340
+ onResult(result);
6341
+ }
6342
+ }
6343
+
6344
+ // src/utils/spoke-cli-helpers.ts
6345
+ var import_chalk6 = __toESM(require("chalk"));
6346
+ async function executeSpokeCli(name, description, options, analyzeFn) {
6347
+ console.log(import_chalk6.default.cyan(`Analyzing ${description.toLowerCase()}...`));
6348
+ try {
6349
+ const report = await analyzeFn({
6350
+ rootDir: process.cwd(),
6351
+ ...options
6352
+ });
6353
+ console.log(import_chalk6.default.bold(`
6354
+ ${name} Analysis Results:`));
6355
+ console.log(
6356
+ `Rating: ${report.summary.rating.toUpperCase()} (Score: ${report.summary.score})`
6357
+ );
6358
+ if (report.issues && report.issues.length > 0) {
6359
+ console.log(import_chalk6.default.red(`
6360
+ Found ${report.issues.length} issues.`));
6361
+ } else {
6362
+ console.log(import_chalk6.default.green("\nNo issues detected."));
6363
+ }
6364
+ return report;
6365
+ } catch (err) {
6366
+ console.error(import_chalk6.default.red(`Error during ${name} analysis: ${err.message}`));
6367
+ process.exit(1);
6368
+ }
6369
+ }
6370
+
6371
+ // src/utils/test-utils.ts
6372
+ var import_fs5 = require("fs");
6373
+ var import_path6 = require("path");
6374
+ var TEST_PATTERNS = [
6375
+ /\.(test|spec)\.(ts|tsx|js|jsx)$/,
6376
+ /_test\.go$/,
6377
+ /test_.*\.py$/,
6378
+ /.*_test\.py$/,
6379
+ /.*Test\.java$/,
6380
+ /.*Tests\.cs$/,
6381
+ /__tests__\//,
6382
+ /\/tests?\//,
6383
+ /\/e2e\//,
6384
+ /\/fixtures\//
6385
+ ];
6386
+ function isTestFile(filePath, extraPatterns) {
6387
+ if (TEST_PATTERNS.some((p) => p.test(filePath))) return true;
6388
+ if (extraPatterns) return extraPatterns.some((p) => filePath.includes(p));
6389
+ return false;
6390
+ }
6391
+ function detectTestFramework(rootDir) {
6392
+ const manifests = [
6393
+ {
6394
+ file: "package.json",
6395
+ deps: [
6396
+ "jest",
6397
+ "vitest",
6398
+ "mocha",
6399
+ "jasmine",
6400
+ "ava",
6401
+ "tap",
6402
+ "playwright",
6403
+ "cypress"
6404
+ ]
6405
+ },
6406
+ { file: "requirements.txt", deps: ["pytest", "unittest", "nose"] },
6407
+ { file: "pyproject.toml", deps: ["pytest"] },
6408
+ { file: "pom.xml", deps: ["junit", "testng"] },
6409
+ { file: "build.gradle", deps: ["junit", "testng"] },
6410
+ { file: "go.mod", deps: ["testing"] }
6411
+ // go testing is built-in
6412
+ ];
6413
+ for (const m of manifests) {
6414
+ const p = (0, import_path6.join)(rootDir, m.file);
6415
+ if ((0, import_fs5.existsSync)(p)) {
6416
+ if (m.file === "go.mod") return true;
6417
+ try {
6418
+ const content = (0, import_fs5.readFileSync)(p, "utf-8");
6419
+ if (m.deps.some((d) => content.includes(d))) return true;
6420
+ } catch {
6421
+ }
6422
+ }
6423
+ }
6424
+ return false;
6425
+ }
6426
+ function isBuildArtifact(filePath) {
6427
+ const lower = filePath.toLowerCase();
6428
+ return lower.includes("/node_modules/") || lower.includes("/dist/") || lower.includes("/build/") || lower.includes("/out/") || lower.includes("/.next/") || lower.includes("/target/") || lower.includes("/bin/");
6429
+ }
6211
6430
  // Annotate the CommonJS export names for ESM import in node:
6212
6431
  0 && (module.exports = {
6213
6432
  AIReadyConfigSchema,
@@ -6253,6 +6472,7 @@ function emitIssuesAsAnnotations(issues) {
6253
6472
  SeveritySchema,
6254
6473
  SpokeOutputSchema,
6255
6474
  SpokeSummarySchema,
6475
+ TEST_PATTERNS,
6256
6476
  TOOL_NAME_MAP,
6257
6477
  ToolName,
6258
6478
  ToolNameSchema,
@@ -6292,14 +6512,18 @@ function emitIssuesAsAnnotations(issues) {
6292
6512
  clearHistory,
6293
6513
  createProvider,
6294
6514
  createStandardProgressCallback,
6515
+ detectTestFramework,
6295
6516
  displayStandardConsoleReport,
6296
6517
  emitAnnotation,
6297
6518
  emitIssuesAsAnnotations,
6298
6519
  emitProgress,
6520
+ ensureDir,
6299
6521
  estimateCostFromBudget,
6300
6522
  estimateTokens,
6523
+ executeSpokeCli,
6301
6524
  exportHistory,
6302
6525
  extractCodeBlocks,
6526
+ filterBySeverity,
6303
6527
  findLatestReport,
6304
6528
  findLatestScanReport,
6305
6529
  formatAcceptanceRate,
@@ -6315,13 +6539,14 @@ function emitIssuesAsAnnotations(issues) {
6315
6539
  generateReportFooter,
6316
6540
  generateReportHead,
6317
6541
  generateReportHero,
6318
- generateScoreCard,
6542
+ generateStandardHtmlReport,
6319
6543
  generateStatCards,
6320
6544
  generateTable,
6321
6545
  generateValueChain,
6322
6546
  getElapsedTime,
6323
6547
  getFileCommitTimestamps,
6324
6548
  getFileExtension,
6549
+ getFilesByPattern,
6325
6550
  getHistorySummary,
6326
6551
  getLineRangeLastModifiedCached,
6327
6552
  getModelPreset,
@@ -6344,6 +6569,7 @@ function emitIssuesAsAnnotations(issues) {
6344
6569
  getSeverityBadge,
6345
6570
  getSeverityColor,
6346
6571
  getSeverityEnum,
6572
+ getSeverityLabel,
6347
6573
  getSeverityLevel,
6348
6574
  getSeverityValue,
6349
6575
  getSupportedLanguages,
@@ -6358,8 +6584,10 @@ function emitIssuesAsAnnotations(issues) {
6358
6584
  inferPatternType,
6359
6585
  initTreeSitter,
6360
6586
  initializeParsers,
6587
+ isBuildArtifact,
6361
6588
  isFileSupported,
6362
6589
  isSourceFile,
6590
+ isTestFile,
6363
6591
  loadConfig,
6364
6592
  loadMergedConfig,
6365
6593
  loadScoreHistory,
@@ -6367,6 +6595,7 @@ function emitIssuesAsAnnotations(issues) {
6367
6595
  normalizeAnalysisResult,
6368
6596
  normalizeIssue,
6369
6597
  normalizeMetrics,
6598
+ normalizeSeverity,
6370
6599
  normalizeSpokeOutput,
6371
6600
  normalizeToolName,
6372
6601
  parseFileExports,
@@ -6377,6 +6606,7 @@ function emitIssuesAsAnnotations(issues) {
6377
6606
  readFileContent,
6378
6607
  resolveOutputFormat,
6379
6608
  resolveOutputPath,
6609
+ runBatchAnalysis,
6380
6610
  runStandardCliAction,
6381
6611
  saveScoreEntry,
6382
6612
  scanEntries,