Skip to content

Commit 252a898

Browse files
authored
Merge pull request #832 from Extra-Chill/fix/issue-824-safe-cleanup
Add safe workspace cleanup entrypoint
2 parents 94fc07d + bddf838 commit 252a898

4 files changed

Lines changed: 531 additions & 23 deletions

File tree

inc/Cli/Commands/WorkspaceCommand.php

Lines changed: 111 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
use DataMachineCode\Cleanup\CompositeCleanupRunEvidenceStore;
2424
use DataMachineCode\Cleanup\CleanupRunEvidenceStoreInterface;
2525
use DataMachineCode\Workspace\Workspace;
26+
use DataMachineCode\Workspace\WorkspaceSafeCleanupOrchestrator;
2627
use DataMachineCode\Workspace\WorktreeContextInjector;
2728
use DataMachineCode\Workspace\WorkspaceMutationLock;
2829

@@ -607,7 +608,7 @@ public function adopt_repo( array $args, array $assoc_args ): void {
607608
* ## OPTIONS
608609
*
609610
* <operation>
610-
* : Cleanup operation. One of: <plan|apply|until-empty|run|status|resume|cancel|evidence>.
611+
* : Cleanup operation. One of: <safe|plan|apply|until-empty|run|status|resume|cancel|evidence>.
611612
* Existing task-backed controls remain: <run|status|resume|cancel|evidence>.
612613
*
613614
* [<run-id>]
@@ -628,17 +629,19 @@ public function adopt_repo( array $args, array $assoc_args ): void {
628629
* ---
629630
*
630631
* [--dry-run]
631-
* : Run the selected cleanup review synchronously through workspace abilities.
632+
* : Run the selected cleanup review synchronously through workspace abilities. For
633+
* `safe`, preview all safe stages and stale lock pruning without removals.
632634
*
633635
* [--force]
634636
* : Pass force=true into the cleanup task params for modes that support it.
637+
* Refused by `safe`.
635638
*
636-
* [--include-artifacts]
637-
* : For `plan --mode=retention`, include artifact cleanup rows. Retention
638-
* planning includes a bounded artifact inventory page by default; this flag
639-
* remains accepted for explicitness and `--mode=artifacts` still creates an
640-
* artifact-only plan. `--mode=stale-worktrees` never includes artifacts unless
641-
* this flag is passed.
639+
* [--include-artifacts]
640+
* : For `plan --mode=retention`, include artifact cleanup rows. Retention
641+
* planning includes a bounded artifact inventory page by default; this flag
642+
* remains accepted for explicitness and `--mode=artifacts` still creates an
643+
* artifact-only plan. `--mode=stale-worktrees` never includes artifacts unless
644+
* this flag is passed.
642645
*
643646
* [--older-than=<duration>]
644647
* : Pass an age gate such as 7d or 24h into cleanup task params.
@@ -647,22 +650,22 @@ public function adopt_repo( array $args, array $assoc_args ): void {
647650
* : For `plan`, number of largest reclaimable paths to show in the upfront
648651
* summary. Defaults to 10.
649652
*
650-
* [--limit=<count>]
651-
* : For DB-backed `apply` / `resume`, maximum pending rows to process in this
652-
* invocation (default 25, max 100). For `plan`, maximum worktrees to scan in
653-
* each cleanup lane page. Plan pages default to 100 so huge workspaces return
654-
* actionable JSON quickly. Use --exhaustive for a full audit.
653+
* [--limit=<count>]
654+
* : For DB-backed `apply` / `resume`, maximum pending rows to process in this
655+
* invocation (default 25, max 100). For `plan`, maximum worktrees to scan in
656+
* each cleanup lane page. Plan pages default to 100 so huge workspaces return
657+
* actionable JSON quickly. Use --exhaustive for a full audit.
655658
*
656-
* [--offset=<count>]
657-
* : Pagination offset (0-indexed) for bounded plan pages and artifact dry-run
658-
* pages. Walk huge workspaces by feeding the previous response's
659-
* `continuation.next_offset` until `continuation.complete` is true.
659+
* [--offset=<count>]
660+
* : Pagination offset (0-indexed) for bounded plan pages and artifact dry-run
661+
* pages. Walk huge workspaces by feeding the previous response's
662+
* `continuation.next_offset` until `continuation.complete` is true.
660663
*
661-
* [--exhaustive]
662-
* : For `plan`, request a full unbounded audit instead of the default bounded
663-
* inventory-first page. For `--mode=artifacts --dry-run`, scan every worktree
664-
* AND run per-worktree git status / unpushed-commit safety probes. Slow on
665-
* huge workspaces; use sparingly for full audits.
664+
* [--exhaustive]
665+
* : For `plan`, request a full unbounded audit instead of the default bounded
666+
* inventory-first page. For `--mode=artifacts --dry-run`, scan every worktree
667+
* AND run per-worktree git status / unpushed-commit safety probes. Slow on
668+
* huge workspaces; use sparingly for full audits.
666669
*
667670
* [--safety-probes]
668671
* : For `--mode=artifacts --dry-run`, run the per-worktree git safety probes
@@ -688,6 +691,12 @@ public function adopt_repo( array $args, array $assoc_args ): void {
688691
* : For `cleanup until-empty --mode=artifacts`, stop before starting another
689692
* pass after this many seconds.
690693
*
694+
* [--passes=<count>]
695+
* : For `cleanup safe`, maximum child-drain passes per cycle. Defaults to 10.
696+
*
697+
* [--cycles=<count>]
698+
* : For `cleanup safe`, maximum safe cleanup cycles before stopping. Defaults to 5.
699+
*
691700
* [--format=<format>]
692701
* : Output format.
693702
* ---
@@ -700,6 +709,9 @@ public function adopt_repo( array $args, array $assoc_args ): void {
700709
*
701710
* ## EXAMPLES
702711
*
712+
* # Apply all currently safe DMC workspace cleanup and report blockers
713+
* wp datamachine-code workspace cleanup safe --format=json
714+
*
703715
* # Create a DB-backed cleanup plan for review
704716
* wp datamachine-code workspace cleanup plan --mode=retention
705717
*
@@ -746,11 +758,15 @@ public function adopt_repo( array $args, array $assoc_args ): void {
746758
public function cleanup( array $args, array $assoc_args ): void {
747759
$operation = (string) ( $args[0] ?? '' );
748760
if ( '' === $operation ) {
749-
WP_CLI::error('Usage: wp datamachine-code workspace cleanup <plan|apply|run|status|resume|cancel|evidence> [<run-id>] [--mode=<mode>]');
761+
WP_CLI::error('Usage: wp datamachine-code workspace cleanup <safe|plan|apply|run|status|resume|cancel|evidence> [<run-id>] [--mode=<mode>]');
750762
return;
751763
}
752764

753765
switch ( $operation ) {
766+
case 'safe':
767+
$this->run_cleanup_safe($assoc_args);
768+
return;
769+
754770
case 'plan':
755771
$this->run_cleanup_plan($assoc_args);
756772
return;
@@ -797,6 +813,78 @@ public function cleanup( array $args, array $assoc_args ): void {
797813
}
798814
}
799815

816+
private function run_cleanup_safe( array $assoc_args ): void {
817+
$input = array(
818+
'dry_run' => ! empty($assoc_args['dry-run']),
819+
'force' => ! empty($assoc_args['force']),
820+
'discard_unpushed' => ! empty($assoc_args['discard-unpushed']),
821+
'source' => self::CLEANUP_CLI_SOURCE,
822+
);
823+
foreach ( array( 'limit', 'passes', 'cycles' ) as $key ) {
824+
if ( isset($assoc_args[ $key ]) ) {
825+
$input[ $key ] = (int) $assoc_args[ $key ];
826+
}
827+
}
828+
if ( isset($assoc_args['until-budget']) && '' !== trim( (string) $assoc_args['until-budget']) ) {
829+
$input['until_budget'] = trim( (string) $assoc_args['until-budget']);
830+
}
831+
832+
$orchestrator = new WorkspaceSafeCleanupOrchestrator();
833+
$result = $orchestrator->run($input);
834+
if ( is_wp_error($result) ) {
835+
$this->render_workspace_error($result);
836+
return;
837+
}
838+
839+
$this->render_cleanup_safe_result($result, $assoc_args);
840+
}
841+
842+
private function render_cleanup_safe_result( array $result, array $assoc_args ): void {
843+
if ( 'json' === (string) ( $assoc_args['format'] ?? '' ) ) {
844+
if ( empty($assoc_args['verbose']) ) {
845+
$result['steps'] = $this->compact_safe_cleanup_steps( (array) ( $result['steps'] ?? array() ) );
846+
}
847+
$this->renderer()->json($result);
848+
return;
849+
}
850+
851+
$summary = (array) ( $result['summary'] ?? array() );
852+
WP_CLI::log('Safe workspace cleanup:');
853+
$this->format_items(
854+
array(
855+
array( 'metric' => 'applied', 'value' => ! empty($result['applied']) ? 'yes' : 'no' ),
856+
array( 'metric' => 'state', 'value' => (string) ( $result['state'] ?? '-' ) ),
857+
array( 'metric' => 'cycles', 'value' => (string) ( $summary['cycles'] ?? 0 ) ),
858+
array( 'metric' => 'removed', 'value' => (string) ( $summary['removed'] ?? 0 ) ),
859+
array( 'metric' => 'would_remove', 'value' => (string) ( $summary['would_remove'] ?? 0 ) ),
860+
array( 'metric' => 'marked_cleanup_eligible', 'value' => (string) ( $summary['marked_cleanup_eligible'] ?? 0 ) ),
861+
array( 'metric' => 'bytes_reclaimed', 'value' => $this->format_bytes( (int) ( $summary['bytes_reclaimed'] ?? 0 ) ) ),
862+
array( 'metric' => 'stale_lock_files_removed', 'value' => (string) ( $summary['lock_files_removed'] ?? 0 ) ),
863+
array( 'metric' => 'blockers', 'value' => (string) ( $summary['blocker_count'] ?? 0 ) ),
864+
),
865+
array( 'metric', 'value' ),
866+
array( 'format' => 'table' ),
867+
'metric'
868+
);
869+
870+
$blockers = (array) ( $result['blockers'] ?? array() );
871+
if ( array() !== $blockers ) {
872+
WP_CLI::log('Compact blockers:');
873+
$this->format_items($blockers, array( 'reason_code', 'count' ), array( 'format' => 'table' ), 'reason_code');
874+
}
875+
}
876+
877+
private function compact_safe_cleanup_steps( array $steps ): array {
878+
$compact = array();
879+
foreach ( $steps as $key => $step ) {
880+
if ( is_array($step) ) {
881+
$compact[ $key ] = $step;
882+
}
883+
}
884+
885+
return $compact;
886+
}
887+
800888
private function run_cleanup_task( array $assoc_args ): void {
801889
if ( isset($assoc_args['dry-run']) ) {
802890
$this->run_cleanup_review($assoc_args);

inc/Workspace/Workspace.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
require_once __DIR__ . '/WorkspaceWorktreeInventoryCleanup.php';
3333
require_once __DIR__ . '/WorkspaceWorktreeEmergencyCleanup.php';
3434
require_once __DIR__ . '/WorktreeCleanupClassifier.php';
35+
require_once __DIR__ . '/WorkspaceSafeCleanupOrchestrator.php';
3536

3637
class Workspace {
3738
use WorkspaceCoreUtilities;

0 commit comments

Comments
 (0)