ralph_workflow/guidelines/
base.rs1#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
10pub enum CheckSeverity {
11 Critical,
13 High,
15 Medium,
17 Low,
19 Info,
21}
22
23impl std::fmt::Display for CheckSeverity {
24 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
25 match self {
26 Self::Critical => write!(f, "CRITICAL"),
27 Self::High => write!(f, "HIGH"),
28 Self::Medium => write!(f, "MEDIUM"),
29 Self::Low => write!(f, "LOW"),
30 Self::Info => write!(f, "INFO"),
31 }
32 }
33}
34
35#[derive(Debug, Clone)]
37pub struct SeverityCheck {
38 pub(crate) check: String,
40 pub(crate) severity: CheckSeverity,
42}
43
44impl SeverityCheck {
45 pub(crate) fn new(check: impl Into<String>, severity: CheckSeverity) -> Self {
46 Self {
47 check: check.into(),
48 severity,
49 }
50 }
51
52 pub(crate) fn critical(check: impl Into<String>) -> Self {
53 Self::new(check, CheckSeverity::Critical)
54 }
55
56 pub(crate) fn high(check: impl Into<String>) -> Self {
57 Self::new(check, CheckSeverity::High)
58 }
59
60 pub(crate) fn medium(check: impl Into<String>) -> Self {
61 Self::new(check, CheckSeverity::Medium)
62 }
63
64 pub(crate) fn low(check: impl Into<String>) -> Self {
65 Self::new(check, CheckSeverity::Low)
66 }
67
68 pub(crate) fn info(check: impl Into<String>) -> Self {
69 Self::new(check, CheckSeverity::Info)
70 }
71}
72
73#[derive(Debug, Clone)]
77pub struct ReviewGuidelines {
78 pub(crate) quality_checks: Vec<String>,
79 pub(crate) security_checks: Vec<String>,
80 pub(crate) performance_checks: Vec<String>,
81 pub(crate) testing_checks: Vec<String>,
82 pub(crate) documentation_checks: Vec<String>,
83 pub(crate) idioms: Vec<String>,
84 pub(crate) anti_patterns: Vec<String>,
85 pub(crate) concurrency_checks: Vec<String>,
86 pub(crate) resource_checks: Vec<String>,
87 pub(crate) observability_checks: Vec<String>,
88 pub(crate) secrets_checks: Vec<String>,
89 pub(crate) api_design_checks: Vec<String>,
90}
91
92impl Default for ReviewGuidelines {
93 fn default() -> Self {
94 Self {
95 quality_checks: vec![
96 "Code follows consistent style and formatting".to_string(),
97 "Functions have single responsibility".to_string(),
98 "Error handling is comprehensive".to_string(),
99 "No dead code or unused imports".to_string(),
100 ],
101 security_checks: vec![
102 "No hardcoded secrets or credentials".to_string(),
103 "Input validation on external data".to_string(),
104 "Proper authentication/authorization checks".to_string(),
105 ],
106 performance_checks: vec![
107 "No obvious performance bottlenecks".to_string(),
108 "Efficient data structures used".to_string(),
109 ],
110 testing_checks: vec![
111 "Tests cover main functionality".to_string(),
112 "Edge cases are tested".to_string(),
113 ],
114 documentation_checks: vec![
115 "Public APIs are documented".to_string(),
116 "Complex logic has explanatory comments".to_string(),
117 ],
118 idioms: vec!["Code follows language conventions".to_string()],
119 anti_patterns: vec!["Avoid code duplication".to_string()],
120 concurrency_checks: vec![
121 "Shared mutable state is properly synchronized".to_string(),
122 "No potential deadlocks (lock ordering)".to_string(),
123 ],
124 resource_checks: vec![
125 "Resources are properly closed/released".to_string(),
126 "No resource leaks in error paths".to_string(),
127 ],
128 observability_checks: vec![
129 "Errors are logged with context".to_string(),
130 "Critical operations have appropriate logging".to_string(),
131 ],
132 secrets_checks: vec![
133 "Secrets loaded from environment/config, not hardcoded".to_string(),
134 "Sensitive data not logged or exposed in errors".to_string(),
135 ],
136 api_design_checks: vec![
137 "API follows consistent naming conventions".to_string(),
138 "Breaking changes are clearly documented".to_string(),
139 ],
140 }
141 }
142}
143
144impl ReviewGuidelines {
145 pub(crate) fn get_all_checks(&self) -> Vec<SeverityCheck> {
150 let mut checks = Vec::new();
151
152 for check in &self.security_checks {
154 checks.push(SeverityCheck::critical(check.clone()));
155 }
156 for check in &self.secrets_checks {
157 checks.push(SeverityCheck::critical(check.clone()));
158 }
159
160 for check in &self.concurrency_checks {
162 checks.push(SeverityCheck::high(check.clone()));
163 }
164
165 for check in &self.resource_checks {
167 checks.push(SeverityCheck::high(check.clone()));
168 }
169
170 for check in &self.quality_checks {
172 checks.push(SeverityCheck::medium(check.clone()));
173 }
174 for check in &self.anti_patterns {
175 checks.push(SeverityCheck::medium(check.clone()));
176 }
177
178 for check in &self.performance_checks {
180 checks.push(SeverityCheck::medium(check.clone()));
181 }
182 for check in &self.testing_checks {
183 checks.push(SeverityCheck::medium(check.clone()));
184 }
185 for check in &self.api_design_checks {
186 checks.push(SeverityCheck::medium(check.clone()));
187 }
188
189 for check in &self.observability_checks {
191 checks.push(SeverityCheck::low(check.clone()));
192 }
193 for check in &self.documentation_checks {
194 checks.push(SeverityCheck::low(check.clone()));
195 }
196
197 for check in &self.idioms {
199 checks.push(SeverityCheck::info(check.clone()));
200 }
201
202 checks
203 }
204
205 pub(crate) fn summary(&self) -> String {
207 format!(
208 "{} quality checks, {} security checks, {} anti-patterns",
209 self.quality_checks.len(),
210 self.security_checks.len(),
211 self.anti_patterns.len()
212 )
213 }
214
215 pub(crate) const fn total_checks(&self) -> usize {
217 self.quality_checks.len()
218 + self.security_checks.len()
219 + self.performance_checks.len()
220 + self.testing_checks.len()
221 + self.documentation_checks.len()
222 + self.idioms.len()
223 + self.anti_patterns.len()
224 + self.concurrency_checks.len()
225 + self.resource_checks.len()
226 + self.observability_checks.len()
227 + self.secrets_checks.len()
228 + self.api_design_checks.len()
229 }
230}
231
232#[cfg(test)]
235impl ReviewGuidelines {
236 fn format_section(items: &[String], title: &str, limit: usize) -> Option<String> {
238 if items.is_empty() {
239 return None;
240 }
241 let mut lines: Vec<String> = items
242 .iter()
243 .take(limit)
244 .map(|s| format!(" - {s}"))
245 .collect();
246 if items.len() > limit {
247 lines.push(format!(" - ... (+{} more)", items.len() - limit));
248 }
249 Some(format!("{}:\n{}", title, lines.join("\n")))
250 }
251
252 pub(crate) fn format_for_prompt(&self) -> String {
254 let mut sections = Vec::new();
255
256 if let Some(s) = Self::format_section(&self.quality_checks, "CODE QUALITY", 10) {
257 sections.push(s);
258 }
259 if let Some(s) = Self::format_section(&self.security_checks, "SECURITY", 10) {
260 sections.push(s);
261 }
262 if let Some(s) = Self::format_section(&self.performance_checks, "PERFORMANCE", 8) {
263 sections.push(s);
264 }
265 if let Some(s) = Self::format_section(&self.anti_patterns, "AVOID", 8) {
266 sections.push(s);
267 }
268
269 sections.join("\n\n")
270 }
271
272 pub(crate) fn format_for_prompt_with_priorities(&self) -> String {
277 fn push_section(
278 sections: &mut Vec<String>,
279 header: &str,
280 checks: &[SeverityCheck],
281 limit: usize,
282 ) {
283 if checks.is_empty() {
284 return;
285 }
286 let mut items: Vec<String> = checks
287 .iter()
288 .take(limit)
289 .map(|c| format!(" - {}", c.check))
290 .collect();
291 if checks.len() > limit {
292 items.push(format!(" - ... (+{} more)", checks.len() - limit));
293 }
294 sections.push(format!("{}\n{}", header, items.join("\n")));
295 }
296
297 let mut sections = Vec::new();
298
299 let critical_checks: Vec<SeverityCheck> = self
301 .security_checks
302 .iter()
303 .chain(self.secrets_checks.iter())
304 .cloned()
305 .map(SeverityCheck::critical)
306 .collect();
307 push_section(
308 &mut sections,
309 "CRITICAL (must fix before merge):",
310 &critical_checks,
311 10,
312 );
313
314 let high_checks: Vec<SeverityCheck> = self
316 .concurrency_checks
317 .iter()
318 .chain(self.resource_checks.iter())
319 .cloned()
320 .map(SeverityCheck::high)
321 .collect();
322 push_section(
323 &mut sections,
324 "HIGH (should fix before merge):",
325 &high_checks,
326 10,
327 );
328
329 let medium_checks: Vec<SeverityCheck> = self
331 .quality_checks
332 .iter()
333 .chain(self.anti_patterns.iter())
334 .chain(self.performance_checks.iter())
335 .chain(self.testing_checks.iter())
336 .chain(self.api_design_checks.iter())
337 .cloned()
338 .map(SeverityCheck::medium)
339 .collect();
340 push_section(
341 &mut sections,
342 "MEDIUM (should address):",
343 &medium_checks,
344 12,
345 );
346
347 let low_checks: Vec<SeverityCheck> = self
349 .documentation_checks
350 .iter()
351 .chain(self.observability_checks.iter())
352 .cloned()
353 .map(SeverityCheck::low)
354 .collect();
355 push_section(&mut sections, "LOW (nice to have):", &low_checks, 10);
356
357 let info_checks: Vec<SeverityCheck> = self
359 .idioms
360 .iter()
361 .cloned()
362 .map(SeverityCheck::info)
363 .collect();
364 push_section(&mut sections, "INFO (observations):", &info_checks, 10);
365
366 sections.join("\n\n")
367 }
368}
369
370#[cfg(test)]
371mod tests {
372 use super::*;
373
374 #[test]
375 fn test_default_guidelines() {
376 let guidelines = ReviewGuidelines::default();
377 assert!(!guidelines.quality_checks.is_empty());
378 assert!(!guidelines.security_checks.is_empty());
379 }
380
381 #[test]
382 fn test_check_severity_ordering() {
383 assert!(CheckSeverity::Critical < CheckSeverity::High);
385 assert!(CheckSeverity::High < CheckSeverity::Medium);
386 assert!(CheckSeverity::Medium < CheckSeverity::Low);
387 assert!(CheckSeverity::Low < CheckSeverity::Info);
388 }
389
390 #[test]
391 fn test_check_severity_display() {
392 assert_eq!(format!("{}", CheckSeverity::Critical), "CRITICAL");
393 assert_eq!(format!("{}", CheckSeverity::High), "HIGH");
394 assert_eq!(format!("{}", CheckSeverity::Medium), "MEDIUM");
395 assert_eq!(format!("{}", CheckSeverity::Low), "LOW");
396 assert_eq!(format!("{}", CheckSeverity::Info), "INFO");
397 }
398
399 #[test]
400 fn test_severity_check_constructors() {
401 let critical = SeverityCheck::critical("test");
402 assert_eq!(critical.severity, CheckSeverity::Critical);
403 assert_eq!(critical.check, "test");
404
405 let high = SeverityCheck::high("high test");
406 assert_eq!(high.severity, CheckSeverity::High);
407
408 let medium = SeverityCheck::medium("medium test");
409 assert_eq!(medium.severity, CheckSeverity::Medium);
410
411 let low = SeverityCheck::low("low test");
412 assert_eq!(low.severity, CheckSeverity::Low);
413 }
414
415 #[test]
416 fn test_get_all_checks() {
417 let guidelines = ReviewGuidelines::default();
418 let all_checks = guidelines.get_all_checks();
419
420 assert!(!all_checks.is_empty());
422
423 let critical_count = all_checks
425 .iter()
426 .filter(|c| c.severity == CheckSeverity::Critical)
427 .count();
428 assert!(critical_count > 0);
429
430 let medium_count = all_checks
432 .iter()
433 .filter(|c| c.severity == CheckSeverity::Medium)
434 .count();
435 assert!(medium_count > 0);
436 }
437
438 #[test]
439 fn test_format_for_prompt_with_priorities() {
440 let guidelines = ReviewGuidelines::default();
441 let formatted = guidelines.format_for_prompt_with_priorities();
442
443 assert!(formatted.contains("CRITICAL"));
445 assert!(formatted.contains("HIGH"));
446 assert!(formatted.contains("MEDIUM"));
447 assert!(formatted.contains("LOW"));
448
449 assert!(formatted.contains("API follows consistent naming conventions"));
451 assert!(formatted.contains("Code follows language conventions"));
452 }
453
454 #[test]
455 fn test_summary() {
456 let guidelines = ReviewGuidelines::default();
457 let summary = guidelines.summary();
458
459 assert!(summary.contains("quality checks"));
460 assert!(summary.contains("security checks"));
461 assert!(summary.contains("anti-patterns"));
462 }
463
464 #[test]
465 fn test_total_checks() {
466 let guidelines = ReviewGuidelines::default();
467 let total = guidelines.total_checks();
468
469 let expected = guidelines.quality_checks.len()
471 + guidelines.security_checks.len()
472 + guidelines.performance_checks.len()
473 + guidelines.testing_checks.len()
474 + guidelines.documentation_checks.len()
475 + guidelines.idioms.len()
476 + guidelines.anti_patterns.len()
477 + guidelines.concurrency_checks.len()
478 + guidelines.resource_checks.len()
479 + guidelines.observability_checks.len()
480 + guidelines.secrets_checks.len()
481 + guidelines.api_design_checks.len();
482
483 assert_eq!(total, expected);
484 assert!(total > 10); }
486
487 #[test]
488 fn test_default_has_new_check_categories() {
489 let guidelines = ReviewGuidelines::default();
490
491 assert!(!guidelines.concurrency_checks.is_empty());
493 assert!(!guidelines.resource_checks.is_empty());
494 assert!(!guidelines.observability_checks.is_empty());
495 assert!(!guidelines.secrets_checks.is_empty());
496 assert!(!guidelines.api_design_checks.is_empty());
497 }
498}