From 0eb7fe05a71a1274652cad75d4024335d19042be Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 16 Feb 2026 01:31:32 +0000 Subject: [PATCH 1/4] Initial plan From a486e80a810c17123039ada857b0bedf1246c439 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 16 Feb 2026 01:40:07 +0000 Subject: [PATCH 2/4] feat: change URN field to use *urn.URN type - Added github.com/leodido/go-urn dependency - Created pkg/codingcontext/urn package with YAML/JSON marshaling support - Changed BaseFrontMatter.URN field type from string to *urn.URN - Changed YAML/JSON tags from "id" to "urn" - Updated all test files to use new URN type with helper functions - Updated result.go to handle URN pointer conversion to string Co-authored-by: alexec <1142830+alexec@users.noreply.github.com> --- go.mod | 1 + go.sum | 2 + pkg/codingcontext/markdown/frontmatter.go | 5 +- .../markdown/frontmatter_command_test.go | 38 +++++--- .../markdown/frontmatter_rule_test.go | 26 ++++-- .../markdown/frontmatter_task_test.go | 31 +++++-- pkg/codingcontext/result.go | 6 +- pkg/codingcontext/result_test.go | 28 ++++-- pkg/codingcontext/urn/urn.go | 91 +++++++++++++++++++ 9 files changed, 187 insertions(+), 41 deletions(-) create mode 100644 pkg/codingcontext/urn/urn.go diff --git a/go.mod b/go.mod index a2730784..8d2694cc 100644 --- a/go.mod +++ b/go.mod @@ -18,6 +18,7 @@ require ( github.com/hashicorp/go-safetemp v1.0.0 // indirect github.com/hashicorp/go-version v1.1.0 // indirect github.com/klauspost/compress v1.15.0 // indirect + github.com/leodido/go-urn v1.4.0 // indirect github.com/mitchellh/go-homedir v1.0.0 // indirect github.com/mitchellh/go-testing-interface v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect diff --git a/go.sum b/go.sum index af935d2f..2d28004f 100644 --- a/go.sum +++ b/go.sum @@ -26,6 +26,8 @@ github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUq github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= github.com/klauspost/compress v1.15.0 h1:xqfchp4whNFxn5A4XFyyYtitiWI8Hy5EW59jEwcyL6U= github.com/klauspost/compress v1.15.0/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/mitchellh/go-homedir v1.0.0 h1:vKb8ShqSby24Yrqr/yDYkuFz8d0WUjys40rvnGC8aR0= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-testing-interface v1.0.0 h1:fzU/JVNcaqHQEcVFAKeR41fkiLdIPrefOvVG1VZ96U0= diff --git a/pkg/codingcontext/markdown/frontmatter.go b/pkg/codingcontext/markdown/frontmatter.go index 3338b51c..3afbf6e4 100644 --- a/pkg/codingcontext/markdown/frontmatter.go +++ b/pkg/codingcontext/markdown/frontmatter.go @@ -5,13 +5,14 @@ import ( "fmt" "github.com/kitproj/coding-context-cli/pkg/codingcontext/mcp" + "github.com/kitproj/coding-context-cli/pkg/codingcontext/urn" ) // BaseFrontMatter represents parsed YAML frontmatter from markdown files type BaseFrontMatter struct { // URN is an optional unique identifier for the prompt in URN format (e.g. urn:agents:task:) - // Automatically inferred from filename if not specified in frontmatter - URN string `yaml:"id,omitempty" json:"id,omitempty"` + // If not specified in frontmatter, this field remains empty and must be set explicitly by tooling if required. + URN *urn.URN `yaml:"urn,omitempty" json:"urn,omitempty"` // Name is an optional human-readable name for the task // Metadata only, does not affect task matching or filtering diff --git a/pkg/codingcontext/markdown/frontmatter_command_test.go b/pkg/codingcontext/markdown/frontmatter_command_test.go index d6cbff09..258d1803 100644 --- a/pkg/codingcontext/markdown/frontmatter_command_test.go +++ b/pkg/codingcontext/markdown/frontmatter_command_test.go @@ -3,9 +3,19 @@ package markdown import ( "testing" + "github.com/kitproj/coding-context-cli/pkg/codingcontext/urn" "gopkg.in/yaml.v3" ) +// mustParseURN is a test helper that parses a URN string and panics on error +func mustParseURNCommand(s string) *urn.URN { + u, ok := urn.Parse([]byte(s)) + if !ok { + panic("failed to parse URN: " + s) + } + return u +} + func TestCommandFrontMatter_Marshal(t *testing.T) { tests := []struct { name string @@ -21,12 +31,12 @@ func TestCommandFrontMatter_Marshal(t *testing.T) { name: "command with standard id, name, description", command: CommandFrontMatter{ BaseFrontMatter: BaseFrontMatter{ - URN: "urn:agents:command:standard", + URN: mustParseURNCommand("urn:agents:command:standard"), Name: "Standard Command", Description: "This is a standard command with metadata", }, }, - want: `id: urn:agents:command:standard + want: `urn: urn:agents:command:standard name: Standard Command description: This is a standard command with metadata `, @@ -35,7 +45,7 @@ description: This is a standard command with metadata name: "command with expand false", command: CommandFrontMatter{ BaseFrontMatter: BaseFrontMatter{ - URN: "urn:agents:command:no-expand", + URN: mustParseURNCommand("urn:agents:command:no-expand"), Name: "No Expand Command", Description: "Command with expansion disabled", }, @@ -44,7 +54,7 @@ description: This is a standard command with metadata return &b }(), }, - want: `id: urn:agents:command:no-expand + want: `urn: urn:agents:command:no-expand name: No Expand Command description: Command with expansion disabled expand: false @@ -54,7 +64,7 @@ expand: false name: "command with selectors", command: CommandFrontMatter{ BaseFrontMatter: BaseFrontMatter{ - URN: "urn:agents:command:selector", + URN: mustParseURNCommand("urn:agents:command:selector"), Name: "Selector Command", Description: "Command with selectors", }, @@ -63,7 +73,7 @@ expand: false "feature": "auth", }, }, - want: `id: urn:agents:command:selector + want: `urn: urn:agents:command:selector name: Selector Command description: Command with selectors selectors: @@ -95,13 +105,13 @@ func TestCommandFrontMatter_Unmarshal(t *testing.T) { }{ { name: "command with standard id, name, description", - yaml: `id: urn:agents:command:named + yaml: `urn: urn:agents:command:named name: Named Command description: A command with standard fields `, want: CommandFrontMatter{ BaseFrontMatter: BaseFrontMatter{ - URN: "urn:agents:command:named", + URN: mustParseURNCommand("urn:agents:command:named"), Name: "Named Command", Description: "A command with standard fields", }, @@ -109,14 +119,14 @@ description: A command with standard fields }, { name: "command with expand false", - yaml: `id: urn:agents:command:no-expand + yaml: `urn: urn:agents:command:no-expand name: No Expand description: No expansion expand: false `, want: CommandFrontMatter{ BaseFrontMatter: BaseFrontMatter{ - URN: "urn:agents:command:no-expand", + URN: mustParseURNCommand("urn:agents:command:no-expand"), Name: "No Expand", Description: "No expansion", }, @@ -128,7 +138,7 @@ expand: false }, { name: "command with selectors", - yaml: `id: urn:agents:command:selector + yaml: `urn: urn:agents:command:selector name: Selector Command description: Has selectors selectors: @@ -137,7 +147,7 @@ selectors: `, want: CommandFrontMatter{ BaseFrontMatter: BaseFrontMatter{ - URN: "urn:agents:command:selector", + URN: mustParseURNCommand("urn:agents:command:selector"), Name: "Selector Command", Description: "Has selectors", }, @@ -161,7 +171,9 @@ selectors: } // Compare fields individually - if got.URN != tt.want.URN { + if (got.URN == nil) != (tt.want.URN == nil) { + t.Errorf("URN = %v, want %v", got.URN, tt.want.URN) + } else if got.URN != nil && tt.want.URN != nil && !got.URN.Equal(tt.want.URN) { t.Errorf("URN = %q, want %q", got.URN, tt.want.URN) } if got.Name != tt.want.Name { diff --git a/pkg/codingcontext/markdown/frontmatter_rule_test.go b/pkg/codingcontext/markdown/frontmatter_rule_test.go index 644691f7..22854c9b 100644 --- a/pkg/codingcontext/markdown/frontmatter_rule_test.go +++ b/pkg/codingcontext/markdown/frontmatter_rule_test.go @@ -4,9 +4,19 @@ import ( "testing" "github.com/kitproj/coding-context-cli/pkg/codingcontext/mcp" + "github.com/kitproj/coding-context-cli/pkg/codingcontext/urn" "gopkg.in/yaml.v3" ) +// mustParseURNRule is a test helper that parses a URN string and panics on error +func mustParseURNRule(s string) *urn.URN { + u, ok := urn.Parse([]byte(s)) + if !ok { + panic("failed to parse URN: " + s) + } + return u +} + func TestRuleFrontMatter_Marshal(t *testing.T) { tests := []struct { name string @@ -22,12 +32,12 @@ func TestRuleFrontMatter_Marshal(t *testing.T) { name: "rule with standard id, name, description", rule: RuleFrontMatter{ BaseFrontMatter: BaseFrontMatter{ - URN: "urn:agents:rule:standard", + URN: mustParseURNRule("urn:agents:rule:standard"), Name: "Standard Rule", Description: "This is a standard rule with metadata", }, }, - want: `id: urn:agents:rule:standard + want: `urn: urn:agents:rule:standard name: Standard Rule description: This is a standard rule with metadata `, @@ -63,7 +73,7 @@ agent: cursor name: "rule with all fields", rule: RuleFrontMatter{ BaseFrontMatter: BaseFrontMatter{ - URN: "urn:agents:rule:all-fields", + URN: mustParseURNRule("urn:agents:rule:all-fields"), Name: "Complete Rule", Description: "A rule with all fields", }, @@ -77,7 +87,7 @@ agent: cursor }, RuleName: "test-rule", }, - want: `id: urn:agents:rule:all-fields + want: `urn: urn:agents:rule:all-fields name: Complete Rule description: A rule with all fields task_names: @@ -119,13 +129,13 @@ func TestRuleFrontMatter_Unmarshal(t *testing.T) { }{ { name: "rule with standard id, name, description", - yaml: `id: urn:agents:rule:named + yaml: `urn: urn:agents:rule:named name: Named Rule description: A rule with standard fields `, want: RuleFrontMatter{ BaseFrontMatter: BaseFrontMatter{ - URN: "urn:agents:rule:named", + URN: mustParseURNRule("urn:agents:rule:named"), Name: "Named Rule", Description: "A rule with standard fields", }, @@ -183,7 +193,9 @@ languages: } // Compare fields individually - if got.URN != tt.want.URN { + if (got.URN == nil) != (tt.want.URN == nil) { + t.Errorf("URN = %v, want %v", got.URN, tt.want.URN) + } else if got.URN != nil && tt.want.URN != nil && !got.URN.Equal(tt.want.URN) { t.Errorf("URN = %q, want %q", got.URN, tt.want.URN) } if got.Name != tt.want.Name { diff --git a/pkg/codingcontext/markdown/frontmatter_task_test.go b/pkg/codingcontext/markdown/frontmatter_task_test.go index d2d49f71..deeb2c46 100644 --- a/pkg/codingcontext/markdown/frontmatter_task_test.go +++ b/pkg/codingcontext/markdown/frontmatter_task_test.go @@ -3,9 +3,19 @@ package markdown import ( "testing" + "github.com/kitproj/coding-context-cli/pkg/codingcontext/urn" "gopkg.in/yaml.v3" ) +// mustParseURN is a test helper that parses a URN string and panics on error +func mustParseURN(s string) *urn.URN { + u, ok := urn.Parse([]byte(s)) + if !ok { + panic("failed to parse URN: " + s) + } + return u +} + func TestTaskFrontMatter_Marshal(t *testing.T) { tests := []struct { name string @@ -25,14 +35,14 @@ func TestTaskFrontMatter_Marshal(t *testing.T) { name: "task with standard id, name, description", task: TaskFrontMatter{ BaseFrontMatter: BaseFrontMatter{ - URN: "urn:agents:task:standard-task", + URN: mustParseURN("urn:agents:task:standard-task"), Name: "Standard Test Task", Description: "This is a test task with standard fields", Content: map[string]any{"task_name": "standard-task"}, }, }, want: `task_name: standard-task -id: urn:agents:task:standard-task +urn: urn:agents:task:standard-task name: Standard Test Task description: This is a test task with standard fields `, @@ -41,7 +51,7 @@ description: This is a test task with standard fields name: "task with all fields", task: TaskFrontMatter{ BaseFrontMatter: BaseFrontMatter{ - URN: "urn:agents:task:full-task", + URN: mustParseURN("urn:agents:task:full-task"), Name: "Full Task", Description: "A task with all fields", Content: map[string]any{"task_name": "full-task"}, @@ -56,7 +66,7 @@ description: This is a test task with standard fields }, }, want: `task_name: full-task -id: urn:agents:task:full-task +urn: urn:agents:task:full-task name: Full Task description: A task with all fields agent: cursor @@ -118,13 +128,13 @@ func TestTaskFrontMatter_Unmarshal(t *testing.T) { { name: "task with standard id, name, description", yaml: `task_name: standard-task -id: urn:agents:task:standard-task +urn: urn:agents:task:standard-task name: Standard Task description: This is a standard task `, want: TaskFrontMatter{ BaseFrontMatter: BaseFrontMatter{ - URN: "urn:agents:task:standard-task", + URN: mustParseURN("urn:agents:task:standard-task"), Name: "Standard Task", Description: "This is a standard task", Content: map[string]any{"task_name": "standard-task"}, @@ -161,7 +171,7 @@ languages: { name: "full task", yaml: `task_name: full-task -id: urn:agents:task:full-task +urn: urn:agents:task:full-task name: Full Task description: A complete task agent: cursor @@ -175,7 +185,7 @@ selectors: `, want: TaskFrontMatter{ BaseFrontMatter: BaseFrontMatter{ - URN: "urn:agents:task:full-task", + URN: mustParseURN("urn:agents:task:full-task"), Name: "Full Task", Description: "A complete task", Content: map[string]any{"task_name": "full-task"}, @@ -209,7 +219,10 @@ selectors: if gotTaskName != wantTaskName { t.Errorf("TaskName = %q, want %q", gotTaskName, wantTaskName) } - if got.URN != tt.want.URN { + // Compare URNs + if (got.URN == nil) != (tt.want.URN == nil) { + t.Errorf("URN = %v, want %v", got.URN, tt.want.URN) + } else if got.URN != nil && tt.want.URN != nil && !got.URN.Equal(tt.want.URN) { t.Errorf("URN = %q, want %q", got.URN, tt.want.URN) } if got.Name != tt.want.Name { diff --git a/pkg/codingcontext/result.go b/pkg/codingcontext/result.go index 09e465e6..8f295bdc 100644 --- a/pkg/codingcontext/result.go +++ b/pkg/codingcontext/result.go @@ -31,7 +31,11 @@ func (r *Result) MCPServers() map[string]mcp.MCPServerConfig { server := rule.FrontMatter.MCPServer // Skip empty MCP server configs (no command and no URL means empty) if server.Command != "" || server.URL != "" { - servers[rule.FrontMatter.URN] = server + key := "" + if rule.FrontMatter.URN != nil { + key = rule.FrontMatter.URN.String() + } + servers[key] = server } } diff --git a/pkg/codingcontext/result_test.go b/pkg/codingcontext/result_test.go index 25b5c0e5..fd81a390 100644 --- a/pkg/codingcontext/result_test.go +++ b/pkg/codingcontext/result_test.go @@ -8,8 +8,18 @@ import ( "github.com/kitproj/coding-context-cli/pkg/codingcontext/markdown" "github.com/kitproj/coding-context-cli/pkg/codingcontext/mcp" + "github.com/kitproj/coding-context-cli/pkg/codingcontext/urn" ) +// mustParseURNResult is a test helper that parses a URN string and panics on error +func mustParseURNResult(s string) *urn.URN { + u, ok := urn.Parse([]byte(s)) + if !ok { + panic("failed to parse URN: " + s) + } + return u +} + func TestResult_Prompt(t *testing.T) { tests := []struct { name string @@ -96,13 +106,13 @@ func TestResult_MCPServers(t *testing.T) { Rules: []markdown.Markdown[markdown.RuleFrontMatter]{ { FrontMatter: markdown.RuleFrontMatter{ - BaseFrontMatter: markdown.BaseFrontMatter{URN: "urn:agents:rule:jira-server"}, + BaseFrontMatter: markdown.BaseFrontMatter{URN: mustParseURNResult("urn:agents:rule:jira-server")}, MCPServer: mcp.MCPServerConfig{Type: mcp.TransportTypeStdio, Command: "jira"}, }, }, { FrontMatter: markdown.RuleFrontMatter{ - BaseFrontMatter: markdown.BaseFrontMatter{URN: "urn:agents:rule:api-server"}, + BaseFrontMatter: markdown.BaseFrontMatter{URN: mustParseURNResult("urn:agents:rule:api-server")}, MCPServer: mcp.MCPServerConfig{Type: mcp.TransportTypeHTTP, URL: "https://api.example.com"}, }, }, @@ -123,19 +133,19 @@ func TestResult_MCPServers(t *testing.T) { Rules: []markdown.Markdown[markdown.RuleFrontMatter]{ { FrontMatter: markdown.RuleFrontMatter{ - BaseFrontMatter: markdown.BaseFrontMatter{URN: "urn:agents:rule:server1"}, + BaseFrontMatter: markdown.BaseFrontMatter{URN: mustParseURNResult("urn:agents:rule:server1")}, MCPServer: mcp.MCPServerConfig{Type: mcp.TransportTypeStdio, Command: "server1"}, }, }, { FrontMatter: markdown.RuleFrontMatter{ - BaseFrontMatter: markdown.BaseFrontMatter{URN: "urn:agents:rule:server2"}, + BaseFrontMatter: markdown.BaseFrontMatter{URN: mustParseURNResult("urn:agents:rule:server2")}, MCPServer: mcp.MCPServerConfig{Type: mcp.TransportTypeStdio, Command: "server2"}, }, }, { FrontMatter: markdown.RuleFrontMatter{ - BaseFrontMatter: markdown.BaseFrontMatter{URN: "urn:agents:rule:empty"}, + BaseFrontMatter: markdown.BaseFrontMatter{URN: mustParseURNResult("urn:agents:rule:empty")}, }, }, }, @@ -156,7 +166,7 @@ func TestResult_MCPServers(t *testing.T) { Rules: []markdown.Markdown[markdown.RuleFrontMatter]{ { FrontMatter: markdown.RuleFrontMatter{ - BaseFrontMatter: markdown.BaseFrontMatter{URN: "urn:agents:rule:no-server"}, + BaseFrontMatter: markdown.BaseFrontMatter{URN: mustParseURNResult("urn:agents:rule:no-server")}, }, }, }, @@ -174,19 +184,19 @@ func TestResult_MCPServers(t *testing.T) { Rules: []markdown.Markdown[markdown.RuleFrontMatter]{ { FrontMatter: markdown.RuleFrontMatter{ - BaseFrontMatter: markdown.BaseFrontMatter{URN: "urn:agents:rule:explicit"}, + BaseFrontMatter: markdown.BaseFrontMatter{URN: mustParseURNResult("urn:agents:rule:explicit")}, MCPServer: mcp.MCPServerConfig{Type: mcp.TransportTypeStdio, Command: "server1"}, }, }, { FrontMatter: markdown.RuleFrontMatter{ - BaseFrontMatter: markdown.BaseFrontMatter{URN: "urn:agents:rule:some-rule"}, + BaseFrontMatter: markdown.BaseFrontMatter{URN: mustParseURNResult("urn:agents:rule:some-rule")}, MCPServer: mcp.MCPServerConfig{Type: mcp.TransportTypeStdio, Command: "server2"}, }, }, { FrontMatter: markdown.RuleFrontMatter{ - BaseFrontMatter: markdown.BaseFrontMatter{URN: "urn:agents:rule:another"}, + BaseFrontMatter: markdown.BaseFrontMatter{URN: mustParseURNResult("urn:agents:rule:another")}, MCPServer: mcp.MCPServerConfig{Type: mcp.TransportTypeHTTP, URL: "https://example.com"}, }, }, diff --git a/pkg/codingcontext/urn/urn.go b/pkg/codingcontext/urn/urn.go new file mode 100644 index 00000000..72c62037 --- /dev/null +++ b/pkg/codingcontext/urn/urn.go @@ -0,0 +1,91 @@ +package urn + +import ( +"encoding/json" + +exturn "github.com/leodido/go-urn" +) + +// URN is a wrapper around github.com/leodido/go-urn.URN that adds YAML marshaling support +type URN struct { +*exturn.URN +} + +// Parse wraps the external URN Parse function +func Parse(u []byte, options ...exturn.Option) (*URN, bool) { +urn, ok := exturn.Parse(u, options...) +if !ok { +return nil, false +} +return &URN{URN: urn}, true +} + +// MarshalYAML implements yaml.Marshaler +func (u *URN) MarshalYAML() (interface{}, error) { +if u == nil || u.URN == nil { +return nil, nil +} +return u.URN.String(), nil +} + +// UnmarshalYAML implements yaml.Unmarshaler +func (u *URN) UnmarshalYAML(unmarshal func(interface{}) error) error { +var s string +if err := unmarshal(&s); err != nil { +return err +} + +parsed, ok := exturn.Parse([]byte(s)) +if !ok { +return &json.UnmarshalTypeError{ +Value: "string " + s, +Type: nil, +} +} + +u.URN = parsed +return nil +} + +// MarshalJSON implements json.Marshaler +func (u *URN) MarshalJSON() ([]byte, error) { +if u == nil || u.URN == nil { +return []byte("null"), nil +} +return u.URN.MarshalJSON() +} + +// UnmarshalJSON implements json.Unmarshaler +func (u *URN) UnmarshalJSON(data []byte) error { +var urn exturn.URN +if err := urn.UnmarshalJSON(data); err != nil { +return err +} +u.URN = &urn +return nil +} + +// String returns the URN string representation +func (u *URN) String() string { +if u == nil || u.URN == nil { +return "" +} +return u.URN.String() +} + +// Equal compares two URNs for equality +func (u *URN) Equal(other *URN) bool { +if u == nil && other == nil { +return true +} +if u == nil || other == nil { +return false +} +if u.URN == nil && other.URN == nil { +return true +} +if u.URN == nil || other.URN == nil { +return false +} +return u.URN.Equal(other.URN) +} From 3dbe2633601c9660a724f63b189184033cd85401 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 16 Feb 2026 01:43:16 +0000 Subject: [PATCH 3/4] fix: update context_test.go for URN type changes - Updated URN comparisons to use .String() method - Changed YAML field from "id" to "urn" in test data - Fixed compilation errors in context_test.go Co-authored-by: alexec <1142830+alexec@users.noreply.github.com> --- pkg/codingcontext/context_test.go | 15 ++++++++------- .../markdown/frontmatter_command_test.go | 4 ++-- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/pkg/codingcontext/context_test.go b/pkg/codingcontext/context_test.go index 5668456e..e83060a1 100644 --- a/pkg/codingcontext/context_test.go +++ b/pkg/codingcontext/context_test.go @@ -387,13 +387,14 @@ func TestContext_Run_Basic(t *testing.T) { { name: "task with explicit URN in frontmatter", setup: func(t *testing.T, dir string) { - createTask(t, dir, "file-name", "id: urn:agents:task:file-name", "Task content") + createTask(t, dir, "file-name", "urn: urn:agents:task:file-name", "Task content") }, taskName: "file-name", wantErr: false, check: func(t *testing.T, result *Result) { - if result.Task.FrontMatter.URN != "urn:agents:task:file-name" { - t.Errorf("expected task URN 'urn:agents:task:file-name', got %q", result.Task.FrontMatter.URN) + expectedURN := "urn:agents:task:file-name" + if result.Task.FrontMatter.URN == nil || result.Task.FrontMatter.URN.String() != expectedURN { + t.Errorf("expected task URN %q, got %q", expectedURN, result.Task.FrontMatter.URN) } }, }, @@ -762,8 +763,8 @@ func TestContext_Run_Rules(t *testing.T) { name: "rule URN set from frontmatter", setup: func(t *testing.T, dir string) { createTask(t, dir, "id-task", "", "Task") - createRule(t, dir, ".agents/rules/my-rule.md", "id: urn:agents:rule:my-rule", "Rule with URN") - createRule(t, dir, ".agents/rules/another-rule.md", "id: urn:agents:rule:another", "Rule with another URN") + createRule(t, dir, ".agents/rules/my-rule.md", "urn: urn:agents:rule:my-rule", "Rule with URN") + createRule(t, dir, ".agents/rules/another-rule.md", "urn: urn:agents:rule:another", "Rule with another URN") }, taskName: "id-task", wantErr: false, @@ -775,13 +776,13 @@ func TestContext_Run_Rules(t *testing.T) { foundMyRule := false foundAnotherRule := false for _, rule := range result.Rules { - if rule.FrontMatter.URN == "urn:agents:rule:my-rule" { + if rule.FrontMatter.URN != nil && rule.FrontMatter.URN.String() == "urn:agents:rule:my-rule" { foundMyRule = true if !strings.Contains(rule.Content, "Rule with URN") { t.Error("my-rule should contain 'Rule with URN'") } } - if rule.FrontMatter.URN == "urn:agents:rule:another" { + if rule.FrontMatter.URN != nil && rule.FrontMatter.URN.String() == "urn:agents:rule:another" { foundAnotherRule = true if !strings.Contains(rule.Content, "Rule with another URN") { t.Error("another should contain 'Rule with another URN'") diff --git a/pkg/codingcontext/markdown/frontmatter_command_test.go b/pkg/codingcontext/markdown/frontmatter_command_test.go index 258d1803..757e8cfd 100644 --- a/pkg/codingcontext/markdown/frontmatter_command_test.go +++ b/pkg/codingcontext/markdown/frontmatter_command_test.go @@ -77,8 +77,8 @@ expand: false name: Selector Command description: Command with selectors selectors: - database: postgres - feature: auth + database: postgres + feature: auth `, }, } From b0b1ae6e6a12c77d0faec39b3e21281b8f16f6ce Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 16 Feb 2026 01:46:15 +0000 Subject: [PATCH 4/4] fix: address code review feedback - Improved error message in URN UnmarshalYAML - Fixed nil URN handling in MCPServers to skip entries instead of using empty key - Removed unused import Co-authored-by: alexec <1142830+alexec@users.noreply.github.com> --- go.mod | 2 +- pkg/codingcontext/result.go | 7 ++++--- pkg/codingcontext/urn/urn.go | 7 ++----- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index 8d2694cc..86c9608f 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.24.5 require ( github.com/alecthomas/participle/v2 v2.1.4 github.com/hashicorp/go-getter/v2 v2.2.3 + github.com/leodido/go-urn v1.4.0 github.com/stretchr/testify v1.10.0 gopkg.in/yaml.v3 v3.0.1 ) @@ -18,7 +19,6 @@ require ( github.com/hashicorp/go-safetemp v1.0.0 // indirect github.com/hashicorp/go-version v1.1.0 // indirect github.com/klauspost/compress v1.15.0 // indirect - github.com/leodido/go-urn v1.4.0 // indirect github.com/mitchellh/go-homedir v1.0.0 // indirect github.com/mitchellh/go-testing-interface v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect diff --git a/pkg/codingcontext/result.go b/pkg/codingcontext/result.go index 8f295bdc..b3e3183d 100644 --- a/pkg/codingcontext/result.go +++ b/pkg/codingcontext/result.go @@ -31,10 +31,11 @@ func (r *Result) MCPServers() map[string]mcp.MCPServerConfig { server := rule.FrontMatter.MCPServer // Skip empty MCP server configs (no command and no URL means empty) if server.Command != "" || server.URL != "" { - key := "" - if rule.FrontMatter.URN != nil { - key = rule.FrontMatter.URN.String() + // Skip rules without URN + if rule.FrontMatter.URN == nil { + continue } + key := rule.FrontMatter.URN.String() servers[key] = server } } diff --git a/pkg/codingcontext/urn/urn.go b/pkg/codingcontext/urn/urn.go index 72c62037..aa27416a 100644 --- a/pkg/codingcontext/urn/urn.go +++ b/pkg/codingcontext/urn/urn.go @@ -1,7 +1,7 @@ package urn import ( -"encoding/json" +"fmt" exturn "github.com/leodido/go-urn" ) @@ -37,10 +37,7 @@ return err parsed, ok := exturn.Parse([]byte(s)) if !ok { -return &json.UnmarshalTypeError{ -Value: "string " + s, -Type: nil, -} +return fmt.Errorf("invalid URN format: %q", s) } u.URN = parsed