bmad-method 6.3.1-next.2 → 6.3.1-next.4

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.
Files changed (36) hide show
  1. package/package.json +1 -1
  2. package/tools/installer/cli-utils.js +0 -137
  3. package/tools/installer/core/manifest.js +0 -577
  4. package/tools/installer/ide/shared/path-utils.js +0 -145
  5. package/tools/installer/modules/custom-module-manager.js +0 -27
  6. package/tools/installer/modules/external-manager.js +0 -40
  7. package/tools/installer/modules/official-modules.js +2 -50
  8. package/tools/installer/modules/registry-client.js +0 -11
  9. package/tools/installer/prompts.js +0 -106
  10. package/tools/installer/ide/shared/agent-command-generator.js +0 -180
  11. package/tools/installer/ide/shared/bmad-artifacts.js +0 -208
  12. package/tools/installer/ide/shared/module-injections.js +0 -136
  13. package/tools/installer/ide/templates/agent-command-template.md +0 -14
  14. package/tools/installer/ide/templates/combined/antigravity.md +0 -8
  15. package/tools/installer/ide/templates/combined/default-agent.md +0 -15
  16. package/tools/installer/ide/templates/combined/default-task.md +0 -10
  17. package/tools/installer/ide/templates/combined/default-tool.md +0 -10
  18. package/tools/installer/ide/templates/combined/default-workflow.md +0 -6
  19. package/tools/installer/ide/templates/combined/gemini-agent.toml +0 -14
  20. package/tools/installer/ide/templates/combined/gemini-task.toml +0 -11
  21. package/tools/installer/ide/templates/combined/gemini-tool.toml +0 -11
  22. package/tools/installer/ide/templates/combined/gemini-workflow-yaml.toml +0 -16
  23. package/tools/installer/ide/templates/combined/gemini-workflow.toml +0 -14
  24. package/tools/installer/ide/templates/combined/kiro-agent.md +0 -16
  25. package/tools/installer/ide/templates/combined/kiro-task.md +0 -9
  26. package/tools/installer/ide/templates/combined/kiro-tool.md +0 -9
  27. package/tools/installer/ide/templates/combined/kiro-workflow.md +0 -7
  28. package/tools/installer/ide/templates/combined/opencode-agent.md +0 -15
  29. package/tools/installer/ide/templates/combined/opencode-task.md +0 -13
  30. package/tools/installer/ide/templates/combined/opencode-tool.md +0 -13
  31. package/tools/installer/ide/templates/combined/opencode-workflow-yaml.md +0 -16
  32. package/tools/installer/ide/templates/combined/opencode-workflow.md +0 -16
  33. package/tools/installer/ide/templates/combined/rovodev.md +0 -9
  34. package/tools/installer/ide/templates/combined/trae.md +0 -9
  35. package/tools/installer/ide/templates/combined/windsurf-workflow.md +0 -10
  36. package/tools/installer/ide/templates/split/.gitkeep +0 -0
@@ -15,8 +15,6 @@
15
15
  * - standalone/agents/fred.md → bmad-agent-standalone-fred.md
16
16
  */
17
17
 
18
- // Type segments - agents are included in naming, others are filtered out
19
- const TYPE_SEGMENTS = ['workflows', 'tasks', 'tools'];
20
18
  const AGENT_SEGMENT = 'agents';
21
19
 
22
20
  // BMAD installation folder name - centralized constant for all installers
@@ -194,125 +192,6 @@ function parseDashName(filename) {
194
192
  };
195
193
  }
196
194
 
197
- // ============================================================================
198
- // LEGACY FUNCTIONS (underscore format) - kept for backward compatibility
199
- // ============================================================================
200
-
201
- /**
202
- * Convert hierarchical path to flat underscore-separated name (LEGACY)
203
- * @deprecated Use toDashName instead
204
- */
205
- function toUnderscoreName(module, type, name) {
206
- const isAgent = type === AGENT_SEGMENT;
207
- if (module === 'core') {
208
- return isAgent ? `bmad_agent_${name}.md` : `bmad_${name}.md`;
209
- }
210
- if (module === 'standalone') {
211
- return isAgent ? `bmad_agent_standalone_${name}.md` : `bmad_standalone_${name}.md`;
212
- }
213
- return isAgent ? `bmad_${module}_agent_${name}.md` : `bmad_${module}_${name}.md`;
214
- }
215
-
216
- /**
217
- * Convert relative path to flat underscore-separated name (LEGACY)
218
- * @deprecated Use toDashPath instead
219
- */
220
- function toUnderscorePath(relativePath) {
221
- // Strip common file extensions (same as toDashPath for consistency)
222
- const withoutExt = relativePath.replace(/\.(md|yaml|yml|json|xml|toml)$/i, '');
223
- const parts = withoutExt.split(/[/\\]/);
224
-
225
- const module = parts[0];
226
- const type = parts[1];
227
- const name = parts.slice(2).join('_');
228
-
229
- return toUnderscoreName(module, type, name);
230
- }
231
-
232
- /**
233
- * Create custom agent underscore name (LEGACY)
234
- * @deprecated Use customAgentDashName instead
235
- */
236
- function customAgentUnderscoreName(agentName) {
237
- return `bmad_custom_${agentName}.md`;
238
- }
239
-
240
- /**
241
- * Check if a filename uses underscore format (LEGACY)
242
- * @deprecated Use isDashFormat instead
243
- */
244
- function isUnderscoreFormat(filename) {
245
- return filename.startsWith('bmad_') && filename.includes('_');
246
- }
247
-
248
- /**
249
- * Extract parts from an underscore-formatted filename (LEGACY)
250
- * @deprecated Use parseDashName instead
251
- */
252
- function parseUnderscoreName(filename) {
253
- const withoutExt = filename.replace('.md', '');
254
- const parts = withoutExt.split('_');
255
-
256
- if (parts.length < 2 || parts[0] !== 'bmad') {
257
- return null;
258
- }
259
-
260
- const agentIndex = parts.indexOf('agent');
261
-
262
- if (agentIndex !== -1) {
263
- if (agentIndex === 1) {
264
- // bmad_agent_... - check for standalone
265
- if (parts.length >= 4 && parts[2] === 'standalone') {
266
- return {
267
- prefix: parts[0],
268
- module: 'standalone',
269
- type: 'agents',
270
- name: parts.slice(3).join('_'),
271
- };
272
- }
273
- return {
274
- prefix: parts[0],
275
- module: 'core',
276
- type: 'agents',
277
- name: parts.slice(agentIndex + 1).join('_'),
278
- };
279
- } else {
280
- return {
281
- prefix: parts[0],
282
- module: parts[1],
283
- type: 'agents',
284
- name: parts.slice(agentIndex + 1).join('_'),
285
- };
286
- }
287
- }
288
-
289
- if (parts.length === 2) {
290
- return {
291
- prefix: parts[0],
292
- module: 'core',
293
- type: 'workflows',
294
- name: parts[1],
295
- };
296
- }
297
-
298
- // Check for standalone non-agent: bmad_standalone_name
299
- if (parts[1] === 'standalone') {
300
- return {
301
- prefix: parts[0],
302
- module: 'standalone',
303
- type: 'workflows',
304
- name: parts.slice(2).join('_'),
305
- };
306
- }
307
-
308
- return {
309
- prefix: parts[0],
310
- module: parts[1],
311
- type: 'workflows',
312
- name: parts.slice(2).join('_'),
313
- };
314
- }
315
-
316
195
  /**
317
196
  * Resolve the skill name for an artifact.
318
197
  * Prefers canonicalId from a bmad-skill-manifest.yaml sidecar when available,
@@ -328,37 +207,13 @@ function resolveSkillName(artifact) {
328
207
  return toDashPath(artifact.relativePath);
329
208
  }
330
209
 
331
- // Backward compatibility aliases (colon format was same as underscore)
332
- const toColonName = toUnderscoreName;
333
- const toColonPath = toUnderscorePath;
334
- const customAgentColonName = customAgentUnderscoreName;
335
- const isColonFormat = isUnderscoreFormat;
336
- const parseColonName = parseUnderscoreName;
337
-
338
210
  module.exports = {
339
- // New standard (dash-based)
340
211
  toDashName,
341
212
  toDashPath,
342
213
  resolveSkillName,
343
214
  customAgentDashName,
344
215
  isDashFormat,
345
216
  parseDashName,
346
-
347
- // Legacy (underscore-based) - kept for backward compatibility
348
- toUnderscoreName,
349
- toUnderscorePath,
350
- customAgentUnderscoreName,
351
- isUnderscoreFormat,
352
- parseUnderscoreName,
353
-
354
- // Backward compatibility aliases
355
- toColonName,
356
- toColonPath,
357
- customAgentColonName,
358
- isColonFormat,
359
- parseColonName,
360
-
361
- TYPE_SEGMENTS,
362
217
  AGENT_SEGMENT,
363
218
  BMAD_FOLDER_NAME,
364
219
  };
@@ -155,33 +155,6 @@ class CustomModuleManager {
155
155
  };
156
156
  }
157
157
 
158
- /**
159
- * @deprecated Use parseSource() instead. Kept for backward compatibility.
160
- * Parse and validate a GitHub repository URL.
161
- * @param {string} url - GitHub URL to validate
162
- * @returns {Object} { owner, repo, isValid, error }
163
- */
164
- validateGitHubUrl(url) {
165
- if (!url || typeof url !== 'string') {
166
- return { owner: null, repo: null, isValid: false, error: 'URL is required' };
167
- }
168
- const trimmed = url.trim();
169
-
170
- // HTTPS format: https://github.com/owner/repo[.git] (strict, no trailing path)
171
- const httpsMatch = trimmed.match(/^https?:\/\/github\.com\/([^/]+)\/([^/.]+?)(?:\.git)?$/);
172
- if (httpsMatch) {
173
- return { owner: httpsMatch[1], repo: httpsMatch[2], isValid: true, error: null };
174
- }
175
-
176
- // SSH format: git@github.com:owner/repo[.git]
177
- const sshMatch = trimmed.match(/^git@github\.com:([^/]+)\/([^/.]+?)(?:\.git)?$/);
178
- if (sshMatch) {
179
- return { owner: sshMatch[1], repo: sshMatch[2], isValid: true, error: null };
180
- }
181
-
182
- return { owner: null, repo: null, isValid: false, error: 'Not a valid GitHub URL (expected https://github.com/owner/repo)' };
183
- }
184
-
185
158
  // ─── Marketplace JSON ─────────────────────────────────────────────────────
186
159
 
187
160
  /**
@@ -109,46 +109,6 @@ class ExternalModuleManager {
109
109
  return modules.find((m) => m.code === code) || null;
110
110
  }
111
111
 
112
- /**
113
- * Get module info by key
114
- * @param {string} key - The module key (e.g., 'bmad-creative-intelligence-suite')
115
- * @returns {Object|null} Module info or null if not found
116
- */
117
- async getModuleByKey(key) {
118
- const modules = await this.listAvailable();
119
- return modules.find((m) => m.key === key) || null;
120
- }
121
-
122
- /**
123
- * Check if a module code exists in external modules
124
- * @param {string} code - The module code to check
125
- * @returns {boolean} True if the module exists
126
- */
127
- async hasModule(code) {
128
- const module = await this.getModuleByCode(code);
129
- return module !== null;
130
- }
131
-
132
- /**
133
- * Get the URL for a module by code
134
- * @param {string} code - The module code
135
- * @returns {string|null} The URL or null if not found
136
- */
137
- async getModuleUrl(code) {
138
- const module = await this.getModuleByCode(code);
139
- return module ? module.url : null;
140
- }
141
-
142
- /**
143
- * Get the module definition path for a module by code
144
- * @param {string} code - The module code
145
- * @returns {string|null} The module definition path or null if not found
146
- */
147
- async getModuleDefinition(code) {
148
- const module = await this.getModuleByCode(code);
149
- return module ? module.moduleDefinition : null;
150
- }
151
-
152
112
  /**
153
113
  * Get the cache directory for external modules
154
114
  * @returns {string} Path to the external modules cache directory
@@ -12,6 +12,8 @@ class OfficialModules {
12
12
  // Config collection state (merged from ConfigCollector)
13
13
  this.collectedConfig = {};
14
14
  this._existingConfig = null;
15
+ // Tracked during interactive config collection so {directory_name}
16
+ // placeholder defaults can be resolved in buildQuestion().
15
17
  this.currentProjectDir = null;
16
18
  }
17
19
 
@@ -500,32 +502,6 @@ class OfficialModules {
500
502
  }
501
503
  }
502
504
 
503
- /**
504
- * Find all .md agent files recursively in a directory
505
- * @param {string} dir - Directory to search
506
- * @returns {Array} List of .md agent file paths
507
- */
508
- async findAgentMdFiles(dir) {
509
- const agentFiles = [];
510
-
511
- async function searchDirectory(searchDir) {
512
- const entries = await fs.readdir(searchDir, { withFileTypes: true });
513
-
514
- for (const entry of entries) {
515
- const fullPath = path.join(searchDir, entry.name);
516
-
517
- if (entry.isFile() && entry.name.endsWith('.md')) {
518
- agentFiles.push(fullPath);
519
- } else if (entry.isDirectory()) {
520
- await searchDirectory(fullPath);
521
- }
522
- }
523
- }
524
-
525
- await searchDirectory(dir);
526
- return agentFiles;
527
- }
528
-
529
505
  /**
530
506
  * Create directories declared in module.yaml's `directories` key
531
507
  * This replaces the security-risky module installer pattern with declarative config
@@ -699,29 +675,6 @@ class OfficialModules {
699
675
  return { createdDirs, movedDirs, createdWdsFolders };
700
676
  }
701
677
 
702
- /**
703
- * Private: Process module configuration
704
- * @param {string} modulePath - Path to installed module
705
- * @param {string} moduleName - Module name
706
- */
707
- async processModuleConfig(modulePath, moduleName) {
708
- const configPath = path.join(modulePath, 'config.yaml');
709
-
710
- if (await fs.pathExists(configPath)) {
711
- try {
712
- let configContent = await fs.readFile(configPath, 'utf8');
713
-
714
- // Replace path placeholders
715
- configContent = configContent.replaceAll('{project-root}', `bmad/${moduleName}`);
716
- configContent = configContent.replaceAll('{module}', moduleName);
717
-
718
- await fs.writeFile(configPath, configContent, 'utf8');
719
- } catch (error) {
720
- await prompts.log.warn(`Failed to process module config: ${error.message}`);
721
- }
722
- }
723
- }
724
-
725
678
  /**
726
679
  * Private: Sync module files (preserving user modifications)
727
680
  * @param {string} sourcePath - Source module path
@@ -1091,7 +1044,6 @@ class OfficialModules {
1091
1044
  */
1092
1045
  async collectModuleConfigQuick(moduleName, projectDir, silentMode = true) {
1093
1046
  this.currentProjectDir = projectDir;
1094
-
1095
1047
  // Load existing config if not already loaded
1096
1048
  if (!this._existingConfig) {
1097
1049
  await this.loadExistingConfig(projectDir);
@@ -50,17 +50,6 @@ class RegistryClient {
50
50
  const content = await this.fetch(url, timeout);
51
51
  return yaml.parse(content);
52
52
  }
53
-
54
- /**
55
- * Fetch a URL and parse the response as JSON.
56
- * @param {string} url - URL to fetch
57
- * @param {number} [timeout] - Timeout in ms
58
- * @returns {Promise<Object>} Parsed JSON content
59
- */
60
- async fetchJson(url, timeout) {
61
- const content = await this.fetch(url, timeout);
62
- return JSON.parse(content);
63
- }
64
53
  }
65
54
 
66
55
  module.exports = { RegistryClient };
@@ -498,26 +498,6 @@ async function password(options) {
498
498
  return result;
499
499
  }
500
500
 
501
- /**
502
- * Group multiple prompts together
503
- * @param {Object} prompts - Object of prompt functions
504
- * @param {Object} [options] - Group options
505
- * @returns {Promise<Object>} Object with all answers
506
- */
507
- async function group(prompts, options = {}) {
508
- const clack = await getClack();
509
-
510
- const result = await clack.group(prompts, {
511
- onCancel: () => {
512
- clack.cancel('Operation cancelled');
513
- process.exit(0);
514
- },
515
- ...options,
516
- });
517
-
518
- return result;
519
- }
520
-
521
501
  /**
522
502
  * Run tasks with spinner feedback
523
503
  * @param {Array} tasks - Array of task objects [{title, task, enabled?}]
@@ -578,42 +558,6 @@ async function box(content, title, options) {
578
558
  clack.box(content, title, options);
579
559
  }
580
560
 
581
- /**
582
- * Create a progress bar for visualizing task completion
583
- * @param {Object} [options] - Progress options (max, style, etc.)
584
- * @returns {Promise<Object>} Progress controller with start, advance, stop methods
585
- */
586
- async function progress(options) {
587
- const clack = await getClack();
588
- return clack.progress(options);
589
- }
590
-
591
- /**
592
- * Create a task log for displaying scrolling subprocess output
593
- * @param {Object} options - TaskLog options (title, limit, retainLog)
594
- * @returns {Promise<Object>} TaskLog controller with message, success, error methods
595
- */
596
- async function taskLog(options) {
597
- const clack = await getClack();
598
- return clack.taskLog(options);
599
- }
600
-
601
- /**
602
- * File system path prompt with autocomplete
603
- * @param {Object} options - Path options
604
- * @param {string} options.message - The prompt message
605
- * @param {string} [options.initialValue] - Initial path value
606
- * @param {boolean} [options.directory=false] - Only allow directories
607
- * @param {Function} [options.validate] - Validation function
608
- * @returns {Promise<string>} Selected path
609
- */
610
- async function pathPrompt(options) {
611
- const clack = await getClack();
612
- const result = await clack.path(options);
613
- await handleCancel(result);
614
- return result;
615
- }
616
-
617
561
  /**
618
562
  * Autocomplete single-select prompt with type-ahead filtering
619
563
  * @param {Object} options - Autocomplete options
@@ -631,50 +575,6 @@ async function autocomplete(options) {
631
575
  return result;
632
576
  }
633
577
 
634
- /**
635
- * Key-based instant selection prompt
636
- * @param {Object} options - SelectKey options
637
- * @param {string} options.message - The prompt message
638
- * @param {Array} options.options - Array of choices [{value, label, hint?}]
639
- * @returns {Promise<any>} Selected value
640
- */
641
- async function selectKey(options) {
642
- const clack = await getClack();
643
- const result = await clack.selectKey(options);
644
- await handleCancel(result);
645
- return result;
646
- }
647
-
648
- /**
649
- * Stream messages with dynamic content (for LLMs, generators, etc.)
650
- */
651
- const stream = {
652
- async info(generator) {
653
- const clack = await getClack();
654
- return clack.stream.info(generator);
655
- },
656
- async success(generator) {
657
- const clack = await getClack();
658
- return clack.stream.success(generator);
659
- },
660
- async step(generator) {
661
- const clack = await getClack();
662
- return clack.stream.step(generator);
663
- },
664
- async warn(generator) {
665
- const clack = await getClack();
666
- return clack.stream.warn(generator);
667
- },
668
- async error(generator) {
669
- const clack = await getClack();
670
- return clack.stream.error(generator);
671
- },
672
- async message(generator, options) {
673
- const clack = await getClack();
674
- return clack.stream.message(generator, options);
675
- },
676
- };
677
-
678
578
  /**
679
579
  * Get the color utility (picocolors instance from @clack/prompts)
680
580
  * @returns {Promise<Object>} The color utility (picocolors)
@@ -790,20 +690,14 @@ module.exports = {
790
690
  note,
791
691
  box,
792
692
  spinner,
793
- progress,
794
- taskLog,
795
693
  select,
796
694
  multiselect,
797
695
  autocompleteMultiselect,
798
696
  autocomplete,
799
- selectKey,
800
697
  confirm,
801
698
  text,
802
- path: pathPrompt,
803
699
  password,
804
- group,
805
700
  tasks,
806
701
  log,
807
- stream,
808
702
  prompt,
809
703
  };
@@ -1,180 +0,0 @@
1
- const path = require('node:path');
2
- const fs = require('fs-extra');
3
- const { toColonPath, toDashPath, customAgentColonName, customAgentDashName, BMAD_FOLDER_NAME } = require('./path-utils');
4
-
5
- /**
6
- * Generates launcher command files for each agent
7
- */
8
- class AgentCommandGenerator {
9
- constructor(bmadFolderName = BMAD_FOLDER_NAME) {
10
- this.templatePath = path.join(__dirname, '../templates/agent-command-template.md');
11
- this.bmadFolderName = bmadFolderName;
12
- }
13
-
14
- /**
15
- * Collect agent artifacts for IDE installation
16
- * @param {string} bmadDir - BMAD installation directory
17
- * @param {Array} selectedModules - Modules to include
18
- * @returns {Object} Artifacts array with metadata
19
- */
20
- async collectAgentArtifacts(bmadDir, selectedModules = []) {
21
- const { getAgentsFromBmad } = require('./bmad-artifacts');
22
-
23
- // Get agents from INSTALLED bmad/ directory
24
- const agents = await getAgentsFromBmad(bmadDir, selectedModules);
25
-
26
- const artifacts = [];
27
-
28
- for (const agent of agents) {
29
- const launcherContent = await this.generateLauncherContent(agent);
30
- // Use relativePath if available (for nested agents), otherwise just name with .md
31
- const agentPathInModule = agent.relativePath || `${agent.name}.md`;
32
- // Calculate the relative agent path (e.g., bmm/agents/pm.md)
33
- let agentRelPath = agent.path || '';
34
- // Normalize path separators for cross-platform compatibility
35
- agentRelPath = agentRelPath.replaceAll('\\', '/');
36
- // Remove _bmad/ prefix if present to get relative path from project root
37
- // Handle both absolute paths (/path/to/_bmad/...) and relative paths (_bmad/...)
38
- if (agentRelPath.includes('_bmad/')) {
39
- const parts = agentRelPath.split(/_bmad\//);
40
- if (parts.length > 1) {
41
- agentRelPath = parts.slice(1).join('/');
42
- }
43
- }
44
- artifacts.push({
45
- type: 'agent-launcher',
46
- name: agent.name,
47
- description: agent.description || `${agent.name} agent`,
48
- module: agent.module,
49
- canonicalId: agent.canonicalId || '',
50
- relativePath: path.join(agent.module, 'agents', agentPathInModule), // For command filename
51
- agentPath: agentRelPath, // Relative path to actual agent file
52
- content: launcherContent,
53
- sourcePath: agent.path,
54
- });
55
- }
56
-
57
- return {
58
- artifacts,
59
- counts: {
60
- agents: agents.length,
61
- },
62
- };
63
- }
64
-
65
- /**
66
- * Generate launcher content for an agent
67
- * @param {Object} agent - Agent metadata
68
- * @returns {string} Launcher file content
69
- */
70
- async generateLauncherContent(agent) {
71
- // Load the template
72
- const template = await fs.readFile(this.templatePath, 'utf8');
73
-
74
- // Replace template variables
75
- // Use relativePath if available (for nested agents), otherwise just name with .md
76
- const agentPathInModule = agent.relativePath || `${agent.name}.md`;
77
- return template
78
- .replaceAll('{{name}}', agent.name)
79
- .replaceAll('{{module}}', agent.module)
80
- .replaceAll('{{path}}', agentPathInModule)
81
- .replaceAll('{{description}}', agent.description || `${agent.name} agent`)
82
- .replaceAll('_bmad', this.bmadFolderName)
83
- .replaceAll('_bmad', '_bmad');
84
- }
85
-
86
- /**
87
- * Write agent launcher artifacts to IDE commands directory
88
- * @param {string} baseCommandsDir - Base commands directory for the IDE
89
- * @param {Array} artifacts - Agent launcher artifacts
90
- * @returns {number} Count of launchers written
91
- */
92
- async writeAgentLaunchers(baseCommandsDir, artifacts) {
93
- let writtenCount = 0;
94
-
95
- for (const artifact of artifacts) {
96
- if (artifact.type === 'agent-launcher') {
97
- const moduleAgentsDir = path.join(baseCommandsDir, artifact.module, 'agents');
98
- await fs.ensureDir(moduleAgentsDir);
99
-
100
- const launcherPath = path.join(moduleAgentsDir, `${artifact.name}.md`);
101
- await fs.writeFile(launcherPath, artifact.content);
102
- writtenCount++;
103
- }
104
- }
105
-
106
- return writtenCount;
107
- }
108
-
109
- /**
110
- * Write agent launcher artifacts using underscore format (Windows-compatible)
111
- * Creates flat files like: bmad_bmm_pm.md
112
- *
113
- * @param {string} baseCommandsDir - Base commands directory for the IDE
114
- * @param {Array} artifacts - Agent launcher artifacts
115
- * @returns {number} Count of launchers written
116
- */
117
- async writeColonArtifacts(baseCommandsDir, artifacts) {
118
- let writtenCount = 0;
119
-
120
- for (const artifact of artifacts) {
121
- if (artifact.type === 'agent-launcher') {
122
- // Convert relativePath to underscore format: bmm/agents/pm.md → bmad_bmm_pm.md
123
- const flatName = toColonPath(artifact.relativePath);
124
- const launcherPath = path.join(baseCommandsDir, flatName);
125
- await fs.ensureDir(path.dirname(launcherPath));
126
- await fs.writeFile(launcherPath, artifact.content);
127
- writtenCount++;
128
- }
129
- }
130
-
131
- return writtenCount;
132
- }
133
-
134
- /**
135
- * Write agent launcher artifacts using dash format (NEW STANDARD)
136
- * Creates flat files like: bmad-agent-bmm-pm.md
137
- *
138
- * The bmad-agent- prefix distinguishes agents from workflows/tasks/tools.
139
- *
140
- * @param {string} baseCommandsDir - Base commands directory for the IDE
141
- * @param {Array} artifacts - Agent launcher artifacts
142
- * @returns {number} Count of launchers written
143
- */
144
- async writeDashArtifacts(baseCommandsDir, artifacts) {
145
- let writtenCount = 0;
146
-
147
- for (const artifact of artifacts) {
148
- if (artifact.type === 'agent-launcher') {
149
- // Convert relativePath to dash format: bmm/agents/pm.md → bmad-agent-bmm-pm.md
150
- const flatName = toDashPath(artifact.relativePath);
151
- const launcherPath = path.join(baseCommandsDir, flatName);
152
- await fs.ensureDir(path.dirname(launcherPath));
153
- await fs.writeFile(launcherPath, artifact.content);
154
- writtenCount++;
155
- }
156
- }
157
-
158
- return writtenCount;
159
- }
160
-
161
- /**
162
- * Get the custom agent name in underscore format (Windows-compatible)
163
- * @param {string} agentName - Custom agent name
164
- * @returns {string} Underscore-formatted filename
165
- */
166
- getCustomAgentColonName(agentName) {
167
- return customAgentColonName(agentName);
168
- }
169
-
170
- /**
171
- * Get the custom agent name in underscore format (Windows-compatible)
172
- * @param {string} agentName - Custom agent name
173
- * @returns {string} Underscore-formatted filename
174
- */
175
- getCustomAgentDashName(agentName) {
176
- return customAgentDashName(agentName);
177
- }
178
- }
179
-
180
- module.exports = { AgentCommandGenerator };