use crate::services::github_issues::{IssueRequest, GitHubIssue};
use serde::{Deserialize, Serialize};
use thiserror::Error;
#[derive(Error, Debug)]
pub enum PdmtGitHubError {
#[error("Invalid issue configuration: {message}")]
InvalidConfig { message: String },
#[error("Template generation failed: {message}")]
TemplateGeneration { message: String },
#[error("PDMT validation failed: {validation_errors:?}")]
ValidationFailed { validation_errors: Vec<String> },
#[error("Serialization error: {0}")]
Serialization(#[from] serde_json::Error),
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum IssueType {
Feature,
Bug,
Enhancement,
Refactor,
Documentation,
Testing,
}
impl IssueType {
pub fn default_labels(&self) -> Vec<String> {
match self {
IssueType::Feature => vec!["enhancement".to_string(), "feature".to_string()],
IssueType::Bug => vec!["bug".to_string()],
IssueType::Enhancement => vec!["enhancement".to_string()],
IssueType::Refactor => vec!["refactor".to_string(), "technical-debt".to_string()],
IssueType::Documentation => vec!["documentation".to_string()],
IssueType::Testing => vec!["testing".to_string()],
}
}
pub fn title_prefix(&self) -> &'static str {
match self {
IssueType::Feature => "feat:",
IssueType::Bug => "fix:",
IssueType::Enhancement => "enhance:",
IssueType::Refactor => "refactor:",
IssueType::Documentation => "docs:",
IssueType::Testing => "test:",
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum Priority {
Low,
Medium,
High,
Critical,
}
impl Priority {
pub fn label(&self) -> String {
match self {
Priority::Low => "priority:low".to_string(),
Priority::Medium => "priority:medium".to_string(),
Priority::High => "priority:high".to_string(),
Priority::Critical => "priority:critical".to_string(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PdmtConfig {
pub seed: u64,
pub quality_level: QualityLevel,
pub granularity: Granularity,
pub enforce_standards: bool,
}
impl Default for PdmtConfig {
fn default() -> Self {
Self {
seed: 42, quality_level: QualityLevel::Strict,
granularity: Granularity::High,
enforce_standards: true,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum QualityLevel {
Strict,
Advisory,
AutoFix,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum Granularity {
Low,
Medium,
High,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PdmtIssueRequest {
pub title: String,
pub description: String,
pub issue_type: IssueType,
pub priority: Priority,
pub complexity_estimate: Option<u8>,
pub assignees: Vec<String>,
pub custom_labels: Vec<String>,
pub config: Option<PdmtConfig>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PdmtIssueTemplate {
pub title: String,
pub body: String,
pub labels: Vec<String>,
pub assignees: Vec<String>,
pub metadata: PdmtMetadata,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PdmtMetadata {
pub seed: u64,
pub quality_level: QualityLevel,
pub granularity: Granularity,
pub issue_type: IssueType,
pub priority: Priority,
pub complexity_estimate: Option<u8>,
pub generated_at: String,
pub validation_commands: Vec<String>,
pub success_criteria: Vec<String>,
pub quality_requirements: QualityRequirements,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct QualityRequirements {
pub test_coverage: u8, pub max_complexity: u8, pub satd_tolerance: u8, pub documentation_required: bool,
pub property_tests_required: bool,
}
impl Default for QualityRequirements {
fn default() -> Self {
Self {
test_coverage: 85,
max_complexity: 20,
satd_tolerance: 0, documentation_required: true,
property_tests_required: true,
}
}
}
#[derive(Debug)]
pub struct PdmtGitHubService {
config: PdmtConfig,
}
impl PdmtGitHubService {
pub fn new() -> Self {
Self {
config: PdmtConfig::default(),
}
}
pub fn with_config(config: PdmtConfig) -> Self {
Self { config }
}
pub fn generate_issue_template(
&self,
request: &PdmtIssueRequest,
) -> Result<PdmtIssueTemplate, PdmtGitHubError> {
let config = request.config.as_ref().unwrap_or(&self.config);
self.validate_request(request)?;
let metadata = self.generate_metadata(request, config)?;
let title = self.generate_title(request);
let body = self.generate_issue_body(request, &metadata)?;
let mut labels = request.issue_type.default_labels();
labels.push(request.priority.label());
labels.extend(request.custom_labels.clone());
labels.push("pdmt".to_string());
if let Some(complexity) = request.complexity_estimate {
labels.push(format!("complexity:{}", self.complexity_category(complexity)));
}
Ok(PdmtIssueTemplate {
title,
body,
labels,
assignees: request.assignees.clone(),
metadata,
})
}
pub fn to_github_request(&self, template: &PdmtIssueTemplate) -> IssueRequest {
IssueRequest {
title: template.title.clone(),
body: template.body.clone(),
labels: template.labels.clone(),
assignees: template.assignees.clone(),
}
}
pub fn extract_metadata(&self, issue: &GitHubIssue) -> Option<PdmtMetadata> {
let body = issue.body.as_ref()?;
if !body.contains("## PDMT Configuration") {
return None;
}
Some(PdmtMetadata {
seed: 42, quality_level: QualityLevel::Strict,
granularity: Granularity::High,
issue_type: self.extract_issue_type_from_title(&issue.title),
priority: Priority::Medium, complexity_estimate: None,
generated_at: issue.created_at.clone(),
validation_commands: self.extract_validation_commands(body),
success_criteria: self.extract_success_criteria(body),
quality_requirements: QualityRequirements::default(),
})
}
fn validate_request(&self, request: &PdmtIssueRequest) -> Result<(), PdmtGitHubError> {
if request.title.is_empty() {
return Err(PdmtGitHubError::InvalidConfig {
message: "Title cannot be empty".to_string(),
});
}
if request.description.is_empty() {
return Err(PdmtGitHubError::InvalidConfig {
message: "Description cannot be empty".to_string(),
});
}
if let Some(complexity) = request.complexity_estimate {
if complexity > 50 {
return Err(PdmtGitHubError::InvalidConfig {
message: "Complexity estimate too high (max 50)".to_string(),
});
}
}
Ok(())
}
fn generate_metadata(
&self,
request: &PdmtIssueRequest,
config: &PdmtConfig,
) -> Result<PdmtMetadata, PdmtGitHubError> {
let now = chrono::Utc::now().to_rfc3339();
let validation_commands = self.generate_validation_commands(request);
let success_criteria = self.generate_success_criteria(request);
let quality_requirements = self.generate_quality_requirements(request);
Ok(PdmtMetadata {
seed: config.seed,
quality_level: config.quality_level.clone(),
granularity: config.granularity.clone(),
issue_type: request.issue_type.clone(),
priority: request.priority.clone(),
complexity_estimate: request.complexity_estimate,
generated_at: now,
validation_commands,
success_criteria,
quality_requirements,
})
}
fn generate_title(&self, request: &PdmtIssueRequest) -> String {
format!("{} {}", request.issue_type.title_prefix(), request.title)
}
fn generate_issue_body(
&self,
request: &PdmtIssueRequest,
metadata: &PdmtMetadata,
) -> Result<String, PdmtGitHubError> {
let mut body = String::new();
body.push_str("## Description\n\n");
body.push_str(&request.description);
body.push_str("\n\n");
body.push_str("## PDMT Configuration\n\n");
body.push_str(&format!("- **Seed**: {} (deterministic)\n", metadata.seed));
body.push_str(&format!("- **Quality Level**: {:?}\n", metadata.quality_level));
body.push_str(&format!("- **Granularity**: {:?}\n", metadata.granularity));
body.push_str(&format!("- **Issue Type**: {:?}\n", metadata.issue_type));
body.push_str(&format!("- **Priority**: {:?}\n", metadata.priority));
if let Some(complexity) = metadata.complexity_estimate {
body.push_str(&format!("- **Estimated Complexity**: {}\n", complexity));
}
body.push_str("\n");
body.push_str("## Quality Requirements\n\n");
let reqs = &metadata.quality_requirements;
body.push_str(&format!("- **Test Coverage**: ≥{}%\n", reqs.test_coverage));
body.push_str(&format!("- **Max Complexity**: ≤{} per function\n", reqs.max_complexity));
body.push_str(&format!("- **SATD Tolerance**: {} (zero tolerance)\n", reqs.satd_tolerance));
body.push_str(&format!("- **Documentation Required**: {}\n", reqs.documentation_required));
body.push_str(&format!("- **Property Tests Required**: {}\n", reqs.property_tests_required));
body.push_str("\n");
body.push_str("## Validation Commands\n\n");
body.push_str("```bash\n");
for command in &metadata.validation_commands {
body.push_str(&format!("{}\n", command));
}
body.push_str("```\n\n");
body.push_str("## Success Criteria\n\n");
for criterion in &metadata.success_criteria {
body.push_str(&format!("- [ ] {}\n", criterion));
}
body.push_str("\n");
body.push_str("## Toyota Way Standards\n\n");
body.push_str("- **Kaizen**: Continuous improvement through quality gates\n");
body.push_str("- **Genchi Genbutsu**: Verify implementation meets requirements\n");
body.push_str("- **Jidoka**: Automated quality enforcement with human oversight\n");
body.push_str("\n");
body.push_str("---\n");
body.push_str("*Generated using PDMT (Pragmatic Deterministic MCP Templating)*\n");
body.push_str(&format!("*Seed: {} | Quality: {:?} | Standards: Zero SATD*",
metadata.seed, metadata.quality_level));
Ok(body)
}
fn generate_validation_commands(&self, request: &PdmtIssueRequest) -> Vec<String> {
let mut commands = vec![
"cargo test --package pmat".to_string(),
"pmat analyze satd --strict".to_string(),
"pmat analyze complexity --max-complexity 20".to_string(),
"make lint".to_string(),
];
match request.issue_type {
IssueType::Feature => {
commands.push("pmat quality-gate --project-wide".to_string());
commands.push("cargo doc --package pmat --no-deps".to_string());
}
IssueType::Bug => {
commands.push("cargo test --package pmat --test integration".to_string());
}
IssueType::Enhancement => {
commands.push("pmat analyze complexity --top-files 5".to_string());
}
IssueType::Refactor => {
commands.push("pmat refactor auto --validate".to_string());
commands.push("pmat quality-gate --strict".to_string());
}
IssueType::Documentation => {
commands.push("cargo test --doc".to_string());
}
IssueType::Testing => {
commands.push("make test-property".to_string());
commands.push("cargo test --package pmat --test property".to_string());
}
}
commands
}
fn generate_success_criteria(&self, request: &PdmtIssueRequest) -> Vec<String> {
let mut criteria = vec![
"All tests pass successfully".to_string(),
"Zero SATD comments in implementation".to_string(),
"All functions ≤20 cyclomatic complexity".to_string(),
"No lint violations".to_string(),
];
match request.issue_type {
IssueType::Feature => {
criteria.push("Complete API documentation".to_string());
criteria.push("Integration tests covering main workflows".to_string());
criteria.push("Quality gate passes for all new code".to_string());
}
IssueType::Bug => {
criteria.push("Root cause identified and documented".to_string());
criteria.push("Regression test added".to_string());
criteria.push("No similar issues exist".to_string());
}
IssueType::Enhancement => {
criteria.push("Performance improvement demonstrated".to_string());
criteria.push("Backward compatibility maintained".to_string());
}
IssueType::Refactor => {
criteria.push("Code quality metrics improved".to_string());
criteria.push("Functionality unchanged (tests pass)".to_string());
criteria.push("Documentation updated".to_string());
}
IssueType::Documentation => {
criteria.push("All examples tested and working".to_string());
criteria.push("Documentation coverage improved".to_string());
}
IssueType::Testing => {
criteria.push("Test coverage increased".to_string());
criteria.push("Property tests cover edge cases".to_string());
}
}
criteria
}
fn generate_quality_requirements(&self, request: &PdmtIssueRequest) -> QualityRequirements {
let base_requirements = QualityRequirements::default();
match request.issue_type {
IssueType::Feature => QualityRequirements {
test_coverage: 90,
property_tests_required: true,
..base_requirements
},
IssueType::Bug => QualityRequirements {
test_coverage: 95,
..base_requirements
},
IssueType::Testing => QualityRequirements {
test_coverage: 100,
property_tests_required: true,
..base_requirements
},
_ => base_requirements,
}
}
fn complexity_category(&self, complexity: u8) -> &'static str {
match complexity {
0..=5 => "trivial",
6..=10 => "low",
11..=15 => "medium",
16..=25 => "high",
_ => "very-high",
}
}
fn extract_issue_type_from_title(&self, title: &str) -> IssueType {
if title.starts_with("feat:") {
IssueType::Feature
} else if title.starts_with("fix:") {
IssueType::Bug
} else if title.starts_with("enhance:") {
IssueType::Enhancement
} else if title.starts_with("refactor:") {
IssueType::Refactor
} else if title.starts_with("docs:") {
IssueType::Documentation
} else if title.starts_with("test:") {
IssueType::Testing
} else {
IssueType::Feature }
}
fn extract_validation_commands(&self, body: &str) -> Vec<String> {
let mut commands = Vec::new();
let mut in_code_block = false;
for line in body.lines() {
if line.starts_with("```bash") {
in_code_block = true;
continue;
}
if line.starts_with("```") && in_code_block {
in_code_block = false;
continue;
}
if in_code_block && !line.trim().is_empty() {
commands.push(line.to_string());
}
}
commands
}
fn extract_success_criteria(&self, body: &str) -> Vec<String> {
let mut criteria = Vec::new();
for line in body.lines() {
if line.starts_with("- [ ]") {
criteria.push(line[5..].trim().to_string());
}
}
criteria
}
}
impl Default for PdmtGitHubService {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_pdmt_config_default() {
let config = PdmtConfig::default();
assert_eq!(config.seed, 42);
assert_eq!(config.quality_level, QualityLevel::Strict);
assert_eq!(config.granularity, Granularity::High);
assert!(config.enforce_standards);
}
#[test]
fn test_issue_type_labels() {
assert_eq!(IssueType::Feature.default_labels(), vec!["enhancement", "feature"]);
assert_eq!(IssueType::Bug.default_labels(), vec!["bug"]);
assert_eq!(IssueType::Refactor.default_labels(), vec!["refactor", "technical-debt"]);
}
#[test]
fn test_issue_type_title_prefix() {
assert_eq!(IssueType::Feature.title_prefix(), "feat:");
assert_eq!(IssueType::Bug.title_prefix(), "fix:");
assert_eq!(IssueType::Enhancement.title_prefix(), "enhance:");
}
#[test]
fn test_priority_labels() {
assert_eq!(Priority::Low.label(), "priority:low");
assert_eq!(Priority::High.label(), "priority:high");
assert_eq!(Priority::Critical.label(), "priority:critical");
}
#[test]
fn test_quality_requirements_default() {
let reqs = QualityRequirements::default();
assert_eq!(reqs.test_coverage, 85);
assert_eq!(reqs.max_complexity, 20);
assert_eq!(reqs.satd_tolerance, 0);
assert!(reqs.documentation_required);
assert!(reqs.property_tests_required);
}
#[test]
fn test_pdmt_service_creation() {
let service = PdmtGitHubService::new();
assert_eq!(service.config.seed, 42);
let custom_config = PdmtConfig {
seed: 123,
quality_level: QualityLevel::Advisory,
granularity: Granularity::Medium,
enforce_standards: false,
};
let service_custom = PdmtGitHubService::with_config(custom_config);
assert_eq!(service_custom.config.seed, 123);
assert_eq!(service_custom.config.quality_level, QualityLevel::Advisory);
}
#[test]
fn test_request_validation() {
let service = PdmtGitHubService::new();
let valid_request = PdmtIssueRequest {
title: "Test feature".to_string(),
description: "Test description".to_string(),
issue_type: IssueType::Feature,
priority: Priority::Medium,
complexity_estimate: Some(10),
assignees: vec![],
custom_labels: vec![],
config: None,
};
assert!(service.validate_request(&valid_request).is_ok());
let invalid_request = PdmtIssueRequest {
title: "".to_string(),
description: "Test description".to_string(),
issue_type: IssueType::Feature,
priority: Priority::Medium,
complexity_estimate: None,
assignees: vec![],
custom_labels: vec![],
config: None,
};
assert!(service.validate_request(&invalid_request).is_err());
}
#[test]
fn test_issue_template_generation() {
let service = PdmtGitHubService::new();
let request = PdmtIssueRequest {
title: "Implement user authentication".to_string(),
description: "Add secure login and registration system".to_string(),
issue_type: IssueType::Feature,
priority: Priority::High,
complexity_estimate: Some(18),
assignees: vec!["developer".to_string()],
custom_labels: vec!["security".to_string()],
config: None,
};
let template = service.generate_issue_template(&request).unwrap();
assert_eq!(template.title, "feat: Implement user authentication");
assert!(template.body.contains("## PDMT Configuration"));
assert!(template.body.contains("- **Seed**: 42"));
assert!(template.body.contains("## Quality Requirements"));
assert!(template.body.contains("## Validation Commands"));
assert!(template.body.contains("## Success Criteria"));
assert!(template.labels.contains(&"enhancement".to_string()));
assert!(template.labels.contains(&"feature".to_string()));
assert!(template.labels.contains(&"priority:high".to_string()));
assert!(template.labels.contains(&"security".to_string()));
assert!(template.labels.contains(&"pdmt".to_string()));
assert!(template.labels.contains(&"complexity:high".to_string()));
assert_eq!(template.assignees, vec!["developer"]);
}
#[test]
fn test_complexity_categories() {
let service = PdmtGitHubService::new();
assert_eq!(service.complexity_category(3), "trivial");
assert_eq!(service.complexity_category(8), "low");
assert_eq!(service.complexity_category(13), "medium");
assert_eq!(service.complexity_category(20), "high");
assert_eq!(service.complexity_category(30), "very-high");
}
#[test]
fn test_issue_type_extraction() {
let service = PdmtGitHubService::new();
assert_eq!(service.extract_issue_type_from_title("feat: New feature"), IssueType::Feature);
assert_eq!(service.extract_issue_type_from_title("fix: Bug fix"), IssueType::Bug);
assert_eq!(service.extract_issue_type_from_title("enhance: Improvement"), IssueType::Enhancement);
assert_eq!(service.extract_issue_type_from_title("refactor: Code cleanup"), IssueType::Refactor);
assert_eq!(service.extract_issue_type_from_title("docs: Documentation"), IssueType::Documentation);
assert_eq!(service.extract_issue_type_from_title("test: Add tests"), IssueType::Testing);
assert_eq!(service.extract_issue_type_from_title("Random title"), IssueType::Feature);
}
}
#[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);
}
}
}