Skip to content

v0.2.0 — False-positive reduction & diff classification

Latest

Choose a tag to compare

@harakeishi harakeishi released this 08 May 03:46
· 2 commits to main since this release

Highlights

After a 5-week field study at hosting/muu (86 PRs scored, 18 flagged as Degraded), 67% of the Degradation comments were false positives caused by mechanical metrics (line count, public method count, single-method case/when, etc.). This release rebuilds the heuristics to reduce that rate to ~17% while keeping the genuine quality regressions in scope.

What's new

ISP — finer-grained public-method scoring

  • #15 Replace the stepped PUBLIC_METHOD_SCORES table with a linear curve (5 → 100, 25 → 20, slope -4pt/method). Eliminates the 20pt cliffs from threshold-crossing.
  • #16 Detect symmetric method pairs (enable_X/disable_X, start_X/stop_X, encrypt/decrypt, …). Each suffix-matched pair counts as one toward the public surface.
  • #17 Floor the score at 80 for Inspection / Diagnostic / Console / Debug / Tools classes (and matching file paths). Anchored regexes prevent mid-word false positives like DiagnosticReport.

SRP — semantic statement counting & inspection mitigation

  • #18 Replace the line-count penalty with effective_statement_count from the AST (:send, :csend, :if, :case, …). Stylistic refactors such as raise X, msgraise X.new(msg) no longer move the score. Nested def/defs boundaries are respected so inner-lambda statements don't leak into the parent count.
  • #17 Inspection-class minimum-score (shared with ISP via the new Mitigations module).

OCP — leniency for narrow conditionals

  • #19 Per-method stepped case/when penalty: 1 clause → 2pt, 2 → 5pt, 3+ → n*5pt. Small justified domain conditionals (payment methods, environment switches) are no longer treated as full OCP violations. Class-level MAX_CASE_WHEN_PENALTY cap retained.

DIP — reward Rails-style memoised factories

  • #20 Detect @svc ||= ServiceClass.new(...) (and .create/.build/.call/.open) memoised factory bodies during parsing. DipAnalyzer adds up to 15pt bonus (5pt per qualifying method, complexity 1, non-stdlib receiver). Queue.new / Net::HTTP.new etc. are explicitly excluded via the standard-library whitelist used elsewhere.

Diff classification

  • #21 New DiffClassifier#classify(before, after) returns delta_total, delta_structural (OCP+LSP+DIP) and delta_mechanical (SRP+ISP). ScoreResult#subscores carries a per-principle breakdown (SRP exposes wmc_penalty, effective_statement_penalty, and named mitigations). JsonFormatter surfaces the breakdown so external review tooling can build structural/mechanical reports.

Configuration extension (parsed but not yet wired)

  • #22 New .solid-score.yml knobs: inspection_classes, symmetric_method_pairs.custom, diff_thresholds.{ignore_below_delta, flag_only_structural_below}, and scoring.{isp_public_method_curve, case_when_2way_lenient}. Defaults preserve current behaviour. Malformed YAML values are tolerated (defaults stick), string scoring values are normalised case-insensitively, and a one-time warning is emitted when a key is parsed but not yet wired into the analyzers.

Notable behavioural changes

These are the internal API shifts intentional in this minor bump:

  • MethodInfo adds effective_statement_count, memoized_factory_receiver, memoized_factory?.
  • ClassInfo adds inspection_class?. The unused ClassInfo#line_count was removed.
  • SrpAnalyzer exposes analyze_with_breakdown (the legacy #analyze delegates to it).
  • New shared Analyzers::Mitigations module providing mitigate_inspection.

Testing

  • 161 → 223 examples, 0 failures
  • Line coverage: 95.6% → 96.3%

Migration

No user-facing CLI flag changed. Projects that already use .solid-score.yml continue to work unchanged. Embedders depending on MethodInfo/ClassInfo/SrpAnalyzer should review the API additions above.

Closed issues

#7 #8 #9 #10 #11 #12 #13 #14

🤖 Generated with Claude Code