mirror of
https://github.com/multica-ai/multica.git
synced 2026-07-05 21:39:54 +02:00
Built-in SKILL.md description values contained unquoted ': ' sequences, which strict YAML parsers (e.g. Codex) reject — silently dropping the skill at load. - Quote all eight built-in skill descriptions. - ensureSkillFrontmatter() re-synthesizes frontmatter that has a name but fails YAML validation, so malformed imports are repaired instead of dropped. - Unify frontmatter delimiter parsing into a single frontmatterParts helper. - Add strict-YAML regression tests over the built-in skills, plus unit tests for the recovery branch and delimiter variants. Closes #3851.
162 lines
5.4 KiB
Go
162 lines
5.4 KiB
Go
package execenv
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
|
|
"gopkg.in/yaml.v3"
|
|
)
|
|
|
|
// parseFrontmatter is a strict YAML parse of a SKILL.md frontmatter block, used
|
|
// by the tests to assert the writer always emits something a strict runtime
|
|
// (e.g. Codex) can load. It mirrors how those runtimes read the file: take the
|
|
// text between the leading `---` and the next `---`, and yaml.Unmarshal it.
|
|
func parseFrontmatter(t *testing.T, content string) map[string]any {
|
|
t.Helper()
|
|
if !strings.HasPrefix(content, "---\n") {
|
|
t.Fatalf("content does not start with a frontmatter block:\n%s", content)
|
|
}
|
|
rest := content[len("---\n"):]
|
|
end := strings.Index(rest, "\n---")
|
|
if end < 0 {
|
|
t.Fatalf("frontmatter has no closing delimiter:\n%s", content)
|
|
}
|
|
var m map[string]any
|
|
if err := yaml.Unmarshal([]byte(rest[:end]), &m); err != nil {
|
|
t.Fatalf("frontmatter is not valid YAML: %v\nblock:\n%s", err, rest[:end])
|
|
}
|
|
return m
|
|
}
|
|
|
|
// TestEnsureSkillFrontmatterReSynthesizesInvalidYAML is the regression guard for
|
|
// the bug this change set fixes: a SKILL.md whose frontmatter has a `name` but
|
|
// is not valid YAML (an unquoted `: ` in the description is the canonical case)
|
|
// must be rewritten into a parseable block instead of being shipped as-is, or a
|
|
// strict runtime drops the whole skill on load.
|
|
func TestEnsureSkillFrontmatterReSynthesizesInvalidYAML(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
const body = "# Heading\n\nReal skill body.\n"
|
|
// `description: bad: value` is the exact failure mode from the issue: the
|
|
// second `: ` makes YAML treat the tail as a nested mapping.
|
|
broken := "---\nname: keep-me\ndescription: bad: value here\n---\n\n" + body
|
|
|
|
got := ensureSkillFrontmatter(broken, "my-slug", "DB description: with a colon")
|
|
|
|
fm := parseFrontmatter(t, got)
|
|
if name, _ := fm["name"].(string); name != "my-slug" {
|
|
t.Errorf("name = %#v, want %q", fm["name"], "my-slug")
|
|
}
|
|
if desc, _ := fm["description"].(string); desc != "DB description: with a colon" {
|
|
t.Errorf("description = %#v, want the DB description verbatim", fm["description"])
|
|
}
|
|
if !strings.Contains(got, "Real skill body.") {
|
|
t.Errorf("body was dropped during re-synthesis:\n%s", got)
|
|
}
|
|
}
|
|
|
|
// When the existing frontmatter is invalid AND the DB description is empty, the
|
|
// writer still has to produce parseable YAML — just with name alone. (An empty
|
|
// description is dropped rather than emitted as `description: ""`.)
|
|
func TestEnsureSkillFrontmatterReSynthesizesInvalidYAMLWithEmptyDescription(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
broken := "---\nname: keep-me\ndescription: bad: value\n---\n\nbody text\n"
|
|
|
|
got := ensureSkillFrontmatter(broken, "my-slug", "")
|
|
|
|
fm := parseFrontmatter(t, got)
|
|
if name, _ := fm["name"].(string); name != "my-slug" {
|
|
t.Errorf("name = %#v, want %q", fm["name"], "my-slug")
|
|
}
|
|
if _, present := fm["description"]; present {
|
|
t.Errorf("description should be omitted when DB description is empty, got %#v", fm["description"])
|
|
}
|
|
if !strings.Contains(got, "body text") {
|
|
t.Errorf("body was dropped during re-synthesis:\n%s", got)
|
|
}
|
|
}
|
|
|
|
// Valid frontmatter that already carries a name must survive untouched —
|
|
// re-synthesis is only for the broken case, so upstream import formatting is
|
|
// preserved on the happy path.
|
|
func TestEnsureSkillFrontmatterLeavesValidYAMLUntouched(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
valid := "---\nname: upstream\ndescription: \"colon: safe because quoted\"\nextra-key: kept\n---\n\nbody\n"
|
|
|
|
if got := ensureSkillFrontmatter(valid, "my-slug", "ignored"); got != valid {
|
|
t.Errorf("valid frontmatter was rewritten;\n got: %q\nwant: %q", got, valid)
|
|
}
|
|
}
|
|
|
|
// frontmatterParts must agree on where a block ends regardless of how the
|
|
// closing delimiter is terminated. Before the shared helper, the validity check
|
|
// and the strip path used different close patterns (`\n---` vs `\n---\n`), so an
|
|
// EOF- or CRLF-terminated block was detected as present by one and missed by the
|
|
// other — leaving a stale block behind on re-synthesis.
|
|
func TestFrontmatterPartsClosingDelimiterVariants(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
cases := []struct {
|
|
name string
|
|
content string
|
|
wantFM string
|
|
wantBody string
|
|
wantOK bool
|
|
}{
|
|
{
|
|
name: "newline terminated with blank line",
|
|
content: "---\nname: x\n---\n\nbody",
|
|
wantFM: "name: x",
|
|
wantBody: "\nbody",
|
|
wantOK: true,
|
|
},
|
|
{
|
|
name: "closing delimiter at EOF",
|
|
content: "---\nname: x\n---",
|
|
wantFM: "name: x",
|
|
wantBody: "",
|
|
wantOK: true,
|
|
},
|
|
{
|
|
name: "crlf terminated",
|
|
content: "---\r\nname: x\r\n---\r\nbody",
|
|
wantFM: "name: x\r",
|
|
wantBody: "body",
|
|
wantOK: true,
|
|
},
|
|
{
|
|
name: "horizontal rule is not a delimiter",
|
|
content: "---\nname: x\n----\n---\nbody",
|
|
wantFM: "name: x\n----",
|
|
wantBody: "body",
|
|
wantOK: true,
|
|
},
|
|
{
|
|
name: "no closing delimiter keeps full content as body",
|
|
content: "---\nname: x\nbody without close",
|
|
wantFM: "",
|
|
wantBody: "---\nname: x\nbody without close",
|
|
wantOK: false,
|
|
},
|
|
{
|
|
name: "no opening delimiter",
|
|
content: "no frontmatter here",
|
|
wantFM: "",
|
|
wantBody: "no frontmatter here",
|
|
wantOK: false,
|
|
},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
fm, body, ok := frontmatterParts(tc.content)
|
|
if ok != tc.wantOK || fm != tc.wantFM || body != tc.wantBody {
|
|
t.Errorf("frontmatterParts(%q) = (%q, %q, %v), want (%q, %q, %v)",
|
|
tc.content, fm, body, ok, tc.wantFM, tc.wantBody, tc.wantOK)
|
|
}
|
|
})
|
|
}
|
|
}
|