Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@ jobs:
- spec-kit-template-gemini-ps-${{ steps.get_tag.outputs.new_version }}.zip
- spec-kit-template-cursor-sh-${{ steps.get_tag.outputs.new_version }}.zip
- spec-kit-template-cursor-ps-${{ steps.get_tag.outputs.new_version }}.zip
- spec-kit-template-kilocode-sh-${{ steps.get_tag.outputs.new_version }}.zip
- spec-kit-template-kilocode-ps-${{ steps.get_tag.outputs.new_version }}.zip
EOF

echo "Generated release notes:"
Expand All @@ -104,7 +106,7 @@ jobs:
# Remove 'v' prefix from version for release title
VERSION_NO_V=${{ steps.get_tag.outputs.new_version }}
VERSION_NO_V=${VERSION_NO_V#v}

gh release create ${{ steps.get_tag.outputs.new_version }} \
spec-kit-template-copilot-sh-${{ steps.get_tag.outputs.new_version }}.zip \
spec-kit-template-copilot-ps-${{ steps.get_tag.outputs.new_version }}.zip \
Expand All @@ -114,6 +116,8 @@ jobs:
spec-kit-template-gemini-ps-${{ steps.get_tag.outputs.new_version }}.zip \
spec-kit-template-cursor-sh-${{ steps.get_tag.outputs.new_version }}.zip \
spec-kit-template-cursor-ps-${{ steps.get_tag.outputs.new_version }}.zip \
spec-kit-template-kilocode-sh-${{ steps.get_tag.outputs.new_version }}.zip \
spec-kit-template-kilocode-ps-${{ steps.get_tag.outputs.new_version }}.zip \
--title "Spec Kit Templates - $VERSION_NO_V" \
--notes-file release_notes.md
env:
Expand Down
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ The `specify` command supports the following options:
| Argument/Option | Type | Description |
|------------------------|----------|------------------------------------------------------------------------------|
| `<project-name>` | Argument | Name for your new project directory (optional if using `--here`) |
| `--ai` | Option | AI assistant to use: `claude`, `gemini`, `copilot`, or `cursor` |
| `--ai` | Option | AI assistant to use: `claude`, `gemini`, `copilot`, `cursor`, or `kilocode` |
| `--script` | Option | Script variant to use: `sh` (bash/zsh) or `ps` (PowerShell) |
| `--ignore-agent-tools` | Flag | Skip checks for AI agent tools like Claude Code |
| `--no-git` | Flag | Skip git repository initialization |
Expand All @@ -108,6 +108,9 @@ specify init my-project --ai claude
# Initialize with Cursor support
specify init my-project --ai cursor

# Initialize with Kilo Code support
specify init my-project --ai kilocode

# Initialize with PowerShell scripts (Windows/cross-platform)
specify init my-project --ai copilot --script ps

Expand Down Expand Up @@ -170,7 +173,7 @@ Our research and experimentation focus on:
## 🔧 Prerequisites

- **Linux/macOS** (or WSL2 on Windows)
- AI coding agent: [Claude Code](https://www.anthropic.com/claude-code), [GitHub Copilot](https://code.visualstudio.com/), [Gemini CLI](https://github.com/google-gemini/gemini-cli), or [Cursor](https://cursor.sh/)
- AI coding agent: [Claude Code](https://www.anthropic.com/claude-code), [GitHub Copilot](https://code.visualstudio.com/), [Gemini CLI](https://github.com/google-gemini/gemini-cli), [Cursor](https://cursor.sh/), or [Kilo Code](https://kilocode.ai/)
- [uv](https://docs.astral.sh/uv/) for package management
- [Python 3.11+](https://www.python.org/downloads/)
- [Git](https://git-scm.com/downloads)
Expand Down
117 changes: 114 additions & 3 deletions src/specify_cli/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,8 @@
"copilot": "GitHub Copilot",
"claude": "Claude Code",
"gemini": "Gemini CLI",
"cursor": "Cursor"
"cursor": "Cursor",
"kilocode": "Kilo Code"
}
# Add script type choices
SCRIPT_TYPE_CHOICES = {"sh": "POSIX Shell (bash/zsh)", "ps": "PowerShell"}
Expand Down Expand Up @@ -375,7 +376,7 @@ def is_git_repo(path: Path = None) -> bool:
"""Check if the specified path is inside a git repository."""
if path is None:
path = Path.cwd()

if not path.is_dir():
return False

Expand All @@ -392,6 +393,21 @@ def is_git_repo(path: Path = None) -> bool:
return False


def get_repo_root() -> str:
"""Get the absolute path to the repository root."""
try:
result = subprocess.run(
["git", "rev-parse", "--show-toplevel"],
check=True,
capture_output=True,
text=True,
)
return result.stdout.strip()
except (subprocess.CalledProcessError, FileNotFoundError):
# Fallback to current directory if not in a git repo
return str(Path.cwd())


def init_git_repo(project_path: Path, quiet: bool = False) -> bool:
"""Initialize a git repository in the specified path.
quiet: if True suppress console output (tracker handles status)
Expand Down Expand Up @@ -719,10 +735,87 @@ def ensure_executable_scripts(project_path: Path, tracker: StepTracker | None =
console.print(f" - {f}")


def generate_kilocode_modes_from_templates(project_path: Path, script_type: str) -> None:
"""Generate .kilocodemodes file by converting existing command templates to Kilo Code format."""
modes_file = project_path / ".kilocodemodes"

# Get absolute paths for scripts
repo_root = get_repo_root()
script_base = f"{repo_root}/.specify/scripts"
bash_script = f"{script_base}/bash" if script_type == "sh" else f"{script_base}/powershell"

# Command configurations with role definitions
command_configs = {
"specify": {
"name": "Specify",
"description": "Create feature specifications",
"role_definition": "You are Kilo Code, a feature specification expert specializing in converting natural language feature descriptions into structured, comprehensive specifications following spec-driven development principles.",
"when_to_use": "Use this mode when you need to convert a natural language feature description into a structured specification document with user stories, acceptance criteria, and technical requirements.",
},
"plan": {
"name": "Plan",
"description": "Implementation planning workflow",
"role_definition": "You are Kilo Code, an implementation planning expert who creates detailed technical plans from feature specifications, including architecture decisions, technology choices, and implementation phases.",
"when_to_use": "Use this mode when you need to generate implementation plans and design artifacts from feature specifications, including data models, API contracts, and research documentation.",
},
"tasks": {
"name": "Tasks",
"description": "Generate actionable task lists",
"role_definition": "You are Kilo Code, a task generation expert who breaks down implementation plans into dependency-ordered, actionable tasks with clear acceptance criteria.",
"when_to_use": "Use this mode when you need to create dependency-ordered task lists from design artifacts, breaking complex implementations into manageable, verifiable steps.",
}
}

custom_modes = []

for command_slug, config in command_configs.items():
# Read existing command template
template_file = project_path / ".specify" / "templates" / "commands" / f"{command_slug}.md"

if template_file.exists():
with open(template_file, 'r') as f:
template_content = f.read()

# Extract custom instructions from template (skip YAML frontmatter)
lines = template_content.split('\n')
custom_instructions = ""
in_frontmatter = False

for line in lines:
if line.strip() == '---':
in_frontmatter = not in_frontmatter
continue
if not in_frontmatter:
custom_instructions += line + '\n'

# Replace template variables with actual paths
custom_instructions = custom_instructions.replace('{SCRIPT}', f'{bash_script}/{command_slug}.sh')
custom_instructions = custom_instructions.replace('{ARGS}', '$ARGUMENTS')
custom_instructions = custom_instructions.replace('repo root', f'{repo_root}')

mode_config = {
"slug": command_slug,
"name": config["name"],
"description": config["description"],
"roleDefinition": config["role_definition"],
"whenToUse": config["when_to_use"],
"groups": ["read", "edit", "command"],
"customInstructions": custom_instructions.strip()
}

custom_modes.append(mode_config)

modes_config = {"customModes": custom_modes}

# Write JSON file (uses built-in json module)
with open(modes_file, 'w') as f:
json.dump(modes_config, f, indent=2)


@app.command()
def init(
project_name: str = typer.Argument(None, help="Name for your new project directory (optional if using --here)"),
ai_assistant: str = typer.Option(None, "--ai", help="AI assistant to use: claude, gemini, copilot, or cursor"),
ai_assistant: str = typer.Option(None, "--ai", help="AI assistant to use: claude, gemini, copilot, cursor, or kilocode"),
script_type: str = typer.Option(None, "--script", help="Script type to use: sh or ps"),
ignore_agent_tools: bool = typer.Option(False, "--ignore-agent-tools", help="Skip checks for AI agent tools like Claude Code"),
no_git: bool = typer.Option(False, "--no-git", help="Skip git repository initialization"),
Expand Down Expand Up @@ -888,6 +981,22 @@ def init(
# Ensure scripts are executable (POSIX)
ensure_executable_scripts(project_path, tracker=tracker)

# Generate AI-specific integration files
if selected_ai == "kilocode":
generate_kilocode_modes_from_templates(project_path, selected_script)
elif selected_ai == "cursor":
# Existing cursor logic unchanged
cursor_commands_dir = project_path / ".cursor" / "commands"
cursor_commands_dir.mkdir(parents=True, exist_ok=True)

template_commands_dir = project_path / ".specify" / "templates" / "commands"
for cmd_file in ["specify.md", "plan.md", "tasks.md"]:
src = template_commands_dir / cmd_file
dst = cursor_commands_dir / cmd_file
if src.exists():
shutil.copy2(src, dst)
# Other AI assistants unchanged...

# Git step
if not no_git:
tracker.start("git")
Expand Down Expand Up @@ -950,6 +1059,8 @@ def init(
steps_lines.append(" - See GEMINI.md for all available commands")
elif selected_ai == "copilot":
steps_lines.append(f"{step_num}. Open in Visual Studio Code and use [bold cyan]/specify[/], [bold cyan]/plan[/], [bold cyan]/tasks[/] commands with GitHub Copilot")
elif selected_ai == "kilocode":
steps_lines.append(f"{step_num}. Open in your IDE and use [bold cyan]/specify[/], [bold cyan]/plan[/], [bold cyan]/tasks[/] commands with Kilo Code")

# Removed script variant step (scripts are transparent to users)
step_num += 1
Expand Down