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.
- package/.coderabbit.yaml +36 -0
- package/{CODE_OF_CONDUCT.md → .github/CODE_OF_CONDUCT.md} +4 -4
- package/CHANGELOG.md +136 -408
- package/README.md +4 -1
- package/docs/custom-content-installation.md +245 -0
- package/docs/index.md +2 -2
- package/docs/installers-bundlers/installers-modules-platforms-reference.md +6 -5
- package/docs/web-bundles-gemini-gpt-guide.md +1 -1
- package/example-custom-content/README.md +4 -0
- package/example-custom-content/agents/commit-poet/commit-poet.agent.yaml +1 -1
- package/example-custom-content/agents/toolsmith/toolsmith-sidecar/instructions.md +1 -1
- package/example-custom-content/agents/toolsmith/toolsmith-sidecar/knowledge/docs.md +1 -1
- package/example-custom-content/agents/toolsmith/toolsmith-sidecar/knowledge/installers.md +1 -1
- package/example-custom-content/agents/toolsmith/toolsmith-sidecar/knowledge/modules.md +2 -2
- package/example-custom-content/agents/toolsmith/toolsmith.agent.yaml +1 -1
- package/example-custom-content/{custom.yaml → module.yaml} +1 -0
- package/example-custom-content/workflows/quiz-master/steps/step-01-init.md +2 -2
- package/example-custom-content/workflows/quiz-master/steps/step-02-q1.md +1 -1
- package/example-custom-content/workflows/quiz-master/steps/step-03-q2.md +1 -1
- package/example-custom-content/workflows/quiz-master/steps/step-04-q3.md +1 -1
- package/example-custom-content/workflows/quiz-master/steps/step-05-q4.md +1 -1
- package/example-custom-content/workflows/quiz-master/steps/step-06-q5.md +1 -1
- package/example-custom-content/workflows/quiz-master/steps/step-07-q6.md +1 -1
- package/example-custom-content/workflows/quiz-master/steps/step-08-q7.md +1 -1
- package/example-custom-content/workflows/quiz-master/steps/step-09-q8.md +1 -1
- package/example-custom-content/workflows/quiz-master/steps/step-10-q9.md +1 -1
- package/example-custom-content/workflows/quiz-master/steps/step-11-q10.md +1 -1
- package/example-custom-content/workflows/quiz-master/steps/step-12-results.md +1 -1
- package/example-custom-content/workflows/quiz-master/workflow.md +1 -1
- package/example-custom-module/mwm/README.md +5 -0
- package/example-custom-module/mwm/agents/cbt-coach/cbt-coach.agent.yaml +1 -0
- package/example-custom-module/mwm/agents/crisis-navigator.agent.yaml +3 -2
- package/example-custom-module/mwm/agents/meditation-guide.agent.yaml +3 -2
- package/example-custom-module/mwm/agents/wellness-companion/wellness-companion.agent.yaml +1 -0
- package/example-custom-module/mwm/{_module-installer/install-config.yaml → module.yaml} +1 -0
- package/package.json +1 -1
- package/src/core/_module-installer/installer.js +1 -1
- package/src/modules/bmb/_module-installer/installer.js +1 -1
- package/src/modules/bmb/docs/agents/index.md +1 -1
- package/src/modules/bmb/workflows/create-module/steps/step-04-structure.md +3 -3
- package/src/modules/bmb/workflows/create-module/steps/step-05-config.md +1 -1
- package/src/modules/bmb/workflows/create-module/steps/step-08-installer.md +8 -8
- package/src/modules/bmb/workflows/create-module/steps/step-09-documentation.md +2 -1
- package/src/modules/bmb/workflows/create-module/steps/step-10-roadmap.md +3 -2
- package/src/modules/bmb/workflows/create-module/steps/step-11-validate.md +3 -3
- package/src/modules/bmb/workflows/create-module/templates/installer.template.js +1 -1
- package/src/modules/bmb/workflows/create-module/validation.md +3 -3
- package/src/modules/bmb/workflows/create-workflow/steps/step-01-init.md +1 -1
- package/src/modules/bmb/workflows/create-workflow/steps/step-07-build.md +1 -1
- package/src/modules/bmgd/README.md +2 -1
- package/src/modules/bmm/_module-installer/installer.js +1 -1
- package/src/modules/bmm/_module-installer/platform-specifics/claude-code.js +1 -1
- package/src/modules/bmm/_module-installer/platform-specifics/windsurf.js +1 -1
- package/src/modules/cis/_module-installer/installer.js +1 -1
- package/tools/cli/README.md +4 -4
- package/tools/cli/installers/lib/core/config-collector.js +16 -8
- package/tools/cli/installers/lib/core/custom-module-cache.js +239 -0
- package/tools/cli/installers/lib/core/detector.js +8 -4
- package/tools/cli/installers/lib/core/installer.js +815 -23
- package/tools/cli/installers/lib/core/manifest-generator.js +176 -13
- package/tools/cli/installers/lib/core/manifest.js +47 -0
- package/tools/cli/installers/lib/custom/handler.js +150 -20
- package/tools/cli/installers/lib/modules/manager.js +78 -32
- package/tools/cli/lib/agent/compiler.js +3 -11
- package/tools/cli/lib/agent/installer.js +2 -1
- package/tools/cli/lib/cli-utils.js +21 -4
- package/tools/cli/lib/ui.js +499 -11
- package/tools/maintainer/review-pr-README.md +55 -0
- package/tools/maintainer/review-pr.md +242 -0
- package/tools/migrate-custom-module-paths.js +124 -0
- package/bmad-method-6.0.0-alpha.14.tgz +0 -0
- package/docs/custom-agent-installation.md +0 -137
- package/example-custom-content/workflows/quiz-master/workflow-plan-quiz-master.md +0 -269
- /package/src/core/{_module-installer/install-config.yaml → module.yaml} +0 -0
- /package/src/modules/bmb/{_module-installer/install-config.yaml → module.yaml} +0 -0
- /package/src/modules/bmb/workflows/create-module/templates/{install-config.template.yaml → module.template.yaml} +0 -0
- /package/src/modules/bmgd/{_module-installer/install-config.yaml → module.yaml} +0 -0
- /package/src/modules/bmm/{_module-installer/install-config.yaml → module.yaml} +0 -0
- /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-
|
|
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
|
-
│ ├──
|
|
118
|
-
│
|
|
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
|
|
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
|
|
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 (
|
|
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
|
|
77
|
+
### 3. Create module.yaml
|
|
78
78
|
|
|
79
|
-
"I'll create the
|
|
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}
|
|
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:
|
|
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
|
-
-
|
|
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
|
|
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
|
|
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/
|
|
210
|
+
├── data/ # ✅ Created
|
|
211
211
|
├── _module-installer/ # ✅ Configured
|
|
212
|
-
└── README.md
|
|
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
|
-
│
|
|
77
|
-
|
|
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
|
|
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
|
|
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
|
-
###
|
|
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
|
-
###
|
|
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
|
|
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
|
|
112
|
+
For other modules, check their module.yaml for custom_workflow_location
|
|
113
113
|
|
|
114
114
|
### 3. Generate workflow.md
|
|
115
115
|
|
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
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
|
package/tools/cli/README.md
CHANGED
|
@@ -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
|
|
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 `
|
|
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/
|
|
191
|
-
- Modules: `src/modules/{module}/
|
|
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', '
|
|
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', '
|
|
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(
|
|
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', '
|
|
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', '
|
|
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(
|
|
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
|
|
279
|
-
|
|
280
|
-
|
|
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
|
}
|