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

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 (127) hide show
  1. package/.claude-plugin/marketplace.json +0 -4
  2. package/README.md +8 -9
  3. package/README_CN.md +1 -1
  4. package/README_VN.md +110 -0
  5. package/package.json +2 -1
  6. package/removals.txt +17 -0
  7. package/src/bmm-skills/1-analysis/bmad-agent-analyst/SKILL.md +7 -4
  8. package/src/bmm-skills/1-analysis/bmad-agent-tech-writer/SKILL.md +6 -4
  9. package/src/bmm-skills/1-analysis/bmad-document-project/workflow.md +8 -10
  10. package/src/bmm-skills/1-analysis/bmad-prfaq/SKILL.md +96 -0
  11. package/src/bmm-skills/1-analysis/bmad-prfaq/agents/artifact-analyzer.md +60 -0
  12. package/src/bmm-skills/1-analysis/bmad-prfaq/agents/web-researcher.md +49 -0
  13. package/src/bmm-skills/1-analysis/bmad-prfaq/assets/prfaq-template.md +62 -0
  14. package/src/bmm-skills/1-analysis/bmad-prfaq/bmad-manifest.json +16 -0
  15. package/src/bmm-skills/1-analysis/bmad-prfaq/references/customer-faq.md +55 -0
  16. package/src/bmm-skills/1-analysis/bmad-prfaq/references/internal-faq.md +51 -0
  17. package/src/bmm-skills/1-analysis/bmad-prfaq/references/press-release.md +60 -0
  18. package/src/bmm-skills/1-analysis/bmad-prfaq/references/verdict.md +79 -0
  19. package/src/bmm-skills/1-analysis/bmad-product-brief/SKILL.md +1 -6
  20. package/src/bmm-skills/1-analysis/bmad-product-brief/bmad-manifest.json +1 -1
  21. package/src/bmm-skills/1-analysis/research/bmad-domain-research/workflow.md +8 -6
  22. package/src/bmm-skills/1-analysis/research/bmad-market-research/workflow.md +8 -6
  23. package/src/bmm-skills/1-analysis/research/bmad-technical-research/workflow.md +8 -6
  24. package/src/bmm-skills/2-plan-workflows/bmad-agent-pm/SKILL.md +6 -4
  25. package/src/bmm-skills/2-plan-workflows/bmad-agent-ux-designer/SKILL.md +6 -4
  26. package/src/bmm-skills/2-plan-workflows/bmad-create-prd/workflow.md +8 -9
  27. package/src/bmm-skills/2-plan-workflows/bmad-create-ux-design/steps/step-13-responsive-accessibility.md +1 -1
  28. package/src/bmm-skills/2-plan-workflows/bmad-create-ux-design/workflow.md +8 -9
  29. package/src/bmm-skills/2-plan-workflows/bmad-edit-prd/steps-e/step-e-01-discovery.md +1 -1
  30. package/src/bmm-skills/2-plan-workflows/bmad-edit-prd/steps-e/step-e-01b-legacy-conversion.md +1 -1
  31. package/src/bmm-skills/2-plan-workflows/bmad-edit-prd/steps-e/step-e-02-review.md +1 -1
  32. package/src/bmm-skills/2-plan-workflows/bmad-edit-prd/steps-e/step-e-03-edit.md +1 -1
  33. package/src/bmm-skills/2-plan-workflows/bmad-edit-prd/steps-e/step-e-04-complete.md +1 -3
  34. package/src/bmm-skills/2-plan-workflows/bmad-edit-prd/workflow.md +8 -9
  35. package/src/bmm-skills/2-plan-workflows/bmad-validate-prd/workflow.md +8 -9
  36. package/src/bmm-skills/3-solutioning/bmad-agent-architect/SKILL.md +6 -4
  37. package/src/bmm-skills/3-solutioning/bmad-check-implementation-readiness/steps/step-01-document-discovery.md +1 -1
  38. package/src/bmm-skills/3-solutioning/bmad-check-implementation-readiness/steps/step-02-prd-analysis.md +1 -1
  39. package/src/bmm-skills/3-solutioning/bmad-check-implementation-readiness/steps/step-03-epic-coverage-validation.md +1 -1
  40. package/src/bmm-skills/3-solutioning/bmad-check-implementation-readiness/workflow.md +9 -11
  41. package/src/bmm-skills/3-solutioning/bmad-create-architecture/workflow.md +8 -14
  42. package/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/workflow.md +10 -12
  43. package/src/bmm-skills/3-solutioning/bmad-generate-project-context/workflow.md +8 -12
  44. package/src/bmm-skills/4-implementation/bmad-agent-dev/SKILL.md +11 -4
  45. package/src/bmm-skills/4-implementation/bmad-checkpoint-preview/SKILL.md +29 -0
  46. package/src/bmm-skills/4-implementation/bmad-checkpoint-preview/generate-trail.md +38 -0
  47. package/src/bmm-skills/4-implementation/bmad-checkpoint-preview/step-01-orientation.md +105 -0
  48. package/src/bmm-skills/4-implementation/bmad-checkpoint-preview/step-02-walkthrough.md +89 -0
  49. package/src/bmm-skills/4-implementation/bmad-checkpoint-preview/step-03-detail-pass.md +106 -0
  50. package/src/bmm-skills/4-implementation/bmad-checkpoint-preview/step-04-testing.md +74 -0
  51. package/src/bmm-skills/4-implementation/bmad-checkpoint-preview/step-05-wrapup.md +24 -0
  52. package/src/bmm-skills/4-implementation/bmad-code-review/steps/step-01-gather-context.md +38 -15
  53. package/src/bmm-skills/4-implementation/bmad-correct-course/checklist.md +2 -2
  54. package/src/bmm-skills/4-implementation/bmad-correct-course/workflow.md +8 -8
  55. package/src/bmm-skills/4-implementation/bmad-qa-generate-e2e-tests/checklist.md +1 -1
  56. package/src/bmm-skills/4-implementation/bmad-quick-dev/compile-epic-context.md +62 -0
  57. package/src/bmm-skills/4-implementation/bmad-quick-dev/spec-template.md +1 -1
  58. package/src/bmm-skills/4-implementation/bmad-quick-dev/step-01-clarify-and-route.md +33 -6
  59. package/src/bmm-skills/4-implementation/bmad-quick-dev/step-02-plan.md +20 -8
  60. package/src/bmm-skills/4-implementation/bmad-quick-dev/step-03-implement.md +2 -0
  61. package/src/bmm-skills/4-implementation/bmad-quick-dev/step-oneshot.md +16 -4
  62. package/src/bmm-skills/4-implementation/bmad-quick-dev/workflow.md +1 -5
  63. package/src/bmm-skills/4-implementation/bmad-retrospective/workflow.md +134 -134
  64. package/src/bmm-skills/4-implementation/bmad-sprint-planning/sprint-status-template.yaml +1 -1
  65. package/src/bmm-skills/4-implementation/bmad-sprint-planning/workflow.md +3 -3
  66. package/src/bmm-skills/4-implementation/bmad-sprint-status/workflow.md +2 -2
  67. package/src/bmm-skills/module-help.csv +4 -1
  68. package/src/core-skills/bmad-advanced-elicitation/SKILL.md +1 -2
  69. package/src/core-skills/bmad-distillator/SKILL.md +0 -1
  70. package/src/core-skills/bmad-distillator/resources/distillate-format-reference.md +9 -9
  71. package/src/core-skills/bmad-help/SKILL.md +4 -2
  72. package/src/core-skills/bmad-party-mode/SKILL.md +121 -2
  73. package/src/core-skills/module-help.csv +1 -0
  74. package/tools/installer/cli-utils.js +18 -9
  75. package/tools/installer/commands/install.js +0 -1
  76. package/tools/installer/core/existing-install.js +2 -8
  77. package/tools/installer/core/install-paths.js +0 -3
  78. package/tools/installer/core/installer.js +176 -464
  79. package/tools/installer/core/manifest-generator.js +4 -12
  80. package/tools/installer/core/manifest.js +82 -97
  81. package/tools/installer/ide/_config-driven.js +149 -38
  82. package/tools/installer/ide/platform-codes.yaml +6 -4
  83. package/tools/installer/ide/shared/skill-manifest.js +1 -16
  84. package/tools/installer/install-messages.yaml +19 -26
  85. package/tools/installer/modules/community-manager.js +377 -0
  86. package/tools/installer/modules/custom-module-manager.js +308 -0
  87. package/tools/installer/modules/external-manager.js +65 -49
  88. package/tools/installer/modules/official-modules.js +37 -65
  89. package/tools/installer/modules/registry-client.js +66 -0
  90. package/tools/installer/{external-official-modules.yaml → modules/registry-fallback.yaml} +3 -12
  91. package/tools/installer/ui.js +340 -672
  92. package/tools/platform-codes.yaml +6 -0
  93. package/src/bmm-skills/2-plan-workflows/create-prd/data/domain-complexity.csv +0 -15
  94. package/src/bmm-skills/2-plan-workflows/create-prd/data/project-types.csv +0 -11
  95. package/src/bmm-skills/2-plan-workflows/create-prd/steps-v/step-v-01-discovery.md +0 -224
  96. package/src/bmm-skills/2-plan-workflows/create-prd/steps-v/step-v-02-format-detection.md +0 -191
  97. package/src/bmm-skills/2-plan-workflows/create-prd/steps-v/step-v-02b-parity-check.md +0 -209
  98. package/src/bmm-skills/2-plan-workflows/create-prd/steps-v/step-v-03-density-validation.md +0 -174
  99. package/src/bmm-skills/2-plan-workflows/create-prd/steps-v/step-v-04-brief-coverage-validation.md +0 -214
  100. package/src/bmm-skills/2-plan-workflows/create-prd/steps-v/step-v-05-measurability-validation.md +0 -228
  101. package/src/bmm-skills/2-plan-workflows/create-prd/steps-v/step-v-06-traceability-validation.md +0 -217
  102. package/src/bmm-skills/2-plan-workflows/create-prd/steps-v/step-v-07-implementation-leakage-validation.md +0 -205
  103. package/src/bmm-skills/2-plan-workflows/create-prd/steps-v/step-v-08-domain-compliance-validation.md +0 -243
  104. package/src/bmm-skills/2-plan-workflows/create-prd/steps-v/step-v-09-project-type-validation.md +0 -263
  105. package/src/bmm-skills/2-plan-workflows/create-prd/steps-v/step-v-10-smart-validation.md +0 -209
  106. package/src/bmm-skills/2-plan-workflows/create-prd/steps-v/step-v-11-holistic-quality-validation.md +0 -264
  107. package/src/bmm-skills/2-plan-workflows/create-prd/steps-v/step-v-12-completeness-validation.md +0 -242
  108. package/src/bmm-skills/2-plan-workflows/create-prd/steps-v/step-v-13-report-complete.md +0 -232
  109. package/src/bmm-skills/2-plan-workflows/create-prd/workflow-validate-prd.md +0 -65
  110. package/src/bmm-skills/4-implementation/bmad-agent-qa/SKILL.md +0 -59
  111. package/src/bmm-skills/4-implementation/bmad-agent-qa/bmad-skill-manifest.yaml +0 -11
  112. package/src/bmm-skills/4-implementation/bmad-agent-quick-flow-solo-dev/SKILL.md +0 -51
  113. package/src/bmm-skills/4-implementation/bmad-agent-quick-flow-solo-dev/bmad-skill-manifest.yaml +0 -11
  114. package/src/bmm-skills/4-implementation/bmad-agent-sm/SKILL.md +0 -53
  115. package/src/bmm-skills/4-implementation/bmad-agent-sm/bmad-skill-manifest.yaml +0 -11
  116. package/src/core-skills/bmad-init/SKILL.md +0 -100
  117. package/src/core-skills/bmad-init/resources/core-module.yaml +0 -25
  118. package/src/core-skills/bmad-init/scripts/bmad_init.py +0 -624
  119. package/src/core-skills/bmad-init/scripts/tests/test_bmad_init.py +0 -393
  120. package/src/core-skills/bmad-party-mode/steps/step-01-agent-loading.md +0 -138
  121. package/src/core-skills/bmad-party-mode/steps/step-02-discussion-orchestration.md +0 -187
  122. package/src/core-skills/bmad-party-mode/steps/step-03-graceful-exit.md +0 -167
  123. package/src/core-skills/bmad-party-mode/workflow.md +0 -190
  124. package/tools/installer/core/custom-module-cache.js +0 -260
  125. package/tools/installer/custom-handler.js +0 -112
  126. package/tools/installer/modules/custom-modules.js +0 -197
  127. /package/src/bmm-skills/2-plan-workflows/{create-prd → bmad-edit-prd}/data/prd-purpose.md +0 -0
@@ -1,260 +0,0 @@
1
- /**
2
- * Custom Module Source Cache
3
- * Caches custom module sources under _config/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
- const prompts = require('../prompts');
11
-
12
- class CustomModuleCache {
13
- constructor(bmadDir) {
14
- this.bmadDir = bmadDir;
15
- this.customCacheDir = path.join(bmadDir, '_config', 'custom');
16
- this.manifestPath = path.join(this.customCacheDir, 'cache-manifest.yaml');
17
- }
18
-
19
- /**
20
- * Ensure the custom cache directory exists
21
- */
22
- async ensureCacheDir() {
23
- await fs.ensureDir(this.customCacheDir);
24
- }
25
-
26
- /**
27
- * Get cache manifest
28
- */
29
- async getCacheManifest() {
30
- if (!(await fs.pathExists(this.manifestPath))) {
31
- return {};
32
- }
33
-
34
- const content = await fs.readFile(this.manifestPath, 'utf8');
35
- const yaml = require('yaml');
36
- return yaml.parse(content) || {};
37
- }
38
-
39
- /**
40
- * Update cache manifest
41
- */
42
- async updateCacheManifest(manifest) {
43
- const yaml = require('yaml');
44
- // Clean the manifest to remove any non-serializable values
45
- const cleanManifest = structuredClone(manifest);
46
-
47
- const content = yaml.stringify(cleanManifest, {
48
- indent: 2,
49
- lineWidth: 0,
50
- sortKeys: false,
51
- });
52
-
53
- await fs.writeFile(this.manifestPath, content);
54
- }
55
-
56
- /**
57
- * Stream a file into the hash to avoid loading entire file into memory
58
- */
59
- async hashFileStream(filePath, hash) {
60
- return new Promise((resolve, reject) => {
61
- const stream = require('node:fs').createReadStream(filePath);
62
- stream.on('data', (chunk) => hash.update(chunk));
63
- stream.on('end', resolve);
64
- stream.on('error', reject);
65
- });
66
- }
67
-
68
- /**
69
- * Calculate hash of a file or directory using streaming to minimize memory usage
70
- */
71
- async calculateHash(sourcePath) {
72
- const hash = crypto.createHash('sha256');
73
-
74
- const isDir = (await fs.stat(sourcePath)).isDirectory();
75
-
76
- if (isDir) {
77
- // For directories, hash all files
78
- const files = [];
79
- async function collectFiles(dir) {
80
- const entries = await fs.readdir(dir, { withFileTypes: true });
81
- for (const entry of entries) {
82
- if (entry.isFile()) {
83
- files.push(path.join(dir, entry.name));
84
- } else if (entry.isDirectory() && !entry.name.startsWith('.')) {
85
- await collectFiles(path.join(dir, entry.name));
86
- }
87
- }
88
- }
89
-
90
- await collectFiles(sourcePath);
91
- files.sort(); // Ensure consistent order
92
-
93
- for (const file of files) {
94
- const relativePath = path.relative(sourcePath, file);
95
- // Hash the path first, then stream file contents
96
- hash.update(relativePath + '|');
97
- await this.hashFileStream(file, hash);
98
- }
99
- } else {
100
- // For single files, stream directly into hash
101
- await this.hashFileStream(sourcePath, hash);
102
- }
103
-
104
- return hash.digest('hex');
105
- }
106
-
107
- /**
108
- * Cache a custom module source
109
- * @param {string} moduleId - Module ID
110
- * @param {string} sourcePath - Original source path
111
- * @param {Object} metadata - Additional metadata to store
112
- * @returns {Object} Cached module info
113
- */
114
- async cacheModule(moduleId, sourcePath, metadata = {}) {
115
- await this.ensureCacheDir();
116
-
117
- const cacheDir = path.join(this.customCacheDir, moduleId);
118
- const cacheManifest = await this.getCacheManifest();
119
-
120
- // Check if already cached and unchanged
121
- if (cacheManifest[moduleId]) {
122
- const cached = cacheManifest[moduleId];
123
- if (cached.originalHash && cached.originalHash === (await this.calculateHash(sourcePath))) {
124
- // Source unchanged, return existing cache info
125
- return {
126
- moduleId,
127
- cachePath: cacheDir,
128
- ...cached,
129
- };
130
- }
131
- }
132
-
133
- // Remove existing cache if it exists
134
- if (await fs.pathExists(cacheDir)) {
135
- await fs.remove(cacheDir);
136
- }
137
-
138
- // Copy module to cache
139
- await fs.copy(sourcePath, cacheDir, {
140
- filter: (src) => {
141
- const relative = path.relative(sourcePath, src);
142
- // Skip node_modules, .git, and other common ignore patterns
143
- return !relative.includes('node_modules') && !relative.startsWith('.git') && !relative.startsWith('.DS_Store');
144
- },
145
- });
146
-
147
- // Calculate hash of the source
148
- const sourceHash = await this.calculateHash(sourcePath);
149
- const cacheHash = await this.calculateHash(cacheDir);
150
-
151
- // Update manifest - don't store absolute paths for portability
152
- // Clean metadata to remove absolute paths
153
- const cleanMetadata = { ...metadata };
154
- if (cleanMetadata.sourcePath) {
155
- delete cleanMetadata.sourcePath;
156
- }
157
-
158
- cacheManifest[moduleId] = {
159
- originalHash: sourceHash,
160
- cacheHash: cacheHash,
161
- cachedAt: new Date().toISOString(),
162
- ...cleanMetadata,
163
- };
164
-
165
- await this.updateCacheManifest(cacheManifest);
166
-
167
- return {
168
- moduleId,
169
- cachePath: cacheDir,
170
- ...cacheManifest[moduleId],
171
- };
172
- }
173
-
174
- /**
175
- * Get cached module info
176
- * @param {string} moduleId - Module ID
177
- * @returns {Object|null} Cached module info or null
178
- */
179
- async getCachedModule(moduleId) {
180
- const cacheManifest = await this.getCacheManifest();
181
- const cached = cacheManifest[moduleId];
182
-
183
- if (!cached) {
184
- return null;
185
- }
186
-
187
- const cacheDir = path.join(this.customCacheDir, moduleId);
188
-
189
- if (!(await fs.pathExists(cacheDir))) {
190
- // Cache dir missing, remove from manifest
191
- delete cacheManifest[moduleId];
192
- await this.updateCacheManifest(cacheManifest);
193
- return null;
194
- }
195
-
196
- // Verify cache integrity
197
- const currentCacheHash = await this.calculateHash(cacheDir);
198
- if (currentCacheHash !== cached.cacheHash) {
199
- await prompts.log.warn(`Cache integrity check failed for ${moduleId}`);
200
- }
201
-
202
- return {
203
- moduleId,
204
- cachePath: cacheDir,
205
- ...cached,
206
- };
207
- }
208
-
209
- /**
210
- * Get all cached modules
211
- * @returns {Array} Array of cached module info
212
- */
213
- async getAllCachedModules() {
214
- const cacheManifest = await this.getCacheManifest();
215
- const cached = [];
216
-
217
- for (const [moduleId, info] of Object.entries(cacheManifest)) {
218
- const cachedModule = await this.getCachedModule(moduleId);
219
- if (cachedModule) {
220
- cached.push(cachedModule);
221
- }
222
- }
223
-
224
- return cached;
225
- }
226
-
227
- /**
228
- * Remove a cached module
229
- * @param {string} moduleId - Module ID to remove
230
- */
231
- async removeCachedModule(moduleId) {
232
- const cacheManifest = await this.getCacheManifest();
233
- const cacheDir = path.join(this.customCacheDir, moduleId);
234
-
235
- // Remove cache directory
236
- if (await fs.pathExists(cacheDir)) {
237
- await fs.remove(cacheDir);
238
- }
239
-
240
- // Remove from manifest
241
- delete cacheManifest[moduleId];
242
- await this.updateCacheManifest(cacheManifest);
243
- }
244
-
245
- /**
246
- * Sync cached modules with a list of module IDs
247
- * @param {Array<string>} moduleIds - Module IDs to keep
248
- */
249
- async syncCache(moduleIds) {
250
- const cached = await this.getAllCachedModules();
251
-
252
- for (const cachedModule of cached) {
253
- if (!moduleIds.includes(cachedModule.moduleId)) {
254
- await this.removeCachedModule(cachedModule.moduleId);
255
- }
256
- }
257
- }
258
- }
259
-
260
- module.exports = { CustomModuleCache };
@@ -1,112 +0,0 @@
1
- const path = require('node:path');
2
- const fs = require('fs-extra');
3
- const yaml = require('yaml');
4
- const prompts = require('./prompts');
5
- /**
6
- * Handler for custom content (custom.yaml)
7
- * Discovers custom agents and workflows in the project
8
- */
9
- class CustomHandler {
10
- /**
11
- * Find all custom.yaml files in the project
12
- * @param {string} projectRoot - Project root directory
13
- * @returns {Array} List of custom content paths
14
- */
15
- async findCustomContent(projectRoot) {
16
- const customPaths = [];
17
-
18
- // Helper function to recursively scan directories
19
- async function scanDirectory(dir, excludePaths = []) {
20
- try {
21
- const entries = await fs.readdir(dir, { withFileTypes: true });
22
-
23
- for (const entry of entries) {
24
- const fullPath = path.join(dir, entry.name);
25
-
26
- // Skip hidden directories and common exclusions
27
- if (
28
- entry.name.startsWith('.') ||
29
- entry.name === 'node_modules' ||
30
- entry.name === 'dist' ||
31
- entry.name === 'build' ||
32
- entry.name === '.git' ||
33
- entry.name === 'bmad'
34
- ) {
35
- continue;
36
- }
37
-
38
- // Skip excluded paths
39
- if (excludePaths.some((exclude) => fullPath.startsWith(exclude))) {
40
- continue;
41
- }
42
-
43
- if (entry.isDirectory()) {
44
- // Recursively scan subdirectories
45
- await scanDirectory(fullPath, excludePaths);
46
- } else if (entry.name === 'custom.yaml') {
47
- // Found a custom.yaml file
48
- customPaths.push(fullPath);
49
- } else if (
50
- entry.name === 'module.yaml' && // Check if this is a custom module (in root directory)
51
- // Skip if it's in src/modules (those are standard modules)
52
- !fullPath.includes(path.join('src', 'modules'))
53
- ) {
54
- customPaths.push(fullPath);
55
- }
56
- }
57
- } catch {
58
- // Ignore errors (e.g., permission denied)
59
- }
60
- }
61
-
62
- // Scan the entire project, but exclude source directories
63
- await scanDirectory(projectRoot, [path.join(projectRoot, 'src'), path.join(projectRoot, 'tools'), path.join(projectRoot, 'test')]);
64
-
65
- return customPaths;
66
- }
67
-
68
- /**
69
- * Get custom content info from a custom.yaml or module.yaml file
70
- * @param {string} configPath - Path to config file
71
- * @param {string} projectRoot - Project root directory for calculating relative paths
72
- * @returns {Object|null} Custom content info
73
- */
74
- async getCustomInfo(configPath, projectRoot = null) {
75
- try {
76
- const configContent = await fs.readFile(configPath, 'utf8');
77
-
78
- // Try to parse YAML with error handling
79
- let config;
80
- try {
81
- config = yaml.parse(configContent);
82
- } catch (parseError) {
83
- await prompts.log.warn('YAML parse error in ' + configPath + ': ' + parseError.message);
84
- return null;
85
- }
86
-
87
- // Check if this is an module.yaml (module) or custom.yaml (custom content)
88
- const isInstallConfig = configPath.endsWith('module.yaml');
89
- const configDir = path.dirname(configPath);
90
-
91
- // Use provided projectRoot or fall back to process.cwd()
92
- const basePath = projectRoot || process.cwd();
93
- const relativePath = path.relative(basePath, configDir);
94
-
95
- return {
96
- id: config.code || 'unknown-code',
97
- name: config.name,
98
- description: config.description || '',
99
- path: configDir,
100
- relativePath: relativePath,
101
- defaultSelected: config.default_selected === true,
102
- config: config,
103
- isInstallConfig: isInstallConfig, // Track which type this is
104
- };
105
- } catch (error) {
106
- await prompts.log.warn('Failed to read ' + configPath + ': ' + error.message);
107
- return null;
108
- }
109
- }
110
- }
111
-
112
- module.exports = { CustomHandler };
@@ -1,197 +0,0 @@
1
- const path = require('node:path');
2
- const fs = require('fs-extra');
3
- const yaml = require('yaml');
4
- const { CustomHandler } = require('../custom-handler');
5
- const { Manifest } = require('../core/manifest');
6
- const prompts = require('../prompts');
7
-
8
- class CustomModules {
9
- constructor() {
10
- this.paths = new Map();
11
- }
12
-
13
- has(moduleCode) {
14
- return this.paths.has(moduleCode);
15
- }
16
-
17
- get(moduleCode) {
18
- return this.paths.get(moduleCode);
19
- }
20
-
21
- set(moduleId, sourcePath) {
22
- this.paths.set(moduleId, sourcePath);
23
- }
24
-
25
- /**
26
- * Install a custom module from its source path.
27
- * @param {string} moduleName - Module identifier
28
- * @param {string} bmadDir - Target bmad directory
29
- * @param {Function} fileTrackingCallback - Optional callback to track installed files
30
- * @param {Object} options - Install options
31
- * @param {Object} options.moduleConfig - Pre-collected module configuration
32
- * @returns {Object} Install result
33
- */
34
- async install(moduleName, bmadDir, fileTrackingCallback = null, options = {}) {
35
- const sourcePath = this.paths.get(moduleName);
36
- if (!sourcePath) {
37
- throw new Error(`No source path for custom module '${moduleName}'`);
38
- }
39
-
40
- if (!(await fs.pathExists(sourcePath))) {
41
- throw new Error(`Source for custom module '${moduleName}' not found at: ${sourcePath}`);
42
- }
43
-
44
- const targetPath = path.join(bmadDir, moduleName);
45
-
46
- // Read custom.yaml and merge into module config
47
- let moduleConfig = options.moduleConfig ? { ...options.moduleConfig } : {};
48
- const customConfigPath = path.join(sourcePath, 'custom.yaml');
49
- if (await fs.pathExists(customConfigPath)) {
50
- try {
51
- const content = await fs.readFile(customConfigPath, 'utf8');
52
- const customConfig = yaml.parse(content);
53
- if (customConfig) {
54
- moduleConfig = { ...moduleConfig, ...customConfig };
55
- }
56
- } catch (error) {
57
- await prompts.log.warn(`Failed to read custom.yaml for ${moduleName}: ${error.message}`);
58
- }
59
- }
60
-
61
- // Remove existing installation
62
- if (await fs.pathExists(targetPath)) {
63
- await fs.remove(targetPath);
64
- }
65
-
66
- // Copy files with filtering
67
- await this._copyWithFiltering(sourcePath, targetPath, fileTrackingCallback);
68
-
69
- // Add to manifest
70
- const manifest = new Manifest();
71
- const versionInfo = await manifest.getModuleVersionInfo(moduleName, bmadDir, sourcePath);
72
- await manifest.addModule(bmadDir, moduleName, {
73
- version: versionInfo.version,
74
- source: versionInfo.source,
75
- npmPackage: versionInfo.npmPackage,
76
- repoUrl: versionInfo.repoUrl,
77
- });
78
-
79
- return { success: true, module: moduleName, path: targetPath, moduleConfig };
80
- }
81
-
82
- /**
83
- * Copy module files, filtering out install-time-only artifacts.
84
- * @param {string} sourcePath - Source module directory
85
- * @param {string} targetPath - Target module directory
86
- * @param {Function} fileTrackingCallback - Optional callback to track installed files
87
- */
88
- async _copyWithFiltering(sourcePath, targetPath, fileTrackingCallback = null) {
89
- const files = await this._getFileList(sourcePath);
90
-
91
- for (const file of files) {
92
- if (file.startsWith('sub-modules/')) continue;
93
-
94
- const isInSidecar = path
95
- .dirname(file)
96
- .split('/')
97
- .some((dir) => dir.toLowerCase().endsWith('-sidecar'));
98
- if (isInSidecar) continue;
99
-
100
- if (file === 'module.yaml') continue;
101
- if (file === 'config.yaml') continue;
102
-
103
- const sourceFile = path.join(sourcePath, file);
104
- const targetFile = path.join(targetPath, file);
105
-
106
- // Skip web-only agents
107
- if (file.startsWith('agents/') && file.endsWith('.md')) {
108
- const content = await fs.readFile(sourceFile, 'utf8');
109
- if (/<agent[^>]*\slocalskip="true"[^>]*>/.test(content)) {
110
- continue;
111
- }
112
- }
113
-
114
- await fs.ensureDir(path.dirname(targetFile));
115
- await fs.copy(sourceFile, targetFile, { overwrite: true });
116
-
117
- if (fileTrackingCallback) {
118
- fileTrackingCallback(targetFile);
119
- }
120
- }
121
- }
122
-
123
- /**
124
- * Recursively list all files in a directory.
125
- * @param {string} dir - Directory to scan
126
- * @param {string} baseDir - Base directory for relative paths
127
- * @returns {string[]} Relative file paths
128
- */
129
- async _getFileList(dir, baseDir = dir) {
130
- const files = [];
131
- const entries = await fs.readdir(dir, { withFileTypes: true });
132
-
133
- for (const entry of entries) {
134
- const fullPath = path.join(dir, entry.name);
135
- if (entry.isDirectory()) {
136
- files.push(...(await this._getFileList(fullPath, baseDir)));
137
- } else {
138
- files.push(path.relative(baseDir, fullPath));
139
- }
140
- }
141
-
142
- return files;
143
- }
144
-
145
- /**
146
- * Discover custom module source paths from all available sources.
147
- * @param {Object} config - Installation configuration
148
- * @param {Object} paths - InstallPaths instance
149
- * @returns {Map<string, string>} Map of module ID to source path
150
- */
151
- async discoverPaths(config, paths) {
152
- this.paths = new Map();
153
-
154
- if (config._quickUpdate) {
155
- if (config._customModuleSources) {
156
- for (const [moduleId, customInfo] of config._customModuleSources) {
157
- this.paths.set(moduleId, customInfo.sourcePath);
158
- }
159
- }
160
- return this.paths;
161
- }
162
-
163
- // From UI: selectedFiles
164
- if (config.customContent && config.customContent.selected && config.customContent.selectedFiles) {
165
- const customHandler = new CustomHandler();
166
- for (const customFile of config.customContent.selectedFiles) {
167
- const customInfo = await customHandler.getCustomInfo(customFile, paths.projectRoot);
168
- if (customInfo && customInfo.id) {
169
- this.paths.set(customInfo.id, customInfo.path);
170
- }
171
- }
172
- }
173
-
174
- // From UI: sources
175
- if (config.customContent && config.customContent.sources) {
176
- for (const source of config.customContent.sources) {
177
- this.paths.set(source.id, source.path);
178
- }
179
- }
180
-
181
- // From UI: cachedModules
182
- if (config.customContent && config.customContent.cachedModules) {
183
- const selectedCachedIds = config.customContent.selectedCachedModules || [];
184
- const shouldIncludeAll = selectedCachedIds.length === 0 && config.customContent.selected;
185
-
186
- for (const cachedModule of config.customContent.cachedModules) {
187
- if (cachedModule.id && cachedModule.cachePath && (shouldIncludeAll || selectedCachedIds.includes(cachedModule.id))) {
188
- this.paths.set(cachedModule.id, cachedModule.cachePath);
189
- }
190
- }
191
- }
192
-
193
- return this.paths;
194
- }
195
- }
196
-
197
- module.exports = { CustomModules };