@probelabs/visor 0.1.17 → 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.
- package/README.md +78 -0
- package/defaults/.visor.yaml +53 -1
- package/dist/ai-review-service.js +2 -2
- package/dist/ai-review-service.js.map +1 -1
- package/dist/check-execution-engine.d.ts +32 -1
- package/dist/check-execution-engine.d.ts.map +1 -1
- package/dist/check-execution-engine.js +289 -19
- package/dist/check-execution-engine.js.map +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +18 -3
- package/dist/config.js.map +1 -1
- package/dist/event-mapper.d.ts.map +1 -1
- package/dist/event-mapper.js +1 -1
- package/dist/event-mapper.js.map +1 -1
- package/dist/failure-condition-evaluator.d.ts +8 -0
- package/dist/failure-condition-evaluator.d.ts.map +1 -1
- package/dist/failure-condition-evaluator.js +25 -3
- package/dist/failure-condition-evaluator.js.map +1 -1
- package/dist/github-check-service.d.ts.map +1 -1
- package/dist/github-check-service.js +26 -39
- package/dist/github-check-service.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +331 -690
- package/dist/index.js.map +1 -1
- package/dist/licenses.txt +2300 -0
- package/dist/output/code-review/schema.json +84 -0
- package/dist/output/code-review/template.liquid +32 -0
- package/dist/output/plain/schema.json +14 -0
- package/dist/output/plain/template.liquid +1 -0
- package/dist/output-formatters.d.ts.map +1 -1
- package/dist/output-formatters.js +14 -14
- package/dist/output-formatters.js.map +1 -1
- package/dist/pr-analyzer.d.ts +2 -1
- package/dist/pr-analyzer.d.ts.map +1 -1
- package/dist/pr-analyzer.js +2 -1
- package/dist/pr-analyzer.js.map +1 -1
- package/dist/providers/ai-check-provider.d.ts.map +1 -1
- package/dist/providers/ai-check-provider.js +4 -0
- package/dist/providers/ai-check-provider.js.map +1 -1
- package/dist/providers/check-provider-registry.js +2 -2
- package/dist/providers/check-provider-registry.js.map +1 -1
- package/dist/providers/check-provider.interface.d.ts +2 -0
- package/dist/providers/check-provider.interface.d.ts.map +1 -1
- package/dist/providers/check-provider.interface.js.map +1 -1
- package/dist/providers/index.d.ts +1 -1
- package/dist/providers/index.d.ts.map +1 -1
- package/dist/providers/index.js +3 -3
- package/dist/providers/index.js.map +1 -1
- package/dist/providers/noop-check-provider.d.ts +25 -0
- package/dist/providers/noop-check-provider.d.ts.map +1 -0
- package/dist/providers/noop-check-provider.js +55 -0
- package/dist/providers/noop-check-provider.js.map +1 -0
- package/dist/providers/tool-check-provider.d.ts +4 -1
- package/dist/providers/tool-check-provider.d.ts.map +1 -1
- package/dist/providers/tool-check-provider.js +64 -15
- package/dist/providers/tool-check-provider.js.map +1 -1
- package/dist/reviewer.d.ts +19 -37
- package/dist/reviewer.d.ts.map +1 -1
- package/dist/reviewer.js +83 -593
- package/dist/reviewer.js.map +1 -1
- package/dist/tiktoken_bg.wasm +0 -0
- package/dist/types/config.d.ts +52 -5
- package/dist/types/config.d.ts.map +1 -1
- package/package.json +3 -3
- package/dist/providers/script-check-provider.d.ts +0 -23
- package/dist/providers/script-check-provider.d.ts.map +0 -1
- package/dist/providers/script-check-provider.js +0 -163
- package/dist/providers/script-check-provider.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -41,11 +41,8 @@ const commands_1 = require("./commands");
|
|
|
41
41
|
const pr_analyzer_1 = require("./pr-analyzer");
|
|
42
42
|
const reviewer_1 = require("./reviewer");
|
|
43
43
|
const action_cli_bridge_1 = require("./action-cli-bridge");
|
|
44
|
-
const github_comments_1 = require("./github-comments");
|
|
45
44
|
const config_1 = require("./config");
|
|
46
|
-
const pr_detector_1 = require("./pr-detector");
|
|
47
45
|
const github_check_service_1 = require("./github-check-service");
|
|
48
|
-
const failure_condition_evaluator_1 = require("./failure-condition-evaluator");
|
|
49
46
|
/**
|
|
50
47
|
* Create an authenticated Octokit instance using either GitHub App or token authentication
|
|
51
48
|
*/
|
|
@@ -131,7 +128,6 @@ async function run() {
|
|
|
131
128
|
'github-token': token,
|
|
132
129
|
owner: (0, core_1.getInput)('owner') || process.env.GITHUB_REPOSITORY_OWNER,
|
|
133
130
|
repo: (0, core_1.getInput)('repo') || process.env.GITHUB_REPOSITORY?.split('/')[1],
|
|
134
|
-
'auto-review': (0, core_1.getInput)('auto-review'),
|
|
135
131
|
debug: (0, core_1.getInput)('debug'),
|
|
136
132
|
// GitHub App authentication inputs
|
|
137
133
|
'app-id': (0, core_1.getInput)('app-id') || undefined,
|
|
@@ -153,7 +149,6 @@ async function run() {
|
|
|
153
149
|
'visor-checks': (0, core_1.getInput)('visor-checks') || undefined,
|
|
154
150
|
};
|
|
155
151
|
const eventName = process.env.GITHUB_EVENT_NAME;
|
|
156
|
-
const autoReview = inputs['auto-review'] === 'true';
|
|
157
152
|
// Create GitHub context for CLI bridge
|
|
158
153
|
const context = {
|
|
159
154
|
event_name: eventName || 'unknown',
|
|
@@ -176,26 +171,35 @@ async function run() {
|
|
|
176
171
|
console.log('Debug: inputs.visor-config-path =', inputs['visor-config-path']);
|
|
177
172
|
if (cliBridge.shouldUseVisor(inputs)) {
|
|
178
173
|
console.log('🔍 Using Visor CLI mode');
|
|
179
|
-
// ENHANCED FIX: For PR auto-reviews, detect PR context across all event types
|
|
180
|
-
const isAutoReview = inputs['auto-review'] === 'true';
|
|
181
|
-
if (isAutoReview) {
|
|
182
|
-
console.log('🔄 Auto-review enabled - attempting to detect PR context across all event types');
|
|
183
|
-
// Try to detect if we're in a PR context (works for push, pull_request, issue_comment, etc.)
|
|
184
|
-
const prDetected = await detectPRContext(inputs, context, octokit);
|
|
185
|
-
if (prDetected) {
|
|
186
|
-
console.log('✅ PR context detected - using GitHub API for PR analysis');
|
|
187
|
-
await handlePullRequestVisorMode(inputs, context, octokit, authType);
|
|
188
|
-
return;
|
|
189
|
-
}
|
|
190
|
-
else {
|
|
191
|
-
console.log('ℹ️ No PR context detected - proceeding with CLI mode for general analysis');
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
174
|
await handleVisorMode(cliBridge, inputs, context, octokit);
|
|
195
175
|
return;
|
|
196
176
|
}
|
|
197
|
-
|
|
198
|
-
|
|
177
|
+
// Default behavior: Use Visor config to determine what to run
|
|
178
|
+
console.log('🤖 Using config-driven mode');
|
|
179
|
+
// Load config to determine which checks should run for this event
|
|
180
|
+
const configManager = new config_1.ConfigManager();
|
|
181
|
+
let config;
|
|
182
|
+
try {
|
|
183
|
+
config = await configManager.findAndLoadConfig();
|
|
184
|
+
console.log('📋 Loaded Visor config');
|
|
185
|
+
}
|
|
186
|
+
catch {
|
|
187
|
+
// Use default config if none found
|
|
188
|
+
config = {
|
|
189
|
+
version: '1.0',
|
|
190
|
+
checks: {},
|
|
191
|
+
output: {
|
|
192
|
+
pr_comment: {
|
|
193
|
+
format: 'markdown',
|
|
194
|
+
group_by: 'check',
|
|
195
|
+
collapse: false,
|
|
196
|
+
},
|
|
197
|
+
},
|
|
198
|
+
};
|
|
199
|
+
console.log('📋 Using default configuration');
|
|
200
|
+
}
|
|
201
|
+
// Determine which event we're handling and run appropriate checks
|
|
202
|
+
await handleEvent(octokit, inputs, eventName, context, config);
|
|
199
203
|
}
|
|
200
204
|
catch (error) {
|
|
201
205
|
(0, core_1.setFailed)(error instanceof Error ? error.message : 'Unknown error');
|
|
@@ -253,231 +257,103 @@ async function handleVisorMode(cliBridge, inputs, _context, _octokit) {
|
|
|
253
257
|
(0, core_1.setFailed)(error instanceof Error ? error.message : 'Visor mode failed');
|
|
254
258
|
}
|
|
255
259
|
}
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
owner: { login: owner },
|
|
275
|
-
name: repo,
|
|
276
|
-
},
|
|
277
|
-
event: process.env.GITHUB_CONTEXT ? JSON.parse(process.env.GITHUB_CONTEXT).event : undefined,
|
|
278
|
-
payload: process.env.GITHUB_CONTEXT ? JSON.parse(process.env.GITHUB_CONTEXT) : {},
|
|
279
|
-
};
|
|
280
|
-
// Use robust PR detection
|
|
281
|
-
const prResult = await prDetector.detectPRNumber(eventContext, owner, repo);
|
|
282
|
-
if (!prResult.prNumber) {
|
|
283
|
-
console.log(`⚠️ No PR found using any detection strategy: ${prResult.details || 'Unknown reason'}`);
|
|
284
|
-
if (inputs.debug === 'true') {
|
|
285
|
-
console.log('Available detection strategies:');
|
|
286
|
-
prDetector.getDetectionStrategies().forEach(strategy => console.log(` ${strategy}`));
|
|
287
|
-
}
|
|
288
|
-
return;
|
|
289
|
-
}
|
|
290
|
-
console.log(`✅ Found PR #${prResult.prNumber} using ${prResult.source} (confidence: ${prResult.confidence})`);
|
|
291
|
-
if (prResult.details) {
|
|
292
|
-
console.log(` Details: ${prResult.details}`);
|
|
293
|
-
}
|
|
294
|
-
const commentManager = new github_comments_1.CommentManager(octokit);
|
|
295
|
-
// Create Visor-formatted comment from CLI output
|
|
296
|
-
let comment = `# 🔍 Visor Code Review Results\n\n`;
|
|
297
|
-
comment += `## 📊 Summary\n`;
|
|
298
|
-
comment += `- **Overall Score**: ${cliOutput.overallScore || 0}/100\n`;
|
|
299
|
-
comment += `- **Issues Found**: ${cliOutput.totalIssues || 0} (${cliOutput.criticalIssues || 0} Critical)\n`;
|
|
300
|
-
comment += `- **Files Analyzed**: ${cliOutput.filesAnalyzed || 'N/A'}\n\n`;
|
|
301
|
-
// Add category scores if available
|
|
302
|
-
if (cliOutput.securityScore ||
|
|
303
|
-
cliOutput.performanceScore ||
|
|
304
|
-
cliOutput.styleScore ||
|
|
305
|
-
cliOutput.architectureScore) {
|
|
306
|
-
comment += `## 📈 Category Scores\n`;
|
|
307
|
-
if (cliOutput.securityScore !== undefined)
|
|
308
|
-
comment += `- **Security**: ${cliOutput.securityScore}/100\n`;
|
|
309
|
-
if (cliOutput.performanceScore !== undefined)
|
|
310
|
-
comment += `- **Performance**: ${cliOutput.performanceScore}/100\n`;
|
|
311
|
-
if (cliOutput.styleScore !== undefined)
|
|
312
|
-
comment += `- **Style**: ${cliOutput.styleScore}/100\n`;
|
|
313
|
-
if (cliOutput.architectureScore !== undefined)
|
|
314
|
-
comment += `- **Architecture**: ${cliOutput.architectureScore}/100\n`;
|
|
315
|
-
comment += '\n';
|
|
316
|
-
}
|
|
317
|
-
// Load config to determine grouping method
|
|
318
|
-
const { ConfigManager } = await Promise.resolve().then(() => __importStar(require('./config')));
|
|
319
|
-
const configManager = new ConfigManager();
|
|
320
|
-
const config = await configManager.findAndLoadConfig();
|
|
321
|
-
// Add issues grouped by check or category based on config
|
|
322
|
-
if (cliOutput.issues && cliOutput.issues.length > 0) {
|
|
323
|
-
// Always use check-based grouping when configured
|
|
324
|
-
const useCheckGrouping = config.output?.pr_comment?.group_by === 'check';
|
|
325
|
-
const groupedIssues = useCheckGrouping
|
|
326
|
-
? groupIssuesByCheck(cliOutput.issues)
|
|
327
|
-
: groupIssuesByCategory(cliOutput.issues);
|
|
328
|
-
// Get configured checks for filtering
|
|
329
|
-
const configuredChecks = config.checks ? Object.keys(config.checks) : [];
|
|
330
|
-
for (const [groupKey, issues] of Object.entries(groupedIssues)) {
|
|
331
|
-
if (issues.length === 0)
|
|
332
|
-
continue;
|
|
333
|
-
// When using check-based grouping, only show configured checks
|
|
334
|
-
if (useCheckGrouping && configuredChecks.length > 0) {
|
|
335
|
-
// Skip if not a configured check (unless it's uncategorized)
|
|
336
|
-
if (!configuredChecks.includes(groupKey) && groupKey !== 'uncategorized') {
|
|
337
|
-
continue;
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
const title = `${groupKey.charAt(0).toUpperCase() + groupKey.slice(1)} Issues (${issues.length})`;
|
|
341
|
-
let sectionContent = '';
|
|
342
|
-
for (const issue of issues.slice(0, 5)) {
|
|
343
|
-
// Limit to 5 issues per category
|
|
344
|
-
sectionContent += `- **${issue.severity?.toUpperCase() || 'UNKNOWN'}**: ${issue.message}\n`;
|
|
345
|
-
sectionContent += ` - **File**: \`${issue.file}:${issue.line}\`\n\n`;
|
|
346
|
-
}
|
|
347
|
-
if (issues.length > 5) {
|
|
348
|
-
sectionContent += `*...and ${issues.length - 5} more issues in this category.*\n\n`;
|
|
349
|
-
}
|
|
350
|
-
comment += commentManager.createCollapsibleSection(title, sectionContent, true);
|
|
351
|
-
comment += '\n\n';
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
// Add suggestions if any
|
|
355
|
-
if (cliOutput.suggestions && cliOutput.suggestions.length > 0) {
|
|
356
|
-
const suggestionsContent = cliOutput.suggestions.map((s) => `- ${s}`).join('\n') + '\n';
|
|
357
|
-
comment += commentManager.createCollapsibleSection('💡 Recommendations', suggestionsContent, true);
|
|
358
|
-
comment += '\n\n';
|
|
359
|
-
}
|
|
360
|
-
// Add debug information if available
|
|
361
|
-
if (cliOutput.debug) {
|
|
362
|
-
const debugContent = formatDebugInfo(cliOutput.debug);
|
|
363
|
-
comment +=
|
|
364
|
-
'\n\n' +
|
|
365
|
-
commentManager.createCollapsibleSection('🐛 Debug Information', debugContent, false);
|
|
366
|
-
comment += '\n\n';
|
|
367
|
-
}
|
|
368
|
-
// Fetch fresh PR data to get the latest commit SHA
|
|
369
|
-
let latestCommitSha;
|
|
370
|
-
try {
|
|
371
|
-
const { data: pullRequest } = await octokit.rest.pulls.get({
|
|
372
|
-
owner,
|
|
373
|
-
repo,
|
|
374
|
-
pull_number: prResult.prNumber,
|
|
375
|
-
});
|
|
376
|
-
latestCommitSha = pullRequest.head.sha;
|
|
377
|
-
console.log(`📝 Latest commit SHA: ${latestCommitSha.substring(0, 7)}`);
|
|
378
|
-
}
|
|
379
|
-
catch (error) {
|
|
380
|
-
console.warn('⚠️ Could not fetch latest PR data:', error);
|
|
381
|
-
// Fallback to environment or event data
|
|
382
|
-
latestCommitSha =
|
|
383
|
-
eventContext.event?.pull_request?.head?.sha ||
|
|
384
|
-
eventContext.payload?.pull_request?.head
|
|
385
|
-
?.sha ||
|
|
386
|
-
process.env.GITHUB_SHA;
|
|
387
|
-
}
|
|
388
|
-
// Use smart comment updating with unique ID
|
|
389
|
-
const commentId = `visor-cli-review-${prResult.prNumber}`;
|
|
390
|
-
await commentManager.updateOrCreateComment(owner, repo, prResult.prNumber, comment, {
|
|
391
|
-
commentId,
|
|
392
|
-
triggeredBy: 'visor-cli',
|
|
393
|
-
allowConcurrentUpdates: true,
|
|
394
|
-
commitSha: latestCommitSha,
|
|
395
|
-
});
|
|
396
|
-
console.log(`✅ Posted CLI review comment to PR #${prResult.prNumber}`);
|
|
397
|
-
}
|
|
398
|
-
catch (error) {
|
|
399
|
-
console.error('❌ Failed to post CLI review comment:', error);
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
function groupIssuesByCategory(issues) {
|
|
403
|
-
const grouped = {
|
|
404
|
-
security: [],
|
|
405
|
-
performance: [],
|
|
406
|
-
style: [],
|
|
407
|
-
logic: [],
|
|
408
|
-
documentation: [],
|
|
409
|
-
architecture: [],
|
|
410
|
-
};
|
|
411
|
-
for (const issue of issues) {
|
|
412
|
-
const category = issue.category || 'logic';
|
|
413
|
-
if (!grouped[category])
|
|
414
|
-
grouped[category] = [];
|
|
415
|
-
grouped[category].push(issue);
|
|
416
|
-
}
|
|
417
|
-
return grouped;
|
|
418
|
-
}
|
|
419
|
-
/**
|
|
420
|
-
* Group issues by the check that found them (extracted from ruleId prefix)
|
|
421
|
-
*/
|
|
422
|
-
function groupIssuesByCheck(issues) {
|
|
423
|
-
const grouped = {};
|
|
424
|
-
for (const issue of issues) {
|
|
425
|
-
// Extract check name from ruleId prefix
|
|
426
|
-
// Format: "checkName/specific-rule" -> "checkName"
|
|
427
|
-
let checkName = 'uncategorized';
|
|
428
|
-
if (issue.ruleId && issue.ruleId.includes('/')) {
|
|
429
|
-
const parts = issue.ruleId.split('/');
|
|
430
|
-
checkName = parts[0];
|
|
431
|
-
}
|
|
432
|
-
// No fallback to category - only use ruleId prefix
|
|
433
|
-
if (!grouped[checkName]) {
|
|
434
|
-
grouped[checkName] = [];
|
|
435
|
-
}
|
|
436
|
-
grouped[checkName].push(issue);
|
|
260
|
+
function mapGitHubEventToTrigger(eventName, action) {
|
|
261
|
+
if (!eventName)
|
|
262
|
+
return 'pr_updated';
|
|
263
|
+
switch (eventName) {
|
|
264
|
+
case 'pull_request':
|
|
265
|
+
if (action === 'opened')
|
|
266
|
+
return 'pr_opened';
|
|
267
|
+
if (action === 'synchronize' || action === 'edited')
|
|
268
|
+
return 'pr_updated';
|
|
269
|
+
return 'pr_updated';
|
|
270
|
+
case 'issues':
|
|
271
|
+
if (action === 'opened')
|
|
272
|
+
return 'issue_opened';
|
|
273
|
+
return 'issue_opened';
|
|
274
|
+
case 'issue_comment':
|
|
275
|
+
return 'issue_comment';
|
|
276
|
+
default:
|
|
277
|
+
return 'pr_updated';
|
|
437
278
|
}
|
|
438
|
-
return grouped;
|
|
439
|
-
}
|
|
440
|
-
function formatDebugInfo(debug) {
|
|
441
|
-
let content = '';
|
|
442
|
-
if (debug.provider)
|
|
443
|
-
content += `**Provider:** ${debug.provider}\n`;
|
|
444
|
-
if (debug.model)
|
|
445
|
-
content += `**Model:** ${debug.model}\n`;
|
|
446
|
-
if (debug.processingTime)
|
|
447
|
-
content += `**Processing Time:** ${debug.processingTime}ms\n`;
|
|
448
|
-
if (debug.parallelExecution !== undefined)
|
|
449
|
-
content += `**Parallel Execution:** ${debug.parallelExecution ? '✅' : '❌'}\n`;
|
|
450
|
-
if (debug.checksExecuted)
|
|
451
|
-
content += `**Checks Executed:** ${debug.checksExecuted.join(', ')}\n`;
|
|
452
|
-
content += '\n';
|
|
453
|
-
return content;
|
|
454
279
|
}
|
|
455
280
|
/**
|
|
456
|
-
* Handle
|
|
281
|
+
* Handle events based on config
|
|
457
282
|
*/
|
|
458
|
-
async function
|
|
283
|
+
async function handleEvent(octokit, inputs, eventName, context, config) {
|
|
459
284
|
const owner = inputs.owner;
|
|
460
285
|
const repo = inputs.repo;
|
|
461
286
|
if (!owner || !repo) {
|
|
462
287
|
throw new Error('Owner and repo are required');
|
|
463
288
|
}
|
|
464
289
|
console.log(`Event: ${eventName}, Owner: ${owner}, Repo: ${repo}`);
|
|
290
|
+
// Map GitHub event to our event trigger format
|
|
291
|
+
const eventType = mapGitHubEventToTrigger(eventName, context.event?.action);
|
|
292
|
+
// Find checks that should run for this event
|
|
293
|
+
const checksToRun = [];
|
|
294
|
+
for (const [checkName, checkConfig] of Object.entries(config.checks || {})) {
|
|
295
|
+
// Check if this check should run for this event
|
|
296
|
+
const checkEvents = checkConfig.on || ['pr_opened', 'pr_updated'];
|
|
297
|
+
if (checkEvents.includes(eventType)) {
|
|
298
|
+
checksToRun.push(checkName);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
if (checksToRun.length === 0) {
|
|
302
|
+
console.log(`ℹ️ No checks configured to run for event: ${eventType}`);
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
console.log(`🔧 Checks to run for ${eventType}: ${checksToRun.join(', ')}`);
|
|
465
306
|
// Handle different GitHub events
|
|
466
307
|
switch (eventName) {
|
|
467
308
|
case 'issue_comment':
|
|
468
309
|
await handleIssueComment(octokit, owner, repo);
|
|
469
310
|
break;
|
|
470
311
|
case 'pull_request':
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
312
|
+
// Run the checks that are configured for this event
|
|
313
|
+
await handlePullRequestWithConfig(octokit, owner, repo, inputs, config, checksToRun);
|
|
314
|
+
break;
|
|
315
|
+
case 'push':
|
|
316
|
+
// Could handle push events that are associated with PRs
|
|
317
|
+
console.log('Push event detected - checking for associated PR');
|
|
474
318
|
break;
|
|
475
319
|
default:
|
|
476
|
-
// Fallback to
|
|
320
|
+
// Fallback to repo info for unknown events
|
|
321
|
+
console.log(`Unknown event: ${eventName}`);
|
|
477
322
|
await handleRepoInfo(octokit, owner, repo);
|
|
478
323
|
break;
|
|
479
324
|
}
|
|
480
325
|
}
|
|
326
|
+
/**
|
|
327
|
+
* Recursively resolve dependencies for a set of check IDs
|
|
328
|
+
*/
|
|
329
|
+
function resolveDependencies(checkIds, config, resolved = new Set(), visiting = new Set()) {
|
|
330
|
+
const result = [];
|
|
331
|
+
for (const checkId of checkIds) {
|
|
332
|
+
if (resolved.has(checkId)) {
|
|
333
|
+
continue;
|
|
334
|
+
}
|
|
335
|
+
if (visiting.has(checkId)) {
|
|
336
|
+
console.warn(`Circular dependency detected involving check: ${checkId}`);
|
|
337
|
+
continue;
|
|
338
|
+
}
|
|
339
|
+
visiting.add(checkId);
|
|
340
|
+
// Get dependencies for this check
|
|
341
|
+
const checkConfig = config?.checks?.[checkId];
|
|
342
|
+
const dependencies = checkConfig?.depends_on || [];
|
|
343
|
+
// Recursively resolve dependencies first
|
|
344
|
+
if (dependencies.length > 0) {
|
|
345
|
+
const resolvedDeps = resolveDependencies(dependencies, config, resolved, visiting);
|
|
346
|
+
result.push(...resolvedDeps.filter(dep => !result.includes(dep)));
|
|
347
|
+
}
|
|
348
|
+
// Add the current check if not already added
|
|
349
|
+
if (!result.includes(checkId)) {
|
|
350
|
+
result.push(checkId);
|
|
351
|
+
}
|
|
352
|
+
resolved.add(checkId);
|
|
353
|
+
visiting.delete(checkId);
|
|
354
|
+
}
|
|
355
|
+
return result;
|
|
356
|
+
}
|
|
481
357
|
async function handleIssueComment(octokit, owner, repo) {
|
|
482
358
|
const context = JSON.parse(process.env.GITHUB_CONTEXT || '{}');
|
|
483
359
|
const comment = context.event?.comment;
|
|
@@ -486,17 +362,21 @@ async function handleIssueComment(octokit, owner, repo) {
|
|
|
486
362
|
console.log('No comment or issue found in context');
|
|
487
363
|
return;
|
|
488
364
|
}
|
|
489
|
-
//
|
|
490
|
-
if (
|
|
491
|
-
|
|
365
|
+
// Prevent recursion: skip if comment is from visor itself
|
|
366
|
+
if (comment.body &&
|
|
367
|
+
(comment.body.includes('<!-- visor-comment-id:') ||
|
|
368
|
+
comment.body.includes('*Powered by [Visor]'))) {
|
|
369
|
+
console.log('Skipping visor comment to prevent recursion');
|
|
492
370
|
return;
|
|
493
371
|
}
|
|
372
|
+
// Process comments on both issues and PRs
|
|
373
|
+
// (issue.pull_request exists for PR comments, doesn't exist for issue comments)
|
|
494
374
|
// Load configuration to get available commands
|
|
495
375
|
const configManager = new config_1.ConfigManager();
|
|
496
376
|
let config;
|
|
497
377
|
const commandRegistry = {};
|
|
498
378
|
try {
|
|
499
|
-
config = await configManager.
|
|
379
|
+
config = await configManager.findAndLoadConfig();
|
|
500
380
|
// Build command registry from config
|
|
501
381
|
if (config.checks) {
|
|
502
382
|
// Add 'review' command that runs all checks
|
|
@@ -517,7 +397,7 @@ async function handleIssueComment(octokit, owner, repo) {
|
|
|
517
397
|
}
|
|
518
398
|
catch {
|
|
519
399
|
console.log('Could not load config, using defaults');
|
|
520
|
-
config =
|
|
400
|
+
config = undefined;
|
|
521
401
|
// Default commands when no config is available
|
|
522
402
|
commandRegistry['review'] = ['security', 'performance', 'style', 'architecture'];
|
|
523
403
|
}
|
|
@@ -534,7 +414,7 @@ async function handleIssueComment(octokit, owner, repo) {
|
|
|
534
414
|
const reviewer = new reviewer_1.PRReviewer(octokit);
|
|
535
415
|
switch (command.type) {
|
|
536
416
|
case 'status':
|
|
537
|
-
const statusPrInfo = await analyzer.fetchPRDiff(owner, repo, prNumber);
|
|
417
|
+
const statusPrInfo = await analyzer.fetchPRDiff(owner, repo, prNumber, undefined, 'issue_comment');
|
|
538
418
|
const statusComment = `## 📊 PR Status\n\n` +
|
|
539
419
|
`**Title:** ${statusPrInfo.title}\n` +
|
|
540
420
|
`**Author:** ${statusPrInfo.author}\n` +
|
|
@@ -562,9 +442,11 @@ async function handleIssueComment(octokit, owner, repo) {
|
|
|
562
442
|
default:
|
|
563
443
|
// Handle custom commands from config
|
|
564
444
|
if (commandRegistry[command.type]) {
|
|
565
|
-
const
|
|
566
|
-
|
|
567
|
-
const
|
|
445
|
+
const initialCheckIds = commandRegistry[command.type];
|
|
446
|
+
// Resolve all dependencies recursively
|
|
447
|
+
const checkIds = resolveDependencies(initialCheckIds, config);
|
|
448
|
+
console.log(`Running checks for command /${command.type} (initial: ${initialCheckIds.join(', ')}, resolved: ${checkIds.join(', ')})`);
|
|
449
|
+
const prInfo = await analyzer.fetchPRDiff(owner, repo, prNumber, undefined, 'issue_comment');
|
|
568
450
|
// Extract common arguments
|
|
569
451
|
const focus = command.args?.find(arg => arg.startsWith('--focus='))?.split('=')[1];
|
|
570
452
|
const format = command.args?.find(arg => arg.startsWith('--format='))?.split('=')[1];
|
|
@@ -576,23 +458,25 @@ async function handleIssueComment(octokit, owner, repo) {
|
|
|
576
458
|
}
|
|
577
459
|
}
|
|
578
460
|
}
|
|
579
|
-
const
|
|
461
|
+
const groupedResults = await reviewer.reviewPR(owner, repo, prNumber, prInfo, {
|
|
580
462
|
focus,
|
|
581
463
|
format,
|
|
582
464
|
config: config,
|
|
583
465
|
checks: checkIds,
|
|
584
466
|
parallelExecution: false,
|
|
585
467
|
});
|
|
586
|
-
await reviewer.postReviewComment(owner, repo, prNumber,
|
|
468
|
+
await reviewer.postReviewComment(owner, repo, prNumber, groupedResults, {
|
|
587
469
|
focus,
|
|
588
470
|
format,
|
|
589
471
|
});
|
|
590
|
-
|
|
472
|
+
// Calculate total check results from grouped results
|
|
473
|
+
const totalChecks = Object.values(groupedResults).flat().length;
|
|
474
|
+
(0, core_1.setOutput)('checks-executed', totalChecks.toString());
|
|
591
475
|
}
|
|
592
476
|
break;
|
|
593
477
|
}
|
|
594
478
|
}
|
|
595
|
-
async function
|
|
479
|
+
async function handlePullRequestWithConfig(octokit, owner, repo, inputs, config, checksToRun) {
|
|
596
480
|
const context = JSON.parse(process.env.GITHUB_CONTEXT || '{}');
|
|
597
481
|
const pullRequest = context.event?.pull_request;
|
|
598
482
|
const action = context.event?.action;
|
|
@@ -600,150 +484,62 @@ async function handlePullRequestEvent(octokit, owner, repo, inputs) {
|
|
|
600
484
|
console.log('No pull request found in context');
|
|
601
485
|
return;
|
|
602
486
|
}
|
|
603
|
-
|
|
604
|
-
const supportedActions = ['opened', 'synchronize', 'edited'];
|
|
605
|
-
if (!supportedActions.includes(action)) {
|
|
606
|
-
console.log(`Unsupported PR action: ${action}. Supported actions: ${supportedActions.join(', ')}`);
|
|
607
|
-
return;
|
|
608
|
-
}
|
|
609
|
-
console.log(`Auto-reviewing PR #${pullRequest.number} (action: ${action})`);
|
|
487
|
+
console.log(`Reviewing PR #${pullRequest.number} with checks: ${checksToRun.join(', ')}`);
|
|
610
488
|
const prNumber = pullRequest.number;
|
|
611
489
|
const analyzer = new pr_analyzer_1.PRAnalyzer(octokit);
|
|
612
490
|
const reviewer = new reviewer_1.PRReviewer(octokit);
|
|
613
|
-
|
|
614
|
-
// Generate comment ID for this PR to enable smart updating
|
|
491
|
+
// Generate comment ID for this PR
|
|
615
492
|
const commentId = `pr-review-${prNumber}`;
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
//
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
'## 🔄 Updated PR Analysis\n\nThis review has been updated to include the latest changes.\n\n';
|
|
626
|
-
}
|
|
627
|
-
else {
|
|
628
|
-
// Fallback to full analysis if no commit SHA available
|
|
629
|
-
prInfo = await analyzer.fetchPRDiff(owner, repo, prNumber);
|
|
630
|
-
reviewContext = '## 🔄 Updated PR Analysis\n\nAnalyzing all changes in this PR.\n\n';
|
|
631
|
-
}
|
|
632
|
-
}
|
|
633
|
-
else {
|
|
634
|
-
// For opened and edited events, do full PR analysis
|
|
635
|
-
prInfo = await analyzer.fetchPRDiff(owner, repo, prNumber);
|
|
636
|
-
if (action === 'opened') {
|
|
637
|
-
reviewContext =
|
|
638
|
-
'## 🚀 Welcome to Automated PR Review!\n\nThis PR has been automatically analyzed. Use `/help` to see available commands.\n\n';
|
|
639
|
-
}
|
|
640
|
-
else {
|
|
641
|
-
reviewContext =
|
|
642
|
-
'## ✏️ PR Analysis Updated\n\nThis review has been updated based on PR changes.\n\n';
|
|
643
|
-
}
|
|
644
|
-
}
|
|
645
|
-
// Load config for the review
|
|
646
|
-
const configManager = new config_1.ConfigManager();
|
|
647
|
-
let config;
|
|
648
|
-
try {
|
|
649
|
-
config = await configManager.loadConfig('.visor.yaml');
|
|
493
|
+
// Map the action to event type
|
|
494
|
+
const eventType = mapGitHubEventToTrigger('pull_request', action);
|
|
495
|
+
// Fetch PR diff
|
|
496
|
+
const prInfo = await analyzer.fetchPRDiff(owner, repo, prNumber, undefined, eventType);
|
|
497
|
+
if (prInfo.files.length === 0) {
|
|
498
|
+
console.log('⚠️ No files changed in this PR - skipping review');
|
|
499
|
+
(0, core_1.setOutput)('review-completed', 'true');
|
|
500
|
+
(0, core_1.setOutput)('issues-found', '0');
|
|
501
|
+
return;
|
|
650
502
|
}
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
group_by: 'check',
|
|
659
|
-
collapse: false,
|
|
660
|
-
},
|
|
661
|
-
},
|
|
662
|
-
checks: {
|
|
663
|
-
'auto-review': {
|
|
664
|
-
type: 'ai',
|
|
665
|
-
on: ['pr_opened', 'pr_updated'],
|
|
666
|
-
prompt: `Review this pull request comprehensively. Look for security issues, performance problems, code quality, bugs, and suggest improvements. Action: ${action}`,
|
|
667
|
-
},
|
|
668
|
-
},
|
|
669
|
-
};
|
|
503
|
+
// Filter checks based on conditions
|
|
504
|
+
const checksToExecute = await filterChecksToExecute(checksToRun, config, prInfo);
|
|
505
|
+
if (checksToExecute.length === 0) {
|
|
506
|
+
console.log('⚠️ No checks meet execution conditions');
|
|
507
|
+
(0, core_1.setOutput)('review-completed', 'true');
|
|
508
|
+
(0, core_1.setOutput)('issues-found', '0');
|
|
509
|
+
return;
|
|
670
510
|
}
|
|
671
|
-
|
|
511
|
+
console.log(`📋 Executing checks: ${checksToExecute.join(', ')}`);
|
|
512
|
+
// Create review options
|
|
672
513
|
const reviewOptions = {
|
|
673
514
|
debug: inputs?.debug === 'true',
|
|
674
515
|
config: config,
|
|
675
|
-
checks:
|
|
676
|
-
parallelExecution:
|
|
516
|
+
checks: checksToExecute,
|
|
517
|
+
parallelExecution: true,
|
|
677
518
|
};
|
|
678
|
-
// Create GitHub check runs
|
|
519
|
+
// Create GitHub check runs if enabled
|
|
679
520
|
let checkResults = null;
|
|
680
|
-
if (inputs && inputs['
|
|
681
|
-
checkResults = await createGitHubChecks(octokit, inputs, owner, repo, pullRequest.head?.sha || 'unknown',
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
console.log(`Provider: ${review.debug.provider}`);
|
|
699
|
-
console.log(`Model: ${review.debug.model}`);
|
|
700
|
-
console.log(`API Key Source: ${review.debug.apiKeySource}`);
|
|
701
|
-
console.log(`Processing Time: ${review.debug.processingTime}ms`);
|
|
702
|
-
console.log(`Prompt Length: ${review.debug.promptLength} characters`);
|
|
703
|
-
console.log(`Response Length: ${review.debug.responseLength} characters`);
|
|
704
|
-
console.log(`JSON Parse Success: ${review.debug.jsonParseSuccess ? '✅' : '❌'}`);
|
|
705
|
-
if (review.debug.errors && review.debug.errors.length > 0) {
|
|
706
|
-
console.log(`\n⚠️ Errors:`);
|
|
707
|
-
review.debug.errors.forEach(err => console.log(` - ${err}`));
|
|
708
|
-
}
|
|
709
|
-
console.log('\n--- AI PROMPT ---');
|
|
710
|
-
console.log(review.debug.prompt.substring(0, 500) + '...');
|
|
711
|
-
console.log('\n--- RAW RESPONSE ---');
|
|
712
|
-
console.log(review.debug.rawResponse.substring(0, 500) + '...');
|
|
713
|
-
console.log('========================================\n');
|
|
714
|
-
}
|
|
715
|
-
const reviewComment = await reviewer['formatReviewCommentWithVisorFormat'](review, reviewOptions);
|
|
716
|
-
const fullComment = reviewContext + reviewComment;
|
|
717
|
-
// Use smart comment updating - will update existing comment or create new one
|
|
718
|
-
try {
|
|
719
|
-
const comment = await commentManager.updateOrCreateComment(owner, repo, prNumber, fullComment, {
|
|
720
|
-
commentId,
|
|
721
|
-
triggeredBy: action,
|
|
722
|
-
allowConcurrentUpdates: true, // Allow updates even if comment was modified externally
|
|
723
|
-
commitSha: pullRequest.head?.sha,
|
|
724
|
-
});
|
|
725
|
-
console.log(`✅ ${action === 'opened' ? 'Created' : 'Updated'} PR review comment (ID: ${comment.id})`);
|
|
726
|
-
}
|
|
727
|
-
catch (error) {
|
|
728
|
-
console.error(`❌ Failed to ${action === 'opened' ? 'create' : 'update'} PR review comment:`, error);
|
|
729
|
-
// Fallback to creating a new comment without the smart updating
|
|
730
|
-
await octokit.rest.issues.createComment({
|
|
731
|
-
owner,
|
|
732
|
-
repo,
|
|
733
|
-
issue_number: prNumber,
|
|
734
|
-
body: fullComment,
|
|
735
|
-
});
|
|
736
|
-
console.log('✅ Created fallback PR review comment');
|
|
737
|
-
}
|
|
521
|
+
if (inputs && inputs['create-check'] !== 'false') {
|
|
522
|
+
checkResults = await createGitHubChecks(octokit, inputs, owner, repo, pullRequest.head?.sha || 'unknown', checksToExecute, config);
|
|
523
|
+
if (checkResults?.checkRunMap) {
|
|
524
|
+
await updateChecksInProgress(octokit, owner, repo, checkResults.checkRunMap);
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
// Perform the review
|
|
528
|
+
const groupedResults = await reviewer.reviewPR(owner, repo, prNumber, prInfo, reviewOptions);
|
|
529
|
+
// Complete GitHub check runs
|
|
530
|
+
if (checkResults?.checkRunMap) {
|
|
531
|
+
await completeGitHubChecks(octokit, owner, repo, checkResults.checkRunMap, groupedResults, config);
|
|
532
|
+
}
|
|
533
|
+
// Post review comment
|
|
534
|
+
await reviewer.postReviewComment(owner, repo, prNumber, groupedResults, {
|
|
535
|
+
commentId,
|
|
536
|
+
triggeredBy: action,
|
|
537
|
+
commitSha: pullRequest.head?.sha,
|
|
538
|
+
});
|
|
738
539
|
// Set outputs
|
|
739
|
-
(0, core_1.setOutput)('
|
|
740
|
-
(0, core_1.setOutput)('
|
|
540
|
+
(0, core_1.setOutput)('review-completed', 'true');
|
|
541
|
+
(0, core_1.setOutput)('checks-executed', checksToExecute.length.toString());
|
|
741
542
|
(0, core_1.setOutput)('pr-action', action);
|
|
742
|
-
(0, core_1.setOutput)('incremental-analysis', action === 'synchronize' ? 'true' : 'false');
|
|
743
|
-
// Set GitHub check run outputs
|
|
744
|
-
(0, core_1.setOutput)('checks-api-available', checkResults?.checksApiAvailable.toString() || 'false');
|
|
745
|
-
(0, core_1.setOutput)('check-runs-created', checkResults?.checkRunsCreated.toString() || '0');
|
|
746
|
-
(0, core_1.setOutput)('check-runs-urls', checkResults?.checkRunUrls.join(',') || '');
|
|
747
543
|
}
|
|
748
544
|
async function handleRepoInfo(octokit, owner, repo) {
|
|
749
545
|
const { data: repoData } = await octokit.rest.repos.get({
|
|
@@ -757,6 +553,57 @@ async function handleRepoInfo(octokit, owner, repo) {
|
|
|
757
553
|
console.log(`Description: ${repoData.description || 'No description'}`);
|
|
758
554
|
console.log(`Stars: ${repoData.stargazers_count}`);
|
|
759
555
|
}
|
|
556
|
+
/**
|
|
557
|
+
* Filter checks based on their if conditions and API requirements
|
|
558
|
+
*/
|
|
559
|
+
async function filterChecksToExecute(checksToRun, config, prInfo) {
|
|
560
|
+
const filteredChecks = [];
|
|
561
|
+
// Create a basic context for condition evaluation
|
|
562
|
+
const context = {
|
|
563
|
+
files: prInfo?.files || [],
|
|
564
|
+
filesChanged: prInfo?.files.map(f => f.filename) || [],
|
|
565
|
+
event: 'pull_request',
|
|
566
|
+
environment: process.env,
|
|
567
|
+
metadata: {
|
|
568
|
+
filesCount: prInfo?.files.length || 0,
|
|
569
|
+
additions: prInfo?.totalAdditions || 0,
|
|
570
|
+
deletions: prInfo?.totalDeletions || 0,
|
|
571
|
+
},
|
|
572
|
+
};
|
|
573
|
+
for (const checkName of checksToRun) {
|
|
574
|
+
const checkConfig = config?.checks?.[checkName];
|
|
575
|
+
if (!checkConfig) {
|
|
576
|
+
// If no config, include the check by default
|
|
577
|
+
filteredChecks.push(checkName);
|
|
578
|
+
continue;
|
|
579
|
+
}
|
|
580
|
+
// Check if the check has an if condition
|
|
581
|
+
if (checkConfig.if) {
|
|
582
|
+
try {
|
|
583
|
+
// Import the failure condition evaluator
|
|
584
|
+
const { FailureConditionEvaluator } = await Promise.resolve().then(() => __importStar(require('./failure-condition-evaluator')));
|
|
585
|
+
const evaluator = new FailureConditionEvaluator();
|
|
586
|
+
const shouldRun = await evaluator.evaluateIfCondition(checkName, checkConfig.if, context);
|
|
587
|
+
if (shouldRun) {
|
|
588
|
+
filteredChecks.push(checkName);
|
|
589
|
+
}
|
|
590
|
+
else {
|
|
591
|
+
console.log(`⚠️ Skipping check '${checkName}' - if condition not met`);
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
catch (error) {
|
|
595
|
+
console.warn(`Warning: Could not evaluate if condition for ${checkName}:`, error);
|
|
596
|
+
// Include the check if we can't evaluate the condition
|
|
597
|
+
filteredChecks.push(checkName);
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
else {
|
|
601
|
+
// No condition, include the check
|
|
602
|
+
filteredChecks.push(checkName);
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
return filteredChecks;
|
|
606
|
+
}
|
|
760
607
|
/**
|
|
761
608
|
* Create GitHub check runs for individual checks if enabled
|
|
762
609
|
*/
|
|
@@ -887,7 +734,7 @@ async function updateChecksInProgress(octokit, owner, repo, checkRunMap) {
|
|
|
887
734
|
/**
|
|
888
735
|
* Complete GitHub check runs with results
|
|
889
736
|
*/
|
|
890
|
-
async function completeGitHubChecks(octokit, owner, repo, checkRunMap,
|
|
737
|
+
async function completeGitHubChecks(octokit, owner, repo, checkRunMap, groupedResults, config) {
|
|
891
738
|
if (!checkRunMap)
|
|
892
739
|
return;
|
|
893
740
|
const checkService = new github_check_service_1.GitHubCheckService(octokit);
|
|
@@ -895,40 +742,113 @@ async function completeGitHubChecks(octokit, owner, repo, checkRunMap, reviewSum
|
|
|
895
742
|
console.log(`🏁 Completing ${checkRunMap.size} GitHub check runs...`);
|
|
896
743
|
if (perCheckMode && !checkRunMap.has('combined')) {
|
|
897
744
|
// Per-check mode: complete individual check runs
|
|
898
|
-
await completeIndividualChecks(checkService, owner, repo, checkRunMap,
|
|
745
|
+
await completeIndividualChecks(checkService, owner, repo, checkRunMap, groupedResults, config);
|
|
899
746
|
}
|
|
900
747
|
else {
|
|
901
748
|
// Combined mode: complete single check run with all results
|
|
902
|
-
await completeCombinedCheck(checkService, owner, repo, checkRunMap,
|
|
749
|
+
await completeCombinedCheck(checkService, owner, repo, checkRunMap, groupedResults, config);
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
/**
|
|
753
|
+
* Extract ReviewIssue[] from GroupedCheckResults content by parsing the rendered text
|
|
754
|
+
* This function parses the structured content created by CheckExecutionEngine.convertReviewSummaryToGroupedResults()
|
|
755
|
+
*/
|
|
756
|
+
function extractIssuesFromGroupedResults(groupedResults) {
|
|
757
|
+
const issues = [];
|
|
758
|
+
for (const [groupName, checkResults] of Object.entries(groupedResults)) {
|
|
759
|
+
for (const checkResult of checkResults) {
|
|
760
|
+
const { checkName, content } = checkResult;
|
|
761
|
+
// First, check if structured issues are available
|
|
762
|
+
if (checkResult.issues && checkResult.issues.length > 0) {
|
|
763
|
+
// Use structured issues directly - they're already properly formatted
|
|
764
|
+
issues.push(...checkResult.issues);
|
|
765
|
+
continue;
|
|
766
|
+
}
|
|
767
|
+
// Fall back to parsing issues from content (legacy support)
|
|
768
|
+
// Parse issues from content - look for lines like:
|
|
769
|
+
// - **CRITICAL**: message (file:line)
|
|
770
|
+
// - **ERROR**: message (file:line)
|
|
771
|
+
// - **WARNING**: message (file:line)
|
|
772
|
+
// - **INFO**: message (file:line)
|
|
773
|
+
const issueRegex = /^- \*\*([A-Z]+)\*\*: (.+?) \(([^:]+):(\d+)\)$/gm;
|
|
774
|
+
let match;
|
|
775
|
+
while ((match = issueRegex.exec(content)) !== null) {
|
|
776
|
+
const [, severityUpper, message, file, lineStr] = match;
|
|
777
|
+
const severity = severityUpper.toLowerCase();
|
|
778
|
+
const line = parseInt(lineStr, 10);
|
|
779
|
+
// Create ReviewIssue with proper format for GitHub annotations
|
|
780
|
+
const issue = {
|
|
781
|
+
file,
|
|
782
|
+
line,
|
|
783
|
+
ruleId: `${checkName}/content-parsed`,
|
|
784
|
+
message: message.trim(),
|
|
785
|
+
severity,
|
|
786
|
+
category: 'logic', // Default category since we can't parse this from content
|
|
787
|
+
group: groupName,
|
|
788
|
+
timestamp: Date.now(),
|
|
789
|
+
};
|
|
790
|
+
issues.push(issue);
|
|
791
|
+
}
|
|
792
|
+
}
|
|
903
793
|
}
|
|
794
|
+
return issues;
|
|
904
795
|
}
|
|
905
796
|
/**
|
|
906
797
|
* Complete individual GitHub check runs
|
|
907
798
|
*/
|
|
908
|
-
async function completeIndividualChecks(checkService, owner, repo, checkRunMap,
|
|
909
|
-
//
|
|
799
|
+
async function completeIndividualChecks(checkService, owner, repo, checkRunMap, groupedResults, config) {
|
|
800
|
+
// Create failure condition evaluator
|
|
801
|
+
const { FailureConditionEvaluator } = await Promise.resolve().then(() => __importStar(require('./failure-condition-evaluator')));
|
|
802
|
+
const failureEvaluator = new FailureConditionEvaluator();
|
|
803
|
+
// Extract all issues once and group by check name for O(N) complexity
|
|
804
|
+
const allIssues = extractIssuesFromGroupedResults(groupedResults);
|
|
910
805
|
const issuesByCheck = new Map();
|
|
911
806
|
// Initialize empty arrays for all checks
|
|
912
807
|
for (const checkName of checkRunMap.keys()) {
|
|
913
808
|
issuesByCheck.set(checkName, []);
|
|
914
809
|
}
|
|
915
|
-
// Group issues by
|
|
916
|
-
for (const issue of
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
issuesByCheck.get(checkName).push(issue);
|
|
921
|
-
}
|
|
810
|
+
// Group issues by check name
|
|
811
|
+
for (const issue of allIssues) {
|
|
812
|
+
const checkName = issue.ruleId?.split('/')[0];
|
|
813
|
+
if (checkName && issuesByCheck.has(checkName)) {
|
|
814
|
+
issuesByCheck.get(checkName).push(issue);
|
|
922
815
|
}
|
|
923
816
|
}
|
|
924
817
|
for (const [checkName, checkRun] of checkRunMap) {
|
|
925
818
|
try {
|
|
819
|
+
// Get pre-grouped issues for this check - O(1) lookup
|
|
926
820
|
const checkIssues = issuesByCheck.get(checkName) || [];
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
821
|
+
// Evaluate failure conditions based on fail_if configuration
|
|
822
|
+
const failureResults = [];
|
|
823
|
+
// Get global and check-specific fail_if conditions
|
|
824
|
+
const globalFailIf = config?.fail_if;
|
|
825
|
+
const checkFailIf = config?.checks?.[checkName]?.fail_if;
|
|
826
|
+
// Create a ReviewSummary for this check's issues
|
|
827
|
+
const checkReviewSummary = {
|
|
828
|
+
issues: checkIssues,
|
|
829
|
+
suggestions: [],
|
|
830
|
+
};
|
|
831
|
+
// Determine which fail_if to use: check-specific overrides global
|
|
832
|
+
const effectiveFailIf = checkFailIf || globalFailIf;
|
|
833
|
+
if (effectiveFailIf) {
|
|
834
|
+
const failed = await failureEvaluator.evaluateSimpleCondition(checkName, config?.checks?.[checkName]?.schema || 'plain', config?.checks?.[checkName]?.group || 'default', checkReviewSummary, effectiveFailIf);
|
|
835
|
+
if (failed) {
|
|
836
|
+
const isCheckSpecific = checkFailIf !== undefined;
|
|
837
|
+
failureResults.push({
|
|
838
|
+
conditionName: isCheckSpecific ? `${checkName}_fail_if` : 'global_fail_if',
|
|
839
|
+
expression: effectiveFailIf,
|
|
840
|
+
failed: true,
|
|
841
|
+
severity: 'error',
|
|
842
|
+
message: isCheckSpecific
|
|
843
|
+
? `Check ${checkName} failure condition met`
|
|
844
|
+
: 'Global failure condition met',
|
|
845
|
+
haltExecution: false,
|
|
846
|
+
});
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
await checkService.completeCheckRun(owner, repo, checkRun.id, checkName, failureResults, checkIssues // Pass extracted issues for GitHub annotations
|
|
850
|
+
);
|
|
851
|
+
console.log(`✅ Completed ${checkName} check with ${checkIssues.length} issues, ${failureResults.length} failure conditions evaluated`);
|
|
932
852
|
}
|
|
933
853
|
catch (error) {
|
|
934
854
|
console.error(`❌ Failed to complete ${checkName} check:`, error);
|
|
@@ -939,133 +859,46 @@ async function completeIndividualChecks(checkService, owner, repo, checkRunMap,
|
|
|
939
859
|
/**
|
|
940
860
|
* Complete combined GitHub check run
|
|
941
861
|
*/
|
|
942
|
-
async function completeCombinedCheck(checkService, owner, repo, checkRunMap,
|
|
862
|
+
async function completeCombinedCheck(checkService, owner, repo, checkRunMap, groupedResults, config) {
|
|
943
863
|
const combinedCheckRun = checkRunMap.get('combined');
|
|
944
864
|
if (!combinedCheckRun)
|
|
945
865
|
return;
|
|
866
|
+
// Create failure condition evaluator
|
|
867
|
+
const { FailureConditionEvaluator } = await Promise.resolve().then(() => __importStar(require('./failure-condition-evaluator')));
|
|
868
|
+
const failureEvaluator = new FailureConditionEvaluator();
|
|
946
869
|
try {
|
|
947
|
-
//
|
|
948
|
-
const allIssues =
|
|
949
|
-
// Evaluate
|
|
950
|
-
const failureResults =
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
*/
|
|
962
|
-
async function evaluateCheckFailureConditions(config, checkConfig, checkName, checkIssues) {
|
|
963
|
-
const failureResults = [];
|
|
964
|
-
const criticalIssues = checkIssues.filter(issue => issue.severity === 'critical').length;
|
|
965
|
-
const errorIssues = checkIssues.filter(issue => issue.severity === 'error').length;
|
|
966
|
-
// Check global fail_if condition
|
|
967
|
-
if (config.fail_if) {
|
|
968
|
-
try {
|
|
969
|
-
const evaluator = new failure_condition_evaluator_1.FailureConditionEvaluator();
|
|
970
|
-
const reviewSummary = {
|
|
971
|
-
issues: [],
|
|
972
|
-
suggestions: [],
|
|
973
|
-
metadata: {
|
|
974
|
-
totalIssues: checkIssues.length,
|
|
975
|
-
criticalIssues,
|
|
976
|
-
errorIssues,
|
|
977
|
-
warningIssues: 0,
|
|
978
|
-
infoIssues: 0,
|
|
979
|
-
},
|
|
980
|
-
};
|
|
981
|
-
const shouldFail = await evaluator.evaluateSimpleCondition(checkName, 'legacy', 'legacy', reviewSummary, config.fail_if);
|
|
982
|
-
if (shouldFail) {
|
|
870
|
+
// Extract all issues from the grouped results for the combined check
|
|
871
|
+
const allIssues = extractIssuesFromGroupedResults(groupedResults);
|
|
872
|
+
// Evaluate failure conditions for combined check
|
|
873
|
+
const failureResults = [];
|
|
874
|
+
// Create a combined ReviewSummary with all issues
|
|
875
|
+
const combinedReviewSummary = {
|
|
876
|
+
issues: allIssues,
|
|
877
|
+
suggestions: [],
|
|
878
|
+
};
|
|
879
|
+
// Evaluate global fail_if for the combined check
|
|
880
|
+
const globalFailIf = config?.fail_if;
|
|
881
|
+
if (globalFailIf) {
|
|
882
|
+
const failed = await failureEvaluator.evaluateSimpleCondition('combined', 'plain', 'combined', combinedReviewSummary, globalFailIf);
|
|
883
|
+
if (failed) {
|
|
983
884
|
failureResults.push({
|
|
984
885
|
conditionName: 'global_fail_if',
|
|
886
|
+
expression: globalFailIf,
|
|
985
887
|
failed: true,
|
|
986
888
|
severity: 'error',
|
|
987
|
-
expression: config.fail_if,
|
|
988
889
|
message: 'Global failure condition met',
|
|
989
890
|
haltExecution: false,
|
|
990
891
|
});
|
|
991
892
|
}
|
|
992
893
|
}
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
}
|
|
894
|
+
await checkService.completeCheckRun(owner, repo, combinedCheckRun.id, 'Code Review', failureResults, allIssues // Pass all extracted issues for GitHub annotations
|
|
895
|
+
);
|
|
896
|
+
console.log(`✅ Completed combined check with ${allIssues.length} issues, ${failureResults.length} failure conditions evaluated`);
|
|
996
897
|
}
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
const evaluator = new failure_condition_evaluator_1.FailureConditionEvaluator();
|
|
1001
|
-
const reviewSummary = {
|
|
1002
|
-
issues: [],
|
|
1003
|
-
suggestions: [],
|
|
1004
|
-
metadata: {
|
|
1005
|
-
totalIssues: checkIssues.length,
|
|
1006
|
-
criticalIssues,
|
|
1007
|
-
errorIssues,
|
|
1008
|
-
warningIssues: 0,
|
|
1009
|
-
infoIssues: 0,
|
|
1010
|
-
},
|
|
1011
|
-
};
|
|
1012
|
-
const shouldFail = await evaluator.evaluateSimpleCondition(checkName, 'legacy', 'legacy', reviewSummary, checkConfig.fail_if);
|
|
1013
|
-
if (shouldFail) {
|
|
1014
|
-
failureResults.push({
|
|
1015
|
-
conditionName: `${checkName}_fail_if`,
|
|
1016
|
-
failed: true,
|
|
1017
|
-
severity: 'error',
|
|
1018
|
-
expression: checkConfig.fail_if,
|
|
1019
|
-
message: `Check ${checkName} failure condition met`,
|
|
1020
|
-
haltExecution: false,
|
|
1021
|
-
});
|
|
1022
|
-
}
|
|
1023
|
-
}
|
|
1024
|
-
catch (error) {
|
|
1025
|
-
console.error('❌ Failed to evaluate check-specific fail_if condition:', checkConfig.fail_if, error);
|
|
1026
|
-
}
|
|
1027
|
-
}
|
|
1028
|
-
return failureResults;
|
|
1029
|
-
}
|
|
1030
|
-
/**
|
|
1031
|
-
* Evaluate global failure conditions for combined check
|
|
1032
|
-
*/
|
|
1033
|
-
async function evaluateGlobalFailureConditions(config, allIssues) {
|
|
1034
|
-
const failureResults = [];
|
|
1035
|
-
const criticalIssues = allIssues.filter(issue => issue.severity === 'critical').length;
|
|
1036
|
-
const errorIssues = allIssues.filter(issue => issue.severity === 'error').length;
|
|
1037
|
-
// Check global fail_if condition
|
|
1038
|
-
if (config.fail_if) {
|
|
1039
|
-
try {
|
|
1040
|
-
const evaluator = new failure_condition_evaluator_1.FailureConditionEvaluator();
|
|
1041
|
-
const reviewSummary = {
|
|
1042
|
-
issues: [],
|
|
1043
|
-
suggestions: [],
|
|
1044
|
-
metadata: {
|
|
1045
|
-
totalIssues: allIssues.length,
|
|
1046
|
-
criticalIssues,
|
|
1047
|
-
errorIssues,
|
|
1048
|
-
warningIssues: 0,
|
|
1049
|
-
infoIssues: 0,
|
|
1050
|
-
},
|
|
1051
|
-
};
|
|
1052
|
-
const shouldFail = await evaluator.evaluateSimpleCondition('combined', 'legacy', 'legacy', reviewSummary, config.fail_if);
|
|
1053
|
-
if (shouldFail) {
|
|
1054
|
-
failureResults.push({
|
|
1055
|
-
conditionName: 'global_fail_if',
|
|
1056
|
-
failed: true,
|
|
1057
|
-
severity: 'error',
|
|
1058
|
-
expression: config.fail_if,
|
|
1059
|
-
message: 'Global failure condition met',
|
|
1060
|
-
haltExecution: false,
|
|
1061
|
-
});
|
|
1062
|
-
}
|
|
1063
|
-
}
|
|
1064
|
-
catch (error) {
|
|
1065
|
-
console.error('❌ Failed to evaluate global fail_if condition:', config.fail_if, error);
|
|
1066
|
-
}
|
|
898
|
+
catch (error) {
|
|
899
|
+
console.error(`❌ Failed to complete combined check:`, error);
|
|
900
|
+
await markCheckAsFailed(checkService, owner, repo, combinedCheckRun.id, 'Code Review', error);
|
|
1067
901
|
}
|
|
1068
|
-
return failureResults;
|
|
1069
902
|
}
|
|
1070
903
|
/**
|
|
1071
904
|
* Mark a check as failed due to execution error
|
|
@@ -1078,198 +911,6 @@ async function markCheckAsFailed(checkService, owner, repo, checkRunId, checkNam
|
|
|
1078
911
|
console.error(`❌ Failed to mark ${checkName} check as failed:`, finalError);
|
|
1079
912
|
}
|
|
1080
913
|
}
|
|
1081
|
-
/**
|
|
1082
|
-
* Handle PR review using Visor config but with proper GitHub API PR diff analysis
|
|
1083
|
-
*/
|
|
1084
|
-
async function handlePullRequestVisorMode(inputs, _context, octokit, _authType) {
|
|
1085
|
-
const owner = inputs.owner || process.env.GITHUB_REPOSITORY_OWNER;
|
|
1086
|
-
const repo = inputs.repo || process.env.GITHUB_REPOSITORY?.split('/')[1];
|
|
1087
|
-
if (!owner || !repo) {
|
|
1088
|
-
console.error('❌ Missing required GitHub parameters for PR analysis');
|
|
1089
|
-
(0, core_1.setFailed)('Missing required GitHub parameters');
|
|
1090
|
-
return;
|
|
1091
|
-
}
|
|
1092
|
-
// Use the provided authenticated Octokit instance
|
|
1093
|
-
const prDetector = new pr_detector_1.PRDetector(octokit, inputs.debug === 'true');
|
|
1094
|
-
// Convert GitHub context to our format
|
|
1095
|
-
const eventContext = {
|
|
1096
|
-
event_name: process.env.GITHUB_EVENT_NAME || 'unknown',
|
|
1097
|
-
repository: {
|
|
1098
|
-
owner: { login: owner },
|
|
1099
|
-
name: repo,
|
|
1100
|
-
},
|
|
1101
|
-
event: process.env.GITHUB_CONTEXT ? JSON.parse(process.env.GITHUB_CONTEXT).event : undefined,
|
|
1102
|
-
payload: process.env.GITHUB_CONTEXT ? JSON.parse(process.env.GITHUB_CONTEXT) : {},
|
|
1103
|
-
};
|
|
1104
|
-
// Use robust PR detection
|
|
1105
|
-
const prResult = await prDetector.detectPRNumber(eventContext, owner, repo);
|
|
1106
|
-
const action = eventContext.event?.action;
|
|
1107
|
-
if (!prResult.prNumber) {
|
|
1108
|
-
console.error(`❌ No PR found using any detection strategy: ${prResult.details || 'Unknown reason'}`);
|
|
1109
|
-
if (inputs.debug === 'true') {
|
|
1110
|
-
console.error('Available detection strategies:');
|
|
1111
|
-
prDetector.getDetectionStrategies().forEach(strategy => console.error(` ${strategy}`));
|
|
1112
|
-
}
|
|
1113
|
-
(0, core_1.setFailed)('No PR number found');
|
|
1114
|
-
return;
|
|
1115
|
-
}
|
|
1116
|
-
const prNumber = prResult.prNumber;
|
|
1117
|
-
console.log(`✅ Found PR #${prNumber} using ${prResult.source} (confidence: ${prResult.confidence})`);
|
|
1118
|
-
if (prResult.details) {
|
|
1119
|
-
console.log(` Details: ${prResult.details}`);
|
|
1120
|
-
}
|
|
1121
|
-
console.log(`🔍 Analyzing PR #${prNumber} using Visor config (action: ${action})`);
|
|
1122
|
-
try {
|
|
1123
|
-
// Use the existing PR analysis infrastructure but with Visor config
|
|
1124
|
-
const analyzer = new pr_analyzer_1.PRAnalyzer(octokit);
|
|
1125
|
-
const reviewer = new reviewer_1.PRReviewer(octokit);
|
|
1126
|
-
// Load Visor config
|
|
1127
|
-
const configManager = new config_1.ConfigManager();
|
|
1128
|
-
let config;
|
|
1129
|
-
const configPath = inputs['config-path'];
|
|
1130
|
-
if (configPath) {
|
|
1131
|
-
try {
|
|
1132
|
-
config = await configManager.loadConfig(configPath);
|
|
1133
|
-
console.log(`📋 Loaded Visor config from: ${configPath}`);
|
|
1134
|
-
}
|
|
1135
|
-
catch (error) {
|
|
1136
|
-
console.error(`⚠️ Could not load config from ${configPath}:`, error);
|
|
1137
|
-
config = await configManager.findAndLoadConfig();
|
|
1138
|
-
}
|
|
1139
|
-
}
|
|
1140
|
-
else {
|
|
1141
|
-
// Try to find and load config from default locations (.visor.yaml)
|
|
1142
|
-
config = await configManager.findAndLoadConfig();
|
|
1143
|
-
const hasCustomConfig = config.checks && Object.keys(config.checks).length > 0;
|
|
1144
|
-
if (hasCustomConfig) {
|
|
1145
|
-
console.log(`📋 Loaded Visor config from default location (.visor.yaml)`);
|
|
1146
|
-
}
|
|
1147
|
-
else {
|
|
1148
|
-
console.log(`📋 Using default Visor configuration`);
|
|
1149
|
-
}
|
|
1150
|
-
}
|
|
1151
|
-
// Extract checks from config
|
|
1152
|
-
const configChecks = Object.keys(config.checks || {});
|
|
1153
|
-
const checksToRun = configChecks.length > 0 ? configChecks : ['security', 'performance', 'style', 'architecture'];
|
|
1154
|
-
console.log(`🔧 Running checks: ${checksToRun.join(', ')}`);
|
|
1155
|
-
// Fetch PR diff using GitHub API
|
|
1156
|
-
const prInfo = await analyzer.fetchPRDiff(owner, repo, prNumber);
|
|
1157
|
-
console.log(`📄 Found ${prInfo.files.length} changed files`);
|
|
1158
|
-
if (prInfo.files.length === 0) {
|
|
1159
|
-
console.log('⚠️ No files changed in this PR - skipping review');
|
|
1160
|
-
// Set basic outputs
|
|
1161
|
-
(0, core_1.setOutput)('auto-review-completed', 'true');
|
|
1162
|
-
(0, core_1.setOutput)('issues-found', '0');
|
|
1163
|
-
(0, core_1.setOutput)('pr-action', action || 'unknown');
|
|
1164
|
-
(0, core_1.setOutput)('incremental-analysis', action === 'synchronize' ? 'true' : 'false');
|
|
1165
|
-
return;
|
|
1166
|
-
}
|
|
1167
|
-
// Create a custom review options with Visor config
|
|
1168
|
-
const reviewOptions = {
|
|
1169
|
-
debug: inputs.debug === 'true',
|
|
1170
|
-
config: config,
|
|
1171
|
-
checks: checksToRun,
|
|
1172
|
-
parallelExecution: true,
|
|
1173
|
-
};
|
|
1174
|
-
// Fetch PR info to get commit SHA for metadata
|
|
1175
|
-
const { data: pullRequest } = await octokit.rest.pulls.get({
|
|
1176
|
-
owner,
|
|
1177
|
-
repo,
|
|
1178
|
-
pull_number: prNumber,
|
|
1179
|
-
});
|
|
1180
|
-
// Create GitHub check runs for each configured check
|
|
1181
|
-
const checkResults = await createGitHubChecks(octokit, inputs, owner, repo, pullRequest.head.sha, checksToRun, config);
|
|
1182
|
-
// Update checks to in-progress status
|
|
1183
|
-
await updateChecksInProgress(octokit, owner, repo, checkResults.checkRunMap);
|
|
1184
|
-
// Perform the review
|
|
1185
|
-
console.log('🤖 Starting parallel AI review with Visor config...');
|
|
1186
|
-
const review = await reviewer.reviewPR(owner, repo, prNumber, prInfo, reviewOptions);
|
|
1187
|
-
// Update the review summary to show correct checks executed
|
|
1188
|
-
if (review.debug) {
|
|
1189
|
-
review.debug.checksExecuted = checksToRun;
|
|
1190
|
-
review.debug.parallelExecution = true;
|
|
1191
|
-
}
|
|
1192
|
-
// Complete GitHub check runs with results
|
|
1193
|
-
if (checkResults) {
|
|
1194
|
-
await completeGitHubChecks(octokit, owner, repo, checkResults.checkRunMap, review, config);
|
|
1195
|
-
}
|
|
1196
|
-
// Post comment using group-based comment separation
|
|
1197
|
-
const commentId = `visor-config-review-${prNumber}`;
|
|
1198
|
-
await reviewer.postReviewComment(owner, repo, prNumber, review, {
|
|
1199
|
-
...reviewOptions,
|
|
1200
|
-
commentId,
|
|
1201
|
-
triggeredBy: `visor-config-${action}`,
|
|
1202
|
-
commitSha: pullRequest.head?.sha,
|
|
1203
|
-
});
|
|
1204
|
-
console.log('✅ Posted Visor config-based review comment');
|
|
1205
|
-
// Check for API errors in the review issues
|
|
1206
|
-
const apiErrors = review.issues.filter(issue => issue.file === 'system' &&
|
|
1207
|
-
issue.severity === 'critical' &&
|
|
1208
|
-
(issue.message.includes('API rate limit') ||
|
|
1209
|
-
issue.message.includes('403') ||
|
|
1210
|
-
issue.message.includes('401') ||
|
|
1211
|
-
issue.message.includes('authentication') ||
|
|
1212
|
-
issue.message.includes('API key')));
|
|
1213
|
-
if (apiErrors.length > 0) {
|
|
1214
|
-
console.error('🚨 Critical API errors detected in review:');
|
|
1215
|
-
apiErrors.forEach(error => {
|
|
1216
|
-
console.error(` - ${error.message}`);
|
|
1217
|
-
});
|
|
1218
|
-
// Check if we should fail on API errors
|
|
1219
|
-
const failOnApiError = inputs['fail-on-api-error'] === 'true';
|
|
1220
|
-
if (failOnApiError) {
|
|
1221
|
-
(0, core_1.setFailed)(`Critical API errors detected: ${apiErrors.length} authentication/rate limit issues found. Please check your API credentials.`);
|
|
1222
|
-
return;
|
|
1223
|
-
}
|
|
1224
|
-
}
|
|
1225
|
-
// Set outputs
|
|
1226
|
-
(0, core_1.setOutput)('auto-review-completed', 'true');
|
|
1227
|
-
(0, core_1.setOutput)('issues-found', (0, reviewer_1.calculateTotalIssues)(review.issues).toString());
|
|
1228
|
-
(0, core_1.setOutput)('pr-action', action || 'unknown');
|
|
1229
|
-
(0, core_1.setOutput)('incremental-analysis', action === 'synchronize' ? 'true' : 'false');
|
|
1230
|
-
(0, core_1.setOutput)('visor-config-used', 'true');
|
|
1231
|
-
(0, core_1.setOutput)('checks-executed', checksToRun.join(','));
|
|
1232
|
-
(0, core_1.setOutput)('api-errors-found', apiErrors.length.toString());
|
|
1233
|
-
// Set GitHub check run outputs
|
|
1234
|
-
(0, core_1.setOutput)('checks-api-available', checkResults.checksApiAvailable.toString());
|
|
1235
|
-
(0, core_1.setOutput)('check-runs-created', checkResults.checkRunsCreated.toString());
|
|
1236
|
-
(0, core_1.setOutput)('check-runs-urls', checkResults.checkRunUrls.join(','));
|
|
1237
|
-
}
|
|
1238
|
-
catch (error) {
|
|
1239
|
-
console.error('❌ Error in Visor PR analysis:', error);
|
|
1240
|
-
(0, core_1.setFailed)(error instanceof Error ? error.message : 'Visor PR analysis failed');
|
|
1241
|
-
}
|
|
1242
|
-
}
|
|
1243
|
-
/**
|
|
1244
|
-
* Detect if we're in a PR context for any GitHub event type
|
|
1245
|
-
*/
|
|
1246
|
-
async function detectPRContext(inputs, context, octokit) {
|
|
1247
|
-
try {
|
|
1248
|
-
const owner = inputs.owner || process.env.GITHUB_REPOSITORY_OWNER;
|
|
1249
|
-
const repo = inputs.repo || process.env.GITHUB_REPOSITORY?.split('/')[1];
|
|
1250
|
-
if (!owner || !repo) {
|
|
1251
|
-
return false;
|
|
1252
|
-
}
|
|
1253
|
-
// Use the provided authenticated Octokit instance
|
|
1254
|
-
const prDetector = new pr_detector_1.PRDetector(octokit, inputs.debug === 'true');
|
|
1255
|
-
// Convert GitHub context to our format
|
|
1256
|
-
const eventContext = {
|
|
1257
|
-
event_name: context.event_name,
|
|
1258
|
-
repository: {
|
|
1259
|
-
owner: { login: owner },
|
|
1260
|
-
name: repo,
|
|
1261
|
-
},
|
|
1262
|
-
event: context.event,
|
|
1263
|
-
payload: context.payload || {},
|
|
1264
|
-
};
|
|
1265
|
-
const prResult = await prDetector.detectPRNumber(eventContext, owner, repo);
|
|
1266
|
-
return prResult.prNumber !== null;
|
|
1267
|
-
}
|
|
1268
|
-
catch (error) {
|
|
1269
|
-
console.error('Error detecting PR context:', error);
|
|
1270
|
-
return false;
|
|
1271
|
-
}
|
|
1272
|
-
}
|
|
1273
914
|
if (require.main === module) {
|
|
1274
915
|
run();
|
|
1275
916
|
}
|