Skip to content

Commit 35ee4dc

Browse files
authored
Add skills directory support for all agents (#192)
1 parent 2243b95 commit 35ee4dc

File tree

3 files changed

+317
-7
lines changed

3 files changed

+317
-7
lines changed

docs/reference/search-paths.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,13 @@ Skill files provide specialized capabilities with progressive disclosure. Within
3737

3838
1. `.agents/skills/*/SKILL.md` (each subdirectory in `.agents/skills/` can contain a `SKILL.md` file)
3939
2. `.cursor/skills/*/SKILL.md` (each subdirectory in `.cursor/skills/` can contain a `SKILL.md` file)
40+
3. `.opencode/skills/*/SKILL.md` (each subdirectory in `.opencode/skills/` can contain a `SKILL.md` file)
41+
4. `.github/skills/*/SKILL.md` (each subdirectory in `.github/skills/` can contain a `SKILL.md` file)
42+
5. `.claude/skills/*/SKILL.md` (each subdirectory in `.claude/skills/` can contain a `SKILL.md` file)
43+
6. `.gemini/skills/*/SKILL.md` (each subdirectory in `.gemini/skills/` can contain a `SKILL.md` file)
44+
7. `.augment/skills/*/SKILL.md` (each subdirectory in `.augment/skills/` can contain a `SKILL.md` file)
45+
8. `.windsurf/skills/*/SKILL.md` (each subdirectory in `.windsurf/skills/` can contain a `SKILL.md` file)
46+
9. `.codex/skills/*/SKILL.md` (each subdirectory in `.codex/skills/` can contain a `SKILL.md` file)
4047

4148
**Example:**
4249
```
@@ -51,6 +58,36 @@ Skill files provide specialized capabilities with progressive disclosure. Within
5158
.cursor/skills/
5259
├── code-review/
5360
│ └── SKILL.md
61+
└── refactoring/
62+
└── SKILL.md
63+
64+
.opencode/skills/
65+
├── testing/
66+
│ └── SKILL.md
67+
└── debugging/
68+
└── SKILL.md
69+
70+
.github/skills/
71+
├── deployment/
72+
│ └── SKILL.md
73+
└── ci-cd/
74+
└── SKILL.md
75+
76+
.claude/skills/
77+
├── analysis/
78+
│ └── SKILL.md
79+
└── writing/
80+
└── SKILL.md
81+
82+
.gemini/skills/
83+
├── search/
84+
│ └── SKILL.md
85+
└── multimodal/
86+
└── SKILL.md
87+
88+
.codex/skills/
89+
├── code-gen/
90+
│ └── SKILL.md
5491
└── refactoring/
5592
└── SKILL.md
5693
```

pkg/codingcontext/agent_paths.go

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,37 +30,44 @@ var agentsPaths = map[Agent]agentPathsConfig{
3030
// OpenCode agent paths
3131
AgentOpenCode: {
3232
rulesPaths: []string{".opencode/agent", ".opencode/rules"},
33+
skillsPath: ".opencode/skills",
3334
commandsPath: ".opencode/command",
34-
// No skills or tasks paths defined for OpenCode
35+
// No tasks path defined for OpenCode
3536
},
3637
// Copilot agent paths
3738
AgentCopilot: {
3839
rulesPaths: []string{".github/copilot-instructions.md", ".github/agents"},
39-
// No skills, commands, or tasks paths defined for Copilot
40+
skillsPath: ".github/skills",
41+
// No commands or tasks paths defined for Copilot
4042
},
4143
// Claude agent paths
4244
AgentClaude: {
4345
rulesPaths: []string{".claude", "CLAUDE.md", "CLAUDE.local.md"},
44-
// No skills, commands, or tasks paths defined for Claude
46+
skillsPath: ".claude/skills",
47+
// No commands or tasks paths defined for Claude
4548
},
4649
// Gemini agent paths
4750
AgentGemini: {
4851
rulesPaths: []string{".gemini/styleguide.md", ".gemini", "GEMINI.md"},
49-
// No skills, commands, or tasks paths defined for Gemini
52+
skillsPath: ".gemini/skills",
53+
// No commands or tasks paths defined for Gemini
5054
},
5155
// Augment agent paths
5256
AgentAugment: {
5357
rulesPaths: []string{".augment/rules", ".augment/guidelines.md"},
54-
// No skills, commands, or tasks paths defined for Augment
58+
skillsPath: ".augment/skills",
59+
// No commands or tasks paths defined for Augment
5560
},
5661
// Windsurf agent paths
5762
AgentWindsurf: {
5863
rulesPaths: []string{".windsurf/rules", ".windsurfrules"},
59-
// No skills, commands, or tasks paths defined for Windsurf
64+
skillsPath: ".windsurf/skills",
65+
// No commands or tasks paths defined for Windsurf
6066
},
6167
// Codex agent paths
6268
AgentCodex: {
6369
rulesPaths: []string{".codex", "AGENTS.md"},
64-
// No skills, commands, or tasks paths defined for Codex
70+
skillsPath: ".codex/skills",
71+
// No commands or tasks paths defined for Codex
6572
},
6673
}

pkg/codingcontext/context_test.go

Lines changed: 266 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2377,6 +2377,272 @@ description: A Cursor IDE skill
23772377
}
23782378
},
23792379
},
2380+
{
2381+
name: "discover skills from .opencode/skills directory",
2382+
setup: func(t *testing.T, dir string) {
2383+
// Create task
2384+
createTask(t, dir, "test-task", "", "Test task content")
2385+
2386+
// Create skill in .opencode/skills directory
2387+
skillDir := filepath.Join(dir, ".opencode", "skills", "opencode-skill")
2388+
if err := os.MkdirAll(skillDir, 0o755); err != nil {
2389+
t.Fatalf("failed to create skill directory: %v", err)
2390+
}
2391+
2392+
skillContent := `---
2393+
name: opencode-skill
2394+
description: A skill for OpenCode
2395+
---
2396+
2397+
# OpenCode Skill
2398+
2399+
This is a skill for OpenCode.
2400+
`
2401+
skillPath := filepath.Join(skillDir, "SKILL.md")
2402+
if err := os.WriteFile(skillPath, []byte(skillContent), 0o644); err != nil {
2403+
t.Fatalf("failed to create skill file: %v", err)
2404+
}
2405+
},
2406+
taskName: "test-task",
2407+
wantErr: false,
2408+
checkFunc: func(t *testing.T, result *Result) {
2409+
if len(result.Skills.Skills) != 1 {
2410+
t.Fatalf("expected 1 skill, got %d", len(result.Skills.Skills))
2411+
}
2412+
skill := result.Skills.Skills[0]
2413+
if skill.Name != "opencode-skill" {
2414+
t.Errorf("expected skill name 'opencode-skill', got %q", skill.Name)
2415+
}
2416+
},
2417+
},
2418+
{
2419+
name: "discover skills from .github/skills directory",
2420+
setup: func(t *testing.T, dir string) {
2421+
// Create task
2422+
createTask(t, dir, "test-task", "", "Test task content")
2423+
2424+
// Create skill in .github/skills directory
2425+
skillDir := filepath.Join(dir, ".github", "skills", "copilot-skill")
2426+
if err := os.MkdirAll(skillDir, 0o755); err != nil {
2427+
t.Fatalf("failed to create skill directory: %v", err)
2428+
}
2429+
2430+
skillContent := `---
2431+
name: copilot-skill
2432+
description: A skill for GitHub Copilot
2433+
---
2434+
2435+
# Copilot Skill
2436+
2437+
This is a skill for GitHub Copilot.
2438+
`
2439+
skillPath := filepath.Join(skillDir, "SKILL.md")
2440+
if err := os.WriteFile(skillPath, []byte(skillContent), 0o644); err != nil {
2441+
t.Fatalf("failed to create skill file: %v", err)
2442+
}
2443+
},
2444+
taskName: "test-task",
2445+
wantErr: false,
2446+
checkFunc: func(t *testing.T, result *Result) {
2447+
if len(result.Skills.Skills) != 1 {
2448+
t.Fatalf("expected 1 skill, got %d", len(result.Skills.Skills))
2449+
}
2450+
skill := result.Skills.Skills[0]
2451+
if skill.Name != "copilot-skill" {
2452+
t.Errorf("expected skill name 'copilot-skill', got %q", skill.Name)
2453+
}
2454+
},
2455+
},
2456+
{
2457+
name: "discover skills from .augment/skills directory",
2458+
setup: func(t *testing.T, dir string) {
2459+
// Create task
2460+
createTask(t, dir, "test-task", "", "Test task content")
2461+
2462+
// Create skill in .augment/skills directory
2463+
skillDir := filepath.Join(dir, ".augment", "skills", "augment-skill")
2464+
if err := os.MkdirAll(skillDir, 0o755); err != nil {
2465+
t.Fatalf("failed to create skill directory: %v", err)
2466+
}
2467+
2468+
skillContent := `---
2469+
name: augment-skill
2470+
description: A skill for Augment
2471+
---
2472+
2473+
# Augment Skill
2474+
2475+
This is a skill for Augment.
2476+
`
2477+
skillPath := filepath.Join(skillDir, "SKILL.md")
2478+
if err := os.WriteFile(skillPath, []byte(skillContent), 0o644); err != nil {
2479+
t.Fatalf("failed to create skill file: %v", err)
2480+
}
2481+
},
2482+
taskName: "test-task",
2483+
wantErr: false,
2484+
checkFunc: func(t *testing.T, result *Result) {
2485+
if len(result.Skills.Skills) != 1 {
2486+
t.Fatalf("expected 1 skill, got %d", len(result.Skills.Skills))
2487+
}
2488+
skill := result.Skills.Skills[0]
2489+
if skill.Name != "augment-skill" {
2490+
t.Errorf("expected skill name 'augment-skill', got %q", skill.Name)
2491+
}
2492+
},
2493+
},
2494+
{
2495+
name: "discover skills from .windsurf/skills directory",
2496+
setup: func(t *testing.T, dir string) {
2497+
// Create task
2498+
createTask(t, dir, "test-task", "", "Test task content")
2499+
2500+
// Create skill in .windsurf/skills directory
2501+
skillDir := filepath.Join(dir, ".windsurf", "skills", "windsurf-skill")
2502+
if err := os.MkdirAll(skillDir, 0o755); err != nil {
2503+
t.Fatalf("failed to create skill directory: %v", err)
2504+
}
2505+
2506+
skillContent := `---
2507+
name: windsurf-skill
2508+
description: A skill for Windsurf
2509+
---
2510+
2511+
# Windsurf Skill
2512+
2513+
This is a skill for Windsurf.
2514+
`
2515+
skillPath := filepath.Join(skillDir, "SKILL.md")
2516+
if err := os.WriteFile(skillPath, []byte(skillContent), 0o644); err != nil {
2517+
t.Fatalf("failed to create skill file: %v", err)
2518+
}
2519+
},
2520+
taskName: "test-task",
2521+
wantErr: false,
2522+
checkFunc: func(t *testing.T, result *Result) {
2523+
if len(result.Skills.Skills) != 1 {
2524+
t.Fatalf("expected 1 skill, got %d", len(result.Skills.Skills))
2525+
}
2526+
skill := result.Skills.Skills[0]
2527+
if skill.Name != "windsurf-skill" {
2528+
t.Errorf("expected skill name 'windsurf-skill', got %q", skill.Name)
2529+
}
2530+
},
2531+
},
2532+
{
2533+
name: "discover skills from .claude/skills directory",
2534+
setup: func(t *testing.T, dir string) {
2535+
// Create task
2536+
createTask(t, dir, "test-task", "", "Test task content")
2537+
2538+
// Create skill in .claude/skills directory
2539+
skillDir := filepath.Join(dir, ".claude", "skills", "claude-skill")
2540+
if err := os.MkdirAll(skillDir, 0o755); err != nil {
2541+
t.Fatalf("failed to create skill directory: %v", err)
2542+
}
2543+
2544+
skillContent := `---
2545+
name: claude-skill
2546+
description: A skill for Claude
2547+
---
2548+
2549+
# Claude Skill
2550+
2551+
This is a skill for Claude.
2552+
`
2553+
skillPath := filepath.Join(skillDir, "SKILL.md")
2554+
if err := os.WriteFile(skillPath, []byte(skillContent), 0o644); err != nil {
2555+
t.Fatalf("failed to create skill file: %v", err)
2556+
}
2557+
},
2558+
taskName: "test-task",
2559+
wantErr: false,
2560+
checkFunc: func(t *testing.T, result *Result) {
2561+
if len(result.Skills.Skills) != 1 {
2562+
t.Fatalf("expected 1 skill, got %d", len(result.Skills.Skills))
2563+
}
2564+
skill := result.Skills.Skills[0]
2565+
if skill.Name != "claude-skill" {
2566+
t.Errorf("expected skill name 'claude-skill', got %q", skill.Name)
2567+
}
2568+
},
2569+
},
2570+
{
2571+
name: "discover skills from .gemini/skills directory",
2572+
setup: func(t *testing.T, dir string) {
2573+
// Create task
2574+
createTask(t, dir, "test-task", "", "Test task content")
2575+
2576+
// Create skill in .gemini/skills directory
2577+
skillDir := filepath.Join(dir, ".gemini", "skills", "gemini-skill")
2578+
if err := os.MkdirAll(skillDir, 0o755); err != nil {
2579+
t.Fatalf("failed to create skill directory: %v", err)
2580+
}
2581+
2582+
skillContent := `---
2583+
name: gemini-skill
2584+
description: A skill for Gemini
2585+
---
2586+
2587+
# Gemini Skill
2588+
2589+
This is a skill for Gemini.
2590+
`
2591+
skillPath := filepath.Join(skillDir, "SKILL.md")
2592+
if err := os.WriteFile(skillPath, []byte(skillContent), 0o644); err != nil {
2593+
t.Fatalf("failed to create skill file: %v", err)
2594+
}
2595+
},
2596+
taskName: "test-task",
2597+
wantErr: false,
2598+
checkFunc: func(t *testing.T, result *Result) {
2599+
if len(result.Skills.Skills) != 1 {
2600+
t.Fatalf("expected 1 skill, got %d", len(result.Skills.Skills))
2601+
}
2602+
skill := result.Skills.Skills[0]
2603+
if skill.Name != "gemini-skill" {
2604+
t.Errorf("expected skill name 'gemini-skill', got %q", skill.Name)
2605+
}
2606+
},
2607+
},
2608+
{
2609+
name: "discover skills from .codex/skills directory",
2610+
setup: func(t *testing.T, dir string) {
2611+
// Create task
2612+
createTask(t, dir, "test-task", "", "Test task content")
2613+
2614+
// Create skill in .codex/skills directory
2615+
skillDir := filepath.Join(dir, ".codex", "skills", "codex-skill")
2616+
if err := os.MkdirAll(skillDir, 0o755); err != nil {
2617+
t.Fatalf("failed to create skill directory: %v", err)
2618+
}
2619+
2620+
skillContent := `---
2621+
name: codex-skill
2622+
description: A skill for Codex
2623+
---
2624+
2625+
# Codex Skill
2626+
2627+
This is a skill for Codex.
2628+
`
2629+
skillPath := filepath.Join(skillDir, "SKILL.md")
2630+
if err := os.WriteFile(skillPath, []byte(skillContent), 0o644); err != nil {
2631+
t.Fatalf("failed to create skill file: %v", err)
2632+
}
2633+
},
2634+
taskName: "test-task",
2635+
wantErr: false,
2636+
checkFunc: func(t *testing.T, result *Result) {
2637+
if len(result.Skills.Skills) != 1 {
2638+
t.Fatalf("expected 1 skill, got %d", len(result.Skills.Skills))
2639+
}
2640+
skill := result.Skills.Skills[0]
2641+
if skill.Name != "codex-skill" {
2642+
t.Errorf("expected skill name 'codex-skill', got %q", skill.Name)
2643+
}
2644+
},
2645+
},
23802646
}
23812647

23822648
for _, tt := range tests {

0 commit comments

Comments
 (0)