mirror of
https://github.com/github/spec-kit.git
synced 2026-03-20 04:13:08 +00:00
feat: migrate Codex/agy init to native skills workflow (#1906)
* feat: migrate codex and agy to native skills flow * fix: harden codex skill frontmatter and script fallback * fix: clarify skills separator default expansion * fix: rewrite agent_scripts paths for codex skills * fix: align kimi guidance and platform-aware codex fallback
This commit is contained in:
@@ -31,7 +31,6 @@ import sys
|
||||
import zipfile
|
||||
import tempfile
|
||||
import shutil
|
||||
import shlex
|
||||
import json
|
||||
import json5
|
||||
import stat
|
||||
@@ -172,8 +171,8 @@ AGENT_CONFIG = {
|
||||
},
|
||||
"codex": {
|
||||
"name": "Codex CLI",
|
||||
"folder": ".codex/",
|
||||
"commands_subdir": "prompts", # Special: uses prompts/ not commands/
|
||||
"folder": ".agents/",
|
||||
"commands_subdir": "skills", # Codex now uses project skills directly
|
||||
"install_url": "https://github.com/openai/codex",
|
||||
"requires_cli": True,
|
||||
},
|
||||
@@ -1211,6 +1210,9 @@ AGENT_SKILLS_DIR_OVERRIDES = {
|
||||
# Default skills directory for agents not in AGENT_CONFIG
|
||||
DEFAULT_SKILLS_DIR = ".agents/skills"
|
||||
|
||||
# Agents whose downloaded template already contains skills in the final layout.
|
||||
NATIVE_SKILLS_AGENTS = {"codex", "kimi"}
|
||||
|
||||
# Enhanced descriptions for each spec-kit command skill
|
||||
SKILL_DESCRIPTIONS = {
|
||||
"specify": "Create or update feature specifications from natural language descriptions. Use when starting new features or refining requirements. Generates spec.md with user stories, functional requirements, and acceptance criteria following spec-driven development methodology.",
|
||||
@@ -1339,8 +1341,6 @@ def install_ai_skills(project_path: Path, selected_ai: str, tracker: StepTracker
|
||||
command_name = command_name[len("speckit."):]
|
||||
if command_name.endswith(".agent"):
|
||||
command_name = command_name[:-len(".agent")]
|
||||
# Kimi CLI discovers skills by directory name and invokes them as
|
||||
# /skill:<name> — use dot separator to match packaging convention.
|
||||
if selected_ai == "kimi":
|
||||
skill_name = f"speckit.{command_name}"
|
||||
else:
|
||||
@@ -1416,20 +1416,45 @@ def install_ai_skills(project_path: Path, selected_ai: str, tracker: StepTracker
|
||||
return installed_count > 0 or skipped_count > 0
|
||||
|
||||
|
||||
def _handle_agy_deprecation(console: Console) -> None:
|
||||
"""
|
||||
Print the deprecation error for the Antigravity (agy) agent and exit.
|
||||
def _has_bundled_skills(project_path: Path, selected_ai: str) -> bool:
|
||||
"""Return True when a native-skills agent has spec-kit bundled skills."""
|
||||
skills_dir = _get_skills_dir(project_path, selected_ai)
|
||||
if not skills_dir.is_dir():
|
||||
return False
|
||||
|
||||
Context for future AI reviewers (e.g., GitHub Copilot):
|
||||
- Prior to Antigravity v1.20.5, users could rely on explicit agent command definitions generated by this tool.
|
||||
- v1.20.5 removed this "command support" in favor of "Agent Skills".
|
||||
- `specify_cli` handles this by throwing a fail-fast deprecation error if `--ai agy` is
|
||||
invoked without the accompanying `--ai-skills` flag. Users are instructed to add
|
||||
the skills flag to generate agent skills templates instead.
|
||||
"""
|
||||
console.print("\n[red]Error:[/red] Explicit command support was deprecated in Antigravity version 1.20.5.")
|
||||
pattern = "speckit.*/SKILL.md" if selected_ai == "kimi" else "speckit-*/SKILL.md"
|
||||
return any(skills_dir.glob(pattern))
|
||||
|
||||
|
||||
AGENT_SKILLS_MIGRATIONS = {
|
||||
"agy": {
|
||||
"error": "Explicit command support was deprecated in Antigravity version 1.20.5.",
|
||||
"usage": "specify init <project> --ai agy --ai-skills",
|
||||
"interactive_note": (
|
||||
"'agy' was selected interactively; enabling [cyan]--ai-skills[/cyan] "
|
||||
"automatically for compatibility (explicit .agent/commands usage is deprecated)."
|
||||
),
|
||||
},
|
||||
"codex": {
|
||||
"error": (
|
||||
"Custom prompt-based spec-kit initialization is deprecated for Codex CLI; "
|
||||
"use agent skills instead."
|
||||
),
|
||||
"usage": "specify init <project> --ai codex --ai-skills",
|
||||
"interactive_note": (
|
||||
"'codex' was selected interactively; enabling [cyan]--ai-skills[/cyan] "
|
||||
"automatically for compatibility (.agents/skills is the recommended Codex layout)."
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def _handle_agent_skills_migration(console: Console, agent_key: str) -> None:
|
||||
"""Print a fail-fast migration error for agents that now require skills."""
|
||||
migration = AGENT_SKILLS_MIGRATIONS[agent_key]
|
||||
console.print(f"\n[red]Error:[/red] {migration['error']}")
|
||||
console.print("Please use [cyan]--ai-skills[/cyan] when initializing to install templates as agent skills instead.")
|
||||
console.print("[yellow]Usage:[/yellow] specify init <project> --ai agy --ai-skills")
|
||||
console.print(f"[yellow]Usage:[/yellow] {migration['usage']}")
|
||||
raise typer.Exit(1)
|
||||
|
||||
@app.command()
|
||||
@@ -1467,7 +1492,7 @@ def init(
|
||||
specify init . --ai claude # Initialize in current directory
|
||||
specify init . # Initialize in current directory (interactive AI selection)
|
||||
specify init --here --ai claude # Alternative syntax for current directory
|
||||
specify init --here --ai codex
|
||||
specify init --here --ai codex --ai-skills
|
||||
specify init --here --ai codebuddy
|
||||
specify init --here --ai vibe # Initialize with Mistral Vibe support
|
||||
specify init --here
|
||||
@@ -1557,24 +1582,16 @@ def init(
|
||||
"copilot"
|
||||
)
|
||||
|
||||
# [DEPRECATION NOTICE: Antigravity (agy)]
|
||||
# As of Antigravity v1.20.5, traditional CLI "command" support was fully removed
|
||||
# in favor of "Agent Skills" (SKILL.md files under <agent_folder>/skills/<skill_name>/).
|
||||
# Because 'specify_cli' historically populated .agent/commands/, we now must explicitly
|
||||
# enforce the `--ai-skills` flag for `agy` to ensure valid template generation.
|
||||
if selected_ai == "agy" and not ai_skills:
|
||||
# If agy was selected interactively (no --ai provided), automatically enable
|
||||
# Agents that have moved from explicit commands/prompts to agent skills.
|
||||
if selected_ai in AGENT_SKILLS_MIGRATIONS and not ai_skills:
|
||||
# If selected interactively (no --ai provided), automatically enable
|
||||
# ai_skills so the agent remains usable without requiring an extra flag.
|
||||
# Preserve deprecation behavior only for explicit '--ai agy' without skills.
|
||||
# Preserve fail-fast behavior only for explicit '--ai <agent>' without skills.
|
||||
if ai_assistant:
|
||||
_handle_agy_deprecation(console)
|
||||
_handle_agent_skills_migration(console, selected_ai)
|
||||
else:
|
||||
ai_skills = True
|
||||
console.print(
|
||||
"\n[yellow]Note:[/yellow] 'agy' was selected interactively; "
|
||||
"enabling [cyan]--ai-skills[/cyan] automatically for compatibility "
|
||||
"(explicit .agent/commands usage is deprecated)."
|
||||
)
|
||||
console.print(f"\n[yellow]Note:[/yellow] {AGENT_SKILLS_MIGRATIONS[selected_ai]['interactive_note']}")
|
||||
|
||||
# Validate --ai-commands-dir usage
|
||||
if selected_ai == "generic":
|
||||
@@ -1698,28 +1715,41 @@ def init(
|
||||
ensure_constitution_from_template(project_path, tracker=tracker)
|
||||
|
||||
if ai_skills:
|
||||
skills_ok = install_ai_skills(project_path, selected_ai, tracker=tracker)
|
||||
if selected_ai in NATIVE_SKILLS_AGENTS:
|
||||
skills_dir = _get_skills_dir(project_path, selected_ai)
|
||||
if not _has_bundled_skills(project_path, selected_ai):
|
||||
raise RuntimeError(
|
||||
f"Expected bundled agent skills in {skills_dir.relative_to(project_path)}, "
|
||||
"but none were found. Re-run with an up-to-date template."
|
||||
)
|
||||
if tracker:
|
||||
tracker.start("ai-skills")
|
||||
tracker.complete("ai-skills", f"bundled skills → {skills_dir.relative_to(project_path)}")
|
||||
else:
|
||||
console.print(f"[green]✓[/green] Using bundled agent skills in {skills_dir.relative_to(project_path)}/")
|
||||
else:
|
||||
skills_ok = install_ai_skills(project_path, selected_ai, tracker=tracker)
|
||||
|
||||
# When --ai-skills is used on a NEW project and skills were
|
||||
# successfully installed, remove the command files that the
|
||||
# template archive just created. Skills replace commands, so
|
||||
# keeping both would be confusing. For --here on an existing
|
||||
# repo we leave pre-existing commands untouched to avoid a
|
||||
# breaking change. We only delete AFTER skills succeed so the
|
||||
# project always has at least one of {commands, skills}.
|
||||
if skills_ok and not here:
|
||||
agent_cfg = AGENT_CONFIG.get(selected_ai, {})
|
||||
agent_folder = agent_cfg.get("folder", "")
|
||||
commands_subdir = agent_cfg.get("commands_subdir", "commands")
|
||||
if agent_folder:
|
||||
cmds_dir = project_path / agent_folder.rstrip("/") / commands_subdir
|
||||
if cmds_dir.exists():
|
||||
try:
|
||||
shutil.rmtree(cmds_dir)
|
||||
except OSError:
|
||||
# Best-effort cleanup: skills are already installed,
|
||||
# so leaving stale commands is non-fatal.
|
||||
console.print("[yellow]Warning: could not remove extracted commands directory[/yellow]")
|
||||
# When --ai-skills is used on a NEW project and skills were
|
||||
# successfully installed, remove the command files that the
|
||||
# template archive just created. Skills replace commands, so
|
||||
# keeping both would be confusing. For --here on an existing
|
||||
# repo we leave pre-existing commands untouched to avoid a
|
||||
# breaking change. We only delete AFTER skills succeed so the
|
||||
# project always has at least one of {commands, skills}.
|
||||
if skills_ok and not here:
|
||||
agent_cfg = AGENT_CONFIG.get(selected_ai, {})
|
||||
agent_folder = agent_cfg.get("folder", "")
|
||||
commands_subdir = agent_cfg.get("commands_subdir", "commands")
|
||||
if agent_folder:
|
||||
cmds_dir = project_path / agent_folder.rstrip("/") / commands_subdir
|
||||
if cmds_dir.exists():
|
||||
try:
|
||||
shutil.rmtree(cmds_dir)
|
||||
except OSError:
|
||||
# Best-effort cleanup: skills are already installed,
|
||||
# so leaving stale commands is non-fatal.
|
||||
console.print("[yellow]Warning: could not remove extracted commands directory[/yellow]")
|
||||
|
||||
if not no_git:
|
||||
tracker.start("git")
|
||||
@@ -1843,38 +1873,48 @@ def init(
|
||||
steps_lines.append("1. You're already in the project directory!")
|
||||
step_num = 2
|
||||
|
||||
# Add Codex-specific setup step if needed
|
||||
if selected_ai == "codex":
|
||||
codex_path = project_path / ".codex"
|
||||
quoted_path = shlex.quote(str(codex_path))
|
||||
if os.name == "nt": # Windows
|
||||
cmd = f"setx CODEX_HOME {quoted_path}"
|
||||
else: # Unix-like systems
|
||||
cmd = f"export CODEX_HOME={quoted_path}"
|
||||
|
||||
steps_lines.append(f"{step_num}. Set [cyan]CODEX_HOME[/cyan] environment variable before running Codex: [cyan]{cmd}[/cyan]")
|
||||
if selected_ai == "codex" and ai_skills:
|
||||
steps_lines.append(f"{step_num}. Start Codex in this project directory; spec-kit skills were installed to [cyan].agents/skills[/cyan]")
|
||||
step_num += 1
|
||||
|
||||
steps_lines.append(f"{step_num}. Start using slash commands with your AI agent:")
|
||||
codex_skill_mode = selected_ai == "codex" and ai_skills
|
||||
kimi_skill_mode = selected_ai == "kimi"
|
||||
native_skill_mode = codex_skill_mode or kimi_skill_mode
|
||||
usage_label = "skills" if native_skill_mode else "slash commands"
|
||||
|
||||
steps_lines.append(" 2.1 [cyan]/speckit.constitution[/] - Establish project principles")
|
||||
steps_lines.append(" 2.2 [cyan]/speckit.specify[/] - Create baseline specification")
|
||||
steps_lines.append(" 2.3 [cyan]/speckit.plan[/] - Create implementation plan")
|
||||
steps_lines.append(" 2.4 [cyan]/speckit.tasks[/] - Generate actionable tasks")
|
||||
steps_lines.append(" 2.5 [cyan]/speckit.implement[/] - Execute implementation")
|
||||
def _display_cmd(name: str) -> str:
|
||||
if codex_skill_mode:
|
||||
return f"$speckit-{name}"
|
||||
if kimi_skill_mode:
|
||||
return f"/skill:speckit.{name}"
|
||||
return f"/speckit.{name}"
|
||||
|
||||
steps_lines.append(f"{step_num}. Start using {usage_label} with your AI agent:")
|
||||
|
||||
steps_lines.append(f" {step_num}.1 [cyan]{_display_cmd('constitution')}[/] - Establish project principles")
|
||||
steps_lines.append(f" {step_num}.2 [cyan]{_display_cmd('specify')}[/] - Create baseline specification")
|
||||
steps_lines.append(f" {step_num}.3 [cyan]{_display_cmd('plan')}[/] - Create implementation plan")
|
||||
steps_lines.append(f" {step_num}.4 [cyan]{_display_cmd('tasks')}[/] - Generate actionable tasks")
|
||||
steps_lines.append(f" {step_num}.5 [cyan]{_display_cmd('implement')}[/] - Execute implementation")
|
||||
|
||||
steps_panel = Panel("\n".join(steps_lines), title="Next Steps", border_style="cyan", padding=(1,2))
|
||||
console.print()
|
||||
console.print(steps_panel)
|
||||
|
||||
enhancement_intro = (
|
||||
"Optional skills that you can use for your specs [bright_black](improve quality & confidence)[/bright_black]"
|
||||
if native_skill_mode
|
||||
else "Optional commands that you can use for your specs [bright_black](improve quality & confidence)[/bright_black]"
|
||||
)
|
||||
enhancement_lines = [
|
||||
"Optional commands that you can use for your specs [bright_black](improve quality & confidence)[/bright_black]",
|
||||
enhancement_intro,
|
||||
"",
|
||||
"○ [cyan]/speckit.clarify[/] [bright_black](optional)[/bright_black] - Ask structured questions to de-risk ambiguous areas before planning (run before [cyan]/speckit.plan[/] if used)",
|
||||
"○ [cyan]/speckit.analyze[/] [bright_black](optional)[/bright_black] - Cross-artifact consistency & alignment report (after [cyan]/speckit.tasks[/], before [cyan]/speckit.implement[/])",
|
||||
"○ [cyan]/speckit.checklist[/] [bright_black](optional)[/bright_black] - Generate quality checklists to validate requirements completeness, clarity, and consistency (after [cyan]/speckit.plan[/])"
|
||||
f"○ [cyan]{_display_cmd('clarify')}[/] [bright_black](optional)[/bright_black] - Ask structured questions to de-risk ambiguous areas before planning (run before [cyan]{_display_cmd('plan')}[/] if used)",
|
||||
f"○ [cyan]{_display_cmd('analyze')}[/] [bright_black](optional)[/bright_black] - Cross-artifact consistency & alignment report (after [cyan]{_display_cmd('tasks')}[/], before [cyan]{_display_cmd('implement')}[/])",
|
||||
f"○ [cyan]{_display_cmd('checklist')}[/] [bright_black](optional)[/bright_black] - Generate quality checklists to validate requirements completeness, clarity, and consistency (after [cyan]{_display_cmd('plan')}[/])"
|
||||
]
|
||||
enhancements_panel = Panel("\n".join(enhancement_lines), title="Enhancement Commands", border_style="cyan", padding=(1,2))
|
||||
enhancements_title = "Enhancement Skills" if native_skill_mode else "Enhancement Commands"
|
||||
enhancements_panel = Panel("\n".join(enhancement_lines), title=enhancements_title, border_style="cyan", padding=(1,2))
|
||||
console.print()
|
||||
console.print(enhancements_panel)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user