use anyhow::Result;
use crate::generator::agent_executor::{AgentExecuteParams, extract};
use crate::types::code::CodeInsight;
use crate::{
generator::context::GeneratorContext,
types::{code_releationship::RelationshipAnalysis, project_structure::ProjectStructure},
utils::prompt_compressor::{CompressionConfig, PromptCompressor},
};
pub struct RelationshipsAnalyze {
prompt_compressor: PromptCompressor,
}
impl RelationshipsAnalyze {
pub fn new() -> Self {
Self {
prompt_compressor: PromptCompressor::new(CompressionConfig::default()),
}
}
pub async fn execute(
&self,
context: &GeneratorContext,
code_insights: &Vec<CodeInsight>,
_project_structure: &ProjectStructure,
) -> Result<RelationshipAnalysis> {
let agent_params = self
.build_optimized_analysis_params(context, code_insights)
.await?;
extract::<RelationshipAnalysis>(context, agent_params).await
}
async fn build_optimized_analysis_params(
&self,
context: &GeneratorContext,
code_insights: &[CodeInsight],
) -> Result<AgentExecuteParams> {
let prompt_sys = "You are a professional software architecture analyst.
You MUST return valid JSON only (no markdown, no code fences, no prose before/after JSON).
The JSON MUST match this exact schema and field names:
{
\"core_dependencies\": [
{
\"from\": \"string\",
\"to\": \"string\",
\"dependency_type\": \"Import|FunctionCall|Inheritance|Composition|DataFlow|Module\",
\"importance\": 1,
\"description\": \"string (optional)\"
}
],
\"architecture_layers\": [
{
\"name\": \"string\",
\"components\": [\"string\"],
\"level\": 1
}
],
\"key_insights\": [\"string\"]
}
Constraints:
- Never omit top-level keys. Always include all three arrays.
- Use plain strings for textual fields; never objects/arrays for those fields.
- Use integer values for \"importance\" and \"level\".
- Keep values concise and architecture-focused.
"
.to_string();
let mut sorted_insights: Vec<_> = code_insights.iter().collect();
sorted_insights.sort_by(|a, b| {
b.code_dossier
.importance_score
.partial_cmp(&a.code_dossier.importance_score)
.unwrap_or(std::cmp::Ordering::Equal)
});
let insights_content = self.build_insights_content(&sorted_insights);
let compression_result = self
.prompt_compressor
.compress_if_needed(context, &insights_content, "Code Insights")
.await?;
if compression_result.was_compressed {
println!(
" ✅ Compression complete: {} -> {} tokens",
compression_result.original_tokens, compression_result.compressed_tokens
);
}
let compressed_insights = compression_result.compressed_content;
let prompt_user = format!(
"Analyze the overall architectural relationship graph of this project based on the code insights below.
Output requirements (strict):
- Return JSON only.
- Do not use markdown code blocks.
- Do not include explanations outside JSON.
- Use exactly the allowed enum labels: Import, FunctionCall, Inheritance, Composition, DataFlow, Module.
- If uncertain, use Module as dependency_type.
## Core Code Insights
{}
## Analysis Requirements:
Generate a project-level dependency relationship graph, focusing on:
1. Dependencies between core modules
2. Key data flows
3. Architectural hierarchy
4. Potential circular dependencies",
compressed_insights
);
Ok(AgentExecuteParams {
prompt_sys,
prompt_user,
cache_scope: "ai_relationships_insights".to_string(),
log_tag: "Dependency Relationship Analysis".to_string(),
})
}
fn build_insights_content(&self, sorted_insights: &[&CodeInsight]) -> String {
sorted_insights
.iter()
.filter(|insight| insight.code_dossier.importance_score >= 0.6)
.take(150) .map(|insight| {
let dependencies_introduce = insight
.dependencies
.iter()
.take(20) .map(|r| format!("{}({})", r.name, r.dependency_type))
.collect::<Vec<_>>()
.join(", ");
format!(
"- {}: {} (path: `{}`, importance: {:.2}, complexity: {:.1}, dependencies: [{}])",
insight.code_dossier.name,
insight.code_dossier.code_purpose.display_name(),
insight.code_dossier.file_path.to_string_lossy(),
insight.code_dossier.importance_score,
insight.complexity_metrics.cyclomatic_complexity,
dependencies_introduce
)
})
.collect::<Vec<_>>()
.join("\n")
}
}