@@ -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,245 @@ 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 (
1342+ 'metric ' => 'planned_rows ' ,
1343+ 'value ' => (int ) ( $ cleanup_counts ['planned ' ] ?? 0 ),
1344+ ),
1345+ array (
1346+ 'metric ' => 'applied_rows ' ,
1347+ 'value ' => (int ) ( $ cleanup_counts ['applied ' ] ?? 0 ),
1348+ ),
1349+ array (
1350+ 'metric ' => 'skipped_rows ' ,
1351+ 'value ' => (int ) ( $ cleanup_counts ['skipped ' ] ?? 0 ),
1352+ ),
1353+ array (
1354+ 'metric ' => 'failed_rows ' ,
1355+ 'value ' => (int ) ( $ cleanup_counts ['failed ' ] ?? 0 ),
1356+ ),
1357+ array (
1358+ 'metric ' => 'bytes_reclaimed ' ,
1359+ 'value ' => $ this ->format_bytes ($ cleanup_counts ['bytes_reclaimed ' ] ?? 0 ),
1360+ ),
1361+ array (
1362+ 'metric ' => 'remaining_reclaimable_artifacts ' ,
1363+ 'value ' => $ this ->format_bytes ($ artifacts ['remaining_reclaimable_artifact_bytes ' ] ?? 0 ),
1364+ ),
1365+ ),
1366+ array ( 'metric ' , 'value ' ),
1367+ array ( 'format ' => 'table ' ),
1368+ 'metric '
1369+ );
1370+
1371+ $ this ->render_cleanup_summary_reason_rows ('Skipped rows by reason: ' , (array ) ( $ summary ['skipped_by_reason ' ] ?? array () ));
1372+ $ this ->render_cleanup_summary_reason_rows ('Failed rows by reason: ' , (array ) ( $ summary ['failed_by_reason ' ] ?? array () ));
1373+
1374+ $ examples = (array ) ( $ summary ['top_blocked_examples ' ] ?? array () );
1375+ if ( array () !== $ examples ) {
1376+ WP_CLI ::log ('' );
1377+ WP_CLI ::log ('Top blocked examples: ' );
1378+ $ rows = array_map (
1379+ fn ( $ row ) => array (
1380+ 'size ' => $ this ->format_bytes (is_array ($ row ) ? ( $ row ['size_bytes ' ] ?? 0 ) : 0 ),
1381+ 'reason ' => is_array ($ row ) ? (string ) ( $ row ['reason ' ] ?? '' ) : '' ,
1382+ 'handle ' => is_array ($ row ) ? (string ) ( $ row ['handle ' ] ?? '' ) : '' ,
1383+ 'artifact_path ' => is_array ($ row ) ? (string ) ( $ row ['artifact_path ' ] ?? '' ) : '' ,
1384+ 'path ' => is_array ($ row ) ? (string ) ( $ row ['path ' ] ?? '' ) : '' ,
1385+ ),
1386+ array_slice ($ examples , 0 , 10 )
1387+ );
1388+ $ this ->format_items ($ rows , array ( 'size ' , 'reason ' , 'handle ' , 'artifact_path ' , 'path ' ), array ( 'format ' => 'table ' ), 'size ' );
1389+ }
1390+
1391+ $ commands = (array ) ( $ summary ['recommended_commands ' ] ?? array () );
1392+ if ( array () !== $ commands ) {
1393+ WP_CLI ::log ('' );
1394+ WP_CLI ::log ('Recommended next commands: ' );
1395+ $ rows = array_map (
1396+ fn ( $ row ) => array (
1397+ 'bucket ' => is_array ($ row ) ? (string ) ( $ row ['bucket ' ] ?? '' ) : '' ,
1398+ 'review_command ' => is_array ($ row ) ? (string ) ( $ row ['command ' ] ?? '' ) : '' ,
1399+ 'apply_command ' => is_array ($ row ) ? (string ) ( $ row ['apply ' ] ?? '' ) : '' ,
1400+ 'apply_destructive ' => is_array ($ row ) && ! empty ($ row ['apply_destructive ' ]) ? 'yes ' : 'no ' ,
1401+ ),
1402+ array_slice ($ commands , 0 , 10 )
1403+ );
1404+ $ this ->format_items ($ rows , array ( 'bucket ' , 'review_command ' , 'apply_command ' , 'apply_destructive ' ), array ( 'format ' => 'table ' ), 'bucket ' );
1405+ }
1406+ }
1407+
1408+ /**
1409+ * Build compact cleanup status/evidence output for chat/operator workflows.
1410+ *
1411+ * @param array<string,mixed> $result Cleanup status/evidence result.
1412+ * @return array<string,mixed>
1413+ */
1414+ private function build_cleanup_operator_summary ( array $ result ): array {
1415+ $ cleanup_items = (array ) ( $ result ['cleanup_items ' ] ?? $ result ['evidence ' ]['cleanup_items ' ] ?? array () );
1416+ $ artifacts = (array ) ( $ result ['artifact_cleanup ' ] ?? $ result ['evidence ' ]['artifact_cleanup ' ] ?? array () );
1417+ $ remaining = (array ) ( $ result ['remaining_work_summary ' ] ?? array () );
1418+
1419+ return array_filter (
1420+ array (
1421+ 'success ' => (bool ) ( $ result ['success ' ] ?? false ),
1422+ 'run_id ' => (string ) ( $ result ['run_id ' ] ?? '' ),
1423+ 'job_id ' => isset ($ result ['job_id ' ]) ? (int ) $ result ['job_id ' ] : null ,
1424+ 'mode ' => (string ) ( $ result ['mode ' ] ?? $ result ['evidence ' ]['engine_data ' ]['cleanup_run ' ]['mode ' ] ?? '' ),
1425+ 'state ' => (string ) ( $ result ['state ' ] ?? '' ),
1426+ 'status ' => (string ) ( $ result ['status ' ] ?? '' ),
1427+ 'parent_status ' => (string ) ( $ result ['parent_status ' ] ?? '' ),
1428+ 'created_at ' => (string ) ( $ result ['created_at ' ] ?? '' ),
1429+ 'completed_at ' => (string ) ( $ result ['completed_at ' ] ?? $ result ['parent_completed_at ' ] ?? '' ),
1430+ 'cleanup_counts ' => array (
1431+ 'planned ' => (int ) ( $ cleanup_items ['planned_rows ' ] ?? 0 ),
1432+ 'applied ' => (int ) ( $ cleanup_items ['applied_rows ' ] ?? 0 ),
1433+ 'skipped ' => (int ) ( $ cleanup_items ['skipped_rows ' ] ?? 0 ),
1434+ 'failed ' => (int ) ( $ cleanup_items ['failed_rows ' ] ?? 0 ),
1435+ 'bytes_reclaimed ' => (int ) ( $ cleanup_items ['bytes_reclaimed ' ] ?? 0 ),
1436+ 'freed_human ' => (string ) ( $ cleanup_items ['freed_human ' ] ?? $ this ->format_bytes ($ cleanup_items ['bytes_reclaimed ' ] ?? 0 ) ),
1437+ ),
1438+ 'artifact_cleanup ' => array (
1439+ 'planned ' => (int ) ( $ artifacts ['planned_rows ' ] ?? 0 ),
1440+ 'applied ' => (int ) ( $ artifacts ['applied_rows ' ] ?? 0 ),
1441+ 'skipped ' => (int ) ( $ artifacts ['skipped_rows ' ] ?? 0 ),
1442+ 'failed ' => (int ) ( $ artifacts ['failed_rows ' ] ?? 0 ),
1443+ 'bytes_reclaimed ' => (int ) ( $ artifacts ['bytes_reclaimed ' ] ?? 0 ),
1444+ 'remaining_reclaimable_artifact_bytes ' => (int ) ( $ remaining ['remaining_reclaimable_artifact_bytes ' ] ?? $ artifacts ['remaining_reclaimable_artifact_bytes ' ] ?? 0 ),
1445+ 'remaining_reclaimable_human ' => $ this ->format_bytes ($ remaining ['remaining_reclaimable_artifact_bytes ' ] ?? $ artifacts ['remaining_reclaimable_artifact_bytes ' ] ?? 0 ),
1446+ ),
1447+ 'children ' => $ this ->build_cleanup_operator_child_summary ( (array ) ( $ result ['children ' ] ?? $ result ['evidence ' ]['children ' ] ?? array () ) ),
1448+ 'by_type ' => (array ) ( $ cleanup_items ['by_type ' ] ?? array () ),
1449+ 'skipped_by_reason ' => (array ) ( $ remaining ['skipped_by_reason ' ] ?? $ cleanup_items ['skipped_examples_by_reason ' ] ?? array () ),
1450+ 'failed_by_reason ' => (array ) ( $ cleanup_items ['failed_by_reason ' ] ?? $ artifacts ['failed_by_reason ' ] ?? array () ),
1451+ 'top_blocked_examples ' => $ this ->cleanup_operator_blocked_examples ($ result ),
1452+ 'recommended_commands ' => (array ) ( $ remaining ['recommended_commands ' ] ?? array () ),
1453+ 'locks ' => (array ) ( $ result ['locks ' ] ?? array () ),
1454+ ),
1455+ fn ( $ value ) => null !== $ value && array () !== $ value && '' !== $ value
1456+ );
1457+ }
1458+
1459+ /**
1460+ * Summarize child cleanup jobs without unbounded ID lists.
1461+ *
1462+ * @param array<string,mixed> $children Child job aggregate.
1463+ * @return array<string,mixed>
1464+ */
1465+ private function build_cleanup_operator_child_summary ( array $ children ): array {
1466+ return array (
1467+ 'total ' => (int ) ( $ children ['total ' ] ?? 0 ),
1468+ 'running ' => (int ) ( $ children ['running ' ] ?? 0 ),
1469+ 'completed ' => (int ) ( $ children ['completed ' ] ?? 0 ),
1470+ 'failed ' => (int ) ( $ children ['failed ' ] ?? 0 ),
1471+ 'skipped ' => (int ) ( $ children ['skipped ' ] ?? 0 ),
1472+ 'statuses ' => (array ) ( $ children ['statuses ' ] ?? array () ),
1473+ 'batch_jobs ' => isset ($ children ['batch_total ' ]) ? (int ) $ children ['batch_total ' ] : count ( (array ) ( $ children ['batch_job_ids ' ] ?? array () ) ),
1474+ 'chunk_jobs ' => isset ($ children ['chunk_total ' ]) ? (int ) $ children ['chunk_total ' ] : count ( (array ) ( $ children ['chunk_job_ids ' ] ?? array () ) ),
1475+ );
1476+ }
1477+
1478+ /**
1479+ * Extract largest blocked cleanup examples from compact summaries and full evidence when available.
1480+ *
1481+ * @param array<string,mixed> $result Cleanup status/evidence result.
1482+ * @return array<int,array<string,mixed>>
1483+ */
1484+ private function cleanup_operator_blocked_examples ( array $ result ): array {
1485+ $ examples = array ();
1486+ foreach ( (array ) ( $ result ['remaining_work_summary ' ]['skipped_by_reason ' ] ?? array () ) as $ reason => $ bucket ) {
1487+ foreach ( (array ) ( is_array ($ bucket ) ? ( $ bucket ['examples ' ] ?? array () ) : array () ) as $ row ) {
1488+ if ( is_array ($ row ) ) {
1489+ $ examples [] = $ this ->cleanup_operator_example_row ($ row , (string ) $ reason );
1490+ }
1491+ }
1492+ }
1493+
1494+ foreach ( (array ) ( $ result ['evidence ' ]['child_jobs ' ] ?? array () ) as $ job ) {
1495+ $ engine_data = (array ) ( is_array ($ job ) ? ( $ job ['engine_data ' ] ?? array () ) : array () );
1496+ foreach ( array ( 'skipped ' , 'failed ' ) as $ bucket ) {
1497+ foreach ( (array ) ( $ engine_data [ $ bucket ] ?? array () ) as $ row ) {
1498+ if ( is_array ($ row ) ) {
1499+ $ examples [] = $ this ->cleanup_operator_example_row ($ row , (string ) ( $ row ['reason_code ' ] ?? $ bucket ));
1500+ }
1501+ }
1502+ }
1503+ }
1504+
1505+ usort ($ examples , fn ( $ a , $ b ) => (int ) ( $ b ['size_bytes ' ] ?? 0 ) <=> (int ) ( $ a ['size_bytes ' ] ?? 0 ));
1506+ $ seen = array ();
1507+ $ deduped = array_values (array_filter (
1508+ $ examples ,
1509+ function ( array $ row ) use ( &$ seen ): bool {
1510+ $ key = (string ) ( $ row ['handle ' ] ?? '' ) . '| ' . (string ) ( $ row ['reason ' ] ?? '' ) . '| ' . (string ) ( $ row ['path ' ] ?? '' );
1511+ if ( isset ($ seen [ $ key ]) ) {
1512+ return false ;
1513+ }
1514+ $ seen [ $ key ] = true ;
1515+ return true ;
1516+ }
1517+ ));
1518+ return array_slice ($ deduped , 0 , 10 );
1519+ }
1520+
1521+ /**
1522+ * Normalize one blocked cleanup example row for compact output.
1523+ *
1524+ * @param array<string,mixed> $row Cleanup row.
1525+ * @param string $reason Fallback reason code.
1526+ * @return array<string,mixed>
1527+ */
1528+ private function cleanup_operator_example_row ( array $ row , string $ reason ): array {
1529+ $ artifact_path = (string ) ( $ row ['artifact_path ' ] ?? '' );
1530+ $ artifacts = (array ) ( $ row ['artifacts ' ] ?? array () );
1531+ if ( '' === $ artifact_path && isset ($ artifacts [0 ]) && is_array ($ artifacts [0 ]) ) {
1532+ $ artifact_path = (string ) ( $ artifacts [0 ]['path ' ] ?? '' );
1533+ }
1534+
1535+ return array_filter (
1536+ array (
1537+ 'handle ' => (string ) ( $ row ['handle ' ] ?? '' ),
1538+ 'reason ' => (string ) ( $ row ['reason_code ' ] ?? $ row ['reason ' ] ?? $ reason ),
1539+ 'path ' => (string ) ( $ row ['path ' ] ?? '' ),
1540+ 'artifact_path ' => $ artifact_path ,
1541+ 'size_bytes ' => $ this ->cleanup_operator_row_bytes ($ row ),
1542+ 'size ' => $ this ->format_bytes ($ this ->cleanup_operator_row_bytes ($ row )),
1543+ ),
1544+ fn ( $ value ) => '' !== $ value && 0 !== $ value
1545+ );
1546+ }
1547+
1548+ /**
1549+ * Return best-known reclaimable bytes for one cleanup row.
1550+ *
1551+ * @param array<string,mixed> $row Cleanup row.
1552+ * @return int
1553+ */
1554+ private function cleanup_operator_row_bytes ( array $ row ): int {
1555+ foreach ( array ( 'artifact_size_bytes ' , 'size_bytes ' , 'bytes_reclaimed ' ) as $ field ) {
1556+ if ( isset ($ row [ $ field ]) ) {
1557+ return max (0 , (int ) $ row [ $ field ]);
1558+ }
1559+ }
1560+ $ total = 0 ;
1561+ foreach ( (array ) ( $ row ['artifacts ' ] ?? array () ) as $ artifact ) {
1562+ $ total += max (0 , (int ) ( is_array ($ artifact ) ? ( $ artifact ['size_bytes ' ] ?? 0 ) : 0 ));
1563+ }
1564+ return $ total ;
1565+ }
1566+
13141567 /**
13151568 * Attach live workspace lock status to cleanup triage surfaces when available.
13161569 *
0 commit comments