Documentation
¶
Overview ¶
Package classify assigns Balanced Coupling classifications to graph edges: strength, distance, volatility, and explicitness.
Package classify — distance_structure.go provides codeStructureDistance, the always-available deterministic baseline for the distance composite (§4.2).
Package classify assigns Balanced Coupling classifications to graph edges: strength, distance, volatility, and explicitness.
Package classify — external_systems.go resolves config-declared external integration seams (`external_systems:`) to the distance ladder's far end.
Index ¶
- func AugmentCargoCrateNodes(g *graph.Graph, modules map[string]module.ModuleDef) map[string]module.ModuleDef
- func AugmentGoWorkspaceModules(g *graph.Graph, modules map[string]module.ModuleDef) map[string]module.ModuleDef
- func AugmentModulesFromGraph(g *graph.Graph, modules map[string]module.ModuleDef) map[string]module.ModuleDef
- func ComputeVolatilityProvenance(g *graph.Graph, declared map[string]module.ModuleDef, c view.ClassifyConfig) *diagnostic.VolatilityProvenance
- func Run(g *graph.Graph, c view.ClassifyConfig) coupling.Index
- type CloneOnlyPair
- type DistanceCompressionEvidence
- type DistanceOmittedRungReason
- type ModuleHierarchySpan
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func AugmentCargoCrateNodes ¶ added in v1.1.1
func AugmentCargoCrateNodes(g *graph.Graph, modules map[string]module.ModuleDef) map[string]module.ModuleDef
AugmentCargoCrateNodes binds crate-level Rust nodes (a `package:<crate>` node whose path is the bare crate name, no "::") to a declared module when the config used a directory-path glob (e.g. "crates/ruff_python_ast/**") that matches the crate's source dir but NOT the bare crate-name node the cargo-metadata extractor emits. Without this, such edges classify distance-unknown → external → 0 scored, and coupling_balance reports n/a even though the architect declared the crates.
For each crate node not already covered by a configured glob: if a configured module covers the crate's repo-relative directory (from graph CrateRoots), the bare crate name is appended to THAT module's Paths so the node binds while keeping the module's owner/subdomain/volatility/layer. If no module covers the dir, a synthetic module is registered (bare name, ancestor-inherited owner).
Gate: ≥2 first-party crate nodes — a single-crate repo is a degenerate graph (coupling unmeasurable → n/a) and is left untouched. Configs that already use bare crate-name globs (tokio, yazi) bind on the first check and are no-ops. Intra-crate "<crate>::<mod>" nodes are handled by AugmentModulesFromGraph. The input map is not mutated; a copy is returned only if something is added.
func AugmentGoWorkspaceModules ¶ added in v0.12.0
func AugmentGoWorkspaceModules(g *graph.Graph, modules map[string]module.ModuleDef) map[string]module.ModuleDef
AugmentGoWorkspaceModules returns a Modules map extended with a synthetic module for each Go workspace member whose directory is not already covered by a configured module's path globs. Called when ≥2 workspace members were loaded so cross-member edges can classify with a real Distance for coupling_balance.
The gate is len(GoModules) >= 2 — single-module repos and archfit's own self-scan (which collapses to 1 surviving member after exclusion) are byte-identical to before, mirroring the Rust "::" gate in AugmentModulesFromGraph.
Members at the repo root (RelDir == ".") are skipped: a root-relative glob would over-match sibling members (abstain-not-fake). Existing config modules keep precedence — a synthetic module is only added when nothing already covers the member's directory. The input map is not mutated; a copy is returned only if something is added.
func AugmentModulesFromGraph ¶ added in v0.6.0
func AugmentModulesFromGraph(g *graph.Graph, modules map[string]module.ModuleDef) map[string]module.ModuleDef
AugmentModulesFromGraph returns a Modules map extended with a synthetic module for every first-party module-graph node not already covered by a configured module's path globs. Rust intra-crate module nodes ("<crate>::<mod>", produced by the cargo-modules extractor) are otherwise unknown to moduleFor, so their edges classify as distance-unknown and never count toward coupling_balance or encapsulation. The gate is the "::" separator, which only the Rust module-graph convention uses (Go/TS use "/", Python "."), so Go/TS/Python graphs and their configured modules are untouched. Existing config modules keep precedence — a synthetic module is only added when nothing already covers the node. The input map is not mutated; a copy is returned only if something is added.
func ComputeVolatilityProvenance ¶ added in v1.2.0
func ComputeVolatilityProvenance(g *graph.Graph, declared map[string]module.ModuleDef, c view.ClassifyConfig) *diagnostic.VolatilityProvenance
ComputeVolatilityProvenance counts the modules classify ran with by the source of their volatility, for the coupling_balance triage disclosure: a repo where every edge carries the same volatility must be visibly uniform-by-inheritance (one declared ancestor fanned out to N synthetic submodules), not mistaken for N measured judgments.
declared is the PRE-augmentation config module map; c.Modules is the augmented map the classification ran with (the Augment* functions are copy-on-write, so the caller's original map is intact). A module present in declared with a base volatility counts as Declared; a synthetic module with a base volatility (donated by inheritAncestorAttrs) counts as Inherited; everything else is Undeclared. Cascade re-derives Run's own computeEffectiveVolatility pass (cheap O(edges), same inputs) and counts the modules it raised — an overlay, not a fourth base bucket.
Returns nil when c.Modules is empty: nothing resolved, nothing to disclose.
func Run ¶
Run classifies every edge in g and returns a coupling.Index keyed by the edge canonical key (from + "\x00" + to + "\x00" + kind).
For each edge:
- Strength: contract if the target path matches a public glob of any module; intrusive if it matches an internal glob; unknown otherwise.
- Distance: derived from module ownership (same_module / cross_module_same_owner / cross_module_different_owner / cross_deploy_unit). When either endpoint cannot be resolved to a module, distance is unknown.
- Volatility: derived from the to-module's subdomain field (core→high, supporting→low, generic→low, ""/"unknown"→unknown).
- Explicitness: explicit when strength=contract; implicit when strength=intrusive; unknown otherwise.
- Score: continuous EdgeScore from the configured Scorer (default: BookScorer). Applied to every known-distance edge; unknown-distance edges are zero. Same-module edges are scored (local_coupling report block) but keep SeverityNone — the advisory pipeline stays cross-boundary.
Types ¶
type CloneOnlyPair ¶ added in v1.2.0
type CloneOnlyPair struct {
FromModule string // canonical pair order: FromModule < ToModule
ToModule string
FromPath string // representative duplicated-code file in FromModule ("" when evidence resolves none)
ToPath string
Locations []graph.Location // both sides' duplicated-code locations (sorted, deduped upstream)
Classification coupling.Classification
}
CloneOnlyPair is a cross-module clone pair (analyzers.clones) between two modules that share NO import-graph edge in either direction — the book's "duplicated knowledge" (Ch7): functional coupling through shared logic that is invisible to the import graph. The symmetric-strength upgrade in classify only fires on an existing edge, so without this detection copy-paste drift between unconnected modules changes no metric, finding, or score.
The Classification is scored through the standard book formula with StrengthSymmetric at the module-pair distance and the worst-of-pair volatility — never a hardcoded severity. The engine decides, through coupling.duplicated_knowledge, whether the pair is score-bearing or advisory-only. No graph edge is invented — the scorer's inputs stay tool-derived facts.
func CloneOnlyPairs ¶ added in v1.2.0
func CloneOnlyPairs(g *graph.Graph, c view.ClassifyConfig) []CloneOnlyPair
CloneOnlyPairs returns the scored duplicated-knowledge pairs for g: every cross-module clone pair in c.CrossModuleClonePairs whose modules have no import edge between them and no human-approved label accepting the pair. Advisory filtering (severity none, coupling.min_severity) lives downstream; this detector stays pure and deterministic. Pairs are processed in sorted key order.
type DistanceCompressionEvidence ¶ added in v1.3.0
type DistanceCompressionEvidence struct {
CompressedMiddleRungs bool
ImplementedRungs []int
OmittedRungs []int
OmittedRungReasons []DistanceOmittedRungReason
DeterministicSplits []string
Rationale string
}
DistanceCompressionEvidence records the deterministic distance-ladder rungs archfit can currently distinguish. Middle Ch8 package/library rungs stay compressed unless a stable graph/config fact separates them.
func DistanceCompression ¶ added in v1.3.0
func DistanceCompression() DistanceCompressionEvidence
DistanceCompression returns a deterministic summary of the distance ladder implemented by classifyDistance. It is disclosure-only: the scorer still reads the concrete Distance on each edge, not these strings.
type DistanceOmittedRungReason ¶ added in v1.3.0
DistanceOmittedRungReason explains why a book distance rung remains compressed instead of being assigned from unstable naming or package-shape guesses.
type ModuleHierarchySpan ¶ added in v1.5.0
type ModuleHierarchySpan struct {
BoundaryCrossings int
}
ModuleHierarchySpan is the raw structural-distance evidence between two module names: how many module-boundary crossings separate them, and how deep their closest common ancestor sits in the hierarchy. It is report-only book Ch8 evidence for compressed middle rungs; scoring still consumes the coarse Distance enum, not these raw counts.
func HierarchySpan ¶ added in v1.5.0
func HierarchySpan(fromMod, toMod string) ModuleHierarchySpan
HierarchySpan estimates structural distance from a module pair's closest common ancestor, as described in book Ch8. The counts are raw evidence, not score inputs: BoundaryCrossings is the number of hierarchy steps that differ across the pair; SharedAncestor is the depth of the closest common ancestor.
Language is inferred from the module-name separator for report-only use: Rust uses "::"; slash-based modules (Go/TS paths and file-like module keys) use "/"; dotted names fall back to Python-style "." segmentation.