Skip to main content

ralph/session/
persistence.rs

1//! Session file persistence helpers.
2//!
3//! Responsibilities:
4//! - Read, write, clear, and locate `.ralph/cache/session.jsonc`.
5//! - Resolve git HEAD metadata used by session tracking.
6//!
7//! Not handled here:
8//! - Session validation logic.
9//! - Interactive recovery prompts.
10//! - Loop-progress mutation.
11//!
12//! Invariants/assumptions:
13//! - Session files are written atomically.
14//! - Forward-version session files log a warning and still attempt to load.
15
16use std::path::{Path, PathBuf};
17
18use anyhow::{Context, Result};
19
20use crate::constants::paths::SESSION_FILENAME;
21use crate::contracts::SessionState;
22use crate::fsutil;
23use crate::git::error::git_head_commit;
24
25/// Get the path to the session file.
26pub fn session_path(cache_dir: &Path) -> PathBuf {
27    cache_dir.join(SESSION_FILENAME)
28}
29
30/// Check if a session file exists.
31pub fn session_exists(cache_dir: &Path) -> bool {
32    session_path(cache_dir).exists()
33}
34
35/// Save session state to disk.
36pub fn save_session(cache_dir: &Path, session: &SessionState) -> Result<()> {
37    let path = session_path(cache_dir);
38    let json = serde_json::to_string_pretty(session).context("serialize session state")?;
39    fsutil::write_atomic(&path, json.as_bytes()).context("write session file")?;
40    log::debug!("Session saved: task_id={}", session.task_id);
41    Ok(())
42}
43
44/// Load session state from disk.
45pub fn load_session(cache_dir: &Path) -> Result<Option<SessionState>> {
46    let path = session_path(cache_dir);
47    if !path.exists() {
48        return Ok(None);
49    }
50
51    let content = std::fs::read_to_string(&path).context("read session file")?;
52    let session: SessionState = serde_json::from_str(&content).context("parse session file")?;
53
54    if session.version > crate::contracts::SESSION_STATE_VERSION {
55        log::warn!(
56            "Session file version {} is newer than supported version {}. Attempting to load anyway.",
57            session.version,
58            crate::contracts::SESSION_STATE_VERSION
59        );
60    }
61
62    Ok(Some(session))
63}
64
65/// Clear (delete) the session file.
66pub fn clear_session(cache_dir: &Path) -> Result<()> {
67    let path = session_path(cache_dir);
68    if path.exists() {
69        std::fs::remove_file(&path).context("remove session file")?;
70        log::debug!("Session cleared");
71    }
72    Ok(())
73}
74
75/// Get the git HEAD commit hash for session tracking.
76pub fn get_git_head_commit(repo_root: &Path) -> Option<String> {
77    git_head_commit(repo_root).ok()
78}