//! Errors raised by the memory subsystem. use std::path::PathBuf; use lint_common::RecordLintError; use thiserror::Error; /// Top-level error for memory operations that don't fit the lint flow. #[derive(Debug, Error)] pub enum MemoryError { #[error("path is not under the memory or knowledge tree: {}", .0.display())] OutsideMemoryTree(PathBuf), #[error("path is not absolute: {}", .0.display())] RelativePath(PathBuf), #[error("io error at {}: {source}", .path.display())] Io { path: PathBuf, #[source] source: std::io::Error, }, } impl MemoryError { pub fn io(path: impl Into, source: std::io::Error) -> Self { Self::Io { path: path.into(), source, } } } /// A single Linter violation. Multiple are aggregated in a [`LintReport`]. /// /// `Display` produces a one-line message used directly in the `ToolError` /// payload returned to the LLM. #[derive(Debug, Clone, Error, PartialEq, Eq)] pub enum LintError { #[error("path is not a valid memory record location: {}", .0.display())] InvalidPath(PathBuf), #[error("path is for a different record kind than expected at this location: {}", .0.display())] WrongRecordKind(PathBuf), #[error(transparent)] Record(#[from] RecordLintError), #[error("missing required frontmatter field: `{0}`")] MissingField(&'static str), #[error("invalid value for `{field}`: {message}")] InvalidField { field: &'static str, message: String, }, #[error("Decisions `status` must be one of open|resolved|replaced (got `{0}`)")] InvalidStatus(String), #[error( "Knowledge with model_invokation: true cannot have description longer than {limit} chars (got {actual})" )] DescriptionTooLong { actual: usize, limit: usize }, #[error("body exceeds the size limit for this record kind: {actual} chars > {limit}")] BodyTooLong { actual: usize, limit: usize }, #[error("slug `{0}` already exists; use the edit tool instead of creating a new record")] SlugAlreadyExists(String), #[error("`{field}` references unknown {kind} slug `{slug}`")] UnknownReference { field: &'static str, kind: &'static str, slug: String, }, #[error("`replaced_by` chain forms a cycle: {chain}")] ReplacedByCycle { chain: String }, #[error("`replaced_by` must point to a different slug than the record itself")] ReplacedBySelf, } /// A single Linter warning (non-blocking). /// /// Warnings ride along in the `ToolOutput.summary` so the agent can act /// on them when convenient; they never abort the write. #[derive(Debug, Clone, PartialEq, Eq)] pub enum LintWarning { /// Single-source record exceeds the importance/size threshold. LowImportanceLargeRecord { chars: usize }, /// `sources` array has grown past the soft cap. SourcesOverflow { count: usize }, /// Multiple slugs in the same kind are within Levenshtein distance 2. SimilarSlugs(Vec), } impl std::fmt::Display for LintWarning { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::LowImportanceLargeRecord { chars } => write!( f, "record is large ({chars} chars) but only has 1 source — consider splitting or trimming" ), Self::SourcesOverflow { count } => write!( f, "`sources` has {count} entries — consider keeping only the most recent and relying on git log for the rest" ), Self::SimilarSlugs(slugs) => { write!(f, "similar slugs detected (consider merging): ")?; for (i, s) in slugs.iter().enumerate() { if i > 0 { write!(f, ", ")?; } write!(f, "{s}")?; } Ok(()) } } } }