Skip to content
Closed
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
28 changes: 27 additions & 1 deletion tests/test_presets.py
Original file line number Diff line number Diff line change
Expand Up @@ -1094,7 +1094,33 @@ def test_resolve_core_returns_none_when_nothing_found(self, project_dir):
result = resolver.resolve_core("nonexistent", "command")
assert result is None

def test_resolve_extension_command_via_manifest_skips_oserror_manifests(self, project_dir):
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")


"""resolve_extension_command_via_manifest skips extensions whose manifest raises OSError."""
import unittest.mock as mock

Expand Down