1#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16pub enum ChunkClassification {
17 Dna = 0b00,
19 Context = 0b01,
21 Pointer = 0b10,
23 Reserved = 0b11,
25}
26
27impl ChunkClassification {
28 pub const fn bits(&self) -> u32 {
30 *self as u32
31 }
32
33 pub fn from_bits(bits: u32) -> Self {
35 match bits & 0b11 {
36 0b00 => Self::Dna,
37 0b01 => Self::Context,
38 0b10 => Self::Pointer,
39 _ => Self::Reserved,
40 }
41 }
42
43 pub const fn name(&self) -> &'static str {
45 match self {
46 Self::Dna => "DNA",
47 Self::Context => "Context",
48 Self::Pointer => "Pointer",
49 Self::Reserved => "Reserved",
50 }
51 }
52}
53
54pub const CLASSIFICATION_MASK: u32 = 0b11;
56
57#[derive(Debug, Clone, Copy)]
59pub struct CanonicalChunk {
60 pub name: &'static str,
61 pub classification: ChunkClassification,
62 pub priority: u8,
63}
64
65pub const CANONICAL_CHUNKS: &[CanonicalChunk] = &[
74 CanonicalChunk {
76 name: "faf_version",
77 classification: ChunkClassification::Dna,
78 priority: 255,
79 },
80 CanonicalChunk {
81 name: "project",
82 classification: ChunkClassification::Dna,
83 priority: 255,
84 },
85 CanonicalChunk {
86 name: "app_type",
87 classification: ChunkClassification::Dna,
88 priority: 200,
89 },
90 CanonicalChunk {
91 name: "about",
92 classification: ChunkClassification::Dna,
93 priority: 150,
94 },
95 CanonicalChunk {
96 name: "stack",
97 classification: ChunkClassification::Dna,
98 priority: 200,
99 },
100 CanonicalChunk {
101 name: "human_context",
102 classification: ChunkClassification::Dna,
103 priority: 200,
104 },
105 CanonicalChunk {
106 name: "tech_stack",
107 classification: ChunkClassification::Dna,
108 priority: 200,
109 },
110 CanonicalChunk {
111 name: "key_files",
112 classification: ChunkClassification::Dna,
113 priority: 200,
114 },
115 CanonicalChunk {
116 name: "commands",
117 classification: ChunkClassification::Dna,
118 priority: 180,
119 },
120 CanonicalChunk {
121 name: "monorepo",
122 classification: ChunkClassification::Dna,
123 priority: 150,
124 },
125 CanonicalChunk {
126 name: "architecture",
127 classification: ChunkClassification::Dna,
128 priority: 128,
129 },
130 CanonicalChunk {
132 name: "scores",
133 classification: ChunkClassification::Context,
134 priority: 64,
135 },
136 CanonicalChunk {
137 name: "context",
138 classification: ChunkClassification::Context,
139 priority: 64,
140 },
141];
142
143pub fn canonical_chunk(name: &str) -> Option<&'static CanonicalChunk> {
145 CANONICAL_CHUNKS.iter().find(|c| c.name == name)
146}
147
148pub fn is_canonical(name: &str) -> bool {
150 canonical_chunk(name).is_some()
151}
152
153#[cfg(test)]
154mod tests {
155 use super::*;
156
157 #[test]
158 fn table_is_closed_at_13() {
159 assert_eq!(CANONICAL_CHUNKS.len(), 13);
161 let dna = CANONICAL_CHUNKS
162 .iter()
163 .filter(|c| c.classification == ChunkClassification::Dna)
164 .count();
165 let ctx = CANONICAL_CHUNKS
166 .iter()
167 .filter(|c| c.classification == ChunkClassification::Context)
168 .count();
169 assert_eq!(dna, 11);
170 assert_eq!(ctx, 2);
171 }
172
173 #[test]
174 fn mirrors_faf_cli_fafdata() {
175 for key in [
177 "faf_version",
178 "project",
179 "app_type",
180 "about",
181 "stack",
182 "human_context",
183 "monorepo",
184 "scores",
185 "tech_stack",
186 "key_files",
187 "commands",
188 "architecture",
189 "context",
190 ] {
191 assert!(is_canonical(key), "FafData key '{}' must be canonical", key);
192 }
193 for key in [
195 "instant_context",
196 "ai_score",
197 "ai_confidence",
198 "ai_tldr",
199 "context_quality",
200 "preferences",
201 "state",
202 "tags",
203 "meta",
204 "bi_sync",
205 "docs",
206 "generated",
207 ] {
208 assert!(!is_canonical(key), "'{}' must fold, not be a chunk", key);
209 }
210 }
211
212 #[test]
213 fn names_are_unique() {
214 for (i, a) in CANONICAL_CHUNKS.iter().enumerate() {
215 for b in &CANONICAL_CHUNKS[i + 1..] {
216 assert_ne!(a.name, b.name);
217 }
218 }
219 }
220
221 #[test]
222 fn identity_chunks_are_critical() {
223 assert_eq!(canonical_chunk("faf_version").unwrap().priority, 255);
224 assert_eq!(canonical_chunk("project").unwrap().priority, 255);
225 }
226
227 #[test]
228 fn context_is_the_fold_target() {
229 assert_eq!(
232 canonical_chunk("context").unwrap().classification,
233 ChunkClassification::Context
234 );
235 assert!(
236 !CANONICAL_CHUNKS
237 .iter()
238 .any(|c| c.classification == ChunkClassification::Pointer)
239 );
240 }
241
242 #[test]
243 fn unknown_keys_are_not_canonical() {
244 assert!(!is_canonical("custom_field"));
245 assert!(!is_canonical("my_exotic_field"));
246 assert!(!is_canonical("Project"));
248 assert!(is_canonical("project"));
249 }
250
251 #[test]
252 fn classification_bits_roundtrip() {
253 for class in &[
254 ChunkClassification::Dna,
255 ChunkClassification::Context,
256 ChunkClassification::Pointer,
257 ChunkClassification::Reserved,
258 ] {
259 assert_eq!(ChunkClassification::from_bits(class.bits()), *class);
260 }
261 assert_eq!(
263 ChunkClassification::from_bits(0xFF00_0001),
264 ChunkClassification::Context
265 );
266 }
267}