Module: Rigor::Triage

Defined in:
lib/rigor/triage.rb,
lib/rigor/triage/hint.rb,
lib/rigor/triage/catalogue.rb

Overview

ADR-23 — diagnostic triage. Aggregates a ‘rigor check` diagnostic stream into the data behind the `rigor triage` report: a rule-ID distribution, per-file hotspots, and the heuristic hint catalogue (Catalogue).

Pure over the diagnostic stream — no second analysis pass, no analyzer internals. ‘Triage.analyze` is the single entry point; rendering is CLI::TriageRenderer’s job.

Defined Under Namespace

Modules: Catalogue Classes: Hint, Hotspot, Report, RuleCount, Summary

Constant Summary collapse

UNCATEGORISED =
"(uncategorised)"

Class Method Summary collapse

Class Method Details

.analyze(diagnostics, top: 10, hints: true) ⇒ Report

Parameters:

  • diagnostics (Array<Analysis::Diagnostic>)
  • top (Integer) (defaults to: 10)

    hotspot-file cap

  • hints (Boolean) (defaults to: true)

    run the heuristic catalogue

Returns:



29
30
31
32
33
34
35
36
# File 'lib/rigor/triage.rb', line 29

def analyze(diagnostics, top: 10, hints: true)
  Report.new(
    summary: build_summary(diagnostics),
    distribution: build_distribution(diagnostics),
    hotspots: build_hotspots(diagnostics, top),
    hints: hints ? Catalogue.recognise(diagnostics) : []
  )
end

.build_distribution(diagnostics) ⇒ Object



54
55
56
57
58
# File 'lib/rigor/triage.rb', line 54

def build_distribution(diagnostics)
  diagnostics.group_by { |d| rule_key(d) }
             .map { |rule, group| RuleCount.new(rule: rule, count: group.size) }
             .sort_by { |row| [-row.count, row.rule] }
end

.build_hotspots(diagnostics, top) ⇒ Object



60
61
62
63
64
65
# File 'lib/rigor/triage.rb', line 60

def build_hotspots(diagnostics, top)
  diagnostics.group_by(&:path)
             .map { |path, group| hotspot_for(path, group) }
             .sort_by { |spot| [-spot.count, spot.file] }
             .first(top)
end

.build_summary(diagnostics) ⇒ Object



44
45
46
47
48
49
50
51
52
# File 'lib/rigor/triage.rb', line 44

def build_summary(diagnostics)
  by_severity = diagnostics.group_by(&:severity).transform_values(&:size)
  Summary.new(
    total: diagnostics.size,
    error: by_severity.fetch(:error, 0),
    warning: by_severity.fetch(:warning, 0),
    info: by_severity.fetch(:info, 0)
  )
end

.hotspot_for(path, group) ⇒ Object



67
68
69
70
71
72
73
# File 'lib/rigor/triage.rb', line 67

def hotspot_for(path, group)
  by_rule = group.group_by { |d| rule_key(d) }
                 .transform_values(&:size)
                 .sort_by { |rule, count| [-count, rule] }
                 .to_h
  Hotspot.new(file: path, count: group.size, by_rule: by_rule)
end

.report_to_h(report) ⇒ Object



75
76
77
78
79
80
81
82
83
84
85
86
87
# File 'lib/rigor/triage.rb', line 75

def report_to_h(report)
  {
    "summary" => {
      "total" => report.summary.total, "error" => report.summary.error,
      "warning" => report.summary.warning, "info" => report.summary.info
    },
    "distribution" => report.distribution.map { |r| { "rule" => r.rule, "count" => r.count } },
    "hotspots" => report.hotspots.map do |h|
      { "file" => h.file, "count" => h.count, "by_rule" => h.by_rule }
    end,
    "hints" => report.hints.map(&:to_h)
  }
end

.rule_key(diagnostic) ⇒ Object

Diagnostics without a ‘rule` (parse errors, internal-analyzer errors) bucket under a single sentinel rather than vanishing.



40
41
42
# File 'lib/rigor/triage.rb', line 40

def rule_key(diagnostic)
  diagnostic.qualified_rule || UNCATEGORISED
end