重构 system prompt 为动态组装系统:PromptComposer 聚合核心身份、AGENTS.md 项目规范和 Skills 目录;新增 read_skill 工具实现技能按需加载;包重命名 internal/context -> internal/prompt 消除 stdlib 冲突。
128 lines
2.9 KiB
Go
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
|
|
} |