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)]
75pub struct ReviewGuidelines {
76 pub(crate) quality_checks: Vec<String>,
78 pub(crate) security_checks: Vec<String>,
80 pub(crate) performance_checks: Vec<String>,
82 pub(crate) testing_checks: Vec<String>,
84 pub(crate) documentation_checks: Vec<String>,
86 pub(crate) idioms: Vec<String>,
88 pub(crate) anti_patterns: Vec<String>,
90 pub(crate) concurrency_checks: Vec<String>,
92 pub(crate) resource_checks: Vec<String>,
94 pub(crate) observability_checks: Vec<String>,
96 pub(crate) secrets_checks: Vec<String>,
98 pub(crate) api_design_checks: Vec<String>,
100}
101
102impl Default for ReviewGuidelines {
103 fn default() -> Self {
104 Self {
105 quality_checks: vec![
106 "Code follows consistent style and formatting".to_string(),
107 "Functions have single responsibility".to_string(),
108 "Error handling is comprehensive".to_string(),
109 "No dead code or unused imports".to_string(),
110 ],
111 security_checks: vec![
112 "No hardcoded secrets or credentials".to_string(),
113 "Input validation on external data".to_string(),
114 "Proper authentication/authorization checks".to_string(),
115 ],
116 performance_checks: vec![
117 "No obvious performance bottlenecks".to_string(),
118 "Efficient data structures used".to_string(),
119 ],
120 testing_checks: vec![
121 "Tests cover main functionality".to_string(),
122 "Edge cases are tested".to_string(),
123 ],
124 documentation_checks: vec![
125 "Public APIs are documented".to_string(),
126 "Complex logic has explanatory comments".to_string(),
127 ],
128 idioms: vec!["Code follows language conventions".to_string()],
129 anti_patterns: vec!["Avoid code duplication".to_string()],
130 concurrency_checks: vec![
131 "Shared mutable state is properly synchronized".to_string(),
132 "No potential deadlocks (lock ordering)".to_string(),
133 ],
134 resource_checks: vec![
135 "Resources are properly closed/released".to_string(),
136 "No resource leaks in error paths".to_string(),
137 ],
138 observability_checks: vec![
139 "Errors are logged with context".to_string(),
140 "Critical operations have appropriate logging".to_string(),
141 ],
142 secrets_checks: vec![
143 "Secrets loaded from environment/config, not hardcoded".to_string(),
144 "Sensitive data not logged or exposed in errors".to_string(),
145 ],
146 api_design_checks: vec![
147 "API follows consistent naming conventions".to_string(),
148 "Breaking changes are clearly documented".to_string(),
149 ],
150 }
151 }
152}
153
154impl ReviewGuidelines {
155 fn format_section(items: &[String], title: &str, limit: usize) -> Option<String> {
156 if items.is_empty() {
157 return None;
158 }
159 let mut lines: Vec<String> = items
160 .iter()
161 .take(limit)
162 .map(|s| format!(" - {s}"))
163 .collect();
164 if items.len() > limit {
165 lines.push(format!(" - ... (+{} more)", items.len() - limit));
166 }
167 Some(format!("{}:\n{}", title, lines.join("\n")))
168 }
169
170 pub(crate) fn format_for_prompt(&self) -> String {
172 let mut sections = Vec::new();
173
174 if let Some(s) = Self::format_section(&self.quality_checks, "CODE QUALITY", 10) {
175 sections.push(s);
176 }
177 if let Some(s) = Self::format_section(&self.security_checks, "SECURITY", 10) {
178 sections.push(s);
179 }
180 if let Some(s) = Self::format_section(&self.performance_checks, "PERFORMANCE", 8) {
181 sections.push(s);
182 }
183 if let Some(s) = Self::format_section(&self.anti_patterns, "AVOID", 8) {
184 sections.push(s);
185 }
186
187 sections.join("\n\n")
188 }
189
190 pub(crate) fn get_all_checks(&self) -> Vec<SeverityCheck> {
195 let mut checks = Vec::new();
196
197 for check in &self.security_checks {
199 checks.push(SeverityCheck::critical(check.clone()));
200 }
201 for check in &self.secrets_checks {
202 checks.push(SeverityCheck::critical(check.clone()));
203 }
204
205 for check in &self.concurrency_checks {
207 checks.push(SeverityCheck::high(check.clone()));
208 }
209
210 for check in &self.resource_checks {
212 checks.push(SeverityCheck::high(check.clone()));
213 }
214
215 for check in &self.quality_checks {
217 checks.push(SeverityCheck::medium(check.clone()));
218 }
219 for check in &self.anti_patterns {
220 checks.push(SeverityCheck::medium(check.clone()));
221 }
222
223 for check in &self.performance_checks {
225 checks.push(SeverityCheck::medium(check.clone()));
226 }
227 for check in &self.testing_checks {
228 checks.push(SeverityCheck::medium(check.clone()));
229 }
230 for check in &self.api_design_checks {
231 checks.push(SeverityCheck::medium(check.clone()));
232 }
233
234 for check in &self.observability_checks {
236 checks.push(SeverityCheck::low(check.clone()));
237 }
238 for check in &self.documentation_checks {
239 checks.push(SeverityCheck::low(check.clone()));
240 }
241
242 for check in &self.idioms {
244 checks.push(SeverityCheck::info(check.clone()));
245 }
246
247 checks
248 }
249
250 pub(crate) fn format_for_prompt_with_priorities(&self) -> String {
255 fn push_section(
256 sections: &mut Vec<String>,
257 header: &str,
258 checks: &[SeverityCheck],
259 limit: usize,
260 ) {
261 if checks.is_empty() {
262 return;
263 }
264 let mut items: Vec<String> = checks
265 .iter()
266 .take(limit)
267 .map(|c| format!(" - {}", c.check))
268 .collect();
269 if checks.len() > limit {
270 items.push(format!(" - ... (+{} more)", checks.len() - limit));
271 }
272 sections.push(format!("{}\n{}", header, items.join("\n")));
273 }
274
275 let mut sections = Vec::new();
276
277 let critical_checks: Vec<SeverityCheck> = self
279 .security_checks
280 .iter()
281 .chain(self.secrets_checks.iter())
282 .cloned()
283 .map(SeverityCheck::critical)
284 .collect();
285 push_section(
286 &mut sections,
287 "🔴 CRITICAL (must fix before merge):",
288 &critical_checks,
289 10,
290 );
291
292 let high_checks: Vec<SeverityCheck> = self
294 .concurrency_checks
295 .iter()
296 .chain(self.resource_checks.iter())
297 .cloned()
298 .map(SeverityCheck::high)
299 .collect();
300 push_section(
301 &mut sections,
302 "🟠HIGH (should fix before merge):",
303 &high_checks,
304 10,
305 );
306
307 let medium_checks: Vec<SeverityCheck> = self
309 .quality_checks
310 .iter()
311 .chain(self.anti_patterns.iter())
312 .chain(self.performance_checks.iter())
313 .chain(self.testing_checks.iter())
314 .chain(self.api_design_checks.iter())
315 .cloned()
316 .map(SeverityCheck::medium)
317 .collect();
318 push_section(
319 &mut sections,
320 "🟡 MEDIUM (should address):",
321 &medium_checks,
322 12,
323 );
324
325 let low_checks: Vec<SeverityCheck> = self
327 .documentation_checks
328 .iter()
329 .chain(self.observability_checks.iter())
330 .cloned()
331 .map(SeverityCheck::low)
332 .collect();
333 push_section(&mut sections, "🟢 LOW (nice to have):", &low_checks, 10);
334
335 let info_checks: Vec<SeverityCheck> = self
337 .idioms
338 .iter()
339 .cloned()
340 .map(SeverityCheck::info)
341 .collect();
342 push_section(&mut sections, "🔵 INFO (observations):", &info_checks, 10);
343
344 sections.join("\n\n")
345 }
346
347 pub(crate) fn summary(&self) -> String {
349 format!(
350 "{} quality checks, {} security checks, {} anti-patterns",
351 self.quality_checks.len(),
352 self.security_checks.len(),
353 self.anti_patterns.len()
354 )
355 }
356
357 pub(crate) const fn total_checks(&self) -> usize {
359 self.quality_checks.len()
360 + self.security_checks.len()
361 + self.performance_checks.len()
362 + self.testing_checks.len()
363 + self.documentation_checks.len()
364 + self.idioms.len()
365 + self.anti_patterns.len()
366 + self.concurrency_checks.len()
367 + self.resource_checks.len()
368 + self.observability_checks.len()
369 + self.secrets_checks.len()
370 + self.api_design_checks.len()
371 }
372}
373
374#[cfg(test)]
375mod tests {
376 use super::*;
377
378 #[test]
379 fn test_default_guidelines() {
380 let guidelines = ReviewGuidelines::default();
381 assert!(!guidelines.quality_checks.is_empty());
382 assert!(!guidelines.security_checks.is_empty());
383 }
384
385 #[test]
386 fn test_check_severity_ordering() {
387 assert!(CheckSeverity::Critical < CheckSeverity::High);
389 assert!(CheckSeverity::High < CheckSeverity::Medium);
390 assert!(CheckSeverity::Medium < CheckSeverity::Low);
391 assert!(CheckSeverity::Low < CheckSeverity::Info);
392 }
393
394 #[test]
395 fn test_check_severity_display() {
396 assert_eq!(format!("{}", CheckSeverity::Critical), "CRITICAL");
397 assert_eq!(format!("{}", CheckSeverity::High), "HIGH");
398 assert_eq!(format!("{}", CheckSeverity::Medium), "MEDIUM");
399 assert_eq!(format!("{}", CheckSeverity::Low), "LOW");
400 assert_eq!(format!("{}", CheckSeverity::Info), "INFO");
401 }
402
403 #[test]
404 fn test_severity_check_constructors() {
405 let critical = SeverityCheck::critical("test");
406 assert_eq!(critical.severity, CheckSeverity::Critical);
407 assert_eq!(critical.check, "test");
408
409 let high = SeverityCheck::high("high test");
410 assert_eq!(high.severity, CheckSeverity::High);
411
412 let medium = SeverityCheck::medium("medium test");
413 assert_eq!(medium.severity, CheckSeverity::Medium);
414
415 let low = SeverityCheck::low("low test");
416 assert_eq!(low.severity, CheckSeverity::Low);
417 }
418
419 #[test]
420 fn test_get_all_checks() {
421 let guidelines = ReviewGuidelines::default();
422 let all_checks = guidelines.get_all_checks();
423
424 assert!(!all_checks.is_empty());
426
427 let critical_count = all_checks
429 .iter()
430 .filter(|c| c.severity == CheckSeverity::Critical)
431 .count();
432 assert!(critical_count > 0);
433
434 let medium_count = all_checks
436 .iter()
437 .filter(|c| c.severity == CheckSeverity::Medium)
438 .count();
439 assert!(medium_count > 0);
440 }
441
442 #[test]
443 fn test_format_for_prompt_with_priorities() {
444 let guidelines = ReviewGuidelines::default();
445 let formatted = guidelines.format_for_prompt_with_priorities();
446
447 assert!(formatted.contains("CRITICAL"));
449 assert!(formatted.contains("HIGH"));
450 assert!(formatted.contains("MEDIUM"));
451 assert!(formatted.contains("LOW"));
452
453 assert!(formatted.contains("API follows consistent naming conventions"));
455 assert!(formatted.contains("Code follows language conventions"));
456 }
457
458 #[test]
459 fn test_summary() {
460 let guidelines = ReviewGuidelines::default();
461 let summary = guidelines.summary();
462
463 assert!(summary.contains("quality checks"));
464 assert!(summary.contains("security checks"));
465 assert!(summary.contains("anti-patterns"));
466 }
467
468 #[test]
469 fn test_total_checks() {
470 let guidelines = ReviewGuidelines::default();
471 let total = guidelines.total_checks();
472
473 let expected = guidelines.quality_checks.len()
475 + guidelines.security_checks.len()
476 + guidelines.performance_checks.len()
477 + guidelines.testing_checks.len()
478 + guidelines.documentation_checks.len()
479 + guidelines.idioms.len()
480 + guidelines.anti_patterns.len()
481 + guidelines.concurrency_checks.len()
482 + guidelines.resource_checks.len()
483 + guidelines.observability_checks.len()
484 + guidelines.secrets_checks.len()
485 + guidelines.api_design_checks.len();
486
487 assert_eq!(total, expected);
488 assert!(total > 10); }
490
491 #[test]
492 fn test_default_has_new_check_categories() {
493 let guidelines = ReviewGuidelines::default();
494
495 assert!(!guidelines.concurrency_checks.is_empty());
497 assert!(!guidelines.resource_checks.is_empty());
498 assert!(!guidelines.observability_checks.is_empty());
499 assert!(!guidelines.secrets_checks.is_empty());
500 assert!(!guidelines.api_design_checks.is_empty());
501 }
502}