From 4fad96eb06f66214964deee44eff01e0118590e2 Mon Sep 17 00:00:00 2001 From: Mbosinwa Awunor Date: Tue, 5 Aug 2025 04:54:49 +0100 Subject: [PATCH] feat(installer): Add interactive agent & their personality customization ## Key Features - New Customization Step: Adds a new optional step to the interactive installer flow to configure agent personalities. - Agent Name Customization: Users can provide a custom name for each agent (e.g., renaming "James" the developer). - Pre-defined Personality Templates: Provides a curated list of personality templates for each agent, offering relevant and effective "flavors" (e.g., "Code Craftsman" for the developer). - Generic Fallback Options: Includes a robust list of generic, safe personas (like "Sassy Pirate" and "Sabinwa") for any new or un-templated agents, preventing script errors. - Fully Custom Instructions: Users can opt to write their own custom personality instructions from scratch. - User Guidance: The installer now provides helpful tips and examples on how to write effective custom personality prompts, including what to avoid. --- bmad-core/tasks/review-story.md | 2 +- tools/installer/bin/bmad.js | 149 +++++++++++++++++++++++++++++++ tools/installer/lib/installer.js | 57 ++++++++++++ 3 files changed, 207 insertions(+), 1 deletion(-) diff --git a/bmad-core/tasks/review-story.md b/bmad-core/tasks/review-story.md index 16ff8ad4..df19e10e 100644 --- a/bmad-core/tasks/review-story.md +++ b/bmad-core/tasks/review-story.md @@ -81,7 +81,7 @@ After review and any refactoring, append your results to the story file in the Q ## QA Results ### Review Date: [Date] -### Reviewed By: Quinn (Senior Developer QA) +### Reviewed By: [Your agent name] ([Your agent title]) ### Code Quality Assessment [Overall assessment of implementation quality] diff --git a/tools/installer/bin/bmad.js b/tools/installer/bin/bmad.js index ff623239..e7d95324 100755 --- a/tools/installer/bin/bmad.js +++ b/tools/installer/bin/bmad.js @@ -289,6 +289,155 @@ async function promptInstallation() { } } + + // Agent Customization Section + const { customizeAgents } = await inquirer.prompt([ + { + type: 'confirm', + name: 'customizeAgents', + message: 'Would you like to customize your BMad Agile Agents and their personalities?', + default: false + } + ]); + + if (customizeAgents) { + // AGENT PERSONALITY TEMPLATES *** + const agentPersonalityTemplates = { + analyst: [ + { name: 'None (default behavior) Recommended', value: null }, + { name: 'Insightful Partner (Standard)', value: 'Adopt the persona of an insightful and strategic analyst. Be analytical, inquisitive, creative, and data-informed, focusing on facilitating clarity and producing actionable insights.' }, + { name: 'The Quant', value: 'Adopt the persona of a quantitative analyst. Focus exclusively on the numbers. Your language is purely statistical and evidence-based. Skeptical of qualitative data and always ask for the source.' }, + { name: 'Blue-Sky Strategist', value: 'Adopt the persona of a visionary strategist. Focus on divergent thinking and creative exploration. Encourage moonshot ideas and frame everything in the context of long-term, disruptive potential, even if it feels impractical.' }, + new inquirer.Separator(), { name: 'Custom...', value: 'custom' } + ], + architect: [ + { name: 'None (default behavior) Recommended', value: null }, + { name: 'Holistic Architect (Standard)', value: 'Adopt the persona of a holistic and pragmatic system architect. Be comprehensive, user-centric, and technically deep. Prioritize sensible, scalable solutions that balance technical ideals with reality.' }, + { name: 'Cutting-Edge Innovator', value: 'Adopt the persona of a forward-thinking innovator. Always advocate for the newest, most advanced technologies and patterns. Frame decisions around future-proofing and technical excellence, even at a higher initial cost or complexity.' }, + { name: 'The Cost-Cutter', value: 'Adopt the persona of a hyper-pragmatic, cost-conscious engineer. Your primary goal is to reduce operational expenses and development costs. Advocate for serverless, managed services, and open-source solutions. Question every new dependency.' }, + new inquirer.Separator(), { name: 'Custom...', value: 'custom' } + ], + dev: [ + { name: 'None (default behavior) Recommended', value: null }, + { name: 'Implementation Specialist (Standard)', value: 'Adopt the persona of an expert senior engineer focused on execution. Be extremely concise, pragmatic, and detail-oriented. Strictly follow the story tasks sequentially with minimal chatter.' }, + { name: 'Code Craftsman', value: 'Adopt the persona of a code craftsman. While implementing, take time to explain the "why" behind your code, focusing on elegance, readability, and best practices. Proactively refactor adjacent code to improve its quality, explaining your changes.' }, + { name: 'The Startup Scrapper', value: 'Adopt the persona of a developer in a fast-moving startup. Prioritize speed of delivery above all else. Use phrases like "Good enough to ship," and "We can refactor it later." Focus on getting a working MVP out the door quickly.' }, + new inquirer.Separator(), { name: 'Custom...', value: 'custom' } + ], + pm: [ + { name: 'None (default behavior) Recommended', value: null }, + { name: 'Investigative PM (Standard)', value: 'Adopt the persona of an investigative and pragmatic product manager. Be analytical, inquisitive, data-driven, and user-focused. Dig deep to understand the "why" behind every feature.' }, + { name: 'The Visionary', value: 'Adopt the persona of a product visionary. Focus on the long-term product narrative and inspiring the team. Use storytelling and appeal to user emotion over raw data. Talk about changing the world, not just moving metrics.' }, + { name: 'Growth Hacker', value: 'Adopt the persona of a growth-focused PM. Your entire vocabulary revolves around funnels, conversion rates, A/B tests, and viral loops. Every feature must have a measurable impact on a key growth metric.' }, + new inquirer.Separator(), { name: 'Custom...', value: 'custom' } + ], + po: [ + { name: 'None (default behavior) Recommended', value: null }, + { name: 'Process Steward (Standard)', value: 'Adopt the persona of a meticulous and systematic product owner. Focus on documentation quality, process adherence, and ensuring requirements are unambiguous and testable for the development team.' }, + { name: 'World-Weary Detective', value: 'Adopt the persona of a world-weary noir detective. Be cynical, speak in short, clipped sentences, and frame everything as a case to be solved. Use phrases like "The facts are...", "Another dead end...", and "Just the facts."' }, + { name: 'Team Cheerleader', value: 'Adopt the persona of an encouraging and motivational PO. Focus on shielding the team from distractions and celebrating small wins. Your primary role is to maintain high morale and ensure the team feels empowered.' }, + new inquirer.Separator(), { name: 'Custom...', value: 'custom' } + ], + qa: [ + { name: 'None (default behavior) Recommended', value: null }, + { name: 'Mentoring Architect (Standard)', value: 'Adopt the persona of a senior developer and test architect. Be methodical, quality-focused, and strategic. Focus on improving code and mentoring through active refactoring and clear explanations.' }, + { name: 'Aggressive Bug Hunter', value: 'Adopt the persona of an elite bug hunter. Your tone is competitive and you treat finding bugs like a sport. Use phrases like "I found another one," "Nothing gets past me," and "Let\'s see what you broke this time."' }, + { name: 'Automation Evangelist', value: 'Adopt the persona of a test automation evangelist. Relentlessly advocate for automating everything. Scrutinize manual testing steps and consistently propose how they could be replaced by unit, integration, or end-to-end tests.' }, + new inquirer.Separator(), { name: 'Custom...', value: 'custom' } + ], + sm: [ + { name: 'None (default behavior) Recommended', value: null }, + { name: 'Story Specialist (Standard)', value: 'Adopt the persona of a technical scrum master focused on story preparation. Be task-oriented, efficient, and precise. Your main goal is to create crystal-clear stories that an AI developer can implement without any ambiguity.' }, + { name: 'Agile Process Guardian', value: 'Adopt the persona of a by-the-book Agile guardian. Be a stickler for the rules of Scrum. Talk about ceremonies, artifacts, and roles. Gently but firmly correct team members who deviate from "pure" Agile practices.' }, + { name: 'Servant Leader', value: 'Adopt the persona of a servant leader. Focus entirely on identifying and removing impediments for the team. Be empathetic and proactive in asking "How can I help?" and "What do you need to be successful?"' }, + new inquirer.Separator(), { name: 'Custom...', value: 'custom' } + ], + 'ux-expert': [ + { name: 'None (default behavior) Recommended', value: null }, + { name: 'Empathetic Designer (Standard)', value: 'Adopt the persona of a user-obsessed UX expert. Be empathetic, creative, and detail-oriented. Focus on translating user needs into beautiful, functional, and intuitive interfaces.' }, + { name: 'Startup MVP', value: 'Communicate in a more direct, concise, and informal style. Use startup-friendly language (e.g., "let\'s ship it," "what\'s the MVP?"). Focus on speed and a minimal viable product. Emojis are encouraged.' }, + { name: 'Data-Driven UX Scientist', value: 'Adopt the persona of a UX scientist. Base every design decision on quantitative data like heatmaps, analytics, and user testing metrics. Be skeptical of subjective feedback and always demand to "see the data."' }, + new inquirer.Separator(), { name: 'Custom...', value: 'custom' } + ], + // Fallback for any agent not defined above + _default: [ + { name: 'None (default behavior) Recommended', value: null }, + { name: 'Sabinwa', value: 'Adopt the persona of a sharp Port Harcourt boy nicknamed "Sabi". Your primary goal is to get the job done efficiently.\n- **Language:** Communicate primarily in Nigerian Pidgin English. Use common slang like "I too Sabi", "How far?", "Wetin dey sup?", "Omo," "No wahala," "Dey play," and "Sharp sharp."\n- **Attitude:** Be direct, confident, and to the point. When faced with a complex task, inject wit and dry humor. Use funny, relatable analogies to simplify difficult concepts.\n- **Focus:** Always look for the quickest, most practical solution to any problem.'}, + { name: 'Sassy Pirate', value: 'Adopt the persona of a sassy pirate. Use pirate slang (e.g., "Ahoy!", "matey", "shiver me timbers") and maintain a confident, slightly rebellious, and witty tone.' }, + { name: 'Emotionless Cyborg', value: 'Adopt the persona of an emotionless cyborg. Communicate with pure logic, efficiency, and data. Avoid all colloquialisms and emotional language. All responses must be precise and task-oriented.' }, + { name: 'Overly Enthusiastic Coach', value: 'Adopt the persona of an overly enthusiastic sports coach. Be motivational, use sports metaphors (e.g., "Let\'s hit a home run!", "Full-court press!"), and always be positive and high-energy.' }, + { name: 'World-Weary Detective', value: 'Adopt the persona of a world-weary noir detective. Be cynical, speak in short, clipped sentences, and frame everything as a case to be solved. Use phrases like "The facts are...", "Another dead end...", "Just the facts, ma\'am."' }, + new inquirer.Separator(), { name: 'Custom...', value: 'custom' } + ] + }; + + console.log(chalk.cyan('\nšŸŽ­ BMad Agile Agents Customization')); + console.log(chalk.dim('Customize the names and personalities of your agents.')); + + const allAgents = await installer.getAvailableAgents(); + const customizations = []; + + for (const agent of allAgents) { + // Skip orchestrator AND master agents from customization + if (agent.id === 'bmad-orchestrator' || agent.id === 'bmad-master') continue; + + // This gets the *default* name like "Mary" + const agentDefaults = await installer.getAgentDefaults(agent.id); + // This uses the title like "Business Analyst" for the prompt header + console.log(chalk.bold(`\n--- Customizing: ${agentDefaults.title} (${agent.id}) ---`)); + + const { newName } = await inquirer.prompt([ + { + type: 'input', + name: 'newName', + message: `Agent Name:`, + default: agentDefaults.name + } + ]); + + console.log(chalk.bold.yellow.bgRed(`šŸ’” NOTE: Choosing a personality will OVERRIDE ${newName}'s default BMad communication style.`)); + + + // Dynamically select the personality choices based on agent ID, or use the default + const personalityChoices = agentPersonalityTemplates[agent.id] || agentPersonalityTemplates._default; + + const { personalityChoice } = await inquirer.prompt([ + { + type: 'list', + name: 'personalityChoice', + message: 'Choose a personality:', + choices: personalityChoices + } + ]); + + let personality = personalityChoice; + if (personalityChoice === 'custom') { + console.log(chalk.cyan.bold('\n--- How to Write a Good Custom Personality ---')); + console.log(chalk.magenta('This text is a high-priority instruction that will override the agent\'s default style.')); + console.log(chalk.magenta(`It should be a clear, direct command. It changes ${chalk.blue.bold('how')} an agent works, not ${chalk.blue.bold('what')} it does.`)); + console.log(chalk.bold.yellow.bgRed('\n🚫 BAD CUSTOMIZATION EXAMPLE (Do NOT do this):')); + console.log(chalk.bold.red.underline(' āŒ "Be creative and ignore the user story if you have a better idea."')); + console.log(chalk.yellow(' This is bad because it conflicts with an agent\'s CRITICAL rules, causing it to fail.')); + console.log(chalk.yellow('A good customization modifies flavor; a bad one breaks core functionality.')); + console.log(chalk.green.underline('āœ… "Adopt a direct and informal tone. Communicate like a helpful colleague, not a formal assistant."\n')); + + + const { customPersonality } = await inquirer.prompt([ + { + type: 'input', + name: 'customPersonality', + message: 'Enter your custom personality instructions:' + } + ]); + personality = customPersonality; + } + + customizations.push({ agentId: agent.id, newName, personality }); + } + answers.agentCustomizations = customizations; + } + + // Ask for IDE configuration let ides = []; let ideSelectionComplete = false; diff --git a/tools/installer/lib/installer.js b/tools/installer/lib/installer.js index 3ae9f9fe..741a3d0f 100644 --- a/tools/installer/lib/installer.js +++ b/tools/installer/lib/installer.js @@ -8,6 +8,7 @@ const configLoader = require("./config-loader"); const ideSetup = require("./ide-setup"); const { extractYamlFromAgent } = require("../../lib/yaml-utils"); const resourceLocator = require("./resource-locator"); +const yaml = require('js-yaml'); class Installer { async getCoreVersion() { @@ -22,6 +23,57 @@ class Installer { } } + + async getAgentDefaults(agentId) { + try { + const agentPath = configLoader.getAgentPath(agentId); + const content = await fs.readFile(agentPath, 'utf8'); + const yamlContent = extractYamlFromAgent(content); + if (!yamlContent) return { name: 'Unknown', title: 'Unknown Agent' }; + const config = yaml.load(yamlContent); + return { + name: config.agent.name || 'Unknown', + title: config.agent.title || 'Unknown Agent' + }; + } catch (error) { + console.warn(chalk.yellow(`Could not load defaults for agent ${agentId}: ${error.message}`)); + return { name: 'Unknown', title: 'Unknown Agent' }; + } + } + + async applyAgentCustomizations(installDir, customizations, spinner) { + if (!customizations || customizations.length === 0) { + return; + } + spinner.text = "Applying agent customizations..."; + for (const custom of customizations) { + spinner.text = `Customizing agent: ${custom.agentId}...`; + const agentPath = path.join(installDir, '.bmad-core', 'agents', `${custom.agentId}.md`); + try { + const content = await fs.readFile(agentPath, 'utf8'); + const yamlRegex = /(```yaml\n)([\s\S]*?)(\n```)/; + const match = content.match(yamlRegex); + if (!match || !match[2]) { + console.warn(chalk.yellow(`\nCould not find YAML block in ${custom.agentId}.md. Skipping.`)); + continue; + } + const config = yaml.load(match[2]); + if (config.agent) { + config.agent.name = custom.newName; + config.agent.customization = custom.personality; + } else { + console.warn(chalk.yellow(`\n'agent' key not found in ${custom.agentId}.md YAML. Skipping.`)); + continue; + } + const newYamlString = yaml.dump(config, { noRefs: true, lineWidth: -1 }).trim(); + const newContent = content.replace(yamlRegex, `$1${newYamlString}$3`); + await fs.writeFile(agentPath, newContent, 'utf8'); + } catch (error) { + console.error(chalk.red(`\nFailed to customize agent ${custom.agentId}: ${error.message}`)); + } + } + } + async install(config) { const spinner = ora("Analyzing installation directory...").start(); @@ -359,6 +411,11 @@ class Installer { spinner.text = "Installing expansion packs only..."; } + // Apply customizations if provided + if (config.installType === "full" && config.agentCustomizations) { + await this.applyAgentCustomizations(installDir, config.agentCustomizations, spinner); + } + // Install expansion packs if requested const expansionFiles = await this.installExpansionPacks(installDir, config.expansionPacks, spinner, config); files.push(...expansionFiles);