Module: Moult::Confidence

Defined in:
lib/moult/confidence.rb,
lib/moult/confidence/rules.rb

Overview

The per-finding confidence model — one of Moult's two protected APIs (the other being the JSON output contract). It answers a single, deliberately humble question: how likely is this definition to actually be dead? It never asserts certain death (Moult's core principle); the highest a finding can score is still a confidence, and every contributing factor is recorded as a Reason so the judgement is auditable.

Confidence.score is a pure function of a Context of already-gathered facts: no IO, no rubydex, no Rails detection happens here. That keeps it trivially unit-testable and lets each Rules::Rule be exercised in isolation. The fact gathering lives in DeadCode; the conventions live in RailsConventions.

Defined Under Namespace

Modules: Rules Classes: Context, Finding, Reason

Constant Summary collapse

CATEGORY =
"dead_code"
BASE =

Base likelihood before any rule fires, keyed by [kind, visibility]. A private method with no caller is the strongest candidate (nothing outside its class can reach it); public symbols are weakest because they are the natural API surface and the place metaprogramming/Rails reach in.

{
  [:method, :private] => 0.75,
  [:method, :protected] => 0.6,
  [:method, :public] => 0.4,
  [:constant, :private] => 0.6,
  [:constant, :public] => 0.5
}.freeze
DEFAULT_BASE =
0.45

Class Method Summary collapse

Class Method Details

.score(ctx, rules: Rules::DEFAULT_RULES) ⇒ Finding

Parameters:

  • ctx (Context)
  • rules (Array<Rules::Rule>) (defaults to: Rules::DEFAULT_RULES)

    injectable for isolated testing

Returns:



76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
# File 'lib/moult/confidence.rb', line 76

def score(ctx, rules: Rules::DEFAULT_RULES)
  base = BASE.fetch([ctx.kind, ctx.visibility], DEFAULT_BASE)
  reasons = [Reason.new(rule: :base_score, delta: base, detail: "base for #{ctx.kind}/#{ctx.visibility}")]
  caps = []

  rules.each do |rule|
    next unless rule.applies?(ctx)
    reasons << Reason.new(rule: rule.name, delta: rule.delta, detail: rule.detail_for(ctx))
    caps << rule.cap if rule.cap
  end

  raw = reasons.sum(&:delta)
  bounded = caps.empty? ? raw : [raw, caps.min].min
  confidence = bounded.clamp(0.0, 1.0).round(2)

  Finding.new(
    symbol_id: ctx.symbol_id,
    kind: ctx.kind,
    name: ctx.name,
    span: ctx.span,
    path: ctx.path,
    confidence: confidence,
    category: CATEGORY,
    reasons: reasons,
    runtime: ctx.runtime
  )
end