@@ -96,6 +96,10 @@ pub struct IndexFacade {
9696 /// Set to true when load_semantic_search fails with DimensionMismatch so
9797 /// hot-reload and other callers do not retry on every reload cycle.
9898 semantic_incompatible : bool ,
99+
100+ /// Persisted semantic metadata for status/reporting when semantic search
101+ /// is not loaded into memory (for example, lite facade loads).
102+ semantic_metadata_snapshot : Option < crate :: semantic:: SemanticMetadata > ,
99103}
100104
101105impl IndexFacade {
@@ -126,6 +130,7 @@ impl IndexFacade {
126130 indexed_paths : HashSet :: new ( ) ,
127131 index_base,
128132 semantic_incompatible : false ,
133+ semantic_metadata_snapshot : None ,
129134 } )
130135 }
131136
@@ -151,6 +156,7 @@ impl IndexFacade {
151156 indexed_paths : HashSet :: new ( ) ,
152157 index_base,
153158 semantic_incompatible : false ,
159+ semantic_metadata_snapshot : None ,
154160 }
155161 }
156162
@@ -201,6 +207,7 @@ impl IndexFacade {
201207 } ;
202208
203209 self . semantic_search = Some ( Arc :: new ( Mutex :: new ( semantic) ) ) ;
210+ self . semantic_metadata_snapshot = self . get_semantic_metadata ( ) ;
204211 self . embedding_pool = Some ( backend) ;
205212
206213 Ok ( ( ) )
@@ -289,6 +296,7 @@ impl IndexFacade {
289296 }
290297
291298 self . semantic_search = Some ( Arc :: new ( Mutex :: new ( semantic) ) ) ;
299+ self . semantic_metadata_snapshot = self . get_semantic_metadata ( ) ;
292300 return Ok ( true ) ;
293301 }
294302 Err ( SemanticSearchError :: DimensionMismatch {
@@ -324,6 +332,18 @@ impl IndexFacade {
324332 Ok ( false )
325333 }
326334
335+ /// Load persisted semantic metadata without initializing the semantic backend.
336+ pub fn load_semantic_metadata_snapshot ( & mut self , path : & Path ) -> FacadeResult < bool > {
337+ if !path. join ( "metadata.json" ) . exists ( ) {
338+ self . semantic_metadata_snapshot = None ;
339+ return Ok ( false ) ;
340+ }
341+
342+ let metadata = crate :: semantic:: SemanticMetadata :: load ( path) ?;
343+ self . semantic_metadata_snapshot = Some ( metadata) ;
344+ Ok ( true )
345+ }
346+
327347 /// Ensure embedding backend is initialized for generating new embeddings.
328348 ///
329349 /// Called lazily by methods that need to compute embeddings (reindexing, watcher).
@@ -343,6 +363,11 @@ impl IndexFacade {
343363 self . semantic_search
344364 . as_ref ( )
345365 . map ( |s| s. lock ( ) . map ( |sem| sem. embedding_count ( ) ) . unwrap_or ( 0 ) )
366+ . or_else ( || {
367+ self . semantic_metadata_snapshot
368+ . as_ref ( )
369+ . map ( |m| m. embedding_count )
370+ } )
346371 . unwrap_or ( 0 )
347372 }
348373
@@ -351,6 +376,7 @@ impl IndexFacade {
351376 self . semantic_search
352377 . as_ref ( )
353378 . and_then ( |s| s. lock ( ) . ok ( ) . and_then ( |sem| sem. metadata ( ) . cloned ( ) ) )
379+ . or_else ( || self . semantic_metadata_snapshot . clone ( ) )
354380 }
355381
356382 // =========================================================================
@@ -1224,6 +1250,20 @@ pub fn resolve_remote_model_name(cfg: &crate::config::SemanticSearchConfig) -> S
12241250 . unwrap_or_else ( || "text-embedding-ada-002" . to_string ( ) )
12251251}
12261252
1253+ /// Format a human-readable semantic search status line for CLI output.
1254+ pub fn format_semantic_status ( cfg : & crate :: config:: SemanticSearchConfig ) -> String {
1255+ let is_remote = std:: env:: var ( "CODANNA_EMBED_URL" ) . is_ok ( ) || cfg. remote_url . is_some ( ) ;
1256+ let threshold = cfg. threshold ;
1257+
1258+ if is_remote {
1259+ let model = resolve_remote_model_name ( cfg) ;
1260+ format ! ( "Semantic search enabled (backend: remote, model: {model}, threshold: {threshold})" )
1261+ } else {
1262+ let model = & cfg. model ;
1263+ format ! ( "Semantic search enabled (model: {model}, threshold: {threshold})" )
1264+ }
1265+ }
1266+
12271267pub fn build_embedding_backend (
12281268 cfg : & crate :: config:: SemanticSearchConfig ,
12291269) -> FacadeResult < EmbeddingBackend > {
0 commit comments