Skip to content

Commit accd1fc

Browse files
authored
Merge pull request #684 from Extra-Chill/fix/issue-674-high-volume-cleanup
Fix high-volume workspace cleanup planning
2 parents c867516 + d08461d commit accd1fc

7 files changed

Lines changed: 384 additions & 66 deletions

inc/Cli/Commands/WorkspaceCommand.php

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -594,10 +594,11 @@ public function adopt_repo( array $args, array $assoc_args ): void {
594594
* : Pass force=true into the cleanup task params for modes that support it.
595595
*
596596
* [--include-artifacts]
597-
* : For `plan --mode=retention`, also include the exhaustive artifact cleanup
598-
* scan. Omitted by default so safe retention planning stays bounded on large
599-
* workspaces; use `--mode=artifacts` for an artifact-only plan.
600-
* `--mode=stale-worktrees` never includes artifacts unless this flag is passed.
597+
* : For `plan --mode=retention`, include artifact cleanup rows. Retention
598+
* planning includes a full-workspace artifact inventory by default; this flag
599+
* remains accepted for explicitness and `--mode=artifacts` still creates an
600+
* artifact-only plan. `--mode=stale-worktrees` never includes artifacts unless
601+
* this flag is passed.
601602
*
602603
* [--older-than=<duration>]
603604
* : Pass an age gate such as 7d or 24h into cleanup task params.
@@ -945,7 +946,7 @@ private function run_cleanup_plan( array $assoc_args ): void {
945946

946947
$input = $this->cleanup_plan_input($mode, $assoc_args);
947948
if ( 'json' !== (string) ( $assoc_args['format'] ?? 'table' ) ) {
948-
$profile = ! empty($input['include_artifacts']) ? 'includes artifact scan' : 'local worktree merge signals';
949+
$profile = ! empty($input['include_artifacts']) ? 'full-workspace inventory, biggest wins first' : 'local worktree merge signals';
949950
WP_CLI::log(sprintf('Planning cleanup (%s; %s)...', $mode, $profile));
950951
}
951952

@@ -966,7 +967,7 @@ private function run_cleanup_plan( array $assoc_args ): void {
966967
* @return array<string,mixed>
967968
*/
968969
private function cleanup_plan_input( string $mode, array $assoc_args ): array {
969-
$include_artifacts = 'artifacts' === $mode || ! empty($assoc_args['include-artifacts']);
970+
$include_artifacts = 'retention' === $mode || 'artifacts' === $mode || ! empty($assoc_args['include-artifacts']);
970971
$include_worktrees = 'artifacts' !== $mode;
971972
$input = array(
972973
'mode' => $mode,
@@ -983,6 +984,11 @@ private function cleanup_plan_input( string $mode, array $assoc_args ): array {
983984
if ( isset($assoc_args['force']) ) {
984985
$input['force_artifact_cleanup'] = (bool) $assoc_args['force'];
985986
}
987+
if ( isset($assoc_args['sort']) && '' !== trim( (string) $assoc_args['sort']) ) {
988+
$sort = trim( (string) $assoc_args['sort']);
989+
$input['artifact_sort'] = $sort;
990+
$input['worktree_sort'] = $sort;
991+
}
986992
if ( 'stale-worktrees' === $mode ) {
987993
$input['worktree_stale_only'] = true;
988994
if ( empty($input['worktree_older_than']) ) {
@@ -1433,8 +1439,18 @@ private function render_cleanup_plan_result( array $result, array $assoc_args ):
14331439
WP_CLI::log(sprintf('Run ID: %s', (string) ( $result['run_id'] ?? '' )));
14341440
WP_CLI::log(sprintf('Plan ID: %s', (string) ( $result['plan_id'] ?? '' )));
14351441
WP_CLI::log(sprintf('Rows: %d', (int) ( $summary['total_rows'] ?? 0 )));
1436-
WP_CLI::log(sprintf('Bytes: %s', $this->format_bytes($summary['total_size_bytes'] ?? 0)));
1442+
WP_CLI::log(sprintf('Reclaimable: %s', $this->format_bytes($summary['total_reclaimable_bytes'] ?? $summary['total_size_bytes'] ?? 0)));
1443+
$byte_totals = (array) ( $summary['byte_totals'] ?? array() );
1444+
if ( array() !== $byte_totals ) {
1445+
foreach ( $byte_totals as $type => $bytes ) {
1446+
WP_CLI::log(sprintf(' %s: %s', (string) $type, $this->format_bytes($bytes)));
1447+
}
1448+
}
14371449
WP_CLI::log(sprintf('Apply: wp datamachine-code workspace cleanup apply %s', (string) ( $result['run_id'] ?? '' )));
1450+
$blocked = (array) ( $summary['blocked_by_type'] ?? array() );
1451+
if ( array_sum(array_map('intval', $blocked)) > 0 ) {
1452+
WP_CLI::log('Blocked/kept rows are included in JSON under `blocked` with reason_code/reason; they are not applyable cleanup rows.');
1453+
}
14381454
$this->render_cleanup_plan_category_totals( (array) ( $summary['category_totals'] ?? array() ) );
14391455
$this->render_cleanup_plan_top_reclaimable( (array) ( $summary['top_reclaimable'] ?? array() ) );
14401456
$this->render_cleanup_plan_blockers( (array) ( $summary['blockers'] ?? array() ) );

inc/Runtime/AgentsMdSections.php

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,14 @@ public static function register(): void {
2222
}
2323

2424
$registry_class = '\DataMachine\Engine\AI\MemoryFileRegistry';
25-
if ( ! method_exists($registry_class, 'register') ) {
25+
if ( ! is_callable(array( $registry_class, 'register' )) ) {
2626
return;
2727
}
28+
$register = array( $registry_class, 'register' );
29+
/** @var callable $register */
2830

29-
$registry_class::register(
31+
call_user_func(
32+
$register,
3033
'AGENTS.md', 5, array(
3134
'layer' => defined($registry_class . '::LAYER_SHARED') ? constant($registry_class . '::LAYER_SHARED') : 'shared',
3235
'protected' => true,
@@ -60,7 +63,7 @@ public static function register_invalidation_hooks( array $hooks ): array {
6063
}
6164

6265
private static function register_auto_generated_marker( string $wp ): void {
63-
\DataMachine\Engine\AI\SectionRegistry::register(
66+
self::register_section(
6467
'AGENTS.md', 'auto-generated-marker', 0, function () use ( $wp ) {
6568
$generated_at = gmdate('c');
6669
return <<<MD
@@ -79,7 +82,7 @@ private static function register_auto_generated_marker( string $wp ): void {
7982
}
8083

8184
private static function register_datamachine_section( string $wp ): void {
82-
\DataMachine\Engine\AI\SectionRegistry::register(
85+
self::register_section(
8386
'AGENTS.md', 'datamachine', 10, function () use ( $wp ) {
8487
$workspace_path = self::resolve_workspace_path();
8588
$agent_slug = self::resolve_agent_slug();
@@ -167,7 +170,7 @@ private static function register_datamachine_section( string $wp ): void {
167170
}
168171

169172
private static function register_workspace_inventory_section( string $wp ): void {
170-
\DataMachine\Engine\AI\SectionRegistry::register(
173+
self::register_section(
171174
'AGENTS.md', 'workspace-inventory', 15, function () use ( $wp ) {
172175
return self::render_workspace_inventory_section($wp);
173176
}, array(
@@ -181,7 +184,7 @@ private static function register_workspace_inventory_section( string $wp ): void
181184
}
182185

183186
private static function register_abilities_section(): void {
184-
\DataMachine\Engine\AI\SectionRegistry::register(
187+
self::register_section(
185188
'AGENTS.md', 'abilities', 20, function () {
186189
return <<<'MD'
187190
## Abilities
@@ -201,7 +204,7 @@ private static function register_abilities_section(): void {
201204
}
202205

203206
private static function register_wordpress_source_section(): void {
204-
\DataMachine\Engine\AI\SectionRegistry::register(
207+
self::register_section(
205208
'AGENTS.md', 'wordpress-source', 30, function () {
206209
return <<<'MD'
207210
## WordPress Source (Read-Only Reference)
@@ -227,7 +230,7 @@ private static function register_multisite_section( string $wp ): void {
227230
return;
228231
}
229232

230-
\DataMachine\Engine\AI\SectionRegistry::register(
233+
self::register_section(
231234
'AGENTS.md', 'multisite', 40, function () use ( $wp ) {
232235
return <<<MD
233236
## Multisite
@@ -266,6 +269,20 @@ private static function resolve_wp_cli_cmd(): string {
266269
return apply_filters('datamachine_wp_cli_cmd', implode(' ', $parts));
267270
}
268271

272+
/**
273+
* Register an AGENTS.md section through the optional Data Machine registry.
274+
*/
275+
private static function register_section( string $file, string $section, int $priority, callable $callback, array $metadata ): void {
276+
$registry_class = '\DataMachine\Engine\AI\SectionRegistry';
277+
if ( ! is_callable(array( $registry_class, 'register' )) ) {
278+
return;
279+
}
280+
$register = array( $registry_class, 'register' );
281+
/** @var callable $register */
282+
283+
call_user_func($register, $file, $section, $priority, $callback, $metadata);
284+
}
285+
269286
private static function resolve_workspace_path(): string {
270287
if ( class_exists(Workspace::class) ) {
271288
$workspace_path = ( new Workspace() )->get_path();
@@ -281,8 +298,8 @@ private static function resolve_agent_slug(): string {
281298
if ( class_exists('\DataMachine\Core\FilesRepository\DirectoryManager') ) {
282299
try {
283300
$directory_manager = new \DataMachine\Core\FilesRepository\DirectoryManager();
284-
$user_id = method_exists($directory_manager, 'get_effective_user_id') ? (int) $directory_manager->get_effective_user_id(0) : 0;
285-
$agent_slug = method_exists($directory_manager, 'resolve_agent_slug') ? (string) $directory_manager->resolve_agent_slug(array( 'user_id' => $user_id )) : '';
301+
$user_id = (int) $directory_manager->get_effective_user_id(0);
302+
$agent_slug = (string) $directory_manager->resolve_agent_slug(array( 'user_id' => $user_id ));
286303

287304
if ( '' !== trim($agent_slug) ) {
288305
return trim($agent_slug);

inc/Workspace/WorkspaceArtifactCleanup.php

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -35,22 +35,23 @@ trait WorkspaceArtifactCleanup {
3535
* @return array<string,mixed>|\WP_Error
3636
*/
3737
public function worktree_cleanup_artifacts( array $opts = array() ): array|\WP_Error {
38-
$dry_run = ! empty($opts['dry_run']);
39-
$force = ! empty($opts['force']);
40-
$apply_plan = isset($opts['apply_plan']) && is_array($opts['apply_plan']) ? $opts['apply_plan'] : null;
41-
$exhaustive = ! empty($opts['exhaustive']);
42-
$sort = isset($opts['sort']) ? strtolower(trim( (string) $opts['sort'])) : '';
43-
$limit = isset($opts['limit']) ? (int) $opts['limit'] : self::ARTIFACT_CLEANUP_DEFAULT_LIMIT;
44-
$offset = isset($opts['offset']) ? max(0, (int) $opts['offset']) : 0;
38+
$dry_run = ! empty($opts['dry_run']);
39+
$force = ! empty($opts['force']);
40+
$apply_plan = isset($opts['apply_plan']) && is_array($opts['apply_plan']) ? $opts['apply_plan'] : null;
41+
$exhaustive = ! empty($opts['exhaustive']);
42+
$full_workspace = ! empty($opts['full_workspace']);
43+
$sort = isset($opts['sort']) ? strtolower(trim( (string) $opts['sort'])) : '';
44+
$limit = isset($opts['limit']) ? (int) $opts['limit'] : self::ARTIFACT_CLEANUP_DEFAULT_LIMIT;
45+
$offset = isset($opts['offset']) ? max(0, (int) $opts['offset']) : 0;
4546
if ( $limit < 0 ) {
4647
return new \WP_Error('invalid_artifact_cleanup_limit', 'Artifact cleanup --limit must be greater than 0. Use --exhaustive for an unbounded full artifact audit.', array( 'status' => 400 ));
4748
}
48-
if ( ! $exhaustive && $limit <= 0 ) {
49-
return new \WP_Error('invalid_artifact_cleanup_limit', 'Artifact cleanup --limit must be greater than 0. Use --exhaustive for an unbounded full artifact audit.', array( 'status' => 400 ));
49+
if ( ! $exhaustive && ! $full_workspace && $limit <= 0 ) {
50+
return new \WP_Error('invalid_artifact_cleanup_limit', 'Artifact cleanup --limit must be greater than 0. Use --exhaustive for an unbounded full artifact audit, or the high-level workspace cleanup plan for full-workspace inventory planning.', array( 'status' => 400 ));
5051
}
5152
// Allow callers to opt out of bounded mode entirely only through the
5253
// explicit exhaustive path, which also enables safety probes.
53-
if ( $exhaustive ) {
54+
if ( $exhaustive || $full_workspace ) {
5455
$limit = 0;
5556
}
5657
$apply_command = $this->build_artifact_cleanup_apply_command();
@@ -115,8 +116,10 @@ public function worktree_cleanup_artifacts( array $opts = array() ): array|\WP_E
115116
if ( $rank_by_size ) {
116117
usort($candidates, fn( $a, $b ) => (int) ( $b['artifact_size_bytes'] ?? 0 ) <=> (int) ( $a['artifact_size_bytes'] ?? 0 ));
117118
$total_ranked = count($candidates);
118-
$candidates = array_slice($candidates, 0, $limit);
119-
$pagination = array(
119+
if ( $limit > 0 ) {
120+
$candidates = array_slice($candidates, 0, $limit);
121+
}
122+
$pagination = array(
120123
'mode' => 'ranked_inventory',
121124
'limit' => $limit,
122125
'offset' => 0,

0 commit comments

Comments
 (0)