Enhance tool documentation and add new features
- Added module-level documentation for memory tools (`remember`, `recall`, `forget`) to clarify their purpose. - Improved documentation in `recall.rs` and `remember.rs` to describe the functionality and flow of memory entry operations. - Updated `mod.rs` to include descriptions for the tool trait and execution context. - Enhanced `plan.rs` with detailed comments on plan-mode signaling tools. - Documented text search tools in `search.rs` to explain their functionality. - Improved sequential-thinking tool documentation in `seqthink.rs`. - Added safety filter documentation in `shell_filter` for credential and git operations. - Enhanced utility tools documentation, including `cd`, `dir_cache_update`, and `todowrite`. - Improved rendering documentation in view modules (`chat`, `markdown`, `status`, `workflow`) to clarify rendering flows and purposes.
This commit is contained in:
@@ -1,6 +1,11 @@
|
||||
//! Long-term agent memory: markdown files with YAML-ish frontmatter storing
|
||||
//! lessons/references, plus slugified filenames and export/import helpers.
|
||||
|
||||
use std::path::{Path, PathBuf};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// A single memory entry (lesson, reference, etc.) with frontmatter
|
||||
/// metadata and free-form markdown content.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Memory {
|
||||
pub name: String,
|
||||
@@ -18,6 +23,17 @@ pub struct Memory {
|
||||
}
|
||||
|
||||
impl Memory {
|
||||
/// Convert an arbitrary string into a filesystem-safe slug.
|
||||
///
|
||||
/// Flow: lowercase → replace non-alphanumeric chars with `-` →
|
||||
/// collapse/trim repeated `-` by splitting on it and rejoining
|
||||
/// non-empty parts.
|
||||
///
|
||||
/// Why: rejects empty or overly long (>80 char) results so callers
|
||||
/// don't write memories with degenerate or unwieldy filenames.
|
||||
///
|
||||
/// Return: `Some(slug)` on success, `None` if the input slugifies to
|
||||
/// empty or exceeds 80 characters.
|
||||
pub fn slugify(s: &str) -> Option<String> {
|
||||
let slug: String = s
|
||||
.to_lowercase()
|
||||
@@ -35,11 +51,27 @@ impl Memory {
|
||||
Some(slug)
|
||||
}
|
||||
|
||||
/// Compute the on-disk path for a memory of the given name.
|
||||
///
|
||||
/// Why: falls back to a fixed `"memory"` slug when `name` slugifies
|
||||
/// to nothing, so a path is always produced.
|
||||
pub fn path(memory_dir: &Path, name: &str) -> PathBuf {
|
||||
let slug = Self::slugify(name).unwrap_or_else(|| "memory".to_string());
|
||||
slug_path(memory_dir, &format!("{}.md", slug))
|
||||
}
|
||||
|
||||
/// Serialize this memory to markdown-with-frontmatter and write it
|
||||
/// atomically to disk.
|
||||
///
|
||||
/// Flow: build the frontmatter block (name/description/kind/timestamps/
|
||||
/// lifecycle/optional fields) → concatenate with body content → write
|
||||
/// to a temp file → rename into place.
|
||||
///
|
||||
/// Why: write-then-rename avoids leaving a half-written memory file if
|
||||
/// the process is interrupted mid-write.
|
||||
///
|
||||
/// Return: `Ok(())` on success, or an `io::Error` from directory
|
||||
/// creation, the temp write, or the rename.
|
||||
pub fn write(&self, memory_dir: &Path) -> std::io::Result<()> {
|
||||
let path = Self::path(memory_dir, &self.name);
|
||||
let parent = path.parent().unwrap();
|
||||
@@ -65,12 +97,29 @@ impl Memory {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Read and parse a memory file by name.
|
||||
///
|
||||
/// Return: the parsed `Memory`, or an `io::Error` if the file is
|
||||
/// missing or its frontmatter is malformed (see `parse`).
|
||||
pub fn read(memory_dir: &Path, name: &str) -> std::io::Result<Self> {
|
||||
let path = Self::path(memory_dir, name);
|
||||
let content = std::fs::read_to_string(&path)?;
|
||||
Self::parse(&content)
|
||||
}
|
||||
|
||||
/// Parse a memory file's contents (frontmatter + body) into a `Memory`.
|
||||
///
|
||||
/// Flow: strip leading `---\n` → split on the first `\n---\n` into
|
||||
/// frontmatter and body → parse frontmatter lines as `key: value`
|
||||
/// pairs into a map → build `Memory` fields from the map with
|
||||
/// sensible defaults for missing keys.
|
||||
///
|
||||
/// Why: unknown/missing frontmatter keys degrade to defaults (e.g.
|
||||
/// `kind` → "reference", `lifecycle` → "new") rather than failing,
|
||||
/// so older or hand-edited memory files still parse.
|
||||
///
|
||||
/// Return: `Err(InvalidData)` only if the `---` frontmatter delimiter
|
||||
/// itself is missing; otherwise `Ok(Memory)`.
|
||||
pub fn parse(content: &str) -> std::io::Result<Self> {
|
||||
let content = content.strip_prefix("---\n").unwrap_or(content);
|
||||
let parts: Vec<&str> = content.splitn(2, "\n---\n").collect();
|
||||
@@ -103,6 +152,9 @@ impl Memory {
|
||||
})
|
||||
}
|
||||
|
||||
/// Delete a memory file by name, if it exists.
|
||||
///
|
||||
/// Return: `Ok(())` whether or not the file existed.
|
||||
pub fn remove(memory_dir: &Path, name: &str) -> std::io::Result<()> {
|
||||
let path = Self::path(memory_dir, name);
|
||||
if path.exists() {
|
||||
@@ -111,6 +163,13 @@ impl Memory {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// List the slugs of all memory files in a directory.
|
||||
///
|
||||
/// Flow: read the directory → keep entries ending in `.md` → exclude
|
||||
/// the special `MEMORY.md` summary file → strip the `.md` suffix.
|
||||
///
|
||||
/// Return: slugs (without extension); empty `Vec` if the directory
|
||||
/// can't be read.
|
||||
pub fn list(memory_dir: &Path) -> Vec<String> {
|
||||
let entries = match std::fs::read_dir(memory_dir) {
|
||||
Ok(e) => e,
|
||||
@@ -129,6 +188,14 @@ impl Memory {
|
||||
}
|
||||
}
|
||||
|
||||
/// Sanitize a raw filename into a safe path under `memory_dir`.
|
||||
///
|
||||
/// Flow: replace any char that isn't alphanumeric, `.`, or `-` with `-` →
|
||||
/// strip leading dots (prevents dotfiles / path traversal via `..`) →
|
||||
/// join to `memory_dir`, falling back to `"memory.md"` if empty.
|
||||
///
|
||||
/// Why: leading-dot stripping specifically blocks accidental hidden
|
||||
/// files and `..`-style traversal attempts embedded in `raw`.
|
||||
pub fn slug_path(memory_dir: &Path, raw: &str) -> PathBuf {
|
||||
let clean: String = raw.chars()
|
||||
.map(|c| if c.is_ascii_alphanumeric() || c == '.' || c == '-' { c } else { '-' })
|
||||
@@ -137,6 +204,14 @@ pub fn slug_path(memory_dir: &Path, raw: &str) -> PathBuf {
|
||||
memory_dir.join(if clean.is_empty() { "memory.md" } else { &clean })
|
||||
}
|
||||
|
||||
/// Export all memories in `memory_dir` to a single JSON file.
|
||||
///
|
||||
/// Flow: list memory slugs → read+parse each into a `Memory` (skipping
|
||||
/// any that fail) → serialize the collected `Vec<Memory>` to pretty JSON
|
||||
/// → write to `output`.
|
||||
///
|
||||
/// Return: `Ok(())` on success, or an `io::Error` from serialization or
|
||||
/// the write.
|
||||
pub fn export_lessons(memory_dir: &Path, output: &Path) -> std::io::Result<()> {
|
||||
let names = Memory::list(memory_dir);
|
||||
let lessons: Vec<Memory> = names.iter()
|
||||
@@ -147,6 +222,17 @@ pub fn export_lessons(memory_dir: &Path, output: &Path) -> std::io::Result<()> {
|
||||
std::fs::write(output, data)?;
|
||||
Ok(())
|
||||
}
|
||||
/// Import memories from a JSON export file into `memory_dir`, skipping
|
||||
/// duplicates.
|
||||
///
|
||||
/// Flow: read+JSON-decode `input` into `Vec<Memory>` → build a set of
|
||||
/// existing slugs in `memory_dir` → for each lesson not already present
|
||||
/// (by slug), write it to disk and count it.
|
||||
///
|
||||
/// Why: slug-based dedup makes repeated imports idempotent — re-running
|
||||
/// import on the same file won't overwrite or duplicate existing memories.
|
||||
///
|
||||
/// Return: the number of memories actually imported (skips existing ones).
|
||||
pub fn import_lessons(memory_dir: &Path, input: &Path) -> std::io::Result<usize> {
|
||||
let data = std::fs::read_to_string(input)?;
|
||||
let lessons: Vec<Memory> = serde_json::from_str(&data)
|
||||
|
||||
Reference in New Issue
Block a user