ui_guardrails 1.7.0 → 2.0.0.rc1

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.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +254 -46
  3. data/doc/A11Y.md +4 -4
  4. data/doc/PRD.md +7 -7
  5. data/doc/PUBLISHING.md +4 -0
  6. data/doc/ROADMAP.md +30 -10
  7. data/doc/VISUAL-DIFF.md +8 -8
  8. data/exe/guardrails-hook +1 -1
  9. data/exe/ui_guardrails-mcp +32 -0
  10. data/lib/guardrails/a11y_deep.rb +1 -1
  11. data/lib/guardrails/audit/markdown_writer.rb +5 -0
  12. data/lib/guardrails/audit.rb +129 -6
  13. data/lib/guardrails/class_itis.rb +2 -45
  14. data/lib/guardrails/erb_parser.rb +53 -0
  15. data/lib/guardrails/feed_ramp/agent_rules.rb +309 -0
  16. data/lib/guardrails/feed_ramp/components_markdown.rb +135 -0
  17. data/lib/guardrails/feed_ramp/config.rb +113 -0
  18. data/lib/guardrails/feed_ramp/design_tokens.rb +112 -0
  19. data/lib/guardrails/feed_ramp/discovery.rb +201 -0
  20. data/lib/guardrails/feed_ramp/libraries.rb +27 -0
  21. data/lib/guardrails/feed_ramp/manifest.rb +142 -0
  22. data/lib/guardrails/feed_ramp/mcp.rb +325 -0
  23. data/lib/guardrails/feed_ramp/mcp_install.rb +150 -0
  24. data/lib/guardrails/feed_ramp/modern_web.rb +83 -0
  25. data/lib/guardrails/feed_ramp/rails_config.rb +144 -0
  26. data/lib/guardrails/feed_ramp/root_element.rb +361 -0
  27. data/lib/guardrails/feed_ramp/ruby_source.rb +230 -0
  28. data/lib/guardrails/feed_ramp/score.rb +256 -0
  29. data/lib/guardrails/feed_ramp/snippet_check.rb +221 -0
  30. data/lib/guardrails/feed_ramp/usages.rb +174 -0
  31. data/lib/guardrails/feed_ramp.rb +27 -0
  32. data/lib/guardrails/hooks/config_file.rb +28 -2
  33. data/lib/guardrails/hooks/installer.rb +5 -2
  34. data/lib/guardrails/hooks.rb +24 -1
  35. data/lib/guardrails/init/config_writer.rb +21 -4
  36. data/lib/guardrails/init/prompter.rb +1 -1
  37. data/lib/guardrails/railtie.rb +2 -0
  38. data/lib/guardrails/report/detectors.rb +1 -1
  39. data/lib/guardrails/report/run.rb +6 -5
  40. data/lib/guardrails/tokens.rb +9 -9
  41. data/lib/guardrails/tui.rb +2 -2
  42. data/lib/guardrails/version.rb +1 -1
  43. data/lib/tasks/guardrails.rake +16 -156
  44. data/lib/tasks/ui_guardrails.rake +304 -0
  45. metadata +21 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bcb56bb52cb2fe3e2d0bd155eaf5b8563177588b53d34af57f4d125653cd6f47
4
- data.tar.gz: 5803d7480e71fe72cc6964bd424333fb888709d73104f215430a520d09cc4b49
3
+ metadata.gz: c33f6b8ca0162e78f0c45e98b72b6474e0e4d2f8f1be918240fd33e08951233c
4
+ data.tar.gz: 2a08781acbc344a263564b64a825e18fd798537d81bf615e560a54c79575eb20
5
5
  SHA512:
6
- metadata.gz: 18bb8c26caf6971a74cc2a0d585acbcf57fbd27b1f703bc0fcedfa4d038724d0362623addcf06723e152f2f4cd4234829a88ba720dfcf0042a79c027067c27bb
7
- data.tar.gz: b559b93cc20faa99f6af5b7a806117ea5925d6dacee2df064cac8e2fe4499d7763277ce0c6a47944f4c77dd7373d091fd01bba65e21a09b3e1dcd56193628d71
6
+ metadata.gz: 9bf82903a9e3d5401dabc2bfe23e13cbf23392eaf167af810b0c937fbae4f0209a75fa7d0d16fac868d24c864ae94dc61968811851efa0a7e613ff918195c8c5
7
+ data.tar.gz: fad3769d0a40a69377dd5a1d5c31e473837f6717c0e174709ede199dcaff3c7c4e0260826c35c26a752593d7a8f2989dc90a22c75990d4e31ed9e137ab9aa921
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.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.
7
+ **Current release:** 2.0.0, which adds [Feed Ramp](#feed-ramp): your component library handed to coding agents before they write markup, not just drift caught after. 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
 
@@ -33,14 +33,14 @@ Guardrails works two ways: **installed in your project** (auto-loaded via a Rail
33
33
  ```ruby
34
34
  # Gemfile
35
35
  group :development, :test do
36
- gem "ui_guardrails", "~> 1.0"
36
+ gem "ui_guardrails", "~> 2.0"
37
37
  end
38
38
  ```
39
39
 
40
40
  ```bash
41
41
  bundle install
42
- bundle exec rake guardrails:init # writes guardrails.yml, scaffolds MQs
43
- bundle exec rake guardrails:audit # run the full audit
42
+ bundle exec rake ui_guardrails:init # writes guardrails.yml, scaffolds MQs
43
+ bundle exec rake ui_guardrails:audit # run the full audit
44
44
  ```
45
45
 
46
46
  The gem's Railtie auto-loads rake tasks and (when [Lookbook](https://lookbook.build) is also in your Gemfile) registers the **Guardrails** panel inside every preview's inspector.
@@ -56,8 +56,8 @@ cd ~/code/guardrails && bundle install
56
56
  # From your Rails app's root:
57
57
  cd ~/code/my-rails-app
58
58
  bundle exec --gemfile=~/code/guardrails/Gemfile \
59
- rake -f ~/code/guardrails/lib/tasks/guardrails.rake \
60
- guardrails:audit
59
+ rake -f ~/code/guardrails/lib/tasks/ui_guardrails.rake \
60
+ ui_guardrails:audit
61
61
  ```
62
62
 
63
63
  Every rake task accepts the same env vars in sidecar mode as in-project. The tasks default `root` to `Rails.root` when Rails is loaded, else `Dir.pwd` — so just run from the target app's root.
@@ -66,22 +66,58 @@ Every rake task accepts the same env vars in sidecar mode as in-project. The tas
66
66
 
67
67
  ## Quick start
68
68
 
69
+ ### Feed Ramp in 60 seconds
70
+
71
+ Give your coding agents the components you already have:
72
+
73
+ ```bash
74
+ bundle add ui_guardrails --group "development, test"
75
+
76
+ # 1. Component manifest + agent rules: writes .ui_guardrails/manifest.json and
77
+ # COMPONENTS.md, and a managed block in CLAUDE.md and AGENTS.md plus a Cursor rule
78
+ bundle exec rake ui_guardrails:feed_ramp:rules
79
+
80
+ # 2. Optional: the MCP server (search_components, get_component, check_snippet)
81
+ bundle exec rake ui_guardrails:feed_ramp:mcp:install # AGENT=all adds Codex, Cursor and Gemini CLI
82
+
83
+ # 3. Optional: the Modern Web Guidance skill (Google Chrome and contributors), so
84
+ # agents also look up current HTML, CSS and JS practice. Run step 1 again after it
85
+ bundle exec rake ui_guardrails:feed_ramp:modern_web:install
86
+
87
+ # 4. Commit it, so every clone and every agent session has it
88
+ # (and .mcp.json if you ran step 2, the skill's files if you ran step 3)
89
+ git add .ui_guardrails CLAUDE.md AGENTS.md .cursor/rules
90
+
91
+ # 5. In CI: fail when the manifest or rules are out of date, and on drift,
92
+ # including hand-written markup that copies a component (component_match)
93
+ CHECK=1 bundle exec rake ui_guardrails:feed_ramp:rules
94
+ bundle exec rake ui_guardrails:audit
95
+
96
+ # 6. Measure it: score now, again after an agent session, and compare
97
+ bundle exec rake ui_guardrails:feed_ramp:score
98
+ bundle exec rake ui_guardrails:feed_ramp:score BASELINE=last
99
+ ```
100
+
101
+ Nothing boots your app, and ViewComponent, Phlex and Lookbook are all optional. `component_match` is a warning, so it fails the audit; set `feed_ramp.component_match: suggestion` in `guardrails.yml` to see what it catches before it gates builds. Details are in [Feed Ramp](#feed-ramp).
102
+
103
+ ### The audit
104
+
69
105
  ```bash
70
106
  # One-time setup — detects your token stack, writes guardrails.yml,
71
107
  # scaffolds prefers-color-scheme media queries if your stylesheet
72
108
  # doesn't already have them. Interactive when on a TTY; CI-safe
73
109
  # default fallback otherwise.
74
- bundle exec rake guardrails:init
110
+ bundle exec rake ui_guardrails:init
75
111
 
76
112
  # Full audit — exits 1 on any violation.
77
- bundle exec rake guardrails:audit
113
+ bundle exec rake ui_guardrails:audit
78
114
 
79
115
  # Get a markdown checklist of fixable findings:
80
- SUGGEST=1 bundle exec rake guardrails:audit
116
+ SUGGEST=1 bundle exec rake ui_guardrails:audit
81
117
  # → writes doc/guardrails-suggestions-{TIMESTAMP}.md
82
118
 
83
119
  # Auto-fix raw_color and tailwind_arbitrary where a token matches:
84
- APPLY=1 bundle exec rake guardrails:audit
120
+ APPLY=1 bundle exec rake ui_guardrails:audit
85
121
  ```
86
122
 
87
123
  ---
@@ -90,20 +126,27 @@ APPLY=1 bundle exec rake guardrails:audit
90
126
 
91
127
  | Task | What it does |
92
128
  |---|---|
93
- | `guardrails:init` | Stack detection, writes `guardrails.yml`, scaffolds prefers-color-scheme / prefers-contrast media queries. Refuses to overwrite an existing config — `FORCE=1` overrides. |
94
- | `guardrails:audit` | Runs every detector — view drift, stimulus, partial similarity, view-components, a11y, cross-codebase patterns, class-itis. Exits 1 on violations. |
95
- | `guardrails:tui` | The same audit, browsable: a severity roll-up you drill into (category → finding → source), regroup by file, filter, and jump from into your editor or the HTML report. See [Browsing findings](#browsing-findings). |
96
- | `guardrails:icons` | Generates an SVG sprite from `app/assets/images/icons/`, flags inline `<svg>` in views, catches emoji / glyph icons across views + helpers + models + JS + locales, reports unused sprite entries. **Exits 1** on any inline SVG, unknown sprite reference, or emoji/glyph icon (1.5.0). Dead icons stay reporting-only. `SUGGEST=1` prints sprite-name suggestions; `FORMAT=json` emits the machine-readable payload. |
97
- | `guardrails:tokens` | Parses your color and type-scale tokens (CSS vars / SCSS vars / Tailwind v3 config / Tailwind v4 `@theme`), reports hex literals in stylesheets that should reference a token. |
98
- | `guardrails:a11y:deep` | Reads axe-core JSON output and folds it into the unified report. Doesn't run axe itself (no Capybara / headless Chrome runtime deps) — point it at axe output your existing tooling produces. |
99
- | `guardrails:visual:deep` | Consumes screenshot-diff tool output (snap_diff-capybara today; BackstopJS in flight, [#15](https://github.com/meticulous/guardrails/issues/15)) and reports visual regressions. Same parse-only design — your existing test toolchain runs screenshots; Guardrails reports. |
100
- | `guardrails:hooks:install` | Adds Guardrails to coding agents' hooks (Claude Code, Codex, Cursor, Gemini CLI), so drift an agent introduces goes back to it while it works. See [Coding agent hooks](#coding-agent-hooks). |
129
+ | `ui_guardrails:init` | Stack detection, writes `guardrails.yml`, scaffolds prefers-color-scheme / prefers-contrast media queries. Refuses to overwrite an existing config — `FORCE=1` overrides. |
130
+ | `ui_guardrails:audit` | Runs every detector — view drift, stimulus, partial similarity, view-components, a11y, cross-codebase patterns, class-itis. Exits 1 on violations. |
131
+ | `ui_guardrails:tui` | The same audit, browsable: a severity roll-up you drill into (category → finding → source), regroup by file, filter, and jump from into your editor or the HTML report. See [Browsing findings](#browsing-findings). |
132
+ | `ui_guardrails:icons` | Generates an SVG sprite from `app/assets/images/icons/`, flags inline `<svg>` in views, catches emoji / glyph icons across views + helpers + models + JS + locales, reports unused sprite entries. **Exits 1** on any inline SVG, unknown sprite reference, or emoji/glyph icon (1.5.0). Dead icons stay reporting-only. `SUGGEST=1` prints sprite-name suggestions; `FORMAT=json` emits the machine-readable payload. |
133
+ | `ui_guardrails:tokens` | Parses your color and type-scale tokens (CSS vars / SCSS vars / Tailwind v3 config / Tailwind v4 `@theme`), reports hex literals in stylesheets that should reference a token. |
134
+ | `ui_guardrails:a11y:deep` | Reads axe-core JSON output and folds it into the unified report. Doesn't run axe itself (no Capybara / headless Chrome runtime deps) — point it at axe output your existing tooling produces. |
135
+ | `ui_guardrails:visual:deep` | Consumes screenshot-diff tool output (snap_diff-capybara today; BackstopJS in flight, [#15](https://github.com/meticulous/guardrails/issues/15)) and reports visual regressions. Same parse-only design — your existing test toolchain runs screenshots; Guardrails reports. |
136
+ | `ui_guardrails:hooks:install` | Adds Guardrails to coding agents' hooks (Claude Code, Codex, Cursor, Gemini CLI), so drift an agent introduces goes back to it while it works. See [Coding agent hooks](#coding-agent-hooks). |
137
+ | `ui_guardrails:feed_ramp:manifest` | Writes `.ui_guardrails/manifest.json` and `COMPONENTS.md`: the app's components and tokens, for coding agents. `CHECK=1` exits 1 when they're out of date. See [Feed Ramp](#feed-ramp). |
138
+ | `ui_guardrails:feed_ramp:score` | Scores UI drift per 100 templates (hardcoded values, reinvented components, a11y) and saves it under `.ui_guardrails/scores/`. `BASELINE=last` or a path compares two runs; `FORMAT=json`. Never fails. See [Drift score](#drift-score). |
139
+ | `ui_guardrails:feed_ramp:mcp:install` | Adds the Feed Ramp MCP server to `.mcp.json` (Claude Code); `AGENT=codex,cursor,gemini` or `all` for the others. See [MCP server](#mcp-server). |
140
+ | `ui_guardrails:feed_ramp:modern_web:install` | Installs the [Modern Web Guidance](https://developer.chrome.com/docs/modern-web-guidance) skill with its own installer (needs Node.js 22+). The agent rules mention it once it's in the project. See [Modern Web Guidance](#modern-web-guidance). |
141
+ | `ui_guardrails:feed_ramp:rules` | Writes the manifest, then a block pointing coding agents at it in `CLAUDE.md` and `AGENTS.md`, plus `.cursor/rules/ui-guardrails.mdc`. `TARGETS=` picks files, `CHECK=1` exits 1 when anything is out of date, `REMOVE=1` takes the blocks out. See [Feed Ramp](#feed-ramp). |
142
+
143
+ Before 2.0 the tasks were `guardrails:*`. Those names still run the `ui_guardrails:` task, with a deprecation notice, until 3.0.
101
144
 
102
145
  ---
103
146
 
104
147
  ## Detectors
105
148
 
106
- ### View-level drift (`guardrails:audit`)
149
+ ### View-level drift (`ui_guardrails:audit`)
107
150
 
108
151
  Static AST walk over every `.html.erb` under `app/views/` and `app/components/` (via [Herb](https://github.com/marcoroth/herb), the ERB-aware parser):
109
152
 
@@ -113,12 +156,13 @@ Static AST walk over every `.html.erb` under `app/views/` and `app/components/`
113
156
  | `raw_color` | Hex / rgb literals in color-bearing attributes (`fill`, `stroke`, `color`, `bgcolor`, `background`, `data-*color*`, etc.). |
114
157
  | `tailwind_arbitrary` | `bg-[#fa3]`, `text-[14px]`, `p-[7px]` — arbitrary values that bypass the theme. |
115
158
  | `helper_recommended` | `<button>` / `<a>` wrapping `<%= … %>` ERB output. Suggests `tag.button` / `link_to` / `button_to` for clean accessible names. Skips elements that already have `aria-label`. |
159
+ | `component_match` | Hand-written markup copying one of your components: its signature class (`class="button …"` for ButtonComponent) on any tag, or its root tag with most of its classes. Suggests the component's real usage example. Needs a [Feed Ramp](#component-match) manifest; warning by default. |
116
160
  | `image_alt` / `button_name` / `link_name` / `input_label` | The four most common static a11y misses (see [`doc/A11Y.md`](doc/A11Y.md)). |
117
161
 
118
162
  ### Suggest mode and auto-fix
119
163
 
120
- - `SUGGEST=1 rake guardrails:audit` writes `doc/guardrails-suggestions-{TIMESTAMP}.md` — a markdown checklist with one `[ ]` per fixable finding, the rule that fired, and the proposed replacement (closest token by exact match, else near-match within a configurable channel-distance).
121
- - `APPLY=1 rake guardrails:audit` auto-fixes `raw_color` (→ `var(--token)`) and `tailwind_arbitrary` (→ Tailwind theme color shorthand) when a matching token exists. Near-match auto-fix is gated by `near_match_policy` in `guardrails.yml` (`fix` / `leave` / `notify`).
164
+ - `SUGGEST=1 rake ui_guardrails:audit` writes `doc/guardrails-suggestions-{TIMESTAMP}.md` — a markdown checklist with one `[ ]` per fixable finding, the rule that fired, and the proposed replacement (closest token by exact match, else near-match within a configurable channel-distance).
165
+ - `APPLY=1 rake ui_guardrails:audit` auto-fixes `raw_color` (→ `var(--token)`) and `tailwind_arbitrary` (→ Tailwind theme color shorthand) when a matching token exists. Near-match auto-fix is gated by `near_match_policy` in `guardrails.yml` (`fix` / `leave` / `notify`).
122
166
 
123
167
  ### Tokens
124
168
 
@@ -167,10 +211,10 @@ Then scans every other stylesheet for hex literals and reports drift, matching e
167
211
  bundle exec rspec spec/system/
168
212
 
169
213
  # Fold visual-diff findings into the audit:
170
- VISUAL_DIFF=1 bundle exec rake guardrails:audit
214
+ VISUAL_DIFF=1 bundle exec rake ui_guardrails:audit
171
215
  ```
172
216
 
173
- Or standalone via `rake guardrails:visual:deep`. Parse-only — same trade as deep a11y, no Capybara/Chromium runtime deps in the gem. BackstopJS adapter tracked in [#15](https://github.com/meticulous/guardrails/issues/15). See [`doc/VISUAL-DIFF.md`](doc/VISUAL-DIFF.md).
217
+ Or standalone via `rake ui_guardrails:visual:deep`. Parse-only — same trade as deep a11y, no Capybara/Chromium runtime deps in the gem. BackstopJS adapter tracked in [#15](https://github.com/meticulous/guardrails/issues/15). See [`doc/VISUAL-DIFF.md`](doc/VISUAL-DIFF.md).
174
218
 
175
219
  ### Deep a11y via axe-core JSON (0.6.0)
176
220
 
@@ -178,10 +222,10 @@ Or standalone via `rake guardrails:visual:deep`. Parse-only — same trade as de
178
222
 
179
223
  ```bash
180
224
  npx @axe-core/cli http://localhost:3000/ --save axe.json
181
- AXE_JSON=axe.json bundle exec rake guardrails:audit
225
+ AXE_JSON=axe.json bundle exec rake ui_guardrails:audit
182
226
  ```
183
227
 
184
- Or standalone via `rake guardrails:a11y:deep`. Stays parse-only — your existing test toolchain runs axe (axe-core-rspec, the CLI, Puppeteer, a CDP script — anything that emits axe v4 JSON), Guardrails provides the merge + report. See [`doc/A11Y.md`](doc/A11Y.md).
228
+ Or standalone via `rake ui_guardrails:a11y:deep`. Stays parse-only — your existing test toolchain runs axe (axe-core-rspec, the CLI, Puppeteer, a CDP script — anything that emits axe v4 JSON), Guardrails provides the merge + report. See [`doc/A11Y.md`](doc/A11Y.md).
185
229
 
186
230
  ### Lookbook auto-panel (0.5.0)
187
231
 
@@ -189,7 +233,7 @@ When [Lookbook](https://lookbook.build) is in the Gemfile, Guardrails auto-regis
189
233
 
190
234
  ### Emoji and glyph icons (1.5.0)
191
235
 
192
- The `guardrails:icons` task now catches emoji and Unicode-dingbat glyphs used as UI iconography — the most common way AI-assisted code bypasses the sprite. Two tiers, both on by default:
236
+ The `ui_guardrails:icons` task now catches emoji and Unicode-dingbat glyphs used as UI iconography — the most common way AI-assisted code bypasses the sprite. Two tiers, both on by default:
193
237
 
194
238
  | Tier | What matches | Example |
195
239
  |---|---|---|
@@ -200,7 +244,7 @@ Grapheme-cluster iteration counts VS16-styled pictographs / ZWJ families / flag
200
244
 
201
245
  **Scans**, by default: `app/views`, `app/components`, `app/helpers`, `app/models`, `app/presenters`, `app/javascript`, and `config/locales` — where the emoji-as-icon pattern usually lives (a helper returning `"📄"` per content type, a Stimulus controller setting `textContent = "✓"`). Comments are masked per file type: Prism-parsed for `.rb`, `<%# %>` for ERB, `//` and `/* */` for JS/SCSS/CSS, `#` for YAML. Line and column stay accurate.
202
246
 
203
- **Config** in `guardrails.yml` (defaults shown; `guardrails:init` writes this block for you):
247
+ **Config** in `guardrails.yml` (defaults shown; `ui_guardrails:init` writes this block for you):
204
248
 
205
249
  ```yaml
206
250
  guardrails:
@@ -227,11 +271,11 @@ Works on the same line or the preceding line, in Ruby `#` comments, ERB `<%# %>`
227
271
 
228
272
  **Suggestions** with `SUGGEST=1`: when the consumer's sprite contains a conventional name for the flagged emoji (`📄 → check for icon-file or icon-document`, `✓ → icon-check` / `icon-checkmark`), the task prints `→ use <use href="#icon-document"/>`. When no equivalent exists, it prints `→ no sprite equivalent — add one`. Only names the consumer's `collect_icon_names` includes are surfaced. No auto-fix path (`APPLY=1`) — the right sprite name depends on the consumer's semantics.
229
273
 
230
- **No-fix policy**: emoji contribute to `guardrails:icons` exit code (part of Result#violations?). Dead icons stay reporting-only — deleting a file is a human decision.
274
+ **No-fix policy**: emoji contribute to `ui_guardrails:icons` exit code (part of Result#violations?). Dead icons stay reporting-only — deleting a file is a human decision.
231
275
 
232
276
  ### Detectors from other gems
233
277
 
234
- Other gems add detectors to `guardrails:audit` with `Guardrails::Report::Detectors.register(name, klass)`. They run after the built-ins and show up in the text report, the summary, `FORMAT=json` (under `extensions.<name>`), `FORMAT=html` and the TUI. The class answers:
278
+ Other gems add detectors to `ui_guardrails:audit` with `Guardrails::Report::Detectors.register(name, klass)`. They run after the built-ins and show up in the text report, the summary, `FORMAT=json` (under `extensions.<name>`), `FORMAT=html` and the TUI. The class answers:
235
279
 
236
280
  | Method | Does |
237
281
  | --- | --- |
@@ -253,7 +297,7 @@ Registration checks the class has all of these. Detectors only read what's alrea
253
297
  Every task prints a human-readable text report by default and exits 1 when violations or failing findings exist. For machine consumption:
254
298
 
255
299
  ```bash
256
- FORMAT=json bundle exec rake guardrails:audit > findings.json
300
+ FORMAT=json bundle exec rake ui_guardrails:audit > findings.json
257
301
  ```
258
302
 
259
303
  The JSON payload has a `summary:` block with finding counts per category plus per-detector arrays — see `Guardrails::Report::Run#to_h` for the exact shape.
@@ -263,11 +307,11 @@ The JSON payload has a `summary:` block with finding counts per category plus pe
263
307
  On a real codebase the text report is long (one of our test apps produces 981 findings). Two ways to navigate it instead of scrolling it:
264
308
 
265
309
  ```bash
266
- bundle exec rake guardrails:tui # interactive, in the terminal
267
- FORMAT=html bundle exec rake guardrails:audit # one self-contained HTML file
310
+ bundle exec rake ui_guardrails:tui # interactive, in the terminal
311
+ FORMAT=html bundle exec rake ui_guardrails:audit # one self-contained HTML file
268
312
  ```
269
313
 
270
- **`guardrails:tui`** opens on the same severity roll-up the text report leads with. `⏎` drills in — category → findings → a single finding with its suggestion, every location, and the source around it. `tab` regroups the whole thing by file (the files with the most findings first), `/` filters across category, title, suggestion, and path, `o` opens the highlighted location in your editor at the right line, `w` writes and opens the HTML report, `r` re-runs the audit, `?` lists the rest. It takes the same env vars as `guardrails:audit` except `APPLY` and `SUGGEST` — browsing never rewrites files. No new dependencies: it's built on Ruby's bundled `io/console`.
314
+ **`ui_guardrails:tui`** opens on the same severity roll-up the text report leads with. `⏎` drills in — category → findings → a single finding with its suggestion, every location, and the source around it. `tab` regroups the whole thing by file (the files with the most findings first), `/` filters across category, title, suggestion, and path, `o` opens the highlighted location in your editor at the right line, `w` writes and opens the HTML report, `r` re-runs the audit, `?` lists the rest. It takes the same env vars as `ui_guardrails:audit` except `APPLY` and `SUGGEST` — browsing never rewrites files. No new dependencies: it's built on Ruby's bundled `io/console`.
271
315
 
272
316
  The editor is `$GUARDRAILS_EDITOR`, then `$VISUAL`, then `$EDITOR`. VS Code-family, Zed, Sublime, TextMate, JetBrains, Helix, and vim/nvim/nano/emacs all open at the finding's line; anything else gets the file path. With no editor set, it falls back to `open` / `xdg-open`.
273
317
 
@@ -282,10 +326,10 @@ The editor is `$GUARDRAILS_EDITOR`, then `$VISUAL`, then `$EDITOR`. VS Code-fami
282
326
  | `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. |
283
327
  | `FORMAT=json` | Emit one JSON document to stdout (all other audit output is suppressed). |
284
328
  | `FORMAT=html` / `OUTPUT=path` | Write the self-contained HTML report (default `tmp/guardrails/audit.html`). |
285
- | `GUARDRAILS_EDITOR=cmd` | Editor `guardrails:tui` opens locations in. Falls back to `$VISUAL`, then `$EDITOR`. |
329
+ | `GUARDRAILS_EDITOR=cmd` | Editor `ui_guardrails:tui` opens locations in. Falls back to `$VISUAL`, then `$EDITOR`. |
286
330
  | `FORCE=1` | Bypass `init`'s refuse-to-overwrite default. |
287
331
  | `AXE_JSON=path` | Fold axe-core findings into the unified report. |
288
- | `VISUAL_DIFF=1` | Fold visual-diff findings into `guardrails:audit`. Embedded installs can flip this on permanently via `Guardrails.configure { \|c\| c.visual_diff.enabled = true }`. |
332
+ | `VISUAL_DIFF=1` | Fold visual-diff findings into `ui_guardrails:audit`. Embedded installs can flip this on permanently via `Guardrails.configure { \|c\| c.visual_diff.enabled = true }`. |
289
333
  | `VISUAL_DIFF_DIR=path` / `VISUAL_DIFF_THRESHOLD=0.02` | Tune the visual-diff snap_diff adapter and mismatch threshold. |
290
334
  | `SIMILARITY_THRESHOLD=0.85` | Override the partial-similarity Jaccard threshold. |
291
335
  | `PATTERN_MIN_SIZE=8` / `PATTERN_MIN_OCCURRENCES=4` | Tune the cross-codebase pattern detector. |
@@ -293,12 +337,169 @@ The editor is `$GUARDRAILS_EDITOR`, then `$VISUAL`, then `$EDITOR`. VS Code-fami
293
337
 
294
338
  ---
295
339
 
340
+ ## Feed Ramp
341
+
342
+ The audit catches drift after it's written. Feed Ramp gives coding agents the app's real component library first, so they render the components you have instead of reinventing them.
343
+
344
+ ```bash
345
+ bundle exec rake ui_guardrails:feed_ramp:rules # manifest + agent rules (CLAUDE.md, AGENTS.md, Cursor)
346
+ CHECK=1 bundle exec rake ui_guardrails:feed_ramp:rules # in CI: exit 1 if any of them is out of date
347
+ bundle exec rake ui_guardrails:feed_ramp:manifest # just .ui_guardrails/manifest.json + COMPONENTS.md
348
+ ```
349
+
350
+ The manifest lists every ViewComponent and Phlex component: class, file, template, constructor params with their defaults, slots, the variants and states from its Lookbook / ViewComponent previews, its doc comment, and one usage example. It also lists the design tokens: everything `ui_guardrails:tokens` reads, plus custom properties in the `:root { }` and Tailwind v4 `@theme { }` blocks of your stylesheets.
351
+
352
+ - **No app boot, network or model call.** Everything is read by parsing the source with Prism. Neither ViewComponent, Phlex nor Lookbook has to be installed: a class is a component when its superclass chain (through your own `ApplicationComponent` or `Components::Base`) reaches `ViewComponent::Base` or `Phlex::HTML`.
353
+ - **Examples are copied, never written.** Each component's example is the shortest render of it (`render X.new(…)`, or a Phlex kit call like `Alert(…)`) in a visible preview, or else in a template or Ruby view, copied verbatim. A usage that names the component in full wins, since it works in any view. A component nothing renders gets no example.
354
+ - **Deterministic.** Same source, byte-identical files: no timestamps, everything sorted. Commit both files so agents on a fresh clone have them and component API changes show up in review. `CHECK=1` rebuilds and compares, so it fails only when the committed files are actually wrong, not on every view edit.
355
+ - **Phlex slots are a heuristic.** Phlex has no slot declarations, so public methods taking a block are listed instead and marked `heuristic`.
356
+
357
+ ### Agent rules
358
+
359
+ `ui_guardrails:feed_ramp:rules` writes the manifest, then tells agents it exists, in the files they read at the start of every session:
360
+
361
+ | Target | File | |
362
+ | --- | --- | --- |
363
+ | `claude` | `CLAUDE.md` | default |
364
+ | `agents` | `AGENTS.md` (Codex, Cursor, and others) | default |
365
+ | `cursor` | `.cursor/rules/ui-guardrails.mdc` | default; attached when the agent works in `app/views` or a component path |
366
+ | `gemini` | `GEMINI.md` | opt in |
367
+ | `copilot` | `.github/copilot-instructions.md` | opt in |
368
+
369
+ Pick them with `TARGETS=claude,agents,copilot` or `rules_targets:` in `guardrails.yml`. The block names the components (up to 50) and token categories, and says to render an existing component before writing markup by hand.
370
+
371
+ - **Everything outside the markers is kept.** The block sits between `<!-- ui_guardrails:feed_ramp:begin -->` and `<!-- ui_guardrails:feed_ramp:end -->`; only that part is ever rewritten. A file without the markers gets the block at the end, and a second run with the same manifest changes nothing. Move the block anywhere in the file and it stays there.
372
+ - **Broken markers stop the run.** With an unpaired, duplicated or reversed marker, nothing is written and the error names the file.
373
+ - **One copy per file.** When `CLAUDE.md` imports `@AGENTS.md`, or is a link to it, the block only goes in `AGENTS.md`.
374
+ - **`REMOVE=1`** takes the blocks out and deletes the files that held nothing else.
375
+
376
+ The Cursor rule file is Feed Ramp's own and is rewritten whole. If a file at that path wasn't written by Feed Ramp, it's left alone and the run fails.
377
+
378
+ When [agent hooks](#coding-agent-hooks) are installed, each new session also gets a one-line pointer to `COMPONENTS.md`.
379
+
380
+ ### MCP server
381
+
382
+ `ui_guardrails-mcp` serves the manifest to coding agents as [MCP](https://modelcontextprotocol.io) tools over stdio:
383
+
384
+ | Tool | Gives |
385
+ | --- | --- |
386
+ | `list_components` | every component in brief (kind, one-line description, params, slots), and the token names by category |
387
+ | `get_component(name)` | one component's full entry: params with defaults, slots, previews, usage example. `Badge` finds `Admin::BadgeComponent` |
388
+ | `search_components(query)` | components ranked by name, description, params, slots and preview labels |
389
+ | `check_snippet(source, language?, path?)` | for ERB: the audit's and static a11y's findings, `component_match` included. For ERB and Ruby (Phlex): every `SomeComponent.new(...)` and Phlex kit call (`Alert(...)`) checked against the manifest (unknown component, keyword it doesn't take, required one missing, wrong positional count, and a likely typo a `**options` param would swallow) |
390
+
391
+ ```bash
392
+ bundle exec rake ui_guardrails:feed_ramp:mcp:install # .mcp.json (Claude Code)
393
+ AGENT=all bundle exec rake ui_guardrails:feed_ramp:mcp:install # + .codex/config.toml, .cursor/mcp.json, .gemini/settings.json
394
+ ```
395
+
396
+ The install keeps every other server and setting in those files and is safe to run again. It uses `bundle exec` when your Gemfile has the gem, and the bare executable otherwise. Cursor's entry passes `--root ${workspaceFolder}`, since Cursor doesn't promise to start servers in the project. Claude Code asks you to approve a project server the first time, and Codex reads `.codex/config.toml` only in projects you've trusted. In that file the install owns the `[mcp_servers.ui_guardrails]` table and leaves every other line as it was.
397
+
398
+ The server never boots the app. It builds the manifest from source when it starts and rebuilds it whenever a component, preview, view, stylesheet or `guardrails.yml` is added, removed or changed. If that changes the committed files it rewrites `.ui_guardrails/`, so `component_match` sees the same components. Pass `--no-write` (or `WRITE=0` for `rake ui_guardrails:feed_ramp:mcp`) to leave them alone. Once a project config starts the server, `ui_guardrails:feed_ramp:rules` mentions the tools in the agent rules block.
399
+
400
+ ### Modern Web Guidance
401
+
402
+ Feed Ramp tells agents what your app already has. [Modern Web Guidance](https://developer.chrome.com/docs/modern-web-guidance) tells them what the web platform already has: a skill that has an agent look up current practice for dialogs, popovers, forms, layout, animation, performance and accessibility before it writes HTML, CSS or client-side JavaScript, instead of the older patterns in its training data.
403
+
404
+ ```bash
405
+ bundle exec rake ui_guardrails:feed_ramp:modern_web:install # runs `npx -y modern-web-guidance@latest install`
406
+ bundle exec rake ui_guardrails:feed_ramp:rules # the rules now mention the skill
407
+ ```
408
+
409
+ - **Their installer, not a copy.** The task runs Modern Web Guidance's own interactive install in your project and nothing of it ships in this gem, so the guides are always theirs and current. It needs Node.js 22 or later. `ARGS="…"` is passed through to the installer.
410
+ - **Your library comes first.** Once the skill is in a project skills directory (`.claude/skills`, `.agents/skills` and the like), the agent rules block tells agents to search it for platform HTML, CSS and JavaScript, and to use it where no component of yours does the job and inside the components themselves.
411
+ - **Commit the skill** with the rules, or `CHECK=1` in CI sees rules that mention a skill the checkout doesn't have. If your team installs it per user or as an agent plugin instead, set `feed_ramp.modern_web_guidance: true` in `guardrails.yml`; `false` keeps it out of the rules.
412
+ - **Telemetry is theirs.** The Modern Web Guidance CLI sends Google anonymous usage data: installs, the calling agent, guide IDs and the agent's search queries. The task says so before it runs. `DISABLE_TELEMETRY=1` in your shell profile opts out.
413
+
414
+ Modern Web Guidance is by Google Chrome and its contributors, stewarded with the Microsoft Edge, Mozilla Firefox and MDN teams. Its code is Apache-2.0 and its guides are CC BY 4.0. Source: [GoogleChrome/modern-web-guidance](https://github.com/GoogleChrome/modern-web-guidance).
415
+
416
+ ### Component match
417
+
418
+ Once a manifest exists, `ui_guardrails:audit` (and so the agent hooks and the Lookbook panel) flags markup that copies one of your components instead of rendering it:
419
+
420
+ ```
421
+ ! WARNING — component_match (1 finding)
422
+ [warning] component_match: <button type="button" class="button button--primary"> looks like ButtonComponent
423
+ → render ButtonComponent instead, e.g. render(ButtonComponent.new(label: "Save"))
424
+ app/views/welcome/broken.html.erb:42:1
425
+ ```
426
+
427
+ Each component's root element is read when the manifest is built: the first element of its ERB template (or the first `<%= tag.div … %>` it opens with), or the first tag its Phlex `view_template` or ViewComponent `call` builds, following the component's own methods. Its classes are the ones it always has, read without rendering: literal strings, and the unconditional string arguments of a class helper (`class_names("kbd", "kbd--bare": @bare)` and `build_css("button", …)` give `kbd` and `button`), followed through methods such as `class: css_classes`, `**wrapper_attributes` or `attrs[:class] = …`. Conditional and interpolated classes (`btn--<%= @variant %>`, `alert--#{@tone}`) don't count. Markup matches with either:
428
+
429
+ - **the component's signature class, on any tag:** a root class named after the component (`button` or `button-component` for ButtonComponent, `icon-button` for IconButtonComponent), even on its own. Any tag, because a button component may render a link; or
430
+ - **the same tag and most of a long class list:** at least `coverage` (0.8) of the root's classes, when the root has at least `min_classes` (3). Shorter lists of generic utilities never match.
431
+
432
+ A component whose classes all depend on its arguments (a `class:` it's given, or one picked in an `if`) has nothing to match on.
433
+
434
+ A component's own template is never matched against itself. The rule reads the committed manifest rather than parsing the app, so it adds nothing noticeable to a hook call, and it says nothing until `ui_guardrails:feed_ramp:manifest` has run.
435
+
436
+ It's a **warning**, so it fails `ui_guardrails:audit` like any other warning. To try it out first, lower it to a suggestion, which reports without failing:
437
+
438
+ ```yaml
439
+ guardrails:
440
+ feed_ramp:
441
+ component_match: suggestion # or warning (default), error, off
442
+ # or, to tune matching too:
443
+ # component_match:
444
+ # severity: suggestion
445
+ # min_classes: 3
446
+ # coverage: 0.8
447
+ ```
448
+
449
+ ### Drift score
450
+
451
+ ```bash
452
+ bundle exec rake ui_guardrails:feed_ramp:score # before
453
+ # ...an agent session, a branch, turning Feed Ramp on...
454
+ bundle exec rake ui_guardrails:feed_ramp:score BASELINE=last # after, compared with the last saved run
455
+ ```
456
+
457
+ ```
458
+ UI drift score: 116.7 issues per 100 templates (7 issues in 6 templates)
459
+
460
+ Hardcoded values 1 stylesheet_color 1
461
+ Reinvented components 4 class_itis 4
462
+ Accessibility 2 image_alt 1, link_name 1
463
+
464
+ Against 2026-10-19T12:00:00Z, 3f2c1aa: 8 → 7 issues (−1), 133.3 → 116.7 per 100 templates
465
+ Hardcoded values 3 → 1 (−2)
466
+ Reinvented components 4 → 4 (0)
467
+ Accessibility 1 → 2 (+1)
468
+ Most changed: app/views/pages/home.html.erb −3, app/views/pages/about.html.erb +2
469
+ ```
470
+
471
+ | Bucket | Counts |
472
+ | --- | --- |
473
+ | Hardcoded values | raw colours, arbitrary Tailwind values and inline styles in templates; hex literals in stylesheets outside the token files |
474
+ | Reinvented components | markup copying a component (`component_match`), each occurrence of a class-itis cluster or repeated markup, and each pair of near-identical partials |
475
+ | Accessibility | static a11y findings (missing alt text, unnamed buttons and links, unlabelled inputs) |
476
+
477
+ The headline is issues per 100 templates, so apps and branches of different sizes compare. Templates are the ones `ui_guardrails:audit` scans: its `scan_paths` and `ignore` apply to every bucket.
478
+
479
+ Each run is saved as JSON under `.ui_guardrails/scores/` (which keeps itself out of git) with the totals, per-type and per-file counts, the git commit, the gem version and a digest of `guardrails.yml`. `BASELINE=` takes `last` or a path to any saved score, and the comparison says when the config or gem version differ between the two. To keep runs comparable it uses the detectors' default thresholds and ignores the audit's env vars. It always exits 0; gate CI with `ui_guardrails:audit`.
480
+
481
+ Config in `guardrails.yml` (all optional; defaults shown):
482
+
483
+ ```yaml
484
+ guardrails:
485
+ feed_ramp:
486
+ component_paths: [app/components, app/views/components]
487
+ preview_paths: [test/components/previews, spec/components/previews, lookbook/previews]
488
+ token_files: [] # extra files whose custom properties are all tokens
489
+ output_dir: .ui_guardrails
490
+ rules_targets: [claude, agents, cursor] # also: gemini, copilot
491
+ ```
492
+
493
+ Preview directories your Rails config adds are found too, without booting the app: `config.view_component.previews.paths << Rails.root.join("…").to_s` (or `preview_paths`, `+=`, `concat`) and `config.lookbook.preview_paths` in `config/application.rb` or `config/environments/{development,test}.rb`, written as strings or built from `Rails.root` or `__dir__`. List any computed at boot here.
494
+
495
+ ---
496
+
296
497
  ## Coding agent hooks
297
498
 
298
499
  ```bash
299
- bundle exec rake guardrails:hooks:install # Claude Code
300
- AGENT=claude,codex bundle exec rake guardrails:hooks:install
301
- AGENT=all bundle exec rake guardrails:hooks:install # Claude Code, Codex, Cursor, Gemini CLI
500
+ bundle exec rake ui_guardrails:hooks:install # Claude Code
501
+ AGENT=claude,codex bundle exec rake ui_guardrails:hooks:install
502
+ AGENT=all bundle exec rake ui_guardrails:hooks:install # Claude Code, Codex, Cursor, Gemini CLI
302
503
  ```
303
504
 
304
505
  This adds Guardrails to each agent's project hook config. It keeps whatever else is in that file and is safe to run again. Each hook calls `guardrails-hook <agent> <event>`, which runs only the parse-only checks: view drift (raw colours, Tailwind arbitrary values, inline styles, helper recommendations) and static a11y. A typical call takes a fraction of a second.
@@ -309,7 +510,7 @@ It reports only what the agent introduced. A template is compared with its versi
309
510
  | --- | --- |
310
511
  | After the agent edits a template | The drift it just introduced goes back to it as context. Nothing is blocked. |
311
512
  | When it tries to finish | New **errors** in any template changed since `HEAD` send it back to work, once per turn. Warnings alone don't. |
312
- | When a session starts | Notes from other gems (ui_guardrails-browser adds open visual-review rejections). |
513
+ | When a session starts | Where the [Feed Ramp](#feed-ramp) component list is, when the app has one, and notes from other gems (ui_guardrails-browser adds open visual-review rejections). |
313
514
 
314
515
  | Agent | Config | Notes |
315
516
  | --- | --- | --- |
@@ -318,7 +519,7 @@ It reports only what the agent introduced. A template is compared with its versi
318
519
  | Cursor | `.cursor/hooks.json` | `stop` and `sessionStart` only: Cursor's after-edit output doesn't reliably reach the model, so the stop check covers every changed template. Cursor also runs `.claude/settings.json` hooks; the hook recognises Cursor's input and doesn't run twice. |
319
520
  | Gemini CLI | `.gemini/settings.json` | `AfterTool` (`write_file`, `replace`), `AfterAgent`, `SessionStart` |
320
521
 
321
- The hook never breaks the agent's turn: if anything goes wrong it prints `{}`, explains on stderr, and exits 0. Other gems add to the same events with `Guardrails::Hooks.register(name, contributor)`, shipped at `lib/guardrails/hook_contributors/<name>.rb` so the hook finds them without booting the app.
522
+ The hook never breaks the agent's turn: if anything goes wrong it prints `{}`, explains on stderr, and exits 0. The installed command ends in `|| echo '{}'`, so the same holds when the hook can't start at all. That happens on a branch whose Gemfile doesn't have the gem, since the agent's config usually isn't tracked by git and outlives the branch. Running the install again replaces an earlier install's hooks instead of adding new ones beside them. Other gems add to the same events with `Guardrails::Hooks.register(name, contributor)`, shipped at `lib/guardrails/hook_contributors/<name>.rb` so the hook finds them without booting the app.
322
523
 
323
524
  ---
324
525
 
@@ -329,14 +530,14 @@ The audit task is a single shell command:
329
530
  ```yaml
330
531
  # .github/workflows/ci.yml
331
532
  - name: Guardrails audit
332
- run: bundle exec rake guardrails:audit
533
+ run: bundle exec rake ui_guardrails:audit
333
534
  ```
334
535
 
335
536
  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:
336
537
 
337
538
  ```yaml
338
539
  - name: Guardrails audit (errors only)
339
- run: bundle exec rake guardrails:audit SEVERITY=error
540
+ run: bundle exec rake ui_guardrails:audit SEVERITY=error
340
541
  ```
341
542
 
342
543
  `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.
@@ -345,7 +546,7 @@ For richer integration:
345
546
 
346
547
  ```yaml
347
548
  - name: Guardrails audit (JSON)
348
- run: bundle exec rake guardrails:audit FORMAT=json > findings.json
549
+ run: bundle exec rake ui_guardrails:audit FORMAT=json > findings.json
349
550
  continue-on-error: true
350
551
 
351
552
  - name: Upload findings
@@ -359,7 +560,7 @@ For richer integration:
359
560
 
360
561
  ## Configuration
361
562
 
362
- `guardrails.yml` at the repo root. `guardrails:init` writes a sensible default after detecting your stack — what's below is annotated to show every available key:
563
+ `guardrails.yml` at the repo root. `ui_guardrails:init` writes a sensible default after detecting your stack — what's below is annotated to show every available key:
363
564
 
364
565
  ```yaml
365
566
  guardrails:
@@ -391,7 +592,7 @@ guardrails:
391
592
 
392
593
  ## Demo app
393
594
 
394
- [`examples/demo`](examples/demo) is a bootable Rails 7.2 app with intentionally seeded findings — every detector trips at least once. Clone, `bin/setup`, `bin/rails server`, then visit `/`, `/broken`, and `/rails/lookbook` to see the auto-registered Guardrails panel. `bundle exec rake guardrails:audit` runs the full audit against the seeded tree. See [`examples/demo/README.md`](examples/demo/README.md).
595
+ [`examples/demo`](examples/demo) is a bootable Rails 7.2 app with intentionally seeded findings — every detector trips at least once. Clone, `bin/setup`, `bin/rails server`, then visit `/`, `/broken`, and `/rails/lookbook` to see the auto-registered Guardrails panel. `bundle exec rake ui_guardrails:audit` runs the full audit against the seeded tree. See [`examples/demo/README.md`](examples/demo/README.md).
395
596
 
396
597
  ---
397
598
 
@@ -409,7 +610,7 @@ Full status table and decision log: [`doc/ROADMAP.md`](doc/ROADMAP.md).
409
610
 
410
611
  ```bash
411
612
  bundle install
412
- bundle exec rspec # 453 examples
613
+ bundle exec rspec # 883 examples
413
614
  ```
414
615
 
415
616
  Real-world signal lives in `examples/demo` (integration spec) and in the dogfood patches against four real codebases (Patchvault, Talos, Forem, Avo — see `CHANGELOG.md` for what each round caught).
@@ -418,6 +619,13 @@ Issues and PRs: <https://github.com/meticulous/guardrails>
418
619
 
419
620
  ---
420
621
 
622
+ ## Credits
623
+
624
+ - [Modern Web Guidance](https://developer.chrome.com/docs/modern-web-guidance), by Google Chrome and its contributors with the Microsoft Edge, Mozilla Firefox and MDN teams (Apache-2.0; guides CC BY 4.0). Feed Ramp installs it with its own installer and points agents at it; none of it is bundled.
625
+ - [Herb](https://github.com/marcoroth/herb), the ERB parser every template detector is built on.
626
+
627
+ ---
628
+
421
629
  ## License
422
630
 
423
631
  MIT
data/doc/A11Y.md CHANGED
@@ -1,12 +1,12 @@
1
1
  # Accessibility (a11y) checks
2
2
 
3
- Guardrails ships **static** a11y checks — element-level rules that can be answered from your view source without rendering. These run as part of `rails guardrails:audit` and contribute to the exit code.
3
+ Guardrails ships **static** a11y checks — element-level rules that can be answered from your view source without rendering. These run as part of `rails ui_guardrails:audit` and contribute to the exit code.
4
4
 
5
5
  For full a11y coverage (color contrast, focus order, ARIA tree, dynamic content), wire **axe-core** alongside Guardrails in your test suite. Static checks catch the obvious template-level mistakes; runtime checks catch everything else.
6
6
 
7
7
  ## Static checks (built-in)
8
8
 
9
- Run via `rails guardrails:audit`. Findings appear under "Guardrails a11y" in the report and as `a11y` in `FORMAT=json` output.
9
+ Run via `rails ui_guardrails:audit`. Findings appear under "Guardrails a11y" in the report and as `a11y` in `FORMAT=json` output.
10
10
 
11
11
  | Rule | Catches |
12
12
  |---|---|
@@ -27,13 +27,13 @@ npx @axe-core/cli http://localhost:3000/ http://localhost:3000/dashboard --save
27
27
  # or via axe-core-rspec, axe-puppeteer, your CDP script — anything that emits axe JSON v4
28
28
 
29
29
  # Hand the JSON to Guardrails:
30
- AXE_JSON=axe.json bundle exec rake guardrails:audit
30
+ AXE_JSON=axe.json bundle exec rake ui_guardrails:audit
31
31
  ```
32
32
 
33
33
  Or run the deep check standalone:
34
34
 
35
35
  ```bash
36
- AXE_JSON=axe.json bundle exec rake guardrails:a11y:deep
36
+ AXE_JSON=axe.json bundle exec rake ui_guardrails:a11y:deep
37
37
  ```
38
38
 
39
39
  The report groups findings by URL and prints rule + impact + selector + help URL per node. Exit code is 1 when any finding's impact is in the failing set (default: `minor`, `moderate`, `serious`, `critical` — i.e. any impact fails). The full payload is also included in `FORMAT=json` output under the `a11y_deep` key.
data/doc/PRD.md CHANGED
@@ -53,7 +53,7 @@ Rails developers without dedicated designers have no tooling to catch this befor
53
53
 
54
54
  ## Features
55
55
 
56
- ### 1. Component Audit (`rails guardrails:audit`)
56
+ ### 1. Component Audit (`rails ui_guardrails:audit`)
57
57
 
58
58
  Scans views and partials for:
59
59
  - Near-duplicate partials (structural similarity, not just naming)
@@ -63,14 +63,14 @@ Scans views and partials for:
63
63
 
64
64
  Outputs: summary report to stdout + optional JSON/HTML report
65
65
 
66
- ### 2. Icon Management (`rails guardrails:icons`)
66
+ ### 2. Icon Management (`rails ui_guardrails:icons`)
67
67
 
68
68
  - Scans configured SVG source directory
69
69
  - Generates optimized SVG sprite sheet
70
70
  - Audits for inline SVGs in views that should be sprite references
71
71
  - Reports icon usage frequency (find dead icons)
72
72
 
73
- ### 3. Design Token Enforcement (`rails guardrails:tokens`)
73
+ ### 3. Design Token Enforcement (`rails ui_guardrails:tokens`)
74
74
 
75
75
  - Reads a `guardrails.yml` token definition (colors, type scale, spacing)
76
76
  - Scans CSS/SCSS/Tailwind config for values that don't match defined tokens
@@ -116,7 +116,7 @@ guardrails:
116
116
 
117
117
  ## Success Metrics
118
118
 
119
- - Developers can run `rails guardrails:audit` in < 30 seconds on a mid-sized app
119
+ - Developers can run `rails ui_guardrails:audit` in < 30 seconds on a mid-sized app
120
120
  - Catches at least 80% of common UI drift patterns in a test app
121
121
  - Zero false positives on a clean, well-structured Rails app
122
122
  - Used by at least 3 Meticulous clients within 6 months of launch
@@ -139,7 +139,7 @@ guardrails:
139
139
  |-----------|--------|-------|
140
140
  | PRD + README stub | 2026-05-04 | ✅ Done |
141
141
  | Gem skeleton + Railtie | TBD | First Claude Code session |
142
- | `guardrails:audit` v0 | TBD | |
143
- | `guardrails:icons` v0 | TBD | |
144
- | `guardrails:tokens` v0 | TBD | |
142
+ | `ui_guardrails:audit` v0 | TBD | |
143
+ | `ui_guardrails:icons` v0 | TBD | |
144
+ | `ui_guardrails:tokens` v0 | TBD | |
145
145
  | Public beta / RubyGems publish | TBD | Before next speaking slot |
data/doc/PUBLISHING.md CHANGED
@@ -35,6 +35,10 @@ git pull --ff-only
35
35
  grep VERSION lib/guardrails/version.rb
36
36
  head -20 CHANGELOG.md
37
37
 
38
+ # A version bump changes the generator line in the demo's committed
39
+ # Feed Ramp manifest; regenerate it (the demo spec fails until you do)
40
+ (cd examples/demo && bundle exec rake ui_guardrails:feed_ramp:manifest)
41
+
38
42
  # Tag the merge commit and push the tag (replace X.Y.Z with the
39
43
  # version you're cutting — the next-release example would be the
40
44
  # version in lib/guardrails/version.rb on main right now).