1use crate::error::Result;
31use serde::{Deserialize, Serialize};
32use std::path::Path;
33use std::time::SystemTime;
34
35#[derive(Debug)]
37pub struct ContributionChecker {
38 #[allow(dead_code)]
39 config: ContributionConfig,
40 quality_gates: Vec<QualityGate>,
41}
42
43#[derive(Debug, Clone, Serialize, Deserialize)]
45pub struct ContributionConfig {
46 pub min_coverage: f64,
48 pub max_complexity: u32,
50 pub require_docs: bool,
52 pub require_property_tests: bool,
54 pub max_line_length: usize,
56 pub clippy_compliance: ClippyLevel,
58 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, }
73 }
74}
75
76#[derive(Debug, Clone, Serialize, Deserialize)]
78pub enum ClippyLevel {
79 AllowedByDefault,
80 Warn,
81 Deny,
82 Forbid,
83}
84
85#[derive(Debug, Clone, Default, Serialize, Deserialize)]
87pub struct ReviewCriteria {
88 pub algorithmic_correctness: AlgorithmicCriteria,
90 pub code_quality: CodeQualityCriteria,
92 pub documentation: DocumentationCriteria,
94 pub testing: TestingCriteria,
96 pub performance: PerformanceCriteria,
98}
99
100#[derive(Debug, Clone, Serialize, Deserialize)]
102pub struct AlgorithmicCriteria {
103 pub mathematical_soundness: bool,
105 pub convergence_guarantees: bool,
107 pub numerical_stability: bool,
109 pub edge_case_handling: bool,
111 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#[derive(Debug, Clone, Serialize, Deserialize)]
129pub struct CodeQualityCriteria {
130 pub type_safety: bool,
132 pub memory_efficiency: bool,
134 pub error_handling: bool,
136 pub api_consistency: bool,
138 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#[derive(Debug, Clone, Serialize, Deserialize)]
156pub struct DocumentationCriteria {
157 pub api_docs: bool,
159 pub examples: bool,
161 pub mathematical_background: bool,
163 pub performance_docs: bool,
165 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#[derive(Debug, Clone, Serialize, Deserialize)]
183pub struct TestingCriteria {
184 pub unit_tests: bool,
186 pub integration_tests: bool,
188 pub property_tests: bool,
190 pub edge_case_tests: bool,
192 pub benchmarks: bool,
194 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#[derive(Debug, Clone, Serialize, Deserialize)]
213pub struct PerformanceCriteria {
214 pub baseline_comparison: bool,
216 pub memory_analysis: bool,
218 pub scalability: bool,
220 pub parallelization: bool,
222 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#[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#[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#[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#[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 pub fn new() -> Self {
284 Self::with_config(ContributionConfig::default())
285 }
286
287 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 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 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 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 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 let coverage = 85.0; 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 let all_tests_pass = true; 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 let lint_score = 95.0; 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 let doc_coverage = 88.0; 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 let performance_change: f64 = -0.02; 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 let vulnerabilities_found = 0; 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 let license_compatible = true; 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 let breaking_changes = false; 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 pub fn quality_score(&self) -> f64 {
623 self.overall_score
624 }
625
626 pub fn meets_requirements(&self) -> bool {
628 self.blocking_issues.is_empty()
629 }
630
631 pub fn recommendations(&self) -> &[String] {
633 &self.recommendations
634 }
635
636 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
691pub struct ContributionWorkflow {
693 steps: Vec<WorkflowStep>,
694}
695
696#[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 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 pub fn steps(&self) -> &[WorkflowStep] {
793 &self.steps
794 }
795
796 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}