feat: auto-append memory to memory index and don't necessarily require the LLM to remember to do it after a write
This commit is contained in:
+77
-4
@@ -1,6 +1,6 @@
|
|||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::env;
|
use std::path::{Path, PathBuf};
|
||||||
use std::path::PathBuf;
|
use std::{env, fs};
|
||||||
|
|
||||||
use anyhow::{Context, Result, anyhow, bail};
|
use anyhow::{Context, Result, anyhow, bail};
|
||||||
use indexmap::IndexMap;
|
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")),
|
path: target_dir.join(format!("{name}.md")),
|
||||||
frontmatter: MemoryFrontmatter {
|
frontmatter: MemoryFrontmatter {
|
||||||
name: name.clone(),
|
name: name.clone(),
|
||||||
description: Some(description),
|
description: Some(description.clone()),
|
||||||
kind,
|
kind,
|
||||||
},
|
},
|
||||||
body: content,
|
body: content,
|
||||||
};
|
};
|
||||||
file.save()?;
|
file.save()?;
|
||||||
|
|
||||||
|
let index_path = target_dir.join("MEMORY.md");
|
||||||
|
let index_updated = ensure_index_entry(&index_path, &name, &description)?;
|
||||||
|
|
||||||
Ok(json!({
|
Ok(json!({
|
||||||
"status": "ok",
|
"status": "ok",
|
||||||
"path": file.path.display().to_string(),
|
"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),
|
"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<bool> {
|
||||||
|
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<String> {
|
fn arg_str(args: &Value, key: &str) -> Result<String> {
|
||||||
args.get(key)
|
args.get(key)
|
||||||
.and_then(Value::as_str)
|
.and_then(Value::as_str)
|
||||||
@@ -339,6 +369,49 @@ mod tests {
|
|||||||
assert!(extract_wikilinks("nothing here").is_empty());
|
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]
|
#[test]
|
||||||
fn workspace_write_dir_returns_structured_dir_directly() {
|
fn workspace_write_dir_returns_structured_dir_directly() {
|
||||||
let root = temp_root("ws_structured");
|
let root = temp_root("ws_structured");
|
||||||
|
|||||||
Reference in New Issue
Block a user