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.
- package/README.md +60 -2
- package/assets/langchainTutorial.gif +0 -0
- package/assets/logo.png +0 -0
- package/assets/reactVSVueJS.gif +0 -0
- package/assets/reactZustand.gif +0 -0
- package/{build → dist}/index.js +545 -162
- package/manifest.json +110 -0
- package/package.json +19 -7
package/{build → dist}/index.js
RENAMED
|
@@ -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 = [
|
|
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
|
|
333
|
-
!stderr
|
|
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 = `
|
|
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
|
-
|
|
1095
|
-
-
|
|
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
|
|
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
|
|
112302
|
-
- Use
|
|
112303
|
-
- Use
|
|
112304
|
-
- Use
|
|
112305
|
-
- Use
|
|
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
|
-
|
|
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
|
-
|
|
112377
|
+
exactQuery: z
|
|
112343
112378
|
.string()
|
|
112344
112379
|
.optional()
|
|
112345
|
-
.describe('
|
|
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('
|
|
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.
|
|
112493
|
-
? extractOwnerRepoFromQuery(args.
|
|
112494
|
-
:
|
|
112495
|
-
|
|
112496
|
-
|
|
112497
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
|
112520
|
-
• Topic
|
|
112521
|
-
•
|
|
112522
|
-
•
|
|
112523
|
-
•
|
|
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.
|
|
112534
|
-
•
|
|
112535
|
-
•
|
|
112536
|
-
• Search fewer organizations
|
|
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
|
|
112543
|
-
|
|
112544
|
-
|
|
112545
|
-
|
|
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
|
|
112556
|
-
if (enhancedArgs.
|
|
112557
|
-
|
|
112615
|
+
const suggestions = [];
|
|
112616
|
+
if (enhancedArgs.exactQuery) {
|
|
112617
|
+
suggestions.push('• Try broader search terms');
|
|
112558
112618
|
}
|
|
112559
112619
|
if (enhancedArgs.language) {
|
|
112560
|
-
|
|
112620
|
+
suggestions.push('• Remove language filter');
|
|
112561
112621
|
}
|
|
112562
112622
|
if (enhancedArgs.stars || enhancedArgs.forks) {
|
|
112563
|
-
|
|
112623
|
+
suggestions.push('• Lower quality thresholds');
|
|
112564
112624
|
}
|
|
112565
112625
|
if (!enhancedArgs.topic) {
|
|
112566
|
-
|
|
112626
|
+
suggestions.push('• Try topic search: { topic: "react" }');
|
|
112567
112627
|
}
|
|
112568
112628
|
if (enhancedArgs.owner) {
|
|
112569
|
-
|
|
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
|
|
112574
|
-
${
|
|
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
|
-
|
|
112712
|
-
|
|
112713
|
-
|
|
112714
|
-
|
|
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
|
|
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
|
-
|
|
112769
|
-
-
|
|
112770
|
-
-
|
|
112771
|
-
-
|
|
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('
|
|
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
|
|
112846
|
+
.describe('Repository owner (use with repo param)'),
|
|
112785
112847
|
repo: z
|
|
112786
112848
|
.string()
|
|
112787
112849
|
.optional()
|
|
112788
|
-
.describe('Repository name
|
|
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('
|
|
112878
|
+
.describe('Filter by author date (e.g., >2020-01-01)'),
|
|
112817
112879
|
'committer-date': z
|
|
112818
112880
|
.string()
|
|
112819
112881
|
.optional()
|
|
112820
|
-
.describe('
|
|
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('
|
|
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
|
|
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
|
|
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
|
|
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,
|
|
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
|
-
|
|
112908
|
-
|
|
112909
|
-
|
|
112910
|
-
|
|
112911
|
-
|
|
112912
|
-
|
|
112913
|
-
|
|
112914
|
-
|
|
112915
|
-
|
|
112916
|
-
|
|
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
|
|
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
|
-
|
|
113042
|
-
- Use
|
|
113043
|
-
-
|
|
113044
|
-
-
|
|
113045
|
-
-
|
|
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
|
|
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
|
|
113230
|
+
.describe('Repository owner (use with repo param)'),
|
|
113058
113231
|
repo: z
|
|
113059
113232
|
.string()
|
|
113060
113233
|
.optional()
|
|
113061
|
-
.describe('Repository name
|
|
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('
|
|
113079
|
-
head: z.string().optional().describe('
|
|
113080
|
-
base: z.string().optional().describe('
|
|
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('
|
|
113258
|
+
.describe('Filter by created date (e.g., >2020-01-01)'),
|
|
113086
113259
|
updated: z
|
|
113087
113260
|
.string()
|
|
113088
113261
|
.optional()
|
|
113089
|
-
.describe('
|
|
113262
|
+
.describe('Filter by updated date (e.g., >2020-01-01)'),
|
|
113090
113263
|
'merged-at': z
|
|
113091
113264
|
.string()
|
|
113092
113265
|
.optional()
|
|
113093
|
-
.describe('
|
|
113266
|
+
.describe('Filter by merged date (e.g., >2020-01-01)'),
|
|
113094
113267
|
closed: z
|
|
113095
113268
|
.string()
|
|
113096
113269
|
.optional()
|
|
113097
|
-
.describe('
|
|
113098
|
-
draft: z.boolean().optional().describe('
|
|
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('
|
|
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('
|
|
113111
|
-
app: z.string().optional().describe('GitHub App
|
|
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('
|
|
113285
|
+
.describe('Filter by repository archived state'),
|
|
113116
113286
|
comments: z
|
|
113117
113287
|
.number()
|
|
113118
113288
|
.optional()
|
|
113119
|
-
.describe('
|
|
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('
|
|
113297
|
+
.describe('Filter by team mentions'),
|
|
113128
113298
|
reactions: z
|
|
113129
113299
|
.number()
|
|
113130
113300
|
.optional()
|
|
113131
|
-
.describe('
|
|
113132
|
-
locked: z
|
|
113133
|
-
|
|
113134
|
-
|
|
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('
|
|
113139
|
-
'no-project': z
|
|
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('
|
|
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('
|
|
113332
|
+
.describe('Restrict search to specific fields'),
|
|
113154
113333
|
limit: z
|
|
113155
113334
|
.number()
|
|
113156
113335
|
.int()
|
|
113157
113336
|
.min(1)
|
|
113158
|
-
.max(
|
|
113337
|
+
.max(100)
|
|
113159
113338
|
.optional()
|
|
113160
|
-
.default(
|
|
113161
|
-
.describe('
|
|
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
|
|
113356
|
+
.describe('Sort fetched results'),
|
|
113178
113357
|
order: z
|
|
113179
113358
|
.enum(['asc', 'desc'])
|
|
113180
113359
|
.optional()
|
|
113181
113360
|
.default('desc')
|
|
113182
|
-
.describe('
|
|
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 -
|
|
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
|
|
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
|
-
|
|
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
|
|
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 ||
|
|
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 ||
|
|
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 =
|
|
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
|
});
|