@@ -116,20 +116,25 @@ public function worktree_cleanup_artifacts( array $opts = array() ): array|\WP_E
116116 }
117117
118118 if ( $ rank_by_size ) {
119- usort ($ candidates , fn ( $ a , $ b ) => (int ) ( $ b ['artifact_size_bytes ' ] ?? 0 ) <=> (int ) ( $ a ['artifact_size_bytes ' ] ?? 0 ));
119+ usort ($ candidates , function ( $ a , $ b ): int {
120+ $ size = (int ) ( $ b ['artifact_size_bytes ' ] ?? 0 ) <=> (int ) ( $ a ['artifact_size_bytes ' ] ?? 0 );
121+ return 0 !== $ size ? $ size : strcmp ( (string ) ( $ a ['handle ' ] ?? '' ), (string ) ( $ b ['handle ' ] ?? '' ));
122+ });
120123 $ total_ranked = count ($ candidates );
124+ $ rank_offset = min ($ offset , $ total_ranked );
121125 if ( $ limit > 0 ) {
122- $ candidates = array_slice ($ candidates , 0 , $ limit );
126+ $ candidates = array_slice ($ candidates , $ rank_offset , $ limit );
123127 }
128+ $ rank_end = $ limit > 0 ? min ($ rank_offset + $ limit , $ total_ranked ) : $ total_ranked ;
124129 $ pagination = array (
125130 'mode ' => 'ranked_inventory ' ,
126131 'limit ' => $ limit ,
127- 'offset ' => 0 ,
132+ 'offset ' => $ rank_offset ,
128133 'scanned ' => (int ) ( $ pagination ['scanned ' ] ?? 0 ),
129134 'total ' => (int ) ( $ pagination ['total ' ] ?? 0 ),
130- 'complete ' => true ,
131- 'partial ' => false ,
132- 'next_offset ' => null ,
135+ 'complete ' => $ rank_end >= $ total_ranked ,
136+ 'partial ' => $ rank_end < $ total_ranked ,
137+ 'next_offset ' => $ rank_end < $ total_ranked ? $ rank_end : null ,
133138 'safety_probes ' => $ safety_probes ,
134139 'sort ' => 'size ' ,
135140 'ranked_total ' => $ total_ranked ,
@@ -432,15 +437,19 @@ private function build_worktree_artifact_cleanup_plan( bool $force, array $opts
432437 }
433438 }
434439 if ( $ dirty_count > 0 && ! $ force ) {
435- $ skipped [] = array_merge (
436- $ base_row , array (
437- 'reason_code ' => 'dirty_worktree ' ,
438- 'reason ' => sprintf ('working tree dirty (%d files) - pass force=true to override artifact cleanup only ' , $ dirty_count ),
439- 'dirty ' => $ dirty_count ,
440- 'artifacts ' => $ artifacts ,
441- )
442- );
443- continue ;
440+ $ artifact_dirty = $ this ->classify_artifact_only_dirty_worktree ($ repo , $ wt_path );
441+ if ( null === $ artifact_dirty ) {
442+ $ skipped [] = array_merge (
443+ $ base_row , array (
444+ 'reason_code ' => 'dirty_worktree ' ,
445+ 'reason ' => sprintf ('working tree contains source changes (%d dirty paths) - leaving artifacts in place ' , $ dirty_count ),
446+ 'dirty ' => $ dirty_count ,
447+ 'artifacts ' => $ artifacts ,
448+ )
449+ );
450+ continue ;
451+ }
452+ $ base_row ['artifact_dirty_paths ' ] = $ artifact_dirty ['paths ' ];
444453 }
445454
446455 if ( null === $ stale_marker_recovery ) {
@@ -549,7 +558,7 @@ private function build_worktree_artifact_cleanup_summary( array $candidates, arr
549558
550559 foreach ( $ removed as $ row ) {
551560 foreach ( (array ) ( $ row ['artifacts ' ] ?? array () ) as $ artifact ) {
552- $ removed_bytes += max (0 , (int ) ( is_array ($ artifact ) ? ( $ artifact ['size_bytes ' ] ?? 0 ) : 0 ));
561+ $ removed_bytes += max (0 , (int ) ( is_array ($ artifact ) ? ( $ artifact ['removal ' ][ ' bytes_reclaimed ' ] ?? $ artifact [ ' size_bytes ' ] ?? 0 ) : 0 ));
553562 ++$ removed_count ;
554563 }
555564 }
@@ -696,6 +705,11 @@ private function scope_worktree_artifact_cleanup_to_plan( array $planned_candida
696705 $ mismatches [] = 'artifact: ' . $ relative ;
697706 continue ;
698707 }
708+ if ( is_array ($ planned_artifact ) && array_key_exists ('size_bytes ' , $ planned_artifact )
709+ && (int ) $ planned_artifact ['size_bytes ' ] !== (int ) ( $ current_artifacts [ $ relative ]['size_bytes ' ] ?? -1 ) ) {
710+ $ mismatches [] = 'artifact_size: ' . $ relative ;
711+ continue ;
712+ }
699713 $ artifacts [] = $ current_artifacts [ $ relative ];
700714 }
701715
@@ -758,6 +772,7 @@ private function remove_worktree_artifact_path( string $worktree_path, string $r
758772 if ( ! $ artifact_validation ['valid ' ] || '' === $ artifact_real || $ artifact_real === $ worktree_real ) {
759773 return new \WP_Error ('artifact_path_outside_worktree ' , sprintf ('Refusing artifact cleanup for %s: %s ' , $ relative , $ artifact_validation ['message ' ] ?? '' ), array ( 'status ' => 403 ));
760774 }
775+ $ bytes_reclaimed = max (0 , $ this ->estimate_path_size_bytes ($ artifact_real ));
761776
762777 $ output = array ();
763778 $ exit = 0 ;
@@ -778,10 +793,11 @@ private function remove_worktree_artifact_path( string $worktree_path, string $r
778793 }
779794
780795 return array (
781- 'resolved_path ' => $ artifact_real ,
782- 'exit_code ' => $ exit ,
783- 'exists_after ' => false ,
784- 'verified_at ' => gmdate ('c ' ),
796+ 'resolved_path ' => $ artifact_real ,
797+ 'bytes_reclaimed ' => $ bytes_reclaimed ,
798+ 'exit_code ' => $ exit ,
799+ 'exists_after ' => false ,
800+ 'verified_at ' => gmdate ('c ' ),
785801 );
786802 }
787803
0 commit comments