Refactor view modules for improved readability and consistency

- Updated markdown rendering logic to use more concise methods for obtaining vector lengths.
- Changed review status display to use the correct flag from settings.
- Cleaned up sidebar rendering code for better formatting and readability.
- Enhanced status bar rendering with improved string formatting and consistent style application.
- Refined workflow panel rendering, ensuring consistent style usage and improved readability.
- Added architecture overview and detailed documentation for backend, data, dependencies, and frontend structures.
This commit is contained in:
asepharyana
2026-07-16 07:56:11 +07:00
parent 7d99cd6618
commit a00aa9bec8
141 changed files with 3420 additions and 2172 deletions
+87 -26
View File
@@ -1,8 +1,7 @@
//! 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};
use std::path::{Path, PathBuf};
/// A single memory entry (lesson, reference, etc.) with frontmatter
/// metadata and free-form markdown content.
@@ -72,15 +71,30 @@ impl Memory {
///
/// Return: `Ok(())` on success, or an `io::Error` from directory
/// creation, the temp write, or the rename.
#[allow(clippy::suspicious_open_options)]
pub fn write(&self, memory_dir: &Path) -> std::io::Result<()> {
let path = Self::path(memory_dir, &self.name);
let parent = path.parent().unwrap();
std::fs::create_dir_all(parent)?;
let outcome_line = self.outcome.as_ref().map(|o| format!("outcome: {o}")).unwrap_or_default();
let scope_line = self.scope.as_ref().map(|s| format!("scope: {s}")).unwrap_or_default();
let before_line = self.before_snippet.as_ref().map(|s| format!("before: {s}")).unwrap_or_default();
let after_line = self.after_snippet.as_ref().map(|s| format!("after: {s}")).unwrap_or_default();
let outcome_line = self
.outcome
.as_ref()
.map(|o| format!("outcome: {o}"))
.unwrap_or_default();
let scope_line = self
.scope
.as_ref()
.map(|s| format!("scope: {s}"))
.unwrap_or_default();
let before_line = self
.before_snippet
.as_ref()
.map(|s| format!("before: {s}"))
.unwrap_or_default();
let after_line = self
.after_snippet
.as_ref()
.map(|s| format!("after: {s}"))
.unwrap_or_default();
let prov_line = if self.provenances.is_empty() {
String::new()
} else {
@@ -99,6 +113,7 @@ impl Memory {
use std::io::Write;
let mut f = std::fs::OpenOptions::new()
.create(true)
.truncate(true)
.write(true)
.open(&tmp)?;
f.write_all(content.as_bytes())?;
@@ -139,7 +154,10 @@ impl Memory {
let content = content.strip_prefix("---\n").unwrap_or(content);
let parts: Vec<&str> = content.splitn(2, "\n---\n").collect();
if parts.len() < 2 {
return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "missing frontmatter"));
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
"missing frontmatter",
));
}
let front: std::collections::HashMap<String, String> = parts[0]
.lines()
@@ -153,16 +171,34 @@ impl Memory {
name: front.get("name").cloned().unwrap_or_default(),
description: front.get("description").cloned().unwrap_or_default(),
content: body,
kind: front.get("kind").cloned().unwrap_or_else(|| "reference".to_string()),
created_at: front.get("created_at").and_then(|v| v.parse().ok()).unwrap_or(0),
updated_at: front.get("updated_at").and_then(|v| v.parse().ok()).unwrap_or(0),
kind: front
.get("kind")
.cloned()
.unwrap_or_else(|| "reference".to_string()),
created_at: front
.get("created_at")
.and_then(|v| v.parse().ok())
.unwrap_or(0),
updated_at: front
.get("updated_at")
.and_then(|v| v.parse().ok())
.unwrap_or(0),
outcome: front.get("outcome").cloned().filter(|s| !s.is_empty()),
lifecycle: front.get("lifecycle").cloned().unwrap_or_else(|| "new".to_string()),
lifecycle: front
.get("lifecycle")
.cloned()
.unwrap_or_else(|| "new".to_string()),
scope: front.get("scope").cloned().filter(|s| !s.is_empty()),
before_snippet: front.get("before").cloned().filter(|s| !s.is_empty()),
after_snippet: front.get("after").cloned().filter(|s| !s.is_empty()),
provenances: front.get("provenances").cloned()
.map(|s| s.split(", ").map(std::string::ToString::to_string).collect())
provenances: front
.get("provenances")
.cloned()
.map(|s| {
s.split(", ")
.map(std::string::ToString::to_string)
.collect()
})
.unwrap_or_default(),
})
}
@@ -186,13 +222,17 @@ impl Memory {
/// Return: slugs (without extension); empty `Vec` if the directory
/// can't be read.
pub fn list(memory_dir: &Path) -> Vec<String> {
let Ok(entries) = std::fs::read_dir(memory_dir) else { return Vec::new() };
let Ok(entries) = std::fs::read_dir(memory_dir) else {
return Vec::new();
};
entries
.filter_map(std::result::Result::ok)
.filter(|e| e.path().extension().is_some_and(|x| x == "md"))
.filter_map(|e| {
let name = e.file_name().to_string_lossy().to_string();
if name == "MEMORY.md" { return None; }
if name == "MEMORY.md" {
return None;
}
let slug = name.strip_suffix(".md")?.to_string();
Some(slug)
})
@@ -209,11 +249,22 @@ impl Memory {
/// 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 { '-' })
let clean: String = raw
.chars()
.map(|c| {
if c.is_ascii_alphanumeric() || c == '.' || c == '-' {
c
} else {
'-'
}
})
.collect();
let clean = clean.trim_start_matches('.').to_string();
memory_dir.join(if clean.is_empty() { "memory.md" } else { &clean })
memory_dir.join(if clean.is_empty() {
"memory.md"
} else {
&clean
})
}
/// Export all memories in `memory_dir` to a single JSON file.
@@ -227,11 +278,11 @@ pub fn slug_path(memory_dir: &Path, raw: &str) -> PathBuf {
#[cfg(test)]
pub fn export_lessons(memory_dir: &Path, output: &Path) -> std::io::Result<()> {
let names = Memory::list(memory_dir);
let lessons: Vec<Memory> = names.iter()
let lessons: Vec<Memory> = names
.iter()
.filter_map(|n| Memory::read(memory_dir, n).ok())
.collect();
let data = serde_json::to_string_pretty(&lessons)
.map_err(std::io::Error::other)?;
let data = serde_json::to_string_pretty(&lessons).map_err(std::io::Error::other)?;
// Write to temp, fsync, then rename for crash-safe export
let tmp = output.with_extension("json.tmp");
std::fs::write(&tmp, data)?;
@@ -259,7 +310,8 @@ 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)
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
let existing: std::collections::HashSet<String> = Memory::list(memory_dir).into_iter().collect();
let existing: std::collections::HashSet<String> =
Memory::list(memory_dir).into_iter().collect();
let mut imported = 0;
for lesson in &lessons {
let slug = Memory::slugify(&lesson.name).unwrap_or_default();
@@ -282,12 +334,18 @@ mod tests {
#[test]
fn test_slugify_basic() {
assert_eq!(Memory::slugify("Hello World"), Some("hello-world".to_string()));
assert_eq!(
Memory::slugify("Hello World"),
Some("hello-world".to_string())
);
}
#[test]
fn test_slugify_special_chars() {
assert_eq!(Memory::slugify("Use & Avoid! @#$"), Some("use-avoid".to_string()));
assert_eq!(
Memory::slugify("Use & Avoid! @#$"),
Some("use-avoid".to_string())
);
}
#[test]
@@ -375,7 +433,10 @@ mod tests {
};
mem.write(&dir).unwrap();
let names = Memory::list(&dir);
assert!(names.contains(&"alpha".to_string()), "list should contain 'alpha', got: {names:?}");
assert!(
names.contains(&"alpha".to_string()),
"list should contain 'alpha', got: {names:?}"
);
let _ = std::fs::remove_dir_all(&dir);
}