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