vtcode_session_store/
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(
36 workspace: &Path,
37 remove_legacy: bool,
38) -> Result<MigrationReport, SessionStoreError> {
39 let mut report = MigrationReport::default();
40 let vt = workspace.join(".vtcode");
41
42 let history_dir = vt.join("history");
43 if history_dir.is_dir() {
44 for entry in std::fs::read_dir(&history_dir)
45 .map_err(|e| SessionStoreError::io(history_dir.clone(), e))?
46 .filter_map(Result::ok)
47 {
48 let path = entry.path();
49 if path.extension().and_then(|e| e.to_str()) != Some("json") {
50 continue;
51 }
52 let Some(name) = path.file_stem().and_then(|s| s.to_str()) else {
53 continue;
54 };
55 let session_id = name.to_string();
56 let bytes = std::fs::read(&path).map_err(|e| SessionStoreError::io(path.clone(), e))?;
57 let dir = session_dir(workspace, &session_id);
58 std::fs::create_dir_all(dir.join(crate::DERIVED_DIR)).map_err(|e| {
59 SessionStoreError::CreateDir {
60 path: dir.clone(),
61 source: e,
62 }
63 })?;
64 let dest = dir.join(crate::DERIVED_DIR).join("memory.json");
65 std::fs::write(&dest, &bytes).map_err(|e| SessionStoreError::io(dest, e))?;
66 write_manifest(&dir, &session_id, &path, "completed")?;
67 report.sessions_created += 1;
68 report.memory_imported += 1;
69 report.bytes_migrated += bytes.len() as u64;
70 }
71 }
72
73 let logs_dir = vt.join("logs");
74 if logs_dir.is_dir() {
75 for entry in std::fs::read_dir(&logs_dir)
76 .map_err(|e| SessionStoreError::io(logs_dir.clone(), e))?
77 .filter_map(Result::ok)
78 {
79 let path = entry.path();
80 if path.extension().and_then(|e| e.to_str()) != Some("jsonl") {
81 continue;
82 }
83 let Some(name) = path.file_stem().and_then(|s| s.to_str()) else {
84 continue;
85 };
86 let session_id = format!("traj-{}", name.trim_start_matches("trajectory-"));
89 let bytes = std::fs::read(&path).map_err(|e| SessionStoreError::io(path.clone(), e))?;
90 let dir = session_dir(workspace, &session_id);
91 std::fs::create_dir_all(dir.join(crate::DERIVED_DIR)).map_err(|e| {
92 SessionStoreError::CreateDir {
93 path: dir.clone(),
94 source: e,
95 }
96 })?;
97 let dest = dir.join(crate::DERIVED_DIR).join("trajectory.jsonl");
98 std::fs::write(&dest, &bytes).map_err(|e| SessionStoreError::io(dest, e))?;
99 write_manifest(&dir, &session_id, &path, "completed")?;
100 report.sessions_created += 1;
101 report.trajectory_imported += 1;
102 report.bytes_migrated += bytes.len() as u64;
103 }
104 }
105
106 if remove_legacy {
107 let freed = crate::retention::gc_legacy(workspace)?;
108 let _ = freed;
109 }
110
111 Ok(report)
112}
113
114fn write_manifest(
115 dir: &Path,
116 session_id: &str,
117 source: &Path,
118 status: &str,
119) -> Result<(), SessionStoreError> {
120 let ts = source
121 .metadata()
122 .ok()
123 .and_then(|m| m.modified().ok())
124 .map(|t| chrono::DateTime::<Utc>::from(t).to_rfc3339())
125 .unwrap_or_else(|| Utc::now().to_rfc3339());
126 let mut manifest = SessionManifest::new(session_id);
127 manifest.created_at = ts.clone();
128 manifest.updated_at = ts;
129 manifest.status = status.to_string();
130 let path = dir.join("manifest.json");
131 let bytes = serde_json::to_string_pretty(&manifest)?;
132 std::fs::write(&path, bytes).map_err(|e| SessionStoreError::io(path, e))?;
133 Ok(())
134}