From d9f2feec1113609c25602760a4ada772e40c685e Mon Sep 17 00:00:00 2001 From: Sam Morrow Date: Wed, 13 May 2026 23:52:05 +0200 Subject: [PATCH 1/4] feat: return minimal code search results with text match snippets Return a MinimalCodeSearchResult type from search_code instead of the raw GitHub API CodeSearchResult. This reduces token usage by ~4x by: - Projecting the repository object to just the full_name string instead of the full ~3KB repository payload repeated per result - Enabling the text-match Accept header so code snippets (fragments) are included in results, which were previously missing Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- pkg/github/minimal_types.go | 17 ++++++++++ pkg/github/search.go | 28 +++++++++++++++-- pkg/github/search_test.go | 62 ++++++++++++++++++++++++------------- 3 files changed, 83 insertions(+), 24 deletions(-) diff --git a/pkg/github/minimal_types.go b/pkg/github/minimal_types.go index a8757c51c3..6bb8acae28 100644 --- a/pkg/github/minimal_types.go +++ b/pkg/github/minimal_types.go @@ -51,6 +51,23 @@ type MinimalSearchRepositoriesResult struct { Items []MinimalRepository `json:"items"` } +// MinimalCodeSearchResult is the trimmed output type for code search results. +type MinimalCodeSearchResult struct { + TotalCount int `json:"total_count"` + IncompleteResults bool `json:"incomplete_results"` + Items []MinimalCodeResult `json:"items"` +} + +// MinimalCodeResult is the trimmed output type for a single code search hit. +type MinimalCodeResult struct { + Name string `json:"name"` + Path string `json:"path"` + SHA string `json:"sha"` + HTMLURL string `json:"html_url"` + Repository string `json:"repository"` + TextMatches []*github.TextMatch `json:"text_matches,omitempty"` +} + // MinimalCommitAuthor represents commit author information. type MinimalCommitAuthor struct { Name string `json:"name,omitempty"` diff --git a/pkg/github/search.go b/pkg/github/search.go index 5009213760..2d0e8d1701 100644 --- a/pkg/github/search.go +++ b/pkg/github/search.go @@ -270,8 +270,9 @@ func SearchCode(t translations.TranslationHelperFunc) inventory.ServerTool { } opts := &github.SearchOptions{ - Sort: sort, - Order: order, + Sort: sort, + Order: order, + TextMatch: true, ListOptions: github.ListOptions{ PerPage: pagination.PerPage, Page: pagination.Page, @@ -301,7 +302,28 @@ func SearchCode(t translations.TranslationHelperFunc) inventory.ServerTool { return ghErrors.NewGitHubAPIStatusErrorResponse(ctx, "failed to search code", resp, body), nil, nil } - r, err := json.Marshal(result) + minimalItems := make([]MinimalCodeResult, 0, len(result.CodeResults)) + for _, code := range result.CodeResults { + item := MinimalCodeResult{ + Name: code.GetName(), + Path: code.GetPath(), + SHA: code.GetSHA(), + HTMLURL: code.GetHTMLURL(), + TextMatches: code.TextMatches, + } + if code.Repository != nil { + item.Repository = code.Repository.GetFullName() + } + minimalItems = append(minimalItems, item) + } + + minimalResult := &MinimalCodeSearchResult{ + TotalCount: result.GetTotal(), + IncompleteResults: result.GetIncompleteResults(), + Items: minimalItems, + } + + r, err := json.Marshal(minimalResult) if err != nil { return utils.NewToolResultErrorFromErr("failed to marshal response", err), nil, nil } diff --git a/pkg/github/search_test.go b/pkg/github/search_test.go index eb5d980753..f39034b230 100644 --- a/pkg/github/search_test.go +++ b/pkg/github/search_test.go @@ -430,18 +430,29 @@ func Test_SearchCode(t *testing.T) { IncompleteResults: github.Ptr(false), CodeResults: []*github.CodeResult{ { - Name: github.Ptr("file1.go"), - Path: github.Ptr("path/to/file1.go"), - SHA: github.Ptr("abc123def456"), - HTMLURL: github.Ptr("https://github.com/owner/repo/blob/main/path/to/file1.go"), - Repository: &github.Repository{Name: github.Ptr("repo"), FullName: github.Ptr("owner/repo")}, + Name: github.Ptr("file1.go"), + Path: github.Ptr("path/to/file1.go"), + SHA: github.Ptr("abc123def456"), + HTMLURL: github.Ptr("https://github.com/owner/repo/blob/main/path/to/file1.go"), + Repository: &github.Repository{ + Name: github.Ptr("repo"), + FullName: github.Ptr("owner/repo"), + }, + TextMatches: []*github.TextMatch{ + { + Fragment: github.Ptr("func main() { fmt.Println(\"hello\") }"), + }, + }, }, { - Name: github.Ptr("file2.go"), - Path: github.Ptr("path/to/file2.go"), - SHA: github.Ptr("def456abc123"), - HTMLURL: github.Ptr("https://github.com/owner/repo/blob/main/path/to/file2.go"), - Repository: &github.Repository{Name: github.Ptr("repo"), FullName: github.Ptr("owner/repo")}, + Name: github.Ptr("file2.go"), + Path: github.Ptr("path/to/file2.go"), + SHA: github.Ptr("def456abc123"), + HTMLURL: github.Ptr("https://github.com/owner/repo/blob/main/path/to/file2.go"), + Repository: &github.Repository{ + Name: github.Ptr("repo"), + FullName: github.Ptr("owner/repo"), + }, }, }, } @@ -540,19 +551,28 @@ func Test_SearchCode(t *testing.T) { // Parse the result and get the text content if no error textContent := getTextResult(t, result) - // Unmarshal and verify the result - var returnedResult github.CodeSearchResult + // Unmarshal and verify the minimal result + var returnedResult MinimalCodeSearchResult err = json.Unmarshal([]byte(textContent.Text), &returnedResult) require.NoError(t, err) - assert.Equal(t, *tc.expectedResult.Total, *returnedResult.Total) - assert.Equal(t, *tc.expectedResult.IncompleteResults, *returnedResult.IncompleteResults) - assert.Len(t, returnedResult.CodeResults, len(tc.expectedResult.CodeResults)) - for i, code := range returnedResult.CodeResults { - assert.Equal(t, *tc.expectedResult.CodeResults[i].Name, *code.Name) - assert.Equal(t, *tc.expectedResult.CodeResults[i].Path, *code.Path) - assert.Equal(t, *tc.expectedResult.CodeResults[i].SHA, *code.SHA) - assert.Equal(t, *tc.expectedResult.CodeResults[i].HTMLURL, *code.HTMLURL) - assert.Equal(t, *tc.expectedResult.CodeResults[i].Repository.FullName, *code.Repository.FullName) + assert.Equal(t, *tc.expectedResult.Total, returnedResult.TotalCount) + assert.Equal(t, *tc.expectedResult.IncompleteResults, returnedResult.IncompleteResults) + assert.Len(t, returnedResult.Items, len(tc.expectedResult.CodeResults)) + for i, code := range returnedResult.Items { + assert.Equal(t, tc.expectedResult.CodeResults[i].GetName(), code.Name) + assert.Equal(t, tc.expectedResult.CodeResults[i].GetPath(), code.Path) + assert.Equal(t, tc.expectedResult.CodeResults[i].GetSHA(), code.SHA) + assert.Equal(t, tc.expectedResult.CodeResults[i].GetHTMLURL(), code.HTMLURL) + assert.Equal(t, tc.expectedResult.CodeResults[i].Repository.GetFullName(), code.Repository) + } + + // Verify text matches are included when present + if len(tc.expectedResult.CodeResults[0].TextMatches) > 0 { + require.NotEmpty(t, returnedResult.Items[0].TextMatches) + assert.Equal(t, + tc.expectedResult.CodeResults[0].TextMatches[0].GetFragment(), + returnedResult.Items[0].TextMatches[0].GetFragment(), + ) } }) } From 7073022c10f5555e615806f513fa1804f6c12f11 Mon Sep 17 00:00:00 2001 From: Sam Morrow Date: Thu, 14 May 2026 00:45:32 +0200 Subject: [PATCH 2/4] refactor: drop html_url from MinimalCodeResult The URL is derivable from repository + path + sha, so it's redundant token cost per result. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- pkg/github/minimal_types.go | 1 - pkg/github/search.go | 1 - pkg/github/search_test.go | 15 ++++++--------- 3 files changed, 6 insertions(+), 11 deletions(-) diff --git a/pkg/github/minimal_types.go b/pkg/github/minimal_types.go index 6bb8acae28..b1e7c23573 100644 --- a/pkg/github/minimal_types.go +++ b/pkg/github/minimal_types.go @@ -63,7 +63,6 @@ type MinimalCodeResult struct { Name string `json:"name"` Path string `json:"path"` SHA string `json:"sha"` - HTMLURL string `json:"html_url"` Repository string `json:"repository"` TextMatches []*github.TextMatch `json:"text_matches,omitempty"` } diff --git a/pkg/github/search.go b/pkg/github/search.go index 2d0e8d1701..8edfc948a6 100644 --- a/pkg/github/search.go +++ b/pkg/github/search.go @@ -308,7 +308,6 @@ func SearchCode(t translations.TranslationHelperFunc) inventory.ServerTool { Name: code.GetName(), Path: code.GetPath(), SHA: code.GetSHA(), - HTMLURL: code.GetHTMLURL(), TextMatches: code.TextMatches, } if code.Repository != nil { diff --git a/pkg/github/search_test.go b/pkg/github/search_test.go index f39034b230..cb5567b886 100644 --- a/pkg/github/search_test.go +++ b/pkg/github/search_test.go @@ -430,10 +430,9 @@ func Test_SearchCode(t *testing.T) { IncompleteResults: github.Ptr(false), CodeResults: []*github.CodeResult{ { - Name: github.Ptr("file1.go"), - Path: github.Ptr("path/to/file1.go"), - SHA: github.Ptr("abc123def456"), - HTMLURL: github.Ptr("https://github.com/owner/repo/blob/main/path/to/file1.go"), + Name: github.Ptr("file1.go"), + Path: github.Ptr("path/to/file1.go"), + SHA: github.Ptr("abc123def456"), Repository: &github.Repository{ Name: github.Ptr("repo"), FullName: github.Ptr("owner/repo"), @@ -445,10 +444,9 @@ func Test_SearchCode(t *testing.T) { }, }, { - Name: github.Ptr("file2.go"), - Path: github.Ptr("path/to/file2.go"), - SHA: github.Ptr("def456abc123"), - HTMLURL: github.Ptr("https://github.com/owner/repo/blob/main/path/to/file2.go"), + Name: github.Ptr("file2.go"), + Path: github.Ptr("path/to/file2.go"), + SHA: github.Ptr("def456abc123"), Repository: &github.Repository{ Name: github.Ptr("repo"), FullName: github.Ptr("owner/repo"), @@ -562,7 +560,6 @@ func Test_SearchCode(t *testing.T) { assert.Equal(t, tc.expectedResult.CodeResults[i].GetName(), code.Name) assert.Equal(t, tc.expectedResult.CodeResults[i].GetPath(), code.Path) assert.Equal(t, tc.expectedResult.CodeResults[i].GetSHA(), code.SHA) - assert.Equal(t, tc.expectedResult.CodeResults[i].GetHTMLURL(), code.HTMLURL) assert.Equal(t, tc.expectedResult.CodeResults[i].Repository.GetFullName(), code.Repository) } From f11bf9ad0a2e42672fa1478fa5d304303c098d5c Mon Sep 17 00:00:00 2001 From: Sam Morrow Date: Thu, 14 May 2026 01:07:12 +0200 Subject: [PATCH 3/4] fix: add minimal_output opt-out and Accept header test for code search Address PR review feedback: 1. Add minimal_output parameter (default: true) to search_code, matching the pattern from search_repositories. When false, returns the full GitHub API CodeSearchResult for backward compatibility. 2. Add Accept header assertion to tests via a new withHeaders() helper on partialMock, verifying the text-match Accept header is actually requested (not just mocked in the response). 3. Add test case for minimal_output=false path. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- README.md | 1 + pkg/github/__toolsnaps__/search_code.snap | 5 ++ pkg/github/helper_test.go | 18 ++++- pkg/github/search.go | 57 +++++++++------ pkg/github/search_test.go | 88 +++++++++++++++++------ 5 files changed, 123 insertions(+), 46 deletions(-) diff --git a/README.md b/README.md index a12f1531ac..fd6534855e 100644 --- a/README.md +++ b/README.md @@ -1273,6 +1273,7 @@ The following sets of tools are available: - **search_code** - Search code - **Required OAuth Scopes**: `repo` + - `minimal_output`: Return minimal repository information (default: true). When false, returns full GitHub API repository objects. (boolean, optional) - `order`: Sort order for results (string, optional) - `page`: Page number for pagination (min 1) (number, optional) - `perPage`: Results per page for pagination (min 1, max 100) (number, optional) diff --git a/pkg/github/__toolsnaps__/search_code.snap b/pkg/github/__toolsnaps__/search_code.snap index 8b5510aa61..246d5b3642 100644 --- a/pkg/github/__toolsnaps__/search_code.snap +++ b/pkg/github/__toolsnaps__/search_code.snap @@ -6,6 +6,11 @@ "description": "Fast and precise code search across ALL GitHub repositories using GitHub's native search engine. Best for finding exact symbols, functions, classes, or specific code patterns.", "inputSchema": { "properties": { + "minimal_output": { + "default": true, + "description": "Return minimal repository information (default: true). When false, returns full GitHub API repository objects.", + "type": "boolean" + }, "order": { "description": "Sort order for results", "enum": [ diff --git a/pkg/github/helper_test.go b/pkg/github/helper_test.go index 67a05fd6c0..2a601c3199 100644 --- a/pkg/github/helper_test.go +++ b/pkg/github/helper_test.go @@ -220,9 +220,15 @@ func expectRequestBody(t *testing.T, expectedRequestBody any) *partialMock { type partialMock struct { t *testing.T - expectedPath string - expectedQueryParams map[string]string - expectedRequestBody any + expectedPath string + expectedQueryParams map[string]string + expectedRequestBody any + expectedHeaderContains map[string]string +} + +func (p *partialMock) withHeaders(headers map[string]string) *partialMock { + p.expectedHeaderContains = headers + return p } func (p *partialMock) andThen(responseHandler http.HandlerFunc) http.HandlerFunc { @@ -247,6 +253,12 @@ func (p *partialMock) andThen(responseHandler http.HandlerFunc) http.HandlerFunc require.Equal(p.t, p.expectedRequestBody, unmarshaledRequestBody) } + if p.expectedHeaderContains != nil { + for k, v := range p.expectedHeaderContains { + require.Contains(p.t, r.Header.Get(k), v, "expected header %q to contain %q", k, v) + } + } + responseHandler(w, r) } } diff --git a/pkg/github/search.go b/pkg/github/search.go index 8edfc948a6..4aaeb91725 100644 --- a/pkg/github/search.go +++ b/pkg/github/search.go @@ -234,6 +234,11 @@ func SearchCode(t translations.TranslationHelperFunc) inventory.ServerTool { Description: "Sort order for results", Enum: []any{"asc", "desc"}, }, + "minimal_output": { + Type: "boolean", + Description: "Return minimal repository information (default: true). When false, returns full GitHub API repository objects.", + Default: json.RawMessage(`true`), + }, }, Required: []string{"query"}, } @@ -268,6 +273,10 @@ func SearchCode(t translations.TranslationHelperFunc) inventory.ServerTool { if err != nil { return utils.NewToolResultError(err.Error()), nil, nil } + minimalOutput, err := OptionalBoolParamWithDefault(args, "minimal_output", true) + if err != nil { + return utils.NewToolResultError(err.Error()), nil, nil + } opts := &github.SearchOptions{ Sort: sort, @@ -302,29 +311,37 @@ func SearchCode(t translations.TranslationHelperFunc) inventory.ServerTool { return ghErrors.NewGitHubAPIStatusErrorResponse(ctx, "failed to search code", resp, body), nil, nil } - minimalItems := make([]MinimalCodeResult, 0, len(result.CodeResults)) - for _, code := range result.CodeResults { - item := MinimalCodeResult{ - Name: code.GetName(), - Path: code.GetPath(), - SHA: code.GetSHA(), - TextMatches: code.TextMatches, - } - if code.Repository != nil { - item.Repository = code.Repository.GetFullName() + var r []byte + if minimalOutput { + minimalItems := make([]MinimalCodeResult, 0, len(result.CodeResults)) + for _, code := range result.CodeResults { + item := MinimalCodeResult{ + Name: code.GetName(), + Path: code.GetPath(), + SHA: code.GetSHA(), + TextMatches: code.TextMatches, + } + if code.Repository != nil { + item.Repository = code.Repository.GetFullName() + } + minimalItems = append(minimalItems, item) } - minimalItems = append(minimalItems, item) - } - minimalResult := &MinimalCodeSearchResult{ - TotalCount: result.GetTotal(), - IncompleteResults: result.GetIncompleteResults(), - Items: minimalItems, - } + minimalResult := &MinimalCodeSearchResult{ + TotalCount: result.GetTotal(), + IncompleteResults: result.GetIncompleteResults(), + Items: minimalItems, + } - r, err := json.Marshal(minimalResult) - if err != nil { - return utils.NewToolResultErrorFromErr("failed to marshal response", err), nil, nil + r, err = json.Marshal(minimalResult) + if err != nil { + return utils.NewToolResultErrorFromErr("failed to marshal minimal response", err), nil, nil + } + } else { + r, err = json.Marshal(result) + if err != nil { + return utils.NewToolResultErrorFromErr("failed to marshal response", err), nil, nil + } } return utils.NewToolResultText(string(r)), nil, nil diff --git a/pkg/github/search_test.go b/pkg/github/search_test.go index cb5567b886..8cf2e0f6c0 100644 --- a/pkg/github/search_test.go +++ b/pkg/github/search_test.go @@ -422,6 +422,7 @@ func Test_SearchCode(t *testing.T) { assert.Contains(t, schema.Properties, "order") assert.Contains(t, schema.Properties, "perPage") assert.Contains(t, schema.Properties, "page") + assert.Contains(t, schema.Properties, "minimal_output") assert.ElementsMatch(t, schema.Required, []string{"query"}) // Setup mock search results @@ -455,6 +456,10 @@ func Test_SearchCode(t *testing.T) { }, } + textMatchAcceptHeader := map[string]string{ + "Accept": "text-match", + } + tests := []struct { name string mockedClient *http.Client @@ -462,6 +467,7 @@ func Test_SearchCode(t *testing.T) { expectError bool expectedResult *github.CodeSearchResult expectedErrMsg string + minimalOutput bool }{ { name: "successful code search with all parameters", @@ -472,7 +478,7 @@ func Test_SearchCode(t *testing.T) { "order": "desc", "page": "1", "per_page": "30", - }).andThen( + }).withHeaders(textMatchAcceptHeader).andThen( mockResponse(t, http.StatusOK, mockSearchResult), ), }), @@ -485,6 +491,7 @@ func Test_SearchCode(t *testing.T) { }, expectError: false, expectedResult: mockSearchResult, + minimalOutput: true, }, { name: "code search with minimal parameters", @@ -493,7 +500,7 @@ func Test_SearchCode(t *testing.T) { "q": "fmt.Println language:go", "page": "1", "per_page": "30", - }).andThen( + }).withHeaders(textMatchAcceptHeader).andThen( mockResponse(t, http.StatusOK, mockSearchResult), ), }), @@ -502,6 +509,26 @@ func Test_SearchCode(t *testing.T) { }, expectError: false, expectedResult: mockSearchResult, + minimalOutput: true, + }, + { + name: "code search with full output", + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetSearchCode: expectQueryParams(t, map[string]string{ + "q": "fmt.Println language:go", + "page": "1", + "per_page": "30", + }).withHeaders(textMatchAcceptHeader).andThen( + mockResponse(t, http.StatusOK, mockSearchResult), + ), + }), + requestArgs: map[string]any{ + "query": "fmt.Println language:go", + "minimal_output": false, + }, + expectError: false, + expectedResult: mockSearchResult, + minimalOutput: false, }, { name: "search code fails", @@ -546,30 +573,45 @@ func Test_SearchCode(t *testing.T) { require.NoError(t, err) require.False(t, result.IsError) - // Parse the result and get the text content if no error textContent := getTextResult(t, result) - // Unmarshal and verify the minimal result - var returnedResult MinimalCodeSearchResult - err = json.Unmarshal([]byte(textContent.Text), &returnedResult) - require.NoError(t, err) - assert.Equal(t, *tc.expectedResult.Total, returnedResult.TotalCount) - assert.Equal(t, *tc.expectedResult.IncompleteResults, returnedResult.IncompleteResults) - assert.Len(t, returnedResult.Items, len(tc.expectedResult.CodeResults)) - for i, code := range returnedResult.Items { - assert.Equal(t, tc.expectedResult.CodeResults[i].GetName(), code.Name) - assert.Equal(t, tc.expectedResult.CodeResults[i].GetPath(), code.Path) - assert.Equal(t, tc.expectedResult.CodeResults[i].GetSHA(), code.SHA) - assert.Equal(t, tc.expectedResult.CodeResults[i].Repository.GetFullName(), code.Repository) - } + if tc.minimalOutput { + // Unmarshal and verify the minimal result + var returnedResult MinimalCodeSearchResult + err = json.Unmarshal([]byte(textContent.Text), &returnedResult) + require.NoError(t, err) + assert.Equal(t, *tc.expectedResult.Total, returnedResult.TotalCount) + assert.Equal(t, *tc.expectedResult.IncompleteResults, returnedResult.IncompleteResults) + assert.Len(t, returnedResult.Items, len(tc.expectedResult.CodeResults)) + for i, code := range returnedResult.Items { + assert.Equal(t, tc.expectedResult.CodeResults[i].GetName(), code.Name) + assert.Equal(t, tc.expectedResult.CodeResults[i].GetPath(), code.Path) + assert.Equal(t, tc.expectedResult.CodeResults[i].GetSHA(), code.SHA) + assert.Equal(t, tc.expectedResult.CodeResults[i].Repository.GetFullName(), code.Repository) + } - // Verify text matches are included when present - if len(tc.expectedResult.CodeResults[0].TextMatches) > 0 { - require.NotEmpty(t, returnedResult.Items[0].TextMatches) - assert.Equal(t, - tc.expectedResult.CodeResults[0].TextMatches[0].GetFragment(), - returnedResult.Items[0].TextMatches[0].GetFragment(), - ) + // Verify text matches are included when present + if len(tc.expectedResult.CodeResults[0].TextMatches) > 0 { + require.NotEmpty(t, returnedResult.Items[0].TextMatches) + assert.Equal(t, + tc.expectedResult.CodeResults[0].TextMatches[0].GetFragment(), + returnedResult.Items[0].TextMatches[0].GetFragment(), + ) + } + } else { + // Verify the full result is returned + var returnedResult github.CodeSearchResult + err = json.Unmarshal([]byte(textContent.Text), &returnedResult) + require.NoError(t, err) + assert.Equal(t, *tc.expectedResult.Total, *returnedResult.Total) + assert.Equal(t, *tc.expectedResult.IncompleteResults, *returnedResult.IncompleteResults) + assert.Len(t, returnedResult.CodeResults, len(tc.expectedResult.CodeResults)) + for i, code := range returnedResult.CodeResults { + assert.Equal(t, *tc.expectedResult.CodeResults[i].Name, *code.Name) + assert.Equal(t, *tc.expectedResult.CodeResults[i].Path, *code.Path) + assert.Equal(t, *tc.expectedResult.CodeResults[i].SHA, *code.SHA) + assert.Equal(t, *tc.expectedResult.CodeResults[i].Repository.FullName, *code.Repository.FullName) + } } }) } From ab5b5abf466fafe1d6536cd4e1d898933890af9d Mon Sep 17 00:00:00 2001 From: Sam Morrow Date: Thu, 14 May 2026 11:31:42 +0200 Subject: [PATCH 4/4] refactor: remove minimal_output opt-out from search_code The full CodeResult only adds a bloated Repository object (~3KB of template URLs) and a derivable HTMLURL. Nothing in the full output is useful beyond what the minimal type already provides, so always return the compact form. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- README.md | 1 - pkg/github/__toolsnaps__/search_code.snap | 5 -- pkg/github/search.go | 57 ++++++----------- pkg/github/search_test.go | 78 ++++++----------------- 4 files changed, 39 insertions(+), 102 deletions(-) diff --git a/README.md b/README.md index fd6534855e..a12f1531ac 100644 --- a/README.md +++ b/README.md @@ -1273,7 +1273,6 @@ The following sets of tools are available: - **search_code** - Search code - **Required OAuth Scopes**: `repo` - - `minimal_output`: Return minimal repository information (default: true). When false, returns full GitHub API repository objects. (boolean, optional) - `order`: Sort order for results (string, optional) - `page`: Page number for pagination (min 1) (number, optional) - `perPage`: Results per page for pagination (min 1, max 100) (number, optional) diff --git a/pkg/github/__toolsnaps__/search_code.snap b/pkg/github/__toolsnaps__/search_code.snap index 246d5b3642..8b5510aa61 100644 --- a/pkg/github/__toolsnaps__/search_code.snap +++ b/pkg/github/__toolsnaps__/search_code.snap @@ -6,11 +6,6 @@ "description": "Fast and precise code search across ALL GitHub repositories using GitHub's native search engine. Best for finding exact symbols, functions, classes, or specific code patterns.", "inputSchema": { "properties": { - "minimal_output": { - "default": true, - "description": "Return minimal repository information (default: true). When false, returns full GitHub API repository objects.", - "type": "boolean" - }, "order": { "description": "Sort order for results", "enum": [ diff --git a/pkg/github/search.go b/pkg/github/search.go index 4aaeb91725..8edfc948a6 100644 --- a/pkg/github/search.go +++ b/pkg/github/search.go @@ -234,11 +234,6 @@ func SearchCode(t translations.TranslationHelperFunc) inventory.ServerTool { Description: "Sort order for results", Enum: []any{"asc", "desc"}, }, - "minimal_output": { - Type: "boolean", - Description: "Return minimal repository information (default: true). When false, returns full GitHub API repository objects.", - Default: json.RawMessage(`true`), - }, }, Required: []string{"query"}, } @@ -273,10 +268,6 @@ func SearchCode(t translations.TranslationHelperFunc) inventory.ServerTool { if err != nil { return utils.NewToolResultError(err.Error()), nil, nil } - minimalOutput, err := OptionalBoolParamWithDefault(args, "minimal_output", true) - if err != nil { - return utils.NewToolResultError(err.Error()), nil, nil - } opts := &github.SearchOptions{ Sort: sort, @@ -311,37 +302,29 @@ func SearchCode(t translations.TranslationHelperFunc) inventory.ServerTool { return ghErrors.NewGitHubAPIStatusErrorResponse(ctx, "failed to search code", resp, body), nil, nil } - var r []byte - if minimalOutput { - minimalItems := make([]MinimalCodeResult, 0, len(result.CodeResults)) - for _, code := range result.CodeResults { - item := MinimalCodeResult{ - Name: code.GetName(), - Path: code.GetPath(), - SHA: code.GetSHA(), - TextMatches: code.TextMatches, - } - if code.Repository != nil { - item.Repository = code.Repository.GetFullName() - } - minimalItems = append(minimalItems, item) + minimalItems := make([]MinimalCodeResult, 0, len(result.CodeResults)) + for _, code := range result.CodeResults { + item := MinimalCodeResult{ + Name: code.GetName(), + Path: code.GetPath(), + SHA: code.GetSHA(), + TextMatches: code.TextMatches, } - - minimalResult := &MinimalCodeSearchResult{ - TotalCount: result.GetTotal(), - IncompleteResults: result.GetIncompleteResults(), - Items: minimalItems, + if code.Repository != nil { + item.Repository = code.Repository.GetFullName() } + minimalItems = append(minimalItems, item) + } - r, err = json.Marshal(minimalResult) - if err != nil { - return utils.NewToolResultErrorFromErr("failed to marshal minimal response", err), nil, nil - } - } else { - r, err = json.Marshal(result) - if err != nil { - return utils.NewToolResultErrorFromErr("failed to marshal response", err), nil, nil - } + minimalResult := &MinimalCodeSearchResult{ + TotalCount: result.GetTotal(), + IncompleteResults: result.GetIncompleteResults(), + Items: minimalItems, + } + + r, err := json.Marshal(minimalResult) + if err != nil { + return utils.NewToolResultErrorFromErr("failed to marshal response", err), nil, nil } return utils.NewToolResultText(string(r)), nil, nil diff --git a/pkg/github/search_test.go b/pkg/github/search_test.go index 8cf2e0f6c0..0c4a30c326 100644 --- a/pkg/github/search_test.go +++ b/pkg/github/search_test.go @@ -422,7 +422,6 @@ func Test_SearchCode(t *testing.T) { assert.Contains(t, schema.Properties, "order") assert.Contains(t, schema.Properties, "perPage") assert.Contains(t, schema.Properties, "page") - assert.Contains(t, schema.Properties, "minimal_output") assert.ElementsMatch(t, schema.Required, []string{"query"}) // Setup mock search results @@ -467,7 +466,6 @@ func Test_SearchCode(t *testing.T) { expectError bool expectedResult *github.CodeSearchResult expectedErrMsg string - minimalOutput bool }{ { name: "successful code search with all parameters", @@ -491,7 +489,6 @@ func Test_SearchCode(t *testing.T) { }, expectError: false, expectedResult: mockSearchResult, - minimalOutput: true, }, { name: "code search with minimal parameters", @@ -509,26 +506,6 @@ func Test_SearchCode(t *testing.T) { }, expectError: false, expectedResult: mockSearchResult, - minimalOutput: true, - }, - { - name: "code search with full output", - mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ - GetSearchCode: expectQueryParams(t, map[string]string{ - "q": "fmt.Println language:go", - "page": "1", - "per_page": "30", - }).withHeaders(textMatchAcceptHeader).andThen( - mockResponse(t, http.StatusOK, mockSearchResult), - ), - }), - requestArgs: map[string]any{ - "query": "fmt.Println language:go", - "minimal_output": false, - }, - expectError: false, - expectedResult: mockSearchResult, - minimalOutput: false, }, { name: "search code fails", @@ -575,43 +552,26 @@ func Test_SearchCode(t *testing.T) { textContent := getTextResult(t, result) - if tc.minimalOutput { - // Unmarshal and verify the minimal result - var returnedResult MinimalCodeSearchResult - err = json.Unmarshal([]byte(textContent.Text), &returnedResult) - require.NoError(t, err) - assert.Equal(t, *tc.expectedResult.Total, returnedResult.TotalCount) - assert.Equal(t, *tc.expectedResult.IncompleteResults, returnedResult.IncompleteResults) - assert.Len(t, returnedResult.Items, len(tc.expectedResult.CodeResults)) - for i, code := range returnedResult.Items { - assert.Equal(t, tc.expectedResult.CodeResults[i].GetName(), code.Name) - assert.Equal(t, tc.expectedResult.CodeResults[i].GetPath(), code.Path) - assert.Equal(t, tc.expectedResult.CodeResults[i].GetSHA(), code.SHA) - assert.Equal(t, tc.expectedResult.CodeResults[i].Repository.GetFullName(), code.Repository) - } + var returnedResult MinimalCodeSearchResult + err = json.Unmarshal([]byte(textContent.Text), &returnedResult) + require.NoError(t, err) + assert.Equal(t, *tc.expectedResult.Total, returnedResult.TotalCount) + assert.Equal(t, *tc.expectedResult.IncompleteResults, returnedResult.IncompleteResults) + assert.Len(t, returnedResult.Items, len(tc.expectedResult.CodeResults)) + for i, code := range returnedResult.Items { + assert.Equal(t, tc.expectedResult.CodeResults[i].GetName(), code.Name) + assert.Equal(t, tc.expectedResult.CodeResults[i].GetPath(), code.Path) + assert.Equal(t, tc.expectedResult.CodeResults[i].GetSHA(), code.SHA) + assert.Equal(t, tc.expectedResult.CodeResults[i].Repository.GetFullName(), code.Repository) + } - // Verify text matches are included when present - if len(tc.expectedResult.CodeResults[0].TextMatches) > 0 { - require.NotEmpty(t, returnedResult.Items[0].TextMatches) - assert.Equal(t, - tc.expectedResult.CodeResults[0].TextMatches[0].GetFragment(), - returnedResult.Items[0].TextMatches[0].GetFragment(), - ) - } - } else { - // Verify the full result is returned - var returnedResult github.CodeSearchResult - err = json.Unmarshal([]byte(textContent.Text), &returnedResult) - require.NoError(t, err) - assert.Equal(t, *tc.expectedResult.Total, *returnedResult.Total) - assert.Equal(t, *tc.expectedResult.IncompleteResults, *returnedResult.IncompleteResults) - assert.Len(t, returnedResult.CodeResults, len(tc.expectedResult.CodeResults)) - for i, code := range returnedResult.CodeResults { - assert.Equal(t, *tc.expectedResult.CodeResults[i].Name, *code.Name) - assert.Equal(t, *tc.expectedResult.CodeResults[i].Path, *code.Path) - assert.Equal(t, *tc.expectedResult.CodeResults[i].SHA, *code.SHA) - assert.Equal(t, *tc.expectedResult.CodeResults[i].Repository.FullName, *code.Repository.FullName) - } + // Verify text matches are included when present + if len(tc.expectedResult.CodeResults[0].TextMatches) > 0 { + require.NotEmpty(t, returnedResult.Items[0].TextMatches) + assert.Equal(t, + tc.expectedResult.CodeResults[0].TextMatches[0].GetFragment(), + returnedResult.Items[0].TextMatches[0].GetFragment(), + ) } }) }