diff --git a/agent/skills/fsskills/source.go b/agent/skills/fsskills/source.go index 84ce917b..29f01376 100644 --- a/agent/skills/fsskills/source.go +++ b/agent/skills/fsskills/source.go @@ -188,9 +188,14 @@ func discoverSkillDirectories(filesystems []fs.FS) []discoveredSkillDir { func searchForSkills(filesystem fs.FS, dir string, results *[]discoveredSkillDir, currentDepth int) { skillPath := path.Join(dir, skillFileName) if _, err := fs.Stat(filesystem, skillPath); err == nil { - sub, err := fs.Sub(filesystem, dir) - if err == nil { + sub := filesystem + var subErr error + if dir != "." { + sub, subErr = fs.Sub(filesystem, dir) + } + if subErr == nil { *results = append(*results, discoveredSkillDir{fsys: sub, path: dir}) + return } } if currentDepth >= defaultSearchDepth { diff --git a/agent/skills/fsskills/source_test.go b/agent/skills/fsskills/source_test.go index d5c576d1..a74e7d46 100644 --- a/agent/skills/fsskills/source_test.go +++ b/agent/skills/fsskills/source_test.go @@ -70,6 +70,56 @@ func TestFileSource_NestedSkillDirectory_DiscoveredWithinDepthLimit(t *testing.T } } +func TestFileSource_RootSkillFileWithNestedSkillFile_DoesNotAbortDiscovery(t *testing.T) { + root := t.TempDir() + if err := os.WriteFile(filepath.Join(root, "SKILL.md"), []byte("---\nname: root-skill\ndescription: Root\n---\nRoot body."), 0o644); err != nil { + t.Fatal(err) + } + childDir := filepath.Join(root, "child") + if err := os.MkdirAll(childDir, 0o755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(childDir, "SKILL.md"), []byte("---\nname: child\ndescription: Child\n---\nChild body."), 0o644); err != nil { + t.Fatal(err) + } + + source := fsskills.NewSource(os.DirFS(root)) + loaded, err := source.Skills(t.Context()) + if err != nil { + t.Fatal(err) + } + if len(loaded) != 1 { + t.Fatalf("expected 1 skill, got %d", len(loaded)) + } + if loaded[0].Frontmatter.Name != "root-skill" { + t.Fatalf("expected root-skill, got %q", loaded[0].Frontmatter.Name) + } +} + +func TestFileSource_NestedSkillFileUnderSkillRoot_NotDiscoveredAsIndependentSkill(t *testing.T) { + root := t.TempDir() + createSkillDir(t, root, "parent-skill", "Parent", "Parent body.") + childDir := filepath.Join(root, "parent-skill", "child") + if err := os.MkdirAll(childDir, 0o755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(childDir, "SKILL.md"), []byte("---\nname: child\ndescription: Child\n---\nChild body."), 0o644); err != nil { + t.Fatal(err) + } + + source := fsskills.NewSource(os.DirFS(root)) + loaded, err := source.Skills(t.Context()) + if err != nil { + t.Fatal(err) + } + if len(loaded) != 1 { + t.Fatalf("expected 1 skill, got %d", len(loaded)) + } + if loaded[0].Frontmatter.Name != "parent-skill" { + t.Fatalf("expected parent-skill, got %q", loaded[0].Frontmatter.Name) + } +} + func TestFileSource_SkillBeyondMaxDepth_NotDiscovered(t *testing.T) { root := t.TempDir() createSkillDir(t, filepath.Join(root, "l1", "l2", "l3"), "deep-skill", "Too deep", "Body.")