bmad-method 6.0.0-alpha.14 → 6.0.0-alpha.15

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 (79) hide show
  1. package/.coderabbit.yaml +36 -0
  2. package/{CODE_OF_CONDUCT.md → .github/CODE_OF_CONDUCT.md} +4 -4
  3. package/CHANGELOG.md +136 -408
  4. package/README.md +4 -1
  5. package/docs/custom-content-installation.md +245 -0
  6. package/docs/index.md +2 -2
  7. package/docs/installers-bundlers/installers-modules-platforms-reference.md +6 -5
  8. package/docs/web-bundles-gemini-gpt-guide.md +1 -1
  9. package/example-custom-content/README.md +4 -0
  10. package/example-custom-content/agents/commit-poet/commit-poet.agent.yaml +1 -1
  11. package/example-custom-content/agents/toolsmith/toolsmith-sidecar/instructions.md +1 -1
  12. package/example-custom-content/agents/toolsmith/toolsmith-sidecar/knowledge/docs.md +1 -1
  13. package/example-custom-content/agents/toolsmith/toolsmith-sidecar/knowledge/installers.md +1 -1
  14. package/example-custom-content/agents/toolsmith/toolsmith-sidecar/knowledge/modules.md +2 -2
  15. package/example-custom-content/agents/toolsmith/toolsmith.agent.yaml +1 -1
  16. package/example-custom-content/{custom.yaml → module.yaml} +1 -0
  17. package/example-custom-content/workflows/quiz-master/steps/step-01-init.md +2 -2
  18. package/example-custom-content/workflows/quiz-master/steps/step-02-q1.md +1 -1
  19. package/example-custom-content/workflows/quiz-master/steps/step-03-q2.md +1 -1
  20. package/example-custom-content/workflows/quiz-master/steps/step-04-q3.md +1 -1
  21. package/example-custom-content/workflows/quiz-master/steps/step-05-q4.md +1 -1
  22. package/example-custom-content/workflows/quiz-master/steps/step-06-q5.md +1 -1
  23. package/example-custom-content/workflows/quiz-master/steps/step-07-q6.md +1 -1
  24. package/example-custom-content/workflows/quiz-master/steps/step-08-q7.md +1 -1
  25. package/example-custom-content/workflows/quiz-master/steps/step-09-q8.md +1 -1
  26. package/example-custom-content/workflows/quiz-master/steps/step-10-q9.md +1 -1
  27. package/example-custom-content/workflows/quiz-master/steps/step-11-q10.md +1 -1
  28. package/example-custom-content/workflows/quiz-master/steps/step-12-results.md +1 -1
  29. package/example-custom-content/workflows/quiz-master/workflow.md +1 -1
  30. package/example-custom-module/mwm/README.md +5 -0
  31. package/example-custom-module/mwm/agents/cbt-coach/cbt-coach.agent.yaml +1 -0
  32. package/example-custom-module/mwm/agents/crisis-navigator.agent.yaml +3 -2
  33. package/example-custom-module/mwm/agents/meditation-guide.agent.yaml +3 -2
  34. package/example-custom-module/mwm/agents/wellness-companion/wellness-companion.agent.yaml +1 -0
  35. package/example-custom-module/mwm/{_module-installer/install-config.yaml → module.yaml} +1 -0
  36. package/package.json +1 -1
  37. package/src/core/_module-installer/installer.js +1 -1
  38. package/src/modules/bmb/_module-installer/installer.js +1 -1
  39. package/src/modules/bmb/docs/agents/index.md +1 -1
  40. package/src/modules/bmb/workflows/create-module/steps/step-04-structure.md +3 -3
  41. package/src/modules/bmb/workflows/create-module/steps/step-05-config.md +1 -1
  42. package/src/modules/bmb/workflows/create-module/steps/step-08-installer.md +8 -8
  43. package/src/modules/bmb/workflows/create-module/steps/step-09-documentation.md +2 -1
  44. package/src/modules/bmb/workflows/create-module/steps/step-10-roadmap.md +3 -2
  45. package/src/modules/bmb/workflows/create-module/steps/step-11-validate.md +3 -3
  46. package/src/modules/bmb/workflows/create-module/templates/installer.template.js +1 -1
  47. package/src/modules/bmb/workflows/create-module/validation.md +3 -3
  48. package/src/modules/bmb/workflows/create-workflow/steps/step-01-init.md +1 -1
  49. package/src/modules/bmb/workflows/create-workflow/steps/step-07-build.md +1 -1
  50. package/src/modules/bmgd/README.md +2 -1
  51. package/src/modules/bmm/_module-installer/installer.js +1 -1
  52. package/src/modules/bmm/_module-installer/platform-specifics/claude-code.js +1 -1
  53. package/src/modules/bmm/_module-installer/platform-specifics/windsurf.js +1 -1
  54. package/src/modules/cis/_module-installer/installer.js +1 -1
  55. package/tools/cli/README.md +4 -4
  56. package/tools/cli/installers/lib/core/config-collector.js +16 -8
  57. package/tools/cli/installers/lib/core/custom-module-cache.js +239 -0
  58. package/tools/cli/installers/lib/core/detector.js +8 -4
  59. package/tools/cli/installers/lib/core/installer.js +815 -23
  60. package/tools/cli/installers/lib/core/manifest-generator.js +176 -13
  61. package/tools/cli/installers/lib/core/manifest.js +47 -0
  62. package/tools/cli/installers/lib/custom/handler.js +150 -20
  63. package/tools/cli/installers/lib/modules/manager.js +78 -32
  64. package/tools/cli/lib/agent/compiler.js +3 -11
  65. package/tools/cli/lib/agent/installer.js +2 -1
  66. package/tools/cli/lib/cli-utils.js +21 -4
  67. package/tools/cli/lib/ui.js +499 -11
  68. package/tools/maintainer/review-pr-README.md +55 -0
  69. package/tools/maintainer/review-pr.md +242 -0
  70. package/tools/migrate-custom-module-paths.js +124 -0
  71. package/bmad-method-6.0.0-alpha.14.tgz +0 -0
  72. package/docs/custom-agent-installation.md +0 -137
  73. package/example-custom-content/workflows/quiz-master/workflow-plan-quiz-master.md +0 -269
  74. /package/src/core/{_module-installer/install-config.yaml → module.yaml} +0 -0
  75. /package/src/modules/bmb/{_module-installer/install-config.yaml → module.yaml} +0 -0
  76. /package/src/modules/bmb/workflows/create-module/templates/{install-config.template.yaml → module.template.yaml} +0 -0
  77. /package/src/modules/bmgd/{_module-installer/install-config.yaml → module.yaml} +0 -0
  78. /package/src/modules/bmm/{_module-installer/install-config.yaml → module.yaml} +0 -0
  79. /package/src/modules/cis/{_module-installer/install-config.yaml → module.yaml} +0 -0
@@ -37,7 +37,7 @@ Production-ready examples in `/src/modules/bmb/reference/agents/`:
37
37
 
38
38
  For installing standalone simple and expert agents, see:
39
39
 
40
- - [Custom Agent Installation](/docs/custom-agent-installation.md)
40
+ - [Custom Agent Installation](/docs/custom-content-installation.md)
41
41
 
42
42
  ## Key Concepts
43
43
 
@@ -113,10 +113,10 @@ For a [module type] module, we'll create this structure:"
113
113
  │ └── [template-files]
114
114
  ├── data/ # Module data files
115
115
  │ └── [data-files]
116
+ ├── module.yaml # Required
116
117
  ├── _module-installer/ # Installation configuration
117
- │ ├── install-config.yaml # Required
118
- ├── installer.js # Optional
119
- │ └── assets/ # Optional install assets
118
+ │ ├── installer.js # Optional
119
+ └── assets/ # Optional install assets
120
120
  └── README.md # Module documentation
121
121
  ```
122
122
 
@@ -184,7 +184,7 @@ Update module-plan.md with configuration section:
184
184
 
185
185
  ### Result Configuration Structure
186
186
 
187
- The install-config.yaml will generate:
187
+ The module.yaml will generate:
188
188
  - Module configuration at: {bmad_folder}/{module_code}/config.yaml
189
189
  - User settings stored as: [describe structure]
190
190
  ````
@@ -37,7 +37,7 @@ partyModeWorkflow: '{project-root}/{bmad_folder}/core/workflows/party-mode/workf
37
37
  ## EXECUTION PROTOCOLS:
38
38
 
39
39
  - 🎯 Use configuration plan from step 5
40
- - 💾 Create install-config.yaml with all fields
40
+ - 💾 Create module.yaml with all fields
41
41
  - 📖 Add "step-08-installer" to stepsCompleted array` before loading next step
42
42
  - 🚫 FORBIDDEN to load next step until user selects 'C'
43
43
 
@@ -50,7 +50,7 @@ partyModeWorkflow: '{project-root}/{bmad_folder}/core/workflows/party-mode/workf
50
50
 
51
51
  ## STEP GOAL:
52
52
 
53
- To create the module installer configuration (install-config.yaml) that defines how users will install and configure the module.
53
+ To create the module installer configuration (module.yaml) that defines how users will install and configure the module.
54
54
 
55
55
  ## INSTALLER SETUP PROCESS:
56
56
 
@@ -74,11 +74,11 @@ From step 5, we planned these configuration fields:
74
74
  Ensure \_module-installer directory exists
75
75
  Directory: {custom_module_location}/{module_name}/\_module-installer/
76
76
 
77
- ### 3. Create install-config.yaml
77
+ ### 3. Create module.yaml
78
78
 
79
- "I'll create the install-config.yaml file based on your configuration plan. This is the core installer configuration file."
79
+ "I'll create the module.yaml file based on your configuration plan. This is the core installer configuration file."
80
80
 
81
- Create file: {custom_module_location}/{module_name}/\_module-installer/install-config.yaml from template {installConfigTemplate}
81
+ Create file: {custom_module_location}/{module_name}/module.yaml from template {installConfigTemplate}
82
82
 
83
83
  ### 4. Handle Custom Installation Logic
84
84
 
@@ -117,7 +117,7 @@ Update module-plan.md with installer section:
117
117
 
118
118
  ### Install Configuration
119
119
 
120
- - File: \_module-installer/install-config.yaml
120
+ - File: module.yaml
121
121
  - Module code: {module_name}
122
122
  - Default selected: false
123
123
  - Configuration fields: [count]
@@ -166,7 +166,7 @@ Display: **Select an Option:** [A] Advanced Elicitation [P] Party Mode [C] Conti
166
166
 
167
167
  ### ✅ SUCCESS:
168
168
 
169
- - install-config.yaml created with all planned fields
169
+ - module.yaml created with all planned fields
170
170
  - YAML syntax valid
171
171
  - Custom installation logic prepared (if needed)
172
172
  - Installer follows BMAD standards
@@ -174,7 +174,7 @@ Display: **Select an Option:** [A] Advanced Elicitation [P] Party Mode [C] Conti
174
174
 
175
175
  ### ❌ SYSTEM FAILURE:
176
176
 
177
- - Not creating install-config.yaml
177
+ - Not creating module.yaml
178
178
  - Invalid YAML syntax
179
179
  - Missing required fields
180
180
  - Not using proper path templates
@@ -133,7 +133,8 @@ bmad install {module_name}
133
133
  ├── tasks/ # Task files
134
134
  ├── templates/ # Shared templates
135
135
  ├── data/ # Module data
136
- ├── _module-installer/ # Installation config
136
+ ├── _module-installer/ # Installation optional js file with custom install routine
137
+ ├── module.yaml # yaml config and install questions
137
138
  └── README.md # This file
138
139
  ```
139
140
 
@@ -207,9 +207,10 @@ workflow {workflow_name}
207
207
  ├── workflows/ # ✅ Structure created, plans written
208
208
  ├── tasks/ # ✅ Created
209
209
  ├── templates/ # ✅ Created
210
- ├── data/ # ✅ Created
210
+ ├── data/ # ✅ Created
211
211
  ├── _module-installer/ # ✅ Configured
212
- └── README.md # ✅ Complete
212
+ └── README.md # ✅ Complete
213
+ └── module.yaml # ✅ Complete
213
214
  ```
214
215
 
215
216
  ## Completion Criteria
@@ -73,8 +73,8 @@ Expected Structure:
73
73
  ├── templates/ [✅/❌]
74
74
  ├── data/ [✅/❌]
75
75
  ├── _module-installer/ [✅/❌]
76
- ├── install-config.yaml [✅/❌]
77
- │ └── installer.js [✅/N/A]
76
+ └── installer.js [✅/N/A]
77
+ ├── module.yaml [✅/❌]
78
78
  └── README.md [✅/❌]
79
79
  ```
80
80
 
@@ -87,7 +87,7 @@ Expected Structure:
87
87
  "**2. Configuration Files Check**"
88
88
 
89
89
  **Install Configuration:**
90
- Validate install-config.yaml
90
+ Validate module.yaml
91
91
 
92
92
  - [ ] YAML syntax valid
93
93
  - [ ] Module code matches folder name
@@ -6,7 +6,7 @@
6
6
  /**
7
7
  * @param {Object} options - Installation options
8
8
  * @param {string} options.projectRoot - Project root directory
9
- * @param {Object} options.config - Module configuration from install-config.yaml
9
+ * @param {Object} options.config - Module configuration from module.yaml
10
10
  * @param {Array} options.installedIDEs - List of IDE codes being configured
11
11
  * @param {Object} options.logger - Logger instance (log, warn, error methods)
12
12
  * @returns {boolean} - true if successful, false to abort installation
@@ -13,15 +13,15 @@ This document provides the validation criteria used in step-11-validate.md to en
13
13
  - [ ] data/ - Module data
14
14
  - [ ] \_module-installer/ - Installation config
15
15
  - [ ] README.md - Module documentation
16
+ - [ ] module.yaml - module config file
16
17
 
17
- ### Required Files in \_module-installer/
18
+ ### Optional File in \_module-installer/
18
19
 
19
- - [ ] install-config.yaml - Installation configuration
20
20
  - [ ] installer.js - Custom logic (if needed)
21
21
 
22
22
  ## Configuration Validation
23
23
 
24
- ### install-config.yaml
24
+ ### module.yaml
25
25
 
26
26
  - [ ] Valid YAML syntax
27
27
  - [ ] Module code matches folder name
@@ -98,7 +98,7 @@ After getting the workflow name:
98
98
  Based on the module selection, confirm the target location:
99
99
 
100
100
  - For bmb module: `{custom_workflow_location}` (defaults to `{bmad_folder}/custom/src/workflows`)
101
- - For other modules: Check their install-config.yaml for custom workflow locations
101
+ - For other modules: Check their module.yaml for custom workflow locations
102
102
  - Confirm the exact folder path where the workflow will be created
103
103
  - Store the confirmed path as `{targetWorkflowPath}`
104
104
 
@@ -109,7 +109,7 @@ Create the workflow folder structure in the target location:
109
109
  ```
110
110
 
111
111
  For bmb module, this will be: `{bmad_folder}/custom/src/workflows/{workflow_name}/`
112
- For other modules, check their install-config.yaml for custom_workflow_location
112
+ For other modules, check their module.yaml for custom_workflow_location
113
113
 
114
114
  ### 3. Generate workflow.md
115
115
 
@@ -129,8 +129,9 @@ bmgd/
129
129
  │ (Uses BMM workflows via cross-module references)
130
130
  ├── templates/
131
131
  ├── data/
132
+ ├── module.yaml
132
133
  └── _module-installer/
133
- └── install-config.yaml
134
+ └── installer.js (optional)
134
135
  ```
135
136
 
136
137
  ## Configuration
@@ -9,7 +9,7 @@ const platformCodes = require(path.join(__dirname, '../../../../tools/cli/lib/pl
9
9
  *
10
10
  * @param {Object} options - Installation options
11
11
  * @param {string} options.projectRoot - The root directory of the target project
12
- * @param {Object} options.config - Module configuration from install-config.yaml
12
+ * @param {Object} options.config - Module configuration from module.yaml
13
13
  * @param {Array<string>} options.installedIDEs - Array of IDE codes that were installed
14
14
  * @param {Object} options.logger - Logger instance for output
15
15
  * @returns {Promise<boolean>} - Success status
@@ -5,7 +5,7 @@ const chalk = require('chalk');
5
5
  *
6
6
  * @param {Object} options - Installation options
7
7
  * @param {string} options.projectRoot - The root directory of the target project
8
- * @param {Object} options.config - Module configuration from install-config.yaml
8
+ * @param {Object} options.config - Module configuration from module.yaml
9
9
  * @param {Object} options.logger - Logger instance for output
10
10
  * @param {Object} options.platformInfo - Platform metadata from global config
11
11
  * @returns {Promise<boolean>} - Success status
@@ -5,7 +5,7 @@ const chalk = require('chalk');
5
5
  *
6
6
  * @param {Object} options - Installation options
7
7
  * @param {string} options.projectRoot - The root directory of the target project
8
- * @param {Object} options.config - Module configuration from install-config.yaml
8
+ * @param {Object} options.config - Module configuration from module.yaml
9
9
  * @param {Object} options.logger - Logger instance for output
10
10
  * @returns {Promise<boolean>} - Success status
11
11
  */
@@ -8,7 +8,7 @@ const chalk = require('chalk');
8
8
  *
9
9
  * @param {Object} options - Installation options
10
10
  * @param {string} options.projectRoot - The root directory of the target project
11
- * @param {Object} options.config - Module configuration from install-config.yaml
11
+ * @param {Object} options.config - Module configuration from module.yaml
12
12
  * @param {Array<string>} options.installedIDEs - Array of IDE codes that were installed
13
13
  * @param {Object} options.logger - Logger instance for output
14
14
  * @returns {Promise<boolean>} - Success status
@@ -98,7 +98,7 @@ The installer is a multi-stage system that handles agent compilation, IDE integr
98
98
  ```
99
99
  1. Collect User Input
100
100
  - Target directory, modules, IDEs
101
- - Custom module configuration (via install-config.yaml)
101
+ - Custom module configuration (via module.yaml)
102
102
 
103
103
  2. Pre-Installation
104
104
  - Validate target, check conflicts, backup existing installations
@@ -183,12 +183,12 @@ The installer supports **15 IDE environments** through a base-derived architectu
183
183
 
184
184
  ### Custom Module Configuration
185
185
 
186
- Modules define interactive configuration menus via `install-config.yaml` files in their `_module-installer/` directories.
186
+ Modules define interactive configuration menus via `module.yaml` files in their `_module-installer/` directories.
187
187
 
188
188
  **Config File Location**:
189
189
 
190
- - Core: `src/core/_module-installer/install-config.yaml`
191
- - Modules: `src/modules/{module}/_module-installer/install-config.yaml`
190
+ - Core: `src/core/module.yaml`
191
+ - Modules: `src/modules/{module}/module.yaml`
192
192
 
193
193
  **Configuration Types**:
194
194
 
@@ -183,24 +183,28 @@ class ConfigCollector {
183
183
 
184
184
  // Load module's install config schema
185
185
  // First, try the standard src/modules location
186
- let installerConfigPath = path.join(getModulePath(moduleName), '_module-installer', 'install-config.yaml');
186
+ let installerConfigPath = path.join(getModulePath(moduleName), '_module-installer', 'module.yaml');
187
+ let moduleConfigPath = path.join(getModulePath(moduleName), 'module.yaml');
187
188
 
188
189
  // If not found in src/modules, we need to find it by searching the project
189
- if (!(await fs.pathExists(installerConfigPath))) {
190
+ if (!(await fs.pathExists(installerConfigPath)) && !(await fs.pathExists(moduleConfigPath))) {
190
191
  // Use the module manager to find the module source
191
192
  const { ModuleManager } = require('../modules/manager');
192
193
  const moduleManager = new ModuleManager();
193
194
  const moduleSourcePath = await moduleManager.findModuleSource(moduleName);
194
195
 
195
196
  if (moduleSourcePath) {
196
- installerConfigPath = path.join(moduleSourcePath, '_module-installer', 'install-config.yaml');
197
+ installerConfigPath = path.join(moduleSourcePath, '_module-installer', 'module.yaml');
198
+ moduleConfigPath = path.join(moduleSourcePath, 'module.yaml');
197
199
  }
198
200
  }
199
201
 
200
202
  let configPath = null;
201
203
  let isCustomModule = false;
202
204
 
203
- if (await fs.pathExists(installerConfigPath)) {
205
+ if (await fs.pathExists(moduleConfigPath)) {
206
+ configPath = moduleConfigPath;
207
+ } else if (await fs.pathExists(installerConfigPath)) {
204
208
  configPath = installerConfigPath;
205
209
  } else {
206
210
  // Check if this is a custom module with custom.yaml
@@ -448,22 +452,26 @@ class ConfigCollector {
448
452
  }
449
453
  // Load module's config
450
454
  // First, try the standard src/modules location
451
- let installerConfigPath = path.join(getModulePath(moduleName), '_module-installer', 'install-config.yaml');
455
+ let installerConfigPath = path.join(getModulePath(moduleName), '_module-installer', 'module.yaml');
456
+ let moduleConfigPath = path.join(getModulePath(moduleName), 'module.yaml');
452
457
 
453
458
  // If not found in src/modules, we need to find it by searching the project
454
- if (!(await fs.pathExists(installerConfigPath))) {
459
+ if (!(await fs.pathExists(installerConfigPath)) && !(await fs.pathExists(moduleConfigPath))) {
455
460
  // Use the module manager to find the module source
456
461
  const { ModuleManager } = require('../modules/manager');
457
462
  const moduleManager = new ModuleManager();
458
463
  const moduleSourcePath = await moduleManager.findModuleSource(moduleName);
459
464
 
460
465
  if (moduleSourcePath) {
461
- installerConfigPath = path.join(moduleSourcePath, '_module-installer', 'install-config.yaml');
466
+ installerConfigPath = path.join(moduleSourcePath, '_module-installer', 'module.yaml');
467
+ moduleConfigPath = path.join(moduleSourcePath, 'module.yaml');
462
468
  }
463
469
  }
464
470
 
465
471
  let configPath = null;
466
- if (await fs.pathExists(installerConfigPath)) {
472
+ if (await fs.pathExists(moduleConfigPath)) {
473
+ configPath = moduleConfigPath;
474
+ } else if (await fs.pathExists(installerConfigPath)) {
467
475
  configPath = installerConfigPath;
468
476
  } else {
469
477
  // No config for this module
@@ -0,0 +1,239 @@
1
+ /**
2
+ * Custom Module Source Cache
3
+ * Caches custom module sources under _cfg/custom/ to ensure they're never lost
4
+ * and can be checked into source control
5
+ */
6
+
7
+ const fs = require('fs-extra');
8
+ const path = require('node:path');
9
+ const crypto = require('node:crypto');
10
+
11
+ class CustomModuleCache {
12
+ constructor(bmadDir) {
13
+ this.bmadDir = bmadDir;
14
+ this.customCacheDir = path.join(bmadDir, '_cfg', 'custom');
15
+ this.manifestPath = path.join(this.customCacheDir, 'cache-manifest.yaml');
16
+ }
17
+
18
+ /**
19
+ * Ensure the custom cache directory exists
20
+ */
21
+ async ensureCacheDir() {
22
+ await fs.ensureDir(this.customCacheDir);
23
+ }
24
+
25
+ /**
26
+ * Get cache manifest
27
+ */
28
+ async getCacheManifest() {
29
+ if (!(await fs.pathExists(this.manifestPath))) {
30
+ return {};
31
+ }
32
+
33
+ const content = await fs.readFile(this.manifestPath, 'utf8');
34
+ const yaml = require('js-yaml');
35
+ return yaml.load(content) || {};
36
+ }
37
+
38
+ /**
39
+ * Update cache manifest
40
+ */
41
+ async updateCacheManifest(manifest) {
42
+ const yaml = require('js-yaml');
43
+ const content = yaml.dump(manifest, {
44
+ indent: 2,
45
+ lineWidth: -1,
46
+ noRefs: true,
47
+ sortKeys: false,
48
+ });
49
+
50
+ await fs.writeFile(this.manifestPath, content);
51
+ }
52
+
53
+ /**
54
+ * Calculate hash of a file or directory
55
+ */
56
+ async calculateHash(sourcePath) {
57
+ const hash = crypto.createHash('sha256');
58
+
59
+ const isDir = (await fs.stat(sourcePath)).isDirectory();
60
+
61
+ if (isDir) {
62
+ // For directories, hash all files
63
+ const files = [];
64
+ async function collectFiles(dir) {
65
+ const entries = await fs.readdir(dir, { withFileTypes: true });
66
+ for (const entry of entries) {
67
+ if (entry.isFile()) {
68
+ files.push(path.join(dir, entry.name));
69
+ } else if (entry.isDirectory() && !entry.name.startsWith('.')) {
70
+ await collectFiles(path.join(dir, entry.name));
71
+ }
72
+ }
73
+ }
74
+
75
+ await collectFiles(sourcePath);
76
+ files.sort(); // Ensure consistent order
77
+
78
+ for (const file of files) {
79
+ const content = await fs.readFile(file);
80
+ const relativePath = path.relative(sourcePath, file);
81
+ hash.update(relativePath + '|' + content.toString('base64'));
82
+ }
83
+ } else {
84
+ // For single files
85
+ const content = await fs.readFile(sourcePath);
86
+ hash.update(content);
87
+ }
88
+
89
+ return hash.digest('hex');
90
+ }
91
+
92
+ /**
93
+ * Cache a custom module source
94
+ * @param {string} moduleId - Module ID
95
+ * @param {string} sourcePath - Original source path
96
+ * @param {Object} metadata - Additional metadata to store
97
+ * @returns {Object} Cached module info
98
+ */
99
+ async cacheModule(moduleId, sourcePath, metadata = {}) {
100
+ await this.ensureCacheDir();
101
+
102
+ const cacheDir = path.join(this.customCacheDir, moduleId);
103
+ const cacheManifest = await this.getCacheManifest();
104
+
105
+ // Check if already cached and unchanged
106
+ if (cacheManifest[moduleId]) {
107
+ const cached = cacheManifest[moduleId];
108
+ if (cached.originalHash && cached.originalHash === (await this.calculateHash(sourcePath))) {
109
+ // Source unchanged, return existing cache info
110
+ return {
111
+ moduleId,
112
+ cachePath: cacheDir,
113
+ ...cached,
114
+ };
115
+ }
116
+ }
117
+
118
+ // Remove existing cache if it exists
119
+ if (await fs.pathExists(cacheDir)) {
120
+ await fs.remove(cacheDir);
121
+ }
122
+
123
+ // Copy module to cache
124
+ await fs.copy(sourcePath, cacheDir, {
125
+ filter: (src) => {
126
+ const relative = path.relative(sourcePath, src);
127
+ // Skip node_modules, .git, and other common ignore patterns
128
+ return !relative.includes('node_modules') && !relative.startsWith('.git') && !relative.startsWith('.DS_Store');
129
+ },
130
+ });
131
+
132
+ // Calculate hash of the source
133
+ const sourceHash = await this.calculateHash(sourcePath);
134
+ const cacheHash = await this.calculateHash(cacheDir);
135
+
136
+ // Update manifest - don't store originalPath for source control friendliness
137
+ cacheManifest[moduleId] = {
138
+ originalHash: sourceHash,
139
+ cacheHash: cacheHash,
140
+ cachedAt: new Date().toISOString(),
141
+ ...metadata,
142
+ };
143
+
144
+ await this.updateCacheManifest(cacheManifest);
145
+
146
+ return {
147
+ moduleId,
148
+ cachePath: cacheDir,
149
+ ...cacheManifest[moduleId],
150
+ };
151
+ }
152
+
153
+ /**
154
+ * Get cached module info
155
+ * @param {string} moduleId - Module ID
156
+ * @returns {Object|null} Cached module info or null
157
+ */
158
+ async getCachedModule(moduleId) {
159
+ const cacheManifest = await this.getCacheManifest();
160
+ const cached = cacheManifest[moduleId];
161
+
162
+ if (!cached) {
163
+ return null;
164
+ }
165
+
166
+ const cacheDir = path.join(this.customCacheDir, moduleId);
167
+
168
+ if (!(await fs.pathExists(cacheDir))) {
169
+ // Cache dir missing, remove from manifest
170
+ delete cacheManifest[moduleId];
171
+ await this.updateCacheManifest(cacheManifest);
172
+ return null;
173
+ }
174
+
175
+ // Verify cache integrity
176
+ const currentCacheHash = await this.calculateHash(cacheDir);
177
+ if (currentCacheHash !== cached.cacheHash) {
178
+ console.warn(`Warning: Cache integrity check failed for ${moduleId}`);
179
+ }
180
+
181
+ return {
182
+ moduleId,
183
+ cachePath: cacheDir,
184
+ ...cached,
185
+ };
186
+ }
187
+
188
+ /**
189
+ * Get all cached modules
190
+ * @returns {Array} Array of cached module info
191
+ */
192
+ async getAllCachedModules() {
193
+ const cacheManifest = await this.getCacheManifest();
194
+ const cached = [];
195
+
196
+ for (const [moduleId, info] of Object.entries(cacheManifest)) {
197
+ const cachedModule = await this.getCachedModule(moduleId);
198
+ if (cachedModule) {
199
+ cached.push(cachedModule);
200
+ }
201
+ }
202
+
203
+ return cached;
204
+ }
205
+
206
+ /**
207
+ * Remove a cached module
208
+ * @param {string} moduleId - Module ID to remove
209
+ */
210
+ async removeCachedModule(moduleId) {
211
+ const cacheManifest = await this.getCacheManifest();
212
+ const cacheDir = path.join(this.customCacheDir, moduleId);
213
+
214
+ // Remove cache directory
215
+ if (await fs.pathExists(cacheDir)) {
216
+ await fs.remove(cacheDir);
217
+ }
218
+
219
+ // Remove from manifest
220
+ delete cacheManifest[moduleId];
221
+ await this.updateCacheManifest(cacheManifest);
222
+ }
223
+
224
+ /**
225
+ * Sync cached modules with a list of module IDs
226
+ * @param {Array<string>} moduleIds - Module IDs to keep
227
+ */
228
+ async syncCache(moduleIds) {
229
+ const cached = await this.getAllCachedModules();
230
+
231
+ for (const cachedModule of cached) {
232
+ if (!moduleIds.includes(cachedModule.moduleId)) {
233
+ await this.removeCachedModule(cachedModule.moduleId);
234
+ }
235
+ }
236
+ }
237
+ }
238
+
239
+ module.exports = { CustomModuleCache };
@@ -17,6 +17,7 @@ class Detector {
17
17
  hasCore: false,
18
18
  modules: [],
19
19
  ides: [],
20
+ customModules: [],
20
21
  manifest: null,
21
22
  };
22
23
 
@@ -32,6 +33,10 @@ class Detector {
32
33
  result.manifest = manifestData;
33
34
  result.version = manifestData.version;
34
35
  result.installed = true;
36
+ // Copy custom modules if they exist
37
+ if (manifestData.customModules) {
38
+ result.customModules = manifestData.customModules;
39
+ }
35
40
  }
36
41
 
37
42
  // Check for core
@@ -275,10 +280,9 @@ class Detector {
275
280
  hasV6Installation = true;
276
281
  // Don't break - continue scanning to be thorough
277
282
  } else {
278
- // Not V6+, check if folder name contains "bmad" (case insensitive)
279
- const nameLower = name.toLowerCase();
280
- if (nameLower.includes('bmad')) {
281
- // Potential V4 legacy folder
283
+ // Not V6+, check if this is the exact V4 folder name "bmad-method"
284
+ if (name === 'bmad-method') {
285
+ // This is the V4 default folder - flag it as legacy
282
286
  potentialV4Folders.push(fullPath);
283
287
  }
284
288
  }