@probelabs/visor 0.1.82 → 0.1.84

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.
@@ -248,6 +248,8 @@ interface CheckConfig {
248
248
  claude_code?: ClaudeCodeConfig;
249
249
  /** Environment variables for this check */
250
250
  env?: EnvConfig;
251
+ /** Timeout in seconds for command execution (default: 60) */
252
+ timeout?: number;
251
253
  /** Check IDs that this check depends on (optional) */
252
254
  depends_on?: string[];
253
255
  /** Group name for comment separation (e.g., "code-review", "pr-overview") - optional */
@@ -258,8 +260,8 @@ interface CheckConfig {
258
260
  template?: CustomTemplateConfig;
259
261
  /** Condition to determine if check should run - runs if expression evaluates to true */
260
262
  if?: string;
261
- /** Whether to reuse AI session from dependency checks (only works with depends_on) */
262
- reuse_ai_session?: boolean;
263
+ /** Check name to reuse AI session from, or true to use first dependency (only works with depends_on) */
264
+ reuse_ai_session?: string | boolean;
263
265
  /** Simple fail condition - fails check if expression evaluates to true */
264
266
  fail_if?: string;
265
267
  /** Check-specific failure conditions - optional (deprecated, use fail_if) */
package/dist/sdk/sdk.d.ts CHANGED
@@ -248,6 +248,8 @@ interface CheckConfig {
248
248
  claude_code?: ClaudeCodeConfig;
249
249
  /** Environment variables for this check */
250
250
  env?: EnvConfig;
251
+ /** Timeout in seconds for command execution (default: 60) */
252
+ timeout?: number;
251
253
  /** Check IDs that this check depends on (optional) */
252
254
  depends_on?: string[];
253
255
  /** Group name for comment separation (e.g., "code-review", "pr-overview") - optional */
@@ -258,8 +260,8 @@ interface CheckConfig {
258
260
  template?: CustomTemplateConfig;
259
261
  /** Condition to determine if check should run - runs if expression evaluates to true */
260
262
  if?: string;
261
- /** Whether to reuse AI session from dependency checks (only works with depends_on) */
262
- reuse_ai_session?: boolean;
263
+ /** Check name to reuse AI session from, or true to use first dependency (only works with depends_on) */
264
+ reuse_ai_session?: string | boolean;
263
265
  /** Simple fail condition - fails check if expression evaluates to true */
264
266
  fail_if?: string;
265
267
  /** Check-specific failure conditions - optional (deprecated, use fail_if) */
package/dist/sdk/sdk.js CHANGED
@@ -4127,6 +4127,7 @@ var init_command_check_provider = __esm({
4127
4127
  try {
4128
4128
  const parsed = JSON.parse(rawOutput);
4129
4129
  output = parsed;
4130
+ logger.debug(`\u{1F527} Debug: Parsed entire output as JSON successfully`);
4130
4131
  } catch {
4131
4132
  const extracted2 = this.extractJsonFromEnd(rawOutput);
4132
4133
  if (extracted2) {
@@ -4135,13 +4136,28 @@ var init_command_check_provider = __esm({
4135
4136
  logger.debug(
4136
4137
  `\u{1F527} Debug: Extracted and parsed JSON from end of output (${extracted2.length} chars from ${rawOutput.length} total)`
4137
4138
  );
4138
- } catch {
4139
+ logger.debug(`\u{1F527} Debug: Extracted JSON content: ${extracted2.slice(0, 200)}`);
4140
+ } catch (parseError) {
4141
+ logger.debug(
4142
+ `\u{1F527} Debug: Extracted text is not valid JSON: ${parseError instanceof Error ? parseError.message : "Unknown error"}`
4143
+ );
4139
4144
  output = rawOutput;
4140
4145
  }
4141
4146
  } else {
4147
+ logger.debug(`\u{1F527} Debug: No JSON found in output, keeping as string`);
4142
4148
  output = rawOutput;
4143
4149
  }
4144
4150
  }
4151
+ if (output !== rawOutput) {
4152
+ try {
4153
+ const outputType = Array.isArray(output) ? `array[${output.length}]` : typeof output;
4154
+ logger.debug(`\u{1F527} Debug: Parsed output type: ${outputType}`);
4155
+ if (typeof output === "object" && output !== null) {
4156
+ logger.debug(`\u{1F527} Debug: Parsed output keys: ${Object.keys(output).join(", ")}`);
4157
+ }
4158
+ } catch {
4159
+ }
4160
+ }
4145
4161
  let finalOutput = output;
4146
4162
  if (transform) {
4147
4163
  try {
@@ -4316,6 +4332,16 @@ return ${returnTarget};
4316
4332
  return result;
4317
4333
  } catch (error) {
4318
4334
  const errorMessage = error instanceof Error ? error.message : "Unknown error";
4335
+ let isTimeout = false;
4336
+ if (error && typeof error === "object") {
4337
+ const execError = error;
4338
+ if (execError.killed && execError.signal === "SIGTERM") {
4339
+ isTimeout = true;
4340
+ }
4341
+ if (execError.code === "ETIMEDOUT") {
4342
+ isTimeout = true;
4343
+ }
4344
+ }
4319
4345
  let stderrOutput = "";
4320
4346
  if (error && typeof error === "object") {
4321
4347
  const execError = error;
@@ -4323,17 +4349,32 @@ return ${returnTarget};
4323
4349
  stderrOutput = execError.stderr.trim();
4324
4350
  }
4325
4351
  }
4326
- const detailedMessage = stderrOutput ? `Command execution failed: ${errorMessage}
4352
+ let detailedMessage;
4353
+ let ruleId;
4354
+ if (isTimeout) {
4355
+ const timeoutSeconds = config.timeout || 60;
4356
+ detailedMessage = `Command execution timed out after ${timeoutSeconds} seconds`;
4357
+ if (stderrOutput) {
4358
+ detailedMessage += `
4359
+
4360
+ Stderr output:
4361
+ ${stderrOutput}`;
4362
+ }
4363
+ ruleId = "command/timeout";
4364
+ } else {
4365
+ detailedMessage = stderrOutput ? `Command execution failed: ${errorMessage}
4327
4366
 
4328
4367
  Stderr output:
4329
4368
  ${stderrOutput}` : `Command execution failed: ${errorMessage}`;
4369
+ ruleId = "command/execution_error";
4370
+ }
4330
4371
  logger.error(`\u2717 ${detailedMessage}`);
4331
4372
  return {
4332
4373
  issues: [
4333
4374
  {
4334
4375
  file: "command",
4335
4376
  line: 0,
4336
- ruleId: "command/execution_error",
4377
+ ruleId,
4337
4378
  message: detailedMessage,
4338
4379
  severity: "error",
4339
4380
  category: "logic"
@@ -5411,6 +5452,14 @@ var init_failure_condition_evaluator = __esm({
5411
5452
  */
5412
5453
  buildEvaluationContext(checkName, checkSchema, checkGroup, reviewSummary, previousOutputs) {
5413
5454
  const { issues, debug } = reviewSummary;
5455
+ const reviewSummaryWithOutput = reviewSummary;
5456
+ const {
5457
+ output: extractedOutput,
5458
+ // Exclude issues from otherFields since we handle it separately
5459
+ issues: _issues,
5460
+ // eslint-disable-line @typescript-eslint/no-unused-vars
5461
+ ...otherFields
5462
+ } = reviewSummaryWithOutput;
5414
5463
  const context = {
5415
5464
  output: {
5416
5465
  issues: (issues || []).map((issue) => ({
@@ -5427,9 +5476,9 @@ var init_failure_condition_evaluator = __esm({
5427
5476
  replacement: issue.replacement
5428
5477
  })),
5429
5478
  // Include additional schema-specific data from reviewSummary
5430
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
5431
- ...reviewSummary
5432
- // Pass through any additional fields
5479
+ ...otherFields,
5480
+ // Spread the extracted output directly (avoid output.output nesting)
5481
+ ...extractedOutput && typeof extractedOutput === "object" ? extractedOutput : {}
5433
5482
  },
5434
5483
  outputs: (() => {
5435
5484
  if (!previousOutputs) return {};
@@ -6869,6 +6918,31 @@ ${expr}
6869
6918
  };
6870
6919
  providerConfig.forEach = checkConfig.forEach;
6871
6920
  const result = await provider.execute(prInfo, providerConfig);
6921
+ if (checkConfig.forEach && (!result.issues || result.issues.length === 0)) {
6922
+ const reviewSummaryWithOutput = result;
6923
+ const validation = this.validateAndNormalizeForEachOutput(
6924
+ checkName,
6925
+ reviewSummaryWithOutput.output,
6926
+ checkConfig.group
6927
+ );
6928
+ if (!validation.isValid) {
6929
+ return validation.error;
6930
+ }
6931
+ }
6932
+ if (config && (config.fail_if || checkConfig.fail_if)) {
6933
+ const failureResults = await this.evaluateFailureConditions(checkName, result, config);
6934
+ if (failureResults.length > 0) {
6935
+ const failureIssues = failureResults.filter((f) => f.failed).map((f) => ({
6936
+ file: "system",
6937
+ line: 0,
6938
+ ruleId: f.conditionName,
6939
+ message: f.message || `Failure condition met: ${f.expression}`,
6940
+ severity: f.severity || "error",
6941
+ category: "logic"
6942
+ }));
6943
+ result.issues = [...result.issues || [], ...failureIssues];
6944
+ }
6945
+ }
6872
6946
  const content = await this.renderCheckContent(checkName, result, checkConfig, prInfo);
6873
6947
  return {
6874
6948
  checkName,
@@ -6879,6 +6953,53 @@ ${expr}
6879
6953
  // Include structured issues
6880
6954
  };
6881
6955
  }
6956
+ /**
6957
+ * Validate and normalize forEach output
6958
+ * Returns normalized array or throws validation error result
6959
+ */
6960
+ validateAndNormalizeForEachOutput(checkName, output, checkGroup) {
6961
+ if (output === void 0) {
6962
+ logger.error(`\u2717 forEach check "${checkName}" produced undefined output`);
6963
+ return {
6964
+ isValid: false,
6965
+ error: {
6966
+ checkName,
6967
+ content: "",
6968
+ group: checkGroup || "default",
6969
+ issues: [
6970
+ {
6971
+ file: "system",
6972
+ line: 0,
6973
+ ruleId: "forEach/undefined_output",
6974
+ message: `forEach check "${checkName}" produced undefined output. Verify your command outputs valid data and your transform_js returns a value.`,
6975
+ severity: "error",
6976
+ category: "logic"
6977
+ }
6978
+ ]
6979
+ }
6980
+ };
6981
+ }
6982
+ let normalizedOutput;
6983
+ if (Array.isArray(output)) {
6984
+ normalizedOutput = output;
6985
+ } else if (typeof output === "string") {
6986
+ try {
6987
+ const parsed = JSON.parse(output);
6988
+ normalizedOutput = Array.isArray(parsed) ? parsed : [parsed];
6989
+ } catch {
6990
+ normalizedOutput = [output];
6991
+ }
6992
+ } else if (output === null) {
6993
+ normalizedOutput = [];
6994
+ } else {
6995
+ normalizedOutput = [output];
6996
+ }
6997
+ logger.info(` Found ${normalizedOutput.length} items for forEach iteration`);
6998
+ return {
6999
+ isValid: true,
7000
+ normalizedOutput
7001
+ };
7002
+ }
6882
7003
  /**
6883
7004
  * Execute multiple checks with dependency awareness - return grouped results with statistics
6884
7005
  */
@@ -7088,10 +7209,14 @@ ${expr}
7088
7209
  const checkConfig = config.checks[checkName];
7089
7210
  if (checkConfig) {
7090
7211
  dependencies[checkName] = checkConfig.depends_on || [];
7091
- if (checkConfig.reuse_ai_session === true) {
7212
+ if (checkConfig.reuse_ai_session) {
7092
7213
  sessionReuseChecks.add(checkName);
7093
- if (checkConfig.depends_on && checkConfig.depends_on.length > 0) {
7094
- sessionProviders.set(checkName, checkConfig.depends_on[0]);
7214
+ if (typeof checkConfig.reuse_ai_session === "string") {
7215
+ sessionProviders.set(checkName, checkConfig.reuse_ai_session);
7216
+ } else if (checkConfig.reuse_ai_session === true) {
7217
+ if (checkConfig.depends_on && checkConfig.depends_on.length > 0) {
7218
+ sessionProviders.set(checkName, checkConfig.depends_on[0]);
7219
+ }
7095
7220
  }
7096
7221
  }
7097
7222
  } else {
@@ -7654,27 +7779,23 @@ ${expr}
7654
7779
  }
7655
7780
  const reviewResult = result.value.result;
7656
7781
  const reviewSummaryWithOutput = reviewResult;
7657
- if (checkConfig?.forEach && reviewSummaryWithOutput.output !== void 0) {
7782
+ if (checkConfig?.forEach && (!reviewResult.issues || reviewResult.issues.length === 0)) {
7783
+ const validation2 = this.validateAndNormalizeForEachOutput(
7784
+ checkName,
7785
+ reviewSummaryWithOutput.output,
7786
+ checkConfig.group
7787
+ );
7788
+ if (!validation2.isValid) {
7789
+ results.set(
7790
+ checkName,
7791
+ validation2.error.issues ? { issues: validation2.error.issues } : {}
7792
+ );
7793
+ continue;
7794
+ }
7795
+ const normalizedOutput = validation2.normalizedOutput;
7658
7796
  logger.debug(
7659
7797
  `\u{1F527} Debug: Raw output for forEach check ${checkName}: ${Array.isArray(reviewSummaryWithOutput.output) ? `array(${reviewSummaryWithOutput.output.length})` : typeof reviewSummaryWithOutput.output}`
7660
7798
  );
7661
- const rawOutput = reviewSummaryWithOutput.output;
7662
- let normalizedOutput;
7663
- if (Array.isArray(rawOutput)) {
7664
- normalizedOutput = rawOutput;
7665
- } else if (typeof rawOutput === "string") {
7666
- try {
7667
- const parsed = JSON.parse(rawOutput);
7668
- normalizedOutput = Array.isArray(parsed) ? parsed : [parsed];
7669
- } catch {
7670
- normalizedOutput = [rawOutput];
7671
- }
7672
- } else if (rawOutput === void 0 || rawOutput === null) {
7673
- normalizedOutput = [];
7674
- } else {
7675
- normalizedOutput = [rawOutput];
7676
- }
7677
- logger.info(` Found ${normalizedOutput.length} items for forEach iteration`);
7678
7799
  try {
7679
7800
  const preview = JSON.stringify(normalizedOutput);
7680
7801
  logger.debug(
@@ -9368,6 +9489,10 @@ var init_config_schema = __esm({
9368
9489
  $ref: "#/definitions/EnvConfig",
9369
9490
  description: "Environment variables for this check"
9370
9491
  },
9492
+ timeout: {
9493
+ type: "number",
9494
+ description: "Timeout in seconds for command execution (default: 60)"
9495
+ },
9371
9496
  depends_on: {
9372
9497
  type: "array",
9373
9498
  items: {
@@ -9399,8 +9524,8 @@ var init_config_schema = __esm({
9399
9524
  description: "Condition to determine if check should run - runs if expression evaluates to true"
9400
9525
  },
9401
9526
  reuse_ai_session: {
9402
- type: "boolean",
9403
- description: "Whether to reuse AI session from dependency checks (only works with depends_on)"
9527
+ type: ["string", "boolean"],
9528
+ description: "Check name to reuse AI session from, or true to use first dependency (only works with depends_on)"
9404
9529
  },
9405
9530
  fail_if: {
9406
9531
  type: "string",
@@ -10737,7 +10862,7 @@ var ConfigManager = class {
10737
10862
  if (!checkConfig.type) {
10738
10863
  checkConfig.type = "ai";
10739
10864
  }
10740
- this.validateCheckConfig(checkName, checkConfig, errors);
10865
+ this.validateCheckConfig(checkName, checkConfig, errors, config);
10741
10866
  if (checkConfig.ai_mcp_servers) {
10742
10867
  this.validateMcpServersObject(
10743
10868
  checkConfig.ai_mcp_servers,
@@ -10837,7 +10962,7 @@ var ConfigManager = class {
10837
10962
  /**
10838
10963
  * Validate individual check configuration
10839
10964
  */
10840
- validateCheckConfig(checkName, checkConfig, errors) {
10965
+ validateCheckConfig(checkName, checkConfig, errors, config) {
10841
10966
  if (!checkConfig.type) {
10842
10967
  checkConfig.type = "ai";
10843
10968
  }
@@ -10918,12 +11043,23 @@ var ConfigManager = class {
10918
11043
  }
10919
11044
  }
10920
11045
  if (checkConfig.reuse_ai_session !== void 0) {
10921
- if (typeof checkConfig.reuse_ai_session !== "boolean") {
11046
+ const isString = typeof checkConfig.reuse_ai_session === "string";
11047
+ const isBoolean = typeof checkConfig.reuse_ai_session === "boolean";
11048
+ if (!isString && !isBoolean) {
10922
11049
  errors.push({
10923
11050
  field: `checks.${checkName}.reuse_ai_session`,
10924
- message: `Invalid reuse_ai_session value for "${checkName}": must be boolean`,
11051
+ message: `Invalid reuse_ai_session value for "${checkName}": must be string (check name) or boolean`,
10925
11052
  value: checkConfig.reuse_ai_session
10926
11053
  });
11054
+ } else if (isString) {
11055
+ const targetCheckName = checkConfig.reuse_ai_session;
11056
+ if (!config?.checks || !config.checks[targetCheckName]) {
11057
+ errors.push({
11058
+ field: `checks.${checkName}.reuse_ai_session`,
11059
+ message: `Check "${checkName}" references non-existent check "${targetCheckName}" for session reuse`,
11060
+ value: checkConfig.reuse_ai_session
11061
+ });
11062
+ }
10927
11063
  } else if (checkConfig.reuse_ai_session === true) {
10928
11064
  if (!checkConfig.depends_on || !Array.isArray(checkConfig.depends_on) || checkConfig.depends_on.length === 0) {
10929
11065
  errors.push({