Skip to main content

faf_kernel/
compress.rs

1//! FAF compression for token optimization
2
3use crate::parser::FafFile;
4use crate::types::*;
5
6/// Compression levels
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub enum CompressionLevel {
9    /// Minimal: ~150 tokens
10    Minimal = 1,
11    /// Standard: ~400 tokens
12    Standard = 2,
13    /// Full: ~800 tokens
14    Full = 3,
15}
16
17/// Compress FAF to specified level
18///
19/// # Example
20///
21/// ```rust
22/// use faf_kernel::{parse, compress, CompressionLevel};
23///
24/// let content = r#"
25/// faf_version: 2.5.0
26/// project:
27///   name: test
28///   goal: Testing
29/// instant_context:
30///   tech_stack: Rust
31///   what_building: Test app
32///   key_files:
33///     - main.rs
34/// stack:
35///   backend: Rust
36/// human_context:
37///   who: Devs
38/// "#;
39///
40/// let faf = parse(content).unwrap();
41/// let compressed = compress(&faf, CompressionLevel::Minimal);
42/// // Minimal only keeps project + tech_stack
43/// ```
44pub fn compress(faf: &FafFile, level: CompressionLevel) -> FafData {
45    match level {
46        CompressionLevel::Minimal => compress_minimal(faf),
47        CompressionLevel::Standard => compress_standard(faf),
48        CompressionLevel::Full => faf.data.clone(),
49    }
50}
51
52fn compress_minimal(faf: &FafFile) -> FafData {
53    FafData {
54        faf_version: faf.data.faf_version.clone(),
55        project: Project {
56            name: faf.data.project.name.clone(),
57            goal: faf.data.project.goal.clone(),
58            main_language: None,
59            approach: None,
60            version: None,
61            license: None,
62        },
63        ai_score: None,
64        ai_confidence: None,
65        ai_tldr: None,
66        instant_context: faf.data.instant_context.as_ref().map(|ic| InstantContext {
67            what_building: None,
68            tech_stack: ic.tech_stack.clone(),
69            deployment: None,
70            key_files: Vec::new(),
71            commands: Default::default(),
72        }),
73        context_quality: None,
74        stack: None,
75        human_context: None,
76        preferences: None,
77        state: None,
78        tags: Vec::new(),
79        commands: Default::default(),
80        security: None,
81        ai_instructions: None,
82        conventions: Vec::new(),
83    }
84}
85
86fn compress_standard(faf: &FafFile) -> FafData {
87    FafData {
88        faf_version: faf.data.faf_version.clone(),
89        project: faf.data.project.clone(),
90        ai_score: faf.data.ai_score.clone(),
91        ai_confidence: None,
92        ai_tldr: None,
93        instant_context: faf.data.instant_context.as_ref().map(|ic| InstantContext {
94            what_building: ic.what_building.clone(),
95            tech_stack: ic.tech_stack.clone(),
96            deployment: None,
97            key_files: ic.key_files.iter().take(5).cloned().collect(),
98            commands: Default::default(),
99        }),
100        context_quality: None,
101        stack: faf.data.stack.clone(),
102        human_context: None,
103        preferences: None,
104        state: None,
105        tags: Vec::new(),
106        commands: Default::default(),
107        security: None,
108        ai_instructions: None,
109        conventions: Vec::new(),
110    }
111}
112
113/// Get estimated token count for compression level
114pub fn estimate_tokens(level: CompressionLevel) -> usize {
115    match level {
116        CompressionLevel::Minimal => 150,
117        CompressionLevel::Standard => 400,
118        CompressionLevel::Full => 800,
119    }
120}
121
122#[cfg(test)]
123mod tests {
124    use super::*;
125    use crate::parse;
126
127    #[test]
128    fn test_compress_minimal() {
129        let content = r#"
130faf_version: 2.5.0
131project:
132  name: test
133  goal: Testing
134instant_context:
135  what_building: App
136  tech_stack: Rust
137  key_files:
138    - a.rs
139    - b.rs
140stack:
141  backend: Rust
142human_context:
143  who: Devs
144"#;
145        let faf = parse(content).unwrap();
146        let compressed = compress(&faf, CompressionLevel::Minimal);
147
148        assert_eq!(compressed.project.name, "test");
149        assert!(
150            compressed
151                .instant_context
152                .as_ref()
153                .unwrap()
154                .tech_stack
155                .is_some()
156        );
157        assert!(compressed.stack.is_none());
158        assert!(compressed.human_context.is_none());
159    }
160
161    #[test]
162    fn test_compress_standard() {
163        let content = r#"
164faf_version: 2.5.0
165project:
166  name: test
167  goal: Testing
168instant_context:
169  what_building: App
170  tech_stack: Rust
171  key_files:
172    - a.rs
173    - b.rs
174    - c.rs
175    - d.rs
176    - e.rs
177    - f.rs
178    - g.rs
179stack:
180  backend: Rust
181human_context:
182  who: Devs
183"#;
184        let faf = parse(content).unwrap();
185        let compressed = compress(&faf, CompressionLevel::Standard);
186
187        assert!(compressed.stack.is_some());
188        // Key files limited to 5
189        assert_eq!(
190            compressed.instant_context.as_ref().unwrap().key_files.len(),
191            5
192        );
193        // Human context still excluded
194        assert!(compressed.human_context.is_none());
195    }
196}