Skip to main content

gn_cli/commands/
resolve.rs

1use anyhow::{anyhow, Result};
2use clap::Args;
3use gn_core::{NoteStatus, NotesEngine};
4
5#[derive(Args)]
6pub struct ResolveArgs {
7    /// Note ID, index number (1, 2, ...), or "latest" / "^" (interactive picker if omitted)
8    pub id: Option<String>,
9
10    /// New status (approved, rejected, resolved, open)
11    #[arg(short, long, default_value = "resolved")]
12    pub status: String,
13}
14
15pub fn run(args: &ResolveArgs) -> Result<()> {
16    let engine = NotesEngine::new(".");
17    let namespaces = vec!["comments", "review", "todos"];
18
19    let mut all_notes = Vec::new();
20
21    for ns in &namespaces {
22        let namespace_enum = gn_core::Namespace::Custom(ns.to_string());
23        if let Ok(notes) = engine.read_notes(&namespace_enum) {
24            all_notes.extend(notes);
25        }
26    }
27
28    if all_notes.is_empty() {
29        return Err(anyhow!("No notes found in repository."));
30    }
31
32    // Interactive picker if ID omitted
33    let found_note = match &args.id {
34        None => match super::picker::pick_note("Select note to resolve:", &all_notes)? {
35            Some(n) => Some(n.clone()),
36            None => {
37                println!("Cancelled.");
38                return Ok(());
39            }
40        },
41        Some(raw_id) => {
42            let target = raw_id.trim().trim_start_matches('#');
43            if target.eq_ignore_ascii_case("latest") || target == "^" {
44                all_notes.last().cloned()
45            } else if let Ok(idx) = target.parse::<usize>() {
46                if idx >= 1 && idx <= all_notes.len() {
47                    Some(all_notes[idx - 1].clone())
48                } else {
49                    None
50                }
51            } else {
52                all_notes
53                    .iter()
54                    .find(|n| n.id.to_string().starts_with(target))
55                    .cloned()
56            }
57        }
58    };
59
60    let mut note = found_note.ok_or_else(|| anyhow!("Target note not found"))?;
61
62    let status = match args.status.to_lowercase().as_str() {
63        "approved" => NoteStatus::Approved,
64        "rejected" => NoteStatus::Rejected,
65        "resolved" => NoteStatus::Resolved,
66        "open" => NoteStatus::Open,
67        _ => {
68            return Err(anyhow!(
69                "Invalid status. Use approved, rejected, resolved, or open."
70            ))
71        }
72    };
73
74    note.status = status;
75    engine.write_note(&note)?;
76
77    println!(
78        "\x1b[32m✔\x1b[0m Note \x1b[36m{}\x1b[0m marked as \x1b[1m{:?}\x1b[0m",
79        &note.id.to_string()[..8],
80        note.status
81    );
82
83    Ok(())
84}