fix(skills): support parsing multiline YAML strings in SKILL.md frontmatter (#1703)

* fix(skills): support parsing multiline YAML strings in SKILL.md frontmatter

* test(skills): add tests for multiline YAML descriptions
This commit is contained in:
Alian 2026-04-01 23:08:30 +08:00 committed by GitHub
parent 68d44f6755
commit e97c8c9943
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 83 additions and 5 deletions

View File

@ -33,15 +33,72 @@ def parse_skill_file(skill_file: Path, category: str, relative_path: Path | None
front_matter = front_matter_match.group(1)
# Parse YAML front matter (simple key-value parsing)
# Parse YAML front matter with basic multiline string support
metadata = {}
for line in front_matter.split("\n"):
line = line.strip()
if not line:
lines = front_matter.split("\n")
current_key = None
current_value = []
is_multiline = False
multiline_style = None
indent_level = None
for line in lines:
if is_multiline:
if not line.strip():
current_value.append("")
continue
current_indent = len(line) - len(line.lstrip())
if indent_level is None:
if current_indent > 0:
indent_level = current_indent
current_value.append(line[indent_level:])
continue
elif current_indent >= indent_level:
current_value.append(line[indent_level:])
continue
# If we reach here, it's either a new key or the end of multiline
if current_key and is_multiline:
if multiline_style == "|":
metadata[current_key] = "\n".join(current_value).rstrip()
else:
text = "\n".join(current_value).rstrip()
# Replace single newlines with spaces for folded blocks
metadata[current_key] = re.sub(r"(?<!\n)\n(?!\n)", " ", text)
current_key = None
current_value = []
is_multiline = False
multiline_style = None
indent_level = None
if not line.strip():
continue
if ":" in line:
# Handle nested dicts simply by ignoring indentation for now,
# or just extracting top-level keys
key, value = line.split(":", 1)
metadata[key.strip()] = value.strip()
key = key.strip()
value = value.strip()
if value in (">", "|"):
current_key = key
is_multiline = True
multiline_style = value
current_value = []
indent_level = None
else:
metadata[key] = value
if current_key and is_multiline:
if multiline_style == "|":
metadata[current_key] = "\n".join(current_value).rstrip()
else:
text = "\n".join(current_value).rstrip()
metadata[current_key] = re.sub(r"(?<!\n)\n(?!\n)", " ", text)
# Extract required fields
name = metadata.get("name")

View File

@ -93,6 +93,27 @@ class TestParseSkillFile:
assert result is not None
assert result.description == "A skill: does things"
def test_multiline_yaml_folded_description(self, tmp_path):
skill_file = _write_skill(
tmp_path,
"---\nname: multiline-skill\ndescription: >\n This is a multiline\n description for a skill.\n\n It spans multiple lines.\nlicense: MIT\n---\n\nBody\n",
)
result = parse_skill_file(skill_file, "public")
assert result is not None
assert result.name == "multiline-skill"
assert result.description == "This is a multiline description for a skill.\n\nIt spans multiple lines."
assert result.license == "MIT"
def test_multiline_yaml_literal_description(self, tmp_path):
skill_file = _write_skill(
tmp_path,
"---\nname: pipe-skill\ndescription: |\n First line.\n Second line.\n---\n\nBody\n",
)
result = parse_skill_file(skill_file, "public")
assert result is not None
assert result.name == "pipe-skill"
assert result.description == "First line.\nSecond line."
def test_empty_front_matter_returns_none(self, tmp_path):
skill_file = _write_skill(tmp_path, "---\n\n---\n\nBody\n")
assert parse_skill_file(skill_file, "public") is None