bmad-method 6.2.3-next.6 → 6.2.3-next.8

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 (57) hide show
  1. package/.claude-plugin/marketplace.json +0 -1
  2. package/package.json +1 -1
  3. package/src/bmm-skills/1-analysis/bmad-agent-analyst/SKILL.md +6 -4
  4. package/src/bmm-skills/1-analysis/bmad-agent-tech-writer/SKILL.md +6 -4
  5. package/src/bmm-skills/1-analysis/bmad-document-project/workflow.md +8 -10
  6. package/src/bmm-skills/1-analysis/bmad-prfaq/SKILL.md +12 -9
  7. package/src/bmm-skills/1-analysis/bmad-product-brief/SKILL.md +1 -6
  8. package/src/bmm-skills/1-analysis/research/bmad-domain-research/workflow.md +8 -6
  9. package/src/bmm-skills/1-analysis/research/bmad-market-research/workflow.md +8 -6
  10. package/src/bmm-skills/1-analysis/research/bmad-technical-research/workflow.md +8 -6
  11. package/src/bmm-skills/2-plan-workflows/bmad-agent-pm/SKILL.md +6 -4
  12. package/src/bmm-skills/2-plan-workflows/bmad-agent-ux-designer/SKILL.md +6 -4
  13. package/src/bmm-skills/2-plan-workflows/bmad-create-prd/workflow.md +8 -9
  14. package/src/bmm-skills/2-plan-workflows/bmad-create-ux-design/workflow.md +8 -9
  15. package/src/bmm-skills/2-plan-workflows/bmad-edit-prd/steps-e/step-e-01-discovery.md +1 -1
  16. package/src/bmm-skills/2-plan-workflows/bmad-edit-prd/steps-e/step-e-01b-legacy-conversion.md +1 -1
  17. package/src/bmm-skills/2-plan-workflows/bmad-edit-prd/steps-e/step-e-02-review.md +1 -1
  18. package/src/bmm-skills/2-plan-workflows/bmad-edit-prd/steps-e/step-e-03-edit.md +1 -1
  19. package/src/bmm-skills/2-plan-workflows/bmad-edit-prd/steps-e/step-e-04-complete.md +1 -1
  20. package/src/bmm-skills/2-plan-workflows/bmad-edit-prd/workflow.md +8 -9
  21. package/src/bmm-skills/2-plan-workflows/bmad-validate-prd/workflow.md +8 -9
  22. package/src/bmm-skills/3-solutioning/bmad-agent-architect/SKILL.md +6 -4
  23. package/src/bmm-skills/3-solutioning/bmad-check-implementation-readiness/workflow.md +8 -10
  24. package/src/bmm-skills/3-solutioning/bmad-create-architecture/workflow.md +8 -14
  25. package/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/workflow.md +10 -12
  26. package/src/bmm-skills/3-solutioning/bmad-generate-project-context/workflow.md +8 -12
  27. package/src/bmm-skills/4-implementation/bmad-agent-dev/SKILL.md +6 -4
  28. package/src/bmm-skills/4-implementation/bmad-agent-qa/SKILL.md +6 -4
  29. package/src/bmm-skills/4-implementation/bmad-agent-quick-flow-solo-dev/SKILL.md +6 -4
  30. package/src/bmm-skills/4-implementation/bmad-agent-sm/SKILL.md +6 -4
  31. package/src/core-skills/bmad-advanced-elicitation/SKILL.md +1 -2
  32. package/src/core-skills/bmad-distillator/SKILL.md +0 -1
  33. package/src/core-skills/bmad-distillator/resources/distillate-format-reference.md +9 -9
  34. package/src/core-skills/bmad-party-mode/workflow.md +5 -12
  35. package/tools/installer/ide/platform-codes.yaml +0 -1
  36. package/src/bmm-skills/2-plan-workflows/create-prd/data/domain-complexity.csv +0 -15
  37. package/src/bmm-skills/2-plan-workflows/create-prd/data/prd-purpose.md +0 -197
  38. package/src/bmm-skills/2-plan-workflows/create-prd/data/project-types.csv +0 -11
  39. package/src/bmm-skills/2-plan-workflows/create-prd/steps-v/step-v-01-discovery.md +0 -224
  40. package/src/bmm-skills/2-plan-workflows/create-prd/steps-v/step-v-02-format-detection.md +0 -191
  41. package/src/bmm-skills/2-plan-workflows/create-prd/steps-v/step-v-02b-parity-check.md +0 -209
  42. package/src/bmm-skills/2-plan-workflows/create-prd/steps-v/step-v-03-density-validation.md +0 -174
  43. package/src/bmm-skills/2-plan-workflows/create-prd/steps-v/step-v-04-brief-coverage-validation.md +0 -214
  44. package/src/bmm-skills/2-plan-workflows/create-prd/steps-v/step-v-05-measurability-validation.md +0 -228
  45. package/src/bmm-skills/2-plan-workflows/create-prd/steps-v/step-v-06-traceability-validation.md +0 -217
  46. package/src/bmm-skills/2-plan-workflows/create-prd/steps-v/step-v-07-implementation-leakage-validation.md +0 -205
  47. package/src/bmm-skills/2-plan-workflows/create-prd/steps-v/step-v-08-domain-compliance-validation.md +0 -243
  48. package/src/bmm-skills/2-plan-workflows/create-prd/steps-v/step-v-09-project-type-validation.md +0 -263
  49. package/src/bmm-skills/2-plan-workflows/create-prd/steps-v/step-v-10-smart-validation.md +0 -209
  50. package/src/bmm-skills/2-plan-workflows/create-prd/steps-v/step-v-11-holistic-quality-validation.md +0 -264
  51. package/src/bmm-skills/2-plan-workflows/create-prd/steps-v/step-v-12-completeness-validation.md +0 -242
  52. package/src/bmm-skills/2-plan-workflows/create-prd/steps-v/step-v-13-report-complete.md +0 -232
  53. package/src/bmm-skills/2-plan-workflows/create-prd/workflow-validate-prd.md +0 -65
  54. package/src/core-skills/bmad-init/SKILL.md +0 -100
  55. package/src/core-skills/bmad-init/resources/core-module.yaml +0 -25
  56. package/src/core-skills/bmad-init/scripts/bmad_init.py +0 -624
  57. package/src/core-skills/bmad-init/scripts/tests/test_bmad_init.py +0 -393
@@ -1,393 +0,0 @@
1
- # /// script
2
- # requires-python = ">=3.10"
3
- # dependencies = ["pyyaml"]
4
- # ///
5
-
6
- #!/usr/bin/env python3
7
- """Unit tests for bmad_init.py"""
8
-
9
- import json
10
- import os
11
- import shutil
12
- import sys
13
- import tempfile
14
- import unittest
15
- from pathlib import Path
16
-
17
- sys.path.insert(0, str(Path(__file__).parent.parent))
18
-
19
- from bmad_init import (
20
- find_project_root,
21
- parse_var_specs,
22
- resolve_project_root_placeholder,
23
- expand_template,
24
- apply_result_template,
25
- load_module_yaml,
26
- find_core_module_yaml,
27
- find_target_module_yaml,
28
- load_config_file,
29
- load_module_config,
30
- )
31
-
32
-
33
- class TestFindProjectRoot(unittest.TestCase):
34
-
35
- def test_finds_bmad_folder(self):
36
- temp_dir = tempfile.mkdtemp()
37
- try:
38
- (Path(temp_dir) / '_bmad').mkdir()
39
- original_cwd = os.getcwd()
40
- try:
41
- os.chdir(temp_dir)
42
- result = find_project_root()
43
- self.assertEqual(result.resolve(), Path(temp_dir).resolve())
44
- finally:
45
- os.chdir(original_cwd)
46
- finally:
47
- shutil.rmtree(temp_dir)
48
-
49
- def test_llm_provided_with_bmad(self):
50
- temp_dir = tempfile.mkdtemp()
51
- try:
52
- (Path(temp_dir) / '_bmad').mkdir()
53
- result = find_project_root(llm_provided=temp_dir)
54
- self.assertEqual(result.resolve(), Path(temp_dir).resolve())
55
- finally:
56
- shutil.rmtree(temp_dir)
57
-
58
- def test_llm_provided_without_bmad_still_returns_dir(self):
59
- """First-run case: LLM provides path but _bmad doesn't exist yet."""
60
- temp_dir = tempfile.mkdtemp()
61
- try:
62
- result = find_project_root(llm_provided=temp_dir)
63
- self.assertEqual(result.resolve(), Path(temp_dir).resolve())
64
- finally:
65
- shutil.rmtree(temp_dir)
66
-
67
-
68
- class TestParseVarSpecs(unittest.TestCase):
69
-
70
- def test_vars_with_defaults(self):
71
- specs = parse_var_specs('var1:value1,var2:value2')
72
- self.assertEqual(len(specs), 2)
73
- self.assertEqual(specs[0]['name'], 'var1')
74
- self.assertEqual(specs[0]['default'], 'value1')
75
-
76
- def test_vars_without_defaults(self):
77
- specs = parse_var_specs('var1,var2')
78
- self.assertEqual(len(specs), 2)
79
- self.assertIsNone(specs[0]['default'])
80
-
81
- def test_mixed_vars(self):
82
- specs = parse_var_specs('required_var,var2:default2')
83
- self.assertIsNone(specs[0]['default'])
84
- self.assertEqual(specs[1]['default'], 'default2')
85
-
86
- def test_colon_in_default(self):
87
- specs = parse_var_specs('path:{project-root}/some/path')
88
- self.assertEqual(specs[0]['default'], '{project-root}/some/path')
89
-
90
- def test_empty_string(self):
91
- self.assertEqual(parse_var_specs(''), [])
92
-
93
- def test_none(self):
94
- self.assertEqual(parse_var_specs(None), [])
95
-
96
-
97
- class TestResolveProjectRootPlaceholder(unittest.TestCase):
98
-
99
- def test_resolve_placeholder(self):
100
- result = resolve_project_root_placeholder('{project-root}/output', Path('/test'))
101
- self.assertEqual(result, '/test/output')
102
-
103
- def test_no_placeholder(self):
104
- result = resolve_project_root_placeholder('/absolute/path', Path('/test'))
105
- self.assertEqual(result, '/absolute/path')
106
-
107
- def test_none(self):
108
- self.assertIsNone(resolve_project_root_placeholder(None, Path('/test')))
109
-
110
- def test_non_string(self):
111
- self.assertEqual(resolve_project_root_placeholder(42, Path('/test')), 42)
112
-
113
- def test_absolute_path_stored_with_prefix(self):
114
- """Absolute output_folder entered by user is stored as '{project-root}//abs/path'
115
- by the '{project-root}/{value}' template. It must resolve to '/abs/path', not
116
- '/project//abs/path'."""
117
- result = resolve_project_root_placeholder(
118
- '{project-root}//Users/me/outside', Path('/Users/me/myproject')
119
- )
120
- self.assertEqual(result, '/Users/me/outside')
121
-
122
- def test_relative_path_with_traversal_is_normalized(self):
123
- """A relative path like '../../sibling' produces '{project-root}/../../sibling'
124
- after the template. It must resolve to the normalized absolute path, not the
125
- un-normalized string '/project/../../sibling'."""
126
- result = resolve_project_root_placeholder(
127
- '{project-root}/../../sibling', Path('/Users/me/myproject')
128
- )
129
- self.assertEqual(result, '/Users/sibling')
130
-
131
- def test_relative_path_one_level_up(self):
132
- result = resolve_project_root_placeholder(
133
- '{project-root}/../outside-outputs', Path('/project/root')
134
- )
135
- self.assertEqual(result, '/project/outside-outputs')
136
-
137
- def test_standard_relative_path_unchanged(self):
138
- """Normal in-project relative paths continue to work correctly."""
139
- result = resolve_project_root_placeholder(
140
- '{project-root}/_bmad-output', Path('/project/root')
141
- )
142
- self.assertEqual(result, '/project/root/_bmad-output')
143
-
144
-
145
- class TestExpandTemplate(unittest.TestCase):
146
-
147
- def test_basic_expansion(self):
148
- result = expand_template('{project-root}/output', {'project-root': '/test'})
149
- self.assertEqual(result, '/test/output')
150
-
151
- def test_multiple_placeholders(self):
152
- result = expand_template(
153
- '{output_folder}/planning',
154
- {'output_folder': '_bmad-output', 'project-root': '/test'}
155
- )
156
- self.assertEqual(result, '_bmad-output/planning')
157
-
158
- def test_none_value(self):
159
- self.assertIsNone(expand_template(None, {}))
160
-
161
- def test_non_string(self):
162
- self.assertEqual(expand_template(42, {}), 42)
163
-
164
-
165
- class TestApplyResultTemplate(unittest.TestCase):
166
-
167
- def test_with_result_template(self):
168
- var_def = {'result': '{project-root}/{value}'}
169
- result = apply_result_template(var_def, '_bmad-output', {'project-root': '/test'})
170
- self.assertEqual(result, '/test/_bmad-output')
171
-
172
- def test_without_result_template(self):
173
- result = apply_result_template({}, 'raw_value', {})
174
- self.assertEqual(result, 'raw_value')
175
-
176
- def test_value_only_template(self):
177
- var_def = {'result': '{value}'}
178
- result = apply_result_template(var_def, 'English', {})
179
- self.assertEqual(result, 'English')
180
-
181
- def test_absolute_value_skips_project_root_template(self):
182
- """When the user enters an absolute path, the '{project-root}/{value}' template
183
- must not be applied — doing so would produce '/project//absolute/path'."""
184
- var_def = {'result': '{project-root}/{value}'}
185
- result = apply_result_template(
186
- var_def, '/Users/me/shared-outputs', {'project-root': '/Users/me/myproject'}
187
- )
188
- self.assertEqual(result, '/Users/me/shared-outputs')
189
-
190
- def test_relative_traversal_value_is_normalized(self):
191
- """A relative path like '../../outside' combined with the project-root template
192
- must produce a clean normalized absolute path, not '/project/../../outside'."""
193
- var_def = {'result': '{project-root}/{value}'}
194
- result = apply_result_template(
195
- var_def, '../../outside-dir', {'project-root': '/Users/me/myproject'}
196
- )
197
- self.assertEqual(result, '/Users/outside-dir')
198
-
199
- def test_relative_one_level_up_is_normalized(self):
200
- var_def = {'result': '{project-root}/{value}'}
201
- result = apply_result_template(
202
- var_def, '../sibling-outputs', {'project-root': '/project/root'}
203
- )
204
- self.assertEqual(result, '/project/sibling-outputs')
205
-
206
- def test_normal_relative_value_unchanged(self):
207
- """Standard in-project relative paths still produce the expected joined path."""
208
- var_def = {'result': '{project-root}/{value}'}
209
- result = apply_result_template(
210
- var_def, '_bmad-output', {'project-root': '/project/root'}
211
- )
212
- self.assertEqual(result, '/project/root/_bmad-output')
213
-
214
-
215
- class TestLoadModuleYaml(unittest.TestCase):
216
-
217
- def setUp(self):
218
- self.temp_dir = tempfile.mkdtemp()
219
-
220
- def tearDown(self):
221
- shutil.rmtree(self.temp_dir)
222
-
223
- def test_loads_core_module_yaml(self):
224
- path = Path(self.temp_dir) / 'module.yaml'
225
- path.write_text(
226
- 'code: core\n'
227
- 'name: "BMad Core Module"\n'
228
- 'header: "Core Config"\n'
229
- 'user_name:\n'
230
- ' prompt: "What should agents call you?"\n'
231
- ' default: "BMad"\n'
232
- ' result: "{value}"\n'
233
- )
234
- result = load_module_yaml(path)
235
- self.assertIsNotNone(result)
236
- self.assertEqual(result['meta']['code'], 'core')
237
- self.assertEqual(result['meta']['name'], 'BMad Core Module')
238
- self.assertIn('user_name', result['variables'])
239
- self.assertEqual(result['variables']['user_name']['prompt'], 'What should agents call you?')
240
-
241
- def test_loads_module_with_directories(self):
242
- path = Path(self.temp_dir) / 'module.yaml'
243
- path.write_text(
244
- 'code: bmm\n'
245
- 'name: "BMad Method"\n'
246
- 'project_name:\n'
247
- ' prompt: "Project name?"\n'
248
- ' default: "{directory_name}"\n'
249
- ' result: "{value}"\n'
250
- 'directories:\n'
251
- ' - "{planning_artifacts}"\n'
252
- )
253
- result = load_module_yaml(path)
254
- self.assertEqual(result['directories'], ['{planning_artifacts}'])
255
-
256
- def test_returns_none_for_missing(self):
257
- result = load_module_yaml(Path(self.temp_dir) / 'nonexistent.yaml')
258
- self.assertIsNone(result)
259
-
260
- def test_returns_none_for_empty(self):
261
- path = Path(self.temp_dir) / 'empty.yaml'
262
- path.write_text('')
263
- result = load_module_yaml(path)
264
- self.assertIsNone(result)
265
-
266
-
267
- class TestFindCoreModuleYaml(unittest.TestCase):
268
-
269
- def test_returns_path_to_resources(self):
270
- path = find_core_module_yaml()
271
- self.assertTrue(str(path).endswith('resources/core-module.yaml'))
272
-
273
-
274
- class TestFindTargetModuleYaml(unittest.TestCase):
275
-
276
- def setUp(self):
277
- self.temp_dir = tempfile.mkdtemp()
278
- self.project_root = Path(self.temp_dir)
279
-
280
- def tearDown(self):
281
- shutil.rmtree(self.temp_dir)
282
-
283
- def test_finds_in_skill_assets(self):
284
- skill_path = self.project_root / 'skills' / 'test-skill'
285
- assets = skill_path / 'assets'
286
- assets.mkdir(parents=True)
287
- (assets / 'module.yaml').write_text('code: test\n')
288
-
289
- result = find_target_module_yaml('test', self.project_root, str(skill_path))
290
- self.assertIsNotNone(result)
291
- self.assertTrue(str(result).endswith('assets/module.yaml'))
292
-
293
- def test_finds_in_skill_root(self):
294
- skill_path = self.project_root / 'skills' / 'test-skill'
295
- skill_path.mkdir(parents=True)
296
- (skill_path / 'module.yaml').write_text('code: test\n')
297
-
298
- result = find_target_module_yaml('test', self.project_root, str(skill_path))
299
- self.assertIsNotNone(result)
300
-
301
- def test_finds_in_bmad_module_dir(self):
302
- module_dir = self.project_root / '_bmad' / 'mymod'
303
- module_dir.mkdir(parents=True)
304
- (module_dir / 'module.yaml').write_text('code: mymod\n')
305
-
306
- result = find_target_module_yaml('mymod', self.project_root)
307
- self.assertIsNotNone(result)
308
-
309
- def test_returns_none_when_not_found(self):
310
- result = find_target_module_yaml('missing', self.project_root)
311
- self.assertIsNone(result)
312
-
313
- def test_skill_path_takes_priority(self):
314
- """Skill assets module.yaml takes priority over _bmad/{module}/."""
315
- skill_path = self.project_root / 'skills' / 'test-skill'
316
- assets = skill_path / 'assets'
317
- assets.mkdir(parents=True)
318
- (assets / 'module.yaml').write_text('code: test\nname: from-skill\n')
319
-
320
- module_dir = self.project_root / '_bmad' / 'test'
321
- module_dir.mkdir(parents=True)
322
- (module_dir / 'module.yaml').write_text('code: test\nname: from-bmad\n')
323
-
324
- result = find_target_module_yaml('test', self.project_root, str(skill_path))
325
- self.assertTrue('assets' in str(result))
326
-
327
-
328
- class TestLoadConfigFile(unittest.TestCase):
329
-
330
- def setUp(self):
331
- self.temp_dir = tempfile.mkdtemp()
332
-
333
- def tearDown(self):
334
- shutil.rmtree(self.temp_dir)
335
-
336
- def test_loads_flat_yaml(self):
337
- path = Path(self.temp_dir) / 'config.yaml'
338
- path.write_text('user_name: Test\ncommunication_language: English\n')
339
- result = load_config_file(path)
340
- self.assertEqual(result['user_name'], 'Test')
341
-
342
- def test_returns_none_for_missing(self):
343
- result = load_config_file(Path(self.temp_dir) / 'missing.yaml')
344
- self.assertIsNone(result)
345
-
346
-
347
- class TestLoadModuleConfig(unittest.TestCase):
348
-
349
- def setUp(self):
350
- self.temp_dir = tempfile.mkdtemp()
351
- self.project_root = Path(self.temp_dir)
352
- bmad_core = self.project_root / '_bmad' / 'core'
353
- bmad_core.mkdir(parents=True)
354
- (bmad_core / 'config.yaml').write_text(
355
- 'user_name: TestUser\n'
356
- 'communication_language: English\n'
357
- 'document_output_language: English\n'
358
- 'output_folder: "{project-root}/_bmad-output"\n'
359
- )
360
- bmad_bmb = self.project_root / '_bmad' / 'bmb'
361
- bmad_bmb.mkdir(parents=True)
362
- (bmad_bmb / 'config.yaml').write_text(
363
- 'user_name: TestUser\n'
364
- 'communication_language: English\n'
365
- 'document_output_language: English\n'
366
- 'output_folder: "{project-root}/_bmad-output"\n'
367
- 'bmad_builder_output_folder: "{project-root}/_bmad-output/skills"\n'
368
- 'bmad_builder_reports: "{project-root}/_bmad-output/reports"\n'
369
- )
370
-
371
- def tearDown(self):
372
- shutil.rmtree(self.temp_dir)
373
-
374
- def test_load_core(self):
375
- result = load_module_config('core', self.project_root)
376
- self.assertIsNotNone(result)
377
- self.assertEqual(result['user_name'], 'TestUser')
378
-
379
- def test_load_module_includes_core_vars(self):
380
- result = load_module_config('bmb', self.project_root)
381
- self.assertIsNotNone(result)
382
- # Module-specific var
383
- self.assertIn('bmad_builder_output_folder', result)
384
- # Core vars also present
385
- self.assertEqual(result['user_name'], 'TestUser')
386
-
387
- def test_missing_module(self):
388
- result = load_module_config('nonexistent', self.project_root)
389
- self.assertIsNone(result)
390
-
391
-
392
- if __name__ == '__main__':
393
- unittest.main()