1package skills
2
3import (
4 "os"
5 "path/filepath"
6 "testing"
7)
8
9func TestSplitFrontmatter(t *testing.T) {
10 in := "---\nname: foo\ndescription: bar\n---\nbody text\n"
11 front, body := splitFrontmatter(in)
12 if front != "name: foo\ndescription: bar" {
13 t.Errorf("front = %q", front)
14 }
15 if body != "body text\n" {
16 t.Errorf("body = %q", body)
17 }
18
19 front2, body2 := splitFrontmatter("no frontmatter here")
20 if front2 != "" || body2 != "no frontmatter here" {
21 t.Errorf("expected pass-through, got front=%q body=%q", front2, body2)
22 }
23}
24
25func TestParseFrontmatter(t *testing.T) {
26 front := `name: code-review
27description: "Review a recent change."
28allowed-tools: [read, bash]
29permissions:
30 bash: ["git diff*", "git log*"]
31`
32 s := &Skill{}
33 parseFrontmatter(front, s)
34 if s.Name != "code-review" {
35 t.Errorf("name = %q", s.Name)
36 }
37 if s.Description != "Review a recent change." {
38 t.Errorf("description = %q", s.Description)
39 }
40 if got := s.AllowedTools; len(got) != 2 || got[0] != "read" || got[1] != "bash" {
41 t.Errorf("allowed-tools = %v", got)
42 }
43 if got := s.Permissions["bash"]; len(got) != 2 || got[0] != "git diff*" || got[1] != "git log*" {
44 t.Errorf("permissions[bash] = %v", got)
45 }
46}
47
48func TestDiscoverProjectAndGlobalPriorityAndDedup(t *testing.T) {
49 tmp := t.TempDir()
50 zotHome := filepath.Join(tmp, "home")
51 cwd := filepath.Join(tmp, "proj")
52
53 mk := func(dir, name, desc string) {
54 full := filepath.Join(dir, name)
55 os.MkdirAll(full, 0o755)
56 body := "---\nname: " + name + "\ndescription: " + desc + "\n---\n# " + name + "\n"
57 os.WriteFile(filepath.Join(full, "SKILL.md"), []byte(body), 0o644)
58 }
59
60 // Same skill name in BOTH project and global; project should win.
61 mk(filepath.Join(cwd, ".zot", "skills"), "shared", "project version")
62 mk(filepath.Join(zotHome, "skills"), "shared", "global version")
63 // Unique skill in global only.
64 mk(filepath.Join(zotHome, "skills"), "global-only", "from global")
65
66 skills, errs := Discover(zotHome, cwd, "")
67 if len(errs) > 0 {
68 t.Fatalf("errs: %v", errs)
69 }
70 if len(skills) != 2 {
71 t.Fatalf("expected 2 skills, got %d (%v)", len(skills), skills)
72 }
73 shared := FindByName(skills, "shared")
74 if shared == nil || shared.Description != "project version" {
75 t.Errorf("expected project to win for 'shared', got %v", shared)
76 }
77 if FindByName(skills, "global-only") == nil {
78 t.Errorf("global-only skill missing")
79 }
80}
81
82func TestSystemPromptAddendum(t *testing.T) {
83 skills := []*Skill{
84 {Name: "a", Description: "Do A."},
85 {Name: "b", Description: "Do B."},
86 }
87 out := SystemPromptAddendum(skills)
88 if want := "- a — Do A.\n- b — Do B.\n"; !contains(out, want) {
89 t.Errorf("addendum missing entries:\n%s", out)
90 }
91}
92
93func contains(s, sub string) bool {
94 return len(s) >= len(sub) && (s == sub || (len(sub) > 0 && stringIndex(s, sub) >= 0))
95}
96
97func stringIndex(s, sub string) int {
98 for i := 0; i+len(sub) <= len(s); i++ {
99 if s[i:i+len(sub)] == sub {
100 return i
101 }
102 }
103 return -1
104}