# retoor import pytest from devplacepy.services.skills import limits from devplacepy.services.skills.parser import SkillFormatError, parse_skill_md def skill(frontmatter: str, body: str = "# Body\n\nDo the thing.") -> str: return f"---\n{frontmatter}\n---\n{body}" def test_minimal_skill_parses(): parsed = parse_skill_md(skill("name: my-skill\ndescription: Does a thing.")) assert parsed.name == "my-skill" assert parsed.description == "Does a thing." assert parsed.body.startswith("# Body") assert parsed.metadata == {} assert parsed.warnings == () def test_all_known_fields_parse(): parsed = parse_skill_md( skill( "name: full\n" "description: Full skill\n" "license: MIT\n" "compatibility: any agent\n" "allowed-tools: Read Write\n" "metadata:\n" " author: someone\n" " version: '1.0'" ) ) assert parsed.license == "MIT" assert parsed.compatibility == "any agent" assert parsed.allowed_tools == "Read Write" assert parsed.metadata == {"author": "someone", "version": "1.0"} def test_block_scalars(): folded = parse_skill_md(skill("name: a\ndescription: >\n first line\n second line")) assert folded.description == "first line second line" literal = parse_skill_md(skill("name: a\ndescription: |\n first\n second")) assert literal.description == "first\nsecond" def test_quoted_values_and_inline_comments(): parsed = parse_skill_md(skill('name: a\ndescription: "Quoted: value" ')) assert parsed.description == "Quoted: value" parsed = parse_skill_md(skill("name: a\ndescription: plain # a comment")) assert parsed.description == "plain" def test_unknown_key_is_ignored_with_a_warning(): parsed = parse_skill_md(skill("name: a\ndescription: d\nfuture-key: x")) assert parsed.warnings == ("Ignored unknown frontmatter key 'future-key'.",) def test_bom_and_crlf_are_accepted(): text = "---\r\nname: a\r\ndescription: d\r\n---\r\nBody" assert parse_skill_md(text).body == "Body" @pytest.mark.parametrize( "text,fragment", [ ("name: a\ndescription: d", "no YAML frontmatter"), ("---\nname: a\ndescription: d\n", "not closed"), (skill("description: d"), "'name' is required"), (skill("name: a"), "'description' is required"), (skill("name: A_B\ndescription: d"), "lowercase letters"), (skill("name: -a\ndescription: d"), "lowercase letters"), (skill("name: a--b\ndescription: d"), "lowercase letters"), (skill("name: a\ndescription: d\nname: b"), "duplicate frontmatter key"), (skill("name: a\ndescription: [x]"), "flow collections"), (skill("name: a\ndescription: d\nmetadata: x"), "indented map"), (skill("name: a\ndescription: d\nmetadata:\n a: 1\n b: 2"), "flat map"), (skill("name: a\ndescription: d\nmetadata:\n a: 1\n a: 2"), "duplicate metadata key"), (skill("name: a\ndescription: \"unterminated"), "unterminated"), (skill("name: a\ndescription: d\n\tbad: x"), "tab"), (skill("name: a\ndescription: d") + "\x00", "NUL"), ], ) def test_malformed_skills_are_rejected_with_a_reason(text, fragment): with pytest.raises(SkillFormatError) as error: parse_skill_md(text) assert fragment in str(error.value) def test_overlong_fields_are_rejected(): with pytest.raises(SkillFormatError): parse_skill_md(skill(f"name: {'a' * (limits.NAME_MAX_CHARS + 1)}\ndescription: d")) with pytest.raises(SkillFormatError): parse_skill_md(skill(f"name: a\ndescription: {'d' * (limits.DESCRIPTION_MAX_CHARS + 1)}")) def test_metadata_entry_cap(): entries = "\n".join(f" k{i}: v" for i in range(limits.METADATA_MAX_ENTRIES + 1)) with pytest.raises(SkillFormatError): parse_skill_md(skill(f"name: a\ndescription: d\nmetadata:\n{entries}")) def test_frontmatter_scan_is_bounded(): padding = "\n".join("# c" for _ in range(limits.FRONTMATTER_MAX_LINES + 5)) with pytest.raises(SkillFormatError): parse_skill_md(f"---\nname: a\ndescription: d\n{padding}\n---\nBody")