@@ -50,8 +50,19 @@ pub struct QueryEngine {
5050 /// Prepend split-identifier words to the embed text (helps static
5151 /// embedders; off by default to leave the transformer path unchanged).
5252 split_identifiers : std:: sync:: atomic:: AtomicBool ,
53+ /// Set when the watcher daemon this session attached to holds vectors
54+ /// built with different embed settings. No embed run will follow, so the
55+ /// search status must not say embeddings are building.
56+ daemon_vectors_mismatched : std:: sync:: atomic:: AtomicBool ,
5357}
5458
59+ /// Search status when the attached watcher daemon's vectors were built with
60+ /// different embed settings than this session's.
61+ const DAEMON_VECTORS_MISMATCHED_STATUS : & str = "The --watch daemon's stored vectors were built \
62+ with different embedding settings, so semantic matching is unavailable this session - results \
63+ are from name/text search only. Restart the daemon with the same --full-body-embedding / \
64+ --split-identifiers / --embedding-model flags as this session.";
65+
5566/// Max characters of function body for full-body embedding.
5667/// ~512 tokens ≈ first 40-50 lines of code.
5768const FULL_BODY_MAX_CHARS : usize = 2048 ;
@@ -397,9 +408,18 @@ impl QueryEngine {
397408 // cannot recover the words on its own. needs_word_split() keeps it
398409 // off for snake_case, where it does not help.
399410 split_identifiers : std:: sync:: atomic:: AtomicBool :: new ( true ) ,
411+ daemon_vectors_mismatched : std:: sync:: atomic:: AtomicBool :: new ( false ) ,
400412 }
401413 }
402414
415+ /// Record that the attached watcher daemon's vectors were built with
416+ /// different embed settings, so they will not load and nothing in this
417+ /// session will build replacements.
418+ pub fn set_daemon_vectors_mismatched ( & self ) {
419+ self . daemon_vectors_mismatched
420+ . store ( true , std:: sync:: atomic:: Ordering :: Relaxed ) ;
421+ }
422+
403423 /// Enable or disable full-body embedding mode.
404424 pub fn set_full_body_embedding ( & self , enabled : bool ) {
405425 self . full_body_embedding
@@ -1405,7 +1425,12 @@ impl QueryEngine {
14051425
14061426 let query_time_ms = start. elapsed ( ) . as_millis ( ) as u64 ;
14071427
1408- let embedding_status = if !self . are_embeddings_ready ( ) {
1428+ let embedding_status = if self
1429+ . daemon_vectors_mismatched
1430+ . load ( std:: sync:: atomic:: Ordering :: Relaxed )
1431+ {
1432+ Some ( DAEMON_VECTORS_MISMATCHED_STATUS . to_string ( ) )
1433+ } else if !self . are_embeddings_ready ( ) {
14091434 Some ( "Embeddings are building in the background. Semantic matching is temporarily unavailable — results are from name/text search only." . to_string ( ) )
14101435 } else {
14111436 None
@@ -3066,6 +3091,27 @@ mod tests {
30663091 assert_eq ! ( results. results[ 0 ] . symbol. name, "validateEmail" ) ;
30673092 }
30683093
3094+ #[ tokio:: test]
3095+ async fn symbol_search_status_names_mismatched_daemon_vectors_not_a_build ( ) {
3096+ let ( engine, _) = create_test_engine ( ) . await ;
3097+ let building = engine
3098+ . symbol_search ( "test" , & SearchOptions :: new ( ) )
3099+ . await
3100+ . embedding_status
3101+ . expect ( "no vectors yet" ) ;
3102+ assert ! ( building. contains( "building" ) ) ;
3103+
3104+ engine. set_daemon_vectors_mismatched ( ) ;
3105+ let status = engine
3106+ . symbol_search ( "test" , & SearchOptions :: new ( ) )
3107+ . await
3108+ . embedding_status
3109+ . expect ( "mismatched daemon vectors leave semantic search unavailable" ) ;
3110+ assert ! ( !status. contains( "building" ) , "{status}" ) ;
3111+ assert ! ( status. contains( "different embedding settings" ) , "{status}" ) ;
3112+ assert ! ( status. contains( "Restart the daemon" ) , "{status}" ) ;
3113+ }
3114+
30693115 #[ tokio:: test]
30703116 async fn test_symbol_search_with_type_filter ( ) {
30713117 let ( engine, graph) = create_test_engine ( ) . await ;
0 commit comments