@@ -633,6 +633,10 @@ public function adopt_repo( array $args, array $assoc_args ): void {
633633 * [--verbose]
634634 * : Include full diagnostic child job ID lists in task-backed cleanup status output.
635635 *
636+ * [--summary]
637+ * : For `status` and `evidence`, print compact operator-focused cleanup counts,
638+ * blockers, examples, and next commands instead of full nested evidence.
639+ *
636640 * [--drain]
637641 * : For `cleanup run`, drain the queued parent job, drain active child cleanup
638642 * jobs discovered from cleanup status, then print verified bytes reclaimed.
@@ -695,6 +699,9 @@ public function adopt_repo( array $args, array $assoc_args ): void {
695699 * # Print recorded evidence / engine data
696700 * wp datamachine-code workspace cleanup evidence cleanup-run-123 --format=json
697701 *
702+ * # Print compact evidence summary for chat/operator follow-up
703+ * wp datamachine-code workspace cleanup evidence cleanup-run-123 --summary
704+ *
698705 * @subcommand cleanup
699706 */
700707 public function cleanup ( array $ args , array $ assoc_args ): void {
@@ -1276,6 +1283,9 @@ private function cleanup_run_control_job_ids( string $operation, int $job_id ):
12761283 private function render_cleanup_control_result ( array $ result , array $ assoc_args ): void {
12771284 $ result = $ this ->attach_current_workspace_lock_status ($ result );
12781285 $ format = (string ) ( $ assoc_args ['format ' ] ?? 'table ' );
1286+ if ( ! empty ($ assoc_args ['summary ' ]) ) {
1287+ $ result = $ this ->build_cleanup_operator_summary ($ result );
1288+ }
12791289 if ( 'json ' === $ format ) {
12801290 WP_CLI ::log ( (string ) wp_json_encode ($ result , JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES ));
12811291 return ;
@@ -1291,6 +1301,10 @@ private function render_cleanup_control_result( array $result, array $assoc_args
12911301 WP_CLI ::log (sprintf ('%s: %s ' , ucfirst (str_replace ('_ ' , ' ' , $ key )), (string ) $ result [ $ key ]));
12921302 }
12931303 }
1304+ if ( ! empty ($ assoc_args ['summary ' ]) ) {
1305+ $ this ->render_cleanup_operator_summary ($ result );
1306+ return ;
1307+ }
12941308 if ( ! empty ($ result ['progress ' ]) && is_array ($ result ['progress ' ]) ) {
12951309 $ this ->render_cleanup_progress_summary ( (array ) $ result ['progress ' ]);
12961310 }
@@ -1311,6 +1325,227 @@ private function render_cleanup_control_result( array $result, array $assoc_args
13111325 }
13121326 }
13131327
1328+ /**
1329+ * Render compact cleanup status/evidence summary tables.
1330+ *
1331+ * @param array<string,mixed> $summary Compact cleanup summary.
1332+ * @return void
1333+ */
1334+ private function render_cleanup_operator_summary ( array $ summary ): void {
1335+ WP_CLI ::log ('' );
1336+ WP_CLI ::log ('Cleanup operator summary: ' );
1337+ $ cleanup_counts = (array ) ( $ summary ['cleanup_counts ' ] ?? array () );
1338+ $ artifacts = (array ) ( $ summary ['artifact_cleanup ' ] ?? array () );
1339+ $ this ->format_items (
1340+ array (
1341+ array ( 'metric ' => 'planned_rows ' , 'value ' => (int ) ( $ cleanup_counts ['planned ' ] ?? 0 ) ),
1342+ array ( 'metric ' => 'applied_rows ' , 'value ' => (int ) ( $ cleanup_counts ['applied ' ] ?? 0 ) ),
1343+ array ( 'metric ' => 'skipped_rows ' , 'value ' => (int ) ( $ cleanup_counts ['skipped ' ] ?? 0 ) ),
1344+ array ( 'metric ' => 'failed_rows ' , 'value ' => (int ) ( $ cleanup_counts ['failed ' ] ?? 0 ) ),
1345+ array ( 'metric ' => 'bytes_reclaimed ' , 'value ' => $ this ->format_bytes ($ cleanup_counts ['bytes_reclaimed ' ] ?? 0 ) ),
1346+ array ( 'metric ' => 'remaining_reclaimable_artifacts ' , 'value ' => $ this ->format_bytes ($ artifacts ['remaining_reclaimable_artifact_bytes ' ] ?? 0 ) ),
1347+ ),
1348+ array ( 'metric ' , 'value ' ),
1349+ array ( 'format ' => 'table ' ),
1350+ 'metric '
1351+ );
1352+
1353+ $ this ->render_cleanup_summary_reason_rows ('Skipped rows by reason: ' , (array ) ( $ summary ['skipped_by_reason ' ] ?? array () ));
1354+ $ this ->render_cleanup_summary_reason_rows ('Failed rows by reason: ' , (array ) ( $ summary ['failed_by_reason ' ] ?? array () ));
1355+
1356+ $ examples = (array ) ( $ summary ['top_blocked_examples ' ] ?? array () );
1357+ if ( array () !== $ examples ) {
1358+ WP_CLI ::log ('' );
1359+ WP_CLI ::log ('Top blocked examples: ' );
1360+ $ rows = array_map (
1361+ fn ( $ row ) => array (
1362+ 'size ' => $ this ->format_bytes (is_array ($ row ) ? ( $ row ['size_bytes ' ] ?? 0 ) : 0 ),
1363+ 'reason ' => is_array ($ row ) ? (string ) ( $ row ['reason ' ] ?? '' ) : '' ,
1364+ 'handle ' => is_array ($ row ) ? (string ) ( $ row ['handle ' ] ?? '' ) : '' ,
1365+ 'artifact_path ' => is_array ($ row ) ? (string ) ( $ row ['artifact_path ' ] ?? '' ) : '' ,
1366+ 'path ' => is_array ($ row ) ? (string ) ( $ row ['path ' ] ?? '' ) : '' ,
1367+ ),
1368+ array_slice ($ examples , 0 , 10 )
1369+ );
1370+ $ this ->format_items ($ rows , array ( 'size ' , 'reason ' , 'handle ' , 'artifact_path ' , 'path ' ), array ( 'format ' => 'table ' ), 'size ' );
1371+ }
1372+
1373+ $ commands = (array ) ( $ summary ['recommended_commands ' ] ?? array () );
1374+ if ( array () !== $ commands ) {
1375+ WP_CLI ::log ('' );
1376+ WP_CLI ::log ('Recommended next commands: ' );
1377+ $ rows = array_map (
1378+ fn ( $ row ) => array (
1379+ 'bucket ' => is_array ($ row ) ? (string ) ( $ row ['bucket ' ] ?? '' ) : '' ,
1380+ 'review_command ' => is_array ($ row ) ? (string ) ( $ row ['command ' ] ?? '' ) : '' ,
1381+ 'apply_command ' => is_array ($ row ) ? (string ) ( $ row ['apply ' ] ?? '' ) : '' ,
1382+ 'apply_destructive ' => is_array ($ row ) && ! empty ($ row ['apply_destructive ' ]) ? 'yes ' : 'no ' ,
1383+ ),
1384+ array_slice ($ commands , 0 , 10 )
1385+ );
1386+ $ this ->format_items ($ rows , array ( 'bucket ' , 'review_command ' , 'apply_command ' , 'apply_destructive ' ), array ( 'format ' => 'table ' ), 'bucket ' );
1387+ }
1388+ }
1389+
1390+ /**
1391+ * Build compact cleanup status/evidence output for chat/operator workflows.
1392+ *
1393+ * @param array<string,mixed> $result Cleanup status/evidence result.
1394+ * @return array<string,mixed>
1395+ */
1396+ private function build_cleanup_operator_summary ( array $ result ): array {
1397+ $ cleanup_items = (array ) ( $ result ['cleanup_items ' ] ?? $ result ['evidence ' ]['cleanup_items ' ] ?? array () );
1398+ $ artifacts = (array ) ( $ result ['artifact_cleanup ' ] ?? $ result ['evidence ' ]['artifact_cleanup ' ] ?? array () );
1399+ $ remaining = (array ) ( $ result ['remaining_work_summary ' ] ?? array () );
1400+
1401+ return array_filter (
1402+ array (
1403+ 'success ' => (bool ) ( $ result ['success ' ] ?? false ),
1404+ 'run_id ' => (string ) ( $ result ['run_id ' ] ?? '' ),
1405+ 'job_id ' => isset ($ result ['job_id ' ]) ? (int ) $ result ['job_id ' ] : null ,
1406+ 'mode ' => (string ) ( $ result ['mode ' ] ?? $ result ['evidence ' ]['engine_data ' ]['cleanup_run ' ]['mode ' ] ?? '' ),
1407+ 'state ' => (string ) ( $ result ['state ' ] ?? '' ),
1408+ 'status ' => (string ) ( $ result ['status ' ] ?? '' ),
1409+ 'parent_status ' => (string ) ( $ result ['parent_status ' ] ?? '' ),
1410+ 'created_at ' => (string ) ( $ result ['created_at ' ] ?? '' ),
1411+ 'completed_at ' => (string ) ( $ result ['completed_at ' ] ?? $ result ['parent_completed_at ' ] ?? '' ),
1412+ 'cleanup_counts ' => array (
1413+ 'planned ' => (int ) ( $ cleanup_items ['planned_rows ' ] ?? 0 ),
1414+ 'applied ' => (int ) ( $ cleanup_items ['applied_rows ' ] ?? 0 ),
1415+ 'skipped ' => (int ) ( $ cleanup_items ['skipped_rows ' ] ?? 0 ),
1416+ 'failed ' => (int ) ( $ cleanup_items ['failed_rows ' ] ?? 0 ),
1417+ 'bytes_reclaimed ' => (int ) ( $ cleanup_items ['bytes_reclaimed ' ] ?? 0 ),
1418+ 'freed_human ' => (string ) ( $ cleanup_items ['freed_human ' ] ?? $ this ->format_bytes ($ cleanup_items ['bytes_reclaimed ' ] ?? 0 ) ),
1419+ ),
1420+ 'artifact_cleanup ' => array (
1421+ 'planned ' => (int ) ( $ artifacts ['planned_rows ' ] ?? 0 ),
1422+ 'applied ' => (int ) ( $ artifacts ['applied_rows ' ] ?? 0 ),
1423+ 'skipped ' => (int ) ( $ artifacts ['skipped_rows ' ] ?? 0 ),
1424+ 'failed ' => (int ) ( $ artifacts ['failed_rows ' ] ?? 0 ),
1425+ 'bytes_reclaimed ' => (int ) ( $ artifacts ['bytes_reclaimed ' ] ?? 0 ),
1426+ 'remaining_reclaimable_artifact_bytes ' => (int ) ( $ remaining ['remaining_reclaimable_artifact_bytes ' ] ?? $ artifacts ['remaining_reclaimable_artifact_bytes ' ] ?? 0 ),
1427+ 'remaining_reclaimable_human ' => $ this ->format_bytes ($ remaining ['remaining_reclaimable_artifact_bytes ' ] ?? $ artifacts ['remaining_reclaimable_artifact_bytes ' ] ?? 0 ),
1428+ ),
1429+ 'children ' => $ this ->build_cleanup_operator_child_summary ( (array ) ( $ result ['children ' ] ?? $ result ['evidence ' ]['children ' ] ?? array () ) ),
1430+ 'by_type ' => (array ) ( $ cleanup_items ['by_type ' ] ?? array () ),
1431+ 'skipped_by_reason ' => (array ) ( $ remaining ['skipped_by_reason ' ] ?? $ cleanup_items ['skipped_examples_by_reason ' ] ?? array () ),
1432+ 'failed_by_reason ' => (array ) ( $ cleanup_items ['failed_by_reason ' ] ?? $ artifacts ['failed_by_reason ' ] ?? array () ),
1433+ 'top_blocked_examples ' => $ this ->cleanup_operator_blocked_examples ($ result ),
1434+ 'recommended_commands ' => (array ) ( $ remaining ['recommended_commands ' ] ?? array () ),
1435+ 'locks ' => (array ) ( $ result ['locks ' ] ?? array () ),
1436+ ),
1437+ fn ( $ value ) => null !== $ value && array () !== $ value && '' !== $ value
1438+ );
1439+ }
1440+
1441+ /**
1442+ * Summarize child cleanup jobs without unbounded ID lists.
1443+ *
1444+ * @param array<string,mixed> $children Child job aggregate.
1445+ * @return array<string,mixed>
1446+ */
1447+ private function build_cleanup_operator_child_summary ( array $ children ): array {
1448+ return array (
1449+ 'total ' => (int ) ( $ children ['total ' ] ?? 0 ),
1450+ 'running ' => (int ) ( $ children ['running ' ] ?? 0 ),
1451+ 'completed ' => (int ) ( $ children ['completed ' ] ?? 0 ),
1452+ 'failed ' => (int ) ( $ children ['failed ' ] ?? 0 ),
1453+ 'skipped ' => (int ) ( $ children ['skipped ' ] ?? 0 ),
1454+ 'statuses ' => (array ) ( $ children ['statuses ' ] ?? array () ),
1455+ 'batch_jobs ' => isset ($ children ['batch_total ' ]) ? (int ) $ children ['batch_total ' ] : count ( (array ) ( $ children ['batch_job_ids ' ] ?? array () ) ),
1456+ 'chunk_jobs ' => isset ($ children ['chunk_total ' ]) ? (int ) $ children ['chunk_total ' ] : count ( (array ) ( $ children ['chunk_job_ids ' ] ?? array () ) ),
1457+ );
1458+ }
1459+
1460+ /**
1461+ * Extract largest blocked cleanup examples from compact summaries and full evidence when available.
1462+ *
1463+ * @param array<string,mixed> $result Cleanup status/evidence result.
1464+ * @return array<int,array<string,mixed>>
1465+ */
1466+ private function cleanup_operator_blocked_examples ( array $ result ): array {
1467+ $ examples = array ();
1468+ foreach ( (array ) ( $ result ['remaining_work_summary ' ]['skipped_by_reason ' ] ?? array () ) as $ reason => $ bucket ) {
1469+ foreach ( (array ) ( is_array ($ bucket ) ? ( $ bucket ['examples ' ] ?? array () ) : array () ) as $ row ) {
1470+ if ( is_array ($ row ) ) {
1471+ $ examples [] = $ this ->cleanup_operator_example_row ($ row , (string ) $ reason );
1472+ }
1473+ }
1474+ }
1475+
1476+ foreach ( (array ) ( $ result ['evidence ' ]['child_jobs ' ] ?? array () ) as $ job ) {
1477+ $ engine_data = (array ) ( is_array ($ job ) ? ( $ job ['engine_data ' ] ?? array () ) : array () );
1478+ foreach ( array ( 'skipped ' , 'failed ' ) as $ bucket ) {
1479+ foreach ( (array ) ( $ engine_data [ $ bucket ] ?? array () ) as $ row ) {
1480+ if ( is_array ($ row ) ) {
1481+ $ examples [] = $ this ->cleanup_operator_example_row ($ row , (string ) ( $ row ['reason_code ' ] ?? $ bucket ));
1482+ }
1483+ }
1484+ }
1485+ }
1486+
1487+ usort ($ examples , fn ( $ a , $ b ) => (int ) ( $ b ['size_bytes ' ] ?? 0 ) <=> (int ) ( $ a ['size_bytes ' ] ?? 0 ));
1488+ $ seen = array ();
1489+ $ deduped = array_values (array_filter (
1490+ $ examples ,
1491+ function ( array $ row ) use ( &$ seen ): bool {
1492+ $ key = (string ) ( $ row ['handle ' ] ?? '' ) . '| ' . (string ) ( $ row ['reason ' ] ?? '' ) . '| ' . (string ) ( $ row ['path ' ] ?? '' );
1493+ if ( isset ($ seen [ $ key ]) ) {
1494+ return false ;
1495+ }
1496+ $ seen [ $ key ] = true ;
1497+ return true ;
1498+ }
1499+ ));
1500+ return array_slice ($ deduped , 0 , 10 );
1501+ }
1502+
1503+ /**
1504+ * Normalize one blocked cleanup example row for compact output.
1505+ *
1506+ * @param array<string,mixed> $row Cleanup row.
1507+ * @param string $reason Fallback reason code.
1508+ * @return array<string,mixed>
1509+ */
1510+ private function cleanup_operator_example_row ( array $ row , string $ reason ): array {
1511+ $ artifact_path = (string ) ( $ row ['artifact_path ' ] ?? '' );
1512+ $ artifacts = (array ) ( $ row ['artifacts ' ] ?? array () );
1513+ if ( '' === $ artifact_path && isset ($ artifacts [0 ]) && is_array ($ artifacts [0 ]) ) {
1514+ $ artifact_path = (string ) ( $ artifacts [0 ]['path ' ] ?? '' );
1515+ }
1516+
1517+ return array_filter (
1518+ array (
1519+ 'handle ' => (string ) ( $ row ['handle ' ] ?? '' ),
1520+ 'reason ' => (string ) ( $ row ['reason_code ' ] ?? $ row ['reason ' ] ?? $ reason ),
1521+ 'path ' => (string ) ( $ row ['path ' ] ?? '' ),
1522+ 'artifact_path ' => $ artifact_path ,
1523+ 'size_bytes ' => $ this ->cleanup_operator_row_bytes ($ row ),
1524+ 'size ' => $ this ->format_bytes ($ this ->cleanup_operator_row_bytes ($ row )),
1525+ ),
1526+ fn ( $ value ) => '' !== $ value && 0 !== $ value
1527+ );
1528+ }
1529+
1530+ /**
1531+ * Return best-known reclaimable bytes for one cleanup row.
1532+ *
1533+ * @param array<string,mixed> $row Cleanup row.
1534+ * @return int
1535+ */
1536+ private function cleanup_operator_row_bytes ( array $ row ): int {
1537+ foreach ( array ( 'artifact_size_bytes ' , 'size_bytes ' , 'bytes_reclaimed ' ) as $ field ) {
1538+ if ( isset ($ row [ $ field ]) ) {
1539+ return max (0 , (int ) $ row [ $ field ]);
1540+ }
1541+ }
1542+ $ total = 0 ;
1543+ foreach ( (array ) ( $ row ['artifacts ' ] ?? array () ) as $ artifact ) {
1544+ $ total += max (0 , (int ) ( is_array ($ artifact ) ? ( $ artifact ['size_bytes ' ] ?? 0 ) : 0 ));
1545+ }
1546+ return $ total ;
1547+ }
1548+
13141549 /**
13151550 * Attach live workspace lock status to cleanup triage surfaces when available.
13161551 *
0 commit comments