From 57c39ba38892e6f34811e15c9cadecda6479a7fd Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 19 May 2026 16:51:35 +0000 Subject: [PATCH 1/4] Initial plan From e7632adedd4ecc628de599835bf312654393b3d0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 19 May 2026 17:12:08 +0000 Subject: [PATCH 2/4] chore: initial plan checkpoint Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/workflows/mcp-inspector.lock.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/mcp-inspector.lock.yml b/.github/workflows/mcp-inspector.lock.yml index 7609d0cd14c..34c3975531d 100644 --- a/.github/workflows/mcp-inspector.lock.yml +++ b/.github/workflows/mcp-inspector.lock.yml @@ -1000,7 +1000,7 @@ jobs: "url": "https://mcp.datadoghq.com/api/unstable/mcp-server/mcp?toolsets=core", "headers": { "DD_API_KEY": "\${DD_API_KEY}", - "DD_APPLICATION_KEY": "\${DD_APPLICATION_KEY}", + "DD_APPLICATION_KEY": "\${DD_APP_KEY}", "DD_SITE": "\${DD_SITE}" }, "tools": [ From 74bc55bab2976633aabeb9c4751fdfbcd4a018b2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 19 May 2026 17:24:33 +0000 Subject: [PATCH 3/4] feat: add generic x-deprecation-message schema walker; emit warning when deprecated fields are set Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/parser/README.md | 2 + pkg/parser/schema_deprecated_test.go | 213 ++++++++++++++++++ pkg/parser/schema_deprecation.go | 152 ++++++++++++- pkg/parser/schemas/main_workflow_schema.json | 3 + pkg/workflow/compiler_orchestrator_tools.go | 28 +++ .../compiler_orchestrator_tools_test.go | 106 +++++++++ pkg/workflow/tools_parser.go | 7 +- 7 files changed, 504 insertions(+), 7 deletions(-) diff --git a/pkg/parser/README.md b/pkg/parser/README.md index fce45cbedd6..b00b443de88 100644 --- a/pkg/parser/README.md +++ b/pkg/parser/README.md @@ -121,6 +121,8 @@ The package is designed for use both in the main CLI binary and in WebAssembly c | `GetSafeOutputTypeKeys` | `func() ([]string, error)` | Returns valid safe-output type keys from the schema | | `GetMainWorkflowDeprecatedFields` | `func() ([]DeprecatedField, error)` | Returns deprecated frontmatter fields with migration notes | | `FindDeprecatedFieldsInFrontmatter` | `func(map[string]any, []DeprecatedField) []DeprecatedField` | Finds deprecated fields present in a parsed frontmatter map | +| `GetMainWorkflowDeprecatedFieldsDeep` | `func() ([]DeprecatedField, error)` | Returns deprecated fields at any schema nesting level (e.g. `tools.grep`) with dot-separated paths | +| `FindDeprecatedFieldsInFrontmatterDeep` | `func(map[string]any, []DeprecatedField) []DeprecatedField` | Finds deprecated fields at any nesting depth in frontmatter using dot-separated paths | | `FindClosestMatches` | `func(target string, candidates []string, maxResults int) []string` | Finds the closest string matches (for typo suggestions) | | `LevenshteinDistance` | `func(a, b string) int` | Computes edit distance between two strings | diff --git a/pkg/parser/schema_deprecated_test.go b/pkg/parser/schema_deprecated_test.go index d1c3d222e09..1c7cfd6b4de 100644 --- a/pkg/parser/schema_deprecated_test.go +++ b/pkg/parser/schema_deprecated_test.go @@ -140,3 +140,216 @@ func TestExtractReplacementFromDescription(t *testing.T) { }) } } + +// --- Tests for deep walker ------------------------------------------------------- + +func TestGetMainWorkflowDeprecatedFieldsDeep(t *testing.T) { + fields, err := GetMainWorkflowDeprecatedFieldsDeep() + if err != nil { + t.Fatalf("GetMainWorkflowDeprecatedFieldsDeep() error = %v", err) + } + + // Build path→field map for easy lookup. + byPath := make(map[string]DeprecatedField, len(fields)) + for _, f := range fields { + byPath[f.Path] = f + } + + // tools.grep must be detected with its x-deprecation-message. + grep, ok := byPath["tools.grep"] + if !ok { + t.Error("expected 'tools.grep' in deep deprecated fields, not found") + } else { + if grep.DeprecationMessage == "" { + t.Error("tools.grep: DeprecationMessage should not be empty") + } + } + + // tools.serena must be detected. + if _, ok := byPath["tools.serena"]; !ok { + t.Error("expected 'tools.serena' in deep deprecated fields, not found") + } + + // tools.github.repos must be detected with its x-deprecation-message. + repos, ok := byPath["tools.github.repos"] + if !ok { + t.Error("expected 'tools.github.repos' in deep deprecated fields, not found") + } else { + if repos.DeprecationMessage == "" { + t.Error("tools.github.repos: DeprecationMessage should not be empty") + } + } + + // tools.github.toolset must be detected. + if _, ok := byPath["tools.github.toolset"]; !ok { + t.Error("expected 'tools.github.toolset' in deep deprecated fields, not found") + } + + t.Logf("Found %d deep deprecated fields in schema", len(fields)) +} + +func TestCollectDeprecatedDeep(t *testing.T) { + // Build a minimal schema that exercises nesting and oneOf traversal. + schema := map[string]any{ + "properties": map[string]any{ + "tools": map[string]any{ + "properties": map[string]any{ + "grep": map[string]any{ + "deprecated": true, + "description": "DEPRECATED: grep is always available.", + "x-deprecation-message": "Use bash instead.", + }, + "github": map[string]any{ + "oneOf": []any{ + map[string]any{"type": "boolean"}, + map[string]any{ + "type": "object", + "properties": map[string]any{ + "repos": map[string]any{ + "deprecated": true, + "description": "Deprecated. Use 'allowed-repos' instead.", + "x-deprecation-message": "'tools.github.repos' is deprecated. Use 'tools.github.allowed-repos' instead.", + }, + }, + }, + }, + }, + }, + }, + }, + } + + var results []DeprecatedField + collectDeprecatedDeep(schema, "", &results) + + byPath := make(map[string]DeprecatedField) + for _, f := range results { + byPath[f.Path] = f + } + + if _, ok := byPath["tools.grep"]; !ok { + t.Error("expected tools.grep to be found") + } + if _, ok := byPath["tools.github.repos"]; !ok { + t.Error("expected tools.github.repos to be found") + } + // Non-deprecated fields must not appear. + if _, ok := byPath["tools"]; ok { + t.Error("tools (non-deprecated) should not appear") + } + if _, ok := byPath["tools.github"]; ok { + t.Error("tools.github (non-deprecated) should not appear") + } +} + +func TestFindDeprecatedFieldsInFrontmatterDeep(t *testing.T) { + fields := []DeprecatedField{ + {Name: "grep", Path: "tools.grep", DeprecationMessage: "Use bash instead."}, + {Name: "repos", Path: "tools.github.repos", DeprecationMessage: "Use allowed-repos."}, + {Name: "old", Path: "old", DeprecationMessage: "Field removed."}, + } + + tests := []struct { + name string + frontmatter map[string]any + wantPaths []string + }{ + { + name: "no deprecated fields used", + frontmatter: map[string]any{ + "engine": "copilot", + "tools": map[string]any{"bash": true}, + }, + wantPaths: nil, + }, + { + name: "tools.grep present", + frontmatter: map[string]any{ + "tools": map[string]any{"grep": true}, + }, + wantPaths: []string{"tools.grep"}, + }, + { + name: "nested tools.github.repos present", + frontmatter: map[string]any{ + "tools": map[string]any{ + "github": map[string]any{"repos": []any{"owner/repo"}}, + }, + }, + wantPaths: []string{"tools.github.repos"}, + }, + { + name: "top-level deprecated field present", + frontmatter: map[string]any{ + "old": "value", + }, + wantPaths: []string{"old"}, + }, + { + name: "multiple deprecated fields", + frontmatter: map[string]any{ + "old": "value", + "tools": map[string]any{"grep": true}, + }, + wantPaths: []string{"old", "tools.grep"}, + }, + { + name: "tools key present but not grep", + frontmatter: map[string]any{ + "tools": map[string]any{"bash": true}, + }, + wantPaths: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + found := FindDeprecatedFieldsInFrontmatterDeep(tt.frontmatter, fields) + + foundPaths := make(map[string]bool) + for _, f := range found { + foundPaths[f.Path] = true + } + + if len(found) != len(tt.wantPaths) { + t.Errorf("FindDeprecatedFieldsInFrontmatterDeep() found %d, want %d (%v vs %v)", + len(found), len(tt.wantPaths), foundPaths, tt.wantPaths) + } + for _, p := range tt.wantPaths { + if !foundPaths[p] { + t.Errorf("expected path %q to be found, got %v", p, foundPaths) + } + } + }) + } +} + +func TestFieldExistsAtPath(t *testing.T) { + m := map[string]any{ + "tools": map[string]any{ + "grep": true, + "github": map[string]any{"repos": []any{"owner/repo"}}, + }, + } + + tests := []struct { + segments []string + want bool + }{ + {[]string{"tools"}, true}, + {[]string{"tools", "grep"}, true}, + {[]string{"tools", "github", "repos"}, true}, + {[]string{"tools", "bash"}, false}, + {[]string{"engine"}, false}, + {[]string{}, false}, + // tools is not a scalar — deeper navigation not possible from non-map + {[]string{"tools", "grep", "nested"}, false}, + } + + for _, tt := range tests { + got := fieldExistsAtPath(m, tt.segments) + if got != tt.want { + t.Errorf("fieldExistsAtPath(%v) = %v, want %v", tt.segments, got, tt.want) + } + } +} diff --git a/pkg/parser/schema_deprecation.go b/pkg/parser/schema_deprecation.go index fb1b640940f..2a49ad16434 100644 --- a/pkg/parser/schema_deprecation.go +++ b/pkg/parser/schema_deprecation.go @@ -5,6 +5,7 @@ import ( "fmt" "regexp" "sort" + "strings" "sync" "github.com/github/gh-aw/pkg/logger" @@ -14,9 +15,11 @@ var schemaDeprecationLog = logger.New("parser:schema_deprecation") // DeprecatedField represents a deprecated field with its replacement information type DeprecatedField struct { - Name string // The deprecated field name - Replacement string // The recommended replacement field name - Description string // Description from the schema + Name string // The deprecated field name (leaf key only) + Path string // Full dot-separated path, e.g. "tools.grep" (empty for top-level fields) + Replacement string // The recommended replacement field name + Description string // Description from the schema + DeprecationMessage string // x-deprecation-message from the schema (preferred user-facing text) } // deprecatedFieldsCache caches the result of parsing the main workflow schema so that @@ -135,3 +138,146 @@ func FindDeprecatedFieldsInFrontmatter(frontmatter map[string]any, deprecatedFie schemaDeprecationLog.Printf("Deprecated field check complete: found %d of %d fields in frontmatter", len(found), len(deprecatedFields)) return found } + +// ---- Deep (nested) schema walker ------------------------------------------------ + +// deprecatedFieldsDeepCache caches the result of the deep schema walk so the +// expensive 414 KB JSON unmarshal is only performed once per process lifetime. +var ( + deprecatedFieldsDeepOnce sync.Once + deprecatedFieldsDeepCache []DeprecatedField + deprecatedFieldsDeepErr error +) + +// GetMainWorkflowDeprecatedFieldsDeep returns deprecated fields from the entire +// schema hierarchy (nested properties and oneOf variants) as dot-separated paths +// (e.g. "tools.grep", "tools.github.repos"). +// The result is cached after the first call so the schema is only parsed once. +// Callers must not modify the returned slice. +func GetMainWorkflowDeprecatedFieldsDeep() ([]DeprecatedField, error) { + deprecatedFieldsDeepOnce.Do(func() { + schemaDeprecationLog.Print("Getting deep deprecated fields from main workflow schema") + var schemaDoc map[string]any + if err := json.Unmarshal([]byte(mainWorkflowSchema), &schemaDoc); err != nil { + deprecatedFieldsDeepErr = fmt.Errorf("failed to parse main workflow schema: %w", err) + return + } + var fields []DeprecatedField + collectDeprecatedDeep(schemaDoc, "", &fields) + sort.Slice(fields, func(i, j int) bool { + return fields[i].Path < fields[j].Path + }) + deprecatedFieldsDeepCache = fields + schemaDeprecationLog.Printf("Found %d deprecated fields (deep) in main workflow schema", len(fields)) + }) + return deprecatedFieldsDeepCache, deprecatedFieldsDeepErr +} + +// collectDeprecatedDeep recursively walks a schema node and appends any +// deprecated leaf properties to results. +// +// parentPath is the dot-joined path to the current schema node's parent +// (empty for the root schema). The function looks at the node's "properties" +// map (and "oneOf" / "anyOf" / "allOf" variants that may add more properties +// at the same level) to find fields marked deprecated:true. +func collectDeprecatedDeep(schemaNode map[string]any, parentPath string, results *[]DeprecatedField) { + properties, ok := schemaNode["properties"].(map[string]any) + if !ok { + return + } + + for fieldName, fieldSchema := range properties { + fieldSchemaMap, ok := fieldSchema.(map[string]any) + if !ok { + continue + } + + path := fieldName + if parentPath != "" { + path = parentPath + "." + fieldName + } + + if isDeprecated, ok := fieldSchemaMap["deprecated"].(bool); ok && isDeprecated { + description := "" + if desc, ok := fieldSchemaMap["description"].(string); ok { + description = desc + } + deprecationMsg := "" + if msg, ok := fieldSchemaMap["x-deprecation-message"].(string); ok { + deprecationMsg = msg + } + replacement := extractReplacementFromDescription(description) + + *results = append(*results, DeprecatedField{ + Name: fieldName, + Path: path, + Replacement: replacement, + Description: description, + DeprecationMessage: deprecationMsg, + }) + // Do not recurse further into a deprecated field — its children + // are implicitly deprecated through the parent. + continue + } + + // Recurse into nested properties. + collectDeprecatedDeep(fieldSchemaMap, path, results) + + // Also recurse into oneOf / anyOf / allOf variants: these can introduce + // additional properties at the same level (e.g. tools.github has an + // oneOf with an object variant that owns toolset, repos, etc.). + for _, keyword := range []string{"oneOf", "anyOf", "allOf"} { + if variants, ok := fieldSchemaMap[keyword].([]any); ok { + for _, v := range variants { + if vm, ok := v.(map[string]any); ok { + collectDeprecatedDeep(vm, path, results) + } + } + } + } + } +} + +// FindDeprecatedFieldsInFrontmatterDeep checks the full (possibly nested) +// frontmatter map for deprecated fields identified by their dot-separated paths. +// It returns every DeprecatedField whose path resolves to an existing key in the +// frontmatter (e.g. "tools.grep" matches frontmatter["tools"]["grep"]). +func FindDeprecatedFieldsInFrontmatterDeep(frontmatter map[string]any, deprecatedFields []DeprecatedField) []DeprecatedField { + schemaDeprecationLog.Printf("Deep-checking frontmatter for deprecated fields: %d fields to check", len(deprecatedFields)) + var found []DeprecatedField + + for _, f := range deprecatedFields { + lookupPath := f.Path + if lookupPath == "" { + lookupPath = f.Name + } + segments := strings.Split(lookupPath, ".") + if fieldExistsAtPath(frontmatter, segments) { + schemaDeprecationLog.Printf("Found deprecated field at path %q", lookupPath) + found = append(found, f) + } + } + + schemaDeprecationLog.Printf("Deep deprecated field check complete: found %d", len(found)) + return found +} + +// fieldExistsAtPath reports whether the nested key path described by segments +// exists in m. An empty segments slice always returns false. +func fieldExistsAtPath(m map[string]any, segments []string) bool { + if len(segments) == 0 { + return false + } + value, exists := m[segments[0]] + if !exists { + return false + } + if len(segments) == 1 { + return true + } + nested, ok := value.(map[string]any) + if !ok { + return false + } + return fieldExistsAtPath(nested, segments[1:]) +} diff --git a/pkg/parser/schemas/main_workflow_schema.json b/pkg/parser/schemas/main_workflow_schema.json index a9eb267e3d5..565d4ef68fa 100644 --- a/pkg/parser/schemas/main_workflow_schema.json +++ b/pkg/parser/schemas/main_workflow_schema.json @@ -3919,6 +3919,7 @@ "toolset": { "description": "Deprecated. Use 'toolsets' instead.", "deprecated": true, + "x-deprecation-message": "'tools.github.toolset' is deprecated. Use 'tools.github.toolsets' instead.", "oneOf": [ { "$ref": "#/$defs/github_toolset_name", @@ -3967,6 +3968,7 @@ "repos": { "description": "Deprecated. Use 'allowed-repos' instead.", "deprecated": true, + "x-deprecation-message": "'tools.github.repos' is deprecated. Use 'tools.github.allowed-repos' instead. Run 'gh aw fix' to automatically migrate.", "oneOf": [ { "type": "string", @@ -4495,6 +4497,7 @@ "serena": { "description": "REMOVED: Built-in support for Serena has been removed. Use the shared/mcp/serena.md workflow instead.", "deprecated": true, + "x-deprecation-message": "Built-in support for Serena has been removed. Import shared/mcp/serena.md instead:\n imports:\n - uses: shared/mcp/serena.md\n with:\n languages: [\"go\", \"typescript\"]", "x-removed": true, "x-removal-message": "tools.serena built-in support has been removed. Import shared/mcp/serena.md instead:\n imports:\n - uses: shared/mcp/serena.md\n with:\n languages: [\"go\", \"typescript\"]" }, diff --git a/pkg/workflow/compiler_orchestrator_tools.go b/pkg/workflow/compiler_orchestrator_tools.go index 19abb57a05a..031000f3112 100644 --- a/pkg/workflow/compiler_orchestrator_tools.go +++ b/pkg/workflow/compiler_orchestrator_tools.go @@ -69,6 +69,9 @@ func (c *Compiler) processToolsAndMarkdown(result *parser.FrontmatterResult, cle c.IncrementWarningCount() } + // Emit schema-driven deprecation warnings for any deprecated frontmatter fields. + c.warnDeprecatedFrontmatterFields(result.Frontmatter) + // Extract SafeOutputs configuration early so we can use it when applying default tools safeOutputs := c.extractSafeOutputsConfig(result.Frontmatter) @@ -429,3 +432,28 @@ func (c *Compiler) hasContentContext(frontmatter map[string]any) bool { orchestratorToolsLog.Printf("No content context detected in trigger events") return false } + +// warnDeprecatedFrontmatterFields emits a console warning for every deprecated +// field found in the frontmatter by walking the JSON schema hierarchy. +// The schema's x-deprecation-message (falling back to description) is used as +// the warning text so deprecations self-document without per-field plumbing. +func (c *Compiler) warnDeprecatedFrontmatterFields(frontmatter map[string]any) { + deprecatedFields, err := parser.GetMainWorkflowDeprecatedFieldsDeep() + if err != nil { + orchestratorToolsLog.Printf("Failed to load deprecated fields from schema: %v", err) + return + } + + found := parser.FindDeprecatedFieldsInFrontmatterDeep(frontmatter, deprecatedFields) + for _, f := range found { + msg := f.DeprecationMessage + if msg == "" { + msg = f.Description + } + if msg == "" { + msg = fmt.Sprintf("'%s' is deprecated", f.Path) + } + fmt.Fprintln(os.Stderr, console.FormatWarningMessage(msg)) + c.IncrementWarningCount() + } +} diff --git a/pkg/workflow/compiler_orchestrator_tools_test.go b/pkg/workflow/compiler_orchestrator_tools_test.go index 25cf0795dfc..cf3f5a2a8c3 100644 --- a/pkg/workflow/compiler_orchestrator_tools_test.go +++ b/pkg/workflow/compiler_orchestrator_tools_test.go @@ -3,8 +3,10 @@ package workflow import ( + "bytes" "os" "path/filepath" + "strings" "testing" "github.com/github/gh-aw/pkg/parser" @@ -719,3 +721,107 @@ engine: copilot assert.NotNil(t, result) } } + +// captureStderr redirects os.Stderr to a pipe and returns the captured output +// after calling fn. The original os.Stderr is always restored. +func captureStderr(fn func()) string { + oldStderr := os.Stderr + r, w, _ := os.Pipe() + os.Stderr = w + fn() + w.Close() + os.Stderr = oldStderr + var buf bytes.Buffer + buf.ReadFrom(r) + return buf.String() +} + +// TestWarnDeprecatedFrontmatterFields_ToolsGrep verifies that using tools.grep +// emits the schema-driven deprecation warning. +func TestWarnDeprecatedFrontmatterFields_ToolsGrep(t *testing.T) { + compiler := NewCompiler() + frontmatter := map[string]any{ + "tools": map[string]any{ + "grep": true, + }, + } + stderr := captureStderr(func() { + compiler.warnDeprecatedFrontmatterFields(frontmatter) + }) + + assert.Contains(t, stderr, "grep", "warning should mention grep") + assert.Equal(t, 1, compiler.warningCount, "warning count should be incremented") +} + +// TestWarnDeprecatedFrontmatterFields_NoDeprecatedFields verifies that no +// warning is emitted when no deprecated fields are present. +func TestWarnDeprecatedFrontmatterFields_NoDeprecatedFields(t *testing.T) { + compiler := NewCompiler() + frontmatter := map[string]any{ + "engine": "copilot", + "tools": map[string]any{"bash": true}, + } + stderr := captureStderr(func() { + compiler.warnDeprecatedFrontmatterFields(frontmatter) + }) + + assert.Empty(t, strings.TrimSpace(stderr), "no warning should be emitted for non-deprecated fields") + assert.Equal(t, 0, compiler.warningCount) +} + +// TestWarnDeprecatedFrontmatterFields_MessagePriority verifies that +// x-deprecation-message is preferred over description when both are present, +// and that description is used as a fallback when x-deprecation-message is empty. +func TestWarnDeprecatedFrontmatterFields_MessagePriority(t *testing.T) { + compiler := NewCompiler() + + // tools.grep has both description and x-deprecation-message in the schema. + // The x-deprecation-message should be the one emitted. + frontmatter := map[string]any{ + "tools": map[string]any{"grep": true}, + } + stderr := captureStderr(func() { + compiler.warnDeprecatedFrontmatterFields(frontmatter) + }) + + // x-deprecation-message for tools.grep starts with "grep is always available" + assert.Contains(t, stderr, "grep is always available", "x-deprecation-message should be preferred") +} + +// TestWarnDeprecatedFrontmatterFields_ToolsGitHubRepos verifies that using the +// deprecated tools.github.repos field emits a schema-driven warning. +func TestWarnDeprecatedFrontmatterFields_ToolsGitHubRepos(t *testing.T) { + compiler := NewCompiler() + frontmatter := map[string]any{ + "tools": map[string]any{ + "github": map[string]any{ + "repos": []any{"owner/repo"}, + }, + }, + } + stderr := captureStderr(func() { + compiler.warnDeprecatedFrontmatterFields(frontmatter) + }) + + assert.Contains(t, stderr, "allowed-repos", "warning should mention the replacement field") + assert.Equal(t, 1, compiler.warningCount) +} + +// TestWarnDeprecatedFrontmatterFields_MultipleFields verifies that multiple +// deprecated fields each emit a warning and increment the count correctly. +func TestWarnDeprecatedFrontmatterFields_MultipleFields(t *testing.T) { + compiler := NewCompiler() + frontmatter := map[string]any{ + "tools": map[string]any{ + "grep": true, + "serena": true, + }, + } + stderr := captureStderr(func() { + compiler.warnDeprecatedFrontmatterFields(frontmatter) + }) + + assert.Contains(t, stderr, "grep", "warning should mention grep") + assert.Contains(t, stderr, "serena", "warning should mention serena") + assert.Equal(t, 2, compiler.warningCount, "one warning per deprecated field") +} diff --git a/pkg/workflow/tools_parser.go b/pkg/workflow/tools_parser.go index b48f0509140..cbe43f08e4a 100644 --- a/pkg/workflow/tools_parser.go +++ b/pkg/workflow/tools_parser.go @@ -52,11 +52,9 @@ package workflow import ( "fmt" "maps" - "os" "strconv" "strings" - "github.com/github/gh-aw/pkg/console" "github.com/github/gh-aw/pkg/logger" ) @@ -292,8 +290,9 @@ func parseGitHubTool(val any) *GitHubToolConfig { if allowedRepos, ok := configMap["allowed-repos"]; ok { config.AllowedRepos = allowedRepos // Store as-is, validation will happen later } else if repos, ok := configMap["repos"]; ok { - // Deprecated: use 'allowed-repos' instead of 'repos' - fmt.Fprintln(os.Stderr, console.FormatWarningMessage("'tools.github.repos' is deprecated. Use 'tools.github.allowed-repos' instead. Run 'gh aw fix' to automatically migrate.")) + // Deprecated: use 'allowed-repos' instead of 'repos'. + // The deprecation warning is emitted by the generic schema-driven walker in + // warnDeprecatedFrontmatterFields; no extra hard-coded warning is needed here. config.AllowedRepos = repos // Populate canonical field for validation } if integrity, ok := configMap["min-integrity"].(string); ok { From d00704e1e63f5186cc1bf96c933b3ed085f96134 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 19 May 2026 19:26:48 +0000 Subject: [PATCH 4/4] Merge origin/main: remove orphan toolset deprecated property from schema Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/workflows/mcp-inspector.lock.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/mcp-inspector.lock.yml b/.github/workflows/mcp-inspector.lock.yml index 34c3975531d..7609d0cd14c 100644 --- a/.github/workflows/mcp-inspector.lock.yml +++ b/.github/workflows/mcp-inspector.lock.yml @@ -1000,7 +1000,7 @@ jobs: "url": "https://mcp.datadoghq.com/api/unstable/mcp-server/mcp?toolsets=core", "headers": { "DD_API_KEY": "\${DD_API_KEY}", - "DD_APPLICATION_KEY": "\${DD_APP_KEY}", + "DD_APPLICATION_KEY": "\${DD_APPLICATION_KEY}", "DD_SITE": "\${DD_SITE}" }, "tools": [