2025-12-03 20:17:02 +02:00
const path = require ( 'node:path' ) ;
const { BaseIdeSetup } = require ( './_base-ide' ) ;
const chalk = require ( 'chalk' ) ;
const fs = require ( 'fs-extra' ) ;
const yaml = require ( 'js-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 ) {
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-' ) || file . includes ( 'bmad' ) ) {
await fs . remove ( path . join ( bmadAgentsDir , file ) ) ;
}
}
console . log ( chalk . dim ( ` 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 = { } ) {
console . log ( chalk . cyan ( ` Setting up ${ this . name } ... ` ) ) ;
await this . cleanup ( projectDir ) ;
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 ) ;
console . log ( chalk . green ( ` ✓ ${ 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 ) {
console . warn ( chalk . yellow ( ` ⚠️ 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' ) ;
2025-12-13 17:50:33 +08:00
const agentData = yaml . parse ( yamlContent ) ;
2025-12-03 20:17:02 +02:00
if ( ! this . validateBmadCompliance ( agentData ) ) {
return ;
}
2025-12-03 22:44:13 -06:00
// 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 ] ;
// Handle both src/core/agents and src/modules/[module]/agents patterns
if ( folderAfterSrc === 'core' ) {
moduleName = 'core' ;
} else if ( folderAfterSrc === 'modules' ) {
moduleName = pathParts [ srcIndex + 2 ] ; // The actual module name
}
}
}
// 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 ) ;
2025-12-03 20:17:02 +02:00
// Create JSON definition
2025-12-03 22:44:13 -06:00
await this . createAgentDefinitionFromYaml ( agentsDir , sanitizedAgentName , agentData ) ;
2025-12-03 20:17:02 +02:00
// Create prompt file
2025-12-03 22:44:13 -06:00
await this . createAgentPromptFromYaml ( agentsDir , sanitizedAgentName , agentData , projectDir ) ;
2025-12-03 20:17:02 +02:00
}
/ * *
* 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 \n You 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 } ;