fix(skills): SkillParser.serialize — закрывающий fence прилипал к последней YAML-строке
kaml encodeToString не ставит завершающий перевод строки, из-за чего сериализованный SKILL.md выглядел так: --- name: x description: "y"--- и SkillParser.parse находил MissingClosingFence — каждый скил, сохранённый через skill_save / SkillMiner, становился нечитаемым после рестарта агента (каталог терял скил). Поставлен явный '\n' перед закрывающим fence. Добавлены юнит-тесты round-trip serialize->parse (обычный, пустой body, спецсимволы YAML).
This commit is contained in:
@@ -152,7 +152,11 @@ object SkillParser {
|
||||
return buildString {
|
||||
append("---\n")
|
||||
append(yamlBody)
|
||||
append("---\n")
|
||||
// kaml encodeToString не добавляет завершающий перевод строки — без
|
||||
// явного `\n` закрывающий fence прилипает к последней YAML-строке
|
||||
// (`description: "..."---`) и [parse] больше не находит fence
|
||||
// (MissingClosingFence).
|
||||
append("\n---\n")
|
||||
if (body.isNotEmpty()) {
|
||||
append("\n")
|
||||
append(body)
|
||||
|
||||
@@ -181,7 +181,52 @@ class SkillParserTest {
|
||||
assertTrue(ok.value.description.contains("line two"))
|
||||
}
|
||||
|
||||
// --- parseAuto: fenced делегируется в parse, голый YAML трактуется как объект ---
|
||||
// --- serialize → parse round-trip (регрессия: кривой fence после serialize) ---
|
||||
|
||||
@Test
|
||||
fun serializeParseRoundTrip() {
|
||||
val skill = SkillFile(
|
||||
name = "greet:ru",
|
||||
description = "Приветствие по-русски",
|
||||
body = "# Greet RU\nКогда пользователь здоровается, отвечай тепло и кратко по-русски.",
|
||||
)
|
||||
val serialized = SkillParser.serialize(skill)
|
||||
// Закрывающий fence обязан быть на отдельной строке — иначе parse
|
||||
// не найдёт его (MissingClosingFence).
|
||||
assertTrue(
|
||||
"\n---" in serialized,
|
||||
"закрывающий fence должен начинаться с новой строки, got: $serialized",
|
||||
)
|
||||
val ok = assertIs<SkillParseResult.Ok>(SkillParser.parse(serialized))
|
||||
assertEquals("greet:ru", ok.value.name)
|
||||
assertEquals("Приветствие по-русски", ok.value.description)
|
||||
assertTrue(ok.value.body.startsWith("# Greet RU"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun serializeParseRoundTripEmptyBody() {
|
||||
val skill = SkillFile(name = "empty-body", description = "no body", body = "")
|
||||
val serialized = SkillParser.serialize(skill)
|
||||
val ok = assertIs<SkillParseResult.Ok>(SkillParser.parse(serialized))
|
||||
assertEquals("empty-body", ok.value.name)
|
||||
assertEquals("", ok.value.body)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun serializeParseRoundTripSpecialYamlChars() {
|
||||
val skill = SkillFile(
|
||||
name = "special",
|
||||
description = "С кавычками \"и\" колонкой: и двоеточие.",
|
||||
body = "line1\nline2",
|
||||
)
|
||||
val serialized = SkillParser.serialize(skill)
|
||||
val ok = assertIs<SkillParseResult.Ok>(SkillParser.parse(serialized))
|
||||
assertEquals(skill.description, ok.value.description)
|
||||
// parse.trimStart('\n') не режет хвостовой newline — сравниваем с trimEnd.
|
||||
assertEquals("line1\nline2", ok.value.body.trimEnd('\n'))
|
||||
}
|
||||
|
||||
// --- parseAuto ---
|
||||
|
||||
@Test
|
||||
fun parseAutoDelegatesFencedFormat() {
|
||||
|
||||
Reference in New Issue
Block a user