From 93c6ba5257bb3f5d2843a6927a4a0b1772d7a37e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 25 Dec 2025 18:29:13 +0000 Subject: [PATCH 1/7] Initial plan From 23164163903764140283737350a5e3f6c97d4b15 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 25 Dec 2025 18:33:27 +0000 Subject: [PATCH 2/7] Add AgentPaths type and configuration map Co-authored-by: alexec <1142830+alexec@users.noreply.github.com> --- pkg/codingcontext/agent_paths.go | 64 +++++++++++++ pkg/codingcontext/agent_paths_test.go | 126 ++++++++++++++++++++++++++ 2 files changed, 190 insertions(+) create mode 100644 pkg/codingcontext/agent_paths.go create mode 100644 pkg/codingcontext/agent_paths_test.go diff --git a/pkg/codingcontext/agent_paths.go b/pkg/codingcontext/agent_paths.go new file mode 100644 index 0000000..a15d418 --- /dev/null +++ b/pkg/codingcontext/agent_paths.go @@ -0,0 +1,64 @@ +package codingcontext + +// AgentPaths describes the search paths for a specific agent +type AgentPaths struct { + RulesPaths []string // Paths to search for rule files + SkillsPath string // Path to search for skill directories + CommandsPath string // Path to search for command files + TasksPath string // Path to search for task files +} + +// AgentsPaths maps each agent to its specific search paths. +// Empty string agent ("") represents the generic .agents directory structure. +// If a path is empty, it is not defined for that agent. +var AgentsPaths = map[Agent]AgentPaths{ + // Generic .agents directory structure (empty agent name) + Agent(""): { + RulesPaths: []string{".agents/rules"}, + SkillsPath: ".agents/skills", + CommandsPath: ".agents/commands", + TasksPath: ".agents/tasks", + }, + // Cursor agent paths + AgentCursor: { + RulesPaths: []string{".cursor/rules", ".cursorrules"}, + CommandsPath: ".cursor/commands", + // No skills or tasks paths defined for Cursor + }, + // OpenCode agent paths + AgentOpenCode: { + RulesPaths: []string{".opencode/agent", ".opencode/rules"}, + CommandsPath: ".opencode/command", + // No skills or tasks paths defined for OpenCode + }, + // Copilot agent paths + AgentCopilot: { + RulesPaths: []string{".github/copilot-instructions.md", ".github/agents"}, + // No skills, commands, or tasks paths defined for Copilot + }, + // Claude agent paths + AgentClaude: { + RulesPaths: []string{".claude", "CLAUDE.md", "CLAUDE.local.md"}, + // No skills, commands, or tasks paths defined for Claude + }, + // Gemini agent paths + AgentGemini: { + RulesPaths: []string{".gemini/styleguide.md", ".gemini", "GEMINI.md"}, + // No skills, commands, or tasks paths defined for Gemini + }, + // Augment agent paths + AgentAugment: { + RulesPaths: []string{".augment/rules", ".augment/guidelines.md"}, + // No skills, commands, or tasks paths defined for Augment + }, + // Windsurf agent paths + AgentWindsurf: { + RulesPaths: []string{".windsurf/rules", ".windsurfrules"}, + // No skills, commands, or tasks paths defined for Windsurf + }, + // Codex agent paths + AgentCodex: { + RulesPaths: []string{".codex", "AGENTS.md"}, + // No skills, commands, or tasks paths defined for Codex + }, +} diff --git a/pkg/codingcontext/agent_paths_test.go b/pkg/codingcontext/agent_paths_test.go new file mode 100644 index 0000000..3198795 --- /dev/null +++ b/pkg/codingcontext/agent_paths_test.go @@ -0,0 +1,126 @@ +package codingcontext + +import ( + "testing" +) + +func TestAgentPaths_Structure(t *testing.T) { + tests := []struct { + name string + agent Agent + }{ + { + name: "empty agent (generic .agents)", + agent: Agent(""), + }, + { + name: "cursor agent", + agent: AgentCursor, + }, + { + name: "opencode agent", + agent: AgentOpenCode, + }, + { + name: "copilot agent", + agent: AgentCopilot, + }, + { + name: "claude agent", + agent: AgentClaude, + }, + { + name: "gemini agent", + agent: AgentGemini, + }, + { + name: "augment agent", + agent: AgentAugment, + }, + { + name: "windsurf agent", + agent: AgentWindsurf, + }, + { + name: "codex agent", + agent: AgentCodex, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + paths, exists := AgentsPaths[tt.agent] + if !exists { + t.Errorf("AgentsPaths[%q] does not exist", tt.agent) + return + } + + // Check that at least one path is defined + hasAnyPath := len(paths.RulesPaths) > 0 || + paths.SkillsPath != "" || + paths.CommandsPath != "" || + paths.TasksPath != "" + + if !hasAnyPath { + t.Errorf("AgentsPaths[%q] has no paths defined", tt.agent) + } + }) + } +} + +func TestAgentPaths_EmptyAgentHasAllPaths(t *testing.T) { + paths, exists := AgentsPaths[Agent("")] + if !exists { + t.Fatal("Empty agent should be defined in AgentsPaths") + } + + if len(paths.RulesPaths) == 0 { + t.Error("Empty agent should have RulesPaths defined") + } + if paths.SkillsPath == "" { + t.Error("Empty agent should have SkillsPath defined") + } + if paths.CommandsPath == "" { + t.Error("Empty agent should have CommandsPath defined") + } + if paths.TasksPath == "" { + t.Error("Empty agent should have TasksPath defined") + } +} + +func TestAgentPaths_RulesPathsNotEmpty(t *testing.T) { + // Every agent should have at least one rules path + for agent, paths := range AgentsPaths { + if len(paths.RulesPaths) == 0 { + t.Errorf("Agent %q should have at least one RulesPaths entry", agent) + } + } +} + +func TestAgentPaths_NoAbsolutePaths(t *testing.T) { + // All paths should be relative (not absolute) + for agent, paths := range AgentsPaths { + for _, rulePath := range paths.RulesPaths { + if len(rulePath) > 0 && rulePath[0] == '/' { + t.Errorf("Agent %q RulesPaths contains absolute path: %q", agent, rulePath) + } + } + if len(paths.SkillsPath) > 0 && paths.SkillsPath[0] == '/' { + t.Errorf("Agent %q SkillsPath is absolute: %q", agent, paths.SkillsPath) + } + if len(paths.CommandsPath) > 0 && paths.CommandsPath[0] == '/' { + t.Errorf("Agent %q CommandsPath is absolute: %q", agent, paths.CommandsPath) + } + if len(paths.TasksPath) > 0 && paths.TasksPath[0] == '/' { + t.Errorf("Agent %q TasksPath is absolute: %q", agent, paths.TasksPath) + } + } +} + +func TestAgentPaths_Count(t *testing.T) { + // Should have 9 entries: 1 empty agent + 8 named agents + expectedCount := 9 + if len(AgentsPaths) != expectedCount { + t.Errorf("AgentsPaths should have %d entries, got %d", expectedCount, len(AgentsPaths)) + } +} From 951d954fd4b7662ba879d206b19dc4028422e968 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 25 Dec 2025 18:43:25 +0000 Subject: [PATCH 3/7] Refactor AgentsPaths to use method-based API with private map Co-authored-by: alexec <1142830+alexec@users.noreply.github.com> --- pkg/codingcontext/agent_paths.go | 50 ++++++++++++-- pkg/codingcontext/agent_paths_test.go | 97 ++++++++++++++++++--------- 2 files changed, 113 insertions(+), 34 deletions(-) diff --git a/pkg/codingcontext/agent_paths.go b/pkg/codingcontext/agent_paths.go index a15d418..30f9845 100644 --- a/pkg/codingcontext/agent_paths.go +++ b/pkg/codingcontext/agent_paths.go @@ -1,17 +1,59 @@ package codingcontext -// AgentPaths describes the search paths for a specific agent -type AgentPaths struct { +// agentPathsConfig describes the search paths for a specific agent +type agentPathsConfig struct { RulesPaths []string // Paths to search for rule files SkillsPath string // Path to search for skill directories CommandsPath string // Path to search for command files TasksPath string // Path to search for task files } -// AgentsPaths maps each agent to its specific search paths. +// AgentsPaths provides access to agent-specific search paths +type AgentsPaths struct { + agent Agent +} + +// RulesPaths returns the rules paths for the agent +func (ap AgentsPaths) RulesPaths() []string { + if paths, exists := agentsPaths[ap.agent]; exists { + return paths.RulesPaths + } + return nil +} + +// SkillsPath returns the skills path for the agent +func (ap AgentsPaths) SkillsPath() string { + if paths, exists := agentsPaths[ap.agent]; exists { + return paths.SkillsPath + } + return "" +} + +// CommandsPath returns the commands path for the agent +func (ap AgentsPaths) CommandsPath() string { + if paths, exists := agentsPaths[ap.agent]; exists { + return paths.CommandsPath + } + return "" +} + +// TasksPath returns the tasks path for the agent +func (ap AgentsPaths) TasksPath() string { + if paths, exists := agentsPaths[ap.agent]; exists { + return paths.TasksPath + } + return "" +} + +// Paths returns an AgentsPaths instance for accessing the agent's paths +func (a Agent) Paths() AgentsPaths { + return AgentsPaths{agent: a} +} + +// agentsPaths maps each agent to its specific search paths. // Empty string agent ("") represents the generic .agents directory structure. // If a path is empty, it is not defined for that agent. -var AgentsPaths = map[Agent]AgentPaths{ +var agentsPaths = map[Agent]agentPathsConfig{ // Generic .agents directory structure (empty agent name) Agent(""): { RulesPaths: []string{".agents/rules"}, diff --git a/pkg/codingcontext/agent_paths_test.go b/pkg/codingcontext/agent_paths_test.go index 3198795..20f6b38 100644 --- a/pkg/codingcontext/agent_paths_test.go +++ b/pkg/codingcontext/agent_paths_test.go @@ -49,49 +49,43 @@ func TestAgentPaths_Structure(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - paths, exists := AgentsPaths[tt.agent] - if !exists { - t.Errorf("AgentsPaths[%q] does not exist", tt.agent) - return - } + paths := tt.agent.Paths() // Check that at least one path is defined - hasAnyPath := len(paths.RulesPaths) > 0 || - paths.SkillsPath != "" || - paths.CommandsPath != "" || - paths.TasksPath != "" + hasAnyPath := len(paths.RulesPaths()) > 0 || + paths.SkillsPath() != "" || + paths.CommandsPath() != "" || + paths.TasksPath() != "" if !hasAnyPath { - t.Errorf("AgentsPaths[%q] has no paths defined", tt.agent) + t.Errorf("Agent %q has no paths defined", tt.agent) } }) } } func TestAgentPaths_EmptyAgentHasAllPaths(t *testing.T) { - paths, exists := AgentsPaths[Agent("")] - if !exists { - t.Fatal("Empty agent should be defined in AgentsPaths") - } + paths := Agent("").Paths() - if len(paths.RulesPaths) == 0 { + if len(paths.RulesPaths()) == 0 { t.Error("Empty agent should have RulesPaths defined") } - if paths.SkillsPath == "" { + if paths.SkillsPath() == "" { t.Error("Empty agent should have SkillsPath defined") } - if paths.CommandsPath == "" { + if paths.CommandsPath() == "" { t.Error("Empty agent should have CommandsPath defined") } - if paths.TasksPath == "" { + if paths.TasksPath() == "" { t.Error("Empty agent should have TasksPath defined") } } func TestAgentPaths_RulesPathsNotEmpty(t *testing.T) { // Every agent should have at least one rules path - for agent, paths := range AgentsPaths { - if len(paths.RulesPaths) == 0 { + for agent := range agentsPaths { + paths := agent.Paths() + if len(paths.RulesPaths()) == 0 { t.Errorf("Agent %q should have at least one RulesPaths entry", agent) } } @@ -99,20 +93,21 @@ func TestAgentPaths_RulesPathsNotEmpty(t *testing.T) { func TestAgentPaths_NoAbsolutePaths(t *testing.T) { // All paths should be relative (not absolute) - for agent, paths := range AgentsPaths { - for _, rulePath := range paths.RulesPaths { + for agent := range agentsPaths { + paths := agent.Paths() + for _, rulePath := range paths.RulesPaths() { if len(rulePath) > 0 && rulePath[0] == '/' { t.Errorf("Agent %q RulesPaths contains absolute path: %q", agent, rulePath) } } - if len(paths.SkillsPath) > 0 && paths.SkillsPath[0] == '/' { - t.Errorf("Agent %q SkillsPath is absolute: %q", agent, paths.SkillsPath) + if len(paths.SkillsPath()) > 0 && paths.SkillsPath()[0] == '/' { + t.Errorf("Agent %q SkillsPath is absolute: %q", agent, paths.SkillsPath()) } - if len(paths.CommandsPath) > 0 && paths.CommandsPath[0] == '/' { - t.Errorf("Agent %q CommandsPath is absolute: %q", agent, paths.CommandsPath) + if len(paths.CommandsPath()) > 0 && paths.CommandsPath()[0] == '/' { + t.Errorf("Agent %q CommandsPath is absolute: %q", agent, paths.CommandsPath()) } - if len(paths.TasksPath) > 0 && paths.TasksPath[0] == '/' { - t.Errorf("Agent %q TasksPath is absolute: %q", agent, paths.TasksPath) + if len(paths.TasksPath()) > 0 && paths.TasksPath()[0] == '/' { + t.Errorf("Agent %q TasksPath is absolute: %q", agent, paths.TasksPath()) } } } @@ -120,7 +115,49 @@ func TestAgentPaths_NoAbsolutePaths(t *testing.T) { func TestAgentPaths_Count(t *testing.T) { // Should have 9 entries: 1 empty agent + 8 named agents expectedCount := 9 - if len(AgentsPaths) != expectedCount { - t.Errorf("AgentsPaths should have %d entries, got %d", expectedCount, len(AgentsPaths)) + if len(agentsPaths) != expectedCount { + t.Errorf("agentsPaths should have %d entries, got %d", expectedCount, len(agentsPaths)) + } +} + +func TestAgent_Paths(t *testing.T) { + tests := []struct { + name string + agent Agent + wantRulesPaths []string + wantSkillsPath string + }{ + { + name: "cursor agent", + agent: AgentCursor, + wantRulesPaths: []string{".cursor/rules", ".cursorrules"}, + wantSkillsPath: "", + }, + { + name: "empty agent", + agent: Agent(""), + wantRulesPaths: []string{".agents/rules"}, + wantSkillsPath: ".agents/skills", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + paths := tt.agent.Paths() + + gotRulesPaths := paths.RulesPaths() + if len(gotRulesPaths) != len(tt.wantRulesPaths) { + t.Errorf("RulesPaths() length = %d, want %d", len(gotRulesPaths), len(tt.wantRulesPaths)) + } + for i, want := range tt.wantRulesPaths { + if i < len(gotRulesPaths) && gotRulesPaths[i] != want { + t.Errorf("RulesPaths()[%d] = %q, want %q", i, gotRulesPaths[i], want) + } + } + + if got := paths.SkillsPath(); got != tt.wantSkillsPath { + t.Errorf("SkillsPath() = %q, want %q", got, tt.wantSkillsPath) + } + }) } } From 64500a59d0fc4a6c81e4686b36e7a5921e41cef1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 25 Dec 2025 18:59:34 +0000 Subject: [PATCH 4/7] Refactor path functions to use AgentsPaths configuration Co-authored-by: alexec <1142830+alexec@users.noreply.github.com> --- pkg/codingcontext/paths.go | 96 +++++++++++------- pkg/codingcontext/paths_test.go | 166 ++++++++++++++++++++++++++++++++ 2 files changed, 225 insertions(+), 37 deletions(-) create mode 100644 pkg/codingcontext/paths_test.go diff --git a/pkg/codingcontext/paths.go b/pkg/codingcontext/paths.go index 5488ab5..3522e6c 100644 --- a/pkg/codingcontext/paths.go +++ b/pkg/codingcontext/paths.go @@ -2,56 +2,78 @@ package codingcontext import "path/filepath" -// DownloadedRulePaths returns the search paths for rule files in downloaded directories +// rulePaths returns the search paths for rule files in a directory. +// It collects rule paths from all agents in the agentsPaths configuration. +// If home is true, only returns paths for agents that are user-level (home directory). func rulePaths(dir string, home bool) []string { - if home { - return []string{ - // user - filepath.Join(dir, ".agents", "rules"), - filepath.Join(dir, ".claude", "CLAUDE.md"), - filepath.Join(dir, ".codex", "AGENTS.md"), - filepath.Join(dir, ".gemini", "GEMINI.md"), - filepath.Join(dir, ".opencode", "rules"), - } + var paths []string + + // Define which agents should be included for home directory + homeAgents := map[Agent]bool{ + Agent(""): true, // generic .agents + AgentClaude: true, + AgentCodex: true, + AgentGemini: true, + AgentOpenCode: true, } - return []string{ - filepath.Join(dir, ".agents", "rules"), - filepath.Join(dir, ".cursor", "rules"), - filepath.Join(dir, ".augment", "rules"), - filepath.Join(dir, ".windsurf", "rules"), - filepath.Join(dir, ".opencode", "agent"), - filepath.Join(dir, ".github", "copilot-instructions.md"), - filepath.Join(dir, ".gemini", "styleguide.md"), - filepath.Join(dir, ".github", "agents"), - filepath.Join(dir, ".augment", "guidelines.md"), - filepath.Join(dir, "AGENTS.md"), - filepath.Join(dir, "CLAUDE.md"), - filepath.Join(dir, "CLAUDE.local.md"), - filepath.Join(dir, "GEMINI.md"), - filepath.Join(dir, ".cursorrules"), - filepath.Join(dir, ".windsurfrules"), + + // Iterate through all configured agents + for agent, config := range agentsPaths { + // Skip non-home agents if we're in home directory mode + if home && !homeAgents[agent] { + continue + } + + // Add each rule path for this agent + for _, rulePath := range config.RulesPaths { + paths = append(paths, filepath.Join(dir, rulePath)) + } } + + return paths } -// taskSearchPaths returns the search paths for task files in a directory +// taskSearchPaths returns the search paths for task files in a directory. +// It collects task paths from all agents in the agentsPaths configuration. func taskSearchPaths(dir string) []string { - return []string{ - filepath.Join(dir, ".agents", "tasks"), + var paths []string + + // Iterate through all configured agents + for _, config := range agentsPaths { + if config.TasksPath != "" { + paths = append(paths, filepath.Join(dir, config.TasksPath)) + } } + + return paths } -// commandSearchPaths returns the search paths for command files in a directory +// commandSearchPaths returns the search paths for command files in a directory. +// It collects command paths from all agents in the agentsPaths configuration. func commandSearchPaths(dir string) []string { - return []string{ - filepath.Join(dir, ".agents", "commands"), - filepath.Join(dir, ".cursor", "commands"), - filepath.Join(dir, ".opencode", "command"), + var paths []string + + // Iterate through all configured agents + for _, config := range agentsPaths { + if config.CommandsPath != "" { + paths = append(paths, filepath.Join(dir, config.CommandsPath)) + } } + + return paths } -// skillSearchPaths returns the search paths for skill directories in a directory +// skillSearchPaths returns the search paths for skill directories in a directory. +// It collects skill paths from all agents in the agentsPaths configuration. func skillSearchPaths(dir string) []string { - return []string{ - filepath.Join(dir, ".agents", "skills"), + var paths []string + + // Iterate through all configured agents + for _, config := range agentsPaths { + if config.SkillsPath != "" { + paths = append(paths, filepath.Join(dir, config.SkillsPath)) + } } + + return paths } diff --git a/pkg/codingcontext/paths_test.go b/pkg/codingcontext/paths_test.go new file mode 100644 index 0000000..5d9dd67 --- /dev/null +++ b/pkg/codingcontext/paths_test.go @@ -0,0 +1,166 @@ +package codingcontext + +import ( + "path/filepath" + "testing" +) + +func TestRulePaths(t *testing.T) { + tests := []struct { + name string + dir string + home bool + wantContains []string + wantNotContain []string + }{ + { + name: "non-home directory includes all agent paths", + dir: "/project", + home: false, + wantContains: []string{ + filepath.Join("/project", ".agents", "rules"), + filepath.Join("/project", ".cursor", "rules"), + filepath.Join("/project", ".cursorrules"), + filepath.Join("/project", ".claude"), + }, + wantNotContain: []string{}, + }, + { + name: "home directory includes only home agents", + dir: "/home/user", + home: true, + wantContains: []string{ + filepath.Join("/home/user", ".agents", "rules"), + filepath.Join("/home/user", ".claude"), + filepath.Join("/home/user", ".codex"), + }, + wantNotContain: []string{ + filepath.Join("/home/user", ".cursor", "rules"), + filepath.Join("/home/user", ".windsurf", "rules"), + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + paths := rulePaths(tt.dir, tt.home) + + // Check that expected paths are present + for _, want := range tt.wantContains { + found := false + for _, path := range paths { + if path == want { + found = true + break + } + } + if !found { + t.Errorf("Expected path %q not found in rulePaths", want) + } + } + + // Check that unwanted paths are not present + for _, unwant := range tt.wantNotContain { + for _, path := range paths { + if path == unwant { + t.Errorf("Unexpected path %q found in rulePaths", unwant) + } + } + } + }) + } +} + +func TestTaskSearchPaths(t *testing.T) { + dir := "/project" + paths := taskSearchPaths(dir) + + // Should contain at least the .agents/tasks path + expectedPath := filepath.Join(dir, ".agents", "tasks") + found := false + for _, path := range paths { + if path == expectedPath { + found = true + break + } + } + if !found { + t.Errorf("Expected path %q not found in taskSearchPaths", expectedPath) + } +} + +func TestCommandSearchPaths(t *testing.T) { + dir := "/project" + paths := commandSearchPaths(dir) + + // Should contain at least the .agents/commands path + expectedPaths := []string{ + filepath.Join(dir, ".agents", "commands"), + filepath.Join(dir, ".cursor", "commands"), + filepath.Join(dir, ".opencode", "command"), + } + + for _, expected := range expectedPaths { + found := false + for _, path := range paths { + if path == expected { + found = true + break + } + } + if !found { + t.Errorf("Expected path %q not found in commandSearchPaths", expected) + } + } +} + +func TestSkillSearchPaths(t *testing.T) { + dir := "/project" + paths := skillSearchPaths(dir) + + // Should contain at least the .agents/skills path + expectedPath := filepath.Join(dir, ".agents", "skills") + found := false + for _, path := range paths { + if path == expectedPath { + found = true + break + } + } + if !found { + t.Errorf("Expected path %q not found in skillSearchPaths", expectedPath) + } +} + +func TestPathsUseAgentsPaths(t *testing.T) { + // Verify that all path functions are using the agentsPaths configuration + // by checking that they return paths for all configured agents + + dir := "/test" + + // Get paths from functions + rulePaths := rulePaths(dir, false) + taskPaths := taskSearchPaths(dir) + commandPaths := commandSearchPaths(dir) + skillPaths := skillSearchPaths(dir) + + // Verify rulePaths contains paths from multiple agents + if len(rulePaths) < 5 { + t.Errorf("rulePaths should contain paths from multiple agents, got %d paths", len(rulePaths)) + } + + // Verify taskPaths is not empty + if len(taskPaths) == 0 { + t.Error("taskSearchPaths should return at least one path") + } + + // Verify commandPaths is not empty + if len(commandPaths) == 0 { + t.Error("commandSearchPaths should return at least one path") + } + + // Verify skillPaths is not empty + if len(skillPaths) == 0 { + t.Error("skillSearchPaths should return at least one path") + } +} From 8788f5a1ccaf0f5fc5a36857008ff032c4393a3d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 25 Dec 2025 19:31:21 +0000 Subject: [PATCH 5/7] Simplify path functions to use same paths for all directories Co-authored-by: alexec <1142830+alexec@users.noreply.github.com> --- pkg/codingcontext/context.go | 2 +- pkg/codingcontext/paths.go | 19 ++------------- pkg/codingcontext/paths_test.go | 42 +++++++-------------------------- 3 files changed, 11 insertions(+), 52 deletions(-) diff --git a/pkg/codingcontext/context.go b/pkg/codingcontext/context.go index 49d3973..7d0655c 100644 --- a/pkg/codingcontext/context.go +++ b/pkg/codingcontext/context.go @@ -502,7 +502,7 @@ func (cc *Context) findExecuteRuleFiles(ctx context.Context, homeDir string) err return nil } - err := cc.visitMarkdownFiles(func(path string) []string { return rulePaths(path, path == homeDir) }, func(path string) error { + err := cc.visitMarkdownFiles(rulePaths, func(path string) error { var frontmatter markdown.RuleFrontMatter md, err := markdown.ParseMarkdownFile(path, &frontmatter) if err != nil { diff --git a/pkg/codingcontext/paths.go b/pkg/codingcontext/paths.go index 3522e6c..427fda9 100644 --- a/pkg/codingcontext/paths.go +++ b/pkg/codingcontext/paths.go @@ -4,26 +4,11 @@ import "path/filepath" // rulePaths returns the search paths for rule files in a directory. // It collects rule paths from all agents in the agentsPaths configuration. -// If home is true, only returns paths for agents that are user-level (home directory). -func rulePaths(dir string, home bool) []string { +func rulePaths(dir string) []string { var paths []string - // Define which agents should be included for home directory - homeAgents := map[Agent]bool{ - Agent(""): true, // generic .agents - AgentClaude: true, - AgentCodex: true, - AgentGemini: true, - AgentOpenCode: true, - } - // Iterate through all configured agents - for agent, config := range agentsPaths { - // Skip non-home agents if we're in home directory mode - if home && !homeAgents[agent] { - continue - } - + for _, config := range agentsPaths { // Add each rule path for this agent for _, rulePath := range config.RulesPaths { paths = append(paths, filepath.Join(dir, rulePath)) diff --git a/pkg/codingcontext/paths_test.go b/pkg/codingcontext/paths_test.go index 5d9dd67..e87362d 100644 --- a/pkg/codingcontext/paths_test.go +++ b/pkg/codingcontext/paths_test.go @@ -7,43 +7,26 @@ import ( func TestRulePaths(t *testing.T) { tests := []struct { - name string - dir string - home bool - wantContains []string - wantNotContain []string + name string + dir string + wantContains []string }{ { - name: "non-home directory includes all agent paths", + name: "directory includes all agent paths", dir: "/project", - home: false, wantContains: []string{ filepath.Join("/project", ".agents", "rules"), filepath.Join("/project", ".cursor", "rules"), filepath.Join("/project", ".cursorrules"), filepath.Join("/project", ".claude"), - }, - wantNotContain: []string{}, - }, - { - name: "home directory includes only home agents", - dir: "/home/user", - home: true, - wantContains: []string{ - filepath.Join("/home/user", ".agents", "rules"), - filepath.Join("/home/user", ".claude"), - filepath.Join("/home/user", ".codex"), - }, - wantNotContain: []string{ - filepath.Join("/home/user", ".cursor", "rules"), - filepath.Join("/home/user", ".windsurf", "rules"), + filepath.Join("/project", ".codex"), }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - paths := rulePaths(tt.dir, tt.home) + paths := rulePaths(tt.dir) // Check that expected paths are present for _, want := range tt.wantContains { @@ -58,15 +41,6 @@ func TestRulePaths(t *testing.T) { t.Errorf("Expected path %q not found in rulePaths", want) } } - - // Check that unwanted paths are not present - for _, unwant := range tt.wantNotContain { - for _, path := range paths { - if path == unwant { - t.Errorf("Unexpected path %q found in rulePaths", unwant) - } - } - } }) } } @@ -137,9 +111,9 @@ func TestPathsUseAgentsPaths(t *testing.T) { // by checking that they return paths for all configured agents dir := "/test" - + // Get paths from functions - rulePaths := rulePaths(dir, false) + rulePaths := rulePaths(dir) taskPaths := taskSearchPaths(dir) commandPaths := commandSearchPaths(dir) skillPaths := skillSearchPaths(dir) From 638709bc9b5f1b2c64bff32c8cb2764aba047528 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 25 Dec 2025 21:23:56 +0000 Subject: [PATCH 6/7] Simplify AgentsPaths to use map directly without wrapper struct Co-authored-by: alexec <1142830+alexec@users.noreply.github.com> --- pkg/codingcontext/agent_paths.go | 45 +----------------- pkg/codingcontext/agent_paths_test.go | 66 +++++++++++++++------------ 2 files changed, 39 insertions(+), 72 deletions(-) diff --git a/pkg/codingcontext/agent_paths.go b/pkg/codingcontext/agent_paths.go index a5170c5..ed81dcd 100644 --- a/pkg/codingcontext/agent_paths.go +++ b/pkg/codingcontext/agent_paths.go @@ -1,6 +1,7 @@ package codingcontext -// agentPathsConfig describes the search paths for a specific agent +// agentPathsConfig describes the search paths for a specific agent. +// This is the internal configuration structure used by the agentsPaths map. type agentPathsConfig struct { RulesPaths []string // Paths to search for rule files SkillsPath string // Path to search for skill directories @@ -8,48 +9,6 @@ type agentPathsConfig struct { TasksPath string // Path to search for task files } -// AgentsPaths provides access to agent-specific search paths -type AgentsPaths struct { - agent Agent -} - -// RulesPaths returns the rules paths for the agent -func (ap AgentsPaths) RulesPaths() []string { - if paths, exists := agentsPaths[ap.agent]; exists { - return paths.RulesPaths - } - return nil -} - -// SkillsPath returns the skills path for the agent -func (ap AgentsPaths) SkillsPath() string { - if paths, exists := agentsPaths[ap.agent]; exists { - return paths.SkillsPath - } - return "" -} - -// CommandsPath returns the commands path for the agent -func (ap AgentsPaths) CommandsPath() string { - if paths, exists := agentsPaths[ap.agent]; exists { - return paths.CommandsPath - } - return "" -} - -// TasksPath returns the tasks path for the agent -func (ap AgentsPaths) TasksPath() string { - if paths, exists := agentsPaths[ap.agent]; exists { - return paths.TasksPath - } - return "" -} - -// Paths returns an AgentsPaths instance for accessing the agent's paths -func (a Agent) Paths() AgentsPaths { - return AgentsPaths{agent: a} -} - // agentsPaths maps each agent to its specific search paths. // Empty string agent ("") represents the generic .agents directory structure. // If a path is empty, it is not defined for that agent. diff --git a/pkg/codingcontext/agent_paths_test.go b/pkg/codingcontext/agent_paths_test.go index 20f6b38..a3c77ca 100644 --- a/pkg/codingcontext/agent_paths_test.go +++ b/pkg/codingcontext/agent_paths_test.go @@ -49,13 +49,17 @@ func TestAgentPaths_Structure(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - paths := tt.agent.Paths() + paths, exists := agentsPaths[tt.agent] + if !exists { + t.Errorf("Agent %q not found in agentsPaths", tt.agent) + return + } // Check that at least one path is defined - hasAnyPath := len(paths.RulesPaths()) > 0 || - paths.SkillsPath() != "" || - paths.CommandsPath() != "" || - paths.TasksPath() != "" + hasAnyPath := len(paths.RulesPaths) > 0 || + paths.SkillsPath != "" || + paths.CommandsPath != "" || + paths.TasksPath != "" if !hasAnyPath { t.Errorf("Agent %q has no paths defined", tt.agent) @@ -65,27 +69,29 @@ func TestAgentPaths_Structure(t *testing.T) { } func TestAgentPaths_EmptyAgentHasAllPaths(t *testing.T) { - paths := Agent("").Paths() + paths, exists := agentsPaths[Agent("")] + if !exists { + t.Fatal("Empty agent not found in agentsPaths") + } - if len(paths.RulesPaths()) == 0 { + if len(paths.RulesPaths) == 0 { t.Error("Empty agent should have RulesPaths defined") } - if paths.SkillsPath() == "" { + if paths.SkillsPath == "" { t.Error("Empty agent should have SkillsPath defined") } - if paths.CommandsPath() == "" { + if paths.CommandsPath == "" { t.Error("Empty agent should have CommandsPath defined") } - if paths.TasksPath() == "" { + if paths.TasksPath == "" { t.Error("Empty agent should have TasksPath defined") } } func TestAgentPaths_RulesPathsNotEmpty(t *testing.T) { // Every agent should have at least one rules path - for agent := range agentsPaths { - paths := agent.Paths() - if len(paths.RulesPaths()) == 0 { + for agent, paths := range agentsPaths { + if len(paths.RulesPaths) == 0 { t.Errorf("Agent %q should have at least one RulesPaths entry", agent) } } @@ -93,21 +99,20 @@ func TestAgentPaths_RulesPathsNotEmpty(t *testing.T) { func TestAgentPaths_NoAbsolutePaths(t *testing.T) { // All paths should be relative (not absolute) - for agent := range agentsPaths { - paths := agent.Paths() - for _, rulePath := range paths.RulesPaths() { + for agent, paths := range agentsPaths { + for _, rulePath := range paths.RulesPaths { if len(rulePath) > 0 && rulePath[0] == '/' { t.Errorf("Agent %q RulesPaths contains absolute path: %q", agent, rulePath) } } - if len(paths.SkillsPath()) > 0 && paths.SkillsPath()[0] == '/' { - t.Errorf("Agent %q SkillsPath is absolute: %q", agent, paths.SkillsPath()) + if len(paths.SkillsPath) > 0 && paths.SkillsPath[0] == '/' { + t.Errorf("Agent %q SkillsPath is absolute: %q", agent, paths.SkillsPath) } - if len(paths.CommandsPath()) > 0 && paths.CommandsPath()[0] == '/' { - t.Errorf("Agent %q CommandsPath is absolute: %q", agent, paths.CommandsPath()) + if len(paths.CommandsPath) > 0 && paths.CommandsPath[0] == '/' { + t.Errorf("Agent %q CommandsPath is absolute: %q", agent, paths.CommandsPath) } - if len(paths.TasksPath()) > 0 && paths.TasksPath()[0] == '/' { - t.Errorf("Agent %q TasksPath is absolute: %q", agent, paths.TasksPath()) + if len(paths.TasksPath) > 0 && paths.TasksPath[0] == '/' { + t.Errorf("Agent %q TasksPath is absolute: %q", agent, paths.TasksPath) } } } @@ -131,7 +136,7 @@ func TestAgent_Paths(t *testing.T) { name: "cursor agent", agent: AgentCursor, wantRulesPaths: []string{".cursor/rules", ".cursorrules"}, - wantSkillsPath: "", + wantSkillsPath: ".cursor/skills", }, { name: "empty agent", @@ -143,20 +148,23 @@ func TestAgent_Paths(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - paths := tt.agent.Paths() + paths, exists := agentsPaths[tt.agent] + if !exists { + t.Fatalf("Agent %q not found in agentsPaths", tt.agent) + } - gotRulesPaths := paths.RulesPaths() + gotRulesPaths := paths.RulesPaths if len(gotRulesPaths) != len(tt.wantRulesPaths) { - t.Errorf("RulesPaths() length = %d, want %d", len(gotRulesPaths), len(tt.wantRulesPaths)) + t.Errorf("RulesPaths length = %d, want %d", len(gotRulesPaths), len(tt.wantRulesPaths)) } for i, want := range tt.wantRulesPaths { if i < len(gotRulesPaths) && gotRulesPaths[i] != want { - t.Errorf("RulesPaths()[%d] = %q, want %q", i, gotRulesPaths[i], want) + t.Errorf("RulesPaths[%d] = %q, want %q", i, gotRulesPaths[i], want) } } - if got := paths.SkillsPath(); got != tt.wantSkillsPath { - t.Errorf("SkillsPath() = %q, want %q", got, tt.wantSkillsPath) + if got := paths.SkillsPath; got != tt.wantSkillsPath { + t.Errorf("SkillsPath = %q, want %q", got, tt.wantSkillsPath) } }) } From a5943c71c38ab88fff4131dd2c57cff8560f8f5c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 25 Dec 2025 21:41:41 +0000 Subject: [PATCH 7/7] Make agentPathsConfig fields private Co-authored-by: alexec <1142830+alexec@users.noreply.github.com> --- pkg/codingcontext/agent_paths.go | 38 +++++++++---------- pkg/codingcontext/agent_paths_test.go | 54 +++++++++++++-------------- pkg/codingcontext/paths.go | 14 +++---- 3 files changed, 53 insertions(+), 53 deletions(-) diff --git a/pkg/codingcontext/agent_paths.go b/pkg/codingcontext/agent_paths.go index ed81dcd..83e1e3a 100644 --- a/pkg/codingcontext/agent_paths.go +++ b/pkg/codingcontext/agent_paths.go @@ -3,10 +3,10 @@ package codingcontext // agentPathsConfig describes the search paths for a specific agent. // This is the internal configuration structure used by the agentsPaths map. type agentPathsConfig struct { - RulesPaths []string // Paths to search for rule files - SkillsPath string // Path to search for skill directories - CommandsPath string // Path to search for command files - TasksPath string // Path to search for task files + rulesPaths []string // Paths to search for rule files + skillsPath string // Path to search for skill directories + commandsPath string // Path to search for command files + tasksPath string // Path to search for task files } // agentsPaths maps each agent to its specific search paths. @@ -15,52 +15,52 @@ type agentPathsConfig struct { var agentsPaths = map[Agent]agentPathsConfig{ // Generic .agents directory structure (empty agent name) Agent(""): { - RulesPaths: []string{".agents/rules"}, - SkillsPath: ".agents/skills", - CommandsPath: ".agents/commands", - TasksPath: ".agents/tasks", + rulesPaths: []string{".agents/rules"}, + skillsPath: ".agents/skills", + commandsPath: ".agents/commands", + tasksPath: ".agents/tasks", }, // Cursor agent paths AgentCursor: { - RulesPaths: []string{".cursor/rules", ".cursorrules"}, - SkillsPath: ".cursor/skills", - CommandsPath: ".cursor/commands", + rulesPaths: []string{".cursor/rules", ".cursorrules"}, + skillsPath: ".cursor/skills", + commandsPath: ".cursor/commands", // No tasks path defined for Cursor }, // OpenCode agent paths AgentOpenCode: { - RulesPaths: []string{".opencode/agent", ".opencode/rules"}, - CommandsPath: ".opencode/command", + rulesPaths: []string{".opencode/agent", ".opencode/rules"}, + commandsPath: ".opencode/command", // No skills or tasks paths defined for OpenCode }, // Copilot agent paths AgentCopilot: { - RulesPaths: []string{".github/copilot-instructions.md", ".github/agents"}, + rulesPaths: []string{".github/copilot-instructions.md", ".github/agents"}, // No skills, commands, or tasks paths defined for Copilot }, // Claude agent paths AgentClaude: { - RulesPaths: []string{".claude", "CLAUDE.md", "CLAUDE.local.md"}, + rulesPaths: []string{".claude", "CLAUDE.md", "CLAUDE.local.md"}, // No skills, commands, or tasks paths defined for Claude }, // Gemini agent paths AgentGemini: { - RulesPaths: []string{".gemini/styleguide.md", ".gemini", "GEMINI.md"}, + rulesPaths: []string{".gemini/styleguide.md", ".gemini", "GEMINI.md"}, // No skills, commands, or tasks paths defined for Gemini }, // Augment agent paths AgentAugment: { - RulesPaths: []string{".augment/rules", ".augment/guidelines.md"}, + rulesPaths: []string{".augment/rules", ".augment/guidelines.md"}, // No skills, commands, or tasks paths defined for Augment }, // Windsurf agent paths AgentWindsurf: { - RulesPaths: []string{".windsurf/rules", ".windsurfrules"}, + rulesPaths: []string{".windsurf/rules", ".windsurfrules"}, // No skills, commands, or tasks paths defined for Windsurf }, // Codex agent paths AgentCodex: { - RulesPaths: []string{".codex", "AGENTS.md"}, + rulesPaths: []string{".codex", "AGENTS.md"}, // No skills, commands, or tasks paths defined for Codex }, } diff --git a/pkg/codingcontext/agent_paths_test.go b/pkg/codingcontext/agent_paths_test.go index a3c77ca..40793f4 100644 --- a/pkg/codingcontext/agent_paths_test.go +++ b/pkg/codingcontext/agent_paths_test.go @@ -56,10 +56,10 @@ func TestAgentPaths_Structure(t *testing.T) { } // Check that at least one path is defined - hasAnyPath := len(paths.RulesPaths) > 0 || - paths.SkillsPath != "" || - paths.CommandsPath != "" || - paths.TasksPath != "" + hasAnyPath := len(paths.rulesPaths) > 0 || + paths.skillsPath != "" || + paths.commandsPath != "" || + paths.tasksPath != "" if !hasAnyPath { t.Errorf("Agent %q has no paths defined", tt.agent) @@ -74,25 +74,25 @@ func TestAgentPaths_EmptyAgentHasAllPaths(t *testing.T) { t.Fatal("Empty agent not found in agentsPaths") } - if len(paths.RulesPaths) == 0 { - t.Error("Empty agent should have RulesPaths defined") + if len(paths.rulesPaths) == 0 { + t.Error("Empty agent should have rulesPaths defined") } - if paths.SkillsPath == "" { - t.Error("Empty agent should have SkillsPath defined") + if paths.skillsPath == "" { + t.Error("Empty agent should have skillsPath defined") } - if paths.CommandsPath == "" { - t.Error("Empty agent should have CommandsPath defined") + if paths.commandsPath == "" { + t.Error("Empty agent should have commandsPath defined") } - if paths.TasksPath == "" { - t.Error("Empty agent should have TasksPath defined") + if paths.tasksPath == "" { + t.Error("Empty agent should have tasksPath defined") } } func TestAgentPaths_RulesPathsNotEmpty(t *testing.T) { // Every agent should have at least one rules path for agent, paths := range agentsPaths { - if len(paths.RulesPaths) == 0 { - t.Errorf("Agent %q should have at least one RulesPaths entry", agent) + if len(paths.rulesPaths) == 0 { + t.Errorf("Agent %q should have at least one rulesPaths entry", agent) } } } @@ -100,19 +100,19 @@ func TestAgentPaths_RulesPathsNotEmpty(t *testing.T) { func TestAgentPaths_NoAbsolutePaths(t *testing.T) { // All paths should be relative (not absolute) for agent, paths := range agentsPaths { - for _, rulePath := range paths.RulesPaths { + for _, rulePath := range paths.rulesPaths { if len(rulePath) > 0 && rulePath[0] == '/' { - t.Errorf("Agent %q RulesPaths contains absolute path: %q", agent, rulePath) + t.Errorf("Agent %q rulesPaths contains absolute path: %q", agent, rulePath) } } - if len(paths.SkillsPath) > 0 && paths.SkillsPath[0] == '/' { - t.Errorf("Agent %q SkillsPath is absolute: %q", agent, paths.SkillsPath) + if len(paths.skillsPath) > 0 && paths.skillsPath[0] == '/' { + t.Errorf("Agent %q skillsPath is absolute: %q", agent, paths.skillsPath) } - if len(paths.CommandsPath) > 0 && paths.CommandsPath[0] == '/' { - t.Errorf("Agent %q CommandsPath is absolute: %q", agent, paths.CommandsPath) + if len(paths.commandsPath) > 0 && paths.commandsPath[0] == '/' { + t.Errorf("Agent %q commandsPath is absolute: %q", agent, paths.commandsPath) } - if len(paths.TasksPath) > 0 && paths.TasksPath[0] == '/' { - t.Errorf("Agent %q TasksPath is absolute: %q", agent, paths.TasksPath) + if len(paths.tasksPath) > 0 && paths.tasksPath[0] == '/' { + t.Errorf("Agent %q tasksPath is absolute: %q", agent, paths.tasksPath) } } } @@ -153,18 +153,18 @@ func TestAgent_Paths(t *testing.T) { t.Fatalf("Agent %q not found in agentsPaths", tt.agent) } - gotRulesPaths := paths.RulesPaths + gotRulesPaths := paths.rulesPaths if len(gotRulesPaths) != len(tt.wantRulesPaths) { - t.Errorf("RulesPaths length = %d, want %d", len(gotRulesPaths), len(tt.wantRulesPaths)) + t.Errorf("rulesPaths length = %d, want %d", len(gotRulesPaths), len(tt.wantRulesPaths)) } for i, want := range tt.wantRulesPaths { if i < len(gotRulesPaths) && gotRulesPaths[i] != want { - t.Errorf("RulesPaths[%d] = %q, want %q", i, gotRulesPaths[i], want) + t.Errorf("rulesPaths[%d] = %q, want %q", i, gotRulesPaths[i], want) } } - if got := paths.SkillsPath; got != tt.wantSkillsPath { - t.Errorf("SkillsPath = %q, want %q", got, tt.wantSkillsPath) + if got := paths.skillsPath; got != tt.wantSkillsPath { + t.Errorf("skillsPath = %q, want %q", got, tt.wantSkillsPath) } }) } diff --git a/pkg/codingcontext/paths.go b/pkg/codingcontext/paths.go index 427fda9..a015f52 100644 --- a/pkg/codingcontext/paths.go +++ b/pkg/codingcontext/paths.go @@ -10,7 +10,7 @@ func rulePaths(dir string) []string { // Iterate through all configured agents for _, config := range agentsPaths { // Add each rule path for this agent - for _, rulePath := range config.RulesPaths { + for _, rulePath := range config.rulesPaths { paths = append(paths, filepath.Join(dir, rulePath)) } } @@ -25,8 +25,8 @@ func taskSearchPaths(dir string) []string { // Iterate through all configured agents for _, config := range agentsPaths { - if config.TasksPath != "" { - paths = append(paths, filepath.Join(dir, config.TasksPath)) + if config.tasksPath != "" { + paths = append(paths, filepath.Join(dir, config.tasksPath)) } } @@ -40,8 +40,8 @@ func commandSearchPaths(dir string) []string { // Iterate through all configured agents for _, config := range agentsPaths { - if config.CommandsPath != "" { - paths = append(paths, filepath.Join(dir, config.CommandsPath)) + if config.commandsPath != "" { + paths = append(paths, filepath.Join(dir, config.commandsPath)) } } @@ -55,8 +55,8 @@ func skillSearchPaths(dir string) []string { // Iterate through all configured agents for _, config := range agentsPaths { - if config.SkillsPath != "" { - paths = append(paths, filepath.Join(dir, config.SkillsPath)) + if config.skillsPath != "" { + paths = append(paths, filepath.Join(dir, config.skillsPath)) } }