1mod aging;
17mod archive;
18mod burndown;
19mod dashboard;
20mod explain;
21mod export;
22mod graph;
23mod history;
24mod import;
25mod issue;
26mod list;
27mod next;
28mod next_id;
29mod prune;
30mod repair;
31mod schema;
32mod search;
33mod shared;
34mod show;
35mod sort;
36mod stats;
37mod stop;
38mod tree;
39mod unlock;
40mod validate;
41
42use anyhow::Result;
43use clap::{Args, Subcommand};
44
45use crate::config;
46
47pub use aging::QueueAgingArgs;
48pub use archive::QueueArchiveArgs;
49pub use burndown::QueueBurndownArgs;
50pub use dashboard::QueueDashboardArgs;
51pub use explain::QueueExplainArgs;
52pub use export::QueueExportArgs;
53pub use graph::QueueGraphArgs;
54pub use history::QueueHistoryArgs;
55pub use import::QueueImportArgs;
56pub use issue::QueueIssueArgs;
57pub use list::QueueListArgs;
58pub use next::QueueNextArgs;
59pub use next_id::QueueNextIdArgs;
60pub use prune::QueuePruneArgs;
61pub use repair::RepairArgs;
62pub use search::QueueSearchArgs;
63pub use shared::{
64 QueueExportFormat, QueueImportFormat, QueueListFormat, QueueListSortBy, QueueReportFormat,
65 QueueShowFormat, QueueSortBy, QueueSortOrder, StatusArg,
66};
67pub use show::QueueShowArgs;
68pub(crate) use show::show_task;
69pub use sort::QueueSortArgs;
70pub use stats::QueueStatsArgs;
71pub use tree::QueueTreeArgs;
72pub use unlock::QueueUnlockArgs;
73
74pub fn handle_queue(cmd: QueueCommand, force: bool) -> Result<()> {
75 let resolved = config::resolve_from_cwd()?;
76 match cmd {
77 QueueCommand::Validate => validate::handle(&resolved),
78 QueueCommand::Next(args) => next::handle(&resolved, args),
79 QueueCommand::NextId(args) => next_id::handle(&resolved, args),
80 QueueCommand::Show(args) => show::handle(&resolved, args),
81 QueueCommand::List(args) => list::handle(&resolved, args),
82 QueueCommand::Search(args) => search::handle(&resolved, args),
83 QueueCommand::Archive(args) => archive::handle(&resolved, force, args),
84 QueueCommand::Repair(args) => repair::handle(&resolved, force, args),
85 QueueCommand::Unlock(args) => unlock::handle(&resolved, args),
86 QueueCommand::Sort(args) => sort::handle(&resolved, force, args),
87 QueueCommand::Stats(args) => stats::handle(&resolved, args),
88 QueueCommand::History(args) => history::handle(&resolved, args),
89 QueueCommand::Burndown(args) => burndown::handle(&resolved, args),
90 QueueCommand::Aging(args) => aging::handle(&resolved, args),
91 QueueCommand::Schema => schema::handle(),
92 QueueCommand::Prune(args) => prune::handle(&resolved, force, args),
93 QueueCommand::Graph(args) => graph::handle(&resolved, args),
94 QueueCommand::Export(args) => export::handle(&resolved, args),
95 QueueCommand::Import(args) => import::handle(&resolved, force, args),
96 QueueCommand::Stop => stop::handle(&resolved),
97 QueueCommand::Explain(args) => explain::handle(&resolved, args),
98 QueueCommand::Tree(args) => tree::handle(&resolved, args),
99 QueueCommand::Dashboard(args) => dashboard::handle(&resolved, args),
100 QueueCommand::Issue(args) => issue::handle(&resolved, force, args),
101 }
102}
103
104#[derive(Args)]
105#[command(
106 about = "Inspect and manage the task queue",
107 after_long_help = "Examples:\n ralph queue list\n ralph queue list --status todo --tag rust\n ralph queue show RQ-0008\n ralph queue next --with-title\n ralph queue next-id\n ralph queue archive"
108)]
109pub struct QueueArgs {
110 #[command(subcommand)]
111 pub command: QueueCommand,
112}
113
114#[derive(Subcommand)]
115pub enum QueueCommand {
116 #[command(
118 after_long_help = "Examples:\n ralph queue validate\n ralph --verbose queue validate"
119 )]
120 Validate,
121
122 #[command(
124 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"
125 )]
126 Prune(QueuePruneArgs),
127
128 #[command(
130 after_long_help = "Examples:\n ralph queue next\n ralph queue next --with-title\n ralph queue next --with-eta\n ralph queue next --with-title --with-eta\n ralph queue next --explain\n ralph queue next --explain --with-title"
131 )]
132 Next(QueueNextArgs),
133
134 #[command(
136 after_long_help = "Examples:\n ralph queue next-id\n ralph queue next-id --count 5\n ralph queue next-id -n 3\n ralph --verbose queue next-id"
137 )]
138 NextId(QueueNextIdArgs),
139
140 Show(QueueShowArgs),
142
143 List(QueueListArgs),
145
146 #[command(
148 after_long_help = "Examples:\n ralph queue search \"authentication\"\n ralph queue search \"RQ-\\d{4}\" --regex\n ralph queue search \"TODO\" --match-case\n ralph queue search \"fix\" --status todo --tag rust\n ralph queue search \"refactor\" --scope crates/ralph --tag rust\n ralph queue search \"auth bug\" --fuzzy\n ralph queue search \"fuzzy search\" --fuzzy --match-case"
149 )]
150 Search(QueueSearchArgs),
151
152 #[command(
154 after_long_help = "Examples:\n ralph queue archive\n ralph queue archive --dry-run"
155 )]
156 Archive(QueueArchiveArgs),
157
158 #[command(after_long_help = "Example:\n ralph queue repair\n ralph queue repair --dry-run")]
160 Repair(RepairArgs),
161
162 #[command(after_long_help = "Safely remove the queue lock directory.\n\n\
164Safety:\n - Checks if the lock holder process is still running\n - Blocks if process is active (override with --force)\n - Requires confirmation in interactive mode (bypass with --yes)\n\n\
165Examples:\n ralph queue unlock --dry-run\n ralph queue unlock --yes\n ralph queue unlock --force --yes")]
166 Unlock(QueueUnlockArgs),
167
168 #[command(
170 after_long_help = "Examples:\n ralph queue sort\n ralph queue sort --order descending\n ralph queue sort --order ascending"
171 )]
172 Sort(QueueSortArgs),
173
174 #[command(
176 after_long_help = "Examples:\n ralph queue stats\n ralph queue stats --tag rust --tag cli\n ralph queue stats --format json"
177 )]
178 Stats(QueueStatsArgs),
179
180 #[command(after_long_help = "Examples:\n ralph queue history\n ralph queue history --days 14")]
182 History(QueueHistoryArgs),
183
184 #[command(
186 after_long_help = "Examples:\n ralph queue burndown\n ralph queue burndown --days 30"
187 )]
188 Burndown(QueueBurndownArgs),
189
190 #[command(
192 after_long_help = "Examples:\n ralph queue aging\n ralph queue aging --format json\n ralph queue aging --status todo --status doing"
193 )]
194 Aging(QueueAgingArgs),
195
196 #[command(after_long_help = "Example:\n ralph queue schema")]
198 Schema,
199
200 #[command(
202 after_long_help = "Examples:\n ralph queue graph\n ralph queue graph --task RQ-0001\n ralph queue graph --format dot\n ralph queue graph --critical\n ralph queue graph --reverse --task RQ-0001"
203 )]
204 Graph(QueueGraphArgs),
205
206 #[command(
208 after_long_help = "Examples:\n ralph queue export\n ralph queue export --format csv --output tasks.csv\n ralph queue export --format json --status done\n ralph queue export --format tsv --tag rust --tag cli\n ralph queue export --format md --status todo\n ralph queue export --format gh --id-pattern RQ-0001\n ralph queue export --include-archive --format csv\n ralph queue export --format csv --created-after 2026-01-01"
209 )]
210 Export(QueueExportArgs),
211
212 #[command(
214 after_long_help = "Examples:\n ralph queue import --format json < tasks.json\n ralph queue import --format csv tasks.csv\n ralph queue import --format tsv --on-duplicate rename tasks.tsv\n ralph queue import --format json --dry-run < tasks.json"
215 )]
216 Import(QueueImportArgs),
217
218 #[command(
220 after_long_help = "Examples:\n ralph queue stop\n\nNotes:\n - This creates a stop signal file that the run loop checks between tasks.\n - Sequential mode: exits between tasks (current task completes, then exits).\n - Parallel mode: stops scheduling new tasks; waits for in-flight tasks to complete.\n - The stop signal is automatically cleared when the loop honors the request.\n - To force immediate termination, use Ctrl+C in the running loop."
221 )]
222 Stop,
223
224 #[command(
226 after_long_help = "Examples:\n ralph queue explain\n ralph queue explain --format json\n ralph queue explain --include-draft\n ralph queue explain --format json --include-draft"
227 )]
228 Explain(QueueExplainArgs),
229
230 #[command(
232 after_long_help = "Examples:\n ralph queue tree\n ralph queue tree --include-done\n ralph queue tree --root RQ-0001\n ralph queue tree --max-depth 25"
233 )]
234 Tree(QueueTreeArgs),
235
236 #[command(
238 after_long_help = "Examples:\n ralph queue dashboard\n ralph queue dashboard --days 30\n ralph queue dashboard --days 7\n\n\
239The dashboard command returns all analytics data in a single JSON payload for GUI clients.\n\
240Each section includes a 'status' field ('ok' or 'unavailable') for graceful partial failure handling."
241 )]
242 Dashboard(QueueDashboardArgs),
243
244 #[command(
246 after_long_help = "Examples:\n ralph queue issue publish RQ-0655\n ralph queue issue publish RQ-0655 --dry-run\n ralph queue issue publish RQ-0655 --label bug --assignee @me\n ralph queue issue publish RQ-0655 --repo owner/repo\n ralph queue issue publish-many --status todo --tag bug --dry-run\n ralph queue issue publish-many --status todo --execute --force"
247 )]
248 Issue(QueueIssueArgs),
249}
250
251#[cfg(test)]
252mod tests;