Skip to main content

codelens_engine/memory/
archive.rs

1use std::path::Path;
2
3use anyhow::{Result, bail};
4
5use super::audit::{AuditRecorder, MemoryAuditEvent};
6use super::paths::{MemoryTier, collect_memory_files, resolve_memory_path};
7use super::policy::{ARCHIVE_DIRNAME, MemoryPolicy};
8
9pub fn archive_memory(memories_dir: &Path, name: &str) -> Result<()> {
10    archive_memory_rec(memories_dir, name, None)
11}
12
13/// Archive a memory entry with optional audit recording.
14pub fn archive_memory_rec(
15    memories_dir: &Path,
16    name: &str,
17    recorder: Option<&dyn AuditRecorder>,
18) -> Result<()> {
19    let policy = MemoryPolicy::load(memories_dir);
20    if policy.is_read_only(name) {
21        bail!("memory '{name}' is read-only and cannot be archived");
22    }
23    let source = resolve_memory_path(memories_dir, name)?;
24    if !source.is_file() {
25        bail!("memory not found: {name}");
26    }
27    let archive_dir = memories_dir.join(ARCHIVE_DIRNAME);
28    std::fs::create_dir_all(&archive_dir)?;
29    let dest = archive_dir.join(source.file_name().expect("file name"));
30    if dest.exists() {
31        bail!("archive already contains an entry for: {name}");
32    }
33    let path_str = source.to_string_lossy().to_string();
34    std::fs::rename(&source, &dest)?;
35    if let Some(rec) = recorder {
36        rec.record(&MemoryAuditEvent::Archived {
37            tier: MemoryTier::Project,
38            path: path_str,
39        });
40    }
41    Ok(())
42}
43
44/// List archived memory entries.
45pub fn list_archived(memories_dir: &Path) -> Result<Vec<String>> {
46    let archive_dir = memories_dir.join(ARCHIVE_DIRNAME);
47    if !archive_dir.is_dir() {
48        return Ok(Vec::new());
49    }
50    let mut names = Vec::new();
51    collect_memory_files(memories_dir, &archive_dir, &mut names);
52    for name in &mut names {
53        *name = format!("archived/{}", name);
54    }
55    names.sort();
56    Ok(names)
57}
58
59/// Restore an archived memory entry.
60/// Records an audit event via `recorder` when provided.
61pub fn restore_archived(memories_dir: &Path, name: &str) -> Result<()> {
62    restore_archived_rec(memories_dir, name, None)
63}
64
65/// Restore an archived memory entry with optional audit recording.
66pub fn restore_archived_rec(
67    memories_dir: &Path,
68    name: &str,
69    recorder: Option<&dyn AuditRecorder>,
70) -> Result<()> {
71    let short_name = name.trim_start_matches("archived/");
72    let archive_dir = memories_dir.join(ARCHIVE_DIRNAME);
73    let source = resolve_memory_path(&archive_dir, short_name)?;
74    if !source.is_file() {
75        bail!("archived memory not found: {name}");
76    }
77    let dest = resolve_memory_path(memories_dir, short_name)?;
78    if dest.exists() {
79        bail!("a memory with the name '{short_name}' already exists; delete it first");
80    }
81    let path_str = dest.to_string_lossy().to_string();
82    std::fs::rename(&source, &dest)?;
83    if let Some(rec) = recorder {
84        rec.record(&MemoryAuditEvent::Restored {
85            tier: MemoryTier::Project,
86            path: path_str,
87        });
88    }
89    Ok(())
90}