Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions inc/Abilities/WorkspaceAbilities.php
Original file line number Diff line number Diff line change
Expand Up @@ -1947,6 +1947,46 @@ private function registerAbilities(): void {
)
);

AbilityRegistry::register(
'datamachine/workspace-worktree-active-no-signal-merged-apply',
array(
'label' => 'Promote Merged Active Worktrees',
'description' => 'Promote clean active_no_signal rows with suggested_action=merged_to_default into explicit cleanup_eligible metadata. Reviewable and bounded; never deletes worktrees.',
'category' => 'datamachine-code-workspace',
'input_schema' => array(
'type' => 'object',
'properties' => array(
'dry_run' => array(
'type' => 'boolean',
'description' => 'If true, preview metadata promotions without writing.',
),
'limit' => array(
'type' => 'integer',
'description' => 'Maximum active_no_signal rows to inspect in this page. Defaults to 25.',
),
'offset' => array(
'type' => 'integer',
'description' => 'Pagination offset into the active_no_signal inventory ordering.',
),
),
),
'output_schema' => array(
'type' => 'object',
'properties' => array(
'success' => array( 'type' => 'boolean' ),
'dry_run' => array( 'type' => 'boolean' ),
'planned' => array( 'type' => 'array' ),
'written' => array( 'type' => 'array' ),
'skipped' => array( 'type' => 'array' ),
'summary' => array( 'type' => 'object' ),
),
),
'execute_callback' => array( self::class, 'worktreeActiveNoSignalMergedApply' ),
'permission_callback' => fn() => PermissionHelper::can_manage(),
'meta' => array( 'show_in_rest' => false ),
)
);

AbilityRegistry::register(
'datamachine/workspace-worktree-cleanup-artifacts',
array(
Expand Down Expand Up @@ -3259,6 +3299,27 @@ public static function worktreeActiveNoSignalEquivalentCleanApply( array $input
return $workspace->worktree_active_no_signal_equivalent_clean_apply($opts);
}

/**
* Promote merged-to-default active/no-signal evidence into cleanup metadata.
*
* @param array $input Input parameters (dry_run, limit, offset).
* @return array<string,mixed>|\WP_Error
*/
public static function worktreeActiveNoSignalMergedApply( array $input ): array|\WP_Error {
$workspace = new Workspace();
$opts = array(
'dry_run' => ! empty($input['dry_run']),
);
if ( array_key_exists('limit', $input) ) {
$opts['limit'] = (int) $input['limit'];
}
if ( array_key_exists('offset', $input) ) {
$opts['offset'] = (int) $input['offset'];
}

return $workspace->worktree_active_no_signal_merged_apply($opts);
}

/**
* Remove profile-derived artifacts inside workspace worktrees.
*
Expand Down
110 changes: 108 additions & 2 deletions inc/Cli/Commands/WorkspaceCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -2065,6 +2065,7 @@ private function renderGitOperationResult( string $operation, array $result, arr
* bounded-cleanup-eligible-apply, emergency-cleanup, reconcile-metadata,
* active-no-signal-report, active-no-signal-finalized-apply,
* active-no-signal-equivalent-clean-apply,
* active-no-signal-merged-apply,
* refresh-context, finalize, mark-cleanup-eligible.
*
* [<repo>]
Expand Down Expand Up @@ -2352,6 +2353,8 @@ private function renderGitOperationResult( string $operation, array $result, arr
* wp datamachine-code workspace worktree active-no-signal-finalized-apply --limit=25 --offset=0 --format=json
* wp datamachine-code workspace worktree active-no-signal-equivalent-clean-apply --dry-run --limit=25 --offset=0 --format=json
* wp datamachine-code workspace worktree active-no-signal-equivalent-clean-apply --limit=25 --offset=0 --format=json
* wp datamachine-code workspace worktree active-no-signal-merged-apply --dry-run --limit=25 --offset=0 --format=json
* wp datamachine-code workspace worktree active-no-signal-merged-apply --limit=25 --offset=0 --format=json
*
* # Ignore dirty working-tree safety (caution)
* wp datamachine-code workspace worktree cleanup --force
Expand Down Expand Up @@ -2384,7 +2387,7 @@ public function worktree( array $args, array $assoc_args ): void {
$operation = $args[0] ?? '';

if ( '' === $operation ) {
WP_CLI::error('Usage: wp datamachine-code workspace worktree <add|list|remove|prune|locks|cleanup|cleanup-artifacts|bounded-cleanup-eligible-apply|emergency-cleanup|reconcile-metadata|backfill-origin-session|active-no-signal-report|active-no-signal-finalized-apply|active-no-signal-equivalent-clean-apply|refresh-context|finalize|mark-cleanup-eligible> [<repo>] [<branch>] [--flags]');
WP_CLI::error('Usage: wp datamachine-code workspace worktree <add|list|remove|prune|locks|cleanup|cleanup-artifacts|bounded-cleanup-eligible-apply|emergency-cleanup|reconcile-metadata|backfill-origin-session|active-no-signal-report|active-no-signal-finalized-apply|active-no-signal-equivalent-clean-apply|active-no-signal-merged-apply|refresh-context|finalize|mark-cleanup-eligible> [<repo>] [<branch>] [--flags]');
return;
}

Expand Down Expand Up @@ -2446,6 +2449,7 @@ public function worktree( array $args, array $assoc_args ): void {
'active-no-signal-report' => 'datamachine-code/workspace-worktree-active-no-signal-report',
'active-no-signal-finalized-apply' => 'datamachine-code/workspace-worktree-active-no-signal-finalized-apply',
'active-no-signal-equivalent-clean-apply' => 'datamachine-code/workspace-worktree-active-no-signal-equivalent-clean-apply',
'active-no-signal-merged-apply' => 'datamachine-code/workspace-worktree-active-no-signal-merged-apply',
'refresh-context' => 'datamachine-code/workspace-worktree-refresh-context',
'finalize' => 'datamachine-code/workspace-worktree-finalize',
'mark-cleanup-eligible' => 'datamachine-code/workspace-worktree-finalize',
Expand Down Expand Up @@ -2621,7 +2625,8 @@ public function worktree( array $args, array $assoc_args ): void {
case 'active-no-signal-report':
case 'active-no-signal-finalized-apply':
case 'active-no-signal-equivalent-clean-apply':
if ( in_array($operation, array( 'active-no-signal-finalized-apply', 'active-no-signal-equivalent-clean-apply' ), true) ) {
case 'active-no-signal-merged-apply':
if ( in_array($operation, array( 'active-no-signal-finalized-apply', 'active-no-signal-equivalent-clean-apply', 'active-no-signal-merged-apply' ), true) ) {
$input['dry_run'] = ! empty($assoc_args['dry-run']);
}
if ( isset($assoc_args['limit']) ) {
Expand Down Expand Up @@ -2818,6 +2823,10 @@ function ( $wt ) {
$this->render_worktree_active_no_signal_equivalent_clean_apply_result($result, $assoc_args);
return;

case 'active-no-signal-merged-apply':
$this->render_worktree_active_no_signal_merged_apply_result($result, $assoc_args);
return;

case 'cleanup-artifacts':
$this->render_worktree_artifact_cleanup_result($result, $assoc_args);
return;
Expand Down Expand Up @@ -4022,6 +4031,103 @@ private function render_worktree_active_no_signal_equivalent_clean_apply_result(
WP_CLI::success(sprintf('Promoted %d equivalent-clean worktree(s) to cleanup_eligible metadata.', count($written)));
}

/**
* Render merged-to-default active/no-signal metadata apply output.
*
* @param array $result Apply result.
* @param array $assoc_args CLI assoc args.
* @return void
*/
private function render_worktree_active_no_signal_merged_apply_result( array $result, array $assoc_args ): void {
$format = isset($assoc_args['format']) ? (string) $assoc_args['format'] : 'table';
if ( 'json' === $format ) {
$json = wp_json_encode($result, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
WP_CLI::log(false === $json ? '{}' : $json);
return;
}

$summary = (array) ( $result['summary'] ?? array() );
$planned = (array) ( $result['planned'] ?? array() );
$written = (array) ( $result['written'] ?? array() );
$skipped = (array) ( $result['skipped'] ?? array() );
$dry_run = ! empty($result['dry_run']);

WP_CLI::log('Merged-to-default active/no-signal apply summary:');
$summary_rows = array(
array(
'metric' => 'inspected',
'count' => (int) ( $summary['inspected'] ?? 0 ),
),
array(
'metric' => 'planned',
'count' => (int) ( $summary['planned'] ?? count($planned) ),
),
array(
'metric' => 'written',
'count' => (int) ( $summary['written'] ?? count($written) ),
),
array(
'metric' => 'skipped',
'count' => (int) ( $summary['skipped'] ?? count($skipped) ),
),
);
foreach ( (array) ( $summary['skipped_by_reason'] ?? array() ) as $reason => $count ) {
$summary_rows[] = array(
'metric' => 'skipped:' . $reason,
'count' => (int) $count,
);
}
$this->format_items($summary_rows, array( 'metric', 'count' ), array( 'format' => 'table' ), 'metric');

$rows = $dry_run ? $planned : $written;
if ( ! empty($rows) ) {
WP_CLI::log('');
WP_CLI::log($dry_run ? 'Would promote:' : 'Promoted:');
$items = array_map(
fn( $row ) => array(
'handle' => $row['handle'] ?? '',
'branch' => $row['branch'] ?? '',
'default_ref' => $row['metadata']['cleanup_eligibility_evidence']['default_ref'] ?? '',
'state' => $row['metadata']['lifecycle_state'] ?? '',
),
$rows
);
$this->format_items($items, array( 'handle', 'branch', 'default_ref', 'state' ), array( 'format' => 'table' ), 'handle');
}

if ( ! empty($skipped) ) {
WP_CLI::log('');
WP_CLI::log('Skipped:');
$items = array_map(
fn( $row ) => array(
'handle' => $row['handle'] ?? '',
'action' => $row['action'] ?? '',
'reason_code' => $row['reason_code'] ?? '',
'reason' => $row['reason'] ?? '',
),
array_slice($skipped, 0, 10)
);
$this->format_items($items, array( 'handle', 'action', 'reason_code', 'reason' ), array( 'format' => 'table' ), 'handle');
}

if ( isset($result['pagination']['next_offset']) ) {
WP_CLI::log('');
WP_CLI::log(
sprintf(
'Next page: wp datamachine-code workspace worktree active-no-signal-merged-apply --limit=%d --offset=%d --format=json',
(int) ( $result['pagination']['limit'] ?? 0 ),
(int) $result['pagination']['next_offset']
)
);
}

if ( $dry_run ) {
WP_CLI::success(sprintf('%d merged-to-default worktree(s) would be promoted to cleanup_eligible metadata.', count($planned)));
return;
}
WP_CLI::success(sprintf('Promoted %d merged-to-default worktree(s) to cleanup_eligible metadata.', count($written)));
}

/**
* Render artifact-only cleanup output.
*
Expand Down
Loading
Loading