diff --git a/docs/feature-flags.md b/docs/feature-flags.md index 37e6e712e..35e0a708c 100644 --- a/docs/feature-flags.md +++ b/docs/feature-flags.md @@ -222,7 +222,7 @@ runtime behavior (such as output formatting) won't appear here. - **update_issue_type** - Update Issue Type - **Required OAuth Scopes**: `repo` - - `confidence`: How confident you are in this choice. Use 'high' for clear signal or explicit user request, 'medium' for reasonable inference with some ambiguity, 'low' for best guess with limited signal. (string, optional) + - `confidence`: How confident you are in this choice. Use 'HIGH' for clear signal or explicit user request, 'MEDIUM' for reasonable inference with some ambiguity, 'LOW' for best guess with limited signal. (string, optional) - `is_suggestion`: If true, this issue type change is sent to the API as a suggestion (suggest:true) rather than an applied value. Whether the type is applied or recorded as a proposal is determined by the API. (boolean, optional) - `issue_number`: The issue number to update (number, required) - `issue_type`: The issue type to set (string, required) diff --git a/pkg/github/__toolsnaps__/set_issue_fields.snap b/pkg/github/__toolsnaps__/set_issue_fields.snap index 8f25d0969..7a98fde2a 100644 --- a/pkg/github/__toolsnaps__/set_issue_fields.snap +++ b/pkg/github/__toolsnaps__/set_issue_fields.snap @@ -12,11 +12,11 @@ "items": { "properties": { "confidence": { - "description": "How confident you are in this choice. Use 'high' for clear signal or explicit user request, 'medium' for reasonable inference with some ambiguity, 'low' for best guess with limited signal.", + "description": "How confident you are in this choice. Use 'HIGH' for clear signal or explicit user request, 'MEDIUM' for reasonable inference with some ambiguity, 'LOW' for best guess with limited signal.", "enum": [ - "low", - "medium", - "high" + "LOW", + "MEDIUM", + "HIGH" ], "type": "string" }, diff --git a/pkg/github/__toolsnaps__/update_issue_labels.snap b/pkg/github/__toolsnaps__/update_issue_labels.snap index 21f7fea6b..2b31d756b 100644 --- a/pkg/github/__toolsnaps__/update_issue_labels.snap +++ b/pkg/github/__toolsnaps__/update_issue_labels.snap @@ -4,7 +4,7 @@ "openWorldHint": true, "title": "Update Issue Labels" }, - "description": "Update the labels of an existing issue. This replaces the current labels with the provided list. When setting values, include a confidence level (low, medium, or high) reflecting how certain you are about the choice.", + "description": "Update the labels of an existing issue. This replaces the current labels with the provided list. When setting values, include a confidence level (LOW, MEDIUM, or HIGH) reflecting how certain you are about the choice.", "inputSchema": { "properties": { "issue_number": { @@ -23,11 +23,11 @@ { "properties": { "confidence": { - "description": "How confident you are in this choice. Use 'high' for clear signal or explicit user request, 'medium' for reasonable inference with some ambiguity, 'low' for best guess with limited signal.", + "description": "How confident you are in this choice. Use 'HIGH' for clear signal or explicit user request, 'MEDIUM' for reasonable inference with some ambiguity, 'LOW' for best guess with limited signal.", "enum": [ - "low", - "medium", - "high" + "LOW", + "MEDIUM", + "HIGH" ], "type": "string" }, diff --git a/pkg/github/__toolsnaps__/update_issue_type.snap b/pkg/github/__toolsnaps__/update_issue_type.snap index 2f39b2d3b..d07a9d43d 100644 --- a/pkg/github/__toolsnaps__/update_issue_type.snap +++ b/pkg/github/__toolsnaps__/update_issue_type.snap @@ -4,15 +4,15 @@ "openWorldHint": true, "title": "Update Issue Type" }, - "description": "Update the type of an existing issue (e.g. 'bug', 'feature'). When setting values, include a confidence level (low, medium, or high) reflecting how certain you are about the choice.", + "description": "Update the type of an existing issue (e.g. 'bug', 'feature'). When setting values, include a confidence level (LOW, MEDIUM, or HIGH) reflecting how certain you are about the choice.", "inputSchema": { "properties": { "confidence": { - "description": "How confident you are in this choice. Use 'high' for clear signal or explicit user request, 'medium' for reasonable inference with some ambiguity, 'low' for best guess with limited signal.", + "description": "How confident you are in this choice. Use 'HIGH' for clear signal or explicit user request, 'MEDIUM' for reasonable inference with some ambiguity, 'LOW' for best guess with limited signal.", "enum": [ - "low", - "medium", - "high" + "LOW", + "MEDIUM", + "HIGH" ], "type": "string" }, diff --git a/pkg/github/granular_tools_test.go b/pkg/github/granular_tools_test.go index 27e8079f9..4a274ac31 100644 --- a/pkg/github/granular_tools_test.go +++ b/pkg/github/granular_tools_test.go @@ -476,12 +476,12 @@ func TestGranularUpdateIssueLabelsConfidence(t *testing.T) { "repo": "repo", "issue_number": float64(1), "labels": []any{ - map[string]any{"name": "bug", "confidence": "high"}, + map[string]any{"name": "bug", "confidence": "HIGH"}, }, }, expectedReq: map[string]any{ "labels": []any{ - map[string]any{"name": "bug", "confidence": "high"}, + map[string]any{"name": "bug", "confidence": "HIGH"}, }, }, }, @@ -492,12 +492,28 @@ func TestGranularUpdateIssueLabelsConfidence(t *testing.T) { "repo": "repo", "issue_number": float64(1), "labels": []any{ - map[string]any{"name": "bug", "rationale": "Reports a crash", "confidence": "medium"}, + map[string]any{"name": "bug", "rationale": "Reports a crash", "confidence": "MEDIUM"}, }, }, expectedReq: map[string]any{ "labels": []any{ - map[string]any{"name": "bug", "rationale": "Reports a crash", "confidence": "medium"}, + map[string]any{"name": "bug", "rationale": "Reports a crash", "confidence": "MEDIUM"}, + }, + }, + }, + { + name: "label confidence is normalized", + requestArgs: map[string]any{ + "owner": "owner", + "repo": "repo", + "issue_number": float64(1), + "labels": []any{ + map[string]any{"name": "bug", "confidence": " high\t"}, + }, + }, + expectedReq: map[string]any{ + "labels": []any{ + map[string]any{"name": "bug", "confidence": "HIGH"}, }, }, }, @@ -528,7 +544,7 @@ func TestGranularUpdateIssueLabelsConfidence(t *testing.T) { require.NoError(t, err) errorContent := getErrorResult(t, result) - assert.Contains(t, errorContent.Text, "confidence must be one of: low, medium, high") + assert.Contains(t, errorContent.Text, "confidence must be one of: LOW, MEDIUM, HIGH") return } @@ -742,12 +758,12 @@ func TestGranularUpdateIssueTypeConfidence(t *testing.T) { "repo": "repo", "issue_number": float64(1), "issue_type": "bug", - "confidence": "high", + "confidence": "HIGH", }, expectedReq: map[string]any{ "type": map[string]any{ "value": "bug", - "confidence": "high", + "confidence": "HIGH", }, }, }, @@ -759,13 +775,13 @@ func TestGranularUpdateIssueTypeConfidence(t *testing.T) { "issue_number": float64(1), "issue_type": "feature", "rationale": "Asks for dark mode support", - "confidence": "medium", + "confidence": "MEDIUM", }, expectedReq: map[string]any{ "type": map[string]any{ "value": "feature", "rationale": "Asks for dark mode support", - "confidence": "medium", + "confidence": "MEDIUM", }, }, }, @@ -776,12 +792,28 @@ func TestGranularUpdateIssueTypeConfidence(t *testing.T) { "repo": "repo", "issue_number": float64(1), "issue_type": "bug", - "confidence": "low", + "confidence": "LOW", + }, + expectedReq: map[string]any{ + "type": map[string]any{ + "value": "bug", + "confidence": "LOW", + }, + }, + }, + { + name: "type confidence is normalized", + requestArgs: map[string]any{ + "owner": "owner", + "repo": "repo", + "issue_number": float64(1), + "issue_type": "bug", + "confidence": " medium ", }, expectedReq: map[string]any{ "type": map[string]any{ "value": "bug", - "confidence": "low", + "confidence": "MEDIUM", }, }, }, @@ -820,7 +852,7 @@ func TestGranularUpdateIssueTypeInvalidConfidence(t *testing.T) { "issue_type": "bug", "confidence": "very_high", }, - expectedErrText: "confidence must be one of: low, medium, high", + expectedErrText: "confidence must be one of: LOW, MEDIUM, HIGH", }, { name: "confidence wrong type", @@ -1599,7 +1631,7 @@ func TestGranularSetIssueFields(t *testing.T) { }) t.Run("successful set with confidence", func(t *testing.T) { - confidence := "high" + confidence := "HIGH" matchers := []githubv4mock.Matcher{ githubv4mock.NewQueryMatcher( struct { @@ -1680,7 +1712,7 @@ func TestGranularSetIssueFields(t *testing.T) { map[string]any{ "field_id": "FIELD_1", "text_value": "hello", - "confidence": "high", + "confidence": " high ", }, }, }) @@ -1709,11 +1741,11 @@ func TestGranularSetIssueFields(t *testing.T) { result, err := handler(ContextWithDeps(context.Background(), deps), &request) require.NoError(t, err) textContent := getTextResult(t, result) - assert.Contains(t, textContent.Text, "confidence must be one of: low, medium, high") + assert.Contains(t, textContent.Text, "confidence must be one of: LOW, MEDIUM, HIGH") }) t.Run("confidence is sent when supplied", func(t *testing.T) { - confidence := "high" + confidence := "HIGH" matchers := []githubv4mock.Matcher{ githubv4mock.NewQueryMatcher( struct { @@ -1794,7 +1826,7 @@ func TestGranularSetIssueFields(t *testing.T) { map[string]any{ "field_id": "FIELD_1", "text_value": "hello", - "confidence": "high", + "confidence": "HIGH", }, }, }) diff --git a/pkg/github/issues_granular.go b/pkg/github/issues_granular.go index 3ddfd682f..157d5595f 100644 --- a/pkg/github/issues_granular.go +++ b/pkg/github/issues_granular.go @@ -19,6 +19,10 @@ import ( "github.com/shurcooL/githubv4" ) +func normalizeConfidence(confidence string) string { + return strings.ToUpper(strings.TrimSpace(confidence)) +} + // issueUpdateTool is a helper to create single-field issue update tools. func issueUpdateTool( t translations.TranslationHelperFunc, @@ -281,7 +285,7 @@ func GranularUpdateIssueLabels(t translations.TranslationHelperFunc) inventory.S ToolsetMetadataIssues, mcp.Tool{ Name: "update_issue_labels", - Description: t("TOOL_UPDATE_ISSUE_LABELS_DESCRIPTION", "Update the labels of an existing issue. This replaces the current labels with the provided list. When setting values, include a confidence level (low, medium, or high) reflecting how certain you are about the choice."), + Description: t("TOOL_UPDATE_ISSUE_LABELS_DESCRIPTION", "Update the labels of an existing issue. This replaces the current labels with the provided list. When setting values, include a confidence level (LOW, MEDIUM, or HIGH) reflecting how certain you are about the choice."), Annotations: &mcp.ToolAnnotations{ Title: t("TOOL_UPDATE_ISSUE_LABELS_USER_TITLE", "Update Issue Labels"), ReadOnlyHint: false, @@ -325,8 +329,8 @@ func GranularUpdateIssueLabels(t translations.TranslationHelperFunc) inventory.S }, "confidence": { Type: "string", - Description: "How confident you are in this choice. Use 'high' for clear signal or explicit user request, 'medium' for reasonable inference with some ambiguity, 'low' for best guess with limited signal.", - Enum: []any{"low", "medium", "high"}, + Description: "How confident you are in this choice. Use 'HIGH' for clear signal or explicit user request, 'MEDIUM' for reasonable inference with some ambiguity, 'LOW' for best guess with limited signal.", + Enum: []any{"LOW", "MEDIUM", "HIGH"}, }, "is_suggestion": { Type: "boolean", @@ -398,8 +402,9 @@ func GranularUpdateIssueLabels(t translations.TranslationHelperFunc) inventory.S if err != nil { return utils.NewToolResultError(err.Error()), nil, nil } - if confidence != "" && confidence != "low" && confidence != "medium" && confidence != "high" { - return utils.NewToolResultError("confidence must be one of: low, medium, high"), nil, nil + confidence = normalizeConfidence(confidence) + if confidence != "" && confidence != "LOW" && confidence != "MEDIUM" && confidence != "HIGH" { + return utils.NewToolResultError("confidence must be one of: LOW, MEDIUM, HIGH"), nil, nil } isSuggestion, err := OptionalParam[bool](v, "is_suggestion") if err != nil { @@ -505,7 +510,7 @@ func GranularUpdateIssueType(t translations.TranslationHelperFunc) inventory.Ser ToolsetMetadataIssues, mcp.Tool{ Name: "update_issue_type", - Description: t("TOOL_UPDATE_ISSUE_TYPE_DESCRIPTION", "Update the type of an existing issue (e.g. 'bug', 'feature'). When setting values, include a confidence level (low, medium, or high) reflecting how certain you are about the choice."), + Description: t("TOOL_UPDATE_ISSUE_TYPE_DESCRIPTION", "Update the type of an existing issue (e.g. 'bug', 'feature'). When setting values, include a confidence level (LOW, MEDIUM, or HIGH) reflecting how certain you are about the choice."), Annotations: &mcp.ToolAnnotations{ Title: t("TOOL_UPDATE_ISSUE_TYPE_USER_TITLE", "Update Issue Type"), ReadOnlyHint: false, @@ -540,8 +545,8 @@ func GranularUpdateIssueType(t translations.TranslationHelperFunc) inventory.Ser }, "confidence": { Type: "string", - Description: "How confident you are in this choice. Use 'high' for clear signal or explicit user request, 'medium' for reasonable inference with some ambiguity, 'low' for best guess with limited signal.", - Enum: []any{"low", "medium", "high"}, + Description: "How confident you are in this choice. Use 'HIGH' for clear signal or explicit user request, 'MEDIUM' for reasonable inference with some ambiguity, 'LOW' for best guess with limited signal.", + Enum: []any{"LOW", "MEDIUM", "HIGH"}, }, "is_suggestion": { Type: "boolean", @@ -582,8 +587,9 @@ func GranularUpdateIssueType(t translations.TranslationHelperFunc) inventory.Ser if err != nil { return utils.NewToolResultError(err.Error()), nil, nil } - if confidence != "" && confidence != "low" && confidence != "medium" && confidence != "high" { - return utils.NewToolResultError("confidence must be one of: low, medium, high"), nil, nil + confidence = normalizeConfidence(confidence) + if confidence != "" && confidence != "LOW" && confidence != "MEDIUM" && confidence != "HIGH" { + return utils.NewToolResultError("confidence must be one of: LOW, MEDIUM, HIGH"), nil, nil } isSuggestion, err := OptionalParam[bool](args, "is_suggestion") if err != nil { @@ -987,8 +993,8 @@ func GranularSetIssueFields(t translations.TranslationHelperFunc) inventory.Serv }, "confidence": { Type: "string", - Description: "How confident you are in this choice. Use 'high' for clear signal or explicit user request, 'medium' for reasonable inference with some ambiguity, 'low' for best guess with limited signal.", - Enum: []any{"low", "medium", "high"}, + Description: "How confident you are in this choice. Use 'HIGH' for clear signal or explicit user request, 'MEDIUM' for reasonable inference with some ambiguity, 'LOW' for best guess with limited signal.", + Enum: []any{"LOW", "MEDIUM", "HIGH"}, }, "is_suggestion": { Type: "boolean", @@ -1111,8 +1117,9 @@ func GranularSetIssueFields(t translations.TranslationHelperFunc) inventory.Serv if err != nil { return utils.NewToolResultError(err.Error()), nil, nil } - if confidence != "" && confidence != "low" && confidence != "medium" && confidence != "high" { - return utils.NewToolResultError("confidence must be one of: low, medium, high"), nil, nil + confidence = normalizeConfidence(confidence) + if confidence != "" && confidence != "LOW" && confidence != "MEDIUM" && confidence != "HIGH" { + return utils.NewToolResultError("confidence must be one of: LOW, MEDIUM, HIGH"), nil, nil } if confidence != "" { input.Confidence = &confidence