Co-Authored-By: Quentin Torroba <quentin.torroba@mistral.ai>
Co-Authored-By: Michel Thomazo <michel.thomazo@mistral.ai>
Co-Authored-By: Clément Drouin <clement.drouin@mistral.ai>
Co-Authored-By: Vincent Guilloux <vincent.guilloux@mistral.ai>
Co-Authored-By: Clément Siriex <clement.sirieix@mistral.ai>
Co-Authored-By: Kim-Adeline Miguel <kimadeline.miguel@mistral.ai>
Co-Authored-By: Thaddee Tyl <thaddee.tyl@gmail.com>
Co-Authored-By: David Brochart <david.brochart@gmail.com>
Co-Authored-By: Joseph Guhlin <joseph.guhlin@gmail.com>
Co-Authored-By: Thomas Kenbeek <thomaskenbeek@gmail.com>
Co-Authored-By: Remenby31 <baptiste.cruvellier31@gmail.com>
This commit is contained in:
Mathias Gesbert 2026-01-27 16:39:30 +01:00 committed by Mathias Gesbert
parent 79f215d91c
commit d33db9fff8
217 changed files with 16911 additions and 4305 deletions

View file

@ -35,6 +35,7 @@ def create_skill(
compatibility: str | None = None,
metadata: dict[str, str] | None = None,
allowed_tools: str | None = None,
user_invocable: bool | None = None,
body: str = "## Instructions\n\nTest instructions here.",
) -> Path:
skill_dir = skills_dir / name
@ -49,6 +50,8 @@ def create_skill(
frontmatter["metadata"] = metadata
if allowed_tools:
frontmatter["allowed-tools"] = allowed_tools
if user_invocable is not None:
frontmatter["user-invocable"] = user_invocable
yaml_str = yaml.dump(frontmatter, default_flow_style=False, allow_unicode=True)
content = f"---\n{yaml_str}---\n\n{body}"

View file

@ -273,3 +273,191 @@ class TestSkillManagerGetSkill:
def test_returns_none_for_unknown_skill(self, skill_manager: SkillManager) -> None:
assert skill_manager.get_skill("nonexistent-skill") is None
class TestSkillManagerFiltering:
def test_enabled_skills_filters_to_only_enabled(self, skills_dir: Path) -> None:
create_skill(skills_dir, "skill-a", "Skill A")
create_skill(skills_dir, "skill-b", "Skill B")
create_skill(skills_dir, "skill-c", "Skill C")
config = VibeConfig(
session_logging=SessionLoggingConfig(enabled=False),
system_prompt_id="tests",
include_project_context=False,
skill_paths=[skills_dir],
enabled_skills=["skill-a", "skill-c"],
)
manager = SkillManager(lambda: config)
skills = manager.available_skills
assert len(skills) == 2
assert "skill-a" in skills
assert "skill-b" not in skills
assert "skill-c" in skills
def test_disabled_skills_excludes_disabled(self, skills_dir: Path) -> None:
create_skill(skills_dir, "skill-a", "Skill A")
create_skill(skills_dir, "skill-b", "Skill B")
create_skill(skills_dir, "skill-c", "Skill C")
config = VibeConfig(
session_logging=SessionLoggingConfig(enabled=False),
system_prompt_id="tests",
include_project_context=False,
skill_paths=[skills_dir],
disabled_skills=["skill-b"],
)
manager = SkillManager(lambda: config)
skills = manager.available_skills
assert len(skills) == 2
assert "skill-a" in skills
assert "skill-b" not in skills
assert "skill-c" in skills
def test_enabled_skills_takes_precedence_over_disabled(
self, skills_dir: Path
) -> None:
create_skill(skills_dir, "skill-a", "Skill A")
create_skill(skills_dir, "skill-b", "Skill B")
config = VibeConfig(
session_logging=SessionLoggingConfig(enabled=False),
system_prompt_id="tests",
include_project_context=False,
skill_paths=[skills_dir],
enabled_skills=["skill-a"],
disabled_skills=["skill-a"], # Should be ignored
)
manager = SkillManager(lambda: config)
skills = manager.available_skills
assert len(skills) == 1
assert "skill-a" in skills
def test_glob_pattern_matching(self, skills_dir: Path) -> None:
create_skill(skills_dir, "search-code", "Search code")
create_skill(skills_dir, "search-docs", "Search docs")
create_skill(skills_dir, "other-skill", "Other skill")
config = VibeConfig(
session_logging=SessionLoggingConfig(enabled=False),
system_prompt_id="tests",
include_project_context=False,
skill_paths=[skills_dir],
enabled_skills=["search-*"],
)
manager = SkillManager(lambda: config)
skills = manager.available_skills
assert len(skills) == 2
assert "search-code" in skills
assert "search-docs" in skills
assert "other-skill" not in skills
def test_regex_pattern_matching(self, skills_dir: Path) -> None:
create_skill(skills_dir, "skill-v1", "Skill v1")
create_skill(skills_dir, "skill-v2", "Skill v2")
create_skill(skills_dir, "other-skill", "Other skill")
config = VibeConfig(
session_logging=SessionLoggingConfig(enabled=False),
system_prompt_id="tests",
include_project_context=False,
skill_paths=[skills_dir],
enabled_skills=["re:skill-v\\d+"],
)
manager = SkillManager(lambda: config)
skills = manager.available_skills
assert len(skills) == 2
assert "skill-v1" in skills
assert "skill-v2" in skills
assert "other-skill" not in skills
def test_get_skill_respects_filtering(self, skills_dir: Path) -> None:
create_skill(skills_dir, "enabled-skill", "Enabled")
create_skill(skills_dir, "disabled-skill", "Disabled")
config = VibeConfig(
session_logging=SessionLoggingConfig(enabled=False),
system_prompt_id="tests",
include_project_context=False,
skill_paths=[skills_dir],
disabled_skills=["disabled-skill"],
)
manager = SkillManager(lambda: config)
assert manager.get_skill("enabled-skill") is not None
assert manager.get_skill("disabled-skill") is None
class TestSkillUserInvocable:
def test_user_invocable_defaults_to_true(self, skills_dir: Path) -> None:
create_skill(skills_dir, "default-skill", "A default skill")
config = VibeConfig(
session_logging=SessionLoggingConfig(enabled=False),
system_prompt_id="tests",
include_project_context=False,
skill_paths=[skills_dir],
)
manager = SkillManager(lambda: config)
skill = manager.get_skill("default-skill")
assert skill is not None
assert skill.user_invocable is True
def test_user_invocable_can_be_set_to_false(self, skills_dir: Path) -> None:
create_skill(skills_dir, "hidden-skill", "A hidden skill", user_invocable=False)
config = VibeConfig(
session_logging=SessionLoggingConfig(enabled=False),
system_prompt_id="tests",
include_project_context=False,
skill_paths=[skills_dir],
)
manager = SkillManager(lambda: config)
skill = manager.get_skill("hidden-skill")
assert skill is not None
assert skill.user_invocable is False
def test_user_invocable_can_be_explicitly_set_to_true(
self, skills_dir: Path
) -> None:
create_skill(
skills_dir, "explicit-skill", "An explicit skill", user_invocable=True
)
config = VibeConfig(
session_logging=SessionLoggingConfig(enabled=False),
system_prompt_id="tests",
include_project_context=False,
skill_paths=[skills_dir],
)
manager = SkillManager(lambda: config)
skill = manager.get_skill("explicit-skill")
assert skill is not None
assert skill.user_invocable is True
def test_mixed_user_invocable_skills(self, skills_dir: Path) -> None:
create_skill(skills_dir, "visible-skill", "Visible", user_invocable=True)
create_skill(skills_dir, "hidden-skill", "Hidden", user_invocable=False)
create_skill(skills_dir, "default-skill", "Default")
config = VibeConfig(
session_logging=SessionLoggingConfig(enabled=False),
system_prompt_id="tests",
include_project_context=False,
skill_paths=[skills_dir],
)
manager = SkillManager(lambda: config)
skills = manager.available_skills
assert len(skills) == 3
assert skills["visible-skill"].user_invocable is True
assert skills["hidden-skill"].user_invocable is False
assert skills["default-skill"].user_invocable is True

View file

@ -18,6 +18,7 @@ class TestSkillMetadata:
assert meta.compatibility is None
assert meta.metadata == {}
assert meta.allowed_tools == []
assert meta.user_invocable is True
def test_creates_with_all_fields(self) -> None:
meta = SkillMetadata(
@ -27,6 +28,7 @@ class TestSkillMetadata:
compatibility="Requires git",
metadata={"author": "Test Author", "version": "1.0"},
allowed_tools=["bash", "read_file"],
user_invocable=False,
)
assert meta.name == "full-skill"
@ -35,6 +37,7 @@ class TestSkillMetadata:
assert meta.compatibility == "Requires git"
assert meta.metadata == {"author": "Test Author", "version": "1.0"}
assert meta.allowed_tools == ["bash", "read_file"]
assert meta.user_invocable is False
def test_raises_error_for_uppercase_name(self) -> None:
with pytest.raises(ValidationError) as exc_info:
@ -144,6 +147,7 @@ class TestSkillInfo:
compatibility="git, docker",
metadata={"author": "Test"},
allowed_tools=["bash"],
user_invocable=False,
skill_path=skill_path,
)
@ -153,6 +157,7 @@ class TestSkillInfo:
assert info.compatibility == "git, docker"
assert info.metadata == {"author": "Test"}
assert info.allowed_tools == ["bash"]
assert info.user_invocable is False
assert info.skill_path == skill_path
assert info.skill_dir == skill_path.parent.resolve()
@ -179,6 +184,7 @@ class TestSkillInfo:
compatibility="Requires Python 3.12",
metadata={"key": "value"},
allowed_tools=["bash", "grep"],
user_invocable=False,
)
info = SkillInfo.from_metadata(meta, skill_path)
@ -186,3 +192,4 @@ class TestSkillInfo:
assert info.compatibility == meta.compatibility
assert info.metadata == meta.metadata
assert info.allowed_tools == meta.allowed_tools
assert info.user_invocable == meta.user_invocable