Skip to main content

lean_ctx/tools/
search_hook.rs

1//! Search hot-path bridge for kernel evidence and repeat detection.
2
3use std::sync::atomic::{AtomicUsize, Ordering};
4
5use crate::core::context_kernel::kernel_config;
6use crate::tools::search_kernel;
7
8static SEARCHES_RECORDED: AtomicUsize = AtomicUsize::new(0);
9static REPEATED_WARNED: AtomicUsize = AtomicUsize::new(0);
10
11/// Cumulative search-hook activity for the current session.
12#[derive(Debug, Clone, Default, serde::Serialize)]
13pub struct SearchHookSummary {
14    /// Number of searches forwarded to the search kernel.
15    pub searches_recorded: usize,
16    /// Number of repeated-query warnings emitted.
17    pub repeated_queries_warned: usize,
18}
19
20/// Records one search in the kernel when kernel processing is enabled.
21pub fn on_search(query: &str, action: &str, result_count: usize, tokens: usize) {
22    if !kernel_config::is_enabled() {
23        return;
24    }
25    let _ = action;
26    search_kernel::record_search(query, result_count, tokens);
27    SEARCHES_RECORDED.fetch_add(1, Ordering::Relaxed);
28}
29
30/// Returns a warning when the query has already been recorded.
31#[must_use]
32pub fn maybe_warn_repeat(query: &str) -> Option<String> {
33    if !kernel_config::is_enabled() || !search_kernel::is_repeated_query(query) {
34        return None;
35    }
36    REPEATED_WARNED.fetch_add(1, Ordering::Relaxed);
37    Some("⚠ Repeated query detected; consider refining it or reusing prior results.".to_owned())
38}
39
40/// Returns cumulative search-hook activity.
41#[must_use]
42pub fn summary() -> SearchHookSummary {
43    SearchHookSummary {
44        searches_recorded: SEARCHES_RECORDED.load(Ordering::Relaxed),
45        repeated_queries_warned: REPEATED_WARNED.load(Ordering::Relaxed),
46    }
47}
48
49/// Clears hook counters and delegated search-kernel state.
50pub fn reset() {
51    SEARCHES_RECORDED.store(0, Ordering::Relaxed);
52    REPEATED_WARNED.store(0, Ordering::Relaxed);
53    search_kernel::reset();
54}
55
56#[cfg(test)]
57mod tests {
58    use super::{maybe_warn_repeat, on_search, reset, summary};
59    use crate::core::context_kernel::kernel_config::{self, KERNEL_TEST_LOCK, KernelFeatures};
60
61    fn isolated() -> std::sync::MutexGuard<'static, ()> {
62        let guard = KERNEL_TEST_LOCK
63            .lock()
64            .unwrap_or_else(std::sync::PoisonError::into_inner);
65        kernel_config::reset_features();
66        reset();
67        guard
68    }
69
70    #[test]
71    fn records_search() {
72        let _guard = isolated();
73        for _ in 0..3 {
74            on_search("query", "regex", 2, 10);
75        }
76        assert_eq!(summary().searches_recorded, 3);
77    }
78
79    #[test]
80    fn detects_repeat() {
81        let _guard = isolated();
82        on_search("same", "regex", 1, 10);
83        on_search("same", "regex", 1, 10);
84        assert!(maybe_warn_repeat("same").is_some());
85        assert_eq!(summary().repeated_queries_warned, 1);
86    }
87
88    #[test]
89    fn disabled_kernel_noop() {
90        let _guard = isolated();
91        let features = KernelFeatures {
92            enabled: false,
93            ..KernelFeatures::default()
94        };
95        kernel_config::update_features(features);
96        on_search("same", "regex", 1, 10);
97        assert!(maybe_warn_repeat("same").is_none());
98        assert_eq!(summary().searches_recorded, 0);
99        assert_eq!(summary().repeated_queries_warned, 0);
100        kernel_config::reset_features();
101    }
102}