@@ -194,17 +194,21 @@ export async function globalInit(params: GlobalInitParams): Promise<GlobalInitRe
194194
195195 // Remove parent-duplicate projects already in the global DB
196196 // (from previous runs before deduplication was added)
197+ // Only remove parents with DIRECT children (one level deeper)
197198 const allRegistered = globalDb . getProjects ( ) ;
198199 const allPaths = allRegistered . map ( p => ( {
199200 id : p . id ,
200201 path : p . path . replace ( / \\ / g, '/' ) . replace ( / \/ + $ / , '' ) + '/' ,
201202 } ) ) ;
202203 for ( const project of allPaths ) {
203- const isParent = allPaths . some ( other =>
204- other . id !== project . id &&
205- other . path . startsWith ( project . path )
206- ) ;
207- if ( isParent ) {
204+ const hasDirectChild = allPaths . some ( other => {
205+ if ( other . id === project . id ) return false ;
206+ if ( ! other . path . startsWith ( project . path ) ) return false ;
207+ const remainder = other . path . slice ( project . path . length ) ;
208+ const segments = remainder . replace ( / \/ + $ / , '' ) . split ( '/' ) ;
209+ return segments . length === 1 ;
210+ } ) ;
211+ if ( hasDirectChild ) {
208212 globalDb . unregisterProject ( project . path . replace ( / \/ + $ / , '' ) ) ;
209213 removedCount ++ ;
210214 }
@@ -505,11 +509,15 @@ function findUnindexedProjects(searchPath: string, maxDepth: number, indexedPath
505509
506510/**
507511 * Deduplicate projects: If project A is a parent directory of project B,
508- * keep only B (the more specific sub-project).
512+ * keep only B (the more specific sub-project) — but only when B is a
513+ * DIRECT child of A (one level deeper).
509514 *
510515 * Example: Given paths [AudioGrabber/, AudioGrabber/AudioGrabber/, AudioGrabber/AudioGrabber2/]
511- * → Remove AudioGrabber/ because it contains sub-projects.
516+ * → Remove AudioGrabber/ because it has direct sub-projects.
512517 * → Keep AudioGrabber/AudioGrabber/ and AudioGrabber/AudioGrabber2/.
518+ *
519+ * Counter-example: [Aidex/, Aidex/SampleLangProjects/java-minimal-json/]
520+ * → Keep Aidex/ because the sub-project is nested 2+ levels deep (not a direct child).
513521 */
514522function deduplicateProjects < T extends { path : string ; name : string } > ( projects : T [ ] ) : T [ ] {
515523 // Normalize all paths
@@ -521,14 +529,19 @@ function deduplicateProjects<T extends { path: string; name: string }>(projects:
521529 // Sort by path length descending (deepest first)
522530 normalized . sort ( ( a , b ) => b . _normPath . length - a . _normPath . length ) ;
523531
524- // A project is a "parent" if any other project's path starts with it
532+ // A project is a "parent" only if another project is a DIRECT child (one level deeper)
525533 const result : typeof projects = [ ] ;
526534 for ( const project of normalized ) {
527- const isParent = normalized . some ( other =>
528- other !== project &&
529- other . _normPath . startsWith ( project . _normPath )
530- ) ;
531- if ( ! isParent ) {
535+ const hasDirectChild = normalized . some ( other => {
536+ if ( other === project ) return false ;
537+ if ( ! other . _normPath . startsWith ( project . _normPath ) ) return false ;
538+ // Check if it's a direct child: the remaining path after the parent
539+ // should contain no additional slashes (only "childName/")
540+ const remainder = other . _normPath . slice ( project . _normPath . length ) ;
541+ const segments = remainder . replace ( / \/ + $ / , '' ) . split ( '/' ) ;
542+ return segments . length === 1 ;
543+ } ) ;
544+ if ( ! hasDirectChild ) {
532545 result . push ( project ) ;
533546 }
534547 }
0 commit comments