@@ -271,10 +271,13 @@ private function prepare_cleanup_plan_blocked_rows( string $type, array $rows ):
271271 if ( ! is_array ($ row ) ) {
272272 continue ;
273273 }
274- $ row ['row_type ' ] = $ type . '_blocked ' ;
275- $ row ['safety_class ' ] = 'blocked ' ;
276- $ row ['row_id ' ] = $ this ->stable_cleanup_row_id ($ type . '_blocked ' , $ row );
277- $ result [] = $ row ;
274+ $ accounting = $ this ->cleanup_plan_row_size_accounting ($ row );
275+ $ row ['size_status ' ] = $ accounting ['status ' ];
276+ $ row ['size_accounting ' ] = $ accounting ;
277+ $ row ['row_type ' ] = $ type . '_blocked ' ;
278+ $ row ['safety_class ' ] = 'blocked ' ;
279+ $ row ['row_id ' ] = $ this ->stable_cleanup_row_id ($ type . '_blocked ' , $ row );
280+ $ result [] = $ row ;
278281 }
279282 return $ result ;
280283 }
@@ -485,7 +488,60 @@ private function build_cleanup_plan_continuation( array $artifact_plan, array $w
485488 * @return int
486489 */
487490 private function cleanup_plan_reclaimable_bytes ( array $ row ): int {
488- return max (0 , (int ) ( $ row ['artifact_size_bytes ' ] ?? $ row ['size_bytes ' ] ?? 0 ));
491+ $ accounting = $ this ->cleanup_plan_row_size_accounting ($ row );
492+ return null === $ accounting ['bytes ' ] ? 0 : max (0 , (int ) $ accounting ['bytes ' ]);
493+ }
494+
495+ /**
496+ * Return explicit size accounting for a cleanup row.
497+ *
498+ * @param array<string,mixed> $row Cleanup row.
499+ * @return array<string,mixed>
500+ */
501+ private function cleanup_plan_row_size_accounting ( array $ row ): array {
502+ foreach ( array ( 'artifact_size_bytes ' , 'size_bytes ' , 'bytes_reclaimed ' ) as $ field ) {
503+ if ( array_key_exists ($ field , $ row ) && is_numeric ($ row [ $ field ]) ) {
504+ $ bytes = max (0 , (int ) $ row [ $ field ]);
505+ return array (
506+ 'status ' => 0 === $ bytes ? 'known_zero ' : 'known ' ,
507+ 'bytes ' => $ bytes ,
508+ 'source ' => $ field ,
509+ );
510+ }
511+ }
512+
513+ $ artifact_bytes = 0 ;
514+ $ artifact_known = false ;
515+ foreach ( (array ) ( $ row ['artifacts ' ] ?? array () ) as $ artifact ) {
516+ if ( is_array ($ artifact ) && array_key_exists ('size_bytes ' , $ artifact ) && is_numeric ($ artifact ['size_bytes ' ]) ) {
517+ $ artifact_known = true ;
518+ $ artifact_bytes += max (0 , (int ) $ artifact ['size_bytes ' ]);
519+ }
520+ }
521+ if ( $ artifact_known ) {
522+ return array (
523+ 'status ' => 0 === $ artifact_bytes ? 'known_zero ' : 'known ' ,
524+ 'bytes ' => $ artifact_bytes ,
525+ 'source ' => 'artifacts.size_bytes ' ,
526+ );
527+ }
528+
529+ $ skipped = array_map ('strval ' , (array ) ( $ row ['fields_skipped ' ] ?? array () ));
530+ if ( array_intersect ($ skipped , array ( 'disk ' , 'size ' , 'sizes ' )) ) {
531+ return array (
532+ 'status ' => 'skipped ' ,
533+ 'bytes ' => null ,
534+ 'source ' => null ,
535+ 'reason ' => 'disk_size_probe_skipped ' ,
536+ );
537+ }
538+
539+ return array (
540+ 'status ' => 'unknown ' ,
541+ 'bytes ' => null ,
542+ 'source ' => null ,
543+ 'reason ' => 'size_not_reported ' ,
544+ );
489545 }
490546
491547 /**
@@ -630,27 +686,51 @@ private function cleanup_plan_blockers( array $artifact_plan, array $worktree_pl
630686 if ( '' === $ repo ) {
631687 $ repo = 'unknown ' ;
632688 }
633- $ bytes = max (0 , (int ) ( $ row ['artifact_size_bytes ' ] ?? $ row ['size_bytes ' ] ?? 0 ));
689+ $ accounting = $ this ->cleanup_plan_row_size_accounting ($ row );
690+ $ bytes = null === $ accounting ['bytes ' ] ? 0 : max (0 , (int ) $ accounting ['bytes ' ]);
691+ $ status = (string ) $ accounting ['status ' ];
634692 $ blockers [ $ reason ] ??= array (
635- 'count ' => 0 ,
636- 'size_bytes ' => 0 ,
637- 'repos ' => array (),
638- 'examples ' => array (),
693+ 'count ' => 0 ,
694+ 'size_bytes ' => 0 ,
695+ 'size_accounting ' => array (
696+ 'known_bytes ' => 0 ,
697+ 'known_count ' => 0 ,
698+ 'known_zero_count ' => 0 ,
699+ 'skipped_count ' => 0 ,
700+ 'unknown_count ' => 0 ,
701+ ),
702+ 'repos ' => array (),
703+ 'examples ' => array (),
639704 );
640705 $ blockers [ $ reason ]['count ' ] = (int ) $ blockers [ $ reason ]['count ' ] + 1 ;
641706 $ blockers [ $ reason ]['size_bytes ' ] += $ bytes ;
707+ $ blockers [ $ reason ]['size_accounting ' ] = $ this ->cleanup_plan_add_size_accounting ($ blockers [ $ reason ]['size_accounting ' ], $ accounting );
642708 $ blockers [ $ reason ]['repos ' ][ $ repo ] ??= array (
643- 'count ' => 0 ,
644- 'size_bytes ' => 0 ,
645- 'examples ' => array (),
709+ 'count ' => 0 ,
710+ 'size_bytes ' => 0 ,
711+ 'size_accounting ' => array (
712+ 'known_bytes ' => 0 ,
713+ 'known_count ' => 0 ,
714+ 'known_zero_count ' => 0 ,
715+ 'skipped_count ' => 0 ,
716+ 'unknown_count ' => 0 ,
717+ ),
718+ 'examples ' => array (),
646719 );
647- $ blockers [ $ reason ]['repos ' ][ $ repo ]['count ' ] = (int ) $ blockers [ $ reason ]['repos ' ][ $ repo ]['count ' ] + 1 ;
648- $ blockers [ $ reason ]['repos ' ][ $ repo ]['size_bytes ' ] += $ bytes ;
720+ $ blockers [ $ reason ]['repos ' ][ $ repo ]['count ' ] = (int ) $ blockers [ $ reason ]['repos ' ][ $ repo ]['count ' ] + 1 ;
721+ $ blockers [ $ reason ]['repos ' ][ $ repo ]['size_bytes ' ] += $ bytes ;
722+ $ blockers [ $ reason ]['repos ' ][ $ repo ]['size_accounting ' ] = $ this ->cleanup_plan_add_size_accounting ($ blockers [ $ reason ]['repos ' ][ $ repo ]['size_accounting ' ], $ accounting );
649723 if ( count ($ blockers [ $ reason ]['examples ' ]) < 5 ) {
650- $ blockers [ $ reason ]['examples ' ][] = (string ) ( $ row ['handle ' ] ?? $ row ['path ' ] ?? '' );
724+ $ blockers [ $ reason ]['examples ' ][] = array (
725+ 'handle ' => (string ) ( $ row ['handle ' ] ?? $ row ['path ' ] ?? '' ),
726+ 'size_status ' => $ status ,
727+ );
651728 }
652729 if ( count ($ blockers [ $ reason ]['repos ' ][ $ repo ]['examples ' ]) < 3 ) {
653- $ blockers [ $ reason ]['repos ' ][ $ repo ]['examples ' ][] = (string ) ( $ row ['handle ' ] ?? $ row ['path ' ] ?? '' );
730+ $ blockers [ $ reason ]['repos ' ][ $ repo ]['examples ' ][] = array (
731+ 'handle ' => (string ) ( $ row ['handle ' ] ?? $ row ['path ' ] ?? '' ),
732+ 'size_status ' => $ status ,
733+ );
654734 }
655735 }
656736 }
@@ -676,6 +756,41 @@ private function cleanup_plan_blockers( array $artifact_plan, array $worktree_pl
676756 return $ blockers ;
677757 }
678758
759+ /**
760+ * Add one row's explicit size accounting into a blocker bucket.
761+ *
762+ * @param array<string,int> $bucket Existing bucket accounting.
763+ * @param array<string,mixed> $accounting Row accounting.
764+ * @return array<string,int>
765+ */
766+ private function cleanup_plan_add_size_accounting ( array $ bucket , array $ accounting ): array {
767+ $ bucket = array_merge (
768+ array (
769+ 'known_bytes ' => 0 ,
770+ 'known_count ' => 0 ,
771+ 'known_zero_count ' => 0 ,
772+ 'skipped_count ' => 0 ,
773+ 'unknown_count ' => 0 ,
774+ ),
775+ array_map ('intval ' , $ bucket )
776+ );
777+
778+ $ status = (string ) ( $ accounting ['status ' ] ?? 'unknown ' );
779+ if ( 'known ' === $ status || 'known_zero ' === $ status ) {
780+ ++$ bucket ['known_count ' ];
781+ $ bucket ['known_bytes ' ] += max (0 , (int ) ( $ accounting ['bytes ' ] ?? 0 ));
782+ if ( 'known_zero ' === $ status ) {
783+ ++$ bucket ['known_zero_count ' ];
784+ }
785+ } elseif ( 'skipped ' === $ status ) {
786+ ++$ bucket ['skipped_count ' ];
787+ } else {
788+ ++$ bucket ['unknown_count ' ];
789+ }
790+
791+ return $ bucket ;
792+ }
793+
679794 /**
680795 * Build directly executable cleanup recommendations with risk labels.
681796 *
@@ -684,6 +799,12 @@ private function cleanup_plan_blockers( array $artifact_plan, array $worktree_pl
684799 */
685800 private function cleanup_plan_recommended_commands ( array $ inputs ): array {
686801 $ commands = array (
802+ array (
803+ 'label ' => 'bounded_size_aware_review ' ,
804+ 'risk ' => 'none ' ,
805+ 'command ' => sprintf ('studio wp datamachine-code workspace hygiene --include-sizes --size-limit=%d --format=json ' , max (1 , (int ) ( $ inputs ['limit ' ] ?? self ::CLEANUP_PLAN_DEFAULT_LIMIT ))),
806+ 'when ' => 'cleanup blockers or cheap inventory rows have skipped or unknown size accounting under disk pressure ' ,
807+ ),
687808 array (
688809 'label ' => 'apply_reviewed_plan ' ,
689810 'risk ' => 'reviewed_destructive ' ,
0 commit comments