@@ -10,7 +10,9 @@ import (
1010 "path/filepath"
1111 "regexp"
1212 "strings"
13+ "sync"
1314
15+ "github.com/charlievieth/fastwalk"
1416 "gopkg.in/yaml.v3"
1517)
1618
@@ -110,17 +112,32 @@ func splitFrontmatter(content string) (frontmatter, body string, err error) {
110112// Discover finds all valid skills in the given paths.
111113func Discover(paths []string) []*Skill {
112114 var skills []*Skill
115+ var mu sync.Mutex
113116 seen := make(map[string]bool)
114117
115118 for _, base := range paths {
116− filepath.WalkDir(base, func(path string, d os.DirEntry, err error) error {
119+ // We use fastwalk with Follow: true instead of filepath.WalkDir because
120+ // WalkDir doesn't follow symlinked directories at any depth—only entry
121+ // points. This ensures skills in symlinked subdirectories are discovered.
122+ // fastwalk is concurrent, so we protect shared state (seen, skills) with mu.
123+ conf := fastwalk.Config{
124+ Follow: true,
125+ ToSlash: fastwalk.DefaultToSlash(),
126+ }
127+ fastwalk.Walk(&conf, base, func(path string, d os.DirEntry, err error) error {
117128 if err != nil {
118129 return nil
119130 }
120− if d.IsDir() || d.Name() != SkillFileName || seen[path] {
131+ if d.IsDir() || d.Name() != SkillFileName {
132+ return nil
133+ }
134+ mu.Lock()
135+ if seen[path] {
136+ mu.Unlock()
121137 return nil
122138 }
123139 seen[path] = true
140+ mu.Unlock()
124141 skill, err := Parse(path)
125142 if err != nil {
126143 slog.Warn("Failed to parse skill file", "path", path, "error", err)
@@ -131,7 +148,9 @@ func Discover(paths []string) []*Skill {
131148 return nil
132149 }
133150 slog.Info("Successfully loaded skill", "name", skill.Name, "path", path)
151+ mu.Lock()
134152 skills = append(skills, skill)
153+ mu.Unlock()
135154 return nil
136155 })
137156 }