package discovery import ( "strings" ) // Frontmatter holds the leniently parsed leading YAML block of an item. type Frontmatter struct { Name string Description string Tags []string Requires []string } // ParseFrontmatter extracts name, description, tags and requires from a // leading `---` fenced block. Parsing is deliberately lenient: a block that // opens but never closes, or contains lines without a colon, yields warnings // instead of failures; the parseable prefix still wins. Files without a // leading fence have no frontmatter and no warning. func ParseFrontmatter(data []byte) (Frontmatter, []string) { var fm Frontmatter var warnings []string lines, fenced, closed := parseFence(data) if !fenced { return fm, nil } for _, line := range lines { trimmed := strings.TrimSpace(line) if trimmed == "" { continue } key, value, ok := strings.Cut(trimmed, ":") if !ok { warnings = append(warnings, "malformed frontmatter line (missing colon): "+trimmed) continue } key = strings.TrimSpace(key) value = strings.TrimSpace(value) switch strings.ToLower(key) { case "name": fm.Name = value case "description": fm.Description = value case "tags": fm.Tags = splitList(value) case "requires": fm.Requires = splitList(value) } } if !closed { warnings = append(warnings, "frontmatter block not closed") } return fm, warnings } // definitionKeys mark a flat .md under agents/ or commands/ as an intentional // agent/command definition rather than dropped documentation. var definitionKeys = map[string]bool{ "name": true, "description": true, "mode": true, "agent": true, "model": true, "argument-hint": true, "temperature": true, "permission": true, } // HasDefinitionFrontmatter reports whether data's leading frontmatter block // contains any key that marks a flat .md as an agent/command definition. func HasDefinitionFrontmatter(data []byte) bool { lines, fenced, _ := parseFence(data) if !fenced { return false } for _, line := range lines { key, _, ok := strings.Cut(strings.TrimSpace(line), ":") if ok && definitionKeys[strings.ToLower(strings.TrimSpace(key))] { return true } } return false } // parseFence returns the lines between the leading `---` fences: the block // lines, whether the file is fenced at all, and whether the fence closes. func parseFence(data []byte) (lines []string, fenced, closed bool) { all := strings.Split(strings.ReplaceAll(string(data), "\r\n", "\n"), "\n") if len(all) == 0 || strings.TrimSpace(all[0]) != "---" { return nil, false, false } fenced = true for _, line := range all[1:] { if strings.TrimSpace(line) == "---" { return lines, fenced, true } lines = append(lines, line) } return lines, fenced, false } func splitList(v string) []string { if v == "" { return nil } parts := strings.Split(v, ",") var out []string for _, p := range parts { if t := strings.TrimSpace(p); t != "" { out = append(out, t) } } return out }