bmad-method 6.2.1-next.24 → 6.2.1-next.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/tools/cli/commands/install.js +1 -8
- package/tools/cli/installers/lib/core/installer.js +1 -209
- package/tools/cli/installers/lib/core/manifest-generator.js +2 -2
- package/tools/cli/installers/lib/custom/handler.js +1 -247
- package/tools/cli/installers/lib/ide/_base-ide.js +0 -16
- package/tools/cli/installers/lib/modules/manager.js +2 -450
- package/tools/cli/lib/ui.js +0 -19
- package/tools/cli/lib/activation-builder.js +0 -165
- package/tools/cli/lib/agent/compiler.js +0 -516
- package/tools/cli/lib/agent/installer.js +0 -680
- package/tools/cli/lib/agent/template-engine.js +0 -152
- package/tools/cli/lib/agent-analyzer.js +0 -97
- package/tools/cli/lib/agent-party-generator.js +0 -194
- package/tools/cli/lib/xml-handler.js +0 -177
- package/tools/cli/lib/xml-to-markdown.js +0 -82
- package/tools/cli/lib/yaml-xml-builder.js +0 -572
package/package.json
CHANGED
|
@@ -18,7 +18,7 @@ module.exports = {
|
|
|
18
18
|
'Comma-separated list of tool/IDE IDs to configure (e.g., "claude-code,cursor"). Use "none" to skip tool configuration.',
|
|
19
19
|
],
|
|
20
20
|
['--custom-content <paths>', 'Comma-separated list of paths to custom modules/agents/workflows'],
|
|
21
|
-
['--action <type>', 'Action type for existing installations: install, update, quick-update
|
|
21
|
+
['--action <type>', 'Action type for existing installations: install, update, or quick-update'],
|
|
22
22
|
['--user-name <name>', 'Name for agents to use (default: system username)'],
|
|
23
23
|
['--communication-language <lang>', 'Language for agent communication (default: English)'],
|
|
24
24
|
['--document-output-language <lang>', 'Language for document output (default: English)'],
|
|
@@ -49,13 +49,6 @@ module.exports = {
|
|
|
49
49
|
process.exit(0);
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
// Handle compile agents separately
|
|
53
|
-
if (config.actionType === 'compile-agents') {
|
|
54
|
-
const result = await installer.compileAgents(config);
|
|
55
|
-
await prompts.log.info(`Recompiled ${result.agentCount} agents with customizations applied`);
|
|
56
|
-
process.exit(0);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
52
|
// Regular install/update flow
|
|
60
53
|
const result = await installer.install(config);
|
|
61
54
|
|
|
@@ -6,7 +6,6 @@ const { ModuleManager } = require('../modules/manager');
|
|
|
6
6
|
const { IdeManager } = require('../ide/manager');
|
|
7
7
|
const { FileOps } = require('../../../lib/file-ops');
|
|
8
8
|
const { Config } = require('../../../lib/config');
|
|
9
|
-
const { XmlHandler } = require('../../../lib/xml-handler');
|
|
10
9
|
const { DependencyResolver } = require('./dependency-resolver');
|
|
11
10
|
const { ConfigCollector } = require('./config-collector');
|
|
12
11
|
const { getProjectRoot, getSourcePath, getModulePath } = require('../../../lib/project-root');
|
|
@@ -25,7 +24,6 @@ class Installer {
|
|
|
25
24
|
this.ideManager = new IdeManager();
|
|
26
25
|
this.fileOps = new FileOps();
|
|
27
26
|
this.config = new Config();
|
|
28
|
-
this.xmlHandler = new XmlHandler();
|
|
29
27
|
this.dependencyResolver = new DependencyResolver();
|
|
30
28
|
this.configCollector = new ConfigCollector();
|
|
31
29
|
this.ideConfigManager = new IdeConfigManager();
|
|
@@ -2114,10 +2112,6 @@ class Installer {
|
|
|
2114
2112
|
},
|
|
2115
2113
|
);
|
|
2116
2114
|
|
|
2117
|
-
// Process agent files to build YAML agents and create customize templates
|
|
2118
|
-
const modulePath = path.join(bmadDir, moduleName);
|
|
2119
|
-
await this.processAgentFiles(modulePath, moduleName);
|
|
2120
|
-
|
|
2121
2115
|
// Dependencies are already included in full module install
|
|
2122
2116
|
}
|
|
2123
2117
|
|
|
@@ -2227,16 +2221,8 @@ class Installer {
|
|
|
2227
2221
|
const sourcePath = getModulePath('core');
|
|
2228
2222
|
const targetPath = path.join(bmadDir, 'core');
|
|
2229
2223
|
|
|
2230
|
-
// Copy core files
|
|
2224
|
+
// Copy core files
|
|
2231
2225
|
await this.copyCoreFiles(sourcePath, targetPath);
|
|
2232
|
-
|
|
2233
|
-
// Compile agents using the same compiler as modules
|
|
2234
|
-
const { ModuleManager } = require('../modules/manager');
|
|
2235
|
-
const moduleManager = new ModuleManager();
|
|
2236
|
-
await moduleManager.compileModuleAgents(sourcePath, targetPath, 'core', bmadDir, this);
|
|
2237
|
-
|
|
2238
|
-
// Process agent files to inject activation block
|
|
2239
|
-
await this.processAgentFiles(targetPath, 'core');
|
|
2240
2226
|
}
|
|
2241
2227
|
|
|
2242
2228
|
/**
|
|
@@ -2254,16 +2240,6 @@ class Installer {
|
|
|
2254
2240
|
continue;
|
|
2255
2241
|
}
|
|
2256
2242
|
|
|
2257
|
-
// Skip sidecar directories - they are handled separately during agent compilation
|
|
2258
|
-
if (
|
|
2259
|
-
path
|
|
2260
|
-
.dirname(file)
|
|
2261
|
-
.split('/')
|
|
2262
|
-
.some((dir) => dir.toLowerCase().includes('sidecar'))
|
|
2263
|
-
) {
|
|
2264
|
-
continue;
|
|
2265
|
-
}
|
|
2266
|
-
|
|
2267
2243
|
// Skip module.yaml at root - it's only needed at install time
|
|
2268
2244
|
if (file === 'module.yaml') {
|
|
2269
2245
|
continue;
|
|
@@ -2274,27 +2250,9 @@ class Installer {
|
|
|
2274
2250
|
continue;
|
|
2275
2251
|
}
|
|
2276
2252
|
|
|
2277
|
-
// Skip .agent.yaml files - they will be compiled separately
|
|
2278
|
-
if (file.endsWith('.agent.yaml')) {
|
|
2279
|
-
continue;
|
|
2280
|
-
}
|
|
2281
|
-
|
|
2282
2253
|
const sourceFile = path.join(sourcePath, file);
|
|
2283
2254
|
const targetFile = path.join(targetPath, file);
|
|
2284
2255
|
|
|
2285
|
-
// Check if this is an agent file
|
|
2286
|
-
if (file.startsWith('agents/') && file.endsWith('.md')) {
|
|
2287
|
-
// Read the file to check for localskip
|
|
2288
|
-
const content = await fs.readFile(sourceFile, 'utf8');
|
|
2289
|
-
|
|
2290
|
-
// Check for localskip="true" in the agent tag
|
|
2291
|
-
const agentMatch = content.match(/<agent[^>]*\slocalskip="true"[^>]*>/);
|
|
2292
|
-
if (agentMatch) {
|
|
2293
|
-
await prompts.log.message(` Skipping web-only agent: ${path.basename(file)}`);
|
|
2294
|
-
continue; // Skip this agent
|
|
2295
|
-
}
|
|
2296
|
-
}
|
|
2297
|
-
|
|
2298
2256
|
// Copy the file with placeholder replacement
|
|
2299
2257
|
await fs.ensureDir(path.dirname(targetFile));
|
|
2300
2258
|
await this.copyFileWithPlaceholderReplacement(sourceFile, targetFile);
|
|
@@ -2328,58 +2286,6 @@ class Installer {
|
|
|
2328
2286
|
return files;
|
|
2329
2287
|
}
|
|
2330
2288
|
|
|
2331
|
-
/**
|
|
2332
|
-
* Process agent files to build YAML agents and inject activation blocks
|
|
2333
|
-
* @param {string} modulePath - Path to module in bmad/ installation
|
|
2334
|
-
* @param {string} moduleName - Module name
|
|
2335
|
-
*/
|
|
2336
|
-
async processAgentFiles(modulePath, moduleName) {
|
|
2337
|
-
const agentsPath = path.join(modulePath, 'agents');
|
|
2338
|
-
|
|
2339
|
-
// Check if agents directory exists
|
|
2340
|
-
if (!(await fs.pathExists(agentsPath))) {
|
|
2341
|
-
return; // No agents to process
|
|
2342
|
-
}
|
|
2343
|
-
|
|
2344
|
-
// Determine project directory (parent of bmad/ directory)
|
|
2345
|
-
const bmadDir = path.dirname(modulePath);
|
|
2346
|
-
const cfgAgentsDir = path.join(bmadDir, '_config', 'agents');
|
|
2347
|
-
|
|
2348
|
-
// Ensure _config/agents directory exists
|
|
2349
|
-
await fs.ensureDir(cfgAgentsDir);
|
|
2350
|
-
|
|
2351
|
-
// Get all agent files
|
|
2352
|
-
const agentFiles = await fs.readdir(agentsPath);
|
|
2353
|
-
|
|
2354
|
-
for (const agentFile of agentFiles) {
|
|
2355
|
-
// Skip .agent.yaml files - they should already be compiled by compileModuleAgents
|
|
2356
|
-
if (agentFile.endsWith('.agent.yaml')) {
|
|
2357
|
-
continue;
|
|
2358
|
-
}
|
|
2359
|
-
|
|
2360
|
-
// Only process .md files (already compiled from YAML)
|
|
2361
|
-
if (!agentFile.endsWith('.md')) {
|
|
2362
|
-
continue;
|
|
2363
|
-
}
|
|
2364
|
-
|
|
2365
|
-
const agentName = agentFile.replace('.md', '');
|
|
2366
|
-
const mdPath = path.join(agentsPath, agentFile);
|
|
2367
|
-
const customizePath = path.join(cfgAgentsDir, `${moduleName}-${agentName}.customize.yaml`);
|
|
2368
|
-
|
|
2369
|
-
// For .md files that are already compiled, we don't need to do much
|
|
2370
|
-
// Just ensure the customize template exists
|
|
2371
|
-
if (!(await fs.pathExists(customizePath))) {
|
|
2372
|
-
const genericTemplatePath = getSourcePath('utility', 'agent-components', 'agent.customize.template.yaml');
|
|
2373
|
-
if (await fs.pathExists(genericTemplatePath)) {
|
|
2374
|
-
await this.copyFileWithPlaceholderReplacement(genericTemplatePath, customizePath);
|
|
2375
|
-
if (process.env.BMAD_VERBOSE_INSTALL === 'true') {
|
|
2376
|
-
await prompts.log.message(` Created customize: ${moduleName}-${agentName}.customize.yaml`);
|
|
2377
|
-
}
|
|
2378
|
-
}
|
|
2379
|
-
}
|
|
2380
|
-
}
|
|
2381
|
-
}
|
|
2382
|
-
|
|
2383
2289
|
/**
|
|
2384
2290
|
* Private: Update core
|
|
2385
2291
|
*/
|
|
@@ -2393,12 +2299,6 @@ class Installer {
|
|
|
2393
2299
|
} else {
|
|
2394
2300
|
// Selective update - preserve user modifications
|
|
2395
2301
|
await this.fileOps.syncDirectory(sourcePath, targetPath);
|
|
2396
|
-
|
|
2397
|
-
// Recompile agents (#1133)
|
|
2398
|
-
const { ModuleManager } = require('../modules/manager');
|
|
2399
|
-
const moduleManager = new ModuleManager();
|
|
2400
|
-
await moduleManager.compileModuleAgents(sourcePath, targetPath, 'core', bmadDir, this);
|
|
2401
|
-
await this.processAgentFiles(targetPath, 'core');
|
|
2402
2302
|
}
|
|
2403
2303
|
}
|
|
2404
2304
|
|
|
@@ -2643,114 +2543,6 @@ class Installer {
|
|
|
2643
2543
|
}
|
|
2644
2544
|
}
|
|
2645
2545
|
|
|
2646
|
-
/**
|
|
2647
|
-
* Compile agents with customizations only
|
|
2648
|
-
* @param {Object} config - Configuration with directory
|
|
2649
|
-
* @returns {Object} Compilation result
|
|
2650
|
-
*/
|
|
2651
|
-
async compileAgents(config) {
|
|
2652
|
-
// Using @clack prompts
|
|
2653
|
-
const { ModuleManager } = require('../modules/manager');
|
|
2654
|
-
const { getSourcePath } = require('../../../lib/project-root');
|
|
2655
|
-
|
|
2656
|
-
const spinner = await prompts.spinner();
|
|
2657
|
-
spinner.start('Recompiling agents with customizations...');
|
|
2658
|
-
|
|
2659
|
-
try {
|
|
2660
|
-
const projectDir = path.resolve(config.directory);
|
|
2661
|
-
const { bmadDir } = await this.findBmadDir(projectDir);
|
|
2662
|
-
|
|
2663
|
-
// Check if bmad directory exists
|
|
2664
|
-
if (!(await fs.pathExists(bmadDir))) {
|
|
2665
|
-
spinner.stop('No BMAD installation found');
|
|
2666
|
-
throw new Error(`BMAD not installed at ${bmadDir}. Use regular install for first-time setup.`);
|
|
2667
|
-
}
|
|
2668
|
-
|
|
2669
|
-
// Detect existing installation
|
|
2670
|
-
const existingInstall = await this.detector.detect(bmadDir);
|
|
2671
|
-
const installedModules = existingInstall.modules.map((m) => m.id);
|
|
2672
|
-
|
|
2673
|
-
// Initialize module manager
|
|
2674
|
-
const moduleManager = new ModuleManager();
|
|
2675
|
-
moduleManager.setBmadFolderName(path.basename(bmadDir));
|
|
2676
|
-
|
|
2677
|
-
let totalAgentCount = 0;
|
|
2678
|
-
|
|
2679
|
-
// Get custom module sources from cache
|
|
2680
|
-
const customModuleSources = new Map();
|
|
2681
|
-
const cacheDir = path.join(bmadDir, '_config', 'custom');
|
|
2682
|
-
if (await fs.pathExists(cacheDir)) {
|
|
2683
|
-
const cachedModules = await fs.readdir(cacheDir, { withFileTypes: true });
|
|
2684
|
-
|
|
2685
|
-
for (const cachedModule of cachedModules) {
|
|
2686
|
-
if (cachedModule.isDirectory()) {
|
|
2687
|
-
const moduleId = cachedModule.name;
|
|
2688
|
-
const cachedPath = path.join(cacheDir, moduleId);
|
|
2689
|
-
const moduleYamlPath = path.join(cachedPath, 'module.yaml');
|
|
2690
|
-
|
|
2691
|
-
// Check if this is actually a custom module
|
|
2692
|
-
if (await fs.pathExists(moduleYamlPath)) {
|
|
2693
|
-
// Check if this is an external official module - skip cache for those
|
|
2694
|
-
const isExternal = await this.moduleManager.isExternalModule(moduleId);
|
|
2695
|
-
if (isExternal) {
|
|
2696
|
-
// External modules are handled via cloneExternalModule, not from cache
|
|
2697
|
-
continue;
|
|
2698
|
-
}
|
|
2699
|
-
customModuleSources.set(moduleId, cachedPath);
|
|
2700
|
-
}
|
|
2701
|
-
}
|
|
2702
|
-
}
|
|
2703
|
-
}
|
|
2704
|
-
|
|
2705
|
-
// Process each installed module
|
|
2706
|
-
for (const moduleId of installedModules) {
|
|
2707
|
-
spinner.message(`Recompiling agents in ${moduleId}...`);
|
|
2708
|
-
|
|
2709
|
-
// Get source path
|
|
2710
|
-
let sourcePath;
|
|
2711
|
-
if (moduleId === 'core') {
|
|
2712
|
-
sourcePath = getSourcePath('core-skills');
|
|
2713
|
-
} else {
|
|
2714
|
-
// First check if it's in the custom cache
|
|
2715
|
-
if (customModuleSources.has(moduleId)) {
|
|
2716
|
-
sourcePath = customModuleSources.get(moduleId);
|
|
2717
|
-
} else {
|
|
2718
|
-
sourcePath = await moduleManager.findModuleSource(moduleId);
|
|
2719
|
-
}
|
|
2720
|
-
}
|
|
2721
|
-
|
|
2722
|
-
if (!sourcePath) {
|
|
2723
|
-
await prompts.log.warn(`Source not found for module ${moduleId}, skipping...`);
|
|
2724
|
-
continue;
|
|
2725
|
-
}
|
|
2726
|
-
|
|
2727
|
-
const targetPath = path.join(bmadDir, moduleId);
|
|
2728
|
-
|
|
2729
|
-
// Compile agents for this module
|
|
2730
|
-
await moduleManager.compileModuleAgents(sourcePath, targetPath, moduleId, bmadDir, this);
|
|
2731
|
-
|
|
2732
|
-
// Count agents (rough estimate based on files)
|
|
2733
|
-
const agentsPath = path.join(targetPath, 'agents');
|
|
2734
|
-
if (await fs.pathExists(agentsPath)) {
|
|
2735
|
-
const agentFiles = await fs.readdir(agentsPath);
|
|
2736
|
-
const agentCount = agentFiles.filter((f) => f.endsWith('.md')).length;
|
|
2737
|
-
totalAgentCount += agentCount;
|
|
2738
|
-
}
|
|
2739
|
-
}
|
|
2740
|
-
|
|
2741
|
-
spinner.stop('Agent recompilation complete!');
|
|
2742
|
-
|
|
2743
|
-
return {
|
|
2744
|
-
success: true,
|
|
2745
|
-
agentCount: totalAgentCount,
|
|
2746
|
-
modules: installedModules,
|
|
2747
|
-
};
|
|
2748
|
-
} catch (error) {
|
|
2749
|
-
spinner.error('Agent recompilation failed');
|
|
2750
|
-
throw error;
|
|
2751
|
-
}
|
|
2752
|
-
}
|
|
2753
|
-
|
|
2754
2546
|
/**
|
|
2755
2547
|
* Private: Prompt for update action
|
|
2756
2548
|
*/
|
|
@@ -515,7 +515,7 @@ class ManifestGenerator {
|
|
|
515
515
|
|
|
516
516
|
/**
|
|
517
517
|
* Get agents from a directory recursively
|
|
518
|
-
* Only includes
|
|
518
|
+
* Only includes .md files with agent content
|
|
519
519
|
*/
|
|
520
520
|
async getAgentsFromDir(dirPath, moduleName, relativePath = '') {
|
|
521
521
|
// Skip directories claimed by collectSkills
|
|
@@ -572,7 +572,7 @@ class ManifestGenerator {
|
|
|
572
572
|
const newRelativePath = relativePath ? `${relativePath}/${entry.name}` : entry.name;
|
|
573
573
|
const subDirAgents = await this.getAgentsFromDir(fullPath, moduleName, newRelativePath);
|
|
574
574
|
agents.push(...subDirAgents);
|
|
575
|
-
} else if (entry.name.endsWith('.md') &&
|
|
575
|
+
} else if (entry.name.endsWith('.md') && entry.name.toLowerCase() !== 'readme.md') {
|
|
576
576
|
const content = await fs.readFile(fullPath, 'utf8');
|
|
577
577
|
|
|
578
578
|
// Skip files that don't contain <agent> tag (e.g., README files)
|
|
@@ -2,19 +2,11 @@ const path = require('node:path');
|
|
|
2
2
|
const fs = require('fs-extra');
|
|
3
3
|
const yaml = require('yaml');
|
|
4
4
|
const prompts = require('../../../lib/prompts');
|
|
5
|
-
const { FileOps } = require('../../../lib/file-ops');
|
|
6
|
-
const { XmlHandler } = require('../../../lib/xml-handler');
|
|
7
|
-
|
|
8
5
|
/**
|
|
9
6
|
* Handler for custom content (custom.yaml)
|
|
10
|
-
*
|
|
7
|
+
* Discovers custom agents and workflows in the project
|
|
11
8
|
*/
|
|
12
9
|
class CustomHandler {
|
|
13
|
-
constructor() {
|
|
14
|
-
this.fileOps = new FileOps();
|
|
15
|
-
this.xmlHandler = new XmlHandler();
|
|
16
|
-
}
|
|
17
|
-
|
|
18
10
|
/**
|
|
19
11
|
* Find all custom.yaml files in the project
|
|
20
12
|
* @param {string} projectRoot - Project root directory
|
|
@@ -115,244 +107,6 @@ class CustomHandler {
|
|
|
115
107
|
return null;
|
|
116
108
|
}
|
|
117
109
|
}
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* Install custom content
|
|
121
|
-
* @param {string} customPath - Path to custom content directory
|
|
122
|
-
* @param {string} bmadDir - Target bmad directory
|
|
123
|
-
* @param {Object} config - Configuration from custom.yaml
|
|
124
|
-
* @param {Function} fileTrackingCallback - Optional callback to track installed files
|
|
125
|
-
* @returns {Object} Installation result
|
|
126
|
-
*/
|
|
127
|
-
async install(customPath, bmadDir, config, fileTrackingCallback = null) {
|
|
128
|
-
const results = {
|
|
129
|
-
agentsInstalled: 0,
|
|
130
|
-
workflowsInstalled: 0,
|
|
131
|
-
filesCopied: 0,
|
|
132
|
-
preserved: 0,
|
|
133
|
-
errors: [],
|
|
134
|
-
};
|
|
135
|
-
|
|
136
|
-
try {
|
|
137
|
-
// Create custom directories in bmad
|
|
138
|
-
const bmadCustomDir = path.join(bmadDir, 'custom');
|
|
139
|
-
const bmadAgentsDir = path.join(bmadCustomDir, 'agents');
|
|
140
|
-
const bmadWorkflowsDir = path.join(bmadCustomDir, 'workflows');
|
|
141
|
-
|
|
142
|
-
await fs.ensureDir(bmadCustomDir);
|
|
143
|
-
await fs.ensureDir(bmadAgentsDir);
|
|
144
|
-
await fs.ensureDir(bmadWorkflowsDir);
|
|
145
|
-
|
|
146
|
-
// Process agents - compile and copy agents
|
|
147
|
-
const agentsDir = path.join(customPath, 'agents');
|
|
148
|
-
if (await fs.pathExists(agentsDir)) {
|
|
149
|
-
await this.compileAndCopyAgents(agentsDir, bmadAgentsDir, bmadDir, config, fileTrackingCallback, results);
|
|
150
|
-
|
|
151
|
-
// Count agent files
|
|
152
|
-
const agentFiles = await this.findFilesRecursively(agentsDir, ['.agent.yaml', '.md']);
|
|
153
|
-
results.agentsInstalled = agentFiles.length;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
// Process workflows - copy entire workflows directory structure
|
|
157
|
-
const workflowsDir = path.join(customPath, 'workflows');
|
|
158
|
-
if (await fs.pathExists(workflowsDir)) {
|
|
159
|
-
await this.copyDirectory(workflowsDir, bmadWorkflowsDir, results, fileTrackingCallback, config);
|
|
160
|
-
|
|
161
|
-
// Count workflow files
|
|
162
|
-
const workflowFiles = await this.findFilesRecursively(workflowsDir, ['.md']);
|
|
163
|
-
results.workflowsInstalled = workflowFiles.length;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
// Process any additional files at root
|
|
167
|
-
const entries = await fs.readdir(customPath, { withFileTypes: true });
|
|
168
|
-
for (const entry of entries) {
|
|
169
|
-
if (entry.isFile() && entry.name !== 'custom.yaml' && !entry.name.startsWith('.') && !entry.name.endsWith('.md')) {
|
|
170
|
-
// Skip .md files at root as they're likely docs
|
|
171
|
-
const sourcePath = path.join(customPath, entry.name);
|
|
172
|
-
const targetPath = path.join(bmadCustomDir, entry.name);
|
|
173
|
-
|
|
174
|
-
try {
|
|
175
|
-
// Check if file already exists
|
|
176
|
-
if (await fs.pathExists(targetPath)) {
|
|
177
|
-
// File already exists, preserve it
|
|
178
|
-
results.preserved = (results.preserved || 0) + 1;
|
|
179
|
-
} else {
|
|
180
|
-
await fs.copy(sourcePath, targetPath);
|
|
181
|
-
results.filesCopied++;
|
|
182
|
-
|
|
183
|
-
if (fileTrackingCallback) {
|
|
184
|
-
fileTrackingCallback(targetPath);
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
} catch (error) {
|
|
188
|
-
results.errors.push(`Failed to copy file ${entry.name}: ${error.message}`);
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
} catch (error) {
|
|
193
|
-
results.errors.push(`Installation failed: ${error.message}`);
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
return results;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
/**
|
|
200
|
-
* Find all files with specific extensions recursively
|
|
201
|
-
* @param {string} dir - Directory to search
|
|
202
|
-
* @param {Array} extensions - File extensions to match
|
|
203
|
-
* @returns {Array} List of matching files
|
|
204
|
-
*/
|
|
205
|
-
async findFilesRecursively(dir, extensions) {
|
|
206
|
-
const files = [];
|
|
207
|
-
|
|
208
|
-
async function search(currentDir) {
|
|
209
|
-
const entries = await fs.readdir(currentDir, { withFileTypes: true });
|
|
210
|
-
|
|
211
|
-
for (const entry of entries) {
|
|
212
|
-
const fullPath = path.join(currentDir, entry.name);
|
|
213
|
-
|
|
214
|
-
if (entry.isDirectory()) {
|
|
215
|
-
await search(fullPath);
|
|
216
|
-
} else if (extensions.some((ext) => entry.name.endsWith(ext))) {
|
|
217
|
-
files.push(fullPath);
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
await search(dir);
|
|
223
|
-
return files;
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
/**
|
|
227
|
-
* Recursively copy a directory
|
|
228
|
-
* @param {string} sourceDir - Source directory
|
|
229
|
-
* @param {string} targetDir - Target directory
|
|
230
|
-
* @param {Object} results - Results object to update
|
|
231
|
-
* @param {Function} fileTrackingCallback - Optional callback
|
|
232
|
-
* @param {Object} config - Configuration for placeholder replacement
|
|
233
|
-
*/
|
|
234
|
-
async copyDirectory(sourceDir, targetDir, results, fileTrackingCallback, config) {
|
|
235
|
-
await fs.ensureDir(targetDir);
|
|
236
|
-
const entries = await fs.readdir(sourceDir, { withFileTypes: true });
|
|
237
|
-
|
|
238
|
-
for (const entry of entries) {
|
|
239
|
-
const sourcePath = path.join(sourceDir, entry.name);
|
|
240
|
-
const targetPath = path.join(targetDir, entry.name);
|
|
241
|
-
|
|
242
|
-
if (entry.isDirectory()) {
|
|
243
|
-
await this.copyDirectory(sourcePath, targetPath, results, fileTrackingCallback, config);
|
|
244
|
-
} else {
|
|
245
|
-
try {
|
|
246
|
-
// Check if file already exists
|
|
247
|
-
if (await fs.pathExists(targetPath)) {
|
|
248
|
-
// File already exists, preserve it
|
|
249
|
-
results.preserved = (results.preserved || 0) + 1;
|
|
250
|
-
} else {
|
|
251
|
-
// Copy with placeholder replacement for text files
|
|
252
|
-
const textExtensions = ['.md', '.yaml', '.yml', '.txt', '.json'];
|
|
253
|
-
if (textExtensions.some((ext) => entry.name.endsWith(ext))) {
|
|
254
|
-
// Read source content
|
|
255
|
-
let content = await fs.readFile(sourcePath, 'utf8');
|
|
256
|
-
|
|
257
|
-
// Replace placeholders
|
|
258
|
-
content = content.replaceAll('{user_name}', config.user_name || 'User');
|
|
259
|
-
content = content.replaceAll('{communication_language}', config.communication_language || 'English');
|
|
260
|
-
content = content.replaceAll('{output_folder}', config.output_folder || 'docs');
|
|
261
|
-
|
|
262
|
-
// Write to target
|
|
263
|
-
await fs.ensureDir(path.dirname(targetPath));
|
|
264
|
-
await fs.writeFile(targetPath, content, 'utf8');
|
|
265
|
-
} else {
|
|
266
|
-
// Copy binary files as-is
|
|
267
|
-
await fs.copy(sourcePath, targetPath);
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
results.filesCopied++;
|
|
271
|
-
if (entry.name.endsWith('.md')) {
|
|
272
|
-
results.workflowsInstalled++;
|
|
273
|
-
}
|
|
274
|
-
if (fileTrackingCallback) {
|
|
275
|
-
fileTrackingCallback(targetPath);
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
} catch (error) {
|
|
279
|
-
results.errors.push(`Failed to copy ${entry.name}: ${error.message}`);
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
/**
|
|
286
|
-
* Compile .agent.yaml files to .md format and handle sidecars
|
|
287
|
-
* @param {string} sourceAgentsPath - Source agents directory
|
|
288
|
-
* @param {string} targetAgentsPath - Target agents directory
|
|
289
|
-
* @param {string} bmadDir - BMAD installation directory
|
|
290
|
-
* @param {Object} config - Configuration for placeholder replacement
|
|
291
|
-
* @param {Function} fileTrackingCallback - Optional callback to track installed files
|
|
292
|
-
* @param {Object} results - Results object to update
|
|
293
|
-
*/
|
|
294
|
-
async compileAndCopyAgents(sourceAgentsPath, targetAgentsPath, bmadDir, config, fileTrackingCallback, results) {
|
|
295
|
-
// Get all .agent.yaml files recursively
|
|
296
|
-
const agentFiles = await this.findFilesRecursively(sourceAgentsPath, ['.agent.yaml']);
|
|
297
|
-
|
|
298
|
-
for (const agentFile of agentFiles) {
|
|
299
|
-
const relativePath = path.relative(sourceAgentsPath, agentFile).split(path.sep).join('/');
|
|
300
|
-
const targetDir = path.join(targetAgentsPath, path.dirname(relativePath));
|
|
301
|
-
|
|
302
|
-
await fs.ensureDir(targetDir);
|
|
303
|
-
|
|
304
|
-
const agentName = path.basename(agentFile, '.agent.yaml');
|
|
305
|
-
const targetMdPath = path.join(targetDir, `${agentName}.md`);
|
|
306
|
-
// Use the actual bmadDir if available (for when installing to temp dir)
|
|
307
|
-
const actualBmadDir = config._bmadDir || bmadDir;
|
|
308
|
-
const customizePath = path.join(actualBmadDir, '_config', 'agents', `custom-${agentName}.customize.yaml`);
|
|
309
|
-
|
|
310
|
-
// Read and compile the YAML
|
|
311
|
-
try {
|
|
312
|
-
const yamlContent = await fs.readFile(agentFile, 'utf8');
|
|
313
|
-
const { compileAgent } = require('../../../lib/agent/compiler');
|
|
314
|
-
|
|
315
|
-
// Create customize template if it doesn't exist
|
|
316
|
-
if (!(await fs.pathExists(customizePath))) {
|
|
317
|
-
const { getSourcePath } = require('../../../lib/project-root');
|
|
318
|
-
const genericTemplatePath = getSourcePath('utility', 'agent-components', 'agent.customize.template.yaml');
|
|
319
|
-
if (await fs.pathExists(genericTemplatePath)) {
|
|
320
|
-
let templateContent = await fs.readFile(genericTemplatePath, 'utf8');
|
|
321
|
-
await fs.writeFile(customizePath, templateContent, 'utf8');
|
|
322
|
-
// Only show customize creation in verbose mode
|
|
323
|
-
if (process.env.BMAD_VERBOSE_INSTALL === 'true') {
|
|
324
|
-
await prompts.log.message(' Created customize: custom-' + agentName + '.customize.yaml');
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
// Compile the agent
|
|
330
|
-
const { xml } = compileAgent(yamlContent, {}, agentName, relativePath, { config });
|
|
331
|
-
|
|
332
|
-
// Replace placeholders in the compiled content
|
|
333
|
-
let processedXml = xml;
|
|
334
|
-
processedXml = processedXml.replaceAll('{user_name}', config.user_name || 'User');
|
|
335
|
-
processedXml = processedXml.replaceAll('{communication_language}', config.communication_language || 'English');
|
|
336
|
-
processedXml = processedXml.replaceAll('{output_folder}', config.output_folder || 'docs');
|
|
337
|
-
|
|
338
|
-
// Write the compiled MD file
|
|
339
|
-
await fs.writeFile(targetMdPath, processedXml, 'utf8');
|
|
340
|
-
|
|
341
|
-
// Track the file
|
|
342
|
-
if (fileTrackingCallback) {
|
|
343
|
-
fileTrackingCallback(targetMdPath);
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
// Only show compilation details in verbose mode
|
|
347
|
-
if (process.env.BMAD_VERBOSE_INSTALL === 'true') {
|
|
348
|
-
await prompts.log.message(' Compiled agent: ' + agentName + ' -> ' + path.relative(targetAgentsPath, targetMdPath));
|
|
349
|
-
}
|
|
350
|
-
} catch (error) {
|
|
351
|
-
await prompts.log.warn(' Failed to compile agent ' + agentName + ': ' + error.message);
|
|
352
|
-
results.errors.push(`Failed to compile agent ${agentName}: ${error.message}`);
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
110
|
}
|
|
357
111
|
|
|
358
112
|
module.exports = { CustomHandler };
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
const path = require('node:path');
|
|
2
2
|
const fs = require('fs-extra');
|
|
3
|
-
const { XmlHandler } = require('../../../lib/xml-handler');
|
|
4
3
|
const prompts = require('../../../lib/prompts');
|
|
5
4
|
const { getSourcePath } = require('../../../lib/project-root');
|
|
6
5
|
const { BMAD_FOLDER_NAME } = require('./shared/path-utils');
|
|
@@ -18,7 +17,6 @@ class BaseIdeSetup {
|
|
|
18
17
|
this.rulesDir = null; // Override in subclasses
|
|
19
18
|
this.configFile = null; // Override in subclasses when detection is file-based
|
|
20
19
|
this.detectionPaths = []; // Additional paths that indicate the IDE is configured
|
|
21
|
-
this.xmlHandler = new XmlHandler();
|
|
22
20
|
this.bmadFolderName = BMAD_FOLDER_NAME; // Default, can be overridden
|
|
23
21
|
}
|
|
24
22
|
|
|
@@ -30,15 +28,6 @@ class BaseIdeSetup {
|
|
|
30
28
|
this.bmadFolderName = bmadFolderName;
|
|
31
29
|
}
|
|
32
30
|
|
|
33
|
-
/**
|
|
34
|
-
* Get the agent command activation header from the central template
|
|
35
|
-
* @returns {string} The activation header text
|
|
36
|
-
*/
|
|
37
|
-
async getAgentCommandHeader() {
|
|
38
|
-
const headerPath = getSourcePath('utility', 'agent-components', 'agent-command-header.md');
|
|
39
|
-
return await fs.readFile(headerPath, 'utf8');
|
|
40
|
-
}
|
|
41
|
-
|
|
42
31
|
/**
|
|
43
32
|
* Main setup method - must be implemented by subclasses
|
|
44
33
|
* @param {string} projectDir - Project directory
|
|
@@ -511,11 +500,6 @@ class BaseIdeSetup {
|
|
|
511
500
|
// Replace placeholders
|
|
512
501
|
let processed = content;
|
|
513
502
|
|
|
514
|
-
// Inject activation block for agent files FIRST (before replacements)
|
|
515
|
-
if (metadata.name && content.includes('<agent')) {
|
|
516
|
-
processed = this.xmlHandler.injectActivationSimple(processed, metadata);
|
|
517
|
-
}
|
|
518
|
-
|
|
519
503
|
// Only replace {project-root} if a specific projectDir is provided
|
|
520
504
|
// Otherwise leave the placeholder intact
|
|
521
505
|
// Note: Don't add trailing slash - paths in source include leading slash
|