Skip to main content

st/file_history/
mod.rs

1//! File History Tracking Module - The Ultimate Context-Driven System!
2//!
3//! Tracks all AI file manipulations in ~/.mem8/.filehistory/
4//! with hash-based change detection and append-first operations.
5//!
6//! 🎸 The Cheet says: "Every file tells a story, let's remember them all!"
7
8use anyhow::Result;
9use chrono::{DateTime, Datelike, Timelike, Utc};
10use serde::{Deserialize, Serialize};
11use sha2::{Digest, Sha256};
12use std::fs::{self, OpenOptions};
13use std::io::Write;
14use std::path::{Path, PathBuf};
15use std::time::{SystemTime, UNIX_EPOCH};
16
17pub mod operations;
18pub mod tracker;
19
20pub use operations::*;
21pub use tracker::*;
22
23/// File history configuration
24pub struct FileHistoryConfig {
25    /// Base directory for file history (default: ~/.mem8/.filehistory/)
26    pub base_dir: PathBuf,
27    /// Whether to auto-create directories
28    pub auto_create: bool,
29    /// Prefer append operations when possible
30    pub prefer_append: bool,
31}
32
33impl Default for FileHistoryConfig {
34    fn default() -> Self {
35        let cwd = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
36        Self {
37            base_dir: cwd.join(".st").join("filehistory"),
38            auto_create: true,
39            prefer_append: true,
40        }
41    }
42}
43
44/// Get timestamp at 10-minute resolution
45pub fn get_time_bucket() -> (String, u64) {
46    let now = SystemTime::now()
47        .duration_since(UNIX_EPOCH)
48        .unwrap()
49        .as_secs();
50
51    let datetime = DateTime::<Utc>::from(UNIX_EPOCH + std::time::Duration::from_secs(now));
52    let minute = datetime.minute() / 10 * 10; // Round down to 10-minute bucket
53
54    let filename = format!(
55        "{:04}{:02}{:02}_{:02}{:02}",
56        datetime.year(),
57        datetime.month(),
58        datetime.day(),
59        datetime.hour(),
60        minute
61    );
62
63    (filename, now)
64}
65
66/// Calculate SHA256 hash of file contents
67pub fn hash_file(path: &Path) -> Result<String> {
68    let contents = fs::read(path)?;
69    let mut hasher = Sha256::new();
70    hasher.update(&contents);
71    Ok(format!("{:x}", hasher.finalize()))
72}
73
74/// Get project ID from path (uses canonical path as ID)
75pub fn get_project_id(path: &Path) -> Result<String> {
76    let canonical = path.canonicalize()?;
77    let mut hasher = Sha256::new();
78    hasher.update(canonical.to_string_lossy().as_bytes());
79    Ok(format!("{:x}", hasher.finalize())[..16].to_string())
80}