Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
A comprehensive collection of Agent Skills for context engineering, multi-agent architectures, and production agent systems.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
researcher/scripts/tests/test_skill_frontmatter.py
1"""Unit tests for the shared SKILL.md frontmatter parser.23Run directly or via the standard library test runner:45python3 -m unittest researcher.scripts.tests.test_skill_frontmatter6python3 researcher/scripts/tests/test_skill_frontmatter.py7"""89from __future__ import annotations1011import sys12import unittest13from pathlib import Path1415SCRIPTS_DIR = Path(__file__).resolve().parents[1]16REPO_ROOT = SCRIPTS_DIR.parents[1]17if str(SCRIPTS_DIR) not in sys.path:18sys.path.insert(0, str(SCRIPTS_DIR))1920import skill_frontmatter as sf # noqa: E402212223VALID_DESC = "A valid description that is comfortably longer than the minimum."242526class SplitFrontmatterTests(unittest.TestCase):27def test_lf(self) -> None:28inner, body = sf.split_frontmatter(f"---\nname: foo\ndescription: {VALID_DESC}\n---\n# Body\ntext")29self.assertIn("name: foo", inner)30self.assertEqual(body, "# Body\ntext")3132def test_crlf(self) -> None:33text = f"---\r\nname: foo\r\ndescription: {VALID_DESC}\r\n---\r\n# Body\r\ntext"34inner, body = sf.split_frontmatter(text)35self.assertIn("name: foo", inner)36self.assertFalse(inner.endswith("\r"))37self.assertEqual(body, "# Body\r\ntext")3839def test_bom(self) -> None:40text = f"\ufeff---\nname: foo\ndescription: {VALID_DESC}\n---\nbody"41inner, body = sf.split_frontmatter(text)42self.assertIn("name: foo", inner)43self.assertEqual(body, "body")4445def test_no_frontmatter(self) -> None:46inner, body = sf.split_frontmatter("# Heading\nno frontmatter")47self.assertIsNone(inner)48self.assertEqual(body, "# Heading\nno frontmatter")4950def test_unterminated_frontmatter(self) -> None:51inner, _ = sf.split_frontmatter("---\nname: foo\nnever closes")52self.assertIsNone(inner)5354def test_longer_dash_line_is_not_a_closing_fence(self) -> None:55text = f"---\nname: foo\ndescription: {VALID_DESC}\n------\nbody"56inner, _ = sf.split_frontmatter(text)57self.assertIsNone(inner)5859def test_closing_fence_allows_trailing_spaces(self) -> None:60text = f"---\nname: foo\ndescription: {VALID_DESC}\n--- \nbody"61inner, body = sf.split_frontmatter(text)62self.assertIn("name: foo", inner)63self.assertEqual(body, "body")646566class ParseFrontmatterTests(unittest.TestCase):67def test_valid_plain(self) -> None:68data, issues = sf.parse_frontmatter(f"---\nname: foo\ndescription: {VALID_DESC}\n---\nbody")69self.assertEqual(issues, [])70self.assertEqual(data["name"], "foo")71self.assertEqual(data["description"], VALID_DESC)7273def test_quoted_description_with_colon(self) -> None:74text = '---\nname: foo\ndescription: "This skill: handles colons safely and clearly."\n---\nbody'75data, issues = sf.parse_frontmatter(text)76self.assertEqual(issues, [])77self.assertEqual(data["description"], "This skill: handles colons safely and clearly.")7879def test_block_scalar_description(self) -> None:80text = "---\nname: foo\ndescription: >\n multi line\n description text that is long enough\n---\nbody"81data, issues = sf.parse_frontmatter(text)82self.assertEqual(issues, [])83self.assertIn("multi line description", data["description"])8485def test_missing_frontmatter(self) -> None:86_data, issues = sf.parse_frontmatter("# No frontmatter")87self.assertTrue(any("delimiter" in i for i in issues))8889def test_missing_name(self) -> None:90_data, issues = sf.parse_frontmatter(f"---\ndescription: {VALID_DESC}\n---\nbody")91self.assertIn("missing name", issues)9293def test_missing_description(self) -> None:94_data, issues = sf.parse_frontmatter("---\nname: foo\n---\nbody")95self.assertIn("missing description", issues)9697def test_short_description(self) -> None:98_data, issues = sf.parse_frontmatter("---\nname: foo\ndescription: too short\n---\nbody")99self.assertTrue(any("too short" in i for i in issues))100101def test_non_mapping_frontmatter(self) -> None:102if sf.yaml is None:103self.skipTest("PyYAML required for mapping validation")104_data, issues = sf.parse_frontmatter("---\n- just\n- a\n- list\n---\nbody")105self.assertTrue(any("mapping" in i for i in issues))106107def test_non_string_name_is_rejected(self) -> None:108_data, issues = sf.parse_frontmatter(f"---\nname: true\ndescription: {VALID_DESC}\n---\nbody")109self.assertTrue(any("name must be a string" in i for i in issues), issues)110111def test_non_string_description_is_rejected(self) -> None:112_data, issues = sf.parse_frontmatter("---\nname: foo\ndescription: true\n---\nbody")113self.assertTrue(any("description must be a string" in i for i in issues), issues)114115116@unittest.skipIf(sf.yaml is None, "PyYAML required for strict-YAML regression test")117class StrictYamlRegressionTests(unittest.TestCase):118"""Guards the exact bug fixed in this PR: unquoted colons in descriptions."""119120def test_unquoted_colon_is_rejected(self) -> None:121text = "---\nname: foo\ndescription: This skill: does several things without quoting\n---\nbody"122_data, issues = sf.parse_frontmatter(text)123self.assertTrue(any("invalid YAML" in i for i in issues), issues)124125126class FallbackParserTests(unittest.TestCase):127def test_fallback_plain(self) -> None:128inner = f"name: foo\ndescription: {VALID_DESC}"129data, issues = sf._parse_frontmatter_fallback(inner, [])130self.assertEqual(issues, [])131self.assertEqual(data["description"], VALID_DESC)132133def test_fallback_block_scalar(self) -> None:134inner = "name: foo\ndescription: >\n multi line\n description long enough now"135data, issues = sf._parse_frontmatter_fallback(inner, [])136self.assertEqual(issues, [])137self.assertIn("multi line description", data["description"])138139140class FormatFrontmatterTests(unittest.TestCase):141def test_round_trip_with_colon(self) -> None:142desc = "This skill: handles colons, commas, and 'quotes' all at once."143rendered = sf.format_frontmatter("foo-bar", desc)144data, issues = sf.parse_frontmatter(rendered + "body")145self.assertEqual(issues, [])146self.assertEqual(data["name"], "foo-bar")147self.assertEqual(data["description"], desc)148149150class CorpusIntegrationTests(unittest.TestCase):151"""Every published skill must parse cleanly with zero issues."""152153def test_all_skills_parse_clean(self) -> None:154skills_dir = REPO_ROOT / "skills"155skill_files = sorted(skills_dir.glob("*/SKILL.md"))156self.assertTrue(skill_files, "no skills found")157for path in skill_files:158with self.subTest(skill=path.parent.name):159text = path.read_text(encoding="utf-8")160data, issues = sf.parse_frontmatter(text)161self.assertEqual(issues, [], f"{path.parent.name}: {issues}")162self.assertEqual(data["name"], path.parent.name)163self.assertRegex(text, r'\ndescription: "')164165@unittest.skipIf(sf.yaml is None, "PyYAML required for strict example validation")166def test_example_and_template_frontmatter_is_strict_yaml(self) -> None:167"""Demo and template skills must be strict-YAML parseable so developers can copy them."""168extra = [REPO_ROOT / "template" / "SKILL.md", REPO_ROOT / "SKILL.md"]169example_skills = sorted((REPO_ROOT / "examples").glob("**/SKILL.md"))170for path in extra + example_skills:171if not path.exists():172continue173with self.subTest(skill=str(path.relative_to(REPO_ROOT))):174text = path.read_text(encoding="utf-8")175inner, _ = sf.split_frontmatter(text)176self.assertIsNotNone(inner, f"{path} missing frontmatter")177loaded = sf.yaml.safe_load(inner)178self.assertIsInstance(loaded, dict)179self.assertTrue(str(loaded.get("description", "")).strip())180self.assertRegex(text, r'\ndescription: "')181182183if __name__ == "__main__":184unittest.main()185