Skip to content

Commit 9b32c61

Browse files
fix: consistent short_name resolution and test hygiene for strategy: wrap
- Pass raw_short_name (dot-separated) to _substitute_core_template in _register_skills to match register_commands, ensuring consistent core template lookup across both code paths - Restore original two-step core_templates_dir existence check in _substitute_core_template to match codebase style - Restore missing reinstall assertion in bundled preset CLI error test - Restore AGENT_CONFIGS on the class (CommandRegistrar.AGENT_CONFIGS) not the instance to prevent state leaking across tests - Add test covering extension command dot-separated name resolution
1 parent 49c83d2 commit 9b32c61

2 files changed

Lines changed: 48 additions & 6 deletions

File tree

src/specify_cli/presets.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,11 @@ def _substitute_core_template(
5151
if "{CORE_TEMPLATE}" not in body:
5252
return body, {}
5353

54-
core_file = project_root / ".specify" / "templates" / "commands" / f"{short_name}.md"
55-
if not core_file.exists():
54+
core_templates_dir = project_root / ".specify" / "templates" / "commands"
55+
core_file = core_templates_dir / f"{short_name}.md" if core_templates_dir.exists() else None
56+
if core_file and not core_file.exists():
57+
core_file = None
58+
if core_file is None:
5659
return body, {}
5760

5861
core_frontmatter, core_body = registrar.parse_frontmatter(core_file.read_text(encoding="utf-8"))
@@ -792,7 +795,7 @@ def _register_skills(
792795
frontmatter, body = registrar.parse_frontmatter(content)
793796

794797
if frontmatter.get("strategy") == "wrap":
795-
body, core_frontmatter = _substitute_core_template(body, short_name, self.project_root, registrar)
798+
body, core_frontmatter = _substitute_core_template(body, raw_short_name, self.project_root, registrar)
796799
frontmatter = dict(frontmatter)
797800
for key in ("scripts", "agent_scripts"):
798801
if key not in frontmatter and key in core_frontmatter:

tests/test_presets.py

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3043,6 +3043,7 @@ def test_bundled_preset_missing_locally_cli_error(self, project_dir):
30433043
assert result.exit_code == 1
30443044
output = strip_ansi(result.output).lower()
30453045
assert "bundled" in output, result.output
3046+
assert "reinstall" in output, result.output
30463047

30473048

30483049
class TestWrapStrategy:
@@ -3139,7 +3140,7 @@ def test_register_commands_substitutes_core_template_for_wrap_strategy(self, pro
31393140
project_dir / "preset", project_dir
31403141
)
31413142
finally:
3142-
registrar.AGENT_CONFIGS = original
3143+
CommandRegistrar.AGENT_CONFIGS = original
31433144

31443145
written = (agent_dir / "speckit.specify.md").read_text()
31453146
assert "{CORE_TEMPLATE}" not in written
@@ -3295,7 +3296,7 @@ def test_register_commands_inherits_scripts_from_core(self, project_dir):
32953296
project_dir,
32963297
)
32973298
finally:
3298-
registrar.AGENT_CONFIGS = original
3299+
CommandRegistrar.AGENT_CONFIGS = original
32993300

33003301
written = (agent_dir / "speckit.specify.md").read_text()
33013302
assert "{CORE_TEMPLATE}" not in written
@@ -3343,7 +3344,7 @@ def test_register_commands_toml_resolves_inherited_scripts(self, project_dir):
33433344
project_dir,
33443345
)
33453346
finally:
3346-
registrar.AGENT_CONFIGS = original
3347+
CommandRegistrar.AGENT_CONFIGS = original
33473348

33483349
written = (toml_dir / "speckit.specify.toml").read_text()
33493350
assert "{CORE_TEMPLATE}" not in written
@@ -3352,3 +3353,41 @@ def test_register_commands_toml_resolves_inherited_scripts(self, project_dir):
33523353
# args token must use TOML format, not the intermediate $ARGUMENTS
33533354
assert "$ARGUMENTS" not in written
33543355
assert "{{args}}" in written
3356+
3357+
def test_extension_command_resolves_dot_separated_template(self, project_dir):
3358+
"""Extension command names like speckit.git.feature look up git.feature.md, not git-feature.md.
3359+
3360+
Covers both _register_skills (via _substitute_core_template with raw_short_name)
3361+
and register_commands (via short_name stripped of speckit. prefix).
3362+
"""
3363+
from specify_cli.presets import _substitute_core_template
3364+
from specify_cli.agents import CommandRegistrar
3365+
3366+
core_dir = project_dir / ".specify" / "templates" / "commands"
3367+
core_dir.mkdir(parents=True, exist_ok=True)
3368+
# Template uses dot-separated name — must NOT be named git-feature.md
3369+
(core_dir / "git.feature.md").write_text(
3370+
"---\ndescription: core git feature\n---\n\n# Git Feature Core\n"
3371+
)
3372+
# Ensure the hyphenated variant does NOT exist to prove we pick the right file
3373+
assert not (core_dir / "git-feature.md").exists()
3374+
3375+
registrar = CommandRegistrar()
3376+
body = "## Wrapper\n\n{CORE_TEMPLATE}\n"
3377+
3378+
# Simulate register_commands: strips "speckit." only
3379+
short_name_commands = "speckit.git.feature"
3380+
if short_name_commands.startswith("speckit."):
3381+
short_name_commands = short_name_commands[len("speckit."):]
3382+
result_commands, _ = _substitute_core_template(body, short_name_commands, project_dir, registrar)
3383+
3384+
# Simulate _register_skills: strips "speckit." then hyphenates for skill dirs,
3385+
# but passes raw_short_name (pre-hyphenation) to _substitute_core_template
3386+
raw_short_name = "speckit.git.feature"
3387+
if raw_short_name.startswith("speckit."):
3388+
raw_short_name = raw_short_name[len("speckit."):]
3389+
result_skills, _ = _substitute_core_template(body, raw_short_name, project_dir, registrar)
3390+
3391+
assert "# Git Feature Core" in result_commands, "register_commands path did not resolve extension template"
3392+
assert "# Git Feature Core" in result_skills, "_register_skills path did not resolve extension template"
3393+
assert result_commands == result_skills, "register_commands and _register_skills resolved differently"

0 commit comments

Comments
 (0)