2026-05-26 21:06:35 +08:00
"""specify init command."""
2026-06-17 01:29:08 +03:00
2026-05-26 21:06:35 +08:00
from __future__ import annotations
import os
import shutil
import sys
from pathlib import Path
from typing import Any
import typer
from rich.live import Live
from rich.panel import Panel
from .._agent_config import (
AGENT_CONFIG ,
DEFAULT_INIT_INTEGRATION ,
SCRIPT_TYPE_CHOICES ,
)
from .._assets import (
_locate_bundled_preset ,
_locate_bundled_workflow ,
get_speckit_version ,
)
from .._console import StepTracker , console , select_with_arrows , show_banner
2026-06-09 06:13:07 -05:00
from .._utils import check_tool
2026-05-26 21:06:35 +08:00
def _stdin_is_interactive () -> bool :
return sys . stdin . isatty ()
def ensure_constitution_from_template (
project_path : Path , tracker : StepTracker | None = None
) -> None :
2026-07-15 11:42:50 +01:00
"""Materialize the resolved constitution template to memory if missing.
Resolution walks the full priority stack (project overrides → installed
presets → extensions → core) via :class:`PresetResolver`, so a preset that
ships a ``constitution-template`` (e.g. ``strategy: replace`` with a ratified
constitution) can seed the memory file. When nothing overrides it, the
resolver falls through to the core template.
"""
from ..presets import _materialize_constitution_template
2026-05-26 21:06:35 +08:00
memory_constitution = project_path / ".specify" / "memory" / "constitution.md"
if memory_constitution . exists ():
if tracker :
tracker . add ( "constitution" , "Constitution setup" )
tracker . skip ( "constitution" , "existing file preserved" )
return
try :
2026-07-15 11:42:50 +01:00
materialization = _materialize_constitution_template (
project_path , memory_constitution
)
if materialization is None :
if tracker :
tracker . add ( "constitution" , "Constitution setup" )
tracker . error ( "constitution" , "template not found" )
return
2026-05-26 21:06:35 +08:00
if tracker :
tracker . add ( "constitution" , "Constitution setup" )
2026-07-15 11:42:50 +01:00
if materialization == "copied" :
tracker . complete ( "constitution" , "copied from template" )
else :
tracker . complete ( "constitution" , "composed from template" )
2026-05-26 21:06:35 +08:00
else :
console . print ( "[cyan]Initialized constitution from template[/cyan]" )
except Exception as e :
if tracker :
tracker . add ( "constitution" , "Constitution setup" )
tracker . error ( "constitution" , str ( e ))
else :
2026-06-17 01:29:08 +03:00
console . print (
f "[yellow]Warning: Could not initialize constitution: { e } [/yellow]"
)
2026-05-26 21:06:35 +08:00
def register ( app : typer . Typer ) -> None :
@app.command ()
def init (
2026-06-17 01:29:08 +03:00
project_name : str = typer . Argument (
None ,
help = "Name for your new project directory (optional if using --here, or use '.' for current directory)" ,
),
script_type : str = typer . Option (
2026-07-21 18:40:15 +02:00
None , "--script" , help = "Script type to use: sh, ps, or py"
2026-06-17 01:29:08 +03:00
),
ignore_agent_tools : bool = typer . Option (
False ,
"--ignore-agent-tools" ,
help = "Skip checks for coding agent tools like Claude Code" ,
),
here : bool = typer . Option (
False ,
"--here" ,
help = "Initialize project in the current directory instead of creating a new one" ,
),
force : bool = typer . Option (
False ,
"--force" ,
help = "Force merge/overwrite when using --here (skip confirmation)" ,
),
skip_tls : bool = typer . Option (
False ,
"--skip-tls" ,
help = "Deprecated (no-op). Previously: skip SSL/TLS verification." ,
hidden = True ,
),
debug : bool = typer . Option (
False ,
"--debug" ,
help = "Deprecated. Previously: show verbose diagnostic output; currently only prints additional diagnostic details on failure." ,
hidden = True ,
),
github_token : str = typer . Option (
None ,
"--github-token" ,
help = "Deprecated (no-op). Previously: GitHub token for API requests." ,
hidden = True ,
),
offline : bool = typer . Option (
False ,
"--offline" ,
help = "Deprecated (no-op). All scaffolding now uses bundled assets." ,
hidden = True ,
),
preset : str = typer . Option (
None ,
"--preset" ,
help = "Install a preset during initialization (by preset ID)" ,
),
integration : str = typer . Option (
None ,
"--integration" ,
help = "AI coding agent integration to use (e.g. --integration copilot). See 'specify check' for available integrations." ,
),
integration_options : str = typer . Option (
None ,
"--integration-options" ,
help = 'Options for the integration (e.g. --integration-options="--commands-dir .myagent/cmds")' ,
),
2026-05-26 21:06:35 +08:00
):
"""
Initialize a new Specify project.
Project files are scaffolded from assets bundled inside the specify-cli
package, so initialization does not need network access and templates
match the installed CLI version.
This command will:
2026-06-09 06:13:07 -05:00
1. Check that required tools are installed
2026-05-26 21:06:35 +08:00
2. Let you choose your coding agent integration, or default to Copilot
in non-interactive sessions
3. Install bundled Spec Kit templates, scripts, workflow, and shared
project infrastructure
2026-06-09 06:13:07 -05:00
4. Set up coding agent integration commands and optional presets
2026-05-26 21:06:35 +08:00
Examples:
specify init my-project
specify init my-project --integration claude
specify init --ignore-agent-tools my-project
specify init . --integration claude # Initialize in current directory
specify init . # Initialize in current directory (interactive integration selection)
specify init --here --integration claude # Alternative syntax for current directory
specify init --here --integration codex --integration-options="--skills"
specify init --here --integration codebuddy
specify init --here --integration vibe # Initialize with Mistral Vibe support
specify init --here
specify init --here --force # Skip confirmation when current directory not empty
specify init my-project --integration claude # Claude installs skills by default
specify init --here --integration gemini
specify init my-project --integration generic --integration-options="--commands-dir .myagent/commands/" # Bring your own agent; requires --commands-dir
specify init my-project --integration claude --preset healthcare-compliance # With preset
"""
# Lazy imports to avoid circular dependency — __init__.py imports this module
from .. import (
_install_shared_infra_or_exit ,
_print_cli_warning ,
ensure_executable_scripts ,
save_init_options ,
)
2026-06-17 01:29:08 +03:00
from ..integration_runtime import (
with_integration_setting as _with_integration_setting ,
)
2026-06-03 01:21:19 +08:00
from ..integrations._commands import (
_parse_integration_options ,
_write_integration_json ,
)
2026-05-26 21:06:35 +08:00
show_banner ()
from ..integrations import INTEGRATION_REGISTRY , get_integration
2026-06-17 01:29:08 +03:00
2026-05-26 21:06:35 +08:00
if integration :
resolved_integration = get_integration ( integration )
if not resolved_integration :
console . print ( f "[red]Error:[/red] Unknown integration: ' { integration } '" )
available = ", " . join ( sorted ( INTEGRATION_REGISTRY ))
console . print ( f "[yellow]Available integrations:[/yellow] { available } " )
raise typer . Exit ( 1 )
if project_name == "." :
here = True
project_name = None
if here and project_name :
2026-06-17 01:29:08 +03:00
console . print (
"[red]Error:[/red] Cannot specify both project name and --here flag"
)
2026-05-26 21:06:35 +08:00
raise typer . Exit ( 1 )
if not here and not project_name :
2026-06-17 01:29:08 +03:00
console . print (
"[red]Error:[/red] Must specify either a project name, use '.' for current directory, or use --here flag"
)
2026-05-26 21:06:35 +08:00
raise typer . Exit ( 1 )
dir_existed_before = False
if here :
project_name = Path . cwd () . name
project_path = Path . cwd ()
dir_existed_before = True
existing_items = list ( project_path . iterdir ())
if existing_items :
2026-06-17 01:29:08 +03:00
console . print (
f "[yellow]Warning:[/yellow] Current directory is not empty ( { len ( existing_items ) } items)"
)
2026-05-26 21:06:35 +08:00
if force :
2026-07-14 01:29:15 +05:00
# Proceeding: the merge/overwrite warning is accurate here.
console . print (
"[yellow]Template files will be merged with existing content and may overwrite existing files[/yellow]"
)
2026-06-17 01:29:08 +03:00
console . print (
"[cyan]--force supplied: skipping confirmation and proceeding with merge[/cyan]"
)
2026-05-26 21:06:35 +08:00
else :
2026-07-14 01:29:15 +05:00
# Fold the merge risk into the confirmation prompt rather than
# printing it unconditionally first: on the EOF/no-input path
# below the command exits without changing anything, so a
# standalone "will be merged" line would mislead. Interactive
# users still see the risk as part of the question.
#
# Call typer.confirm normally so piped y/n is honored — e.g.
# `echo y | specify init --here` keeps reaching the
# non-destructive preserve-merge path.
try :
proceed = typer . confirm (
"Template files will be merged with existing content "
"and may overwrite existing files. Do you want to continue?"
)
except ( typer . Abort , EOFError ):
# typer.confirm raises Abort for BOTH an interactive Ctrl+C
# and an EOF on closed/empty stdin. Distinguish them: a real
# TTY cancellation is a normal exit (0, "cancelled"), while a
# missing-input EOF (non-interactive) becomes an actionable
# error pointing at --force.
if _stdin_is_interactive ():
console . print ( "[yellow]Operation cancelled[/yellow]" )
raise typer . Exit ( 0 ) from None
console . print (
"[red]Error:[/red] Current directory is not empty and no "
"confirmation input is available. Re-run with "
"[bold]--force[/bold] to merge into it."
)
raise typer . Exit ( 1 ) from None
if not proceed :
2026-05-26 21:06:35 +08:00
console . print ( "[yellow]Operation cancelled[/yellow]" )
raise typer . Exit ( 0 )
else :
project_path = Path ( project_name ) . resolve ()
dir_existed_before = project_path . exists ()
if project_path . exists ():
if not project_path . is_dir ():
2026-06-17 01:29:08 +03:00
console . print (
f "[red]Error:[/red] ' { project_name } ' exists but is not a directory."
)
2026-05-26 21:06:35 +08:00
raise typer . Exit ( 1 )
existing_items = list ( project_path . iterdir ())
if force :
if existing_items :
2026-06-17 01:29:08 +03:00
console . print (
f "[yellow]Warning:[/yellow] Directory ' { project_name } ' is not empty ( { len ( existing_items ) } items)"
)
console . print (
"[yellow]Template files will be merged with existing content and may overwrite existing files[/yellow]"
)
console . print (
f "[cyan]--force supplied: merging into existing directory '[cyan] { project_name } [/cyan]'[/cyan]"
)
2026-05-26 21:06:35 +08:00
else :
error_panel = Panel (
f "Directory already exists: '[cyan] { project_name } [/cyan]' \n "
"Please choose a different project name or remove the existing directory. \n "
"Use [bold]--force[/bold] to merge into the existing directory." ,
title = "[red]Directory Conflict[/red]" ,
border_style = "red" ,
2026-06-17 01:29:08 +03:00
padding = ( 1 , 2 ),
2026-05-26 21:06:35 +08:00
)
console . print ()
console . print ( error_panel )
raise typer . Exit ( 1 )
2026-06-05 14:56:28 -05:00
if integration :
if integration not in AGENT_CONFIG :
2026-06-17 01:29:08 +03:00
console . print (
f "[red]Error:[/red] Invalid integration ' { integration } '. Choose from: { ', ' . join ( AGENT_CONFIG . keys ()) } "
)
2026-05-26 21:06:35 +08:00
raise typer . Exit ( 1 )
2026-06-05 14:56:28 -05:00
selected_ai = integration
2026-05-26 21:06:35 +08:00
elif not _stdin_is_interactive ():
console . print (
f "[dim]Non-interactive session detected: defaulting to ' { DEFAULT_INIT_INTEGRATION } '. "
"Use --integration to choose a different agent.[/dim]"
)
selected_ai = DEFAULT_INIT_INTEGRATION
else :
ai_choices = { key : config [ "name" ] for key , config in AGENT_CONFIG . items ()}
selected_ai = select_with_arrows (
ai_choices ,
"Choose your coding agent integration:" ,
DEFAULT_INIT_INTEGRATION ,
)
2026-06-05 14:56:28 -05:00
if not integration :
2026-05-26 21:06:35 +08:00
resolved_integration = get_integration ( selected_ai )
if not resolved_integration :
console . print ( f "[red]Error:[/red] Unknown agent ' { selected_ai } '" )
raise typer . Exit ( 1 )
if selected_ai == "generic" and not integration_options :
2026-06-17 01:29:08 +03:00
console . print (
"[red]Error:[/red] --integration generic requires --integration-options with --commands-dir"
)
console . print (
'[dim]Example: specify init my-project --integration generic --integration-options="--commands-dir .myagent/commands/"[/dim]'
)
2026-06-05 14:56:28 -05:00
raise typer . Exit ( 1 )
2026-05-26 21:06:35 +08:00
current_dir = Path . cwd ()
setup_lines = [
"[cyan]Specify Project Setup[/cyan]" ,
"" ,
f " { 'Project' : <15 } [green] { project_path . name } [/green]" ,
f " { 'Working Path' : <15 } [dim] { current_dir } [/dim]" ,
]
if not here :
setup_lines . append ( f " { 'Target Path' : <15 } [dim] { project_path } [/dim]" )
2026-06-17 01:29:08 +03:00
console . print (
Panel ( " \n " . join ( setup_lines ), border_style = "cyan" , padding = ( 1 , 2 ))
)
2026-05-26 21:06:35 +08:00
if not ignore_agent_tools :
agent_config = AGENT_CONFIG . get ( selected_ai )
if agent_config and agent_config [ "requires_cli" ]:
install_url = agent_config [ "install_url" ]
if not check_tool ( selected_ai ):
error_panel = Panel (
f "[cyan] { selected_ai } [/cyan] not found \n "
f "Install from: [cyan] { install_url } [/cyan] \n "
f " { agent_config [ 'name' ] } is required to continue with this project type. \n\n "
"Tip: Use [cyan]--ignore-agent-tools[/cyan] to skip this check" ,
title = "[red]Agent Detection Error[/red]" ,
border_style = "red" ,
2026-06-17 01:29:08 +03:00
padding = ( 1 , 2 ),
2026-05-26 21:06:35 +08:00
)
console . print ()
console . print ( error_panel )
raise typer . Exit ( 1 )
if script_type :
if script_type not in SCRIPT_TYPE_CHOICES :
2026-06-17 01:29:08 +03:00
console . print (
f "[red]Error:[/red] Invalid script type ' { script_type } '. Choose from: { ', ' . join ( SCRIPT_TYPE_CHOICES . keys ()) } "
)
2026-05-26 21:06:35 +08:00
raise typer . Exit ( 1 )
selected_script = script_type
else :
default_script = "ps" if os . name == "nt" else "sh"
if _stdin_is_interactive ():
2026-06-17 01:29:08 +03:00
selected_script = select_with_arrows (
SCRIPT_TYPE_CHOICES ,
"Choose script type (or press Enter)" ,
default_script ,
)
2026-05-26 21:06:35 +08:00
else :
selected_script = default_script
console . print ( f "[cyan]Selected coding agent integration:[/cyan] { selected_ai } " )
console . print ( f "[cyan]Selected script type:[/cyan] { selected_script } " )
tracker = StepTracker ( "Initialize Specify Project" )
tracker . add ( "precheck" , "Check required tools" )
tracker . complete ( "precheck" , "ok" )
tracker . add ( "ai-select" , "Select coding agent integration" )
tracker . complete ( "ai-select" , f " { selected_ai } " )
tracker . add ( "script-select" , "Select script type" )
tracker . complete ( "script-select" , selected_script )
tracker . add ( "integration" , "Install integration" )
tracker . add ( "shared-infra" , "Install shared infrastructure" )
for key , label in [
( "chmod" , "Ensure scripts executable" ),
( "constitution" , "Constitution setup" ),
( "workflow" , "Install bundled workflow" ),
( "final" , "Finalize" ),
]:
tracker . add ( key , label )
2026-06-17 06:48:42 -05:00
# Disable transient mode on Windows: PowerShell 5.1's legacy console
# hangs when Rich tries to restore cursor state via VT escape sequences.
_transient = sys . platform != "win32"
2026-06-17 01:29:08 +03:00
with Live (
2026-06-17 06:48:42 -05:00
tracker . render (), console = console , refresh_per_second = 8 , transient = _transient
2026-06-17 01:29:08 +03:00
) as live :
2026-05-26 21:06:35 +08:00
tracker . attach_refresh ( lambda : live . update ( tracker . render ()))
try :
from ..integrations.manifest import IntegrationManifest
2026-06-17 01:29:08 +03:00
2026-05-26 21:06:35 +08:00
tracker . start ( "integration" )
manifest = IntegrationManifest (
2026-06-17 01:29:08 +03:00
resolved_integration . key ,
project_path ,
version = get_speckit_version (),
2026-05-26 21:06:35 +08:00
)
integration_parsed_options : dict [ str , Any ] = {}
if integration_options :
2026-06-17 01:29:08 +03:00
extra = _parse_integration_options (
resolved_integration , integration_options
)
2026-05-26 21:06:35 +08:00
if extra :
integration_parsed_options . update ( extra )
resolved_integration . setup (
2026-06-17 01:29:08 +03:00
project_path ,
manifest ,
2026-05-26 21:06:35 +08:00
parsed_options = integration_parsed_options or None ,
script_type = selected_script ,
raw_options = integration_options ,
)
manifest . save ()
integration_settings = _with_integration_setting (
{},
resolved_integration . key ,
resolved_integration ,
script_type = selected_script ,
raw_options = integration_options ,
parsed_options = integration_parsed_options or None ,
2026-07-21 16:20:20 +02:00
project_root = project_path ,
2026-05-26 21:06:35 +08:00
)
_write_integration_json (
project_path ,
resolved_integration . key ,
[ resolved_integration . key ],
integration_settings ,
)
2026-06-17 01:29:08 +03:00
tracker . complete (
"integration" ,
resolved_integration . config . get ( "name" , resolved_integration . key ),
)
2026-05-26 21:06:35 +08:00
tracker . start ( "shared-infra" )
_install_shared_infra_or_exit (
project_path ,
selected_script ,
tracker = tracker ,
force = force ,
2026-06-17 01:29:08 +03:00
invoke_separator = resolved_integration . effective_invoke_separator (
2026-07-21 16:20:20 +02:00
integration_parsed_options , project_root = project_path
2026-06-17 01:29:08 +03:00
),
)
tracker . complete (
"shared-infra" , f "scripts ( { selected_script } ) + templates"
2026-05-26 21:06:35 +08:00
)
try :
bundled_wf = _locate_bundled_workflow ( "speckit" )
if bundled_wf :
from ..workflows.catalog import WorkflowRegistry
from ..workflows.engine import WorkflowDefinition
2026-06-17 01:29:08 +03:00
2026-05-26 21:06:35 +08:00
wf_registry = WorkflowRegistry ( project_path )
if wf_registry . is_installed ( "speckit" ):
tracker . complete ( "workflow" , "already installed" )
else :
import shutil as _shutil
2026-06-17 01:29:08 +03:00
dest_wf = (
project_path / ".specify" / "workflows" / "speckit"
)
2026-05-26 21:06:35 +08:00
dest_wf . mkdir ( parents = True , exist_ok = True )
_shutil . copy2 (
bundled_wf / "workflow.yml" ,
dest_wf / "workflow.yml" ,
)
2026-06-17 01:29:08 +03:00
definition = WorkflowDefinition . from_yaml (
dest_wf / "workflow.yml"
)
wf_registry . add (
"speckit" ,
{
"name" : definition . name ,
"version" : definition . version ,
"description" : definition . description ,
"source" : "bundled" ,
},
)
2026-05-26 21:06:35 +08:00
tracker . complete ( "workflow" , "speckit installed" )
else :
tracker . skip ( "workflow" , "bundled workflow not found" )
except Exception as wf_err :
2026-06-17 01:29:08 +03:00
sanitized_wf = str ( wf_err ) . replace ( " \n " , " " ) . strip ()
2026-05-26 21:06:35 +08:00
tracker . error ( "workflow" , f "install failed: { sanitized_wf [: 120 ] } " )
init_opts = {
"ai" : selected_ai ,
"integration" : resolved_integration . key ,
"here" : here ,
"script" : selected_script ,
2026-06-09 06:13:07 -05:00
"feature_numbering" : "sequential" ,
2026-05-26 21:06:35 +08:00
"speckit_version" : get_speckit_version (),
}
2026-07-21 16:20:20 +02:00
if resolved_integration . is_skills_mode (
integration_parsed_options or None , project_root = project_path
2026-06-17 01:29:08 +03:00
):
2026-05-26 21:06:35 +08:00
init_opts [ "ai_skills" ] = True
save_init_options ( project_path , init_opts )
2026-05-30 06:37:18 -05:00
ensure_executable_scripts ( project_path , tracker = tracker )
2026-05-26 21:06:35 +08:00
if preset :
try :
2026-06-17 01:29:08 +03:00
from ..presets import PresetCatalog , PresetError , PresetManager
2026-05-26 21:06:35 +08:00
preset_manager = PresetManager ( project_path )
speckit_ver = get_speckit_version ()
local_path = Path ( preset ) . resolve ()
if local_path . is_dir () and ( local_path / "preset.yml" ) . exists ():
2026-06-17 01:29:08 +03:00
preset_manager . install_from_directory (
local_path , speckit_ver
)
2026-05-26 21:06:35 +08:00
else :
bundled_path = _locate_bundled_preset ( preset )
if bundled_path :
2026-06-17 01:29:08 +03:00
preset_manager . install_from_directory (
bundled_path , speckit_ver
)
2026-05-26 21:06:35 +08:00
else :
preset_catalog = PresetCatalog ( project_path )
pack_info = preset_catalog . get_pack_info ( preset )
if not pack_info :
2026-06-17 01:29:08 +03:00
console . print (
f "[yellow]Warning:[/yellow] Preset ' { preset } ' not found in catalog. Skipping."
)
elif pack_info . get ( "bundled" ) and not pack_info . get (
"download_url"
):
2026-05-26 21:06:35 +08:00
from ..extensions import REINSTALL_COMMAND
2026-06-17 01:29:08 +03:00
2026-05-26 21:06:35 +08:00
console . print (
f "[yellow]Warning:[/yellow] Preset ' { preset } ' is bundled with spec-kit "
f "but could not be found in the installed package."
)
console . print (
"This usually means the spec-kit installation is incomplete or corrupted."
)
2026-06-17 01:29:08 +03:00
console . print (
f "Try reinstalling: { REINSTALL_COMMAND } "
)
2026-05-26 21:06:35 +08:00
else :
zip_path = None
try :
zip_path = preset_catalog . download_pack ( preset )
2026-06-17 01:29:08 +03:00
preset_manager . install_from_zip (
zip_path , speckit_ver
)
2026-05-26 21:06:35 +08:00
except PresetError as preset_err :
_print_cli_warning (
"install" ,
"preset" ,
preset ,
preset_err ,
continuing = "Continuing without the optional preset." ,
)
finally :
if zip_path is not None :
try :
zip_path . unlink ( missing_ok = True )
except OSError :
pass
except Exception as preset_err :
_print_cli_warning (
"install" ,
"preset" ,
preset ,
preset_err ,
continuing = "Continuing without the optional preset." ,
)
2026-07-15 11:42:50 +01:00
# Seed the constitution AFTER preset installation so that a
# preset-provided constitution-template (resolved via the
# priority stack) wins over the core template.
ensure_constitution_from_template ( project_path , tracker = tracker )
2026-05-26 21:06:35 +08:00
tracker . complete ( "final" , "project ready" )
except ( typer . Exit , SystemExit ):
raise
except Exception as e :
tracker . error ( "final" , str ( e ))
2026-06-17 01:29:08 +03:00
console . print (
Panel (
f "Initialization failed: { e } " ,
title = "Failure" ,
border_style = "red" ,
)
)
2026-05-26 21:06:35 +08:00
if debug :
_env_pairs = [
( "Python" , sys . version . split ()[ 0 ]),
( "Platform" , sys . platform ),
( "CWD" , str ( Path . cwd ())),
]
_label_width = max ( len ( k ) for k , _ in _env_pairs )
2026-06-17 01:29:08 +03:00
env_lines = [
f " { k . ljust ( _label_width ) } → [bright_black] { v } [/bright_black]"
for k , v in _env_pairs
]
console . print (
Panel (
" \n " . join ( env_lines ),
title = "Debug Environment" ,
border_style = "magenta" ,
)
)
2026-05-26 21:06:35 +08:00
if not here and project_path . exists () and not dir_existed_before :
shutil . rmtree ( project_path )
raise typer . Exit ( 1 )
finally :
pass
2026-06-17 06:48:42 -05:00
if _transient :
console . print ( tracker . render ())
2026-05-26 21:06:35 +08:00
console . print ( " \n [bold green]Project ready.[/bold green]" )
agent_config = AGENT_CONFIG . get ( selected_ai )
if agent_config :
2026-06-17 01:29:08 +03:00
agent_folder = agent_config [ "folder" ] or integration_parsed_options . get (
"commands_dir"
)
2026-05-26 21:06:35 +08:00
if agent_folder :
security_notice = Panel (
f "Some agents may store credentials, auth tokens, or other identifying and private artifacts in the agent folder within your project. \n "
f "Consider adding [cyan] { agent_folder } [/cyan] (or parts of it) to [cyan].gitignore[/cyan] to prevent accidental credential leakage." ,
title = "[yellow]Agent Folder Security[/yellow]" ,
border_style = "yellow" ,
2026-06-17 01:29:08 +03:00
padding = ( 1 , 2 ),
2026-05-26 21:06:35 +08:00
)
console . print ()
console . print ( security_notice )
steps_lines = []
if not here :
2026-06-17 01:29:08 +03:00
steps_lines . append (
f "1. Go to the project folder: [cyan]cd { project_name } [/cyan]"
)
2026-05-26 21:06:35 +08:00
step_num = 2
else :
steps_lines . append ( "1. You're already in the project directory!" )
step_num = 2
2026-07-21 16:20:20 +02:00
_is_skills_integration = resolved_integration . is_skills_mode (
integration_parsed_options or None , project_root = project_path
)
2026-05-26 21:06:35 +08:00
2026-06-05 14:56:28 -05:00
codex_skill_mode = selected_ai == "codex" and _is_skills_integration
2026-06-23 01:14:18 +08:00
zcode_skill_mode = selected_ai == "zcode" and _is_skills_integration
2026-06-05 14:56:28 -05:00
claude_skill_mode = selected_ai == "claude" and _is_skills_integration
2026-05-26 21:06:35 +08:00
kimi_skill_mode = selected_ai == "kimi"
agy_skill_mode = selected_ai == "agy" and _is_skills_integration
trae_skill_mode = selected_ai == "trae"
2026-06-17 01:29:08 +03:00
cursor_agent_skill_mode = (
selected_ai == "cursor-agent" and _is_skills_integration
)
2026-05-26 21:06:35 +08:00
copilot_skill_mode = selected_ai == "copilot" and _is_skills_integration
devin_skill_mode = selected_ai == "devin"
2026-06-17 01:29:08 +03:00
zed_skill_mode = selected_ai == "zed" and _is_skills_integration
2026-07-15 15:55:25 -04:00
grok_skill_mode = selected_ai == "grok" and _is_skills_integration
2026-06-01 13:20:48 -03:00
cline_skill_mode = selected_ai == "cline"
2026-07-22 17:46:45 +05:00
forge_skill_mode = selected_ai == "forge"
2026-07-21 16:20:20 +02:00
bob_skill_mode = selected_ai == "bob" and _is_skills_integration
2026-06-17 01:29:08 +03:00
native_skill_mode = (
codex_skill_mode
2026-06-23 01:14:18 +08:00
or zcode_skill_mode
2026-06-17 01:29:08 +03:00
or claude_skill_mode
or kimi_skill_mode
or agy_skill_mode
or trae_skill_mode
or cursor_agent_skill_mode
or copilot_skill_mode
or devin_skill_mode
or zed_skill_mode
2026-07-15 15:55:25 -04:00
or grok_skill_mode
2026-07-21 16:20:20 +02:00
or bob_skill_mode
2026-06-17 01:29:08 +03:00
)
2026-05-26 21:06:35 +08:00
2026-06-05 14:56:28 -05:00
if codex_skill_mode :
2026-06-17 01:29:08 +03:00
steps_lines . append (
f " { step_num } . Start Codex in this project directory; spec-kit skills were installed to [cyan].agents/skills[/cyan]"
)
2026-05-26 21:06:35 +08:00
step_num += 1
2026-06-23 01:14:18 +08:00
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
2026-06-05 14:56:28 -05:00
if claude_skill_mode :
2026-06-17 01:29:08 +03:00
steps_lines . append (
f " { step_num } . Start Claude in this project directory; spec-kit skills were installed to [cyan].claude/skills[/cyan]"
)
2026-05-26 21:06:35 +08:00
step_num += 1
2026-06-05 14:56:28 -05:00
if cursor_agent_skill_mode :
2026-06-17 01:29:08 +03:00
steps_lines . append (
f " { step_num } . Start Cursor Agent in this project directory; spec-kit skills were installed to [cyan].cursor/skills[/cyan]"
)
2026-05-26 21:06:35 +08:00
step_num += 1
if devin_skill_mode :
2026-06-17 01:29:08 +03:00
steps_lines . append (
f " { step_num } . Start Devin in this project directory; spec-kit skills were installed to [cyan].devin/skills[/cyan]"
)
step_num += 1
if zed_skill_mode :
steps_lines . append (
f " { step_num } . Start Zed in this project directory; spec-kit skills were installed to [cyan].agents/skills[/cyan]"
)
2026-05-26 21:06:35 +08:00
step_num += 1
2026-07-15 15:55:25 -04:00
if grok_skill_mode :
steps_lines . append (
f " { step_num } . Start Grok Build in this project directory; spec-kit skills were installed to [cyan].grok/skills[/cyan]"
)
step_num += 1
2026-07-21 16:20:20 +02:00
if bob_skill_mode :
steps_lines . append (
f " { step_num } . Start Bob in this project directory; spec-kit skills were installed to [cyan].bob/skills[/cyan]"
)
step_num += 1
2026-05-26 21:06:35 +08:00
usage_label = "skills" if native_skill_mode else "slash commands"
2026-06-23 01:14:18 +08:00
from .._invocation_style import (
is_dollar_skills_agent as _is_dollar_skills_agent ,
is_slash_skills_agent as _is_slash_skills_agent ,
)
2026-06-17 01:29:08 +03:00
# `_is_skills_integration` means the integration is installed in
# skills mode, which is the semantic equivalent of `ai_skills_enabled`
# used by `is_slash_skills_agent()`.
_ai_skills_enabled = _is_skills_integration
2026-05-26 21:06:35 +08:00
def _display_cmd ( name : str ) -> str :
2026-06-23 01:14:18 +08:00
if _is_dollar_skills_agent ( selected_ai , _ai_skills_enabled ):
2026-05-26 21:06:35 +08:00
return f "$speckit- { name } "
if kimi_skill_mode :
return f "/skill:speckit- { name } "
2026-06-17 01:29:08 +03:00
if (
_is_slash_skills_agent ( selected_ai , _ai_skills_enabled )
or cline_skill_mode
2026-07-22 17:46:45 +05:00
or forge_skill_mode
2026-06-17 01:29:08 +03:00
):
2026-05-26 21:06:35 +08:00
return f "/speckit- { name } "
return f "/speckit. { name } "
2026-06-17 01:29:08 +03:00
steps_lines . append (
f " { step_num } . Start using { usage_label } with your coding agent:"
)
2026-05-26 21:06:35 +08:00
2026-06-17 01:29:08 +03:00
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"
)
2026-06-17 20:47:00 +01:00
steps_lines . append (
f " { step_num } .6 [cyan] { _display_cmd ( 'converge' ) } [/] - Assess the codebase and append remaining work as tasks"
)
2026-05-26 21:06:35 +08:00
2026-06-17 01:29:08 +03:00
steps_panel = Panel (
" \n " . join ( steps_lines ),
title = "Next Steps" ,
border_style = "cyan" ,
padding = ( 1 , 2 ),
)
2026-05-26 21:06:35 +08:00
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 = [
enhancement_intro ,
"" ,
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' ) } [/])" ,
2026-06-17 01:29:08 +03:00
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' ) } [/])" ,
2026-05-26 21:06:35 +08:00
]
2026-06-17 01:29:08 +03:00
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 ),
)
2026-05-26 21:06:35 +08:00
console . print ()
console . print ( enhancements_panel )