bmad-method 6.3.1-next.13 → 6.3.1-next.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package.json",
3
3
  "name": "bmad-method",
4
- "version": "6.3.1-next.13",
4
+ "version": "6.3.1-next.14",
5
5
  "description": "Breakthrough Method of Agile AI-driven Development",
6
6
  "keywords": [
7
7
  "agile",
@@ -51,7 +51,7 @@ Load config from `{project-root}/_bmad/bmm/config.yaml` and resolve:
51
51
 
52
52
  ### Required Inputs
53
53
 
54
- - `agent_manifest` = `{project-root}/_bmad/_config/agent-manifest.csv`
54
+ - `agent_roster` = resolved via `python3 {project-root}/_bmad/scripts/resolve_config.py --project-root {project-root} --key agents` (merges four layers in order: `_bmad/config.toml`, `_bmad/config.user.toml`, `_bmad/custom/config.toml`, `_bmad/custom/config.user.toml`)
55
55
 
56
56
  ### Context
57
57
 
@@ -478,7 +478,7 @@ Amelia (Developer): "No problem. We'll still do a thorough retro on Epic {{epic_
478
478
 
479
479
  <step n="5" goal="Initialize Retrospective with Rich Context">
480
480
 
481
- <action>Load agent configurations from {agent_manifest}</action>
481
+ <action>Load agent roster from {agent_roster}</action>
482
482
  <action>Identify which agents participated in Epic {{epic_number}} based on story records</action>
483
483
  <action>Ensure key roles present: Product Owner, Developer (facilitating), Testing/QA, Architect</action>
484
484
 
@@ -18,6 +18,7 @@ user_skill_level:
18
18
  prompt:
19
19
  - "What is your development experience level?"
20
20
  - "This affects how agents explain concepts in chat."
21
+ scope: user
21
22
  default: "intermediate"
22
23
  result: "{value}"
23
24
  single-select:
@@ -48,3 +49,45 @@ directories:
48
49
  - "{planning_artifacts}"
49
50
  - "{implementation_artifacts}"
50
51
  - "{project_knowledge}"
52
+
53
+ # Agent roster — essence only. External skills (party-mode, retrospective,
54
+ # advanced-elicitation, help catalog) read these descriptors to route, display,
55
+ # and embody agents. Full persona and behavior live in each agent's
56
+ # customize.toml. `team` defaults to the module code when omitted; users can
57
+ # add their own agents (real or fictional) via _bmad/custom/config.toml or _bmad/custom/config.user.toml.
58
+ agents:
59
+ - code: bmad-agent-analyst
60
+ name: Mary
61
+ title: Business Analyst
62
+ icon: "📊"
63
+ description: "Channels Porter's strategic rigor and Minto's Pyramid Principle, grounds every finding in verifiable evidence, represents every stakeholder voice. Speaks like a treasure hunter narrating the find: thrilled by every clue, precise once the pattern emerges."
64
+
65
+ - code: bmad-agent-tech-writer
66
+ name: Paige
67
+ title: Technical Writer
68
+ icon: "📚"
69
+ description: "Master of CommonMark, DITA, and OpenAPI; turns complex concepts into accessible structured docs, favors diagrams over walls of text, every word earning its place. Speaks like the patient teacher you wish you'd had, using analogies that make complex things feel simple."
70
+
71
+ - code: bmad-agent-pm
72
+ name: John
73
+ title: Product Manager
74
+ icon: "📋"
75
+ description: "Drives Jobs-to-be-Done over template filling, user value first, technical feasibility is a constraint not the driver. Speaks like a detective interrogating a cold case: short questions, sharper follow-ups, every 'why?' tightening the net."
76
+
77
+ - code: bmad-agent-ux-designer
78
+ name: Sally
79
+ title: UX Designer
80
+ icon: "🎨"
81
+ description: "Balances empathy with edge-case rigor, starts simple and evolves through feedback, every decision serves a genuine user need. Speaks like a filmmaker pitching the scene before the code exists, painting user stories that make you feel the problem."
82
+
83
+ - code: bmad-agent-architect
84
+ name: Winston
85
+ title: System Architect
86
+ icon: "🏗️"
87
+ description: "Favors boring technology for stability, developer productivity as architecture, ties every decision to business value. Speaks like a seasoned engineer at the whiteboard: measured, always laying out trade-offs rather than verdicts."
88
+
89
+ - code: bmad-agent-dev
90
+ name: Amelia
91
+ title: Senior Software Engineer
92
+ icon: "💻"
93
+ description: "Test-first discipline (red, green, refactor), 100% pass before review, no fluff all precision. Speaks like a terminal prompt: exact file paths, AC IDs, and commit-message brevity — every statement citable."
@@ -35,7 +35,13 @@ When invoked from another prompt or process:
35
35
 
36
36
  ### Step 1: Method Registry Loading
37
37
 
38
- **Action:** Load and read `./methods.csv` and '{project-root}/_bmad/_config/agent-manifest.csv'
38
+ **Action:** Load `./methods.csv` for elicitation methods. If party-mode may participate, resolve the agent roster via:
39
+
40
+ ```bash
41
+ python3 {project-root}/_bmad/scripts/resolve_config.py --project-root {project-root} --key agents
42
+ ```
43
+
44
+ The resolver merges four layers in order: `_bmad/config.toml` (installer base, team-scoped), `_bmad/config.user.toml` (installer base, user-scoped), `_bmad/custom/config.toml` (team overrides), and `_bmad/custom/config.user.toml` (personal overrides). Each entry under `agents` is keyed by the agent's `code` and carries `name`, `title`, `icon`, `description`, `module`, and `team`.
39
45
 
40
46
  #### CSV Structure
41
47
 
@@ -174,7 +174,7 @@ parts: 1
174
174
  ## Current Installer (migration context)
175
175
  - Entry: `tools/installer/bmad-cli.js` (Commander.js) → `tools/installer/core/installer.js`
176
176
  - Platforms: `platform-codes.yaml` (~20 platforms with target dirs, legacy dirs, template types, special flags)
177
- - Manifests: CSV files (skill/workflow/agent-manifest.csv) are current source of truth, not JSON
177
+ - Manifests: skill-manifest.csv is the current source of truth; agent essence lives in `_bmad/config.toml` (generated from each module.yaml's `agents:` block)
178
178
  - External modules: `external-official-modules.yaml` (CIS, GDS, TEA, WDS) from npm with semver
179
179
  - Dependencies: 4-pass resolver (collect → parse → resolve → transitive); YAML-declared only
180
180
  - Config: prompts for name, communication language, document output language, output folder
@@ -26,7 +26,13 @@ Party mode accepts optional arguments when invoked:
26
26
  - Use `{user_name}` for greeting
27
27
  - Use `{communication_language}` for all communications
28
28
 
29
- 3. **Read the agent manifest** at `{project-root}/_bmad/_config/agent-manifest.csv`. Build an internal roster of available agents with their displayName, title, icon, role, identity, communicationStyle, and principles.
29
+ 3. **Resolve the agent roster** by running:
30
+
31
+ ```bash
32
+ python3 {project-root}/_bmad/scripts/resolve_config.py --project-root {project-root} --key agents
33
+ ```
34
+
35
+ The resolver merges four layers in order: `_bmad/config.toml` (installer base, team-scoped), `_bmad/config.user.toml` (installer base, user-scoped), `_bmad/custom/config.toml` (team overrides), and `_bmad/custom/config.user.toml` (personal overrides). Each entry under `agents` is keyed by the agent's `code` and carries `name`, `title`, `icon`, `description`, `module`, and `team`. Build an internal roster of available agents from those fields.
30
36
 
31
37
  4. **Load project context** — search for `**/project-context.md`. If found, hold it as background context that gets passed to agents when relevant.
32
38
 
@@ -50,15 +56,12 @@ Choose 2-4 agents whose expertise is most relevant to what the user is asking. U
50
56
 
51
57
  For each selected agent, spawn a subagent using the Agent tool. Each subagent gets:
52
58
 
53
- **The agent prompt** (built from the manifest data):
59
+ **The agent prompt** (built from the resolved roster entry):
54
60
  ```
55
- You are {displayName} ({title}), a BMAD agent in a collaborative roundtable discussion.
61
+ You are {name} ({title}), a BMAD agent in a collaborative roundtable discussion.
56
62
 
57
63
  ## Your Persona
58
- - Icon: {icon}
59
- - Communication Style: {communicationStyle}
60
- - Principles: {principles}
61
- - Identity: {identity}
64
+ {icon} {name} {description}
62
65
 
63
66
  ## Discussion Context
64
67
  {summary of the conversation so far — keep under 400 words}
@@ -72,11 +75,11 @@ You are {displayName} ({title}), a BMAD agent in a collaborative roundtable disc
72
75
  {the user's actual message}
73
76
 
74
77
  ## Guidelines
75
- - Respond authentically as {displayName}. Your perspective should reflect your genuine expertise.
76
- - Start your response with: {icon} **{displayName}:**
78
+ - Respond authentically as {name}. Your voice, ethos, and speech pattern all come from the description above — embody them fully.
79
+ - Start your response with: {icon} **{name}:**
77
80
  - Speak in {communication_language}.
78
81
  - Scale your response to the substance — don't pad. If you have a brief point, make it briefly.
79
- - Disagree with other agents when your expertise tells you to. Don't hedge or be polite about it.
82
+ - Disagree with other agents when your perspective tells you to. Don't hedge or be polite about it.
80
83
  - If you have nothing substantive to add, say so in one sentence rather than manufacturing an opinion.
81
84
  - You may ask the user direct questions if something needs clarification.
82
85
  - Do NOT use tools. Just respond with your perspective.
@@ -7,11 +7,13 @@ subheader: "Configure the core settings for your BMad installation.\nThese setti
7
7
 
8
8
  user_name:
9
9
  prompt: "What should agents call you? (Use your name or a team name)"
10
+ scope: user
10
11
  default: "BMad"
11
12
  result: "{value}"
12
13
 
13
14
  communication_language:
14
15
  prompt: "What language should agents use when chatting with you?"
16
+ scope: user
15
17
  default: "English"
16
18
  result: "{value}"
17
19
 
@@ -0,0 +1,176 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Resolve BMad's central config using four-layer TOML merge.
4
+
5
+ Reads from four layers (highest priority last):
6
+ 1. {project-root}/_bmad/config.toml (installer-owned team)
7
+ 2. {project-root}/_bmad/config.user.toml (installer-owned user)
8
+ 3. {project-root}/_bmad/custom/config.toml (human-authored team, committed)
9
+ 4. {project-root}/_bmad/custom/config.user.toml (human-authored user, gitignored)
10
+
11
+ Outputs merged JSON to stdout. Errors go to stderr.
12
+
13
+ Requires Python 3.11+ (uses stdlib `tomllib`). No `uv`, no `pip install`,
14
+ no virtualenv — plain `python3` is sufficient.
15
+
16
+ python3 resolve_config.py --project-root /abs/path/to/project
17
+ python3 resolve_config.py --project-root ... --key core
18
+ python3 resolve_config.py --project-root ... --key agents
19
+
20
+ Merge rules (same as resolve_customization.py):
21
+ - Scalars: override wins
22
+ - Tables: deep merge
23
+ - Arrays of tables where every item shares `code` or `id`: merge by that key
24
+ - All other arrays: append
25
+ """
26
+
27
+ import argparse
28
+ import json
29
+ import sys
30
+ from pathlib import Path
31
+
32
+ try:
33
+ import tomllib
34
+ except ImportError:
35
+ sys.stderr.write(
36
+ "error: Python 3.11+ is required (stdlib `tomllib` not found).\n"
37
+ )
38
+ sys.exit(3)
39
+
40
+
41
+ _MISSING = object()
42
+ _KEYED_MERGE_FIELDS = ("code", "id")
43
+
44
+
45
+ def load_toml(file_path: Path, required: bool = False) -> dict:
46
+ if not file_path.exists():
47
+ if required:
48
+ sys.stderr.write(f"error: required config file not found: {file_path}\n")
49
+ sys.exit(1)
50
+ return {}
51
+ try:
52
+ with file_path.open("rb") as f:
53
+ parsed = tomllib.load(f)
54
+ if not isinstance(parsed, dict):
55
+ return {}
56
+ return parsed
57
+ except tomllib.TOMLDecodeError as error:
58
+ level = "error" if required else "warning"
59
+ sys.stderr.write(f"{level}: failed to parse {file_path}: {error}\n")
60
+ if required:
61
+ sys.exit(1)
62
+ return {}
63
+ except OSError as error:
64
+ level = "error" if required else "warning"
65
+ sys.stderr.write(f"{level}: failed to read {file_path}: {error}\n")
66
+ if required:
67
+ sys.exit(1)
68
+ return {}
69
+
70
+
71
+ def _detect_keyed_merge_field(items):
72
+ if not items or not all(isinstance(item, dict) for item in items):
73
+ return None
74
+ for candidate in _KEYED_MERGE_FIELDS:
75
+ if all(item.get(candidate) is not None for item in items):
76
+ return candidate
77
+ return None
78
+
79
+
80
+ def _merge_by_key(base, override, key_name):
81
+ result = []
82
+ index_by_key = {}
83
+ for item in base:
84
+ if not isinstance(item, dict):
85
+ continue
86
+ if item.get(key_name) is not None:
87
+ index_by_key[item[key_name]] = len(result)
88
+ result.append(dict(item))
89
+ for item in override:
90
+ if not isinstance(item, dict):
91
+ result.append(item)
92
+ continue
93
+ key = item.get(key_name)
94
+ if key is not None and key in index_by_key:
95
+ result[index_by_key[key]] = dict(item)
96
+ else:
97
+ if key is not None:
98
+ index_by_key[key] = len(result)
99
+ result.append(dict(item))
100
+ return result
101
+
102
+
103
+ def _merge_arrays(base, override):
104
+ base_arr = base if isinstance(base, list) else []
105
+ override_arr = override if isinstance(override, list) else []
106
+ keyed_field = _detect_keyed_merge_field(base_arr + override_arr)
107
+ if keyed_field:
108
+ return _merge_by_key(base_arr, override_arr, keyed_field)
109
+ return base_arr + override_arr
110
+
111
+
112
+ def deep_merge(base, override):
113
+ if isinstance(base, dict) and isinstance(override, dict):
114
+ result = dict(base)
115
+ for key, over_val in override.items():
116
+ if key in result:
117
+ result[key] = deep_merge(result[key], over_val)
118
+ else:
119
+ result[key] = over_val
120
+ return result
121
+ if isinstance(base, list) and isinstance(override, list):
122
+ return _merge_arrays(base, override)
123
+ return override
124
+
125
+
126
+ def extract_key(data, dotted_key: str):
127
+ parts = dotted_key.split(".")
128
+ current = data
129
+ for part in parts:
130
+ if isinstance(current, dict) and part in current:
131
+ current = current[part]
132
+ else:
133
+ return _MISSING
134
+ return current
135
+
136
+
137
+ def main():
138
+ parser = argparse.ArgumentParser(
139
+ description="Resolve BMad central config using four-layer TOML merge.",
140
+ )
141
+ parser.add_argument(
142
+ "--project-root", "-p", required=True,
143
+ help="Absolute path to the project root (contains _bmad/)",
144
+ )
145
+ parser.add_argument(
146
+ "--key", "-k", action="append", default=[],
147
+ help="Dotted field path to resolve (repeatable). Omit for full dump.",
148
+ )
149
+ args = parser.parse_args()
150
+
151
+ project_root = Path(args.project_root).resolve()
152
+ bmad_dir = project_root / "_bmad"
153
+
154
+ base_team = load_toml(bmad_dir / "config.toml", required=True)
155
+ base_user = load_toml(bmad_dir / "config.user.toml")
156
+ custom_team = load_toml(bmad_dir / "custom" / "config.toml")
157
+ custom_user = load_toml(bmad_dir / "custom" / "config.user.toml")
158
+
159
+ merged = deep_merge(base_team, base_user)
160
+ merged = deep_merge(merged, custom_team)
161
+ merged = deep_merge(merged, custom_user)
162
+
163
+ if args.key:
164
+ output = {}
165
+ for key in args.key:
166
+ value = extract_key(merged, key)
167
+ if value is not _MISSING:
168
+ output[key] = value
169
+ else:
170
+ output = merged
171
+
172
+ sys.stdout.write(json.dumps(output, indent=2, ensure_ascii=False) + "\n")
173
+
174
+
175
+ if __name__ == "__main__":
176
+ main()
@@ -54,8 +54,11 @@ class InstallPaths {
54
54
  manifestFile() {
55
55
  return path.join(this.configDir, 'manifest.yaml');
56
56
  }
57
- agentManifest() {
58
- return path.join(this.configDir, 'agent-manifest.csv');
57
+ centralConfig() {
58
+ return path.join(this.bmadDir, 'config.toml');
59
+ }
60
+ centralUserConfig() {
61
+ return path.join(this.bmadDir, 'config.user.toml');
59
62
  }
60
63
  filesManifest() {
61
64
  return path.join(this.configDir, 'files-manifest.csv');
@@ -310,7 +310,8 @@ class Installer {
310
310
  addResult('Configurations', 'ok', 'generated');
311
311
 
312
312
  this.installedFiles.add(paths.manifestFile());
313
- this.installedFiles.add(paths.agentManifest());
313
+ this.installedFiles.add(paths.centralConfig());
314
+ this.installedFiles.add(paths.centralUserConfig());
314
315
 
315
316
  message('Generating manifests...');
316
317
  const manifestGen = new ManifestGenerator();
@@ -331,10 +332,11 @@ class Installer {
331
332
  await manifestGen.generateManifests(paths.bmadDir, allModulesForManifest, [...this.installedFiles], {
332
333
  ides: config.ides || [],
333
334
  preservedModules: modulesForCsvPreserve,
335
+ moduleConfigs,
334
336
  });
335
337
 
336
338
  message('Generating help catalog...');
337
- await this.mergeModuleHelpCatalogs(paths.bmadDir);
339
+ await this.mergeModuleHelpCatalogs(paths.bmadDir, manifestGen.agents);
338
340
  addResult('Help catalog', 'ok');
339
341
 
340
342
  return 'Configurations generated';
@@ -922,46 +924,30 @@ class Installer {
922
924
  }
923
925
 
924
926
  /**
925
- * Merge all module-help.csv files into a single bmad-help.csv
926
- * Scans all installed modules for module-help.csv and merges them
927
- * Enriches agent info from agent-manifest.csv
928
- * Output is written to _bmad/_config/bmad-help.csv
927
+ * Merge all module-help.csv files into a single bmad-help.csv.
928
+ * Scans all installed modules for module-help.csv and merges them.
929
+ * Enriches agent info from the in-memory agent list produced by ManifestGenerator.
930
+ * Output is written to _bmad/_config/bmad-help.csv.
929
931
  * @param {string} bmadDir - BMAD installation directory
932
+ * @param {Array<Object>} agentEntries - Agents collected from module.yaml (code, name, title, icon, module, ...)
930
933
  */
931
- async mergeModuleHelpCatalogs(bmadDir) {
934
+ async mergeModuleHelpCatalogs(bmadDir, agentEntries = []) {
932
935
  const allRows = [];
933
936
  const headerRow =
934
937
  'module,phase,name,code,sequence,workflow-file,command,required,agent-name,agent-command,agent-display-name,agent-title,options,description,output-location,outputs';
935
938
 
936
- // Load agent manifest for agent info lookup
937
- const agentManifestPath = path.join(bmadDir, '_config', 'agent-manifest.csv');
938
- const agentInfo = new Map(); // agent-name -> {command, displayName, title+icon}
939
-
940
- if (await fs.pathExists(agentManifestPath)) {
941
- const manifestContent = await fs.readFile(agentManifestPath, 'utf8');
942
- const lines = manifestContent.split('\n').filter((line) => line.trim());
943
-
944
- for (const line of lines) {
945
- if (line.startsWith('name,')) continue; // Skip header
946
-
947
- const cols = line.split(',');
948
- if (cols.length >= 4) {
949
- const agentName = cols[0].replaceAll('"', '').trim();
950
- const displayName = cols[1].replaceAll('"', '').trim();
951
- const title = cols[2].replaceAll('"', '').trim();
952
- const icon = cols[3].replaceAll('"', '').trim();
953
- const module = cols[10] ? cols[10].replaceAll('"', '').trim() : '';
954
-
955
- // Build agent command: bmad:module:agent:name
956
- const agentCommand = module ? `bmad:${module}:agent:${agentName}` : `bmad:agent:${agentName}`;
957
-
958
- agentInfo.set(agentName, {
959
- command: agentCommand,
960
- displayName: displayName || agentName,
961
- title: icon && title ? `${icon} ${title}` : title || agentName,
962
- });
963
- }
964
- }
939
+ // Build agent lookup from the in-memory list (agent code → command + display fields).
940
+ const agentInfo = new Map();
941
+ for (const agent of agentEntries) {
942
+ if (!agent || !agent.code) continue;
943
+ const agentCommand = agent.module ? `bmad:${agent.module}:agent:${agent.code}` : `bmad:agent:${agent.code}`;
944
+ const displayName = agent.name || agent.code;
945
+ const titleCombined = agent.icon && agent.title ? `${agent.icon} ${agent.title}` : agent.title || agent.code;
946
+ agentInfo.set(agent.code, {
947
+ command: agentCommand,
948
+ displayName,
949
+ title: titleCombined,
950
+ });
965
951
  }
966
952
 
967
953
  // Get all installed module directories
@@ -2,14 +2,8 @@ const path = require('node:path');
2
2
  const fs = require('../fs-native');
3
3
  const yaml = require('yaml');
4
4
  const crypto = require('node:crypto');
5
- const csv = require('csv-parse/sync');
6
- const { getSourcePath, getModulePath } = require('../project-root');
5
+ const { getModulePath } = require('../project-root');
7
6
  const prompts = require('../prompts');
8
- const {
9
- loadSkillManifest: loadSkillManifestShared,
10
- getCanonicalId: getCanonicalIdShared,
11
- getArtifactType: getArtifactTypeShared,
12
- } = require('../ide/shared/skill-manifest');
13
7
 
14
8
  // Load package.json for version info
15
9
  const packageJson = require('../../../package.json');
@@ -26,21 +20,6 @@ class ManifestGenerator {
26
20
  this.selectedIdes = [];
27
21
  }
28
22
 
29
- /** Delegate to shared skill-manifest module */
30
- async loadSkillManifest(dirPath) {
31
- return loadSkillManifestShared(dirPath);
32
- }
33
-
34
- /** Delegate to shared skill-manifest module */
35
- getCanonicalId(manifest, filename) {
36
- return getCanonicalIdShared(manifest, filename);
37
- }
38
-
39
- /** Delegate to shared skill-manifest module */
40
- getArtifactType(manifest, filename) {
41
- return getArtifactTypeShared(manifest, filename);
42
- }
43
-
44
23
  /**
45
24
  * Clean text for CSV output by normalizing whitespace.
46
25
  * Note: Quote escaping is handled by escapeCsv() at write time.
@@ -98,17 +77,21 @@ class ManifestGenerator {
98
77
  // Collect skills first (populates skillClaimedDirs before legacy collectors run)
99
78
  await this.collectSkills();
100
79
 
101
- // Collect agent data - use updatedModules which includes all installed modules
102
- await this.collectAgents(this.updatedModules);
80
+ // Collect agent essence from each module's source module.yaml `agents:` array
81
+ await this.collectAgentsFromModuleYaml();
103
82
 
104
83
  // Write manifest files and collect their paths
84
+ const [teamConfigPath, userConfigPath] = await this.writeCentralConfig(bmadDir, options.moduleConfigs || {});
105
85
  const manifestFiles = [
106
86
  await this.writeMainManifest(cfgDir),
107
87
  await this.writeSkillManifest(cfgDir),
108
- await this.writeAgentManifest(cfgDir),
88
+ teamConfigPath,
89
+ userConfigPath,
109
90
  await this.writeFilesManifest(cfgDir),
110
91
  ];
111
92
 
93
+ await this.ensureCustomConfigStubs(bmadDir);
94
+
112
95
  return {
113
96
  skills: this.skills.length,
114
97
  agents: this.agents.length,
@@ -150,24 +133,13 @@ class ManifestGenerator {
150
133
  const skillMeta = await this.parseSkillMd(skillMdPath, dir, dirName, debug);
151
134
 
152
135
  if (skillMeta) {
153
- // Load manifest when present (for agent metadata)
154
- const manifest = await this.loadSkillManifest(dir);
155
- const artifactType = this.getArtifactType(manifest, skillFile);
156
-
157
136
  // Build path relative from module root (points to SKILL.md — the permanent entrypoint)
158
137
  const relativePath = path.relative(modulePath, dir).split(path.sep).join('/');
159
138
  const installPath = relativePath
160
139
  ? `${this.bmadFolderName}/${moduleName}/${relativePath}/${skillFile}`
161
140
  : `${this.bmadFolderName}/${moduleName}/${skillFile}`;
162
141
 
163
- // Native SKILL.md entrypoints derive canonicalId from directory name.
164
- // Agent entrypoints may keep canonicalId metadata for compatibility, so
165
- // only warn for non-agent SKILL.md directories.
166
- if (manifest && manifest.__single && manifest.__single.canonicalId && artifactType !== 'agent') {
167
- console.warn(
168
- `Warning: Native entrypoint manifest at ${dir}/bmad-skill-manifest.yaml contains canonicalId — this field is ignored for SKILL.md directories (directory name is the canonical ID)`,
169
- );
170
- }
142
+ // Native SKILL.md entrypoints always derive canonicalId from directory name.
171
143
  const canonicalId = dirName;
172
144
 
173
145
  this.skills.push({
@@ -263,105 +235,49 @@ class ManifestGenerator {
263
235
  }
264
236
 
265
237
  /**
266
- * Collect all agents from selected modules by walking their directory trees.
238
+ * Collect agents from each installed module's source module.yaml `agents:` array.
239
+ * Essence fields (code, name, title, icon, description) are authored in module.yaml;
240
+ * `team` defaults to module code when not set; `module` is always the owning module.
267
241
  */
268
- async collectAgents(selectedModules) {
242
+ async collectAgentsFromModuleYaml() {
269
243
  this.agents = [];
270
244
  const debug = process.env.BMAD_DEBUG_MANIFEST === 'true';
271
245
 
272
- // Walk each module's full directory tree looking for type:agent manifests
273
246
  for (const moduleName of this.updatedModules) {
274
- const modulePath = path.join(this.bmadDir, moduleName);
275
- if (!(await fs.pathExists(modulePath))) continue;
276
-
277
- const moduleAgents = await this.getAgentsFromDirRecursive(modulePath, moduleName, '', debug);
278
- this.agents.push(...moduleAgents);
279
- }
280
-
281
- // Get standalone agents from bmad/agents/ directory
282
- const standaloneAgentsDir = path.join(this.bmadDir, 'agents');
283
- if (await fs.pathExists(standaloneAgentsDir)) {
284
- const standaloneAgents = await this.getAgentsFromDirRecursive(standaloneAgentsDir, 'standalone', '', debug);
285
- this.agents.push(...standaloneAgents);
286
- }
287
-
288
- if (debug) {
289
- console.log(`[DEBUG] collectAgents: total agents found: ${this.agents.length}`);
290
- }
291
- }
292
-
293
- /**
294
- * Recursively walk a directory tree collecting agents.
295
- * Discovers agents via directory with bmad-skill-manifest.yaml containing type: agent
296
- *
297
- * @param {string} dirPath - Current directory being scanned
298
- * @param {string} moduleName - Module this directory belongs to
299
- * @param {string} relativePath - Path relative to the module root (for install path construction)
300
- * @param {boolean} debug - Emit debug messages
301
- */
302
- async getAgentsFromDirRecursive(dirPath, moduleName, relativePath = '', debug = false) {
303
- const agents = [];
304
- let entries;
305
- try {
306
- entries = await fs.readdir(dirPath, { withFileTypes: true });
307
- } catch {
308
- return agents;
309
- }
310
-
311
- for (const entry of entries) {
312
- if (!entry.isDirectory()) continue;
313
- if (entry.name.startsWith('.') || entry.name.startsWith('_')) continue;
247
+ const moduleYamlPath = path.join(getModulePath(moduleName), 'module.yaml');
248
+ if (!(await fs.pathExists(moduleYamlPath))) continue;
314
249
 
315
- const fullPath = path.join(dirPath, entry.name);
316
-
317
- // Check for type:agent manifest BEFORE checking skillClaimedDirs —
318
- // agent dirs may be claimed by collectSkills for IDE installation,
319
- // but we still need them in agent-manifest.csv.
320
- const dirManifest = await this.loadSkillManifest(fullPath);
321
- if (dirManifest && dirManifest.__single && dirManifest.__single.type === 'agent') {
322
- const m = dirManifest.__single;
323
- const dirRelativePath = relativePath ? `${relativePath}/${entry.name}` : entry.name;
324
- const agentModule = m.module || moduleName;
325
- const installPath = `${this.bmadFolderName}/${agentModule}/${dirRelativePath}`;
326
-
327
- agents.push({
328
- name: m.name || entry.name,
329
- displayName: m.displayName || m.name || entry.name,
330
- title: m.title || '',
331
- icon: m.icon || '',
332
- role: m.role ? this.cleanForCSV(m.role) : '',
333
- identity: m.identity ? this.cleanForCSV(m.identity) : '',
334
- communicationStyle: m.communicationStyle ? this.cleanForCSV(m.communicationStyle) : '',
335
- principles: m.principles ? this.cleanForCSV(m.principles) : '',
336
- module: agentModule,
337
- path: installPath,
338
- canonicalId: m.canonicalId || '',
339
- });
340
-
341
- this.files.push({
342
- type: 'agent',
343
- name: m.name || entry.name,
344
- module: agentModule,
345
- path: installPath,
346
- });
347
-
348
- if (debug) {
349
- console.log(`[DEBUG] collectAgents: found type:agent "${m.name || entry.name}" at ${fullPath}`);
350
- }
250
+ let moduleDef;
251
+ try {
252
+ moduleDef = yaml.parse(await fs.readFile(moduleYamlPath, 'utf8'));
253
+ } catch (error) {
254
+ if (debug) console.log(`[DEBUG] collectAgentsFromModuleYaml: failed to parse ${moduleYamlPath}: ${error.message}`);
351
255
  continue;
352
256
  }
353
257
 
354
- // Skip directories claimed by collectSkills (non-agent type skills)
355
- // avoids recursing into skill trees that can't contain agents.
356
- if (this.skillClaimedDirs && this.skillClaimedDirs.has(fullPath)) continue;
258
+ if (!moduleDef || !Array.isArray(moduleDef.agents)) continue;
259
+
260
+ for (const entry of moduleDef.agents) {
261
+ if (!entry || typeof entry.code !== 'string') continue;
262
+ this.agents.push({
263
+ code: entry.code,
264
+ name: entry.name || '',
265
+ title: entry.title || '',
266
+ icon: entry.icon || '',
267
+ description: entry.description || '',
268
+ module: moduleName,
269
+ team: entry.team || moduleName,
270
+ });
271
+ }
357
272
 
358
- // Recurse into subdirectories
359
- const newRelativePath = relativePath ? `${relativePath}/${entry.name}` : entry.name;
360
- const subDirAgents = await this.getAgentsFromDirRecursive(fullPath, moduleName, newRelativePath, debug);
361
- agents.push(...subDirAgents);
273
+ if (debug) {
274
+ console.log(`[DEBUG] collectAgentsFromModuleYaml: ${moduleName} contributed ${moduleDef.agents.length} agents`);
275
+ }
362
276
  }
363
277
 
364
- return agents;
278
+ if (debug) {
279
+ console.log(`[DEBUG] collectAgentsFromModuleYaml: total agents found: ${this.agents.length}`);
280
+ }
365
281
  }
366
282
 
367
283
  /**
@@ -477,75 +393,230 @@ class ManifestGenerator {
477
393
  }
478
394
 
479
395
  /**
480
- * Write agent manifest CSV
481
- * @returns {string} Path to the manifest file
396
+ * Write central _bmad/config.toml with [core], [modules.<code>], [agents.<code>] tables.
397
+ * Install-owned. Team-scope answers config.toml; user-scope answers → config.user.toml.
398
+ * Both files are regenerated on every install. User overrides live in
399
+ * _bmad/custom/config.toml and _bmad/custom/config.user.toml (never touched by installer).
400
+ * @returns {string[]} Paths to the written config files
482
401
  */
483
- async writeAgentManifest(cfgDir) {
484
- const csvPath = path.join(cfgDir, 'agent-manifest.csv');
485
- const escapeCsv = (value) => `"${String(value ?? '').replaceAll('"', '""')}"`;
486
-
487
- // Read existing manifest to preserve entries
488
- const existingEntries = new Map();
489
- if (await fs.pathExists(csvPath)) {
490
- const content = await fs.readFile(csvPath, 'utf8');
491
- const records = csv.parse(content, {
492
- columns: true,
493
- skip_empty_lines: true,
494
- });
495
- for (const record of records) {
496
- existingEntries.set(`${record.module}:${record.name}`, record);
402
+ async writeCentralConfig(bmadDir, moduleConfigs) {
403
+ const teamPath = path.join(bmadDir, 'config.toml');
404
+ const userPath = path.join(bmadDir, 'config.user.toml');
405
+
406
+ // Load each module's source module.yaml to determine scope per prompt key.
407
+ // Default scope is 'team' when the prompt doesn't declare one.
408
+ // When a module.yaml is unreadable we warn — for known official modules
409
+ // this means user-scoped keys (e.g. user_name) could mis-file into the
410
+ // team config, so the operator should notice.
411
+ const scopeByModuleKey = {};
412
+ for (const moduleName of this.updatedModules) {
413
+ const moduleYamlPath = path.join(getModulePath(moduleName), 'module.yaml');
414
+ if (!(await fs.pathExists(moduleYamlPath))) continue;
415
+ try {
416
+ const parsed = yaml.parse(await fs.readFile(moduleYamlPath, 'utf8'));
417
+ if (!parsed || typeof parsed !== 'object') continue;
418
+ scopeByModuleKey[moduleName] = {};
419
+ for (const [key, value] of Object.entries(parsed)) {
420
+ if (value && typeof value === 'object' && 'prompt' in value) {
421
+ scopeByModuleKey[moduleName][key] = value.scope === 'user' ? 'user' : 'team';
422
+ }
423
+ }
424
+ } catch (error) {
425
+ console.warn(
426
+ `[warn] writeCentralConfig: could not parse module.yaml for '${moduleName}' (${error.message}). ` +
427
+ `Answers from this module will default to team scope — user-scoped keys may mis-file into config.toml.`,
428
+ );
497
429
  }
498
430
  }
499
431
 
500
- // Create CSV header with persona fields and canonicalId
501
- let csvContent = 'name,displayName,title,icon,role,identity,communicationStyle,principles,module,path,canonicalId\n';
432
+ // Core keys are always known (core module.yaml is built-in). These are
433
+ // the only keys allowed in [core]; they must be stripped from every
434
+ // non-core module bucket because legacy _bmad/{mod}/config.yaml files
435
+ // spread core values into each module. Core belongs in [core] only —
436
+ // workflows that need user_name/language/etc. read [core] directly.
437
+ const coreKeys = new Set(Object.keys(scopeByModuleKey.core || {}));
438
+
439
+ // Partition a module's answered config into team vs user buckets.
440
+ // For non-core modules: strip core keys always; when we know the module's
441
+ // own schema, also drop keys it doesn't declare. Unknown-schema modules
442
+ // (external / marketplace) fall through with their remaining answers as
443
+ // team so they don't vanish from the config.
444
+ const partition = (moduleName, cfg, onlyDeclaredKeys = false) => {
445
+ const team = {};
446
+ const user = {};
447
+ const scopes = scopeByModuleKey[moduleName] || {};
448
+ const isCore = moduleName === 'core';
449
+ for (const [key, value] of Object.entries(cfg || {})) {
450
+ if (!isCore && coreKeys.has(key)) continue;
451
+ if (onlyDeclaredKeys && !(key in scopes)) continue;
452
+ if (scopes[key] === 'user') {
453
+ user[key] = value;
454
+ } else {
455
+ team[key] = value;
456
+ }
457
+ }
458
+ return { team, user };
459
+ };
460
+
461
+ const teamHeader = [
462
+ '# ─────────────────────────────────────────────────────────────────',
463
+ '# Installer-managed. Regenerated on every install — treat as read-only.',
464
+ '#',
465
+ '# Direct edits to this file will be overwritten on the next install.',
466
+ '# To change an install answer durably, re-run the installer (your prior',
467
+ '# answers are remembered as defaults). To pin a value regardless of',
468
+ '# install answers, or to add custom agents / override descriptors, use:',
469
+ '# _bmad/custom/config.toml (team, committed)',
470
+ '# _bmad/custom/config.user.toml (personal, gitignored)',
471
+ '# Those files are never touched by the installer.',
472
+ '# ─────────────────────────────────────────────────────────────────',
473
+ '',
474
+ ];
475
+
476
+ const userHeader = [
477
+ '# ─────────────────────────────────────────────────────────────────',
478
+ '# Installer-managed. Regenerated on every install — treat as read-only.',
479
+ '# Holds install answers scoped to YOU personally.',
480
+ '#',
481
+ '# Direct edits to this file will be overwritten on the next install.',
482
+ '# To change an answer durably, re-run the installer (your prior answers',
483
+ '# are remembered as defaults). For pinned overrides or custom sections',
484
+ '# the installer does not know about, use _bmad/custom/config.user.toml',
485
+ '# — it is never touched by the installer.',
486
+ '# ─────────────────────────────────────────────────────────────────',
487
+ '',
488
+ ];
489
+
490
+ const teamLines = [...teamHeader];
491
+ const userLines = [...userHeader];
492
+
493
+ // [core] — split into team and user
494
+ const coreConfig = moduleConfigs.core || {};
495
+ const { team: coreTeam, user: coreUser } = partition('core', coreConfig);
496
+ if (Object.keys(coreTeam).length > 0) {
497
+ teamLines.push('[core]');
498
+ for (const [key, value] of Object.entries(coreTeam)) {
499
+ teamLines.push(`${key} = ${formatTomlValue(value)}`);
500
+ }
501
+ teamLines.push('');
502
+ }
503
+ if (Object.keys(coreUser).length > 0) {
504
+ userLines.push('[core]');
505
+ for (const [key, value] of Object.entries(coreUser)) {
506
+ userLines.push(`${key} = ${formatTomlValue(value)}`);
507
+ }
508
+ userLines.push('');
509
+ }
502
510
 
503
- // Combine existing and new agents, preferring new data for duplicates
504
- const allAgents = new Map();
511
+ // [modules.<code>] split per module
512
+ for (const moduleName of this.updatedModules) {
513
+ if (moduleName === 'core') continue;
514
+ const cfg = moduleConfigs[moduleName];
515
+ if (!cfg || Object.keys(cfg).length === 0) continue;
516
+ // Only filter out spread-from-core pollution when we actually know
517
+ // this module's prompt schema. For external/marketplace modules whose
518
+ // module.yaml isn't in the src tree, fall through as all-team so we
519
+ // don't drop their real answers.
520
+ const haveSchema = Object.keys(scopeByModuleKey[moduleName] || {}).length > 0;
521
+ const { team: modTeam, user: modUser } = partition(moduleName, cfg, haveSchema);
522
+ if (Object.keys(modTeam).length > 0) {
523
+ teamLines.push(`[modules.${moduleName}]`);
524
+ for (const [key, value] of Object.entries(modTeam)) {
525
+ teamLines.push(`${key} = ${formatTomlValue(value)}`);
526
+ }
527
+ teamLines.push('');
528
+ }
529
+ if (Object.keys(modUser).length > 0) {
530
+ userLines.push(`[modules.${moduleName}]`);
531
+ for (const [key, value] of Object.entries(modUser)) {
532
+ userLines.push(`${key} = ${formatTomlValue(value)}`);
533
+ }
534
+ userLines.push('');
535
+ }
536
+ }
505
537
 
506
- // Add existing entries
507
- for (const [key, value] of existingEntries) {
508
- allAgents.set(key, value);
538
+ // [agents.<code>] always team (agent roster is organizational).
539
+ // Freshly collected agents come from module.yaml this run. If a module
540
+ // was preserved (e.g. during quickUpdate when its source isn't available),
541
+ // its module.yaml wasn't read — so its agents aren't in `this.agents` and
542
+ // would silently disappear from the roster. Preserve those existing
543
+ // [agents.*] blocks verbatim from the prior config.toml.
544
+ const freshAgentCodes = new Set(this.agents.map((a) => a.code));
545
+ const contributingModules = new Set(this.agents.map((a) => a.module));
546
+ const preservedModules = this.updatedModules.filter((m) => !contributingModules.has(m));
547
+ const preservedBlocks = [];
548
+ if (preservedModules.length > 0 && (await fs.pathExists(teamPath))) {
549
+ try {
550
+ const prev = await fs.readFile(teamPath, 'utf8');
551
+ for (const block of extractAgentBlocks(prev)) {
552
+ if (freshAgentCodes.has(block.code)) continue;
553
+ if (block.module && preservedModules.includes(block.module)) {
554
+ preservedBlocks.push(block.body);
555
+ }
556
+ }
557
+ } catch (error) {
558
+ console.warn(`[warn] writeCentralConfig: could not read prior config.toml to preserve agents: ${error.message}`);
559
+ }
509
560
  }
510
561
 
511
- // Add/update new agents
512
562
  for (const agent of this.agents) {
513
- const key = `${agent.module}:${agent.name}`;
514
- allAgents.set(key, {
515
- name: agent.name,
516
- displayName: agent.displayName,
517
- title: agent.title,
518
- icon: agent.icon,
519
- role: agent.role,
520
- identity: agent.identity,
521
- communicationStyle: agent.communicationStyle,
522
- principles: agent.principles,
523
- module: agent.module,
524
- path: agent.path,
525
- canonicalId: agent.canonicalId || '',
526
- });
563
+ const agentLines = [`[agents.${agent.code}]`, `module = ${formatTomlValue(agent.module)}`, `team = ${formatTomlValue(agent.team)}`];
564
+ if (agent.name) agentLines.push(`name = ${formatTomlValue(agent.name)}`);
565
+ if (agent.title) agentLines.push(`title = ${formatTomlValue(agent.title)}`);
566
+ if (agent.icon) agentLines.push(`icon = ${formatTomlValue(agent.icon)}`);
567
+ if (agent.description) agentLines.push(`description = ${formatTomlValue(agent.description)}`);
568
+ agentLines.push('');
569
+ teamLines.push(...agentLines);
527
570
  }
528
571
 
529
- // Write all agents
530
- for (const [, record] of allAgents) {
531
- const row = [
532
- escapeCsv(record.name),
533
- escapeCsv(record.displayName),
534
- escapeCsv(record.title),
535
- escapeCsv(record.icon),
536
- escapeCsv(record.role),
537
- escapeCsv(record.identity),
538
- escapeCsv(record.communicationStyle),
539
- escapeCsv(record.principles),
540
- escapeCsv(record.module),
541
- escapeCsv(record.path),
542
- escapeCsv(record.canonicalId),
543
- ].join(',');
544
- csvContent += row + '\n';
572
+ for (const body of preservedBlocks) {
573
+ teamLines.push(body, '');
545
574
  }
546
575
 
547
- await fs.writeFile(csvPath, csvContent);
548
- return csvPath;
576
+ const teamContent = teamLines.join('\n').replace(/\n+$/, '\n');
577
+ const userContent = userLines.join('\n').replace(/\n+$/, '\n');
578
+ await fs.writeFile(teamPath, teamContent);
579
+ await fs.writeFile(userPath, userContent);
580
+ return [teamPath, userPath];
581
+ }
582
+
583
+ /**
584
+ * Create empty _bmad/custom/config.toml and _bmad/custom/config.user.toml stubs
585
+ * on first install only. Installer never touches these files again after creation.
586
+ */
587
+ async ensureCustomConfigStubs(bmadDir) {
588
+ const customDir = path.join(bmadDir, 'custom');
589
+ await fs.ensureDir(customDir);
590
+
591
+ const stubs = [
592
+ {
593
+ file: path.join(customDir, 'config.toml'),
594
+ header: [
595
+ '# Team / enterprise overrides for _bmad/config.toml.',
596
+ '# Committed to the repo — applies to every developer on the project.',
597
+ '# Tables deep-merge over base config; keyed entries merge by key.',
598
+ '# Example: override an agent descriptor, or add a new agent.',
599
+ '#',
600
+ '# [agents.bmad-agent-pm]',
601
+ '# description = "Prefers short, bulleted PRDs over narrative drafts."',
602
+ '',
603
+ ],
604
+ },
605
+ {
606
+ file: path.join(customDir, 'config.user.toml'),
607
+ header: [
608
+ '# Personal overrides for _bmad/config.toml.',
609
+ '# NOT committed (gitignored) — applies only to your local install.',
610
+ '# Wins over both base config and team overrides.',
611
+ '',
612
+ ],
613
+ },
614
+ ];
615
+
616
+ for (const { file, header } of stubs) {
617
+ if (await fs.pathExists(file)) continue;
618
+ await fs.writeFile(file, header.join('\n'));
619
+ }
549
620
  }
550
621
 
551
622
  /**
@@ -691,4 +762,59 @@ class ManifestGenerator {
691
762
  }
692
763
  }
693
764
 
765
+ /**
766
+ * Format a JS scalar as a TOML value literal.
767
+ * Handles strings (quoted + escaped), booleans, numbers, and arrays of scalars.
768
+ * Objects are not expected at this emit path.
769
+ */
770
+ function formatTomlValue(value) {
771
+ if (value === null || value === undefined) return '""';
772
+ if (typeof value === 'boolean') return value ? 'true' : 'false';
773
+ if (typeof value === 'number' && Number.isFinite(value)) return String(value);
774
+ if (Array.isArray(value)) return `[${value.map((v) => formatTomlValue(v)).join(', ')}]`;
775
+ const str = String(value);
776
+ const escaped = str
777
+ .replaceAll('\\', '\\\\')
778
+ .replaceAll('"', String.raw`\"`)
779
+ .replaceAll('\n', String.raw`\n`)
780
+ .replaceAll('\r', String.raw`\r`)
781
+ .replaceAll('\t', String.raw`\t`);
782
+ return `"${escaped}"`;
783
+ }
784
+
785
+ /**
786
+ * Extract [agents.<code>] blocks from a previously-emitted config.toml.
787
+ * We only need this for roster preservation — the file is our own controlled
788
+ * output, so a simple line scanner is safer than adding a TOML parser
789
+ * dependency. Each block runs from its `[agents.<code>]` header until the
790
+ * next `[` heading or EOF; the `module = "..."` line inside drives which
791
+ * entries we keep on the next write.
792
+ * @returns {Array<{code: string, module: string | null, body: string}>}
793
+ */
794
+ function extractAgentBlocks(tomlContent) {
795
+ const blocks = [];
796
+ const lines = tomlContent.split('\n');
797
+ let i = 0;
798
+ while (i < lines.length) {
799
+ const header = lines[i].match(/^\[agents\.([^\]]+)]\s*$/);
800
+ if (!header) {
801
+ i++;
802
+ continue;
803
+ }
804
+ const code = header[1];
805
+ const blockLines = [lines[i]];
806
+ let moduleName = null;
807
+ i++;
808
+ while (i < lines.length && !lines[i].startsWith('[')) {
809
+ blockLines.push(lines[i]);
810
+ const m = lines[i].match(/^module\s*=\s*"((?:[^"\\]|\\.)*)"\s*$/);
811
+ if (m) moduleName = m[1];
812
+ i++;
813
+ }
814
+ while (blockLines.length > 1 && blockLines.at(-1) === '') blockLines.pop();
815
+ blocks.push({ code, module: moduleName, body: blockLines.join('\n') });
816
+ }
817
+ return blocks;
818
+ }
819
+
694
820
  module.exports = { ManifestGenerator };
@@ -1,11 +0,0 @@
1
- type: agent
2
- name: bmad-agent-analyst
3
- displayName: Mary
4
- title: Business Analyst
5
- icon: "📊"
6
- capabilities: "market research, competitive analysis, requirements elicitation, domain expertise"
7
- role: Strategic Business Analyst + Requirements Expert
8
- identity: "Senior analyst with deep expertise in market research, competitive analysis, and requirements elicitation. Specializes in translating vague needs into actionable specs."
9
- communicationStyle: "Speaks with the excitement of a treasure hunter - thrilled by every clue, energized when patterns emerge. Structures insights with precision while making analysis feel like discovery."
10
- principles: "Channel expert business analysis frameworks: draw upon Porter's Five Forces, SWOT analysis, root cause analysis, and competitive intelligence methodologies to uncover what others miss. Every business challenge has root causes waiting to be discovered. Ground findings in verifiable evidence. Articulate requirements with absolute precision. Ensure all stakeholder voices heard."
11
- module: bmm
@@ -1,11 +0,0 @@
1
- type: agent
2
- name: bmad-agent-tech-writer
3
- displayName: Paige
4
- title: Technical Writer
5
- icon: "📚"
6
- capabilities: "documentation, Mermaid diagrams, standards compliance, concept explanation"
7
- role: Technical Documentation Specialist + Knowledge Curator
8
- identity: "Experienced technical writer expert in CommonMark, DITA, OpenAPI. Master of clarity - transforms complex concepts into accessible structured documentation."
9
- communicationStyle: "Patient educator who explains like teaching a friend. Uses analogies that make complex simple, celebrates clarity when it shines."
10
- principles: "Every Technical Document I touch helps someone accomplish a task. Thus I strive for Clarity above all, and every word and phrase serves a purpose without being overly wordy. I believe a picture/diagram is worth 1000s of words and will include diagrams over drawn out text. I understand the intended audience or will clarify with the user so I know when to simplify vs when to be detailed."
11
- module: bmm
@@ -1,11 +0,0 @@
1
- type: agent
2
- name: bmad-agent-pm
3
- displayName: John
4
- title: Product Manager
5
- icon: "📋"
6
- capabilities: "PRD creation, requirements discovery, stakeholder alignment, user interviews"
7
- role: "Product Manager specializing in collaborative PRD creation through user interviews, requirement discovery, and stakeholder alignment."
8
- identity: "Product management veteran with 8+ years launching B2B and consumer products. Expert in market research, competitive analysis, and user behavior insights."
9
- communicationStyle: "Asks 'WHY?' relentlessly like a detective on a case. Direct and data-sharp, cuts through fluff to what actually matters."
10
- principles: "Channel expert product manager thinking: draw upon deep knowledge of user-centered design, Jobs-to-be-Done framework, opportunity scoring, and what separates great products from mediocre ones. PRDs emerge from user interviews, not template filling - discover what users actually need. Ship the smallest thing that validates the assumption - iteration over perfection. Technical feasibility is a constraint, not the driver - user value first."
11
- module: bmm
@@ -1,11 +0,0 @@
1
- type: agent
2
- name: bmad-agent-ux-designer
3
- displayName: Sally
4
- title: UX Designer
5
- icon: "🎨"
6
- capabilities: "user research, interaction design, UI patterns, experience strategy"
7
- role: User Experience Designer + UI Specialist
8
- identity: "Senior UX Designer with 7+ years creating intuitive experiences across web and mobile. Expert in user research, interaction design, AI-assisted tools."
9
- communicationStyle: "Paints pictures with words, telling user stories that make you FEEL the problem. Empathetic advocate with creative storytelling flair."
10
- principles: "Every decision serves genuine user needs. Start simple, evolve through feedback. Balance empathy with edge case attention. AI tools accelerate human-centered design. Data-informed but always creative."
11
- module: bmm
@@ -1,11 +0,0 @@
1
- type: agent
2
- name: bmad-agent-architect
3
- displayName: Winston
4
- title: Architect
5
- icon: "🏗️"
6
- capabilities: "distributed systems, cloud infrastructure, API design, scalable patterns"
7
- role: System Architect + Technical Design Leader
8
- identity: "Senior architect with expertise in distributed systems, cloud infrastructure, and API design. Specializes in scalable patterns and technology selection."
9
- communicationStyle: "Speaks in calm, pragmatic tones, balancing 'what could be' with 'what should be.'"
10
- principles: "Channel expert lean architecture wisdom: draw upon deep knowledge of distributed systems, cloud patterns, scalability trade-offs, and what actually ships successfully. User journeys drive technical decisions. Embrace boring technology for stability. Design simple solutions that scale when needed. Developer productivity is architecture. Connect every decision to business value and user impact."
11
- module: bmm
@@ -1,11 +0,0 @@
1
- type: agent
2
- name: bmad-agent-dev
3
- displayName: Amelia
4
- title: Developer Agent
5
- icon: "💻"
6
- capabilities: "story execution, test-driven development, code implementation"
7
- role: Senior Software Engineer
8
- identity: "Executes approved stories with strict adherence to story details and team standards and practices."
9
- communicationStyle: "Ultra-succinct. Speaks in file paths and AC IDs - every statement citable. No fluff, all precision."
10
- principles: "All existing and new tests must pass 100% before story is ready for review. Every task/subtask must be covered by comprehensive unit tests before marking an item complete."
11
- module: bmm