impl ValidationError {
#[must_use]
pub fn recovery_commands(&self) -> (String, Vec<String>) {
match self {
Self::FileMissing { path } => {
let problem = format!(
"The file '{path}' is missing but was present when the checkpoint was created."
);
let commands = if path.contains("PROMPT.md") {
vec![
format!("# Check if file exists elsewhere"),
format!("find . -name 'PROMPT.md' -type f 2>/dev/null"),
format!(""),
format!("# Or recreate from requirements"),
format!("# Restore from backup or recreate PROMPT.md"),
format!(""),
format!("# If unrecoverable, delete checkpoint to start fresh"),
format!("rm .agent/checkpoint.json"),
]
} else if path.contains(".agent/PLAN.md") {
vec![
format!("# Check if file exists elsewhere"),
format!("find . -name 'PLAN.md' -type f 2>/dev/null"),
format!(""),
format!("# Restore plan file from backup"),
format!("# The pipeline requires PLAN.md to resume properly"),
format!(""),
format!("# If unrecoverable, delete checkpoint to start fresh"),
format!("rm .agent/checkpoint.json"),
]
} else if path.contains(".agent/ISSUES.md") {
vec![
format!("# Issues file may be regenerated by running review phase"),
format!("# Delete checkpoint to start fresh if needed"),
format!("rm .agent/checkpoint.json"),
]
} else {
vec![
format!("# Check if file exists elsewhere"),
format!("find . -name '{}' -type f 2>/dev/null", path),
format!(""),
format!("# Restore the file from git"),
format!("git checkout HEAD -- {}", path),
format!(""),
format!("# If unrecoverable, delete checkpoint to start fresh"),
format!("rm .agent/checkpoint.json"),
]
};
(problem, commands)
}
Self::FileUnexpectedlyExists { path } => {
let problem = format!(
"The file '{path}' exists but was not present when the checkpoint was created."
);
let commands = vec![
format!("# Review the unexpected file"),
format!("ls -la {}", path),
format!("cat {}", path),
format!(""),
format!("# Option 1: Remove the file to match checkpoint state"),
format!("rm {}", path),
format!(""),
format!("# Option 2: Accept new state and delete checkpoint"),
format!("rm .agent/checkpoint.json"),
];
(problem, commands)
}
Self::FileContentChanged { path } => {
let problem = format!(
"The file '{path}' content has changed since the checkpoint was created."
);
let commands = if path.contains("PROMPT.md") || path.contains(".agent/PLAN.md") {
vec![
format!("# Compare current file with git version"),
format!("git diff HEAD -- {}", path),
format!(""),
format!("# If changes are intentional, delete checkpoint to accept new state"),
format!("rm .agent/checkpoint.json"),
format!(""),
format!("# Or restore file to checkpoint state"),
format!("git checkout HEAD -- {}", path),
]
} else {
vec![
format!("# Compare file with git version"),
format!("git diff HEAD -- {}", path),
format!(""),
format!("# Option 1: Restore file to checkpoint state"),
format!("git checkout HEAD -- {}", path),
format!(""),
format!("# Or stash current changes and restore from checkpoint"),
format!("git stash"),
]
};
(problem, commands)
}
Self::GitHeadChanged { expected, actual } => {
let problem = format!(
"Git HEAD has changed from {expected} to {actual}. New commits may have been made or HEAD was reset."
);
let commands = vec![
format!("# View the commits that were made after checkpoint"),
format!("git log {}..HEAD --oneline", expected),
format!(""),
format!("# Option 1: Reset to checkpoint state"),
format!("git reset {}", expected),
format!(""),
format!("# Option 2: Accept new state and delete checkpoint"),
format!("rm .agent/checkpoint.json"),
format!(""),
format!("# Option 3: Use --recovery-strategy=force to proceed anyway (risky)"),
];
(problem, commands)
}
Self::GitStateInvalid { reason } => {
let problem = format!("Git state is invalid: {reason}");
let commands = if reason.contains("detached") {
vec![
format!("# View current branch situation"),
format!("git branch -a"),
format!(""),
format!("# Reattach to a branch"),
format!("git checkout <branch-name>"),
format!(""),
format!("# Or list recent commits to choose from"),
format!("git log --oneline -10"),
]
} else if reason.contains("merge") || reason.contains("rebase") {
vec![
format!("# Check current git status"),
format!("git status"),
format!(""),
format!("# Option 1: Continue the operation"),
format!("# (resolve conflicts, then git add/rm && git continue)"),
format!(""),
format!("# Option 2: Abort the operation"),
format!("git merge --abort # or 'git rebase --abort'"),
format!(""),
format!("# Option 3: Delete checkpoint and start fresh"),
format!("rm .agent/checkpoint.json"),
]
} else {
vec![
format!("# Check current git status"),
format!("git status"),
format!(""),
format!("# Fix the reported issue or delete checkpoint to start fresh"),
format!("rm .agent/checkpoint.json"),
]
};
(problem, commands)
}
Self::GitWorkingTreeChanged { changes } => {
let problem = format!("Git working tree has uncommitted changes: {changes}");
let commands = vec![
format!("# View what changed"),
format!("git status"),
format!("git diff"),
format!(""),
format!("# Option 1: Commit the changes"),
format!("git add -A && git commit -m 'Save work before resume'"),
format!(""),
format!("# Option 2: Stash the changes"),
format!("git stash push -m 'Work saved before resume'"),
format!(""),
format!("# Option 3: Discard the changes"),
format!("git reset --hard HEAD"),
format!(""),
format!("# Option 4: Use --recovery-strategy=force to proceed anyway"),
];
(problem, commands)
}
}
}
}