Skip to main content

ralph/cli/queue/
prune.rs

1//! Queue prune subcommand.
2
3use anyhow::Result;
4use clap::Args;
5
6use crate::config::Resolved;
7use crate::queue;
8
9use super::StatusArg;
10
11/// Arguments for `ralph queue prune`.
12#[derive(Args)]
13#[command(
14    after_long_help = "Prune removes old tasks from .ralph/done.jsonc while preserving recent history.\n\nSafety:\n  --keep-last always protects the N most recently completed tasks (by completed_at).\n  If no filters are provided, all tasks are pruned except those protected by --keep-last.\n  Missing or invalid completed_at timestamps are treated as oldest for keep-last ordering\n  but do NOT match the age filter (safety-first).\n\nExamples:\n  ralph queue prune --dry-run --age 30 --status rejected\n  ralph queue prune --keep-last 100\n  ralph queue prune --age 90\n  ralph queue prune --age 30 --status done --keep-last 50"
15)]
16pub struct QueuePruneArgs {
17    /// Only prune tasks completed at least N days ago.
18    #[arg(long)]
19    pub age: Option<u32>,
20
21    /// Filter by task status (repeatable).
22    #[arg(long, value_enum)]
23    pub status: Vec<StatusArg>,
24
25    /// Keep the N most recently completed tasks regardless of filters.
26    #[arg(long)]
27    pub keep_last: Option<u32>,
28
29    /// Show what would be pruned without writing to disk.
30    #[arg(long)]
31    pub dry_run: bool,
32}
33
34pub(crate) fn handle(resolved: &Resolved, force: bool, args: QueuePruneArgs) -> Result<()> {
35    let _queue_lock = queue::acquire_queue_lock(&resolved.repo_root, "queue prune", force)?;
36
37    // Create undo snapshot before mutation (only if not dry-run)
38    if !args.dry_run {
39        crate::undo::create_undo_snapshot(resolved, "queue prune")?;
40    }
41
42    let report: queue::PruneReport = queue::prune_done_tasks(
43        &resolved.done_path,
44        queue::PruneOptions {
45            age_days: args.age,
46            statuses: args.status.into_iter().map(|s| s.into()).collect(),
47            keep_last: args.keep_last,
48            dry_run: args.dry_run,
49        },
50    )?;
51    if args.dry_run {
52        log::info!("Dry run: would prune {} task(s).", report.pruned_ids.len());
53        if !report.pruned_ids.is_empty() {
54            log::info!("Pruned IDs: {}", report.pruned_ids.join(", "));
55        }
56        if !report.kept_ids.is_empty() {
57            log::info!("Kept IDs: {}", report.kept_ids.join(", "));
58        }
59    } else {
60        if report.pruned_ids.is_empty() {
61            log::info!("No tasks pruned.");
62        } else {
63            log::info!("Pruned {} task(s).", report.pruned_ids.len());
64        }
65        if !report.kept_ids.is_empty() {
66            log::debug!("Kept {} task(s).", report.kept_ids.len());
67        }
68    }
69    Ok(())
70}