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    }
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!(
142            compressed
143                .instant_context
144                .as_ref()
145                .unwrap()
146                .tech_stack
147                .is_some()
148        );
149        assert!(compressed.stack.is_none());
150        assert!(compressed.human_context.is_none());
151    }
152
153    #[test]
154    fn test_compress_standard() {
155        let content = r#"
156faf_version: 2.5.0
157project:
158  name: test
159  goal: Testing
160instant_context:
161  what_building: App
162  tech_stack: Rust
163  key_files:
164    - a.rs
165    - b.rs
166    - c.rs
167    - d.rs
168    - e.rs
169    - f.rs
170    - g.rs
171stack:
172  backend: Rust
173human_context:
174  who: Devs
175"#;
176        let faf = parse(content).unwrap();
177        let compressed = compress(&faf, CompressionLevel::Standard);
178
179        assert!(compressed.stack.is_some());
180        // Key files limited to 5
181        assert_eq!(
182            compressed.instant_context.as_ref().unwrap().key_files.len(),
183            5
184        );
185        // Human context still excluded
186        assert!(compressed.human_context.is_none());
187    }
188}