@probelabs/visor 0.1.79 → 0.1.81

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,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- process.env.VISOR_VERSION = '0.1.79';
2
+ process.env.VISOR_VERSION = '0.1.81';
3
3
  process.env.PROBE_VERSION = '0.6.0-rc122';
4
4
  /******/ (() => { // webpackBootstrap
5
5
  /******/ var __webpack_modules__ = ({
@@ -102520,21 +102520,30 @@ class CheckExecutionEngine {
102520
102520
  dependencyResults.set(depId, depResult);
102521
102521
  }
102522
102522
  }
102523
- // If any direct dependency failed (errors/critical or */error ruleIds), skip this check
102523
+ // If any direct dependency failed or was skipped, skip this check
102524
102524
  const directDeps = checkConfig.depends_on || [];
102525
102525
  const failedDeps = [];
102526
102526
  for (const depId of directDeps) {
102527
102527
  const depRes = results.get(depId);
102528
102528
  if (!depRes)
102529
102529
  continue;
102530
- // Only treat command provider execution/transform failures as dependency-fatal
102531
- const hasFatalCommandFailure = (depRes.issues || []).some(issue => {
102530
+ // Check if dependency was skipped
102531
+ const wasSkipped = (depRes.issues || []).some(issue => {
102532
102532
  const id = issue.ruleId || '';
102533
- return (id.endsWith('/command/execution_error') ||
102533
+ return id.endsWith('/__skipped');
102534
+ });
102535
+ // Check for fatal failures: command provider execution/transform failures and forEach iteration errors
102536
+ const hasFatalFailure = (depRes.issues || []).some(issue => {
102537
+ const id = issue.ruleId || '';
102538
+ return (id === 'command/execution_error' ||
102539
+ id.endsWith('/command/execution_error') ||
102540
+ id === 'command/transform_js_error' ||
102534
102541
  id.endsWith('/command/transform_js_error') ||
102535
- id.endsWith('/command/transform_error'));
102542
+ id === 'command/transform_error' ||
102543
+ id.endsWith('/command/transform_error') ||
102544
+ id.endsWith('/forEach/iteration_error'));
102536
102545
  });
102537
- if (hasFatalCommandFailure)
102546
+ if (wasSkipped || hasFatalFailure)
102538
102547
  failedDeps.push(depId);
102539
102548
  }
102540
102549
  if (failedDeps.length > 0) {
@@ -102606,8 +102615,8 @@ class CheckExecutionEngine {
102606
102615
  if (debug) {
102607
102616
  log(`🔄 Debug: Check "${checkName}" depends on forEach check "${forEachParentName}", executing ${forEachItems.length} times`);
102608
102617
  }
102609
- // Log forEach processing start
102610
- logger_1.logger.info(` Processing ${forEachItems.length} items...`);
102618
+ // Log forEach processing start (non-debug)
102619
+ logger_1.logger.info(` forEach: processing ${forEachItems.length} items from "${forEachParentName}"...`);
102611
102620
  const allIssues = [];
102612
102621
  const allOutputs = [];
102613
102622
  const aggregatedContents = [];
@@ -102678,8 +102687,19 @@ class CheckExecutionEngine {
102678
102687
  parent: forEachParentName,
102679
102688
  });
102680
102689
  // Record iteration completion
102690
+ // Check if this iteration had fatal errors
102691
+ const hadFatalError = (itemResult.issues || []).some(issue => {
102692
+ const id = issue.ruleId || '';
102693
+ return (id === 'command/execution_error' ||
102694
+ id.endsWith('/command/execution_error') ||
102695
+ id === 'command/transform_js_error' ||
102696
+ id.endsWith('/command/transform_js_error') ||
102697
+ id === 'command/transform_error' ||
102698
+ id.endsWith('/command/transform_error'));
102699
+ });
102681
102700
  const iterationDuration = (Date.now() - iterationStart) / 1000;
102682
- this.recordIterationComplete(checkName, iterationStart, true, itemResult.issues || [], itemResult.output);
102701
+ this.recordIterationComplete(checkName, iterationStart, !hadFatalError, // Success if no fatal errors
102702
+ itemResult.issues || [], itemResult.output);
102683
102703
  // Log iteration progress
102684
102704
  logger_1.logger.info(` ✔ ${itemIndex + 1}/${forEachItems.length} (${iterationDuration.toFixed(1)}s)`);
102685
102705
  return { index: itemIndex, itemResult };
@@ -102691,7 +102711,22 @@ class CheckExecutionEngine {
102691
102711
  const forEachResults = await this.executeWithLimitedParallelism(itemTasks, forEachConcurrency, false);
102692
102712
  for (const result of forEachResults) {
102693
102713
  if (result.status === 'rejected') {
102694
- throw result.reason;
102714
+ // Instead of throwing, record the failure and continue with other iterations
102715
+ const error = result.reason;
102716
+ const errorMessage = error instanceof Error ? error.message : String(error);
102717
+ // Create an error issue for this failed iteration
102718
+ allIssues.push({
102719
+ ruleId: `${checkName}/forEach/iteration_error`,
102720
+ severity: 'error',
102721
+ category: 'logic',
102722
+ message: `forEach iteration failed: ${errorMessage}`,
102723
+ file: '',
102724
+ line: 0,
102725
+ });
102726
+ if (debug) {
102727
+ log(`🔄 Debug: forEach iteration for check "${checkName}" failed: ${errorMessage}`);
102728
+ }
102729
+ continue;
102695
102730
  }
102696
102731
  // Skip results from skipped items (those that failed if condition)
102697
102732
  if (result.value.skipped) {
@@ -102749,7 +102784,18 @@ class CheckExecutionEngine {
102749
102784
  // Execute with retry/routing semantics
102750
102785
  finalResult = await this.executeWithRouting(checkName, checkConfig, provider, providerConfig, prInfo, dependencyResults, sessionInfo, config, dependencyGraph, debug, results);
102751
102786
  // Record normal (non-forEach) execution
102752
- this.recordIterationComplete(checkName, checkStartTime, true, finalResult.issues || [], finalResult.output);
102787
+ // Check if this check had fatal errors
102788
+ const hadFatalError = (finalResult.issues || []).some(issue => {
102789
+ const id = issue.ruleId || '';
102790
+ return (id === 'command/execution_error' ||
102791
+ id.endsWith('/command/execution_error') ||
102792
+ id === 'command/transform_js_error' ||
102793
+ id.endsWith('/command/transform_js_error') ||
102794
+ id === 'command/transform_error' ||
102795
+ id.endsWith('/command/transform_error'));
102796
+ });
102797
+ this.recordIterationComplete(checkName, checkStartTime, !hadFatalError, // Success if no fatal errors
102798
+ finalResult.issues || [], finalResult.output);
102753
102799
  if (checkConfig.forEach) {
102754
102800
  try {
102755
102801
  const finalResultWithOutput = finalResult;
@@ -102830,11 +102876,24 @@ class CheckExecutionEngine {
102830
102876
  const result = levelResults[i];
102831
102877
  const checkConfig = config.checks[checkName];
102832
102878
  if (result.status === 'fulfilled' && result.value.result && !result.value.error) {
102833
- // Skip storing results for skipped checks (they should not appear in outputs)
102879
+ // For skipped checks, store a marker so dependent checks can detect the skip
102834
102880
  if (result.value.skipped) {
102835
102881
  if (debug) {
102836
- log(`🔧 Debug: Not storing result for skipped check "${checkName}"`);
102882
+ log(`🔧 Debug: Storing skip marker for skipped check "${checkName}"`);
102837
102883
  }
102884
+ // Store a special marker result with a skip issue so dependencies can detect it
102885
+ results.set(checkName, {
102886
+ issues: [
102887
+ {
102888
+ ruleId: `${checkName}/__skipped`,
102889
+ severity: 'info',
102890
+ category: 'logic',
102891
+ message: 'Check was skipped',
102892
+ file: '',
102893
+ line: 0,
102894
+ },
102895
+ ],
102896
+ });
102838
102897
  continue;
102839
102898
  }
102840
102899
  const reviewResult = result.value.result;
@@ -102864,6 +102923,8 @@ class CheckExecutionEngine {
102864
102923
  else {
102865
102924
  normalizedOutput = [rawOutput];
102866
102925
  }
102926
+ // Log forEach items found (non-debug)
102927
+ logger_1.logger.info(` Found ${normalizedOutput.length} items for forEach iteration`);
102867
102928
  try {
102868
102929
  const preview = JSON.stringify(normalizedOutput);
102869
102930
  logger_1.logger.debug(`🔧 Debug: Check "${checkName}" forEach output: ${preview?.slice(0, 200) || '(empty)'}`);
@@ -102928,19 +102989,6 @@ class CheckExecutionEngine {
102928
102989
  }
102929
102990
  }
102930
102991
  }
102931
- // Build and log final execution summary
102932
- const executionStatistics = this.buildExecutionStatistics();
102933
- // Show detailed summary table (only if logFn outputs to console)
102934
- // Skip when output format is JSON/SARIF to avoid polluting structured output
102935
- // Check if logFn is console.log (not a no-op or console.error)
102936
- if (logFn === console.log) {
102937
- this.logExecutionSummary(executionStatistics);
102938
- }
102939
- // Add warning if execution stopped early
102940
- if (shouldStopExecution) {
102941
- logger_1.logger.info('');
102942
- logger_1.logger.warn(`⚠️ Execution stopped early due to fail-fast`);
102943
- }
102944
102992
  if (debug) {
102945
102993
  if (shouldStopExecution) {
102946
102994
  log(`🛑 Execution stopped early due to fail-fast after processing ${results.size} of ${checks.length} checks`);
@@ -102949,7 +102997,7 @@ class CheckExecutionEngine {
102949
102997
  log(`✅ Dependency-aware execution completed successfully for all ${results.size} checks`);
102950
102998
  }
102951
102999
  }
102952
- // Cleanup sessions after execution
103000
+ // Cleanup sessions BEFORE printing summary to avoid mixing debug logs with table output
102953
103001
  if (sessionIds.size > 0 && debug) {
102954
103002
  log(`🧹 Cleaning up ${sessionIds.size} AI sessions...`);
102955
103003
  for (const [checkName, sessionId] of sessionIds) {
@@ -102962,6 +103010,19 @@ class CheckExecutionEngine {
102962
103010
  }
102963
103011
  }
102964
103012
  }
103013
+ // Build and log final execution summary
103014
+ const executionStatistics = this.buildExecutionStatistics();
103015
+ // Show detailed summary table (only if logFn outputs to console)
103016
+ // Skip when output format is JSON/SARIF to avoid polluting structured output
103017
+ // Check if logFn is console.log (not a no-op or console.error)
103018
+ if (logFn === console.log) {
103019
+ this.logExecutionSummary(executionStatistics);
103020
+ }
103021
+ // Add warning if execution stopped early
103022
+ if (shouldStopExecution) {
103023
+ logger_1.logger.info('');
103024
+ logger_1.logger.warn(`⚠️ Execution stopped early due to fail-fast`);
103025
+ }
102965
103026
  // Aggregate all results
102966
103027
  return this.aggregateDependencyAwareResults(results, dependencyGraph, debug, shouldStopExecution);
102967
103028
  }
@@ -103166,7 +103227,9 @@ class CheckExecutionEngine {
103166
103227
  debugInfo.push(`✅ Check "${checkName}" completed: ${(result.issues || []).length} issues found (level ${executionGroup.level})`);
103167
103228
  }
103168
103229
  // Issues are already prefixed and enriched with group/schema info
103169
- aggregatedIssues.push(...(result.issues || []));
103230
+ // Filter out internal __skipped markers
103231
+ const nonInternalIssues = (result.issues || []).filter(issue => !issue.ruleId?.endsWith('/__skipped'));
103232
+ aggregatedIssues.push(...nonInternalIssues);
103170
103233
  const resultSummary = result;
103171
103234
  const resultContent = resultSummary.content;
103172
103235
  if (typeof resultContent === 'string' && resultContent.trim()) {
@@ -104514,6 +104577,13 @@ async function main() {
104514
104577
  const hasRepositoryError = allResults.some((result) => {
104515
104578
  return result.content.includes('Not a git repository');
104516
104579
  });
104580
+ // Cleanup AI sessions before exit to prevent process hanging
104581
+ const { SessionRegistry } = await Promise.resolve().then(() => __importStar(__nccwpck_require__(46059)));
104582
+ const sessionRegistry = SessionRegistry.getInstance();
104583
+ if (sessionRegistry.getActiveSessionIds().length > 0) {
104584
+ logger_1.logger.debug(`🧹 Cleaning up ${sessionRegistry.getActiveSessionIds().length} active AI sessions...`);
104585
+ sessionRegistry.clearAllSessions();
104586
+ }
104517
104587
  if (criticalCount > 0 || hasRepositoryError) {
104518
104588
  process.exit(1);
104519
104589
  }
@@ -108970,6 +109040,15 @@ async function run() {
108970
109040
  (0, core_1.setFailed)(error instanceof Error ? error.message : 'Unknown error');
108971
109041
  }
108972
109042
  }
109043
+ finally {
109044
+ // Cleanup AI sessions before GitHub Action exits to prevent process hanging
109045
+ const { SessionRegistry } = await Promise.resolve().then(() => __importStar(__nccwpck_require__(46059)));
109046
+ const sessionRegistry = SessionRegistry.getInstance();
109047
+ if (sessionRegistry.getActiveSessionIds().length > 0) {
109048
+ console.log(`🧹 Cleaning up ${sessionRegistry.getActiveSessionIds().length} active AI sessions...`);
109049
+ sessionRegistry.clearAllSessions();
109050
+ }
109051
+ }
108973
109052
  }
108974
109053
  function mapGitHubEventToTrigger(eventName, action) {
108975
109054
  if (!eventName)
@@ -111193,7 +111272,6 @@ const issue_filter_1 = __nccwpck_require__(36879);
111193
111272
  const liquid_extensions_1 = __nccwpck_require__(33042);
111194
111273
  const promises_1 = __importDefault(__nccwpck_require__(91943));
111195
111274
  const path_1 = __importDefault(__nccwpck_require__(16928));
111196
- const claude_code_types_1 = __nccwpck_require__(21710);
111197
111275
  /**
111198
111276
  * AI-powered check provider using probe agent
111199
111277
  */
@@ -111520,53 +111598,6 @@ class AICheckProvider extends check_provider_interface_1.CheckProvider {
111520
111598
  throw new Error(`Failed to render prompt template: ${error instanceof Error ? error.message : 'Unknown error'}`);
111521
111599
  }
111522
111600
  }
111523
- /**
111524
- * Setup MCP tools based on AI configuration
111525
- */
111526
- async setupMcpTools(aiConfig) {
111527
- const tools = [];
111528
- // Setup custom MCP servers if configured
111529
- if (aiConfig.mcpServers) {
111530
- try {
111531
- // Import MCP SDK for custom server creation using safe import
111532
- const mcpModule = await (0, claude_code_types_1.safeImport)('@modelcontextprotocol/sdk');
111533
- if (!mcpModule) {
111534
- console.warn('@modelcontextprotocol/sdk package not found. MCP servers disabled.');
111535
- return tools;
111536
- }
111537
- const createSdkMcpServer = mcpModule.createSdkMcpServer || mcpModule.default?.createSdkMcpServer;
111538
- if (typeof createSdkMcpServer === 'function') {
111539
- for (const [serverName, serverConfig] of Object.entries(aiConfig.mcpServers)) {
111540
- try {
111541
- // Create MCP server instance
111542
- const server = await createSdkMcpServer({
111543
- name: serverName,
111544
- command: serverConfig.command,
111545
- args: serverConfig.args || [],
111546
- env: { ...process.env, ...serverConfig.env },
111547
- });
111548
- // Add server tools to available tools
111549
- const serverTools = (await server.listTools());
111550
- tools.push(...serverTools.map(tool => ({
111551
- name: tool.name,
111552
- server: serverName,
111553
- })));
111554
- }
111555
- catch (serverError) {
111556
- console.warn(`Failed to setup MCP server ${serverName}: ${serverError instanceof Error ? serverError.message : 'Unknown error'}`);
111557
- }
111558
- }
111559
- }
111560
- else {
111561
- console.warn('createSdkMcpServer function not found in @modelcontextprotocol/sdk. MCP servers disabled.');
111562
- }
111563
- }
111564
- catch (error) {
111565
- console.warn(`Failed to import MCP SDK: ${error instanceof Error ? error.message : 'Unknown error'}. MCP servers disabled.`);
111566
- }
111567
- }
111568
- return tools;
111569
- }
111570
111601
  async execute(prInfo, config, _dependencyResults, sessionInfo) {
111571
111602
  // Apply environment configuration if present
111572
111603
  if (config.env) {
@@ -111630,15 +111661,12 @@ class AICheckProvider extends check_provider_interface_1.CheckProvider {
111630
111661
  if (config.ai?.mcpServers) {
111631
111662
  Object.assign(mcpServers, config.ai.mcpServers);
111632
111663
  }
111633
- // Setup MCP tools if any servers are configured
111664
+ // Pass MCP server config directly to AI service
111634
111665
  if (Object.keys(mcpServers).length > 0) {
111635
- // Pass raw server config to AI service so it can enable MCP in ProbeAgent
111666
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
111636
111667
  aiConfig.mcpServers = mcpServers;
111637
- // Optional: attempt to enumerate tools for debug visibility (not required for functionality)
111638
- const mcpConfig = { mcpServers };
111639
- const mcpTools = await this.setupMcpTools(mcpConfig);
111640
111668
  if (aiConfig.debug) {
111641
- console.error(`🔧 Debug: AI check MCP configured with ${Object.keys(mcpServers).length} servers; discovered ${mcpTools.length} tools`);
111669
+ console.error(`🔧 Debug: AI check MCP configured with ${Object.keys(mcpServers).length} servers`);
111642
111670
  }
111643
111671
  }
111644
111672
  // Process prompt with Liquid templates and file loading
@@ -112052,59 +112080,6 @@ class ClaudeCodeCheckProvider extends check_provider_interface_1.CheckProvider {
112052
112080
  throw new Error(`Failed to initialize Claude Code SDK: ${error instanceof Error ? error.message : 'Unknown error'}`);
112053
112081
  }
112054
112082
  }
112055
- /**
112056
- * Setup MCP tools based on configuration
112057
- */
112058
- async setupMcpTools(config) {
112059
- const tools = [];
112060
- // Add allowed tools
112061
- if (config.allowedTools) {
112062
- for (const toolName of config.allowedTools) {
112063
- tools.push({ name: toolName });
112064
- }
112065
- }
112066
- // Setup custom MCP servers if configured
112067
- if (config.mcpServers) {
112068
- try {
112069
- // Import MCP SDK for custom server creation using safe import
112070
- const mcpModule = await (0, claude_code_types_1.safeImport)('@modelcontextprotocol/sdk');
112071
- if (!mcpModule) {
112072
- console.warn('@modelcontextprotocol/sdk package not found. MCP servers disabled.');
112073
- return tools;
112074
- }
112075
- const createSdkMcpServer = mcpModule.createSdkMcpServer || mcpModule.default?.createSdkMcpServer;
112076
- if (typeof createSdkMcpServer === 'function') {
112077
- for (const [serverName, serverConfig] of Object.entries(config.mcpServers)) {
112078
- try {
112079
- // Create MCP server instance
112080
- const server = await createSdkMcpServer({
112081
- name: serverName,
112082
- command: serverConfig.command,
112083
- args: serverConfig.args || [],
112084
- env: { ...process.env, ...serverConfig.env },
112085
- });
112086
- // Add server tools to available tools
112087
- const serverTools = (await server.listTools());
112088
- tools.push(...serverTools.map(tool => ({
112089
- name: tool.name,
112090
- server: serverName,
112091
- })));
112092
- }
112093
- catch (serverError) {
112094
- console.warn(`Failed to setup MCP server ${serverName}: ${serverError instanceof Error ? serverError.message : 'Unknown error'}`);
112095
- }
112096
- }
112097
- }
112098
- else {
112099
- console.warn('createSdkMcpServer function not found in @modelcontextprotocol/sdk. MCP servers disabled.');
112100
- }
112101
- }
112102
- catch (error) {
112103
- console.warn(`Failed to import MCP SDK: ${error instanceof Error ? error.message : 'Unknown error'}. MCP servers disabled.`);
112104
- }
112105
- }
112106
- return tools;
112107
- }
112108
112083
  /**
112109
112084
  * Group files by their file extension for template context
112110
112085
  */
@@ -112369,16 +112344,22 @@ class ClaudeCodeCheckProvider extends check_provider_interface_1.CheckProvider {
112369
112344
  try {
112370
112345
  // Initialize Claude Code client
112371
112346
  const client = await this.initializeClaudeCodeClient();
112372
- // Setup MCP tools
112373
- const tools = await this.setupMcpTools(claudeCodeConfig);
112374
- // Prepare query object
112347
+ // Prepare query object with MCP servers passed directly to SDK
112375
112348
  const query = {
112376
112349
  query: processedPrompt,
112377
- tools: tools.length > 0 ? tools : undefined,
112378
112350
  maxTurns: claudeCodeConfig.maxTurns || 5,
112379
112351
  systemPrompt: claudeCodeConfig.systemPrompt,
112380
112352
  subagent: claudeCodeConfig.subagent,
112381
112353
  };
112354
+ // Add allowed tools if specified
112355
+ if (claudeCodeConfig.allowedTools && claudeCodeConfig.allowedTools.length > 0) {
112356
+ query.tools = claudeCodeConfig.allowedTools.map(name => ({ name }));
112357
+ }
112358
+ // Pass MCP servers directly to the SDK - let it handle spawning and tool discovery
112359
+ if (claudeCodeConfig.mcpServers && Object.keys(claudeCodeConfig.mcpServers).length > 0) {
112360
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
112361
+ query.mcpServers = claudeCodeConfig.mcpServers;
112362
+ }
112382
112363
  // Execute query with Claude Code
112383
112364
  let response;
112384
112365
  if (sessionInfo?.reuseSession && sessionInfo.parentSessionId) {
@@ -112412,7 +112393,6 @@ class ClaudeCodeCheckProvider extends check_provider_interface_1.CheckProvider {
112412
112393
  sessionId: response.session_id,
112413
112394
  turnCount: response.turn_count,
112414
112395
  usage: response.usage,
112415
- toolsUsed: tools.map(t => t.name),
112416
112396
  };
112417
112397
  // Apply issue suppression filtering
112418
112398
  const suppressionEnabled = config.suppressionEnabled !== false;
@@ -112708,8 +112688,22 @@ class CommandCheckProvider extends check_provider_interface_1.CheckProvider {
112708
112688
  output = parsed;
112709
112689
  }
112710
112690
  catch {
112711
- // If not JSON, keep as string
112712
- output = rawOutput;
112691
+ // Try to extract JSON from the end of output (for commands with debug logs)
112692
+ const extracted = this.extractJsonFromEnd(rawOutput);
112693
+ if (extracted) {
112694
+ try {
112695
+ output = JSON.parse(extracted);
112696
+ logger_1.logger.debug(`🔧 Debug: Extracted and parsed JSON from end of output (${extracted.length} chars from ${rawOutput.length} total)`);
112697
+ }
112698
+ catch {
112699
+ // Extraction found something but it's not valid JSON
112700
+ output = rawOutput;
112701
+ }
112702
+ }
112703
+ else {
112704
+ // Not JSON, keep as string
112705
+ output = rawOutput;
112706
+ }
112713
112707
  }
112714
112708
  // Apply transform if specified (Liquid or JavaScript)
112715
112709
  let finalOutput = output;
@@ -112945,6 +112939,7 @@ class CommandCheckProvider extends check_provider_interface_1.CheckProvider {
112945
112939
  * - If it's a JSON string, expose parsed properties via Proxy (e.g., value.key)
112946
112940
  * - When coerced to string (toString/valueOf/Symbol.toPrimitive), return the original raw string
112947
112941
  * - If parsing fails or value is not a string, return the value unchanged
112942
+ * - Attempts to extract JSON from the end of the output if full parse fails
112948
112943
  */
112949
112944
  makeJsonSmart(value) {
112950
112945
  if (typeof value !== 'string') {
@@ -112952,12 +112947,28 @@ class CommandCheckProvider extends check_provider_interface_1.CheckProvider {
112952
112947
  }
112953
112948
  const raw = value;
112954
112949
  let parsed;
112950
+ // First try: parse the entire string as JSON
112955
112951
  try {
112956
112952
  parsed = JSON.parse(raw);
112957
112953
  }
112958
112954
  catch {
112959
- // Not JSON, return original string
112960
- return raw;
112955
+ // Second try: extract JSON from the end of the output
112956
+ // Look for { or [ at the start of a line and take everything after it
112957
+ const jsonMatch = this.extractJsonFromEnd(raw);
112958
+ if (jsonMatch) {
112959
+ try {
112960
+ parsed = JSON.parse(jsonMatch);
112961
+ logger_1.logger.debug(`🔧 Debug: Extracted JSON from end of output (${jsonMatch.length} chars from ${raw.length} total)`);
112962
+ }
112963
+ catch {
112964
+ // Not valid JSON even after extraction, return original string
112965
+ return raw;
112966
+ }
112967
+ }
112968
+ else {
112969
+ // Not JSON, return original string
112970
+ return raw;
112971
+ }
112961
112972
  }
112962
112973
  // Use a boxed string so string methods still work via Proxy fallback
112963
112974
  const boxed = new String(raw);
@@ -113010,6 +113021,30 @@ class CommandCheckProvider extends check_provider_interface_1.CheckProvider {
113010
113021
  };
113011
113022
  return new Proxy(boxed, handler);
113012
113023
  }
113024
+ /**
113025
+ * Extract JSON from the end of a string that may contain logs/debug output
113026
+ * Looks for the last occurrence of { or [ and tries to parse from there
113027
+ */
113028
+ extractJsonFromEnd(text) {
113029
+ // Strategy: Find the last line that starts with { or [
113030
+ // Then try to parse from that point to the end
113031
+ const lines = text.split('\n');
113032
+ // Search backwards for a line starting with { or [
113033
+ for (let i = lines.length - 1; i >= 0; i--) {
113034
+ const trimmed = lines[i].trim();
113035
+ if (trimmed.startsWith('{') || trimmed.startsWith('[')) {
113036
+ // Found potential JSON start - take everything from here to the end
113037
+ const candidate = lines.slice(i).join('\n');
113038
+ // Quick validation: does it look like valid JSON structure?
113039
+ const trimmedCandidate = candidate.trim();
113040
+ if ((trimmedCandidate.startsWith('{') && trimmedCandidate.endsWith('}')) ||
113041
+ (trimmedCandidate.startsWith('[') && trimmedCandidate.endsWith(']'))) {
113042
+ return trimmedCandidate;
113043
+ }
113044
+ }
113045
+ }
113046
+ return null;
113047
+ }
113013
113048
  /**
113014
113049
  * Recursively apply JSON-smart wrapper to outputs object values
113015
113050
  */
@@ -114404,7 +114439,11 @@ exports.SessionRegistry = void 0;
114404
114439
  class SessionRegistry {
114405
114440
  static instance;
114406
114441
  sessions = new Map();
114407
- constructor() { }
114442
+ exitHandlerRegistered = false;
114443
+ constructor() {
114444
+ // Register process exit handlers to cleanup sessions
114445
+ this.registerExitHandlers();
114446
+ }
114408
114447
  /**
114409
114448
  * Get the singleton instance of SessionRegistry
114410
114449
  */
@@ -114437,7 +114476,19 @@ class SessionRegistry {
114437
114476
  unregisterSession(sessionId) {
114438
114477
  if (this.sessions.has(sessionId)) {
114439
114478
  console.error(`🗑️ Unregistering AI session: ${sessionId}`);
114479
+ const agent = this.sessions.get(sessionId);
114440
114480
  this.sessions.delete(sessionId);
114481
+ // Cleanup the ProbeAgent instance to prevent hanging processes
114482
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
114483
+ if (agent && typeof agent.cleanup === 'function') {
114484
+ try {
114485
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
114486
+ agent.cleanup();
114487
+ }
114488
+ catch (error) {
114489
+ console.error(`⚠️ Warning: Failed to cleanup ProbeAgent: ${error}`);
114490
+ }
114491
+ }
114441
114492
  }
114442
114493
  }
114443
114494
  /**
@@ -114445,6 +114496,19 @@ class SessionRegistry {
114445
114496
  */
114446
114497
  clearAllSessions() {
114447
114498
  console.error(`🧹 Clearing all AI sessions (${this.sessions.size} sessions)`);
114499
+ // Cleanup each ProbeAgent instance before clearing
114500
+ for (const [, agent] of this.sessions.entries()) {
114501
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
114502
+ if (agent && typeof agent.cleanup === 'function') {
114503
+ try {
114504
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
114505
+ agent.cleanup();
114506
+ }
114507
+ catch {
114508
+ // Silent fail during bulk cleanup
114509
+ }
114510
+ }
114511
+ }
114448
114512
  this.sessions.clear();
114449
114513
  }
114450
114514
  /**
@@ -114459,6 +114523,51 @@ class SessionRegistry {
114459
114523
  hasSession(sessionId) {
114460
114524
  return this.sessions.has(sessionId);
114461
114525
  }
114526
+ /**
114527
+ * Register process exit handlers to cleanup sessions on exit
114528
+ */
114529
+ registerExitHandlers() {
114530
+ if (this.exitHandlerRegistered) {
114531
+ return;
114532
+ }
114533
+ const cleanupAndExit = (signal) => {
114534
+ if (this.sessions.size > 0) {
114535
+ console.error(`\n🧹 [${signal}] Cleaning up ${this.sessions.size} active AI sessions...`);
114536
+ this.clearAllSessions();
114537
+ }
114538
+ };
114539
+ // Handle normal process exit
114540
+ process.on('exit', () => {
114541
+ if (this.sessions.size > 0) {
114542
+ console.error(`🧹 [exit] Cleaning up ${this.sessions.size} active AI sessions...`);
114543
+ // Note: async operations won't complete here, but sync cleanup methods will
114544
+ for (const [, agent] of this.sessions.entries()) {
114545
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
114546
+ if (agent && typeof agent.cleanup === 'function') {
114547
+ try {
114548
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
114549
+ agent.cleanup();
114550
+ }
114551
+ catch {
114552
+ // Silent fail on exit
114553
+ }
114554
+ }
114555
+ }
114556
+ this.sessions.clear();
114557
+ }
114558
+ });
114559
+ // Handle SIGINT (Ctrl+C)
114560
+ process.on('SIGINT', () => {
114561
+ cleanupAndExit('SIGINT');
114562
+ process.exit(0);
114563
+ });
114564
+ // Handle SIGTERM
114565
+ process.on('SIGTERM', () => {
114566
+ cleanupAndExit('SIGTERM');
114567
+ process.exit(0);
114568
+ });
114569
+ this.exitHandlerRegistered = true;
114570
+ }
114462
114571
  }
114463
114572
  exports.SessionRegistry = SessionRegistry;
114464
114573
 
@@ -228692,7 +228801,7 @@ module.exports = /*#__PURE__*/JSON.parse('{"application/1d-interleaved-parityfec
228692
228801
  /***/ ((module) => {
228693
228802
 
228694
228803
  "use strict";
228695
- module.exports = {"rE":"0.1.79"};
228804
+ module.exports = {"rE":"0.1.81"};
228696
228805
 
228697
228806
  /***/ })
228698
228807
 
@@ -35,10 +35,6 @@ export declare class AICheckProvider extends CheckProvider {
35
35
  * Render Liquid template in prompt with comprehensive event context
36
36
  */
37
37
  private renderPromptTemplate;
38
- /**
39
- * Setup MCP tools based on AI configuration
40
- */
41
- private setupMcpTools;
42
38
  execute(prInfo: PRInfo, config: CheckProviderConfig, _dependencyResults?: Map<string, ReviewSummary>, sessionInfo?: {
43
39
  parentSessionId?: string;
44
40
  reuseSession?: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"ai-check-provider.d.ts","sourceRoot":"","sources":["file:///home/runner/work/visor/visor/src/providers/ai-check-provider.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAChF,OAAO,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AACxC,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAW5C;;GAEG;AACH,qBAAa,eAAgB,SAAQ,aAAa;IAChD,OAAO,CAAC,eAAe,CAAkB;IACzC,OAAO,CAAC,YAAY,CAAS;;IAQ7B,OAAO,IAAI,MAAM;IAIjB,cAAc,IAAI,MAAM;IAIlB,cAAc,CAAC,MAAM,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;IAoDvD;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAqB1B;;OAEG;IACH,OAAO,CAAC,qBAAqB;IAiB7B;;OAEG;YACW,aAAa;IAmB3B;;OAEG;YACW,UAAU;IAsExB;;OAEG;YACW,kBAAkB;IA0ChC;;OAEG;YACW,oBAAoB;IA0IlC;;OAEG;YACW,aAAa;IA8DrB,OAAO,CACX,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,mBAAmB,EAC3B,kBAAkB,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,EAC/C,WAAW,CAAC,EAAE;QAAE,eAAe,CAAC,EAAE,MAAM,CAAC;QAAC,YAAY,CAAC,EAAE,OAAO,CAAA;KAAE,GACjE,OAAO,CAAC,aAAa,CAAC;YAiBX,iBAAiB;IAuL/B,sBAAsB,IAAI,MAAM,EAAE;IAmB5B,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC;IAYrC,eAAe,IAAI,MAAM,EAAE;CAQ5B"}
1
+ {"version":3,"file":"ai-check-provider.d.ts","sourceRoot":"","sources":["file:///home/runner/work/visor/visor/src/providers/ai-check-provider.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAChF,OAAO,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AACxC,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAS5C;;GAEG;AACH,qBAAa,eAAgB,SAAQ,aAAa;IAChD,OAAO,CAAC,eAAe,CAAkB;IACzC,OAAO,CAAC,YAAY,CAAS;;IAQ7B,OAAO,IAAI,MAAM;IAIjB,cAAc,IAAI,MAAM;IAIlB,cAAc,CAAC,MAAM,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;IAoDvD;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAqB1B;;OAEG;IACH,OAAO,CAAC,qBAAqB;IAiB7B;;OAEG;YACW,aAAa;IAmB3B;;OAEG;YACW,UAAU;IAsExB;;OAEG;YACW,kBAAkB;IA0ChC;;OAEG;YACW,oBAAoB;IA0I5B,OAAO,CACX,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,mBAAmB,EAC3B,kBAAkB,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,EAC/C,WAAW,CAAC,EAAE;QAAE,eAAe,CAAC,EAAE,MAAM,CAAC;QAAC,YAAY,CAAC,EAAE,OAAO,CAAA;KAAE,GACjE,OAAO,CAAC,aAAa,CAAC;YAiBX,iBAAiB;IAoL/B,sBAAsB,IAAI,MAAM,EAAE;IAmB5B,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC;IAYrC,eAAe,IAAI,MAAM,EAAE;CAQ5B"}
@@ -28,10 +28,6 @@ export declare class ClaudeCodeCheckProvider extends CheckProvider {
28
28
  * Initialize Claude Code SDK client
29
29
  */
30
30
  private initializeClaudeCodeClient;
31
- /**
32
- * Setup MCP tools based on configuration
33
- */
34
- private setupMcpTools;
35
31
  /**
36
32
  * Group files by their file extension for template context
37
33
  */