bmad-method 6.2.3-next.8 → 6.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/.claude-plugin/marketplace.json +0 -3
  2. package/README.md +8 -9
  3. package/README_CN.md +1 -1
  4. package/README_VN.md +110 -0
  5. package/package.json +1 -1
  6. package/removals.txt +17 -0
  7. package/src/bmm-skills/2-plan-workflows/bmad-create-ux-design/steps/step-13-responsive-accessibility.md +1 -1
  8. package/src/bmm-skills/2-plan-workflows/bmad-edit-prd/data/prd-purpose.md +197 -0
  9. package/src/bmm-skills/2-plan-workflows/bmad-edit-prd/steps-e/step-e-01-discovery.md +1 -1
  10. package/src/bmm-skills/2-plan-workflows/bmad-edit-prd/steps-e/step-e-01b-legacy-conversion.md +1 -1
  11. package/src/bmm-skills/2-plan-workflows/bmad-edit-prd/steps-e/step-e-02-review.md +1 -1
  12. package/src/bmm-skills/2-plan-workflows/bmad-edit-prd/steps-e/step-e-03-edit.md +1 -1
  13. package/src/bmm-skills/2-plan-workflows/bmad-edit-prd/steps-e/step-e-04-complete.md +1 -3
  14. package/src/bmm-skills/3-solutioning/bmad-check-implementation-readiness/steps/step-01-document-discovery.md +1 -1
  15. package/src/bmm-skills/3-solutioning/bmad-check-implementation-readiness/steps/step-02-prd-analysis.md +1 -1
  16. package/src/bmm-skills/3-solutioning/bmad-check-implementation-readiness/steps/step-03-epic-coverage-validation.md +1 -1
  17. package/src/bmm-skills/3-solutioning/bmad-check-implementation-readiness/workflow.md +1 -1
  18. package/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/workflow.md +1 -1
  19. package/src/bmm-skills/4-implementation/bmad-agent-dev/SKILL.md +5 -0
  20. package/src/bmm-skills/4-implementation/bmad-checkpoint-preview/SKILL.md +29 -0
  21. package/src/bmm-skills/4-implementation/bmad-checkpoint-preview/generate-trail.md +38 -0
  22. package/src/bmm-skills/4-implementation/bmad-checkpoint-preview/step-01-orientation.md +105 -0
  23. package/src/bmm-skills/4-implementation/bmad-checkpoint-preview/step-02-walkthrough.md +89 -0
  24. package/src/bmm-skills/4-implementation/bmad-checkpoint-preview/step-03-detail-pass.md +106 -0
  25. package/src/bmm-skills/4-implementation/bmad-checkpoint-preview/step-04-testing.md +74 -0
  26. package/src/bmm-skills/4-implementation/bmad-checkpoint-preview/step-05-wrapup.md +24 -0
  27. package/src/bmm-skills/4-implementation/bmad-code-review/steps/step-01-gather-context.md +38 -15
  28. package/src/bmm-skills/4-implementation/bmad-correct-course/checklist.md +2 -2
  29. package/src/bmm-skills/4-implementation/bmad-correct-course/workflow.md +8 -8
  30. package/src/bmm-skills/4-implementation/bmad-qa-generate-e2e-tests/checklist.md +1 -1
  31. package/src/bmm-skills/4-implementation/bmad-quick-dev/compile-epic-context.md +62 -0
  32. package/src/bmm-skills/4-implementation/bmad-quick-dev/spec-template.md +1 -1
  33. package/src/bmm-skills/4-implementation/bmad-quick-dev/step-01-clarify-and-route.md +33 -6
  34. package/src/bmm-skills/4-implementation/bmad-quick-dev/step-02-plan.md +20 -8
  35. package/src/bmm-skills/4-implementation/bmad-quick-dev/step-03-implement.md +2 -0
  36. package/src/bmm-skills/4-implementation/bmad-quick-dev/step-oneshot.md +16 -4
  37. package/src/bmm-skills/4-implementation/bmad-quick-dev/workflow.md +1 -5
  38. package/src/bmm-skills/4-implementation/bmad-retrospective/workflow.md +134 -134
  39. package/src/bmm-skills/4-implementation/bmad-sprint-planning/sprint-status-template.yaml +1 -1
  40. package/src/bmm-skills/4-implementation/bmad-sprint-planning/workflow.md +3 -3
  41. package/src/bmm-skills/4-implementation/bmad-sprint-status/workflow.md +2 -2
  42. package/src/bmm-skills/module-help.csv +2 -0
  43. package/src/core-skills/bmad-help/SKILL.md +4 -2
  44. package/src/core-skills/bmad-party-mode/SKILL.md +121 -2
  45. package/src/core-skills/module-help.csv +1 -0
  46. package/tools/installer/cli-utils.js +18 -9
  47. package/tools/installer/commands/install.js +1 -1
  48. package/tools/installer/core/existing-install.js +2 -8
  49. package/tools/installer/core/install-paths.js +0 -3
  50. package/tools/installer/core/installer.js +180 -463
  51. package/tools/installer/core/manifest-generator.js +8 -14
  52. package/tools/installer/core/manifest.js +94 -102
  53. package/tools/installer/ide/_config-driven.js +149 -38
  54. package/tools/installer/ide/shared/skill-manifest.js +1 -16
  55. package/tools/installer/install-messages.yaml +19 -26
  56. package/tools/installer/modules/community-manager.js +377 -0
  57. package/tools/installer/modules/custom-module-manager.js +644 -0
  58. package/tools/installer/modules/external-manager.js +65 -49
  59. package/tools/installer/modules/official-modules.js +117 -65
  60. package/tools/installer/modules/plugin-resolver.js +398 -0
  61. package/tools/installer/modules/registry-client.js +66 -0
  62. package/tools/installer/{external-official-modules.yaml → modules/registry-fallback.yaml} +3 -12
  63. package/tools/installer/ui.js +549 -666
  64. package/src/bmm-skills/4-implementation/bmad-agent-qa/SKILL.md +0 -61
  65. package/src/bmm-skills/4-implementation/bmad-agent-qa/bmad-skill-manifest.yaml +0 -11
  66. package/src/bmm-skills/4-implementation/bmad-agent-quick-flow-solo-dev/SKILL.md +0 -53
  67. package/src/bmm-skills/4-implementation/bmad-agent-quick-flow-solo-dev/bmad-skill-manifest.yaml +0 -11
  68. package/src/bmm-skills/4-implementation/bmad-agent-sm/SKILL.md +0 -55
  69. package/src/bmm-skills/4-implementation/bmad-agent-sm/bmad-skill-manifest.yaml +0 -11
  70. package/src/core-skills/bmad-party-mode/steps/step-01-agent-loading.md +0 -138
  71. package/src/core-skills/bmad-party-mode/steps/step-02-discussion-orchestration.md +0 -187
  72. package/src/core-skills/bmad-party-mode/steps/step-03-graceful-exit.md +0 -167
  73. package/src/core-skills/bmad-party-mode/workflow.md +0 -183
  74. package/tools/installer/core/custom-module-cache.js +0 -260
  75. package/tools/installer/custom-handler.js +0 -112
  76. package/tools/installer/modules/custom-modules.js +0 -197
@@ -0,0 +1,398 @@
1
+ const fs = require('fs-extra');
2
+ const path = require('node:path');
3
+ const yaml = require('yaml');
4
+
5
+ /**
6
+ * Resolves how to install a plugin from marketplace.json by analyzing
7
+ * where module.yaml and module-help.csv live relative to the listed skills.
8
+ *
9
+ * Five strategies, tried in order:
10
+ * 1. Root module files at the common parent of all skills
11
+ * 2. A -setup skill with assets/module.yaml + assets/module-help.csv
12
+ * 3. Single standalone skill with both files in its assets/
13
+ * 4. Multiple standalone skills, each with both files in assets/
14
+ * 5. Fallback: synthesize from marketplace.json + SKILL.md frontmatter
15
+ */
16
+ class PluginResolver {
17
+ /**
18
+ * Resolve a plugin to one or more installable module definitions.
19
+ * @param {string} repoPath - Absolute path to the cloned repository root
20
+ * @param {Object} plugin - Plugin object from marketplace.json
21
+ * @param {string} plugin.name - Plugin identifier
22
+ * @param {string} [plugin.source] - Relative path from repo root
23
+ * @param {string} [plugin.version] - Semantic version
24
+ * @param {string} [plugin.description] - Plugin description
25
+ * @param {string[]} [plugin.skills] - Relative paths to skill directories
26
+ * @returns {Promise<ResolvedModule[]>} Array of resolved module definitions
27
+ */
28
+ async resolve(repoPath, plugin) {
29
+ const skillRelPaths = plugin.skills || [];
30
+
31
+ // No skills array: legacy behavior - caller should use existing findModuleSource
32
+ if (skillRelPaths.length === 0) {
33
+ return [];
34
+ }
35
+
36
+ // Resolve skill paths to absolute, constrain to repo root, filter non-existent
37
+ const repoRoot = path.resolve(repoPath);
38
+ const skillPaths = [];
39
+ for (const rel of skillRelPaths) {
40
+ const normalized = rel.replace(/^\.\//, '');
41
+ const abs = path.resolve(repoPath, normalized);
42
+ // Guard against path traversal (.. segments, absolute paths in marketplace.json)
43
+ if (!abs.startsWith(repoRoot + path.sep) && abs !== repoRoot) {
44
+ continue;
45
+ }
46
+ if (await fs.pathExists(abs)) {
47
+ skillPaths.push(abs);
48
+ }
49
+ }
50
+
51
+ if (skillPaths.length === 0) {
52
+ return [];
53
+ }
54
+
55
+ // Try each strategy in order
56
+ const result =
57
+ (await this._tryRootModuleFiles(repoPath, plugin, skillPaths)) ||
58
+ (await this._trySetupSkill(repoPath, plugin, skillPaths)) ||
59
+ (await this._trySingleStandalone(repoPath, plugin, skillPaths)) ||
60
+ (await this._tryMultipleStandalone(repoPath, plugin, skillPaths)) ||
61
+ (await this._synthesizeFallback(repoPath, plugin, skillPaths));
62
+
63
+ return result;
64
+ }
65
+
66
+ // ─── Strategy 1: Root Module Files ──────────────────────────────────────────
67
+
68
+ /**
69
+ * Check if module.yaml + module-help.csv exist at the common parent of all skills.
70
+ */
71
+ async _tryRootModuleFiles(repoPath, plugin, skillPaths) {
72
+ const commonParent = this._computeCommonParent(skillPaths);
73
+ const moduleYamlPath = path.join(commonParent, 'module.yaml');
74
+ const moduleHelpPath = path.join(commonParent, 'module-help.csv');
75
+
76
+ if (!(await fs.pathExists(moduleYamlPath)) || !(await fs.pathExists(moduleHelpPath))) {
77
+ return null;
78
+ }
79
+
80
+ const moduleData = await this._readModuleYaml(moduleYamlPath);
81
+ if (!moduleData) return null;
82
+
83
+ return [
84
+ {
85
+ code: moduleData.code || plugin.name,
86
+ name: moduleData.name || plugin.name,
87
+ version: plugin.version || moduleData.module_version || null,
88
+ description: moduleData.description || plugin.description || '',
89
+ strategy: 1,
90
+ pluginName: plugin.name,
91
+ moduleYamlPath,
92
+ moduleHelpCsvPath: moduleHelpPath,
93
+ skillPaths,
94
+ synthesizedModuleYaml: null,
95
+ synthesizedHelpCsv: null,
96
+ },
97
+ ];
98
+ }
99
+
100
+ // ─── Strategy 2: Setup Skill ────────────────────────────────────────────────
101
+
102
+ /**
103
+ * Search for a skill ending in -setup with assets/module.yaml + assets/module-help.csv.
104
+ */
105
+ async _trySetupSkill(repoPath, plugin, skillPaths) {
106
+ for (const skillPath of skillPaths) {
107
+ const dirName = path.basename(skillPath);
108
+ if (!dirName.endsWith('-setup')) continue;
109
+
110
+ const moduleYamlPath = path.join(skillPath, 'assets', 'module.yaml');
111
+ const moduleHelpPath = path.join(skillPath, 'assets', 'module-help.csv');
112
+
113
+ if (!(await fs.pathExists(moduleYamlPath)) || !(await fs.pathExists(moduleHelpPath))) {
114
+ continue;
115
+ }
116
+
117
+ const moduleData = await this._readModuleYaml(moduleYamlPath);
118
+ if (!moduleData) continue;
119
+
120
+ return [
121
+ {
122
+ code: moduleData.code || plugin.name,
123
+ name: moduleData.name || plugin.name,
124
+ version: plugin.version || moduleData.module_version || null,
125
+ description: moduleData.description || plugin.description || '',
126
+ strategy: 2,
127
+ pluginName: plugin.name,
128
+ moduleYamlPath,
129
+ moduleHelpCsvPath: moduleHelpPath,
130
+ skillPaths,
131
+ synthesizedModuleYaml: null,
132
+ synthesizedHelpCsv: null,
133
+ },
134
+ ];
135
+ }
136
+
137
+ return null;
138
+ }
139
+
140
+ // ─── Strategy 3: Single Standalone Skill ────────────────────────────────────
141
+
142
+ /**
143
+ * One skill listed, with assets/module.yaml + assets/module-help.csv.
144
+ */
145
+ async _trySingleStandalone(repoPath, plugin, skillPaths) {
146
+ if (skillPaths.length !== 1) return null;
147
+
148
+ const skillPath = skillPaths[0];
149
+ const moduleYamlPath = path.join(skillPath, 'assets', 'module.yaml');
150
+ const moduleHelpPath = path.join(skillPath, 'assets', 'module-help.csv');
151
+
152
+ if (!(await fs.pathExists(moduleYamlPath)) || !(await fs.pathExists(moduleHelpPath))) {
153
+ return null;
154
+ }
155
+
156
+ const moduleData = await this._readModuleYaml(moduleYamlPath);
157
+ if (!moduleData) return null;
158
+
159
+ return [
160
+ {
161
+ code: moduleData.code || plugin.name,
162
+ name: moduleData.name || plugin.name,
163
+ version: plugin.version || moduleData.module_version || null,
164
+ description: moduleData.description || plugin.description || '',
165
+ strategy: 3,
166
+ pluginName: plugin.name,
167
+ moduleYamlPath,
168
+ moduleHelpCsvPath: moduleHelpPath,
169
+ skillPaths,
170
+ synthesizedModuleYaml: null,
171
+ synthesizedHelpCsv: null,
172
+ },
173
+ ];
174
+ }
175
+
176
+ // ─── Strategy 4: Multiple Standalone Skills ─────────────────────────────────
177
+
178
+ /**
179
+ * Multiple skills, each with assets/module.yaml + assets/module-help.csv.
180
+ * Each becomes its own installable module.
181
+ */
182
+ async _tryMultipleStandalone(repoPath, plugin, skillPaths) {
183
+ if (skillPaths.length < 2) return null;
184
+
185
+ const resolved = [];
186
+
187
+ for (const skillPath of skillPaths) {
188
+ const moduleYamlPath = path.join(skillPath, 'assets', 'module.yaml');
189
+ const moduleHelpPath = path.join(skillPath, 'assets', 'module-help.csv');
190
+
191
+ if (!(await fs.pathExists(moduleYamlPath)) || !(await fs.pathExists(moduleHelpPath))) {
192
+ continue;
193
+ }
194
+
195
+ const moduleData = await this._readModuleYaml(moduleYamlPath);
196
+ if (!moduleData) continue;
197
+
198
+ resolved.push({
199
+ code: moduleData.code || path.basename(skillPath),
200
+ name: moduleData.name || path.basename(skillPath),
201
+ version: plugin.version || moduleData.module_version || null,
202
+ description: moduleData.description || '',
203
+ strategy: 4,
204
+ pluginName: plugin.name,
205
+ moduleYamlPath,
206
+ moduleHelpCsvPath: moduleHelpPath,
207
+ skillPaths: [skillPath],
208
+ synthesizedModuleYaml: null,
209
+ synthesizedHelpCsv: null,
210
+ });
211
+ }
212
+
213
+ // Only use strategy 4 if ALL skills have module files
214
+ if (resolved.length === skillPaths.length) {
215
+ return resolved;
216
+ }
217
+
218
+ // Partial match: fall through to strategy 5
219
+ return null;
220
+ }
221
+
222
+ // ─── Strategy 5: Fallback (Synthesized) ─────────────────────────────────────
223
+
224
+ /**
225
+ * No module files found anywhere. Synthesize from marketplace.json metadata
226
+ * and SKILL.md frontmatter.
227
+ */
228
+ async _synthesizeFallback(repoPath, plugin, skillPaths) {
229
+ const skillInfos = [];
230
+
231
+ for (const skillPath of skillPaths) {
232
+ const frontmatter = await this._parseSkillFrontmatter(skillPath);
233
+ skillInfos.push({
234
+ dirName: path.basename(skillPath),
235
+ name: frontmatter.name || path.basename(skillPath),
236
+ description: frontmatter.description || '',
237
+ });
238
+ }
239
+
240
+ const moduleName = this._formatDisplayName(plugin.name);
241
+ const code = plugin.name;
242
+
243
+ const synthesizedYaml = {
244
+ code,
245
+ name: moduleName,
246
+ description: plugin.description || '',
247
+ module_version: plugin.version || '1.0.0',
248
+ default_selected: false,
249
+ };
250
+
251
+ const synthesizedCsv = this._buildSynthesizedHelpCsv(moduleName, skillInfos);
252
+
253
+ return [
254
+ {
255
+ code,
256
+ name: moduleName,
257
+ version: plugin.version || null,
258
+ description: plugin.description || '',
259
+ strategy: 5,
260
+ pluginName: plugin.name,
261
+ moduleYamlPath: null,
262
+ moduleHelpCsvPath: null,
263
+ skillPaths,
264
+ synthesizedModuleYaml: synthesizedYaml,
265
+ synthesizedHelpCsv: synthesizedCsv,
266
+ },
267
+ ];
268
+ }
269
+
270
+ // ─── Helpers ────────────────────────────────────────────────────────────────
271
+
272
+ /**
273
+ * Compute the deepest common ancestor directory of an array of absolute paths.
274
+ * @param {string[]} absPaths - Absolute directory paths
275
+ * @returns {string} Common parent directory
276
+ */
277
+ _computeCommonParent(absPaths) {
278
+ if (absPaths.length === 0) return '/';
279
+ if (absPaths.length === 1) return path.dirname(absPaths[0]);
280
+
281
+ const segments = absPaths.map((p) => p.split(path.sep));
282
+ const minLen = Math.min(...segments.map((s) => s.length));
283
+ const common = [];
284
+
285
+ for (let i = 0; i < minLen; i++) {
286
+ const segment = segments[0][i];
287
+ if (segments.every((s) => s[i] === segment)) {
288
+ common.push(segment);
289
+ } else {
290
+ break;
291
+ }
292
+ }
293
+
294
+ return common.join(path.sep) || '/';
295
+ }
296
+
297
+ /**
298
+ * Read and parse a module.yaml file.
299
+ * @param {string} yamlPath - Absolute path to module.yaml
300
+ * @returns {Object|null} Parsed content or null on failure
301
+ */
302
+ async _readModuleYaml(yamlPath) {
303
+ try {
304
+ const content = await fs.readFile(yamlPath, 'utf8');
305
+ return yaml.parse(content);
306
+ } catch {
307
+ return null;
308
+ }
309
+ }
310
+
311
+ /**
312
+ * Extract name and description from a SKILL.md YAML frontmatter block.
313
+ * @param {string} skillDirPath - Absolute path to the skill directory
314
+ * @returns {Object} { name, description } or empty strings
315
+ */
316
+ async _parseSkillFrontmatter(skillDirPath) {
317
+ const skillMdPath = path.join(skillDirPath, 'SKILL.md');
318
+ try {
319
+ const content = await fs.readFile(skillMdPath, 'utf8');
320
+ const match = content.match(/^---\s*\n([\s\S]*?)\n---/);
321
+ if (!match) return { name: '', description: '' };
322
+
323
+ const parsed = yaml.parse(match[1]);
324
+ return {
325
+ name: parsed.name || '',
326
+ description: parsed.description || '',
327
+ };
328
+ } catch {
329
+ return { name: '', description: '' };
330
+ }
331
+ }
332
+
333
+ /**
334
+ * Build a synthesized module-help.csv from plugin metadata and skill frontmatter.
335
+ * Uses the standard 13-column format.
336
+ * @param {string} moduleName - Display name for the module column
337
+ * @param {Array<{dirName: string, name: string, description: string}>} skillInfos
338
+ * @returns {string} CSV content
339
+ */
340
+ _buildSynthesizedHelpCsv(moduleName, skillInfos) {
341
+ const header = 'module,skill,display-name,menu-code,description,action,args,phase,after,before,required,output-location,outputs';
342
+ const rows = [header];
343
+
344
+ for (const info of skillInfos) {
345
+ const displayName = this._formatDisplayName(info.name || info.dirName);
346
+ const menuCode = this._generateMenuCode(info.name || info.dirName);
347
+ const description = this._escapeCSVField(info.description);
348
+
349
+ rows.push(`${moduleName},${info.dirName},${displayName},${menuCode},${description},activate,,anytime,,,false,,`);
350
+ }
351
+
352
+ return rows.join('\n') + '\n';
353
+ }
354
+
355
+ /**
356
+ * Format a kebab-case or snake_case name into a display name.
357
+ * Strips common prefixes like "bmad-" or "bmad-agent-".
358
+ * @param {string} name - Raw name
359
+ * @returns {string} Formatted display name
360
+ */
361
+ _formatDisplayName(name) {
362
+ let cleaned = name.replace(/^bmad-agent-/, '').replace(/^bmad-/, '');
363
+ return cleaned
364
+ .split(/[-_]/)
365
+ .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
366
+ .join(' ');
367
+ }
368
+
369
+ /**
370
+ * Generate a short menu code from a skill name.
371
+ * Takes first letter of each significant word, uppercased, max 3 chars.
372
+ * @param {string} name - Skill name (kebab-case)
373
+ * @returns {string} Menu code (e.g., "CC" for "code-coach")
374
+ */
375
+ _generateMenuCode(name) {
376
+ const cleaned = name.replace(/^bmad-agent-/, '').replace(/^bmad-/, '');
377
+ const words = cleaned.split(/[-_]/).filter((w) => w.length > 0);
378
+ return words
379
+ .map((w) => w.charAt(0).toUpperCase())
380
+ .join('')
381
+ .slice(0, 3);
382
+ }
383
+
384
+ /**
385
+ * Escape a value for CSV output (wrap in quotes if it contains commas, quotes, or newlines).
386
+ * @param {string} value
387
+ * @returns {string}
388
+ */
389
+ _escapeCSVField(value) {
390
+ if (!value) return '';
391
+ if (value.includes(',') || value.includes('"') || value.includes('\n')) {
392
+ return `"${value.replaceAll('"', '""')}"`;
393
+ }
394
+ return value;
395
+ }
396
+ }
397
+
398
+ module.exports = { PluginResolver };
@@ -0,0 +1,66 @@
1
+ const https = require('node:https');
2
+ const yaml = require('yaml');
3
+
4
+ /**
5
+ * Shared HTTP client for fetching registry data from GitHub.
6
+ * Used by ExternalModuleManager, CommunityModuleManager, and CustomModuleManager.
7
+ */
8
+ class RegistryClient {
9
+ constructor(options = {}) {
10
+ this.timeout = options.timeout || 10_000;
11
+ }
12
+
13
+ /**
14
+ * Fetch a URL and return the response body as a string.
15
+ * Follows one redirect (GitHub sometimes 301s).
16
+ * @param {string} url - URL to fetch
17
+ * @param {number} [timeout] - Timeout in ms (overrides default)
18
+ * @returns {Promise<string>} Response body
19
+ */
20
+ fetch(url, timeout) {
21
+ const timeoutMs = timeout || this.timeout;
22
+ return new Promise((resolve, reject) => {
23
+ const req = https
24
+ .get(url, { timeout: timeoutMs }, (res) => {
25
+ if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
26
+ return this.fetch(res.headers.location, timeoutMs).then(resolve, reject);
27
+ }
28
+ if (res.statusCode !== 200) {
29
+ return reject(new Error(`HTTP ${res.statusCode}`));
30
+ }
31
+ let data = '';
32
+ res.on('data', (chunk) => (data += chunk));
33
+ res.on('end', () => resolve(data));
34
+ })
35
+ .on('error', reject)
36
+ .on('timeout', () => {
37
+ req.destroy();
38
+ reject(new Error('Request timed out'));
39
+ });
40
+ });
41
+ }
42
+
43
+ /**
44
+ * Fetch a URL and parse the response as YAML.
45
+ * @param {string} url - URL to fetch
46
+ * @param {number} [timeout] - Timeout in ms
47
+ * @returns {Promise<Object>} Parsed YAML content
48
+ */
49
+ async fetchYaml(url, timeout) {
50
+ const content = await this.fetch(url, timeout);
51
+ return yaml.parse(content);
52
+ }
53
+
54
+ /**
55
+ * Fetch a URL and parse the response as JSON.
56
+ * @param {string} url - URL to fetch
57
+ * @param {number} [timeout] - Timeout in ms
58
+ * @returns {Promise<Object>} Parsed JSON content
59
+ */
60
+ async fetchJson(url, timeout) {
61
+ const content = await this.fetch(url, timeout);
62
+ return JSON.parse(content);
63
+ }
64
+ }
65
+
66
+ module.exports = { RegistryClient };
@@ -1,5 +1,6 @@
1
- # This file allows these modules under bmad-code-org to also be installed with the bmad method installer, while
2
- # allowing us to keep the source of these projects in separate repos.
1
+ # Fallback module registry used only when the BMad Marketplace repo
2
+ # (bmad-code-org/bmad-plugins-marketplace) is unreachable.
3
+ # The remote registry/official.yaml is the source of truth.
3
4
 
4
5
  modules:
5
6
  bmad-builder:
@@ -41,13 +42,3 @@ modules:
41
42
  defaultSelected: false
42
43
  type: bmad-org
43
44
  npmPackage: bmad-method-test-architecture-enterprise
44
-
45
- whiteport-design-studio:
46
- url: https://github.com/bmad-code-org/bmad-method-wds-expansion
47
- module-definition: src/module.yaml
48
- code: wds
49
- name: "Whiteport Design Studio (For UX Professionals)"
50
- description: "Whiteport Design Studio (For UX Professionals)"
51
- defaultSelected: false
52
- type: community
53
- npmPackage: bmad-method-wds-expansion