diff --git a/backend/packages/harness/deerflow/skills/parser.py b/backend/packages/harness/deerflow/skills/parser.py index b7cb89761..d2a3af67b 100644 --- a/backend/packages/harness/deerflow/skills/parser.py +++ b/backend/packages/harness/deerflow/skills/parser.py @@ -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"(?", "|"): + 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 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