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 sens = crate::core::config::Config::load().sensitivity;
108 let current_facts: Vec<&KnowledgeFact> = self
109 .facts
110 .iter()
111 .filter(|f| f.is_current())
112 .filter(|f| {
113 !sens.enabled_effective()
114 || !crate::core::sensitivity::floor_blocks(
115 f.sensitivity
116 .max(crate::core::sensitivity::classify_content(&f.value)),
117 &sens,
118 )
119 })
120 .collect();
121
122 if current_facts.is_empty() && self.patterns.is_empty() {
123 return String::new();
124 }
125
126 let mut out = String::new();
127
128 let mut rooms: Vec<(String, usize)> = self.list_rooms();
129 rooms.sort_by(|a, b| b.1.cmp(&a.1).then_with(|| a.0.cmp(&b.0)));
130 rooms.truncate(crate::core::budgets::KNOWLEDGE_AAAK_ROOMS_LIMIT);
131
132 for (cat, _count) in rooms {
133 let mut facts_in_cat: Vec<&KnowledgeFact> = current_facts
134 .iter()
135 .copied()
136 .filter(|f| f.category == cat)
137 .collect();
138 facts_in_cat.sort_by(|a, b| sort_fact_for_output(a, b));
139 facts_in_cat.truncate(crate::core::budgets::KNOWLEDGE_AAAK_FACTS_PER_ROOM_LIMIT);
140
141 let items: Vec<String> = facts_in_cat
142 .iter()
143 .map(|f| {
144 let stars = confidence_stars(f.confidence);
145 let key = crate::core::sanitize::neutralize_metadata(&f.key);
146 let val = crate::core::sanitize::neutralize_metadata(&f.value);
147 format!("{key}={val}{stars}")
148 })
149 .collect();
150 out.push_str(&format!(
151 "{}:{}\n",
152 crate::core::sanitize::neutralize_metadata(&cat.to_uppercase()),
153 items.join("|")
154 ));
155 }
156
157 if !self.patterns.is_empty() {
158 let mut patterns = self.patterns.clone();
159 patterns.sort_by(|a, b| {
160 b.created_at
161 .cmp(&a.created_at)
162 .then_with(|| a.pattern_type.cmp(&b.pattern_type))
163 .then_with(|| a.description.cmp(&b.description))
164 });
165 patterns.truncate(crate::core::budgets::KNOWLEDGE_PATTERNS_LIMIT);
166 let pat_items: Vec<String> = patterns
167 .iter()
168 .map(|p| {
169 let ty = crate::core::sanitize::neutralize_metadata(&p.pattern_type);
170 let desc = crate::core::sanitize::neutralize_metadata(&p.description);
171 format!("{ty}.{desc}")
172 })
173 .collect();
174 out.push_str(&format!("PAT:{}\n", pat_items.join("|")));
175 }
176
177 if out.is_empty() {
178 out
179 } else {
180 crate::core::sanitize::fence_content("project_memory_aaak", out.trim_end())
181 }
182 }
183
184 pub fn format_wakeup(&self) -> String {
185 let current_facts: Vec<&KnowledgeFact> = self
186 .facts
187 .iter()
188 .filter(|f| f.is_current() && f.confidence >= 0.7)
189 .collect();
190
191 if current_facts.is_empty() {
192 return String::new();
193 }
194
195 let mut top_facts: Vec<&KnowledgeFact> = current_facts;
199 top_facts.sort_by(|a, b| sort_fact_for_output(a, b));
200 top_facts.truncate(20);
201
202 let clusters = super::chunking::cluster_facts(&top_facts);
203 crate::core::sanitize::fence_content(
204 "project_facts_wakeup",
205 &super::chunking::render_chunked(&clusters),
206 )
207 }
208}