From f20170f45c7642756dc63cb73bd7a0857e0d84f7 Mon Sep 17 00:00:00 2001 From: deborre Date: Wed, 5 Aug 2026 00:15:53 +0100 Subject: [PATCH] fix: keep long frontmatter values on a single line `CommandRegistrar.render_frontmatter` calls `yaml.dump()` without `width=`, so PyYAML applies its default ~80-column wrap and folds any long scalar onto a continuation line. A `description` longer than roughly 80 characters is therefore rendered as: --- name: speckit-implement description: Execute the implementation plan by processing and executing all tasks defined in tasks.md --- The YAML remains valid and round-trips faithfully through `yaml.safe_load`, so this is not data loss. It is a shape inconsistency with real consequences: - Hand-written core command templates always keep `description` on one line, so preset- and extension-rendered commands do not match the files they sit beside in the same directory. - Consumers that read frontmatter line-wise rather than with a YAML parser see the description truncated at the fold, followed by a stray line. Spec Kit itself hand-builds SKILL.md frontmatter in the skills path (see #3391), so this is not a hypothetical class of consumer. - `speckit.implement`'s own description is 89 characters, so a preset that overrides it hits this immediately. `width=float("inf")` disables the line-wrapping only; escaping, quoting and the handling of genuinely multi-line values are unchanged, since PyYAML selects the scalar style before applying width. Adds a regression test that fails without the change. Verified against the repo's own suite: 6354 passed. Four failures in tests/integrations/test_integration_subcommand.py are present on a clean checkout too (ANSI escapes in captured output) and are unrelated. --- src/specify_cli/agents.py | 6 +++++- tests/test_extensions.py | 25 +++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/src/specify_cli/agents.py b/src/specify_cli/agents.py index 173f843e42..dede50e0b1 100644 --- a/src/specify_cli/agents.py +++ b/src/specify_cli/agents.py @@ -157,7 +157,11 @@ def render_frontmatter(fm: dict) -> str: return "" yaml_str = yaml.dump( - fm, default_flow_style=False, sort_keys=False, allow_unicode=True + fm, + default_flow_style=False, + sort_keys=False, + allow_unicode=True, + width=float("inf"), ) return f"---\n{yaml_str}---\n" diff --git a/tests/test_extensions.py b/tests/test_extensions.py index 616a1dfe12..9442f0bfbe 100644 --- a/tests/test_extensions.py +++ b/tests/test_extensions.py @@ -17,6 +17,7 @@ import tempfile import shutil import tomllib +import yaml from contextlib import contextmanager from pathlib import Path from datetime import datetime, timezone @@ -2996,6 +2997,30 @@ def test_render_frontmatter_unicode(self): assert "Prüfe Konformität" in output assert "\\u" not in output + def test_render_frontmatter_keeps_long_description_on_one_line(self): + """A long description must not be folded across lines. + + PyYAML wraps plain scalars at ~80 columns by default, which splits a + long ``description`` onto a continuation line. The YAML stays valid, + but the rendered frontmatter then differs in shape from the + hand-written core command templates, where ``description`` is always a + single line -- and consumers that read frontmatter line-wise see a + truncated description followed by a stray line. + """ + long_description = ( + "Execute the implementation plan by processing and executing all " + "tasks defined in tasks.md" + ) + frontmatter = {"name": "speckit-implement", "description": long_description} + + registrar = CommandRegistrar() + output = registrar.render_frontmatter(frontmatter) + + assert f"description: {long_description}\n" in output + + body = output.split("---\n")[1] + assert yaml.safe_load(body)["description"] == long_description + def test_adjust_script_paths_does_not_mutate_input(self): """Path adjustments should not mutate caller-owned frontmatter dicts.""" from specify_cli.agents import CommandRegistrar as AgentCommandRegistrar