mirror of
https://github.com/github/spec-kit.git
synced 2026-07-06 14:01:01 +08:00
* fix(integrations): share skills hook note post-processing * fix(integrations): tighten skill post-processing Apply skill content post-processing before the initial write, use an exact hook-note sentinel for idempotence, and route Copilot skill post-processing through the shared helper before adding mode frontmatter. * Make hook note injection per instruction * Deduplicate Codex hook note processing --------- Co-authored-by: Puneet Dixit <236133619+puneetdixit200@users.noreply.github.com> Co-authored-by: Puneet Dixit <puneetdixit200@users.noreply.github.com>
111 lines
3.4 KiB
Python
111 lines
3.4 KiB
Python
"""
|
|
Mistral Vibe CLI integration — skills-based agent.
|
|
|
|
Vibe uses ``.vibe/skills/speckit-<name>/SKILL.md`` layout (enforced since v2.0.0).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
from ..base import IntegrationOption, SkillsIntegration
|
|
from ..manifest import IntegrationManifest
|
|
|
|
|
|
class VibeIntegration(SkillsIntegration):
|
|
key = "vibe"
|
|
config = {
|
|
"name": "Mistral Vibe",
|
|
"folder": ".vibe/",
|
|
"commands_subdir": "skills",
|
|
"install_url": "https://github.com/mistralai/mistral-vibe",
|
|
"requires_cli": True,
|
|
}
|
|
registrar_config = {
|
|
"dir": ".vibe/skills",
|
|
"format": "markdown",
|
|
"args": "$ARGUMENTS",
|
|
"extension": "/SKILL.md",
|
|
}
|
|
context_file = "AGENTS.md"
|
|
|
|
@classmethod
|
|
def options(cls) -> list[IntegrationOption]:
|
|
return [
|
|
IntegrationOption(
|
|
"--skills",
|
|
is_flag=True,
|
|
default=True,
|
|
help="Install as agent skills",
|
|
),
|
|
]
|
|
|
|
@staticmethod
|
|
def _inject_frontmatter_flag(content: str, key: str, value: str = "true") -> str:
|
|
"""
|
|
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)
|
|
|
|
def post_process_skill_content(self, content: str) -> str:
|
|
"""
|
|
Inject shared hook guidance and Vibe-specific frontmatter flags:
|
|
- user-invocable: allows the skill to be invoked by the user (not just other agents)
|
|
"""
|
|
updated = super().post_process_skill_content(content)
|
|
updated = self._inject_frontmatter_flag(updated, "user-invocable")
|
|
return updated
|
|
|
|
def setup(
|
|
self,
|
|
project_root: Path,
|
|
manifest: IntegrationManifest,
|
|
parsed_options: dict[str, Any] | None = None,
|
|
**opts: Any,
|
|
) -> list[Path]:
|
|
"""Install Vibe skills then inject Vibe-specific flags"""
|
|
import click
|
|
|
|
click.secho(
|
|
"Warning: The .vibe/skills layout requires Mistral Vibe v2.0.0 or newer. "
|
|
"Please ensure your installation is up to date.",
|
|
fg="yellow",
|
|
err=True,
|
|
)
|
|
|
|
return super().setup(project_root, manifest, parsed_options=parsed_options, **opts)
|