From 7f08f31286e02c1f75e4a0053bee5df504746b12 Mon Sep 17 00:00:00 2001 From: Hamilton Snow Date: Mon, 6 Apr 2026 21:39:01 +0800 Subject: [PATCH] [stage2] fix: serialize multiline descriptions in legacy TOML renderer (#2097) * fix: preserve multiline descriptions in legacy toml renderer * refactor: reuse toml escape helper for prompt fallback --- src/specify_cli/agents.py | 26 ++++++++++++++++---------- tests/test_extensions.py | 16 ++++++++++++++++ 2 files changed, 32 insertions(+), 10 deletions(-) diff --git a/src/specify_cli/agents.py b/src/specify_cli/agents.py index bb25b3fc1..4b869283c 100644 --- a/src/specify_cli/agents.py +++ b/src/specify_cli/agents.py @@ -191,8 +191,9 @@ class CommandRegistrar: toml_lines = [] if "description" in frontmatter: - desc = frontmatter["description"].replace('"', '\\"') - toml_lines.append(f'description = "{desc}"') + toml_lines.append( + f'description = {self._render_basic_toml_string(frontmatter["description"])}' + ) toml_lines.append("") toml_lines.append(f"# Source: {source_id}") @@ -209,17 +210,22 @@ class CommandRegistrar: toml_lines.append(body) toml_lines.append("'''") else: - escaped_body = ( - body.replace("\\", "\\\\") - .replace('"', '\\"') - .replace("\n", "\\n") - .replace("\r", "\\r") - .replace("\t", "\\t") - ) - toml_lines.append(f'prompt = "{escaped_body}"') + toml_lines.append(f"prompt = {self._render_basic_toml_string(body)}") return "\n".join(toml_lines) + @staticmethod + def _render_basic_toml_string(value: str) -> str: + """Render *value* as a TOML basic string literal.""" + escaped = ( + value.replace("\\", "\\\\") + .replace('"', '\\"') + .replace("\n", "\\n") + .replace("\r", "\\r") + .replace("\t", "\\t") + ) + return f'"{escaped}"' + def render_skill_command( self, agent_name: str, diff --git a/tests/test_extensions.py b/tests/test_extensions.py index 350b368ea..6ebb39789 100644 --- a/tests/test_extensions.py +++ b/tests/test_extensions.py @@ -13,6 +13,7 @@ import pytest import json import tempfile import shutil +import tomllib from pathlib import Path from datetime import datetime, timezone @@ -1014,6 +1015,21 @@ $ARGUMENTS assert "\\n" in output assert "\\\"\\\"\\\"" in output + def test_render_toml_command_preserves_multiline_description(self): + """Multiline descriptions should render as parseable TOML with preserved semantics.""" + from specify_cli.agents import CommandRegistrar as AgentCommandRegistrar + + registrar = AgentCommandRegistrar() + output = registrar.render_toml_command( + {"description": "first line\nsecond line\n"}, + "body", + "extension:test-ext", + ) + + parsed = tomllib.loads(output) + + assert parsed["description"] == "first line\nsecond line\n" + def test_register_commands_for_claude(self, extension_dir, project_dir): """Test registering commands for Claude agent.""" # Create .claude directory