Skip to main content

cargo_mate/
display.rs

1use crate::checklist;
2use crate::history;
3use crate::captain::parser::{self, MessageData, ParsedError, ParsedWarning};
4use crate::captain::tide::TideCharts;
5use crate::captain::tide::BuildMetrics;
6use crate::captain::license;
7use colored::*;
8use anyhow::{Result, Context};
9use indicatif::{ProgressBar, ProgressStyle, MultiProgress};
10use std::fs;
11use std::io::{BufRead, BufReader, Write};
12use std::process::{Command, Stdio};
13use std::sync::atomic::{AtomicUsize, Ordering};
14use std::sync::Arc;
15use std::thread;
16use std::time::{Duration, Instant};
17use chrono::Utc;
18use std::collections::{HashMap, HashSet};
19use sha2::{Sha256, Digest};
20use serde::{Serialize, Deserialize};
21#[derive(Debug, Clone, Serialize, Deserialize)]
22pub struct ErrorDeduplicator {
23    seen_fingerprints: HashMap<String, ErrorGroup>,
24    similarity_threshold: f32,
25}
26#[derive(Debug, Clone, Serialize, Deserialize)]
27pub struct ErrorGroup {
28    primary_error: ParsedError,
29    variations: Vec<ParsedError>,
30    count: usize,
31    first_seen: String,
32    locations: HashSet<String>,
33}
34impl ErrorDeduplicator {
35    pub fn new() -> Self {
36        Self {
37            seen_fingerprints: HashMap::new(),
38            similarity_threshold: 0.8,
39        }
40    }
41    pub fn fingerprint(&self, error: &ParsedError) -> String {
42        let mut hasher = Sha256::new();
43        let normalized = self.normalize_error_message(&error.message);
44        hasher.update(normalized.as_bytes());
45        if !error.file.is_empty() {
46            hasher.update(error.file.as_bytes());
47            if error.line > 0 {
48                hasher.update((error.line / 10).to_string().as_bytes());
49            }
50        }
51        format!("{:x}", hasher.finalize())
52    }
53    fn normalize_error_message(&self, msg: &str) -> String {
54        msg.split_whitespace()
55            .map(|word| {
56                if word.starts_with('`') && word.ends_with('`') {
57                    "`<identifier>`"
58                } else {
59                    word
60                }
61            })
62            .collect::<Vec<_>>()
63            .join(" ")
64    }
65    pub fn process_errors(&mut self, errors: &[ParsedError]) -> Vec<ErrorGroup> {
66        for error in errors {
67            let fingerprint = self.fingerprint(error);
68            self.seen_fingerprints
69                .entry(fingerprint)
70                .or_insert_with(|| ErrorGroup {
71                    primary_error: error.clone(),
72                    variations: Vec::new(),
73                    count: 0,
74                    first_seen: Utc::now().to_rfc3339(),
75                    locations: HashSet::new(),
76                })
77                .add_variation(error);
78        }
79        let mut groups: Vec<_> = self.seen_fingerprints.values().cloned().collect();
80        groups.sort_by(|a, b| b.count.cmp(&a.count));
81        groups
82    }
83}
84impl ErrorGroup {
85    pub fn add_variation(&mut self, error: &ParsedError) {
86        self.variations.push(error.clone());
87        self.count += 1;
88        if !error.file.is_empty() {
89            self.locations.insert(format!("{}:{}", error.file, error.line));
90        }
91    }
92}
93#[derive(Debug, Clone)]
94pub struct ErrorPrioritizer {
95    weights: PriorityWeights,
96}
97#[derive(Debug, Clone)]
98pub struct PriorityWeights {
99    never_seen_before: f32,
100    blocking_compilation: f32,
101    has_quick_fix: f32,
102    frequently_ignored: f32,
103    in_dependency: f32,
104    test_only: f32,
105}
106impl Default for PriorityWeights {
107    fn default() -> Self {
108        Self {
109            never_seen_before: 10.0,
110            blocking_compilation: 8.0,
111            has_quick_fix: -2.0,
112            frequently_ignored: -5.0,
113            in_dependency: -3.0,
114            test_only: -1.0,
115        }
116    }
117}
118impl ErrorPrioritizer {
119    pub fn new() -> Self {
120        Self {
121            weights: PriorityWeights::default(),
122        }
123    }
124    pub fn sort_errors(&self, errors: Vec<ParsedError>) -> Vec<ParsedError> {
125        let mut scored_errors: Vec<(ParsedError, f32)> = errors
126            .into_iter()
127            .map(|error| {
128                let mut score = 5.0;
129                score += self.weights.never_seen_before;
130                if self.has_known_fix(&error) {
131                    score += self.weights.has_quick_fix;
132                }
133                if error.file.contains("/dependencies/") {
134                    score += self.weights.in_dependency;
135                }
136                if error.file.contains("/tests/") || error.file.contains("_test.rs") {
137                    score += self.weights.test_only;
138                }
139                (error, score)
140            })
141            .collect();
142        scored_errors.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap());
143        scored_errors.into_iter().map(|(e, _)| e).collect()
144    }
145    fn has_known_fix(&self, error: &ParsedError) -> bool {
146        false
147    }
148}
149#[derive(Debug, Clone)]
150pub struct BuildCoach {
151    tips: Vec<CoachingTip>,
152    shown_tips: HashSet<String>,
153}
154#[derive(Debug, Clone)]
155pub struct CoachingTip {
156    id: String,
157    condition: BuildCondition,
158    message: String,
159    priority: u8,
160}
161#[derive(Debug, Clone)]
162pub enum BuildCondition {
163    SlowBuild(Duration),
164    ManyWarnings(usize),
165    RecurringErrors,
166    LargeErrorCount(usize),
167}
168impl BuildCoach {
169    pub fn new() -> Self {
170        let mut tips = Vec::new();
171        tips.push(CoachingTip {
172            id: "slow_build".to_string(),
173            condition: BuildCondition::SlowBuild(Duration::from_secs(30)),
174            message: "๐Ÿ’ก Long build? Try `cm optimize aggressive` for faster builds"
175                .to_string(),
176            priority: 5,
177        });
178        tips.push(CoachingTip {
179            id: "many_warnings".to_string(),
180            condition: BuildCondition::ManyWarnings(20),
181            message: "๐Ÿ’ก Many warnings? Use `cm mutiny allow-warnings` temporarily"
182                .to_string(),
183            priority: 3,
184        });
185        tips.push(CoachingTip {
186            id: "recurring_error".to_string(),
187            condition: BuildCondition::RecurringErrors,
188            message: "๐Ÿ’ก Recurring error? Try `cm wtf er` for AI assistance"
189                .to_string(),
190            priority: 8,
191        });
192        tips.push(CoachingTip {
193            id: "many_errors".to_string(),
194            condition: BuildCondition::LargeErrorCount(10),
195            message: "๐Ÿ’ก Many errors? Focus on the first few - they often cascade"
196                .to_string(),
197            priority: 6,
198        });
199        Self {
200            tips,
201            shown_tips: HashSet::new(),
202        }
203    }
204    pub fn check_and_show_tip(&mut self, context: &BuildContext) -> Option<String> {
205        for tip in &self.tips {
206            if !self.shown_tips.contains(&tip.id) && tip.condition.matches(context) {
207                self.shown_tips.insert(tip.id.clone());
208                return Some(tip.message.clone());
209            }
210        }
211        None
212    }
213}
214impl BuildCondition {
215    pub fn matches(&self, context: &BuildContext) -> bool {
216        match self {
217            BuildCondition::SlowBuild(duration) => context.elapsed > *duration,
218            BuildCondition::ManyWarnings(count) => context.warning_count > *count,
219            BuildCondition::RecurringErrors => context.has_recurring_errors,
220            BuildCondition::LargeErrorCount(count) => context.error_count > *count,
221        }
222    }
223}
224#[derive(Debug)]
225pub struct BuildContext {
226    pub elapsed: Duration,
227    pub warning_count: usize,
228    pub error_count: usize,
229    pub has_recurring_errors: bool,
230}
231fn process_and_display_errors(errors: &[ParsedError]) {
232    if errors.is_empty() {
233        return;
234    }
235    let mut deduplicator = ErrorDeduplicator::new();
236    let groups = deduplicator.process_errors(errors);
237    if !groups.is_empty() {
238        println!(
239            "\n{}", format!("๐Ÿ”ด {} Unique Error Patterns:", groups.len()) .red().bold()
240        );
241        for (i, group) in groups.iter().take(5).enumerate() {
242            println!(
243                "  {}. {} ({}x across {} locations)", i + 1, group.primary_error.message,
244                group.count, group.locations.len()
245            );
246            if group.variations.len() > 1 {
247                println!(
248                    "     {} Similar variations grouped", group.variations.len()
249                    .to_string().dimmed()
250                );
251            }
252        }
253    }
254}
255pub fn run_cargo_passthrough(args: &[&str]) {
256    let cargo_path = std::env::var("CARGO_BIN_PATH")
257        .unwrap_or_else(|_| "/root/.cargo/bin/cargo".to_string());
258    let status = Command::new(&cargo_path)
259        .args(args)
260        .status()
261        .unwrap_or_else(|e| {
262            eprintln!("Failed to start cargo: {}", e);
263            std::process::exit(1);
264        });
265    std::process::exit(status.code().unwrap_or(1));
266}
267const NAUTICAL_MESSAGES: &[&str] = &[
268    "[ANCHOR] Dropping anchor and securing position...",
269    "[WAVE] Riding the waves with steady resolve...",
270    "[PIRATE] Hoisting the Jolly Roger - compilation begins! [SWORD]",
271    "[MAP] Charting course through dependency seas...",
272    "[SAIL] Catching wind in our dependency sails...",
273    "[SHIP] Setting sail across the Rust seas...",
274    "[COMPASS] Navigating treacherous compilation waters...",
275    "[SUNRISE] Chasing horizons of clean builds...",
276    "[HAMMER] Forging dependencies in the shipyard...",
277    "[GEAR] Machining precision components...",
278    "[BOLT] Tightening bolts in the engine room...",
279    "[WRENCH] Calibrating the build compass...",
280    "[RULER] Measuring twice, compiling once...",
281    "[SCOPE] Inspecting code quality under magnification...",
282    "[FLASK] Distilling pure Rust essence...",
283    "[TEST] Testing the waters before deep diving...",
284    "๐Ÿ“ฆ Loading cargo containers with care...",
285    "๐Ÿš› Hauling dependencies across the digital dock...",
286    "๐Ÿ—๏ธ Constructing the foundation of your project...",
287    "๐Ÿงฑ Laying bricks of reliable code...",
288    "๐Ÿญ Manufacturing robust binaries...",
289    "๐Ÿ“‹ Checking manifest against the cargo log...",
290    "๐Ÿ” Scanning for hidden treasures in the code...",
291    "๐Ÿงน Sweeping the deck of compilation artifacts...",
292    "โšก Full speed ahead - compiling at flank speed! โšก",
293    "๐ŸŽฏ Setting course for build success...",
294    "๐ŸŒŸ Following the North Star of clean code...",
295    "๐Ÿ† Battling compilation dragons...",
296    "๐Ÿ›ก๏ธ Shielding against compilation errors...",
297    "๐ŸŽช Performing the great cargo circus act...",
298    "๐ŸŽญ Wearing multiple compilation hats...",
299    "๐ŸŽช Juggling dependencies like a master performer...",
300    "๐ŸŒŠ Sailing through calm compilation seas...",
301    "โ›ˆ๏ธ Weathering the storm of complex dependencies...",
302    "๐ŸŒช๏ธ Surfing the waves of async compilation...",
303    "๐ŸŒˆ Riding the rainbow after the storm...",
304    "๐ŸŒŠ Dancing with the tides of build progress...",
305    "๐ŸŒ… Sunset approaches - build almost complete...",
306    "๐ŸŒ„ Dawn breaks - new build cycle begins...",
307    "๐ŸŒ  Shooting stars guide our compilation path...",
308    "๐Ÿ‘ฅ Manning the compilation stations...",
309    "๐Ÿดโ€โ˜ ๏ธ Crew chanting sea shanties of success...",
310    "๐Ÿง‘โ€โš“ First mate checking the build log...",
311    "๐Ÿ‘จโ€๐Ÿณ Cook preparing a feast of fresh binaries...",
312    "๐Ÿง‘โ€๐Ÿš€ Navigator plotting course through error logs...",
313    "๐Ÿ‘จโ€๐Ÿ”ง Engineer fine-tuning the compilation engine...",
314    "๐Ÿง‘โ€๐ŸŽจ Artist painting the canvas of clean code...",
315    "๐Ÿ‘ฉโ€โš–๏ธ Judge reviewing code quality standards...",
316    "๐Ÿดโ€โ˜ ๏ธ Searching for buried compilation treasures...",
317    "๐Ÿ—๏ธ Unlocking the secrets of dependency resolution...",
318    "๐Ÿ’Ž Polishing the gems of optimized code...",
319    "๐Ÿ—บ๏ธ Following the treasure map of build instructions...",
320    "๐Ÿ”ฎ Crystal ball shows successful compilation...",
321    "๐Ÿง™โ€โ™‚๏ธ Wizard casting spells of optimization...",
322    "๐Ÿฆ„ Unicorn blessing the codebase...",
323    "๐Ÿ‰ Dragon guarding the gates of compilation success...",
324    "๐Ÿ”ง Twisting the knobs of optimization...",
325    "โš–๏ธ Balancing the scales of performance...",
326    "๐Ÿ”„ Spinning the wheels of progress...",
327    "๐Ÿ“Š Graphing the peaks of build performance...",
328    "๐ŸŽต Orchestrating the symphony of compilation...",
329    "๐ŸŽญ Directing the play of parallel compilation...",
330    "๐ŸŽช Conducting the circus of crate dependencies...",
331    "๐ŸŽจ Painting the masterpiece of working binaries...",
332    "๐Ÿฆ€ Crab walking through memory safety checks...",
333    "๐Ÿฆ€ Pinning ownership to the compilation board...",
334    "๐Ÿฆ€ Borrowing references from the lending library...",
335    "๐Ÿฆ€ Sending values across the borrow checker...",
336    "๐Ÿฆ€ Moving types through the ownership maze...",
337    "๐Ÿฆ€ Deriving traits from the trait workshop...",
338    "๐Ÿฆ€ Implementing interfaces in the code factory...",
339    "๐Ÿฆ€ Matching patterns in the pattern matching parlor...",
340    "๐ŸŽช Clown car of dependencies arriving...",
341    "๐Ÿค– Robot army assembling your binaries...",
342    "๐Ÿš€ Spaceship preparing for launch sequence...",
343    "๐Ÿง  Brain computing optimal compilation path...",
344    "๐ŸŽฏ Target acquired - building with precision...",
345    "๐Ÿงฉ Piecing together the puzzle of dependencies...",
346    "๐ŸŽช Big top compilation show in progress...",
347    "๐ŸŽช Tent of dependencies being raised...",
348    "โ˜€๏ธ Sunny compilation day ahead...",
349    "๐ŸŒ™ Night shift compilation crew reporting...",
350    "โ„๏ธ Cool compilation in progress...",
351    "๐Ÿ”ฅ Hot compilation action heating up...",
352    "๐ŸŒช๏ธ Tornado of dependencies spinning up...",
353    "๐ŸŒˆ Rainbow compilation bridge forming...",
354    "โญ Starry night compilation under way...",
355    "๐ŸŒŒ Galactic compilation sequence initiated...",
356];
357const BUILD_STAGES: &[&str] = &[
358    "๐Ÿ” Analyzing dependencies in the code harbor...",
359    "๐Ÿ“ฆ Downloading crates from the digital dockyard...",
360    "๐Ÿ”จ Compiling dependencies in the shipyard forge...",
361    "โš™๏ธ Building project with precision engineering...",
362    "๐Ÿงช Running tests through quality control gauntlet...",
363    "๐Ÿ“‹ Generating documentation for future explorers...",
364    "๐Ÿš€ Finalizing build - preparing for launch sequence...",
365    "๐ŸŽฏ Calibrating build targets and cross-checking manifests...",
366    "๐Ÿ”ฌ Inspecting binaries under the quality microscope...",
367    "๐Ÿ“Š Generating build metrics and performance reports...",
368    "๐Ÿงน Sweeping up compilation artifacts and loose ends...",
369    "๐Ÿ† Polishing the final executable to a mirror shine...",
370    "๐Ÿš€ Loading binary into launch tube - ready for deployment...",
371];
372pub fn run_cargo_with_display(args: &[&str]) {
373    let start_time = Instant::now();
374    
375    // ONLY use wrapper for 'build' command - all other commands run directly
376    // This prevents issues with --message-format=json and other wrapper interference
377    let cmd_name = args.get(0).map(|s| *s).unwrap_or("");
378    let needs_wrapper = cmd_name == "build";
379    
380    if !needs_wrapper {
381        // For ALL commands other than build (check, test, doc, clippy, fmt, bench, run, publish, install, etc.)
382        // Run directly without any wrapper interference
383        // No license checks, no JSON parsing, no delays - just pure cargo passthrough
384        let mut command = Command::new("cargo");
385        command.args(args);
386        // Inherit stdin/stdout/stderr for interactive commands
387        let status = command.status().unwrap_or_else(|e| {
388            eprintln!("Failed to start cargo: {}", e);
389            std::process::exit(1);
390        });
391        std::process::exit(status.code().unwrap_or(1));
392    }
393    
394    let mut error_deduplicator = ErrorDeduplicator::new();
395    let error_prioritizer = ErrorPrioritizer::new();
396    let mut build_coach = BuildCoach::new();
397
398    // Only 'build' command uses the wrapper, so we can safely add JSON format
399    // All other commands have already been handled above with direct passthrough
400    let mut command = Command::new("cargo");
401    command.args(args);
402    
403    // Add JSON format for build command (it's the only one that reaches here)
404    if !args.iter().any(|arg| matches!(*arg, "--help" | "-h" | "help")) {
405        command.arg("--message-format=json");
406    }
407
408    let mut child = command
409        .stdout(Stdio::piped())
410        .stderr(Stdio::piped())
411        .spawn()
412        .unwrap_or_else(|e| {
413            eprintln!("Failed to start cargo: {}", e);
414            std::process::exit(1);
415        });
416    let stdout = child.stdout.take().unwrap();
417    let stderr = child.stderr.take().unwrap();
418    let reader = BufReader::new(stdout);
419    let err_reader = BufReader::new(stderr);
420    let mut errors = Vec::new();
421    let mut warnings = Vec::new();
422    let mut artifacts = Vec::new();
423    let mut build_scripts = Vec::new();
424    let error_count = Arc::new(AtomicUsize::new(0));
425    let warning_count = Arc::new(AtomicUsize::new(0));
426    let artifact_count = Arc::new(AtomicUsize::new(0));
427    // Show initial animation message (like it used to)
428    if let Some(message) = NAUTICAL_MESSAGES.get(0) {
429        println!("{}", message.cyan());
430    }
431    
432    // Only show progress bars if we're actually processing JSON output
433    // For commands with immediate output, progress bars can be distracting
434    let multi_progress = MultiProgress::new();
435    let main_pb = create_main_progress_bar();
436    let main_pb = multi_progress.add(main_pb);
437    let status_pb = create_status_bar();
438    let status_pb = multi_progress.add(status_pb);
439    let file_pb = create_file_counter_bar();
440    let file_pb = multi_progress.add(file_pb);
441    
442    main_pb.set_message(format!("๐Ÿšข {}", args.join(" ")));
443    status_pb.set_message("โณ Initializing...");
444    file_pb.set_message("๐Ÿ“ 0 files processed");
445    let mut message_index = 0;
446    let mut stage_index = 0;
447    let mut tick_count = 0;
448    let mut last_stage_change = Instant::now();
449    let mut last_output = Instant::now();
450    
451    if !NAUTICAL_MESSAGES.is_empty() && message_index >= NAUTICAL_MESSAGES.len() {
452        message_index = 0;
453    }
454    if !BUILD_STAGES.is_empty() && stage_index >= BUILD_STAGES.len() {
455        stage_index = 0;
456    }
457    
458    // Don't show initial animation - output will come soon enough
459    
460    let err_handle = thread::spawn(move || {
461        let reader = BufReader::new(err_reader);
462        for line in reader.lines() {
463            if let Ok(line) = line {
464                eprintln!("{}", line);
465            }
466        }
467    });
468    
469    // Process output line by line, showing it immediately
470    for line in reader.lines() {
471        if let Ok(line) = line {
472            // Show output immediately for non-JSON lines (fallback output)
473            // This ensures users see output right away, especially for commands like run
474            if !line.trim_start().starts_with('{') {
475                println!("{}", line);
476                last_output = Instant::now();
477                continue; // Skip JSON parsing for non-JSON lines
478            }
479            
480            if let Some(msg) = parser::parse_cargo_message(&line) {
481                match msg.data {
482                    MessageData::CompilerMessage(cm) => {
483                        match cm.message.level.as_str() {
484                            "error" => {
485                                let parsed_error = parser::format_error(&cm.message);
486                                errors.push(parsed_error.clone());
487                                error_count.store(errors.len(), Ordering::Relaxed);
488                                
489                                // Show shortened error output immediately
490                                if errors.len() <= 3 {
491                                    println!("๐Ÿ”ด {}", parsed_error);
492                                } else if errors.len() == 4 {
493                                    println!("๐Ÿ”ด ... and more errors");
494                                }
495                                
496                                status_pb
497                                    .set_message(
498                                        format!(
499                                            "๐Ÿ”ด {} errors, โš ๏ธ {} warnings", error_count
500                                            .load(Ordering::Relaxed), warning_count
501                                            .load(Ordering::Relaxed)
502                                        ),
503                                    );
504                                error_deduplicator.process_errors(&[parsed_error]);
505                            }
506                            "warning" => {
507                                let parsed_warning = parser::format_warning(&cm.message);
508                                warnings.push(parsed_warning.clone());
509                                warning_count.store(warnings.len(), Ordering::Relaxed);
510                                
511                                // Show shortened warning output immediately (only first few)
512                                if warnings.len() <= 3 {
513                                    println!("โš ๏ธ  {}", parsed_warning);
514                                } else if warnings.len() == 4 {
515                                    println!("โš ๏ธ  ... and more warnings");
516                                }
517                                
518                                status_pb
519                                    .set_message(
520                                        format!(
521                                            "๐Ÿ”ด {} errors, โš ๏ธ {} warnings", error_count
522                                            .load(Ordering::Relaxed), warning_count
523                                            .load(Ordering::Relaxed)
524                                        ),
525                                    );
526                            }
527                            _ => {}
528                        }
529                    }
530                    MessageData::BuildScriptExecuted(bs) => {
531                        build_scripts.push(bs);
532                        artifact_count
533                            .store(
534                                artifact_count.load(Ordering::Relaxed) + 1,
535                                Ordering::Relaxed,
536                            );
537                        file_pb
538                            .set_message(
539                                format!(
540                                    "๐Ÿ“ {} files, ๐Ÿ”จ {} build scripts", artifact_count
541                                    .load(Ordering::Relaxed), build_scripts.len()
542                                ),
543                            );
544                    }
545                    MessageData::CompilerArtifact(ca) => {
546                        let target_name = ca.target.name.clone();
547                        artifacts.push(ca);
548                        artifact_count.store(artifacts.len(), Ordering::Relaxed);
549                        
550                        // Show shortened artifact output (only first few)
551                        if artifacts.len() <= 3 {
552                            println!("๐Ÿ“ฆ Compiled: {}", target_name);
553                        }
554                        
555                        file_pb
556                            .set_message(
557                                format!(
558                                    "๐Ÿ“ {} files, ๐Ÿ”จ {} build scripts", artifact_count
559                                    .load(Ordering::Relaxed), build_scripts.len()
560                                ),
561                            );
562                    }
563                    _ => {}
564                }
565                tick_count += 1;
566                last_output = Instant::now();
567                
568                // Update progress bars less frequently to avoid interfering with output
569                // Only tick progress bars, don't update messages if we have recent output
570                if last_output.elapsed() > Duration::from_millis(1000) {
571                    // No recent output, safe to update animations
572                    if tick_count > 1_000_000 {
573                        tick_count = 0;
574                    }
575                    if tick_count % 20 == 0 && !NAUTICAL_MESSAGES.is_empty() {
576                        message_index = (message_index + 1) % NAUTICAL_MESSAGES.len();
577                        if let Some(message) = NAUTICAL_MESSAGES.get(message_index) {
578                            main_pb.set_prefix(message.to_string());
579                        }
580                    }
581                    if last_stage_change.elapsed() > Duration::from_secs(5)
582                        && !BUILD_STAGES.is_empty()
583                    {
584                        stage_index = (stage_index + 1) % BUILD_STAGES.len();
585                        if let Some(stage) = BUILD_STAGES.get(stage_index) {
586                            status_pb.set_message(stage.to_string());
587                        }
588                        last_stage_change = Instant::now();
589                    }
590                }
591                // Always tick progress bars, but less frequently
592                if tick_count % 5 == 0 {
593                    main_pb.tick();
594                    status_pb.tick();
595                    file_pb.tick();
596                }
597            }
598        }
599    }
600    let elapsed = start_time.elapsed();
601    
602    // Finish progress bars before showing summary (clear them)
603    main_pb.finish();
604    status_pb.finish();
605    file_pb.finish();
606    
607    let _ = err_handle.join();
608    let status = child.wait().unwrap();
609    let has_recurring_errors = !errors.is_empty()
610        && error_count.load(Ordering::Relaxed) > 1;
611    let build_context = BuildContext {
612        elapsed,
613        warning_count: warnings.len(),
614        error_count: errors.len(),
615        has_recurring_errors,
616    };
617    // Show summary first (like it used to)
618    display_summary(
619        &errors,
620        &warnings,
621        &artifacts,
622        &build_scripts,
623        status.success(),
624        elapsed,
625    );
626    
627    // Then show helpful tips and suggestions
628    if let Some(tip) = build_coach.check_and_show_tip(&build_context) {
629        println!("\n{}", tip.cyan());
630    }
631    
632    // Show error patterns if there are errors
633    if !errors.is_empty() {
634        let prioritized_errors = error_prioritizer.sort_errors(errors.clone());
635        process_and_display_errors(&prioritized_errors);
636    }
637    
638    // Save results and record metrics
639    save_results(&errors, &warnings, &artifacts, &build_scripts, args);
640    record_build_metrics(args, elapsed, errors.len(), warnings.len(), status.success());
641    
642    // Show checklist and view options at the end
643    if !errors.is_empty() || !warnings.is_empty() {
644        checklist::generate_checklist(&errors, &warnings);
645        println!("\n๐Ÿ“‹ Run {} to see your checklist", "cm checklist".yellow());
646    }
647    display_view_options(&errors, &warnings, &artifacts, &build_scripts);
648}
649fn create_main_progress_bar() -> ProgressBar {
650    let pb = ProgressBar::new_spinner();
651    pb.set_style(
652        ProgressStyle::default_spinner()
653            .template("{prefix:.cyan} {spinner:.green} {msg}")
654            .unwrap()
655            .tick_chars("|-\\|/-"),
656    );
657    pb.enable_steady_tick(Duration::from_millis(80));
658    pb
659}
660fn create_status_bar() -> ProgressBar {
661    let pb = ProgressBar::new_spinner();
662    pb.set_style(
663        ProgressStyle::default_spinner()
664            .template("{spinner:.blue} {msg}")
665            .unwrap()
666            .tick_chars("...oooOOO"),
667    );
668    pb.enable_steady_tick(Duration::from_millis(120));
669    pb
670}
671fn create_file_counter_bar() -> ProgressBar {
672    let pb = ProgressBar::new_spinner();
673    pb.set_style(
674        ProgressStyle::default_spinner()
675            .template("{spinner:.yellow} {msg}")
676            .unwrap()
677            .tick_chars("123456789"),
678    );
679    pb.enable_steady_tick(Duration::from_millis(100));
680    pb
681}
682fn save_results(
683    errors: &[ParsedError],
684    warnings: &[ParsedWarning],
685    artifacts: &[parser::CompilerArtifact],
686    build_scripts: &[parser::BuildScriptExecuted],
687    args: &[&str],
688) {
689    let shipwreck = dirs::home_dir().unwrap().join(".shipwreck");
690    fs::create_dir_all(&shipwreck).unwrap();
691    let error_file = shipwreck.join("errors").join("latest.txt");
692    fs::create_dir_all(error_file.parent().unwrap()).unwrap();
693    let mut f = fs::File::create(&error_file).unwrap();
694    for error in errors {
695        writeln!(f, "{}", error).unwrap();
696    }
697    let warning_file = shipwreck.join("warnings").join("latest.txt");
698    fs::create_dir_all(warning_file.parent().unwrap()).unwrap();
699    let mut f = fs::File::create(&warning_file).unwrap();
700    for warning in warnings {
701        writeln!(f, "{}", warning).unwrap();
702    }
703    let artifact_file = shipwreck.join("artifacts").join("latest.txt");
704    fs::create_dir_all(artifact_file.parent().unwrap()).unwrap();
705    let mut f = fs::File::create(&artifact_file).unwrap();
706    for artifact in artifacts {
707        writeln!(f, "๐Ÿ“ฆ {} -> {}", artifact.target.name, artifact.filenames.join(", "))
708            .unwrap();
709    }
710    let script_file = shipwreck.join("scripts").join("latest.txt");
711    fs::create_dir_all(script_file.parent().unwrap()).unwrap();
712    let mut f = fs::File::create(&script_file).unwrap();
713    for script in build_scripts {
714        writeln!(
715            f, "๐Ÿ”จ {} -> libs: {}, paths: {}, cfgs: {}", script.package_id, script
716            .linked_libs.len(), script.linked_paths.len(), script.cfgs.len()
717        )
718            .unwrap();
719    }
720    history::save_to_history(args.join(" "), errors.to_vec(), warnings.to_vec());
721}
722fn display_summary(
723    errors: &[ParsedError],
724    warnings: &[ParsedWarning],
725    artifacts: &[parser::CompilerArtifact],
726    build_scripts: &[parser::BuildScriptExecuted],
727    success: bool,
728    elapsed: Duration,
729) {
730    println!("\n{}", "โ•".repeat(60).blue());
731    if success && errors.is_empty() {
732        println!("{}", "โœ… Build Successful!".green().bold());
733    } else {
734        println!("{}", "โŒ Build Failed!".red().bold());
735    }
736    println!("โฑ๏ธ  Build time: {:.1}s", elapsed.as_secs_f32());
737    println!("๐Ÿ“ Files generated: {}", artifacts.len());
738    println!("๐Ÿ”จ Build scripts: {}", build_scripts.len());
739    if !errors.is_empty() {
740        println!("\n{}", format!("๐Ÿ”ด {} Error(s):", errors.len()) .red().bold());
741        // Show shortened error list (top 3)
742        for (i, error) in errors.iter().take(3).enumerate() {
743            println!("  {}. {}", i + 1, error);
744        }
745        if errors.len() > 3 {
746            println!("  ... and {} more", errors.len() - 3);
747        }
748    }
749    if !warnings.is_empty() {
750        println!(
751            "\n{}", format!("โš ๏ธ  {} Warning(s):", warnings.len()) .yellow().bold()
752        );
753        // Show shortened warning list (top 3)
754        for (i, warning) in warnings.iter().take(3).enumerate() {
755            println!("  {}. {}", i + 1, warning);
756        }
757        if warnings.len() > 3 {
758            println!("  ... and {} more", warnings.len() - 3);
759        }
760    }
761    println!("{}", "โ•".repeat(60).blue());
762}
763fn display_view_options(
764    errors: &[ParsedError],
765    warnings: &[ParsedWarning],
766    _artifacts: &[parser::CompilerArtifact],
767    _build_scripts: &[parser::BuildScriptExecuted],
768) {
769    println!("\n๐Ÿ” View Options:");
770    println!("  {} - View all errors and warnings", "cm view errors".cyan());
771    println!("  {} - View generated files and locations", "cm view artifacts".cyan());
772    println!("  {} - View build script outputs", "cm view scripts".cyan());
773    println!("  {} - View detailed build history", "cm view history".cyan());
774    println!("  {} - View checklist and fixes", "cm view checklist".cyan());
775    println!("  {} - View all results in one place", "cm view all".cyan());
776    if !errors.is_empty() || !warnings.is_empty() {
777        println!("  {} - Quick view of latest issues", "cm view latest".cyan());
778    }
779    println!("  {} - Open results in file explorer", "cm view open".cyan());
780}
781fn record_build_metrics(
782    args: &[&str],
783    elapsed: Duration,
784    error_count: usize,
785    warning_count: usize,
786    success: bool,
787) {
788    if let Ok(mut tide) = TideCharts::new() {
789        let command = format!("cargo {}", args.join(" "));
790        let profile = determine_profile(args);
791        let features = extract_features(args);
792        let dependencies_compiled = get_dependencies_compiled();
793        let crate_units_compiled = get_crate_units_compiled();
794        let metrics = BuildMetrics {
795            timestamp: Utc::now(),
796            command,
797            duration_seconds: elapsed.as_secs_f64(),
798            success,
799            error_count: error_count.try_into().unwrap(),
800            warning_count: warning_count.try_into().unwrap(),
801            incremental: args.contains(&"--incremental") || args.contains(&"-i"),
802            profile,
803            features,
804            dependencies_compiled: dependencies_compiled.try_into().unwrap(),
805            crate_units_compiled: crate_units_compiled.try_into().unwrap(),
806            memory_peak_mb: None,
807            cpu_usage_percent: None,
808        };
809        if let Err(e) = tide.record_build(metrics) {
810            eprintln!("โš ๏ธ  Failed to record build metrics: {}", e);
811        }
812    }
813}
814fn determine_profile(args: &[&str]) -> String {
815    if args.contains(&"--release") {
816        "release".to_string()
817    } else if args.contains(&"--debug") {
818        "debug".to_string()
819    } else {
820        for (i, arg) in args.iter().enumerate() {
821            if *arg == "--profile" && i + 1 < args.len() {
822                return args[i + 1].to_string();
823            }
824        }
825        "debug".to_string()
826    }
827}
828fn extract_features(args: &[&str]) -> Vec<String> {
829    let mut features = Vec::new();
830    let mut found_features = false;
831    for (i, arg) in args.iter().enumerate() {
832        if *arg == "--features" && i + 1 < args.len() {
833            found_features = true;
834            features = args[i + 1].split(',').map(|s| s.trim().to_string()).collect();
835            break;
836        } else if *arg == "--all-features" {
837            features.push("all-features".to_string());
838            break;
839        } else if *arg == "--no-default-features" {
840            features.push("no-default-features".to_string());
841        }
842    }
843    if !found_features && !args.contains(&"--no-default-features")
844        && !args.contains(&"--all-features")
845    {
846        features.push("default".to_string());
847    }
848    features
849}
850fn get_dependencies_compiled() -> usize {
851    match Command::new("cargo").args(&["metadata", "--format-version", "1"]).output() {
852        Ok(output) if output.status.success() => {
853            if let Ok(metadata) = serde_json::from_slice::<
854                serde_json::Value,
855            >(&output.stdout) {
856                if let Some(packages) = metadata
857                    .get("packages")
858                    .and_then(|p| p.as_array())
859                {
860                    if let Some(root) = metadata.get("root").and_then(|r| r.get("name"))
861                    {
862                        let root_name = root.as_str().unwrap_or("");
863                        return packages
864                            .iter()
865                            .filter(|pkg| {
866                                pkg.get("name")
867                                    .and_then(|n| n.as_str())
868                                    .map(|name| name != root_name)
869                                    .unwrap_or(false)
870                            })
871                            .count();
872                    }
873                }
874            }
875        }
876        _ => {}
877    }
878    0
879}
880fn get_crate_units_compiled() -> usize {
881    0
882}
883pub fn check_first_mate_monitor(command: &str) -> Result<bool, anyhow::Error> {
884    println!(
885        "๐Ÿฅฝ First mate monitoring command '{}' - all hands report!", command.cyan()
886    );
887    let license_manager = license::LicenseManager::new();
888    match license_manager?.enforce_license(command) {
889        Ok(_) => {
890            println!(
891                "โœ… First mate reports: Command '{}' cleared for action!", command
892                .green()
893            );
894            println!("   ๐Ÿฅฝ All crew stations manned - ready to execute!");
895            Ok(true)
896        }
897        Err(e) => {
898            if e.to_string().contains("limit") {
899                println!("โš ๏ธ  First mate's log: Command ration exceeded!");
900                println!("   ๐Ÿฅฝ Resupply at: https://cargo.do/checkout");
901                println!("   ๐Ÿฅฝ Upgrade to unlimited command rations");
902            } else if e.to_string().contains("License not found") {
903                println!("โŒ First mate reports: No command authority papers!");
904                println!("   ๐Ÿฅฝ Commission with 'cm register <key>'");
905            } else {
906                println!(
907                    "โŒ First mate emergency: Command check failed: {}", e.to_string()
908                    .red()
909                );
910                println!("   ๐Ÿฅฝ Secure all stations and alert the captain");
911            }
912            Ok(false)
913        }
914    }
915}
916
917fn handle_publish_version_check() -> Result<(), anyhow::Error> {
918    println!("๐Ÿ“ฆ Checking crates.io for latest version before publish...");
919
920    // Read current package info from Cargo.toml
921    let cargo_toml = fs::read_to_string("Cargo.toml")
922        .context("Failed to read Cargo.toml")?;
923
924    let package_name = extract_package_name(&cargo_toml)?;
925    let current_version = extract_package_version(&cargo_toml)?;
926
927    println!("   ๐Ÿ“ฆ Package: {}", package_name.cyan());
928    println!("   ๐Ÿ“ฆ Current version: {}", current_version.cyan());
929
930    // Query crates.io API for latest version
931    let client = reqwest::blocking::Client::new();
932    let api_url = format!("https://crates.io/api/v1/crates/{}", package_name);
933
934    let response = client
935        .get(&api_url)
936        .send()
937        .context("Failed to query crates.io API")?;
938
939    if !response.status().is_success() {
940        if response.status() == 404 {
941            println!("   ๐Ÿ†• New package - no existing versions on crates.io");
942            return Ok(());
943        }
944        return Err(anyhow::anyhow!("Crates.io API returned status: {}", response.status()));
945    }
946
947    let api_response: serde_json::Value = response.json()
948        .context("Failed to parse crates.io API response")?;
949
950    if let Some(latest_version) = api_response
951        .get("crate")
952        .and_then(|c| c.get("max_version"))
953        .and_then(|v| v.as_str())
954    {
955        println!("   ๐Ÿ“ฆ Latest published version: {}", latest_version.green());
956
957        if latest_version == current_version {
958            println!("   ๐Ÿ”„ Version matches published version - incrementing...");
959            let new_version = increment_version(&current_version)?;
960            println!("   ๐Ÿ“ฆ New version: {} -> {}", current_version.yellow(), new_version.green());
961
962            update_cargo_toml_version(&cargo_toml, &current_version, &new_version)?;
963            println!("   โœ… Cargo.toml updated with new version");
964        } else {
965            println!("   โœ… Version is newer than published version - proceeding with publish");
966        }
967    } else {
968        println!("   โš ๏ธ  Could not determine latest published version");
969    }
970
971    Ok(())
972}
973
974fn extract_package_name(cargo_toml: &str) -> Result<String, anyhow::Error> {
975    for line in cargo_toml.lines() {
976        if line.trim().starts_with("name = ") {
977            let name = line
978                .split('"')
979                .nth(1)
980                .ok_or_else(|| anyhow::anyhow!("Cannot parse package name from Cargo.toml"))?;
981            return Ok(name.to_string());
982        }
983    }
984    Err(anyhow::anyhow!("Package name not found in Cargo.toml"))
985}
986
987fn extract_package_version(cargo_toml: &str) -> Result<String, anyhow::Error> {
988    for line in cargo_toml.lines() {
989        if line.trim().starts_with("version = ") {
990            let version = line
991                .split('"')
992                .nth(1)
993                .ok_or_else(|| anyhow::anyhow!("Cannot parse version from Cargo.toml"))?;
994            return Ok(version.to_string());
995        }
996    }
997    Err(anyhow::anyhow!("Version not found in Cargo.toml"))
998}
999
1000fn increment_version(version: &str) -> Result<String, anyhow::Error> {
1001    let parts: Vec<&str> = version.split('.').collect();
1002    if parts.len() != 3 {
1003        return Err(anyhow::anyhow!("Invalid version format: {}", version));
1004    }
1005
1006    let major: u32 = parts[0].parse()?;
1007    let minor: u32 = parts[1].parse()?;
1008    let patch: u32 = parts[2].parse()?;
1009
1010    // Increment patch version, rolling over to minor if patch is 9
1011    let (new_minor, new_patch) = if patch == 9 {
1012        (minor + 1, 0)
1013    } else {
1014        (minor, patch + 1)
1015    };
1016
1017    Ok(format!("{}.{}.{}", major, new_minor, new_patch))
1018}
1019
1020fn update_cargo_toml_version(cargo_toml: &str, old_version: &str, new_version: &str) -> Result<(), anyhow::Error> {
1021    let new_content = cargo_toml.replace(
1022        &format!("version = \"{}\"", old_version),
1023        &format!("version = \"{}\"", new_version)
1024    );
1025
1026    fs::write("Cargo.toml", new_content)
1027        .context("Failed to update Cargo.toml with new version")?;
1028
1029    Ok(())
1030}