@probelabs/visor 0.1.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.
Files changed (116) hide show
  1. package/README.md +1240 -0
  2. package/action.yml +142 -0
  3. package/defaults/.visor.yaml +184 -0
  4. package/dist/action-cli-bridge.d.ts +104 -0
  5. package/dist/action-cli-bridge.d.ts.map +1 -0
  6. package/dist/action-cli-bridge.js +372 -0
  7. package/dist/action-cli-bridge.js.map +1 -0
  8. package/dist/ai-review-service.d.ts +84 -0
  9. package/dist/ai-review-service.d.ts.map +1 -0
  10. package/dist/ai-review-service.js +674 -0
  11. package/dist/ai-review-service.js.map +1 -0
  12. package/dist/check-execution-engine.d.ts +165 -0
  13. package/dist/check-execution-engine.d.ts.map +1 -0
  14. package/dist/check-execution-engine.js +1172 -0
  15. package/dist/check-execution-engine.js.map +1 -0
  16. package/dist/cli-main.d.ts +6 -0
  17. package/dist/cli-main.d.ts.map +1 -0
  18. package/dist/cli-main.js +247 -0
  19. package/dist/cli-main.js.map +1 -0
  20. package/dist/cli.d.ts +47 -0
  21. package/dist/cli.d.ts.map +1 -0
  22. package/dist/cli.js +224 -0
  23. package/dist/cli.js.map +1 -0
  24. package/dist/commands.d.ts +10 -0
  25. package/dist/commands.d.ts.map +1 -0
  26. package/dist/commands.js +53 -0
  27. package/dist/commands.js.map +1 -0
  28. package/dist/config.d.ts +63 -0
  29. package/dist/config.d.ts.map +1 -0
  30. package/dist/config.js +369 -0
  31. package/dist/config.js.map +1 -0
  32. package/dist/dependency-resolver.d.ts +54 -0
  33. package/dist/dependency-resolver.d.ts.map +1 -0
  34. package/dist/dependency-resolver.js +163 -0
  35. package/dist/dependency-resolver.js.map +1 -0
  36. package/dist/event-mapper.d.ts +125 -0
  37. package/dist/event-mapper.d.ts.map +1 -0
  38. package/dist/event-mapper.js +311 -0
  39. package/dist/event-mapper.js.map +1 -0
  40. package/dist/failure-condition-evaluator.d.ts +81 -0
  41. package/dist/failure-condition-evaluator.d.ts.map +1 -0
  42. package/dist/failure-condition-evaluator.js +445 -0
  43. package/dist/failure-condition-evaluator.js.map +1 -0
  44. package/dist/git-repository-analyzer.d.ts +45 -0
  45. package/dist/git-repository-analyzer.d.ts.map +1 -0
  46. package/dist/git-repository-analyzer.js +285 -0
  47. package/dist/git-repository-analyzer.js.map +1 -0
  48. package/dist/github-check-service.d.ts +104 -0
  49. package/dist/github-check-service.d.ts.map +1 -0
  50. package/dist/github-check-service.js +382 -0
  51. package/dist/github-check-service.js.map +1 -0
  52. package/dist/github-comments.d.ts +109 -0
  53. package/dist/github-comments.d.ts.map +1 -0
  54. package/dist/github-comments.js +289 -0
  55. package/dist/github-comments.js.map +1 -0
  56. package/dist/index.d.ts +2 -0
  57. package/dist/index.d.ts.map +1 -0
  58. package/dist/index.js +1265 -0
  59. package/dist/index.js.map +1 -0
  60. package/dist/output-formatters.d.ts +66 -0
  61. package/dist/output-formatters.d.ts.map +1 -0
  62. package/dist/output-formatters.js +624 -0
  63. package/dist/output-formatters.js.map +1 -0
  64. package/dist/pr-analyzer.d.ts +47 -0
  65. package/dist/pr-analyzer.d.ts.map +1 -0
  66. package/dist/pr-analyzer.js +194 -0
  67. package/dist/pr-analyzer.js.map +1 -0
  68. package/dist/pr-detector.d.ts +78 -0
  69. package/dist/pr-detector.d.ts.map +1 -0
  70. package/dist/pr-detector.js +357 -0
  71. package/dist/pr-detector.js.map +1 -0
  72. package/dist/providers/ai-check-provider.d.ts +40 -0
  73. package/dist/providers/ai-check-provider.d.ts.map +1 -0
  74. package/dist/providers/ai-check-provider.js +416 -0
  75. package/dist/providers/ai-check-provider.js.map +1 -0
  76. package/dist/providers/check-provider-registry.d.ts +67 -0
  77. package/dist/providers/check-provider-registry.d.ts.map +1 -0
  78. package/dist/providers/check-provider-registry.js +138 -0
  79. package/dist/providers/check-provider-registry.js.map +1 -0
  80. package/dist/providers/check-provider.interface.d.ts +78 -0
  81. package/dist/providers/check-provider.interface.d.ts.map +1 -0
  82. package/dist/providers/check-provider.interface.js +11 -0
  83. package/dist/providers/check-provider.interface.js.map +1 -0
  84. package/dist/providers/index.d.ts +10 -0
  85. package/dist/providers/index.d.ts.map +1 -0
  86. package/dist/providers/index.js +19 -0
  87. package/dist/providers/index.js.map +1 -0
  88. package/dist/providers/script-check-provider.d.ts +20 -0
  89. package/dist/providers/script-check-provider.d.ts.map +1 -0
  90. package/dist/providers/script-check-provider.js +163 -0
  91. package/dist/providers/script-check-provider.js.map +1 -0
  92. package/dist/providers/tool-check-provider.d.ts +19 -0
  93. package/dist/providers/tool-check-provider.d.ts.map +1 -0
  94. package/dist/providers/tool-check-provider.js +125 -0
  95. package/dist/providers/tool-check-provider.js.map +1 -0
  96. package/dist/providers/webhook-check-provider.d.ts +21 -0
  97. package/dist/providers/webhook-check-provider.d.ts.map +1 -0
  98. package/dist/providers/webhook-check-provider.js +173 -0
  99. package/dist/providers/webhook-check-provider.js.map +1 -0
  100. package/dist/reviewer.d.ts +88 -0
  101. package/dist/reviewer.d.ts.map +1 -0
  102. package/dist/reviewer.js +760 -0
  103. package/dist/reviewer.js.map +1 -0
  104. package/dist/types/cli.d.ts +41 -0
  105. package/dist/types/cli.d.ts.map +1 -0
  106. package/dist/types/cli.js +3 -0
  107. package/dist/types/cli.js.map +1 -0
  108. package/dist/types/config.d.ts +315 -0
  109. package/dist/types/config.d.ts.map +1 -0
  110. package/dist/types/config.js +6 -0
  111. package/dist/types/config.js.map +1 -0
  112. package/dist/utils/env-resolver.d.ts +38 -0
  113. package/dist/utils/env-resolver.d.ts.map +1 -0
  114. package/dist/utils/env-resolver.js +130 -0
  115. package/dist/utils/env-resolver.js.map +1 -0
  116. package/package.json +116 -0
@@ -0,0 +1,1172 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CheckExecutionEngine = void 0;
4
+ const reviewer_1 = require("./reviewer");
5
+ const git_repository_analyzer_1 = require("./git-repository-analyzer");
6
+ const check_provider_registry_1 = require("./providers/check-provider-registry");
7
+ const dependency_resolver_1 = require("./dependency-resolver");
8
+ const failure_condition_evaluator_1 = require("./failure-condition-evaluator");
9
+ const github_check_service_1 = require("./github-check-service");
10
+ /**
11
+ * Filter environment variables to only include safe ones for sandbox evaluation
12
+ */
13
+ function getSafeEnvironmentVariables() {
14
+ const safeEnvVars = [
15
+ 'CI',
16
+ 'GITHUB_EVENT_NAME',
17
+ 'GITHUB_REPOSITORY',
18
+ 'GITHUB_REF',
19
+ 'GITHUB_SHA',
20
+ 'GITHUB_HEAD_REF',
21
+ 'GITHUB_BASE_REF',
22
+ 'GITHUB_ACTOR',
23
+ 'GITHUB_WORKFLOW',
24
+ 'GITHUB_RUN_ID',
25
+ 'GITHUB_RUN_NUMBER',
26
+ 'NODE_ENV',
27
+ ];
28
+ const safeEnv = {};
29
+ for (const key of safeEnvVars) {
30
+ if (process.env[key]) {
31
+ safeEnv[key] = process.env[key];
32
+ }
33
+ }
34
+ return safeEnv;
35
+ }
36
+ class CheckExecutionEngine {
37
+ gitAnalyzer;
38
+ mockOctokit;
39
+ reviewer;
40
+ providerRegistry;
41
+ failureEvaluator;
42
+ githubCheckService;
43
+ checkRunMap;
44
+ githubContext;
45
+ constructor(workingDirectory) {
46
+ this.gitAnalyzer = new git_repository_analyzer_1.GitRepositoryAnalyzer(workingDirectory);
47
+ this.providerRegistry = check_provider_registry_1.CheckProviderRegistry.getInstance();
48
+ this.failureEvaluator = new failure_condition_evaluator_1.FailureConditionEvaluator();
49
+ // Create a mock Octokit instance for local analysis
50
+ // This allows us to reuse the existing PRReviewer logic without network calls
51
+ this.mockOctokit = this.createMockOctokit();
52
+ this.reviewer = new reviewer_1.PRReviewer(this.mockOctokit);
53
+ }
54
+ /**
55
+ * Execute checks on the local repository
56
+ */
57
+ async executeChecks(options) {
58
+ const startTime = Date.now();
59
+ const timestamp = new Date().toISOString();
60
+ try {
61
+ // Determine where to send log messages based on output format
62
+ const logFn = options.outputFormat === 'json' || options.outputFormat === 'sarif'
63
+ ? console.error
64
+ : console.log;
65
+ // Initialize GitHub checks if enabled
66
+ if (options.githubChecks?.enabled && options.githubChecks.octokit) {
67
+ await this.initializeGitHubChecks(options, logFn);
68
+ }
69
+ // Analyze the repository
70
+ logFn('🔍 Analyzing local git repository...');
71
+ const repositoryInfo = await this.gitAnalyzer.analyzeRepository();
72
+ if (!repositoryInfo.isGitRepository) {
73
+ // Complete GitHub checks with error if they were initialized
74
+ if (this.checkRunMap) {
75
+ await this.completeGitHubChecksWithError('Not a git repository or no changes found');
76
+ }
77
+ return this.createErrorResult(repositoryInfo, 'Not a git repository or no changes found', startTime, timestamp, options.checks);
78
+ }
79
+ // Convert to PRInfo format for compatibility with existing reviewer
80
+ const prInfo = this.gitAnalyzer.toPRInfo(repositoryInfo);
81
+ // Update GitHub checks to in-progress status
82
+ if (this.checkRunMap) {
83
+ await this.updateGitHubChecksInProgress(options);
84
+ }
85
+ // Execute checks using the existing PRReviewer
86
+ logFn(`🤖 Executing checks: ${options.checks.join(', ')}`);
87
+ const reviewSummary = await this.executeReviewChecks(prInfo, options.checks, options.timeout, options.config, options.outputFormat, options.debug);
88
+ // Complete GitHub checks with results
89
+ if (this.checkRunMap) {
90
+ await this.completeGitHubChecksWithResults(reviewSummary, options);
91
+ }
92
+ const executionTime = Date.now() - startTime;
93
+ // Collect debug information when debug mode is enabled
94
+ let debugInfo;
95
+ if (options.debug && reviewSummary.debug) {
96
+ debugInfo = {
97
+ provider: reviewSummary.debug.provider,
98
+ model: reviewSummary.debug.model,
99
+ processingTime: reviewSummary.debug.processingTime,
100
+ parallelExecution: options.checks.length > 1,
101
+ checksExecuted: options.checks,
102
+ totalApiCalls: reviewSummary.debug.totalApiCalls || options.checks.length,
103
+ apiCallDetails: reviewSummary.debug.apiCallDetails,
104
+ };
105
+ }
106
+ return {
107
+ repositoryInfo,
108
+ reviewSummary,
109
+ executionTime,
110
+ timestamp,
111
+ checksExecuted: options.checks,
112
+ debug: debugInfo,
113
+ };
114
+ }
115
+ catch (error) {
116
+ console.error('Error executing checks:', error);
117
+ // Complete GitHub checks with error if they were initialized
118
+ if (this.checkRunMap) {
119
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
120
+ await this.completeGitHubChecksWithError(errorMessage);
121
+ }
122
+ const fallbackRepositoryInfo = {
123
+ title: 'Error during analysis',
124
+ body: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
125
+ author: 'system',
126
+ base: 'main',
127
+ head: 'HEAD',
128
+ files: [],
129
+ totalAdditions: 0,
130
+ totalDeletions: 0,
131
+ isGitRepository: false,
132
+ workingDirectory: options.workingDirectory || process.cwd(),
133
+ };
134
+ return this.createErrorResult(fallbackRepositoryInfo, error instanceof Error ? error.message : 'Unknown error occurred', startTime, timestamp, options.checks);
135
+ }
136
+ }
137
+ /**
138
+ * Execute review checks using parallel execution for multiple AI checks
139
+ */
140
+ async executeReviewChecks(prInfo, checks, timeout, config, outputFormat, debug) {
141
+ // Determine where to send log messages based on output format
142
+ const logFn = outputFormat === 'json' || outputFormat === 'sarif' ? console.error : console.log;
143
+ logFn(`🔧 Debug: executeReviewChecks called with checks: ${JSON.stringify(checks)}`);
144
+ logFn(`🔧 Debug: Config available: ${!!config}, Config has checks: ${!!config?.checks}`);
145
+ // If we have a config with individual check definitions, use dependency-aware execution
146
+ // Check if any of the checks have dependencies or if there are multiple checks
147
+ const hasDependencies = config?.checks &&
148
+ checks.some(checkName => {
149
+ const checkConfig = config.checks[checkName];
150
+ return checkConfig?.depends_on && checkConfig.depends_on.length > 0;
151
+ });
152
+ if (config?.checks && (checks.length > 1 || hasDependencies)) {
153
+ logFn(`🔧 Debug: Using dependency-aware execution for ${checks.length} checks (has dependencies: ${hasDependencies})`);
154
+ return await this.executeDependencyAwareChecks(prInfo, checks, timeout, config, logFn, debug);
155
+ }
156
+ // Single check execution (existing logic)
157
+ if (checks.length === 1) {
158
+ logFn(`🔧 Debug: Using single check execution for: ${checks[0]}`);
159
+ // If we have a config definition for this check, use it
160
+ if (config?.checks?.[checks[0]]) {
161
+ return await this.executeSingleConfiguredCheck(prInfo, checks[0], timeout, config, logFn);
162
+ }
163
+ // Try provider system for single checks
164
+ if (this.providerRegistry.hasProvider(checks[0])) {
165
+ const provider = this.providerRegistry.getProviderOrThrow(checks[0]);
166
+ const providerConfig = {
167
+ type: checks[0],
168
+ prompt: 'all',
169
+ ai: timeout ? { timeout } : undefined,
170
+ };
171
+ const result = await provider.execute(prInfo, providerConfig);
172
+ // Prefix issues with check name for consistent grouping
173
+ const prefixedIssues = result.issues.map(issue => ({
174
+ ...issue,
175
+ ruleId: `${checks[0]}/${issue.ruleId}`,
176
+ }));
177
+ return {
178
+ ...result,
179
+ issues: prefixedIssues,
180
+ };
181
+ }
182
+ }
183
+ // Check if 'ai' provider is available for focus-based checks (legacy support)
184
+ if (this.providerRegistry.hasProvider('ai')) {
185
+ logFn(`🔧 Debug: Using AI provider with focus mapping`);
186
+ const provider = this.providerRegistry.getProviderOrThrow('ai');
187
+ let focus = 'all';
188
+ let checkName = 'all';
189
+ if (checks.length === 1) {
190
+ checkName = checks[0];
191
+ if (checks[0] === 'security' || checks[0] === 'performance' || checks[0] === 'style') {
192
+ focus = checks[0];
193
+ }
194
+ }
195
+ else {
196
+ // For multiple checks, combine them into 'all' focus
197
+ focus = 'all';
198
+ }
199
+ const providerConfig = {
200
+ type: 'ai',
201
+ prompt: focus,
202
+ focus: focus,
203
+ ai: timeout ? { timeout } : undefined,
204
+ // Inherit global AI provider and model settings if config is available
205
+ ai_provider: config?.ai_provider,
206
+ ai_model: config?.ai_model,
207
+ };
208
+ const result = await provider.execute(prInfo, providerConfig);
209
+ // Prefix issues with check name for consistent grouping
210
+ const prefixedIssues = result.issues.map(issue => ({
211
+ ...issue,
212
+ ruleId: `${checkName}/${issue.ruleId}`,
213
+ }));
214
+ return {
215
+ ...result,
216
+ issues: prefixedIssues,
217
+ };
218
+ }
219
+ // Fallback to existing PRReviewer for backward compatibility
220
+ logFn(`🔧 Debug: Using legacy PRReviewer fallback`);
221
+ const focusMap = {
222
+ security: 'security',
223
+ performance: 'performance',
224
+ style: 'style',
225
+ all: 'all',
226
+ architecture: 'all',
227
+ };
228
+ let focus = 'all';
229
+ if (checks.length === 1 && focusMap[checks[0]]) {
230
+ focus = focusMap[checks[0]];
231
+ }
232
+ return await this.reviewer.reviewPR('local', 'repository', 0, prInfo, {
233
+ focus,
234
+ format: 'table',
235
+ });
236
+ }
237
+ /**
238
+ * Execute multiple checks with dependency awareness - intelligently parallel and sequential
239
+ */
240
+ async executeDependencyAwareChecks(prInfo, checks, timeout, config, logFn, debug) {
241
+ const log = logFn || console.error;
242
+ log(`🔧 Debug: Starting dependency-aware execution of ${checks.length} checks`);
243
+ if (!config?.checks) {
244
+ throw new Error('Config with check definitions required for dependency-aware execution');
245
+ }
246
+ // Build dependency graph
247
+ const dependencies = {};
248
+ for (const checkName of checks) {
249
+ const checkConfig = config.checks[checkName];
250
+ if (checkConfig) {
251
+ dependencies[checkName] = checkConfig.depends_on || [];
252
+ }
253
+ else {
254
+ dependencies[checkName] = [];
255
+ }
256
+ }
257
+ // Validate dependencies
258
+ const validation = dependency_resolver_1.DependencyResolver.validateDependencies(checks, dependencies);
259
+ if (!validation.valid) {
260
+ return {
261
+ issues: [
262
+ {
263
+ severity: 'error',
264
+ message: `Dependency validation failed: ${validation.errors.join(', ')}`,
265
+ file: '',
266
+ line: 0,
267
+ ruleId: 'dependency-validation-error',
268
+ category: 'logic',
269
+ },
270
+ ],
271
+ suggestions: [],
272
+ };
273
+ }
274
+ // Build dependency graph
275
+ const dependencyGraph = dependency_resolver_1.DependencyResolver.buildDependencyGraph(dependencies);
276
+ if (dependencyGraph.hasCycles) {
277
+ return {
278
+ issues: [
279
+ {
280
+ severity: 'error',
281
+ message: `Circular dependencies detected: ${dependencyGraph.cycleNodes?.join(' -> ')}`,
282
+ file: '',
283
+ line: 0,
284
+ ruleId: 'circular-dependency-error',
285
+ category: 'logic',
286
+ },
287
+ ],
288
+ suggestions: [],
289
+ };
290
+ }
291
+ // Log execution plan
292
+ const stats = dependency_resolver_1.DependencyResolver.getExecutionStats(dependencyGraph);
293
+ log(`🔧 Debug: Execution plan - ${stats.totalChecks} checks in ${stats.parallelLevels} levels, max parallelism: ${stats.maxParallelism}`);
294
+ // Execute checks level by level
295
+ const results = new Map();
296
+ const provider = this.providerRegistry.getProviderOrThrow('ai');
297
+ for (let levelIndex = 0; levelIndex < dependencyGraph.executionOrder.length; levelIndex++) {
298
+ const executionGroup = dependencyGraph.executionOrder[levelIndex];
299
+ log(`🔧 Debug: Executing level ${executionGroup.level} with ${executionGroup.parallel.length} checks in parallel`);
300
+ // Execute all checks in this level in parallel
301
+ const levelTasks = executionGroup.parallel.map(async (checkName) => {
302
+ const checkConfig = config.checks[checkName];
303
+ if (!checkConfig) {
304
+ return {
305
+ checkName,
306
+ error: `No configuration found for check: ${checkName}`,
307
+ result: null,
308
+ };
309
+ }
310
+ try {
311
+ log(`🔧 Debug: Starting check: ${checkName} at level ${executionGroup.level}`);
312
+ // Evaluate if condition to determine whether to run this check
313
+ if (checkConfig.if) {
314
+ const shouldRun = await this.failureEvaluator.evaluateIfCondition(checkName, checkConfig.if, {
315
+ branch: prInfo.head,
316
+ baseBranch: prInfo.base,
317
+ filesChanged: prInfo.files.map(f => f.filename),
318
+ event: 'manual', // TODO: Get actual event from context
319
+ environment: getSafeEnvironmentVariables(),
320
+ previousResults: results,
321
+ });
322
+ if (!shouldRun) {
323
+ log(`🔧 Debug: Skipping check '${checkName}' - if condition evaluated to false`);
324
+ return {
325
+ checkName,
326
+ error: null,
327
+ result: {
328
+ issues: [],
329
+ suggestions: [`Check '${checkName}' was skipped - condition not met`],
330
+ },
331
+ };
332
+ }
333
+ }
334
+ // Create provider config for this specific check
335
+ const providerConfig = {
336
+ type: 'ai',
337
+ prompt: checkConfig.prompt,
338
+ focus: checkConfig.focus || this.mapCheckNameToFocus(checkName),
339
+ schema: checkConfig.schema,
340
+ group: checkConfig.group,
341
+ ai: {
342
+ timeout: timeout || 600000,
343
+ debug: debug,
344
+ ...(checkConfig.ai || {}),
345
+ },
346
+ };
347
+ // Pass results from dependencies if needed
348
+ const dependencyResults = new Map();
349
+ for (const depId of checkConfig.depends_on || []) {
350
+ if (results.has(depId)) {
351
+ dependencyResults.set(depId, results.get(depId));
352
+ }
353
+ }
354
+ const result = await provider.execute(prInfo, providerConfig, dependencyResults);
355
+ log(`🔧 Debug: Completed check: ${checkName}, issues found: ${result.issues.length}`);
356
+ // Add group, schema, template info and timestamp to issues from config
357
+ const enrichedIssues = result.issues.map(issue => ({
358
+ ...issue,
359
+ ruleId: `${checkName}/${issue.ruleId}`,
360
+ group: checkConfig.group,
361
+ schema: checkConfig.schema,
362
+ template: checkConfig.template,
363
+ timestamp: Date.now(),
364
+ }));
365
+ const enrichedResult = {
366
+ ...result,
367
+ issues: enrichedIssues,
368
+ };
369
+ return {
370
+ checkName,
371
+ error: null,
372
+ result: enrichedResult,
373
+ };
374
+ }
375
+ catch (error) {
376
+ const errorMessage = error instanceof Error ? error.message : String(error);
377
+ log(`🔧 Debug: Error in check ${checkName}: ${errorMessage}`);
378
+ return {
379
+ checkName,
380
+ error: errorMessage,
381
+ result: null,
382
+ };
383
+ }
384
+ });
385
+ // Wait for all checks in this level to complete
386
+ const levelResults = await Promise.allSettled(levelTasks);
387
+ // Process results and store them for next level
388
+ for (let i = 0; i < levelResults.length; i++) {
389
+ const checkName = executionGroup.parallel[i];
390
+ const result = levelResults[i];
391
+ if (result.status === 'fulfilled' && result.value.result && !result.value.error) {
392
+ results.set(checkName, result.value.result);
393
+ }
394
+ else {
395
+ // Store error result for dependency tracking
396
+ const errorSummary = {
397
+ issues: [
398
+ {
399
+ file: 'system',
400
+ line: 0,
401
+ endLine: undefined,
402
+ ruleId: `${checkName}/error`,
403
+ message: result.status === 'fulfilled'
404
+ ? result.value.error || 'Unknown error'
405
+ : result.reason instanceof Error
406
+ ? result.reason.message
407
+ : String(result.reason),
408
+ severity: 'error',
409
+ category: 'logic',
410
+ suggestion: undefined,
411
+ replacement: undefined,
412
+ },
413
+ ],
414
+ suggestions: [],
415
+ };
416
+ results.set(checkName, errorSummary);
417
+ }
418
+ }
419
+ }
420
+ // Aggregate all results
421
+ return this.aggregateDependencyAwareResults(results, dependencyGraph, debug);
422
+ }
423
+ /**
424
+ * Execute multiple checks in parallel using Promise.allSettled (legacy method)
425
+ */
426
+ async executeParallelChecks(prInfo, checks, timeout, config, logFn, debug) {
427
+ const log = logFn || console.error;
428
+ log(`🔧 Debug: Starting parallel execution of ${checks.length} checks`);
429
+ if (!config?.checks) {
430
+ throw new Error('Config with check definitions required for parallel execution');
431
+ }
432
+ const provider = this.providerRegistry.getProviderOrThrow('ai');
433
+ // Create individual check tasks
434
+ const checkTasks = checks.map(async (checkName) => {
435
+ const checkConfig = config.checks[checkName];
436
+ if (!checkConfig) {
437
+ log(`🔧 Debug: No config found for check: ${checkName}`);
438
+ return {
439
+ checkName,
440
+ error: `No configuration found for check: ${checkName}`,
441
+ result: null,
442
+ };
443
+ }
444
+ try {
445
+ console.error(`🔧 Debug: Starting check: ${checkName} with prompt type: ${typeof checkConfig.prompt}`);
446
+ // Evaluate if condition to determine whether to run this check
447
+ if (checkConfig.if) {
448
+ const shouldRun = await this.failureEvaluator.evaluateIfCondition(checkName, checkConfig.if, {
449
+ branch: prInfo.head,
450
+ baseBranch: prInfo.base,
451
+ filesChanged: prInfo.files.map(f => f.filename),
452
+ event: 'manual', // TODO: Get actual event from context
453
+ environment: getSafeEnvironmentVariables(),
454
+ previousResults: new Map(), // No previous results in parallel execution
455
+ });
456
+ if (!shouldRun) {
457
+ console.error(`🔧 Debug: Skipping check '${checkName}' - if condition evaluated to false`);
458
+ return {
459
+ checkName,
460
+ error: null,
461
+ result: {
462
+ issues: [],
463
+ suggestions: [`Check '${checkName}' was skipped - condition not met`],
464
+ },
465
+ };
466
+ }
467
+ }
468
+ // Create provider config for this specific check
469
+ const providerConfig = {
470
+ type: 'ai',
471
+ prompt: checkConfig.prompt,
472
+ focus: checkConfig.focus || this.mapCheckNameToFocus(checkName),
473
+ schema: checkConfig.schema,
474
+ group: checkConfig.group,
475
+ ai: {
476
+ timeout: timeout || 600000,
477
+ debug: debug, // Pass debug flag to AI provider
478
+ ...(checkConfig.ai || {}),
479
+ },
480
+ };
481
+ const result = await provider.execute(prInfo, providerConfig);
482
+ console.error(`🔧 Debug: Completed check: ${checkName}, issues found: ${result.issues.length}`);
483
+ // Add group, schema info and timestamp to issues from config
484
+ const enrichedIssues = result.issues.map(issue => ({
485
+ ...issue,
486
+ ruleId: `${checkName}/${issue.ruleId}`,
487
+ group: checkConfig.group,
488
+ schema: checkConfig.schema,
489
+ template: checkConfig.template,
490
+ timestamp: Date.now(),
491
+ }));
492
+ const enrichedResult = {
493
+ ...result,
494
+ issues: enrichedIssues,
495
+ };
496
+ return {
497
+ checkName,
498
+ error: null,
499
+ result: enrichedResult,
500
+ };
501
+ }
502
+ catch (error) {
503
+ const errorMessage = error instanceof Error ? error.message : String(error);
504
+ log(`🔧 Debug: Error in check ${checkName}: ${errorMessage}`);
505
+ return {
506
+ checkName,
507
+ error: errorMessage,
508
+ result: null,
509
+ };
510
+ }
511
+ });
512
+ // Execute all checks in parallel using Promise.allSettled
513
+ log(`🔧 Debug: Executing ${checkTasks.length} checks in parallel`);
514
+ const results = await Promise.allSettled(checkTasks);
515
+ // Aggregate results from all checks
516
+ return this.aggregateParallelResults(results, checks, debug);
517
+ }
518
+ /**
519
+ * Execute a single configured check
520
+ */
521
+ async executeSingleConfiguredCheck(prInfo, checkName, timeout, config, _logFn) {
522
+ if (!config?.checks?.[checkName]) {
523
+ throw new Error(`No configuration found for check: ${checkName}`);
524
+ }
525
+ const checkConfig = config.checks[checkName];
526
+ const provider = this.providerRegistry.getProviderOrThrow('ai');
527
+ const providerConfig = {
528
+ type: 'ai',
529
+ prompt: checkConfig.prompt,
530
+ focus: checkConfig.focus || this.mapCheckNameToFocus(checkName),
531
+ schema: checkConfig.schema,
532
+ group: checkConfig.group,
533
+ ai: {
534
+ timeout: timeout || 600000,
535
+ ...(checkConfig.ai || {}),
536
+ },
537
+ // Inherit global AI provider and model settings
538
+ ai_provider: checkConfig.ai_provider || config.ai_provider,
539
+ ai_model: checkConfig.ai_model || config.ai_model,
540
+ };
541
+ const result = await provider.execute(prInfo, providerConfig);
542
+ // Prefix issues with check name and add group/schema info and timestamp from config
543
+ const prefixedIssues = result.issues.map(issue => ({
544
+ ...issue,
545
+ ruleId: `${checkName}/${issue.ruleId}`,
546
+ group: checkConfig.group,
547
+ schema: checkConfig.schema,
548
+ timestamp: Date.now(),
549
+ }));
550
+ return {
551
+ ...result,
552
+ issues: prefixedIssues,
553
+ };
554
+ }
555
+ /**
556
+ * Map check name to focus for AI provider
557
+ * This is a fallback when focus is not explicitly configured
558
+ */
559
+ mapCheckNameToFocus(checkName) {
560
+ const focusMap = {
561
+ security: 'security',
562
+ performance: 'performance',
563
+ style: 'style',
564
+ architecture: 'architecture',
565
+ };
566
+ return focusMap[checkName] || 'all';
567
+ }
568
+ /**
569
+ * Aggregate results from dependency-aware check execution
570
+ */
571
+ aggregateDependencyAwareResults(results, dependencyGraph, debug) {
572
+ const aggregatedIssues = [];
573
+ const aggregatedSuggestions = [];
574
+ const debugInfo = [];
575
+ // Add execution plan info
576
+ const stats = dependency_resolver_1.DependencyResolver.getExecutionStats(dependencyGraph);
577
+ debugInfo.push(`🔍 Dependency-aware execution completed:`, ` - ${stats.totalChecks} checks in ${stats.parallelLevels} execution levels`, ` - Maximum parallelism: ${stats.maxParallelism}`, ` - Average parallelism: ${stats.averageParallelism.toFixed(1)}`, ` - Checks with dependencies: ${stats.checksWithDependencies}`);
578
+ // Process results in dependency order for better output organization
579
+ for (const executionGroup of dependencyGraph.executionOrder) {
580
+ for (const checkName of executionGroup.parallel) {
581
+ const result = results.get(checkName);
582
+ if (!result) {
583
+ debugInfo.push(`❌ Check "${checkName}" had no result`);
584
+ continue;
585
+ }
586
+ // Check if this was a successful result
587
+ const hasErrors = result.issues.some(issue => issue.ruleId?.includes('/error') || issue.ruleId?.includes('/promise-error'));
588
+ if (hasErrors) {
589
+ debugInfo.push(`❌ Check "${checkName}" failed with errors`);
590
+ }
591
+ else {
592
+ debugInfo.push(`✅ Check "${checkName}" completed: ${result.issues.length} issues found (level ${executionGroup.level})`);
593
+ }
594
+ // Issues are already prefixed and enriched with group/schema info
595
+ aggregatedIssues.push(...result.issues);
596
+ // Add suggestions with check name prefix
597
+ const prefixedSuggestions = result.suggestions.map(suggestion => `[${checkName}] ${suggestion}`);
598
+ aggregatedSuggestions.push(...prefixedSuggestions);
599
+ }
600
+ }
601
+ // Add summary information
602
+ aggregatedSuggestions.unshift(...debugInfo);
603
+ console.error(`🔧 Debug: Aggregated ${aggregatedIssues.length} issues from ${results.size} dependency-aware checks`);
604
+ // Collect debug information when debug mode is enabled
605
+ let aggregatedDebug;
606
+ if (debug) {
607
+ const debugResults = Array.from(results.entries()).filter(([_, result]) => result.debug);
608
+ if (debugResults.length > 0) {
609
+ const [, firstResult] = debugResults[0];
610
+ const firstDebug = firstResult.debug;
611
+ const totalProcessingTime = debugResults.reduce((sum, [_, result]) => {
612
+ return sum + (result.debug.processingTime || 0);
613
+ }, 0);
614
+ aggregatedDebug = {
615
+ provider: firstDebug.provider,
616
+ model: firstDebug.model,
617
+ apiKeySource: firstDebug.apiKeySource,
618
+ processingTime: totalProcessingTime,
619
+ prompt: debugResults
620
+ .map(([checkName, result]) => `[${checkName}]\n${result.debug.prompt}`)
621
+ .join('\n\n'),
622
+ rawResponse: debugResults
623
+ .map(([checkName, result]) => `[${checkName}]\n${result.debug.rawResponse}`)
624
+ .join('\n\n'),
625
+ promptLength: debugResults.reduce((sum, [_, result]) => sum + (result.debug.promptLength || 0), 0),
626
+ responseLength: debugResults.reduce((sum, [_, result]) => sum + (result.debug.responseLength || 0), 0),
627
+ jsonParseSuccess: debugResults.every(([_, result]) => result.debug.jsonParseSuccess),
628
+ errors: debugResults.flatMap(([checkName, result]) => (result.debug.errors || []).map((error) => `[${checkName}] ${error}`)),
629
+ timestamp: new Date().toISOString(),
630
+ totalApiCalls: debugResults.length,
631
+ apiCallDetails: debugResults.map(([checkName, result]) => ({
632
+ checkName,
633
+ provider: result.debug.provider,
634
+ model: result.debug.model,
635
+ processingTime: result.debug.processingTime,
636
+ success: result.debug.jsonParseSuccess,
637
+ })),
638
+ };
639
+ }
640
+ }
641
+ return {
642
+ issues: aggregatedIssues,
643
+ suggestions: aggregatedSuggestions,
644
+ debug: aggregatedDebug,
645
+ };
646
+ }
647
+ /**
648
+ * Aggregate results from parallel check execution (legacy method)
649
+ */
650
+ aggregateParallelResults(results, checkNames, debug) {
651
+ const aggregatedIssues = [];
652
+ const aggregatedSuggestions = [];
653
+ const debugInfo = [];
654
+ let successfulChecks = 0;
655
+ let failedChecks = 0;
656
+ results.forEach((result, index) => {
657
+ const checkName = checkNames[index];
658
+ if (result.status === 'fulfilled') {
659
+ const checkResult = result.value;
660
+ if (checkResult.error) {
661
+ failedChecks++;
662
+ const log = console.error;
663
+ log(`🔧 Debug: Check ${checkName} failed: ${checkResult.error}`);
664
+ debugInfo.push(`❌ Check "${checkName}" failed: ${checkResult.error}`);
665
+ // Check if this is a critical error
666
+ const isCriticalError = checkResult.error.includes('API rate limit') ||
667
+ checkResult.error.includes('403') ||
668
+ checkResult.error.includes('401') ||
669
+ checkResult.error.includes('authentication') ||
670
+ checkResult.error.includes('API key');
671
+ // Add error as an issue with appropriate severity
672
+ aggregatedIssues.push({
673
+ file: 'system',
674
+ line: 0,
675
+ endLine: undefined,
676
+ ruleId: `${checkName}/error`,
677
+ message: `Check "${checkName}" failed: ${checkResult.error}`,
678
+ severity: isCriticalError ? 'critical' : 'error',
679
+ category: 'logic',
680
+ suggestion: isCriticalError
681
+ ? 'Please check your API credentials and rate limits'
682
+ : undefined,
683
+ replacement: undefined,
684
+ });
685
+ }
686
+ else if (checkResult.result) {
687
+ successfulChecks++;
688
+ console.error(`🔧 Debug: Check ${checkName} succeeded with ${checkResult.result.issues.length} issues`);
689
+ debugInfo.push(`✅ Check "${checkName}" completed: ${checkResult.result.issues.length} issues found`);
690
+ // Issues are already prefixed and enriched with group/schema info
691
+ aggregatedIssues.push(...checkResult.result.issues);
692
+ // Add suggestions with check name prefix
693
+ const prefixedSuggestions = checkResult.result.suggestions.map(suggestion => `[${checkName}] ${suggestion}`);
694
+ aggregatedSuggestions.push(...prefixedSuggestions);
695
+ }
696
+ }
697
+ else {
698
+ failedChecks++;
699
+ const errorMessage = result.reason instanceof Error ? result.reason.message : String(result.reason);
700
+ const log = console.error;
701
+ log(`🔧 Debug: Check ${checkName} promise rejected: ${errorMessage}`);
702
+ debugInfo.push(`❌ Check "${checkName}" promise rejected: ${errorMessage}`);
703
+ // Check if this is a critical error
704
+ const isCriticalError = errorMessage.includes('API rate limit') ||
705
+ errorMessage.includes('403') ||
706
+ errorMessage.includes('401') ||
707
+ errorMessage.includes('authentication') ||
708
+ errorMessage.includes('API key');
709
+ aggregatedIssues.push({
710
+ file: 'system',
711
+ line: 0,
712
+ endLine: undefined,
713
+ ruleId: `${checkName}/promise-error`,
714
+ message: `Check "${checkName}" execution failed: ${errorMessage}`,
715
+ severity: isCriticalError ? 'critical' : 'error',
716
+ category: 'logic',
717
+ suggestion: isCriticalError
718
+ ? 'Please check your API credentials and rate limits'
719
+ : undefined,
720
+ replacement: undefined,
721
+ });
722
+ }
723
+ });
724
+ // Add summary information
725
+ debugInfo.unshift(`🔍 Parallel execution completed: ${successfulChecks} successful, ${failedChecks} failed`);
726
+ aggregatedSuggestions.unshift(...debugInfo);
727
+ console.error(`🔧 Debug: Aggregated ${aggregatedIssues.length} issues from ${results.length} checks`);
728
+ // Collect debug information when debug mode is enabled
729
+ let aggregatedDebug;
730
+ if (debug) {
731
+ // Find the first successful result with debug information to use as template
732
+ const debugResults = results
733
+ .map((result, index) => ({
734
+ result,
735
+ checkName: checkNames[index],
736
+ }))
737
+ .filter(({ result }) => result.status === 'fulfilled' && result.value?.result?.debug);
738
+ if (debugResults.length > 0) {
739
+ const firstResult = debugResults[0].result;
740
+ if (firstResult.status === 'fulfilled') {
741
+ const firstDebug = firstResult.value.result.debug;
742
+ const totalProcessingTime = debugResults.reduce((sum, { result }) => {
743
+ if (result.status === 'fulfilled') {
744
+ return sum + (result.value.result.debug.processingTime || 0);
745
+ }
746
+ return sum;
747
+ }, 0);
748
+ aggregatedDebug = {
749
+ // Use first result as template for provider/model info
750
+ provider: firstDebug.provider,
751
+ model: firstDebug.model,
752
+ apiKeySource: firstDebug.apiKeySource,
753
+ // Aggregate processing time from all checks
754
+ processingTime: totalProcessingTime,
755
+ // Combine prompts with check names
756
+ prompt: debugResults
757
+ .map(({ checkName, result }) => {
758
+ if (result.status === 'fulfilled') {
759
+ return `[${checkName}]\n${result.value.result.debug.prompt}`;
760
+ }
761
+ return `[${checkName}] Error: Promise was rejected`;
762
+ })
763
+ .join('\n\n'),
764
+ // Combine responses
765
+ rawResponse: debugResults
766
+ .map(({ checkName, result }) => {
767
+ if (result.status === 'fulfilled') {
768
+ return `[${checkName}]\n${result.value.result.debug.rawResponse}`;
769
+ }
770
+ return `[${checkName}] Error: Promise was rejected`;
771
+ })
772
+ .join('\n\n'),
773
+ promptLength: debugResults.reduce((sum, { result }) => {
774
+ if (result.status === 'fulfilled') {
775
+ return sum + (result.value.result.debug.promptLength || 0);
776
+ }
777
+ return sum;
778
+ }, 0),
779
+ responseLength: debugResults.reduce((sum, { result }) => {
780
+ if (result.status === 'fulfilled') {
781
+ return sum + (result.value.result.debug.responseLength || 0);
782
+ }
783
+ return sum;
784
+ }, 0),
785
+ jsonParseSuccess: debugResults.every(({ result }) => {
786
+ if (result.status === 'fulfilled') {
787
+ return result.value.result.debug.jsonParseSuccess;
788
+ }
789
+ return false;
790
+ }),
791
+ errors: debugResults.flatMap(({ result, checkName }) => {
792
+ if (result.status === 'fulfilled') {
793
+ return (result.value.result.debug.errors || []).map((error) => `[${checkName}] ${error}`);
794
+ }
795
+ return [`[${checkName}] Promise was rejected`];
796
+ }),
797
+ timestamp: new Date().toISOString(),
798
+ // Add additional debug information for parallel execution
799
+ totalApiCalls: debugResults.length,
800
+ apiCallDetails: debugResults.map(({ checkName, result }) => {
801
+ if (result.status === 'fulfilled') {
802
+ return {
803
+ checkName,
804
+ provider: result.value.result.debug.provider,
805
+ model: result.value.result.debug.model,
806
+ processingTime: result.value.result.debug.processingTime,
807
+ success: result.value.result.debug.jsonParseSuccess,
808
+ };
809
+ }
810
+ return {
811
+ checkName,
812
+ provider: 'unknown',
813
+ model: 'unknown',
814
+ processingTime: 0,
815
+ success: false,
816
+ };
817
+ }),
818
+ };
819
+ }
820
+ }
821
+ }
822
+ return {
823
+ issues: aggregatedIssues,
824
+ suggestions: aggregatedSuggestions,
825
+ debug: aggregatedDebug,
826
+ };
827
+ }
828
+ /**
829
+ * Get available check types
830
+ */
831
+ static getAvailableCheckTypes() {
832
+ const registry = check_provider_registry_1.CheckProviderRegistry.getInstance();
833
+ const providerTypes = registry.getAvailableProviders();
834
+ // Add standard focus-based checks
835
+ const standardTypes = ['security', 'performance', 'style', 'architecture', 'all'];
836
+ // Combine provider types with standard types (remove duplicates)
837
+ return [...new Set([...providerTypes, ...standardTypes])];
838
+ }
839
+ /**
840
+ * Validate check types
841
+ */
842
+ static validateCheckTypes(checks) {
843
+ const availableChecks = CheckExecutionEngine.getAvailableCheckTypes();
844
+ const valid = [];
845
+ const invalid = [];
846
+ for (const check of checks) {
847
+ if (availableChecks.includes(check)) {
848
+ valid.push(check);
849
+ }
850
+ else {
851
+ invalid.push(check);
852
+ }
853
+ }
854
+ return { valid, invalid };
855
+ }
856
+ /**
857
+ * List available providers with their status
858
+ */
859
+ async listProviders() {
860
+ return await this.providerRegistry.listProviders();
861
+ }
862
+ /**
863
+ * Create a mock Octokit instance for local analysis
864
+ */
865
+ createMockOctokit() {
866
+ // Create simple mock functions that return promises
867
+ const mockGet = async () => ({
868
+ data: {
869
+ number: 0,
870
+ title: 'Local Analysis',
871
+ body: 'Local repository analysis',
872
+ user: { login: 'local-user' },
873
+ base: { ref: 'main' },
874
+ head: { ref: 'HEAD' },
875
+ },
876
+ });
877
+ const mockListFiles = async () => ({
878
+ data: [],
879
+ });
880
+ const mockListComments = async () => ({
881
+ data: [],
882
+ });
883
+ const mockCreateComment = async () => ({
884
+ data: { id: 1 },
885
+ });
886
+ return {
887
+ rest: {
888
+ pulls: {
889
+ get: mockGet,
890
+ listFiles: mockListFiles,
891
+ },
892
+ issues: {
893
+ listComments: mockListComments,
894
+ createComment: mockCreateComment,
895
+ },
896
+ },
897
+ request: async () => ({ data: {} }),
898
+ graphql: async () => ({}),
899
+ log: {
900
+ debug: () => { },
901
+ info: () => { },
902
+ warn: () => { },
903
+ error: () => { },
904
+ },
905
+ hook: {
906
+ before: () => { },
907
+ after: () => { },
908
+ error: () => { },
909
+ wrap: () => { },
910
+ },
911
+ auth: async () => ({ token: 'mock-token' }),
912
+ };
913
+ }
914
+ /**
915
+ * Create an error result
916
+ */
917
+ createErrorResult(repositoryInfo, errorMessage, startTime, timestamp, checksExecuted) {
918
+ const executionTime = Date.now() - startTime;
919
+ return {
920
+ repositoryInfo,
921
+ reviewSummary: {
922
+ issues: [
923
+ {
924
+ file: 'system',
925
+ line: 0,
926
+ endLine: undefined,
927
+ ruleId: 'system/error',
928
+ message: errorMessage,
929
+ severity: 'error',
930
+ category: 'logic',
931
+ suggestion: undefined,
932
+ replacement: undefined,
933
+ },
934
+ ],
935
+ suggestions: [`Error: ${errorMessage}`],
936
+ },
937
+ executionTime,
938
+ timestamp,
939
+ checksExecuted,
940
+ };
941
+ }
942
+ /**
943
+ * Check if the working directory is a valid git repository
944
+ */
945
+ async isGitRepository() {
946
+ try {
947
+ const repositoryInfo = await this.gitAnalyzer.analyzeRepository();
948
+ return repositoryInfo.isGitRepository;
949
+ }
950
+ catch {
951
+ return false;
952
+ }
953
+ }
954
+ /**
955
+ * Evaluate failure conditions for a check result
956
+ */
957
+ async evaluateFailureConditions(checkName, reviewSummary, config) {
958
+ if (!config) {
959
+ return [];
960
+ }
961
+ const checkConfig = config.checks[checkName];
962
+ const checkSchema = checkConfig?.schema || '';
963
+ const checkGroup = checkConfig?.group || '';
964
+ // Handle new simple fail_if syntax
965
+ const globalFailIf = config.fail_if;
966
+ const checkFailIf = checkConfig?.fail_if;
967
+ // If using new fail_if syntax
968
+ if (globalFailIf || checkFailIf) {
969
+ const results = [];
970
+ // Evaluate global fail_if
971
+ if (globalFailIf) {
972
+ const failed = await this.failureEvaluator.evaluateSimpleCondition(checkName, checkSchema, checkGroup, reviewSummary, globalFailIf);
973
+ if (failed) {
974
+ results.push({
975
+ conditionName: 'global_fail_if',
976
+ expression: globalFailIf,
977
+ failed: true,
978
+ severity: 'error',
979
+ message: 'Global failure condition met',
980
+ haltExecution: false,
981
+ });
982
+ }
983
+ }
984
+ // Evaluate check-specific fail_if (overrides global if present)
985
+ if (checkFailIf) {
986
+ const failed = await this.failureEvaluator.evaluateSimpleCondition(checkName, checkSchema, checkGroup, reviewSummary, checkFailIf);
987
+ if (failed) {
988
+ results.push({
989
+ conditionName: `${checkName}_fail_if`,
990
+ expression: checkFailIf,
991
+ failed: true,
992
+ severity: 'error',
993
+ message: `Check ${checkName} failure condition met`,
994
+ haltExecution: false,
995
+ });
996
+ }
997
+ }
998
+ return results;
999
+ }
1000
+ // Fall back to old failure_conditions syntax
1001
+ const globalConditions = config.failure_conditions;
1002
+ const checkConditions = checkConfig?.failure_conditions;
1003
+ return await this.failureEvaluator.evaluateConditions(checkName, checkSchema, checkGroup, reviewSummary, globalConditions, checkConditions);
1004
+ }
1005
+ /**
1006
+ * Get repository status summary
1007
+ */
1008
+ async getRepositoryStatus() {
1009
+ try {
1010
+ const repositoryInfo = await this.gitAnalyzer.analyzeRepository();
1011
+ return {
1012
+ isGitRepository: repositoryInfo.isGitRepository,
1013
+ hasChanges: repositoryInfo.files.length > 0,
1014
+ branch: repositoryInfo.head,
1015
+ filesChanged: repositoryInfo.files.length,
1016
+ };
1017
+ }
1018
+ catch {
1019
+ return {
1020
+ isGitRepository: false,
1021
+ hasChanges: false,
1022
+ branch: 'unknown',
1023
+ filesChanged: 0,
1024
+ };
1025
+ }
1026
+ }
1027
+ /**
1028
+ * Initialize GitHub check runs for each configured check
1029
+ */
1030
+ async initializeGitHubChecks(options, logFn) {
1031
+ if (!options.githubChecks?.octokit ||
1032
+ !options.githubChecks.owner ||
1033
+ !options.githubChecks.repo ||
1034
+ !options.githubChecks.headSha) {
1035
+ logFn('⚠️ GitHub checks enabled but missing required parameters');
1036
+ return;
1037
+ }
1038
+ try {
1039
+ this.githubCheckService = new github_check_service_1.GitHubCheckService(options.githubChecks.octokit);
1040
+ this.checkRunMap = new Map();
1041
+ this.githubContext = {
1042
+ owner: options.githubChecks.owner,
1043
+ repo: options.githubChecks.repo,
1044
+ };
1045
+ logFn(`🔍 Creating GitHub check runs for ${options.checks.length} checks...`);
1046
+ for (const checkName of options.checks) {
1047
+ try {
1048
+ const checkRunOptions = {
1049
+ owner: options.githubChecks.owner,
1050
+ repo: options.githubChecks.repo,
1051
+ head_sha: options.githubChecks.headSha,
1052
+ name: `Visor: ${checkName}`,
1053
+ external_id: `visor-${checkName}-${options.githubChecks.headSha.substring(0, 7)}`,
1054
+ };
1055
+ const checkRun = await this.githubCheckService.createCheckRun(checkRunOptions, {
1056
+ title: `${checkName} Analysis`,
1057
+ summary: `Running ${checkName} check using AI-powered analysis...`,
1058
+ });
1059
+ this.checkRunMap.set(checkName, checkRun);
1060
+ logFn(`✅ Created check run for ${checkName}: ${checkRun.url}`);
1061
+ }
1062
+ catch (error) {
1063
+ logFn(`❌ Failed to create check run for ${checkName}: ${error}`);
1064
+ }
1065
+ }
1066
+ }
1067
+ catch (error) {
1068
+ // Check if this is a permissions error
1069
+ if (error instanceof Error &&
1070
+ (error.message.includes('403') || error.message.includes('checks:write'))) {
1071
+ logFn('⚠️ GitHub checks API not available - insufficient permissions. Check runs will be skipped.');
1072
+ logFn('💡 To enable check runs, ensure your GitHub token has "checks:write" permission.');
1073
+ this.githubCheckService = undefined;
1074
+ this.checkRunMap = undefined;
1075
+ }
1076
+ else {
1077
+ logFn(`❌ Failed to initialize GitHub check runs: ${error}`);
1078
+ this.githubCheckService = undefined;
1079
+ this.checkRunMap = undefined;
1080
+ }
1081
+ }
1082
+ }
1083
+ /**
1084
+ * Update GitHub check runs to in-progress status
1085
+ */
1086
+ async updateGitHubChecksInProgress(options) {
1087
+ if (!this.githubCheckService ||
1088
+ !this.checkRunMap ||
1089
+ !options.githubChecks?.owner ||
1090
+ !options.githubChecks.repo) {
1091
+ return;
1092
+ }
1093
+ for (const [checkName, checkRun] of this.checkRunMap) {
1094
+ try {
1095
+ await this.githubCheckService.updateCheckRunInProgress(options.githubChecks.owner, options.githubChecks.repo, checkRun.id, {
1096
+ title: `Analyzing with ${checkName}...`,
1097
+ summary: `AI-powered analysis is in progress for ${checkName} check.`,
1098
+ });
1099
+ console.log(`🔄 Updated ${checkName} check to in-progress status`);
1100
+ }
1101
+ catch (error) {
1102
+ console.error(`❌ Failed to update ${checkName} check to in-progress: ${error}`);
1103
+ }
1104
+ }
1105
+ }
1106
+ /**
1107
+ * Complete GitHub check runs with results
1108
+ */
1109
+ async completeGitHubChecksWithResults(reviewSummary, options) {
1110
+ if (!this.githubCheckService ||
1111
+ !this.checkRunMap ||
1112
+ !options.githubChecks?.owner ||
1113
+ !options.githubChecks.repo) {
1114
+ return;
1115
+ }
1116
+ // Group issues by check name
1117
+ const issuesByCheck = new Map();
1118
+ // Initialize empty arrays for all checks
1119
+ for (const checkName of this.checkRunMap.keys()) {
1120
+ issuesByCheck.set(checkName, []);
1121
+ }
1122
+ // Group issues by their check name (extracted from ruleId prefix)
1123
+ for (const issue of reviewSummary.issues || []) {
1124
+ if (issue.ruleId && issue.ruleId.includes('/')) {
1125
+ const checkName = issue.ruleId.split('/')[0];
1126
+ if (issuesByCheck.has(checkName)) {
1127
+ issuesByCheck.get(checkName).push(issue);
1128
+ }
1129
+ }
1130
+ }
1131
+ console.log(`🏁 Completing ${this.checkRunMap.size} GitHub check runs...`);
1132
+ for (const [checkName, checkRun] of this.checkRunMap) {
1133
+ try {
1134
+ const checkIssues = issuesByCheck.get(checkName) || [];
1135
+ // Evaluate failure conditions for this specific check
1136
+ const failureResults = await this.evaluateFailureConditions(checkName, { issues: checkIssues, suggestions: [] }, options.config);
1137
+ await this.githubCheckService.completeCheckRun(options.githubChecks.owner, options.githubChecks.repo, checkRun.id, checkName, failureResults, checkIssues);
1138
+ console.log(`✅ Completed ${checkName} check with ${checkIssues.length} issues`);
1139
+ }
1140
+ catch (error) {
1141
+ console.error(`❌ Failed to complete ${checkName} check: ${error}`);
1142
+ // Try to mark the check as failed due to execution error
1143
+ try {
1144
+ await this.githubCheckService.completeCheckRun(options.githubChecks.owner, options.githubChecks.repo, checkRun.id, checkName, [], [], error instanceof Error ? error.message : 'Unknown error occurred');
1145
+ }
1146
+ catch (finalError) {
1147
+ console.error(`❌ Failed to mark ${checkName} check as failed: ${finalError}`);
1148
+ }
1149
+ }
1150
+ }
1151
+ }
1152
+ /**
1153
+ * Complete GitHub check runs with error status
1154
+ */
1155
+ async completeGitHubChecksWithError(errorMessage) {
1156
+ if (!this.githubCheckService || !this.checkRunMap || !this.githubContext) {
1157
+ return;
1158
+ }
1159
+ console.log(`❌ Completing ${this.checkRunMap.size} GitHub check runs with error...`);
1160
+ for (const [checkName, checkRun] of this.checkRunMap) {
1161
+ try {
1162
+ await this.githubCheckService.completeCheckRun(this.githubContext.owner, this.githubContext.repo, checkRun.id, checkName, [], [], errorMessage);
1163
+ console.log(`❌ Completed ${checkName} check with error: ${errorMessage}`);
1164
+ }
1165
+ catch (error) {
1166
+ console.error(`❌ Failed to complete ${checkName} check with error: ${error}`);
1167
+ }
1168
+ }
1169
+ }
1170
+ }
1171
+ exports.CheckExecutionEngine = CheckExecutionEngine;
1172
+ //# sourceMappingURL=check-execution-engine.js.map