ralph-workflow 0.7.18

PROMPT-driven multi-agent orchestrator for git repos
Documentation
/// Recovery suggestion for a validation error.
impl ValidationError {
    /// Get a structured recovery guide with "What's wrong" and "How to fix" sections.
    ///
    /// Returns a tuple of (`problem_description`, `recovery_commands`) where:
    /// - `problem_description` explains what the issue is
    /// - `recovery_commands` is a vector of suggested commands to fix it
    #[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)
            }
        }
    }
}