mirror of
https://github.com/github/spec-kit.git
synced 2026-07-07 06:35:06 +08:00
feat: add ZCode (Z.AI) integration (#3063)
* feat: add ZCode (Z.AI) integration Add a skills-based integration for ZCode, Z.AI's Claude-Code-style agent. ZCode uses the same SKILL.md layout as Claude Code, so spec-kit installs workflows into .zcode/skills/speckit-<name>/SKILL.md, invoked in chat as $speckit-<name>. - ZcodeIntegration(SkillsIntegration) with .zcode/ folder and --skills option - Register in INTEGRATION_REGISTRY - Catalog entry (tags: cli, skills, z-ai) - Tests via SkillsIntegrationTests mixin - Document in integrations reference and README Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix: render $speckit-* invocations for ZCode skills ZCode is documented as a skills agent invoked with $speckit-<command>, but the central invocation rendering only special-cased codex, so specify init Next Steps and extension hooks rendered the dotted /speckit.<command> form instead. Centralize the $speckit-* decision in a DOLLAR_SKILLS_AGENTS set with an is_dollar_skills_agent() helper, and route both init Next Steps and HookExecutor._render_hook_invocation through it. Add ZCode invocation regression tests mirroring the existing Codex/Kimi coverage. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -8,6 +8,9 @@ and ``specify init``'s next-steps output stay consistent.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
# Agents that render $speckit-<name> (chat invocation) when in skills mode.
|
||||
DOLLAR_SKILLS_AGENTS: frozenset[str] = frozenset({"codex", "zcode"})
|
||||
|
||||
# Agents that always render /speckit-<name>, regardless of ai_skills.
|
||||
ALWAYS_SLASH_AGENTS: frozenset[str] = frozenset({"devin", "trae", "zed"})
|
||||
|
||||
@@ -26,6 +29,17 @@ CONDITIONAL_SLASH_AGENTS: frozenset[str] = frozenset(
|
||||
)
|
||||
|
||||
|
||||
def is_dollar_skills_agent(selected_ai: str | None, ai_skills_enabled: bool) -> bool:
|
||||
"""Return ``True`` if *selected_ai* uses ``$speckit-<name>`` invocations.
|
||||
|
||||
Agents in `DOLLAR_SKILLS_AGENTS` (e.g. ``codex``, ``zcode``) render
|
||||
``$speckit-<name>`` chat invocations when installed in skills mode.
|
||||
"""
|
||||
if not isinstance(selected_ai, str):
|
||||
return False
|
||||
return selected_ai in DOLLAR_SKILLS_AGENTS and ai_skills_enabled
|
||||
|
||||
|
||||
def is_slash_skills_agent(selected_ai: str | None, ai_skills_enabled: bool) -> bool:
|
||||
"""Return ``True`` if *selected_ai* uses ``/speckit-<name>`` invocations.
|
||||
|
||||
|
||||
@@ -693,6 +693,7 @@ def register(app: typer.Typer) -> None:
|
||||
) or getattr(resolved_integration, "_skills_mode", False)
|
||||
|
||||
codex_skill_mode = selected_ai == "codex" and _is_skills_integration
|
||||
zcode_skill_mode = selected_ai == "zcode" and _is_skills_integration
|
||||
claude_skill_mode = selected_ai == "claude" and _is_skills_integration
|
||||
kimi_skill_mode = selected_ai == "kimi"
|
||||
agy_skill_mode = selected_ai == "agy" and _is_skills_integration
|
||||
@@ -706,6 +707,7 @@ def register(app: typer.Typer) -> None:
|
||||
cline_skill_mode = selected_ai == "cline"
|
||||
native_skill_mode = (
|
||||
codex_skill_mode
|
||||
or zcode_skill_mode
|
||||
or claude_skill_mode
|
||||
or kimi_skill_mode
|
||||
or agy_skill_mode
|
||||
@@ -721,6 +723,11 @@ def register(app: typer.Typer) -> None:
|
||||
f"{step_num}. Start Codex in this project directory; spec-kit skills were installed to [cyan].agents/skills[/cyan]"
|
||||
)
|
||||
step_num += 1
|
||||
if zcode_skill_mode:
|
||||
steps_lines.append(
|
||||
f"{step_num}. Start ZCode in this project directory; spec-kit skills were installed to [cyan].zcode/skills[/cyan]"
|
||||
)
|
||||
step_num += 1
|
||||
if claude_skill_mode:
|
||||
steps_lines.append(
|
||||
f"{step_num}. Start Claude in this project directory; spec-kit skills were installed to [cyan].claude/skills[/cyan]"
|
||||
@@ -743,7 +750,10 @@ def register(app: typer.Typer) -> None:
|
||||
step_num += 1
|
||||
usage_label = "skills" if native_skill_mode else "slash commands"
|
||||
|
||||
from .._invocation_style import is_slash_skills_agent as _is_slash_skills_agent
|
||||
from .._invocation_style import (
|
||||
is_dollar_skills_agent as _is_dollar_skills_agent,
|
||||
is_slash_skills_agent as _is_slash_skills_agent,
|
||||
)
|
||||
|
||||
# `_is_skills_integration` means the integration is installed in
|
||||
# skills mode, which is the semantic equivalent of `ai_skills_enabled`
|
||||
@@ -751,7 +761,7 @@ def register(app: typer.Typer) -> None:
|
||||
_ai_skills_enabled = _is_skills_integration
|
||||
|
||||
def _display_cmd(name: str) -> str:
|
||||
if codex_skill_mode:
|
||||
if _is_dollar_skills_agent(selected_ai, _ai_skills_enabled):
|
||||
return f"$speckit-{name}"
|
||||
if kimi_skill_mode:
|
||||
return f"/skill:speckit-{name}"
|
||||
|
||||
@@ -27,7 +27,7 @@ from packaging import version as pkg_version
|
||||
from packaging.specifiers import InvalidSpecifier, SpecifierSet
|
||||
|
||||
from ._init_options import is_ai_skills_enabled
|
||||
from ._invocation_style import is_slash_skills_agent
|
||||
from ._invocation_style import is_dollar_skills_agent, is_slash_skills_agent
|
||||
from ._utils import dump_frontmatter, relative_extension_path_violation
|
||||
from .catalogs import CatalogEntry as BaseCatalogEntry
|
||||
from .catalogs import CatalogStackBase
|
||||
@@ -2886,12 +2886,12 @@ class HookExecutor:
|
||||
selected_ai = init_options.get("ai")
|
||||
ai_skills_enabled = is_ai_skills_enabled(init_options)
|
||||
|
||||
codex_skill_mode = selected_ai == "codex" and ai_skills_enabled
|
||||
dollar_skill_mode = is_dollar_skills_agent(selected_ai, ai_skills_enabled)
|
||||
kimi_skill_mode = selected_ai == "kimi"
|
||||
cline_mode = selected_ai == "cline"
|
||||
|
||||
skill_name = self._skill_name_from_command(command_id)
|
||||
if codex_skill_mode and skill_name:
|
||||
if dollar_skill_mode and skill_name:
|
||||
return f"${skill_name}"
|
||||
if kimi_skill_mode and skill_name:
|
||||
return f"/skill:{skill_name}"
|
||||
|
||||
@@ -80,6 +80,7 @@ def _register_builtins() -> None:
|
||||
from .trae import TraeIntegration
|
||||
from .vibe import VibeIntegration
|
||||
from .windsurf import WindsurfIntegration
|
||||
from .zcode import ZcodeIntegration
|
||||
from .zed import ZedIntegration
|
||||
|
||||
# -- Registration (alphabetical) --------------------------------------
|
||||
@@ -116,6 +117,7 @@ def _register_builtins() -> None:
|
||||
_register(TraeIntegration())
|
||||
_register(VibeIntegration())
|
||||
_register(WindsurfIntegration())
|
||||
_register(ZcodeIntegration())
|
||||
_register(ZedIntegration())
|
||||
|
||||
|
||||
|
||||
43
src/specify_cli/integrations/zcode/__init__.py
Normal file
43
src/specify_cli/integrations/zcode/__init__.py
Normal file
@@ -0,0 +1,43 @@
|
||||
"""ZCode integration — skills-based agent (Z.AI).
|
||||
|
||||
ZCode uses the ``.zcode/skills/speckit-<name>/SKILL.md`` layout, matching
|
||||
the Claude Code skill format. Skills are invoked in chat with
|
||||
``$speckit-<name>``. Z.AI recommends skills (over simple ``/`` commands)
|
||||
for template- and script-driven workflows such as spec-kit.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from ..base import IntegrationOption, SkillsIntegration
|
||||
|
||||
|
||||
class ZcodeIntegration(SkillsIntegration):
|
||||
"""Integration for ZCode CLI (Z.AI)."""
|
||||
|
||||
key = "zcode"
|
||||
config = {
|
||||
"name": "ZCode",
|
||||
"folder": ".zcode/",
|
||||
"commands_subdir": "skills",
|
||||
"install_url": "https://zcode.z.ai/",
|
||||
"requires_cli": True,
|
||||
}
|
||||
registrar_config = {
|
||||
"dir": ".zcode/skills",
|
||||
"format": "markdown",
|
||||
"args": "$ARGUMENTS",
|
||||
"extension": "/SKILL.md",
|
||||
}
|
||||
context_file = "ZCODE.md"
|
||||
multi_install_safe = True
|
||||
|
||||
@classmethod
|
||||
def options(cls) -> list[IntegrationOption]:
|
||||
return [
|
||||
IntegrationOption(
|
||||
"--skills",
|
||||
is_flag=True,
|
||||
default=True,
|
||||
help="Install as agent skills (default for ZCode)",
|
||||
),
|
||||
]
|
||||
Reference in New Issue
Block a user