1use anyhow::Result;
2use clap::{Parser, Subcommand};
3use tracing_subscriber::filter::LevelFilter;
4
5use crate::commands::{
6 ArchiveCommand, ConfigCommand, CreateCommand, InitCommand, ListCommand, McpCommand,
7 SearchCommand, StatusCommand, SyncCommand, TransitionCommand, TuiCommand, ValidateCommand,
8};
9
10#[derive(Parser)]
11#[command(name = "metis")]
12#[command(about = "A document management system for strategic planning")]
13#[command(version)]
14pub struct Cli {
15 #[arg(short, long, action = clap::ArgAction::Count)]
17 pub verbose: u8,
18
19 #[command(subcommand)]
20 pub command: Commands,
21}
22
23#[derive(Subcommand)]
24pub enum Commands {
25 Init(InitCommand),
27 Sync(SyncCommand),
29 Create(CreateCommand),
31 Search(SearchCommand),
33 Transition(TransitionCommand),
35 List(ListCommand),
37 Status(StatusCommand),
39 Archive(ArchiveCommand),
41 Validate(ValidateCommand),
43 Tui(TuiCommand),
45 Mcp(McpCommand),
47 Config(ConfigCommand),
49}
50
51impl Cli {
52 pub fn init_logging(&self) {
53 let level = match self.verbose {
54 0 => LevelFilter::WARN,
55 1 => LevelFilter::INFO,
56 2 => LevelFilter::DEBUG,
57 _ => LevelFilter::TRACE,
58 };
59
60 tracing_subscriber::fmt()
61 .with_max_level(level)
62 .with_target(false)
63 .init();
64 }
65
66 pub async fn execute(&self) -> Result<()> {
67 match &self.command {
68 Commands::Init(cmd) => cmd.execute().await,
69 Commands::Sync(cmd) => cmd.execute().await,
70 Commands::Create(cmd) => cmd.execute().await,
71 Commands::Search(cmd) => cmd.execute().await,
72 Commands::Transition(cmd) => cmd.execute().await,
73 Commands::List(cmd) => cmd.execute().await,
74 Commands::Status(cmd) => cmd.execute().await,
75 Commands::Archive(cmd) => cmd.execute().await,
76 Commands::Validate(cmd) => cmd.execute().await,
77 Commands::Tui(cmd) => cmd.execute().await,
78 Commands::Mcp(cmd) => cmd.execute().await,
79 Commands::Config(cmd) => cmd.execute().await,
80 }
81 }
82}
83
84#[cfg(test)]
85mod tests {
86 use super::*;
87 use crate::commands::create::CreateCommands;
88 use crate::commands::{
89 ArchiveCommand, CreateCommand, ListCommand, SearchCommand, StatusCommand, SyncCommand,
90 TransitionCommand, ValidateCommand,
91 };
92 use std::fs;
93 use tempfile::tempdir;
94
95 #[tokio::test]
96 async fn test_comprehensive_cli_workflow() {
97 let temp_dir = tempdir().unwrap();
98 let original_dir = std::env::current_dir().ok();
99
100 std::env::set_current_dir(temp_dir.path()).unwrap();
102
103 let init_cmd = InitCommand {
105 name: Some("Integration Test Project".to_string()),
106 prefix: None,
107 preset: None,
108 strategies: None,
109 initiatives: None,
110 };
111 init_cmd
112 .execute()
113 .await
114 .expect("Failed to initialize project");
115
116 let metis_dir = temp_dir.path().join(".metis");
117 assert!(
118 metis_dir.exists(),
119 "Metis directory should exist after init"
120 );
121 assert!(
122 metis_dir.join("vision.md").exists(),
123 "Vision document should be created"
124 );
125
126 let sync_cmd = SyncCommand {};
128 sync_cmd.execute().await.expect("Failed to sync workspace");
129
130 let create_strategy_cmd = CreateCommand {
132 document_type: CreateCommands::Strategy {
133 title: "Test Strategy for Integration".to_string(),
134 vision: Some("integration-test-project".to_string()),
135 },
136 };
137 create_strategy_cmd
138 .execute()
139 .await
140 .expect("Failed to create strategy");
141
142 let create_initiative_cmd = CreateCommand {
144 document_type: CreateCommands::Initiative {
145 title: "Test Initiative".to_string(),
146 strategy: "TEST-S-0001".to_string(),
147 },
148 };
149 create_initiative_cmd
150 .execute()
151 .await
152 .expect("Failed to create initiative");
153
154 let create_task_cmd = CreateCommand {
156 document_type: CreateCommands::Task {
157 title: "Test Task".to_string(),
158 initiative: "TEST-I-0001".to_string(),
159 },
160 };
161 create_task_cmd
162 .execute()
163 .await
164 .expect("Failed to create task");
165
166 let create_adr_cmd = CreateCommand {
168 document_type: CreateCommands::Adr {
169 title: "Test Architecture Decision".to_string(),
170 },
171 };
172 create_adr_cmd
173 .execute()
174 .await
175 .expect("Failed to create ADR");
176
177 let adrs_dir = metis_dir.join("adrs");
179 let adr_files: Vec<_> = fs::read_dir(&adrs_dir)
180 .unwrap()
181 .filter_map(|entry| entry.ok())
182 .filter(|entry| entry.path().extension().is_some_and(|ext| ext == "md"))
183 .collect();
184 assert!(!adr_files.is_empty(), "ADR file should be created");
185
186 let sync_cmd2 = SyncCommand {};
188 sync_cmd2
189 .execute()
190 .await
191 .expect("Failed to sync after creating documents");
192
193 let transition_vision_cmd = TransitionCommand {
195 short_code: "TEST-V-0001".to_string(),
196 document_type: Some("vision".to_string()),
197 phase: Some("review".to_string()),
198 };
199 transition_vision_cmd
200 .execute()
201 .await
202 .expect("Failed to transition vision");
203
204 let transition_strategy_to_design_cmd = TransitionCommand {
206 short_code: "TEST-S-0001".to_string(),
207 document_type: Some("strategy".to_string()),
208 phase: Some("design".to_string()),
209 };
210 transition_strategy_to_design_cmd
211 .execute()
212 .await
213 .expect("Failed to transition strategy to design");
214
215 let transition_strategy_to_ready_cmd = TransitionCommand {
216 short_code: "TEST-S-0001".to_string(),
217 document_type: Some("strategy".to_string()),
218 phase: Some("ready".to_string()),
219 };
220 transition_strategy_to_ready_cmd
221 .execute()
222 .await
223 .expect("Failed to transition strategy to ready");
224
225 let transition_strategy_to_active_cmd = TransitionCommand {
226 short_code: "TEST-S-0001".to_string(),
227 document_type: Some("strategy".to_string()),
228 phase: Some("active".to_string()),
229 };
230 transition_strategy_to_active_cmd
231 .execute()
232 .await
233 .expect("Failed to transition strategy to active");
234
235 let transition_task_to_active_cmd = TransitionCommand {
237 short_code: "TEST-T-0001".to_string(),
238 document_type: Some("task".to_string()),
239 phase: Some("active".to_string()),
240 };
241 transition_task_to_active_cmd
242 .execute()
243 .await
244 .expect("Failed to transition task to active");
245
246 let transition_task_to_completed_cmd = TransitionCommand {
247 short_code: "TEST-T-0001".to_string(),
248 document_type: Some("task".to_string()),
249 phase: Some("completed".to_string()),
250 };
251 transition_task_to_completed_cmd
252 .execute()
253 .await
254 .expect("Failed to transition task to completed");
255
256 let archive_task_cmd = ArchiveCommand {
258 short_code: "TEST-T-0001".to_string(),
259 document_type: Some("task".to_string()),
260 };
261 archive_task_cmd
262 .execute()
263 .await
264 .expect("Failed to archive task");
265
266 let list_cmd = ListCommand {
268 document_type: None,
269 phase: None,
270 all: true,
271 include_archived: true,
272 };
273 list_cmd.execute().await.expect("Failed to list documents");
274
275 let status_cmd = StatusCommand {
277 include_archived: false,
278 };
279 status_cmd.execute().await.expect("Failed to get status");
280
281 let search_cmd = SearchCommand {
283 query: "test".to_string(),
284 limit: 10,
285 };
286 search_cmd
287 .execute()
288 .await
289 .expect("Failed to search documents");
290
291 let validate_cmd = ValidateCommand {
293 file_path: metis_dir.join("vision.md"),
294 };
295 validate_cmd
296 .execute()
297 .await
298 .expect("Failed to validate document");
299
300 if let Some(original) = original_dir {
302 let _ = std::env::set_current_dir(&original);
303 }
304
305 println!("✓ Comprehensive CLI workflow test completed successfully");
306 }
307}