Files
harness-sync/internal/canonical/load.go
T

183 lines
4.1 KiB
Go

package canonical
import (
"fmt"
"os"
"path/filepath"
"strings"
"gopkg.in/yaml.v3"
)
func Load(root string) (*Bundle, error) {
info, err := os.Stat(root)
if err != nil {
return nil, fmt.Errorf("canonical root %s: %w", root, err)
}
if !info.IsDir() {
return nil, fmt.Errorf("canonical root %s is not a directory", root)
}
b := &Bundle{Root: root}
err = loadYAML(filepath.Join(root, "config.yaml"), &b.Config)
if err != nil {
return nil, err
}
if b.Config.ActiveProfile == "" {
return nil, fmt.Errorf("config.yaml: active_profile is required")
}
profPath := filepath.Join(root, "profiles", b.Config.ActiveProfile+".yaml")
err = loadYAML(profPath, &b.Profile)
if err != nil {
return nil, fmt.Errorf("profile %q: %w", b.Config.ActiveProfile, err)
}
if b.Profile.Name == "" {
return nil, fmt.Errorf("profile %q (%s): name is required (expected key 'name' with a string value)", b.Config.ActiveProfile, profPath)
}
mcpPath := filepath.Join(root, "mcp.yaml")
if fileExists(mcpPath) {
err = loadYAML(mcpPath, &b.MCP)
if err != nil {
return nil, err
}
}
skills, err := loadMarkdownDir(filepath.Join(root, "skills"), "SKILL.md")
if err != nil {
return nil, err
}
for _, m := range skills {
b.Skills = append(b.Skills, Skill(m))
}
agents, err := loadMarkdownDir(filepath.Join(root, "agents"), "")
if err != nil {
return nil, err
}
for _, m := range agents {
b.Agents = append(b.Agents, Agent(m))
}
b.Instructions.PerHarness = map[string]string{}
body, err := readFileIfExists(filepath.Join(root, "instructions", "global.md"))
if err != nil {
return nil, err
}
b.Instructions.Global = body
perHarnessDir := filepath.Join(root, "instructions", "per-harness")
if entries, err := os.ReadDir(perHarnessDir); err == nil {
for _, e := range entries {
if e.IsDir() || !strings.HasSuffix(e.Name(), ".md") {
continue
}
body, err := os.ReadFile(filepath.Join(perHarnessDir, e.Name()))
if err != nil {
return nil, err
}
harness := strings.TrimSuffix(e.Name(), ".md")
b.Instructions.PerHarness[harness] = string(body)
}
}
return b, nil
}
type markdownDoc struct {
Name string
Description string
Body string
Path string
}
func loadMarkdownDir(dir, requiredFilename string) ([]markdownDoc, error) {
var docs []markdownDoc
if !dirExists(dir) {
return docs, nil
}
err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if info.IsDir() {
return nil
}
if requiredFilename != "" && filepath.Base(path) != requiredFilename {
return nil
}
if requiredFilename == "" && !strings.HasSuffix(path, ".md") {
return nil
}
body, err := os.ReadFile(path)
if err != nil {
return err
}
rel, _ := filepath.Rel(dir, path)
name, desc := parseFrontmatter(body)
if name == "" {
name = strings.TrimSuffix(filepath.Base(path), ".md")
}
docs = append(docs, markdownDoc{
Name: name,
Description: desc,
Body: string(body),
Path: rel,
})
return nil
})
return docs, err
}
func parseFrontmatter(b []byte) (name, description string) {
s := string(b)
if !strings.HasPrefix(s, "---\n") {
return "", ""
}
end := strings.Index(s[4:], "\n---")
if end == -1 {
return "", ""
}
fm := s[4 : 4+end]
var meta struct {
Name string `yaml:"name"`
Description string `yaml:"description"`
}
_ = yaml.Unmarshal([]byte(fm), &meta)
return meta.Name, meta.Description
}
func loadYAML(path string, out interface{}) error {
b, err := os.ReadFile(path)
if err != nil {
return fmt.Errorf("read %s: %w", path, err)
}
if err := yaml.Unmarshal(b, out); err != nil {
return fmt.Errorf("parse %s: %w (check YAML syntax and field names)", path, err)
}
return nil
}
func readFileIfExists(path string) (string, error) {
b, err := os.ReadFile(path)
if os.IsNotExist(err) {
return "", nil
}
if err != nil {
return "", err
}
return string(b), nil
}
func fileExists(path string) bool {
info, err := os.Stat(path)
return err == nil && !info.IsDir()
}
func dirExists(path string) bool {
info, err := os.Stat(path)
return err == nil && info.IsDir()
}