From 9a65010e084a68960bfe1249336a452700012ca6 Mon Sep 17 00:00:00 2001 From: "homeboy-ci[bot]" <266378653+homeboy-ci[bot]@users.noreply.github.com> Date: Fri, 26 Jun 2026 21:31:04 -0400 Subject: [PATCH] Clarify hygiene inventory blocker counts --- inc/Cli/Commands/WorkspaceCommand.php | 24 +++++- inc/Cli/WorkspaceCompactOutput.php | 2 + inc/Workspace/WorkspaceHygieneReport.php | 93 ++++++++++++++++++------ tests/workspace-compact-output.php | 34 ++++++++- 4 files changed, 124 insertions(+), 29 deletions(-) diff --git a/inc/Cli/Commands/WorkspaceCommand.php b/inc/Cli/Commands/WorkspaceCommand.php index 4464ea5..d847dfe 100644 --- a/inc/Cli/Commands/WorkspaceCommand.php +++ b/inc/Cli/Commands/WorkspaceCommand.php @@ -4766,12 +4766,28 @@ private function render_workspace_hygiene_report( array $report, array $assoc_ar 'value' => (string) ( $worktrees['artifacts'] ?? 0 ), ), array( - 'metric' => 'dirty_protected', - 'value' => (string) ( $worktrees['protected_dirty'] ?? 0 ), + 'metric' => 'inventory_known_dirty', + 'value' => (string) ( $worktrees['inventory_known_dirty'] ?? 0 ), ), array( - 'metric' => 'unpushed_protected', - 'value' => (string) ( $worktrees['protected_unpushed'] ?? 0 ), + 'metric' => 'inventory_known_dirty_blockers', + 'value' => (string) ( $worktrees['protected_dirty_inventory_known'] ?? 0 ), + ), + array( + 'metric' => 'inventory_known_unpushed_blockers', + 'value' => (string) ( $worktrees['protected_unpushed_inventory_known'] ?? 0 ), + ), + array( + 'metric' => 'fresh_probed_dirty_blockers', + 'value' => (string) ( $worktrees['protected_dirty_fresh_probed'] ?? 0 ), + ), + array( + 'metric' => 'fresh_probed_unpushed_blockers', + 'value' => (string) ( $worktrees['protected_unpushed_fresh_probed'] ?? 0 ), + ), + array( + 'metric' => 'blocker_probe_source', + 'value' => (string) ( $worktrees['protected_count_probe_source'] ?? 'none' ), ), array( 'metric' => 'missing_metadata', diff --git a/inc/Cli/WorkspaceCompactOutput.php b/inc/Cli/WorkspaceCompactOutput.php index e80cf60..2ed6dbb 100644 --- a/inc/Cli/WorkspaceCompactOutput.php +++ b/inc/Cli/WorkspaceCompactOutput.php @@ -89,6 +89,8 @@ public static function hygiene_report( array $report ): array { 'worktree_status_mode' => $report['worktree_status_mode'] ?? null, 'locks' => isset( $report['locks'] ) ? self::lock_result( (array) $report['locks'] ) : null, 'cleanup' => array( + 'blocker_probe_source' => $cleanup['blocker_probe_source'] ?? null, + 'blocker_counts' => $cleanup['blocker_counts'] ?? null, 'summary' => (array) ( $cleanup['summary'] ?? array() ), 'biggest_candidates' => self::compact_rows( (array) ( $cleanup['biggest_candidates'] ?? array() ) ), ), diff --git a/inc/Workspace/WorkspaceHygieneReport.php b/inc/Workspace/WorkspaceHygieneReport.php index be7696f..4fd1ece 100644 --- a/inc/Workspace/WorkspaceHygieneReport.php +++ b/inc/Workspace/WorkspaceHygieneReport.php @@ -577,27 +577,33 @@ private function build_workspace_disk_report(): array { */ private function summarize_workspace_worktrees( array $worktrees, ?array $cleanup ): array { $summary = array( - 'total' => count($worktrees), - 'primaries' => 0, - 'worktrees' => 0, - 'artifacts' => 0, - 'external' => 0, - 'dirty' => 0, - 'protected_dirty' => 0, - 'protected_unpushed' => 0, - 'missing_metadata' => 0, - 'stale_primaries' => 0, - 'primary_freshness_by_status' => array(), - 'primary_freshness_attention' => array(), - 'base_branch_worktree_count' => 0, - 'base_branch_worktrees' => array(), - 'by_liveness' => array( + 'total' => count($worktrees), + 'primaries' => 0, + 'worktrees' => 0, + 'artifacts' => 0, + 'external' => 0, + 'dirty' => 0, + 'inventory_known_dirty' => 0, + 'protected_dirty' => 0, + 'protected_unpushed' => 0, + 'protected_dirty_inventory_known' => 0, + 'protected_unpushed_inventory_known' => 0, + 'protected_dirty_fresh_probed' => 0, + 'protected_unpushed_fresh_probed' => 0, + 'protected_count_probe_source' => 'none', + 'missing_metadata' => 0, + 'stale_primaries' => 0, + 'primary_freshness_by_status' => array(), + 'primary_freshness_attention' => array(), + 'base_branch_worktree_count' => 0, + 'base_branch_worktrees' => array(), + 'by_liveness' => array( WorktreeContextInjector::LIVENESS_LIVE => 0, WorktreeContextInjector::LIVENESS_STOPPED => 0, WorktreeContextInjector::LIVENESS_STALE => 0, WorktreeContextInjector::LIVENESS_UNKNOWN => 0, ), - 'duplicate_task_groups' => 0, + 'duplicate_task_groups' => 0, ); foreach ( $worktrees as $row ) { @@ -632,6 +638,7 @@ private function summarize_workspace_worktrees( array $worktrees, ?array $cleanu if ( (int) ( $row['dirty'] ?? 0 ) > 0 ) { ++$summary['dirty']; + ++$summary['inventory_known_dirty']; } if ( ! empty($row['missing_metadata']) ) { @@ -657,11 +664,23 @@ private function summarize_workspace_worktrees( array $worktrees, ?array $cleanu $summary['duplicates'] = $duplicates; if ( null !== $cleanup ) { - $by_reason = (array) ( $cleanup['summary']['skipped_by_reason'] ?? array() ); - $summary['protected_dirty'] = (int) ( $by_reason['dirty_worktree'] ?? 0 ); - $summary['protected_unpushed'] = (int) ( $by_reason['unpushed_commits'] ?? 0 ); - $summary['missing_metadata'] = (int) ( $by_reason['missing_metadata'] ?? 0 ); - $summary['external'] = max($summary['external'], (int) ( $by_reason['external_worktree'] ?? 0 )); + $by_reason = (array) ( $cleanup['summary']['skipped_by_reason'] ?? array() ); + $dirty_blockers = (int) ( $by_reason['dirty_worktree'] ?? 0 ); + $unpushed_blockers = (int) ( $by_reason['unpushed_commits'] ?? 0 ); + $summary['protected_dirty'] = $dirty_blockers; + $summary['protected_unpushed'] = $unpushed_blockers; + $summary['missing_metadata'] = (int) ( $by_reason['missing_metadata'] ?? 0 ); + $summary['external'] = max($summary['external'], (int) ( $by_reason['external_worktree'] ?? 0 )); + + $probe_source = ! empty($cleanup['inventory_only']) ? 'inventory_known' : 'fresh_probe'; + $summary['protected_count_probe_source'] = $probe_source; + if ( 'fresh_probe' === $probe_source ) { + $summary['protected_dirty_fresh_probed'] = $dirty_blockers; + $summary['protected_unpushed_fresh_probed'] = $unpushed_blockers; + } else { + $summary['protected_dirty_inventory_known'] = $dirty_blockers; + $summary['protected_unpushed_inventory_known'] = $unpushed_blockers; + } } return $summary; @@ -702,15 +721,32 @@ private function summarize_workspace_cleanup( ?array $cleanup, ?array $error, ar } unset($candidate); usort($candidates, fn( $a, $b ) => (int) ( $b['size_bytes'] ?? 0 ) <=> (int) ( $a['size_bytes'] ?? 0 )); + $summary = (array) ( $cleanup['summary'] ?? array() ); + $skipped_reason = (array) ( $summary['skipped_by_reason'] ?? array() ); + $probe_source = ! empty($cleanup['inventory_only']) ? 'inventory_known' : 'fresh_probe'; + $empty_blockers = array( + 'dirty_worktree' => 0, + 'unpushed_commits' => 0, + ); + $blocker_counts = array( + 'inventory_known' => $empty_blockers, + 'fresh_probe' => $empty_blockers, + ); + $blocker_counts[ $probe_source ] = array( + 'dirty_worktree' => (int) ( $skipped_reason['dirty_worktree'] ?? 0 ), + 'unpushed_commits' => (int) ( $skipped_reason['unpushed_commits'] ?? 0 ), + ); return array( 'included' => true, 'dry_run' => true, 'skip_github' => true, 'inventory_only' => ! empty($cleanup['inventory_only']), - 'summary' => $cleanup['summary'] ?? array(), + 'blocker_probe_source' => $probe_source, + 'blocker_counts' => $blocker_counts, + 'summary' => $summary, 'biggest_candidates' => array_slice($candidates, 0, 10), - 'skipped_by_reason' => $cleanup['summary']['skipped_by_reason'] ?? array(), - 'candidates_by_signal' => $cleanup['summary']['candidates_by_signal'] ?? array(), + 'skipped_by_reason' => $skipped_reason, + 'candidates_by_signal' => $summary['candidates_by_signal'] ?? array(), ); } @@ -732,6 +768,9 @@ private function build_workspace_fast_stats( array $worktrees, ?array $cleanup, 'cleanup_eligible_unprobed_count' => count($cleanup_candidates), 'valid_clean_count' => 0, 'valid_dirty_count' => 0, + 'inventory_known_dirty_count' => 0, + 'inventory_known_blocker_count' => 0, + 'fresh_probed_blocker_count' => 0, 'invalid_broken_orphan_count' => 0, 'unmanaged_skipped_count' => 0, 'dirty_probe_skipped_count' => 0, @@ -774,12 +813,18 @@ private function build_workspace_fast_stats( array $worktrees, ?array $cleanup, if ( (int) $dirty > 0 ) { ++$counts['valid_dirty_count']; + ++$counts['inventory_known_dirty_count']; } else { ++$counts['valid_clean_count']; } } $blocked_dirty = (int) ( $cleanup_summary['skipped_by_reason']['dirty_worktree'] ?? 0 ) + (int) ( $cleanup_summary['skipped_by_reason']['unpushed_commits'] ?? 0 ); + if ( ! empty($cleanup['inventory_only']) ) { + $counts['inventory_known_blocker_count'] = $blocked_dirty; + } else { + $counts['fresh_probed_blocker_count'] = $blocked_dirty; + } if ( $blocked_dirty > $counts['valid_dirty_count'] ) { $counts['valid_dirty_count'] = $blocked_dirty; } diff --git a/tests/workspace-compact-output.php b/tests/workspace-compact-output.php index 977b2df..a6559ff 100644 --- a/tests/workspace-compact-output.php +++ b/tests/workspace-compact-output.php @@ -167,12 +167,35 @@ function compact_output_large_rows( int $count ): array { 'counts' => array( 'cleanup_eligible_unprobed_count' => 40, 'dirty_probe_skipped_count' => 40, + 'inventory_known_dirty_count' => 20, + 'inventory_known_blocker_count' => 3, + 'fresh_probed_blocker_count' => 0, ), 'safety_probe_status' => 'not_run_inventory_only', ), - 'worktrees' => array( 'worktrees' => 40, 'protected_dirty' => 20 ), + 'worktrees' => array( + 'worktrees' => 40, + 'inventory_known_dirty' => 20, + 'protected_dirty' => 3, + 'protected_dirty_inventory_known' => 3, + 'protected_dirty_fresh_probed' => 0, + 'protected_unpushed_inventory_known' => 0, + 'protected_unpushed_fresh_probed' => 0, + 'protected_count_probe_source' => 'inventory_known', + ), 'locks' => array( 'active' => 2, 'stale' => 40, 'database' => array( 'locks' => $large_rows ) ), 'cleanup' => array( + 'blocker_probe_source' => 'inventory_known', + 'blocker_counts' => array( + 'inventory_known' => array( + 'dirty_worktree' => 3, + 'unpushed_commits' => 0, + ), + 'fresh_probe' => array( + 'dirty_worktree' => 0, + 'unpushed_commits' => 0, + ), + ), 'summary' => array( 'would_remove' => 40, 'artifact_size_bytes' => 654321, @@ -195,6 +218,15 @@ function compact_output_large_rows( int $count ): array { compact_output_assert(40 === ( $hygiene['worktrees']['worktrees'] ?? null ), 'Compact hygiene output must preserve worktree counts.'); compact_output_assert(40 === ( $hygiene['fast_stats']['counts']['cleanup_eligible_unprobed_count'] ?? null ), 'Compact hygiene output must label cheap cleanup candidates as unprobed.'); compact_output_assert(! isset($hygiene['fast_stats']['counts']['safe_removable_count']), 'Compact hygiene output must not expose misleading safe_removable_count for cheap inventory.'); +compact_output_assert(20 === ( $hygiene['fast_stats']['counts']['inventory_known_dirty_count'] ?? null ), 'Compact hygiene output must preserve inventory-known dirty counts.'); +compact_output_assert(3 === ( $hygiene['fast_stats']['counts']['inventory_known_blocker_count'] ?? null ), 'Compact hygiene output must preserve inventory-known blocker counts.'); +compact_output_assert(0 === ( $hygiene['fast_stats']['counts']['fresh_probed_blocker_count'] ?? null ), 'Compact hygiene output must distinguish fresh-probed blocker counts.'); +compact_output_assert(20 === ( $hygiene['worktrees']['inventory_known_dirty'] ?? null ), 'Compact hygiene output must label inventory-known dirty worktrees.'); +compact_output_assert(3 === ( $hygiene['worktrees']['protected_dirty_inventory_known'] ?? null ), 'Compact hygiene output must label inventory-known dirty blockers.'); +compact_output_assert(0 === ( $hygiene['worktrees']['protected_dirty_fresh_probed'] ?? null ), 'Compact hygiene output must label fresh-probed dirty blockers separately.'); +compact_output_assert('inventory_known' === ( $hygiene['cleanup']['blocker_probe_source'] ?? null ), 'Compact hygiene output must expose blocker probe source.'); +compact_output_assert(3 === ( $hygiene['cleanup']['blocker_counts']['inventory_known']['dirty_worktree'] ?? null ), 'Compact hygiene output must preserve inventory-known dirty blocker bucket.'); +compact_output_assert(0 === ( $hygiene['cleanup']['blocker_counts']['fresh_probe']['dirty_worktree'] ?? null ), 'Compact hygiene output must preserve fresh-probed dirty blocker bucket.'); compact_output_assert(40 === ( $hygiene['cleanup']['summary']['cleanup_buckets']['cleanup_eligible_pending_revalidation'] ?? null ), 'Compact cleanup summary must preserve pending-revalidation bucket.'); compact_output_assert(0 === ( $hygiene['cleanup']['summary']['cleanup_buckets']['safe_to_remove_now'] ?? null ), 'Compact cleanup summary must not mark unprobed inventory candidates safe.'); compact_output_assert(123456 === ( $hygiene['size']['total_bytes'] ?? null ), 'Compact hygiene output must preserve size bytes.');