diff --git a/crates/compass-output/src/report.rs b/crates/compass-output/src/report.rs index b0f8d31cc..4c4f27d6c 100644 --- a/crates/compass-output/src/report.rs +++ b/crates/compass-output/src/report.rs @@ -443,8 +443,13 @@ pub fn agent_orientation( let node_communities = invert_communities(communities); let graph = ReportGraph::new(document, &node_communities); let cycle_probe = find_import_cycles(document, 5, DETAIL_LIMIT.saturating_add(1)); - let (community_models, community_total) = - build_communities(&graph, communities, cohesion_scores, community_labels); + let (community_models, community_total) = build_communities( + &graph, + communities, + cohesion_scores, + community_labels, + options.min_community_size, + ); let hub_total = god_node_list.len(); let hubs = god_node_list .iter() @@ -992,6 +997,7 @@ fn build_communities( communities: &Communities, cohesion_scores: &BTreeMap, labels: &BTreeMap, + min_community_size: usize, ) -> (Vec, usize) { let mut eligible = communities .iter() @@ -1009,7 +1015,12 @@ fn build_communities( }) .collect::>(); let total = eligible.len(); - eligible.retain(|(_, document_table_only, _)| !document_table_only); + // This threshold only bounds the architecture report. The graph document + // and its source-backed community assignments remain complete and + // queryable, including singleton communities. + eligible.retain(|(_, document_table_only, real)| { + !document_table_only && real.len() >= min_community_size + }); eligible.sort_by(|(left_id, _, left_members), (right_id, _, right_members)| { right_members .len() diff --git a/crates/compass-output/tests/orientation.rs b/crates/compass-output/tests/orientation.rs index f14390816..20d6bb3cb 100644 --- a/crates/compass-output/tests/orientation.rs +++ b/crates/compass-output/tests/orientation.rs @@ -375,6 +375,79 @@ fn report_is_a_label_first_directory_of_all_bounded_communities() -> Result<(), Ok(()) } +#[test] +fn report_minimum_community_size_omits_singletons_without_changing_graph_totals() +-> Result<(), Box> { + let document: GraphDocument = serde_json::from_value(json!({ + "directed": true, + "graph": {}, + "nodes": [ + { + "id": "connected-a", + "label": "Connected A", + "source_file": "src/connected.rs", + "file_type": "code" + }, + { + "id": "connected-b", + "label": "Connected B", + "source_file": "src/connected.rs", + "file_type": "code" + }, + { + "id": "isolated", + "label": "Isolated", + "source_file": "src/isolated.rs", + "file_type": "code" + } + ], + "links": [ + {"source": "connected-a", "target": "connected-b", "relation": "calls"} + ] + }))?; + let communities = BTreeMap::from([ + (0, vec!["connected-a".to_owned(), "connected-b".to_owned()]), + (1, vec!["isolated".to_owned()]), + ]); + let labels = BTreeMap::from([ + (0, "Connected subsystem".to_owned()), + (1, "Isolated singleton".to_owned()), + ]); + let mut options = ReportOptions::new("minimum-community-size"); + options.min_community_size = 2; + + let model = agent_orientation( + &document, + &communities, + &BTreeMap::new(), + &labels, + &[], + &[], + &DetectionSummary::default(), + TokenCost::default(), + None, + None, + &options, + ); + + assert_eq!(model.graph_summary.nodes, 3); + assert_eq!(model.graph_summary.edges, 1); + assert_eq!(model.graph_summary.communities, 2); + assert_eq!(model.communities.len(), 1); + assert_eq!(model.communities[0].id, 0); + assert_eq!( + model.omissions.communities, + compass_output::SectionOmission { + total: 2, + shown: 1, + omitted: 1, + } + ); + assert_eq!(document.nodes.len(), 3); + assert_eq!(communities.get(&1), Some(&vec!["isolated".to_owned()])); + Ok(()) +} + #[test] fn report_omits_pipe_table_only_communities_from_architecture_directory() -> Result<(), Box> { diff --git a/docs/reference/commands.md b/docs/reference/commands.md index ec77a1dbd..bb26e4414 100644 --- a/docs/reference/commands.md +++ b/docs/reference/commands.md @@ -241,6 +241,11 @@ compass label [PATH] [--timing] ``` +`--min-community-size` controls which communities are presented for labeling +and in the bounded architecture report. It does not remove nodes, edges, or +community assignments from the graph; omitted communities remain queryable and +are included in the report's coverage disclosure. The default is `3`. + ## Read and query ### `query`