Skip to content

feat(vibe): migrate to SkillsIntegration from the old prompts-based MarkdownIntegration#2336

Open
Fango2007 wants to merge 2 commits intogithub:mainfrom
Fango2007:feat/integrate-mistral-vibe-skills
Open

feat(vibe): migrate to SkillsIntegration from the old prompts-based MarkdownIntegration#2336
Fango2007 wants to merge 2 commits intogithub:mainfrom
Fango2007:feat/integrate-mistral-vibe-skills

Conversation

@Fango2007
Copy link
Copy Markdown

@Fango2007 Fango2007 commented Apr 23, 2026

PR purpose : Updating Mistral Vibe integration

Since v2.0.0, Mistral Vibe supports custom slash commands through skills system https://github.com/mistralai/mistral-vibe#custom-slash-commands-via-skills

Summary of changes

Switches VibeIntegration from the old prompts-based MarkdownIntegration to SkillsIntegration, adopting the .vibe/skills/speckit-/SKILL.md layout required by Mistral Vibe v2.0.0+.
Post-processes each generated SKILL.md to inject user-invocable: true so skills are directly callable by users, not just by other agents.

Testing

  • Tested locally with uv run specify --help
  • Ran existing tests with uv sync && uv run pytest
uv run python -m pytest tests/test_agent_config_consistency.py -q
(specify-cli) MacBook-Pro:spec-kit *$ uv run python -m pytest tests/test_agent_config_consistency.py -q
============================== test session starts =========================
platform darwin -- Python 3.12.12, pytest-9.0.3, pluggy-1.6.0
rootdir: /Users/*/*/Projects/codebase/clone/spec-kit
configfile: pyproject.toml
plugins: cov-7.1.0
collected 24 items                                                                                                                                                                                                                                         

tests/test_agent_config_consistency.py ........................    [100%]
================ 24 passed in 0.06s =======================================
  • Manual test results
**Agent**: [Vibe]  |  **OS/Shell**: [macOS/zsh]

| Command tested | Notes |
|----------------|-------|
| `/speckit.constitution` | OK |
| `/speckit.specify` | OK |
  • AI review
  • Required tests: all passed
┌───────────────────────────────────────────────┬───────────────────────┬──────┬────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐   
  │                 Changed file                  │        Affects        │ Test │                                                                                          Why                                                                                           │   
  ├───────────────────────────────────────────────┼───────────────────────┼──────┼────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
  │ src/specify_cli/integrations/vibe/__init__.py │ specify init --ai     │ T1   │ Python CLI src → test the affected CLI command; vibe integration controls how .vibe/skills/*/SKILL.md files are scaffolded (adds user-invocable: true frontmatter via                  │   
  │                                               │ vibe                  │      │ post_process_skill_content)                                                                                                                                                            │ 
  ├───────────────────────────────────────────────┼───────────────────────┼──────┼────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤   
  │ src/specify_cli/integrations/vibe/__init__.py │ /speckit.specify      │ T2   │ Init/scaffolding change → mapping rule requires at minimum /speckit.specify to verify the scaffolded skills work end-to-end in a live agent session; depends on T1                     │
  ├───────────────────────────────────────────────┼───────────────────────┼──────┼────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤   
  │ tests/integrations/test_integration_vibe.py   │ specify init --ai     │ T1   │ Tests the same vibe integration code path; no additional slash-command surface beyond what the source file already maps to                                                             │ 
  │                                               │ vibe                  │      │                                                                                                                                                                                        │   
  └───────────────────────────────────────────────┴───────────────────────┴──────┴────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ 
T1: specify init --ai vibe (CLI command) — The VibeIntegration.setup() and post_process_skill_content() changes affect every SKILL.md file written during init. Run uv run specify init <tmp-dir>/speckit-vibe-test --ai vibe --offline, then verify that every .vibe/skills/speckit-*/SKILL.md file contains user-invocable: true injected in its frontmatter block.                                                                                                                                                            
T2: /speckit.specify — Minimum slash-command test required for any init/scaffolding change. Open the T1-initialized project in Mistral Vibe and invoke /speckit.specify with a sample feature description. Confirm the command is user-invocable (Vibe surfaces it as a callable skill) and completes successfully, producing specs/<dir>/spec.md and the quality checklist. Requires T1.
  • Tested with a sample project

AI Disclosure

  • I did use AI assistance (describe below)
    This PR was drafted with the help of Claude. The code was then manually refined, largely by aligning it with the Claude integration. It also helped me understand the underlying logic and validate the implementation.

…ontmatter

Switches VibeIntegration from the old prompts-based MarkdownIntegration to SkillsIntegration, adopting the .vibe/skills/speckit-<name>/SKILL.md layout required by Mistral Vibe v2.0.0+. Post-processes each generated SKILL.md to inject `user-invocable: true` so skills are directly callable by users, not  just by other agents.
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Migrates the Mistral Vibe integration from the legacy prompts-based Markdown command layout to the skills-based speckit-<name>/SKILL.md layout required by newer Vibe versions, and post-processes generated skills to add Vibe-specific frontmatter.

Changes:

  • Switch VibeIntegration to subclass SkillsIntegration, updating config/registrar settings to .vibe/skills/speckit-<name>/SKILL.md.
  • Add Vibe-specific post_process_skill_content() and a setup() post-pass to inject user-invocable: true.
  • Update the Vibe integration test mixin from Markdown-based tests to skills-based tests.
Show a summary per file
File Description
src/specify_cli/integrations/vibe/__init__.py Converts Vibe integration to skills layout and injects user-invocable into generated SKILL.md files.
tests/integrations/test_integration_vibe.py Updates integration test harness to validate Vibe as a SkillsIntegration.

Copilot's findings

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

  • Files reviewed: 2/2 changed files
  • Comments generated: 2

Comment on lines +85 to +91
def post_process_skill_content(self, content: str) -> str:
"""
Inject Vibe-specific frontmatter flags:
- user-invocable: allows the skill to be invoked by the user (not just other agents)
"""
updated = self._inject_frontmatter_flag(content, "user-invocable")
return updated
Copy link

Copilot AI Apr 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new user-invocable injection logic isn’t covered by tests for the Vibe integration. Since this behavior is the core purpose of the change (skills should be directly callable), please add an assertion in the Vibe integration tests that generated .vibe/skills/speckit-*/SKILL.md files contain user-invocable: true in frontmatter (similar to the existing Claude integration tests).

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Acknowledged.
An assertion test has been added in the Vibe Integration tests.

MacBook-Pro:spec-kit *$ python -m pytest tests/integrations/test_integration_vibe.py -v 2>&1

================================== test session starts ==============
platform darwin -- Python 3.12.12, pytest-9.0.3, pluggy-1.6.0 -- /Users/*/*/Projects/codebase/clone/spec-kit/.venv/bin/python
cachedir: .pytest_cache
rootdir: /Users/Fango/DEV/Projects/codebase/clone/spec-kit
configfile: pyproject.toml
plugins: cov-7.1.0
collected 27 items                                                                                                                                                                                                                                            

tests/integrations/test_integration_vibe.py::TestVibeIntegration::test_registered <- tests/integrations/test_integration_base_skills.py PASSED                                                                                                          [  3%]
tests/integrations/test_integration_vibe.py::TestVibeIntegration::test_is_skills_integration <- tests/integrations/test_integration_base_skills.py PASSED                                                                                               [  7%]
tests/integrations/test_integration_vibe.py::TestVibeIntegration::test_config_folder <- tests/integrations/test_integration_base_skills.py PASSED                                                                                                       [ 11%]
tests/integrations/test_integration_vibe.py::TestVibeIntegration::test_config_commands_subdir <- tests/integrations/test_integration_base_skills.py PASSED                                                                                              [ 14%]
tests/integrations/test_integration_vibe.py::TestVibeIntegration::test_registrar_config <- tests/integrations/test_integration_base_skills.py PASSED                                                                                                    [ 18%]
tests/integrations/test_integration_vibe.py::TestVibeIntegration::test_context_file <- tests/integrations/test_integration_base_skills.py PASSED                                                                                                        [ 22%]
tests/integrations/test_integration_vibe.py::TestVibeIntegration::test_setup_creates_files <- tests/integrations/test_integration_base_skills.py PASSED                                                                                                 [ 25%]
tests/integrations/test_integration_vibe.py::TestVibeIntegration::test_setup_writes_to_correct_directory <- tests/integrations/test_integration_base_skills.py PASSED                                                                                   [ 29%]
tests/integrations/test_integration_vibe.py::TestVibeIntegration::test_skill_directory_structure <- tests/integrations/test_integration_base_skills.py PASSED                                                                                           [ 33%]
tests/integrations/test_integration_vibe.py::TestVibeIntegration::test_skill_frontmatter_structure <- tests/integrations/test_integration_base_skills.py PASSED                                                                                         [ 37%]
tests/integrations/test_integration_vibe.py::TestVibeIntegration::test_skill_uses_template_descriptions <- tests/integrations/test_integration_base_skills.py PASSED                                                                                    [ 40%]
tests/integrations/test_integration_vibe.py::TestVibeIntegration::test_templates_are_processed <- tests/integrations/test_integration_base_skills.py PASSED                                                                                             [ 44%]
tests/integrations/test_integration_vibe.py::TestVibeIntegration::test_skill_body_has_content <- tests/integrations/test_integration_base_skills.py PASSED                                                                                              [ 48%]
tests/integrations/test_integration_vibe.py::TestVibeIntegration::test_plan_references_correct_context_file <- tests/integrations/test_integration_base_skills.py PASSED                                                                                [ 51%]
tests/integrations/test_integration_vibe.py::TestVibeIntegration::test_all_files_tracked_in_manifest <- tests/integrations/test_integration_base_skills.py PASSED                                                                                       [ 55%]
tests/integrations/test_integration_vibe.py::TestVibeIntegration::test_install_uninstall_roundtrip <- tests/integrations/test_integration_base_skills.py PASSED                                                                                         [ 59%]
tests/integrations/test_integration_vibe.py::TestVibeIntegration::test_modified_file_survives_uninstall <- tests/integrations/test_integration_base_skills.py PASSED                                                                                    [ 62%]
tests/integrations/test_integration_vibe.py::TestVibeIntegration::test_pre_existing_skills_not_removed <- tests/integrations/test_integration_base_skills.py PASSED                                                                                     [ 66%]
tests/integrations/test_integration_vibe.py::TestVibeIntegration::test_setup_upserts_context_section <- tests/integrations/test_integration_base_skills.py PASSED                                                                                       [ 70%]
tests/integrations/test_integration_vibe.py::TestVibeIntegration::test_teardown_removes_context_section <- tests/integrations/test_integration_base_skills.py PASSED                                                                                    [ 74%]
tests/integrations/test_integration_vibe.py::TestVibeIntegration::test_ai_flag_auto_promotes <- tests/integrations/test_integration_base_skills.py PASSED                                                                                               [ 77%]
tests/integrations/test_integration_vibe.py::TestVibeIntegration::test_integration_flag_creates_files <- tests/integrations/test_integration_base_skills.py PASSED                                                                                      [ 81%]
tests/integrations/test_integration_vibe.py::TestVibeIntegration::test_init_options_includes_context_file <- tests/integrations/test_integration_base_skills.py PASSED                                                                                  [ 85%]
tests/integrations/test_integration_vibe.py::TestVibeIntegration::test_options_include_skills_flag <- tests/integrations/test_integration_base_skills.py PASSED                                                                                         [ 88%]
tests/integrations/test_integration_vibe.py::TestVibeIntegration::test_complete_file_inventory_sh <- tests/integrations/test_integration_base_skills.py PASSED                                                                                          [ 92%]
tests/integrations/test_integration_vibe.py::TestVibeIntegration::test_complete_file_inventory_ps <- tests/integrations/test_integration_base_skills.py PASSED                                                                                          [ 96%]
tests/integrations/test_integration_vibe.py::TestVibeUserInvocable::test_all_skills_have_user_invocable PASSED                                                                                                                                          [100%]

====================== 27 passed in 0.37s ====

(specify-cli) MacBook-Pro:spec-kit Fango$ uv run python -m pytest tests/test_agent_config_consistency.py -q
================================== test session starts ==================================
platform darwin -- Python 3.12.12, pytest-9.0.3, pluggy-1.6.0
rootdir: /Users/Fango/DEV/Projects/codebase/clone/spec-kit
configfile: pyproject.toml
plugins: cov-7.1.0
collected 24 items                                                                                                                                                                                                                                            

tests/test_agent_config_consistency.py ........................                                                                                                                                                                                         [100%]

================================== 24 passed in 0.05s ===

Comment on lines +47 to +84
Insert ``key: value`` before the closing ``---`` if not already present.
Value: true by default
"""
lines = content.splitlines(keepends=True)

# Pre-scan: bail out if already present in frontmatter
dash_count = 0
for line in lines:
stripped = line.rstrip("\n\r")
if stripped == "---":
dash_count += 1
if dash_count == 2:
break
continue
if dash_count == 1 and stripped.startswith(f"{key}:"):
return content

# Inject before the closing --- of frontmatter
out: list[str] = []
dash_count = 0
injected = False
for line in lines:
stripped = line.rstrip("\n\r")
if stripped == "---":
dash_count += 1
if dash_count == 2 and not injected:
if line.endswith("\r\n"):
eol = "\r\n"
elif line.endswith("\n"):
eol = "\n"
else:
eol = ""
out.append(f"{key}: {value}{eol}")
injected = True
out.append(line)
return "".join(out)


Copy link

Copilot AI Apr 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_inject_frontmatter_flag() duplicates the same frontmatter-injection helper that already exists in ClaudeIntegration. To avoid drift between integrations (and reduce future bug-fix duplication), consider moving this helper to a shared location (e.g., SkillsIntegration/IntegrationBase) and reusing it from both integrations.

Suggested change
Insert ``key: value`` before the closing ``---`` if not already present.
Value: true by default
"""
lines = content.splitlines(keepends=True)
# Pre-scan: bail out if already present in frontmatter
dash_count = 0
for line in lines:
stripped = line.rstrip("\n\r")
if stripped == "---":
dash_count += 1
if dash_count == 2:
break
continue
if dash_count == 1 and stripped.startswith(f"{key}:"):
return content
# Inject before the closing --- of frontmatter
out: list[str] = []
dash_count = 0
injected = False
for line in lines:
stripped = line.rstrip("\n\r")
if stripped == "---":
dash_count += 1
if dash_count == 2 and not injected:
if line.endswith("\r\n"):
eol = "\r\n"
elif line.endswith("\n"):
eol = "\n"
else:
eol = ""
out.append(f"{key}: {value}{eol}")
injected = True
out.append(line)
return "".join(out)
Reuse the shared frontmatter injection helper from the base integration
to avoid drift between integrations.
"""
return SkillsIntegration._inject_frontmatter_flag(content, key, value)

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Acknowledged.
Moving _inject_frontmatter_flag to SkillsIntegration would require updating ClaudeIntegration class. This code is maintained by someone else and has significantly more complexity than VibeIntegration (argument hints, hook notes, model invocation flags).
To avoid risk to that integration, the helper is kept local to VibeIntegration, at least for now. A follow-up refactor is up to the Claude integration maintainer who can consolidate it if desired.

Copy link
Copy Markdown
Collaborator

@mnriem mnriem left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please address Copilot feedback. If it is truly shareable code between all integrations using skills please pull it up into the SkillIngration

@Fango2007
Copy link
Copy Markdown
Author

Done

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot's findings

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

  • Files reviewed: 2/2 changed files
  • Comments generated: 1

Comment on lines +27 to +28
content = f.read_text(encoding="utf-8")
parts = content.split("---", 2)
Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The frontmatter parsing here can raise an IndexError if a SKILL.md is generated without a valid --- ... --- block (since parts[1] is accessed unconditionally). Consider asserting content.startswith('---') and len(parts) >= 3 (with a helpful failure message) before calling yaml.safe_load, so the test fails cleanly when frontmatter is missing/malformed.

Suggested change
content = f.read_text(encoding="utf-8")
parts = content.split("---", 2)
content = f.read_text(encoding="utf-8")
assert content.startswith("---"), (
f"{f.parent.name}/SKILL.md is missing the opening frontmatter delimiter '---'"
)
parts = content.split("---", 2)
assert len(parts) >= 3, (
f"{f.parent.name}/SKILL.md has malformed frontmatter; expected a '--- ... ---' block"
)

Copilot uses AI. Check for mistakes.
@mnriem
Copy link
Copy Markdown
Collaborator

mnriem commented Apr 24, 2026

@Fango2007 Please address Copilot feedback

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants