Compare commits
13 Commits
739e940c58
...
fa1b7474c7
| Author | SHA1 | Date |
|---|---|---|
|
|
fa1b7474c7 | |
|
|
677c000820 | |
|
|
3ac539b61f | |
|
|
331a67eeb3 | |
|
|
fbdb91b991 | |
|
|
54e6745a55 | |
|
|
f793cf8fcd | |
|
|
9223e2be21 | |
|
|
2cac74cfb5 | |
|
|
ddd0e8fbf2 | |
|
|
9e70b1d41c | |
|
|
9a3a46314e | |
|
|
a4c394fc78 |
|
|
@ -70,3 +70,4 @@ z*/
|
||||||
.codex
|
.codex
|
||||||
.github/chatmodes
|
.github/chatmodes
|
||||||
.agent
|
.agent
|
||||||
|
.agentvibes/
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
{
|
{
|
||||||
"name": "bmad-method",
|
"name": "bmad-method",
|
||||||
"version": "6.0.0-alpha.11",
|
"version": "6.0.0-alpha.12",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "bmad-method",
|
"name": "bmad-method",
|
||||||
"version": "6.0.0-alpha.11",
|
"version": "6.0.0-alpha.12",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@kayvan/markdown-tree-parser": "^1.6.1",
|
"@kayvan/markdown-tree-parser": "^1.6.1",
|
||||||
|
|
@ -1023,9 +1023,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": {
|
"node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": {
|
||||||
"version": "3.14.1",
|
"version": "3.14.2",
|
||||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
|
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz",
|
||||||
"integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
|
"integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
@ -1329,9 +1329,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@jest/reporters/node_modules/glob": {
|
"node_modules/@jest/reporters/node_modules/glob": {
|
||||||
"version": "10.4.5",
|
"version": "10.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
|
"resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz",
|
||||||
"integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==",
|
"integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
@ -2618,9 +2618,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/c8/node_modules/glob": {
|
"node_modules/c8/node_modules/glob": {
|
||||||
"version": "10.4.5",
|
"version": "10.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
|
"resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz",
|
||||||
"integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==",
|
"integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
@ -4103,14 +4103,14 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/glob": {
|
"node_modules/glob": {
|
||||||
"version": "11.0.3",
|
"version": "11.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/glob/-/glob-11.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/glob/-/glob-11.1.0.tgz",
|
||||||
"integrity": "sha512-2Nim7dha1KVkaiF4q6Dj+ngPPMdfvLJEOpZk/jKiUAkqKebpGAWQXAq9z1xu9HKu5lWfqw/FASuccEjyznjPaA==",
|
"integrity": "sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw==",
|
||||||
"license": "ISC",
|
"license": "BlueOak-1.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"foreground-child": "^3.3.1",
|
"foreground-child": "^3.3.1",
|
||||||
"jackspeak": "^4.1.1",
|
"jackspeak": "^4.1.1",
|
||||||
"minimatch": "^10.0.3",
|
"minimatch": "^10.1.1",
|
||||||
"minipass": "^7.1.2",
|
"minipass": "^7.1.2",
|
||||||
"package-json-from-dist": "^1.0.0",
|
"package-json-from-dist": "^1.0.0",
|
||||||
"path-scurry": "^2.0.0"
|
"path-scurry": "^2.0.0"
|
||||||
|
|
@ -4139,10 +4139,10 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/glob/node_modules/minimatch": {
|
"node_modules/glob/node_modules/minimatch": {
|
||||||
"version": "10.0.3",
|
"version": "10.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.1.1.tgz",
|
||||||
"integrity": "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==",
|
"integrity": "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==",
|
||||||
"license": "ISC",
|
"license": "BlueOak-1.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@isaacs/brace-expansion": "^5.0.0"
|
"@isaacs/brace-expansion": "^5.0.0"
|
||||||
},
|
},
|
||||||
|
|
@ -4808,9 +4808,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/jest-config/node_modules/glob": {
|
"node_modules/jest-config/node_modules/glob": {
|
||||||
"version": "10.4.5",
|
"version": "10.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
|
"resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz",
|
||||||
"integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==",
|
"integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
@ -5181,9 +5181,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/jest-runtime/node_modules/glob": {
|
"node_modules/jest-runtime/node_modules/glob": {
|
||||||
"version": "10.4.5",
|
"version": "10.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
|
"resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz",
|
||||||
"integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==",
|
"integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
@ -5413,9 +5413,9 @@
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/js-yaml": {
|
"node_modules/js-yaml": {
|
||||||
"version": "4.1.0",
|
"version": "4.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
|
||||||
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
|
"integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"argparse": "^2.0.1"
|
"argparse": "^2.0.1"
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
<critical>The workflow execution engine is governed by: {project_root}/{bmad_folder}/core/tasks/workflow.xml</critical>
|
<critical>The workflow execution engine is governed by: {project_root}/{bmad_folder}/core/tasks/workflow.xml</critical>
|
||||||
<critical>This workflow orchestrates group discussions between all installed BMAD agents</critical>
|
<critical>This workflow orchestrates group discussions between all installed BMAD agents</critical>
|
||||||
|
|
||||||
<!-- TTS_INJECTION:party-mode -->
|
<!-- TTS_INJECTION:party-mode -->
|
||||||
|
|
||||||
<workflow>
|
<workflow>
|
||||||
|
|
@ -97,14 +98,7 @@
|
||||||
<substep n="3d" goal="Format and Present Responses">
|
<substep n="3d" goal="Format and Present Responses">
|
||||||
<action>For each agent response, output text THEN trigger their voice:</action>
|
<action>For each agent response, output text THEN trigger their voice:</action>
|
||||||
|
|
||||||
<procedure>
|
<!-- TTS_INJECTION:party-mode -->
|
||||||
1. Output the agent's text in format: [Icon Emoji] [Agent Name]: [dialogue]
|
|
||||||
2. If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
|
||||||
- Use Bash tool: `.claude/hooks/bmad-speak.sh "[Agent Name]" "[dialogue]"`
|
|
||||||
- This speaks the dialogue with the agent's unique voice
|
|
||||||
- Run in background (&) to not block next agent
|
|
||||||
3. Repeat for each agent in the response
|
|
||||||
</procedure>
|
|
||||||
|
|
||||||
<format>
|
<format>
|
||||||
[Icon Emoji] [Agent Name]: [Their response in their voice/style]
|
[Icon Emoji] [Agent Name]: [Their response in their voice/style]
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 93 KiB After Width: | Height: | Size: 88 KiB |
|
|
@ -3,6 +3,8 @@
|
||||||
<critical>You MUST have already loaded and processed: {installed_path}/workflow.yaml</critical>
|
<critical>You MUST have already loaded and processed: {installed_path}/workflow.yaml</critical>
|
||||||
<critical>Communicate all responses in {communication_language} and language MUST be tailored to {user_skill_level}</critical>
|
<critical>Communicate all responses in {communication_language} and language MUST be tailored to {user_skill_level}</critical>
|
||||||
<critical>Generate all documents in {document_output_language}</critical>
|
<critical>Generate all documents in {document_output_language}</critical>
|
||||||
|
<critical>ALWAYS reload {{sprint_status}} (docs/sprint-artifacts/sprint-status.yaml by default) from disk immediately before reading or
|
||||||
|
updating statuses. Multiple workflows change this file between runs; never rely on a previously loaded copy or cached memory.</critical>
|
||||||
|
|
||||||
<critical>🔥 YOU ARE AN ADVERSARIAL CODE REVIEWER - Find what's wrong or missing! 🔥</critical>
|
<critical>🔥 YOU ARE AN ADVERSARIAL CODE REVIEWER - Find what's wrong or missing! 🔥</critical>
|
||||||
<critical>Your purpose: Validate story file claims against actual implementation</critical>
|
<critical>Your purpose: Validate story file claims against actual implementation</critical>
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,8 @@
|
||||||
<critical>The workflow execution engine is governed by: {project-root}/{bmad_folder}/core/tasks/workflow.xml</critical>
|
<critical>The workflow execution engine is governed by: {project-root}/{bmad_folder}/core/tasks/workflow.xml</critical>
|
||||||
<critical>You MUST have already loaded and processed: {installed_path}/workflow.yaml</critical>
|
<critical>You MUST have already loaded and processed: {installed_path}/workflow.yaml</critical>
|
||||||
<critical>Communicate all responses in {communication_language} and generate all documents in {document_output_language}</critical>
|
<critical>Communicate all responses in {communication_language} and generate all documents in {document_output_language}</critical>
|
||||||
|
<critical>ALWAYS reload {{sprint_status}} (docs/sprint-artifacts/sprint-status.yaml by default) from disk immediately before reading or
|
||||||
|
updating statuses. Multiple workflows change this file between runs; never rely on a previously loaded copy or cached memory.</critical>
|
||||||
|
|
||||||
<critical>🔥 CRITICAL MISSION: You are creating the ULTIMATE story context engine that prevents LLM developer mistakes, omissions or
|
<critical>🔥 CRITICAL MISSION: You are creating the ULTIMATE story context engine that prevents LLM developer mistakes, omissions or
|
||||||
disasters! 🔥</critical>
|
disasters! 🔥</critical>
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,8 @@
|
||||||
<critical>You MUST have already loaded and processed: {installed_path}/workflow.yaml</critical>
|
<critical>You MUST have already loaded and processed: {installed_path}/workflow.yaml</critical>
|
||||||
<critical>Communicate all responses in {communication_language} and language MUST be tailored to {user_skill_level}</critical>
|
<critical>Communicate all responses in {communication_language} and language MUST be tailored to {user_skill_level}</critical>
|
||||||
<critical>Generate all documents in {document_output_language}</critical>
|
<critical>Generate all documents in {document_output_language}</critical>
|
||||||
|
<critical>ALWAYS reload {{sprint_status}} (docs/sprint-artifacts/sprint-status.yaml by default) from disk immediately before reading or
|
||||||
|
updating statuses. Multiple workflows change this file between runs; never rely on a previously loaded copy or cached memory.</critical>
|
||||||
<critical>Only modify the story file in these areas: Tasks/Subtasks checkboxes, Dev Agent Record (Debug Log, Completion Notes), File List,
|
<critical>Only modify the story file in these areas: Tasks/Subtasks checkboxes, Dev Agent Record (Debug Log, Completion Notes), File List,
|
||||||
Change Log, and Status</critical>
|
Change Log, and Status</critical>
|
||||||
<critical>Execute ALL steps in exact order; do NOT skip steps</critical>
|
<critical>Execute ALL steps in exact order; do NOT skip steps</critical>
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ PARTY MODE PROTOCOL:
|
||||||
- Create natural back-and-forth with user actively participating
|
- Create natural back-and-forth with user actively participating
|
||||||
- Show disagreements, diverse perspectives, authentic team dynamics
|
- Show disagreements, diverse perspectives, authentic team dynamics
|
||||||
</critical>
|
</critical>
|
||||||
|
<critical>ALWAYS reload {{sprint_status_file}} (docs/sprint-artifacts/sprint-status.yaml by default) from disk immediately before reading or updating statuses. Multiple workflows change this file between runs; never rely on a previously loaded copy or cached memory.</critical>
|
||||||
|
|
||||||
<workflow>
|
<workflow>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,141 @@
|
||||||
|
const chalk = require('chalk');
|
||||||
|
const nodePath = require('node:path');
|
||||||
|
const { Installer } = require('../installers/lib/core/installer');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
command: 'cleanup',
|
||||||
|
description: 'Clean up obsolete files from BMAD installation',
|
||||||
|
options: [
|
||||||
|
['-d, --dry-run', 'Show what would be deleted without actually deleting'],
|
||||||
|
['-a, --auto-delete', 'Automatically delete non-retained files without prompts'],
|
||||||
|
['-l, --list-retained', 'List currently retained files'],
|
||||||
|
['-c, --clear-retained', 'Clear retained files list'],
|
||||||
|
],
|
||||||
|
action: async (options) => {
|
||||||
|
try {
|
||||||
|
// Create installer and let it find the BMAD directory
|
||||||
|
const installer = new Installer();
|
||||||
|
const bmadDir = await installer.findBmadDir(process.cwd());
|
||||||
|
|
||||||
|
if (!bmadDir) {
|
||||||
|
console.error(chalk.red('❌ BMAD installation not found'));
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const retentionPath = nodePath.join(bmadDir, '_cfg', 'user-retained-files.yaml');
|
||||||
|
|
||||||
|
// Handle list-retained option
|
||||||
|
if (options.listRetained) {
|
||||||
|
const fs = require('fs-extra');
|
||||||
|
const yaml = require('js-yaml');
|
||||||
|
|
||||||
|
if (await fs.pathExists(retentionPath)) {
|
||||||
|
const retentionContent = await fs.readFile(retentionPath, 'utf8');
|
||||||
|
const retentionData = yaml.load(retentionContent) || { retainedFiles: [] };
|
||||||
|
|
||||||
|
if (retentionData.retainedFiles.length > 0) {
|
||||||
|
console.log(chalk.cyan('\n📋 Retained Files:\n'));
|
||||||
|
for (const file of retentionData.retainedFiles) {
|
||||||
|
console.log(chalk.dim(` - ${file}`));
|
||||||
|
}
|
||||||
|
console.log();
|
||||||
|
} else {
|
||||||
|
console.log(chalk.yellow('\n✨ No retained files found\n'));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log(chalk.yellow('\n✨ No retained files found\n'));
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle clear-retained option
|
||||||
|
if (options.clearRetained) {
|
||||||
|
const fs = require('fs-extra');
|
||||||
|
|
||||||
|
if (await fs.pathExists(retentionPath)) {
|
||||||
|
await fs.remove(retentionPath);
|
||||||
|
console.log(chalk.green('\n✅ Cleared retained files list\n'));
|
||||||
|
} else {
|
||||||
|
console.log(chalk.yellow('\n✨ No retained files list to clear\n'));
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle cleanup operations
|
||||||
|
if (options.dryRun) {
|
||||||
|
console.log(chalk.cyan('\n🔍 Legacy File Scan (Dry Run)\n'));
|
||||||
|
|
||||||
|
const legacyFiles = await installer.scanForLegacyFiles(bmadDir);
|
||||||
|
const allLegacyFiles = [
|
||||||
|
...legacyFiles.backup,
|
||||||
|
...legacyFiles.documentation,
|
||||||
|
...legacyFiles.deprecated_task,
|
||||||
|
...legacyFiles.unknown,
|
||||||
|
];
|
||||||
|
|
||||||
|
if (allLegacyFiles.length === 0) {
|
||||||
|
console.log(chalk.green('✨ No legacy files found\n'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Group files by category
|
||||||
|
const categories = [];
|
||||||
|
if (legacyFiles.backup.length > 0) {
|
||||||
|
categories.push({ name: 'Backup Files (.bak)', files: legacyFiles.backup });
|
||||||
|
}
|
||||||
|
if (legacyFiles.documentation.length > 0) {
|
||||||
|
categories.push({ name: 'Documentation', files: legacyFiles.documentation });
|
||||||
|
}
|
||||||
|
if (legacyFiles.deprecated_task.length > 0) {
|
||||||
|
categories.push({ name: 'Deprecated Task Files', files: legacyFiles.deprecated_task });
|
||||||
|
}
|
||||||
|
if (legacyFiles.unknown.length > 0) {
|
||||||
|
categories.push({ name: 'Unknown Files', files: legacyFiles.unknown });
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const category of categories) {
|
||||||
|
console.log(chalk.yellow(`${category.name}:`));
|
||||||
|
for (const file of category.files) {
|
||||||
|
const size = (file.size / 1024).toFixed(1);
|
||||||
|
const date = file.mtime.toLocaleDateString();
|
||||||
|
let line = ` - ${file.relativePath} (${size}KB, ${date})`;
|
||||||
|
if (file.suggestedAlternative) {
|
||||||
|
line += chalk.dim(` → ${file.suggestedAlternative}`);
|
||||||
|
}
|
||||||
|
console.log(chalk.dim(line));
|
||||||
|
}
|
||||||
|
console.log();
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(chalk.cyan(`Found ${allLegacyFiles.length} legacy file(s) that could be cleaned up.\n`));
|
||||||
|
console.log(chalk.dim('Run "bmad cleanup" to actually delete these files.\n'));
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perform actual cleanup
|
||||||
|
console.log(chalk.cyan('\n🧹 Cleaning up legacy files...\n'));
|
||||||
|
|
||||||
|
const result = await installer.performCleanup(bmadDir, options.autoDelete);
|
||||||
|
|
||||||
|
if (result.message) {
|
||||||
|
console.log(chalk.dim(result.message));
|
||||||
|
} else {
|
||||||
|
if (result.deleted > 0) {
|
||||||
|
console.log(chalk.green(`✅ Deleted ${result.deleted} legacy file(s)`));
|
||||||
|
}
|
||||||
|
if (result.retained > 0) {
|
||||||
|
console.log(chalk.yellow(`⏭️ Retained ${result.retained} file(s)`));
|
||||||
|
console.log(chalk.dim('Run "bmad cleanup --list-retained" to see retained files\n'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log();
|
||||||
|
} catch (error) {
|
||||||
|
console.error(chalk.red(`❌ Error: ${error.message}`));
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
@ -9,8 +9,8 @@ const ui = new UI();
|
||||||
module.exports = {
|
module.exports = {
|
||||||
command: 'install',
|
command: 'install',
|
||||||
description: 'Install BMAD Core agents and tools',
|
description: 'Install BMAD Core agents and tools',
|
||||||
options: [],
|
options: [['--skip-cleanup', 'Skip automatic cleanup of legacy files']],
|
||||||
action: async () => {
|
action: async (options) => {
|
||||||
try {
|
try {
|
||||||
const config = await ui.promptInstall();
|
const config = await ui.promptInstall();
|
||||||
|
|
||||||
|
|
@ -44,6 +44,11 @@ module.exports = {
|
||||||
config._requestedReinstall = true;
|
config._requestedReinstall = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add skip cleanup flag if option provided
|
||||||
|
if (options && options.skipCleanup) {
|
||||||
|
config.skipCleanup = true;
|
||||||
|
}
|
||||||
|
|
||||||
// Regular install/update flow
|
// Regular install/update flow
|
||||||
const result = await installer.install(config);
|
const result = await installer.install(config);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1018,6 +1018,23 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
||||||
customFiles: customFiles.length > 0 ? customFiles : undefined,
|
customFiles: customFiles.length > 0 ? customFiles : undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Offer cleanup for legacy files (only for updates, not fresh installs, and only if not skipped)
|
||||||
|
if (!config.skipCleanup && config._isUpdate) {
|
||||||
|
try {
|
||||||
|
const cleanupResult = await this.performCleanup(bmadDir, false);
|
||||||
|
if (cleanupResult.deleted > 0) {
|
||||||
|
console.log(chalk.green(`\n✓ Cleaned up ${cleanupResult.deleted} legacy file${cleanupResult.deleted > 1 ? 's' : ''}`));
|
||||||
|
}
|
||||||
|
if (cleanupResult.retained > 0) {
|
||||||
|
console.log(chalk.dim(`Run 'bmad cleanup' anytime to manage retained files`));
|
||||||
|
}
|
||||||
|
} catch (cleanupError) {
|
||||||
|
// Don't fail the installation for cleanup errors
|
||||||
|
console.log(chalk.yellow(`\n⚠️ Cleanup warning: ${cleanupError.message}`));
|
||||||
|
console.log(chalk.dim('Run "bmad cleanup" to manually clean up legacy files'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
path: bmadDir,
|
path: bmadDir,
|
||||||
|
|
@ -1939,7 +1956,7 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
||||||
|
|
||||||
if (existingBmadFolderName === newBmadFolderName) {
|
if (existingBmadFolderName === newBmadFolderName) {
|
||||||
// Normal quick update - start the spinner
|
// Normal quick update - start the spinner
|
||||||
spinner.start('Updating BMAD installation...');
|
console.log(chalk.cyan('Updating BMAD installation...'));
|
||||||
} else {
|
} else {
|
||||||
// Folder name has changed - stop spinner and let install() handle it
|
// Folder name has changed - stop spinner and let install() handle it
|
||||||
spinner.stop();
|
spinner.stop();
|
||||||
|
|
@ -2578,6 +2595,362 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scan for legacy/obsolete files in BMAD installation
|
||||||
|
* @param {string} bmadDir - BMAD installation directory
|
||||||
|
* @returns {Object} Categorized files for cleanup
|
||||||
|
*/
|
||||||
|
async scanForLegacyFiles(bmadDir) {
|
||||||
|
const legacyFiles = {
|
||||||
|
backup: [],
|
||||||
|
documentation: [],
|
||||||
|
deprecated_task: [],
|
||||||
|
unknown: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Load files manifest to understand what should exist
|
||||||
|
const manifestPath = path.join(bmadDir, 'files-manifest.csv');
|
||||||
|
const manifestFiles = new Set();
|
||||||
|
|
||||||
|
if (await fs.pathExists(manifestPath)) {
|
||||||
|
const manifestContent = await fs.readFile(manifestPath, 'utf8');
|
||||||
|
const lines = manifestContent.split('\n').slice(1); // Skip header
|
||||||
|
for (const line of lines) {
|
||||||
|
if (line.trim()) {
|
||||||
|
const relativePath = line.split(',')[0];
|
||||||
|
if (relativePath) {
|
||||||
|
manifestFiles.add(relativePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scan all files recursively
|
||||||
|
const allFiles = await this.getAllFiles(bmadDir);
|
||||||
|
|
||||||
|
for (const filePath of allFiles) {
|
||||||
|
const relativePath = path.relative(bmadDir, filePath);
|
||||||
|
|
||||||
|
// Skip expected files
|
||||||
|
if (this.isExpectedFile(relativePath, manifestFiles)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Categorize legacy files
|
||||||
|
if (relativePath.endsWith('.bak')) {
|
||||||
|
legacyFiles.backup.push({
|
||||||
|
path: filePath,
|
||||||
|
relativePath: relativePath,
|
||||||
|
size: (await fs.stat(filePath)).size,
|
||||||
|
mtime: (await fs.stat(filePath)).mtime,
|
||||||
|
});
|
||||||
|
} else if (this.isDocumentationFile(relativePath)) {
|
||||||
|
legacyFiles.documentation.push({
|
||||||
|
path: filePath,
|
||||||
|
relativePath: relativePath,
|
||||||
|
size: (await fs.stat(filePath)).size,
|
||||||
|
mtime: (await fs.stat(filePath)).mtime,
|
||||||
|
});
|
||||||
|
} else if (this.isDeprecatedTaskFile(relativePath)) {
|
||||||
|
const suggestedAlternative = this.suggestAlternative(relativePath);
|
||||||
|
legacyFiles.deprecated_task.push({
|
||||||
|
path: filePath,
|
||||||
|
relativePath: relativePath,
|
||||||
|
size: (await fs.stat(filePath)).size,
|
||||||
|
mtime: (await fs.stat(filePath)).mtime,
|
||||||
|
suggestedAlternative,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
legacyFiles.unknown.push({
|
||||||
|
path: filePath,
|
||||||
|
relativePath: relativePath,
|
||||||
|
size: (await fs.stat(filePath)).size,
|
||||||
|
mtime: (await fs.stat(filePath)).mtime,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(`Warning: Could not scan for legacy files: ${error.message}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return legacyFiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all files in directory recursively
|
||||||
|
* @param {string} dir - Directory to scan
|
||||||
|
* @returns {Array} Array of file paths
|
||||||
|
*/
|
||||||
|
async getAllFiles(dir) {
|
||||||
|
const files = [];
|
||||||
|
|
||||||
|
async function scan(currentDir) {
|
||||||
|
const entries = await fs.readdir(currentDir);
|
||||||
|
|
||||||
|
for (const entry of entries) {
|
||||||
|
const fullPath = path.join(currentDir, entry);
|
||||||
|
const stat = await fs.stat(fullPath);
|
||||||
|
|
||||||
|
if (stat.isDirectory()) {
|
||||||
|
// Skip certain directories
|
||||||
|
if (!['node_modules', '.git', 'dist', 'build'].includes(entry)) {
|
||||||
|
await scan(fullPath);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
files.push(fullPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await scan(dir);
|
||||||
|
return files;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if file is expected in installation
|
||||||
|
* @param {string} relativePath - Relative path from BMAD dir
|
||||||
|
* @param {Set} manifestFiles - Files from manifest
|
||||||
|
* @returns {boolean} True if expected file
|
||||||
|
*/
|
||||||
|
isExpectedFile(relativePath, manifestFiles) {
|
||||||
|
// Core files in manifest
|
||||||
|
if (manifestFiles.has(relativePath)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configuration files
|
||||||
|
if (relativePath.startsWith('_cfg/') || relativePath === 'config.yaml') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Custom files
|
||||||
|
if (relativePath.startsWith('custom/') || relativePath === 'manifest.yaml') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generated files
|
||||||
|
if (relativePath === 'manifest.csv' || relativePath === 'files-manifest.csv') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// IDE-specific files
|
||||||
|
const ides = ['vscode', 'cursor', 'windsurf', 'claude-code', 'github-copilot', 'zsh', 'bash', 'fish'];
|
||||||
|
if (ides.some((ide) => relativePath.includes(ide))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// BMAD MODULE STRUCTURES - recognize valid module content
|
||||||
|
const modulePrefixes = ['bmb/', 'bmm/', 'cis/', 'core/', 'bmgd/'];
|
||||||
|
const validExtensions = ['.yaml', '.yml', '.json', '.csv', '.md', '.xml', '.svg', '.png', '.jpg', '.gif', '.excalidraw', '.js'];
|
||||||
|
|
||||||
|
// Check if this file is in a recognized module directory
|
||||||
|
for (const modulePrefix of modulePrefixes) {
|
||||||
|
if (relativePath.startsWith(modulePrefix)) {
|
||||||
|
// Check if it has a valid extension
|
||||||
|
const hasValidExtension = validExtensions.some((ext) => relativePath.endsWith(ext));
|
||||||
|
if (hasValidExtension) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Special case for core module resources
|
||||||
|
if (relativePath.startsWith('core/resources/')) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Special case for docs directory
|
||||||
|
if (relativePath.startsWith('docs/')) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if file is documentation
|
||||||
|
* @param {string} relativePath - Relative path
|
||||||
|
* @returns {boolean} True if documentation
|
||||||
|
*/
|
||||||
|
isDocumentationFile(relativePath) {
|
||||||
|
const docExtensions = ['.md', '.txt', '.pdf'];
|
||||||
|
const docPatterns = ['docs/', 'README', 'CHANGELOG', 'LICENSE'];
|
||||||
|
|
||||||
|
return docExtensions.some((ext) => relativePath.endsWith(ext)) || docPatterns.some((pattern) => relativePath.includes(pattern));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if file is deprecated task file
|
||||||
|
* @param {string} relativePath - Relative path
|
||||||
|
* @returns {boolean} True if deprecated
|
||||||
|
*/
|
||||||
|
isDeprecatedTaskFile(relativePath) {
|
||||||
|
// Known deprecated files
|
||||||
|
const deprecatedFiles = ['adv-elicit-methods.csv', 'game-resources.json', 'ux-workflow.json'];
|
||||||
|
|
||||||
|
return deprecatedFiles.some((dep) => relativePath.includes(dep));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Suggest alternative for deprecated file
|
||||||
|
* @param {string} relativePath - Deprecated file path
|
||||||
|
* @returns {string} Suggested alternative
|
||||||
|
*/
|
||||||
|
suggestAlternative(relativePath) {
|
||||||
|
const alternatives = {
|
||||||
|
'adv-elicit-methods.csv': 'Use the new structured workflows in src/modules/',
|
||||||
|
'game-resources.json': 'Resources are now integrated into modules',
|
||||||
|
'ux-workflow.json': 'UX workflows are now in src/modules/bmm/workflows/',
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const [deprecated, alternative] of Object.entries(alternatives)) {
|
||||||
|
if (relativePath.includes(deprecated)) {
|
||||||
|
return alternative;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'Check src/modules/ for new alternatives';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform interactive cleanup of legacy files
|
||||||
|
* @param {string} bmadDir - BMAD installation directory
|
||||||
|
* @param {boolean} skipInteractive - Skip interactive prompts
|
||||||
|
* @returns {Object} Cleanup results
|
||||||
|
*/
|
||||||
|
async performCleanup(bmadDir, skipInteractive = false) {
|
||||||
|
const inquirer = require('inquirer');
|
||||||
|
const yaml = require('js-yaml');
|
||||||
|
|
||||||
|
// Load user retention preferences
|
||||||
|
const retentionPath = path.join(bmadDir, '_cfg', 'user-retained-files.yaml');
|
||||||
|
let retentionData = { retainedFiles: [], history: [] };
|
||||||
|
|
||||||
|
if (await fs.pathExists(retentionPath)) {
|
||||||
|
const retentionContent = await fs.readFile(retentionPath, 'utf8');
|
||||||
|
retentionData = yaml.load(retentionContent) || retentionData;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scan for legacy files
|
||||||
|
const legacyFiles = await this.scanForLegacyFiles(bmadDir);
|
||||||
|
const allLegacyFiles = [...legacyFiles.backup, ...legacyFiles.documentation, ...legacyFiles.deprecated_task, ...legacyFiles.unknown];
|
||||||
|
|
||||||
|
if (allLegacyFiles.length === 0) {
|
||||||
|
return { deleted: 0, retained: 0, message: 'No legacy files found' };
|
||||||
|
}
|
||||||
|
|
||||||
|
let deletedCount = 0;
|
||||||
|
let retainedCount = 0;
|
||||||
|
const filesToDelete = [];
|
||||||
|
|
||||||
|
if (skipInteractive) {
|
||||||
|
// Auto-delete all non-retained files
|
||||||
|
for (const file of allLegacyFiles) {
|
||||||
|
if (!retentionData.retainedFiles.includes(file.relativePath)) {
|
||||||
|
filesToDelete.push(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Interactive cleanup
|
||||||
|
console.log(chalk.cyan('\n🧹 Legacy File Cleanup\n'));
|
||||||
|
console.log(chalk.dim('The following obsolete files were found:\n'));
|
||||||
|
|
||||||
|
// Group files by category
|
||||||
|
const categories = [];
|
||||||
|
if (legacyFiles.backup.length > 0) {
|
||||||
|
categories.push({ name: 'Backup Files (.bak)', files: legacyFiles.backup });
|
||||||
|
}
|
||||||
|
if (legacyFiles.documentation.length > 0) {
|
||||||
|
categories.push({ name: 'Documentation', files: legacyFiles.documentation });
|
||||||
|
}
|
||||||
|
if (legacyFiles.deprecated_task.length > 0) {
|
||||||
|
categories.push({ name: 'Deprecated Task Files', files: legacyFiles.deprecated_task });
|
||||||
|
}
|
||||||
|
if (legacyFiles.unknown.length > 0) {
|
||||||
|
categories.push({ name: 'Unknown Files', files: legacyFiles.unknown });
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const category of categories) {
|
||||||
|
console.log(chalk.yellow(`${category.name}:`));
|
||||||
|
for (const file of category.files) {
|
||||||
|
const size = (file.size / 1024).toFixed(1);
|
||||||
|
const date = file.mtime.toLocaleDateString();
|
||||||
|
let line = ` - ${file.relativePath} (${size}KB, ${date})`;
|
||||||
|
if (file.suggestedAlternative) {
|
||||||
|
line += chalk.dim(` → ${file.suggestedAlternative}`);
|
||||||
|
}
|
||||||
|
console.log(chalk.dim(line));
|
||||||
|
}
|
||||||
|
console.log();
|
||||||
|
}
|
||||||
|
|
||||||
|
const prompt = await inquirer.prompt([
|
||||||
|
{
|
||||||
|
type: 'confirm',
|
||||||
|
name: 'proceed',
|
||||||
|
message: 'Would you like to review these files for cleanup?',
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (!prompt.proceed) {
|
||||||
|
return { deleted: 0, retained: allLegacyFiles.length, message: 'Cleanup cancelled by user' };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show selection interface
|
||||||
|
const selectionPrompt = await inquirer.prompt([
|
||||||
|
{
|
||||||
|
type: 'checkbox',
|
||||||
|
name: 'filesToDelete',
|
||||||
|
message: 'Select files to delete (use SPACEBAR to select, ENTER to continue):',
|
||||||
|
choices: allLegacyFiles.map((file) => {
|
||||||
|
const isRetained = retentionData.retainedFiles.includes(file.relativePath);
|
||||||
|
const description = `${file.relativePath} (${(file.size / 1024).toFixed(1)}KB)`;
|
||||||
|
return {
|
||||||
|
name: description,
|
||||||
|
value: file,
|
||||||
|
checked: !isRetained && !file.relativePath.includes('.bak'),
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
pageSize: Math.min(allLegacyFiles.length, 15),
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
filesToDelete.push(...selectionPrompt.filesToDelete);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete selected files
|
||||||
|
for (const file of filesToDelete) {
|
||||||
|
try {
|
||||||
|
await fs.remove(file.path);
|
||||||
|
deletedCount++;
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(`Warning: Could not delete ${file.relativePath}: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Count retained files
|
||||||
|
retainedCount = allLegacyFiles.length - deletedCount;
|
||||||
|
|
||||||
|
// Update retention data
|
||||||
|
const newlyRetained = allLegacyFiles.filter((f) => !filesToDelete.includes(f)).map((f) => f.relativePath);
|
||||||
|
|
||||||
|
retentionData.retainedFiles = [...new Set([...retentionData.retainedFiles, ...newlyRetained])];
|
||||||
|
retentionData.history.push({
|
||||||
|
date: new Date().toISOString(),
|
||||||
|
deleted: deletedCount,
|
||||||
|
retained: retainedCount,
|
||||||
|
files: filesToDelete.map((f) => f.relativePath),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Save retention data
|
||||||
|
await fs.ensureDir(path.dirname(retentionPath));
|
||||||
|
await fs.writeFile(retentionPath, yaml.dump(retentionData), 'utf8');
|
||||||
|
|
||||||
|
return { deleted: deletedCount, retained: retainedCount };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { Installer };
|
module.exports = { Installer };
|
||||||
|
|
|
||||||
|
|
@ -6,13 +6,13 @@ const { AgentCommandGenerator } = require('./shared/agent-command-generator');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* GitHub Copilot setup handler
|
* GitHub Copilot setup handler
|
||||||
* Creates chat modes in .github/chatmodes/ and configures VS Code settings
|
* Creates agents in .github/agents/ and configures VS Code settings
|
||||||
*/
|
*/
|
||||||
class GitHubCopilotSetup extends BaseIdeSetup {
|
class GitHubCopilotSetup extends BaseIdeSetup {
|
||||||
constructor() {
|
constructor() {
|
||||||
super('github-copilot', 'GitHub Copilot', true); // preferred IDE
|
super('github-copilot', 'GitHub Copilot', true); // preferred IDE
|
||||||
this.configDir = '.github';
|
this.configDir = '.github';
|
||||||
this.chatmodesDir = 'chatmodes';
|
this.agentsDir = 'agents';
|
||||||
this.vscodeDir = '.vscode';
|
this.vscodeDir = '.vscode';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -50,7 +50,8 @@ class GitHubCopilotSetup extends BaseIdeSetup {
|
||||||
message: 'Maximum requests per session (1-50)?',
|
message: 'Maximum requests per session (1-50)?',
|
||||||
default: '15',
|
default: '15',
|
||||||
validate: (input) => {
|
validate: (input) => {
|
||||||
const num = parseInt(input);
|
const num = parseInt(input, 10);
|
||||||
|
if (isNaN(num)) return 'Enter a valid number 1-50';
|
||||||
return (num >= 1 && num <= 50) || 'Enter 1-50';
|
return (num >= 1 && num <= 50) || 'Enter 1-50';
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -97,10 +98,10 @@ class GitHubCopilotSetup extends BaseIdeSetup {
|
||||||
const config = options.preCollectedConfig || {};
|
const config = options.preCollectedConfig || {};
|
||||||
await this.configureVsCodeSettings(projectDir, { ...options, ...config });
|
await this.configureVsCodeSettings(projectDir, { ...options, ...config });
|
||||||
|
|
||||||
// Create .github/chatmodes directory
|
// Create .github/agents directory
|
||||||
const githubDir = path.join(projectDir, this.configDir);
|
const githubDir = path.join(projectDir, this.configDir);
|
||||||
const chatmodesDir = path.join(githubDir, this.chatmodesDir);
|
const agentsDir = path.join(githubDir, this.agentsDir);
|
||||||
await this.ensureDir(chatmodesDir);
|
await this.ensureDir(agentsDir);
|
||||||
|
|
||||||
// Clean up any existing BMAD files before reinstalling
|
// Clean up any existing BMAD files before reinstalling
|
||||||
await this.cleanup(projectDir);
|
await this.cleanup(projectDir);
|
||||||
|
|
@ -109,29 +110,29 @@ class GitHubCopilotSetup extends BaseIdeSetup {
|
||||||
const agentGen = new AgentCommandGenerator(this.bmadFolderName);
|
const agentGen = new AgentCommandGenerator(this.bmadFolderName);
|
||||||
const { artifacts: agentArtifacts } = await agentGen.collectAgentArtifacts(bmadDir, options.selectedModules || []);
|
const { artifacts: agentArtifacts } = await agentGen.collectAgentArtifacts(bmadDir, options.selectedModules || []);
|
||||||
|
|
||||||
// Create chat mode files with bmad- prefix
|
// Create agent files with bmd- prefix
|
||||||
let modeCount = 0;
|
let agentCount = 0;
|
||||||
for (const artifact of agentArtifacts) {
|
for (const artifact of agentArtifacts) {
|
||||||
const content = artifact.content;
|
const content = artifact.content;
|
||||||
const chatmodeContent = await this.createChatmodeContent({ module: artifact.module, name: artifact.name }, content);
|
const agentContent = await this.createAgentContent({ module: artifact.module, name: artifact.name }, content);
|
||||||
|
|
||||||
// Use bmad- prefix: bmad-agent-{module}-{name}.chatmode.md
|
// Use bmd- prefix: bmd-custom-{module}-{name}.agent.md
|
||||||
const targetPath = path.join(chatmodesDir, `bmad-agent-${artifact.module}-${artifact.name}.chatmode.md`);
|
const targetPath = path.join(agentsDir, `bmd-custom-${artifact.module}-${artifact.name}.agent.md`);
|
||||||
await this.writeFile(targetPath, chatmodeContent);
|
await this.writeFile(targetPath, agentContent);
|
||||||
modeCount++;
|
agentCount++;
|
||||||
|
|
||||||
console.log(chalk.green(` ✓ Created chat mode: bmad-agent-${artifact.module}-${artifact.name}`));
|
console.log(chalk.green(` ✓ Created agent: bmd-custom-${artifact.module}-${artifact.name}`));
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(chalk.green(`✓ ${this.name} configured:`));
|
console.log(chalk.green(`✓ ${this.name} configured:`));
|
||||||
console.log(chalk.dim(` - ${modeCount} chat modes created`));
|
console.log(chalk.dim(` - ${agentCount} agents created`));
|
||||||
console.log(chalk.dim(` - Chat modes directory: ${path.relative(projectDir, chatmodesDir)}`));
|
console.log(chalk.dim(` - Agents directory: ${path.relative(projectDir, agentsDir)}`));
|
||||||
console.log(chalk.dim(` - VS Code settings configured`));
|
console.log(chalk.dim(` - VS Code settings configured`));
|
||||||
console.log(chalk.dim('\n Chat modes available in VS Code Chat view'));
|
console.log(chalk.dim('\n Agents available in VS Code Chat view'));
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
chatmodes: modeCount,
|
agents: agentCount,
|
||||||
settings: true,
|
settings: true,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -187,9 +188,10 @@ class GitHubCopilotSetup extends BaseIdeSetup {
|
||||||
// Manual configuration - use pre-collected settings
|
// Manual configuration - use pre-collected settings
|
||||||
const manual = options.manualSettings || {};
|
const manual = options.manualSettings || {};
|
||||||
|
|
||||||
|
const maxRequests = parseInt(manual.maxRequests || '15', 10);
|
||||||
bmadSettings = {
|
bmadSettings = {
|
||||||
'chat.agent.enabled': true,
|
'chat.agent.enabled': true,
|
||||||
'chat.agent.maxRequests': parseInt(manual.maxRequests || 15),
|
'chat.agent.maxRequests': isNaN(maxRequests) ? 15 : maxRequests,
|
||||||
'github.copilot.chat.agent.runTasks': manual.runTasks === undefined ? true : manual.runTasks,
|
'github.copilot.chat.agent.runTasks': manual.runTasks === undefined ? true : manual.runTasks,
|
||||||
'chat.mcp.discovery.enabled': manual.mcpDiscovery === undefined ? true : manual.mcpDiscovery,
|
'chat.mcp.discovery.enabled': manual.mcpDiscovery === undefined ? true : manual.mcpDiscovery,
|
||||||
'github.copilot.chat.agent.autoFix': manual.autoFix === undefined ? true : manual.autoFix,
|
'github.copilot.chat.agent.autoFix': manual.autoFix === undefined ? true : manual.autoFix,
|
||||||
|
|
@ -206,9 +208,9 @@ class GitHubCopilotSetup extends BaseIdeSetup {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create chat mode content
|
* Create agent content
|
||||||
*/
|
*/
|
||||||
async createChatmodeContent(agent, content) {
|
async createAgentContent(agent, content) {
|
||||||
// Extract metadata from launcher frontmatter if present
|
// Extract metadata from launcher frontmatter if present
|
||||||
const descMatch = content.match(/description:\s*"([^"]+)"/);
|
const descMatch = content.match(/description:\s*"([^"]+)"/);
|
||||||
const title = descMatch ? descMatch[1] : this.formatTitle(agent.name);
|
const title = descMatch ? descMatch[1] : this.formatTitle(agent.name);
|
||||||
|
|
@ -226,30 +228,21 @@ class GitHubCopilotSetup extends BaseIdeSetup {
|
||||||
// Reference: https://code.visualstudio.com/docs/copilot/reference/copilot-vscode-features#_chat-tools
|
// Reference: https://code.visualstudio.com/docs/copilot/reference/copilot-vscode-features#_chat-tools
|
||||||
const tools = [
|
const tools = [
|
||||||
'changes', // List of source control changes
|
'changes', // List of source control changes
|
||||||
'codebase', // Perform code search in workspace
|
'edit', // Edit files in your workspace including: createFile, createDirectory, editNotebook, newJupyterNotebook and editFiles
|
||||||
'createDirectory', // Create new directory in workspace
|
|
||||||
'createFile', // Create new file in workspace
|
|
||||||
'editFiles', // Apply edits to files in workspace
|
|
||||||
'fetch', // Fetch content from web page
|
'fetch', // Fetch content from web page
|
||||||
'fileSearch', // Search files using glob patterns
|
|
||||||
'githubRepo', // Perform code search in GitHub repo
|
'githubRepo', // Perform code search in GitHub repo
|
||||||
'listDirectory', // List files in a directory
|
|
||||||
'problems', // Add workspace issues from Problems panel
|
'problems', // Add workspace issues from Problems panel
|
||||||
'readFile', // Read content of a file in workspace
|
'runCommands', // Runs commands in the terminal including: getTerminalOutput, terminalSelection, terminalLastCommand and runInTerminal
|
||||||
'runInTerminal', // Run shell command in integrated terminal
|
'runTasks', // Runs tasks and gets their output for your workspace
|
||||||
'runTask', // Run existing task in workspace
|
|
||||||
'runTests', // Run unit tests in workspace
|
'runTests', // Run unit tests in workspace
|
||||||
'runVscodeCommand', // Run VS Code command
|
'search', // Search and read files in your workspace, including:fileSearch, textSearch, listDirectory, readFile, codebase and searchResults
|
||||||
'search', // Enable file searching in workspace
|
'runSubagent', // Runs a task within an isolated subagent context. Enables efficient organization of tasks and context window management.
|
||||||
'searchResults', // Get search results from Search view
|
|
||||||
'terminalLastCommand', // Get last terminal command and output
|
|
||||||
'terminalSelection', // Get current terminal selection
|
|
||||||
'testFailure', // Get unit test failure information
|
'testFailure', // Get unit test failure information
|
||||||
'textSearch', // Find text in files
|
'todos', // Tool for managing and tracking todo items for task planning
|
||||||
'usages', // Find references and navigate definitions
|
'usages', // Find references and navigate definitions
|
||||||
];
|
];
|
||||||
|
|
||||||
let chatmodeContent = `---
|
let agentContent = `---
|
||||||
description: "${description.replaceAll('"', String.raw`\"`)}"
|
description: "${description.replaceAll('"', String.raw`\"`)}"
|
||||||
tools: ${JSON.stringify(tools)}
|
tools: ${JSON.stringify(tools)}
|
||||||
---
|
---
|
||||||
|
|
@ -260,7 +253,7 @@ ${cleanContent}
|
||||||
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
return chatmodeContent;
|
return agentContent;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -278,10 +271,10 @@ ${cleanContent}
|
||||||
*/
|
*/
|
||||||
async cleanup(projectDir) {
|
async cleanup(projectDir) {
|
||||||
const fs = require('fs-extra');
|
const fs = require('fs-extra');
|
||||||
const chatmodesDir = path.join(projectDir, this.configDir, this.chatmodesDir);
|
|
||||||
|
|
||||||
|
// Clean up old chatmodes directory
|
||||||
|
const chatmodesDir = path.join(projectDir, this.configDir, 'chatmodes');
|
||||||
if (await fs.pathExists(chatmodesDir)) {
|
if (await fs.pathExists(chatmodesDir)) {
|
||||||
// Only remove files that start with bmad- prefix
|
|
||||||
const files = await fs.readdir(chatmodesDir);
|
const files = await fs.readdir(chatmodesDir);
|
||||||
let removed = 0;
|
let removed = 0;
|
||||||
|
|
||||||
|
|
@ -293,7 +286,25 @@ ${cleanContent}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (removed > 0) {
|
if (removed > 0) {
|
||||||
console.log(chalk.dim(` Cleaned up ${removed} existing BMAD chat modes`));
|
console.log(chalk.dim(` Cleaned up ${removed} old BMAD chat modes`));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up new agents directory
|
||||||
|
const agentsDir = path.join(projectDir, this.configDir, this.agentsDir);
|
||||||
|
if (await fs.pathExists(agentsDir)) {
|
||||||
|
const files = await fs.readdir(agentsDir);
|
||||||
|
let removed = 0;
|
||||||
|
|
||||||
|
for (const file of files) {
|
||||||
|
if (file.startsWith('bmd-') && file.endsWith('.agent.md')) {
|
||||||
|
await fs.remove(path.join(agentsDir, file));
|
||||||
|
removed++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (removed > 0) {
|
||||||
|
console.log(chalk.dim(` Cleaned up ${removed} existing BMAD agents`));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -307,13 +318,13 @@ ${cleanContent}
|
||||||
* @returns {Object|null} Info about created command
|
* @returns {Object|null} Info about created command
|
||||||
*/
|
*/
|
||||||
async installCustomAgentLauncher(projectDir, agentName, agentPath, metadata) {
|
async installCustomAgentLauncher(projectDir, agentName, agentPath, metadata) {
|
||||||
const chatmodesDir = path.join(projectDir, this.configDir, this.chatmodesDir);
|
const agentsDir = path.join(projectDir, this.configDir, this.agentsDir);
|
||||||
|
|
||||||
if (!(await this.exists(path.join(projectDir, this.configDir)))) {
|
if (!(await this.exists(path.join(projectDir, this.configDir)))) {
|
||||||
return null; // IDE not configured for this project
|
return null; // IDE not configured for this project
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.ensureDir(chatmodesDir);
|
await this.ensureDir(agentsDir);
|
||||||
|
|
||||||
const launcherContent = `You must fully embody this agent's persona and follow all activation instructions exactly as specified. NEVER break character until given an exit command.
|
const launcherContent = `You must fully embody this agent's persona and follow all activation instructions exactly as specified. NEVER break character until given an exit command.
|
||||||
|
|
||||||
|
|
@ -353,7 +364,7 @@ ${cleanContent}
|
||||||
'usages',
|
'usages',
|
||||||
];
|
];
|
||||||
|
|
||||||
const chatmodeContent = `---
|
const agentContent = `---
|
||||||
description: "Activates the ${metadata.title || agentName} agent persona."
|
description: "Activates the ${metadata.title || agentName} agent persona."
|
||||||
tools: ${JSON.stringify(copilotTools)}
|
tools: ${JSON.stringify(copilotTools)}
|
||||||
---
|
---
|
||||||
|
|
@ -363,12 +374,12 @@ tools: ${JSON.stringify(copilotTools)}
|
||||||
${launcherContent}
|
${launcherContent}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const chatmodePath = path.join(chatmodesDir, `bmad-agent-custom-${agentName}.chatmode.md`);
|
const agentFilePath = path.join(agentsDir, `bmd-custom-${agentName}.agent.md`);
|
||||||
await this.writeFile(chatmodePath, chatmodeContent);
|
await this.writeFile(agentFilePath, agentContent);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
path: chatmodePath,
|
path: agentFilePath,
|
||||||
command: `bmad-agent-custom-${agentName}`,
|
command: `bmd-custom-${agentName}`,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -123,6 +123,9 @@ class KiloSetup extends BaseIdeSetup {
|
||||||
modeEntry += ` groups:\n`;
|
modeEntry += ` groups:\n`;
|
||||||
modeEntry += ` - read\n`;
|
modeEntry += ` - read\n`;
|
||||||
modeEntry += ` - edit\n`;
|
modeEntry += ` - edit\n`;
|
||||||
|
modeEntry += ` - browser\n`;
|
||||||
|
modeEntry += ` - command\n`;
|
||||||
|
modeEntry += ` - mcp\n`;
|
||||||
|
|
||||||
return modeEntry;
|
return modeEntry;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -120,12 +120,15 @@ class WorkflowCommandGenerator {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const coreWorkflowPath = `${this.bmadFolderName}/core/tasks/workflow.xml`;
|
||||||
|
|
||||||
// Replace template variables
|
// Replace template variables
|
||||||
return template
|
return template
|
||||||
.replaceAll('{{name}}', workflow.name)
|
.replaceAll('{{name}}', workflow.name)
|
||||||
.replaceAll('{{module}}', workflow.module)
|
.replaceAll('{{module}}', workflow.module)
|
||||||
.replaceAll('{{description}}', workflow.description)
|
.replaceAll('{{description}}', workflow.description)
|
||||||
.replaceAll('{{workflow_path}}', workflowPath)
|
.replaceAll('{{workflow_path}}', workflowPath)
|
||||||
|
.replaceAll('{{core_workflow_path}}', coreWorkflowPath)
|
||||||
.replaceAll('{bmad_folder}', this.bmadFolderName)
|
.replaceAll('{bmad_folder}', this.bmadFolderName)
|
||||||
.replaceAll('{{interactive}}', workflow.interactive)
|
.replaceAll('{{interactive}}', workflow.interactive)
|
||||||
.replaceAll('{{author}}', workflow.author || 'BMAD');
|
.replaceAll('{{author}}', workflow.author || 'BMAD');
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ description: '{{description}}'
|
||||||
IT IS CRITICAL THAT YOU FOLLOW THESE STEPS - while staying in character as the current agent persona you may have loaded:
|
IT IS CRITICAL THAT YOU FOLLOW THESE STEPS - while staying in character as the current agent persona you may have loaded:
|
||||||
|
|
||||||
<steps CRITICAL="TRUE">
|
<steps CRITICAL="TRUE">
|
||||||
1. Always LOAD the FULL @{bmad_folder}/core/tasks/workflow.xml
|
1. Always LOAD the FULL @{{core_workflow_path}}
|
||||||
2. READ its entire contents - this is the CORE OS for EXECUTING the specific workflow-config @{{workflow_path}}
|
2. READ its entire contents - this is the CORE OS for EXECUTING the specific workflow-config @{{workflow_path}}
|
||||||
3. Pass the yaml path {{workflow_path}} as 'workflow-config' parameter to the workflow.xml instructions
|
3. Pass the yaml path {{workflow_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
|
4. Follow workflow.xml instructions EXACTLY as written to process and follow the specific workflow config and its instructions
|
||||||
|
|
|
||||||
|
|
@ -720,8 +720,8 @@ class UI {
|
||||||
{
|
{
|
||||||
type: 'confirm',
|
type: 'confirm',
|
||||||
name: 'enableTts',
|
name: 'enableTts',
|
||||||
message: 'Enable AgentVibes TTS? (Agents speak with unique voices in party mode)',
|
message: 'Enable Agents to Speak Out loud (powered by Agent Vibes? Claude Code only currently)',
|
||||||
default: true, // Default to yes - recommended for best experience
|
default: false, // Default to yes - recommended for best experience
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue