@@ -173,7 +173,7 @@ export const INDEX_CONSUMING_RESOURCE_NAMES = ['Codebase Intelligence'] as const
173173
174174type IndexStatus = 'ready' | 'rebuild-required' | 'indexing' | 'unknown' ;
175175type IndexConfidence = 'high' | 'low' ;
176- type IndexAction = 'served' | 'rebuild-started' | 'rebuilt-and-served' | 'rebuild-failed' ;
176+ type IndexAction = 'served' | 'rebuild-started' | 'rebuilt-and-served' ;
177177
178178export type IndexSignal = {
179179 status : IndexStatus ;
@@ -230,6 +230,25 @@ async function ensureValidIndexOrAutoHeal(project: ProjectState): Promise<IndexS
230230 }
231231}
232232
233+ async function validateProjectDirectory ( rootPath : string ) : Promise < ToolResponse | undefined > {
234+ try {
235+ const stats = await fs . stat ( rootPath ) ;
236+ if ( stats . isDirectory ( ) ) {
237+ return undefined ;
238+ }
239+
240+ return buildProjectSelectionError (
241+ 'unknown_project' ,
242+ `project_directory is not a directory: ${ rootPath } `
243+ ) ;
244+ } catch {
245+ return buildProjectSelectionError (
246+ 'unknown_project' ,
247+ `project_directory does not exist: ${ rootPath } `
248+ ) ;
249+ }
250+ }
251+
233252/**
234253 * Check if file/directory exists
235254 */
@@ -349,14 +368,6 @@ async function generateCodebaseContext(project: ProjectState): Promise<string> {
349368 ( index . reason ? `\nReason: ${ index . reason } ` : '' )
350369 ) ;
351370 }
352- if ( index . action === 'rebuild-failed' ) {
353- return (
354- '# Codebase Intelligence\n\n' +
355- 'Index rebuild required before intelligence can be served.\n\n' +
356- `Index: ${ index . status } (${ index . confidence } , ${ index . action } )` +
357- ( index . reason ? `\nReason: ${ index . reason } ` : '' )
358- ) ;
359- }
360371
361372 try {
362373 const content = await fs . readFile ( intelligencePath , 'utf-8' ) ;
@@ -685,9 +696,16 @@ async function resolveProjectForTool(args: Record<string, unknown>): Promise<Pro
685696 } ;
686697 }
687698
688- const rootPath = knownRootPath ?? registerKnownRoot ( requestedProjectDirectory ) ;
699+ const rootPath = knownRootPath ?? requestedProjectDirectory ;
700+ const invalidProjectResponse = await validateProjectDirectory ( rootPath ) ;
701+ if ( invalidProjectResponse ) {
702+ return { ok : false , response : invalidProjectResponse } ;
703+ }
704+
689705 const project = getOrCreateProject ( rootPath ) ;
690- await initProject ( project . rootPath , watcherDebounceMs ) ;
706+ await initProject ( project . rootPath , watcherDebounceMs , {
707+ enableWatcher : knownRootPath !== undefined
708+ } ) ;
691709 return { ok : true , project } ;
692710 }
693711
@@ -703,7 +721,7 @@ async function resolveProjectForTool(args: Record<string, unknown>): Promise<Pro
703721
704722 const [ rootPath ] = availableRoots ;
705723 const project = getOrCreateProject ( rootPath ) ;
706- await initProject ( project . rootPath , watcherDebounceMs ) ;
724+ await initProject ( project . rootPath , watcherDebounceMs , { enableWatcher : true } ) ;
707725 return { ok : true , project } ;
708726}
709727
@@ -715,7 +733,7 @@ async function resolveProjectForResource(): Promise<ProjectState | undefined> {
715733
716734 const [ rootPath ] = availableRoots ;
717735 const project = getOrCreateProject ( rootPath ) ;
718- await initProject ( project . rootPath , watcherDebounceMs ) ;
736+ await initProject ( project . rootPath , watcherDebounceMs , { enableWatcher : true } ) ;
719737 return project ;
720738}
721739
@@ -768,7 +786,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
768786 } ;
769787 }
770788 indexSignal = await ensureValidIndexOrAutoHeal ( project ) ;
771- if ( indexSignal . action === 'rebuild-started' || indexSignal . action === 'rebuild-failed' ) {
789+ if ( indexSignal . action === 'rebuild-started' ) {
772790 return {
773791 content : [
774792 {
@@ -817,45 +835,58 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
817835 * Initialize a project: migrate legacy structure, check index, start watcher.
818836 * Deduplicates via normalized root key.
819837 */
820- async function initProject ( rootPath : string , debounceMs : number ) : Promise < void > {
821- const project = getOrCreateProject ( rootPath ) ;
838+ type InitProjectOptions = {
839+ enableWatcher : boolean ;
840+ } ;
822841
823- // Skip if already initialized
824- if (
825- project . indexState . status === 'indexing' ||
826- project . indexState . status === 'ready' ||
827- project . stopWatcher
828- ) {
842+ async function ensureProjectInitialized ( project : ProjectState ) : Promise < void > {
843+ if ( project . initPromise ) {
844+ await project . initPromise ;
829845 return ;
830846 }
831847
832- // Migrate legacy structure
833- try {
834- const legacyPaths = makeLegacyPaths ( project . rootPath ) ;
835- const migrated = await migrateToNewStructure ( project . paths , legacyPaths ) ;
836- if ( migrated && process . env . CODEBASE_CONTEXT_DEBUG ) {
837- console . error ( `[DEBUG] Migrated to .codebase-context/ structure: ${ project . rootPath } ` ) ;
838- }
839- } catch {
840- // Non-fatal
848+ if ( project . indexState . status !== 'idle' ) {
849+ return ;
841850 }
842851
843- // Check if indexing is needed
844- const needsIndex = await shouldReindex ( project . paths ) ;
845- if ( needsIndex ) {
846- if ( process . env . CODEBASE_CONTEXT_DEBUG ) {
847- console . error ( `[DEBUG] Starting indexing: ${ project . rootPath } ` ) ;
852+ project . initPromise = ( async ( ) => {
853+ // Migrate legacy structure
854+ try {
855+ const legacyPaths = makeLegacyPaths ( project . rootPath ) ;
856+ const migrated = await migrateToNewStructure ( project . paths , legacyPaths ) ;
857+ if ( migrated && process . env . CODEBASE_CONTEXT_DEBUG ) {
858+ console . error ( `[DEBUG] Migrated to .codebase-context/ structure: ${ project . rootPath } ` ) ;
859+ }
860+ } catch {
861+ // Non-fatal
848862 }
849- void performIndexing ( project ) ;
850- } else {
851- if ( process . env . CODEBASE_CONTEXT_DEBUG ) {
852- console . error ( `[DEBUG] Index found. Ready: ${ project . rootPath } ` ) ;
863+
864+ // Check if indexing is needed
865+ const needsIndex = await shouldReindex ( project . paths ) ;
866+ if ( needsIndex ) {
867+ if ( process . env . CODEBASE_CONTEXT_DEBUG ) {
868+ console . error ( `[DEBUG] Starting indexing: ${ project . rootPath } ` ) ;
869+ }
870+ void performIndexing ( project ) ;
871+ } else {
872+ if ( process . env . CODEBASE_CONTEXT_DEBUG ) {
873+ console . error ( `[DEBUG] Index found. Ready: ${ project . rootPath } ` ) ;
874+ }
875+ project . indexState . status = 'ready' ;
876+ project . indexState . lastIndexed = new Date ( ) ;
853877 }
854- project . indexState . status = 'ready' ;
855- project . indexState . lastIndexed = new Date ( ) ;
878+ } ) ( ) . finally ( ( ) => {
879+ project . initPromise = undefined ;
880+ } ) ;
881+
882+ await project . initPromise ;
883+ }
884+
885+ function ensureProjectWatcher ( project : ProjectState , debounceMs : number ) : void {
886+ if ( project . stopWatcher ) {
887+ return ;
856888 }
857889
858- // Start file watcher
859890 project . stopWatcher = startFileWatcher ( {
860891 rootPath : project . rootPath ,
861892 debounceMs,
@@ -881,6 +912,19 @@ async function initProject(rootPath: string, debounceMs: number): Promise<void>
881912 } ) ;
882913}
883914
915+ async function initProject (
916+ rootPath : string ,
917+ debounceMs : number ,
918+ options : InitProjectOptions
919+ ) : Promise < void > {
920+ const project = getOrCreateProject ( rootPath ) ;
921+ await ensureProjectInitialized ( project ) ;
922+
923+ if ( options . enableWatcher ) {
924+ ensureProjectWatcher ( project , debounceMs ) ;
925+ }
926+ }
927+
884928async function main ( ) {
885929 // Validate root path exists and is a directory
886930 try {
@@ -928,7 +972,7 @@ async function main() {
928972 // Preserve current single-project startup behavior without eagerly indexing every root.
929973 const startupRoots = getKnownRootPaths ( ) ;
930974 if ( startupRoots . length === 1 ) {
931- await initProject ( startupRoots [ 0 ] , watcherDebounceMs ) ;
975+ await initProject ( startupRoots [ 0 ] , watcherDebounceMs , { enableWatcher : true } ) ;
932976 }
933977
934978 // Subscribe to root changes
0 commit comments