Fix Alquimia argument hints after folded descriptions (#4063)
Co-authored-by: root <kinsonnee@gmail.com>
This commit is contained in:
@@ -65,7 +65,16 @@ class AlquimiaAIIntegration(SkillsIntegration):
|
||||
|
||||
@staticmethod
|
||||
def inject_argument_hint(content: str, hint: str) -> str:
|
||||
"""Insert ``argument-hint`` after the first ``description:`` in YAML frontmatter.
|
||||
"""Insert ``argument-hint`` after the ``description:`` scalar in YAML frontmatter.
|
||||
|
||||
A long ``description`` gets folded by the YAML dumper across
|
||||
indented continuation lines (plain or quoted), and an embedded
|
||||
paragraph break can add unindented blank lines inside a quoted
|
||||
scalar. Inserting the new line right after the *first* line of
|
||||
that scalar — instead of after the whole scalar — either produces
|
||||
invalid YAML or gets silently absorbed into the description
|
||||
string (#4044), so every continuation line (indented, or blank)
|
||||
is skipped first.
|
||||
|
||||
Skips injection if ``argument-hint:`` already exists in the
|
||||
frontmatter to avoid duplicate keys.
|
||||
@@ -88,15 +97,29 @@ class AlquimiaAIIntegration(SkillsIntegration):
|
||||
in_fm = False
|
||||
dash_count = 0
|
||||
injected = False
|
||||
for line in lines:
|
||||
i = 0
|
||||
n = len(lines)
|
||||
while i < n:
|
||||
line = lines[i]
|
||||
stripped = line.rstrip("\n\r")
|
||||
if stripped == "---":
|
||||
dash_count += 1
|
||||
in_fm = dash_count == 1
|
||||
out.append(line)
|
||||
i += 1
|
||||
continue
|
||||
if in_fm and not injected and stripped.startswith("description:"):
|
||||
out.append(line)
|
||||
i += 1
|
||||
# Skip folded/quoted continuation lines before inserting
|
||||
# so the new key lands after the description scalar ends.
|
||||
# Blank lines count too: PyYAML emits unindented blank
|
||||
# lines for embedded "\n\n" inside a quoted scalar.
|
||||
while i < n and (
|
||||
lines[i][:1] in (" ", "\t") or lines[i].rstrip("\r\n") == ""
|
||||
):
|
||||
out.append(lines[i])
|
||||
i += 1
|
||||
# Preserve the exact line-ending style (\r\n vs \n)
|
||||
if line.endswith("\r\n"):
|
||||
eol = "\r\n"
|
||||
@@ -109,6 +132,7 @@ class AlquimiaAIIntegration(SkillsIntegration):
|
||||
injected = True
|
||||
continue
|
||||
out.append(line)
|
||||
i += 1
|
||||
return "".join(out)
|
||||
|
||||
@staticmethod
|
||||
|
||||
@@ -471,6 +471,101 @@ class TestAlquimiaArgumentHints:
|
||||
hint_count = sum(1 for ln in lines if ln.startswith("argument-hint:"))
|
||||
assert hint_count == 1
|
||||
|
||||
def test_inject_argument_hint_survives_folded_description(self):
|
||||
"""A long description folded across lines must not corrupt the YAML (#4044).
|
||||
|
||||
A description long enough for the YAML dumper to fold it into a
|
||||
multi-line plain scalar previously had ``argument-hint:`` spliced
|
||||
into the *middle* of that scalar, producing invalid YAML.
|
||||
"""
|
||||
from specify_cli.integrations.alquimia import AlquimiaAIIntegration
|
||||
|
||||
frontmatter = {
|
||||
"name": "speckit-specify",
|
||||
"description": (
|
||||
"Create or update the feature specification from a natural "
|
||||
"language feature description. Also accepts an issue URL "
|
||||
"resolved via gh CLI (demo customization)."
|
||||
),
|
||||
"compatibility": "Requires spec-kit project structure with .specify/ directory",
|
||||
}
|
||||
frontmatter_text = yaml.safe_dump(
|
||||
frontmatter, sort_keys=False, allow_unicode=True
|
||||
).strip()
|
||||
content = f"---\n{frontmatter_text}\n---\n\nBody text\n"
|
||||
assert "\n " in content, "fixture description must actually fold across lines"
|
||||
|
||||
result = AlquimiaAIIntegration.inject_argument_hint(
|
||||
content, "Describe the feature"
|
||||
)
|
||||
|
||||
parsed = yaml.safe_load(result.split("---")[1])
|
||||
assert parsed["argument-hint"] == "Describe the feature"
|
||||
assert parsed["description"] == frontmatter["description"]
|
||||
|
||||
def test_inject_argument_hint_survives_quoted_folded_description(self):
|
||||
"""A folded description forced into quotes must not absorb the hint (#4044)."""
|
||||
from specify_cli.integrations.alquimia import AlquimiaAIIntegration
|
||||
|
||||
frontmatter = {
|
||||
"name": "speckit-specify",
|
||||
"description": (
|
||||
"Create or update the feature specification from a natural "
|
||||
"language feature description. Also accepts a GitHub "
|
||||
"issue/PR URL or #N reference resolved via gh CLI (demo)."
|
||||
),
|
||||
"compatibility": "Requires spec-kit project structure with .specify/ directory",
|
||||
}
|
||||
frontmatter_text = yaml.safe_dump(
|
||||
frontmatter, sort_keys=False, allow_unicode=True
|
||||
).strip()
|
||||
content = f"---\n{frontmatter_text}\n---\n\nBody text\n"
|
||||
assert "\n " in content, "fixture description must actually fold across lines"
|
||||
|
||||
result = AlquimiaAIIntegration.inject_argument_hint(
|
||||
content, "Describe the feature"
|
||||
)
|
||||
|
||||
parsed = yaml.safe_load(result.split("---")[1])
|
||||
assert parsed["argument-hint"] == "Describe the feature"
|
||||
assert parsed["description"] == frontmatter["description"]
|
||||
|
||||
def test_inject_argument_hint_survives_multi_paragraph_description(self):
|
||||
"""A description with an embedded blank line must not absorb the hint.
|
||||
|
||||
PyYAML serializes an embedded ``\\n\\n`` inside a quoted scalar as
|
||||
unindented blank lines, not indented ones, so a fix that only skips
|
||||
indented continuation lines still fails on this case.
|
||||
"""
|
||||
from specify_cli.integrations.alquimia import AlquimiaAIIntegration
|
||||
|
||||
frontmatter = {
|
||||
"name": "speckit-specify",
|
||||
"description": (
|
||||
"First paragraph of a fairly long description that will "
|
||||
"need to wrap across multiple lines when dumped by PyYAML."
|
||||
"\n\n"
|
||||
"Second paragraph continues the description after a blank "
|
||||
"line separator to force embedded newlines in the scalar."
|
||||
),
|
||||
"compatibility": "Requires spec-kit project structure with .specify/ directory",
|
||||
}
|
||||
frontmatter_text = yaml.safe_dump(
|
||||
frontmatter, sort_keys=False, allow_unicode=True
|
||||
).strip()
|
||||
content = f"---\n{frontmatter_text}\n---\n\nBody text\n"
|
||||
assert "\n\n" in frontmatter_text, (
|
||||
"fixture must produce a blank continuation line"
|
||||
)
|
||||
|
||||
result = AlquimiaAIIntegration.inject_argument_hint(
|
||||
content, "Describe the feature"
|
||||
)
|
||||
|
||||
parsed = yaml.safe_load(result.split("---")[1])
|
||||
assert parsed["argument-hint"] == "Describe the feature"
|
||||
assert parsed["description"] == frontmatter["description"]
|
||||
|
||||
|
||||
class TestAlquimiaDisableModelInvocation:
|
||||
"""Verify disable-model-invocation is false for Alquimia skills."""
|
||||
|
||||
Reference in New Issue
Block a user