@@ -886,6 +886,9 @@ private function run_cleanup_review( array $assoc_args ): void {
886886 if ( ! empty ($ assoc_args ['safety-probes ' ]) ) {
887887 $ artifact_input ['safety_probes ' ] = true ;
888888 }
889+ if ( isset ($ assoc_args ['sort ' ]) && '' !== trim ( (string ) $ assoc_args ['sort ' ]) ) {
890+ $ artifact_input ['sort ' ] = trim ( (string ) $ assoc_args ['sort ' ]);
891+ }
889892 $ result = $ ability ? $ ability ->execute ($ artifact_input ) : new \WP_Error ('artifact_cleanup_ability_missing ' , 'Artifact cleanup ability not registered. ' );
890893 $ this ->render_worktree_artifact_cleanup_result_from_ability ($ result , $ assoc_args );
891894 return ;
@@ -2580,8 +2583,10 @@ private function renderGitOperationResult( string $operation, array $result, arr
25802583 * metadata older than the compact duration (cleanup only, e.g. 7d, 24h).
25812584 * Candidate worktrees without valid `created_at` metadata are skipped.
25822585 *
2583- * [--sort=<field>]
2584- * : Sort cleanup candidates by reporting field (cleanup only).
2586+ * [--sort=<field>]
2587+ * : Sort cleanup candidates by reporting field. For artifact cleanup,
2588+ * `--sort=size` scans the cheap inventory once and returns the largest
2589+ * artifact opportunities without manual pagination.
25852590 * ---
25862591 * options:
25872592 * - size
@@ -3078,6 +3083,9 @@ public function worktree( array $args, array $assoc_args ): void {
30783083 if ( ! empty ($ assoc_args ['safety-probes ' ]) ) {
30793084 $ input ['safety_probes ' ] = true ;
30803085 }
3086+ if ( isset ($ assoc_args ['sort ' ]) && '' !== trim ( (string ) $ assoc_args ['sort ' ]) ) {
3087+ $ input ['sort ' ] = trim ( (string ) $ assoc_args ['sort ' ]);
3088+ }
30813089 if ( ! empty ($ assoc_args ['apply-plan ' ]) ) {
30823090 $ input ['apply_plan ' ] = $ this ->read_worktree_cleanup_plan ( (string ) $ assoc_args ['apply-plan ' ]);
30833091 }
@@ -4580,6 +4588,10 @@ private function render_worktree_cleanup_result( array $result, array $assoc_arg
45804588 return ;
45814589 }
45824590
4591+ if ( ! $ dry_run ) {
4592+ WP_CLI ::log (sprintf ('Result: removed %d worktree(s); reclaimed %s; skipped %d. ' , (int ) ( $ summary ['removed ' ] ?? count ($ removed ) ), $ this ->format_bytes ($ summary ['bytes_reclaimed ' ] ?? 0 ), (int ) ( $ summary ['skipped ' ] ?? count ($ skipped ) )));
4593+ }
4594+
45834595 WP_CLI ::log ('Summary: ' );
45844596 $ summary_rows = array (
45854597 array (
@@ -4702,27 +4714,32 @@ private function render_worktree_cleanup_result( array $result, array $assoc_arg
47024714
47034715 if ( ! empty ($ skipped ) ) {
47044716 WP_CLI ::log ('' );
4705- WP_CLI ::log ('Skipped: ' );
4706- $ skipped_rows = array_map (
4707- fn ( $ s ) => array (
4708- 'handle ' => $ s ['handle ' ] ?? '' ,
4709- 'reason_code ' => $ s ['reason_code ' ] ?? '' ,
4710- 'reason ' => $ verbose ? ( $ s ['reason ' ] ?? '' ) : $ this ->shorten_cleanup_reason ( (string ) ( $ s ['reason ' ] ?? '' )),
4711- 'age_days ' => $ s ['age_days ' ] ?? '' ,
4712- 'size ' => $ this ->format_bytes ($ s ['size_bytes ' ] ?? null ),
4713- 'artifacts ' => $ this ->format_bytes ($ s ['artifact_size_bytes ' ] ?? 0 ),
4714- 'repo ' => $ s ['repo ' ] ?? '' ,
4715- 'branch ' => $ s ['branch ' ] ?? '' ,
4716- 'path ' => $ s ['path ' ] ?? '' ,
4717- 'primary_path ' => $ s ['primary_path ' ] ?? '' ,
4718- 'missing ' => implode (', ' , (array ) ( $ s ['missing_fields ' ] ?? array () )),
4719- 'hint ' => $ s ['hint ' ] ?? '' ,
4720- ),
4721- array_slice ($ skipped , 0 , $ limit )
4722- );
4723- $ fields = $ verbose ? array ( 'handle ' , 'reason_code ' , 'reason ' , 'age_days ' , 'size ' , 'artifacts ' , 'repo ' , 'branch ' , 'path ' , 'primary_path ' , 'missing ' , 'hint ' ) : array ( 'handle ' , 'reason_code ' , 'age_days ' , 'size ' , 'artifacts ' , 'reason ' );
4724- $ this ->format_items ($ skipped_rows , $ fields , array ( 'format ' => 'table ' ), 'handle ' );
4725- $ this ->render_cleanup_truncation_hint (count ($ skipped ), $ limit , 'skipped rows ' );
4717+ if ( $ verbose ) {
4718+ WP_CLI ::log ('Skipped: ' );
4719+ $ skipped_rows = array_map (
4720+ fn ( $ s ) => array (
4721+ 'handle ' => $ s ['handle ' ] ?? '' ,
4722+ 'reason_code ' => $ s ['reason_code ' ] ?? '' ,
4723+ 'reason ' => $ s ['reason ' ] ?? '' ,
4724+ 'age_days ' => $ s ['age_days ' ] ?? '' ,
4725+ 'size ' => $ this ->format_bytes ($ s ['size_bytes ' ] ?? null ),
4726+ 'artifacts ' => $ this ->format_bytes ($ s ['artifact_size_bytes ' ] ?? 0 ),
4727+ 'repo ' => $ s ['repo ' ] ?? '' ,
4728+ 'branch ' => $ s ['branch ' ] ?? '' ,
4729+ 'path ' => $ s ['path ' ] ?? '' ,
4730+ 'primary_path ' => $ s ['primary_path ' ] ?? '' ,
4731+ 'missing ' => implode (', ' , (array ) ( $ s ['missing_fields ' ] ?? array () )),
4732+ 'hint ' => $ s ['hint ' ] ?? '' ,
4733+ ),
4734+ array_slice ($ skipped , 0 , $ limit )
4735+ );
4736+ $ this ->format_items ($ skipped_rows , array ( 'handle ' , 'reason_code ' , 'reason ' , 'age_days ' , 'size ' , 'artifacts ' , 'repo ' , 'branch ' , 'path ' , 'primary_path ' , 'missing ' , 'hint ' ), array ( 'format ' => 'table ' ), 'handle ' );
4737+ $ this ->render_cleanup_truncation_hint (count ($ skipped ), $ limit , 'skipped rows ' );
4738+ } else {
4739+ WP_CLI ::log ('Skipped summary: ' );
4740+ $ this ->format_items ($ this ->summarize_cleanup_skipped_rows ($ skipped ), array ( 'reason_code ' , 'count ' , 'examples ' ), array ( 'format ' => 'table ' ), 'reason_code ' );
4741+ WP_CLI ::log ('Re-run with --verbose to list every skipped row or --only=<reason_code> to inspect one bucket. ' );
4742+ }
47264743 }
47274744
47284745 WP_CLI ::log ('' );
@@ -5455,6 +5472,8 @@ private function render_worktree_artifact_cleanup_result( array $result, array $
54555472 $ skipped = (array ) ( $ result ['skipped ' ] ?? array () );
54565473 $ summary = (array ) ( $ result ['summary ' ] ?? array () );
54575474 $ dry_run = ! empty ($ result ['dry_run ' ]);
5475+ $ verbose = ! empty ($ assoc_args ['verbose ' ]);
5476+ $ pagination = $ result ['pagination ' ] ?? ( $ summary ['pagination ' ] ?? null );
54585477
54595478 if ( empty ($ candidates ) && empty ($ removed ) && empty ($ skipped ) ) {
54605479 WP_CLI ::log ('No worktree artifacts found. ' );
@@ -5488,7 +5507,7 @@ private function render_worktree_artifact_cleanup_result( array $result, array $
54885507
54895508 if ( ! empty ($ candidates ) ) {
54905509 WP_CLI ::log ('' );
5491- WP_CLI ::log ($ dry_run ? ' Would remove artifacts: ' : 'Artifact candidates: ' );
5510+ WP_CLI ::log ($ dry_run && is_array ( $ pagination ) && ' size ' === ( string ) ( $ pagination [ ' sort ' ] ?? '' ) ? ' Largest artifact opportunities: ' : ( $ dry_run ? ' Would remove artifacts: ' : 'Artifact candidates: ' ) );
54925511 $ this ->format_items ($ this ->flatten_artifact_cleanup_rows ($ candidates ), array ( 'handle ' , 'repo ' , 'branch ' , 'artifact ' , 'size ' , 'path ' ), array ( 'format ' => 'table ' ), 'handle ' );
54935512 }
54945513
@@ -5500,24 +5519,29 @@ private function render_worktree_artifact_cleanup_result( array $result, array $
55005519
55015520 if ( ! empty ($ skipped ) ) {
55025521 WP_CLI ::log ('' );
5503- WP_CLI ::log ('Skipped worktrees: ' );
5504- $ rows = array_map (
5505- fn ( $ row ) => array (
5506- 'handle ' => $ row ['handle ' ] ?? '' ,
5507- 'repo ' => $ row ['repo ' ] ?? '' ,
5508- 'branch ' => $ row ['branch ' ] ?? '' ,
5509- 'artifacts ' => count ( (array ) ( $ row ['artifacts ' ] ?? array () )),
5510- 'reason_code ' => $ row ['reason_code ' ] ?? '' ,
5511- 'reason ' => $ row ['reason ' ] ?? '' ,
5512- ),
5513- $ skipped
5514- );
5515- $ this ->format_items ($ rows , array ( 'handle ' , 'repo ' , 'branch ' , 'artifacts ' , 'reason_code ' , 'reason ' ), array ( 'format ' => 'table ' ), 'handle ' );
5522+ if ( $ verbose ) {
5523+ WP_CLI ::log ('Skipped worktrees: ' );
5524+ $ rows = array_map (
5525+ fn ( $ row ) => array (
5526+ 'handle ' => $ row ['handle ' ] ?? '' ,
5527+ 'repo ' => $ row ['repo ' ] ?? '' ,
5528+ 'branch ' => $ row ['branch ' ] ?? '' ,
5529+ 'artifacts ' => count ( (array ) ( $ row ['artifacts ' ] ?? array () )),
5530+ 'reason_code ' => $ row ['reason_code ' ] ?? '' ,
5531+ 'reason ' => $ row ['reason ' ] ?? '' ,
5532+ ),
5533+ $ skipped
5534+ );
5535+ $ this ->format_items ($ rows , array ( 'handle ' , 'repo ' , 'branch ' , 'artifacts ' , 'reason_code ' , 'reason ' ), array ( 'format ' => 'table ' ), 'handle ' );
5536+ } else {
5537+ WP_CLI ::log ('Skipped worktrees summary: ' );
5538+ $ this ->format_items ($ this ->summarize_cleanup_skipped_rows ($ skipped ), array ( 'reason_code ' , 'count ' , 'examples ' ), array ( 'format ' => 'table ' ), 'reason_code ' );
5539+ WP_CLI ::log ('Re-run with --verbose to list every skipped worktree. ' );
5540+ }
55165541 }
55175542
55185543 WP_CLI ::log ('' );
55195544
5520- $ pagination = $ result ['pagination ' ] ?? ( $ summary ['pagination ' ] ?? null );
55215545 if ( is_array ($ pagination ) ) {
55225546 $ mode_label = (string ) ( $ pagination ['mode ' ] ?? 'bounded_inventory ' );
55235547 WP_CLI ::log (
@@ -5534,6 +5558,8 @@ private function render_worktree_artifact_cleanup_result( array $result, array $
55345558 );
55355559 if ( ! empty ($ pagination ['partial ' ]) && isset ($ pagination ['next_offset ' ]) ) {
55365560 WP_CLI ::log (sprintf ('Partial scan — re-run with --offset=%d to continue, or pass --exhaustive for a full audit. ' , (int ) $ pagination ['next_offset ' ]));
5561+ } elseif ( 'size ' === (string ) ( $ pagination ['sort ' ] ?? '' ) ) {
5562+ WP_CLI ::log (sprintf ('Ranked by size across %d scanned worktree(s); showing the largest %d candidate(s). ' , (int ) ( $ pagination ['scanned ' ] ?? 0 ), count ($ candidates )));
55375563 }
55385564 WP_CLI ::log ('' );
55395565 }
@@ -5595,6 +5621,11 @@ private function render_worktree_bounded_cleanup_eligible_apply_result( array $r
55955621 $ continuation = (array ) ( $ result ['continuation ' ] ?? array () );
55965622 $ dry_run = ! empty ($ result ['dry_run ' ]);
55975623 $ job_backed = ! empty ($ result ['job_backed ' ]);
5624+ $ verbose = ! empty ($ assoc_args ['verbose ' ]);
5625+
5626+ if ( ! $ dry_run ) {
5627+ WP_CLI ::log (sprintf ('Result: removed %d worktree(s); reclaimed %s; skipped %d. ' , (int ) ( $ summary ['removed ' ] ?? count ($ removed ) ), $ this ->format_bytes ($ summary ['bytes_reclaimed ' ] ?? 0 ), (int ) ( $ summary ['skipped ' ] ?? count ($ skipped ) )));
5628+ }
55985629
55995630 WP_CLI ::log ('Bounded cleanup apply summary: ' );
56005631 $ summary_rows = array (
@@ -5666,16 +5697,22 @@ private function render_worktree_bounded_cleanup_eligible_apply_result( array $r
56665697
56675698 if ( ! empty ($ skipped ) ) {
56685699 WP_CLI ::log ('' );
5669- WP_CLI ::log ('Skipped: ' );
5670- $ rows = array_map (
5671- fn ( $ row ) => array (
5672- 'handle ' => $ row ['handle ' ] ?? '' ,
5673- 'reason_code ' => $ row ['reason_code ' ] ?? '' ,
5674- 'reason ' => $ this ->shorten_cleanup_reason ( (string ) ( $ row ['reason ' ] ?? '' )),
5675- ),
5676- $ skipped
5677- );
5678- $ this ->format_items ($ rows , array ( 'handle ' , 'reason_code ' , 'reason ' ), array ( 'format ' => 'table ' ), 'handle ' );
5700+ if ( $ verbose ) {
5701+ WP_CLI ::log ('Skipped: ' );
5702+ $ rows = array_map (
5703+ fn ( $ row ) => array (
5704+ 'handle ' => $ row ['handle ' ] ?? '' ,
5705+ 'reason_code ' => $ row ['reason_code ' ] ?? '' ,
5706+ 'reason ' => $ this ->shorten_cleanup_reason ( (string ) ( $ row ['reason ' ] ?? '' )),
5707+ ),
5708+ $ skipped
5709+ );
5710+ $ this ->format_items ($ rows , array ( 'handle ' , 'reason_code ' , 'reason ' ), array ( 'format ' => 'table ' ), 'handle ' );
5711+ } else {
5712+ WP_CLI ::log ('Skipped summary: ' );
5713+ $ this ->format_items ($ this ->summarize_cleanup_skipped_rows ($ skipped ), array ( 'reason_code ' , 'count ' , 'examples ' ), array ( 'format ' => 'table ' ), 'reason_code ' );
5714+ WP_CLI ::log ('Re-run with --verbose to list every skipped row. ' );
5715+ }
56795716 }
56805717
56815718 WP_CLI ::log ('' );
@@ -5941,6 +5978,43 @@ private function render_cleanup_truncation_hint( int $total, int $limit, string
59415978 WP_CLI ::log (sprintf ('Showing %d of %d %s. Re-run with --verbose for all rows or --only=<reason_code> to filter. ' , $ limit , $ total , $ label ));
59425979 }
59435980
5981+ /**
5982+ * Summarize skipped cleanup rows by reason with representative handles.
5983+ *
5984+ * @param array<int,array<string,mixed>> $skipped Skipped rows.
5985+ * @return array<int,array<string,mixed>>
5986+ */
5987+ private function summarize_cleanup_skipped_rows ( array $ skipped ): array {
5988+ $ summary = array ();
5989+ foreach ( $ skipped as $ row ) {
5990+ $ reason_code = (string ) ( $ row ['reason_code ' ] ?? 'unknown ' );
5991+ if ( ! isset ($ summary [ $ reason_code ]) ) {
5992+ $ summary [ $ reason_code ] = array (
5993+ 'reason_code ' => $ reason_code ,
5994+ 'count ' => 0 ,
5995+ 'examples ' => array (),
5996+ );
5997+ }
5998+ ++$ summary [ $ reason_code ]['count ' ];
5999+ $ handle = (string ) ( $ row ['handle ' ] ?? '' );
6000+ if ( '' !== $ handle && count ($ summary [ $ reason_code ]['examples ' ]) < 3 ) {
6001+ $ summary [ $ reason_code ]['examples ' ][] = $ handle ;
6002+ }
6003+ }
6004+
6005+ ksort ($ summary );
6006+ return array_values (
6007+ array_map (
6008+ fn ( $ row ) => array (
6009+ 'reason_code ' => $ row ['reason_code ' ],
6010+ 'count ' => $ row ['count ' ],
6011+ 'examples ' => implode (', ' , $ row ['examples ' ]),
6012+ ),
6013+ $ summary
6014+ )
6015+ );
6016+ }
6017+
59446018 /**
59456019 * Format a byte count without depending on WordPress helpers in smoke tests.
59466020 *
0 commit comments