sklears_core/
contribution.rs

1/// Contribution guidelines and review process for sklears
2///
3/// This module provides comprehensive guidelines, tools, and processes for contributing
4/// to the sklears machine learning library. It includes automated checks, quality gates,
5/// and best practices to ensure consistent code quality and maintainability.
6///
7/// # Key Components
8///
9/// - **Code Review Guidelines**: Structured review criteria and checklists
10/// - **Quality Gates**: Automated checks for code quality and compliance
11/// - **Contribution Workflow**: Step-by-step contribution process
12/// - **Best Practices**: Coding standards and patterns specific to ML in Rust
13/// - **Documentation Standards**: Requirements for API documentation and examples
14///
15/// # Usage
16///
17/// ```rust
18/// use sklears_core::contribution::{ContributionChecker, ReviewCriteria};
19///
20/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
21/// let checker = ContributionChecker::new();
22/// let criteria = ReviewCriteria::default();
23///
24/// // Check a contribution against quality gates
25/// let result = checker.check_contribution("path/to/code", &criteria)?;
26/// println!("Contribution quality score: {:.2}", result.quality_score());
27/// # Ok(())
28/// # }
29/// ```
30use crate::error::Result;
31use serde::{Deserialize, Serialize};
32use std::path::Path;
33use std::time::SystemTime;
34
35/// Main contribution checker and validator
36#[derive(Debug)]
37pub struct ContributionChecker {
38    #[allow(dead_code)]
39    config: ContributionConfig,
40    quality_gates: Vec<QualityGate>,
41}
42
43/// Configuration for contribution checking
44#[derive(Debug, Clone, Serialize, Deserialize)]
45pub struct ContributionConfig {
46    /// Minimum code coverage percentage required
47    pub min_coverage: f64,
48    /// Maximum cyclomatic complexity allowed
49    pub max_complexity: u32,
50    /// Require comprehensive documentation
51    pub require_docs: bool,
52    /// Require property-based tests for ML algorithms
53    pub require_property_tests: bool,
54    /// Maximum line length for code
55    pub max_line_length: usize,
56    /// Required clippy compliance level
57    pub clippy_compliance: ClippyLevel,
58    /// Performance regression threshold
59    pub performance_threshold: f64,
60}
61
62impl Default for ContributionConfig {
63    fn default() -> Self {
64        Self {
65            min_coverage: 90.0,
66            max_complexity: 10,
67            require_docs: true,
68            require_property_tests: true,
69            max_line_length: 100,
70            clippy_compliance: ClippyLevel::AllowedByDefault,
71            performance_threshold: 0.05, // 5% regression threshold
72        }
73    }
74}
75
76/// Clippy compliance levels
77#[derive(Debug, Clone, Serialize, Deserialize)]
78pub enum ClippyLevel {
79    AllowedByDefault,
80    Warn,
81    Deny,
82    Forbid,
83}
84
85/// Review criteria for contributions
86#[derive(Debug, Clone, Default, Serialize, Deserialize)]
87pub struct ReviewCriteria {
88    /// Algorithmic correctness requirements
89    pub algorithmic_correctness: AlgorithmicCriteria,
90    /// Code quality requirements
91    pub code_quality: CodeQualityCriteria,
92    /// Documentation requirements
93    pub documentation: DocumentationCriteria,
94    /// Testing requirements
95    pub testing: TestingCriteria,
96    /// Performance requirements
97    pub performance: PerformanceCriteria,
98}
99
100/// Algorithmic correctness criteria
101#[derive(Debug, Clone, Serialize, Deserialize)]
102pub struct AlgorithmicCriteria {
103    /// Mathematical foundations must be sound
104    pub mathematical_soundness: bool,
105    /// Algorithm must converge where expected
106    pub convergence_guarantees: bool,
107    /// Numerical stability must be ensured
108    pub numerical_stability: bool,
109    /// Edge cases must be handled properly
110    pub edge_case_handling: bool,
111    /// Reference implementations for comparison
112    pub reference_validation: bool,
113}
114
115impl Default for AlgorithmicCriteria {
116    fn default() -> Self {
117        Self {
118            mathematical_soundness: true,
119            convergence_guarantees: true,
120            numerical_stability: true,
121            edge_case_handling: true,
122            reference_validation: true,
123        }
124    }
125}
126
127/// Code quality criteria
128#[derive(Debug, Clone, Serialize, Deserialize)]
129pub struct CodeQualityCriteria {
130    /// Type safety and generic usage
131    pub type_safety: bool,
132    /// Memory efficiency considerations
133    pub memory_efficiency: bool,
134    /// Error handling completeness
135    pub error_handling: bool,
136    /// API design consistency
137    pub api_consistency: bool,
138    /// Rust idioms and best practices
139    pub rust_idioms: bool,
140}
141
142impl Default for CodeQualityCriteria {
143    fn default() -> Self {
144        Self {
145            type_safety: true,
146            memory_efficiency: true,
147            error_handling: true,
148            api_consistency: true,
149            rust_idioms: true,
150        }
151    }
152}
153
154/// Documentation criteria
155#[derive(Debug, Clone, Serialize, Deserialize)]
156pub struct DocumentationCriteria {
157    /// API documentation completeness
158    pub api_docs: bool,
159    /// Executable examples provided
160    pub examples: bool,
161    /// Mathematical background explained
162    pub mathematical_background: bool,
163    /// Performance characteristics documented
164    pub performance_docs: bool,
165    /// Usage patterns and best practices
166    pub usage_patterns: bool,
167}
168
169impl Default for DocumentationCriteria {
170    fn default() -> Self {
171        Self {
172            api_docs: true,
173            examples: true,
174            mathematical_background: true,
175            performance_docs: true,
176            usage_patterns: true,
177        }
178    }
179}
180
181/// Testing criteria
182#[derive(Debug, Clone, Serialize, Deserialize)]
183pub struct TestingCriteria {
184    /// Unit test coverage
185    pub unit_tests: bool,
186    /// Integration test coverage
187    pub integration_tests: bool,
188    /// Property-based testing for algorithms
189    pub property_tests: bool,
190    /// Edge case testing
191    pub edge_case_tests: bool,
192    /// Performance benchmarks
193    pub benchmarks: bool,
194    /// Regression tests
195    pub regression_tests: bool,
196}
197
198impl Default for TestingCriteria {
199    fn default() -> Self {
200        Self {
201            unit_tests: true,
202            integration_tests: true,
203            property_tests: true,
204            edge_case_tests: true,
205            benchmarks: true,
206            regression_tests: true,
207        }
208    }
209}
210
211/// Performance criteria
212#[derive(Debug, Clone, Serialize, Deserialize)]
213pub struct PerformanceCriteria {
214    /// Performance compared to baseline
215    pub baseline_comparison: bool,
216    /// Memory usage analysis
217    pub memory_analysis: bool,
218    /// Scalability characteristics
219    pub scalability: bool,
220    /// Parallelization effectiveness
221    pub parallelization: bool,
222    /// Time complexity documentation
223    pub complexity_analysis: bool,
224}
225
226impl Default for PerformanceCriteria {
227    fn default() -> Self {
228        Self {
229            baseline_comparison: true,
230            memory_analysis: true,
231            scalability: true,
232            parallelization: true,
233            complexity_analysis: true,
234        }
235    }
236}
237
238/// Quality gate definition
239#[derive(Debug, Clone, Serialize, Deserialize)]
240pub struct QualityGate {
241    pub name: String,
242    pub description: String,
243    pub gate_type: QualityGateType,
244    pub threshold: f64,
245    pub blocking: bool,
246}
247
248/// Types of quality gates
249#[derive(Debug, Clone, Serialize, Deserialize)]
250pub enum QualityGateType {
251    CodeCoverage,
252    TestPassing,
253    LintCompliance,
254    DocumentationCoverage,
255    PerformanceRegression,
256    SecurityVulnerabilities,
257    DependencyLicenses,
258    APIBreaking,
259}
260
261/// Result of contribution check
262#[derive(Debug, Clone, Serialize, Deserialize)]
263pub struct ContributionResult {
264    pub overall_score: f64,
265    pub gate_results: Vec<GateResult>,
266    pub recommendations: Vec<String>,
267    pub blocking_issues: Vec<String>,
268    pub timestamp: SystemTime,
269}
270
271/// Result of individual quality gate
272#[derive(Debug, Clone, Serialize, Deserialize)]
273pub struct GateResult {
274    pub gate_name: String,
275    pub passed: bool,
276    pub score: f64,
277    pub details: String,
278    pub improvement_suggestions: Vec<String>,
279}
280
281impl ContributionChecker {
282    /// Create a new contribution checker with default configuration
283    pub fn new() -> Self {
284        Self::with_config(ContributionConfig::default())
285    }
286
287    /// Create a contribution checker with custom configuration
288    pub fn with_config(config: ContributionConfig) -> Self {
289        let quality_gates = Self::create_default_quality_gates(&config);
290        Self {
291            config,
292            quality_gates,
293        }
294    }
295
296    /// Check a contribution against all quality gates
297    pub fn check_contribution(
298        &self,
299        path: impl AsRef<Path>,
300        criteria: &ReviewCriteria,
301    ) -> Result<ContributionResult> {
302        let path = path.as_ref();
303        let mut gate_results = Vec::new();
304        let mut blocking_issues = Vec::new();
305        let mut recommendations = Vec::new();
306
307        // Run all quality gates
308        for gate in &self.quality_gates {
309            let result = self.run_quality_gate(gate, path, criteria)?;
310
311            if gate.blocking && !result.passed {
312                blocking_issues.push(format!("Blocking issue: {}", result.details));
313            }
314
315            recommendations.extend(result.improvement_suggestions.clone());
316            gate_results.push(result);
317        }
318
319        // Calculate overall score
320        let overall_score = if gate_results.is_empty() {
321            0.0
322        } else {
323            gate_results.iter().map(|r| r.score).sum::<f64>() / gate_results.len() as f64
324        };
325
326        Ok(ContributionResult {
327            overall_score,
328            gate_results,
329            recommendations,
330            blocking_issues,
331            timestamp: SystemTime::now(),
332        })
333    }
334
335    /// Run a specific quality gate
336    fn run_quality_gate(
337        &self,
338        gate: &QualityGate,
339        path: &Path,
340        _criteria: &ReviewCriteria,
341    ) -> Result<GateResult> {
342        match gate.gate_type {
343            QualityGateType::CodeCoverage => self.check_code_coverage(gate, path),
344            QualityGateType::TestPassing => self.check_test_passing(gate, path),
345            QualityGateType::LintCompliance => self.check_lint_compliance(gate, path),
346            QualityGateType::DocumentationCoverage => self.check_documentation_coverage(gate, path),
347            QualityGateType::PerformanceRegression => self.check_performance_regression(gate, path),
348            QualityGateType::SecurityVulnerabilities => {
349                self.check_security_vulnerabilities(gate, path)
350            }
351            QualityGateType::DependencyLicenses => self.check_dependency_licenses(gate, path),
352            QualityGateType::APIBreaking => self.check_api_breaking(gate, path),
353        }
354    }
355
356    fn check_code_coverage(&self, gate: &QualityGate, _path: &Path) -> Result<GateResult> {
357        // Placeholder implementation - in real scenario would integrate with coverage tools
358        let coverage = 85.0; // Mock coverage percentage
359        let passed = coverage >= gate.threshold;
360
361        Ok(GateResult {
362            gate_name: gate.name.clone(),
363            passed,
364            score: if passed { 100.0 } else { coverage },
365            details: format!(
366                "Code coverage: {:.1}% (threshold: {:.1}%)",
367                coverage, gate.threshold
368            ),
369            improvement_suggestions: if passed {
370                vec![]
371            } else {
372                vec![
373                    "Add more unit tests for uncovered code paths".to_string(),
374                    "Consider adding property-based tests for algorithmic code".to_string(),
375                    "Add integration tests for end-to-end workflows".to_string(),
376                ]
377            },
378        })
379    }
380
381    fn check_test_passing(&self, gate: &QualityGate, _path: &Path) -> Result<GateResult> {
382        // Placeholder implementation
383        let all_tests_pass = true; // Mock test result
384        let score = if all_tests_pass { 100.0 } else { 0.0 };
385
386        Ok(GateResult {
387            gate_name: gate.name.clone(),
388            passed: all_tests_pass,
389            score,
390            details: if all_tests_pass {
391                "All tests passing".to_string()
392            } else {
393                "Some tests failing".to_string()
394            },
395            improvement_suggestions: if all_tests_pass {
396                vec![]
397            } else {
398                vec![
399                    "Fix failing tests before submitting".to_string(),
400                    "Ensure tests are deterministic and reproducible".to_string(),
401                ]
402            },
403        })
404    }
405
406    fn check_lint_compliance(&self, gate: &QualityGate, _path: &Path) -> Result<GateResult> {
407        // Placeholder implementation
408        let lint_score = 95.0; // Mock lint compliance score
409        let passed = lint_score >= gate.threshold;
410
411        Ok(GateResult {
412            gate_name: gate.name.clone(),
413            passed,
414            score: lint_score,
415            details: format!("Lint compliance: {lint_score:.1}%"),
416            improvement_suggestions: if passed {
417                vec![]
418            } else {
419                vec![
420                    "Fix clippy warnings and errors".to_string(),
421                    "Follow Rust naming conventions".to_string(),
422                    "Remove unused imports and variables".to_string(),
423                ]
424            },
425        })
426    }
427
428    fn check_documentation_coverage(&self, gate: &QualityGate, _path: &Path) -> Result<GateResult> {
429        // Placeholder implementation
430        let doc_coverage = 88.0; // Mock documentation coverage
431        let passed = doc_coverage >= gate.threshold;
432
433        Ok(GateResult {
434            gate_name: gate.name.clone(),
435            passed,
436            score: doc_coverage,
437            details: format!("Documentation coverage: {doc_coverage:.1}%"),
438            improvement_suggestions: if passed {
439                vec![]
440            } else {
441                vec![
442                    "Add doc comments for all public APIs".to_string(),
443                    "Include executable examples in documentation".to_string(),
444                    "Document mathematical foundations and algorithms".to_string(),
445                    "Add performance characteristics documentation".to_string(),
446                ]
447            },
448        })
449    }
450
451    fn check_performance_regression(&self, gate: &QualityGate, _path: &Path) -> Result<GateResult> {
452        // Placeholder implementation
453        let performance_change: f64 = -0.02; // Mock 2% improvement
454        let passed = performance_change.abs() <= gate.threshold;
455
456        Ok(GateResult {
457            gate_name: gate.name.clone(),
458            passed,
459            score: if passed { 100.0 } else { 50.0 },
460            details: format!(
461                "Performance change: {:.1}% (threshold: ±{:.1}%)",
462                performance_change * 100.0,
463                gate.threshold * 100.0
464            ),
465            improvement_suggestions: if passed {
466                vec![]
467            } else {
468                vec![
469                    "Profile the code to identify performance bottlenecks".to_string(),
470                    "Consider algorithmic optimizations".to_string(),
471                    "Add benchmarks to track performance over time".to_string(),
472                ]
473            },
474        })
475    }
476
477    fn check_security_vulnerabilities(
478        &self,
479        gate: &QualityGate,
480        _path: &Path,
481    ) -> Result<GateResult> {
482        // Placeholder implementation
483        let vulnerabilities_found = 0; // Mock security scan result
484        let passed = vulnerabilities_found == 0;
485
486        Ok(GateResult {
487            gate_name: gate.name.clone(),
488            passed,
489            score: if passed { 100.0 } else { 0.0 },
490            details: format!("Security vulnerabilities found: {vulnerabilities_found}"),
491            improvement_suggestions: if passed {
492                vec![]
493            } else {
494                vec![
495                    "Update dependencies with known vulnerabilities".to_string(),
496                    "Review unsafe code blocks for safety".to_string(),
497                    "Validate all external inputs".to_string(),
498                ]
499            },
500        })
501    }
502
503    fn check_dependency_licenses(&self, gate: &QualityGate, _path: &Path) -> Result<GateResult> {
504        // Placeholder implementation
505        let license_compatible = true; // Mock license check
506
507        Ok(GateResult {
508            gate_name: gate.name.clone(),
509            passed: license_compatible,
510            score: if license_compatible { 100.0 } else { 0.0 },
511            details: if license_compatible {
512                "All dependencies have compatible licenses".to_string()
513            } else {
514                "Some dependencies have incompatible licenses".to_string()
515            },
516            improvement_suggestions: if license_compatible {
517                vec![]
518            } else {
519                vec![
520                    "Replace dependencies with incompatible licenses".to_string(),
521                    "Ensure all licenses are compatible with project license".to_string(),
522                ]
523            },
524        })
525    }
526
527    fn check_api_breaking(&self, gate: &QualityGate, _path: &Path) -> Result<GateResult> {
528        // Placeholder implementation
529        let breaking_changes = false; // Mock API breaking change detection
530
531        Ok(GateResult {
532            gate_name: gate.name.clone(),
533            passed: !breaking_changes,
534            score: if breaking_changes { 0.0 } else { 100.0 },
535            details: if breaking_changes {
536                "Breaking API changes detected".to_string()
537            } else {
538                "No breaking API changes detected".to_string()
539            },
540            improvement_suggestions: if breaking_changes {
541                vec![
542                    "Consider deprecation warnings before removing APIs".to_string(),
543                    "Use semantic versioning for breaking changes".to_string(),
544                    "Provide migration guides for API changes".to_string(),
545                ]
546            } else {
547                vec![]
548            },
549        })
550    }
551
552    fn create_default_quality_gates(config: &ContributionConfig) -> Vec<QualityGate> {
553        vec![
554            QualityGate {
555                name: "Code Coverage".to_string(),
556                description: "Minimum code coverage percentage".to_string(),
557                gate_type: QualityGateType::CodeCoverage,
558                threshold: config.min_coverage,
559                blocking: true,
560            },
561            QualityGate {
562                name: "Test Passing".to_string(),
563                description: "All tests must pass".to_string(),
564                gate_type: QualityGateType::TestPassing,
565                threshold: 100.0,
566                blocking: true,
567            },
568            QualityGate {
569                name: "Lint Compliance".to_string(),
570                description: "Code must pass linting checks".to_string(),
571                gate_type: QualityGateType::LintCompliance,
572                threshold: 95.0,
573                blocking: true,
574            },
575            QualityGate {
576                name: "Documentation Coverage".to_string(),
577                description: "Minimum documentation coverage".to_string(),
578                gate_type: QualityGateType::DocumentationCoverage,
579                threshold: 85.0,
580                blocking: false,
581            },
582            QualityGate {
583                name: "Performance Regression".to_string(),
584                description: "No significant performance regression".to_string(),
585                gate_type: QualityGateType::PerformanceRegression,
586                threshold: config.performance_threshold,
587                blocking: false,
588            },
589            QualityGate {
590                name: "Security Vulnerabilities".to_string(),
591                description: "No security vulnerabilities".to_string(),
592                gate_type: QualityGateType::SecurityVulnerabilities,
593                threshold: 0.0,
594                blocking: true,
595            },
596            QualityGate {
597                name: "Dependency Licenses".to_string(),
598                description: "All dependencies have compatible licenses".to_string(),
599                gate_type: QualityGateType::DependencyLicenses,
600                threshold: 100.0,
601                blocking: true,
602            },
603            QualityGate {
604                name: "API Breaking Changes".to_string(),
605                description: "No breaking API changes without version bump".to_string(),
606                gate_type: QualityGateType::APIBreaking,
607                threshold: 0.0,
608                blocking: true,
609            },
610        ]
611    }
612}
613
614impl Default for ContributionChecker {
615    fn default() -> Self {
616        Self::new()
617    }
618}
619
620impl ContributionResult {
621    /// Get the overall quality score
622    pub fn quality_score(&self) -> f64 {
623        self.overall_score
624    }
625
626    /// Check if contribution meets all requirements
627    pub fn meets_requirements(&self) -> bool {
628        self.blocking_issues.is_empty()
629    }
630
631    /// Get recommendations for improvement
632    pub fn recommendations(&self) -> &[String] {
633        &self.recommendations
634    }
635
636    /// Generate a detailed report
637    pub fn generate_report(&self) -> String {
638        let mut report = String::new();
639
640        report.push_str("# Contribution Review Report\n\n");
641        report.push_str(&format!(
642            "**Overall Score**: {:.1}/100\n",
643            self.overall_score
644        ));
645        report.push_str(&format!(
646            "**Status**: {}\n\n",
647            if self.meets_requirements() {
648                "✅ APPROVED"
649            } else {
650                "❌ NEEDS WORK"
651            }
652        ));
653
654        if !self.blocking_issues.is_empty() {
655            report.push_str("## 🚫 Blocking Issues\n\n");
656            for issue in &self.blocking_issues {
657                report.push_str(&format!("- {issue}\n"));
658            }
659            report.push('\n');
660        }
661
662        report.push_str("## 📊 Quality Gate Results\n\n");
663        for result in &self.gate_results {
664            let status = if result.passed { "✅" } else { "❌" };
665            report.push_str(&format!(
666                "### {} {} ({:.1}/100)\n",
667                status, result.gate_name, result.score
668            ));
669            report.push_str(&format!("{}\n\n", result.details));
670
671            if !result.improvement_suggestions.is_empty() {
672                report.push_str("**Suggestions:**\n");
673                for suggestion in &result.improvement_suggestions {
674                    report.push_str(&format!("- {suggestion}\n"));
675                }
676                report.push('\n');
677            }
678        }
679
680        if !self.recommendations.is_empty() {
681            report.push_str("## 💡 General Recommendations\n\n");
682            for recommendation in &self.recommendations {
683                report.push_str(&format!("- {recommendation}\n"));
684            }
685        }
686
687        report
688    }
689}
690
691/// Contribution workflow helper
692pub struct ContributionWorkflow {
693    steps: Vec<WorkflowStep>,
694}
695
696/// Individual workflow step
697#[derive(Debug, Clone)]
698pub struct WorkflowStep {
699    pub name: String,
700    pub description: String,
701    pub commands: Vec<String>,
702    pub automated: bool,
703}
704
705impl ContributionWorkflow {
706    /// Create the standard contribution workflow
707    pub fn standard() -> Self {
708        let steps = vec![
709            WorkflowStep {
710                name: "Fork and Clone".to_string(),
711                description: "Fork the repository and clone your fork".to_string(),
712                commands: vec![
713                    "# Fork on GitHub".to_string(),
714                    "git clone https://github.com/cool-japan/sklears.git".to_string(),
715                    "cd sklears".to_string(),
716                    "git remote add upstream https://github.com/cool-japan/sklears.git".to_string(),
717                ],
718                automated: false,
719            },
720            WorkflowStep {
721                name: "Create Feature Branch".to_string(),
722                description: "Create a new branch for your feature".to_string(),
723                commands: vec!["git checkout -b feature/your-feature-name".to_string()],
724                automated: false,
725            },
726            WorkflowStep {
727                name: "Development".to_string(),
728                description: "Implement your changes following best practices".to_string(),
729                commands: vec![
730                    "# Make your changes".to_string(),
731                    "cargo fmt".to_string(),
732                    "cargo clippy -- -D warnings".to_string(),
733                ],
734                automated: false,
735            },
736            WorkflowStep {
737                name: "Testing".to_string(),
738                description: "Run comprehensive tests".to_string(),
739                commands: vec![
740                    "cargo nextest run --no-fail-fast".to_string(),
741                    "cargo test --doc".to_string(),
742                    "cargo bench".to_string(),
743                ],
744                automated: true,
745            },
746            WorkflowStep {
747                name: "Documentation".to_string(),
748                description: "Update documentation".to_string(),
749                commands: vec![
750                    "cargo doc --no-deps".to_string(),
751                    "# Update CHANGELOG.md".to_string(),
752                    "# Update relevant README files".to_string(),
753                ],
754                automated: false,
755            },
756            WorkflowStep {
757                name: "Quality Checks".to_string(),
758                description: "Run quality gates".to_string(),
759                commands: vec![
760                    "cargo audit".to_string(),
761                    "cargo deny check".to_string(),
762                    "# Run contribution checker".to_string(),
763                ],
764                automated: true,
765            },
766            WorkflowStep {
767                name: "Commit and Push".to_string(),
768                description: "Commit changes with descriptive message".to_string(),
769                commands: vec![
770                    "git add .".to_string(),
771                    "git commit -m \"feat: add your feature description\"".to_string(),
772                    "git push origin feature/your-feature-name".to_string(),
773                ],
774                automated: false,
775            },
776            WorkflowStep {
777                name: "Pull Request".to_string(),
778                description: "Create pull request for review".to_string(),
779                commands: vec![
780                    "# Create PR on GitHub".to_string(),
781                    "# Fill out PR template".to_string(),
782                    "# Request reviews".to_string(),
783                ],
784                automated: false,
785            },
786        ];
787
788        Self { steps }
789    }
790
791    /// Get all workflow steps
792    pub fn steps(&self) -> &[WorkflowStep] {
793        &self.steps
794    }
795
796    /// Generate workflow documentation
797    pub fn generate_guide(&self) -> String {
798        let mut guide = String::new();
799
800        guide.push_str("# Contribution Workflow Guide\n\n");
801        guide.push_str("Follow these steps to contribute to sklears:\n\n");
802
803        for (i, step) in self.steps.iter().enumerate() {
804            guide.push_str(&format!("## {}. {}\n\n", i + 1, step.name));
805            guide.push_str(&format!("{}\n\n", step.description));
806
807            if !step.commands.is_empty() {
808                guide.push_str("```bash\n");
809                for command in &step.commands {
810                    guide.push_str(&format!("{command}\n"));
811                }
812                guide.push_str("```\n\n");
813            }
814
815            if step.automated {
816                guide.push_str("*This step can be automated in CI/CD.*\n\n");
817            }
818        }
819
820        guide
821    }
822}
823
824#[allow(non_snake_case)]
825#[cfg(test)]
826mod tests {
827    use super::*;
828
829    #[test]
830    fn test_contribution_checker_creation() {
831        let checker = ContributionChecker::new();
832        assert_eq!(checker.config.min_coverage, 90.0);
833        assert!(!checker.quality_gates.is_empty());
834    }
835
836    #[test]
837    fn test_review_criteria_default() {
838        let criteria = ReviewCriteria::default();
839        assert!(criteria.algorithmic_correctness.mathematical_soundness);
840        assert!(criteria.code_quality.type_safety);
841        assert!(criteria.documentation.api_docs);
842        assert!(criteria.testing.unit_tests);
843        assert!(criteria.performance.baseline_comparison);
844    }
845
846    #[test]
847    fn test_contribution_workflow() {
848        let workflow = ContributionWorkflow::standard();
849        assert!(!workflow.steps().is_empty());
850
851        let guide = workflow.generate_guide();
852        assert!(guide.contains("Contribution Workflow Guide"));
853        assert!(guide.contains("Fork and Clone"));
854    }
855
856    #[test]
857    fn test_quality_gate_types() {
858        let config = ContributionConfig::default();
859        let gates = ContributionChecker::create_default_quality_gates(&config);
860
861        let gate_types: Vec<_> = gates.iter().map(|g| &g.gate_type).collect();
862        assert!(gate_types
863            .iter()
864            .any(|t| matches!(t, QualityGateType::CodeCoverage)));
865        assert!(gate_types
866            .iter()
867            .any(|t| matches!(t, QualityGateType::TestPassing)));
868        assert!(gate_types
869            .iter()
870            .any(|t| matches!(t, QualityGateType::LintCompliance)));
871    }
872
873    #[test]
874    fn test_contribution_result() {
875        let result = ContributionResult {
876            overall_score: 85.5,
877            gate_results: vec![GateResult {
878                gate_name: "Test".to_string(),
879                passed: true,
880                score: 100.0,
881                details: "All tests pass".to_string(),
882                improvement_suggestions: vec![],
883            }],
884            recommendations: vec!["Improve documentation".to_string()],
885            blocking_issues: vec![],
886            timestamp: SystemTime::now(),
887        };
888
889        assert_eq!(result.quality_score(), 85.5);
890        assert!(result.meets_requirements());
891        assert_eq!(result.recommendations().len(), 1);
892
893        let report = result.generate_report();
894        assert!(report.contains("Contribution Review Report"));
895        assert!(report.contains("✅ APPROVED"));
896    }
897}