Skip to main content

lean_ctx/core/
search_hook.rs

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