ci: add GitHub Actions workflows with semantic-release auto-versioning
chore: fix all 702 clippy warnings across codebase - auto-fix 475 via cargo clippy --fix - fix remaining 227 manually: uninlined_format_args, redundant_closure, match_same_arms, underscore_binding, format_push_string, items_after_statements, needless_pass_by_value, clone_on_copy, case_sensitive_extension, single_match/let-else, write_with_newline, and other clippy lints
This commit is contained in:
@@ -43,18 +43,12 @@ impl EditLog {
|
||||
/// `MAX_MEMORY_ENTRIES` entries. The full history is preserved on disk
|
||||
/// regardless of the in-memory limit.
|
||||
fn load_from_disk(path: &std::path::Path) -> Vec<EditLogEntry> {
|
||||
let file = match std::fs::File::open(path) {
|
||||
Ok(f) => f,
|
||||
Err(_) => return Vec::new(),
|
||||
};
|
||||
use std::io::{BufRead, BufReader};
|
||||
let Ok(file) = std::fs::File::open(path) else { return Vec::new() };
|
||||
let reader = BufReader::new(file);
|
||||
let mut entries: Vec<EditLogEntry> = Vec::new();
|
||||
for line in reader.lines() {
|
||||
let line = match line {
|
||||
Ok(l) => l,
|
||||
Err(_) => continue,
|
||||
};
|
||||
let Ok(line) = line else { continue };
|
||||
if let Ok(entry) = serde_json::from_str::<EditLogEntry>(&line) {
|
||||
// Keep only the most recent entries in memory
|
||||
if entries.len() >= MAX_MEMORY_ENTRIES {
|
||||
@@ -81,6 +75,7 @@ impl EditLog {
|
||||
/// Return: `Ok(())` on success; an `io::Error` if serialization or
|
||||
/// any filesystem operation fails.
|
||||
pub fn append(&mut self, entry: EditLogEntry) -> std::io::Result<()> {
|
||||
use std::io::Write;
|
||||
let line = serde_json::to_string(&entry)? + "\n";
|
||||
// Ensure parent directory exists; fall back to the current
|
||||
// directory if path has no parent (should not happen in practice
|
||||
@@ -92,7 +87,6 @@ impl EditLog {
|
||||
.create(true)
|
||||
.append(true)
|
||||
.open(&self.path)?;
|
||||
use std::io::Write;
|
||||
file.write_all(line.as_bytes())?;
|
||||
file.sync_all()?;
|
||||
self.entries.push(entry);
|
||||
|
||||
+11
-13
@@ -57,7 +57,7 @@ impl Memory {
|
||||
/// 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))
|
||||
slug_path(memory_dir, &format!("{slug}.md"))
|
||||
}
|
||||
|
||||
/// Serialize this memory to markdown-with-frontmatter and write it
|
||||
@@ -72,14 +72,15 @@ 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 {
|
||||
@@ -95,11 +96,11 @@ impl Memory {
|
||||
// Write to temp file with fsync for crash safety (prevents
|
||||
// partial writes surviving a power loss).
|
||||
{
|
||||
use std::io::Write;
|
||||
let mut f = std::fs::OpenOptions::new()
|
||||
.create(true)
|
||||
.write(true)
|
||||
.open(&tmp)?;
|
||||
use std::io::Write;
|
||||
f.write_all(content.as_bytes())?;
|
||||
f.sync_all()?;
|
||||
}
|
||||
@@ -161,7 +162,7 @@ impl Memory {
|
||||
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(|p| p.to_string()).collect())
|
||||
.map(|s| s.split(", ").map(std::string::ToString::to_string).collect())
|
||||
.unwrap_or_default(),
|
||||
})
|
||||
}
|
||||
@@ -185,13 +186,10 @@ impl Memory {
|
||||
/// 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,
|
||||
Err(_) => return Vec::new(),
|
||||
};
|
||||
let Ok(entries) = std::fs::read_dir(memory_dir) else { return Vec::new() };
|
||||
entries
|
||||
.filter_map(|e| e.ok())
|
||||
.filter(|e| e.path().extension().map(|x| x == "md").unwrap_or(false))
|
||||
.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; }
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
//! Persistence and domain model layer: sessions, conversations, memory,
|
||||
//! message log (SQLite), edit log, and app/settings config.
|
||||
//! message log (`SQLite`), edit log, and app/settings config.
|
||||
|
||||
pub mod app_config;
|
||||
pub mod editlog;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! Binary blob storage in the message-log SQLite database (e.g. images,
|
||||
//! Binary blob storage in the message-log `SQLite` database (e.g. images,
|
||||
//! attachments), keyed by session id and an arbitrary blob key.
|
||||
|
||||
use rusqlite::{Connection, params};
|
||||
@@ -9,7 +9,7 @@ use anyhow::Result;
|
||||
/// Flow: compute current timestamp → `INSERT OR REPLACE` into `blobs`
|
||||
/// keyed on `(session_id, blob_key)`.
|
||||
///
|
||||
/// Return: `Ok(())` on success, or the underlying SQLite error.
|
||||
/// Return: `Ok(())` on success, or the underlying `SQLite` error.
|
||||
pub fn store_blob(conn: &Connection, session_id: &str, blob_key: &str, data: &[u8], mime_type: Option<&str>) -> Result<()> {
|
||||
let created_at = chrono::Utc::now().timestamp_millis();
|
||||
conn.execute(
|
||||
@@ -22,7 +22,7 @@ pub fn store_blob(conn: &Connection, session_id: &str, blob_key: &str, data: &[u
|
||||
/// Fetch a blob's bytes for a session by key.
|
||||
///
|
||||
/// Return: `Ok(Some(data))` if found, `Ok(None)` if no matching row
|
||||
/// exists, `Err` for any other SQLite failure.
|
||||
/// exists, `Err` for any other `SQLite` failure.
|
||||
pub fn retrieve_blob(conn: &Connection, session_id: &str, blob_key: &str) -> Result<Option<Vec<u8>>> {
|
||||
let result = conn.query_row(
|
||||
"SELECT data FROM blobs WHERE session_id = ?1 AND blob_key = ?2",
|
||||
@@ -52,7 +52,7 @@ pub fn delete_blob(conn: &Connection, session_id: &str, blob_key: &str) -> Resul
|
||||
/// List all blob keys stored for a session, oldest first.
|
||||
///
|
||||
/// Return: `Ok(Vec<String>)` of keys ordered by `created_at`, or the
|
||||
/// underlying SQLite error.
|
||||
/// underlying `SQLite` error.
|
||||
pub fn list_blob_keys(conn: &Connection, session_id: &str) -> Result<Vec<String>> {
|
||||
let mut stmt = conn.prepare(
|
||||
"SELECT blob_key FROM blobs WHERE session_id = ?1 ORDER BY created_at ASC"
|
||||
|
||||
@@ -12,7 +12,7 @@ pub use query::insert_message;
|
||||
/// schema is initialized.
|
||||
///
|
||||
/// Flow: resolve `<session_dir>/messages.sqlite` → create parent dirs →
|
||||
/// open a SQLite connection → run `schema::init_schema`.
|
||||
/// open a `SQLite` connection → run `schema::init_schema`.
|
||||
///
|
||||
/// Return: an open, schema-ready `Connection`, or an error if any step
|
||||
/// fails.
|
||||
|
||||
@@ -6,7 +6,7 @@ use crate::dto::chat::message::{ChatMessage, Role};
|
||||
|
||||
/// Insert a chat message into the session's message log.
|
||||
///
|
||||
/// Flow: extract optional content/tool_call_id/tool_name → serialize
|
||||
/// Flow: extract optional `content/tool_call_id/tool_name` → serialize
|
||||
/// `tool_calls` to a JSON string if present → map `Role` to its string
|
||||
/// column value → `INSERT` the row with the current timestamp.
|
||||
///
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! SQLite schema definition for the message log database.
|
||||
//! `SQLite` schema definition for the message log database.
|
||||
|
||||
use rusqlite::Connection;
|
||||
use anyhow::Result;
|
||||
@@ -9,7 +9,7 @@ use anyhow::Result;
|
||||
/// Why: idempotent via `CREATE TABLE/INDEX IF NOT EXISTS`, so it's safe
|
||||
/// to call on every `open_or_create`.
|
||||
///
|
||||
/// Return: `Ok(())` on success, or the underlying SQLite error.
|
||||
/// Return: `Ok(())` on success, or the underlying `SQLite` error.
|
||||
pub fn init_schema(conn: &Connection) -> Result<()> {
|
||||
conn.execute_batch("PRAGMA foreign_keys = ON;")?;
|
||||
conn.execute_batch(
|
||||
|
||||
@@ -89,7 +89,7 @@ impl Session {
|
||||
if id.contains('/') || id.contains('\\') || id.contains("..") {
|
||||
return Err(std::io::Error::new(
|
||||
std::io::ErrorKind::InvalidInput,
|
||||
format!("invalid session id '{}': must not contain path separators", id),
|
||||
format!("invalid session id '{id}': must not contain path separators"),
|
||||
));
|
||||
}
|
||||
let path = base_dir.join("sessions").join(id).join("session.json");
|
||||
@@ -108,12 +108,9 @@ impl Session {
|
||||
/// contains no valid sessions.
|
||||
pub fn list(base_dir: &Path) -> Vec<Self> {
|
||||
let sessions_dir = base_dir.join("sessions");
|
||||
let entries = match std::fs::read_dir(&sessions_dir) {
|
||||
Ok(e) => e,
|
||||
Err(_) => return Vec::new(),
|
||||
};
|
||||
let Ok(entries) = std::fs::read_dir(&sessions_dir) else { return Vec::new() };
|
||||
entries
|
||||
.filter_map(|e| e.ok())
|
||||
.filter_map(std::result::Result::ok)
|
||||
.filter(|e| e.path().is_dir())
|
||||
.filter_map(|e| {
|
||||
let id = e.file_name().to_string_lossy().to_string();
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#![allow(clippy::cast_possible_truncation, clippy::cast_sign_loss, clippy::cast_precision_loss, clippy::cast_possible_wrap)]
|
||||
//! PID-file based advisory lock preventing two processes from operating on
|
||||
//! the same session directory concurrently.
|
||||
|
||||
@@ -37,6 +38,7 @@ impl SessionLock {
|
||||
///
|
||||
/// Return: `Ok(true)` if acquired, `Ok(false)` if another live
|
||||
/// process holds it, `Err` on I/O failure.
|
||||
#[allow(clippy::suspicious_open_options)]
|
||||
pub fn try_lock(&self) -> std::io::Result<bool> {
|
||||
// Phase 1: try atomic create. If it succeeds, the lock is ours.
|
||||
match fs::OpenOptions::new()
|
||||
@@ -90,6 +92,7 @@ impl SessionLock {
|
||||
/// Check whether a process with the given PID is currently alive and
|
||||
/// is actually a zesdex process (not a recycled PID from a different
|
||||
/// program).
|
||||
#[allow(clippy::unused_self)]
|
||||
fn is_alive(&self, pid: u32) -> bool {
|
||||
// SAFETY: `libc::kill(pid, 0)` does not send a signal; it only checks
|
||||
// whether the process exists and the caller has permission to signal
|
||||
@@ -102,18 +105,12 @@ impl SessionLock {
|
||||
// from a different program would answer kill but shouldn't hold
|
||||
// our lock). This is best-effort — /proc may not be available
|
||||
// on all platforms.
|
||||
let proc_exe = std::path::PathBuf::from(format!("/proc/{}/exe", pid));
|
||||
match std::fs::read_link(&proc_exe) {
|
||||
Ok(target) => match std::env::current_exe() {
|
||||
Ok(exe) => {
|
||||
if target != exe {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Err(_) => { /* cannot resolve own exe, trust kill check */ }
|
||||
},
|
||||
Err(_) => { /* /proc unavailable, trust kill check */ }
|
||||
}
|
||||
let proc_exe = std::path::PathBuf::from(format!("/proc/{pid}/exe"));
|
||||
if let Ok(target) = std::fs::read_link(&proc_exe) { if let Ok(exe) = std::env::current_exe() {
|
||||
if target != exe {
|
||||
return false;
|
||||
}
|
||||
} else { /* cannot resolve own exe, trust kill check */ } } else { /* /proc unavailable, trust kill check */ }
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user