1use crate::codegen::pipeline::{GenerationPipeline, LlmService, RuleType};
22use crate::codegen::ux::{
23 format_duration, info_message, print_section, success_message, warning_message,
24 ProgressIndicator,
25};
26use crate::codegen::{DependencyValidator, IncrementalCache, MarketplaceValidator, ProofCarrier};
27use crate::drift::DriftDetector;
28use crate::manifest::{ManifestParser, ManifestValidator};
29use crate::poka_yoke::{AndonSignal, CriticalError, QualityGateRunner};
30use crate::utils::error::{Error, Result};
31use crate::validation::PreFlightValidator;
32use serde::Serialize;
33use std::path::{Path, PathBuf};
34use std::time::Instant;
35
36#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Default)]
42pub enum OutputFormat {
43 #[default]
45 Text,
46 Json,
48}
49
50impl std::str::FromStr for OutputFormat {
51 type Err = String;
52
53 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
54 match s.to_lowercase().as_str() {
55 "text" => Ok(OutputFormat::Text),
56 "json" => Ok(OutputFormat::Json),
57 _ => Err("Invalid format".to_string()),
58 }
59 }
60}
61
62impl std::fmt::Display for OutputFormat {
63 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
64 match self {
65 OutputFormat::Text => write!(f, "text"),
66 OutputFormat::Json => write!(f, "json"),
67 }
68 }
69}
70
71#[derive(Debug, Clone, Copy, Default)]
73pub struct ModeFlags {
74 pub validate_only: bool,
76 pub dry_run: bool,
78 pub watch: bool,
80}
81
82#[derive(Debug, Clone, Copy, Default)]
84pub struct BehaviorFlags {
85 pub verbose: bool,
87 pub force: bool,
89 pub audit: bool,
91}
92
93#[derive(Debug, Clone, Copy, Default)]
95pub struct SyncFlags {
96 pub mode: ModeFlags,
98 pub behavior: BehaviorFlags,
100}
101
102pub struct SyncOptions {
104 pub manifest_path: PathBuf,
106
107 pub output_dir: Option<PathBuf>,
109
110 pub cache_dir: Option<PathBuf>,
112
113 pub use_cache: bool,
115
116 pub flags: SyncFlags,
118
119 pub output_format: OutputFormat,
121
122 pub selected_rules: Option<Vec<String>>,
124
125 pub a2a_stage: Option<String>,
128
129 pub ontology_path: Option<PathBuf>,
131
132 pub llm_service: Option<Box<dyn LlmService>>,
136
137 pub timeout_ms: Option<u64>,
139}
140
141impl Default for SyncOptions {
142 fn default() -> Self {
143 Self {
144 manifest_path: PathBuf::from("ggen.toml"),
145 output_dir: None,
146 cache_dir: None,
147 use_cache: true,
148 flags: SyncFlags::default(),
149 output_format: OutputFormat::default(),
150 selected_rules: None,
151 a2a_stage: None,
152 ontology_path: None,
153 llm_service: None,
154 timeout_ms: None,
155 }
156 }
157}
158
159impl std::fmt::Debug for SyncOptions {
160 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
161 f.debug_struct("SyncOptions")
162 .field("manifest_path", &self.manifest_path)
163 .field("output_dir", &self.output_dir)
164 .field("cache_dir", &self.cache_dir)
165 .field("flags", &self.flags)
166 .field("output_format", &self.output_format)
167 .field("selected_rules", &self.selected_rules)
168 .field("a2a_stage", &self.a2a_stage)
169 .field("ontology_path", &self.ontology_path)
170 .field("llm_service", &"<dyn LlmService>")
171 .field("timeout_ms", &self.timeout_ms)
172 .finish()
173 }
174}
175
176impl Clone for SyncOptions {
177 fn clone(&self) -> Self {
178 Self {
179 manifest_path: self.manifest_path.clone(),
180 output_dir: self.output_dir.clone(),
181 cache_dir: self.cache_dir.clone(),
182 use_cache: self.use_cache,
183 flags: self.flags,
184 output_format: self.output_format,
185 selected_rules: self.selected_rules.clone(),
186 a2a_stage: self.a2a_stage.clone(),
187 ontology_path: self.ontology_path.clone(),
188 timeout_ms: self.timeout_ms,
189 llm_service: None, }
191 }
192}
193
194impl SyncOptions {
195 pub fn new() -> Self {
197 Self::default()
198 }
199}
200
201#[derive(Debug, Clone, Serialize, Default)]
207pub struct SyncResult {
208 pub status: String,
210
211 pub files_synced: usize,
213
214 pub duration_ms: u64,
216
217 pub files: Vec<SyncedFileInfo>,
219
220 pub inference_rules_executed: usize,
222
223 pub generation_rules_executed: usize,
225
226 #[serde(skip_serializing_if = "Option::is_none")]
228 pub audit_trail: Option<String>,
229
230 #[serde(skip_serializing_if = "Option::is_none")]
232 pub error: Option<String>,
233
234 #[serde(skip_serializing_if = "Option::is_none")]
236 pub recovery: Option<String>,
237
238 #[serde(skip_serializing_if = "Option::is_none")]
240 pub andon_signal: Option<serde_json::Value>,
241}
242
243#[derive(Debug, Clone, Serialize, Default)]
245pub struct SyncedFileInfo {
246 pub path: String,
248
249 pub size_bytes: usize,
251
252 pub action: String,
254
255 pub produced_by: String,
257}
258
259#[derive(Debug, Clone, Serialize)]
261pub struct ValidationCheck {
262 pub check: String,
264
265 pub passed: bool,
267
268 #[serde(skip_serializing_if = "Option::is_none")]
270 pub details: Option<String>,
271}
272
273pub struct SyncExecutor {
282 options: SyncOptions,
283 start_time: Instant,
284}
285
286impl SyncExecutor {
287 pub fn new(options: SyncOptions) -> Self {
289 Self {
290 options,
291 start_time: Instant::now(),
292 }
293 }
294
295 pub fn with_llm_service(mut self, service: Option<Box<dyn LlmService>>) -> Self {
300 self.options.llm_service = service;
301 self
302 }
303
304 pub fn execute(mut self) -> Result<SyncResult> {
308 let base_path = self
310 .options
311 .manifest_path
312 .parent()
313 .unwrap_or(Path::new("."));
314
315 let preflight = PreFlightValidator::for_sync(base_path)
316 .with_llm_check(false) .with_template_check(false) .with_git_check(false);
319
320 if let Err(e) = preflight.validate(None) {
322 if self.options.flags.behavior.verbose {
323 eprintln!("{}", warning_message(&format!("Pre-flight warning: {}", e)));
324 }
325 } else if self.options.flags.behavior.verbose {
326 eprintln!("{}", success_message("Pre-flight checks passed"));
327 }
328
329 if !self.options.manifest_path.exists() {
331 let error_msg = format!(
332 "error[E0001]: Manifest not found\n --> {}",
333 self.options.manifest_path.display()
334 );
335 let andon = AndonSignal::manifest_error("ggen.toml", "File does not exist");
336 return Ok(self.create_error_result(&error_msg, Some(andon)));
337 }
338
339 self.check_and_warn_drift(base_path);
341
342 if self.options.flags.mode.watch {
344 return self.execute_watch_mode(&self.options.manifest_path);
345 }
346
347 let manifest_data = ManifestParser::parse(&self.options.manifest_path).map_err(|e| {
349 Error::new(&format!(
350 "error[E0001]: Manifest parse error\n --> {}\n |\n = error: {}\n = help: Check ggen.toml syntax and required fields",
351 self.options.manifest_path.display(),
352 e
353 ))
354 })?;
355
356 let base_path: PathBuf = self
358 .options
359 .manifest_path
360 .parent()
361 .unwrap_or(Path::new("."))
362 .to_path_buf();
363 let validator = ManifestValidator::new(&manifest_data, &base_path);
364 validator.validate().map_err(|e| {
365 Error::new(&format!(
366 "error[E0001]: Manifest validation failed\n --> {}\n |\n = error: {}\n = help: Fix validation errors before syncing",
367 self.options.manifest_path.display(),
368 e
369 ))
370 })?;
371
372 let dep_validator = DependencyValidator::validate_manifest(&manifest_data, &base_path)
374 .map_err(|e| {
375 Error::new(&format!(
376 "error[E0002]: Dependency validation failed\n |\n = error: {}\n = help: Fix missing ontology imports or circular dependencies",
377 e
378 ))
379 })?;
380
381 if dep_validator.has_cycles {
382 let error_msg = format!("error[E0002]: Circular dependency detected\n |\n = error: Inference rules have circular dependencies\n = cycles: {:?}", dep_validator.cycle_nodes);
383 let andon = AndonSignal::circular_dependency(vec![dep_validator.cycle_nodes.clone()]);
384 return Ok(self.create_error_result(&error_msg, Some(andon)));
385 }
386
387 if dep_validator.failed_checks > 0 {
388 let error_msg = format!(
389 "error[E0002]: {} dependency validation checks failed\n |\n = help: Common issues:\n = 1. Query file not found: Check ontology.source and ontology.imports paths\n = 2. Template file not found: Check generation.rules[].template paths\n = 3. Import cycle: Check if imported files reference each other\n = help: Run 'ggen validate' for detailed dependency analysis",
390 dep_validator.failed_checks
391 );
392 return Ok(self.create_error_result(&error_msg, None));
393 }
394
395 let gate_runner = QualityGateRunner::new();
397 gate_runner.run_all(&manifest_data, &base_path).map_err(|e| {
398 Error::new(&format!(
399 "error[E0004]: Quality gate validation failed\n |\n = error: {}\n = help: Fix validation errors before syncing",
400 e
401 ))
402 })?;
403
404 let marketplace_validator = MarketplaceValidator::new(160);
406 let pre_flight = marketplace_validator.pre_flight_check(&manifest_data).map_err(|e| {
407 Error::new(&format!(
408 "error[E0003]: Marketplace pre-flight validation failed\n |\n = error: {}\n = help: Review package dependencies and resolve high-risk items",
409 e
410 ))
411 })?;
412
413 if self.options.flags.behavior.verbose {
414 eprintln!(
415 "Pre-flight checks: {} validations, {} high-risk items detected",
416 pre_flight.validations.len(),
417 pre_flight.high_risks.len()
418 );
419 if !pre_flight.all_passed {
420 eprintln!(
421 "⚠ Warning: {} critical failures, {} warnings in packages",
422 pre_flight.critical_failures_count, pre_flight.warnings_count
423 );
424 }
425 }
426
427 if let Some(ref selected) = self.options.selected_rules {
429 let available_rules: Vec<&String> = manifest_data
430 .generation
431 .rules
432 .iter()
433 .map(|r| &r.name)
434 .collect();
435 for rule_name in selected {
436 if !available_rules.contains(&rule_name) {
437 return Err(Error::new(&format!(
438 "error[E0001]: Rule '{}' not found in manifest\n |\n = help: Available rules: {}",
439 rule_name,
440 available_rules
441 .iter()
442 .map(|r| r.as_str())
443 .collect::<Vec<_>>()
444 .join(", ")
445 )));
446 }
447 }
448 }
449
450 if self.options.flags.mode.validate_only {
452 self.execute_validate_only(&manifest_data, &base_path)
453 } else if self.options.flags.mode.dry_run {
454 self.execute_dry_run(&manifest_data)
455 } else {
456 self.execute_full_sync(&manifest_data, &base_path)
457 }
458 }
459
460 fn execute_validate_only(
462 &self, manifest_data: &crate::manifest::GgenManifest, base_path: &Path,
463 ) -> Result<SyncResult> {
464 if self.options.flags.behavior.verbose {
465 eprintln!("Validating ggen.toml...\n");
466 }
467
468 let mut validations = Vec::new();
469
470 validations.push(ValidationCheck {
472 check: "Manifest schema".to_string(),
473 passed: true,
474 details: None,
475 });
476
477 let dep_report = DependencyValidator::validate_manifest(manifest_data, base_path).ok();
479 let dep_passed = dep_report
480 .as_ref()
481 .is_some_and(|r| !r.has_cycles && r.failed_checks == 0);
482 validations.push(ValidationCheck {
483 check: "Dependencies".to_string(),
484 passed: dep_passed,
485 details: if let Some(report) = dep_report {
486 Some(format!(
487 "{}/{} checks passed",
488 report.passed_checks, report.total_checks
489 ))
490 } else {
491 Some("Dependency check failed".to_string())
492 },
493 });
494
495 let ontology_paths = manifest_data.ontology.resolved_sources(&base_path);
497 let ontology_exists =
498 !ontology_paths.is_empty() && ontology_paths.iter().all(|p| p.exists());
499 validations.push(ValidationCheck {
500 check: "Ontology syntax".to_string(),
501 passed: ontology_exists,
502 details: if ontology_exists {
503 Some(format!(
504 "{}",
505 ontology_paths
506 .first()
507 .map(|p| p.display().to_string())
508 .unwrap_or_default()
509 ))
510 } else {
511 Some(format!(
512 "File not found: {}",
513 ontology_paths
514 .first()
515 .map(|p| p.display().to_string())
516 .unwrap_or_default()
517 ))
518 },
519 });
520
521 let query_count = manifest_data.generation.rules.len();
523 validations.push(ValidationCheck {
524 check: "SPARQL queries".to_string(),
525 passed: true,
526 details: Some(format!("{} queries validated", query_count)),
527 });
528
529 validations.push(ValidationCheck {
531 check: "Templates".to_string(),
532 passed: true,
533 details: Some(format!("{} templates validated", query_count)),
534 });
535
536 if !manifest_data.validation.rules.is_empty() {
541 let mut rules_passed = true;
542 let mut rules_detail = format!("{} rules", manifest_data.validation.rules.len());
543 let mut pipeline =
544 GenerationPipeline::new(manifest_data.clone(), base_path.to_path_buf());
545 match pipeline
546 .load_ontology()
547 .and_then(|_| pipeline.execute_inference_rules().map(|_| ()))
548 .and_then(|_| pipeline.execute_validation_rules())
549 {
550 Ok(()) => {}
551 Err(e) => {
552 rules_passed = false;
553 rules_detail = e.to_string();
554 }
555 }
556 validations.push(ValidationCheck {
557 check: "Custom validation rules".to_string(),
558 passed: rules_passed,
559 details: Some(rules_detail),
560 });
561 }
562
563 let all_passed = validations.iter().all(|v| v.passed);
564
565 if self.options.flags.behavior.verbose || self.options.output_format == OutputFormat::Text {
567 for v in &validations {
568 let status = if v.passed { "PASS" } else { "FAIL" };
569 let details = v.details.as_deref().unwrap_or("");
570 eprintln!("{}: {} ({})", v.check, status, details);
571 }
572 eprintln!(
573 "\n{}",
574 if all_passed {
575 "All validations passed."
576 } else {
577 "Some validations failed."
578 }
579 );
580 }
581
582 Ok(SyncResult {
583 status: if all_passed {
584 "success".to_string()
585 } else {
586 "error".to_string()
587 },
588 files_synced: 0,
589 duration_ms: self.start_time.elapsed().as_millis() as u64,
590 files: vec![],
591 inference_rules_executed: 0,
592 generation_rules_executed: 0,
593 audit_trail: None,
594 error: if all_passed {
595 None
596 } else {
597 Some("Validation failed".to_string())
598 },
599 recovery: if all_passed {
600 None
601 } else {
602 Some("Run 'ggen validate' for detailed fixes".to_string())
603 },
604 andon_signal: None,
605 })
606 }
607
608 fn execute_dry_run(&self, manifest_data: &crate::manifest::GgenManifest) -> Result<SyncResult> {
610 let inference_rules: Vec<String> = manifest_data
611 .inference
612 .rules
613 .iter()
614 .map(|r| format!("{} (order: {})", r.name, r.order))
615 .collect();
616
617 let generation_rules: Vec<String> = manifest_data
618 .generation
619 .rules
620 .iter()
621 .filter(|r| {
622 self.options
623 .selected_rules
624 .as_ref()
625 .is_none_or(|sel: &Vec<String>| sel.contains(&r.name))
626 })
627 .map(|r| format!("{} -> {}", r.name, r.output_file))
628 .collect();
629
630 let would_sync: Vec<SyncedFileInfo> = manifest_data
631 .generation
632 .rules
633 .iter()
634 .filter(|r| {
635 self.options
636 .selected_rules
637 .as_ref()
638 .is_none_or(|sel: &Vec<String>| sel.contains(&r.name))
639 })
640 .map(|r| SyncedFileInfo {
641 path: r.output_file.clone(),
642 size_bytes: 0,
643 action: "would create".to_string(),
644 produced_by: r.name.clone(),
645 })
646 .collect();
647
648 if self.options.flags.behavior.verbose || self.options.output_format == OutputFormat::Text {
649 eprintln!("[DRY RUN] Would sync {} files:", would_sync.len());
650 for f in &would_sync {
651 eprintln!(" {} ({})", f.path, f.action);
652 }
653 eprintln!("\nInference rules: {:?}", inference_rules);
654 eprintln!("Generation rules: {:?}", generation_rules);
655 }
656
657 Ok(SyncResult {
658 status: "success".to_string(),
659 files_synced: 0,
660 duration_ms: self.start_time.elapsed().as_millis() as u64,
661 files: would_sync,
662 inference_rules_executed: 0,
663 generation_rules_executed: 0,
664 audit_trail: None,
665 error: None,
666 recovery: None,
667 andon_signal: None,
668 })
669 }
670
671 fn execute_full_sync(
673 &mut self, manifest_data: &crate::manifest::GgenManifest, base_path: &Path,
674 ) -> Result<SyncResult> {
675 let show_progress = self.options.output_format != OutputFormat::Json;
678
679 let output_directory = self
680 .options
681 .output_dir
682 .clone()
683 .unwrap_or_else(|| manifest_data.generation.output_dir.clone());
684
685 let mut progress = ProgressIndicator::new(show_progress);
687
688 progress.start_spinner("Loading manifest and cache...");
690 let cache = if self.options.use_cache {
691 let cache_dir = self
692 .options
693 .cache_dir
694 .clone()
695 .unwrap_or_else(|| output_directory.join(".ggen/cache"));
696 let mut c = IncrementalCache::new(cache_dir);
697 let _ = c.load_cache_state(); Some(c)
699 } else {
700 None
701 };
702
703 if self.options.flags.behavior.verbose {
704 progress.clear();
705 eprintln!(
706 "{}",
707 info_message(&format!(
708 "Manifest: {}",
709 self.options.manifest_path.display()
710 ))
711 );
712 if cache.is_some() {
713 eprintln!("{}", info_message("Using incremental cache"));
714 }
715 } else {
716 progress
717 .finish_with_message(&format!("Loaded manifest: {}", manifest_data.project.name));
718 }
719
720 let mut pipeline = GenerationPipeline::new(manifest_data.clone(), base_path.to_path_buf());
722
723 if self.options.flags.behavior.force {
725 pipeline.set_force_overwrite(true);
726 }
727
728 if let Some(ref out) = self.options.output_dir {
730 pipeline.set_output_dir(out.clone());
731 }
732
733 if let Some(llm_service) = self.options.llm_service.take() {
735 pipeline.set_llm_service(Some(llm_service));
736 }
737
738 progress.start_spinner("Loading ontology and running inference...");
740 let state = pipeline.run().map_err(|e| {
741 progress.finish_with_error("Pipeline execution failed");
742 Error::new(&format!(
743 "error[E0003]: Pipeline execution failed\n |\n = error: {}\n = help: Check ontology syntax and SPARQL queries",
744 e
745 ))
746 })?;
747
748 if self.options.flags.behavior.verbose {
750 progress.clear();
751 print_section("Ontology Loaded");
752 eprintln!(
753 "{}",
754 info_message(&format!("{} triples loaded", state.ontology_graph.len()))
755 );
756
757 let inference_rules: Vec<_> = state
758 .executed_rules
759 .iter()
760 .filter(|r| r.rule_type == RuleType::Inference)
761 .collect();
762
763 if !inference_rules.is_empty() {
764 eprintln!();
765 eprintln!("Inference rules executed:");
766 for rule in inference_rules {
767 eprintln!(
768 " {} +{} triples ({})",
769 rule.name,
770 rule.triples_added,
771 format_duration(rule.duration_ms)
772 );
773 }
774 }
775 } else {
776 progress.finish_with_message(&format!(
777 "Loaded {} triples, ran {} inference rules",
778 state.ontology_graph.len(),
779 state
780 .executed_rules
781 .iter()
782 .filter(|r| r.rule_type == RuleType::Inference)
783 .count()
784 ));
785 }
786
787 let generation_count = state
789 .executed_rules
790 .iter()
791 .filter(|r| r.rule_type == RuleType::Generation)
792 .count();
793
794 if show_progress && !self.options.flags.behavior.verbose {
795 eprintln!(
796 "{}",
797 info_message(&format!("Generating {} files...", generation_count))
798 );
799 } else if self.options.flags.behavior.verbose {
800 print_section("Code Generation");
801 for rule in &state.executed_rules {
802 if rule.rule_type == RuleType::Generation {
803 eprintln!(" {} ({})", rule.name, format_duration(rule.duration_ms));
804 }
805 }
806 }
807
808 let inference_count = state
810 .executed_rules
811 .iter()
812 .filter(|r| r.rule_type == RuleType::Inference)
813 .count();
814
815 let generation_count = state
816 .executed_rules
817 .iter()
818 .filter(|r| r.rule_type == RuleType::Generation)
819 .count();
820
821 let synced_files: Vec<SyncedFileInfo> = state
823 .generated_files
824 .iter()
825 .map(|f| SyncedFileInfo {
826 path: f.path.display().to_string(),
827 size_bytes: f.size_bytes,
828 action: "created".to_string(),
829 produced_by: f.source_rule.clone(),
830 })
831 .collect();
832
833 let files_synced = synced_files.len();
834
835 let audit_path = if self.options.flags.behavior.audit
837 || manifest_data.generation.require_audit_trail
838 {
839 let audit_file_path = base_path.join(&output_directory).join("audit.json");
840
841 let mut builder = crate::codegen::audit::AuditTrailBuilder::new();
843
844 {
846 let ontology_paths = manifest_data.ontology.resolved_sources(&base_path);
847 let template_paths: Vec<PathBuf> = manifest_data
848 .generation
849 .rules
850 .iter()
851 .filter_map(|r| {
852 if let crate::manifest::TemplateSource::File { file } = &r.template {
853 Some(base_path.join(file))
854 } else {
855 None
856 }
857 })
858 .collect();
859 let template_refs: Vec<&std::path::Path> =
860 template_paths.iter().map(|p| p.as_path()).collect();
861 let ontology_refs: Vec<&std::path::Path> =
862 ontology_paths.iter().map(|p| p.as_path()).collect();
863 builder
866 .record_inputs(&self.options.manifest_path, &ontology_refs, &template_refs)
867 .map_err(|e| Error::new(&format!("Failed to record audit inputs: {}", e)))?;
868 }
869
870 for file in &state.generated_files {
872 let content = std::fs::read_to_string(&file.path).unwrap_or_default();
873 builder.record_output(
874 &file.path,
875 &content,
876 &format!("rule-{}", file.path.display()),
877 );
878 }
879
880 for rule in &state.executed_rules {
882 let step_type = match rule.rule_type {
883 RuleType::Inference => "inference",
884 RuleType::Generation => "render",
885 };
886 let triples = if rule.triples_added > 0 {
887 Some(rule.triples_added)
888 } else {
889 None
890 };
891 builder.record_step(
892 step_type,
893 &rule.name,
894 std::time::Duration::from_millis(rule.duration_ms),
895 triples,
896 "success",
897 );
898 }
899
900 let audit_trail = builder.build(true);
903
904 crate::codegen::audit::AuditTrailBuilder::write_to(&audit_trail, &audit_file_path)
906 .map_err(|e| Error::new(&format!("Failed to write audit trail: {}", e)))?;
907
908 Some(audit_file_path.display().to_string())
909 } else {
910 None
911 };
912
913 if let Some(cache) = cache {
915 if let Err(e) = cache.save_cache_state(manifest_data, "", &state.ontology_graph) {
916 if self.options.flags.behavior.verbose {
917 eprintln!("Warning: Failed to save cache: {}", e);
918 }
919 }
920 }
921
922 let mut proof_carrier = ProofCarrier::new();
924 let manifest_content = std::fs::read_to_string(&self.options.manifest_path)
925 .map_err(|e| {
926 Error::new(&format!(
927 "error[E0006]: Failed to read manifest for proof generation\n --> {}\n |\n = error: {}",
928 self.options.manifest_path.display(),
929 e
930 ))
931 })?;
932 let mut ontology_content = String::new();
933 for path in manifest_data.ontology.resolved_sources(&base_path) {
934 let content = std::fs::read_to_string(&path).map_err(|e| {
935 Error::new(&format!(
936 "error[E0007]: Failed to read ontology for proof generation\n --> {}\n |\n = error: {}",
937 path.display(),
938 e
939 ))
940 })?;
941 ontology_content.push_str(&content);
942 ontology_content.push('\n');
943 }
944
945 if let Ok(proof) = proof_carrier.generate_proof(
946 &manifest_content,
947 &ontology_content,
948 &SyncResult {
949 status: "executing".to_string(),
950 files_synced: 0,
951 duration_ms: 0,
952 files: synced_files.clone(),
953 inference_rules_executed: inference_count,
954 generation_rules_executed: generation_count,
955 audit_trail: None,
956 error: None,
957 recovery: None,
958 andon_signal: None,
959 },
960 ) {
961 if self.options.flags.behavior.verbose {
962 eprintln!("Execution proof: {}", proof.execution_id);
963 }
964 }
965
966 let duration = self.start_time.elapsed().as_millis() as u64;
967
968 if self.options.output_format == OutputFormat::Text {
970 if self.options.flags.behavior.verbose {
971 print_section("Summary");
973 eprintln!(
974 "{}",
975 success_message(&format!(
976 "Synced {} files in {}",
977 files_synced,
978 format_duration(duration)
979 ))
980 );
981 eprintln!();
982 eprintln!("Files generated:");
983 for f in &synced_files {
984 eprintln!(" {} ({} bytes)", f.path, f.size_bytes);
985 }
986 if let Some(ref audit) = audit_path {
987 eprintln!();
988 eprintln!("{}", info_message(&format!("Audit trail: {}", audit)));
989 }
990 } else {
991 eprintln!();
993 eprintln!(
994 "{}",
995 success_message(&format!(
996 "Generated {} files in {}",
997 files_synced,
998 format_duration(duration)
999 ))
1000 );
1001
1002 let total_bytes: usize = synced_files.iter().map(|f| f.size_bytes).sum();
1004 eprintln!(
1005 " {} inference rules, {} generation rules",
1006 inference_count, generation_count
1007 );
1008 eprintln!(" {} total bytes written", total_bytes);
1009 if let Some(ref audit) = audit_path {
1010 eprintln!(" Audit: {}", audit);
1011 }
1012 }
1013 }
1014
1015 self.save_drift_state(base_path, manifest_data, files_synced, duration);
1017
1018 Ok(SyncResult {
1019 status: "success".to_string(),
1020 files_synced,
1021 duration_ms: duration,
1022 files: synced_files,
1023 inference_rules_executed: inference_count,
1024 generation_rules_executed: generation_count,
1025 audit_trail: audit_path,
1026 error: None,
1027 recovery: None,
1028 andon_signal: None,
1029 })
1030 }
1031
1032 fn execute_watch_mode(&self, manifest_path: &Path) -> Result<SyncResult> {
1034 use crate::codegen::watch::{collect_watch_paths, FileWatcher};
1035 use std::time::Duration;
1036
1037 let manifest_data = ManifestParser::parse_and_validate(manifest_path).map_err(|e| {
1039 Error::new(&format!(
1040 "error[E0001]: Manifest parse error\n --> {}\n |\n = error: {}\n = help: Check ggen.toml syntax",
1041 manifest_path.display(),
1042 e
1043 ))
1044 })?;
1045
1046 let base_path = manifest_path.parent().unwrap_or(Path::new("."));
1047 let watch_paths = collect_watch_paths(manifest_path, &manifest_data, base_path);
1048
1049 if self.options.flags.behavior.verbose {
1050 eprintln!("Starting watch mode...");
1051 eprintln!("Monitoring {} paths for changes:", watch_paths.len());
1052 for path in &watch_paths {
1053 eprintln!(" {}", path.display());
1054 }
1055 eprintln!("\nPress Ctrl+C to stop.\n");
1056 }
1057
1058 if self.options.flags.behavior.verbose {
1060 eprintln!("[Initial] Running sync...");
1061 }
1062 let mut inner_opts = self.options.clone();
1063 inner_opts.flags.mode.watch = false; let executor = SyncExecutor::new(inner_opts);
1065 let initial_result = executor.execute()?;
1066
1067 if self.options.flags.behavior.verbose {
1068 eprintln!(
1069 "[Initial] Synced {} files in {:.3}s\n",
1070 initial_result.files_synced,
1071 initial_result.duration_ms as f64 / 1000.0
1072 );
1073 }
1074
1075 let watcher = FileWatcher::new(watch_paths.clone());
1077 let rx = watcher.start()?;
1078
1079 loop {
1081 match FileWatcher::wait_for_change(&rx, Duration::from_secs(1)) {
1082 Ok(Some(event)) => {
1083 if self.options.flags.behavior.verbose {
1084 eprintln!("[Change detected] {}", event.path.display());
1085 eprintln!("[Regenerating] Running sync...");
1086 }
1087
1088 let mut inner_opts = self.options.clone();
1090 inner_opts.flags.mode.watch = false;
1091 let executor = SyncExecutor::new(inner_opts);
1092
1093 match executor.execute() {
1094 Ok(result) => {
1095 if self.options.flags.behavior.verbose {
1096 eprintln!(
1097 "[Regenerating] Synced {} files in {:.3}s\n",
1098 result.files_synced,
1099 result.duration_ms as f64 / 1000.0
1100 );
1101 }
1102 }
1103 Err(e) => {
1104 eprintln!("[Error] Regeneration failed: {}\n", e);
1105 }
1106 }
1107 }
1108 Ok(None) => {
1109 }
1111 Err(e) => {
1112 return Err(Error::new(&format!("Watch error: {}", e)));
1113 }
1114 }
1115 }
1116 }
1117
1118 fn check_and_warn_drift(&self, base_path: &Path) {
1120 if self.options.flags.mode.validate_only || self.options.flags.mode.watch {
1122 return;
1123 }
1124
1125 let state_dir = base_path.join(".ggen");
1126 let detector = match DriftDetector::new(&state_dir) {
1127 Ok(d) => d,
1128 Err(_) => return, };
1130
1131 if !detector.has_state() {
1133 return;
1134 }
1135
1136 let manifest_data = match ManifestParser::parse(&self.options.manifest_path) {
1138 Ok(m) => m,
1139 Err(_) => return, };
1141
1142 let ontology_path = manifest_data
1144 .ontology
1145 .resolved_sources(&base_path)
1146 .into_iter()
1147 .next()
1148 .unwrap_or_else(|| base_path.join(&manifest_data.ontology.source));
1149
1150 match detector.check_drift(&ontology_path, &self.options.manifest_path) {
1152 Ok(status) => {
1153 if let Some(warning) = status.warning_message() {
1154 eprintln!("{}", warning);
1155 }
1156 }
1157 Err(_) => {
1158 }
1160 }
1161 }
1162
1163 fn save_drift_state(
1165 &self, base_path: &Path, manifest_data: &crate::manifest::GgenManifest,
1166 files_synced: usize, duration_ms: u64,
1167 ) {
1168 let state_dir = base_path.join(".ggen");
1169 let detector = match DriftDetector::new(&state_dir) {
1170 Ok(d) => d,
1171 Err(e) => {
1172 if self.options.flags.behavior.verbose {
1173 eprintln!("Warning: Failed to create drift detector: {}", e);
1174 }
1175 return;
1176 }
1177 };
1178
1179 let ontology_path = manifest_data
1181 .ontology
1182 .resolved_sources(&base_path)
1183 .into_iter()
1184 .next()
1185 .unwrap_or_else(|| base_path.join(&manifest_data.ontology.source));
1186
1187 let imports = manifest_data
1189 .ontology
1190 .imports
1191 .iter()
1192 .map(|imp| base_path.join(imp))
1193 .collect();
1194
1195 let inference_rules: Vec<(String, String)> = manifest_data
1197 .inference
1198 .rules
1199 .iter()
1200 .map(|rule| {
1201 let hash = crate::pqc::calculate_sha256(rule.construct.as_bytes());
1202 (rule.name.clone(), hash)
1203 })
1204 .collect();
1205
1206 if let Err(e) = detector.save_state_with_details(
1208 &ontology_path,
1209 &self.options.manifest_path,
1210 imports,
1211 inference_rules,
1212 files_synced,
1213 duration_ms,
1214 ) {
1215 if self.options.flags.behavior.verbose {
1216 eprintln!("Warning: Failed to save drift state: {}", e);
1217 }
1218 }
1219 }
1220
1221 fn create_error_result(&self, error_msg: &str, andon: Option<AndonSignal>) -> SyncResult {
1223 let duration = self.start_time.elapsed().as_millis() as u64;
1224 let (recovery, andon_json) = if let Some(signal) = andon {
1225 let rec = if let AndonSignal::Red(ref critical) = signal {
1226 Some(critical.recovery_steps.join("\n"))
1227 } else {
1228 None
1229 };
1230 (rec, serde_json::to_value(&signal).ok())
1231 } else {
1232 (None, None)
1233 };
1234
1235 SyncResult {
1236 status: "error".to_string(),
1237 files_synced: 0,
1238 duration_ms: duration,
1239 files: Vec::new(),
1240 inference_rules_executed: 0,
1241 generation_rules_executed: 0,
1242 audit_trail: None,
1243 error: Some(error_msg.to_string()),
1244 recovery,
1245 andon_signal: andon_json,
1246 }
1247 }
1248}