diff --git a/strix/skills/__init__.py b/strix/skills/__init__.py index 334dd8ee..ccfe49dc 100644 --- a/strix/skills/__init__.py +++ b/strix/skills/__init__.py @@ -4,9 +4,6 @@ import threading from collections import Counter from collections.abc import Iterator from pathlib import Path -from typing import cast - -import yaml from strix.telemetry import posthog, scarf from strix.utils.resource_paths import get_strix_resource_path @@ -15,6 +12,7 @@ from strix.utils.resource_paths import get_strix_resource_path logger = logging.getLogger(__name__) _FRONTMATTER_PATTERN = re.compile(r"^---\s*\n.*?\n---\s*\n", re.DOTALL) +_FRONTMATTER_LINE_PATTERN = re.compile(r"^(?P[A-Za-z_][\w-]*):(?:[ \t]*(?P.*))?$") _INTERNAL_SKILL_CATEGORIES: frozenset[str] = frozenset({"scan_modes", "coordination"}) _ROOT_SKILL_CATEGORY = "root" @@ -160,33 +158,16 @@ def _parse_skill_content(content: str) -> tuple[dict[str, str], str]: if frontmatter is None: return {}, content.lstrip() - try: - metadata_text = frontmatter.group(0)[4:].rsplit("\n---", 1)[0] - try: - metadata_value: object = yaml.safe_load(metadata_text) or {} - except yaml.YAMLError: - # Preserve the historical support for an unquoted colon in a - # description while accepting full YAML for other frontmatter. - normalized_lines: list[str] = [] - for line in metadata_text.splitlines(): - if line.startswith("description: ") and not line.startswith( - ('description: "', "description: '") - ): - value = line.removeprefix("description: ") - value = value.replace("\\", "\\\\").replace('"', '\\"') - normalized_lines.append(f'description: "{value}"') - else: - normalized_lines.append(line) - metadata_value = yaml.safe_load("\n".join(normalized_lines)) or {} - except yaml.YAMLError: - logger.warning("Invalid skill frontmatter") - metadata_value = {} - metadata = ( - cast("dict[object, object]", metadata_value) if isinstance(metadata_value, dict) else {} - ) - - normalized = {str(key): "" if value is None else str(value) for key, value in metadata.items()} - return normalized, content[frontmatter.end() :].lstrip() + metadata: dict[str, str] = {} + for line in frontmatter.group(0).splitlines()[1:]: + match = _FRONTMATTER_LINE_PATTERN.match(line) + if match is None: + continue + value = match.group("value") or "" + if len(value) >= 2 and value[0] == value[-1] and value[0] in "\"'": + value = value[1:-1] + metadata[match.group("key")] = value + return metadata, content[frontmatter.end() :].lstrip() def _read_skill_metadata(file_path: Path) -> dict[str, str]: diff --git a/tests/test_skill_dir_extension.py b/tests/test_skill_dir_extension.py index 88532da0..1c649c0d 100644 --- a/tests/test_skill_dir_extension.py +++ b/tests/test_skill_dir_extension.py @@ -98,7 +98,7 @@ def test_available_skill_supports_colon_in_description(tmp_path: Path) -> None: ] -def test_available_skill_normalizes_quoted_multiline_description(tmp_path: Path) -> None: +def test_available_skill_normalizes_quoted_description(tmp_path: Path) -> None: _write_skill( tmp_path, "extra", @@ -112,20 +112,6 @@ def test_available_skill_normalizes_quoted_multiline_description(tmp_path: Path) ] -def test_available_skill_normalizes_block_description(tmp_path: Path) -> None: - _write_skill( - tmp_path, - "extra", - "widget", - "---\nname: widget\ndescription: |\n First line\n Second line\n---\nwidget body", - ) - register_skill_dir(tmp_path) - - assert get_available_skills()["extra"] == [ - {"name": "widget", "description": "First line Second line"} - ] - - def test_system_prompt_renders_skill_descriptions() -> None: prompt = render_system_prompt(scan_mode="quick", is_root=True)