bmad-method 6.3.1-next.1 → 6.3.1-next.11
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 +1 -2
- package/src/bmm-skills/1-analysis/bmad-agent-analyst/SKILL.md +47 -36
- package/src/bmm-skills/1-analysis/bmad-agent-analyst/customize.yaml +44 -0
- package/src/bmm-skills/1-analysis/bmad-agent-tech-writer/SKILL.md +46 -33
- package/src/bmm-skills/1-analysis/bmad-agent-tech-writer/customize.yaml +38 -0
- package/src/bmm-skills/1-analysis/bmad-product-brief/SKILL.md +22 -3
- package/src/bmm-skills/1-analysis/bmad-product-brief/customize.yaml +6 -0
- package/src/bmm-skills/1-analysis/bmad-product-brief/prompts/contextual-discovery.md +7 -7
- package/src/bmm-skills/1-analysis/bmad-product-brief/prompts/draft-and-review.md +5 -5
- package/src/bmm-skills/1-analysis/bmad-product-brief/prompts/finalize.md +3 -1
- package/src/bmm-skills/1-analysis/bmad-product-brief/prompts/guided-elicitation.md +2 -2
- package/src/bmm-skills/2-plan-workflows/bmad-agent-pm/SKILL.md +46 -35
- package/src/bmm-skills/2-plan-workflows/bmad-agent-pm/customize.yaml +41 -0
- package/src/bmm-skills/2-plan-workflows/bmad-agent-ux-designer/SKILL.md +46 -31
- package/src/bmm-skills/2-plan-workflows/bmad-agent-ux-designer/customize.yaml +26 -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/3-solutioning/bmad-agent-architect/SKILL.md +46 -30
- package/src/bmm-skills/3-solutioning/bmad-agent-architect/customize.yaml +29 -0
- package/src/bmm-skills/4-implementation/bmad-agent-dev/SKILL.md +57 -43
- package/src/bmm-skills/4-implementation/bmad-agent-dev/customize.yaml +44 -0
- package/src/bmm-skills/4-implementation/bmad-quick-dev/step-01-clarify-and-route.md +14 -5
- package/src/bmm-skills/4-implementation/bmad-quick-dev/step-03-implement.md +2 -0
- package/src/bmm-skills/4-implementation/bmad-quick-dev/step-05-present.md +17 -8
- package/src/bmm-skills/4-implementation/bmad-quick-dev/step-oneshot.md +4 -0
- package/src/bmm-skills/4-implementation/bmad-quick-dev/sync-sprint-status.md +19 -0
- package/src/bmm-skills/4-implementation/bmad-quick-dev/workflow.md +1 -0
- package/src/core-skills/module.yaml +1 -0
- package/src/scripts/resolve_customization.py +248 -0
- package/tools/installer/cli-utils.js +0 -137
- package/tools/installer/commands/status.js +1 -1
- package/tools/installer/commands/uninstall.js +1 -1
- package/tools/installer/core/existing-install.js +1 -1
- package/tools/installer/core/install-paths.js +7 -4
- package/tools/installer/core/installer.js +58 -10
- package/tools/installer/core/manifest-generator.js +9 -10
- package/tools/installer/core/manifest.js +1 -578
- 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/community-manager.js +11 -6
- package/tools/installer/modules/custom-module-manager.js +1 -28
- package/tools/installer/modules/external-manager.js +5 -44
- package/tools/installer/modules/official-modules.js +5 -53
- package/tools/installer/modules/plugin-resolver.js +1 -1
- package/tools/installer/modules/registry-client.js +133 -12
- package/tools/installer/project-root.js +1 -1
- package/tools/installer/prompts.js +0 -106
- package/tools/installer/ui.js +45 -12
- package/tools/migrate-custom-module-paths.js +1 -1
- 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
|
|
|
@@ -498,26 +498,6 @@ async function password(options) {
|
|
|
498
498
|
return result;
|
|
499
499
|
}
|
|
500
500
|
|
|
501
|
-
/**
|
|
502
|
-
* Group multiple prompts together
|
|
503
|
-
* @param {Object} prompts - Object of prompt functions
|
|
504
|
-
* @param {Object} [options] - Group options
|
|
505
|
-
* @returns {Promise<Object>} Object with all answers
|
|
506
|
-
*/
|
|
507
|
-
async function group(prompts, options = {}) {
|
|
508
|
-
const clack = await getClack();
|
|
509
|
-
|
|
510
|
-
const result = await clack.group(prompts, {
|
|
511
|
-
onCancel: () => {
|
|
512
|
-
clack.cancel('Operation cancelled');
|
|
513
|
-
process.exit(0);
|
|
514
|
-
},
|
|
515
|
-
...options,
|
|
516
|
-
});
|
|
517
|
-
|
|
518
|
-
return result;
|
|
519
|
-
}
|
|
520
|
-
|
|
521
501
|
/**
|
|
522
502
|
* Run tasks with spinner feedback
|
|
523
503
|
* @param {Array} tasks - Array of task objects [{title, task, enabled?}]
|
|
@@ -578,42 +558,6 @@ async function box(content, title, options) {
|
|
|
578
558
|
clack.box(content, title, options);
|
|
579
559
|
}
|
|
580
560
|
|
|
581
|
-
/**
|
|
582
|
-
* Create a progress bar for visualizing task completion
|
|
583
|
-
* @param {Object} [options] - Progress options (max, style, etc.)
|
|
584
|
-
* @returns {Promise<Object>} Progress controller with start, advance, stop methods
|
|
585
|
-
*/
|
|
586
|
-
async function progress(options) {
|
|
587
|
-
const clack = await getClack();
|
|
588
|
-
return clack.progress(options);
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
/**
|
|
592
|
-
* Create a task log for displaying scrolling subprocess output
|
|
593
|
-
* @param {Object} options - TaskLog options (title, limit, retainLog)
|
|
594
|
-
* @returns {Promise<Object>} TaskLog controller with message, success, error methods
|
|
595
|
-
*/
|
|
596
|
-
async function taskLog(options) {
|
|
597
|
-
const clack = await getClack();
|
|
598
|
-
return clack.taskLog(options);
|
|
599
|
-
}
|
|
600
|
-
|
|
601
|
-
/**
|
|
602
|
-
* File system path prompt with autocomplete
|
|
603
|
-
* @param {Object} options - Path options
|
|
604
|
-
* @param {string} options.message - The prompt message
|
|
605
|
-
* @param {string} [options.initialValue] - Initial path value
|
|
606
|
-
* @param {boolean} [options.directory=false] - Only allow directories
|
|
607
|
-
* @param {Function} [options.validate] - Validation function
|
|
608
|
-
* @returns {Promise<string>} Selected path
|
|
609
|
-
*/
|
|
610
|
-
async function pathPrompt(options) {
|
|
611
|
-
const clack = await getClack();
|
|
612
|
-
const result = await clack.path(options);
|
|
613
|
-
await handleCancel(result);
|
|
614
|
-
return result;
|
|
615
|
-
}
|
|
616
|
-
|
|
617
561
|
/**
|
|
618
562
|
* Autocomplete single-select prompt with type-ahead filtering
|
|
619
563
|
* @param {Object} options - Autocomplete options
|
|
@@ -631,50 +575,6 @@ async function autocomplete(options) {
|
|
|
631
575
|
return result;
|
|
632
576
|
}
|
|
633
577
|
|
|
634
|
-
/**
|
|
635
|
-
* Key-based instant selection prompt
|
|
636
|
-
* @param {Object} options - SelectKey options
|
|
637
|
-
* @param {string} options.message - The prompt message
|
|
638
|
-
* @param {Array} options.options - Array of choices [{value, label, hint?}]
|
|
639
|
-
* @returns {Promise<any>} Selected value
|
|
640
|
-
*/
|
|
641
|
-
async function selectKey(options) {
|
|
642
|
-
const clack = await getClack();
|
|
643
|
-
const result = await clack.selectKey(options);
|
|
644
|
-
await handleCancel(result);
|
|
645
|
-
return result;
|
|
646
|
-
}
|
|
647
|
-
|
|
648
|
-
/**
|
|
649
|
-
* Stream messages with dynamic content (for LLMs, generators, etc.)
|
|
650
|
-
*/
|
|
651
|
-
const stream = {
|
|
652
|
-
async info(generator) {
|
|
653
|
-
const clack = await getClack();
|
|
654
|
-
return clack.stream.info(generator);
|
|
655
|
-
},
|
|
656
|
-
async success(generator) {
|
|
657
|
-
const clack = await getClack();
|
|
658
|
-
return clack.stream.success(generator);
|
|
659
|
-
},
|
|
660
|
-
async step(generator) {
|
|
661
|
-
const clack = await getClack();
|
|
662
|
-
return clack.stream.step(generator);
|
|
663
|
-
},
|
|
664
|
-
async warn(generator) {
|
|
665
|
-
const clack = await getClack();
|
|
666
|
-
return clack.stream.warn(generator);
|
|
667
|
-
},
|
|
668
|
-
async error(generator) {
|
|
669
|
-
const clack = await getClack();
|
|
670
|
-
return clack.stream.error(generator);
|
|
671
|
-
},
|
|
672
|
-
async message(generator, options) {
|
|
673
|
-
const clack = await getClack();
|
|
674
|
-
return clack.stream.message(generator, options);
|
|
675
|
-
},
|
|
676
|
-
};
|
|
677
|
-
|
|
678
578
|
/**
|
|
679
579
|
* Get the color utility (picocolors instance from @clack/prompts)
|
|
680
580
|
* @returns {Promise<Object>} The color utility (picocolors)
|
|
@@ -790,20 +690,14 @@ module.exports = {
|
|
|
790
690
|
note,
|
|
791
691
|
box,
|
|
792
692
|
spinner,
|
|
793
|
-
progress,
|
|
794
|
-
taskLog,
|
|
795
693
|
select,
|
|
796
694
|
multiselect,
|
|
797
695
|
autocompleteMultiselect,
|
|
798
696
|
autocomplete,
|
|
799
|
-
selectKey,
|
|
800
697
|
confirm,
|
|
801
698
|
text,
|
|
802
|
-
path: pathPrompt,
|
|
803
699
|
password,
|
|
804
|
-
group,
|
|
805
700
|
tasks,
|
|
806
701
|
log,
|
|
807
|
-
stream,
|
|
808
702
|
prompt,
|
|
809
703
|
};
|
package/tools/installer/ui.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const path = require('node:path');
|
|
2
2
|
const os = require('node:os');
|
|
3
|
-
const fs = require('fs-
|
|
3
|
+
const fs = require('./fs-native');
|
|
4
4
|
const { CLIUtils } = require('./cli-utils');
|
|
5
5
|
const { ExternalModuleManager } = require('./modules/external-manager');
|
|
6
6
|
const { getProjectRoot } = require('./project-root');
|
|
@@ -598,7 +598,7 @@ class UI {
|
|
|
598
598
|
const officialCodes = new Set(officialSelected);
|
|
599
599
|
const externalManager = new ExternalModuleManager();
|
|
600
600
|
const registryModules = await externalManager.listAvailable();
|
|
601
|
-
const officialRegistryCodes = new Set(registryModules.map((m) => m.code));
|
|
601
|
+
const officialRegistryCodes = new Set(['core', 'bmm', ...registryModules.map((m) => m.code)]);
|
|
602
602
|
const installedNonOfficial = [...installedModuleIds].filter((id) => !officialRegistryCodes.has(id));
|
|
603
603
|
|
|
604
604
|
// Phase 2: Community modules (category drill-down)
|
|
@@ -630,6 +630,11 @@ class UI {
|
|
|
630
630
|
* @returns {Array} Selected official module codes
|
|
631
631
|
*/
|
|
632
632
|
async _selectOfficialModules(installedModuleIds = new Set()) {
|
|
633
|
+
// Built-in modules (core, bmm) come from local source, not the registry
|
|
634
|
+
const { OfficialModules } = require('./modules/official-modules');
|
|
635
|
+
const builtInModules = (await new OfficialModules().listAvailable()).modules || [];
|
|
636
|
+
|
|
637
|
+
// External modules come from the registry (with fallback)
|
|
633
638
|
const externalManager = new ExternalModuleManager();
|
|
634
639
|
const registryModules = await externalManager.listAvailable();
|
|
635
640
|
|
|
@@ -637,20 +642,34 @@ class UI {
|
|
|
637
642
|
const initialValues = [];
|
|
638
643
|
const lockedValues = ['core'];
|
|
639
644
|
|
|
640
|
-
const buildModuleEntry = async (
|
|
641
|
-
const isInstalled = installedModuleIds.has(
|
|
642
|
-
const version = await getMarketplaceVersion(
|
|
643
|
-
const label = version ? `${
|
|
645
|
+
const buildModuleEntry = async (code, name, description, isDefault) => {
|
|
646
|
+
const isInstalled = installedModuleIds.has(code);
|
|
647
|
+
const version = await getMarketplaceVersion(code);
|
|
648
|
+
const label = version ? `${name} (v${version})` : name;
|
|
644
649
|
return {
|
|
645
650
|
label,
|
|
646
|
-
value:
|
|
647
|
-
hint:
|
|
648
|
-
selected: isInstalled,
|
|
651
|
+
value: code,
|
|
652
|
+
hint: description,
|
|
653
|
+
selected: isInstalled || isDefault,
|
|
649
654
|
};
|
|
650
655
|
};
|
|
651
656
|
|
|
657
|
+
// Add built-in modules first (always available regardless of network)
|
|
658
|
+
const builtInCodes = new Set();
|
|
659
|
+
for (const mod of builtInModules) {
|
|
660
|
+
const code = mod.id;
|
|
661
|
+
builtInCodes.add(code);
|
|
662
|
+
const entry = await buildModuleEntry(code, mod.name, mod.description, mod.defaultSelected);
|
|
663
|
+
allOptions.push({ label: entry.label, value: entry.value, hint: entry.hint });
|
|
664
|
+
if (entry.selected) {
|
|
665
|
+
initialValues.push(code);
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
// Add external registry modules (skip built-in duplicates)
|
|
652
670
|
for (const mod of registryModules) {
|
|
653
|
-
|
|
671
|
+
if (mod.builtIn || builtInCodes.has(mod.code)) continue;
|
|
672
|
+
const entry = await buildModuleEntry(mod.code, mod.name, mod.description, mod.defaultSelected);
|
|
654
673
|
allOptions.push({ label: entry.label, value: entry.value, hint: entry.hint });
|
|
655
674
|
if (entry.selected) {
|
|
656
675
|
initialValues.push(mod.code);
|
|
@@ -1122,12 +1141,26 @@ class UI {
|
|
|
1122
1141
|
* @returns {Array} Default module codes
|
|
1123
1142
|
*/
|
|
1124
1143
|
async getDefaultModules(installedModuleIds = new Set()) {
|
|
1125
|
-
|
|
1126
|
-
const
|
|
1144
|
+
// Built-in modules with default_selected come from local source
|
|
1145
|
+
const { OfficialModules } = require('./modules/official-modules');
|
|
1146
|
+
const builtInModules = (await new OfficialModules().listAvailable()).modules || [];
|
|
1127
1147
|
|
|
1128
1148
|
const defaultModules = [];
|
|
1149
|
+
const seen = new Set();
|
|
1150
|
+
|
|
1151
|
+
for (const mod of builtInModules) {
|
|
1152
|
+
if (mod.defaultSelected || installedModuleIds.has(mod.id)) {
|
|
1153
|
+
defaultModules.push(mod.id);
|
|
1154
|
+
seen.add(mod.id);
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
// Add external registry defaults
|
|
1159
|
+
const externalManager = new ExternalModuleManager();
|
|
1160
|
+
const registryModules = await externalManager.listAvailable();
|
|
1129
1161
|
|
|
1130
1162
|
for (const mod of registryModules) {
|
|
1163
|
+
if (mod.builtIn || seen.has(mod.code)) continue;
|
|
1131
1164
|
if (mod.defaultSelected || installedModuleIds.has(mod.code)) {
|
|
1132
1165
|
defaultModules.push(mod.code);
|
|
1133
1166
|
}
|
|
@@ -1,180 +0,0 @@
|
|
|
1
|
-
const path = require('node:path');
|
|
2
|
-
const fs = require('fs-extra');
|
|
3
|
-
const { toColonPath, toDashPath, customAgentColonName, customAgentDashName, BMAD_FOLDER_NAME } = require('./path-utils');
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Generates launcher command files for each agent
|
|
7
|
-
*/
|
|
8
|
-
class AgentCommandGenerator {
|
|
9
|
-
constructor(bmadFolderName = BMAD_FOLDER_NAME) {
|
|
10
|
-
this.templatePath = path.join(__dirname, '../templates/agent-command-template.md');
|
|
11
|
-
this.bmadFolderName = bmadFolderName;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Collect agent artifacts for IDE installation
|
|
16
|
-
* @param {string} bmadDir - BMAD installation directory
|
|
17
|
-
* @param {Array} selectedModules - Modules to include
|
|
18
|
-
* @returns {Object} Artifacts array with metadata
|
|
19
|
-
*/
|
|
20
|
-
async collectAgentArtifacts(bmadDir, selectedModules = []) {
|
|
21
|
-
const { getAgentsFromBmad } = require('./bmad-artifacts');
|
|
22
|
-
|
|
23
|
-
// Get agents from INSTALLED bmad/ directory
|
|
24
|
-
const agents = await getAgentsFromBmad(bmadDir, selectedModules);
|
|
25
|
-
|
|
26
|
-
const artifacts = [];
|
|
27
|
-
|
|
28
|
-
for (const agent of agents) {
|
|
29
|
-
const launcherContent = await this.generateLauncherContent(agent);
|
|
30
|
-
// Use relativePath if available (for nested agents), otherwise just name with .md
|
|
31
|
-
const agentPathInModule = agent.relativePath || `${agent.name}.md`;
|
|
32
|
-
// Calculate the relative agent path (e.g., bmm/agents/pm.md)
|
|
33
|
-
let agentRelPath = agent.path || '';
|
|
34
|
-
// Normalize path separators for cross-platform compatibility
|
|
35
|
-
agentRelPath = agentRelPath.replaceAll('\\', '/');
|
|
36
|
-
// Remove _bmad/ prefix if present to get relative path from project root
|
|
37
|
-
// Handle both absolute paths (/path/to/_bmad/...) and relative paths (_bmad/...)
|
|
38
|
-
if (agentRelPath.includes('_bmad/')) {
|
|
39
|
-
const parts = agentRelPath.split(/_bmad\//);
|
|
40
|
-
if (parts.length > 1) {
|
|
41
|
-
agentRelPath = parts.slice(1).join('/');
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
artifacts.push({
|
|
45
|
-
type: 'agent-launcher',
|
|
46
|
-
name: agent.name,
|
|
47
|
-
description: agent.description || `${agent.name} agent`,
|
|
48
|
-
module: agent.module,
|
|
49
|
-
canonicalId: agent.canonicalId || '',
|
|
50
|
-
relativePath: path.join(agent.module, 'agents', agentPathInModule), // For command filename
|
|
51
|
-
agentPath: agentRelPath, // Relative path to actual agent file
|
|
52
|
-
content: launcherContent,
|
|
53
|
-
sourcePath: agent.path,
|
|
54
|
-
});
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
return {
|
|
58
|
-
artifacts,
|
|
59
|
-
counts: {
|
|
60
|
-
agents: agents.length,
|
|
61
|
-
},
|
|
62
|
-
};
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Generate launcher content for an agent
|
|
67
|
-
* @param {Object} agent - Agent metadata
|
|
68
|
-
* @returns {string} Launcher file content
|
|
69
|
-
*/
|
|
70
|
-
async generateLauncherContent(agent) {
|
|
71
|
-
// Load the template
|
|
72
|
-
const template = await fs.readFile(this.templatePath, 'utf8');
|
|
73
|
-
|
|
74
|
-
// Replace template variables
|
|
75
|
-
// Use relativePath if available (for nested agents), otherwise just name with .md
|
|
76
|
-
const agentPathInModule = agent.relativePath || `${agent.name}.md`;
|
|
77
|
-
return template
|
|
78
|
-
.replaceAll('{{name}}', agent.name)
|
|
79
|
-
.replaceAll('{{module}}', agent.module)
|
|
80
|
-
.replaceAll('{{path}}', agentPathInModule)
|
|
81
|
-
.replaceAll('{{description}}', agent.description || `${agent.name} agent`)
|
|
82
|
-
.replaceAll('_bmad', this.bmadFolderName)
|
|
83
|
-
.replaceAll('_bmad', '_bmad');
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* Write agent launcher artifacts to IDE commands directory
|
|
88
|
-
* @param {string} baseCommandsDir - Base commands directory for the IDE
|
|
89
|
-
* @param {Array} artifacts - Agent launcher artifacts
|
|
90
|
-
* @returns {number} Count of launchers written
|
|
91
|
-
*/
|
|
92
|
-
async writeAgentLaunchers(baseCommandsDir, artifacts) {
|
|
93
|
-
let writtenCount = 0;
|
|
94
|
-
|
|
95
|
-
for (const artifact of artifacts) {
|
|
96
|
-
if (artifact.type === 'agent-launcher') {
|
|
97
|
-
const moduleAgentsDir = path.join(baseCommandsDir, artifact.module, 'agents');
|
|
98
|
-
await fs.ensureDir(moduleAgentsDir);
|
|
99
|
-
|
|
100
|
-
const launcherPath = path.join(moduleAgentsDir, `${artifact.name}.md`);
|
|
101
|
-
await fs.writeFile(launcherPath, artifact.content);
|
|
102
|
-
writtenCount++;
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
return writtenCount;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* Write agent launcher artifacts using underscore format (Windows-compatible)
|
|
111
|
-
* Creates flat files like: bmad_bmm_pm.md
|
|
112
|
-
*
|
|
113
|
-
* @param {string} baseCommandsDir - Base commands directory for the IDE
|
|
114
|
-
* @param {Array} artifacts - Agent launcher artifacts
|
|
115
|
-
* @returns {number} Count of launchers written
|
|
116
|
-
*/
|
|
117
|
-
async writeColonArtifacts(baseCommandsDir, artifacts) {
|
|
118
|
-
let writtenCount = 0;
|
|
119
|
-
|
|
120
|
-
for (const artifact of artifacts) {
|
|
121
|
-
if (artifact.type === 'agent-launcher') {
|
|
122
|
-
// Convert relativePath to underscore format: bmm/agents/pm.md → bmad_bmm_pm.md
|
|
123
|
-
const flatName = toColonPath(artifact.relativePath);
|
|
124
|
-
const launcherPath = path.join(baseCommandsDir, flatName);
|
|
125
|
-
await fs.ensureDir(path.dirname(launcherPath));
|
|
126
|
-
await fs.writeFile(launcherPath, artifact.content);
|
|
127
|
-
writtenCount++;
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
return writtenCount;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
/**
|
|
135
|
-
* Write agent launcher artifacts using dash format (NEW STANDARD)
|
|
136
|
-
* Creates flat files like: bmad-agent-bmm-pm.md
|
|
137
|
-
*
|
|
138
|
-
* The bmad-agent- prefix distinguishes agents from workflows/tasks/tools.
|
|
139
|
-
*
|
|
140
|
-
* @param {string} baseCommandsDir - Base commands directory for the IDE
|
|
141
|
-
* @param {Array} artifacts - Agent launcher artifacts
|
|
142
|
-
* @returns {number} Count of launchers written
|
|
143
|
-
*/
|
|
144
|
-
async writeDashArtifacts(baseCommandsDir, artifacts) {
|
|
145
|
-
let writtenCount = 0;
|
|
146
|
-
|
|
147
|
-
for (const artifact of artifacts) {
|
|
148
|
-
if (artifact.type === 'agent-launcher') {
|
|
149
|
-
// Convert relativePath to dash format: bmm/agents/pm.md → bmad-agent-bmm-pm.md
|
|
150
|
-
const flatName = toDashPath(artifact.relativePath);
|
|
151
|
-
const launcherPath = path.join(baseCommandsDir, flatName);
|
|
152
|
-
await fs.ensureDir(path.dirname(launcherPath));
|
|
153
|
-
await fs.writeFile(launcherPath, artifact.content);
|
|
154
|
-
writtenCount++;
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
return writtenCount;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
/**
|
|
162
|
-
* Get the custom agent name in underscore format (Windows-compatible)
|
|
163
|
-
* @param {string} agentName - Custom agent name
|
|
164
|
-
* @returns {string} Underscore-formatted filename
|
|
165
|
-
*/
|
|
166
|
-
getCustomAgentColonName(agentName) {
|
|
167
|
-
return customAgentColonName(agentName);
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
/**
|
|
171
|
-
* Get the custom agent name in underscore format (Windows-compatible)
|
|
172
|
-
* @param {string} agentName - Custom agent name
|
|
173
|
-
* @returns {string} Underscore-formatted filename
|
|
174
|
-
*/
|
|
175
|
-
getCustomAgentDashName(agentName) {
|
|
176
|
-
return customAgentDashName(agentName);
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
module.exports = { AgentCommandGenerator };
|