fix: keep long frontmatter values on a single line (#3989)

`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.
This commit is contained in:
deborre
2026-08-05 08:38:26 -05:00
committed by GitHub
parent 0ecb277f0e
commit 4d5458c883
2 changed files with 30 additions and 1 deletions
+5 -1
View File
@@ -157,7 +157,11 @@ class CommandRegistrar:
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"
+25
View File
@@ -17,6 +17,7 @@ import platform
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 @@ Real body starts here.
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