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 +1 -1
- package/tools/cli/installers/lib/ide/_config-driven.js +12 -167
- package/tools/cli/installers/lib/ide/manager.js +2 -7
- package/tools/cli/installers/lib/ide/shared/agent-command-generator.js +0 -1
- package/tools/cli/installers/lib/ide/shared/task-tool-command-generator.js +0 -368
- package/tools/cli/installers/lib/ide/shared/workflow-command-generator.js +0 -179
package/package.json
CHANGED
|
@@ -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
|
|
115
|
+
const { target_dir } = config;
|
|
119
116
|
|
|
120
|
-
|
|
121
|
-
|
|
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
|
-
|
|
131
|
-
const results = {
|
|
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
|
-
|
|
161
|
-
|
|
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 = {
|
|
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
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
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
|
|
165
|
-
|
|
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 };
|