lean_ctx/core/knowledge/
format.rs1use super::ranking::{confidence_stars, sort_fact_for_output};
2use super::types::{ConsolidatedInsight, KnowledgeFact, ProjectKnowledge};
3use crate::core::memory_policy::MemoryPolicy;
4
5impl ProjectKnowledge {
6 pub fn consolidate(&mut self, summary: &str, session_ids: Vec<String>, policy: &MemoryPolicy) {
7 self.history.push(ConsolidatedInsight {
8 summary: summary.to_string(),
9 from_sessions: session_ids,
10 timestamp: chrono::Utc::now(),
11 });
12
13 if self.history.len() > policy.knowledge.max_history {
14 self.history
15 .drain(0..self.history.len() - policy.knowledge.max_history);
16 }
17 self.updated_at = chrono::Utc::now();
18 }
19
20 pub fn format_summary(&self) -> String {
21 let mut out = String::new();
22 let current_facts: Vec<&KnowledgeFact> =
23 self.facts.iter().filter(|f| f.is_current()).collect();
24
25 if !current_facts.is_empty() {
26 out.push_str("PROJECT KNOWLEDGE:\n");
27 let mut rooms: Vec<(String, usize)> = self.list_rooms();
28 rooms.sort_by(|a, b| b.1.cmp(&a.1).then_with(|| a.0.cmp(&b.0)));
29
30 let total_rooms = rooms.len();
31 rooms.truncate(crate::core::budgets::KNOWLEDGE_SUMMARY_ROOMS_LIMIT);
32
33 for (cat, _count) in rooms {
34 out.push_str(&format!(" [{cat}]\n"));
35
36 let mut facts_in_cat: Vec<&KnowledgeFact> = current_facts
37 .iter()
38 .copied()
39 .filter(|f| f.category == cat)
40 .collect();
41 facts_in_cat.sort_by(|a, b| sort_fact_for_output(a, b));
42
43 let total_in_cat = facts_in_cat.len();
44 facts_in_cat.truncate(crate::core::budgets::KNOWLEDGE_SUMMARY_FACTS_PER_ROOM_LIMIT);
45
46 for f in facts_in_cat {
47 let key = crate::core::sanitize::neutralize_metadata(&f.key);
48 let val = crate::core::sanitize::neutralize_metadata(&f.value);
49 out.push_str(&format!(
50 " {}: {} (confidence: {:.0}%)\n",
51 key,
52 val,
53 f.confidence * 100.0
54 ));
55 }
56 if total_in_cat > crate::core::budgets::KNOWLEDGE_SUMMARY_FACTS_PER_ROOM_LIMIT {
57 out.push_str(&format!(
58 " … +{} more\n",
59 total_in_cat - crate::core::budgets::KNOWLEDGE_SUMMARY_FACTS_PER_ROOM_LIMIT
60 ));
61 }
62 }
63
64 if total_rooms > crate::core::budgets::KNOWLEDGE_SUMMARY_ROOMS_LIMIT {
65 out.push_str(&format!(
66 " … +{} more rooms\n",
67 total_rooms - crate::core::budgets::KNOWLEDGE_SUMMARY_ROOMS_LIMIT
68 ));
69 }
70 }
71
72 if !self.patterns.is_empty() {
73 out.push_str("PROJECT PATTERNS:\n");
74 let mut patterns = self.patterns.clone();
75 patterns.sort_by(|a, b| {
76 b.created_at
77 .cmp(&a.created_at)
78 .then_with(|| a.pattern_type.cmp(&b.pattern_type))
79 .then_with(|| a.description.cmp(&b.description))
80 });
81 let total = patterns.len();
82 patterns.truncate(crate::core::budgets::KNOWLEDGE_PATTERNS_LIMIT);
83 for p in &patterns {
84 let ty = crate::core::sanitize::neutralize_metadata(&p.pattern_type);
85 let desc = crate::core::sanitize::neutralize_metadata(&p.description);
86 out.push_str(&format!(" [{ty}] {desc}\n"));
87 }
88 if total > crate::core::budgets::KNOWLEDGE_PATTERNS_LIMIT {
89 out.push_str(&format!(
90 " … +{} more\n",
91 total - crate::core::budgets::KNOWLEDGE_PATTERNS_LIMIT
92 ));
93 }
94 }
95
96 if out.is_empty() {
97 out
98 } else {
99 crate::core::sanitize::fence_content("project_knowledge", out.trim_end())
100 }
101 }
102
103 pub fn format_aaak(&self) -> String {
104 let current_facts: Vec<&KnowledgeFact> =
105 self.facts.iter().filter(|f| f.is_current()).collect();
106
107 if current_facts.is_empty() && self.patterns.is_empty() {
108 return String::new();
109 }
110
111 let mut out = String::new();
112
113 let mut rooms: Vec<(String, usize)> = self.list_rooms();
114 rooms.sort_by(|a, b| b.1.cmp(&a.1).then_with(|| a.0.cmp(&b.0)));
115 rooms.truncate(crate::core::budgets::KNOWLEDGE_AAAK_ROOMS_LIMIT);
116
117 for (cat, _count) in rooms {
118 let mut facts_in_cat: Vec<&KnowledgeFact> = current_facts
119 .iter()
120 .copied()
121 .filter(|f| f.category == cat)
122 .collect();
123 facts_in_cat.sort_by(|a, b| sort_fact_for_output(a, b));
124 facts_in_cat.truncate(crate::core::budgets::KNOWLEDGE_AAAK_FACTS_PER_ROOM_LIMIT);
125
126 let items: Vec<String> = facts_in_cat
127 .iter()
128 .map(|f| {
129 let stars = confidence_stars(f.confidence);
130 let key = crate::core::sanitize::neutralize_metadata(&f.key);
131 let val = crate::core::sanitize::neutralize_metadata(&f.value);
132 format!("{key}={val}{stars}")
133 })
134 .collect();
135 out.push_str(&format!(
136 "{}:{}\n",
137 crate::core::sanitize::neutralize_metadata(&cat.to_uppercase()),
138 items.join("|")
139 ));
140 }
141
142 if !self.patterns.is_empty() {
143 let mut patterns = self.patterns.clone();
144 patterns.sort_by(|a, b| {
145 b.created_at
146 .cmp(&a.created_at)
147 .then_with(|| a.pattern_type.cmp(&b.pattern_type))
148 .then_with(|| a.description.cmp(&b.description))
149 });
150 patterns.truncate(crate::core::budgets::KNOWLEDGE_PATTERNS_LIMIT);
151 let pat_items: Vec<String> = patterns
152 .iter()
153 .map(|p| {
154 let ty = crate::core::sanitize::neutralize_metadata(&p.pattern_type);
155 let desc = crate::core::sanitize::neutralize_metadata(&p.description);
156 format!("{ty}.{desc}")
157 })
158 .collect();
159 out.push_str(&format!("PAT:{}\n", pat_items.join("|")));
160 }
161
162 if out.is_empty() {
163 out
164 } else {
165 crate::core::sanitize::fence_content("project_memory_aaak", out.trim_end())
166 }
167 }
168
169 pub fn format_wakeup(&self) -> String {
170 let current_facts: Vec<&KnowledgeFact> = self
171 .facts
172 .iter()
173 .filter(|f| f.is_current() && f.confidence >= 0.7)
174 .collect();
175
176 if current_facts.is_empty() {
177 return String::new();
178 }
179
180 let mut top_facts: Vec<&KnowledgeFact> = current_facts;
181 top_facts.sort_by(|a, b| sort_fact_for_output(a, b));
182 top_facts.truncate(10);
183
184 let items: Vec<String> = top_facts
185 .iter()
186 .map(|f| {
187 let cat = crate::core::sanitize::neutralize_metadata(&f.category);
188 let key = crate::core::sanitize::neutralize_metadata(&f.key);
189 let val = crate::core::sanitize::neutralize_metadata(&f.value);
190 format!("{cat}/{key}={val}")
191 })
192 .collect();
193
194 crate::core::sanitize::fence_content(
195 "project_facts_wakeup",
196 &format!("FACTS:{}", items.join("|")),
197 )
198 }
199}