skills support
This commit is contained in:
+108
@@ -2525,3 +2525,111 @@ func TestWriteFileOmittedOverwritesWholeFile(t *testing.T) {
|
||||
}
|
||||
|
||||
|
||||
|
||||
// #18: skillsDir mirrors toolsDir, and skillPrompt composes SKILL.md + prompt.
|
||||
func TestSkills(t *testing.T) {
|
||||
base := t.TempDir()
|
||||
skillDir := filepath.Join(base, "skills")
|
||||
if err := os.MkdirAll(filepath.Join(skillDir, "greet"), 0755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
skillMd := "You are a friendly greeter.\n"
|
||||
if err := os.WriteFile(filepath.Join(skillDir, "greet", "SKILL.md"), []byte(skillMd), 0644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// skillsDir prefers the env var, then the config key.
|
||||
t.Setenv("BANTAM_SKILLS_DIR", skillDir)
|
||||
if got := skillsDir(&Cfg{}); got != skillDir {
|
||||
t.Errorf("skillsDir() with env = %q, want %q", got, skillDir)
|
||||
}
|
||||
t.Setenv("BANTAM_SKILLS_DIR", "")
|
||||
if got := skillsDir(&Cfg{Raw: map[string]string{"bantam_skills_dir": skillDir}}); got != skillDir {
|
||||
t.Errorf("skillsDir() with config = %q, want %q", got, skillDir)
|
||||
}
|
||||
|
||||
// With a skills dir set, the name is resolved relative to it.
|
||||
got, err := skillPrompt("/skill greet hello there", &Cfg{Raw: map[string]string{"bantam_skills_dir": skillDir}})
|
||||
if err != nil {
|
||||
t.Fatalf("skillPrompt(relative): %v", err)
|
||||
}
|
||||
want := skillMd + "\nhello there"
|
||||
if got != want {
|
||||
t.Errorf("skillPrompt(relative) = %q, want %q", got, want)
|
||||
}
|
||||
|
||||
// With no skills dir, the name is an absolute path to the skill directory.
|
||||
got, err = skillPrompt("/skill "+filepath.Join(skillDir, "greet")+" just hi", &Cfg{})
|
||||
if err != nil {
|
||||
t.Fatalf("skillPrompt(absolute): %v", err)
|
||||
}
|
||||
if got != skillMd+"\njust hi" {
|
||||
t.Errorf("skillPrompt(absolute) = %q, want %q", got, skillMd+"\njust hi")
|
||||
}
|
||||
|
||||
// Absolute path directly to a SKILL.md file also works.
|
||||
got, err = skillPrompt("/skill "+filepath.Join(skillDir, "greet", "SKILL.md"), &Cfg{})
|
||||
if err != nil {
|
||||
t.Fatalf("skillPrompt(file): %v", err)
|
||||
}
|
||||
if got != strings.TrimRight(skillMd, "\n") {
|
||||
t.Errorf("skillPrompt(file) = %q, want %q", got, strings.TrimRight(skillMd, "\n"))
|
||||
}
|
||||
|
||||
// Missing skill reports an error; bare /skill reports usage.
|
||||
if _, err := skillPrompt("/skill nope", &Cfg{Raw: map[string]string{"bantam_skills_dir": skillDir}}); err == nil {
|
||||
t.Error("skillPrompt(missing) expected error, got nil")
|
||||
}
|
||||
if _, err := skillPrompt("/skill", &Cfg{}); err == nil {
|
||||
t.Error("skillPrompt(bare) expected error, got nil")
|
||||
}
|
||||
}
|
||||
|
||||
// #19: bare /skill lists skills under the configured dir, or reports none.
|
||||
func TestListSkills(t *testing.T) {
|
||||
base := t.TempDir()
|
||||
skillDir := filepath.Join(base, "skills")
|
||||
if err := os.MkdirAll(filepath.Join(skillDir, "alpha"), 0755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.MkdirAll(filepath.Join(skillDir, "beta"), 0755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.WriteFile(filepath.Join(skillDir, "alpha", "SKILL.md"), []byte("a"), 0644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.WriteFile(filepath.Join(skillDir, "beta", "SKILL.md"), []byte("b"), 0644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// a directory without SKILL.md must NOT be reported as a skill
|
||||
if err := os.MkdirAll(filepath.Join(skillDir, "notaskill"), 0755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
capture := func() string {
|
||||
r, w, err := os.Pipe()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
old := os.Stdout
|
||||
os.Stdout = w
|
||||
listSkills(&Cfg{Raw: map[string]string{"bantam_skills_dir": skillDir}})
|
||||
w.Close()
|
||||
os.Stdout = old
|
||||
data, _ := io.ReadAll(r)
|
||||
return string(data)
|
||||
}
|
||||
|
||||
out := capture()
|
||||
if !strings.Contains(out, "alpha") || !strings.Contains(out, "beta") {
|
||||
t.Errorf("listSkills missing skills, got:\n%s", out)
|
||||
}
|
||||
if strings.Contains(out, "notaskill") {
|
||||
t.Errorf("listSkills reported a non-skill directory, got:\n%s", out)
|
||||
}
|
||||
|
||||
// No skills dir configured -> clear notice, no panic.
|
||||
os.Stdout, _ = os.Open(os.DevNull)
|
||||
listSkills(&Cfg{})
|
||||
os.Stdout.Close()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user