From c2b6d416234ab22bc37fa76c61d973908e6e62b8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 25 Dec 2025 23:08:56 +0000 Subject: [PATCH 1/3] Initial plan From 41a19a56f269313df6536860edb41c50ab1103a2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 25 Dec 2025 23:18:30 +0000 Subject: [PATCH 2/3] Add skills directory support for OpenCode, Copilot, Augment, and Windsurf agents - Added skillsPath configuration for OpenCode (.opencode/skills) - Added skillsPath configuration for GitHub Copilot (.github/skills) - Added skillsPath configuration for Augment (.augment/skills) - Added skillsPath configuration for Windsurf (.windsurf/skills) - Updated documentation to reflect new skills paths - Added comprehensive tests for all new skills directories - All tests pass and linting successful Co-authored-by: alexec <1142830+alexec@users.noreply.github.com> --- docs/reference/search-paths.md | 16 ++++ pkg/codingcontext/agent_paths.go | 12 ++- pkg/codingcontext/context_test.go | 152 ++++++++++++++++++++++++++++++ 3 files changed, 176 insertions(+), 4 deletions(-) diff --git a/docs/reference/search-paths.md b/docs/reference/search-paths.md index 4a351ea..65c6b0b 100644 --- a/docs/reference/search-paths.md +++ b/docs/reference/search-paths.md @@ -37,6 +37,10 @@ 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. `.augment/skills/*/SKILL.md` (each subdirectory in `.augment/skills/` can contain a `SKILL.md` file) +6. `.windsurf/skills/*/SKILL.md` (each subdirectory in `.windsurf/skills/` can contain a `SKILL.md` file) **Example:** ``` @@ -53,6 +57,18 @@ Skill files provide specialized capabilities with progressive disclosure. Within │ └── SKILL.md └── refactoring/ └── SKILL.md + +.opencode/skills/ +├── testing/ +│ └── SKILL.md +└── debugging/ + └── SKILL.md + +.github/skills/ +├── deployment/ +│ └── SKILL.md +└── ci-cd/ + └── SKILL.md ``` ### Discovery Rules diff --git a/pkg/codingcontext/agent_paths.go b/pkg/codingcontext/agent_paths.go index 83e1e3a..2d27b33 100644 --- a/pkg/codingcontext/agent_paths.go +++ b/pkg/codingcontext/agent_paths.go @@ -30,13 +30,15 @@ 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: { @@ -51,12 +53,14 @@ var agentsPaths = map[Agent]agentPathsConfig{ // 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: { diff --git a/pkg/codingcontext/context_test.go b/pkg/codingcontext/context_test.go index f4aea0e..3cadbe0 100644 --- a/pkg/codingcontext/context_test.go +++ b/pkg/codingcontext/context_test.go @@ -2377,6 +2377,158 @@ 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) + } + }, + }, } for _, tt := range tests { From 268b1f13eac0e38a4ac01a9c0692d07f05da2cb4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 25 Dec 2025 23:25:02 +0000 Subject: [PATCH 3/3] Add skills directory support for Claude, Gemini, and Codex agents Per feedback, now assuming all agents support skills with the same directory structure. - Added skillsPath configuration for Claude (.claude/skills) - Added skillsPath configuration for Gemini (.gemini/skills) - Added skillsPath configuration for Codex (.codex/skills) - Updated documentation to include all three new skills paths - Added comprehensive tests for all three new agents - All tests pass and linting successful Co-authored-by: alexec <1142830+alexec@users.noreply.github.com> --- docs/reference/search-paths.md | 25 ++++++- pkg/codingcontext/agent_paths.go | 9 ++- pkg/codingcontext/context_test.go | 114 ++++++++++++++++++++++++++++++ 3 files changed, 143 insertions(+), 5 deletions(-) diff --git a/docs/reference/search-paths.md b/docs/reference/search-paths.md index 65c6b0b..e44b2a0 100644 --- a/docs/reference/search-paths.md +++ b/docs/reference/search-paths.md @@ -39,8 +39,11 @@ Skill files provide specialized capabilities with progressive disclosure. Within 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. `.augment/skills/*/SKILL.md` (each subdirectory in `.augment/skills/` can contain a `SKILL.md` file) -6. `.windsurf/skills/*/SKILL.md` (each subdirectory in `.windsurf/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:** ``` @@ -69,6 +72,24 @@ Skill files provide specialized capabilities with progressive disclosure. Within │ └── 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 ``` ### Discovery Rules diff --git a/pkg/codingcontext/agent_paths.go b/pkg/codingcontext/agent_paths.go index 2d27b33..8b838ec 100644 --- a/pkg/codingcontext/agent_paths.go +++ b/pkg/codingcontext/agent_paths.go @@ -43,12 +43,14 @@ var agentsPaths = map[Agent]agentPathsConfig{ // 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: { @@ -65,6 +67,7 @@ var agentsPaths = map[Agent]agentPathsConfig{ // 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 3cadbe0..e11025d 100644 --- a/pkg/codingcontext/context_test.go +++ b/pkg/codingcontext/context_test.go @@ -2529,6 +2529,120 @@ This is a skill for Windsurf. } }, }, + { + 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 {