bmad-method 6.2.3-next.3 → 6.2.3-next.30

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 (126) hide show
  1. package/.claude-plugin/marketplace.json +0 -4
  2. package/README.md +8 -9
  3. package/README_CN.md +1 -1
  4. package/README_VN.md +110 -0
  5. package/package.json +2 -1
  6. package/removals.txt +17 -0
  7. package/src/bmm-skills/1-analysis/bmad-agent-analyst/SKILL.md +7 -4
  8. package/src/bmm-skills/1-analysis/bmad-agent-tech-writer/SKILL.md +6 -4
  9. package/src/bmm-skills/1-analysis/bmad-document-project/workflow.md +8 -10
  10. package/src/bmm-skills/1-analysis/bmad-prfaq/SKILL.md +96 -0
  11. package/src/bmm-skills/1-analysis/bmad-prfaq/agents/artifact-analyzer.md +60 -0
  12. package/src/bmm-skills/1-analysis/bmad-prfaq/agents/web-researcher.md +49 -0
  13. package/src/bmm-skills/1-analysis/bmad-prfaq/assets/prfaq-template.md +62 -0
  14. package/src/bmm-skills/1-analysis/bmad-prfaq/bmad-manifest.json +16 -0
  15. package/src/bmm-skills/1-analysis/bmad-prfaq/references/customer-faq.md +55 -0
  16. package/src/bmm-skills/1-analysis/bmad-prfaq/references/internal-faq.md +51 -0
  17. package/src/bmm-skills/1-analysis/bmad-prfaq/references/press-release.md +60 -0
  18. package/src/bmm-skills/1-analysis/bmad-prfaq/references/verdict.md +79 -0
  19. package/src/bmm-skills/1-analysis/bmad-product-brief/SKILL.md +1 -6
  20. package/src/bmm-skills/1-analysis/bmad-product-brief/bmad-manifest.json +1 -1
  21. package/src/bmm-skills/1-analysis/research/bmad-domain-research/workflow.md +8 -6
  22. package/src/bmm-skills/1-analysis/research/bmad-market-research/workflow.md +8 -6
  23. package/src/bmm-skills/1-analysis/research/bmad-technical-research/workflow.md +8 -6
  24. package/src/bmm-skills/2-plan-workflows/bmad-agent-pm/SKILL.md +6 -4
  25. package/src/bmm-skills/2-plan-workflows/bmad-agent-ux-designer/SKILL.md +6 -4
  26. package/src/bmm-skills/2-plan-workflows/bmad-create-prd/workflow.md +8 -9
  27. package/src/bmm-skills/2-plan-workflows/bmad-create-ux-design/steps/step-13-responsive-accessibility.md +1 -1
  28. package/src/bmm-skills/2-plan-workflows/bmad-create-ux-design/workflow.md +8 -9
  29. package/src/bmm-skills/2-plan-workflows/bmad-edit-prd/steps-e/step-e-01-discovery.md +1 -1
  30. package/src/bmm-skills/2-plan-workflows/bmad-edit-prd/steps-e/step-e-01b-legacy-conversion.md +1 -1
  31. package/src/bmm-skills/2-plan-workflows/bmad-edit-prd/steps-e/step-e-02-review.md +1 -1
  32. package/src/bmm-skills/2-plan-workflows/bmad-edit-prd/steps-e/step-e-03-edit.md +1 -1
  33. package/src/bmm-skills/2-plan-workflows/bmad-edit-prd/steps-e/step-e-04-complete.md +1 -3
  34. package/src/bmm-skills/2-plan-workflows/bmad-edit-prd/workflow.md +8 -9
  35. package/src/bmm-skills/2-plan-workflows/bmad-validate-prd/workflow.md +8 -9
  36. package/src/bmm-skills/3-solutioning/bmad-agent-architect/SKILL.md +6 -4
  37. package/src/bmm-skills/3-solutioning/bmad-check-implementation-readiness/steps/step-01-document-discovery.md +1 -1
  38. package/src/bmm-skills/3-solutioning/bmad-check-implementation-readiness/steps/step-02-prd-analysis.md +1 -1
  39. package/src/bmm-skills/3-solutioning/bmad-check-implementation-readiness/steps/step-03-epic-coverage-validation.md +1 -1
  40. package/src/bmm-skills/3-solutioning/bmad-check-implementation-readiness/workflow.md +9 -11
  41. package/src/bmm-skills/3-solutioning/bmad-create-architecture/workflow.md +8 -14
  42. package/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/workflow.md +10 -12
  43. package/src/bmm-skills/3-solutioning/bmad-generate-project-context/workflow.md +8 -12
  44. package/src/bmm-skills/4-implementation/bmad-agent-dev/SKILL.md +11 -4
  45. package/src/bmm-skills/4-implementation/bmad-checkpoint-preview/SKILL.md +29 -0
  46. package/src/bmm-skills/4-implementation/bmad-checkpoint-preview/generate-trail.md +38 -0
  47. package/src/bmm-skills/4-implementation/bmad-checkpoint-preview/step-01-orientation.md +105 -0
  48. package/src/bmm-skills/4-implementation/bmad-checkpoint-preview/step-02-walkthrough.md +89 -0
  49. package/src/bmm-skills/4-implementation/bmad-checkpoint-preview/step-03-detail-pass.md +106 -0
  50. package/src/bmm-skills/4-implementation/bmad-checkpoint-preview/step-04-testing.md +74 -0
  51. package/src/bmm-skills/4-implementation/bmad-checkpoint-preview/step-05-wrapup.md +24 -0
  52. package/src/bmm-skills/4-implementation/bmad-code-review/steps/step-01-gather-context.md +38 -15
  53. package/src/bmm-skills/4-implementation/bmad-correct-course/checklist.md +2 -2
  54. package/src/bmm-skills/4-implementation/bmad-correct-course/workflow.md +8 -8
  55. package/src/bmm-skills/4-implementation/bmad-qa-generate-e2e-tests/checklist.md +1 -1
  56. package/src/bmm-skills/4-implementation/bmad-quick-dev/spec-template.md +1 -1
  57. package/src/bmm-skills/4-implementation/bmad-quick-dev/step-01-clarify-and-route.md +20 -6
  58. package/src/bmm-skills/4-implementation/bmad-quick-dev/step-02-plan.md +20 -8
  59. package/src/bmm-skills/4-implementation/bmad-quick-dev/step-03-implement.md +2 -0
  60. package/src/bmm-skills/4-implementation/bmad-quick-dev/step-oneshot.md +16 -4
  61. package/src/bmm-skills/4-implementation/bmad-quick-dev/workflow.md +1 -5
  62. package/src/bmm-skills/4-implementation/bmad-retrospective/workflow.md +134 -134
  63. package/src/bmm-skills/4-implementation/bmad-sprint-planning/sprint-status-template.yaml +1 -1
  64. package/src/bmm-skills/4-implementation/bmad-sprint-planning/workflow.md +3 -3
  65. package/src/bmm-skills/4-implementation/bmad-sprint-status/workflow.md +2 -2
  66. package/src/bmm-skills/module-help.csv +4 -1
  67. package/src/core-skills/bmad-advanced-elicitation/SKILL.md +1 -2
  68. package/src/core-skills/bmad-distillator/SKILL.md +0 -1
  69. package/src/core-skills/bmad-distillator/resources/distillate-format-reference.md +9 -9
  70. package/src/core-skills/bmad-help/SKILL.md +4 -2
  71. package/src/core-skills/bmad-party-mode/SKILL.md +121 -2
  72. package/src/core-skills/module-help.csv +1 -0
  73. package/tools/installer/cli-utils.js +18 -9
  74. package/tools/installer/commands/install.js +0 -1
  75. package/tools/installer/core/existing-install.js +2 -8
  76. package/tools/installer/core/install-paths.js +0 -3
  77. package/tools/installer/core/installer.js +176 -464
  78. package/tools/installer/core/manifest-generator.js +4 -12
  79. package/tools/installer/core/manifest.js +82 -97
  80. package/tools/installer/ide/_config-driven.js +149 -38
  81. package/tools/installer/ide/platform-codes.yaml +6 -4
  82. package/tools/installer/ide/shared/skill-manifest.js +1 -16
  83. package/tools/installer/install-messages.yaml +19 -26
  84. package/tools/installer/modules/community-manager.js +377 -0
  85. package/tools/installer/modules/custom-module-manager.js +308 -0
  86. package/tools/installer/modules/external-manager.js +65 -49
  87. package/tools/installer/modules/official-modules.js +37 -65
  88. package/tools/installer/modules/registry-client.js +66 -0
  89. package/tools/installer/{external-official-modules.yaml → modules/registry-fallback.yaml} +3 -12
  90. package/tools/installer/ui.js +340 -672
  91. package/tools/platform-codes.yaml +6 -0
  92. package/src/bmm-skills/2-plan-workflows/create-prd/data/domain-complexity.csv +0 -15
  93. package/src/bmm-skills/2-plan-workflows/create-prd/data/project-types.csv +0 -11
  94. package/src/bmm-skills/2-plan-workflows/create-prd/steps-v/step-v-01-discovery.md +0 -224
  95. package/src/bmm-skills/2-plan-workflows/create-prd/steps-v/step-v-02-format-detection.md +0 -191
  96. package/src/bmm-skills/2-plan-workflows/create-prd/steps-v/step-v-02b-parity-check.md +0 -209
  97. package/src/bmm-skills/2-plan-workflows/create-prd/steps-v/step-v-03-density-validation.md +0 -174
  98. package/src/bmm-skills/2-plan-workflows/create-prd/steps-v/step-v-04-brief-coverage-validation.md +0 -214
  99. package/src/bmm-skills/2-plan-workflows/create-prd/steps-v/step-v-05-measurability-validation.md +0 -228
  100. package/src/bmm-skills/2-plan-workflows/create-prd/steps-v/step-v-06-traceability-validation.md +0 -217
  101. package/src/bmm-skills/2-plan-workflows/create-prd/steps-v/step-v-07-implementation-leakage-validation.md +0 -205
  102. package/src/bmm-skills/2-plan-workflows/create-prd/steps-v/step-v-08-domain-compliance-validation.md +0 -243
  103. package/src/bmm-skills/2-plan-workflows/create-prd/steps-v/step-v-09-project-type-validation.md +0 -263
  104. package/src/bmm-skills/2-plan-workflows/create-prd/steps-v/step-v-10-smart-validation.md +0 -209
  105. package/src/bmm-skills/2-plan-workflows/create-prd/steps-v/step-v-11-holistic-quality-validation.md +0 -264
  106. package/src/bmm-skills/2-plan-workflows/create-prd/steps-v/step-v-12-completeness-validation.md +0 -242
  107. package/src/bmm-skills/2-plan-workflows/create-prd/steps-v/step-v-13-report-complete.md +0 -232
  108. package/src/bmm-skills/2-plan-workflows/create-prd/workflow-validate-prd.md +0 -65
  109. package/src/bmm-skills/4-implementation/bmad-agent-qa/SKILL.md +0 -59
  110. package/src/bmm-skills/4-implementation/bmad-agent-qa/bmad-skill-manifest.yaml +0 -11
  111. package/src/bmm-skills/4-implementation/bmad-agent-quick-flow-solo-dev/SKILL.md +0 -51
  112. package/src/bmm-skills/4-implementation/bmad-agent-quick-flow-solo-dev/bmad-skill-manifest.yaml +0 -11
  113. package/src/bmm-skills/4-implementation/bmad-agent-sm/SKILL.md +0 -53
  114. package/src/bmm-skills/4-implementation/bmad-agent-sm/bmad-skill-manifest.yaml +0 -11
  115. package/src/core-skills/bmad-init/SKILL.md +0 -100
  116. package/src/core-skills/bmad-init/resources/core-module.yaml +0 -25
  117. package/src/core-skills/bmad-init/scripts/bmad_init.py +0 -624
  118. package/src/core-skills/bmad-init/scripts/tests/test_bmad_init.py +0 -393
  119. package/src/core-skills/bmad-party-mode/steps/step-01-agent-loading.md +0 -138
  120. package/src/core-skills/bmad-party-mode/steps/step-02-discussion-orchestration.md +0 -187
  121. package/src/core-skills/bmad-party-mode/steps/step-03-graceful-exit.md +0 -167
  122. package/src/core-skills/bmad-party-mode/workflow.md +0 -190
  123. package/tools/installer/core/custom-module-cache.js +0 -260
  124. package/tools/installer/custom-handler.js +0 -112
  125. package/tools/installer/modules/custom-modules.js +0 -197
  126. /package/src/bmm-skills/2-plan-workflows/{create-prd → bmad-edit-prd}/data/prd-purpose.md +0 -0
@@ -1,624 +0,0 @@
1
- # /// script
2
- # requires-python = ">=3.10"
3
- # dependencies = ["pyyaml"]
4
- # ///
5
-
6
- #!/usr/bin/env python3
7
- """
8
- BMad Init — Project configuration bootstrap and config loader.
9
-
10
- Config files (flat YAML per module):
11
- - _bmad/core/config.yaml (core settings — user_name, language, output_folder, etc.)
12
- - _bmad/{module}/config.yaml (module settings + core values merged in)
13
-
14
- Usage:
15
- # Fast path — load all vars for a module (includes core vars)
16
- python bmad_init.py load --module bmb --all --project-root /path
17
-
18
- # Load specific vars with optional defaults
19
- python bmad_init.py load --module bmb --vars var1:default1,var2 --project-root /path
20
-
21
- # Load core only
22
- python bmad_init.py load --all --project-root /path
23
-
24
- # Check if init is needed
25
- python bmad_init.py check --project-root /path
26
- python bmad_init.py check --module bmb --skill-path /path/to/skill --project-root /path
27
-
28
- # Resolve module defaults given core answers
29
- python bmad_init.py resolve-defaults --module bmb --core-answers '{"output_folder":"..."}' --project-root /path
30
-
31
- # Write config from answered questions
32
- python bmad_init.py write --answers '{"core": {...}, "bmb": {...}}' --project-root /path
33
- """
34
-
35
- import argparse
36
- import json
37
- import os
38
- import sys
39
- from pathlib import Path
40
-
41
- import yaml
42
-
43
-
44
- # =============================================================================
45
- # Project Root Detection
46
- # =============================================================================
47
-
48
- def find_project_root(llm_provided=None):
49
- """
50
- Find project root by looking for _bmad folder.
51
-
52
- Args:
53
- llm_provided: Path explicitly provided via --project-root.
54
-
55
- Returns:
56
- Path to project root, or None if not found.
57
- """
58
- if llm_provided:
59
- candidate = Path(llm_provided)
60
- if (candidate / '_bmad').exists():
61
- return candidate
62
- # First run — _bmad won't exist yet but LLM path is still valid
63
- if candidate.is_dir():
64
- return candidate
65
-
66
- for start_dir in [Path.cwd(), Path(__file__).resolve().parent]:
67
- current_dir = start_dir
68
- while current_dir != current_dir.parent:
69
- if (current_dir / '_bmad').exists():
70
- return current_dir
71
- current_dir = current_dir.parent
72
-
73
- return None
74
-
75
-
76
- # =============================================================================
77
- # Module YAML Loading
78
- # =============================================================================
79
-
80
- def load_module_yaml(path):
81
- """
82
- Load and parse a module.yaml file, separating metadata from variable definitions.
83
-
84
- Returns:
85
- Dict with 'meta' (code, name, etc.) and 'variables' (var definitions)
86
- and 'directories' (list of dir templates), or None on failure.
87
- """
88
- try:
89
- with open(path, 'r', encoding='utf-8') as f:
90
- raw = yaml.safe_load(f)
91
- except Exception:
92
- return None
93
-
94
- if not raw or not isinstance(raw, dict):
95
- return None
96
-
97
- meta_keys = {'code', 'name', 'description', 'default_selected', 'header', 'subheader'}
98
- meta = {}
99
- variables = {}
100
- directories = []
101
-
102
- for key, value in raw.items():
103
- if key == 'directories':
104
- directories = value if isinstance(value, list) else []
105
- elif key in meta_keys:
106
- meta[key] = value
107
- elif isinstance(value, dict) and 'prompt' in value:
108
- variables[key] = value
109
- # Skip comment-only entries (## var_name lines become None values)
110
-
111
- return {'meta': meta, 'variables': variables, 'directories': directories}
112
-
113
-
114
- def find_core_module_yaml():
115
- """Find the core module.yaml bundled with this skill."""
116
- return Path(__file__).resolve().parent.parent / 'resources' / 'core-module.yaml'
117
-
118
-
119
- def find_target_module_yaml(module_code, project_root, skill_path=None):
120
- """
121
- Find module.yaml for a given module code.
122
-
123
- Search order:
124
- 1. skill_path/assets/module.yaml (calling skill's assets)
125
- 2. skill_path/module.yaml (calling skill's root)
126
- 3. _bmad/{module_code}/module.yaml (installed module location)
127
- """
128
- search_paths = []
129
-
130
- if skill_path:
131
- sp = Path(skill_path)
132
- search_paths.append(sp / 'assets' / 'module.yaml')
133
- search_paths.append(sp / 'module.yaml')
134
-
135
- if project_root and module_code:
136
- search_paths.append(Path(project_root) / '_bmad' / module_code / 'module.yaml')
137
-
138
- for path in search_paths:
139
- if path.exists():
140
- return path
141
-
142
- return None
143
-
144
-
145
- # =============================================================================
146
- # Config Loading (Flat per-module files)
147
- # =============================================================================
148
-
149
- def load_config_file(path):
150
- """Load a flat YAML config file. Returns dict or None."""
151
- try:
152
- with open(path, 'r', encoding='utf-8') as f:
153
- data = yaml.safe_load(f)
154
- return data if isinstance(data, dict) else None
155
- except Exception:
156
- return None
157
-
158
-
159
- def load_module_config(module_code, project_root):
160
- """Load config for a specific module from _bmad/{module}/config.yaml."""
161
- config_path = Path(project_root) / '_bmad' / module_code / 'config.yaml'
162
- return load_config_file(config_path)
163
-
164
-
165
- def resolve_project_root_placeholder(value, project_root):
166
- """Replace {project-root} placeholder with actual path."""
167
- if not value or not isinstance(value, str):
168
- return value
169
- if '{project-root}' not in value:
170
- return value
171
-
172
- # Strip the {project-root} token to inspect what remains, so we can
173
- # correctly handle absolute paths stored as "{project-root}//absolute/path"
174
- # (produced by the "{project-root}/{value}" template applied to an absolute value).
175
- suffix = value.replace('{project-root}', '', 1)
176
-
177
- # Strip the one path separator that follows the token (if any)
178
- if suffix.startswith('/') or suffix.startswith('\\'):
179
- remainder = suffix[1:]
180
- else:
181
- remainder = suffix
182
-
183
- if os.path.isabs(remainder):
184
- # The original value was an absolute path stored with a {project-root}/ prefix.
185
- # Return the absolute path directly — no joining needed.
186
- return remainder
187
-
188
- # Relative path: join with project root and normalize to resolve any .. segments.
189
- return os.path.normpath(os.path.join(str(project_root), remainder))
190
-
191
-
192
- def parse_var_specs(vars_string):
193
- """
194
- Parse variable specs: var_name:default_value,var_name2:default_value2
195
- No default = returns null if missing.
196
- """
197
- if not vars_string:
198
- return []
199
- specs = []
200
- for spec in vars_string.split(','):
201
- spec = spec.strip()
202
- if not spec:
203
- continue
204
- if ':' in spec:
205
- parts = spec.split(':', 1)
206
- specs.append({'name': parts[0].strip(), 'default': parts[1].strip()})
207
- else:
208
- specs.append({'name': spec, 'default': None})
209
- return specs
210
-
211
-
212
- # =============================================================================
213
- # Template Expansion
214
- # =============================================================================
215
-
216
- def expand_template(value, context):
217
- """
218
- Expand {placeholder} references in a string using context dict.
219
-
220
- Supports: {project-root}, {value}, {output_folder}, {directory_name}, etc.
221
- """
222
- if not value or not isinstance(value, str):
223
- return value
224
- result = value
225
- for key, val in context.items():
226
- placeholder = '{' + key + '}'
227
- if placeholder in result and val is not None:
228
- result = result.replace(placeholder, str(val))
229
- return result
230
-
231
-
232
- def apply_result_template(var_def, raw_value, context):
233
- """
234
- Apply a variable's result template to transform the raw user answer.
235
-
236
- E.g., result: "{project-root}/{value}" with value="_bmad-output"
237
- becomes "/Users/foo/project/_bmad-output"
238
- """
239
- result_template = var_def.get('result')
240
- if not result_template:
241
- return raw_value
242
-
243
- # If the user supplied an absolute path and the template would prefix it with
244
- # "{project-root}/", skip the template entirely to avoid producing a broken path
245
- # like "/my/project//absolute/path".
246
- if isinstance(raw_value, str) and os.path.isabs(raw_value):
247
- return raw_value
248
-
249
- ctx = dict(context)
250
- ctx['value'] = raw_value
251
- result = expand_template(result_template, ctx)
252
-
253
- # Normalize the resulting path to resolve any ".." segments (e.g. when the user
254
- # entered a relative path such as "../../outside-dir").
255
- if isinstance(result, str) and '{' not in result and os.path.isabs(result):
256
- result = os.path.normpath(result)
257
-
258
- return result
259
-
260
-
261
- # =============================================================================
262
- # Load Command (Fast Path)
263
- # =============================================================================
264
-
265
- def cmd_load(args):
266
- """Load config vars — the fast path."""
267
- project_root = find_project_root(llm_provided=args.project_root)
268
- if not project_root:
269
- print(json.dumps({'error': 'Project root not found (_bmad folder not detected)'}),
270
- file=sys.stderr)
271
- sys.exit(1)
272
-
273
- module_code = args.module or 'core'
274
-
275
- # Load the module's config (which includes core vars)
276
- config = load_module_config(module_code, project_root)
277
- if config is None:
278
- print(json.dumps({
279
- 'init_required': True,
280
- 'missing_module': module_code,
281
- }), file=sys.stderr)
282
- sys.exit(1)
283
-
284
- # Resolve {project-root} in all values
285
- for key in config:
286
- config[key] = resolve_project_root_placeholder(config[key], project_root)
287
-
288
- if args.all:
289
- print(json.dumps(config, indent=2))
290
- else:
291
- var_specs = parse_var_specs(args.vars)
292
- if not var_specs:
293
- print(json.dumps({'error': 'Either --vars or --all must be specified'}),
294
- file=sys.stderr)
295
- sys.exit(1)
296
- result = {}
297
- for spec in var_specs:
298
- val = config.get(spec['name'])
299
- if val is not None and val != '':
300
- result[spec['name']] = val
301
- elif spec['default'] is not None:
302
- result[spec['name']] = spec['default']
303
- else:
304
- result[spec['name']] = None
305
- print(json.dumps(result, indent=2))
306
-
307
-
308
- # =============================================================================
309
- # Check Command
310
- # =============================================================================
311
-
312
- def cmd_check(args):
313
- """Check if config exists and return status with module.yaml questions if needed."""
314
- project_root = find_project_root(llm_provided=args.project_root)
315
- if not project_root:
316
- print(json.dumps({
317
- 'status': 'no_project',
318
- 'message': 'No project root found. Provide --project-root to bootstrap.',
319
- }, indent=2))
320
- return
321
-
322
- project_root = Path(project_root)
323
- module_code = args.module
324
-
325
- # Check core config
326
- core_config = load_module_config('core', project_root)
327
- core_exists = core_config is not None
328
-
329
- # If no module requested, just check core
330
- if not module_code or module_code == 'core':
331
- if core_exists:
332
- print(json.dumps({'status': 'ready', 'project_root': str(project_root)}, indent=2))
333
- else:
334
- core_yaml_path = find_core_module_yaml()
335
- core_module = load_module_yaml(core_yaml_path) if core_yaml_path.exists() else None
336
- print(json.dumps({
337
- 'status': 'core_missing',
338
- 'project_root': str(project_root),
339
- 'core_module': core_module,
340
- }, indent=2))
341
- return
342
-
343
- # Module requested — check if its config exists
344
- module_config = load_module_config(module_code, project_root)
345
- if module_config is not None:
346
- print(json.dumps({'status': 'ready', 'project_root': str(project_root)}, indent=2))
347
- return
348
-
349
- # Module config missing — find its module.yaml for questions
350
- target_yaml_path = find_target_module_yaml(
351
- module_code, project_root, skill_path=args.skill_path
352
- )
353
- target_module = load_module_yaml(target_yaml_path) if target_yaml_path else None
354
-
355
- result = {
356
- 'project_root': str(project_root),
357
- }
358
-
359
- if not core_exists:
360
- result['status'] = 'core_missing'
361
- core_yaml_path = find_core_module_yaml()
362
- result['core_module'] = load_module_yaml(core_yaml_path) if core_yaml_path.exists() else None
363
- else:
364
- result['status'] = 'module_missing'
365
- result['core_vars'] = core_config
366
-
367
- result['target_module'] = target_module
368
- if target_yaml_path:
369
- result['target_module_yaml_path'] = str(target_yaml_path)
370
-
371
- print(json.dumps(result, indent=2))
372
-
373
-
374
- # =============================================================================
375
- # Resolve Defaults Command
376
- # =============================================================================
377
-
378
- def cmd_resolve_defaults(args):
379
- """Given core answers, resolve a module's variable defaults."""
380
- project_root = find_project_root(llm_provided=args.project_root)
381
- if not project_root:
382
- print(json.dumps({'error': 'Project root not found'}), file=sys.stderr)
383
- sys.exit(1)
384
-
385
- try:
386
- core_answers = json.loads(args.core_answers)
387
- except json.JSONDecodeError as e:
388
- print(json.dumps({'error': f'Invalid JSON in --core-answers: {e}'}),
389
- file=sys.stderr)
390
- sys.exit(1)
391
-
392
- # Build context for template expansion
393
- context = {
394
- 'project-root': str(project_root),
395
- 'directory_name': Path(project_root).name,
396
- }
397
- context.update(core_answers)
398
-
399
- # Find and load the module's module.yaml
400
- module_code = args.module
401
- target_yaml_path = find_target_module_yaml(
402
- module_code, project_root, skill_path=args.skill_path
403
- )
404
- if not target_yaml_path:
405
- print(json.dumps({'error': f'No module.yaml found for module: {module_code}'}),
406
- file=sys.stderr)
407
- sys.exit(1)
408
-
409
- module_def = load_module_yaml(target_yaml_path)
410
- if not module_def:
411
- print(json.dumps({'error': f'Failed to parse module.yaml at: {target_yaml_path}'}),
412
- file=sys.stderr)
413
- sys.exit(1)
414
-
415
- # Resolve defaults in each variable
416
- resolved_vars = {}
417
- for var_name, var_def in module_def['variables'].items():
418
- default = var_def.get('default', '')
419
- resolved_default = expand_template(str(default), context)
420
- resolved_vars[var_name] = dict(var_def)
421
- resolved_vars[var_name]['default'] = resolved_default
422
-
423
- result = {
424
- 'module_code': module_code,
425
- 'meta': module_def['meta'],
426
- 'variables': resolved_vars,
427
- 'directories': module_def['directories'],
428
- }
429
- print(json.dumps(result, indent=2))
430
-
431
-
432
- # =============================================================================
433
- # Write Command
434
- # =============================================================================
435
-
436
- def cmd_write(args):
437
- """Write config files from answered questions."""
438
- project_root = find_project_root(llm_provided=args.project_root)
439
- if not project_root:
440
- if args.project_root:
441
- project_root = Path(args.project_root)
442
- else:
443
- print(json.dumps({'error': 'Project root not found and --project-root not provided'}),
444
- file=sys.stderr)
445
- sys.exit(1)
446
-
447
- project_root = Path(project_root)
448
-
449
- try:
450
- answers = json.loads(args.answers)
451
- except json.JSONDecodeError as e:
452
- print(json.dumps({'error': f'Invalid JSON in --answers: {e}'}),
453
- file=sys.stderr)
454
- sys.exit(1)
455
-
456
- context = {
457
- 'project-root': str(project_root),
458
- 'directory_name': project_root.name,
459
- }
460
-
461
- # Load module.yaml definitions to get result templates
462
- core_yaml_path = find_core_module_yaml()
463
- core_def = load_module_yaml(core_yaml_path) if core_yaml_path.exists() else None
464
-
465
- files_written = []
466
- dirs_created = []
467
-
468
- # Process core answers first (needed for module config expansion)
469
- core_answers_raw = answers.get('core', {})
470
- core_config = {}
471
-
472
- if core_answers_raw and core_def:
473
- for var_name, raw_value in core_answers_raw.items():
474
- var_def = core_def['variables'].get(var_name, {})
475
- expanded = apply_result_template(var_def, raw_value, context)
476
- core_config[var_name] = expanded
477
-
478
- # Write core config
479
- core_dir = project_root / '_bmad' / 'core'
480
- core_dir.mkdir(parents=True, exist_ok=True)
481
- core_config_path = core_dir / 'config.yaml'
482
-
483
- # Merge with existing if present
484
- existing = load_config_file(core_config_path) or {}
485
- existing.update(core_config)
486
-
487
- _write_config_file(core_config_path, existing, 'CORE')
488
- files_written.append(str(core_config_path))
489
- elif core_answers_raw:
490
- # No core_def available — write raw values
491
- core_config = dict(core_answers_raw)
492
- core_dir = project_root / '_bmad' / 'core'
493
- core_dir.mkdir(parents=True, exist_ok=True)
494
- core_config_path = core_dir / 'config.yaml'
495
- existing = load_config_file(core_config_path) or {}
496
- existing.update(core_config)
497
- _write_config_file(core_config_path, existing, 'CORE')
498
- files_written.append(str(core_config_path))
499
-
500
- # Update context with resolved core values for module expansion
501
- context.update(core_config)
502
-
503
- # Process module answers
504
- for module_code, module_answers_raw in answers.items():
505
- if module_code == 'core':
506
- continue
507
-
508
- # Find module.yaml for result templates
509
- target_yaml_path = find_target_module_yaml(
510
- module_code, project_root, skill_path=args.skill_path
511
- )
512
- module_def = load_module_yaml(target_yaml_path) if target_yaml_path else None
513
-
514
- # Build module config: start with core values, then add module values
515
- # Re-read core config to get the latest (may have been updated above)
516
- latest_core = load_module_config('core', project_root) or core_config
517
- module_config = dict(latest_core)
518
-
519
- for var_name, raw_value in module_answers_raw.items():
520
- if module_def:
521
- var_def = module_def['variables'].get(var_name, {})
522
- expanded = apply_result_template(var_def, raw_value, context)
523
- else:
524
- expanded = raw_value
525
- module_config[var_name] = expanded
526
- context[var_name] = expanded # Available for subsequent template expansion
527
-
528
- # Write module config
529
- module_dir = project_root / '_bmad' / module_code
530
- module_dir.mkdir(parents=True, exist_ok=True)
531
- module_config_path = module_dir / 'config.yaml'
532
-
533
- existing = load_config_file(module_config_path) or {}
534
- existing.update(module_config)
535
-
536
- module_name = module_def['meta'].get('name', module_code.upper()) if module_def else module_code.upper()
537
- _write_config_file(module_config_path, existing, module_name)
538
- files_written.append(str(module_config_path))
539
-
540
- # Create directories declared in module.yaml
541
- if module_def and module_def.get('directories'):
542
- for dir_template in module_def['directories']:
543
- dir_path = expand_template(dir_template, context)
544
- if dir_path:
545
- Path(dir_path).mkdir(parents=True, exist_ok=True)
546
- dirs_created.append(dir_path)
547
-
548
- result = {
549
- 'status': 'written',
550
- 'files_written': files_written,
551
- 'dirs_created': dirs_created,
552
- }
553
- print(json.dumps(result, indent=2))
554
-
555
-
556
- def _write_config_file(path, data, module_label):
557
- """Write a config YAML file with a header comment."""
558
- from datetime import datetime, timezone
559
- with open(path, 'w', encoding='utf-8') as f:
560
- f.write(f'# {module_label} Module Configuration\n')
561
- f.write(f'# Generated by bmad-init\n')
562
- f.write(f'# Date: {datetime.now(timezone.utc).isoformat()}\n\n')
563
- yaml.safe_dump(data, f, default_flow_style=False, allow_unicode=True, sort_keys=False)
564
-
565
-
566
- # =============================================================================
567
- # CLI Entry Point
568
- # =============================================================================
569
-
570
- def main():
571
- parser = argparse.ArgumentParser(
572
- description='BMad Init — Project configuration bootstrap and config loader.'
573
- )
574
- subparsers = parser.add_subparsers(dest='command')
575
-
576
- # --- load ---
577
- load_parser = subparsers.add_parser('load', help='Load config vars (fast path)')
578
- load_parser.add_argument('--module', help='Module code (omit for core only)')
579
- load_parser.add_argument('--vars', help='Comma-separated vars with optional defaults')
580
- load_parser.add_argument('--all', action='store_true', help='Return all config vars')
581
- load_parser.add_argument('--project-root', help='Project root path')
582
-
583
- # --- check ---
584
- check_parser = subparsers.add_parser('check', help='Check if init is needed')
585
- check_parser.add_argument('--module', help='Module code to check (optional)')
586
- check_parser.add_argument('--skill-path', help='Path to the calling skill folder')
587
- check_parser.add_argument('--project-root', help='Project root path')
588
-
589
- # --- resolve-defaults ---
590
- resolve_parser = subparsers.add_parser('resolve-defaults',
591
- help='Resolve module defaults given core answers')
592
- resolve_parser.add_argument('--module', required=True, help='Module code')
593
- resolve_parser.add_argument('--core-answers', required=True, help='JSON string of core answers')
594
- resolve_parser.add_argument('--skill-path', help='Path to calling skill folder')
595
- resolve_parser.add_argument('--project-root', help='Project root path')
596
-
597
- # --- write ---
598
- write_parser = subparsers.add_parser('write', help='Write config files')
599
- write_parser.add_argument('--answers', required=True, help='JSON string of all answers')
600
- write_parser.add_argument('--skill-path', help='Path to calling skill (for module.yaml lookup)')
601
- write_parser.add_argument('--project-root', help='Project root path')
602
-
603
- args = parser.parse_args()
604
- if args.command is None:
605
- parser.print_help()
606
- sys.exit(1)
607
-
608
- commands = {
609
- 'load': cmd_load,
610
- 'check': cmd_check,
611
- 'resolve-defaults': cmd_resolve_defaults,
612
- 'write': cmd_write,
613
- }
614
-
615
- handler = commands.get(args.command)
616
- if handler:
617
- handler(args)
618
- else:
619
- parser.print_help()
620
- sys.exit(1)
621
-
622
-
623
- if __name__ == '__main__':
624
- main()