Skip to main content

mana_core/ops/
show.rs

1use std::path::{Path, PathBuf};
2
3use anyhow::{Context, Result};
4
5use crate::discovery::find_archived_unit;
6use crate::resolve::resolve_unit;
7use crate::unit::Unit;
8
9/// Result of loading a unit.
10pub struct GetResult {
11    pub unit: Unit,
12    pub path: PathBuf,
13}
14
15/// Load a unit by ID and return its full data.
16pub fn get(mana_dir: &Path, id: &str) -> Result<GetResult> {
17    let result = resolve_unit(mana_dir, id).or_else(|_| {
18        let unit_path =
19            find_archived_unit(mana_dir, id).with_context(|| format!("Unit not found: {}", id))?;
20        let unit =
21            Unit::from_file(&unit_path).with_context(|| format!("Failed to load unit: {}", id))?;
22        Ok::<_, anyhow::Error>(crate::resolve::ResolvedUnit {
23            unit,
24            path: unit_path,
25        })
26    })?;
27    Ok(GetResult {
28        unit: result.unit,
29        path: result.path,
30    })
31}
32
33#[cfg(test)]
34mod tests {
35    use super::*;
36    use crate::ops::create::{self, tests::minimal_params};
37    use crate::util::title_to_slug;
38    use std::fs;
39    use tempfile::TempDir;
40
41    fn setup() -> (TempDir, PathBuf) {
42        let dir = TempDir::new().unwrap();
43        let bd = dir.path().join(".mana");
44        fs::create_dir(&bd).unwrap();
45        crate::config::Config {
46            project: "test".to_string(),
47            next_id: 1,
48            auto_close_parent: true,
49            run: None,
50            plan: None,
51            max_loops: 10,
52            max_concurrent: 4,
53            poll_interval: 30,
54            extends: vec![],
55            rules_file: None,
56            file_locking: false,
57            worktree: false,
58            on_close: None,
59            on_fail: None,
60            verify_timeout: None,
61            review: None,
62            user: None,
63            user_email: None,
64            auto_commit: false,
65            commit_template: None,
66            research: None,
67            run_model: None,
68            plan_model: None,
69            review_model: None,
70            research_model: None,
71            batch_verify: false,
72            memory_reserve_mb: 0,
73            notify: None,
74        }
75        .save(&bd)
76        .unwrap();
77        (dir, bd)
78    }
79
80    #[test]
81    fn get_existing() {
82        let (_dir, bd) = setup();
83        create::create(&bd, minimal_params("My task")).unwrap();
84        let r = get(&bd, "1").unwrap();
85        assert_eq!(r.unit.title, "My task");
86        assert!(r.path.exists());
87    }
88
89    #[test]
90    fn get_archived_when_active_missing() {
91        let (_dir, bd) = setup();
92        let archive_dir = bd.join("archive/2026/04");
93        fs::create_dir_all(&archive_dir).unwrap();
94
95        let mut unit = Unit::new("1", "Archived task");
96        unit.is_archived = true;
97        let slug = title_to_slug(&unit.title);
98        let archived_path = archive_dir.join(format!("1-{}.md", slug));
99        unit.to_file(&archived_path).unwrap();
100
101        let r = get(&bd, "1").unwrap();
102        assert_eq!(r.unit.title, "Archived task");
103        assert_eq!(r.path, archived_path);
104        assert!(r.unit.is_archived);
105    }
106
107    #[test]
108    fn get_nonexistent() {
109        let (_dir, bd) = setup();
110        assert!(get(&bd, "99").is_err());
111    }
112}