Skip to content

Commit f65a652

Browse files
Improve cleanup summary output (#781)
* Improve cleanup summary output * Fix cleanup output lint --------- Co-authored-by: homeboy-ci[bot] <266378653+homeboy-ci[bot]@users.noreply.github.com>
1 parent 89ff7ee commit f65a652

4 files changed

Lines changed: 163 additions & 30 deletions

File tree

inc/Cleanup/CleanupRemainingWorkSummary.php

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,9 +90,13 @@ private static function empty_summary(): array {
9090
'applied_by_type' => array(),
9191
'skipped_by_reason' => array(),
9292
'blocked_resolvers_by_reason' => array(),
93+
'total_bytes_reclaimed' => 0,
9394
'remaining_reclaimable_artifact_bytes' => 0,
9495
'remaining_safely_removable_worktrees' => 0,
96+
'remaining_safe_candidates' => 0,
97+
'protected_unpushed_candidates' => 0,
9598
'recommended_commands' => array(),
99+
'next_commands' => array(),
96100
);
97101
}
98102

@@ -170,10 +174,47 @@ private static function finalize( array $summary ): array {
170174
ksort($summary['applied_by_type']);
171175
ksort($summary['skipped_by_reason']);
172176
ksort($summary['blocked_resolvers_by_reason']);
173-
$summary['recommended_commands'] = self::recommended_commands($summary);
177+
$summary['total_bytes_reclaimed'] = self::total_applied_bytes( (array) $summary['applied_by_type']);
178+
$summary['remaining_safe_candidates'] = (int) ( $summary['remaining_safely_removable_worktrees'] ?? 0 );
179+
$summary['protected_unpushed_candidates'] = self::reason_count($summary, 'unpushed_commits');
180+
$summary['recommended_commands'] = self::recommended_commands($summary);
181+
$summary['next_commands'] = self::next_commands( (array) $summary['recommended_commands']);
174182
return $summary;
175183
}
176184

185+
private static function total_applied_bytes( array $types ): int {
186+
$total = 0;
187+
foreach ( $types as $row ) {
188+
$total += max(0, (int) ( is_array($row) ? ( $row['bytes_reclaimed'] ?? 0 ) : 0 ));
189+
}
190+
return $total;
191+
}
192+
193+
private static function reason_count( array $summary, string $reason ): int {
194+
$total = 0;
195+
foreach ( array( 'skipped_by_reason', 'blocked_resolvers_by_reason' ) as $bucket ) {
196+
$row = (array) ( $summary[ $bucket ][ $reason ] ?? array() );
197+
$total += max(0, (int) ( $row['count'] ?? 0 ));
198+
}
199+
return $total;
200+
}
201+
202+
private static function next_commands( array $commands ): array {
203+
$next = array();
204+
foreach ( $commands as $row ) {
205+
if ( ! is_array($row) ) {
206+
continue;
207+
}
208+
foreach ( array( 'command', 'apply', 'alternative' ) as $field ) {
209+
$value = (string) ( $row[ $field ] ?? '' );
210+
if ( '' !== $value ) {
211+
$next[] = $value;
212+
}
213+
}
214+
}
215+
return array_values(array_unique($next));
216+
}
217+
177218
private static function recommended_commands( array $summary ): array {
178219
$commands = array();
179220
if ( (int) $summary['remaining_reclaimable_artifact_bytes'] > 0 ) {

inc/Cli/Commands/WorkspaceCommand.php

Lines changed: 43 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1377,6 +1377,7 @@ private function render_cleanup_operator_summary( array $summary ): void {
13771377
WP_CLI::log('Cleanup operator summary:');
13781378
$cleanup_counts = (array) ( $summary['cleanup_counts'] ?? array() );
13791379
$artifacts = (array) ( $summary['artifact_cleanup'] ?? array() );
1380+
$remaining_safe = (int) ( $summary['remaining_safe_candidates'] ?? $summary['remaining_safely_removable_worktrees'] ?? 0 );
13801381
$this->format_items(
13811382
array(
13821383
array(
@@ -1399,6 +1400,14 @@ private function render_cleanup_operator_summary( array $summary ): void {
13991400
'metric' => 'bytes_reclaimed',
14001401
'value' => $this->format_bytes($cleanup_counts['bytes_reclaimed'] ?? 0),
14011402
),
1403+
array(
1404+
'metric' => 'remaining_safe_candidates',
1405+
'value' => $remaining_safe,
1406+
),
1407+
array(
1408+
'metric' => 'protected_unpushed_candidates',
1409+
'value' => (int) ( $summary['protected_unpushed_candidates'] ?? 0 ),
1410+
),
14021411
array(
14031412
'metric' => 'remaining_reclaimable_artifacts',
14041413
'value' => $this->format_bytes($artifacts['remaining_reclaimable_artifact_bytes'] ?? 0),
@@ -1459,24 +1468,28 @@ private function build_cleanup_operator_summary( array $result ): array {
14591468

14601469
return array_filter(
14611470
array(
1462-
'success' => (bool) ( $result['success'] ?? false ),
1463-
'run_id' => (string) ( $result['run_id'] ?? '' ),
1464-
'job_id' => isset($result['job_id']) ? (int) $result['job_id'] : null,
1465-
'mode' => (string) ( $result['mode'] ?? $result['evidence']['engine_data']['cleanup_run']['mode'] ?? '' ),
1466-
'state' => (string) ( $result['state'] ?? '' ),
1467-
'status' => (string) ( $result['status'] ?? '' ),
1468-
'parent_status' => (string) ( $result['parent_status'] ?? '' ),
1469-
'created_at' => (string) ( $result['created_at'] ?? '' ),
1470-
'completed_at' => (string) ( $result['completed_at'] ?? $result['parent_completed_at'] ?? '' ),
1471-
'cleanup_counts' => array(
1471+
'success' => (bool) ( $result['success'] ?? false ),
1472+
'run_id' => (string) ( $result['run_id'] ?? '' ),
1473+
'job_id' => isset($result['job_id']) ? (int) $result['job_id'] : null,
1474+
'mode' => (string) ( $result['mode'] ?? $result['evidence']['engine_data']['cleanup_run']['mode'] ?? '' ),
1475+
'state' => (string) ( $result['state'] ?? '' ),
1476+
'status' => (string) ( $result['status'] ?? '' ),
1477+
'parent_status' => (string) ( $result['parent_status'] ?? '' ),
1478+
'created_at' => (string) ( $result['created_at'] ?? '' ),
1479+
'completed_at' => (string) ( $result['completed_at'] ?? $result['parent_completed_at'] ?? '' ),
1480+
'cleanup_counts' => array(
14721481
'planned' => (int) ( $cleanup_items['planned_rows'] ?? 0 ),
14731482
'applied' => (int) ( $cleanup_items['applied_rows'] ?? 0 ),
14741483
'skipped' => (int) ( $cleanup_items['skipped_rows'] ?? 0 ),
14751484
'failed' => (int) ( $cleanup_items['failed_rows'] ?? 0 ),
14761485
'bytes_reclaimed' => (int) ( $cleanup_items['bytes_reclaimed'] ?? 0 ),
14771486
'freed_human' => (string) ( $cleanup_items['freed_human'] ?? $this->format_bytes($cleanup_items['bytes_reclaimed'] ?? 0) ),
14781487
),
1479-
'artifact_cleanup' => array(
1488+
'total_bytes_reclaimed' => (int) ( $remaining['total_bytes_reclaimed'] ?? $cleanup_items['bytes_reclaimed'] ?? 0 ),
1489+
'total_reclaimed_human' => $this->format_bytes($remaining['total_bytes_reclaimed'] ?? $cleanup_items['bytes_reclaimed'] ?? 0),
1490+
'remaining_safe_candidates' => (int) ( $remaining['remaining_safe_candidates'] ?? $remaining['remaining_safely_removable_worktrees'] ?? 0 ),
1491+
'protected_unpushed_candidates' => (int) ( $remaining['protected_unpushed_candidates'] ?? 0 ),
1492+
'artifact_cleanup' => array(
14801493
'planned' => (int) ( $artifacts['planned_rows'] ?? 0 ),
14811494
'applied' => (int) ( $artifacts['applied_rows'] ?? 0 ),
14821495
'skipped' => (int) ( $artifacts['skipped_rows'] ?? 0 ),
@@ -1485,13 +1498,14 @@ private function build_cleanup_operator_summary( array $result ): array {
14851498
'remaining_reclaimable_artifact_bytes' => (int) ( $remaining['remaining_reclaimable_artifact_bytes'] ?? $artifacts['remaining_reclaimable_artifact_bytes'] ?? 0 ),
14861499
'remaining_reclaimable_human' => $this->format_bytes($remaining['remaining_reclaimable_artifact_bytes'] ?? $artifacts['remaining_reclaimable_artifact_bytes'] ?? 0),
14871500
),
1488-
'children' => $this->build_cleanup_operator_child_summary( (array) ( $result['children'] ?? $result['evidence']['children'] ?? array() ) ),
1489-
'by_type' => (array) ( $cleanup_items['by_type'] ?? array() ),
1490-
'skipped_by_reason' => (array) ( $remaining['skipped_by_reason'] ?? $cleanup_items['skipped_examples_by_reason'] ?? array() ),
1491-
'failed_by_reason' => (array) ( $cleanup_items['failed_by_reason'] ?? $artifacts['failed_by_reason'] ?? array() ),
1492-
'top_blocked_examples' => $this->cleanup_operator_blocked_examples($result),
1493-
'recommended_commands' => (array) ( $remaining['recommended_commands'] ?? array() ),
1494-
'locks' => (array) ( $result['locks'] ?? array() ),
1501+
'children' => $this->build_cleanup_operator_child_summary( (array) ( $result['children'] ?? $result['evidence']['children'] ?? array() ) ),
1502+
'by_type' => (array) ( $cleanup_items['by_type'] ?? array() ),
1503+
'skipped_by_reason' => (array) ( $remaining['skipped_by_reason'] ?? $cleanup_items['skipped_examples_by_reason'] ?? array() ),
1504+
'failed_by_reason' => (array) ( $cleanup_items['failed_by_reason'] ?? $artifacts['failed_by_reason'] ?? array() ),
1505+
'top_blocked_examples' => $this->cleanup_operator_blocked_examples($result),
1506+
'recommended_commands' => (array) ( $remaining['recommended_commands'] ?? array() ),
1507+
'next_commands' => (array) ( $remaining['next_commands'] ?? array() ),
1508+
'locks' => (array) ( $result['locks'] ?? array() ),
14951509
),
14961510
fn( $value ) => null !== $value && array() !== $value && '' !== $value
14971511
);
@@ -1673,13 +1687,21 @@ private function render_cleanup_remaining_work_summary( array $summary ): void {
16731687
WP_CLI::log('Remaining work summary:');
16741688
$this->format_items(
16751689
array(
1690+
array(
1691+
'metric' => 'total_bytes_reclaimed',
1692+
'value' => $this->format_bytes($summary['total_bytes_reclaimed'] ?? 0),
1693+
),
16761694
array(
16771695
'metric' => 'remaining_reclaimable_artifact_bytes',
16781696
'value' => $this->format_bytes($summary['remaining_reclaimable_artifact_bytes'] ?? 0),
16791697
),
16801698
array(
1681-
'metric' => 'remaining_safely_removable_worktrees',
1682-
'value' => (int) ( $summary['remaining_safely_removable_worktrees'] ?? 0 ),
1699+
'metric' => 'remaining_safe_candidates',
1700+
'value' => (int) ( $summary['remaining_safe_candidates'] ?? $summary['remaining_safely_removable_worktrees'] ?? 0 ),
1701+
),
1702+
array(
1703+
'metric' => 'protected_unpushed_candidates',
1704+
'value' => (int) ( $summary['protected_unpushed_candidates'] ?? 0 ),
16831705
),
16841706
),
16851707
array( 'metric', 'value' ),
@@ -3635,7 +3657,7 @@ public function worktree( array $args, array $assoc_args ): void {
36353657

36363658
$input = array();
36373659
$input_builder = (string) ( $operation_config['input_builder'] ?? '' );
3638-
if ( '' !== $input_builder && method_exists($this, $input_builder) ) {
3660+
if ( '' !== $input_builder ) {
36393661
$input = $this->{$input_builder}($operation, $assoc_args);
36403662
}
36413663

inc/Workspace/CleanupRunService.php

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -607,17 +607,20 @@ private function run_progress( array $run, array $items, array $summary ): array
607607
private function remaining_work_summary( string $run_id, array $items, array $progress ): array {
608608
$summary = CleanupRemainingWorkSummary::from_items($items);
609609
if ( ! empty($progress['resumable']) ) {
610+
$resume_command = array(
611+
'bucket' => 'current_run_resume',
612+
'command' => sprintf('studio wp datamachine-code workspace cleanup status %s --format=json', $run_id),
613+
'apply' => sprintf('studio wp datamachine-code workspace cleanup resume %s --limit=%d', $run_id, self::DEFAULT_APPLY_LIMIT),
614+
'destructive' => false,
615+
'apply_destructive' => true,
616+
'why' => 'Resume the reviewed DB-backed cleanup run from persisted pending/failed/applying rows.',
617+
);
610618
array_unshift(
611619
$summary['recommended_commands'],
612-
array(
613-
'bucket' => 'current_run_resume',
614-
'command' => sprintf('studio wp datamachine-code workspace cleanup status %s --format=json', $run_id),
615-
'apply' => sprintf('studio wp datamachine-code workspace cleanup resume %s --limit=%d', $run_id, self::DEFAULT_APPLY_LIMIT),
616-
'destructive' => false,
617-
'apply_destructive' => true,
618-
'why' => 'Resume the reviewed DB-backed cleanup run from persisted pending/failed/applying rows.',
619-
)
620+
$resume_command
620621
);
622+
array_unshift($summary['next_commands'], (string) $resume_command['command'], (string) $resume_command['apply']);
623+
$summary['next_commands'] = array_values(array_unique($summary['next_commands']));
621624
}
622625

623626
return $summary;
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
<?php
2+
/**
3+
* Standalone coverage for cleanup remaining-work summary rendering fields.
4+
*/
5+
6+
declare(strict_types=1);
7+
8+
if ( ! defined('ABSPATH') ) {
9+
define('ABSPATH', __DIR__ . '/fixtures/');
10+
}
11+
12+
require_once dirname(__DIR__) . '/inc/Cleanup/CleanupRemainingWorkSummary.php';
13+
14+
use DataMachineCode\Cleanup\CleanupRemainingWorkSummary;
15+
16+
function cleanup_summary_assert_same( mixed $expected, mixed $actual, string $message ): void {
17+
if ( $expected !== $actual ) {
18+
throw new RuntimeException(sprintf("%s\nExpected: %s\nActual: %s", $message, var_export($expected, true), var_export($actual, true)));
19+
}
20+
}
21+
22+
$summary = CleanupRemainingWorkSummary::from_items(
23+
array(
24+
array(
25+
'item_type' => 'artifact_cleanup',
26+
'status' => 'applied',
27+
'bytes_reclaimed' => 2048,
28+
),
29+
array(
30+
'item_type' => 'worktree_removal',
31+
'status' => 'applied',
32+
'bytes_reclaimed' => 1024,
33+
),
34+
array(
35+
'item_type' => 'worktree_removal',
36+
'status' => 'pending',
37+
'handle' => 'repo@safe-candidate',
38+
),
39+
array(
40+
'item_type' => 'worktree_removal',
41+
'status' => 'skipped',
42+
'handle' => 'repo@unpushed',
43+
'reason_code' => 'unpushed_commits',
44+
),
45+
array(
46+
'item_type' => 'artifact_cleanup',
47+
'status' => 'pending',
48+
'evidence' => array(
49+
'handle' => 'repo@artifact',
50+
'artifact_size_bytes' => 4096,
51+
),
52+
),
53+
)
54+
);
55+
56+
cleanup_summary_assert_same(3072, $summary['total_bytes_reclaimed'] ?? null, 'Total reclaimed bytes should sum applied rows across types.');
57+
cleanup_summary_assert_same(1, $summary['remaining_safe_candidates'] ?? null, 'Remaining safe candidates should mirror safely removable worktrees.');
58+
cleanup_summary_assert_same(1, $summary['protected_unpushed_candidates'] ?? null, 'Protected unpushed candidates should be counted explicitly.');
59+
cleanup_summary_assert_same(4096, $summary['remaining_reclaimable_artifact_bytes'] ?? null, 'Remaining reclaimable artifact bytes should stay visible.');
60+
61+
$next_commands = (array) ( $summary['next_commands'] ?? array() );
62+
cleanup_summary_assert_same(true, in_array('studio wp datamachine-code workspace worktree bounded-cleanup-eligible-apply --dry-run --limit=25', $next_commands, true), 'Safe worktree review command should be flattened into next_commands.');
63+
cleanup_summary_assert_same(true, in_array('studio wp datamachine-code workspace cleanup run --mode=retention', $next_commands, true), 'Safe worktree apply command should be flattened into next_commands.');
64+
cleanup_summary_assert_same(true, in_array('git -C <worktree-path> log --oneline --decorate @{u}..HEAD', $next_commands, true), 'Unpushed inspection command should be flattened into next_commands.');
65+
cleanup_summary_assert_same(true, in_array('studio wp datamachine-code workspace cleanup run --mode=retention --dry-run --only=unpushed_commits --verbose --format=json', $next_commands, true), 'Unpushed focused review command should be flattened into next_commands.');
66+
67+
echo "cleanup remaining work summary test passed.\n";

0 commit comments

Comments
 (0)