feat: recursive skill discovery + tree-url type-root folding
- skills are found at any depth under the skills dir, so category-nested layouts (skills/<category>/<id>/SKILL.md) vendor correctly; the folder basename is the upstream id and the outermost SKILL.md wins over anything nested inside a skill folder - web-tree urls pointing directly at a type directory (.../tree/main/skills) now map that directory as the type root instead of nesting it under the default layout - verified against the real github.com/mattpocock/skills repository (36 skills across categories; include-selection by basename)
This commit is contained in:
@@ -57,6 +57,7 @@ func Scan(tree Tree, cfg Config) ([]Item, error) {
|
||||
dirEntries[d] = map[string]bool{}
|
||||
fileEntries[d] = map[string]bool{}
|
||||
}
|
||||
var skillDescendants []string
|
||||
|
||||
err := tree.Walk(func(rel string, isDir bool) error {
|
||||
parent, base := splitPath(rel)
|
||||
@@ -64,6 +65,9 @@ func Scan(tree Tree, cfg Config) ([]Item, error) {
|
||||
if set, ok := dirEntries[parent]; ok {
|
||||
set[base] = true
|
||||
}
|
||||
if skillRoot != "" && (parent == skillRoot || strings.HasPrefix(parent, skillRoot+"/")) {
|
||||
skillDescendants = append(skillDescendants, rel)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if set, ok := fileEntries[parent]; ok {
|
||||
@@ -76,18 +80,7 @@ func Scan(tree Tree, cfg Config) ([]Item, error) {
|
||||
}
|
||||
|
||||
var items []Item
|
||||
for name := range dirEntries[skillRoot] {
|
||||
skillMd := join(skillRoot, name, "SKILL.md")
|
||||
data, err := tree.ReadFile(skillMd)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
it := Item{Type: lockfile.TypeSkill, UpstreamID: name, RelPath: join(skillRoot, name)}
|
||||
fm, warnings := ParseFrontmatter(data)
|
||||
it.Frontmatter = &fm
|
||||
for _, w := range warnings {
|
||||
it.Warnings = append(it.Warnings, fmt.Sprintf("skills/%s: %s", name, w))
|
||||
}
|
||||
for _, it := range scanSkills(tree, skillRoot, skillDescendants) {
|
||||
items = append(items, it)
|
||||
}
|
||||
items = append(items, scanFlatFiles(tree, fileEntries, commandRoot, lockfile.TypeCommand)...)
|
||||
@@ -103,6 +96,53 @@ func Scan(tree Tree, cfg Config) ([]Item, error) {
|
||||
return items, nil
|
||||
}
|
||||
|
||||
// scanSkills finds skill folders at any depth under skillRoot (flat layouts
|
||||
// and category-nested layouts alike). The upstream id is the folder's base
|
||||
// name; a directory that is itself a skill wins over anything nested inside
|
||||
// it, and same-basename folders collide at the vendor layer.
|
||||
func scanSkills(tree Tree, skillRoot string, descendants []string) []Item {
|
||||
if skillRoot == "" {
|
||||
return nil
|
||||
}
|
||||
sorted := append([]string(nil), descendants...)
|
||||
sort.Strings(sorted)
|
||||
withSkillMd := map[string]bool{}
|
||||
for _, d := range sorted {
|
||||
if _, err := tree.ReadFile(join(d, "SKILL.md")); err == nil {
|
||||
withSkillMd[d] = true
|
||||
}
|
||||
}
|
||||
var items []Item
|
||||
for _, d := range sorted {
|
||||
if !withSkillMd[d] {
|
||||
continue
|
||||
}
|
||||
nested := false
|
||||
for other := range withSkillMd {
|
||||
if other != d && strings.HasPrefix(d, other+"/") {
|
||||
nested = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if nested {
|
||||
continue
|
||||
}
|
||||
data, err := tree.ReadFile(join(d, "SKILL.md"))
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
name := d[strings.LastIndex(d, "/")+1:]
|
||||
it := Item{Type: lockfile.TypeSkill, UpstreamID: name, RelPath: d}
|
||||
fm, warnings := ParseFrontmatter(data)
|
||||
it.Frontmatter = &fm
|
||||
for _, w := range warnings {
|
||||
it.Warnings = append(it.Warnings, fmt.Sprintf("skills/%s: %s", name, w))
|
||||
}
|
||||
items = append(items, it)
|
||||
}
|
||||
return items
|
||||
}
|
||||
|
||||
func scanFlatFiles(tree Tree, fileEntries map[string]map[string]bool, root, typ string) []Item {
|
||||
var items []Item
|
||||
for base := range fileEntries[root] {
|
||||
|
||||
Reference in New Issue
Block a user