@@ -47,6 +47,7 @@ class WorkspaceCommand extends BaseCommand {
4747 'cleanup ' => array ( 'ability ' => 'datamachine-code/workspace-worktree-cleanup ' ),
4848 'cleanup-artifacts ' => array ( 'ability ' => 'datamachine-code/workspace-worktree-cleanup-artifacts ' ),
4949 'bounded-cleanup-eligible-apply ' => array ( 'ability ' => 'datamachine-code/workspace-worktree-bounded-cleanup-eligible-apply ' ),
50+ 'cleanup-eligible-drain ' => array ( 'ability ' => 'datamachine-code/workspace-worktree-cleanup-eligible-drain ' ),
5051 'emergency-cleanup ' => array ( 'ability ' => 'datamachine-code/workspace-worktree-emergency-cleanup ' ),
5152 'reconcile-metadata ' => array ( 'ability ' => 'datamachine-code/workspace-worktree-reconcile-metadata ' ),
5253 'active-no-signal-report ' => array (
@@ -3211,7 +3212,7 @@ private function renderGitOperationResult( string $operation, array $result, arr
32113212 *
32123213 * <operation>
32133214 * : Worktree operation: add, list, remove, prune, locks, cleanup, cleanup-artifacts,
3214- * bounded-cleanup-eligible-apply, emergency-cleanup, reconcile-metadata,
3215+ * bounded-cleanup-eligible-apply, cleanup-eligible-drain, emergency-cleanup, reconcile-metadata,
32153216 * active-no-signal-report, active-no-signal-finalized-apply,
32163217 * active-no-signal-equivalent-clean-apply,
32173218 * active-no-signal-merged-apply, active-no-signal-remote-clean-apply,
@@ -3358,7 +3359,8 @@ private function renderGitOperationResult( string $operation, array $result, arr
33583359 * [--discard-unpushed]
33593360 * : With bounded-cleanup-eligible-apply only, explicitly discard unpushed
33603361 * commits after reviewed cleanup eligibility and fresh safety probes. This
3361- * is a data-loss mode and is not implied by --force.
3362+ * is a data-loss mode and is not implied by --force. Cleanup-eligible-drain
3363+ * refuses this option.
33623364 *
33633365 * [--older-than=<duration>]
33643366 * : Limit cleanup candidates to worktrees with lifecycle `created_at`
@@ -3414,7 +3416,8 @@ private function renderGitOperationResult( string $operation, array $result, arr
34143416 *
34153417 * [--passes=<count>]
34163418 * : For `abandoned`, maximum apply passes to run after marking eligible rows.
3417- * Preview mode always runs a single non-destructive classification pass.
3419+ * For `cleanup-eligible-drain`, maximum bounded cleanup-eligible apply
3420+ * passes to run. Preview mode always runs one non-destructive pass.
34183421 *
34193422 * [--stage=<stage>]
34203423 * : For `abandoned`, resume from a specific orchestration stage. Supported
@@ -3427,7 +3430,7 @@ private function renderGitOperationResult( string $operation, array $result, arr
34273430 * passing the previous response's `pagination.next_offset`.
34283431 *
34293432 * [--until-budget=<duration>]
3430- * : For `cleanup --dry-run` and `reconcile-metadata`, enforce a compact
3433+ * : For `cleanup --dry-run`, `cleanup-eligible-drain`, and `reconcile-metadata`, enforce a compact
34313434 * wall-clock budget for dry-run pages or direct-apply drains (e.g. 60s,
34323435 * 10m). Also supported by `active-no-signal-report` and the active/no-signal
34333436 * apply flows. Returns continuation
@@ -3524,6 +3527,8 @@ private function renderGitOperationResult( string $operation, array $result, arr
35243527 * wp datamachine-code workspace worktree bounded-cleanup-eligible-apply --via-jobs --limit=10 --older-than=7d
35253528 * wp datamachine-code workspace worktree bounded-cleanup-eligible-apply --dry-run --include-repaired-metadata --older-than=7d --limit=25
35263529 * wp datamachine-code workspace worktree bounded-cleanup-eligible-apply --include-repaired-metadata --older-than=7d --limit=25
3530+ * wp datamachine-code workspace worktree cleanup-eligible-drain --limit=25 --format=json
3531+ * wp datamachine-code workspace worktree cleanup-eligible-drain --apply --limit=25 --passes=10 --until-budget=120s --format=json
35273532 *
35283533 * # Local-only detection (no GitHub API call)
35293534 * wp datamachine-code workspace worktree cleanup --skip-github
@@ -3581,7 +3586,7 @@ public function worktree( array $args, array $assoc_args ): void {
35813586 $ operation = $ args [0 ] ?? '' ;
35823587
35833588 if ( '' === $ operation ) {
3584- WP_CLI ::error ('Usage: wp datamachine-code workspace worktree <add|list|remove|prune|locks|cleanup|cleanup-artifacts|abandoned|bounded-cleanup-eligible-apply|emergency-cleanup|reconcile-metadata|backfill-origin-session|active-no-signal-report|active-no-signal-finalized-apply|active-no-signal-equivalent-clean-apply|active-no-signal-merged-apply|active-no-signal-remote-clean-apply|refresh-context|finalize|mark-cleanup-eligible> [<repo>] [<branch>] [--flags] ' );
3589+ WP_CLI ::error ('Usage: wp datamachine-code workspace worktree <add|list|remove|prune|locks|cleanup|cleanup-artifacts|abandoned|bounded-cleanup-eligible-apply|cleanup-eligible-drain| emergency-cleanup|reconcile-metadata|backfill-origin-session|active-no-signal-report|active-no-signal-finalized-apply|active-no-signal-equivalent-clean-apply|active-no-signal-merged-apply|active-no-signal-remote-clean-apply|refresh-context|finalize|mark-cleanup-eligible> [<repo>] [<branch>] [--flags] ' );
35853590 return ;
35863591 }
35873592
@@ -3898,6 +3903,24 @@ public function worktree( array $args, array $assoc_args ): void {
38983903 $ input ['remove_timeout ' ] = (int ) $ assoc_args ['remove-timeout ' ];
38993904 }
39003905 break ;
3906+
3907+ case 'cleanup-eligible-drain ' :
3908+ $ input ['apply ' ] = ! empty ($ assoc_args ['apply ' ]);
3909+ $ input ['force ' ] = ! empty ($ assoc_args ['force ' ]);
3910+ $ input ['discard_unpushed ' ] = ! empty ($ assoc_args ['discard-unpushed ' ]);
3911+ $ input ['include_repaired_metadata ' ] = ! empty ($ assoc_args ['include-repaired-metadata ' ]);
3912+ $ input ['source ' ] = self ::CLEANUP_CLI_SOURCE ;
3913+ foreach ( array ( 'limit ' , 'passes ' , 'remove-timeout ' ) as $ key ) {
3914+ if ( isset ($ assoc_args [ $ key ]) ) {
3915+ $ input [ str_replace ('- ' , '_ ' , $ key ) ] = (int ) $ assoc_args [ $ key ];
3916+ }
3917+ }
3918+ foreach ( array ( 'older-than ' , 'sort ' , 'until-budget ' ) as $ key ) {
3919+ if ( isset ($ assoc_args [ $ key ]) && '' !== trim ( (string ) $ assoc_args [ $ key ]) ) {
3920+ $ input [ str_replace ('- ' , '_ ' , $ key ) ] = trim ( (string ) $ assoc_args [ $ key ]);
3921+ }
3922+ }
3923+ break ;
39013924 }
39023925
39033926 $ result = $ ability ->execute ($ input );
@@ -4283,6 +4306,10 @@ function ( $wt ) {
42834306 $ this ->render_worktree_bounded_cleanup_eligible_apply_result ($ result , $ assoc_args );
42844307 return ;
42854308
4309+ case 'cleanup-eligible-drain ' :
4310+ $ this ->render_worktree_cleanup_eligible_drain_result ($ result , $ assoc_args );
4311+ return ;
4312+
42864313 case 'emergency-cleanup ' :
42874314 $ this ->render_worktree_emergency_cleanup_result ($ result , $ assoc_args );
42884315 return ;
@@ -6048,6 +6075,95 @@ private function render_worktree_bounded_cleanup_eligible_apply_result( array $r
60486075 }
60496076 }
60506077
6078+ /**
6079+ * Render cleanup-eligible drain output.
6080+ *
6081+ * @param array $result Drain result.
6082+ * @param array $assoc_args CLI args.
6083+ * @return void
6084+ */
6085+ private function render_worktree_cleanup_eligible_drain_result ( array $ result , array $ assoc_args ): void {
6086+ if ( 'json ' === (string ) ( $ assoc_args ['format ' ] ?? '' ) ) {
6087+ $ this ->renderer ()->json ($ result );
6088+ return ;
6089+ }
6090+
6091+ $ summary = (array ) ( $ result ['summary ' ] ?? array () );
6092+ $ final_free_space = (array ) ( $ summary ['final_free_space ' ] ?? array () );
6093+ WP_CLI ::log ('Cleanup-eligible drain summary: ' );
6094+ $ this ->format_items (
6095+ array (
6096+ array (
6097+ 'metric ' => 'mode ' ,
6098+ 'value ' => ! empty ($ result ['applied ' ]) ? 'apply ' : 'preview ' ,
6099+ ),
6100+ array (
6101+ 'metric ' => 'passes ' ,
6102+ 'value ' => (int ) ( $ summary ['passes ' ] ?? 0 ),
6103+ ),
6104+ array (
6105+ 'metric ' => 'processed ' ,
6106+ 'value ' => (int ) ( $ summary ['processed ' ] ?? 0 ),
6107+ ),
6108+ array (
6109+ 'metric ' => 'would_remove ' ,
6110+ 'value ' => (int ) ( $ summary ['would_remove ' ] ?? 0 ),
6111+ ),
6112+ array (
6113+ 'metric ' => 'removed ' ,
6114+ 'value ' => (int ) ( $ summary ['removed ' ] ?? 0 ),
6115+ ),
6116+ array (
6117+ 'metric ' => 'skipped ' ,
6118+ 'value ' => (int ) ( $ summary ['skipped ' ] ?? 0 ),
6119+ ),
6120+ array (
6121+ 'metric ' => 'bytes_reclaimed ' ,
6122+ 'value ' => $ this ->format_bytes ( (int ) ( $ summary ['bytes_reclaimed ' ] ?? 0 ) ),
6123+ ),
6124+ array (
6125+ 'metric ' => 'stop_reason ' ,
6126+ 'value ' => (string ) ( $ summary ['stop_reason ' ] ?? '' ),
6127+ ),
6128+ array (
6129+ 'metric ' => 'final_free_space ' ,
6130+ 'value ' => (string ) ( $ final_free_space ['free_human ' ] ?? 'unknown ' ),
6131+ ),
6132+ ),
6133+ array ( 'metric ' , 'value ' ),
6134+ array ( 'format ' => 'table ' ),
6135+ 'metric '
6136+ );
6137+
6138+ $ passes = (array ) ( $ result ['pass_results ' ] ?? array () );
6139+ if ( ! empty ($ passes ) ) {
6140+ WP_CLI ::log ('' );
6141+ WP_CLI ::log ('Pass evidence: ' );
6142+ $ this ->format_items (
6143+ array_map (
6144+ fn ( $ row ) => array (
6145+ 'pass ' => (int ) ( $ row ['pass ' ] ?? 0 ),
6146+ 'processed ' => (int ) ( $ row ['processed ' ] ?? 0 ),
6147+ 'would_remove ' => (int ) ( $ row ['would_remove ' ] ?? 0 ),
6148+ 'removed ' => (int ) ( $ row ['removed ' ] ?? 0 ),
6149+ 'skipped ' => (int ) ( $ row ['skipped ' ] ?? 0 ),
6150+ 'remaining_total ' => (int ) ( $ row ['remaining_total ' ] ?? 0 ),
6151+ 'bytes ' => $ this ->format_bytes ($ row ['bytes_reclaimed ' ] ?? 0 ),
6152+ ),
6153+ $ passes
6154+ ),
6155+ array ( 'pass ' , 'processed ' , 'would_remove ' , 'removed ' , 'skipped ' , 'remaining_total ' , 'bytes ' ),
6156+ array ( 'format ' => 'table ' ),
6157+ 'pass '
6158+ );
6159+ }
6160+
6161+ if ( ! empty ($ result ['next_commands ' ]) ) {
6162+ WP_CLI ::log ('' );
6163+ WP_CLI ::log ('Next command: ' . (string ) ( (array ) $ result ['next_commands ' ] )[0 ]);
6164+ }
6165+ }
6166+
60516167 /**
60526168 * Compact bounded cleanup JSON for chat/operator output.
60536169 *
0 commit comments