Skip to content

Commit 7873c44

Browse files
authored
fix(agents): parse frontmatter on the --- delimiter line, not any --- substring (#3590)
CommandRegistrar.parse_frontmatter located the closing delimiter with content.find("---", 3), a raw substring search. It stopped at the first "---" anywhere after the opening — including one embedded in a frontmatter value (e.g. a description "Separate sections with --- markers") or inside an indented literal block — which truncated the frontmatter and spilled the remainder into the body, silently corrupting both the parsed metadata and the rendered command body. Match the closing "---" on line boundaries, mirroring the line-anchored scan already used by VibeIntegration._inject_frontmatter_flag.
1 parent eabfabb commit 7873c44

2 files changed

Lines changed: 31 additions & 5 deletions

File tree

src/specify_cli/agents.py

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -114,13 +114,24 @@ def parse_frontmatter(content: str) -> tuple[dict, str]:
114114
if not content.startswith("---"):
115115
return {}, content
116116

117-
# Find second ---
118-
end_marker = content.find("---", 3)
119-
if end_marker == -1:
117+
# The closing delimiter is a line that is exactly ``---`` (a YAML
118+
# document separator), not any ``---`` substring. Scanning with
119+
# ``content.find("---", 3)`` stops at the first ``---`` *anywhere* —
120+
# including one embedded in a frontmatter value (e.g. a description like
121+
# "Separate sections with ---") or inside an indented literal block —
122+
# which truncates the frontmatter and spills the remainder into the
123+
# body. Match on line boundaries instead, mirroring the line-anchored
124+
# scan in ``VibeIntegration._inject_frontmatter_flag``.
125+
lines = content.splitlines(keepends=True)
126+
end_line = next(
127+
(i for i in range(1, len(lines)) if lines[i].rstrip() == "---"),
128+
None,
129+
)
130+
if end_line is None:
120131
return {}, content
121132

122-
frontmatter_str = content[3:end_marker].strip()
123-
body = content[end_marker + 3 :].strip()
133+
frontmatter_str = "".join(lines[1:end_line]).strip()
134+
body = "".join(lines[end_line + 1 :]).strip()
124135

125136
try:
126137
frontmatter = yaml.safe_load(frontmatter_str) or {}

tests/test_extensions.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2418,6 +2418,21 @@ def test_parse_frontmatter_non_mapping_returns_empty_dict(self):
24182418
assert frontmatter == {}
24192419
assert "Command body" in body
24202420

2421+
def test_parse_frontmatter_dash_in_value(self):
2422+
"""A ``---`` inside a frontmatter value must not close the block early."""
2423+
content = """---
2424+
description: Separate sections with --- markers
2425+
argument-hint: "[name]"
2426+
---
2427+
Real body starts here.
2428+
"""
2429+
registrar = CommandRegistrar()
2430+
frontmatter, body = registrar.parse_frontmatter(content)
2431+
2432+
assert frontmatter["description"] == "Separate sections with --- markers"
2433+
assert frontmatter["argument-hint"] == "[name]"
2434+
assert body == "Real body starts here."
2435+
24212436
def test_render_frontmatter(self):
24222437
"""Test rendering frontmatter to YAML."""
24232438
frontmatter = {

0 commit comments

Comments
 (0)