Module: McpDiff::Render
- Defined in:
- lib/mcp_diff/render.rb
Overview
Output renderers: pretty (default), --json, --md. Findings always ordered most-severe first, then rule, then target. Port of cli/src/render.ts.
Constant Summary collapse
- GLYPHS =
{ "breaking" => "✗", "behavior" => "⚠", "compat" => "✓", "info" => "ℹ" }.freeze
- COLORS =
{ "breaking" => 31, "behavior" => 33, "compat" => 32, "info" => 34 }.freeze
- SEVERITY_RANK =
{ "breaking" => 0, "behavior" => 1, "compat" => 2, "info" => 3 }.freeze
Class Method Summary collapse
- .bold(str) ⇒ Object
- .color? ⇒ Boolean
- .dim(str) ⇒ Object
- .paint(code, str) ⇒ Object
- .render(format, findings, exit_code) ⇒ Object
- .render_json(findings, exit_code) ⇒ Object
- .render_markdown(findings, exit_code) ⇒ Object
- .render_pretty(findings, exit_code) ⇒ Object
- .sort_findings(findings) ⇒ Object
Class Method Details
.bold(str) ⇒ Object
23 24 25 |
# File 'lib/mcp_diff/render.rb', line 23 def bold(str) color? ? "\e[1m#{str}\e[22m" : str end |
.color? ⇒ Boolean
15 16 17 |
# File 'lib/mcp_diff/render.rb', line 15 def color? ENV["NO_COLOR"].nil? && $stdout.tty? end |
.dim(str) ⇒ Object
27 28 29 |
# File 'lib/mcp_diff/render.rb', line 27 def dim(str) color? ? "\e[2m#{str}\e[22m" : str end |
.paint(code, str) ⇒ Object
19 20 21 |
# File 'lib/mcp_diff/render.rb', line 19 def paint(code, str) color? ? "\e[#{code}m#{str}\e[39m" : str end |
.render(format, findings, exit_code) ⇒ Object
35 36 37 38 39 40 41 42 |
# File 'lib/mcp_diff/render.rb', line 35 def render(format, findings, exit_code) sorted = sort_findings(findings) case format when "json" then render_json(sorted, exit_code) when "md" then render_markdown(sorted, exit_code) else render_pretty(sorted, exit_code) end end |
.render_json(findings, exit_code) ⇒ Object
61 62 63 64 65 66 67 68 69 70 71 72 73 |
# File 'lib/mcp_diff/render.rb', line 61 def render_json(findings, exit_code) count = ->(cls) { findings.count { |f| f["class"] == cls } } JSON.pretty_generate( "findings" => findings, "summary" => { "breaking" => count.call("breaking"), "behavior" => count.call("behavior"), "compat" => count.call("compat"), "info" => count.call("info") }, "exitCode" => exit_code ) end |
.render_markdown(findings, exit_code) ⇒ Object
75 76 77 78 79 80 81 82 83 84 85 86 87 88 |
# File 'lib/mcp_diff/render.rb', line 75 def render_markdown(findings, exit_code) return "✅ **mcp-diff**: contract unchanged" if findings.empty? rows = findings.map do |f| "| #{GLYPHS[f['class']]} #{f['class'].upcase} | `#{f['ruleId']}` | `#{f['target']}` | #{f['message']} |" end header = case exit_code when 1 then "❌ **mcp-diff**: breaking contract changes" when 2 then "⚠️ **mcp-diff**: behavior-level contract changes" else "✅ **mcp-diff**: compatible contract changes" end [header, "", "| Severity | Rule | Target | Change |", "|---|---|---|---|", *rows].join("\n") end |
.render_pretty(findings, exit_code) ⇒ Object
44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 |
# File 'lib/mcp_diff/render.rb', line 44 def render_pretty(findings, exit_code) return paint(COLORS["compat"], "✓ contract unchanged") if findings.empty? rule_width = findings.map { |f| f["ruleId"].length }.max + 2 lines = findings.map do |f| code = COLORS[f["class"]] [ paint(code, GLYPHS[f["class"]]), paint(code, f["class"].upcase.ljust(9)), f["ruleId"].ljust(rule_width), "#{bold(f['target'])}: #{f['message']}" ].join(" ") end lines << dim("exit code #{exit_code}") lines.join("\n") end |
.sort_findings(findings) ⇒ Object
31 32 33 |
# File 'lib/mcp_diff/render.rb', line 31 def sort_findings(findings) findings.sort_by { |f| [SEVERITY_RANK[f["class"]], f["ruleId"], f["target"]] } end |