Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
3 changes: 1 addition & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ go 1.24.5

require (
github.com/alecthomas/participle/v2 v2.1.4
github.com/goccy/go-yaml v1.18.0
github.com/hashicorp/go-getter/v2 v2.2.3
github.com/stretchr/testify v1.10.0
gopkg.in/yaml.v3 v3.0.1
)

require (
Expand All @@ -22,5 +22,4 @@ require (
github.com/mitchellh/go-testing-interface v1.0.0 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/ulikunitz/xz v0.5.8 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
2 changes: 0 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@ github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d h1:xDfNPAt8lFiC1U
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ00z/TKoufEY6K/a0k6AhaJrQKdFe6OfVXsa4=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=
github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
Expand Down
23 changes: 0 additions & 23 deletions pkg/codingcontext/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,14 +58,6 @@ func New(opts ...Option) *Context {
return c
}

// generateIDFromPath generates an ID from a file path by extracting the filename without extension.
// Used to auto-set ID fields in frontmatter when not explicitly provided.
func generateIDFromPath(path string) string {
baseName := filepath.Base(path)
ext := filepath.Ext(baseName)
return strings.TrimSuffix(baseName, ext)
}

type markdownVisitor func(path string, fm *markdown.BaseFrontMatter) error

// findMarkdownFile searches for a markdown file by name in the given directories.
Expand Down Expand Up @@ -139,11 +131,6 @@ func (cc *Context) findTask(taskName string) error {
return fmt.Errorf("failed to parse task file %s: %w", path, err)
}

// Automatically set ID to filename (without extension) if not set in frontmatter
if frontMatter.ID == "" {
frontMatter.ID = generateIDFromPath(path)
}

// Extract selector labels from task frontmatter and add them to cc.includes.
// This combines CLI selectors (from -s flag) with task selectors using OR logic:
// rules match if their frontmatter value matches ANY selector value for a given key.
Expand Down Expand Up @@ -244,11 +231,6 @@ func (cc *Context) findCommand(commandName string, params taskparser.Params) (st
return fmt.Errorf("failed to parse command file %s: %w", path, err)
}

// Automatically set ID to filename (without extension) if not set in frontmatter
if frontMatter.ID == "" {
frontMatter.ID = generateIDFromPath(path)
}

// Extract selector labels from command frontmatter and add them to cc.includes.
// This combines CLI selectors, task selectors, and command selectors using OR logic:
// rules match if their frontmatter value matches ANY selector value for a given key.
Expand Down Expand Up @@ -536,11 +518,6 @@ func (cc *Context) findExecuteRuleFiles(ctx context.Context, homeDir string) err
return fmt.Errorf("failed to parse markdown file %s: %w", path, err)
}

// Automatically set ID to filename (without extension) if not set in frontmatter
if frontmatter.ID == "" {
frontmatter.ID = generateIDFromPath(path)
}

// Expand parameters only if expand is not explicitly set to false
var processedContent string
if shouldExpandParams(frontmatter.ExpandParams) {
Expand Down
44 changes: 15 additions & 29 deletions pkg/codingcontext/context_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -385,28 +385,15 @@ func TestContext_Run_Basic(t *testing.T) {
},
},
{
name: "task ID automatically set from filename",
name: "task with explicit URN in frontmatter",
setup: func(t *testing.T, dir string) {
createTask(t, dir, "my-task", "", "Task content")
},
taskName: "my-task",
wantErr: false,
check: func(t *testing.T, result *Result) {
if result.Task.FrontMatter.ID != "my-task" {
t.Errorf("expected task ID 'my-task', got %q", result.Task.FrontMatter.ID)
}
},
},
{
name: "task with explicit ID in frontmatter",
setup: func(t *testing.T, dir string) {
createTask(t, dir, "file-name", "id: explicit-task-id", "Task content")
createTask(t, dir, "file-name", "id: urn:agents:task:file-name", "Task content")
},
taskName: "file-name",
wantErr: false,
check: func(t *testing.T, result *Result) {
if result.Task.FrontMatter.ID != "explicit-task-id" {
t.Errorf("expected task ID 'explicit-task-id', got %q", result.Task.FrontMatter.ID)
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)
}
},
},
Comment thread
alexec marked this conversation as resolved.
Expand Down Expand Up @@ -772,11 +759,11 @@ func TestContext_Run_Rules(t *testing.T) {
},
},
{
name: "rule IDs automatically set from filename",
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", "", "Rule without ID in frontmatter")
createRule(t, dir, ".agents/rules/another-rule.md", "id: explicit-id", "Rule with explicit ID")
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")
},
taskName: "id-task",
wantErr: false,
Expand All @@ -785,29 +772,28 @@ func TestContext_Run_Rules(t *testing.T) {
t.Fatalf("expected 2 rules, got %d", len(result.Rules))
}

// Check that one rule has auto-generated ID from filename
foundMyRule := false
foundAnotherRule := false
for _, rule := range result.Rules {
if rule.FrontMatter.ID == "my-rule" {
if rule.FrontMatter.URN == "urn:agents:rule:my-rule" {
foundMyRule = true
if !strings.Contains(rule.Content, "Rule without ID") {
t.Error("my-rule should contain 'Rule without ID'")
if !strings.Contains(rule.Content, "Rule with URN") {
t.Error("my-rule should contain 'Rule with URN'")
}
}
if rule.FrontMatter.ID == "explicit-id" {
if rule.FrontMatter.URN == "urn:agents:rule:another" {
foundAnotherRule = true
if !strings.Contains(rule.Content, "Rule with explicit ID") {
t.Error("explicit-id should contain 'Rule with explicit ID'")
if !strings.Contains(rule.Content, "Rule with another URN") {
t.Error("another should contain 'Rule with another URN'")
}
}
}

if !foundMyRule {
t.Error("expected to find rule with auto-generated ID 'my-rule'")
t.Error("expected to find rule with URN 'urn:agents:rule:my-rule'")
}
if !foundAnotherRule {
t.Error("expected to find rule with explicit ID 'explicit-id'")
t.Error("expected to find rule with URN 'urn:agents:rule:another'")
}
},
},
Comment thread
alexec marked this conversation as resolved.
Expand Down
45 changes: 10 additions & 35 deletions pkg/codingcontext/markdown/frontmatter.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,24 +9,23 @@ import (

// BaseFrontMatter represents parsed YAML frontmatter from markdown files
type BaseFrontMatter struct {
Content map[string]any `json:"-" yaml:",inline"`
}

// TaskFrontMatter represents the standard frontmatter fields for task files
type TaskFrontMatter struct {
BaseFrontMatter `yaml:",inline"`

// ID is an optional unique identifier for the task
// Metadata only, does not affect task matching or filtering
ID string `yaml:"id,omitempty" json:"id,omitempty"`
// URN is an optional unique identifier for the prompt in URN format (e.g. urn:agents:task:<name>)
// Automatically inferred from filename if not specified in frontmatter
Comment thread
alexec marked this conversation as resolved.
URN string `yaml:"id,omitempty" json:"id,omitempty"`

// Name is an optional human-readable name for the task
// Metadata only, does not affect task matching or filtering
Name string `yaml:"name,omitempty" json:"name,omitempty"`

// Description is an optional description of what the task does
// Metadata only, does not affect task matching or filtering
Description string `yaml:"description,omitempty" json:"description,omitempty"`
Description string `yaml:"description,omitempty" json:"description,omitempty"`
Content map[string]any `json:"-" yaml:",inline"`
Comment thread
alexec marked this conversation as resolved.
Outdated
}

// TaskFrontMatter represents the standard frontmatter fields for task files
type TaskFrontMatter struct {
BaseFrontMatter `yaml:",inline"`

// Agent specifies the default agent if not specified via -a flag
// This is not used for selecting tasks or rules, only as a default
Expand Down Expand Up @@ -87,18 +86,6 @@ func (t *TaskFrontMatter) UnmarshalJSON(data []byte) error {
type CommandFrontMatter struct {
BaseFrontMatter `yaml:",inline"`

// ID is an optional unique identifier for the command
// Metadata only, does not affect command matching or filtering
ID string `yaml:"id,omitempty" json:"id,omitempty"`

// Name is an optional human-readable name for the command
// Metadata only, does not affect command matching or filtering
Name string `yaml:"name,omitempty" json:"name,omitempty"`

// Description is an optional description of what the command does
// Metadata only, does not affect command matching or filtering
Description string `yaml:"description,omitempty" json:"description,omitempty"`

// ExpandParams controls whether parameter expansion should occur
// Defaults to true if not specified
ExpandParams *bool `yaml:"expand,omitempty" json:"expand,omitempty"`
Expand Down Expand Up @@ -134,18 +121,6 @@ func (c *CommandFrontMatter) UnmarshalJSON(data []byte) error {
type RuleFrontMatter struct {
BaseFrontMatter `yaml:",inline"`

// ID is an optional unique identifier for the rule
// Metadata only, does not affect rule matching or filtering
ID string `yaml:"id,omitempty" json:"id,omitempty"`

// Name is an optional human-readable name for the rule
// Metadata only, does not affect rule matching or filtering
Name string `yaml:"name,omitempty" json:"name,omitempty"`

// Description is an optional description of what the rule provides
// Metadata only, does not affect rule matching or filtering
Description string `yaml:"description,omitempty" json:"description,omitempty"`

// TaskNames specifies which task(s) this rule applies to
// Array of task names for OR logic
TaskNames []string `yaml:"task_names,omitempty" json:"task_names,omitempty"`
Expand Down
66 changes: 39 additions & 27 deletions pkg/codingcontext/markdown/frontmatter_command_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package markdown
import (
"testing"

"github.com/goccy/go-yaml"
"gopkg.in/yaml.v3"
)

func TestCommandFrontMatter_Marshal(t *testing.T) {
Expand All @@ -20,27 +20,31 @@ func TestCommandFrontMatter_Marshal(t *testing.T) {
{
name: "command with standard id, name, description",
command: CommandFrontMatter{
ID: "cmd-123",
Name: "Standard Command",
Description: "This is a standard command with metadata",
BaseFrontMatter: BaseFrontMatter{
URN: "urn:agents:command:standard",
Name: "Standard Command",
Description: "This is a standard command with metadata",
},
},
want: `id: cmd-123
want: `id: urn:agents:command:standard
name: Standard Command
description: This is a standard command with metadata
`,
},
{
name: "command with expand false",
command: CommandFrontMatter{
ID: "cmd-456",
Name: "No Expand Command",
Description: "Command with expansion disabled",
BaseFrontMatter: BaseFrontMatter{
URN: "urn:agents:command:no-expand",
Name: "No Expand Command",
Description: "Command with expansion disabled",
},
ExpandParams: func() *bool {
b := false
return &b
}(),
},
want: `id: cmd-456
want: `id: urn:agents:command:no-expand
name: No Expand Command
description: Command with expansion disabled
expand: false
Expand All @@ -49,15 +53,17 @@ expand: false
{
name: "command with selectors",
command: CommandFrontMatter{
ID: "cmd-789",
Name: "Selector Command",
Description: "Command with selectors",
BaseFrontMatter: BaseFrontMatter{
URN: "urn:agents:command:selector",
Name: "Selector Command",
Description: "Command with selectors",
},
Selectors: map[string]any{
"database": "postgres",
"feature": "auth",
},
},
want: `id: cmd-789
want: `id: urn:agents:command:selector
name: Selector Command
description: Command with selectors
selectors:
Expand Down Expand Up @@ -89,27 +95,31 @@ func TestCommandFrontMatter_Unmarshal(t *testing.T) {
}{
{
name: "command with standard id, name, description",
yaml: `id: cmd-abc
yaml: `id: urn:agents:command:named
name: Named Command
description: A command with standard fields
`,
want: CommandFrontMatter{
ID: "cmd-abc",
Name: "Named Command",
Description: "A command with standard fields",
BaseFrontMatter: BaseFrontMatter{
URN: "urn:agents:command:named",
Name: "Named Command",
Description: "A command with standard fields",
},
},
},
{
name: "command with expand false",
yaml: `id: cmd-def
yaml: `id: urn:agents:command:no-expand
name: No Expand
description: No expansion
expand: false
`,
want: CommandFrontMatter{
ID: "cmd-def",
Name: "No Expand",
Description: "No expansion",
BaseFrontMatter: BaseFrontMatter{
URN: "urn:agents:command:no-expand",
Name: "No Expand",
Description: "No expansion",
},
ExpandParams: func() *bool {
b := false
return &b
Expand All @@ -118,17 +128,19 @@ expand: false
},
{
name: "command with selectors",
yaml: `id: cmd-ghi
yaml: `id: urn:agents:command:selector
name: Selector Command
description: Has selectors
selectors:
database: postgres
feature: auth
`,
want: CommandFrontMatter{
ID: "cmd-ghi",
Name: "Selector Command",
Description: "Has selectors",
BaseFrontMatter: BaseFrontMatter{
URN: "urn:agents:command:selector",
Name: "Selector Command",
Description: "Has selectors",
},
Selectors: map[string]any{
"database": "postgres",
"feature": "auth",
Expand All @@ -149,8 +161,8 @@ selectors:
}

// Compare fields individually
if got.ID != tt.want.ID {
t.Errorf("ID = %q, want %q", got.ID, tt.want.ID)
if got.URN != tt.want.URN {
t.Errorf("URN = %q, want %q", got.URN, tt.want.URN)
}
if got.Name != tt.want.Name {
t.Errorf("Name = %q, want %q", got.Name, tt.want.Name)
Expand Down
Loading
Loading