refactor(core): Rename GiError to LoreError and add search infrastructure

Mechanical rename of GiError -> LoreError across the core module to
match the project's rebranding from gitlab-inbox to gitlore/lore.
Updates the error enum name, all From impls, and the Result type alias.

Additionally introduces:

- New error variants for embedding pipeline: OllamaUnavailable,
  OllamaModelNotFound, EmbeddingFailed, EmbeddingsNotBuilt. Each
  includes actionable suggestions (e.g., "ollama serve", "ollama pull
  nomic-embed-text") to guide users through recovery.

- New error codes 14-16 for programmatic handling of Ollama failures.

- Savepoint-based migration execution in db.rs: each migration now
  runs inside a SQLite SAVEPOINT so a failed migration rolls back
  cleanly without corrupting the schema_version tracking. Previously
  a partial migration could leave the database in an inconsistent
  state.

- core::backoff module: exponential backoff with jitter utility for
  retry loops in the embedding pipeline and discussion queues.

- core::project module: helper for resolving project IDs and paths
  from the local database, used by the document regenerator and
  search filters.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Taylor Eernisse
2026-01-30 15:45:54 -05:00
parent 4270603da4
commit 6e22f120d0
8 changed files with 361 additions and 22 deletions

View File

@@ -6,7 +6,7 @@ use serde::Deserialize;
use std::fs;
use std::path::Path;
use super::error::{GiError, Result};
use super::error::{LoreError, Result};
use super::paths::get_config_path;
/// GitLab connection settings.
@@ -130,7 +130,7 @@ impl Config {
let config_path = get_config_path(cli_override);
if !config_path.exists() {
return Err(GiError::ConfigNotFound {
return Err(LoreError::ConfigNotFound {
path: config_path.display().to_string(),
});
}
@@ -140,25 +140,25 @@ impl Config {
/// Load configuration from a specific path.
pub fn load_from_path(path: &Path) -> Result<Self> {
let content = fs::read_to_string(path).map_err(|e| GiError::ConfigInvalid {
let content = fs::read_to_string(path).map_err(|e| LoreError::ConfigInvalid {
details: format!("Failed to read config file: {e}"),
})?;
let config: Config =
serde_json::from_str(&content).map_err(|e| GiError::ConfigInvalid {
serde_json::from_str(&content).map_err(|e| LoreError::ConfigInvalid {
details: format!("Invalid JSON: {e}"),
})?;
// Validate required fields
if config.projects.is_empty() {
return Err(GiError::ConfigInvalid {
return Err(LoreError::ConfigInvalid {
details: "At least one project is required".to_string(),
});
}
for project in &config.projects {
if project.path.is_empty() {
return Err(GiError::ConfigInvalid {
return Err(LoreError::ConfigInvalid {
details: "Project path cannot be empty".to_string(),
});
}
@@ -166,7 +166,7 @@ impl Config {
// Validate URL format
if url::Url::parse(&config.gitlab.base_url).is_err() {
return Err(GiError::ConfigInvalid {
return Err(LoreError::ConfigInvalid {
details: format!("Invalid GitLab URL: {}", config.gitlab.base_url),
});
}