bmad-method 4.7.0 → 4.9.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.
Files changed (49) hide show
  1. package/.prettierignore +0 -1
  2. package/CHANGELOG.md +24 -0
  3. package/README.md +18 -1
  4. package/bmad-core/agents/analyst.md +11 -8
  5. package/bmad-core/agents/architect.md +10 -7
  6. package/bmad-core/agents/bmad-master.md +13 -11
  7. package/bmad-core/agents/bmad-orchestrator.md +3 -0
  8. package/bmad-core/agents/dev.md +20 -27
  9. package/bmad-core/agents/pm.md +8 -5
  10. package/bmad-core/agents/po.md +13 -10
  11. package/bmad-core/agents/qa.md +8 -5
  12. package/bmad-core/agents/sm.md +15 -25
  13. package/bmad-core/agents/ux-expert.md +11 -8
  14. package/bmad-core/core-config.yml +26 -0
  15. package/bmad-core/data/bmad-kb.md +6 -3
  16. package/bmad-core/tasks/create-next-story.md +63 -45
  17. package/bmad-core/utils/file-resolution-context.md +10 -0
  18. package/dist/agents/analyst.txt +404 -17
  19. package/dist/agents/architect.txt +14 -6
  20. package/dist/agents/bmad-master.txt +485 -109
  21. package/dist/agents/bmad-orchestrator.txt +402 -22
  22. package/dist/agents/dev.txt +26 -34
  23. package/dist/agents/pm.txt +9 -5
  24. package/dist/agents/po.txt +10 -10
  25. package/dist/agents/qa.txt +4 -4
  26. package/dist/agents/sm.txt +74 -63
  27. package/dist/agents/ux-expert.txt +9 -7
  28. package/dist/expansion-packs/bmad-2d-phaser-game-dev/teams/phaser-2d-nodejs-game-team.txt +49 -20
  29. package/dist/expansion-packs/expansion-creator/agents/bmad-the-creator.txt +6 -3
  30. package/dist/teams/team-all.txt +561 -158
  31. package/dist/teams/team-fullstack.txt +457 -57
  32. package/dist/teams/team-ide-minimal.txt +516 -133
  33. package/dist/teams/team-no-ui.txt +448 -50
  34. package/docs/bmad-workflow-guide.md +1 -1
  35. package/docs/claude-code-guide.md +1 -1
  36. package/docs/core-architecture.md +4 -4
  37. package/docs/cursor-guide.md +1 -1
  38. package/docs/roo-code-guide.md +1 -1
  39. package/docs/user-guide.md +1 -1
  40. package/docs/windsurf-guide.md +1 -1
  41. package/expansion-packs/expansion-creator/templates/agent-tmpl.md +4 -1
  42. package/package.json +1 -1
  43. package/tools/builders/web-builder.js +158 -94
  44. package/tools/installer/bin/bmad.js +34 -2
  45. package/tools/installer/lib/file-manager.js +3 -2
  46. package/tools/installer/lib/ide-setup.js +39 -123
  47. package/tools/installer/lib/installer.js +13 -1
  48. package/tools/installer/package.json +1 -1
  49. /package/bmad-core/{templates/web-agent-startup-instructions-template.md → utils/web-agent-startup-instructions.md} +0 -0
@@ -23,7 +23,7 @@ For ideation and planning, use Google's Gemini Custom Gem with the team-fullstac
23
23
 
24
24
  1. Open [Google gems](https://gemini.google.com/gems/view)
25
25
  2. Create a new Gem - give it a title and description
26
- 3. Copy the contents of `.<install location>/web-bundles/teams/team-fullstack.txt`
26
+ 3. Copy the contents of `.<install location>/<web-bundles>/teams/team-fullstack.txt` (location can vary if you chose a non default installation location for the bundles) - or just use the bundle premade from the repo dist folder.
27
27
  4. Paste this content into Gemini to set up the team
28
28
 
29
29
  ### Gemini Planning Phase
@@ -35,7 +35,7 @@ graph TD
35
35
  end
36
36
 
37
37
  subgraph Outputs
38
- J[".bmad-core/web-bundles"]
38
+ J["dist"]
39
39
  end
40
40
 
41
41
  B -- defines dependencies for --> E
@@ -133,17 +133,17 @@ The framework is designed for two primary environments: local IDEs and web-based
133
133
 
134
134
  ### 4.1. Web Builder (`tools/builders/web-builder.js`)
135
135
 
136
- - **Purpose**: This Node.js script is responsible for creating the `.txt` bundles found in `.bmad-core/web-bundles/`.
136
+ - **Purpose**: This Node.js script is responsible for creating the `.txt` bundles found in `dist`.
137
137
  - **Process**:
138
138
  1. **Resolves Dependencies**: For a given agent or team, the script reads its definition file.
139
139
  2. It recursively finds all dependent resources (tasks, templates, etc.) that the agent/team needs.
140
140
  3. **Bundles Content**: It reads the content of all these files and concatenates them into a single, large text file, with clear separators indicating the original file path of each section.
141
- 4. **Outputs Bundle**: The final `.txt` file is saved in the `web-bundles` directory, ready to be uploaded to a web UI.
141
+ 4. **Outputs Bundle**: The final `.txt` file is saved in the `dist` directory, ready to be uploaded to a web UI.
142
142
 
143
143
  ### 4.2. Environment-Specific Usage
144
144
 
145
145
  - **For IDEs**: Users interact with the agents directly via their markdown files in `.bmad-core/agents/`. The IDE integration (for Cursor, Claude Code, etc.) knows how to call these agents.
146
- - **For Web UIs**: Users upload a pre-built bundle from `.bmad-core/web-bundles/`. This single file provides the AI with the context of the entire team and all their required tools and knowledge.
146
+ - **For Web UIs**: Users upload a pre-built bundle from `dist`. This single file provides the AI with the context of the entire team and all their required tools and knowledge.
147
147
 
148
148
  ## 5. BMAD Workflows
149
149
 
@@ -23,7 +23,7 @@ For ideation and planning, use Google's Gemini Custom Gem with the team-fullstac
23
23
 
24
24
  1. Open [Google gems](https://gemini.google.com/gems/view)
25
25
  2. Create a new Gem - give it a title and description
26
- 3. Copy the contents of `.<install location>/web-bundles/teams/team-fullstack.txt`
26
+ 3. Copy the contents of `.<install location>/<web-bundles>/teams/team-fullstack.txt` (location can vary if you chose a non default installation location for the bundles) - or just use the bundle premade from the repo dist folder.
27
27
  4. Paste this content into Gemini to set up the team
28
28
 
29
29
  ### Gemini Planning Phase
@@ -23,7 +23,7 @@ For ideation and planning, use Google's Gemini Custom Gem with the team-fullstac
23
23
 
24
24
  1. Open [Google gems](https://gemini.google.com/gems/view)
25
25
  2. Create a new Gem - give it a title and description
26
- 3. Copy the contents of `.<install location>/web-bundles/teams/team-fullstack.txt`
26
+ 3. Copy the contents of `.<install location>/<web-bundles>/teams/team-fullstack.txt` (location can vary if you chose a non default installation location for the bundles) - or just use the bundle premade from the repo dist folder.
27
27
  4. Paste this content into Gemini to set up the team
28
28
 
29
29
  ### Gemini Planning Phase
@@ -46,7 +46,7 @@ BMAD-METHOD (Breakthrough Method of Agile AI-Driven Development) is an AI agent
46
46
 
47
47
  Best for: ChatGPT, Claude, Gemini users
48
48
 
49
- 1. Navigate to `.bmad-core/web-bundles/teams/`
49
+ 1. Navigate to `dist/teams/`
50
50
  2. Copy `team-fullstack.txt` content
51
51
  3. Create new Gemini Gem or CustomGPT
52
52
  4. Upload file with instructions: "Your critical operating instructions are attached, do not break character as directed"
@@ -23,7 +23,7 @@ For ideation and planning, use Google's Gemini Custom Gem with the team-fullstac
23
23
 
24
24
  1. Open [Google gems](https://gemini.google.com/gems/view)
25
25
  2. Create a new Gem - give it a title and description
26
- 3. Copy the contents of `.<install location>/web-bundles/teams/team-fullstack.txt`
26
+ 3. Copy the contents of `.<install location>/<web-bundles>/teams/team-fullstack.txt` (location can vary if you chose a non default installation location for the bundles) - or just use the bundle premade from the repo dist folder.
27
27
  4. Paste this content into Gemini to set up the team
28
28
 
29
29
  ### Gemini Planning Phase
@@ -16,6 +16,7 @@ activation-instructions:
16
16
  - Only read the files/tasks listed here when user selects them for execution to minimize context usage
17
17
  - The customization field ALWAYS takes precedence over any conflicting instructions
18
18
  - When listing tasks/templates or presenting options during conversations, always show as numbered options list, allowing the user to type a number to select or execute
19
+ - Command
19
20
 
20
21
  agent:
21
22
  name: [AGENT_NAME]
@@ -36,7 +37,9 @@ persona:
36
37
  # Add more principles as needed
37
38
 
38
39
  startup:
39
- - [STARTUP_INSTRUCTIONS]
40
+ - Greet the user with your name and role, and inform of the *help command.
41
+ - [STARTUP_INSTRUCTION]
42
+ - [STARTUP_INSTRUCTION]...
40
43
 
41
44
  commands:
42
45
  - "*help" - Show: numbered list of the following commands to allow selection
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bmad-method",
3
- "version": "4.7.0",
3
+ "version": "4.9.0",
4
4
  "description": "Breakthrough Method of Agile AI-driven Development",
5
5
  "main": "tools/cli.js",
6
6
  "bin": {
@@ -1,19 +1,22 @@
1
- const fs = require('node:fs').promises;
2
- const path = require('node:path');
3
- const DependencyResolver = require('../lib/dependency-resolver');
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(this.rootDir, 'bmad-core', 'templates', 'web-agent-startup-instructions-template.md');
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('js-yaml');
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, 'agents');
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, 'utf8');
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, 'teams');
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, 'utf8');
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, 'utf8');
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('\n');
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, 'utf8');
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('\n');
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('\n');
169
+ "",
170
+ ].join("\n");
117
171
  }
118
172
 
119
173
  async validate() {
120
- console.log('Validating agent configurations...');
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('\nValidating team configurations...');
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, 'expansion-packs', packName);
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, 'agents');
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('.md'));
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('.md', '');
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, 'agents');
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, 'utf8');
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, 'agent-teams');
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('.yml'));
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, 'teams');
269
+ const teamsOutputDir = path.join(outputDir, "teams");
218
270
  await fs.mkdir(teamsOutputDir, { recursive: true });
219
- const outputFile = path.join(teamsOutputDir, teamFile.replace('.yml', '.txt'));
220
- await fs.writeFile(outputFile, bundle, 'utf8');
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, 'utf8');
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, 'agents', `${agentName}.md`);
237
- const agentContent = await fs.readFile(agentPath, 'utf8');
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('js-yaml');
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 = ['.md', '.yml', '.yaml'];
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, 'utf8');
260
- sections.push(this.formatSection(`${resourceType}#${resourceName}`, resourceContent));
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(this.rootDir, 'bmad-core', resourceType, `${resourceName}${ext}`);
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, 'utf8');
274
- sections.push(this.formatSection(`${resourceType}#${resourceName}`, coreContent));
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(` ⚠ Dependency ${resourceType}#${resourceName} not found in expansion pack or core`);
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('\n');
358
+ return sections.join("\n");
296
359
  }
297
360
 
298
361
  async buildExpansionTeamBundle(packName, packDir, teamConfigPath) {
299
- const template = await fs.readFile(this.templatePath, 'utf8');
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, 'utf8');
305
- const teamFileName = path.basename(teamConfigPath, '.yml');
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, 'agents');
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('.md'))) {
315
- const agentName = agentFile.replace('.md', '');
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 = ['templates', 'tasks', 'checklists', 'workflows', 'data'];
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(f => f.endsWith('.md') || f.endsWith('.yml'))) {
330
- const fileName = resourceFile.replace(/\.(md|yml)$/, '');
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('bmad-orchestrator')) {
407
+ if (!agentsToProcess.includes("bmad-orchestrator")) {
343
408
  console.warn(` ⚠ Team ${teamFileName} missing bmad-orchestrator, adding automatically`);
344
- agentsToProcess.unshift('bmad-orchestrator');
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, 'utf8');
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, 'bmad-core', 'agents', `${agentId}.md`);
383
- const coreAgentContent = await fs.readFile(coreAgentPath, 'utf8');
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, '$1$2');
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 = ['.md', '.yml', '.yaml'];
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, 'utf8');
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, 'bmad-core', dep.type, `${dep.name}${ext}`);
507
+ const corePath = path.join(this.rootDir, "bmad-core", dep.type, `${dep.name}${ext}`);
444
508
  try {
445
- const content = await fs.readFile(corePath, 'utf8');
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(f => f.endsWith('.md') || f.endsWith('.yml'))) {
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, 'utf8');
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('\n');
547
+ return sections.join("\n");
482
548
  }
483
549
 
484
550
  async listExpansionPacks() {
485
- const expansionPacksDir = path.join(this.rootDir, 'expansion-packs');
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('No expansion-packs directory found');
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;