1use crate::budget::{CompressionMode, TaskScope};
10use crate::input::InputFormat;
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
13pub enum TransformId {
14 JsonMinify,
15 JsonFieldFold,
16 JsonValueDict,
17 SchemaCompaction,
18 LogCompaction,
19 DiffCompaction,
20}
21
22impl TransformId {
23 pub fn as_str(&self) -> &'static str {
24 match self {
25 TransformId::JsonMinify => "json_minify",
26 TransformId::JsonFieldFold => "json_field_fold",
27 TransformId::JsonValueDict => "json_value_dict",
28 TransformId::SchemaCompaction => "schema_compaction",
29 TransformId::LogCompaction => "log_compaction",
30 TransformId::DiffCompaction => "diff_compaction",
31 }
32 }
33}
34
35#[derive(Debug, Clone, Copy)]
36pub struct ModeEntry {
37 pub transform_id: TransformId,
38 pub version: &'static str,
39 pub conservative_enabled: bool,
40 pub balanced_enabled: bool,
41 pub aggressive_enabled: bool,
42 pub experimental: bool,
43 pub max_ratio_conservative: f64,
44 pub max_ratio_balanced: f64,
45 pub max_ratio_aggressive: f64,
46 pub task_scopes: &'static [TaskScope],
47 pub applicable_formats: &'static [InputFormat],
48}
49
50impl ModeEntry {
51 pub fn enabled_for(&self, mode: CompressionMode) -> bool {
52 match mode {
53 CompressionMode::Conservative => self.conservative_enabled,
54 CompressionMode::Balanced => self.balanced_enabled,
55 CompressionMode::Aggressive => self.aggressive_enabled,
56 }
57 }
58
59 pub fn max_ratio_for(&self, mode: CompressionMode) -> f64 {
60 match mode {
61 CompressionMode::Conservative => self.max_ratio_conservative,
62 CompressionMode::Balanced => self.max_ratio_balanced,
63 CompressionMode::Aggressive => self.max_ratio_aggressive,
64 }
65 }
66
67 fn applies_to_format(&self, format: InputFormat) -> bool {
68 self.applicable_formats.contains(&format)
69 }
70}
71
72pub static ALL_ENTRIES: &[ModeEntry] = &[
79 ModeEntry {
80 transform_id: TransformId::JsonMinify,
81 version: "1.0.0",
82 conservative_enabled: true,
83 balanced_enabled: true,
84 aggressive_enabled: true,
85 experimental: false,
86 max_ratio_conservative: 1.0,
87 max_ratio_balanced: 1.0,
88 max_ratio_aggressive: 1.0,
89 task_scopes: &[TaskScope::All],
90 applicable_formats: &[
91 InputFormat::OpenAiJson,
92 InputFormat::AnthropicJson,
93 InputFormat::Json,
94 ],
95 },
96 ModeEntry {
102 transform_id: TransformId::JsonFieldFold,
103 version: "1.0.0",
104 conservative_enabled: false,
105 balanced_enabled: true,
106 aggressive_enabled: true,
107 experimental: false,
108 max_ratio_conservative: 0.0,
109 max_ratio_balanced: 1.0,
110 max_ratio_aggressive: 1.0,
111 task_scopes: &[TaskScope::All],
112 applicable_formats: &[InputFormat::Json],
113 },
114 ModeEntry {
118 transform_id: TransformId::JsonValueDict,
119 version: "1.0.0",
120 conservative_enabled: false,
121 balanced_enabled: true,
122 aggressive_enabled: true,
123 experimental: false,
124 max_ratio_conservative: 0.0,
125 max_ratio_balanced: 1.0,
126 max_ratio_aggressive: 1.0,
127 task_scopes: &[TaskScope::All],
128 applicable_formats: &[InputFormat::Json],
129 },
130 ModeEntry {
131 transform_id: TransformId::SchemaCompaction,
132 version: "1.0.0",
133 conservative_enabled: true,
134 balanced_enabled: true,
135 aggressive_enabled: true,
136 experimental: false,
137 max_ratio_conservative: 0.15,
138 max_ratio_balanced: 0.30,
139 max_ratio_aggressive: 0.50,
140 task_scopes: &[TaskScope::All],
141 applicable_formats: &[InputFormat::OpenAiJson, InputFormat::AnthropicJson],
142 },
143 ModeEntry {
144 transform_id: TransformId::LogCompaction,
145 version: "1.0.0",
146 conservative_enabled: false,
152 balanced_enabled: true,
153 aggressive_enabled: true,
154 experimental: false,
155 max_ratio_conservative: 0.0,
156 max_ratio_balanced: 0.65, max_ratio_aggressive: 0.75,
158 task_scopes: &[TaskScope::General, TaskScope::ChangeSummary],
159 applicable_formats: &[InputFormat::PlainText, InputFormat::CommandOutput],
160 },
161 ModeEntry {
162 transform_id: TransformId::DiffCompaction,
163 version: "1.0.0",
164 conservative_enabled: false,
173 balanced_enabled: false,
174 aggressive_enabled: false,
175 experimental: true,
176 max_ratio_conservative: 0.0,
177 max_ratio_balanced: 0.60,
178 max_ratio_aggressive: 0.70,
179 task_scopes: &[TaskScope::CodeReview, TaskScope::ChangeSummary],
180 applicable_formats: &[
181 InputFormat::PlainText,
182 InputFormat::CommandOutput,
183 InputFormat::GitDiff,
184 ],
185 },
186 ];
189
190pub fn pipeline_for(
194 mode: CompressionMode,
195 task_scope: TaskScope,
196 format: InputFormat,
197 experimental: bool,
198 enabled_ids: &[String],
199 disabled_ids: &[String],
200) -> Vec<&'static ModeEntry> {
201 ALL_ENTRIES
202 .iter()
203 .filter(|e| {
204 let mode_enabled = e.enabled_for(mode);
205 let experimentally_enabled = e.experimental && experimental;
206 let explicitly_enabled = (!e.experimental || experimental)
207 && enabled_ids.iter().any(|id| id == e.transform_id.as_str());
208 mode_enabled || experimentally_enabled || explicitly_enabled
209 })
210 .filter(|e| !disabled_ids.iter().any(|id| id == e.transform_id.as_str()))
211 .filter(|e| e.task_scopes.contains(&TaskScope::All) || e.task_scopes.contains(&task_scope))
212 .filter(|e| e.applies_to_format(format))
213 .collect()
214}
215
216#[cfg(test)]
217mod tests {
218 use super::*;
219
220 #[test]
221 fn transform_id_as_str_matches_canonical_ids() {
222 assert_eq!(TransformId::JsonMinify.as_str(), "json_minify");
223 assert_eq!(TransformId::SchemaCompaction.as_str(), "schema_compaction");
224 assert_eq!(TransformId::LogCompaction.as_str(), "log_compaction");
225 assert_eq!(TransformId::DiffCompaction.as_str(), "diff_compaction");
226 }
227
228 #[test]
229 fn conservative_mode_never_includes_experimental_lossy_transforms() {
230 let entries = pipeline_for(
231 CompressionMode::Conservative,
232 TaskScope::All,
233 InputFormat::PlainText,
234 true,
235 &[],
236 &[],
237 );
238 assert!(
239 entries
240 .iter()
241 .all(|e| e.transform_id != TransformId::LogCompaction
242 && e.transform_id != TransformId::DiffCompaction)
243 );
244 }
245
246 #[test]
247 fn balanced_mode_lossless_transforms_apply_to_openai_json() {
248 let entries = pipeline_for(
249 CompressionMode::Balanced,
250 TaskScope::All,
251 InputFormat::OpenAiJson,
252 false,
253 &[],
254 &[],
255 );
256 let ids: Vec<_> = entries.iter().map(|e| e.transform_id).collect();
257 assert!(ids.contains(&TransformId::JsonMinify));
258 assert!(ids.contains(&TransformId::SchemaCompaction));
259 }
260
261 #[test]
262 fn experimental_flag_enables_log_compaction_for_matching_task_scope() {
263 let entries = pipeline_for(
264 CompressionMode::Balanced,
265 TaskScope::General,
266 InputFormat::CommandOutput,
267 true,
268 &[],
269 &[],
270 );
271 assert!(
272 entries
273 .iter()
274 .any(|e| e.transform_id == TransformId::LogCompaction)
275 );
276 }
277
278 #[test]
279 fn log_compaction_skipped_for_non_applicable_format_even_when_experimental() {
280 let entries = pipeline_for(
281 CompressionMode::Balanced,
282 TaskScope::General,
283 InputFormat::OpenAiJson,
284 true,
285 &[],
286 &[],
287 );
288 assert!(
289 !entries
290 .iter()
291 .any(|e| e.transform_id == TransformId::LogCompaction)
292 );
293 }
294
295 #[test]
296 fn disabled_ids_remove_a_transform_even_when_otherwise_enabled() {
297 let entries = pipeline_for(
298 CompressionMode::Balanced,
299 TaskScope::All,
300 InputFormat::OpenAiJson,
301 false,
302 &[],
303 &["json_minify".to_string()],
304 );
305 assert!(
306 !entries
307 .iter()
308 .any(|e| e.transform_id == TransformId::JsonMinify)
309 );
310 }
311
312 #[test]
313 fn diff_compaction_requires_matching_task_scope_even_with_enable_flag() {
314 let entries = pipeline_for(
316 CompressionMode::Balanced,
317 TaskScope::Debugging,
318 InputFormat::GitDiff,
319 true,
320 &["diff_compaction".to_string()],
321 &[],
322 );
323 assert!(
324 !entries
325 .iter()
326 .any(|e| e.transform_id == TransformId::DiffCompaction)
327 );
328 }
329}