lean_ctx/core/context_prefetch/
preloader.rs1use super::trajectory::FileTrajectory;
7
8#[derive(Debug, Clone)]
10pub struct PrefetchPlan {
11 pub files: Vec<PrefetchEntry>,
13 pub total_predicted_tokens: usize,
15}
16
17#[derive(Debug, Clone)]
19pub struct PrefetchEntry {
20 pub path: String,
22 pub confidence: f64,
24 pub reason: &'static str,
26}
27
28pub(crate) fn build_prefetch_plan(
33 trajectory: &FileTrajectory,
34 loaded_files: &[&str],
35 max_files: usize,
36 min_confidence: f64,
37) -> PrefetchPlan {
38 let files: Vec<PrefetchEntry> = trajectory
39 .predict(max_files.saturating_add(loaded_files.len()))
40 .into_iter()
41 .filter(|(path, confidence)| {
42 *confidence > min_confidence && !loaded_files.contains(&path.as_str())
43 })
44 .take(max_files)
45 .map(|(path, confidence)| PrefetchEntry {
46 path,
47 confidence,
48 reason: "trajectory transition",
49 })
50 .collect();
51 let total_predicted_tokens = 0;
53
54 PrefetchPlan {
55 files,
56 total_predicted_tokens,
57 }
58}
59
60#[cfg(test)]
61mod tests {
62 use super::*;
63
64 #[test]
65 fn empty_trajectory_gives_empty_plan() {
66 let plan = build_prefetch_plan(&FileTrajectory::new(10), &[], 3, 0.2);
67 assert!(plan.files.is_empty());
68 assert_eq!(plan.total_predicted_tokens, 0);
69 }
70
71 #[test]
72 fn loaded_files_are_excluded() {
73 let mut trajectory = FileTrajectory::new(10);
74 for path in ["src/a.rs", "src/b.rs", "src/a.rs"] {
75 trajectory.record(path);
76 }
77
78 let plan = build_prefetch_plan(&trajectory, &["src/b.rs"], 3, 0.2);
79 assert!(plan.files.is_empty());
80 }
81
82 #[test]
83 fn low_confidence_filtered() {
84 let mut trajectory = FileTrajectory::new(10);
85 for path in ["src/a.rs", "src/b.rs", "src/a.rs", "src/c.rs", "src/a.rs"] {
86 trajectory.record(path);
87 }
88
89 let plan = build_prefetch_plan(&trajectory, &[], 3, 0.5);
90 assert!(plan.files.is_empty());
91 }
92
93 #[test]
94 fn selected_files_have_zero_token_estimate_until_wired() {
95 let mut trajectory = FileTrajectory::new(10);
96 for path in ["src/a.rs", "src/b.rs", "src/a.rs"] {
97 trajectory.record(path);
98 }
99
100 let plan = build_prefetch_plan(&trajectory, &[], 1, 0.2);
101 assert_eq!(plan.files.len(), 1);
102 assert_eq!(plan.total_predicted_tokens, 0);
103 }
104}