bmad-method 6.0.0-alpha.10 → 6.0.0-alpha.11

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 (107) hide show
  1. package/CHANGELOG.md +215 -1109
  2. package/README.md +129 -359
  3. package/docs/custom-agent-installation.md +169 -0
  4. package/{v6-open-items.md → docs/v6-open-items.md} +1 -1
  5. package/package.json +2 -1
  6. package/src/core/resources/excalidraw/README.md +160 -0
  7. package/src/core/resources/excalidraw/library-loader.md +50 -0
  8. package/src/modules/bmb/docs/agent-compilation.md +340 -0
  9. package/src/modules/bmb/docs/agent-menu-patterns.md +524 -0
  10. package/src/modules/bmb/docs/expert-agent-architecture.md +364 -0
  11. package/src/modules/bmb/docs/index.md +55 -0
  12. package/src/modules/bmb/docs/module-agent-architecture.md +367 -0
  13. package/src/modules/bmb/docs/simple-agent-architecture.md +288 -0
  14. package/src/modules/bmb/docs/understanding-agent-types.md +184 -0
  15. package/src/modules/bmb/reference/agents/expert-examples/journal-keeper/README.md +242 -0
  16. package/src/modules/bmb/reference/agents/expert-examples/journal-keeper/journal-keeper-sidecar/breakthroughs.md +24 -0
  17. package/src/modules/bmb/reference/agents/expert-examples/journal-keeper/journal-keeper-sidecar/instructions.md +108 -0
  18. package/src/modules/bmb/reference/agents/expert-examples/journal-keeper/journal-keeper-sidecar/memories.md +46 -0
  19. package/src/modules/bmb/reference/agents/expert-examples/journal-keeper/journal-keeper-sidecar/mood-patterns.md +39 -0
  20. package/src/modules/bmb/reference/agents/expert-examples/journal-keeper/journal-keeper.agent.yaml +152 -0
  21. package/src/modules/bmb/reference/agents/module-examples/README.md +50 -0
  22. package/src/modules/bmb/reference/agents/module-examples/security-engineer.agent.yaml +53 -0
  23. package/src/modules/bmb/reference/agents/module-examples/trend-analyst.agent.yaml +57 -0
  24. package/src/modules/bmb/reference/agents/simple-examples/README.md +223 -0
  25. package/src/modules/bmb/reference/agents/simple-examples/commit-poet.agent.yaml +126 -0
  26. package/src/modules/bmb/reference/readme.md +3 -0
  27. package/src/modules/bmb/workflows/create-agent/agent-validation-checklist.md +174 -0
  28. package/src/modules/bmb/workflows/create-agent/brainstorm-context.md +99 -120
  29. package/src/modules/bmb/workflows/create-agent/communication-presets.csv +61 -0
  30. package/src/modules/bmb/workflows/create-agent/instructions.md +126 -65
  31. package/src/modules/bmb/workflows/create-agent/workflow.yaml +19 -12
  32. package/src/modules/bmb/workflows/edit-agent/README.md +174 -47
  33. package/src/modules/bmb/workflows/edit-agent/instructions.md +397 -33
  34. package/src/modules/bmb/workflows/edit-agent/workflow.yaml +24 -8
  35. package/src/modules/bmgd/workflows/4-production/story-context/workflow.yaml +1 -1
  36. package/src/modules/bmm/agents/analyst.agent.yaml +2 -2
  37. package/src/modules/bmm/agents/architect.agent.yaml +10 -2
  38. package/src/modules/bmm/agents/dev.agent.yaml +2 -2
  39. package/src/modules/bmm/agents/pm.agent.yaml +7 -3
  40. package/src/modules/bmm/agents/sm.agent.yaml +2 -2
  41. package/src/modules/bmm/agents/tea.agent.yaml +2 -2
  42. package/src/modules/bmm/agents/tech-writer.agent.yaml +15 -3
  43. package/src/modules/bmm/agents/ux-designer.agent.yaml +6 -2
  44. package/src/modules/bmm/docs/README.md +4 -0
  45. package/src/modules/bmm/docs/images/workflow-method-greenfield.excalidraw +5919 -0
  46. package/src/modules/bmm/docs/images/workflow-method-greenfield.svg +2 -0
  47. package/src/modules/bmm/docs/quick-start.md +6 -0
  48. package/src/modules/bmm/docs/scale-adaptive-system.md +6 -0
  49. package/src/modules/bmm/docs/workflows-implementation.md +10 -0
  50. package/src/modules/bmm/workflows/2-plan-workflows/prd/workflow.yaml +4 -4
  51. package/src/modules/bmm/workflows/{2-plan-workflows → 3-solutioning}/create-epics-and-stories/workflow.yaml +5 -5
  52. package/src/modules/bmm/workflows/4-implementation/story-context/workflow.yaml +1 -1
  53. package/src/modules/bmm/workflows/{frame-expert → diagrams}/create-dataflow/instructions.md +7 -8
  54. package/src/modules/bmm/workflows/diagrams/create-dataflow/workflow.yaml +27 -0
  55. package/src/modules/bmm/workflows/{frame-expert → diagrams}/create-diagram/instructions.md +9 -10
  56. package/src/modules/bmm/workflows/diagrams/create-diagram/workflow.yaml +27 -0
  57. package/src/modules/bmm/workflows/{frame-expert → diagrams}/create-flowchart/instructions.md +4 -5
  58. package/src/modules/bmm/workflows/diagrams/create-flowchart/workflow.yaml +27 -0
  59. package/src/modules/bmm/workflows/{frame-expert → diagrams}/create-wireframe/instructions.md +3 -3
  60. package/src/modules/bmm/workflows/diagrams/create-wireframe/workflow.yaml +27 -0
  61. package/src/modules/bmm/workflows/workflow-status/paths/enterprise-brownfield.yaml +18 -30
  62. package/src/modules/bmm/workflows/workflow-status/paths/enterprise-greenfield.yaml +2 -14
  63. package/src/modules/bmm/workflows/workflow-status/paths/method-brownfield.yaml +2 -14
  64. package/src/modules/bmm/workflows/workflow-status/paths/method-greenfield.yaml +2 -14
  65. package/src/modules/cis/agents/presentation-master.agent.yaml +60 -0
  66. package/tools/cli/commands/agent-install.js +409 -0
  67. package/tools/cli/installers/lib/core/installer.js +119 -0
  68. package/tools/cli/installers/lib/ide/_base-ide.js +25 -0
  69. package/tools/cli/installers/lib/ide/antigravity.js +463 -0
  70. package/tools/cli/installers/lib/ide/claude-code.js +43 -0
  71. package/tools/cli/installers/lib/ide/codex.js +217 -32
  72. package/tools/cli/installers/lib/ide/cursor.js +48 -0
  73. package/tools/cli/installers/lib/ide/github-copilot.js +74 -0
  74. package/tools/cli/installers/lib/ide/manager.js +35 -0
  75. package/tools/cli/installers/lib/ide/opencode.js +45 -0
  76. package/tools/cli/installers/lib/ide/windsurf.js +47 -0
  77. package/tools/cli/lib/agent/compiler.js +390 -0
  78. package/tools/cli/lib/agent/installer.js +725 -0
  79. package/tools/cli/lib/agent/template-engine.js +152 -0
  80. package/docs/installers-bundlers/web-bundler-usage.md +0 -54
  81. package/src/modules/bmb/workflows/create-agent/README.md +0 -203
  82. package/src/modules/bmb/workflows/create-agent/agent-architecture.md +0 -415
  83. package/src/modules/bmb/workflows/create-agent/agent-command-patterns.md +0 -759
  84. package/src/modules/bmb/workflows/create-agent/agent-types.md +0 -292
  85. package/src/modules/bmb/workflows/create-agent/checklist.md +0 -62
  86. package/src/modules/bmb/workflows/create-agent/communication-styles.md +0 -202
  87. package/src/modules/bmb/workflows/edit-agent/checklist.md +0 -112
  88. package/src/modules/bmb/workflows/redoc/README.md +0 -87
  89. package/src/modules/bmb/workflows/redoc/checklist.md +0 -99
  90. package/src/modules/bmb/workflows/redoc/instructions.md +0 -265
  91. package/src/modules/bmb/workflows/redoc/workflow.yaml +0 -34
  92. package/src/modules/bmm/agents/frame-expert.agent.yaml +0 -42
  93. package/src/modules/bmm/workflows/frame-expert/create-dataflow/workflow.yaml +0 -24
  94. package/src/modules/bmm/workflows/frame-expert/create-diagram/workflow.yaml +0 -25
  95. package/src/modules/bmm/workflows/frame-expert/create-flowchart/workflow.yaml +0 -28
  96. package/src/modules/bmm/workflows/frame-expert/create-wireframe/workflow.yaml +0 -24
  97. package/src/modules/bmm/workflows/workflow-status/paths/game-design.yaml +0 -52
  98. /package/src/{modules/bmm/workflows/frame-expert/_shared → core/resources/excalidraw}/excalidraw-helpers.md +0 -0
  99. /package/src/{modules/bmm/workflows/frame-expert/_shared → core/resources/excalidraw}/validate-json-instructions.md +0 -0
  100. /package/src/modules/bmm/workflows/{2-plan-workflows → 3-solutioning}/create-epics-and-stories/epics-template.md +0 -0
  101. /package/src/modules/bmm/workflows/{2-plan-workflows → 3-solutioning}/create-epics-and-stories/instructions.md +0 -0
  102. /package/src/modules/bmm/workflows/{frame-expert → diagrams}/_shared/excalidraw-library.json +0 -0
  103. /package/src/modules/bmm/workflows/{frame-expert → diagrams}/_shared/excalidraw-templates.yaml +0 -0
  104. /package/src/modules/bmm/workflows/{frame-expert → diagrams}/create-dataflow/checklist.md +0 -0
  105. /package/src/modules/bmm/workflows/{frame-expert → diagrams}/create-diagram/checklist.md +0 -0
  106. /package/src/modules/bmm/workflows/{frame-expert → diagrams}/create-flowchart/checklist.md +0 -0
  107. /package/src/modules/bmm/workflows/{frame-expert → diagrams}/create-wireframe/checklist.md +0 -0
@@ -0,0 +1,409 @@
1
+ const chalk = require('chalk');
2
+ const path = require('node:path');
3
+ const fs = require('node:fs');
4
+ const readline = require('node:readline');
5
+ const {
6
+ findBmadConfig,
7
+ resolvePath,
8
+ discoverAgents,
9
+ loadAgentConfig,
10
+ promptInstallQuestions,
11
+ detectBmadProject,
12
+ addToManifest,
13
+ extractManifestData,
14
+ checkManifestForPath,
15
+ updateManifestEntry,
16
+ saveAgentSource,
17
+ createIdeSlashCommands,
18
+ updateManifestYaml,
19
+ } = require('../lib/agent/installer');
20
+
21
+ module.exports = {
22
+ command: 'agent-install',
23
+ description: 'Install and compile BMAD agents with personalization',
24
+ options: [
25
+ ['-p, --path <path>', 'Path to specific agent YAML file or folder'],
26
+ ['-d, --defaults', 'Use default values without prompting'],
27
+ ['-t, --target <path>', 'Target installation directory (default: .bmad/agents)'],
28
+ ],
29
+ action: async (options) => {
30
+ try {
31
+ console.log(chalk.cyan('\n🔧 BMAD Agent Installer\n'));
32
+
33
+ // Find BMAD config
34
+ const config = findBmadConfig();
35
+ if (!config) {
36
+ console.log(chalk.yellow('No BMAD installation found in current directory.'));
37
+ console.log(chalk.dim('Looking for .bmad/bmb/config.yaml...'));
38
+ console.log(chalk.red('\nPlease run this command from a project with BMAD installed.'));
39
+ process.exit(1);
40
+ }
41
+
42
+ console.log(chalk.dim(`Found BMAD at: ${config.bmadFolder}`));
43
+
44
+ let selectedAgent = null;
45
+
46
+ // If path provided, use it directly
47
+ if (options.path) {
48
+ const providedPath = path.resolve(options.path);
49
+
50
+ if (!fs.existsSync(providedPath)) {
51
+ console.log(chalk.red(`Path not found: ${providedPath}`));
52
+ process.exit(1);
53
+ }
54
+
55
+ const stat = fs.statSync(providedPath);
56
+ if (stat.isFile() && providedPath.endsWith('.agent.yaml')) {
57
+ selectedAgent = {
58
+ type: 'simple',
59
+ name: path.basename(providedPath, '.agent.yaml'),
60
+ path: providedPath,
61
+ yamlFile: providedPath,
62
+ };
63
+ } else if (stat.isDirectory()) {
64
+ const yamlFiles = fs.readdirSync(providedPath).filter((f) => f.endsWith('.agent.yaml'));
65
+ if (yamlFiles.length === 1) {
66
+ selectedAgent = {
67
+ type: 'expert',
68
+ name: path.basename(providedPath),
69
+ path: providedPath,
70
+ yamlFile: path.join(providedPath, yamlFiles[0]),
71
+ hasSidecar: true,
72
+ };
73
+ } else {
74
+ console.log(chalk.red('Directory must contain exactly one .agent.yaml file'));
75
+ process.exit(1);
76
+ }
77
+ } else {
78
+ console.log(chalk.red('Path must be an .agent.yaml file or a folder containing one'));
79
+ process.exit(1);
80
+ }
81
+ } else {
82
+ // Discover agents from custom location
83
+ const customAgentLocation = config.custom_agent_location
84
+ ? resolvePath(config.custom_agent_location, config)
85
+ : path.join(config.bmadFolder, 'custom', 'agents');
86
+
87
+ console.log(chalk.dim(`Searching for agents in: ${customAgentLocation}\n`));
88
+
89
+ const agents = discoverAgents(customAgentLocation);
90
+
91
+ if (agents.length === 0) {
92
+ console.log(chalk.yellow('No agents found in custom agent location.'));
93
+ console.log(chalk.dim(`Expected location: ${customAgentLocation}`));
94
+ console.log(chalk.dim('\nCreate agents using the BMad Builder workflow or place .agent.yaml files there.'));
95
+ process.exit(0);
96
+ }
97
+
98
+ // List available agents
99
+ console.log(chalk.cyan('Available Agents:\n'));
100
+ for (const [idx, agent] of agents.entries()) {
101
+ const typeIcon = agent.type === 'expert' ? '📚' : '📄';
102
+ console.log(` ${idx + 1}. ${typeIcon} ${chalk.bold(agent.name)} ${chalk.dim(`(${agent.type})`)}`);
103
+ }
104
+
105
+ // Prompt for selection
106
+ const rl = readline.createInterface({
107
+ input: process.stdin,
108
+ output: process.stdout,
109
+ });
110
+
111
+ const selection = await new Promise((resolve) => {
112
+ rl.question('\nSelect agent to install (number): ', resolve);
113
+ });
114
+ rl.close();
115
+
116
+ const selectedIdx = parseInt(selection, 10) - 1;
117
+ if (isNaN(selectedIdx) || selectedIdx < 0 || selectedIdx >= agents.length) {
118
+ console.log(chalk.red('Invalid selection'));
119
+ process.exit(1);
120
+ }
121
+
122
+ selectedAgent = agents[selectedIdx];
123
+ }
124
+
125
+ console.log(chalk.cyan(`\nSelected: ${chalk.bold(selectedAgent.name)}`));
126
+
127
+ // Load agent configuration
128
+ const agentConfig = loadAgentConfig(selectedAgent.yamlFile);
129
+
130
+ if (agentConfig.metadata.name) {
131
+ console.log(chalk.dim(`Agent Name: ${agentConfig.metadata.name}`));
132
+ }
133
+ if (agentConfig.metadata.title) {
134
+ console.log(chalk.dim(`Title: ${agentConfig.metadata.title}`));
135
+ }
136
+
137
+ // Get the agent type (source name)
138
+ const agentType = selectedAgent.name; // e.g., "commit-poet"
139
+
140
+ // Confirm/customize agent persona name
141
+ const rl1 = readline.createInterface({
142
+ input: process.stdin,
143
+ output: process.stdout,
144
+ });
145
+
146
+ const defaultPersonaName = agentConfig.metadata.name || agentType;
147
+ console.log(chalk.cyan('\n📛 Agent Persona Name\n'));
148
+ console.log(chalk.dim(` Agent type: ${agentType}`));
149
+ console.log(chalk.dim(` Default persona: ${defaultPersonaName}`));
150
+ console.log(chalk.dim(' Leave blank to use default, or provide a custom name.'));
151
+ console.log(chalk.dim(' Examples:'));
152
+ console.log(chalk.dim(` - (blank) → "${defaultPersonaName}" as ${agentType}.md`));
153
+ console.log(chalk.dim(` - "Fred" → "Fred" as fred-${agentType}.md`));
154
+ console.log(chalk.dim(` - "Captain Code" → "Captain Code" as captain-code-${agentType}.md`));
155
+
156
+ const customPersonaName = await new Promise((resolve) => {
157
+ rl1.question(`\n Custom name (or Enter for default): `, resolve);
158
+ });
159
+ rl1.close();
160
+
161
+ // Determine final agent file name based on persona name
162
+ let finalAgentName;
163
+ let personaName;
164
+ if (customPersonaName.trim()) {
165
+ personaName = customPersonaName.trim();
166
+ const namePrefix = personaName.toLowerCase().replaceAll(/\s+/g, '-');
167
+ finalAgentName = `${namePrefix}-${agentType}`;
168
+ } else {
169
+ personaName = defaultPersonaName;
170
+ finalAgentName = agentType;
171
+ }
172
+
173
+ console.log(chalk.dim(` Persona: ${personaName}`));
174
+ console.log(chalk.dim(` File: ${finalAgentName}.md`));
175
+
176
+ // Get answers (prompt or use defaults)
177
+ let presetAnswers = {};
178
+
179
+ // If custom persona name provided, inject it as custom_name for template processing
180
+ if (customPersonaName.trim()) {
181
+ presetAnswers.custom_name = personaName;
182
+ }
183
+
184
+ let answers;
185
+ if (agentConfig.installConfig && !options.defaults) {
186
+ answers = await promptInstallQuestions(agentConfig.installConfig, agentConfig.defaults, presetAnswers);
187
+ } else if (agentConfig.installConfig && options.defaults) {
188
+ console.log(chalk.dim('\nUsing default configuration values.'));
189
+ answers = { ...agentConfig.defaults, ...presetAnswers };
190
+ } else {
191
+ answers = { ...agentConfig.defaults, ...presetAnswers };
192
+ }
193
+
194
+ // Determine target directory
195
+ let targetDir = options.target ? path.resolve(options.target) : null;
196
+
197
+ // If no target specified, prompt for it
198
+ if (targetDir) {
199
+ // If target provided via --target, check if it's a project root and adjust
200
+ const otherProject = detectBmadProject(targetDir);
201
+ if (otherProject && !targetDir.includes('agents')) {
202
+ // User specified project root, redirect to custom agents folder
203
+ targetDir = path.join(otherProject.bmadFolder, 'custom', 'agents');
204
+ console.log(chalk.dim(` Auto-selecting custom agents folder: ${targetDir}`));
205
+ }
206
+ } else {
207
+ const rl = readline.createInterface({
208
+ input: process.stdin,
209
+ output: process.stdout,
210
+ });
211
+
212
+ console.log(chalk.cyan('\n📂 Installation Target\n'));
213
+
214
+ // Option 1: Current project's custom agents folder
215
+ const currentCustom = path.join(config.bmadFolder, 'custom', 'agents');
216
+ console.log(` 1. Current project: ${chalk.dim(currentCustom)}`);
217
+
218
+ // Option 2: Specify another project path
219
+ console.log(` 2. Another project (enter path)`);
220
+
221
+ const choice = await new Promise((resolve) => {
222
+ rl.question('\n Select option (1 or path): ', resolve);
223
+ });
224
+
225
+ if (choice.trim() === '1' || choice.trim() === '') {
226
+ targetDir = currentCustom;
227
+ } else if (choice.trim() === '2') {
228
+ const projectPath = await new Promise((resolve) => {
229
+ rl.question(' Project path: ', resolve);
230
+ });
231
+
232
+ // Detect if it's a BMAD project and use its custom folder
233
+ const otherProject = detectBmadProject(path.resolve(projectPath));
234
+ if (otherProject) {
235
+ targetDir = path.join(otherProject.bmadFolder, 'custom', 'agents');
236
+ console.log(chalk.dim(` Found BMAD project, using: ${targetDir}`));
237
+ } else {
238
+ targetDir = path.resolve(projectPath);
239
+ }
240
+ } else {
241
+ // User entered a path directly
242
+ const otherProject = detectBmadProject(path.resolve(choice));
243
+ if (otherProject) {
244
+ targetDir = path.join(otherProject.bmadFolder, 'custom', 'agents');
245
+ console.log(chalk.dim(` Found BMAD project, using: ${targetDir}`));
246
+ } else {
247
+ targetDir = path.resolve(choice);
248
+ }
249
+ }
250
+
251
+ rl.close();
252
+ }
253
+
254
+ if (!fs.existsSync(targetDir)) {
255
+ fs.mkdirSync(targetDir, { recursive: true });
256
+ }
257
+
258
+ console.log(chalk.dim(`\nInstalling to: ${targetDir}`));
259
+
260
+ // Detect if target is within a BMAD project
261
+ const targetProject = detectBmadProject(targetDir);
262
+ if (targetProject) {
263
+ console.log(chalk.cyan(` Detected BMAD project at: ${targetProject.projectRoot}`));
264
+ }
265
+
266
+ // Check for duplicate in manifest by path (not by type)
267
+ let shouldUpdateExisting = false;
268
+ let existingEntry = null;
269
+
270
+ if (targetProject) {
271
+ // Check if this exact installed name already exists
272
+ const expectedPath = `.bmad/custom/agents/${finalAgentName}/${finalAgentName}.md`;
273
+ existingEntry = checkManifestForPath(targetProject.manifestFile, expectedPath);
274
+
275
+ if (existingEntry) {
276
+ const rl2 = readline.createInterface({
277
+ input: process.stdin,
278
+ output: process.stdout,
279
+ });
280
+
281
+ console.log(chalk.yellow(`\n⚠️ Agent "${finalAgentName}" already installed`));
282
+ console.log(chalk.dim(` Type: ${agentType}`));
283
+ console.log(chalk.dim(` Path: ${existingEntry.path}`));
284
+
285
+ const overwrite = await new Promise((resolve) => {
286
+ rl2.question(' Overwrite existing installation? [Y/n]: ', resolve);
287
+ });
288
+ rl2.close();
289
+
290
+ if (overwrite.toLowerCase() === 'n') {
291
+ console.log(chalk.yellow('Installation cancelled.'));
292
+ process.exit(0);
293
+ }
294
+
295
+ shouldUpdateExisting = true;
296
+ }
297
+ }
298
+
299
+ // Install the agent with custom name
300
+ // Override the folder name with finalAgentName
301
+ const agentTargetDir = path.join(targetDir, finalAgentName);
302
+
303
+ if (!fs.existsSync(agentTargetDir)) {
304
+ fs.mkdirSync(agentTargetDir, { recursive: true });
305
+ }
306
+
307
+ // Compile and install
308
+ const { compileAgent } = require('../lib/agent/compiler');
309
+
310
+ // Calculate target path for agent ID
311
+ const projectRoot = targetProject ? targetProject.projectRoot : config.projectRoot;
312
+ const compiledFileName = `${finalAgentName}.md`;
313
+ const compiledPath = path.join(agentTargetDir, compiledFileName);
314
+ const relativePath = path.relative(projectRoot, compiledPath);
315
+
316
+ // Compile with proper name and path
317
+ const { xml, metadata, processedYaml } = compileAgent(
318
+ fs.readFileSync(selectedAgent.yamlFile, 'utf8'),
319
+ answers,
320
+ finalAgentName,
321
+ relativePath,
322
+ );
323
+
324
+ // Write compiled XML (.md) with custom name
325
+ fs.writeFileSync(compiledPath, xml, 'utf8');
326
+
327
+ const result = {
328
+ success: true,
329
+ agentName: finalAgentName,
330
+ targetDir: agentTargetDir,
331
+ compiledFile: compiledPath,
332
+ sidecarCopied: false,
333
+ };
334
+
335
+ // Copy sidecar files for expert agents
336
+ if (selectedAgent.hasSidecar && selectedAgent.type === 'expert') {
337
+ const { copySidecarFiles } = require('../lib/agent/installer');
338
+ const sidecarFiles = copySidecarFiles(selectedAgent.path, agentTargetDir, selectedAgent.yamlFile);
339
+ result.sidecarCopied = true;
340
+ result.sidecarFiles = sidecarFiles;
341
+ }
342
+
343
+ console.log(chalk.green('\n✨ Agent installed successfully!'));
344
+ console.log(chalk.cyan(` Name: ${result.agentName}`));
345
+ console.log(chalk.cyan(` Location: ${result.targetDir}`));
346
+ console.log(chalk.cyan(` Compiled: ${path.basename(result.compiledFile)}`));
347
+
348
+ if (result.sidecarCopied) {
349
+ console.log(chalk.cyan(` Sidecar files: ${result.sidecarFiles.length} files copied`));
350
+ }
351
+
352
+ // Save source YAML to _cfg/custom/agents/ and register in manifest
353
+ if (targetProject) {
354
+ // Save source for reinstallation with embedded answers
355
+ console.log(chalk.dim(`\nSaving source to: ${targetProject.cfgFolder}/custom/agents/`));
356
+ saveAgentSource(selectedAgent, targetProject.cfgFolder, finalAgentName, answers);
357
+ console.log(chalk.green(` ✓ Source saved for reinstallation`));
358
+
359
+ // Register/update in manifest
360
+ console.log(chalk.dim(`Registering in manifest: ${targetProject.manifestFile}`));
361
+
362
+ const manifestData = extractManifestData(xml, { ...metadata, name: finalAgentName }, relativePath, 'custom');
363
+ // Use finalAgentName as the manifest name field (unique identifier)
364
+ manifestData.name = finalAgentName;
365
+ // Use compiled metadata.name (persona name after template processing), not source agentConfig
366
+ manifestData.displayName = metadata.name || agentType;
367
+ // Store the actual installed path/name
368
+ manifestData.path = relativePath;
369
+
370
+ if (shouldUpdateExisting && existingEntry) {
371
+ updateManifestEntry(targetProject.manifestFile, manifestData, existingEntry._lineNumber);
372
+ console.log(chalk.green(` ✓ Updated existing entry in agent-manifest.csv`));
373
+ } else {
374
+ addToManifest(targetProject.manifestFile, manifestData);
375
+ console.log(chalk.green(` ✓ Added to agent-manifest.csv`));
376
+ }
377
+
378
+ // Create IDE slash commands
379
+ const ideResults = await createIdeSlashCommands(targetProject.projectRoot, finalAgentName, relativePath, metadata);
380
+ if (Object.keys(ideResults).length > 0) {
381
+ console.log(chalk.green(` ✓ Created IDE commands:`));
382
+ for (const [ideName, result] of Object.entries(ideResults)) {
383
+ console.log(chalk.dim(` ${ideName}: ${result.command}`));
384
+ }
385
+ }
386
+
387
+ // Update manifest.yaml with custom_agents tracking
388
+ const manifestYamlPath = path.join(targetProject.cfgFolder, 'manifest.yaml');
389
+ if (updateManifestYaml(manifestYamlPath, finalAgentName, agentType)) {
390
+ console.log(chalk.green(` ✓ Updated manifest.yaml custom_agents`));
391
+ }
392
+ }
393
+
394
+ console.log(chalk.dim(`\nAgent ID: ${relativePath}`));
395
+
396
+ if (targetProject) {
397
+ console.log(chalk.yellow('\nAgent is now registered and available in the target project!'));
398
+ } else {
399
+ console.log(chalk.yellow('\nTo use this agent, reference it in your manifest or load it directly.'));
400
+ }
401
+
402
+ process.exit(0);
403
+ } catch (error) {
404
+ console.error(chalk.red('Agent installation failed:'), error.message);
405
+ console.error(chalk.dim(error.stack));
406
+ process.exit(1);
407
+ }
408
+ },
409
+ };
@@ -840,6 +840,15 @@ class Installer {
840
840
  console.log(chalk.dim('Review the .bak files to see your changes and merge if needed.\n'));
841
841
  }
842
842
 
843
+ // Reinstall custom agents from _cfg/custom/agents/ sources
844
+ const customAgentResults = await this.reinstallCustomAgents(projectDir, bmadDir);
845
+ if (customAgentResults.count > 0) {
846
+ console.log(chalk.green(`\n✓ Reinstalled ${customAgentResults.count} custom agent${customAgentResults.count > 1 ? 's' : ''}`));
847
+ for (const agent of customAgentResults.agents) {
848
+ console.log(chalk.dim(` - ${agent}`));
849
+ }
850
+ }
851
+
843
852
  // Display completion message
844
853
  const { UI } = require('../../../lib/ui');
845
854
  const ui = new UI();
@@ -2245,6 +2254,116 @@ class Installer {
2245
2254
  return nodes;
2246
2255
  }
2247
2256
 
2257
+ /**
2258
+ * Reinstall custom agents from _cfg/custom/agents/ sources
2259
+ * This preserves custom agents across quick updates/reinstalls
2260
+ * @param {string} projectDir - Project directory
2261
+ * @param {string} bmadDir - BMAD installation directory
2262
+ * @returns {Object} Result with count and agent names
2263
+ */
2264
+ async reinstallCustomAgents(projectDir, bmadDir) {
2265
+ const customAgentsCfgDir = path.join(bmadDir, '_cfg', 'custom', 'agents');
2266
+ const results = { count: 0, agents: [] };
2267
+
2268
+ if (!(await fs.pathExists(customAgentsCfgDir))) {
2269
+ return results;
2270
+ }
2271
+
2272
+ try {
2273
+ const {
2274
+ discoverAgents,
2275
+ loadAgentConfig,
2276
+ extractManifestData,
2277
+ addToManifest,
2278
+ createIdeSlashCommands,
2279
+ updateManifestYaml,
2280
+ } = require('../../../lib/agent/installer');
2281
+ const { compileAgent } = require('../../../lib/agent/compiler');
2282
+
2283
+ // Discover custom agents in _cfg/custom/agents/
2284
+ const agents = discoverAgents(customAgentsCfgDir);
2285
+
2286
+ if (agents.length === 0) {
2287
+ return results;
2288
+ }
2289
+
2290
+ const customAgentsDir = path.join(bmadDir, 'custom', 'agents');
2291
+ await fs.ensureDir(customAgentsDir);
2292
+
2293
+ const manifestFile = path.join(bmadDir, '_cfg', 'agent-manifest.csv');
2294
+ const manifestYamlFile = path.join(bmadDir, '_cfg', 'manifest.yaml');
2295
+
2296
+ for (const agent of agents) {
2297
+ try {
2298
+ const agentConfig = loadAgentConfig(agent.yamlFile);
2299
+ const finalAgentName = agent.name; // Already named correctly from save
2300
+
2301
+ // Determine agent type from the name (e.g., "fred-commit-poet" → "commit-poet")
2302
+ let agentType = finalAgentName;
2303
+ const parts = finalAgentName.split('-');
2304
+ if (parts.length >= 2) {
2305
+ // Try to extract type (last part or last two parts)
2306
+ // For "fred-commit-poet", we want "commit-poet"
2307
+ // This is heuristic - could be improved with metadata storage
2308
+ agentType = parts.slice(-2).join('-'); // Take last 2 parts as type
2309
+ }
2310
+
2311
+ // Create target directory
2312
+ const agentTargetDir = path.join(customAgentsDir, finalAgentName);
2313
+ await fs.ensureDir(agentTargetDir);
2314
+
2315
+ // Calculate paths
2316
+ const compiledFileName = `${finalAgentName}.md`;
2317
+ const compiledPath = path.join(agentTargetDir, compiledFileName);
2318
+ const relativePath = path.relative(projectDir, compiledPath);
2319
+
2320
+ // Compile with embedded defaults (answers are already in defaults section)
2321
+ const { xml, metadata } = compileAgent(
2322
+ await fs.readFile(agent.yamlFile, 'utf8'),
2323
+ agentConfig.defaults || {},
2324
+ finalAgentName,
2325
+ relativePath,
2326
+ );
2327
+
2328
+ // Write compiled agent
2329
+ await fs.writeFile(compiledPath, xml, 'utf8');
2330
+
2331
+ // Copy sidecar files if expert agent
2332
+ if (agent.hasSidecar && agent.type === 'expert') {
2333
+ const { copySidecarFiles } = require('../../../lib/agent/installer');
2334
+ copySidecarFiles(agent.path, agentTargetDir, agent.yamlFile);
2335
+ }
2336
+
2337
+ // Update manifest CSV
2338
+ if (await fs.pathExists(manifestFile)) {
2339
+ const manifestData = extractManifestData(xml, { ...metadata, name: finalAgentName }, relativePath, 'custom');
2340
+ manifestData.name = finalAgentName;
2341
+ manifestData.displayName = metadata.name || finalAgentName;
2342
+ manifestData.path = relativePath;
2343
+ addToManifest(manifestFile, manifestData);
2344
+ }
2345
+
2346
+ // Create IDE slash commands (async function)
2347
+ await createIdeSlashCommands(projectDir, finalAgentName, relativePath, metadata);
2348
+
2349
+ // Update manifest.yaml
2350
+ if (await fs.pathExists(manifestYamlFile)) {
2351
+ updateManifestYaml(manifestYamlFile, finalAgentName, agentType);
2352
+ }
2353
+
2354
+ results.count++;
2355
+ results.agents.push(finalAgentName);
2356
+ } catch (agentError) {
2357
+ console.log(chalk.yellow(` ⚠️ Failed to reinstall ${agent.name}: ${agentError.message}`));
2358
+ }
2359
+ }
2360
+ } catch (error) {
2361
+ console.log(chalk.yellow(` ⚠️ Error reinstalling custom agents: ${error.message}`));
2362
+ }
2363
+
2364
+ return results;
2365
+ }
2366
+
2248
2367
  /**
2249
2368
  * Copy IDE-specific documentation to BMAD docs
2250
2369
  * @param {Array} ides - List of selected IDEs
@@ -73,6 +73,19 @@ class BaseIdeSetup {
73
73
  }
74
74
  }
75
75
 
76
+ /**
77
+ * Install a custom agent launcher - subclasses should override
78
+ * @param {string} projectDir - Project directory
79
+ * @param {string} agentName - Agent name (e.g., "fred-commit-poet")
80
+ * @param {string} agentPath - Path to compiled agent (relative to project root)
81
+ * @param {Object} metadata - Agent metadata
82
+ * @returns {Object|null} Info about created command, or null if not supported
83
+ */
84
+ async installCustomAgentLauncher(projectDir, agentName, agentPath, metadata) {
85
+ // Default implementation - subclasses can override
86
+ return null;
87
+ }
88
+
76
89
  /**
77
90
  * Detect whether this IDE already has configuration in the project
78
91
  * Subclasses can override for custom logic
@@ -601,6 +614,18 @@ class BaseIdeSetup {
601
614
  .join(' ');
602
615
  }
603
616
 
617
+ /**
618
+ * Flatten a relative path to a single filename for flat slash command naming
619
+ * Example: 'module/agents/name.md' -> 'bmad-module-agents-name.md'
620
+ * Used by IDEs that ignore directory structure for slash commands (e.g., Antigravity, Codex)
621
+ * @param {string} relativePath - Relative path to flatten
622
+ * @returns {string} Flattened filename with 'bmad-' prefix
623
+ */
624
+ flattenFilename(relativePath) {
625
+ const sanitized = relativePath.replaceAll(/[/\\]/g, '-');
626
+ return `bmad-${sanitized}`;
627
+ }
628
+
604
629
  /**
605
630
  * Create agent configuration file
606
631
  * @param {string} bmadDir - BMAD installation directory