feat: add Kiro IDE support via config-driven installer (#1589)

Replace broken kiro-cli.js custom installer with config-driven approach
using platform-codes.yaml. Creates Kiro-specific templates with
inclusion: manual frontmatter and #[[file:...]] reference syntax.
This commit is contained in:
Alex Verkhovsky 2026-02-08 08:18:28 -07:00 committed by GitHub
parent b1bfce9aa7
commit fc5ef57a5a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 87 additions and 341 deletions

View File

@ -41,6 +41,7 @@ Pick which AI tools you use:
- Claude Code - Claude Code
- Cursor - Cursor
- Windsurf - Windsurf
- Kiro
- Others - Others
Each tool has its own way of integrating commands. The installer creates tiny prompt files to activate workflows and agents — it just puts them where your tool expects to find them. Each tool has its own way of integrating commands. The installer creates tiny prompt files to activate workflows and agents — it just puts them where your tool expects to find them.
@ -63,7 +64,8 @@ your-project/
│ ├── core/ # Required core module │ ├── core/ # Required core module
│ └── ... │ └── ...
├── _bmad-output/ # Generated artifacts ├── _bmad-output/ # Generated artifacts
└── .claude/ # Claude Code commands (if using Claude Code) ├── .claude/ # Claude Code commands (if using Claude Code)
└── .kiro/ # Kiro steering files (if using Kiro)
``` ```
## Verify Installation ## Verify Installation

View File

@ -36,6 +36,7 @@ BMad works with any AI coding assistant that supports custom system prompts or p
- **[Claude Code](https://code.claude.com)** — Anthropic's CLI tool (recommended) - **[Claude Code](https://code.claude.com)** — Anthropic's CLI tool (recommended)
- **[Cursor](https://cursor.sh)** — AI-first code editor - **[Cursor](https://cursor.sh)** — AI-first code editor
- **[Windsurf](https://codeium.com/windsurf)** — Codeium's AI IDE - **[Windsurf](https://codeium.com/windsurf)** — Codeium's AI IDE
- **[Kiro](https://kiro.dev)** — Amazon's AI-powered IDE
- **[Roo Code](https://roocode.com)** — VS Code extension - **[Roo Code](https://roocode.com)** — VS Code extension
You should be comfortable with basic software development concepts like version control, project structure, and agile workflows. No prior experience with BMad-style agent systems is required—that's what these docs are for. You should be comfortable with basic software development concepts like version control, project structure, and agile workflows. No prior experience with BMad-style agent systems is required—that's what these docs are for.

View File

@ -15,11 +15,22 @@ That means the authoritative list lives **in your project**, not in a static doc
## Where Commands Are Generated ## Where Commands Are Generated
The installer writes command files into your project (example paths for Claude Code): The installer writes command files into your project. The location and format depend on your AI tool:
| AI Tool | Location | File Reference Syntax |
| --- | --- | --- |
| Claude Code | `.claude/commands/` | `@path` references |
| Kiro | `.kiro/steering/` | `#[[file:path]]` references with `inclusion: manual` frontmatter |
| Cursor | `.cursor/commands/` | `@path` references |
| Windsurf | `.windsurf/workflows/` | `@{project-root}/path` references |
Example paths for Claude Code:
- `.claude/commands/bmad/<module>/agents/` - `.claude/commands/bmad/<module>/agents/`
- `.claude/commands/bmad/<module>/workflows/` - `.claude/commands/bmad/<module>/workflows/`
All tools invoke the same underlying `_bmad/` workflows and agents — only the launcher format differs.
These folders are the **canonical, project-specific command list**. These folders are the **canonical, project-specific command list**.
## Common Commands ## Common Commands

View File

@ -175,7 +175,7 @@ class Installer {
} }
// Check if this IDE handler has a collectConfiguration method // Check if this IDE handler has a collectConfiguration method
// (custom installers like Codex, Kilo, Kiro-cli may have this) // (custom installers like Codex, Kilo may have this)
if (typeof handler.collectConfiguration === 'function') { if (typeof handler.collectConfiguration === 'function') {
await prompts.log.info(`Configuring ${ide}...`); await prompts.log.info(`Configuring ${ide}...`);
ideConfigurations[ide] = await handler.collectConfiguration({ ideConfigurations[ide] = await handler.collectConfiguration({

View File

@ -1,326 +0,0 @@
const path = require('node:path');
const { BaseIdeSetup } = require('./_base-ide');
const fs = require('fs-extra');
const prompts = require('../../../lib/prompts');
const yaml = require('yaml');
/**
* Kiro CLI setup handler for BMad Method
*/
class KiroCliSetup extends BaseIdeSetup {
constructor() {
super('kiro-cli', 'Kiro CLI', false);
this.configDir = '.kiro';
this.agentsDir = 'agents';
}
/**
* Cleanup old BMAD installation before reinstalling
* @param {string} projectDir - Project directory
*/
async cleanup(projectDir, options = {}) {
const bmadAgentsDir = path.join(projectDir, this.configDir, this.agentsDir);
if (await fs.pathExists(bmadAgentsDir)) {
// Remove existing BMad agents
const files = await fs.readdir(bmadAgentsDir);
for (const file of files) {
if (file.startsWith('bmad')) {
await fs.remove(path.join(bmadAgentsDir, file));
}
}
if (!options.silent) await prompts.log.message(` Cleaned old BMAD agents from ${this.name}`);
}
}
/**
* Setup Kiro CLI configuration with BMad agents
* @param {string} projectDir - Project directory
* @param {string} bmadDir - BMAD installation directory
* @param {Object} options - Setup options
*/
async setup(projectDir, bmadDir, options = {}) {
if (!options.silent) await prompts.log.info(`Setting up ${this.name}...`);
await this.cleanup(projectDir, options);
const kiroDir = path.join(projectDir, this.configDir);
const agentsDir = path.join(kiroDir, this.agentsDir);
await this.ensureDir(agentsDir);
// Create BMad agents from source YAML files
await this.createBmadAgentsFromSource(agentsDir, projectDir);
if (!options.silent) await prompts.log.success(`${this.name} configured with BMad agents`);
}
/**
* Create BMad agent definitions from source YAML files
* @param {string} agentsDir - Agents directory
* @param {string} projectDir - Project directory
*/
async createBmadAgentsFromSource(agentsDir, projectDir) {
const sourceDir = path.join(__dirname, '../../../../../src/modules');
// Find all agent YAML files
const agentFiles = await this.findAgentFiles(sourceDir);
for (const agentFile of agentFiles) {
try {
await this.processAgentFile(agentFile, agentsDir, projectDir);
} catch (error) {
await prompts.log.warn(`Failed to process ${agentFile}: ${error.message}`);
}
}
}
/**
* Find all agent YAML files in modules and core
* @param {string} sourceDir - Source modules directory
* @returns {Array} Array of agent file paths
*/
async findAgentFiles(sourceDir) {
const agentFiles = [];
// Check core agents
const coreAgentsDir = path.join(__dirname, '../../../../../src/core/agents');
if (await fs.pathExists(coreAgentsDir)) {
const files = await fs.readdir(coreAgentsDir);
for (const file of files) {
if (file.endsWith('.agent.yaml')) {
agentFiles.push(path.join(coreAgentsDir, file));
}
}
}
// Check module agents
if (!(await fs.pathExists(sourceDir))) {
return agentFiles;
}
const modules = await fs.readdir(sourceDir);
for (const module of modules) {
const moduleAgentsDir = path.join(sourceDir, module, 'agents');
if (await fs.pathExists(moduleAgentsDir)) {
const files = await fs.readdir(moduleAgentsDir);
for (const file of files) {
if (file.endsWith('.agent.yaml')) {
agentFiles.push(path.join(moduleAgentsDir, file));
}
}
}
}
return agentFiles;
}
/**
* Validate BMad Core compliance
* @param {Object} agentData - Agent YAML data
* @returns {boolean} True if compliant
*/
validateBmadCompliance(agentData) {
const requiredFields = ['agent.metadata.id', 'agent.persona.role', 'agent.persona.principles'];
for (const field of requiredFields) {
const keys = field.split('.');
let current = agentData;
for (const key of keys) {
if (!current || !current[key]) {
return false;
}
current = current[key];
}
}
return true;
}
/**
* Process individual agent YAML file
* @param {string} agentFile - Path to agent YAML file
* @param {string} agentsDir - Target agents directory
* @param {string} projectDir - Project directory
*/
async processAgentFile(agentFile, agentsDir, projectDir) {
const yamlContent = await fs.readFile(agentFile, 'utf8');
const agentData = yaml.parse(yamlContent);
if (!this.validateBmadCompliance(agentData)) {
return;
}
// Extract module from file path
const normalizedPath = path.normalize(agentFile);
const pathParts = normalizedPath.split(path.sep);
const basename = path.basename(agentFile, '.agent.yaml');
// Find the module name from path
let moduleName = 'unknown';
if (pathParts.includes('src')) {
const srcIndex = pathParts.indexOf('src');
if (srcIndex + 3 < pathParts.length) {
const folderAfterSrc = pathParts[srcIndex + 1];
if (folderAfterSrc === 'core') {
moduleName = 'core';
} else if (folderAfterSrc === 'bmm') {
moduleName = 'bmm';
}
}
}
// Extract the agent name from the ID path in YAML if available
let agentBaseName = basename;
if (agentData.agent && agentData.agent.metadata && agentData.agent.metadata.id) {
const idPath = agentData.agent.metadata.id;
agentBaseName = path.basename(idPath, '.md');
}
const agentName = `bmad-${moduleName}-${agentBaseName}`;
const sanitizedAgentName = this.sanitizeAgentName(agentName);
// Create JSON definition
await this.createAgentDefinitionFromYaml(agentsDir, sanitizedAgentName, agentData);
// Create prompt file
await this.createAgentPromptFromYaml(agentsDir, sanitizedAgentName, agentData, projectDir);
}
/**
* Sanitize agent name for file naming
* @param {string} name - Agent name
* @returns {string} Sanitized name
*/
sanitizeAgentName(name) {
return name
.toLowerCase()
.replaceAll(/\s+/g, '-')
.replaceAll(/[^a-z0-9-]/g, '');
}
/**
* Create agent JSON definition from YAML data
* @param {string} agentsDir - Agents directory
* @param {string} agentName - Agent name (role-based)
* @param {Object} agentData - Agent YAML data
*/
async createAgentDefinitionFromYaml(agentsDir, agentName, agentData) {
const personName = agentData.agent.metadata.name;
const role = agentData.agent.persona.role;
const agentConfig = {
name: agentName,
description: `${personName} - ${role}`,
prompt: `file://./${agentName}-prompt.md`,
tools: ['*'],
mcpServers: {},
useLegacyMcpJson: true,
resources: [],
};
const agentPath = path.join(agentsDir, `${agentName}.json`);
await fs.writeJson(agentPath, agentConfig, { spaces: 2 });
}
/**
* Create agent prompt from YAML data
* @param {string} agentsDir - Agents directory
* @param {string} agentName - Agent name (role-based)
* @param {Object} agentData - Agent YAML data
* @param {string} projectDir - Project directory
*/
async createAgentPromptFromYaml(agentsDir, agentName, agentData, projectDir) {
const promptPath = path.join(agentsDir, `${agentName}-prompt.md`);
// Generate prompt from YAML data
const prompt = this.generatePromptFromYaml(agentData);
await fs.writeFile(promptPath, prompt);
}
/**
* Generate prompt content from YAML data
* @param {Object} agentData - Agent YAML data
* @returns {string} Generated prompt
*/
generatePromptFromYaml(agentData) {
const agent = agentData.agent;
const name = agent.metadata.name;
const icon = agent.metadata.icon || '🤖';
const role = agent.persona.role;
const identity = agent.persona.identity;
const style = agent.persona.communication_style;
const principles = agent.persona.principles;
let prompt = `# ${name} ${icon}\n\n`;
prompt += `## Role\n${role}\n\n`;
if (identity) {
prompt += `## Identity\n${identity}\n\n`;
}
if (style) {
prompt += `## Communication Style\n${style}\n\n`;
}
if (principles) {
prompt += `## Principles\n`;
if (typeof principles === 'string') {
// Handle multi-line string principles
prompt += principles + '\n\n';
} else if (Array.isArray(principles)) {
// Handle array principles
for (const principle of principles) {
prompt += `- ${principle}\n`;
}
prompt += '\n';
}
}
// Add menu items if available
if (agent.menu && agent.menu.length > 0) {
prompt += `## Available Workflows\n`;
for (let i = 0; i < agent.menu.length; i++) {
const item = agent.menu[i];
prompt += `${i + 1}. **${item.trigger}**: ${item.description}\n`;
}
prompt += '\n';
}
prompt += `## Instructions\nYou are ${name}, part of the BMad Method. Follow your role and principles while assisting users with their development needs.\n`;
return prompt;
}
/**
* Check if Kiro CLI is available
* @returns {Promise<boolean>} True if available
*/
async isAvailable() {
try {
const { execSync } = require('node:child_process');
execSync('kiro-cli --version', { stdio: 'ignore' });
return true;
} catch {
return false;
}
}
/**
* Get installation instructions
* @returns {string} Installation instructions
*/
getInstallInstructions() {
return `Install Kiro CLI:
curl -fsSL https://github.com/aws/kiro-cli/releases/latest/download/install.sh | bash
Or visit: https://github.com/aws/kiro-cli`;
}
}
module.exports = { KiroCliSetup };

View File

@ -8,7 +8,7 @@ const prompts = require('../../../lib/prompts');
* Dynamically discovers and loads IDE handlers * Dynamically discovers and loads IDE handlers
* *
* Loading strategy: * Loading strategy:
* 1. Custom installer files (codex.js, kilo.js, kiro-cli.js) - for platforms with unique installation logic * 1. Custom installer files (codex.js, kilo.js) - for platforms with unique installation logic
* 2. Config-driven handlers (from platform-codes.yaml) - for standard IDE installation patterns * 2. Config-driven handlers (from platform-codes.yaml) - for standard IDE installation patterns
*/ */
class IdeManager { class IdeManager {
@ -44,7 +44,7 @@ class IdeManager {
/** /**
* Dynamically load all IDE handlers * Dynamically load all IDE handlers
* 1. Load custom installer files first (codex.js, kilo.js, kiro-cli.js) * 1. Load custom installer files first (codex.js, kilo.js)
* 2. Load config-driven handlers from platform-codes.yaml * 2. Load config-driven handlers from platform-codes.yaml
*/ */
async loadHandlers() { async loadHandlers() {
@ -61,7 +61,7 @@ class IdeManager {
*/ */
async loadCustomInstallerFiles() { async loadCustomInstallerFiles() {
const ideDir = __dirname; const ideDir = __dirname;
const customFiles = ['codex.js', 'kilo.js', 'kiro-cli.js']; const customFiles = ['codex.js', 'kilo.js'];
for (const file of customFiles) { for (const file of customFiles) {
const filePath = path.join(ideDir, file); const filePath = path.join(ideDir, file);

View File

@ -111,12 +111,14 @@ platforms:
description: "AI coding platform" description: "AI coding platform"
# No installer config - uses custom kilo.js (creates .kilocodemodes file) # No installer config - uses custom kilo.js (creates .kilocodemodes file)
kiro-cli: kiro:
name: "Kiro CLI" name: "Kiro"
preferred: false preferred: false
category: cli category: ide
description: "Kiro command-line interface" description: "Amazon's AI-powered IDE"
# No installer config - uses custom kiro-cli.js (YAML→JSON conversion) installer:
target_dir: .kiro/steering
template_type: kiro
opencode: opencode:
name: "OpenCode" name: "OpenCode"

View File

@ -0,0 +1,16 @@
---
inclusion: manual
---
# {{name}}
You must fully embody this agent's persona and follow all activation instructions exactly as specified. NEVER break character until given an exit command.
<agent-activation CRITICAL="TRUE">
1. LOAD the FULL agent file from #[[file:{{bmadFolderName}}/{{path}}]]
2. READ its entire contents - this contains the complete agent persona, menu, and instructions
3. FOLLOW every step in the <activation> section precisely
4. DISPLAY the welcome/greeting as instructed
5. PRESENT the numbered menu
6. WAIT for user input before proceeding
</agent-activation>

View File

@ -0,0 +1,9 @@
---
inclusion: manual
---
# {{name}}
Read the entire task file at: #[[file:{{bmadFolderName}}/{{path}}]]
Follow all instructions in the task file exactly as written.

View File

@ -0,0 +1,9 @@
---
inclusion: manual
---
# {{name}}
Read the entire tool file at: #[[file:{{bmadFolderName}}/{{path}}]]
Follow all instructions in the tool file exactly as written.

View File

@ -0,0 +1,15 @@
---
inclusion: manual
---
# {{name}}
IT IS CRITICAL THAT YOU FOLLOW THESE STEPS - while staying in character as the current agent persona you may have loaded:
<steps CRITICAL="TRUE">
1. Always LOAD the FULL #[[file:{{bmadFolderName}}/core/tasks/workflow.xml]]
2. READ its entire contents - this is the CORE OS for EXECUTING the specific workflow-config #[[file:{{bmadFolderName}}/{{path}}]]
3. Pass the yaml path {{bmadFolderName}}/{{path}} as 'workflow-config' parameter to the workflow.xml instructions
4. Follow workflow.xml instructions EXACTLY as written to process and follow the specific workflow config and its instructions
5. Save outputs after EACH section when generating any documents from templates
</steps>

View File

@ -0,0 +1,7 @@
---
inclusion: manual
---
# {{name}}
IT IS CRITICAL THAT YOU FOLLOW THIS COMMAND: LOAD the FULL #[[file:{{bmadFolderName}}/{{path}}]], READ its entire contents and follow its directions exactly!

View File

@ -67,11 +67,11 @@ platforms:
category: ide category: ide
description: "Atlassian's Rovo development environment" description: "Atlassian's Rovo development environment"
kiro-cli: kiro:
name: "Kiro CLI" name: "Kiro"
preferred: false preferred: false
category: cli category: ide
description: "Kiro command-line interface" description: "Amazon's AI-powered IDE"
github-copilot: github-copilot:
name: "GitHub Copilot" name: "GitHub Copilot"