use crate::core::adapters::ai::KandilAI;
use crate::core::agents::base::{Agent, AgentState};
use anyhow::Result;
use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::Arc;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ArchitecturePatterns {
pub patterns: HashMap<String, Pattern>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Pattern {
pub name: String,
pub description: String,
pub pros: Vec<String>,
pub cons: Vec<String>,
pub use_cases: Vec<String>,
pub alternatives: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ArchitectureReview {
pub score: u8,
pub recommendations: Vec<String>,
pub pattern_suggestions: Vec<String>,
pub identified_issues: Vec<String>,
pub architecture_diagram: Option<String>, }
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ArchitectureDecision {
pub id: String,
pub title: String,
pub status: DecisionStatus,
pub context: String,
pub decision: String,
pub consequences: Vec<String>,
pub date: String,
pub author: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum DecisionStatus {
Proposed,
Accepted,
Rejected,
Deprecated,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ArchitectureDecisionRecord {
pub decisions: Vec<ArchitectureDecision>,
pub decision_log: String,
}
pub struct ArchitectSimulation {
pub ai: Arc<KandilAI>,
knowledge: ArchitecturePatterns,
decision_log: Vec<ArchitectureDecision>,
}
impl ArchitectSimulation {
pub fn new(ai: Arc<KandilAI>) -> Result<Self> {
let mut patterns = HashMap::new();
patterns.insert(
"clean_architecture".to_string(),
Pattern {
name: "Clean Architecture".to_string(),
description: "Layers: Entities → Use Cases → Interface Adapters → Frameworks"
.to_string(),
pros: vec![
"Testability".to_string(),
"Separation of Concerns".to_string(),
"Independent of Frameworks".to_string(),
],
cons: vec![
"Overhead for small projects".to_string(),
"Learning curve".to_string(),
"Additional complexity".to_string(),
],
use_cases: vec![
"Enterprise apps".to_string(),
"Multi-platform".to_string(),
"Long-lived systems".to_string(),
],
alternatives: vec![
"Hexagonal Architecture".to_string(),
"Layered Architecture".to_string(),
],
},
);
patterns.insert(
"hexagonal".to_string(),
Pattern {
name: "Hexagonal Architecture".to_string(),
description: "Also known as Ports and Adapters".to_string(),
pros: vec![
"Testability".to_string(),
"Framework independence".to_string(),
"Clear boundaries".to_string(),
],
cons: vec!["Complexity".to_string(), "Multiple layers".to_string()],
use_cases: vec![
"Integration-heavy systems".to_string(),
"Testing scenarios".to_string(),
],
alternatives: vec![
"Clean Architecture".to_string(),
"Layered Architecture".to_string(),
],
},
);
Ok(Self {
ai,
knowledge: ArchitecturePatterns { patterns },
decision_log: vec![],
})
}
pub async fn review_design(&self, uml: &str) -> Result<ArchitectureReview> {
let prompt = format!(
r#"As a Principal Architect with 20 years experience:
Review this architecture diagram:
{}
Evaluate for:
- Design consistency
- Pattern application
- Scalability concerns
- Performance implications
- Security considerations
- Maintainability
Provide specific recommendations.
"#,
uml
);
let review_text = self.ai.chat(&prompt).await?;
Ok(ArchitectureReview {
score: 85,
recommendations: vec![
"Consider implementing a caching layer".to_string(),
"Add monitoring and observability components".to_string(),
],
pattern_suggestions: vec!["Clean Architecture".to_string()],
identified_issues: vec!["Tight coupling between services".to_string()],
architecture_diagram: None,
})
}
pub async fn make_architecture_decision(
&mut self,
context: &str,
decision: &str,
) -> Result<ArchitectureDecision> {
let decision_id = format!("ADR-{}", self.decision_log.len() + 1);
let new_decision = ArchitectureDecision {
id: decision_id,
title: format!(
"Decision for: {}",
context.chars().take(30).collect::<String>()
),
status: DecisionStatus::Proposed,
context: context.to_string(),
decision: decision.to_string(),
consequences: vec![
"Will impact performance".to_string(),
"May require additional resources".to_string(),
],
date: chrono::Utc::now().format("%Y-%m-%d").to_string(),
author: "Architect Simulation".to_string(),
};
self.decision_log.push(new_decision.clone());
Ok(new_decision)
}
pub async fn generate_adr(&self, decision_id: &str) -> Result<String> {
if let Some(decision) = self.decision_log.iter().find(|d| d.id == decision_id) {
Ok(format!(
"# {}\n\n## Status\n{:?}\n\n## Context\n{}\n\n## Decision\n{}\n\n## Consequences\n- {}",
decision.title,
decision.status,
decision.context,
decision.decision,
decision.consequences.join("\n- ")
))
} else {
Err(anyhow::anyhow!("Decision {} not found", decision_id))
}
}
pub fn get_architecture_pattern(&self, name: &str) -> Option<&Pattern> {
self.knowledge.patterns.get(name)
}
pub fn get_decision_log(&self) -> &Vec<ArchitectureDecision> {
&self.decision_log
}
}
#[async_trait]
impl Agent for ArchitectSimulation {
async fn plan(&self, state: &AgentState) -> Result<String> {
let prompt = format!(
"As a Software Architect, given this architecture challenge: {}\n\nPlan the next architectural decision or review step. Consider patterns, trade-offs, and long-term implications.",
state.task
);
self.ai.chat(&prompt).await
}
async fn act(&self, plan: &str) -> Result<String> {
let prompt = format!(
"Implement this architectural plan: {}\n\nDesign system components, select patterns, and document decisions.",
plan
);
self.ai.chat(&prompt).await
}
async fn observe(&self, result: &str) -> Result<String> {
let prompt = format!(
"Analyze these architectural results: {}\n\nHow does this architecture address the requirements? What risks or opportunities does it present?",
result
);
self.ai.chat(&prompt).await
}
}