supercode-harness 0.4.81

The optional native Supercode agent and tool harness
Documentation
//! The portable residue's primary store: `$SUPERCODE_HOME/residue/<format>/<key>.json`, one
//! segment per chain key (`docs/plans/portable-residue.md`). Content-addressed, so a segment is
//! written once and every session sharing that conversation prefix reuses it.
//!
//! The store is a cache: restoration checks what it writes and otherwise renders, so pruning never
//! costs correctness. A lookup refreshes a segment's time; at most once a day a store drops the
//! segments unused for [`MAX_AGE`], then the least recently used until it is under [`MAX_BYTES`].

use std::path::{Path, PathBuf};
use std::time::{Duration, SystemTime};
use supercode_interchange::session::{ResidueSegment, Session, SessionFormat};

const MAX_AGE: Duration = Duration::from_secs(90 * 24 * 60 * 60);
const MAX_BYTES: u64 = 4 * 1024 * 1024 * 1024;
const PRUNE_EVERY: Duration = Duration::from_secs(24 * 60 * 60);

fn store_root() -> PathBuf {
    crate::agent::global_instructions_dir().join("residue")
}

/// A key's directory: one file per source session whose conversation reaches that key.
fn key_dir(format: SessionFormat, key: &str) -> PathBuf {
    store_root()
        .join(format!("{format:?}").to_lowercase())
        .join(key.get(..2).unwrap_or("00"))
        .join(key)
}

/// Keep `session`'s segments. Best effort: a store that cannot be written leaves translation
/// unaffected, and the restoring side then renders instead of restoring.
pub(crate) fn store_segments(session: &Session) {
    let segments = session.residue_segments();
    if segments.is_empty() {
        return;
    }
    prune_if_due(&store_root());
    for keyed in segments {
        let path = key_dir(keyed.format, &keyed.key).join(format!("{}.json", keyed.segment.source));
        if path.exists() {
            continue;
        }
        let Some(parent) = path.parent() else {
            continue;
        };
        let Ok(json) = serde_json::to_vec(&keyed.segment) else {
            continue;
        };
        let temp = path.with_extension("json.tmp");
        if std::fs::create_dir_all(parent).is_ok() && std::fs::write(&temp, json).is_ok() {
            let _ = std::fs::rename(&temp, &path);
        }
    }
}

pub(crate) fn lookup(format: SessionFormat, key: &str) -> Vec<ResidueSegment> {
    let Ok(entries) = std::fs::read_dir(key_dir(format, key)) else {
        return Vec::new();
    };
    let mut segments = Vec::new();
    for entry in entries.flatten() {
        let path = entry.path();
        if path.extension().and_then(|e| e.to_str()) != Some("json") {
            continue;
        }
        let Some(segment) = std::fs::read(&path)
            .ok()
            .and_then(|bytes| serde_json::from_slice::<ResidueSegment>(&bytes).ok())
        else {
            continue;
        };
        if let Ok(file) = std::fs::File::options().write(true).open(&path) {
            let _ = file.set_modified(SystemTime::now());
        }
        segments.push(segment);
    }
    segments
}

fn prune_if_due(root: &Path) {
    let marker = root.join(".pruned");
    let due = std::fs::metadata(&marker)
        .and_then(|metadata| metadata.modified())
        .map(|at| at.elapsed().map(|age| age >= PRUNE_EVERY).unwrap_or(true))
        .unwrap_or(true);
    if !due || std::fs::create_dir_all(root).is_err() || std::fs::write(&marker, b"").is_err() {
        return;
    }
    let mut segments = Vec::new();
    let mut pending = vec![root.to_path_buf()];
    while let Some(dir) = pending.pop() {
        let Ok(entries) = std::fs::read_dir(&dir) else {
            continue;
        };
        for entry in entries.flatten() {
            let path = entry.path();
            let Ok(metadata) = entry.metadata() else {
                continue;
            };
            if metadata.is_dir() {
                pending.push(path);
            } else if path.extension().and_then(|e| e.to_str()) == Some("json") {
                let modified = metadata.modified().unwrap_or(SystemTime::UNIX_EPOCH);
                segments.push((modified, metadata.len(), path));
            }
        }
    }
    segments.sort_by_key(|(modified, _, _)| *modified);
    let mut total: u64 = segments.iter().map(|(_, size, _)| size).sum();
    for (modified, size, path) in segments {
        let stale = modified.elapsed().map(|age| age > MAX_AGE).unwrap_or(false);
        if !stale && total <= MAX_BYTES {
            break;
        }
        if std::fs::remove_file(&path).is_ok() {
            total -= size;
        }
    }
}