vtcode_memory/
migration.rs1use std::path::Path;
12
13use chrono::Utc;
14
15use crate::error::SessionStoreError;
16use crate::{SessionManifest, session_dir};
17
18#[derive(Debug, Clone, Default, PartialEq, Eq)]
20pub struct MigrationReport {
21 pub sessions_created: usize,
23 pub memory_imported: usize,
25 pub trajectory_imported: usize,
27 pub bytes_migrated: u64,
29}
30
31pub fn migrate_legacy(workspace: &Path, remove_legacy: bool) -> Result<MigrationReport, SessionStoreError> {
36 let mut report = MigrationReport::default();
37 let vt = workspace.join(".vtcode");
38
39 let history_dir = vt.join("history");
40 if history_dir.is_dir() {
41 for entry in std::fs::read_dir(&history_dir)
42 .map_err(|e| SessionStoreError::io(history_dir.clone(), e))?
43 .filter_map(Result::ok)
44 {
45 let path = entry.path();
46 if path.extension().and_then(|e| e.to_str()) != Some("json") {
47 continue;
48 }
49 let Some(name) = path.file_stem().and_then(|s| s.to_str()) else {
50 continue;
51 };
52 let session_id = name.to_string();
53 let bytes = std::fs::read(&path).map_err(|e| SessionStoreError::io(path.clone(), e))?;
54 let dir = session_dir(workspace, &session_id);
55 crate::ensure_private_directory(&dir)?;
56 crate::ensure_private_directory(&dir.join(crate::DERIVED_DIR))?;
57 let dest = dir.join(crate::DERIVED_DIR).join("memory.json");
58 vtcode_commons::VtCodePaths::write_private_file_atomic(&dest, &bytes)
59 .map_err(|error| SessionStoreError::io(dest, std::io::Error::other(error)))?;
60 write_manifest(&dir, &session_id, &path, "completed")?;
61 report.sessions_created += 1;
62 report.memory_imported += 1;
63 report.bytes_migrated += bytes.len() as u64;
64 }
65 }
66
67 let logs_dir = vt.join("logs");
68 if logs_dir.is_dir() {
69 for entry in std::fs::read_dir(&logs_dir)
70 .map_err(|e| SessionStoreError::io(logs_dir.clone(), e))?
71 .filter_map(Result::ok)
72 {
73 let path = entry.path();
74 if path.extension().and_then(|e| e.to_str()) != Some("jsonl") {
75 continue;
76 }
77 let Some(name) = path.file_stem().and_then(|s| s.to_str()) else {
78 continue;
79 };
80 let session_id = format!("traj-{}", name.trim_start_matches("trajectory-"));
83 let bytes = std::fs::read(&path).map_err(|e| SessionStoreError::io(path.clone(), e))?;
84 let dir = session_dir(workspace, &session_id);
85 crate::ensure_private_directory(&dir)?;
86 crate::ensure_private_directory(&dir.join(crate::DERIVED_DIR))?;
87 let dest = dir.join(crate::DERIVED_DIR).join("trajectory.jsonl");
88 vtcode_commons::VtCodePaths::write_private_file_atomic(&dest, &bytes)
89 .map_err(|error| SessionStoreError::io(dest, std::io::Error::other(error)))?;
90 write_manifest(&dir, &session_id, &path, "completed")?;
91 report.sessions_created += 1;
92 report.trajectory_imported += 1;
93 report.bytes_migrated += bytes.len() as u64;
94 }
95 }
96
97 if remove_legacy {
98 let freed = crate::retention::gc_legacy(workspace)?;
99 let _ = freed;
100 }
101
102 Ok(report)
103}
104
105fn write_manifest(dir: &Path, session_id: &str, source: &Path, status: &str) -> Result<(), SessionStoreError> {
106 let ts = source
107 .metadata()
108 .ok()
109 .and_then(|m| m.modified().ok())
110 .map(|t| chrono::DateTime::<Utc>::from(t).to_rfc3339())
111 .unwrap_or_else(|| Utc::now().to_rfc3339());
112 let mut manifest = SessionManifest::new(session_id);
113 manifest.created_at = ts.clone();
114 manifest.updated_at = ts;
115 manifest.status = status.to_string();
116 let path = dir.join("manifest.json");
117 let bytes = serde_json::to_vec(&manifest)?;
118 vtcode_commons::VtCodePaths::write_private_file_atomic(&path, &bytes)
119 .map_err(|error| SessionStoreError::io(path, std::io::Error::other(error)))?;
120 Ok(())
121}