bmad-method 6.3.1-next.9 → 6.4.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/package.json +3 -2
- 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-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-checkpoint-preview/SKILL.md +46 -7
- package/src/bmm-skills/4-implementation/bmad-checkpoint-preview/customize.toml +41 -0
- package/src/bmm-skills/4-implementation/bmad-checkpoint-preview/step-05-wrapup.md +6 -0
- package/src/bmm-skills/4-implementation/bmad-code-review/SKILL.md +85 -1
- package/src/bmm-skills/4-implementation/bmad-code-review/customize.toml +41 -0
- package/src/bmm-skills/4-implementation/bmad-code-review/steps/step-04-present.md +6 -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 +424 -1
- package/src/bmm-skills/4-implementation/bmad-create-story/customize.toml +41 -0
- package/src/bmm-skills/4-implementation/bmad-dev-story/SKILL.md +480 -1
- package/src/bmm-skills/4-implementation/bmad-dev-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-quick-dev/SKILL.md +106 -1
- package/src/bmm-skills/4-implementation/bmad-quick-dev/customize.toml +41 -0
- package/src/bmm-skills/4-implementation/bmad-quick-dev/step-05-present.md +6 -0
- package/src/bmm-skills/4-implementation/bmad-quick-dev/step-oneshot.md +6 -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/4-implementation/bmad-sprint-planning/SKILL.md +294 -1
- package/src/bmm-skills/4-implementation/bmad-sprint-planning/customize.toml +41 -0
- package/src/bmm-skills/4-implementation/bmad-sprint-status/SKILL.md +292 -1
- package/src/bmm-skills/4-implementation/bmad-sprint-status/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 +2 -0
- package/src/scripts/resolve_config.py +176 -0
- package/src/scripts/resolve_customization.py +230 -0
- package/tools/installer/commands/install.js +13 -0
- package/tools/installer/core/config.js +4 -1
- package/tools/installer/core/install-paths.js +11 -5
- package/tools/installer/core/installer.js +181 -94
- package/tools/installer/core/manifest-generator.js +339 -184
- package/tools/installer/core/manifest.js +86 -86
- package/tools/installer/ide/platform-codes.yaml +6 -0
- 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 +130 -23
- package/tools/installer/modules/custom-module-manager.js +160 -19
- package/tools/installer/modules/external-manager.js +235 -32
- package/tools/installer/modules/official-modules.js +58 -12
- package/tools/installer/modules/registry-client.js +139 -7
- package/tools/installer/modules/registry-fallback.yaml +8 -0
- package/tools/installer/modules/version-resolver.js +336 -0
- package/tools/installer/project-root.js +54 -0
- package/tools/installer/ui.js +561 -50
- package/tools/platform-codes.yaml +6 -0
- 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-code-review/workflow.md +0 -55
- 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-dev-story/workflow.md +0 -450
- package/src/bmm-skills/4-implementation/bmad-qa-generate-e2e-tests/workflow.md +0 -136
- package/src/bmm-skills/4-implementation/bmad-quick-dev/workflow.md +0 -76
- package/src/bmm-skills/4-implementation/bmad-retrospective/workflow.md +0 -1479
- package/src/bmm-skills/4-implementation/bmad-sprint-planning/workflow.md +0 -263
- package/src/bmm-skills/4-implementation/bmad-sprint-status/workflow.md +0 -261
|
@@ -2,14 +2,8 @@ const path = require('node:path');
|
|
|
2
2
|
const fs = require('../fs-native');
|
|
3
3
|
const yaml = require('yaml');
|
|
4
4
|
const crypto = require('node:crypto');
|
|
5
|
-
const
|
|
6
|
-
const { getSourcePath, getModulePath } = require('../project-root');
|
|
5
|
+
const { resolveInstalledModuleYaml } = require('../project-root');
|
|
7
6
|
const prompts = require('../prompts');
|
|
8
|
-
const {
|
|
9
|
-
loadSkillManifest: loadSkillManifestShared,
|
|
10
|
-
getCanonicalId: getCanonicalIdShared,
|
|
11
|
-
getArtifactType: getArtifactTypeShared,
|
|
12
|
-
} = require('../ide/shared/skill-manifest');
|
|
13
7
|
|
|
14
8
|
// Load package.json for version info
|
|
15
9
|
const packageJson = require('../../../package.json');
|
|
@@ -26,21 +20,6 @@ class ManifestGenerator {
|
|
|
26
20
|
this.selectedIdes = [];
|
|
27
21
|
}
|
|
28
22
|
|
|
29
|
-
/** Delegate to shared skill-manifest module */
|
|
30
|
-
async loadSkillManifest(dirPath) {
|
|
31
|
-
return loadSkillManifestShared(dirPath);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/** Delegate to shared skill-manifest module */
|
|
35
|
-
getCanonicalId(manifest, filename) {
|
|
36
|
-
return getCanonicalIdShared(manifest, filename);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/** Delegate to shared skill-manifest module */
|
|
40
|
-
getArtifactType(manifest, filename) {
|
|
41
|
-
return getArtifactTypeShared(manifest, filename);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
23
|
/**
|
|
45
24
|
* Clean text for CSV output by normalizing whitespace.
|
|
46
25
|
* Note: Quote escaping is handled by escapeCsv() at write time.
|
|
@@ -98,17 +77,21 @@ class ManifestGenerator {
|
|
|
98
77
|
// Collect skills first (populates skillClaimedDirs before legacy collectors run)
|
|
99
78
|
await this.collectSkills();
|
|
100
79
|
|
|
101
|
-
// Collect agent
|
|
102
|
-
await this.
|
|
80
|
+
// Collect agent essence from each module's source module.yaml `agents:` array
|
|
81
|
+
await this.collectAgentsFromModuleYaml();
|
|
103
82
|
|
|
104
83
|
// Write manifest files and collect their paths
|
|
84
|
+
const [teamConfigPath, userConfigPath] = await this.writeCentralConfig(bmadDir, options.moduleConfigs || {});
|
|
105
85
|
const manifestFiles = [
|
|
106
86
|
await this.writeMainManifest(cfgDir),
|
|
107
87
|
await this.writeSkillManifest(cfgDir),
|
|
108
|
-
|
|
88
|
+
teamConfigPath,
|
|
89
|
+
userConfigPath,
|
|
109
90
|
await this.writeFilesManifest(cfgDir),
|
|
110
91
|
];
|
|
111
92
|
|
|
93
|
+
await this.ensureCustomConfigStubs(bmadDir);
|
|
94
|
+
|
|
112
95
|
return {
|
|
113
96
|
skills: this.skills.length,
|
|
114
97
|
agents: this.agents.length,
|
|
@@ -150,24 +133,13 @@ class ManifestGenerator {
|
|
|
150
133
|
const skillMeta = await this.parseSkillMd(skillMdPath, dir, dirName, debug);
|
|
151
134
|
|
|
152
135
|
if (skillMeta) {
|
|
153
|
-
// Load manifest when present (for agent metadata)
|
|
154
|
-
const manifest = await this.loadSkillManifest(dir);
|
|
155
|
-
const artifactType = this.getArtifactType(manifest, skillFile);
|
|
156
|
-
|
|
157
136
|
// Build path relative from module root (points to SKILL.md — the permanent entrypoint)
|
|
158
137
|
const relativePath = path.relative(modulePath, dir).split(path.sep).join('/');
|
|
159
138
|
const installPath = relativePath
|
|
160
139
|
? `${this.bmadFolderName}/${moduleName}/${relativePath}/${skillFile}`
|
|
161
140
|
: `${this.bmadFolderName}/${moduleName}/${skillFile}`;
|
|
162
141
|
|
|
163
|
-
// Native SKILL.md entrypoints derive canonicalId from directory name.
|
|
164
|
-
// Agent entrypoints may keep canonicalId metadata for compatibility, so
|
|
165
|
-
// only warn for non-agent SKILL.md directories.
|
|
166
|
-
if (manifest && manifest.__single && manifest.__single.canonicalId && artifactType !== 'agent') {
|
|
167
|
-
console.warn(
|
|
168
|
-
`Warning: Native entrypoint manifest at ${dir}/bmad-skill-manifest.yaml contains canonicalId — this field is ignored for SKILL.md directories (directory name is the canonical ID)`,
|
|
169
|
-
);
|
|
170
|
-
}
|
|
142
|
+
// Native SKILL.md entrypoints always derive canonicalId from directory name.
|
|
171
143
|
const canonicalId = dirName;
|
|
172
144
|
|
|
173
145
|
this.skills.push({
|
|
@@ -263,106 +235,60 @@ class ManifestGenerator {
|
|
|
263
235
|
}
|
|
264
236
|
|
|
265
237
|
/**
|
|
266
|
-
* Collect
|
|
238
|
+
* Collect agents from each installed module's source module.yaml `agents:` array.
|
|
239
|
+
* Essence fields (code, name, title, icon, description) are authored in module.yaml;
|
|
240
|
+
* `team` defaults to module code when not set; `module` is always the owning module.
|
|
267
241
|
*/
|
|
268
|
-
async
|
|
242
|
+
async collectAgentsFromModuleYaml() {
|
|
269
243
|
this.agents = [];
|
|
270
244
|
const debug = process.env.BMAD_DEBUG_MANIFEST === 'true';
|
|
271
245
|
|
|
272
|
-
// Walk each module's full directory tree looking for type:agent manifests
|
|
273
246
|
for (const moduleName of this.updatedModules) {
|
|
274
|
-
const
|
|
275
|
-
if (!
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
this.agents.push(...standaloneAgents);
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
if (debug) {
|
|
289
|
-
console.log(`[DEBUG] collectAgents: total agents found: ${this.agents.length}`);
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
/**
|
|
294
|
-
* Recursively walk a directory tree collecting agents.
|
|
295
|
-
* Discovers agents via directory with bmad-skill-manifest.yaml containing type: agent
|
|
296
|
-
*
|
|
297
|
-
* @param {string} dirPath - Current directory being scanned
|
|
298
|
-
* @param {string} moduleName - Module this directory belongs to
|
|
299
|
-
* @param {string} relativePath - Path relative to the module root (for install path construction)
|
|
300
|
-
* @param {boolean} debug - Emit debug messages
|
|
301
|
-
*/
|
|
302
|
-
async getAgentsFromDirRecursive(dirPath, moduleName, relativePath = '', debug = false) {
|
|
303
|
-
const agents = [];
|
|
304
|
-
let entries;
|
|
305
|
-
try {
|
|
306
|
-
entries = await fs.readdir(dirPath, { withFileTypes: true });
|
|
307
|
-
} catch {
|
|
308
|
-
return agents;
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
for (const entry of entries) {
|
|
312
|
-
if (!entry.isDirectory()) continue;
|
|
313
|
-
if (entry.name.startsWith('.') || entry.name.startsWith('_')) continue;
|
|
314
|
-
|
|
315
|
-
const fullPath = path.join(dirPath, entry.name);
|
|
316
|
-
|
|
317
|
-
// Check for type:agent manifest BEFORE checking skillClaimedDirs —
|
|
318
|
-
// agent dirs may be claimed by collectSkills for IDE installation,
|
|
319
|
-
// but we still need them in agent-manifest.csv.
|
|
320
|
-
const dirManifest = await this.loadSkillManifest(fullPath);
|
|
321
|
-
if (dirManifest && dirManifest.__single && dirManifest.__single.type === 'agent') {
|
|
322
|
-
const m = dirManifest.__single;
|
|
323
|
-
const dirRelativePath = relativePath ? `${relativePath}/${entry.name}` : entry.name;
|
|
324
|
-
const agentModule = m.module || moduleName;
|
|
325
|
-
const installPath = `${this.bmadFolderName}/${agentModule}/${dirRelativePath}`;
|
|
326
|
-
|
|
327
|
-
agents.push({
|
|
328
|
-
name: m.name || entry.name,
|
|
329
|
-
displayName: m.displayName || m.name || entry.name,
|
|
330
|
-
title: m.title || '',
|
|
331
|
-
icon: m.icon || '',
|
|
332
|
-
capabilities: m.capabilities ? this.cleanForCSV(m.capabilities) : '',
|
|
333
|
-
role: m.role ? this.cleanForCSV(m.role) : '',
|
|
334
|
-
identity: m.identity ? this.cleanForCSV(m.identity) : '',
|
|
335
|
-
communicationStyle: m.communicationStyle ? this.cleanForCSV(m.communicationStyle) : '',
|
|
336
|
-
principles: m.principles ? this.cleanForCSV(m.principles) : '',
|
|
337
|
-
module: agentModule,
|
|
338
|
-
path: installPath,
|
|
339
|
-
canonicalId: m.canonicalId || '',
|
|
340
|
-
});
|
|
341
|
-
|
|
342
|
-
this.files.push({
|
|
343
|
-
type: 'agent',
|
|
344
|
-
name: m.name || entry.name,
|
|
345
|
-
module: agentModule,
|
|
346
|
-
path: installPath,
|
|
347
|
-
});
|
|
247
|
+
const moduleYamlPath = await resolveInstalledModuleYaml(moduleName);
|
|
248
|
+
if (!moduleYamlPath) {
|
|
249
|
+
// External modules live in ~/.bmad/cache/external-modules, not src/modules.
|
|
250
|
+
// Warn rather than silently skip so missing agent rosters don't vanish
|
|
251
|
+
// from config.toml without notice.
|
|
252
|
+
console.warn(
|
|
253
|
+
`[warn] collectAgentsFromModuleYaml: could not locate module.yaml for '${moduleName}'. ` +
|
|
254
|
+
`Agents declared by this module will not be written to config.toml.`,
|
|
255
|
+
);
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
348
258
|
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
259
|
+
let moduleDef;
|
|
260
|
+
try {
|
|
261
|
+
moduleDef = yaml.parse(await fs.readFile(moduleYamlPath, 'utf8'));
|
|
262
|
+
} catch (error) {
|
|
263
|
+
if (debug) console.log(`[DEBUG] collectAgentsFromModuleYaml: failed to parse ${moduleYamlPath}: ${error.message}`);
|
|
352
264
|
continue;
|
|
353
265
|
}
|
|
354
266
|
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
267
|
+
if (!moduleDef || !Array.isArray(moduleDef.agents)) continue;
|
|
268
|
+
|
|
269
|
+
for (const entry of moduleDef.agents) {
|
|
270
|
+
if (!entry || typeof entry.code !== 'string') continue;
|
|
271
|
+
this.agents.push({
|
|
272
|
+
code: entry.code,
|
|
273
|
+
name: entry.name || '',
|
|
274
|
+
title: entry.title || '',
|
|
275
|
+
icon: entry.icon || '',
|
|
276
|
+
description: entry.description || '',
|
|
277
|
+
module: moduleName,
|
|
278
|
+
team: entry.team || moduleName,
|
|
279
|
+
});
|
|
280
|
+
}
|
|
358
281
|
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
282
|
+
if (debug) {
|
|
283
|
+
console.log(
|
|
284
|
+
`[DEBUG] collectAgentsFromModuleYaml: ${moduleName} contributed ${moduleDef.agents.length} agents from ${moduleYamlPath}`,
|
|
285
|
+
);
|
|
286
|
+
}
|
|
363
287
|
}
|
|
364
288
|
|
|
365
|
-
|
|
289
|
+
if (debug) {
|
|
290
|
+
console.log(`[DEBUG] collectAgentsFromModuleYaml: total agents found: ${this.agents.length}`);
|
|
291
|
+
}
|
|
366
292
|
}
|
|
367
293
|
|
|
368
294
|
/**
|
|
@@ -423,7 +349,22 @@ class ManifestGenerator {
|
|
|
423
349
|
npmPackage: versionInfo.npmPackage,
|
|
424
350
|
repoUrl: versionInfo.repoUrl,
|
|
425
351
|
};
|
|
426
|
-
|
|
352
|
+
// Preserve channel/sha from the resolution (external/community/custom)
|
|
353
|
+
// or from the existing entry if this is a no-change rewrite.
|
|
354
|
+
const channel = versionInfo.channel ?? existing?.channel;
|
|
355
|
+
const sha = versionInfo.sha ?? existing?.sha;
|
|
356
|
+
if (channel) moduleEntry.channel = channel;
|
|
357
|
+
if (sha) moduleEntry.sha = sha;
|
|
358
|
+
if (versionInfo.localPath || existing?.localPath) {
|
|
359
|
+
moduleEntry.localPath = versionInfo.localPath || existing.localPath;
|
|
360
|
+
}
|
|
361
|
+
if (versionInfo.rawSource || existing?.rawSource) {
|
|
362
|
+
moduleEntry.rawSource = versionInfo.rawSource || existing.rawSource;
|
|
363
|
+
}
|
|
364
|
+
const regTag = versionInfo.registryApprovedTag ?? existing?.registryApprovedTag;
|
|
365
|
+
const regSha = versionInfo.registryApprovedSha ?? existing?.registryApprovedSha;
|
|
366
|
+
if (regTag) moduleEntry.registryApprovedTag = regTag;
|
|
367
|
+
if (regSha) moduleEntry.registryApprovedSha = regSha;
|
|
427
368
|
updatedModules.push(moduleEntry);
|
|
428
369
|
}
|
|
429
370
|
|
|
@@ -478,77 +419,236 @@ class ManifestGenerator {
|
|
|
478
419
|
}
|
|
479
420
|
|
|
480
421
|
/**
|
|
481
|
-
* Write
|
|
482
|
-
*
|
|
422
|
+
* Write central _bmad/config.toml with [core], [modules.<code>], [agents.<code>] tables.
|
|
423
|
+
* Install-owned. Team-scope answers → config.toml; user-scope answers → config.user.toml.
|
|
424
|
+
* Both files are regenerated on every install. User overrides live in
|
|
425
|
+
* _bmad/custom/config.toml and _bmad/custom/config.user.toml (never touched by installer).
|
|
426
|
+
* @returns {string[]} Paths to the written config files
|
|
483
427
|
*/
|
|
484
|
-
async
|
|
485
|
-
const
|
|
486
|
-
const
|
|
487
|
-
|
|
488
|
-
//
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
428
|
+
async writeCentralConfig(bmadDir, moduleConfigs) {
|
|
429
|
+
const teamPath = path.join(bmadDir, 'config.toml');
|
|
430
|
+
const userPath = path.join(bmadDir, 'config.user.toml');
|
|
431
|
+
|
|
432
|
+
// Load each module's source module.yaml to determine scope per prompt key.
|
|
433
|
+
// Default scope is 'team' when the prompt doesn't declare one.
|
|
434
|
+
// When a module.yaml is unreadable we warn — for known official modules
|
|
435
|
+
// this means user-scoped keys (e.g. user_name) could mis-file into the
|
|
436
|
+
// team config, so the operator should notice.
|
|
437
|
+
const scopeByModuleKey = {};
|
|
438
|
+
for (const moduleName of this.updatedModules) {
|
|
439
|
+
const moduleYamlPath = await resolveInstalledModuleYaml(moduleName);
|
|
440
|
+
if (!moduleYamlPath) {
|
|
441
|
+
console.warn(
|
|
442
|
+
`[warn] writeCentralConfig: could not locate module.yaml for '${moduleName}'. ` +
|
|
443
|
+
`Answers from this module will default to team scope — user-scoped keys may mis-file into config.toml.`,
|
|
444
|
+
);
|
|
445
|
+
continue;
|
|
446
|
+
}
|
|
447
|
+
try {
|
|
448
|
+
const parsed = yaml.parse(await fs.readFile(moduleYamlPath, 'utf8'));
|
|
449
|
+
if (!parsed || typeof parsed !== 'object') continue;
|
|
450
|
+
scopeByModuleKey[moduleName] = {};
|
|
451
|
+
for (const [key, value] of Object.entries(parsed)) {
|
|
452
|
+
if (value && typeof value === 'object' && 'prompt' in value) {
|
|
453
|
+
scopeByModuleKey[moduleName][key] = value.scope === 'user' ? 'user' : 'team';
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
} catch (error) {
|
|
457
|
+
console.warn(
|
|
458
|
+
`[warn] writeCentralConfig: could not parse module.yaml for '${moduleName}' (${error.message}). ` +
|
|
459
|
+
`Answers from this module will default to team scope — user-scoped keys may mis-file into config.toml.`,
|
|
460
|
+
);
|
|
498
461
|
}
|
|
499
462
|
}
|
|
500
463
|
|
|
501
|
-
//
|
|
502
|
-
|
|
464
|
+
// Core keys are always known (core module.yaml is built-in). These are
|
|
465
|
+
// the only keys allowed in [core]; they must be stripped from every
|
|
466
|
+
// non-core module bucket because legacy _bmad/{mod}/config.yaml files
|
|
467
|
+
// spread core values into each module. Core belongs in [core] only —
|
|
468
|
+
// workflows that need user_name/language/etc. read [core] directly.
|
|
469
|
+
const coreKeys = new Set(Object.keys(scopeByModuleKey.core || {}));
|
|
470
|
+
|
|
471
|
+
// Partition a module's answered config into team vs user buckets.
|
|
472
|
+
// For non-core modules: strip core keys always; when we know the module's
|
|
473
|
+
// own schema, also drop keys it doesn't declare. Unknown-schema modules
|
|
474
|
+
// (external / marketplace) fall through with their remaining answers as
|
|
475
|
+
// team so they don't vanish from the config.
|
|
476
|
+
const partition = (moduleName, cfg, onlyDeclaredKeys = false) => {
|
|
477
|
+
const team = {};
|
|
478
|
+
const user = {};
|
|
479
|
+
const scopes = scopeByModuleKey[moduleName] || {};
|
|
480
|
+
const isCore = moduleName === 'core';
|
|
481
|
+
for (const [key, value] of Object.entries(cfg || {})) {
|
|
482
|
+
if (!isCore && coreKeys.has(key)) continue;
|
|
483
|
+
if (onlyDeclaredKeys && !(key in scopes)) continue;
|
|
484
|
+
if (scopes[key] === 'user') {
|
|
485
|
+
user[key] = value;
|
|
486
|
+
} else {
|
|
487
|
+
team[key] = value;
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
return { team, user };
|
|
491
|
+
};
|
|
492
|
+
|
|
493
|
+
const teamHeader = [
|
|
494
|
+
'# ─────────────────────────────────────────────────────────────────',
|
|
495
|
+
'# Installer-managed. Regenerated on every install — treat as read-only.',
|
|
496
|
+
'#',
|
|
497
|
+
'# Direct edits to this file will be overwritten on the next install.',
|
|
498
|
+
'# To change an install answer durably, re-run the installer (your prior',
|
|
499
|
+
'# answers are remembered as defaults). To pin a value regardless of',
|
|
500
|
+
'# install answers, or to add custom agents / override descriptors, use:',
|
|
501
|
+
'# _bmad/custom/config.toml (team, committed)',
|
|
502
|
+
'# _bmad/custom/config.user.toml (personal, gitignored)',
|
|
503
|
+
'# Those files are never touched by the installer.',
|
|
504
|
+
'# ─────────────────────────────────────────────────────────────────',
|
|
505
|
+
'',
|
|
506
|
+
];
|
|
507
|
+
|
|
508
|
+
const userHeader = [
|
|
509
|
+
'# ─────────────────────────────────────────────────────────────────',
|
|
510
|
+
'# Installer-managed. Regenerated on every install — treat as read-only.',
|
|
511
|
+
'# Holds install answers scoped to YOU personally.',
|
|
512
|
+
'#',
|
|
513
|
+
'# Direct edits to this file will be overwritten on the next install.',
|
|
514
|
+
'# To change an answer durably, re-run the installer (your prior answers',
|
|
515
|
+
'# are remembered as defaults). For pinned overrides or custom sections',
|
|
516
|
+
'# the installer does not know about, use _bmad/custom/config.user.toml',
|
|
517
|
+
'# — it is never touched by the installer.',
|
|
518
|
+
'# ─────────────────────────────────────────────────────────────────',
|
|
519
|
+
'',
|
|
520
|
+
];
|
|
521
|
+
|
|
522
|
+
const teamLines = [...teamHeader];
|
|
523
|
+
const userLines = [...userHeader];
|
|
524
|
+
|
|
525
|
+
// [core] — split into team and user
|
|
526
|
+
const coreConfig = moduleConfigs.core || {};
|
|
527
|
+
const { team: coreTeam, user: coreUser } = partition('core', coreConfig);
|
|
528
|
+
if (Object.keys(coreTeam).length > 0) {
|
|
529
|
+
teamLines.push('[core]');
|
|
530
|
+
for (const [key, value] of Object.entries(coreTeam)) {
|
|
531
|
+
teamLines.push(`${key} = ${formatTomlValue(value)}`);
|
|
532
|
+
}
|
|
533
|
+
teamLines.push('');
|
|
534
|
+
}
|
|
535
|
+
if (Object.keys(coreUser).length > 0) {
|
|
536
|
+
userLines.push('[core]');
|
|
537
|
+
for (const [key, value] of Object.entries(coreUser)) {
|
|
538
|
+
userLines.push(`${key} = ${formatTomlValue(value)}`);
|
|
539
|
+
}
|
|
540
|
+
userLines.push('');
|
|
541
|
+
}
|
|
503
542
|
|
|
504
|
-
//
|
|
505
|
-
const
|
|
543
|
+
// [modules.<code>] — split per module
|
|
544
|
+
for (const moduleName of this.updatedModules) {
|
|
545
|
+
if (moduleName === 'core') continue;
|
|
546
|
+
const cfg = moduleConfigs[moduleName];
|
|
547
|
+
if (!cfg || Object.keys(cfg).length === 0) continue;
|
|
548
|
+
// Only filter out spread-from-core pollution when we actually know
|
|
549
|
+
// this module's prompt schema. For external/marketplace modules whose
|
|
550
|
+
// module.yaml isn't in the src tree, fall through as all-team so we
|
|
551
|
+
// don't drop their real answers.
|
|
552
|
+
const haveSchema = Object.keys(scopeByModuleKey[moduleName] || {}).length > 0;
|
|
553
|
+
const { team: modTeam, user: modUser } = partition(moduleName, cfg, haveSchema);
|
|
554
|
+
if (Object.keys(modTeam).length > 0) {
|
|
555
|
+
teamLines.push(`[modules.${moduleName}]`);
|
|
556
|
+
for (const [key, value] of Object.entries(modTeam)) {
|
|
557
|
+
teamLines.push(`${key} = ${formatTomlValue(value)}`);
|
|
558
|
+
}
|
|
559
|
+
teamLines.push('');
|
|
560
|
+
}
|
|
561
|
+
if (Object.keys(modUser).length > 0) {
|
|
562
|
+
userLines.push(`[modules.${moduleName}]`);
|
|
563
|
+
for (const [key, value] of Object.entries(modUser)) {
|
|
564
|
+
userLines.push(`${key} = ${formatTomlValue(value)}`);
|
|
565
|
+
}
|
|
566
|
+
userLines.push('');
|
|
567
|
+
}
|
|
568
|
+
}
|
|
506
569
|
|
|
507
|
-
//
|
|
508
|
-
|
|
509
|
-
|
|
570
|
+
// [agents.<code>] — always team (agent roster is organizational).
|
|
571
|
+
// Freshly collected agents come from module.yaml this run. If a module
|
|
572
|
+
// was preserved (e.g. during quickUpdate when its source isn't available),
|
|
573
|
+
// its module.yaml wasn't read — so its agents aren't in `this.agents` and
|
|
574
|
+
// would silently disappear from the roster. Preserve those existing
|
|
575
|
+
// [agents.*] blocks verbatim from the prior config.toml.
|
|
576
|
+
const freshAgentCodes = new Set(this.agents.map((a) => a.code));
|
|
577
|
+
const contributingModules = new Set(this.agents.map((a) => a.module));
|
|
578
|
+
const preservedModules = this.updatedModules.filter((m) => !contributingModules.has(m));
|
|
579
|
+
const preservedBlocks = [];
|
|
580
|
+
if (preservedModules.length > 0 && (await fs.pathExists(teamPath))) {
|
|
581
|
+
try {
|
|
582
|
+
const prev = await fs.readFile(teamPath, 'utf8');
|
|
583
|
+
for (const block of extractAgentBlocks(prev)) {
|
|
584
|
+
if (freshAgentCodes.has(block.code)) continue;
|
|
585
|
+
if (block.module && preservedModules.includes(block.module)) {
|
|
586
|
+
preservedBlocks.push(block.body);
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
} catch (error) {
|
|
590
|
+
console.warn(`[warn] writeCentralConfig: could not read prior config.toml to preserve agents: ${error.message}`);
|
|
591
|
+
}
|
|
510
592
|
}
|
|
511
593
|
|
|
512
|
-
// Add/update new agents
|
|
513
594
|
for (const agent of this.agents) {
|
|
514
|
-
const
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
role: agent.role,
|
|
522
|
-
identity: agent.identity,
|
|
523
|
-
communicationStyle: agent.communicationStyle,
|
|
524
|
-
principles: agent.principles,
|
|
525
|
-
module: agent.module,
|
|
526
|
-
path: agent.path,
|
|
527
|
-
canonicalId: agent.canonicalId || '',
|
|
528
|
-
});
|
|
595
|
+
const agentLines = [`[agents.${agent.code}]`, `module = ${formatTomlValue(agent.module)}`, `team = ${formatTomlValue(agent.team)}`];
|
|
596
|
+
if (agent.name) agentLines.push(`name = ${formatTomlValue(agent.name)}`);
|
|
597
|
+
if (agent.title) agentLines.push(`title = ${formatTomlValue(agent.title)}`);
|
|
598
|
+
if (agent.icon) agentLines.push(`icon = ${formatTomlValue(agent.icon)}`);
|
|
599
|
+
if (agent.description) agentLines.push(`description = ${formatTomlValue(agent.description)}`);
|
|
600
|
+
agentLines.push('');
|
|
601
|
+
teamLines.push(...agentLines);
|
|
529
602
|
}
|
|
530
603
|
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
const row = [
|
|
534
|
-
escapeCsv(record.name),
|
|
535
|
-
escapeCsv(record.displayName),
|
|
536
|
-
escapeCsv(record.title),
|
|
537
|
-
escapeCsv(record.icon),
|
|
538
|
-
escapeCsv(record.capabilities),
|
|
539
|
-
escapeCsv(record.role),
|
|
540
|
-
escapeCsv(record.identity),
|
|
541
|
-
escapeCsv(record.communicationStyle),
|
|
542
|
-
escapeCsv(record.principles),
|
|
543
|
-
escapeCsv(record.module),
|
|
544
|
-
escapeCsv(record.path),
|
|
545
|
-
escapeCsv(record.canonicalId),
|
|
546
|
-
].join(',');
|
|
547
|
-
csvContent += row + '\n';
|
|
604
|
+
for (const body of preservedBlocks) {
|
|
605
|
+
teamLines.push(body, '');
|
|
548
606
|
}
|
|
549
607
|
|
|
550
|
-
|
|
551
|
-
|
|
608
|
+
const teamContent = teamLines.join('\n').replace(/\n+$/, '\n');
|
|
609
|
+
const userContent = userLines.join('\n').replace(/\n+$/, '\n');
|
|
610
|
+
await fs.writeFile(teamPath, teamContent);
|
|
611
|
+
await fs.writeFile(userPath, userContent);
|
|
612
|
+
return [teamPath, userPath];
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
/**
|
|
616
|
+
* Create empty _bmad/custom/config.toml and _bmad/custom/config.user.toml stubs
|
|
617
|
+
* on first install only. Installer never touches these files again after creation.
|
|
618
|
+
*/
|
|
619
|
+
async ensureCustomConfigStubs(bmadDir) {
|
|
620
|
+
const customDir = path.join(bmadDir, 'custom');
|
|
621
|
+
await fs.ensureDir(customDir);
|
|
622
|
+
|
|
623
|
+
const stubs = [
|
|
624
|
+
{
|
|
625
|
+
file: path.join(customDir, 'config.toml'),
|
|
626
|
+
header: [
|
|
627
|
+
'# Team / enterprise overrides for _bmad/config.toml.',
|
|
628
|
+
'# Committed to the repo — applies to every developer on the project.',
|
|
629
|
+
'# Tables deep-merge over base config; keyed entries merge by key.',
|
|
630
|
+
'# Example: override an agent descriptor, or add a new agent.',
|
|
631
|
+
'#',
|
|
632
|
+
'# [agents.bmad-agent-pm]',
|
|
633
|
+
'# description = "Prefers short, bulleted PRDs over narrative drafts."',
|
|
634
|
+
'',
|
|
635
|
+
],
|
|
636
|
+
},
|
|
637
|
+
{
|
|
638
|
+
file: path.join(customDir, 'config.user.toml'),
|
|
639
|
+
header: [
|
|
640
|
+
'# Personal overrides for _bmad/config.toml.',
|
|
641
|
+
'# NOT committed (gitignored) — applies only to your local install.',
|
|
642
|
+
'# Wins over both base config and team overrides.',
|
|
643
|
+
'',
|
|
644
|
+
],
|
|
645
|
+
},
|
|
646
|
+
];
|
|
647
|
+
|
|
648
|
+
for (const { file, header } of stubs) {
|
|
649
|
+
if (await fs.pathExists(file)) continue;
|
|
650
|
+
await fs.writeFile(file, header.join('\n'));
|
|
651
|
+
}
|
|
552
652
|
}
|
|
553
653
|
|
|
554
654
|
/**
|
|
@@ -694,4 +794,59 @@ class ManifestGenerator {
|
|
|
694
794
|
}
|
|
695
795
|
}
|
|
696
796
|
|
|
797
|
+
/**
|
|
798
|
+
* Format a JS scalar as a TOML value literal.
|
|
799
|
+
* Handles strings (quoted + escaped), booleans, numbers, and arrays of scalars.
|
|
800
|
+
* Objects are not expected at this emit path.
|
|
801
|
+
*/
|
|
802
|
+
function formatTomlValue(value) {
|
|
803
|
+
if (value === null || value === undefined) return '""';
|
|
804
|
+
if (typeof value === 'boolean') return value ? 'true' : 'false';
|
|
805
|
+
if (typeof value === 'number' && Number.isFinite(value)) return String(value);
|
|
806
|
+
if (Array.isArray(value)) return `[${value.map((v) => formatTomlValue(v)).join(', ')}]`;
|
|
807
|
+
const str = String(value);
|
|
808
|
+
const escaped = str
|
|
809
|
+
.replaceAll('\\', '\\\\')
|
|
810
|
+
.replaceAll('"', String.raw`\"`)
|
|
811
|
+
.replaceAll('\n', String.raw`\n`)
|
|
812
|
+
.replaceAll('\r', String.raw`\r`)
|
|
813
|
+
.replaceAll('\t', String.raw`\t`);
|
|
814
|
+
return `"${escaped}"`;
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
/**
|
|
818
|
+
* Extract [agents.<code>] blocks from a previously-emitted config.toml.
|
|
819
|
+
* We only need this for roster preservation — the file is our own controlled
|
|
820
|
+
* output, so a simple line scanner is safer than adding a TOML parser
|
|
821
|
+
* dependency. Each block runs from its `[agents.<code>]` header until the
|
|
822
|
+
* next `[` heading or EOF; the `module = "..."` line inside drives which
|
|
823
|
+
* entries we keep on the next write.
|
|
824
|
+
* @returns {Array<{code: string, module: string | null, body: string}>}
|
|
825
|
+
*/
|
|
826
|
+
function extractAgentBlocks(tomlContent) {
|
|
827
|
+
const blocks = [];
|
|
828
|
+
const lines = tomlContent.split('\n');
|
|
829
|
+
let i = 0;
|
|
830
|
+
while (i < lines.length) {
|
|
831
|
+
const header = lines[i].match(/^\[agents\.([^\]]+)]\s*$/);
|
|
832
|
+
if (!header) {
|
|
833
|
+
i++;
|
|
834
|
+
continue;
|
|
835
|
+
}
|
|
836
|
+
const code = header[1];
|
|
837
|
+
const blockLines = [lines[i]];
|
|
838
|
+
let moduleName = null;
|
|
839
|
+
i++;
|
|
840
|
+
while (i < lines.length && !lines[i].startsWith('[')) {
|
|
841
|
+
blockLines.push(lines[i]);
|
|
842
|
+
const m = lines[i].match(/^module\s*=\s*"((?:[^"\\]|\\.)*)"\s*$/);
|
|
843
|
+
if (m) moduleName = m[1];
|
|
844
|
+
i++;
|
|
845
|
+
}
|
|
846
|
+
while (blockLines.length > 1 && blockLines.at(-1) === '') blockLines.pop();
|
|
847
|
+
blocks.push({ code, module: moduleName, body: blockLines.join('\n') });
|
|
848
|
+
}
|
|
849
|
+
return blocks;
|
|
850
|
+
}
|
|
851
|
+
|
|
697
852
|
module.exports = { ManifestGenerator };
|