Skip to main content

code_ranker_graph/
lib.rs

1//! Operations over the generic property-graph model defined in
2//! `code-ranker-plugin-api`: cycle detection, Henry-Kafura coupling, aggregate
3//! stats, id relativization, and the serializable [`Snapshot`] artifact.
4//!
5//! Everything here is language-agnostic. Plugins emit a pure
6//! [`api::Graph`](code_ranker_plugin_api::graph::Graph) (structure only); this crate
7//! and the orchestrator enrich it (writing computed values into node `attrs`
8//! by id) and assemble the snapshot. Which edge kinds count as information
9//! flow is read from the level's `edge_kinds` (`EdgeKindSpec.flow`), passed in
10//! as a `flow_kinds` set — there is no hardcoded `uses`/`contains` knowledge.
11
12pub mod attrs;
13pub mod cycles;
14pub mod finalize;
15pub mod hk;
16pub mod level_graph;
17pub mod metrics;
18pub mod relativize;
19pub mod serialize;
20pub mod snapshot;
21pub mod stats;
22
23pub use attrs::{num_attr, round_sig3};
24pub use cycles::annotate_cycles;
25pub use finalize::finalize_graph;
26pub use hk::annotate_hk;
27pub use level_graph::{CycleGroup, LevelGraph, LevelUi};
28pub use metrics::{FileMetrics, metric_specs, write_metrics};
29pub use relativize::{relativize_graph, relativize_level};
30pub use serialize::{to_canonical_string, to_canonical_string_pretty};
31pub use snapshot::{GitInfo, Snapshot, StageTime};
32pub use stats::compute_stats;
33
34use code_ranker_plugin_api::{
35    attrs::ValueType,
36    level::{AttributeGroup, AttributeSpec, Direction, SpecRow, attr_dict, group},
37};
38use std::collections::BTreeMap;
39
40/// The coupling/cycle attribute dictionary produced by [`annotate_hk`](hk::annotate_hk) /
41/// [`annotate_cycles`](cycles::annotate_cycles), plus the `coupling` group. The orchestrator merges these
42/// into each level's `node_attributes` / `attribute_groups`.
43pub fn coupling_specs() -> (
44    BTreeMap<String, AttributeSpec>,
45    BTreeMap<String, AttributeGroup>,
46) {
47    let specs = attr_dict(vec![
48        // No direction: raw fan-in is neutral — broad reuse (good) and bottleneck
49        // risk (bad) pull opposite ways, so a growing/shrinking count carries no
50        // clear verdict.
51        (
52            "fan_in",
53            SpecRow {
54                group: "coupling",
55                label: "Fan-in",
56                name: "Fan-in",
57                short: "Fan-in",
58                description: "Number of nodes that depend on this one. High fan-in means broadly reused.",
59                ..Default::default()
60            },
61        ),
62        // Also neutral: like fan-in, high fan-out is dual — a tangled unit (bad) or
63        // a legitimate coordinator/composition root (fine). The directional coupling
64        // signal lives in `hk`, which already folds in fan_out.
65        (
66            "fan_out",
67            SpecRow {
68                group: "coupling",
69                label: "Fan-out",
70                name: "Fan-out",
71                short: "Fan-out",
72                description: "Number of nodes this one depends on. High fan-out means many \
73                              dependencies. External-library edges are counted separately.",
74                ..Default::default()
75            },
76        ),
77        (
78            "fan_out_external",
79            SpecRow {
80                group: "coupling",
81                label: "Fan-out (external)",
82                description: "Number of distinct external libraries this node depends on.",
83                ..Default::default()
84            },
85        ),
86        (
87            "hk",
88            SpecRow {
89                group: "coupling",
90                value_type: ValueType::Float,
91                label: "HK",
92                name: "Henry–Kafura",
93                short: "HK",
94                description: "Henry–Kafura — combines unit size with incoming/outgoing coupling \
95                              (internal edges only).",
96                formula: "sloc × (fan_in × fan_out)²",
97                calc: "sloc * (fan_in * fan_out) ** 2",
98                direction: Direction::LowerBetter,
99                abbreviate: true,
100                ..Default::default()
101            },
102        ),
103        (
104            "cycle",
105            SpecRow {
106                value_type: ValueType::Str,
107                label: "Cycle",
108                short: "Cycle",
109                description: "Cycle kind this node participates in.",
110                ..Default::default()
111            },
112        ),
113    ]);
114
115    let mut groups = BTreeMap::new();
116    groups.insert(
117        "coupling".to_string(),
118        group("Coupling", "Internal coupling (Henry-Kafura)"),
119    );
120    (specs, groups)
121}