Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions src/specify_cli/presets/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2703,7 +2703,7 @@ def resolve(
# (source-checkout / editable install). This is the canonical home for
# speckit's built-in command/template files and must always be checked
# so that strategy:wrap presets can locate {CORE_TEMPLATE}.
from specify_cli import _locate_core_pack # local import to avoid cycles
from specify_cli import _locate_core_pack, _repo_root # local import to avoid cycles
_core_pack = _locate_core_pack()
if _core_pack is not None:
# Wheel install path
Expand All @@ -2723,7 +2723,7 @@ def resolve(
return candidate
else:
# Source-checkout / editable install: templates live at repo root
repo_root = Path(__file__).parent.parent.parent
repo_root = _repo_root()
if template_type == "template":
candidate = repo_root / "templates" / f"{template_name}.md"
elif template_type == "command":
Expand Down Expand Up @@ -3075,7 +3075,7 @@ def _find_bundled_core(
``.specify/templates/`` doesn't contain the core file.
"""
try:
from specify_cli import _locate_core_pack
from specify_cli import _locate_core_pack, _repo_root
except ImportError:
return None

Expand All @@ -3098,7 +3098,7 @@ def _find_bundled_core(
if c.exists():
return c
else:
repo_root = Path(__file__).parent.parent.parent
repo_root = _repo_root()
for name in names:
if template_type == "template":
c = repo_root / "templates" / f"{name}.md"
Expand Down
26 changes: 26 additions & 0 deletions tests/test_presets.py
Original file line number Diff line number Diff line change
Expand Up @@ -1033,6 +1033,32 @@ def test_resolve_skips_hidden_extension_dirs(self, project_dir):
result = resolver.resolve("hidden-template")
assert result is None

def test_collect_all_layers_finds_bundled_core_without_specify_commands(
self, project_dir
):
"""Tier-5 fallback locates the bundled core command when
.specify/templates/commands/ has no matching file.

Regression test for #3086: a stale ``.parent`` chain made the
source-checkout fallback resolve to ``src/templates/...`` (which does
not exist), so ``wrap`` presets found no base layer. The fallback must
resolve against the real repo-root ``templates/commands`` tree.
"""
# project_dir's commands dir is empty, so tier-4 cannot satisfy this.
resolver = PresetResolver(project_dir)
layers = resolver.collect_all_layers("speckit.implement", "command")
assert layers, "expected a bundled core base layer to be found"
assert layers[-1]["source"] == "core (bundled)"
assert layers[-1]["path"].parts[-2:] == ("commands", "implement.md")

def test_resolve_command_falls_back_to_bundled_core(self, project_dir):
"""resolve() tier-5 returns the bundled core command when
.specify/templates/commands/ lacks it (regression for #3086)."""
resolver = PresetResolver(project_dir)
result = resolver.resolve("speckit.implement", "command")
assert result is not None
assert result.parts[-2:] == ("commands", "implement.md")


class TestResolveCore:
"""Test PresetResolver.resolve_core() skips the installed-presets tier."""
Expand Down
Loading