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
3 changes: 1 addition & 2 deletions data-machine-code.php
Original file line number Diff line number Diff line change
Expand Up @@ -407,8 +407,7 @@ function datamachine_code_load_chat_tools() {
'task_params' => array(
'source' => 'recurring_schedule',
'include_cleanup' => true,
'include_sizes' => true,
'size_limit' => 200,
'include_sizes' => false,
),
);
return $schedules;
Expand Down
8 changes: 4 additions & 4 deletions inc/Abilities/WorkspaceAbilities.php
Original file line number Diff line number Diff line change
Expand Up @@ -1523,7 +1523,7 @@ private function registerAbilities(): void {
),
'include_sizes' => array(
'type' => 'boolean',
'description' => 'Include best-effort top-level workspace size data. Default true.',
'description' => 'Include best-effort top-level workspace size data. Default false for huge-workspace safety.',
),
'include_worktree_status' => array(
'type' => 'boolean',
Expand All @@ -1535,7 +1535,7 @@ private function registerAbilities(): void {
),
'size_limit' => array(
'type' => 'integer',
'description' => 'Maximum top-level workspace entries to size. Default 1000.',
'description' => 'Maximum top-level workspace entries to size when include_sizes is true. Default 1000.',
),
),
),
Expand All @@ -1556,6 +1556,7 @@ private function registerAbilities(): void {
'locks' => array( 'type' => 'object' ),
'cleanup' => array( 'type' => 'object' ),
'suggested_cleanup_command' => array( 'type' => 'string' ),
'suggested_size_command' => array( 'type' => 'string' ),
'notes' => array( 'type' => 'array' ),
),
),
Expand Down Expand Up @@ -3838,9 +3839,8 @@ public static function workspaceCleanupRun( array $input ): array|\WP_Error {
'task_type' => 'workspace_hygiene_report',
'params' => array(
'include_cleanup' => true,
'include_sizes' => true,
'include_sizes' => false,
'include_worktree_status' => false,
'size_limit' => 200,
),
),
'artifacts' => array(
Expand Down
17 changes: 11 additions & 6 deletions inc/Cli/Commands/WorkspaceCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -1157,9 +1157,8 @@ private function run_cleanup_review( array $assoc_args ): void {
$result = $ability ? $ability->execute(
array(
'include_cleanup' => true,
'include_sizes' => true,
'include_sizes' => false,
'include_worktree_status' => false,
'size_limit' => 200,
)
) : new \WP_Error('workspace_hygiene_ability_missing', 'Workspace hygiene ability not registered.');
$this->render_workspace_hygiene_report_from_ability($result, $assoc_args);
Expand Down Expand Up @@ -2062,8 +2061,8 @@ public function remove_repo( array $args, array $assoc_args ): void {
* [--skip-cleanup]
* : Skip the local cleanup dry-run summary.
*
* [--skip-sizes]
* : Skip best-effort workspace size collection.
* [--include-sizes]
* : Include best-effort workspace size collection. This can be expensive on huge workspaces; combine with --size-limit.
*
* [--include-worktree-status]
* : Include full per-worktree git status. This can be expensive on huge workspaces.
Expand Down Expand Up @@ -2093,7 +2092,7 @@ public function hygiene( array $args, array $assoc_args ): void { // phpcs:ign

$input = array(
'include_cleanup' => empty($assoc_args['skip-cleanup']),
'include_sizes' => empty($assoc_args['skip-sizes']),
'include_sizes' => ! empty($assoc_args['include-sizes']),
'include_worktree_status' => ! empty($assoc_args['include-worktree-status']),
'refresh_inventory' => ! empty($assoc_args['refresh-inventory']),
);
Expand Down Expand Up @@ -3635,7 +3634,7 @@ public function worktree( array $args, array $assoc_args ): void {

$input = array();
$input_builder = (string) ( $operation_config['input_builder'] ?? '' );
if ( '' !== $input_builder && method_exists($this, $input_builder) ) {
if ( '' !== $input_builder ) {
$input = $this->{$input_builder}($operation, $assoc_args);
}

Expand Down Expand Up @@ -4826,6 +4825,12 @@ private function render_workspace_hygiene_report( array $report, array $assoc_ar
WP_CLI::log( (string) $report['suggested_cleanup_command']);
}

if ( ! empty($report['suggested_size_command']) ) {
WP_CLI::log('');
WP_CLI::log('Suggested bounded size review:');
WP_CLI::log( (string) $report['suggested_size_command']);
}

foreach ( (array) ( $report['notes'] ?? array() ) as $note ) {
WP_CLI::log('Note: ' . $note);
}
Expand Down
4 changes: 2 additions & 2 deletions inc/Tasks/WorkspaceHygieneReportTask.php
Original file line number Diff line number Diff line change
Expand Up @@ -91,9 +91,9 @@ public function executeTask( int $jobId, array $params ): void {
$result = $workspace->workspace_hygiene_report(
array(
'include_cleanup' => array_key_exists('include_cleanup', $params) ? (bool) $params['include_cleanup'] : true,
'include_sizes' => array_key_exists('include_sizes', $params) ? (bool) $params['include_sizes'] : true,
'include_sizes' => array_key_exists('include_sizes', $params) ? (bool) $params['include_sizes'] : false,
'include_worktree_status' => array_key_exists('include_worktree_status', $params) ? (bool) $params['include_worktree_status'] : false,
'size_limit' => isset($params['size_limit']) ? (int) $params['size_limit'] : 200,
'size_limit' => isset($params['size_limit']) ? (int) $params['size_limit'] : Workspace::HYGIENE_DEFAULT_SIZE_LIMIT,
)
);

Expand Down
10 changes: 6 additions & 4 deletions inc/Workspace/WorkspaceHygieneReport.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,20 @@ trait WorkspaceHygieneReport {
*
* The report intentionally defaults to local-only cleanup detection so an
* on-demand or scheduled run never depends on GitHub API availability. Size
* collection is best-effort and bounded by a top-level entry limit.
* collection is best-effort, bounded by a top-level entry limit, and opt-in
* so the default path stays cheap on very large workspaces.
*
* @param array $opts {
* @type bool $include_cleanup Whether to include a cleanup dry-run. Default true.
* @type bool $include_sizes Whether to include best-effort `du` sizes. Default true.
* @type bool $include_sizes Whether to include best-effort `du` sizes. Default false.
* @type bool $include_worktree_status Whether to include full git worktree status. Default false.
* @type int $size_limit Maximum top-level workspace entries to size. Default 1000.
* }
* @return array<string,mixed>|\WP_Error
*/
public function workspace_hygiene_report( array $opts = array() ): array|\WP_Error {
$include_cleanup = array_key_exists('include_cleanup', $opts) ? (bool) $opts['include_cleanup'] : true;
$include_sizes = array_key_exists('include_sizes', $opts) ? (bool) $opts['include_sizes'] : true;
$include_sizes = array_key_exists('include_sizes', $opts) ? (bool) $opts['include_sizes'] : false;
$include_worktree_status = array_key_exists('include_worktree_status', $opts) ? (bool) $opts['include_worktree_status'] : false;
$refresh_inventory = ! empty($opts['refresh_inventory']);
$size_limit = isset($opts['size_limit']) ? max(0, (int) $opts['size_limit']) : self::HYGIENE_DEFAULT_SIZE_LIMIT;
Expand Down Expand Up @@ -100,10 +101,11 @@ public function workspace_hygiene_report( array $opts = array() ): array|\WP_Err
'locks' => $locks,
'cleanup' => $this->summarize_workspace_cleanup($cleanup, $cleanup_error, (array) ( $size_report['entries'] ?? array() )),
'suggested_cleanup_command' => 'wp datamachine-code workspace worktree cleanup --dry-run --inventory-only --skip-github --format=json',
'suggested_size_command' => 'wp datamachine-code workspace hygiene --include-sizes --size-limit=100 --format=json',
'notes' => array_values(
array_filter(
array(
$include_sizes ? (string) ( $size_report['mode_note'] ?? '' ) : 'Size scan disabled by request.',
$include_sizes ? (string) ( $size_report['mode_note'] ?? '' ) : 'Size scan skipped by default for large-workspace safety; pass --include-sizes with --size-limit for a bounded size pass.',
$include_worktree_status ? 'Full worktree status enabled; this may run git status across every worktree.' : 'Worktree status uses cheap top-level inventory; pass --include-worktree-status for full git status.',
$include_cleanup ? 'Cleanup summary uses inventory-only dry-run detection (--inventory-only --skip-github); no per-worktree git probes or GitHub API lookups are required.' : 'Cleanup dry-run disabled by request.',
! empty($worktree_summary['stale_primaries']) ? 'One or more primary checkouts are behind their configured upstream according to local remote refs; refresh before using a primary for verification.' : '',
Expand Down
2 changes: 2 additions & 0 deletions phpstan.neon
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
parameters:
scanFiles:
- stubs/phpstan/datamachine-base-command.php
- stubs/phpstan/datamachine-plugin-settings.php
- stubs/phpstan/datamachine-system-task.php
24 changes: 24 additions & 0 deletions stubs/phpstan/datamachine-plugin-settings.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php
/**
* PHPStan stub for Data Machine plugin settings.
*
* @package DataMachineCode\Stubs
*/

namespace DataMachine\Core;

defined('ABSPATH') || exit;

/**
* Static-analysis shape for the external Data Machine PluginSettings class.
*/
class PluginSettings {

/**
* @param mixed $fallback Fallback value returned when the setting is unset.
* @return mixed
*/
public static function get( string $key, mixed $fallback = null ): mixed {
return $fallback;
}
}
25 changes: 25 additions & 0 deletions stubs/phpstan/datamachine-system-task.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php
/**
* PHPStan stub for Data Machine system tasks.
*
* @package DataMachineCode\Stubs
*/

namespace DataMachine\Engine\AI\System\Tasks;

defined('ABSPATH') || exit;

/**
* Static-analysis shape for the external Data Machine SystemTask class.
*/
abstract class SystemTask {

/**
* @param array<string,mixed> $result Job result payload.
*/
protected function completeJob( int $jobId, array $result = array() ): void {
}

protected function failJob( int $jobId, string $message ): void {
}
}
42 changes: 42 additions & 0 deletions tests/workspace-hygiene-timeout-safe-contract.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php

declare(strict_types=1);

$root = dirname(__DIR__);

function workspace_hygiene_contract_assert_contains( string $needle, string $haystack, string $message ): void {
if ( ! str_contains($haystack, $needle) ) {
throw new RuntimeException($message . ' Missing: ' . $needle);
}
}

function workspace_hygiene_contract_assert_not_contains( string $needle, string $haystack, string $message ): void {
if ( str_contains($haystack, $needle) ) {
throw new RuntimeException($message . ' Unexpected: ' . $needle);
}
}

$hygiene = file_get_contents($root . '/inc/Workspace/WorkspaceHygieneReport.php');
$cli = file_get_contents($root . '/inc/Cli/Commands/WorkspaceCommand.php');
$task = file_get_contents($root . '/inc/Tasks/WorkspaceHygieneReportTask.php');
$plugin = file_get_contents($root . '/data-machine-code.php');
$ability = file_get_contents($root . '/inc/Abilities/WorkspaceAbilities.php');

if ( false === $hygiene || false === $cli || false === $task || false === $plugin || false === $ability ) {
throw new RuntimeException('Unable to read workspace hygiene source files.');
}

workspace_hygiene_contract_assert_contains("array_key_exists('include_sizes', \$opts) ? (bool) \$opts['include_sizes'] : false", $hygiene, 'Workspace hygiene must skip du sizing by default.');
workspace_hygiene_contract_assert_contains('Size scan skipped by default for large-workspace safety', $hygiene, 'Default report must explain why size data is partial.');
workspace_hygiene_contract_assert_contains('suggested_size_command', $hygiene, 'Default report must expose a continuation command for bounded sizing.');
workspace_hygiene_contract_assert_contains('--include-sizes --size-limit=100 --format=json', $hygiene, 'Continuation command must keep sizing bounded.');

workspace_hygiene_contract_assert_contains("'include_sizes' => ! empty(\$assoc_args['include-sizes'])", $cli, 'CLI hygiene must require explicit size opt-in.');
workspace_hygiene_contract_assert_contains("'include_sizes' => false", $cli, 'Cleanup-run inventory mode must not force size scans.');
workspace_hygiene_contract_assert_not_contains("'include_sizes' => true", $cli, 'CLI wrappers must not opt into size scans by default.');

workspace_hygiene_contract_assert_contains("array_key_exists('include_sizes', \$params) ? (bool) \$params['include_sizes'] : false", $task, 'Scheduled hygiene task must skip sizing unless requested.');
workspace_hygiene_contract_assert_contains("'include_sizes' => false", $plugin, 'Registered weekly hygiene schedule must remain cheap by default.');
workspace_hygiene_contract_assert_contains('Default false for huge-workspace safety', $ability, 'Ability schema must document cheap size default.');

echo "workspace-hygiene-timeout-safe-contract: ok\n";
Loading