Files
executus/skill/skill_test.go
T
steve 41659b2412 P4: skill noun — domain + LEAN SkillStore + ToRunnable + Memory
The skill half of the persona/skill pair, as a clean redesign (not a faithful
lift of mort's 60-method skills.Storage kitchen sink):
- skill.go/skill_version.go/validate.go/inputs.go/schedule.go moved clean; the
  only host couplings severed: llms.IsTierName -> model.IsTierName, and the
  chatbot DefaultChatbotInputName const localized.
- store.go: a DELIBERATELY LEAN SkillStore — skill lifecycle (CRUD + visibility)
  + versioning + scheduling ONLY. The KV/file/quota sub-stores that were fused
  into mort's interface are the tools/ store seams; email/channel grants stay
  host concerns.
- runnable.go: Skill.ToRunnable() lowers a skill into run.RunnableAgent (flat
  tool list, no palette — composition is a host concern); DueAt() helper.
- memory.go: NewMemory() — zero-dep in-process SkillStore (visibility filters,
  newest-first versions).

Tests: ToRunnable mapping, visibility (public/shared/private) listing, version
ordering + lookup. No mort dependency (go.mod tidy clean); core imports ZERO
from skill.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 22:40:58 -04:00

58 lines
2.0 KiB
Go

package skill
import (
"context"
"testing"
"time"
)
func TestSkillToRunnable(t *testing.T) {
s := &Skill{
ID: "s1", Name: "summarizer", SystemPrompt: "summarize well", ModelTier: "fast",
MaxIterations: 4, MaxRuntime: 20 * time.Second, Tools: []string{"summarize", "now"},
}
r := s.ToRunnable()
if r.ID != "s1" || r.ModelTier != "fast" || r.MaxIterations != 4 || len(r.LowLevelTools) != 2 {
t.Fatalf("ToRunnable mapping wrong: %+v", r)
}
// A skill exposes a flat tool list, not a palette.
if len(r.SkillPalette) != 0 || len(r.SubAgentPalette) != 0 {
t.Errorf("skill should have empty palettes, got %+v", r)
}
}
func TestMemoryStoreVisibilityAndVersions(t *testing.T) {
ctx := context.Background()
m := NewMemory()
pub := &Skill{ID: "a", Name: "pub", OwnerID: "o1", Visibility: VisibilityPublic}
shared := &Skill{ID: "b", Name: "shr", OwnerID: "o1", Visibility: VisibilityShared, SharedWith: []string{"bob"}}
priv := &Skill{ID: "c", Name: "prv", OwnerID: "o1", Visibility: VisibilityPrivate}
for _, s := range []*Skill{pub, shared, priv} {
if err := m.Save(ctx, s); err != nil {
t.Fatal(err)
}
}
if ps, _ := m.ListPublic(ctx); len(ps) != 1 || ps[0].ID != "a" {
t.Errorf("ListPublic = %+v", ps)
}
if ss, _ := m.ListSharedWith(ctx, "bob"); len(ss) != 1 || ss[0].ID != "b" {
t.Errorf("ListSharedWith(bob) = %+v", ss)
}
if ss, _ := m.ListSharedWith(ctx, "carol"); len(ss) != 0 {
t.Errorf("ListSharedWith(carol) should be empty, got %+v", ss)
}
if all, _ := m.ListByOwner(ctx, "o1"); len(all) != 3 {
t.Errorf("ListByOwner = %d, want 3", len(all))
}
// Versions: newest-first, fetchable by id.
m.AppendVersion(ctx, SkillVersion{ID: "v1", SkillID: "a", Version: "1.0.0"})
m.AppendVersion(ctx, SkillVersion{ID: "v2", SkillID: "a", Version: "1.1.0"})
vs, _ := m.ListVersionsBySkill(ctx, "a", 10)
if len(vs) != 2 || vs[0].ID != "v2" {
t.Errorf("versions newest-first wrong: %+v", vs)
}
if got, err := m.GetVersionByID(ctx, "v1"); err != nil || got.Version != "1.0.0" {
t.Errorf("GetVersionByID: %v %+v", err, got)
}
}