metis_docs_cli/commands/
archive.rs

1use crate::workspace;
2use anyhow::Result;
3use clap::Args;
4use metis_core::application::services::workspace::ArchiveService;
5
6#[derive(Args)]
7pub struct ArchiveCommand {
8    /// Document short code to archive (e.g., PROJ-V-0001)
9    pub short_code: String,
10
11    /// Document type (vision, strategy, initiative, task, adr) - auto-detected if not provided
12    #[arg(short = 't', long)]
13    pub document_type: Option<String>,
14}
15
16impl ArchiveCommand {
17    pub async fn execute(&self) -> Result<()> {
18        // 1. Validate we're in a metis workspace
19        let (workspace_exists, metis_dir) = workspace::has_metis_vault();
20        if !workspace_exists {
21            anyhow::bail!("Not in a Metis workspace. Run 'metis init' to create one.");
22        }
23        let metis_dir = metis_dir.unwrap();
24
25        // 2. Create the archive service with database optimization
26        let db = metis_core::dal::Database::new(&metis_dir.join("metis.db").to_string_lossy())
27            .map_err(|e| anyhow::anyhow!("Database initialization failed: {}", e))?;
28        let mut db_service =
29            metis_core::application::services::DatabaseService::new(db.into_repository());
30        let archive_service = ArchiveService::new(&metis_dir);
31
32        // 2.5. Sync workspace to ensure documents are in database
33        let mut sync_service = metis_core::application::services::SyncService::new(&mut db_service)
34            .with_workspace_dir(&metis_dir);
35        sync_service
36            .sync_directory(&metis_dir)
37            .await
38            .map_err(|e| anyhow::anyhow!("Failed to sync workspace: {}", e))?;
39
40        // 3. Archive the document and its children using database-optimized method
41        let archive_result = archive_service
42            .archive_document_by_short_code(&self.short_code, &mut db_service)
43            .await?;
44
45        // 4. Report results
46        println!("✓ Archived {} documents:", archive_result.total_archived);
47        for doc in archive_result.archived_documents {
48            println!("  - {} ({})", doc.document_id, doc.document_type);
49        }
50
51        // 5. Archive completed
52        println!("Archive completed.");
53
54        Ok(())
55    }
56}
57
58#[cfg(test)]
59mod tests {
60    use super::*;
61    use crate::commands::InitCommand;
62    use tempfile::tempdir;
63
64    #[tokio::test]
65    async fn test_archive_command_no_workspace() {
66        let temp_dir = tempdir().unwrap();
67        let original_dir = std::env::current_dir().ok();
68
69        // Change to temp directory without workspace
70        if std::env::set_current_dir(temp_dir.path()).is_err() {
71            return; // Skip test if we can't change directory
72        }
73
74        let cmd = ArchiveCommand {
75            short_code: "TEST-T-0001".to_string(),
76            document_type: None,
77        };
78
79        let result = cmd.execute().await;
80
81        // Always restore original directory first
82        if let Some(original) = original_dir {
83            let _ = std::env::set_current_dir(&original);
84        }
85
86        assert!(result.is_err());
87        assert!(result
88            .unwrap_err()
89            .to_string()
90            .contains("Not in a Metis workspace"));
91    }
92
93    #[tokio::test]
94    async fn test_archive_document_not_found() {
95        let temp_dir = tempdir().unwrap();
96        let original_dir = std::env::current_dir().ok();
97
98        // Change to temp directory
99        std::env::set_current_dir(temp_dir.path()).unwrap();
100
101        // Create workspace
102        let init_cmd = InitCommand {
103            name: Some("Test Project".to_string()),
104            prefix: None,
105            preset: None,
106            strategies: None,
107            initiatives: None,
108        };
109        init_cmd.execute().await.unwrap();
110
111        let cmd = ArchiveCommand {
112            short_code: "TEST-T-9999".to_string(),
113            document_type: None,
114        };
115
116        let result = cmd.execute().await;
117
118        // Always restore original directory first
119        if let Some(original) = original_dir {
120            let _ = std::env::set_current_dir(&original);
121        }
122
123        assert!(result.is_err());
124        assert!(result.unwrap_err().to_string().contains("not found"));
125    }
126
127    #[tokio::test]
128    async fn test_archive_vision_document() {
129        let temp_dir = tempdir().unwrap();
130        let original_dir = std::env::current_dir().ok();
131
132        // Change to temp directory
133        std::env::set_current_dir(temp_dir.path()).unwrap();
134
135        // Create workspace
136        let init_cmd = InitCommand {
137            name: Some("Test Project".to_string()),
138            prefix: None,
139            preset: None,
140            strategies: None,
141            initiatives: None,
142        };
143        init_cmd.execute().await.unwrap();
144
145        let metis_dir = temp_dir.path().join(".metis");
146        let vision_path = metis_dir.join("vision.md");
147        let archived_vision_path = metis_dir.join("archived").join("vision.md");
148
149        // Verify vision exists
150        assert!(vision_path.exists());
151        assert!(!archived_vision_path.exists());
152
153        let cmd = ArchiveCommand {
154            short_code: "TEST-V-0001".to_string(),
155            document_type: Some("vision".to_string()),
156        };
157
158        let result = cmd.execute().await;
159
160        // Always restore original directory first
161        if let Some(original) = original_dir {
162            let _ = std::env::set_current_dir(&original);
163        }
164
165        assert!(result.is_ok(), "Archive failed: {:?}", result.err());
166
167        // Verify file was moved and marked as archived
168        assert!(!vision_path.exists());
169        assert!(archived_vision_path.exists());
170
171        // Verify archived flag was set
172        let archived_content = std::fs::read_to_string(&archived_vision_path).unwrap();
173        assert!(archived_content.contains("archived: true"));
174    }
175}