Skip to main content

faf_rust_sdk/
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_rust_sdk::{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    }
80}
81
82fn compress_standard(faf: &FafFile) -> FafData {
83    FafData {
84        faf_version: faf.data.faf_version.clone(),
85        project: faf.data.project.clone(),
86        ai_score: faf.data.ai_score.clone(),
87        ai_confidence: None,
88        ai_tldr: None,
89        instant_context: faf.data.instant_context.as_ref().map(|ic| InstantContext {
90            what_building: ic.what_building.clone(),
91            tech_stack: ic.tech_stack.clone(),
92            deployment: None,
93            key_files: ic.key_files.iter().take(5).cloned().collect(),
94            commands: Default::default(),
95        }),
96        context_quality: None,
97        stack: faf.data.stack.clone(),
98        human_context: None,
99        preferences: None,
100        state: None,
101        tags: Vec::new(),
102    }
103}
104
105/// Get estimated token count for compression level
106pub fn estimate_tokens(level: CompressionLevel) -> usize {
107    match level {
108        CompressionLevel::Minimal => 150,
109        CompressionLevel::Standard => 400,
110        CompressionLevel::Full => 800,
111    }
112}
113
114#[cfg(test)]
115mod tests {
116    use super::*;
117    use crate::parse;
118
119    #[test]
120    fn test_compress_minimal() {
121        let content = r#"
122faf_version: 2.5.0
123project:
124  name: test
125  goal: Testing
126instant_context:
127  what_building: App
128  tech_stack: Rust
129  key_files:
130    - a.rs
131    - b.rs
132stack:
133  backend: Rust
134human_context:
135  who: Devs
136"#;
137        let faf = parse(content).unwrap();
138        let compressed = compress(&faf, CompressionLevel::Minimal);
139
140        assert_eq!(compressed.project.name, "test");
141        assert!(compressed
142            .instant_context
143            .as_ref()
144            .unwrap()
145            .tech_stack
146            .is_some());
147        assert!(compressed.stack.is_none());
148        assert!(compressed.human_context.is_none());
149    }
150
151    #[test]
152    fn test_compress_standard() {
153        let content = r#"
154faf_version: 2.5.0
155project:
156  name: test
157  goal: Testing
158instant_context:
159  what_building: App
160  tech_stack: Rust
161  key_files:
162    - a.rs
163    - b.rs
164    - c.rs
165    - d.rs
166    - e.rs
167    - f.rs
168    - g.rs
169stack:
170  backend: Rust
171human_context:
172  who: Devs
173"#;
174        let faf = parse(content).unwrap();
175        let compressed = compress(&faf, CompressionLevel::Standard);
176
177        assert!(compressed.stack.is_some());
178        // Key files limited to 5
179        assert_eq!(
180            compressed.instant_context.as_ref().unwrap().key_files.len(),
181            5
182        );
183        // Human context still excluded
184        assert!(compressed.human_context.is_none());
185    }
186}