Skip to content

Commit 6719048

Browse files
authored
Merge branch 'main' into safe-cleanup-drain
2 parents bf49aef + 599962d commit 6719048

20 files changed

Lines changed: 657 additions & 96 deletions

data-machine-code.php

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* Plugin Name: Data Machine Code
44
* Plugin URI: https://github.com/Extra-Chill/data-machine-code
55
* Description: Bridge between WordPress and an external coding-agent runtime. Owns AGENTS.md, the workspace area, and the GitHub / workspace / git abilities the runtime calls back into. Activation is the declarative "a coding agent lives here" signal.
6-
* Version: 0.48.10
6+
* Version: 0.48.11
77
* Requires at least: 6.9
88
* Requires PHP: 8.2
99
* Author: Chris Huber, extrachill
@@ -17,7 +17,7 @@
1717
die;
1818
}
1919

20-
define( 'DATAMACHINE_CODE_VERSION', '0.48.10' );
20+
define( 'DATAMACHINE_CODE_VERSION', '0.48.11' );
2121
define( 'DATAMACHINE_CODE_PATH', plugin_dir_path( __FILE__ ) );
2222
define( 'DATAMACHINE_CODE_URL', plugin_dir_url( __FILE__ ) );
2323

@@ -407,8 +407,7 @@ function datamachine_code_load_chat_tools() {
407407
'task_params' => array(
408408
'source' => 'recurring_schedule',
409409
'include_cleanup' => true,
410-
'include_sizes' => true,
411-
'size_limit' => 200,
410+
'include_sizes' => false,
412411
),
413412
);
414413
return $schedules;

docs/CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,16 @@
22

33
All notable changes to Data Machine Code will be documented in this file.
44

5+
## [0.48.11] - 2026-06-17
6+
7+
### Changed
8+
- Add active no-signal triage preview
9+
- Make workspace hygiene size scans opt-in
10+
- Improve cleanup summary output
11+
12+
### Fixed
13+
- Fix disk-budget refusal cleanup guidance
14+
515
## [0.48.10] - 2026-06-17
616

717
### Fixed

inc/Abilities/WorkspaceAbilities.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1524,7 +1524,7 @@ private function registerAbilities(): void {
15241524
),
15251525
'include_sizes' => array(
15261526
'type' => 'boolean',
1527-
'description' => 'Include best-effort top-level workspace size data. Default true.',
1527+
'description' => 'Include best-effort top-level workspace size data. Default false for huge-workspace safety.',
15281528
),
15291529
'include_worktree_status' => array(
15301530
'type' => 'boolean',
@@ -1536,7 +1536,7 @@ private function registerAbilities(): void {
15361536
),
15371537
'size_limit' => array(
15381538
'type' => 'integer',
1539-
'description' => 'Maximum top-level workspace entries to size. Default 1000.',
1539+
'description' => 'Maximum top-level workspace entries to size when include_sizes is true. Default 1000.',
15401540
),
15411541
),
15421542
),
@@ -1557,6 +1557,7 @@ private function registerAbilities(): void {
15571557
'locks' => array( 'type' => 'object' ),
15581558
'cleanup' => array( 'type' => 'object' ),
15591559
'suggested_cleanup_command' => array( 'type' => 'string' ),
1560+
'suggested_size_command' => array( 'type' => 'string' ),
15601561
'notes' => array( 'type' => 'array' ),
15611562
),
15621563
),
@@ -3868,9 +3869,8 @@ public static function workspaceCleanupRun( array $input ): array|\WP_Error {
38683869
'task_type' => 'workspace_hygiene_report',
38693870
'params' => array(
38703871
'include_cleanup' => true,
3871-
'include_sizes' => true,
3872+
'include_sizes' => false,
38723873
'include_worktree_status' => false,
3873-
'size_limit' => 200,
38743874
),
38753875
),
38763876
'artifacts' => array(

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: 117 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1158,9 +1158,8 @@ private function run_cleanup_review( array $assoc_args ): void {
11581158
$result = $ability ? $ability->execute(
11591159
array(
11601160
'include_cleanup' => true,
1161-
'include_sizes' => true,
1161+
'include_sizes' => false,
11621162
'include_worktree_status' => false,
1163-
'size_limit' => 200,
11641163
)
11651164
) : new \WP_Error('workspace_hygiene_ability_missing', 'Workspace hygiene ability not registered.');
11661165
$this->render_workspace_hygiene_report_from_ability($result, $assoc_args);
@@ -1378,6 +1377,7 @@ private function render_cleanup_operator_summary( array $summary ): void {
13781377
WP_CLI::log('Cleanup operator summary:');
13791378
$cleanup_counts = (array) ( $summary['cleanup_counts'] ?? array() );
13801379
$artifacts = (array) ( $summary['artifact_cleanup'] ?? array() );
1380+
$remaining_safe = (int) ( $summary['remaining_safe_candidates'] ?? $summary['remaining_safely_removable_worktrees'] ?? 0 );
13811381
$this->format_items(
13821382
array(
13831383
array(
@@ -1400,6 +1400,14 @@ private function render_cleanup_operator_summary( array $summary ): void {
14001400
'metric' => 'bytes_reclaimed',
14011401
'value' => $this->format_bytes($cleanup_counts['bytes_reclaimed'] ?? 0),
14021402
),
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+
),
14031411
array(
14041412
'metric' => 'remaining_reclaimable_artifacts',
14051413
'value' => $this->format_bytes($artifacts['remaining_reclaimable_artifact_bytes'] ?? 0),
@@ -1460,24 +1468,28 @@ private function build_cleanup_operator_summary( array $result ): array {
14601468

14611469
return array_filter(
14621470
array(
1463-
'success' => (bool) ( $result['success'] ?? false ),
1464-
'run_id' => (string) ( $result['run_id'] ?? '' ),
1465-
'job_id' => isset($result['job_id']) ? (int) $result['job_id'] : null,
1466-
'mode' => (string) ( $result['mode'] ?? $result['evidence']['engine_data']['cleanup_run']['mode'] ?? '' ),
1467-
'state' => (string) ( $result['state'] ?? '' ),
1468-
'status' => (string) ( $result['status'] ?? '' ),
1469-
'parent_status' => (string) ( $result['parent_status'] ?? '' ),
1470-
'created_at' => (string) ( $result['created_at'] ?? '' ),
1471-
'completed_at' => (string) ( $result['completed_at'] ?? $result['parent_completed_at'] ?? '' ),
1472-
'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(
14731481
'planned' => (int) ( $cleanup_items['planned_rows'] ?? 0 ),
14741482
'applied' => (int) ( $cleanup_items['applied_rows'] ?? 0 ),
14751483
'skipped' => (int) ( $cleanup_items['skipped_rows'] ?? 0 ),
14761484
'failed' => (int) ( $cleanup_items['failed_rows'] ?? 0 ),
14771485
'bytes_reclaimed' => (int) ( $cleanup_items['bytes_reclaimed'] ?? 0 ),
14781486
'freed_human' => (string) ( $cleanup_items['freed_human'] ?? $this->format_bytes($cleanup_items['bytes_reclaimed'] ?? 0) ),
14791487
),
1480-
'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(
14811493
'planned' => (int) ( $artifacts['planned_rows'] ?? 0 ),
14821494
'applied' => (int) ( $artifacts['applied_rows'] ?? 0 ),
14831495
'skipped' => (int) ( $artifacts['skipped_rows'] ?? 0 ),
@@ -1486,13 +1498,14 @@ private function build_cleanup_operator_summary( array $result ): array {
14861498
'remaining_reclaimable_artifact_bytes' => (int) ( $remaining['remaining_reclaimable_artifact_bytes'] ?? $artifacts['remaining_reclaimable_artifact_bytes'] ?? 0 ),
14871499
'remaining_reclaimable_human' => $this->format_bytes($remaining['remaining_reclaimable_artifact_bytes'] ?? $artifacts['remaining_reclaimable_artifact_bytes'] ?? 0),
14881500
),
1489-
'children' => $this->build_cleanup_operator_child_summary( (array) ( $result['children'] ?? $result['evidence']['children'] ?? array() ) ),
1490-
'by_type' => (array) ( $cleanup_items['by_type'] ?? array() ),
1491-
'skipped_by_reason' => (array) ( $remaining['skipped_by_reason'] ?? $cleanup_items['skipped_examples_by_reason'] ?? array() ),
1492-
'failed_by_reason' => (array) ( $cleanup_items['failed_by_reason'] ?? $artifacts['failed_by_reason'] ?? array() ),
1493-
'top_blocked_examples' => $this->cleanup_operator_blocked_examples($result),
1494-
'recommended_commands' => (array) ( $remaining['recommended_commands'] ?? array() ),
1495-
'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() ),
14961509
),
14971510
fn( $value ) => null !== $value && array() !== $value && '' !== $value
14981511
);
@@ -1674,13 +1687,21 @@ private function render_cleanup_remaining_work_summary( array $summary ): void {
16741687
WP_CLI::log('Remaining work summary:');
16751688
$this->format_items(
16761689
array(
1690+
array(
1691+
'metric' => 'total_bytes_reclaimed',
1692+
'value' => $this->format_bytes($summary['total_bytes_reclaimed'] ?? 0),
1693+
),
16771694
array(
16781695
'metric' => 'remaining_reclaimable_artifact_bytes',
16791696
'value' => $this->format_bytes($summary['remaining_reclaimable_artifact_bytes'] ?? 0),
16801697
),
16811698
array(
1682-
'metric' => 'remaining_safely_removable_worktrees',
1683-
'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 ),
16841705
),
16851706
),
16861707
array( 'metric', 'value' ),
@@ -2063,8 +2084,8 @@ public function remove_repo( array $args, array $assoc_args ): void {
20632084
* [--skip-cleanup]
20642085
* : Skip the local cleanup dry-run summary.
20652086
*
2066-
* [--skip-sizes]
2067-
* : Skip best-effort workspace size collection.
2087+
* [--include-sizes]
2088+
* : Include best-effort workspace size collection. This can be expensive on huge workspaces; combine with --size-limit.
20682089
*
20692090
* [--include-worktree-status]
20702091
* : Include full per-worktree git status. This can be expensive on huge workspaces.
@@ -2094,7 +2115,7 @@ public function hygiene( array $args, array $assoc_args ): void { // phpcs:ign
20942115

20952116
$input = array(
20962117
'include_cleanup' => empty($assoc_args['skip-cleanup']),
2097-
'include_sizes' => empty($assoc_args['skip-sizes']),
2118+
'include_sizes' => ! empty($assoc_args['include-sizes']),
20982119
'include_worktree_status' => ! empty($assoc_args['include-worktree-status']),
20992120
'refresh_inventory' => ! empty($assoc_args['refresh-inventory']),
21002121
);
@@ -4853,6 +4874,12 @@ private function render_workspace_hygiene_report( array $report, array $assoc_ar
48534874
WP_CLI::log( (string) $report['suggested_cleanup_command']);
48544875
}
48554876

4877+
if ( ! empty($report['suggested_size_command']) ) {
4878+
WP_CLI::log('');
4879+
WP_CLI::log('Suggested bounded size review:');
4880+
WP_CLI::log( (string) $report['suggested_size_command']);
4881+
}
4882+
48564883
foreach ( (array) ( $report['notes'] ?? array() ) as $note ) {
48574884
WP_CLI::log('Note: ' . $note);
48584885
}
@@ -6025,6 +6052,8 @@ private function render_worktree_bounded_cleanup_eligible_apply_result( array $r
60256052
}
60266053
}
60276054

6055+
$this->render_active_no_signal_triage_preview( (array) ( $result['active_no_signal_triage'] ?? array() ) );
6056+
60286057
WP_CLI::log('');
60296058
$remaining = (int) ( $continuation['remaining_total'] ?? 0 );
60306059
if ( $remaining > 0 ) {
@@ -6163,19 +6192,20 @@ private function compact_worktree_bounded_cleanup_eligible_apply_json( array $re
61636192
);
61646193

61656194
$report = array(
6166-
'success' => (bool) ( $result['success'] ?? true ),
6167-
'mode' => (string) ( $result['mode'] ?? 'bounded_cleanup_eligible_apply' ),
6168-
'dry_run' => ! empty($result['dry_run']),
6169-
'destructive' => ! empty($result['destructive']),
6170-
'workspace_path' => $result['workspace_path'] ?? null,
6171-
'generated_at' => $result['generated_at'] ?? null,
6172-
'summary' => $compact_summary,
6173-
'blocker_buckets' => $buckets,
6174-
'next_actions' => $actions,
6175-
'candidates' => $this->compact_cleanup_rows($candidates, 25),
6176-
'removed' => $this->compact_cleanup_rows($removed, 25),
6177-
'continuation' => $this->compact_cleanup_continuation( (array) ( $result['continuation'] ?? $result['pagination'] ?? array() ) ),
6178-
'evidence' => $this->compact_cleanup_evidence( (array) ( $result['evidence'] ?? array() ), $skipped ),
6195+
'success' => (bool) ( $result['success'] ?? true ),
6196+
'mode' => (string) ( $result['mode'] ?? 'bounded_cleanup_eligible_apply' ),
6197+
'dry_run' => ! empty($result['dry_run']),
6198+
'destructive' => ! empty($result['destructive']),
6199+
'workspace_path' => $result['workspace_path'] ?? null,
6200+
'generated_at' => $result['generated_at'] ?? null,
6201+
'summary' => $compact_summary,
6202+
'blocker_buckets' => $buckets,
6203+
'next_actions' => $actions,
6204+
'active_no_signal_triage' => (array) ( $result['active_no_signal_triage'] ?? array() ),
6205+
'candidates' => $this->compact_cleanup_rows($candidates, 25),
6206+
'removed' => $this->compact_cleanup_rows($removed, 25),
6207+
'continuation' => $this->compact_cleanup_continuation( (array) ( $result['continuation'] ?? $result['pagination'] ?? array() ) ),
6208+
'evidence' => $this->compact_cleanup_evidence( (array) ( $result['evidence'] ?? array() ), $skipped ),
61796209
);
61806210

61816211
if ( ! empty($result['job_backed']) ) {
@@ -6185,6 +6215,55 @@ private function compact_worktree_bounded_cleanup_eligible_apply_json( array $re
61856215
return array_filter($report, fn( $value ) => null !== $value);
61866216
}
61876217

6218+
/**
6219+
* Render concise active/no-signal triage preview from bounded cleanup output.
6220+
*
6221+
* @param array<string,mixed> $preview Triage preview payload.
6222+
* @return void
6223+
*/
6224+
private function render_active_no_signal_triage_preview( array $preview ): void {
6225+
$total = (int) ( $preview['total'] ?? 0 );
6226+
if ( $total <= 0 ) {
6227+
return;
6228+
}
6229+
6230+
WP_CLI::log('');
6231+
WP_CLI::log(sprintf('Active/no-signal triage preview: %d unresolved active worktree(s).', $total));
6232+
$summary_rows = array();
6233+
foreach ( (array) ( $preview['by_age'] ?? array() ) as $bucket => $count ) {
6234+
if ( (int) $count > 0 ) {
6235+
$summary_rows[] = array(
6236+
'dimension' => 'age',
6237+
'bucket' => (string) $bucket,
6238+
'count' => (int) $count,
6239+
);
6240+
}
6241+
}
6242+
foreach ( (array) ( $preview['by_liveness'] ?? array() ) as $bucket => $count ) {
6243+
$summary_rows[] = array(
6244+
'dimension' => 'liveness',
6245+
'bucket' => (string) $bucket,
6246+
'count' => (int) $count,
6247+
);
6248+
}
6249+
foreach ( (array) ( $preview['by_repo'] ?? array() ) as $bucket => $count ) {
6250+
$summary_rows[] = array(
6251+
'dimension' => 'repo',
6252+
'bucket' => (string) $bucket,
6253+
'count' => (int) $count,
6254+
);
6255+
}
6256+
$this->format_items($summary_rows, array( 'dimension', 'bucket', 'count' ), array( 'format' => 'table' ), 'dimension');
6257+
6258+
WP_CLI::log('Non-destructive next commands:');
6259+
foreach ( (array) ( $preview['commands'] ?? array() ) as $label => $command ) {
6260+
WP_CLI::log(sprintf(' %s: %s', (string) $label, (string) $command));
6261+
}
6262+
if ( ! empty($preview['safety']) ) {
6263+
WP_CLI::log('Safety: ' . (string) $preview['safety']);
6264+
}
6265+
}
6266+
61886267
/**
61896268
* Build skipped blocker buckets with bounded examples.
61906269
*

0 commit comments

Comments
 (0)