feat(rails-patterns): add Rails framework patterns skill (#3074)

Adds skills/rails-patterns alongside laravel-patterns and django-patterns: directory contract, skinny controllers with service objects, form and query objects, idiomatic ActiveRecord, background jobs, ViewComponent, Hotwire, and the Rails 8 Solid stack. Decisions defer to rules/ruby/patterns.md. Registered in install-modules (framework-language), agent.yaml, package files, and every skill count (292) across plugin, marketplace, AGENTS and README variants. Independent exact-head review passed with no P0/P1; validate-skills, catalog:check, install manifests, plugin manifest, unicode and personal-path checks all pass; CI 44/44 at the head.
This commit is contained in:
Rockwell Windsor Rice
2026-09-12 01:48:16 +01:00
committed by GitHub
parent 2083c9839a
commit 95b9fe157f
12 changed files with 495 additions and 15 deletions
+1 -1
View File
@@ -11,7 +11,7 @@
{
"name": "ecc",
"source": "./",
"description": "Harness-native ECC operator layer - 68 agents, 291 skills, 94 legacy command shims, reusable hooks, rules, selective install profiles, and production-ready workflows for Claude Code, Codex, OpenCode, Cursor, and related agent harnesses",
"description": "Harness-native ECC operator layer - 68 agents, 292 skills, 94 legacy command shims, reusable hooks, rules, selective install profiles, and production-ready workflows for Claude Code, Codex, OpenCode, Cursor, and related agent harnesses",
"version": "2.2.1",
"author": {
"name": "Affaan Mustafa",
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "ecc",
"version": "2.2.1",
"description": "Harness-native ECC plugin for engineering teams - 68 agents, 291 skills, 94 legacy command shims, reusable hooks, rules, MCP conventions, and operator workflows for Claude Code plus adjacent agent harnesses",
"description": "Harness-native ECC plugin for engineering teams - 68 agents, 292 skills, 94 legacy command shims, reusable hooks, rules, MCP conventions, and operator workflows for Claude Code plus adjacent agent harnesses",
"author": {
"name": "Affaan Mustafa",
"url": "https://x.com/affaanmustafa"
+2 -2
View File
@@ -1,6 +1,6 @@
# Everything Claude Code (ECC) — Agent Instructions
This is a **production-ready AI coding plugin** providing 68 specialized agents, 291 skills, 94 commands, and automated hook workflows for software development.
This is a **production-ready AI coding plugin** providing 68 specialized agents, 292 skills, 94 commands, and automated hook workflows for software development.
**Version:** 2.2.1
@@ -154,7 +154,7 @@ Troubleshoot failures: check test isolation → verify mocks → fix implementat
```
agents/ — 68 specialized subagents
skills/ — 291 workflow skills and domain knowledge
skills/ — 292 workflow skills and domain knowledge
commands/ — 94 slash commands
hooks/ — Trigger-based automations
rules/ — Always-follow guidelines (common + per-language)
+4 -3
View File
@@ -136,12 +136,12 @@ Instead of rebuilding that process in every prompt, you install it once and make
ECC is MIT-licensed open source. It works best with Claude Code today, has a supported Codex sync path, and provides capability-limited adapters for Cursor, OpenCode, Gemini, Zed, GitHub Copilot, Antigravity, Qwen, and other harnesses. See the [support status matrix](#platform-support) before assuming feature parity.
Access to 68 agents, 291 skills, and 94 legacy command shims, plus hooks, rules, memory, continuous learning, and AgentShield security scanning. The agents are specialized for planning, review, build repair, security, architecture, and domain work.
Access to 68 agents, 292 skills, and 94 legacy command shims, plus hooks, rules, memory, continuous learning, and AgentShield security scanning. The agents are specialized for planning, review, build repair, security, architecture, and domain work.
| Included | Count | What it gives you |
| ---------------- | ----------: | ------------------------------------------------------------------------------------ |
| Agents | 68 agents | Planning, review, build repair, security, architecture, and domain work |
| Skills | 291 skills | TDD, research, security, docs, frontend, data, ML, operations, and more |
| Skills | 292 skills | TDD, research, security, docs, frontend, data, ML, operations, and more |
| Commands | 94 commands | Convenient entry points while ECC moves to a skills-first surface |
| Hooks and memory | Runtime | Enforcement, session summaries, continuous learning, instincts, and context controls |
| Rules | Selective | Always-loaded standards you choose by language or project |
@@ -794,7 +794,7 @@ Stable graduation of the 2.0 line: control-pane substrate, worktree lifecycle se
```text
ECC/
|-- agents/ # 68 specialized subagents for delegation
|-- skills/ # 291 reusable workflows loaded on demand
|-- skills/ # 292 reusable workflows loaded on demand
|-- commands/ # 94 maintained slash-command shims
|-- rules/ # opt-in common and language standards
|-- hooks/ # runtime automation and enforcement
@@ -887,6 +887,7 @@ ECC/
| |-- quarkus-security/ # Quarkus security
| |-- quarkus-tdd/ # Quarkus TDD
| |-- quarkus-verification/ # Quarkus verification
| |-- rails-patterns/ # Rails architecture patterns
| |-- springboot-patterns/ # Java Spring Boot patterns
| |-- springboot-security/ # Spring Boot security
| |-- springboot-tdd/ # Spring Boot TDD
+1 -1
View File
@@ -196,7 +196,7 @@ Copy-Item -Recurse rules/typescript "$HOME/.claude/rules/"
/plugin list ecc@ecc
```
**完成!** 你现在可以使用 68 个代理、291 个技能和 94 个命令。
**完成!** 你现在可以使用 68 个代理、292 个技能和 94 个命令。
### multi-* 命令需要额外配置
+1
View File
@@ -125,6 +125,7 @@ skills:
- quarkus-security
- quarkus-tdd
- quarkus-verification
- rails-patterns
- ralphinho-rfc-pipeline
- react-patterns
- react-performance
+2 -2
View File
@@ -1,6 +1,6 @@
# Everything Claude Code (ECC) — Agent Talimatları
Bu, yazılım geliştirme için 68 özel agent, 291 skill, 94 command ve otomatik hook iş akışları sağlayan **üretime hazır bir AI kodlama eklentisidir**.
Bu, yazılım geliştirme için 68 özel agent, 292 skill, 94 command ve otomatik hook iş akışları sağlayan **üretime hazır bir AI kodlama eklentisidir**.
**Sürüm:** 2.2.1
@@ -142,7 +142,7 @@ Başarısızlık sorunlarını giderin: test izolasyonunu kontrol edin → mockl
```
agents/ — 68 özel subagent
skills/ — 291 iş akışı skillleri ve alan bilgisi
skills/ — 292 iş akışı skillleri ve alan bilgisi
commands/ — 94 slash command
hooks/ — Tetikleyici tabanlı otomasyonlar
rules/ — Her zaman uyulması gereken kurallar (ortak + dile özel)
+2 -2
View File
@@ -1,6 +1,6 @@
# Everything Claude Code (ECC) — 智能体指令
这是一个**生产就绪的 AI 编码插件**,提供 68 个专业代理、291 项技能、94 条命令以及自动化钩子工作流,用于软件开发。
这是一个**生产就绪的 AI 编码插件**,提供 68 个专业代理、292 项技能、94 条命令以及自动化钩子工作流,用于软件开发。
**版本:** 2.2.1
@@ -147,7 +147,7 @@
```
agents/ — 68 个专业子代理
skills/ — 291 个工作流技能和领域知识
skills/ — 292 个工作流技能和领域知识
commands/ — 94 个斜杠命令
hooks/ — 基于触发的自动化
rules/ — 始终遵循的指导方针(通用 + 每种语言)
+3 -3
View File
@@ -260,7 +260,7 @@ Copy-Item -Recurse rules/typescript "$HOME/.claude/rules/"
/plugin list ecc@ecc
```
**搞定!** 你现在可以使用 68 个智能体、291 项技能和 94 个命令了。
**搞定!** 你现在可以使用 68 个智能体、292 项技能和 94 个命令了。
***
@@ -1174,7 +1174,7 @@ opencode
|---------|---------------|----------|--------|
| 智能体 | PASS: 68 个 | PASS: 12 个 | **Claude Code 领先** |
| 命令 | PASS: 94 个 | PASS: 35 个 | **Claude Code 领先** |
| 技能 | PASS: 291 项 | PASS: 37 项 | **Claude Code 领先** |
| 技能 | PASS: 292 项 | PASS: 37 项 | **Claude Code 领先** |
| 钩子 | PASS: 8 种事件类型 | PASS: 11 种事件 | **OpenCode 更多!** |
| 规则 | PASS: 29 条 | PASS: 13 条指令 | **Claude Code 领先** |
| MCP 服务器 | PASS: 14 个 | PASS: 完整 | **完全对等** |
@@ -1282,7 +1282,7 @@ ECC 是**第一个最大化利用每个主要 AI 编码工具的插件**。以
|---------|-----------------------|------------|-----------|----------|
| **智能体** | 68 | 共享 (AGENTS.md) | 共享 (AGENTS.md) | 12 |
| **命令** | 94 | 共享 | 基于指令 | 35 |
| **技能** | 291 | 共享 | 10 (原生格式) | 37 |
| **技能** | 292 | 共享 | 10 (原生格式) | 37 |
| **钩子事件** | 8 种类型 | 15 种类型 | SessionStart1 种类型) | 11 种类型 |
| **钩子脚本** | 20+ 个脚本 | 16 个脚本 (DRY 适配器) | 1 个 SessionStart 引导脚本 | 插件钩子 |
| **规则** | 34 (通用 + 语言) | 34 (YAML 前页) | 基于指令 | 13 条指令 |
+1
View File
@@ -196,6 +196,7 @@
"skills/quarkus-patterns",
"skills/quarkus-tdd",
"skills/quarkus-verification",
"skills/rails-patterns",
"skills/react-patterns",
"skills/react-performance",
"skills/react-testing",
+1
View File
@@ -302,6 +302,7 @@
"skills/quarkus-security/",
"skills/quarkus-tdd/",
"skills/quarkus-verification/",
"skills/rails-patterns/",
"skills/ralphinho-rfc-pipeline/",
"skills/react-patterns/",
"skills/react-performance/",
+476
View File
@@ -0,0 +1,476 @@
---
name: rails-patterns
description: Ruby on Rails framework patterns for Rails 7.1+ and 8.x apps. Covers the directory contract, skinny controllers with service objects, form objects, query objects, idiomatic ActiveRecord, background jobs, ViewComponent, Hotwire, and the Rails 8 Solid stack. Use when building or reviewing Rails apps, controllers, models, services, jobs, or views.
origin: community
---
# Rails Patterns
Framework patterns for modern Ruby on Rails applications (Rails 7.1+ and 8.x). Rails is opinionated by design; these are the patterns the community has converged on for apps that stay maintainable past the 50-model mark. This skill is the "how." For the "what" and "when" (the decisions about which pattern to reach for), see the Ruby patterns rules — `rules/ruby/patterns.md` in this repository, installed as `rules/ecc/ruby/patterns.md`.
## When to Activate
- Building a Rails application (full-stack, API-only, or hybrid)
- Reviewing a PR that touches `app/` or `config/`
- Generating models, controllers, services, or jobs
- A controller action grows past ~10 lines
- A model file grows past ~200 lines
- ActiveRecord queries start appearing in controllers or views
## Core Concepts
### The directory contract
Rails apps follow a predictable structure. Add directories deliberately, not casually.
```
app/
models/ ActiveRecord models. Persistence and domain logic close to the data.
controllers/ HTTP request handling. Thin orchestration only.
views/ ERB templates. No business logic.
components/ ViewComponent classes. View logic that needs tests.
services/ Service objects. Multi-step business operations.
forms/ Form objects. Complex form handling across multiple models.
queries/ Query objects. Reusable, composable ActiveRecord queries.
jobs/ Background jobs. Async work via Solid Queue, Sidekiq, or GoodJob.
mailers/ ActionMailer classes.
helpers/ View helpers. Tiny presentational logic only.
policies/ Authorization policies (if using Pundit). Optional.
channels/ ActionCable channels for WebSocket work.
```
Avoid `app/lib/`, `app/utils/`, `app/managers/`. If something does not fit the directories above, the design usually needs rethinking, not a new directory. Truly generic code goes in `lib/`.
### Skinny controllers
Controllers receive a request, delegate to the right object, and render a response. Business logic lives elsewhere. (Per the Ruby patterns rules, extract to a service object when the controller starts carrying multiple responsibilities.)
### Service objects
The default for business operations that touch more than a single model save. Conventions that keep them consistent:
- Namespace by domain (`Invoices::Create`), not by suffix (`InvoiceCreator`).
- A class method `.call` delegates to an instance `#call`.
- Return a Result object, not a boolean or a bare record, so the caller can branch on success, errors, and the affected record.
- Wrap multi-record writes in a transaction.
- Keep each service single-purpose (`Invoices::Create`, `Invoices::MarkPaid`), never `Invoices::Manager`.
### Form objects
When a form spans multiple models or has fields that do not map to columns, use a form object rather than nested attributes or virtual attributes on the wrong model. It quacks like a model to the view (`form_with model: @form`) while composing records cleanly.
### Query objects
For ActiveRecord queries reused across controllers or services, or too complex for a scope, extract a query object that accepts a scope as input so it composes. Rule of thumb: a scope that grows past three chained conditions or starts taking parameters wants to be a query object.
### Background jobs
Offload anything slow. (Per the Ruby patterns rules, Solid Queue for greenfield Rails 8 with modest throughput; Sidekiq when you need mature observability, high throughput, or existing Redis.) Regardless of adapter: pass IDs not records, make `perform` idempotent, and set `retry_on`/`discard_on` explicitly.
### ViewComponent over partials
For view logic with conditional rendering, more than two arguments, or reuse across more than three places, prefer a ViewComponent. Components are testable in isolation and surface their interface explicitly; partials with deep conditional logic become debt.
### Hotwire: Turbo and Stimulus
The default Rails frontend stack. (Per the Ruby patterns rules, prefer Hotwire for server-rendered apps; reach for React/Vue only when interaction complexity justifies the client surface.) Turbo Frames for partial page updates, Turbo Streams for server-driven updates, Stimulus for small client-side behaviors next to the markup.
### The Rails 8 Solid stack
Rails 8 ships database-backed defaults that previously needed Redis: Solid Queue (jobs), Solid Cache (cache), Solid Cable (ActionCable). The tradeoff is more database load for one fewer infrastructure component; a good fit for modest throughput, with Redis still winning at high scale. Kamal is the default Docker-based deploy tool.
## Code Examples
### Skinny controller with a service object
```ruby
# Bad: business logic in the controller
class InvoicesController < ApplicationController
def create
@invoice = Invoice.new(invoice_params)
@invoice.user = current_user
@invoice.line_items.build(invoice_params[:line_items])
@invoice.tax_total = TaxCalculator.new(@invoice).calculate
@invoice.total = @invoice.line_items.sum(&:amount) + @invoice.tax_total
if @invoice.save
InvoiceMailer.created(@invoice).deliver_later
AccountingExportJob.perform_later(@invoice.id)
redirect_to @invoice, notice: "Invoice created"
else
render :new
end
end
end
# Good: controller orchestrates, service does the work
class InvoicesController < ApplicationController
def create
result = Invoices::Create.call(params: invoice_params, user: current_user)
if result.success?
redirect_to result.invoice, notice: "Invoice created"
else
@invoice = result.invoice
render :new, status: :unprocessable_entity
end
end
end
```
### The service object
```ruby
# app/services/invoices/create.rb
module Invoices
class Create
# Struct keeps this runnable on every Ruby that Rails 7.1 supports.
# On Ruby 3.2+, `Data.define(:success?, :invoice, :errors)` is a more
# concise immutable alternative.
Result = Struct.new(:success, :invoice, :errors, keyword_init: true) do
def success?
success
end
end
def self.call(params:, user:)
new(params: params, user: user).call
end
def initialize(params:, user:)
@params = params
@user = user
end
def call
invoice = build_invoice
ApplicationRecord.transaction do
invoice.save!
end
begin
send_notifications(invoice)
rescue StandardError => e
Rails.logger.error("Notification dispatch failed for invoice #{invoice.id}: #{e.message}")
end
Result.new(success: true, invoice: invoice, errors: nil)
rescue ActiveRecord::RecordInvalid => e
Result.new(success: false, invoice: e.record, errors: e.record.errors)
end
private
attr_reader :params, :user
def build_invoice
invoice = user.invoices.new(params.except(:line_items))
invoice.tax_total = TaxCalculator.call(invoice)
invoice.line_items.build(params[:line_items])
invoice.total = invoice.line_items.sum(&:amount) + invoice.tax_total
invoice
end
def send_notifications(invoice)
InvoiceMailer.created(invoice).deliver_later
AccountingExportJob.perform_later(invoice.id)
end
end
end
```
### Form object
```ruby
# app/forms/signup_form.rb
class SignupForm
include ActiveModel::Model
include ActiveModel::Attributes
attribute :email, :string
attribute :password, :string
attribute :company_name, :string
attribute :terms_accepted, :boolean
validates :email, presence: true, format: URI::MailTo::EMAIL_REGEXP
validates :password, presence: true, length: { minimum: 12 }
validates :company_name, presence: true
validates :terms_accepted, acceptance: true
attr_reader :user, :company
def save
return false unless valid?
ApplicationRecord.transaction do
@company = Company.create!(name: company_name)
@user = @company.users.create!(email: email, password: password, role: :owner)
end
true
rescue ActiveRecord::RecordInvalid => e
errors.merge!(e.record.errors)
false
end
end
```
### Query object
```ruby
# app/queries/invoices/overdue.rb
module Invoices
class Overdue
def self.call(scope: Invoice.all, as_of: Time.current)
new(scope: scope, as_of: as_of).call
end
def initialize(scope:, as_of:)
@scope = scope
@as_of = as_of
end
def call
scope
.where(status: :sent)
.where(due_date: ..as_of)
.where.not(id: paid_invoice_ids)
.includes(:customer, :line_items)
end
private
attr_reader :scope, :as_of
def paid_invoice_ids
Payment.where(created_at: ..as_of).pluck(:invoice_id)
end
end
end
```
Query objects accept a scope, so they compose: `Invoices::Overdue.call(scope: current_user.invoices)`.
### N+1 prevention
```ruby
# Bad: N+1 in the view when it calls post.author.name
@posts = Post.published
# Good: eager load
@posts = Post.published.includes(:author)
```
`includes` lets Rails choose preload vs eager_load. Force `preload` for separate queries, `eager_load` for a JOIN when filtering on the association. In Rails 7.1+, `strict_loading` raises on accidental lazy loads.
### Counter cache
```ruby
class Comment < ApplicationRecord
belongs_to :post, counter_cache: true
end
```
```ruby
add_column :posts, :comments_count, :integer, default: 0, null: false
```
`post.comments_count` becomes a column read instead of a `COUNT(*)`. This example
assumes a new table; adding a counter cache to a table that already has rows requires a
backfill, which is out of scope here.
### Background job shape
Pass record IDs, not records. Retries make delivery at-least-once, so any job that calls
an external service must be idempotent — otherwise a transient failure after the remote
call succeeds will duplicate the effect on the next attempt.
```ruby
class AccountingExportJob < ApplicationJob
queue_as :exports
retry_on AccountingApi::TransientError, wait: :polynomially_longer, attempts: 5
discard_on AccountingApi::PermanentError
def perform(invoice_id)
invoice = Invoice.find(invoice_id)
export = AccountingExport.create_or_find_by!(
invoice: invoice,
idempotency_key: "invoice-export-#{invoice.id}-#{invoice.updated_at.to_i}"
)
return if export.completed_at?
receipt = AccountingApi.export(invoice, idempotency_key: export.idempotency_key)
export.update!(completed_at: Time.current, external_id: receipt.id)
end
end
```
```ruby
add_index :accounting_exports, :idempotency_key, unique: true
```
The unique index is what makes this safe: when two attempts race, the database rejects
the second insert and Active Record resolves the conflict inside the call, returning the
existing row. That happens without any job-level retry — `retry_on` above covers only
`AccountingApi::TransientError`. The guard
covers the window before the remote call; passing `idempotency_key` through to the API
covers the window after it, so a crash between the API call and `update!` still resolves
to a single export.
### ViewComponent
```ruby
# app/components/invoice_status_badge_component.rb
class InvoiceStatusBadgeComponent < ViewComponent::Base
STATUS_CLASSES = {
draft: "bg-gray-100 text-gray-800",
sent: "bg-blue-100 text-blue-800",
paid: "bg-green-100 text-green-800",
overdue: "bg-red-100 text-red-800"
}.freeze
def initialize(invoice:)
@invoice = invoice
end
def call
tag.span(@invoice.status.humanize, class: "rounded-full px-2 py-1 text-sm #{status_class}")
end
private
def status_class
STATUS_CLASSES.fetch(@invoice.status.to_sym, "bg-gray-100")
end
end
```
```erb
<%= render InvoiceStatusBadgeComponent.new(invoice: @invoice) %>
```
### Hotwire
```erb
<%# Turbo Frame: clicking Edit replaces only this frame %>
<%= turbo_frame_tag "invoice_#{@invoice.id}" do %>
<div class="invoice">
<%= link_to "Edit", edit_invoice_path(@invoice) %>
</div>
<% end %>
```
```erb
<%# Turbo Stream: app/views/comments/create.turbo_stream.erb %>
<%= turbo_stream.append "comments", @comment %>
<%= turbo_stream.update "comment_form", partial: "form", locals: { comment: Comment.new } %>
```
```javascript
// app/javascript/controllers/copy_to_clipboard_controller.js
import { Controller } from "@hotwired/stimulus"
export default class extends Controller {
static targets = ["source"]
copy() {
navigator.clipboard.writeText(this.sourceTarget.value)
}
}
```
### Acceptable vs unacceptable callbacks
```ruby
# Acceptable: pure data normalization
class User < ApplicationRecord
before_validation :normalize_email
private
def normalize_email
self.email = email.to_s.downcase.strip
end
end
# Move to a service instead: side effects hidden in a callback
# class User < ApplicationRecord
# after_create :send_welcome_email # hard to opt out of, hard to test
# end
```
### Good concern vs bad concern
```ruby
# Good: genuinely cross-cutting, reusable across unrelated models
# app/models/concerns/soft_deletable.rb
module SoftDeletable
extend ActiveSupport::Concern
included do
scope :active, -> { where(deleted_at: nil) }
scope :deleted, -> { where.not(deleted_at: nil) }
end
def soft_delete! = update!(deleted_at: Time.current)
def restore! = update!(deleted_at: nil)
end
# Bad: a "concern" used by exactly one model, holding logic that belongs on it
# app/models/concerns/invoice_calculations.rb
module InvoiceCalculations
extend ActiveSupport::Concern
def calculate_total
line_items.sum(&:amount) + tax_total
end
end
# Only Invoice includes this. It isn't cross-cutting; it's Invoice's own logic
# hidden in a module for the appearance of a "skinny" model. Put it back on Invoice.
```
A concern used by only one class is just moving code; it belongs in that class. A concern should be reusable across at least two unrelated models.
## Anti-Patterns
### God controllers
Any controller past ~80 lines is doing too much. Split actions across controllers or extract to services.
### Fat models with 30+ methods
Models should know about their own data. Methods that orchestrate other models, send notifications, or coordinate workflows belong in services.
### Callback chains
`after_save :update_cache, :send_notifications, :enqueue_export` is the start of a debugging nightmare. Move them into a service that runs them explicitly.
### Nested attributes for complex forms
`accepts_nested_attributes_for` is fine for simple cases. For conditional validation or cross-model logic, use a form object.
### Default scopes on critical models
`default_scope { where(deleted: false) }` silently excludes records from every query in the app, including the ones you need for support and debugging. Prefer an explicit named scope.
### Models named after database concepts
`UserRole`, `OrderStatus`, `InvoiceState` are usually enum candidates, not models.
### Reaching for a JS framework before Hotwire
If the page is server-rendered with occasional interactivity, Hotwire ships faster. Reserve React/Vue for genuinely SPA-shaped apps.
## Best Practices
- Keep controllers thin; push business logic into services.
- Return Result objects from services so callers branch on outcome, not exceptions.
- Wrap multi-record writes in a transaction; let notification/side-effect failures log without breaking the primary write.
- Pass IDs to jobs, keep `perform` idempotent, set retry/discard explicitly.
- Default to eager loading; treat an accidental N+1 as a bug, not a nuisance.
- Reserve concerns for behavior shared across at least two unrelated models.
- Reach for Hotwire before a client-side framework on server-rendered apps.
## Related Skills
- `backend-patterns` — service boundaries and adapter patterns (referenced by the Ruby patterns rules)
- `ruby-patterns` — language-level Ruby idioms (if present)
- Ruby patterns rules (`rules/ruby/patterns.md`, installed as `rules/ecc/ruby/patterns.md`) — the decisions and when-to-use guidance this skill implements