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}