llm(三协议 Provider)/ session(JSONL 持久化)/ tools(Registry、围栏、 builtin、computer)/ cdp(browser)/ skills / mcp / agent(循环、两级压缩、 编排、审批门控)+ 底座 jsonl/textx/execx/diag/scratch/spill/imagecache。 产品装配代码(config 桥接、schedule 工具、memory/plan/retro/prompt) 留在 AutoGo,宿主接入方式见 MIGRATION.md。
87 lines
2.6 KiB
Go
87 lines
2.6 KiB
Go
package skills
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func writeSkill(t *testing.T, root, name, content string) {
|
|
t.Helper()
|
|
dir := filepath.Join(root, name)
|
|
if err := os.MkdirAll(dir, 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.WriteFile(filepath.Join(dir, "SKILL.md"), []byte(content), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
// TestLoad 验证 frontmatter 解析、优先级覆盖与缺省名。
|
|
func TestLoad(t *testing.T) {
|
|
user := t.TempDir()
|
|
proj := t.TempDir()
|
|
|
|
writeSkill(t, user, "deploy", "---\nname: deploy\ndescription: \"How to deploy\"\nallowed-tools: read, grep\n---\n\nStep 1: run make deploy\n")
|
|
writeSkill(t, user, "notes", "No frontmatter here, just body.\n")
|
|
writeSkill(t, proj, "deploy", "---\ndescription: Project override\n---\nProject deploy steps.\n")
|
|
|
|
got := Load(proj, user) // 项目级优先
|
|
if len(got) != 2 {
|
|
t.Fatalf("want 2 skills, got %d: %+v", len(got), got)
|
|
}
|
|
byName := map[string]Skill{}
|
|
for _, s := range got {
|
|
byName[s.Name] = s
|
|
}
|
|
dep := byName["deploy"]
|
|
if dep.Description != "Project override" || !strings.Contains(dep.Body, "Project deploy steps") {
|
|
t.Fatalf("project skill should win: %+v", dep)
|
|
}
|
|
notes := byName["notes"]
|
|
if notes.Name != "notes" || !strings.Contains(notes.Body, "just body") {
|
|
t.Fatalf("missing frontmatter should fall back to dir name: %+v", notes)
|
|
}
|
|
if len(dep.AllowedTools) != 0 {
|
|
t.Fatalf("override should not inherit allowed-tools from user version")
|
|
}
|
|
|
|
// 用户级版本的字段
|
|
gotUser := Load(user)
|
|
var found bool
|
|
for _, s := range gotUser {
|
|
if s.Name == "deploy" {
|
|
found = true
|
|
if len(s.AllowedTools) != 2 || s.AllowedTools[0] != "read" || s.AllowedTools[1] != "grep" {
|
|
t.Fatalf("allowed-tools parse: %+v", s.AllowedTools)
|
|
}
|
|
if !strings.Contains(s.Body, "make deploy") {
|
|
t.Fatalf("body: %q", s.Body)
|
|
}
|
|
}
|
|
}
|
|
if !found {
|
|
t.Fatal("user deploy skill missing")
|
|
}
|
|
}
|
|
|
|
// TestPromptSection:列表注入、预算截断、空列表为空。
|
|
func TestPromptSection(t *testing.T) {
|
|
if PromptSection(nil) != "" {
|
|
t.Fatal("empty skills should produce empty section")
|
|
}
|
|
sk := []Skill{{Name: "deploy", Description: "How to deploy", AllowedTools: []string{"read"}}}
|
|
sec := PromptSection(sk)
|
|
if !strings.Contains(sec, "- deploy: How to deploy") || !strings.Contains(sec, "allowed-tools: read") {
|
|
t.Fatalf("section: %q", sec)
|
|
}
|
|
many := make([]Skill, 200)
|
|
for i := range many {
|
|
many[i] = Skill{Name: strings.Repeat("s", 40) + string(rune('a'+i%26)) + string(rune(i))}
|
|
}
|
|
if got := PromptSection(many); len(got) > maxPromptChars+100 {
|
|
t.Fatalf("budget exceeded: %d", len(got))
|
|
}
|
|
}
|