use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use crate::tools::agentic::{
AgenticContext, AgenticTool, AgenticToolResult, InteractionRole, ToolGoal,
};
use crate::tools::ToolError;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LanguageInfo {
pub language: String,
pub confidence: f64,
pub file_extension: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ComplexityMetrics {
pub lines_of_code: usize,
pub cyclomatic_complexity: usize,
pub function_count: usize,
pub max_function_lines: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ReviewStrategy {
Quick,
Standard,
Deep,
}
pub struct SmartCodeReviewTool {
name: String,
description: String,
language_patterns: HashMap<String, Vec<String>>,
}
impl Default for SmartCodeReviewTool {
fn default() -> Self {
let mut language_patterns = HashMap::new();
language_patterns.insert("rust".to_string(), vec![".rs".to_string()]);
language_patterns.insert("python".to_string(), vec![".py".to_string()]);
language_patterns.insert(
"javascript".to_string(),
vec![".js".to_string(), ".jsx".to_string()],
);
language_patterns.insert(
"typescript".to_string(),
vec![".ts".to_string(), ".tsx".to_string()],
);
language_patterns.insert("go".to_string(), vec![".go".to_string()]);
language_patterns.insert("java".to_string(), vec![".java".to_string()]);
language_patterns.insert("c".to_string(), vec![".c".to_string(), ".h".to_string()]);
language_patterns.insert(
"cpp".to_string(),
vec![".cpp".to_string(), ".hpp".to_string(), ".cc".to_string()],
);
Self {
name: "smart_code_review".to_string(),
description: "Autonomous code review tool that adapts its strategy based on code complexity and language".to_string(),
language_patterns,
}
}
}
impl SmartCodeReviewTool {
pub fn new() -> Self {
Self::default()
}
fn detect_language(&self, file_path: Option<&str>, content: &str) -> LanguageInfo {
if let Some(path) = file_path {
let path_lower = path.to_lowercase();
for (lang, extensions) in &self.language_patterns {
for ext in extensions {
if path_lower.ends_with(ext) {
return LanguageInfo {
language: lang.clone(),
confidence: 0.95,
file_extension: ext.clone(),
};
}
}
}
}
self.detect_language_from_content(content)
}
fn detect_language_from_content(&self, content: &str) -> LanguageInfo {
let content = content.trim();
if content.contains("fn ") && content.contains("use std::") {
return LanguageInfo {
language: "rust".to_string(),
confidence: 0.85,
file_extension: ".rs".to_string(),
};
}
if content.contains("def ") && (content.contains(":") && content.contains("import ")) {
return LanguageInfo {
language: "python".to_string(),
confidence: 0.85,
file_extension: ".py".to_string(),
};
}
if content.contains("const ") || content.contains("let ") || content.contains("function ") {
if content.contains(": ") && content.contains("interface ") {
return LanguageInfo {
language: "typescript".to_string(),
confidence: 0.80,
file_extension: ".ts".to_string(),
};
}
return LanguageInfo {
language: "javascript".to_string(),
confidence: 0.80,
file_extension: ".js".to_string(),
};
}
if content.contains("package ") && content.contains("func ") {
return LanguageInfo {
language: "go".to_string(),
confidence: 0.85,
file_extension: ".go".to_string(),
};
}
LanguageInfo {
language: "unknown".to_string(),
confidence: 0.0,
file_extension: ".txt".to_string(),
}
}
fn calculate_complexity(&self, content: &str) -> ComplexityMetrics {
let lines: Vec<&str> = content.lines().collect();
let lines_of_code = lines.len();
let function_keywords = ["fn ", "def ", "function ", "func "];
let function_count = lines
.iter()
.filter(|line| {
let trimmed = line.trim();
function_keywords.iter().any(|kw| trimmed.starts_with(kw))
})
.count();
let control_flow = [
"if ", "for ", "while ", "match ", "switch ", "?", "&&", "||",
];
let cyclomatic_complexity = lines
.iter()
.map(|line| {
control_flow
.iter()
.map(|kw| line.matches(kw).count())
.sum::<usize>()
})
.sum::<usize>()
+ 1;
let max_function_lines = if function_count > 0 {
lines_of_code / function_count.max(1)
} else {
lines_of_code
};
ComplexityMetrics {
lines_of_code,
cyclomatic_complexity,
function_count,
max_function_lines,
}
}
fn determine_strategy(&self, metrics: &ComplexityMetrics) -> ReviewStrategy {
if metrics.lines_of_code < 50 && metrics.function_count <= 2 {
ReviewStrategy::Quick
} else if metrics.cyclomatic_complexity > 20 || metrics.lines_of_code > 500 {
ReviewStrategy::Deep
} else {
ReviewStrategy::Standard
}
}
fn quick_review(&self, content: &str, lang: &LanguageInfo) -> Vec<String> {
let mut issues = Vec::new();
if content.len() > 10000 {
issues.push("⚠️ File is quite long, consider splitting".to_string());
}
match lang.language.as_str() {
"rust" if !content.contains("///") && !content.contains("//") => {
issues.push("⚠️ No documentation comments found".to_string());
}
"python" if !content.contains("\"\"\"") && !content.contains("#") => {
issues.push("⚠️ No docstrings or comments found".to_string());
}
_ => {}
}
if issues.is_empty() {
issues.push("✅ Quick review passed".to_string());
}
issues
}
fn standard_review(&self, content: &str, lang: &LanguageInfo) -> Vec<String> {
let mut issues = self.quick_review(content, lang);
let lines: Vec<&str> = content.lines().collect();
let todo_count = lines
.iter()
.filter(|l| l.contains("TODO") || l.contains("FIXME"))
.count();
if todo_count > 0 {
issues.push(format!("📋 Found {} TODO/FIXME comments", todo_count));
}
let long_lines = lines
.iter()
.enumerate()
.filter(|(_, l)| l.len() > 120)
.count();
if long_lines > 0 {
issues.push(format!(
"📏 Found {} lines exceeding 120 characters",
long_lines
));
}
match lang.language.as_str() {
"rust" => {
if content.contains("unwrap()") {
let unwrap_count = content.matches("unwrap()").count();
issues.push(format!(
"⚠️ Found {} uses of unwrap() - consider proper error handling",
unwrap_count
));
}
if content.contains("panic!") {
issues.push("⚠️ Found panic! macro - ensure these are justified".to_string());
}
}
"python" if content.contains("except:") && !content.contains("except ") => {
issues.push("⚠️ Found bare except: - use specific exceptions".to_string());
}
_ => {}
}
issues
}
fn deep_review(&self, content: &str, lang: &LanguageInfo) -> Vec<String> {
let mut issues = self.standard_review(content, lang);
let lines: Vec<&str> = content.lines().collect();
let mut line_counts: HashMap<String, usize> = HashMap::new();
for line in &lines {
let trimmed = line.trim().to_string();
if trimmed.len() > 20 {
*line_counts.entry(trimmed).or_insert(0) += 1;
}
}
let duplicates: Vec<_> = line_counts.iter().filter(|(_, c)| **c > 2).collect();
if !duplicates.is_empty() {
issues.push(format!(
"🔍 Found {} potentially duplicated code blocks",
duplicates.len()
));
}
let metrics = self.calculate_complexity(content);
if metrics.cyclomatic_complexity > 20 {
issues.push(format!(
"🚨 High cyclomatic complexity: {}. Consider refactoring into smaller functions",
metrics.cyclomatic_complexity
));
}
let security_issues = self.check_security_issues(content, lang);
issues.extend(security_issues);
issues
}
fn check_security_issues(&self, content: &str, lang: &LanguageInfo) -> Vec<String> {
let mut issues = Vec::new();
match lang.language.as_str() {
"rust" if content.contains("unsafe ") => {
let unsafe_count = content.matches("unsafe ").count();
issues.push(format!(
"🚨 Found {} unsafe blocks - ensure memory safety is maintained",
unsafe_count
));
}
"python" => {
if content.contains("eval(") {
issues.push("🚨 Found eval() - potential security risk".to_string());
}
if content.contains("exec(") {
issues.push("🚨 Found exec() - potential security risk".to_string());
}
}
"javascript" | "typescript" => {
if content.contains("eval(") {
issues.push("🚨 Found eval() - potential security risk".to_string());
}
if content.contains("innerHTML") {
issues.push("⚠️ Found innerHTML - potential XSS risk".to_string());
}
}
_ => {}
}
issues
}
fn has_critical_issues(&self, issues: &[String]) -> bool {
issues.iter().any(|i| i.starts_with("🚨"))
}
fn count_by_severity(&self, issues: &[String]) -> (usize, usize, usize) {
let critical = issues.iter().filter(|i| i.starts_with("🚨")).count();
let warning = issues.iter().filter(|i| i.starts_with("⚠️")).count();
let info = issues
.iter()
.filter(|i| {
i.starts_with("✅")
|| i.starts_with("📋")
|| i.starts_with("📏")
|| i.starts_with("🔍")
})
.count();
(critical, warning, info)
}
}
#[async_trait]
impl AgenticTool for SmartCodeReviewTool {
fn name(&self) -> &str {
&self.name
}
fn description(&self) -> &str {
&self.description
}
async fn execute(
&self,
goal: ToolGoal,
context: &mut AgenticContext,
) -> Result<AgenticToolResult, ToolError> {
let file_path = goal.params.get("file_path").and_then(|v| v.as_str());
let content = goal
.params
.get("content")
.and_then(|v| v.as_str())
.ok_or_else(|| {
ToolError::InvalidArguments("Missing 'content' parameter".to_string())
})?;
context.record_interaction(
InteractionRole::System,
format!("Starting smart code review for goal: {}", goal.intent),
);
let language = self.detect_language(file_path, content);
context.record_interaction_with_metadata(
InteractionRole::Assistant,
format!(
"Detected language: {} (confidence: {})",
language.language, language.confidence
),
serde_json::to_value(&language).unwrap_or_default(),
);
let complexity = self.calculate_complexity(content);
context.record_interaction_with_metadata(
InteractionRole::Assistant,
format!(
"Calculated complexity: {} lines, {} functions, complexity score {}",
complexity.lines_of_code,
complexity.function_count,
complexity.cyclomatic_complexity
),
serde_json::to_value(&complexity).unwrap_or_default(),
);
let strategy = self.determine_strategy(&complexity);
let strategy_str = match strategy {
ReviewStrategy::Quick => "Quick",
ReviewStrategy::Standard => "Standard",
ReviewStrategy::Deep => "Deep",
};
context.record_interaction(
InteractionRole::Assistant,
format!("Selected review strategy: {}", strategy_str),
);
let issues = match strategy {
ReviewStrategy::Quick => self.quick_review(content, &language),
ReviewStrategy::Standard => self.standard_review(content, &language),
ReviewStrategy::Deep => self.deep_review(content, &language),
};
let (critical, warning, info) = self.count_by_severity(&issues);
if self.has_critical_issues(&issues) && context.is_first_iteration() {
let review_state = serde_json::json!({
"language": language,
"complexity": complexity,
"strategy": strategy_str,
"issues": issues,
"critical_count": critical,
"warning_count": warning,
"info_count": info,
});
context.update_state(review_state).await;
return Ok(AgenticToolResult::need_clarification_with_options(
format!(
"Found {} critical issue(s) and {} warning(s) in the code. \
The critical issues may require immediate attention. \
How would you like to proceed?",
critical, warning
),
vec![
"Fix critical issues automatically (if possible)".to_string(),
"Show me detailed explanations of the issues".to_string(),
"Continue with current code (I understand the risks)".to_string(),
"Generate refactoring suggestions".to_string(),
],
));
}
let result = serde_json::json!({
"language": language,
"complexity": complexity,
"strategy": strategy_str,
"summary": {
"critical": critical,
"warning": warning,
"info": info,
"total": issues.len(),
},
"issues": issues,
});
context.update_state(result.clone()).await;
Ok(AgenticToolResult::success(
serde_json::to_string_pretty(&result).unwrap_or_default(),
))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::tools::agentic::{AgenticContext, AgenticToolExecutor};
use crate::tools::ToolCall;
use std::sync::Arc;
struct MockExecutor;
#[async_trait]
impl AgenticToolExecutor for MockExecutor {
async fn execute(&self, _call: &ToolCall) -> Result<AgenticToolResult, ToolError> {
Ok(AgenticToolResult::success("mock"))
}
}
#[test]
fn test_language_detection_from_extension() {
let tool = SmartCodeReviewTool::new();
let lang = tool.detect_language(Some("test.rs"), "");
assert_eq!(lang.language, "rust");
assert_eq!(lang.confidence, 0.95);
let lang = tool.detect_language(Some("test.py"), "");
assert_eq!(lang.language, "python");
}
#[test]
fn test_language_detection_from_content() {
let tool = SmartCodeReviewTool::new();
let rust_code = r#"
use std::collections::HashMap;
fn main() {
println!("Hello");
}
"#;
let lang = tool.detect_language(None, rust_code);
assert_eq!(lang.language, "rust");
let python_code = r#"
import os
def main():
print("Hello")
"#;
let lang = tool.detect_language(None, python_code);
assert_eq!(lang.language, "python");
}
#[test]
fn test_complexity_calculation() {
let tool = SmartCodeReviewTool::new();
let code = r#"
fn main() {
if true {
for i in 0..10 {
while false {}
}
}
}
"#;
let metrics = tool.calculate_complexity(code);
assert!(metrics.lines_of_code > 0);
assert!(metrics.cyclomatic_complexity >= 4); }
#[test]
fn test_strategy_selection() {
let tool = SmartCodeReviewTool::new();
let simple = ComplexityMetrics {
lines_of_code: 30,
cyclomatic_complexity: 2,
function_count: 1,
max_function_lines: 30,
};
assert!(matches!(
tool.determine_strategy(&simple),
ReviewStrategy::Quick
));
let complex = ComplexityMetrics {
lines_of_code: 600,
cyclomatic_complexity: 30,
function_count: 10,
max_function_lines: 60,
};
assert!(matches!(
tool.determine_strategy(&complex),
ReviewStrategy::Deep
));
}
#[test]
fn test_security_issue_detection() {
let tool = SmartCodeReviewTool::new();
let rust_code = "unsafe { *ptr }";
let lang = LanguageInfo {
language: "rust".to_string(),
confidence: 1.0,
file_extension: ".rs".to_string(),
};
let issues = tool.check_security_issues(rust_code, &lang);
assert!(issues.iter().any(|i| i.contains("unsafe")));
let python_code = "eval(user_input)";
let lang = LanguageInfo {
language: "python".to_string(),
confidence: 1.0,
file_extension: ".py".to_string(),
};
let issues = tool.check_security_issues(python_code, &lang);
assert!(issues.iter().any(|i| i.contains("eval")));
}
#[tokio::test]
async fn test_smart_review_execution() {
let tool = SmartCodeReviewTool::new();
let executor: Arc<dyn AgenticToolExecutor> = Arc::new(MockExecutor);
let mut context = AgenticContext::new(executor);
let goal = ToolGoal::new(
"Review this Rust code",
serde_json::json!({
"file_path": "test.rs",
"content": r#"
fn main() {
println!("Hello");
}
"#
}),
);
let result = tool.execute(goal, &mut context).await;
assert!(result.is_ok());
let agentic_result = result.unwrap();
assert!(agentic_result.is_success());
assert!(!context.interaction_history.is_empty());
}
#[tokio::test]
async fn test_critical_issues_trigger_clarification() {
let tool = SmartCodeReviewTool::new();
let executor: Arc<dyn AgenticToolExecutor> = Arc::new(MockExecutor);
let mut context = AgenticContext::new(executor);
let goal = ToolGoal::new(
"Review this code with security issues",
serde_json::json!({
"file_path": "test.rs",
"content": r#"
// Line 1
// Line 2
// Line 3
// Line 4
// Line 5
unsafe fn dangerous() {
let ptr: *const i32 = std::ptr::null();
unsafe { *ptr }
}
fn complex_function(x: i32) -> i32 {
if x > 0 {
if x < 10 {
for i in 0..x {
while i < x {
if i == 5 {
return i;
}
}
}
}
}
x
}
fn another_complex(y: i32) -> i32 {
match y {
1 => 1,
2 => 2,
3 => 3,
4 => 4,
5 => 5,
_ => 0,
}
}
"#
}),
);
let result = tool.execute(goal, &mut context).await;
assert!(result.is_ok());
let agentic_result = result.unwrap();
assert!(
agentic_result.needs_clarification() || agentic_result.is_success(),
"Expected either clarification request (Deep strategy) or success (Standard strategy)"
);
}
}