Skip to main content

navi_core/memory/
schemas.rs

1use serde::{Deserialize, Serialize};
2
3/// Represents the structured working state of the current session.
4#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
5pub struct SessionCheckpoint {
6    pub intent: String,
7    pub next_action: String,
8    pub constraints: String,
9    pub task_tree: String,
10    pub current_work: String,
11    pub involved_files: String,
12    pub discoveries: String,
13    pub errors_fixes: String,
14    pub runtime_state: String,
15    pub decisions: String,
16    pub misc: String,
17}
18
19impl SessionCheckpoint {
20    /// Parses a `SessionCheckpoint` from its Markdown string representation.
21    pub fn from_markdown(text: &str) -> Self {
22        let mut checkpoint = Self::default();
23        let mut current_section: Option<String> = None;
24        let mut current_content = String::new();
25
26        let commit_section = |checkpoint: &mut Self, section: Option<&str>, content: &str| {
27            let trimmed = content.trim().to_string();
28            if let Some(sec) = section {
29                match sec {
30                    "Current Intent" => checkpoint.intent = trimmed,
31                    "Next Action" => checkpoint.next_action = trimmed,
32                    "Working Constraints" => checkpoint.constraints = trimmed,
33                    "Task Tree" => checkpoint.task_tree = trimmed,
34                    "Current Work" => checkpoint.current_work = trimmed,
35                    "Involved Files" => checkpoint.involved_files = trimmed,
36                    "Cross-Task Discoveries" => checkpoint.discoveries = trimmed,
37                    "Errors and Fixes" => checkpoint.errors_fixes = trimmed,
38                    "Runtime State" => checkpoint.runtime_state = trimmed,
39                    "Design Decisions" => checkpoint.decisions = trimmed,
40                    "Miscellaneous Notes" => checkpoint.misc = trimmed,
41                    _ => {}
42                }
43            }
44        };
45
46        for line in text.lines() {
47            if line.starts_with("## ") {
48                commit_section(
49                    &mut checkpoint,
50                    current_section.as_deref(),
51                    &current_content,
52                );
53                current_section = Some(line["## ".len()..].trim().to_string());
54                current_content.clear();
55            } else if line.starts_with("# ") {
56                // Ignore main title "# Session Checkpoint"
57            } else {
58                if current_section.is_some() {
59                    current_content.push_str(line);
60                    current_content.push('\n');
61                }
62            }
63        }
64        commit_section(
65            &mut checkpoint,
66            current_section.as_deref(),
67            &current_content,
68        );
69        checkpoint
70    }
71
72    /// Renders the checkpoint to its standard Markdown format.
73    pub fn to_markdown(&self) -> String {
74        let mut out = String::new();
75        out.push_str("# Session Checkpoint\n\n");
76        out.push_str("## Current Intent\n");
77        out.push_str(if self.intent.is_empty() {
78            ""
79        } else {
80            &self.intent
81        });
82        out.push_str("\n\n## Next Action\n");
83        out.push_str(if self.next_action.is_empty() {
84            ""
85        } else {
86            &self.next_action
87        });
88        out.push_str("\n\n## Working Constraints\n");
89        out.push_str(if self.constraints.is_empty() {
90            ""
91        } else {
92            &self.constraints
93        });
94        out.push_str("\n\n## Task Tree\n");
95        out.push_str(if self.task_tree.is_empty() {
96            ""
97        } else {
98            &self.task_tree
99        });
100        out.push_str("\n\n## Current Work\n");
101        out.push_str(if self.current_work.is_empty() {
102            ""
103        } else {
104            &self.current_work
105        });
106        out.push_str("\n\n## Involved Files\n");
107        out.push_str(if self.involved_files.is_empty() {
108            ""
109        } else {
110            &self.involved_files
111        });
112        out.push_str("\n\n## Cross-Task Discoveries\n");
113        out.push_str(if self.discoveries.is_empty() {
114            ""
115        } else {
116            &self.discoveries
117        });
118        out.push_str("\n\n## Errors and Fixes\n");
119        out.push_str(if self.errors_fixes.is_empty() {
120            ""
121        } else {
122            &self.errors_fixes
123        });
124        out.push_str("\n\n## Runtime State\n");
125        out.push_str(if self.runtime_state.is_empty() {
126            ""
127        } else {
128            &self.runtime_state
129        });
130        out.push_str("\n\n## Design Decisions\n");
131        out.push_str(if self.decisions.is_empty() {
132            ""
133        } else {
134            &self.decisions
135        });
136        out.push_str("\n\n## Miscellaneous Notes\n");
137        out.push_str(if self.misc.is_empty() { "" } else { &self.misc });
138        out.push_str("\n");
139        out
140    }
141}