bmad-method 6.0.0-alpha.14 → 6.0.0-alpha.15
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/.coderabbit.yaml +36 -0
- package/{CODE_OF_CONDUCT.md → .github/CODE_OF_CONDUCT.md} +4 -4
- package/CHANGELOG.md +136 -408
- package/README.md +4 -1
- package/docs/custom-content-installation.md +245 -0
- package/docs/index.md +2 -2
- package/docs/installers-bundlers/installers-modules-platforms-reference.md +6 -5
- package/docs/web-bundles-gemini-gpt-guide.md +1 -1
- package/example-custom-content/README.md +4 -0
- package/example-custom-content/agents/commit-poet/commit-poet.agent.yaml +1 -1
- package/example-custom-content/agents/toolsmith/toolsmith-sidecar/instructions.md +1 -1
- package/example-custom-content/agents/toolsmith/toolsmith-sidecar/knowledge/docs.md +1 -1
- package/example-custom-content/agents/toolsmith/toolsmith-sidecar/knowledge/installers.md +1 -1
- package/example-custom-content/agents/toolsmith/toolsmith-sidecar/knowledge/modules.md +2 -2
- package/example-custom-content/agents/toolsmith/toolsmith.agent.yaml +1 -1
- package/example-custom-content/{custom.yaml → module.yaml} +1 -0
- package/example-custom-content/workflows/quiz-master/steps/step-01-init.md +2 -2
- package/example-custom-content/workflows/quiz-master/steps/step-02-q1.md +1 -1
- package/example-custom-content/workflows/quiz-master/steps/step-03-q2.md +1 -1
- package/example-custom-content/workflows/quiz-master/steps/step-04-q3.md +1 -1
- package/example-custom-content/workflows/quiz-master/steps/step-05-q4.md +1 -1
- package/example-custom-content/workflows/quiz-master/steps/step-06-q5.md +1 -1
- package/example-custom-content/workflows/quiz-master/steps/step-07-q6.md +1 -1
- package/example-custom-content/workflows/quiz-master/steps/step-08-q7.md +1 -1
- package/example-custom-content/workflows/quiz-master/steps/step-09-q8.md +1 -1
- package/example-custom-content/workflows/quiz-master/steps/step-10-q9.md +1 -1
- package/example-custom-content/workflows/quiz-master/steps/step-11-q10.md +1 -1
- package/example-custom-content/workflows/quiz-master/steps/step-12-results.md +1 -1
- package/example-custom-content/workflows/quiz-master/workflow.md +1 -1
- package/example-custom-module/mwm/README.md +5 -0
- package/example-custom-module/mwm/agents/cbt-coach/cbt-coach.agent.yaml +1 -0
- package/example-custom-module/mwm/agents/crisis-navigator.agent.yaml +3 -2
- package/example-custom-module/mwm/agents/meditation-guide.agent.yaml +3 -2
- package/example-custom-module/mwm/agents/wellness-companion/wellness-companion.agent.yaml +1 -0
- package/example-custom-module/mwm/{_module-installer/install-config.yaml → module.yaml} +1 -0
- package/package.json +1 -1
- package/src/core/_module-installer/installer.js +1 -1
- package/src/modules/bmb/_module-installer/installer.js +1 -1
- package/src/modules/bmb/docs/agents/index.md +1 -1
- package/src/modules/bmb/workflows/create-module/steps/step-04-structure.md +3 -3
- package/src/modules/bmb/workflows/create-module/steps/step-05-config.md +1 -1
- package/src/modules/bmb/workflows/create-module/steps/step-08-installer.md +8 -8
- package/src/modules/bmb/workflows/create-module/steps/step-09-documentation.md +2 -1
- package/src/modules/bmb/workflows/create-module/steps/step-10-roadmap.md +3 -2
- package/src/modules/bmb/workflows/create-module/steps/step-11-validate.md +3 -3
- package/src/modules/bmb/workflows/create-module/templates/installer.template.js +1 -1
- package/src/modules/bmb/workflows/create-module/validation.md +3 -3
- package/src/modules/bmb/workflows/create-workflow/steps/step-01-init.md +1 -1
- package/src/modules/bmb/workflows/create-workflow/steps/step-07-build.md +1 -1
- package/src/modules/bmgd/README.md +2 -1
- package/src/modules/bmm/_module-installer/installer.js +1 -1
- package/src/modules/bmm/_module-installer/platform-specifics/claude-code.js +1 -1
- package/src/modules/bmm/_module-installer/platform-specifics/windsurf.js +1 -1
- package/src/modules/cis/_module-installer/installer.js +1 -1
- package/tools/cli/README.md +4 -4
- package/tools/cli/installers/lib/core/config-collector.js +16 -8
- package/tools/cli/installers/lib/core/custom-module-cache.js +239 -0
- package/tools/cli/installers/lib/core/detector.js +8 -4
- package/tools/cli/installers/lib/core/installer.js +815 -23
- package/tools/cli/installers/lib/core/manifest-generator.js +176 -13
- package/tools/cli/installers/lib/core/manifest.js +47 -0
- package/tools/cli/installers/lib/custom/handler.js +150 -20
- package/tools/cli/installers/lib/modules/manager.js +78 -32
- package/tools/cli/lib/agent/compiler.js +3 -11
- package/tools/cli/lib/agent/installer.js +2 -1
- package/tools/cli/lib/cli-utils.js +21 -4
- package/tools/cli/lib/ui.js +499 -11
- package/tools/maintainer/review-pr-README.md +55 -0
- package/tools/maintainer/review-pr.md +242 -0
- package/tools/migrate-custom-module-paths.js +124 -0
- package/bmad-method-6.0.0-alpha.14.tgz +0 -0
- package/docs/custom-agent-installation.md +0 -137
- package/example-custom-content/workflows/quiz-master/workflow-plan-quiz-master.md +0 -269
- /package/src/core/{_module-installer/install-config.yaml → module.yaml} +0 -0
- /package/src/modules/bmb/{_module-installer/install-config.yaml → module.yaml} +0 -0
- /package/src/modules/bmb/workflows/create-module/templates/{install-config.template.yaml → module.template.yaml} +0 -0
- /package/src/modules/bmgd/{_module-installer/install-config.yaml → module.yaml} +0 -0
- /package/src/modules/bmm/{_module-installer/install-config.yaml → module.yaml} +0 -0
- /package/src/modules/cis/{_module-installer/install-config.yaml → module.yaml} +0 -0
|
@@ -22,6 +22,7 @@ const path = require('node:path');
|
|
|
22
22
|
const fs = require('fs-extra');
|
|
23
23
|
const chalk = require('chalk');
|
|
24
24
|
const ora = require('ora');
|
|
25
|
+
const inquirer = require('inquirer');
|
|
25
26
|
const { Detector } = require('./detector');
|
|
26
27
|
const { Manifest } = require('./manifest');
|
|
27
28
|
const { ModuleManager } = require('../modules/manager');
|
|
@@ -129,7 +130,7 @@ class Installer {
|
|
|
129
130
|
*/
|
|
130
131
|
async copyFileWithPlaceholderReplacement(sourcePath, targetPath, bmadFolderName) {
|
|
131
132
|
// List of text file extensions that should have placeholder replacement
|
|
132
|
-
const textExtensions = ['.md', '.yaml', '.yml', '.txt', '.json', '.js', '.ts', '.html', '.css', '.sh', '.bat', '.csv'];
|
|
133
|
+
const textExtensions = ['.md', '.yaml', '.yml', '.txt', '.json', '.js', '.ts', '.html', '.css', '.sh', '.bat', '.csv', '.xml'];
|
|
133
134
|
const ext = path.extname(sourcePath).toLowerCase();
|
|
134
135
|
|
|
135
136
|
// Check if this is a text file that might contain placeholders
|
|
@@ -750,13 +751,81 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
|
|
750
751
|
spinner.text = 'Creating directory structure...';
|
|
751
752
|
await this.createDirectoryStructure(bmadDir);
|
|
752
753
|
|
|
753
|
-
//
|
|
754
|
-
spinner.text = 'Resolving dependencies...';
|
|
754
|
+
// Get project root
|
|
755
755
|
const projectRoot = getProjectRoot();
|
|
756
|
-
|
|
756
|
+
|
|
757
|
+
// Step 1: Install core module first (if requested)
|
|
758
|
+
if (config.installCore) {
|
|
759
|
+
spinner.start('Installing BMAD core...');
|
|
760
|
+
await this.installCoreWithDependencies(bmadDir, { core: {} });
|
|
761
|
+
spinner.succeed('Core installed');
|
|
762
|
+
|
|
763
|
+
// Generate core config file
|
|
764
|
+
await this.generateModuleConfigs(bmadDir, { core: config.coreConfig || {} });
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
// Custom content is already handled in UI before module selection
|
|
768
|
+
let finalCustomContent = config.customContent;
|
|
769
|
+
|
|
770
|
+
// Step 3: Prepare modules list including cached custom modules
|
|
771
|
+
let allModules = [...(config.modules || [])];
|
|
772
|
+
|
|
773
|
+
// During quick update, we might have custom module sources from the manifest
|
|
774
|
+
if (config._customModuleSources) {
|
|
775
|
+
// Add custom modules from stored sources
|
|
776
|
+
for (const [moduleId, customInfo] of config._customModuleSources) {
|
|
777
|
+
if (!allModules.includes(moduleId) && (await fs.pathExists(customInfo.sourcePath))) {
|
|
778
|
+
allModules.push(moduleId);
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
// Add cached custom modules
|
|
784
|
+
if (finalCustomContent && finalCustomContent.cachedModules) {
|
|
785
|
+
for (const cachedModule of finalCustomContent.cachedModules) {
|
|
786
|
+
if (!allModules.includes(cachedModule.id)) {
|
|
787
|
+
allModules.push(cachedModule.id);
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
// Regular custom content from user input (non-cached)
|
|
793
|
+
if (finalCustomContent && finalCustomContent.selected && finalCustomContent.selectedFiles) {
|
|
794
|
+
// Add custom modules to the installation list
|
|
795
|
+
for (const customFile of finalCustomContent.selectedFiles) {
|
|
796
|
+
const { CustomHandler } = require('../custom/handler');
|
|
797
|
+
const customHandler = new CustomHandler();
|
|
798
|
+
const customInfo = await customHandler.getCustomInfo(customFile, projectDir);
|
|
799
|
+
if (customInfo && customInfo.id) {
|
|
800
|
+
allModules.push(customInfo.id);
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
// Don't include core again if already installed
|
|
806
|
+
if (config.installCore) {
|
|
807
|
+
allModules = allModules.filter((m) => m !== 'core');
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
const modulesToInstall = allModules;
|
|
757
811
|
|
|
758
812
|
// For dependency resolution, we need to pass the project root
|
|
759
|
-
|
|
813
|
+
// Create a temporary module manager that knows about custom content locations
|
|
814
|
+
const tempModuleManager = new ModuleManager({
|
|
815
|
+
scanProjectForModules: true,
|
|
816
|
+
bmadDir: bmadDir, // Pass bmadDir so we can check cache
|
|
817
|
+
});
|
|
818
|
+
|
|
819
|
+
// Make sure custom modules are discoverable
|
|
820
|
+
if (config.customContent && config.customContent.selected && config.customContent.selectedFiles) {
|
|
821
|
+
// The dependency resolver needs to know about these modules
|
|
822
|
+
// We'll handle custom modules separately in the installation loop
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
const resolution = await this.dependencyResolver.resolve(projectRoot, allModules, {
|
|
826
|
+
verbose: config.verbose,
|
|
827
|
+
moduleManager: tempModuleManager,
|
|
828
|
+
});
|
|
760
829
|
|
|
761
830
|
if (config.verbose) {
|
|
762
831
|
spinner.succeed('Dependencies resolved');
|
|
@@ -764,24 +833,159 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
|
|
764
833
|
spinner.succeed('Dependencies resolved');
|
|
765
834
|
}
|
|
766
835
|
|
|
767
|
-
//
|
|
768
|
-
if (config.installCore || resolution.byModule.core) {
|
|
769
|
-
spinner.start('Installing BMAD core...');
|
|
770
|
-
await this.installCoreWithDependencies(bmadDir, resolution.byModule.core);
|
|
771
|
-
spinner.succeed('Core installed');
|
|
772
|
-
}
|
|
836
|
+
// Core is already installed above, skip if included in resolution
|
|
773
837
|
|
|
774
838
|
// Install modules with their dependencies
|
|
775
|
-
if (
|
|
776
|
-
|
|
839
|
+
if (allModules && allModules.length > 0) {
|
|
840
|
+
const installedModuleNames = new Set();
|
|
841
|
+
|
|
842
|
+
for (const moduleName of allModules) {
|
|
843
|
+
// Skip if already installed
|
|
844
|
+
if (installedModuleNames.has(moduleName)) {
|
|
845
|
+
continue;
|
|
846
|
+
}
|
|
847
|
+
installedModuleNames.add(moduleName);
|
|
848
|
+
|
|
777
849
|
spinner.start(`Installing module: ${moduleName}...`);
|
|
778
|
-
|
|
850
|
+
|
|
851
|
+
// Check if this is a custom module
|
|
852
|
+
let isCustomModule = false;
|
|
853
|
+
let customInfo = null;
|
|
854
|
+
let useCache = false;
|
|
855
|
+
|
|
856
|
+
// First check if we have a cached version
|
|
857
|
+
if (finalCustomContent && finalCustomContent.cachedModules) {
|
|
858
|
+
const cachedModule = finalCustomContent.cachedModules.find((m) => m.id === moduleName);
|
|
859
|
+
if (cachedModule) {
|
|
860
|
+
isCustomModule = true;
|
|
861
|
+
customInfo = {
|
|
862
|
+
id: moduleName,
|
|
863
|
+
path: cachedModule.cachePath,
|
|
864
|
+
config: {},
|
|
865
|
+
};
|
|
866
|
+
useCache = true;
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
// Then check if we have custom module sources from the manifest (for quick update)
|
|
871
|
+
if (!isCustomModule && config._customModuleSources && config._customModuleSources.has(moduleName)) {
|
|
872
|
+
customInfo = config._customModuleSources.get(moduleName);
|
|
873
|
+
isCustomModule = true;
|
|
874
|
+
|
|
875
|
+
// Check if this is a cached module (source path starts with _cfg)
|
|
876
|
+
if (customInfo.sourcePath && (customInfo.sourcePath.startsWith('_cfg') || customInfo.sourcePath.includes('_cfg/custom'))) {
|
|
877
|
+
useCache = true;
|
|
878
|
+
// Make sure we have the right path structure
|
|
879
|
+
if (!customInfo.path) {
|
|
880
|
+
customInfo.path = customInfo.sourcePath;
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
// Finally check regular custom content
|
|
886
|
+
if (!isCustomModule && finalCustomContent && finalCustomContent.selected && finalCustomContent.selectedFiles) {
|
|
887
|
+
const { CustomHandler } = require('../custom/handler');
|
|
888
|
+
const customHandler = new CustomHandler();
|
|
889
|
+
for (const customFile of finalCustomContent.selectedFiles) {
|
|
890
|
+
const info = await customHandler.getCustomInfo(customFile, projectDir);
|
|
891
|
+
if (info && info.id === moduleName) {
|
|
892
|
+
isCustomModule = true;
|
|
893
|
+
customInfo = info;
|
|
894
|
+
break;
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
if (isCustomModule && customInfo) {
|
|
900
|
+
// Install custom module using CustomHandler but as a proper module
|
|
901
|
+
const { CustomHandler } = require('../custom/handler');
|
|
902
|
+
const customHandler = new CustomHandler();
|
|
903
|
+
|
|
904
|
+
// Install to module directory instead of custom directory
|
|
905
|
+
const moduleTargetPath = path.join(bmadDir, moduleName);
|
|
906
|
+
await fs.ensureDir(moduleTargetPath);
|
|
907
|
+
|
|
908
|
+
const result = await customHandler.install(
|
|
909
|
+
customInfo.path,
|
|
910
|
+
path.join(bmadDir, 'temp-custom'),
|
|
911
|
+
{ ...config.coreConfig, ...customInfo.config, _bmadDir: bmadDir },
|
|
912
|
+
(filePath) => {
|
|
913
|
+
// Track installed files with correct path
|
|
914
|
+
const relativePath = path.relative(path.join(bmadDir, 'temp-custom'), filePath);
|
|
915
|
+
const finalPath = path.join(moduleTargetPath, relativePath);
|
|
916
|
+
this.installedFiles.push(finalPath);
|
|
917
|
+
},
|
|
918
|
+
);
|
|
919
|
+
|
|
920
|
+
// Move from temp-custom to actual module directory
|
|
921
|
+
const tempCustomPath = path.join(bmadDir, 'temp-custom');
|
|
922
|
+
if (await fs.pathExists(tempCustomPath)) {
|
|
923
|
+
const customDir = path.join(tempCustomPath, 'custom');
|
|
924
|
+
if (await fs.pathExists(customDir)) {
|
|
925
|
+
// Move contents to module directory
|
|
926
|
+
const items = await fs.readdir(customDir);
|
|
927
|
+
for (const item of items) {
|
|
928
|
+
const srcPath = path.join(customDir, item);
|
|
929
|
+
const destPath = path.join(moduleTargetPath, item);
|
|
930
|
+
|
|
931
|
+
// If destination exists, remove it first (or we could merge)
|
|
932
|
+
if (await fs.pathExists(destPath)) {
|
|
933
|
+
await fs.remove(destPath);
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
await fs.move(srcPath, destPath);
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
await fs.remove(tempCustomPath);
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
// Create module config
|
|
943
|
+
await this.generateModuleConfigs(bmadDir, { [moduleName]: { ...config.coreConfig, ...customInfo.config } });
|
|
944
|
+
|
|
945
|
+
// Store custom module info for later manifest update
|
|
946
|
+
if (!config._customModulesToTrack) {
|
|
947
|
+
config._customModulesToTrack = [];
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
// For cached modules, use appropriate path handling
|
|
951
|
+
let sourcePath;
|
|
952
|
+
if (useCache) {
|
|
953
|
+
// Check if we have cached modules info (from initial install)
|
|
954
|
+
if (finalCustomContent && finalCustomContent.cachedModules) {
|
|
955
|
+
sourcePath = finalCustomContent.cachedModules.find((m) => m.id === moduleName)?.relativePath;
|
|
956
|
+
} else {
|
|
957
|
+
// During update, the sourcePath is already cache-relative if it starts with _cfg
|
|
958
|
+
sourcePath =
|
|
959
|
+
customInfo.sourcePath && customInfo.sourcePath.startsWith('_cfg')
|
|
960
|
+
? customInfo.sourcePath
|
|
961
|
+
: path.relative(bmadDir, customInfo.path || customInfo.sourcePath);
|
|
962
|
+
}
|
|
963
|
+
} else {
|
|
964
|
+
sourcePath = path.resolve(customInfo.path || customInfo.sourcePath);
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
config._customModulesToTrack.push({
|
|
968
|
+
id: customInfo.id,
|
|
969
|
+
name: customInfo.name,
|
|
970
|
+
sourcePath: sourcePath,
|
|
971
|
+
installDate: new Date().toISOString(),
|
|
972
|
+
});
|
|
973
|
+
} else {
|
|
974
|
+
// Regular module installation
|
|
975
|
+
// Special case for core module
|
|
976
|
+
if (moduleName === 'core') {
|
|
977
|
+
await this.installCoreWithDependencies(bmadDir, resolution.byModule[moduleName]);
|
|
978
|
+
} else {
|
|
979
|
+
await this.installModuleWithDependencies(moduleName, bmadDir, resolution.byModule[moduleName]);
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
|
|
779
983
|
spinner.succeed(`Module installed: ${moduleName}`);
|
|
780
984
|
}
|
|
781
985
|
|
|
782
986
|
// Install partial modules (only dependencies)
|
|
783
987
|
for (const [module, files] of Object.entries(resolution.byModule)) {
|
|
784
|
-
if (!
|
|
988
|
+
if (!allModules.includes(module) && module !== 'core') {
|
|
785
989
|
const totalFiles =
|
|
786
990
|
files.agents.length +
|
|
787
991
|
files.tasks.length +
|
|
@@ -798,6 +1002,72 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
|
|
798
1002
|
}
|
|
799
1003
|
}
|
|
800
1004
|
|
|
1005
|
+
// Install custom content if provided AND selected
|
|
1006
|
+
// Process custom content that wasn't installed as modules
|
|
1007
|
+
// This is now handled in the module installation loop above
|
|
1008
|
+
// This section is kept for backward compatibility with any custom content
|
|
1009
|
+
// that doesn't have a module structure
|
|
1010
|
+
const remainingCustomContent = [];
|
|
1011
|
+
if (
|
|
1012
|
+
config.customContent &&
|
|
1013
|
+
config.customContent.hasCustomContent &&
|
|
1014
|
+
config.customContent.customPath &&
|
|
1015
|
+
config.customContent.selected &&
|
|
1016
|
+
config.customContent.selectedFiles
|
|
1017
|
+
) {
|
|
1018
|
+
// Filter out custom modules that were already installed
|
|
1019
|
+
for (const customFile of config.customContent.selectedFiles) {
|
|
1020
|
+
const { CustomHandler } = require('../custom/handler');
|
|
1021
|
+
const customHandler = new CustomHandler();
|
|
1022
|
+
const customInfo = await customHandler.getCustomInfo(customFile, projectDir);
|
|
1023
|
+
|
|
1024
|
+
// Skip if this was installed as a module
|
|
1025
|
+
if (!customInfo || !customInfo.id || !allModules.includes(customInfo.id)) {
|
|
1026
|
+
remainingCustomContent.push(customFile);
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
if (remainingCustomContent.length > 0) {
|
|
1032
|
+
spinner.start('Installing remaining custom content...');
|
|
1033
|
+
const { CustomHandler } = require('../custom/handler');
|
|
1034
|
+
const customHandler = new CustomHandler();
|
|
1035
|
+
|
|
1036
|
+
// Use the remaining files
|
|
1037
|
+
const customFiles = remainingCustomContent;
|
|
1038
|
+
|
|
1039
|
+
if (customFiles.length > 0) {
|
|
1040
|
+
console.log(chalk.cyan(`\n Found ${customFiles.length} custom content file(s):`));
|
|
1041
|
+
for (const customFile of customFiles) {
|
|
1042
|
+
const customInfo = await customHandler.getCustomInfo(customFile, projectDir);
|
|
1043
|
+
if (customInfo) {
|
|
1044
|
+
console.log(chalk.dim(` • ${customInfo.name} (${customInfo.relativePath})`));
|
|
1045
|
+
|
|
1046
|
+
// Install the custom content
|
|
1047
|
+
const result = await customHandler.install(
|
|
1048
|
+
customInfo.path,
|
|
1049
|
+
bmadDir,
|
|
1050
|
+
{ ...config.coreConfig, ...customInfo.config },
|
|
1051
|
+
(filePath) => {
|
|
1052
|
+
// Track installed files
|
|
1053
|
+
this.installedFiles.push(filePath);
|
|
1054
|
+
},
|
|
1055
|
+
);
|
|
1056
|
+
|
|
1057
|
+
if (result.errors.length > 0) {
|
|
1058
|
+
console.log(chalk.yellow(` ⚠️ ${result.errors.length} error(s) occurred`));
|
|
1059
|
+
for (const error of result.errors) {
|
|
1060
|
+
console.log(chalk.dim(` - ${error}`));
|
|
1061
|
+
}
|
|
1062
|
+
} else {
|
|
1063
|
+
console.log(chalk.green(` ✓ Installed ${result.agentsInstalled} agents, ${result.workflowsInstalled} workflows`));
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
spinner.succeed('Custom content installed');
|
|
1069
|
+
}
|
|
1070
|
+
|
|
801
1071
|
// Generate clean config.yaml files for each installed module
|
|
802
1072
|
spinner.start('Generating module configurations...');
|
|
803
1073
|
await this.generateModuleConfigs(bmadDir, moduleConfigs);
|
|
@@ -820,14 +1090,37 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
|
|
820
1090
|
spinner.start('Generating workflow and agent manifests...');
|
|
821
1091
|
const manifestGen = new ManifestGenerator();
|
|
822
1092
|
|
|
823
|
-
//
|
|
824
|
-
|
|
1093
|
+
// For quick update, we need ALL installed modules in the manifest
|
|
1094
|
+
// Not just the ones being updated
|
|
1095
|
+
const allModulesForManifest = config._quickUpdate
|
|
1096
|
+
? config._existingModules || allModules || []
|
|
1097
|
+
: config._preserveModules
|
|
1098
|
+
? [...allModules, ...config._preserveModules]
|
|
1099
|
+
: allModules || [];
|
|
825
1100
|
|
|
826
|
-
|
|
1101
|
+
// For regular installs (including when called from quick update), use what we have
|
|
1102
|
+
let modulesForCsvPreserve;
|
|
1103
|
+
if (config._quickUpdate) {
|
|
1104
|
+
// Quick update - use existing modules or fall back to modules being updated
|
|
1105
|
+
modulesForCsvPreserve = config._existingModules || allModules || [];
|
|
1106
|
+
} else {
|
|
1107
|
+
// Regular install - use the modules we're installing plus any preserved ones
|
|
1108
|
+
modulesForCsvPreserve = config._preserveModules ? [...allModules, ...config._preserveModules] : allModules;
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
const manifestStats = await manifestGen.generateManifests(bmadDir, allModulesForManifest, this.installedFiles, {
|
|
827
1112
|
ides: config.ides || [],
|
|
828
|
-
preservedModules:
|
|
1113
|
+
preservedModules: modulesForCsvPreserve, // Scan these from installed bmad/ dir
|
|
829
1114
|
});
|
|
830
1115
|
|
|
1116
|
+
// Add custom modules to manifest (now that it exists)
|
|
1117
|
+
if (config._customModulesToTrack && config._customModulesToTrack.length > 0) {
|
|
1118
|
+
spinner.text = 'Storing custom module sources...';
|
|
1119
|
+
for (const customModule of config._customModulesToTrack) {
|
|
1120
|
+
await this.manifest.addCustomModule(bmadDir, customModule);
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
|
|
831
1124
|
spinner.succeed(
|
|
832
1125
|
`Manifests generated: ${manifestStats.workflows} workflows, ${manifestStats.agents} agents, ${manifestStats.tasks} tasks, ${manifestStats.tools} tools, ${manifestStats.files} files`,
|
|
833
1126
|
);
|
|
@@ -1090,6 +1383,30 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
|
|
1090
1383
|
const currentVersion = existingInstall.version;
|
|
1091
1384
|
const newVersion = require(path.join(getProjectRoot(), 'package.json')).version;
|
|
1092
1385
|
|
|
1386
|
+
// Check for custom modules with missing sources before update
|
|
1387
|
+
const customModuleSources = new Map();
|
|
1388
|
+
if (existingInstall.customModules) {
|
|
1389
|
+
for (const customModule of existingInstall.customModules) {
|
|
1390
|
+
customModuleSources.set(customModule.id, customModule);
|
|
1391
|
+
}
|
|
1392
|
+
}
|
|
1393
|
+
|
|
1394
|
+
if (customModuleSources.size > 0) {
|
|
1395
|
+
spinner.stop();
|
|
1396
|
+
console.log(chalk.yellow('\nChecking custom module sources before update...'));
|
|
1397
|
+
|
|
1398
|
+
const projectRoot = getProjectRoot();
|
|
1399
|
+
await this.handleMissingCustomSources(
|
|
1400
|
+
customModuleSources,
|
|
1401
|
+
bmadDir,
|
|
1402
|
+
projectRoot,
|
|
1403
|
+
'update',
|
|
1404
|
+
existingInstall.modules.map((m) => m.id),
|
|
1405
|
+
);
|
|
1406
|
+
|
|
1407
|
+
spinner.start('Preparing update...');
|
|
1408
|
+
}
|
|
1409
|
+
|
|
1093
1410
|
if (config.dryRun) {
|
|
1094
1411
|
spinner.stop();
|
|
1095
1412
|
console.log(chalk.cyan('\n🔍 Update Preview (Dry Run)\n'));
|
|
@@ -1547,6 +1864,9 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
|
|
1547
1864
|
// DO NOT replace {project-root} - LLMs understand this placeholder at runtime
|
|
1548
1865
|
// const processedContent = xmlContent.replaceAll('{project-root}', projectDir);
|
|
1549
1866
|
|
|
1867
|
+
// Replace {bmad_folder} with actual folder name
|
|
1868
|
+
xmlContent = xmlContent.replaceAll('{bmad_folder}', this.bmadFolderName || 'bmad');
|
|
1869
|
+
|
|
1550
1870
|
// Replace {agent_sidecar_folder} if configured
|
|
1551
1871
|
const coreConfig = this.configCollector.collectedConfig.core || {};
|
|
1552
1872
|
if (coreConfig.agent_sidecar_folder && xmlContent.includes('{agent_sidecar_folder}')) {
|
|
@@ -1858,6 +2178,24 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
|
|
1858
2178
|
throw new Error(`BMAD not installed at ${bmadDir}`);
|
|
1859
2179
|
}
|
|
1860
2180
|
|
|
2181
|
+
// Check for custom modules with missing sources
|
|
2182
|
+
const manifest = await this.manifest.read(bmadDir);
|
|
2183
|
+
if (manifest && manifest.customModules && manifest.customModules.length > 0) {
|
|
2184
|
+
spinner.stop();
|
|
2185
|
+
console.log(chalk.yellow('\nChecking custom module sources before compilation...'));
|
|
2186
|
+
|
|
2187
|
+
const customModuleSources = new Map();
|
|
2188
|
+
for (const customModule of manifest.customModules) {
|
|
2189
|
+
customModuleSources.set(customModule.id, customModule);
|
|
2190
|
+
}
|
|
2191
|
+
|
|
2192
|
+
const projectRoot = getProjectRoot();
|
|
2193
|
+
const installedModules = manifest.modules || [];
|
|
2194
|
+
await this.handleMissingCustomSources(customModuleSources, bmadDir, projectRoot, 'compile-agents', installedModules);
|
|
2195
|
+
|
|
2196
|
+
spinner.start('Rebuilding agent files...');
|
|
2197
|
+
}
|
|
2198
|
+
|
|
1861
2199
|
let agentCount = 0;
|
|
1862
2200
|
let taskCount = 0;
|
|
1863
2201
|
|
|
@@ -2002,17 +2340,245 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
|
|
2002
2340
|
const existingInstall = await this.detector.detect(bmadDir);
|
|
2003
2341
|
const installedModules = existingInstall.modules.map((m) => m.id);
|
|
2004
2342
|
const configuredIdes = existingInstall.ides || [];
|
|
2343
|
+
const projectRoot = path.dirname(bmadDir);
|
|
2344
|
+
|
|
2345
|
+
// Get custom module sources from manifest
|
|
2346
|
+
const customModuleSources = new Map();
|
|
2347
|
+
if (existingInstall.customModules) {
|
|
2348
|
+
for (const customModule of existingInstall.customModules) {
|
|
2349
|
+
// Ensure we have an absolute sourcePath
|
|
2350
|
+
let absoluteSourcePath = customModule.sourcePath;
|
|
2351
|
+
|
|
2352
|
+
// Check if sourcePath is a cache-relative path (starts with _cfg/)
|
|
2353
|
+
if (absoluteSourcePath && absoluteSourcePath.startsWith('_cfg')) {
|
|
2354
|
+
// Convert cache-relative path to absolute path
|
|
2355
|
+
absoluteSourcePath = path.join(bmadDir, absoluteSourcePath);
|
|
2356
|
+
}
|
|
2357
|
+
// If no sourcePath but we have relativePath, convert it
|
|
2358
|
+
else if (!absoluteSourcePath && customModule.relativePath) {
|
|
2359
|
+
// relativePath is relative to the project root (parent of bmad dir)
|
|
2360
|
+
absoluteSourcePath = path.resolve(projectRoot, customModule.relativePath);
|
|
2361
|
+
}
|
|
2362
|
+
// Ensure sourcePath is absolute for anything else
|
|
2363
|
+
else if (absoluteSourcePath && !path.isAbsolute(absoluteSourcePath)) {
|
|
2364
|
+
absoluteSourcePath = path.resolve(absoluteSourcePath);
|
|
2365
|
+
}
|
|
2366
|
+
|
|
2367
|
+
// Update the custom module object with the absolute path
|
|
2368
|
+
const updatedModule = {
|
|
2369
|
+
...customModule,
|
|
2370
|
+
sourcePath: absoluteSourcePath,
|
|
2371
|
+
};
|
|
2372
|
+
|
|
2373
|
+
customModuleSources.set(customModule.id, updatedModule);
|
|
2374
|
+
}
|
|
2375
|
+
}
|
|
2005
2376
|
|
|
2006
2377
|
// Load saved IDE configurations
|
|
2007
2378
|
const savedIdeConfigs = await this.ideConfigManager.loadAllIdeConfigs(bmadDir);
|
|
2008
2379
|
|
|
2009
2380
|
// Get available modules (what we have source for)
|
|
2010
|
-
const
|
|
2011
|
-
const
|
|
2381
|
+
const availableModulesData = await this.moduleManager.listAvailable();
|
|
2382
|
+
const availableModules = [...availableModulesData.modules, ...availableModulesData.customModules];
|
|
2383
|
+
|
|
2384
|
+
// Add custom modules from manifest if their sources exist
|
|
2385
|
+
for (const [moduleId, customModule] of customModuleSources) {
|
|
2386
|
+
// Use the absolute sourcePath
|
|
2387
|
+
const sourcePath = customModule.sourcePath;
|
|
2388
|
+
|
|
2389
|
+
// Check if source exists at the recorded path
|
|
2390
|
+
if (
|
|
2391
|
+
sourcePath &&
|
|
2392
|
+
(await fs.pathExists(sourcePath)) && // Add to available modules if not already there
|
|
2393
|
+
!availableModules.some((m) => m.id === moduleId)
|
|
2394
|
+
) {
|
|
2395
|
+
availableModules.push({
|
|
2396
|
+
id: moduleId,
|
|
2397
|
+
name: customModule.name || moduleId,
|
|
2398
|
+
path: sourcePath,
|
|
2399
|
+
isCustom: true,
|
|
2400
|
+
fromManifest: true,
|
|
2401
|
+
});
|
|
2402
|
+
}
|
|
2403
|
+
}
|
|
2404
|
+
|
|
2405
|
+
// Check for untracked custom modules (installed but not in manifest)
|
|
2406
|
+
const untrackedCustomModules = [];
|
|
2407
|
+
for (const installedModule of installedModules) {
|
|
2408
|
+
// Skip standard modules and core
|
|
2409
|
+
const standardModuleIds = ['bmb', 'bmgd', 'bmm', 'cis', 'core'];
|
|
2410
|
+
if (standardModuleIds.includes(installedModule)) {
|
|
2411
|
+
continue;
|
|
2412
|
+
}
|
|
2413
|
+
|
|
2414
|
+
// Check if this installed module is not tracked in customModules
|
|
2415
|
+
if (!customModuleSources.has(installedModule)) {
|
|
2416
|
+
const modulePath = path.join(bmadDir, installedModule);
|
|
2417
|
+
if (await fs.pathExists(modulePath)) {
|
|
2418
|
+
untrackedCustomModules.push({
|
|
2419
|
+
id: installedModule,
|
|
2420
|
+
name: installedModule, // We don't have the original name
|
|
2421
|
+
path: modulePath,
|
|
2422
|
+
untracked: true,
|
|
2423
|
+
});
|
|
2424
|
+
}
|
|
2425
|
+
}
|
|
2426
|
+
}
|
|
2427
|
+
|
|
2428
|
+
// If we found untracked custom modules, offer to track them
|
|
2429
|
+
if (untrackedCustomModules.length > 0) {
|
|
2430
|
+
spinner.stop();
|
|
2431
|
+
console.log(chalk.yellow(`\n⚠️ Found ${untrackedCustomModules.length} custom module(s) not tracked in manifest:`));
|
|
2432
|
+
|
|
2433
|
+
for (const untracked of untrackedCustomModules) {
|
|
2434
|
+
console.log(chalk.dim(` • ${untracked.id} (installed at ${path.relative(projectRoot, untracked.path)})`));
|
|
2435
|
+
}
|
|
2436
|
+
|
|
2437
|
+
const { trackModules } = await inquirer.prompt([
|
|
2438
|
+
{
|
|
2439
|
+
type: 'confirm',
|
|
2440
|
+
name: 'trackModules',
|
|
2441
|
+
message: chalk.cyan('Would you like to scan for their source locations?'),
|
|
2442
|
+
default: true,
|
|
2443
|
+
},
|
|
2444
|
+
]);
|
|
2445
|
+
|
|
2446
|
+
if (trackModules) {
|
|
2447
|
+
const { scanDirectory } = await inquirer.prompt([
|
|
2448
|
+
{
|
|
2449
|
+
type: 'input',
|
|
2450
|
+
name: 'scanDirectory',
|
|
2451
|
+
message: 'Enter directory to scan for custom module sources (or leave blank to skip):',
|
|
2452
|
+
default: projectRoot,
|
|
2453
|
+
validate: async (input) => {
|
|
2454
|
+
if (input && input.trim() !== '') {
|
|
2455
|
+
const expandedPath = path.resolve(input.trim());
|
|
2456
|
+
if (!(await fs.pathExists(expandedPath))) {
|
|
2457
|
+
return 'Directory does not exist';
|
|
2458
|
+
}
|
|
2459
|
+
const stats = await fs.stat(expandedPath);
|
|
2460
|
+
if (!stats.isDirectory()) {
|
|
2461
|
+
return 'Path must be a directory';
|
|
2462
|
+
}
|
|
2463
|
+
}
|
|
2464
|
+
return true;
|
|
2465
|
+
},
|
|
2466
|
+
},
|
|
2467
|
+
]);
|
|
2468
|
+
|
|
2469
|
+
if (scanDirectory && scanDirectory.trim() !== '') {
|
|
2470
|
+
console.log(chalk.dim('\nScanning for custom module sources...'));
|
|
2471
|
+
|
|
2472
|
+
// Scan for all module.yaml files
|
|
2473
|
+
const allModulePaths = await this.moduleManager.findModulesInProject(scanDirectory);
|
|
2474
|
+
const { ModuleManager } = require('../modules/manager');
|
|
2475
|
+
const mm = new ModuleManager({ scanProjectForModules: true });
|
|
2476
|
+
|
|
2477
|
+
for (const untracked of untrackedCustomModules) {
|
|
2478
|
+
let foundSource = null;
|
|
2479
|
+
|
|
2480
|
+
// Try to find by module ID
|
|
2481
|
+
for (const modulePath of allModulePaths) {
|
|
2482
|
+
try {
|
|
2483
|
+
const moduleInfo = await mm.getModuleInfo(modulePath);
|
|
2484
|
+
if (moduleInfo && moduleInfo.id === untracked.id) {
|
|
2485
|
+
foundSource = {
|
|
2486
|
+
path: modulePath,
|
|
2487
|
+
info: moduleInfo,
|
|
2488
|
+
};
|
|
2489
|
+
break;
|
|
2490
|
+
}
|
|
2491
|
+
} catch {
|
|
2492
|
+
// Continue searching
|
|
2493
|
+
}
|
|
2494
|
+
}
|
|
2495
|
+
|
|
2496
|
+
if (foundSource) {
|
|
2497
|
+
console.log(chalk.green(` ✓ Found source for ${untracked.id}: ${path.relative(projectRoot, foundSource.path)}`));
|
|
2498
|
+
|
|
2499
|
+
// Add to manifest
|
|
2500
|
+
await this.manifest.addCustomModule(bmadDir, {
|
|
2501
|
+
id: untracked.id,
|
|
2502
|
+
name: foundSource.info.name || untracked.name,
|
|
2503
|
+
sourcePath: path.resolve(foundSource.path),
|
|
2504
|
+
installDate: new Date().toISOString(),
|
|
2505
|
+
tracked: true,
|
|
2506
|
+
});
|
|
2507
|
+
|
|
2508
|
+
// Add to customModuleSources for processing
|
|
2509
|
+
customModuleSources.set(untracked.id, {
|
|
2510
|
+
id: untracked.id,
|
|
2511
|
+
name: foundSource.info.name || untracked.name,
|
|
2512
|
+
sourcePath: path.resolve(foundSource.path),
|
|
2513
|
+
});
|
|
2514
|
+
} else {
|
|
2515
|
+
console.log(chalk.yellow(` ⚠ Could not find source for ${untracked.id}`));
|
|
2516
|
+
}
|
|
2517
|
+
}
|
|
2518
|
+
}
|
|
2519
|
+
}
|
|
2520
|
+
|
|
2521
|
+
console.log(chalk.dim('\nUntracked custom modules will remain installed but cannot be updated without their source.'));
|
|
2522
|
+
spinner.start('Preparing update...');
|
|
2523
|
+
}
|
|
2524
|
+
|
|
2525
|
+
// Handle missing custom module sources using shared method
|
|
2526
|
+
const customModuleResult = await this.handleMissingCustomSources(
|
|
2527
|
+
customModuleSources,
|
|
2528
|
+
bmadDir,
|
|
2529
|
+
projectRoot,
|
|
2530
|
+
'update',
|
|
2531
|
+
installedModules,
|
|
2532
|
+
);
|
|
2533
|
+
|
|
2534
|
+
// Handle both old return format (array) and new format (object)
|
|
2535
|
+
let validCustomModules = [];
|
|
2536
|
+
let keptModulesWithoutSources = [];
|
|
2537
|
+
|
|
2538
|
+
if (Array.isArray(customModuleResult)) {
|
|
2539
|
+
// Old format - just an array
|
|
2540
|
+
validCustomModules = customModuleResult;
|
|
2541
|
+
} else if (customModuleResult && typeof customModuleResult === 'object') {
|
|
2542
|
+
// New format - object with two arrays
|
|
2543
|
+
validCustomModules = customModuleResult.validCustomModules || [];
|
|
2544
|
+
keptModulesWithoutSources = customModuleResult.keptModulesWithoutSources || [];
|
|
2545
|
+
}
|
|
2546
|
+
|
|
2547
|
+
const customModulesFromManifest = validCustomModules.map((m) => ({
|
|
2548
|
+
...m,
|
|
2549
|
+
isCustom: true,
|
|
2550
|
+
hasUpdate: true,
|
|
2551
|
+
}));
|
|
2552
|
+
|
|
2553
|
+
// Add untracked modules to the update list but mark them as untrackable
|
|
2554
|
+
for (const untracked of untrackedCustomModules) {
|
|
2555
|
+
if (!customModuleSources.has(untracked.id)) {
|
|
2556
|
+
customModulesFromManifest.push({
|
|
2557
|
+
...untracked,
|
|
2558
|
+
isCustom: true,
|
|
2559
|
+
hasUpdate: false, // Can't update without source
|
|
2560
|
+
untracked: true,
|
|
2561
|
+
});
|
|
2562
|
+
}
|
|
2563
|
+
}
|
|
2564
|
+
|
|
2565
|
+
const allAvailableModules = [...availableModules, ...customModulesFromManifest];
|
|
2566
|
+
const availableModuleIds = new Set(allAvailableModules.map((m) => m.id));
|
|
2567
|
+
|
|
2568
|
+
// Core module is special - never include it in update flow
|
|
2569
|
+
const nonCoreInstalledModules = installedModules.filter((id) => id !== 'core');
|
|
2012
2570
|
|
|
2013
2571
|
// Only update modules that are BOTH installed AND available (we have source for)
|
|
2014
|
-
const modulesToUpdate =
|
|
2015
|
-
const skippedModules =
|
|
2572
|
+
const modulesToUpdate = nonCoreInstalledModules.filter((id) => availableModuleIds.has(id));
|
|
2573
|
+
const skippedModules = nonCoreInstalledModules.filter((id) => !availableModuleIds.has(id));
|
|
2574
|
+
|
|
2575
|
+
// Add custom modules that were kept without sources to the skipped modules
|
|
2576
|
+
// This ensures their agents are preserved in the manifest
|
|
2577
|
+
for (const keptModule of keptModulesWithoutSources) {
|
|
2578
|
+
if (!skippedModules.includes(keptModule)) {
|
|
2579
|
+
skippedModules.push(keptModule);
|
|
2580
|
+
}
|
|
2581
|
+
}
|
|
2016
2582
|
|
|
2017
2583
|
spinner.succeed(`Found ${modulesToUpdate.length} module(s) to update and ${configuredIdes.length} configured tool(s)`);
|
|
2018
2584
|
|
|
@@ -2077,6 +2643,8 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
|
|
2077
2643
|
_quickUpdate: true, // Flag to skip certain prompts
|
|
2078
2644
|
_preserveModules: skippedModules, // Preserve these in manifest even though we didn't update them
|
|
2079
2645
|
_savedIdeConfigs: savedIdeConfigs, // Pass saved IDE configs to installer
|
|
2646
|
+
_customModuleSources: customModuleSources, // Pass custom module sources for updates
|
|
2647
|
+
_existingModules: installedModules, // Pass all installed modules for manifest generation
|
|
2080
2648
|
};
|
|
2081
2649
|
|
|
2082
2650
|
// Call the standard install method
|
|
@@ -2716,6 +3284,230 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
|
|
2716
3284
|
}
|
|
2717
3285
|
}
|
|
2718
3286
|
}
|
|
3287
|
+
|
|
3288
|
+
/**
|
|
3289
|
+
* Handle missing custom module sources interactively
|
|
3290
|
+
* @param {Map} customModuleSources - Map of custom module ID to info
|
|
3291
|
+
* @param {string} bmadDir - BMAD directory
|
|
3292
|
+
* @param {string} projectRoot - Project root directory
|
|
3293
|
+
* @param {string} operation - Current operation ('update', 'compile', etc.)
|
|
3294
|
+
* @param {Array} installedModules - Array of installed module IDs (will be modified)
|
|
3295
|
+
* @returns {Object} Object with validCustomModules array and keptModulesWithoutSources array
|
|
3296
|
+
*/
|
|
3297
|
+
async handleMissingCustomSources(customModuleSources, bmadDir, projectRoot, operation, installedModules) {
|
|
3298
|
+
const validCustomModules = [];
|
|
3299
|
+
const keptModulesWithoutSources = []; // Track modules kept without sources
|
|
3300
|
+
const customModulesWithMissingSources = [];
|
|
3301
|
+
|
|
3302
|
+
// Check which sources exist
|
|
3303
|
+
for (const [moduleId, customInfo] of customModuleSources) {
|
|
3304
|
+
if (await fs.pathExists(customInfo.sourcePath)) {
|
|
3305
|
+
validCustomModules.push({
|
|
3306
|
+
id: moduleId,
|
|
3307
|
+
name: customInfo.name,
|
|
3308
|
+
path: customInfo.sourcePath,
|
|
3309
|
+
info: customInfo,
|
|
3310
|
+
});
|
|
3311
|
+
} else {
|
|
3312
|
+
customModulesWithMissingSources.push({
|
|
3313
|
+
id: moduleId,
|
|
3314
|
+
name: customInfo.name,
|
|
3315
|
+
sourcePath: customInfo.sourcePath,
|
|
3316
|
+
relativePath: customInfo.relativePath,
|
|
3317
|
+
info: customInfo,
|
|
3318
|
+
});
|
|
3319
|
+
}
|
|
3320
|
+
}
|
|
3321
|
+
|
|
3322
|
+
// If no missing sources, return immediately
|
|
3323
|
+
if (customModulesWithMissingSources.length === 0) {
|
|
3324
|
+
return validCustomModules;
|
|
3325
|
+
}
|
|
3326
|
+
|
|
3327
|
+
// Stop any spinner for interactive prompts
|
|
3328
|
+
const currentSpinner = ora();
|
|
3329
|
+
if (currentSpinner.isSpinning) {
|
|
3330
|
+
currentSpinner.stop();
|
|
3331
|
+
}
|
|
3332
|
+
|
|
3333
|
+
console.log(chalk.yellow(`\n⚠️ Found ${customModulesWithMissingSources.length} custom module(s) with missing sources:`));
|
|
3334
|
+
|
|
3335
|
+
const inquirer = require('inquirer');
|
|
3336
|
+
let keptCount = 0;
|
|
3337
|
+
let updatedCount = 0;
|
|
3338
|
+
let removedCount = 0;
|
|
3339
|
+
|
|
3340
|
+
for (const missing of customModulesWithMissingSources) {
|
|
3341
|
+
console.log(chalk.dim(` • ${missing.name} (${missing.id})`));
|
|
3342
|
+
console.log(chalk.dim(` Original source: ${missing.relativePath}`));
|
|
3343
|
+
console.log(chalk.dim(` Full path: ${missing.sourcePath}`));
|
|
3344
|
+
|
|
3345
|
+
const choices = [
|
|
3346
|
+
{
|
|
3347
|
+
name: 'Keep installed (will not be processed)',
|
|
3348
|
+
value: 'keep',
|
|
3349
|
+
short: 'Keep',
|
|
3350
|
+
},
|
|
3351
|
+
{
|
|
3352
|
+
name: 'Specify new source location',
|
|
3353
|
+
value: 'update',
|
|
3354
|
+
short: 'Update',
|
|
3355
|
+
},
|
|
3356
|
+
];
|
|
3357
|
+
|
|
3358
|
+
// Only add remove option if not just compiling agents
|
|
3359
|
+
if (operation !== 'compile-agents') {
|
|
3360
|
+
choices.push({
|
|
3361
|
+
name: '⚠️ REMOVE module completely (destructive!)',
|
|
3362
|
+
value: 'remove',
|
|
3363
|
+
short: 'Remove',
|
|
3364
|
+
});
|
|
3365
|
+
}
|
|
3366
|
+
|
|
3367
|
+
const { action } = await inquirer.prompt([
|
|
3368
|
+
{
|
|
3369
|
+
type: 'list',
|
|
3370
|
+
name: 'action',
|
|
3371
|
+
message: `How would you like to handle "${missing.name}"?`,
|
|
3372
|
+
choices,
|
|
3373
|
+
},
|
|
3374
|
+
]);
|
|
3375
|
+
|
|
3376
|
+
switch (action) {
|
|
3377
|
+
case 'update': {
|
|
3378
|
+
const { newSourcePath } = await inquirer.prompt([
|
|
3379
|
+
{
|
|
3380
|
+
type: 'input',
|
|
3381
|
+
name: 'newSourcePath',
|
|
3382
|
+
message: 'Enter the new path to the custom module:',
|
|
3383
|
+
default: missing.sourcePath,
|
|
3384
|
+
validate: async (input) => {
|
|
3385
|
+
if (!input || input.trim() === '') {
|
|
3386
|
+
return 'Please enter a path';
|
|
3387
|
+
}
|
|
3388
|
+
const expandedPath = path.resolve(input.trim());
|
|
3389
|
+
if (!(await fs.pathExists(expandedPath))) {
|
|
3390
|
+
return 'Path does not exist';
|
|
3391
|
+
}
|
|
3392
|
+
// Check if it looks like a valid module
|
|
3393
|
+
const moduleYamlPath = path.join(expandedPath, 'module.yaml');
|
|
3394
|
+
const agentsPath = path.join(expandedPath, 'agents');
|
|
3395
|
+
const workflowsPath = path.join(expandedPath, 'workflows');
|
|
3396
|
+
|
|
3397
|
+
if (!(await fs.pathExists(moduleYamlPath)) && !(await fs.pathExists(agentsPath)) && !(await fs.pathExists(workflowsPath))) {
|
|
3398
|
+
return 'Path does not appear to contain a valid custom module';
|
|
3399
|
+
}
|
|
3400
|
+
return true;
|
|
3401
|
+
},
|
|
3402
|
+
},
|
|
3403
|
+
]);
|
|
3404
|
+
|
|
3405
|
+
// Update the source in manifest
|
|
3406
|
+
const resolvedPath = path.resolve(newSourcePath.trim());
|
|
3407
|
+
missing.info.sourcePath = resolvedPath;
|
|
3408
|
+
// Remove relativePath - we only store absolute sourcePath now
|
|
3409
|
+
delete missing.info.relativePath;
|
|
3410
|
+
await this.manifest.addCustomModule(bmadDir, missing.info);
|
|
3411
|
+
|
|
3412
|
+
validCustomModules.push({
|
|
3413
|
+
id: moduleId,
|
|
3414
|
+
name: missing.name,
|
|
3415
|
+
path: resolvedPath,
|
|
3416
|
+
info: missing.info,
|
|
3417
|
+
});
|
|
3418
|
+
|
|
3419
|
+
updatedCount++;
|
|
3420
|
+
console.log(chalk.green(`✓ Updated source location`));
|
|
3421
|
+
|
|
3422
|
+
break;
|
|
3423
|
+
}
|
|
3424
|
+
case 'remove': {
|
|
3425
|
+
// Extra confirmation for destructive remove
|
|
3426
|
+
console.log(chalk.red.bold(`\n⚠️ WARNING: This will PERMANENTLY DELETE "${missing.name}" and all its files!`));
|
|
3427
|
+
console.log(chalk.red(` Module location: ${path.join(bmadDir, moduleId)}`));
|
|
3428
|
+
|
|
3429
|
+
const { confirm } = await inquirer.prompt([
|
|
3430
|
+
{
|
|
3431
|
+
type: 'confirm',
|
|
3432
|
+
name: 'confirm',
|
|
3433
|
+
message: chalk.red.bold('Are you absolutely sure you want to delete this module?'),
|
|
3434
|
+
default: false,
|
|
3435
|
+
},
|
|
3436
|
+
]);
|
|
3437
|
+
|
|
3438
|
+
if (confirm) {
|
|
3439
|
+
const { typedConfirm } = await inquirer.prompt([
|
|
3440
|
+
{
|
|
3441
|
+
type: 'input',
|
|
3442
|
+
name: 'typedConfirm',
|
|
3443
|
+
message: chalk.red.bold('Type "DELETE" to confirm permanent deletion:'),
|
|
3444
|
+
validate: (input) => {
|
|
3445
|
+
if (input !== 'DELETE') {
|
|
3446
|
+
return chalk.red('You must type "DELETE" exactly to proceed');
|
|
3447
|
+
}
|
|
3448
|
+
return true;
|
|
3449
|
+
},
|
|
3450
|
+
},
|
|
3451
|
+
]);
|
|
3452
|
+
|
|
3453
|
+
if (typedConfirm === 'DELETE') {
|
|
3454
|
+
// Remove the module from filesystem and manifest
|
|
3455
|
+
const modulePath = path.join(bmadDir, moduleId);
|
|
3456
|
+
if (await fs.pathExists(modulePath)) {
|
|
3457
|
+
const fsExtra = require('fs-extra');
|
|
3458
|
+
await fsExtra.remove(modulePath);
|
|
3459
|
+
console.log(chalk.yellow(` ✓ Deleted module directory: ${path.relative(projectRoot, modulePath)}`));
|
|
3460
|
+
}
|
|
3461
|
+
|
|
3462
|
+
await this.manifest.removeModule(bmadDir, moduleId);
|
|
3463
|
+
await this.manifest.removeCustomModule(bmadDir, moduleId);
|
|
3464
|
+
console.log(chalk.yellow(` ✓ Removed from manifest`));
|
|
3465
|
+
|
|
3466
|
+
// Also remove from installedModules list
|
|
3467
|
+
if (installedModules && installedModules.includes(moduleId)) {
|
|
3468
|
+
const index = installedModules.indexOf(moduleId);
|
|
3469
|
+
if (index !== -1) {
|
|
3470
|
+
installedModules.splice(index, 1);
|
|
3471
|
+
}
|
|
3472
|
+
}
|
|
3473
|
+
|
|
3474
|
+
removedCount++;
|
|
3475
|
+
console.log(chalk.red.bold(`✓ "${missing.name}" has been permanently removed`));
|
|
3476
|
+
} else {
|
|
3477
|
+
console.log(chalk.dim(' Removal cancelled - module will be kept'));
|
|
3478
|
+
keptCount++;
|
|
3479
|
+
}
|
|
3480
|
+
} else {
|
|
3481
|
+
console.log(chalk.dim(' Removal cancelled - module will be kept'));
|
|
3482
|
+
keptCount++;
|
|
3483
|
+
}
|
|
3484
|
+
|
|
3485
|
+
break;
|
|
3486
|
+
}
|
|
3487
|
+
case 'keep': {
|
|
3488
|
+
keptCount++;
|
|
3489
|
+
keptModulesWithoutSources.push(moduleId);
|
|
3490
|
+
console.log(chalk.dim(` Module will be kept as-is`));
|
|
3491
|
+
|
|
3492
|
+
break;
|
|
3493
|
+
}
|
|
3494
|
+
// No default
|
|
3495
|
+
}
|
|
3496
|
+
}
|
|
3497
|
+
|
|
3498
|
+
// Show summary
|
|
3499
|
+
if (keptCount > 0 || updatedCount > 0 || removedCount > 0) {
|
|
3500
|
+
console.log(chalk.dim(`\nSummary for custom modules with missing sources:`));
|
|
3501
|
+
if (keptCount > 0) console.log(chalk.dim(` • ${keptCount} module(s) kept as-is`));
|
|
3502
|
+
if (updatedCount > 0) console.log(chalk.dim(` • ${updatedCount} module(s) updated with new sources`));
|
|
3503
|
+
if (removedCount > 0) console.log(chalk.red(` • ${removedCount} module(s) permanently deleted`));
|
|
3504
|
+
}
|
|
3505
|
+
|
|
3506
|
+
return {
|
|
3507
|
+
validCustomModules,
|
|
3508
|
+
keptModulesWithoutSources,
|
|
3509
|
+
};
|
|
3510
|
+
}
|
|
2719
3511
|
}
|
|
2720
3512
|
|
|
2721
3513
|
module.exports = { Installer };
|