Skip to main content

sift_queue/
lib.rs

1pub mod cli;
2pub mod collect;
3pub mod queue;
4pub mod queue_path;
5
6use clap::{builder::StyledStr, Args, Command, CommandFactory, Parser, Subcommand};
7use std::path::PathBuf;
8
9#[derive(Parser)]
10#[command(name = "sq", version)]
11pub struct Cli {
12    /// Path to queue file
13    #[arg(short = 'q', long = "queue", value_name = "PATH", global = true)]
14    pub queue: Option<PathBuf>,
15
16    #[command(subcommand)]
17    pub command: Commands,
18}
19
20pub fn build_cli() -> Command {
21    Cli::command().mut_subcommand("collect", |subcmd| {
22        let styles = subcmd.get_styles();
23        let header = styles.get_header();
24        let literal = styles.get_literal();
25        let help = StyledStr::from(format!(
26            "{header}Examples:{header:#}\n  {literal}rg --json PATTERN | sq collect --by-file{literal:#}\n  {literal}rg --json -n -C2 PATTERN | sq collect --by-file --title-template \"migrate: {{{{filepath}}}}\"{literal:#}\n\n{header}Templates:{header:#}\n  {literal}{{{{filepath}}}}{literal:#}     Full file path for the grouped result\n  {literal}{{{{filename}}}}{literal:#}     Basename of {literal}{{{{filepath}}}}{literal:#}\n  {literal}{{{{match_count}}}}{literal:#}  Number of rg match events collected for the file\n\n  Default title template: {literal}{{{{match_count}}}}:{{{{filepath}}}}{literal:#}"
27        ));
28
29        subcmd.after_help(help)
30    })
31}
32
33#[derive(Subcommand)]
34pub enum Commands {
35    /// Add a new item to the review queue
36    Add(AddArgs),
37    /// Collect items from stdin into queue items
38    Collect(CollectArgs),
39    /// List queue items
40    List(ListArgs),
41    /// Show details of a queue item
42    Show(ShowArgs),
43    /// Edit an existing queue item
44    Edit(EditArgs),
45    /// Mark an item as closed
46    Close(StatusArgs),
47    /// Remove an item from the queue
48    Rm(RmArgs),
49    /// Output queue workflow context for AI agents
50    Prime(PrimeArgs),
51}
52
53#[derive(Parser)]
54pub struct AddArgs {
55    /// Add diff source (repeatable)
56    #[arg(long = "diff", value_name = "PATH")]
57    pub diff: Vec<String>,
58
59    /// Add file source (repeatable)
60    #[arg(long = "file", value_name = "PATH")]
61    pub file: Vec<String>,
62
63    /// Add text source (repeatable)
64    #[arg(long = "text", value_name = "STRING")]
65    pub text: Vec<String>,
66
67    /// Add directory source (repeatable)
68    #[arg(long = "directory", value_name = "PATH")]
69    pub directory: Vec<String>,
70
71    /// Read source content from stdin (diff|file|text|directory)
72    #[arg(long = "stdin", value_name = "TYPE")]
73    pub stdin: Option<String>,
74
75    /// Title for the item
76    #[arg(long = "title", value_name = "TITLE")]
77    pub title: Option<String>,
78
79    /// Description for the item
80    #[arg(long = "description", value_name = "TEXT")]
81    pub description: Option<String>,
82
83    /// Attach metadata as JSON
84    #[arg(long = "metadata", value_name = "JSON")]
85    pub metadata: Option<String>,
86
87    /// Comma-separated blocker IDs
88    #[arg(long = "blocked-by", value_name = "IDS")]
89    pub blocked_by: Option<String>,
90
91    /// Output as JSON
92    #[arg(long = "json")]
93    pub json: bool,
94}
95
96#[derive(Args)]
97#[command(about = "Collect items from stdin into queue items")]
98pub struct CollectArgs {
99    /// Split stdin into one item per file
100    #[arg(long = "by-file")]
101    pub by_file: bool,
102
103    /// Input format: currently only rg-json is supported
104    #[arg(long = "stdin-format", value_name = "FORMAT")]
105    pub stdin_format: Option<String>,
106
107    /// Title for every created item
108    #[arg(long = "title", value_name = "TITLE")]
109    pub title: Option<String>,
110
111    /// Description for every created item
112    #[arg(long = "description", value_name = "TEXT")]
113    pub description: Option<String>,
114
115    /// Template for each created item title
116    #[arg(long = "title-template", value_name = "TEMPLATE")]
117    pub title_template: Option<String>,
118
119    /// Attach metadata as JSON
120    #[arg(long = "metadata", value_name = "JSON")]
121    pub metadata: Option<String>,
122
123    /// Comma-separated blocker IDs
124    #[arg(long = "blocked-by", value_name = "IDS")]
125    pub blocked_by: Option<String>,
126
127    /// Output as JSON
128    #[arg(long = "json")]
129    pub json: bool,
130}
131
132#[derive(Parser)]
133pub struct ListArgs {
134    /// Filter by status (pending|in_progress|closed)
135    #[arg(long = "status", value_name = "STATUS")]
136    pub status: Option<String>,
137
138    /// Include closed items when status is not explicitly filtered
139    #[arg(long = "all")]
140    pub all: bool,
141
142    /// Output as JSON
143    #[arg(long = "json")]
144    pub json: bool,
145
146    /// jq select expression
147    #[arg(long = "filter", value_name = "EXPR")]
148    pub filter: Option<String>,
149
150    /// jq path expression to sort by
151    #[arg(long = "sort", value_name = "PATH")]
152    pub sort: Option<String>,
153
154    /// Reverse sort order
155    #[arg(long = "reverse")]
156    pub reverse: bool,
157
158    /// Show only ready items (pending and unblocked)
159    #[arg(long = "ready")]
160    pub ready: bool,
161}
162
163#[derive(Parser)]
164pub struct ShowArgs {
165    /// Item ID
166    pub id: Option<String>,
167
168    /// Output as JSON
169    #[arg(long = "json")]
170    pub json: bool,
171}
172
173#[derive(Parser)]
174pub struct EditArgs {
175    /// Item ID
176    pub id: Option<String>,
177
178    /// Add diff source
179    #[arg(long = "add-diff", value_name = "PATH")]
180    pub add_diff: Vec<String>,
181
182    /// Add file source
183    #[arg(long = "add-file", value_name = "PATH")]
184    pub add_file: Vec<String>,
185
186    /// Add text source
187    #[arg(long = "add-text", value_name = "STRING")]
188    pub add_text: Vec<String>,
189
190    /// Add directory source
191    #[arg(long = "add-directory", value_name = "PATH")]
192    pub add_directory: Vec<String>,
193
194    /// Add transcript source
195    #[arg(long = "add-transcript", value_name = "PATH")]
196    pub add_transcript: Vec<String>,
197
198    /// Remove source by index (0-based, repeatable)
199    #[arg(long = "rm-source", value_name = "INDEX")]
200    pub rm_source: Vec<usize>,
201
202    /// Change status (pending|in_progress|closed)
203    #[arg(long = "set-status", value_name = "STATUS")]
204    pub set_status: Option<String>,
205
206    /// Set title for the item
207    #[arg(long = "set-title", value_name = "TITLE")]
208    pub set_title: Option<String>,
209
210    /// Set description for the item
211    #[arg(long = "set-description", value_name = "TEXT")]
212    pub set_description: Option<String>,
213
214    /// Set metadata as JSON (replaces full metadata object)
215    #[arg(long = "set-metadata", value_name = "JSON")]
216    pub set_metadata: Option<String>,
217
218    /// Merge metadata object as JSON (deep object merge)
219    #[arg(long = "merge-metadata", value_name = "JSON")]
220    pub merge_metadata: Option<String>,
221
222    /// Set blocker IDs (comma-separated, empty to clear)
223    #[arg(long = "set-blocked-by", value_name = "IDS")]
224    pub set_blocked_by: Option<String>,
225
226    /// Output as JSON
227    #[arg(long = "json")]
228    pub json: bool,
229}
230
231#[derive(Parser)]
232pub struct StatusArgs {
233    /// Item ID
234    pub id: Option<String>,
235
236    /// Output as JSON
237    #[arg(long = "json")]
238    pub json: bool,
239}
240
241#[derive(Parser)]
242pub struct RmArgs {
243    /// Item ID
244    pub id: Option<String>,
245
246    /// Output as JSON
247    #[arg(long = "json")]
248    pub json: bool,
249}
250
251#[derive(Parser)]
252pub struct PrimeArgs {
253    /// Force full CLI output
254    #[arg(long = "full")]
255    pub full: bool,
256}