diff --git a/packages/skillport-core/pyproject.toml b/packages/skillport-core/pyproject.toml index a42c9a3..94368c9 100644 --- a/packages/skillport-core/pyproject.toml +++ b/packages/skillport-core/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "skillport-core" -version = "1.0.1" +version = "1.1.0" description = "Core SkillPort library (shared logic). See https://github.com/gotalab/skillport for documentation." requires-python = ">=3.10" license = "MIT" diff --git a/packages/skillport-core/src/skillport/interfaces/cli/commands/meta.py b/packages/skillport-core/src/skillport/interfaces/cli/commands/meta.py index 3555208..c99f051 100644 --- a/packages/skillport-core/src/skillport/interfaces/cli/commands/meta.py +++ b/packages/skillport-core/src/skillport/interfaces/cli/commands/meta.py @@ -134,19 +134,55 @@ def _load_frontmatter(skill_md: Path) -> tuple[dict[str, Any], str]: return meta, body -class _QuotedStringDumper(yaml.SafeDumper): +class _FrontmatterStr(str): pass -def _represent_str_quoted(dumper: yaml.Dumper, data: str) -> yaml.ScalarNode: +class _FrontmatterDumper(yaml.SafeDumper): + pass + + +def _represent_frontmatter_str(dumper: yaml.Dumper, data: _FrontmatterStr) -> yaml.ScalarNode: return dumper.represent_scalar("tag:yaml.org,2002:str", data, style='"') -_QuotedStringDumper.add_representer(str, _represent_str_quoted) +_FrontmatterDumper.add_representer(_FrontmatterStr, _represent_frontmatter_str) + + +def _should_quote_string(value: str) -> bool: + if "\n" in value or "\r" in value: + return True + if value == "" or value.strip() != value: + return True + try: + loaded = yaml.safe_load(value) + except yaml.YAMLError: + return True + return not isinstance(loaded, str) or loaded != value + + +def _prepare_frontmatter_for_dump(value: Any) -> Any: + if isinstance(value, dict): + return {k: _prepare_frontmatter_for_dump(v) for k, v in value.items()} + if isinstance(value, list): + return [_prepare_frontmatter_for_dump(v) for v in value] + if isinstance(value, tuple): + return [_prepare_frontmatter_for_dump(v) for v in value] + if isinstance(value, str) and _should_quote_string(value): + return _FrontmatterStr(value) + return value def _write_frontmatter(skill_md: Path, meta: dict[str, Any], body: str) -> None: - meta_text = yaml.dump(meta, sort_keys=False, Dumper=_QuotedStringDumper).strip() + prepared = _prepare_frontmatter_for_dump(meta) + meta_text = yaml.dump( + prepared, + sort_keys=False, + default_flow_style=False, + allow_unicode=True, + width=1_000_000, + Dumper=_FrontmatterDumper, + ).strip() cleaned_body = body.lstrip("\n") content = f"---\n{meta_text}\n---\n{cleaned_body}" skill_md.write_text(content, encoding="utf-8") @@ -357,7 +393,13 @@ def _emit_show_results( continue console.print(f"[skill.id]{skill_id}[/skill.id]") payload = {"metadata": result["metadata"]} - yaml_text = yaml.safe_dump(payload, sort_keys=False).rstrip() + yaml_text = yaml.safe_dump( + payload, + sort_keys=False, + default_flow_style=False, + allow_unicode=True, + width=1_000_000, + ).rstrip() console.print(yaml_text) console.print() diff --git a/packages/skillport-mcp/pyproject.toml b/packages/skillport-mcp/pyproject.toml index 7b94766..f3edf70 100644 --- a/packages/skillport-mcp/pyproject.toml +++ b/packages/skillport-mcp/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "skillport-mcp" -version = "1.0.1" +version = "1.1.0" description = "SkillPort MCP server (indexed search + tools). See https://github.com/gotalab/skillport for documentation." requires-python = ">=3.10" license = "MIT" @@ -23,7 +23,7 @@ classifiers = [ "Programming Language :: Python :: 3.14", ] dependencies = [ - "skillport-core==1.0.1", + "skillport-core==1.1.0", "fastmcp>=2.0.0", "lancedb>=0.26.0", "openai>=1.0.0", diff --git a/pyproject.toml b/pyproject.toml index 1f9f1f1..4fc382e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,7 +24,7 @@ classifiers = [ "Programming Language :: Python :: 3.14", ] dependencies = [ - "skillport-core==1.0.1", + "skillport-core==1.1.0", "typer>=0.4.0", "rich>=10.4.0", ] @@ -70,7 +70,12 @@ Repository = "https://github.com/gotalab/skillport" skillport = "skillport.interfaces.cli.app:run" [tool.uv.sources] -skillport-core = { path = "packages/skillport-core" } +skillport-core = { workspace = true } + +[tool.uv.workspace] +members = [ + "packages/skillport-core", +] [tool.ruff] target-version = "py310" diff --git a/tests/unit/test_meta_frontmatter_format.py b/tests/unit/test_meta_frontmatter_format.py new file mode 100644 index 0000000..98677ab --- /dev/null +++ b/tests/unit/test_meta_frontmatter_format.py @@ -0,0 +1,34 @@ +from pathlib import Path + +from skillport.interfaces.cli.commands.meta import _write_frontmatter +from skillport.shared.utils import parse_frontmatter + + +def test_write_frontmatter_is_readable_yaml(tmp_path: Path): + skill_md = tmp_path / "SKILL.md" + meta = { + "name": "anthropic-design", + "description": ( + "Applies warm, human-centered brand design with earth-tone colors (#C96442 accent, " + "cream backgrounds) and serif/sans-serif typography pairing. Use when creating " + "React/HTML artifacts, dashboards, landing pages, or UI components requiring a " + "trustworthy, approachable aesthetic. Triggers: Anthropic style, Claude branding, " + "warm design, brand colors, professional theme, human-centered UI, earth tones." + ), + "metadata": {"author": "gota"}, + } + body = "# Skill\n\nHello.\n" + + _write_frontmatter(skill_md, meta, body) + + text = skill_md.read_text(encoding="utf-8") + assert text.startswith("---\n") + assert '"name":' not in text + assert '"description":' not in text + assert '"metadata":' not in text + assert "\\\n" not in text # avoid PyYAML double-quoted line-wrap continuation + + parsed_meta, parsed_body = parse_frontmatter(skill_md) + assert parsed_meta == meta + assert parsed_body == body + diff --git a/uv.lock b/uv.lock index 2919c3e..a613343 100644 --- a/uv.lock +++ b/uv.lock @@ -7,6 +7,12 @@ resolution-markers = [ "python_full_version < '3.10.2'", ] +[manifest] +members = [ + "skillport", + "skillport-core", +] + [[package]] name = "annotated-types" version = "0.7.0" @@ -1857,7 +1863,7 @@ dev = [ [package.metadata] requires-dist = [ { name = "rich", specifier = ">=10.4.0" }, - { name = "skillport-core", directory = "packages/skillport-core" }, + { name = "skillport-core", editable = "packages/skillport-core" }, { name = "typer", specifier = ">=0.4.0" }, ] @@ -1877,8 +1883,8 @@ dev = [ [[package]] name = "skillport-core" -version = "1.0.1" -source = { directory = "packages/skillport-core" } +version = "1.1.0" +source = { editable = "packages/skillport-core" } dependencies = [ { name = "pydantic" }, { name = "pydantic-settings" },