1use std::path::PathBuf;
2use thiserror::Error;
3
4#[derive(Error, Debug)]
5pub enum Autom8Error {
6 #[error("Spec file not found: {0}\n\nThe spec file does not exist at the specified path.\n\nTo fix this:\n 1. Check that the path is correct\n 2. Run 'autom8 init' to create the project structure\n 3. Create a spec file or use 'autom8' to generate one interactively")]
7 SpecNotFound(PathBuf),
8
9 #[error("Invalid spec format: {0}\n\nThe spec file exists but cannot be parsed.\n\nTo fix this:\n 1. Ensure the file is valid JSON or Markdown\n 2. Check for syntax errors (missing commas, brackets, etc.)\n 3. See CLAUDE.md for spec format requirements")]
10 InvalidSpec(String),
11
12 #[error("No incomplete stories found in spec\n\nAll user stories in the spec have passes: true.\n\nTo continue:\n 1. Add new user stories to the spec, or\n 2. Set passes: false on stories you want to re-implement")]
13 NoIncompleteStories,
14
15 #[error("Claude process failed: {0}")]
16 ClaudeError(String),
17
18 #[error("Claude process timed out after {0} seconds")]
19 ClaudeTimeout(u64),
20
21 #[error("State file error: {0}")]
22 StateError(String),
23
24 #[error("No active run to resume\n\nNo incomplete session was found for this project.\n\nTo start a new run:\n 1. Run 'autom8 spec.json' to start from a spec file, or\n 2. Run 'autom8' to create a new spec interactively, or\n 3. Use 'autom8 status --all' to check all sessions")]
25 NoActiveRun,
26
27 #[error("Run already in progress: {0}\n\nAnother session is actively running for this project.\n\nTo resolve this:\n 1. Wait for the current run to complete, or\n 2. Use --worktree to run in a separate worktree, or\n 3. Use 'autom8 status --all' to see all sessions")]
28 RunInProgress(String),
29
30 #[error("IO error: {0}")]
31 Io(#[from] std::io::Error),
32
33 #[error("JSON error: {0}")]
34 Json(#[from] serde_json::Error),
35
36 #[error("Git error: {0}")]
37 GitError(String),
38
39 #[error("Spec markdown file not found: {0}")]
40 SpecMarkdownNotFound(PathBuf),
41
42 #[error("Spec file is empty")]
43 EmptySpec,
44
45 #[error("Spec generation failed: {0}")]
46 SpecGenerationFailed(String),
47
48 #[error("Invalid generated spec: {0}")]
49 InvalidGeneratedSpec(String),
50
51 #[error("Configuration error: {0}")]
52 Config(String),
53
54 #[error("Review failed after 3 iterations. Please manually review autom8_review.md for remaining issues.")]
55 MaxReviewIterationsReached,
56
57 #[error("No incomplete specs found in spec/\n\nNo spec files with incomplete user stories were found.\n\nTo start a new run:\n 1. Run 'autom8' to create a new spec interactively, or\n 2. Add a spec file to ~/.config/autom8/<project>/spec/, or\n 3. Set passes: false on stories you want to re-implement")]
58 NoSpecsToResume,
59
60 #[error("Shell completion error: {0}")]
61 ShellCompletion(String),
62
63 #[error("Worktree error: {0}")]
64 WorktreeError(String),
65
66 #[error("Branch conflict: branch '{branch}' is already in use by session '{session_id}' at {worktree_path}.\n\nThe branch is checked out in another worktree session.\n\nTo resolve this:\n 1. Wait for the other session to complete, or\n 2. Use a different branch name in your spec, or\n 3. Resume the existing session: autom8 resume --session {session_id}, or\n 4. Clean up the conflicting session: autom8 clean --session {session_id}")]
67 BranchConflict {
68 branch: String,
69 session_id: String,
70 worktree_path: std::path::PathBuf,
71 },
72
73 #[error("Signal handler error: {0}")]
74 SignalHandler(String),
75
76 #[error("Interrupted by user")]
77 Interrupted,
78
79 #[error("GUI error: {0}")]
80 GuiError(String),
81
82 #[error("Not in a git repository\n\nThe improve command must be run from within a git repository.\n\nTo fix this:\n 1. Navigate to your project directory: cd /path/to/your/project\n 2. Ensure the directory is a git repository (contains .git folder)\n 3. If not initialized, run: git init")]
83 NotInGitRepo,
84
85 #[error("Claude CLI not found\n\nThe 'claude' command could not be found in your PATH.\n\nTo fix this:\n 1. Install Claude Code: https://claude.ai/code\n 2. Ensure 'claude' is in your PATH\n 3. Try running 'claude --version' to verify installation")]
86 ClaudeNotFound,
87
88 #[error("Failed to spawn Claude: {0}\n\nCould not start the Claude CLI process.\n\nTo fix this:\n 1. Check that 'claude' is installed and working\n 2. Ensure you have permissions to run the command\n 3. Try running 'claude' manually to diagnose the issue")]
89 ClaudeSpawnError(String),
90}
91
92pub type Result<T> = std::result::Result<T, Autom8Error>;
93
94#[cfg(test)]
95mod tests {
96 use super::*;
97
98 #[test]
103 fn test_us012_branch_conflict_error_includes_what_happened() {
104 let err = Autom8Error::BranchConflict {
105 branch: "feature/test".to_string(),
106 session_id: "abc123".to_string(),
107 worktree_path: PathBuf::from("/path/to/worktree"),
108 };
109 let msg = err.to_string();
110
111 assert!(
113 msg.contains("Branch conflict"),
114 "Error should describe what happened"
115 );
116 assert!(
117 msg.contains("feature/test"),
118 "Error should include branch name"
119 );
120 }
121
122 #[test]
123 fn test_us012_branch_conflict_error_includes_why() {
124 let err = Autom8Error::BranchConflict {
125 branch: "feature/test".to_string(),
126 session_id: "abc123".to_string(),
127 worktree_path: PathBuf::from("/path/to/worktree"),
128 };
129 let msg = err.to_string();
130
131 assert!(
133 msg.contains("already in use") || msg.contains("checked out"),
134 "Error should explain why"
135 );
136 }
137
138 #[test]
139 fn test_us012_branch_conflict_error_includes_how_to_fix() {
140 let err = Autom8Error::BranchConflict {
141 branch: "feature/test".to_string(),
142 session_id: "abc123".to_string(),
143 worktree_path: PathBuf::from("/path/to/worktree"),
144 };
145 let msg = err.to_string();
146
147 assert!(
149 msg.contains("To resolve"),
150 "Error should include resolution steps"
151 );
152 assert!(
153 msg.contains("autom8 resume"),
154 "Error should suggest resume command"
155 );
156 assert!(
157 msg.contains("autom8 clean"),
158 "Error should suggest clean command"
159 );
160 assert!(
161 msg.contains("abc123"),
162 "Error should include session ID for commands"
163 );
164 }
165
166 #[test]
167 fn test_us012_spec_not_found_error_includes_fix() {
168 let err = Autom8Error::SpecNotFound(PathBuf::from("/missing/spec.json"));
169 let msg = err.to_string();
170
171 assert!(
172 msg.contains("not found"),
173 "Error should describe what happened"
174 );
175 assert!(msg.contains("To fix"), "Error should include fix steps");
176 assert!(
177 msg.contains("autom8 init") || msg.contains("init"),
178 "Error should suggest init command"
179 );
180 }
181
182 #[test]
183 fn test_us012_no_active_run_error_includes_fix() {
184 let err = Autom8Error::NoActiveRun;
185 let msg = err.to_string();
186
187 assert!(
188 msg.contains("No active run"),
189 "Error should describe what happened"
190 );
191 assert!(
192 msg.contains("To start") || msg.contains("To fix"),
193 "Error should include fix steps"
194 );
195 assert!(
196 msg.contains("autom8 status"),
197 "Error should suggest status command"
198 );
199 }
200
201 #[test]
202 fn test_us012_run_in_progress_error_includes_fix() {
203 let err = Autom8Error::RunInProgress("session123".to_string());
204 let msg = err.to_string();
205
206 assert!(
207 msg.contains("in progress"),
208 "Error should describe what happened"
209 );
210 assert!(msg.contains("To resolve"), "Error should include fix steps");
211 assert!(
212 msg.contains("--worktree"),
213 "Error should suggest worktree option"
214 );
215 }
216
217 #[test]
218 fn test_us012_no_incomplete_stories_error_includes_fix() {
219 let err = Autom8Error::NoIncompleteStories;
220 let msg = err.to_string();
221
222 assert!(
223 msg.contains("No incomplete stories"),
224 "Error should describe what happened"
225 );
226 assert!(
227 msg.contains("To continue"),
228 "Error should include fix steps"
229 );
230 assert!(
231 msg.contains("passes: false"),
232 "Error should suggest how to re-run stories"
233 );
234 }
235
236 #[test]
237 fn test_us012_no_specs_to_resume_error_includes_fix() {
238 let err = Autom8Error::NoSpecsToResume;
239 let msg = err.to_string();
240
241 assert!(
242 msg.contains("No incomplete specs"),
243 "Error should describe what happened"
244 );
245 assert!(msg.contains("To start"), "Error should include fix steps");
246 }
247
248 #[test]
249 fn test_us012_invalid_spec_error_includes_fix() {
250 let err = Autom8Error::InvalidSpec("missing field 'id'".to_string());
251 let msg = err.to_string();
252
253 assert!(
254 msg.contains("Invalid spec format"),
255 "Error should describe what happened"
256 );
257 assert!(msg.contains("To fix"), "Error should include fix steps");
258 assert!(
259 msg.contains("JSON") || msg.contains("syntax"),
260 "Error should mention format"
261 );
262 }
263
264 #[test]
269 fn test_us009_not_in_git_repo_error_includes_what_happened() {
270 let err = Autom8Error::NotInGitRepo;
271 let msg = err.to_string();
272
273 assert!(
274 msg.contains("Not in a git repository"),
275 "Error should describe what happened"
276 );
277 }
278
279 #[test]
280 fn test_us009_not_in_git_repo_error_includes_why() {
281 let err = Autom8Error::NotInGitRepo;
282 let msg = err.to_string();
283
284 assert!(
285 msg.contains("must be run from within a git repository"),
286 "Error should explain why"
287 );
288 }
289
290 #[test]
291 fn test_us009_not_in_git_repo_error_includes_fix() {
292 let err = Autom8Error::NotInGitRepo;
293 let msg = err.to_string();
294
295 assert!(msg.contains("To fix"), "Error should include fix steps");
296 assert!(
297 msg.contains("cd") || msg.contains("Navigate"),
298 "Error should suggest changing directory"
299 );
300 assert!(
301 msg.contains(".git") || msg.contains("git init"),
302 "Error should mention git initialization"
303 );
304 }
305}