diff --git a/docs/reference/search-paths.md b/docs/reference/search-paths.md index 4a351ea..e44b2a0 100644 --- a/docs/reference/search-paths.md +++ b/docs/reference/search-paths.md @@ -37,6 +37,13 @@ Skill files provide specialized capabilities with progressive disclosure. Within 1. `.agents/skills/*/SKILL.md` (each subdirectory in `.agents/skills/` can contain a `SKILL.md` file) 2. `.cursor/skills/*/SKILL.md` (each subdirectory in `.cursor/skills/` can contain a `SKILL.md` file) +3. `.opencode/skills/*/SKILL.md` (each subdirectory in `.opencode/skills/` can contain a `SKILL.md` file) +4. `.github/skills/*/SKILL.md` (each subdirectory in `.github/skills/` can contain a `SKILL.md` file) +5. `.claude/skills/*/SKILL.md` (each subdirectory in `.claude/skills/` can contain a `SKILL.md` file) +6. `.gemini/skills/*/SKILL.md` (each subdirectory in `.gemini/skills/` can contain a `SKILL.md` file) +7. `.augment/skills/*/SKILL.md` (each subdirectory in `.augment/skills/` can contain a `SKILL.md` file) +8. `.windsurf/skills/*/SKILL.md` (each subdirectory in `.windsurf/skills/` can contain a `SKILL.md` file) +9. `.codex/skills/*/SKILL.md` (each subdirectory in `.codex/skills/` can contain a `SKILL.md` file) **Example:** ``` @@ -51,6 +58,36 @@ Skill files provide specialized capabilities with progressive disclosure. Within .cursor/skills/ ├── code-review/ │ └── SKILL.md +└── refactoring/ + └── SKILL.md + +.opencode/skills/ +├── testing/ +│ └── SKILL.md +└── debugging/ + └── SKILL.md + +.github/skills/ +├── deployment/ +│ └── SKILL.md +└── ci-cd/ + └── SKILL.md + +.claude/skills/ +├── analysis/ +│ └── SKILL.md +└── writing/ + └── SKILL.md + +.gemini/skills/ +├── search/ +│ └── SKILL.md +└── multimodal/ + └── SKILL.md + +.codex/skills/ +├── code-gen/ +│ └── SKILL.md └── refactoring/ └── SKILL.md ``` diff --git a/pkg/codingcontext/agent_paths.go b/pkg/codingcontext/agent_paths.go index 83e1e3a..8b838ec 100644 --- a/pkg/codingcontext/agent_paths.go +++ b/pkg/codingcontext/agent_paths.go @@ -30,37 +30,44 @@ var agentsPaths = map[Agent]agentPathsConfig{ // OpenCode agent paths AgentOpenCode: { rulesPaths: []string{".opencode/agent", ".opencode/rules"}, + skillsPath: ".opencode/skills", commandsPath: ".opencode/command", - // No skills or tasks paths defined for OpenCode + // No tasks path defined for OpenCode }, // Copilot agent paths AgentCopilot: { rulesPaths: []string{".github/copilot-instructions.md", ".github/agents"}, - // No skills, commands, or tasks paths defined for Copilot + skillsPath: ".github/skills", + // No 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 + skillsPath: ".claude/skills", + // No 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 + skillsPath: ".gemini/skills", + // No 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 + skillsPath: ".augment/skills", + // No 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 + skillsPath: ".windsurf/skills", + // No 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 + skillsPath: ".codex/skills", + // No commands or tasks paths defined for Codex }, } diff --git a/pkg/codingcontext/context_test.go b/pkg/codingcontext/context_test.go index f4aea0e..e11025d 100644 --- a/pkg/codingcontext/context_test.go +++ b/pkg/codingcontext/context_test.go @@ -2377,6 +2377,272 @@ description: A Cursor IDE skill } }, }, + { + name: "discover skills from .opencode/skills directory", + setup: func(t *testing.T, dir string) { + // Create task + createTask(t, dir, "test-task", "", "Test task content") + + // Create skill in .opencode/skills directory + skillDir := filepath.Join(dir, ".opencode", "skills", "opencode-skill") + if err := os.MkdirAll(skillDir, 0o755); err != nil { + t.Fatalf("failed to create skill directory: %v", err) + } + + skillContent := `--- +name: opencode-skill +description: A skill for OpenCode +--- + +# OpenCode Skill + +This is a skill for OpenCode. +` + skillPath := filepath.Join(skillDir, "SKILL.md") + if err := os.WriteFile(skillPath, []byte(skillContent), 0o644); err != nil { + t.Fatalf("failed to create skill file: %v", err) + } + }, + taskName: "test-task", + wantErr: false, + checkFunc: func(t *testing.T, result *Result) { + if len(result.Skills.Skills) != 1 { + t.Fatalf("expected 1 skill, got %d", len(result.Skills.Skills)) + } + skill := result.Skills.Skills[0] + if skill.Name != "opencode-skill" { + t.Errorf("expected skill name 'opencode-skill', got %q", skill.Name) + } + }, + }, + { + name: "discover skills from .github/skills directory", + setup: func(t *testing.T, dir string) { + // Create task + createTask(t, dir, "test-task", "", "Test task content") + + // Create skill in .github/skills directory + skillDir := filepath.Join(dir, ".github", "skills", "copilot-skill") + if err := os.MkdirAll(skillDir, 0o755); err != nil { + t.Fatalf("failed to create skill directory: %v", err) + } + + skillContent := `--- +name: copilot-skill +description: A skill for GitHub Copilot +--- + +# Copilot Skill + +This is a skill for GitHub Copilot. +` + skillPath := filepath.Join(skillDir, "SKILL.md") + if err := os.WriteFile(skillPath, []byte(skillContent), 0o644); err != nil { + t.Fatalf("failed to create skill file: %v", err) + } + }, + taskName: "test-task", + wantErr: false, + checkFunc: func(t *testing.T, result *Result) { + if len(result.Skills.Skills) != 1 { + t.Fatalf("expected 1 skill, got %d", len(result.Skills.Skills)) + } + skill := result.Skills.Skills[0] + if skill.Name != "copilot-skill" { + t.Errorf("expected skill name 'copilot-skill', got %q", skill.Name) + } + }, + }, + { + name: "discover skills from .augment/skills directory", + setup: func(t *testing.T, dir string) { + // Create task + createTask(t, dir, "test-task", "", "Test task content") + + // Create skill in .augment/skills directory + skillDir := filepath.Join(dir, ".augment", "skills", "augment-skill") + if err := os.MkdirAll(skillDir, 0o755); err != nil { + t.Fatalf("failed to create skill directory: %v", err) + } + + skillContent := `--- +name: augment-skill +description: A skill for Augment +--- + +# Augment Skill + +This is a skill for Augment. +` + skillPath := filepath.Join(skillDir, "SKILL.md") + if err := os.WriteFile(skillPath, []byte(skillContent), 0o644); err != nil { + t.Fatalf("failed to create skill file: %v", err) + } + }, + taskName: "test-task", + wantErr: false, + checkFunc: func(t *testing.T, result *Result) { + if len(result.Skills.Skills) != 1 { + t.Fatalf("expected 1 skill, got %d", len(result.Skills.Skills)) + } + skill := result.Skills.Skills[0] + if skill.Name != "augment-skill" { + t.Errorf("expected skill name 'augment-skill', got %q", skill.Name) + } + }, + }, + { + name: "discover skills from .windsurf/skills directory", + setup: func(t *testing.T, dir string) { + // Create task + createTask(t, dir, "test-task", "", "Test task content") + + // Create skill in .windsurf/skills directory + skillDir := filepath.Join(dir, ".windsurf", "skills", "windsurf-skill") + if err := os.MkdirAll(skillDir, 0o755); err != nil { + t.Fatalf("failed to create skill directory: %v", err) + } + + skillContent := `--- +name: windsurf-skill +description: A skill for Windsurf +--- + +# Windsurf Skill + +This is a skill for Windsurf. +` + skillPath := filepath.Join(skillDir, "SKILL.md") + if err := os.WriteFile(skillPath, []byte(skillContent), 0o644); err != nil { + t.Fatalf("failed to create skill file: %v", err) + } + }, + taskName: "test-task", + wantErr: false, + checkFunc: func(t *testing.T, result *Result) { + if len(result.Skills.Skills) != 1 { + t.Fatalf("expected 1 skill, got %d", len(result.Skills.Skills)) + } + skill := result.Skills.Skills[0] + if skill.Name != "windsurf-skill" { + t.Errorf("expected skill name 'windsurf-skill', got %q", skill.Name) + } + }, + }, + { + name: "discover skills from .claude/skills directory", + setup: func(t *testing.T, dir string) { + // Create task + createTask(t, dir, "test-task", "", "Test task content") + + // Create skill in .claude/skills directory + skillDir := filepath.Join(dir, ".claude", "skills", "claude-skill") + if err := os.MkdirAll(skillDir, 0o755); err != nil { + t.Fatalf("failed to create skill directory: %v", err) + } + + skillContent := `--- +name: claude-skill +description: A skill for Claude +--- + +# Claude Skill + +This is a skill for Claude. +` + skillPath := filepath.Join(skillDir, "SKILL.md") + if err := os.WriteFile(skillPath, []byte(skillContent), 0o644); err != nil { + t.Fatalf("failed to create skill file: %v", err) + } + }, + taskName: "test-task", + wantErr: false, + checkFunc: func(t *testing.T, result *Result) { + if len(result.Skills.Skills) != 1 { + t.Fatalf("expected 1 skill, got %d", len(result.Skills.Skills)) + } + skill := result.Skills.Skills[0] + if skill.Name != "claude-skill" { + t.Errorf("expected skill name 'claude-skill', got %q", skill.Name) + } + }, + }, + { + name: "discover skills from .gemini/skills directory", + setup: func(t *testing.T, dir string) { + // Create task + createTask(t, dir, "test-task", "", "Test task content") + + // Create skill in .gemini/skills directory + skillDir := filepath.Join(dir, ".gemini", "skills", "gemini-skill") + if err := os.MkdirAll(skillDir, 0o755); err != nil { + t.Fatalf("failed to create skill directory: %v", err) + } + + skillContent := `--- +name: gemini-skill +description: A skill for Gemini +--- + +# Gemini Skill + +This is a skill for Gemini. +` + skillPath := filepath.Join(skillDir, "SKILL.md") + if err := os.WriteFile(skillPath, []byte(skillContent), 0o644); err != nil { + t.Fatalf("failed to create skill file: %v", err) + } + }, + taskName: "test-task", + wantErr: false, + checkFunc: func(t *testing.T, result *Result) { + if len(result.Skills.Skills) != 1 { + t.Fatalf("expected 1 skill, got %d", len(result.Skills.Skills)) + } + skill := result.Skills.Skills[0] + if skill.Name != "gemini-skill" { + t.Errorf("expected skill name 'gemini-skill', got %q", skill.Name) + } + }, + }, + { + name: "discover skills from .codex/skills directory", + setup: func(t *testing.T, dir string) { + // Create task + createTask(t, dir, "test-task", "", "Test task content") + + // Create skill in .codex/skills directory + skillDir := filepath.Join(dir, ".codex", "skills", "codex-skill") + if err := os.MkdirAll(skillDir, 0o755); err != nil { + t.Fatalf("failed to create skill directory: %v", err) + } + + skillContent := `--- +name: codex-skill +description: A skill for Codex +--- + +# Codex Skill + +This is a skill for Codex. +` + skillPath := filepath.Join(skillDir, "SKILL.md") + if err := os.WriteFile(skillPath, []byte(skillContent), 0o644); err != nil { + t.Fatalf("failed to create skill file: %v", err) + } + }, + taskName: "test-task", + wantErr: false, + checkFunc: func(t *testing.T, result *Result) { + if len(result.Skills.Skills) != 1 { + t.Fatalf("expected 1 skill, got %d", len(result.Skills.Skills)) + } + skill := result.Skills.Skills[0] + if skill.Name != "codex-skill" { + t.Errorf("expected skill name 'codex-skill', got %q", skill.Name) + } + }, + }, } for _, tt := range tests {