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