discoverEntities walks a directory and finds entities using the same conventions as `gh skill install --from-local`: 1. skills/*/SKILL.md → standard flat layout 2. skills/{scope}/*/SKILL.md → namespaced layout 3. {prefix}/skills/*/SKILL.md → deeply nested skills/ dir 4. */SKILL.md
(kind entities.Kind, root string)
| 1680 | // Hidden directories (dot-prefixed like .claude/, .github/) are skipped. |
| 1681 | // Duplicates (same name) are deduplicated — first discovered wins. |
| 1682 | func discoverEntities(kind entities.Kind, root string) []discoveredItem { |
| 1683 | fileTarget := defaultManifestName(kind) |
| 1684 | seen := make(map[string]bool) |
| 1685 | var items []discoveredItem |
| 1686 | |
| 1687 | _ = filepath.WalkDir(root, func(p string, d os.DirEntry, err error) error { |
| 1688 | if err != nil { |
| 1689 | return err |
| 1690 | } |
| 1691 | |
| 1692 | // Skip hidden directories (dot-prefixed). |
| 1693 | if d.IsDir() && strings.HasPrefix(d.Name(), ".") { |
| 1694 | return filepath.SkipDir |
| 1695 | } |
| 1696 | if d.IsDir() { |
| 1697 | return nil |
| 1698 | } |
| 1699 | |
| 1700 | if filepath.Base(p) != fileTarget { |
| 1701 | return nil |
| 1702 | } |
| 1703 | |
| 1704 | // Get path relative to root for convention matching. |
| 1705 | relPath, relErr := filepath.Rel(root, p) |
| 1706 | if relErr != nil { |
| 1707 | return nil |
| 1708 | } |
| 1709 | relPath = filepath.ToSlash(relPath) |
| 1710 | |
| 1711 | // Check if path matches a known skill convention. |
| 1712 | if !matchesSkillConvention(relPath) { |
| 1713 | return nil |
| 1714 | } |
| 1715 | |
| 1716 | name := filepath.Base(filepath.Dir(p)) |
| 1717 | if seen[name] { |
| 1718 | return nil // deduplicate |
| 1719 | } |
| 1720 | seen[name] = true |
| 1721 | |
| 1722 | data, readErr := os.ReadFile(p) |
| 1723 | if readErr != nil { |
| 1724 | return nil |
| 1725 | } |
| 1726 | items = append(items, discoveredItem{name: name, content: string(data), path: p}) |
| 1727 | return nil |
| 1728 | }) |
| 1729 | return items |
| 1730 | } |
| 1731 | |
| 1732 | // matchesSkillConvention checks whether a relative SKILL.md path matches |
| 1733 | // any recognized skill directory convention, mirroring `gh skill install` |
no test coverage detected