Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions docs/reference/search-paths.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ Command files are referenced via slash commands inside task content. Within each
Skill files provide specialized capabilities with progressive disclosure. Within each directory, skill files are searched in:

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)

**Example:**
```
Expand All @@ -46,6 +47,12 @@ Skill files provide specialized capabilities with progressive disclosure. Within
│ └── SKILL.md
└── api-testing/
└── SKILL.md

.cursor/skills/
├── code-review/
│ └── SKILL.md
└── refactoring/
└── SKILL.md
```

### Discovery Rules
Expand Down
98 changes: 98 additions & 0 deletions pkg/codingcontext/context_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2279,6 +2279,104 @@ This is a test skill.
}
},
},
{
name: "discover skills from .cursor/skills directory",
setup: func(t *testing.T, dir string) {
// Create task
createTask(t, dir, "test-task", "", "Test task content")

// Create skill in .cursor/skills directory
skillDir := filepath.Join(dir, ".cursor", "skills", "cursor-skill")
if err := os.MkdirAll(skillDir, 0o755); err != nil {
t.Fatalf("failed to create skill directory: %v", err)
}

skillContent := `---
name: cursor-skill
description: A skill for Cursor IDE
---

# Cursor Skill

This is a skill for Cursor.
`
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 != "cursor-skill" {
t.Errorf("expected skill name 'cursor-skill', got %q", skill.Name)
}
if skill.Description != "A skill for Cursor IDE" {
t.Errorf("expected skill description 'A skill for Cursor IDE', got %q", skill.Description)
}
if skill.Location == "" {
t.Error("expected skill Location to be set")
}
},
},
{
name: "discover skills from both .agents/skills and .cursor/skills",
setup: func(t *testing.T, dir string) {
// Create task
createTask(t, dir, "test-task", "", "Test task content")

// Create skill in .agents/skills directory
skillDir1 := filepath.Join(dir, ".agents", "skills", "agents-skill")
if err := os.MkdirAll(skillDir1, 0o755); err != nil {
t.Fatalf("failed to create skill directory: %v", err)
}
skillContent1 := `---
name: agents-skill
description: A generic agents skill
---

# Agents Skill
`
skillPath1 := filepath.Join(skillDir1, "SKILL.md")
if err := os.WriteFile(skillPath1, []byte(skillContent1), 0o644); err != nil {
t.Fatalf("failed to create skill file: %v", err)
}

// Create skill in .cursor/skills directory
skillDir2 := filepath.Join(dir, ".cursor", "skills", "cursor-skill")
if err := os.MkdirAll(skillDir2, 0o755); err != nil {
t.Fatalf("failed to create skill directory: %v", err)
}
skillContent2 := `---
name: cursor-skill
description: A Cursor IDE skill
---

# Cursor Skill
`
skillPath2 := filepath.Join(skillDir2, "SKILL.md")
if err := os.WriteFile(skillPath2, []byte(skillContent2), 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) != 2 {
t.Fatalf("expected 2 skills, got %d", len(result.Skills.Skills))
}
names := []string{result.Skills.Skills[0].Name, result.Skills.Skills[1].Name}
// Verify both skills are present (order doesn't matter)
if (names[0] != "agents-skill" && names[0] != "cursor-skill") ||
(names[1] != "agents-skill" && names[1] != "cursor-skill") {
t.Errorf("expected skills 'agents-skill' and 'cursor-skill', got %v", names)
}
},
},
}

for _, tt := range tests {
Expand Down
1 change: 1 addition & 0 deletions pkg/codingcontext/paths.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,5 +53,6 @@ func commandSearchPaths(dir string) []string {
func skillSearchPaths(dir string) []string {
return []string{
filepath.Join(dir, ".agents", "skills"),
filepath.Join(dir, ".cursor", "skills"),
}
}