@@ -247,3 +247,136 @@ func TestToPromptXMLEmpty(t *testing.T) {
247247 require.Empty(t, ToPromptXML(nil))
248248 require.Empty(t, ToPromptXML([]*Skill{}))
249249}
250+
251+func TestToPromptXMLBuiltinType(t *testing.T) {
252+ t.Parallel()
253+
254+ skills := []*Skill{
255+ {Name: "builtin-skill", Description: "A builtin.", SkillFilePath: "crush://skills/builtin-skill/SKILL.md", Builtin: true},
256+ {Name: "user-skill", Description: "A user skill.", SkillFilePath: "/home/user/.config/crush/skills/user-skill/SKILL.md"},
257+ }
258+ xml := ToPromptXML(skills)
259+ require.Contains(t, xml, "<type>builtin</type>")
260+ require.Equal(t, 1, strings.Count(xml, "<type>builtin</type>"))
261+}
262+
263+func TestParseContent(t *testing.T) {
264+ t.Parallel()
265+
266+ content := []byte(`---
267+name: my-skill
268+description: A test skill.
269+---
270+
271+# My Skill
272+
273+Instructions here.
274+`)
275+ skill, err := ParseContent(content)
276+ require.NoError(t, err)
277+ require.Equal(t, "my-skill", skill.Name)
278+ require.Equal(t, "A test skill.", skill.Description)
279+ require.Equal(t, "# My Skill\n\nInstructions here.", skill.Instructions)
280+ require.Empty(t, skill.Path)
281+ require.Empty(t, skill.SkillFilePath)
282+}
283+
284+func TestParseContent_NoFrontmatter(t *testing.T) {
285+ t.Parallel()
286+
287+ _, err := ParseContent([]byte("# Just Markdown"))
288+ require.Error(t, err)
289+}
290+
291+func TestDiscoverBuiltin(t *testing.T) {
292+ t.Parallel()
293+
294+ discovered := DiscoverBuiltin()
295+ require.NotEmpty(t, discovered)
296+
297+ var found bool
298+ for _, s := range discovered {
299+ if s.Name == "crush-config" {
300+ found = true
301+ require.True(t, strings.HasPrefix(s.SkillFilePath, BuiltinPrefix))
302+ require.True(t, strings.HasPrefix(s.Path, BuiltinPrefix))
303+ require.Equal(t, "crush://skills/crush-config/SKILL.md", s.SkillFilePath)
304+ require.Equal(t, "crush://skills/crush-config", s.Path)
305+ require.NotEmpty(t, s.Description)
306+ require.NotEmpty(t, s.Instructions)
307+ require.True(t, s.Builtin)
308+ }
309+ }
310+ require.True(t, found, "crush-config builtin skill not found")
311+}
312+
313+func TestDeduplicate(t *testing.T) {
314+ t.Parallel()
315+
316+ tests := []struct {
317+ name string
318+ input []*Skill
319+ wantLen int
320+ wantName string
321+ wantPath string
322+ }{
323+ {
324+ name: "no duplicates",
325+ input: []*Skill{{Name: "a", Path: "/a"}, {Name: "b", Path: "/b"}},
326+ wantLen: 2,
327+ },
328+ {
329+ name: "user overrides builtin",
330+ input: []*Skill{{Name: "crush-config", Path: "crush://skills/crush-config"}, {Name: "crush-config", Path: "/user/crush-config"}},
331+ wantLen: 1,
332+ wantName: "crush-config",
333+ wantPath: "/user/crush-config",
334+ },
335+ {
336+ name: "empty",
337+ input: nil,
338+ wantLen: 0,
339+ },
340+ }
341+
342+ for _, tt := range tests {
343+ t.Run(tt.name, func(t *testing.T) {
344+ t.Parallel()
345+ result := Deduplicate(tt.input)
346+ require.Len(t, result, tt.wantLen)
347+ if tt.wantName != "" {
348+ require.Equal(t, tt.wantName, result[0].Name)
349+ require.Equal(t, tt.wantPath, result[0].Path)
350+ }
351+ })
352+ }
353+}
354+
355+func TestFilter(t *testing.T) {
356+ t.Parallel()
357+
358+ all := []*Skill{
359+ {Name: "a"},
360+ {Name: "b"},
361+ {Name: "c"},
362+ }
363+
364+ tests := []struct {
365+ name string
366+ disabled []string
367+ wantLen int
368+ }{
369+ {"no filter", nil, 3},
370+ {"filter one", []string{"b"}, 2},
371+ {"filter all", []string{"a", "b", "c"}, 0},
372+ {"filter nonexistent", []string{"d"}, 3},
373+ }
374+
375+ for _, tt := range tests {
376+ t.Run(tt.name, func(t *testing.T) {
377+ t.Parallel()
378+ result := Filter(all, tt.disabled)
379+ require.Len(t, result, tt.wantLen)
380+ })
381+ }
382+}