Compare commits
1 Commits
1f2788bd5b
...
f82cdef6cf
| Author | SHA1 | Date |
|---|---|---|
|
|
f82cdef6cf |
Binary file not shown.
|
|
@ -28,7 +28,7 @@ This uses **micro-file architecture** for disciplined execution:
|
||||||
|
|
||||||
### Configuration Loading
|
### Configuration Loading
|
||||||
|
|
||||||
Load config from `{project-root}/{bmad_folder}/core/config.yaml` and resolve:
|
Load config from `{project-root}/{bmad_folder}/bmm/config.yaml` and resolve:
|
||||||
|
|
||||||
- `project_name`, `output_folder`, `user_name`
|
- `project_name`, `output_folder`, `user_name`
|
||||||
- `communication_language`, `document_output_language`, `user_skill_level`
|
- `communication_language`, `document_output_language`, `user_skill_level`
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ This uses **micro-file architecture** with **sequential conversation orchestrati
|
||||||
|
|
||||||
### Configuration Loading
|
### Configuration Loading
|
||||||
|
|
||||||
Load config from `{project-root}/{bmad_folder}/core/config.yaml` and resolve:
|
Load config from `{project-root}/{bmad_folder}/bmm/config.yaml` and resolve:
|
||||||
|
|
||||||
- `project_name`, `output_folder`, `user_name`
|
- `project_name`, `output_folder`, `user_name`
|
||||||
- `communication_language`, `document_output_language`, `user_skill_level`
|
- `communication_language`, `document_output_language`, `user_skill_level`
|
||||||
|
|
|
||||||
|
|
@ -139,6 +139,9 @@ Comprehensive documentation for all BMM workflows organized by phase:
|
||||||
- Complete story lifecycle
|
- Complete story lifecycle
|
||||||
- One-story-at-a-time discipline
|
- One-story-at-a-time discipline
|
||||||
|
|
||||||
|
<<<<<<< Updated upstream
|
||||||
|
<<<<<<< Updated upstream
|
||||||
|
|
||||||
- **[Testing & QA Workflows](./test-architecture.md)** - Comprehensive quality assurance (1,420 lines)
|
- **[Testing & QA Workflows](./test-architecture.md)** - Comprehensive quality assurance (1,420 lines)
|
||||||
- Test strategy, automation, quality gates
|
- Test strategy, automation, quality gates
|
||||||
- TEA agent and test healing
|
- TEA agent and test healing
|
||||||
|
|
@ -146,6 +149,14 @@ Comprehensive documentation for all BMM workflows organized by phase:
|
||||||
|
|
||||||
**Total: 34 workflows documented across all phases**
|
**Total: 34 workflows documented across all phases**
|
||||||
|
|
||||||
|
=======
|
||||||
|
|
||||||
|
> > > > > > > Stashed changes
|
||||||
|
|
||||||
|
=======
|
||||||
|
|
||||||
|
> > > > > > > Stashed changes
|
||||||
|
|
||||||
### Advanced Workflow References
|
### Advanced Workflow References
|
||||||
|
|
||||||
For detailed technical documentation on specific complex workflows:
|
For detailed technical documentation on specific complex workflows:
|
||||||
|
|
@ -170,9 +181,23 @@ Quality assurance guidance:
|
||||||
|
|
||||||
<!-- Test Architect documentation to be added -->
|
<!-- Test Architect documentation to be added -->
|
||||||
|
|
||||||
|
<<<<<<< Updated upstream
|
||||||
|
|
||||||
|
<<<<<<< Updated upstream
|
||||||
|
|
||||||
- Test design workflows
|
- Test design workflows
|
||||||
- Quality gates
|
- Quality gates
|
||||||
- Risk assessment
|
- Risk assessment
|
||||||
|
- # NFR validation
|
||||||
|
=======
|
||||||
|
> > > > > > > Stashed changes
|
||||||
|
- Test design workflows
|
||||||
|
- Quality gates
|
||||||
|
- Risk assessment
|
||||||
|
- NFR validation
|
||||||
|
> > > > > > > Stashed changes
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 🏗️ Module Structure
|
## 🏗️ Module Structure
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -133,6 +133,7 @@ The `sprint-status.yaml` file is the single source of truth for all implementati
|
||||||
### (BMad Method / Enterprise)
|
### (BMad Method / Enterprise)
|
||||||
|
|
||||||
```
|
```
|
||||||
|
<<<<<<< Updated upstream
|
||||||
PRD (PM) → Architecture (Architect)
|
PRD (PM) → Architecture (Architect)
|
||||||
→ create-epics-and-stories (PM) ← V6: After architecture!
|
→ create-epics-and-stories (PM) ← V6: After architecture!
|
||||||
→ implementation-readiness (Architect)
|
→ implementation-readiness (Architect)
|
||||||
|
|
@ -141,6 +142,7 @@ PRD (PM) → Architecture (Architect)
|
||||||
→ story loop (SM/DEV)
|
→ story loop (SM/DEV)
|
||||||
→ retrospective (SM)
|
→ retrospective (SM)
|
||||||
→ [Next Epic]
|
→ [Next Epic]
|
||||||
|
=======
|
||||||
Current Phase: 4 (Implementation)
|
Current Phase: 4 (Implementation)
|
||||||
Current Epic: Epic 1 (Authentication)
|
Current Epic: Epic 1 (Authentication)
|
||||||
Current Sprint: Sprint 1
|
Current Sprint: Sprint 1
|
||||||
|
|
@ -188,12 +190,108 @@ See: [workflow-status instructions](../workflows/workflow-status/instructions.md
|
||||||
|
|
||||||
See: [document-project reference](./workflow-document-project-reference.md)
|
See: [document-project reference](./workflow-document-project-reference.md)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Story Lifecycle Visualization
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────────┐
|
||||||
|
│ PHASE 4: IMPLEMENTATION (Iterative Story Lifecycle) │
|
||||||
|
└─────────────────────────────────────────────────────────────┘
|
||||||
|
|
||||||
|
┌─────────────────┐
|
||||||
|
│ Sprint Planning │ → Creates sprint-status.yaml
|
||||||
|
└────────┬────────┘ Defines story queue
|
||||||
|
│
|
||||||
|
├──────────────────────────────────────────┐
|
||||||
|
│ │
|
||||||
|
▼ │
|
||||||
|
┌─────────────────────┐ │
|
||||||
|
│ Epic Tech Context │ → Optional per epic │
|
||||||
|
│ (Once per epic) │ Provides technical │
|
||||||
|
└─────────────────────┘ guidance │
|
||||||
|
│ │
|
||||||
|
▼ │
|
||||||
|
┌─────────────────────────────────────────────────┤
|
||||||
|
│ FOR EACH STORY IN QUEUE: │
|
||||||
|
├─────────────────────────────────────────────────┤
|
||||||
|
│ │
|
||||||
|
▼ │
|
||||||
|
┌─────────────────┐ │
|
||||||
|
│ Create Story │ → Generates story file │
|
||||||
|
│ (TODO → IN PROGRESS) │
|
||||||
|
└────────┬────────┘ │
|
||||||
|
│ │
|
||||||
|
▼ │
|
||||||
|
┌─────────────────┐ │
|
||||||
|
│ Story Context │ → Assembles focused context │
|
||||||
|
└────────┬────────┘ │
|
||||||
|
│ │
|
||||||
|
▼ │
|
||||||
|
┌─────────────────┐ │
|
||||||
|
│ Dev Story │ → Implements + tests │
|
||||||
|
│ (IN PROGRESS) │ │
|
||||||
|
└────────┬────────┘ │
|
||||||
|
│ │
|
||||||
|
▼ │
|
||||||
|
┌─────────────────┐ │
|
||||||
|
│ Code Review │ → Senior dev review │
|
||||||
|
│ (IN PROGRESS → │ │
|
||||||
|
│ READY FOR REVIEW) │
|
||||||
|
└────────┬────────┘ │
|
||||||
|
│ │
|
||||||
|
┌────┴────┐ │
|
||||||
|
│ Result? │ │
|
||||||
|
└────┬────┘ │
|
||||||
|
│ │
|
||||||
|
┌────┼────────────────────┐ │
|
||||||
|
│ │ │ │
|
||||||
|
▼ ▼ ▼ │
|
||||||
|
APPROVED APPROVED REQUEST │
|
||||||
|
WITH COMMENTS CHANGES │
|
||||||
|
│ │ │ │
|
||||||
|
└─────────┴───────────────────┘ │
|
||||||
|
│ │
|
||||||
|
▼ │
|
||||||
|
┌─────────────────┐ │
|
||||||
|
│ Story Done │ → READY FOR REVIEW → DONE│
|
||||||
|
└────────┬────────┘ │
|
||||||
|
│ │
|
||||||
|
├─────────────────────────────────────┘
|
||||||
|
│ More stories?
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌────────────────┐
|
||||||
|
│ Epic Complete? │
|
||||||
|
└────────┬───────┘
|
||||||
|
│
|
||||||
|
┌────┼────┐
|
||||||
|
│ │
|
||||||
|
Yes No
|
||||||
|
│ └──> Continue to next story
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌─────────────────┐
|
||||||
|
│ Retrospective │ → Review epic, lessons learned
|
||||||
|
└─────────────────┘
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
All epics done?
|
||||||
|
│
|
||||||
|
Yes → PROJECT COMPLETE
|
||||||
|
>>>>>>> Stashed changes
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Related Documentation
|
## Related Documentation
|
||||||
|
|
||||||
- [Phase 1: Analysis Workflows](./workflows-analysis.md)
|
- [Phase 1: Analysis Workflows](./workflows-analysis.md)
|
||||||
- [Phase 2: Planning Workflows](./workflows-planning.md)
|
- [Phase 2: Planning Workflows](./workflows-planning.md)
|
||||||
- [Phase 3: Solutioning Workflows](./workflows-solutioning.md)
|
- [Phase 3: Solutioning Workflows](./workflows-solutioning.md)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
|
|
||||||
**Q: Which workflow should I run next?**
|
**Q: Which workflow should I run next?**
|
||||||
|
|
@ -208,4 +306,6 @@ A: Not recommended. Complete one story's full lifecycle before starting the next
|
||||||
**Q: What if code review finds issues?**
|
**Q: What if code review finds issues?**
|
||||||
A: DEV runs `dev-story` to make fixes, re-runs tests, then runs `code-review` again until it passes.
|
A: DEV runs `dev-story` to make fixes, re-runs tests, then runs `code-review` again until it passes.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
_Phase 4 Implementation - One story at a time, done right._
|
_Phase 4 Implementation - One story at a time, done right._
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
code: bmm
|
code: bmm
|
||||||
name: "BMM: BMad Method Agile-AI Driven-Development"
|
name: "BMM: BMad Method Agile-AI Driven-Development"
|
||||||
default_selected: true # This module will be selected by default for new installations
|
default_selected: false # This module will be selected by default for new installations
|
||||||
|
|
||||||
header: "BMad Method™: Breakthrough Method of Agile-Ai Driven-Dev"
|
header: "BMad Method™: Breakthrough Method of Agile-Ai Driven-Dev"
|
||||||
subheader: "Agent and Workflow Configuration for this module"
|
subheader: "Agent and Workflow Configuration for this module"
|
||||||
|
|
|
||||||
|
|
@ -132,12 +132,8 @@ class ConfigCollector {
|
||||||
* Collect configuration for all modules
|
* Collect configuration for all modules
|
||||||
* @param {Array} modules - List of modules to configure (including 'core')
|
* @param {Array} modules - List of modules to configure (including 'core')
|
||||||
* @param {string} projectDir - Target project directory
|
* @param {string} projectDir - Target project directory
|
||||||
* @param {Object} options - Additional options
|
|
||||||
* @param {Map} options.customModulePaths - Map of module ID to source path for custom modules
|
|
||||||
*/
|
*/
|
||||||
async collectAllConfigurations(modules, projectDir, options = {}) {
|
async collectAllConfigurations(modules, projectDir) {
|
||||||
// Store custom module paths for use in collectModuleConfig
|
|
||||||
this.customModulePaths = options.customModulePaths || new Map();
|
|
||||||
await this.loadExistingConfig(projectDir);
|
await this.loadExistingConfig(projectDir);
|
||||||
|
|
||||||
// Check if core was already collected (e.g., in early collection phase)
|
// Check if core was already collected (e.g., in early collection phase)
|
||||||
|
|
@ -455,21 +451,11 @@ class ConfigCollector {
|
||||||
this.allAnswers = {};
|
this.allAnswers = {};
|
||||||
}
|
}
|
||||||
// Load module's config
|
// Load module's config
|
||||||
// First, check if we have a custom module path for this module
|
// First, try the standard src/modules location
|
||||||
let installerConfigPath = null;
|
let installerConfigPath = path.join(getModulePath(moduleName), '_module-installer', 'module.yaml');
|
||||||
let moduleConfigPath = null;
|
let moduleConfigPath = path.join(getModulePath(moduleName), 'module.yaml');
|
||||||
|
|
||||||
if (this.customModulePaths && this.customModulePaths.has(moduleName)) {
|
// If not found in src/modules, we need to find it by searching the project
|
||||||
const customPath = this.customModulePaths.get(moduleName);
|
|
||||||
installerConfigPath = path.join(customPath, '_module-installer', 'module.yaml');
|
|
||||||
moduleConfigPath = path.join(customPath, 'module.yaml');
|
|
||||||
} else {
|
|
||||||
// Try the standard src/modules location
|
|
||||||
installerConfigPath = path.join(getModulePath(moduleName), '_module-installer', 'module.yaml');
|
|
||||||
moduleConfigPath = path.join(getModulePath(moduleName), 'module.yaml');
|
|
||||||
}
|
|
||||||
|
|
||||||
// If not found in src/modules or custom paths, search the project
|
|
||||||
if (!(await fs.pathExists(installerConfigPath)) && !(await fs.pathExists(moduleConfigPath))) {
|
if (!(await fs.pathExists(installerConfigPath)) && !(await fs.pathExists(moduleConfigPath))) {
|
||||||
// Use the module manager to find the module source
|
// Use the module manager to find the module source
|
||||||
const { ModuleManager } = require('../modules/manager');
|
const { ModuleManager } = require('../modules/manager');
|
||||||
|
|
|
||||||
|
|
@ -51,19 +51,7 @@ class CustomModuleCache {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stream a file into the hash to avoid loading entire file into memory
|
* Calculate hash of a file or directory
|
||||||
*/
|
|
||||||
async hashFileStream(filePath, hash) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const stream = require('node:fs').createReadStream(filePath);
|
|
||||||
stream.on('data', (chunk) => hash.update(chunk));
|
|
||||||
stream.on('end', resolve);
|
|
||||||
stream.on('error', reject);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculate hash of a file or directory using streaming to minimize memory usage
|
|
||||||
*/
|
*/
|
||||||
async calculateHash(sourcePath) {
|
async calculateHash(sourcePath) {
|
||||||
const hash = crypto.createHash('sha256');
|
const hash = crypto.createHash('sha256');
|
||||||
|
|
@ -88,14 +76,14 @@ class CustomModuleCache {
|
||||||
files.sort(); // Ensure consistent order
|
files.sort(); // Ensure consistent order
|
||||||
|
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
|
const content = await fs.readFile(file);
|
||||||
const relativePath = path.relative(sourcePath, file);
|
const relativePath = path.relative(sourcePath, file);
|
||||||
// Hash the path first, then stream file contents
|
hash.update(relativePath + '|' + content.toString('base64'));
|
||||||
hash.update(relativePath + '|');
|
|
||||||
await this.hashFileStream(file, hash);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// For single files, stream directly into hash
|
// For single files
|
||||||
await this.hashFileStream(sourcePath, hash);
|
const content = await fs.readFile(sourcePath);
|
||||||
|
hash.update(content);
|
||||||
}
|
}
|
||||||
|
|
||||||
return hash.digest('hex');
|
return hash.digest('hex');
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,6 @@ const { CLIUtils } = require('../../../lib/cli-utils');
|
||||||
const { ManifestGenerator } = require('./manifest-generator');
|
const { ManifestGenerator } = require('./manifest-generator');
|
||||||
const { IdeConfigManager } = require('./ide-config-manager');
|
const { IdeConfigManager } = require('./ide-config-manager');
|
||||||
const { replaceAgentSidecarFolders } = require('./post-install-sidecar-replacement');
|
const { replaceAgentSidecarFolders } = require('./post-install-sidecar-replacement');
|
||||||
const { CustomHandler } = require('../custom/handler');
|
|
||||||
|
|
||||||
class Installer {
|
class Installer {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
|
@ -408,10 +407,7 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
||||||
* @param {string[]} config.ides - IDEs to configure
|
* @param {string[]} config.ides - IDEs to configure
|
||||||
* @param {boolean} config.skipIde - Skip IDE configuration
|
* @param {boolean} config.skipIde - Skip IDE configuration
|
||||||
*/
|
*/
|
||||||
async install(originalConfig) {
|
async install(config) {
|
||||||
// Clone config to avoid mutating the caller's object
|
|
||||||
const config = { ...originalConfig };
|
|
||||||
|
|
||||||
// Display BMAD logo
|
// Display BMAD logo
|
||||||
CLIUtils.displayLogo();
|
CLIUtils.displayLogo();
|
||||||
|
|
||||||
|
|
@ -439,52 +435,8 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
||||||
// Quick update already collected all configs, use them directly
|
// Quick update already collected all configs, use them directly
|
||||||
moduleConfigs = this.configCollector.collectedConfig;
|
moduleConfigs = this.configCollector.collectedConfig;
|
||||||
} else {
|
} else {
|
||||||
// Build custom module paths map from customContent
|
|
||||||
const customModulePaths = new Map();
|
|
||||||
|
|
||||||
// Handle selectedFiles (from existing install path or manual directory input)
|
|
||||||
if (config.customContent && config.customContent.selected && config.customContent.selectedFiles) {
|
|
||||||
const customHandler = new CustomHandler();
|
|
||||||
for (const customFile of config.customContent.selectedFiles) {
|
|
||||||
const customInfo = await customHandler.getCustomInfo(customFile, path.resolve(config.directory));
|
|
||||||
if (customInfo && customInfo.id) {
|
|
||||||
customModulePaths.set(customInfo.id, customInfo.path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle cachedModules (from new install path where modules are cached)
|
|
||||||
// Only include modules that were actually selected for installation
|
|
||||||
if (config.customContent && config.customContent.cachedModules) {
|
|
||||||
// Get selected cached module IDs (if available)
|
|
||||||
const selectedCachedIds = config.customContent.selectedCachedModules || [];
|
|
||||||
// If no selection info, include all cached modules (for backward compatibility)
|
|
||||||
const shouldIncludeAll = selectedCachedIds.length === 0 && config.customContent.selected;
|
|
||||||
|
|
||||||
for (const cachedModule of config.customContent.cachedModules) {
|
|
||||||
// For cached modules, the path is the cachePath which contains the module.yaml
|
|
||||||
if (
|
|
||||||
cachedModule.id &&
|
|
||||||
cachedModule.cachePath && // Include if selected or if we should include all
|
|
||||||
(shouldIncludeAll || selectedCachedIds.includes(cachedModule.id))
|
|
||||||
) {
|
|
||||||
customModulePaths.set(cachedModule.id, cachedModule.cachePath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get list of all modules including custom modules
|
|
||||||
const allModulesForConfig = [...(config.modules || [])];
|
|
||||||
for (const [moduleId] of customModulePaths) {
|
|
||||||
if (!allModulesForConfig.includes(moduleId)) {
|
|
||||||
allModulesForConfig.push(moduleId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Regular install - collect configurations (core was already collected in UI.promptInstall if interactive)
|
// Regular install - collect configurations (core was already collected in UI.promptInstall if interactive)
|
||||||
moduleConfigs = await this.configCollector.collectAllConfigurations(allModulesForConfig, path.resolve(config.directory), {
|
moduleConfigs = await this.configCollector.collectAllConfigurations(config.modules || [], path.resolve(config.directory));
|
||||||
customModulePaths,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get bmad_folder from config (default to 'bmad' for backwards compatibility)
|
// Get bmad_folder from config (default to 'bmad' for backwards compatibility)
|
||||||
|
|
@ -840,8 +792,9 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
||||||
// Regular custom content from user input (non-cached)
|
// Regular custom content from user input (non-cached)
|
||||||
if (finalCustomContent && finalCustomContent.selected && finalCustomContent.selectedFiles) {
|
if (finalCustomContent && finalCustomContent.selected && finalCustomContent.selectedFiles) {
|
||||||
// Add custom modules to the installation list
|
// Add custom modules to the installation list
|
||||||
const customHandler = new CustomHandler();
|
|
||||||
for (const customFile of finalCustomContent.selectedFiles) {
|
for (const customFile of finalCustomContent.selectedFiles) {
|
||||||
|
const { CustomHandler } = require('../custom/handler');
|
||||||
|
const customHandler = new CustomHandler();
|
||||||
const customInfo = await customHandler.getCustomInfo(customFile, projectDir);
|
const customInfo = await customHandler.getCustomInfo(customFile, projectDir);
|
||||||
if (customInfo && customInfo.id) {
|
if (customInfo && customInfo.id) {
|
||||||
allModules.push(customInfo.id);
|
allModules.push(customInfo.id);
|
||||||
|
|
@ -931,6 +884,7 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
||||||
|
|
||||||
// Finally check regular custom content
|
// Finally check regular custom content
|
||||||
if (!isCustomModule && finalCustomContent && finalCustomContent.selected && finalCustomContent.selectedFiles) {
|
if (!isCustomModule && finalCustomContent && finalCustomContent.selected && finalCustomContent.selectedFiles) {
|
||||||
|
const { CustomHandler } = require('../custom/handler');
|
||||||
const customHandler = new CustomHandler();
|
const customHandler = new CustomHandler();
|
||||||
for (const customFile of finalCustomContent.selectedFiles) {
|
for (const customFile of finalCustomContent.selectedFiles) {
|
||||||
const info = await customHandler.getCustomInfo(customFile, projectDir);
|
const info = await customHandler.getCustomInfo(customFile, projectDir);
|
||||||
|
|
@ -944,19 +898,17 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
||||||
|
|
||||||
if (isCustomModule && customInfo) {
|
if (isCustomModule && customInfo) {
|
||||||
// Install custom module using CustomHandler but as a proper module
|
// Install custom module using CustomHandler but as a proper module
|
||||||
|
const { CustomHandler } = require('../custom/handler');
|
||||||
const customHandler = new CustomHandler();
|
const customHandler = new CustomHandler();
|
||||||
|
|
||||||
// Install to module directory instead of custom directory
|
// Install to module directory instead of custom directory
|
||||||
const moduleTargetPath = path.join(bmadDir, moduleName);
|
const moduleTargetPath = path.join(bmadDir, moduleName);
|
||||||
await fs.ensureDir(moduleTargetPath);
|
await fs.ensureDir(moduleTargetPath);
|
||||||
|
|
||||||
// Get collected config for this custom module (from module.yaml prompts)
|
|
||||||
const collectedModuleConfig = moduleConfigs[moduleName] || {};
|
|
||||||
|
|
||||||
const result = await customHandler.install(
|
const result = await customHandler.install(
|
||||||
customInfo.path,
|
customInfo.path,
|
||||||
path.join(bmadDir, 'temp-custom'),
|
path.join(bmadDir, 'temp-custom'),
|
||||||
{ ...config.coreConfig, ...customInfo.config, ...collectedModuleConfig, _bmadDir: bmadDir },
|
{ ...config.coreConfig, ...customInfo.config, _bmadDir: bmadDir },
|
||||||
(filePath) => {
|
(filePath) => {
|
||||||
// Track installed files with correct path
|
// Track installed files with correct path
|
||||||
const relativePath = path.relative(path.join(bmadDir, 'temp-custom'), filePath);
|
const relativePath = path.relative(path.join(bmadDir, 'temp-custom'), filePath);
|
||||||
|
|
@ -972,8 +924,6 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
||||||
if (await fs.pathExists(customDir)) {
|
if (await fs.pathExists(customDir)) {
|
||||||
// Move contents to module directory
|
// Move contents to module directory
|
||||||
const items = await fs.readdir(customDir);
|
const items = await fs.readdir(customDir);
|
||||||
const movedItems = [];
|
|
||||||
try {
|
|
||||||
for (const item of items) {
|
for (const item of items) {
|
||||||
const srcPath = path.join(customDir, item);
|
const srcPath = path.join(customDir, item);
|
||||||
const destPath = path.join(moduleTargetPath, item);
|
const destPath = path.join(moduleTargetPath, item);
|
||||||
|
|
@ -984,33 +934,13 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
||||||
}
|
}
|
||||||
|
|
||||||
await fs.move(srcPath, destPath);
|
await fs.move(srcPath, destPath);
|
||||||
movedItems.push({ src: srcPath, dest: destPath });
|
|
||||||
}
|
|
||||||
} catch (moveError) {
|
|
||||||
// Rollback: restore any successfully moved items
|
|
||||||
for (const moved of movedItems) {
|
|
||||||
try {
|
|
||||||
await fs.move(moved.dest, moved.src);
|
|
||||||
} catch {
|
|
||||||
// Best-effort rollback - log if it fails
|
|
||||||
console.error(`Failed to rollback ${moved.dest} during cleanup`);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw new Error(`Failed to move custom module files: ${moveError.message}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
await fs.remove(tempCustomPath);
|
await fs.remove(tempCustomPath);
|
||||||
} catch (cleanupError) {
|
|
||||||
// Non-fatal: temp directory cleanup failed but files were moved successfully
|
|
||||||
console.warn(`Warning: Could not clean up temp directory: ${cleanupError.message}`);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create module config (include collected config from module.yaml prompts)
|
// Create module config
|
||||||
await this.generateModuleConfigs(bmadDir, {
|
await this.generateModuleConfigs(bmadDir, { [moduleName]: { ...config.coreConfig, ...customInfo.config } });
|
||||||
[moduleName]: { ...config.coreConfig, ...customInfo.config, ...collectedModuleConfig },
|
|
||||||
});
|
|
||||||
|
|
||||||
// Store custom module info for later manifest update
|
// Store custom module info for later manifest update
|
||||||
if (!config._customModulesToTrack) {
|
if (!config._customModulesToTrack) {
|
||||||
|
|
@ -1086,8 +1016,9 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
||||||
config.customContent.selectedFiles
|
config.customContent.selectedFiles
|
||||||
) {
|
) {
|
||||||
// Filter out custom modules that were already installed
|
// Filter out custom modules that were already installed
|
||||||
const customHandler = new CustomHandler();
|
|
||||||
for (const customFile of config.customContent.selectedFiles) {
|
for (const customFile of config.customContent.selectedFiles) {
|
||||||
|
const { CustomHandler } = require('../custom/handler');
|
||||||
|
const customHandler = new CustomHandler();
|
||||||
const customInfo = await customHandler.getCustomInfo(customFile, projectDir);
|
const customInfo = await customHandler.getCustomInfo(customFile, projectDir);
|
||||||
|
|
||||||
// Skip if this was installed as a module
|
// Skip if this was installed as a module
|
||||||
|
|
@ -1099,6 +1030,7 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
||||||
|
|
||||||
if (remainingCustomContent.length > 0) {
|
if (remainingCustomContent.length > 0) {
|
||||||
spinner.start('Installing remaining custom content...');
|
spinner.start('Installing remaining custom content...');
|
||||||
|
const { CustomHandler } = require('../custom/handler');
|
||||||
const customHandler = new CustomHandler();
|
const customHandler = new CustomHandler();
|
||||||
|
|
||||||
// Use the remaining files
|
// Use the remaining files
|
||||||
|
|
@ -2599,7 +2531,18 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
||||||
installedModules,
|
installedModules,
|
||||||
);
|
);
|
||||||
|
|
||||||
const { validCustomModules, keptModulesWithoutSources } = customModuleResult;
|
// Handle both old return format (array) and new format (object)
|
||||||
|
let validCustomModules = [];
|
||||||
|
let keptModulesWithoutSources = [];
|
||||||
|
|
||||||
|
if (Array.isArray(customModuleResult)) {
|
||||||
|
// Old format - just an array
|
||||||
|
validCustomModules = customModuleResult;
|
||||||
|
} else if (customModuleResult && typeof customModuleResult === 'object') {
|
||||||
|
// New format - object with two arrays
|
||||||
|
validCustomModules = customModuleResult.validCustomModules || [];
|
||||||
|
keptModulesWithoutSources = customModuleResult.keptModulesWithoutSources || [];
|
||||||
|
}
|
||||||
|
|
||||||
const customModulesFromManifest = validCustomModules.map((m) => ({
|
const customModulesFromManifest = validCustomModules.map((m) => ({
|
||||||
...m,
|
...m,
|
||||||
|
|
@ -3378,10 +3321,7 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
||||||
|
|
||||||
// If no missing sources, return immediately
|
// If no missing sources, return immediately
|
||||||
if (customModulesWithMissingSources.length === 0) {
|
if (customModulesWithMissingSources.length === 0) {
|
||||||
return {
|
return validCustomModules;
|
||||||
validCustomModules,
|
|
||||||
keptModulesWithoutSources: [],
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stop any spinner for interactive prompts
|
// Stop any spinner for interactive prompts
|
||||||
|
|
|
||||||
|
|
@ -391,8 +391,8 @@ class ModuleManager {
|
||||||
if (config.code === moduleName) {
|
if (config.code === moduleName) {
|
||||||
return modulePath;
|
return modulePath;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch {
|
||||||
throw new Error(`Failed to parse module.yaml at ${configPath}: ${error.message}`);
|
// Skip if can't read config
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,6 @@ const path = require('node:path');
|
||||||
const os = require('node:os');
|
const os = require('node:os');
|
||||||
const fs = require('fs-extra');
|
const fs = require('fs-extra');
|
||||||
const { CLIUtils } = require('./cli-utils');
|
const { CLIUtils } = require('./cli-utils');
|
||||||
const { CustomHandler } = require('../installers/lib/custom/handler');
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* UI utilities for the installer
|
* UI utilities for the installer
|
||||||
|
|
@ -151,6 +150,7 @@ class UI {
|
||||||
const { CustomModuleCache } = require('../installers/lib/core/custom-module-cache');
|
const { CustomModuleCache } = require('../installers/lib/core/custom-module-cache');
|
||||||
const cache = new CustomModuleCache(bmadDir);
|
const cache = new CustomModuleCache(bmadDir);
|
||||||
|
|
||||||
|
const { CustomHandler } = require('../installers/lib/custom/handler');
|
||||||
const customHandler = new CustomHandler();
|
const customHandler = new CustomHandler();
|
||||||
const customFiles = await customHandler.findCustomContent(customContentConfig.customPath);
|
const customFiles = await customHandler.findCustomContent(customContentConfig.customPath);
|
||||||
|
|
||||||
|
|
@ -218,6 +218,7 @@ class UI {
|
||||||
customContentConfig.selectedFiles = selectedCustomContent.map((mod) => mod.replace('__CUSTOM_CONTENT__', ''));
|
customContentConfig.selectedFiles = selectedCustomContent.map((mod) => mod.replace('__CUSTOM_CONTENT__', ''));
|
||||||
// Convert custom content to module IDs for installation
|
// Convert custom content to module IDs for installation
|
||||||
const customContentModuleIds = [];
|
const customContentModuleIds = [];
|
||||||
|
const { CustomHandler } = require('../installers/lib/custom/handler');
|
||||||
const customHandler = new CustomHandler();
|
const customHandler = new CustomHandler();
|
||||||
for (const customFile of customContentConfig.selectedFiles) {
|
for (const customFile of customContentConfig.selectedFiles) {
|
||||||
// Get the module info to extract the ID
|
// Get the module info to extract the ID
|
||||||
|
|
@ -636,8 +637,8 @@ class UI {
|
||||||
moduleData = yaml.load(yamlContent);
|
moduleData = yaml.load(yamlContent);
|
||||||
foundPath = configPath;
|
foundPath = configPath;
|
||||||
break;
|
break;
|
||||||
} catch (error) {
|
} catch {
|
||||||
throw new Error(`Failed to parse config at ${configPath}: ${error.message}`);
|
// Continue to next path
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -653,11 +654,20 @@ class UI {
|
||||||
cached: true,
|
cached: true,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// Module config not found - skip silently (non-critical)
|
// Debug: show what paths we tried to check
|
||||||
|
console.log(chalk.dim(`DEBUG: No module config found for ${cachedModule.id}`));
|
||||||
|
console.log(
|
||||||
|
chalk.dim(
|
||||||
|
`DEBUG: Tried paths:`,
|
||||||
|
possibleConfigPaths.map((p) => p.replace(cachedModule.cachePath, '.')),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
console.log(chalk.dim(`DEBUG: cachedModule:`, JSON.stringify(cachedModule, null, 2)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (customContentConfig.customPath) {
|
} else if (customContentConfig.customPath) {
|
||||||
// Existing installation - show from directory
|
// Existing installation - show from directory
|
||||||
|
const { CustomHandler } = require('../installers/lib/custom/handler');
|
||||||
const customHandler = new CustomHandler();
|
const customHandler = new CustomHandler();
|
||||||
const customFiles = await customHandler.findCustomContent(customContentConfig.customPath);
|
const customFiles = await customHandler.findCustomContent(customContentConfig.customPath);
|
||||||
|
|
||||||
|
|
@ -872,6 +882,7 @@ class UI {
|
||||||
expandedPath = this.expandUserPath(directory.trim());
|
expandedPath = this.expandUserPath(directory.trim());
|
||||||
|
|
||||||
// Check if directory has custom content
|
// Check if directory has custom content
|
||||||
|
const { CustomHandler } = require('../installers/lib/custom/handler');
|
||||||
const customHandler = new CustomHandler();
|
const customHandler = new CustomHandler();
|
||||||
const customFiles = await customHandler.findCustomContent(expandedPath);
|
const customFiles = await customHandler.findCustomContent(expandedPath);
|
||||||
|
|
||||||
|
|
@ -1266,6 +1277,7 @@ class UI {
|
||||||
const resolvedPath = CLIUtils.expandPath(customPath);
|
const resolvedPath = CLIUtils.expandPath(customPath);
|
||||||
|
|
||||||
// Find custom content
|
// Find custom content
|
||||||
|
const { CustomHandler } = require('../installers/lib/custom/handler');
|
||||||
const customHandler = new CustomHandler();
|
const customHandler = new CustomHandler();
|
||||||
const customFiles = await customHandler.findCustomContent(resolvedPath);
|
const customFiles = await customHandler.findCustomContent(resolvedPath);
|
||||||
|
|
||||||
|
|
@ -1290,10 +1302,12 @@ class UI {
|
||||||
|
|
||||||
// Display found items
|
// Display found items
|
||||||
console.log(chalk.cyan(`\nFound ${customFiles.length} custom content file(s):`));
|
console.log(chalk.cyan(`\nFound ${customFiles.length} custom content file(s):`));
|
||||||
|
const { CustomHandler: CustomHandler2 } = require('../installers/lib/custom/handler');
|
||||||
|
const customHandler2 = new CustomHandler2();
|
||||||
const customContentItems = [];
|
const customContentItems = [];
|
||||||
|
|
||||||
for (const customFile of customFiles) {
|
for (const customFile of customFiles) {
|
||||||
const customInfo = await customHandler.getCustomInfo(customFile);
|
const customInfo = await customHandler2.getCustomInfo(customFile);
|
||||||
if (customInfo) {
|
if (customInfo) {
|
||||||
customContentItems.push({
|
customContentItems.push({
|
||||||
name: `${chalk.cyan('✓')} ${customInfo.name} ${chalk.gray(`(${customInfo.relativePath})`)}`,
|
name: `${chalk.cyan('✓')} ${customInfo.name} ${chalk.gray(`(${customInfo.relativePath})`)}`,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue