Skip to main content

linthis/review/
mod.rs

1//! AI-powered code review module.
2//!
3//! Analyzes git diffs using AI to find code quality, security, and architecture
4//! issues. Supports background execution, auto-fix with PR/MR creation, and
5//! configurable notifications.
6
7pub mod analyzer;
8pub mod background;
9pub mod diff;
10pub mod notifier;
11pub mod platform;
12pub mod prompts;
13pub mod report;
14pub mod reviewer;
15
16pub mod fixer;
17
18use serde::{Deserialize, Serialize};
19use std::collections::HashMap;
20use std::fmt;
21use std::path::PathBuf;
22use std::str::FromStr;
23
24/// Auto-fix mode for the review command
25#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
26#[serde(rename_all = "lowercase")]
27pub enum AutoFixMode {
28    /// Create fix branch, commit, push, create PR/MR
29    Pr,
30    /// Commit fixes on current branch, do not push (exit 1 to block push)
31    Commit,
32    /// Apply fixes to working tree only, do not commit (exit 1 to block push)
33    #[default]
34    Apply,
35}
36
37impl FromStr for AutoFixMode {
38    type Err = String;
39
40    fn from_str(s: &str) -> Result<Self, Self::Err> {
41        match s.to_lowercase().as_str() {
42            "pr" => Ok(AutoFixMode::Pr),
43            "commit" => Ok(AutoFixMode::Commit),
44            "apply" => Ok(AutoFixMode::Apply),
45            _ => Err(format!(
46                "invalid auto-fix mode '{}': expected pr, commit, or apply",
47                s
48            )),
49        }
50    }
51}
52
53impl fmt::Display for AutoFixMode {
54    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
55        match self {
56            AutoFixMode::Pr => write!(f, "pr"),
57            AutoFixMode::Commit => write!(f, "commit"),
58            AutoFixMode::Apply => write!(f, "apply"),
59        }
60    }
61}
62
63/// Overall assessment of a code review
64#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
65pub enum Assessment {
66    /// Code is ready to merge
67    Ready,
68    /// Code needs work before merging
69    NeedsWork,
70    /// Critical issues found that must be fixed
71    CriticalIssues,
72}
73
74impl fmt::Display for Assessment {
75    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
76        match self {
77            Assessment::Ready => write!(f, "Ready"),
78            Assessment::NeedsWork => write!(f, "Needs Work"),
79            Assessment::CriticalIssues => write!(f, "Critical Issues"),
80        }
81    }
82}
83
84/// Severity level for a review issue
85#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
86pub enum Severity {
87    /// Must fix before merging
88    Critical,
89    /// Should fix, may block merge
90    Important,
91    /// Nice to fix, won't block merge
92    Minor,
93}
94
95impl fmt::Display for Severity {
96    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
97        match self {
98            Severity::Critical => write!(f, "Critical"),
99            Severity::Important => write!(f, "Important"),
100            Severity::Minor => write!(f, "Minor"),
101        }
102    }
103}
104
105/// A single issue found during code review
106#[derive(Debug, Clone, Serialize, Deserialize)]
107pub struct ReviewIssue {
108    /// Severity of the issue
109    pub severity: Severity,
110    /// Category (e.g., "security", "performance", "style")
111    pub category: String,
112    /// File path where the issue was found
113    pub file: PathBuf,
114    /// Line number (if applicable)
115    pub line: Option<u32>,
116    /// Description of the issue
117    pub message: String,
118    /// Suggested fix (if available)
119    pub suggestion: Option<String>,
120}
121
122impl fmt::Display for ReviewIssue {
123    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
124        write!(
125            f,
126            "[{}] {} ({}): {}",
127            self.severity,
128            self.file.display(),
129            self.category,
130            self.message
131        )
132    }
133}
134
135/// Summary statistics for a review
136#[derive(Debug, Clone, Serialize, Deserialize)]
137pub struct ReviewSummary {
138    /// Total number of files reviewed
139    pub files_reviewed: usize,
140    /// Total number of issues found
141    pub total_issues: usize,
142    /// Number of critical issues
143    pub critical_count: usize,
144    /// Number of important issues
145    pub important_count: usize,
146    /// Number of minor issues
147    pub minor_count: usize,
148    /// Overall assessment
149    pub assessment: Assessment,
150    /// AI-generated summary text
151    pub summary_text: String,
152}
153
154impl fmt::Display for ReviewSummary {
155    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
156        write!(
157            f,
158            "Review: {} ({} files, {} issues: {} critical, {} important, {} minor)",
159            self.assessment,
160            self.files_reviewed,
161            self.total_issues,
162            self.critical_count,
163            self.important_count,
164            self.minor_count
165        )
166    }
167}
168
169/// Status of a file in the diff
170#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
171pub enum FileStatus {
172    /// Newly added file
173    Added,
174    /// Modified existing file
175    Modified,
176    /// Deleted file
177    Deleted,
178    /// Renamed file (with original path)
179    Renamed { from: PathBuf },
180}
181
182impl fmt::Display for FileStatus {
183    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
184        match self {
185            FileStatus::Added => write!(f, "Added"),
186            FileStatus::Modified => write!(f, "Modified"),
187            FileStatus::Deleted => write!(f, "Deleted"),
188            FileStatus::Renamed { from } => {
189                write!(f, "Renamed from {}", from.display())
190            }
191        }
192    }
193}
194
195/// A file that was reviewed
196#[derive(Debug, Clone, Serialize, Deserialize)]
197pub struct ReviewedFile {
198    /// Path to the file
199    pub path: PathBuf,
200    /// File status in the diff
201    pub status: FileStatus,
202    /// Issues found in this file
203    pub issues: Vec<ReviewIssue>,
204}
205
206impl fmt::Display for ReviewedFile {
207    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
208        write!(
209            f,
210            "{} ({}, {} issues)",
211            self.path.display(),
212            self.status,
213            self.issues.len()
214        )
215    }
216}
217
218/// A single auto-fixed issue
219#[derive(Debug, Clone, Serialize, Deserialize)]
220pub struct AutoFix {
221    /// File that was fixed
222    pub file: PathBuf,
223    /// Line number of the fix
224    pub line: Option<u32>,
225    /// Description of what was fixed
226    pub description: String,
227}
228
229/// Complete result of a code review
230#[derive(Debug, Clone, Serialize, Deserialize)]
231pub struct ReviewResult {
232    /// Summary of the review
233    pub summary: ReviewSummary,
234    /// Files that were reviewed
235    pub files: Vec<ReviewedFile>,
236    /// All issues found across all files
237    pub issues: Vec<ReviewIssue>,
238    /// Base ref used for the diff
239    pub base_ref: String,
240    /// Head ref used for the diff
241    pub head_ref: String,
242    /// Issues that were auto-fixed
243    #[serde(default)]
244    pub auto_fixes: Vec<AutoFix>,
245}
246
247impl ReviewResult {
248    /// Count issues grouped by severity
249    pub fn count_by_severity(&self) -> HashMap<Severity, usize> {
250        let mut counts = HashMap::new();
251        for issue in &self.issues {
252            *counts.entry(issue.severity).or_insert(0) += 1;
253        }
254        counts
255    }
256}
257
258impl fmt::Display for ReviewResult {
259    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
260        write!(
261            f,
262            "Review {}..{}: {}",
263            self.base_ref, self.head_ref, self.summary
264        )
265    }
266}