@probelabs/visor 0.1.17 → 0.1.19

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 (68) hide show
  1. package/README.md +78 -0
  2. package/defaults/.visor.yaml +53 -1
  3. package/dist/ai-review-service.js +2 -2
  4. package/dist/ai-review-service.js.map +1 -1
  5. package/dist/check-execution-engine.d.ts +32 -1
  6. package/dist/check-execution-engine.d.ts.map +1 -1
  7. package/dist/check-execution-engine.js +289 -19
  8. package/dist/check-execution-engine.js.map +1 -1
  9. package/dist/config.d.ts.map +1 -1
  10. package/dist/config.js +18 -3
  11. package/dist/config.js.map +1 -1
  12. package/dist/event-mapper.d.ts.map +1 -1
  13. package/dist/event-mapper.js +1 -1
  14. package/dist/event-mapper.js.map +1 -1
  15. package/dist/failure-condition-evaluator.d.ts +8 -0
  16. package/dist/failure-condition-evaluator.d.ts.map +1 -1
  17. package/dist/failure-condition-evaluator.js +25 -3
  18. package/dist/failure-condition-evaluator.js.map +1 -1
  19. package/dist/github-check-service.d.ts.map +1 -1
  20. package/dist/github-check-service.js +26 -39
  21. package/dist/github-check-service.js.map +1 -1
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +331 -690
  24. package/dist/index.js.map +1 -1
  25. package/dist/licenses.txt +2300 -0
  26. package/dist/output/code-review/schema.json +84 -0
  27. package/dist/output/code-review/template.liquid +32 -0
  28. package/dist/output/plain/schema.json +14 -0
  29. package/dist/output/plain/template.liquid +1 -0
  30. package/dist/output-formatters.d.ts.map +1 -1
  31. package/dist/output-formatters.js +14 -14
  32. package/dist/output-formatters.js.map +1 -1
  33. package/dist/pr-analyzer.d.ts +2 -1
  34. package/dist/pr-analyzer.d.ts.map +1 -1
  35. package/dist/pr-analyzer.js +2 -1
  36. package/dist/pr-analyzer.js.map +1 -1
  37. package/dist/providers/ai-check-provider.d.ts.map +1 -1
  38. package/dist/providers/ai-check-provider.js +4 -0
  39. package/dist/providers/ai-check-provider.js.map +1 -1
  40. package/dist/providers/check-provider-registry.js +2 -2
  41. package/dist/providers/check-provider-registry.js.map +1 -1
  42. package/dist/providers/check-provider.interface.d.ts +2 -0
  43. package/dist/providers/check-provider.interface.d.ts.map +1 -1
  44. package/dist/providers/check-provider.interface.js.map +1 -1
  45. package/dist/providers/index.d.ts +1 -1
  46. package/dist/providers/index.d.ts.map +1 -1
  47. package/dist/providers/index.js +3 -3
  48. package/dist/providers/index.js.map +1 -1
  49. package/dist/providers/noop-check-provider.d.ts +25 -0
  50. package/dist/providers/noop-check-provider.d.ts.map +1 -0
  51. package/dist/providers/noop-check-provider.js +55 -0
  52. package/dist/providers/noop-check-provider.js.map +1 -0
  53. package/dist/providers/tool-check-provider.d.ts +4 -1
  54. package/dist/providers/tool-check-provider.d.ts.map +1 -1
  55. package/dist/providers/tool-check-provider.js +64 -15
  56. package/dist/providers/tool-check-provider.js.map +1 -1
  57. package/dist/reviewer.d.ts +19 -37
  58. package/dist/reviewer.d.ts.map +1 -1
  59. package/dist/reviewer.js +83 -593
  60. package/dist/reviewer.js.map +1 -1
  61. package/dist/tiktoken_bg.wasm +0 -0
  62. package/dist/types/config.d.ts +52 -5
  63. package/dist/types/config.d.ts.map +1 -1
  64. package/package.json +3 -3
  65. package/dist/providers/script-check-provider.d.ts +0 -23
  66. package/dist/providers/script-check-provider.d.ts.map +0 -1
  67. package/dist/providers/script-check-provider.js +0 -163
  68. 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");
@@ -226,7 +259,7 @@ class CheckExecutionEngine {
226
259
  };
227
260
  const result = await provider.execute(prInfo, providerConfig);
228
261
  // Prefix issues with check name for consistent grouping
229
- const prefixedIssues = result.issues.map(issue => ({
262
+ const prefixedIssues = (result.issues || []).map(issue => ({
230
263
  ...issue,
231
264
  ruleId: `${checks[0]}/${issue.ruleId}`,
232
265
  }));
@@ -263,7 +296,7 @@ class CheckExecutionEngine {
263
296
  };
264
297
  const result = await provider.execute(prInfo, providerConfig);
265
298
  // Prefix issues with check name for consistent grouping
266
- const prefixedIssues = result.issues.map(issue => ({
299
+ const prefixedIssues = (result.issues || []).map(issue => ({
267
300
  ...issue,
268
301
  ruleId: `${checkName}/${issue.ruleId}`,
269
302
  }));
@@ -290,6 +323,243 @@ class CheckExecutionEngine {
290
323
  format: 'table',
291
324
  });
292
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
+ }
293
563
  /**
294
564
  * Execute multiple checks with dependency awareness - intelligently parallel and sequential
295
565
  */
@@ -403,7 +673,7 @@ class CheckExecutionEngine {
403
673
  branch: prInfo.head,
404
674
  baseBranch: prInfo.base,
405
675
  filesChanged: prInfo.files.map(f => f.filename),
406
- event: 'manual', // TODO: Get actual event from context
676
+ event: 'issue_comment', // Command triggered from comment
407
677
  environment: getSafeEnvironmentVariables(),
408
678
  previousResults: results,
409
679
  });
@@ -467,9 +737,9 @@ class CheckExecutionEngine {
467
737
  providerConfig.sessionId = currentSessionId;
468
738
  }
469
739
  const result = await provider.execute(prInfo, providerConfig, dependencyResults, sessionInfo);
470
- log(`🔧 Debug: Completed check: ${checkName}, issues found: ${result.issues.length}`);
740
+ log(`🔧 Debug: Completed check: ${checkName}, issues found: ${(result.issues || []).length}`);
471
741
  // Add group, schema, template info and timestamp to issues from config
472
- const enrichedIssues = result.issues.map(issue => ({
742
+ const enrichedIssues = (result.issues || []).map(issue => ({
473
743
  ...issue,
474
744
  ruleId: `${checkName}/${issue.ruleId}`,
475
745
  group: checkConfig.group,
@@ -544,7 +814,7 @@ class CheckExecutionEngine {
544
814
  const result = levelResults[i];
545
815
  if (result.status === 'fulfilled' && result.value.result && !result.value.error) {
546
816
  // Check for issues that should trigger fail-fast
547
- 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');
548
818
  if (hasFailuresToReport) {
549
819
  log(`🛑 Check "${checkName}" found critical/error issues and fail-fast is enabled - stopping execution`);
550
820
  shouldStopExecution = true;
@@ -612,7 +882,7 @@ class CheckExecutionEngine {
612
882
  branch: prInfo.head,
613
883
  baseBranch: prInfo.base,
614
884
  filesChanged: prInfo.files.map(f => f.filename),
615
- event: 'manual', // TODO: Get actual event from context
885
+ event: 'issue_comment', // Command triggered from comment
616
886
  environment: getSafeEnvironmentVariables(),
617
887
  previousResults: new Map(), // No previous results in parallel execution
618
888
  });
@@ -642,9 +912,9 @@ class CheckExecutionEngine {
642
912
  },
643
913
  };
644
914
  const result = await provider.execute(prInfo, providerConfig);
645
- console.error(`🔧 Debug: Completed check: ${checkName}, issues found: ${result.issues.length}`);
915
+ console.error(`🔧 Debug: Completed check: ${checkName}, issues found: ${(result.issues || []).length}`);
646
916
  // Add group, schema info and timestamp to issues from config
647
- const enrichedIssues = result.issues.map(issue => ({
917
+ const enrichedIssues = (result.issues || []).map(issue => ({
648
918
  ...issue,
649
919
  ruleId: `${checkName}/${issue.ruleId}`,
650
920
  group: checkConfig.group,
@@ -712,7 +982,7 @@ class CheckExecutionEngine {
712
982
  };
713
983
  const result = await provider.execute(prInfo, providerConfig);
714
984
  // Prefix issues with check name and add group/schema info and timestamp from config
715
- const prefixedIssues = result.issues.map(issue => ({
985
+ const prefixedIssues = (result.issues || []).map(issue => ({
716
986
  ...issue,
717
987
  ruleId: `${checkName}/${issue.ruleId}`,
718
988
  group: checkConfig.group,
@@ -767,17 +1037,17 @@ class CheckExecutionEngine {
767
1037
  continue;
768
1038
  }
769
1039
  // Check if this was a successful result
770
- 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'));
771
1041
  if (hasErrors) {
772
1042
  debugInfo.push(`❌ Check "${checkName}" failed with errors`);
773
1043
  }
774
1044
  else {
775
- 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})`);
776
1046
  }
777
1047
  // Issues are already prefixed and enriched with group/schema info
778
- aggregatedIssues.push(...result.issues);
1048
+ aggregatedIssues.push(...(result.issues || []));
779
1049
  // Add suggestions with check name prefix
780
- const prefixedSuggestions = result.suggestions.map(suggestion => `[${checkName}] ${suggestion}`);
1050
+ const prefixedSuggestions = (result.suggestions || []).map(suggestion => `[${checkName}] ${suggestion}`);
781
1051
  aggregatedSuggestions.push(...prefixedSuggestions);
782
1052
  }
783
1053
  }
@@ -868,12 +1138,12 @@ class CheckExecutionEngine {
868
1138
  }
869
1139
  else if (checkResult.result) {
870
1140
  successfulChecks++;
871
- console.error(`🔧 Debug: Check ${checkName} succeeded with ${checkResult.result.issues.length} issues`);
872
- 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`);
873
1143
  // Issues are already prefixed and enriched with group/schema info
874
- aggregatedIssues.push(...checkResult.result.issues);
1144
+ aggregatedIssues.push(...(checkResult.result.issues || []));
875
1145
  // Add suggestions with check name prefix
876
- const prefixedSuggestions = checkResult.result.suggestions.map(suggestion => `[${checkName}] ${suggestion}`);
1146
+ const prefixedSuggestions = (checkResult.result.suggestions || []).map(suggestion => `[${checkName}] ${suggestion}`);
877
1147
  aggregatedSuggestions.push(...prefixedSuggestions);
878
1148
  }
879
1149
  }
@@ -1134,7 +1404,7 @@ class CheckExecutionEngine {
1134
1404
  }
1135
1405
  // If the result has a result with critical or error issues, it should fail fast
1136
1406
  if (result?.result?.issues) {
1137
- 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');
1138
1408
  }
1139
1409
  return false;
1140
1410
  }