@@ -6385,7 +6385,8 @@ private function flatten_artifact_cleanup_rows( array $rows ): array {
63856385 private function render_worktree_bounded_cleanup_eligible_apply_result ( array $ result , array $ assoc_args ): void {
63866386 $ format = isset ($ assoc_args ['format ' ]) ? (string ) $ assoc_args ['format ' ] : 'table ' ;
63876387 if ( 'json ' === $ format ) {
6388- $ json = wp_json_encode ($ result , JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES );
6388+ $ report = ! empty ($ assoc_args ['verbose ' ]) ? $ result : $ this ->compact_worktree_bounded_cleanup_eligible_apply_json ($ result );
6389+ $ json = wp_json_encode ($ report , JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES );
63896390 WP_CLI ::log (false === $ json ? '{} ' : $ json );
63906391 return ;
63916392 }
@@ -6512,6 +6513,237 @@ private function render_worktree_bounded_cleanup_eligible_apply_result( array $r
65126513 }
65136514 }
65146515
6516+ /**
6517+ * Compact bounded cleanup JSON for chat/operator output.
6518+ *
6519+ * @param array<string,mixed> $result Full bounded apply result.
6520+ * @return array<string,mixed>
6521+ */
6522+ private function compact_worktree_bounded_cleanup_eligible_apply_json ( array $ result ): array {
6523+ $ summary = (array ) ( $ result ['summary ' ] ?? array () );
6524+ $ candidates = (array ) ( $ result ['candidates ' ] ?? array () );
6525+ $ removed = (array ) ( $ result ['removed ' ] ?? array () );
6526+ $ skipped = (array ) ( $ result ['skipped ' ] ?? array () );
6527+ $ buckets = $ this ->build_cleanup_blocker_buckets ($ skipped );
6528+ $ actions = $ this ->build_cleanup_next_actions ($ buckets , (array ) ( $ summary ['skipped_next_commands ' ] ?? array () ));
6529+
6530+ $ compact_summary = array_merge (
6531+ $ summary ,
6532+ array (
6533+ 'processed ' => (int ) ( $ summary ['processed ' ] ?? $ summary ['inspected ' ] ?? count ($ candidates ) ),
6534+ 'would_remove ' => (int ) ( $ summary ['would_remove ' ] ?? ( ! empty ($ result ['dry_run ' ]) ? count ($ candidates ) : 0 ) ),
6535+ 'removed ' => (int ) ( $ summary ['removed ' ] ?? count ($ removed ) ),
6536+ 'skipped ' => max ( (int ) ( $ summary ['skipped ' ] ?? 0 ), count ($ skipped ) ),
6537+ 'bytes_reclaimed ' => (int ) ( $ summary ['bytes_reclaimed ' ] ?? 0 ),
6538+ 'skipped_by_reason ' => array_map (fn ( $ bucket ) => (int ) ( $ bucket ['count ' ] ?? 0 ), $ buckets ),
6539+ 'blocker_bucket_count ' => count ($ buckets ),
6540+ )
6541+ );
6542+
6543+ $ report = array (
6544+ 'success ' => (bool ) ( $ result ['success ' ] ?? true ),
6545+ 'mode ' => (string ) ( $ result ['mode ' ] ?? 'bounded_cleanup_eligible_apply ' ),
6546+ 'dry_run ' => ! empty ($ result ['dry_run ' ]),
6547+ 'destructive ' => ! empty ($ result ['destructive ' ]),
6548+ 'workspace_path ' => $ result ['workspace_path ' ] ?? null ,
6549+ 'generated_at ' => $ result ['generated_at ' ] ?? null ,
6550+ 'summary ' => $ compact_summary ,
6551+ 'blocker_buckets ' => $ buckets ,
6552+ 'next_actions ' => $ actions ,
6553+ 'candidates ' => $ this ->compact_cleanup_rows ($ candidates , 25 ),
6554+ 'removed ' => $ this ->compact_cleanup_rows ($ removed , 25 ),
6555+ 'continuation ' => $ this ->compact_cleanup_continuation ( (array ) ( $ result ['continuation ' ] ?? $ result ['pagination ' ] ?? array () ) ),
6556+ 'evidence ' => $ this ->compact_cleanup_evidence ( (array ) ( $ result ['evidence ' ] ?? array () ), $ skipped ),
6557+ );
6558+
6559+ if ( ! empty ($ result ['job_backed ' ]) ) {
6560+ $ report ['job_backed ' ] = true ;
6561+ }
6562+
6563+ return array_filter ($ report , fn ( $ value ) => null !== $ value );
6564+ }
6565+
6566+ /**
6567+ * Build skipped blocker buckets with bounded examples.
6568+ *
6569+ * @param array<int,array<string,mixed>> $rows Skipped rows.
6570+ * @return array<string,array<string,mixed>>
6571+ */
6572+ private function build_cleanup_blocker_buckets ( array $ rows ): array {
6573+ $ buckets = array ();
6574+ foreach ( $ rows as $ row ) {
6575+ $ reason_code = (string ) ( $ row ['reason_code ' ] ?? 'unknown ' );
6576+ if ( ! isset ($ buckets [ $ reason_code ]) ) {
6577+ $ buckets [ $ reason_code ] = array (
6578+ 'count ' => 0 ,
6579+ 'examples ' => array (),
6580+ 'reason ' => (string ) ( $ row ['reason ' ] ?? '' ),
6581+ );
6582+ }
6583+ ++$ buckets [ $ reason_code ]['count ' ];
6584+ if ( count ($ buckets [ $ reason_code ]['examples ' ]) < 3 ) {
6585+ $ buckets [ $ reason_code ]['examples ' ][] = $ this ->compact_cleanup_row ($ row );
6586+ }
6587+ }
6588+
6589+ ksort ($ buckets );
6590+ return $ buckets ;
6591+ }
6592+
6593+ /**
6594+ * Build compact next actions for unresolved blocker classes.
6595+ *
6596+ * @param array<string,array<string,mixed>> $buckets Blocker buckets.
6597+ * @param array<int,array<string,mixed>> $commands Existing cleanup command hints.
6598+ * @return array<int,array<string,mixed>>
6599+ */
6600+ private function build_cleanup_next_actions ( array $ buckets , array $ commands ): array {
6601+ $ by_reason = array ();
6602+ foreach ( $ commands as $ command ) {
6603+ $ reason_code = (string ) ( $ command ['reason_code ' ] ?? '' );
6604+ if ( '' !== $ reason_code ) {
6605+ $ by_reason [ $ reason_code ] = $ command ;
6606+ }
6607+ }
6608+
6609+ $ defaults = array (
6610+ 'active_no_signal ' => array (
6611+ 'command ' => 'studio wp datamachine-code workspace worktree active-no-signal-report --limit=25 --offset=0 --format=json ' ,
6612+ 'destructive ' => false ,
6613+ ),
6614+ 'needs_metadata_reconcile ' => array (
6615+ 'command ' => 'studio wp datamachine-code workspace worktree reconcile-metadata --dry-run --limit=25 --offset=0 --until-budget=30s --format=json ' ,
6616+ 'destructive ' => false ,
6617+ ),
6618+ 'lifecycle_reconciliation_candidate ' => array (
6619+ 'command ' => 'studio wp datamachine-code workspace worktree cleanup --dry-run --format=json ' ,
6620+ 'destructive ' => false ,
6621+ ),
6622+ 'dirty_worktree ' => array (
6623+ 'command ' => 'git -C <worktree-path> status --short --branch --untracked-files=normal ' ,
6624+ 'destructive ' => false ,
6625+ ),
6626+ 'unpushed_commits ' => array (
6627+ 'command ' => 'git -C <worktree-path> log --oneline --decorate @{u}..HEAD ' ,
6628+ 'destructive ' => false ,
6629+ ),
6630+ 'stale_worktree_marker ' => array (
6631+ 'command ' => 'git -C <primary-path> worktree prune --dry-run --verbose ' ,
6632+ 'destructive ' => false ,
6633+ ),
6634+ 'primary_missing ' => array (
6635+ 'command ' => 'studio wp datamachine-code workspace show <repo> ' ,
6636+ 'destructive ' => false ,
6637+ ),
6638+ 'submodule_worktree ' => array (
6639+ 'command ' => 'git -C <worktree-path> submodule status --recursive ' ,
6640+ 'destructive ' => false ,
6641+ ),
6642+ 'remove_timeout ' => array (
6643+ 'command ' => 'studio wp datamachine-code workspace worktree bounded-cleanup-eligible-apply --limit=25 --remove-timeout=<seconds> ' ,
6644+ 'destructive ' => true ,
6645+ ),
6646+ );
6647+
6648+ $ actions = array ();
6649+ foreach ( $ buckets as $ reason_code => $ bucket ) {
6650+ $ hint = $ by_reason [ $ reason_code ] ?? $ defaults [ $ reason_code ] ?? array (
6651+ 'command ' => 'Re-run with --verbose --format=json and inspect this reason_code before retrying cleanup. ' ,
6652+ 'destructive ' => false ,
6653+ );
6654+ $ actions [] = array (
6655+ 'reason_code ' => $ reason_code ,
6656+ 'count ' => (int ) ( $ bucket ['count ' ] ?? 0 ),
6657+ 'command ' => (string ) ( $ hint ['command ' ] ?? '' ),
6658+ 'alternative ' => (string ) ( $ hint ['alternative ' ] ?? '' ),
6659+ 'destructive ' => ! empty ($ hint ['destructive ' ]),
6660+ );
6661+ }
6662+
6663+ return $ actions ;
6664+ }
6665+
6666+ /**
6667+ * Compact a bounded cleanup continuation without full handle lists.
6668+ *
6669+ * @param array<string,mixed> $continuation Continuation payload.
6670+ * @return array<string,mixed>
6671+ */
6672+ private function compact_cleanup_continuation ( array $ continuation ): array {
6673+ if ( empty ($ continuation ) ) {
6674+ return array ();
6675+ }
6676+
6677+ $ handles = array_values (array_filter (array_map ('strval ' , (array ) ( $ continuation ['remaining_handles ' ] ?? array () ))));
6678+ unset($ continuation ['remaining_handles ' ]);
6679+ if ( ! isset ($ continuation ['remaining_total ' ]) && isset ($ continuation ['total ' ]) ) {
6680+ $ continuation ['remaining_total ' ] = (int ) $ continuation ['total ' ];
6681+ }
6682+ $ continuation ['remaining_handles_count ' ] = count ($ handles );
6683+ $ continuation ['remaining_handles_examples ' ] = array_slice ($ handles , 0 , 10 );
6684+ if ( count ($ handles ) > 10 ) {
6685+ $ continuation ['remaining_handles_truncated ' ] = true ;
6686+ }
6687+
6688+ return $ continuation ;
6689+ }
6690+
6691+ /**
6692+ * Compact evidence while removing full skipped handle lists.
6693+ *
6694+ * @param array<string,mixed> $evidence Evidence payload.
6695+ * @param array<int,array<string,mixed>> $skipped Skipped rows.
6696+ * @return array<string,mixed>
6697+ */
6698+ private function compact_cleanup_evidence ( array $ evidence , array $ skipped ): array {
6699+ $ skipped_handles = array_values (array_filter (array_map (fn ( $ row ) => (string ) ( $ row ['handle ' ] ?? '' ), $ skipped )));
6700+ unset($ evidence ['skipped_handles ' ]);
6701+ $ evidence ['skipped_handles_count ' ] = count ($ skipped_handles );
6702+ $ evidence ['skipped_handles_examples ' ] = array_slice ($ skipped_handles , 0 , 10 );
6703+ if ( count ($ skipped_handles ) > 10 ) {
6704+ $ evidence ['skipped_handles_truncated ' ] = true ;
6705+ }
6706+ $ evidence ['full_detail_hint ' ] = 'Re-run with --verbose --format=json for full skipped rows and handle lists. ' ;
6707+
6708+ return $ evidence ;
6709+ }
6710+
6711+ /**
6712+ * Compact cleanup rows to the fields operators need first.
6713+ *
6714+ * @param array<int,array<string,mixed>> $rows Rows to compact.
6715+ * @param int $limit Maximum rows.
6716+ * @return array<int,array<string,mixed>>
6717+ */
6718+ private function compact_cleanup_rows ( array $ rows , int $ limit ): array {
6719+ return array_map (fn ( $ row ) => $ this ->compact_cleanup_row ( (array ) $ row ), array_slice ($ rows , 0 , $ limit ));
6720+ }
6721+
6722+ /**
6723+ * Compact one cleanup row.
6724+ *
6725+ * @param array<string,mixed> $row Cleanup row.
6726+ * @return array<string,mixed>
6727+ */
6728+ private function compact_cleanup_row ( array $ row ): array {
6729+ $ compact = array (
6730+ 'handle ' => $ row ['handle ' ] ?? null ,
6731+ 'repo ' => $ row ['repo ' ] ?? null ,
6732+ 'branch ' => $ row ['branch ' ] ?? null ,
6733+ 'reason_code ' => $ row ['reason_code ' ] ?? $ row ['signal ' ] ?? null ,
6734+ 'reason ' => isset ($ row ['reason ' ]) ? $ this ->shorten_cleanup_reason ( (string ) $ row ['reason ' ] ) : null ,
6735+ 'path ' => $ row ['path ' ] ?? null ,
6736+ );
6737+
6738+ foreach ( array ( 'size_bytes ' , 'artifact_size_bytes ' , 'dirty ' , 'unpushed ' , 'created_at ' , 'liveness ' , 'pr_url ' ) as $ field ) {
6739+ if ( array_key_exists ($ field , $ row ) ) {
6740+ $ compact [ $ field ] = $ row [ $ field ];
6741+ }
6742+ }
6743+
6744+ return array_filter ($ compact , fn ( $ value ) => null !== $ value && '' !== $ value && array () !== $ value );
6745+ }
6746+
65156747 private function render_worktree_emergency_cleanup_result ( array $ result , array $ assoc_args ): void {
65166748 $ format = isset ($ assoc_args ['format ' ]) ? (string ) $ assoc_args ['format ' ] : 'table ' ;
65176749 if ( 'json ' === $ format ) {
0 commit comments