bmad-method 6.3.1-next.8 → 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.
- package/package.json +3 -2
- package/src/bmm-skills/1-analysis/bmad-agent-analyst/SKILL.md +51 -36
- package/src/bmm-skills/1-analysis/bmad-agent-analyst/customize.toml +90 -0
- package/src/bmm-skills/1-analysis/bmad-agent-tech-writer/SKILL.md +50 -33
- package/src/bmm-skills/1-analysis/bmad-agent-tech-writer/customize.toml +81 -0
- package/src/bmm-skills/1-analysis/bmad-document-project/SKILL.md +57 -1
- package/src/bmm-skills/1-analysis/bmad-document-project/customize.toml +41 -0
- package/src/bmm-skills/1-analysis/bmad-document-project/workflows/deep-dive-instructions.md +1 -0
- package/src/bmm-skills/1-analysis/bmad-document-project/workflows/full-scan-instructions.md +1 -0
- package/src/bmm-skills/1-analysis/bmad-prfaq/SKILL.md +48 -9
- package/src/bmm-skills/1-analysis/bmad-prfaq/customize.toml +41 -0
- package/src/bmm-skills/1-analysis/bmad-prfaq/references/verdict.md +4 -0
- package/src/bmm-skills/1-analysis/bmad-product-brief/SKILL.md +44 -9
- package/src/bmm-skills/1-analysis/bmad-product-brief/customize.toml +47 -0
- package/src/bmm-skills/1-analysis/bmad-product-brief/prompts/contextual-discovery.md +8 -7
- package/src/bmm-skills/1-analysis/bmad-product-brief/prompts/draft-and-review.md +6 -5
- package/src/bmm-skills/1-analysis/bmad-product-brief/prompts/finalize.md +4 -1
- package/src/bmm-skills/1-analysis/bmad-product-brief/prompts/guided-elicitation.md +3 -2
- package/src/bmm-skills/1-analysis/research/bmad-domain-research/SKILL.md +91 -1
- package/src/bmm-skills/1-analysis/research/bmad-domain-research/customize.toml +41 -0
- package/src/bmm-skills/1-analysis/research/bmad-domain-research/domain-steps/step-06-research-synthesis.md +6 -0
- package/src/bmm-skills/1-analysis/research/bmad-market-research/SKILL.md +91 -1
- package/src/bmm-skills/1-analysis/research/bmad-market-research/customize.toml +41 -0
- package/src/bmm-skills/1-analysis/research/bmad-market-research/steps/step-06-research-completion.md +6 -0
- package/src/bmm-skills/1-analysis/research/bmad-technical-research/SKILL.md +91 -1
- package/src/bmm-skills/1-analysis/research/bmad-technical-research/customize.toml +41 -0
- package/src/bmm-skills/1-analysis/research/bmad-technical-research/technical-steps/step-06-research-synthesis.md +6 -0
- package/src/bmm-skills/2-plan-workflows/bmad-agent-pm/SKILL.md +50 -35
- package/src/bmm-skills/2-plan-workflows/bmad-agent-pm/customize.toml +85 -0
- package/src/bmm-skills/2-plan-workflows/bmad-agent-ux-designer/SKILL.md +50 -31
- package/src/bmm-skills/2-plan-workflows/bmad-agent-ux-designer/customize.toml +60 -0
- package/src/bmm-skills/2-plan-workflows/bmad-create-prd/SKILL.md +99 -1
- package/src/bmm-skills/2-plan-workflows/bmad-create-prd/customize.toml +41 -0
- package/src/bmm-skills/2-plan-workflows/bmad-create-prd/steps-c/step-12-complete.md +6 -0
- package/src/bmm-skills/2-plan-workflows/bmad-create-ux-design/SKILL.md +70 -1
- package/src/bmm-skills/2-plan-workflows/bmad-create-ux-design/customize.toml +41 -0
- package/src/bmm-skills/2-plan-workflows/bmad-create-ux-design/steps/step-14-complete.md +6 -0
- package/src/bmm-skills/2-plan-workflows/bmad-edit-prd/SKILL.md +97 -1
- package/src/bmm-skills/2-plan-workflows/bmad-edit-prd/customize.toml +42 -0
- package/src/bmm-skills/2-plan-workflows/bmad-edit-prd/steps-e/step-e-04-complete.md +2 -0
- package/src/bmm-skills/2-plan-workflows/bmad-validate-prd/SKILL.md +99 -1
- package/src/bmm-skills/2-plan-workflows/bmad-validate-prd/customize.toml +42 -0
- package/src/bmm-skills/2-plan-workflows/bmad-validate-prd/steps-v/step-v-13-report-complete.md +1 -0
- package/src/bmm-skills/3-solutioning/bmad-agent-architect/SKILL.md +50 -30
- package/src/bmm-skills/3-solutioning/bmad-agent-architect/customize.toml +65 -0
- package/src/bmm-skills/3-solutioning/bmad-check-implementation-readiness/SKILL.md +86 -1
- package/src/bmm-skills/3-solutioning/bmad-check-implementation-readiness/customize.toml +41 -0
- package/src/bmm-skills/3-solutioning/bmad-check-implementation-readiness/steps/step-06-final-assessment.md +6 -0
- package/src/bmm-skills/3-solutioning/bmad-create-architecture/SKILL.md +69 -1
- package/src/bmm-skills/3-solutioning/bmad-create-architecture/customize.toml +41 -0
- package/src/bmm-skills/3-solutioning/bmad-create-architecture/steps/step-08-complete.md +6 -0
- package/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/SKILL.md +88 -1
- package/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/customize.toml +41 -0
- package/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/steps/step-04-final-validation.md +6 -0
- package/src/bmm-skills/3-solutioning/bmad-generate-project-context/SKILL.md +76 -1
- package/src/bmm-skills/3-solutioning/bmad-generate-project-context/customize.toml +41 -0
- package/src/bmm-skills/3-solutioning/bmad-generate-project-context/steps/step-03-complete.md +6 -0
- package/src/bmm-skills/4-implementation/bmad-agent-dev/SKILL.md +48 -43
- package/src/bmm-skills/4-implementation/bmad-agent-dev/customize.toml +90 -0
- package/src/bmm-skills/4-implementation/bmad-checkpoint-preview/SKILL.md +46 -7
- package/src/bmm-skills/4-implementation/bmad-checkpoint-preview/customize.toml +41 -0
- package/src/bmm-skills/4-implementation/bmad-checkpoint-preview/step-05-wrapup.md +6 -0
- package/src/bmm-skills/4-implementation/bmad-code-review/SKILL.md +85 -1
- package/src/bmm-skills/4-implementation/bmad-code-review/customize.toml +41 -0
- package/src/bmm-skills/4-implementation/bmad-code-review/steps/step-04-present.md +6 -0
- package/src/bmm-skills/4-implementation/bmad-correct-course/SKILL.md +296 -1
- package/src/bmm-skills/4-implementation/bmad-correct-course/customize.toml +41 -0
- package/src/bmm-skills/4-implementation/bmad-create-story/SKILL.md +424 -1
- package/src/bmm-skills/4-implementation/bmad-create-story/customize.toml +41 -0
- package/src/bmm-skills/4-implementation/bmad-dev-story/SKILL.md +480 -1
- package/src/bmm-skills/4-implementation/bmad-dev-story/customize.toml +41 -0
- package/src/bmm-skills/4-implementation/bmad-qa-generate-e2e-tests/SKILL.md +171 -1
- package/src/bmm-skills/4-implementation/bmad-qa-generate-e2e-tests/customize.toml +41 -0
- package/src/bmm-skills/4-implementation/bmad-quick-dev/SKILL.md +106 -1
- package/src/bmm-skills/4-implementation/bmad-quick-dev/customize.toml +41 -0
- package/src/bmm-skills/4-implementation/bmad-quick-dev/step-05-present.md +6 -0
- package/src/bmm-skills/4-implementation/bmad-quick-dev/step-oneshot.md +6 -0
- package/src/bmm-skills/4-implementation/bmad-retrospective/SKILL.md +1507 -1
- package/src/bmm-skills/4-implementation/bmad-retrospective/customize.toml +41 -0
- package/src/bmm-skills/4-implementation/bmad-sprint-planning/SKILL.md +294 -1
- package/src/bmm-skills/4-implementation/bmad-sprint-planning/customize.toml +41 -0
- package/src/bmm-skills/4-implementation/bmad-sprint-status/SKILL.md +292 -1
- package/src/bmm-skills/4-implementation/bmad-sprint-status/customize.toml +41 -0
- package/src/bmm-skills/module.yaml +49 -0
- package/src/core-skills/bmad-advanced-elicitation/SKILL.md +7 -1
- package/src/core-skills/bmad-customize/SKILL.md +111 -0
- package/src/core-skills/bmad-customize/scripts/list_customizable_skills.py +231 -0
- package/src/core-skills/bmad-customize/scripts/tests/test_list_customizable_skills.py +249 -0
- package/src/core-skills/bmad-distillator/resources/distillate-format-reference.md +1 -1
- package/src/core-skills/bmad-party-mode/SKILL.md +13 -10
- package/src/core-skills/module-help.csv +1 -0
- package/src/core-skills/module.yaml +2 -0
- package/src/scripts/resolve_config.py +176 -0
- package/src/scripts/resolve_customization.py +230 -0
- package/tools/installer/commands/install.js +13 -0
- package/tools/installer/core/config.js +4 -1
- package/tools/installer/core/install-paths.js +11 -5
- package/tools/installer/core/installer.js +181 -94
- package/tools/installer/core/manifest-generator.js +339 -184
- package/tools/installer/core/manifest.js +86 -86
- package/tools/installer/fs-native.js +5 -0
- package/tools/installer/ide/platform-codes.yaml +6 -0
- package/tools/installer/modules/channel-plan.js +203 -0
- package/tools/installer/modules/channel-resolver.js +241 -0
- package/tools/installer/modules/community-manager.js +130 -23
- package/tools/installer/modules/custom-module-manager.js +160 -19
- package/tools/installer/modules/external-manager.js +235 -32
- package/tools/installer/modules/official-modules.js +58 -12
- package/tools/installer/modules/registry-client.js +139 -7
- package/tools/installer/modules/registry-fallback.yaml +8 -0
- package/tools/installer/modules/version-resolver.js +336 -0
- package/tools/installer/project-root.js +54 -0
- package/tools/installer/ui.js +561 -50
- package/tools/platform-codes.yaml +6 -0
- package/src/bmm-skills/1-analysis/bmad-agent-analyst/bmad-skill-manifest.yaml +0 -11
- package/src/bmm-skills/1-analysis/bmad-agent-tech-writer/bmad-skill-manifest.yaml +0 -11
- package/src/bmm-skills/1-analysis/bmad-document-project/workflow.md +0 -25
- package/src/bmm-skills/1-analysis/research/bmad-domain-research/workflow.md +0 -51
- package/src/bmm-skills/1-analysis/research/bmad-market-research/workflow.md +0 -51
- package/src/bmm-skills/1-analysis/research/bmad-technical-research/workflow.md +0 -52
- package/src/bmm-skills/2-plan-workflows/bmad-agent-pm/bmad-skill-manifest.yaml +0 -11
- package/src/bmm-skills/2-plan-workflows/bmad-agent-ux-designer/bmad-skill-manifest.yaml +0 -11
- package/src/bmm-skills/2-plan-workflows/bmad-create-prd/workflow.md +0 -61
- package/src/bmm-skills/2-plan-workflows/bmad-create-ux-design/workflow.md +0 -35
- package/src/bmm-skills/2-plan-workflows/bmad-edit-prd/workflow.md +0 -62
- package/src/bmm-skills/2-plan-workflows/bmad-validate-prd/workflow.md +0 -61
- package/src/bmm-skills/3-solutioning/bmad-agent-architect/bmad-skill-manifest.yaml +0 -11
- package/src/bmm-skills/3-solutioning/bmad-check-implementation-readiness/workflow.md +0 -47
- package/src/bmm-skills/3-solutioning/bmad-create-architecture/workflow.md +0 -32
- package/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/workflow.md +0 -51
- package/src/bmm-skills/3-solutioning/bmad-generate-project-context/workflow.md +0 -39
- package/src/bmm-skills/4-implementation/bmad-agent-dev/bmad-skill-manifest.yaml +0 -11
- package/src/bmm-skills/4-implementation/bmad-code-review/workflow.md +0 -55
- package/src/bmm-skills/4-implementation/bmad-correct-course/workflow.md +0 -267
- package/src/bmm-skills/4-implementation/bmad-create-story/workflow.md +0 -380
- package/src/bmm-skills/4-implementation/bmad-dev-story/workflow.md +0 -450
- package/src/bmm-skills/4-implementation/bmad-qa-generate-e2e-tests/workflow.md +0 -136
- package/src/bmm-skills/4-implementation/bmad-quick-dev/workflow.md +0 -76
- package/src/bmm-skills/4-implementation/bmad-retrospective/workflow.md +0 -1479
- package/src/bmm-skills/4-implementation/bmad-sprint-planning/workflow.md +0 -263
- 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:
|
|
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. **
|
|
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
|
|
59
|
+
**The agent prompt** (built from the resolved roster entry):
|
|
54
60
|
```
|
|
55
|
-
You are {
|
|
61
|
+
You are {name} ({title}), a BMAD agent in a collaborative roundtable discussion.
|
|
56
62
|
|
|
57
63
|
## Your Persona
|
|
58
|
-
|
|
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 {
|
|
76
|
-
- Start your response with: {icon} **{
|
|
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
|
|
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()
|