@probelabs/visor 0.1.25 → 0.1.29

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/dist/index.js CHANGED
@@ -1,3 +1,4 @@
1
+ #!/usr/bin/env node
1
2
  /******/ (() => { // webpackBootstrap
2
3
  /******/ var __webpack_modules__ = ({
3
4
 
@@ -80653,408 +80654,6 @@ exports.fromPromise = function (fn) {
80653
80654
  }
80654
80655
 
80655
80656
 
80656
- /***/ }),
80657
-
80658
- /***/ 42058:
80659
- /***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {
80660
-
80661
- "use strict";
80662
-
80663
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
80664
- if (k2 === undefined) k2 = k;
80665
- var desc = Object.getOwnPropertyDescriptor(m, k);
80666
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
80667
- desc = { enumerable: true, get: function() { return m[k]; } };
80668
- }
80669
- Object.defineProperty(o, k2, desc);
80670
- }) : (function(o, m, k, k2) {
80671
- if (k2 === undefined) k2 = k;
80672
- o[k2] = m[k];
80673
- }));
80674
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
80675
- Object.defineProperty(o, "default", { enumerable: true, value: v });
80676
- }) : function(o, v) {
80677
- o["default"] = v;
80678
- });
80679
- var __importStar = (this && this.__importStar) || (function () {
80680
- var ownKeys = function(o) {
80681
- ownKeys = Object.getOwnPropertyNames || function (o) {
80682
- var ar = [];
80683
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
80684
- return ar;
80685
- };
80686
- return ownKeys(o);
80687
- };
80688
- return function (mod) {
80689
- if (mod && mod.__esModule) return mod;
80690
- var result = {};
80691
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
80692
- __setModuleDefault(result, mod);
80693
- return result;
80694
- };
80695
- })();
80696
- Object.defineProperty(exports, "__esModule", ({ value: true }));
80697
- exports.ActionCliBridge = void 0;
80698
- const child_process_1 = __nccwpck_require__(35317);
80699
- const fs_1 = __nccwpck_require__(79896);
80700
- const path = __importStar(__nccwpck_require__(16928));
80701
- /**
80702
- * Bridge between GitHub Action and Visor CLI
80703
- */
80704
- class ActionCliBridge {
80705
- githubToken;
80706
- context;
80707
- constructor(githubToken, context) {
80708
- this.githubToken = githubToken;
80709
- this.context = context;
80710
- }
80711
- /**
80712
- * Determine if Visor CLI should be used based on inputs
80713
- */
80714
- shouldUseVisor(inputs) {
80715
- return !!(inputs['config-path'] ||
80716
- inputs['visor-config-path'] ||
80717
- inputs.checks ||
80718
- inputs['visor-checks']);
80719
- }
80720
- /**
80721
- * Parse GitHub Action inputs to CLI arguments
80722
- */
80723
- parseGitHubInputsToCliArgs(inputs) {
80724
- const args = [];
80725
- // Add config path if specified (prefer new input name over legacy)
80726
- const configPath = inputs['config-path'] || inputs['visor-config-path'];
80727
- if (configPath && configPath.trim() !== '') {
80728
- args.push('--config', configPath);
80729
- }
80730
- // Add checks if specified (prefer new input name over legacy)
80731
- const checksInput = inputs.checks || inputs['visor-checks'];
80732
- if (checksInput) {
80733
- const checks = checksInput
80734
- .split(',')
80735
- .map(check => check.trim())
80736
- .filter(check => this.isValidCheck(check));
80737
- // CRITICAL FIX: When "all" is specified, don't add any --check arguments
80738
- // This allows CLI to extract all checks from the config file
80739
- if (checks.length > 0 && !checks.includes('all')) {
80740
- // Only add specific checks if "all" is not in the list
80741
- for (const check of checks) {
80742
- args.push('--check', check);
80743
- }
80744
- }
80745
- // When checks includes 'all', we intentionally don't add any --check arguments
80746
- // The CLI will then use all checks defined in .visor.yaml
80747
- }
80748
- // Add output format if specified
80749
- if (inputs['output-format']) {
80750
- args.push('--output', inputs['output-format']);
80751
- }
80752
- else {
80753
- // Always use JSON output for programmatic processing
80754
- args.push('--output', 'json');
80755
- }
80756
- // Add debug flag if enabled
80757
- if (inputs.debug === 'true') {
80758
- args.push('--debug');
80759
- }
80760
- // Add max parallelism if specified
80761
- if (inputs['max-parallelism']) {
80762
- args.push('--max-parallelism', inputs['max-parallelism']);
80763
- }
80764
- // Add fail-fast flag if enabled
80765
- if (inputs['fail-fast'] === 'true') {
80766
- args.push('--fail-fast');
80767
- }
80768
- return args;
80769
- }
80770
- /**
80771
- * Execute CLI with GitHub context
80772
- */
80773
- async executeCliWithContext(inputs, options = {}) {
80774
- const { workingDir = process.cwd(), timeout = 300000 } = options; // 5 min timeout
80775
- try {
80776
- const cliArgs = this.parseGitHubInputsToCliArgs(inputs);
80777
- // Set up environment variables for CLI
80778
- const env = {
80779
- ...process.env,
80780
- GITHUB_EVENT_NAME: this.context.event_name,
80781
- GITHUB_CONTEXT: JSON.stringify(this.context),
80782
- GITHUB_REPOSITORY_OWNER: this.context.repository?.owner.login || inputs.owner || '',
80783
- GITHUB_REPOSITORY: this.context.repository
80784
- ? `${this.context.repository.owner.login}/${this.context.repository.name}`
80785
- : `${inputs.owner || ''}/${inputs.repo || ''}`,
80786
- };
80787
- // Pass GitHub App credentials if they exist in inputs (use GitHub Actions input format)
80788
- if (inputs['app-id']) {
80789
- env['INPUT_APP-ID'] = inputs['app-id'];
80790
- }
80791
- if (inputs['private-key']) {
80792
- env['INPUT_PRIVATE-KEY'] = inputs['private-key'];
80793
- }
80794
- if (inputs['installation-id']) {
80795
- env['INPUT_INSTALLATION-ID'] = inputs['installation-id'];
80796
- }
80797
- // Pass GitHub token using GitHub Actions input format
80798
- const isUsingGitHubApp = inputs['app-id'] && inputs['private-key'];
80799
- if (this.githubToken && !isUsingGitHubApp) {
80800
- env['INPUT_GITHUB-TOKEN'] = this.githubToken;
80801
- }
80802
- // Also pass owner and repo as inputs
80803
- if (inputs.owner) {
80804
- env.INPUT_OWNER = inputs.owner;
80805
- }
80806
- if (inputs.repo) {
80807
- env.INPUT_REPO = inputs.repo;
80808
- }
80809
- console.log(`🚀 Executing Visor CLI with args: ${cliArgs.join(' ')}`);
80810
- // Use bundled index.js for CLI execution
80811
- // When running as a GitHub Action, GITHUB_ACTION_PATH points to the action's directory
80812
- // The bundled index.js contains both action and CLI functionality
80813
- const actionPath = process.env.GITHUB_ACTION_PATH;
80814
- const bundledPath = actionPath
80815
- ? path.join(actionPath, 'dist', 'index.js')
80816
- : path.join(__dirname, 'index.js'); // When running locally
80817
- // Pass --cli flag to force CLI mode even when GITHUB_ACTIONS env var is set
80818
- const result = await this.executeCommand('node', [bundledPath, '--cli', ...cliArgs], {
80819
- cwd: workingDir,
80820
- env,
80821
- timeout,
80822
- });
80823
- if (result.exitCode === 0) {
80824
- // Try to parse CLI output for additional data
80825
- const cliOutput = this.parseCliOutput(result.output);
80826
- return {
80827
- success: true,
80828
- output: result.output,
80829
- exitCode: result.exitCode,
80830
- cliOutput,
80831
- };
80832
- }
80833
- else {
80834
- return {
80835
- success: false,
80836
- output: result.output,
80837
- error: result.error,
80838
- exitCode: result.exitCode,
80839
- };
80840
- }
80841
- }
80842
- catch (error) {
80843
- return {
80844
- success: false,
80845
- error: error instanceof Error ? error.message : 'Unknown error',
80846
- exitCode: -1,
80847
- };
80848
- }
80849
- }
80850
- /**
80851
- * Merge CLI and Action outputs for backward compatibility
80852
- */
80853
- mergeActionAndCliOutputs(actionInputs, cliResult, legacyOutputs) {
80854
- const outputs = {
80855
- // Preserve legacy outputs if present
80856
- ...(legacyOutputs || {}),
80857
- };
80858
- if (cliResult.success && cliResult.cliOutput) {
80859
- const cli = cliResult.cliOutput;
80860
- if (cli.reviewScore !== undefined) {
80861
- outputs['review-score'] = cli.reviewScore.toString();
80862
- }
80863
- if (cli.issuesFound !== undefined) {
80864
- outputs['issues-found'] = cli.issuesFound.toString();
80865
- }
80866
- if (cli.autoReviewCompleted !== undefined) {
80867
- outputs['auto-review-completed'] = cli.autoReviewCompleted.toString();
80868
- }
80869
- }
80870
- return outputs;
80871
- }
80872
- /**
80873
- * Execute command with timeout and proper error handling
80874
- */
80875
- executeCommand(command, args, options = {}) {
80876
- return new Promise((resolve, reject) => {
80877
- const { cwd, env, timeout = 30000 } = options;
80878
- const child = (0, child_process_1.spawn)(command, args, {
80879
- cwd,
80880
- env,
80881
- stdio: ['ignore', 'pipe', 'pipe'],
80882
- });
80883
- let output = '';
80884
- let error = '';
80885
- let timeoutHandle = null;
80886
- if (child.stdout) {
80887
- child.stdout.on('data', data => {
80888
- output += data.toString();
80889
- });
80890
- }
80891
- if (child.stderr) {
80892
- child.stderr.on('data', data => {
80893
- error += data.toString();
80894
- });
80895
- }
80896
- child.on('close', code => {
80897
- if (timeoutHandle) {
80898
- clearTimeout(timeoutHandle);
80899
- }
80900
- resolve({
80901
- output: output.trim(),
80902
- error: error.trim(),
80903
- exitCode: code || 0,
80904
- });
80905
- });
80906
- child.on('error', err => {
80907
- if (timeoutHandle) {
80908
- clearTimeout(timeoutHandle);
80909
- }
80910
- reject(new Error(`Command execution failed: ${err.message}`));
80911
- });
80912
- // Set timeout if specified
80913
- if (timeout > 0) {
80914
- timeoutHandle = setTimeout(() => {
80915
- child.kill('SIGTERM');
80916
- reject(new Error(`Command execution timed out after ${timeout}ms`));
80917
- }, timeout);
80918
- }
80919
- });
80920
- }
80921
- /**
80922
- * Parse CLI JSON output to extract relevant data
80923
- */
80924
- parseCliOutput(output) {
80925
- try {
80926
- // Look for JSON output in the CLI result
80927
- const lines = output.split('\n');
80928
- for (const line of lines) {
80929
- const trimmed = line.trim();
80930
- if (trimmed.startsWith('{') && trimmed.endsWith('}')) {
80931
- const parsed = JSON.parse(trimmed);
80932
- // Extract relevant data that can be used for Action outputs
80933
- return {
80934
- reviewScore: parsed.reviewScore || parsed.overallScore,
80935
- issuesFound: parsed.issuesFound || parsed.totalIssues,
80936
- autoReviewCompleted: parsed.autoReviewCompleted || false,
80937
- };
80938
- }
80939
- }
80940
- return {};
80941
- }
80942
- catch {
80943
- console.log('Could not parse CLI output as JSON, using default values');
80944
- return {};
80945
- }
80946
- }
80947
- /**
80948
- * Check if a check type is valid
80949
- */
80950
- isValidCheck(check) {
80951
- const validChecks = ['performance', 'architecture', 'security', 'style', 'all'];
80952
- return validChecks.includes(check);
80953
- }
80954
- /**
80955
- * Create temporary config file from action inputs
80956
- */
80957
- async createTempConfigFromInputs(inputs, options = {}) {
80958
- const { workingDir = process.cwd() } = options;
80959
- if (!inputs['visor-checks']) {
80960
- return null;
80961
- }
80962
- const checks = inputs['visor-checks']
80963
- .split(',')
80964
- .map(check => check.trim())
80965
- .filter(check => this.isValidCheck(check));
80966
- if (checks.length === 0) {
80967
- return null;
80968
- }
80969
- // Create a basic Visor config from the checks
80970
- const config = {
80971
- version: '1.0',
80972
- checks: {},
80973
- output: {
80974
- pr_comment: {
80975
- format: 'markdown',
80976
- group_by: 'check',
80977
- collapse: true,
80978
- },
80979
- },
80980
- };
80981
- // Map GitHub Action checks to Visor config format
80982
- for (const check of checks) {
80983
- const checkName = `${check}-check`;
80984
- config.checks[checkName] = {
80985
- type: 'ai',
80986
- prompt: this.getPromptForCheck(check),
80987
- on: ['pr_opened', 'pr_updated'],
80988
- };
80989
- }
80990
- // Write temporary config file
80991
- const tempConfigPath = path.join(workingDir, '.visor-temp.yaml');
80992
- try {
80993
- const yaml = __nccwpck_require__(74281);
80994
- const yamlContent = yaml.dump(config);
80995
- await fs_1.promises.writeFile(tempConfigPath, yamlContent, 'utf8');
80996
- return tempConfigPath;
80997
- }
80998
- catch (error) {
80999
- console.error('Failed to create temporary config file:', error);
81000
- return null;
81001
- }
81002
- }
81003
- /**
81004
- * Get AI prompt for a specific check type
81005
- */
81006
- getPromptForCheck(check) {
81007
- const prompts = {
81008
- security: `Review this code for security vulnerabilities, focusing on:
81009
- - SQL injection, XSS, CSRF vulnerabilities
81010
- - Authentication and authorization flaws
81011
- - Sensitive data exposure
81012
- - Input validation issues
81013
- - Cryptographic weaknesses`,
81014
- performance: `Analyze this code for performance issues, focusing on:
81015
- - Database query efficiency (N+1 problems, missing indexes)
81016
- - Memory usage and potential leaks
81017
- - Algorithmic complexity issues
81018
- - Caching opportunities
81019
- - Resource utilization`,
81020
- architecture: `Review the architectural aspects of this code, focusing on:
81021
- - Design patterns and code organization
81022
- - Separation of concerns
81023
- - SOLID principles adherence
81024
- - Code maintainability and extensibility
81025
- - Technical debt`,
81026
- style: `Review code style and maintainability, focusing on:
81027
- - Consistent naming conventions
81028
- - Code formatting and readability
81029
- - Documentation quality
81030
- - Error handling patterns
81031
- - Code complexity`,
81032
- all: `Perform a comprehensive code review covering:
81033
- - Security vulnerabilities and best practices
81034
- - Performance optimization opportunities
81035
- - Architectural improvements
81036
- - Code style and maintainability
81037
- - Documentation and testing coverage`,
81038
- };
81039
- return prompts[check];
81040
- }
81041
- /**
81042
- * Cleanup temporary files
81043
- */
81044
- async cleanup(options = {}) {
81045
- const { workingDir = process.cwd() } = options;
81046
- const tempConfigPath = path.join(workingDir, '.visor-temp.yaml');
81047
- try {
81048
- await fs_1.promises.unlink(tempConfigPath);
81049
- }
81050
- catch {
81051
- // Ignore cleanup errors
81052
- }
81053
- }
81054
- }
81055
- exports.ActionCliBridge = ActionCliBridge;
81056
-
81057
-
81058
80657
  /***/ }),
81059
80658
 
81060
80659
  /***/ 51796:
@@ -83665,7 +83264,8 @@ async function main() {
83665
83264
  const cli = new cli_1.CLI();
83666
83265
  const configManager = new config_1.ConfigManager();
83667
83266
  // Check for help flag before parsing
83668
- const args = process.argv.slice(2);
83267
+ // Filter out --cli flag which is only used to force CLI mode in GitHub Actions
83268
+ const args = process.argv.slice(2).filter(arg => arg !== '--cli');
83669
83269
  if (args.includes('--help') || args.includes('-h')) {
83670
83270
  console.log(cli.getHelpText());
83671
83271
  process.exit(0);
@@ -83680,7 +83280,9 @@ async function main() {
83680
83280
  let config;
83681
83281
  if (cliOptions.configPath) {
83682
83282
  try {
83683
- config = await configManager.loadConfig(cliOptions.configPath);
83283
+ config = await configManager.loadConfig(cliOptions.configPath, {
83284
+ allowedRemotePatterns: cliOptions.allowedRemotePatterns,
83285
+ });
83684
83286
  }
83685
83287
  catch (error) {
83686
83288
  console.error(`⚠️ Warning: ${error instanceof Error ? error.message : 'Configuration file not found'}`);
@@ -83689,7 +83291,9 @@ async function main() {
83689
83291
  }
83690
83292
  }
83691
83293
  else {
83692
- config = await configManager.findAndLoadConfig();
83294
+ config = await configManager.findAndLoadConfig({
83295
+ allowedRemotePatterns: cliOptions.allowedRemotePatterns,
83296
+ });
83693
83297
  }
83694
83298
  // Merge CLI options with configuration
83695
83299
  const mergedConfig = configManager.mergeWithCliOptions(config, cliOptions);
@@ -83964,6 +83568,7 @@ class CLI {
83964
83568
  .option('--max-parallelism <count>', 'Maximum number of checks to run in parallel (default: 3)', value => parseInt(value, 10))
83965
83569
  .option('--debug', 'Enable debug mode for detailed output')
83966
83570
  .option('--fail-fast', 'Stop execution on first failure condition')
83571
+ .option('--no-remote-extends', 'Disable loading configurations from remote URLs')
83967
83572
  .addHelpText('after', this.getExamplesText())
83968
83573
  .exitOverride(); // Prevent automatic process.exit for better error handling
83969
83574
  // Add validation for options
@@ -83996,6 +83601,7 @@ class CLI {
83996
83601
  .option('--max-parallelism <count>', 'Maximum number of checks to run in parallel (default: 3)', value => parseInt(value, 10))
83997
83602
  .option('--debug', 'Enable debug mode for detailed output')
83998
83603
  .option('--fail-fast', 'Stop execution on first failure condition')
83604
+ .option('--allowed-remote-patterns <patterns>', 'Comma-separated list of allowed URL prefixes for remote config extends (e.g., "https://github.com/,https://raw.githubusercontent.com/")')
83999
83605
  .allowUnknownOption(false)
84000
83606
  .allowExcessArguments(false) // Don't allow positional arguments
84001
83607
  .addHelpText('after', this.getExamplesText())
@@ -84006,6 +83612,17 @@ class CLI {
84006
83612
  this.validateOptions(options);
84007
83613
  // Remove duplicates and preserve order
84008
83614
  const uniqueChecks = [...new Set(options.check)];
83615
+ // Set environment variable if no-remote-extends is set
83616
+ if (options.noRemoteExtends) {
83617
+ process.env.VISOR_NO_REMOTE_EXTENDS = 'true';
83618
+ }
83619
+ // Parse allowed remote patterns if provided
83620
+ let allowedRemotePatterns;
83621
+ if (options.allowedRemotePatterns) {
83622
+ allowedRemotePatterns = options.allowedRemotePatterns
83623
+ .split(',')
83624
+ .map((p) => p.trim());
83625
+ }
84009
83626
  return {
84010
83627
  checks: uniqueChecks,
84011
83628
  output: options.output,
@@ -84014,6 +83631,7 @@ class CLI {
84014
83631
  maxParallelism: options.maxParallelism,
84015
83632
  debug: options.debug,
84016
83633
  failFast: options.failFast,
83634
+ allowedRemotePatterns,
84017
83635
  help: options.help,
84018
83636
  version: options.version,
84019
83637
  };
@@ -84250,6 +83868,8 @@ const yaml = __importStar(__nccwpck_require__(74281));
84250
83868
  const fs = __importStar(__nccwpck_require__(79896));
84251
83869
  const path = __importStar(__nccwpck_require__(16928));
84252
83870
  const simple_git_1 = __importDefault(__nccwpck_require__(59065));
83871
+ const config_loader_1 = __nccwpck_require__(73836);
83872
+ const config_merger_1 = __nccwpck_require__(20730);
84253
83873
  /**
84254
83874
  * Configuration manager for Visor
84255
83875
  */
@@ -84269,7 +83889,7 @@ class ConfigManager {
84269
83889
  * Load configuration from a file
84270
83890
  */
84271
83891
  async loadConfig(configPath, options = {}) {
84272
- const { validate = true, mergeDefaults = true } = options;
83892
+ const { validate = true, mergeDefaults = true, allowedRemotePatterns } = options;
84273
83893
  try {
84274
83894
  if (!fs.existsSync(configPath)) {
84275
83895
  throw new Error(`Configuration file not found: ${configPath}`);
@@ -84286,6 +83906,34 @@ class ConfigManager {
84286
83906
  if (!parsedConfig || typeof parsedConfig !== 'object') {
84287
83907
  throw new Error('Configuration file must contain a valid YAML object');
84288
83908
  }
83909
+ // Handle extends directive if present
83910
+ if (parsedConfig.extends) {
83911
+ const loaderOptions = {
83912
+ baseDir: path.dirname(configPath),
83913
+ allowRemote: this.isRemoteExtendsAllowed(),
83914
+ maxDepth: 10,
83915
+ allowedRemotePatterns,
83916
+ };
83917
+ const loader = new config_loader_1.ConfigLoader(loaderOptions);
83918
+ const merger = new config_merger_1.ConfigMerger();
83919
+ // Process extends
83920
+ const extends_ = Array.isArray(parsedConfig.extends)
83921
+ ? parsedConfig.extends
83922
+ : [parsedConfig.extends];
83923
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
83924
+ const { extends: _extendsField, ...configWithoutExtends } = parsedConfig;
83925
+ // Load and merge all parent configurations
83926
+ let mergedConfig = {};
83927
+ for (const source of extends_) {
83928
+ console.log(`📦 Extending from: ${source}`);
83929
+ const parentConfig = await loader.fetchConfig(source);
83930
+ mergedConfig = merger.merge(mergedConfig, parentConfig);
83931
+ }
83932
+ // Merge with current config (child overrides parent)
83933
+ parsedConfig = merger.merge(mergedConfig, configWithoutExtends);
83934
+ // Remove disabled checks (those with empty 'on' array)
83935
+ parsedConfig = merger.removeDisabledChecks(parsedConfig);
83936
+ }
84289
83937
  if (validate) {
84290
83938
  this.validateConfig(parsedConfig);
84291
83939
  }
@@ -84308,7 +83956,7 @@ class ConfigManager {
84308
83956
  /**
84309
83957
  * Find and load configuration from default locations
84310
83958
  */
84311
- async findAndLoadConfig() {
83959
+ async findAndLoadConfig(options = {}) {
84312
83960
  // Try to find the git repository root first, fall back to current directory
84313
83961
  const gitRoot = await this.findGitRepositoryRoot();
84314
83962
  const searchDirs = [gitRoot, process.cwd()].filter(Boolean);
@@ -84316,7 +83964,7 @@ class ConfigManager {
84316
83964
  const possiblePaths = [path.join(baseDir, '.visor.yaml'), path.join(baseDir, '.visor.yml')];
84317
83965
  for (const configPath of possiblePaths) {
84318
83966
  if (fs.existsSync(configPath)) {
84319
- return this.loadConfig(configPath);
83967
+ return this.loadConfig(configPath, options);
84320
83968
  }
84321
83969
  }
84322
83970
  }
@@ -84611,6 +84259,18 @@ class ConfigManager {
84611
84259
  }
84612
84260
  }
84613
84261
  }
84262
+ /**
84263
+ * Check if remote extends are allowed
84264
+ */
84265
+ isRemoteExtendsAllowed() {
84266
+ // Check environment variable first
84267
+ if (process.env.VISOR_NO_REMOTE_EXTENDS === 'true' ||
84268
+ process.env.VISOR_NO_REMOTE_EXTENDS === '1') {
84269
+ return false;
84270
+ }
84271
+ // Default to allowing remote extends
84272
+ return true;
84273
+ }
84614
84274
  /**
84615
84275
  * Merge configuration with default values
84616
84276
  */
@@ -85834,7 +85494,9 @@ class GitHubCheckService {
85834
85494
  });
85835
85495
  }
85836
85496
  // Footer
85497
+ sections.push('');
85837
85498
  sections.push('---');
85499
+ sections.push('');
85838
85500
  sections.push('*Generated by [Visor](https://github.com/probelabs/visor) - AI-powered code review*');
85839
85501
  return sections.join('\n');
85840
85502
  }
@@ -86274,6 +85936,9 @@ exports.CommentManager = CommentManager;
86274
85936
 
86275
85937
  "use strict";
86276
85938
 
85939
+ /* eslint-disable @typescript-eslint/no-explicit-any */
85940
+ // GitHub event objects have complex dynamic structures that are difficult to fully type
85941
+ // Using 'any' for these objects is acceptable as they come from external GitHub webhooks
86277
85942
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
86278
85943
  if (k2 === undefined) k2 = k;
86279
85944
  var desc = Object.getOwnPropertyDescriptor(m, k);
@@ -86312,10 +85977,10 @@ exports.run = run;
86312
85977
  const rest_1 = __nccwpck_require__(47432);
86313
85978
  const auth_app_1 = __nccwpck_require__(76479);
86314
85979
  const core_1 = __nccwpck_require__(37484);
85980
+ const path = __importStar(__nccwpck_require__(16928));
86315
85981
  const commands_1 = __nccwpck_require__(99153);
86316
85982
  const pr_analyzer_1 = __nccwpck_require__(80100);
86317
85983
  const reviewer_1 = __nccwpck_require__(532);
86318
- const action_cli_bridge_1 = __nccwpck_require__(42058);
86319
85984
  const config_1 = __nccwpck_require__(22973);
86320
85985
  const github_check_service_1 = __nccwpck_require__(21367);
86321
85986
  /**
@@ -86396,11 +86061,9 @@ async function run() {
86396
86061
  try {
86397
86062
  const { octokit, authType } = await createAuthenticatedOctokit();
86398
86063
  console.log(`✅ Authenticated successfully using ${authType}`);
86399
- // Get token for passing to CLI bridge (might be undefined if using App auth)
86400
- const token = (0, core_1.getInput)('github-token') || '';
86401
86064
  // Collect all GitHub Action inputs
86402
86065
  const inputs = {
86403
- 'github-token': token,
86066
+ 'github-token': (0, core_1.getInput)('github-token') || '',
86404
86067
  owner: (0, core_1.getInput)('owner') || process.env.GITHUB_REPOSITORY_OWNER,
86405
86068
  repo: (0, core_1.getInput)('repo') || process.env.GITHUB_REPOSITORY?.split('/')[1],
86406
86069
  debug: (0, core_1.getInput)('debug'),
@@ -86419,11 +86082,25 @@ async function run() {
86419
86082
  'fail-on-api-error': (0, core_1.getInput)('fail-on-api-error') || undefined,
86420
86083
  'min-score': (0, core_1.getInput)('min-score') || undefined,
86421
86084
  'max-parallelism': (0, core_1.getInput)('max-parallelism') || undefined,
86085
+ 'ai-provider': (0, core_1.getInput)('ai-provider') || undefined,
86086
+ 'ai-model': (0, core_1.getInput)('ai-model') || undefined,
86422
86087
  // Legacy inputs for backward compatibility
86423
86088
  'visor-config-path': (0, core_1.getInput)('visor-config-path') || undefined,
86424
86089
  'visor-checks': (0, core_1.getInput)('visor-checks') || undefined,
86425
86090
  };
86426
86091
  const eventName = process.env.GITHUB_EVENT_NAME;
86092
+ // Load GitHub event data from event file
86093
+ let eventData = {};
86094
+ if (process.env.GITHUB_EVENT_PATH) {
86095
+ try {
86096
+ const fs = await Promise.resolve().then(() => __importStar(__nccwpck_require__(79896)));
86097
+ const eventContent = fs.readFileSync(process.env.GITHUB_EVENT_PATH, 'utf8');
86098
+ eventData = JSON.parse(eventContent);
86099
+ }
86100
+ catch (error) {
86101
+ console.error('Failed to load GitHub event data:', error);
86102
+ }
86103
+ }
86427
86104
  // Create GitHub context for CLI bridge
86428
86105
  const context = {
86429
86106
  event_name: eventName || 'unknown',
@@ -86433,51 +86110,57 @@ async function run() {
86433
86110
  name: process.env.GITHUB_REPOSITORY.split('/')[1],
86434
86111
  }
86435
86112
  : undefined,
86436
- event: process.env.GITHUB_CONTEXT ? JSON.parse(process.env.GITHUB_CONTEXT).event : {},
86437
- payload: process.env.GITHUB_CONTEXT ? JSON.parse(process.env.GITHUB_CONTEXT) : {},
86113
+ event: eventData,
86114
+ payload: eventData,
86438
86115
  };
86439
- // Initialize CLI bridge
86440
- const cliBridge = new action_cli_bridge_1.ActionCliBridge(token, context);
86441
- // Check if we should use Visor CLI
86116
+ // Debug logging for inputs
86442
86117
  console.log('Debug: inputs.debug =', inputs.debug);
86443
86118
  console.log('Debug: inputs.checks =', inputs.checks);
86444
86119
  console.log('Debug: inputs.config-path =', inputs['config-path']);
86445
86120
  console.log('Debug: inputs.visor-checks =', inputs['visor-checks']);
86446
86121
  console.log('Debug: inputs.visor-config-path =', inputs['visor-config-path']);
86447
- if (cliBridge.shouldUseVisor(inputs)) {
86448
- console.log('🔍 Using Visor CLI mode');
86449
- // Instead of spawning subprocess, directly run CLI logic
86450
- const cliArgs = cliBridge.parseGitHubInputsToCliArgs(inputs);
86451
- // Set argv for CLI parsing
86452
- process.argv = ['node', 'visor', ...cliArgs];
86453
- // Import and run CLI directly
86454
- const { main } = await Promise.resolve().then(() => __importStar(__nccwpck_require__(10091)));
86455
- await main();
86456
- return;
86457
- }
86458
- // Default behavior: Use Visor config to determine what to run
86122
+ // Always use config-driven mode in GitHub Actions
86123
+ // The CLI mode is only for local development, not for GitHub Actions
86459
86124
  console.log('🤖 Using config-driven mode');
86460
86125
  // Load config to determine which checks should run for this event
86461
86126
  const configManager = new config_1.ConfigManager();
86462
86127
  let config;
86128
+ // First try to load user config, then fall back to defaults/.visor.yaml
86129
+ const configPath = inputs['config-path'] || inputs['visor-config-path'];
86463
86130
  try {
86464
- config = await configManager.findAndLoadConfig();
86465
- console.log('📋 Loaded Visor config');
86131
+ if (configPath) {
86132
+ // Load specified config
86133
+ config = await configManager.loadConfig(configPath);
86134
+ console.log(`📋 Loaded config from: ${configPath}`);
86135
+ }
86136
+ else {
86137
+ // Try to find config in project
86138
+ config = await configManager.findAndLoadConfig();
86139
+ console.log('📋 Loaded Visor config from project');
86140
+ }
86466
86141
  }
86467
86142
  catch {
86468
- // Use default config if none found
86469
- config = {
86470
- version: '1.0',
86471
- checks: {},
86472
- output: {
86473
- pr_comment: {
86474
- format: 'markdown',
86475
- group_by: 'check',
86476
- collapse: false,
86143
+ // Fall back to bundled default config
86144
+ try {
86145
+ const defaultConfigPath = path.join(process.env.GITHUB_ACTION_PATH || __dirname, 'defaults', '.visor.yaml');
86146
+ config = await configManager.loadConfig(defaultConfigPath);
86147
+ console.log('📋 Using bundled default configuration from defaults/.visor.yaml');
86148
+ }
86149
+ catch {
86150
+ // Ultimate fallback if even defaults/.visor.yaml can't be loaded
86151
+ config = {
86152
+ version: '1.0',
86153
+ checks: {},
86154
+ output: {
86155
+ pr_comment: {
86156
+ format: 'markdown',
86157
+ group_by: 'check',
86158
+ collapse: false,
86159
+ },
86477
86160
  },
86478
- },
86479
- };
86480
- console.log('📋 Using default configuration');
86161
+ };
86162
+ console.log('⚠️ Could not load defaults/.visor.yaml, using minimal configuration');
86163
+ }
86481
86164
  }
86482
86165
  // Determine which event we're handling and run appropriate checks
86483
86166
  await handleEvent(octokit, inputs, eventName, context, config);
@@ -86486,58 +86169,6 @@ async function run() {
86486
86169
  (0, core_1.setFailed)(error instanceof Error ? error.message : 'Unknown error');
86487
86170
  }
86488
86171
  }
86489
- /**
86490
- * Handle Visor CLI mode
86491
- */
86492
- async function handleVisorMode(cliBridge, inputs, _context, _octokit) {
86493
- try {
86494
- // Note: PR auto-review cases are now handled upstream in the main run() function
86495
- // Execute CLI with the provided config file (no temp config creation)
86496
- const result = await cliBridge.executeCliWithContext(inputs);
86497
- if (result.success) {
86498
- console.log('✅ Visor CLI execution completed successfully');
86499
- // Parse JSON output for PR comment creation
86500
- let cliOutput;
86501
- try {
86502
- // Extract JSON from CLI output
86503
- const outputLines = result.output?.split('\n') || [];
86504
- const jsonLine = outputLines.find(line => line.trim().startsWith('{') && line.trim().endsWith('}'));
86505
- if (jsonLine) {
86506
- cliOutput = JSON.parse(jsonLine);
86507
- console.log('📊 CLI Review Results:', cliOutput);
86508
- // Note: PR comment posting is now handled by handlePullRequestVisorMode for PR events
86509
- // CLI mode output is intended for non-PR scenarios
86510
- }
86511
- else {
86512
- console.log('📄 CLI Output (non-JSON):', result.output);
86513
- }
86514
- }
86515
- catch (parseError) {
86516
- console.log('⚠️ Could not parse CLI output as JSON:', parseError);
86517
- console.log('📄 Raw CLI Output:', result.output);
86518
- }
86519
- // Set outputs based on CLI result
86520
- const outputs = cliBridge.mergeActionAndCliOutputs(inputs, result);
86521
- // Add additional outputs from parsed JSON
86522
- if (cliOutput) {
86523
- outputs['total-issues'] = cliOutput.totalIssues?.toString() || '0';
86524
- outputs['critical-issues'] = cliOutput.criticalIssues?.toString() || '0';
86525
- }
86526
- for (const [key, value] of Object.entries(outputs)) {
86527
- (0, core_1.setOutput)(key, value);
86528
- }
86529
- }
86530
- else {
86531
- console.error('❌ Visor CLI execution failed');
86532
- console.error(result.error || result.output);
86533
- (0, core_1.setFailed)(result.error || 'CLI execution failed');
86534
- }
86535
- }
86536
- catch (error) {
86537
- console.error('❌ Visor mode error:', error);
86538
- (0, core_1.setFailed)(error instanceof Error ? error.message : 'Visor mode failed');
86539
- }
86540
- }
86541
86172
  function mapGitHubEventToTrigger(eventName, action) {
86542
86173
  if (!eventName)
86543
86174
  return 'pr_updated';
@@ -86571,14 +86202,53 @@ async function handleEvent(octokit, inputs, eventName, context, config) {
86571
86202
  // Map GitHub event to our event trigger format
86572
86203
  const eventType = mapGitHubEventToTrigger(eventName, context.event?.action);
86573
86204
  // Find checks that should run for this event
86574
- const checksToRun = [];
86205
+ let checksToRun = [];
86206
+ // First, get all checks that are configured for this event type
86207
+ const eventChecks = [];
86575
86208
  for (const [checkName, checkConfig] of Object.entries(config.checks || {})) {
86576
86209
  // Check if this check should run for this event
86577
86210
  const checkEvents = checkConfig.on || ['pr_opened', 'pr_updated'];
86578
86211
  if (checkEvents.includes(eventType)) {
86579
- checksToRun.push(checkName);
86212
+ eventChecks.push(checkName);
86213
+ }
86214
+ }
86215
+ // Now apply the 'checks' input filter if provided
86216
+ const checksInput = inputs.checks || inputs['visor-checks'];
86217
+ if (checksInput && checksInput.trim() !== '') {
86218
+ const requestedChecks = checksInput.split(',').map(c => c.trim());
86219
+ if (requestedChecks.includes('all')) {
86220
+ // If 'all' is specified, run all event checks
86221
+ checksToRun = eventChecks;
86222
+ console.log('📋 Running all available checks for this event');
86223
+ }
86224
+ else {
86225
+ // Filter to only the requested checks that are also configured for this event
86226
+ // Map simplified check names to actual config check names if needed
86227
+ for (const requested of requestedChecks) {
86228
+ // Try exact match first
86229
+ if (eventChecks.includes(requested)) {
86230
+ checksToRun.push(requested);
86231
+ }
86232
+ else {
86233
+ // Try with '-check' suffix (e.g., 'security' -> 'security-check')
86234
+ const withSuffix = `${requested}-check`;
86235
+ if (eventChecks.includes(withSuffix)) {
86236
+ checksToRun.push(withSuffix);
86237
+ }
86238
+ else {
86239
+ // Try to find any check that contains the requested string
86240
+ const matching = eventChecks.filter(check => check.toLowerCase().includes(requested.toLowerCase()));
86241
+ checksToRun.push(...matching);
86242
+ }
86243
+ }
86244
+ }
86245
+ console.log(`📋 Running requested checks: ${requestedChecks.join(', ')}`);
86580
86246
  }
86581
86247
  }
86248
+ else {
86249
+ // No checks input provided, run all event checks
86250
+ checksToRun = eventChecks;
86251
+ }
86582
86252
  if (checksToRun.length === 0) {
86583
86253
  console.log(`ℹ️ No checks configured to run for event: ${eventType}`);
86584
86254
  return;
@@ -86587,11 +86257,15 @@ async function handleEvent(octokit, inputs, eventName, context, config) {
86587
86257
  // Handle different GitHub events
86588
86258
  switch (eventName) {
86589
86259
  case 'issue_comment':
86590
- await handleIssueComment(octokit, owner, repo);
86260
+ await handleIssueComment(octokit, owner, repo, context, inputs);
86591
86261
  break;
86592
86262
  case 'pull_request':
86593
86263
  // Run the checks that are configured for this event
86594
- await handlePullRequestWithConfig(octokit, owner, repo, inputs, config, checksToRun);
86264
+ await handlePullRequestWithConfig(octokit, owner, repo, inputs, config, checksToRun, context);
86265
+ break;
86266
+ case 'issues':
86267
+ // Handle issue events (opened, closed, etc)
86268
+ await handleIssueEvent(octokit, owner, repo, context, inputs, config, checksToRun);
86595
86269
  break;
86596
86270
  case 'push':
86597
86271
  // Could handle push events that are associated with PRs
@@ -86635,8 +86309,81 @@ function resolveDependencies(checkIds, config, resolved = new Set(), visiting =
86635
86309
  }
86636
86310
  return result;
86637
86311
  }
86638
- async function handleIssueComment(octokit, owner, repo) {
86639
- const context = JSON.parse(process.env.GITHUB_CONTEXT || '{}');
86312
+ /**
86313
+ * Handle issue events (opened, edited, etc)
86314
+ */
86315
+ async function handleIssueEvent(octokit, owner, repo, context, inputs, config, checksToRun) {
86316
+ const issue = context.event?.issue;
86317
+ const action = context.event?.action;
86318
+ if (!issue) {
86319
+ console.log('No issue found in context');
86320
+ return;
86321
+ }
86322
+ // Skip if this is a pull request (has pull_request property)
86323
+ if (issue.pull_request) {
86324
+ console.log('Skipping PR-related issue event');
86325
+ return;
86326
+ }
86327
+ console.log(`Processing issue #${issue.number} event: ${action} with checks: ${checksToRun.join(', ')}`);
86328
+ // For issue events, we need to create a PR-like structure for the checks to process
86329
+ // This allows us to reuse the existing check infrastructure
86330
+ const prInfo = {
86331
+ number: issue.number,
86332
+ title: issue.title || '',
86333
+ body: issue.body || '',
86334
+ author: issue.user?.login || 'unknown',
86335
+ base: 'main', // Issues don't have branches
86336
+ head: 'issue', // Issues don't have branches
86337
+ files: [], // No file changes for issues
86338
+ additions: 0,
86339
+ deletions: 0,
86340
+ totalAdditions: 0,
86341
+ totalDeletions: 0,
86342
+ eventType: mapGitHubEventToTrigger('issues', action),
86343
+ };
86344
+ // Run the checks using CheckExecutionEngine
86345
+ const { CheckExecutionEngine } = await Promise.resolve().then(() => __importStar(__nccwpck_require__(80299)));
86346
+ const engine = new CheckExecutionEngine();
86347
+ try {
86348
+ const result = await engine.executeGroupedChecks(prInfo, checksToRun, undefined, // timeout
86349
+ config, undefined, // outputFormat
86350
+ inputs.debug === 'true');
86351
+ // Format and post results as a comment on the issue
86352
+ if (Object.keys(result).length > 0) {
86353
+ let commentBody = `## 🤖 Issue Assistant Results\n\n`;
86354
+ for (const checks of Object.values(result)) {
86355
+ for (const check of checks) {
86356
+ if (check.content && check.content.trim()) {
86357
+ commentBody += `### ${check.checkName}\n`;
86358
+ commentBody += `${check.content}\n\n`;
86359
+ }
86360
+ }
86361
+ }
86362
+ commentBody += `\n---\n*Powered by [Visor](https://github.com/probelabs/visor)*`;
86363
+ // Post comment to the issue
86364
+ await octokit.rest.issues.createComment({
86365
+ owner,
86366
+ repo,
86367
+ issue_number: issue.number,
86368
+ body: commentBody,
86369
+ });
86370
+ console.log(`✅ Posted issue assistant results to issue #${issue.number}`);
86371
+ }
86372
+ else {
86373
+ console.log('No results from issue assistant checks');
86374
+ }
86375
+ // Set outputs for GitHub Actions
86376
+ (0, core_1.setOutput)('review-completed', 'true');
86377
+ (0, core_1.setOutput)('checks-executed', checksToRun.length.toString());
86378
+ }
86379
+ catch (error) {
86380
+ console.error('Error running issue assistant checks:', error);
86381
+ (0, core_1.setOutput)('review-completed', 'false');
86382
+ (0, core_1.setOutput)('error', error instanceof Error ? error.message : 'Unknown error');
86383
+ throw error;
86384
+ }
86385
+ }
86386
+ async function handleIssueComment(octokit, owner, repo, context, inputs) {
86640
86387
  const comment = context.event?.comment;
86641
86388
  const issue = context.event?.issue;
86642
86389
  if (!comment || !issue) {
@@ -86703,7 +86450,7 @@ async function handleIssueComment(octokit, owner, repo) {
86703
86450
  `**Additions:** +${statusPrInfo.totalAdditions}\n` +
86704
86451
  `**Deletions:** -${statusPrInfo.totalDeletions}\n` +
86705
86452
  `**Base:** ${statusPrInfo.base} → **Head:** ${statusPrInfo.head}\n\n` +
86706
- `---\n` +
86453
+ `\n---\n\n` +
86707
86454
  `*Powered by [Visor](https://probelabs.com/visor) from [Probelabs](https://probelabs.com)*`;
86708
86455
  await octokit.rest.issues.createComment({
86709
86456
  owner,
@@ -86746,10 +86493,17 @@ async function handleIssueComment(octokit, owner, repo) {
86746
86493
  checks: checkIds,
86747
86494
  parallelExecution: false,
86748
86495
  });
86749
- await reviewer.postReviewComment(owner, repo, prNumber, groupedResults, {
86750
- focus,
86751
- format,
86752
- });
86496
+ // Check if commenting is enabled before posting
86497
+ const shouldComment = inputs['comment-on-pr'] !== 'false';
86498
+ if (shouldComment) {
86499
+ await reviewer.postReviewComment(owner, repo, prNumber, groupedResults, {
86500
+ focus,
86501
+ format,
86502
+ });
86503
+ }
86504
+ else {
86505
+ console.log('📝 Skipping PR comment (comment-on-pr is disabled)');
86506
+ }
86753
86507
  // Calculate total check results from grouped results
86754
86508
  const totalChecks = Object.values(groupedResults).flat().length;
86755
86509
  (0, core_1.setOutput)('checks-executed', totalChecks.toString());
@@ -86757,8 +86511,7 @@ async function handleIssueComment(octokit, owner, repo) {
86757
86511
  break;
86758
86512
  }
86759
86513
  }
86760
- async function handlePullRequestWithConfig(octokit, owner, repo, inputs, config, checksToRun) {
86761
- const context = JSON.parse(process.env.GITHUB_CONTEXT || '{}');
86514
+ async function handlePullRequestWithConfig(octokit, owner, repo, inputs, config, checksToRun, context) {
86762
86515
  const pullRequest = context.event?.pull_request;
86763
86516
  const action = context.event?.action;
86764
86517
  if (!pullRequest) {
@@ -86773,8 +86526,22 @@ async function handlePullRequestWithConfig(octokit, owner, repo, inputs, config,
86773
86526
  const commentId = `pr-review-${prNumber}`;
86774
86527
  // Map the action to event type
86775
86528
  const eventType = mapGitHubEventToTrigger('pull_request', action);
86776
- // Fetch PR diff
86777
- const prInfo = await analyzer.fetchPRDiff(owner, repo, prNumber, undefined, eventType);
86529
+ // Fetch PR diff (handle test scenarios gracefully)
86530
+ let prInfo;
86531
+ try {
86532
+ prInfo = await analyzer.fetchPRDiff(owner, repo, prNumber, undefined, eventType);
86533
+ }
86534
+ catch (error) {
86535
+ // Handle test scenarios with mock repos
86536
+ if (inputs['ai-provider'] === 'mock' || inputs['ai-model'] === 'mock') {
86537
+ console.log(`📋 Running in test mode with mock provider - using empty PR data`);
86538
+ (0, core_1.setOutput)('review-completed', 'true');
86539
+ (0, core_1.setOutput)('issues-found', '0');
86540
+ (0, core_1.setOutput)('checks-executed', '0');
86541
+ return;
86542
+ }
86543
+ throw error;
86544
+ }
86778
86545
  if (prInfo.files.length === 0) {
86779
86546
  console.log('⚠️ No files changed in this PR - skipping review');
86780
86547
  (0, core_1.setOutput)('review-completed', 'true');
@@ -86811,28 +86578,43 @@ async function handlePullRequestWithConfig(octokit, owner, repo, inputs, config,
86811
86578
  if (checkResults?.checkRunMap) {
86812
86579
  await completeGitHubChecks(octokit, owner, repo, checkResults.checkRunMap, groupedResults, config);
86813
86580
  }
86814
- // Post review comment
86815
- await reviewer.postReviewComment(owner, repo, prNumber, groupedResults, {
86816
- commentId,
86817
- triggeredBy: action,
86818
- commitSha: pullRequest.head?.sha,
86819
- });
86581
+ // Post review comment (only if comment-on-pr is not disabled)
86582
+ const shouldComment = inputs['comment-on-pr'] !== 'false';
86583
+ if (shouldComment) {
86584
+ await reviewer.postReviewComment(owner, repo, prNumber, groupedResults, {
86585
+ commentId,
86586
+ triggeredBy: action,
86587
+ commitSha: pullRequest.head?.sha,
86588
+ });
86589
+ }
86590
+ else {
86591
+ console.log('📝 Skipping PR comment (comment-on-pr is disabled)');
86592
+ }
86820
86593
  // Set outputs
86821
86594
  (0, core_1.setOutput)('review-completed', 'true');
86822
86595
  (0, core_1.setOutput)('checks-executed', checksToExecute.length.toString());
86823
86596
  (0, core_1.setOutput)('pr-action', action);
86824
86597
  }
86825
86598
  async function handleRepoInfo(octokit, owner, repo) {
86826
- const { data: repoData } = await octokit.rest.repos.get({
86827
- owner,
86828
- repo,
86829
- });
86830
- (0, core_1.setOutput)('repo-name', repoData.name);
86831
- (0, core_1.setOutput)('repo-description', repoData.description || '');
86832
- (0, core_1.setOutput)('repo-stars', repoData.stargazers_count.toString());
86833
- console.log(`Repository: ${repoData.full_name}`);
86834
- console.log(`Description: ${repoData.description || 'No description'}`);
86835
- console.log(`Stars: ${repoData.stargazers_count}`);
86599
+ try {
86600
+ const { data: repoData } = await octokit.rest.repos.get({
86601
+ owner,
86602
+ repo,
86603
+ });
86604
+ (0, core_1.setOutput)('repo-name', repoData.name);
86605
+ (0, core_1.setOutput)('repo-description', repoData.description || '');
86606
+ (0, core_1.setOutput)('repo-stars', repoData.stargazers_count.toString());
86607
+ console.log(`Repository: ${repoData.full_name}`);
86608
+ console.log(`Description: ${repoData.description || 'No description'}`);
86609
+ console.log(`Stars: ${repoData.stargazers_count}`);
86610
+ }
86611
+ catch {
86612
+ // Handle test scenarios or missing repos gracefully
86613
+ console.log(`📋 Running in test mode or repository not accessible: ${owner}/${repo}`);
86614
+ (0, core_1.setOutput)('repo-name', repo);
86615
+ (0, core_1.setOutput)('repo-description', 'Test repository');
86616
+ (0, core_1.setOutput)('repo-stars', '0');
86617
+ }
86836
86618
  }
86837
86619
  /**
86838
86620
  * Filter checks based on their if conditions and API requirements
@@ -87194,27 +86976,31 @@ async function markCheckAsFailed(checkService, owner, repo, checkRunId, checkNam
87194
86976
  }
87195
86977
  // Entry point - execute immediately when the script is run
87196
86978
  // Note: require.main === module check doesn't work reliably with ncc bundling
87197
- (() => {
87198
- // Simple mode detection: use GITHUB_ACTIONS env var which is always 'true' in GitHub Actions
87199
- // Also check for --cli flag to force CLI mode even in GitHub Actions environment
87200
- const isGitHubAction = process.env.GITHUB_ACTIONS === 'true' && !process.argv.includes('--cli');
87201
- if (isGitHubAction) {
87202
- // Run as GitHub Action
87203
- run();
87204
- }
87205
- else {
87206
- // Import and run CLI
87207
- Promise.resolve().then(() => __importStar(__nccwpck_require__(10091))).then(({ main }) => {
87208
- main().catch(error => {
87209
- console.error('CLI execution failed:', error);
86979
+ // Only execute if not in test environment
86980
+ if (process.env.NODE_ENV !== 'test' && process.env.JEST_WORKER_ID === undefined) {
86981
+ (() => {
86982
+ // Simple mode detection: use GITHUB_ACTIONS env var which is always 'true' in GitHub Actions
86983
+ // Also check for --cli flag to force CLI mode even in GitHub Actions environment
86984
+ const isGitHubAction = process.env.GITHUB_ACTIONS === 'true' && !process.argv.includes('--cli');
86985
+ if (isGitHubAction) {
86986
+ // Run as GitHub Action
86987
+ run();
86988
+ }
86989
+ else {
86990
+ // Import and run CLI
86991
+ Promise.resolve().then(() => __importStar(__nccwpck_require__(10091))).then(({ main }) => {
86992
+ main().catch(error => {
86993
+ console.error('CLI execution failed:', error);
86994
+ process.exit(1);
86995
+ });
86996
+ })
86997
+ .catch(error => {
86998
+ console.error('Failed to import CLI module:', error);
87210
86999
  process.exit(1);
87211
87000
  });
87212
- }).catch(error => {
87213
- console.error('Failed to import CLI module:', error);
87214
- process.exit(1);
87215
- });
87216
- }
87217
- })();
87001
+ }
87002
+ })();
87003
+ }
87218
87004
 
87219
87005
 
87220
87006
  /***/ }),
@@ -89001,7 +88787,7 @@ class WebhookCheckProvider extends check_provider_interface_1.CheckProvider {
89001
88787
  }
89002
88788
  catch (error) {
89003
88789
  clearTimeout(timeoutId);
89004
- if (error?.name === 'AbortError') {
88790
+ if (error instanceof Error && error.name === 'AbortError') {
89005
88791
  throw new Error(`Webhook request timed out after ${timeout}ms`);
89006
88792
  }
89007
88793
  throw error;
@@ -89235,7 +89021,7 @@ class PRReviewer {
89235
89021
  comment += '\n\n' + this.formatDebugSection(debugInfo);
89236
89022
  comment += '\n\n';
89237
89023
  }
89238
- comment += `\n---\n*Powered by [Visor](https://probelabs.com/visor) from [Probelabs](https://probelabs.com)*`;
89024
+ comment += `\n\n---\n\n*Powered by [Visor](https://probelabs.com/visor) from [Probelabs](https://probelabs.com)*`;
89239
89025
  return comment;
89240
89026
  }
89241
89027
  formatDebugSection(debug) {
@@ -89421,6 +89207,699 @@ class SessionRegistry {
89421
89207
  exports.SessionRegistry = SessionRegistry;
89422
89208
 
89423
89209
 
89210
+ /***/ }),
89211
+
89212
+ /***/ 73836:
89213
+ /***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {
89214
+
89215
+ "use strict";
89216
+
89217
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
89218
+ if (k2 === undefined) k2 = k;
89219
+ var desc = Object.getOwnPropertyDescriptor(m, k);
89220
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
89221
+ desc = { enumerable: true, get: function() { return m[k]; } };
89222
+ }
89223
+ Object.defineProperty(o, k2, desc);
89224
+ }) : (function(o, m, k, k2) {
89225
+ if (k2 === undefined) k2 = k;
89226
+ o[k2] = m[k];
89227
+ }));
89228
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
89229
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
89230
+ }) : function(o, v) {
89231
+ o["default"] = v;
89232
+ });
89233
+ var __importStar = (this && this.__importStar) || (function () {
89234
+ var ownKeys = function(o) {
89235
+ ownKeys = Object.getOwnPropertyNames || function (o) {
89236
+ var ar = [];
89237
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
89238
+ return ar;
89239
+ };
89240
+ return ownKeys(o);
89241
+ };
89242
+ return function (mod) {
89243
+ if (mod && mod.__esModule) return mod;
89244
+ var result = {};
89245
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
89246
+ __setModuleDefault(result, mod);
89247
+ return result;
89248
+ };
89249
+ })();
89250
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
89251
+ exports.ConfigLoader = exports.ConfigSourceType = void 0;
89252
+ const fs = __importStar(__nccwpck_require__(79896));
89253
+ const path = __importStar(__nccwpck_require__(16928));
89254
+ const yaml = __importStar(__nccwpck_require__(74281));
89255
+ /**
89256
+ * Configuration source types
89257
+ */
89258
+ var ConfigSourceType;
89259
+ (function (ConfigSourceType) {
89260
+ ConfigSourceType["LOCAL"] = "local";
89261
+ ConfigSourceType["REMOTE"] = "remote";
89262
+ ConfigSourceType["DEFAULT"] = "default";
89263
+ })(ConfigSourceType || (exports.ConfigSourceType = ConfigSourceType = {}));
89264
+ /**
89265
+ * Utility class for loading configurations from various sources
89266
+ */
89267
+ class ConfigLoader {
89268
+ options;
89269
+ cache = new Map();
89270
+ loadedConfigs = new Set();
89271
+ constructor(options = {}) {
89272
+ this.options = options;
89273
+ this.options = {
89274
+ allowRemote: true,
89275
+ cacheTTL: 5 * 60 * 1000, // 5 minutes
89276
+ timeout: 30 * 1000, // 30 seconds
89277
+ maxDepth: 10,
89278
+ allowedRemotePatterns: [], // Empty by default for security
89279
+ projectRoot: this.findProjectRoot(),
89280
+ ...options,
89281
+ };
89282
+ }
89283
+ /**
89284
+ * Determine the source type from a string
89285
+ */
89286
+ getSourceType(source) {
89287
+ if (source === 'default') {
89288
+ return ConfigSourceType.DEFAULT;
89289
+ }
89290
+ if (source.startsWith('http://') || source.startsWith('https://')) {
89291
+ return ConfigSourceType.REMOTE;
89292
+ }
89293
+ return ConfigSourceType.LOCAL;
89294
+ }
89295
+ /**
89296
+ * Fetch configuration from any source
89297
+ */
89298
+ async fetchConfig(source, currentDepth = 0) {
89299
+ // Check recursion depth
89300
+ if (currentDepth >= (this.options.maxDepth || 10)) {
89301
+ throw new Error(`Maximum extends depth (${this.options.maxDepth}) exceeded. Check for circular dependencies.`);
89302
+ }
89303
+ // Check for circular dependencies
89304
+ const normalizedSource = this.normalizeSource(source);
89305
+ if (this.loadedConfigs.has(normalizedSource)) {
89306
+ throw new Error(`Circular dependency detected: ${normalizedSource} is already in the extends chain`);
89307
+ }
89308
+ const sourceType = this.getSourceType(source);
89309
+ try {
89310
+ this.loadedConfigs.add(normalizedSource);
89311
+ switch (sourceType) {
89312
+ case ConfigSourceType.DEFAULT:
89313
+ return await this.fetchDefaultConfig();
89314
+ case ConfigSourceType.REMOTE:
89315
+ if (!this.options.allowRemote) {
89316
+ throw new Error('Remote extends are disabled. Enable with --allow-remote-extends or remove VISOR_NO_REMOTE_EXTENDS environment variable.');
89317
+ }
89318
+ return await this.fetchRemoteConfig(source);
89319
+ case ConfigSourceType.LOCAL:
89320
+ return await this.fetchLocalConfig(source);
89321
+ default:
89322
+ throw new Error(`Unknown configuration source: ${source}`);
89323
+ }
89324
+ }
89325
+ finally {
89326
+ this.loadedConfigs.delete(normalizedSource);
89327
+ }
89328
+ }
89329
+ /**
89330
+ * Normalize source path/URL for comparison
89331
+ */
89332
+ normalizeSource(source) {
89333
+ const sourceType = this.getSourceType(source);
89334
+ switch (sourceType) {
89335
+ case ConfigSourceType.DEFAULT:
89336
+ return 'default';
89337
+ case ConfigSourceType.REMOTE:
89338
+ return source.toLowerCase();
89339
+ case ConfigSourceType.LOCAL:
89340
+ const basePath = this.options.baseDir || process.cwd();
89341
+ return path.resolve(basePath, source);
89342
+ default:
89343
+ return source;
89344
+ }
89345
+ }
89346
+ /**
89347
+ * Load configuration from local file system
89348
+ */
89349
+ async fetchLocalConfig(filePath) {
89350
+ const basePath = this.options.baseDir || process.cwd();
89351
+ const resolvedPath = path.resolve(basePath, filePath);
89352
+ // Validate against path traversal attacks
89353
+ this.validateLocalPath(resolvedPath);
89354
+ if (!fs.existsSync(resolvedPath)) {
89355
+ throw new Error(`Configuration file not found: ${resolvedPath}`);
89356
+ }
89357
+ try {
89358
+ const content = fs.readFileSync(resolvedPath, 'utf8');
89359
+ const config = yaml.load(content);
89360
+ if (!config || typeof config !== 'object') {
89361
+ throw new Error(`Invalid YAML in configuration file: ${resolvedPath}`);
89362
+ }
89363
+ // Update base directory for nested extends
89364
+ const previousBaseDir = this.options.baseDir;
89365
+ this.options.baseDir = path.dirname(resolvedPath);
89366
+ try {
89367
+ // Process extends if present
89368
+ if (config.extends) {
89369
+ const processedConfig = await this.processExtends(config);
89370
+ return processedConfig;
89371
+ }
89372
+ return config;
89373
+ }
89374
+ finally {
89375
+ // Restore previous base directory
89376
+ this.options.baseDir = previousBaseDir;
89377
+ }
89378
+ }
89379
+ catch (error) {
89380
+ if (error instanceof Error) {
89381
+ throw new Error(`Failed to load configuration from ${resolvedPath}: ${error.message}`);
89382
+ }
89383
+ throw error;
89384
+ }
89385
+ }
89386
+ /**
89387
+ * Fetch configuration from remote URL
89388
+ */
89389
+ async fetchRemoteConfig(url) {
89390
+ // Validate URL protocol
89391
+ if (!url.startsWith('http://') && !url.startsWith('https://')) {
89392
+ throw new Error(`Invalid URL: ${url}. Only HTTP and HTTPS protocols are supported.`);
89393
+ }
89394
+ // Validate against SSRF attacks
89395
+ this.validateRemoteURL(url);
89396
+ // Check cache
89397
+ const cacheEntry = this.cache.get(url);
89398
+ if (cacheEntry && Date.now() - cacheEntry.timestamp < cacheEntry.ttl) {
89399
+ console.log(`📦 Using cached configuration from: ${url}`);
89400
+ return cacheEntry.config;
89401
+ }
89402
+ console.log(`⬇️ Fetching remote configuration from: ${url}`);
89403
+ try {
89404
+ const controller = new AbortController();
89405
+ const timeoutId = setTimeout(() => controller.abort(), this.options.timeout || 30000);
89406
+ const response = await fetch(url, {
89407
+ signal: controller.signal,
89408
+ headers: {
89409
+ 'User-Agent': 'Visor/1.0',
89410
+ },
89411
+ });
89412
+ clearTimeout(timeoutId);
89413
+ if (!response.ok) {
89414
+ throw new Error(`Failed to fetch config: ${response.status} ${response.statusText}`);
89415
+ }
89416
+ const content = await response.text();
89417
+ const config = yaml.load(content);
89418
+ if (!config || typeof config !== 'object') {
89419
+ throw new Error(`Invalid YAML in remote configuration: ${url}`);
89420
+ }
89421
+ // Cache the configuration
89422
+ this.cache.set(url, {
89423
+ config,
89424
+ timestamp: Date.now(),
89425
+ ttl: this.options.cacheTTL || 5 * 60 * 1000,
89426
+ });
89427
+ // Process extends if present
89428
+ if (config.extends) {
89429
+ return await this.processExtends(config);
89430
+ }
89431
+ return config;
89432
+ }
89433
+ catch (error) {
89434
+ if (error instanceof Error) {
89435
+ if (error.name === 'AbortError') {
89436
+ throw new Error(`Timeout fetching configuration from ${url} (${this.options.timeout}ms)`);
89437
+ }
89438
+ throw new Error(`Failed to fetch remote configuration from ${url}: ${error.message}`);
89439
+ }
89440
+ throw error;
89441
+ }
89442
+ }
89443
+ /**
89444
+ * Load bundled default configuration
89445
+ */
89446
+ async fetchDefaultConfig() {
89447
+ // First try to find the bundled default config
89448
+ let packageRoot = this.findPackageRoot();
89449
+ if (!packageRoot) {
89450
+ // Fallback: try relative to current file
89451
+ packageRoot = path.resolve(__dirname, '..', '..');
89452
+ }
89453
+ const defaultConfigPath = path.join(packageRoot, 'defaults', '.visor.yaml');
89454
+ if (fs.existsSync(defaultConfigPath)) {
89455
+ console.log(`📦 Loading bundled default configuration`);
89456
+ const content = fs.readFileSync(defaultConfigPath, 'utf8');
89457
+ const config = yaml.load(content);
89458
+ if (!config || typeof config !== 'object') {
89459
+ throw new Error('Invalid default configuration');
89460
+ }
89461
+ // Default configs shouldn't have extends, but handle it just in case
89462
+ if (config.extends) {
89463
+ return await this.processExtends(config);
89464
+ }
89465
+ return config;
89466
+ }
89467
+ // Return minimal default if bundled config not found
89468
+ console.warn('⚠️ Bundled default configuration not found, using minimal defaults');
89469
+ return {
89470
+ version: '1.0',
89471
+ checks: {},
89472
+ output: {
89473
+ pr_comment: {
89474
+ format: 'markdown',
89475
+ group_by: 'check',
89476
+ collapse: true,
89477
+ },
89478
+ },
89479
+ };
89480
+ }
89481
+ /**
89482
+ * Process extends directive in a configuration
89483
+ */
89484
+ async processExtends(config) {
89485
+ if (!config.extends) {
89486
+ return config;
89487
+ }
89488
+ const extends_ = Array.isArray(config.extends) ? config.extends : [config.extends];
89489
+ // Remove extends from the config to avoid infinite recursion
89490
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
89491
+ const { extends: _extendsField, ...configWithoutExtends } = config;
89492
+ // Load all parent configurations
89493
+ const parentConfigs = [];
89494
+ for (const source of extends_) {
89495
+ const parentConfig = await this.fetchConfig(source, this.loadedConfigs.size);
89496
+ parentConfigs.push(parentConfig);
89497
+ }
89498
+ // Merge configurations (will be implemented in config-merger.ts)
89499
+ // For now, we'll import it dynamically
89500
+ const { ConfigMerger } = await Promise.resolve().then(() => __importStar(__nccwpck_require__(20730)));
89501
+ const merger = new ConfigMerger();
89502
+ // Merge all parent configs together first
89503
+ let mergedParents = {};
89504
+ for (const parentConfig of parentConfigs) {
89505
+ mergedParents = merger.merge(mergedParents, parentConfig);
89506
+ }
89507
+ // Then merge with the current config (child overrides parent)
89508
+ return merger.merge(mergedParents, configWithoutExtends);
89509
+ }
89510
+ /**
89511
+ * Find project root directory (for security validation)
89512
+ */
89513
+ findProjectRoot() {
89514
+ // Try to find git root first
89515
+ try {
89516
+ const { execSync } = __nccwpck_require__(35317);
89517
+ const gitRoot = execSync('git rev-parse --show-toplevel', { encoding: 'utf8' }).trim();
89518
+ if (gitRoot)
89519
+ return gitRoot;
89520
+ }
89521
+ catch {
89522
+ // Not a git repo, continue
89523
+ }
89524
+ // Fall back to finding package.json
89525
+ const packageRoot = this.findPackageRoot();
89526
+ if (packageRoot)
89527
+ return packageRoot;
89528
+ // Last resort: use current working directory
89529
+ return process.cwd();
89530
+ }
89531
+ /**
89532
+ * Validate remote URL against allowlist
89533
+ */
89534
+ validateRemoteURL(url) {
89535
+ // If allowlist is empty, allow all URLs (backward compatibility)
89536
+ const allowedPatterns = this.options.allowedRemotePatterns || [];
89537
+ if (allowedPatterns.length === 0) {
89538
+ return;
89539
+ }
89540
+ // Check if URL matches any allowed pattern
89541
+ const isAllowed = allowedPatterns.some(pattern => url.startsWith(pattern));
89542
+ if (!isAllowed) {
89543
+ throw new Error(`Security error: URL ${url} is not in the allowed list. Allowed patterns: ${allowedPatterns.join(', ')}`);
89544
+ }
89545
+ }
89546
+ /**
89547
+ * Validate local path against traversal attacks
89548
+ */
89549
+ validateLocalPath(resolvedPath) {
89550
+ const projectRoot = this.options.projectRoot || process.cwd();
89551
+ const normalizedPath = path.normalize(resolvedPath);
89552
+ const normalizedRoot = path.normalize(projectRoot);
89553
+ // Check if the resolved path is within the project root
89554
+ if (!normalizedPath.startsWith(normalizedRoot)) {
89555
+ throw new Error(`Security error: Path traversal detected. Cannot access files outside project root: ${projectRoot}`);
89556
+ }
89557
+ // Additional check for sensitive system files
89558
+ const sensitivePatterns = [
89559
+ '/etc/passwd',
89560
+ '/etc/shadow',
89561
+ '/.ssh/',
89562
+ '/.aws/',
89563
+ '/.env',
89564
+ '/private/',
89565
+ ];
89566
+ const lowerPath = normalizedPath.toLowerCase();
89567
+ for (const pattern of sensitivePatterns) {
89568
+ if (lowerPath.includes(pattern)) {
89569
+ throw new Error(`Security error: Cannot access potentially sensitive file: ${pattern}`);
89570
+ }
89571
+ }
89572
+ }
89573
+ /**
89574
+ * Find package root directory
89575
+ */
89576
+ findPackageRoot() {
89577
+ let currentDir = __dirname;
89578
+ const root = path.parse(currentDir).root;
89579
+ while (currentDir !== root) {
89580
+ const packageJsonPath = path.join(currentDir, 'package.json');
89581
+ if (fs.existsSync(packageJsonPath)) {
89582
+ try {
89583
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
89584
+ // Check if this is the Visor package
89585
+ if (packageJson.name === '@probelabs/visor') {
89586
+ return currentDir;
89587
+ }
89588
+ }
89589
+ catch {
89590
+ // Continue searching
89591
+ }
89592
+ }
89593
+ currentDir = path.dirname(currentDir);
89594
+ }
89595
+ return null;
89596
+ }
89597
+ /**
89598
+ * Clear the configuration cache
89599
+ */
89600
+ clearCache() {
89601
+ this.cache.clear();
89602
+ }
89603
+ /**
89604
+ * Reset the loaded configs tracking (for testing)
89605
+ */
89606
+ reset() {
89607
+ this.loadedConfigs.clear();
89608
+ this.clearCache();
89609
+ }
89610
+ }
89611
+ exports.ConfigLoader = ConfigLoader;
89612
+
89613
+
89614
+ /***/ }),
89615
+
89616
+ /***/ 20730:
89617
+ /***/ ((__unused_webpack_module, exports) => {
89618
+
89619
+ "use strict";
89620
+
89621
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
89622
+ exports.ConfigMerger = void 0;
89623
+ /**
89624
+ * Utility class for merging Visor configurations with proper override semantics
89625
+ */
89626
+ class ConfigMerger {
89627
+ /**
89628
+ * Merge two configurations with child overriding parent
89629
+ * @param parent - Base configuration
89630
+ * @param child - Configuration to merge on top
89631
+ * @returns Merged configuration
89632
+ */
89633
+ merge(parent, child) {
89634
+ // Start with a deep copy of parent
89635
+ const result = this.deepCopy(parent);
89636
+ // Merge simple properties (child overrides parent)
89637
+ if (child.version !== undefined)
89638
+ result.version = child.version;
89639
+ if (child.ai_model !== undefined)
89640
+ result.ai_model = child.ai_model;
89641
+ if (child.ai_provider !== undefined)
89642
+ result.ai_provider = child.ai_provider;
89643
+ if (child.max_parallelism !== undefined)
89644
+ result.max_parallelism = child.max_parallelism;
89645
+ if (child.fail_fast !== undefined)
89646
+ result.fail_fast = child.fail_fast;
89647
+ if (child.fail_if !== undefined)
89648
+ result.fail_if = child.fail_if;
89649
+ if (child.failure_conditions !== undefined)
89650
+ result.failure_conditions = child.failure_conditions;
89651
+ // Merge environment variables (deep merge)
89652
+ if (child.env) {
89653
+ result.env = this.mergeObjects(parent.env || {}, child.env);
89654
+ }
89655
+ // Merge output configuration (deep merge)
89656
+ if (child.output) {
89657
+ result.output = this.mergeOutputConfig(parent.output, child.output);
89658
+ }
89659
+ // Merge checks (special handling)
89660
+ if (child.checks) {
89661
+ result.checks = this.mergeChecks(parent.checks || {}, child.checks);
89662
+ }
89663
+ // Note: extends should not be in the final merged config
89664
+ // It's only used during the loading process
89665
+ return result;
89666
+ }
89667
+ /**
89668
+ * Deep copy an object
89669
+ */
89670
+ deepCopy(obj) {
89671
+ if (obj === null || obj === undefined) {
89672
+ return obj;
89673
+ }
89674
+ if (obj instanceof Date) {
89675
+ return new Date(obj.getTime());
89676
+ }
89677
+ if (obj instanceof Array) {
89678
+ const copy = [];
89679
+ for (const item of obj) {
89680
+ copy.push(this.deepCopy(item));
89681
+ }
89682
+ return copy;
89683
+ }
89684
+ if (obj instanceof Object) {
89685
+ const copy = {};
89686
+ for (const key in obj) {
89687
+ if (Object.prototype.hasOwnProperty.call(obj, key)) {
89688
+ copy[key] = this.deepCopy(obj[key]);
89689
+ }
89690
+ }
89691
+ return copy;
89692
+ }
89693
+ return obj;
89694
+ }
89695
+ /**
89696
+ * Merge two objects (child overrides parent)
89697
+ */
89698
+ mergeObjects(parent, child) {
89699
+ const result = { ...parent };
89700
+ for (const key in child) {
89701
+ if (Object.prototype.hasOwnProperty.call(child, key)) {
89702
+ const parentValue = parent[key];
89703
+ const childValue = child[key];
89704
+ if (childValue === null || childValue === undefined) {
89705
+ // null/undefined in child removes the key
89706
+ delete result[key];
89707
+ }
89708
+ else if (typeof parentValue === 'object' &&
89709
+ typeof childValue === 'object' &&
89710
+ !Array.isArray(parentValue) &&
89711
+ !Array.isArray(childValue) &&
89712
+ parentValue !== null &&
89713
+ childValue !== null) {
89714
+ // Deep merge objects
89715
+ result[key] = this.mergeObjects(parentValue, childValue);
89716
+ }
89717
+ else {
89718
+ // Child overrides parent (including arrays)
89719
+ result[key] = this.deepCopy(childValue);
89720
+ }
89721
+ }
89722
+ }
89723
+ return result;
89724
+ }
89725
+ /**
89726
+ * Merge output configurations
89727
+ */
89728
+ mergeOutputConfig(parent, child) {
89729
+ if (!child)
89730
+ return parent;
89731
+ if (!parent)
89732
+ return child;
89733
+ const result = this.deepCopy(parent);
89734
+ // Merge pr_comment
89735
+ if (child.pr_comment) {
89736
+ result.pr_comment = this.mergeObjects((parent.pr_comment || {}), child.pr_comment);
89737
+ }
89738
+ // Merge file_comment
89739
+ if (child.file_comment !== undefined) {
89740
+ if (child.file_comment === null) {
89741
+ delete result.file_comment;
89742
+ }
89743
+ else {
89744
+ result.file_comment = this.mergeObjects((parent.file_comment || {}), child.file_comment);
89745
+ }
89746
+ }
89747
+ // Merge github_checks
89748
+ if (child.github_checks !== undefined) {
89749
+ if (child.github_checks === null) {
89750
+ delete result.github_checks;
89751
+ }
89752
+ else {
89753
+ result.github_checks = this.mergeObjects((parent.github_checks || {}), child.github_checks);
89754
+ }
89755
+ }
89756
+ return result;
89757
+ }
89758
+ /**
89759
+ * Merge check configurations with special handling
89760
+ */
89761
+ mergeChecks(parent, child) {
89762
+ const result = {};
89763
+ // Start with all parent checks
89764
+ for (const [checkName, checkConfig] of Object.entries(parent)) {
89765
+ result[checkName] = this.deepCopy(checkConfig);
89766
+ }
89767
+ // Process child checks
89768
+ for (const [checkName, childConfig] of Object.entries(child)) {
89769
+ const parentConfig = parent[checkName];
89770
+ if (!parentConfig) {
89771
+ // New check - need to process appendPrompt even without parent
89772
+ const copiedConfig = this.deepCopy(childConfig);
89773
+ // Handle appendPrompt for new checks (convert to prompt)
89774
+ if (copiedConfig.appendPrompt !== undefined) {
89775
+ // If there's no parent, appendPrompt becomes the prompt
89776
+ if (!copiedConfig.prompt) {
89777
+ copiedConfig.prompt = copiedConfig.appendPrompt;
89778
+ }
89779
+ else {
89780
+ // If both prompt and appendPrompt exist in child, append them
89781
+ copiedConfig.prompt = copiedConfig.prompt + '\n\n' + copiedConfig.appendPrompt;
89782
+ }
89783
+ // Remove appendPrompt from final config
89784
+ delete copiedConfig.appendPrompt;
89785
+ }
89786
+ result[checkName] = copiedConfig;
89787
+ }
89788
+ else {
89789
+ // Merge existing check
89790
+ result[checkName] = this.mergeCheckConfig(parentConfig, childConfig);
89791
+ }
89792
+ }
89793
+ return result;
89794
+ }
89795
+ /**
89796
+ * Merge individual check configurations
89797
+ */
89798
+ mergeCheckConfig(parent, child) {
89799
+ const result = this.deepCopy(parent);
89800
+ // Simple properties (child overrides parent)
89801
+ if (child.type !== undefined)
89802
+ result.type = child.type;
89803
+ if (child.prompt !== undefined)
89804
+ result.prompt = child.prompt;
89805
+ // Handle appendPrompt - append to existing prompt
89806
+ if (child.appendPrompt !== undefined) {
89807
+ if (result.prompt) {
89808
+ // Append with a newline separator if parent has a prompt
89809
+ result.prompt = result.prompt + '\n\n' + child.appendPrompt;
89810
+ }
89811
+ else {
89812
+ // If no parent prompt, appendPrompt becomes the prompt
89813
+ result.prompt = child.appendPrompt;
89814
+ }
89815
+ // Don't carry forward appendPrompt to avoid re-appending
89816
+ delete result.appendPrompt;
89817
+ }
89818
+ if (child.exec !== undefined)
89819
+ result.exec = child.exec;
89820
+ if (child.stdin !== undefined)
89821
+ result.stdin = child.stdin;
89822
+ if (child.url !== undefined)
89823
+ result.url = child.url;
89824
+ if (child.focus !== undefined)
89825
+ result.focus = child.focus;
89826
+ if (child.command !== undefined)
89827
+ result.command = child.command;
89828
+ if (child.ai_model !== undefined)
89829
+ result.ai_model = child.ai_model;
89830
+ if (child.ai_provider !== undefined)
89831
+ result.ai_provider = child.ai_provider;
89832
+ if (child.group !== undefined)
89833
+ result.group = child.group;
89834
+ if (child.schema !== undefined)
89835
+ result.schema = child.schema;
89836
+ if (child.if !== undefined)
89837
+ result.if = child.if;
89838
+ if (child.reuse_ai_session !== undefined)
89839
+ result.reuse_ai_session = child.reuse_ai_session;
89840
+ if (child.fail_if !== undefined)
89841
+ result.fail_if = child.fail_if;
89842
+ if (child.failure_conditions !== undefined)
89843
+ result.failure_conditions = child.failure_conditions;
89844
+ // Special handling for 'on' array
89845
+ if (child.on !== undefined) {
89846
+ if (Array.isArray(child.on) && child.on.length === 0) {
89847
+ // Empty array disables the check
89848
+ result.on = [];
89849
+ }
89850
+ else {
89851
+ // Replace parent's on array
89852
+ result.on = [...child.on];
89853
+ }
89854
+ }
89855
+ // Arrays that get replaced (not concatenated)
89856
+ if (child.triggers !== undefined) {
89857
+ result.triggers = child.triggers ? [...child.triggers] : undefined;
89858
+ }
89859
+ if (child.depends_on !== undefined) {
89860
+ result.depends_on = child.depends_on ? [...child.depends_on] : undefined;
89861
+ }
89862
+ // Deep merge objects
89863
+ if (child.env) {
89864
+ result.env = this.mergeObjects((parent.env || {}), child.env);
89865
+ }
89866
+ if (child.ai) {
89867
+ result.ai = this.mergeObjects((parent.ai || {}), child.ai);
89868
+ }
89869
+ if (child.template) {
89870
+ result.template = this.mergeObjects((parent.template || {}), child.template);
89871
+ }
89872
+ return result;
89873
+ }
89874
+ /**
89875
+ * Check if a check is disabled (has empty 'on' array)
89876
+ */
89877
+ isCheckDisabled(check) {
89878
+ return Array.isArray(check.on) && check.on.length === 0;
89879
+ }
89880
+ /**
89881
+ * Remove disabled checks from the configuration
89882
+ */
89883
+ removeDisabledChecks(config) {
89884
+ if (!config.checks)
89885
+ return config;
89886
+ const result = this.deepCopy(config);
89887
+ const enabledChecks = {};
89888
+ for (const [checkName, checkConfig] of Object.entries(result.checks)) {
89889
+ if (!this.isCheckDisabled(checkConfig)) {
89890
+ enabledChecks[checkName] = checkConfig;
89891
+ }
89892
+ else {
89893
+ console.log(`ℹ️ Check '${checkName}' is disabled (empty 'on' array)`);
89894
+ }
89895
+ }
89896
+ result.checks = enabledChecks;
89897
+ return result;
89898
+ }
89899
+ }
89900
+ exports.ConfigMerger = ConfigMerger;
89901
+
89902
+
89424
89903
  /***/ }),
89425
89904
 
89426
89905
  /***/ 58749:
@@ -109824,7 +110303,8 @@ ${fixedContent}
109824
110303
  modifiedLine = modifiedLine.replace(/\[([^\]"]*\([^\]"]*)\]/g, (match, content) => {
109825
110304
  if (!content.trim().startsWith('"') || !content.trim().endsWith('"')) {
109826
110305
  wasFixed = true;
109827
- return `["${content}"]`;
110306
+ const safeContent = content.replace(/"/g, "'");
110307
+ return `["${safeContent}"]`;
109828
110308
  }
109829
110309
  return match;
109830
110310
  });
@@ -109833,7 +110313,8 @@ ${fixedContent}
109833
110313
  modifiedLine = modifiedLine.replace(/\{([^{}"]*\([^{}"]*)\}/g, (match, content) => {
109834
110314
  if (!content.trim().startsWith('"') || !content.trim().endsWith('"')) {
109835
110315
  wasFixed = true;
109836
- return `{"${content}"}`;
110316
+ const safeContent = content.replace(/"/g, "'");
110317
+ return `{"${safeContent}"}`;
109837
110318
  }
109838
110319
  return match;
109839
110320
  });
@@ -109904,6 +110385,8 @@ ${fixedContent}
109904
110385
  };
109905
110386
  }
109906
110387
  }
110388
+ const { diagrams: updatedDiagrams } = extractMermaidFromMarkdown(fixedResponse);
110389
+ const updatedValidation = await validateMermaidResponse(fixedResponse);
109907
110390
  if (debug) {
109908
110391
  const stillInvalidAfterHtml2 = updatedValidation?.diagrams?.filter((d) => !d.isValid)?.length || invalidCount;
109909
110392
  console.log(`[DEBUG] Mermaid validation: ${stillInvalidAfterHtml2} diagrams still invalid after HTML entity decoding, starting AI fixing...`);
@@ -109920,8 +110403,6 @@ ${fixedContent}
109920
110403
  debug,
109921
110404
  tracer
109922
110405
  });
109923
- const { diagrams: updatedDiagrams } = extractMermaidFromMarkdown(fixedResponse);
109924
- const updatedValidation = await validateMermaidResponse(fixedResponse);
109925
110406
  const stillInvalidDiagrams = updatedValidation.diagrams.map((result, index) => ({ ...result, originalIndex: index })).filter((result) => !result.isValid).reverse();
109926
110407
  if (debug) {
109927
110408
  console.log(`[DEBUG] Mermaid validation: Found ${stillInvalidDiagrams.length} diagrams requiring AI fixing`);