From 73c4449e7f79ebd5db6f593419614aecb55aceb2 Mon Sep 17 00:00:00 2001 From: Alex Clarke Date: Wed, 10 Jun 2026 21:31:37 -0600 Subject: [PATCH] feat: auto-append memory to memory index and don't necessarily require the LLM to remember to do it after a write --- src/function/memory.rs | 81 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 77 insertions(+), 4 deletions(-) diff --git a/src/function/memory.rs b/src/function/memory.rs index 2d16982..60c0466 100644 --- a/src/function/memory.rs +++ b/src/function/memory.rs @@ -1,6 +1,6 @@ use std::collections::HashSet; -use std::env; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; +use std::{env, fs}; use anyhow::{Context, Result, anyhow, bail}; use indexmap::IndexMap; @@ -192,17 +192,21 @@ pub fn handle_memory_tool(ctx: &mut RequestContext, cmd_name: &str, args: &Value path: target_dir.join(format!("{name}.md")), frontmatter: MemoryFrontmatter { name: name.clone(), - description: Some(description), + description: Some(description.clone()), kind, }, body: content, }; file.save()?; + let index_path = target_dir.join("MEMORY.md"); + let index_updated = ensure_index_entry(&index_path, &name, &description)?; + Ok(json!({ "status": "ok", "path": file.path.display().to_string(), - "reminder": "Update MEMORY.md to keep the index accurate.", + "index_path": index_path.display().to_string(), + "index_updated": index_updated, })) } "lint" => lint_memory(&store), @@ -210,6 +214,32 @@ pub fn handle_memory_tool(ctx: &mut RequestContext, cmd_name: &str, args: &Value } } +fn ensure_index_entry(index_path: &Path, name: &str, description: &str) -> Result { + let existing = fs::read_to_string(index_path).unwrap_or_default(); + let already_referenced = + existing.contains(&format!("[[{name}]]")) || existing.contains(&format!("{name}.md")); + + if already_referenced { + return Ok(false); + } + + let entry = format!("- [[{name}]]: {description}\n"); + let new_content = if existing.is_empty() { + format!("# Memory Index\n\n{entry}") + } else if existing.ends_with('\n') { + format!("{existing}{entry}") + } else { + format!("{existing}\n{entry}") + }; + + if let Some(parent) = index_path.parent() { + fs::create_dir_all(parent)?; + } + + fs::write(index_path, new_content)?; + Ok(true) +} + fn arg_str(args: &Value, key: &str) -> Result { args.get(key) .and_then(Value::as_str) @@ -339,6 +369,49 @@ mod tests { assert!(extract_wikilinks("nothing here").is_empty()); } + #[test] + fn ensure_index_entry_appends_when_missing() { + let root = temp_root("index_append"); + let index = root.join("MEMORY.md"); + fs::write(&index, "# Memory Index\n\n- [[existing]] — already here\n").unwrap(); + + let updated = ensure_index_entry(&index, "new_one", "newly added").unwrap(); + assert!(updated); + let content = fs::read_to_string(&index).unwrap(); + assert!(content.contains("- [[existing]] — already here")); + assert!(content.contains("- [[new_one]] — newly added")); + + let _ = fs::remove_dir_all(&root); + } + + #[test] + fn ensure_index_entry_skips_when_referenced() { + let root = temp_root("index_skip"); + let index = root.join("MEMORY.md"); + let original = "# Memory Index\n\n- [[existing]] — already here\n"; + fs::write(&index, original).unwrap(); + + let updated = ensure_index_entry(&index, "existing", "different description").unwrap(); + assert!(!updated); + assert_eq!(fs::read_to_string(&index).unwrap(), original); + + let _ = fs::remove_dir_all(&root); + } + + #[test] + fn ensure_index_entry_creates_index_when_absent() { + let root = temp_root("index_create"); + let index = root.join("memory").join("MEMORY.md"); + + let updated = ensure_index_entry(&index, "first", "first ever").unwrap(); + assert!(updated); + let content = fs::read_to_string(&index).unwrap(); + assert!(content.starts_with("# Memory Index")); + assert!(content.contains("- [[first]] — first ever")); + + let _ = fs::remove_dir_all(&root); + } + #[test] fn workspace_write_dir_returns_structured_dir_directly() { let root = temp_root("ws_structured");