@@ -325,51 +325,113 @@ impl TextSearcher {
325325 } ) ?;
326326
327327 let mut matches = Vec :: new ( ) ;
328+
329+ // Check if the entire file is test context based on path
330+ let is_test_file = Self :: is_test_file_path ( file_path) ;
331+
328332 let mut in_test_module = false ;
329- let mut brace_depth = 0 ;
333+ let mut test_module_depth: i32 = 0 ;
334+ let mut current_depth: i32 = 0 ;
335+ let mut next_fn_is_test = false ;
336+ let mut in_test_fn = false ;
337+ let mut test_fn_depth: i32 = 0 ;
330338
331339 for ( line_number, line) in content. lines ( ) . enumerate ( ) {
332340 let line_number = line_number + 1 ; // Convert to 1-indexed
341+ let trimmed = line. trim ( ) ;
333342
334- // Track if we're in a test module
335- if line. contains ( "#[cfg(test)]" ) || line. contains ( "mod tests" ) {
343+ // Track #[cfg(test)] / mod tests blocks
344+ if trimmed. contains ( "#[cfg(test)]" )
345+ || ( trimmed. starts_with ( "mod tests" ) && !trimmed. starts_with ( "mod tests_" ) )
346+ {
336347 in_test_module = true ;
337- brace_depth = 0 ;
348+ test_module_depth = current_depth ;
338349 }
339350
340- // Track brace depth to know when we exit test modules
341- brace_depth += line. chars ( ) . filter ( |& c| c == '{' ) . count ( ) as i32 ;
342- brace_depth -= line. chars ( ) . filter ( |& c| c == '}' ) . count ( ) as i32 ;
351+ // Track #[test] attribute for next function
352+ if trimmed == "#[test]" || trimmed. starts_with ( "#[test]" )
353+ || trimmed == "#[tokio::test]" || trimmed. starts_with ( "#[tokio::test]" )
354+ {
355+ next_fn_is_test = true ;
356+ }
357+
358+ // Track function starts after #[test]
359+ if next_fn_is_test && ( trimmed. starts_with ( "fn " ) || trimmed. starts_with ( "pub fn " )
360+ || trimmed. starts_with ( "async fn " ) || trimmed. starts_with ( "pub async fn " ) )
361+ {
362+ in_test_fn = true ;
363+ test_fn_depth = current_depth;
364+ next_fn_is_test = false ;
365+ }
343366
344- if in_test_module && brace_depth <= 0 {
367+ // Track brace depth
368+ let opens = line. chars ( ) . filter ( |& c| c == '{' ) . count ( ) as i32 ;
369+ let closes = line. chars ( ) . filter ( |& c| c == '}' ) . count ( ) as i32 ;
370+ current_depth += opens - closes;
371+
372+ // Exit test module when we return to its entry depth
373+ if in_test_module && current_depth <= test_module_depth {
345374 in_test_module = false ;
346375 }
347376
377+ // Exit test function when we return to its entry depth
378+ if in_test_fn && current_depth <= test_fn_depth {
379+ in_test_fn = false ;
380+ }
381+
348382 // Check if line matches pattern
349383 if regex. is_match ( line) {
350384 matches. push ( SearchMatch {
351385 file_path : file_path. to_path_buf ( ) ,
352386 line_number,
353387 line_content : line. to_string ( ) ,
354388 is_comment : self . is_comment_line ( line) ,
355- is_test_context : in_test_module || self . is_test_function ( line ) ,
389+ is_test_context : is_test_file || in_test_module || in_test_fn ,
356390 } ) ;
357391 }
358392 }
359393
360394 Ok ( matches)
361395 }
362396
397+ /// Check if a file path indicates non-production code (tests, examples, benches, build tools)
398+ fn is_test_file_path ( path : & Path ) -> bool {
399+ let path_str = path. to_string_lossy ( ) ;
400+
401+ // Files in tests/, examples/, benches/ directories
402+ if path_str. contains ( "/tests/" ) || path_str. contains ( "/test/" )
403+ || path_str. contains ( "/examples/" ) || path_str. contains ( "/benches/" )
404+ {
405+ return true ;
406+ }
407+
408+ // Build tool crates are not safety-critical runtime code
409+ if path_str. contains ( "/cargo-kiln/" ) || path_str. contains ( "/kiln-build-core/" ) {
410+ return true ;
411+ }
412+
413+ // Daemon crate (server binary, not embedded runtime)
414+ if path_str. contains ( "/kilnd/" ) {
415+ return true ;
416+ }
417+
418+ // Test helper files (suffix-based detection)
419+ if let Some ( name) = path. file_name ( ) . and_then ( |n| n. to_str ( ) ) {
420+ if name. ends_with ( "_test.rs" ) || name. ends_with ( "_tests.rs" )
421+ || name == "test_lib.rs" || name == "test_utils.rs"
422+ {
423+ return true ;
424+ }
425+ }
426+
427+ false
428+ }
429+
363430 /// Check if a line is a comment
364431 fn is_comment_line ( & self , line : & str ) -> bool {
365432 let trimmed = line. trim ( ) ;
366433 trimmed. starts_with ( "//" ) || trimmed. starts_with ( "/*" ) || trimmed. starts_with ( "*" )
367434 }
368-
369- /// Check if a line is in a test function context
370- fn is_test_function ( & self , line : & str ) -> bool {
371- line. contains ( "#[test]" ) || line. contains ( "#[cfg(test)]" )
372- }
373435}
374436
375437/// Count matches from search results
@@ -382,6 +444,28 @@ pub fn count_production_matches(matches: &[SearchMatch]) -> usize {
382444 matches. iter ( ) . filter ( |m| !m. is_comment && !m. is_test_context ) . count ( )
383445}
384446
447+ /// Format only production (non-test, non-comment) matches for display
448+ pub fn format_production_matches ( matches : & [ SearchMatch ] , max_display : usize ) -> String {
449+ let production: Vec < _ > = matches
450+ . iter ( )
451+ . filter ( |m| !m. is_comment && !m. is_test_context )
452+ . collect ( ) ;
453+ let mut output = String :: new ( ) ;
454+ for ( i, m) in production. iter ( ) . take ( max_display) . enumerate ( ) {
455+ output. push_str ( & format ! (
456+ " {}:{}:{}\n " ,
457+ m. file_path. display( ) ,
458+ m. line_number,
459+ m. line_content. trim( )
460+ ) ) ;
461+ if i >= max_display - 1 && production. len ( ) > max_display {
462+ output. push_str ( & format ! ( " ... and {} more\n " , production. len( ) - max_display) ) ;
463+ break ;
464+ }
465+ }
466+ output
467+ }
468+
385469/// Format search results for display
386470pub fn format_matches ( matches : & [ SearchMatch ] , max_display : Option < usize > ) -> String {
387471 let mut output = String :: new ( ) ;
0 commit comments