ui_guardrails 1.3.0 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +11 -1
- data/doc/ROADMAP.md +20 -2
- data/lib/guardrails/a11y_deep.rb +5 -2
- data/lib/guardrails/audit.rb +10 -2
- data/lib/guardrails/report/html/template.html.erb +1 -1
- data/lib/guardrails/report/html.rb +10 -1
- data/lib/guardrails/report/run.rb +36 -11
- data/lib/guardrails/report/severity.rb +43 -0
- data/lib/guardrails/tui.rb +1 -1
- data/lib/guardrails/version.rb +1 -1
- data/lib/tasks/guardrails.rake +15 -4
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: da306d3808653d6bd263a867d1ba0aaf159a2c3a515a6e1284276cc69e34cc8c
|
|
4
|
+
data.tar.gz: c895cef92d7cc7acf0be8ddf97079a51a9753aa434223fc3ce3e5fe28125cc39
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4c250a84dfd1ae1ce7ccf39e9e617ede812facb0dfa529bf53ef7bfe2ffd8812327d47409bbaafa4b21a712ae32a6d565ef9fa2c83b5802314793375f5b3208f
|
|
7
|
+
data.tar.gz: 6e97b1ec55de42f6083ee6590c85599445e310d0a8c9bc8d12bce1dec41f2c3becadb1a9955b4b799112e52b754657f0b6bdb0fe3bf3db036a119b0278d8eea7
|
data/README.md
CHANGED
|
@@ -4,7 +4,7 @@ A Rails toolset that prevents UI drift in AI-assisted applications. Static audit
|
|
|
4
4
|
|
|
5
5
|
Built and maintained by [Meticulous](https://meticulous.com).
|
|
6
6
|
|
|
7
|
-
**Current release:** 1.
|
|
7
|
+
**Current release:** 1.4.0 — V0 + V1 + V2 complete, published on [RubyGems.org as `ui_guardrails`](https://rubygems.org/gems/ui_guardrails). The Ruby module stays `Guardrails` (so `require "guardrails"` is unchanged) — only the gem package name on rubygems carries the `ui_` prefix, to clear RubyGems' similarity rule against the unrelated [`guard-rails`](https://rubygems.org/gems/guard-rails) gem. See [`doc/ROADMAP.md`](doc/ROADMAP.md) for status, [`CHANGELOG.md`](CHANGELOG.md) for the full naming rationale.
|
|
8
8
|
|
|
9
9
|
---
|
|
10
10
|
|
|
@@ -219,6 +219,7 @@ The editor is `$GUARDRAILS_EDITOR`, then `$VISUAL`, then `$EDITOR`. VS Code-fami
|
|
|
219
219
|
|---|---|
|
|
220
220
|
| `SUGGEST=1` | Write the markdown checklist alongside the text report. |
|
|
221
221
|
| `APPLY=1` | Auto-fix raw_color + tailwind_arbitrary where tokens match. |
|
|
222
|
+
| `SEVERITY=error` / `SEVERITY=warning` | Severity floor. `error` checks errors only; `warning` drops suggestions; default is everything. Applies to the report, the exit code, JSON, HTML, and the TUI alike. Detectors that can only produce muted findings aren't run. An unrecognized value aborts rather than being ignored. |
|
|
222
223
|
| `FORMAT=json` | Emit one JSON document to stdout (all other audit output is suppressed). |
|
|
223
224
|
| `FORMAT=html` / `OUTPUT=path` | Write the self-contained HTML report (default `tmp/guardrails/audit.html`). |
|
|
224
225
|
| `GUARDRAILS_EDITOR=cmd` | Editor `guardrails:tui` opens locations in. Falls back to `$VISUAL`, then `$EDITOR`. |
|
|
@@ -242,6 +243,15 @@ The audit task is a single shell command:
|
|
|
242
243
|
run: bundle exec rake guardrails:audit
|
|
243
244
|
```
|
|
244
245
|
|
|
246
|
+
Adopting Guardrails on an existing codebase usually means a backlog of warnings you can't clear in one PR. Gate on errors now and ratchet down later:
|
|
247
|
+
|
|
248
|
+
```yaml
|
|
249
|
+
- name: Guardrails audit (errors only)
|
|
250
|
+
run: bundle exec rake guardrails:audit SEVERITY=error
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
`SEVERITY=error` exits 1 only when there are error-level findings (raw colors, arbitrary Tailwind values, a11y, visual diffs) and skips the warning- and suggestion-tier detectors entirely, so it's also the fastest way to run the audit. The report ends with a line saying what wasn't checked, so a green run can't be mistaken for a clean codebase.
|
|
254
|
+
|
|
245
255
|
For richer integration:
|
|
246
256
|
|
|
247
257
|
```yaml
|
data/doc/ROADMAP.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Guardrails — Roadmap
|
|
2
2
|
|
|
3
|
-
**Status:** **1.
|
|
4
|
-
**Last updated:** 2026-
|
|
3
|
+
**Status:** **1.4.0 released on RubyGems.org** — V0 ✅, V1 ✅, V2 ✅ (the originally-planned roadmap, published as 1.0.0). Post-1.0 releases have been about reading the findings rather than producing more of them: inline suggestions (1.1.0), then an interactive browser + HTML report (1.3.0).
|
|
4
|
+
**Last updated:** 2026-09-19
|
|
5
5
|
|
|
6
6
|
## Context
|
|
7
7
|
|
|
@@ -116,6 +116,24 @@ This doc tracks shipped vs. planned scope and parks remaining unknowns. V0 + mos
|
|
|
116
116
|
|
|
117
117
|
---
|
|
118
118
|
|
|
119
|
+
## Post-1.0 — Report UX
|
|
120
|
+
|
|
121
|
+
The planned roadmap ended at V2. Running the gem on real codebases (Patchvault: 981 findings) showed the next problem wasn't detection, it was *reading* the output.
|
|
122
|
+
|
|
123
|
+
| Item | Status | Notes |
|
|
124
|
+
|---|---|---|
|
|
125
|
+
| Inline suggestions + triage summary | ✅ shipped (1.1.0) | Every finding carries a `→` action; severity-grouped summary at top and bottom; `[error]` / `[warning]` / `[suggest]` tags; TTY-aware ASCII styling, `NO_COLOR` honored. |
|
|
126
|
+
| Ruby 3.4 / railties 7.2 floor | ✅ shipped (1.2.0) | Compatibility-only release. Ruby 3.2/3.3 or Rails 7.1 users stay on 1.1.0. |
|
|
127
|
+
| Findings data layer | ✅ shipped (1.3.0) | `Guardrails::Report::Run` runs the audit once for every front-end; `Report::Finding` / `Location` / `Category` are detector-agnostic finding data. Every detector implements `categories(result)`, sharing its title / suggestion strings with the text report. New detectors only need to implement that to show up in the TUI and HTML report. |
|
|
128
|
+
| Interactive browser (`rake guardrails:tui`) | ✅ shipped (1.3.0) | Roll-up → category → finding → source, regroup by file, live filter, open in `$EDITOR` at the line, re-run. `io/console` only — no new dependencies. If the hand-rolled rendering stops being enough, swapping in a TUI library means replacing `TUI::Screen` + the loop in `tui.rb`; `TUI::State` (navigation) doesn't know about terminals. |
|
|
129
|
+
| HTML report (`FORMAT=html`) | ✅ shipped (1.3.0) | One self-contained file, no server or assets — opens from disk or as a CI artifact. Generated rather than served: a mounted engine was more surface than the problem needed, and the Lookbook panel already covers in-app. |
|
|
130
|
+
| File-grouped view | ✅ shipped (1.3.0) | `tab` in the TUI. Was a 1.1.0 follow-up. |
|
|
131
|
+
| `SEVERITY=error\|warning` floor | ✅ shipped (1.4.0) | One severity floor across text report, exit code, JSON, HTML, and TUI. Skips detectors that can only produce muted findings. Filtered reports say what wasn't checked; unknown values abort. Supersedes the `--strict` flag dropped in V0. |
|
|
132
|
+
| Lookbook panel on the new data layer | not started | The auto-registered panel still renders from its own `ComponentReport`; it could render `Report::Finding`s and pick up suggestions for free. |
|
|
133
|
+
| CI test workflow | not started | Specs only run inside the release workflow, on one Ruby. A PR-time matrix (3.4 + head) would catch floor regressions before tag time. |
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
119
137
|
## Decisions Captured
|
|
120
138
|
|
|
121
139
|
| Question | Decision |
|
data/lib/guardrails/a11y_deep.rb
CHANGED
|
@@ -5,6 +5,7 @@ require "pathname"
|
|
|
5
5
|
require "set"
|
|
6
6
|
require_relative "report/style"
|
|
7
7
|
require_relative "report/finding"
|
|
8
|
+
require_relative "report/severity"
|
|
8
9
|
|
|
9
10
|
module Guardrails
|
|
10
11
|
# Consumes axe-core JSON output and folds the findings into Guardrails'
|
|
@@ -39,7 +40,9 @@ module Guardrails
|
|
|
39
40
|
# `failing_impacts:` if your rule pack emits custom severities.
|
|
40
41
|
DEFAULT_FAILING_IMPACTS = %w[minor moderate serious critical].freeze
|
|
41
42
|
|
|
42
|
-
def initialize(input:, output: $stdout, failing_impacts: DEFAULT_FAILING_IMPACTS, style: nil
|
|
43
|
+
def initialize(input:, output: $stdout, failing_impacts: DEFAULT_FAILING_IMPACTS, style: nil,
|
|
44
|
+
min_severity: Report::Severity::DEFAULT)
|
|
45
|
+
@min_severity = min_severity
|
|
43
46
|
@input = input
|
|
44
47
|
@output = output
|
|
45
48
|
@failing_impacts = Set.new(failing_impacts.map(&:to_s))
|
|
@@ -47,7 +50,7 @@ module Guardrails
|
|
|
47
50
|
end
|
|
48
51
|
|
|
49
52
|
def run
|
|
50
|
-
findings = parse_input
|
|
53
|
+
findings = parse_input.select { |f| Report::Severity.include?(impact_to_severity(f.impact), @min_severity) }
|
|
51
54
|
print_report(findings)
|
|
52
55
|
findings
|
|
53
56
|
end
|
data/lib/guardrails/audit.rb
CHANGED
|
@@ -7,6 +7,7 @@ require "yaml"
|
|
|
7
7
|
require_relative "erb_parser"
|
|
8
8
|
require_relative "report/style"
|
|
9
9
|
require_relative "report/finding"
|
|
10
|
+
require_relative "report/severity"
|
|
10
11
|
|
|
11
12
|
module Guardrails
|
|
12
13
|
class Audit
|
|
@@ -71,7 +72,9 @@ module Guardrails
|
|
|
71
72
|
flood-color lighting-color stop-color
|
|
72
73
|
].freeze
|
|
73
74
|
|
|
74
|
-
def initialize(root:, output: $stdout, suggest: false, format: :text, apply: false, style: nil
|
|
75
|
+
def initialize(root:, output: $stdout, suggest: false, format: :text, apply: false, style: nil,
|
|
76
|
+
min_severity: Report::Severity::DEFAULT)
|
|
77
|
+
@min_severity = min_severity
|
|
75
78
|
@root = Pathname(root)
|
|
76
79
|
@output = output
|
|
77
80
|
@suggest = suggest
|
|
@@ -82,7 +85,12 @@ module Guardrails
|
|
|
82
85
|
end
|
|
83
86
|
|
|
84
87
|
def run
|
|
85
|
-
|
|
88
|
+
# Filtered before anything else sees them, so the report, the
|
|
89
|
+
# SUGGEST checklist, and the returned list (→ exit code, JSON)
|
|
90
|
+
# all agree on what this run was about.
|
|
91
|
+
violations = collect_files.flat_map { |file| scan_file(file) }.select do |v|
|
|
92
|
+
Report::Severity.include?(SEVERITY_FOR_TYPE.fetch(v.type, :warning), @min_severity)
|
|
93
|
+
end
|
|
86
94
|
print_report(violations)
|
|
87
95
|
remaining = @apply ? apply_auto_fixes(violations) : violations
|
|
88
96
|
write_suggestions(remaining) if @suggest
|
|
@@ -107,7 +107,7 @@
|
|
|
107
107
|
</header>
|
|
108
108
|
|
|
109
109
|
<main class="wrap">
|
|
110
|
-
<p class="meta"><%= pluralize(findings.length, "finding") %> across <%= pluralize(@categories.length, "category", "categories") %> · <span class="mono"><%= h @root %></span> · generated <%= h @generated_at.strftime("%Y-%m-%d %H:%M") %> by ui_guardrails <%= h Guardrails::VERSION %></p>
|
|
110
|
+
<p class="meta"><%= pluralize(findings.length, "finding") %> across <%= pluralize(@categories.length, "category", "categories") %> · <span class="mono"><%= h @root %></span> · generated <%= h @generated_at.strftime("%Y-%m-%d %H:%M") %> by ui_guardrails <%= h Guardrails::VERSION %><% if muted_note %> · <strong><%= h muted_note %></strong><% end %></p>
|
|
111
111
|
|
|
112
112
|
<%- if @categories.empty? -%>
|
|
113
113
|
<p class="clean">✓ No findings — the audit is clean.</p>
|
|
@@ -34,7 +34,10 @@ module Guardrails
|
|
|
34
34
|
|
|
35
35
|
SEVERITY_LABEL = { error: "Error", warning: "Warning", suggestion: "Suggestion" }.freeze
|
|
36
36
|
|
|
37
|
-
|
|
37
|
+
# `muted` — severities the run was told not to check (SEVERITY=).
|
|
38
|
+
# Stated on the page so a filtered report can't pass for a full one.
|
|
39
|
+
def initialize(categories:, root:, generated_at: Time.now, muted: [])
|
|
40
|
+
@muted = muted
|
|
38
41
|
@categories = categories
|
|
39
42
|
@root = Pathname(root).expand_path
|
|
40
43
|
@generated_at = generated_at
|
|
@@ -74,6 +77,12 @@ module Guardrails
|
|
|
74
77
|
end
|
|
75
78
|
end
|
|
76
79
|
|
|
80
|
+
def muted_note
|
|
81
|
+
return nil if @muted.empty?
|
|
82
|
+
|
|
83
|
+
"#{@muted.map { |s| "#{SEVERITY_LABEL[s].downcase}s" }.join(' and ')} not checked"
|
|
84
|
+
end
|
|
85
|
+
|
|
77
86
|
def severity_count(severity)
|
|
78
87
|
findings.count { |f| f.severity == severity }
|
|
79
88
|
end
|
|
@@ -13,6 +13,7 @@ require_relative "../a11y_deep"
|
|
|
13
13
|
require_relative "../visual_diff"
|
|
14
14
|
require_relative "summary"
|
|
15
15
|
require_relative "finding"
|
|
16
|
+
require_relative "severity"
|
|
16
17
|
require_relative "../configuration"
|
|
17
18
|
|
|
18
19
|
module Guardrails
|
|
@@ -33,11 +34,13 @@ module Guardrails
|
|
|
33
34
|
TRUTHY = %w[1 true yes].freeze
|
|
34
35
|
|
|
35
36
|
attr_reader :violations, :stimulus, :similarity, :view_components,
|
|
36
|
-
:a11y, :patterns, :classitis, :a11y_deep, :visual_diff
|
|
37
|
+
:a11y, :patterns, :classitis, :a11y_deep, :visual_diff, :min_severity
|
|
37
38
|
|
|
38
39
|
# Builds a Run from the documented env vars (SUGGEST, APPLY,
|
|
39
40
|
# AXE_JSON, VISUAL_DIFF*, SIMILARITY_THRESHOLD, PATTERN_*,
|
|
40
|
-
# CLASSITIS_
|
|
41
|
+
# CLASSITIS_*, SEVERITY). Raises ArgumentError on a SEVERITY it
|
|
42
|
+
# doesn't recognize — a typo there would otherwise silently
|
|
43
|
+
# un-gate a CI check.
|
|
41
44
|
def self.from_env(root:, style: nil, env: ENV)
|
|
42
45
|
# Visual-diff is opt-in (baselines need deliberate setup). Enabled
|
|
43
46
|
# when either VISUAL_DIFF=1 is set in the env (sidecar mode) or
|
|
@@ -66,7 +69,8 @@ module Guardrails
|
|
|
66
69
|
new(root: root, style: style,
|
|
67
70
|
suggest: truthy?(env["SUGGEST"]), apply: truthy?(env["APPLY"]),
|
|
68
71
|
axe_json: env["AXE_JSON"], visual_diff: visual_diff_on,
|
|
69
|
-
similarity: similarity, patterns: patterns, classitis: classitis
|
|
72
|
+
similarity: similarity, patterns: patterns, classitis: classitis,
|
|
73
|
+
min_severity: Severity.parse(env["SEVERITY"]))
|
|
70
74
|
end
|
|
71
75
|
|
|
72
76
|
def self.truthy?(value)
|
|
@@ -80,7 +84,9 @@ module Guardrails
|
|
|
80
84
|
# uncolored output (what JSON mode wants — the body is discarded).
|
|
81
85
|
def initialize(root:, style: nil, suggest: false, apply: false,
|
|
82
86
|
axe_json: nil, visual_diff: false,
|
|
83
|
-
similarity: {}, patterns: {}, classitis: {}
|
|
87
|
+
similarity: {}, patterns: {}, classitis: {},
|
|
88
|
+
min_severity: Severity::DEFAULT)
|
|
89
|
+
@min_severity = min_severity
|
|
84
90
|
@root = Pathname(root)
|
|
85
91
|
@style = style
|
|
86
92
|
@suggest = suggest
|
|
@@ -94,20 +100,35 @@ module Guardrails
|
|
|
94
100
|
end
|
|
95
101
|
|
|
96
102
|
def call
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
103
|
+
# Detectors whose findings all sit below the severity floor
|
|
104
|
+
# aren't run at all — SEVERITY=error skips the two slowest
|
|
105
|
+
# (similarity, patterns) rather than computing results to
|
|
106
|
+
# discard. Audit and A11yDeep emit mixed severities, so they
|
|
107
|
+
# always run and filter internally.
|
|
108
|
+
@violations = detect(Audit.new(**common, suggest: @suggest, apply: @apply, format: :text,
|
|
109
|
+
min_severity: @min_severity))
|
|
110
|
+
@stimulus = wanted?(:warning) ? detect(StimulusAudit.new(**common)) : StimulusAudit::Result.new(orphaned: [], dead: [])
|
|
111
|
+
@similarity = wanted?(:suggestion) ? detect(PartialSimilarity.new(**common, **@similarity_opts)) : []
|
|
112
|
+
@view_components = if wanted?(:warning) then detect(ViewComponentAudit.new(**common))
|
|
113
|
+
else ViewComponentAudit::Result.new(missing_previews: [], orphan_slots: [])
|
|
114
|
+
end
|
|
101
115
|
@a11y = detect(A11yAudit.new(**common))
|
|
102
|
-
@patterns = detect(CrossCodebasePatterns.new(**common, **@pattern_opts))
|
|
103
|
-
@classitis = detect(ClassItis.new(**common, **@classitis_opts))
|
|
104
|
-
@a11y_deep_runner =
|
|
116
|
+
@patterns = wanted?(:suggestion) ? detect(CrossCodebasePatterns.new(**common, **@pattern_opts)) : []
|
|
117
|
+
@classitis = wanted?(:suggestion) ? detect(ClassItis.new(**common, **@classitis_opts)) : []
|
|
118
|
+
@a11y_deep_runner = if @axe_json
|
|
119
|
+
A11yDeep.new(input: @axe_json, output: @sink, style: @style, min_severity: @min_severity)
|
|
120
|
+
end
|
|
105
121
|
@a11y_deep = @a11y_deep_runner ? detect(@a11y_deep_runner) : []
|
|
106
122
|
@visual_diff_runner = @visual_diff_on ? VisualDiff.new(**common) : nil
|
|
107
123
|
@visual_diff = @visual_diff_runner ? detect(@visual_diff_runner) : []
|
|
108
124
|
self
|
|
109
125
|
end
|
|
110
126
|
|
|
127
|
+
# Severities this run didn't look at (empty by default).
|
|
128
|
+
def muted_severities
|
|
129
|
+
Severity.muted(@min_severity)
|
|
130
|
+
end
|
|
131
|
+
|
|
111
132
|
# Every finding as detector-agnostic data (see Report::Finding),
|
|
112
133
|
# grouped by category and ordered errors → warnings →
|
|
113
134
|
# suggestions, biggest category first — the same order the
|
|
@@ -208,6 +229,10 @@ module Guardrails
|
|
|
208
229
|
result
|
|
209
230
|
end
|
|
210
231
|
|
|
232
|
+
def wanted?(severity)
|
|
233
|
+
Severity.include?(severity, @min_severity)
|
|
234
|
+
end
|
|
235
|
+
|
|
211
236
|
def common
|
|
212
237
|
{ root: @root, output: @sink, style: @style }
|
|
213
238
|
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "summary"
|
|
4
|
+
|
|
5
|
+
module Guardrails
|
|
6
|
+
module Report
|
|
7
|
+
# The severity floor behind SEVERITY=: "only tell me about findings
|
|
8
|
+
# at least this serious". `:suggestion` (the default) is everything;
|
|
9
|
+
# `:warning` mutes suggestions; `:error` mutes warnings too.
|
|
10
|
+
module Severity
|
|
11
|
+
ORDER = Summary::SEVERITY_ORDER
|
|
12
|
+
DEFAULT = :suggestion
|
|
13
|
+
|
|
14
|
+
NAMES = {
|
|
15
|
+
"error" => :error, "errors" => :error,
|
|
16
|
+
"warning" => :warning, "warnings" => :warning,
|
|
17
|
+
"suggestion" => :suggestion, "suggestions" => :suggestion, "suggest" => :suggestion, "all" => :suggestion
|
|
18
|
+
}.freeze
|
|
19
|
+
|
|
20
|
+
module_function
|
|
21
|
+
|
|
22
|
+
# nil / blank is "not set", not an error — same blank-env
|
|
23
|
+
# tolerance as the VISUAL_DIFF_* vars.
|
|
24
|
+
def parse(value)
|
|
25
|
+
text = value.to_s.strip.downcase
|
|
26
|
+
return DEFAULT if text.empty?
|
|
27
|
+
|
|
28
|
+
NAMES.fetch(text) do
|
|
29
|
+
raise ArgumentError, "SEVERITY=#{value} isn't a severity. Use error, warning, or suggestion."
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def include?(severity, floor)
|
|
34
|
+
ORDER.index(severity) <= ORDER.index(floor)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# The severities a floor hides, most serious first.
|
|
38
|
+
def muted(floor)
|
|
39
|
+
ORDER.reject { |severity| include?(severity, floor) }
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
data/lib/guardrails/tui.rb
CHANGED
|
@@ -90,7 +90,7 @@ module Guardrails
|
|
|
90
90
|
end
|
|
91
91
|
|
|
92
92
|
def open_web_report
|
|
93
|
-
path = Report::Html.new(categories: @run.categories, root: @root).write
|
|
93
|
+
path = Report::Html.new(categories: @run.categories, root: @root, muted: @run.muted_severities).write
|
|
94
94
|
opener = Editor.opener(path.to_s)
|
|
95
95
|
opened = opener && system(*opener, out: File::NULL, err: File::NULL)
|
|
96
96
|
@state.notice = "#{opened ? 'Opened' : 'Wrote'} #{path.relative_path_from(Pathname(@root).expand_path)}"
|
data/lib/guardrails/version.rb
CHANGED
data/lib/tasks/guardrails.rake
CHANGED
|
@@ -9,7 +9,7 @@ namespace :guardrails do
|
|
|
9
9
|
Guardrails::Init.new(root: root, force: force).run
|
|
10
10
|
end
|
|
11
11
|
|
|
12
|
-
desc "Audit views and components for UI drift (SUGGEST=1, APPLY=1, FORMAT=json|html)"
|
|
12
|
+
desc "Audit views and components for UI drift (SUGGEST=1, APPLY=1, FORMAT=json|html, SEVERITY=error|warning)"
|
|
13
13
|
task :audit do
|
|
14
14
|
require "guardrails/report/run"
|
|
15
15
|
require "guardrails/report/style"
|
|
@@ -22,7 +22,11 @@ namespace :guardrails do
|
|
|
22
22
|
# (see Report::Run). JSON and HTML modes discard the report body,
|
|
23
23
|
# so they run unstyled.
|
|
24
24
|
report_style = format == :text ? Guardrails::Report::Style.new(io: $stdout) : nil
|
|
25
|
-
run =
|
|
25
|
+
run = begin
|
|
26
|
+
Guardrails::Report::Run.from_env(root: root, style: report_style).call
|
|
27
|
+
rescue ArgumentError => e
|
|
28
|
+
abort e.message
|
|
29
|
+
end
|
|
26
30
|
|
|
27
31
|
if format == :json
|
|
28
32
|
require "json"
|
|
@@ -32,7 +36,7 @@ namespace :guardrails do
|
|
|
32
36
|
# CI artifact. OUTPUT= overrides the default tmp/ location.
|
|
33
37
|
require "guardrails/report/html"
|
|
34
38
|
output = ENV["OUTPUT"].to_s.strip
|
|
35
|
-
path = Guardrails::Report::Html.new(categories: run.categories, root: root)
|
|
39
|
+
path = Guardrails::Report::Html.new(categories: run.categories, root: root, muted: run.muted_severities)
|
|
36
40
|
.write(output.empty? ? Guardrails::Report::Html::DEFAULT_PATH : output)
|
|
37
41
|
$stdout.puts "Guardrails audit: #{run.findings.length} findings → #{path}"
|
|
38
42
|
else
|
|
@@ -45,6 +49,13 @@ namespace :guardrails do
|
|
|
45
49
|
summary.render
|
|
46
50
|
$stdout.write run.body
|
|
47
51
|
summary.render(recap: true)
|
|
52
|
+
# A filtered run that prints "no violations found" must not read
|
|
53
|
+
# as a clean bill of health.
|
|
54
|
+
unless run.muted_severities.empty?
|
|
55
|
+
muted = run.muted_severities.map { |s| "#{s}s" }.join(" and ")
|
|
56
|
+
$stdout.puts ""
|
|
57
|
+
$stdout.puts report_style.colorize("SEVERITY=#{run.min_severity} — #{muted} were not checked.", :dim)
|
|
58
|
+
end
|
|
48
59
|
end
|
|
49
60
|
|
|
50
61
|
exit 1 if run.failing?
|
|
@@ -63,7 +74,7 @@ namespace :guardrails do
|
|
|
63
74
|
runner = -> { Guardrails::Report::Run.from_env(root: root, env: env).call }
|
|
64
75
|
begin
|
|
65
76
|
Guardrails::TUI.new(runner: runner, root: root).start
|
|
66
|
-
rescue Guardrails::TUI::NotInteractive => e
|
|
77
|
+
rescue Guardrails::TUI::NotInteractive, ArgumentError => e
|
|
67
78
|
abort e.message
|
|
68
79
|
end
|
|
69
80
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ui_guardrails
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- John Athayde
|
|
@@ -109,6 +109,7 @@ files:
|
|
|
109
109
|
- lib/guardrails/report/html.rb
|
|
110
110
|
- lib/guardrails/report/html/template.html.erb
|
|
111
111
|
- lib/guardrails/report/run.rb
|
|
112
|
+
- lib/guardrails/report/severity.rb
|
|
112
113
|
- lib/guardrails/report/style.rb
|
|
113
114
|
- lib/guardrails/report/summary.rb
|
|
114
115
|
- lib/guardrails/stimulus_audit.rb
|