Skip to main content

sift_queue/cli/commands/
prime.rs

1use anyhow::Result;
2
3/// Execute the `sq prime` command.
4pub fn execute() -> Result<i32> {
5    println!("{}", generate());
6    Ok(0)
7}
8
9/// Generate the prime context string.
10pub fn generate() -> String {
11    let mut parts = Vec::new();
12
13    parts.push(
14        r#"# sq — Lightweight task-list CLI with structured sources
15
16`sq` manages tasks in a JSONL file for agent workflows.
17
18By default, `sq` stores tasks in `.sift/issues.jsonl`. Override with `-q, --queue <PATH>` or `SQ_QUEUE_PATH=<PATH>`.
19
20## Examples
21
22```bash
23sq add --title "Investigate checkout exception" \
24  --description "Review the pasted error report and identify the failing code path" \
25  --priority 1 \
26  --text "Sentry alert: NoMethodError in Checkout::ApplyDiscount at app/services/checkout/apply_discount.rb:42"
27
28rg --json -n -C2 'OldApi.call' | sq collect --by-file \
29  --title-template "migrate: {{filepath}}" \
30  --description "Migrate OldApi.call to NewApi.call" \
31  --priority 2
32
33sq list --ready
34```
35
36## Priority
37
38Priority uses the inclusive range `0..4`, where `0` is highest.
39
40## `sq` Commands"#
41            .to_string(),
42    );
43
44    parts.push(generate_command_reference());
45
46    parts.join("\n\n")
47}
48
49fn generate_command_reference() -> String {
50    let cmd = crate::build_cli();
51    let mut lines = Vec::new();
52
53    for sub in cmd.get_subcommands() {
54        let name = sub.get_name();
55        if name == "prime" || name == "help" {
56            continue;
57        }
58
59        let about = sub.get_about().map(|a| a.to_string()).unwrap_or_default();
60        lines.push(format!("### `sq {}` — {}\n", name, about));
61        lines.push("```".to_string());
62
63        for arg in sub.get_arguments() {
64            if arg.is_hide_set() {
65                continue;
66            }
67            let id = arg.get_id().as_str();
68            if id == "help" || id == "version" {
69                continue;
70            }
71
72            // Skip positional arguments in the flags listing
73            let is_positional = arg.get_long().is_none() && arg.get_short().is_none();
74            if is_positional {
75                continue;
76            }
77
78            let long = arg.get_long().map(|l| format!("--{}", l));
79            let short = arg.get_short().map(|s| format!("-{}", s));
80            let names: Vec<String> = [short, long].into_iter().flatten().collect();
81            let names_str = names.join(", ");
82
83            // For boolean flags (num_vals == 0), don't show value name
84            let is_bool = arg.get_action().takes_values();
85            let value = if is_bool {
86                arg.get_value_names()
87                    .map(|v| {
88                        v.iter()
89                            .map(|s| s.to_string())
90                            .collect::<Vec<_>>()
91                            .join(" ")
92                    })
93                    .unwrap_or_default()
94            } else {
95                String::new()
96            };
97
98            let usage = if value.is_empty() {
99                names_str
100            } else {
101                format!("{} {}", names_str, value)
102            };
103
104            let help = arg.get_help().map(|h| h.to_string()).unwrap_or_default();
105
106            lines.push(format!("  {}  {}", usage, help));
107        }
108
109        lines.push("```".to_string());
110        lines.push(String::new());
111        lines.push(format!("For more information, use `sq {} --help`.", name));
112        lines.push(String::new());
113    }
114
115    lines.join("\n")
116}