mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-10-13 22:15:47 +00:00
* fix(skills): parse YAML multiline blocks and lists in skill frontmatter
parseSimpleYAML and ParseSkillFrontmatter only handled single-line
key: value pairs. YAML multiline block scalars (|, >) and list values
(- item) were parsed incorrectly:
description: |
Multi-line text here...
was stored as description="|" (literal pipe character), and indented
continuation lines were misinterpreted as new key-value pairs.
Fix: parseSimpleYAML now tracks indented continuation lines after
block scalar indicators (|, >, |-, >-) and empty-value keys (for
list blocks). Continuation lines are joined with spaces. List items
(- value) have the prefix stripped.
ParseSkillFrontmatter now delegates to parseSimpleYAML instead of
duplicating the parsing logic.
* fix(skills): preserve empty-value keys in parseSimpleYAML
When a key has no value and no indented continuation (e.g. "slug:"),
flushBlock now stores "" instead of silently dropping the key.
Maintains backward compatibility with the previous parser behavior.
---------
Co-authored-by: Luvu182 <208665161+Luvu182@users.noreply.github.com>
Co-authored-by: viettranx <viettranx@gmail.com>
72 lines
2.0 KiB
Go
72 lines
2.0 KiB
Go
package skills
|
|
|
|
import (
|
|
"path/filepath"
|
|
"regexp"
|
|
"strings"
|
|
)
|
|
|
|
// SlugRegexp validates skill slugs: lowercase alphanumeric with hyphens, no leading/trailing hyphen.
|
|
var SlugRegexp = regexp.MustCompile(`^[a-z0-9][a-z0-9-]*[a-z0-9]$`)
|
|
|
|
// ParseSkillFrontmatter extracts name, description, and slug from SKILL.md YAML frontmatter.
|
|
// Also returns the full parsed frontmatter as a map for DB storage.
|
|
func ParseSkillFrontmatter(content string) (name, description, slug string, allFields map[string]string) {
|
|
allFields = make(map[string]string)
|
|
if !strings.HasPrefix(content, "---") {
|
|
return "", "", "", allFields
|
|
}
|
|
end := strings.Index(content[3:], "---")
|
|
if end < 0 {
|
|
return "", "", "", allFields
|
|
}
|
|
fm := content[3 : 3+end]
|
|
allFields = parseSimpleYAML(fm)
|
|
name = allFields["name"]
|
|
description = allFields["description"]
|
|
slug = allFields["slug"]
|
|
return
|
|
}
|
|
|
|
// Slugify converts a skill name into a valid slug (lowercase, alphanumeric + hyphens).
|
|
func Slugify(name string) string {
|
|
s := strings.ToLower(name)
|
|
s = strings.Map(func(r rune) rune {
|
|
if (r >= 'a' && r <= 'z') || (r >= '0' && r <= '9') {
|
|
return r
|
|
}
|
|
return '-'
|
|
}, s)
|
|
// Collapse multiple dashes
|
|
for strings.Contains(s, "--") {
|
|
s = strings.ReplaceAll(s, "--", "-")
|
|
}
|
|
s = strings.Trim(s, "-")
|
|
if s == "" {
|
|
s = "skill"
|
|
}
|
|
return s
|
|
}
|
|
|
|
// IsSystemArtifact returns true for OS-generated junk that should be skipped
|
|
// during file extraction and listing (e.g. __MACOSX, .DS_Store, Thumbs.db).
|
|
func IsSystemArtifact(name string) bool {
|
|
base := filepath.Base(name)
|
|
// macOS resource fork / metadata folders and files
|
|
if base == "__MACOSX" || strings.HasPrefix(base, "._") {
|
|
return true
|
|
}
|
|
// Check if any path component is __MACOSX
|
|
for _, part := range strings.Split(filepath.ToSlash(name), "/") {
|
|
if part == "__MACOSX" {
|
|
return true
|
|
}
|
|
}
|
|
// Common OS junk files
|
|
switch base {
|
|
case ".DS_Store", "Thumbs.db", "desktop.ini", ".Spotlight-V100", ".Trashes", ".fseventsd":
|
|
return true
|
|
}
|
|
return false
|
|
}
|