use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::str::FromStr;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub enum TemplateCategory {
Frontend,
Backend,
DevOps,
Testing,
Documentation,
BugFix,
Review,
Optimization,
Refactoring,
General,
Custom(String),
}
impl FromStr for TemplateCategory {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"frontend" | "front" | "ui" => Ok(TemplateCategory::Frontend),
"backend" | "back" | "api" => Ok(TemplateCategory::Backend),
"devops" | "ops" | "infra" | "infrastructure" => Ok(TemplateCategory::DevOps),
"testing" | "test" | "qa" => Ok(TemplateCategory::Testing),
"documentation" | "docs" | "doc" => Ok(TemplateCategory::Documentation),
"bugfix" | "bug" | "fix" => Ok(TemplateCategory::BugFix),
"review" | "code-review" => Ok(TemplateCategory::Review),
"optimization" | "optimize" | "performance" => Ok(TemplateCategory::Optimization),
"refactoring" | "refactor" => Ok(TemplateCategory::Refactoring),
"general" | "misc" => Ok(TemplateCategory::General),
_ => Ok(TemplateCategory::Custom(s.to_string())),
}
}
}
impl std::fmt::Display for TemplateCategory {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
TemplateCategory::Frontend => write!(f, "Frontend"),
TemplateCategory::Backend => write!(f, "Backend"),
TemplateCategory::DevOps => write!(f, "DevOps"),
TemplateCategory::Testing => write!(f, "Testing"),
TemplateCategory::Documentation => write!(f, "Documentation"),
TemplateCategory::BugFix => write!(f, "BugFix"),
TemplateCategory::Review => write!(f, "Review"),
TemplateCategory::Optimization => write!(f, "Optimization"),
TemplateCategory::Refactoring => write!(f, "Refactoring"),
TemplateCategory::General => write!(f, "General"),
TemplateCategory::Custom(name) => write!(f, "{}", name),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum VariableType {
Text,
Boolean,
Number,
FilePath,
Url,
List,
Choice(Vec<String>),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TemplateVariable {
pub name: String,
pub description: String,
pub variable_type: VariableType,
pub default_value: Option<String>,
pub required: bool,
pub example: Option<String>,
pub validation_pattern: Option<String>,
}
impl TemplateVariable {
pub fn text(name: impl Into<String>, description: impl Into<String>) -> Self {
Self {
name: name.into(),
description: description.into(),
variable_type: VariableType::Text,
default_value: None,
required: true,
example: None,
validation_pattern: None,
}
}
pub fn text_with_default(
name: impl Into<String>,
description: impl Into<String>,
default: impl Into<String>,
) -> Self {
Self {
name: name.into(),
description: description.into(),
variable_type: VariableType::Text,
default_value: Some(default.into()),
required: false,
example: None,
validation_pattern: None,
}
}
pub fn choice(
name: impl Into<String>,
description: impl Into<String>,
choices: Vec<String>,
) -> Self {
Self {
name: name.into(),
description: description.into(),
variable_type: VariableType::Choice(choices),
default_value: None,
required: true,
example: None,
validation_pattern: None,
}
}
pub fn boolean(name: impl Into<String>, description: impl Into<String>) -> Self {
Self {
name: name.into(),
description: description.into(),
variable_type: VariableType::Boolean,
default_value: Some("false".to_string()),
required: false,
example: Some("true".to_string()),
validation_pattern: None,
}
}
pub fn optional(mut self) -> Self {
self.required = false;
self
}
pub fn with_example(mut self, example: impl Into<String>) -> Self {
self.example = Some(example.into());
self
}
pub fn with_validation(mut self, pattern: impl Into<String>) -> Self {
self.validation_pattern = Some(pattern.into());
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Template {
pub id: String,
pub name: String,
pub description: String,
pub category: TemplateCategory,
pub author: Option<String>,
pub version: String,
pub created_at: chrono::DateTime<chrono::Utc>,
pub updated_at: chrono::DateTime<chrono::Utc>,
pub tags: Vec<String>,
pub task_description: String,
pub task_details: Option<String>,
pub default_priority: crate::agent::Priority,
pub default_task_type: crate::agent::TaskType,
pub estimated_duration: Option<u32>,
pub variables: Vec<TemplateVariable>,
pub target_files: Vec<String>,
pub preconditions: Vec<String>,
pub post_actions: Vec<String>,
pub usage_count: u64,
pub success_rate: Option<f64>,
pub metadata: HashMap<String, String>,
}
impl Template {
pub fn new(
id: impl Into<String>,
name: impl Into<String>,
description: impl Into<String>,
category: TemplateCategory,
) -> Self {
let now = chrono::Utc::now();
Self {
id: id.into(),
name: name.into(),
description: description.into(),
category,
author: None,
version: "1.0.0".to_string(),
created_at: now,
updated_at: now,
tags: Vec::new(),
task_description: String::new(),
task_details: None,
default_priority: crate::agent::Priority::Medium,
default_task_type: crate::agent::TaskType::Development,
estimated_duration: None,
variables: Vec::new(),
target_files: Vec::new(),
preconditions: Vec::new(),
post_actions: Vec::new(),
usage_count: 0,
success_rate: None,
metadata: HashMap::new(),
}
}
pub fn with_author(mut self, author: impl Into<String>) -> Self {
self.author = Some(author.into());
self
}
pub fn with_version(mut self, version: impl Into<String>) -> Self {
self.version = version.into();
self
}
pub fn with_tags(mut self, tags: Vec<String>) -> Self {
self.tags = tags;
self
}
pub fn with_task_description(mut self, description: impl Into<String>) -> Self {
self.task_description = description.into();
self
}
pub fn with_task_details(mut self, details: impl Into<String>) -> Self {
self.task_details = Some(details.into());
self
}
pub fn with_priority(mut self, priority: crate::agent::Priority) -> Self {
self.default_priority = priority;
self
}
pub fn with_task_type(mut self, task_type: crate::agent::TaskType) -> Self {
self.default_task_type = task_type;
self
}
pub fn with_duration(mut self, minutes: u32) -> Self {
self.estimated_duration = Some(minutes);
self
}
pub fn with_variables(mut self, variables: Vec<TemplateVariable>) -> Self {
self.variables = variables;
self
}
pub fn with_target_files(mut self, files: Vec<String>) -> Self {
self.target_files = files;
self
}
pub fn with_preconditions(mut self, preconditions: Vec<String>) -> Self {
self.preconditions = preconditions;
self
}
pub fn with_post_actions(mut self, actions: Vec<String>) -> Self {
self.post_actions = actions;
self
}
pub fn with_metadata(mut self, metadata: HashMap<String, String>) -> Self {
self.metadata = metadata;
self
}
pub fn increment_usage(&mut self) {
self.usage_count += 1;
self.updated_at = chrono::Utc::now();
}
pub fn update_success_rate(&mut self, success: bool) {
let current_rate = self.success_rate.unwrap_or(0.0);
let current_count = self.usage_count as f64;
let new_rate = if current_count == 0.0 {
if success { 1.0 } else { 0.0 }
} else {
let total_successes = current_rate * current_count;
let new_successes = if success {
total_successes + 1.0
} else {
total_successes
};
new_successes / (current_count + 1.0)
};
self.success_rate = Some(new_rate);
self.updated_at = chrono::Utc::now();
}
pub fn get_variable_names(&self) -> Vec<String> {
self.variables.iter().map(|v| v.name.clone()).collect()
}
pub fn is_valid(&self) -> bool {
!self.task_description.is_empty() && !self.name.is_empty() && !self.id.is_empty()
}
}
#[derive(Debug, Clone, Default)]
pub struct TemplateQuery {
pub search_term: Option<String>,
pub category: Option<TemplateCategory>,
pub tags: Vec<String>,
pub author: Option<String>,
pub min_success_rate: Option<f64>,
pub sort_by_popularity: bool,
pub sort_by_success_rate: bool,
pub sort_by_date: bool,
pub limit: Option<usize>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct TemplateContext {
pub variables: HashMap<String, String>,
pub metadata: HashMap<String, String>,
pub project_name: Option<String>,
pub agent_role: Option<String>,
}
impl TemplateContext {
pub fn new() -> Self {
Self::default()
}
pub fn with_variables(variables: HashMap<String, String>) -> Self {
Self {
variables,
metadata: HashMap::new(),
project_name: None,
agent_role: None,
}
}
pub fn with_variable(mut self, name: impl Into<String>, value: impl Into<String>) -> Self {
self.variables.insert(name.into(), value.into());
self
}
pub fn add_variable(&mut self, name: impl Into<String>, value: impl Into<String>) {
self.variables.insert(name.into(), value.into());
}
pub fn add_metadata(&mut self, key: impl Into<String>, value: impl Into<String>) {
self.metadata.insert(key.into(), value.into());
}
}
#[derive(Debug, thiserror::Error)]
pub enum TemplateError {
#[error("Template not found: {id}")]
NotFound { id: String },
#[error("Template already exists: {id}")]
AlreadyExists { id: String },
#[error("Template validation failed: {reason}")]
ValidationFailed { reason: String },
#[error("Template storage error: {0}")]
StorageError(#[from] std::io::Error),
#[error("Template parsing error: {0}")]
ParseError(String),
#[error("Variable substitution error: missing variable '{name}'")]
MissingVariable { name: String },
#[error("Template substitution failed: {reason}")]
SubstitutionFailed { reason: String },
#[error(transparent)]
Other(#[from] anyhow::Error),
}
impl TemplateQuery {
pub fn new() -> Self {
Self::default()
}
pub fn with_search_term(mut self, term: impl Into<String>) -> Self {
self.search_term = Some(term.into());
self
}
pub fn with_category(mut self, category: TemplateCategory) -> Self {
self.category = Some(category);
self
}
pub fn with_tags(mut self, tags: Vec<String>) -> Self {
self.tags = tags;
self
}
pub fn with_author(mut self, author: impl Into<String>) -> Self {
self.author = Some(author.into());
self
}
pub fn with_min_success_rate(mut self, rate: f64) -> Self {
self.min_success_rate = Some(rate);
self
}
pub fn sort_by_popularity(mut self) -> Self {
self.sort_by_popularity = true;
self
}
pub fn sort_by_success_rate(mut self) -> Self {
self.sort_by_success_rate = true;
self
}
pub fn sort_by_date(mut self) -> Self {
self.sort_by_date = true;
self
}
pub fn with_limit(mut self, limit: usize) -> Self {
self.limit = Some(limit);
self
}
}