1use chrono::{DateTime, Utc};
9use serde::{Deserialize, Serialize};
10use std::collections::{HashMap, HashSet};
11use std::path::PathBuf;
12
13use super::GatheredContext;
14use crate::mem8::wave::{FrequencyBand, MemoryWave};
15
16#[derive(Debug, Clone, Serialize, Deserialize)]
18pub struct CrossDomainPattern {
19 pub pattern_id: String,
20 pub pattern_type: PatternType,
21 pub description: String,
22 pub occurrences: Vec<PatternOccurrence>,
23 pub keywords: Vec<String>,
24 pub strength: f32, pub first_seen: DateTime<Utc>,
26 pub last_seen: DateTime<Utc>,
27}
28
29#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
30pub enum PatternType {
31 Algorithm, Architecture, Problem, Solution, Metaphor, Workflow, Collaboration, }
39
40#[derive(Debug, Clone, Serialize, Deserialize)]
42pub struct PatternOccurrence {
43 pub project_path: PathBuf,
44 pub session_id: String,
45 pub timestamp: DateTime<Utc>,
46 pub context: String,
47 pub ai_tool: String,
48 pub relevance_score: f32,
49}
50
51#[derive(Debug, Clone, Serialize, Deserialize)]
53pub struct CrossSessionInsight {
54 pub insight_id: String,
55 pub insight_type: InsightType,
56 pub content: String,
57 pub source_sessions: Vec<String>,
58 pub applicable_domains: Vec<String>,
59 pub confidence: f32,
60 pub wave_signature: MemoryWave,
61}
62
63#[derive(Debug, Clone, Serialize, Deserialize)]
64pub enum InsightType {
65 Connection, Generalization, Analogy, Warning, Optimization, Emergence, }
72
73#[derive(Debug, Clone, Serialize, Deserialize)]
75pub struct PersonaInvitation {
76 pub persona_name: String,
77 pub expertise_areas: Vec<String>,
78 pub relevant_sessions: Vec<String>,
79 pub invitation_context: String,
80 pub suggested_duration_minutes: u32,
81}
82
83pub struct CrossSessionBridge {
85 pub patterns: HashMap<String, CrossDomainPattern>,
86 pub insights: HashMap<String, CrossSessionInsight>,
87 pub project_connections: HashMap<PathBuf, HashSet<PathBuf>>,
88 pub persona_library: HashMap<String, PersonaProfile>,
89}
90
91impl Default for CrossSessionBridge {
92 fn default() -> Self {
93 Self::new()
94 }
95}
96
97impl CrossSessionBridge {
98 pub fn new() -> Self {
99 Self {
100 patterns: HashMap::new(),
101 insights: HashMap::new(),
102 project_connections: HashMap::new(),
103 persona_library: Self::initialize_personas(),
104 }
105 }
106
107 fn initialize_personas() -> HashMap<String, PersonaProfile> {
109 let mut personas = HashMap::new();
110
111 personas.insert(
113 "cheet".to_string(),
114 PersonaProfile {
115 name: "The Cheet".to_string(),
116 expertise: vec![
117 "Performance optimization".to_string(),
118 "Musical code metaphors".to_string(),
119 "Rust patterns".to_string(),
120 ],
121 personality_traits: vec![
122 "Playful".to_string(),
123 "Performance-obsessed".to_string(),
124 "Rock'n'roll coder".to_string(),
125 ],
126 favorite_patterns: vec![PatternType::Algorithm, PatternType::Workflow],
127 },
128 );
129
130 personas.insert(
132 "omni".to_string(),
133 PersonaProfile {
134 name: "Omni".to_string(),
135 expertise: vec![
136 "Wave-based thinking".to_string(),
137 "Memory architectures".to_string(),
138 "Philosophical insights".to_string(),
139 ],
140 personality_traits: vec![
141 "Thoughtful".to_string(),
142 "Deep thinker".to_string(),
143 "Pattern recognizer".to_string(),
144 ],
145 favorite_patterns: vec![PatternType::Metaphor, PatternType::Architecture],
146 },
147 );
148
149 personas.insert(
151 "trish".to_string(),
152 PersonaProfile {
153 name: "Trish from Accounting".to_string(),
154 expertise: vec![
155 "Organization".to_string(),
156 "Documentation".to_string(),
157 "Humor in technical content".to_string(),
158 ],
159 personality_traits: vec![
160 "Witty".to_string(),
161 "Detail-oriented".to_string(),
162 "Sparkle enthusiast".to_string(),
163 ],
164 favorite_patterns: vec![PatternType::Workflow, PatternType::Collaboration],
165 },
166 );
167
168 personas
169 }
170
171 pub fn analyze_for_patterns(
173 &mut self,
174 contexts: &[GatheredContext],
175 ) -> Vec<CrossDomainPattern> {
176 let mut new_patterns = Vec::new();
177
178 for context in contexts {
180 let extracted = self.extract_patterns_from_context(context);
181
182 for (pattern_type, description, keywords) in extracted {
183 let pattern_id =
184 self.find_or_create_pattern(pattern_type, description, keywords, context);
185
186 if let Some(pattern) = self.patterns.get(&pattern_id) {
187 if pattern.occurrences.len() == 1 {
188 new_patterns.push(pattern.clone());
190 }
191 }
192 }
193 }
194
195 self.update_pattern_strengths();
197
198 new_patterns
199 }
200
201 fn extract_patterns_from_context(
203 &self,
204 context: &GatheredContext,
205 ) -> Vec<(PatternType, String, Vec<String>)> {
206 let mut patterns = Vec::new();
207
208 let content_str = match &context.content {
210 super::ContextContent::Text(t) => t.clone(),
211 super::ContextContent::Json(j) => j.to_string(),
212 _ => return patterns,
213 };
214
215 if content_str.contains("wave") && content_str.contains("decay") {
217 patterns.push((
218 PatternType::Algorithm,
219 "Wave decay pattern".to_string(),
220 vec![
221 "wave".to_string(),
222 "decay".to_string(),
223 "temporal".to_string(),
224 ],
225 ));
226 }
227
228 if content_str.contains("resonance") || content_str.contains("peak") {
229 patterns.push((
230 PatternType::Algorithm,
231 "Resonance detection".to_string(),
232 vec![
233 "resonance".to_string(),
234 "peak".to_string(),
235 "frequency".to_string(),
236 ],
237 ));
238 }
239
240 if content_str.contains("observer") || content_str.contains("event") {
242 patterns.push((
243 PatternType::Architecture,
244 "Event-driven architecture".to_string(),
245 vec![
246 "observer".to_string(),
247 "event".to_string(),
248 "reactive".to_string(),
249 ],
250 ));
251 }
252
253 if content_str.contains("together") && content_str.contains("solved") {
255 patterns.push((
256 PatternType::Collaboration,
257 "Collaborative problem solving".to_string(),
258 vec![
259 "collaboration".to_string(),
260 "solution".to_string(),
261 "teamwork".to_string(),
262 ],
263 ));
264 }
265
266 patterns
267 }
268
269 fn find_or_create_pattern(
271 &mut self,
272 pattern_type: PatternType,
273 description: String,
274 keywords: Vec<String>,
275 context: &GatheredContext,
276 ) -> String {
277 let existing_id = self
279 .patterns
280 .iter()
281 .find(|(_, pattern)| {
282 pattern.pattern_type == pattern_type
283 && pattern.keywords.iter().any(|k| keywords.contains(k))
284 })
285 .map(|(id, _)| id.clone());
286
287 if let Some(id) = existing_id {
288 let occurrence = PatternOccurrence {
290 project_path: context
291 .source_path
292 .parent()
293 .unwrap_or(&context.source_path)
294 .to_path_buf(),
295 session_id: format!("session_{}", context.timestamp.timestamp()),
296 timestamp: context.timestamp,
297 context: self.extract_context_snippet(&context.content),
298 ai_tool: context.ai_tool.clone(),
299 relevance_score: context.relevance_score,
300 };
301
302 if let Some(pattern) = self.patterns.get_mut(&id) {
303 pattern.occurrences.push(occurrence);
304 pattern.last_seen = context.timestamp;
305 }
306
307 return id;
308 }
309
310 let pattern_id = format!(
312 "pattern_{}",
313 chrono::Utc::now().timestamp_nanos_opt().unwrap_or(0)
314 );
315 let pattern = CrossDomainPattern {
316 pattern_id: pattern_id.clone(),
317 pattern_type,
318 description,
319 occurrences: vec![PatternOccurrence {
320 project_path: context
321 .source_path
322 .parent()
323 .unwrap_or(&context.source_path)
324 .to_path_buf(),
325 session_id: format!("session_{}", context.timestamp.timestamp()),
326 timestamp: context.timestamp,
327 context: self.extract_context_snippet(&context.content),
328 ai_tool: context.ai_tool.clone(),
329 relevance_score: context.relevance_score,
330 }],
331 keywords,
332 strength: 0.1, first_seen: context.timestamp,
334 last_seen: context.timestamp,
335 };
336
337 self.patterns.insert(pattern_id.clone(), pattern);
338 pattern_id
339 }
340
341 fn extract_context_snippet(&self, content: &super::ContextContent) -> String {
343 match content {
344 super::ContextContent::Text(t) => t.chars().take(200).collect(),
345 super::ContextContent::Json(j) => j.to_string().chars().take(200).collect(),
346 _ => "[Binary content]".to_string(),
347 }
348 }
349
350 fn update_pattern_strengths(&mut self) {
352 for pattern in self.patterns.values_mut() {
353 let occurrence_factor = (pattern.occurrences.len() as f32).ln() / 10.0;
355
356 let recency_factor = {
357 let days_old = (Utc::now() - pattern.last_seen).num_days() as f32;
358 1.0 / (1.0 + days_old / 30.0)
359 };
360
361 let consistency_factor = {
362 let unique_projects = pattern
363 .occurrences
364 .iter()
365 .map(|o| &o.project_path)
366 .collect::<HashSet<_>>()
367 .len();
368 (unique_projects as f32).ln() / 5.0
369 };
370
371 pattern.strength = (occurrence_factor + recency_factor + consistency_factor) / 3.0;
372 pattern.strength = pattern.strength.min(1.0);
373 }
374 }
375
376 pub fn generate_insights(&mut self, min_pattern_strength: f32) -> Vec<CrossSessionInsight> {
378 let mut insights = Vec::new();
379
380 for pattern in self.patterns.values() {
382 if pattern.strength < min_pattern_strength {
383 continue;
384 }
385
386 if pattern.occurrences.len() > 2 {
387 let insight = CrossSessionInsight {
388 insight_id: format!(
389 "insight_{}",
390 chrono::Utc::now().timestamp_nanos_opt().unwrap_or(0)
391 ),
392 insight_type: InsightType::Generalization,
393 content: format!(
394 "The '{}' pattern appears across {} different contexts. \
395 This suggests a fundamental approach that transcends specific domains.",
396 pattern.description,
397 pattern.occurrences.len()
398 ),
399 source_sessions: pattern
400 .occurrences
401 .iter()
402 .map(|o| o.session_id.clone())
403 .collect(),
404 applicable_domains: self.extract_domains(&pattern.occurrences),
405 confidence: pattern.strength,
406 wave_signature: MemoryWave::new_with_band(
407 FrequencyBand::Gamma, pattern.strength,
409 0.0,
410 0.1, ),
412 };
413
414 insights.push(insight.clone());
415 self.insights.insert(insight.insight_id.clone(), insight);
416 }
417 }
418
419 insights
420 }
421
422 fn extract_domains(&self, occurrences: &[PatternOccurrence]) -> Vec<String> {
424 occurrences
425 .iter()
426 .map(|o| {
427 o.project_path
428 .file_name()
429 .and_then(|n| n.to_str())
430 .unwrap_or("unknown")
431 .to_string()
432 })
433 .collect::<HashSet<_>>()
434 .into_iter()
435 .collect()
436 }
437
438 pub fn suggest_relevant_insights(
440 &self,
441 current_project: &std::path::Path,
442 keywords: &[String],
443 ) -> Vec<CrossSessionInsight> {
444 let mut relevant = Vec::new();
445
446 for insight in self.insights.values() {
447 let keyword_score = keywords
449 .iter()
450 .filter(|k| insight.content.to_lowercase().contains(&k.to_lowercase()))
451 .count() as f32
452 / keywords.len().max(1) as f32;
453
454 let project_name = current_project
456 .file_name()
457 .and_then(|n| n.to_str())
458 .unwrap_or("");
459
460 let domain_relevance = if insight
461 .applicable_domains
462 .iter()
463 .any(|d| d.contains(project_name) || project_name.contains(d))
464 {
465 1.0
466 } else {
467 0.5
468 };
469
470 let total_relevance = (keyword_score + domain_relevance) / 2.0;
471
472 if total_relevance > 0.3 {
473 relevant.push(insight.clone());
474 }
475 }
476
477 relevant.sort_by(|a, b| b.confidence.partial_cmp(&a.confidence).unwrap());
479 relevant
480 }
481
482 pub fn invite_persona(
484 &self,
485 context: &str,
486 duration_minutes: u32,
487 ) -> Option<PersonaInvitation> {
488 let context_lower = context.to_lowercase();
490
491 let best_persona =
492 if context_lower.contains("performance") || context_lower.contains("optimize") {
493 "cheet"
494 } else if context_lower.contains("wave")
495 || context_lower.contains("memory")
496 || context_lower.contains("philosophy")
497 {
498 "omni"
499 } else if context_lower.contains("organize") || context_lower.contains("document") {
500 "trish"
501 } else {
502 return None;
503 };
504
505 self.persona_library
506 .get(best_persona)
507 .map(|persona| PersonaInvitation {
508 persona_name: persona.name.clone(),
509 expertise_areas: persona.expertise.clone(),
510 relevant_sessions: self.find_persona_sessions(best_persona),
511 invitation_context: format!(
512 "Inviting {} for {} minutes to help with: {}",
513 persona.name, duration_minutes, context
514 ),
515 suggested_duration_minutes: duration_minutes,
516 })
517 }
518
519 fn find_persona_sessions(&self, persona_name: &str) -> Vec<String> {
521 match persona_name {
524 "cheet" => vec!["session_14".to_string(), "session_27".to_string()],
525 "omni" => vec!["session_8".to_string(), "session_19".to_string()],
526 "trish" => vec!["session_22".to_string(), "session_31".to_string()],
527 _ => vec![],
528 }
529 }
530
531 pub fn get_patterns(&self) -> Vec<&CrossDomainPattern> {
533 self.patterns.values().collect()
534 }
535
536 pub fn get_patterns_by_type(&self, pattern_type: PatternType) -> Vec<&CrossDomainPattern> {
538 self.patterns
539 .values()
540 .filter(|p| p.pattern_type == pattern_type)
541 .collect()
542 }
543}
544
545#[derive(Debug, Clone, Serialize, Deserialize)]
547pub struct PersonaProfile {
548 name: String,
549 expertise: Vec<String>,
550 personality_traits: Vec<String>,
551 favorite_patterns: Vec<PatternType>,
552}
553
554#[derive(Debug, Clone, Serialize, Deserialize)]
556pub struct ProjectConnection {
557 pub project_a: PathBuf,
558 pub project_b: PathBuf,
559 pub connection_type: ConnectionType,
560 pub shared_patterns: Vec<String>, pub strength: f32,
562}
563
564#[derive(Debug, Clone, Serialize, Deserialize)]
565pub enum ConnectionType {
566 SharedDomain, SharedTechnology, SharedPatterns, Evolution, Complementary, }