Skip to content

Commit 235c750

Browse files
authored
fix: expose scoped artifact cleanup apply command (#616)
* fix: expose scoped artifact cleanup apply command * fix: align workspace command arrays
1 parent 551353d commit 235c750

4 files changed

Lines changed: 66 additions & 22 deletions

File tree

inc/Cli/Commands/WorkspaceCommand.php

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -168,15 +168,15 @@ public function list_repos( array $args, array $assoc_args ): void {
168168
function ( $repo ) {
169169
$freshness = is_array($repo['primary_freshness'] ?? null) ? $repo['primary_freshness'] : null;
170170
return array(
171-
'name' => $repo['name'],
172-
'kind' => ! empty($repo['is_worktree']) ? 'worktree' : 'primary',
173-
'repo' => $repo['repo'] ?? $repo['name'],
174-
'branch' => $repo['branch'] ?? '-',
175-
'freshness' => is_array($freshness) ? (string) ( $freshness['status'] ?? '-' ) : '-',
176-
'behind' => is_array($freshness) && null !== ( $freshness['behind'] ?? null ) ? (string) $freshness['behind'] : '-',
177-
'remote' => $repo['remote'] ?? '-',
178-
'git' => $repo['git'] ? 'yes' : 'no',
179-
'path' => $repo['path'],
171+
'name' => $repo['name'],
172+
'kind' => ! empty($repo['is_worktree']) ? 'worktree' : 'primary',
173+
'repo' => $repo['repo'] ?? $repo['name'],
174+
'branch' => $repo['branch'] ?? '-',
175+
'freshness' => is_array($freshness) ? (string) ( $freshness['status'] ?? '-' ) : '-',
176+
'behind' => is_array($freshness) && null !== ( $freshness['behind'] ?? null ) ? (string) $freshness['behind'] : '-',
177+
'remote' => $repo['remote'] ?? '-',
178+
'git' => $repo['git'] ? 'yes' : 'no',
179+
'path' => $repo['path'],
180180
);
181181
},
182182
$result['repos']
@@ -230,7 +230,7 @@ public function clone_repo( array $args, array $assoc_args ): void {
230230
array(
231231
'full' => isset($assoc_args['full']),
232232
'allow_duplicate_remote' => isset($assoc_args['allow-duplicate-remote']),
233-
'progress_callback' => static function ( array $event ): void {
233+
'progress_callback' => static function ( array $event ): void {
234234
$elapsed = number_format( (float) ( $event['elapsed'] ?? 0 ), 1);
235235
WP_CLI::log(sprintf('[clone %ss] %s', $elapsed, (string) ( $event['message'] ?? '' )));
236236
},
@@ -5106,7 +5106,8 @@ private function render_worktree_artifact_cleanup_result( array $result, array $
51065106
}
51075107

51085108
if ( $dry_run ) {
5109-
WP_CLI::success(sprintf('%d artifact(s) would be removed. Prefer `workspace cleanup run --mode=artifacts`; --apply-plan remains a low-level escape hatch until DB-backed cleanup runs land.', (int) ( $summary['would_remove_artifacts'] ?? 0 )));
5109+
$apply_command = (string) ( $result['apply_command'] ?? $summary['apply_command'] ?? 'studio wp datamachine-code workspace cleanup run --mode=artifacts --format=json' );
5110+
WP_CLI::success(sprintf('%d artifact(s) would be removed. Apply this page with `%s`; --apply-plan remains a low-level escape hatch.', (int) ( $summary['would_remove_artifacts'] ?? 0 ), $apply_command));
51105111
return;
51115112
}
51125113
WP_CLI::success(sprintf('Removed %d artifact(s); %d worktree(s) skipped.', (int) ( $summary['removed_artifacts'] ?? 0 ), count($skipped)));

inc/Workspace/WorkspaceArtifactCleanup.php

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ public function worktree_cleanup_artifacts( array $opts = array() ): array|\WP_E
5454
if ( $exhaustive ) {
5555
$limit = 0;
5656
}
57+
$apply_command = $this->build_artifact_cleanup_apply_command($limit, $offset, $exhaustive);
5758
// Apply paths default to safety probing (small subset). Dry-run defaults
5859
// to skipping the per-worktree git probes unless explicitly requested or
5960
// the caller asked for exhaustive mode.
@@ -66,7 +67,7 @@ public function worktree_cleanup_artifacts( array $opts = array() ): array|\WP_E
6667
}
6768

6869
if ( ! $dry_run && null === $apply_plan ) {
69-
return new \WP_Error('artifact_cleanup_plan_required', 'Artifact cleanup applies through reviewed JSON only on this low-level command. Prefer workspace cleanup run --mode=artifacts for daily cleanup; use --dry-run first and --apply-plan=<file> only as an escape hatch.', array( 'status' => 400 ));
70+
return new \WP_Error('artifact_cleanup_plan_required', sprintf('Artifact cleanup applies through the high-level cleanup runner for daily cleanup. Run `%s` to apply the same bounded page, or use --dry-run first and --apply-plan=<file> only as a low-level escape hatch.', $apply_command), array( 'status' => 400 ));
7071
}
7172

7273
$only_handles = null;
@@ -116,12 +117,13 @@ public function worktree_cleanup_artifacts( array $opts = array() ): array|\WP_E
116117

117118
if ( $dry_run ) {
118119
$response = array(
119-
'success' => true,
120-
'dry_run' => true,
121-
'candidates' => $candidates,
122-
'removed' => array(),
123-
'skipped' => $skipped,
124-
'summary' => $summary,
120+
'success' => true,
121+
'dry_run' => true,
122+
'apply_command' => $apply_command,
123+
'candidates' => $candidates,
124+
'removed' => array(),
125+
'skipped' => $skipped,
126+
'summary' => array( 'apply_command' => $apply_command ) + $summary,
125127
);
126128
if ( null !== $pagination ) {
127129
$response['pagination'] = $pagination;
@@ -178,6 +180,30 @@ public function worktree_cleanup_artifacts( array $opts = array() ): array|\WP_E
178180
return $response;
179181
}
180182

183+
/**
184+
* Build the high-level command that applies the same artifact cleanup page.
185+
*
186+
* @param int $limit Effective bounded scan limit.
187+
* @param int $offset Bounded inventory offset.
188+
* @param bool $exhaustive Whether the dry-run used exhaustive mode.
189+
* @return string
190+
*/
191+
private function build_artifact_cleanup_apply_command( int $limit, int $offset, bool $exhaustive ): string {
192+
$parts = array(
193+
'studio wp datamachine-code workspace cleanup run',
194+
'--mode=artifacts',
195+
);
196+
if ( $exhaustive ) {
197+
$parts[] = '--exhaustive';
198+
} else {
199+
$parts[] = sprintf('--limit=%d', $limit);
200+
$parts[] = sprintf('--offset=%d', $offset);
201+
}
202+
$parts[] = '--format=json';
203+
204+
return implode(' ', $parts);
205+
}
206+
181207
/**
182208
* Build current artifact cleanup candidates and safety skips.
183209
*

tests/smoke-worktree-cleanup-artifacts.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,8 @@ function apply_filters( string $hook_name, $value ) // phpcs:ignore Generic.Cod
208208
$assert('bounded_inventory', $plan['pagination']['mode'] ?? '', 'bounded dry-run advertises bounded_inventory mode');
209209
$assert(false, (bool) ( $plan['pagination']['safety_probes'] ?? true ), 'bounded dry-run reports safety_probes=false');
210210
$assert(true, (bool) ( $plan['pagination']['complete'] ?? false ), 'bounded dry-run completes when total <= limit');
211+
$assert('studio wp datamachine-code workspace cleanup run --mode=artifacts --limit=100 --offset=0 --format=json', $plan['apply_command'] ?? '', 'bounded dry-run exposes matching high-level apply command');
212+
$assert($plan['apply_command'] ?? '', $plan['summary']['apply_command'] ?? '', 'bounded dry-run summary repeats apply command');
211213

212214
$bounded_skip_reasons = array_column($plan['skipped'] ?? array(), 'reason_code', 'handle');
213215
$assert('active_symlink_target', $bounded_skip_reasons['demo@active'] ?? '', 'active plugin symlink target is protected even in bounded mode');
@@ -230,6 +232,7 @@ function apply_filters( string $hook_name, $value ) // phpcs:ignore Generic.Cod
230232
$assert(1, count($exhaustive_plan['candidates'] ?? array()), 'exhaustive dry-run skips dirty/unpushed worktrees');
231233
$assert('demo@clean', $exhaustive_plan['candidates'][0]['handle'] ?? '', 'exhaustive clean worktree is candidate');
232234
$assert('target', $exhaustive_plan['candidates'][0]['artifacts'][0]['path'] ?? '', 'exhaustive candidate artifact path comes from profile');
235+
$assert('studio wp datamachine-code workspace cleanup run --mode=artifacts --exhaustive --format=json', $exhaustive_plan['apply_command'] ?? '', 'exhaustive dry-run exposes matching high-level apply command');
233236

234237
$skip_reasons = array_column($exhaustive_plan['skipped'] ?? array(), 'reason_code', 'handle');
235238
$assert('dirty_worktree', $skip_reasons['demo@dirty'] ?? '', 'exhaustive dirty worktree is protected');
@@ -252,6 +255,7 @@ function apply_filters( string $hook_name, $value ) // phpcs:ignore Generic.Cod
252255
$page_one = $workspace->worktree_cleanup_artifacts(array( 'dry_run' => true, 'limit' => 1, 'offset' => 0 ));
253256
$assert(false, is_wp_error($page_one), 'page-1 dry-run succeeds');
254257
$assert(1, (int) ( $page_one['pagination']['scanned'] ?? 0 ), 'page-1 scanned exactly one worktree');
258+
$assert('studio wp datamachine-code workspace cleanup run --mode=artifacts --limit=1 --offset=0 --format=json', $page_one['apply_command'] ?? '', 'page-1 dry-run apply command preserves page scope');
255259
$assert(true, (bool) ( $page_one['pagination']['partial'] ?? false ), 'page-1 reports partial=true');
256260
$assert(false, (bool) ( $page_one['pagination']['complete'] ?? true ), 'page-1 reports complete=false');
257261
$assert(1, (int) ( $page_one['pagination']['next_offset'] ?? 0 ), 'page-1 next_offset advances by limit');
@@ -263,6 +267,7 @@ function apply_filters( string $hook_name, $value ) // phpcs:ignore Generic.Cod
263267
$direct_apply = $workspace->worktree_cleanup_artifacts(array());
264268
$assert(true, is_wp_error($direct_apply), 'direct apply without plan is rejected');
265269
$assert('artifact_cleanup_plan_required', $direct_apply->code ?? '', 'direct apply error is explicit');
270+
$assert(true, str_contains($direct_apply->get_error_message(), 'workspace cleanup run --mode=artifacts --limit=100 --offset=0 --format=json'), 'direct apply error points to matching high-level apply command');
266271

267272
// Build a stricter plan from the exhaustive scan for precise apply-shape
268273
// assertions. This keeps the source-file-mismatch test deterministic

tests/smoke-worktree-cleanup-cli.php

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -292,10 +292,19 @@ class FakeArtifactCleanupAbility
292292
public function execute( array $input ): array
293293
{
294294
$this->last_input = $input;
295+
$apply_command = 'studio wp datamachine-code workspace cleanup run --mode=artifacts';
296+
if ( ! empty($input['exhaustive']) ) {
297+
$apply_command .= ' --exhaustive';
298+
} else {
299+
$apply_command .= ' --limit=' . (int) ( $input['limit'] ?? 100 );
300+
$apply_command .= ' --offset=' . (int) ( $input['offset'] ?? 0 );
301+
}
302+
$apply_command .= ' --format=json';
295303
return array(
296-
'success' => true,
297-
'dry_run' => ! empty($input['dry_run']),
298-
'candidates' => array(
304+
'success' => true,
305+
'dry_run' => ! empty($input['dry_run']),
306+
'apply_command' => $apply_command,
307+
'candidates' => array(
299308
array(
300309
'handle' => 'repo@old',
301310
'repo' => 'repo',
@@ -316,6 +325,7 @@ public function execute( array $input ): array
316325
),
317326
),
318327
'summary' => array(
328+
'apply_command' => $apply_command,
319329
'would_remove_artifacts' => 1,
320330
'removed_artifacts' => 0,
321331
'skipped' => 1,
@@ -1376,11 +1386,13 @@ public function execute( array $input ): array
13761386
datamachine_code_cleanup_assert(array( 'dry_run' => true, 'force' => false ) === $artifact_ability->last_input, 'cleanup-artifacts dry-run flags forwarded to ability');
13771387
$artifact_json = json_decode(WP_CLI::$logs[0] ?? '', true);
13781388
datamachine_code_cleanup_assert('target' === ( $artifact_json['candidates'][0]['artifacts'][0]['path'] ?? '' ), 'cleanup-artifacts JSON includes artifact paths');
1389+
datamachine_code_cleanup_assert('studio wp datamachine-code workspace cleanup run --mode=artifacts --limit=100 --offset=0 --format=json' === ( $artifact_json['apply_command'] ?? '' ), 'cleanup-artifacts JSON includes matching high-level apply command');
1390+
datamachine_code_cleanup_assert(( $artifact_json['apply_command'] ?? '' ) === ( $artifact_json['summary']['apply_command'] ?? null ), 'cleanup-artifacts summary repeats matching apply command');
13791391

13801392
WP_CLI::$logs = array();
13811393
WP_CLI::$successes = array();
13821394
$command->worktree(array( 'cleanup-artifacts' ), array( 'dry-run' => true ));
1383-
datamachine_code_cleanup_assert(str_contains(WP_CLI::$successes[0] ?? '', 'workspace cleanup run --mode=artifacts'), 'cleanup-artifacts dry-run points daily apply path to task-backed cleanup');
1395+
datamachine_code_cleanup_assert(str_contains(WP_CLI::$successes[0] ?? '', 'workspace cleanup run --mode=artifacts --limit=100 --offset=0 --format=json'), 'cleanup-artifacts dry-run points daily apply path to same task-backed page');
13841396
datamachine_code_cleanup_assert(str_contains(WP_CLI::$successes[0] ?? '', 'low-level escape hatch'), 'cleanup-artifacts dry-run demotes apply-plan wording');
13851397
datamachine_code_cleanup_assert(! str_contains(WP_CLI::$successes[0] ?? '', 'Save JSON'), 'cleanup-artifacts dry-run does not normalize saving plan files');
13861398

0 commit comments

Comments
 (0)