@@ -600,6 +600,10 @@ public function adopt_repo( array $args, array $assoc_args ): void {
600600 * [--older-than=<duration>]
601601 * : Pass an age gate such as 7d or 24h into cleanup task params.
602602 *
603+ * [--top=<count>]
604+ * : For `plan`, number of largest reclaimable paths to show in the upfront
605+ * summary. Defaults to 10.
606+ *
603607 * [--limit=<count>]
604608 * : For DB-backed `apply` / `resume`, maximum pending rows to process in this
605609 * invocation (default 25, max 100). For `--mode=artifacts` pages, maximum
@@ -942,6 +946,9 @@ private function run_cleanup_plan( array $assoc_args ): void {
942946 if ( isset ($ assoc_args ['older-than ' ]) && '' !== trim ( (string ) $ assoc_args ['older-than ' ]) ) {
943947 $ input ['worktree_older_than ' ] = trim ( (string ) $ assoc_args ['older-than ' ]);
944948 }
949+ if ( isset ($ assoc_args ['top ' ]) ) {
950+ $ input ['top_n ' ] = (int ) $ assoc_args ['top ' ];
951+ }
945952 if ( isset ($ assoc_args ['force ' ]) ) {
946953 $ input ['force_artifact_cleanup ' ] = (bool ) $ assoc_args ['force ' ];
947954 }
@@ -1399,12 +1406,131 @@ private function render_cleanup_plan_result( array $result, array $assoc_args ):
13991406 WP_CLI ::log (sprintf ('Rows: %d ' , (int ) ( $ summary ['total_rows ' ] ?? 0 )));
14001407 WP_CLI ::log (sprintf ('Bytes: %s ' , $ this ->format_bytes ($ summary ['total_size_bytes ' ] ?? 0 )));
14011408 WP_CLI ::log (sprintf ('Apply: wp datamachine-code workspace cleanup apply %s ' , (string ) ( $ result ['run_id ' ] ?? '' )));
1409+ $ this ->render_cleanup_plan_category_totals ( (array ) ( $ summary ['category_totals ' ] ?? array () ) );
1410+ $ this ->render_cleanup_plan_top_reclaimable ( (array ) ( $ summary ['top_reclaimable ' ] ?? array () ) );
1411+ $ this ->render_cleanup_plan_blockers ( (array ) ( $ summary ['blockers ' ] ?? array () ) );
1412+ $ this ->render_cleanup_plan_recommended_commands ( (array ) ( $ summary ['recommended_commands ' ] ?? array () ), (string ) ( $ result ['run_id ' ] ?? '' ) );
14021413 $ inputs = (array ) ( $ result ['inputs ' ] ?? array () );
14031414 if ( empty ($ inputs ['include_artifacts ' ]) ) {
14041415 WP_CLI ::log ('Artifacts: skipped for bounded retention planning; run `wp datamachine-code workspace cleanup plan --mode=artifacts` when you want artifact rows. ' );
14051416 }
14061417 }
14071418
1419+ /**
1420+ * Render reclaimable cleanup bytes by category.
1421+ *
1422+ * @param array<string,int> $totals Category totals.
1423+ */
1424+ private function render_cleanup_plan_category_totals ( array $ totals ): void {
1425+ if ( array () === $ totals ) {
1426+ return ;
1427+ }
1428+
1429+ WP_CLI ::log ('' );
1430+ WP_CLI ::log ('Reclaimable space by category: ' );
1431+ $ labels = array (
1432+ 'whole_worktrees ' => 'whole worktrees ' ,
1433+ 'dependency_artifacts ' => 'dependency artifacts ' ,
1434+ 'build_outputs ' => 'build outputs ' ,
1435+ 'caches ' => 'caches ' ,
1436+ );
1437+ $ rows = array ();
1438+ foreach ( $ labels as $ category => $ label ) {
1439+ $ rows [] = array (
1440+ 'category ' => $ label ,
1441+ 'bytes ' => $ this ->format_bytes ($ totals [ $ category ] ?? 0 ),
1442+ );
1443+ }
1444+ $ this ->format_items ($ rows , array ( 'category ' , 'bytes ' ), array ( 'format ' => 'table ' ), 'category ' );
1445+ }
1446+
1447+ /**
1448+ * Render largest reclaimable paths.
1449+ *
1450+ * @param array<int,array<string,mixed>> $paths Top paths.
1451+ */
1452+ private function render_cleanup_plan_top_reclaimable ( array $ paths ): void {
1453+ if ( array () === $ paths ) {
1454+ return ;
1455+ }
1456+
1457+ WP_CLI ::log ('' );
1458+ WP_CLI ::log ('Top reclaimable paths: ' );
1459+ $ rows = array_map (
1460+ fn ( $ row ) => array (
1461+ 'size ' => is_array ($ row ) ? $ this ->format_bytes ($ row ['size_bytes ' ] ?? 0 ) : '0 B ' ,
1462+ 'category ' => is_array ($ row ) ? (string ) ( $ row ['category ' ] ?? '' ) : '' ,
1463+ 'risk ' => is_array ($ row ) ? (string ) ( $ row ['safety_class ' ] ?? '' ) : '' ,
1464+ 'handle ' => is_array ($ row ) ? (string ) ( $ row ['handle ' ] ?? '' ) : '' ,
1465+ 'path ' => is_array ($ row ) ? (string ) ( $ row ['path ' ] ?? '' ) : '' ,
1466+ ),
1467+ $ paths
1468+ );
1469+ $ this ->format_items ($ rows , array ( 'size ' , 'category ' , 'risk ' , 'handle ' , 'path ' ), array ( 'format ' => 'table ' ), 'size ' );
1470+ }
1471+
1472+ /**
1473+ * Render blockers grouped by reason and repo.
1474+ *
1475+ * @param array<string,array<string,mixed>> $blockers Blocker buckets.
1476+ */
1477+ private function render_cleanup_plan_blockers ( array $ blockers ): void {
1478+ if ( array () === $ blockers ) {
1479+ return ;
1480+ }
1481+
1482+ WP_CLI ::log ('' );
1483+ WP_CLI ::log ('Blockers by reason and repo: ' );
1484+ $ rows = array ();
1485+ foreach ( $ blockers as $ reason => $ bucket ) {
1486+ $ bucket = (array ) $ bucket ;
1487+ $ repos = array ();
1488+ foreach ( (array ) ( $ bucket ['repos ' ] ?? array () ) as $ repo => $ repo_bucket ) {
1489+ $ repo_bucket = (array ) $ repo_bucket ;
1490+ $ repos [] = sprintf ('%s=%d ' , (string ) $ repo , (int ) ( $ repo_bucket ['count ' ] ?? 0 ));
1491+ }
1492+ $ rows [] = array (
1493+ 'reason ' => (string ) $ reason ,
1494+ 'count ' => (int ) ( $ bucket ['count ' ] ?? 0 ),
1495+ 'bytes ' => $ this ->format_bytes ($ bucket ['size_bytes ' ] ?? 0 ),
1496+ 'repos ' => implode (', ' , array_slice ($ repos , 0 , 5 )),
1497+ 'examples ' => implode (', ' , array_slice (array_map ('strval ' , (array ) ( $ bucket ['examples ' ] ?? array () )), 0 , 5 )),
1498+ );
1499+ }
1500+ $ this ->format_items ($ rows , array ( 'reason ' , 'count ' , 'bytes ' , 'repos ' , 'examples ' ), array ( 'format ' => 'table ' ), 'reason ' );
1501+ }
1502+
1503+ /**
1504+ * Render directly executable recommended cleanup commands.
1505+ *
1506+ * @param array<int,array<string,string>> $commands Recommended commands.
1507+ * @param string $run_id Cleanup run ID.
1508+ */
1509+ private function render_cleanup_plan_recommended_commands ( array $ commands , string $ run_id ): void {
1510+ if ( array () === $ commands ) {
1511+ return ;
1512+ }
1513+
1514+ WP_CLI ::log ('' );
1515+ WP_CLI ::log ('Recommended commands: ' );
1516+ $ rows = array_map (
1517+ function ( $ row ) use ( $ run_id ): array {
1518+ $ row = (array ) $ row ;
1519+ $ command = (string ) ( $ row ['command ' ] ?? '' );
1520+ if ( '' !== $ run_id ) {
1521+ $ command = str_replace ('<run-id> ' , $ run_id , $ command );
1522+ }
1523+ return array (
1524+ 'label ' => (string ) ( $ row ['label ' ] ?? '' ),
1525+ 'risk ' => (string ) ( $ row ['risk ' ] ?? '' ),
1526+ 'command ' => $ command ,
1527+ );
1528+ },
1529+ $ commands
1530+ );
1531+ $ this ->format_items ($ rows , array ( 'label ' , 'risk ' , 'command ' ), array ( 'format ' => 'table ' ), 'label ' );
1532+ }
1533+
14081534 private function cleanup_run_id ( int $ job_id ): string {
14091535 return 'cleanup-run- ' . $ job_id ;
14101536 }
0 commit comments