@probelabs/visor 0.1.10 → 0.1.18

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.
Files changed (75) hide show
  1. package/README.md +78 -0
  2. package/defaults/.visor.yaml +54 -0
  3. package/dist/ai-review-service.d.ts +13 -0
  4. package/dist/ai-review-service.d.ts.map +1 -1
  5. package/dist/ai-review-service.js +142 -73
  6. package/dist/ai-review-service.js.map +1 -1
  7. package/dist/check-execution-engine.d.ts +41 -1
  8. package/dist/check-execution-engine.d.ts.map +1 -1
  9. package/dist/check-execution-engine.js +376 -19
  10. package/dist/check-execution-engine.js.map +1 -1
  11. package/dist/config.d.ts.map +1 -1
  12. package/dist/config.js +19 -3
  13. package/dist/config.js.map +1 -1
  14. package/dist/event-mapper.d.ts.map +1 -1
  15. package/dist/event-mapper.js +3 -5
  16. package/dist/event-mapper.js.map +1 -1
  17. package/dist/failure-condition-evaluator.d.ts +11 -3
  18. package/dist/failure-condition-evaluator.d.ts.map +1 -1
  19. package/dist/failure-condition-evaluator.js +41 -5
  20. package/dist/failure-condition-evaluator.js.map +1 -1
  21. package/dist/github-check-service.d.ts.map +1 -1
  22. package/dist/github-check-service.js +26 -39
  23. package/dist/github-check-service.js.map +1 -1
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +332 -681
  26. package/dist/index.js.map +1 -1
  27. package/dist/licenses.txt +2300 -0
  28. package/dist/output/code-review/schema.json +84 -0
  29. package/dist/output/code-review/template.liquid +32 -0
  30. package/dist/output/plain/schema.json +14 -0
  31. package/dist/output/plain/template.liquid +1 -0
  32. package/dist/output-formatters.d.ts.map +1 -1
  33. package/dist/output-formatters.js +14 -14
  34. package/dist/output-formatters.js.map +1 -1
  35. package/dist/pr-analyzer.d.ts +10 -1
  36. package/dist/pr-analyzer.d.ts.map +1 -1
  37. package/dist/pr-analyzer.js +2 -1
  38. package/dist/pr-analyzer.js.map +1 -1
  39. package/dist/pr-detector.d.ts +14 -4
  40. package/dist/pr-detector.d.ts.map +1 -1
  41. package/dist/pr-detector.js.map +1 -1
  42. package/dist/providers/ai-check-provider.d.ts.map +1 -1
  43. package/dist/providers/ai-check-provider.js +27 -23
  44. package/dist/providers/ai-check-provider.js.map +1 -1
  45. package/dist/providers/check-provider-registry.js +2 -2
  46. package/dist/providers/check-provider-registry.js.map +1 -1
  47. package/dist/providers/check-provider.interface.d.ts +3 -1
  48. package/dist/providers/check-provider.interface.d.ts.map +1 -1
  49. package/dist/providers/check-provider.interface.js.map +1 -1
  50. package/dist/providers/index.d.ts +1 -1
  51. package/dist/providers/index.d.ts.map +1 -1
  52. package/dist/providers/index.js +3 -3
  53. package/dist/providers/index.js.map +1 -1
  54. package/dist/providers/noop-check-provider.d.ts +25 -0
  55. package/dist/providers/noop-check-provider.d.ts.map +1 -0
  56. package/dist/providers/noop-check-provider.js +55 -0
  57. package/dist/providers/noop-check-provider.js.map +1 -0
  58. package/dist/providers/tool-check-provider.d.ts +4 -1
  59. package/dist/providers/tool-check-provider.d.ts.map +1 -1
  60. package/dist/providers/tool-check-provider.js +64 -15
  61. package/dist/providers/tool-check-provider.js.map +1 -1
  62. package/dist/providers/webhook-check-provider.js +3 -3
  63. package/dist/providers/webhook-check-provider.js.map +1 -1
  64. package/dist/reviewer.d.ts +19 -37
  65. package/dist/reviewer.d.ts.map +1 -1
  66. package/dist/reviewer.js +85 -596
  67. package/dist/reviewer.js.map +1 -1
  68. package/dist/tiktoken_bg.wasm +0 -0
  69. package/dist/types/config.d.ts +79 -6
  70. package/dist/types/config.d.ts.map +1 -1
  71. package/package.json +3 -3
  72. package/dist/providers/script-check-provider.d.ts +0 -23
  73. package/dist/providers/script-check-provider.d.ts.map +0 -1
  74. package/dist/providers/script-check-provider.js +0 -163
  75. package/dist/providers/script-check-provider.js.map +0 -1
@@ -1,4 +1,37 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
2
35
  Object.defineProperty(exports, "__esModule", { value: true });
3
36
  exports.CheckExecutionEngine = void 0;
4
37
  const reviewer_1 = require("./reviewer");
@@ -157,6 +190,7 @@ class CheckExecutionEngine {
157
190
  const result = await tasks[taskIndex]();
158
191
  results[taskIndex] = { status: 'fulfilled', value: result };
159
192
  // Check if we should stop due to fail-fast
193
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
160
194
  if (failFast && this.shouldFailFast(result)) {
161
195
  shouldStop = true;
162
196
  break;
@@ -190,6 +224,13 @@ class CheckExecutionEngine {
190
224
  const logFn = outputFormat === 'json' || outputFormat === 'sarif' ? console.error : console.log;
191
225
  logFn(`🔧 Debug: executeReviewChecks called with checks: ${JSON.stringify(checks)}`);
192
226
  logFn(`🔧 Debug: Config available: ${!!config}, Config has checks: ${!!config?.checks}`);
227
+ // Filter checks based on current event type to prevent execution of checks that shouldn't run
228
+ const filteredChecks = this.filterChecksByEvent(checks, config, prInfo, logFn);
229
+ if (filteredChecks.length !== checks.length) {
230
+ logFn(`🔧 Debug: Event filtering reduced checks from ${checks.length} to ${filteredChecks.length}: ${JSON.stringify(filteredChecks)}`);
231
+ }
232
+ // Use filtered checks for execution
233
+ checks = filteredChecks;
193
234
  // If we have a config with individual check definitions, use dependency-aware execution
194
235
  // Check if any of the checks have dependencies or if there are multiple checks
195
236
  const hasDependencies = config?.checks &&
@@ -218,7 +259,7 @@ class CheckExecutionEngine {
218
259
  };
219
260
  const result = await provider.execute(prInfo, providerConfig);
220
261
  // Prefix issues with check name for consistent grouping
221
- const prefixedIssues = result.issues.map(issue => ({
262
+ const prefixedIssues = (result.issues || []).map(issue => ({
222
263
  ...issue,
223
264
  ruleId: `${checks[0]}/${issue.ruleId}`,
224
265
  }));
@@ -255,7 +296,7 @@ class CheckExecutionEngine {
255
296
  };
256
297
  const result = await provider.execute(prInfo, providerConfig);
257
298
  // Prefix issues with check name for consistent grouping
258
- const prefixedIssues = result.issues.map(issue => ({
299
+ const prefixedIssues = (result.issues || []).map(issue => ({
259
300
  ...issue,
260
301
  ruleId: `${checkName}/${issue.ruleId}`,
261
302
  }));
@@ -282,6 +323,243 @@ class CheckExecutionEngine {
282
323
  format: 'table',
283
324
  });
284
325
  }
326
+ /**
327
+ * Execute review checks and return grouped results for new architecture
328
+ */
329
+ async executeGroupedChecks(prInfo, checks, timeout, config, outputFormat, debug, maxParallelism, failFast) {
330
+ // Determine where to send log messages based on output format
331
+ const logFn = outputFormat === 'json' || outputFormat === 'sarif' ? console.error : console.log;
332
+ logFn(`🔧 Debug: executeGroupedChecks called with checks: ${JSON.stringify(checks)}`);
333
+ logFn(`🔧 Debug: Config available: ${!!config}, Config has checks: ${!!config?.checks}`);
334
+ // Filter checks based on current event type to prevent execution of checks that shouldn't run
335
+ const filteredChecks = this.filterChecksByEvent(checks, config, prInfo, logFn);
336
+ if (filteredChecks.length !== checks.length) {
337
+ logFn(`🔧 Debug: Event filtering reduced checks from ${checks.length} to ${filteredChecks.length}: ${JSON.stringify(filteredChecks)}`);
338
+ }
339
+ // Use filtered checks for execution
340
+ checks = filteredChecks;
341
+ if (!config?.checks) {
342
+ throw new Error('Config with check definitions required for grouped execution');
343
+ }
344
+ // If we have a config with individual check definitions, use dependency-aware execution
345
+ const hasDependencies = checks.some(checkName => {
346
+ const checkConfig = config.checks[checkName];
347
+ return checkConfig?.depends_on && checkConfig.depends_on.length > 0;
348
+ });
349
+ if (checks.length > 1 || hasDependencies) {
350
+ logFn(`🔧 Debug: Using grouped dependency-aware execution for ${checks.length} checks (has dependencies: ${hasDependencies})`);
351
+ return await this.executeGroupedDependencyAwareChecks(prInfo, checks, timeout, config, logFn, debug, maxParallelism, failFast);
352
+ }
353
+ // Single check execution
354
+ if (checks.length === 1) {
355
+ logFn(`🔧 Debug: Using grouped single check execution for: ${checks[0]}`);
356
+ const checkResult = await this.executeSingleGroupedCheck(prInfo, checks[0], timeout, config, logFn, debug);
357
+ const groupedResults = {};
358
+ groupedResults[checkResult.group] = [checkResult];
359
+ return groupedResults;
360
+ }
361
+ // No checks to execute
362
+ return {};
363
+ }
364
+ /**
365
+ * Execute single check and return grouped result
366
+ */
367
+ async executeSingleGroupedCheck(prInfo, checkName, timeout, config, logFn, debug) {
368
+ if (!config?.checks?.[checkName]) {
369
+ throw new Error(`No configuration found for check: ${checkName}`);
370
+ }
371
+ const checkConfig = config.checks[checkName];
372
+ const provider = this.providerRegistry.getProviderOrThrow('ai');
373
+ const providerConfig = {
374
+ type: 'ai',
375
+ prompt: checkConfig.prompt,
376
+ focus: checkConfig.focus || this.mapCheckNameToFocus(checkName),
377
+ schema: checkConfig.schema,
378
+ group: checkConfig.group,
379
+ ai: {
380
+ timeout: timeout || 600000,
381
+ debug: debug,
382
+ ...(checkConfig.ai || {}),
383
+ },
384
+ ai_provider: checkConfig.ai_provider || config.ai_provider,
385
+ ai_model: checkConfig.ai_model || config.ai_model,
386
+ };
387
+ const result = await provider.execute(prInfo, providerConfig);
388
+ // Render the check content using the appropriate template
389
+ const content = await this.renderCheckContent(checkName, result, checkConfig, prInfo);
390
+ return {
391
+ checkName,
392
+ content,
393
+ group: checkConfig.group || 'default',
394
+ debug: result.debug,
395
+ issues: result.issues, // Include structured issues
396
+ };
397
+ }
398
+ /**
399
+ * Execute multiple checks with dependency awareness - return grouped results
400
+ */
401
+ async executeGroupedDependencyAwareChecks(prInfo, checks, timeout, config, logFn, debug, maxParallelism, failFast) {
402
+ // Use the existing dependency-aware execution logic
403
+ const reviewSummary = await this.executeDependencyAwareChecks(prInfo, checks, timeout, config, logFn, debug, maxParallelism, failFast);
404
+ // Convert the flat ReviewSummary to grouped CheckResults
405
+ return await this.convertReviewSummaryToGroupedResults(reviewSummary, checks, config, prInfo);
406
+ }
407
+ /**
408
+ * Convert ReviewSummary to GroupedCheckResults
409
+ */
410
+ async convertReviewSummaryToGroupedResults(reviewSummary, checks, config, prInfo) {
411
+ const groupedResults = {};
412
+ // Process each check individually
413
+ for (const checkName of checks) {
414
+ const checkConfig = config?.checks?.[checkName];
415
+ if (!checkConfig)
416
+ continue;
417
+ // Extract issues for this check
418
+ const checkIssues = (reviewSummary.issues || []).filter(issue => issue.ruleId?.startsWith(`${checkName}/`));
419
+ // Extract suggestions for this check
420
+ const checkSuggestions = (reviewSummary.suggestions || []).filter(suggestion => suggestion.startsWith(`[${checkName}]`));
421
+ // Create a mini ReviewSummary for this check
422
+ const checkSummary = {
423
+ issues: checkIssues,
424
+ suggestions: checkSuggestions,
425
+ debug: reviewSummary.debug,
426
+ };
427
+ // Render content for this check
428
+ const content = await this.renderCheckContent(checkName, checkSummary, checkConfig, prInfo);
429
+ const checkResult = {
430
+ checkName,
431
+ content,
432
+ group: checkConfig.group || 'default',
433
+ debug: reviewSummary.debug,
434
+ issues: checkIssues, // Include structured issues
435
+ };
436
+ // Add to appropriate group
437
+ const group = checkResult.group;
438
+ if (!groupedResults[group]) {
439
+ groupedResults[group] = [];
440
+ }
441
+ groupedResults[group].push(checkResult);
442
+ }
443
+ return groupedResults;
444
+ }
445
+ /**
446
+ * Validates that a file path is safe and within the project directory
447
+ * Prevents path traversal attacks by:
448
+ * - Blocking absolute paths
449
+ * - Blocking paths with ".." segments
450
+ * - Ensuring resolved path is within project directory
451
+ * - Blocking special characters and null bytes
452
+ * - Enforcing .liquid file extension
453
+ */
454
+ async validateTemplatePath(templatePath) {
455
+ const path = await Promise.resolve().then(() => __importStar(require('path')));
456
+ // Validate input
457
+ if (!templatePath || typeof templatePath !== 'string' || templatePath.trim() === '') {
458
+ throw new Error('Template path must be a non-empty string');
459
+ }
460
+ // Block null bytes and other dangerous characters
461
+ if (templatePath.includes('\0') || templatePath.includes('\x00')) {
462
+ throw new Error('Template path contains invalid characters');
463
+ }
464
+ // Enforce .liquid file extension
465
+ if (!templatePath.endsWith('.liquid')) {
466
+ throw new Error('Template file must have .liquid extension');
467
+ }
468
+ // Block absolute paths
469
+ if (path.isAbsolute(templatePath)) {
470
+ throw new Error('Template path must be relative to project directory');
471
+ }
472
+ // Block paths with ".." segments
473
+ if (templatePath.includes('..')) {
474
+ throw new Error('Template path cannot contain ".." segments');
475
+ }
476
+ // Block paths starting with ~ (home directory)
477
+ if (templatePath.startsWith('~')) {
478
+ throw new Error('Template path cannot reference home directory');
479
+ }
480
+ // Get the project root directory from git analyzer
481
+ const repositoryInfo = await this.gitAnalyzer.analyzeRepository();
482
+ const projectRoot = repositoryInfo.workingDirectory;
483
+ // Validate project root
484
+ if (!projectRoot || typeof projectRoot !== 'string') {
485
+ throw new Error('Unable to determine project root directory');
486
+ }
487
+ // Resolve the template path relative to project root
488
+ const resolvedPath = path.resolve(projectRoot, templatePath);
489
+ const resolvedProjectRoot = path.resolve(projectRoot);
490
+ // Validate resolved paths
491
+ if (!resolvedPath ||
492
+ !resolvedProjectRoot ||
493
+ resolvedPath === '' ||
494
+ resolvedProjectRoot === '') {
495
+ throw new Error(`Unable to resolve template path: projectRoot="${projectRoot}", templatePath="${templatePath}", resolvedPath="${resolvedPath}", resolvedProjectRoot="${resolvedProjectRoot}"`);
496
+ }
497
+ // Ensure the resolved path is still within the project directory
498
+ if (!resolvedPath.startsWith(resolvedProjectRoot + path.sep) &&
499
+ resolvedPath !== resolvedProjectRoot) {
500
+ throw new Error('Template path escapes project directory');
501
+ }
502
+ return resolvedPath;
503
+ }
504
+ /**
505
+ * Render check content using the appropriate template
506
+ */
507
+ async renderCheckContent(checkName, reviewSummary, checkConfig, _prInfo) {
508
+ // Import the liquid template system
509
+ const { Liquid } = await Promise.resolve().then(() => __importStar(require('liquidjs')));
510
+ const fs = await Promise.resolve().then(() => __importStar(require('fs/promises')));
511
+ const path = await Promise.resolve().then(() => __importStar(require('path')));
512
+ const liquid = new Liquid({
513
+ trimTagLeft: false,
514
+ trimTagRight: false,
515
+ trimOutputLeft: false,
516
+ trimOutputRight: false,
517
+ greedy: false,
518
+ });
519
+ // Determine template to use
520
+ const schema = checkConfig.schema || 'plain';
521
+ let templateContent;
522
+ if (checkConfig.template) {
523
+ // Custom template
524
+ if (checkConfig.template.content) {
525
+ templateContent = checkConfig.template.content;
526
+ }
527
+ else if (checkConfig.template.file) {
528
+ // Validate the template file path to prevent path traversal attacks
529
+ const validatedPath = await this.validateTemplatePath(checkConfig.template.file);
530
+ templateContent = await fs.readFile(validatedPath, 'utf-8');
531
+ }
532
+ else {
533
+ throw new Error('Custom template must specify either "file" or "content"');
534
+ }
535
+ }
536
+ else if (schema === 'plain') {
537
+ // Plain schema - return raw content directly
538
+ // Strip [checkName] prefixes from suggestions before joining
539
+ const cleanedSuggestions = (reviewSummary.suggestions || []).map(suggestion => {
540
+ // Remove [checkName] prefix if present
541
+ return suggestion.replace(/^\[[^\]]+\]\s*/, '');
542
+ });
543
+ return (reviewSummary.issues?.[0]?.message || '') + (cleanedSuggestions.join('\n\n') || '');
544
+ }
545
+ else {
546
+ // Use built-in schema template
547
+ const sanitizedSchema = schema.replace(/[^a-zA-Z0-9-]/g, '');
548
+ if (!sanitizedSchema) {
549
+ throw new Error('Invalid schema name');
550
+ }
551
+ const templatePath = path.join(__dirname, `../output/${sanitizedSchema}/template.liquid`);
552
+ templateContent = await fs.readFile(templatePath, 'utf-8');
553
+ }
554
+ // Prepare template data
555
+ const templateData = {
556
+ issues: reviewSummary.issues || [],
557
+ checkName: checkName,
558
+ suggestions: reviewSummary.suggestions || [],
559
+ };
560
+ const rendered = await liquid.parseAndRender(templateContent, templateData);
561
+ return rendered.trim();
562
+ }
285
563
  /**
286
564
  * Execute multiple checks with dependency awareness - intelligently parallel and sequential
287
565
  */
@@ -395,7 +673,7 @@ class CheckExecutionEngine {
395
673
  branch: prInfo.head,
396
674
  baseBranch: prInfo.base,
397
675
  filesChanged: prInfo.files.map(f => f.filename),
398
- event: 'manual', // TODO: Get actual event from context
676
+ event: 'issue_comment', // Command triggered from comment
399
677
  environment: getSafeEnvironmentVariables(),
400
678
  previousResults: results,
401
679
  });
@@ -459,9 +737,9 @@ class CheckExecutionEngine {
459
737
  providerConfig.sessionId = currentSessionId;
460
738
  }
461
739
  const result = await provider.execute(prInfo, providerConfig, dependencyResults, sessionInfo);
462
- log(`🔧 Debug: Completed check: ${checkName}, issues found: ${result.issues.length}`);
740
+ log(`🔧 Debug: Completed check: ${checkName}, issues found: ${(result.issues || []).length}`);
463
741
  // Add group, schema, template info and timestamp to issues from config
464
- const enrichedIssues = result.issues.map(issue => ({
742
+ const enrichedIssues = (result.issues || []).map(issue => ({
465
743
  ...issue,
466
744
  ruleId: `${checkName}/${issue.ruleId}`,
467
745
  group: checkConfig.group,
@@ -536,7 +814,7 @@ class CheckExecutionEngine {
536
814
  const result = levelResults[i];
537
815
  if (result.status === 'fulfilled' && result.value.result && !result.value.error) {
538
816
  // Check for issues that should trigger fail-fast
539
- const hasFailuresToReport = result.value.result.issues.some(issue => issue.severity === 'error' || issue.severity === 'critical');
817
+ const hasFailuresToReport = (result.value.result.issues || []).some(issue => issue.severity === 'error' || issue.severity === 'critical');
540
818
  if (hasFailuresToReport) {
541
819
  log(`🛑 Check "${checkName}" found critical/error issues and fail-fast is enabled - stopping execution`);
542
820
  shouldStopExecution = true;
@@ -604,7 +882,7 @@ class CheckExecutionEngine {
604
882
  branch: prInfo.head,
605
883
  baseBranch: prInfo.base,
606
884
  filesChanged: prInfo.files.map(f => f.filename),
607
- event: 'manual', // TODO: Get actual event from context
885
+ event: 'issue_comment', // Command triggered from comment
608
886
  environment: getSafeEnvironmentVariables(),
609
887
  previousResults: new Map(), // No previous results in parallel execution
610
888
  });
@@ -634,9 +912,9 @@ class CheckExecutionEngine {
634
912
  },
635
913
  };
636
914
  const result = await provider.execute(prInfo, providerConfig);
637
- console.error(`🔧 Debug: Completed check: ${checkName}, issues found: ${result.issues.length}`);
915
+ console.error(`🔧 Debug: Completed check: ${checkName}, issues found: ${(result.issues || []).length}`);
638
916
  // Add group, schema info and timestamp to issues from config
639
- const enrichedIssues = result.issues.map(issue => ({
917
+ const enrichedIssues = (result.issues || []).map(issue => ({
640
918
  ...issue,
641
919
  ruleId: `${checkName}/${issue.ruleId}`,
642
920
  group: checkConfig.group,
@@ -704,7 +982,7 @@ class CheckExecutionEngine {
704
982
  };
705
983
  const result = await provider.execute(prInfo, providerConfig);
706
984
  // Prefix issues with check name and add group/schema info and timestamp from config
707
- const prefixedIssues = result.issues.map(issue => ({
985
+ const prefixedIssues = (result.issues || []).map(issue => ({
708
986
  ...issue,
709
987
  ruleId: `${checkName}/${issue.ruleId}`,
710
988
  group: checkConfig.group,
@@ -759,17 +1037,17 @@ class CheckExecutionEngine {
759
1037
  continue;
760
1038
  }
761
1039
  // Check if this was a successful result
762
- const hasErrors = result.issues.some(issue => issue.ruleId?.includes('/error') || issue.ruleId?.includes('/promise-error'));
1040
+ const hasErrors = (result.issues || []).some(issue => issue.ruleId?.includes('/error') || issue.ruleId?.includes('/promise-error'));
763
1041
  if (hasErrors) {
764
1042
  debugInfo.push(`❌ Check "${checkName}" failed with errors`);
765
1043
  }
766
1044
  else {
767
- debugInfo.push(`✅ Check "${checkName}" completed: ${result.issues.length} issues found (level ${executionGroup.level})`);
1045
+ debugInfo.push(`✅ Check "${checkName}" completed: ${(result.issues || []).length} issues found (level ${executionGroup.level})`);
768
1046
  }
769
1047
  // Issues are already prefixed and enriched with group/schema info
770
- aggregatedIssues.push(...result.issues);
1048
+ aggregatedIssues.push(...(result.issues || []));
771
1049
  // Add suggestions with check name prefix
772
- const prefixedSuggestions = result.suggestions.map(suggestion => `[${checkName}] ${suggestion}`);
1050
+ const prefixedSuggestions = (result.suggestions || []).map(suggestion => `[${checkName}] ${suggestion}`);
773
1051
  aggregatedSuggestions.push(...prefixedSuggestions);
774
1052
  }
775
1053
  }
@@ -860,12 +1138,12 @@ class CheckExecutionEngine {
860
1138
  }
861
1139
  else if (checkResult.result) {
862
1140
  successfulChecks++;
863
- console.error(`🔧 Debug: Check ${checkName} succeeded with ${checkResult.result.issues.length} issues`);
864
- debugInfo.push(`✅ Check "${checkName}" completed: ${checkResult.result.issues.length} issues found`);
1141
+ console.error(`🔧 Debug: Check ${checkName} succeeded with ${(checkResult.result.issues || []).length} issues`);
1142
+ debugInfo.push(`✅ Check "${checkName}" completed: ${(checkResult.result.issues || []).length} issues found`);
865
1143
  // Issues are already prefixed and enriched with group/schema info
866
- aggregatedIssues.push(...checkResult.result.issues);
1144
+ aggregatedIssues.push(...(checkResult.result.issues || []));
867
1145
  // Add suggestions with check name prefix
868
- const prefixedSuggestions = checkResult.result.suggestions.map(suggestion => `[${checkName}] ${suggestion}`);
1146
+ const prefixedSuggestions = (checkResult.result.suggestions || []).map(suggestion => `[${checkName}] ${suggestion}`);
869
1147
  aggregatedSuggestions.push(...prefixedSuggestions);
870
1148
  }
871
1149
  }
@@ -1126,7 +1404,7 @@ class CheckExecutionEngine {
1126
1404
  }
1127
1405
  // If the result has a result with critical or error issues, it should fail fast
1128
1406
  if (result?.result?.issues) {
1129
- return result.result.issues.some((issue) => issue.severity === 'error' || issue.severity === 'critical');
1407
+ return (result.result.issues || []).some((issue) => issue.severity === 'error' || issue.severity === 'critical');
1130
1408
  }
1131
1409
  return false;
1132
1410
  }
@@ -1358,6 +1636,85 @@ class CheckExecutionEngine {
1358
1636
  }
1359
1637
  }
1360
1638
  }
1639
+ /**
1640
+ * Filter checks based on their event triggers to prevent execution of checks
1641
+ * that shouldn't run for the current event type
1642
+ */
1643
+ filterChecksByEvent(checks, config, prInfo, logFn) {
1644
+ if (!config?.checks) {
1645
+ // No config available, return all checks (fallback behavior)
1646
+ return checks;
1647
+ }
1648
+ // If we have event context from GitHub (prInfo with eventType), apply strict filtering
1649
+ // Otherwise (CLI, tests), use conservative filtering
1650
+ const prInfoWithEvent = prInfo;
1651
+ const hasEventContext = prInfoWithEvent && 'eventType' in prInfoWithEvent && prInfoWithEvent.eventType;
1652
+ if (hasEventContext) {
1653
+ // GitHub Action context - apply strict event filtering
1654
+ const currentEvent = prInfoWithEvent.eventType;
1655
+ logFn?.(`🔧 Debug: GitHub Action context, current event: ${currentEvent}`);
1656
+ const filteredChecks = [];
1657
+ for (const checkName of checks) {
1658
+ const checkConfig = config.checks[checkName];
1659
+ if (!checkConfig) {
1660
+ filteredChecks.push(checkName);
1661
+ continue;
1662
+ }
1663
+ const eventTriggers = checkConfig.on || [];
1664
+ if (eventTriggers.length === 0) {
1665
+ // No triggers specified, include it
1666
+ filteredChecks.push(checkName);
1667
+ logFn?.(`🔧 Debug: Check '${checkName}' has no event triggers, including`);
1668
+ }
1669
+ else if (eventTriggers.includes(currentEvent)) {
1670
+ // Check matches current event
1671
+ filteredChecks.push(checkName);
1672
+ logFn?.(`🔧 Debug: Check '${checkName}' matches event '${currentEvent}', including`);
1673
+ }
1674
+ else {
1675
+ // Check doesn't match current event
1676
+ logFn?.(`🔧 Debug: Check '${checkName}' does not match event '${currentEvent}' (triggers: ${JSON.stringify(eventTriggers)}), skipping`);
1677
+ }
1678
+ }
1679
+ return filteredChecks;
1680
+ }
1681
+ else {
1682
+ // CLI/Test context - conservative filtering (only exclude manual-only checks)
1683
+ logFn?.(`🔧 Debug: CLI/Test context, using conservative filtering`);
1684
+ const filteredChecks = [];
1685
+ for (const checkName of checks) {
1686
+ const checkConfig = config.checks[checkName];
1687
+ if (!checkConfig) {
1688
+ filteredChecks.push(checkName);
1689
+ continue;
1690
+ }
1691
+ const eventTriggers = checkConfig.on || [];
1692
+ // Only exclude checks that are explicitly manual-only
1693
+ if (eventTriggers.length === 1 && eventTriggers[0] === 'manual') {
1694
+ logFn?.(`🔧 Debug: Check '${checkName}' is manual-only, skipping`);
1695
+ }
1696
+ else {
1697
+ filteredChecks.push(checkName);
1698
+ logFn?.(`🔧 Debug: Check '${checkName}' included (triggers: ${JSON.stringify(eventTriggers)})`);
1699
+ }
1700
+ }
1701
+ return filteredChecks;
1702
+ }
1703
+ }
1704
+ /**
1705
+ * Determine the current event type from PR info
1706
+ */
1707
+ getCurrentEventType(prInfo) {
1708
+ if (!prInfo) {
1709
+ return 'pr_opened'; // Default fallback
1710
+ }
1711
+ // For now, assume all PR-related operations are 'pr_updated' since we don't have
1712
+ // direct access to the original GitHub event here. This is a simplification.
1713
+ // In the future, we could pass the actual event type through the call chain.
1714
+ // The key insight is that issue-assistant should only run on issue_opened/issue_comment
1715
+ // events, which don't generate PRInfo objects in the first place.
1716
+ return 'pr_updated';
1717
+ }
1361
1718
  }
1362
1719
  exports.CheckExecutionEngine = CheckExecutionEngine;
1363
1720
  //# sourceMappingURL=check-execution-engine.js.map