use crate::services::cache::{
config::CacheConfig,
diagnostics::{CacheDiagnostics, CacheEffectiveness, CacheStatsSnapshot},
persistent::PersistentCache,
strategies::AstCacheStrategy,
};
use crate::services::context::FileContext;
use anyhow::Result;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use std::time::Instant;
use uuid::Uuid;
pub struct PersistentCacheManager {
ast_cache: Arc<PersistentCache<AstCacheStrategy>>,
config: CacheConfig,
session_id: Uuid,
created: Instant,
#[allow(dead_code)]
cache_dir: PathBuf,
}
impl PersistentCacheManager {
pub fn new(config: CacheConfig, cache_dir: PathBuf) -> Result<Self> {
let ast_cache_dir = cache_dir.join("ast");
Ok(Self {
ast_cache: Arc::new(PersistentCache::new(AstCacheStrategy, ast_cache_dir)?),
config,
session_id: Uuid::new_v4(),
created: Instant::now(),
cache_dir,
})
}
pub fn with_default_dir(config: CacheConfig) -> Result<Self> {
let cache_dir = Self::default_cache_dir()?;
Self::new(config, cache_dir)
}
pub fn default_cache_dir() -> Result<PathBuf> {
if let Some(cache_dir) = dirs::cache_dir() {
Ok(cache_dir.join("paiml-mcp-agent-toolkit"))
} else if let Some(home_dir) = dirs::home_dir() {
Ok(home_dir.join(".cache").join("paiml-mcp-agent-toolkit"))
} else {
Ok(PathBuf::from("/tmp/paiml-mcp-agent-toolkit-cache"))
}
}
pub async fn get_or_compute_ast<F, Fut>(
&self,
path: &Path,
compute: F,
) -> Result<Arc<FileContext>>
where
F: FnOnce() -> Fut,
Fut: std::future::Future<Output = Result<FileContext>>,
{
let path_buf = path.to_path_buf();
if let Some(ast) = self.ast_cache.get(&path_buf) {
return Ok(ast);
}
let ast = compute().await?;
let _ = self.ast_cache.put(path_buf, ast.clone());
Ok(Arc::new(ast))
}
pub fn cleanup_expired(&self) {
self.ast_cache.cleanup_expired();
}
pub fn clear(&self) {
let _ = self.ast_cache.clear();
}
#[must_use]
pub fn get_diagnostics(&self) -> CacheDiagnostics {
let uptime = self.created.elapsed();
let ast_size = self.ast_cache.stats.memory_usage();
let memory_usage_mb = ast_size as f64 / (1024.0 * 1024.0);
let memory_pressure = if self.config.max_memory_mb > 0 {
(memory_usage_mb / self.config.max_memory_mb as f64).min(1.0) as f32
} else {
0.0
};
if memory_pressure > 0.8 {
self.ast_cache.cleanup_expired();
}
let cache_stats = vec![(
"ast".to_string(),
CacheStatsSnapshot::from((&self.ast_cache.stats, self.ast_cache.len())),
)];
let total_operations = cache_stats
.iter()
.map(|(_, stats)| stats.hits + stats.misses)
.sum::<u64>();
let total_hits = cache_stats.iter().map(|(_, stats)| stats.hits).sum::<u64>();
let overall_hit_rate = if total_operations > 0 {
total_hits as f64 / total_operations as f64
} else {
0.0
};
let memory_efficiency = 1.0 - f64::from(memory_pressure);
let time_saved_ms = total_hits * 100;
let most_valuable_caches = vec![("ast".to_string(), total_hits as f64)];
let effectiveness = CacheEffectiveness {
overall_hit_rate,
memory_efficiency,
time_saved_ms,
most_valuable_caches,
};
CacheDiagnostics {
session_id: self.session_id,
uptime,
memory_usage_mb,
memory_pressure,
cache_stats,
hot_paths: Vec::new(), effectiveness,
}
}
}
#[cfg(test)]
mod tests {
#[test]
fn test_persistent_manager_basic() {
assert_eq!(1 + 1, 2);
}
}
#[cfg(test)]
mod property_tests {
use proptest::prelude::*;
proptest! {
#[test]
fn basic_property_stability(_input in ".*") {
prop_assert!(true);
}
#[test]
fn module_consistency_check(_x in 0u32..1000) {
prop_assert!(_x < 1001);
}
}
}