lean_ctx/core/context_kernel/
ctx_read_dedup.rs1use super::dedup_wiring::{self, DedupAction};
4
5#[derive(Debug, Clone, Default, serde::Serialize)]
7pub struct ReadDedupSummary {
8 pub total_reads: usize,
10 pub dedup_hits: usize,
12 pub tokens_saved: usize,
14 pub hit_rate: f64,
16}
17
18#[must_use]
20pub fn should_dedup(mode: &str) -> bool {
21 super::kernel_config::is_enabled()
22 && super::kernel_config::is_feature_enabled("content_dedup")
23 && mode != "raw"
24}
25
26#[must_use]
32pub fn try_dedup(path: &str, content: &str) -> Option<String> {
33 if !super::kernel_config::is_enabled() {
34 return None;
35 }
36 match dedup_wiring::check_content(path, content) {
37 DedupAction::DeliverStub { stub } => Some(stub),
38 DedupAction::DeliverFull | DedupAction::DeliverModified => None,
39 }
40}
41pub fn on_file_write(path: &str) {
43 dedup_wiring::invalidate(path);
44}
45
46#[must_use]
48pub fn dedup_summary() -> ReadDedupSummary {
49 let stats = dedup_wiring::dedup_stats();
50 ReadDedupSummary {
51 total_reads: stats.total_checks,
52 dedup_hits: stats.cache_hits,
53 tokens_saved: stats.tokens_saved,
54 hit_rate: stats.hit_rate,
55 }
56}
57
58pub fn reset() {
60 dedup_wiring::reset_dedup();
61}
62
63#[cfg(test)]
64mod tests {
65 use std::sync::MutexGuard;
66
67 use super::{dedup_summary, on_file_write, reset, should_dedup, try_dedup};
68 use crate::core::context_kernel::kernel_config::{
69 KERNEL_TEST_LOCK, KernelFeatures, reset_features, update_features,
70 };
71
72 fn isolated() -> MutexGuard<'static, ()> {
73 let guard = KERNEL_TEST_LOCK
74 .lock()
75 .unwrap_or_else(std::sync::PoisonError::into_inner);
76 reset_features();
77 crate::core::context_kernel::dedup_wiring::reset_dedup();
78 reset();
79 guard
80 }
81
82 #[test]
83 fn should_dedup_respects_config() {
84 let _guard = isolated();
85 let features = KernelFeatures {
86 enabled: false,
87 ..KernelFeatures::default()
88 };
89 update_features(features);
90 assert!(!should_dedup("full"));
91 }
92
93 #[test]
94 fn should_dedup_raw_mode_false() {
95 let _guard = isolated();
96 assert!(!should_dedup("raw"));
97 assert!(should_dedup("full"));
98 }
99
100 #[test]
101 fn try_dedup_new_file_none() {
102 let _guard = isolated();
103 assert_eq!(try_dedup("new.rs", "content"), None);
104 }
105
106 #[test]
107 fn try_dedup_repeated_file_some() {
108 let _guard = isolated();
109 assert_eq!(try_dedup("repeat.rs", "content"), None);
110 let stub = try_dedup("repeat.rs", "content");
111 assert!(stub.is_some());
112 assert!(stub.is_some_and(|value| value.contains("repeat.rs unchanged")));
113 }
114
115 #[test]
116 fn on_write_invalidates() {
117 let _guard = isolated();
118 assert_eq!(try_dedup("written.rs", "content"), None);
119 assert!(try_dedup("written.rs", "content").is_some());
120 on_file_write("written.rs");
121 assert_eq!(try_dedup("written.rs", "content"), None);
122 }
123
124 #[test]
125 fn summary_tracks_reads() {
126 let _guard = isolated();
127 assert_eq!(try_dedup("a.rs", "one"), None);
128 assert!(try_dedup("a.rs", "one").is_some());
129 assert!(try_dedup("a.rs", "one").is_some());
130 assert_eq!(try_dedup("b.rs", "two"), None);
131 assert!(try_dedup("b.rs", "two").is_some());
132
133 let summary = dedup_summary();
134 assert_eq!(summary.total_reads, 5);
135 assert_eq!(summary.dedup_hits, 3);
136 assert!((summary.hit_rate - 0.6).abs() < f64::EPSILON);
137 }
138}