@@ -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 (
@@ -3190,7 +3191,7 @@ private function renderGitOperationResult( string $operation, array $result, arr
31903191 *
31913192 * <operation>
31923193 * : Worktree operation: add, list, remove, prune, locks, cleanup, cleanup-artifacts,
3193- * bounded-cleanup-eligible-apply, emergency-cleanup, reconcile-metadata,
3194+ * bounded-cleanup-eligible-apply, cleanup-eligible-drain, emergency-cleanup, reconcile-metadata,
31943195 * active-no-signal-report, active-no-signal-finalized-apply,
31953196 * active-no-signal-equivalent-clean-apply,
31963197 * active-no-signal-merged-apply, active-no-signal-remote-clean-apply,
@@ -3337,7 +3338,8 @@ private function renderGitOperationResult( string $operation, array $result, arr
33373338 * [--discard-unpushed]
33383339 * : With bounded-cleanup-eligible-apply only, explicitly discard unpushed
33393340 * commits after reviewed cleanup eligibility and fresh safety probes. This
3340- * is a data-loss mode and is not implied by --force.
3341+ * is a data-loss mode and is not implied by --force. Cleanup-eligible-drain
3342+ * refuses this option.
33413343 *
33423344 * [--older-than=<duration>]
33433345 * : Limit cleanup candidates to worktrees with lifecycle `created_at`
@@ -3393,7 +3395,8 @@ private function renderGitOperationResult( string $operation, array $result, arr
33933395 *
33943396 * [--passes=<count>]
33953397 * : For `abandoned`, maximum apply passes to run after marking eligible rows.
3396- * Preview mode always runs a single non-destructive classification pass.
3398+ * For `cleanup-eligible-drain`, maximum bounded cleanup-eligible apply
3399+ * passes to run. Preview mode always runs one non-destructive pass.
33973400 *
33983401 * [--stage=<stage>]
33993402 * : For `abandoned`, resume from a specific orchestration stage. Supported
@@ -3406,7 +3409,7 @@ private function renderGitOperationResult( string $operation, array $result, arr
34063409 * passing the previous response's `pagination.next_offset`.
34073410 *
34083411 * [--until-budget=<duration>]
3409- * : For `cleanup --dry-run` and `reconcile-metadata`, enforce a compact
3412+ * : For `cleanup --dry-run`, `cleanup-eligible-drain`, and `reconcile-metadata`, enforce a compact
34103413 * wall-clock budget for dry-run pages or direct-apply drains (e.g. 60s,
34113414 * 10m). Also supported by `active-no-signal-report` and the active/no-signal
34123415 * apply flows. Returns continuation
@@ -3503,6 +3506,8 @@ private function renderGitOperationResult( string $operation, array $result, arr
35033506 * wp datamachine-code workspace worktree bounded-cleanup-eligible-apply --via-jobs --limit=10 --older-than=7d
35043507 * wp datamachine-code workspace worktree bounded-cleanup-eligible-apply --dry-run --include-repaired-metadata --older-than=7d --limit=25
35053508 * wp datamachine-code workspace worktree bounded-cleanup-eligible-apply --include-repaired-metadata --older-than=7d --limit=25
3509+ * wp datamachine-code workspace worktree cleanup-eligible-drain --limit=25 --format=json
3510+ * wp datamachine-code workspace worktree cleanup-eligible-drain --apply --limit=25 --passes=10 --until-budget=120s --format=json
35063511 *
35073512 * # Local-only detection (no GitHub API call)
35083513 * wp datamachine-code workspace worktree cleanup --skip-github
@@ -3560,7 +3565,7 @@ public function worktree( array $args, array $assoc_args ): void {
35603565 $ operation = $ args [0 ] ?? '' ;
35613566
35623567 if ( '' === $ operation ) {
3563- 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] ' );
3568+ 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] ' );
35643569 return ;
35653570 }
35663571
@@ -3877,6 +3882,24 @@ public function worktree( array $args, array $assoc_args ): void {
38773882 $ input ['remove_timeout ' ] = (int ) $ assoc_args ['remove-timeout ' ];
38783883 }
38793884 break ;
3885+
3886+ case 'cleanup-eligible-drain ' :
3887+ $ input ['apply ' ] = ! empty ($ assoc_args ['apply ' ]);
3888+ $ input ['force ' ] = ! empty ($ assoc_args ['force ' ]);
3889+ $ input ['discard_unpushed ' ] = ! empty ($ assoc_args ['discard-unpushed ' ]);
3890+ $ input ['include_repaired_metadata ' ] = ! empty ($ assoc_args ['include-repaired-metadata ' ]);
3891+ $ input ['source ' ] = self ::CLEANUP_CLI_SOURCE ;
3892+ foreach ( array ( 'limit ' , 'passes ' , 'remove-timeout ' ) as $ key ) {
3893+ if ( isset ($ assoc_args [ $ key ]) ) {
3894+ $ input [ str_replace ('- ' , '_ ' , $ key ) ] = (int ) $ assoc_args [ $ key ];
3895+ }
3896+ }
3897+ foreach ( array ( 'older-than ' , 'sort ' , 'until-budget ' ) as $ key ) {
3898+ if ( isset ($ assoc_args [ $ key ]) && '' !== trim ( (string ) $ assoc_args [ $ key ]) ) {
3899+ $ input [ str_replace ('- ' , '_ ' , $ key ) ] = trim ( (string ) $ assoc_args [ $ key ]);
3900+ }
3901+ }
3902+ break ;
38803903 }
38813904
38823905 $ result = $ ability ->execute ($ input );
@@ -4262,6 +4285,10 @@ function ( $wt ) {
42624285 $ this ->render_worktree_bounded_cleanup_eligible_apply_result ($ result , $ assoc_args );
42634286 return ;
42644287
4288+ case 'cleanup-eligible-drain ' :
4289+ $ this ->render_worktree_cleanup_eligible_drain_result ($ result , $ assoc_args );
4290+ return ;
4291+
42654292 case 'emergency-cleanup ' :
42664293 $ this ->render_worktree_emergency_cleanup_result ($ result , $ assoc_args );
42674294 return ;
@@ -6019,6 +6046,67 @@ private function render_worktree_bounded_cleanup_eligible_apply_result( array $r
60196046 }
60206047 }
60216048
6049+ /**
6050+ * Render cleanup-eligible drain output.
6051+ *
6052+ * @param array $result Drain result.
6053+ * @param array $assoc_args CLI args.
6054+ * @return void
6055+ */
6056+ private function render_worktree_cleanup_eligible_drain_result ( array $ result , array $ assoc_args ): void {
6057+ if ( 'json ' === (string ) ( $ assoc_args ['format ' ] ?? '' ) ) {
6058+ $ this ->renderer ()->json ($ result );
6059+ return ;
6060+ }
6061+
6062+ $ summary = (array ) ( $ result ['summary ' ] ?? array () );
6063+ WP_CLI ::log ('Cleanup-eligible drain summary: ' );
6064+ $ this ->format_items (
6065+ array (
6066+ array ( 'metric ' => 'mode ' , 'value ' => ! empty ($ result ['applied ' ]) ? 'apply ' : 'preview ' ),
6067+ array ( 'metric ' => 'passes ' , 'value ' => (int ) ( $ summary ['passes ' ] ?? 0 ) ),
6068+ array ( 'metric ' => 'processed ' , 'value ' => (int ) ( $ summary ['processed ' ] ?? 0 ) ),
6069+ array ( 'metric ' => 'would_remove ' , 'value ' => (int ) ( $ summary ['would_remove ' ] ?? 0 ) ),
6070+ array ( 'metric ' => 'removed ' , 'value ' => (int ) ( $ summary ['removed ' ] ?? 0 ) ),
6071+ array ( 'metric ' => 'skipped ' , 'value ' => (int ) ( $ summary ['skipped ' ] ?? 0 ) ),
6072+ array ( 'metric ' => 'bytes_reclaimed ' , 'value ' => $ this ->format_bytes ($ summary ['bytes_reclaimed ' ] ?? 0 ) ),
6073+ array ( 'metric ' => 'stop_reason ' , 'value ' => (string ) ( $ summary ['stop_reason ' ] ?? '' ) ),
6074+ array ( 'metric ' => 'final_free_space ' , 'value ' => (string ) ( (array ) ( $ summary ['final_free_space ' ] ?? array () )['free_human ' ] ?? 'unknown ' ) ),
6075+ ),
6076+ array ( 'metric ' , 'value ' ),
6077+ array ( 'format ' => 'table ' ),
6078+ 'metric '
6079+ );
6080+
6081+ $ passes = (array ) ( $ result ['pass_results ' ] ?? array () );
6082+ if ( ! empty ($ passes ) ) {
6083+ WP_CLI ::log ('' );
6084+ WP_CLI ::log ('Pass evidence: ' );
6085+ $ this ->format_items (
6086+ array_map (
6087+ fn ( $ row ) => array (
6088+ 'pass ' => (int ) ( $ row ['pass ' ] ?? 0 ),
6089+ 'processed ' => (int ) ( $ row ['processed ' ] ?? 0 ),
6090+ 'would_remove ' => (int ) ( $ row ['would_remove ' ] ?? 0 ),
6091+ 'removed ' => (int ) ( $ row ['removed ' ] ?? 0 ),
6092+ 'skipped ' => (int ) ( $ row ['skipped ' ] ?? 0 ),
6093+ 'remaining_total ' => (int ) ( $ row ['remaining_total ' ] ?? 0 ),
6094+ 'bytes ' => $ this ->format_bytes ($ row ['bytes_reclaimed ' ] ?? 0 ),
6095+ ),
6096+ $ passes
6097+ ),
6098+ array ( 'pass ' , 'processed ' , 'would_remove ' , 'removed ' , 'skipped ' , 'remaining_total ' , 'bytes ' ),
6099+ array ( 'format ' => 'table ' ),
6100+ 'pass '
6101+ );
6102+ }
6103+
6104+ if ( ! empty ($ result ['next_commands ' ]) ) {
6105+ WP_CLI ::log ('' );
6106+ WP_CLI ::log ('Next command: ' . (string ) ( (array ) $ result ['next_commands ' ] )[0 ]);
6107+ }
6108+ }
6109+
60226110 /**
60236111 * Compact bounded cleanup JSON for chat/operator output.
60246112 *
0 commit comments