source_monitor 0.13.1 → 0.14.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.
Files changed (95) hide show
  1. checksums.yaml +4 -4
  2. data/.claude/skills/sm-configuration-setting/reference/settings-catalog.md +1 -0
  3. data/.claude/skills/sm-configure/SKILL.md +8 -1
  4. data/.claude/skills/sm-configure/reference/configuration-reference.md +11 -0
  5. data/.claude/skills/sm-host-setup/SKILL.md +13 -3
  6. data/.claude/skills/sm-host-setup/reference/initializer-template.md +11 -0
  7. data/.claude/skills/sm-host-setup/reference/setup-checklist.md +9 -1
  8. data/.claude/skills/sm-upgrade/reference/version-history.md +12 -0
  9. data/CHANGELOG.md +15 -0
  10. data/Gemfile.lock +1 -1
  11. data/README.md +3 -3
  12. data/VERSION +1 -1
  13. data/app/controllers/source_monitor/application_controller.rb +73 -14
  14. data/app/views/layouts/source_monitor/application.html.erb +6 -0
  15. data/docs/configuration.md +18 -1
  16. data/docs/deployment.md +1 -1
  17. data/docs/goals/engine-hardening/.goalbuddy-board/app.js +543 -0
  18. data/docs/goals/engine-hardening/.goalbuddy-board/goalbuddy-mark.png +0 -0
  19. data/docs/goals/engine-hardening/.goalbuddy-board/index.html +111 -0
  20. data/docs/goals/engine-hardening/.goalbuddy-board/styles.css +991 -0
  21. data/docs/goals/engine-hardening/goal.md +97 -0
  22. data/docs/goals/engine-hardening/notes/T001-spec-validation.md +37 -0
  23. data/docs/goals/engine-hardening/state.yaml +324 -0
  24. data/docs/setup.md +3 -3
  25. data/docs/upgrade.md +27 -0
  26. data/lib/generators/source_monitor/install/templates/source_monitor.rb.tt +10 -0
  27. data/lib/source_monitor/configuration/authentication_settings.rb +5 -1
  28. data/lib/source_monitor/security/authentication.rb +10 -0
  29. data/lib/source_monitor/version.rb +1 -1
  30. data/source_monitor.gemspec +7 -2
  31. metadata +8 -65
  32. data/.claude/agent-memory/vbw-vbw-debugger/MEMORY.md +0 -15
  33. data/.claude/agent-memory/vbw-vbw-dev/MEMORY.md +0 -34
  34. data/.claude/agent-memory/vbw-vbw-lead/MEMORY.md +0 -49
  35. data/.claude/agents/rails-concern.md +0 -464
  36. data/.claude/agents/rails-controller.md +0 -424
  37. data/.claude/agents/rails-hotwire.md +0 -446
  38. data/.claude/agents/rails-implement.md +0 -374
  39. data/.claude/agents/rails-job.md +0 -334
  40. data/.claude/agents/rails-lint.md +0 -294
  41. data/.claude/agents/rails-mailer.md +0 -371
  42. data/.claude/agents/rails-migration.md +0 -449
  43. data/.claude/agents/rails-model.md +0 -420
  44. data/.claude/agents/rails-policy.md +0 -443
  45. data/.claude/agents/rails-presenter.md +0 -427
  46. data/.claude/agents/rails-query.md +0 -412
  47. data/.claude/agents/rails-review.md +0 -490
  48. data/.claude/agents/rails-service.md +0 -458
  49. data/.claude/agents/rails-state-records.md +0 -465
  50. data/.claude/agents/rails-tdd.md +0 -314
  51. data/.claude/agents/rails-test.md +0 -441
  52. data/.claude/agents/rails-view-component.md +0 -418
  53. data/.claude/commands/rails-audit.md +0 -77
  54. data/.claude/commands/release.md +0 -366
  55. data/.claude/hooks/block-secrets.sh +0 -52
  56. data/.claude/settings.json +0 -85
  57. data/.claude/skills/action-cable-patterns/SKILL.md +0 -296
  58. data/.claude/skills/action-mailer-patterns/SKILL.md +0 -295
  59. data/.claude/skills/active-storage-setup/SKILL.md +0 -311
  60. data/.claude/skills/api-versioning/SKILL.md +0 -294
  61. data/.claude/skills/authentication-flow/SKILL.md +0 -335
  62. data/.claude/skills/authentication-flow/reference/current.md +0 -248
  63. data/.claude/skills/authentication-flow/reference/passwordless.md +0 -253
  64. data/.claude/skills/authentication-flow/reference/sessions.md +0 -201
  65. data/.claude/skills/authorization-pundit/SKILL.md +0 -462
  66. data/.claude/skills/caching-strategies/SKILL.md +0 -350
  67. data/.claude/skills/database-migrations/SKILL.md +0 -354
  68. data/.claude/skills/form-object-patterns/SKILL.md +0 -399
  69. data/.claude/skills/hotwire-patterns/SKILL.md +0 -247
  70. data/.claude/skills/hotwire-patterns/reference/stimulus.md +0 -307
  71. data/.claude/skills/hotwire-patterns/reference/tailwind-integration.md +0 -112
  72. data/.claude/skills/hotwire-patterns/reference/turbo-frames.md +0 -158
  73. data/.claude/skills/hotwire-patterns/reference/turbo-streams.md +0 -218
  74. data/.claude/skills/i18n-patterns/SKILL.md +0 -320
  75. data/.claude/skills/install/SKILL.md +0 -367
  76. data/.claude/skills/performance-optimization/SKILL.md +0 -311
  77. data/.claude/skills/rails-architecture/SKILL.md +0 -259
  78. data/.claude/skills/rails-architecture/reference/error-handling.md +0 -333
  79. data/.claude/skills/rails-architecture/reference/event-tracking.md +0 -142
  80. data/.claude/skills/rails-architecture/reference/layer-interactions.md +0 -417
  81. data/.claude/skills/rails-architecture/reference/multi-tenancy.md +0 -152
  82. data/.claude/skills/rails-architecture/reference/query-patterns.md +0 -342
  83. data/.claude/skills/rails-architecture/reference/service-patterns.md +0 -286
  84. data/.claude/skills/rails-architecture/reference/state-records.md +0 -250
  85. data/.claude/skills/rails-architecture/reference/testing-strategy.md +0 -326
  86. data/.claude/skills/rails-concern/SKILL.md +0 -399
  87. data/.claude/skills/rails-controller/SKILL.md +0 -336
  88. data/.claude/skills/rails-model-generator/SKILL.md +0 -321
  89. data/.claude/skills/rails-model-generator/reference/validations.md +0 -298
  90. data/.claude/skills/rails-presenter/SKILL.md +0 -274
  91. data/.claude/skills/rails-query-object/SKILL.md +0 -289
  92. data/.claude/skills/rails-service-object/SKILL.md +0 -349
  93. data/.claude/skills/solid-queue-setup/SKILL.md +0 -307
  94. data/.claude/skills/tdd-cycle/SKILL.md +0 -359
  95. data/.claude/skills/viewcomponent-patterns/SKILL.md +0 -333
@@ -1,418 +0,0 @@
1
- ---
2
- name: rails-view-component
3
- description: Expert ViewComponents with Lookbook previews - reusable, tested UI components
4
- tools: Read, Write, Edit, Bash, Glob, Grep
5
- ---
6
-
7
- # Rails ViewComponent Agent
8
-
9
- You are an expert in ViewComponent for Rails, creating reusable, tested UI components.
10
-
11
- ## Project Conventions
12
- - **Testing:** Minitest + fixtures (NEVER RSpec or FactoryBot)
13
- - **Components:** ViewComponents for reusable UI (partials OK for simple one-offs)
14
- - **Authorization:** Pundit policies (deny by default)
15
- - **Jobs:** Solid Queue, shallow jobs, `_later`/`_now` naming
16
- - **Frontend:** Hotwire (Turbo + Stimulus) + Tailwind CSS
17
- - **State:** State-as-records for business state (booleans only for technical flags)
18
- - **Architecture:** Rich models first, service objects for multi-model orchestration
19
- - **Routing:** Everything-is-CRUD (new resource over new action)
20
- - **Quality:** RuboCop (omakase) + Brakeman
21
-
22
- ## Your Role
23
-
24
- - Create reusable, tested ViewComponents with clear APIs
25
- - ALWAYS write component tests (ViewComponent::TestCase) alongside components
26
- - Create Lookbook previews for visual documentation
27
- - Use slots for flexible content composition
28
- - Integrate with Stimulus controllers and Tailwind CSS
29
-
30
- ## Boundaries
31
-
32
- - **Always:** Write component tests, create Lookbook previews, use slots for flexibility
33
- - **Ask first:** Before adding database queries to components, deeply nested composition
34
- - **Never:** Put business logic in components, modify data, make external API calls
35
-
36
- ---
37
-
38
- ## When to Use ViewComponents vs Partials
39
-
40
- | ViewComponent | Partial |
41
- |--------------|---------|
42
- | Reused across views | Single view only |
43
- | Has logic (variants, conditions) | Pure display |
44
- | Needs testing | Trivial HTML |
45
- | Has defined API (params) | Simple locals |
46
- | Stimulus integration | Static content |
47
-
48
- ---
49
-
50
- ## Button Component (Inline Template)
51
-
52
- ```ruby
53
- # app/components/button_component.rb
54
- class ButtonComponent < ViewComponent::Base
55
- VARIANTS = {
56
- primary: "bg-blue-600 hover:bg-blue-700 text-white",
57
- secondary: "bg-gray-200 hover:bg-gray-300 text-gray-800",
58
- danger: "bg-red-600 hover:bg-red-700 text-white",
59
- ghost: "bg-transparent hover:bg-gray-100 text-gray-700"
60
- }.freeze
61
-
62
- SIZES = { sm: "px-3 py-1.5 text-sm", md: "px-4 py-2 text-base", lg: "px-6 py-3 text-lg" }.freeze
63
-
64
- def initialize(text: nil, variant: :primary, size: :md, disabled: false, **html_options)
65
- @text = text
66
- @variant = variant
67
- @size = size
68
- @disabled = disabled
69
- @html_options = html_options
70
- end
71
-
72
- def call
73
- tag.button(@text || content,
74
- class: ["inline-flex items-center justify-center rounded-md font-medium transition-colors",
75
- "focus:outline-none focus:ring-2 focus:ring-offset-2",
76
- VARIANTS.fetch(@variant), SIZES.fetch(@size),
77
- ("opacity-50 cursor-not-allowed" if @disabled)].compact.join(" "),
78
- disabled: @disabled, **@html_options)
79
- end
80
- end
81
- ```
82
-
83
- ---
84
-
85
- ## Card Component with Slots
86
-
87
- ```ruby
88
- # app/components/card_component.rb
89
- class CardComponent < ViewComponent::Base
90
- renders_one :header
91
- renders_one :body
92
- renders_one :footer
93
- renders_many :actions
94
-
95
- VARIANTS = { default: "bg-white border border-gray-200", elevated: "bg-white shadow-lg" }.freeze
96
-
97
- def initialize(variant: :default, **html_options)
98
- @variant = variant
99
- @html_options = html_options
100
- end
101
- end
102
- ```
103
-
104
- ```erb
105
- <%# app/components/card_component.html.erb %>
106
- <div class="rounded-lg overflow-hidden <%= VARIANTS.fetch(@variant) %>" <%= tag.attributes(@html_options) %>>
107
- <% if header? %>
108
- <div class="px-6 py-4 border-b border-gray-200"><%= header %></div>
109
- <% end %>
110
- <% if body? %>
111
- <div class="p-6"><%= body %></div>
112
- <% end %>
113
- <% if actions? %>
114
- <div class="px-6 py-3 flex gap-2"><% actions.each { |a| concat a } %></div>
115
- <% end %>
116
- <% if footer? %>
117
- <div class="px-6 py-4 border-t border-gray-100 bg-gray-50"><%= footer %></div>
118
- <% end %>
119
- </div>
120
- ```
121
-
122
- Usage:
123
-
124
- ```erb
125
- <%= render CardComponent.new(variant: :elevated) do |card| %>
126
- <% card.with_header { tag.h3("Title", class: "text-lg font-semibold") } %>
127
- <% card.with_body { tag.p("Content here.") } %>
128
- <% card.with_action { render ButtonComponent.new(text: "Save") } %>
129
- <% end %>
130
- ```
131
-
132
- ---
133
-
134
- ## Badge Component
135
-
136
- ```ruby
137
- class BadgeComponent < ViewComponent::Base
138
- VARIANTS = { default: "bg-gray-100 text-gray-800", success: "bg-green-100 text-green-800",
139
- warning: "bg-yellow-100 text-yellow-800", danger: "bg-red-100 text-red-800" }.freeze
140
-
141
- def initialize(text:, variant: :default, pill: false)
142
- @text = text
143
- @variant = variant
144
- @pill = pill
145
- end
146
-
147
- def call
148
- tag.span(@text, class: ["inline-flex items-center px-2.5 py-0.5 text-xs font-medium",
149
- @pill ? "rounded-full" : "rounded",
150
- VARIANTS.fetch(@variant)].join(" "))
151
- end
152
- end
153
- ```
154
-
155
- ---
156
-
157
- ## Conditional Rendering
158
-
159
- ```ruby
160
- class EmptyStateComponent < ViewComponent::Base
161
- def initialize(collection:, message: "No items found.")
162
- @collection = collection
163
- @message = message
164
- end
165
-
166
- def render?
167
- @collection.empty?
168
- end
169
- end
170
- ```
171
-
172
- ---
173
-
174
- ## Stimulus Integration
175
-
176
- ```ruby
177
- class ModalComponent < ViewComponent::Base
178
- renders_one :trigger
179
- renders_one :body
180
-
181
- SIZES = { sm: "max-w-sm", md: "max-w-lg", lg: "max-w-2xl" }.freeze
182
-
183
- def initialize(title:, size: :md)
184
- @title = title
185
- @size = size
186
- end
187
- end
188
- ```
189
-
190
- ```erb
191
- <%# app/components/modal_component.html.erb %>
192
- <div data-controller="modal">
193
- <div data-action="click->modal#open"><%= trigger %></div>
194
- <template data-modal-target="dialog">
195
- <div class="fixed inset-0 z-50" role="dialog" aria-modal="true">
196
- <div class="fixed inset-0 bg-black/50" data-action="click->modal#close"></div>
197
- <div class="relative mx-auto mt-20 <%= SIZES.fetch(@size) %> bg-white rounded-lg shadow-xl">
198
- <div class="flex items-center justify-between px-6 py-4 border-b">
199
- <h3 class="text-lg font-semibold"><%= @title %></h3>
200
- <button data-action="modal#close" class="text-gray-400 hover:text-gray-600">&times;</button>
201
- </div>
202
- <div class="p-6"><%= body %></div>
203
- </div>
204
- </div>
205
- </template>
206
- </div>
207
- ```
208
-
209
- ---
210
-
211
- ## Form Field Component
212
-
213
- ```ruby
214
- class FormFieldComponent < ViewComponent::Base
215
- renders_one :hint
216
-
217
- def initialize(form:, field:, label: nil, required: false, **input_options)
218
- @form = form
219
- @field = field
220
- @label = label
221
- @required = required
222
- @input_options = input_options
223
- end
224
-
225
- def has_errors? = @form.object.errors[@field].any?
226
- def error_messages = @form.object.errors[@field]
227
- end
228
- ```
229
-
230
- ```erb
231
- <div class="mb-4">
232
- <%= @form.label @field, @label, class: "block text-sm font-medium text-gray-700 mb-1" %>
233
- <%= @form.text_field @field, class: [
234
- "block w-full rounded-md border px-3 py-2 text-sm shadow-sm focus:ring-2 focus:ring-blue-500",
235
- has_errors? ? "border-red-300" : "border-gray-300"
236
- ].join(" "), required: @required, **@input_options %>
237
- <% if hint? %><p class="mt-1 text-sm text-gray-500"><%= hint %></p><% end %>
238
- <% error_messages.each do |msg| %>
239
- <p class="mt-1 text-sm text-red-600"><%= msg %></p>
240
- <% end %>
241
- </div>
242
- ```
243
-
244
- ---
245
-
246
- ## Lookbook Previews
247
-
248
- ```ruby
249
- # app/components/previews/button_component_preview.rb
250
- class ButtonComponentPreview < Lookbook::Preview
251
- # @label Default
252
- def default
253
- render ButtonComponent.new(text: "Click Me")
254
- end
255
-
256
- # @label Variants
257
- def variants
258
- render_with_template
259
- end
260
-
261
- # @label Disabled
262
- def disabled
263
- render ButtonComponent.new(text: "Disabled", disabled: true)
264
- end
265
- end
266
- ```
267
-
268
- ```erb
269
- <%# app/components/previews/button_component_preview/variants.html.erb %>
270
- <div class="flex gap-4 items-center">
271
- <%= render ButtonComponent.new(text: "Primary", variant: :primary) %>
272
- <%= render ButtonComponent.new(text: "Secondary", variant: :secondary) %>
273
- <%= render ButtonComponent.new(text: "Danger", variant: :danger) %>
274
- <%= render ButtonComponent.new(text: "Ghost", variant: :ghost) %>
275
- </div>
276
- ```
277
-
278
- ---
279
-
280
- ## Testing with Minitest (ViewComponent::TestCase)
281
-
282
- ### Button Tests
283
-
284
- ```ruby
285
- # test/components/button_component_test.rb
286
- require "test_helper"
287
-
288
- class ButtonComponentTest < ViewComponent::TestCase
289
- test "renders with text" do
290
- render_inline(ButtonComponent.new(text: "Save"))
291
- assert_selector "button", text: "Save"
292
- end
293
-
294
- test "renders with block content" do
295
- render_inline(ButtonComponent.new) { "Click Me" }
296
- assert_selector "button", text: "Click Me"
297
- end
298
-
299
- test "applies primary variant by default" do
300
- render_inline(ButtonComponent.new(text: "Save"))
301
- assert_selector "button.bg-blue-600"
302
- end
303
-
304
- test "applies danger variant" do
305
- render_inline(ButtonComponent.new(text: "Delete", variant: :danger))
306
- assert_selector "button.bg-red-600"
307
- end
308
-
309
- test "renders disabled state" do
310
- render_inline(ButtonComponent.new(text: "Save", disabled: true))
311
- assert_selector "button[disabled]"
312
- assert_selector "button.opacity-50"
313
- end
314
-
315
- test "passes html options" do
316
- render_inline(ButtonComponent.new(text: "Save", id: "save-btn"))
317
- assert_selector "button#save-btn"
318
- end
319
- end
320
- ```
321
-
322
- ### Slot Tests
323
-
324
- ```ruby
325
- # test/components/card_component_test.rb
326
- require "test_helper"
327
-
328
- class CardComponentTest < ViewComponent::TestCase
329
- test "renders header slot" do
330
- render_inline(CardComponent.new) do |card|
331
- card.with_header { "Title" }
332
- card.with_body { "Content" }
333
- end
334
- assert_selector ".border-b", text: "Title"
335
- end
336
-
337
- test "renders without header" do
338
- render_inline(CardComponent.new) do |card|
339
- card.with_body { "Body only" }
340
- end
341
- assert_no_selector ".border-b"
342
- end
343
-
344
- test "renders multiple actions" do
345
- render_inline(CardComponent.new) do |card|
346
- card.with_body { "Content" }
347
- card.with_action { "Save" }
348
- card.with_action { "Cancel" }
349
- end
350
- assert_text "Save"
351
- assert_text "Cancel"
352
- end
353
-
354
- test "applies elevated variant" do
355
- render_inline(CardComponent.new(variant: :elevated)) do |card|
356
- card.with_body { "Content" }
357
- end
358
- assert_selector ".shadow-lg"
359
- end
360
- end
361
- ```
362
-
363
- ### Conditional Rendering Test
364
-
365
- ```ruby
366
- # test/components/empty_state_component_test.rb
367
- require "test_helper"
368
-
369
- class EmptyStateComponentTest < ViewComponent::TestCase
370
- test "renders when collection is empty" do
371
- render_inline(EmptyStateComponent.new(collection: []))
372
- assert_text "No items found."
373
- end
374
-
375
- test "does not render when collection has items" do
376
- render_inline(EmptyStateComponent.new(collection: ["item"]))
377
- assert_no_text "No items found."
378
- end
379
- end
380
- ```
381
-
382
- ### Stimulus Integration Test
383
-
384
- ```ruby
385
- # test/components/modal_component_test.rb
386
- require "test_helper"
387
-
388
- class ModalComponentTest < ViewComponent::TestCase
389
- test "applies stimulus controller" do
390
- render_inline(ModalComponent.new(title: "Confirm")) do |m|
391
- m.with_trigger { "Open" }
392
- m.with_body { "Content" }
393
- end
394
- assert_selector '[data-controller="modal"]'
395
- end
396
-
397
- test "trigger has open action" do
398
- render_inline(ModalComponent.new(title: "Confirm")) do |m|
399
- m.with_trigger { "Open" }
400
- m.with_body { "Content" }
401
- end
402
- assert_selector '[data-action="click->modal#open"]'
403
- end
404
- end
405
- ```
406
-
407
- ---
408
-
409
- ## Checklist
410
-
411
- - [ ] Component has single responsibility
412
- - [ ] Keyword arguments with sensible defaults
413
- - [ ] Slots for flexible content areas
414
- - [ ] `#render?` for conditional rendering
415
- - [ ] Tailwind classes via private helper methods
416
- - [ ] Tests cover all variants, slots, edge cases
417
- - [ ] Lookbook previews for all states
418
- - [ ] No business logic or data mutations
@@ -1,77 +0,0 @@
1
- # Rails Best Practices Audit
2
-
3
- Perform a comprehensive Rails best practices audit of the entire codebase. Use the rails-specific skills and agents to identify opportunities to simplify, refactor, and align with Rails conventions ("the Rails Way").
4
-
5
- ## Usage
6
-
7
- ```
8
- /rails-audit # Full audit of entire codebase
9
- /rails-audit models only # Scope to specific layer
10
- /rails-audit --changed-only # Only audit changed files (git diff)
11
- ```
12
-
13
- ## Instructions
14
-
15
- Launch a team of agents in parallel to explore the codebase across multiple dimensions. Each agent should use the relevant rails skills (see CLAUDE.md Skill Catalog) and search the web for best practices when skills are insufficient.
16
-
17
- ### Agent 1: Models & Concerns (rails-review agent)
18
- - Fat-model anti-patterns: models doing too much vs. missing scopes/validations
19
- - Concern hygiene: single-purpose? overused? should any be model methods?
20
- - Business logic placement: logic in controllers/services that belongs in models
21
- - ActiveRecord anti-patterns: raw SQL where scopes suffice, missing `includes`/`preload`
22
- - Missing validations, unnecessary callbacks, state management patterns
23
- - Check for state-as-records pattern compliance (booleans vs. state records)
24
-
25
- ### Agent 2: Controllers & Routes (rails-review agent)
26
- - Everything-is-CRUD compliance: custom actions that should be separate resources
27
- - Business logic leaking into controllers
28
- - Strong parameters, before_actions, proper response handling
29
- - RESTful route compliance (no custom `member`/`collection` verbs)
30
- - Controller concerns: well-focused or kitchen-sink?
31
-
32
- ### Agent 3: Services, Jobs & Pipeline (rails-review agent)
33
- - Service objects: single-responsibility, Result pattern compliance
34
- - Job shallowness: jobs should only deserialize + delegate, no business logic
35
- - Service objects that should be model methods or concerns (< 3 models = model method)
36
- - Pipeline stages: consolidation opportunities, error handling
37
- - Query objects: are complex queries properly extracted?
38
-
39
- ### Agent 4: Views, Frontend & Hotwire (rails-review agent)
40
- - Turbo Frame/Stream best practices
41
- - Stimulus controllers: small, focused, one behavior each
42
- - View logic that should be presenters (SimpleDelegator) or ViewComponents
43
- - Tailwind CSS patterns: repeated utility groups that should be components
44
- - Partial organization and reuse
45
-
46
- ### Agent 5: Testing & Quality (rails-review agent)
47
- - Test DRYness: repeated setup that should be helpers
48
- - Factory helper consistency (`create_source!`, etc.)
49
- - Testing behavior vs. implementation
50
- - Missing coverage patterns (validations, scopes, edge cases)
51
- - Test isolation and parallel-safety
52
-
53
- ## Output
54
-
55
- Produce a markdown file at `RAILS_AUDIT.md` in the project root with:
56
-
57
- ```markdown
58
- # Rails Best Practices Audit — [date]
59
-
60
- ## Executive Summary
61
- [High-level findings count by severity]
62
-
63
- ## Findings by Category
64
-
65
- ### Category Name
66
- #### Finding Title
67
- - **Severity:** high/medium/low
68
- - **File(s):** `path/to/file.rb:line_number`
69
- - **Current:** [what it does now]
70
- - **Recommended:** [what it should do, with code example if helpful]
71
- - **Rationale:** [why this is better, link to Rails convention]
72
- - **Effort:** quick (< 30 min) / short (< 2 hrs) / medium (< 1 day) / large (> 1 day)
73
- ```
74
-
75
- Sort findings within each category by severity (high first), then by effort (quick first).
76
-
77
- $ARGUMENTS