bmad-method 6.3.1-next.9 → 6.4.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 (140) hide show
  1. package/package.json +3 -2
  2. package/src/bmm-skills/1-analysis/bmad-agent-analyst/SKILL.md +51 -36
  3. package/src/bmm-skills/1-analysis/bmad-agent-analyst/customize.toml +90 -0
  4. package/src/bmm-skills/1-analysis/bmad-agent-tech-writer/SKILL.md +50 -33
  5. package/src/bmm-skills/1-analysis/bmad-agent-tech-writer/customize.toml +81 -0
  6. package/src/bmm-skills/1-analysis/bmad-document-project/SKILL.md +57 -1
  7. package/src/bmm-skills/1-analysis/bmad-document-project/customize.toml +41 -0
  8. package/src/bmm-skills/1-analysis/bmad-document-project/workflows/deep-dive-instructions.md +1 -0
  9. package/src/bmm-skills/1-analysis/bmad-document-project/workflows/full-scan-instructions.md +1 -0
  10. package/src/bmm-skills/1-analysis/bmad-prfaq/SKILL.md +48 -9
  11. package/src/bmm-skills/1-analysis/bmad-prfaq/customize.toml +41 -0
  12. package/src/bmm-skills/1-analysis/bmad-prfaq/references/verdict.md +4 -0
  13. package/src/bmm-skills/1-analysis/bmad-product-brief/SKILL.md +44 -9
  14. package/src/bmm-skills/1-analysis/bmad-product-brief/customize.toml +47 -0
  15. package/src/bmm-skills/1-analysis/bmad-product-brief/prompts/contextual-discovery.md +8 -7
  16. package/src/bmm-skills/1-analysis/bmad-product-brief/prompts/draft-and-review.md +6 -5
  17. package/src/bmm-skills/1-analysis/bmad-product-brief/prompts/finalize.md +4 -1
  18. package/src/bmm-skills/1-analysis/bmad-product-brief/prompts/guided-elicitation.md +3 -2
  19. package/src/bmm-skills/1-analysis/research/bmad-domain-research/SKILL.md +91 -1
  20. package/src/bmm-skills/1-analysis/research/bmad-domain-research/customize.toml +41 -0
  21. package/src/bmm-skills/1-analysis/research/bmad-domain-research/domain-steps/step-06-research-synthesis.md +6 -0
  22. package/src/bmm-skills/1-analysis/research/bmad-market-research/SKILL.md +91 -1
  23. package/src/bmm-skills/1-analysis/research/bmad-market-research/customize.toml +41 -0
  24. package/src/bmm-skills/1-analysis/research/bmad-market-research/steps/step-06-research-completion.md +6 -0
  25. package/src/bmm-skills/1-analysis/research/bmad-technical-research/SKILL.md +91 -1
  26. package/src/bmm-skills/1-analysis/research/bmad-technical-research/customize.toml +41 -0
  27. package/src/bmm-skills/1-analysis/research/bmad-technical-research/technical-steps/step-06-research-synthesis.md +6 -0
  28. package/src/bmm-skills/2-plan-workflows/bmad-agent-pm/SKILL.md +50 -35
  29. package/src/bmm-skills/2-plan-workflows/bmad-agent-pm/customize.toml +85 -0
  30. package/src/bmm-skills/2-plan-workflows/bmad-agent-ux-designer/SKILL.md +50 -31
  31. package/src/bmm-skills/2-plan-workflows/bmad-agent-ux-designer/customize.toml +60 -0
  32. package/src/bmm-skills/2-plan-workflows/bmad-create-prd/SKILL.md +99 -1
  33. package/src/bmm-skills/2-plan-workflows/bmad-create-prd/customize.toml +41 -0
  34. package/src/bmm-skills/2-plan-workflows/bmad-create-prd/steps-c/step-12-complete.md +6 -0
  35. package/src/bmm-skills/2-plan-workflows/bmad-create-ux-design/SKILL.md +70 -1
  36. package/src/bmm-skills/2-plan-workflows/bmad-create-ux-design/customize.toml +41 -0
  37. package/src/bmm-skills/2-plan-workflows/bmad-create-ux-design/steps/step-14-complete.md +6 -0
  38. package/src/bmm-skills/2-plan-workflows/bmad-edit-prd/SKILL.md +97 -1
  39. package/src/bmm-skills/2-plan-workflows/bmad-edit-prd/customize.toml +42 -0
  40. package/src/bmm-skills/2-plan-workflows/bmad-edit-prd/steps-e/step-e-04-complete.md +2 -0
  41. package/src/bmm-skills/2-plan-workflows/bmad-validate-prd/SKILL.md +99 -1
  42. package/src/bmm-skills/2-plan-workflows/bmad-validate-prd/customize.toml +42 -0
  43. package/src/bmm-skills/2-plan-workflows/bmad-validate-prd/steps-v/step-v-13-report-complete.md +1 -0
  44. package/src/bmm-skills/3-solutioning/bmad-agent-architect/SKILL.md +50 -30
  45. package/src/bmm-skills/3-solutioning/bmad-agent-architect/customize.toml +65 -0
  46. package/src/bmm-skills/3-solutioning/bmad-check-implementation-readiness/SKILL.md +86 -1
  47. package/src/bmm-skills/3-solutioning/bmad-check-implementation-readiness/customize.toml +41 -0
  48. package/src/bmm-skills/3-solutioning/bmad-check-implementation-readiness/steps/step-06-final-assessment.md +6 -0
  49. package/src/bmm-skills/3-solutioning/bmad-create-architecture/SKILL.md +69 -1
  50. package/src/bmm-skills/3-solutioning/bmad-create-architecture/customize.toml +41 -0
  51. package/src/bmm-skills/3-solutioning/bmad-create-architecture/steps/step-08-complete.md +6 -0
  52. package/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/SKILL.md +88 -1
  53. package/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/customize.toml +41 -0
  54. package/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/steps/step-04-final-validation.md +6 -0
  55. package/src/bmm-skills/3-solutioning/bmad-generate-project-context/SKILL.md +76 -1
  56. package/src/bmm-skills/3-solutioning/bmad-generate-project-context/customize.toml +41 -0
  57. package/src/bmm-skills/3-solutioning/bmad-generate-project-context/steps/step-03-complete.md +6 -0
  58. package/src/bmm-skills/4-implementation/bmad-agent-dev/SKILL.md +48 -43
  59. package/src/bmm-skills/4-implementation/bmad-agent-dev/customize.toml +90 -0
  60. package/src/bmm-skills/4-implementation/bmad-checkpoint-preview/SKILL.md +46 -7
  61. package/src/bmm-skills/4-implementation/bmad-checkpoint-preview/customize.toml +41 -0
  62. package/src/bmm-skills/4-implementation/bmad-checkpoint-preview/step-05-wrapup.md +6 -0
  63. package/src/bmm-skills/4-implementation/bmad-code-review/SKILL.md +85 -1
  64. package/src/bmm-skills/4-implementation/bmad-code-review/customize.toml +41 -0
  65. package/src/bmm-skills/4-implementation/bmad-code-review/steps/step-04-present.md +6 -0
  66. package/src/bmm-skills/4-implementation/bmad-correct-course/SKILL.md +296 -1
  67. package/src/bmm-skills/4-implementation/bmad-correct-course/customize.toml +41 -0
  68. package/src/bmm-skills/4-implementation/bmad-create-story/SKILL.md +424 -1
  69. package/src/bmm-skills/4-implementation/bmad-create-story/customize.toml +41 -0
  70. package/src/bmm-skills/4-implementation/bmad-dev-story/SKILL.md +480 -1
  71. package/src/bmm-skills/4-implementation/bmad-dev-story/customize.toml +41 -0
  72. package/src/bmm-skills/4-implementation/bmad-qa-generate-e2e-tests/SKILL.md +171 -1
  73. package/src/bmm-skills/4-implementation/bmad-qa-generate-e2e-tests/customize.toml +41 -0
  74. package/src/bmm-skills/4-implementation/bmad-quick-dev/SKILL.md +106 -1
  75. package/src/bmm-skills/4-implementation/bmad-quick-dev/customize.toml +41 -0
  76. package/src/bmm-skills/4-implementation/bmad-quick-dev/step-05-present.md +6 -0
  77. package/src/bmm-skills/4-implementation/bmad-quick-dev/step-oneshot.md +6 -0
  78. package/src/bmm-skills/4-implementation/bmad-retrospective/SKILL.md +1507 -1
  79. package/src/bmm-skills/4-implementation/bmad-retrospective/customize.toml +41 -0
  80. package/src/bmm-skills/4-implementation/bmad-sprint-planning/SKILL.md +294 -1
  81. package/src/bmm-skills/4-implementation/bmad-sprint-planning/customize.toml +41 -0
  82. package/src/bmm-skills/4-implementation/bmad-sprint-status/SKILL.md +292 -1
  83. package/src/bmm-skills/4-implementation/bmad-sprint-status/customize.toml +41 -0
  84. package/src/bmm-skills/module.yaml +49 -0
  85. package/src/core-skills/bmad-advanced-elicitation/SKILL.md +7 -1
  86. package/src/core-skills/bmad-customize/SKILL.md +111 -0
  87. package/src/core-skills/bmad-customize/scripts/list_customizable_skills.py +231 -0
  88. package/src/core-skills/bmad-customize/scripts/tests/test_list_customizable_skills.py +249 -0
  89. package/src/core-skills/bmad-distillator/resources/distillate-format-reference.md +1 -1
  90. package/src/core-skills/bmad-party-mode/SKILL.md +13 -10
  91. package/src/core-skills/module-help.csv +1 -0
  92. package/src/core-skills/module.yaml +2 -0
  93. package/src/scripts/resolve_config.py +176 -0
  94. package/src/scripts/resolve_customization.py +230 -0
  95. package/tools/installer/commands/install.js +13 -0
  96. package/tools/installer/core/config.js +4 -1
  97. package/tools/installer/core/install-paths.js +11 -5
  98. package/tools/installer/core/installer.js +181 -94
  99. package/tools/installer/core/manifest-generator.js +339 -184
  100. package/tools/installer/core/manifest.js +86 -86
  101. package/tools/installer/ide/platform-codes.yaml +6 -0
  102. package/tools/installer/modules/channel-plan.js +203 -0
  103. package/tools/installer/modules/channel-resolver.js +241 -0
  104. package/tools/installer/modules/community-manager.js +130 -23
  105. package/tools/installer/modules/custom-module-manager.js +160 -19
  106. package/tools/installer/modules/external-manager.js +235 -32
  107. package/tools/installer/modules/official-modules.js +58 -12
  108. package/tools/installer/modules/registry-client.js +139 -7
  109. package/tools/installer/modules/registry-fallback.yaml +8 -0
  110. package/tools/installer/modules/version-resolver.js +336 -0
  111. package/tools/installer/project-root.js +54 -0
  112. package/tools/installer/ui.js +561 -50
  113. package/tools/platform-codes.yaml +6 -0
  114. package/src/bmm-skills/1-analysis/bmad-agent-analyst/bmad-skill-manifest.yaml +0 -11
  115. package/src/bmm-skills/1-analysis/bmad-agent-tech-writer/bmad-skill-manifest.yaml +0 -11
  116. package/src/bmm-skills/1-analysis/bmad-document-project/workflow.md +0 -25
  117. package/src/bmm-skills/1-analysis/research/bmad-domain-research/workflow.md +0 -51
  118. package/src/bmm-skills/1-analysis/research/bmad-market-research/workflow.md +0 -51
  119. package/src/bmm-skills/1-analysis/research/bmad-technical-research/workflow.md +0 -52
  120. package/src/bmm-skills/2-plan-workflows/bmad-agent-pm/bmad-skill-manifest.yaml +0 -11
  121. package/src/bmm-skills/2-plan-workflows/bmad-agent-ux-designer/bmad-skill-manifest.yaml +0 -11
  122. package/src/bmm-skills/2-plan-workflows/bmad-create-prd/workflow.md +0 -61
  123. package/src/bmm-skills/2-plan-workflows/bmad-create-ux-design/workflow.md +0 -35
  124. package/src/bmm-skills/2-plan-workflows/bmad-edit-prd/workflow.md +0 -62
  125. package/src/bmm-skills/2-plan-workflows/bmad-validate-prd/workflow.md +0 -61
  126. package/src/bmm-skills/3-solutioning/bmad-agent-architect/bmad-skill-manifest.yaml +0 -11
  127. package/src/bmm-skills/3-solutioning/bmad-check-implementation-readiness/workflow.md +0 -47
  128. package/src/bmm-skills/3-solutioning/bmad-create-architecture/workflow.md +0 -32
  129. package/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/workflow.md +0 -51
  130. package/src/bmm-skills/3-solutioning/bmad-generate-project-context/workflow.md +0 -39
  131. package/src/bmm-skills/4-implementation/bmad-agent-dev/bmad-skill-manifest.yaml +0 -11
  132. package/src/bmm-skills/4-implementation/bmad-code-review/workflow.md +0 -55
  133. package/src/bmm-skills/4-implementation/bmad-correct-course/workflow.md +0 -267
  134. package/src/bmm-skills/4-implementation/bmad-create-story/workflow.md +0 -380
  135. package/src/bmm-skills/4-implementation/bmad-dev-story/workflow.md +0 -450
  136. package/src/bmm-skills/4-implementation/bmad-qa-generate-e2e-tests/workflow.md +0 -136
  137. package/src/bmm-skills/4-implementation/bmad-quick-dev/workflow.md +0 -76
  138. package/src/bmm-skills/4-implementation/bmad-retrospective/workflow.md +0 -1479
  139. package/src/bmm-skills/4-implementation/bmad-sprint-planning/workflow.md +0 -263
  140. package/src/bmm-skills/4-implementation/bmad-sprint-status/workflow.md +0 -261
@@ -0,0 +1,249 @@
1
+ #!/usr/bin/env python3
2
+ # /// script
3
+ # requires-python = ">=3.11"
4
+ # ///
5
+ """Unit tests for list_customizable_skills.py.
6
+
7
+ Exercises the scanner against a synthesized install tree:
8
+ - an agent-only customize.toml
9
+ - a workflow-only customize.toml
10
+ - a customize.toml that exposes both surfaces
11
+ - a skill directory with no customize.toml (ignored)
12
+ - a pre-existing team override in _bmad/custom/
13
+ - malformed TOML (surfaces as an error without aborting)
14
+ - multiple skills roots (e.g. project-local + user-global mix)
15
+
16
+ Run: python3 scripts/tests/test_list_customizable_skills.py
17
+ """
18
+
19
+ from __future__ import annotations
20
+
21
+ import importlib.util
22
+ import json
23
+ import subprocess
24
+ import sys
25
+ import tempfile
26
+ import unittest
27
+ from pathlib import Path
28
+
29
+ SCRIPT = Path(__file__).resolve().parent.parent / "list_customizable_skills.py"
30
+
31
+
32
+ def _load_module():
33
+ spec = importlib.util.spec_from_file_location("list_customizable_skills", SCRIPT)
34
+ module = importlib.util.module_from_spec(spec)
35
+ spec.loader.exec_module(module) # type: ignore[union-attr]
36
+ return module
37
+
38
+
39
+ MODULE = _load_module()
40
+
41
+
42
+ def _make_skill(parent: Path, name: str, body: str, skill_md: str | None = None) -> Path:
43
+ skill_dir = parent / name
44
+ skill_dir.mkdir(parents=True, exist_ok=True)
45
+ (skill_dir / "customize.toml").write_text(body, encoding="utf-8")
46
+ if skill_md is not None:
47
+ (skill_dir / "SKILL.md").write_text(skill_md, encoding="utf-8")
48
+ return skill_dir
49
+
50
+
51
+ class ScannerTest(unittest.TestCase):
52
+ def setUp(self):
53
+ self.tmp = tempfile.TemporaryDirectory()
54
+ self.root = Path(self.tmp.name)
55
+ self.skills = self.root / "skills"
56
+ self.skills.mkdir(parents=True)
57
+ self.custom = self.root / "_bmad" / "custom"
58
+ self.custom.mkdir(parents=True)
59
+
60
+ def tearDown(self):
61
+ self.tmp.cleanup()
62
+
63
+ def test_agent_only_skill_detected(self):
64
+ _make_skill(
65
+ self.skills,
66
+ "bmad-agent-pm",
67
+ "[agent]\nicon = \"🧠\"\n",
68
+ "---\nname: bmad-agent-pm\ndescription: Product manager.\n---\n",
69
+ )
70
+ result = MODULE.scan_skills([self.skills], self.root)
71
+ self.assertEqual(len(result["agents"]), 1)
72
+ self.assertEqual(len(result["workflows"]), 0)
73
+ entry = result["agents"][0]
74
+ self.assertEqual(entry["name"], "bmad-agent-pm")
75
+ self.assertEqual(entry["surface"], "agent")
76
+ self.assertEqual(entry["description"], "Product manager.")
77
+ self.assertFalse(entry["has_team_override"])
78
+ self.assertFalse(entry["has_user_override"])
79
+
80
+ def test_workflow_only_skill_detected(self):
81
+ _make_skill(
82
+ self.skills,
83
+ "bmad-create-prd",
84
+ "[workflow]\npersistent_facts = []\n",
85
+ "---\nname: bmad-create-prd\ndescription: 'Create a PRD.'\n---\n",
86
+ )
87
+ result = MODULE.scan_skills([self.skills], self.root)
88
+ self.assertEqual(len(result["agents"]), 0)
89
+ self.assertEqual(len(result["workflows"]), 1)
90
+ entry = result["workflows"][0]
91
+ self.assertEqual(entry["description"], "Create a PRD.")
92
+
93
+ def test_dual_surface_skill_emits_two_entries(self):
94
+ _make_skill(
95
+ self.skills,
96
+ "bmad-dual",
97
+ "[agent]\nicon = \"x\"\n\n[workflow]\npersistent_facts = []\n",
98
+ "---\nname: bmad-dual\ndescription: Dual.\n---\n",
99
+ )
100
+ result = MODULE.scan_skills([self.skills], self.root)
101
+ self.assertEqual(len(result["agents"]), 1)
102
+ self.assertEqual(len(result["workflows"]), 1)
103
+ self.assertEqual(result["agents"][0]["name"], "bmad-dual")
104
+ self.assertEqual(result["workflows"][0]["name"], "bmad-dual")
105
+
106
+ def test_skill_without_customize_toml_ignored(self):
107
+ (self.skills / "bmad-plain").mkdir()
108
+ (self.skills / "bmad-plain" / "SKILL.md").write_text("# plain\n")
109
+ result = MODULE.scan_skills([self.skills], self.root)
110
+ self.assertEqual(len(result["agents"]) + len(result["workflows"]), 0)
111
+ self.assertEqual(result["errors"], [])
112
+
113
+ def test_existing_team_override_flagged(self):
114
+ _make_skill(
115
+ self.skills,
116
+ "bmad-agent-pm",
117
+ "[agent]\nicon = \"x\"\n",
118
+ "---\nname: bmad-agent-pm\ndescription: PM.\n---\n",
119
+ )
120
+ (self.custom / "bmad-agent-pm.toml").write_text("[agent]\n")
121
+ result = MODULE.scan_skills([self.skills], self.root)
122
+ entry = result["agents"][0]
123
+ self.assertTrue(entry["has_team_override"])
124
+ self.assertFalse(entry["has_user_override"])
125
+
126
+ def test_missing_surface_block_reports_error(self):
127
+ _make_skill(self.skills, "bmad-broken", "[not_a_surface]\nfoo = 1\n")
128
+ result = MODULE.scan_skills([self.skills], self.root)
129
+ self.assertEqual(len(result["agents"]) + len(result["workflows"]), 0)
130
+ self.assertEqual(len(result["errors"]), 1)
131
+ self.assertIn("no [agent] or [workflow] block", result["errors"][0])
132
+
133
+ def test_malformed_toml_reports_error_without_aborting(self):
134
+ skill_dir = self.skills / "bmad-bad"
135
+ skill_dir.mkdir()
136
+ (skill_dir / "customize.toml").write_text("this is not [valid toml\n")
137
+ # Plus a good sibling to confirm scanning continues.
138
+ _make_skill(
139
+ self.skills,
140
+ "bmad-good",
141
+ "[agent]\nicon = \"x\"\n",
142
+ "---\nname: bmad-good\ndescription: Good.\n---\n",
143
+ )
144
+ result = MODULE.scan_skills([self.skills], self.root)
145
+ self.assertEqual(len(result["agents"]), 1)
146
+ self.assertEqual(result["agents"][0]["name"], "bmad-good")
147
+ self.assertTrue(any("failed to parse" in e for e in result["errors"]))
148
+
149
+ def test_description_with_double_quotes_stripped(self):
150
+ _make_skill(
151
+ self.skills,
152
+ "bmad-q",
153
+ "[agent]\nicon = \"x\"\n",
154
+ '---\nname: bmad-q\ndescription: "Double-quoted desc."\n---\n',
155
+ )
156
+ result = MODULE.scan_skills([self.skills], self.root)
157
+ self.assertEqual(result["agents"][0]["description"], "Double-quoted desc.")
158
+
159
+ def test_multiple_skills_roots_are_merged(self):
160
+ extra_root = self.root / "extra-skills"
161
+ extra_root.mkdir()
162
+ _make_skill(
163
+ self.skills,
164
+ "bmad-agent-pm",
165
+ "[agent]\nicon = \"x\"\n",
166
+ "---\nname: bmad-agent-pm\ndescription: PM.\n---\n",
167
+ )
168
+ _make_skill(
169
+ extra_root,
170
+ "bmad-agent-dev",
171
+ "[agent]\nicon = \"y\"\n",
172
+ "---\nname: bmad-agent-dev\ndescription: Dev.\n---\n",
173
+ )
174
+ result = MODULE.scan_skills([self.skills, extra_root], self.root)
175
+ names = {a["name"] for a in result["agents"]}
176
+ self.assertEqual(names, {"bmad-agent-pm", "bmad-agent-dev"})
177
+ self.assertEqual(len(result["scanned_roots"]), 2)
178
+
179
+ def test_duplicate_skill_name_across_roots_first_wins(self):
180
+ extra_root = self.root / "extra-skills"
181
+ extra_root.mkdir()
182
+ _make_skill(
183
+ self.skills,
184
+ "bmad-agent-pm",
185
+ "[agent]\nicon = \"primary\"\n",
186
+ "---\nname: bmad-agent-pm\ndescription: Primary.\n---\n",
187
+ )
188
+ _make_skill(
189
+ extra_root,
190
+ "bmad-agent-pm",
191
+ "[agent]\nicon = \"duplicate\"\n",
192
+ "---\nname: bmad-agent-pm\ndescription: Duplicate.\n---\n",
193
+ )
194
+ result = MODULE.scan_skills([self.skills, extra_root], self.root)
195
+ self.assertEqual(len(result["agents"]), 1)
196
+ self.assertEqual(result["agents"][0]["description"], "Primary.")
197
+ self.assertEqual(result["agents"][0]["skills_root"], str(self.skills))
198
+
199
+ def test_missing_skills_root_reports_error(self):
200
+ result = MODULE.scan_skills(
201
+ [self.root / "does-not-exist", self.skills],
202
+ self.root,
203
+ )
204
+ self.assertTrue(any("skills root does not exist" in e for e in result["errors"]))
205
+
206
+ def test_cli_emits_valid_json_and_exits_zero(self):
207
+ _make_skill(
208
+ self.skills,
209
+ "bmad-agent-pm",
210
+ "[agent]\nicon = \"x\"\n",
211
+ "---\nname: bmad-agent-pm\ndescription: PM.\n---\n",
212
+ )
213
+ proc = subprocess.run(
214
+ [
215
+ sys.executable,
216
+ str(SCRIPT),
217
+ "--project-root",
218
+ str(self.root),
219
+ "--skills-root",
220
+ str(self.skills),
221
+ ],
222
+ capture_output=True,
223
+ text=True,
224
+ check=False,
225
+ )
226
+ self.assertEqual(proc.returncode, 0, proc.stderr)
227
+ payload = json.loads(proc.stdout)
228
+ self.assertEqual(len(payload["agents"]), 1)
229
+
230
+ def test_cli_exits_two_on_missing_project_root(self):
231
+ proc = subprocess.run(
232
+ [
233
+ sys.executable,
234
+ str(SCRIPT),
235
+ "--project-root",
236
+ str(self.root / "does-not-exist"),
237
+ "--skills-root",
238
+ str(self.skills),
239
+ ],
240
+ capture_output=True,
241
+ text=True,
242
+ check=False,
243
+ )
244
+ self.assertEqual(proc.returncode, 2)
245
+ self.assertIn("does not exist", proc.stderr)
246
+
247
+
248
+ if __name__ == "__main__":
249
+ unittest.main()
@@ -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.
@@ -10,3 +10,4 @@ Core,bmad-editorial-review-structure,Editorial Review - Structure,ES,Use when do
10
10
  Core,bmad-review-adversarial-general,Adversarial Review,AR,"Use for quality assurance or before finalizing deliverables. Code Review in other modules runs this automatically, but also useful for document reviews.",[path],anytime,,,false,,
11
11
  Core,bmad-review-edge-case-hunter,Edge Case Hunter Review,ECH,Use alongside adversarial review for orthogonal coverage — method-driven not attitude-driven.,[path],anytime,,,false,,
12
12
  Core,bmad-distillator,Distillator,DG,Use when you need token-efficient distillates that preserve all information for downstream LLM consumption.,[path],anytime,,,false,adjacent to source document or specified output_path,distillate markdown file(s)
13
+ Core,bmad-customize,BMad Customize,BC,"Use when you want to change how an agent or workflow behaves — add persistent facts, swap templates, insert activation hooks, or customize menus. Scans what's customizable, picks the right scope (agent vs workflow), writes the override to _bmad/custom/, and verifies the merge. No TOML hand-authoring required.",,anytime,,,false,{project-root}/_bmad/custom,TOML override files
@@ -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()