Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 20 additions & 4 deletions inc/Cli/Commands/WorkspaceCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
2 changes: 2 additions & 0 deletions inc/Cli/WorkspaceCompactOutput.php
Original file line number Diff line number Diff line change
Expand Up @@ -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() ) ),
),
Expand Down
93 changes: 69 additions & 24 deletions inc/Workspace/WorkspaceHygieneReport.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 ) {
Expand Down Expand Up @@ -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']) ) {
Expand All @@ -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;
Expand Down Expand Up @@ -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(),
);
}

Expand All @@ -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,
Expand Down Expand Up @@ -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;
}
Expand Down
34 changes: 33 additions & 1 deletion tests/workspace-compact-output.php
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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.');
Expand Down
Loading