Skip to content
Merged
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: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,12 @@ The generator supports the following AI coding assistants:
- Windows: `%APPDATA%\Code - Insiders\User\prompts`
- **OpenCode CLI**: Commands installed to `~/.config/opencode/command`
- **Amazon Q**: Commands installed to `~/.aws/amazonq/prompts` (Windows & macOS/Linux)
- **Kiro CLI**: Prompts installed to `~/.kiro/prompts`
- Invoke with `@prompt-name` (e.g., `@generate-spec`)
- **Note**: Kiro CLI prompts do not support tool permissions. Run `/tools trust-all` at the start of your session to auto-approve file operations (write, shell, etc.).
- **Kiro IDE**: Steering files installed to `~/.kiro/steering`
- Invoke with `/prompt-name` slash command (e.g., `/generate-spec`)
- Files have `inclusion: manual` frontmatter for manual inclusion

## Documentation

Expand Down
52 changes: 51 additions & 1 deletion docs/slash-command-generator.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ The Slash Command Generator automates the creation of slash command files for AI

The generator reads markdown prompts from the `prompts/` directory and produces command files in the appropriate format for each configured AI assistant. It supports:

- **Multiple agents**: 7 supported AI assistants with different command formats
- **Multiple agents**: 11 supported AI assistants with different command formats
- **Auto-detection**: Automatically detects configured agents in your workspace
- **Dry run mode**: Preview changes without writing files
- **Safe overwrite handling**: Prompts before overwriting existing files with backup support
Expand Down Expand Up @@ -189,10 +189,13 @@ The following agents are supported:

| Agent | Display Name | Format | Extension | Target Directory | Reference |
|-------|--------------|--------|-----------|------------------|-----------|
| `amazon-q` | Amazon Q | Markdown | `.md` | `.aws/amazonq/prompts` | [Home](https://aws.amazon.com/q/) · [Docs](https://docs.aws.amazon.com/amazonq/) |
| `claude-code` | Claude Code | Markdown | `.md` | `.claude/commands` | [Home](https://docs.claude.com/) · [Docs](https://docs.claude.com/en/docs/claude-code/overview) |
| `codex-cli` | Codex CLI | Markdown | `.md` | `.codex/prompts` | [Home](https://developers.openai.com/codex) · [Docs](https://developers.openai.com/codex/cli/) |
| `cursor` | Cursor | Markdown | `.md` | `.cursor/commands` | [Home](https://cursor.com/) · [Docs](https://cursor.com/docs) |
| `gemini-cli` | Gemini CLI | TOML | `.toml` | `.gemini/commands` | [Home](https://github.com/google-gemini/gemini-cli) · [Docs](https://geminicli.com/docs/) |
| `kiro-cli` | Kiro CLI | Kiro | `.md` | `.kiro/prompts` | [Home](https://kiro.dev/cli/) · [Docs](https://kiro.dev/docs/cli/) |
| `kiro-ide` | Kiro IDE | Kiro IDE | `.md` | `.kiro/steering` | [Home](https://kiro.dev/) · [Docs](https://kiro.dev/docs/) |
| `opencode` | OpenCode CLI | Markdown | `.md` | `.config/opencode/command` | [Home](https://opencode.ai) · [Docs](https://opencode.ai/docs/commands) |
| `vs-code` | VS Code | Markdown | `.prompt.md` | Platform-specific (see note below) | [Home](https://code.visualstudio.com/) · [Docs](https://code.visualstudio.com/docs) |
| `vs-code-insiders` | VS Code Insiders | Markdown | `.prompt.md` | Platform-specific (see note below) | [Home](https://code.visualstudio.com/insiders/) · [Docs](https://code.visualstudio.com/docs) |
Expand All @@ -214,6 +217,53 @@ The following agents are supported:

The generator automatically detects your platform and installs commands to the correct location. VS Code and VS Code Insiders maintain separate prompt directories and do not share configurations.

### Kiro (CLI and IDE)

Kiro has two separate products that use different command formats. You can install for one or both:

| Product | What it does | Install path | Invocation |
|---------|-------------|--------------|------------|
| **Kiro CLI** | Terminal-based AI assistant | `~/.kiro/prompts/*.md` | `@prompt-name` |
| **Kiro IDE** | VS Code-based IDE with steering files | `~/.kiro/steering/*.md` | `/prompt-name` |

#### Quick Start

```bash
# Install for Kiro CLI only
uv run slash-man generate --agent kiro-cli --yes

# Install for Kiro IDE only
uv run slash-man generate --agent kiro-ide --yes

# Install for both
uv run slash-man generate --agent kiro-cli --agent kiro-ide --yes

# Install SDD workflow prompts from GitHub
uv run slash-man generate --agent kiro-cli --agent kiro-ide \
--github-repo liatrio-labs/spec-driven-workflow \
--github-branch main --github-path prompts --yes
```

#### How It Works

The generator automatically adapts prompts for Kiro's conventions:

- **Kiro IDE steering files get YAML frontmatter** with `inclusion: manual`, `name`, `description`, and `tools: ["*"]` (wildcard tool access)
- **Kiro CLI prompts are plain markdown** with no frontmatter — just the prompt body
- **Prompt names are preserved**: A source prompt named `SDD-1-generate-spec` becomes `SDD-1-generate-spec.md` and is invoked as `@SDD-1-generate-spec` (CLI) or `/SDD-1-generate-spec` (IDE)

#### Kiro CLI Tool Permissions

Kiro CLI prompts do not support declaring tool permissions. By default, Kiro CLI will prompt you to confirm every `write` and `shell` operation, which interrupts workflows like SDD that need to create files automatically.

**Workaround**: Run `/tools trust-all` at the start of your Kiro CLI session to auto-approve all tool operations for that session. This only needs to be done once per session.

```text
> /tools trust-all
```

See the [Kiro CLI permissions documentation](https://kiro.dev/docs/cli/chat/permissions/) for more details.

## Command File Formats

### Markdown Format
Expand Down
20 changes: 20 additions & 0 deletions slash_commands/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ class CommandFormat(str, Enum):

MARKDOWN = "markdown"
TOML = "toml"
KIRO = "kiro"
KIRO_IDE = "kiro-ide"


@dataclass(frozen=True)
Expand Down Expand Up @@ -140,6 +142,24 @@ def get_command_dir(self) -> str:
(".aws/amazonq",),
None,
),
(
"kiro-cli",
"Kiro CLI",
".kiro/prompts",
CommandFormat.KIRO,
".md",
(".kiro",),
None,
),
(
"kiro-ide",
"Kiro IDE",
".kiro/steering",
CommandFormat.KIRO_IDE,
".md",
(".kiro",),
None,
),
)

_SORTED_AGENT_DATA = tuple(sorted(_SUPPORTED_AGENT_DATA, key=lambda item: item[0]))
Expand Down
116 changes: 116 additions & 0 deletions slash_commands/generators.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from __future__ import annotations

import re
from datetime import UTC, datetime
from typing import Any, Protocol

Expand Down Expand Up @@ -291,6 +292,117 @@ def _dict_to_toml(self, data: dict) -> str:
return tomli_w.dumps(data)


def _strip_ordering_prefix(name: str) -> str:
"""Strip ordering prefixes like 'SDD-1-' from a prompt name."""
return re.sub(r"^[A-Z]+-\d+-", "", name)


class KiroCommandGenerator:
"""Generator for Kiro CLI prompts.

Kiro CLI expects simple markdown files with no frontmatter.
The prompt content is injected directly when the user invokes @prompt-name.
Tracking metadata is appended as a trailing HTML comment so it does not
interfere with the prompt instructions the model sees first.
"""

def generate(
self,
prompt: MarkdownPrompt,
agent: AgentConfig,
source_metadata: dict[str, Any] | None = None,
) -> str:
"""Generate a Kiro CLI prompt file.

Args:
prompt: The source prompt to generate from
agent: The agent configuration
source_metadata: Optional source metadata (local or GitHub)

Returns:
Simple markdown content for Kiro CLI
"""
_description, arguments, _enabled = _apply_agent_overrides(prompt, agent)

# Replace placeholders in body
body = _replace_placeholders(prompt.body, arguments, replace_double_braces=True)

# Output the prompt body directly — no extra headers or preamble.
# The body already contains its own structure (headings, sections, etc.)
output = body + "\n"

# Append tracking metadata as a trailing HTML comment.
# Placed at the end so it doesn't pollute the instructions the model sees first.
meta_lines = [
f"source: {prompt.name}",
f"version: {__version__}",
f"updated: {datetime.now(UTC).strftime('%Y-%m-%d')}",
]
if source_metadata:
if "source_repo" in source_metadata:
meta_lines.append(f"repo: {source_metadata['source_repo']}")

output += "\n<!-- slash-command-manager: " + " | ".join(meta_lines) + " -->\n"
Comment thread
coderabbitai[bot] marked this conversation as resolved.

return _normalize_output(output)


class KiroIdeCommandGenerator:
"""Generator for Kiro IDE steering files.

Kiro IDE expects markdown files with YAML frontmatter containing
inclusion mode. Files are stored at ~/.kiro/steering/*.md and
are manually included via / command markers in chat.
"""

def generate(
self,
prompt: MarkdownPrompt,
agent: AgentConfig,
source_metadata: dict[str, Any] | None = None,
) -> str:
"""Generate a Kiro IDE steering file.

Args:
prompt: The source prompt to generate from
agent: The agent configuration
source_metadata: Optional source metadata (local or GitHub)

Returns:
Markdown with Kiro IDE steering frontmatter
"""
description, arguments, _enabled = _apply_agent_overrides(prompt, agent)

# Build Kiro IDE steering frontmatter with inclusion first, then name, description, tools
frontmatter: dict[str, Any] = {
"inclusion": "manual",
"name": prompt.name,
"description": description,
"tools": ["*"],
}

# Replace placeholders and rewrite command references (/ prefix for steering files)
body = _replace_placeholders(prompt.body, arguments, replace_double_braces=True)

# Format as YAML frontmatter + body
yaml_content = yaml.safe_dump(frontmatter, allow_unicode=True, sort_keys=False)
output = f"---\n{yaml_content}---\n\n{body}\n"

# Append tracking metadata as a trailing HTML comment
meta_lines = [
f"source: {prompt.name}",
f"version: {__version__}",
f"updated: {datetime.now(UTC).strftime('%Y-%m-%d')}",
]
if source_metadata:
if "source_repo" in source_metadata:
meta_lines.append(f"repo: {source_metadata['source_repo']}")

output += "\n<!-- slash-command-manager: " + " | ".join(meta_lines) + " -->\n"

return _normalize_output(output)


class CommandGenerator:
"""Base class for command generators."""

Expand All @@ -301,5 +413,9 @@ def create(format: CommandFormat) -> CommandGeneratorProtocol:
return MarkdownCommandGenerator()
elif format == CommandFormat.TOML:
return TomlCommandGenerator()
elif format == CommandFormat.KIRO:
return KiroCommandGenerator()
elif format == CommandFormat.KIRO_IDE:
return KiroIdeCommandGenerator()
else:
raise ValueError(f"Unsupported command format: {format}")
14 changes: 14 additions & 0 deletions slash_commands/writer.py
Original file line number Diff line number Diff line change
Expand Up @@ -510,6 +510,8 @@ def _is_generated_file(self, file_path: Path, agent: AgentConfig) -> bool:
return self._is_generated_markdown(content)
elif agent.command_format.value == "toml":
return self._is_generated_toml(content)
elif agent.command_format.value in ("kiro", "kiro-ide"):
return self._is_generated_kiro(content)
return False

def _is_generated_markdown(self, content: str) -> bool:
Expand Down Expand Up @@ -561,6 +563,18 @@ def _is_generated_toml(self, content: str) -> bool:
except tomllib.TOMLDecodeError:
return False

def _is_generated_kiro(self, content: str) -> bool:
"""Check if Kiro CLI content was generated by this tool.

Args:
content: File content

Returns:
True if generated by this tool
"""
# Kiro files have an HTML comment marker (at the end of the file)
return "<!-- slash-command-manager:" in content

def cleanup(
self, agents: list[str] | None = None, include_backups: bool = True, dry_run: bool = False
) -> dict[str, Any]:
Expand Down
19 changes: 14 additions & 5 deletions tests/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,12 @@ def supported_agents_by_key() -> dict[str, AgentConfig]:
return {agent.key: agent for agent in SUPPORTED_AGENTS}


def test_command_format_defines_markdown_and_toml():
def test_command_format_defines_markdown_toml_and_kiro():
assert CommandFormat.MARKDOWN.value == "markdown"
assert CommandFormat.TOML.value == "toml"
assert {member.value for member in CommandFormat} == {"markdown", "toml"}
assert CommandFormat.KIRO.value == "kiro"
assert CommandFormat.KIRO_IDE.value == "kiro-ide"
assert {member.value for member in CommandFormat} == {"markdown", "toml", "kiro", "kiro-ide"}


def test_agent_config_is_frozen_dataclass():
Expand Down Expand Up @@ -64,8 +66,10 @@ def test_supported_agents_have_valid_structure(
or agent.command_dir.endswith("/prompts")
or agent.command_dir.endswith("/global_workflows")
or agent.command_dir.endswith("/command")
or agent.command_dir.endswith("/agents")
or agent.command_dir.endswith("/steering")
), (
f"{agent.key}: command_dir must end with /commands, /prompts, /global_workflows, or /command"
f"{agent.key}: command_dir must end with /commands, /prompts, /global_workflows, /command, /agents, or /steering"
)
# File extension must start with a dot
assert agent.command_file_extension.startswith("."), (
Expand All @@ -91,10 +95,15 @@ def test_supported_agents_have_valid_command_formats(
supported_agents_by_key: dict[str, AgentConfig],
):
"""Validate that all agents use a supported command format."""
valid_formats = {CommandFormat.MARKDOWN, CommandFormat.TOML}
valid_formats = {
CommandFormat.MARKDOWN,
CommandFormat.TOML,
CommandFormat.KIRO,
CommandFormat.KIRO_IDE,
}
for agent in supported_agents_by_key.values():
assert agent.command_format in valid_formats, (
f"{agent.key}: command_format must be MARKDOWN or TOML"
f"{agent.key}: command_format must be MARKDOWN, TOML, KIRO, or KIRO_IDE"
)


Expand Down
Loading