bmad-method 6.3.1-next.2 → 6.3.1-next.21
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/package.json +3 -3
- package/src/bmm-skills/1-analysis/bmad-agent-analyst/SKILL.md +51 -36
- package/src/bmm-skills/1-analysis/bmad-agent-analyst/customize.toml +90 -0
- package/src/bmm-skills/1-analysis/bmad-agent-tech-writer/SKILL.md +50 -33
- package/src/bmm-skills/1-analysis/bmad-agent-tech-writer/customize.toml +81 -0
- package/src/bmm-skills/1-analysis/bmad-document-project/SKILL.md +57 -1
- package/src/bmm-skills/1-analysis/bmad-document-project/customize.toml +41 -0
- package/src/bmm-skills/1-analysis/bmad-document-project/workflows/deep-dive-instructions.md +1 -0
- package/src/bmm-skills/1-analysis/bmad-document-project/workflows/full-scan-instructions.md +1 -0
- package/src/bmm-skills/1-analysis/bmad-prfaq/SKILL.md +48 -9
- package/src/bmm-skills/1-analysis/bmad-prfaq/customize.toml +41 -0
- package/src/bmm-skills/1-analysis/bmad-prfaq/references/verdict.md +4 -0
- package/src/bmm-skills/1-analysis/bmad-product-brief/SKILL.md +44 -9
- package/src/bmm-skills/1-analysis/bmad-product-brief/customize.toml +47 -0
- package/src/bmm-skills/1-analysis/bmad-product-brief/prompts/contextual-discovery.md +8 -7
- package/src/bmm-skills/1-analysis/bmad-product-brief/prompts/draft-and-review.md +6 -5
- package/src/bmm-skills/1-analysis/bmad-product-brief/prompts/finalize.md +4 -1
- package/src/bmm-skills/1-analysis/bmad-product-brief/prompts/guided-elicitation.md +3 -2
- package/src/bmm-skills/1-analysis/research/bmad-domain-research/SKILL.md +91 -1
- package/src/bmm-skills/1-analysis/research/bmad-domain-research/customize.toml +41 -0
- package/src/bmm-skills/1-analysis/research/bmad-domain-research/domain-steps/step-06-research-synthesis.md +6 -0
- package/src/bmm-skills/1-analysis/research/bmad-market-research/SKILL.md +91 -1
- package/src/bmm-skills/1-analysis/research/bmad-market-research/customize.toml +41 -0
- package/src/bmm-skills/1-analysis/research/bmad-market-research/steps/step-06-research-completion.md +6 -0
- package/src/bmm-skills/1-analysis/research/bmad-technical-research/SKILL.md +91 -1
- package/src/bmm-skills/1-analysis/research/bmad-technical-research/customize.toml +41 -0
- package/src/bmm-skills/1-analysis/research/bmad-technical-research/technical-steps/step-06-research-synthesis.md +6 -0
- package/src/bmm-skills/2-plan-workflows/bmad-agent-pm/SKILL.md +50 -35
- package/src/bmm-skills/2-plan-workflows/bmad-agent-pm/customize.toml +85 -0
- package/src/bmm-skills/2-plan-workflows/bmad-agent-ux-designer/SKILL.md +50 -31
- package/src/bmm-skills/2-plan-workflows/bmad-agent-ux-designer/customize.toml +60 -0
- package/src/bmm-skills/2-plan-workflows/bmad-create-prd/SKILL.md +99 -1
- package/src/bmm-skills/2-plan-workflows/bmad-create-prd/customize.toml +41 -0
- package/src/bmm-skills/2-plan-workflows/bmad-create-prd/steps-c/step-08-scoping.md +70 -23
- package/src/bmm-skills/2-plan-workflows/bmad-create-prd/steps-c/step-11-polish.md +1 -1
- package/src/bmm-skills/2-plan-workflows/bmad-create-prd/steps-c/step-12-complete.md +6 -0
- package/src/bmm-skills/2-plan-workflows/bmad-create-ux-design/SKILL.md +70 -1
- package/src/bmm-skills/2-plan-workflows/bmad-create-ux-design/customize.toml +41 -0
- package/src/bmm-skills/2-plan-workflows/bmad-create-ux-design/steps/step-14-complete.md +6 -0
- package/src/bmm-skills/2-plan-workflows/bmad-edit-prd/SKILL.md +97 -1
- package/src/bmm-skills/2-plan-workflows/bmad-edit-prd/customize.toml +42 -0
- package/src/bmm-skills/2-plan-workflows/bmad-edit-prd/steps-e/step-e-04-complete.md +2 -0
- package/src/bmm-skills/2-plan-workflows/bmad-validate-prd/SKILL.md +99 -1
- package/src/bmm-skills/2-plan-workflows/bmad-validate-prd/customize.toml +42 -0
- package/src/bmm-skills/2-plan-workflows/bmad-validate-prd/steps-v/step-v-13-report-complete.md +1 -0
- package/src/bmm-skills/3-solutioning/bmad-agent-architect/SKILL.md +50 -30
- package/src/bmm-skills/3-solutioning/bmad-agent-architect/customize.toml +65 -0
- package/src/bmm-skills/3-solutioning/bmad-check-implementation-readiness/SKILL.md +86 -1
- package/src/bmm-skills/3-solutioning/bmad-check-implementation-readiness/customize.toml +41 -0
- package/src/bmm-skills/3-solutioning/bmad-check-implementation-readiness/steps/step-06-final-assessment.md +6 -0
- package/src/bmm-skills/3-solutioning/bmad-create-architecture/SKILL.md +69 -1
- package/src/bmm-skills/3-solutioning/bmad-create-architecture/customize.toml +41 -0
- package/src/bmm-skills/3-solutioning/bmad-create-architecture/steps/step-08-complete.md +6 -0
- package/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/SKILL.md +88 -1
- package/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/customize.toml +41 -0
- package/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/steps/step-04-final-validation.md +6 -0
- package/src/bmm-skills/3-solutioning/bmad-generate-project-context/SKILL.md +76 -1
- package/src/bmm-skills/3-solutioning/bmad-generate-project-context/customize.toml +41 -0
- package/src/bmm-skills/3-solutioning/bmad-generate-project-context/steps/step-03-complete.md +6 -0
- package/src/bmm-skills/4-implementation/bmad-agent-dev/SKILL.md +48 -43
- package/src/bmm-skills/4-implementation/bmad-agent-dev/customize.toml +90 -0
- package/src/bmm-skills/4-implementation/bmad-correct-course/SKILL.md +296 -1
- package/src/bmm-skills/4-implementation/bmad-correct-course/customize.toml +41 -0
- package/src/bmm-skills/4-implementation/bmad-create-story/SKILL.md +412 -1
- package/src/bmm-skills/4-implementation/bmad-create-story/customize.toml +41 -0
- package/src/bmm-skills/4-implementation/bmad-qa-generate-e2e-tests/SKILL.md +171 -1
- package/src/bmm-skills/4-implementation/bmad-qa-generate-e2e-tests/customize.toml +41 -0
- package/src/bmm-skills/4-implementation/bmad-retrospective/SKILL.md +1507 -1
- package/src/bmm-skills/4-implementation/bmad-retrospective/customize.toml +41 -0
- package/src/bmm-skills/module.yaml +49 -0
- package/src/core-skills/bmad-advanced-elicitation/SKILL.md +7 -1
- package/src/core-skills/bmad-customize/SKILL.md +111 -0
- package/src/core-skills/bmad-customize/scripts/list_customizable_skills.py +231 -0
- package/src/core-skills/bmad-customize/scripts/tests/test_list_customizable_skills.py +249 -0
- package/src/core-skills/bmad-distillator/resources/distillate-format-reference.md +1 -1
- package/src/core-skills/bmad-party-mode/SKILL.md +13 -10
- package/src/core-skills/module-help.csv +1 -0
- package/src/core-skills/module.yaml +3 -0
- package/src/scripts/resolve_config.py +176 -0
- package/src/scripts/resolve_customization.py +230 -0
- package/tools/installer/cli-utils.js +0 -137
- package/tools/installer/commands/install.js +13 -0
- package/tools/installer/commands/status.js +1 -1
- package/tools/installer/commands/uninstall.js +1 -1
- package/tools/installer/core/config.js +4 -1
- package/tools/installer/core/existing-install.js +1 -1
- package/tools/installer/core/install-paths.js +12 -6
- package/tools/installer/core/installer.js +182 -95
- package/tools/installer/core/manifest-generator.js +347 -190
- package/tools/installer/core/manifest.js +49 -642
- package/tools/installer/file-ops.js +1 -1
- package/tools/installer/fs-native.js +116 -0
- package/tools/installer/ide/_config-driven.js +1 -1
- package/tools/installer/ide/platform-codes.js +1 -1
- package/tools/installer/ide/shared/path-utils.js +0 -145
- package/tools/installer/ide/shared/skill-manifest.js +1 -1
- package/tools/installer/message-loader.js +1 -1
- package/tools/installer/modules/channel-plan.js +203 -0
- package/tools/installer/modules/channel-resolver.js +241 -0
- package/tools/installer/modules/community-manager.js +131 -24
- package/tools/installer/modules/custom-module-manager.js +161 -47
- package/tools/installer/modules/external-manager.js +236 -73
- package/tools/installer/modules/official-modules.js +61 -63
- package/tools/installer/modules/plugin-resolver.js +1 -1
- package/tools/installer/modules/registry-client.js +133 -12
- package/tools/installer/modules/registry-fallback.yaml +8 -0
- package/tools/installer/modules/version-resolver.js +336 -0
- package/tools/installer/project-root.js +55 -1
- package/tools/installer/prompts.js +0 -106
- package/tools/installer/ui.js +457 -51
- package/tools/migrate-custom-module-paths.js +1 -1
- package/src/bmm-skills/1-analysis/bmad-agent-analyst/bmad-skill-manifest.yaml +0 -11
- package/src/bmm-skills/1-analysis/bmad-agent-tech-writer/bmad-skill-manifest.yaml +0 -11
- package/src/bmm-skills/1-analysis/bmad-document-project/workflow.md +0 -25
- package/src/bmm-skills/1-analysis/research/bmad-domain-research/workflow.md +0 -51
- package/src/bmm-skills/1-analysis/research/bmad-market-research/workflow.md +0 -51
- package/src/bmm-skills/1-analysis/research/bmad-technical-research/workflow.md +0 -52
- package/src/bmm-skills/2-plan-workflows/bmad-agent-pm/bmad-skill-manifest.yaml +0 -11
- package/src/bmm-skills/2-plan-workflows/bmad-agent-ux-designer/bmad-skill-manifest.yaml +0 -11
- package/src/bmm-skills/2-plan-workflows/bmad-create-prd/workflow.md +0 -61
- package/src/bmm-skills/2-plan-workflows/bmad-create-ux-design/workflow.md +0 -35
- package/src/bmm-skills/2-plan-workflows/bmad-edit-prd/workflow.md +0 -62
- package/src/bmm-skills/2-plan-workflows/bmad-validate-prd/workflow.md +0 -61
- package/src/bmm-skills/3-solutioning/bmad-agent-architect/bmad-skill-manifest.yaml +0 -11
- package/src/bmm-skills/3-solutioning/bmad-check-implementation-readiness/workflow.md +0 -47
- package/src/bmm-skills/3-solutioning/bmad-create-architecture/workflow.md +0 -32
- package/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/workflow.md +0 -51
- package/src/bmm-skills/3-solutioning/bmad-generate-project-context/workflow.md +0 -39
- package/src/bmm-skills/4-implementation/bmad-agent-dev/bmad-skill-manifest.yaml +0 -11
- package/src/bmm-skills/4-implementation/bmad-correct-course/workflow.md +0 -267
- package/src/bmm-skills/4-implementation/bmad-create-story/workflow.md +0 -380
- package/src/bmm-skills/4-implementation/bmad-qa-generate-e2e-tests/workflow.md +0 -136
- package/src/bmm-skills/4-implementation/bmad-retrospective/workflow.md +0 -1479
- package/tools/installer/ide/shared/agent-command-generator.js +0 -180
- package/tools/installer/ide/shared/bmad-artifacts.js +0 -208
- package/tools/installer/ide/shared/module-injections.js +0 -136
- package/tools/installer/ide/templates/agent-command-template.md +0 -14
- package/tools/installer/ide/templates/combined/antigravity.md +0 -8
- package/tools/installer/ide/templates/combined/default-agent.md +0 -15
- package/tools/installer/ide/templates/combined/default-task.md +0 -10
- package/tools/installer/ide/templates/combined/default-tool.md +0 -10
- package/tools/installer/ide/templates/combined/default-workflow.md +0 -6
- package/tools/installer/ide/templates/combined/gemini-agent.toml +0 -14
- package/tools/installer/ide/templates/combined/gemini-task.toml +0 -11
- package/tools/installer/ide/templates/combined/gemini-tool.toml +0 -11
- package/tools/installer/ide/templates/combined/gemini-workflow-yaml.toml +0 -16
- package/tools/installer/ide/templates/combined/gemini-workflow.toml +0 -14
- package/tools/installer/ide/templates/combined/kiro-agent.md +0 -16
- package/tools/installer/ide/templates/combined/kiro-task.md +0 -9
- package/tools/installer/ide/templates/combined/kiro-tool.md +0 -9
- package/tools/installer/ide/templates/combined/kiro-workflow.md +0 -7
- package/tools/installer/ide/templates/combined/opencode-agent.md +0 -15
- package/tools/installer/ide/templates/combined/opencode-task.md +0 -13
- package/tools/installer/ide/templates/combined/opencode-tool.md +0 -13
- package/tools/installer/ide/templates/combined/opencode-workflow-yaml.md +0 -16
- package/tools/installer/ide/templates/combined/opencode-workflow.md +0 -16
- package/tools/installer/ide/templates/combined/rovodev.md +0 -9
- package/tools/installer/ide/templates/combined/trae.md +0 -9
- package/tools/installer/ide/templates/combined/windsurf-workflow.md +0 -10
- package/tools/installer/ide/templates/split/.gitkeep +0 -0
|
@@ -1,6 +1,37 @@
|
|
|
1
1
|
const https = require('node:https');
|
|
2
2
|
const yaml = require('yaml');
|
|
3
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
|
+
|
|
4
35
|
/**
|
|
5
36
|
* Shared HTTP client for fetching registry data from GitHub.
|
|
6
37
|
* Used by ExternalModuleManager, CommunityModuleManager, and CustomModuleManager.
|
|
@@ -12,25 +43,31 @@ class RegistryClient {
|
|
|
12
43
|
|
|
13
44
|
/**
|
|
14
45
|
* Fetch a URL and return the response body as a string.
|
|
15
|
-
* Follows
|
|
46
|
+
* Follows up to 3 redirects (GitHub sometimes 301s).
|
|
16
47
|
* @param {string} url - URL to fetch
|
|
17
48
|
* @param {number} [timeout] - Timeout in ms (overrides default)
|
|
49
|
+
* @param {number} [maxRedirects=3] - Maximum redirects to follow
|
|
18
50
|
* @returns {Promise<string>} Response body
|
|
19
51
|
*/
|
|
20
|
-
fetch(url, timeout) {
|
|
52
|
+
fetch(url, timeout, maxRedirects = 3) {
|
|
21
53
|
const timeoutMs = timeout || this.timeout;
|
|
22
54
|
return new Promise((resolve, reject) => {
|
|
23
55
|
const req = https
|
|
24
56
|
.get(url, { timeout: timeoutMs }, (res) => {
|
|
25
57
|
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
return
|
|
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);
|
|
30
62
|
}
|
|
31
63
|
let data = '';
|
|
32
64
|
res.on('data', (chunk) => (data += chunk));
|
|
33
|
-
res.on('end', () =>
|
|
65
|
+
res.on('end', () => {
|
|
66
|
+
if (res.statusCode !== 200) {
|
|
67
|
+
return reject(buildHttpError(url, res, data));
|
|
68
|
+
}
|
|
69
|
+
resolve(data);
|
|
70
|
+
});
|
|
34
71
|
})
|
|
35
72
|
.on('error', reject)
|
|
36
73
|
.on('timeout', () => {
|
|
@@ -52,14 +89,98 @@ class RegistryClient {
|
|
|
52
89
|
}
|
|
53
90
|
|
|
54
91
|
/**
|
|
55
|
-
* Fetch a
|
|
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.
|
|
56
140
|
* @param {string} url - URL to fetch
|
|
141
|
+
* @param {Object} headers - Request headers
|
|
57
142
|
* @param {number} [timeout] - Timeout in ms
|
|
58
|
-
* @
|
|
143
|
+
* @param {number} [maxRedirects=3] - Maximum redirects to follow
|
|
144
|
+
* @returns {Promise<string>} Response body
|
|
145
|
+
* @private
|
|
59
146
|
*/
|
|
60
|
-
|
|
61
|
-
const
|
|
62
|
-
|
|
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
|
+
});
|
|
63
184
|
}
|
|
64
185
|
}
|
|
65
186
|
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
# Fallback module registry — used only when the BMad Marketplace repo
|
|
2
2
|
# (bmad-code-org/bmad-plugins-marketplace) is unreachable.
|
|
3
3
|
# The remote registry/official.yaml is the source of truth.
|
|
4
|
+
#
|
|
5
|
+
# default_channel (optional) — the install channel when the user does not
|
|
6
|
+
# override with --channel/--pin/--next. Valid values: stable | next.
|
|
7
|
+
# Omit to inherit the installer's hardcoded default (stable).
|
|
4
8
|
|
|
5
9
|
modules:
|
|
6
10
|
bmad-builder:
|
|
@@ -12,6 +16,7 @@ modules:
|
|
|
12
16
|
defaultSelected: false
|
|
13
17
|
type: bmad-org
|
|
14
18
|
npmPackage: bmad-builder
|
|
19
|
+
default_channel: stable
|
|
15
20
|
|
|
16
21
|
bmad-creative-intelligence-suite:
|
|
17
22
|
url: https://github.com/bmad-code-org/bmad-module-creative-intelligence-suite
|
|
@@ -22,6 +27,7 @@ modules:
|
|
|
22
27
|
defaultSelected: false
|
|
23
28
|
type: bmad-org
|
|
24
29
|
npmPackage: bmad-creative-intelligence-suite
|
|
30
|
+
default_channel: stable
|
|
25
31
|
|
|
26
32
|
bmad-game-dev-studio:
|
|
27
33
|
url: https://github.com/bmad-code-org/bmad-module-game-dev-studio.git
|
|
@@ -32,6 +38,7 @@ modules:
|
|
|
32
38
|
defaultSelected: false
|
|
33
39
|
type: bmad-org
|
|
34
40
|
npmPackage: bmad-game-dev-studio
|
|
41
|
+
default_channel: stable
|
|
35
42
|
|
|
36
43
|
bmad-method-test-architecture-enterprise:
|
|
37
44
|
url: https://github.com/bmad-code-org/bmad-method-test-architecture-enterprise
|
|
@@ -42,3 +49,4 @@ modules:
|
|
|
42
49
|
defaultSelected: false
|
|
43
50
|
type: bmad-org
|
|
44
51
|
npmPackage: bmad-method-test-architecture-enterprise
|
|
52
|
+
default_channel: stable
|
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
const path = require('node:path');
|
|
2
|
+
const semver = require('semver');
|
|
3
|
+
const yaml = require('yaml');
|
|
4
|
+
const fs = require('../fs-native');
|
|
5
|
+
const { getExternalModuleCachePath, getModulePath, resolveInstalledModuleYaml } = require('../project-root');
|
|
6
|
+
|
|
7
|
+
const DEFAULT_PARENT_DEPTH = 8;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Resolve a module version from authoritative on-disk metadata.
|
|
11
|
+
* Preference order:
|
|
12
|
+
* 1. package.json nearest the module source/cache root
|
|
13
|
+
* 2. module.yaml in the module source directory
|
|
14
|
+
* 3. .claude-plugin/marketplace.json
|
|
15
|
+
* 4. caller-provided fallback version
|
|
16
|
+
*
|
|
17
|
+
* @param {string} moduleName - Module code/name
|
|
18
|
+
* @param {Object} [options]
|
|
19
|
+
* @param {string} [options.moduleSourcePath] - Directory containing module.yaml
|
|
20
|
+
* @param {string} [options.fallbackVersion] - Final fallback when no metadata is found
|
|
21
|
+
* @param {string[]} [options.marketplacePluginNames] - Preferred marketplace plugin names
|
|
22
|
+
* @returns {Promise<{version: string|null, source: string|null, path: string|null}>}
|
|
23
|
+
*/
|
|
24
|
+
async function resolveModuleVersion(moduleName, options = {}) {
|
|
25
|
+
const moduleSourcePath = await normalizeDirectoryPath(options.moduleSourcePath);
|
|
26
|
+
const packageJsonPath = await findPackageJsonPath(moduleName, moduleSourcePath);
|
|
27
|
+
|
|
28
|
+
if (packageJsonPath) {
|
|
29
|
+
const packageVersion = await readPackageJsonVersion(packageJsonPath);
|
|
30
|
+
if (packageVersion) {
|
|
31
|
+
return {
|
|
32
|
+
version: packageVersion,
|
|
33
|
+
source: 'package.json',
|
|
34
|
+
path: packageJsonPath,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const moduleYamlPath = await findModuleYamlPath(moduleName, moduleSourcePath);
|
|
40
|
+
if (moduleYamlPath) {
|
|
41
|
+
const moduleVersion = await readModuleYamlVersion(moduleYamlPath);
|
|
42
|
+
if (moduleVersion) {
|
|
43
|
+
return {
|
|
44
|
+
version: moduleVersion,
|
|
45
|
+
source: 'module.yaml',
|
|
46
|
+
path: moduleYamlPath,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const marketplaceVersion = await findMarketplaceVersion(moduleName, moduleSourcePath, options.marketplacePluginNames || []);
|
|
52
|
+
if (marketplaceVersion) {
|
|
53
|
+
return marketplaceVersion;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const fallbackVersion = normalizeVersion(options.fallbackVersion);
|
|
57
|
+
if (fallbackVersion) {
|
|
58
|
+
return {
|
|
59
|
+
version: fallbackVersion,
|
|
60
|
+
source: 'fallback',
|
|
61
|
+
path: null,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
version: null,
|
|
67
|
+
source: null,
|
|
68
|
+
path: null,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async function findPackageJsonPath(moduleName, moduleSourcePath) {
|
|
73
|
+
const roots = await buildSearchRoots(moduleName, moduleSourcePath);
|
|
74
|
+
|
|
75
|
+
for (const root of roots) {
|
|
76
|
+
const packageJsonPath = await findNearestUpwardFile(root.searchDir, 'package.json', { boundaryDir: root.boundaryDir });
|
|
77
|
+
if (packageJsonPath) {
|
|
78
|
+
return packageJsonPath;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async function findModuleYamlPath(moduleName, moduleSourcePath) {
|
|
86
|
+
if (moduleSourcePath) {
|
|
87
|
+
const directModuleYamlPath = path.join(moduleSourcePath, 'module.yaml');
|
|
88
|
+
if (await fs.pathExists(directModuleYamlPath)) {
|
|
89
|
+
return directModuleYamlPath;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return resolveInstalledModuleYaml(moduleName);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async function findMarketplaceVersion(moduleName, moduleSourcePath, marketplacePluginNames) {
|
|
97
|
+
const roots = await buildSearchRoots(moduleName, moduleSourcePath);
|
|
98
|
+
|
|
99
|
+
for (const root of roots) {
|
|
100
|
+
const marketplacePath = await findNearestUpwardFile(root.searchDir, path.join('.claude-plugin', 'marketplace.json'), {
|
|
101
|
+
boundaryDir: root.boundaryDir,
|
|
102
|
+
});
|
|
103
|
+
if (!marketplacePath) {
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const data = await readJsonFile(marketplacePath);
|
|
108
|
+
if (!data) {
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const version = extractMarketplaceVersion(data, moduleName, marketplacePluginNames);
|
|
113
|
+
if (version) {
|
|
114
|
+
return {
|
|
115
|
+
version,
|
|
116
|
+
source: 'marketplace.json',
|
|
117
|
+
path: marketplacePath,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async function buildSearchRoots(moduleName, moduleSourcePath) {
|
|
126
|
+
const roots = [];
|
|
127
|
+
const seen = new Set();
|
|
128
|
+
|
|
129
|
+
const addRoot = async (candidate) => {
|
|
130
|
+
const normalized = await normalizeExistingDirectory(candidate);
|
|
131
|
+
if (!normalized || seen.has(normalized)) {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
seen.add(normalized);
|
|
136
|
+
roots.push({
|
|
137
|
+
searchDir: normalized,
|
|
138
|
+
boundaryDir: await findSearchBoundary(normalized),
|
|
139
|
+
});
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
await addRoot(moduleSourcePath);
|
|
143
|
+
|
|
144
|
+
if (moduleName === 'core' || moduleName === 'bmm') {
|
|
145
|
+
await addRoot(getModulePath(moduleName));
|
|
146
|
+
} else {
|
|
147
|
+
await addRoot(getExternalModuleCachePath(moduleName));
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return roots;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async function findNearestUpwardFile(startDir, relativeFilePath, options = {}) {
|
|
154
|
+
const normalizedStartDir = await normalizeExistingDirectory(startDir);
|
|
155
|
+
if (!normalizedStartDir) {
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const maxDepth = options.maxDepth ?? DEFAULT_PARENT_DEPTH;
|
|
160
|
+
const normalizedBoundaryDir = await normalizeDirectoryPath(options.boundaryDir);
|
|
161
|
+
let currentDir = normalizedStartDir;
|
|
162
|
+
for (let depth = 0; depth <= maxDepth; depth++) {
|
|
163
|
+
const candidate = path.join(currentDir, relativeFilePath);
|
|
164
|
+
if (await fs.pathExists(candidate)) {
|
|
165
|
+
return candidate;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (normalizedBoundaryDir && currentDir === normalizedBoundaryDir) {
|
|
169
|
+
break;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const parentDir = path.dirname(currentDir);
|
|
173
|
+
if (parentDir === currentDir) {
|
|
174
|
+
break;
|
|
175
|
+
}
|
|
176
|
+
currentDir = parentDir;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
async function findSearchBoundary(startDir) {
|
|
183
|
+
const normalizedStartDir = await normalizeExistingDirectory(startDir);
|
|
184
|
+
if (!normalizedStartDir) {
|
|
185
|
+
return null;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
let currentDir = normalizedStartDir;
|
|
189
|
+
for (let depth = 0; depth <= DEFAULT_PARENT_DEPTH; depth++) {
|
|
190
|
+
if (
|
|
191
|
+
(await fs.pathExists(path.join(currentDir, 'package.json'))) ||
|
|
192
|
+
(await fs.pathExists(path.join(currentDir, '.claude-plugin', 'marketplace.json'))) ||
|
|
193
|
+
(await fs.pathExists(path.join(currentDir, '.git')))
|
|
194
|
+
) {
|
|
195
|
+
return currentDir;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const parentDir = path.dirname(currentDir);
|
|
199
|
+
if (parentDir === currentDir) {
|
|
200
|
+
break;
|
|
201
|
+
}
|
|
202
|
+
currentDir = parentDir;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return normalizedStartDir;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
async function normalizeDirectoryPath(candidate) {
|
|
209
|
+
if (!candidate) {
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const resolvedPath = path.resolve(candidate);
|
|
214
|
+
try {
|
|
215
|
+
const stats = await fs.stat(resolvedPath);
|
|
216
|
+
return stats.isDirectory() ? resolvedPath : path.dirname(resolvedPath);
|
|
217
|
+
} catch {
|
|
218
|
+
return resolvedPath;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
async function normalizeExistingDirectory(candidate) {
|
|
223
|
+
const normalized = await normalizeDirectoryPath(candidate);
|
|
224
|
+
if (!normalized) {
|
|
225
|
+
return null;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (!(await fs.pathExists(normalized))) {
|
|
229
|
+
return null;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return normalized;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
async function readPackageJsonVersion(packageJsonPath) {
|
|
236
|
+
const data = await readJsonFile(packageJsonPath);
|
|
237
|
+
return normalizeVersion(data?.version);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
async function readModuleYamlVersion(moduleYamlPath) {
|
|
241
|
+
try {
|
|
242
|
+
const content = await fs.readFile(moduleYamlPath, 'utf8');
|
|
243
|
+
const data = yaml.parse(content);
|
|
244
|
+
return normalizeVersion(data?.version || data?.module_version || data?.moduleVersion);
|
|
245
|
+
} catch {
|
|
246
|
+
return null;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
async function readJsonFile(filePath) {
|
|
251
|
+
try {
|
|
252
|
+
const content = await fs.readFile(filePath, 'utf8');
|
|
253
|
+
return JSON.parse(content);
|
|
254
|
+
} catch {
|
|
255
|
+
return null;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function extractMarketplaceVersion(data, moduleName, marketplacePluginNames = []) {
|
|
260
|
+
const plugins = Array.isArray(data?.plugins) ? data.plugins : [];
|
|
261
|
+
if (plugins.length === 0) {
|
|
262
|
+
return null;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const preferredNames = new Set(
|
|
266
|
+
[moduleName, ...marketplacePluginNames]
|
|
267
|
+
.filter((value) => typeof value === 'string')
|
|
268
|
+
.map((value) => value.trim())
|
|
269
|
+
.filter(Boolean),
|
|
270
|
+
);
|
|
271
|
+
|
|
272
|
+
const exactMatches = [];
|
|
273
|
+
const fallbackVersions = [];
|
|
274
|
+
|
|
275
|
+
for (const plugin of plugins) {
|
|
276
|
+
const version = normalizeVersion(plugin?.version);
|
|
277
|
+
if (!version) {
|
|
278
|
+
continue;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
fallbackVersions.push(version);
|
|
282
|
+
|
|
283
|
+
const pluginNames = [plugin?.name, plugin?.code].filter((value) => typeof value === 'string').map((value) => value.trim());
|
|
284
|
+
if (pluginNames.some((name) => preferredNames.has(name))) {
|
|
285
|
+
exactMatches.push(version);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
return pickBestVersion(exactMatches.length > 0 ? exactMatches : fallbackVersions);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function pickBestVersion(versions) {
|
|
293
|
+
const candidates = versions.map(normalizeVersion).filter(Boolean);
|
|
294
|
+
if (candidates.length === 0) {
|
|
295
|
+
return null;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
candidates.sort(compareVersionsDescending);
|
|
299
|
+
return candidates[0];
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function compareVersionsDescending(left, right) {
|
|
303
|
+
const leftSemver = normalizeSemver(left);
|
|
304
|
+
const rightSemver = normalizeSemver(right);
|
|
305
|
+
|
|
306
|
+
if (leftSemver && rightSemver) {
|
|
307
|
+
return semver.rcompare(leftSemver, rightSemver);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
if (leftSemver) {
|
|
311
|
+
return -1;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
if (rightSemver) {
|
|
315
|
+
return 1;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
return right.localeCompare(left, undefined, { numeric: true, sensitivity: 'base' });
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
function normalizeSemver(version) {
|
|
322
|
+
return semver.valid(version) || semver.valid(semver.coerce(version));
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
function normalizeVersion(version) {
|
|
326
|
+
if (typeof version !== 'string') {
|
|
327
|
+
return null;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
const trimmed = version.trim();
|
|
331
|
+
return trimmed || null;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
module.exports = {
|
|
335
|
+
resolveModuleVersion,
|
|
336
|
+
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const path = require('node:path');
|
|
2
|
-
const
|
|
2
|
+
const os = require('node:os');
|
|
3
|
+
const fs = require('./fs-native');
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Find the BMAD project root directory by looking for package.json
|
|
@@ -69,9 +70,62 @@ function getModulePath(moduleName, ...segments) {
|
|
|
69
70
|
return getSourcePath('modules', moduleName, ...segments);
|
|
70
71
|
}
|
|
71
72
|
|
|
73
|
+
/**
|
|
74
|
+
* Path to the local external-module clone cache.
|
|
75
|
+
* External official modules (bmb, cis, gds, tea, wds, etc.) are cloned here
|
|
76
|
+
* by ExternalModuleManager during install and are not copied into <src>/modules/.
|
|
77
|
+
*/
|
|
78
|
+
function getExternalModuleCachePath(moduleName, ...segments) {
|
|
79
|
+
const base = process.env.BMAD_EXTERNAL_MODULES_CACHE || path.join(os.homedir(), '.bmad', 'cache', 'external-modules');
|
|
80
|
+
return path.join(base, moduleName, ...segments);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Locate an installed module's `module.yaml` by filesystem lookup only.
|
|
85
|
+
*
|
|
86
|
+
* Built-in modules (core, bmm) live under <src>. External official modules are
|
|
87
|
+
* cloned into ~/.bmad/cache/external-modules/<name>/ with varying internal
|
|
88
|
+
* layouts (some at src/module.yaml, some at skills/module.yaml, some nested).
|
|
89
|
+
* This mirrors the candidate-path search in
|
|
90
|
+
* ExternalModuleManager.findExternalModuleSource but performs no git/network
|
|
91
|
+
* work, which keeps it safe to call during manifest writing.
|
|
92
|
+
*
|
|
93
|
+
* @param {string} moduleName
|
|
94
|
+
* @returns {Promise<string|null>} Absolute path to module.yaml, or null if not found.
|
|
95
|
+
*/
|
|
96
|
+
async function resolveInstalledModuleYaml(moduleName) {
|
|
97
|
+
const builtIn = path.join(getModulePath(moduleName), 'module.yaml');
|
|
98
|
+
if (await fs.pathExists(builtIn)) return builtIn;
|
|
99
|
+
|
|
100
|
+
const cacheRoot = getExternalModuleCachePath(moduleName);
|
|
101
|
+
if (!(await fs.pathExists(cacheRoot))) return null;
|
|
102
|
+
|
|
103
|
+
for (const dir of ['skills', 'src']) {
|
|
104
|
+
const direct = path.join(cacheRoot, dir, 'module.yaml');
|
|
105
|
+
if (await fs.pathExists(direct)) return direct;
|
|
106
|
+
|
|
107
|
+
const dirPath = path.join(cacheRoot, dir);
|
|
108
|
+
if (await fs.pathExists(dirPath)) {
|
|
109
|
+
const entries = await fs.readdir(dirPath, { withFileTypes: true });
|
|
110
|
+
for (const entry of entries) {
|
|
111
|
+
if (!entry.isDirectory()) continue;
|
|
112
|
+
const nested = path.join(dirPath, entry.name, 'module.yaml');
|
|
113
|
+
if (await fs.pathExists(nested)) return nested;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const atRoot = path.join(cacheRoot, 'module.yaml');
|
|
119
|
+
if (await fs.pathExists(atRoot)) return atRoot;
|
|
120
|
+
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
|
|
72
124
|
module.exports = {
|
|
73
125
|
getProjectRoot,
|
|
74
126
|
getSourcePath,
|
|
75
127
|
getModulePath,
|
|
128
|
+
getExternalModuleCachePath,
|
|
129
|
+
resolveInstalledModuleYaml,
|
|
76
130
|
findProjectRoot,
|
|
77
131
|
};
|