1+ //! Quality enforcement implementation for PDMT
2+ //!
3+ //! This module provides the core quality enforcement logic that integrates
4+ //! with PMAT quality proxy to ensure all generated code meets strict standards.
5+
6+ use crate :: error:: Result ;
7+ use crate :: models:: todo:: { Todo , TodoList } ;
8+ use crate :: quality:: proxy:: { ProxyConfig , ProxyOperation , ProxyRequest , QualityProxy } ;
9+ use serde:: { Deserialize , Serialize } ;
10+ use std:: collections:: HashMap ;
11+
12+ /// Configuration for quality enforcement
13+ #[ derive( Debug , Clone , Serialize , Deserialize ) ]
14+ pub struct EnforcementConfig {
15+ /// Proxy configuration
16+ pub proxy_config : ProxyConfig ,
17+ /// Enable coverage validation
18+ pub validate_coverage : bool ,
19+ /// Enable doctest validation
20+ pub validate_doctests : bool ,
21+ /// Enable property test validation
22+ pub validate_property_tests : bool ,
23+ /// Enable example validation
24+ pub validate_examples : bool ,
25+ /// Enable SATD detection
26+ pub detect_satd : bool ,
27+ /// Enable complexity analysis
28+ pub analyze_complexity : bool ,
29+ }
30+
31+ impl Default for EnforcementConfig {
32+ fn default ( ) -> Self {
33+ Self {
34+ proxy_config : ProxyConfig :: default ( ) ,
35+ validate_coverage : true ,
36+ validate_doctests : true ,
37+ validate_property_tests : true ,
38+ validate_examples : true ,
39+ detect_satd : true ,
40+ analyze_complexity : true ,
41+ }
42+ }
43+ }
44+
45+ /// Result of quality enforcement
46+ #[ derive( Debug , Clone , Serialize , Deserialize ) ]
47+ pub enum EnforcementResult {
48+ /// All quality gates passed
49+ AllPassed {
50+ /// Quality metrics
51+ metrics : HashMap < String , f64 > ,
52+ /// Applied fixes
53+ fixes : Vec < String > ,
54+ } ,
55+ /// Some quality gates failed
56+ Failed {
57+ /// Failed quality gates
58+ failures : Vec < QualityFailure > ,
59+ /// Suggestions for fixes
60+ suggestions : Vec < String > ,
61+ } ,
62+ /// Quality gates passed with warnings
63+ PassedWithWarnings {
64+ /// Warning messages
65+ warnings : Vec < String > ,
66+ /// Quality metrics
67+ metrics : HashMap < String , f64 > ,
68+ } ,
69+ }
70+
71+ /// Quality failure information
72+ #[ derive( Debug , Clone , Serialize , Deserialize ) ]
73+ pub struct QualityFailure {
74+ /// Gate that failed
75+ pub gate : String ,
76+ /// Failure message
77+ pub message : String ,
78+ /// Severity level
79+ pub severity : FailureSeverity ,
80+ /// File path (if applicable)
81+ pub file_path : Option < String > ,
82+ /// Line number (if applicable)
83+ pub line_number : Option < usize > ,
84+ }
85+
86+ /// Severity of quality failure
87+ #[ derive( Debug , Clone , Copy , Serialize , Deserialize , PartialEq , Eq , PartialOrd , Ord ) ]
88+ pub enum FailureSeverity {
89+ /// Informational - doesn't block
90+ Info ,
91+ /// Warning - should be addressed
92+ Warning ,
93+ /// Error - must be fixed
94+ Error ,
95+ /// Critical - immediate action required
96+ Critical ,
97+ }
98+
99+ /// Quality enforcer for PDMT with PMAT integration
100+ #[ derive( Debug ) ]
101+ pub struct QualityEnforcer {
102+ /// Quality proxy instance
103+ proxy : QualityProxy ,
104+ /// Enforcement configuration
105+ config : EnforcementConfig ,
106+ /// Cached validation results
107+ _cache : HashMap < String , EnforcementResult > ,
108+ }
109+
110+ impl QualityEnforcer {
111+ /// Create a new quality enforcer
112+ pub fn new ( proxy_endpoint : String ) -> Self {
113+ Self :: with_config ( proxy_endpoint, EnforcementConfig :: default ( ) )
114+ }
115+
116+ /// Create a new quality enforcer with custom configuration
117+ pub fn with_config ( proxy_endpoint : String , config : EnforcementConfig ) -> Self {
118+ let proxy = QualityProxy :: with_config ( proxy_endpoint, config. proxy_config . clone ( ) ) ;
119+ Self {
120+ proxy,
121+ config,
122+ _cache : HashMap :: new ( ) ,
123+ }
124+ }
125+
126+ /// Enforce quality standards on a todo list
127+ pub async fn enforce_todo_quality ( & mut self , todo_list : & TodoList ) -> Result < EnforcementResult > {
128+ let mut failures = Vec :: new ( ) ;
129+ let warnings = Vec :: new ( ) ;
130+ let mut metrics = HashMap :: new ( ) ;
131+
132+ // Validate each todo
133+ for todo in & todo_list. todos {
134+ if let Err ( failure) = self . validate_todo ( todo) . await {
135+ failures. push ( failure) ;
136+ }
137+ }
138+
139+ // TODO: Check for circular dependencies when TodoList supports it
140+
141+ // Calculate quality metrics
142+ let total_todos = todo_list. todos . len ( ) ;
143+ let actionable_todos = todo_list. todos . iter ( )
144+ . filter ( |t| Self :: is_actionable ( & t. content ) )
145+ . count ( ) ;
146+
147+ metrics. insert ( "total_todos" . to_string ( ) , total_todos as f64 ) ;
148+ metrics. insert ( "actionable_ratio" . to_string ( ) ,
149+ if total_todos > 0 { actionable_todos as f64 / total_todos as f64 } else { 0.0 } ) ;
150+
151+ // Determine result
152+ if failures. is_empty ( ) {
153+ if warnings. is_empty ( ) {
154+ Ok ( EnforcementResult :: AllPassed {
155+ metrics,
156+ fixes : Vec :: new ( ) ,
157+ } )
158+ } else {
159+ Ok ( EnforcementResult :: PassedWithWarnings {
160+ warnings,
161+ metrics,
162+ } )
163+ }
164+ } else {
165+ let suggestions = self . generate_suggestions ( & failures) ;
166+ Ok ( EnforcementResult :: Failed {
167+ failures,
168+ suggestions,
169+ } )
170+ }
171+ }
172+
173+ /// Validate a single todo
174+ async fn validate_todo ( & self , todo : & Todo ) -> std:: result:: Result < ( ) , QualityFailure > {
175+ // Check actionability
176+ if !Self :: is_actionable ( & todo. content ) {
177+ return Err ( QualityFailure {
178+ gate : "actionability" . to_string ( ) ,
179+ message : format ! ( "Todo '{}' does not start with an action verb" , todo. content) ,
180+ severity : FailureSeverity :: Error ,
181+ file_path : None ,
182+ line_number : None ,
183+ } ) ;
184+ }
185+
186+ // Check time estimate
187+ if let Some ( hours) = todo. estimated_hours {
188+ if hours < 0.5 || hours > 40.0 {
189+ return Err ( QualityFailure {
190+ gate : "time_estimation" . to_string ( ) ,
191+ message : format ! ( "Unrealistic time estimate: {} hours" , hours) ,
192+ severity : FailureSeverity :: Warning ,
193+ file_path : None ,
194+ line_number : None ,
195+ } ) ;
196+ }
197+ }
198+
199+ // Check content length
200+ if todo. content . len ( ) < 10 {
201+ return Err ( QualityFailure {
202+ gate : "content_validation" . to_string ( ) ,
203+ message : "Todo content too short (minimum 10 characters)" . to_string ( ) ,
204+ severity : FailureSeverity :: Error ,
205+ file_path : None ,
206+ line_number : None ,
207+ } ) ;
208+ }
209+
210+ if todo. content . len ( ) > 100 {
211+ return Err ( QualityFailure {
212+ gate : "content_validation" . to_string ( ) ,
213+ message : "Todo content too long (maximum 100 characters)" . to_string ( ) ,
214+ severity : FailureSeverity :: Warning ,
215+ file_path : None ,
216+ line_number : None ,
217+ } ) ;
218+ }
219+
220+ Ok ( ( ) )
221+ }
222+
223+ /// Check if content is actionable
224+ fn is_actionable ( content : & str ) -> bool {
225+ const ACTION_VERBS : & [ & str ] = & [
226+ "implement" , "create" , "build" , "fix" , "update" , "add" , "remove" ,
227+ "refactor" , "optimize" , "test" , "document" , "review" , "deploy" ,
228+ "configure" , "setup" , "install" , "integrate" , "validate" , "verify" ,
229+ "analyze" , "design" , "develop" , "enhance" , "improve" , "migrate" ,
230+ ] ;
231+
232+ let lower = content. to_lowercase ( ) ;
233+ ACTION_VERBS . iter ( ) . any ( |verb| lower. starts_with ( verb) )
234+ }
235+
236+ /// Generate suggestions for fixing failures
237+ fn generate_suggestions ( & self , failures : & [ QualityFailure ] ) -> Vec < String > {
238+ let mut suggestions = Vec :: new ( ) ;
239+
240+ for failure in failures {
241+ match failure. gate . as_str ( ) {
242+ "actionability" => {
243+ suggestions. push ( format ! (
244+ "Start todo with an action verb like 'Implement', 'Create', or 'Fix'"
245+ ) ) ;
246+ }
247+ "time_estimation" => {
248+ suggestions. push ( format ! (
249+ "Adjust time estimate to be between 0.5 and 40 hours"
250+ ) ) ;
251+ }
252+ "content_validation" => {
253+ if failure. message . contains ( "short" ) {
254+ suggestions. push ( "Add more specific details to the todo" . to_string ( ) ) ;
255+ } else {
256+ suggestions. push ( "Break down complex todo into smaller tasks" . to_string ( ) ) ;
257+ }
258+ }
259+ "dependency_validation" => {
260+ suggestions. push ( "Review and fix circular dependencies between tasks" . to_string ( ) ) ;
261+ }
262+ _ => { }
263+ }
264+ }
265+
266+ suggestions
267+ }
268+
269+ /// Enforce quality on generated code
270+ pub async fn enforce_code_quality (
271+ & mut self ,
272+ code : & str ,
273+ file_path : & str ,
274+ ) -> Result < EnforcementResult > {
275+ // Create proxy request
276+ let request = ProxyRequest {
277+ operation : ProxyOperation :: Validate ,
278+ file_path : file_path. to_string ( ) ,
279+ content : Some ( code. to_string ( ) ) ,
280+ mode : self . config . proxy_config . mode ,
281+ quality_config : self . config . proxy_config . clone ( ) ,
282+ metadata : HashMap :: new ( ) ,
283+ } ;
284+
285+ // Send to proxy
286+ let response = self . proxy . proxy_operation ( request) . await ?;
287+
288+ // Process response
289+ use crate :: quality:: proxy:: ProxyStatus ;
290+ match response. status {
291+ ProxyStatus :: Accepted => {
292+ let mut metrics = HashMap :: new ( ) ;
293+ metrics. insert ( "coverage" . to_string ( ) , response. metrics . coverage ) ;
294+ metrics. insert ( "complexity" . to_string ( ) , response. metrics . complexity as f64 ) ;
295+ metrics. insert ( "doctest_count" . to_string ( ) , response. metrics . doctest_count as f64 ) ;
296+
297+ Ok ( EnforcementResult :: AllPassed {
298+ metrics,
299+ fixes : response. applied_fixes ,
300+ } )
301+ }
302+ ProxyStatus :: Modified => {
303+ let mut metrics = HashMap :: new ( ) ;
304+ metrics. insert ( "coverage" . to_string ( ) , response. metrics . coverage ) ;
305+
306+ Ok ( EnforcementResult :: PassedWithWarnings {
307+ warnings : response. applied_fixes ,
308+ metrics,
309+ } )
310+ }
311+ ProxyStatus :: Rejected => {
312+ let failures: Vec < QualityFailure > = response. quality_report . violations
313+ . into_iter ( )
314+ . map ( |v| QualityFailure {
315+ gate : v. violation_type ,
316+ message : v. message ,
317+ severity : match v. severity {
318+ crate :: error:: Severity :: Error => FailureSeverity :: Error ,
319+ crate :: error:: Severity :: Warning => FailureSeverity :: Warning ,
320+ crate :: error:: Severity :: Info => FailureSeverity :: Info ,
321+ } ,
322+ file_path : Some ( file_path. to_string ( ) ) ,
323+ line_number : v. location . and_then ( |loc| {
324+ loc. split ( ':' ) . nth ( 1 ) . and_then ( |s| s. parse ( ) . ok ( ) )
325+ } ) ,
326+ } )
327+ . collect ( ) ;
328+
329+ Ok ( EnforcementResult :: Failed {
330+ failures,
331+ suggestions : response. quality_report . suggestions ,
332+ } )
333+ }
334+ }
335+ }
336+ }
0 commit comments