#![allow(
dead_code,
unused_imports,
reason = "Intentional compatibility, platform, or test-only suppression."
)]
#![expect(
unused_results,
clippy::let_underscore_must_use,
clippy::cast_possible_truncation,
clippy::cast_possible_wrap,
clippy::string_slice,
reason = "The memory store uses compact persisted counters, bounded timestamp conversions, and side-effect-only index maintenance."
)]
pub mod error;
pub mod event_log;
pub mod manifest;
pub mod migration;
pub mod progress;
pub mod query;
pub mod retention;
pub use error::SessionStoreError;
pub use event_log::{DEFAULT_MAX_EVENTS, SessionEventLog, SessionManifest, TurnIndex, TurnIndexEntry};
pub use migration::{MigrationReport, migrate_legacy};
pub use progress::{
GoalClassifierVerdict, GoalEvent, GoalHistoryEntry, GoalOrchestration, GoalPauseReason, GoalPhase, GoalStatus,
GoalTracker, Milestone, MilestoneStatus, ProgressLedger, load_progress, progress_path, save_progress,
};
pub use query::{FactRecord, MemorySearchResult, SessionSummary, query_facts, recent_sessions, search_memory};
pub use retention::{RetentionPolicy, apply_retention, apply_retention_preserving, gc_legacy};
use std::path::{Path, PathBuf};
const SESSIONS_DIR: &str = ".vtcode/sessions";
const DERIVED_DIR: &str = "derived";
const SESSION_STORE_SCHEMA_VERSION: u32 = 1;
#[must_use]
pub(crate) fn sessions_root(workspace: &Path) -> PathBuf {
workspace.join(SESSIONS_DIR)
}
#[must_use]
pub(crate) fn session_dir(workspace: &Path, session_id: &str) -> PathBuf {
sessions_root(workspace).join(sanitize_id(session_id))
}
#[must_use]
pub fn session_directory(workspace: &Path, session_id: &str) -> PathBuf {
session_dir(workspace, session_id)
}
pub fn open(workspace: &Path, session_id: &str, max_events: usize) -> Result<SessionEventLog, SessionStoreError> {
SessionEventLog::open(workspace, session_id, max_events)
}
fn sanitize_id(id: &str) -> String {
let mut out = String::with_capacity(id.len());
for c in id.chars() {
if c.is_ascii_alphanumeric() || c == '-' || c == '_' || c == '.' {
out.push(c);
} else {
out.push('_');
}
}
let out = out.trim_start_matches('.').to_string();
if out.is_empty() { "session".to_string() } else { out }
}
#[cfg(test)]
mod tests;