classify

package
v1.6.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 9, 2026 License: Apache-2.0 Imports: 11 Imported by: 0

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

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

type DistanceOmittedRungReason struct {
	Rung   int
	Reason string
}

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
	SharedAncestor    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.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL