use super::*;
fn index_ctx(dir: &std::path::Path) -> SessionContext {
SessionContext {
workspace: dir.to_string_lossy().into(),
session_id: "s".into(),
}
}
async fn seed_notes(path: &std::path::Path, n: usize) {
let mut ns = NoteStore::new(path, NoteStore::DEFAULT_CHAR_LIMIT);
ns.initialize(&index_ctx(path.parent().unwrap()))
.await
.unwrap();
for i in 0..n {
ns.add(&format!("note number {i}\nbody line for {i}"))
.unwrap();
}
}
#[tokio::test]
async fn memory_index_stays_under_pinned_budget() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("NOTES.md");
seed_notes(&path, MEMORY_INDEX_BUDGET + 25).await;
let mut idx = MemoryIndex::new(&path);
idx.initialize(&index_ctx(dir.path())).await.unwrap();
assert!(
idx.rows().len() <= MEMORY_INDEX_BUDGET,
"index surface ({}) exceeds the pinned budget ({MEMORY_INDEX_BUDGET})",
idx.rows().len()
);
assert_eq!(idx.rows().len(), MEMORY_INDEX_BUDGET, "fills to the cap");
let block = idx.system_prompt_block().unwrap();
assert!(block.contains("use `recall`"), "overflow hint: {block}");
}
#[tokio::test]
async fn memory_index_lists_ids_and_titles_not_bodies() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("NOTES.md");
seed_notes(&path, 2).await;
let mut idx = MemoryIndex::new(&path);
idx.initialize(&index_ctx(dir.path())).await.unwrap();
let block = idx.system_prompt_block().unwrap();
assert!(block.contains("note:1 note number 0"), "got: {block}");
assert!(block.contains("note:2 note number 1"), "got: {block}");
assert!(!block.contains("body line for 0"), "body leaked: {block}");
assert!(
block.contains("call `memory_fetch`"),
"names the fetch tool: {block}"
);
}
#[tokio::test]
async fn memory_index_is_system_prompt_only() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("NOTES.md");
seed_notes(&path, 1).await;
let mut idx = MemoryIndex::new(&path);
idx.initialize(&index_ctx(dir.path())).await.unwrap();
assert!(idx.build_messages("sys", "task").is_empty());
}
#[tokio::test]
async fn memory_index_empty_notes_contributes_no_block() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("NOTES.md");
let mut idx = MemoryIndex::new(&path); idx.initialize(&index_ctx(dir.path())).await.unwrap();
assert!(idx.system_prompt_block().is_none());
}
#[tokio::test]
async fn disclosure_frozen_default_is_bit_for_bit_unchanged() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("NOTES.md");
seed_notes(&path, 3).await;
async fn build(
path: &std::path::Path,
ws: &std::path::Path,
with_index: bool,
) -> (String, Vec<MemMessage>) {
let mut mgr = MemoryManager::new();
mgr.add_provider(RollingWindow::new(20));
mgr.add_provider(NoteStore::new(path, NoteStore::DEFAULT_CHAR_LIMIT));
if with_index {
mgr.add_provider(MemoryIndex::new(path));
}
mgr.initialize_all(&SessionContext {
workspace: ws.to_string_lossy().into(),
session_id: "s".into(),
})
.await;
(
mgr.build_system_prompt_additions(),
mgr.build_messages("sys", "task"),
)
}
let (frozen_sys, frozen_msgs) = build(&path, dir.path(), false).await;
let (frozen_sys2, frozen_msgs2) = build(&path, dir.path(), false).await;
assert_eq!(frozen_sys, frozen_sys2);
assert_eq!(frozen_msgs, frozen_msgs2);
assert!(
!frozen_sys.contains("Memory index"),
"frozen mode must NOT add the block: {frozen_sys}"
);
let (index_sys, _index_msgs) = build(&path, dir.path(), true).await;
assert!(
index_sys.contains("Memory index"),
"index mode adds the block: {index_sys}"
);
}