lean_ctx/core/context_package/
loader.rs1use crate::core::knowledge::ProjectKnowledge;
2use crate::core::memory_policy::MemoryPolicy;
3use crate::core::property_graph::{CodeGraph, Edge, EdgeKind, Node, NodeKind};
4
5use super::content::{GraphLayer, KnowledgeLayer, PackageContent};
6use super::manifest::PackageManifest;
7
8#[derive(Debug, Clone, Default)]
9pub struct LoadReport {
10 pub package_name: String,
11 pub package_version: String,
12 pub knowledge_facts_merged: u32,
13 pub knowledge_facts_skipped: u32,
14 pub knowledge_patterns_merged: u32,
15 pub graph_nodes_imported: u32,
16 pub graph_edges_imported: u32,
17 pub gotchas_imported: u32,
18 pub warnings: Vec<String>,
19}
20
21impl std::fmt::Display for LoadReport {
22 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
23 writeln!(
24 f,
25 "Package: {} v{}",
26 self.package_name, self.package_version
27 )?;
28 if self.knowledge_facts_merged > 0 || self.knowledge_facts_skipped > 0 {
29 writeln!(
30 f,
31 " Knowledge: {} facts merged, {} skipped (duplicates)",
32 self.knowledge_facts_merged, self.knowledge_facts_skipped
33 )?;
34 }
35 if self.knowledge_patterns_merged > 0 {
36 writeln!(
37 f,
38 " Patterns: {} imported",
39 self.knowledge_patterns_merged
40 )?;
41 }
42 if self.graph_nodes_imported > 0 || self.graph_edges_imported > 0 {
43 writeln!(
44 f,
45 " Graph: {} nodes, {} edges imported",
46 self.graph_nodes_imported, self.graph_edges_imported
47 )?;
48 }
49 if self.gotchas_imported > 0 {
50 writeln!(f, " Gotchas: {} imported", self.gotchas_imported)?;
51 }
52 for w in &self.warnings {
53 writeln!(f, " WARNING: {w}")?;
54 }
55 Ok(())
56 }
57}
58
59pub fn load_package(
60 manifest: &PackageManifest,
61 content: &PackageContent,
62 project_root: &str,
63) -> Result<LoadReport, String> {
64 let mut report = LoadReport {
65 package_name: manifest.name.clone(),
66 package_version: manifest.version.clone(),
67 ..Default::default()
68 };
69
70 if let Some(ref kl) = content.knowledge {
71 merge_knowledge(kl, project_root, manifest, &mut report)?;
72 }
73
74 if let Some(ref gl) = content.graph {
75 import_graph(gl, project_root, &mut report)?;
76 }
77
78 if let Some(ref gotchas) = content.gotchas {
79 import_gotchas(gotchas, project_root, &mut report);
80 }
81
82 Ok(report)
83}
84
85fn merge_knowledge(
86 layer: &KnowledgeLayer,
87 project_root: &str,
88 manifest: &PackageManifest,
89 report: &mut LoadReport,
90) -> Result<(), String> {
91 let mut knowledge = ProjectKnowledge::load_or_create(project_root);
92 let policy = MemoryPolicy::default();
93 let source_tag = format!("{}@{}", manifest.name, manifest.version);
94
95 for fact in &layer.facts {
96 let exists = knowledge
97 .facts
98 .iter()
99 .any(|f| f.category == fact.category && f.key == fact.key && f.value == fact.value);
100
101 if exists {
102 report.knowledge_facts_skipped += 1;
103 continue;
104 }
105
106 knowledge.remember(
107 &fact.category,
108 &fact.key,
109 &fact.value,
110 &fact.source_session,
111 fact.confidence.min(0.8),
112 &policy,
113 );
114 if let Some(last) = knowledge.facts.last_mut() {
115 last.imported_from = Some(source_tag.clone());
116 }
117 report.knowledge_facts_merged += 1;
118 }
119
120 for pattern in &layer.patterns {
121 let exists = knowledge.patterns.iter().any(|p| {
122 p.pattern_type == pattern.pattern_type && p.description == pattern.description
123 });
124
125 if !exists {
126 knowledge.patterns.push(pattern.clone());
127 report.knowledge_patterns_merged += 1;
128 }
129 }
130
131 knowledge.save()?;
132 Ok(())
133}
134
135fn import_graph(
136 layer: &GraphLayer,
137 project_root: &str,
138 report: &mut LoadReport,
139) -> Result<(), String> {
140 let graph = CodeGraph::open(project_root).map_err(|e| format!("graph open: {e}"))?;
141
142 for node_export in &layer.nodes {
143 let node = Node {
144 id: None,
145 kind: NodeKind::parse(&node_export.kind),
146 name: node_export.name.clone(),
147 file_path: node_export.file_path.clone(),
148 line_start: node_export.line_start,
149 line_end: node_export.line_end,
150 metadata: node_export.metadata.clone(),
151 };
152
153 match graph.upsert_node(&node) {
154 Ok(_) => report.graph_nodes_imported += 1,
155 Err(e) => {
156 report
157 .warnings
158 .push(format!("node import failed ({}): {e}", node_export.name));
159 }
160 }
161 }
162
163 for edge_export in &layer.edges {
164 let source = graph
165 .get_node_by_path(&edge_export.source_path)
166 .map_err(|e| e.to_string())?;
167 let target = graph
168 .get_node_by_path(&edge_export.target_path)
169 .map_err(|e| e.to_string())?;
170
171 if let (Some(src), Some(tgt)) = (source, target) {
172 let edge = Edge {
173 id: None,
174 source_id: src.id.unwrap_or(0),
175 target_id: tgt.id.unwrap_or(0),
176 kind: EdgeKind::parse(&edge_export.kind),
177 metadata: edge_export.metadata.clone(),
178 };
179
180 match graph.upsert_edge(&edge) {
181 Ok(()) => report.graph_edges_imported += 1,
182 Err(e) => {
183 report.warnings.push(format!(
184 "edge import failed ({} -> {}): {e}",
185 edge_export.source_name, edge_export.target_name
186 ));
187 }
188 }
189 }
190 }
191
192 Ok(())
193}
194
195fn import_gotchas(
196 layer: &super::content::GotchasLayer,
197 project_root: &str,
198 report: &mut LoadReport,
199) {
200 use crate::core::gotcha_tracker::{
201 Gotcha, GotchaCategory, GotchaSeverity, GotchaSource, GotchaStore,
202 };
203
204 let mut store = GotchaStore::load(project_root);
205 let before = store.gotchas.len();
206
207 for g in &layer.gotchas {
208 let dup = store.gotchas.iter().any(|e| e.id == g.id);
209 if dup {
210 continue;
211 }
212
213 let category = GotchaCategory::from_str_loose(&g.category);
214 let severity = match g.severity.as_str() {
215 "critical" => GotchaSeverity::Critical,
216 "warning" => GotchaSeverity::Warning,
217 _ => GotchaSeverity::Info,
218 };
219
220 let mut gotcha = Gotcha::new(
221 category,
222 severity,
223 &g.trigger,
224 &g.resolution,
225 GotchaSource::AgentReported {
226 session_id: "package-import".into(),
227 },
228 "package-import",
229 );
230 g.id.clone_into(&mut gotcha.id);
231 g.file_patterns.clone_into(&mut gotcha.file_patterns);
232 gotcha.confidence = g.confidence.min(0.8);
233
234 store.gotchas.push(gotcha);
235 }
236
237 report.gotchas_imported = (store.gotchas.len() - before) as u32;
238 let _ = store.save(project_root);
239}