mirror of
https://github.com/github/spec-kit.git
synced 2026-07-11 02:24:16 +08:00
* fix(agents): resolve skill placeholders in Goose (yaml) command output
CommandRegistrar.register_commands resolves {SCRIPT}/__AGENT__ and the
$ARGUMENTS placeholder in the markdown and toml branches, but the yaml
branch (Goose recipes) called render_yaml_command directly, skipping
both. So extension/preset command bodies installed for Goose kept literal
{SCRIPT}, __AGENT__, and repo-relative script paths in the generated
.goose/recipes/*.yaml prompt. Mirror the markdown/toml branches: run
resolve_skill_placeholders + _convert_argument_placeholder on the body
before render_yaml_command.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test(goose): assert positive placeholder replacements in recipe prompt
Per review: parse the generated recipe with yaml.safe_load and assert the
prompt contains the resolved values (.specify/scripts/, 'agent goose',
{{args}}), not merely that the literal tokens are absent — a wrong output
that happens to omit the exact strings would otherwise pass.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
86 lines
3.3 KiB
Python
86 lines
3.3 KiB
Python
"""Tests for GooseIntegration."""
|
|
|
|
import yaml
|
|
from specify_cli.integrations import get_integration
|
|
from specify_cli.integrations.manifest import IntegrationManifest
|
|
|
|
from .test_integration_base_yaml import YamlIntegrationTests
|
|
|
|
|
|
class TestGooseIntegration(YamlIntegrationTests):
|
|
KEY = "goose"
|
|
FOLDER = ".goose/"
|
|
COMMANDS_SUBDIR = "recipes"
|
|
REGISTRAR_DIR = ".goose/recipes"
|
|
|
|
def test_setup_declares_args_parameter_for_args_prompt(self, tmp_path):
|
|
# “If a generated Goose recipe uses {{args}} in its prompt, it
|
|
# must declare a corresponding args parameter.”
|
|
|
|
integration = get_integration("goose")
|
|
assert integration is not None
|
|
|
|
manifest = IntegrationManifest("goose", tmp_path)
|
|
created = integration.setup(tmp_path, manifest, script_type="sh")
|
|
|
|
recipe_files = [path for path in created if path.suffix == ".yaml"]
|
|
assert recipe_files
|
|
|
|
for recipe_file in recipe_files:
|
|
data = yaml.safe_load(recipe_file.read_text(encoding="utf-8"))
|
|
|
|
if "{{args}}" not in data["prompt"]:
|
|
continue
|
|
|
|
assert any(
|
|
param.get("key") == "args"
|
|
for param in data.get("parameters", [])
|
|
), f"{recipe_file} uses {{{{args}}}} but does not declare args"
|
|
|
|
|
|
class TestGooseCommandPlaceholderResolution:
|
|
"""register_commands must resolve skill placeholders for the yaml branch.
|
|
|
|
The yaml (Goose recipe) branch previously skipped
|
|
resolve_skill_placeholders / _convert_argument_placeholder that the
|
|
markdown and toml branches apply, so extension/preset command bodies
|
|
kept literal {SCRIPT} / __AGENT__ / repo-relative paths.
|
|
"""
|
|
|
|
def test_register_commands_resolves_placeholders_in_recipe(self, tmp_path):
|
|
from specify_cli.agents import CommandRegistrar
|
|
|
|
ext_dir = tmp_path / "extension"
|
|
cmd_dir = ext_dir / "commands"
|
|
cmd_dir.mkdir(parents=True)
|
|
cmd_file = cmd_dir / "example.md"
|
|
cmd_file.write_text(
|
|
"---\n"
|
|
"description: Placeholder command\n"
|
|
"scripts:\n"
|
|
" sh: scripts/bash/do.sh\n"
|
|
" ps: scripts/powershell/do.ps1\n"
|
|
"---\n\n"
|
|
"Run {SCRIPT} for agent __AGENT__ with $ARGUMENTS.\n",
|
|
encoding="utf-8",
|
|
)
|
|
|
|
registrar = CommandRegistrar()
|
|
commands = [{"name": "speckit.example", "file": "commands/example.md"}]
|
|
registrar.register_commands("goose", commands, "test-ext", ext_dir, tmp_path)
|
|
|
|
recipe = tmp_path / ".goose" / "recipes" / "speckit.example.yaml"
|
|
assert recipe.exists(), "goose recipe should be generated"
|
|
# Parse the recipe and assert the prompt actually got the correct
|
|
# replacements — not merely that the literal tokens are absent (which
|
|
# a wrong-but-token-free output could also satisfy).
|
|
data = yaml.safe_load(recipe.read_text(encoding="utf-8"))
|
|
prompt = data["prompt"]
|
|
assert ".specify/scripts/" in prompt # {SCRIPT} -> resolved script path
|
|
assert "agent goose" in prompt # __AGENT__ -> agent name
|
|
assert "{{args}}" in prompt # $ARGUMENTS -> goose args token
|
|
# And the raw placeholders must not survive.
|
|
assert "{SCRIPT}" not in prompt
|
|
assert "__AGENT__" not in prompt
|
|
assert "$ARGUMENTS" not in prompt
|