Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
132 changes: 132 additions & 0 deletions pkg/github/minimal_types.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package github

import (
"time"

"github.com/google/go-github/v82/github"
)

Expand Down Expand Up @@ -134,8 +136,138 @@ type MinimalProject struct {
OwnerType string `json:"owner_type,omitempty"`
}

// MinimalPullRequest is the trimmed output type for pull request objects to reduce verbosity.
type MinimalPullRequest struct {
Number int `json:"number"`
Title string `json:"title"`
Body string `json:"body,omitempty"`
State string `json:"state"`
Draft bool `json:"draft"`
Merged bool `json:"merged"`
MergeableState string `json:"mergeable_state,omitempty"`
HTMLURL string `json:"html_url"`
User *MinimalUser `json:"user,omitempty"`
Labels []string `json:"labels,omitempty"`
Assignees []string `json:"assignees,omitempty"`
RequestedReviewers []string `json:"requested_reviewers,omitempty"`
MergedBy string `json:"merged_by,omitempty"`
Head *MinimalPRBranch `json:"head,omitempty"`
Base *MinimalPRBranch `json:"base,omitempty"`
Additions int `json:"additions,omitempty"`
Deletions int `json:"deletions,omitempty"`
ChangedFiles int `json:"changed_files,omitempty"`
Commits int `json:"commits,omitempty"`
Comments int `json:"comments,omitempty"`
CreatedAt string `json:"created_at,omitempty"`
UpdatedAt string `json:"updated_at,omitempty"`
ClosedAt string `json:"closed_at,omitempty"`
MergedAt string `json:"merged_at,omitempty"`
Milestone string `json:"milestone,omitempty"`
}

// MinimalPRBranch is the trimmed output type for pull request branch references.
type MinimalPRBranch struct {
Ref string `json:"ref"`
SHA string `json:"sha"`
Repo *MinimalPRBranchRepo `json:"repo,omitempty"`
}

// MinimalPRBranchRepo is the trimmed repo info nested inside a PR branch.
type MinimalPRBranchRepo struct {
FullName string `json:"full_name"`
Description string `json:"description,omitempty"`
}

// Helper functions

func convertToMinimalPullRequest(pr *github.PullRequest) MinimalPullRequest {
m := MinimalPullRequest{
Number: pr.GetNumber(),
Title: pr.GetTitle(),
Body: pr.GetBody(),
State: pr.GetState(),
Draft: pr.GetDraft(),
Merged: pr.GetMerged(),
MergeableState: pr.GetMergeableState(),
HTMLURL: pr.GetHTMLURL(),
User: convertToMinimalUser(pr.GetUser()),
Additions: pr.GetAdditions(),
Deletions: pr.GetDeletions(),
ChangedFiles: pr.GetChangedFiles(),
Commits: pr.GetCommits(),
Comments: pr.GetComments(),
}

if pr.CreatedAt != nil {
m.CreatedAt = pr.CreatedAt.Format(time.RFC3339)
}
if pr.UpdatedAt != nil {
m.UpdatedAt = pr.UpdatedAt.Format(time.RFC3339)
}
if pr.ClosedAt != nil {
m.ClosedAt = pr.ClosedAt.Format(time.RFC3339)
}
if pr.MergedAt != nil {
m.MergedAt = pr.MergedAt.Format(time.RFC3339)
}
Comment on lines +201 to +212
Copy link

Copilot AI Feb 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR timestamp fields in MinimalPullRequest are formatted with time.RFC3339, but other minimal outputs in this repo format timestamps using the fixed layout "2006-01-02T15:04:05Z" (e.g., minimal commit author dates, minimal repository timestamps). To keep output consistent (and avoid offsets like "+00:00"), format CreatedAt/UpdatedAt/ClosedAt/MergedAt using the same layout (ideally via a shared const) instead of time.RFC3339.

Copilot uses AI. Check for mistakes.

for _, label := range pr.Labels {
if label != nil {
m.Labels = append(m.Labels, label.GetName())
}
}

for _, assignee := range pr.Assignees {
if assignee != nil {
m.Assignees = append(m.Assignees, assignee.GetLogin())
}
}

for _, reviewer := range pr.RequestedReviewers {
if reviewer != nil {
m.RequestedReviewers = append(m.RequestedReviewers, reviewer.GetLogin())
}
}

if mergedBy := pr.GetMergedBy(); mergedBy != nil {
m.MergedBy = mergedBy.GetLogin()
}

if head := pr.Head; head != nil {
m.Head = convertToMinimalPRBranch(head)
}

if base := pr.Base; base != nil {
m.Base = convertToMinimalPRBranch(base)
}

if milestone := pr.GetMilestone(); milestone != nil {
m.Milestone = milestone.GetTitle()
}

return m
}

func convertToMinimalPRBranch(branch *github.PullRequestBranch) *MinimalPRBranch {
if branch == nil {
return nil
}

b := &MinimalPRBranch{
Ref: branch.GetRef(),
SHA: branch.GetSHA(),
}

if repo := branch.GetRepo(); repo != nil {
b.Repo = &MinimalPRBranchRepo{
FullName: repo.GetFullName(),
Description: repo.GetDescription(),
}
}

return b
}

func convertToMinimalProject(fullProject *github.ProjectV2) *MinimalProject {
if fullProject == nil {
return nil
Expand Down
7 changes: 2 additions & 5 deletions pkg/github/pullrequests.go
Original file line number Diff line number Diff line change
Expand Up @@ -186,12 +186,9 @@ func GetPullRequest(ctx context.Context, client *github.Client, deps ToolDepende
}
}

r, err := json.Marshal(pr)
if err != nil {
return nil, fmt.Errorf("failed to marshal response: %w", err)
}
minimalPR := convertToMinimalPullRequest(pr)

return utils.NewToolResultText(string(r)), nil
return MarshalledTextResult(minimalPR), nil
}

func GetPullRequestDiff(ctx context.Context, client *github.Client, owner, repo string, pullNumber int) (*mcp.CallToolResult, error) {
Expand Down
12 changes: 6 additions & 6 deletions pkg/github/pullrequests_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,14 +127,14 @@ func Test_GetPullRequest(t *testing.T) {
// Parse the result and get the text content if no error
textContent := getTextResult(t, result)

// Unmarshal and verify the result
var returnedPR github.PullRequest
// Unmarshal and verify the minimal result
var returnedPR MinimalPullRequest
err = json.Unmarshal([]byte(textContent.Text), &returnedPR)
require.NoError(t, err)
assert.Equal(t, *tc.expectedPR.Number, *returnedPR.Number)
assert.Equal(t, *tc.expectedPR.Title, *returnedPR.Title)
assert.Equal(t, *tc.expectedPR.State, *returnedPR.State)
assert.Equal(t, *tc.expectedPR.HTMLURL, *returnedPR.HTMLURL)
assert.Equal(t, tc.expectedPR.GetNumber(), returnedPR.Number)
assert.Equal(t, tc.expectedPR.GetTitle(), returnedPR.Title)
assert.Equal(t, tc.expectedPR.GetState(), returnedPR.State)
assert.Equal(t, tc.expectedPR.GetHTMLURL(), returnedPR.HTMLURL)
})
}
}
Expand Down