@@ -439,6 +439,33 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
439439 }
440440 }
441441 } else {
442+ // For regular updates (modify flow), check manifest for custom module sources
443+ if ( config . _isUpdate && config . _existingInstall && config . _existingInstall . customModules ) {
444+ for ( const customModule of config . _existingInstall . customModules ) {
445+ // Ensure we have an absolute sourcePath
446+ let absoluteSourcePath = customModule . sourcePath ;
447+
448+ // Check if sourcePath is a cache-relative path (starts with _config)
449+ if ( absoluteSourcePath && absoluteSourcePath . startsWith ( '_config' ) ) {
450+ // Convert cache-relative path to absolute path
451+ absoluteSourcePath = path . join ( bmadDir , absoluteSourcePath ) ;
452+ }
453+ // If no sourcePath but we have relativePath, convert it
454+ else if ( ! absoluteSourcePath && customModule . relativePath ) {
455+ // relativePath is relative to the project root (parent of bmad dir)
456+ absoluteSourcePath = path . resolve ( projectDir , customModule . relativePath ) ;
457+ }
458+ // Ensure sourcePath is absolute for anything else
459+ else if ( absoluteSourcePath && ! path . isAbsolute ( absoluteSourcePath ) ) {
460+ absoluteSourcePath = path . resolve ( absoluteSourcePath ) ;
461+ }
462+
463+ if ( absoluteSourcePath ) {
464+ customModulePaths . set ( customModule . id , absoluteSourcePath ) ;
465+ }
466+ }
467+ }
468+
442469 // Build custom module paths map from customContent
443470
444471 // Handle selectedFiles (from existing install path or manual directory input)
@@ -589,20 +616,39 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
589616
590617 // Detect custom and modified files BEFORE updating (compare current files vs files-manifest.csv)
591618 const existingFilesManifest = await this . readFilesManifest ( bmadDir ) ;
592- console . log ( chalk . dim ( `DEBUG: Read ${ existingFilesManifest . length } files from manifest` ) ) ;
593- console . log ( chalk . dim ( `DEBUG: Manifest has hashes: ${ existingFilesManifest . some ( ( f ) => f . hash ) } ` ) ) ;
594-
595619 const { customFiles, modifiedFiles } = await this . detectCustomFiles ( bmadDir , existingFilesManifest ) ;
596620
597- console . log ( chalk . dim ( `DEBUG: Found ${ customFiles . length } custom files, ${ modifiedFiles . length } modified files` ) ) ;
598- if ( modifiedFiles . length > 0 ) {
599- console . log ( chalk . yellow ( 'DEBUG: Modified files:' ) ) ;
600- for ( const f of modifiedFiles ) console . log ( chalk . dim ( ` - ${ f . path } ` ) ) ;
601- }
602-
603621 config . _customFiles = customFiles ;
604622 config . _modifiedFiles = modifiedFiles ;
605623
624+ // Also check cache directory for custom modules (like quick update does)
625+ const cacheDir = path . join ( bmadDir , '_config' , 'custom' ) ;
626+ if ( await fs . pathExists ( cacheDir ) ) {
627+ const cachedModules = await fs . readdir ( cacheDir , { withFileTypes : true } ) ;
628+
629+ for ( const cachedModule of cachedModules ) {
630+ if ( cachedModule . isDirectory ( ) ) {
631+ const moduleId = cachedModule . name ;
632+
633+ // Skip if we already have this module from manifest
634+ if ( customModulePaths . has ( moduleId ) ) {
635+ continue ;
636+ }
637+
638+ const cachedPath = path . join ( cacheDir , moduleId ) ;
639+
640+ // Check if this is actually a custom module (has module.yaml)
641+ const moduleYamlPath = path . join ( cachedPath , 'module.yaml' ) ;
642+ if ( await fs . pathExists ( moduleYamlPath ) ) {
643+ customModulePaths . set ( moduleId , cachedPath ) ;
644+ }
645+ }
646+ }
647+
648+ // Update module manager with the new custom module paths from cache
649+ this . moduleManager . setCustomModulePaths ( customModulePaths ) ;
650+ }
651+
606652 // If there are custom files, back them up temporarily
607653 if ( customFiles . length > 0 ) {
608654 const tempBackupDir = path . join ( projectDir , '_bmad-custom-backup-temp' ) ;
@@ -625,20 +671,16 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
625671 const tempModifiedBackupDir = path . join ( projectDir , '_bmad-modified-backup-temp' ) ;
626672 await fs . ensureDir ( tempModifiedBackupDir ) ;
627673
628- console . log ( chalk . yellow ( `\nDEBUG: Backing up ${ modifiedFiles . length } modified files to temp location` ) ) ;
629674 spinner . start ( `Backing up ${ modifiedFiles . length } modified files...` ) ;
630675 for ( const modifiedFile of modifiedFiles ) {
631676 const relativePath = path . relative ( bmadDir , modifiedFile . path ) ;
632677 const tempBackupPath = path . join ( tempModifiedBackupDir , relativePath ) ;
633- console . log ( chalk . dim ( `DEBUG: Backing up ${ relativePath } to temp` ) ) ;
634678 await fs . ensureDir ( path . dirname ( tempBackupPath ) ) ;
635679 await fs . copy ( modifiedFile . path , tempBackupPath , { overwrite : true } ) ;
636680 }
637681 spinner . succeed ( `Backed up ${ modifiedFiles . length } modified files` ) ;
638682
639683 config . _tempModifiedBackupDir = tempModifiedBackupDir ;
640- } else {
641- console . log ( chalk . dim ( 'DEBUG: No modified files detected' ) ) ;
642684 }
643685 }
644686 } else if ( existingInstall . installed && config . _quickUpdate ) {
@@ -654,6 +696,34 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
654696 config . _customFiles = customFiles ;
655697 config . _modifiedFiles = modifiedFiles ;
656698
699+ // Also check cache directory for custom modules (like quick update does)
700+ const cacheDir = path . join ( bmadDir , '_config' , 'custom' ) ;
701+ if ( await fs . pathExists ( cacheDir ) ) {
702+ const cachedModules = await fs . readdir ( cacheDir , { withFileTypes : true } ) ;
703+
704+ for ( const cachedModule of cachedModules ) {
705+ if ( cachedModule . isDirectory ( ) ) {
706+ const moduleId = cachedModule . name ;
707+
708+ // Skip if we already have this module from manifest
709+ if ( customModulePaths . has ( moduleId ) ) {
710+ continue ;
711+ }
712+
713+ const cachedPath = path . join ( cacheDir , moduleId ) ;
714+
715+ // Check if this is actually a custom module (has module.yaml)
716+ const moduleYamlPath = path . join ( cachedPath , 'module.yaml' ) ;
717+ if ( await fs . pathExists ( moduleYamlPath ) ) {
718+ customModulePaths . set ( moduleId , cachedPath ) ;
719+ }
720+ }
721+ }
722+
723+ // Update module manager with the new custom module paths from cache
724+ this . moduleManager . setCustomModulePaths ( customModulePaths ) ;
725+ }
726+
657727 // Back up custom files
658728 if ( customFiles . length > 0 ) {
659729 const tempBackupDir = path . join ( projectDir , '_bmad-custom-backup-temp' ) ;
@@ -832,7 +902,6 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
832902 // For dependency resolution, we need to pass the project root
833903 // Create a temporary module manager that knows about custom content locations
834904 const tempModuleManager = new ModuleManager ( {
835- scanProjectForModules : true ,
836905 bmadDir : bmadDir , // Pass bmadDir so we can check cache
837906 } ) ;
838907
@@ -1057,7 +1126,7 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
10571126
10581127 // Pass pre-collected configuration to avoid re-prompting
10591128 await this . ideManager . setup ( ide , projectDir , bmadDir , {
1060- selectedModules : config . modules || [ ] ,
1129+ selectedModules : allModules || [ ] ,
10611130 preCollectedConfig : ideConfigurations [ ide ] || null ,
10621131 verbose : config . verbose ,
10631132 } ) ;
@@ -1184,11 +1253,6 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
11841253 // Report custom and modified files if any were found
11851254 if ( customFiles . length > 0 ) {
11861255 console . log ( chalk . cyan ( `\n📁 Custom files preserved: ${ customFiles . length } ` ) ) ;
1187- console . log ( chalk . dim ( 'The following custom files were found and restored:\n' ) ) ;
1188- for ( const customFile of customFiles ) {
1189- const relativePath = path . relative ( projectDir , customFile ) ;
1190- console . log ( chalk . dim ( ` • ${ relativePath } ` ) ) ;
1191- }
11921256 }
11931257
11941258 if ( modifiedFiles . length > 0 ) {
@@ -2184,41 +2248,8 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
21842248 const configuredIdes = existingInstall . ides || [ ] ;
21852249 const projectRoot = path . dirname ( bmadDir ) ;
21862250
2187- // Get custom module sources from manifest and cache
2251+ // Get custom module sources from cache
21882252 const customModuleSources = new Map ( ) ;
2189-
2190- // First check manifest for backward compatibility
2191- if ( existingInstall . customModules ) {
2192- for ( const customModule of existingInstall . customModules ) {
2193- // Ensure we have an absolute sourcePath
2194- let absoluteSourcePath = customModule . sourcePath ;
2195-
2196- // Check if sourcePath is a cache-relative path (starts with _config/)
2197- if ( absoluteSourcePath && absoluteSourcePath . startsWith ( '_config' ) ) {
2198- // Convert cache-relative path to absolute path
2199- absoluteSourcePath = path . join ( bmadDir , absoluteSourcePath ) ;
2200- }
2201- // If no sourcePath but we have relativePath, convert it
2202- else if ( ! absoluteSourcePath && customModule . relativePath ) {
2203- // relativePath is relative to the project root (parent of bmad dir)
2204- absoluteSourcePath = path . resolve ( projectRoot , customModule . relativePath ) ;
2205- }
2206- // Ensure sourcePath is absolute for anything else
2207- else if ( absoluteSourcePath && ! path . isAbsolute ( absoluteSourcePath ) ) {
2208- absoluteSourcePath = path . resolve ( absoluteSourcePath ) ;
2209- }
2210-
2211- // Update the custom module object with the absolute path
2212- const updatedModule = {
2213- ...customModule ,
2214- sourcePath : absoluteSourcePath ,
2215- } ;
2216-
2217- customModuleSources . set ( customModule . id , updatedModule ) ;
2218- }
2219- }
2220-
2221- // Also check cache directory for any modules not in manifest
22222253 const cacheDir = path . join ( bmadDir , '_config' , 'custom' ) ;
22232254 if ( await fs . pathExists ( cacheDir ) ) {
22242255 const cachedModules = await fs . readdir ( cacheDir , { withFileTypes : true } ) ;
@@ -2277,126 +2308,6 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
22772308 }
22782309 }
22792310
2280- // Check for untracked custom modules (installed but not in manifest)
2281- const untrackedCustomModules = [ ] ;
2282- for ( const installedModule of installedModules ) {
2283- // Skip standard modules and core
2284- const standardModuleIds = [ 'bmb' , 'bmgd' , 'bmm' , 'cis' , 'core' ] ;
2285- if ( standardModuleIds . includes ( installedModule ) ) {
2286- continue ;
2287- }
2288-
2289- // Check if this installed module is not tracked in customModules
2290- if ( ! customModuleSources . has ( installedModule ) ) {
2291- const modulePath = path . join ( bmadDir , installedModule ) ;
2292- if ( await fs . pathExists ( modulePath ) ) {
2293- untrackedCustomModules . push ( {
2294- id : installedModule ,
2295- name : installedModule , // We don't have the original name
2296- path : modulePath ,
2297- untracked : true ,
2298- } ) ;
2299- }
2300- }
2301- }
2302-
2303- // If we found untracked custom modules, offer to track them
2304- if ( untrackedCustomModules . length > 0 ) {
2305- spinner . stop ( ) ;
2306- console . log ( chalk . yellow ( `\n⚠️ Found ${ untrackedCustomModules . length } custom module(s) not tracked in manifest:` ) ) ;
2307-
2308- for ( const untracked of untrackedCustomModules ) {
2309- console . log ( chalk . dim ( ` • ${ untracked . id } (installed at ${ path . relative ( projectRoot , untracked . path ) } )` ) ) ;
2310- }
2311-
2312- const { trackModules } = await inquirer . prompt ( [
2313- {
2314- type : 'confirm' ,
2315- name : 'trackModules' ,
2316- message : chalk . cyan ( 'Would you like to scan for their source locations?' ) ,
2317- default : true ,
2318- } ,
2319- ] ) ;
2320-
2321- if ( trackModules ) {
2322- const { scanDirectory } = await inquirer . prompt ( [
2323- {
2324- type : 'input' ,
2325- name : 'scanDirectory' ,
2326- message : 'Enter directory to scan for custom module sources (or leave blank to skip):' ,
2327- default : projectRoot ,
2328- validate : async ( input ) => {
2329- if ( input && input . trim ( ) !== '' ) {
2330- const expandedPath = path . resolve ( input . trim ( ) ) ;
2331- if ( ! ( await fs . pathExists ( expandedPath ) ) ) {
2332- return 'Directory does not exist' ;
2333- }
2334- const stats = await fs . stat ( expandedPath ) ;
2335- if ( ! stats . isDirectory ( ) ) {
2336- return 'Path must be a directory' ;
2337- }
2338- }
2339- return true ;
2340- } ,
2341- } ,
2342- ] ) ;
2343-
2344- if ( scanDirectory && scanDirectory . trim ( ) !== '' ) {
2345- console . log ( chalk . dim ( '\nScanning for custom module sources...' ) ) ;
2346-
2347- // Scan for all module.yaml files
2348- const allModulePaths = await this . moduleManager . findModulesInProject ( scanDirectory ) ;
2349- const { ModuleManager } = require ( '../modules/manager' ) ;
2350- const mm = new ModuleManager ( { scanProjectForModules : true } ) ;
2351-
2352- for ( const untracked of untrackedCustomModules ) {
2353- let foundSource = null ;
2354-
2355- // Try to find by module ID
2356- for ( const modulePath of allModulePaths ) {
2357- try {
2358- const moduleInfo = await mm . getModuleInfo ( modulePath ) ;
2359- if ( moduleInfo && moduleInfo . id === untracked . id ) {
2360- foundSource = {
2361- path : modulePath ,
2362- info : moduleInfo ,
2363- } ;
2364- break ;
2365- }
2366- } catch {
2367- // Continue searching
2368- }
2369- }
2370-
2371- if ( foundSource ) {
2372- console . log ( chalk . green ( ` ✓ Found source for ${ untracked . id } : ${ path . relative ( projectRoot , foundSource . path ) } ` ) ) ;
2373-
2374- // Add to manifest
2375- await this . manifest . addCustomModule ( bmadDir , {
2376- id : untracked . id ,
2377- name : foundSource . info . name || untracked . name ,
2378- sourcePath : path . resolve ( foundSource . path ) ,
2379- installDate : new Date ( ) . toISOString ( ) ,
2380- tracked : true ,
2381- } ) ;
2382-
2383- // Add to customModuleSources for processing
2384- customModuleSources . set ( untracked . id , {
2385- id : untracked . id ,
2386- name : foundSource . info . name || untracked . name ,
2387- sourcePath : path . resolve ( foundSource . path ) ,
2388- } ) ;
2389- } else {
2390- console . log ( chalk . yellow ( ` ⚠ Could not find source for ${ untracked . id } ` ) ) ;
2391- }
2392- }
2393- }
2394- }
2395-
2396- console . log ( chalk . dim ( '\nUntracked custom modules will remain installed but cannot be updated without their source.' ) ) ;
2397- spinner . start ( 'Preparing update...' ) ;
2398- }
2399-
24002311 // Handle missing custom module sources using shared method
24012312 const customModuleResult = await this . handleMissingCustomSources (
24022313 customModuleSources ,
@@ -2414,18 +2325,6 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
24142325 hasUpdate : true ,
24152326 } ) ) ;
24162327
2417- // Add untracked modules to the update list but mark them as untrackable
2418- for ( const untracked of untrackedCustomModules ) {
2419- if ( ! customModuleSources . has ( untracked . id ) ) {
2420- customModulesFromManifest . push ( {
2421- ...untracked ,
2422- isCustom : true ,
2423- hasUpdate : false , // Can't update without source
2424- untracked : true ,
2425- } ) ;
2426- }
2427- }
2428-
24292328 const allAvailableModules = [ ...availableModules , ...customModulesFromManifest ] ;
24302329 const availableModuleIds = new Set ( allAvailableModules . map ( ( m ) => m . id ) ) ;
24312330
0 commit comments