Skip to main content

devclean/
model.rs

1use std::fmt;
2use std::path::PathBuf;
3
4use clap::ValueEnum;
5use serde::{Deserialize, Serialize};
6
7/// A class of rebuildable development data.
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, ValueEnum)]
9#[serde(rename_all = "kebab-case")]
10pub enum Category {
11    /// Cargo compilation outputs with Rust-specific markers.
12    RustTarget,
13    /// JavaScript dependency installations.
14    NodeModules,
15    /// Framework caches such as `.next` and `.svelte-kit`.
16    FrameworkCache,
17    /// Build directories underneath a recognized project manifest.
18    BuildOutput,
19    /// Test, mutation, type-checker, and lint caches.
20    TestCache,
21    /// Downloaded package-manager or tool caches.
22    GlobalCache,
23    /// Large runtimes or model caches that are expensive to restore.
24    ExpensiveGlobalCache,
25}
26
27/// Confidence assigned to a filesystem observation.
28#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
29#[serde(rename_all = "kebab-case")]
30pub enum Confidence {
31    /// A known rebuildable artifact with filesystem-verifiable evidence.
32    #[default]
33    Safe,
34    /// A cache-like directory that must be reviewed before a cleanup rule is added.
35    Review,
36    /// A directory protected from cleanup because it may contain source or user data.
37    Protected,
38}
39
40impl Category {
41    /// Returns all categories discovered by a comprehensive scan.
42    #[must_use]
43    pub fn all() -> [Self; 7] {
44        [
45            Self::RustTarget,
46            Self::NodeModules,
47            Self::FrameworkCache,
48            Self::BuildOutput,
49            Self::TestCache,
50            Self::GlobalCache,
51            Self::ExpensiveGlobalCache,
52        ]
53    }
54
55    /// Returns conservative categories used by `clean` unless overridden.
56    #[must_use]
57    pub fn safe_defaults() -> [Self; 3] {
58        [Self::RustTarget, Self::NodeModules, Self::FrameworkCache]
59    }
60}
61
62impl fmt::Display for Category {
63    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
64        let value = match self {
65            Self::RustTarget => "rust-target",
66            Self::NodeModules => "node-modules",
67            Self::FrameworkCache => "framework-cache",
68            Self::BuildOutput => "build-output",
69            Self::TestCache => "test-cache",
70            Self::GlobalCache => "global-cache",
71            Self::ExpensiveGlobalCache => "expensive-global-cache",
72        };
73        formatter.write_str(value)
74    }
75}
76
77/// A directory that can be rebuilt or downloaded again.
78#[derive(Debug, Clone, Serialize, Deserialize)]
79pub struct Candidate {
80    /// Artifact category.
81    pub category: Category,
82    /// Absolute or user-supplied path to the artifact.
83    pub path: PathBuf,
84    /// Estimated allocated bytes on disk.
85    pub bytes: u64,
86    /// Evidence used to classify the directory.
87    pub reason: String,
88    /// Latest observed modification time as seconds since the Unix epoch.
89    pub modified_at_unix: Option<u64>,
90    /// Safety confidence assigned by the scanner.
91    #[serde(default)]
92    pub confidence: Confidence,
93}
94
95/// A large cache-like directory observed by Learning Mode but never selected for cleanup.
96#[derive(Debug, Clone, Serialize, Deserialize)]
97pub struct ReviewCandidate {
98    /// Absolute path retained only in the local report.
99    pub path: PathBuf,
100    /// Estimated allocated bytes on disk.
101    pub bytes: u64,
102    /// Evidence that made the directory interesting to Learning Mode.
103    pub reason: String,
104    /// Latest observed modification time as seconds since the Unix epoch.
105    pub modified_at_unix: Option<u64>,
106    /// Review candidates are never promoted to safe without an explicit product rule.
107    pub confidence: Confidence,
108}
109
110/// One local-only Learning Mode measurement independent of cleanup eligibility filters.
111#[derive(Debug, Clone, Serialize, Deserialize)]
112pub struct LearningObservation {
113    /// Observed artifact path. Remote telemetry must never include this value.
114    pub path: PathBuf,
115    /// Known category when the scanner can classify the artifact safely.
116    pub category: Option<Category>,
117    /// Estimated allocated bytes on disk.
118    pub bytes: u64,
119    /// Filesystem evidence behind the observation.
120    pub reason: String,
121    /// Latest observed modification time as seconds since the Unix epoch.
122    pub modified_at_unix: Option<u64>,
123    /// Safety confidence independent of cleanup age and size filters.
124    pub confidence: Confidence,
125}
126
127/// Result of scanning one or more roots.
128#[derive(Debug, Clone, Serialize, Deserialize)]
129pub struct ScanReport {
130    /// Roots traversed by the scanner.
131    pub roots: Vec<PathBuf>,
132    /// Rebuildable directories found.
133    pub candidates: Vec<Candidate>,
134    /// Cache-like directories that require review and cannot be cleaned yet.
135    #[serde(default)]
136    pub review_candidates: Vec<ReviewCandidate>,
137    /// Local measurements used for growth history, including active artifacts filtered from cleanup.
138    #[serde(default)]
139    pub learning_observations: Vec<LearningObservation>,
140    /// Non-fatal traversal or metadata errors.
141    pub warnings: Vec<String>,
142    /// Total estimated allocated bytes for all candidates.
143    pub total_bytes: u64,
144    /// Total allocated bytes represented by review-only observations.
145    #[serde(default)]
146    pub review_total_bytes: u64,
147    /// Total allocated bytes represented by Learning Mode measurements.
148    #[serde(default)]
149    pub observed_total_bytes: u64,
150    /// Whether cleanup must repeat the Git tracked-file guard.
151    #[serde(default = "default_true")]
152    pub protect_git_tracked: bool,
153}
154
155const fn default_true() -> bool {
156    true
157}
158
159/// Human- or machine-readable report format.
160#[derive(Debug, Clone, Copy, ValueEnum)]
161pub enum OutputFormat {
162    /// Compact terminal table.
163    Table,
164    /// Structured JSON.
165    Json,
166    /// One JSON object per line for streaming automation.
167    Jsonl,
168    /// Standalone HTML document.
169    Html,
170}
171
172/// Controls presentation without changing the underlying scan result.
173#[derive(Debug, Clone, Copy, Default)]
174pub struct RenderOptions {
175    /// Replace absolute paths with stable root-relative placeholders.
176    pub redact_paths: bool,
177}