bmad-method 6.2.1-next.23 → 6.2.1-next.24

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package.json",
3
3
  "name": "bmad-method",
4
- "version": "6.2.1-next.23",
4
+ "version": "6.2.1-next.24",
5
5
  "description": "Breakthrough Method of Agile AI-driven Development",
6
6
  "keywords": [
7
7
  "agile",
@@ -4,9 +4,6 @@ const fs = require('fs-extra');
4
4
  const yaml = require('yaml');
5
5
  const { BaseIdeSetup } = require('./_base-ide');
6
6
  const prompts = require('../../../lib/prompts');
7
- const { AgentCommandGenerator } = require('./shared/agent-command-generator');
8
- const { WorkflowCommandGenerator } = require('./shared/workflow-command-generator');
9
- const { TaskToolCommandGenerator } = require('./shared/task-tool-command-generator');
10
7
  const csv = require('csv-parse/sync');
11
8
 
12
9
  /**
@@ -115,53 +112,20 @@ class ConfigDrivenIdeSetup extends BaseIdeSetup {
115
112
  * @returns {Promise<Object>} Installation result
116
113
  */
117
114
  async installToTarget(projectDir, bmadDir, config, options) {
118
- const { target_dir, template_type, artifact_types } = config;
115
+ const { target_dir } = config;
119
116
 
120
- // Skip targets with explicitly empty artifact_types and no verbatim skills
121
- // This prevents creating empty directories when no artifacts will be written
122
- const skipStandardArtifacts = Array.isArray(artifact_types) && artifact_types.length === 0;
123
- if (skipStandardArtifacts && !config.skill_format) {
124
- return { success: true, results: { agents: 0, workflows: 0, tasks: 0, tools: 0, skills: 0 } };
117
+ if (!config.skill_format) {
118
+ return { success: false, reason: 'missing-skill-format', error: 'Installer config missing skill_format — cannot install skills' };
125
119
  }
126
120
 
127
121
  const targetPath = path.join(projectDir, target_dir);
128
122
  await this.ensureDir(targetPath);
129
123
 
130
- const selectedModules = options.selectedModules || [];
131
- const results = { agents: 0, workflows: 0, tasks: 0, tools: 0, skills: 0 };
132
- this.skillWriteTracker = config.skill_format ? new Set() : null;
133
-
134
- // Install standard artifacts (agents, workflows, tasks, tools)
135
- if (!skipStandardArtifacts) {
136
- // Install agents
137
- if (!artifact_types || artifact_types.includes('agents')) {
138
- const agentGen = new AgentCommandGenerator(this.bmadFolderName);
139
- const { artifacts } = await agentGen.collectAgentArtifacts(bmadDir, selectedModules);
140
- results.agents = await this.writeAgentArtifacts(targetPath, artifacts, template_type, config);
141
- }
142
-
143
- // Install workflows
144
- if (!artifact_types || artifact_types.includes('workflows')) {
145
- const workflowGen = new WorkflowCommandGenerator(this.bmadFolderName);
146
- const { artifacts } = await workflowGen.collectWorkflowArtifacts(bmadDir);
147
- results.workflows = await this.writeWorkflowArtifacts(targetPath, artifacts, template_type, config);
148
- }
149
-
150
- // Install tasks and tools using template system (supports TOML for Gemini, MD for others)
151
- if (!artifact_types || artifact_types.includes('tasks') || artifact_types.includes('tools')) {
152
- const taskToolGen = new TaskToolCommandGenerator(this.bmadFolderName);
153
- const { artifacts } = await taskToolGen.collectTaskToolArtifacts(bmadDir);
154
- const taskToolResult = await this.writeTaskToolArtifacts(targetPath, artifacts, template_type, config);
155
- results.tasks = taskToolResult.tasks || 0;
156
- results.tools = taskToolResult.tools || 0;
157
- }
158
- }
124
+ this.skillWriteTracker = new Set();
125
+ const results = { skills: 0 };
159
126
 
160
- // Install verbatim skills (type: skill)
161
- if (config.skill_format) {
162
- results.skills = await this.installVerbatimSkills(projectDir, bmadDir, targetPath, config);
163
- results.skillDirectories = this.skillWriteTracker ? this.skillWriteTracker.size : 0;
164
- }
127
+ results.skills = await this.installVerbatimSkills(projectDir, bmadDir, targetPath, config);
128
+ results.skillDirectories = this.skillWriteTracker.size;
165
129
 
166
130
  await this.printSummary(results, target_dir, options);
167
131
  this.skillWriteTracker = null;
@@ -177,15 +141,11 @@ class ConfigDrivenIdeSetup extends BaseIdeSetup {
177
141
  * @returns {Promise<Object>} Installation result
178
142
  */
179
143
  async installToMultipleTargets(projectDir, bmadDir, targets, options) {
180
- const allResults = { agents: 0, workflows: 0, tasks: 0, tools: 0, skills: 0 };
144
+ const allResults = { skills: 0 };
181
145
 
182
146
  for (const target of targets) {
183
147
  const result = await this.installToTarget(projectDir, bmadDir, target, options);
184
148
  if (result.success) {
185
- allResults.agents += result.results.agents || 0;
186
- allResults.workflows += result.results.workflows || 0;
187
- allResults.tasks += result.results.tasks || 0;
188
- allResults.tools += result.results.tools || 0;
189
149
  allResults.skills += result.results.skills || 0;
190
150
  }
191
151
  }
@@ -193,118 +153,6 @@ class ConfigDrivenIdeSetup extends BaseIdeSetup {
193
153
  return { success: true, results: allResults };
194
154
  }
195
155
 
196
- /**
197
- * Write agent artifacts to target directory
198
- * @param {string} targetPath - Target directory path
199
- * @param {Array} artifacts - Agent artifacts
200
- * @param {string} templateType - Template type to use
201
- * @param {Object} config - Installation configuration
202
- * @returns {Promise<number>} Count of artifacts written
203
- */
204
- async writeAgentArtifacts(targetPath, artifacts, templateType, config = {}) {
205
- // Try to load platform-specific template, fall back to default-agent
206
- const { content: template, extension } = await this.loadTemplate(templateType, 'agent', config, 'default-agent');
207
- let count = 0;
208
-
209
- for (const artifact of artifacts) {
210
- const content = this.renderTemplate(template, artifact);
211
- const filename = this.generateFilename(artifact, 'agent', extension);
212
-
213
- if (config.skill_format) {
214
- await this.writeSkillFile(targetPath, artifact, content);
215
- } else {
216
- const filePath = path.join(targetPath, filename);
217
- await this.writeFile(filePath, content);
218
- }
219
- count++;
220
- }
221
-
222
- return count;
223
- }
224
-
225
- /**
226
- * Write workflow artifacts to target directory
227
- * @param {string} targetPath - Target directory path
228
- * @param {Array} artifacts - Workflow artifacts
229
- * @param {string} templateType - Template type to use
230
- * @param {Object} config - Installation configuration
231
- * @returns {Promise<number>} Count of artifacts written
232
- */
233
- async writeWorkflowArtifacts(targetPath, artifacts, templateType, config = {}) {
234
- let count = 0;
235
-
236
- for (const artifact of artifacts) {
237
- if (artifact.type === 'workflow-command') {
238
- const workflowTemplateType = config.md_workflow_template || `${templateType}-workflow`;
239
- const { content: template, extension } = await this.loadTemplate(workflowTemplateType, '', config, 'default-workflow');
240
- const content = this.renderTemplate(template, artifact);
241
- const filename = this.generateFilename(artifact, 'workflow', extension);
242
-
243
- if (config.skill_format) {
244
- await this.writeSkillFile(targetPath, artifact, content);
245
- } else {
246
- const filePath = path.join(targetPath, filename);
247
- await this.writeFile(filePath, content);
248
- }
249
- count++;
250
- }
251
- }
252
-
253
- return count;
254
- }
255
-
256
- /**
257
- * Write task/tool artifacts to target directory using templates
258
- * @param {string} targetPath - Target directory path
259
- * @param {Array} artifacts - Task/tool artifacts
260
- * @param {string} templateType - Template type to use
261
- * @param {Object} config - Installation configuration
262
- * @returns {Promise<Object>} Counts of tasks and tools written
263
- */
264
- async writeTaskToolArtifacts(targetPath, artifacts, templateType, config = {}) {
265
- let taskCount = 0;
266
- let toolCount = 0;
267
-
268
- // Pre-load templates to avoid repeated file I/O in the loop
269
- const taskTemplate = await this.loadTemplate(templateType, 'task', config, 'default-task');
270
- const toolTemplate = await this.loadTemplate(templateType, 'tool', config, 'default-tool');
271
-
272
- const { artifact_types } = config;
273
-
274
- for (const artifact of artifacts) {
275
- if (artifact.type !== 'task' && artifact.type !== 'tool') {
276
- continue;
277
- }
278
-
279
- // Skip if the specific artifact type is not requested in config
280
- if (artifact_types) {
281
- if (artifact.type === 'task' && !artifact_types.includes('tasks')) continue;
282
- if (artifact.type === 'tool' && !artifact_types.includes('tools')) continue;
283
- }
284
-
285
- // Use pre-loaded template based on artifact type
286
- const { content: template, extension } = artifact.type === 'task' ? taskTemplate : toolTemplate;
287
-
288
- const content = this.renderTemplate(template, artifact);
289
- const filename = this.generateFilename(artifact, artifact.type, extension);
290
-
291
- if (config.skill_format) {
292
- await this.writeSkillFile(targetPath, artifact, content);
293
- } else {
294
- const filePath = path.join(targetPath, filename);
295
- await this.writeFile(filePath, content);
296
- }
297
-
298
- if (artifact.type === 'task') {
299
- taskCount++;
300
- } else {
301
- toolCount++;
302
- }
303
- }
304
-
305
- return { tasks: taskCount, tools: toolCount };
306
- }
307
-
308
156
  /**
309
157
  * Load template based on type and configuration
310
158
  * @param {string} templateType - Template type (claude, windsurf, etc.)
@@ -711,13 +559,10 @@ LOAD and execute from: {project-root}/{{bmadFolderName}}/{{path}}
711
559
  */
712
560
  async printSummary(results, targetDir, options = {}) {
713
561
  if (options.silent) return;
714
- const parts = [];
715
- const totalDirs =
716
- results.skillDirectories || (results.workflows || 0) + (results.tasks || 0) + (results.tools || 0) + (results.skills || 0);
717
- const skillCount = totalDirs - (results.agents || 0);
718
- if (skillCount > 0) parts.push(`${skillCount} skills`);
719
- if (results.agents > 0) parts.push(`${results.agents} agents`);
720
- await prompts.log.success(`${this.name} configured: ${parts.join(', ')} → ${targetDir}`);
562
+ const count = results.skillDirectories || results.skills || 0;
563
+ if (count > 0) {
564
+ await prompts.log.success(`${this.name} configured: ${count} skills ${targetDir}`);
565
+ }
721
566
  }
722
567
 
723
568
  /**
@@ -159,14 +159,9 @@ class IdeManager {
159
159
  // Build detail string from handler-returned data
160
160
  let detail = '';
161
161
  if (handlerResult && handlerResult.results) {
162
- // Config-driven handlers return { success, results: { agents, workflows, tasks, tools } }
163
162
  const r = handlerResult.results;
164
- const parts = [];
165
- const totalDirs = r.skillDirectories || (r.workflows || 0) + (r.tasks || 0) + (r.tools || 0) + (r.skills || 0);
166
- const skillCount = totalDirs - (r.agents || 0);
167
- if (skillCount > 0) parts.push(`${skillCount} skills`);
168
- if (r.agents > 0) parts.push(`${r.agents} agents`);
169
- detail = parts.join(', ');
163
+ const count = r.skillDirectories || r.skills || 0;
164
+ if (count > 0) detail = `${count} skills`;
170
165
  }
171
166
  // Propagate handler's success status (default true for backward compat)
172
167
  const success = handlerResult?.success !== false;
@@ -4,7 +4,6 @@ const { toColonPath, toDashPath, customAgentColonName, customAgentDashName, BMAD
4
4
 
5
5
  /**
6
6
  * Generates launcher command files for each agent
7
- * Similar to WorkflowCommandGenerator but for agents
8
7
  */
9
8
  class AgentCommandGenerator {
10
9
  constructor(bmadFolderName = BMAD_FOLDER_NAME) {
@@ -1,368 +0,0 @@
1
- const path = require('node:path');
2
- const fs = require('fs-extra');
3
- const csv = require('csv-parse/sync');
4
- const { toColonName, toColonPath, toDashPath, BMAD_FOLDER_NAME } = require('./path-utils');
5
-
6
- /**
7
- * Generates command files for standalone tasks and tools
8
- */
9
- class TaskToolCommandGenerator {
10
- /**
11
- * @param {string} bmadFolderName - Name of the BMAD folder for template rendering (default: '_bmad')
12
- * Note: This parameter is accepted for API consistency with AgentCommandGenerator and
13
- * WorkflowCommandGenerator, but is not used for path stripping. The manifest always stores
14
- * filesystem paths with '_bmad/' prefix (the actual folder name), while bmadFolderName is
15
- * used for template placeholder rendering ({{bmadFolderName}}).
16
- */
17
- constructor(bmadFolderName = BMAD_FOLDER_NAME) {
18
- this.bmadFolderName = bmadFolderName;
19
- }
20
-
21
- /**
22
- * Collect task and tool artifacts for IDE installation
23
- * @param {string} bmadDir - BMAD installation directory
24
- * @returns {Promise<Object>} Artifacts array with metadata
25
- */
26
- async collectTaskToolArtifacts(bmadDir) {
27
- const tasks = await this.loadTaskManifest(bmadDir);
28
- const tools = await this.loadToolManifest(bmadDir);
29
-
30
- // All tasks/tools in manifest are standalone (internal=true items are filtered during manifest generation)
31
- const artifacts = [];
32
- const bmadPrefix = `${BMAD_FOLDER_NAME}/`;
33
-
34
- // Collect task artifacts
35
- for (const task of tasks || []) {
36
- let taskPath = (task.path || '').replaceAll('\\', '/');
37
- // Convert absolute paths to relative paths
38
- if (path.isAbsolute(taskPath)) {
39
- taskPath = path.relative(bmadDir, taskPath).replaceAll('\\', '/');
40
- }
41
- // Remove _bmad/ prefix if present to get relative path within bmad folder
42
- if (taskPath.startsWith(bmadPrefix)) {
43
- taskPath = taskPath.slice(bmadPrefix.length);
44
- }
45
-
46
- const taskExt = path.extname(taskPath) || '.md';
47
- artifacts.push({
48
- type: 'task',
49
- name: task.name,
50
- displayName: task.displayName || task.name,
51
- description: task.description || `Execute ${task.displayName || task.name}`,
52
- module: task.module,
53
- canonicalId: task.canonicalId || '',
54
- // Use forward slashes for cross-platform consistency (not path.join which uses backslashes on Windows)
55
- relativePath: `${task.module}/tasks/${task.name}${taskExt}`,
56
- path: taskPath,
57
- });
58
- }
59
-
60
- // Collect tool artifacts
61
- for (const tool of tools || []) {
62
- let toolPath = (tool.path || '').replaceAll('\\', '/');
63
- // Convert absolute paths to relative paths
64
- if (path.isAbsolute(toolPath)) {
65
- toolPath = path.relative(bmadDir, toolPath).replaceAll('\\', '/');
66
- }
67
- // Remove _bmad/ prefix if present to get relative path within bmad folder
68
- if (toolPath.startsWith(bmadPrefix)) {
69
- toolPath = toolPath.slice(bmadPrefix.length);
70
- }
71
-
72
- const toolExt = path.extname(toolPath) || '.md';
73
- artifacts.push({
74
- type: 'tool',
75
- name: tool.name,
76
- displayName: tool.displayName || tool.name,
77
- description: tool.description || `Execute ${tool.displayName || tool.name}`,
78
- module: tool.module,
79
- canonicalId: tool.canonicalId || '',
80
- // Use forward slashes for cross-platform consistency (not path.join which uses backslashes on Windows)
81
- relativePath: `${tool.module}/tools/${tool.name}${toolExt}`,
82
- path: toolPath,
83
- });
84
- }
85
-
86
- return {
87
- artifacts,
88
- counts: {
89
- tasks: (tasks || []).length,
90
- tools: (tools || []).length,
91
- },
92
- };
93
- }
94
-
95
- /**
96
- * Generate task and tool commands from manifest CSVs
97
- * @param {string} projectDir - Project directory
98
- * @param {string} bmadDir - BMAD installation directory
99
- * @param {string} baseCommandsDir - Optional base commands directory (defaults to .claude/commands/bmad)
100
- */
101
- async generateTaskToolCommands(projectDir, bmadDir, baseCommandsDir = null) {
102
- const tasks = await this.loadTaskManifest(bmadDir);
103
- const tools = await this.loadToolManifest(bmadDir);
104
-
105
- // Base commands directory - use provided or default to Claude Code structure
106
- const commandsDir = baseCommandsDir || path.join(projectDir, '.claude', 'commands', 'bmad');
107
-
108
- let generatedCount = 0;
109
-
110
- // Generate command files for tasks
111
- for (const task of tasks || []) {
112
- const moduleTasksDir = path.join(commandsDir, task.module, 'tasks');
113
- await fs.ensureDir(moduleTasksDir);
114
-
115
- const commandContent = this.generateCommandContent(task, 'task');
116
- const commandPath = path.join(moduleTasksDir, `${task.name}.md`);
117
-
118
- await fs.writeFile(commandPath, commandContent);
119
- generatedCount++;
120
- }
121
-
122
- // Generate command files for tools
123
- for (const tool of tools || []) {
124
- const moduleToolsDir = path.join(commandsDir, tool.module, 'tools');
125
- await fs.ensureDir(moduleToolsDir);
126
-
127
- const commandContent = this.generateCommandContent(tool, 'tool');
128
- const commandPath = path.join(moduleToolsDir, `${tool.name}.md`);
129
-
130
- await fs.writeFile(commandPath, commandContent);
131
- generatedCount++;
132
- }
133
-
134
- return {
135
- generated: generatedCount,
136
- tasks: (tasks || []).length,
137
- tools: (tools || []).length,
138
- };
139
- }
140
-
141
- /**
142
- * Generate command content for a task or tool
143
- */
144
- generateCommandContent(item, type) {
145
- const description = item.description || `Execute ${item.displayName || item.name}`;
146
-
147
- // Convert path to use {project-root} placeholder
148
- // Handle undefined/missing path by constructing from module and name
149
- let itemPath = item.path;
150
- if (!itemPath || typeof itemPath !== 'string') {
151
- // Fallback: construct path from module and name if path is missing
152
- const typePlural = type === 'task' ? 'tasks' : 'tools';
153
- itemPath = `{project-root}/${this.bmadFolderName}/${item.module}/${typePlural}/${item.name}.md`;
154
- } else {
155
- // Normalize path separators to forward slashes
156
- itemPath = itemPath.replaceAll('\\', '/');
157
-
158
- // Extract relative path from absolute paths (Windows or Unix)
159
- // Look for _bmad/ or bmad/ in the path and extract everything after it
160
- // Match patterns like: /_bmad/core/tasks/... or /bmad/core/tasks/...
161
- // Use [/\\] to handle both Unix forward slashes and Windows backslashes,
162
- // and also paths without a leading separator (e.g., C:/_bmad/...)
163
- const bmadMatch = itemPath.match(/[/\\]_bmad[/\\](.+)$/) || itemPath.match(/[/\\]bmad[/\\](.+)$/);
164
- if (bmadMatch) {
165
- // Found /_bmad/ or /bmad/ - use relative path after it
166
- itemPath = `{project-root}/${this.bmadFolderName}/${bmadMatch[1]}`;
167
- } else if (itemPath.startsWith(`${BMAD_FOLDER_NAME}/`)) {
168
- // Relative path starting with _bmad/
169
- itemPath = `{project-root}/${this.bmadFolderName}/${itemPath.slice(BMAD_FOLDER_NAME.length + 1)}`;
170
- } else if (itemPath.startsWith('bmad/')) {
171
- // Relative path starting with bmad/
172
- itemPath = `{project-root}/${this.bmadFolderName}/${itemPath.slice(5)}`;
173
- } else if (!itemPath.startsWith('{project-root}')) {
174
- // For other relative paths, prefix with project root and bmad folder
175
- itemPath = `{project-root}/${this.bmadFolderName}/${itemPath}`;
176
- }
177
- }
178
-
179
- return `---
180
- description: '${description.replaceAll("'", "''")}'
181
- ---
182
-
183
- # ${item.displayName || item.name}
184
-
185
- Read the entire ${type} file at: ${itemPath}
186
-
187
- Follow all instructions in the ${type} file exactly as written.
188
- `;
189
- }
190
-
191
- /**
192
- * Load task manifest CSV
193
- */
194
- async loadTaskManifest(bmadDir) {
195
- const manifestPath = path.join(bmadDir, '_config', 'task-manifest.csv');
196
-
197
- if (!(await fs.pathExists(manifestPath))) {
198
- return null;
199
- }
200
-
201
- const csvContent = await fs.readFile(manifestPath, 'utf8');
202
- return csv.parse(csvContent, {
203
- columns: true,
204
- skip_empty_lines: true,
205
- });
206
- }
207
-
208
- /**
209
- * Load tool manifest CSV
210
- */
211
- async loadToolManifest(bmadDir) {
212
- const manifestPath = path.join(bmadDir, '_config', 'tool-manifest.csv');
213
-
214
- if (!(await fs.pathExists(manifestPath))) {
215
- return null;
216
- }
217
-
218
- const csvContent = await fs.readFile(manifestPath, 'utf8');
219
- return csv.parse(csvContent, {
220
- columns: true,
221
- skip_empty_lines: true,
222
- });
223
- }
224
-
225
- /**
226
- * Generate task and tool commands using underscore format (Windows-compatible)
227
- * Creates flat files like: bmad_bmm_help.md
228
- *
229
- * @param {string} projectDir - Project directory
230
- * @param {string} bmadDir - BMAD installation directory
231
- * @param {string} baseCommandsDir - Base commands directory for the IDE
232
- * @returns {Object} Generation results
233
- */
234
- async generateColonTaskToolCommands(projectDir, bmadDir, baseCommandsDir) {
235
- const tasks = await this.loadTaskManifest(bmadDir);
236
- const tools = await this.loadToolManifest(bmadDir);
237
-
238
- let generatedCount = 0;
239
-
240
- // Generate command files for tasks
241
- for (const task of tasks || []) {
242
- const commandContent = this.generateCommandContent(task, 'task');
243
- // Use underscore format: bmad_bmm_name.md
244
- const flatName = toColonName(task.module, 'tasks', task.name);
245
- const commandPath = path.join(baseCommandsDir, flatName);
246
- await fs.ensureDir(path.dirname(commandPath));
247
- await fs.writeFile(commandPath, commandContent);
248
- generatedCount++;
249
- }
250
-
251
- // Generate command files for tools
252
- for (const tool of tools || []) {
253
- const commandContent = this.generateCommandContent(tool, 'tool');
254
- // Use underscore format: bmad_bmm_name.md
255
- const flatName = toColonName(tool.module, 'tools', tool.name);
256
- const commandPath = path.join(baseCommandsDir, flatName);
257
- await fs.ensureDir(path.dirname(commandPath));
258
- await fs.writeFile(commandPath, commandContent);
259
- generatedCount++;
260
- }
261
-
262
- return {
263
- generated: generatedCount,
264
- tasks: (tasks || []).length,
265
- tools: (tools || []).length,
266
- };
267
- }
268
-
269
- /**
270
- * Generate task and tool commands using underscore format (Windows-compatible)
271
- * Creates flat files like: bmad_bmm_help.md
272
- *
273
- * @param {string} projectDir - Project directory
274
- * @param {string} bmadDir - BMAD installation directory
275
- * @param {string} baseCommandsDir - Base commands directory for the IDE
276
- * @returns {Object} Generation results
277
- */
278
- async generateDashTaskToolCommands(projectDir, bmadDir, baseCommandsDir) {
279
- const tasks = await this.loadTaskManifest(bmadDir);
280
- const tools = await this.loadToolManifest(bmadDir);
281
-
282
- let generatedCount = 0;
283
-
284
- // Generate command files for tasks
285
- for (const task of tasks || []) {
286
- const commandContent = this.generateCommandContent(task, 'task');
287
- // Use dash format: bmad-bmm-name.md
288
- const flatName = toDashPath(`${task.module}/tasks/${task.name}.md`);
289
- const commandPath = path.join(baseCommandsDir, flatName);
290
- await fs.ensureDir(path.dirname(commandPath));
291
- await fs.writeFile(commandPath, commandContent);
292
- generatedCount++;
293
- }
294
-
295
- // Generate command files for tools
296
- for (const tool of tools || []) {
297
- const commandContent = this.generateCommandContent(tool, 'tool');
298
- // Use dash format: bmad-bmm-name.md
299
- const flatName = toDashPath(`${tool.module}/tools/${tool.name}.md`);
300
- const commandPath = path.join(baseCommandsDir, flatName);
301
- await fs.ensureDir(path.dirname(commandPath));
302
- await fs.writeFile(commandPath, commandContent);
303
- generatedCount++;
304
- }
305
-
306
- return {
307
- generated: generatedCount,
308
- tasks: (tasks || []).length,
309
- tools: (tools || []).length,
310
- };
311
- }
312
-
313
- /**
314
- * Write task/tool artifacts using underscore format (Windows-compatible)
315
- * Creates flat files like: bmad_bmm_help.md
316
- *
317
- * @param {string} baseCommandsDir - Base commands directory for the IDE
318
- * @param {Array} artifacts - Task/tool artifacts with relativePath
319
- * @returns {number} Count of commands written
320
- */
321
- async writeColonArtifacts(baseCommandsDir, artifacts) {
322
- let writtenCount = 0;
323
-
324
- for (const artifact of artifacts) {
325
- if (artifact.type === 'task' || artifact.type === 'tool') {
326
- const commandContent = this.generateCommandContent(artifact, artifact.type);
327
- // Use underscore format: bmad_module_name.md
328
- const flatName = toColonPath(artifact.relativePath);
329
- const commandPath = path.join(baseCommandsDir, flatName);
330
- await fs.ensureDir(path.dirname(commandPath));
331
- await fs.writeFile(commandPath, commandContent);
332
- writtenCount++;
333
- }
334
- }
335
-
336
- return writtenCount;
337
- }
338
-
339
- /**
340
- * Write task/tool artifacts using dash format (NEW STANDARD)
341
- * Creates flat files like: bmad-bmm-help.md
342
- *
343
- * Note: Tasks/tools do NOT have bmad-agent- prefix - only agents do.
344
- *
345
- * @param {string} baseCommandsDir - Base commands directory for the IDE
346
- * @param {Array} artifacts - Task/tool artifacts with relativePath
347
- * @returns {number} Count of commands written
348
- */
349
- async writeDashArtifacts(baseCommandsDir, artifacts) {
350
- let writtenCount = 0;
351
-
352
- for (const artifact of artifacts) {
353
- if (artifact.type === 'task' || artifact.type === 'tool') {
354
- const commandContent = this.generateCommandContent(artifact, artifact.type);
355
- // Use dash format: bmad-module-name.md
356
- const flatName = toDashPath(artifact.relativePath);
357
- const commandPath = path.join(baseCommandsDir, flatName);
358
- await fs.ensureDir(path.dirname(commandPath));
359
- await fs.writeFile(commandPath, commandContent);
360
- writtenCount++;
361
- }
362
- }
363
-
364
- return writtenCount;
365
- }
366
- }
367
-
368
- module.exports = { TaskToolCommandGenerator };
@@ -1,179 +0,0 @@
1
- const path = require('node:path');
2
- const fs = require('fs-extra');
3
- const csv = require('csv-parse/sync');
4
- const { BMAD_FOLDER_NAME } = require('./path-utils');
5
-
6
- /**
7
- * Generates command files for each workflow in the manifest
8
- */
9
- class WorkflowCommandGenerator {
10
- constructor(bmadFolderName = BMAD_FOLDER_NAME) {
11
- this.bmadFolderName = bmadFolderName;
12
- }
13
-
14
- async collectWorkflowArtifacts(bmadDir) {
15
- const workflows = await this.loadWorkflowManifest(bmadDir);
16
-
17
- if (!workflows) {
18
- return { artifacts: [], counts: { commands: 0, launchers: 0 } };
19
- }
20
-
21
- // ALL workflows now generate commands - no standalone filtering
22
- const allWorkflows = workflows;
23
-
24
- const artifacts = [];
25
-
26
- for (const workflow of allWorkflows) {
27
- // Calculate the relative workflow path (e.g., bmm/workflows/4-implementation/sprint-planning/workflow.md)
28
- let workflowRelPath = workflow.path || '';
29
- // Normalize path separators for cross-platform compatibility
30
- workflowRelPath = workflowRelPath.replaceAll('\\', '/');
31
- // Remove _bmad/ prefix if present to get relative path from project root
32
- // Handle both absolute paths (/path/to/_bmad/...) and relative paths (_bmad/...)
33
- if (workflowRelPath.includes('_bmad/')) {
34
- const parts = workflowRelPath.split(/_bmad\//);
35
- if (parts.length > 1) {
36
- workflowRelPath = parts.slice(1).join('/');
37
- }
38
- } else if (workflowRelPath.includes('/src/')) {
39
- // Normalize source paths (e.g. .../src/bmm/...) to relative module path (e.g. bmm/...)
40
- const match = workflowRelPath.match(/\/src\/([^/]+)\/(.+)/);
41
- if (match) {
42
- workflowRelPath = `${match[1]}/${match[2]}`;
43
- }
44
- }
45
- artifacts.push({
46
- type: 'workflow-command',
47
- name: workflow.name,
48
- description: workflow.description || `${workflow.name} workflow`,
49
- module: workflow.module,
50
- canonicalId: workflow.canonicalId || '',
51
- relativePath: path.join(workflow.module, 'workflows', `${workflow.name}.md`),
52
- workflowPath: workflowRelPath, // Relative path to actual workflow file
53
- sourcePath: workflow.path,
54
- });
55
- }
56
-
57
- const groupedWorkflows = this.groupWorkflowsByModule(allWorkflows);
58
- for (const [module, launcherContent] of Object.entries(this.buildModuleWorkflowLaunchers(groupedWorkflows))) {
59
- artifacts.push({
60
- type: 'workflow-launcher',
61
- module,
62
- relativePath: path.join(module, 'workflows', 'README.md'),
63
- content: launcherContent,
64
- sourcePath: null,
65
- });
66
- }
67
-
68
- return {
69
- artifacts,
70
- counts: {
71
- commands: allWorkflows.length,
72
- launchers: Object.keys(groupedWorkflows).length,
73
- },
74
- };
75
- }
76
-
77
- /**
78
- * Create workflow launcher files for each module
79
- */
80
- async createModuleWorkflowLaunchers(baseCommandsDir, workflowsByModule) {
81
- for (const [module, moduleWorkflows] of Object.entries(workflowsByModule)) {
82
- const content = this.buildLauncherContent(module, moduleWorkflows);
83
- const moduleWorkflowsDir = path.join(baseCommandsDir, module, 'workflows');
84
- await fs.ensureDir(moduleWorkflowsDir);
85
- const launcherPath = path.join(moduleWorkflowsDir, 'README.md');
86
- await fs.writeFile(launcherPath, content);
87
- }
88
- }
89
-
90
- groupWorkflowsByModule(workflows) {
91
- const workflowsByModule = {};
92
-
93
- for (const workflow of workflows) {
94
- if (!workflowsByModule[workflow.module]) {
95
- workflowsByModule[workflow.module] = [];
96
- }
97
-
98
- workflowsByModule[workflow.module].push({
99
- ...workflow,
100
- displayPath: this.transformWorkflowPath(workflow.path),
101
- });
102
- }
103
-
104
- return workflowsByModule;
105
- }
106
-
107
- buildModuleWorkflowLaunchers(groupedWorkflows) {
108
- const launchers = {};
109
-
110
- for (const [module, moduleWorkflows] of Object.entries(groupedWorkflows)) {
111
- launchers[module] = this.buildLauncherContent(module, moduleWorkflows);
112
- }
113
-
114
- return launchers;
115
- }
116
-
117
- buildLauncherContent(module, moduleWorkflows) {
118
- let content = `# ${module.toUpperCase()} Workflows
119
-
120
- ## Available Workflows in ${module}
121
-
122
- `;
123
-
124
- for (const workflow of moduleWorkflows) {
125
- content += `**${workflow.name}**\n`;
126
- content += `- Path: \`${workflow.displayPath}\`\n`;
127
- content += `- ${workflow.description}\n\n`;
128
- }
129
-
130
- content += `
131
- ## Execution
132
-
133
- When running any workflow:
134
- 1. LOAD the workflow.md file at the path shown above
135
- 2. READ its entire contents and follow its directions exactly
136
- 3. Save outputs after EACH section
137
-
138
- ## Modes
139
- - Normal: Full interaction
140
- - #yolo: Skip optional steps
141
- `;
142
-
143
- return content;
144
- }
145
-
146
- transformWorkflowPath(workflowPath) {
147
- let transformed = workflowPath;
148
-
149
- if (workflowPath.includes('/src/bmm-skills/')) {
150
- const match = workflowPath.match(/\/src\/bmm-skills\/(.+)/);
151
- if (match) {
152
- transformed = `{project-root}/${this.bmadFolderName}/bmm/${match[1]}`;
153
- }
154
- } else if (workflowPath.includes('/src/core-skills/')) {
155
- const match = workflowPath.match(/\/src\/core-skills\/(.+)/);
156
- if (match) {
157
- transformed = `{project-root}/${this.bmadFolderName}/core/${match[1]}`;
158
- }
159
- }
160
-
161
- return transformed;
162
- }
163
-
164
- async loadWorkflowManifest(bmadDir) {
165
- const manifestPath = path.join(bmadDir, '_config', 'workflow-manifest.csv');
166
-
167
- if (!(await fs.pathExists(manifestPath))) {
168
- return null;
169
- }
170
-
171
- const csvContent = await fs.readFile(manifestPath, 'utf8');
172
- return csv.parse(csvContent, {
173
- columns: true,
174
- skip_empty_lines: true,
175
- });
176
- }
177
- }
178
-
179
- module.exports = { WorkflowCommandGenerator };