Skip to main content

lean_ctx/core/context_package/
bundle.rs

1//! The portable context-package bundle format (#293).
2
3use chrono::{DateTime, Utc};
4use serde::{Deserialize, Serialize};
5
6use crate::core::session::{Decision, FileTouched, Finding, TaskInfo, TestSnapshot};
7use crate::core::session_summary::SummaryRecord;
8
9pub(crate) const FORMAT_VERSION: u32 = 1;
10
11/// A portable, self-contained context package.
12#[derive(Debug, Clone, Serialize, Deserialize)]
13pub struct ContextPackage {
14    pub format_version: u32,
15    pub created_at: DateTime<Utc>,
16    pub project_root: String,
17    pub session_id: String,
18    pub metadata: PackageMetadata,
19    pub session: SessionSlice,
20    #[serde(default, skip_serializing_if = "Vec::is_empty")]
21    pub summaries: Vec<SummaryRecord>,
22    #[serde(default, skip_serializing_if = "Vec::is_empty")]
23    pub knowledge: Vec<KnowledgeFact>,
24}
25
26/// Human-readable metadata about the package.
27#[derive(Debug, Clone, Serialize, Deserialize)]
28pub struct PackageMetadata {
29    pub agent_id: Option<String>,
30    pub description: Option<String>,
31    pub tool_calls: u32,
32    pub tokens_saved: u64,
33}
34
35/// The essential slice of session state to restore.
36#[derive(Debug, Clone, Serialize, Deserialize)]
37pub struct SessionSlice {
38    pub task: Option<TaskInfo>,
39    pub findings: Vec<Finding>,
40    pub decisions: Vec<Decision>,
41    pub files: Vec<FileTouched>,
42    pub next_steps: Vec<String>,
43    pub test_results: Option<TestSnapshot>,
44}
45
46/// A knowledge fact (compact, portable representation).
47#[derive(Debug, Clone, Serialize, Deserialize)]
48pub struct KnowledgeFact {
49    #[serde(default)]
50    pub origin: crate::core::knowledge::FactOrigin,
51    pub category: String,
52    pub key: String,
53    pub value: String,
54    pub confidence: f32,
55    pub created_at: DateTime<Utc>,
56}
57
58impl ContextPackage {
59    pub(crate) fn is_compatible(&self) -> bool {
60        self.format_version <= FORMAT_VERSION
61    }
62
63    pub fn summary_line(&self) -> String {
64        let desc = self
65            .metadata
66            .description
67            .as_deref()
68            .or(self.session.task.as_ref().map(|t| t.description.as_str()))
69            .unwrap_or("(no description)");
70        format!(
71            "[{}] {} — {} files, {} decisions, {} summaries, {} facts",
72            self.session_id
73                .split('-')
74                .next()
75                .unwrap_or(&self.session_id),
76            desc,
77            self.session.files.len(),
78            self.session.decisions.len(),
79            self.summaries.len(),
80            self.knowledge.len()
81        )
82    }
83}