bmad-method 6.2.3-next.22 → 6.2.3-next.24

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/README.md CHANGED
@@ -3,6 +3,8 @@
3
3
  [![Version](https://img.shields.io/npm/v/bmad-method?color=blue&label=version)](https://www.npmjs.com/package/bmad-method)
4
4
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
5
5
  [![Node.js Version](https://img.shields.io/badge/node-%3E%3D20.0.0-brightgreen)](https://nodejs.org)
6
+ [![Python Version](https://img.shields.io/badge/python-%3E%3D3.10-blue?logo=python&logoColor=white)](https://www.python.org)
7
+ [![uv](https://img.shields.io/badge/uv-package%20manager-blueviolet?logo=uv)](https://docs.astral.sh/uv/)
6
8
  [![Discord](https://img.shields.io/badge/Discord-Join%20Community-7289da?logo=discord&logoColor=white)](https://discord.gg/gk8jAdXWmj)
7
9
 
8
10
  **Build More Architect Dreams** — An AI-driven agile development module for the BMad Method Module Ecosystem, the best and most comprehensive Agile AI Driven Development framework that has true scale-adaptive intelligence that adjusts from bug fixes to enterprise systems.
@@ -34,7 +36,7 @@ Traditional AI tools do the thinking for you, producing average results. BMad ag
34
36
 
35
37
  ## Quick Start
36
38
 
37
- **Prerequisites**: [Node.js](https://nodejs.org) v20+
39
+ **Prerequisites**: [Node.js](https://nodejs.org) v20+ · [Python](https://www.python.org) 3.10+ · [uv](https://docs.astral.sh/uv/)
38
40
 
39
41
  ```bash
40
42
  npx bmad-method install
@@ -79,18 +81,15 @@ BMad Method extends with official modules for specialized domains. Available dur
79
81
  ## Community
80
82
 
81
83
  - [Discord](https://discord.gg/gk8jAdXWmj) — Get help, share ideas, collaborate
82
- - [Subscribe on YouTube](https://www.youtube.com/@BMadCode) — Tutorials, master class, and podcast (launching Feb 2025)
84
+ - [YouTube](https://youtube.com/@BMadCode) — Tutorials, master class, and more
85
+ - [X / Twitter](https://x.com/BMadCode)
86
+ - [Website](https://bmadcode.com)
83
87
  - [GitHub Issues](https://github.com/bmad-code-org/BMAD-METHOD/issues) — Bug reports and feature requests
84
88
  - [Discussions](https://github.com/bmad-code-org/BMAD-METHOD/discussions) — Community conversations
85
89
 
86
90
  ## Support BMad
87
91
 
88
- BMad is free for everyone and always will be. If you'd like to support development:
89
-
90
- - ⭐ Please click the star project icon near the top right of this page
91
- - ☕ [Buy Me a Coffee](https://buymeacoffee.com/bmad) — Fuel the development
92
- - 🏢 Corporate sponsorship — DM on Discord
93
- - 🎤 Speaking & Media — Available for conferences, podcasts, interviews (BM on Discord)
92
+ BMad is free for everyone and always will be. Star this repo, [buy me a coffee](https://buymeacoffee.com/bmad), or email <contact@bmadcode.com> for corporate sponsorship.
94
93
 
95
94
  ## Contributing
96
95
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package.json",
3
3
  "name": "bmad-method",
4
- "version": "6.2.3-next.22",
4
+ "version": "6.2.3-next.24",
5
5
  "description": "Breakthrough Method of Agile AI-driven Development",
6
6
  "keywords": [
7
7
  "agile",
package/removals.txt ADDED
@@ -0,0 +1,17 @@
1
+ # BMad Method - Skill Removal List
2
+ # Entries listed here will be removed from IDE skill directories during install/update.
3
+ # One entry per line. Lines starting with # are comments.
4
+ # Each entry is a skill directory name (canonicalId) that was removed or renamed.
5
+
6
+ # Removed agents (v6.2.0 - v6.2.2)
7
+ bmad-agent-sm
8
+ bmad-agent-qa
9
+ bmad-agent-quick-flow-solo-dev
10
+
11
+ # Removed skills (v6.2.0 - v6.2.2)
12
+ bmad-create-product-brief
13
+ bmad-product-brief-preview
14
+ bmad-quick-spec
15
+ bmad-quick-flow
16
+ bmad-quick-dev-new-preview
17
+ bmad-init
@@ -0,0 +1,197 @@
1
+ # BMAD PRD Purpose
2
+
3
+ **The PRD is the top of the required funnel that feeds all subsequent product development work in rhw BMad Method.**
4
+
5
+ ---
6
+
7
+ ## What is a BMAD PRD?
8
+
9
+ A dual-audience document serving:
10
+ 1. **Human Product Managers and builders** - Vision, strategy, stakeholder communication
11
+ 2. **LLM Downstream Consumption** - UX Design → Architecture → Epics → Development AI Agents
12
+
13
+ Each successive document becomes more AI-tailored and granular.
14
+
15
+ ---
16
+
17
+ ## Core Philosophy: Information Density
18
+
19
+ **High Signal-to-Noise Ratio**
20
+
21
+ Every sentence must carry information weight. LLMs consume precise, dense content efficiently.
22
+
23
+ **Anti-Patterns (Eliminate These):**
24
+ - ❌ "The system will allow users to..." → ✅ "Users can..."
25
+ - ❌ "It is important to note that..." → ✅ State the fact directly
26
+ - ❌ "In order to..." → ✅ "To..."
27
+ - ❌ Conversational filler and padding → ✅ Direct, concise statements
28
+
29
+ **Goal:** Maximum information per word. Zero fluff.
30
+
31
+ ---
32
+
33
+ ## The Traceability Chain
34
+
35
+ **PRD starts the chain:**
36
+ ```
37
+ Vision → Success Criteria → User Journeys → Functional Requirements → (future: User Stories)
38
+ ```
39
+
40
+ **In the PRD, establish:**
41
+ - Vision → Success Criteria alignment
42
+ - Success Criteria → User Journey coverage
43
+ - User Journey → Functional Requirement mapping
44
+ - All requirements traceable to user needs
45
+
46
+ **Why:** Each downstream artifact (UX, Architecture, Epics, Stories) must trace back to documented user needs and business objectives. This chain ensures we build the right thing.
47
+
48
+ ---
49
+
50
+ ## What Makes Great Functional Requirements?
51
+
52
+ ### FRs are Capabilities, Not Implementation
53
+
54
+ **Good FR:** "Users can reset their password via email link"
55
+ **Bad FR:** "System sends JWT via email and validates with database" (implementation leakage)
56
+
57
+ **Good FR:** "Dashboard loads in under 2 seconds for 95th percentile"
58
+ **Bad FR:** "Fast loading time" (subjective, unmeasurable)
59
+
60
+ ### SMART Quality Criteria
61
+
62
+ **Specific:** Clear, precisely defined capability
63
+ **Measurable:** Quantifiable with test criteria
64
+ **Attainable:** Realistic within constraints
65
+ **Relevant:** Aligns with business objectives
66
+ **Traceable:** Links to source (executive summary or user journey)
67
+
68
+ ### FR Anti-Patterns
69
+
70
+ **Subjective Adjectives:**
71
+ - ❌ "easy to use", "intuitive", "user-friendly", "fast", "responsive"
72
+ - ✅ Use metrics: "completes task in under 3 clicks", "loads in under 2 seconds"
73
+
74
+ **Implementation Leakage:**
75
+ - ❌ Technology names, specific libraries, implementation details
76
+ - ✅ Focus on capability and measurable outcomes
77
+
78
+ **Vague Quantifiers:**
79
+ - ❌ "multiple users", "several options", "various formats"
80
+ - ✅ "up to 100 concurrent users", "3-5 options", "PDF, DOCX, TXT formats"
81
+
82
+ **Missing Test Criteria:**
83
+ - ❌ "The system shall provide notifications"
84
+ - ✅ "The system shall send email notifications within 30 seconds of trigger event"
85
+
86
+ ---
87
+
88
+ ## What Makes Great Non-Functional Requirements?
89
+
90
+ ### NFRs Must Be Measurable
91
+
92
+ **Template:**
93
+ ```
94
+ "The system shall [metric] [condition] [measurement method]"
95
+ ```
96
+
97
+ **Examples:**
98
+ - ✅ "The system shall respond to API requests in under 200ms for 95th percentile as measured by APM monitoring"
99
+ - ✅ "The system shall maintain 99.9% uptime during business hours as measured by cloud provider SLA"
100
+ - ✅ "The system shall support 10,000 concurrent users as measured by load testing"
101
+
102
+ ### NFR Anti-Patterns
103
+
104
+ **Unmeasurable Claims:**
105
+ - ❌ "The system shall be scalable" → ✅ "The system shall handle 10x load growth through horizontal scaling"
106
+ - ❌ "High availability required" → ✅ "99.9% uptime as measured by cloud provider SLA"
107
+
108
+ **Missing Context:**
109
+ - ❌ "Response time under 1 second" → ✅ "API response time under 1 second for 95th percentile under normal load"
110
+
111
+ ---
112
+
113
+ ## Domain-Specific Requirements
114
+
115
+ **Auto-Detect and Enforce Based on Project Context**
116
+
117
+ Certain industries have mandatory requirements that must be present:
118
+
119
+ - **Healthcare:** HIPAA Privacy & Security Rules, PHI encryption, audit logging, MFA
120
+ - **Fintech:** PCI-DSS Level 1, AML/KYC compliance, SOX controls, financial audit trails
121
+ - **GovTech:** NIST framework, Section 508 accessibility (WCAG 2.1 AA), FedRAMP, data residency
122
+ - **E-Commerce:** PCI-DSS for payments, inventory accuracy, tax calculation by jurisdiction
123
+
124
+ **Why:** Missing these requirements in the PRD means they'll be missed in architecture and implementation, creating expensive rework. During PRD creation there is a step to cover this - during validation we want to make sure it was covered. For this purpose steps will utilize a domain-complexity.csv and project-types.csv.
125
+
126
+ ---
127
+
128
+ ## Document Structure (Markdown, Human-Readable)
129
+
130
+ ### Required Sections
131
+ 1. **Executive Summary** - Vision, differentiator, target users
132
+ 2. **Success Criteria** - Measurable outcomes (SMART)
133
+ 3. **Product Scope** - MVP, Growth, Vision phases
134
+ 4. **User Journeys** - Comprehensive coverage
135
+ 5. **Domain Requirements** - Industry-specific compliance (if applicable)
136
+ 6. **Innovation Analysis** - Competitive differentiation (if applicable)
137
+ 7. **Project-Type Requirements** - Platform-specific needs
138
+ 8. **Functional Requirements** - Capability contract (FRs)
139
+ 9. **Non-Functional Requirements** - Quality attributes (NFRs)
140
+
141
+ ### Formatting for Dual Consumption
142
+
143
+ **For Humans:**
144
+ - Clear, professional language
145
+ - Logical flow from vision to requirements
146
+ - Easy for stakeholders to review and approve
147
+
148
+ **For LLMs:**
149
+ - ## Level 2 headers for all main sections (enables extraction)
150
+ - Consistent structure and patterns
151
+ - Precise, testable language
152
+ - High information density
153
+
154
+ ---
155
+
156
+ ## Downstream Impact
157
+
158
+ **How the PRD Feeds Next Artifacts:**
159
+
160
+ **UX Design:**
161
+ - User journeys → interaction flows
162
+ - FRs → design requirements
163
+ - Success criteria → UX metrics
164
+
165
+ **Architecture:**
166
+ - FRs → system capabilities
167
+ - NFRs → architecture decisions
168
+ - Domain requirements → compliance architecture
169
+ - Project-type requirements → platform choices
170
+
171
+ **Epics & Stories (created after architecture):**
172
+ - FRs → user stories (1 FR could map to 1-3 stories potentially)
173
+ - Acceptance criteria → story acceptance tests
174
+ - Priority → sprint sequencing
175
+ - Traceability → stories map back to vision
176
+
177
+ **Development AI Agents:**
178
+ - Precise requirements → implementation clarity
179
+ - Test criteria → automated test generation
180
+ - Domain requirements → compliance enforcement
181
+ - Measurable NFRs → performance targets
182
+
183
+ ---
184
+
185
+ ## Summary: What Makes a Great BMAD PRD?
186
+
187
+ ✅ **High Information Density** - Every sentence carries weight, zero fluff
188
+ ✅ **Measurable Requirements** - All FRs and NFRs are testable with specific criteria
189
+ ✅ **Clear Traceability** - Each requirement links to user need and business objective
190
+ ✅ **Domain Awareness** - Industry-specific requirements auto-detected and included
191
+ ✅ **Zero Anti-Patterns** - No subjective adjectives, implementation leakage, or vague quantifiers
192
+ ✅ **Dual Audience Optimized** - Human-readable AND LLM-consumable
193
+ ✅ **Markdown Format** - Professional, clean, accessible to all stakeholders
194
+
195
+ ---
196
+
197
+ **Remember:** The PRD is the foundation. Quality here ripples through every subsequent phase. A dense, precise, well-traced PRD makes UX design, architecture, epic breakdown, and AI development dramatically more effective.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  # File references (ONLY variables used in this step)
3
- prdPurpose: '{project-root}/_bmad/bmm-skills/2-plan-workflows/bmad-create-prd/data/prd-purpose.md'
3
+ prdPurpose: '../data/prd-purpose.md'
4
4
  ---
5
5
 
6
6
  # Step E-1: Discovery & Understanding
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  # File references (ONLY variables used in this step)
3
3
  prdFile: '{prd_file_path}'
4
- prdPurpose: '{project-root}/_bmad/bmm-skills/2-plan-workflows/bmad-create-prd/data/prd-purpose.md'
4
+ prdPurpose: '../data/prd-purpose.md'
5
5
  ---
6
6
 
7
7
  # Step E-1B: Legacy PRD Conversion Assessment
@@ -2,7 +2,7 @@
2
2
  # File references (ONLY variables used in this step)
3
3
  prdFile: '{prd_file_path}'
4
4
  validationReport: '{validation_report_path}' # If provided
5
- prdPurpose: '{project-root}/_bmad/bmm-skills/2-plan-workflows/bmad-create-prd/data/prd-purpose.md'
5
+ prdPurpose: '../data/prd-purpose.md'
6
6
  ---
7
7
 
8
8
  # Step E-2: Deep Review & Analysis
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  # File references (ONLY variables used in this step)
3
3
  prdFile: '{prd_file_path}'
4
- prdPurpose: '{project-root}/_bmad/bmm-skills/2-plan-workflows/bmad-create-prd/data/prd-purpose.md'
4
+ prdPurpose: '../data/prd-purpose.md'
5
5
  ---
6
6
 
7
7
  # Step E-3: Edit & Update
@@ -1,7 +1,6 @@
1
1
  ---
2
2
  # File references (ONLY variables used in this step)
3
3
  prdFile: '{prd_file_path}'
4
- validationWorkflow: '{project-root}/_bmad/bmm-skills/2-plan-workflows/bmad-validate-prd/steps-v/step-v-01-discovery.md'
5
4
  ---
6
5
 
7
6
  # Step E-4: Complete & Validate
@@ -117,8 +116,7 @@ Display:
117
116
  - Display: "This will run all 13 validation checks on the updated PRD."
118
117
  - Display: "Preparing to validate: {prd_file_path}"
119
118
  - Display: "**Proceeding to validation...**"
120
- - Read fully and follow: {validationWorkflow} (steps-v/step-v-01-discovery.md)
121
- - Note: This hands off to the validation workflow which will run its complete 13-step process
119
+ - Invoke the `bmad-validate-prd` skill to run the complete validation workflow
122
120
 
123
121
  - **IF E (Edit More):**
124
122
  - Display: "**Additional Edits**"
@@ -19,24 +19,33 @@ const CLIUtils = {
19
19
  * Display BMAD logo and version using @clack intro + box
20
20
  */
21
21
  async displayLogo() {
22
- const version = this.getVersion();
23
22
  const color = await prompts.getColor();
24
-
25
- // ASCII art logo
26
- const logo = [
23
+ const termWidth = process.stdout.columns || 80;
24
+
25
+ // Full "BMad Method" logo for wide terminals, "BMad" only for narrow
26
+ const logoWide = [
27
+ ' ██████╗ ███╗ ███╗ █████╗ ██████╗ ███╗ ███╗███████╗████████╗██╗ ██╗ ██████╗ ██████╗ ™',
28
+ '██╔══██╗████╗ ████║██╔══██╗██╔══██╗ ████╗ ████║██╔════╝╚══██╔══╝██║ ██║██╔═══██╗██╔══██╗',
29
+ '██████╔╝██╔████╔██║███████║██║ ██║ ██╔████╔██║█████╗ ██║ ███████║██║ ██║██║ ██║',
30
+ '██╔══██╗██║╚██╔╝██║██╔══██║██║ ██║ ██║╚██╔╝██║██╔══╝ ██║ ██╔══██║██║ ██║██║ ██║',
31
+ '██████╔╝██║ ╚═╝ ██║██║ ██║██████╔╝ ██║ ╚═╝ ██║███████╗ ██║ ██║ ██║╚██████╔╝██████╔╝',
32
+ '╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═════╝ ╚═╝ ╚═╝╚══════╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ',
33
+ ];
34
+
35
+ const logoNarrow = [
27
36
  ' ██████╗ ███╗ ███╗ █████╗ ██████╗ ™',
28
37
  ' ██╔══██╗████╗ ████║██╔══██╗██╔══██╗',
29
38
  ' ██████╔╝██╔████╔██║███████║██║ ██║',
30
39
  ' ██╔══██╗██║╚██╔╝██║██╔══██║██║ ██║',
31
40
  ' ██████╔╝██║ ╚═╝ ██║██║ ██║██████╔╝',
32
41
  ' ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═════╝',
33
- ]
34
- .map((line) => color.yellow(line))
35
- .join('\n');
42
+ ];
36
43
 
37
- const tagline = ' Build More, Architect Dreams';
44
+ const logoLines = termWidth >= 95 ? logoWide : logoNarrow;
45
+ const logo = logoLines.map((line) => color.blue(line)).join('\n');
46
+ const tagline = color.white(' Build More, Architect Dreams\n © BMad Code');
38
47
 
39
- await prompts.box(`${logo}\n${tagline}`, `v${version}`, {
48
+ await prompts.box(`${logo}\n${tagline}`, '', {
40
49
  contentAlign: 'center',
41
50
  rounded: true,
42
51
  formatBorder: color.blue,
@@ -26,6 +26,44 @@ class Installer {
26
26
  this.bmadFolderName = BMAD_FOLDER_NAME;
27
27
  }
28
28
 
29
+ /**
30
+ * Read the module version from .claude-plugin/marketplace.json
31
+ * Walks up from sourcePath looking for .claude-plugin/marketplace.json
32
+ * @param {string} sourcePath - Module source directory
33
+ * @returns {string} Version string or empty string
34
+ */
35
+ async _getMarketplaceVersion(sourcePath) {
36
+ let dir = sourcePath;
37
+ for (let i = 0; i < 5; i++) {
38
+ const marketplacePath = path.join(dir, '.claude-plugin', 'marketplace.json');
39
+ if (await fs.pathExists(marketplacePath)) {
40
+ try {
41
+ const data = JSON.parse(await fs.readFile(marketplacePath, 'utf8'));
42
+ return this._extractMarketplaceVersion(data);
43
+ } catch {
44
+ return '';
45
+ }
46
+ }
47
+ const parent = path.dirname(dir);
48
+ if (parent === dir) break;
49
+ dir = parent;
50
+ }
51
+ return '';
52
+ }
53
+
54
+ /**
55
+ * Extract the highest version from marketplace.json plugins array
56
+ */
57
+ _extractMarketplaceVersion(data) {
58
+ const plugins = data?.plugins;
59
+ if (!Array.isArray(plugins) || plugins.length === 0) return '';
60
+ let best = '';
61
+ for (const p of plugins) {
62
+ if (p.version && (!best || p.version > best)) best = p.version;
63
+ }
64
+ return best;
65
+ }
66
+
29
67
  /**
30
68
  * Main installation method
31
69
  * @param {Object} config - Installation configuration
@@ -52,9 +90,36 @@ class Installer {
52
90
 
53
91
  await this._validateIdeSelection(config);
54
92
 
93
+ // Capture pre-install module versions for from→to display
94
+ const preInstallVersions = new Map();
95
+ if (existingInstall.installed) {
96
+ const existingModules = await this.manifest.getAllModuleVersions(paths.bmadDir);
97
+ for (const mod of existingModules) {
98
+ if (mod.name && mod.version) {
99
+ preInstallVersions.set(mod.name, mod.version);
100
+ }
101
+ }
102
+ }
103
+
55
104
  // Results collector for consolidated summary
56
105
  const results = [];
57
- const addResult = (step, status, detail = '') => results.push({ step, status, detail });
106
+ const addResult = (step, status, detail = '', meta = {}) => results.push({ step, status, detail, ...meta });
107
+
108
+ // Capture previously installed skill IDs before they get overwritten
109
+ const previousSkillIds = new Set();
110
+ const prevCsvPath = path.join(paths.bmadDir, '_config', 'skill-manifest.csv');
111
+ if (await fs.pathExists(prevCsvPath)) {
112
+ try {
113
+ const csvParse = require('csv-parse/sync');
114
+ const content = await fs.readFile(prevCsvPath, 'utf8');
115
+ const records = csvParse.parse(content, { columns: true, skip_empty_lines: true });
116
+ for (const r of records) {
117
+ if (r.canonicalId) previousSkillIds.add(r.canonicalId);
118
+ }
119
+ } catch (error) {
120
+ await prompts.log.warn(`Failed to parse skill-manifest.csv: ${error.message}`);
121
+ }
122
+ }
58
123
 
59
124
  await this._cacheCustomModules(paths, addResult);
60
125
 
@@ -65,7 +130,11 @@ class Installer {
65
130
 
66
131
  await this._installAndConfigure(config, originalConfig, paths, officialModuleIds, allModules, addResult, officialModules);
67
132
 
68
- await this._setupIdes(config, allModules, paths, addResult);
133
+ await this._setupIdes(config, allModules, paths, addResult, previousSkillIds);
134
+
135
+ // Skills are now in IDE directories — remove redundant copies from _bmad/.
136
+ // Also cleans up skill dirs left by older installer versions.
137
+ await this._cleanupSkillDirs(paths.bmadDir);
69
138
 
70
139
  const restoreResult = await this._restoreUserFiles(paths, updateState);
71
140
 
@@ -76,6 +145,7 @@ class Installer {
76
145
  ides: config.ides,
77
146
  customFiles: restoreResult.customFiles.length > 0 ? restoreResult.customFiles : undefined,
78
147
  modifiedFiles: restoreResult.modifiedFiles.length > 0 ? restoreResult.modifiedFiles : undefined,
148
+ preInstallVersions,
79
149
  });
80
150
 
81
151
  return {
@@ -321,7 +391,7 @@ class Installer {
321
391
  /**
322
392
  * Set up IDE integrations for each selected IDE.
323
393
  */
324
- async _setupIdes(config, allModules, paths, addResult) {
394
+ async _setupIdes(config, allModules, paths, addResult, previousSkillIds = new Set()) {
325
395
  if (config.skipIde || !config.ides || config.ides.length === 0) return;
326
396
 
327
397
  await this.ideManager.ensureInitialized();
@@ -336,6 +406,7 @@ class Installer {
336
406
  const setupResult = await this.ideManager.setup(ide, paths.projectRoot, paths.bmadDir, {
337
407
  selectedModules: allModules || [],
338
408
  verbose: config.verbose,
409
+ previousSkillIds,
339
410
  });
340
411
 
341
412
  if (setupResult.success) {
@@ -346,6 +417,33 @@ class Installer {
346
417
  }
347
418
  }
348
419
 
420
+ /**
421
+ * Remove skill directories from _bmad/ after IDE installation.
422
+ * Skills are self-contained in IDE directories, so _bmad/ only needs
423
+ * module-level files (config.yaml, _config/, etc.).
424
+ * Also cleans up skill dirs left by older installer versions.
425
+ * @param {string} bmadDir - BMAD installation directory
426
+ */
427
+ async _cleanupSkillDirs(bmadDir) {
428
+ const csv = require('csv-parse/sync');
429
+ const csvPath = path.join(bmadDir, '_config', 'skill-manifest.csv');
430
+ if (!(await fs.pathExists(csvPath))) return;
431
+
432
+ const csvContent = await fs.readFile(csvPath, 'utf8');
433
+ const records = csv.parse(csvContent, { columns: true, skip_empty_lines: true });
434
+ const bmadFolderName = path.basename(bmadDir);
435
+ const bmadPrefix = bmadFolderName + '/';
436
+
437
+ for (const record of records) {
438
+ if (!record.path) continue;
439
+ const relativePath = record.path.startsWith(bmadPrefix) ? record.path.slice(bmadPrefix.length) : record.path;
440
+ const sourceDir = path.dirname(path.join(bmadDir, relativePath));
441
+ if (await fs.pathExists(sourceDir)) {
442
+ await fs.remove(sourceDir);
443
+ }
444
+ }
445
+ }
446
+
349
447
  /**
350
448
  * Restore custom and modified files that were backed up before the update.
351
449
  * No-op for fresh installs (updateState is null).
@@ -556,7 +654,7 @@ class Installer {
556
654
  message(`${isQuickUpdate ? 'Updating' : 'Installing'} ${moduleName}...`);
557
655
 
558
656
  const moduleConfig = officialModules.moduleConfigs[moduleName] || {};
559
- await officialModules.install(
657
+ const installResult = await officialModules.install(
560
658
  moduleName,
561
659
  paths.bmadDir,
562
660
  (filePath) => {
@@ -570,7 +668,12 @@ class Installer {
570
668
  },
571
669
  );
572
670
 
573
- addResult(`Module: ${moduleName}`, 'ok', isQuickUpdate ? 'updated' : 'installed');
671
+ // Get display name from source module.yaml; version from marketplace.json
672
+ const sourcePath = await officialModules.findModuleSource(moduleName, { silent: true });
673
+ const moduleInfo = sourcePath ? await officialModules.getModuleInfo(sourcePath, moduleName, '') : null;
674
+ const displayName = moduleInfo?.name || moduleName;
675
+ const version = sourcePath ? await this._getMarketplaceVersion(sourcePath) : '';
676
+ addResult(displayName, 'ok', '', { moduleCode: moduleName, newVersion: version });
574
677
  }
575
678
  }
576
679
 
@@ -598,7 +701,11 @@ class Installer {
598
701
  [moduleName]: { ...config.coreConfig, ...result.moduleConfig, ...collectedModuleConfig },
599
702
  });
600
703
 
601
- addResult(`Module: ${moduleName}`, 'ok', isQuickUpdate ? 'updated' : 'installed');
704
+ // Get display name from source module.yaml; version from marketplace.json
705
+ const moduleInfo = await officialModules.getModuleInfo(sourcePath, moduleName, '');
706
+ const displayName = moduleInfo?.name || moduleName;
707
+ const version = await this._getMarketplaceVersion(sourcePath);
708
+ addResult(displayName, 'ok', '', { moduleCode: moduleName, newVersion: version });
602
709
  }
603
710
  }
604
711
 
@@ -1062,23 +1169,10 @@ class Installer {
1062
1169
  const selectedIdes = new Set((context.ides || []).map((ide) => String(ide).toLowerCase()));
1063
1170
 
1064
1171
  // Build step lines with status indicators
1172
+ const preVersions = context.preInstallVersions || new Map();
1065
1173
  const lines = [];
1066
1174
  for (const r of results) {
1067
- let stepLabel = null;
1068
-
1069
- if (r.status !== 'ok') {
1070
- stepLabel = r.step;
1071
- } else if (r.step === 'Core') {
1072
- stepLabel = 'BMAD';
1073
- } else if (r.step.startsWith('Module: ')) {
1074
- stepLabel = r.step;
1075
- } else if (selectedIdes.has(String(r.step).toLowerCase())) {
1076
- stepLabel = r.step;
1077
- }
1078
-
1079
- if (!stepLabel) {
1080
- continue;
1081
- }
1175
+ const stepLabel = r.step;
1082
1176
 
1083
1177
  let icon;
1084
1178
  if (r.status === 'ok') {
@@ -1088,18 +1182,32 @@ class Installer {
1088
1182
  } else {
1089
1183
  icon = color.red('\u2717');
1090
1184
  }
1091
- const detail = r.detail ? color.dim(` (${r.detail})`) : '';
1185
+
1186
+ // Build version detail for module results
1187
+ let detail = '';
1188
+ if (r.moduleCode && r.newVersion) {
1189
+ const oldVersion = preVersions.get(r.moduleCode);
1190
+ if (oldVersion && oldVersion === r.newVersion) {
1191
+ detail = ` (v${r.newVersion}, no change)`;
1192
+ } else if (oldVersion) {
1193
+ detail = ` (v${oldVersion} → v${r.newVersion})`;
1194
+ } else {
1195
+ detail = ` (v${r.newVersion}, installed)`;
1196
+ }
1197
+ } else if (r.detail) {
1198
+ detail = ` (${r.detail})`;
1199
+ }
1092
1200
  lines.push(` ${icon} ${stepLabel}${detail}`);
1093
1201
  }
1094
1202
 
1095
1203
  if ((context.ides || []).length === 0) {
1096
- lines.push(` ${color.green('\u2713')} No IDE selected ${color.dim('(installed in _bmad only)')}`);
1204
+ lines.push(` ${color.green('\u2713')} No IDE selected (installed in _bmad only)`);
1097
1205
  }
1098
1206
 
1099
1207
  // Context and warnings
1100
1208
  lines.push('');
1101
1209
  if (context.bmadDir) {
1102
- lines.push(` Installed to: ${color.dim(context.bmadDir)}`);
1210
+ lines.push(` Installed to: ${context.bmadDir}`);
1103
1211
  }
1104
1212
  if (context.customFiles && context.customFiles.length > 0) {
1105
1213
  lines.push(` ${color.cyan(`Custom files preserved: ${context.customFiles.length}`)}`);
@@ -1111,17 +1219,18 @@ class Installer {
1111
1219
  // Next steps
1112
1220
  lines.push(
1113
1221
  '',
1114
- ' Next steps:',
1115
- ` Read our new Docs Site: ${color.dim('https://docs.bmad-method.org/')}`,
1116
- ` Join our Discord: ${color.dim('https://discord.gg/gk8jAdXWmj')}`,
1117
- ` Star us on GitHub: ${color.dim('https://github.com/bmad-code-org/BMAD-METHOD/')}`,
1118
- ` Subscribe on YouTube: ${color.dim('https://www.youtube.com/@BMadCode')}`,
1222
+ ' Get started:',
1223
+ ` 1. Launch your AI agent from your project folder`,
1224
+ ` 2. Not sure what to do? Invoke the ${color.cyan('bmad-help')} skill and ask it what to do!`,
1225
+ '',
1226
+ ` Blog, Docs and Guides: ${color.blue('https://bmadcode.com/')}`,
1227
+ ` Community: ${color.blue('https://discord.gg/gk8jAdXWmj')}`,
1119
1228
  );
1120
- if (context.ides && context.ides.length > 0) {
1121
- lines.push(` Invoke the ${color.cyan('bmad-help')} skill in your IDE Agent to get started`);
1122
- }
1123
1229
 
1124
- await prompts.note(lines.join('\n'), 'BMAD is ready to use!');
1230
+ await prompts.box(lines.join('\n'), 'BMAD is ready to use!', {
1231
+ rounded: true,
1232
+ formatBorder: color.green,
1233
+ });
1125
1234
  }
1126
1235
 
1127
1236
  /**
@@ -1231,6 +1340,7 @@ class Installer {
1231
1340
  }
1232
1341
 
1233
1342
  for (const moduleName of modulesToUpdate) {
1343
+ if (moduleName === 'core') continue; // Already collected above
1234
1344
  const modulePrompted = await quickModules.collectModuleConfigQuick(moduleName, projectDir, true);
1235
1345
  if (modulePrompted) {
1236
1346
  promptedForNewFields = true;
@@ -9,7 +9,6 @@ const {
9
9
  loadSkillManifest: loadSkillManifestShared,
10
10
  getCanonicalId: getCanonicalIdShared,
11
11
  getArtifactType: getArtifactTypeShared,
12
- getInstallToBmad: getInstallToBmadShared,
13
12
  } = require('../ide/shared/skill-manifest');
14
13
 
15
14
  // Load package.json for version info
@@ -42,11 +41,6 @@ class ManifestGenerator {
42
41
  return getArtifactTypeShared(manifest, filename);
43
42
  }
44
43
 
45
- /** Delegate to shared skill-manifest module */
46
- getInstallToBmad(manifest, filename) {
47
- return getInstallToBmadShared(manifest, filename);
48
- }
49
-
50
44
  /**
51
45
  * Clean text for CSV output by normalizing whitespace.
52
46
  * Note: Quote escaping is handled by escapeCsv() at write time.
@@ -127,7 +121,7 @@ class ManifestGenerator {
127
121
  * Recursively walk a module directory tree, collecting native SKILL.md entrypoints.
128
122
  * A directory is discovered as a skill when it contains a SKILL.md file with
129
123
  * valid name/description frontmatter (name must match directory name).
130
- * Manifest YAML is loaded only when present — for install_to_bmad and agent metadata.
124
+ * Manifest YAML is loaded only when present — for agent metadata.
131
125
  * Populates this.skills[] and this.skillClaimedDirs (Set of absolute paths).
132
126
  */
133
127
  async collectSkills() {
@@ -156,7 +150,7 @@ class ManifestGenerator {
156
150
  const skillMeta = await this.parseSkillMd(skillMdPath, dir, dirName, debug);
157
151
 
158
152
  if (skillMeta) {
159
- // Load manifest when present (for install_to_bmad and agent metadata)
153
+ // Load manifest when present (for agent metadata)
160
154
  const manifest = await this.loadSkillManifest(dir);
161
155
  const artifactType = this.getArtifactType(manifest, skillFile);
162
156
 
@@ -182,7 +176,6 @@ class ManifestGenerator {
182
176
  module: moduleName,
183
177
  path: installPath,
184
178
  canonicalId,
185
- install_to_bmad: this.getInstallToBmad(manifest, skillFile),
186
179
  });
187
180
 
188
181
  // Add to files list
@@ -472,7 +465,7 @@ class ManifestGenerator {
472
465
  const csvPath = path.join(cfgDir, 'skill-manifest.csv');
473
466
  const escapeCsv = (value) => `"${String(value ?? '').replaceAll('"', '""')}"`;
474
467
 
475
- let csvContent = 'canonicalId,name,description,module,path,install_to_bmad\n';
468
+ let csvContent = 'canonicalId,name,description,module,path\n';
476
469
 
477
470
  for (const skill of this.skills) {
478
471
  const row = [
@@ -481,7 +474,6 @@ class ManifestGenerator {
481
474
  escapeCsv(skill.description),
482
475
  escapeCsv(skill.module),
483
476
  escapeCsv(skill.path),
484
- escapeCsv(skill.install_to_bmad),
485
477
  ].join(',');
486
478
  csvContent += row + '\n';
487
479
  }
@@ -837,14 +837,13 @@ class Manifest {
837
837
  * @returns {Object} Version info object with version, source, npmPackage, repoUrl
838
838
  */
839
839
  async getModuleVersionInfo(moduleName, bmadDir, moduleSourcePath = null) {
840
- const os = require('node:os');
841
840
  const yaml = require('yaml');
842
841
 
843
- // Built-in modules use BMad version (only core and bmm are in BMAD-METHOD repo)
842
+ // Resolve source type first, then read version with the correct path context
844
843
  if (['core', 'bmm'].includes(moduleName)) {
845
- const bmadVersion = require(path.join(getProjectRoot(), 'package.json')).version;
844
+ const version = await this._readMarketplaceVersion(moduleName, moduleSourcePath);
846
845
  return {
847
- version: bmadVersion,
846
+ version,
848
847
  source: 'built-in',
849
848
  npmPackage: null,
850
849
  repoUrl: null,
@@ -857,42 +856,20 @@ class Manifest {
857
856
  const moduleInfo = await extMgr.getModuleByCode(moduleName);
858
857
 
859
858
  if (moduleInfo) {
860
- // External module - try to get version from npm registry first, then fall back to cache
861
- let version = null;
862
-
863
- if (moduleInfo.npmPackage) {
864
- // Fetch version from npm registry
865
- try {
866
- version = await this.fetchNpmVersion(moduleInfo.npmPackage);
867
- } catch {
868
- // npm fetch failed, try cache as fallback
869
- }
870
- }
871
-
872
- // If npm didn't work, try reading from cached repo's package.json
873
- if (!version) {
874
- const cacheDir = path.join(os.homedir(), '.bmad', 'cache', 'external-modules', moduleName);
875
- const packageJsonPath = path.join(cacheDir, 'package.json');
876
-
877
- if (await fs.pathExists(packageJsonPath)) {
878
- try {
879
- const pkg = require(packageJsonPath);
880
- version = pkg.version;
881
- } catch (error) {
882
- await prompts.log.warn(`Failed to read package.json for ${moduleName}: ${error.message}`);
883
- }
884
- }
885
- }
886
-
859
+ // External module: use moduleSourcePath if provided, otherwise fall back to cache
860
+ const version = await this._readMarketplaceVersion(moduleName, moduleSourcePath);
887
861
  return {
888
- version: version,
862
+ version,
889
863
  source: 'external',
890
864
  npmPackage: moduleInfo.npmPackage || null,
891
865
  repoUrl: moduleInfo.url || null,
892
866
  };
893
867
  }
894
868
 
895
- // Custom module - check cache directory
869
+ // Custom module: resolve path from source or cache before reading version
870
+ const customSourcePath = moduleSourcePath || path.join(bmadDir, '_config', 'custom', moduleName);
871
+ const version = await this._readMarketplaceVersion(moduleName, customSourcePath);
872
+
896
873
  const cacheDir = path.join(bmadDir, '_config', 'custom', moduleName);
897
874
  const moduleYamlPath = path.join(cacheDir, 'module.yaml');
898
875
 
@@ -901,7 +878,7 @@ class Manifest {
901
878
  const yamlContent = await fs.readFile(moduleYamlPath, 'utf8');
902
879
  const moduleConfig = yaml.parse(yamlContent);
903
880
  return {
904
- version: moduleConfig.version || null,
881
+ version: version || moduleConfig.version || null,
905
882
  source: 'custom',
906
883
  npmPackage: moduleConfig.npmPackage || null,
907
884
  repoUrl: moduleConfig.repoUrl || null,
@@ -913,13 +890,62 @@ class Manifest {
913
890
 
914
891
  // Unknown module
915
892
  return {
916
- version: null,
893
+ version,
917
894
  source: 'unknown',
918
895
  npmPackage: null,
919
896
  repoUrl: null,
920
897
  };
921
898
  }
922
899
 
900
+ /**
901
+ * Read version from .claude-plugin/marketplace.json for a module
902
+ * @param {string} moduleName - Module code
903
+ * @returns {string|null} Version or null
904
+ */
905
+ async _readMarketplaceVersion(moduleName, moduleSourcePath = null) {
906
+ const os = require('node:os');
907
+ let marketplacePath;
908
+
909
+ if (['core', 'bmm'].includes(moduleName)) {
910
+ marketplacePath = path.join(getProjectRoot(), '.claude-plugin', 'marketplace.json');
911
+ } else if (moduleSourcePath) {
912
+ // Walk up from source path to find marketplace.json
913
+ let dir = moduleSourcePath;
914
+ for (let i = 0; i < 5; i++) {
915
+ const candidate = path.join(dir, '.claude-plugin', 'marketplace.json');
916
+ if (await fs.pathExists(candidate)) {
917
+ marketplacePath = candidate;
918
+ break;
919
+ }
920
+ const parent = path.dirname(dir);
921
+ if (parent === dir) break;
922
+ dir = parent;
923
+ }
924
+ }
925
+
926
+ // Fallback to external module cache
927
+ if (!marketplacePath) {
928
+ const cacheDir = path.join(os.homedir(), '.bmad', 'cache', 'external-modules', moduleName);
929
+ marketplacePath = path.join(cacheDir, '.claude-plugin', 'marketplace.json');
930
+ }
931
+
932
+ try {
933
+ if (await fs.pathExists(marketplacePath)) {
934
+ const data = JSON.parse(await fs.readFile(marketplacePath, 'utf8'));
935
+ const plugins = data?.plugins;
936
+ if (!Array.isArray(plugins) || plugins.length === 0) return null;
937
+ let best = null;
938
+ for (const p of plugins) {
939
+ if (p.version && (!best || p.version > best)) best = p.version;
940
+ }
941
+ return best;
942
+ }
943
+ } catch {
944
+ // ignore
945
+ }
946
+ return null;
947
+ }
948
+
923
949
  /**
924
950
  * Fetch latest version from npm for a package
925
951
  * @param {string} packageName - npm package name
@@ -86,7 +86,7 @@ class ConfigDrivenIdeSetup {
86
86
  if (!options.silent) await prompts.log.info(`Setting up ${this.name}...`);
87
87
 
88
88
  // Clean up any old BMAD installation first
89
- await this.cleanup(projectDir, options);
89
+ await this.cleanup(projectDir, options, bmadDir);
90
90
 
91
91
  if (!this.installerConfig) {
92
92
  return { success: false, reason: 'no-config' };
@@ -183,18 +183,6 @@ class ConfigDrivenIdeSetup {
183
183
  count++;
184
184
  }
185
185
 
186
- // Post-install cleanup: remove _bmad/ directories for skills with install_to_bmad === "false"
187
- for (const record of records) {
188
- if (record.install_to_bmad === 'false') {
189
- const relativePath = record.path.startsWith(bmadPrefix) ? record.path.slice(bmadPrefix.length) : record.path;
190
- const sourceFile = path.join(bmadDir, relativePath);
191
- const sourceDir = path.dirname(sourceFile);
192
- if (await fs.pathExists(sourceDir)) {
193
- await fs.remove(sourceDir);
194
- }
195
- }
196
- }
197
-
198
186
  return count;
199
187
  }
200
188
 
@@ -215,15 +203,34 @@ class ConfigDrivenIdeSetup {
215
203
  * Cleanup IDE configuration
216
204
  * @param {string} projectDir - Project directory
217
205
  */
218
- async cleanup(projectDir, options = {}) {
206
+ async cleanup(projectDir, options = {}, bmadDir = null) {
207
+ const resolvedBmadDir = bmadDir || (await this._findBmadDir(projectDir));
208
+
209
+ // Build removal set: previously installed skills + removals.txt entries
210
+ let removalSet;
211
+ if (options.previousSkillIds && options.previousSkillIds.size > 0) {
212
+ // Install/update flow: use pre-captured skill IDs (before manifest was overwritten)
213
+ removalSet = new Set(options.previousSkillIds);
214
+ if (resolvedBmadDir) {
215
+ const removals = await this.loadRemovalLists(resolvedBmadDir);
216
+ for (const entry of removals) removalSet.add(entry);
217
+ }
218
+ } else if (resolvedBmadDir) {
219
+ // Uninstall flow: read from current skill-manifest.csv + removals.txt
220
+ removalSet = await this._buildUninstallSet(resolvedBmadDir);
221
+ } else {
222
+ removalSet = new Set();
223
+ }
224
+
219
225
  // Migrate legacy target directories (e.g. .opencode/agent → .opencode/agents)
226
+ // Legacy dirs are abandoned entirely, so use prefix matching (null removalSet)
220
227
  if (this.installerConfig?.legacy_targets) {
221
228
  if (!options.silent) await prompts.log.message(' Migrating legacy directories...');
222
229
  for (const legacyDir of this.installerConfig.legacy_targets) {
223
230
  if (this.isGlobalPath(legacyDir)) {
224
231
  await this.warnGlobalLegacy(legacyDir, options);
225
232
  } else {
226
- await this.cleanupTarget(projectDir, legacyDir, options);
233
+ await this.cleanupTarget(projectDir, legacyDir, options, null);
227
234
  await this.removeEmptyParents(projectDir, legacyDir);
228
235
  }
229
236
  }
@@ -244,9 +251,9 @@ class ConfigDrivenIdeSetup {
244
251
  await this.cleanupRovoDevPrompts(projectDir, options);
245
252
  }
246
253
 
247
- // Clean target directory
254
+ // Clean current target directory
248
255
  if (this.installerConfig?.target_dir) {
249
- await this.cleanupTarget(projectDir, this.installerConfig.target_dir, options);
256
+ await this.cleanupTarget(projectDir, this.installerConfig.target_dir, options, removalSet);
250
257
  }
251
258
  }
252
259
 
@@ -286,23 +293,117 @@ class ConfigDrivenIdeSetup {
286
293
  }
287
294
 
288
295
  /**
289
- * Cleanup a specific target directory
296
+ * Find the _bmad directory in a project
297
+ * @param {string} projectDir - Project directory
298
+ * @returns {string|null} Path to bmad dir or null
299
+ */
300
+ async _findBmadDir(projectDir) {
301
+ const bmadDir = path.join(projectDir, BMAD_FOLDER_NAME);
302
+ return (await fs.pathExists(bmadDir)) ? bmadDir : null;
303
+ }
304
+
305
+ /**
306
+ * Build the full set of entries to remove for uninstall.
307
+ * Reads skill-manifest.csv to know exactly what was installed, plus removal lists.
308
+ * @param {string} bmadDir - BMAD installation directory
309
+ * @returns {Set<string>} Set of entries to remove
310
+ */
311
+ async _buildUninstallSet(bmadDir) {
312
+ const removals = await this.loadRemovalLists(bmadDir);
313
+
314
+ // Also add all currently installed skills from skill-manifest.csv
315
+ const csvPath = path.join(bmadDir, '_config', 'skill-manifest.csv');
316
+ try {
317
+ if (await fs.pathExists(csvPath)) {
318
+ const content = await fs.readFile(csvPath, 'utf8');
319
+ const records = csv.parse(content, { columns: true, skip_empty_lines: true });
320
+ for (const record of records) {
321
+ if (record.canonicalId) {
322
+ removals.add(record.canonicalId);
323
+ }
324
+ }
325
+ }
326
+ } catch {
327
+ // If we can't read the manifest, we still have the removal lists
328
+ }
329
+
330
+ return removals;
331
+ }
332
+
333
+ /**
334
+ * Load removal lists from all module sources in the bmad directory.
335
+ * Each module can have an optional removals.txt listing entries to remove.
336
+ * @param {string} bmadDir - BMAD installation directory
337
+ * @returns {Set<string>} Set of entries to remove
338
+ */
339
+ async loadRemovalLists(bmadDir) {
340
+ const removals = new Set();
341
+ const { getProjectRoot } = require('../project-root');
342
+
343
+ // Read project-level removals.txt (covers core and bmm)
344
+ const projectRemovalsPath = path.join(getProjectRoot(), 'removals.txt');
345
+ await this._readRemovalFile(projectRemovalsPath, removals);
346
+
347
+ // Read per-module removals.txt from installed module directories
348
+ try {
349
+ const entries = await fs.readdir(bmadDir);
350
+ for (const entry of entries) {
351
+ if (entry.startsWith('_')) continue;
352
+ const removalPath = path.join(bmadDir, entry, 'removals.txt');
353
+ await this._readRemovalFile(removalPath, removals);
354
+ }
355
+ } catch {
356
+ // bmadDir may not exist yet on fresh install
357
+ }
358
+
359
+ return removals;
360
+ }
361
+
362
+ /**
363
+ * Read a removals.txt file and add entries to the set
364
+ * @param {string} filePath - Path to removals.txt
365
+ * @param {Set<string>} removals - Set to add entries to
366
+ */
367
+ async _readRemovalFile(filePath, removals) {
368
+ try {
369
+ if (await fs.pathExists(filePath)) {
370
+ const content = await fs.readFile(filePath, 'utf8');
371
+ for (const line of content.split('\n')) {
372
+ const trimmed = line.trim();
373
+ if (trimmed && !trimmed.startsWith('#')) {
374
+ removals.add(trimmed);
375
+ }
376
+ }
377
+ }
378
+ } catch {
379
+ // Optional file — ignore errors
380
+ }
381
+ }
382
+
383
+ /**
384
+ * Cleanup a specific target directory.
385
+ * When removalSet is provided, only removes entries in that set.
386
+ * When removalSet is null (legacy dirs), removes all bmad-prefixed entries.
290
387
  * @param {string} projectDir - Project directory
291
388
  * @param {string} targetDir - Target directory to clean
389
+ * @param {Object} options - Cleanup options
390
+ * @param {Set<string>|null} removalSet - Entries to remove, or null for legacy prefix matching
292
391
  */
293
- async cleanupTarget(projectDir, targetDir, options = {}) {
392
+ async cleanupTarget(projectDir, targetDir, options = {}, removalSet = new Set()) {
294
393
  const targetPath = path.join(projectDir, targetDir);
295
394
 
296
395
  if (!(await fs.pathExists(targetPath))) {
297
396
  return;
298
397
  }
299
398
 
300
- // Remove all bmad* files
399
+ if (removalSet && removalSet.size === 0) {
400
+ return;
401
+ }
402
+
301
403
  let entries;
302
404
  try {
303
405
  entries = await fs.readdir(targetPath);
304
406
  } catch {
305
- // Directory exists but can't be read - skip cleanup
306
407
  return;
307
408
  }
308
409
 
@@ -313,23 +414,26 @@ class ConfigDrivenIdeSetup {
313
414
  let removedCount = 0;
314
415
 
315
416
  for (const entry of entries) {
316
- if (!entry || typeof entry !== 'string') {
317
- continue;
318
- }
319
- if (entry.startsWith('bmad') && !entry.startsWith('bmad-os-')) {
320
- const entryPath = path.join(targetPath, entry);
417
+ if (!entry || typeof entry !== 'string') continue;
418
+
419
+ // Always preserve bmad-os-* utility skills regardless of cleanup mode
420
+ if (entry.startsWith('bmad-os-')) continue;
421
+
422
+ // Surgical removal from set, or legacy prefix matching when set is null
423
+ const shouldRemove = removalSet ? removalSet.has(entry) : entry.startsWith('bmad');
424
+
425
+ if (shouldRemove) {
321
426
  try {
322
- await fs.remove(entryPath);
427
+ await fs.remove(path.join(targetPath, entry));
323
428
  removedCount++;
324
429
  } catch {
325
- // Skip entries that can't be removed (broken symlinks, permission errors)
430
+ // Skip entries that can't be removed
326
431
  }
327
432
  }
328
433
  }
329
434
 
330
- if (removedCount > 0 && !options.silent) {
331
- await prompts.log.message(` Cleaned ${removedCount} BMAD files from ${targetDir}`);
332
- }
435
+ // Only log cleanup when it's not a routine reinstall (legacy dir cleanup or actual removals)
436
+ // Suppress for current target_dir since it's always cleaned before a fresh write
333
437
 
334
438
  // Remove empty directory after cleanup
335
439
  if (removedCount > 0) {
@@ -339,7 +443,7 @@ class ConfigDrivenIdeSetup {
339
443
  await fs.remove(targetPath);
340
444
  }
341
445
  } catch {
342
- // Directory may already be gone or in use — skip
446
+ // Directory may already be gone or in use
343
447
  }
344
448
  }
345
449
  }
@@ -54,19 +54,4 @@ function getArtifactType(manifest, filename) {
54
54
  return null;
55
55
  }
56
56
 
57
- /**
58
- * Get the install_to_bmad flag for a specific file from a loaded skill manifest.
59
- * @param {Object|null} manifest - Loaded manifest (from loadSkillManifest)
60
- * @param {string} filename - Source filename to look up
61
- * @returns {boolean} install_to_bmad value (defaults to true)
62
- */
63
- function getInstallToBmad(manifest, filename) {
64
- if (!manifest) return true;
65
- // Single-entry manifest applies to all files in the directory
66
- if (manifest.__single) return manifest.__single.install_to_bmad !== false;
67
- // Multi-entry: look up by filename directly
68
- if (manifest[filename]) return manifest[filename].install_to_bmad !== false;
69
- return true;
70
- }
71
-
72
- module.exports = { loadSkillManifest, getCanonicalId, getArtifactType, getInstallToBmad };
57
+ module.exports = { loadSkillManifest, getCanonicalId, getArtifactType };
@@ -6,32 +6,25 @@
6
6
  startMessage: |
7
7
  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
8
8
 
9
- 🎉 V6 IS HERE! Welcome to BMad Method V6 - Official Stable Release!
10
-
11
- The BMad Method is now a Platform powered by the BMad Method Core and Module Ecosystem!
12
- - Select and install modules during setup - customize your experience
13
- - New BMad Method for Agile AI-Driven Development (the evolution of V4)
14
- - Exciting new modules available during installation, with community modules coming soon
15
- - Documentation: https://docs.bmad-method.org
16
-
17
- 🌟 BMad is 100% free and open source.
18
- - No gated Discord. No paywalls. No gated content.
19
- - We believe in empowering everyone, not just those who can pay.
20
- - Knowledge should be shared, not sold.
21
-
22
- 🎤 SPEAKING & MEDIA:
23
- - Available for conferences, podcasts, and media appearances
24
- - Topics: AI-Native Transformation, Spec and Context Engineering, BMad Method
25
- - For speaking inquiries or interviews, reach out to BMad on Discord!
26
-
27
- HELP US GROW:
28
- - Star us on GitHub: https://github.com/bmad-code-org/BMAD-METHOD/
29
- - Subscribe on YouTube: https://www.youtube.com/@BMadCode
30
- - Free Community and Support: https://discord.gg/gk8jAdXWmj
31
- - Donate: https://buymeacoffee.com/bmad
32
- - Corporate Sponsorship available
33
-
34
- Latest updates: https://github.com/bmad-code-org/BMAD-METHOD/blob/main/CHANGELOG.md
9
+ Agile AI-Driven Development. Powered by BMad Core and a growing module ecosystem.
10
+ Install official and community modules during setup to customize your experience.
11
+
12
+ 🌟 100% free. 100% open source. Always.
13
+ No paywalls. No gated content. Knowledge shared, not sold.
14
+
15
+ 🌐 CONNECT:
16
+ Website: https://bmadcode.com/
17
+ Discord: https://discord.gg/gk8jAdXWmj
18
+ YouTube: https://www.youtube.com/@BMadCode
19
+ X: https://x.com/BMadCode
20
+ Facebook: https://facebook.com/@BMadCode
21
+
22
+ SUPPORT THE PROJECT:
23
+ Star us: https://github.com/bmad-code-org/BMAD-METHOD/
24
+ Donate: https://buymeacoffee.com/bmad
25
+ Corporate sponsorship and speaking inquiries: contact@bmadcode.com
26
+
27
+ Docs, blog, and latest updates: https://bmadcode.com/
35
28
 
36
29
  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
37
30
 
@@ -4,8 +4,50 @@ const fs = require('fs-extra');
4
4
  const { CLIUtils } = require('./cli-utils');
5
5
  const { CustomHandler } = require('./custom-handler');
6
6
  const { ExternalModuleManager } = require('./modules/external-manager');
7
+ const { getProjectRoot } = require('./project-root');
7
8
  const prompts = require('./prompts');
8
9
 
10
+ /**
11
+ * Read module version from .claude-plugin/marketplace.json
12
+ * @param {string} moduleCode - Module code (e.g., 'core', 'bmm', 'cis')
13
+ * @returns {string} Version string or empty string
14
+ */
15
+ async function getMarketplaceVersion(moduleCode) {
16
+ let marketplacePath;
17
+ if (moduleCode === 'core' || moduleCode === 'bmm') {
18
+ marketplacePath = path.join(getProjectRoot(), '.claude-plugin', 'marketplace.json');
19
+ } else {
20
+ const cacheDir = path.join(os.homedir(), '.bmad', 'cache', 'external-modules', moduleCode);
21
+ marketplacePath = path.join(cacheDir, '.claude-plugin', 'marketplace.json');
22
+ }
23
+ try {
24
+ if (await fs.pathExists(marketplacePath)) {
25
+ const data = JSON.parse(await fs.readFile(marketplacePath, 'utf8'));
26
+ return _extractMarketplaceVersion(data);
27
+ }
28
+ } catch {
29
+ // ignore
30
+ }
31
+ return '';
32
+ }
33
+
34
+ /**
35
+ * Extract the highest version from marketplace.json plugins array.
36
+ * Handles multiple plugins per file safely.
37
+ * @param {Object} data - Parsed marketplace.json
38
+ * @returns {string} Version string or empty string
39
+ */
40
+ function _extractMarketplaceVersion(data) {
41
+ const plugins = data?.plugins;
42
+ if (!Array.isArray(plugins) || plugins.length === 0) return '';
43
+ // Use the highest version across all plugins in the file
44
+ let best = '';
45
+ for (const p of plugins) {
46
+ if (p.version && (!best || p.version > best)) best = p.version;
47
+ }
48
+ return best;
49
+ }
50
+
9
51
  // Separator class for visual grouping in select/multiselect prompts
10
52
  // Note: @clack/prompts doesn't support separators natively, they are filtered out
11
53
  class Separator {
@@ -70,17 +112,14 @@ class UI {
70
112
  if (hasExistingInstall) {
71
113
  // Get version information
72
114
  const { existingInstall, bmadDir } = await this.getExistingInstallation(confirmedDirectory);
73
- const packageJsonPath = path.join(__dirname, '../../package.json');
74
- const currentVersion = require(packageJsonPath).version;
75
- const installedVersion = existingInstall.installed ? existingInstall.version || 'unknown' : 'unknown';
76
115
 
77
116
  // Build menu choices dynamically
78
117
  const choices = [];
79
118
 
80
119
  // Always show Quick Update first (allows refreshing installation even on same version)
81
- if (installedVersion !== 'unknown') {
120
+ if (existingInstall.installed) {
82
121
  choices.push({
83
- name: `Quick Update (v${installedVersion} → v${currentVersion})`,
122
+ name: 'Quick Update',
84
123
  value: 'quick-update',
85
124
  });
86
125
  }
@@ -880,14 +919,18 @@ class UI {
880
919
  const lockedValues = ['core'];
881
920
 
882
921
  // Core module is always installed — show it locked at the top
883
- allOptions.push({ label: 'BMad Core Module', value: 'core', hint: 'Core configuration and shared resources' });
922
+ const coreVersion = await getMarketplaceVersion('core');
923
+ const coreLabel = coreVersion ? `BMad Core Module (v${coreVersion})` : 'BMad Core Module';
924
+ allOptions.push({ label: coreLabel, value: 'core', hint: 'Core configuration and shared resources' });
884
925
  initialValues.push('core');
885
926
 
886
927
  // Helper to build module entry with proper sorting and selection
887
- const buildModuleEntry = (mod, value, group) => {
928
+ const buildModuleEntry = async (mod, value, group) => {
888
929
  const isInstalled = installedModuleIds.has(value);
930
+ const version = await getMarketplaceVersion(value);
931
+ const label = version ? `${mod.name} (v${version})` : mod.name;
889
932
  return {
890
- label: mod.name,
933
+ label,
891
934
  value,
892
935
  hint: mod.description || group,
893
936
  // Pre-select only if already installed (not on fresh install)
@@ -899,7 +942,7 @@ class UI {
899
942
  const localEntries = [];
900
943
  for (const mod of localModules) {
901
944
  if (!mod.isCustom && mod.id !== 'core') {
902
- const entry = buildModuleEntry(mod, mod.id, 'Local');
945
+ const entry = await buildModuleEntry(mod, mod.id, 'Local');
903
946
  localEntries.push(entry);
904
947
  if (entry.selected) {
905
948
  initialValues.push(mod.id);
@@ -912,7 +955,7 @@ class UI {
912
955
  const officialModules = [];
913
956
  for (const mod of externalModules) {
914
957
  if (mod.type === 'bmad-org') {
915
- const entry = buildModuleEntry(mod, mod.code, 'Official');
958
+ const entry = await buildModuleEntry(mod, mod.code, 'Official');
916
959
  officialModules.push(entry);
917
960
  if (entry.selected) {
918
961
  initialValues.push(mod.code);
@@ -925,7 +968,7 @@ class UI {
925
968
  const communityModules = [];
926
969
  for (const mod of externalModules) {
927
970
  if (mod.type === 'community') {
928
- const entry = buildModuleEntry(mod, mod.code, 'Community');
971
+ const entry = await buildModuleEntry(mod, mod.code, 'Community');
929
972
  communityModules.push(entry);
930
973
  if (entry.selected) {
931
974
  initialValues.push(mod.code);