1use crate::mem8::{FrequencyBand, MemoryWave, WaveGrid};
9use anyhow::{Context, Result};
10use chrono::{DateTime, Utc};
11use serde::{Deserialize, Serialize};
12use std::collections::HashMap;
13use std::fs;
14use std::path::{Path, PathBuf};
15use std::sync::{Arc, RwLock};
16
17#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
19pub enum MemoryType {
20 Pattern,
22 Solution,
24 Conversation,
26 Technical,
28 Learning,
30 Joke,
32}
33
34impl MemoryType {
35 pub fn to_frequency_band(&self) -> FrequencyBand {
37 match self {
38 Self::Pattern => FrequencyBand::Delta,
39 Self::Solution => FrequencyBand::Theta,
40 Self::Conversation => FrequencyBand::Alpha,
41 Self::Technical => FrequencyBand::Beta,
42 Self::Learning => FrequencyBand::Gamma,
43 Self::Joke => FrequencyBand::HyperGamma,
44 }
45 }
46
47 pub fn frequency(&self) -> f32 {
49 let (min, max) = self.to_frequency_band().range();
50 (min + max) / 2.0
51 }
52
53 pub fn parse(s: &str) -> Self {
55 match s.to_lowercase().as_str() {
56 "pattern" | "pattern_insight" => Self::Pattern,
57 "solution" | "breakthrough" => Self::Solution,
58 "conversation" | "rapport" => Self::Conversation,
59 "technical" | "technical_pattern" => Self::Technical,
60 "learning" | "learning_moment" => Self::Learning,
61 "joke" | "shared_joke" => Self::Joke,
62 _ => Self::Technical, }
64 }
65}
66
67#[derive(Debug, Clone, Serialize, Deserialize)]
69pub struct AnchoredMemory {
70 pub id: String,
72 pub content: String,
74 pub keywords: Vec<String>,
76 pub memory_type: MemoryType,
78 pub valence: f32,
80 pub arousal: f32,
82 pub x: u8,
84 pub y: u8,
85 pub z: u16,
86 pub created_at: DateTime<Utc>,
88 pub last_accessed: DateTime<Utc>,
90 pub access_count: u32,
92 pub origin: String,
94 pub project_path: Option<PathBuf>,
96}
97
98impl AnchoredMemory {
99 pub fn calculate_coordinates(
101 content: &str,
102 keywords: &[String],
103 memory_type: MemoryType,
104 ) -> (u8, u8, u16) {
105 let content_hash = Self::hash_string(content);
107 let x = (content_hash % 256) as u8;
108
109 let type_offset = match memory_type {
111 MemoryType::Pattern => 0,
112 MemoryType::Solution => 42,
113 MemoryType::Conversation => 84,
114 MemoryType::Technical => 128,
115 MemoryType::Learning => 170,
116 MemoryType::Joke => 212,
117 };
118 let keyword_hash = keywords.iter().map(|k| Self::hash_string(k)).sum::<u64>();
119 let y = ((type_offset as u64 + keyword_hash % 43) % 256) as u8;
120
121 let now = Utc::now().timestamp() as u64;
123 let z = ((now / 60) % 65536) as u16; (x, y, z)
126 }
127
128 fn hash_string(s: &str) -> u64 {
130 s.bytes()
131 .fold(0u64, |acc, b| acc.wrapping_mul(31).wrapping_add(b as u64))
132 }
133
134 pub fn resonance_with(&self, other: &AnchoredMemory) -> f32 {
136 let freq_sim = if self.memory_type == other.memory_type {
138 1.0
139 } else {
140 0.5
141 };
142
143 let overlap = self
145 .keywords
146 .iter()
147 .filter(|k| other.keywords.contains(k))
148 .count() as f32;
149 let total = (self.keywords.len() + other.keywords.len()).max(1) as f32;
150 let keyword_sim = 2.0 * overlap / total;
151
152 let valence_diff = (self.valence - other.valence).abs();
154 let arousal_diff = (self.arousal - other.arousal).abs();
155 let emotion_sim = 1.0 - (valence_diff + arousal_diff) / 4.0;
156
157 let dx = (self.x as i32 - other.x as i32).abs() as f32 / 256.0;
159 let dy = (self.y as i32 - other.y as i32).abs() as f32 / 256.0;
160 let spatial_sim = 1.0 - (dx + dy) / 2.0;
161
162 0.3 * freq_sim + 0.4 * keyword_sim + 0.2 * emotion_sim + 0.1 * spatial_sim
164 }
165
166 pub fn to_wave(&self) -> MemoryWave {
168 let frequency = self.memory_type.frequency();
169 let amplitude = 0.5 + (self.access_count as f32 / 100.0).min(0.5); let mut wave = MemoryWave::new(frequency, amplitude);
172 wave.valence = self.valence;
173 wave.arousal = self.arousal;
174
175 let hours = self.created_at.timestamp() as f32 / 3600.0;
177 wave.phase = (hours % (2.0 * std::f32::consts::PI)).abs();
178
179 wave
180 }
181}
182
183#[derive(Debug, Default, Serialize, Deserialize)]
185struct KeywordIndex {
186 keywords: HashMap<String, Vec<String>>,
188}
189
190impl KeywordIndex {
191 fn add(&mut self, keyword: &str, memory_id: &str) {
192 self.keywords
193 .entry(keyword.to_lowercase())
194 .or_default()
195 .push(memory_id.to_string());
196 }
197
198 fn find(&self, keywords: &[String]) -> Vec<String> {
199 let mut scores: HashMap<String, usize> = HashMap::new();
200
201 for keyword in keywords {
202 if let Some(ids) = self.keywords.get(&keyword.to_lowercase()) {
203 for id in ids {
204 *scores.entry(id.clone()).or_default() += 1;
205 }
206 }
207 }
208
209 let mut results: Vec<_> = scores.into_iter().collect();
211 results.sort_by(|a, b| b.1.cmp(&a.1));
212 results.into_iter().map(|(id, _)| id).collect()
213 }
214}
215
216pub struct WaveMemoryManager {
225 wave_grid: Arc<RwLock<WaveGrid>>,
227 memories: HashMap<String, AnchoredMemory>,
229 keyword_index: KeywordIndex,
231 storage_path: PathBuf,
233 dirty: bool,
235}
236
237impl WaveMemoryManager {
238 pub fn new(storage_dir: Option<&Path>) -> Self {
241 let storage_path = storage_dir
242 .map(|p| p.join(".st").join("mem8").join("wave_memory.m8"))
243 .unwrap_or_else(|| {
244 std::env::current_dir()
245 .unwrap_or_else(|_| PathBuf::from("."))
246 .join(".st")
247 .join("mem8")
248 .join("wave_memory.m8")
249 });
250
251 let mut manager = Self {
252 wave_grid: Arc::new(RwLock::new(WaveGrid::new())),
253 memories: HashMap::new(),
254 keyword_index: KeywordIndex::default(),
255 storage_path,
256 dirty: false,
257 };
258
259 if let Err(e) = manager.load() {
261 eprintln!("Note: Starting fresh wave memory ({})", e);
262 }
263
264 manager
265 }
266
267 pub fn new_compact(storage_dir: Option<&Path>) -> Self {
270 let storage_path = storage_dir
271 .map(|p| p.join(".st").join("mem8").join("wave_memory.m8"))
272 .unwrap_or_else(|| {
273 dirs::home_dir()
274 .unwrap_or_else(|| PathBuf::from("."))
275 .join(".mem8")
276 .join("wave_memory.m8")
277 });
278
279 let mut manager = Self {
280 wave_grid: Arc::new(RwLock::new(WaveGrid::new_compact())),
281 memories: HashMap::new(),
282 keyword_index: KeywordIndex::default(),
283 storage_path,
284 dirty: false,
285 };
286
287 if let Err(e) = manager.load() {
289 eprintln!("Note: Starting fresh wave memory ({})", e);
290 }
291
292 manager
293 }
294
295 #[cfg(test)]
298 pub fn new_test(storage_dir: Option<&Path>) -> Self {
299 let storage_path = storage_dir
300 .map(|p| p.join(".st").join("mem8").join("wave_memory_test.m8"))
301 .unwrap_or_else(|| {
302 std::env::current_dir()
303 .unwrap_or_else(|_| PathBuf::from("."))
304 .join(".st")
305 .join("mem8")
306 .join("wave_memory_test.m8")
307 });
308
309 let mut manager = Self {
310 wave_grid: Arc::new(RwLock::new(WaveGrid::new_test())),
311 memories: HashMap::new(),
312 keyword_index: KeywordIndex::default(),
313 storage_path,
314 dirty: false,
315 };
316
317 let _ = manager.load();
319
320 manager
321 }
322
323 #[allow(clippy::too_many_arguments)] pub fn anchor(
326 &mut self,
327 content: String,
328 keywords: Vec<String>,
329 memory_type: MemoryType,
330 valence: f32,
331 arousal: f32,
332 origin: String,
333 project_path: Option<PathBuf>,
334 ) -> Result<String> {
335 let id = uuid::Uuid::new_v4().to_string();
336 let (x, y, z) = AnchoredMemory::calculate_coordinates(&content, &keywords, memory_type);
337
338 let memory = AnchoredMemory {
339 id: id.clone(),
340 content,
341 keywords: keywords.clone(),
342 memory_type,
343 valence: valence.clamp(-1.0, 1.0),
344 arousal: arousal.clamp(0.0, 1.0),
345 x,
346 y,
347 z,
348 created_at: Utc::now(),
349 last_accessed: Utc::now(),
350 access_count: 1,
351 origin,
352 project_path,
353 };
354
355 let wave = memory.to_wave();
357 if let Ok(mut grid) = self.wave_grid.write() {
358 grid.store(x, y, z, wave);
359 }
360
361 for keyword in &keywords {
363 self.keyword_index.add(keyword, &id);
364 }
365
366 self.memories.insert(id.clone(), memory);
368 self.dirty = true;
369
370 Ok(id)
371 }
372
373 pub fn find_by_keywords(
376 &mut self,
377 keywords: &[String],
378 max_results: usize,
379 ) -> Vec<AnchoredMemory> {
380 let ids = self.keyword_index.find(keywords);
381 let found_ids: Vec<String> = ids.iter().take(max_results).cloned().collect();
382
383 let results: Vec<AnchoredMemory> = found_ids
385 .iter()
386 .filter_map(|id| self.memories.get(id).cloned())
387 .collect();
388
389 for id in &found_ids {
391 if let Some(mem) = self.memories.get_mut(id) {
392 mem.access_count += 1;
393 mem.last_accessed = Utc::now();
394 self.dirty = true;
395 }
396 }
397
398 results
399 }
400
401 pub fn find_by_resonance(
404 &mut self,
405 query_content: &str,
406 query_keywords: &[String],
407 query_type: MemoryType,
408 threshold: f32,
409 max_results: usize,
410 ) -> Vec<(AnchoredMemory, f32)> {
411 let (x, y, z) =
413 AnchoredMemory::calculate_coordinates(query_content, query_keywords, query_type);
414 let query = AnchoredMemory {
415 id: String::new(),
416 content: query_content.to_string(),
417 keywords: query_keywords.to_vec(),
418 memory_type: query_type,
419 valence: 0.0,
420 arousal: 0.5,
421 x,
422 y,
423 z,
424 created_at: Utc::now(),
425 last_accessed: Utc::now(),
426 access_count: 0,
427 origin: String::new(),
428 project_path: None,
429 };
430
431 let mut resonances: Vec<(String, AnchoredMemory, f32)> = self
433 .memories
434 .values()
435 .map(|mem| (mem.id.clone(), mem.clone(), mem.resonance_with(&query)))
436 .filter(|(_, _, r)| *r >= threshold)
437 .collect();
438
439 resonances.sort_by(|a, b| b.2.partial_cmp(&a.2).unwrap_or(std::cmp::Ordering::Equal));
441
442 let update_ids: Vec<String> = resonances
444 .iter()
445 .take(max_results)
446 .map(|(id, _, _)| id.clone())
447 .collect();
448
449 for id in &update_ids {
451 if let Some(m) = self.memories.get_mut(id) {
452 m.access_count += 1;
453 m.last_accessed = Utc::now();
454 self.dirty = true;
455 }
456 }
457
458 resonances
459 .into_iter()
460 .take(max_results)
461 .map(|(_, mem, r)| (mem, r))
462 .collect()
463 }
464
465 pub fn get_interference(&self, x: u8, y: u8, z: u16, t: f32) -> f32 {
467 if let Ok(grid) = self.wave_grid.read() {
468 grid.calculate_interference(x, y, z, t)
469 } else {
470 0.0
471 }
472 }
473
474 pub fn stats(&self) -> serde_json::Value {
476 let type_counts: HashMap<String, usize> =
477 self.memories.values().fold(HashMap::new(), |mut acc, mem| {
478 *acc.entry(format!("{:?}", mem.memory_type)).or_default() += 1;
479 acc
480 });
481
482 let active_count = if let Ok(grid) = self.wave_grid.read() {
483 grid.active_memory_count()
484 } else {
485 0
486 };
487
488 serde_json::json!({
489 "total_memories": self.memories.len(),
490 "active_waves": active_count,
491 "unique_keywords": self.keyword_index.keywords.len(),
492 "by_type": type_counts,
493 "storage_path": self.storage_path.display().to_string(),
494 })
495 }
496
497 pub fn save(&mut self) -> Result<()> {
499 if !self.dirty {
500 return Ok(());
501 }
502
503 if let Some(parent) = self.storage_path.parent() {
505 fs::create_dir_all(parent).context("Failed to create memory directory")?;
506 }
507
508 let data = serde_json::json!({
510 "version": 1,
511 "memories": self.memories,
512 "keyword_index": self.keyword_index,
513 });
514
515 let json = serde_json::to_string_pretty(&data).context("Failed to serialize memories")?;
516
517 fs::write(&self.storage_path, json).context("Failed to write memory file")?;
518
519 self.dirty = false;
520 eprintln!(
521 "💾 Saved {} memories to {}",
522 self.memories.len(),
523 self.storage_path.display()
524 );
525
526 Ok(())
527 }
528
529 pub fn load(&mut self) -> Result<()> {
531 if !self.storage_path.exists() {
532 return Err(anyhow::anyhow!("No memory file found"));
533 }
534
535 let json = fs::read_to_string(&self.storage_path).context("Failed to read memory file")?;
536
537 let data: serde_json::Value =
538 serde_json::from_str(&json).context("Failed to parse memory file")?;
539
540 if let Some(memories) = data.get("memories") {
542 self.memories = serde_json::from_value(memories.clone())
543 .context("Failed to deserialize memories")?;
544 }
545
546 if let Some(index) = data.get("keyword_index") {
548 self.keyword_index = serde_json::from_value(index.clone())
549 .context("Failed to deserialize keyword index")?;
550 }
551
552 if let Ok(mut grid) = self.wave_grid.write() {
554 for memory in self.memories.values() {
555 let wave = memory.to_wave();
556 grid.store(memory.x, memory.y, memory.z, wave);
557 }
558 }
559
560 eprintln!(
561 "🧠 Loaded {} memories from {}",
562 self.memories.len(),
563 self.storage_path.display()
564 );
565
566 Ok(())
567 }
568
569 pub fn get(&self, id: &str) -> Option<&AnchoredMemory> {
571 self.memories.get(id)
572 }
573
574 pub fn delete(&mut self, id: &str) -> bool {
576 if let Some(memory) = self.memories.remove(id) {
577 for keyword in &memory.keywords {
580 if let Some(ids) = self.keyword_index.keywords.get_mut(&keyword.to_lowercase()) {
581 ids.retain(|i| i != id);
582 }
583 }
584 self.dirty = true;
585 true
586 } else {
587 false
588 }
589 }
590}
591
592impl Drop for WaveMemoryManager {
593 fn drop(&mut self) {
594 let _ = self.save();
596 }
597}
598
599static WAVE_MEMORY: std::sync::OnceLock<std::sync::Mutex<WaveMemoryManager>> =
601 std::sync::OnceLock::new();
602
603pub fn get_wave_memory() -> &'static std::sync::Mutex<WaveMemoryManager> {
605 WAVE_MEMORY.get_or_init(|| std::sync::Mutex::new(WaveMemoryManager::new(None)))
606}
607
608pub fn init_wave_memory(storage_dir: &Path) {
610 let _ = WAVE_MEMORY.set(std::sync::Mutex::new(WaveMemoryManager::new(Some(
611 storage_dir,
612 ))));
613}
614
615#[cfg(test)]
616mod tests {
617 use super::*;
618 use tempfile::tempdir;
619
620 #[test]
621 fn test_anchor_and_find() {
622 let dir = tempdir().unwrap();
623 let mut manager = WaveMemoryManager::new_test(Some(dir.path()));
624
625 let id = manager
627 .anchor(
628 "The solution to the authentication bug was using JWT refresh tokens".to_string(),
629 vec!["auth".to_string(), "jwt".to_string(), "bug".to_string()],
630 MemoryType::Solution,
631 0.8, 0.7, "tandem:hue:claude".to_string(),
634 None,
635 )
636 .unwrap();
637
638 assert!(!id.is_empty());
639
640 let results = manager.find_by_keywords(&["auth".to_string(), "jwt".to_string()], 10);
642 assert_eq!(results.len(), 1);
643 assert!(results[0].content.contains("JWT refresh tokens"));
644 }
645
646 #[test]
647 fn test_resonance_search() {
648 let dir = tempdir().unwrap();
649 let mut manager = WaveMemoryManager::new_test(Some(dir.path()));
650
651 manager
653 .anchor(
654 "Rust async/await pattern for error handling".to_string(),
655 vec!["rust".to_string(), "async".to_string(), "error".to_string()],
656 MemoryType::Technical,
657 0.3,
658 0.5,
659 "tandem:hue:claude".to_string(),
660 None,
661 )
662 .unwrap();
663
664 manager
665 .anchor(
666 "Go channels for concurrent error propagation".to_string(),
667 vec![
668 "go".to_string(),
669 "channels".to_string(),
670 "error".to_string(),
671 ],
672 MemoryType::Technical,
673 0.2,
674 0.4,
675 "tandem:hue:claude".to_string(),
676 None,
677 )
678 .unwrap();
679
680 let results = manager.find_by_resonance(
682 "error handling in async code",
683 &["async".to_string(), "error".to_string()],
684 MemoryType::Technical,
685 0.3, 10,
687 );
688
689 assert!(!results.is_empty());
691 assert!(results[0].0.content.contains("Rust") || results[0].0.content.contains("error"));
692 }
693
694 #[test]
695 fn test_persistence() {
696 let dir = tempdir().unwrap();
697
698 {
700 let mut manager = WaveMemoryManager::new_test(Some(dir.path()));
701 manager
702 .anchor(
703 "Aye loves Elvis!".to_string(),
704 vec!["aye".to_string(), "elvis".to_string()],
705 MemoryType::Joke,
706 1.0,
707 1.0,
708 "tandem:hue:claude".to_string(),
709 None,
710 )
711 .unwrap();
712 manager.save().unwrap();
713 }
714
715 {
717 let mut manager = WaveMemoryManager::new_test(Some(dir.path()));
718 let results = manager.find_by_keywords(&["elvis".to_string()], 10);
719 assert_eq!(results.len(), 1);
720 assert!(results[0].content.contains("Elvis"));
721 }
722 }
723
724 #[test]
725 fn test_memory_types_to_frequencies() {
726 assert!(MemoryType::Pattern.frequency() < MemoryType::Solution.frequency());
727 assert!(MemoryType::Solution.frequency() < MemoryType::Technical.frequency());
728 assert!(MemoryType::Technical.frequency() < MemoryType::Joke.frequency());
729 }
730}