bmad-method 6.6.1-next.8 → 6.7.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.
- package/README.md +3 -3
- package/{tools/installer/modules/registry-fallback.yaml → bmad-modules.yaml} +29 -15
- package/package.json +4 -4
- package/src/bmm-skills/1-analysis/bmad-product-brief/SKILL.md +18 -11
- package/src/bmm-skills/1-analysis/bmad-product-brief/customize.toml +13 -8
- package/src/bmm-skills/2-plan-workflows/bmad-prd/SKILL.md +54 -57
- package/src/bmm-skills/2-plan-workflows/bmad-prd/assets/headless-schemas.md +2 -2
- package/src/bmm-skills/2-plan-workflows/bmad-prd/assets/prd-template.md +40 -30
- package/src/bmm-skills/2-plan-workflows/bmad-prd/assets/prd-validation-checklist.md +126 -21
- package/src/bmm-skills/2-plan-workflows/bmad-prd/assets/validation-report-template.html +193 -58
- package/src/bmm-skills/2-plan-workflows/bmad-prd/customize.toml +47 -13
- package/src/bmm-skills/2-plan-workflows/bmad-prd/references/headless.md +27 -12
- package/src/bmm-skills/2-plan-workflows/bmad-prd/references/validate.md +97 -0
- package/src/bmm-skills/module.yaml +2 -2
- package/src/core-skills/module.yaml +1 -1
- package/tools/installer/core/installer.js +1 -22
- package/tools/installer/core/manifest.js +0 -22
- package/tools/installer/modules/channel-plan.js +1 -1
- package/tools/installer/modules/external-manager.js +9 -27
- package/tools/installer/modules/official-modules.js +9 -48
- package/tools/installer/prompts.js +149 -0
- package/tools/installer/ui.js +13 -197
- package/src/bmm-skills/2-plan-workflows/bmad-prd/references/facilitation-guide.md +0 -79
- package/src/bmm-skills/2-plan-workflows/bmad-prd/references/validation-render.md +0 -58
- package/src/bmm-skills/2-plan-workflows/bmad-prd/scripts/render-validation-html.py +0 -290
- package/tools/installer/modules/community-manager.js +0 -704
- package/tools/installer/modules/registry-client.js +0 -187
|
@@ -1,187 +0,0 @@
|
|
|
1
|
-
const https = require('node:https');
|
|
2
|
-
const yaml = require('yaml');
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Build a rich Error from a non-2xx response. Includes the URL, the GitHub
|
|
6
|
-
* JSON error message (or a truncated body snippet), rate-limit reset time,
|
|
7
|
-
* and Retry-After — anything present that would help a user recover.
|
|
8
|
-
*/
|
|
9
|
-
function buildHttpError(url, res, body) {
|
|
10
|
-
const parts = [`HTTP ${res.statusCode} ${url}`];
|
|
11
|
-
|
|
12
|
-
if (body) {
|
|
13
|
-
try {
|
|
14
|
-
const parsed = JSON.parse(body);
|
|
15
|
-
if (parsed.message) parts.push(parsed.message);
|
|
16
|
-
if (parsed.documentation_url) parts.push(`(see ${parsed.documentation_url})`);
|
|
17
|
-
} catch {
|
|
18
|
-
const snippet = body.slice(0, 200).trim();
|
|
19
|
-
if (snippet) parts.push(snippet);
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
const remaining = res.headers['x-ratelimit-remaining'];
|
|
24
|
-
const reset = res.headers['x-ratelimit-reset'];
|
|
25
|
-
if (remaining === '0' && reset) {
|
|
26
|
-
parts.push(`rate limit exhausted; resets at ${new Date(Number(reset) * 1000).toISOString()}`);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const retryAfter = res.headers['retry-after'];
|
|
30
|
-
if (retryAfter) parts.push(`retry after ${retryAfter}`);
|
|
31
|
-
|
|
32
|
-
return new Error(parts.join(' — '));
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Shared HTTP client for fetching registry data from GitHub.
|
|
37
|
-
* Used by ExternalModuleManager, CommunityModuleManager, and CustomModuleManager.
|
|
38
|
-
*/
|
|
39
|
-
class RegistryClient {
|
|
40
|
-
constructor(options = {}) {
|
|
41
|
-
this.timeout = options.timeout || 10_000;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Fetch a URL and return the response body as a string.
|
|
46
|
-
* Follows up to 3 redirects (GitHub sometimes 301s).
|
|
47
|
-
* @param {string} url - URL to fetch
|
|
48
|
-
* @param {number} [timeout] - Timeout in ms (overrides default)
|
|
49
|
-
* @param {number} [maxRedirects=3] - Maximum redirects to follow
|
|
50
|
-
* @returns {Promise<string>} Response body
|
|
51
|
-
*/
|
|
52
|
-
fetch(url, timeout, maxRedirects = 3) {
|
|
53
|
-
const timeoutMs = timeout || this.timeout;
|
|
54
|
-
return new Promise((resolve, reject) => {
|
|
55
|
-
const req = https
|
|
56
|
-
.get(url, { timeout: timeoutMs }, (res) => {
|
|
57
|
-
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
58
|
-
if (maxRedirects <= 0) {
|
|
59
|
-
return reject(new Error('Too many redirects'));
|
|
60
|
-
}
|
|
61
|
-
return this.fetch(res.headers.location, timeoutMs, maxRedirects - 1).then(resolve, reject);
|
|
62
|
-
}
|
|
63
|
-
let data = '';
|
|
64
|
-
res.on('data', (chunk) => (data += chunk));
|
|
65
|
-
res.on('end', () => {
|
|
66
|
-
if (res.statusCode !== 200) {
|
|
67
|
-
return reject(buildHttpError(url, res, data));
|
|
68
|
-
}
|
|
69
|
-
resolve(data);
|
|
70
|
-
});
|
|
71
|
-
})
|
|
72
|
-
.on('error', reject)
|
|
73
|
-
.on('timeout', () => {
|
|
74
|
-
req.destroy();
|
|
75
|
-
reject(new Error('Request timed out'));
|
|
76
|
-
});
|
|
77
|
-
});
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Fetch a URL and parse the response as YAML.
|
|
82
|
-
* @param {string} url - URL to fetch
|
|
83
|
-
* @param {number} [timeout] - Timeout in ms
|
|
84
|
-
* @returns {Promise<Object>} Parsed YAML content
|
|
85
|
-
*/
|
|
86
|
-
async fetchYaml(url, timeout) {
|
|
87
|
-
const content = await this.fetch(url, timeout);
|
|
88
|
-
return yaml.parse(content);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* Fetch a file from a GitHub repo using the Contents API first,
|
|
93
|
-
* falling back to raw.githubusercontent.com if the API fails.
|
|
94
|
-
*
|
|
95
|
-
* The API endpoint (`api.github.com`) is tried first because corporate
|
|
96
|
-
* proxies commonly block `raw.githubusercontent.com` while allowing
|
|
97
|
-
* `api.github.com` under the "Software Development" category.
|
|
98
|
-
*
|
|
99
|
-
* @param {string} owner - Repository owner (e.g., 'bmad-code-org')
|
|
100
|
-
* @param {string} repo - Repository name (e.g., 'bmad-plugins-marketplace')
|
|
101
|
-
* @param {string} filePath - Path within the repo (e.g., 'registry/official.yaml')
|
|
102
|
-
* @param {string} ref - Git ref (branch, tag, or SHA; e.g., 'main')
|
|
103
|
-
* @param {number} [timeout] - Timeout in ms (overrides default)
|
|
104
|
-
* @returns {Promise<string>} Raw file content
|
|
105
|
-
*/
|
|
106
|
-
async fetchGitHubFile(owner, repo, filePath, ref, timeout) {
|
|
107
|
-
const apiUrl = `https://api.github.com/repos/${owner}/${repo}/contents/${filePath}?ref=${ref}`;
|
|
108
|
-
const rawUrl = `https://raw.githubusercontent.com/${owner}/${repo}/${ref}/${filePath}`;
|
|
109
|
-
|
|
110
|
-
// Try GitHub Contents API first (with raw content accept header)
|
|
111
|
-
try {
|
|
112
|
-
return await this._fetchWithHeaders(apiUrl, { Accept: 'application/vnd.github.raw+json' }, timeout);
|
|
113
|
-
} catch (apiError) {
|
|
114
|
-
// API failed — fall back to raw CDN
|
|
115
|
-
try {
|
|
116
|
-
return await this.fetch(rawUrl, timeout);
|
|
117
|
-
} catch (cdnError) {
|
|
118
|
-
throw new AggregateError([apiError, cdnError], `Both GitHub API and raw CDN failed for ${filePath}`);
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
/**
|
|
124
|
-
* Fetch a file from GitHub and parse as YAML.
|
|
125
|
-
* @param {string} owner - Repository owner
|
|
126
|
-
* @param {string} repo - Repository name
|
|
127
|
-
* @param {string} filePath - Path within the repo
|
|
128
|
-
* @param {string} ref - Git ref
|
|
129
|
-
* @param {number} [timeout] - Timeout in ms
|
|
130
|
-
* @returns {Promise<Object>} Parsed YAML content
|
|
131
|
-
*/
|
|
132
|
-
async fetchGitHubYaml(owner, repo, filePath, ref, timeout) {
|
|
133
|
-
const content = await this.fetchGitHubFile(owner, repo, filePath, ref, timeout);
|
|
134
|
-
return yaml.parse(content);
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
/**
|
|
138
|
-
* Fetch a URL with custom headers. Used for GitHub API requests.
|
|
139
|
-
* Follows up to 3 redirects.
|
|
140
|
-
* @param {string} url - URL to fetch
|
|
141
|
-
* @param {Object} headers - Request headers
|
|
142
|
-
* @param {number} [timeout] - Timeout in ms
|
|
143
|
-
* @param {number} [maxRedirects=3] - Maximum redirects to follow
|
|
144
|
-
* @returns {Promise<string>} Response body
|
|
145
|
-
* @private
|
|
146
|
-
*/
|
|
147
|
-
_fetchWithHeaders(url, headers, timeout, maxRedirects = 3) {
|
|
148
|
-
const timeoutMs = timeout || this.timeout;
|
|
149
|
-
const parsed = new URL(url);
|
|
150
|
-
const options = {
|
|
151
|
-
hostname: parsed.hostname,
|
|
152
|
-
path: parsed.pathname + parsed.search,
|
|
153
|
-
timeout: timeoutMs,
|
|
154
|
-
headers: {
|
|
155
|
-
'User-Agent': 'bmad-installer',
|
|
156
|
-
...headers,
|
|
157
|
-
},
|
|
158
|
-
};
|
|
159
|
-
|
|
160
|
-
return new Promise((resolve, reject) => {
|
|
161
|
-
const req = https
|
|
162
|
-
.get(options, (res) => {
|
|
163
|
-
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
164
|
-
if (maxRedirects <= 0) {
|
|
165
|
-
return reject(new Error('Too many redirects'));
|
|
166
|
-
}
|
|
167
|
-
return this._fetchWithHeaders(res.headers.location, headers, timeoutMs, maxRedirects - 1).then(resolve, reject);
|
|
168
|
-
}
|
|
169
|
-
let data = '';
|
|
170
|
-
res.on('data', (chunk) => (data += chunk));
|
|
171
|
-
res.on('end', () => {
|
|
172
|
-
if (res.statusCode !== 200) {
|
|
173
|
-
return reject(buildHttpError(url, res, data));
|
|
174
|
-
}
|
|
175
|
-
resolve(data);
|
|
176
|
-
});
|
|
177
|
-
})
|
|
178
|
-
.on('error', reject)
|
|
179
|
-
.on('timeout', () => {
|
|
180
|
-
req.destroy();
|
|
181
|
-
reject(new Error('Request timed out'));
|
|
182
|
-
});
|
|
183
|
-
});
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
module.exports = { RegistryClient };
|