diff --git a/pkg/github/__toolsnaps__/update_issue_type.snap b/pkg/github/__toolsnaps__/update_issue_type.snap index 237603a6e..7fb5fde89 100644 --- a/pkg/github/__toolsnaps__/update_issue_type.snap +++ b/pkg/github/__toolsnaps__/update_issue_type.snap @@ -7,6 +7,10 @@ "description": "Update the type of an existing issue (e.g. 'bug', 'feature').", "inputSchema": { "properties": { + "is_suggestion": { + "description": "If true, propose the issue type change instead of applying it. Defaults to false, which applies the change to the issue.", + "type": "boolean" + }, "issue_number": { "description": "The issue number to update", "minimum": 1, diff --git a/pkg/github/granular_tools_test.go b/pkg/github/granular_tools_test.go index 59eb47822..88bd560b4 100644 --- a/pkg/github/granular_tools_test.go +++ b/pkg/github/granular_tools_test.go @@ -2,6 +2,7 @@ package github import ( "context" + "encoding/json" "net/http" "strings" "testing" @@ -458,6 +459,70 @@ func TestGranularUpdateIssueType(t *testing.T) { } } +func TestGranularUpdateIssueTypeSuggest(t *testing.T) { + tests := []struct { + name string + requestArgs map[string]any + expected map[string]any + }{ + { + name: "suggest without rationale", + requestArgs: map[string]any{ + "owner": "owner", + "repo": "repo", + "issue_number": float64(1), + "issue_type": "bug", + "suggest": true, + }, + expected: map[string]any{ + "owner": "owner", + "repo": "repo", + "issue_number": float64(1), + "issue_type": "bug", + "suggested": true, + }, + }, + { + name: "suggest with rationale", + requestArgs: map[string]any{ + "owner": "owner", + "repo": "repo", + "issue_number": float64(1), + "issue_type": "feature", + "rationale": " Asks for dark mode support ", + "suggest": true, + }, + expected: map[string]any{ + "owner": "owner", + "repo": "repo", + "issue_number": float64(1), + "issue_type": "feature", + "rationale": "Asks for dark mode support", + "suggested": true, + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + // No HTTP handler registered: any API call would fail the test. + deps := BaseDeps{Client: mustNewGHClient(t, MockHTTPClientWithHandlers(nil))} + serverTool := GranularUpdateIssueType(translations.NullTranslationHelper) + handler := serverTool.Handler(deps) + + request := createMCPRequest(tc.requestArgs) + result, err := handler(ContextWithDeps(context.Background(), deps), &request) + require.NoError(t, err) + require.False(t, result.IsError) + + textContent := getTextResult(t, result) + var got map[string]any + require.NoError(t, json.Unmarshal([]byte(textContent.Text), &got)) + assert.Equal(t, tc.expected, got) + }) + } +} + func TestGranularUpdateIssueTypeInvalidRationale(t *testing.T) { tests := []struct { name string diff --git a/pkg/github/issues_granular.go b/pkg/github/issues_granular.go index 400a22f5c..9e789c6d1 100644 --- a/pkg/github/issues_granular.go +++ b/pkg/github/issues_granular.go @@ -512,6 +512,11 @@ func GranularUpdateIssueType(t translations.TranslationHelperFunc) inventory.Ser "State the concrete signal (e.g. 'Reports a crash when saving' → bug, 'Asks for dark mode support' → feature).", MaxLength: jsonschema.Ptr(280), }, + "is_suggestion": { + Type: "boolean", + Description: "If true, propose the issue type change instead of applying it. " + + "Defaults to false, which applies the change to the issue.", + }, }, Required: []string{"owner", "repo", "issue_number", "issue_type"}, }, @@ -542,6 +547,28 @@ func GranularUpdateIssueType(t translations.TranslationHelperFunc) inventory.Ser if len([]rune(rationale)) > 280 { return utils.NewToolResultError("parameter rationale must be 280 characters or less"), nil, nil } + suggest, err := OptionalParam[bool](args, "suggest") + if err != nil { + return utils.NewToolResultError(err.Error()), nil, nil + } + + if suggest { + suggestion := map[string]any{ + "owner": owner, + "repo": repo, + "issue_number": issueNumber, + "issue_type": issueType, + "suggested": true, + } + if rationale != "" { + suggestion["rationale"] = rationale + } + r, err := json.Marshal(suggestion) + if err != nil { + return utils.NewToolResultErrorFromErr("failed to marshal suggestion", err), nil, nil + } + return utils.NewToolResultText(string(r)), nil, nil + } client, err := deps.GetClient(ctx) if err != nil {