octocode-mcp 2.3.13 → 2.3.16

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.
@@ -85,7 +85,14 @@ const ALLOWED_NPM_COMMANDS = [
85
85
  'whoami',
86
86
  ];
87
87
  // Allowed command prefixes - this prevents shell injection by restricting to safe commands
88
- const ALLOWED_GH_COMMANDS = ['search', 'api', 'auth', 'org', 'pr'];
88
+ const ALLOWED_GH_COMMANDS = [
89
+ 'search',
90
+ 'api',
91
+ 'auth',
92
+ 'org',
93
+ 'pr',
94
+ 'repo',
95
+ ];
89
96
  function createSuccessResult(data) {
90
97
  return {
91
98
  content: [{ type: 'text', text: JSON.stringify(data, null, 2) }],
@@ -247,10 +254,10 @@ async function executeGitHubCommand(command, args = [], options = {}) {
247
254
  // Get shell configuration
248
255
  const shellConfig = getShellConfig(options.windowsShell);
249
256
  // Build command with validated prefix and properly escaped arguments
250
- // For GitHub search commands, we need minimal escaping to avoid interfering with GitHub CLI
251
257
  const escapedArgs = args.map((arg, index) => {
252
258
  const isMainQueryArgument = command === 'search' && index === 1;
253
259
  const isCliFlag = arg.startsWith('--');
260
+ const isApiPath = command === 'api' && arg.startsWith('/');
254
261
  // CLI flags like --language=javascript, --repo=owner/repo need minimal escaping
255
262
  if (isCliFlag) {
256
263
  // Only escape CLI flags if they contain dangerous shell characters
@@ -259,6 +266,22 @@ async function executeGitHubCommand(command, args = [], options = {}) {
259
266
  }
260
267
  return arg;
261
268
  }
269
+ // API paths with query parameters need proper escaping
270
+ if (isApiPath) {
271
+ // Always quote API paths that contain query parameters or special characters
272
+ if (arg.includes('?') || arg.includes('&') || /[\s*?[\]{}]/.test(arg)) {
273
+ if (shellConfig.type === 'unix') {
274
+ return `'${arg.replace(/'/g, "'\"'\"'")}'`;
275
+ }
276
+ else if (shellConfig.type === 'cmd') {
277
+ return `"${arg.replace(/"/g, '""')}"`;
278
+ }
279
+ else {
280
+ return `'${arg.replace(/'/g, "''")}'`;
281
+ }
282
+ }
283
+ return arg;
284
+ }
262
285
  // For search queries, only escape if absolutely necessary for shell safety
263
286
  if (isMainQueryArgument) {
264
287
  // Only escape if the argument contains shell metacharacters that could be dangerous
@@ -294,6 +317,29 @@ async function executeGitHubCommand(command, args = [], options = {}) {
294
317
  }
295
318
  return executeGhCommand();
296
319
  }
320
+ /**
321
+ * Helper function to detect shell configuration errors vs real GitHub errors
322
+ */
323
+ function isShellConfigurationError(errorText) {
324
+ if (!errorText)
325
+ return false;
326
+ // Preserve GitHub API errors
327
+ if (errorText.includes('HTTP 404') ||
328
+ errorText.includes('HTTP 403') ||
329
+ errorText.includes('Not Found') ||
330
+ errorText.includes('API rate limit')) {
331
+ return false;
332
+ }
333
+ // Shell configuration conflicts to ignore
334
+ return (errorText.includes('head: illegal option') ||
335
+ errorText.includes('head: |: No such file or directory') ||
336
+ errorText.includes('head: cat: No such file or directory') ||
337
+ /^head:\s+/.test(errorText) ||
338
+ /^\s*head:\s+/.test(errorText) ||
339
+ (errorText.includes('No such file or directory') &&
340
+ !errorText.includes('HTTP') &&
341
+ !errorText.includes('gh:')));
342
+ }
297
343
  /**
298
344
  * Execute shell commands with improved environment handling and error detection
299
345
  */
@@ -329,13 +375,8 @@ async function executeCommand(fullCommand, type, options = {}, shellConfig) {
329
375
  : stderr &&
330
376
  !stderr.includes('Warning:') &&
331
377
  !stderr.includes('notice:') &&
332
- // Ignore shell configuration conflicts - common in development environments
333
- !stderr.includes('No such file or directory') &&
334
- !stderr.includes('head: illegal option') &&
335
- !stderr.includes('head: |: No such file or directory') &&
336
- !stderr.includes('head: cat: No such file or directory') &&
337
- !/^head:\s+/.test(stderr) && // Ignore all head command errors (shell conflicts)
338
- !/^\s*head:\s+/.test(stderr) && // Ignore head errors with leading whitespace
378
+ // Ignore shell configuration conflicts but preserve GitHub API errors
379
+ !isShellConfigurationError(stderr) &&
339
380
  stderr.trim() !== '';
340
381
  if (shouldTreatAsError) {
341
382
  const errorType = type === 'npm' ? 'NPM command error' : 'GitHub CLI command error';
@@ -929,10 +970,7 @@ function createToolSuggestion(currentTool, suggestedTools) {
929
970
  }
930
971
 
931
972
  const API_STATUS_CHECK_TOOL_NAME = 'apiStatusCheck';
932
- const DESCRIPTION$9 = `initial tool to verify user connections
933
-
934
- - Github: check gh login status and list available organizations.
935
- - Npm: check npm login status and list npm registry.`;
973
+ const DESCRIPTION$9 = `Check user status: GitHub/NPM connections, organizations, and current timestamp. Essential for understanding user's data access and API capabilities.`;
936
974
  // Helper function to parse execution results with proper typing
937
975
  function parseExecResult(result) {
938
976
  if (!result.isError && result.content?.[0]?.text) {
@@ -1053,6 +1091,7 @@ function registerApiStatusCheckTool(server) {
1053
1091
  }
1054
1092
  return createResult({
1055
1093
  data: {
1094
+ timestamp: new Date().toISOString(),
1056
1095
  login: {
1057
1096
  github: {
1058
1097
  connected: githubConnected,
@@ -1090,24 +1129,12 @@ const GITHUB_SEARCH_CODE_TOOL_NAME = 'githubSearchCode';
1090
1129
  const DESCRIPTION$8 = `Search code across GitHub repositories using GitHub's code search API via GitHub CLI.
1091
1130
 
1092
1131
  SEARCH STRATEGY FOR BEST RESULTS:
1093
-
1094
- EXACT vs TERMS (Choose ONE):
1095
- - exactQuery: Use for exact phrase matching
1096
- - queryTerms: Use minimal words for broader coverage
1097
-
1098
- TERM OPTIMIZATION:
1099
- - BEST: Single terms for maximum coverage
1100
- - GOOD: 2-3 minimal terms
1101
- - AVOID: Long phrases in queryTerms
1102
-
1103
- MULTI-SEARCH STRATEGY:
1132
+ - use 'exactQuery' for exact phrase matching
1133
+ - use 'queryTerms' for minimal words for broader coverage (recommended: 2-3 terms)
1134
+ - queryTerms can be exacts strings or words (e.g. term1, "term2 exact string"...)
1104
1135
  - Use separate searches for different aspects
1105
1136
  - Separate searches provide broader coverage than complex queries
1106
-
1107
- Filter Usage:
1108
- - Use filters to narrow scope: language, owner, repo, filename
1109
- - Combine filters strategically: language + owner for organization-wide searches
1110
- - Never use filters on exploratory searches - use to refine results`;
1137
+ - Use filters to narrow scope (never use filters on exploratory searches - use to refine results)`;
1111
1138
  function registerGitHubSearchCodeTool(server) {
1112
1139
  server.registerTool(GITHUB_SEARCH_CODE_TOOL_NAME, {
1113
1140
  description: DESCRIPTION$8,
@@ -111960,7 +111987,10 @@ async function fetchGitHubFileContent(params) {
111960
111987
  const { owner, repo, branch, filePath } = params;
111961
111988
  try {
111962
111989
  // Try to fetch file content directly
111963
- const apiPath = `/repos/${owner}/${repo}/contents/${filePath}?ref=${branch}`;
111990
+ const isCommitSha = branch.match(/^[0-9a-f]{40}$/);
111991
+ const apiPath = isCommitSha
111992
+ ? `/repos/${owner}/${repo}/contents/${filePath}?ref=${branch}` // Use contents API with ref for commit SHA
111993
+ : `/repos/${owner}/${repo}/contents/${filePath}?ref=${branch}`; // Use contents API for branches/tags
111964
111994
  const result = await executeGitHubCommand('api', [apiPath], {
111965
111995
  cache: false,
111966
111996
  });
@@ -112297,14 +112327,19 @@ async function suggestCodeSearchFallback(owner, filePath) {
112297
112327
  const GITHUB_SEARCH_REPOSITORIES_TOOL_NAME = 'githubSearchRepositories';
112298
112328
  const DESCRIPTION$6 = `Search GitHub repositories using gh search repos CLI.
112299
112329
 
112330
+ THREE SEARCH APPROACHES:
112331
+ - exactQuery: Single exact phrase/word search
112332
+ - queryTerms: Multiple search terms (broader coverage)
112333
+ - topic: Find repositories by technology/subject
112334
+
112300
112335
  BEST PRACTICES:
112301
- - Use topic for discovering repositories by technology/purpose
112302
- - Use query for searching by repository name
112303
- - Use owner to explore specific organizations
112304
- - Use language to filter by programming language
112305
- - Use quality filters (stars, forks) for refinement
112336
+ - Use exactQuery for specific repository names
112337
+ - Use queryTerms with minimal words for broader results
112338
+ - Use topic for technology/subject discovery
112339
+ - Use owner for organization exploration
112340
+ - Use filters (language, stars, forks) for refinement
112306
112341
 
112307
- Separate searches for different topics and use minimal filters to get the most relevant results`;
112342
+ Multiple focused searches work better than complex single queries.`;
112308
112343
  /**
112309
112344
  * Extract owner/repo information from various query formats
112310
112345
  */
@@ -112339,10 +112374,14 @@ function registerSearchGitHubReposTool(server) {
112339
112374
  server.registerTool(GITHUB_SEARCH_REPOSITORIES_TOOL_NAME, {
112340
112375
  description: DESCRIPTION$6,
112341
112376
  inputSchema: {
112342
- query: z
112377
+ exactQuery: z
112343
112378
  .string()
112344
112379
  .optional()
112345
- .describe('Search by repository name. Use minimal words for repository names or specific projects. For topic discovery, use topic parameter instead.'),
112380
+ .describe('Single exact phrase/word search'),
112381
+ queryTerms: z
112382
+ .array(z.string())
112383
+ .optional()
112384
+ .describe('Multiple search terms for broader coverage'),
112346
112385
  // CORE FILTERS (GitHub CLI flags)
112347
112386
  owner: z
112348
112387
  .union([z.string(), z.array(z.string())])
@@ -112364,7 +112403,7 @@ function registerSearchGitHubReposTool(server) {
112364
112403
  topic: z
112365
112404
  .union([z.string(), z.array(z.string())])
112366
112405
  .optional()
112367
- .describe('Discover repositories by topic. Use for exploring unknown domains and finding projects by technology or purpose.'),
112406
+ .describe('Find repositories by technology/subject'),
112368
112407
  forks: z
112369
112408
  .union([
112370
112409
  z.number().int().min(0),
@@ -112487,15 +112526,29 @@ function registerSearchGitHubReposTool(server) {
112487
112526
  openWorldHint: true,
112488
112527
  },
112489
112528
  }, async (args) => {
112529
+ // Validate that exactly one search parameter is provided (not both)
112530
+ const hasExactQuery = !!args.exactQuery;
112531
+ const hasQueryTerms = args.queryTerms && args.queryTerms.length > 0;
112532
+ if (hasExactQuery && hasQueryTerms) {
112533
+ return createResult({
112534
+ error: 'Use either exactQuery OR queryTerms, not both. Choose one search approach.',
112535
+ });
112536
+ }
112490
112537
  try {
112491
112538
  // Extract owner/repo from query if present
112492
- const queryInfo = args.query
112493
- ? extractOwnerRepoFromQuery(args.query)
112494
- : {
112495
- cleanedQuery: '',
112496
- extractedOwner: undefined,
112497
- extractedRepo: undefined,
112498
- };
112539
+ const queryInfo = args.exactQuery
112540
+ ? extractOwnerRepoFromQuery(args.exactQuery)
112541
+ : args.queryTerms
112542
+ ? {
112543
+ cleanedQuery: args.queryTerms.join(' '),
112544
+ extractedOwner: undefined,
112545
+ extractedRepo: undefined,
112546
+ }
112547
+ : {
112548
+ cleanedQuery: '',
112549
+ extractedOwner: undefined,
112550
+ extractedRepo: undefined,
112551
+ };
112499
112552
  // Merge extracted owner with explicit owner parameter
112500
112553
  let finalOwner = args.owner;
112501
112554
  if (queryInfo.extractedOwner && !finalOwner) {
@@ -112504,11 +112557,13 @@ function registerSearchGitHubReposTool(server) {
112504
112557
  // Update parameters with extracted information
112505
112558
  const enhancedArgs = {
112506
112559
  ...args,
112507
- query: queryInfo.cleanedQuery || args.query,
112560
+ exactQuery: args.exactQuery ? queryInfo.cleanedQuery : undefined,
112561
+ queryTerms: args.queryTerms,
112508
112562
  owner: finalOwner,
112509
112563
  };
112510
112564
  // Enhanced validation logic for primary filters
112511
- const hasPrimaryFilter = enhancedArgs.query?.trim() ||
112565
+ const hasPrimaryFilter = enhancedArgs.exactQuery?.trim() ||
112566
+ (enhancedArgs.queryTerms && enhancedArgs.queryTerms.length > 0) ||
112512
112567
  enhancedArgs.owner ||
112513
112568
  enhancedArgs.language ||
112514
112569
  enhancedArgs.topic ||
@@ -112516,11 +112571,14 @@ function registerSearchGitHubReposTool(server) {
112516
112571
  enhancedArgs.forks;
112517
112572
  if (!hasPrimaryFilter) {
112518
112573
  return createResult({
112519
- error: `Repository search requires at least one filter. Try these patterns:
112520
- • Topic exploration: { topic: ["react", "typescript"] }
112521
- Organization search: { owner: "microsoft", visibility: "public" }
112522
- Language + quality: { language: "go", "good-first-issues": ">=10" }
112523
- Simple query: { query: "cli shell" }`,
112574
+ error: `Repository search requires at least one filter. Try:
112575
+ • Topic search: { topic: "react" }
112576
+ Exact search: { exactQuery: "cli shell" }
112577
+ Multiple terms: { queryTerms: ["react", "hooks"] }
112578
+ Organization: { owner: "microsoft" }
112579
+ • Language filter: { language: "go" }
112580
+
112581
+ Alternative: Use npm search for package discovery.`,
112524
112582
  });
112525
112583
  }
112526
112584
  // First attempt: Search with current parameters
@@ -112530,19 +112588,21 @@ function registerSearchGitHubReposTool(server) {
112530
112588
  // Smart fallbacks based on error type
112531
112589
  if (errorMsg.includes('rate limit')) {
112532
112590
  return createResult({
112533
- error: `GitHub API rate limit. Smart alternatives:
112534
- Try npm package search for package discovery
112535
- Use broader filters (remove stars/forks constraints)
112536
- • Search fewer organizations at once
112591
+ error: `GitHub API rate limit exceeded. Alternatives:
112592
+ Use npm search for package discovery
112593
+ Remove quality filters (stars/forks)
112594
+ • Search fewer organizations
112537
112595
  • Wait 5-10 minutes and retry`,
112538
112596
  });
112539
112597
  }
112540
112598
  if (errorMsg.includes('authentication')) {
112541
112599
  return createResult({
112542
- error: `Authentication required. Quick fix:
112543
- 1. Run: gh auth login
112544
- 2. For private repos: use api_status_check to verify access
112545
- 3. Public repos should work without auth - check query`,
112600
+ error: `Authentication required:
112601
+ Run: gh auth login
112602
+ For private repos: use api_status_check tool
112603
+ Public repos work without auth
112604
+
112605
+ Alternative: Use npm search for packages.`,
112546
112606
  });
112547
112607
  }
112548
112608
  return result; // Return original error for other cases
@@ -112552,31 +112612,26 @@ function registerSearchGitHubReposTool(server) {
112552
112612
  const hasResults = resultData.total_count > 0;
112553
112613
  // Smart fallback strategies for no results
112554
112614
  if (!hasResults) {
112555
- const fallbackSuggestions = [];
112556
- if (enhancedArgs.query) {
112557
- fallbackSuggestions.push('• Try broader search terms or remove query filter');
112615
+ const suggestions = [];
112616
+ if (enhancedArgs.exactQuery) {
112617
+ suggestions.push('• Try broader search terms');
112558
112618
  }
112559
112619
  if (enhancedArgs.language) {
112560
- fallbackSuggestions.push('• Remove language filter for broader discovery');
112620
+ suggestions.push('• Remove language filter');
112561
112621
  }
112562
112622
  if (enhancedArgs.stars || enhancedArgs.forks) {
112563
- fallbackSuggestions.push('• Lower quality thresholds (stars/forks)');
112623
+ suggestions.push('• Lower quality thresholds');
112564
112624
  }
112565
112625
  if (!enhancedArgs.topic) {
112566
- fallbackSuggestions.push('• 🎯 Try topic exploration: { topic: ["web", "api"] }');
112626
+ suggestions.push('• Try topic search: { topic: "react" }');
112567
112627
  }
112568
112628
  if (enhancedArgs.owner) {
112569
- fallbackSuggestions.push('• Search without owner filter for global discovery');
112570
- fallbackSuggestions.push('• Use npm package search if looking for packages');
112629
+ suggestions.push('• Remove owner filter for global search');
112571
112630
  }
112631
+ suggestions.push('• Use npm search for package discovery');
112572
112632
  return createResult({
112573
- error: `No repositories found. Try these alternatives:
112574
- ${fallbackSuggestions.join('\n')}
112575
-
112576
- Quick discovery patterns:
112577
- • 🎯 Topic exploration: { topic: ["react"] }
112578
- • Organization search: { owner: "microsoft" }
112579
- • Quality filter: { stars: ">100", language: "python" }`,
112633
+ error: `No repositories found. Try:
112634
+ ${suggestions.join('\n')}`,
112580
112635
  });
112581
112636
  }
112582
112637
  // Fallback for private repositories: If no results and owner is specified, try with private visibility
@@ -112706,13 +112761,19 @@ async function searchGitHubRepos(params) {
112706
112761
  });
112707
112762
  }
112708
112763
  function buildGitHubReposSearchCommand(params) {
112709
- const query = params.query?.trim() || '';
112710
112764
  const args = ['repos'];
112711
- const hasEmbeddedQualifiers = query &&
112712
- /\b(stars|language|org|repo|topic|user|created|updated|size|license|archived|fork|good-first-issues|help-wanted-issues):/i.test(query);
112713
- if (query) {
112714
- args.push(query);
112765
+ let queryForQualifierCheck = '';
112766
+ if (params.exactQuery) {
112767
+ args.push(params.exactQuery.trim());
112768
+ queryForQualifierCheck = params.exactQuery.trim();
112769
+ }
112770
+ else if (params.queryTerms && params.queryTerms.length > 0) {
112771
+ // Add each term as separate argument for AND logic
112772
+ params.queryTerms.forEach(term => args.push(term.trim()));
112773
+ queryForQualifierCheck = params.queryTerms.join(' ');
112715
112774
  }
112775
+ const hasEmbeddedQualifiers = queryForQualifierCheck &&
112776
+ /\b(stars|language|org|repo|topic|user|created|updated|size|license|archived|fork|good-first-issues|help-wanted-issues):/i.test(queryForQualifierCheck);
112716
112777
  args.push('--json=name,fullName,description,language,stargazersCount,forksCount,updatedAt,createdAt,url,owner,isPrivate,license,hasIssues,openIssuesCount,isArchived,isFork,visibility');
112717
112778
  const addArg = (paramName, cliFlag, condition = true, formatter) => {
112718
112779
  const value = params[paramName];
@@ -112763,12 +112824,13 @@ function buildGitHubReposSearchCommand(params) {
112763
112824
  }
112764
112825
 
112765
112826
  const GITHUB_SEARCH_COMMITS_TOOL_NAME = 'githubSearchCommits';
112766
- const DESCRIPTION$5 = `Search commit history across GitHub repositories. Find commits by message, author, date, or repository. Supports advanced filtering for comprehensive commit analysis. Returns commit SHA, message, author, and date information.
112827
+ const DESCRIPTION$5 = `Search GitHub commits by message, author, or date. Returns SHAs for github_fetch_content (branch=SHA). Can fetch commit content changes (diffs/patches) when getChangesContent=true.
112767
112828
 
112768
- INTEGRATION WORKFLOW:
112769
- - Returned commit SHAs can be used directly with github fetch content (branch=SHA)
112770
- - Use SHA as 'branch' parameter to view files from specific commits
112771
- - Perfect for examining code changes and historical versions`;
112829
+ SEARCH STRATEGY FOR BEST RESULTS:
112830
+ - Use minimal search terms for broader coverage (2-3 words max)
112831
+ - Separate searches for different aspects vs complex queries
112832
+ - Use filters to narrow scope after getting initial results
112833
+ - getChangesContent=true only when analyzing actual code changes`;
112772
112834
  function registerGitHubSearchCommitsTool(server) {
112773
112835
  server.registerTool(GITHUB_SEARCH_COMMITS_TOOL_NAME, {
112774
112836
  description: DESCRIPTION$5,
@@ -112776,16 +112838,16 @@ function registerGitHubSearchCommitsTool(server) {
112776
112838
  query: z
112777
112839
  .string()
112778
112840
  .optional()
112779
- .describe('Search terms. Start simple: "bug fix", "refactor". Use quotes for exact phrases.'),
112841
+ .describe('Commit message search terms (keep minimal for broader results)'),
112780
112842
  // Repository filters
112781
112843
  owner: z
112782
112844
  .string()
112783
112845
  .optional()
112784
- .describe('Repository owner/organization name only (e.g., "facebook", "microsoft"). Do NOT include repository name. Must be used with repo parameter for repository-specific searches.'),
112846
+ .describe('Repository owner (use with repo param)'),
112785
112847
  repo: z
112786
112848
  .string()
112787
112849
  .optional()
112788
- .describe('Repository name only (e.g., "react", "vscode"). Do NOT include owner prefix. Must be used together with owner parameter.'),
112850
+ .describe('Repository name (use with owner param)'),
112789
112851
  // Author filters
112790
112852
  author: z
112791
112853
  .string()
@@ -112813,11 +112875,11 @@ function registerGitHubSearchCommitsTool(server) {
112813
112875
  'author-date': z
112814
112876
  .string()
112815
112877
  .optional()
112816
- .describe('When authored. Format: >2020-01-01, <2023-12-31, 2020-01-01..2023-12-31'),
112878
+ .describe('Filter by author date (e.g., >2020-01-01)'),
112817
112879
  'committer-date': z
112818
112880
  .string()
112819
112881
  .optional()
112820
- .describe('When committed. Format: >2020-01-01, <2023-12-31, 2020-01-01..2023-12-31'),
112882
+ .describe('Filter by commit date (e.g., >2020-01-01)'),
112821
112883
  // Hash filters
112822
112884
  hash: z.string().optional().describe('Commit SHA (full or partial)'),
112823
112885
  parent: z.string().optional().describe('Parent commit SHA'),
@@ -112840,19 +112902,24 @@ function registerGitHubSearchCommitsTool(server) {
112840
112902
  .max(50)
112841
112903
  .optional()
112842
112904
  .default(25)
112843
- .describe('Results limit (1-50). Default: 25'),
112905
+ .describe('Maximum number of results to fetch'),
112844
112906
  sort: z
112845
112907
  .enum(['author-date', 'committer-date'])
112846
112908
  .optional()
112847
- .describe('Sort by date. Default: best match'),
112909
+ .describe('Sort by date field'),
112848
112910
  order: z
112849
112911
  .enum(['asc', 'desc'])
112850
112912
  .optional()
112851
112913
  .default('desc')
112852
- .describe('Sort order. Default: desc'),
112914
+ .describe('Sort order'),
112915
+ getChangesContent: z
112916
+ .boolean()
112917
+ .optional()
112918
+ .default(false)
112919
+ .describe('Get actual code diffs - only use when analyzing changes, not for commit identification'),
112853
112920
  },
112854
112921
  annotations: {
112855
- title: 'GitHub Commit Search - Smart History Analysis',
112922
+ title: 'GitHub Commit Search - Smart & Effective',
112856
112923
  readOnlyHint: true,
112857
112924
  destructiveHint: false,
112858
112925
  idempotentHint: true,
@@ -112870,12 +112937,65 @@ function registerGitHubSearchCommitsTool(server) {
112870
112937
  const items = Array.isArray(commits) ? commits : [];
112871
112938
  // Smart handling for no results - provide actionable suggestions
112872
112939
  if (items.length === 0) {
112940
+ // Progressive simplification strategy based on current search complexity
112941
+ const simplificationSteps = [];
112942
+ let hasFilters = false;
112943
+ // Check for active filters
112944
+ const activeFilters = [];
112945
+ if (args.owner && args.repo)
112946
+ activeFilters.push('repo');
112947
+ if (args.author)
112948
+ activeFilters.push('author');
112949
+ if (args['author-email'])
112950
+ activeFilters.push('author-email');
112951
+ if (args.hash)
112952
+ activeFilters.push('hash');
112953
+ if (args['author-date'])
112954
+ activeFilters.push('date');
112955
+ if (args.visibility)
112956
+ activeFilters.push('visibility');
112957
+ hasFilters = activeFilters.length > 0;
112958
+ // Step 1: If complex query, simplify search terms
112959
+ if (args.query && args.query.trim().split(' ').length > 2) {
112960
+ const words = args.query.trim().split(' ');
112961
+ const simplified = words.slice(0, 1).join(' '); // Take first word only
112962
+ simplificationSteps.push(`Try simpler search: "${simplified}" instead of "${args.query}"`);
112963
+ }
112964
+ // Step 2: Remove filters progressively
112965
+ if (hasFilters) {
112966
+ if (activeFilters.length > 1) {
112967
+ simplificationSteps.push(`Remove some filters (currently: ${activeFilters.join(', ')}) and keep only the most important one`);
112968
+ }
112969
+ else {
112970
+ simplificationSteps.push(`Remove the ${activeFilters[0]} filter and search more broadly`);
112971
+ }
112972
+ }
112973
+ // Step 3: Alternative approaches
112974
+ if (!args.query && hasFilters) {
112975
+ simplificationSteps.push('Add basic search terms like "fix", "update", or "add" with your filters');
112976
+ }
112977
+ // Step 4: Ask for user guidance if no obvious simplification
112978
+ if (simplificationSteps.length === 0) {
112979
+ simplificationSteps.push("Try different keywords or ask the user to be more specific about what commits they're looking for");
112980
+ }
112873
112981
  return createResult({
112874
- error: createNoResultsError('commits'),
112982
+ error: `${createNoResultsError('commits')}
112983
+
112984
+ Try these simplified searches:
112985
+ ${simplificationSteps.map(step => `• ${step}`).join('\n')}
112986
+
112987
+ Or ask the user:
112988
+ • "What specific type of commits are you looking for?"
112989
+ • "Can you provide different keywords to search for?"
112990
+ • "Should I search in a specific repository instead?"
112991
+
112992
+ Alternative tools:
112993
+ • Use github_search_code for file-specific commits
112994
+ • Use github_search_repos to find repositories first`,
112875
112995
  });
112876
112996
  }
112877
112997
  // Transform to optimized format
112878
- const optimizedResult = transformCommitsToOptimizedFormat(items, args);
112998
+ const optimizedResult = await transformCommitsToOptimizedFormat(items, args);
112879
112999
  return createResult({ data: optimizedResult });
112880
113000
  }
112881
113001
  catch (error) {
@@ -112899,22 +113019,75 @@ function registerGitHubSearchCommitsTool(server) {
112899
113019
  /**
112900
113020
  * Transform GitHub CLI response to optimized format
112901
113021
  */
112902
- function transformCommitsToOptimizedFormat(items, _params) {
113022
+ async function transformCommitsToOptimizedFormat(items, params) {
112903
113023
  // Extract repository info if single repo search
112904
113024
  const singleRepo = extractSingleRepository(items);
113025
+ // Fetch diff information if requested and this is a repo-specific search
113026
+ const shouldFetchDiff = params.getChangesContent && params.owner && params.repo;
113027
+ const diffData = new Map();
113028
+ if (shouldFetchDiff && items.length > 0) {
113029
+ // Fetch diff info for each commit (limit to first 10 to avoid rate limits)
113030
+ const commitShas = items.slice(0, 10).map(item => item.sha);
113031
+ const diffPromises = commitShas.map(async (sha) => {
113032
+ try {
113033
+ const diffResult = await executeGitHubCommand('api', [`/repos/${params.owner}/${params.repo}/commits/${sha}`], { cache: false });
113034
+ if (!diffResult.isError) {
113035
+ const diffExecResult = JSON.parse(diffResult.content[0].text);
113036
+ return { sha, commitData: diffExecResult.result };
113037
+ }
113038
+ }
113039
+ catch (e) {
113040
+ // Ignore diff fetch errors
113041
+ }
113042
+ return { sha, commitData: null };
113043
+ });
113044
+ const diffResults = await Promise.all(diffPromises);
113045
+ diffResults.forEach(({ sha, commitData }) => {
113046
+ if (commitData) {
113047
+ diffData.set(sha, commitData);
113048
+ }
113049
+ });
113050
+ }
112905
113051
  const optimizedCommits = items
112906
- .map(item => ({
112907
- sha: item.sha,
112908
- message: getCommitTitle(item.commit?.message ?? ''),
112909
- author: item.commit?.author?.name ?? item.author?.login ?? 'Unknown',
112910
- date: toDDMMYYYY(item.commit?.author?.date ?? ''),
112911
- repository: singleRepo
112912
- ? undefined
112913
- : simplifyRepoUrl(item.repository?.url || ''),
112914
- url: singleRepo
112915
- ? item.sha
112916
- : `${simplifyRepoUrl(item.repository?.url || '')}@${item.sha}`,
112917
- }))
113052
+ .map(item => {
113053
+ const commitObj = {
113054
+ sha: item.sha, // Use as branch parameter in github_fetch_content
113055
+ message: getCommitTitle(item.commit?.message ?? ''),
113056
+ author: item.commit?.author?.name ?? item.author?.login ?? 'Unknown',
113057
+ date: toDDMMYYYY(item.commit?.author?.date ?? ''),
113058
+ repository: singleRepo
113059
+ ? undefined
113060
+ : simplifyRepoUrl(item.repository?.url || ''),
113061
+ url: singleRepo
113062
+ ? item.sha
113063
+ : `${simplifyRepoUrl(item.repository?.url || '')}@${item.sha}`,
113064
+ };
113065
+ // Add diff information if available
113066
+ if (shouldFetchDiff && diffData.has(item.sha)) {
113067
+ const commitData = diffData.get(item.sha);
113068
+ const files = commitData.files || [];
113069
+ commitObj.diff = {
113070
+ changed_files: files.length,
113071
+ additions: commitData.stats?.additions || 0,
113072
+ deletions: commitData.stats?.deletions || 0,
113073
+ total_changes: commitData.stats?.total || 0,
113074
+ files: files
113075
+ .map((f) => ({
113076
+ filename: f.filename,
113077
+ status: f.status,
113078
+ additions: f.additions,
113079
+ deletions: f.deletions,
113080
+ changes: f.changes,
113081
+ patch: f.patch
113082
+ ? f.patch.substring(0, 1000) +
113083
+ (f.patch.length > 1000 ? '...' : '')
113084
+ : undefined,
113085
+ }))
113086
+ .slice(0, 5), // Limit to 5 files per commit
113087
+ };
113088
+ }
113089
+ return commitObj;
113090
+ })
112918
113091
  .map(commit => {
112919
113092
  // Remove undefined fields
112920
113093
  const cleanCommit = {};
@@ -113036,13 +113209,13 @@ function buildGitHubCommitCliArgs(params) {
113036
113209
 
113037
113210
  // TODO: add PR commeents. e.g, gh pr view <PR_NUMBER_OR_URL_OR_BRANCH> --comments
113038
113211
  const GITHUB_SEARCH_PULL_REQUESTS_TOOL_NAME = 'githubSearchPullRequests';
113039
- const DESCRIPTION$4 = `Search GitHub pull requests for code changes, feature implementations, and bug fixes. Find PRs by keywords, state, author, review status, or repository. Returns PR number, title, state, branches, and review information for code review analysis.
113212
+ const DESCRIPTION$4 = `Search GitHub PRs by keywords, state, or author. Returns head/base SHAs for github_fetch_content (branch=SHA). Can fetch PR content changes (diffs/patches) when getChangesContent=true.
113040
113213
 
113041
- INTEGRATION WORKFLOW:
113042
- - Use PR head/base branch names with github_fetch_content to view PR code
113043
- - Repository-specific searches return commit SHAs (head_sha, base_sha) for direct use with github fetch content (branch=SHA)
113044
- - Combine with github_search_commits to find specific commit SHAs from PR
113045
- - Perfect for reviewing implementations and understanding changes`;
113214
+ SEARCH STRATEGY FOR BEST RESULTS:
113215
+ - Use minimal search terms for broader coverage (2-3 words max)
113216
+ - Separate searches for different aspects vs complex queries
113217
+ - Use filters to narrow scope after getting initial results
113218
+ - getChangesContent=true only when analyzing actual code changes`;
113046
113219
  function registerSearchGitHubPullRequestsTool(server) {
113047
113220
  server.registerTool(GITHUB_SEARCH_PULL_REQUESTS_TOOL_NAME, {
113048
113221
  description: DESCRIPTION$4,
@@ -113050,15 +113223,15 @@ function registerSearchGitHubPullRequestsTool(server) {
113050
113223
  query: z
113051
113224
  .string()
113052
113225
  .min(1, 'Search query is required and cannot be empty')
113053
- .describe('Search terms. Start simple: "refactor", "optimization". Use quotes for exact phrases.'),
113226
+ .describe('Search query for PR content (keep minimal for broader results)'),
113054
113227
  owner: z
113055
113228
  .string()
113056
113229
  .optional()
113057
- .describe('Repository owner/organization name only (e.g., "facebook", "microsoft"). Do NOT include repository name. Must be used with repo parameter for repository-specific searches.'),
113230
+ .describe('Repository owner (use with repo param)'),
113058
113231
  repo: z
113059
113232
  .string()
113060
113233
  .optional()
113061
- .describe('Repository name only (e.g., "react", "vscode"). Do NOT include owner prefix. Must be used together with owner parameter.'),
113234
+ .describe('Repository name (use with owner param)'),
113062
113235
  author: z.string().optional().describe('GitHub username of PR author'),
113063
113236
  assignee: z.string().optional().describe('GitHub username of assignee'),
113064
113237
  mentions: z.string().optional().describe('PRs mentioning this user'),
@@ -113075,48 +113248,45 @@ function registerSearchGitHubPullRequestsTool(server) {
113075
113248
  state: z
113076
113249
  .enum(['open', 'closed'])
113077
113250
  .optional()
113078
- .describe('PR state. Default: all'),
113079
- head: z.string().optional().describe('Source branch name'),
113080
- base: z.string().optional().describe('Target branch name'),
113251
+ .describe('Filter by state: open or closed'),
113252
+ head: z.string().optional().describe('Filter on head branch name'),
113253
+ base: z.string().optional().describe('Filter on base branch name'),
113081
113254
  language: z.string().optional().describe('Repository language'),
113082
113255
  created: z
113083
113256
  .string()
113084
113257
  .optional()
113085
- .describe('When created. Format: >2020-01-01'),
113258
+ .describe('Filter by created date (e.g., >2020-01-01)'),
113086
113259
  updated: z
113087
113260
  .string()
113088
113261
  .optional()
113089
- .describe('When updated. Format: >2020-01-01'),
113262
+ .describe('Filter by updated date (e.g., >2020-01-01)'),
113090
113263
  'merged-at': z
113091
113264
  .string()
113092
113265
  .optional()
113093
- .describe('When merged. Format: >2020-01-01'),
113266
+ .describe('Filter by merged date (e.g., >2020-01-01)'),
113094
113267
  closed: z
113095
113268
  .string()
113096
113269
  .optional()
113097
- .describe('When closed. Format: >2020-01-01'),
113098
- draft: z.boolean().optional().describe('Draft PR status'),
113270
+ .describe('Filter by closed date (e.g., >2020-01-01)'),
113271
+ draft: z.boolean().optional().describe('Filter by draft state'),
113099
113272
  checks: z
113100
113273
  .enum(['pending', 'success', 'failure'])
113101
113274
  .optional()
113102
- .describe('CI/CD check status'),
113103
- merged: z
113104
- .boolean()
113105
- .optional()
113106
- .describe('Only merged PRs (true) or unmerged (false)'),
113275
+ .describe('Filter by checks status'),
113276
+ merged: z.boolean().optional().describe('Filter by merged state'),
113107
113277
  review: z
113108
113278
  .enum(['none', 'required', 'approved', 'changes_requested'])
113109
113279
  .optional()
113110
- .describe('Review status filter'),
113111
- app: z.string().optional().describe('GitHub App that created the PR'),
113280
+ .describe('Filter by review status'),
113281
+ app: z.string().optional().describe('Filter by GitHub App author'),
113112
113282
  archived: z
113113
113283
  .boolean()
113114
113284
  .optional()
113115
- .describe('Include archived repositories'),
113285
+ .describe('Filter by repository archived state'),
113116
113286
  comments: z
113117
113287
  .number()
113118
113288
  .optional()
113119
- .describe('Comment count filter. Format: >10, <5, 5..10'),
113289
+ .describe('Filter by number of comments'),
113120
113290
  interactions: z
113121
113291
  .number()
113122
113292
  .optional()
@@ -113124,23 +113294,32 @@ function registerSearchGitHubPullRequestsTool(server) {
113124
113294
  'team-mentions': z
113125
113295
  .string()
113126
113296
  .optional()
113127
- .describe('Team mentioned in PR (@org/team-name)'),
113297
+ .describe('Filter by team mentions'),
113128
113298
  reactions: z
113129
113299
  .number()
113130
113300
  .optional()
113131
- .describe('Reaction count filter. Format: >10'),
113132
- locked: z.boolean().optional().describe('Conversation locked status'),
113133
- 'no-assignee': z.boolean().optional().describe('PRs without assignee'),
113134
- 'no-label': z.boolean().optional().describe('PRs without labels'),
113301
+ .describe('Filter by number of reactions'),
113302
+ locked: z
113303
+ .boolean()
113304
+ .optional()
113305
+ .describe('Filter by locked conversation status'),
113306
+ 'no-assignee': z
113307
+ .boolean()
113308
+ .optional()
113309
+ .describe('Filter by missing assignee'),
113310
+ 'no-label': z.boolean().optional().describe('Filter by missing label'),
113135
113311
  'no-milestone': z
113136
113312
  .boolean()
113137
113313
  .optional()
113138
- .describe('PRs without milestone'),
113139
- 'no-project': z.boolean().optional().describe('PRs not in projects'),
113314
+ .describe('Filter by missing milestone'),
113315
+ 'no-project': z
113316
+ .boolean()
113317
+ .optional()
113318
+ .describe('Filter by missing project'),
113140
113319
  label: z
113141
113320
  .union([z.string(), z.array(z.string())])
113142
113321
  .optional()
113143
- .describe('Label names. Can be single string or array.'),
113322
+ .describe('Filter by label'),
113144
113323
  milestone: z.string().optional().describe('Milestone title'),
113145
113324
  project: z.string().optional().describe('Project board owner/number'),
113146
113325
  visibility: z
@@ -113148,17 +113327,17 @@ function registerSearchGitHubPullRequestsTool(server) {
113148
113327
  .optional()
113149
113328
  .describe('Repository visibility'),
113150
113329
  match: z
113151
- .enum(['title', 'body', 'comments'])
113330
+ .array(z.enum(['title', 'body', 'comments']))
113152
113331
  .optional()
113153
- .describe('Search scope. Default: title and body'),
113332
+ .describe('Restrict search to specific fields'),
113154
113333
  limit: z
113155
113334
  .number()
113156
113335
  .int()
113157
113336
  .min(1)
113158
- .max(50)
113337
+ .max(100)
113159
113338
  .optional()
113160
- .default(25)
113161
- .describe('Results limit (1-50). Default: 25'),
113339
+ .default(30)
113340
+ .describe('Maximum number of results to fetch'),
113162
113341
  sort: z
113163
113342
  .enum([
113164
113343
  'comments',
@@ -113174,15 +113353,20 @@ function registerSearchGitHubPullRequestsTool(server) {
113174
113353
  'updated',
113175
113354
  ])
113176
113355
  .optional()
113177
- .describe('Sort by activity or reactions. Default: best match'),
113356
+ .describe('Sort fetched results'),
113178
113357
  order: z
113179
113358
  .enum(['asc', 'desc'])
113180
113359
  .optional()
113181
113360
  .default('desc')
113182
- .describe('Sort order. Default: desc'),
113361
+ .describe('Order of results (requires --sort)'),
113362
+ getChangesContent: z
113363
+ .boolean()
113364
+ .optional()
113365
+ .default(false)
113366
+ .describe('Get actual code diffs - only use when analyzing changes, not for PR identification'),
113183
113367
  },
113184
113368
  annotations: {
113185
- title: 'GitHub PR Search - Implementation Discovery',
113369
+ title: 'GitHub PR Search - Smart & Effective',
113186
113370
  readOnlyHint: true,
113187
113371
  destructiveHint: false,
113188
113372
  idempotentHint: true,
@@ -113215,6 +113399,52 @@ async function searchGitHubPullRequests(params) {
113215
113399
  const { command, args } = buildGitHubPullRequestsAPICommand(params);
113216
113400
  const result = await executeGitHubCommand(command, args, { cache: false });
113217
113401
  if (result.isError) {
113402
+ const errorMsg = result.content[0].text;
113403
+ // Enhanced error handling for repository-specific searches
113404
+ if (params.owner && params.repo) {
113405
+ // Handle 404 errors with repository and branch checking
113406
+ if (errorMsg.includes('404')) {
113407
+ // Single repository check to avoid duplicate API calls
113408
+ const repoCheckResult = await executeGitHubCommand('api', [`/repos/${params.owner}/${params.repo}`], { cache: false });
113409
+ if (repoCheckResult.isError) {
113410
+ // Repository doesn't exist
113411
+ return createResult({
113412
+ error: `Repository "${params.owner}/${params.repo}" not found. Use github_search_repositories to find the correct repository name.`,
113413
+ });
113414
+ }
113415
+ // Repository exists, check if it's a branch-related error
113416
+ if (params.head || params.base) {
113417
+ try {
113418
+ const repoData = JSON.parse(repoCheckResult.content[0].text);
113419
+ const defaultBranch = repoData.result?.default_branch || 'main';
113420
+ let branchSuggestion = '';
113421
+ if (params.head && params.head !== defaultBranch) {
113422
+ branchSuggestion = `\n\nNote: Branch "${params.head}" may not exist. Repository default branch is "${defaultBranch}".`;
113423
+ branchSuggestion += `\nTry searching without head branch filter or use head="${defaultBranch}".`;
113424
+ }
113425
+ if (params.base && params.base !== defaultBranch) {
113426
+ branchSuggestion += `\n\nNote: Branch "${params.base}" may not exist. Repository default branch is "${defaultBranch}".`;
113427
+ branchSuggestion += `\nTry searching without base branch filter or use base="${defaultBranch}".`;
113428
+ }
113429
+ if (branchSuggestion) {
113430
+ return createResult({
113431
+ error: createNoResultsError('pull_requests') + branchSuggestion,
113432
+ });
113433
+ }
113434
+ }
113435
+ catch (e) {
113436
+ // Continue with original error if parsing fails
113437
+ }
113438
+ }
113439
+ }
113440
+ // Handle rate limit errors
113441
+ if (errorMsg.includes('rate limit') ||
113442
+ errorMsg.includes('API rate limit')) {
113443
+ return createResult({
113444
+ error: ERROR_MESSAGES.RATE_LIMIT_EXCEEDED,
113445
+ });
113446
+ }
113447
+ }
113218
113448
  return result;
113219
113449
  }
113220
113450
  const execResult = JSON.parse(result.content[0].text);
@@ -113224,8 +113454,97 @@ async function searchGitHubPullRequests(params) {
113224
113454
  ? execResult.result
113225
113455
  : execResult.result?.items || [];
113226
113456
  if (pullRequests.length === 0) {
113457
+ // Progressive simplification strategy based on current search complexity
113458
+ const simplificationSteps = [];
113459
+ let hasFilters = false;
113460
+ // Check for active filters
113461
+ const activeFilters = [];
113462
+ if (params.owner && params.repo)
113463
+ activeFilters.push('repo');
113464
+ if (params.author)
113465
+ activeFilters.push('author');
113466
+ if (params.state)
113467
+ activeFilters.push('state');
113468
+ if (params.label)
113469
+ activeFilters.push('label');
113470
+ if (params.head)
113471
+ activeFilters.push('head-branch');
113472
+ if (params.base)
113473
+ activeFilters.push('base-branch');
113474
+ if (params.assignee)
113475
+ activeFilters.push('assignee');
113476
+ if (params.created)
113477
+ activeFilters.push('date');
113478
+ hasFilters = activeFilters.length > 0;
113479
+ // Step 1: If complex query, simplify search terms
113480
+ if (params.query && params.query.trim().split(' ').length > 2) {
113481
+ const words = params.query.trim().split(' ');
113482
+ const simplified = words.slice(0, 1).join(' '); // Take first word only
113483
+ simplificationSteps.push(`Try simpler search: "${simplified}" instead of "${params.query}"`);
113484
+ }
113485
+ // Step 2: Remove filters progressively
113486
+ if (hasFilters) {
113487
+ if (activeFilters.length > 2) {
113488
+ simplificationSteps.push(`Remove some filters (currently: ${activeFilters.join(', ')}) and keep only the most important ones`);
113489
+ }
113490
+ else if (activeFilters.length > 1) {
113491
+ simplificationSteps.push(`Remove one filter (currently: ${activeFilters.join(', ')}) to broaden search`);
113492
+ }
113493
+ else {
113494
+ simplificationSteps.push(`Remove the ${activeFilters[0]} filter and search more broadly`);
113495
+ }
113496
+ }
113497
+ // Step 3: Alternative approaches
113498
+ if (!params.query && hasFilters) {
113499
+ simplificationSteps.push('Add basic search terms like "fix", "feature", "bug", or "update" with your filters');
113500
+ }
113501
+ // Step 4: Repository-specific guidance
113502
+ if (!params.owner && !params.repo) {
113503
+ simplificationSteps.push('Try searching in a specific repository first using owner and repo parameters');
113504
+ }
113505
+ // Step 5: Ask for user guidance if no obvious simplification
113506
+ if (simplificationSteps.length === 0) {
113507
+ simplificationSteps.push("Try different keywords or ask the user to be more specific about what PRs they're looking for");
113508
+ }
113227
113509
  return createResult({
113228
- error: createNoResultsError('pull_requests'),
113510
+ error: `${createNoResultsError('pull_requests')}
113511
+
113512
+ Try these simplified searches:
113513
+ ${simplificationSteps.map(step => `• ${step}`).join('\n')}
113514
+
113515
+ Or ask the user:
113516
+ • "What specific type of pull requests are you looking for?"
113517
+ • "Can you provide different keywords to search for?"
113518
+ • "Should I search in a specific repository instead?"
113519
+ • "Are you looking for open or closed PRs?"
113520
+
113521
+ Alternative tools:
113522
+ • Use github_search_code for PR-related file changes
113523
+ • Use github_search_repos to find repositories first`,
113524
+ });
113525
+ }
113526
+ // Fetch diff information if requested and this is a repo-specific search
113527
+ const shouldFetchDiff = params.getChangesContent && params.owner && params.repo;
113528
+ const diffData = new Map();
113529
+ if (shouldFetchDiff && pullRequests.length > 0) {
113530
+ // Fetch diff info for each PR (limit to first 10 to avoid rate limits)
113531
+ const prNumbers = pullRequests.slice(0, 10).map((pr) => pr.number);
113532
+ const diffPromises = prNumbers.map(async (prNumber) => {
113533
+ try {
113534
+ const diffResult = await executeGitHubCommand('api', [`/repos/${params.owner}/${params.repo}/pulls/${prNumber}/files`], { cache: false });
113535
+ if (!diffResult.isError) {
113536
+ const diffExecResult = JSON.parse(diffResult.content[0].text);
113537
+ return { prNumber, files: diffExecResult.result };
113538
+ }
113539
+ }
113540
+ catch (e) {
113541
+ // Ignore diff fetch errors
113542
+ }
113543
+ return { prNumber, files: [] };
113544
+ });
113545
+ const diffResults = await Promise.all(diffPromises);
113546
+ diffResults.forEach(({ prNumber, files }) => {
113547
+ diffData.set(prNumber, files);
113229
113548
  });
113230
113549
  }
113231
113550
  const cleanPRs = pullRequests.map((pr) => {
@@ -113245,15 +113564,38 @@ async function searchGitHubPullRequests(params) {
113245
113564
  reactions: 0, // Not available in list format
113246
113565
  draft: pr.isDraft || false,
113247
113566
  };
113248
- // Add commit SHAs - this is the key enhancement!
113567
+ // Add commit SHAs for use with github_fetch_content
113568
+ // Use head_sha/base_sha as branch parameter to view PR files
113249
113569
  if (pr.headRefName)
113250
113570
  result.head = pr.headRefName;
113251
113571
  if (pr.headRefOid)
113252
- result.head_sha = pr.headRefOid;
113572
+ result.head_sha = pr.headRefOid; // Use as branch=SHA
113253
113573
  if (pr.baseRefName)
113254
113574
  result.base = pr.baseRefName;
113255
113575
  if (pr.baseRefOid)
113256
- result.base_sha = pr.baseRefOid;
113576
+ result.base_sha = pr.baseRefOid; // Use as branch=SHA
113577
+ // Add diff information if available
113578
+ if (shouldFetchDiff && diffData.has(pr.number)) {
113579
+ const files = diffData.get(pr.number);
113580
+ result.diff = {
113581
+ changed_files: files.length,
113582
+ additions: files.reduce((sum, f) => sum + (f.additions || 0), 0),
113583
+ deletions: files.reduce((sum, f) => sum + (f.deletions || 0), 0),
113584
+ files: files
113585
+ .map((f) => ({
113586
+ filename: f.filename,
113587
+ status: f.status,
113588
+ additions: f.additions,
113589
+ deletions: f.deletions,
113590
+ changes: f.changes,
113591
+ patch: f.patch
113592
+ ? f.patch.substring(0, 1000) +
113593
+ (f.patch.length > 1000 ? '...' : '')
113594
+ : undefined,
113595
+ }))
113596
+ .slice(0, 5), // Limit to 5 files per PR
113597
+ };
113598
+ }
113257
113599
  return result;
113258
113600
  }
113259
113601
  // Handle search API format
@@ -113280,6 +113622,28 @@ async function searchGitHubPullRequests(params) {
113280
113622
  result.head = pr.head.ref;
113281
113623
  if (pr.base?.ref)
113282
113624
  result.base = pr.base.ref;
113625
+ // Add diff information if available (search API format)
113626
+ if (shouldFetchDiff && diffData.has(pr.number)) {
113627
+ const files = diffData.get(pr.number);
113628
+ result.diff = {
113629
+ changed_files: files.length,
113630
+ additions: files.reduce((sum, f) => sum + (f.additions || 0), 0),
113631
+ deletions: files.reduce((sum, f) => sum + (f.deletions || 0), 0),
113632
+ files: files
113633
+ .map((f) => ({
113634
+ filename: f.filename,
113635
+ status: f.status,
113636
+ additions: f.additions,
113637
+ deletions: f.deletions,
113638
+ changes: f.changes,
113639
+ patch: f.patch
113640
+ ? f.patch.substring(0, 1000) +
113641
+ (f.patch.length > 1000 ? '...' : '')
113642
+ : undefined,
113643
+ }))
113644
+ .slice(0, 5), // Limit to 5 files per PR
113645
+ };
113646
+ }
113283
113647
  return result;
113284
113648
  });
113285
113649
  const searchResult = {
@@ -113288,11 +113652,25 @@ async function searchGitHubPullRequests(params) {
113288
113652
  ? cleanPRs.length
113289
113653
  : execResult.result?.total_count || cleanPRs.length,
113290
113654
  };
113291
- return createResult({ data: searchResult });
113655
+ // Add helpful context if filtering by branches that might not exist
113656
+ let additionalContext = '';
113657
+ if (cleanPRs.length === 0 &&
113658
+ params.owner &&
113659
+ params.repo &&
113660
+ (params.head || params.base)) {
113661
+ additionalContext =
113662
+ '\n\nNote: No PRs found with specified branch filters. Consider removing head/base filters or checking if branches exist.';
113663
+ }
113664
+ return createResult({
113665
+ data: searchResult,
113666
+ ...(additionalContext && { message: additionalContext }),
113667
+ });
113292
113668
  });
113293
113669
  }
113294
113670
  function buildGitHubPullRequestsAPICommand(params) {
113295
- // For repository-specific searches, use gh pr list to get commit SHAs
113671
+ // For repository-specific searches, use gh pr list instead of search API
113672
+ // This provides commit SHAs (head_sha, base_sha) which are essential for
113673
+ // integrating with github_fetch_content to view PR code at specific commits
113296
113674
  if (params.owner && params.repo) {
113297
113675
  return buildGitHubPullRequestsListCommand(params);
113298
113676
  }
@@ -113370,10 +113748,13 @@ function buildGitHubPullRequestsAPICommand(params) {
113370
113748
  queryParts.push(`milestone:"${params.milestone}"`);
113371
113749
  if (params.project)
113372
113750
  queryParts.push(`project:${params.project}`);
113751
+ if (params.match) {
113752
+ params.match.forEach(field => queryParts.push(`in:${field}`));
113753
+ }
113373
113754
  // Add type qualifier to search only pull requests
113374
113755
  queryParts.push('type:pr');
113375
113756
  const query = queryParts.filter(Boolean).join(' ');
113376
- const limit = Math.min(params.limit || 25, 100);
113757
+ const limit = Math.min(params.limit || 30, 100);
113377
113758
  let apiPath = `search/issues?q=${encodeURIComponent(query)}&per_page=${limit}`;
113378
113759
  if (params.sort)
113379
113760
  apiPath += `&sort=${params.sort}`;
@@ -113389,7 +113770,7 @@ function buildGitHubPullRequestsListCommand(params) {
113389
113770
  '--json',
113390
113771
  'number,title,headRefName,headRefOid,baseRefName,baseRefOid,state,author,labels,createdAt,updatedAt,url,comments,isDraft',
113391
113772
  '--limit',
113392
- String(Math.min(params.limit || 25, 100)),
113773
+ String(Math.min(params.limit || 30, 100)),
113393
113774
  ];
113394
113775
  // Add filters
113395
113776
  if (params.state) {
@@ -113725,8 +114106,10 @@ async function viewRepositoryStructure(params) {
113725
114106
  try {
113726
114107
  // Clean up path
113727
114108
  const cleanPath = path.startsWith('/') ? path.substring(1) : path;
113728
- // Try the requested branch first
113729
- const apiPath = `/repos/${owner}/${repo}/contents/${cleanPath}?ref=${branch}`;
114109
+ // Try the requested branch first - handle empty path correctly
114110
+ const apiPath = cleanPath
114111
+ ? `/repos/${owner}/${repo}/contents/${cleanPath}?ref=${branch}`
114112
+ : `/repos/${owner}/${repo}/contents?ref=${branch}`;
113730
114113
  const result = await executeGitHubCommand('api', [apiPath], {
113731
114114
  cache: false,
113732
114115
  });