bmad-method 6.2.3-next.8 → 6.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +0 -3
- package/README.md +8 -9
- package/README_CN.md +1 -1
- package/README_VN.md +110 -0
- package/package.json +1 -1
- package/removals.txt +17 -0
- package/src/bmm-skills/2-plan-workflows/bmad-create-ux-design/steps/step-13-responsive-accessibility.md +1 -1
- package/src/bmm-skills/2-plan-workflows/bmad-edit-prd/data/prd-purpose.md +197 -0
- package/src/bmm-skills/2-plan-workflows/bmad-edit-prd/steps-e/step-e-01-discovery.md +1 -1
- package/src/bmm-skills/2-plan-workflows/bmad-edit-prd/steps-e/step-e-01b-legacy-conversion.md +1 -1
- package/src/bmm-skills/2-plan-workflows/bmad-edit-prd/steps-e/step-e-02-review.md +1 -1
- package/src/bmm-skills/2-plan-workflows/bmad-edit-prd/steps-e/step-e-03-edit.md +1 -1
- package/src/bmm-skills/2-plan-workflows/bmad-edit-prd/steps-e/step-e-04-complete.md +1 -3
- package/src/bmm-skills/3-solutioning/bmad-check-implementation-readiness/steps/step-01-document-discovery.md +1 -1
- package/src/bmm-skills/3-solutioning/bmad-check-implementation-readiness/steps/step-02-prd-analysis.md +1 -1
- package/src/bmm-skills/3-solutioning/bmad-check-implementation-readiness/steps/step-03-epic-coverage-validation.md +1 -1
- package/src/bmm-skills/3-solutioning/bmad-check-implementation-readiness/workflow.md +1 -1
- package/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/workflow.md +1 -1
- package/src/bmm-skills/4-implementation/bmad-agent-dev/SKILL.md +5 -0
- package/src/bmm-skills/4-implementation/bmad-checkpoint-preview/SKILL.md +29 -0
- package/src/bmm-skills/4-implementation/bmad-checkpoint-preview/generate-trail.md +38 -0
- package/src/bmm-skills/4-implementation/bmad-checkpoint-preview/step-01-orientation.md +105 -0
- package/src/bmm-skills/4-implementation/bmad-checkpoint-preview/step-02-walkthrough.md +89 -0
- package/src/bmm-skills/4-implementation/bmad-checkpoint-preview/step-03-detail-pass.md +106 -0
- package/src/bmm-skills/4-implementation/bmad-checkpoint-preview/step-04-testing.md +74 -0
- package/src/bmm-skills/4-implementation/bmad-checkpoint-preview/step-05-wrapup.md +24 -0
- package/src/bmm-skills/4-implementation/bmad-code-review/steps/step-01-gather-context.md +38 -15
- package/src/bmm-skills/4-implementation/bmad-correct-course/checklist.md +2 -2
- package/src/bmm-skills/4-implementation/bmad-correct-course/workflow.md +8 -8
- package/src/bmm-skills/4-implementation/bmad-qa-generate-e2e-tests/checklist.md +1 -1
- package/src/bmm-skills/4-implementation/bmad-quick-dev/compile-epic-context.md +62 -0
- package/src/bmm-skills/4-implementation/bmad-quick-dev/spec-template.md +1 -1
- package/src/bmm-skills/4-implementation/bmad-quick-dev/step-01-clarify-and-route.md +33 -6
- package/src/bmm-skills/4-implementation/bmad-quick-dev/step-02-plan.md +20 -8
- 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-oneshot.md +16 -4
- package/src/bmm-skills/4-implementation/bmad-quick-dev/workflow.md +1 -5
- package/src/bmm-skills/4-implementation/bmad-retrospective/workflow.md +134 -134
- package/src/bmm-skills/4-implementation/bmad-sprint-planning/sprint-status-template.yaml +1 -1
- package/src/bmm-skills/4-implementation/bmad-sprint-planning/workflow.md +3 -3
- package/src/bmm-skills/4-implementation/bmad-sprint-status/workflow.md +2 -2
- package/src/bmm-skills/module-help.csv +2 -0
- package/src/core-skills/bmad-help/SKILL.md +4 -2
- package/src/core-skills/bmad-party-mode/SKILL.md +121 -2
- package/src/core-skills/module-help.csv +1 -0
- package/tools/installer/cli-utils.js +18 -9
- package/tools/installer/commands/install.js +1 -1
- package/tools/installer/core/existing-install.js +2 -8
- package/tools/installer/core/install-paths.js +0 -3
- package/tools/installer/core/installer.js +180 -463
- package/tools/installer/core/manifest-generator.js +8 -14
- package/tools/installer/core/manifest.js +94 -102
- package/tools/installer/ide/_config-driven.js +149 -38
- package/tools/installer/ide/shared/skill-manifest.js +1 -16
- package/tools/installer/install-messages.yaml +19 -26
- package/tools/installer/modules/community-manager.js +377 -0
- package/tools/installer/modules/custom-module-manager.js +644 -0
- package/tools/installer/modules/external-manager.js +65 -49
- package/tools/installer/modules/official-modules.js +117 -65
- package/tools/installer/modules/plugin-resolver.js +398 -0
- package/tools/installer/modules/registry-client.js +66 -0
- package/tools/installer/{external-official-modules.yaml → modules/registry-fallback.yaml} +3 -12
- package/tools/installer/ui.js +549 -666
- package/src/bmm-skills/4-implementation/bmad-agent-qa/SKILL.md +0 -61
- package/src/bmm-skills/4-implementation/bmad-agent-qa/bmad-skill-manifest.yaml +0 -11
- package/src/bmm-skills/4-implementation/bmad-agent-quick-flow-solo-dev/SKILL.md +0 -53
- package/src/bmm-skills/4-implementation/bmad-agent-quick-flow-solo-dev/bmad-skill-manifest.yaml +0 -11
- package/src/bmm-skills/4-implementation/bmad-agent-sm/SKILL.md +0 -55
- package/src/bmm-skills/4-implementation/bmad-agent-sm/bmad-skill-manifest.yaml +0 -11
- package/src/core-skills/bmad-party-mode/steps/step-01-agent-loading.md +0 -138
- package/src/core-skills/bmad-party-mode/steps/step-02-discussion-orchestration.md +0 -187
- package/src/core-skills/bmad-party-mode/steps/step-03-graceful-exit.md +0 -167
- package/src/core-skills/bmad-party-mode/workflow.md +0 -183
- package/tools/installer/core/custom-module-cache.js +0 -260
- package/tools/installer/custom-handler.js +0 -112
- package/tools/installer/modules/custom-modules.js +0 -197
|
@@ -9,7 +9,6 @@ const {
|
|
|
9
9
|
loadSkillManifest: loadSkillManifestShared,
|
|
10
10
|
getCanonicalId: getCanonicalIdShared,
|
|
11
11
|
getArtifactType: getArtifactTypeShared,
|
|
12
|
-
getInstallToBmad: getInstallToBmadShared,
|
|
13
12
|
} = require('../ide/shared/skill-manifest');
|
|
14
13
|
|
|
15
14
|
// Load package.json for version info
|
|
@@ -42,11 +41,6 @@ class ManifestGenerator {
|
|
|
42
41
|
return getArtifactTypeShared(manifest, filename);
|
|
43
42
|
}
|
|
44
43
|
|
|
45
|
-
/** Delegate to shared skill-manifest module */
|
|
46
|
-
getInstallToBmad(manifest, filename) {
|
|
47
|
-
return getInstallToBmadShared(manifest, filename);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
44
|
/**
|
|
51
45
|
* Clean text for CSV output by normalizing whitespace.
|
|
52
46
|
* Note: Quote escaping is handled by escapeCsv() at write time.
|
|
@@ -127,7 +121,7 @@ class ManifestGenerator {
|
|
|
127
121
|
* Recursively walk a module directory tree, collecting native SKILL.md entrypoints.
|
|
128
122
|
* A directory is discovered as a skill when it contains a SKILL.md file with
|
|
129
123
|
* valid name/description frontmatter (name must match directory name).
|
|
130
|
-
* Manifest YAML is loaded only when present — for
|
|
124
|
+
* Manifest YAML is loaded only when present — for agent metadata.
|
|
131
125
|
* Populates this.skills[] and this.skillClaimedDirs (Set of absolute paths).
|
|
132
126
|
*/
|
|
133
127
|
async collectSkills() {
|
|
@@ -156,7 +150,7 @@ class ManifestGenerator {
|
|
|
156
150
|
const skillMeta = await this.parseSkillMd(skillMdPath, dir, dirName, debug);
|
|
157
151
|
|
|
158
152
|
if (skillMeta) {
|
|
159
|
-
// Load manifest when present (for
|
|
153
|
+
// Load manifest when present (for agent metadata)
|
|
160
154
|
const manifest = await this.loadSkillManifest(dir);
|
|
161
155
|
const artifactType = this.getArtifactType(manifest, skillFile);
|
|
162
156
|
|
|
@@ -182,7 +176,6 @@ class ManifestGenerator {
|
|
|
182
176
|
module: moduleName,
|
|
183
177
|
path: installPath,
|
|
184
178
|
canonicalId,
|
|
185
|
-
install_to_bmad: this.getInstallToBmad(manifest, skillFile),
|
|
186
179
|
});
|
|
187
180
|
|
|
188
181
|
// Add to files list
|
|
@@ -377,11 +370,11 @@ class ManifestGenerator {
|
|
|
377
370
|
*/
|
|
378
371
|
async writeMainManifest(cfgDir) {
|
|
379
372
|
const manifestPath = path.join(cfgDir, 'manifest.yaml');
|
|
373
|
+
const installedModuleSet = new Set(this.modules);
|
|
380
374
|
|
|
381
375
|
// Read existing manifest to preserve install date
|
|
382
376
|
let existingInstallDate = null;
|
|
383
377
|
const existingModulesMap = new Map();
|
|
384
|
-
|
|
385
378
|
if (await fs.pathExists(manifestPath)) {
|
|
386
379
|
try {
|
|
387
380
|
const existingContent = await fs.readFile(manifestPath, 'utf8');
|
|
@@ -419,7 +412,7 @@ class ManifestGenerator {
|
|
|
419
412
|
// Get existing install date if available
|
|
420
413
|
const existing = existingModulesMap.get(moduleName);
|
|
421
414
|
|
|
422
|
-
|
|
415
|
+
const moduleEntry = {
|
|
423
416
|
name: moduleName,
|
|
424
417
|
version: versionInfo.version,
|
|
425
418
|
installDate: existing?.installDate || new Date().toISOString(),
|
|
@@ -427,7 +420,9 @@ class ManifestGenerator {
|
|
|
427
420
|
source: versionInfo.source,
|
|
428
421
|
npmPackage: versionInfo.npmPackage,
|
|
429
422
|
repoUrl: versionInfo.repoUrl,
|
|
430
|
-
}
|
|
423
|
+
};
|
|
424
|
+
if (versionInfo.localPath) moduleEntry.localPath = versionInfo.localPath;
|
|
425
|
+
updatedModules.push(moduleEntry);
|
|
431
426
|
}
|
|
432
427
|
|
|
433
428
|
const manifest = {
|
|
@@ -463,7 +458,7 @@ class ManifestGenerator {
|
|
|
463
458
|
const csvPath = path.join(cfgDir, 'skill-manifest.csv');
|
|
464
459
|
const escapeCsv = (value) => `"${String(value ?? '').replaceAll('"', '""')}"`;
|
|
465
460
|
|
|
466
|
-
let csvContent = 'canonicalId,name,description,module,path
|
|
461
|
+
let csvContent = 'canonicalId,name,description,module,path\n';
|
|
467
462
|
|
|
468
463
|
for (const skill of this.skills) {
|
|
469
464
|
const row = [
|
|
@@ -472,7 +467,6 @@ class ManifestGenerator {
|
|
|
472
467
|
escapeCsv(skill.description),
|
|
473
468
|
escapeCsv(skill.module),
|
|
474
469
|
escapeCsv(skill.path),
|
|
475
|
-
escapeCsv(skill.install_to_bmad),
|
|
476
470
|
].join(',');
|
|
477
471
|
csvContent += row + '\n';
|
|
478
472
|
}
|
|
@@ -97,7 +97,6 @@ class Manifest {
|
|
|
97
97
|
lastUpdated: manifestData.installation?.lastUpdated,
|
|
98
98
|
modules: moduleNames, // Simple array of module names for backward compatibility
|
|
99
99
|
modulesDetailed: hasDetailedModules ? modules : null, // New detailed format
|
|
100
|
-
customModules: manifestData.customModules || [], // Keep for backward compatibility
|
|
101
100
|
ides: manifestData.ides || [],
|
|
102
101
|
};
|
|
103
102
|
} catch (error) {
|
|
@@ -182,10 +181,10 @@ class Manifest {
|
|
|
182
181
|
|
|
183
182
|
// Handle adding a new module with version info
|
|
184
183
|
if (updates.addModule) {
|
|
185
|
-
const { name, version, source, npmPackage, repoUrl } = updates.addModule;
|
|
184
|
+
const { name, version, source, npmPackage, repoUrl, localPath } = updates.addModule;
|
|
186
185
|
const existing = manifest.modules.find((m) => m.name === name);
|
|
187
186
|
if (!existing) {
|
|
188
|
-
|
|
187
|
+
const entry = {
|
|
189
188
|
name,
|
|
190
189
|
version: version || null,
|
|
191
190
|
installDate: new Date().toISOString(),
|
|
@@ -193,7 +192,9 @@ class Manifest {
|
|
|
193
192
|
source: source || 'external',
|
|
194
193
|
npmPackage: npmPackage || null,
|
|
195
194
|
repoUrl: repoUrl || null,
|
|
196
|
-
}
|
|
195
|
+
};
|
|
196
|
+
if (localPath) entry.localPath = localPath;
|
|
197
|
+
manifest.modules.push(entry);
|
|
197
198
|
}
|
|
198
199
|
}
|
|
199
200
|
|
|
@@ -254,7 +255,6 @@ class Manifest {
|
|
|
254
255
|
lastUpdated: manifest.installation?.lastUpdated,
|
|
255
256
|
modules: moduleNames,
|
|
256
257
|
modulesDetailed: hasDetailedModules ? modules : null,
|
|
257
|
-
customModules: manifest.customModules || [],
|
|
258
258
|
ides: manifest.ides || [],
|
|
259
259
|
};
|
|
260
260
|
}
|
|
@@ -282,7 +282,7 @@ class Manifest {
|
|
|
282
282
|
|
|
283
283
|
if (existingIndex === -1) {
|
|
284
284
|
// Module doesn't exist, add it
|
|
285
|
-
|
|
285
|
+
const entry = {
|
|
286
286
|
name: moduleName,
|
|
287
287
|
version: options.version || null,
|
|
288
288
|
installDate: new Date().toISOString(),
|
|
@@ -290,7 +290,9 @@ class Manifest {
|
|
|
290
290
|
source: options.source || 'unknown',
|
|
291
291
|
npmPackage: options.npmPackage || null,
|
|
292
292
|
repoUrl: options.repoUrl || null,
|
|
293
|
-
}
|
|
293
|
+
};
|
|
294
|
+
if (options.localPath) entry.localPath = options.localPath;
|
|
295
|
+
manifest.modules.push(entry);
|
|
294
296
|
} else {
|
|
295
297
|
// Module exists, update its version info
|
|
296
298
|
const existing = manifest.modules[existingIndex];
|
|
@@ -300,6 +302,7 @@ class Manifest {
|
|
|
300
302
|
source: options.source || existing.source,
|
|
301
303
|
npmPackage: options.npmPackage === undefined ? existing.npmPackage : options.npmPackage,
|
|
302
304
|
repoUrl: options.repoUrl === undefined ? existing.repoUrl : options.repoUrl,
|
|
305
|
+
localPath: options.localPath === undefined ? existing.localPath : options.localPath,
|
|
303
306
|
lastUpdated: new Date().toISOString(),
|
|
304
307
|
};
|
|
305
308
|
}
|
|
@@ -783,52 +786,6 @@ class Manifest {
|
|
|
783
786
|
|
|
784
787
|
return configs;
|
|
785
788
|
}
|
|
786
|
-
/**
|
|
787
|
-
* Add a custom module to the manifest with its source path
|
|
788
|
-
* @param {string} bmadDir - Path to bmad directory
|
|
789
|
-
* @param {Object} customModule - Custom module info
|
|
790
|
-
*/
|
|
791
|
-
async addCustomModule(bmadDir, customModule) {
|
|
792
|
-
const manifest = await this.read(bmadDir);
|
|
793
|
-
if (!manifest) {
|
|
794
|
-
throw new Error('No manifest found');
|
|
795
|
-
}
|
|
796
|
-
|
|
797
|
-
if (!manifest.customModules) {
|
|
798
|
-
manifest.customModules = [];
|
|
799
|
-
}
|
|
800
|
-
|
|
801
|
-
// Check if custom module already exists
|
|
802
|
-
const existingIndex = manifest.customModules.findIndex((m) => m.id === customModule.id);
|
|
803
|
-
if (existingIndex === -1) {
|
|
804
|
-
// Add new entry
|
|
805
|
-
manifest.customModules.push(customModule);
|
|
806
|
-
} else {
|
|
807
|
-
// Update existing entry
|
|
808
|
-
manifest.customModules[existingIndex] = customModule;
|
|
809
|
-
}
|
|
810
|
-
|
|
811
|
-
await this.update(bmadDir, { customModules: manifest.customModules });
|
|
812
|
-
}
|
|
813
|
-
|
|
814
|
-
/**
|
|
815
|
-
* Remove a custom module from the manifest
|
|
816
|
-
* @param {string} bmadDir - Path to bmad directory
|
|
817
|
-
* @param {string} moduleId - Module ID to remove
|
|
818
|
-
*/
|
|
819
|
-
async removeCustomModule(bmadDir, moduleId) {
|
|
820
|
-
const manifest = await this.read(bmadDir);
|
|
821
|
-
if (!manifest || !manifest.customModules) {
|
|
822
|
-
return;
|
|
823
|
-
}
|
|
824
|
-
|
|
825
|
-
const index = manifest.customModules.findIndex((m) => m.id === moduleId);
|
|
826
|
-
if (index !== -1) {
|
|
827
|
-
manifest.customModules.splice(index, 1);
|
|
828
|
-
await this.update(bmadDir, { customModules: manifest.customModules });
|
|
829
|
-
}
|
|
830
|
-
}
|
|
831
|
-
|
|
832
789
|
/**
|
|
833
790
|
* Get module version info from source
|
|
834
791
|
* @param {string} moduleName - Module name/code
|
|
@@ -837,14 +794,13 @@ class Manifest {
|
|
|
837
794
|
* @returns {Object} Version info object with version, source, npmPackage, repoUrl
|
|
838
795
|
*/
|
|
839
796
|
async getModuleVersionInfo(moduleName, bmadDir, moduleSourcePath = null) {
|
|
840
|
-
const os = require('node:os');
|
|
841
797
|
const yaml = require('yaml');
|
|
842
798
|
|
|
843
|
-
//
|
|
799
|
+
// Resolve source type first, then read version with the correct path context
|
|
844
800
|
if (['core', 'bmm'].includes(moduleName)) {
|
|
845
|
-
const
|
|
801
|
+
const version = await this._readMarketplaceVersion(moduleName, moduleSourcePath);
|
|
846
802
|
return {
|
|
847
|
-
version
|
|
803
|
+
version,
|
|
848
804
|
source: 'built-in',
|
|
849
805
|
npmPackage: null,
|
|
850
806
|
repoUrl: null,
|
|
@@ -857,69 +813,105 @@ class Manifest {
|
|
|
857
813
|
const moduleInfo = await extMgr.getModuleByCode(moduleName);
|
|
858
814
|
|
|
859
815
|
if (moduleInfo) {
|
|
860
|
-
// External module
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
if (moduleInfo.npmPackage) {
|
|
864
|
-
// Fetch version from npm registry
|
|
865
|
-
try {
|
|
866
|
-
version = await this.fetchNpmVersion(moduleInfo.npmPackage);
|
|
867
|
-
} catch {
|
|
868
|
-
// npm fetch failed, try cache as fallback
|
|
869
|
-
}
|
|
870
|
-
}
|
|
871
|
-
|
|
872
|
-
// If npm didn't work, try reading from cached repo's package.json
|
|
873
|
-
if (!version) {
|
|
874
|
-
const cacheDir = path.join(os.homedir(), '.bmad', 'cache', 'external-modules', moduleName);
|
|
875
|
-
const packageJsonPath = path.join(cacheDir, 'package.json');
|
|
876
|
-
|
|
877
|
-
if (await fs.pathExists(packageJsonPath)) {
|
|
878
|
-
try {
|
|
879
|
-
const pkg = require(packageJsonPath);
|
|
880
|
-
version = pkg.version;
|
|
881
|
-
} catch (error) {
|
|
882
|
-
await prompts.log.warn(`Failed to read package.json for ${moduleName}: ${error.message}`);
|
|
883
|
-
}
|
|
884
|
-
}
|
|
885
|
-
}
|
|
886
|
-
|
|
816
|
+
// External module: use moduleSourcePath if provided, otherwise fall back to cache
|
|
817
|
+
const version = await this._readMarketplaceVersion(moduleName, moduleSourcePath);
|
|
887
818
|
return {
|
|
888
|
-
version
|
|
819
|
+
version,
|
|
889
820
|
source: 'external',
|
|
890
821
|
npmPackage: moduleInfo.npmPackage || null,
|
|
891
822
|
repoUrl: moduleInfo.url || null,
|
|
892
823
|
};
|
|
893
824
|
}
|
|
894
825
|
|
|
895
|
-
//
|
|
896
|
-
const
|
|
897
|
-
const
|
|
826
|
+
// Check if this is a community module
|
|
827
|
+
const { CommunityModuleManager } = require('../modules/community-manager');
|
|
828
|
+
const communityMgr = new CommunityModuleManager();
|
|
829
|
+
const communityInfo = await communityMgr.getModuleByCode(moduleName);
|
|
830
|
+
if (communityInfo) {
|
|
831
|
+
const communityVersion = await this._readMarketplaceVersion(moduleName, moduleSourcePath);
|
|
832
|
+
return {
|
|
833
|
+
version: communityVersion || communityInfo.version,
|
|
834
|
+
source: 'community',
|
|
835
|
+
npmPackage: communityInfo.npmPackage || null,
|
|
836
|
+
repoUrl: communityInfo.url || null,
|
|
837
|
+
};
|
|
838
|
+
}
|
|
898
839
|
|
|
899
|
-
if (
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
840
|
+
// Check if this is a custom module (from user-provided URL or local path)
|
|
841
|
+
const { CustomModuleManager } = require('../modules/custom-module-manager');
|
|
842
|
+
const customMgr = new CustomModuleManager();
|
|
843
|
+
const resolved = customMgr.getResolution(moduleName);
|
|
844
|
+
const customSource = await customMgr.findModuleSourceByCode(moduleName, { bmadDir });
|
|
845
|
+
if (customSource || resolved) {
|
|
846
|
+
const customVersion = resolved?.version || (await this._readMarketplaceVersion(moduleName, moduleSourcePath));
|
|
847
|
+
return {
|
|
848
|
+
version: customVersion,
|
|
849
|
+
source: 'custom',
|
|
850
|
+
npmPackage: null,
|
|
851
|
+
repoUrl: resolved?.repoUrl || null,
|
|
852
|
+
localPath: resolved?.localPath || null,
|
|
853
|
+
};
|
|
912
854
|
}
|
|
913
855
|
|
|
914
856
|
// Unknown module
|
|
857
|
+
const version = await this._readMarketplaceVersion(moduleName, moduleSourcePath);
|
|
915
858
|
return {
|
|
916
|
-
version
|
|
859
|
+
version,
|
|
917
860
|
source: 'unknown',
|
|
918
861
|
npmPackage: null,
|
|
919
862
|
repoUrl: null,
|
|
920
863
|
};
|
|
921
864
|
}
|
|
922
865
|
|
|
866
|
+
/**
|
|
867
|
+
* Read version from .claude-plugin/marketplace.json for a module
|
|
868
|
+
* @param {string} moduleName - Module code
|
|
869
|
+
* @returns {string|null} Version or null
|
|
870
|
+
*/
|
|
871
|
+
async _readMarketplaceVersion(moduleName, moduleSourcePath = null) {
|
|
872
|
+
const os = require('node:os');
|
|
873
|
+
let marketplacePath;
|
|
874
|
+
|
|
875
|
+
if (['core', 'bmm'].includes(moduleName)) {
|
|
876
|
+
marketplacePath = path.join(getProjectRoot(), '.claude-plugin', 'marketplace.json');
|
|
877
|
+
} else if (moduleSourcePath) {
|
|
878
|
+
// Walk up from source path to find marketplace.json
|
|
879
|
+
let dir = moduleSourcePath;
|
|
880
|
+
for (let i = 0; i < 5; i++) {
|
|
881
|
+
const candidate = path.join(dir, '.claude-plugin', 'marketplace.json');
|
|
882
|
+
if (await fs.pathExists(candidate)) {
|
|
883
|
+
marketplacePath = candidate;
|
|
884
|
+
break;
|
|
885
|
+
}
|
|
886
|
+
const parent = path.dirname(dir);
|
|
887
|
+
if (parent === dir) break;
|
|
888
|
+
dir = parent;
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
// Fallback to external module cache
|
|
893
|
+
if (!marketplacePath) {
|
|
894
|
+
const cacheDir = path.join(os.homedir(), '.bmad', 'cache', 'external-modules', moduleName);
|
|
895
|
+
marketplacePath = path.join(cacheDir, '.claude-plugin', 'marketplace.json');
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
try {
|
|
899
|
+
if (await fs.pathExists(marketplacePath)) {
|
|
900
|
+
const data = JSON.parse(await fs.readFile(marketplacePath, 'utf8'));
|
|
901
|
+
const plugins = data?.plugins;
|
|
902
|
+
if (!Array.isArray(plugins) || plugins.length === 0) return null;
|
|
903
|
+
let best = null;
|
|
904
|
+
for (const p of plugins) {
|
|
905
|
+
if (p.version && (!best || p.version > best)) best = p.version;
|
|
906
|
+
}
|
|
907
|
+
return best;
|
|
908
|
+
}
|
|
909
|
+
} catch {
|
|
910
|
+
// ignore
|
|
911
|
+
}
|
|
912
|
+
return null;
|
|
913
|
+
}
|
|
914
|
+
|
|
923
915
|
/**
|
|
924
916
|
* Fetch latest version from npm for a package
|
|
925
917
|
* @param {string} packageName - npm package name
|
|
@@ -86,7 +86,7 @@ class ConfigDrivenIdeSetup {
|
|
|
86
86
|
if (!options.silent) await prompts.log.info(`Setting up ${this.name}...`);
|
|
87
87
|
|
|
88
88
|
// Clean up any old BMAD installation first
|
|
89
|
-
await this.cleanup(projectDir, options);
|
|
89
|
+
await this.cleanup(projectDir, options, bmadDir);
|
|
90
90
|
|
|
91
91
|
if (!this.installerConfig) {
|
|
92
92
|
return { success: false, reason: 'no-config' };
|
|
@@ -183,18 +183,6 @@ class ConfigDrivenIdeSetup {
|
|
|
183
183
|
count++;
|
|
184
184
|
}
|
|
185
185
|
|
|
186
|
-
// Post-install cleanup: remove _bmad/ directories for skills with install_to_bmad === "false"
|
|
187
|
-
for (const record of records) {
|
|
188
|
-
if (record.install_to_bmad === 'false') {
|
|
189
|
-
const relativePath = record.path.startsWith(bmadPrefix) ? record.path.slice(bmadPrefix.length) : record.path;
|
|
190
|
-
const sourceFile = path.join(bmadDir, relativePath);
|
|
191
|
-
const sourceDir = path.dirname(sourceFile);
|
|
192
|
-
if (await fs.pathExists(sourceDir)) {
|
|
193
|
-
await fs.remove(sourceDir);
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
|
|
198
186
|
return count;
|
|
199
187
|
}
|
|
200
188
|
|
|
@@ -215,16 +203,42 @@ class ConfigDrivenIdeSetup {
|
|
|
215
203
|
* Cleanup IDE configuration
|
|
216
204
|
* @param {string} projectDir - Project directory
|
|
217
205
|
*/
|
|
218
|
-
async cleanup(projectDir, options = {}) {
|
|
206
|
+
async cleanup(projectDir, options = {}, bmadDir = null) {
|
|
207
|
+
const resolvedBmadDir = bmadDir || (await this._findBmadDir(projectDir));
|
|
208
|
+
|
|
209
|
+
// Build removal set: previously installed skills + removals.txt entries
|
|
210
|
+
let removalSet;
|
|
211
|
+
if (options.previousSkillIds && options.previousSkillIds.size > 0) {
|
|
212
|
+
// Install/update flow: use pre-captured skill IDs (before manifest was overwritten)
|
|
213
|
+
removalSet = new Set(options.previousSkillIds);
|
|
214
|
+
if (resolvedBmadDir) {
|
|
215
|
+
const removals = await this.loadRemovalLists(resolvedBmadDir);
|
|
216
|
+
for (const entry of removals) removalSet.add(entry);
|
|
217
|
+
}
|
|
218
|
+
} else if (resolvedBmadDir) {
|
|
219
|
+
// Uninstall flow: read from current skill-manifest.csv + removals.txt
|
|
220
|
+
removalSet = await this._buildUninstallSet(resolvedBmadDir);
|
|
221
|
+
} else {
|
|
222
|
+
removalSet = new Set();
|
|
223
|
+
}
|
|
224
|
+
|
|
219
225
|
// Migrate legacy target directories (e.g. .opencode/agent → .opencode/agents)
|
|
226
|
+
// Legacy dirs are abandoned entirely, so use prefix matching (null removalSet)
|
|
220
227
|
if (this.installerConfig?.legacy_targets) {
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
+
const legacyDirsExist = await Promise.all(
|
|
229
|
+
this.installerConfig.legacy_targets.map((d) =>
|
|
230
|
+
this.isGlobalPath(d) ? fs.pathExists(d.replace(/^~/, os.homedir())) : fs.pathExists(path.join(projectDir, d)),
|
|
231
|
+
),
|
|
232
|
+
);
|
|
233
|
+
if (legacyDirsExist.some(Boolean)) {
|
|
234
|
+
if (!options.silent) await prompts.log.message(' Migrating legacy directories...');
|
|
235
|
+
for (const legacyDir of this.installerConfig.legacy_targets) {
|
|
236
|
+
if (this.isGlobalPath(legacyDir)) {
|
|
237
|
+
await this.warnGlobalLegacy(legacyDir, options);
|
|
238
|
+
} else {
|
|
239
|
+
await this.cleanupTarget(projectDir, legacyDir, options, null);
|
|
240
|
+
await this.removeEmptyParents(projectDir, legacyDir);
|
|
241
|
+
}
|
|
228
242
|
}
|
|
229
243
|
}
|
|
230
244
|
}
|
|
@@ -244,9 +258,9 @@ class ConfigDrivenIdeSetup {
|
|
|
244
258
|
await this.cleanupRovoDevPrompts(projectDir, options);
|
|
245
259
|
}
|
|
246
260
|
|
|
247
|
-
// Clean target directory
|
|
261
|
+
// Clean current target directory
|
|
248
262
|
if (this.installerConfig?.target_dir) {
|
|
249
|
-
await this.cleanupTarget(projectDir, this.installerConfig.target_dir, options);
|
|
263
|
+
await this.cleanupTarget(projectDir, this.installerConfig.target_dir, options, removalSet);
|
|
250
264
|
}
|
|
251
265
|
}
|
|
252
266
|
|
|
@@ -286,23 +300,117 @@ class ConfigDrivenIdeSetup {
|
|
|
286
300
|
}
|
|
287
301
|
|
|
288
302
|
/**
|
|
289
|
-
*
|
|
303
|
+
* Find the _bmad directory in a project
|
|
304
|
+
* @param {string} projectDir - Project directory
|
|
305
|
+
* @returns {string|null} Path to bmad dir or null
|
|
306
|
+
*/
|
|
307
|
+
async _findBmadDir(projectDir) {
|
|
308
|
+
const bmadDir = path.join(projectDir, BMAD_FOLDER_NAME);
|
|
309
|
+
return (await fs.pathExists(bmadDir)) ? bmadDir : null;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Build the full set of entries to remove for uninstall.
|
|
314
|
+
* Reads skill-manifest.csv to know exactly what was installed, plus removal lists.
|
|
315
|
+
* @param {string} bmadDir - BMAD installation directory
|
|
316
|
+
* @returns {Set<string>} Set of entries to remove
|
|
317
|
+
*/
|
|
318
|
+
async _buildUninstallSet(bmadDir) {
|
|
319
|
+
const removals = await this.loadRemovalLists(bmadDir);
|
|
320
|
+
|
|
321
|
+
// Also add all currently installed skills from skill-manifest.csv
|
|
322
|
+
const csvPath = path.join(bmadDir, '_config', 'skill-manifest.csv');
|
|
323
|
+
try {
|
|
324
|
+
if (await fs.pathExists(csvPath)) {
|
|
325
|
+
const content = await fs.readFile(csvPath, 'utf8');
|
|
326
|
+
const records = csv.parse(content, { columns: true, skip_empty_lines: true });
|
|
327
|
+
for (const record of records) {
|
|
328
|
+
if (record.canonicalId) {
|
|
329
|
+
removals.add(record.canonicalId);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
} catch {
|
|
334
|
+
// If we can't read the manifest, we still have the removal lists
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
return removals;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Load removal lists from all module sources in the bmad directory.
|
|
342
|
+
* Each module can have an optional removals.txt listing entries to remove.
|
|
343
|
+
* @param {string} bmadDir - BMAD installation directory
|
|
344
|
+
* @returns {Set<string>} Set of entries to remove
|
|
345
|
+
*/
|
|
346
|
+
async loadRemovalLists(bmadDir) {
|
|
347
|
+
const removals = new Set();
|
|
348
|
+
const { getProjectRoot } = require('../project-root');
|
|
349
|
+
|
|
350
|
+
// Read project-level removals.txt (covers core and bmm)
|
|
351
|
+
const projectRemovalsPath = path.join(getProjectRoot(), 'removals.txt');
|
|
352
|
+
await this._readRemovalFile(projectRemovalsPath, removals);
|
|
353
|
+
|
|
354
|
+
// Read per-module removals.txt from installed module directories
|
|
355
|
+
try {
|
|
356
|
+
const entries = await fs.readdir(bmadDir);
|
|
357
|
+
for (const entry of entries) {
|
|
358
|
+
if (entry.startsWith('_')) continue;
|
|
359
|
+
const removalPath = path.join(bmadDir, entry, 'removals.txt');
|
|
360
|
+
await this._readRemovalFile(removalPath, removals);
|
|
361
|
+
}
|
|
362
|
+
} catch {
|
|
363
|
+
// bmadDir may not exist yet on fresh install
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
return removals;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* Read a removals.txt file and add entries to the set
|
|
371
|
+
* @param {string} filePath - Path to removals.txt
|
|
372
|
+
* @param {Set<string>} removals - Set to add entries to
|
|
373
|
+
*/
|
|
374
|
+
async _readRemovalFile(filePath, removals) {
|
|
375
|
+
try {
|
|
376
|
+
if (await fs.pathExists(filePath)) {
|
|
377
|
+
const content = await fs.readFile(filePath, 'utf8');
|
|
378
|
+
for (const line of content.split('\n')) {
|
|
379
|
+
const trimmed = line.trim();
|
|
380
|
+
if (trimmed && !trimmed.startsWith('#')) {
|
|
381
|
+
removals.add(trimmed);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
} catch {
|
|
386
|
+
// Optional file — ignore errors
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Cleanup a specific target directory.
|
|
392
|
+
* When removalSet is provided, only removes entries in that set.
|
|
393
|
+
* When removalSet is null (legacy dirs), removes all bmad-prefixed entries.
|
|
290
394
|
* @param {string} projectDir - Project directory
|
|
291
395
|
* @param {string} targetDir - Target directory to clean
|
|
396
|
+
* @param {Object} options - Cleanup options
|
|
397
|
+
* @param {Set<string>|null} removalSet - Entries to remove, or null for legacy prefix matching
|
|
292
398
|
*/
|
|
293
|
-
async cleanupTarget(projectDir, targetDir, options = {}) {
|
|
399
|
+
async cleanupTarget(projectDir, targetDir, options = {}, removalSet = new Set()) {
|
|
294
400
|
const targetPath = path.join(projectDir, targetDir);
|
|
295
401
|
|
|
296
402
|
if (!(await fs.pathExists(targetPath))) {
|
|
297
403
|
return;
|
|
298
404
|
}
|
|
299
405
|
|
|
300
|
-
|
|
406
|
+
if (removalSet && removalSet.size === 0) {
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
409
|
+
|
|
301
410
|
let entries;
|
|
302
411
|
try {
|
|
303
412
|
entries = await fs.readdir(targetPath);
|
|
304
413
|
} catch {
|
|
305
|
-
// Directory exists but can't be read - skip cleanup
|
|
306
414
|
return;
|
|
307
415
|
}
|
|
308
416
|
|
|
@@ -313,23 +421,26 @@ class ConfigDrivenIdeSetup {
|
|
|
313
421
|
let removedCount = 0;
|
|
314
422
|
|
|
315
423
|
for (const entry of entries) {
|
|
316
|
-
if (!entry || typeof entry !== 'string')
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
if (entry.startsWith('bmad
|
|
320
|
-
|
|
424
|
+
if (!entry || typeof entry !== 'string') continue;
|
|
425
|
+
|
|
426
|
+
// Always preserve bmad-os-* utility skills regardless of cleanup mode
|
|
427
|
+
if (entry.startsWith('bmad-os-')) continue;
|
|
428
|
+
|
|
429
|
+
// Surgical removal from set, or legacy prefix matching when set is null
|
|
430
|
+
const shouldRemove = removalSet ? removalSet.has(entry) : entry.startsWith('bmad');
|
|
431
|
+
|
|
432
|
+
if (shouldRemove) {
|
|
321
433
|
try {
|
|
322
|
-
await fs.remove(
|
|
434
|
+
await fs.remove(path.join(targetPath, entry));
|
|
323
435
|
removedCount++;
|
|
324
436
|
} catch {
|
|
325
|
-
// Skip entries that can't be removed
|
|
437
|
+
// Skip entries that can't be removed
|
|
326
438
|
}
|
|
327
439
|
}
|
|
328
440
|
}
|
|
329
441
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
}
|
|
442
|
+
// Only log cleanup when it's not a routine reinstall (legacy dir cleanup or actual removals)
|
|
443
|
+
// Suppress for current target_dir since it's always cleaned before a fresh write
|
|
333
444
|
|
|
334
445
|
// Remove empty directory after cleanup
|
|
335
446
|
if (removedCount > 0) {
|
|
@@ -339,7 +450,7 @@ class ConfigDrivenIdeSetup {
|
|
|
339
450
|
await fs.remove(targetPath);
|
|
340
451
|
}
|
|
341
452
|
} catch {
|
|
342
|
-
// Directory may already be gone or in use
|
|
453
|
+
// Directory may already be gone or in use
|
|
343
454
|
}
|
|
344
455
|
}
|
|
345
456
|
}
|
|
@@ -54,19 +54,4 @@ function getArtifactType(manifest, filename) {
|
|
|
54
54
|
return null;
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
-
|
|
58
|
-
* Get the install_to_bmad flag for a specific file from a loaded skill manifest.
|
|
59
|
-
* @param {Object|null} manifest - Loaded manifest (from loadSkillManifest)
|
|
60
|
-
* @param {string} filename - Source filename to look up
|
|
61
|
-
* @returns {boolean} install_to_bmad value (defaults to true)
|
|
62
|
-
*/
|
|
63
|
-
function getInstallToBmad(manifest, filename) {
|
|
64
|
-
if (!manifest) return true;
|
|
65
|
-
// Single-entry manifest applies to all files in the directory
|
|
66
|
-
if (manifest.__single) return manifest.__single.install_to_bmad !== false;
|
|
67
|
-
// Multi-entry: look up by filename directly
|
|
68
|
-
if (manifest[filename]) return manifest[filename].install_to_bmad !== false;
|
|
69
|
-
return true;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
module.exports = { loadSkillManifest, getCanonicalId, getArtifactType, getInstallToBmad };
|
|
57
|
+
module.exports = { loadSkillManifest, getCanonicalId, getArtifactType };
|