@probelabs/visor 0.1.86 → 0.1.88

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.
Files changed (33) hide show
  1. package/README.md +19 -2
  2. package/dist/action-cli-bridge.d.ts.map +1 -1
  3. package/dist/ai-review-service.d.ts +2 -1
  4. package/dist/ai-review-service.d.ts.map +1 -1
  5. package/dist/check-execution-engine.d.ts.map +1 -1
  6. package/dist/config.d.ts.map +1 -1
  7. package/dist/failure-condition-evaluator.d.ts.map +1 -1
  8. package/dist/generated/config-schema.d.ts +5 -0
  9. package/dist/generated/config-schema.d.ts.map +1 -1
  10. package/dist/generated/config-schema.json +8 -0
  11. package/dist/github-check-service.d.ts.map +1 -1
  12. package/dist/index.js +384 -80
  13. package/dist/output-formatters.d.ts +8 -0
  14. package/dist/output-formatters.d.ts.map +1 -1
  15. package/dist/providers/ai-check-provider.d.ts.map +1 -1
  16. package/dist/reviewer.d.ts +2 -0
  17. package/dist/reviewer.d.ts.map +1 -1
  18. package/dist/sdk/{check-execution-engine-WI7LJL2G.mjs → check-execution-engine-D6FPIIKR.mjs} +2 -2
  19. package/dist/sdk/{chunk-P4XLP7NN.mjs → chunk-N34GS4A5.mjs} +160 -32
  20. package/dist/sdk/chunk-N34GS4A5.mjs.map +1 -0
  21. package/dist/sdk/sdk.d.mts +4 -0
  22. package/dist/sdk/sdk.d.ts +4 -0
  23. package/dist/sdk/sdk.js +179 -30
  24. package/dist/sdk/sdk.js.map +1 -1
  25. package/dist/sdk/sdk.mjs +22 -1
  26. package/dist/sdk/sdk.mjs.map +1 -1
  27. package/dist/session-registry.d.ts +5 -0
  28. package/dist/session-registry.d.ts.map +1 -1
  29. package/dist/types/config.d.ts +2 -0
  30. package/dist/types/config.d.ts.map +1 -1
  31. package/package.json +1 -1
  32. package/dist/sdk/chunk-P4XLP7NN.mjs.map +0 -1
  33. /package/dist/sdk/{check-execution-engine-WI7LJL2G.mjs.map → check-execution-engine-D6FPIIKR.mjs.map} +0 -0
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- process.env.VISOR_VERSION = '0.1.86';
2
+ process.env.VISOR_VERSION = '0.1.88';
3
3
  process.env.PROBE_VERSION = '0.6.0-rc124';
4
4
  /******/ (() => { // webpackBootstrap
5
5
  /******/ var __webpack_modules__ = ({
@@ -99834,8 +99834,9 @@ class AIReviewService {
99834
99834
  }
99835
99835
  /**
99836
99836
  * Execute AI review using session reuse - reuses an existing ProbeAgent session
99837
+ * @param sessionMode - 'clone' (default) clones history, 'append' shares history
99837
99838
  */
99838
- async executeReviewWithSessionReuse(prInfo, customPrompt, parentSessionId, schema, checkName) {
99839
+ async executeReviewWithSessionReuse(prInfo, customPrompt, parentSessionId, schema, checkName, sessionMode = 'clone') {
99839
99840
  const startTime = Date.now();
99840
99841
  const timestamp = new Date().toISOString();
99841
99842
  // Get the existing session
@@ -99845,7 +99846,25 @@ class AIReviewService {
99845
99846
  }
99846
99847
  // Build prompt from custom instructions
99847
99848
  const prompt = await this.buildCustomPrompt(prInfo, customPrompt, schema);
99848
- log(`🔄 Reusing AI session ${parentSessionId} for review...`);
99849
+ // Determine which agent to use based on session mode
99850
+ let agentToUse;
99851
+ let currentSessionId;
99852
+ if (sessionMode === 'clone') {
99853
+ // Clone the session - creates a new agent with copied history
99854
+ currentSessionId = `${parentSessionId}-clone-${Date.now()}`;
99855
+ log(`📋 Cloning AI session ${parentSessionId} → ${currentSessionId}...`);
99856
+ const clonedAgent = await this.sessionRegistry.cloneSession(parentSessionId, currentSessionId);
99857
+ if (!clonedAgent) {
99858
+ throw new Error(`Failed to clone session ${parentSessionId}. Falling back to append mode.`);
99859
+ }
99860
+ agentToUse = clonedAgent;
99861
+ }
99862
+ else {
99863
+ // Append mode - use the same agent instance
99864
+ log(`🔄 Appending to AI session ${parentSessionId} (shared history)...`);
99865
+ agentToUse = existingAgent;
99866
+ currentSessionId = parentSessionId;
99867
+ }
99849
99868
  log(`🔧 Debug: Raw schema parameter: ${JSON.stringify(schema)} (type: ${typeof schema})`);
99850
99869
  log(`Schema type: ${schema || 'none (no schema)'}`);
99851
99870
  let debugInfo;
@@ -99867,8 +99886,8 @@ class AIReviewService {
99867
99886
  };
99868
99887
  }
99869
99888
  try {
99870
- // Use existing agent's answer method instead of creating new agent
99871
- const { response, effectiveSchema } = await this.callProbeAgentWithExistingSession(existingAgent, prompt, schema, debugInfo, checkName);
99889
+ // Use the determined agent (cloned or original)
99890
+ const { response, effectiveSchema } = await this.callProbeAgentWithExistingSession(agentToUse, prompt, schema, debugInfo, checkName);
99872
99891
  const processingTime = Date.now() - startTime;
99873
99892
  if (debugInfo) {
99874
99893
  debugInfo.rawResponse = response;
@@ -99879,6 +99898,11 @@ class AIReviewService {
99879
99898
  if (debugInfo) {
99880
99899
  result.debug = debugInfo;
99881
99900
  }
99901
+ // Include the session ID in the result for cleanup tracking
99902
+ // Only include if we created a new cloned session
99903
+ if (sessionMode === 'clone' && currentSessionId !== parentSessionId) {
99904
+ result.sessionId = currentSessionId;
99905
+ }
99882
99906
  return result;
99883
99907
  }
99884
99908
  catch (error) {
@@ -99975,7 +99999,8 @@ ${prContext}
99975
99999
  <rule>For INCREMENTAL analysis, ONLY review changes in commit_diff section</rule>
99976
100000
  <rule>For FULL analysis, review all changes in full_diff section</rule>
99977
100001
  <rule>Reference specific XML elements like files_summary, metadata when providing context</rule>
99978
- <rule>SEVERITY ASSIGNMENT: All severity levels (critical, error, warning, info) should be based on the NEW code being introduced, not on what it fixes. When code INTRODUCES new problems (bugs, vulnerabilities, performance issues, breaking changes), assign appropriate severity (critical/error/warning). When code FIXES or IMPROVES existing issues, use lower severity (info/warning) to acknowledge the improvement. This applies to all issue types: security, performance, style, logic, documentation. The schema provides detailed severity level definitions.</rule>
100002
+ <rule>STRICT OUTPUT POLICY: Report only actual problems, risks, or deficiencies. Do not write praise, congratulations, or celebratory text. Do not create issues that merely restate improvements or say "no action needed".</rule>
100003
+ <rule>SEVERITY ASSIGNMENT: Assign severity ONLY to problems introduced or left unresolved by this change (critical/error/warning/info as appropriate). Do NOT create issue entries solely to acknowledge improvements; if no problems exist, return zero issues.</rule>
99979
100004
  </rules>
99980
100005
  </review_request>`;
99981
100006
  }
@@ -102344,10 +102369,12 @@ class CheckExecutionEngine {
102344
102369
  // - command provider execution/transform failures
102345
102370
  // - forEach validation/iteration errors
102346
102371
  // - fail_if conditions (global or check-specific)
102347
- const hasFatalFailure = (depRes.issues || []).some(issue => {
102372
+ let hasFatalFailure = (depRes.issues || []).some(issue => {
102348
102373
  const id = issue.ruleId || '';
102349
102374
  return (id === 'command/execution_error' ||
102350
102375
  id.endsWith('/command/execution_error') ||
102376
+ id === 'command/timeout' ||
102377
+ id.endsWith('/command/timeout') ||
102351
102378
  id === 'command/transform_js_error' ||
102352
102379
  id.endsWith('/command/transform_js_error') ||
102353
102380
  id === 'command/transform_error' ||
@@ -102358,6 +102385,14 @@ class CheckExecutionEngine {
102358
102385
  id.endsWith('_fail_if') ||
102359
102386
  id.endsWith('/global_fail_if'));
102360
102387
  });
102388
+ // As a fallback, evaluate fail_if on the dependency result now (in case the provider path didn't add issues yet)
102389
+ if (!hasFatalFailure && config && (config.fail_if || config.checks[depId]?.fail_if)) {
102390
+ const failIfResults = await this.evaluateFailureConditions(depId, depRes, config);
102391
+ hasFatalFailure = failIfResults.some(r => r.failed);
102392
+ }
102393
+ if (debug) {
102394
+ log(`🔧 Debug: gating check '${checkName}' against dep '${depId}': wasSkipped=${wasSkipped} hasFatalFailure=${hasFatalFailure}`);
102395
+ }
102361
102396
  if (wasSkipped || hasFatalFailure)
102362
102397
  failedDeps.push(depId);
102363
102398
  }
@@ -102584,6 +102619,23 @@ class CheckExecutionEngine {
102584
102619
  issues: allIssues,
102585
102620
  ...(finalOutput !== undefined ? { output: finalOutput } : {}),
102586
102621
  };
102622
+ // Evaluate fail_if for aggregated forEach results (applies to the whole check)
102623
+ if (config && (config.fail_if || checkConfig.fail_if)) {
102624
+ const failureResults = await this.evaluateFailureConditions(checkName, finalResult, config);
102625
+ if (failureResults.length > 0) {
102626
+ const failureIssues = failureResults
102627
+ .filter(f => f.failed)
102628
+ .map(f => ({
102629
+ file: 'system',
102630
+ line: 0,
102631
+ ruleId: f.conditionName,
102632
+ message: f.message || `Failure condition met: ${f.expression}`,
102633
+ severity: (f.severity || 'error'),
102634
+ category: 'logic',
102635
+ }));
102636
+ finalResult.issues = [...(finalResult.issues || []), ...failureIssues];
102637
+ }
102638
+ }
102587
102639
  // IMPORTANT: Mark this result as forEach-capable so that checks depending on it
102588
102640
  // will also iterate over the items (propagate forEach behavior down the chain)
102589
102641
  if (allOutputs.length > 0) {
@@ -102618,12 +102670,31 @@ class CheckExecutionEngine {
102618
102670
  }
102619
102671
  // Execute with retry/routing semantics
102620
102672
  finalResult = await this.executeWithRouting(checkName, checkConfig, provider, providerConfig, prInfo, dependencyResults, sessionInfo, config, dependencyGraph, debug, results);
102673
+ // Evaluate fail_if for normal (non-forEach) execution
102674
+ if (config && (config.fail_if || checkConfig.fail_if)) {
102675
+ const failureResults = await this.evaluateFailureConditions(checkName, finalResult, config);
102676
+ if (failureResults.length > 0) {
102677
+ const failureIssues = failureResults
102678
+ .filter(f => f.failed)
102679
+ .map(f => ({
102680
+ file: 'system',
102681
+ line: 0,
102682
+ ruleId: f.conditionName,
102683
+ message: f.message || `Failure condition met: ${f.expression}`,
102684
+ severity: (f.severity || 'error'),
102685
+ category: 'logic',
102686
+ }));
102687
+ finalResult.issues = [...(finalResult.issues || []), ...failureIssues];
102688
+ }
102689
+ }
102621
102690
  // Record normal (non-forEach) execution
102622
102691
  // Check if this check had fatal errors
102623
102692
  const hadFatalError = (finalResult.issues || []).some(issue => {
102624
102693
  const id = issue.ruleId || '';
102625
102694
  return (id === 'command/execution_error' ||
102626
102695
  id.endsWith('/command/execution_error') ||
102696
+ id === 'command/timeout' ||
102697
+ id.endsWith('/command/timeout') ||
102627
102698
  id === 'command/transform_js_error' ||
102628
102699
  id.endsWith('/command/transform_js_error') ||
102629
102700
  id === 'command/transform_error' ||
@@ -102646,6 +102717,13 @@ class CheckExecutionEngine {
102646
102717
  if (debug) {
102647
102718
  log(`🔧 Debug: Completed check: ${checkName}, issues found: ${(finalResult.issues || []).length}`);
102648
102719
  }
102720
+ // Track cloned session IDs for cleanup
102721
+ if (finalResult.sessionId) {
102722
+ sessionIds.set(checkName, finalResult.sessionId);
102723
+ if (debug) {
102724
+ log(`🔧 Debug: Tracked cloned session for cleanup: ${finalResult.sessionId}`);
102725
+ }
102726
+ }
102649
102727
  }
102650
102728
  // Add checkName, group, schema, template info and timestamp to issues from config
102651
102729
  const enrichedIssues = (finalResult.issues || []).map(issue => ({
@@ -105446,6 +105524,24 @@ class ConfigManager {
105446
105524
  }
105447
105525
  }
105448
105526
  }
105527
+ // Validate session_mode configuration
105528
+ if (checkConfig.session_mode !== undefined) {
105529
+ if (checkConfig.session_mode !== 'clone' && checkConfig.session_mode !== 'append') {
105530
+ errors.push({
105531
+ field: `checks.${checkName}.session_mode`,
105532
+ message: `Invalid session_mode value for "${checkName}": must be 'clone' or 'append'`,
105533
+ value: checkConfig.session_mode,
105534
+ });
105535
+ }
105536
+ // session_mode only makes sense with reuse_ai_session
105537
+ if (!checkConfig.reuse_ai_session) {
105538
+ errors.push({
105539
+ field: `checks.${checkName}.session_mode`,
105540
+ message: `Check "${checkName}" has session_mode but no reuse_ai_session. session_mode requires reuse_ai_session to be set.`,
105541
+ value: checkConfig.session_mode,
105542
+ });
105543
+ }
105544
+ }
105449
105545
  // Validate tags configuration
105450
105546
  if (checkConfig.tags !== undefined) {
105451
105547
  if (!Array.isArray(checkConfig.tags)) {
@@ -106467,26 +106563,37 @@ class FailureConditionEvaluator {
106467
106563
  // Exclude issues from otherFields since we handle it separately
106468
106564
  issues: _issues, // eslint-disable-line @typescript-eslint/no-unused-vars
106469
106565
  ...otherFields } = reviewSummaryWithOutput;
106566
+ // Build output object with safety for array-based outputs (forEach aggregation)
106567
+ const aggregatedOutput = {
106568
+ issues: (issues || []).map(issue => ({
106569
+ file: issue.file,
106570
+ line: issue.line,
106571
+ endLine: issue.endLine,
106572
+ ruleId: issue.ruleId,
106573
+ message: issue.message,
106574
+ severity: issue.severity,
106575
+ category: issue.category,
106576
+ group: issue.group,
106577
+ schema: issue.schema,
106578
+ suggestion: issue.suggestion,
106579
+ replacement: issue.replacement,
106580
+ })),
106581
+ // Include additional schema-specific data from reviewSummary
106582
+ ...otherFields,
106583
+ };
106584
+ if (Array.isArray(extractedOutput)) {
106585
+ // Preserve items array and lift common flags for convenience (e.g., output.error)
106586
+ aggregatedOutput.items = extractedOutput;
106587
+ const anyError = extractedOutput.find(it => it && typeof it === 'object' && it.error);
106588
+ if (anyError && anyError.error !== undefined) {
106589
+ aggregatedOutput.error = anyError.error;
106590
+ }
106591
+ }
106592
+ else if (extractedOutput && typeof extractedOutput === 'object') {
106593
+ Object.assign(aggregatedOutput, extractedOutput);
106594
+ }
106470
106595
  const context = {
106471
- output: {
106472
- issues: (issues || []).map(issue => ({
106473
- file: issue.file,
106474
- line: issue.line,
106475
- endLine: issue.endLine,
106476
- ruleId: issue.ruleId,
106477
- message: issue.message,
106478
- severity: issue.severity,
106479
- category: issue.category,
106480
- group: issue.group,
106481
- schema: issue.schema,
106482
- suggestion: issue.suggestion,
106483
- replacement: issue.replacement,
106484
- })),
106485
- // Include additional schema-specific data from reviewSummary
106486
- ...otherFields,
106487
- // Spread the extracted output directly (avoid output.output nesting)
106488
- ...(extractedOutput && typeof extractedOutput === 'object' ? extractedOutput : {}),
106489
- },
106596
+ output: aggregatedOutput,
106490
106597
  outputs: (() => {
106491
106598
  if (!previousOutputs)
106492
106599
  return {};
@@ -106813,6 +106920,11 @@ exports.configSchema = {
106813
106920
  type: ['string', 'boolean'],
106814
106921
  description: 'Check name to reuse AI session from, or true to use first dependency (only works with depends_on)',
106815
106922
  },
106923
+ session_mode: {
106924
+ type: 'string',
106925
+ enum: ['clone', 'append'],
106926
+ description: "How to reuse AI session: 'clone' (default, copy history) or 'append' (share history)",
106927
+ },
106816
106928
  fail_if: {
106817
106929
  type: 'string',
106818
106930
  description: 'Simple fail condition - fails check if expression evaluates to true',
@@ -107939,11 +108051,13 @@ class GitHubCheckService {
107939
108051
  await this.clearOldAnnotations(owner, repo, prNumber, checkName, currentCommitSha, check_run_id);
107940
108052
  }
107941
108053
  const { conclusion, summary } = this.determineCheckRunConclusion(checkName, failureResults, reviewIssues, executionError);
108054
+ // Filter out system-level issues (fail_if conditions, internal errors)
108055
+ // These should not appear as annotations but affect the check conclusion
108056
+ let filteredIssues = reviewIssues.filter(issue => !(issue.file === 'system' && issue.line === 0));
107942
108057
  // Filter annotations to only include files changed in this commit
107943
108058
  // This prevents old annotations from previous commits showing up in the Files tab
107944
- let filteredIssues = reviewIssues;
107945
108059
  if (filesChangedInCommit && filesChangedInCommit.length > 0) {
107946
- filteredIssues = reviewIssues.filter(issue => filesChangedInCommit.some(changedFile => issue.file === changedFile));
108060
+ filteredIssues = filteredIssues.filter(issue => filesChangedInCommit.some(changedFile => issue.file === changedFile));
107947
108061
  }
107948
108062
  const annotations = this.convertIssuesToAnnotations(filteredIssues);
107949
108063
  await this.octokit.rest.checks.update({
@@ -108926,7 +109040,39 @@ async function handleEvent(octokit, inputs, eventName, context, config) {
108926
109040
  if (!owner || !repo) {
108927
109041
  throw new Error('Owner and repo are required');
108928
109042
  }
108929
- console.log(`Event: ${eventName}, Owner: ${owner}, Repo: ${repo}`);
109043
+ // Determine context type for better logging
109044
+ const isPullRequest = eventName === 'pull_request';
109045
+ const isIssue = eventName === 'issues';
109046
+ const isIssueComment = eventName === 'issue_comment';
109047
+ const isManualCLI = !eventName || eventName === 'unknown';
109048
+ // Map GitHub event to our event trigger format
109049
+ const eventType = mapGitHubEventToTrigger(eventName, context.event?.action);
109050
+ // Enhanced event logging with context
109051
+ if (isManualCLI) {
109052
+ console.log(`🖥️ Mode: Manual CLI`);
109053
+ console.log(`📂 Repository: ${owner}/${repo}`);
109054
+ }
109055
+ else {
109056
+ console.log(`🤖 Mode: GitHub Action`);
109057
+ console.log(`📂 Repository: ${owner}/${repo}`);
109058
+ console.log(`📋 Event: ${eventName}${context.event?.action ? ` (action: ${context.event?.action})` : ''}`);
109059
+ console.log(`🎯 Trigger: ${eventType}`);
109060
+ // Show context-specific information
109061
+ if (isPullRequest) {
109062
+ const prNumber = context.event?.pull_request?.number;
109063
+ console.log(`🔀 Context: Pull Request #${prNumber}`);
109064
+ }
109065
+ else if (isIssue) {
109066
+ const issueNumber = context.event?.issue?.number;
109067
+ console.log(`🎫 Context: Issue #${issueNumber}`);
109068
+ }
109069
+ else if (isIssueComment) {
109070
+ const issueOrPR = context.event?.issue;
109071
+ const isPR = issueOrPR?.pull_request ? true : false;
109072
+ const number = issueOrPR?.number;
109073
+ console.log(`💬 Context: Comment on ${isPR ? 'Pull Request' : 'Issue'} #${number}`);
109074
+ }
109075
+ }
108930
109076
  // Debug: Log the checks that are available in the loaded config
108931
109077
  const allChecks = Object.keys(config.checks || {});
108932
109078
  console.log(`📚 Total checks in loaded config: ${allChecks.length}`);
@@ -108934,8 +109080,6 @@ async function handleEvent(octokit, inputs, eventName, context, config) {
108934
109080
  // Only log check names if there aren't too many
108935
109081
  console.log(`📚 Available checks: ${allChecks.join(', ')}`);
108936
109082
  }
108937
- // Map GitHub event to our event trigger format
108938
- const eventType = mapGitHubEventToTrigger(eventName, context.event?.action);
108939
109083
  // Find checks that should run for this event
108940
109084
  let checksToRun = [];
108941
109085
  // First, get all checks that are configured for this event type
@@ -109059,7 +109203,7 @@ async function handleIssueEvent(octokit, owner, repo, context, inputs, config, c
109059
109203
  console.log('Skipping PR-related issue event');
109060
109204
  return;
109061
109205
  }
109062
- console.log(`Processing issue #${issue.number} event: ${action} with checks: ${checksToRun.join(', ')}`);
109206
+ console.log(`📋 Processing issue #${issue.number} event: ${action} with ${checksToRun.length} check(s): ${checksToRun.join(', ')}`);
109063
109207
  // For issue events, we need to create a PR-like structure for the checks to process
109064
109208
  // This allows us to reuse the existing check infrastructure
109065
109209
  const prInfo = {
@@ -109098,6 +109242,19 @@ async function handleIssueEvent(octokit, owner, repo, context, inputs, config, c
109098
109242
  config, undefined, // outputFormat
109099
109243
  inputs.debug === 'true');
109100
109244
  const { results } = executionResult;
109245
+ // Log execution results for debugging (only in debug mode)
109246
+ if (inputs.debug === 'true') {
109247
+ console.log(`📊 Check execution completed: ${Object.keys(results).length} group(s)`);
109248
+ for (const [group, checks] of Object.entries(results)) {
109249
+ console.log(` Group "${group}": ${checks.length} check(s)`);
109250
+ for (const check of checks) {
109251
+ const hasContent = check.content && check.content.trim();
109252
+ const contentLength = hasContent ? check.content.trim().length : 0;
109253
+ const issueCount = check.issues?.length || 0;
109254
+ console.log(` - ${check.checkName}: ${hasContent ? `${contentLength} chars` : 'empty'}, ${issueCount} issue(s)`);
109255
+ }
109256
+ }
109257
+ }
109101
109258
  // Format and post results as a comment on the issue
109102
109259
  if (Object.keys(results).length > 0) {
109103
109260
  let commentBody = '';
@@ -109109,18 +109266,24 @@ async function handleIssueEvent(octokit, owner, repo, context, inputs, config, c
109109
109266
  }
109110
109267
  }
109111
109268
  }
109112
- commentBody += `\n---\n*Powered by [Visor](https://github.com/probelabs/visor)*`;
109113
- // Post comment to the issue
109114
- await octokit.rest.issues.createComment({
109115
- owner,
109116
- repo,
109117
- issue_number: issue.number,
109118
- body: commentBody,
109119
- });
109120
- console.log(`✅ Posted issue assistant results to issue #${issue.number}`);
109269
+ // Only post if there's actual content (not just empty checks)
109270
+ if (commentBody.trim()) {
109271
+ commentBody += `\n---\n*Powered by [Visor](https://github.com/probelabs/visor)*`;
109272
+ // Post comment to the issue
109273
+ await octokit.rest.issues.createComment({
109274
+ owner,
109275
+ repo,
109276
+ issue_number: issue.number,
109277
+ body: commentBody,
109278
+ });
109279
+ console.log(`✅ Posted issue assistant results to issue #${issue.number}`);
109280
+ }
109281
+ else {
109282
+ console.log('ℹ️ No content to post - all checks returned empty results');
109283
+ }
109121
109284
  }
109122
109285
  else {
109123
- console.log('No results from issue assistant checks');
109286
+ console.log('⚠️ No results from issue assistant checks');
109124
109287
  }
109125
109288
  // Set outputs for GitHub Actions
109126
109289
  (0, core_1.setOutput)('review-completed', 'true');
@@ -109150,7 +109313,7 @@ async function handleIssueComment(octokit, owner, repo, context, inputs, actionC
109150
109313
  comment.body.includes('*Powered by [Visor](https://probelabs.com/visor)') ||
109151
109314
  comment.body.includes('*Powered by [Visor](https://github.com/probelabs/visor)'));
109152
109315
  if (isVisorBot || hasVisorMarkers) {
109153
- console.log(`Skipping visor comment to prevent recursion. Author: ${comment.user?.login}, Type: ${comment.user?.type}, Has markers: ${hasVisorMarkers}`);
109316
+ console.log(`✓ Skipping bot's own comment to prevent recursion. Author: ${comment.user?.login}, Type: ${comment.user?.type}, Has markers: ${hasVisorMarkers}`);
109154
109317
  return;
109155
109318
  }
109156
109319
  // Process comments on both issues and PRs
@@ -109577,7 +109740,11 @@ async function createGitHubChecks(octokit, inputs, owner, repo, headSha, checksT
109577
109740
  console.log(`✅ Created check run for ${checkName}: ${checkRun.url}`);
109578
109741
  }
109579
109742
  catch (error) {
109580
- console.error(`❌ Failed to create check run for ${checkName}:`, error);
109743
+ const errorMessage = error instanceof Error ? error.message : String(error);
109744
+ // Extract just the meaningful error message without stack trace
109745
+ const cleanMessage = errorMessage.split('\n')[0].replace('Error: ', '');
109746
+ console.error(`⚠️ Could not create check run for ${checkName}: ${cleanMessage}`);
109747
+ console.log('💬 Review will continue using PR comments instead');
109581
109748
  // Continue with other checks even if one fails
109582
109749
  }
109583
109750
  }
@@ -109603,7 +109770,10 @@ async function createGitHubChecks(octokit, inputs, owner, repo, headSha, checksT
109603
109770
  console.log(`✅ Created combined check run: ${checkRun.url}`);
109604
109771
  }
109605
109772
  catch (error) {
109606
- console.error(`❌ Failed to create combined check run:`, error);
109773
+ const errorMessage = error instanceof Error ? error.message : String(error);
109774
+ const cleanMessage = errorMessage.split('\n')[0].replace('Error: ', '');
109775
+ console.error(`⚠️ Could not create combined check run: ${cleanMessage}`);
109776
+ console.log('💬 Review will continue using PR comments instead');
109607
109777
  }
109608
109778
  }
109609
109779
  return {
@@ -109627,7 +109797,10 @@ async function createGitHubChecks(octokit, inputs, owner, repo, headSha, checksT
109627
109797
  };
109628
109798
  }
109629
109799
  else {
109630
- console.error('❌ Failed to create GitHub check runs:', error);
109800
+ const errorMessage = error instanceof Error ? error.message : String(error);
109801
+ const cleanMessage = errorMessage.split('\n')[0].replace('Error: ', '');
109802
+ console.error(`⚠️ Could not create GitHub check runs: ${cleanMessage}`);
109803
+ console.log('💬 Review will continue using PR comments instead');
109631
109804
  return {
109632
109805
  checkRunMap: null,
109633
109806
  checksApiAvailable: false,
@@ -110280,14 +110453,22 @@ exports.OutputFormatters = void 0;
110280
110453
  const cli_table3_1 = __importDefault(__nccwpck_require__(25832));
110281
110454
  const reviewer_1 = __nccwpck_require__(532);
110282
110455
  class OutputFormatters {
110456
+ // Hard safety limits to prevent pathological table rendering hangs
110457
+ // Can be tuned via env vars if needed
110458
+ static MAX_CELL_CHARS = parseInt(process.env.VISOR_MAX_TABLE_CELL || '4000', 10);
110459
+ static MAX_CODE_LINES = parseInt(process.env.VISOR_MAX_TABLE_CODE_LINES || '120', 10);
110460
+ static WRAP_WIDTH_MESSAGE = 55;
110461
+ static WRAP_WIDTH_MESSAGE_NARROW = 45;
110462
+ static WRAP_WIDTH_CODE = 58; // fits into Message col width ~60
110283
110463
  /**
110284
110464
  * Format analysis results as a table using cli-table3
110285
110465
  */
110286
110466
  static formatAsTable(result, options = {}) {
110287
110467
  const { showDetails = false, groupByCategory = true } = options;
110288
110468
  let output = '';
110289
- // Calculate metrics from issues once at the top
110290
- const issues = result.reviewSummary.issues || [];
110469
+ // Filter out system-level issues (fail_if conditions, internal errors)
110470
+ // These should not appear in user-facing output
110471
+ const issues = (result.reviewSummary.issues || []).filter(issue => !(issue.file === 'system' && issue.line === 0));
110291
110472
  const totalIssues = issues.length;
110292
110473
  const criticalIssues = issues.filter(i => i.severity === 'critical').length;
110293
110474
  // Check if this is a code review context
@@ -110314,7 +110495,7 @@ class OutputFormatters {
110314
110495
  summaryTable.push(['Files Analyzed', result.repositoryInfo.files.length.toString()], ['Total Additions', result.repositoryInfo.totalAdditions.toString()], ['Total Deletions', result.repositoryInfo.totalDeletions.toString()]);
110315
110496
  }
110316
110497
  // Always show execution time and checks executed
110317
- summaryTable.push(['Execution Time', `${result.executionTime}ms`], ['Checks Executed', result.checksExecuted.join(', ')]);
110498
+ summaryTable.push(['Execution Time', `${result.executionTime}ms`], ['Checks Executed', this.truncateCell(result.checksExecuted.join(', '))]);
110318
110499
  output += 'Analysis Summary\n';
110319
110500
  output += summaryTable.toString() + '\n';
110320
110501
  output += '\n';
@@ -110329,7 +110510,8 @@ class OutputFormatters {
110329
110510
  const categoryTable = new cli_table3_1.default({
110330
110511
  head: ['File', 'Line', 'Severity', 'Message'],
110331
110512
  colWidths: [25, 8, 15, 60],
110332
- wordWrap: true,
110513
+ // We pre-wrap and truncate ourselves to avoid expensive wrap-ansi work
110514
+ wordWrap: false,
110333
110515
  style: {
110334
110516
  head: ['cyan', 'bold'],
110335
110517
  border: ['grey'],
@@ -110339,25 +110521,24 @@ class OutputFormatters {
110339
110521
  for (const comment of comments.slice(0, showDetails ? comments.length : 5)) {
110340
110522
  // Convert comment back to issue to access suggestion/replacement fields
110341
110523
  const issue = (issues || []).find(i => i.file === comment.file && i.line === comment.line);
110342
- let messageContent = this.wrapText(comment.message, 55);
110524
+ // Pre-wrap and truncate content to keep cli-table3 fast and responsive
110525
+ let messageContent = this.safeWrapAndTruncate(comment.message, OutputFormatters.WRAP_WIDTH_MESSAGE);
110343
110526
  // Add suggestion if available
110344
110527
  if (issue?.suggestion) {
110345
- messageContent += '\nSuggestion: ' + this.wrapText(issue.suggestion, 53);
110528
+ messageContent +=
110529
+ '\nSuggestion: ' +
110530
+ this.safeWrapAndTruncate(issue.suggestion, OutputFormatters.WRAP_WIDTH_MESSAGE - 2);
110346
110531
  }
110347
110532
  // Add replacement code if available
110348
110533
  if (issue?.replacement) {
110349
- messageContent +=
110350
- '\nCode fix:\n' +
110351
- issue.replacement
110352
- .split('\n')
110353
- .map(line => ' ' + line)
110354
- .join('\n');
110534
+ const code = this.formatCodeBlock(issue.replacement);
110535
+ messageContent += '\nCode fix:\n' + code;
110355
110536
  }
110356
110537
  categoryTable.push([
110357
110538
  comment.file,
110358
110539
  comment.line.toString(),
110359
110540
  { content: this.formatSeverity(comment.severity), hAlign: 'center' },
110360
- messageContent,
110541
+ this.truncateCell(messageContent),
110361
110542
  ]);
110362
110543
  }
110363
110544
  output += categoryTable.toString() + '\n';
@@ -110372,7 +110553,7 @@ class OutputFormatters {
110372
110553
  const issuesTable = new cli_table3_1.default({
110373
110554
  head: ['File', 'Line', 'Category', 'Severity', 'Message'],
110374
110555
  colWidths: [20, 6, 12, 15, 50],
110375
- wordWrap: true,
110556
+ wordWrap: false,
110376
110557
  style: {
110377
110558
  head: ['cyan', 'bold'],
110378
110559
  border: ['grey'],
@@ -110380,26 +110561,24 @@ class OutputFormatters {
110380
110561
  });
110381
110562
  output += 'All Issues\n';
110382
110563
  for (const issue of issues.slice(0, showDetails ? undefined : 10)) {
110383
- let messageContent = this.wrapText(issue.message, 45);
110564
+ let messageContent = this.safeWrapAndTruncate(issue.message, OutputFormatters.WRAP_WIDTH_MESSAGE_NARROW);
110384
110565
  // Add suggestion if available
110385
110566
  if (issue.suggestion) {
110386
- messageContent += '\nSuggestion: ' + this.wrapText(issue.suggestion, 43);
110567
+ messageContent +=
110568
+ '\nSuggestion: ' +
110569
+ this.safeWrapAndTruncate(issue.suggestion, OutputFormatters.WRAP_WIDTH_MESSAGE_NARROW - 2);
110387
110570
  }
110388
110571
  // Add replacement code if available
110389
110572
  if (issue.replacement) {
110390
- messageContent +=
110391
- '\nCode fix:\n' +
110392
- issue.replacement
110393
- .split('\n')
110394
- .map(line => ' ' + line)
110395
- .join('\n');
110573
+ const code = this.formatCodeBlock(issue.replacement);
110574
+ messageContent += '\nCode fix:\n' + code;
110396
110575
  }
110397
110576
  issuesTable.push([
110398
110577
  this.truncateText(issue.file, 18),
110399
110578
  issue.line.toString(),
110400
110579
  issue.category,
110401
110580
  this.formatSeverity(issue.severity),
110402
- messageContent,
110581
+ this.truncateCell(messageContent),
110403
110582
  ]);
110404
110583
  }
110405
110584
  output += issuesTable.toString() + '\n\n';
@@ -110439,8 +110618,8 @@ class OutputFormatters {
110439
110618
  * Format analysis results as JSON
110440
110619
  */
110441
110620
  static formatAsJSON(result, options = {}) {
110442
- // Calculate metrics from issues
110443
- const issues = result.reviewSummary.issues;
110621
+ // Filter out system-level issues (fail_if conditions, internal errors)
110622
+ const issues = (result.reviewSummary.issues || []).filter(issue => !(issue.file === 'system' && issue.line === 0));
110444
110623
  const totalIssues = (0, reviewer_1.calculateTotalIssues)(issues);
110445
110624
  const criticalIssues = (0, reviewer_1.calculateCriticalIssues)(issues);
110446
110625
  const jsonResult = {
@@ -110475,8 +110654,8 @@ class OutputFormatters {
110475
110654
  * Format analysis results as SARIF 2.1.0
110476
110655
  */
110477
110656
  static formatAsSarif(result, _options = {}) {
110478
- // Get issues from result
110479
- const issues = result.reviewSummary.issues;
110657
+ // Filter out system-level issues (fail_if conditions, internal errors)
110658
+ const issues = (result.reviewSummary.issues || []).filter(issue => !(issue.file === 'system' && issue.line === 0));
110480
110659
  // Generate unique rule definitions for each issue category
110481
110660
  const rules = [
110482
110661
  {
@@ -110598,8 +110777,8 @@ class OutputFormatters {
110598
110777
  static formatAsMarkdown(result, options = {}) {
110599
110778
  const { showDetails = false, groupByCategory = true } = options;
110600
110779
  let output = '';
110601
- // Calculate metrics from issues
110602
- const issues = result.reviewSummary.issues;
110780
+ // Filter out system-level issues (fail_if conditions, internal errors)
110781
+ const issues = (result.reviewSummary.issues || []).filter(issue => !(issue.file === 'system' && issue.line === 0));
110603
110782
  const totalIssues = (0, reviewer_1.calculateTotalIssues)(issues);
110604
110783
  const criticalIssues = (0, reviewer_1.calculateCriticalIssues)(issues);
110605
110784
  // Header with summary
@@ -110876,13 +111055,50 @@ class OutputFormatters {
110876
111055
  else {
110877
111056
  if (currentLine)
110878
111057
  lines.push(currentLine);
110879
- currentLine = word;
111058
+ // Break overly-long words to avoid pathological wrapping in cli-table3
111059
+ if (word.length > width) {
111060
+ const chunks = word.match(new RegExp(`.{1,${width}}`, 'g')) || [word];
111061
+ // First chunk becomes current line, the rest are full lines
111062
+ currentLine = chunks.shift() || '';
111063
+ for (const chunk of chunks)
111064
+ lines.push(chunk);
111065
+ }
111066
+ else {
111067
+ currentLine = word;
111068
+ }
110880
111069
  }
110881
111070
  }
110882
111071
  if (currentLine)
110883
111072
  lines.push(currentLine);
110884
111073
  return lines.join('\n');
110885
111074
  }
111075
+ // Truncate any cell content defensively
111076
+ static truncateCell(text) {
111077
+ if (text.length <= OutputFormatters.MAX_CELL_CHARS)
111078
+ return text;
111079
+ return text.substring(0, OutputFormatters.MAX_CELL_CHARS - 12) + '\n… [truncated]\n';
111080
+ }
111081
+ // Safer wrapper that first wraps, then truncates
111082
+ static safeWrapAndTruncate(text, width) {
111083
+ return this.truncateCell(this.wrapText(text, width));
111084
+ }
111085
+ // Format code blocks with line and width limits to keep rendering fast
111086
+ static formatCodeBlock(code) {
111087
+ const lines = code.split('\n');
111088
+ const limited = lines.slice(0, OutputFormatters.MAX_CODE_LINES).map(line => {
111089
+ // Soft-wrap code lines to avoid cli-table heavy wrapping
111090
+ const chunks = line.match(new RegExp(`.{1,${OutputFormatters.WRAP_WIDTH_CODE}}`, 'g')) || [
111091
+ '',
111092
+ ];
111093
+ return chunks.map(c => ' ' + c).join('\n');
111094
+ });
111095
+ let out = limited.join('\n');
111096
+ // Indicate truncation of extra lines
111097
+ if (lines.length > OutputFormatters.MAX_CODE_LINES) {
111098
+ out += `\n … [${lines.length - OutputFormatters.MAX_CODE_LINES} more lines truncated]`;
111099
+ }
111100
+ return this.truncateCell(out);
111101
+ }
110886
111102
  }
110887
111103
  exports.OutputFormatters = OutputFormatters;
110888
111104
 
@@ -111535,10 +111751,12 @@ class AICheckProvider extends check_provider_interface_1.CheckProvider {
111535
111751
  let result;
111536
111752
  // Check if we should use session reuse
111537
111753
  if (sessionInfo?.reuseSession && sessionInfo.parentSessionId) {
111754
+ // Get session_mode from config, default to 'clone'
111755
+ const sessionMode = config.session_mode || 'clone';
111538
111756
  if (aiConfig.debug) {
111539
- console.error(`🔄 Debug: Using session reuse with parent session: ${sessionInfo.parentSessionId}`);
111757
+ console.error(`🔄 Debug: Using session reuse with parent session: ${sessionInfo.parentSessionId} (mode: ${sessionMode})`);
111540
111758
  }
111541
- result = await service.executeReviewWithSessionReuse(prInfo, processedPrompt, sessionInfo.parentSessionId, schema, config.checkName);
111759
+ result = await service.executeReviewWithSessionReuse(prInfo, processedPrompt, sessionInfo.parentSessionId, schema, config.checkName, sessionMode);
111542
111760
  }
111543
111761
  else {
111544
111762
  if (aiConfig.debug) {
@@ -114317,10 +114535,43 @@ exports.PRReviewer = PRReviewer;
114317
114535
  /***/ }),
114318
114536
 
114319
114537
  /***/ 46059:
114320
- /***/ ((__unused_webpack_module, exports) => {
114538
+ /***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {
114321
114539
 
114322
114540
  "use strict";
114323
114541
 
114542
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
114543
+ if (k2 === undefined) k2 = k;
114544
+ var desc = Object.getOwnPropertyDescriptor(m, k);
114545
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
114546
+ desc = { enumerable: true, get: function() { return m[k]; } };
114547
+ }
114548
+ Object.defineProperty(o, k2, desc);
114549
+ }) : (function(o, m, k, k2) {
114550
+ if (k2 === undefined) k2 = k;
114551
+ o[k2] = m[k];
114552
+ }));
114553
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
114554
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
114555
+ }) : function(o, v) {
114556
+ o["default"] = v;
114557
+ });
114558
+ var __importStar = (this && this.__importStar) || (function () {
114559
+ var ownKeys = function(o) {
114560
+ ownKeys = Object.getOwnPropertyNames || function (o) {
114561
+ var ar = [];
114562
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
114563
+ return ar;
114564
+ };
114565
+ return ownKeys(o);
114566
+ };
114567
+ return function (mod) {
114568
+ if (mod && mod.__esModule) return mod;
114569
+ var result = {};
114570
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
114571
+ __setModuleDefault(result, mod);
114572
+ return result;
114573
+ };
114574
+ })();
114324
114575
  Object.defineProperty(exports, "__esModule", ({ value: true }));
114325
114576
  exports.SessionRegistry = void 0;
114326
114577
  /**
@@ -114413,6 +114664,59 @@ class SessionRegistry {
114413
114664
  hasSession(sessionId) {
114414
114665
  return this.sessions.has(sessionId);
114415
114666
  }
114667
+ /**
114668
+ * Clone a session with a new session ID
114669
+ * Creates a new ProbeAgent with a copy of the conversation history
114670
+ */
114671
+ async cloneSession(sourceSessionId, newSessionId) {
114672
+ const sourceAgent = this.sessions.get(sourceSessionId);
114673
+ if (!sourceAgent) {
114674
+ console.error(`⚠️ Cannot clone session: ${sourceSessionId} not found`);
114675
+ return undefined;
114676
+ }
114677
+ try {
114678
+ // Access the conversation history from the source agent
114679
+ // ProbeAgent stores history in a private field, we need to access it via 'any'
114680
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
114681
+ const sourceHistory = sourceAgent.conversationHistory || [];
114682
+ // Create a new agent with the same configuration
114683
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
114684
+ const sourceOptions = sourceAgent.options || {};
114685
+ // Import ProbeAgent dynamically to create new instance
114686
+ const { ProbeAgent: ProbeAgentClass } = await Promise.resolve().then(() => __importStar(__nccwpck_require__(83841)));
114687
+ const clonedAgent = new ProbeAgentClass({
114688
+ ...sourceOptions,
114689
+ sessionId: newSessionId,
114690
+ });
114691
+ // Deep copy the conversation history to ensure complete isolation
114692
+ // Use JSON serialization for deep cloning to prevent shared object references
114693
+ if (sourceHistory.length > 0) {
114694
+ try {
114695
+ // Deep clone the history array and all message objects within it
114696
+ const deepClonedHistory = JSON.parse(JSON.stringify(sourceHistory));
114697
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
114698
+ clonedAgent.conversationHistory = deepClonedHistory;
114699
+ console.error(`📋 Cloned session ${sourceSessionId} → ${newSessionId} (${sourceHistory.length} messages, deep copy)`);
114700
+ }
114701
+ catch (cloneError) {
114702
+ // Fallback to shallow copy if deep clone fails (e.g., circular references)
114703
+ console.error(`⚠️ Warning: Deep clone failed for session ${sourceSessionId}, using shallow copy: ${cloneError}`);
114704
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
114705
+ clonedAgent.conversationHistory = [...sourceHistory];
114706
+ }
114707
+ }
114708
+ else {
114709
+ console.error(`📋 Cloned session ${sourceSessionId} → ${newSessionId} (no history)`);
114710
+ }
114711
+ // Register the cloned session
114712
+ this.registerSession(newSessionId, clonedAgent);
114713
+ return clonedAgent;
114714
+ }
114715
+ catch (error) {
114716
+ console.error(`⚠️ Failed to clone session ${sourceSessionId}: ${error}`);
114717
+ return undefined;
114718
+ }
114719
+ }
114416
114720
  /**
114417
114721
  * Register process exit handlers to cleanup sessions on exit
114418
114722
  */
@@ -231977,7 +232281,7 @@ module.exports = /*#__PURE__*/JSON.parse('{"application/1d-interleaved-parityfec
231977
232281
  /***/ ((module) => {
231978
232282
 
231979
232283
  "use strict";
231980
- module.exports = {"rE":"0.1.86"};
232284
+ module.exports = {"rE":"0.1.88"};
231981
232285
 
231982
232286
  /***/ })
231983
232287