Skip to content

Commit f6c7105

Browse files
authored
Merge pull request #830 from Extra-Chill/issue-825-hygiene-counts
Clarify hygiene inventory blocker counts
2 parents af0f74d + 9a65010 commit f6c7105

4 files changed

Lines changed: 124 additions & 29 deletions

File tree

inc/Cli/Commands/WorkspaceCommand.php

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4766,12 +4766,28 @@ private function render_workspace_hygiene_report( array $report, array $assoc_ar
47664766
'value' => (string) ( $worktrees['artifacts'] ?? 0 ),
47674767
),
47684768
array(
4769-
'metric' => 'dirty_protected',
4770-
'value' => (string) ( $worktrees['protected_dirty'] ?? 0 ),
4769+
'metric' => 'inventory_known_dirty',
4770+
'value' => (string) ( $worktrees['inventory_known_dirty'] ?? 0 ),
47714771
),
47724772
array(
4773-
'metric' => 'unpushed_protected',
4774-
'value' => (string) ( $worktrees['protected_unpushed'] ?? 0 ),
4773+
'metric' => 'inventory_known_dirty_blockers',
4774+
'value' => (string) ( $worktrees['protected_dirty_inventory_known'] ?? 0 ),
4775+
),
4776+
array(
4777+
'metric' => 'inventory_known_unpushed_blockers',
4778+
'value' => (string) ( $worktrees['protected_unpushed_inventory_known'] ?? 0 ),
4779+
),
4780+
array(
4781+
'metric' => 'fresh_probed_dirty_blockers',
4782+
'value' => (string) ( $worktrees['protected_dirty_fresh_probed'] ?? 0 ),
4783+
),
4784+
array(
4785+
'metric' => 'fresh_probed_unpushed_blockers',
4786+
'value' => (string) ( $worktrees['protected_unpushed_fresh_probed'] ?? 0 ),
4787+
),
4788+
array(
4789+
'metric' => 'blocker_probe_source',
4790+
'value' => (string) ( $worktrees['protected_count_probe_source'] ?? 'none' ),
47754791
),
47764792
array(
47774793
'metric' => 'missing_metadata',

inc/Cli/WorkspaceCompactOutput.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,8 @@ public static function hygiene_report( array $report ): array {
9393
'worktree_status_mode' => $report['worktree_status_mode'] ?? null,
9494
'locks' => isset( $report['locks'] ) ? self::lock_result( (array) $report['locks'] ) : null,
9595
'cleanup' => array(
96+
'blocker_probe_source' => $cleanup['blocker_probe_source'] ?? null,
97+
'blocker_counts' => $cleanup['blocker_counts'] ?? null,
9698
'summary' => (array) ( $cleanup['summary'] ?? array() ),
9799
'biggest_candidates' => self::compact_rows( (array) ( $cleanup['biggest_candidates'] ?? array() ) ),
98100
),

inc/Workspace/WorkspaceHygieneReport.php

Lines changed: 69 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -577,27 +577,33 @@ private function build_workspace_disk_report(): array {
577577
*/
578578
private function summarize_workspace_worktrees( array $worktrees, ?array $cleanup ): array {
579579
$summary = array(
580-
'total' => count($worktrees),
581-
'primaries' => 0,
582-
'worktrees' => 0,
583-
'artifacts' => 0,
584-
'external' => 0,
585-
'dirty' => 0,
586-
'protected_dirty' => 0,
587-
'protected_unpushed' => 0,
588-
'missing_metadata' => 0,
589-
'stale_primaries' => 0,
590-
'primary_freshness_by_status' => array(),
591-
'primary_freshness_attention' => array(),
592-
'base_branch_worktree_count' => 0,
593-
'base_branch_worktrees' => array(),
594-
'by_liveness' => array(
580+
'total' => count($worktrees),
581+
'primaries' => 0,
582+
'worktrees' => 0,
583+
'artifacts' => 0,
584+
'external' => 0,
585+
'dirty' => 0,
586+
'inventory_known_dirty' => 0,
587+
'protected_dirty' => 0,
588+
'protected_unpushed' => 0,
589+
'protected_dirty_inventory_known' => 0,
590+
'protected_unpushed_inventory_known' => 0,
591+
'protected_dirty_fresh_probed' => 0,
592+
'protected_unpushed_fresh_probed' => 0,
593+
'protected_count_probe_source' => 'none',
594+
'missing_metadata' => 0,
595+
'stale_primaries' => 0,
596+
'primary_freshness_by_status' => array(),
597+
'primary_freshness_attention' => array(),
598+
'base_branch_worktree_count' => 0,
599+
'base_branch_worktrees' => array(),
600+
'by_liveness' => array(
595601
WorktreeContextInjector::LIVENESS_LIVE => 0,
596602
WorktreeContextInjector::LIVENESS_STOPPED => 0,
597603
WorktreeContextInjector::LIVENESS_STALE => 0,
598604
WorktreeContextInjector::LIVENESS_UNKNOWN => 0,
599605
),
600-
'duplicate_task_groups' => 0,
606+
'duplicate_task_groups' => 0,
601607
);
602608

603609
foreach ( $worktrees as $row ) {
@@ -632,6 +638,7 @@ private function summarize_workspace_worktrees( array $worktrees, ?array $cleanu
632638

633639
if ( (int) ( $row['dirty'] ?? 0 ) > 0 ) {
634640
++$summary['dirty'];
641+
++$summary['inventory_known_dirty'];
635642
}
636643

637644
if ( ! empty($row['missing_metadata']) ) {
@@ -657,11 +664,23 @@ private function summarize_workspace_worktrees( array $worktrees, ?array $cleanu
657664
$summary['duplicates'] = $duplicates;
658665

659666
if ( null !== $cleanup ) {
660-
$by_reason = (array) ( $cleanup['summary']['skipped_by_reason'] ?? array() );
661-
$summary['protected_dirty'] = (int) ( $by_reason['dirty_worktree'] ?? 0 );
662-
$summary['protected_unpushed'] = (int) ( $by_reason['unpushed_commits'] ?? 0 );
663-
$summary['missing_metadata'] = (int) ( $by_reason['missing_metadata'] ?? 0 );
664-
$summary['external'] = max($summary['external'], (int) ( $by_reason['external_worktree'] ?? 0 ));
667+
$by_reason = (array) ( $cleanup['summary']['skipped_by_reason'] ?? array() );
668+
$dirty_blockers = (int) ( $by_reason['dirty_worktree'] ?? 0 );
669+
$unpushed_blockers = (int) ( $by_reason['unpushed_commits'] ?? 0 );
670+
$summary['protected_dirty'] = $dirty_blockers;
671+
$summary['protected_unpushed'] = $unpushed_blockers;
672+
$summary['missing_metadata'] = (int) ( $by_reason['missing_metadata'] ?? 0 );
673+
$summary['external'] = max($summary['external'], (int) ( $by_reason['external_worktree'] ?? 0 ));
674+
675+
$probe_source = ! empty($cleanup['inventory_only']) ? 'inventory_known' : 'fresh_probe';
676+
$summary['protected_count_probe_source'] = $probe_source;
677+
if ( 'fresh_probe' === $probe_source ) {
678+
$summary['protected_dirty_fresh_probed'] = $dirty_blockers;
679+
$summary['protected_unpushed_fresh_probed'] = $unpushed_blockers;
680+
} else {
681+
$summary['protected_dirty_inventory_known'] = $dirty_blockers;
682+
$summary['protected_unpushed_inventory_known'] = $unpushed_blockers;
683+
}
665684
}
666685

667686
return $summary;
@@ -702,15 +721,32 @@ private function summarize_workspace_cleanup( ?array $cleanup, ?array $error, ar
702721
}
703722
unset($candidate);
704723
usort($candidates, fn( $a, $b ) => (int) ( $b['size_bytes'] ?? 0 ) <=> (int) ( $a['size_bytes'] ?? 0 ));
724+
$summary = (array) ( $cleanup['summary'] ?? array() );
725+
$skipped_reason = (array) ( $summary['skipped_by_reason'] ?? array() );
726+
$probe_source = ! empty($cleanup['inventory_only']) ? 'inventory_known' : 'fresh_probe';
727+
$empty_blockers = array(
728+
'dirty_worktree' => 0,
729+
'unpushed_commits' => 0,
730+
);
731+
$blocker_counts = array(
732+
'inventory_known' => $empty_blockers,
733+
'fresh_probe' => $empty_blockers,
734+
);
735+
$blocker_counts[ $probe_source ] = array(
736+
'dirty_worktree' => (int) ( $skipped_reason['dirty_worktree'] ?? 0 ),
737+
'unpushed_commits' => (int) ( $skipped_reason['unpushed_commits'] ?? 0 ),
738+
);
705739
return array(
706740
'included' => true,
707741
'dry_run' => true,
708742
'skip_github' => true,
709743
'inventory_only' => ! empty($cleanup['inventory_only']),
710-
'summary' => $cleanup['summary'] ?? array(),
744+
'blocker_probe_source' => $probe_source,
745+
'blocker_counts' => $blocker_counts,
746+
'summary' => $summary,
711747
'biggest_candidates' => array_slice($candidates, 0, 10),
712-
'skipped_by_reason' => $cleanup['summary']['skipped_by_reason'] ?? array(),
713-
'candidates_by_signal' => $cleanup['summary']['candidates_by_signal'] ?? array(),
748+
'skipped_by_reason' => $skipped_reason,
749+
'candidates_by_signal' => $summary['candidates_by_signal'] ?? array(),
714750
);
715751
}
716752

@@ -732,6 +768,9 @@ private function build_workspace_fast_stats( array $worktrees, ?array $cleanup,
732768
'cleanup_eligible_unprobed_count' => count($cleanup_candidates),
733769
'valid_clean_count' => 0,
734770
'valid_dirty_count' => 0,
771+
'inventory_known_dirty_count' => 0,
772+
'inventory_known_blocker_count' => 0,
773+
'fresh_probed_blocker_count' => 0,
735774
'invalid_broken_orphan_count' => 0,
736775
'unmanaged_skipped_count' => 0,
737776
'dirty_probe_skipped_count' => 0,
@@ -774,12 +813,18 @@ private function build_workspace_fast_stats( array $worktrees, ?array $cleanup,
774813

775814
if ( (int) $dirty > 0 ) {
776815
++$counts['valid_dirty_count'];
816+
++$counts['inventory_known_dirty_count'];
777817
} else {
778818
++$counts['valid_clean_count'];
779819
}
780820
}
781821

782822
$blocked_dirty = (int) ( $cleanup_summary['skipped_by_reason']['dirty_worktree'] ?? 0 ) + (int) ( $cleanup_summary['skipped_by_reason']['unpushed_commits'] ?? 0 );
823+
if ( ! empty($cleanup['inventory_only']) ) {
824+
$counts['inventory_known_blocker_count'] = $blocked_dirty;
825+
} else {
826+
$counts['fresh_probed_blocker_count'] = $blocked_dirty;
827+
}
783828
if ( $blocked_dirty > $counts['valid_dirty_count'] ) {
784829
$counts['valid_dirty_count'] = $blocked_dirty;
785830
}

tests/workspace-compact-output.php

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,12 +170,35 @@ function compact_output_large_rows( int $count ): array {
170170
'counts' => array(
171171
'cleanup_eligible_unprobed_count' => 40,
172172
'dirty_probe_skipped_count' => 40,
173+
'inventory_known_dirty_count' => 20,
174+
'inventory_known_blocker_count' => 3,
175+
'fresh_probed_blocker_count' => 0,
173176
),
174177
'safety_probe_status' => 'not_run_inventory_only',
175178
),
176-
'worktrees' => array( 'worktrees' => 40, 'protected_dirty' => 20 ),
179+
'worktrees' => array(
180+
'worktrees' => 40,
181+
'inventory_known_dirty' => 20,
182+
'protected_dirty' => 3,
183+
'protected_dirty_inventory_known' => 3,
184+
'protected_dirty_fresh_probed' => 0,
185+
'protected_unpushed_inventory_known' => 0,
186+
'protected_unpushed_fresh_probed' => 0,
187+
'protected_count_probe_source' => 'inventory_known',
188+
),
177189
'locks' => array( 'active' => 2, 'stale' => 40, 'database' => array( 'locks' => $large_rows ) ),
178190
'cleanup' => array(
191+
'blocker_probe_source' => 'inventory_known',
192+
'blocker_counts' => array(
193+
'inventory_known' => array(
194+
'dirty_worktree' => 3,
195+
'unpushed_commits' => 0,
196+
),
197+
'fresh_probe' => array(
198+
'dirty_worktree' => 0,
199+
'unpushed_commits' => 0,
200+
),
201+
),
179202
'summary' => array(
180203
'would_remove' => 40,
181204
'artifact_size_bytes' => 654321,
@@ -198,6 +221,15 @@ function compact_output_large_rows( int $count ): array {
198221
compact_output_assert(40 === ( $hygiene['worktrees']['worktrees'] ?? null ), 'Compact hygiene output must preserve worktree counts.');
199222
compact_output_assert(40 === ( $hygiene['fast_stats']['counts']['cleanup_eligible_unprobed_count'] ?? null ), 'Compact hygiene output must label cheap cleanup candidates as unprobed.');
200223
compact_output_assert(! isset($hygiene['fast_stats']['counts']['safe_removable_count']), 'Compact hygiene output must not expose misleading safe_removable_count for cheap inventory.');
224+
compact_output_assert(20 === ( $hygiene['fast_stats']['counts']['inventory_known_dirty_count'] ?? null ), 'Compact hygiene output must preserve inventory-known dirty counts.');
225+
compact_output_assert(3 === ( $hygiene['fast_stats']['counts']['inventory_known_blocker_count'] ?? null ), 'Compact hygiene output must preserve inventory-known blocker counts.');
226+
compact_output_assert(0 === ( $hygiene['fast_stats']['counts']['fresh_probed_blocker_count'] ?? null ), 'Compact hygiene output must distinguish fresh-probed blocker counts.');
227+
compact_output_assert(20 === ( $hygiene['worktrees']['inventory_known_dirty'] ?? null ), 'Compact hygiene output must label inventory-known dirty worktrees.');
228+
compact_output_assert(3 === ( $hygiene['worktrees']['protected_dirty_inventory_known'] ?? null ), 'Compact hygiene output must label inventory-known dirty blockers.');
229+
compact_output_assert(0 === ( $hygiene['worktrees']['protected_dirty_fresh_probed'] ?? null ), 'Compact hygiene output must label fresh-probed dirty blockers separately.');
230+
compact_output_assert('inventory_known' === ( $hygiene['cleanup']['blocker_probe_source'] ?? null ), 'Compact hygiene output must expose blocker probe source.');
231+
compact_output_assert(3 === ( $hygiene['cleanup']['blocker_counts']['inventory_known']['dirty_worktree'] ?? null ), 'Compact hygiene output must preserve inventory-known dirty blocker bucket.');
232+
compact_output_assert(0 === ( $hygiene['cleanup']['blocker_counts']['fresh_probe']['dirty_worktree'] ?? null ), 'Compact hygiene output must preserve fresh-probed dirty blocker bucket.');
201233
compact_output_assert(40 === ( $hygiene['cleanup']['summary']['cleanup_buckets']['cleanup_eligible_pending_revalidation'] ?? null ), 'Compact cleanup summary must preserve pending-revalidation bucket.');
202234
compact_output_assert(0 === ( $hygiene['cleanup']['summary']['cleanup_buckets']['safe_to_remove_now'] ?? null ), 'Compact cleanup summary must not mark unprobed inventory candidates safe.');
203235
compact_output_assert(123456 === ( $hygiene['size']['total_bytes'] ?? null ), 'Compact hygiene output must preserve size bytes.');

0 commit comments

Comments
 (0)