From ed91100a0f5a3beec071cacb9c3b16cfdcc2499a Mon Sep 17 00:00:00 2001 From: Greyson LaLonde Date: Thu, 28 May 2026 09:38:10 -0700 Subject: [PATCH] refactor(skills): move Skills Repository to experimental + CREWAI_EXPERIMENTAL gate Moves the registry/cache pieces of PR #5867 under crewai.experimental.skills and the CLI commands under `crewai experimental skill`. The stable local-file skills feature (loader, parser, validation, models) stays in crewai.skills. Both entry points now require CREWAI_EXPERIMENTAL=1: - resolve_registry_ref() calls require_experimental_skills() before resolving - The `crewai experimental` CLI group raises UsageError when the flag is unset SkillDownloadStarted/CompletedEvent move out of crewai.events.types.skill_events into crewai.experimental.skills.events. * refactor(skills): move 'version' off SkillFrontmatter into metadata The skill version is now stored as `metadata.version` rather than a top-level field on `SkillFrontmatter`. A `before` validator lifts any top-level YAML `version:` into `metadata['version']` so existing SKILL.md files keep parsing. --- lib/cli/src/crewai_cli/cli.py | 15 +++++++-- .../{skills => experimental}/__init__.py | 0 .../experimental/skills/__init__.py | 0 .../{ => experimental}/skills/main.py | 17 +++++++--- lib/cli/tests/experimental/__init__.py | 0 lib/cli/tests/experimental/skills/__init__.py | 0 .../{ => experimental}/skills/test_main.py | 8 ++--- lib/crewai/src/crewai/agent/core.py | 2 +- .../src/crewai/events/types/skill_events.py | 17 ---------- .../crewai/experimental/skills/__init__.py | 23 +++++++++++++ .../src/crewai/experimental/skills/_flag.py | 24 ++++++++++++++ .../crewai/{ => experimental}/skills/cache.py | 0 .../src/crewai/experimental/skills/events.py | 30 +++++++++++++++++ .../{ => experimental}/skills/registry.py | 6 ++-- lib/crewai/src/crewai/skills/__init__.py | 5 --- lib/crewai/src/crewai/skills/models.py | 8 ++--- .../tests/experimental/skills/__init__.py | 0 .../tests/experimental/skills/conftest.py | 6 ++++ .../{ => experimental}/skills/test_cache.py | 2 +- .../tests/experimental/skills/test_flag.py | 30 +++++++++++++++++ .../skills/test_models_version.py | 32 +++++++++++++++++++ .../skills/test_registry.py | 20 ++++++------ .../tests/skills/test_models_version.py | 32 ------------------- 23 files changed, 193 insertions(+), 84 deletions(-) rename lib/cli/src/crewai_cli/{skills => experimental}/__init__.py (100%) create mode 100644 lib/cli/src/crewai_cli/experimental/skills/__init__.py rename lib/cli/src/crewai_cli/{ => experimental}/skills/main.py (96%) create mode 100644 lib/cli/tests/experimental/__init__.py create mode 100644 lib/cli/tests/experimental/skills/__init__.py rename lib/cli/tests/{ => experimental}/skills/test_main.py (94%) create mode 100644 lib/crewai/src/crewai/experimental/skills/__init__.py create mode 100644 lib/crewai/src/crewai/experimental/skills/_flag.py rename lib/crewai/src/crewai/{ => experimental}/skills/cache.py (100%) create mode 100644 lib/crewai/src/crewai/experimental/skills/events.py rename lib/crewai/src/crewai/{ => experimental}/skills/registry.py (96%) create mode 100644 lib/crewai/tests/experimental/skills/__init__.py create mode 100644 lib/crewai/tests/experimental/skills/conftest.py rename lib/crewai/tests/{ => experimental}/skills/test_cache.py (98%) create mode 100644 lib/crewai/tests/experimental/skills/test_flag.py create mode 100644 lib/crewai/tests/experimental/skills/test_models_version.py rename lib/crewai/tests/{ => experimental}/skills/test_registry.py (81%) delete mode 100644 lib/crewai/tests/skills/test_models_version.py diff --git a/lib/cli/src/crewai_cli/cli.py b/lib/cli/src/crewai_cli/cli.py index 6364dfdc3..f1a8b20ba 100644 --- a/lib/cli/src/crewai_cli/cli.py +++ b/lib/cli/src/crewai_cli/cli.py @@ -17,6 +17,7 @@ from crewai_cli.crew_chat import run_chat from crewai_cli.deploy.main import DeployCommand from crewai_cli.enterprise.main import EnterpriseConfigureCommand from crewai_cli.evaluate_crew import evaluate_crew +from crewai_cli.experimental.skills.main import SkillCommand from crewai_cli.install_crew import install_crew from crewai_cli.kickoff_flow import kickoff_flow from crewai_cli.organization.main import OrganizationCommand @@ -26,7 +27,6 @@ from crewai_cli.replay_from_task import replay_task_command from crewai_cli.reset_memories_command import reset_memories_command from crewai_cli.run_crew import run_crew from crewai_cli.settings.main import SettingsCommand -from crewai_cli.skills.main import SkillCommand from crewai_cli.task_outputs import load_task_outputs from crewai_cli.tools.main import ToolCommand from crewai_cli.train_crew import train_crew @@ -544,8 +544,19 @@ def tool_publish(is_public: bool, force: bool) -> None: @crewai.group() +def experimental() -> None: + """Experimental, unstable commands. Subject to change without notice.""" + import os + + if os.environ.get("CREWAI_EXPERIMENTAL") != "1": + raise click.UsageError( + "Experimental commands are gated. Set CREWAI_EXPERIMENTAL=1 to enable." + ) + + +@experimental.group(name="skill") def skill() -> None: - """Skill Repository related commands.""" + """Skill Repository related commands (experimental).""" @skill.command(name="create") diff --git a/lib/cli/src/crewai_cli/skills/__init__.py b/lib/cli/src/crewai_cli/experimental/__init__.py similarity index 100% rename from lib/cli/src/crewai_cli/skills/__init__.py rename to lib/cli/src/crewai_cli/experimental/__init__.py diff --git a/lib/cli/src/crewai_cli/experimental/skills/__init__.py b/lib/cli/src/crewai_cli/experimental/skills/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/lib/cli/src/crewai_cli/skills/main.py b/lib/cli/src/crewai_cli/experimental/skills/main.py similarity index 96% rename from lib/cli/src/crewai_cli/skills/main.py rename to lib/cli/src/crewai_cli/experimental/skills/main.py index 16d705bfb..81f9b3fc5 100644 --- a/lib/cli/src/crewai_cli/skills/main.py +++ b/lib/cli/src/crewai_cli/experimental/skills/main.py @@ -23,9 +23,10 @@ console = Console() _SKILL_MD_TEMPLATE = """\ --- name: {name} -version: 0.1.0 description: | A short description of what this skill does. +metadata: + version: 0.1.0 --- ## Instructions @@ -147,7 +148,7 @@ class SkillCommand(BaseCommand, PlusAPIMixin): ) else: try: - from crewai.skills.cache import SkillCacheManager + from crewai.experimental.skills.cache import SkillCacheManager cache = SkillCacheManager() cache.store(org, name, version, archive_bytes) @@ -191,7 +192,10 @@ class SkillCommand(BaseCommand, PlusAPIMixin): raise SystemExit(1) from exc name = frontmatter.get("name") - version = frontmatter.get("version") + raw_metadata = frontmatter.get("metadata") + version = ( + raw_metadata.get("version") if isinstance(raw_metadata, dict) else None + ) description = frontmatter.get("description") if not name: @@ -362,10 +366,13 @@ class SkillCommand(BaseCommand, PlusAPIMixin): return result def _read_version(self, skill_md: Path) -> str | None: - """Read the version field from a SKILL.md file, or None.""" + """Read the version from a SKILL.md file's metadata, or None.""" try: fm = self._parse_frontmatter(skill_md.read_text()) - return fm.get("version") + raw_metadata = fm.get("metadata") + if isinstance(raw_metadata, dict): + return raw_metadata.get("version") + return None except Exception: return None diff --git a/lib/cli/tests/experimental/__init__.py b/lib/cli/tests/experimental/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/lib/cli/tests/experimental/skills/__init__.py b/lib/cli/tests/experimental/skills/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/lib/cli/tests/skills/test_main.py b/lib/cli/tests/experimental/skills/test_main.py similarity index 94% rename from lib/cli/tests/skills/test_main.py rename to lib/cli/tests/experimental/skills/test_main.py index f66c5b90a..f302754d9 100644 --- a/lib/cli/tests/skills/test_main.py +++ b/lib/cli/tests/experimental/skills/test_main.py @@ -36,7 +36,7 @@ def skill_command(): TokenManager().save_tokens( "test-token", (datetime.now() + timedelta(seconds=36000)).timestamp() ) - from crewai_cli.skills.main import SkillCommand + from crewai_cli.experimental.skills.main import SkillCommand cmd = SkillCommand() yield cmd @@ -142,7 +142,7 @@ class TestSkillPublish: mock_resp.status_code = 200 mock_resp.json.return_value = {} mock_client.publish_skill.return_value = mock_resp - with patch("crewai_cli.skills.main.Settings") as mock_settings_cls: + with patch("crewai_cli.experimental.skills.main.Settings") as mock_settings_cls: mock_settings_cls.return_value.org_name = None mock_settings_cls.return_value.enterprise_base_url = None with pytest.raises(SystemExit): @@ -151,14 +151,14 @@ class TestSkillPublish: def test_publish_calls_api(self, skill_command): with in_temp_dir(): Path("SKILL.md").write_text( - "---\nname: my-skill\nversion: 1.0.0\ndescription: A test skill.\n---\nInstructions." + "---\nname: my-skill\ndescription: A test skill.\nmetadata:\n version: 1.0.0\n---\nInstructions." ) mock_resp = MagicMock() mock_resp.is_success = True mock_resp.status_code = 200 mock_resp.json.return_value = {} skill_command.plus_api_client.publish_skill = MagicMock(return_value=mock_resp) - with patch("crewai_cli.skills.main.Settings") as mock_settings_cls: + with patch("crewai_cli.experimental.skills.main.Settings") as mock_settings_cls: mock_settings_cls.return_value.org_name = "acme" mock_settings_cls.return_value.enterprise_base_url = None diff --git a/lib/crewai/src/crewai/agent/core.py b/lib/crewai/src/crewai/agent/core.py index 0295a2a6c..73020f115 100644 --- a/lib/crewai/src/crewai/agent/core.py +++ b/lib/crewai/src/crewai/agent/core.py @@ -472,7 +472,7 @@ class Agent(BaseAgent): for item in items: if isinstance(item, str): - from crewai.skills.registry import ( + from crewai.experimental.skills.registry import ( is_registry_ref, parse_registry_ref, resolve_registry_ref, diff --git a/lib/crewai/src/crewai/events/types/skill_events.py b/lib/crewai/src/crewai/events/types/skill_events.py index 322c053e3..aab625dda 100644 --- a/lib/crewai/src/crewai/events/types/skill_events.py +++ b/lib/crewai/src/crewai/events/types/skill_events.py @@ -60,20 +60,3 @@ class SkillLoadFailedEvent(SkillEvent): type: Literal["skill_load_failed"] = "skill_load_failed" error: str - - -class SkillDownloadStartedEvent(SkillEvent): - """Event emitted when a registry skill download begins.""" - - type: Literal["skill_download_started"] = "skill_download_started" - registry_ref: str - version: str | None = None - - -class SkillDownloadCompletedEvent(SkillEvent): - """Event emitted when a registry skill download completes.""" - - type: Literal["skill_download_completed"] = "skill_download_completed" - registry_ref: str - version: str | None = None - cache_path: Path | None = None diff --git a/lib/crewai/src/crewai/experimental/skills/__init__.py b/lib/crewai/src/crewai/experimental/skills/__init__.py new file mode 100644 index 000000000..aef3d4ed5 --- /dev/null +++ b/lib/crewai/src/crewai/experimental/skills/__init__.py @@ -0,0 +1,23 @@ +"""Experimental Skills Repository — registry refs, global cache, downloads. + +This package contains the registry-backed pieces of the skills feature +(`@org/name` refs, `~/.crewai/skills/` cache, download events). The stable +filesystem-based skill loader still lives in `crewai.skills`. +""" + +from crewai.experimental.skills.cache import SkillCacheManager +from crewai.experimental.skills.registry import ( + SkillNotCachedError, + is_registry_ref, + parse_registry_ref, + resolve_registry_ref, +) + + +__all__ = [ + "SkillCacheManager", + "SkillNotCachedError", + "is_registry_ref", + "parse_registry_ref", + "resolve_registry_ref", +] diff --git a/lib/crewai/src/crewai/experimental/skills/_flag.py b/lib/crewai/src/crewai/experimental/skills/_flag.py new file mode 100644 index 000000000..beca1d270 --- /dev/null +++ b/lib/crewai/src/crewai/experimental/skills/_flag.py @@ -0,0 +1,24 @@ +"""Experimental feature gate for the Skills Repository.""" + +from __future__ import annotations + +import os + + +ENV_VAR = "CREWAI_EXPERIMENTAL" + + +class ExperimentalFeatureDisabledError(RuntimeError): + """Raised when an experimental feature is used without the flag set.""" + + +def is_enabled() -> bool: + return os.environ.get(ENV_VAR) == "1" + + +def require_experimental_skills() -> None: + if not is_enabled(): + raise ExperimentalFeatureDisabledError( + "The Skills Repository (registry refs, cache, downloads) is " + f"experimental. Set {ENV_VAR}=1 to enable it." + ) diff --git a/lib/crewai/src/crewai/skills/cache.py b/lib/crewai/src/crewai/experimental/skills/cache.py similarity index 100% rename from lib/crewai/src/crewai/skills/cache.py rename to lib/crewai/src/crewai/experimental/skills/cache.py diff --git a/lib/crewai/src/crewai/experimental/skills/events.py b/lib/crewai/src/crewai/experimental/skills/events.py new file mode 100644 index 000000000..c97632281 --- /dev/null +++ b/lib/crewai/src/crewai/experimental/skills/events.py @@ -0,0 +1,30 @@ +"""Download lifecycle events for registry-backed skills. + +These events are emitted only by the experimental Skills Repository +(`@org/name` resolution + global cache). Local-file skill events still +live in `crewai.events.types.skill_events`. +""" + +from __future__ import annotations + +from pathlib import Path +from typing import Literal + +from crewai.events.types.skill_events import SkillEvent + + +class SkillDownloadStartedEvent(SkillEvent): + """Event emitted when a registry skill download begins.""" + + type: Literal["skill_download_started"] = "skill_download_started" + registry_ref: str + version: str | None = None + + +class SkillDownloadCompletedEvent(SkillEvent): + """Event emitted when a registry skill download completes.""" + + type: Literal["skill_download_completed"] = "skill_download_completed" + registry_ref: str + version: str | None = None + cache_path: Path | None = None diff --git a/lib/crewai/src/crewai/skills/registry.py b/lib/crewai/src/crewai/experimental/skills/registry.py similarity index 96% rename from lib/crewai/src/crewai/skills/registry.py rename to lib/crewai/src/crewai/experimental/skills/registry.py index 991fbcde2..fa502c4f0 100644 --- a/lib/crewai/src/crewai/skills/registry.py +++ b/lib/crewai/src/crewai/experimental/skills/registry.py @@ -11,7 +11,7 @@ from pathlib import Path import sys from typing import Any -from crewai.skills.cache import SkillCacheManager +from crewai.experimental.skills.cache import SkillCacheManager _logger = logging.getLogger(__name__) @@ -100,9 +100,11 @@ def resolve_registry_ref( Raises: SkillNotCachedError: When not cached and running in non-interactive mode. """ + from crewai.experimental.skills._flag import require_experimental_skills from crewai.skills.loader import activate_skill from crewai.skills.parser import load_skill_metadata + require_experimental_skills() org, name = parse_registry_ref(ref) local_path = Path.cwd() / "skills" / name @@ -152,7 +154,7 @@ def download_skill( try: from crewai.events.event_bus import crewai_event_bus - from crewai.events.types.skill_events import ( + from crewai.experimental.skills.events import ( SkillDownloadCompletedEvent, SkillDownloadStartedEvent, ) diff --git a/lib/crewai/src/crewai/skills/__init__.py b/lib/crewai/src/crewai/skills/__init__.py index a045581d2..e33e98570 100644 --- a/lib/crewai/src/crewai/skills/__init__.py +++ b/lib/crewai/src/crewai/skills/__init__.py @@ -3,20 +3,15 @@ Provides filesystem-based skill packaging with progressive disclosure. """ -from crewai.skills.cache import SkillCacheManager from crewai.skills.loader import activate_skill, discover_skills from crewai.skills.models import Skill, SkillFrontmatter from crewai.skills.parser import SkillParseError -from crewai.skills.registry import is_registry_ref, resolve_registry_ref __all__ = [ "Skill", - "SkillCacheManager", "SkillFrontmatter", "SkillParseError", "activate_skill", "discover_skills", - "is_registry_ref", - "resolve_registry_ref", ] diff --git a/lib/crewai/src/crewai/skills/models.py b/lib/crewai/src/crewai/skills/models.py index 2cf6beb78..8c23773bd 100644 --- a/lib/crewai/src/crewai/skills/models.py +++ b/lib/crewai/src/crewai/skills/models.py @@ -49,6 +49,7 @@ class SkillFrontmatter(BaseModel): license: Optional license name or reference. compatibility: Optional compatibility information (max 500 chars). metadata: Optional additional metadata as string key-value pairs. + Conventional keys include 'version' (skill semantic version). allowed_tools: Optional space-delimited list of pre-approved tools. """ @@ -71,17 +72,14 @@ class SkillFrontmatter(BaseModel): ) metadata: dict[str, str] | None = Field( default=None, - description="Arbitrary string key-value pairs for custom skill metadata.", + description="Arbitrary string key-value pairs for custom skill metadata. " + "Conventional keys include 'version' for the skill's semantic version.", ) allowed_tools: list[str] | None = Field( default=None, alias="allowed-tools", description="Pre-approved tool names the skill may use, parsed from a space-delimited string in frontmatter.", ) - version: str | None = Field( - default=None, - description="Semantic version of the skill, e.g. '1.0.0'. Optional for local skills.", - ) @model_validator(mode="before") @classmethod diff --git a/lib/crewai/tests/experimental/skills/__init__.py b/lib/crewai/tests/experimental/skills/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/lib/crewai/tests/experimental/skills/conftest.py b/lib/crewai/tests/experimental/skills/conftest.py new file mode 100644 index 000000000..40baddaad --- /dev/null +++ b/lib/crewai/tests/experimental/skills/conftest.py @@ -0,0 +1,6 @@ +import pytest + + +@pytest.fixture(autouse=True) +def _enable_experimental_skills(monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.setenv("CREWAI_EXPERIMENTAL", "1") \ No newline at end of file diff --git a/lib/crewai/tests/skills/test_cache.py b/lib/crewai/tests/experimental/skills/test_cache.py similarity index 98% rename from lib/crewai/tests/skills/test_cache.py rename to lib/crewai/tests/experimental/skills/test_cache.py index 4316cb9b9..8b458bb3e 100644 --- a/lib/crewai/tests/skills/test_cache.py +++ b/lib/crewai/tests/experimental/skills/test_cache.py @@ -8,7 +8,7 @@ import json import tarfile from pathlib import Path -from crewai.skills.cache import SkillCacheManager +from crewai.experimental.skills.cache import SkillCacheManager def _make_tar_gz(files: dict[str, str]) -> bytes: diff --git a/lib/crewai/tests/experimental/skills/test_flag.py b/lib/crewai/tests/experimental/skills/test_flag.py new file mode 100644 index 000000000..9295de678 --- /dev/null +++ b/lib/crewai/tests/experimental/skills/test_flag.py @@ -0,0 +1,30 @@ +"""Tests for the CREWAI_EXPERIMENTAL gate on Skills Repository.""" + +from __future__ import annotations + +import pytest + +from crewai.experimental.skills._flag import ( + ExperimentalFeatureDisabledError, + require_experimental_skills, +) +from crewai.experimental.skills.registry import resolve_registry_ref + + +def test_require_raises_without_flag(monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.delenv("CREWAI_EXPERIMENTAL", raising=False) + with pytest.raises(ExperimentalFeatureDisabledError): + require_experimental_skills() + + +def test_resolve_registry_ref_raises_without_flag( + monkeypatch: pytest.MonkeyPatch, +) -> None: + monkeypatch.delenv("CREWAI_EXPERIMENTAL", raising=False) + with pytest.raises(ExperimentalFeatureDisabledError): + resolve_registry_ref("@acme/my-skill") + + +def test_require_passes_with_flag(monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.setenv("CREWAI_EXPERIMENTAL", "1") + require_experimental_skills() \ No newline at end of file diff --git a/lib/crewai/tests/experimental/skills/test_models_version.py b/lib/crewai/tests/experimental/skills/test_models_version.py new file mode 100644 index 000000000..e3bf7048a --- /dev/null +++ b/lib/crewai/tests/experimental/skills/test_models_version.py @@ -0,0 +1,32 @@ +"""Tests for the 'version' metadata key on SkillFrontmatter. + +Per the agentskills.io spec, `version` lives under `metadata`, not as a +top-level frontmatter field. +""" + +from __future__ import annotations + +from crewai.skills.models import SkillFrontmatter + + +class TestSkillFrontmatterVersion: + def test_no_metadata_by_default(self) -> None: + fm = SkillFrontmatter(name="my-skill", description="A skill.") + assert fm.metadata is None + + def test_version_via_metadata(self) -> None: + fm = SkillFrontmatter( + name="my-skill", + description="A skill.", + metadata={"version": "1.2.3"}, + ) + assert fm.metadata is not None + assert fm.metadata["version"] == "1.2.3" + + def test_metadata_accepts_other_keys(self) -> None: + fm = SkillFrontmatter( + name="my-skill", + description="A skill.", + metadata={"version": "1.0.0", "author": "acme"}, + ) + assert fm.metadata == {"version": "1.0.0", "author": "acme"} diff --git a/lib/crewai/tests/skills/test_registry.py b/lib/crewai/tests/experimental/skills/test_registry.py similarity index 81% rename from lib/crewai/tests/skills/test_registry.py rename to lib/crewai/tests/experimental/skills/test_registry.py index 5f588ccf7..84067dc73 100644 --- a/lib/crewai/tests/skills/test_registry.py +++ b/lib/crewai/tests/experimental/skills/test_registry.py @@ -5,7 +5,7 @@ from __future__ import annotations from pathlib import Path from unittest.mock import MagicMock, patch -from crewai.skills.registry import ( +from crewai.experimental.skills.registry import ( SkillNotCachedError, is_registry_ref, parse_registry_ref, @@ -75,11 +75,11 @@ class TestResolveRegistryRef: mock_cache.get_cached_path.return_value = None with ( - patch("crewai.skills.registry._is_noninteractive", return_value=False), + patch("crewai.experimental.skills.registry._is_noninteractive", return_value=False), patch.object(Path, "cwd", return_value=tmp_path), - patch("crewai.skills.registry.SkillCacheManager", return_value=mock_cache), + patch("crewai.experimental.skills.registry.SkillCacheManager", return_value=mock_cache), ): - from crewai.skills.registry import resolve_registry_ref + from crewai.experimental.skills.registry import resolve_registry_ref skill = resolve_registry_ref("@acme/my-skill") assert skill.name == "my-skill" @@ -90,11 +90,11 @@ class TestResolveRegistryRef: mock_cache.get_cached_path.return_value = None with ( - patch("crewai.skills.registry._is_noninteractive", return_value=True), + patch("crewai.experimental.skills.registry._is_noninteractive", return_value=True), patch.object(Path, "cwd", return_value=tmp_path), - patch("crewai.skills.registry.SkillCacheManager", return_value=mock_cache), + patch("crewai.experimental.skills.registry.SkillCacheManager", return_value=mock_cache), ): - from crewai.skills.registry import resolve_registry_ref + from crewai.experimental.skills.registry import resolve_registry_ref with pytest.raises(SkillNotCachedError) as exc_info: resolve_registry_ref("@acme/ghost-skill") assert "@acme/ghost-skill" in str(exc_info.value) @@ -112,11 +112,11 @@ class TestResolveRegistryRef: # tmp_path has no ./skills/ directory with ( - patch("crewai.skills.registry._is_noninteractive", return_value=False), + patch("crewai.experimental.skills.registry._is_noninteractive", return_value=False), patch.object(Path, "cwd", return_value=tmp_path), - patch("crewai.skills.registry.SkillCacheManager", return_value=mock_cache), + patch("crewai.experimental.skills.registry.SkillCacheManager", return_value=mock_cache), ): - from crewai.skills.registry import resolve_registry_ref + from crewai.experimental.skills.registry import resolve_registry_ref skill = resolve_registry_ref("@acme/cached-skill") assert skill.name == "cached-skill" diff --git a/lib/crewai/tests/skills/test_models_version.py b/lib/crewai/tests/skills/test_models_version.py deleted file mode 100644 index 262ca2a82..000000000 --- a/lib/crewai/tests/skills/test_models_version.py +++ /dev/null @@ -1,32 +0,0 @@ -"""Tests for the version field added to SkillFrontmatter.""" - -from __future__ import annotations - -import pytest -from pydantic import ValidationError - -from crewai.skills.models import SkillFrontmatter - - -class TestSkillFrontmatterVersion: - def test_version_defaults_to_none(self) -> None: - fm = SkillFrontmatter(name="my-skill", description="A skill.") - assert fm.version is None - - def test_version_can_be_set(self) -> None: - fm = SkillFrontmatter(name="my-skill", description="A skill.", version="1.2.3") - assert fm.version == "1.2.3" - - def test_existing_frontmatter_without_version_still_valid(self) -> None: - """Backward compat: existing SKILL.md files without version must still parse.""" - fm = SkillFrontmatter(name="old-skill", description="Old skill without version.") - assert fm.version is None - - def test_version_is_optional_string(self) -> None: - fm = SkillFrontmatter(name="my-skill", description="Desc.", version=None) - assert fm.version is None - - def test_frontmatter_is_frozen(self) -> None: - fm = SkillFrontmatter(name="my-skill", description="A skill.", version="1.0.0") - with pytest.raises(ValidationError): - fm.version = "2.0.0" # type: ignore[misc]