diff --git a/.gitignore b/.gitignore index 6af83303..89f4370b 100644 --- a/.gitignore +++ b/.gitignore @@ -36,6 +36,7 @@ cursor CLAUDE.local.md .serena/ .claude/settings.local.json +.vibe z*/ diff --git a/tools/cli/installers/lib/ide/_base-ide.js b/tools/cli/installers/lib/ide/_base-ide.js index 4ae11677..50638c33 100644 --- a/tools/cli/installers/lib/ide/_base-ide.js +++ b/tools/cli/installers/lib/ide/_base-ide.js @@ -536,6 +536,11 @@ class BaseIdeSetup { content = content.replaceAll('_bmad', this.bmadFolderName); } + // Replace {bmad-folder} placeholder if present + if (typeof content === 'string' && content.includes('{bmad-folder}')) { + content = content.replaceAll('{bmad-folder}', this.bmadFolderName); + } + // Replace escape sequence _bmad with literal _bmad if (typeof content === 'string' && content.includes('_bmad')) { content = content.replaceAll('_bmad', '_bmad'); diff --git a/tools/cli/installers/lib/ide/manager.js b/tools/cli/installers/lib/ide/manager.js index 7d00588c..e3bbfbc0 100644 --- a/tools/cli/installers/lib/ide/manager.js +++ b/tools/cli/installers/lib/ide/manager.js @@ -61,7 +61,7 @@ class IdeManager { */ loadCustomInstallerFiles() { const ideDir = __dirname; - const customFiles = ['codex.js', 'kilo.js', 'kiro-cli.js']; + const customFiles = ['codex.js', 'kilo.js', 'kiro-cli.js', 'mistral-vibe.js']; for (const file of customFiles) { const filePath = path.join(ideDir, file); diff --git a/tools/cli/installers/lib/ide/mistral-vibe.js b/tools/cli/installers/lib/ide/mistral-vibe.js new file mode 100644 index 00000000..3f116e1f --- /dev/null +++ b/tools/cli/installers/lib/ide/mistral-vibe.js @@ -0,0 +1,372 @@ +const path = require('node:path'); +const fs = require('fs-extra'); +const os = require('node:os'); +const chalk = require('chalk'); +const { BaseIdeSetup } = require('./_base-ide'); +const { WorkflowCommandGenerator } = require('./shared/workflow-command-generator'); +const { AgentCommandGenerator } = require('./shared/agent-command-generator'); +const { getTasksFromBmad } = require('./shared/bmad-artifacts'); + +/** + * Mistral Vibe setup handler (CLI mode) + */ +class MistralVibeSetup extends BaseIdeSetup { + constructor() { + super('mistral-vibe', 'Mistral Vibe CLI', false); + } + + /** + * Setup Mistral Vibe configuration + * @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}...`)); + + // Always use CLI mode + const mode = 'cli'; + + // Get the skills directory + const skillsDir = path.join(projectDir, '.vibe', 'skills'); + await fs.ensureDir(skillsDir); + await this.clearOldBmadFiles(skillsDir); + + // Collect artifacts and write using the same pattern as Codex + const agentGen = new AgentCommandGenerator(this.bmadFolderName); + + // Collect core and BMM agents separately + const { artifacts: coreAgents } = await agentGen.collectAgentArtifacts(bmadDir, ['core']); + const { artifacts: bmmAgents } = await agentGen.collectAgentArtifacts(bmadDir, ['bmm']); + + // Filter bmad-master from BMM agents (it appears in both) + const filteredBmmAgents = bmmAgents.filter((a) => a.name !== 'bmad-master'); + + // Combine: core bmad-master + BMM agents + const filteredAgentArtifacts = [...coreAgents, ...filteredBmmAgents]; + + // Write agent skills with "agent-" or "agent-bmm-" prefix + const agentCount = await this.writeMistralVibeArtifacts(skillsDir, filteredAgentArtifacts, 'agent'); + + // Collect workflows + const workflowGen = new WorkflowCommandGenerator(this.bmadFolderName); + const workflows = await workflowGen.loadWorkflowManifest(bmadDir); + + // Filter to only include BMM workflows (matching existing .vibe/skills/) + const bmmWorkflows = workflows ? workflows.filter((wf) => wf.module === 'bmm') : []; + + // Write workflow skills with "bmm-" prefix + const workflowCount = await this.writeMistralVibeWorkflows(skillsDir, bmmWorkflows); + + // Write other skills (brainstorming, help, party-mode) + const otherCount = await this.writeOtherSkills(skillsDir); + + const totalWritten = agentCount + workflowCount + otherCount; + + console.log(chalk.green(`✓ ${this.name} configured:`)); + console.log(chalk.dim(` - ${totalWritten} skills generated`)); + console.log(chalk.dim(` - Destination: ${skillsDir}`)); + + return { success: true, written: totalWritten, destination: skillsDir }; + } + + /** + * Clear old BMAD files from the destination directory + */ + async clearOldBmadFiles(destDir) { + if (await fs.pathExists(destDir)) { + const existingFiles = await fs.readdir(destDir); + for (const file of existingFiles) { + if (file !== 'README.md') { + const filePath = path.join(destDir, file); + const stat = await fs.lstat(filePath); + if (stat.isDirectory()) { + await fs.remove(filePath); + } + } + } + } + } + + /** + * Write Mistral Vibe agent artifacts + */ + async writeMistralVibeArtifacts(destDir, artifacts, prefix = '') { + let count = 0; + for (const artifact of artifacts) { + const skillName = + prefix === 'agent' + ? (artifact.module === 'bmm' ? `agent-bmm-${artifact.name}` : `agent-${artifact.name}`) + : artifact.name; + + const skillDir = path.join(destDir, skillName); + await fs.ensureDir(skillDir); + + const skillContent = await this.generateAgentSkillContent(artifact, skillName); + await fs.writeFile(path.join(skillDir, 'SKILL.md'), skillContent); + count++; + } + return count; + } + + /** + * Write Mistral Vibe workflow artifacts + */ + async writeMistralVibeWorkflows(destDir, workflows) { + let count = 0; + for (const workflow of workflows) { + const skillName = `bmm-${workflow.name}`; + const skillDir = path.join(destDir, skillName); + await fs.ensureDir(skillDir); + + const skillContent = await this.generateWorkflowSkillContent(workflow, skillName); + await fs.writeFile(path.join(skillDir, 'SKILL.md'), skillContent); + count++; + } + return count; + } + + /** + * Write other skills (brainstorming, help, party-mode) + */ + async writeOtherSkills(destDir) { + const otherSkills = [ + { name: 'brainstorming', description: 'Brainstorm Project Ideas with guided facilitation of a brainstorming coach' }, + { name: 'help', description: 'Execute undefined' }, + { + name: 'party-mode', + description: 'Orchestrates group discussions between all installed BMAD agents, enabling natural multi-agent conversations', + }, + ]; + + let count = 0; + for (const skill of otherSkills) { + const skillDir = path.join(destDir, skill.name); + await fs.ensureDir(skillDir); + + const skillContent = `--- +name: ${skillName} +description: ${skill.description} +license: MIT +compatibility: Mistral Vibe CLI +user-invocable: True +--- + +# ${skill.name.toUpperCase()} + +IT IS CRITICAL THAT YOU FOLLOW THIS COMMAND: LOAD the FULL @.vibe/${this.bmadFolderName}/core/workflows/${skill.name}/workflow.md, READ its entire contents and follow its directions exactly!`; + + await fs.writeFile(path.join(skillDir, 'SKILL.md'), skillContent); + count++; + } + return count; + } + + async generateAgentSkills(skillsDir, bmadDir, options) { + // Get agents from both core and bmm modules + const agentGen = new AgentCommandGenerator(this.bmadFolderName); + const { artifacts: coreAgents } = await agentGen.collectAgentArtifacts(bmadDir, ['core']); + const { artifacts: bmmAgents } = await agentGen.collectAgentArtifacts(bmadDir, ['bmm']); + const allAgents = [...coreAgents, ...bmmAgents]; + + let written = 0; + for (const artifact of allAgents) { + // Use "agent-" prefix for core, "agent-bmm-" for BMM agents to match existing format + const skillName = artifact.module === 'bmm' ? `agent-bmm-${artifact.name}` : `agent-${artifact.name}`; + const skillDir = path.join(skillsDir, skillName); + await fs.ensureDir(skillDir); + + const skillContent = this.addUserInvocableToContent(artifact.content, { + ...artifact, + name: skillName, + }); + const skillPath = path.join(skillDir, 'SKILL.md'); + await fs.writeFile(skillPath, skillContent); + written++; + } + + return written; + } + + async generateWorkflowSkills(skillsDir, bmadDir) { + const workflowGen = new WorkflowCommandGenerator(this.bmadFolderName); + const workflows = await workflowGen.loadWorkflowManifest(bmadDir); + + if (!workflows || workflows.length === 0) { + return 0; + } + + let written = 0; + for (const workflow of workflows) { + // Add module prefix to workflow names (bmm- for BMM workflows) + const skillName = workflow.module === 'bmm' ? `bmm-${workflow.name}` : workflow.name; + const skillDir = path.join(skillsDir, skillName); + await fs.ensureDir(skillDir); + + // Generate workflow content in the same format as existing skills + const workflowContent = await this.generateSimpleWorkflowSkillContent(workflow, bmadDir); + const skillContent = this.addUserInvocableToContent(workflowContent, { + name: skillName, + description: workflow.description || `${workflow.name} workflow`, + }); + + const skillPath = path.join(skillDir, 'SKILL.md'); + await fs.writeFile(skillPath, skillContent); + written++; + } + + return written; + } + + async generateSimpleWorkflowSkillContent(workflow, bmadDir) { + // Generate content in the same format as existing workflow skills + return `IT IS CRITICAL THAT YOU FOLLOW THIS COMMAND: LOAD the FULL @${workflow.path}, READ its entire contents and follow its directions exactly!`; + } + + async generateOtherSkills(skillsDir, bmadDir) { + // Generate additional skills that aren't agents or workflows + // These are core workflows that don't have the bmm- prefix + const otherSkills = [ + { name: 'help', description: 'BMAD help system' }, + { name: 'brainstorming', description: 'Brainstorming workflow' }, + { name: 'party-mode', description: 'Multi-agent collaboration mode' }, + ]; + + let written = 0; + for (const skill of otherSkills) { + const skillDir = path.join(skillsDir, skill.name); + await fs.ensureDir(skillDir); + + // Create basic skill content + const skillContent = `--- +name: ${skill.name} +description: ${skill.description} +license: MIT +compatibility: Mistral Vibe CLI +user-invocable: True +--- + +# ${skill.name.toUpperCase()} Skill + +This skill provides ${skill.description.toLowerCase()} functionality.`; + + const skillPath = path.join(skillDir, 'SKILL.md'); + await fs.writeFile(skillPath, skillContent); + written++; + } + + return written; + } + + async generateAgentSkillContent(artifact, skillName) { + const agentName = artifact.name; + const isBmmAgent = artifact.module === 'bmm'; + const agentPath = isBmmAgent ? `${this.bmadFolderName}/bmm/agents/${agentName}.md` : `${this.bmadFolderName}/core/agents/${agentName}.md`; + + return `--- +name: ${skillName} +description: ${agentName} agent +license: MIT +compatibility: Mistral Vibe CLI +user-invocable: True +--- + +# Agent ${agentName.toUpperCase()} + +You must fully embody this agent's persona and follow all activation instructions exactly as specified. NEVER break character until given an exit command. + + +1. LOAD the FULL agent file from @${agentPath} +2. READ its entire contents - this contains the complete agent persona, menu, and instructions +3. Execute ALL activation steps exactly as written in the agent file +4. Follow the agent's persona and menu system precisely +5. Stay in character throughout the session +`; + } + + async generateWorkflowSkillContent(workflow, skillName) { + // Generate workflow skill content in the exact format + const workflowName = skillName.replace('bmm-', ''); + + // Determine the workflow path based on the workflow name + let workflowPath; + switch (workflowName) { + case 'brainstorming': + case 'party-mode': { + workflowPath = `${this.bmadFolderName}/core/workflows/${workflowName}/workflow.md`; + break; + } + case 'help': { + workflowPath = `${this.bmadFolderName}/core/tasks/help.md`; + break; + } + default: { + // BMM workflows are in various subdirectories + workflowPath = `${this.bmadFolderName}/bmm/workflows/${workflowName}/workflow.md`; + } + } + + return `--- +name: ${skillName} +description: ${this.getWorkflowDescription(workflowName)} +license: MIT +compatibility: Mistral Vibe CLI +user-invocable: True +--- + +# ${workflowName.toUpperCase()} + +IT IS CRITICAL THAT YOU FOLLOW THIS COMMAND: LOAD the FULL @${workflowPath}, READ its entire contents and follow its directions exactly!`; + } + + generateOtherSkillContent(skill) { + // Generate other skill content + const descriptions = { + brainstorming: 'Brainstorm Project Ideas with guided facilitation of a brainstorming coach', + help: 'Execute undefined', + 'party-mode': 'Orchestrates group discussions between all installed BMAD agents, enabling natural multi-agent conversations', + }; + + return `--- +name: ${skill.name} +description: ${descriptions[skill.name] || skill.name} +license: MIT +compatibility: Mistral Vibe CLI +user-invocable: True +--- + +# ${skill.name.toUpperCase()} + +IT IS CRITICAL THAT YOU FOLLOW THIS COMMAND: LOAD the FULL @.vibe/${this.bmadFolderName}/core/workflows/${skill.name}/workflow.md, READ its entire contents and follow its directions exactly!`; + } + + getWorkflowDescription(workflowName) { + const descriptions = { + 'check-implementation-readiness': 'Gate check before implementation', + 'code-review': 'Validate implementation quality', + 'correct-course': 'Handle significant mid-sprint changes', + 'create-architecture': 'Make technical decisions explicit', + 'create-epics-and-stories': 'Break requirements into implementable work', + 'create-excalidraw-dataflow': 'Create data flow diagrams', + 'create-excalidraw-diagram': 'Create diagrams', + 'create-excalidraw-flowchart': 'Create flowcharts', + 'create-excalidraw-wireframe': 'Create wireframes', + 'create-prd': 'Define requirements (FRs/NFRs)', + 'create-product-brief': 'Capture strategic vision', + 'create-story': 'Prepare next story for implementation', + 'create-ux-design': 'Design user experience (when UX matters)', + 'dev-story': 'Implement the story', + 'document-project': 'Document the project', + 'qa-automate': 'Generate tests for existing features', + 'quick-dev': 'Implement from spec or direct instructions', + 'quick-spec': 'Define an ad-hoc change', + research: 'Validate market, technical, or domain assumptions', + retrospective: 'Review after epic completion', + 'sprint-planning': 'Initialize tracking (once per project)', + 'sprint-status': 'Track sprint status', + }; + return descriptions[workflowName] || workflowName; + } +} + +module.exports = { MistralVibeSetup }; diff --git a/tools/cli/installers/lib/ide/platform-codes.yaml b/tools/cli/installers/lib/ide/platform-codes.yaml index 2ca32aed..3f94eaca 100644 --- a/tools/cli/installers/lib/ide/platform-codes.yaml +++ b/tools/cli/installers/lib/ide/platform-codes.yaml @@ -84,6 +84,13 @@ platforms: target_dir: .gemini/commands template_type: gemini + mistral-vibe: + name: "Mistral Vibe CLI" + preferred: false + category: cli + description: "Mistral's vibe command-line interface" + # No installer config - uses custom mistral-vibe.js + github-copilot: name: "GitHub Copilot" preferred: false