From dc303222acf9ecb092f3f07b20ef38c5bb117af7 Mon Sep 17 00:00:00 2001 From: ciregenz Date: Sun, 16 Aug 2026 01:53:28 -0700 Subject: [PATCH] [eric] skills: frontmatter parser reads YAML block scalars, so a |- description no longer renders literally (ENG-307) --- .../skill_registry/skill_registry_github.py | 22 +++++++++--- backend/apps/skills/skills.py | 14 ++------ .../tests/test_frontmatter_block_scalars.py | 35 +++++++++++++++++++ 3 files changed, 55 insertions(+), 16 deletions(-) create mode 100644 backend/tests/test_frontmatter_block_scalars.py diff --git a/backend/apps/skill_registry/skill_registry_github.py b/backend/apps/skill_registry/skill_registry_github.py index d867c70a..4a560647 100644 --- a/backend/apps/skill_registry/skill_registry_github.py +++ b/backend/apps/skill_registry/skill_registry_github.py @@ -23,10 +23,24 @@ def parse_frontmatter(raw: str) -> tuple[dict, str]: fm_block = raw[3:end].strip() body = raw[end + 3:].strip() meta: dict = {} - for line in fm_block.splitlines(): - m = re.match(r"^(\w[\w_-]*)\s*:\s*(.+)$", line) - if m: - meta[m.group(1).strip()] = m.group(2).strip().strip('"').strip("'") + lines = fm_block.splitlines() + i = 0 + while i < len(lines): + m = re.match(r"^(\w[\w_-]*)\s*:\s*(.*)$", lines[i]) + i += 1 + if not m: + continue + key, val = m.group(1).strip(), m.group(2).strip() + if re.fullmatch(r"[|>][+-]?", val): + # YAML block scalar: the text is the indented lines below; the bare indicator once shipped as a card's whole description (ENG-307). + block: list[str] = [] + while i < len(lines) and (not lines[i].strip() or lines[i][0] in (" ", "\t")): + block.append(lines[i].strip()) + i += 1 + joiner = "\n" if val[0] == "|" else " " + meta[key] = joiner.join(b for b in block if b).strip() + else: + meta[key] = val.strip('"').strip("'") return meta, body diff --git a/backend/apps/skills/skills.py b/backend/apps/skills/skills.py index 878252fe..70572b95 100644 --- a/backend/apps/skills/skills.py +++ b/backend/apps/skills/skills.py @@ -12,6 +12,7 @@ import time import zipfile from contextlib import asynccontextmanager from fastapi import HTTPException +from backend.apps.skill_registry.skill_registry_github import parse_frontmatter from backend.config.Apps import SubApp from backend.apps.skills.models import Skill, SkillCreate, SkillLoadRequest, SkillUpdate, SkillUpload, SkillWorkspaceSeedRequest @@ -339,18 +340,7 @@ async def load_skill(body: SkillLoadRequest): def p_parse_skill_frontmatter(raw: str) -> dict: """Extract YAML frontmatter fields from a SKILL.md file.""" - if not raw.startswith("---"): - return {} - end = raw.find("---", 3) - if end == -1: - return {} - fm_block = raw[3:end].strip() - meta: dict = {} - for line in fm_block.splitlines(): - m = re.match(r"^(\w[\w_-]*)\s*:\s*(.+)$", line) - if m: - meta[m.group(1).strip()] = m.group(2).strip().strip('"').strip("'") - return meta + return parse_frontmatter(raw)[0] @skills.router.post("/workspace/seed") diff --git a/backend/tests/test_frontmatter_block_scalars.py b/backend/tests/test_frontmatter_block_scalars.py new file mode 100644 index 00000000..1f4f9b36 --- /dev/null +++ b/backend/tests/test_frontmatter_block_scalars.py @@ -0,0 +1,35 @@ +"""A SKILL.md whose description uses a YAML block scalar rendered as the literal string "|-" on +its marketplace card (ENG-307, seen live on /claude-api). The line-regex parser matched the +indicator as the value and dropped the indented block below it. One parser now serves both the +registry and the upload path, and it reads block scalars. +""" +from backend.apps.skill_registry.skill_registry_github import parse_frontmatter +from backend.apps.skills.skills import p_parse_skill_frontmatter + + +def test_literal_block_scalar_reads_the_indented_text(): + meta, body = parse_frontmatter("---\nname: claude-api\ndescription: |-\n Talk to the API\n with retries.\n---\nBody") + assert meta["description"] == "Talk to the API\nwith retries." + assert meta["name"] == "claude-api" + assert body == "Body" + + +def test_folded_scalar_joins_with_spaces(): + meta, _ = parse_frontmatter("---\ndescription: >-\n One line\n folded.\n---\n") + assert meta["description"] == "One line folded." + + +def test_plain_and_quoted_values_are_unchanged(): + meta, _ = parse_frontmatter('---\nname: "quoted"\nlicense: MIT\n---\n') + assert meta == {"name": "quoted", "license": "MIT"} + + +def test_empty_block_scalar_yields_empty_not_the_indicator(): + meta, _ = parse_frontmatter("---\ndescription: |-\nname: x\n---\n") + assert meta["description"] == "" + assert meta["name"] == "x" + + +def test_upload_path_uses_the_same_parser(): + meta = p_parse_skill_frontmatter("---\ndescription: |-\n Uploaded skill.\n---\n") + assert meta["description"] == "Uploaded skill."