Skip to main content

lean_ctx/core/context_package/
content.rs

1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3
4use crate::core::knowledge::{ConsolidatedInsight, KnowledgeFact, ProjectPattern};
5
6use super::graph_model::ContextGraph;
7
8#[derive(Debug, Clone, Serialize, Deserialize, Default)]
9pub struct PackageContent {
10    #[serde(default, skip_serializing_if = "Option::is_none")]
11    pub knowledge: Option<KnowledgeLayer>,
12    #[serde(default, skip_serializing_if = "Option::is_none")]
13    pub graph: Option<GraphLayer>,
14    #[serde(default, skip_serializing_if = "Option::is_none")]
15    pub session: Option<SessionLayer>,
16    #[serde(default, skip_serializing_if = "Option::is_none")]
17    pub patterns: Option<PatternsLayer>,
18    #[serde(default, skip_serializing_if = "Option::is_none")]
19    pub gotchas: Option<GotchasLayer>,
20    #[serde(default, skip_serializing_if = "Option::is_none")]
21    pub context_graph: Option<ContextGraph>,
22}
23
24#[derive(Debug, Clone, Serialize, Deserialize)]
25pub struct KnowledgeLayer {
26    pub facts: Vec<KnowledgeFact>,
27    pub patterns: Vec<ProjectPattern>,
28    pub insights: Vec<ConsolidatedInsight>,
29    pub exported_at: DateTime<Utc>,
30}
31
32#[derive(Debug, Clone, Serialize, Deserialize)]
33pub struct GraphLayer {
34    pub nodes: Vec<GraphNodeExport>,
35    pub edges: Vec<GraphEdgeExport>,
36    pub exported_at: DateTime<Utc>,
37}
38
39#[derive(Debug, Clone, Serialize, Deserialize)]
40pub struct GraphNodeExport {
41    pub kind: String,
42    pub name: String,
43    pub file_path: String,
44    #[serde(default, skip_serializing_if = "Option::is_none")]
45    pub line_start: Option<usize>,
46    #[serde(default, skip_serializing_if = "Option::is_none")]
47    pub line_end: Option<usize>,
48    #[serde(default, skip_serializing_if = "Option::is_none")]
49    pub metadata: Option<String>,
50}
51
52#[derive(Debug, Clone, Serialize, Deserialize)]
53pub struct GraphEdgeExport {
54    pub source_path: String,
55    pub source_name: String,
56    pub target_path: String,
57    pub target_name: String,
58    pub kind: String,
59    #[serde(default, skip_serializing_if = "Option::is_none")]
60    pub metadata: Option<String>,
61}
62
63#[derive(Debug, Clone, Serialize, Deserialize)]
64pub struct SessionLayer {
65    pub task_description: Option<String>,
66    pub findings: Vec<SessionFinding>,
67    pub decisions: Vec<SessionDecision>,
68    pub next_steps: Vec<String>,
69    pub files_touched: Vec<String>,
70    pub exported_at: DateTime<Utc>,
71}
72
73#[derive(Debug, Clone, Serialize, Deserialize)]
74pub struct SessionFinding {
75    pub summary: String,
76    #[serde(default, skip_serializing_if = "Option::is_none")]
77    pub file: Option<String>,
78    #[serde(default, skip_serializing_if = "Option::is_none")]
79    pub line: Option<u32>,
80}
81
82#[derive(Debug, Clone, Serialize, Deserialize)]
83pub struct SessionDecision {
84    pub summary: String,
85    #[serde(default, skip_serializing_if = "Option::is_none")]
86    pub rationale: Option<String>,
87}
88
89#[derive(Debug, Clone, Serialize, Deserialize)]
90pub struct PatternsLayer {
91    pub patterns: Vec<ProjectPattern>,
92    pub exported_at: DateTime<Utc>,
93}
94
95#[derive(Debug, Clone, Serialize, Deserialize)]
96pub struct GotchasLayer {
97    pub gotchas: Vec<GotchaExport>,
98    pub exported_at: DateTime<Utc>,
99}
100
101#[derive(Debug, Clone, Serialize, Deserialize)]
102pub struct GotchaExport {
103    pub id: String,
104    pub category: String,
105    pub severity: String,
106    pub trigger: String,
107    pub resolution: String,
108    #[serde(default)]
109    pub file_patterns: Vec<String>,
110    pub confidence: f32,
111}
112
113impl PackageContent {
114    pub fn active_layer_count(&self) -> usize {
115        let mut n = 0;
116        if self.knowledge.is_some() {
117            n += 1;
118        }
119        if self.graph.is_some() {
120            n += 1;
121        }
122        if self.session.is_some() {
123            n += 1;
124        }
125        if self.patterns.is_some() {
126            n += 1;
127        }
128        if self.gotchas.is_some() {
129            n += 1;
130        }
131        if self.context_graph.is_some() {
132            n += 1;
133        }
134        n
135    }
136
137    pub fn is_empty(&self) -> bool {
138        self.active_layer_count() == 0
139    }
140
141    pub fn estimated_token_count(&self) -> usize {
142        let json = serde_json::to_string(self).unwrap_or_default();
143        json.len() / 4
144    }
145}