1use anyhow::Result;
2use std::path::Path;
3
4use crate::db;
5
6pub fn run(root: &Path, id: &str, json: bool) -> Result<()> {
7 let store = db::open(root)?;
8 let task_id = db::resolve_task_id(&store, id, true)?;
9 let task = store
10 .get_task(&task_id, true)?
11 .ok_or_else(|| anyhow::anyhow!("task {id} not found"))?;
12
13 if json {
14 println!("{}", serde_json::to_string(&task)?);
15 return Ok(());
16 }
17
18 let c = crate::color::stdout_theme();
19
20 println!(
21 "{}# {}{} {}[{}]{}",
22 c.bold,
23 task.title,
24 c.reset,
25 c.yellow,
26 db::status_label(task.status),
27 c.reset
28 );
29
30 if !task.description.is_empty() {
31 println!();
32 println!("{}", task.description);
33 }
34
35 println!();
36 println!(
37 "{}{}{} · {} · {}{}{} priority · {}{}{} effort",
38 c.bold,
39 task.id,
40 c.reset,
41 task.task_type,
42 c.red,
43 db::priority_label(task.priority),
44 c.reset,
45 c.blue,
46 db::effort_label(task.effort),
47 c.reset,
48 );
49
50 if !task.labels.is_empty() {
51 println!("labels: {}", task.labels.join(", "));
52 }
53
54 let blockers = db::partition_blockers(&store, &task.blockers)?;
55 let total = blockers.open.len() + blockers.resolved.len();
56 if total > 0 {
57 let label = if total == 1 { "blocker" } else { "blockers" };
58 if blockers.open.is_empty() {
59 // All closed: prefix with [all closed], no individual markers.
60 let ids: Vec<String> = blockers.resolved.iter().map(ToString::to_string).collect();
61 println!("{label}: [all closed] {}", ids.join(", "));
62 } else {
63 // Mixed or all open: annotate only the closed ones.
64 let mut ids: Vec<String> = blockers.open.iter().map(ToString::to_string).collect();
65 ids.extend(blockers.resolved.iter().map(|id| format!("{id} [closed]")));
66 println!("{label}: {}", ids.join(", "));
67 }
68 }
69
70 println!("created {} · updated {}", task.created_at, task.updated_at);
71
72 if !task.logs.is_empty() {
73 println!();
74 println!("--- log ---");
75 for log in task.logs {
76 println!("[{}] {}", log.timestamp, log.message);
77 }
78 }
79
80 Ok(())
81}