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 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    pub category: String,
50    pub key: String,
51    pub value: String,
52    pub confidence: f32,
53    pub created_at: DateTime<Utc>,
54}
55
56impl ContextPackage {
57    pub fn is_compatible(&self) -> bool {
58        self.format_version <= FORMAT_VERSION
59    }
60
61    pub fn summary_line(&self) -> String {
62        let desc = self
63            .metadata
64            .description
65            .as_deref()
66            .or(self.session.task.as_ref().map(|t| t.description.as_str()))
67            .unwrap_or("(no description)");
68        format!(
69            "[{}] {} — {} files, {} decisions, {} summaries, {} facts",
70            self.session_id
71                .split('-')
72                .next()
73                .unwrap_or(&self.session_id),
74            desc,
75            self.session.files.len(),
76            self.session.decisions.len(),
77            self.summaries.len(),
78            self.knowledge.len()
79        )
80    }
81}