terraphim_session_analyzer/
lib.rs1pub mod analyzer;
38pub mod models;
39pub mod parser;
40pub mod patterns;
41pub mod reporter;
42pub mod tool_analyzer;
43
44#[cfg(feature = "terraphim")]
45pub mod kg;
46
47pub mod connectors;
48
49pub use analyzer::{Analyzer, SummaryStats};
51pub use models::{
52 get_agent_category, normalize_agent_name, AgentAttribution, AgentInvocation, AgentStatistics,
53 AgentToolCorrelation, AnalyzerConfig, CollaborationPattern, FileOperation, SessionAnalysis,
54 ToolAnalysis, ToolCategory, ToolChain, ToolInvocation, ToolStatistics,
55};
56pub use parser::{SessionParser, TimelineEvent, TimelineEventType};
57pub use patterns::{
58 create_matcher, load_patterns, AhoCorasickMatcher, PatternMatcher, ToolMatch, ToolMetadata,
59 ToolPattern,
60};
61pub use reporter::Reporter;
62
63pub const VERSION: &str = env!("CARGO_PKG_VERSION");
65
66pub const DEFAULT_SESSION_DIR: &str = ".claude/projects";
68
69pub const COMMON_AGENT_TYPES: &[&str] = &[
71 "architect",
72 "developer",
73 "backend-architect",
74 "frontend-developer",
75 "rust-performance-expert",
76 "rust-code-reviewer",
77 "debugger",
78 "technical-writer",
79 "test-writer-fixer",
80 "rapid-prototyper",
81 "devops-automator",
82 "overseer",
83 "ai-engineer",
84 "general-purpose",
85];
86
87pub mod utils {
89 use crate::models::*;
90 use std::path::Path;
91
92 #[must_use]
94 pub fn get_default_session_dir() -> Option<std::path::PathBuf> {
95 home::home_dir().map(|home| home.join(crate::DEFAULT_SESSION_DIR))
96 }
97
98 pub fn is_session_file<P: AsRef<Path>>(path: P) -> bool {
100 let path = path.as_ref();
101 path.extension() == Some("jsonl".as_ref())
102 && path
103 .file_name()
104 .and_then(|name| name.to_str())
105 .is_some_and(|name| name.len() == 42) }
107
108 pub fn extract_project_name<P: AsRef<Path>>(session_path: P) -> Option<String> {
110 let path = session_path.as_ref();
111 path.parent()
112 .and_then(|parent| parent.file_name())
113 .and_then(|name| name.to_str())
114 .map(|name| {
115 name.replace("-home-", "/home/").replace('-', "/")
117 })
118 }
119
120 #[must_use]
122 pub fn filter_by_project<'a>(
123 analyses: &'a [SessionAnalysis],
124 project_filter: &str,
125 ) -> Vec<&'a SessionAnalysis> {
126 analyses
127 .iter()
128 .filter(|analysis| analysis.project_path.contains(project_filter))
129 .collect()
130 }
131
132 #[must_use]
134 pub fn get_unique_agents(analyses: &[SessionAnalysis]) -> Vec<String> {
135 let mut agents: std::collections::HashSet<String> = std::collections::HashSet::new();
136
137 for analysis in analyses {
138 for agent in &analysis.agents {
139 agents.insert(agent.agent_type.clone());
140 }
141 }
142
143 let mut sorted: Vec<String> = agents.into_iter().collect();
144 sorted.sort();
145 sorted
146 }
147
148 #[must_use]
150 pub fn total_session_time(analyses: &[SessionAnalysis]) -> u64 {
151 analyses.iter().map(|a| a.duration_ms).sum()
152 }
153
154 #[must_use]
156 pub fn most_productive_session(analyses: &[SessionAnalysis]) -> Option<&SessionAnalysis> {
157 analyses.iter().max_by_key(|a| a.file_to_agents.len())
158 }
159
160 #[must_use]
162 pub fn sessions_with_agent<'a>(
163 analyses: &'a [SessionAnalysis],
164 agent_type: &str,
165 ) -> Vec<&'a SessionAnalysis> {
166 analyses
167 .iter()
168 .filter(|analysis| {
169 analysis
170 .agents
171 .iter()
172 .any(|agent| agent.agent_type == agent_type)
173 })
174 .collect()
175 }
176}
177
178#[cfg(test)]
179mod tests {
180 use crate::utils::*;
181
182 #[test]
183 fn test_is_session_file() {
184 assert!(is_session_file(
185 "b325985c-5c1c-48f1-97e2-e3185bb55886.jsonl"
186 ));
187 assert!(!is_session_file("regular-file.txt"));
188 assert!(!is_session_file("short.jsonl"));
189 }
190
191 #[test]
192 fn test_extract_project_name() {
193 let path = "/home/alex/.claude/projects/-home-alex-projects-zestic-at-charm/session.jsonl";
194 let project = extract_project_name(path);
195 assert_eq!(
196 project,
197 Some("/home/alex/projects/zestic/at/charm".to_string())
198 );
199 }
200
201 #[test]
202 fn test_get_default_session_dir() {
203 let dir = get_default_session_dir();
204 assert!(dir.is_some());
205
206 let path = dir.unwrap();
207 assert!(path.to_string_lossy().contains(".claude"));
208 assert!(path.to_string_lossy().contains("projects"));
209 }
210}