1#![allow(hidden_glob_reexports)]
9
10pub mod commands;
11pub mod noun_verb_integration;
12pub mod telemetry;
13pub mod types;
14pub mod utils;
15
16use crate::error::Result;
17use clap::Parser;
18use std::path::PathBuf;
19use tracing::error;
20
21use self::commands::run::run_tests_with_shard_and_report;
23use self::types::{Cli, Commands};
24use self::utils::setup_logging;
25
26use self::commands::health::system_health_check;
28use self::commands::init::init_project;
29use self::commands::report::generate_report;
30use self::commands::validate::validate_config;
31
32pub async fn run_cli() -> Result<()> {
36 let cli = Cli::parse();
37
38 setup_logging(cli.verbose)?;
40
41 let result = match cli.command {
42 Commands::Run {
43 paths,
44 parallel,
45 jobs,
46 fail_fast,
47 watch,
48 force,
49 shard,
50 digest,
51 report_junit,
52 validate,
53 otel_exporter,
54 otel_endpoint,
55 live_check,
56 validation_mode,
57 registry_path,
58 otlp_port,
59 admin_port,
60 diagnostic_format,
61 stop_timeout,
62 } => {
63 let should_validate = validate || live_check;
65
66 let config = crate::cli::types::CliConfig {
67 parallel,
68 jobs,
69 format: cli.format.clone(),
70 fail_fast,
71 watch,
72 verbose: cli.verbose,
73 force,
74 digest,
75 validate: should_validate,
76 };
77
78 let paths_to_run = if let Some(paths) = paths {
80 paths
81 } else {
82 vec![PathBuf::from(".")]
84 };
85
86 let _ = (
90 validation_mode,
91 registry_path,
92 otlp_port,
93 admin_port,
94 diagnostic_format,
95 stop_timeout,
96 );
97
98 run_tests_with_shard_and_report(
99 &paths_to_run,
100 &config,
101 shard,
102 report_junit.as_deref(),
103 &otel_exporter,
104 otel_endpoint.as_deref(),
105 )
106 .await
107 }
108
109 Commands::Validate { files } => {
110 for file in files {
111 validate_config(&file)?;
112 }
113 Ok(())
114 }
115
116 Commands::Init { force, config } => {
117 init_project(force, config)?;
118 Ok(())
119 }
120
121 Commands::Template {
122 template,
123 name,
124 output,
125 } => {
126 let template_result = match template.as_str() {
128 "otel" => Some((generate_otel_template()?, "OTEL validation template")),
129 "matrix" => Some((generate_matrix_template()?, "Matrix testing template")),
130 "macros" | "macro-library" => {
131 Some((generate_macro_library()?, "Tera macro library"))
132 }
133 "full-validation" | "validation" => Some((
134 generate_full_validation_template()?,
135 "Full validation template",
136 )),
137 "deterministic" => Some((
138 generate_deterministic_template()?,
139 "Deterministic testing template",
140 )),
141 "lifecycle-matcher" => {
142 Some((generate_lifecycle_matcher()?, "Lifecycle matcher template"))
143 }
144 _ => None,
145 };
146
147 if let Some((content, description)) = template_result {
148 if let Some(output_path) = output {
150 std::fs::write(&output_path, &content).map_err(|e| {
151 crate::error::CleanroomError::io_error(format!(
152 "Failed to write template to {}: {}",
153 output_path.display(),
154 e
155 ))
156 })?;
157 println!("✓ {} generated: {}", description, output_path.display());
158 } else {
159 println!("{}", content);
160 }
161 Ok(())
162 } else {
163 generate_from_template(&template, name.as_deref())?;
165 Ok(())
166 }
167 }
168
169 Commands::Plugins => {
170 list_plugins()?;
171 Ok(())
172 }
173
174 Commands::Services { command } => match command {
175 ServiceCommands::Status => {
176 show_service_status().await?;
177 Ok(())
178 }
179 ServiceCommands::Logs { service, lines } => {
180 show_service_logs(&service, lines).await?;
181 Ok(())
182 }
183 ServiceCommands::Restart { service } => {
184 restart_service(&service).await?;
185 Ok(())
186 }
187 #[cfg(feature = "ai")]
188 ServiceCommands::AiManage {
189 auto_scale: _,
190 predict_load: _,
191 optimize_resources: _,
192 horizon_minutes: _,
193 service: _,
194 } => Err(crate::error::CleanroomError::validation_error(
195 "AI service management is not available in this version.",
196 )),
197 },
198
199 Commands::Report {
200 input,
201 output,
202 format,
203 } => {
204 let format_str = match format {
205 ReportFormat::Html => "html",
206 ReportFormat::Markdown => "markdown",
207 ReportFormat::Json => "json",
208 ReportFormat::Pdf => "pdf",
209 };
210 generate_report(input.as_ref(), output.as_ref(), format_str).await?;
211 Ok(())
212 }
213
214 Commands::SelfTest {
215 suite,
216 report,
217 otel_exporter,
218 otel_endpoint,
219 } => {
220 run_self_tests(suite, report, otel_exporter, otel_endpoint).await?;
221 Ok(())
222 }
223
224 #[cfg(feature = "ai")]
225 Commands::AiOrchestrate {
226 paths: _,
227 predict_failures: _,
228 auto_optimize: _,
229 confidence_threshold: _,
230 max_workers: _,
231 } => Err(crate::error::CleanroomError::validation_error(
232 "AI orchestration is not available in this version.",
233 )),
234
235 #[cfg(feature = "ai")]
236 Commands::AiPredict {
237 analyze_history: _,
238 predict_failures: _,
239 recommendations: _,
240 format: _,
241 } => Err(crate::error::CleanroomError::validation_error(
242 "AI predictive analytics is not available in this version.",
243 )),
244
245 #[cfg(feature = "ai")]
246 Commands::AiOptimize {
247 execution_order: _,
248 resource_allocation: _,
249 parallel_execution: _,
250 auto_apply: _,
251 } => Err(crate::error::CleanroomError::validation_error(
252 "AI test optimization is not available in this version.",
253 )),
254
255 #[cfg(feature = "ai")]
256 Commands::AiReal { analyze: _ } => Err(crate::error::CleanroomError::validation_error(
257 "AI real-time analysis is not available in this version.",
258 )),
259
260 Commands::Health { verbose } => system_health_check(verbose).await,
261
262 Commands::Fmt {
263 files,
264 check,
265 verify,
266 } => {
267 format_files(&files, check, verify)?;
268 Ok(())
269 }
270
271 Commands::DryRun { files, verbose } => {
272 use crate::CleanroomError;
273 let file_refs: Vec<_> = files.iter().map(|p| p.as_path()).collect();
274 let results = dry_run_validate(file_refs, verbose)?;
275
276 let failed_count = results.iter().filter(|r| !r.valid).count();
278
279 if failed_count > 0 {
281 return Err(CleanroomError::validation_error(format!(
282 "{} file(s) failed validation",
283 failed_count
284 )));
285 }
286
287 Ok(())
288 }
289
290 Commands::Dev {
291 paths,
292 debounce_ms,
293 clear,
294 only,
295 timebox,
296 } => {
297 let config = crate::cli::types::CliConfig {
298 format: cli.format.clone(),
299 verbose: cli.verbose,
300 ..Default::default()
301 };
302
303 run_dev_mode_with_filters(paths, debounce_ms, clear, only, timebox, config).await
304 }
305
306 Commands::Lint {
307 files,
308 format,
309 deny_warnings,
310 } => {
311 let file_refs: Vec<_> = files.iter().map(|p| p.as_path()).collect();
312
313 let format_str = match format {
315 crate::cli::types::LintFormat::Human => "human",
316 crate::cli::types::LintFormat::Json => "json",
317 crate::cli::types::LintFormat::Github => "github",
318 };
319
320 lint_files(file_refs, format_str, deny_warnings)?;
322
323 Ok(())
324 }
325
326 Commands::Diff {
327 baseline,
328 current,
329 format,
330 only_changes,
331 } => {
332 let format_str = match format {
334 crate::cli::types::DiffFormat::Tree => "tree",
335 crate::cli::types::DiffFormat::Json => "json",
336 crate::cli::types::DiffFormat::SideBySide => "side-by-side",
337 };
338
339 let result = diff_traces(&baseline, ¤t, format_str, only_changes)?;
340
341 if result.added_count > 0 || result.removed_count > 0 || result.modified_count > 0 {
343 std::process::exit(1);
344 }
345
346 Ok(())
347 }
348
349 Commands::Record { paths, output } => run_record(paths, output).await,
350
351 #[cfg(feature = "ai")]
352 Commands::AiMonitor {
353 interval: _,
354 anomaly_threshold: _,
355 ai_alerts: _,
356 anomaly_detection: _,
357 proactive_healing: _,
358 webhook_url: _,
359 } => Err(crate::error::CleanroomError::validation_error(
360 "AI monitoring is not available in this version.",
361 )),
362
363 Commands::Pull {
365 paths,
366 parallel,
367 jobs,
368 } => pull_images(paths, parallel, jobs).await,
369
370 Commands::Graph {
371 trace,
372 format,
373 highlight_missing,
374 filter,
375 } => visualize_graph(&trace, &format, highlight_missing, filter.as_deref()),
376
377 Commands::Repro {
378 baseline,
379 verify_digest,
380 output,
381 } => reproduce_baseline(&baseline, verify_digest, output.as_deref()).await,
382
383 Commands::RedGreen {
384 paths,
385 expect,
386 verify_red,
387 verify_green,
388 } => {
389 let (should_verify_red, should_verify_green) = match expect {
391 Some(crate::cli::types::TddState::Red) => (true, false),
392 Some(crate::cli::types::TddState::Green) => (false, true),
393 None => (verify_red, verify_green),
394 };
395 run_red_green_validation(&paths, should_verify_red, should_verify_green).await
396 }
397
398 Commands::Render {
399 template,
400 map,
401 output,
402 show_vars,
403 } => {
404 let map_str = if map.is_empty() {
406 "{}".to_string()
407 } else {
408 let mut json_map = std::collections::HashMap::new();
410 for pair in &map {
411 if let Some((key, value)) = pair.split_once('=') {
412 json_map.insert(
413 key.to_string(),
414 serde_json::Value::String(value.to_string()),
415 );
416 }
417 }
418 serde_json::to_string(&json_map).map_err(|e| {
419 crate::error::CleanroomError::serialization_error(format!(
420 "Failed to serialize map: {}",
421 e
422 ))
423 })?
424 };
425 render_template_with_vars(&template, &map_str, output.as_deref(), show_vars)?;
426 Ok(())
427 }
428
429 Commands::Spans {
430 trace,
431 grep,
432 format,
433 show_attrs,
434 show_events,
435 } => filter_spans(&trace, grep.as_deref(), &format, show_attrs, show_events),
436
437 Commands::Collector { command } => match command {
438 crate::cli::types::CollectorCommands::Up {
439 image,
440 http_port,
441 grpc_port,
442 detach,
443 } => start_collector(&image, http_port, grpc_port, detach).await,
444 crate::cli::types::CollectorCommands::Down { volumes } => stop_collector(volumes).await,
445 crate::cli::types::CollectorCommands::Status => show_collector_status().await,
446 crate::cli::types::CollectorCommands::Logs { lines, follow } => {
447 show_collector_logs(lines, follow).await
448 }
449 },
450
451 Commands::Analyze { test_file, traces } => {
452 use crate::cli::commands::analyze::analyze_traces;
453
454 match analyze_traces(&test_file, traces.as_deref()) {
455 Ok(report) => {
456 println!("{}", report.format_report());
457
458 if !report.is_success() {
460 std::process::exit(1);
461 }
462 Ok(())
463 }
464 Err(e) => {
465 eprintln!("Error analyzing traces: {}", e);
466 std::process::exit(1);
467 }
468 }
469 }
470
471 Commands::LiveCheck { command } => match command {
472 crate::cli::types::LiveCheckCommands::Status => show_status(),
473 crate::cli::types::LiveCheckCommands::ValidateRegistry { registry } => {
474 validate_registry(®istry)
475 }
476 crate::cli::types::LiveCheckCommands::TestWeaver => test_weaver(),
477 crate::cli::types::LiveCheckCommands::Modes => show_modes(),
478 crate::cli::types::LiveCheckCommands::Version => show_version(),
479 },
480 };
481
482 if let Err(e) = result {
483 error!("Command failed: {}", e);
484 std::process::exit(1);
485 }
486
487 Ok(())
488}
489
490pub use commands::*;
492pub use types::*;