Documentation
¶
Overview ¶
Package modularity implements the modularity metrics (blast_radius, change_amplification, hidden_coupling, structural_weight, cohesion_lcom) and the functional_candidates metric. All are report-only (band "info"). The shared module-graph/history helpers live in internal/metrics/internal/modgraph.
Index ¶
- func ComputeInstability(g *graph.Graph) map[string]float64
- func ComputePropagation(g *graph.Graph) (reach map[string]int, n int)
- func UnstableDependency(from, to string, instability map[string]float64) bool
- type AbstractnessMetric
- type BlastRadiusMetric
- type ChangeAmplificationMetric
- type ChangeCouplingMetric
- type CohesionMetric
- type FunctionalCandidatesMetric
- type HiddenCouplingMetric
- type InstabilityMetric
- type MartinDistanceMetric
- type PropagationCostMetric
- type StructuralWeightMetric
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func ComputeInstability ¶ added in v0.3.0
ComputeInstability returns per-module instability I=Ce/(Ca+Ce) from the graph. Returns nil when graph is nil. First-party modules only (same as BlastRadius).
func ComputePropagation ¶ added in v0.3.0
ComputePropagation returns, for each first-party module, the count of other first-party modules that are transitively reachable from it in the forward direction ("how many modules can this one cascade into?"). It also returns the total number of first-party modules.
System-level PC = sum(reach[m]) / (n*(n-1)). Per-module fraction = reach[m] / (n-1).
Only first-party modules (those present as graph nodes) are counted. Self-loops and duplicate edges are collapsed before the BFS.
func UnstableDependency ¶ added in v0.3.0
UnstableDependency reports whether the edge from→to is an unstable dependency (Designite smell): I(to) > I(from), meaning the caller depends on a module that is itself more dependent on others than it is depended upon. instability must be precomputed by ComputeInstability. Returns false when either module is absent from the instability map (unknown → conservative).
Types ¶
type AbstractnessMetric ¶ added in v0.3.0
type AbstractnessMetric struct{}
AbstractnessMetric reports per-module abstractness A = contract_inbound / (contract_inbound + concrete_inbound). This is a proxy derived from coupling classifications (strength labels assigned by the classify step) rather than SCIP type-kind data; it approximates Martin's A without requiring a full symbol index. A module with zero classified inbound edges gets A=0 (entirely concrete is the conservative default when evidence is absent).
Beyond Balanced Coupling, report-only.
func (AbstractnessMetric) Calculate ¶ added in v0.3.0
func (m AbstractnessMetric) Calculate(in signal.CommonInput) diagnostic.MetricResult
Calculate computes per-module abstractness from the edge strength hints.
func (AbstractnessMetric) Name ¶ added in v0.3.0
func (m AbstractnessMetric) Name() string
Name returns "abstractness".
func (AbstractnessMetric) Version ¶ added in v0.3.0
func (m AbstractnessMetric) Version() string
Version returns "abstractness.v1".
type BlastRadiusMetric ¶
type BlastRadiusMetric struct{}
BlastRadiusMetric reports change-impact concentration: how many modules are "hubs" whose change forces a wide transitive rebuild, and which ones. It is a factual finding (report-only), not a quality verdict — a stable hub is fine; the volatile-hub judgement belongs to change_amplification.
func (BlastRadiusMetric) Calculate ¶
func (m BlastRadiusMetric) Calculate(in signal.CommonInput) diagnostic.MetricResult
Calculate computes per-module blast radius and reports the hubs above the threshold. Indeterminate (n/a) for graphs too small to have meaningful concentration.
func (BlastRadiusMetric) Name ¶
func (m BlastRadiusMetric) Name() string
Name returns "blast_radius".
func (BlastRadiusMetric) Version ¶
func (m BlastRadiusMetric) Version() string
Version returns "blast_radius.v1".
type ChangeAmplificationMetric ¶
type ChangeAmplificationMetric struct{}
ChangeAmplificationMetric reports modules whose change is expensive AND likely: blast radius (transitive reverse-deps) weighted by volatility (recent churn). A stable hub scores low (fine); a volatile hub scores high (the real risk). This is archfit's on-mission signal — expected change cost — reported as a ranked finding.
func (ChangeAmplificationMetric) Calculate ¶
func (m ChangeAmplificationMetric) Calculate(in signal.HistoryInput) diagnostic.MetricResult
Calculate ranks modules by change amplification. n/a without a graph or churn.
func (ChangeAmplificationMetric) Name ¶
func (m ChangeAmplificationMetric) Name() string
Name returns "change_amplification".
func (ChangeAmplificationMetric) Version ¶
func (m ChangeAmplificationMetric) Version() string
Version returns "change_amplification.v1".
type ChangeCouplingMetric ¶ added in v0.3.0
type ChangeCouplingMetric struct{}
ChangeCouplingMetric sharpens hidden_coupling with CodeScene's exact formula:
CC(A,B) = C_AB / min(C_A, C_B)
where C_A and C_B are the number of commits that touched module A and B respectively, and C_AB is the number of commits that touched BOTH. Pairs with CC ≥ 65% are flagged as change-coupled.
This metric covers ALL module pairs (not just those without a static import edge), so it complements hidden_coupling (which filters out importing pairs). The 65% threshold is CodeScene's published calibration value for "temporal coupling that is likely accidental or fragile". Report-only, band "info", never gates. Beyond Balanced Coupling standard metric.
func (ChangeCouplingMetric) Calculate ¶ added in v0.3.0
func (m ChangeCouplingMetric) Calculate(in signal.HistoryInput) diagnostic.MetricResult
Calculate counts module pairs with CC ≥ 65%.
func (ChangeCouplingMetric) Name ¶ added in v0.3.0
func (m ChangeCouplingMetric) Name() string
Name returns "change_coupling".
func (ChangeCouplingMetric) Version ¶ added in v0.3.0
func (m ChangeCouplingMetric) Version() string
Version returns "change_coupling.v1".
type CohesionMetric ¶ added in v0.4.0
type CohesionMetric struct{}
CohesionMetric is a report-only Lack-of-Cohesion-of-Methods (LCOM) edge-density proxy over the SCIP symbol graph. For each measurable module it builds the undirected graph of the module's definition symbols connected by same-module references (symbol.Graph.IntraRefs) and counts connected components: a cohesive module's symbols reference each other (one component); a module bundling unrelated concerns splits into several (LCOM4 > 1).
It is intentionally DISTINCT from the removed cohesion_spread metric (which measured outbound cross-module spread and failed its validation gate): this proxy reasons over INTRA-module connectivity, not external dispersion.
The metric is gated behind an eval (docs/plans/notes/gap-closure-task20-cohesion-eval.md): it is kept report-only (band "info", never gates) and disabled in archfit's own .archfit.yaml because the document-scoped attribution ceiling leaves it blind for single-file Python/TS modules and unable to credit internal sub-package cohesion.
func (CohesionMetric) Calculate ¶ added in v0.4.0
func (m CohesionMetric) Calculate(in signal.SymbolInput) diagnostic.MetricResult
Calculate computes per-module LCOM and reports fragmented modules. Returns n/a when SCIP is off, no same-module edges exist, or no module is measurable (every module is single-document — the document-scoped attribution ceiling).
func (CohesionMetric) Name ¶ added in v0.4.0
func (m CohesionMetric) Name() string
Name returns "cohesion_lcom".
func (CohesionMetric) Version ¶ added in v0.4.0
func (m CohesionMetric) Version() string
Version returns "cohesion_lcom.v1".
type FunctionalCandidatesMetric ¶
type FunctionalCandidatesMetric struct{}
FunctionalCandidatesMetric reports cross-module pairs that share duplicated logic as detected by a clone detector (e.g. jscpd). It optionally flags which of those pairs also co-change, making them stronger refactoring candidates.
DISTINCT from hidden_coupling: hidden_coupling finds pairs that co-change WITHOUT a static import edge (invisible runtime coupling). This metric is DUPLICATION-based — it surfaces pairs whose source code is literally copied, regardless of whether they import each other. A pair can appear in both metrics (cloned AND invisibly coupled), but each captures a different signal.
Band is always "info" (report-only); this metric never gates.
func (FunctionalCandidatesMetric) Calculate ¶
func (m FunctionalCandidatesMetric) Calculate(in signal.DuplicationInput) diagnostic.MetricResult
Calculate counts cross-module pairs sharing duplicated code blocks, with an optional co-change cross-reference. Reports n/a when no clone data is present.
func (FunctionalCandidatesMetric) Name ¶
func (m FunctionalCandidatesMetric) Name() string
Name returns "functional_candidates".
func (FunctionalCandidatesMetric) Version ¶
func (m FunctionalCandidatesMetric) Version() string
Version returns "functional_candidates.v1".
type HiddenCouplingMetric ¶
type HiddenCouplingMetric struct{}
HiddenCouplingMetric reports module pairs that change together (git co-change) but do NOT import each other — coupling invisible to the dependency graph (e.g. deflected by boundary tests or mediated by a shared data model). The canonical case is a module with low import fan-in that co-changes with many others.
func (HiddenCouplingMetric) Calculate ¶
func (m HiddenCouplingMetric) Calculate(in signal.HistoryInput) diagnostic.MetricResult
Calculate counts hidden-coupling module pairs (co-change without a static edge).
func (HiddenCouplingMetric) Name ¶
func (m HiddenCouplingMetric) Name() string
Name returns "hidden_coupling".
func (HiddenCouplingMetric) Version ¶
func (m HiddenCouplingMetric) Version() string
Version returns "hidden_coupling.v1".
type InstabilityMetric ¶ added in v0.3.0
type InstabilityMetric struct{}
InstabilityMetric reports per-module instability I=Ce/(Ca+Ce): the fraction of dependencies that are outgoing. High instability means the module depends on many others and is depended upon by few — common for leaf/glue modules. Report-only (band "info"); high I is not inherently bad, but pairing with volatile targets (see change_amplification) is a risk.
Shared-DTO trap: high Ca + low I is NOT automatically good — a widely-imported shared-DTO module can become a change magnet if its API is volatile. Volatility and strength (see abstractness) must moderate the reading. These metrics are report-only for precisely this reason.
func (InstabilityMetric) Calculate ¶ added in v0.3.0
func (m InstabilityMetric) Calculate(in signal.CommonInput) diagnostic.MetricResult
Calculate computes per-module instability and reports modules with I>0.7.
func (InstabilityMetric) Name ¶ added in v0.3.0
func (m InstabilityMetric) Name() string
Name returns "instability".
func (InstabilityMetric) Version ¶ added in v0.3.0
func (m InstabilityMetric) Version() string
Version returns "instability.v1".
type MartinDistanceMetric ¶ added in v0.3.0
type MartinDistanceMetric struct{}
MartinDistanceMetric reports per-module distance from the main sequence: Dms = |A+I-1|. Modules with Dms > 0.5 fall into the zone of pain (too concrete + stable: A≈0, I≈0) or the zone of uselessness (too abstract + unstable: A≈1, I≈1). Beyond Balanced Coupling, report-only.
func (MartinDistanceMetric) Calculate ¶ added in v0.3.0
func (m MartinDistanceMetric) Calculate(in signal.CommonInput) diagnostic.MetricResult
Calculate computes Dms per module and reports those exceeding 0.5.
func (MartinDistanceMetric) Name ¶ added in v0.3.0
func (m MartinDistanceMetric) Name() string
Name returns "martin_distance".
func (MartinDistanceMetric) Version ¶ added in v0.3.0
func (m MartinDistanceMetric) Version() string
Version returns "martin_distance.v1".
type PropagationCostMetric ¶ added in v0.3.0
type PropagationCostMetric struct{}
PropagationCostMetric reports Propagation Cost (PC): the fraction of all ordered module pairs (i,j) where j is transitively reachable from i.
PC = reachable_pairs / (n*(n-1))
where reachable_pairs = sum of reach[m] for all first-party modules m, and n = number of first-party modules.
A system PC of 1.0 means every module cascades into every other (fully connected transitive closure). A value near 0 means a sparse graph.
Per-module PC = reach[m]/(n-1): the fraction of the system reachable from m. Modules exceeding 50% are surfaced in the display as wide-blast contributors.
Report-only (band "info"); never gates. Beyond Balanced Coupling — a standard cascade metric (CodeScene, LCOM literature).
func (PropagationCostMetric) Calculate ¶ added in v0.3.0
func (m PropagationCostMetric) Calculate(in signal.CommonInput) diagnostic.MetricResult
Calculate computes system-level and per-module propagation cost.
func (PropagationCostMetric) Name ¶ added in v0.3.0
func (m PropagationCostMetric) Name() string
Name returns "propagation_cost".
func (PropagationCostMetric) Version ¶ added in v0.3.0
func (m PropagationCostMetric) Version() string
Version returns "propagation_cost.v1".
type StructuralWeightMetric ¶
type StructuralWeightMetric struct{}
StructuralWeightMetric reports size skew: modules far larger than the codebase median, which concentrate responsibilities (a cohesion smell). Report-only; the ranked god-module list is the signal, not a quality verdict.
func (StructuralWeightMetric) Calculate ¶
func (m StructuralWeightMetric) Calculate(in signal.SizeInput) diagnostic.MetricResult
Calculate aggregates per-file LOC onto modules and flags god-modules. n/a without LOC data or a graph (the latter gives the language for file→module).
func (StructuralWeightMetric) Name ¶
func (m StructuralWeightMetric) Name() string
Name returns "structural_weight".
func (StructuralWeightMetric) Version ¶
func (m StructuralWeightMetric) Version() string
Version returns "structural_weight.v1".