@probelabs/visor 0.1.39 → 0.1.41

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
@@ -94115,6 +94115,21 @@ ${prContext}
94115
94115
  formatPRContext(prInfo) {
94116
94116
  // Check if this is an issue (not a PR)
94117
94117
  const isIssue = prInfo.isIssue === true;
94118
+ // Check if we should include code context (diffs)
94119
+ const isPRContext = prInfo.isPRContext === true;
94120
+ // In PR context, always include diffs. Otherwise check the flag.
94121
+ const includeCodeContext = isPRContext || prInfo.includeCodeContext !== false;
94122
+ // Log the decision for transparency
94123
+ const log = this.config.debug ? console.error : () => { };
94124
+ if (isPRContext) {
94125
+ log('🔍 Including full code diffs in AI context (PR mode)');
94126
+ }
94127
+ else if (!includeCodeContext) {
94128
+ log('📊 Including only file summary in AI context (no diffs)');
94129
+ }
94130
+ else {
94131
+ log('🔍 Including code diffs in AI context');
94132
+ }
94118
94133
  if (isIssue) {
94119
94134
  // Format as issue context
94120
94135
  let context = `<issue>
@@ -94236,31 +94251,39 @@ ${this.escapeXml(prInfo.body)}
94236
94251
  ${this.escapeXml(prInfo.body)}
94237
94252
  </description>`;
94238
94253
  }
94239
- // Add full diff if available (for complete PR review)
94240
- if (prInfo.fullDiff) {
94241
- context += `
94254
+ // Add diffs only if includeCodeContext is true (or in PR mode)
94255
+ if (includeCodeContext) {
94256
+ // Add full diff if available (for complete PR review)
94257
+ if (prInfo.fullDiff) {
94258
+ context += `
94242
94259
  <!-- Complete unified diff showing all changes in the pull request -->
94243
94260
  <full_diff>
94244
94261
  ${this.escapeXml(prInfo.fullDiff)}
94245
94262
  </full_diff>`;
94246
- }
94247
- // Add incremental commit diff if available (for new commit analysis)
94248
- if (prInfo.isIncremental) {
94249
- if (prInfo.commitDiff && prInfo.commitDiff.length > 0) {
94250
- context += `
94263
+ }
94264
+ // Add incremental commit diff if available (for new commit analysis)
94265
+ if (prInfo.isIncremental) {
94266
+ if (prInfo.commitDiff && prInfo.commitDiff.length > 0) {
94267
+ context += `
94251
94268
  <!-- Diff of only the latest commit for incremental analysis -->
94252
94269
  <commit_diff>
94253
94270
  ${this.escapeXml(prInfo.commitDiff)}
94254
94271
  </commit_diff>`;
94255
- }
94256
- else {
94257
- context += `
94272
+ }
94273
+ else {
94274
+ context += `
94258
94275
  <!-- Commit diff could not be retrieved - falling back to full diff analysis -->
94259
94276
  <commit_diff>
94260
94277
  ${prInfo.fullDiff ? this.escapeXml(prInfo.fullDiff) : ''}
94261
94278
  </commit_diff>`;
94279
+ }
94262
94280
  }
94263
94281
  }
94282
+ else {
94283
+ // When not including diffs, add a note about it
94284
+ context += `
94285
+ <!-- Code diffs excluded to reduce token usage (no code-review schema detected or disabled by flag) -->`;
94286
+ }
94264
94287
  // Add file summary for context
94265
94288
  if (prInfo.files.length > 0) {
94266
94289
  context += `
@@ -95612,8 +95635,7 @@ class CheckExecutionEngine {
95612
95635
  // Execute checks level by level
95613
95636
  const results = new Map();
95614
95637
  const sessionRegistry = (__nccwpck_require__(46059).SessionRegistry).getInstance();
95615
- const provider = this.providerRegistry.getProviderOrThrow('ai');
95616
- this.setProviderWebhookContext(provider);
95638
+ // Note: We'll get the provider dynamically per check, not a single one for all
95617
95639
  const sessionIds = new Map(); // checkName -> sessionId
95618
95640
  let shouldStopExecution = false;
95619
95641
  for (let levelIndex = 0; levelIndex < dependencyGraph.executionOrder.length && !shouldStopExecution; levelIndex++) {
@@ -95661,15 +95683,24 @@ class CheckExecutionEngine {
95661
95683
  };
95662
95684
  }
95663
95685
  }
95686
+ // Get the appropriate provider for this check type
95687
+ const providerType = checkConfig.type || 'ai';
95688
+ const provider = this.providerRegistry.getProviderOrThrow(providerType);
95689
+ this.setProviderWebhookContext(provider);
95664
95690
  // Create provider config for this specific check
95665
95691
  const providerConfig = {
95666
- type: 'ai',
95692
+ type: providerType,
95667
95693
  prompt: checkConfig.prompt,
95694
+ exec: checkConfig.exec,
95668
95695
  focus: checkConfig.focus || this.mapCheckNameToFocus(checkName),
95669
95696
  schema: checkConfig.schema,
95670
95697
  group: checkConfig.group,
95671
95698
  checkName: checkName, // Add checkName for sessionID
95672
95699
  eventContext: prInfo.eventContext, // Pass event context for templates
95700
+ transform: checkConfig.transform,
95701
+ level: checkConfig.level,
95702
+ message: checkConfig.message,
95703
+ env: checkConfig.env,
95673
95704
  ai: {
95674
95705
  timeout: timeout || 600000,
95675
95706
  debug: debug,
@@ -95678,9 +95709,19 @@ class CheckExecutionEngine {
95678
95709
  };
95679
95710
  // Pass results from dependencies if needed
95680
95711
  const dependencyResults = new Map();
95712
+ let isForEachDependent = false;
95713
+ let forEachItems = [];
95714
+ let forEachParentName;
95681
95715
  for (const depId of checkConfig.depends_on || []) {
95682
95716
  if (results.has(depId)) {
95683
- dependencyResults.set(depId, results.get(depId));
95717
+ const depResult = results.get(depId);
95718
+ dependencyResults.set(depId, depResult);
95719
+ // Check if this dependency has forEach enabled
95720
+ if (depResult.isForEach && depResult.forEachItems) {
95721
+ isForEachDependent = true;
95722
+ forEachItems = depResult.forEachItems;
95723
+ forEachParentName = depId;
95724
+ }
95684
95725
  }
95685
95726
  }
95686
95727
  // Determine if we should use session reuse
@@ -95709,10 +95750,54 @@ class CheckExecutionEngine {
95709
95750
  // Add session ID to provider config
95710
95751
  providerConfig.sessionId = currentSessionId;
95711
95752
  }
95712
- const result = await provider.execute(prInfo, providerConfig, dependencyResults, sessionInfo);
95713
- log(`🔧 Debug: Completed check: ${checkName}, issues found: ${(result.issues || []).length}`);
95753
+ // Handle forEach dependent execution
95754
+ let finalResult;
95755
+ if (isForEachDependent && forEachParentName) {
95756
+ log(`🔄 Debug: Check "${checkName}" depends on forEach check "${forEachParentName}", executing ${forEachItems.length} times`);
95757
+ const allIssues = [];
95758
+ const allOutputs = [];
95759
+ // Execute check for each item in the forEach array
95760
+ for (let itemIndex = 0; itemIndex < forEachItems.length; itemIndex++) {
95761
+ const item = forEachItems[itemIndex];
95762
+ // Create modified dependency results with current item
95763
+ const forEachDependencyResults = new Map();
95764
+ for (const [depName, depResult] of dependencyResults) {
95765
+ if (depName === forEachParentName) {
95766
+ // Replace the forEach parent's output with the current item
95767
+ const modifiedResult = {
95768
+ ...depResult,
95769
+ output: item,
95770
+ };
95771
+ forEachDependencyResults.set(depName, modifiedResult);
95772
+ }
95773
+ else {
95774
+ forEachDependencyResults.set(depName, depResult);
95775
+ }
95776
+ }
95777
+ log(`🔄 Debug: Executing check "${checkName}" for item ${itemIndex + 1}/${forEachItems.length}`);
95778
+ const itemResult = await provider.execute(prInfo, providerConfig, forEachDependencyResults, sessionInfo);
95779
+ // Collect issues from each iteration
95780
+ if (itemResult.issues) {
95781
+ allIssues.push(...itemResult.issues);
95782
+ }
95783
+ // Collect outputs from each iteration
95784
+ if (itemResult.output) {
95785
+ allOutputs.push(itemResult.output);
95786
+ }
95787
+ }
95788
+ finalResult = {
95789
+ issues: allIssues,
95790
+ output: allOutputs.length > 0 ? allOutputs : undefined,
95791
+ };
95792
+ log(`🔄 Debug: Completed forEach execution for check "${checkName}", total issues: ${allIssues.length}`);
95793
+ }
95794
+ else {
95795
+ // Normal single execution
95796
+ finalResult = await provider.execute(prInfo, providerConfig, dependencyResults, sessionInfo);
95797
+ log(`🔧 Debug: Completed check: ${checkName}, issues found: ${(finalResult.issues || []).length}`);
95798
+ }
95714
95799
  // Add group, schema, template info and timestamp to issues from config
95715
- const enrichedIssues = (result.issues || []).map(issue => ({
95800
+ const enrichedIssues = (finalResult.issues || []).map(issue => ({
95716
95801
  ...issue,
95717
95802
  ruleId: `${checkName}/${issue.ruleId}`,
95718
95803
  group: checkConfig.group,
@@ -95721,7 +95806,7 @@ class CheckExecutionEngine {
95721
95806
  timestamp: Date.now(),
95722
95807
  }));
95723
95808
  const enrichedResult = {
95724
- ...result,
95809
+ ...finalResult,
95725
95810
  issues: enrichedIssues,
95726
95811
  };
95727
95812
  return {
@@ -95746,8 +95831,34 @@ class CheckExecutionEngine {
95746
95831
  for (let i = 0; i < levelResults.length; i++) {
95747
95832
  const checkName = executionGroup.parallel[i];
95748
95833
  const result = levelResults[i];
95834
+ const checkConfig = config.checks[checkName];
95749
95835
  if (result.status === 'fulfilled' && result.value.result && !result.value.error) {
95750
- results.set(checkName, result.value.result);
95836
+ const reviewResult = result.value.result;
95837
+ // Handle forEach logic - process array outputs
95838
+ if (checkConfig?.forEach && reviewResult.output) {
95839
+ let outputArray = reviewResult.output;
95840
+ // Ensure output is an array
95841
+ if (!Array.isArray(outputArray)) {
95842
+ // Try to parse as JSON if it's a string
95843
+ if (typeof outputArray === 'string') {
95844
+ try {
95845
+ const parsed = JSON.parse(outputArray);
95846
+ outputArray = Array.isArray(parsed) ? parsed : [parsed];
95847
+ }
95848
+ catch {
95849
+ outputArray = [outputArray];
95850
+ }
95851
+ }
95852
+ else {
95853
+ outputArray = [outputArray];
95854
+ }
95855
+ }
95856
+ log(`🔄 Debug: Check "${checkName}" has forEach enabled, processing ${outputArray.length} items`);
95857
+ // Store the array for iteration by dependent checks
95858
+ reviewResult.forEachItems = outputArray;
95859
+ reviewResult.isForEach = true;
95860
+ }
95861
+ results.set(checkName, reviewResult);
95751
95862
  }
95752
95863
  else {
95753
95864
  // Store error result for dependency tracking
@@ -96781,10 +96892,6 @@ async function main() {
96781
96892
  .findAndLoadConfig()
96782
96893
  .catch(() => configManager.getDefaultConfig());
96783
96894
  }
96784
- // Get repository info using GitRepositoryAnalyzer
96785
- const { GitRepositoryAnalyzer } = await Promise.resolve().then(() => __importStar(__nccwpck_require__(71259)));
96786
- const analyzer = new GitRepositoryAnalyzer(process.cwd());
96787
- const repositoryInfo = await analyzer.analyzeRepository();
96788
96895
  // Determine checks to run and validate check types early
96789
96896
  const checksToRun = options.checks.length > 0 ? options.checks : Object.keys(config.checks || {});
96790
96897
  // Validate that all requested checks exist in the configuration
@@ -96794,13 +96901,58 @@ async function main() {
96794
96901
  console.error(`❌ Error: No configuration found for check: ${invalidChecks[0]}`);
96795
96902
  process.exit(1);
96796
96903
  }
96797
- // Check if we're in a git repository and handle error early
96904
+ // Use stderr for status messages when outputting formatted results to stdout
96905
+ const logFn = console.error;
96906
+ // Determine if we should include code context (diffs)
96907
+ // In CLI mode (local), we do smart detection. PR mode always includes context.
96908
+ const isPRContext = false; // This is CLI mode, not GitHub Action
96909
+ let includeCodeContext = false;
96910
+ if (isPRContext) {
96911
+ // ALWAYS include full context in PR/GitHub Action mode
96912
+ includeCodeContext = true;
96913
+ logFn('📝 Code context: ENABLED (PR context - always included)');
96914
+ }
96915
+ else if (options.codeContext === 'enabled') {
96916
+ includeCodeContext = true;
96917
+ logFn('📝 Code context: ENABLED (forced by --enable-code-context)');
96918
+ }
96919
+ else if (options.codeContext === 'disabled') {
96920
+ includeCodeContext = false;
96921
+ logFn('📝 Code context: DISABLED (forced by --disable-code-context)');
96922
+ }
96923
+ else {
96924
+ // Auto-detect based on schemas (CLI mode only)
96925
+ const hasCodeReviewSchema = checksToRun.some(check => config.checks?.[check]?.schema === 'code-review');
96926
+ includeCodeContext = hasCodeReviewSchema;
96927
+ if (hasCodeReviewSchema) {
96928
+ logFn('📝 Code context: ENABLED (code-review schema detected in local mode)');
96929
+ }
96930
+ else {
96931
+ logFn('📝 Code context: DISABLED (no code-review schema found in local mode)');
96932
+ }
96933
+ }
96934
+ // Get repository info using GitRepositoryAnalyzer
96935
+ const { GitRepositoryAnalyzer } = await Promise.resolve().then(() => __importStar(__nccwpck_require__(71259)));
96936
+ const analyzer = new GitRepositoryAnalyzer(process.cwd());
96937
+ let repositoryInfo;
96938
+ try {
96939
+ repositoryInfo = await analyzer.analyzeRepository(includeCodeContext);
96940
+ }
96941
+ catch (error) {
96942
+ console.error('❌ Error analyzing git repository:', error);
96943
+ console.error('💡 Make sure you are in a git repository or initialize one with "git init"');
96944
+ process.exit(1);
96945
+ }
96946
+ // Check if we're in a git repository
96798
96947
  if (!repositoryInfo.isGitRepository) {
96799
- console.error('❌ Error: Not a git repository or no changes found');
96948
+ console.error('❌ Error: Not a git repository. Run "git init" to initialize a repository.');
96949
+ process.exit(1);
96950
+ }
96951
+ // Check if there are any changes to analyze
96952
+ if (repositoryInfo.files.length === 0) {
96953
+ console.error('❌ Error: No changes to analyze. Make some file changes first.');
96800
96954
  process.exit(1);
96801
96955
  }
96802
- // Use stderr for status messages when outputting formatted results to stdout
96803
- const logFn = console.error;
96804
96956
  logFn('🔍 Visor - AI-powered code review tool');
96805
96957
  logFn(`Configuration version: ${config.version}`);
96806
96958
  logFn(`Configuration source: ${options.configPath || 'default search locations'}`);
@@ -96823,8 +96975,12 @@ async function main() {
96823
96975
  exclude: options.excludeTags,
96824
96976
  }
96825
96977
  : undefined;
96826
- // Execute checks with proper parameters (cast to PRInfo)
96827
- const groupedResults = await engine.executeGroupedChecks(repositoryInfo, checksToRun, options.timeout, config, options.output, options.debug || false, options.maxParallelism, options.failFast, tagFilter);
96978
+ // Convert repository info to PRInfo format
96979
+ const prInfo = analyzer.toPRInfo(repositoryInfo, includeCodeContext);
96980
+ // Store the includeCodeContext flag in prInfo for downstream use
96981
+ prInfo.includeCodeContext = includeCodeContext;
96982
+ // Execute checks with proper parameters
96983
+ const groupedResults = await engine.executeGroupedChecks(prInfo, checksToRun, options.timeout, config, options.output, options.debug || false, options.maxParallelism, options.failFast, tagFilter);
96828
96984
  // Format output based on format type
96829
96985
  let output;
96830
96986
  if (options.output === 'json') {
@@ -96993,6 +97149,8 @@ class CLI {
96993
97149
  .option('--tags <tags>', 'Include checks with these tags (comma-separated)')
96994
97150
  .option('--exclude-tags <tags>', 'Exclude checks with these tags (comma-separated)')
96995
97151
  .option('--no-remote-extends', 'Disable loading configurations from remote URLs')
97152
+ .option('--enable-code-context', 'Force include code diffs in analysis (CLI mode)')
97153
+ .option('--disable-code-context', 'Force exclude code diffs from analysis (CLI mode)')
96996
97154
  .addHelpText('after', this.getExamplesText())
96997
97155
  .exitOverride(); // Prevent automatic process.exit for better error handling
96998
97156
  // Add validation for options
@@ -97030,6 +97188,8 @@ class CLI {
97030
97188
  .option('--tags <tags>', 'Include checks with these tags (comma-separated)')
97031
97189
  .option('--exclude-tags <tags>', 'Exclude checks with these tags (comma-separated)')
97032
97190
  .option('--allowed-remote-patterns <patterns>', 'Comma-separated list of allowed URL prefixes for remote config extends (e.g., "https://github.com/,https://raw.githubusercontent.com/")')
97191
+ .option('--enable-code-context', 'Force include code diffs in analysis (CLI mode)')
97192
+ .option('--disable-code-context', 'Force exclude code diffs from analysis (CLI mode)')
97033
97193
  .allowUnknownOption(false)
97034
97194
  .allowExcessArguments(false) // Don't allow positional arguments
97035
97195
  .addHelpText('after', this.getExamplesText())
@@ -97060,6 +97220,14 @@ class CLI {
97060
97220
  if (options.excludeTags) {
97061
97221
  excludeTags = options.excludeTags.split(',').map((t) => t.trim());
97062
97222
  }
97223
+ // Determine code context mode
97224
+ let codeContext = 'auto';
97225
+ if (options.enableCodeContext) {
97226
+ codeContext = 'enabled';
97227
+ }
97228
+ else if (options.disableCodeContext) {
97229
+ codeContext = 'disabled';
97230
+ }
97063
97231
  return {
97064
97232
  checks: uniqueChecks,
97065
97233
  output: options.output,
@@ -97073,6 +97241,7 @@ class CLI {
97073
97241
  allowedRemotePatterns,
97074
97242
  help: options.help,
97075
97243
  version: options.version,
97244
+ codeContext,
97076
97245
  };
97077
97246
  }
97078
97247
  catch (error) {
@@ -97141,6 +97310,10 @@ class CLI {
97141
97310
  .option('--max-parallelism <count>', 'Maximum number of checks to run in parallel (default: 3)', value => parseInt(value, 10))
97142
97311
  .option('--debug', 'Enable debug mode for detailed output')
97143
97312
  .option('--fail-fast', 'Stop execution on first failure condition')
97313
+ .option('--tags <tags>', 'Include checks with these tags (comma-separated)')
97314
+ .option('--exclude-tags <tags>', 'Exclude checks with these tags (comma-separated)')
97315
+ .option('--enable-code-context', 'Force include code diffs in analysis (CLI mode)')
97316
+ .option('--disable-code-context', 'Force exclude code diffs from analysis (CLI mode)')
97144
97317
  .addHelpText('after', this.getExamplesText());
97145
97318
  // Get the basic help and append examples manually if addHelpText doesn't work
97146
97319
  const basicHelp = tempProgram.helpInformation();
@@ -97315,7 +97488,7 @@ class ConfigManager {
97315
97488
  validCheckTypes = [
97316
97489
  'ai',
97317
97490
  'claude-code',
97318
- 'tool',
97491
+ 'command',
97319
97492
  'http',
97320
97493
  'http_input',
97321
97494
  'http_client',
@@ -97661,11 +97834,11 @@ class ConfigManager {
97661
97834
  message: `Invalid check configuration for "${checkName}": missing prompt (required for AI checks)`,
97662
97835
  });
97663
97836
  }
97664
- // Tool checks require exec field
97665
- if (checkConfig.type === 'tool' && !checkConfig.exec) {
97837
+ // Command checks require exec field
97838
+ if (checkConfig.type === 'command' && !checkConfig.exec) {
97666
97839
  errors.push({
97667
97840
  field: `checks.${checkName}.exec`,
97668
- message: `Invalid check configuration for "${checkName}": missing exec field (required for tool checks)`,
97841
+ message: `Invalid check configuration for "${checkName}": missing exec field (required for command checks)`,
97669
97842
  });
97670
97843
  }
97671
97844
  // HTTP output checks require url and body fields
@@ -98692,7 +98865,7 @@ class GitRepositoryAnalyzer {
98692
98865
  /**
98693
98866
  * Analyze the current git repository state and return data compatible with PRInfo interface
98694
98867
  */
98695
- async analyzeRepository() {
98868
+ async analyzeRepository(includeContext = true) {
98696
98869
  // Check if we're in a git repository
98697
98870
  const isRepo = await this.isGitRepository();
98698
98871
  if (!isRepo) {
@@ -98705,15 +98878,36 @@ class GitRepositoryAnalyzer {
98705
98878
  this.getCurrentBranch(),
98706
98879
  ]);
98707
98880
  // Get uncommitted changes
98708
- const uncommittedFiles = await this.getUncommittedChanges();
98709
- // Get recent commit info
98710
- const recentCommits = await this.git.log({ maxCount: 1 });
98711
- const lastCommit = recentCommits.latest;
98881
+ const uncommittedFiles = await this.getUncommittedChanges(includeContext);
98882
+ // Get recent commit info (handle repos with no commits)
98883
+ let lastCommit = null;
98884
+ try {
98885
+ const recentCommits = await this.git.log({ maxCount: 1 });
98886
+ lastCommit = recentCommits.latest;
98887
+ }
98888
+ catch {
98889
+ // Repository has no commits yet - this is OK
98890
+ console.log('📝 Repository has no commits yet, analyzing uncommitted changes');
98891
+ }
98892
+ // Get author from git config if no commits exist
98893
+ let author = lastCommit?.author_name;
98894
+ if (!author) {
98895
+ try {
98896
+ const [userName, userEmail] = await Promise.all([
98897
+ this.git.raw(['config', 'user.name']).catch(() => null),
98898
+ this.git.raw(['config', 'user.email']).catch(() => null),
98899
+ ]);
98900
+ author = userName?.trim() || userEmail?.trim() || 'unknown';
98901
+ }
98902
+ catch {
98903
+ author = 'unknown';
98904
+ }
98905
+ }
98712
98906
  // Create repository info
98713
98907
  const repositoryInfo = {
98714
98908
  title: this.generateTitle(status, currentBranch),
98715
98909
  body: this.generateDescription(status, lastCommit),
98716
- author: lastCommit?.author_name || 'unknown',
98910
+ author,
98717
98911
  base: await this.getBaseBranch(),
98718
98912
  head: currentBranch,
98719
98913
  files: uncommittedFiles,
@@ -98725,14 +98919,32 @@ class GitRepositoryAnalyzer {
98725
98919
  return repositoryInfo;
98726
98920
  }
98727
98921
  catch (error) {
98728
- console.error('Error analyzing git repository:', error);
98922
+ // Don't log the full error object to avoid confusing stack traces
98923
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
98924
+ console.error('Error analyzing git repository:', errorMessage);
98729
98925
  return this.createEmptyRepositoryInfo('Error analyzing git repository');
98730
98926
  }
98731
98927
  }
98732
98928
  /**
98733
98929
  * Convert GitRepositoryInfo to PRInfo format for compatibility with existing PRReviewer
98734
98930
  */
98735
- toPRInfo(repositoryInfo) {
98931
+ toPRInfo(repositoryInfo, includeContext = true) {
98932
+ const files = repositoryInfo.files.map((file) => ({
98933
+ filename: file.filename,
98934
+ additions: file.additions,
98935
+ deletions: file.deletions,
98936
+ changes: file.changes,
98937
+ patch: includeContext ? file.patch : undefined,
98938
+ status: file.status,
98939
+ }));
98940
+ // Generate fullDiff from patches if includeContext is true
98941
+ let fullDiff;
98942
+ if (includeContext) {
98943
+ fullDiff = files
98944
+ .filter(file => file.patch)
98945
+ .map(file => `--- ${file.filename}\n${file.patch}`)
98946
+ .join('\n\n');
98947
+ }
98736
98948
  return {
98737
98949
  number: 0, // Local analysis doesn't have PR number
98738
98950
  title: repositoryInfo.title,
@@ -98740,16 +98952,10 @@ class GitRepositoryAnalyzer {
98740
98952
  author: repositoryInfo.author,
98741
98953
  base: repositoryInfo.base,
98742
98954
  head: repositoryInfo.head,
98743
- files: repositoryInfo.files.map((file) => ({
98744
- filename: file.filename,
98745
- additions: file.additions,
98746
- deletions: file.deletions,
98747
- changes: file.changes,
98748
- patch: file.patch,
98749
- status: file.status,
98750
- })),
98955
+ files,
98751
98956
  totalAdditions: repositoryInfo.totalAdditions,
98752
98957
  totalDeletions: repositoryInfo.totalDeletions,
98958
+ fullDiff,
98753
98959
  };
98754
98960
  }
98755
98961
  async isGitRepository() {
@@ -98799,7 +99005,7 @@ class GitRepositoryAnalyzer {
98799
99005
  return null;
98800
99006
  }
98801
99007
  }
98802
- async getUncommittedChanges() {
99008
+ async getUncommittedChanges(includeContext = true) {
98803
99009
  try {
98804
99010
  const status = await this.git.status();
98805
99011
  const changes = [];
@@ -98815,7 +99021,7 @@ class GitRepositoryAnalyzer {
98815
99021
  ];
98816
99022
  for (const { file, status } of fileChanges) {
98817
99023
  const filePath = path.join(this.cwd, file);
98818
- const fileChange = await this.analyzeFileChange(file, status, filePath);
99024
+ const fileChange = await this.analyzeFileChange(file, status, filePath, includeContext);
98819
99025
  changes.push(fileChange);
98820
99026
  }
98821
99027
  return changes;
@@ -98825,14 +99031,14 @@ class GitRepositoryAnalyzer {
98825
99031
  return [];
98826
99032
  }
98827
99033
  }
98828
- async analyzeFileChange(filename, status, filePath) {
99034
+ async analyzeFileChange(filename, status, filePath, includeContext = true) {
98829
99035
  let additions = 0;
98830
99036
  let deletions = 0;
98831
99037
  let patch;
98832
99038
  let content;
98833
99039
  try {
98834
99040
  // Get diff for the file if it exists and is not binary
98835
- if (status !== 'added' && fs.existsSync(filePath)) {
99041
+ if (includeContext && status !== 'added' && fs.existsSync(filePath)) {
98836
99042
  const diff = await this.git.diff(['--', filename]).catch(() => '');
98837
99043
  if (diff) {
98838
99044
  patch = diff;
@@ -98842,15 +99048,28 @@ class GitRepositoryAnalyzer {
98842
99048
  deletions = lines.filter(line => line.startsWith('-')).length;
98843
99049
  }
98844
99050
  }
98845
- // For added files, count lines as additions
99051
+ else if (status !== 'added' && fs.existsSync(filePath)) {
99052
+ // If not including context, still count changes for statistics
99053
+ const diff = await this.git.diff(['--', filename]).catch(() => '');
99054
+ if (diff) {
99055
+ const lines = diff.split('\n');
99056
+ additions = lines.filter(line => line.startsWith('+')).length;
99057
+ deletions = lines.filter(line => line.startsWith('-')).length;
99058
+ }
99059
+ }
99060
+ // For added files
98846
99061
  if (status === 'added' && fs.existsSync(filePath)) {
98847
99062
  try {
98848
99063
  const stats = fs.statSync(filePath);
98849
99064
  if (stats.isFile() && stats.size < 1024 * 1024) {
98850
99065
  // Skip files larger than 1MB
98851
- content = fs.readFileSync(filePath, 'utf8');
98852
- additions = content.split('\n').length;
98853
- patch = content; // For new files, the entire content is the "patch"
99066
+ if (includeContext) {
99067
+ content = fs.readFileSync(filePath, 'utf8');
99068
+ patch = content; // For new files, the entire content is the "patch"
99069
+ }
99070
+ // Always count additions for statistics
99071
+ const fileContent = includeContext ? content : fs.readFileSync(filePath, 'utf8');
99072
+ additions = fileContent.split('\n').length;
98854
99073
  }
98855
99074
  }
98856
99075
  catch {
@@ -100232,6 +100451,9 @@ async function handleIssueComment(octokit, owner, repo, context, inputs, actionC
100232
100451
  prInfo = await analyzer.fetchPRDiff(owner, repo, prNumber, undefined, 'issue_comment');
100233
100452
  // Add event context for templates and XML generation
100234
100453
  prInfo.eventContext = context.event;
100454
+ // PR context always includes code diffs
100455
+ prInfo.includeCodeContext = true;
100456
+ prInfo.isPRContext = true;
100235
100457
  }
100236
100458
  else {
100237
100459
  // It's an issue comment - create a minimal PRInfo structure for issue assistant
@@ -100337,11 +100559,16 @@ async function handlePullRequestWithConfig(octokit, owner, repo, inputs, config,
100337
100559
  // Map the action to event type
100338
100560
  const eventType = mapGitHubEventToTrigger('pull_request', action);
100339
100561
  // Fetch PR diff (handle test scenarios gracefully)
100562
+ // In PR context, ALWAYS include full diffs for proper code review
100563
+ console.log('📝 Code context: ENABLED (PR context - always included)');
100340
100564
  let prInfo;
100341
100565
  try {
100342
100566
  prInfo = await analyzer.fetchPRDiff(owner, repo, prNumber, undefined, eventType);
100343
100567
  // Add event context for templates and XML generation
100344
100568
  prInfo.eventContext = context.event;
100569
+ // Mark that we're in PR context and should always include diffs
100570
+ prInfo.includeCodeContext = true;
100571
+ prInfo.isPRContext = true;
100345
100572
  }
100346
100573
  catch (error) {
100347
100574
  // Handle test scenarios with mock repos
@@ -102367,13 +102594,13 @@ exports.AICheckProvider = AICheckProvider;
102367
102594
  Object.defineProperty(exports, "__esModule", ({ value: true }));
102368
102595
  exports.CheckProviderRegistry = void 0;
102369
102596
  const ai_check_provider_1 = __nccwpck_require__(98901);
102370
- const tool_check_provider_1 = __nccwpck_require__(70477);
102371
102597
  const http_check_provider_1 = __nccwpck_require__(31115);
102372
102598
  const http_input_provider_1 = __nccwpck_require__(74423);
102373
102599
  const http_client_provider_1 = __nccwpck_require__(36270);
102374
102600
  const noop_check_provider_1 = __nccwpck_require__(53003);
102375
102601
  const log_check_provider_1 = __nccwpck_require__(24903);
102376
102602
  const claude_code_check_provider_1 = __nccwpck_require__(17985);
102603
+ const command_check_provider_1 = __nccwpck_require__(11878);
102377
102604
  /**
102378
102605
  * Registry for managing check providers
102379
102606
  */
@@ -102399,7 +102626,7 @@ class CheckProviderRegistry {
102399
102626
  registerDefaultProviders() {
102400
102627
  // Register all built-in providers
102401
102628
  this.register(new ai_check_provider_1.AICheckProvider());
102402
- this.register(new tool_check_provider_1.ToolCheckProvider());
102629
+ this.register(new command_check_provider_1.CommandCheckProvider());
102403
102630
  this.register(new http_check_provider_1.HttpCheckProvider());
102404
102631
  this.register(new http_input_provider_1.HttpInputProvider());
102405
102632
  this.register(new http_client_provider_1.HttpClientProvider());
@@ -103183,6 +103410,244 @@ async function safeImport(moduleName) {
103183
103410
  }
103184
103411
 
103185
103412
 
103413
+ /***/ }),
103414
+
103415
+ /***/ 11878:
103416
+ /***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {
103417
+
103418
+ "use strict";
103419
+
103420
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
103421
+ if (k2 === undefined) k2 = k;
103422
+ var desc = Object.getOwnPropertyDescriptor(m, k);
103423
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
103424
+ desc = { enumerable: true, get: function() { return m[k]; } };
103425
+ }
103426
+ Object.defineProperty(o, k2, desc);
103427
+ }) : (function(o, m, k, k2) {
103428
+ if (k2 === undefined) k2 = k;
103429
+ o[k2] = m[k];
103430
+ }));
103431
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
103432
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
103433
+ }) : function(o, v) {
103434
+ o["default"] = v;
103435
+ });
103436
+ var __importStar = (this && this.__importStar) || (function () {
103437
+ var ownKeys = function(o) {
103438
+ ownKeys = Object.getOwnPropertyNames || function (o) {
103439
+ var ar = [];
103440
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
103441
+ return ar;
103442
+ };
103443
+ return ownKeys(o);
103444
+ };
103445
+ return function (mod) {
103446
+ if (mod && mod.__esModule) return mod;
103447
+ var result = {};
103448
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
103449
+ __setModuleDefault(result, mod);
103450
+ return result;
103451
+ };
103452
+ })();
103453
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
103454
+ exports.CommandCheckProvider = void 0;
103455
+ const check_provider_interface_1 = __nccwpck_require__(14131);
103456
+ const liquidjs_1 = __nccwpck_require__(48694);
103457
+ /**
103458
+ * Check provider that executes shell commands and captures their output
103459
+ * Supports JSON parsing and integration with forEach functionality
103460
+ */
103461
+ class CommandCheckProvider extends check_provider_interface_1.CheckProvider {
103462
+ liquid;
103463
+ constructor() {
103464
+ super();
103465
+ this.liquid = new liquidjs_1.Liquid({
103466
+ cache: false,
103467
+ strictFilters: false,
103468
+ strictVariables: false,
103469
+ });
103470
+ }
103471
+ getName() {
103472
+ return 'command';
103473
+ }
103474
+ getDescription() {
103475
+ return 'Execute shell commands and capture output for processing';
103476
+ }
103477
+ async validateConfig(config) {
103478
+ if (!config || typeof config !== 'object') {
103479
+ return false;
103480
+ }
103481
+ const cfg = config;
103482
+ // Must have exec specified
103483
+ if (!cfg.exec || typeof cfg.exec !== 'string') {
103484
+ return false;
103485
+ }
103486
+ return true;
103487
+ }
103488
+ async execute(prInfo, config, dependencyResults) {
103489
+ const command = config.exec;
103490
+ const transform = config.transform;
103491
+ // Prepare template context for Liquid rendering
103492
+ const templateContext = {
103493
+ pr: {
103494
+ number: prInfo.number,
103495
+ title: prInfo.title,
103496
+ author: prInfo.author,
103497
+ branch: prInfo.head,
103498
+ base: prInfo.base,
103499
+ },
103500
+ files: prInfo.files,
103501
+ fileCount: prInfo.files.length,
103502
+ outputs: this.buildOutputContext(dependencyResults),
103503
+ env: this.getSafeEnvironmentVariables(),
103504
+ };
103505
+ try {
103506
+ // Render the command with Liquid templates if needed
103507
+ let renderedCommand = command;
103508
+ if (command.includes('{{') || command.includes('{%')) {
103509
+ renderedCommand = await this.liquid.parseAndRender(command, templateContext);
103510
+ }
103511
+ // Prepare environment variables - convert all to strings
103512
+ const scriptEnv = {};
103513
+ for (const [key, value] of Object.entries(process.env)) {
103514
+ if (value !== undefined) {
103515
+ scriptEnv[key] = value;
103516
+ }
103517
+ }
103518
+ if (config.env) {
103519
+ for (const [key, value] of Object.entries(config.env)) {
103520
+ if (value !== undefined && value !== null) {
103521
+ scriptEnv[key] = String(value);
103522
+ }
103523
+ }
103524
+ }
103525
+ // Execute the script using dynamic import to avoid Jest issues
103526
+ const { exec } = await Promise.resolve().then(() => __importStar(__nccwpck_require__(35317)));
103527
+ const { promisify } = await Promise.resolve().then(() => __importStar(__nccwpck_require__(39023)));
103528
+ const execAsync = promisify(exec);
103529
+ const { stdout, stderr } = await execAsync(renderedCommand, {
103530
+ env: scriptEnv,
103531
+ timeout: 60000, // 60 second timeout
103532
+ maxBuffer: 10 * 1024 * 1024, // 10MB buffer
103533
+ });
103534
+ if (stderr && process.env.DEBUG) {
103535
+ console.error(`Command stderr: ${stderr}`);
103536
+ }
103537
+ // Try to parse output as JSON
103538
+ let output = stdout.trim();
103539
+ try {
103540
+ // Attempt to parse as JSON
103541
+ const parsed = JSON.parse(stdout.trim());
103542
+ output = parsed;
103543
+ }
103544
+ catch {
103545
+ // If not JSON, keep as string
103546
+ output = stdout.trim();
103547
+ }
103548
+ // Apply transform if specified
103549
+ let finalOutput = output;
103550
+ if (transform) {
103551
+ try {
103552
+ const transformContext = {
103553
+ ...templateContext,
103554
+ output,
103555
+ };
103556
+ const rendered = await this.liquid.parseAndRender(transform, transformContext);
103557
+ // Try to parse the transformed result as JSON
103558
+ try {
103559
+ finalOutput = JSON.parse(rendered.trim());
103560
+ }
103561
+ catch {
103562
+ finalOutput = rendered.trim();
103563
+ }
103564
+ }
103565
+ catch (error) {
103566
+ return {
103567
+ issues: [
103568
+ {
103569
+ file: 'command',
103570
+ line: 0,
103571
+ ruleId: 'command/transform_error',
103572
+ message: `Failed to apply transform: ${error instanceof Error ? error.message : 'Unknown error'}`,
103573
+ severity: 'error',
103574
+ category: 'logic',
103575
+ },
103576
+ ],
103577
+ };
103578
+ }
103579
+ }
103580
+ // Return the output as part of the review summary
103581
+ // The output will be available to dependent checks
103582
+ return {
103583
+ issues: [],
103584
+ output: finalOutput,
103585
+ };
103586
+ }
103587
+ catch (error) {
103588
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
103589
+ return {
103590
+ issues: [
103591
+ {
103592
+ file: 'command',
103593
+ line: 0,
103594
+ ruleId: 'command/execution_error',
103595
+ message: `Command execution failed: ${errorMessage}`,
103596
+ severity: 'error',
103597
+ category: 'logic',
103598
+ },
103599
+ ],
103600
+ };
103601
+ }
103602
+ }
103603
+ buildOutputContext(dependencyResults) {
103604
+ if (!dependencyResults) {
103605
+ return {};
103606
+ }
103607
+ const outputs = {};
103608
+ for (const [checkName, result] of dependencyResults) {
103609
+ if (result.output !== undefined) {
103610
+ outputs[checkName] = result.output;
103611
+ }
103612
+ else {
103613
+ outputs[checkName] = {
103614
+ issueCount: result.issues?.length || 0,
103615
+ issues: result.issues || [],
103616
+ };
103617
+ }
103618
+ }
103619
+ return outputs;
103620
+ }
103621
+ getSafeEnvironmentVariables() {
103622
+ const safeVars = {};
103623
+ const allowedPrefixes = ['CI_', 'GITHUB_', 'RUNNER_', 'NODE_', 'npm_', 'PATH', 'HOME', 'USER'];
103624
+ for (const [key, value] of Object.entries(process.env)) {
103625
+ if (value !== undefined && allowedPrefixes.some(prefix => key.startsWith(prefix))) {
103626
+ safeVars[key] = value;
103627
+ }
103628
+ }
103629
+ // Add current working directory
103630
+ safeVars['PWD'] = process.cwd();
103631
+ return safeVars;
103632
+ }
103633
+ getSupportedConfigKeys() {
103634
+ return ['type', 'exec', 'transform', 'env', 'depends_on', 'on', 'if', 'group', 'forEach'];
103635
+ }
103636
+ async isAvailable() {
103637
+ // Command provider is always available as long as we can execute commands
103638
+ return true;
103639
+ }
103640
+ getRequirements() {
103641
+ return [
103642
+ 'Valid shell command to execute',
103643
+ 'Shell environment available',
103644
+ 'Optional: Transform template for processing output',
103645
+ ];
103646
+ }
103647
+ }
103648
+ exports.CommandCheckProvider = CommandCheckProvider;
103649
+
103650
+
103186
103651
  /***/ }),
103187
103652
 
103188
103653
  /***/ 31115:
@@ -104041,181 +104506,6 @@ class NoopCheckProvider extends check_provider_interface_1.CheckProvider {
104041
104506
  exports.NoopCheckProvider = NoopCheckProvider;
104042
104507
 
104043
104508
 
104044
- /***/ }),
104045
-
104046
- /***/ 70477:
104047
- /***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
104048
-
104049
- "use strict";
104050
-
104051
- Object.defineProperty(exports, "__esModule", ({ value: true }));
104052
- exports.ToolCheckProvider = void 0;
104053
- const check_provider_interface_1 = __nccwpck_require__(14131);
104054
- const issue_filter_1 = __nccwpck_require__(36879);
104055
- const child_process_1 = __nccwpck_require__(35317);
104056
- const liquidjs_1 = __nccwpck_require__(48694);
104057
- /**
104058
- * Check provider that executes external tools and commands with Liquid template support
104059
- * Supports both simple commands and complex templated execution with stdin
104060
- */
104061
- class ToolCheckProvider extends check_provider_interface_1.CheckProvider {
104062
- liquid;
104063
- constructor() {
104064
- super();
104065
- this.liquid = new liquidjs_1.Liquid({
104066
- strictVariables: false,
104067
- strictFilters: false,
104068
- });
104069
- }
104070
- getName() {
104071
- return 'tool';
104072
- }
104073
- getDescription() {
104074
- return 'Execute external code analysis tools (ESLint, Prettier, etc.)';
104075
- }
104076
- async validateConfig(config) {
104077
- if (!config || typeof config !== 'object') {
104078
- return false;
104079
- }
104080
- const cfg = config;
104081
- // Type must be 'tool'
104082
- if (cfg.type !== 'tool') {
104083
- return false;
104084
- }
104085
- // Must have exec specified for tool execution
104086
- if (typeof cfg.exec !== 'string' || !cfg.exec) {
104087
- return false;
104088
- }
104089
- return true;
104090
- }
104091
- async execute(prInfo, config, _dependencyResults, _sessionInfo) {
104092
- const execTemplate = config.exec;
104093
- const stdinTemplate = config.stdin;
104094
- // Prepare template context
104095
- const templateContext = {
104096
- pr: {
104097
- number: prInfo.number,
104098
- title: prInfo.title,
104099
- body: prInfo.body,
104100
- author: prInfo.author,
104101
- base: prInfo.base,
104102
- head: prInfo.head,
104103
- totalAdditions: prInfo.totalAdditions,
104104
- totalDeletions: prInfo.totalDeletions,
104105
- },
104106
- files: prInfo.files.map(f => ({
104107
- filename: f.filename,
104108
- status: f.status,
104109
- additions: f.additions,
104110
- deletions: f.deletions,
104111
- changes: f.changes,
104112
- patch: f.patch,
104113
- })),
104114
- // Add convenience arrays for common use cases
104115
- filenames: prInfo.files.map(f => f.filename),
104116
- config: config, // Allow access to config values in templates
104117
- };
104118
- // Render the command template
104119
- const renderedCommand = await this.liquid.parseAndRender(execTemplate, templateContext);
104120
- // Render stdin if provided
104121
- let renderedStdin;
104122
- if (stdinTemplate) {
104123
- renderedStdin = await this.liquid.parseAndRender(stdinTemplate, templateContext);
104124
- }
104125
- // Execute the tool
104126
- const output = await this.executeCommand(renderedCommand.trim(), renderedStdin);
104127
- // Parse tool output (this would be customized per tool)
104128
- const comments = this.parseToolOutput(output, renderedCommand);
104129
- const issues = comments.map(comment => ({
104130
- file: comment.file,
104131
- line: comment.line,
104132
- endLine: undefined,
104133
- ruleId: `tool/${comment.category}`,
104134
- message: comment.message,
104135
- severity: comment.severity,
104136
- category: comment.category,
104137
- suggestion: undefined,
104138
- replacement: undefined,
104139
- }));
104140
- // Apply issue suppression filtering
104141
- const suppressionEnabled = config.suppressionEnabled !== false;
104142
- const issueFilter = new issue_filter_1.IssueFilter(suppressionEnabled);
104143
- const filteredIssues = issueFilter.filterIssues(issues, process.cwd());
104144
- return {
104145
- issues: filteredIssues,
104146
- };
104147
- }
104148
- async executeCommand(command, stdin) {
104149
- return new Promise((resolve, reject) => {
104150
- // Parse command and arguments (handle quoted arguments)
104151
- const parts = command.match(/(?:[^\s"]+|"[^"]*")+/g) || [command];
104152
- const cmd = parts[0];
104153
- const args = parts.slice(1).map(arg => arg.replace(/^"(.*)"$/, '$1'));
104154
- const child = (0, child_process_1.spawn)(cmd, args, {
104155
- shell: false,
104156
- stdio: ['pipe', 'pipe', 'pipe'],
104157
- });
104158
- let output = '';
104159
- let error = '';
104160
- child.stdout.on('data', data => {
104161
- output += data.toString();
104162
- });
104163
- child.stderr.on('data', data => {
104164
- error += data.toString();
104165
- });
104166
- // Send stdin data if provided
104167
- if (stdin) {
104168
- child.stdin.write(stdin);
104169
- child.stdin.end();
104170
- }
104171
- child.on('close', _code => {
104172
- // Many tools return non-zero on issues found
104173
- resolve(output || error);
104174
- });
104175
- child.on('error', err => {
104176
- reject(new Error(`Failed to execute ${cmd}: ${err.message}`));
104177
- });
104178
- });
104179
- }
104180
- parseToolOutput(output, _command) {
104181
- const comments = [];
104182
- // This is a simplified parser - real implementation would handle specific tool formats
104183
- const lines = output.split('\n');
104184
- for (const line of lines) {
104185
- // Example: file.js:10:5: error: Missing semicolon
104186
- const match = line.match(/^(.+?):(\d+):(\d+):\s*(critical|error|warning|info):\s*(.+)$/);
104187
- if (match) {
104188
- const severity = match[4];
104189
- comments.push({
104190
- file: match[1],
104191
- line: parseInt(match[2]),
104192
- message: match[5],
104193
- severity: severity,
104194
- category: 'style',
104195
- });
104196
- }
104197
- }
104198
- return comments;
104199
- }
104200
- getSupportedConfigKeys() {
104201
- return ['type', 'exec', 'command', 'stdin', 'timeout', 'workingDirectory'];
104202
- }
104203
- async isAvailable() {
104204
- // Check if common tools are available
104205
- // In a real implementation, this would check for specific tools based on config
104206
- return true;
104207
- }
104208
- getRequirements() {
104209
- return [
104210
- 'External tool must be installed (e.g., eslint, prettier)',
104211
- 'Tool must be accessible in PATH',
104212
- 'Appropriate configuration files for the tool',
104213
- ];
104214
- }
104215
- }
104216
- exports.ToolCheckProvider = ToolCheckProvider;
104217
-
104218
-
104219
104509
  /***/ }),
104220
104510
 
104221
104511
  /***/ 532:
@@ -125696,7 +125986,7 @@ async function delegate({ task, timeout = 300, debug = false, currentIteration =
125696
125986
  console.error(`[DELEGATE] Using binary at: ${binaryPath}`);
125697
125987
  console.error(`[DELEGATE] Command args: ${args.join(" ")}`);
125698
125988
  }
125699
- return new Promise((resolve, reject) => {
125989
+ return new Promise((resolve2, reject) => {
125700
125990
  const delegationSpan = tracer ? tracer.createDelegationSpan(sessionId, task) : null;
125701
125991
  const process2 = (0, import_child_process5.spawn)(binaryPath, args, {
125702
125992
  stdio: ["pipe", "pipe", "pipe"],
@@ -125761,7 +126051,7 @@ async function delegate({ task, timeout = 300, debug = false, currentIteration =
125761
126051
  delegationSpan.end();
125762
126052
  }
125763
126053
  }
125764
- resolve(response);
126054
+ resolve2(response);
125765
126055
  } else {
125766
126056
  const errorMessage = stderr.trim() || `Delegate process failed with exit code ${code}`;
125767
126057
  if (debug) {
@@ -126193,13 +126483,19 @@ For GitHub-compatible mermaid diagrams, avoid single quotes and parentheses in n
126193
126483
 
126194
126484
  **Rules:**
126195
126485
  - NO single quotes in any node labels: 'text' \u2192 "text" or text
126196
- - NO parentheses in square brackets: [Text (detail)] \u2192 [Text - detail]
126486
+ - NO parentheses in square brackets: [Text (detail)] \u2192 [Text - detail]
126197
126487
  - NO complex expressions in diamonds: {a && b} \u2192 {condition}
126488
+ - NO HTML tags in node labels: [<pre>code</pre>] \u2192 ["code block"] or [Code Block]
126198
126489
  - USE single quotes for styles and classes: classDef highlight fill:'#ff9999'
126490
+ - CRITICAL: When using quotes in node labels, place them INSIDE the brackets: ["quoted text"], NOT [quoted text"]
126199
126491
 
126200
126492
  **Examples:**
126201
126493
  - \u2705 [Load Config] ["Run command"] {Valid?}
126494
+ - \u2705 ["depends_on: [generate-items]"] (correct quote placement)
126495
+ - \u2705 ["Code Block"] (clean text instead of HTML)
126202
126496
  - \u274C [Load (config)] [Run 'command'] {isValid('x')}
126497
+ - \u274C [depends_on: [generate-items"] (incorrect quote placement - quote ends inside bracket)
126498
+ - \u274C [<pre>depends_on: [generate-items]</pre>] (HTML tags in node labels)
126203
126499
 
126204
126500
  **Diagram Type Selection:**
126205
126501
 
@@ -127015,7 +127311,7 @@ function createMockProvider() {
127015
127311
  provider: "mock",
127016
127312
  // Mock the doGenerate method used by Vercel AI SDK
127017
127313
  doGenerate: async ({ messages, tools: tools2 }) => {
127018
- await new Promise((resolve) => setTimeout(resolve, 10));
127314
+ await new Promise((resolve2) => setTimeout(resolve2, 10));
127019
127315
  return {
127020
127316
  text: "This is a mock response for testing",
127021
127317
  toolCalls: [],
@@ -128840,7 +129136,7 @@ var ProbeAgent_exports = {};
128840
129136
  __export(ProbeAgent_exports, {
128841
129137
  ProbeAgent: () => ProbeAgent
128842
129138
  });
128843
- var import_anthropic, import_openai, import_google, import_ai2, import_crypto5, import_events2, MAX_TOOL_ITERATIONS, MAX_HISTORY_MESSAGES, ProbeAgent;
129139
+ var import_anthropic, import_openai, import_google, import_ai2, import_crypto5, import_events2, import_fs5, import_promises, import_path7, MAX_TOOL_ITERATIONS, MAX_HISTORY_MESSAGES, SUPPORTED_IMAGE_EXTENSIONS, MAX_IMAGE_FILE_SIZE, ProbeAgent;
128844
129140
  var init_ProbeAgent = __esm({
128845
129141
  "src/agent/ProbeAgent.js"() {
128846
129142
  "use strict";
@@ -128850,6 +129146,9 @@ var init_ProbeAgent = __esm({
128850
129146
  import_ai2 = __nccwpck_require__(86619);
128851
129147
  import_crypto5 = __nccwpck_require__(76982);
128852
129148
  import_events2 = __nccwpck_require__(24434);
129149
+ import_fs5 = __nccwpck_require__(79896);
129150
+ import_promises = __nccwpck_require__(91943);
129151
+ import_path7 = __nccwpck_require__(16928);
128853
129152
  init_tokenCounter();
128854
129153
  init_tools2();
128855
129154
  init_common();
@@ -128860,6 +129159,8 @@ var init_ProbeAgent = __esm({
128860
129159
  init_mcp();
128861
129160
  MAX_TOOL_ITERATIONS = parseInt(process.env.MAX_TOOL_ITERATIONS || "30", 10);
128862
129161
  MAX_HISTORY_MESSAGES = 100;
129162
+ SUPPORTED_IMAGE_EXTENSIONS = ["png", "jpg", "jpeg", "webp", "gif", "bmp", "svg"];
129163
+ MAX_IMAGE_FILE_SIZE = 20 * 1024 * 1024;
128863
129164
  ProbeAgent = class {
128864
129165
  /**
128865
129166
  * Create a new ProbeAgent instance
@@ -128903,6 +129204,8 @@ var init_ProbeAgent = __esm({
128903
129204
  }
128904
129205
  this.initializeTools();
128905
129206
  this.history = [];
129207
+ this.pendingImages = /* @__PURE__ */ new Map();
129208
+ this.currentImages = [];
128906
129209
  this.events = new import_events2.EventEmitter();
128907
129210
  this.enableMcp = !!options.enableMcp || process.env.ENABLE_MCP === "1";
128908
129211
  this.mcpConfigPath = options.mcpConfigPath || null;
@@ -129028,6 +129331,175 @@ var init_ProbeAgent = __esm({
129028
129331
  console.log(`Using Google API with model: ${this.model}${apiUrl ? ` (URL: ${apiUrl})` : ""}`);
129029
129332
  }
129030
129333
  }
129334
+ /**
129335
+ * Process assistant response content and detect/load image references
129336
+ * @param {string} content - The assistant's response content
129337
+ * @returns {Promise<void>}
129338
+ */
129339
+ async processImageReferences(content) {
129340
+ if (!content) return;
129341
+ const extensionsPattern = `(?:${SUPPORTED_IMAGE_EXTENSIONS.join("|")})`;
129342
+ const imagePatterns = [
129343
+ // Direct file path mentions: "./screenshot.png", "/path/to/image.jpg", etc.
129344
+ new RegExp(`(?:\\.?\\.\\/)?[^\\s"'<>\\[\\]]+\\.${extensionsPattern}(?!\\w)`, "gi"),
129345
+ // Contextual mentions: "look at image.png", "the file screenshot.jpg shows"
129346
+ new RegExp(`(?:image|file|screenshot|diagram|photo|picture|graphic)\\s*:?\\s*([^\\s"'<>\\[\\]]+\\.${extensionsPattern})(?!\\w)`, "gi"),
129347
+ // Tool result mentions: often contain file paths
129348
+ new RegExp(`(?:found|saved|created|generated).*?([^\\s"'<>\\[\\]]+\\.${extensionsPattern})(?!\\w)`, "gi")
129349
+ ];
129350
+ const foundPaths = /* @__PURE__ */ new Set();
129351
+ for (const pattern of imagePatterns) {
129352
+ let match;
129353
+ while ((match = pattern.exec(content)) !== null) {
129354
+ const imagePath = match[1] || match[0];
129355
+ if (imagePath && imagePath.length > 0) {
129356
+ foundPaths.add(imagePath.trim());
129357
+ }
129358
+ }
129359
+ }
129360
+ if (foundPaths.size === 0) return;
129361
+ if (this.debug) {
129362
+ console.log(`[DEBUG] Found ${foundPaths.size} potential image references:`, Array.from(foundPaths));
129363
+ }
129364
+ for (const imagePath of foundPaths) {
129365
+ await this.loadImageIfValid(imagePath);
129366
+ }
129367
+ }
129368
+ /**
129369
+ * Load and cache an image if it's valid and accessible
129370
+ * @param {string} imagePath - Path to the image file
129371
+ * @returns {Promise<boolean>} - True if image was loaded successfully
129372
+ */
129373
+ async loadImageIfValid(imagePath) {
129374
+ try {
129375
+ if (this.pendingImages.has(imagePath)) {
129376
+ if (this.debug) {
129377
+ console.log(`[DEBUG] Image already loaded: ${imagePath}`);
129378
+ }
129379
+ return true;
129380
+ }
129381
+ const allowedDirs = this.allowedFolders && this.allowedFolders.length > 0 ? this.allowedFolders : [process.cwd()];
129382
+ let absolutePath;
129383
+ let isPathAllowed = false;
129384
+ if ((0, import_path7.isAbsolute)(imagePath)) {
129385
+ absolutePath = imagePath;
129386
+ isPathAllowed = allowedDirs.some((dir) => absolutePath.startsWith((0, import_path7.resolve)(dir)));
129387
+ } else {
129388
+ for (const dir of allowedDirs) {
129389
+ const resolvedPath = (0, import_path7.resolve)(dir, imagePath);
129390
+ if (resolvedPath.startsWith((0, import_path7.resolve)(dir))) {
129391
+ absolutePath = resolvedPath;
129392
+ isPathAllowed = true;
129393
+ break;
129394
+ }
129395
+ }
129396
+ }
129397
+ if (!isPathAllowed) {
129398
+ if (this.debug) {
129399
+ console.log(`[DEBUG] Image path outside allowed directories: ${imagePath}`);
129400
+ }
129401
+ return false;
129402
+ }
129403
+ let fileStats;
129404
+ try {
129405
+ fileStats = await (0, import_promises.stat)(absolutePath);
129406
+ } catch (error) {
129407
+ if (this.debug) {
129408
+ console.log(`[DEBUG] Image file not found: ${absolutePath}`);
129409
+ }
129410
+ return false;
129411
+ }
129412
+ if (fileStats.size > MAX_IMAGE_FILE_SIZE) {
129413
+ if (this.debug) {
129414
+ console.log(`[DEBUG] Image file too large: ${absolutePath} (${fileStats.size} bytes, max: ${MAX_IMAGE_FILE_SIZE})`);
129415
+ }
129416
+ return false;
129417
+ }
129418
+ const extension = absolutePath.toLowerCase().split(".").pop();
129419
+ if (!SUPPORTED_IMAGE_EXTENSIONS.includes(extension)) {
129420
+ if (this.debug) {
129421
+ console.log(`[DEBUG] Unsupported image format: ${extension}`);
129422
+ }
129423
+ return false;
129424
+ }
129425
+ const mimeTypes = {
129426
+ "png": "image/png",
129427
+ "jpg": "image/jpeg",
129428
+ "jpeg": "image/jpeg",
129429
+ "webp": "image/webp",
129430
+ "gif": "image/gif",
129431
+ "bmp": "image/bmp",
129432
+ "svg": "image/svg+xml"
129433
+ };
129434
+ const mimeType = mimeTypes[extension];
129435
+ const fileBuffer = await (0, import_promises.readFile)(absolutePath);
129436
+ const base64Data = fileBuffer.toString("base64");
129437
+ const dataUrl = `data:${mimeType};base64,${base64Data}`;
129438
+ this.pendingImages.set(imagePath, dataUrl);
129439
+ if (this.debug) {
129440
+ console.log(`[DEBUG] Successfully loaded image: ${imagePath} (${fileBuffer.length} bytes)`);
129441
+ }
129442
+ return true;
129443
+ } catch (error) {
129444
+ if (this.debug) {
129445
+ console.log(`[DEBUG] Failed to load image ${imagePath}: ${error.message}`);
129446
+ }
129447
+ return false;
129448
+ }
129449
+ }
129450
+ /**
129451
+ * Get all currently loaded images as an array for AI model consumption
129452
+ * @returns {Array<string>} - Array of base64 data URLs
129453
+ */
129454
+ getCurrentImages() {
129455
+ return Array.from(this.pendingImages.values());
129456
+ }
129457
+ /**
129458
+ * Clear loaded images (useful for new conversations)
129459
+ */
129460
+ clearLoadedImages() {
129461
+ this.pendingImages.clear();
129462
+ this.currentImages = [];
129463
+ if (this.debug) {
129464
+ console.log("[DEBUG] Cleared all loaded images");
129465
+ }
129466
+ }
129467
+ /**
129468
+ * Prepare messages for AI consumption, adding images to the latest user message if available
129469
+ * @param {Array} messages - Current conversation messages
129470
+ * @returns {Array} - Messages formatted for AI SDK with potential image content
129471
+ */
129472
+ prepareMessagesWithImages(messages) {
129473
+ const loadedImages = this.getCurrentImages();
129474
+ if (loadedImages.length === 0) {
129475
+ return messages;
129476
+ }
129477
+ const messagesWithImages = [...messages];
129478
+ const lastUserMessageIndex = messagesWithImages.map((m) => m.role).lastIndexOf("user");
129479
+ if (lastUserMessageIndex === -1) {
129480
+ if (this.debug) {
129481
+ console.log("[DEBUG] No user messages found to attach images to");
129482
+ }
129483
+ return messages;
129484
+ }
129485
+ const lastUserMessage = messagesWithImages[lastUserMessageIndex];
129486
+ if (typeof lastUserMessage.content === "string") {
129487
+ messagesWithImages[lastUserMessageIndex] = {
129488
+ ...lastUserMessage,
129489
+ content: [
129490
+ { type: "text", text: lastUserMessage.content },
129491
+ ...loadedImages.map((imageData) => ({
129492
+ type: "image",
129493
+ image: imageData
129494
+ }))
129495
+ ]
129496
+ };
129497
+ if (this.debug) {
129498
+ console.log(`[DEBUG] Added ${loadedImages.length} images to the latest user message`);
129499
+ }
129500
+ }
129501
+ return messagesWithImages;
129502
+ }
129031
129503
  /**
129032
129504
  * Initialize mock model for testing
129033
129505
  */
@@ -129127,7 +129599,7 @@ Examples:
129127
129599
  </extract>
129128
129600
 
129129
129601
  <attempt_completion>
129130
- <result>The configuration is loaded from src/config.js lines 15-25 which contains the database settings.</result>
129602
+ The configuration is loaded from src/config.js lines 15-25 which contains the database settings.
129131
129603
  </attempt_completion>
129132
129604
 
129133
129605
  # Special Case: Quick Completion
@@ -129154,7 +129626,7 @@ I need to find code related to error handling in the search module. The most app
129154
129626
  6. Wait for the tool execution result, which will be provided in the next message (within a <tool_result> block).
129155
129627
  7. Analyze the tool result and decide the next step. If more tool calls are needed, repeat steps 2-6.
129156
129628
  8. If the task is fully complete and all previous steps were successful, use the \`<attempt_completion>\` tool to provide the final answer. This is the ONLY way to finish the task.
129157
- 9. If you cannot proceed (e.g., missing information, invalid request), explain the issue clearly before using \`<attempt_completion>\` with an appropriate message in the \`<result>\` tag.
129629
+ 9. If you cannot proceed (e.g., missing information, invalid request), use \`<attempt_completion>\` to explain the issue clearly with an appropriate message directly inside the tags.
129158
129630
  10. If your previous response was already correct and complete, you may use \`<attempt_complete>\` as a shorthand.
129159
129631
 
129160
129632
  Available Tools:
@@ -129409,9 +129881,10 @@ You are working with a repository located at: ${searchDirectory}
129409
129881
  let assistantResponseContent = "";
129410
129882
  try {
129411
129883
  const executeAIRequest = async () => {
129884
+ const messagesForAI = this.prepareMessagesWithImages(currentMessages);
129412
129885
  const result = await (0, import_ai2.streamText)({
129413
129886
  model: this.provider(this.model),
129414
- messages: currentMessages,
129887
+ messages: messagesForAI,
129415
129888
  maxTokens: maxResponseTokens,
129416
129889
  temperature: 0.3
129417
129890
  });
@@ -129445,6 +129918,9 @@ You are working with a repository located at: ${searchDirectory}
129445
129918
  const assistantPreview = createMessagePreview(assistantResponseContent);
129446
129919
  console.log(`[DEBUG] Assistant response (${assistantResponseContent.length} chars): ${assistantPreview}`);
129447
129920
  }
129921
+ if (assistantResponseContent) {
129922
+ await this.processImageReferences(assistantResponseContent);
129923
+ }
129448
129924
  const validTools = [
129449
129925
  "search",
129450
129926
  "query",
@@ -129569,12 +130045,17 @@ ${toolResultContent}
129569
130045
  throw toolError;
129570
130046
  }
129571
130047
  currentMessages.push({ role: "assistant", content: assistantResponseContent });
130048
+ const toolResultContent = typeof toolResult === "string" ? toolResult : JSON.stringify(toolResult, null, 2);
130049
+ const toolResultMessage = `<tool_result>
130050
+ ${toolResultContent}
130051
+ </tool_result>`;
129572
130052
  currentMessages.push({
129573
130053
  role: "user",
129574
- content: `<tool_result>
129575
- ${typeof toolResult === "string" ? toolResult : JSON.stringify(toolResult, null, 2)}
129576
- </tool_result>`
130054
+ content: toolResultMessage
129577
130055
  });
130056
+ if (toolResultContent) {
130057
+ await this.processImageReferences(toolResultContent);
130058
+ }
129578
130059
  if (this.debug) {
129579
130060
  console.log(`[DEBUG] Tool ${toolName} executed successfully. Result length: ${typeof toolResult === "string" ? toolResult.length : JSON.stringify(toolResult).length}`);
129580
130061
  }
@@ -130072,12 +130553,12 @@ function initializeSimpleTelemetryFromOptions(options) {
130072
130553
  });
130073
130554
  return telemetry;
130074
130555
  }
130075
- var import_fs5, import_path7, SimpleTelemetry, SimpleAppTracer;
130556
+ var import_fs6, import_path8, SimpleTelemetry, SimpleAppTracer;
130076
130557
  var init_simpleTelemetry = __esm({
130077
130558
  "src/agent/simpleTelemetry.js"() {
130078
130559
  "use strict";
130079
- import_fs5 = __nccwpck_require__(79896);
130080
- import_path7 = __nccwpck_require__(16928);
130560
+ import_fs6 = __nccwpck_require__(79896);
130561
+ import_path8 = __nccwpck_require__(16928);
130081
130562
  SimpleTelemetry = class {
130082
130563
  constructor(options = {}) {
130083
130564
  this.serviceName = options.serviceName || "probe-agent";
@@ -130091,11 +130572,11 @@ var init_simpleTelemetry = __esm({
130091
130572
  }
130092
130573
  initializeFileExporter() {
130093
130574
  try {
130094
- const dir = (0, import_path7.dirname)(this.filePath);
130095
- if (!(0, import_fs5.existsSync)(dir)) {
130096
- (0, import_fs5.mkdirSync)(dir, { recursive: true });
130575
+ const dir = (0, import_path8.dirname)(this.filePath);
130576
+ if (!(0, import_fs6.existsSync)(dir)) {
130577
+ (0, import_fs6.mkdirSync)(dir, { recursive: true });
130097
130578
  }
130098
- this.stream = (0, import_fs5.createWriteStream)(this.filePath, { flags: "a" });
130579
+ this.stream = (0, import_fs6.createWriteStream)(this.filePath, { flags: "a" });
130099
130580
  this.stream.on("error", (error) => {
130100
130581
  console.error(`[SimpleTelemetry] Stream error: ${error.message}`);
130101
130582
  });
@@ -130156,20 +130637,20 @@ var init_simpleTelemetry = __esm({
130156
130637
  }
130157
130638
  async flush() {
130158
130639
  if (this.stream) {
130159
- return new Promise((resolve) => {
130160
- this.stream.once("drain", resolve);
130640
+ return new Promise((resolve2) => {
130641
+ this.stream.once("drain", resolve2);
130161
130642
  if (!this.stream.writableNeedDrain) {
130162
- resolve();
130643
+ resolve2();
130163
130644
  }
130164
130645
  });
130165
130646
  }
130166
130647
  }
130167
130648
  async shutdown() {
130168
130649
  if (this.stream) {
130169
- return new Promise((resolve) => {
130650
+ return new Promise((resolve2) => {
130170
130651
  this.stream.end(() => {
130171
130652
  console.log(`[SimpleTelemetry] File stream closed: ${this.filePath}`);
130172
- resolve();
130653
+ resolve2();
130173
130654
  });
130174
130655
  });
130175
130656
  }