-
Notifications
You must be signed in to change notification settings - Fork 1
Add centralized agent path configuration and simplify path handling #189
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 6 commits
93c6ba5
2316416
951d954
64500a5
8788f5a
72a163e
638709b
a5943c7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,107 @@ | ||
| package codingcontext | ||
|
|
||
| // 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 | ||
| } | ||
alexec marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| // 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 | ||
alexec marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
| 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]agentPathsConfig{ | ||
alexec marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| // 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"}, | ||
| 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", | ||
| // 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 | ||
| }, | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,163 @@ | ||
| 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 := tt.agent.Paths() | ||
|
|
||
| // Check that at least one path is defined | ||
| hasAnyPath := len(paths.RulesPaths()) > 0 || | ||
| paths.SkillsPath() != "" || | ||
| paths.CommandsPath() != "" || | ||
| paths.TasksPath() != "" | ||
|
|
||
| if !hasAnyPath { | ||
| t.Errorf("Agent %q has no paths defined", tt.agent) | ||
| } | ||
| }) | ||
| } | ||
| } | ||
|
|
||
| func TestAgentPaths_EmptyAgentHasAllPaths(t *testing.T) { | ||
| paths := Agent("").Paths() | ||
|
|
||
| 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 := range agentsPaths { | ||
| paths := agent.Paths() | ||
| if len(paths.RulesPaths()) == 0 { | ||
| t.Errorf("Agent %q should have at least one RulesPaths entry", agent) | ||
| } | ||
| } | ||
| } | ||
|
Comment on lines
91
to
98
|
||
|
|
||
| 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() { | ||
| 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()) | ||
| } | ||
| } | ||
| } | ||
|
Comment on lines
100
to
118
|
||
|
|
||
| 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)) | ||
| } | ||
| } | ||
|
|
||
| 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) | ||
| } | ||
| }) | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,57 +2,63 @@ package codingcontext | |
|
|
||
| import "path/filepath" | ||
|
|
||
| // DownloadedRulePaths returns the search paths for rule files in downloaded directories | ||
| 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"), | ||
| // rulePaths returns the search paths for rule files in a directory. | ||
| // It collects rule paths from all agents in the agentsPaths configuration. | ||
| func rulePaths(dir string) []string { | ||
| var paths []string | ||
|
|
||
| // Iterate through all configured agents | ||
| for _, config := range agentsPaths { | ||
| // Add each rule path for this agent | ||
| for _, rulePath := range config.RulesPaths { | ||
| paths = append(paths, filepath.Join(dir, rulePath)) | ||
| } | ||
| } | ||
| 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"), | ||
| } | ||
|
|
||
| return paths | ||
| } | ||
|
Comment on lines
7
to
19
|
||
|
|
||
| // 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 | ||
| } | ||
alexec marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| // 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 | ||
| } | ||
alexec marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| // 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"), | ||
| filepath.Join(dir, ".cursor", "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 | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.