Module: Moult::Confidence::Rules
- Defined in:
- lib/moult/confidence/rules.rb
Overview
The named, ordered adjusters score applies on top of the base score. Each is a small value object so a single rule can be tested in isolation and the set can be extended without touching the scorer.
Direction is encoded in delta: positive raises confidence-of-death,
negative lowers it. A rule may instead (or also) impose a cap — an upper
bound on the final confidence — used when a factor means "we genuinely
cannot be sure", e.g. an unresolved index. No rule ever removes a finding:
consistent with "never assert certain death", uncertainty lowers
confidence and records a reason, it never hides the candidate.
Defined Under Namespace
Classes: Rule
Constant Summary collapse
- DEFAULT_RULES =
[ Rule.new( name: :no_references, applies: ->(c) { c.reference_count.to_i.zero? }, delta: 0.0, detail: "no resolvable references found" ), Rule.new( name: :has_test_only_references, applies: ->(c) { c.test_only }, delta: -0.2, detail: "only referenced from test/spec files" ), Rule.new( name: :rails_entrypoint, applies: ->(c) { !Array(c.rails_signals).empty? }, delta: -0.5, detail: ->(c) { "Rails framework entrypoint: #{Array(c.rails_signals).map(&:detail).join("; ")}" } ), Rule.new( name: :dynamic_dispatch_present, applies: ->(c) { c.dynamic_dispatch }, delta: -0.35, detail: "dynamic dispatch (send/define_method/method_missing/const_get/eval) present in file" ), # Constructors are invoked implicitly by `.new`, not by a call to # `initialize`, so the index never records a reference. Universal Ruby # (not Rails); kept narrow to this one near-certain implicit entrypoint. Rule.new( name: :implicit_constructor, applies: ->(c) { c.kind == :method && c.name.to_s.end_with?("#initialize") }, delta: -0.4, detail: "constructor invoked implicitly via .new" ), # A method that overrides/implements an ancestor's method is reachable # through that ancestor's interface (polymorphic dispatch) even with no # by-name call site — the same signal a typed tool gets free from its # inheritance graph. Covers framework hooks (visitor #visit_*, job # #perform) when the ancestor's source is indexed. Rule.new( name: :overrides_ancestor, applies: ->(c) { c.override_of }, delta: -0.4, detail: ->(c) { "overrides #{c.override_of} (reachable via that interface)" } ), Rule.new( name: :private_unused, applies: ->(c) { c.kind == :method && c.visibility == :private && c.reference_count.to_i.zero? }, delta: 0.1, detail: "private method with no caller in the codebase" ), Rule.new( name: :public_api, applies: ->(c) { c.kind == :method && c.visibility == :public }, delta: -0.1, detail: "public method may be an external API entrypoint" ), Rule.new( name: :deprecated_marked, applies: ->(c) { c.deprecated }, delta: 0.1, detail: "marked deprecated" ), Rule.new( name: :index_unresolved, applies: ->(c) { c.index_resolved == false }, delta: 0.0, cap: 0.5, detail: "index did not fully resolve; confidence capped" ), # Phase 3 runtime evidence. Applied last so it is the headline reason and, # for the rescue case, caps over every static signal. Methods only — a # constant's line runs at load regardless of use, so the resolver returns # :untracked for constants and neither rule fires. # # runtime-cold corroborates a static candidate: the body never executed in # the supplied run. Additive (not a cap) — coverage can be incomplete or # stale (stale-detection deferred), so it raises confidence, never asserts. Rule.new( name: :runtime_cold, applies: ->(c) { c.runtime == :cold }, delta: 0.2, detail: "never executed in the supplied coverage run (runtime-cold corroborates)" ), # runtime-hot overrides: the symbol executed despite no resolvable static # reference — the false positive static analysis missed (send / dynamic # dispatch / metaprogramming). The cap drives it below default confidence # gates while leaving a sliver, since coverage may be stale/incomplete. Rule.new( name: :runtime_hot, applies: ->(c) { c.runtime == :hot }, delta: -0.6, cap: 0.1, detail: "executed at runtime (coverage) despite no static reference; rescued" ) ].freeze
Instance Attribute Summary collapse
-
#applies ⇒ Object
readonly
ctx -> Boolean.
-
#detail ⇒ Object
readonly
human-readable reason (Proc gets ctx).
Instance Method Summary collapse
-
#cap(value) ⇒ Object
optional upper bound on final confidence.
-
#delta(value) ⇒ Object
signed adjustment when it applies.
Instance Attribute Details
#applies ⇒ Object (readonly)
ctx -> Boolean
20 21 22 23 24 25 26 27 28 |
# File 'lib/moult/confidence/rules.rb', line 20 Rule = Struct.new(:name, :applies, :delta, :cap, :detail) do def applies?(ctx) applies.call(ctx) end def detail_for(ctx) detail.respond_to?(:call) ? detail.call(ctx) : detail end end |
#detail ⇒ Object (readonly)
human-readable reason (Proc gets ctx)
20 21 22 23 24 25 26 27 28 |
# File 'lib/moult/confidence/rules.rb', line 20 Rule = Struct.new(:name, :applies, :delta, :cap, :detail) do def applies?(ctx) applies.call(ctx) end def detail_for(ctx) detail.respond_to?(:call) ? detail.call(ctx) : detail end end |
Instance Method Details
#cap=(value) ⇒ Object
optional upper bound on final confidence
20 21 22 23 24 25 26 27 28 |
# File 'lib/moult/confidence/rules.rb', line 20 Rule = Struct.new(:name, :applies, :delta, :cap, :detail) do def applies?(ctx) applies.call(ctx) end def detail_for(ctx) detail.respond_to?(:call) ? detail.call(ctx) : detail end end |
#delta=(value) ⇒ Object
signed adjustment when it applies
20 21 22 23 24 25 26 27 28 |
# File 'lib/moult/confidence/rules.rb', line 20 Rule = Struct.new(:name, :applies, :delta, :cap, :detail) do def applies?(ctx) applies.call(ctx) end def detail_for(ctx) detail.respond_to?(:call) ? detail.call(ctx) : detail end end |