1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
#![cfg_attr(coverage_nightly, coverage(off))]
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use super::grade::{Grade, MetricCategory, PenaltyAttribution};
use super::language_simple::Language;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
/// Tdg score.
pub struct TdgScore {
pub structural_complexity: f32,
pub semantic_complexity: f32,
pub duplication_ratio: f32,
pub coupling_score: f32,
pub doc_coverage: f32,
pub consistency_score: f32,
pub entropy_score: f32, // New: Pattern entropy analysis
pub total: f32,
pub grade: Grade,
pub confidence: f32,
pub language: Language,
pub file_path: Option<PathBuf>,
pub penalties_applied: Vec<PenaltyAttribution>,
pub critical_defects_count: usize, // Known Defects v2.1: Count of critical defects
pub has_critical_defects: bool, // Known Defects v2.1: Auto-fail flag
pub has_contract_coverage: bool, // CB-1400: Provable-contract coverage (caps A→A- if false)
}
impl Default for TdgScore {
fn default() -> Self {
Self {
structural_complexity: 25.0,
semantic_complexity: 20.0,
duplication_ratio: 20.0,
coupling_score: 15.0,
doc_coverage: 10.0,
consistency_score: 10.0,
entropy_score: 0.0, // New: Start with 0, calculated during analysis
total: 100.0,
grade: Grade::APlus,
confidence: 1.0,
language: Language::Unknown,
file_path: None,
penalties_applied: Vec::new(),
critical_defects_count: 0,
has_critical_defects: false,
has_contract_coverage: false,
}
}
}
impl TdgScore {
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
/// Calculate total.
pub fn calculate_total(&mut self) {
// Clamp individual components to their expected weight ranges
// This ensures components can never exceed their designated contribution
self.structural_complexity = self.structural_complexity.clamp(0.0, 25.0);
self.semantic_complexity = self.semantic_complexity.clamp(0.0, 20.0);
self.duplication_ratio = self.duplication_ratio.clamp(0.0, 20.0);
self.coupling_score = self.coupling_score.clamp(0.0, 15.0);
self.doc_coverage = self.doc_coverage.clamp(0.0, 10.0);
self.consistency_score = self.consistency_score.clamp(0.0, 10.0);
// Entropy score should have a reasonable weight (max ~10 points)
// to balance with other metrics without dominating
self.entropy_score = self.entropy_score.clamp(0.0, 10.0);
// Sum all clamped components
let raw_total = self.structural_complexity
+ self.semantic_complexity
+ self.duplication_ratio
+ self.coupling_score
+ self.doc_coverage
+ self.consistency_score
+ self.entropy_score;
// The total is already in 0-110 range after clamping individual components
// Since the original weights sum to 100, and entropy adds up to 10 more,
// we need to normalize back to 0-100 scale
// Strategy: If raw_total <= 100, use it as-is for backward compatibility
// If raw_total > 100, scale it proportionally
if raw_total <= 100.0 {
self.total = raw_total.clamp(0.0, 100.0);
} else {
// Scale down proportionally when entropy pushes total above 100
const THEORETICAL_MAX: f32 = 110.0; // 25+20+20+15+10+10+10
self.total = (raw_total / THEORETICAL_MAX * 100.0).clamp(0.0, 100.0);
}
// Known Defects v2.1: Auto-fail if critical defects detected
if self.has_critical_defects {
self.total = 0.0;
self.grade = Grade::F;
} else {
// GH #680, second round. Both the file path and the aggregate path
// now use `Grade::from_score` and nothing else, so the grade is a
// function of the score alone.
//
// What used to sit here was the CB-1400 override
// `if !has_contract_coverage && grade < AMinus { grade = AMinus }`.
// `has_contract_coverage` is false whenever contract coverage was
// never *measured* (no `contracts/binding.yaml` ⇒ the field keeps
// its `false` default), so the cap fired for essentially every
// project: a fixture totalling 100.0 was graded `AMinus`, and
// `pmat tdg` printed `Overall Score: 100.0/100 (A-)`. An unmeasured
// signal must never rewrite a measured one. Contract coverage is
// still recorded on `has_contract_coverage` and still gated by
// `pmat comply` (CB-1400).
self.grade = Grade::from_score(self.total);
}
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
/// Set metric.
pub fn set_metric(&mut self, category: MetricCategory, value: f32) {
match category {
MetricCategory::StructuralComplexity => self.structural_complexity = value,
MetricCategory::SemanticComplexity => self.semantic_complexity = value,
MetricCategory::Duplication => self.duplication_ratio = value,
MetricCategory::Coupling => self.coupling_score = value,
MetricCategory::Documentation => self.doc_coverage = value,
MetricCategory::Consistency => self.consistency_score = value,
}
}
}