use std::path::PathBuf;
use thiserror::Error;
#[derive(Error, Debug)]
pub enum AgitError {
#[error("Repository not initialized. Run 'agit init' first.")]
NotInitialized,
#[error("Repository already initialized at {}", path.display())]
AlreadyInitialized {
path: PathBuf,
},
#[error("Not a git repository. Run 'git init' first.")]
NotGitRepository,
#[error("Lock acquisition failed: {reason}")]
LockFailed {
reason: String,
},
#[error("Git operation failed: {0}")]
Git(#[from] git2::Error),
#[error("Storage error: {0}")]
Storage(#[from] StorageError),
#[error("Index error: {0}")]
Index(#[from] IndexError),
#[error("Configuration error: {0}")]
Config(String),
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
#[error("JSON error: {0}")]
Json(#[from] serde_json::Error),
#[error("Search error: {0}")]
Search(#[from] tantivy::TantivyError),
#[error("Query parse error: {0}")]
QueryParse(#[from] tantivy::query::QueryParserError),
#[error("Invalid argument: {0}")]
InvalidArgument(String),
#[error("Operation cancelled")]
Cancelled,
#[error(
"Nothing to commit. Record thoughts with 'agit record' or stage code with 'agit add'."
)]
NothingToCommit,
#[error("Context conflict: external changes touched files in your pending thoughts.\nConflicting files: {}\nUse --force to commit anyway, or clear pending thoughts with 'agit reset'.", files.join(", "))]
SemanticConflict {
files: Vec<String>,
},
#[error("⚠️ {operation} in progress. Please resolve conflicts and finish the {operation} before adding neural memories.")]
ConflictedState {
operation: String,
},
#[error("⛔ SECURITY VIOLATION: Path '{path}' is outside repository scope ({repo_root}). Agit is a single-repo tool. Switch to the correct directory to log context for external files.")]
PathOutsideRepository {
path: String,
repo_root: String,
},
#[error("File '{path}' does not exist in repository ({repo_root}). Only existing files can be referenced in location metadata.")]
FileNotFound {
path: String,
repo_root: String,
},
}
#[derive(Error, Debug)]
pub enum StorageError {
#[error("Object not found: {hash}")]
NotFound {
hash: String,
},
#[error("Corrupt object {hash}: {reason}")]
Corrupt {
hash: String,
reason: String,
},
#[error("Write failed: {0}")]
WriteFailed(String),
#[error("Read failed: {0}")]
ReadFailed(String),
#[error("Invalid hash format: {0}")]
InvalidHash(String),
}
#[derive(Error, Debug)]
pub enum IndexError {
#[error("Index is empty")]
Empty,
#[error("Malformed entry at line {line}: {reason}")]
MalformedEntry {
line: usize,
reason: String,
},
#[error("Failed to append entry: {0}")]
AppendFailed(String),
#[error("Failed to clear index: {0}")]
ClearFailed(String),
}
pub type Result<T> = std::result::Result<T, AgitError>;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_error_display() {
let err = AgitError::NotInitialized;
assert_eq!(
err.to_string(),
"Repository not initialized. Run 'agit init' first."
);
let err = AgitError::AlreadyInitialized {
path: PathBuf::from("/test/.agit"),
};
assert!(err.to_string().contains("/test/.agit"));
}
#[test]
fn test_storage_error_display() {
let err = StorageError::NotFound {
hash: "abc123".to_string(),
};
assert_eq!(err.to_string(), "Object not found: abc123");
}
#[test]
fn test_index_error_display() {
let err = IndexError::MalformedEntry {
line: 42,
reason: "invalid JSON".to_string(),
};
assert_eq!(err.to_string(), "Malformed entry at line 42: invalid JSON");
}
}