@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/sdk/sdk.js CHANGED
@@ -326,7 +326,9 @@ var init_session_registry = __esm({
326
326
  SessionRegistry = class _SessionRegistry {
327
327
  static instance;
328
328
  sessions = /* @__PURE__ */ new Map();
329
+ exitHandlerRegistered = false;
329
330
  constructor() {
331
+ this.registerExitHandlers();
330
332
  }
331
333
  /**
332
334
  * Get the singleton instance of SessionRegistry
@@ -360,7 +362,15 @@ var init_session_registry = __esm({
360
362
  unregisterSession(sessionId) {
361
363
  if (this.sessions.has(sessionId)) {
362
364
  console.error(`\u{1F5D1}\uFE0F Unregistering AI session: ${sessionId}`);
365
+ const agent = this.sessions.get(sessionId);
363
366
  this.sessions.delete(sessionId);
367
+ if (agent && typeof agent.cleanup === "function") {
368
+ try {
369
+ agent.cleanup();
370
+ } catch (error) {
371
+ console.error(`\u26A0\uFE0F Warning: Failed to cleanup ProbeAgent: ${error}`);
372
+ }
373
+ }
364
374
  }
365
375
  }
366
376
  /**
@@ -368,6 +378,14 @@ var init_session_registry = __esm({
368
378
  */
369
379
  clearAllSessions() {
370
380
  console.error(`\u{1F9F9} Clearing all AI sessions (${this.sessions.size} sessions)`);
381
+ for (const [, agent] of this.sessions.entries()) {
382
+ if (agent && typeof agent.cleanup === "function") {
383
+ try {
384
+ agent.cleanup();
385
+ } catch {
386
+ }
387
+ }
388
+ }
371
389
  this.sessions.clear();
372
390
  }
373
391
  /**
@@ -382,6 +400,44 @@ var init_session_registry = __esm({
382
400
  hasSession(sessionId) {
383
401
  return this.sessions.has(sessionId);
384
402
  }
403
+ /**
404
+ * Register process exit handlers to cleanup sessions on exit
405
+ */
406
+ registerExitHandlers() {
407
+ if (this.exitHandlerRegistered) {
408
+ return;
409
+ }
410
+ const cleanupAndExit = (signal) => {
411
+ if (this.sessions.size > 0) {
412
+ console.error(`
413
+ \u{1F9F9} [${signal}] Cleaning up ${this.sessions.size} active AI sessions...`);
414
+ this.clearAllSessions();
415
+ }
416
+ };
417
+ process.on("exit", () => {
418
+ if (this.sessions.size > 0) {
419
+ console.error(`\u{1F9F9} [exit] Cleaning up ${this.sessions.size} active AI sessions...`);
420
+ for (const [, agent] of this.sessions.entries()) {
421
+ if (agent && typeof agent.cleanup === "function") {
422
+ try {
423
+ agent.cleanup();
424
+ } catch {
425
+ }
426
+ }
427
+ }
428
+ this.sessions.clear();
429
+ }
430
+ });
431
+ process.on("SIGINT", () => {
432
+ cleanupAndExit("SIGINT");
433
+ process.exit(0);
434
+ });
435
+ process.on("SIGTERM", () => {
436
+ cleanupAndExit("SIGTERM");
437
+ process.exit(0);
438
+ });
439
+ this.exitHandlerRegistered = true;
440
+ }
385
441
  };
386
442
  }
387
443
  });
@@ -2283,20 +2339,6 @@ var init_liquid_extensions = __esm({
2283
2339
  }
2284
2340
  });
2285
2341
 
2286
- // src/providers/claude-code-types.ts
2287
- async function safeImport(moduleName) {
2288
- try {
2289
- return await import(moduleName);
2290
- } catch {
2291
- return null;
2292
- }
2293
- }
2294
- var init_claude_code_types = __esm({
2295
- "src/providers/claude-code-types.ts"() {
2296
- "use strict";
2297
- }
2298
- });
2299
-
2300
2342
  // src/providers/ai-check-provider.ts
2301
2343
  var import_promises2, import_path2, AICheckProvider;
2302
2344
  var init_ai_check_provider = __esm({
@@ -2309,7 +2351,6 @@ var init_ai_check_provider = __esm({
2309
2351
  init_liquid_extensions();
2310
2352
  import_promises2 = __toESM(require("fs/promises"));
2311
2353
  import_path2 = __toESM(require("path"));
2312
- init_claude_code_types();
2313
2354
  AICheckProvider = class extends CheckProvider {
2314
2355
  aiReviewService;
2315
2356
  liquidEngine;
@@ -2587,54 +2628,6 @@ var init_ai_check_provider = __esm({
2587
2628
  );
2588
2629
  }
2589
2630
  }
2590
- /**
2591
- * Setup MCP tools based on AI configuration
2592
- */
2593
- async setupMcpTools(aiConfig) {
2594
- const tools = [];
2595
- if (aiConfig.mcpServers) {
2596
- try {
2597
- const mcpModule = await safeImport("@modelcontextprotocol/sdk");
2598
- if (!mcpModule) {
2599
- console.warn("@modelcontextprotocol/sdk package not found. MCP servers disabled.");
2600
- return tools;
2601
- }
2602
- const createSdkMcpServer = mcpModule.createSdkMcpServer || mcpModule.default?.createSdkMcpServer;
2603
- if (typeof createSdkMcpServer === "function") {
2604
- for (const [serverName, serverConfig] of Object.entries(aiConfig.mcpServers)) {
2605
- try {
2606
- const server = await createSdkMcpServer({
2607
- name: serverName,
2608
- command: serverConfig.command,
2609
- args: serverConfig.args || [],
2610
- env: { ...process.env, ...serverConfig.env }
2611
- });
2612
- const serverTools = await server.listTools();
2613
- tools.push(
2614
- ...serverTools.map((tool) => ({
2615
- name: tool.name,
2616
- server: serverName
2617
- }))
2618
- );
2619
- } catch (serverError) {
2620
- console.warn(
2621
- `Failed to setup MCP server ${serverName}: ${serverError instanceof Error ? serverError.message : "Unknown error"}`
2622
- );
2623
- }
2624
- }
2625
- } else {
2626
- console.warn(
2627
- "createSdkMcpServer function not found in @modelcontextprotocol/sdk. MCP servers disabled."
2628
- );
2629
- }
2630
- } catch (error) {
2631
- console.warn(
2632
- `Failed to import MCP SDK: ${error instanceof Error ? error.message : "Unknown error"}. MCP servers disabled.`
2633
- );
2634
- }
2635
- }
2636
- return tools;
2637
- }
2638
2631
  async execute(prInfo, config, _dependencyResults, sessionInfo) {
2639
2632
  if (config.env) {
2640
2633
  const result = EnvironmentResolver.withTemporaryEnv(config.env, () => {
@@ -2691,11 +2684,9 @@ var init_ai_check_provider = __esm({
2691
2684
  }
2692
2685
  if (Object.keys(mcpServers).length > 0) {
2693
2686
  aiConfig.mcpServers = mcpServers;
2694
- const mcpConfig = { mcpServers };
2695
- const mcpTools = await this.setupMcpTools(mcpConfig);
2696
2687
  if (aiConfig.debug) {
2697
2688
  console.error(
2698
- `\u{1F527} Debug: AI check MCP configured with ${Object.keys(mcpServers).length} servers; discovered ${mcpTools.length} tools`
2689
+ `\u{1F527} Debug: AI check MCP configured with ${Object.keys(mcpServers).length} servers`
2699
2690
  );
2700
2691
  }
2701
2692
  }
@@ -3566,6 +3557,20 @@ var init_log_check_provider = __esm({
3566
3557
  }
3567
3558
  });
3568
3559
 
3560
+ // src/providers/claude-code-types.ts
3561
+ async function safeImport(moduleName) {
3562
+ try {
3563
+ return await import(moduleName);
3564
+ } catch {
3565
+ return null;
3566
+ }
3567
+ }
3568
+ var init_claude_code_types = __esm({
3569
+ "src/providers/claude-code-types.ts"() {
3570
+ "use strict";
3571
+ }
3572
+ });
3573
+
3569
3574
  // src/providers/claude-code-check-provider.ts
3570
3575
  function isClaudeCodeConstructor(value) {
3571
3576
  return typeof value === "function";
@@ -3679,59 +3684,6 @@ var init_claude_code_check_provider = __esm({
3679
3684
  );
3680
3685
  }
3681
3686
  }
3682
- /**
3683
- * Setup MCP tools based on configuration
3684
- */
3685
- async setupMcpTools(config) {
3686
- const tools = [];
3687
- if (config.allowedTools) {
3688
- for (const toolName of config.allowedTools) {
3689
- tools.push({ name: toolName });
3690
- }
3691
- }
3692
- if (config.mcpServers) {
3693
- try {
3694
- const mcpModule = await safeImport("@modelcontextprotocol/sdk");
3695
- if (!mcpModule) {
3696
- console.warn("@modelcontextprotocol/sdk package not found. MCP servers disabled.");
3697
- return tools;
3698
- }
3699
- const createSdkMcpServer = mcpModule.createSdkMcpServer || mcpModule.default?.createSdkMcpServer;
3700
- if (typeof createSdkMcpServer === "function") {
3701
- for (const [serverName, serverConfig] of Object.entries(config.mcpServers)) {
3702
- try {
3703
- const server = await createSdkMcpServer({
3704
- name: serverName,
3705
- command: serverConfig.command,
3706
- args: serverConfig.args || [],
3707
- env: { ...process.env, ...serverConfig.env }
3708
- });
3709
- const serverTools = await server.listTools();
3710
- tools.push(
3711
- ...serverTools.map((tool) => ({
3712
- name: tool.name,
3713
- server: serverName
3714
- }))
3715
- );
3716
- } catch (serverError) {
3717
- console.warn(
3718
- `Failed to setup MCP server ${serverName}: ${serverError instanceof Error ? serverError.message : "Unknown error"}`
3719
- );
3720
- }
3721
- }
3722
- } else {
3723
- console.warn(
3724
- "createSdkMcpServer function not found in @modelcontextprotocol/sdk. MCP servers disabled."
3725
- );
3726
- }
3727
- } catch (error) {
3728
- console.warn(
3729
- `Failed to import MCP SDK: ${error instanceof Error ? error.message : "Unknown error"}. MCP servers disabled.`
3730
- );
3731
- }
3732
- }
3733
- return tools;
3734
- }
3735
3687
  /**
3736
3688
  * Group files by their file extension for template context
3737
3689
  */
@@ -3962,14 +3914,18 @@ var init_claude_code_check_provider = __esm({
3962
3914
  const startTime = Date.now();
3963
3915
  try {
3964
3916
  const client = await this.initializeClaudeCodeClient();
3965
- const tools = await this.setupMcpTools(claudeCodeConfig);
3966
3917
  const query = {
3967
3918
  query: processedPrompt,
3968
- tools: tools.length > 0 ? tools : void 0,
3969
3919
  maxTurns: claudeCodeConfig.maxTurns || 5,
3970
3920
  systemPrompt: claudeCodeConfig.systemPrompt,
3971
3921
  subagent: claudeCodeConfig.subagent
3972
3922
  };
3923
+ if (claudeCodeConfig.allowedTools && claudeCodeConfig.allowedTools.length > 0) {
3924
+ query.tools = claudeCodeConfig.allowedTools.map((name) => ({ name }));
3925
+ }
3926
+ if (claudeCodeConfig.mcpServers && Object.keys(claudeCodeConfig.mcpServers).length > 0) {
3927
+ query.mcpServers = claudeCodeConfig.mcpServers;
3928
+ }
3973
3929
  let response;
3974
3930
  if (sessionInfo?.reuseSession && sessionInfo.parentSessionId) {
3975
3931
  response = await client.query({
@@ -3997,8 +3953,7 @@ var init_claude_code_check_provider = __esm({
3997
3953
  // Claude Code specific debug info
3998
3954
  sessionId: response.session_id,
3999
3955
  turnCount: response.turn_count,
4000
- usage: response.usage,
4001
- toolsUsed: tools.map((t) => t.name)
3956
+ usage: response.usage
4002
3957
  };
4003
3958
  const suppressionEnabled = config.suppressionEnabled !== false;
4004
3959
  const issueFilter = new IssueFilter(suppressionEnabled);
@@ -4173,7 +4128,19 @@ var init_command_check_provider = __esm({
4173
4128
  const parsed = JSON.parse(rawOutput);
4174
4129
  output = parsed;
4175
4130
  } catch {
4176
- output = rawOutput;
4131
+ const extracted2 = this.extractJsonFromEnd(rawOutput);
4132
+ if (extracted2) {
4133
+ try {
4134
+ output = JSON.parse(extracted2);
4135
+ logger.debug(
4136
+ `\u{1F527} Debug: Extracted and parsed JSON from end of output (${extracted2.length} chars from ${rawOutput.length} total)`
4137
+ );
4138
+ } catch {
4139
+ output = rawOutput;
4140
+ }
4141
+ } else {
4142
+ output = rawOutput;
4143
+ }
4177
4144
  }
4178
4145
  let finalOutput = output;
4179
4146
  if (transform) {
@@ -4392,6 +4359,7 @@ ${stderrOutput}` : `Command execution failed: ${errorMessage}`;
4392
4359
  * - If it's a JSON string, expose parsed properties via Proxy (e.g., value.key)
4393
4360
  * - When coerced to string (toString/valueOf/Symbol.toPrimitive), return the original raw string
4394
4361
  * - If parsing fails or value is not a string, return the value unchanged
4362
+ * - Attempts to extract JSON from the end of the output if full parse fails
4395
4363
  */
4396
4364
  makeJsonSmart(value) {
4397
4365
  if (typeof value !== "string") {
@@ -4402,7 +4370,19 @@ ${stderrOutput}` : `Command execution failed: ${errorMessage}`;
4402
4370
  try {
4403
4371
  parsed = JSON.parse(raw);
4404
4372
  } catch {
4405
- return raw;
4373
+ const jsonMatch = this.extractJsonFromEnd(raw);
4374
+ if (jsonMatch) {
4375
+ try {
4376
+ parsed = JSON.parse(jsonMatch);
4377
+ logger.debug(
4378
+ `\u{1F527} Debug: Extracted JSON from end of output (${jsonMatch.length} chars from ${raw.length} total)`
4379
+ );
4380
+ } catch {
4381
+ return raw;
4382
+ }
4383
+ } else {
4384
+ return raw;
4385
+ }
4406
4386
  }
4407
4387
  const boxed = new String(raw);
4408
4388
  const handler = {
@@ -4451,6 +4431,24 @@ ${stderrOutput}` : `Command execution failed: ${errorMessage}`;
4451
4431
  };
4452
4432
  return new Proxy(boxed, handler);
4453
4433
  }
4434
+ /**
4435
+ * Extract JSON from the end of a string that may contain logs/debug output
4436
+ * Looks for the last occurrence of { or [ and tries to parse from there
4437
+ */
4438
+ extractJsonFromEnd(text) {
4439
+ const lines = text.split("\n");
4440
+ for (let i = lines.length - 1; i >= 0; i--) {
4441
+ const trimmed = lines[i].trim();
4442
+ if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
4443
+ const candidate = lines.slice(i).join("\n");
4444
+ const trimmedCandidate = candidate.trim();
4445
+ if (trimmedCandidate.startsWith("{") && trimmedCandidate.endsWith("}") || trimmedCandidate.startsWith("[") && trimmedCandidate.endsWith("]")) {
4446
+ return trimmedCandidate;
4447
+ }
4448
+ }
4449
+ }
4450
+ return null;
4451
+ }
4454
4452
  /**
4455
4453
  * Recursively apply JSON-smart wrapper to outputs object values
4456
4454
  */
@@ -7257,11 +7255,15 @@ ${expr}
7257
7255
  for (const depId of directDeps) {
7258
7256
  const depRes = results.get(depId);
7259
7257
  if (!depRes) continue;
7260
- const hasFatalCommandFailure = (depRes.issues || []).some((issue) => {
7258
+ const wasSkipped = (depRes.issues || []).some((issue) => {
7261
7259
  const id = issue.ruleId || "";
7262
- return id.endsWith("/command/execution_error") || id.endsWith("/command/transform_js_error") || id.endsWith("/command/transform_error");
7260
+ return id.endsWith("/__skipped");
7263
7261
  });
7264
- if (hasFatalCommandFailure) failedDeps.push(depId);
7262
+ const hasFatalFailure = (depRes.issues || []).some((issue) => {
7263
+ const id = issue.ruleId || "";
7264
+ return id === "command/execution_error" || id.endsWith("/command/execution_error") || id === "command/transform_js_error" || id.endsWith("/command/transform_js_error") || id === "command/transform_error" || id.endsWith("/command/transform_error") || id.endsWith("/forEach/iteration_error");
7265
+ });
7266
+ if (wasSkipped || hasFatalFailure) failedDeps.push(depId);
7265
7267
  }
7266
7268
  if (failedDeps.length > 0) {
7267
7269
  this.recordSkip(checkName, "dependency_failed");
@@ -7327,7 +7329,9 @@ ${expr}
7327
7329
  `\u{1F504} Debug: Check "${checkName}" depends on forEach check "${forEachParentName}", executing ${forEachItems.length} times`
7328
7330
  );
7329
7331
  }
7330
- logger.info(` Processing ${forEachItems.length} items...`);
7332
+ logger.info(
7333
+ ` forEach: processing ${forEachItems.length} items from "${forEachParentName}"...`
7334
+ );
7331
7335
  const allIssues = [];
7332
7336
  const allOutputs = [];
7333
7337
  const aggregatedContents = [];
@@ -7404,11 +7408,16 @@ ${expr}
7404
7408
  parent: forEachParentName
7405
7409
  }
7406
7410
  );
7411
+ const hadFatalError = (itemResult.issues || []).some((issue) => {
7412
+ const id = issue.ruleId || "";
7413
+ return id === "command/execution_error" || id.endsWith("/command/execution_error") || id === "command/transform_js_error" || id.endsWith("/command/transform_js_error") || id === "command/transform_error" || id.endsWith("/command/transform_error");
7414
+ });
7407
7415
  const iterationDuration = (Date.now() - iterationStart) / 1e3;
7408
7416
  this.recordIterationComplete(
7409
7417
  checkName,
7410
7418
  iterationStart,
7411
- true,
7419
+ !hadFatalError,
7420
+ // Success if no fatal errors
7412
7421
  itemResult.issues || [],
7413
7422
  itemResult.output
7414
7423
  );
@@ -7433,7 +7442,22 @@ ${expr}
7433
7442
  );
7434
7443
  for (const result of forEachResults) {
7435
7444
  if (result.status === "rejected") {
7436
- throw result.reason;
7445
+ const error = result.reason;
7446
+ const errorMessage = error instanceof Error ? error.message : String(error);
7447
+ allIssues.push({
7448
+ ruleId: `${checkName}/forEach/iteration_error`,
7449
+ severity: "error",
7450
+ category: "logic",
7451
+ message: `forEach iteration failed: ${errorMessage}`,
7452
+ file: "",
7453
+ line: 0
7454
+ });
7455
+ if (debug) {
7456
+ log2(
7457
+ `\u{1F504} Debug: forEach iteration for check "${checkName}" failed: ${errorMessage}`
7458
+ );
7459
+ }
7460
+ continue;
7437
7461
  }
7438
7462
  if (result.value.skipped) {
7439
7463
  continue;
@@ -7501,10 +7525,15 @@ ${expr}
7501
7525
  debug,
7502
7526
  results
7503
7527
  );
7528
+ const hadFatalError = (finalResult.issues || []).some((issue) => {
7529
+ const id = issue.ruleId || "";
7530
+ return id === "command/execution_error" || id.endsWith("/command/execution_error") || id === "command/transform_js_error" || id.endsWith("/command/transform_js_error") || id === "command/transform_error" || id.endsWith("/command/transform_error");
7531
+ });
7504
7532
  this.recordIterationComplete(
7505
7533
  checkName,
7506
7534
  checkStartTime,
7507
- true,
7535
+ !hadFatalError,
7536
+ // Success if no fatal errors
7508
7537
  finalResult.issues || [],
7509
7538
  finalResult.output
7510
7539
  );
@@ -7592,8 +7621,20 @@ ${expr}
7592
7621
  if (result.status === "fulfilled" && result.value.result && !result.value.error) {
7593
7622
  if (result.value.skipped) {
7594
7623
  if (debug) {
7595
- log2(`\u{1F527} Debug: Not storing result for skipped check "${checkName}"`);
7624
+ log2(`\u{1F527} Debug: Storing skip marker for skipped check "${checkName}"`);
7596
7625
  }
7626
+ results.set(checkName, {
7627
+ issues: [
7628
+ {
7629
+ ruleId: `${checkName}/__skipped`,
7630
+ severity: "info",
7631
+ category: "logic",
7632
+ message: "Check was skipped",
7633
+ file: "",
7634
+ line: 0
7635
+ }
7636
+ ]
7637
+ });
7597
7638
  continue;
7598
7639
  }
7599
7640
  const reviewResult = result.value.result;
@@ -7618,6 +7659,7 @@ ${expr}
7618
7659
  } else {
7619
7660
  normalizedOutput = [rawOutput];
7620
7661
  }
7662
+ logger.info(` Found ${normalizedOutput.length} items for forEach iteration`);
7621
7663
  try {
7622
7664
  const preview = JSON.stringify(normalizedOutput);
7623
7665
  logger.debug(
@@ -7676,14 +7718,6 @@ ${expr}
7676
7718
  }
7677
7719
  }
7678
7720
  }
7679
- const executionStatistics = this.buildExecutionStatistics();
7680
- if (logFn === console.log) {
7681
- this.logExecutionSummary(executionStatistics);
7682
- }
7683
- if (shouldStopExecution) {
7684
- logger.info("");
7685
- logger.warn(`\u26A0\uFE0F Execution stopped early due to fail-fast`);
7686
- }
7687
7721
  if (debug) {
7688
7722
  if (shouldStopExecution) {
7689
7723
  log2(
@@ -7704,6 +7738,14 @@ ${expr}
7704
7738
  }
7705
7739
  }
7706
7740
  }
7741
+ const executionStatistics = this.buildExecutionStatistics();
7742
+ if (logFn === console.log) {
7743
+ this.logExecutionSummary(executionStatistics);
7744
+ }
7745
+ if (shouldStopExecution) {
7746
+ logger.info("");
7747
+ logger.warn(`\u26A0\uFE0F Execution stopped early due to fail-fast`);
7748
+ }
7707
7749
  return this.aggregateDependencyAwareResults(
7708
7750
  results,
7709
7751
  dependencyGraph,
@@ -7922,7 +7964,10 @@ ${expr}
7922
7964
  `\u2705 Check "${checkName}" completed: ${(result.issues || []).length} issues found (level ${executionGroup.level})`
7923
7965
  );
7924
7966
  }
7925
- aggregatedIssues.push(...result.issues || []);
7967
+ const nonInternalIssues = (result.issues || []).filter(
7968
+ (issue) => !issue.ruleId?.endsWith("/__skipped")
7969
+ );
7970
+ aggregatedIssues.push(...nonInternalIssues);
7926
7971
  const resultSummary = result;
7927
7972
  const resultContent = resultSummary.content;
7928
7973
  if (typeof resultContent === "string" && resultContent.trim()) {