Skip to content
Open
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
1 change: 1 addition & 0 deletions pkg/github/__toolsnaps__/add_sub_issue.snap
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,7 @@
}
},
"required": [
"labels",
"assignees"
],
"type": [
Expand Down
5 changes: 4 additions & 1 deletion pkg/github/__toolsnaps__/issue_read.snap
Original file line number Diff line number Diff line change
Expand Up @@ -642,7 +642,9 @@
"number",
"title",
"state",
"assignees"
"labels",
"assignees",
"comments"
],
"type": [
"null",
Expand Down Expand Up @@ -915,6 +917,7 @@
}
},
"required": [
"labels",
"assignees"
],
"type": [
Expand Down
5 changes: 4 additions & 1 deletion pkg/github/__toolsnaps__/pull_request_read.snap
Original file line number Diff line number Diff line change
Expand Up @@ -342,7 +342,10 @@
"state",
"draft",
"merged",
"html_url"
"html_url",
"labels",
"assignees",
"requested_reviewers"
],
"type": "object"
},
Expand Down
1 change: 1 addition & 0 deletions pkg/github/__toolsnaps__/remove_sub_issue.snap
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,7 @@
}
},
"required": [
"labels",
"assignees"
],
"type": [
Expand Down
1 change: 1 addition & 0 deletions pkg/github/__toolsnaps__/reprioritize_sub_issue.snap
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,7 @@
}
},
"required": [
"labels",
"assignees"
],
"type": [
Expand Down
1 change: 1 addition & 0 deletions pkg/github/__toolsnaps__/sub_issue_write.snap
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,7 @@
}
},
"required": [
"labels",
"assignees"
],
"type": [
Expand Down
6 changes: 3 additions & 3 deletions pkg/github/consolidated_issue_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -144,10 +144,10 @@ type IssueDetailsOutput struct {
HTMLURL string `json:"html_url,omitempty" jsonschema:"Human-readable issue link."`
User *IssueUserOutput `json:"user,omitempty"`
AuthorAssociation string `json:"author_association,omitempty"`
Labels []string `json:"labels,omitempty"`
Labels []string `json:"labels"`
Assignees []string `json:"assignees"`
Milestone string `json:"milestone,omitempty"`
Comments int `json:"comments,omitempty" jsonschema:"Number of comments."`
Comments int `json:"comments" jsonschema:"Number of comments."`
Reactions *MinimalReactions `json:"reactions,omitempty"`
CreatedAt string `json:"created_at,omitempty" jsonschema:"Creation time (RFC 3339)."`
UpdatedAt string `json:"updated_at,omitempty" jsonschema:"Last update time (RFC 3339)."`
Expand Down Expand Up @@ -326,7 +326,7 @@ type SubIssueOutput struct {
StateReason *string `json:"state_reason,omitempty"`
HTMLURL *string `json:"html_url,omitempty" jsonschema:"Human-readable issue link."`
User *IssueUserOutput `json:"user,omitempty"`
Labels []string `json:"labels,omitempty"`
Labels []string `json:"labels"`
Assignees []string `json:"assignees"`
Milestone string `json:"milestone,omitempty"`
Comments *int `json:"comments,omitempty" jsonschema:"Number of comments."`
Expand Down
27 changes: 19 additions & 8 deletions pkg/github/minimal_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -556,9 +556,11 @@ type MinimalFieldValue struct {

// MinimalIssue is the trimmed output type for issue objects to reduce verbosity.
//
// Assignees is deliberately not omitempty: both conversions below populate it
// non-nil, so an empty list is a definitive "nobody is assigned" answer rather
// than an absent key. Callers filtering for unassigned issues depend on that.
// Labels, Assignees and Comments are deliberately not omitempty: both
// conversions below populate the lists non-nil, so an empty list or zero count
// is a definitive answer (for example "nobody is assigned" or "no labels")
// rather than an absent key. Callers filtering for unassigned issues depend on
// that.
type MinimalIssue struct {
Number int `json:"number"`
Title string `json:"title"`
Expand All @@ -570,10 +572,10 @@ type MinimalIssue struct {
HTMLURL string `json:"html_url,omitempty"`
User *MinimalUser `json:"user,omitempty"`
AuthorAssociation string `json:"author_association,omitempty"`
Labels []string `json:"labels,omitempty"`
Labels []string `json:"labels"`
Assignees []string `json:"assignees"`
Milestone string `json:"milestone,omitempty"`
Comments int `json:"comments,omitempty"`
Comments int `json:"comments"`
Reactions *MinimalReactions `json:"reactions,omitempty"`
CreatedAt string `json:"created_at,omitempty"`
UpdatedAt string `json:"updated_at,omitempty"`
Expand Down Expand Up @@ -710,6 +712,10 @@ type MinimalFileCommit struct {
}

// MinimalPullRequest is the trimmed output type for pull request objects to reduce verbosity.
//
// Labels, Assignees and RequestedReviewers are deliberately not omitempty and
// are always populated non-nil, so an empty list is a definitive answer rather
// than an absent key.
type MinimalPullRequest struct {
Number int `json:"number"`
Title string `json:"title"`
Expand All @@ -720,9 +726,9 @@ type MinimalPullRequest struct {
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"`
Labels []string `json:"labels"`
Assignees []string `json:"assignees"`
RequestedReviewers []string `json:"requested_reviewers"`
MergedBy string `json:"merged_by,omitempty"`
Head *MinimalPRBranch `json:"head,omitempty"`
Base *MinimalPRBranch `json:"base,omitempty"`
Expand Down Expand Up @@ -836,6 +842,7 @@ func convertToMinimalIssue(issue *github.Issue) MinimalIssue {
m.ClosedAt = issue.ClosedAt.Format(time.RFC3339)
}

m.Labels = make([]string, 0, len(issue.Labels))
for _, label := range issue.Labels {
if label != nil {
m.Labels = append(m.Labels, label.GetName())
Expand Down Expand Up @@ -936,6 +943,7 @@ func fragmentWithoutFieldValuesToMinimalIssue(fragment issueFragmentWithoutField
},
}

m.Labels = make([]string, 0, len(fragment.Labels.Nodes))
for _, label := range fragment.Labels.Nodes {
m.Labels = append(m.Labels, string(label.Name))
}
Expand Down Expand Up @@ -1117,18 +1125,21 @@ func convertToMinimalPullRequest(pr *github.PullRequest) MinimalPullRequest {
m.MergedAt = pr.MergedAt.Format(time.RFC3339)
}

m.Labels = make([]string, 0, len(pr.Labels))
for _, label := range pr.Labels {
if label != nil {
m.Labels = append(m.Labels, label.GetName())
}
}

m.Assignees = make([]string, 0, len(pr.Assignees))
for _, assignee := range pr.Assignees {
if assignee != nil {
m.Assignees = append(m.Assignees, assignee.GetLogin())
}
}

m.RequestedReviewers = make([]string, 0, len(pr.RequestedReviewers))
for _, reviewer := range pr.RequestedReviewers {
if reviewer != nil {
m.RequestedReviewers = append(m.RequestedReviewers, reviewer.GetLogin())
Expand Down
2 changes: 1 addition & 1 deletion pkg/github/typed_consolidated_issue_enrichment_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ func TestTypedIssueReadEnrichmentAndLockdown(t *testing.T) {
Flags: stubFeatureFlags(map[string]bool{"lockdown-mode": lockdown}),
}
session, schemas := consolidatedIssueSession(t, deps, protocol, false)
text := `{"number":1,"title":"Child","state":"open","user":{"login":"author"},"assignees":[],"has_parent":true,"has_children":true,`
text := `{"number":1,"title":"Child","state":"open","user":{"login":"author"},"labels":[],"assignees":[],"comments":0,"has_parent":true,"has_children":true,`
if !lockdown {
text += `"parent":{"number":7,"title":"Parent","state":"OPEN","url":"parent","repository":"owner/repo"},`
}
Expand Down
24 changes: 12 additions & 12 deletions pkg/github/typed_consolidated_issue_outputs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -245,13 +245,13 @@ func consolidatedIssueDeps(t *testing.T) BaseDeps {
func TestTypedConsolidatedIssueOutputs(t *testing.T) {
const mutationText = `{"id":"123","url":"https://github.com/owner/repo/issues/1"}`
const subText = `{"id":7,"number":7,"title":"Child","body":"body","created_at":"2026-01-01T00:00:00Z","issue_field_values":[{"issue_field_id":11,"node_id":"IF_11","data_type":"number","value":0}]}`
const subStructured = `{"id":7,"number":7,"title":"Child","body":"body","created_at":"2026-01-01T00:00:00Z","assignees":[],"field_values":[{"issue_field_id":11,"data_type":"number","value":0}]}`
const subStructured = `{"id":7,"number":7,"title":"Child","body":"body","created_at":"2026-01-01T00:00:00Z","labels":[],"assignees":[],"field_values":[{"issue_field_id":11,"data_type":"number","value":0}]}`
cases := []struct {
tool string
args map[string]any
text string
}{
{"issue_read", map[string]any{"method": "get", "issue_number": "1.0"}, `{"number":1,"title":"Subject","state":"open","html_url":"https://github.com/owner/repo/issues/1","assignees":[]}`},
{"issue_read", map[string]any{"method": "get", "issue_number": "1.0"}, `{"number":1,"title":"Subject","state":"open","html_url":"https://github.com/owner/repo/issues/1","labels":[],"assignees":[],"comments":0}`},
{"issue_read", map[string]any{"method": "get_comments", "issue_number": "1", "page": "0", "perPage": "0"}, `[{"id":42,"body":"hello","html_url":"https://github.com/owner/repo/issues/1#issuecomment-42"}]`},
{"issue_read", map[string]any{"method": "get_comments", "issue_number": 2}, `[]`},
{"issue_read", map[string]any{"method": "get_comments", "issue_number": 3}, `[]`},
Expand Down Expand Up @@ -422,7 +422,7 @@ func TestTypedConsolidatedNullMutationResponses(t *testing.T) {
"method": method, "owner": "owner", "repo": "repo", "issue_number": 1, "sub_issue_id": 7, "after_id": 8,
}, payload)
if protocol == inventory.ProtocolVersionMultiRoundTrip {
data := `{"assignees":[]}`
data := `{"assignees":[],"labels":[]}`
if payload == "null" {
data = "null"
}
Expand All @@ -444,7 +444,7 @@ func TestConsolidatedIssueStrictSchemasAndScalars(t *testing.T) {
}{
{
issueReadOutputSchema(),
[]string{`{"method":"get","issue":null}`, `{"method":"get_comments","comments":[]}`, `{"method":"get_sub_issues","sub_issues":[null]}`, `{"method":"get_parent","parent":null}`, `{"method":"get","issue":{"number":0,"title":"","state":"","assignees":[]}}`, `{"method":"get_labels","labels":[],"totalCount":0}`},
[]string{`{"method":"get","issue":null}`, `{"method":"get_comments","comments":[]}`, `{"method":"get_sub_issues","sub_issues":[null]}`, `{"method":"get_parent","parent":null}`, `{"method":"get","issue":{"number":0,"title":"","state":"","comments":0,"labels":[],"assignees":[]}}`, `{"method":"get_labels","labels":[],"totalCount":0}`},
[]string{`null`, `[]`, `{}`, `{"method":"get"}`, `{"method":"get","comments":[]}`, `{"method":"get","issue":{"number":1}}`, `{"method":"get_parent","parent":{"number":1}}`, `{"method":"get_labels","labels":[]}`, `{"method":"get","issue":null,"comments":[]}`},
},
{
Expand All @@ -456,17 +456,17 @@ func TestConsolidatedIssueStrictSchemasAndScalars(t *testing.T) {
subIssueWriteOutputSchema(),
[]string{
`{"method":"add","issue":null}`,
`{"method":"remove","issue":{"assignees":[]}}`,
`{"method":"reprioritize","issue":{"state":"closed","state_reason":"duplicate","assignees":[]}}`,
`{"method":"add","issue":{"assignees":[],"field_values":[null,{"issue_field_id":1,"data_type":"date","value":"2026-01-01"}]}}`,
`{"method":"remove","issue":{"assignees":[],"labels":[]}}`,
`{"method":"reprioritize","issue":{"state":"closed","state_reason":"duplicate","assignees":[],"labels":[]}}`,
`{"method":"add","issue":{"assignees":[],"labels":[],"field_values":[null,{"issue_field_id":1,"data_type":"date","value":"2026-01-01"}]}}`,
},
[]string{
`null`, `{}`, `{"method":"add"}`, `{"method":"get","issue":null}`,
`{"method":"add","issue":{"assignees":[],"state":"OPEN"}}`,
`{"method":"add","issue":{"assignees":[],"state_reason":"unknown"}}`,
`{"method":"add","issue":{"assignees":[],"url":"api"}}`,
`{"method":"add","issue":{"assignees":[],"field_values":[{"issue_field_id":1,"data_type":"unknown","value":1}]}}`,
`{"method":"add","issue":{"assignees":[],"field_values":[{"issue_field_id":1,"data_type":"number","value":{}}]}}`,
`{"method":"add","issue":{"assignees":[],"labels":[],"state":"OPEN"}}`,
`{"method":"add","issue":{"assignees":[],"labels":[],"state_reason":"unknown"}}`,
`{"method":"add","issue":{"assignees":[],"labels":[],"url":"api"}}`,
`{"method":"add","issue":{"assignees":[],"labels":[],"field_values":[{"issue_field_id":1,"data_type":"unknown","value":1}]}}`,
`{"method":"add","issue":{"assignees":[],"labels":[],"field_values":[{"issue_field_id":1,"data_type":"number","value":{}}]}}`,
},
},
{
Expand Down
8 changes: 4 additions & 4 deletions pkg/github/typed_consolidated_pull_request_outputs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,7 @@ func runTypedPullRequestCases(t *testing.T, session *mcp.ClientSession, schemas
}

func TestTypedConsolidatedPullRequestOutputs(t *testing.T) {
const minimalPR = `{"number":1,"title":"Subject","body":"body","state":"open","draft":false,"merged":false,"mergeable_state":"clean","html_url":"https://github.com/owner/repo/pull/1","user":{"login":"octocat"},"head":{"ref":"feature","sha":"abc"},"base":{"ref":"main","sha":"def"},"created_at":"2026-01-01T00:00:00Z","updated_at":"2026-01-01T00:00:00Z"}`
const minimalPR = `{"number":1,"title":"Subject","body":"body","state":"open","draft":false,"merged":false,"mergeable_state":"clean","html_url":"https://github.com/owner/repo/pull/1","user":{"login":"octocat"},"labels":[],"assignees":[],"requested_reviewers":[],"head":{"ref":"feature","sha":"abc"},"base":{"ref":"main","sha":"def"},"created_at":"2026-01-01T00:00:00Z","updated_at":"2026-01-01T00:00:00Z"}`
const prRef = `{"id":"0","url":"https://github.com/owner/repo/pull/1"}`
cases := []typedPullRequestCase{
{"pull_request_read", map[string]any{"method": "get", "pullNumber": "1"}, minimalPR},
Expand Down Expand Up @@ -522,7 +522,7 @@ func TestTypedPullRequestContentParity(t *testing.T) {
require.NoError(t, schemas["pull_request_read"].Validate(result.StructuredContent))
} else {
assert.Nil(t, result.StructuredContent)
assert.Equal(t, `{"number":1,"title":"Subject","body":"body","state":"open","draft":false,"merged":false,"html_url":"https://github.com/owner/repo/pull/1","user":{"login":"octocat"}}`, text)
assert.Equal(t, `{"number":1,"title":"Subject","body":"body","state":"open","draft":false,"merged":false,"html_url":"https://github.com/owner/repo/pull/1","user":{"login":"octocat"},"labels":[],"assignees":[],"requested_reviewers":[]}`, text)
}
})
}
Expand All @@ -537,8 +537,8 @@ func TestConsolidatedPullRequestOutputSchemas(t *testing.T) {
}{
{
pullRequestReadOutputSchema(),
[]string{`null`, `[]`, `{"diff":""}`, `{"number":1,"title":"","state":"","draft":false,"merged":false,"html_url":""}`, `{"state":"","sha":"","total_count":0,"statuses":[]}`, `{"total_count":0,"check_runs":[]}`, `{"review_threads":[],"totalCount":0,"pageInfo":{"hasNextPage":false,"hasPreviousPage":false}}`, `[{"filename":"f"}]`, `[{"id":1,"html_url":""}]`},
[]string{`{}`, `"diff"`, `{"diff":1}`, `[{"unknown":1}]`, `{"number":1}`, `{"number":1,"title":"","state":"unknown","draft":false,"merged":false,"html_url":""}`},
[]string{`null`, `[]`, `{"diff":""}`, `{"number":1,"title":"","state":"","draft":false,"merged":false,"html_url":"","labels":[],"assignees":[],"requested_reviewers":[]}`, `{"state":"","sha":"","total_count":0,"statuses":[]}`, `{"total_count":0,"check_runs":[]}`, `{"review_threads":[],"totalCount":0,"pageInfo":{"hasNextPage":false,"hasPreviousPage":false}}`, `[{"filename":"f"}]`, `[{"id":1,"html_url":""}]`},
[]string{`{}`, `"diff"`, `{"diff":1}`, `[{"unknown":1}]`, `{"number":1}`, `{"number":1,"title":"","state":"unknown","draft":false,"merged":false,"html_url":"","labels":[],"assignees":[],"requested_reviewers":[]}`},
},
{
pullRequestWriteOutputSchema(),
Expand Down
8 changes: 4 additions & 4 deletions pkg/github/typed_granular_issue_outputs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ const granularMutationText = `{"id":"123","url":"https://github.com/owner/repo/i
const granularSubIssueAPIResponse = `{"id":7,"number":7,"title":"Child\u202e","body":"body\u202e","url":"https://api.github.com/repos/owner/repo/issues/7","html_url":"https://github.com/owner/repo/issues/7","comments_url":"https://api.github.com/repos/owner/repo/issues/7/comments","node_id":"I_7","issue_field_values":[{"issue_field_id":11,"node_id":"IF_11","data_type":"number","value":0},{"issue_field_id":12,"node_id":"IF_12","data_type":"text","value":""},{"issue_field_id":13,"node_id":"IF_13","data_type":"text","value":false},{"issue_field_id":14,"node_id":"IF_14","data_type":"text","value":null}]}`
const granularSubIssueText = `{"id":7,"number":7,"title":"Child","body":"body","url":"https://api.github.com/repos/owner/repo/issues/7","html_url":"https://github.com/owner/repo/issues/7","comments_url":"https://api.github.com/repos/owner/repo/issues/7/comments","node_id":"I_7","issue_field_values":[{"issue_field_id":11,"node_id":"IF_11","data_type":"number","value":0},{"issue_field_id":12,"node_id":"IF_12","data_type":"text","value":""},{"issue_field_id":13,"node_id":"IF_13","data_type":"text","value":false},{"issue_field_id":14,"node_id":"IF_14","data_type":"text","value":null}]}`

const granularSubIssueStructured = `{"method":"add","issue":{"id":7,"number":7,"title":"Child","body":"body","html_url":"https://github.com/owner/repo/issues/7","assignees":[],"field_values":[{"issue_field_id":11,"data_type":"number","value":0},{"issue_field_id":12,"data_type":"text","value":""},{"issue_field_id":13,"data_type":"text","value":false},{"issue_field_id":14,"data_type":"text","value":null}]}}`
const granularSubIssueStructured = `{"method":"add","issue":{"id":7,"number":7,"title":"Child","body":"body","html_url":"https://github.com/owner/repo/issues/7","labels":[],"assignees":[],"field_values":[{"issue_field_id":11,"data_type":"number","value":0},{"issue_field_id":12,"data_type":"text","value":""},{"issue_field_id":13,"data_type":"text","value":false},{"issue_field_id":14,"data_type":"text","value":null}]}}`

func granularIssueWireCases() []granularIssueWireCase {
cases := []granularIssueWireCase{
Expand Down Expand Up @@ -284,8 +284,8 @@ func TestGranularSubIssueStructuredSchema(t *testing.T) {
require.NoError(t, err)
for _, raw := range []string{
`{"method":"add","issue":null}`,
`{"method":"add","issue":{"id":7,"number":7,"state":"open","title":"Child","html_url":"https://github.com/owner/repo/issues/7","assignees":[],"field_values":[]}}`,
`{"method":"reprioritize","issue":{"id":7,"assignees":[],"field_values":[{"issue_field_id":11,"data_type":"number","value":0},{"issue_field_id":12,"data_type":"text","value":null}]}}`,
`{"method":"add","issue":{"id":7,"number":7,"state":"open","title":"Child","html_url":"https://github.com/owner/repo/issues/7","labels":[],"assignees":[],"field_values":[]}}`,
`{"method":"reprioritize","issue":{"id":7,"labels":[],"assignees":[],"field_values":[{"issue_field_id":11,"data_type":"number","value":0},{"issue_field_id":12,"data_type":"text","value":null}]}}`,
} {
var output any
require.NoError(t, json.Unmarshal([]byte(raw), &output))
Expand Down Expand Up @@ -416,7 +416,7 @@ func TestTypedGranularIssueWireErrors(t *testing.T) {
method := map[string]string{"add_sub_issue": "add", "remove_sub_issue": "remove", "reprioritize_sub_issue": "reprioritize"}[tc.name]
expectedText = fmt.Sprintf(`{"method":%q,"issue":null}`, method)
if payload == `{}` {
expectedText = fmt.Sprintf(`{"method":%q,"issue":{"assignees":[]}}`, method)
expectedText = fmt.Sprintf(`{"method":%q,"issue":{"assignees":[],"labels":[]}}`, method)
}
}
if schemas[tc.name] != nil {
Expand Down
Loading
Loading