Compare commits

..

1 Commits

Author SHA1 Message Date
mrsaifullah52 f82cdef6cf
Merge c6e53dbbc7 into 2da016f797 2025-12-08 09:53:19 +02:00
11 changed files with 194 additions and 141 deletions

Binary file not shown.

View File

@ -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`

View File

@ -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`

View File

@ -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

View File

@ -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._

View File

@ -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"

View File

@ -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');

View File

@ -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');

View File

@ -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

View File

@ -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
} }
} }
} }

View File

@ -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})`)}`,