lean_ctx/core/context_package/
content.rs1use 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}