use std::path::Path;
pub use crate::tools::ctx_read::ReadOutput;
pub use crate::tools::ctx_search::SearchOutcome;
pub use lean_ctx::core::protocol::CrpMode;
pub struct OperatorCache {
inner: lean_ctx::core::cache::SessionCache,
}
impl Default for OperatorCache {
fn default() -> Self {
Self::new()
}
}
impl OperatorCache {
pub fn new() -> Self {
Self {
inner: lean_ctx::core::cache::SessionCache::new(),
}
}
pub fn clear(&mut self) -> usize {
self.inner.clear()
}
}
#[derive(Debug, Clone, Copy)]
pub struct ReadRequest<'a> {
pub path: &'a Path,
pub project_root: &'a Path,
pub mode: &'a str,
pub fresh: bool,
pub crp_mode: CrpMode,
pub task: Option<&'a str>,
pub aggressiveness: Option<f64>,
pub protect: &'a [String],
}
pub fn read(
cache: &mut OperatorCache,
request: ReadRequest<'_>,
) -> Result<ReadOutput, lean_ctx::core::error::PathJailError> {
let path = lean_ctx::core::pathjail::jail_path(request.path, request.project_root)?;
let path = path.to_string_lossy();
let output = if request.fresh {
crate::tools::ctx_read::handle_fresh_with_task_resolved_tuned(
&mut cache.inner,
&path,
request.mode,
request.crp_mode,
request.task,
request.aggressiveness,
request.protect,
)
} else {
crate::tools::ctx_read::handle_with_task_resolved_tuned(
&mut cache.inner,
&path,
request.mode,
request.crp_mode,
request.task,
request.aggressiveness,
request.protect,
)
};
Ok(output)
}
#[derive(Debug, Clone, Copy)]
pub struct SearchRequest<'a> {
pub pattern: &'a str,
pub directory: &'a Path,
pub project_root: &'a Path,
pub include: Option<&'a str>,
pub max_results: usize,
pub crp_mode: CrpMode,
pub respect_gitignore: bool,
pub allow_secret_paths: bool,
pub anchored: bool,
pub exclude: Option<&'a str>,
pub exclude_pattern: Option<&'a str>,
}
pub fn search(
request: SearchRequest<'_>,
) -> Result<SearchOutcome, lean_ctx::core::error::PathJailError> {
let directory = lean_ctx::core::pathjail::jail_path(request.directory, request.project_root)?;
let directory = directory.to_string_lossy();
Ok(crate::tools::ctx_search::handle_filtered(
request.pattern,
&directory,
request.include,
request.max_results,
request.crp_mode,
request.respect_gitignore,
request.allow_secret_paths,
request.anchored,
request.exclude,
request.exclude_pattern,
))
}
pub fn compress_shell_output(command: &str, output: &str, exit_code: i32) -> String {
lean_ctx::shell::compress::engine::compress_for_outcome(command, output, exit_code)
}
pub fn redact_output(input: &str) -> String {
lean_ctx::core::redaction::redact_text(input)
}
pub fn render_shared_signatures(content: &str, file_ext: &str, tdd: bool) -> String {
let (signatures, _) =
lean_ctx::core::signatures::extract_signatures_with_backend(content, file_ext);
signatures
.iter()
.map(|signature| {
if tdd {
signature.to_tdd_located()
} else {
signature.to_compact_located()
}
})
.collect::<Vec<_>>()
.join("\n")
}
#[cfg(test)]
mod tests {
use super::*;
fn assert_read_eq(actual: &ReadOutput, expected: &ReadOutput) {
assert_eq!(actual.content, expected.content);
assert_eq!(actual.resolved_mode, expected.resolved_mode);
assert_eq!(actual.output_tokens, expected.output_tokens);
assert_eq!(actual.is_cache_hit, expected.is_cache_hit);
}
#[test]
fn read_boundary_matches_existing_operator() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("sample.rs");
std::fs::write(&path, "pub fn answer() -> u32 { 42 }\n").unwrap();
let request = ReadRequest {
path: &path,
project_root: dir.path(),
mode: "signatures",
fresh: false,
crp_mode: CrpMode::Off,
task: None,
aggressiveness: None,
protect: &[],
};
let mut boundary_cache = OperatorCache::new();
let mut direct_cache = lean_ctx::core::cache::SessionCache::new();
let actual = read(&mut boundary_cache, request).unwrap();
let path = lean_ctx::core::pathjail::jail_path(request.path, request.project_root).unwrap();
let path = path.to_string_lossy();
let expected = crate::tools::ctx_read::handle_with_task_resolved_tuned(
&mut direct_cache,
&path,
request.mode,
request.crp_mode,
request.task,
request.aggressiveness,
request.protect,
);
assert_read_eq(&actual, &expected);
}
#[test]
fn fresh_read_boundary_matches_existing_operator() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("fresh.txt");
std::fs::write(&path, "alpha\nbeta\n").unwrap();
let request = ReadRequest {
path: &path,
project_root: dir.path(),
mode: "full",
fresh: true,
crp_mode: CrpMode::Off,
task: Some("alpha"),
aggressiveness: None,
protect: &[],
};
let mut boundary_cache = OperatorCache::new();
let mut direct_cache = lean_ctx::core::cache::SessionCache::new();
let actual = read(&mut boundary_cache, request).unwrap();
let path = lean_ctx::core::pathjail::jail_path(request.path, request.project_root).unwrap();
let path = path.to_string_lossy();
let expected = crate::tools::ctx_read::handle_fresh_with_task_resolved_tuned(
&mut direct_cache,
&path,
request.mode,
request.crp_mode,
request.task,
request.aggressiveness,
request.protect,
);
assert_read_eq(&actual, &expected);
}
#[test]
fn search_boundary_matches_existing_operator() {
let _no_policy = lean_ctx::core::policy::runtime::TestPolicyOverride::set(None);
let dir = tempfile::tempdir().unwrap();
std::fs::write(dir.path().join("sample.txt"), "alpha needle omega\n").unwrap();
let request = SearchRequest {
pattern: "needle",
directory: dir.path(),
project_root: dir.path(),
include: Some("*.txt"),
max_results: 10,
crp_mode: CrpMode::Off,
respect_gitignore: true,
allow_secret_paths: false,
anchored: false,
exclude: None,
exclude_pattern: None,
};
let actual = search(request).unwrap();
let directory =
lean_ctx::core::pathjail::jail_path(request.directory, request.project_root).unwrap();
let directory = directory.to_string_lossy();
let expected = crate::tools::ctx_search::handle_filtered(
request.pattern,
&directory,
request.include,
request.max_results,
request.crp_mode,
request.respect_gitignore,
request.allow_secret_paths,
request.anchored,
request.exclude,
request.exclude_pattern,
);
assert_eq!(actual.text, expected.text);
assert_eq!(actual.modeled_baseline, expected.modeled_baseline);
assert_eq!(actual.observed_tokens, expected.observed_tokens);
}
#[test]
fn shell_boundary_matches_existing_operator() {
let output = "running 2 tests\ntest alpha ... ok\ntest beta ... ok\ntest result: ok. 2 passed; 0 failed\n";
assert_eq!(
compress_shell_output("cargo test", output, 0),
lean_ctx::shell::compress::engine::compress_for_outcome("cargo test", output, 0)
);
}
#[test]
fn failed_shell_boundary_matches_existing_operator() {
let output = "step 1 ok\nstep 2 ok\nerror: deployment failed\n";
assert_eq!(
compress_shell_output("./deploy.sh", output, 13),
lean_ctx::shell::compress::engine::compress_for_outcome("./deploy.sh", output, 13)
);
}
#[test]
fn redaction_boundary_matches_existing_operator() {
let input = "Authorization: Bearer abcdefghijklmnopqrstuvwxyz";
let actual = redact_output(input);
assert_eq!(actual, lean_ctx::core::redaction::redact_text(input));
assert!(!actual.contains("abcdefghijklmnopqrstuvwxyz"));
}
#[test]
fn cache_boundary_reuses_session_cache_type() {
let mut cache = OperatorCache::new();
cache
.inner
.store("virtual.txt", "same cache implementation");
assert_eq!(cache.clear(), 1);
assert!(cache.inner.get_full_content("virtual.txt").is_none());
}
#[test]
fn shared_signature_operator_matches_local_extractor() {
let source = "pub fn answer() -> u32 { 42 }\nfn detail() {}\n";
let expected = lean_ctx::core::signatures::extract_signatures(source, "rs")
.iter()
.map(lean_ctx::core::signatures::Signature::to_compact_located)
.collect::<Vec<_>>()
.join("\n");
assert_eq!(render_shared_signatures(source, "rs", false), expected);
}
#[test]
#[cfg(not(feature = "no-jail"))]
fn read_boundary_rejects_paths_outside_project_root() {
let project = tempfile::tempdir().unwrap();
let outside = tempfile::NamedTempFile::new().unwrap();
let mut cache = OperatorCache::new();
let result = read(
&mut cache,
ReadRequest {
path: outside.path(),
project_root: project.path(),
mode: "full",
fresh: false,
crp_mode: CrpMode::Off,
task: None,
aggressiveness: None,
protect: &[],
},
);
assert!(result.is_err());
}
#[test]
#[cfg(not(feature = "no-jail"))]
fn search_boundary_rejects_directories_outside_project_root() {
let project = tempfile::tempdir().unwrap();
let outside = tempfile::tempdir().unwrap();
let result = search(SearchRequest {
pattern: "needle",
directory: outside.path(),
project_root: project.path(),
include: None,
max_results: 10,
crp_mode: CrpMode::Off,
respect_gitignore: true,
allow_secret_paths: false,
anchored: false,
exclude: None,
exclude_pattern: None,
});
assert!(result.is_err());
}
}