From 78ccb0f99f8675f4ac608eed2beae9d8dcc1de0d Mon Sep 17 00:00:00 2001 From: Chris Huber Date: Mon, 11 May 2026 10:58:39 -0400 Subject: [PATCH] fix: support bounded reconciliation apply --- inc/Cli/Commands/WorkspaceCommand.php | 9 ++++++++- inc/Workspace/Workspace.php | 22 +++++++++++++++++++-- tests/smoke-worktree-metadata-reconcile.php | 8 ++++++++ 3 files changed, 36 insertions(+), 3 deletions(-) diff --git a/inc/Cli/Commands/WorkspaceCommand.php b/inc/Cli/Commands/WorkspaceCommand.php index 062028e..a7e20d6 100644 --- a/inc/Cli/Commands/WorkspaceCommand.php +++ b/inc/Cli/Commands/WorkspaceCommand.php @@ -2100,7 +2100,7 @@ private function renderGitOperationResult( string $operation, array $result, arr * # Adopt/reconcile unmanaged worktree metadata before cleanup * wp datamachine-code workspace worktree reconcile-metadata --dry-run --format=json * wp datamachine-code workspace worktree reconcile-metadata --dry-run --limit=25 --offset=0 --format=json - * wp datamachine-code workspace worktree reconcile-metadata --apply --format=json + * wp datamachine-code workspace worktree reconcile-metadata --apply --limit=25 --offset=0 --format=json * * # Ignore dirty working-tree safety (caution) * wp datamachine-code workspace worktree cleanup --force @@ -3201,6 +3201,13 @@ private function render_worktree_metadata_reconciliation_result( array $result, WP_CLI::success( sprintf( '%d metadata reconciliation proposal(s). Review JSON output before applying; --apply-plan remains a low-level escape hatch until DB-backed cleanup runs land.', count( $proposals ) ) ); return; } + if ( isset( $result['pagination']['next_offset'] ) ) { + WP_CLI::log( sprintf( + 'Next page: wp datamachine-code workspace worktree reconcile-metadata --apply --limit=%d --offset=%d --format=json', + (int) ( $result['pagination']['limit'] ?? 0 ), + (int) $result['pagination']['next_offset'] + ) ); + } WP_CLI::success( sprintf( 'Wrote metadata for %d worktree(s); %d skipped.', count( $written ), count( $skipped ) ) ); } diff --git a/inc/Workspace/Workspace.php b/inc/Workspace/Workspace.php index 3c7662d..24a43c4 100644 --- a/inc/Workspace/Workspace.php +++ b/inc/Workspace/Workspace.php @@ -3889,6 +3889,7 @@ public function worktree_reconcile_metadata( array $opts = array() ): array|\WP_ } if ( $apply ) { + $plan['direct_apply'] = true; return $this->apply_worktree_metadata_reconciliation_plan( $plan ); } @@ -4426,10 +4427,12 @@ private function apply_worktree_metadata_reconciliation_plan( array $plan ): arr $classified_skips = $this->classify_worktree_metadata_reconciliation_skips( $skipped ); - return array( + $inspected = isset( $plan['summary']['inspected'] ) ? (int) $plan['summary']['inspected'] : count( (array) ( $listing['worktrees'] ?? array() ) ); + $result = array( 'success' => true, 'dry_run' => false, 'applied' => true, + 'direct_apply' => ! empty( $plan['direct_apply'] ), 'generated_at' => gmdate( 'c' ), 'workspace_path' => $this->workspace_path, 'proposals' => $planned, @@ -4437,8 +4440,23 @@ private function apply_worktree_metadata_reconciliation_plan( array $plan ): arr 'skipped' => $skipped, 'still_unsafe' => $classified_skips['still_unsafe'], 'external_worktrees' => $classified_skips['external_worktrees'], - 'summary' => $this->build_worktree_metadata_reconciliation_summary( count( (array) ( $listing['worktrees'] ?? array() ) ), $planned, $written, $skipped ), + 'summary' => $this->build_worktree_metadata_reconciliation_summary( $inspected, $planned, $written, $skipped ), ); + + if ( isset( $plan['pagination'] ) && is_array( $plan['pagination'] ) ) { + $result['pagination'] = $plan['pagination']; + } + if ( isset( $plan['evidence'] ) && is_array( $plan['evidence'] ) ) { + $result['evidence'] = array_merge( + $plan['evidence'], + array( + 'scope' => ! empty( $plan['direct_apply'] ) ? 'paginated metadata reconciliation direct apply' : (string) ( $plan['evidence']['scope'] ?? 'paginated metadata reconciliation apply-plan' ), + 'apply_source' => ! empty( $plan['direct_apply'] ) ? 'direct_apply' : 'apply_plan', + ) + ); + } + + return $result; } /** diff --git a/tests/smoke-worktree-metadata-reconcile.php b/tests/smoke-worktree-metadata-reconcile.php index 468e9d3..a3a7869 100644 --- a/tests/smoke-worktree-metadata-reconcile.php +++ b/tests/smoke-worktree-metadata-reconcile.php @@ -394,6 +394,14 @@ function size_format( $bytes ): string { $auto_apply = $ws->worktree_reconcile_metadata( array( 'apply' => true ) ); $assert( true, ! is_wp_error( $auto_apply ) && ( $auto_apply['success'] ?? false ), 'DMC-owned reconciliation apply path runs without a manual plan' ); + $bounded_auto_apply = $ws->worktree_reconcile_metadata( array( 'apply' => true, 'limit' => 2, 'offset' => 2 ) ); + $assert( true, ! is_wp_error( $bounded_auto_apply ) && ( $bounded_auto_apply['success'] ?? false ), 'bounded direct reconciliation apply runs without a manual plan file' ); + $assert( true, (bool) ( $bounded_auto_apply['direct_apply'] ?? false ), 'bounded direct apply identifies direct apply source' ); + $assert( false, (bool) ( $bounded_auto_apply['dry_run'] ?? true ), 'bounded direct apply is not a dry-run' ); + $assert( 2, (int) ( $bounded_auto_apply['summary']['inspected'] ?? 0 ), 'bounded direct apply summary stays page-scoped' ); + $assert( 2, (int) ( $bounded_auto_apply['pagination']['limit'] ?? 0 ), 'bounded direct apply preserves pagination limit' ); + $assert( 2, (int) ( $bounded_auto_apply['pagination']['offset'] ?? 0 ), 'bounded direct apply preserves pagination offset' ); + $assert( 'direct_apply', $bounded_auto_apply['evidence']['apply_source'] ?? '', 'bounded direct apply exposes evidence source' ); $inventory_after = $ws->worktree_cleanup_merged( array( 'dry_run' => true, 'inventory_only' => true, 'skip_github' => true ) ); $assert( 1, (int) ( $inventory_after['summary']['skipped_by_reason']['needs_metadata_reconcile'] ?? 0 ), 'inventory cleanup requires fewer metadata reconciliation passes after apply' );