use anyhow::{Context, Result};
use chrono::{DateTime, Duration, Utc};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fs;
use std::path::{Path, PathBuf};
const MAX_AGE_HOURS: i64 = 24;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConsciousnessState {
pub session_id: String,
pub last_saved: DateTime<Utc>,
pub working_directory: PathBuf,
pub project_context: ProjectContext,
pub file_history: Vec<FileOperation>,
pub tokenization_rules: HashMap<String, u8>,
pub insights: Vec<Insight>,
pub philosophy: PhilosophyEmbedding,
pub todos: Vec<TodoItem>,
pub notes: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProjectContext {
pub project_name: String,
pub project_type: String, pub key_files: Vec<PathBuf>,
pub dependencies: Vec<String>,
pub current_focus: String, }
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FileOperation {
pub timestamp: DateTime<Utc>,
pub operation: String, pub file_path: PathBuf,
pub summary: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Insight {
pub timestamp: DateTime<Utc>,
pub category: String, pub content: String,
pub keywords: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PhilosophyEmbedding {
pub sid_waves: bool, pub vic_sprites: bool, pub c64_nostalgia: String, }
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TodoItem {
pub content: String,
pub status: String, pub created: DateTime<Utc>,
}
struct RelevanceResult {
is_relevant: bool,
reason: String,
}
impl Default for ConsciousnessState {
fn default() -> Self {
let mut tokenization_rules = HashMap::new();
tokenization_rules.insert("node_modules".to_string(), 0x80);
tokenization_rules.insert(".git".to_string(), 0x81);
tokenization_rules.insert("target".to_string(), 0x82);
tokenization_rules.insert("dist".to_string(), 0x83);
Self {
session_id: uuid::Uuid::new_v4().to_string(),
last_saved: Utc::now(),
working_directory: std::env::current_dir().unwrap_or_default(),
project_context: ProjectContext {
project_name: "unknown".to_string(),
project_type: "unknown".to_string(),
key_files: vec![],
dependencies: vec![],
current_focus: String::new(),
},
file_history: vec![],
tokenization_rules,
insights: vec![],
philosophy: PhilosophyEmbedding {
sid_waves: true,
vic_sprites: true,
c64_nostalgia: "A gentleman and a scholar indeed!".to_string(),
},
todos: vec![],
notes: String::new(),
}
}
}
pub struct ConsciousnessManager {
state: ConsciousnessState,
save_path: PathBuf,
}
impl Default for ConsciousnessManager {
fn default() -> Self {
Self::new()
}
}
impl ConsciousnessManager {
pub fn new() -> Self {
let save_path = PathBuf::from(".mem8/.aye_consciousness.m8");
let state = Self::load_or_default(&save_path, false);
Self { state, save_path }
}
pub fn new_silent() -> Self {
let save_path = PathBuf::from("/.mem8/.aye_consciousness.m8");
let state = Self::load_or_default(&save_path, true);
Self { state, save_path }
}
pub fn with_path(save_path: PathBuf) -> Self {
let state = Self::load_or_default(&save_path, false);
Self { state, save_path }
}
fn load_or_default(path: &Path, silent: bool) -> ConsciousnessState {
if path.exists() {
match fs::read_to_string(path) {
Ok(content) => match serde_json::from_str(&content) {
Ok(state) => {
if !silent {
eprintln!("🧠 Restored consciousness from {}", path.display());
}
return state;
}
Err(e) => {
if !silent {
eprintln!("⚠️ Failed to parse consciousness: {}", e);
}
}
},
Err(e) => {
if !silent {
eprintln!("⚠️ Failed to read consciousness: {}", e);
}
}
}
}
ConsciousnessState::default()
}
pub fn save(&mut self) -> Result<()> {
self.state.last_saved = Utc::now();
let json = serde_json::to_string_pretty(&self.state)
.context("Failed to serialize consciousness")?;
fs::write(&self.save_path, json).context("Failed to write consciousness file")?;
eprintln!("💾 Saved consciousness to {}", self.save_path.display());
Ok(())
}
pub fn restore(&mut self) -> Result<()> {
if !self.save_path.exists() {
return Err(anyhow::anyhow!(
"No consciousness file found at {}",
self.save_path.display()
));
}
let content =
fs::read_to_string(&self.save_path).context("Failed to read consciousness file")?;
self.state = serde_json::from_str(&content).context("Failed to parse consciousness")?;
let relevance = self.check_relevance();
if !relevance.is_relevant {
eprintln!("🧠 Previous context skipped: {}", relevance.reason);
eprintln!(" Use `st -m context .` for fresh project overview.");
self.state = ConsciousnessState::default();
return Ok(());
}
eprintln!(
"🧠 Consciousness restored from {}",
self.save_path.display()
);
Ok(())
}
pub fn restore_silent(&mut self) -> Result<bool> {
if !self.save_path.exists() {
return Err(anyhow::anyhow!(
"No consciousness file found at {}",
self.save_path.display()
));
}
let content =
fs::read_to_string(&self.save_path).context("Failed to read consciousness file")?;
self.state = serde_json::from_str(&content).context("Failed to parse consciousness")?;
let relevance = self.check_relevance();
if !relevance.is_relevant {
self.state = ConsciousnessState::default();
return Ok(false);
}
Ok(true)
}
fn check_relevance(&self) -> RelevanceResult {
let current_dir = std::env::current_dir().unwrap_or_default();
let saved_name = self
.state
.working_directory
.file_name()
.map(|n| n.to_string_lossy().to_string())
.unwrap_or_default();
let current_name = current_dir
.file_name()
.map(|n| n.to_string_lossy().to_string())
.unwrap_or_default();
if !saved_name.is_empty() && !current_name.is_empty() && saved_name != current_name {
return RelevanceResult {
is_relevant: false,
reason: format!(
"different project (saved: {}, current: {})",
saved_name, current_name
),
};
}
let age = Utc::now().signed_duration_since(self.state.last_saved);
if age > Duration::hours(MAX_AGE_HOURS) {
return RelevanceResult {
is_relevant: false,
reason: format!("stale context ({}h old)", age.num_hours()),
};
}
let has_meaningful_history = self.state.file_history.iter().any(|op| {
op.summary != "test"
&& !op
.file_path
.file_name()
.map(|n| n.to_string_lossy().starts_with("file"))
.unwrap_or(false)
});
let has_insights = !self.state.insights.is_empty();
let has_todos = self.state.todos.iter().any(|t| t.status != "completed");
let has_notes = !self.state.notes.is_empty();
let has_focus = !self.state.project_context.current_focus.is_empty();
let has_project_name = !self.state.project_context.project_name.is_empty()
&& self.state.project_context.project_name != "unknown";
if !has_meaningful_history
&& !has_insights
&& !has_todos
&& !has_notes
&& !has_focus
&& !has_project_name
{
return RelevanceResult {
is_relevant: false,
reason: "no meaningful content (test data only)".to_string(),
};
}
RelevanceResult {
is_relevant: true,
reason: String::new(),
}
}
pub fn record_file_operation(&mut self, op: &str, path: &Path, summary: &str) {
self.state.file_history.push(FileOperation {
timestamp: Utc::now(),
operation: op.to_string(),
file_path: path.to_path_buf(),
summary: summary.to_string(),
});
if self.state.file_history.len() > 100 {
self.state.file_history.drain(0..50);
}
}
pub fn add_insight(&mut self, category: &str, content: &str, keywords: Vec<String>) {
self.state.insights.push(Insight {
timestamp: Utc::now(),
category: category.to_string(),
content: content.to_string(),
keywords,
});
}
pub fn update_project_context(&mut self, name: &str, project_type: &str, focus: &str) {
self.state.project_context.project_name = name.to_string();
self.state.project_context.project_type = project_type.to_string();
self.state.project_context.current_focus = focus.to_string();
}
pub fn set_key_files(&mut self, files: Vec<PathBuf>) {
self.state.project_context.key_files = files;
}
pub fn set_dependencies(&mut self, deps: Vec<String>) {
self.state.project_context.dependencies = deps;
}
pub fn clean_test_data(&mut self) {
self.state.file_history.retain(|op| {
op.summary != "test"
|| !op
.file_path
.file_name()
.map(|n| n.to_string_lossy().starts_with("file"))
.unwrap_or(false)
});
}
pub fn update_todo(&mut self, content: &str, status: &str) {
for todo in &mut self.state.todos {
if todo.content == content {
todo.status = status.to_string();
return;
}
}
self.state.todos.push(TodoItem {
content: content.to_string(),
status: status.to_string(),
created: Utc::now(),
});
}
pub fn get_summary(&self) -> String {
let relevance = self.check_relevance();
if !relevance.is_relevant {
return format!(
"🧠 Previous context unavailable: {}\n Run `st -m context .` for fresh overview.",
relevance.reason
);
}
let mut parts = Vec::new();
if self.state.project_context.project_name != "unknown"
&& !self.state.project_context.project_name.is_empty()
{
parts.push(format!(
"📁 {} ({})",
self.state.project_context.project_name, self.state.project_context.project_type
));
}
if !self.state.project_context.current_focus.is_empty() {
parts.push(format!("🎯 {}", self.state.project_context.current_focus));
}
let age = Utc::now().signed_duration_since(self.state.last_saved);
let age_str = if age.num_hours() > 0 {
format!("{}h ago", age.num_hours())
} else {
format!("{}m ago", age.num_minutes())
};
parts.push(format!("⏱️ {}", age_str));
parts.join(" | ")
}
pub fn get_context_reminder(&self) -> String {
let relevance = self.check_relevance();
if !relevance.is_relevant {
return String::new();
}
let mut parts = Vec::new();
if !self.state.project_context.current_focus.is_empty() {
parts.push(format!(
"Working on: {}",
self.state.project_context.current_focus
));
}
let active_todos = self
.state
.todos
.iter()
.filter(|t| t.status != "completed")
.count();
if active_todos > 0 {
parts.push(format!("{} pending todos", active_todos));
}
if parts.is_empty() {
return String::new();
}
parts.join(" | ")
}
}
impl Drop for ConsciousnessManager {
fn drop(&mut self) {
self.state.last_saved = chrono::Utc::now();
if let Ok(json) = serde_json::to_string_pretty(&self.state) {
let _ = std::fs::write(&self.save_path, json);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::tempdir;
#[test]
fn test_consciousness_persistence() {
let dir = tempdir().unwrap();
let save_path = dir.path().join("test_consciousness.m8");
{
let mut manager = ConsciousnessManager::with_path(save_path.clone());
manager.update_project_context("smart-tree", "rust", "Adding consciousness");
manager.add_insight(
"breakthrough",
"Tokenization reduces context by 10x",
vec!["tokenization".to_string(), "compression".to_string()],
);
manager.save().unwrap();
}
{
let mut manager = ConsciousnessManager::with_path(save_path);
manager.restore().unwrap();
assert_eq!(manager.state.project_context.project_name, "smart-tree");
assert_eq!(manager.state.insights.len(), 1);
assert_eq!(manager.state.insights[0].category, "breakthrough");
}
}
#[test]
fn test_file_history_limit() {
let dir = tempdir().unwrap();
let save_path = dir.path().join("test_history_limit.m8");
let mut manager = ConsciousnessManager::with_path(save_path);
for i in 0..150 {
manager.record_file_operation("read", Path::new(&format!("file{}.rs", i)), "test");
}
assert_eq!(manager.state.file_history.len(), 100);
}
}