Files
go-tiny-claw/internal/prompt/skill.go
T
aruo 3e91c7d0a1 v1.6 System Prompt 工程化 + 渐进式技能发现
重构 system prompt 为动态组装系统:PromptComposer 聚合核心身份、AGENTS.md
项目规范和 Skills 目录;新增 read_skill 工具实现技能按需加载;包重命名
internal/context -> internal/prompt 消除 stdlib 冲突。
2026-05-17 00:05:31 +08:00

128 lines
2.9 KiB
Go

package prompt
import (
"fmt"
"io/fs"
"os"
"path/filepath"
"strings"
)
// Skill 定义了从 SKILL.md 中解析出的标准化技能结构
type Skill struct {
Name string
Description string
Body string
}
// SkillSummary 是技能的轻量目录项(仅元信息,不含 Body)
type SkillSummary struct {
Name string
Description string
}
// SkillLoader 负责从本地文件系统中加载技能模板
type SkillLoader struct {
workDir string
}
func NewSkillLoader(workDir string) *SkillLoader {
return &SkillLoader{workDir: workDir}
}
// List 扫描 skills 目录,返回所有技能的轻量目录(仅名称和描述)
func (s *SkillLoader) List() []SkillSummary {
skillBaseDir := filepath.Join(s.workDir, "skills")
if _, err := os.Stat(skillBaseDir); os.IsNotExist(err) {
return nil
}
var summaries []SkillSummary
filepath.WalkDir(skillBaseDir, func(path string, d fs.DirEntry, err error) error {
if err != nil {
return nil
}
if d.IsDir() || d.Name() != "SKILL.md" {
return nil
}
content, err := os.ReadFile(path)
if err != nil {
return nil
}
skill := parseSkillMD(string(content))
summaries = append(summaries, SkillSummary{
Name: skill.Name,
Description: skill.Description,
})
return nil
})
return summaries
}
// Load 按技能名称加载完整的 Skill(含 Body)
func (s *SkillLoader) Load(name string) (*Skill, error) {
skillBaseDir := filepath.Join(s.workDir, "skills")
if _, err := os.Stat(skillBaseDir); os.IsNotExist(err) {
return nil, fmt.Errorf("skills 目录不存在")
}
var found *Skill
filepath.WalkDir(skillBaseDir, func(path string, d fs.DirEntry, err error) error {
if err != nil || found != nil {
return nil
}
if d.IsDir() || d.Name() != "SKILL.md" {
return nil
}
content, err := os.ReadFile(path)
if err != nil {
return nil
}
skill := parseSkillMD(string(content))
if skill.Name == name {
found = &skill
}
return nil
})
if found == nil {
return nil, fmt.Errorf("未找到技能: %s", name)
}
return found, nil
}
// parseSkillMD 解析带有 YAML Frontmatter 的 Markdown 内容
func parseSkillMD(content string) Skill {
skill := Skill{
Name: "Unknown Skill",
Description: "No description provided.",
Body: content,
}
if strings.HasPrefix(content, "---\n") || strings.HasPrefix(content, "---\r\n") {
parts := strings.SplitN(content, "---", 3)
if len(parts) == 3 {
frontmatter := parts[1]
skill.Body = strings.TrimSpace(parts[2])
lines := strings.Split(frontmatter, "\n")
for _, line := range lines {
line = strings.TrimSpace(line)
if strings.HasPrefix(line, "name:") {
skill.Name = strings.TrimSpace(strings.TrimPrefix(line, "name:"))
} else if strings.HasPrefix(line, "description:") {
skill.Description = strings.TrimSpace(strings.TrimPrefix(line, "description:"))
}
}
}
}
return skill
}