Skip to main content

sift_queue/cli/commands/
prime.rs

1use crate::PrimeArgs;
2use anyhow::Result;
3
4/// Execute the `sq prime` command.
5pub fn execute(args: &PrimeArgs) -> Result<i32> {
6    println!("{}", generate(args.prelude));
7    Ok(0)
8}
9
10/// Generate the prime context string.
11pub fn generate(prelude_only: bool) -> String {
12    let mut parts = Vec::new();
13
14    parts.push(
15        r#"# sq — Lightweight task-list CLI with structured sources
16
17Use `sq` to manage tasks in a JSONL file during agent workflows.
18
19By default, `sq` discovers the nearest existing `.sift/issues.jsonl` within the current git worktree and otherwise falls back to `<cwd>/.sift/issues.jsonl`. Override with `-q, --queue <PATH>` or `SQ_QUEUE_PATH=<PATH>`.
20
21## Examples
22
23```bash
24sq add --title "Investigate checkout exception" \
25  --description "Review the pasted error report and identify the failing code path" \
26  --priority 1 \
27  --text "Sentry alert: NoMethodError in Checkout::ApplyDiscount at app/services/checkout/apply_discount.rb:42"
28
29rg --json -n -C2 'OldApi.call' | sq collect --by-file \
30  --title-template "migrate: {{filepath}}" \
31  --description "Migrate OldApi.call to NewApi.call" \
32  --priority 2
33
34sq list --ready
35```
36
37## Readiness and dependencies
38
39Use `blocked_by` to model dependencies. A task is ready when it is `pending` and none of its blocker IDs refer to another non-closed task.
40
41Use these list views:
42
43- `sq list --ready` — actionable work only (`pending` and unblocked)
44- `sq list` — default view; shows all non-closed items so blocked dependencies and `in_progress` work stay visible
45- `sq list --all` — include closed items for history/auditing
46
47When you need the next task, start with `sq list --ready`.
48
49Manage blockers like this:
50
51```bash
52sq add --title "Implement feature" --blocked-by abc
53sq edit xyz --set-blocked-by abc,def
54sq edit xyz --set-blocked-by ""
55sq show xyz
56```
57
58## Priority
59
60Priority uses the inclusive range `0..4`, where `0` is highest.
61
62Use priority to order ready work. Do not treat it as a measure of overall importance.
63
64Combine `priority` with `blocked_by` so ready items form a practical next-work queue.
65
66User instruction overrides queue order. If the user asks for a specific task, do that task even when other tasks have higher priority.
67
68Do not treat lower-priority tasks as ignorable; they are just not the default next task."#
69            .to_string(),
70    );
71
72    if !prelude_only {
73        parts.push(generate_command_reference());
74    }
75
76    parts.join("\n\n")
77}
78
79fn generate_command_reference() -> String {
80    let cmd = crate::build_cli();
81    let mut lines = vec!["## `sq` Commands".to_string(), String::new()];
82
83    for sub in cmd.get_subcommands() {
84        let name = sub.get_name();
85        if name == "prime" || name == "help" {
86            continue;
87        }
88
89        let about = sub.get_about().map(|a| a.to_string()).unwrap_or_default();
90        lines.push(format!("### `sq {}` — {}\n", name, about));
91        lines.push("```".to_string());
92
93        for arg in sub.get_arguments() {
94            if arg.is_hide_set() {
95                continue;
96            }
97            let id = arg.get_id().as_str();
98            if id == "help" || id == "version" {
99                continue;
100            }
101
102            // Skip positional arguments in the flags listing
103            let is_positional = arg.get_long().is_none() && arg.get_short().is_none();
104            if is_positional {
105                continue;
106            }
107
108            let long = arg.get_long().map(|l| format!("--{}", l));
109            let short = arg.get_short().map(|s| format!("-{}", s));
110            let names: Vec<String> = [short, long].into_iter().flatten().collect();
111            let names_str = names.join(", ");
112
113            // For boolean flags (num_vals == 0), don't show value name
114            let is_bool = arg.get_action().takes_values();
115            let value = if is_bool {
116                arg.get_value_names()
117                    .map(|v| {
118                        v.iter()
119                            .map(|s| s.to_string())
120                            .collect::<Vec<_>>()
121                            .join(" ")
122                    })
123                    .unwrap_or_default()
124            } else {
125                String::new()
126            };
127
128            let usage = if value.is_empty() {
129                names_str
130            } else {
131                format!("{} {}", names_str, value)
132            };
133
134            let help = arg.get_help().map(|h| h.to_string()).unwrap_or_default();
135
136            lines.push(format!("  {}  {}", usage, help));
137        }
138
139        lines.push("```".to_string());
140        lines.push(String::new());
141        lines.push(format!("For more information, use `sq {} --help`.", name));
142        lines.push(String::new());
143    }
144
145    lines.join("\n")
146}