bmad-method 4.6.3 → 4.8.0
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/.prettierignore +0 -1
- package/CHANGELOG.md +24 -0
- package/README.md +18 -1
- package/bmad-core/agents/analyst.md +11 -8
- package/bmad-core/agents/architect.md +10 -7
- package/bmad-core/agents/bmad-master.md +13 -11
- package/bmad-core/agents/bmad-orchestrator.md +3 -0
- package/bmad-core/agents/dev.md +19 -14
- package/bmad-core/agents/pm.md +8 -5
- package/bmad-core/agents/po.md +13 -10
- package/bmad-core/agents/qa.md +8 -5
- package/bmad-core/agents/sm.md +15 -25
- package/bmad-core/agents/ux-expert.md +11 -8
- package/bmad-core/core-config.yml +24 -0
- package/bmad-core/data/bmad-kb.md +391 -10
- package/bmad-core/tasks/create-next-story.md +63 -45
- package/bmad-core/utils/file-resolution-context.md +10 -0
- package/dist/agents/analyst.txt +404 -17
- package/dist/agents/architect.txt +14 -6
- package/dist/agents/bmad-master.txt +485 -109
- package/dist/agents/bmad-orchestrator.txt +402 -22
- package/dist/agents/dev.txt +26 -34
- package/dist/agents/pm.txt +9 -5
- package/dist/agents/po.txt +10 -10
- package/dist/agents/qa.txt +4 -4
- package/dist/agents/sm.txt +74 -63
- package/dist/agents/ux-expert.txt +9 -7
- package/dist/expansion-packs/bmad-2d-phaser-game-dev/teams/phaser-2d-nodejs-game-team.txt +49 -20
- package/dist/expansion-packs/expansion-creator/agents/bmad-the-creator.txt +6 -3
- package/dist/teams/team-all.txt +561 -158
- package/dist/teams/team-fullstack.txt +457 -57
- package/dist/teams/team-ide-minimal.txt +516 -133
- package/dist/teams/team-no-ui.txt +448 -50
- package/docs/bmad-workflow-guide.md +10 -8
- package/docs/claude-code-guide.md +1 -1
- package/docs/core-architecture.md +4 -4
- package/docs/cursor-guide.md +1 -1
- package/docs/roo-code-guide.md +1 -1
- package/docs/user-guide.md +30 -21
- package/docs/windsurf-guide.md +1 -1
- package/expansion-packs/bmad-infrastructure-devops/data/bmad-kb.md +3 -0
- package/expansion-packs/expansion-creator/templates/agent-tmpl.md +4 -1
- package/package.json +1 -1
- package/tools/builders/web-builder.js +158 -94
- package/tools/installer/bin/bmad.js +34 -2
- package/tools/installer/lib/file-manager.js +3 -2
- package/tools/installer/lib/ide-setup.js +39 -123
- package/tools/installer/lib/installer.js +13 -1
- package/tools/installer/package.json +1 -1
- /package/bmad-core/{templates/web-agent-startup-instructions-template.md → utils/web-agent-startup-instructions.md} +0 -0
|
@@ -1,19 +1,22 @@
|
|
|
1
|
-
const fs = require(
|
|
2
|
-
const path = require(
|
|
3
|
-
const DependencyResolver = require(
|
|
1
|
+
const fs = require("node:fs").promises;
|
|
2
|
+
const path = require("node:path");
|
|
3
|
+
const DependencyResolver = require("../lib/dependency-resolver");
|
|
4
4
|
|
|
5
5
|
class WebBuilder {
|
|
6
6
|
constructor(options = {}) {
|
|
7
7
|
this.rootDir = options.rootDir || process.cwd();
|
|
8
|
-
this.outputDirs = options.outputDirs || [
|
|
9
|
-
path.join(this.rootDir, 'dist')
|
|
10
|
-
];
|
|
8
|
+
this.outputDirs = options.outputDirs || [path.join(this.rootDir, "dist")];
|
|
11
9
|
this.resolver = new DependencyResolver(this.rootDir);
|
|
12
|
-
this.templatePath = path.join(
|
|
10
|
+
this.templatePath = path.join(
|
|
11
|
+
this.rootDir,
|
|
12
|
+
"bmad-core",
|
|
13
|
+
"utils",
|
|
14
|
+
"web-agent-startup-instructions.md"
|
|
15
|
+
);
|
|
13
16
|
}
|
|
14
17
|
|
|
15
18
|
parseYaml(content) {
|
|
16
|
-
const yaml = require(
|
|
19
|
+
const yaml = require("js-yaml");
|
|
17
20
|
return yaml.load(content);
|
|
18
21
|
}
|
|
19
22
|
|
|
@@ -38,10 +41,10 @@ class WebBuilder {
|
|
|
38
41
|
|
|
39
42
|
// Write to all output directories
|
|
40
43
|
for (const outputDir of this.outputDirs) {
|
|
41
|
-
const outputPath = path.join(outputDir,
|
|
44
|
+
const outputPath = path.join(outputDir, "agents");
|
|
42
45
|
await fs.mkdir(outputPath, { recursive: true });
|
|
43
46
|
const outputFile = path.join(outputPath, `${agentId}.txt`);
|
|
44
|
-
await fs.writeFile(outputFile, bundle,
|
|
47
|
+
await fs.writeFile(outputFile, bundle, "utf8");
|
|
45
48
|
}
|
|
46
49
|
}
|
|
47
50
|
|
|
@@ -57,10 +60,10 @@ class WebBuilder {
|
|
|
57
60
|
|
|
58
61
|
// Write to all output directories
|
|
59
62
|
for (const outputDir of this.outputDirs) {
|
|
60
|
-
const outputPath = path.join(outputDir,
|
|
63
|
+
const outputPath = path.join(outputDir, "teams");
|
|
61
64
|
await fs.mkdir(outputPath, { recursive: true });
|
|
62
65
|
const outputFile = path.join(outputPath, `${teamId}.txt`);
|
|
63
|
-
await fs.writeFile(outputFile, bundle,
|
|
66
|
+
await fs.writeFile(outputFile, bundle, "utf8");
|
|
64
67
|
}
|
|
65
68
|
}
|
|
66
69
|
|
|
@@ -69,7 +72,7 @@ class WebBuilder {
|
|
|
69
72
|
|
|
70
73
|
async buildAgentBundle(agentId) {
|
|
71
74
|
const dependencies = await this.resolver.resolveAgentDependencies(agentId);
|
|
72
|
-
const template = await fs.readFile(this.templatePath,
|
|
75
|
+
const template = await fs.readFile(this.templatePath, "utf8");
|
|
73
76
|
|
|
74
77
|
const sections = [template];
|
|
75
78
|
|
|
@@ -81,12 +84,12 @@ class WebBuilder {
|
|
|
81
84
|
sections.push(this.formatSection(resource.path, resource.content));
|
|
82
85
|
}
|
|
83
86
|
|
|
84
|
-
return sections.join(
|
|
87
|
+
return sections.join("\n");
|
|
85
88
|
}
|
|
86
89
|
|
|
87
90
|
async buildTeamBundle(teamId) {
|
|
88
91
|
const dependencies = await this.resolver.resolveTeamDependencies(teamId);
|
|
89
|
-
const template = await fs.readFile(this.templatePath,
|
|
92
|
+
const template = await fs.readFile(this.templatePath, "utf8");
|
|
90
93
|
|
|
91
94
|
const sections = [template];
|
|
92
95
|
|
|
@@ -103,21 +106,72 @@ class WebBuilder {
|
|
|
103
106
|
sections.push(this.formatSection(resource.path, resource.content));
|
|
104
107
|
}
|
|
105
108
|
|
|
106
|
-
return sections.join(
|
|
109
|
+
return sections.join("\n");
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
processAgentContent(content) {
|
|
113
|
+
// First, replace content before YAML with the template
|
|
114
|
+
const yamlMatch = content.match(/```ya?ml\n([\s\S]*?)\n```/);
|
|
115
|
+
if (!yamlMatch) return content;
|
|
116
|
+
|
|
117
|
+
const yamlContent = yamlMatch[1];
|
|
118
|
+
const yamlStartIndex = content.indexOf(yamlMatch[0]);
|
|
119
|
+
const yamlEndIndex = yamlStartIndex + yamlMatch[0].length;
|
|
120
|
+
|
|
121
|
+
// Parse YAML and remove root and IDE-FILE-RESOLUTION properties
|
|
122
|
+
try {
|
|
123
|
+
const yaml = require("js-yaml");
|
|
124
|
+
const parsed = yaml.load(yamlContent);
|
|
125
|
+
|
|
126
|
+
// Remove the properties if they exist at root level
|
|
127
|
+
delete parsed.root;
|
|
128
|
+
delete parsed['IDE-FILE-RESOLUTION'];
|
|
129
|
+
delete parsed['REQUEST-RESOLUTION'];
|
|
130
|
+
|
|
131
|
+
// Also remove from activation-instructions if they exist
|
|
132
|
+
if (parsed['activation-instructions'] && Array.isArray(parsed['activation-instructions'])) {
|
|
133
|
+
parsed['activation-instructions'] = parsed['activation-instructions'].filter(instruction => {
|
|
134
|
+
return !instruction.startsWith('IDE-FILE-RESOLUTION:') &&
|
|
135
|
+
!instruction.startsWith('REQUEST-RESOLUTION:');
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Reconstruct the YAML
|
|
140
|
+
const cleanedYaml = yaml.dump(parsed, { lineWidth: -1 });
|
|
141
|
+
|
|
142
|
+
// Get the agent name from the YAML for the header
|
|
143
|
+
const agentName = parsed.agent?.id || 'agent';
|
|
144
|
+
|
|
145
|
+
// Build the new content with just the agent header and YAML
|
|
146
|
+
const newHeader = `# ${agentName}\n\nCRITICAL: Read the full YML, start activation to alter your state of being, follow startup section instructions, stay in this being until told to exit this mode:\n\n`;
|
|
147
|
+
const afterYaml = content.substring(yamlEndIndex);
|
|
148
|
+
|
|
149
|
+
return newHeader + "```yaml\n" + cleanedYaml.trim() + "\n```" + afterYaml;
|
|
150
|
+
} catch (error) {
|
|
151
|
+
console.warn("Failed to process agent YAML:", error.message);
|
|
152
|
+
// If parsing fails, return original content
|
|
153
|
+
return content;
|
|
154
|
+
}
|
|
107
155
|
}
|
|
108
156
|
|
|
109
157
|
formatSection(path, content) {
|
|
110
|
-
const separator =
|
|
158
|
+
const separator = "====================";
|
|
159
|
+
|
|
160
|
+
// Process agent content if this is an agent file
|
|
161
|
+
if (path.startsWith("agents#")) {
|
|
162
|
+
content = this.processAgentContent(content);
|
|
163
|
+
}
|
|
164
|
+
|
|
111
165
|
return [
|
|
112
166
|
`${separator} START: ${path} ${separator}`,
|
|
113
167
|
content.trim(),
|
|
114
168
|
`${separator} END: ${path} ${separator}`,
|
|
115
|
-
|
|
116
|
-
].join(
|
|
169
|
+
"",
|
|
170
|
+
].join("\n");
|
|
117
171
|
}
|
|
118
172
|
|
|
119
173
|
async validate() {
|
|
120
|
-
console.log(
|
|
174
|
+
console.log("Validating agent configurations...");
|
|
121
175
|
const agents = await this.resolver.listAgents();
|
|
122
176
|
for (const agentId of agents) {
|
|
123
177
|
try {
|
|
@@ -129,7 +183,7 @@ class WebBuilder {
|
|
|
129
183
|
}
|
|
130
184
|
}
|
|
131
185
|
|
|
132
|
-
console.log(
|
|
186
|
+
console.log("\nValidating team configurations...");
|
|
133
187
|
const teams = await this.resolver.listTeams();
|
|
134
188
|
for (const teamId of teams) {
|
|
135
189
|
try {
|
|
@@ -154,10 +208,8 @@ class WebBuilder {
|
|
|
154
208
|
}
|
|
155
209
|
|
|
156
210
|
async buildExpansionPack(packName, options = {}) {
|
|
157
|
-
const packDir = path.join(this.rootDir,
|
|
158
|
-
const outputDirs = [
|
|
159
|
-
path.join(this.rootDir, 'dist', 'expansion-packs', packName)
|
|
160
|
-
];
|
|
211
|
+
const packDir = path.join(this.rootDir, "expansion-packs", packName);
|
|
212
|
+
const outputDirs = [path.join(this.rootDir, "dist", "expansion-packs", packName)];
|
|
161
213
|
|
|
162
214
|
// Clean output directories if requested
|
|
163
215
|
if (options.clean !== false) {
|
|
@@ -171,27 +223,27 @@ class WebBuilder {
|
|
|
171
223
|
}
|
|
172
224
|
|
|
173
225
|
// Build individual agents first
|
|
174
|
-
const agentsDir = path.join(packDir,
|
|
226
|
+
const agentsDir = path.join(packDir, "agents");
|
|
175
227
|
try {
|
|
176
228
|
const agentFiles = await fs.readdir(agentsDir);
|
|
177
|
-
const agentMarkdownFiles = agentFiles.filter(f => f.endsWith(
|
|
178
|
-
|
|
229
|
+
const agentMarkdownFiles = agentFiles.filter((f) => f.endsWith(".md"));
|
|
230
|
+
|
|
179
231
|
if (agentMarkdownFiles.length > 0) {
|
|
180
232
|
console.log(` Building individual agents for ${packName}:`);
|
|
181
|
-
|
|
233
|
+
|
|
182
234
|
for (const agentFile of agentMarkdownFiles) {
|
|
183
|
-
const agentName = agentFile.replace(
|
|
235
|
+
const agentName = agentFile.replace(".md", "");
|
|
184
236
|
console.log(` - ${agentName}`);
|
|
185
|
-
|
|
237
|
+
|
|
186
238
|
// Build individual agent bundle
|
|
187
239
|
const bundle = await this.buildExpansionAgentBundle(packName, packDir, agentName);
|
|
188
|
-
|
|
240
|
+
|
|
189
241
|
// Write to all output directories
|
|
190
242
|
for (const outputDir of outputDirs) {
|
|
191
|
-
const agentsOutputDir = path.join(outputDir,
|
|
243
|
+
const agentsOutputDir = path.join(outputDir, "agents");
|
|
192
244
|
await fs.mkdir(agentsOutputDir, { recursive: true });
|
|
193
245
|
const outputFile = path.join(agentsOutputDir, `${agentName}.txt`);
|
|
194
|
-
await fs.writeFile(outputFile, bundle,
|
|
246
|
+
await fs.writeFile(outputFile, bundle, "utf8");
|
|
195
247
|
}
|
|
196
248
|
}
|
|
197
249
|
}
|
|
@@ -200,24 +252,24 @@ class WebBuilder {
|
|
|
200
252
|
}
|
|
201
253
|
|
|
202
254
|
// Build team bundle
|
|
203
|
-
const agentTeamsDir = path.join(packDir,
|
|
255
|
+
const agentTeamsDir = path.join(packDir, "agent-teams");
|
|
204
256
|
try {
|
|
205
257
|
const teamFiles = await fs.readdir(agentTeamsDir);
|
|
206
|
-
const teamFile = teamFiles.find(f => f.endsWith(
|
|
207
|
-
|
|
258
|
+
const teamFile = teamFiles.find((f) => f.endsWith(".yml"));
|
|
259
|
+
|
|
208
260
|
if (teamFile) {
|
|
209
261
|
console.log(` Building team bundle for ${packName}`);
|
|
210
262
|
const teamConfigPath = path.join(agentTeamsDir, teamFile);
|
|
211
|
-
|
|
263
|
+
|
|
212
264
|
// Build expansion pack as a team bundle
|
|
213
265
|
const bundle = await this.buildExpansionTeamBundle(packName, packDir, teamConfigPath);
|
|
214
|
-
|
|
266
|
+
|
|
215
267
|
// Write to all output directories
|
|
216
268
|
for (const outputDir of outputDirs) {
|
|
217
|
-
const teamsOutputDir = path.join(outputDir,
|
|
269
|
+
const teamsOutputDir = path.join(outputDir, "teams");
|
|
218
270
|
await fs.mkdir(teamsOutputDir, { recursive: true });
|
|
219
|
-
const outputFile = path.join(teamsOutputDir, teamFile.replace(
|
|
220
|
-
await fs.writeFile(outputFile, bundle,
|
|
271
|
+
const outputFile = path.join(teamsOutputDir, teamFile.replace(".yml", ".txt"));
|
|
272
|
+
await fs.writeFile(outputFile, bundle, "utf8");
|
|
221
273
|
console.log(` ✓ Created bundle: ${path.relative(this.rootDir, outputFile)}`);
|
|
222
274
|
}
|
|
223
275
|
} else {
|
|
@@ -229,49 +281,58 @@ class WebBuilder {
|
|
|
229
281
|
}
|
|
230
282
|
|
|
231
283
|
async buildExpansionAgentBundle(packName, packDir, agentName) {
|
|
232
|
-
const template = await fs.readFile(this.templatePath,
|
|
284
|
+
const template = await fs.readFile(this.templatePath, "utf8");
|
|
233
285
|
const sections = [template];
|
|
234
286
|
|
|
235
287
|
// Add agent configuration
|
|
236
|
-
const agentPath = path.join(packDir,
|
|
237
|
-
const agentContent = await fs.readFile(agentPath,
|
|
288
|
+
const agentPath = path.join(packDir, "agents", `${agentName}.md`);
|
|
289
|
+
const agentContent = await fs.readFile(agentPath, "utf8");
|
|
238
290
|
sections.push(this.formatSection(`agents#${agentName}`, agentContent));
|
|
239
291
|
|
|
240
292
|
// Resolve and add agent dependencies
|
|
241
293
|
const agentYaml = agentContent.match(/```yaml\n([\s\S]*?)\n```/);
|
|
242
294
|
if (agentYaml) {
|
|
243
295
|
try {
|
|
244
|
-
const yaml = require(
|
|
296
|
+
const yaml = require("js-yaml");
|
|
245
297
|
const agentConfig = yaml.load(agentYaml[1]);
|
|
246
|
-
|
|
298
|
+
|
|
247
299
|
if (agentConfig.dependencies) {
|
|
248
300
|
// Add resources, first try expansion pack, then core
|
|
249
301
|
for (const [resourceType, resources] of Object.entries(agentConfig.dependencies)) {
|
|
250
302
|
if (Array.isArray(resources)) {
|
|
251
303
|
for (const resourceName of resources) {
|
|
252
304
|
let found = false;
|
|
253
|
-
const extensions = [
|
|
254
|
-
|
|
305
|
+
const extensions = [".md", ".yml", ".yaml"];
|
|
306
|
+
|
|
255
307
|
// Try expansion pack first
|
|
256
308
|
for (const ext of extensions) {
|
|
257
309
|
const resourcePath = path.join(packDir, resourceType, `${resourceName}${ext}`);
|
|
258
310
|
try {
|
|
259
|
-
const resourceContent = await fs.readFile(resourcePath,
|
|
260
|
-
sections.push(
|
|
311
|
+
const resourceContent = await fs.readFile(resourcePath, "utf8");
|
|
312
|
+
sections.push(
|
|
313
|
+
this.formatSection(`${resourceType}#${resourceName}`, resourceContent)
|
|
314
|
+
);
|
|
261
315
|
found = true;
|
|
262
316
|
break;
|
|
263
317
|
} catch (error) {
|
|
264
318
|
// Not in expansion pack, continue
|
|
265
319
|
}
|
|
266
320
|
}
|
|
267
|
-
|
|
321
|
+
|
|
268
322
|
// If not found in expansion pack, try core
|
|
269
323
|
if (!found) {
|
|
270
324
|
for (const ext of extensions) {
|
|
271
|
-
const corePath = path.join(
|
|
325
|
+
const corePath = path.join(
|
|
326
|
+
this.rootDir,
|
|
327
|
+
"bmad-core",
|
|
328
|
+
resourceType,
|
|
329
|
+
`${resourceName}${ext}`
|
|
330
|
+
);
|
|
272
331
|
try {
|
|
273
|
-
const coreContent = await fs.readFile(corePath,
|
|
274
|
-
sections.push(
|
|
332
|
+
const coreContent = await fs.readFile(corePath, "utf8");
|
|
333
|
+
sections.push(
|
|
334
|
+
this.formatSection(`${resourceType}#${resourceName}`, coreContent)
|
|
335
|
+
);
|
|
275
336
|
found = true;
|
|
276
337
|
break;
|
|
277
338
|
} catch (error) {
|
|
@@ -279,9 +340,11 @@ class WebBuilder {
|
|
|
279
340
|
}
|
|
280
341
|
}
|
|
281
342
|
}
|
|
282
|
-
|
|
343
|
+
|
|
283
344
|
if (!found) {
|
|
284
|
-
console.warn(
|
|
345
|
+
console.warn(
|
|
346
|
+
` ⚠ Dependency ${resourceType}#${resourceName} not found in expansion pack or core`
|
|
347
|
+
);
|
|
285
348
|
}
|
|
286
349
|
}
|
|
287
350
|
}
|
|
@@ -292,27 +355,27 @@ class WebBuilder {
|
|
|
292
355
|
}
|
|
293
356
|
}
|
|
294
357
|
|
|
295
|
-
return sections.join(
|
|
358
|
+
return sections.join("\n");
|
|
296
359
|
}
|
|
297
360
|
|
|
298
361
|
async buildExpansionTeamBundle(packName, packDir, teamConfigPath) {
|
|
299
|
-
const template = await fs.readFile(this.templatePath,
|
|
362
|
+
const template = await fs.readFile(this.templatePath, "utf8");
|
|
300
363
|
|
|
301
364
|
const sections = [template];
|
|
302
365
|
|
|
303
366
|
// Add team configuration and parse to get agent list
|
|
304
|
-
const teamContent = await fs.readFile(teamConfigPath,
|
|
305
|
-
const teamFileName = path.basename(teamConfigPath,
|
|
367
|
+
const teamContent = await fs.readFile(teamConfigPath, "utf8");
|
|
368
|
+
const teamFileName = path.basename(teamConfigPath, ".yml");
|
|
306
369
|
const teamConfig = this.parseYaml(teamContent);
|
|
307
370
|
sections.push(this.formatSection(`agent-teams#${teamFileName}`, teamContent));
|
|
308
371
|
|
|
309
372
|
// Get list of expansion pack agents
|
|
310
373
|
const expansionAgents = new Set();
|
|
311
|
-
const agentsDir = path.join(packDir,
|
|
374
|
+
const agentsDir = path.join(packDir, "agents");
|
|
312
375
|
try {
|
|
313
376
|
const agentFiles = await fs.readdir(agentsDir);
|
|
314
|
-
for (const agentFile of agentFiles.filter(f => f.endsWith(
|
|
315
|
-
const agentName = agentFile.replace(
|
|
377
|
+
for (const agentFile of agentFiles.filter((f) => f.endsWith(".md"))) {
|
|
378
|
+
const agentName = agentFile.replace(".md", "");
|
|
316
379
|
expansionAgents.add(agentName);
|
|
317
380
|
}
|
|
318
381
|
} catch (error) {
|
|
@@ -321,13 +384,15 @@ class WebBuilder {
|
|
|
321
384
|
|
|
322
385
|
// Build a map of all available expansion pack resources for override checking
|
|
323
386
|
const expansionResources = new Map();
|
|
324
|
-
const resourceDirs = [
|
|
387
|
+
const resourceDirs = ["templates", "tasks", "checklists", "workflows", "data"];
|
|
325
388
|
for (const resourceDir of resourceDirs) {
|
|
326
389
|
const resourcePath = path.join(packDir, resourceDir);
|
|
327
390
|
try {
|
|
328
391
|
const resourceFiles = await fs.readdir(resourcePath);
|
|
329
|
-
for (const resourceFile of resourceFiles.filter(
|
|
330
|
-
|
|
392
|
+
for (const resourceFile of resourceFiles.filter(
|
|
393
|
+
(f) => f.endsWith(".md") || f.endsWith(".yml")
|
|
394
|
+
)) {
|
|
395
|
+
const fileName = resourceFile.replace(/\.(md|yml)$/, "");
|
|
331
396
|
expansionResources.set(`${resourceDir}#${fileName}`, true);
|
|
332
397
|
}
|
|
333
398
|
} catch (error) {
|
|
@@ -337,22 +402,21 @@ class WebBuilder {
|
|
|
337
402
|
|
|
338
403
|
// Process all agents listed in team configuration
|
|
339
404
|
const agentsToProcess = teamConfig.agents || [];
|
|
340
|
-
|
|
405
|
+
|
|
341
406
|
// Ensure bmad-orchestrator is always included for teams
|
|
342
|
-
if (!agentsToProcess.includes(
|
|
407
|
+
if (!agentsToProcess.includes("bmad-orchestrator")) {
|
|
343
408
|
console.warn(` ⚠ Team ${teamFileName} missing bmad-orchestrator, adding automatically`);
|
|
344
|
-
agentsToProcess.unshift(
|
|
409
|
+
agentsToProcess.unshift("bmad-orchestrator");
|
|
345
410
|
}
|
|
346
411
|
|
|
347
412
|
// Track all dependencies from all agents (deduplicated)
|
|
348
413
|
const allDependencies = new Map();
|
|
349
414
|
|
|
350
415
|
for (const agentId of agentsToProcess) {
|
|
351
|
-
|
|
352
416
|
if (expansionAgents.has(agentId)) {
|
|
353
417
|
// Use expansion pack version (override)
|
|
354
418
|
const agentPath = path.join(agentsDir, `${agentId}.md`);
|
|
355
|
-
const agentContent = await fs.readFile(agentPath,
|
|
419
|
+
const agentContent = await fs.readFile(agentPath, "utf8");
|
|
356
420
|
sections.push(this.formatSection(`agents#${agentId}`, agentContent));
|
|
357
421
|
|
|
358
422
|
// Parse and collect dependencies from expansion agent
|
|
@@ -379,8 +443,8 @@ class WebBuilder {
|
|
|
379
443
|
} else {
|
|
380
444
|
// Use core BMAD version
|
|
381
445
|
try {
|
|
382
|
-
const coreAgentPath = path.join(this.rootDir,
|
|
383
|
-
const coreAgentContent = await fs.readFile(coreAgentPath,
|
|
446
|
+
const coreAgentPath = path.join(this.rootDir, "bmad-core", "agents", `${agentId}.md`);
|
|
447
|
+
const coreAgentContent = await fs.readFile(coreAgentPath, "utf8");
|
|
384
448
|
sections.push(this.formatSection(`agents#${agentId}`, coreAgentContent));
|
|
385
449
|
|
|
386
450
|
// Parse and collect dependencies from core agent
|
|
@@ -389,8 +453,8 @@ class WebBuilder {
|
|
|
389
453
|
try {
|
|
390
454
|
// Clean up the YAML to handle command descriptions after dashes
|
|
391
455
|
let yamlContent = agentYaml[1];
|
|
392
|
-
yamlContent = yamlContent.replace(/^(\s*-)(\s*"[^"]+")(\s*-\s*.*)$/gm,
|
|
393
|
-
|
|
456
|
+
yamlContent = yamlContent.replace(/^(\s*-)(\s*"[^"]+")(\s*-\s*.*)$/gm, "$1$2");
|
|
457
|
+
|
|
394
458
|
const agentConfig = this.parseYaml(yamlContent);
|
|
395
459
|
if (agentConfig.dependencies) {
|
|
396
460
|
for (const [resourceType, resources] of Object.entries(agentConfig.dependencies)) {
|
|
@@ -418,15 +482,15 @@ class WebBuilder {
|
|
|
418
482
|
// Always prefer expansion pack versions if they exist
|
|
419
483
|
for (const [key, dep] of allDependencies) {
|
|
420
484
|
let found = false;
|
|
421
|
-
const extensions = [
|
|
422
|
-
|
|
485
|
+
const extensions = [".md", ".yml", ".yaml"];
|
|
486
|
+
|
|
423
487
|
// Always check expansion pack first, even if the dependency came from a core agent
|
|
424
488
|
if (expansionResources.has(key)) {
|
|
425
489
|
// We know it exists in expansion pack, find and load it
|
|
426
490
|
for (const ext of extensions) {
|
|
427
491
|
const expansionPath = path.join(packDir, dep.type, `${dep.name}${ext}`);
|
|
428
492
|
try {
|
|
429
|
-
const content = await fs.readFile(expansionPath,
|
|
493
|
+
const content = await fs.readFile(expansionPath, "utf8");
|
|
430
494
|
sections.push(this.formatSection(key, content));
|
|
431
495
|
console.log(` ✓ Using expansion override for ${key}`);
|
|
432
496
|
found = true;
|
|
@@ -436,13 +500,13 @@ class WebBuilder {
|
|
|
436
500
|
}
|
|
437
501
|
}
|
|
438
502
|
}
|
|
439
|
-
|
|
503
|
+
|
|
440
504
|
// If not found in expansion pack (or doesn't exist there), try core
|
|
441
505
|
if (!found) {
|
|
442
506
|
for (const ext of extensions) {
|
|
443
|
-
const corePath = path.join(this.rootDir,
|
|
507
|
+
const corePath = path.join(this.rootDir, "bmad-core", dep.type, `${dep.name}${ext}`);
|
|
444
508
|
try {
|
|
445
|
-
const content = await fs.readFile(corePath,
|
|
509
|
+
const content = await fs.readFile(corePath, "utf8");
|
|
446
510
|
sections.push(this.formatSection(key, content));
|
|
447
511
|
found = true;
|
|
448
512
|
break;
|
|
@@ -451,7 +515,7 @@ class WebBuilder {
|
|
|
451
515
|
}
|
|
452
516
|
}
|
|
453
517
|
}
|
|
454
|
-
|
|
518
|
+
|
|
455
519
|
if (!found) {
|
|
456
520
|
console.warn(` ⚠ Dependency ${key} not found in expansion pack or core`);
|
|
457
521
|
}
|
|
@@ -462,11 +526,13 @@ class WebBuilder {
|
|
|
462
526
|
const resourcePath = path.join(packDir, resourceDir);
|
|
463
527
|
try {
|
|
464
528
|
const resourceFiles = await fs.readdir(resourcePath);
|
|
465
|
-
for (const resourceFile of resourceFiles.filter(
|
|
529
|
+
for (const resourceFile of resourceFiles.filter(
|
|
530
|
+
(f) => f.endsWith(".md") || f.endsWith(".yml")
|
|
531
|
+
)) {
|
|
466
532
|
const filePath = path.join(resourcePath, resourceFile);
|
|
467
|
-
const fileContent = await fs.readFile(filePath,
|
|
468
|
-
const fileName = resourceFile.replace(/\.(md|yml)$/,
|
|
469
|
-
|
|
533
|
+
const fileContent = await fs.readFile(filePath, "utf8");
|
|
534
|
+
const fileName = resourceFile.replace(/\.(md|yml)$/, "");
|
|
535
|
+
|
|
470
536
|
// Only add if not already included as a dependency
|
|
471
537
|
const resourceKey = `${resourceDir}#${fileName}`;
|
|
472
538
|
if (!allDependencies.has(resourceKey)) {
|
|
@@ -478,18 +544,16 @@ class WebBuilder {
|
|
|
478
544
|
}
|
|
479
545
|
}
|
|
480
546
|
|
|
481
|
-
return sections.join(
|
|
547
|
+
return sections.join("\n");
|
|
482
548
|
}
|
|
483
549
|
|
|
484
550
|
async listExpansionPacks() {
|
|
485
|
-
const expansionPacksDir = path.join(this.rootDir,
|
|
551
|
+
const expansionPacksDir = path.join(this.rootDir, "expansion-packs");
|
|
486
552
|
try {
|
|
487
553
|
const entries = await fs.readdir(expansionPacksDir, { withFileTypes: true });
|
|
488
|
-
return entries
|
|
489
|
-
.filter(entry => entry.isDirectory())
|
|
490
|
-
.map(entry => entry.name);
|
|
554
|
+
return entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name);
|
|
491
555
|
} catch (error) {
|
|
492
|
-
console.warn(
|
|
556
|
+
console.warn("No expansion-packs directory found");
|
|
493
557
|
return [];
|
|
494
558
|
}
|
|
495
559
|
}
|
|
@@ -499,4 +563,4 @@ class WebBuilder {
|
|
|
499
563
|
}
|
|
500
564
|
}
|
|
501
565
|
|
|
502
|
-
module.exports = WebBuilder;
|
|
566
|
+
module.exports = WebBuilder;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
const { program } = require('commander');
|
|
4
|
+
const path = require('path');
|
|
4
5
|
|
|
5
6
|
// Dynamic imports for ES modules
|
|
6
7
|
let chalk, inquirer;
|
|
@@ -57,7 +58,9 @@ program
|
|
|
57
58
|
if (!options.full && !options.agent && !options.team && !options.expansionOnly) {
|
|
58
59
|
// Interactive mode
|
|
59
60
|
const answers = await promptInstallation();
|
|
60
|
-
|
|
61
|
+
if (!answers._alreadyInstalled) {
|
|
62
|
+
await installer.install(answers);
|
|
63
|
+
}
|
|
61
64
|
} else {
|
|
62
65
|
// Direct mode
|
|
63
66
|
let installType = 'full';
|
|
@@ -158,6 +161,35 @@ async function promptInstallation() {
|
|
|
158
161
|
]);
|
|
159
162
|
answers.directory = directory;
|
|
160
163
|
|
|
164
|
+
// Check if this is an existing v4 installation
|
|
165
|
+
const installDir = path.resolve(answers.directory);
|
|
166
|
+
const state = await installer.detectInstallationState(installDir);
|
|
167
|
+
|
|
168
|
+
if (state.type === 'v4_existing') {
|
|
169
|
+
console.log(chalk.yellow('\n🔍 Found existing BMAD v4 installation'));
|
|
170
|
+
console.log(` Directory: ${installDir}`);
|
|
171
|
+
console.log(` Version: ${state.manifest?.version || 'Unknown'}`);
|
|
172
|
+
console.log(` Installed: ${state.manifest?.installed_at ? new Date(state.manifest.installed_at).toLocaleDateString() : 'Unknown'}`);
|
|
173
|
+
|
|
174
|
+
const { shouldUpdate } = await inquirer.prompt([
|
|
175
|
+
{
|
|
176
|
+
type: 'confirm',
|
|
177
|
+
name: 'shouldUpdate',
|
|
178
|
+
message: 'Would you like to update your existing BMAD v4 installation?',
|
|
179
|
+
default: true
|
|
180
|
+
}
|
|
181
|
+
]);
|
|
182
|
+
|
|
183
|
+
if (shouldUpdate) {
|
|
184
|
+
// Skip other prompts and go directly to update
|
|
185
|
+
answers.installType = 'update';
|
|
186
|
+
answers._alreadyInstalled = true; // Flag to prevent double installation
|
|
187
|
+
await installer.install(answers);
|
|
188
|
+
return answers; // Return the answers object
|
|
189
|
+
}
|
|
190
|
+
// If user doesn't want to update, continue with normal flow
|
|
191
|
+
}
|
|
192
|
+
|
|
161
193
|
// Ask for installation type
|
|
162
194
|
const { installType } = await inquirer.prompt([
|
|
163
195
|
{
|
|
@@ -373,7 +405,7 @@ async function promptInstallation() {
|
|
|
373
405
|
type: 'input',
|
|
374
406
|
name: 'webBundlesDirectory',
|
|
375
407
|
message: 'Enter directory for web bundles:',
|
|
376
|
-
default: `${directory}/web-bundles`,
|
|
408
|
+
default: `${answers.directory}/web-bundles`,
|
|
377
409
|
validate: (input) => {
|
|
378
410
|
if (!input.trim()) {
|
|
379
411
|
return 'Please enter a valid directory path';
|
|
@@ -2,6 +2,7 @@ const fs = require("fs-extra");
|
|
|
2
2
|
const path = require("path");
|
|
3
3
|
const crypto = require("crypto");
|
|
4
4
|
const glob = require("glob");
|
|
5
|
+
const yaml = require("js-yaml");
|
|
5
6
|
|
|
6
7
|
// Dynamic import for ES module
|
|
7
8
|
let chalk;
|
|
@@ -106,7 +107,7 @@ class FileManager {
|
|
|
106
107
|
|
|
107
108
|
// Write manifest
|
|
108
109
|
await fs.ensureDir(path.dirname(manifestPath));
|
|
109
|
-
await fs.writeFile(manifestPath,
|
|
110
|
+
await fs.writeFile(manifestPath, yaml.dump(manifest, { indent: 2 }));
|
|
110
111
|
|
|
111
112
|
return manifest;
|
|
112
113
|
}
|
|
@@ -120,7 +121,7 @@ class FileManager {
|
|
|
120
121
|
|
|
121
122
|
try {
|
|
122
123
|
const content = await fs.readFile(manifestPath, "utf8");
|
|
123
|
-
return
|
|
124
|
+
return yaml.load(content);
|
|
124
125
|
} catch (error) {
|
|
125
126
|
return null;
|
|
126
127
|
}
|