1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
use thiserror::Error;
/// Top-level service-layer error type.
#[derive(Debug, Error)]
pub(crate) enum ServiceError {
#[error(transparent)]
ProjectFiles(#[from] ProjectFilesError),
#[error(transparent)]
Transcript(#[from] TranscriptError),
#[error(transparent)]
PkgList(#[from] PkgListError),
#[error(transparent)]
HubRegistries(#[from] HubRegistriesError),
#[error(transparent)]
CardPublish(#[from] CardPublishError),
/// User-supplied parameter was invalid (sort key, field name, etc.).
/// This is a client error (bad input), not a server-side failure.
#[error("{0}")]
InvalidInput(String),
}
impl From<ServiceError> for String {
fn from(e: ServiceError) -> Self {
e.to_string()
}
}
#[derive(Debug, Error)]
pub enum ProjectFilesError {
#[error("packages dir {path}: {source}")]
PackagesDir {
path: String,
source: std::io::Error,
},
/// Advisory lock acquisition failed.
#[error("project files lock: {0}")]
Lock(#[from] crate::service::lock::LockError),
#[error("alc.toml load: {0}")]
AlcTomlLoad(String),
#[error("alc.toml save: {0}")]
AlcTomlSave(String),
#[error("alc.lock load: {0}")]
AlcLockLoad(String),
#[error("alc.lock save: {0}")]
AlcLockSave(String),
}
/// Errors arising from `pkg_list` filesystem reads.
///
/// Corruption (parse error / version mismatch) is distinguished from file-absent
/// (`load_lockfile` / `load_alc_toml` return `Ok(None)` for absent files) so
/// callers can surface the former as a `warnings` field in the MCP wire response.
#[allow(clippy::enum_variant_names)]
#[derive(Debug, Error)]
pub(crate) enum PkgListError {
#[error("alc.lock parse: {0}")]
LockfileParse(String),
#[error("alc.toml parse: {0}")]
AlcTomlParse(String),
#[error("alc.local.toml parse: {0}")]
AlcLocalTomlParse(String),
}
/// Errors arising from `hub_registries.json` reads.
///
/// File-absent is `Ok(HubRegistries::default())` — the file is created lazily.
/// Parse failures (corrupt JSON) are `Err` so callers can surface them in the
/// MCP wire `warnings` field instead of silently degrading hub discovery.
#[derive(Debug, Error)]
pub(crate) enum HubRegistriesError {
#[error("hub_registries.json parse: {0}")]
Parse(String),
}
#[derive(Debug, Error)]
pub enum TranscriptError {
#[error("transcript log dir {path}: {source}")]
LogDir {
path: String,
source: std::io::Error,
},
#[error("transcript path: {0}")]
Path(String),
#[error("transcript serialize: {0}")]
Serialize(String),
#[error("transcript write {path}: {source}")]
Write {
path: String,
source: std::io::Error,
},
}
/// Errors arising from `card_publish` operations.
///
/// Push failures due to missing credentials are distinguished from other
/// errors so callers can surface actionable setup guidance. A successful
/// push is never rolled back when only reindex fails — that outcome is
/// represented as a separate field in `CardPublishOutcome`.
#[derive(Debug, Error)]
pub enum CardPublishError {
/// Git push failed due to missing or invalid credentials.
///
/// `guidance` contains actionable setup steps derived from
/// `gh_credentials::diagnose()` + `build_guidance()`.
#[error("missing credentials: {guidance}")]
MissingCredentials { guidance: String },
/// The requested card was not found in the card store.
#[error("card '{0}' not found")]
CardNotFound(String),
/// A git subprocess (add/commit/push/rev-parse) failed for a reason
/// other than credential authentication.
#[error("git {cmd} failed: {stderr}")]
GitCommand { cmd: String, stderr: String },
/// Subprocess spawn failure or temporary directory creation error.
#[error("io: {0}")]
Io(#[from] std::io::Error),
/// `target_repo` is not a valid URL (e.g. a bare pkg slug was supplied).
#[error("invalid target_repo: {0}")]
InvalidTarget(String),
}