@probelabs/visor 0.1.71 → 0.1.73

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/sdk/sdk.js CHANGED
@@ -1069,6 +1069,10 @@ ${schemaString}`);
1069
1069
  // We don't want the agent to modify files
1070
1070
  debug: this.config.debug || false
1071
1071
  };
1072
+ if (this.config.mcpServers && Object.keys(this.config.mcpServers).length > 0) {
1073
+ options.enableMcp = true;
1074
+ options.mcpConfig = { mcpServers: this.config.mcpServers };
1075
+ }
1072
1076
  if (this.config.provider) {
1073
1077
  const providerOverride = this.config.provider === "claude-code" || this.config.provider === "bedrock" ? "anthropic" : this.config.provider === "anthropic" || this.config.provider === "openai" || this.config.provider === "google" ? this.config.provider : void 0;
1074
1078
  if (providerOverride) {
@@ -1536,7 +1540,7 @@ var init_reviewer = __esm({
1536
1540
  if (config && checks && checks.length > 0) {
1537
1541
  const { CheckExecutionEngine: CheckExecutionEngine2 } = await Promise.resolve().then(() => (init_check_execution_engine(), check_execution_engine_exports));
1538
1542
  const engine = new CheckExecutionEngine2();
1539
- const groupedResults = await engine.executeGroupedChecks(
1543
+ const { results } = await engine.executeGroupedChecks(
1540
1544
  prInfo,
1541
1545
  checks,
1542
1546
  void 0,
@@ -1544,7 +1548,7 @@ var init_reviewer = __esm({
1544
1548
  void 0,
1545
1549
  debug
1546
1550
  );
1547
- return groupedResults;
1551
+ return results;
1548
1552
  }
1549
1553
  throw new Error(
1550
1554
  "No configuration provided. Please create a .visor.yaml file with check definitions. Built-in prompts have been removed - all checks must be explicitly configured."
@@ -2686,15 +2690,13 @@ var init_ai_check_provider = __esm({
2686
2690
  Object.assign(mcpServers, config.ai.mcpServers);
2687
2691
  }
2688
2692
  if (Object.keys(mcpServers).length > 0) {
2693
+ aiConfig.mcpServers = mcpServers;
2689
2694
  const mcpConfig = { mcpServers };
2690
2695
  const mcpTools = await this.setupMcpTools(mcpConfig);
2691
- if (mcpTools.length > 0) {
2692
- aiConfig.tools = mcpTools;
2693
- if (aiConfig.debug) {
2694
- console.error(
2695
- `\u{1F527} Debug: AI check configured with ${mcpTools.length} MCP tools from ${Object.keys(mcpServers).length} servers`
2696
- );
2697
- }
2696
+ if (aiConfig.debug) {
2697
+ console.error(
2698
+ `\u{1F527} Debug: AI check MCP configured with ${Object.keys(mcpServers).length} servers; discovered ${mcpTools.length} tools`
2699
+ );
2698
2700
  }
2699
2701
  }
2700
2702
  const processedPrompt = await this.processPrompt(
@@ -6014,6 +6016,7 @@ var init_check_execution_engine = __esm({
6014
6016
  config;
6015
6017
  webhookContext;
6016
6018
  routingSandbox;
6019
+ executionStats = /* @__PURE__ */ new Map();
6017
6020
  constructor(workingDirectory) {
6018
6021
  this.workingDirectory = workingDirectory || process.cwd();
6019
6022
  this.gitAnalyzer = new GitRepositoryAnalyzer(this.workingDirectory);
@@ -6552,12 +6555,14 @@ ${expr}
6552
6555
  apiCallDetails: reviewSummary.debug.apiCallDetails
6553
6556
  };
6554
6557
  }
6558
+ const executionStatistics = this.buildExecutionStatistics();
6555
6559
  return {
6556
6560
  repositoryInfo,
6557
6561
  reviewSummary,
6558
6562
  executionTime,
6559
6563
  timestamp,
6560
6564
  checksExecuted: filteredChecks,
6565
+ executionStatistics,
6561
6566
  debug: debugInfo
6562
6567
  };
6563
6568
  } catch (error) {
@@ -6750,7 +6755,7 @@ ${expr}
6750
6755
  });
6751
6756
  }
6752
6757
  /**
6753
- * Execute review checks and return grouped results for new architecture
6758
+ * Execute review checks and return grouped results with statistics for new architecture
6754
6759
  */
6755
6760
  async executeGroupedChecks(prInfo, checks, timeout, config, outputFormat, debug, maxParallelism, failFast, tagFilter) {
6756
6761
  const logFn = outputFormat === "json" || outputFormat === "sarif" ? debug ? console.error : () => {
@@ -6780,7 +6785,10 @@ ${expr}
6780
6785
  checks = tagFilteredChecks;
6781
6786
  if (checks.length === 0) {
6782
6787
  logger.warn("\u26A0\uFE0F No checks remain after tag filtering");
6783
- return {};
6788
+ return {
6789
+ results: {},
6790
+ statistics: this.buildExecutionStatistics()
6791
+ };
6784
6792
  }
6785
6793
  if (!config?.checks) {
6786
6794
  throw new Error("Config with check definitions required for grouped execution");
@@ -6820,9 +6828,15 @@ ${expr}
6820
6828
  );
6821
6829
  const groupedResults = {};
6822
6830
  groupedResults[checkResult.group] = [checkResult];
6823
- return groupedResults;
6831
+ return {
6832
+ results: groupedResults,
6833
+ statistics: this.buildExecutionStatistics()
6834
+ };
6824
6835
  }
6825
- return {};
6836
+ return {
6837
+ results: {},
6838
+ statistics: this.buildExecutionStatistics()
6839
+ };
6826
6840
  }
6827
6841
  /**
6828
6842
  * Execute single check and return grouped result
@@ -6868,7 +6882,7 @@ ${expr}
6868
6882
  };
6869
6883
  }
6870
6884
  /**
6871
- * Execute multiple checks with dependency awareness - return grouped results
6885
+ * Execute multiple checks with dependency awareness - return grouped results with statistics
6872
6886
  */
6873
6887
  async executeGroupedDependencyAwareChecks(prInfo, checks, timeout, config, logFn, debug, maxParallelism, failFast) {
6874
6888
  const reviewSummary = await this.executeDependencyAwareChecks(
@@ -6881,7 +6895,17 @@ ${expr}
6881
6895
  maxParallelism,
6882
6896
  failFast
6883
6897
  );
6884
- return await this.convertReviewSummaryToGroupedResults(reviewSummary, checks, config, prInfo);
6898
+ const executionStatistics = this.buildExecutionStatistics();
6899
+ const groupedResults = await this.convertReviewSummaryToGroupedResults(
6900
+ reviewSummary,
6901
+ checks,
6902
+ config,
6903
+ prInfo
6904
+ );
6905
+ return {
6906
+ results: groupedResults,
6907
+ statistics: executionStatistics
6908
+ };
6885
6909
  }
6886
6910
  /**
6887
6911
  * Convert ReviewSummary to GroupedCheckResults
@@ -7144,9 +7168,9 @@ ${expr}
7144
7168
  let shouldStopExecution = false;
7145
7169
  let completedChecksCount = 0;
7146
7170
  const totalChecksCount = stats.totalChecks;
7147
- let skippedChecksCount = 0;
7148
- let failedChecksCount = 0;
7149
- const executionStartTime = Date.now();
7171
+ for (const checkName of checks) {
7172
+ this.initializeCheckStats(checkName);
7173
+ }
7150
7174
  for (let levelIndex = 0; levelIndex < dependencyGraph.executionOrder.length && !shouldStopExecution; levelIndex++) {
7151
7175
  const executionGroup = dependencyGraph.executionOrder[levelIndex];
7152
7176
  const checksInLevel = executionGroup.parallel;
@@ -7276,11 +7300,13 @@ ${expr}
7276
7300
  }
7277
7301
  let finalResult;
7278
7302
  if (isForEachDependent && forEachParentName) {
7303
+ this.recordForEachPreview(checkName, forEachItems);
7279
7304
  if (debug) {
7280
7305
  log2(
7281
7306
  `\u{1F504} Debug: Check "${checkName}" depends on forEach check "${forEachParentName}", executing ${forEachItems.length} times`
7282
7307
  );
7283
7308
  }
7309
+ logger.info(` Processing ${forEachItems.length} items...`);
7284
7310
  const allIssues = [];
7285
7311
  const allOutputs = [];
7286
7312
  const aggregatedContents = [];
@@ -7337,6 +7363,7 @@ ${expr}
7337
7363
  `\u{1F504} Debug: Executing check "${checkName}" for item ${itemIndex + 1}/${forEachItems.length}`
7338
7364
  );
7339
7365
  }
7366
+ const iterationStart = this.recordIterationStart(checkName);
7340
7367
  const itemResult = await this.executeWithRouting(
7341
7368
  checkName,
7342
7369
  checkConfig,
@@ -7356,6 +7383,17 @@ ${expr}
7356
7383
  parent: forEachParentName
7357
7384
  }
7358
7385
  );
7386
+ const iterationDuration = (Date.now() - iterationStart) / 1e3;
7387
+ this.recordIterationComplete(
7388
+ checkName,
7389
+ iterationStart,
7390
+ true,
7391
+ itemResult.issues || [],
7392
+ itemResult.output
7393
+ );
7394
+ logger.info(
7395
+ ` \u2714 ${itemIndex + 1}/${forEachItems.length} (${iterationDuration.toFixed(1)}s)`
7396
+ );
7359
7397
  return { index: itemIndex, itemResult };
7360
7398
  });
7361
7399
  const forEachConcurrency = Math.max(
@@ -7417,8 +7455,8 @@ ${expr}
7417
7455
  debug
7418
7456
  );
7419
7457
  if (!shouldRun) {
7420
- skippedChecksCount++;
7421
- logger.info(`\u23ED Skipping check: ${checkName} (if condition evaluated to false)`);
7458
+ this.recordSkip(checkName, "if_condition", checkConfig.if);
7459
+ logger.info(`\u23ED Skipped (if: ${this.truncate(checkConfig.if, 40)})`);
7422
7460
  return {
7423
7461
  checkName,
7424
7462
  error: null,
@@ -7442,6 +7480,13 @@ ${expr}
7442
7480
  debug,
7443
7481
  results
7444
7482
  );
7483
+ this.recordIterationComplete(
7484
+ checkName,
7485
+ checkStartTime,
7486
+ true,
7487
+ finalResult.issues || [],
7488
+ finalResult.output
7489
+ );
7445
7490
  if (checkConfig.forEach) {
7446
7491
  try {
7447
7492
  const finalResultWithOutput = finalResult;
@@ -7471,7 +7516,22 @@ ${expr}
7471
7516
  };
7472
7517
  const checkDuration = ((Date.now() - checkStartTime) / 1e3).toFixed(1);
7473
7518
  const issueCount = enrichedIssues.length;
7474
- if (issueCount > 0) {
7519
+ const checkStats = this.executionStats.get(checkName);
7520
+ if (checkStats && checkStats.totalRuns > 1) {
7521
+ if (issueCount > 0) {
7522
+ logger.success(
7523
+ `Check complete: ${checkName} (${checkDuration}s) - ${checkStats.totalRuns} runs, ${issueCount} issue${issueCount === 1 ? "" : "s"}`
7524
+ );
7525
+ } else {
7526
+ logger.success(
7527
+ `Check complete: ${checkName} (${checkDuration}s) - ${checkStats.totalRuns} runs`
7528
+ );
7529
+ }
7530
+ } else if (checkStats && checkStats.outputsProduced && checkStats.outputsProduced > 0) {
7531
+ logger.success(
7532
+ `Check complete: ${checkName} (${checkDuration}s) - ${checkStats.outputsProduced} items`
7533
+ );
7534
+ } else if (issueCount > 0) {
7475
7535
  logger.success(
7476
7536
  `Check complete: ${checkName} (${checkDuration}s) - ${issueCount} issue${issueCount === 1 ? "" : "s"} found`
7477
7537
  );
@@ -7484,9 +7544,10 @@ ${expr}
7484
7544
  result: enrichedResult
7485
7545
  };
7486
7546
  } catch (error) {
7487
- failedChecksCount++;
7488
7547
  const errorMessage = error instanceof Error ? error.message : String(error);
7489
7548
  const checkDuration = ((Date.now() - checkStartTime) / 1e3).toFixed(1);
7549
+ this.recordError(checkName, error instanceof Error ? error : new Error(String(error)));
7550
+ this.recordIterationComplete(checkName, checkStartTime, false, [], void 0);
7490
7551
  logger.error(`\u2716 Check failed: ${checkName} (${checkDuration}s) - ${errorMessage}`);
7491
7552
  if (debug) {
7492
7553
  log2(`\u{1F527} Debug: Error in check ${checkName}: ${errorMessage}`);
@@ -7594,19 +7655,13 @@ ${expr}
7594
7655
  }
7595
7656
  }
7596
7657
  }
7597
- const executionDuration = ((Date.now() - executionStartTime) / 1e3).toFixed(1);
7598
- const successfulChecks = totalChecksCount - failedChecksCount - skippedChecksCount;
7599
- logger.info("");
7600
- logger.step(`Execution complete (${executionDuration}s)`);
7601
- logger.info(` \u2714 Successful: ${successfulChecks}/${totalChecksCount}`);
7602
- if (skippedChecksCount > 0) {
7603
- logger.info(` \u23ED Skipped: ${skippedChecksCount}`);
7604
- }
7605
- if (failedChecksCount > 0) {
7606
- logger.info(` \u2716 Failed: ${failedChecksCount}`);
7658
+ const executionStatistics = this.buildExecutionStatistics();
7659
+ if (logFn === console.log) {
7660
+ this.logExecutionSummary(executionStatistics);
7607
7661
  }
7608
7662
  if (shouldStopExecution) {
7609
- logger.warn(` \u26A0\uFE0F Execution stopped early due to fail-fast`);
7663
+ logger.info("");
7664
+ logger.warn(`\u26A0\uFE0F Execution stopped early due to fail-fast`);
7610
7665
  }
7611
7666
  if (debug) {
7612
7667
  if (shouldStopExecution) {
@@ -8561,6 +8616,220 @@ ${result.value.result.debug.rawResponse}`;
8561
8616
  }
8562
8617
  return "pr_updated";
8563
8618
  }
8619
+ /**
8620
+ * Initialize execution statistics for a check
8621
+ */
8622
+ initializeCheckStats(checkName) {
8623
+ this.executionStats.set(checkName, {
8624
+ checkName,
8625
+ totalRuns: 0,
8626
+ successfulRuns: 0,
8627
+ failedRuns: 0,
8628
+ skipped: false,
8629
+ totalDuration: 0,
8630
+ issuesFound: 0,
8631
+ issuesBySeverity: {
8632
+ critical: 0,
8633
+ error: 0,
8634
+ warning: 0,
8635
+ info: 0
8636
+ },
8637
+ perIterationDuration: []
8638
+ });
8639
+ }
8640
+ /**
8641
+ * Record the start of a check iteration
8642
+ * Returns the start timestamp for duration tracking
8643
+ */
8644
+ recordIterationStart(_checkName) {
8645
+ return Date.now();
8646
+ }
8647
+ /**
8648
+ * Record completion of a check iteration
8649
+ */
8650
+ recordIterationComplete(checkName, startTime, success, issues, output) {
8651
+ const stats = this.executionStats.get(checkName);
8652
+ if (!stats) return;
8653
+ const duration = Date.now() - startTime;
8654
+ stats.totalRuns++;
8655
+ if (success) {
8656
+ stats.successfulRuns++;
8657
+ } else {
8658
+ stats.failedRuns++;
8659
+ }
8660
+ stats.totalDuration += duration;
8661
+ stats.perIterationDuration.push(duration);
8662
+ for (const issue of issues) {
8663
+ stats.issuesFound++;
8664
+ if (issue.severity === "critical") stats.issuesBySeverity.critical++;
8665
+ else if (issue.severity === "error") stats.issuesBySeverity.error++;
8666
+ else if (issue.severity === "warning") stats.issuesBySeverity.warning++;
8667
+ else if (issue.severity === "info") stats.issuesBySeverity.info++;
8668
+ }
8669
+ if (output !== void 0) {
8670
+ stats.outputsProduced = (stats.outputsProduced || 0) + 1;
8671
+ }
8672
+ }
8673
+ /**
8674
+ * Record that a check was skipped
8675
+ */
8676
+ recordSkip(checkName, reason, condition) {
8677
+ const stats = this.executionStats.get(checkName);
8678
+ if (!stats) return;
8679
+ stats.skipped = true;
8680
+ stats.skipReason = reason;
8681
+ if (condition) {
8682
+ stats.skipCondition = condition;
8683
+ }
8684
+ }
8685
+ /**
8686
+ * Record forEach preview items
8687
+ */
8688
+ recordForEachPreview(checkName, items) {
8689
+ const stats = this.executionStats.get(checkName);
8690
+ if (!stats || !items.length) return;
8691
+ const preview = items.slice(0, 3).map((item) => {
8692
+ const str = typeof item === "string" ? item : JSON.stringify(item);
8693
+ return str.length > 50 ? str.substring(0, 47) + "..." : str;
8694
+ });
8695
+ if (items.length > 3) {
8696
+ preview.push(`...${items.length - 3} more`);
8697
+ }
8698
+ stats.forEachPreview = preview;
8699
+ }
8700
+ /**
8701
+ * Record an error for a check
8702
+ */
8703
+ recordError(checkName, error) {
8704
+ const stats = this.executionStats.get(checkName);
8705
+ if (!stats) return;
8706
+ stats.errorMessage = error instanceof Error ? error.message : String(error);
8707
+ }
8708
+ /**
8709
+ * Build the final execution statistics object
8710
+ */
8711
+ buildExecutionStatistics() {
8712
+ const checks = Array.from(this.executionStats.values());
8713
+ const totalExecutions = checks.reduce((sum, s) => sum + s.totalRuns, 0);
8714
+ const successfulExecutions = checks.reduce((sum, s) => sum + s.successfulRuns, 0);
8715
+ const failedExecutions = checks.reduce((sum, s) => sum + s.failedRuns, 0);
8716
+ const skippedChecks = checks.filter((s) => s.skipped).length;
8717
+ const totalDuration = checks.reduce((sum, s) => sum + s.totalDuration, 0);
8718
+ return {
8719
+ totalChecksConfigured: checks.length,
8720
+ totalExecutions,
8721
+ successfulExecutions,
8722
+ failedExecutions,
8723
+ skippedChecks,
8724
+ totalDuration,
8725
+ checks
8726
+ };
8727
+ }
8728
+ /**
8729
+ * Truncate a string to max length with ellipsis
8730
+ */
8731
+ truncate(str, maxLen) {
8732
+ if (str.length <= maxLen) return str;
8733
+ return str.substring(0, maxLen - 3) + "...";
8734
+ }
8735
+ /**
8736
+ * Format the Status column for execution summary table
8737
+ */
8738
+ formatStatusColumn(stats) {
8739
+ if (stats.skipped) {
8740
+ if (stats.skipReason === "if_condition") return "\u23ED if";
8741
+ if (stats.skipReason === "fail_fast") return "\u23ED ff";
8742
+ if (stats.skipReason === "dependency_failed") return "\u23ED dep";
8743
+ return "\u23ED";
8744
+ }
8745
+ if (stats.totalRuns === 0) return "-";
8746
+ const symbol = stats.failedRuns === 0 ? "\u2714" : stats.successfulRuns === 0 ? "\u2716" : "\u2714/\u2716";
8747
+ if (stats.totalRuns > 1) {
8748
+ if (stats.failedRuns > 0 && stats.successfulRuns > 0) {
8749
+ return `${symbol} ${stats.successfulRuns}/${stats.totalRuns}`;
8750
+ } else {
8751
+ return `${symbol} \xD7${stats.totalRuns}`;
8752
+ }
8753
+ }
8754
+ return symbol;
8755
+ }
8756
+ /**
8757
+ * Format the Details column for execution summary table
8758
+ */
8759
+ formatDetailsColumn(stats) {
8760
+ const parts = [];
8761
+ if (stats.outputsProduced && stats.outputsProduced > 0) {
8762
+ parts.push(`\u2192${stats.outputsProduced}`);
8763
+ }
8764
+ if (stats.issuesBySeverity.critical > 0) {
8765
+ parts.push(`${stats.issuesBySeverity.critical}\u{1F534}`);
8766
+ }
8767
+ if (stats.issuesBySeverity.warning > 0) {
8768
+ parts.push(`${stats.issuesBySeverity.warning}\u26A0\uFE0F`);
8769
+ }
8770
+ if (stats.issuesBySeverity.info > 0 && stats.issuesBySeverity.critical === 0 && stats.issuesBySeverity.warning === 0) {
8771
+ parts.push(`${stats.issuesBySeverity.info}\u{1F4A1}`);
8772
+ }
8773
+ if (stats.errorMessage) {
8774
+ parts.push(this.truncate(stats.errorMessage, 20));
8775
+ } else if (stats.skipCondition) {
8776
+ parts.push(this.truncate(stats.skipCondition, 20));
8777
+ }
8778
+ return parts.join(" ");
8779
+ }
8780
+ /**
8781
+ * Log the execution summary table
8782
+ */
8783
+ logExecutionSummary(stats) {
8784
+ const totalIssues = stats.checks.reduce((sum, s) => sum + s.issuesFound, 0);
8785
+ const criticalIssues = stats.checks.reduce((sum, s) => sum + s.issuesBySeverity.critical, 0);
8786
+ const warningIssues = stats.checks.reduce((sum, s) => sum + s.issuesBySeverity.warning, 0);
8787
+ const durationSec = (stats.totalDuration / 1e3).toFixed(1);
8788
+ const summaryTable = new (require("cli-table3"))({
8789
+ style: {
8790
+ head: [],
8791
+ border: []
8792
+ },
8793
+ colWidths: [41]
8794
+ });
8795
+ summaryTable.push(
8796
+ [`Execution Complete (${durationSec}s)`],
8797
+ [`Checks: ${stats.totalChecksConfigured} configured \u2192 ${stats.totalExecutions} executions`],
8798
+ [
8799
+ `Status: ${stats.successfulExecutions} \u2714 \u2502 ${stats.failedExecutions} \u2716 \u2502 ${stats.skippedChecks} \u23ED`
8800
+ ]
8801
+ );
8802
+ if (totalIssues > 0) {
8803
+ let issuesLine = `Issues: ${totalIssues} total`;
8804
+ if (criticalIssues > 0) issuesLine += ` (${criticalIssues} \u{1F534}`;
8805
+ if (warningIssues > 0) issuesLine += `${criticalIssues > 0 ? " " : " ("}${warningIssues} \u26A0\uFE0F)`;
8806
+ else if (criticalIssues > 0) issuesLine += ")";
8807
+ summaryTable.push([issuesLine]);
8808
+ }
8809
+ logger.info("");
8810
+ logger.info(summaryTable.toString());
8811
+ logger.info("");
8812
+ logger.info("Check Details:");
8813
+ const detailsTable = new (require("cli-table3"))({
8814
+ head: ["Check", "Duration", "Status", "Details"],
8815
+ colWidths: [21, 10, 10, 21],
8816
+ style: {
8817
+ head: ["cyan"],
8818
+ border: ["grey"]
8819
+ }
8820
+ });
8821
+ for (const checkStats of stats.checks) {
8822
+ const duration = checkStats.skipped ? "-" : `${(checkStats.totalDuration / 1e3).toFixed(1)}s`;
8823
+ const status = this.formatStatusColumn(checkStats);
8824
+ const details = this.formatDetailsColumn(checkStats);
8825
+ detailsTable.push([checkStats.checkName, duration, status, details]);
8826
+ }
8827
+ logger.info(detailsTable.toString());
8828
+ logger.info("");
8829
+ logger.info(
8830
+ "Legend: \u2714=success \u2502 \u2716=failed \u2502 \u23ED=skipped \u2502 \xD7N=iterations \u2502 \u2192N=outputs \u2502 N\u{1F534}=critical \u2502 N\u26A0\uFE0F=warnings"
8831
+ );
8832
+ }
8564
8833
  };
8565
8834
  }
8566
8835
  });