use crate::generator::agent_executor::{AgentExecuteParams, extract};
use crate::{
generator::{
context::GeneratorContext,
preprocess::extractors::language_processors::LanguageProcessorManager,
},
types::{
code::{CodeDossier, CodeInsight},
project_structure::ProjectStructure,
},
utils::{sources::read_dependency_code_source, threads::do_parallel_with_limit},
};
use anyhow::Result;
pub struct CodeAnalyze {
language_processor: LanguageProcessorManager,
}
impl CodeAnalyze {
pub fn new() -> Self {
Self {
language_processor: LanguageProcessorManager::new(),
}
}
pub async fn execute(
&self,
context: &GeneratorContext,
codes: &Vec<CodeDossier>,
project_structure: &ProjectStructure,
) -> Result<Vec<CodeInsight>> {
let max_parallels = context.config.llm.max_parallels;
let analysis_futures: Vec<_> = codes
.iter()
.map(|code| {
let code_clone = code.clone();
let context_clone = context.clone();
let project_structure_clone = project_structure.clone();
let language_processor = self.language_processor.clone();
Box::pin(async move {
let code_analyze = CodeAnalyze { language_processor };
let agent_params = code_analyze
.prepare_single_code_agent_params(&project_structure_clone, &code_clone)
.await?;
let mut code_insight =
extract::<CodeInsight>(&context_clone, agent_params).await?;
code_insight.code_dossier.source_summary = code_clone.source_summary.to_owned();
Result::<CodeInsight>::Ok(code_insight)
})
})
.collect();
let analysis_results = do_parallel_with_limit(analysis_futures, max_parallels).await;
let mut code_insights = Vec::new();
for result in analysis_results {
match result {
Ok(code_insight) => {
code_insights.push(code_insight);
}
Err(e) => {
eprintln!("❌ Code analysis failed: {}", e);
return Err(e);
}
}
}
println!("✓ Concurrent code analysis completed, successfully analyzed {} files", code_insights.len());
Ok(code_insights)
}
}
impl CodeAnalyze {
async fn prepare_single_code_agent_params(
&self,
project_structure: &ProjectStructure,
codes: &CodeDossier,
) -> Result<AgentExecuteParams> {
let code_analyse = self.analyze_code_by_rules(codes, project_structure).await?;
let prompt_user = self.build_code_analysis_prompt(project_structure, &code_analyse);
let prompt_sys = include_str!("prompts/code_analyze_sys.tpl").to_string();
Ok(AgentExecuteParams {
prompt_sys,
prompt_user,
cache_scope: "ai_code_insight".to_string(),
log_tag: codes.name.to_string(),
})
}
}
impl CodeAnalyze {
fn build_code_analysis_prompt(
&self,
project_structure: &ProjectStructure,
analysis: &CodeInsight,
) -> String {
let project_path = &project_structure.root_path;
let dependency_code =
read_dependency_code_source(&self.language_processor, analysis, project_path);
format!(
include_str!("prompts/code_analyze_user.tpl"),
analysis.code_dossier.name,
analysis.code_dossier.file_path.display(),
analysis.code_dossier.code_purpose.display_name(),
analysis.code_dossier.importance_score,
analysis.responsibilities.join(", "),
analysis.interfaces.len(),
analysis.dependencies.len(),
analysis.complexity_metrics.lines_of_code,
analysis.complexity_metrics.cyclomatic_complexity,
analysis.code_dossier.source_summary,
dependency_code
)
}
async fn analyze_code_by_rules(
&self,
code: &CodeDossier,
project_structure: &ProjectStructure,
) -> Result<CodeInsight> {
let full_path = project_structure.root_path.join(&code.file_path);
let content = if full_path.exists() {
tokio::fs::read_to_string(&full_path).await?
} else {
String::new()
};
let interfaces = self
.language_processor
.extract_interfaces(&code.file_path, &content);
let dependencies = self
.language_processor
.extract_dependencies(&code.file_path, &content);
let complexity_metrics = self
.language_processor
.calculate_complexity_metrics(&content);
Ok(CodeInsight {
code_dossier: code.clone(),
detailed_description: format!("Detailed analysis of {}", code.name),
interfaces,
dependencies,
complexity_metrics,
responsibilities: vec![],
})
}
}