bmad-method 6.0.0-alpha.18 → 6.0.0-alpha.19
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.
|
@@ -13,12 +13,13 @@ const { XmlHandler } = require('../../../lib/xml-handler');
|
|
|
13
13
|
const { DependencyResolver } = require('./dependency-resolver');
|
|
14
14
|
const { ConfigCollector } = require('./config-collector');
|
|
15
15
|
const { getProjectRoot, getSourcePath, getModulePath } = require('../../../lib/project-root');
|
|
16
|
-
const { AgentPartyGenerator } = require('../../../lib/agent-party-generator');
|
|
17
16
|
const { CLIUtils } = require('../../../lib/cli-utils');
|
|
18
17
|
const { ManifestGenerator } = require('./manifest-generator');
|
|
19
18
|
const { IdeConfigManager } = require('./ide-config-manager');
|
|
20
19
|
const { CustomHandler } = require('../custom/handler');
|
|
21
|
-
|
|
20
|
+
|
|
21
|
+
// BMAD installation folder name - this is constant and should never change
|
|
22
|
+
const BMAD_FOLDER_NAME = '_bmad';
|
|
22
23
|
|
|
23
24
|
class Installer {
|
|
24
25
|
constructor() {
|
|
@@ -34,58 +35,35 @@ class Installer {
|
|
|
34
35
|
this.ideConfigManager = new IdeConfigManager();
|
|
35
36
|
this.installedFiles = new Set(); // Track all installed files
|
|
36
37
|
this.ttsInjectedFiles = []; // Track files with TTS injection applied
|
|
38
|
+
this.bmadFolderName = BMAD_FOLDER_NAME;
|
|
37
39
|
}
|
|
38
40
|
|
|
39
41
|
/**
|
|
40
42
|
* Find the bmad installation directory in a project
|
|
41
|
-
*
|
|
43
|
+
* Always uses the standard _bmad folder name
|
|
42
44
|
* Also checks for legacy _cfg folder for migration
|
|
43
45
|
* @param {string} projectDir - Project directory
|
|
44
46
|
* @returns {Promise<Object>} { bmadDir: string, hasLegacyCfg: boolean }
|
|
45
47
|
*/
|
|
46
48
|
async findBmadDir(projectDir) {
|
|
49
|
+
const bmadDir = path.join(projectDir, BMAD_FOLDER_NAME);
|
|
50
|
+
|
|
47
51
|
// Check if project directory exists
|
|
48
52
|
if (!(await fs.pathExists(projectDir))) {
|
|
49
53
|
// Project doesn't exist yet, return default
|
|
50
|
-
return { bmadDir
|
|
54
|
+
return { bmadDir, hasLegacyCfg: false };
|
|
51
55
|
}
|
|
52
56
|
|
|
53
|
-
|
|
57
|
+
// Check for legacy _cfg folder if bmad directory exists
|
|
54
58
|
let hasLegacyCfg = false;
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
if (entry.isDirectory()) {
|
|
60
|
-
const bmadPath = path.join(projectDir, entry.name);
|
|
61
|
-
|
|
62
|
-
// Check for current _config folder
|
|
63
|
-
const manifestPath = path.join(bmadPath, '_config', 'manifest.yaml');
|
|
64
|
-
if (await fs.pathExists(manifestPath)) {
|
|
65
|
-
// Found a V6+ installation with current _config folder
|
|
66
|
-
return { bmadDir: bmadPath, hasLegacyCfg: false };
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// Check for legacy _cfg folder
|
|
70
|
-
const legacyManifestPath = path.join(bmadPath, '_cfg', 'manifest.yaml');
|
|
71
|
-
if (await fs.pathExists(legacyManifestPath)) {
|
|
72
|
-
bmadDir = bmadPath;
|
|
73
|
-
hasLegacyCfg = true;
|
|
74
|
-
}
|
|
75
|
-
}
|
|
59
|
+
if (await fs.pathExists(bmadDir)) {
|
|
60
|
+
const legacyCfgPath = path.join(bmadDir, '_cfg');
|
|
61
|
+
if (await fs.pathExists(legacyCfgPath)) {
|
|
62
|
+
hasLegacyCfg = true;
|
|
76
63
|
}
|
|
77
|
-
} catch {
|
|
78
|
-
console.log(chalk.red('Error reading project directory for BMAD installation detection'));
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// If we found a bmad directory (with or without legacy _cfg)
|
|
82
|
-
if (bmadDir) {
|
|
83
|
-
return { bmadDir, hasLegacyCfg };
|
|
84
64
|
}
|
|
85
65
|
|
|
86
|
-
|
|
87
|
-
// This will be used for new installations
|
|
88
|
-
return { bmadDir: path.join(projectDir, '_bmad'), hasLegacyCfg: false };
|
|
66
|
+
return { bmadDir, hasLegacyCfg };
|
|
89
67
|
}
|
|
90
68
|
|
|
91
69
|
/**
|
|
@@ -120,7 +98,7 @@ class Installer {
|
|
|
120
98
|
*
|
|
121
99
|
* 3. Document marker in instructions.md (if applicable)
|
|
122
100
|
*/
|
|
123
|
-
async copyFileWithPlaceholderReplacement(sourcePath, targetPath
|
|
101
|
+
async copyFileWithPlaceholderReplacement(sourcePath, targetPath) {
|
|
124
102
|
// List of text file extensions that should have placeholder replacement
|
|
125
103
|
const textExtensions = ['.md', '.yaml', '.yml', '.txt', '.json', '.js', '.ts', '.html', '.css', '.sh', '.bat', '.csv', '.xml'];
|
|
126
104
|
const ext = path.extname(sourcePath).toLowerCase();
|
|
@@ -285,7 +263,7 @@ class Installer {
|
|
|
285
263
|
// Check for already configured IDEs
|
|
286
264
|
const { Detector } = require('./detector');
|
|
287
265
|
const detector = new Detector();
|
|
288
|
-
const bmadDir = path.join(projectDir,
|
|
266
|
+
const bmadDir = path.join(projectDir, BMAD_FOLDER_NAME);
|
|
289
267
|
|
|
290
268
|
// During full reinstall, use the saved previous IDEs since bmad dir was deleted
|
|
291
269
|
// Otherwise detect from existing installation
|
|
@@ -532,18 +510,14 @@ class Installer {
|
|
|
532
510
|
}
|
|
533
511
|
}
|
|
534
512
|
|
|
535
|
-
// Always use _bmad as the folder name
|
|
536
|
-
const bmadFolderName = '_bmad';
|
|
537
|
-
this.bmadFolderName = bmadFolderName; // Store for use in other methods
|
|
538
|
-
|
|
539
513
|
// Store AgentVibes configuration for injection point processing
|
|
540
514
|
this.enableAgentVibes = config.enableAgentVibes || false;
|
|
541
515
|
|
|
542
516
|
// Set bmad folder name on module manager and IDE manager for placeholder replacement
|
|
543
|
-
this.moduleManager.setBmadFolderName(
|
|
517
|
+
this.moduleManager.setBmadFolderName(BMAD_FOLDER_NAME);
|
|
544
518
|
this.moduleManager.setCoreConfig(moduleConfigs.core || {});
|
|
545
519
|
this.moduleManager.setCustomModulePaths(customModulePaths);
|
|
546
|
-
this.ideManager.setBmadFolderName(
|
|
520
|
+
this.ideManager.setBmadFolderName(BMAD_FOLDER_NAME);
|
|
547
521
|
|
|
548
522
|
// Tool selection will be collected after we determine if it's a reinstall/update/new install
|
|
549
523
|
|
|
@@ -553,14 +527,8 @@ class Installer {
|
|
|
553
527
|
// Resolve target directory (path.resolve handles platform differences)
|
|
554
528
|
const projectDir = path.resolve(config.directory);
|
|
555
529
|
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
if (await fs.pathExists(projectDir)) {
|
|
560
|
-
const result = await this.findBmadDir(projectDir);
|
|
561
|
-
existingBmadDir = result.bmadDir;
|
|
562
|
-
existingBmadFolderName = path.basename(existingBmadDir);
|
|
563
|
-
}
|
|
530
|
+
// Always use the standard _bmad folder name
|
|
531
|
+
const bmadDir = path.join(projectDir, BMAD_FOLDER_NAME);
|
|
564
532
|
|
|
565
533
|
// Create a project directory if it doesn't exist (user already confirmed)
|
|
566
534
|
if (!(await fs.pathExists(projectDir))) {
|
|
@@ -582,8 +550,6 @@ class Installer {
|
|
|
582
550
|
}
|
|
583
551
|
}
|
|
584
552
|
|
|
585
|
-
const bmadDir = path.join(projectDir, bmadFolderName);
|
|
586
|
-
|
|
587
553
|
// Check existing installation
|
|
588
554
|
spinner.text = 'Checking for existing installation...';
|
|
589
555
|
const existingInstall = await this.detector.detect(bmadDir);
|
|
@@ -1606,7 +1572,7 @@ class Installer {
|
|
|
1606
1572
|
const targetPath = path.join(agentsDir, fileName);
|
|
1607
1573
|
|
|
1608
1574
|
if (await fs.pathExists(sourcePath)) {
|
|
1609
|
-
await this.copyFileWithPlaceholderReplacement(sourcePath, targetPath
|
|
1575
|
+
await this.copyFileWithPlaceholderReplacement(sourcePath, targetPath);
|
|
1610
1576
|
this.installedFiles.add(targetPath);
|
|
1611
1577
|
}
|
|
1612
1578
|
}
|
|
@@ -1622,7 +1588,7 @@ class Installer {
|
|
|
1622
1588
|
const targetPath = path.join(tasksDir, fileName);
|
|
1623
1589
|
|
|
1624
1590
|
if (await fs.pathExists(sourcePath)) {
|
|
1625
|
-
await this.copyFileWithPlaceholderReplacement(sourcePath, targetPath
|
|
1591
|
+
await this.copyFileWithPlaceholderReplacement(sourcePath, targetPath);
|
|
1626
1592
|
this.installedFiles.add(targetPath);
|
|
1627
1593
|
}
|
|
1628
1594
|
}
|
|
@@ -1638,7 +1604,7 @@ class Installer {
|
|
|
1638
1604
|
const targetPath = path.join(toolsDir, fileName);
|
|
1639
1605
|
|
|
1640
1606
|
if (await fs.pathExists(sourcePath)) {
|
|
1641
|
-
await this.copyFileWithPlaceholderReplacement(sourcePath, targetPath
|
|
1607
|
+
await this.copyFileWithPlaceholderReplacement(sourcePath, targetPath);
|
|
1642
1608
|
this.installedFiles.add(targetPath);
|
|
1643
1609
|
}
|
|
1644
1610
|
}
|
|
@@ -1654,7 +1620,7 @@ class Installer {
|
|
|
1654
1620
|
const targetPath = path.join(templatesDir, fileName);
|
|
1655
1621
|
|
|
1656
1622
|
if (await fs.pathExists(sourcePath)) {
|
|
1657
|
-
await this.copyFileWithPlaceholderReplacement(sourcePath, targetPath
|
|
1623
|
+
await this.copyFileWithPlaceholderReplacement(sourcePath, targetPath);
|
|
1658
1624
|
this.installedFiles.add(targetPath);
|
|
1659
1625
|
}
|
|
1660
1626
|
}
|
|
@@ -1669,7 +1635,7 @@ class Installer {
|
|
|
1669
1635
|
await fs.ensureDir(path.dirname(targetPath));
|
|
1670
1636
|
|
|
1671
1637
|
if (await fs.pathExists(dataPath)) {
|
|
1672
|
-
await this.copyFileWithPlaceholderReplacement(dataPath, targetPath
|
|
1638
|
+
await this.copyFileWithPlaceholderReplacement(dataPath, targetPath);
|
|
1673
1639
|
this.installedFiles.add(targetPath);
|
|
1674
1640
|
}
|
|
1675
1641
|
}
|
|
@@ -1759,14 +1725,9 @@ class Installer {
|
|
|
1759
1725
|
}
|
|
1760
1726
|
}
|
|
1761
1727
|
|
|
1762
|
-
//
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
await this.copyWorkflowYamlStripped(sourceFile, targetFile);
|
|
1766
|
-
} else {
|
|
1767
|
-
// Copy the file with placeholder replacement
|
|
1768
|
-
await this.copyFileWithPlaceholderReplacement(sourceFile, targetFile, this.bmadFolderName || 'bmad');
|
|
1769
|
-
}
|
|
1728
|
+
// Copy the file with placeholder replacement
|
|
1729
|
+
await fs.ensureDir(path.dirname(targetFile));
|
|
1730
|
+
await this.copyFileWithPlaceholderReplacement(sourceFile, targetFile);
|
|
1770
1731
|
|
|
1771
1732
|
// Track the installed file
|
|
1772
1733
|
this.installedFiles.add(targetFile);
|
|
@@ -1844,7 +1805,7 @@ class Installer {
|
|
|
1844
1805
|
if (!(await fs.pathExists(customizePath))) {
|
|
1845
1806
|
const genericTemplatePath = getSourcePath('utility', 'agent-components', 'agent.customize.template.yaml');
|
|
1846
1807
|
if (await fs.pathExists(genericTemplatePath)) {
|
|
1847
|
-
await this.copyFileWithPlaceholderReplacement(genericTemplatePath, customizePath
|
|
1808
|
+
await this.copyFileWithPlaceholderReplacement(genericTemplatePath, customizePath);
|
|
1848
1809
|
if (process.env.BMAD_VERBOSE_INSTALL === 'true') {
|
|
1849
1810
|
console.log(chalk.dim(` Created customize: ${moduleName}-${agentName}.customize.yaml`));
|
|
1850
1811
|
}
|
|
@@ -1853,235 +1814,6 @@ class Installer {
|
|
|
1853
1814
|
}
|
|
1854
1815
|
}
|
|
1855
1816
|
|
|
1856
|
-
/**
|
|
1857
|
-
* Build standalone agents in bmad/agents/ directory
|
|
1858
|
-
* @param {string} bmadDir - Path to bmad directory
|
|
1859
|
-
* @param {string} projectDir - Path to project directory
|
|
1860
|
-
*/
|
|
1861
|
-
async buildStandaloneAgents(bmadDir, projectDir) {
|
|
1862
|
-
const standaloneAgentsPath = path.join(bmadDir, 'agents');
|
|
1863
|
-
const cfgAgentsDir = path.join(bmadDir, '_config', 'agents');
|
|
1864
|
-
|
|
1865
|
-
// Check if standalone agents directory exists
|
|
1866
|
-
if (!(await fs.pathExists(standaloneAgentsPath))) {
|
|
1867
|
-
return;
|
|
1868
|
-
}
|
|
1869
|
-
|
|
1870
|
-
// Get all subdirectories in agents/
|
|
1871
|
-
const agentDirs = await fs.readdir(standaloneAgentsPath, { withFileTypes: true });
|
|
1872
|
-
|
|
1873
|
-
for (const agentDir of agentDirs) {
|
|
1874
|
-
if (!agentDir.isDirectory()) continue;
|
|
1875
|
-
|
|
1876
|
-
const agentDirPath = path.join(standaloneAgentsPath, agentDir.name);
|
|
1877
|
-
|
|
1878
|
-
// Find any .agent.yaml file in the directory
|
|
1879
|
-
const files = await fs.readdir(agentDirPath);
|
|
1880
|
-
const yamlFile = files.find((f) => f.endsWith('.agent.yaml'));
|
|
1881
|
-
|
|
1882
|
-
if (!yamlFile) continue;
|
|
1883
|
-
|
|
1884
|
-
const agentName = path.basename(yamlFile, '.agent.yaml');
|
|
1885
|
-
const sourceYamlPath = path.join(agentDirPath, yamlFile);
|
|
1886
|
-
const targetMdPath = path.join(agentDirPath, `${agentName}.md`);
|
|
1887
|
-
const customizePath = path.join(cfgAgentsDir, `${agentName}.customize.yaml`);
|
|
1888
|
-
|
|
1889
|
-
// Check for customizations
|
|
1890
|
-
const customizeExists = await fs.pathExists(customizePath);
|
|
1891
|
-
let customizedFields = [];
|
|
1892
|
-
|
|
1893
|
-
if (customizeExists) {
|
|
1894
|
-
const customizeContent = await fs.readFile(customizePath, 'utf8');
|
|
1895
|
-
const yaml = require('yaml');
|
|
1896
|
-
const customizeYaml = yaml.parse(customizeContent);
|
|
1897
|
-
|
|
1898
|
-
// Detect what fields are customized (similar to rebuildAgentFiles)
|
|
1899
|
-
if (customizeYaml) {
|
|
1900
|
-
if (customizeYaml.persona) {
|
|
1901
|
-
for (const [key, value] of Object.entries(customizeYaml.persona)) {
|
|
1902
|
-
if (value !== '' && value !== null && !(Array.isArray(value) && value.length === 0)) {
|
|
1903
|
-
customizedFields.push(`persona.${key}`);
|
|
1904
|
-
}
|
|
1905
|
-
}
|
|
1906
|
-
}
|
|
1907
|
-
if (customizeYaml.agent?.metadata) {
|
|
1908
|
-
for (const [key, value] of Object.entries(customizeYaml.agent.metadata)) {
|
|
1909
|
-
if (value !== '' && value !== null) {
|
|
1910
|
-
customizedFields.push(`metadata.${key}`);
|
|
1911
|
-
}
|
|
1912
|
-
}
|
|
1913
|
-
}
|
|
1914
|
-
if (customizeYaml.critical_actions && customizeYaml.critical_actions.length > 0) {
|
|
1915
|
-
customizedFields.push('critical_actions');
|
|
1916
|
-
}
|
|
1917
|
-
if (customizeYaml.menu && customizeYaml.menu.length > 0) {
|
|
1918
|
-
customizedFields.push('menu');
|
|
1919
|
-
}
|
|
1920
|
-
}
|
|
1921
|
-
}
|
|
1922
|
-
|
|
1923
|
-
// Build YAML to XML .md
|
|
1924
|
-
let xmlContent = await this.xmlHandler.buildFromYaml(sourceYamlPath, customizeExists ? customizePath : null, {
|
|
1925
|
-
includeMetadata: true,
|
|
1926
|
-
});
|
|
1927
|
-
|
|
1928
|
-
// DO NOT replace {project-root} - LLMs understand this placeholder at runtime
|
|
1929
|
-
// const processedContent = xmlContent.replaceAll('{project-root}', projectDir);
|
|
1930
|
-
|
|
1931
|
-
// Process TTS injection points (pass targetPath for tracking)
|
|
1932
|
-
xmlContent = this.processTTSInjectionPoints(xmlContent, targetMdPath);
|
|
1933
|
-
|
|
1934
|
-
// Write the built .md file with POSIX-compliant final newline
|
|
1935
|
-
const content = xmlContent.endsWith('\n') ? xmlContent : xmlContent + '\n';
|
|
1936
|
-
await fs.writeFile(targetMdPath, content, 'utf8');
|
|
1937
|
-
|
|
1938
|
-
// Display result
|
|
1939
|
-
if (customizedFields.length > 0) {
|
|
1940
|
-
console.log(chalk.dim(` Built standalone agent: ${agentName}.md `) + chalk.yellow(`(customized: ${customizedFields.join(', ')})`));
|
|
1941
|
-
} else {
|
|
1942
|
-
console.log(chalk.dim(` Built standalone agent: ${agentName}.md`));
|
|
1943
|
-
}
|
|
1944
|
-
}
|
|
1945
|
-
}
|
|
1946
|
-
|
|
1947
|
-
/**
|
|
1948
|
-
* Rebuild agent files from installer source (for compile command)
|
|
1949
|
-
* @param {string} modulePath - Path to module in bmad/ installation
|
|
1950
|
-
* @param {string} moduleName - Module name
|
|
1951
|
-
*/
|
|
1952
|
-
async rebuildAgentFiles(modulePath, moduleName) {
|
|
1953
|
-
// Get source agents directory from installer
|
|
1954
|
-
const sourceAgentsPath =
|
|
1955
|
-
moduleName === 'core' ? path.join(getModulePath('core'), 'agents') : path.join(getSourcePath(`modules/${moduleName}`), 'agents');
|
|
1956
|
-
|
|
1957
|
-
if (!(await fs.pathExists(sourceAgentsPath))) {
|
|
1958
|
-
return; // No source agents to rebuild
|
|
1959
|
-
}
|
|
1960
|
-
|
|
1961
|
-
// Determine project directory (parent of bmad/ directory)
|
|
1962
|
-
const bmadDir = path.dirname(modulePath);
|
|
1963
|
-
const projectDir = path.dirname(bmadDir);
|
|
1964
|
-
const cfgAgentsDir = path.join(bmadDir, '_config', 'agents');
|
|
1965
|
-
const targetAgentsPath = path.join(modulePath, 'agents');
|
|
1966
|
-
|
|
1967
|
-
// Ensure target directory exists
|
|
1968
|
-
await fs.ensureDir(targetAgentsPath);
|
|
1969
|
-
|
|
1970
|
-
// Get all YAML agent files from source
|
|
1971
|
-
const sourceFiles = await fs.readdir(sourceAgentsPath);
|
|
1972
|
-
|
|
1973
|
-
for (const file of sourceFiles) {
|
|
1974
|
-
if (file.endsWith('.agent.yaml')) {
|
|
1975
|
-
const agentName = file.replace('.agent.yaml', '');
|
|
1976
|
-
const sourceYamlPath = path.join(sourceAgentsPath, file);
|
|
1977
|
-
const targetMdPath = path.join(targetAgentsPath, `${agentName}.md`);
|
|
1978
|
-
const customizePath = path.join(cfgAgentsDir, `${moduleName}-${agentName}.customize.yaml`);
|
|
1979
|
-
|
|
1980
|
-
// Check for customizations
|
|
1981
|
-
const customizeExists = await fs.pathExists(customizePath);
|
|
1982
|
-
let customizedFields = [];
|
|
1983
|
-
|
|
1984
|
-
if (customizeExists) {
|
|
1985
|
-
const customizeContent = await fs.readFile(customizePath, 'utf8');
|
|
1986
|
-
const yaml = require('yaml');
|
|
1987
|
-
const customizeYaml = yaml.parse(customizeContent);
|
|
1988
|
-
|
|
1989
|
-
// Detect what fields are customized
|
|
1990
|
-
if (customizeYaml) {
|
|
1991
|
-
if (customizeYaml.persona) {
|
|
1992
|
-
for (const [key, value] of Object.entries(customizeYaml.persona)) {
|
|
1993
|
-
if (value !== '' && value !== null && !(Array.isArray(value) && value.length === 0)) {
|
|
1994
|
-
customizedFields.push(`persona.${key}`);
|
|
1995
|
-
}
|
|
1996
|
-
}
|
|
1997
|
-
}
|
|
1998
|
-
if (customizeYaml.agent?.metadata) {
|
|
1999
|
-
for (const [key, value] of Object.entries(customizeYaml.agent.metadata)) {
|
|
2000
|
-
if (value !== '' && value !== null) {
|
|
2001
|
-
customizedFields.push(`metadata.${key}`);
|
|
2002
|
-
}
|
|
2003
|
-
}
|
|
2004
|
-
}
|
|
2005
|
-
if (customizeYaml.critical_actions && customizeYaml.critical_actions.length > 0) {
|
|
2006
|
-
customizedFields.push('critical_actions');
|
|
2007
|
-
}
|
|
2008
|
-
if (customizeYaml.memories && customizeYaml.memories.length > 0) {
|
|
2009
|
-
customizedFields.push('memories');
|
|
2010
|
-
}
|
|
2011
|
-
if (customizeYaml.menu && customizeYaml.menu.length > 0) {
|
|
2012
|
-
customizedFields.push('menu');
|
|
2013
|
-
}
|
|
2014
|
-
if (customizeYaml.prompts && customizeYaml.prompts.length > 0) {
|
|
2015
|
-
customizedFields.push('prompts');
|
|
2016
|
-
}
|
|
2017
|
-
}
|
|
2018
|
-
}
|
|
2019
|
-
|
|
2020
|
-
// Read the YAML content
|
|
2021
|
-
const yamlContent = await fs.readFile(sourceYamlPath, 'utf8');
|
|
2022
|
-
|
|
2023
|
-
// Read customize content if exists
|
|
2024
|
-
let customizeData = {};
|
|
2025
|
-
if (customizeExists) {
|
|
2026
|
-
const customizeContent = await fs.readFile(customizePath, 'utf8');
|
|
2027
|
-
const yaml = require('yaml');
|
|
2028
|
-
customizeData = yaml.parse(customizeContent);
|
|
2029
|
-
}
|
|
2030
|
-
|
|
2031
|
-
// Build agent answers from customize data (filter empty values)
|
|
2032
|
-
const answers = {};
|
|
2033
|
-
if (customizeData.persona) {
|
|
2034
|
-
Object.assign(answers, filterCustomizationData(customizeData.persona));
|
|
2035
|
-
}
|
|
2036
|
-
if (customizeData.agent?.metadata) {
|
|
2037
|
-
const filteredMetadata = filterCustomizationData(customizeData.agent.metadata);
|
|
2038
|
-
if (Object.keys(filteredMetadata).length > 0) {
|
|
2039
|
-
Object.assign(answers, { metadata: filteredMetadata });
|
|
2040
|
-
}
|
|
2041
|
-
}
|
|
2042
|
-
if (customizeData.critical_actions && customizeData.critical_actions.length > 0) {
|
|
2043
|
-
answers.critical_actions = customizeData.critical_actions;
|
|
2044
|
-
}
|
|
2045
|
-
if (customizeData.memories && customizeData.memories.length > 0) {
|
|
2046
|
-
answers.memories = customizeData.memories;
|
|
2047
|
-
}
|
|
2048
|
-
|
|
2049
|
-
const coreConfigPath = path.join(bmadDir, 'core', 'config.yaml');
|
|
2050
|
-
let coreConfig = {};
|
|
2051
|
-
if (await fs.pathExists(coreConfigPath)) {
|
|
2052
|
-
const yaml = require('yaml');
|
|
2053
|
-
const coreConfigContent = await fs.readFile(coreConfigPath, 'utf8');
|
|
2054
|
-
coreConfig = yaml.parse(coreConfigContent);
|
|
2055
|
-
}
|
|
2056
|
-
|
|
2057
|
-
// Compile using the same compiler as initial installation
|
|
2058
|
-
const { compileAgent } = require('../../../lib/agent/compiler');
|
|
2059
|
-
const result = await compileAgent(yamlContent, answers, agentName, path.relative(bmadDir, targetMdPath), {
|
|
2060
|
-
config: coreConfig,
|
|
2061
|
-
});
|
|
2062
|
-
|
|
2063
|
-
// Check if compilation succeeded
|
|
2064
|
-
if (!result || !result.xml) {
|
|
2065
|
-
throw new Error(`Failed to compile agent ${agentName}: No XML returned from compiler`);
|
|
2066
|
-
}
|
|
2067
|
-
|
|
2068
|
-
// Replace _bmad with actual folder name if needed
|
|
2069
|
-
const finalXml = result.xml.replaceAll('_bmad', path.basename(bmadDir));
|
|
2070
|
-
|
|
2071
|
-
// Write the rebuilt .md file with POSIX-compliant final newline
|
|
2072
|
-
const content = finalXml.endsWith('\n') ? finalXml : finalXml + '\n';
|
|
2073
|
-
await fs.writeFile(targetMdPath, content, 'utf8');
|
|
2074
|
-
|
|
2075
|
-
// Display result with customizations if any
|
|
2076
|
-
if (customizedFields.length > 0) {
|
|
2077
|
-
console.log(chalk.dim(` Rebuilt agent: ${agentName}.md `) + chalk.yellow(`(customized: ${customizedFields.join(', ')})`));
|
|
2078
|
-
} else {
|
|
2079
|
-
console.log(chalk.dim(` Rebuilt agent: ${agentName}.md`));
|
|
2080
|
-
}
|
|
2081
|
-
}
|
|
2082
|
-
}
|
|
2083
|
-
}
|
|
2084
|
-
|
|
2085
1817
|
/**
|
|
2086
1818
|
* Private: Update core
|
|
2087
1819
|
*/
|
|
@@ -2677,190 +2409,6 @@ class Installer {
|
|
|
2677
2409
|
return { customFiles, modifiedFiles };
|
|
2678
2410
|
}
|
|
2679
2411
|
|
|
2680
|
-
/**
|
|
2681
|
-
* Private: Create agent configuration files
|
|
2682
|
-
* @param {string} bmadDir - BMAD installation directory
|
|
2683
|
-
* @param {Object} userInfo - User information including name and language
|
|
2684
|
-
*/
|
|
2685
|
-
async createAgentConfigs(bmadDir, userInfo = null) {
|
|
2686
|
-
const agentConfigDir = path.join(bmadDir, '_config', 'agents');
|
|
2687
|
-
await fs.ensureDir(agentConfigDir);
|
|
2688
|
-
|
|
2689
|
-
// Get all agents from all modules
|
|
2690
|
-
const agents = [];
|
|
2691
|
-
const agentDetails = []; // For manifest generation
|
|
2692
|
-
|
|
2693
|
-
// Check modules for agents (including core)
|
|
2694
|
-
const entries = await fs.readdir(bmadDir, { withFileTypes: true });
|
|
2695
|
-
for (const entry of entries) {
|
|
2696
|
-
if (entry.isDirectory() && entry.name !== '_config') {
|
|
2697
|
-
const moduleAgentsPath = path.join(bmadDir, entry.name, 'agents');
|
|
2698
|
-
if (await fs.pathExists(moduleAgentsPath)) {
|
|
2699
|
-
const agentFiles = await fs.readdir(moduleAgentsPath);
|
|
2700
|
-
for (const agentFile of agentFiles) {
|
|
2701
|
-
if (agentFile.endsWith('.md')) {
|
|
2702
|
-
const agentPath = path.join(moduleAgentsPath, agentFile);
|
|
2703
|
-
const agentContent = await fs.readFile(agentPath, 'utf8');
|
|
2704
|
-
|
|
2705
|
-
// Skip agents with localskip="true"
|
|
2706
|
-
const hasLocalSkip = agentContent.match(/<agent[^>]*\slocalskip="true"[^>]*>/);
|
|
2707
|
-
if (hasLocalSkip) {
|
|
2708
|
-
continue; // Skip this agent - it should not have been installed
|
|
2709
|
-
}
|
|
2710
|
-
|
|
2711
|
-
const agentName = path.basename(agentFile, '.md');
|
|
2712
|
-
|
|
2713
|
-
// Extract any nodes with agentConfig="true"
|
|
2714
|
-
const agentConfigNodes = this.extractAgentConfigNodes(agentContent);
|
|
2715
|
-
|
|
2716
|
-
agents.push({
|
|
2717
|
-
name: agentName,
|
|
2718
|
-
module: entry.name,
|
|
2719
|
-
agentConfigNodes: agentConfigNodes,
|
|
2720
|
-
});
|
|
2721
|
-
|
|
2722
|
-
// Use shared AgentPartyGenerator to extract details
|
|
2723
|
-
let details = AgentPartyGenerator.extractAgentDetails(agentContent, entry.name, agentName);
|
|
2724
|
-
|
|
2725
|
-
// Apply config overrides if they exist
|
|
2726
|
-
if (details) {
|
|
2727
|
-
const configPath = path.join(agentConfigDir, `${entry.name}-${agentName}.md`);
|
|
2728
|
-
if (await fs.pathExists(configPath)) {
|
|
2729
|
-
const configContent = await fs.readFile(configPath, 'utf8');
|
|
2730
|
-
details = AgentPartyGenerator.applyConfigOverrides(details, configContent);
|
|
2731
|
-
}
|
|
2732
|
-
agentDetails.push(details);
|
|
2733
|
-
}
|
|
2734
|
-
}
|
|
2735
|
-
}
|
|
2736
|
-
}
|
|
2737
|
-
}
|
|
2738
|
-
}
|
|
2739
|
-
|
|
2740
|
-
// Create config file for each agent
|
|
2741
|
-
let createdCount = 0;
|
|
2742
|
-
let skippedCount = 0;
|
|
2743
|
-
|
|
2744
|
-
// Load agent config template
|
|
2745
|
-
const templatePath = getSourcePath('utility', 'models', 'agent-config-template.md');
|
|
2746
|
-
const templateContent = await fs.readFile(templatePath, 'utf8');
|
|
2747
|
-
|
|
2748
|
-
for (const agent of agents) {
|
|
2749
|
-
const configPath = path.join(agentConfigDir, `${agent.module}-${agent.name}.md`);
|
|
2750
|
-
|
|
2751
|
-
// Skip if config file already exists (preserve custom configurations)
|
|
2752
|
-
if (await fs.pathExists(configPath)) {
|
|
2753
|
-
skippedCount++;
|
|
2754
|
-
continue;
|
|
2755
|
-
}
|
|
2756
|
-
|
|
2757
|
-
// Build config content header
|
|
2758
|
-
let configContent = `# Agent Config: ${agent.name}\n\n`;
|
|
2759
|
-
|
|
2760
|
-
// Process template and add agent-specific config nodes
|
|
2761
|
-
let processedTemplate = templateContent;
|
|
2762
|
-
|
|
2763
|
-
// Replace {core:user_name} placeholder with actual user name if available
|
|
2764
|
-
if (userInfo && userInfo.userName) {
|
|
2765
|
-
processedTemplate = processedTemplate.replaceAll('{core:user_name}', userInfo.userName);
|
|
2766
|
-
}
|
|
2767
|
-
|
|
2768
|
-
// Replace {core:communication_language} placeholder with actual language if available
|
|
2769
|
-
if (userInfo && userInfo.responseLanguage) {
|
|
2770
|
-
processedTemplate = processedTemplate.replaceAll('{core:communication_language}', userInfo.responseLanguage);
|
|
2771
|
-
}
|
|
2772
|
-
|
|
2773
|
-
// If this agent has agentConfig nodes, add them after the existing comment
|
|
2774
|
-
if (agent.agentConfigNodes && agent.agentConfigNodes.length > 0) {
|
|
2775
|
-
// Find the agent-specific configuration nodes comment
|
|
2776
|
-
const commentPattern = /(\s*<!-- Agent-specific configuration nodes -->)/;
|
|
2777
|
-
const commentMatch = processedTemplate.match(commentPattern);
|
|
2778
|
-
|
|
2779
|
-
if (commentMatch) {
|
|
2780
|
-
// Add nodes right after the comment
|
|
2781
|
-
let agentSpecificNodes = '';
|
|
2782
|
-
for (const node of agent.agentConfigNodes) {
|
|
2783
|
-
agentSpecificNodes += `\n ${node}`;
|
|
2784
|
-
}
|
|
2785
|
-
|
|
2786
|
-
processedTemplate = processedTemplate.replace(commentPattern, `$1${agentSpecificNodes}`);
|
|
2787
|
-
}
|
|
2788
|
-
}
|
|
2789
|
-
|
|
2790
|
-
configContent += processedTemplate;
|
|
2791
|
-
|
|
2792
|
-
// Ensure POSIX-compliant final newline
|
|
2793
|
-
if (!configContent.endsWith('\n')) {
|
|
2794
|
-
configContent += '\n';
|
|
2795
|
-
}
|
|
2796
|
-
|
|
2797
|
-
await fs.writeFile(configPath, configContent, 'utf8');
|
|
2798
|
-
this.installedFiles.add(configPath); // Track agent config files
|
|
2799
|
-
createdCount++;
|
|
2800
|
-
}
|
|
2801
|
-
|
|
2802
|
-
// Generate agent manifest with overrides applied
|
|
2803
|
-
await this.generateAgentManifest(bmadDir, agentDetails);
|
|
2804
|
-
|
|
2805
|
-
return { total: agents.length, created: createdCount, skipped: skippedCount };
|
|
2806
|
-
}
|
|
2807
|
-
|
|
2808
|
-
/**
|
|
2809
|
-
* Generate agent manifest XML file
|
|
2810
|
-
* @param {string} bmadDir - BMAD installation directory
|
|
2811
|
-
* @param {Array} agentDetails - Array of agent details
|
|
2812
|
-
*/
|
|
2813
|
-
async generateAgentManifest(bmadDir, agentDetails) {
|
|
2814
|
-
const manifestPath = path.join(bmadDir, '_config', 'agent-manifest.csv');
|
|
2815
|
-
await AgentPartyGenerator.writeAgentParty(manifestPath, agentDetails, { forWeb: false });
|
|
2816
|
-
}
|
|
2817
|
-
|
|
2818
|
-
/**
|
|
2819
|
-
* Extract nodes with agentConfig="true" from agent content
|
|
2820
|
-
* @param {string} content - Agent file content
|
|
2821
|
-
* @returns {Array} Array of XML nodes that should be added to agent config
|
|
2822
|
-
*/
|
|
2823
|
-
extractAgentConfigNodes(content) {
|
|
2824
|
-
const nodes = [];
|
|
2825
|
-
|
|
2826
|
-
try {
|
|
2827
|
-
// Find all XML nodes with agentConfig="true"
|
|
2828
|
-
// Match self-closing tags and tags with content
|
|
2829
|
-
const selfClosingPattern = /<([a-zA-Z][a-zA-Z0-9_-]*)\s+[^>]*agentConfig="true"[^>]*\/>/g;
|
|
2830
|
-
const withContentPattern = /<([a-zA-Z][a-zA-Z0-9_-]*)\s+[^>]*agentConfig="true"[^>]*>([\s\S]*?)<\/\1>/g;
|
|
2831
|
-
|
|
2832
|
-
// Extract self-closing tags
|
|
2833
|
-
let match;
|
|
2834
|
-
while ((match = selfClosingPattern.exec(content)) !== null) {
|
|
2835
|
-
// Extract just the tag without children (structure only)
|
|
2836
|
-
const tagMatch = match[0].match(/<([a-zA-Z][a-zA-Z0-9_-]*)([^>]*)\/>/);
|
|
2837
|
-
if (tagMatch) {
|
|
2838
|
-
const tagName = tagMatch[1];
|
|
2839
|
-
const attributes = tagMatch[2].replace(/\s*agentConfig="true"/, ''); // Remove agentConfig attribute
|
|
2840
|
-
nodes.push(`<${tagName}${attributes}></${tagName}>`);
|
|
2841
|
-
}
|
|
2842
|
-
}
|
|
2843
|
-
|
|
2844
|
-
// Extract tags with content
|
|
2845
|
-
while ((match = withContentPattern.exec(content)) !== null) {
|
|
2846
|
-
const fullMatch = match[0];
|
|
2847
|
-
const tagName = match[1];
|
|
2848
|
-
|
|
2849
|
-
// Extract opening tag with attributes (removing agentConfig="true")
|
|
2850
|
-
const openingTagMatch = fullMatch.match(new RegExp(`<${tagName}([^>]*)>`));
|
|
2851
|
-
if (openingTagMatch) {
|
|
2852
|
-
const attributes = openingTagMatch[1].replace(/\s*agentConfig="true"/, '');
|
|
2853
|
-
// Add empty node structure (no children)
|
|
2854
|
-
nodes.push(`<${tagName}${attributes}></${tagName}>`);
|
|
2855
|
-
}
|
|
2856
|
-
}
|
|
2857
|
-
} catch (error) {
|
|
2858
|
-
console.error('Error extracting agentConfig nodes:', error);
|
|
2859
|
-
}
|
|
2860
|
-
|
|
2861
|
-
return nodes;
|
|
2862
|
-
}
|
|
2863
|
-
|
|
2864
2412
|
/**
|
|
2865
2413
|
* Handle missing custom module sources interactively
|
|
2866
2414
|
* @param {Map} customModuleSources - Map of custom module ID to info
|
|
@@ -2999,7 +2547,7 @@ class Installer {
|
|
|
2999
2547
|
await this.manifest.addCustomModule(bmadDir, missing.info);
|
|
3000
2548
|
|
|
3001
2549
|
validCustomModules.push({
|
|
3002
|
-
id:
|
|
2550
|
+
id: missing.id,
|
|
3003
2551
|
name: missing.name,
|
|
3004
2552
|
path: resolvedPath,
|
|
3005
2553
|
info: missing.info,
|
|
@@ -3013,7 +2561,7 @@ class Installer {
|
|
|
3013
2561
|
case 'remove': {
|
|
3014
2562
|
// Extra confirmation for destructive remove
|
|
3015
2563
|
console.log(chalk.red.bold(`\n⚠️ WARNING: This will PERMANENTLY DELETE "${missing.name}" and all its files!`));
|
|
3016
|
-
console.log(chalk.red(` Module location: ${path.join(bmadDir,
|
|
2564
|
+
console.log(chalk.red(` Module location: ${path.join(bmadDir, missing.id)}`));
|
|
3017
2565
|
|
|
3018
2566
|
const { confirm } = await inquirer.prompt([
|
|
3019
2567
|
{
|
|
@@ -731,7 +731,7 @@ class ModuleManager {
|
|
|
731
731
|
async compileModuleAgents(sourcePath, targetPath, moduleName, bmadDir, installer = null) {
|
|
732
732
|
const sourceAgentsPath = path.join(sourcePath, 'agents');
|
|
733
733
|
const targetAgentsPath = path.join(targetPath, 'agents');
|
|
734
|
-
const cfgAgentsDir = path.join(bmadDir, '
|
|
734
|
+
const cfgAgentsDir = path.join(bmadDir, '_config', 'agents');
|
|
735
735
|
|
|
736
736
|
// Check if agents directory exists in source
|
|
737
737
|
if (!(await fs.pathExists(sourceAgentsPath))) {
|