diff --git a/crates/terraphim-session-analyzer/src/analyzer.rs b/crates/terraphim-session-analyzer/src/analyzer.rs index c575e9b..8bc12ef 100644 --- a/crates/terraphim-session-analyzer/src/analyzer.rs +++ b/crates/terraphim-session-analyzer/src/analyzer.rs @@ -75,10 +75,10 @@ impl Analyzer { match self.analyze_session(parser, target_file) { Ok(analysis) => { // If target file specified, only include sessions with relevant operations - if let Some(_target) = target_file { - if analysis.file_operations.is_empty() { - return None; // Skip sessions without target file operations - } + if let Some(_target) = target_file + && analysis.file_operations.is_empty() + { + return None; // Skip sessions without target file operations } Some(Ok(analysis)) } diff --git a/crates/terraphim-session-analyzer/src/connectors/aider.rs b/crates/terraphim-session-analyzer/src/connectors/aider.rs index 72eddbe..049bb44 100644 --- a/crates/terraphim-session-analyzer/src/connectors/aider.rs +++ b/crates/terraphim-session-analyzer/src/connectors/aider.rs @@ -93,10 +93,10 @@ impl SessionConnector for AiderConnector { .is_some_and(|n| n == ".aider.chat.history.md") }) { - if let Some(limit) = options.limit { - if sessions.len() >= limit { - break; - } + if let Some(limit) = options.limit + && sessions.len() >= limit + { + break; } match self.parse_history_file(entry.path()) { @@ -122,10 +122,10 @@ impl AiderConnector { // New session starts with "# aider chat started at" if line.starts_with("# aider chat started at") { // Save previous session if exists - if let Some(builder) = current_session.take() { - if let Some(session) = builder.build(path) { - sessions.push(session); - } + if let Some(builder) = current_session.take() + && let Some(session) = builder.build(path) + { + sessions.push(session); } // Parse timestamp: "# aider chat started at 2025-06-19 14:32:16" @@ -137,10 +137,10 @@ impl AiderConnector { } // Don't forget the last session - if let Some(builder) = current_session { - if let Some(session) = builder.build(path) { - sessions.push(session); - } + if let Some(builder) = current_session + && let Some(session) = builder.build(path) + { + sessions.push(session); } Ok(sessions) diff --git a/crates/terraphim-session-analyzer/src/connectors/codex.rs b/crates/terraphim-session-analyzer/src/connectors/codex.rs index 3e0987b..95db847 100644 --- a/crates/terraphim-session-analyzer/src/connectors/codex.rs +++ b/crates/terraphim-session-analyzer/src/connectors/codex.rs @@ -127,10 +127,10 @@ impl SessionConnector for CodexConnector { .filter_map(|e| e.ok()) .filter(|e| e.path().extension().is_some_and(|ext| ext == "jsonl")) { - if let Some(limit) = options.limit { - if sessions.len() >= limit { - break; - } + if let Some(limit) = options.limit + && sessions.len() >= limit + { + break; } match self.parse_session_file(entry.path()) { diff --git a/crates/terraphim-session-analyzer/src/connectors/cursor.rs b/crates/terraphim-session-analyzer/src/connectors/cursor.rs index 70bcf60..cc771e5 100644 --- a/crates/terraphim-session-analyzer/src/connectors/cursor.rs +++ b/crates/terraphim-session-analyzer/src/connectors/cursor.rs @@ -131,11 +131,11 @@ impl SessionConnector for CursorConnector { } // Apply limit if specified - if let Some(limit) = options.limit { - if sessions.len() >= limit { - sessions.truncate(limit); - break; - } + if let Some(limit) = options.limit + && sessions.len() >= limit + { + sessions.truncate(limit); + break; } } diff --git a/crates/terraphim-session-analyzer/src/connectors/opencode.rs b/crates/terraphim-session-analyzer/src/connectors/opencode.rs index 9f43274..a860f03 100644 --- a/crates/terraphim-session-analyzer/src/connectors/opencode.rs +++ b/crates/terraphim-session-analyzer/src/connectors/opencode.rs @@ -86,30 +86,29 @@ impl SessionConnector for OpenCodeConnector { if line.trim().is_empty() { continue; } - if let Ok(entry) = serde_json::from_str::(line) { - if let Some(input) = entry.input { - if !input.is_empty() { - messages.push(NormalizedMessage { - idx, - role: "user".to_string(), - author: None, - content: input, - created_at: None, - extra: serde_json::json!({ - "mode": entry.mode, - "parts": entry.parts, - }), - }); - } - } + if let Ok(entry) = serde_json::from_str::(line) + && let Some(input) = entry.input + && !input.is_empty() + { + messages.push(NormalizedMessage { + idx, + role: "user".to_string(), + author: None, + content: input, + created_at: None, + extra: serde_json::json!({ + "mode": entry.mode, + "parts": entry.parts, + }), + }); } } // Apply limit if specified - if let Some(limit) = options.limit { - if limit > 0 { - messages.truncate(limit); - } + if let Some(limit) = options.limit + && limit > 0 + { + messages.truncate(limit); } if messages.is_empty() { diff --git a/crates/terraphim-session-analyzer/src/main.rs b/crates/terraphim-session-analyzer/src/main.rs index ed32679..8bb224a 100644 --- a/crates/terraphim-session-analyzer/src/main.rs +++ b/crates/terraphim-session-analyzer/src/main.rs @@ -351,10 +351,10 @@ fn list_sessions(cli: &Cli, detailed: bool, project_filter: Option<&str>) -> Res for analysis in &analyses { // Apply project filter if specified - if let Some(filter) = &project_filter { - if !analysis.project_path.contains(filter) { - continue; - } + if let Some(filter) = &project_filter + && !analysis.project_path.contains(filter) + { + continue; } println!("{} {}", "Session:".bold(), analysis.session_id.yellow()); @@ -401,7 +401,7 @@ fn list_sessions(cli: &Cli, detailed: bool, project_filter: Option<&str>) -> Res .filter(|a| { project_filter .as_ref() - .map_or(true, |f| a.project_path.contains(f)) + .is_none_or(|f| a.project_path.contains(f)) }) .count() } else { @@ -1003,24 +1003,22 @@ fn analyze_tools( .into_iter() .filter(|(name, stats)| { // Tool name filter - if let Some(tool_filter_str) = tool_filter { - if !name + if let Some(tool_filter_str) = tool_filter + && !name .to_lowercase() .contains(&tool_filter_str.to_lowercase()) - { - return false; - } + { + return false; } // Agent filter - if let Some(agent_filter_str) = agent_filter { - if !stats + if let Some(agent_filter_str) = agent_filter + && !stats .agents_using .iter() .any(|a| a.to_lowercase().contains(&agent_filter_str.to_lowercase())) - { - return false; - } + { + return false; } // Minimum usage filter @@ -1124,12 +1122,12 @@ fn find_session_path(session_id: &str, cli: &Cli) -> Result { .into_iter() .filter_map(|e| e.ok()) { - if entry.file_type().is_file() { - if let Some(name) = entry.file_name().to_str() { - if name.ends_with(".jsonl") && name.contains(session_id) { - return Ok(entry.path().to_path_buf()); - } - } + if entry.file_type().is_file() + && let Some(name) = entry.file_name().to_str() + && name.ends_with(".jsonl") + && name.contains(session_id) + { + return Ok(entry.path().to_path_buf()); } } @@ -1147,43 +1145,40 @@ fn extract_tool_invocations_from_session( for entry in parser.entries() { if let Message::Assistant { content, .. } = &entry.message { for block in content { - if let ContentBlock::ToolUse { name, input, .. } = block { - if name == "Bash" { - if let Some(command) = input.get("command").and_then(|v| v.as_str()) { - let matches = matcher.find_matches(command); - - for tool_match in matches { - // Parse the command context - if let Some((full_cmd, args, flags)) = - tool_analyzer::parse_command_context(command, tool_match.start) - { - if let Ok(timestamp) = models::parse_timestamp(&entry.timestamp) - { - // Map category string to ToolCategory enum - let category = match tool_match.category.as_str() { - "package-manager" => ToolCategory::PackageManager, - "version-control" => ToolCategory::Git, - "testing" => ToolCategory::Testing, - "linting" => ToolCategory::Linting, - "cloudflare" => ToolCategory::CloudDeploy, - _ => ToolCategory::Other(tool_match.category.clone()), - }; - - invocations.push(ToolInvocation { - timestamp, - tool_name: tool_match.tool_name.clone(), - tool_category: category, - command_line: full_cmd, - arguments: args, - flags, - exit_code: None, - agent_context: None, - session_id: entry.session_id.clone(), - message_id: entry.uuid.clone(), - }); - } - } - } + if let ContentBlock::ToolUse { name, input, .. } = block + && name == "Bash" + && let Some(command) = input.get("command").and_then(|v| v.as_str()) + { + let matches = matcher.find_matches(command); + + for tool_match in matches { + // Parse the command context + if let Some((full_cmd, args, flags)) = + tool_analyzer::parse_command_context(command, tool_match.start) + && let Ok(timestamp) = models::parse_timestamp(&entry.timestamp) + { + // Map category string to ToolCategory enum + let category = match tool_match.category.as_str() { + "package-manager" => ToolCategory::PackageManager, + "version-control" => ToolCategory::Git, + "testing" => ToolCategory::Testing, + "linting" => ToolCategory::Linting, + "cloudflare" => ToolCategory::CloudDeploy, + _ => ToolCategory::Other(tool_match.category.clone()), + }; + + invocations.push(ToolInvocation { + timestamp, + tool_name: tool_match.tool_name.clone(), + tool_category: category, + command_line: full_cmd, + arguments: args, + flags, + exit_code: None, + agent_context: None, + session_id: entry.session_id.clone(), + message_id: entry.uuid.clone(), + }); } } } diff --git a/crates/terraphim-session-analyzer/src/models.rs b/crates/terraphim-session-analyzer/src/models.rs index c584246..934ca5c 100644 --- a/crates/terraphim-session-analyzer/src/models.rs +++ b/crates/terraphim-session-analyzer/src/models.rs @@ -440,12 +440,11 @@ pub fn extract_file_path(input: &serde_json::Value) -> Option { } // For MultiEdit, check the edits array - if let Some(edits) = input.get("edits").and_then(|v| v.as_array()) { - if !edits.is_empty() { - if let Some(file_path) = input.get("file_path").and_then(|v| v.as_str()) { - return Some(file_path.to_string()); - } - } + if let Some(edits) = input.get("edits").and_then(|v| v.as_array()) + && !edits.is_empty() + && let Some(file_path) = input.get("file_path").and_then(|v| v.as_str()) + { + return Some(file_path.to_string()); } None diff --git a/crates/terraphim-session-analyzer/src/parser.rs b/crates/terraphim-session-analyzer/src/parser.rs index fa1f381..9c629a7 100644 --- a/crates/terraphim-session-analyzer/src/parser.rs +++ b/crates/terraphim-session-analyzer/src/parser.rs @@ -77,10 +77,10 @@ impl SessionParser { if session_id.is_empty() { session_id.clone_from(&entry.session_id); } - if project_path.is_empty() { - if let Some(cwd) = &entry.cwd { - project_path.clone_from(cwd); - } + if project_path.is_empty() + && let Some(cwd) = &entry.cwd + { + project_path.clone_from(cwd); } entries.push(entry); } @@ -178,10 +178,10 @@ impl SessionParser { .filter_map(|entry| { if let Message::Assistant { content, .. } = &entry.message { for block in content { - if let ContentBlock::ToolUse { name, input, id } = block { - if name == "Task" { - return self.parse_task_invocation(entry, input, id); - } + if let ContentBlock::ToolUse { name, input, id } = block + && name == "Task" + { + return self.parse_task_invocation(entry, input, id); } } } @@ -243,30 +243,26 @@ impl SessionParser { .filter_map(|entry| { if let Message::Assistant { content, .. } = &entry.message { for block in content { - if let ContentBlock::ToolUse { name, input, .. } = block { - if let Ok(op_type) = name.parse::() { - if let Some(file_path) = extract_file_path(input) { - let timestamp = match parse_timestamp(&entry.timestamp) { - Ok(ts) => ts, - Err(e) => { - warn!( - "Failed to parse timestamp '{}': {}", - entry.timestamp, e - ); - continue; - } - }; - - return Some(FileOperation { - timestamp, - operation: op_type, - file_path, - agent_context: None, // Will be set during analysis - session_id: self.session_id.clone(), - message_id: entry.uuid.clone(), - }); + if let ContentBlock::ToolUse { name, input, .. } = block + && let Ok(op_type) = name.parse::() + && let Some(file_path) = extract_file_path(input) + { + let timestamp = match parse_timestamp(&entry.timestamp) { + Ok(ts) => ts, + Err(e) => { + warn!("Failed to parse timestamp '{}': {}", entry.timestamp, e); + continue; } - } + }; + + return Some(FileOperation { + timestamp, + operation: op_type, + file_path, + agent_context: None, // Will be set during analysis + session_id: self.session_id.clone(), + message_id: entry.uuid.clone(), + }); } } } @@ -316,14 +312,12 @@ impl SessionParser { // Look for Task tool invocations if let Message::Assistant { content, .. } = &entry.message { for block in content { - if let ContentBlock::ToolUse { name, input, .. } = block { - if name == "Task" { - if let Some(agent_type) = - input.get("subagent_type").and_then(|v| v.as_str()) - { - return Some(agent_type.to_string()); - } - } + if let ContentBlock::ToolUse { name, input, .. } = block + && name == "Task" + && let Some(agent_type) = + input.get("subagent_type").and_then(|v| v.as_str()) + { + return Some(agent_type.to_string()); } } } @@ -465,45 +459,45 @@ fn extract_from_bash_command( session_id: &str, ) -> Option { for block in content { - if let ContentBlock::ToolUse { name, input, .. } = block { - if name == "Bash" { - // Extract the command from the input - let command = input.get("command").and_then(|v| v.as_str())?; - - // Find tool matches using the pattern matcher - let matches = matcher.find_matches(command); - - if let Some(tool_match) = matches.first() { - // Parse command context to extract arguments and flags - if let Some((full_cmd, arguments, flags)) = - tool_analyzer::parse_command_context(command, tool_match.start) - { - // Filter out shell built-ins - if !tool_analyzer::is_actual_tool(&tool_match.tool_name) { + if let ContentBlock::ToolUse { name, input, .. } = block + && name == "Bash" + { + // Extract the command from the input + let command = input.get("command").and_then(|v| v.as_str())?; + + // Find tool matches using the pattern matcher + let matches = matcher.find_matches(command); + + if let Some(tool_match) = matches.first() { + // Parse command context to extract arguments and flags + if let Some((full_cmd, arguments, flags)) = + tool_analyzer::parse_command_context(command, tool_match.start) + { + // Filter out shell built-ins + if !tool_analyzer::is_actual_tool(&tool_match.tool_name) { + continue; + } + + let timestamp = match parse_timestamp(&entry.timestamp) { + Ok(ts) => ts, + Err(e) => { + warn!("Failed to parse timestamp '{}': {}", entry.timestamp, e); continue; } - - let timestamp = match parse_timestamp(&entry.timestamp) { - Ok(ts) => ts, - Err(e) => { - warn!("Failed to parse timestamp '{}': {}", entry.timestamp, e); - continue; - } - }; - - return Some(ToolInvocation { - timestamp, - tool_name: tool_match.tool_name.clone(), - tool_category: ToolCategory::from_string(&tool_match.category), - command_line: full_cmd, - arguments, - flags, - exit_code: None, // Exit code not available from logs - agent_context: None, // Will be populated later - session_id: session_id.to_string(), - message_id: entry.uuid.clone(), - }); - } + }; + + return Some(ToolInvocation { + timestamp, + tool_name: tool_match.tool_name.clone(), + tool_category: ToolCategory::from_string(&tool_match.category), + command_line: full_cmd, + arguments, + flags, + exit_code: None, // Exit code not available from logs + agent_context: None, // Will be populated later + session_id: session_id.to_string(), + message_id: entry.uuid.clone(), + }); } } } diff --git a/crates/terraphim-session-analyzer/src/tool_analyzer.rs b/crates/terraphim-session-analyzer/src/tool_analyzer.rs index e1d7e4e..d6e04b8 100644 --- a/crates/terraphim-session-analyzer/src/tool_analyzer.rs +++ b/crates/terraphim-session-analyzer/src/tool_analyzer.rs @@ -167,10 +167,10 @@ pub fn calculate_tool_statistics( stat.total_invocations += 1; // Track agents - if let Some(ref agent) = inv.agent_context { - if !stat.agents_using.contains(agent) { - stat.agents_using.push(agent.clone()); - } + if let Some(ref agent) = inv.agent_context + && !stat.agents_using.contains(agent) + { + stat.agents_using.push(agent.clone()); } // Track sessions diff --git a/crates/terraphim-session-analyzer/tests/filename_target_filtering_tests.rs b/crates/terraphim-session-analyzer/tests/filename_target_filtering_tests.rs index 6880923..afe5795 100644 --- a/crates/terraphim-session-analyzer/tests/filename_target_filtering_tests.rs +++ b/crates/terraphim-session-analyzer/tests/filename_target_filtering_tests.rs @@ -752,16 +752,16 @@ mod cli_integration_tests { if let Some(start_idx) = json_start { let json_content = lines[start_idx..].join("\n"); - if let Ok(parsed) = serde_json::from_str::(&json_content) { - if let Some(analyses) = parsed.as_array() { - for analysis in analyses { - // Each analysis should have file_to_agents with content - if let Some(file_to_agents) = analysis.get("file_to_agents") { - assert!( - !file_to_agents.as_object().unwrap().is_empty(), - "With --files-only, each session should have modified files" - ); - } + if let Ok(parsed) = serde_json::from_str::(&json_content) + && let Some(analyses) = parsed.as_array() + { + for analysis in analyses { + // Each analysis should have file_to_agents with content + if let Some(file_to_agents) = analysis.get("file_to_agents") { + assert!( + !file_to_agents.as_object().unwrap().is_empty(), + "With --files-only, each session should have modified files" + ); } } } diff --git a/crates/terraphim_command_runtime/src/lib.rs b/crates/terraphim_command_runtime/src/lib.rs index 75c937c..6971055 100644 --- a/crates/terraphim_command_runtime/src/lib.rs +++ b/crates/terraphim_command_runtime/src/lib.rs @@ -44,7 +44,7 @@ pub async fn find_role_by_name_or_shortname( let query_lower = query.to_lowercase(); // First try exact match on name - for (name, _role) in config.roles.iter() { + for name in config.roles.keys() { if name.to_string().to_lowercase() == query_lower { return Some(name.clone()); } diff --git a/crates/terraphim_lsp/Cargo.toml b/crates/terraphim_lsp/Cargo.toml index 4673161..34c3944 100644 --- a/crates/terraphim_lsp/Cargo.toml +++ b/crates/terraphim_lsp/Cargo.toml @@ -21,7 +21,7 @@ path = "src/bin/terraphim-lsp.rs" required-features = ["terraphim-lsp"] [dependencies] -terraphim_negative_contribution = { path = "../terraphim_negative_contribution", version = "0.1.0" } +terraphim_negative_contribution = { path = "../terraphim_negative_contribution", version = "1.21.1" } terraphim_types = { version = "1.0.0" } tower-lsp = "0.20" tokio = { workspace = true, features = ["full"] } diff --git a/crates/terraphim_update/src/lib.rs b/crates/terraphim_update/src/lib.rs index a026cd9..5474efb 100644 --- a/crates/terraphim_update/src/lib.rs +++ b/crates/terraphim_update/src/lib.rs @@ -348,8 +348,7 @@ impl TerraphimUpdater { show_progress, ..Default::default() }; - if let Err(e) = - downloader::download_with_retry(&asset_url, &archive_path, Some(dl_cfg)) + if let Err(e) = downloader::download_with_retry(&asset_url, &archive_path, Some(dl_cfg)) { // Transport failure -> Err so the caller can fall back. return Err(anyhow!("download failed: {e}"));