From 6a1948b228ba49c542e7aa9093adf198847aeaa5 Mon Sep 17 00:00:00 2001 From: Chris Huber Date: Sun, 7 Jun 2026 07:59:49 -0400 Subject: [PATCH] fix: delete marked worktrees before classification --- inc/Cli/Commands/WorkspaceCommand.php | 17 +++++++++++++++++ tests/smoke-worktree-cleanup-cli.php | 8 +++++--- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/inc/Cli/Commands/WorkspaceCommand.php b/inc/Cli/Commands/WorkspaceCommand.php index fc19574..014b1d0 100644 --- a/inc/Cli/Commands/WorkspaceCommand.php +++ b/inc/Cli/Commands/WorkspaceCommand.php @@ -2850,6 +2850,23 @@ private function run_worktree_abandoned_orchestration( array $assoc_args ): arra 'source' => self::CLEANUP_CLI_SOURCE, ); + if ( $apply ) { + $bounded = $this->run_worktree_abandoned_bounded_apply($abilities['bounded_apply'], $result, true, $force, $limit, 'initial'); + if ( is_wp_error($bounded) ) { + return $bounded; + } + + if ( 'bounded' === $stage ) { + $prune = $abilities['prune']->execute(array()); + if ( is_wp_error($prune) ) { + return $prune; + } + $result['steps']['prune'] = $this->summarize_worktree_abandoned_step($prune); + + return $this->finalize_worktree_abandoned_result($result, $apply, $force, $limit, $passes, $until_budget, $started_at); + } + } + if ( $stage_order[ $stage ] <= $stage_order['reconcile'] ) { $reconcile_input = array_merge( $common_page, diff --git a/tests/smoke-worktree-cleanup-cli.php b/tests/smoke-worktree-cleanup-cli.php index 66aff88..ebb465a 100644 --- a/tests/smoke-worktree-cleanup-cli.php +++ b/tests/smoke-worktree-cleanup-cli.php @@ -1130,6 +1130,7 @@ public function execute( array $input ): array datamachine_code_cleanup_assert(JSON_ERROR_NONE === json_last_error(), 'abandoned JSON output parses cleanly'); datamachine_code_cleanup_assert(true === ( $abandoned_json['applied'] ?? null ), 'abandoned apply mode is explicit in JSON'); datamachine_code_cleanup_assert(true === ( $abandoned_json['force'] ?? null ), 'abandoned force mode is explicit in JSON'); + datamachine_code_cleanup_assert('bounded_apply_initial' === array_key_first($abandoned_json['steps'] ?? array()), 'abandoned apply drains already-marked rows before slow classifiers'); datamachine_code_cleanup_assert(1 === (int) ( $reconcile_metadata_ability->last_input['limit'] ?? 0 ), 'abandoned forwards limit to metadata reconciliation'); datamachine_code_cleanup_assert(false === ( $reconcile_metadata_ability->last_input['dry_run'] ?? null ), 'abandoned --apply applies metadata reconciliation'); datamachine_code_cleanup_assert(array( 0, 1 ) === array_map(fn( $input ) => (int) ( $input['offset'] ?? 0 ), array_slice($reconcile_metadata_ability->inputs, -2)), 'abandoned drains metadata reconciliation pages in apply mode'); @@ -1140,7 +1141,7 @@ public function execute( array $input ): array datamachine_code_cleanup_assert(true === ( $bounded_apply_ability->last_input['force'] ?? null ), 'abandoned forwards force only to bounded cleanup removal'); datamachine_code_cleanup_assert(false === ( $bounded_apply_ability->last_input['dry_run'] ?? null ), 'abandoned --apply removes eligible rows'); datamachine_code_cleanup_assert(1 === $prune_ability->calls, 'abandoned prunes stale git metadata after cleanup pass'); - datamachine_code_cleanup_assert(1 === (int) ( $abandoned_json['summary']['removed'] ?? 0 ), 'abandoned summary reports removed rows'); + datamachine_code_cleanup_assert(2 === (int) ( $abandoned_json['summary']['removed'] ?? 0 ), 'abandoned summary reports removed rows from initial and post-classifier drains'); datamachine_code_cleanup_assert(2 === (int) ( $abandoned_json['summary']['blocked'] ?? 0 ), 'abandoned summary reports blocked rows'); datamachine_code_cleanup_assert(1 === (int) ( $abandoned_json['summary']['blocked_by_reason']['unpushed_commits'] ?? 0 ), 'abandoned preserves unpushed-commit blocker evidence'); @@ -1172,9 +1173,10 @@ public function execute( array $input ): array datamachine_code_cleanup_assert(JSON_ERROR_NONE === json_last_error(), 'abandoned stalled remote-clean JSON output parses cleanly'); datamachine_code_cleanup_assert('remote-clean' === ( $abandoned_remote_clean_stalled_json['continuation']['stage'] ?? '' ), 'abandoned stalled remote-clean emits remote-clean continuation'); datamachine_code_cleanup_assert(42 === (int) ( $abandoned_remote_clean_stalled_json['continuation']['offset'] ?? -1 ), 'abandoned stalled remote-clean keeps current offset continuation'); - datamachine_code_cleanup_assert(count($bounded_apply_ability->inputs) === $bounded_call_count_before_stalled_classifier + 1, 'abandoned drains bounded cleanup before returning stalled classifier continuation'); + datamachine_code_cleanup_assert(count($bounded_apply_ability->inputs) === $bounded_call_count_before_stalled_classifier + 2, 'abandoned drains bounded cleanup before and after stalled classifier continuation'); + datamachine_code_cleanup_assert('bounded_apply_initial' === array_key_first($abandoned_remote_clean_stalled_json['steps'] ?? array()), 'abandoned stalled classifier run starts with bounded cleanup'); datamachine_code_cleanup_assert(isset($abandoned_remote_clean_stalled_json['steps']['bounded_apply_remote-clean']), 'abandoned stalled classifier output includes bounded cleanup step'); - datamachine_code_cleanup_assert(1 === (int) ( $abandoned_remote_clean_stalled_json['summary']['removed'] ?? 0 ), 'abandoned stalled classifier summary includes bounded removals'); + datamachine_code_cleanup_assert(2 === (int) ( $abandoned_remote_clean_stalled_json['summary']['removed'] ?? 0 ), 'abandoned stalled classifier summary includes bounded removals'); $active_remote_clean_ability->stall_at_offset = null; $reconcile_metadata_ability->stall_at_offset = 90;