use super::{TaskStatus, Priority, QualityGateConfig, Sprint, Complexity, Task, HashMap};
use crate::models::pdmt::{PdmtQualityConfig, PdmtTodo};
use crate::services::pdmt_service::PdmtService;
use anyhow::Result;
use serde::{Deserialize, Serialize};
use std::time::Duration;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RoadmapTask {
pub id: String,
pub title: String,
pub description: String,
pub status: TaskStatus,
pub priority: Priority,
pub estimated_hours: f64,
pub assigned_to: Option<String>,
pub dependencies: Vec<String>,
pub tags: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct QualityEnforcedTodo {
pub id: String,
pub task_id: String, pub description: String,
pub implementation_spec: String,
pub quality_requirements: QualityRequirements,
pub validation_commands: Vec<String>,
pub success_criteria: Vec<String>,
pub estimated_time: std::time::Duration,
pub dependencies: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct QualityRequirements {
pub max_complexity: u32,
pub min_test_coverage: u8,
pub required_docs: bool,
pub satd_allowed: u32, pub lint_compliance: bool,
}
impl Default for QualityRequirements {
fn default() -> Self {
Self {
max_complexity: 20,
min_test_coverage: 80,
required_docs: true,
satd_allowed: 0,
lint_compliance: true,
}
}
}
pub struct RoadmapTodoGenerator {
pdmt_service: PdmtService,
quality_config: QualityGateConfig,
}
impl RoadmapTodoGenerator {
#[must_use]
pub fn new(quality_config: QualityGateConfig) -> Self {
Self {
pdmt_service: PdmtService::new(),
quality_config,
}
}
pub fn generate_todos_from_task(
&mut self,
task: &RoadmapTask,
) -> Result<Vec<QualityEnforcedTodo>> {
let mut todos = Vec::new();
let todo = self.create_quality_enforced_todo(task, 1);
todos.push(todo);
if task.estimated_hours > 4.0 {
let test_todo = QualityEnforcedTodo {
id: format!("{}-test", task.id),
task_id: task.id.clone(),
description: format!("Comprehensive testing for {}", task.title),
implementation_spec: "Add comprehensive test coverage".to_string(),
quality_requirements: QualityRequirements::default(),
validation_commands: vec!["cargo test".to_string()],
success_criteria: vec!["Test coverage >80%".to_string()],
estimated_time: Duration::from_secs_f64(task.estimated_hours * 0.5 * 3600.0),
dependencies: vec![task.id.clone()],
};
todos.push(test_todo);
}
Ok(todos)
}
pub fn generate_todos_from_sprint(
&mut self,
sprint: &Sprint,
) -> Result<Vec<QualityEnforcedTodo>> {
let mut all_todos = Vec::new();
for task in &sprint.tasks {
let roadmap_task = RoadmapTask {
id: task.id.clone(),
title: task.description.clone(),
description: task.description.clone(),
status: task.status,
priority: task.priority,
estimated_hours: match task.complexity {
Complexity::Low => 2.0,
Complexity::Medium => 4.0,
Complexity::High => 8.0,
},
assigned_to: task.assignee.clone(),
dependencies: Vec::new(),
tags: Vec::new(),
};
let task_todos = self.generate_todos_from_task(&roadmap_task)?;
all_todos.extend(task_todos);
}
Ok(all_todos)
}
#[must_use]
pub fn create_quality_enforced_todo(
&self,
task: &RoadmapTask,
sequence: usize,
) -> QualityEnforcedTodo {
QualityEnforcedTodo {
id: format!("{}-{}", task.id, sequence),
task_id: task.id.clone(),
description: task.description.clone(),
implementation_spec: format!("Implement {} with TDD methodology", task.title),
quality_requirements: QualityRequirements::default(),
validation_commands: self.generate_validation_commands(task),
success_criteria: self.generate_success_criteria(task),
estimated_time: Duration::from_secs_f64(task.estimated_hours * 3600.0),
dependencies: task.dependencies.clone(),
}
}
#[must_use]
pub fn generate_validation_commands(&self, task: &RoadmapTask) -> Vec<String> {
let mut commands = vec![
"cargo test".to_string(),
"cargo clippy -- -D warnings".to_string(),
"cargo fmt --check".to_string(),
];
if task.tags.contains(&"backend".to_string()) {
commands.push("cargo doc --no-deps".to_string());
}
if task.tags.contains(&"api".to_string()) {
commands.push("cargo test --test integration".to_string());
}
commands
}
#[must_use]
pub fn generate_success_criteria(&self, task: &RoadmapTask) -> Vec<String> {
let mut criteria = vec![
"All tests pass".to_string(),
"Code coverage >80%".to_string(),
"Zero SATD violations".to_string(),
"Lint checks pass".to_string(),
];
if task.priority == Priority::P0 {
criteria.push("Performance benchmarks pass".to_string());
}
if task.estimated_hours > 8.0 {
criteria.push("Documentation updated".to_string());
criteria.push("Integration tests pass".to_string());
}
criteria
}
#[must_use]
pub fn format_todos_as_markdown(&self, todos: &[QualityEnforcedTodo]) -> String {
let mut output = String::new();
output.push_str("# Quality-Enforced Todo List\n\n");
for todo in todos {
output.push_str(&format!("## {} - {}\n\n", todo.task_id, todo.description));
output.push_str(&format!(
"**Implementation**: {}\n\n",
todo.implementation_spec
));
output.push_str("### Validation Commands\n");
for cmd in &todo.validation_commands {
output.push_str(&format!("- `{cmd}`\n"));
}
output.push('\n');
output.push_str("### Success Criteria\n");
for criterion in &todo.success_criteria {
output.push_str(&format!("- {criterion}\n"));
}
output.push_str("\n---\n\n");
}
output
}
pub async fn generate_sprint_todos(&self, sprint: &Sprint) -> Result<Vec<QualityEnforcedTodo>> {
let mut todos = Vec::new();
for task in &sprint.tasks {
if task.status != TaskStatus::Completed {
let task_todos = self.generate_task_todos(task).await?;
todos.extend(task_todos);
}
}
Ok(todos)
}
pub async fn generate_task_todos(&self, task: &Task) -> Result<Vec<QualityEnforcedTodo>> {
let quality_config = PdmtQualityConfig {
enforcement_mode: crate::models::pdmt::EnforcementMode::Strict,
coverage_threshold: f32::from(self.quality_config.coverage_min),
max_complexity: self.quality_config.complexity_max,
require_doctests: self.quality_config.documentation_required,
require_property_tests: true,
require_examples: false,
zero_satd_tolerance: self.quality_config.satd_tolerance == 0,
};
let todo_list = self.pdmt_service.generate_todos(
vec![task.description.clone()],
Some(task.id.clone()),
&task.complexity.to_string().to_lowercase(),
quality_config,
)?;
let pdmt_todos = todo_list.todos;
let mut quality_todos = Vec::new();
for (i, pdmt_todo) in pdmt_todos.into_iter().enumerate() {
let quality_todo = self.enhance_with_quality(task, &pdmt_todo, i)?;
quality_todos.push(quality_todo);
}
Ok(quality_todos)
}
fn enhance_with_quality(
&self,
task: &Task,
pdmt_todo: &PdmtTodo,
index: usize,
) -> Result<QualityEnforcedTodo> {
let mut success_criteria = pdmt_todo.success_criteria.clone();
let mut validation_commands = vec![
pdmt_todo.validation_commands.unit_tests.clone(),
pdmt_todo.validation_commands.doctests.clone(),
pdmt_todo.validation_commands.coverage_check.clone(),
pdmt_todo.validation_commands.quality_proxy.clone(),
];
validation_commands.push(format!("pmat roadmap quality-check --task-id {}", task.id));
validation_commands.push("pmat quality-gate --file-path .".to_string());
success_criteria.push("All quality gates pass".to_string());
success_criteria.push(format!(
"Complexity ≤ {}",
self.quality_config.complexity_max
));
success_criteria.push(format!(
"Test coverage ≥ {}%",
self.quality_config.coverage_min
));
success_criteria.push("Zero SATD violations".to_string());
if self.quality_config.documentation_required {
validation_commands.push("pmat roadmap check-docs".to_string());
success_criteria.push("Documentation updated".to_string());
}
if self.quality_config.lint_compliance {
validation_commands.push("make lint".to_string());
success_criteria.push("Zero lint warnings".to_string());
}
Ok(QualityEnforcedTodo {
id: format!("{}-{:03}", task.id, index + 1),
task_id: task.id.clone(),
description: pdmt_todo.content.clone(),
implementation_spec: pdmt_todo
.implementation_specs
.primary_files
.first()
.cloned()
.unwrap_or_default(),
quality_requirements: QualityRequirements {
max_complexity: self.quality_config.complexity_max,
min_test_coverage: self.quality_config.coverage_min,
required_docs: self.quality_config.documentation_required,
satd_allowed: self.quality_config.satd_tolerance,
lint_compliance: self.quality_config.lint_compliance,
},
validation_commands,
success_criteria,
estimated_time: std::time::Duration::from_secs(3600), dependencies: pdmt_todo.dependencies.clone(),
})
}
#[must_use]
pub fn export_todos_markdown(&self, todos: &[QualityEnforcedTodo]) -> String {
let mut output = String::new();
output.push_str("# Sprint Todos (PDMT Generated)\n\n");
output.push_str("*Generated with deterministic seeds for reproducibility*\n\n");
let mut tasks_map: HashMap<String, Vec<&QualityEnforcedTodo>> = HashMap::new();
for todo in todos {
tasks_map
.entry(todo.task_id.clone())
.or_default()
.push(todo);
}
for (task_id, task_todos) in tasks_map {
output.push_str(&format!("## Task {task_id}\n\n"));
for todo in task_todos {
output.push_str(&format!("### {} - {}\n\n", todo.id, todo.description));
if !todo.implementation_spec.is_empty() {
output.push_str("**Implementation:**\n");
output.push_str(&format!("{}\n\n", todo.implementation_spec));
}
output.push_str("**Quality Requirements:**\n");
output.push_str(&format!(
"- Max Complexity: {}\n",
todo.quality_requirements.max_complexity
));
output.push_str(&format!(
"- Min Test Coverage: {}%\n",
todo.quality_requirements.min_test_coverage
));
output.push_str(&format!(
"- Documentation Required: {}\n",
todo.quality_requirements.required_docs
));
output.push_str(&format!(
"- SATD Allowed: {}\n",
todo.quality_requirements.satd_allowed
));
output.push_str(&format!(
"- Lint Compliance: {}\n\n",
todo.quality_requirements.lint_compliance
));
output.push_str("**Validation Commands:**\n```bash\n");
for cmd in &todo.validation_commands {
output.push_str(&format!("{cmd}\n"));
}
output.push_str("```\n\n");
output.push_str("**Success Criteria:**\n");
for criterion in &todo.success_criteria {
output.push_str(&format!("- [ ] {criterion}\n"));
}
output.push('\n');
if !todo.dependencies.is_empty() {
output.push_str("**Dependencies:**\n");
for dep in &todo.dependencies {
output.push_str(&format!("- {dep}\n"));
}
output.push('\n');
}
output.push_str("---\n\n");
}
}
output
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::time::Duration;
#[test]
fn test_quality_requirements_default() {
let qr = QualityRequirements::default();
assert_eq!(qr.max_complexity, 20);
assert_eq!(qr.min_test_coverage, 80);
assert!(qr.required_docs);
assert_eq!(qr.satd_allowed, 0);
assert!(qr.lint_compliance);
}
#[test]
fn test_quality_enforced_todo_creation() {
let todo = QualityEnforcedTodo {
id: "todo-001".to_string(),
task_id: "PMAT-1001".to_string(),
description: "Implement feature X".to_string(),
implementation_spec: "Create module with tests".to_string(),
quality_requirements: QualityRequirements::default(),
validation_commands: vec!["cargo test".to_string()],
success_criteria: vec!["All tests pass".to_string()],
estimated_time: Duration::from_secs(4 * 3600),
dependencies: vec![],
};
assert_eq!(todo.id, "todo-001");
assert_eq!(todo.task_id, "PMAT-1001");
assert!(todo.description.contains("feature X"));
assert_eq!(todo.validation_commands.len(), 1);
assert_eq!(todo.success_criteria.len(), 1);
}
#[test]
fn test_roadmap_todo_generator_new() {
let config = QualityGateConfig::default();
let generator = RoadmapTodoGenerator::new(config.clone());
assert_eq!(generator.quality_config.coverage_min, config.coverage_min);
}
#[test]
fn test_generate_todos_from_task() {
let config = QualityGateConfig::default();
let mut generator = RoadmapTodoGenerator::new(config);
let task = RoadmapTask {
id: "PMAT-2001".to_string(),
title: "Test task".to_string(),
description: "Test description".to_string(),
status: TaskStatus::Planned,
priority: Priority::P1,
estimated_hours: 8.0,
assigned_to: None,
dependencies: vec![],
tags: vec![],
};
let result = generator.generate_todos_from_task(&task);
assert!(result.is_ok());
let todos = result.unwrap();
assert!(!todos.is_empty());
assert!(todos[0].task_id.contains("PMAT-2001"));
}
#[test]
fn test_generate_todos_from_sprint() {
let config = QualityGateConfig::default();
let mut generator = RoadmapTodoGenerator::new(config);
let sprint = Sprint {
version: "v1.0.0".to_string(),
title: "Test Sprint".to_string(),
start_date: chrono::Utc::now(),
end_date: chrono::Utc::now() + chrono::Duration::days(14),
priority: Priority::P0,
tasks: vec![Task {
id: "PMAT-3001".to_string(),
description: "Task 1: Description 1".to_string(),
status: TaskStatus::Planned,
complexity: Complexity::Medium,
priority: Priority::P1,
assignee: None,
started_at: None,
completed_at: None,
}],
definition_of_done: vec!["All tests pass".to_string()],
quality_gates: vec!["Coverage >80%".to_string()],
};
let result = generator.generate_todos_from_sprint(&sprint);
assert!(result.is_ok());
let todos = result.unwrap();
assert!(!todos.is_empty());
}
#[test]
fn test_create_quality_enforced_todo() {
let config = QualityGateConfig::default();
let generator = RoadmapTodoGenerator::new(config);
let task = RoadmapTask {
id: "PMAT-4001".to_string(),
title: "Complex task".to_string(),
description: "A complex implementation task".to_string(),
status: TaskStatus::Planned,
priority: Priority::P0,
estimated_hours: 12.0,
assigned_to: Some("developer".to_string()),
dependencies: vec!["PMAT-4000".to_string()],
tags: vec!["feature".to_string(), "backend".to_string()],
};
let todo = generator.create_quality_enforced_todo(&task, 1);
assert_eq!(todo.task_id, "PMAT-4001");
assert!(todo.description.contains("Complex task"));
assert!(todo.quality_requirements.min_test_coverage >= 80);
assert_eq!(todo.quality_requirements.satd_allowed, 0);
assert!(todo.quality_requirements.lint_compliance);
assert!(!todo.dependencies.is_empty());
}
#[test]
fn test_generate_validation_commands() {
let config = QualityGateConfig::default();
let generator = RoadmapTodoGenerator::new(config);
let task = RoadmapTask {
id: "PMAT-5001".to_string(),
title: "Test validation".to_string(),
description: "Test validation commands generation".to_string(),
status: TaskStatus::Planned,
priority: Priority::P2,
estimated_hours: 2.0,
assigned_to: None,
dependencies: vec![],
tags: vec!["testing".to_string()],
};
let commands = generator.generate_validation_commands(&task);
assert!(!commands.is_empty());
assert!(commands.iter().any(|cmd| cmd.contains("cargo test")));
assert!(commands.iter().any(|cmd| cmd.contains("cargo clippy")));
}
#[test]
fn test_generate_success_criteria() {
let config = QualityGateConfig::default();
let generator = RoadmapTodoGenerator::new(config);
let task = RoadmapTask {
id: "PMAT-6001".to_string(),
title: "Success criteria test".to_string(),
description: "Test success criteria generation".to_string(),
status: TaskStatus::Planned,
priority: Priority::P1,
estimated_hours: 6.0,
assigned_to: None,
dependencies: vec![],
tags: vec![],
};
let criteria = generator.generate_success_criteria(&task);
assert!(!criteria.is_empty());
assert!(criteria.iter().any(|c| c.contains("test")));
assert!(criteria.iter().any(|c| c.contains("coverage")));
assert!(criteria.iter().any(|c| c.contains("SATD")));
}
#[test]
fn test_format_todos_as_markdown() {
let config = QualityGateConfig::default();
let generator = RoadmapTodoGenerator::new(config);
let todos = vec![QualityEnforcedTodo {
id: "todo-001".to_string(),
task_id: "PMAT-7001".to_string(),
description: "Test todo 1".to_string(),
implementation_spec: "Implement with TDD".to_string(),
quality_requirements: QualityRequirements::default(),
validation_commands: vec!["cargo test".to_string()],
success_criteria: vec!["Tests pass".to_string()],
estimated_time: Duration::from_secs(2 * 3600),
dependencies: vec![],
}];
let markdown = generator.format_todos_as_markdown(&todos);
assert!(markdown.contains("PMAT-7001"));
assert!(markdown.contains("Test todo 1"));
assert!(markdown.contains("cargo test"));
assert!(markdown.contains("Tests pass"));
}
#[test]
fn test_quality_requirements_serialization() {
let qr = QualityRequirements {
max_complexity: 15,
min_test_coverage: 90,
required_docs: false,
satd_allowed: 0,
lint_compliance: true,
};
let json = serde_json::to_string(&qr).unwrap();
let deserialized: QualityRequirements = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized.max_complexity, 15);
assert_eq!(deserialized.min_test_coverage, 90);
assert!(!deserialized.required_docs);
assert_eq!(deserialized.satd_allowed, 0);
assert!(deserialized.lint_compliance);
}
}
#[cfg(test)]
mod property_tests {
use proptest::prelude::*;
proptest! {
#[test]
fn basic_property_stability(_input in ".*") {
prop_assert!(true);
}
#[test]
fn module_consistency_check(_x in 0u32..1000) {
prop_assert!(_x < 1001);
}
}
}