1use anyhow::Result;
7use serde::{Deserialize, Serialize};
8use std::collections::HashMap;
9use std::sync::{Arc, RwLock};
10use std::time::{Duration, SystemTime, UNIX_EPOCH};
11
12#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
14pub struct SessionId(pub String);
15
16impl SessionId {
17 pub fn new() -> Self {
19 let timestamp = SystemTime::now()
20 .duration_since(UNIX_EPOCH)
21 .unwrap_or(Duration::from_secs(0))
22 .as_millis();
23 let random: u32 = rand::random();
24 Self(format!("session_{}_{}", timestamp, random))
25 }
26}
27
28impl Default for SessionId {
29 fn default() -> Self {
30 Self::new()
31 }
32}
33
34#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
36pub enum WorkflowStage {
37 Discovery,
39 Mapping,
41 DeepDive,
43 Synthesis,
45}
46
47impl WorkflowStage {
48 pub fn recommended_tools(&self) -> Vec<&'static str> {
50 match self {
51 WorkflowStage::Discovery => vec![
52 "repository_stats",
53 "search_content",
54 "find_files",
55 "content_stats",
56 ],
57 WorkflowStage::Mapping => vec![
58 "search_symbols",
59 "find_dependencies",
60 "detect_patterns",
61 "trace_path",
62 ],
63 WorkflowStage::DeepDive => vec![
64 "explain_symbol",
65 "trace_inheritance",
66 "analyze_decorators",
67 "find_references",
68 ],
69 WorkflowStage::Synthesis => vec!["analyze_complexity"],
70 }
71 }
72
73 pub fn next_stage(&self) -> Option<WorkflowStage> {
75 match self {
76 WorkflowStage::Discovery => Some(WorkflowStage::Mapping),
77 WorkflowStage::Mapping => Some(WorkflowStage::DeepDive),
78 WorkflowStage::DeepDive => Some(WorkflowStage::Synthesis),
79 WorkflowStage::Synthesis => None,
80 }
81 }
82}
83
84#[derive(Debug, Clone, Serialize, Deserialize)]
86pub struct AnalysisRecord {
87 pub tool_name: String,
89 pub parameters: serde_json::Value,
91 pub timestamp: u64,
93 pub success: bool,
95 pub result_summary: Option<String>,
97 pub symbols_analyzed: Vec<String>,
99}
100
101impl AnalysisRecord {
102 pub fn new(
104 tool_name: String,
105 parameters: serde_json::Value,
106 success: bool,
107 result_summary: Option<String>,
108 symbols_analyzed: Vec<String>,
109 ) -> Self {
110 let timestamp = SystemTime::now()
111 .duration_since(UNIX_EPOCH)
112 .unwrap_or(Duration::from_secs(0))
113 .as_secs();
114
115 Self {
116 tool_name,
117 parameters,
118 timestamp,
119 success,
120 result_summary,
121 symbols_analyzed,
122 }
123 }
124}
125
126#[derive(Debug, Clone, Default, Serialize, Deserialize)]
128pub struct AnalysisHistory {
129 pub records: Vec<AnalysisRecord>,
131 pub analyzed_symbols: std::collections::HashSet<String>,
133 pub discovered_patterns: Vec<String>,
135 pub focus_areas: Vec<String>,
137}
138
139impl AnalysisHistory {
140 pub fn add_record(&mut self, record: AnalysisRecord) {
142 for symbol in &record.symbols_analyzed {
144 self.analyzed_symbols.insert(symbol.clone());
145 }
146
147 self.records.push(record);
148 }
149
150 pub fn was_recently_analyzed(&self, symbol: &str, within_minutes: u64) -> bool {
152 let cutoff_time = SystemTime::now()
153 .duration_since(UNIX_EPOCH)
154 .unwrap_or(Duration::from_secs(0))
155 .as_secs()
156 - (within_minutes * 60);
157
158 self.records.iter().any(|record| {
159 record.timestamp > cutoff_time && record.symbols_analyzed.contains(&symbol.to_string())
160 })
161 }
162
163 pub fn recent_tools(&self, within_minutes: u64) -> Vec<String> {
165 let cutoff_time = SystemTime::now()
166 .duration_since(UNIX_EPOCH)
167 .unwrap_or(Duration::from_secs(0))
168 .as_secs()
169 - (within_minutes * 60);
170
171 self.records
172 .iter()
173 .filter(|record| record.timestamp > cutoff_time)
174 .map(|record| record.tool_name.clone())
175 .collect::<std::collections::HashSet<_>>()
176 .into_iter()
177 .collect()
178 }
179
180 pub fn detect_workflow_stage(&self) -> WorkflowStage {
182 let recent_tools = self.recent_tools(30); let discovery_tools = [
186 "repository_stats",
187 "search_content",
188 "find_files",
189 "content_stats",
190 ];
191 let mapping_tools = [
192 "search_symbols",
193 "find_dependencies",
194 "detect_patterns",
195 "trace_path",
196 ];
197 let deepdive_tools = [
198 "explain_symbol",
199 "trace_inheritance",
200 "analyze_decorators",
201 "find_references",
202 ];
203 let synthesis_tools = ["analyze_complexity"];
204
205 let discovery_count = recent_tools
206 .iter()
207 .filter(|t| discovery_tools.contains(&t.as_str()))
208 .count();
209 let mapping_count = recent_tools
210 .iter()
211 .filter(|t| mapping_tools.contains(&t.as_str()))
212 .count();
213 let deepdive_count = recent_tools
214 .iter()
215 .filter(|t| deepdive_tools.contains(&t.as_str()))
216 .count();
217 let synthesis_count = recent_tools
218 .iter()
219 .filter(|t| synthesis_tools.contains(&t.as_str()))
220 .count();
221
222 if synthesis_count > 0 {
224 WorkflowStage::Synthesis
225 } else if deepdive_count > mapping_count && deepdive_count > discovery_count {
226 WorkflowStage::DeepDive
227 } else if mapping_count > discovery_count {
228 WorkflowStage::Mapping
229 } else {
230 WorkflowStage::Discovery
231 }
232 }
233}
234
235#[derive(Debug, Clone, Serialize, Deserialize)]
237pub struct SessionState {
238 pub id: SessionId,
240 pub created_at: u64,
242 pub last_activity: u64,
244 pub history: AnalysisHistory,
246 pub current_stage: WorkflowStage,
248 pub metadata: HashMap<String, serde_json::Value>,
250}
251
252impl SessionState {
253 pub fn new() -> Self {
255 let now = SystemTime::now()
256 .duration_since(UNIX_EPOCH)
257 .unwrap_or(Duration::from_secs(0))
258 .as_secs();
259
260 Self {
261 id: SessionId::new(),
262 created_at: now,
263 last_activity: now,
264 history: AnalysisHistory::default(),
265 current_stage: WorkflowStage::Discovery,
266 metadata: HashMap::new(),
267 }
268 }
269
270 pub fn touch(&mut self) {
272 self.last_activity = SystemTime::now()
273 .duration_since(UNIX_EPOCH)
274 .unwrap_or(Duration::from_secs(0))
275 .as_secs();
276
277 self.current_stage = self.history.detect_workflow_stage();
279 }
280
281 pub fn record_analysis(
283 &mut self,
284 tool_name: String,
285 parameters: serde_json::Value,
286 success: bool,
287 result_summary: Option<String>,
288 symbols_analyzed: Vec<String>,
289 ) {
290 let record = AnalysisRecord::new(
291 tool_name,
292 parameters,
293 success,
294 result_summary,
295 symbols_analyzed,
296 );
297
298 self.history.add_record(record);
299 self.touch();
300 }
301
302 pub fn is_expired(&self) -> bool {
304 let now = SystemTime::now()
305 .duration_since(UNIX_EPOCH)
306 .unwrap_or(Duration::from_secs(0))
307 .as_secs();
308
309 now - self.last_activity > 3600 }
311}
312
313impl Default for SessionState {
314 fn default() -> Self {
315 Self::new()
316 }
317}
318
319#[derive(Debug)]
321pub struct SessionManager {
322 sessions: Arc<RwLock<HashMap<SessionId, SessionState>>>,
324}
325
326impl SessionManager {
327 pub fn new() -> Self {
329 Self {
330 sessions: Arc::new(RwLock::new(HashMap::new())),
331 }
332 }
333
334 pub fn create_session(&self) -> Result<SessionId> {
336 let session = SessionState::new();
337 let session_id = session.id.clone();
338
339 let mut sessions = self
340 .sessions
341 .write()
342 .map_err(|_| anyhow::anyhow!("Failed to acquire write lock on sessions"))?;
343
344 sessions.insert(session_id.clone(), session);
345 Ok(session_id)
346 }
347
348 pub fn get_or_create_session(&self, session_id: Option<SessionId>) -> Result<SessionId> {
350 match session_id {
351 Some(id) => {
352 let sessions = self
353 .sessions
354 .read()
355 .map_err(|_| anyhow::anyhow!("Failed to acquire read lock on sessions"))?;
356
357 if sessions.contains_key(&id) {
358 Ok(id)
359 } else {
360 drop(sessions);
361 self.create_session()
362 }
363 }
364 None => self.create_session(),
365 }
366 }
367
368 pub fn get_session(&self, session_id: &SessionId) -> Result<Option<SessionState>> {
370 let sessions = self
371 .sessions
372 .read()
373 .map_err(|_| anyhow::anyhow!("Failed to acquire read lock on sessions"))?;
374
375 Ok(sessions.get(session_id).cloned())
376 }
377
378 pub fn record_analysis(
380 &self,
381 session_id: &SessionId,
382 tool_name: String,
383 parameters: serde_json::Value,
384 success: bool,
385 result_summary: Option<String>,
386 symbols_analyzed: Vec<String>,
387 ) -> Result<()> {
388 let mut sessions = self
389 .sessions
390 .write()
391 .map_err(|_| anyhow::anyhow!("Failed to acquire write lock on sessions"))?;
392
393 if let Some(session) = sessions.get_mut(session_id) {
394 session.record_analysis(
395 tool_name,
396 parameters,
397 success,
398 result_summary,
399 symbols_analyzed,
400 );
401 }
402
403 Ok(())
404 }
405
406 pub fn cleanup_expired_sessions(&self) -> Result<usize> {
408 let mut sessions = self
409 .sessions
410 .write()
411 .map_err(|_| anyhow::anyhow!("Failed to acquire write lock on sessions"))?;
412
413 let initial_count = sessions.len();
414 sessions.retain(|_, session| !session.is_expired());
415
416 Ok(initial_count - sessions.len())
417 }
418
419 pub fn active_session_count(&self) -> Result<usize> {
421 let sessions = self
422 .sessions
423 .read()
424 .map_err(|_| anyhow::anyhow!("Failed to acquire read lock on sessions"))?;
425
426 Ok(sessions.len())
427 }
428}
429
430impl Default for SessionManager {
431 fn default() -> Self {
432 Self::new()
433 }
434}
435
436#[cfg(test)]
437mod tests {
438 use super::*;
439
440 #[test]
441 fn test_session_id_generation() {
442 let id1 = SessionId::new();
443 let id2 = SessionId::new();
444 assert_ne!(id1, id2);
445 assert!(id1.0.starts_with("session_"));
446 }
447
448 #[test]
449 fn test_workflow_stage_progression() {
450 assert_eq!(
451 WorkflowStage::Discovery.next_stage(),
452 Some(WorkflowStage::Mapping)
453 );
454 assert_eq!(
455 WorkflowStage::Mapping.next_stage(),
456 Some(WorkflowStage::DeepDive)
457 );
458 assert_eq!(
459 WorkflowStage::DeepDive.next_stage(),
460 Some(WorkflowStage::Synthesis)
461 );
462 assert_eq!(WorkflowStage::Synthesis.next_stage(), None);
463 }
464
465 #[test]
466 fn test_workflow_stage_tools() {
467 let discovery_tools = WorkflowStage::Discovery.recommended_tools();
468 assert!(discovery_tools.contains(&"repository_stats"));
469 assert!(discovery_tools.contains(&"search_content"));
470 }
471
472 #[test]
473 fn test_analysis_history() {
474 let mut history = AnalysisHistory::default();
475
476 let record = AnalysisRecord::new(
477 "explain_symbol".to_string(),
478 serde_json::json!({"symbol_id": "test123"}),
479 true,
480 Some("Symbol explained successfully".to_string()),
481 vec!["test123".to_string()],
482 );
483
484 history.add_record(record);
485 assert_eq!(history.records.len(), 1);
486 assert!(history.analyzed_symbols.contains("test123"));
487 }
488
489 #[test]
490 fn test_session_state_creation() {
491 let session = SessionState::new();
492 assert_eq!(session.current_stage, WorkflowStage::Discovery);
493 assert!(session.history.records.is_empty());
494 }
495
496 #[test]
497 fn test_session_manager() {
498 let manager = SessionManager::new();
499
500 let session_id = manager.create_session().unwrap();
502
503 let session = manager.get_session(&session_id).unwrap();
505 assert!(session.is_some());
506
507 manager
509 .record_analysis(
510 &session_id,
511 "test_tool".to_string(),
512 serde_json::json!({}),
513 true,
514 None,
515 vec![],
516 )
517 .unwrap();
518
519 let updated_session = manager.get_session(&session_id).unwrap().unwrap();
521 assert_eq!(updated_session.history.records.len(), 1);
522 }
523
524 #[test]
525 fn test_workflow_stage_detection() {
526 let mut history = AnalysisHistory::default();
527
528 for tool in ["search_symbols", "find_dependencies"] {
530 history.add_record(AnalysisRecord::new(
531 tool.to_string(),
532 serde_json::json!({}),
533 true,
534 None,
535 vec![],
536 ));
537 }
538
539 let stage = history.detect_workflow_stage();
540 assert_eq!(stage, WorkflowStage::Mapping);
541 }
542}