@@ -551,107 +551,128 @@ def cmd_update(args: argparse.Namespace) -> int:
551551 # Total includes both regular tools and multi-version entries
552552 total = len (regular_tools ) + len (multi_version_tools )
553553
554- # Parallel audit with progress tracking, grouped by category
554+ # Parallel audit with progress tracking
555+ # All tools submitted to a single executor for maximum parallelism
556+ # (no per-category sequential bottleneck)
555557 results = []
556558 completed = 0
557559
560+ # ANSI colors for all platforms
561+ GREEN = "\033 [32m"
562+ BOLD_GREEN = "\033 [1;32m"
563+ YELLOW = "\033 [33m"
564+ BLUE = "\033 [34m"
565+ RESET = "\033 [0m"
566+
558567 try :
568+ if regular_tools :
569+ with ThreadPoolExecutor (max_workers = min (MAX_WORKERS , len (regular_tools ))) as executor :
570+ future_to_tool = {executor .submit (audit_tool , tool , None ): tool for tool in regular_tools }
571+
572+ try :
573+ for future in as_completed (future_to_tool ):
574+ tool = future_to_tool [future ]
575+ try :
576+ result = future .result ()
577+ results .append (result )
578+ completed += 1
579+
580+ # Progress
581+ inst = result .get ("installed" , "" )
582+ latest = result .get ("latest_upstream" , "" )
583+ status = result .get ("status" , "" )
584+ cat = tool .category or "general"
585+
586+ # Color the installed version based on status
587+ if status == "UP-TO-DATE" :
588+ inst_color = GREEN
589+ latest_color = GREEN
590+ op = "==="
591+ elif status == "OUTDATED" :
592+ inst_color = YELLOW
593+ latest_color = BOLD_GREEN
594+ op = "!=="
595+ elif status == "CONFLICT" :
596+ inst_color = YELLOW
597+ latest_color = BOLD_GREEN
598+ op = "⚠️"
599+ else : # NOT INSTALLED, UNKNOWN
600+ inst_color = BLUE
601+ latest_color = BLUE
602+ op = "?"
603+
604+ inst_display = inst if inst else "n/a"
605+ latest_display = latest if latest else "n/a"
606+
607+ # Add pinned/skip markers (reuse catalog from outer scope)
608+ markers = []
609+ if catalog .is_pinned (tool .name ):
610+ markers .append ("PINNED" )
611+ if catalog .should_skip (tool .name , latest ):
612+ markers .append ("SKIP" )
613+
614+ marker_str = f" [{ ' ' .join (markers )} ]" if markers else ""
615+ inst_fmt = f"{ inst_color } { inst_display } { RESET } "
616+ latest_fmt = f"{ latest_color } { latest_display } { RESET } "
617+ msg = f"# [{ completed } /{ total } ] [{ cat } ] { tool .name } (installed: { inst_fmt } { op } latest: { latest_fmt } ){ marker_str } "
618+
619+ print (msg , file = sys .stderr , flush = True )
620+
621+ except Exception as e :
622+ completed += 1
623+ print (f"# [{ completed } /{ total } ] { tool .name } (failed: { e } )" , file = sys .stderr , flush = True )
624+
625+ # Add failure entry
626+ results .append ({
627+ "tool" : tool .name ,
628+ "category" : tool .category ,
629+ "installed" : "" ,
630+ "installed_method" : "" ,
631+ "installed_version" : "" ,
632+ "installed_path_selected" : "" ,
633+ "classification_reason_selected" : "Detection failed" ,
634+ "latest_upstream" : "" ,
635+ "latest_version" : "" ,
636+ "upstream_method" : tool .source_kind ,
637+ "status" : "UNKNOWN" ,
638+ "tool_url" : tool_homepage_url (tool ),
639+ "latest_url" : "" ,
640+ "hint" : "" ,
641+ })
642+ except KeyboardInterrupt :
643+ executor .shutdown (wait = False , cancel_futures = True )
644+ raise
645+
646+ # Print grouped summary
647+ print (f"\n # Summary by category:" , file = sys .stderr )
559648 for category in sorted_cats :
560649 cat_tools = categorized [category ]
561650 icon = CATEGORY_ICON .get (category , "📦" )
562651 desc = CATEGORY_DESC .get (category , category )
563- print (f"\n # { icon } { desc } ({ len (cat_tools )} tools)" , file = sys .stderr )
564-
565- with ThreadPoolExecutor (max_workers = min (MAX_WORKERS , len (cat_tools ))) as executor :
566- future_to_tool = {executor .submit (audit_tool , tool , None ): tool for tool in cat_tools }
567-
568- for future in as_completed (future_to_tool ):
569- tool = future_to_tool [future ]
570- try :
571- result = future .result ()
572- results .append (result )
573- completed += 1
574-
575- # Progress
576- inst = result .get ("installed" , "" )
577- latest = result .get ("latest_upstream" , "" )
578- status = result .get ("status" , "" )
579-
580- # ANSI colors for all platforms
581- GREEN = "\033 [32m"
582- BOLD_GREEN = "\033 [1;32m"
583- YELLOW = "\033 [33m"
584- BLUE = "\033 [34m"
585- RESET = "\033 [0m"
586-
587- # Color the installed version based on status
588- if status == "UP-TO-DATE" :
589- inst_color = GREEN
590- latest_color = GREEN
591- op = "==="
592- elif status == "OUTDATED" :
593- inst_color = YELLOW
594- latest_color = BOLD_GREEN # Latest is newer, make it bold green
595- op = "!=="
596- elif status == "CONFLICT" :
597- inst_color = YELLOW
598- latest_color = BOLD_GREEN
599- op = "⚠️"
600- else : # NOT INSTALLED, UNKNOWN
601- inst_color = BLUE # Blue for not-installed
602- latest_color = BLUE
603- op = "?"
604-
605- inst_display = inst if inst else "n/a"
606- latest_display = latest if latest else "n/a"
607-
608- # Add pinned/skip markers
609- from cli_audit .catalog import ToolCatalog
610- catalog = ToolCatalog ()
611- markers = []
612- if catalog .is_pinned (tool .name ):
613- markers .append ("PINNED" )
614- if catalog .should_skip (tool .name , latest ):
615- markers .append ("SKIP" )
616-
617- marker_str = f" [{ ' ' .join (markers )} ]" if markers else ""
618- inst_fmt = f"{ inst_color } { inst_display } { RESET } "
619- latest_fmt = f"{ latest_color } { latest_display } { RESET } "
620- msg = f"# [{ completed } /{ total } ] { tool .name } (installed: { inst_fmt } { op } latest: { latest_fmt } ){ marker_str } "
621-
622- print (msg , file = sys .stderr , flush = True )
623-
624- except Exception as e :
625- completed += 1
626- print (f"# [{ completed } /{ total } ] { tool .name } (failed: { e } )" , file = sys .stderr , flush = True )
627-
628- # Add failure entry
629- results .append ({
630- "tool" : tool .name ,
631- "category" : tool .category ,
632- "installed" : "" ,
633- "installed_method" : "" ,
634- "installed_version" : "" ,
635- "installed_path_selected" : "" ,
636- "classification_reason_selected" : "Detection failed" ,
637- "latest_upstream" : "" ,
638- "latest_version" : "" ,
639- "upstream_method" : tool .source_kind ,
640- "status" : "UNKNOWN" ,
641- "tool_url" : tool_homepage_url (tool ),
642- "latest_url" : "" ,
643- "hint" : "" ,
644- })
652+ # Count statuses for this category
653+ cat_names = {t .name for t in cat_tools }
654+ cat_results = [r for r in results if r .get ("tool" ) in cat_names ]
655+ up_to_date = sum (1 for r in cat_results if r .get ("status" ) == "UP-TO-DATE" )
656+ outdated = sum (1 for r in cat_results if r .get ("status" ) == "OUTDATED" )
657+ not_installed = sum (1 for r in cat_results if r .get ("status" ) == "NOT INSTALLED" )
658+ conflict = sum (1 for r in cat_results if r .get ("status" ) == "CONFLICT" )
659+ unknown = sum (1 for r in cat_results if r .get ("status" ) == "UNKNOWN" )
660+ parts = []
661+ if up_to_date :
662+ parts .append (f"{ GREEN } { up_to_date } current{ RESET } " )
663+ if outdated :
664+ parts .append (f"{ YELLOW } { outdated } outdated{ RESET } " )
665+ if not_installed :
666+ parts .append (f"{ BLUE } { not_installed } missing{ RESET } " )
667+ if conflict :
668+ parts .append (f"{ YELLOW } { conflict } conflict{ RESET } " )
669+ if unknown :
670+ parts .append (f"{ BLUE } { unknown } unknown{ RESET } " )
671+ summary = ", " .join (parts ) if parts else "–"
672+ print (f"# { icon } { desc } : { summary } " , file = sys .stderr )
645673
646674 # Audit multi-version runtimes
647675 if multi_version_tools :
648- # ANSI colors
649- GREEN = "\033 [32m"
650- BOLD_GREEN = "\033 [1;32m"
651- YELLOW = "\033 [33m"
652- BLUE = "\033 [34m"
653- RESET = "\033 [0m"
654-
655676 print (f"\n # 🔄 Multi-version runtimes ({ len (multi_version_tools )} runtimes)" , file = sys .stderr )
656677
657678 for tool_name , (catalog_data , mv_config ) in multi_version_tools .items ():
@@ -686,8 +707,6 @@ def cmd_update(args: argparse.Namespace) -> int:
686707 print (f"# → { versioned_name } : { inst_fmt } { op } { latest_fmt } " , file = sys .stderr , flush = True )
687708
688709 except KeyboardInterrupt :
689- # Shutdown executor immediately without waiting for threads
690- executor .shutdown (wait = False , cancel_futures = True )
691710 print ("\n \n ✗ Interrupted" , file = sys .stderr )
692711 # Reset terminal state before exiting
693712 print ("\033 [0m" , end = "" , file = sys .stderr , flush = True )
0 commit comments