diff --git a/src/specify_cli/presets/__init__.py b/src/specify_cli/presets/__init__.py index b30c6f3c1..66f1bbc5e 100644 --- a/src/specify_cli/presets/__init__.py +++ b/src/specify_cli/presets/__init__.py @@ -2707,7 +2707,7 @@ class PresetResolver: # (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 @@ -2727,7 +2727,7 @@ class PresetResolver: 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": @@ -3079,7 +3079,7 @@ class PresetResolver: ``.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 @@ -3102,7 +3102,7 @@ class PresetResolver: 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" diff --git a/tests/test_presets.py b/tests/test_presets.py index a64e28bb3..58574bbc9 100644 --- a/tests/test_presets.py +++ b/tests/test_presets.py @@ -1033,6 +1033,32 @@ class TestPresetResolver: 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."""