claude-utils 0.2.0

Cross-platform companion toolkit for Anthropic's Claude Code CLI
Documentation
use chrono::Utc;
use std::collections::VecDeque;
use std::path::PathBuf;
use tokio::fs;
use tracing::{error, info};

use super::rollback::Checkpoint;

const CHECKPOINT_FILE: &str = "checkpoints.json";

pub struct CheckpointStore {
    storage_dir: PathBuf,
}

impl Default for CheckpointStore {
    fn default() -> Self {
        Self::new()
    }
}

impl CheckpointStore {
    pub fn new() -> Self {
        let storage_dir = dirs::data_dir()
            .unwrap_or_else(|| PathBuf::from("."))
            .join("claude-utils")
            .join("turbo");
        
        Self { storage_dir }
    }

    async fn ensure_storage_dir(&self) -> crate::Result<()> {
        fs::create_dir_all(&self.storage_dir).await?;
        Ok(())
    }

    fn checkpoint_file(&self) -> PathBuf {
        self.storage_dir.join(CHECKPOINT_FILE)
    }

    pub async fn load_checkpoints(&self) -> crate::Result<VecDeque<Checkpoint>> {
        self.ensure_storage_dir().await?;
        
        let file_path = self.checkpoint_file();
        if !file_path.exists() {
            return Ok(VecDeque::new());
        }

        let content = fs::read_to_string(&file_path).await?;
        let checkpoints: VecDeque<Checkpoint> = serde_json::from_str(&content)
            .unwrap_or_else(|e| {
                error!("Failed to parse checkpoints: {}", e);
                VecDeque::new()
            });

        Ok(checkpoints)
    }

    pub async fn save_checkpoints(&self, checkpoints: &VecDeque<Checkpoint>) -> crate::Result<()> {
        self.ensure_storage_dir().await?;
        
        let content = serde_json::to_string_pretty(checkpoints)?;
        fs::write(self.checkpoint_file(), content).await?;
        
        info!("Saved {} checkpoints to disk", checkpoints.len());
        Ok(())
    }

    pub async fn clear_old_checkpoints(&self, max_age_days: i64) -> crate::Result<()> {
        let mut checkpoints = self.load_checkpoints().await?;
        let cutoff = Utc::now() - chrono::Duration::days(max_age_days);
        
        let original_count = checkpoints.len();
        checkpoints.retain(|cp| cp.timestamp > cutoff);
        
        if checkpoints.len() < original_count {
            info!("Removed {} old checkpoints", original_count - checkpoints.len());
            self.save_checkpoints(&checkpoints).await?;
        }
        
        Ok(())
    }
}