sift_queue/cli/commands/
prime.rs1use anyhow::Result;
2
3pub fn execute() -> Result<i32> {
5 println!("{}", generate());
6 Ok(0)
7}
8
9pub 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## Readiness and dependencies
37
38Dependencies are modeled with `blocked_by`: an item is ready when it is `pending` and none of its blocker IDs refer to another non-closed item.
39
40Use these list views intentionally:
41
42- `sq list --ready` — actionable work only (`pending` and unblocked)
43- `sq list` — default view; shows all non-closed items so blocked dependencies and `in_progress` work stay visible
44- `sq list --all` — include closed items for history/auditing
45
46When choosing the next task to start, prefer `sq list --ready`.
47
48Blocker management examples:
49
50```bash
51sq add --title "Implement feature" --blocked-by abc
52sq edit xyz --set-blocked-by abc,def
53sq edit xyz --set-blocked-by ""
54sq show xyz
55```
56
57## Priority
58
59Priority uses the inclusive range `0..4`, where `0` is highest.
60
61## `sq` Commands"#
62 .to_string(),
63 );
64
65 parts.push(generate_command_reference());
66
67 parts.join("\n\n")
68}
69
70fn generate_command_reference() -> String {
71 let cmd = crate::build_cli();
72 let mut lines = Vec::new();
73
74 for sub in cmd.get_subcommands() {
75 let name = sub.get_name();
76 if name == "prime" || name == "help" {
77 continue;
78 }
79
80 let about = sub.get_about().map(|a| a.to_string()).unwrap_or_default();
81 lines.push(format!("### `sq {}` — {}\n", name, about));
82 lines.push("```".to_string());
83
84 for arg in sub.get_arguments() {
85 if arg.is_hide_set() {
86 continue;
87 }
88 let id = arg.get_id().as_str();
89 if id == "help" || id == "version" {
90 continue;
91 }
92
93 let is_positional = arg.get_long().is_none() && arg.get_short().is_none();
95 if is_positional {
96 continue;
97 }
98
99 let long = arg.get_long().map(|l| format!("--{}", l));
100 let short = arg.get_short().map(|s| format!("-{}", s));
101 let names: Vec<String> = [short, long].into_iter().flatten().collect();
102 let names_str = names.join(", ");
103
104 let is_bool = arg.get_action().takes_values();
106 let value = if is_bool {
107 arg.get_value_names()
108 .map(|v| {
109 v.iter()
110 .map(|s| s.to_string())
111 .collect::<Vec<_>>()
112 .join(" ")
113 })
114 .unwrap_or_default()
115 } else {
116 String::new()
117 };
118
119 let usage = if value.is_empty() {
120 names_str
121 } else {
122 format!("{} {}", names_str, value)
123 };
124
125 let help = arg.get_help().map(|h| h.to_string()).unwrap_or_default();
126
127 lines.push(format!(" {} {}", usage, help));
128 }
129
130 lines.push("```".to_string());
131 lines.push(String::new());
132 lines.push(format!("For more information, use `sq {} --help`.", name));
133 lines.push(String::new());
134 }
135
136 lines.join("\n")
137}