diff --git a/README.md b/README.md index 9527478a..cc4608ef 100644 --- a/README.md +++ b/README.md @@ -1236,6 +1236,7 @@ The following sets of tools are available: - `owner`: Repository owner (string, required) - `pullNumber`: Pull request number (number, required) - `repo`: Repository name (string, required) + - `resolutionReason`: Optional reason for resolving a Copilot code review thread: addressed, wont-fix, or invalid. (string, optional) - `threadId`: The node ID of the review thread (e.g., PRRT_kwDOxxx). Required for resolve_thread and unresolve_thread methods. Get thread IDs from pull_request_read with method get_review_comments. (string, optional) - **search_pull_requests** - Search pull requests diff --git a/docs/feature-flags.md b/docs/feature-flags.md index 0de5bdd7..af050556 100644 --- a/docs/feature-flags.md +++ b/docs/feature-flags.md @@ -251,6 +251,7 @@ runtime behavior (such as output formatting) won't appear here. - **resolve_review_thread** - Resolve Review Thread - **Required OAuth Scopes**: `repo` + - `resolutionReason`: Optional reason for resolving a Copilot code review thread: addressed, wont-fix, or invalid. (string, optional) - `threadID`: The node ID of the review thread to resolve (e.g., PRRT_kwDOxxx) (string, required) - **submit_pending_pull_request_review** - Submit Pending Pull Request Review diff --git a/pkg/github/__toolsnaps__/pull_request_review_write.snap b/pkg/github/__toolsnaps__/pull_request_review_write.snap index 74ef8085..43b97bc3 100644 --- a/pkg/github/__toolsnaps__/pull_request_review_write.snap +++ b/pkg/github/__toolsnaps__/pull_request_review_write.snap @@ -47,6 +47,10 @@ "description": "Repository name", "type": "string" }, + "resolutionReason": { + "description": "Optional reason for resolving a Copilot code review thread: addressed, wont-fix, or invalid.", + "type": "string" + }, "threadId": { "description": "The node ID of the review thread (e.g., PRRT_kwDOxxx). Required for resolve_thread and unresolve_thread methods. Get thread IDs from pull_request_read with method get_review_comments.", "type": "string" diff --git a/pkg/github/__toolsnaps__/resolve_review_thread.snap b/pkg/github/__toolsnaps__/resolve_review_thread.snap index 5f2b21b3..b9d4758c 100644 --- a/pkg/github/__toolsnaps__/resolve_review_thread.snap +++ b/pkg/github/__toolsnaps__/resolve_review_thread.snap @@ -9,6 +9,10 @@ "description": "Resolve a review thread on a pull request. Resolving an already-resolved thread is a no-op.", "inputSchema": { "properties": { + "resolutionReason": { + "description": "Optional reason for resolving a Copilot code review thread: addressed, wont-fix, or invalid.", + "type": "string" + }, "threadID": { "description": "The node ID of the review thread to resolve (e.g., PRRT_kwDOxxx)", "type": "string" diff --git a/pkg/github/granular_tools_test.go b/pkg/github/granular_tools_test.go index d70dd568..f427d774 100644 --- a/pkg/github/granular_tools_test.go +++ b/pkg/github/granular_tools_test.go @@ -1738,8 +1738,9 @@ func TestGranularResolveReviewThread(t *testing.T) { } } `graphql:"resolveReviewThread(input: $input)"` }{}, - githubv4.ResolveReviewThreadInput{ - ThreadID: githubv4.ID("PRRT_123"), + resolveReviewThreadInput{ + ThreadID: githubv4.ID("PRRT_123"), + ResolutionReason: newGQLStringlike[githubv4.String]("addressed"), }, nil, githubv4mock.DataResponse(map[string]any{ @@ -1755,7 +1756,8 @@ func TestGranularResolveReviewThread(t *testing.T) { handler := serverTool.Handler(deps) request := createMCPRequest(map[string]any{ - "threadID": "PRRT_123", + "threadID": "PRRT_123", + "resolutionReason": "addressed", }) result, err := handler(ContextWithDeps(context.Background(), deps), &request) require.NoError(t, err) diff --git a/pkg/github/pullrequests.go b/pkg/github/pullrequests.go index 8801ec28..bd530651 100644 --- a/pkg/github/pullrequests.go +++ b/pkg/github/pullrequests.go @@ -1760,14 +1760,15 @@ func UpdatePullRequestBranch(t translations.TranslationHelperFunc) inventory.Ser } type PullRequestReviewWriteParams struct { - Method string - Owner string - Repo string - PullNumber int32 - Body string - Event string - CommitID *string - ThreadID string + Method string + Owner string + Repo string + PullNumber int32 + Body string + Event string + CommitID *string + ThreadID string + ResolutionReason *string } func PullRequestReviewWrite(t translations.TranslationHelperFunc) inventory.ServerTool { @@ -1811,6 +1812,10 @@ func PullRequestReviewWrite(t translations.TranslationHelperFunc) inventory.Serv Type: "string", Description: "The node ID of the review thread (e.g., PRRT_kwDOxxx). Required for resolve_thread and unresolve_thread methods. Get thread IDs from pull_request_read with method get_review_comments.", }, + "resolutionReason": { + Type: "string", + Description: "Optional reason for resolving a Copilot code review thread: addressed, wont-fix, or invalid.", + }, }, Required: []string{"method", "owner", "repo", "pullNumber"}, } @@ -1858,10 +1863,10 @@ Available methods: result, err := DeletePendingPullRequestReview(ctx, client, params) return result, nil, err case "resolve_thread": - result, err := ResolveReviewThread(ctx, client, params.ThreadID, true) + result, err := ResolveReviewThread(ctx, client, params.ThreadID, params.ResolutionReason, true) return result, nil, err case "unresolve_thread": - result, err := ResolveReviewThread(ctx, client, params.ThreadID, false) + result, err := ResolveReviewThread(ctx, client, params.ThreadID, nil, false) return result, nil, err default: return utils.NewToolResultError(fmt.Sprintf("unknown method: %s", params.Method)), nil, nil @@ -2094,8 +2099,13 @@ func DeletePendingPullRequestReview(ctx context.Context, client *githubv4.Client return utils.NewToolResultText("pending pull request review successfully deleted"), nil } +type resolveReviewThreadInput struct { + ThreadID githubv4.ID `json:"threadId"` + ResolutionReason *githubv4.String `json:"resolutionReason,omitempty"` +} + // ResolveReviewThread resolves or unresolves a PR review thread using GraphQL mutations. -func ResolveReviewThread(ctx context.Context, client *githubv4.Client, threadID string, resolve bool) (*mcp.CallToolResult, error) { +func ResolveReviewThread(ctx context.Context, client *githubv4.Client, threadID string, resolutionReason *string, resolve bool) (*mcp.CallToolResult, error) { if threadID == "" { return utils.NewToolResultError("threadId is required for resolve_thread and unresolve_thread methods"), nil } @@ -2110,8 +2120,9 @@ func ResolveReviewThread(ctx context.Context, client *githubv4.Client, threadID } `graphql:"resolveReviewThread(input: $input)"` } - input := githubv4.ResolveReviewThreadInput{ - ThreadID: githubv4.ID(threadID), + input := resolveReviewThreadInput{ + ThreadID: githubv4.ID(threadID), + ResolutionReason: newGQLStringlikePtr[githubv4.String](resolutionReason), } if err := client.Mutate(ctx, &mutation, input, nil); err != nil { diff --git a/pkg/github/pullrequests_granular.go b/pkg/github/pullrequests_granular.go index c727beb6..3d3cff76 100644 --- a/pkg/github/pullrequests_granular.go +++ b/pkg/github/pullrequests_granular.go @@ -690,6 +690,10 @@ func GranularResolveReviewThread(t translations.TranslationHelperFunc) inventory Type: "string", Description: "The node ID of the review thread to resolve (e.g., PRRT_kwDOxxx)", }, + "resolutionReason": { + Type: "string", + Description: "Optional reason for resolving a Copilot code review thread: addressed, wont-fix, or invalid.", + }, }, Required: []string{"threadID"}, }, @@ -700,13 +704,21 @@ func GranularResolveReviewThread(t translations.TranslationHelperFunc) inventory if err != nil { return utils.NewToolResultError(err.Error()), nil, nil } + resolutionReason, hasResolutionReason, err := OptionalParamOK[string](args, "resolutionReason") + if err != nil { + return utils.NewToolResultError(err.Error()), nil, nil + } + var resolutionReasonPtr *string + if hasResolutionReason { + resolutionReasonPtr = &resolutionReason + } gqlClient, err := deps.GetGQLClient(ctx) if err != nil { return utils.NewToolResultErrorFromErr("failed to get GitHub GraphQL client", err), nil, nil } - result, err := ResolveReviewThread(ctx, gqlClient, threadID, true) + result, err := ResolveReviewThread(ctx, gqlClient, threadID, resolutionReasonPtr, true) return result, nil, err }, ) @@ -750,7 +762,7 @@ func GranularUnresolveReviewThread(t translations.TranslationHelperFunc) invento return utils.NewToolResultErrorFromErr("failed to get GitHub GraphQL client", err), nil, nil } - result, err := ResolveReviewThread(ctx, gqlClient, threadID, false) + result, err := ResolveReviewThread(ctx, gqlClient, threadID, nil, false) return result, nil, err }, ) diff --git a/pkg/github/pullrequests_test.go b/pkg/github/pullrequests_test.go index 03ec851c..9e9843cf 100644 --- a/pkg/github/pullrequests_test.go +++ b/pkg/github/pullrequests_test.go @@ -4572,7 +4572,7 @@ func TestResolveReviewThread(t *testing.T) { } } `graphql:"resolveReviewThread(input: $input)"` }{}, - githubv4.ResolveReviewThreadInput{ + resolveReviewThreadInput{ ThreadID: githubv4.ID("PRRT_kwDOTest123"), }, nil, @@ -4588,6 +4588,43 @@ func TestResolveReviewThread(t *testing.T) { ), expectedResult: "review thread resolved successfully", }, + { + name: "successful resolve thread with resolution reason", + requestArgs: map[string]any{ + "method": "resolve_thread", + "owner": "owner", + "repo": "repo", + "pullNumber": float64(42), + "threadId": "PRRT_kwDOTest123", + "resolutionReason": "wont-fix", + }, + mockedClient: githubv4mock.NewMockedHTTPClient( + githubv4mock.NewMutationMatcher( + struct { + ResolveReviewThread struct { + Thread struct { + ID githubv4.ID + IsResolved githubv4.Boolean + } + } `graphql:"resolveReviewThread(input: $input)"` + }{}, + resolveReviewThreadInput{ + ThreadID: githubv4.ID("PRRT_kwDOTest123"), + ResolutionReason: newGQLStringlike[githubv4.String]("wont-fix"), + }, + nil, + githubv4mock.DataResponse(map[string]any{ + "resolveReviewThread": map[string]any{ + "thread": map[string]any{ + "id": "PRRT_kwDOTest123", + "isResolved": true, + }, + }, + }), + ), + ), + expectedResult: "review thread resolved successfully", + }, { name: "successful unresolve thread", requestArgs: map[string]any{ @@ -4692,7 +4729,7 @@ func TestResolveReviewThread(t *testing.T) { } } `graphql:"resolveReviewThread(input: $input)"` }{}, - githubv4.ResolveReviewThreadInput{ + resolveReviewThreadInput{ ThreadID: githubv4.ID("PRRT_invalid"), }, nil,