@@ -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,207 @@ 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 ( 'command ' => 'studio wp datamachine-code workspace worktree active-no-signal-report --limit=25 --offset=0 --format=json ' , 'destructive ' => false ),
6611+ 'needs_metadata_reconcile ' => array ( 'command ' => 'studio wp datamachine-code workspace worktree reconcile-metadata --dry-run --limit=25 --offset=0 --until-budget=30s --format=json ' , 'destructive ' => false ),
6612+ 'lifecycle_reconciliation_candidate ' => array ( 'command ' => 'studio wp datamachine-code workspace worktree cleanup --dry-run --format=json ' , 'destructive ' => false ),
6613+ 'dirty_worktree ' => array ( 'command ' => 'git -C <worktree-path> status --short --branch --untracked-files=normal ' , 'destructive ' => false ),
6614+ 'unpushed_commits ' => array ( 'command ' => 'git -C <worktree-path> log --oneline --decorate @{u}..HEAD ' , 'destructive ' => false ),
6615+ 'stale_worktree_marker ' => array ( 'command ' => 'git -C <primary-path> worktree prune --dry-run --verbose ' , 'destructive ' => false ),
6616+ 'primary_missing ' => array ( 'command ' => 'studio wp datamachine-code workspace show <repo> ' , 'destructive ' => false ),
6617+ 'submodule_worktree ' => array ( 'command ' => 'git -C <worktree-path> submodule status --recursive ' , 'destructive ' => false ),
6618+ 'remove_timeout ' => array ( 'command ' => 'studio wp datamachine-code workspace worktree bounded-cleanup-eligible-apply --limit=25 --remove-timeout=<seconds> ' , 'destructive ' => true ),
6619+ );
6620+
6621+ $ actions = array ();
6622+ foreach ( $ buckets as $ reason_code => $ bucket ) {
6623+ $ hint = $ by_reason [ $ reason_code ] ?? $ defaults [ $ reason_code ] ?? array ( 'command ' => 'Re-run with --verbose --format=json and inspect this reason_code before retrying cleanup. ' , 'destructive ' => false );
6624+ $ actions [] = array (
6625+ 'reason_code ' => $ reason_code ,
6626+ 'count ' => (int ) ( $ bucket ['count ' ] ?? 0 ),
6627+ 'command ' => (string ) ( $ hint ['command ' ] ?? '' ),
6628+ 'alternative ' => (string ) ( $ hint ['alternative ' ] ?? '' ),
6629+ 'destructive ' => ! empty ($ hint ['destructive ' ]),
6630+ );
6631+ }
6632+
6633+ return $ actions ;
6634+ }
6635+
6636+ /**
6637+ * Compact a bounded cleanup continuation without full handle lists.
6638+ *
6639+ * @param array<string,mixed> $continuation Continuation payload.
6640+ * @return array<string,mixed>
6641+ */
6642+ private function compact_cleanup_continuation ( array $ continuation ): array {
6643+ if ( empty ($ continuation ) ) {
6644+ return array ();
6645+ }
6646+
6647+ $ handles = array_values (array_filter (array_map ('strval ' , (array ) ( $ continuation ['remaining_handles ' ] ?? array () ))));
6648+ unset($ continuation ['remaining_handles ' ]);
6649+ if ( ! isset ($ continuation ['remaining_total ' ]) && isset ($ continuation ['total ' ]) ) {
6650+ $ continuation ['remaining_total ' ] = (int ) $ continuation ['total ' ];
6651+ }
6652+ $ continuation ['remaining_handles_count ' ] = count ($ handles );
6653+ $ continuation ['remaining_handles_examples ' ] = array_slice ($ handles , 0 , 10 );
6654+ if ( count ($ handles ) > 10 ) {
6655+ $ continuation ['remaining_handles_truncated ' ] = true ;
6656+ }
6657+
6658+ return $ continuation ;
6659+ }
6660+
6661+ /**
6662+ * Compact evidence while removing full skipped handle lists.
6663+ *
6664+ * @param array<string,mixed> $evidence Evidence payload.
6665+ * @param array<int,array<string,mixed>> $skipped Skipped rows.
6666+ * @return array<string,mixed>
6667+ */
6668+ private function compact_cleanup_evidence ( array $ evidence , array $ skipped ): array {
6669+ $ skipped_handles = array_values (array_filter (array_map (fn ( $ row ) => is_array ($ row ) ? (string ) ( $ row ['handle ' ] ?? '' ) : '' , $ skipped )));
6670+ unset($ evidence ['skipped_handles ' ]);
6671+ $ evidence ['skipped_handles_count ' ] = count ($ skipped_handles );
6672+ $ evidence ['skipped_handles_examples ' ] = array_slice ($ skipped_handles , 0 , 10 );
6673+ if ( count ($ skipped_handles ) > 10 ) {
6674+ $ evidence ['skipped_handles_truncated ' ] = true ;
6675+ }
6676+ $ evidence ['full_detail_hint ' ] = 'Re-run with --verbose --format=json for full skipped rows and handle lists. ' ;
6677+
6678+ return $ evidence ;
6679+ }
6680+
6681+ /**
6682+ * Compact cleanup rows to the fields operators need first.
6683+ *
6684+ * @param array<int,array<string,mixed>> $rows Rows to compact.
6685+ * @param int $limit Maximum rows.
6686+ * @return array<int,array<string,mixed>>
6687+ */
6688+ private function compact_cleanup_rows ( array $ rows , int $ limit ): array {
6689+ return array_values (array_map (fn ( $ row ) => $ this ->compact_cleanup_row ((array ) $ row ), array_slice ($ rows , 0 , $ limit )));
6690+ }
6691+
6692+ /**
6693+ * Compact one cleanup row.
6694+ *
6695+ * @param array<string,mixed> $row Cleanup row.
6696+ * @return array<string,mixed>
6697+ */
6698+ private function compact_cleanup_row ( array $ row ): array {
6699+ $ compact = array (
6700+ 'handle ' => $ row ['handle ' ] ?? null ,
6701+ 'repo ' => $ row ['repo ' ] ?? null ,
6702+ 'branch ' => $ row ['branch ' ] ?? null ,
6703+ 'reason_code ' => $ row ['reason_code ' ] ?? $ row ['signal ' ] ?? null ,
6704+ 'reason ' => isset ($ row ['reason ' ]) ? $ this ->shorten_cleanup_reason ((string ) $ row ['reason ' ]) : null ,
6705+ 'path ' => $ row ['path ' ] ?? null ,
6706+ );
6707+
6708+ foreach ( array ( 'size_bytes ' , 'artifact_size_bytes ' , 'dirty ' , 'unpushed ' , 'created_at ' , 'liveness ' , 'pr_url ' ) as $ field ) {
6709+ if ( array_key_exists ($ field , $ row ) ) {
6710+ $ compact [ $ field ] = $ row [ $ field ];
6711+ }
6712+ }
6713+
6714+ return array_filter ($ compact , fn ( $ value ) => null !== $ value && '' !== $ value && array () !== $ value );
6715+ }
6716+
65156717 private function render_worktree_emergency_cleanup_result ( array $ result , array $ assoc_args ): void {
65166718 $ format = isset ($ assoc_args ['format ' ]) ? (string ) $ assoc_args ['format ' ] : 'table ' ;
65176719 if ( 'json ' === $ format ) {
0 commit comments