ListSkills
Go · module github.com/muthuishere/toolnexus/golang · SPEC §3 · golang/skill.go
func ListSkills(opts LoadSkillsOptions) *SkillInventory
type SkillInventory struct { Skills []SkillInfo Skipped []SkillSkip}
type SkillSkip struct { Location string Reason SkillSkipReason}Runs exactly the discovery and validation pass LoadSkills runs, but builds
no tool — it hands you the parsed skills and a typed list of every candidate that did not
become a skill, with the reason. It is the answer to “I dropped a SKILL.md in and nothing showed
up.”
When to use it
Section titled “When to use it”- Debugging a skill that will not load.
Skippednames the file and tells you whether the frontmatter was malformed, thenamewas missing, the file was unreadable, or the name collided. - Authoring an allowlist. The inventory is deliberately unfiltered, so you can list
everything, then choose what a given agent gets via
LoadSkillsOptions.Filter. - A CI check or a
skills doctorcommand — fail the build when a skill directory contains a brokenSKILL.md.
Why this and not the alternative
Section titled “Why this and not the alternative”Examples
Section titled “Examples”1. Inventory the shared fixture
Section titled “1. Inventory the shared fixture”package main
import ( "fmt" "log" "os" "path/filepath"
toolnexus "github.com/muthuishere/toolnexus/golang")
func main() { // TOOLNEXUS_REPO is set by the docs test runner; in your own code just use a path. dir := filepath.Join(os.Getenv("TOOLNEXUS_REPO"), "examples", "skills")
inv := toolnexus.ListSkills(toolnexus.LoadSkillsOptions{Dirs: []string{dir}})
if len(inv.Skills) != 1 || inv.Skills[0].Name != "hello-world" { log.Fatalf("unexpected inventory: %+v", inv.Skills) } // A clean directory skips nothing. if len(inv.Skipped) != 0 { log.Fatalf("expected no skips, got %+v", inv.Skipped) }
fmt.Println("ok:", inv.Skills[0].Name, "| skipped:", len(inv.Skipped))}2. The reason a SKILL.md did not load
Section titled “2. The reason a SKILL.md did not load”Four typed reasons, and this is how each one shows up.
package main
import ( "fmt" "log" "os" "path/filepath"
toolnexus "github.com/muthuishere/toolnexus/golang")
func write(dir, name, body string) { full := filepath.Join(dir, name) if err := os.MkdirAll(full, 0o755); err != nil { log.Fatal(err) } if err := os.WriteFile(filepath.Join(full, "SKILL.md"), []byte(body), 0o644); err != nil { log.Fatal(err) }}
func main() { root, err := os.MkdirTemp("", "skills") if err != nil { log.Fatal(err) } defer os.RemoveAll(root)
write(root, "good", "---\nname: good\ndescription: Fine.\n---\n\nBody.\n") // Fences present, YAML invalid — an unterminated quoted scalar. write(root, "broken", "---\nname: \"oops\n---\n\nBody.\n") // Parses, but has no name — nothing to call it by. write(root, "unnamed", "---\ndescription: No name here.\n---\n\nBody.\n")
inv := toolnexus.ListSkills(toolnexus.LoadSkillsOptions{Dirs: []string{root}})
if len(inv.Skills) != 1 || inv.Skills[0].Name != "good" { log.Fatalf("expected only 'good', got %+v", inv.Skills) }
reasons := map[toolnexus.SkillSkipReason]string{} for _, s := range inv.Skipped { reasons[s.Reason] = s.Location } if _, ok := reasons[toolnexus.SkipMalformed]; !ok { log.Fatalf("expected a malformed-frontmatter skip, got %+v", inv.Skipped) } if _, ok := reasons[toolnexus.SkipMissingName]; !ok { log.Fatalf("expected a missing-name skip, got %+v", inv.Skipped) } // Reasons are typed constants with stable string values. if string(toolnexus.SkipMalformed) != "malformed-frontmatter" { log.Fatal("skip reason values are part of the contract") }
fmt.Println("ok: 1 skill,", len(inv.Skipped), "skips")}3. Every source, duplicate detection, and authoring the allowlist
Section titled “3. Every source, duplicate detection, and authoring the allowlist”Directories and data skills go into the same inventory, and the second skill to claim a name is
reported as duplicate-name — first one wins.
package main
import ( "fmt" "log" "os" "path/filepath" "sort"
toolnexus "github.com/muthuishere/toolnexus/golang")
func main() { repo := os.Getenv("TOOLNEXUS_REPO") // docs-runner detail shared := filepath.Join(repo, "examples", "skills")
opts := toolnexus.LoadSkillsOptions{ Dirs: []string{shared}, Skills: []toolnexus.SkillDef{ { Name: "deploy", Description: "Ship the service to prod.", Content: "Run the taskfile.", Resources: []string{"skill://deploy/checklist.md"}, }, // Collides with the fixture on disk — dirs are collected first, so this loses. {Name: "hello-world", Description: "A shadow.", Content: "Ignored."}, // No name at all. {Description: "Anonymous.", Content: "Ignored."}, }, // Filter is accepted but has NO effect on the inventory. Filter: map[string]bool{"deploy": true}, }
inv := toolnexus.ListSkills(opts)
var names []string for _, s := range inv.Skills { names = append(names, s.Name) } sort.Strings(names) if len(names) != 2 || names[0] != "deploy" || names[1] != "hello-world" { log.Fatalf("unfiltered inventory expected, got %v", names) }
var dup, missing int for _, s := range inv.Skipped { switch s.Reason { case toolnexus.SkipDuplicateName: dup++ case toolnexus.SkipMissingName: missing++ } } if dup != 1 || missing != 1 { log.Fatalf("expected 1 duplicate + 1 missing-name, got %+v", inv.Skipped) }
// The fixture on disk won: hello-world is still the fs-sourced one. for _, s := range inv.Skills { if s.Name == "hello-world" && s.Origin != "fs" { log.Fatalf("first source should win, got origin %q", s.Origin) } if s.Name == "deploy" && s.Origin != "logical" { log.Fatalf("data skills are logical, got %q", s.Origin) } }
// Now author the allowlist from what you actually found, and load for real. allow := map[string]bool{} for _, s := range inv.Skills { allow[s.Name] = s.Name == "deploy" } src := toolnexus.LoadSkillsWith(toolnexus.LoadSkillsOptions{ Dirs: opts.Dirs, Skills: opts.Skills, Filter: allow, }) if len(src.Skills) != 1 { log.Fatalf("expected the filter to narrow to 1, got %v", src.Skills) }
fmt.Println("ok:", names, "| skipped:", len(inv.Skipped))}Options and results
Section titled “Options and results”LoadSkillsOptions — the same struct LoadSkillsWith takes:
| Field | Type | Effect on ListSkills |
|---|---|---|
Dirs |
[]string |
Roots walked for **/SKILL.md. A missing root logs and contributes nothing. |
Skills |
[]SkillDef |
Skills supplied as data. Collected after Dirs, so directories win a name clash. |
Filter |
map[string]bool |
Ignored — the inventory is always unfiltered. |
SampleLimit |
int |
Ignored — no tool is built, so nothing is sampled. |
SkillSkipReason — the typed values in Skipped:
| Constant | Value | Means |
|---|---|---|
SkipMissingName |
missing-name |
Frontmatter parsed but carried no name (including “no frontmatter at all”). |
SkipMalformed |
malformed-frontmatter |
--- fences present, YAML inside failed to parse. |
SkipDuplicateName |
duplicate-name |
Another candidate already claimed that name; the first one is kept. |
SkipUnreadable |
unreadable |
The SKILL.md could not be read (permissions, broken link). |
See also
Section titled “See also”LoadSkills— discover and build theskilltoolLoadSkillsWith— data skills, allowlists, sample capsCreateToolkit— wires the skill source into a toolkit