Initial commit
This commit is contained in:
352
skills/agent-coordination-patterns/skill.md
Normal file
352
skills/agent-coordination-patterns/skill.md
Normal file
@@ -0,0 +1,352 @@
|
||||
---
|
||||
name: agent-coordination-patterns
|
||||
description: Optimal multi-agent coordination strategies for the rails-architect
|
||||
auto_invoke: true
|
||||
trigger_for: [rails-architect]
|
||||
tags: [coordination, orchestration, multi-agent, workflow, parallel, sequential]
|
||||
priority: 4
|
||||
version: 2.1
|
||||
---
|
||||
|
||||
# Agent Coordination Patterns Skill
|
||||
|
||||
Provides optimal coordination strategies for multi-agent Rails workflows.
|
||||
|
||||
## What This Skill Does
|
||||
|
||||
**For Rails Architect:**
|
||||
- Parallel vs sequential execution decisions
|
||||
- Dependency management between agents
|
||||
- Error recovery across agent handoffs
|
||||
- State management in multi-agent workflows
|
||||
|
||||
**Auto-invokes:** Only for rails-architect agent
|
||||
**Purpose:** Optimize agent coordination for efficiency
|
||||
|
||||
## Coordination Strategies
|
||||
|
||||
### 1. Parallel Execution
|
||||
|
||||
**When to use:**
|
||||
- Tasks are independent
|
||||
- No shared dependencies
|
||||
- Can execute simultaneously
|
||||
|
||||
**Example:**
|
||||
```
|
||||
Feature: Blog with posts and comments
|
||||
|
||||
Parallel execution:
|
||||
├── Agent 1: Create Post model
|
||||
└── Agent 2: Create Comment model
|
||||
|
||||
Both can start immediately (independent models)
|
||||
```
|
||||
|
||||
**Pattern:**
|
||||
```markdown
|
||||
Task tool invocations:
|
||||
1. Invoke rails-model-specialist (Post) - don't wait
|
||||
2. Invoke rails-model-specialist (Comment) - don't wait
|
||||
3. Wait for both completions
|
||||
4. Proceed with next phase
|
||||
```
|
||||
|
||||
**Benefits:**
|
||||
- 2x faster for 2 independent tasks
|
||||
- Better resource utilization
|
||||
- Faster user feedback
|
||||
|
||||
### 2. Sequential Execution
|
||||
|
||||
**When to use:**
|
||||
- Task B depends on Task A output
|
||||
- Shared state modification
|
||||
- Order matters for correctness
|
||||
|
||||
**Example:**
|
||||
```
|
||||
Feature: API endpoint for posts
|
||||
|
||||
Sequential execution:
|
||||
1. Create Post model (needed by controller)
|
||||
2. Wait for completion
|
||||
3. Create PostsController (uses Post model)
|
||||
4. Wait for completion
|
||||
5. Create tests (test both model + controller)
|
||||
```
|
||||
|
||||
**Pattern:**
|
||||
```markdown
|
||||
Task tool invocations:
|
||||
1. Invoke rails-model-specialist (Post)
|
||||
2. Wait for completion
|
||||
3. Invoke rails-controller-specialist (uses Post)
|
||||
4. Wait for completion
|
||||
5. Invoke rails-test-specialist
|
||||
```
|
||||
|
||||
**Benefits:**
|
||||
- Ensures correct dependency order
|
||||
- Avoids race conditions
|
||||
- Clearer error attribution
|
||||
|
||||
### 3. Hybrid Execution
|
||||
|
||||
**When to use:**
|
||||
- Mix of dependent and independent tasks
|
||||
- Optimize for maximum parallelism
|
||||
|
||||
**Example:**
|
||||
```
|
||||
Feature: Complete e-commerce order flow
|
||||
|
||||
Phase 1 (Parallel):
|
||||
├── Create Order model
|
||||
├── Create OrderItem model
|
||||
└── Create Product model (if needed)
|
||||
|
||||
Phase 2 (Sequential after Phase 1):
|
||||
├── Create OrderProcessingService (needs Order, OrderItem, Product)
|
||||
|
||||
Phase 3 (Parallel):
|
||||
├── Create OrdersController
|
||||
└── Create API serializers
|
||||
|
||||
Phase 4 (Sequential):
|
||||
└── Create comprehensive tests
|
||||
```
|
||||
|
||||
**Pattern:**
|
||||
```markdown
|
||||
# Phase 1
|
||||
parallel_invoke([
|
||||
rails-model-specialist(Order),
|
||||
rails-model-specialist(OrderItem),
|
||||
rails-model-specialist(Product)
|
||||
])
|
||||
wait_all()
|
||||
|
||||
# Phase 2
|
||||
invoke(rails-service-specialist(OrderProcessingService))
|
||||
wait()
|
||||
|
||||
# Phase 3
|
||||
parallel_invoke([
|
||||
rails-controller-specialist(OrdersController),
|
||||
rails-view-specialist(Serializers)
|
||||
])
|
||||
wait_all()
|
||||
|
||||
# Phase 4
|
||||
invoke(rails-test-specialist(ComprehensiveTests))
|
||||
```
|
||||
|
||||
**Benefits:**
|
||||
- Maximizes parallelism
|
||||
- Respects dependencies
|
||||
- Optimal execution time
|
||||
|
||||
## Dependency Analysis
|
||||
|
||||
### Dependency Types
|
||||
|
||||
**1. Model Dependencies:**
|
||||
```
|
||||
User model → Post model (belongs_to :user)
|
||||
Post model → Comment model (has_many :comments)
|
||||
|
||||
Order: User → Post → Comment (sequential)
|
||||
```
|
||||
|
||||
**2. Controller Dependencies:**
|
||||
```
|
||||
Model exists → Controller can be created
|
||||
Routes defined → Controller actions valid
|
||||
|
||||
Order: Model → Controller (sequential)
|
||||
```
|
||||
|
||||
**3. View Dependencies:**
|
||||
```
|
||||
Controller exists → Views can reference actions
|
||||
Model exists → Views can access attributes
|
||||
|
||||
Order: Model + Controller → Views (sequential after both)
|
||||
```
|
||||
|
||||
**4. Test Dependencies:**
|
||||
```
|
||||
Implementation exists → Tests can be written
|
||||
|
||||
Order: Feature implementation → Tests (sequential)
|
||||
Exception: TDD approach inverts this (tests first)
|
||||
```
|
||||
|
||||
### Dependency Detection
|
||||
|
||||
**Automatic detection:**
|
||||
```ruby
|
||||
# Code analysis
|
||||
class Post < ApplicationRecord
|
||||
belongs_to :user # Depends on User model
|
||||
end
|
||||
|
||||
# Architect detects:
|
||||
# - User model must exist before Post
|
||||
# - Sequential: User → Post
|
||||
```
|
||||
|
||||
**Manual specification:**
|
||||
```markdown
|
||||
User specifies: "Create Order with OrderItems"
|
||||
|
||||
Architect infers:
|
||||
- OrderItem references Order (foreign key)
|
||||
- Order must be created first
|
||||
- Sequential: Order → OrderItem
|
||||
```
|
||||
|
||||
## Error Recovery Patterns
|
||||
|
||||
### Pattern 1: Retry with Fix
|
||||
|
||||
**Scenario:** Agent fails with correctable error
|
||||
|
||||
**Strategy:**
|
||||
```
|
||||
1. Agent fails (e.g., missing gem)
|
||||
2. Analyze error message
|
||||
3. Apply fix (add gem to Gemfile)
|
||||
4. Retry same agent
|
||||
5. Success
|
||||
```
|
||||
|
||||
**Max retries:** 3 per agent
|
||||
|
||||
### Pattern 2: Alternative Approach
|
||||
|
||||
**Scenario:** Agent fails, different approach needed
|
||||
|
||||
**Strategy:**
|
||||
```
|
||||
1. Agent fails (e.g., complex service object too ambitious)
|
||||
2. Analyze failure reason
|
||||
3. Switch to simpler pattern (extract to concern instead)
|
||||
4. Invoke different agent or modify parameters
|
||||
5. Success
|
||||
```
|
||||
|
||||
### Pattern 3: Graceful Degradation
|
||||
|
||||
**Scenario:** Agent fails, feature can be partial
|
||||
|
||||
**Strategy:**
|
||||
```
|
||||
1. Core agent succeeds (Model created)
|
||||
2. Enhancement agent fails (Serializer generation)
|
||||
3. Decision: Ship core functionality
|
||||
4. Log TODO for enhancement
|
||||
5. Continue with partial implementation
|
||||
```
|
||||
|
||||
## State Management
|
||||
|
||||
### Passing Context Between Agents
|
||||
|
||||
**Problem:** Agent B needs info from Agent A
|
||||
|
||||
**Solution 1: File system state**
|
||||
```
|
||||
1. Agent A creates Model file
|
||||
2. Agent B reads Model file
|
||||
3. Agent B uses Model info (class name, associations)
|
||||
```
|
||||
|
||||
**Solution 2: Explicit parameter passing**
|
||||
```
|
||||
1. Agent A returns: { model_name: "Post", attributes: [...] }
|
||||
2. Architect stores in context
|
||||
3. Agent B receives context: create_controller(context[:model_name])
|
||||
```
|
||||
|
||||
### Shared State Conflicts
|
||||
|
||||
**Problem:** Two agents modify same file
|
||||
|
||||
**Solution: Sequential execution**
|
||||
```
|
||||
Scenario: Two agents both need to modify routes.rb
|
||||
|
||||
Wrong (parallel):
|
||||
├── Agent A: adds posts routes
|
||||
└── Agent B: adds comments routes
|
||||
Result: Race condition, lost changes
|
||||
|
||||
Correct (sequential):
|
||||
1. Agent A: adds posts routes
|
||||
2. Wait for completion
|
||||
3. Agent B: adds comments routes (reads latest routes.rb)
|
||||
Result: Both changes preserved
|
||||
```
|
||||
|
||||
## Performance Optimization
|
||||
|
||||
### Parallelism Limits
|
||||
|
||||
**Don't over-parallelize:**
|
||||
```
|
||||
Bad: Spawn 10 agents simultaneously
|
||||
- Resource contention
|
||||
- Harder to debug
|
||||
- Diminishing returns
|
||||
|
||||
Good: Spawn 2-3 agents per phase
|
||||
- Manageable
|
||||
- Clear progress
|
||||
- Easier error tracking
|
||||
```
|
||||
|
||||
### Execution Time Estimates
|
||||
|
||||
**Sequential baseline:**
|
||||
```
|
||||
7 agents × 5 min each = 35 min total
|
||||
```
|
||||
|
||||
**With optimal parallelism:**
|
||||
```
|
||||
Phase 1: 2 agents parallel = 5 min
|
||||
Phase 2: 3 agents parallel = 5 min
|
||||
Phase 3: 2 agents parallel = 5 min
|
||||
Total: 15 min (2.3x faster)
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
```yaml
|
||||
# .agent-coordination.yml
|
||||
execution:
|
||||
max_parallel_agents: 3
|
||||
retry_limit: 3
|
||||
timeout_per_agent: 300 # 5 min
|
||||
|
||||
dependencies:
|
||||
auto_detect: true
|
||||
strict_ordering: false
|
||||
|
||||
error_recovery:
|
||||
retry_on_failure: true
|
||||
alternative_approaches: true
|
||||
graceful_degradation: true
|
||||
```
|
||||
|
||||
## References
|
||||
|
||||
- **Orchestration Pattern**: Used by rails-architect agent
|
||||
- **Task Tool**: Native Claude Code multi-agent support
|
||||
- **Pattern Library**: /patterns/api-patterns.md for feature patterns
|
||||
|
||||
---
|
||||
|
||||
**This skill helps the architect coordinate agents efficiently for fast, reliable implementations.**
|
||||
330
skills/rails-api-lookup/SKILL.md
Normal file
330
skills/rails-api-lookup/SKILL.md
Normal file
@@ -0,0 +1,330 @@
|
||||
# Rails API Lookup
|
||||
|
||||
---
|
||||
name: rails-api-lookup
|
||||
description: Looks up Rails API documentation for specific classes/methods using Ref (primary) or WebFetch (fallback) with API reference
|
||||
version: 1.2.0
|
||||
author: Rails Workflow Team
|
||||
tags: [rails, api, documentation, reference, ref]
|
||||
priority: 3
|
||||
---
|
||||
|
||||
## Purpose
|
||||
|
||||
Fetches precise API documentation from official Rails API docs for specific classes, modules, and methods. Returns signatures, parameters, return values, and usage examples.
|
||||
|
||||
**Replaces**: `mcp__rails__search_docs` MCP tool (API-specific queries)
|
||||
|
||||
## Usage
|
||||
|
||||
**Auto-invoked** when agents need API details:
|
||||
```
|
||||
Agent: "What parameters does validates :email accept?"
|
||||
*invokes rails-api-lookup class="ActiveModel::Validations" method="validates"*
|
||||
```
|
||||
|
||||
**Manual invocation**:
|
||||
```
|
||||
@rails-api-lookup class="ActiveRecord::Base" method="where"
|
||||
@rails-api-lookup class="ActionController::Base"
|
||||
@rails-api-lookup module="ActiveSupport::Concern"
|
||||
```
|
||||
|
||||
## Supported Lookups
|
||||
|
||||
See `reference.md` for complete class/module list. Common APIs:
|
||||
|
||||
### Active Record
|
||||
- `ActiveRecord::Base` - Model base class
|
||||
- `ActiveRecord::Relation` - Query interface (where, joins, etc.)
|
||||
- `ActiveRecord::Associations` - Association methods
|
||||
- `ActiveRecord::Validations` - Validation methods
|
||||
- `ActiveRecord::Callbacks` - Callback methods
|
||||
|
||||
### Action Controller
|
||||
- `ActionController::Base` - Controller base class
|
||||
- `ActionController::Metal` - Minimal controller
|
||||
- `ActionController::API` - API controller
|
||||
|
||||
### Action View
|
||||
- `ActionView::Base` - View rendering
|
||||
- `ActionView::Helpers` - View helpers
|
||||
- `ActionView::Template` - Template handling
|
||||
|
||||
### Active Support
|
||||
- `ActiveSupport::Concern` - Module mixins
|
||||
- `ActiveSupport::Callbacks` - Callback framework
|
||||
- `ActiveSupport::TimeWithZone` - Time handling
|
||||
|
||||
## Search Process
|
||||
|
||||
### Step 1: Version Detection
|
||||
```
|
||||
Invokes: @rails-version-detector
|
||||
Result: Rails 7.1.3
|
||||
Maps to: https://api.rubyonrails.org/v7.1/
|
||||
```
|
||||
|
||||
### Step 2: Class/Method Mapping
|
||||
```
|
||||
Input: class="ActiveRecord::Base" method="where"
|
||||
Lookup: reference.md → "ActiveRecord/QueryMethods.html#method-i-where"
|
||||
URL: https://api.rubyonrails.org/v7.1/ActiveRecord/QueryMethods.html#method-i-where
|
||||
```
|
||||
|
||||
### Step 3: Content Fetch
|
||||
|
||||
**Method 1: Context7 (Fastest)**:
|
||||
```
|
||||
Tool: context7_fetch
|
||||
Query: "Rails [version] [class] [method] API"
|
||||
```
|
||||
|
||||
**Method 2: Ref (Token-Efficient)**:
|
||||
```
|
||||
Tool: ref_search_documentation
|
||||
Query: "Rails [version] [class] [method] API documentation"
|
||||
Then: ref_read_url
|
||||
```
|
||||
|
||||
**Method 3: Tavily (Search)**:
|
||||
```
|
||||
Tool: tavily_search
|
||||
Query: "Rails [version] [class] [method] API documentation"
|
||||
```
|
||||
|
||||
**Method 4: WebFetch (Fallback)**:
|
||||
```
|
||||
Tool: WebFetch
|
||||
URL: [constructed URL from reference.md]
|
||||
Prompt: "Extract method signature, parameters, and examples for [method]"
|
||||
```
|
||||
|
||||
### Step 4: Response Formatting
|
||||
```ruby
|
||||
## ActiveRecord::QueryMethods#where (v7.1)
|
||||
|
||||
**Signature**: `where(**opts)`
|
||||
|
||||
**Parameters**:
|
||||
- `opts` (Hash) - Conditions as key-value pairs
|
||||
- `opts` (String) - Raw SQL conditions
|
||||
- `opts` (Array) - SQL with placeholders
|
||||
|
||||
**Returns**: `ActiveRecord::Relation`
|
||||
|
||||
**Examples**:
|
||||
User.where(name: 'Alice')
|
||||
User.where("age > ?", 18)
|
||||
User.where(age: 18..65)
|
||||
|
||||
**Source**: https://api.rubyonrails.org/v7.1/ActiveRecord/QueryMethods.html#method-i-where
|
||||
```
|
||||
|
||||
## Reference Lookup
|
||||
|
||||
**Class/Method → API URL mapping** in `reference.md`:
|
||||
|
||||
```yaml
|
||||
ActiveRecord::Base:
|
||||
url_path: "ActiveRecord/Base.html"
|
||||
common_methods:
|
||||
- save: "method-i-save"
|
||||
- update: "method-i-update"
|
||||
- destroy: "method-i-destroy"
|
||||
|
||||
ActiveRecord::QueryMethods:
|
||||
url_path: "ActiveRecord/QueryMethods.html"
|
||||
common_methods:
|
||||
- where: "method-i-where"
|
||||
- joins: "method-i-joins"
|
||||
- includes: "method-i-includes"
|
||||
```
|
||||
|
||||
## Output Format
|
||||
|
||||
### Class Overview
|
||||
```ruby
|
||||
## ActiveRecord::Base (v7.1)
|
||||
|
||||
Active Record base class for all models.
|
||||
|
||||
**Inherits from**: Object
|
||||
**Includes**: ActiveModel::Validations, ActiveRecord::Persistence
|
||||
|
||||
**Common Methods**:
|
||||
- `.create` - Creates and saves record
|
||||
- `#save` - Saves record to database
|
||||
- `#update` - Updates attributes and saves
|
||||
- `#destroy` - Deletes record from database
|
||||
|
||||
**Source**: https://api.rubyonrails.org/v7.1/ActiveRecord/Base.html
|
||||
```
|
||||
|
||||
### Method Details
|
||||
```ruby
|
||||
## ActiveRecord::Base#save (v7.1)
|
||||
|
||||
**Signature**: `save(options = {})`
|
||||
|
||||
**Parameters**:
|
||||
- `validate` (Boolean, default: true) - Run validations before saving
|
||||
- `context` (Symbol) - Validation context
|
||||
- `touch` (Boolean, default: true) - Update timestamps
|
||||
|
||||
**Returns**:
|
||||
- `true` if saved successfully
|
||||
- `false` if validation failed
|
||||
|
||||
**Raises**:
|
||||
- `ActiveRecord::RecordInvalid` if `save!` and validation fails
|
||||
|
||||
**Examples**:
|
||||
```ruby
|
||||
user.save # => true/false
|
||||
user.save(validate: false) # skip validations
|
||||
user.save! # raises on failure
|
||||
```
|
||||
|
||||
**Source**: [full URL]
|
||||
```
|
||||
|
||||
### Not Found Response
|
||||
```markdown
|
||||
## Class/Method Not Found: [class]#[method]
|
||||
|
||||
Searched in: Rails [version] API docs
|
||||
|
||||
Suggestions:
|
||||
- Check spelling: "ActiveRecord::Base" (not "ActiveRecords::Base")
|
||||
- Try class overview: @rails-api-lookup class="ActiveRecord::Base"
|
||||
- Search guides instead: @rails-docs-search topic="active_record_basics"
|
||||
|
||||
Common classes:
|
||||
- ActiveRecord::Base
|
||||
- ActionController::Base
|
||||
- ActiveSupport::Concern
|
||||
```
|
||||
|
||||
## Implementation Details
|
||||
|
||||
**Tools used** (in order of preference):
|
||||
1. **@rails-version-detector** - Get project Rails version
|
||||
2. **Read** - Load `reference.md` API mappings
|
||||
3. **context7_fetch** (primary) - Fetch API docs via Context7 MCP
|
||||
4. **ref_search_documentation** (secondary) - Search Rails API docs via Ref MCP
|
||||
5. **tavily_search** (tertiary) - Optimized search via Tavily MCP
|
||||
6. **WebFetch** (fallback) - Fetch API docs if MCPs not available
|
||||
7. **Grep** (optional) - Search for method names in cached docs
|
||||
|
||||
**Optional dependencies**:
|
||||
- **context7-mcp**: Fastest API documentation
|
||||
- **ref-tools-mcp**: Token-efficient API doc fetching
|
||||
- **tavily-mcp**: Optimized search for LLMs
|
||||
- If neither installed: Falls back to WebFetch (still works!)
|
||||
|
||||
**URL construction**:
|
||||
```
|
||||
Base: https://api.rubyonrails.org/
|
||||
Versioned: https://api.rubyonrails.org/v7.1/
|
||||
Class: https://api.rubyonrails.org/v7.1/ActiveRecord/Base.html
|
||||
Method: https://api.rubyonrails.org/v7.1/ActiveRecord/Base.html#method-i-save
|
||||
```
|
||||
|
||||
**Version handling**:
|
||||
- Rails 8.x → `/v8.0/`
|
||||
- Rails 7.1.x → `/v7.1/`
|
||||
- Rails 7.0.x → `/v7.0/`
|
||||
- Rails 6.1.x → `/v6.1/`
|
||||
|
||||
**Caching strategy**:
|
||||
- Cache API documentation for session
|
||||
- Re-fetch if version changes
|
||||
- Cache key: `{class}:{method}:{version}`
|
||||
|
||||
**Prompt Caching (Opus 4.5 Optimized)**:
|
||||
- Use 1-hour cache duration for extended thinking tasks
|
||||
- API signatures rarely change - maximize cache reuse
|
||||
- Cache prefix: Include Rails version + common class list in system prompt
|
||||
- Reduces token costs significantly for repeated API lookups
|
||||
|
||||
## Error Handling
|
||||
|
||||
**Network failure**:
|
||||
```markdown
|
||||
⚠️ Failed to fetch API docs from api.rubyonrails.org
|
||||
|
||||
Fallback: Use Claude's knowledge (may be less accurate)
|
||||
URL attempted: [URL]
|
||||
```
|
||||
|
||||
**Invalid class name**:
|
||||
```markdown
|
||||
❌ Unknown class: "[class]"
|
||||
|
||||
Did you mean: [closest match from reference.md]?
|
||||
|
||||
Tip: Use full module path (e.g., "ActiveRecord::Base", not "Base")
|
||||
```
|
||||
|
||||
**Method not found**:
|
||||
```markdown
|
||||
⚠️ Method "[method]" not found in [class]
|
||||
|
||||
Class exists, but method not documented or name incorrect.
|
||||
|
||||
Available methods in [class]:
|
||||
- [method1]
|
||||
- [method2]
|
||||
[...from reference.md...]
|
||||
```
|
||||
|
||||
## Integration
|
||||
|
||||
**Auto-invoked by**:
|
||||
- All 7 Rails agents when they need precise API signatures
|
||||
- @rails-model-specialist for ActiveRecord methods
|
||||
- @rails-controller-specialist for ActionController methods
|
||||
- @rails-view-specialist for ActionView helpers
|
||||
|
||||
**Complements**:
|
||||
- @rails-docs-search (concepts vs API details)
|
||||
- @rails-pattern-finder (API usage vs code patterns)
|
||||
|
||||
## Special Features
|
||||
|
||||
### Multiple methods lookup
|
||||
```
|
||||
@rails-api-lookup class="ActiveRecord::Base" method="save,update,destroy"
|
||||
→ Returns all three method signatures
|
||||
```
|
||||
|
||||
### Inheritance chain
|
||||
```
|
||||
@rails-api-lookup class="User" inherit=true
|
||||
→ Shows methods from User, ApplicationRecord, ActiveRecord::Base
|
||||
```
|
||||
|
||||
### Version comparison
|
||||
```
|
||||
@rails-api-lookup class="ActiveRecord::Base" method="save" compare="7.0,7.1"
|
||||
→ Shows differences between versions
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
**Test cases**:
|
||||
1. class="ActiveRecord::Base" method="save" → Exact signature
|
||||
2. class="UnknownClass" → Error with suggestions
|
||||
3. Network down → Graceful fallback
|
||||
4. Rails 8.0 method lookup → Uses v8.0 docs
|
||||
5. Method with multiple signatures → Lists all variants
|
||||
|
||||
## Notes
|
||||
|
||||
- API docs fetched live (not stored in plugin)
|
||||
- Reference mappings maintained in `reference.md`
|
||||
- Version-appropriate URLs ensure API accuracy
|
||||
- WebFetch tool handles HTML parsing
|
||||
- This skill focuses on **API signatures**, use @rails-docs-search for **concepts**
|
||||
- Method anchors use Rails convention: `#method-i-name` (instance), `#method-c-name` (class)
|
||||
451
skills/rails-api-lookup/reference.md
Normal file
451
skills/rails-api-lookup/reference.md
Normal file
@@ -0,0 +1,451 @@
|
||||
# Rails API Reference Mappings
|
||||
|
||||
**Purpose**: Maps Rails classes/modules to API documentation URLs
|
||||
|
||||
**Version**: 1.0.0 (supports Rails 6.1 - 8.0)
|
||||
|
||||
---
|
||||
|
||||
## Active Record Classes
|
||||
|
||||
### Core Classes
|
||||
|
||||
```yaml
|
||||
ActiveRecord::Base:
|
||||
url_path: "ActiveRecord/Base.html"
|
||||
description: "Base class for all models"
|
||||
common_methods:
|
||||
save: "method-i-save"
|
||||
save!: "method-i-save-21"
|
||||
update: "method-i-update"
|
||||
update!: "method-i-update-21"
|
||||
destroy: "method-i-destroy"
|
||||
destroy!: "method-i-destroy-21"
|
||||
reload: "method-i-reload"
|
||||
persisted?: "method-i-persisted-3F"
|
||||
new_record?: "method-i-new_record-3F"
|
||||
|
||||
ActiveRecord::Relation:
|
||||
url_path: "ActiveRecord/Relation.html"
|
||||
description: "Query result collection"
|
||||
common_methods:
|
||||
to_a: "method-i-to_a"
|
||||
each: "method-i-each"
|
||||
map: "method-i-map"
|
||||
pluck: "method-i-pluck"
|
||||
ids: "method-i-ids"
|
||||
```
|
||||
|
||||
### Query Interface
|
||||
|
||||
```yaml
|
||||
ActiveRecord::QueryMethods:
|
||||
url_path: "ActiveRecord/QueryMethods.html"
|
||||
description: "Query building methods"
|
||||
common_methods:
|
||||
where: "method-i-where"
|
||||
not: "method-i-not"
|
||||
order: "method-i-order"
|
||||
limit: "method-i-limit"
|
||||
offset: "method-i-offset"
|
||||
joins: "method-i-joins"
|
||||
left_joins: "method-i-left_joins"
|
||||
includes: "method-i-includes"
|
||||
eager_load: "method-i-eager_load"
|
||||
preload: "method-i-preload"
|
||||
references: "method-i-references"
|
||||
group: "method-i-group"
|
||||
having: "method-i-having"
|
||||
distinct: "method-i-distinct"
|
||||
select: "method-i-select"
|
||||
reorder: "method-i-reorder"
|
||||
reverse_order: "method-i-reverse_order"
|
||||
```
|
||||
|
||||
### Associations
|
||||
|
||||
```yaml
|
||||
ActiveRecord::Associations::ClassMethods:
|
||||
url_path: "ActiveRecord/Associations/ClassMethods.html"
|
||||
description: "Association declarations"
|
||||
common_methods:
|
||||
belongs_to: "method-i-belongs_to"
|
||||
has_one: "method-i-has_one"
|
||||
has_many: "method-i-has_many"
|
||||
has_and_belongs_to_many: "method-i-has_and_belongs_to_many"
|
||||
has_one_attached: "method-i-has_one_attached"
|
||||
has_many_attached: "method-i-has_many_attached"
|
||||
```
|
||||
|
||||
### Validations
|
||||
|
||||
```yaml
|
||||
ActiveModel::Validations::ClassMethods:
|
||||
url_path: "ActiveModel/Validations/ClassMethods.html"
|
||||
description: "Validation declarations"
|
||||
common_methods:
|
||||
validates: "method-i-validates"
|
||||
validates_each: "method-i-validates_each"
|
||||
validates_with: "method-i-validates_with"
|
||||
validate: "method-i-validate"
|
||||
|
||||
ActiveModel::Validations::HelperMethods:
|
||||
url_path: "ActiveModel/Validations/HelperMethods.html"
|
||||
description: "Built-in validators"
|
||||
common_methods:
|
||||
validates_presence_of: "method-i-validates_presence_of"
|
||||
validates_absence_of: "method-i-validates_absence_of"
|
||||
validates_length_of: "method-i-validates_length_of"
|
||||
validates_size_of: "method-i-validates_size_of"
|
||||
validates_numericality_of: "method-i-validates_numericality_of"
|
||||
validates_inclusion_of: "method-i-validates_inclusion_of"
|
||||
validates_exclusion_of: "method-i-validates_exclusion_of"
|
||||
validates_format_of: "method-i-validates_format_of"
|
||||
validates_uniqueness_of: "method-i-validates_uniqueness_of"
|
||||
```
|
||||
|
||||
### Callbacks
|
||||
|
||||
```yaml
|
||||
ActiveRecord::Callbacks:
|
||||
url_path: "ActiveRecord/Callbacks.html"
|
||||
description: "Model lifecycle callbacks"
|
||||
common_methods:
|
||||
after_initialize: "method-i-after_initialize"
|
||||
after_find: "method-i-after_find"
|
||||
before_validation: "method-i-before_validation"
|
||||
after_validation: "method-i-after_validation"
|
||||
before_save: "method-i-before_save"
|
||||
around_save: "method-i-around_save"
|
||||
after_save: "method-i-after_save"
|
||||
before_create: "method-i-before_create"
|
||||
around_create: "method-i-around_create"
|
||||
after_create: "method-i-after_create"
|
||||
before_update: "method-i-before_update"
|
||||
around_update: "method-i-around_update"
|
||||
after_update: "method-i-after_update"
|
||||
before_destroy: "method-i-before_destroy"
|
||||
around_destroy: "method-i-around_destroy"
|
||||
after_destroy: "method-i-after_destroy"
|
||||
after_commit: "method-i-after_commit"
|
||||
after_rollback: "method-i-after_rollback"
|
||||
```
|
||||
|
||||
### Migrations
|
||||
|
||||
```yaml
|
||||
ActiveRecord::Migration:
|
||||
url_path: "ActiveRecord/Migration.html"
|
||||
description: "Database migration base class"
|
||||
common_methods:
|
||||
change: "method-i-change"
|
||||
up: "method-i-up"
|
||||
down: "method-i-down"
|
||||
reversible: "method-i-reversible"
|
||||
|
||||
ActiveRecord::ConnectionAdapters::SchemaStatements:
|
||||
url_path: "ActiveRecord/ConnectionAdapters/SchemaStatements.html"
|
||||
description: "Schema manipulation methods"
|
||||
common_methods:
|
||||
create_table: "method-i-create_table"
|
||||
drop_table: "method-i-drop_table"
|
||||
rename_table: "method-i-rename_table"
|
||||
add_column: "method-i-add_column"
|
||||
remove_column: "method-i-remove_column"
|
||||
rename_column: "method-i-rename_column"
|
||||
change_column: "method-i-change_column"
|
||||
add_index: "method-i-add_index"
|
||||
remove_index: "method-i-remove_index"
|
||||
add_foreign_key: "method-i-add_foreign_key"
|
||||
remove_foreign_key: "method-i-remove_foreign_key"
|
||||
add_reference: "method-i-add_reference"
|
||||
remove_reference: "method-i-remove_reference"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Action Controller Classes
|
||||
|
||||
### Core Classes
|
||||
|
||||
```yaml
|
||||
ActionController::Base:
|
||||
url_path: "ActionController/Base.html"
|
||||
description: "Base controller class"
|
||||
common_methods:
|
||||
render: "method-i-render"
|
||||
redirect_to: "method-i-redirect_to"
|
||||
head: "method-i-head"
|
||||
|
||||
ActionController::API:
|
||||
url_path: "ActionController/API.html"
|
||||
description: "API-only controller base"
|
||||
version_support: "5.0+"
|
||||
|
||||
ActionController::Metal:
|
||||
url_path: "ActionController/Metal.html"
|
||||
description: "Minimal controller implementation"
|
||||
```
|
||||
|
||||
### Controller Features
|
||||
|
||||
```yaml
|
||||
ActionController::StrongParameters:
|
||||
url_path: "ActionController/StrongParameters.html"
|
||||
description: "Parameter filtering"
|
||||
common_methods:
|
||||
params: "method-i-params"
|
||||
permit: "method-i-permit"
|
||||
require: "method-i-require"
|
||||
|
||||
ActionController::Helpers:
|
||||
url_path: "ActionController/Helpers.html"
|
||||
description: "Helper method declarations"
|
||||
common_methods:
|
||||
helper_method: "method-i-helper_method"
|
||||
|
||||
ActionController::Cookies:
|
||||
url_path: "ActionController/Cookies.html"
|
||||
description: "Cookie handling"
|
||||
common_methods:
|
||||
cookies: "method-i-cookies"
|
||||
|
||||
ActionController::Flash:
|
||||
url_path: "ActionController/Flash.html"
|
||||
description: "Flash message handling"
|
||||
common_methods:
|
||||
flash: "method-i-flash"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Action View Classes
|
||||
|
||||
### Core Classes
|
||||
|
||||
```yaml
|
||||
ActionView::Base:
|
||||
url_path: "ActionView/Base.html"
|
||||
description: "View rendering base class"
|
||||
|
||||
ActionView::Helpers:
|
||||
url_path: "ActionView/Helpers.html"
|
||||
description: "View helper modules"
|
||||
```
|
||||
|
||||
### View Helpers
|
||||
|
||||
```yaml
|
||||
ActionView::Helpers::FormHelper:
|
||||
url_path: "ActionView/Helpers/FormHelper.html"
|
||||
description: "Form building helpers"
|
||||
common_methods:
|
||||
form_with: "method-i-form_with"
|
||||
form_for: "method-i-form_for"
|
||||
text_field: "method-i-text_field"
|
||||
text_area: "method-i-text_area"
|
||||
select: "method-i-select"
|
||||
check_box: "method-i-check_box"
|
||||
radio_button: "method-i-radio_button"
|
||||
|
||||
ActionView::Helpers::UrlHelper:
|
||||
url_path: "ActionView/Helpers/UrlHelper.html"
|
||||
description: "URL generation helpers"
|
||||
common_methods:
|
||||
link_to: "method-i-link_to"
|
||||
button_to: "method-i-button_to"
|
||||
url_for: "method-i-url_for"
|
||||
|
||||
ActionView::Helpers::TagHelper:
|
||||
url_path: "ActionView/Helpers/TagHelper.html"
|
||||
description: "HTML tag helpers"
|
||||
common_methods:
|
||||
content_tag: "method-i-content_tag"
|
||||
tag: "method-i-tag"
|
||||
|
||||
ActionView::Helpers::AssetTagHelper:
|
||||
url_path: "ActionView/Helpers/AssetTagHelper.html"
|
||||
description: "Asset inclusion helpers"
|
||||
common_methods:
|
||||
javascript_include_tag: "method-i-javascript_include_tag"
|
||||
stylesheet_link_tag: "method-i-stylesheet_link_tag"
|
||||
image_tag: "method-i-image_tag"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Active Support Classes
|
||||
|
||||
### Core Extensions
|
||||
|
||||
```yaml
|
||||
ActiveSupport::Concern:
|
||||
url_path: "ActiveSupport/Concern.html"
|
||||
description: "Module mixin pattern"
|
||||
common_methods:
|
||||
included: "method-i-included"
|
||||
class_methods: "method-i-class_methods"
|
||||
|
||||
ActiveSupport::Callbacks:
|
||||
url_path: "ActiveSupport/Callbacks.html"
|
||||
description: "Callback framework"
|
||||
common_methods:
|
||||
define_callbacks: "method-i-define_callbacks"
|
||||
set_callback: "method-i-set_callback"
|
||||
skip_callback: "method-i-skip_callback"
|
||||
run_callbacks: "method-i-run_callbacks"
|
||||
```
|
||||
|
||||
### Time & Date
|
||||
|
||||
```yaml
|
||||
ActiveSupport::TimeWithZone:
|
||||
url_path: "ActiveSupport/TimeWithZone.html"
|
||||
description: "Timezone-aware time"
|
||||
common_methods:
|
||||
in_time_zone: "method-i-in_time_zone"
|
||||
utc: "method-i-utc"
|
||||
local: "method-i-local"
|
||||
|
||||
ActiveSupport::Duration:
|
||||
url_path: "ActiveSupport/Duration.html"
|
||||
description: "Time duration"
|
||||
common_methods:
|
||||
ago: "method-i-ago"
|
||||
since: "method-i-since"
|
||||
from_now: "method-i-from_now"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Action Mailer Classes
|
||||
|
||||
```yaml
|
||||
ActionMailer::Base:
|
||||
url_path: "ActionMailer/Base.html"
|
||||
description: "Mailer base class"
|
||||
common_methods:
|
||||
mail: "method-i-mail"
|
||||
deliver_now: "method-i-deliver_now"
|
||||
deliver_later: "method-i-deliver_later"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Action Cable Classes
|
||||
|
||||
```yaml
|
||||
ActionCable::Channel::Base:
|
||||
url_path: "ActionCable/Channel/Base.html"
|
||||
description: "Cable channel base class"
|
||||
common_methods:
|
||||
stream_from: "method-i-stream_from"
|
||||
stream_for: "method-i-stream_for"
|
||||
transmit: "method-i-transmit"
|
||||
|
||||
ActionCable::Connection::Base:
|
||||
url_path: "ActionCable/Connection/Base.html"
|
||||
description: "Cable connection base class"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Active Job Classes
|
||||
|
||||
```yaml
|
||||
ActiveJob::Base:
|
||||
url_path: "ActiveJob/Base.html"
|
||||
description: "Background job base class"
|
||||
common_methods:
|
||||
perform_later: "method-i-perform_later"
|
||||
perform_now: "method-i-perform_now"
|
||||
set: "method-i-set"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Active Storage Classes
|
||||
|
||||
```yaml
|
||||
ActiveStorage::Attached::One:
|
||||
url_path: "ActiveStorage/Attached/One.html"
|
||||
description: "Single file attachment"
|
||||
version_support: "5.2+"
|
||||
common_methods:
|
||||
attach: "method-i-attach"
|
||||
attached?: "method-i-attached-3F"
|
||||
purge: "method-i-purge"
|
||||
|
||||
ActiveStorage::Attached::Many:
|
||||
url_path: "ActiveStorage/Attached/Many.html"
|
||||
description: "Multiple file attachments"
|
||||
version_support: "5.2+"
|
||||
common_methods:
|
||||
attach: "method-i-attach"
|
||||
attached?: "method-i-attached-3F"
|
||||
purge: "method-i-purge"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## URL Construction
|
||||
|
||||
**Pattern**: `https://api.rubyonrails.org/v{MAJOR.MINOR}/{url_path}#{method_anchor}`
|
||||
|
||||
**Examples**:
|
||||
|
||||
**Class page**:
|
||||
```
|
||||
https://api.rubyonrails.org/v7.1/ActiveRecord/Base.html
|
||||
```
|
||||
|
||||
**Instance method**:
|
||||
```
|
||||
https://api.rubyonrails.org/v7.1/ActiveRecord/Base.html#method-i-save
|
||||
```
|
||||
|
||||
**Class method**:
|
||||
```
|
||||
https://api.rubyonrails.org/v7.1/ActiveRecord/Base.html#method-c-create
|
||||
```
|
||||
|
||||
**Special characters in anchor**:
|
||||
- `?` → `-3F`
|
||||
- `!` → `-21`
|
||||
- `=` → `-3D`
|
||||
|
||||
**Examples**:
|
||||
- `persisted?` → `#method-i-persisted-3F`
|
||||
- `save!` → `#method-i-save-21`
|
||||
- `==` → `#method-i--3D-3D`
|
||||
|
||||
---
|
||||
|
||||
## Usage in rails-api-lookup
|
||||
|
||||
```ruby
|
||||
# Pseudocode for API lookup
|
||||
class_name = "ActiveRecord::Base"
|
||||
method_name = "save"
|
||||
mapping = reference[class_name]
|
||||
version = detect_rails_version() # e.g., "7.1"
|
||||
anchor = mapping.common_methods[method_name] # e.g., "method-i-save"
|
||||
url = "https://api.rubyonrails.org/v#{version}/#{mapping.url_path}##{anchor}"
|
||||
content = WebFetch(url, prompt: "Extract method signature and documentation")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Maintenance
|
||||
|
||||
**Update frequency**: Quarterly or when new Rails version released
|
||||
|
||||
**Adding new APIs**:
|
||||
1. Check official Rails API docs index
|
||||
2. Add mapping with url_path and common_methods
|
||||
3. Test URL accessibility
|
||||
4. Update this file
|
||||
|
||||
**Version-specific APIs**:
|
||||
- Mark with `version_support`
|
||||
- Skill should gracefully handle unavailable APIs for older Rails versions
|
||||
212
skills/rails-conventions/skill.md
Normal file
212
skills/rails-conventions/skill.md
Normal file
@@ -0,0 +1,212 @@
|
||||
---
|
||||
name: rails-conventions
|
||||
description: Automatically enforces Rails naming conventions, MVC separation, and RESTful patterns
|
||||
auto_invoke: true
|
||||
trigger_on: [file_create, file_modify]
|
||||
file_patterns: ["*.rb", "*.erb", "config/routes.rb"]
|
||||
tags: [rails, conventions, patterns, rest, mvc]
|
||||
priority: 1
|
||||
version: 2.0
|
||||
---
|
||||
|
||||
# Rails Conventions Skill
|
||||
|
||||
Auto-validates and enforces Rails conventions across all code changes.
|
||||
|
||||
## What This Skill Does
|
||||
|
||||
**Automatic Enforcement:**
|
||||
- Model naming: PascalCase, singular (User, not Users)
|
||||
- Controller naming: PascalCase, plural, ends with Controller (UsersController)
|
||||
- File paths: snake_case matching class names (users_controller.rb)
|
||||
- RESTful routes: Standard 7 actions (index, show, new, create, edit, update, destroy)
|
||||
- MVC separation: No business logic in views, no SQL in controllers
|
||||
|
||||
**When It Activates:**
|
||||
- Every time a .rb file is created or modified
|
||||
- When routes.rb is changed
|
||||
- When view files are created
|
||||
|
||||
**What It Checks:**
|
||||
|
||||
1. **Naming Conventions**
|
||||
- Class names follow Rails conventions
|
||||
- File names match class names (snake_case ↔ PascalCase)
|
||||
- Variable names descriptive and snake_case
|
||||
- Constants in SCREAMING_SNAKE_CASE
|
||||
|
||||
2. **MVC Separation**
|
||||
- Controllers: Thin, delegate to models/services
|
||||
- Models: Business logic, no rendering
|
||||
- Views: Presentation only, minimal logic
|
||||
|
||||
3. **RESTful Patterns**
|
||||
- Routes follow REST conventions
|
||||
- Custom actions documented and justified
|
||||
- Nested routes limited to 2 levels
|
||||
|
||||
4. **Rails Idioms**
|
||||
- Use scopes over class methods for queries
|
||||
- Use concerns for shared behavior
|
||||
- Use before_action for controller filters
|
||||
|
||||
## Validation Examples
|
||||
|
||||
### Example 1: Model Naming
|
||||
|
||||
**Input:**
|
||||
```ruby
|
||||
# app/models/users.rb
|
||||
class Users < ApplicationRecord
|
||||
end
|
||||
```
|
||||
|
||||
**Skill Output:**
|
||||
```
|
||||
❌ Convention Violation: Model class name should be singular
|
||||
Expected: User
|
||||
Found: Users
|
||||
|
||||
Fix:
|
||||
- Rename class to "User"
|
||||
- Rename file to "user.rb"
|
||||
```
|
||||
|
||||
### Example 2: Controller Naming
|
||||
|
||||
**Input:**
|
||||
```ruby
|
||||
# app/controllers/user.rb
|
||||
class User < ApplicationController
|
||||
end
|
||||
```
|
||||
|
||||
**Skill Output:**
|
||||
```
|
||||
❌ Convention Violation: Controller class name should be plural and end with 'Controller'
|
||||
Expected: UsersController
|
||||
Found: User
|
||||
|
||||
❌ File Naming: Controller file should match class name
|
||||
Expected: users_controller.rb
|
||||
Found: user.rb
|
||||
```
|
||||
|
||||
### Example 3: MVC Separation
|
||||
|
||||
**Input:**
|
||||
```ruby
|
||||
# app/controllers/posts_controller.rb
|
||||
def index
|
||||
@posts = Post.where("created_at > ?", 1.week.ago)
|
||||
.joins(:author)
|
||||
.order(created_at: :desc)
|
||||
end
|
||||
```
|
||||
|
||||
**Skill Output:**
|
||||
```
|
||||
⚠️ MVC Separation: Complex query logic in controller
|
||||
Recommendation: Extract to model scope
|
||||
|
||||
Suggested refactoring:
|
||||
# app/models/post.rb
|
||||
scope :recent, -> { where("created_at > ?", 1.week.ago) }
|
||||
scope :with_author, -> { joins(:author) }
|
||||
scope :newest_first, -> { order(created_at: :desc) }
|
||||
|
||||
# app/controllers/posts_controller.rb
|
||||
def index
|
||||
@posts = Post.recent.with_author.newest_first
|
||||
end
|
||||
```
|
||||
|
||||
### Example 4: RESTful Routes
|
||||
|
||||
**Input:**
|
||||
```ruby
|
||||
# config/routes.rb
|
||||
resources :posts do
|
||||
get 'activate'
|
||||
get 'deactivate'
|
||||
get 'toggle_status'
|
||||
get 'publish'
|
||||
end
|
||||
```
|
||||
|
||||
**Skill Output:**
|
||||
```
|
||||
⚠️ RESTful Pattern: Multiple custom actions detected
|
||||
Current: 4 custom actions (activate, deactivate, toggle_status, publish)
|
||||
Recommendation: Consider consolidating to single update action
|
||||
|
||||
Suggested refactoring:
|
||||
resources :posts do
|
||||
patch 'status', to: 'posts#update_status'
|
||||
end
|
||||
|
||||
# Controller:
|
||||
def update_status
|
||||
action = params[:action_type] # 'activate', 'deactivate', 'publish'
|
||||
@post.update!(status: action)
|
||||
end
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
Skill can be configured via `.rails-conventions.yml`:
|
||||
|
||||
```yaml
|
||||
# .rails-conventions.yml
|
||||
naming:
|
||||
enforce_singular_models: true
|
||||
enforce_plural_controllers: true
|
||||
|
||||
mvc_separation:
|
||||
max_controller_lines: 100
|
||||
warn_on_complex_queries: true
|
||||
|
||||
restful:
|
||||
max_custom_actions: 2
|
||||
max_nesting_depth: 2
|
||||
```
|
||||
|
||||
## Auto-Fix Capability
|
||||
|
||||
This skill can automatically fix simple violations:
|
||||
|
||||
**Auto-fixable:**
|
||||
- File renaming to match class names
|
||||
- Converting class method queries to scopes
|
||||
- Extracting inline queries to model scopes
|
||||
|
||||
**Manual fix required:**
|
||||
- Class name changes (impacts migrations, associations)
|
||||
- MVC layer violations (requires architectural decisions)
|
||||
- Custom route consolidation (business logic dependent)
|
||||
|
||||
## Integration with Agents
|
||||
|
||||
This skill enhances all agents:
|
||||
|
||||
- **@rails-model-specialist**: Validates model naming and scope usage
|
||||
- **@rails-controller-specialist**: Enforces RESTful patterns and thin controllers
|
||||
- **@rails-view-specialist**: Validates view logic separation
|
||||
- **@rails-architect**: Provides convention checks during coordination
|
||||
|
||||
## Severity Levels
|
||||
|
||||
- **❌ Error**: Blocks commit (via pre-commit hook) - naming violations, missing strong params
|
||||
- **⚠️ Warning**: Suggests improvement - complex queries, non-RESTful routes
|
||||
- **ℹ️ Info**: Best practice suggestion - use of concerns, scope opportunities
|
||||
|
||||
## Performance
|
||||
|
||||
- Activates on: File save
|
||||
- Execution time: < 100ms per file
|
||||
- No network calls
|
||||
- Works offline
|
||||
|
||||
---
|
||||
|
||||
**This skill runs automatically - no invocation needed. It keeps your Rails code conventional and maintainable.**
|
||||
291
skills/rails-docs-search/SKILL.md
Normal file
291
skills/rails-docs-search/SKILL.md
Normal file
@@ -0,0 +1,291 @@
|
||||
# Rails Docs Search
|
||||
|
||||
---
|
||||
name: rails-docs-search
|
||||
description: Searches Rails Guides for conceptual documentation using Ref (primary) or WebFetch (fallback) with reference mappings
|
||||
version: 1.2.0
|
||||
author: Rails Workflow Team
|
||||
tags: [rails, documentation, guides, search, ref]
|
||||
priority: 3
|
||||
---
|
||||
|
||||
## Purpose
|
||||
|
||||
Fetches conceptual documentation from official Rails Guides based on topics. Returns relevant guide sections with version-appropriate URLs.
|
||||
|
||||
**Replaces**: `mcp__rails__search_docs` MCP tool
|
||||
|
||||
## Usage
|
||||
|
||||
**Auto-invoked** when agents need Rails concepts:
|
||||
```
|
||||
Agent: "How do I implement Action Cable subscriptions?"
|
||||
*invokes rails-docs-search topic="action_cable"*
|
||||
```
|
||||
|
||||
**Manual invocation**:
|
||||
```
|
||||
@rails-docs-search topic="active_record_associations"
|
||||
@rails-docs-search topic="routing"
|
||||
```
|
||||
|
||||
## Supported Topics
|
||||
|
||||
See `reference.md` for complete topic list. Common topics:
|
||||
|
||||
### Core Concepts
|
||||
- `getting_started` - Rails basics and first app
|
||||
- `active_record_basics` - ORM fundamentals
|
||||
- `routing` - URL patterns and routes
|
||||
- `controllers` - Request/response handling
|
||||
- `views` - Templates and rendering
|
||||
|
||||
### Advanced Features
|
||||
- `active_record_associations` - Relationships (has_many, belongs_to)
|
||||
- `active_record_validations` - Data validation
|
||||
- `active_record_callbacks` - Lifecycle hooks
|
||||
- `action_mailer` - Email sending
|
||||
- `action_cable` - WebSockets
|
||||
|
||||
### Testing & Security
|
||||
- `testing` - Rails testing guide
|
||||
- `security` - Security best practices
|
||||
- `debugging` - Debugging techniques
|
||||
|
||||
### Deployment & Configuration
|
||||
- `configuring` - Application configuration
|
||||
- `asset_pipeline` - Asset management
|
||||
- `caching` - Performance caching
|
||||
|
||||
## Search Process
|
||||
|
||||
### Step 1: Version Detection
|
||||
```
|
||||
Invokes: @rails-version-detector
|
||||
Result: Rails 7.1.3
|
||||
Maps to: https://guides.rubyonrails.org/v7.1/
|
||||
```
|
||||
|
||||
### Step 2: Topic Mapping
|
||||
```
|
||||
Input: topic="active_record_associations"
|
||||
Lookup: reference.md → "association_basics.html"
|
||||
URL: https://guides.rubyonrails.org/v7.1/association_basics.html
|
||||
```
|
||||
|
||||
### Step 3: Content Fetch
|
||||
|
||||
**Method 1: Context7 (Fastest)**:
|
||||
```
|
||||
Tool: context7_fetch
|
||||
Query: "Rails [version] [topic]"
|
||||
```
|
||||
|
||||
**Method 2: Ref (Token-Efficient)**:
|
||||
```
|
||||
Tool: ref_search_documentation
|
||||
Query: "Rails [version] [topic] documentation"
|
||||
Then: ref_read_url
|
||||
```
|
||||
|
||||
**Method 3: Tavily (Search)**:
|
||||
```
|
||||
Tool: tavily_search
|
||||
Query: "Rails [version] [topic] guide"
|
||||
```
|
||||
|
||||
**Method 4: WebFetch (Fallback)**:
|
||||
```
|
||||
Tool: WebFetch
|
||||
URL: [constructed URL from reference.md]
|
||||
Prompt: "Extract sections about [specific query]"
|
||||
|
||||
Note: WebFetch has a built-in 15-minute cache for faster responses
|
||||
when repeatedly accessing the same URL within a session.
|
||||
```
|
||||
|
||||
### Step 4: Response Formatting
|
||||
```markdown
|
||||
## Rails Guide: Active Record Associations (v7.1)
|
||||
|
||||
### belongs_to
|
||||
A `belongs_to` association sets up a one-to-one connection...
|
||||
|
||||
### has_many
|
||||
A `has_many` association indicates a one-to-many connection...
|
||||
|
||||
Source: https://guides.rubyonrails.org/v7.1/association_basics.html
|
||||
```
|
||||
|
||||
## Reference Lookup
|
||||
|
||||
**Topic → Guide URL mapping** in `reference.md`:
|
||||
|
||||
```yaml
|
||||
active_record_associations:
|
||||
title: "Active Record Associations"
|
||||
url_path: "association_basics.html"
|
||||
version_support: "all"
|
||||
keywords: [has_many, belongs_to, has_one, through]
|
||||
|
||||
routing:
|
||||
title: "Rails Routing"
|
||||
url_path: "routing.html"
|
||||
version_support: "all"
|
||||
keywords: [routes, resources, namespace]
|
||||
```
|
||||
|
||||
## Output Format
|
||||
|
||||
### Success Response
|
||||
```markdown
|
||||
## Rails Guide: [Topic Title] (v[X.Y])
|
||||
|
||||
[Fetched content from guide...]
|
||||
|
||||
### Key Points
|
||||
- [Summary point 1]
|
||||
- [Summary point 2]
|
||||
|
||||
**Source**: [full URL]
|
||||
**Version**: [Rails version]
|
||||
```
|
||||
|
||||
### Not Found Response
|
||||
```markdown
|
||||
## Topic Not Found: [topic]
|
||||
|
||||
Available topics:
|
||||
- getting_started
|
||||
- active_record_basics
|
||||
- routing
|
||||
[...more topics...]
|
||||
|
||||
Try: @rails-docs-search topic="[one of above]"
|
||||
```
|
||||
|
||||
### Version Mismatch Warning
|
||||
```markdown
|
||||
## Rails Guide: [Topic] (v7.1)
|
||||
|
||||
⚠️ **Note**: Guide is for Rails 7.1, but project uses Rails 6.1.
|
||||
Some features may not be available in your version.
|
||||
|
||||
[Content...]
|
||||
```
|
||||
|
||||
## Implementation Details
|
||||
|
||||
**Tools used** (in order of preference):
|
||||
1. **@rails-version-detector** - Get project Rails version
|
||||
2. **Read** - Load `reference.md` topic mappings
|
||||
3. **context7_fetch** (primary) - Fetch curated docs via Context7 MCP
|
||||
4. **ref_search_documentation** (secondary) - Search Rails docs via Ref MCP
|
||||
5. **tavily_search** (tertiary) - Optimized search via Tavily MCP
|
||||
6. **WebFetch** (fallback) - Fetch guide content if MCPs not available
|
||||
7. **Grep** (optional) - Search local cached guides if available
|
||||
|
||||
**Optional dependencies**:
|
||||
- **context7-mcp**: Fastest, curated documentation
|
||||
- **ref-tools-mcp**: Token-efficient documentation search
|
||||
- **tavily-mcp**: Optimized search for LLMs
|
||||
- If neither installed: Falls back to WebFetch (still works!)
|
||||
|
||||
**URL construction**:
|
||||
```
|
||||
Base: https://guides.rubyonrails.org/
|
||||
Versioned: https://guides.rubyonrails.org/v7.1/
|
||||
Guide: https://guides.rubyonrails.org/v7.1/routing.html
|
||||
```
|
||||
|
||||
**Version handling**:
|
||||
- Rails 8.x → `/v8.0/` (or latest if 8.0 not published)
|
||||
- Rails 7.1.x → `/v7.1/`
|
||||
- Rails 7.0.x → `/v7.0/`
|
||||
- Rails 6.1.x → `/v6.1/`
|
||||
|
||||
**Caching strategy**:
|
||||
- Cache fetched guide content for session
|
||||
- Re-fetch if version changes
|
||||
- Cache key: `{topic}:{version}`
|
||||
|
||||
**Prompt Caching (Opus 4.5 Optimized)**:
|
||||
- Use 1-hour cache duration for extended thinking tasks
|
||||
- Documentation content is stable - leverage longer cache windows
|
||||
- Cache prefix: Include Rails version in system prompt for cache reuse
|
||||
- Reduces token costs significantly for repeated lookups
|
||||
|
||||
## Error Handling
|
||||
|
||||
**Network failure**:
|
||||
```markdown
|
||||
⚠️ Failed to fetch guide from rubyonrails.org
|
||||
|
||||
Fallback: Check local knowledge or ask user for clarification.
|
||||
URL attempted: [URL]
|
||||
```
|
||||
|
||||
**Invalid topic**:
|
||||
```markdown
|
||||
❌ Unknown topic: "[topic]"
|
||||
|
||||
Did you mean: [closest match from reference.md]?
|
||||
|
||||
See available topics: @rails-docs-search list
|
||||
```
|
||||
|
||||
**Version not supported**:
|
||||
```markdown
|
||||
⚠️ Rails [version] guides not available.
|
||||
|
||||
Using closest version: [fallback version]
|
||||
Some information may differ from your Rails version.
|
||||
```
|
||||
|
||||
## Integration
|
||||
|
||||
**Auto-invoked by**:
|
||||
- All 7 Rails agents when they need conceptual information
|
||||
- @rails-architect for architecture decisions
|
||||
- User questions about "How do I..." or "What is..."
|
||||
|
||||
**Complements**:
|
||||
- @rails-api-lookup (this skill = concepts, that skill = API details)
|
||||
- @rails-pattern-finder (this skill = theory, that skill = code examples)
|
||||
|
||||
## Special Features
|
||||
|
||||
### Multi-topic search
|
||||
```
|
||||
@rails-docs-search topic="routing,controllers"
|
||||
→ Fetches both guides and combines relevant sections
|
||||
```
|
||||
|
||||
### Keyword search within topic
|
||||
```
|
||||
@rails-docs-search topic="active_record_associations" keyword="polymorphic"
|
||||
→ Focuses on polymorphic association sections only
|
||||
```
|
||||
|
||||
### List available topics
|
||||
```
|
||||
@rails-docs-search list
|
||||
→ Returns all available topics from reference.md
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
**Test cases**:
|
||||
1. Topic="routing" + Rails 7.1 → Fetches v7.1 routing guide
|
||||
2. Topic="unknown" → Returns error with suggestions
|
||||
3. Network down → Graceful fallback
|
||||
4. Rails 8.0 (future) → Uses latest available version
|
||||
5. Multiple topics → Combines results
|
||||
|
||||
## Notes
|
||||
|
||||
- Guides content fetched live (not stored in plugin)
|
||||
- Reference mappings maintained in `reference.md`
|
||||
- Version-appropriate URLs ensure accuracy
|
||||
- WebFetch tool handles HTML → Markdown conversion
|
||||
- This skill focuses on **concepts**, use @rails-api-lookup for **API signatures**
|
||||
322
skills/rails-docs-search/reference.md
Normal file
322
skills/rails-docs-search/reference.md
Normal file
@@ -0,0 +1,322 @@
|
||||
# Rails Guides Reference Mappings
|
||||
|
||||
**Purpose**: Maps topic names to official Rails Guides URLs
|
||||
|
||||
**Version**: 1.0.0 (supports Rails 6.1 - 8.0)
|
||||
|
||||
---
|
||||
|
||||
## Topic Mappings
|
||||
|
||||
### Getting Started
|
||||
```yaml
|
||||
getting_started:
|
||||
title: "Getting Started with Rails"
|
||||
url_path: "getting_started.html"
|
||||
version_support: "all"
|
||||
keywords: [tutorial, first app, setup, install]
|
||||
|
||||
initialization:
|
||||
title: "The Rails Initialization Process"
|
||||
url_path: "initialization.html"
|
||||
version_support: "all"
|
||||
keywords: [boot, startup, initialization]
|
||||
```
|
||||
|
||||
### Active Record
|
||||
|
||||
```yaml
|
||||
active_record_basics:
|
||||
title: "Active Record Basics"
|
||||
url_path: "active_record_basics.html"
|
||||
version_support: "all"
|
||||
keywords: [orm, models, database, CRUD]
|
||||
|
||||
active_record_migrations:
|
||||
title: "Active Record Migrations"
|
||||
url_path: "active_record_migrations.html"
|
||||
version_support: "all"
|
||||
keywords: [migrations, schema, database changes]
|
||||
|
||||
active_record_validations:
|
||||
title: "Active Record Validations"
|
||||
url_path: "active_record_validations.html"
|
||||
version_support: "all"
|
||||
keywords: [validation, validates, presence, format]
|
||||
|
||||
active_record_callbacks:
|
||||
title: "Active Record Callbacks"
|
||||
url_path: "active_record_callbacks.html"
|
||||
version_support: "all"
|
||||
keywords: [callbacks, before_save, after_create, lifecycle]
|
||||
|
||||
active_record_associations:
|
||||
title: "Active Record Associations"
|
||||
url_path: "association_basics.html"
|
||||
version_support: "all"
|
||||
keywords: [has_many, belongs_to, has_one, through, polymorphic]
|
||||
|
||||
active_record_querying:
|
||||
title: "Active Record Query Interface"
|
||||
url_path: "active_record_querying.html"
|
||||
version_support: "all"
|
||||
keywords: [query, where, joins, includes, eager loading]
|
||||
```
|
||||
|
||||
### Action Controller
|
||||
|
||||
```yaml
|
||||
action_controller_overview:
|
||||
title: "Action Controller Overview"
|
||||
url_path: "action_controller_overview.html"
|
||||
version_support: "all"
|
||||
keywords: [controllers, requests, responses, filters]
|
||||
|
||||
routing:
|
||||
title: "Rails Routing from the Outside In"
|
||||
url_path: "routing.html"
|
||||
version_support: "all"
|
||||
keywords: [routes, resources, namespace, scope, member, collection]
|
||||
```
|
||||
|
||||
### Action View
|
||||
|
||||
```yaml
|
||||
action_view_overview:
|
||||
title: "Action View Overview"
|
||||
url_path: "action_view_overview.html"
|
||||
version_support: "all"
|
||||
keywords: [views, templates, rendering, partials]
|
||||
|
||||
layouts_and_rendering:
|
||||
title: "Layouts and Rendering in Rails"
|
||||
url_path: "layouts_and_rendering.html"
|
||||
version_support: "all"
|
||||
keywords: [layouts, render, yield, content_for]
|
||||
|
||||
form_helpers:
|
||||
title: "Action View Form Helpers"
|
||||
url_path: "form_helpers.html"
|
||||
version_support: "all"
|
||||
keywords: [forms, form_with, form_for, input fields]
|
||||
```
|
||||
|
||||
### Action Mailer
|
||||
|
||||
```yaml
|
||||
action_mailer_basics:
|
||||
title: "Action Mailer Basics"
|
||||
url_path: "action_mailer_basics.html"
|
||||
version_support: "all"
|
||||
keywords: [email, mailer, deliver, smtp]
|
||||
```
|
||||
|
||||
### Action Cable
|
||||
|
||||
```yaml
|
||||
action_cable_overview:
|
||||
title: "Action Cable Overview"
|
||||
url_path: "action_cable_overview.html"
|
||||
version_support: "all"
|
||||
keywords: [websockets, channels, subscriptions, broadcasting]
|
||||
```
|
||||
|
||||
### Active Job
|
||||
|
||||
```yaml
|
||||
active_job_basics:
|
||||
title: "Active Job Basics"
|
||||
url_path: "active_job_basics.html"
|
||||
version_support: "all"
|
||||
keywords: [jobs, background, queues, sidekiq, delayed_job]
|
||||
```
|
||||
|
||||
### Active Storage
|
||||
|
||||
```yaml
|
||||
active_storage_overview:
|
||||
title: "Active Storage Overview"
|
||||
url_path: "active_storage_overview.html"
|
||||
version_support: "6.0+"
|
||||
keywords: [uploads, files, attachments, S3, cloud storage]
|
||||
```
|
||||
|
||||
### Testing
|
||||
|
||||
```yaml
|
||||
testing:
|
||||
title: "Testing Rails Applications"
|
||||
url_path: "testing.html"
|
||||
version_support: "all"
|
||||
keywords: [tests, minitest, rspec, fixtures, factories]
|
||||
```
|
||||
|
||||
### Security
|
||||
|
||||
```yaml
|
||||
security:
|
||||
title: "Securing Rails Applications"
|
||||
url_path: "security.html"
|
||||
version_support: "all"
|
||||
keywords: [security, CSRF, XSS, SQL injection, authentication]
|
||||
```
|
||||
|
||||
### Debugging
|
||||
|
||||
```yaml
|
||||
debugging_rails_applications:
|
||||
title: "Debugging Rails Applications"
|
||||
url_path: "debugging_rails_applications.html"
|
||||
version_support: "all"
|
||||
keywords: [debug, byebug, pry, logs, debugging]
|
||||
```
|
||||
|
||||
### Configuration
|
||||
|
||||
```yaml
|
||||
configuring:
|
||||
title: "Configuring Rails Applications"
|
||||
url_path: "configuring.html"
|
||||
version_support: "all"
|
||||
keywords: [config, environment, settings, credentials]
|
||||
|
||||
rails_application_templates:
|
||||
title: "Rails Application Templates"
|
||||
url_path: "rails_application_templates.html"
|
||||
version_support: "all"
|
||||
keywords: [templates, generators, app templates]
|
||||
```
|
||||
|
||||
### Performance
|
||||
|
||||
```yaml
|
||||
caching_with_rails:
|
||||
title: "Caching with Rails"
|
||||
url_path: "caching_with_rails.html"
|
||||
version_support: "all"
|
||||
keywords: [cache, caching, fragment cache, low-level cache]
|
||||
|
||||
asset_pipeline:
|
||||
title: "The Asset Pipeline"
|
||||
url_path: "asset_pipeline.html"
|
||||
version_support: "all"
|
||||
keywords: [assets, sprockets, javascript, css, images]
|
||||
```
|
||||
|
||||
### Internationalization
|
||||
|
||||
```yaml
|
||||
i18n:
|
||||
title: "Rails Internationalization (I18n) API"
|
||||
url_path: "i18n.html"
|
||||
version_support: "all"
|
||||
keywords: [i18n, translations, locales, internationalization]
|
||||
```
|
||||
|
||||
### Action Mailbox
|
||||
|
||||
```yaml
|
||||
action_mailbox_basics:
|
||||
title: "Action Mailbox Basics"
|
||||
url_path: "action_mailbox_basics.html"
|
||||
version_support: "6.0+"
|
||||
keywords: [incoming email, mailbox, inbound email]
|
||||
```
|
||||
|
||||
### Action Text
|
||||
|
||||
```yaml
|
||||
action_text_overview:
|
||||
title: "Action Text Overview"
|
||||
url_path: "action_text_overview.html"
|
||||
version_support: "6.0+"
|
||||
keywords: [rich text, trix, wysiwyg, text editor]
|
||||
```
|
||||
|
||||
### Rails 7+ Specific
|
||||
|
||||
```yaml
|
||||
autoloading_and_reloading_constants:
|
||||
title: "Autoloading and Reloading Constants"
|
||||
url_path: "autoloading_and_reloading_constants.html"
|
||||
version_support: "all"
|
||||
keywords: [autoload, zeitwerk, eager loading]
|
||||
|
||||
engines:
|
||||
title: "Getting Started with Engines"
|
||||
url_path: "engines.html"
|
||||
version_support: "all"
|
||||
keywords: [engines, plugins, mountable]
|
||||
|
||||
api_app:
|
||||
title: "Using Rails for API-only Applications"
|
||||
url_path: "api_app.html"
|
||||
version_support: "5.0+"
|
||||
keywords: [api, json, api-only, rest]
|
||||
```
|
||||
|
||||
### Rails 8+ Specific
|
||||
|
||||
```yaml
|
||||
solid_cache:
|
||||
title: "Solid Cache"
|
||||
url_path: "solid_cache.html"
|
||||
version_support: "8.0+"
|
||||
keywords: [solid cache, caching, database cache]
|
||||
|
||||
solid_queue:
|
||||
title: "Solid Queue"
|
||||
url_path: "solid_queue.html"
|
||||
version_support: "8.0+"
|
||||
keywords: [solid queue, jobs, background jobs]
|
||||
|
||||
solid_cable:
|
||||
title: "Solid Cable"
|
||||
url_path: "solid_cable.html"
|
||||
version_support: "8.0+"
|
||||
keywords: [solid cable, websockets, action cable]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Version Support Legend
|
||||
|
||||
- `all` - Available in Rails 3.0+
|
||||
- `5.0+` - Available from Rails 5.0 onwards
|
||||
- `6.0+` - Available from Rails 6.0 onwards
|
||||
- `7.0+` - Available from Rails 7.0 onwards
|
||||
- `8.0+` - Available from Rails 8.0 onwards
|
||||
|
||||
## URL Construction
|
||||
|
||||
**Pattern**: `https://guides.rubyonrails.org/v{MAJOR.MINOR}/{url_path}`
|
||||
|
||||
**Examples**:
|
||||
- Rails 7.1: `https://guides.rubyonrails.org/v7.1/routing.html`
|
||||
- Rails 8.0: `https://guides.rubyonrails.org/v8.0/routing.html`
|
||||
- Latest: `https://guides.rubyonrails.org/routing.html` (edge)
|
||||
|
||||
## Usage in rails-docs-search
|
||||
|
||||
```ruby
|
||||
# Pseudocode for topic lookup
|
||||
topic = "active_record_associations"
|
||||
mapping = reference[topic]
|
||||
version = detect_rails_version() # e.g., "7.1"
|
||||
url = "https://guides.rubyonrails.org/v#{version}/#{mapping.url_path}"
|
||||
content = WebFetch(url, prompt: "Extract information about associations")
|
||||
```
|
||||
|
||||
## Maintenance
|
||||
|
||||
**Update frequency**: Quarterly or when new Rails version released
|
||||
|
||||
**Adding new topics**:
|
||||
1. Check official Rails Guides index
|
||||
2. Add mapping with all fields
|
||||
3. Test URL accessibility
|
||||
4. Update this file
|
||||
|
||||
**Version-specific topics**:
|
||||
- Mark with `version_support`
|
||||
- Skill should gracefully handle unavailable guides for older Rails versions
|
||||
374
skills/rails-pattern-finder/SKILL.md
Normal file
374
skills/rails-pattern-finder/SKILL.md
Normal file
@@ -0,0 +1,374 @@
|
||||
# Rails Pattern Finder
|
||||
|
||||
---
|
||||
name: rails-pattern-finder
|
||||
description: Finds Rails code patterns and best practices using Ref (primary), Grep, and reference patterns with WebFetch fallback
|
||||
version: 1.2.0
|
||||
author: Rails Workflow Team
|
||||
tags: [rails, patterns, best-practices, code-examples, ref]
|
||||
priority: 3
|
||||
---
|
||||
|
||||
## Purpose
|
||||
|
||||
Searches the current Rails codebase for existing patterns and provides best-practice code examples. Helps agents write code consistent with project conventions.
|
||||
|
||||
**Replaces**: Manual codebase exploration and pattern recognition
|
||||
|
||||
## Usage
|
||||
|
||||
**Auto-invoked** when agents need code examples:
|
||||
```
|
||||
Agent: "How is authentication implemented in this project?"
|
||||
*invokes rails-pattern-finder pattern="authentication"*
|
||||
```
|
||||
|
||||
**Manual invocation**:
|
||||
```
|
||||
@rails-pattern-finder pattern="service_objects"
|
||||
@rails-pattern-finder pattern="api_serialization"
|
||||
@rails-pattern-finder pattern="background_jobs"
|
||||
```
|
||||
|
||||
## Supported Patterns
|
||||
|
||||
See `reference.md` for complete pattern list. Common patterns:
|
||||
|
||||
### Architectural Patterns
|
||||
- `service_objects` - Service layer implementation
|
||||
- `form_objects` - Form object pattern
|
||||
- `query_objects` - Complex query encapsulation
|
||||
- `decorators` - Decorator/presenter pattern
|
||||
- `policies` - Authorization policies (Pundit)
|
||||
|
||||
### API Patterns
|
||||
- `api_versioning` - API version management
|
||||
- `api_serialization` - JSON response formatting
|
||||
- `api_authentication` - Token/JWT authentication
|
||||
- `api_error_handling` - Error response patterns
|
||||
|
||||
### Database Patterns
|
||||
- `scopes` - Named scope usage
|
||||
- `concerns` - Model concern organization
|
||||
- `polymorphic_associations` - Polymorphic pattern
|
||||
- `sti` - Single Table Inheritance
|
||||
- `database_views` - Database view usage
|
||||
|
||||
### Testing Patterns
|
||||
- `factory_usage` - FactoryBot patterns
|
||||
- `request_specs` - API request testing
|
||||
- `system_specs` - System/feature testing
|
||||
- `shared_examples` - RSpec shared examples
|
||||
|
||||
## Search Process
|
||||
|
||||
### Step 1: Pattern Lookup
|
||||
```
|
||||
Input: pattern="service_objects"
|
||||
Lookup: reference.md → search_paths, file_patterns, code_patterns
|
||||
```
|
||||
|
||||
### Step 2: Codebase Search
|
||||
```
|
||||
Tool: Grep
|
||||
Pattern: "class.*Service$"
|
||||
Glob: "app/services/**/*.rb"
|
||||
Output: List of matching files
|
||||
```
|
||||
|
||||
### Step 3: Example Extraction
|
||||
```
|
||||
Tool: Read
|
||||
Files: [top 3 matches by relevance]
|
||||
Extract: Class structure, method signatures, usage patterns
|
||||
```
|
||||
|
||||
### Step 4: Response Formatting
|
||||
```ruby
|
||||
## Pattern: Service Objects
|
||||
|
||||
### Found in Project (3 examples):
|
||||
|
||||
**1. UserRegistrationService** (app/services/user_registration_service.rb)
|
||||
```ruby
|
||||
class UserRegistrationService
|
||||
def initialize(params)
|
||||
@params = params
|
||||
end
|
||||
|
||||
def call
|
||||
user = User.create!(@params)
|
||||
send_welcome_email(user)
|
||||
user
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def send_welcome_email(user)
|
||||
UserMailer.welcome(user).deliver_later
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
**2. PaymentProcessingService** (app/services/payment_processing_service.rb)
|
||||
[Example code...]
|
||||
|
||||
### Best Practice from Rails Community:
|
||||
[Fetch from reference.md or WebSearch]
|
||||
|
||||
**Source**: Project codebase + Rails best practices
|
||||
```
|
||||
|
||||
## Reference Lookup
|
||||
|
||||
**Pattern → Search Strategy mapping** in `reference.md`:
|
||||
|
||||
```yaml
|
||||
service_objects:
|
||||
title: "Service Objects"
|
||||
search_paths: ["app/services/**/*.rb"]
|
||||
file_patterns: ["*_service.rb"]
|
||||
code_patterns:
|
||||
- "class \\w+Service"
|
||||
- "def call"
|
||||
best_practice_url: "https://example.com/rails-service-objects"
|
||||
keywords: [service, business logic, call method]
|
||||
|
||||
api_serialization:
|
||||
title: "API Serialization"
|
||||
search_paths: ["app/serializers/**/*.rb", "app/blueprints/**/*.rb"]
|
||||
file_patterns: ["*_serializer.rb", "*_blueprint.rb"]
|
||||
code_patterns:
|
||||
- "class \\w+Serializer"
|
||||
- "ActiveModel::Serializer"
|
||||
- "Blueprinter::Base"
|
||||
keywords: [json, serializer, blueprint, jbuilder]
|
||||
```
|
||||
|
||||
## Output Format
|
||||
|
||||
### Pattern Found in Project
|
||||
```markdown
|
||||
## Pattern: [Pattern Name]
|
||||
|
||||
### Found in Project ([N] examples):
|
||||
|
||||
**File**: [path/to/file.rb]
|
||||
**Purpose**: [What this implementation does]
|
||||
|
||||
```ruby
|
||||
[Code example from project]
|
||||
```
|
||||
|
||||
**Key characteristics**:
|
||||
- [Feature 1]
|
||||
- [Feature 2]
|
||||
|
||||
**Usage in project**:
|
||||
[How this pattern is used - grep for usage examples]
|
||||
```
|
||||
|
||||
### Pattern Not Found
|
||||
```markdown
|
||||
## Pattern: [Pattern Name] - Not found in project
|
||||
|
||||
**Searched**:
|
||||
- app/services/**/*.rb
|
||||
- app/lib/**/*.rb
|
||||
|
||||
**Best Practice Implementation**:
|
||||
|
||||
```ruby
|
||||
[Example from reference.md or external source]
|
||||
```
|
||||
|
||||
**To implement in this project**:
|
||||
1. Create: app/services/[name]_service.rb
|
||||
2. Follow structure above
|
||||
3. Test in: spec/services/[name]_service_spec.rb
|
||||
|
||||
**Similar patterns in project**:
|
||||
- [Alternative pattern found]
|
||||
```
|
||||
|
||||
### Multiple Variants Found
|
||||
```markdown
|
||||
## Pattern: [Pattern Name] - Multiple variants found
|
||||
|
||||
This project uses [N] different approaches:
|
||||
|
||||
### Variant 1: [Approach Name] ([N] files)
|
||||
[Example code...]
|
||||
|
||||
### Variant 2: [Approach Name] ([N] files)
|
||||
[Example code...]
|
||||
|
||||
**Recommendation**: [Which variant to use for consistency]
|
||||
```
|
||||
|
||||
## Implementation Details
|
||||
|
||||
**Tools used** (in order of preference):
|
||||
1. **Read** - Load `reference.md` pattern definitions
|
||||
2. **context7_fetch** (primary) - Fetch curated patterns via Context7 MCP
|
||||
3. **ref_search_documentation** (secondary) - Search Rails pattern docs via Ref MCP
|
||||
4. **ref_read_url** (secondary) - Fetch specific pattern guides via Ref MCP
|
||||
5. **tavily_search** (tertiary) - Optimized search via Tavily MCP
|
||||
6. **Grep** - Search codebase for pattern matches
|
||||
7. **Read** - Extract code examples from matching files
|
||||
8. **WebFetch** (fallback) - Fetch pattern docs if MCPs not available
|
||||
9. **WebSearch** (optional) - Fetch external best practices
|
||||
10. **Glob** - Find files matching pattern
|
||||
|
||||
**Optional dependencies**:
|
||||
- **context7-mcp**: Fastest pattern documentation
|
||||
- **ref-tools-mcp**: Token-efficient pattern search
|
||||
- **tavily-mcp**: Optimized search for LLMs
|
||||
- If neither installed: Falls back to WebFetch and local searches (still works!)
|
||||
|
||||
**Search strategies**:
|
||||
|
||||
**File-based search**:
|
||||
```bash
|
||||
# Find files matching naming convention
|
||||
glob: "app/services/*_service.rb"
|
||||
```
|
||||
|
||||
**Content-based search**:
|
||||
```bash
|
||||
# Find class definitions
|
||||
grep: "class \\w+Service$"
|
||||
path: "app/services"
|
||||
```
|
||||
|
||||
**Usage search**:
|
||||
```bash
|
||||
# Find where pattern is used
|
||||
grep: "UserRegistrationService\\.new"
|
||||
path: "app/controllers"
|
||||
```
|
||||
|
||||
**Relevance ranking**:
|
||||
1. Most recently modified files (likely current patterns)
|
||||
2. Files with most references (commonly used)
|
||||
3. Files with comprehensive examples (best for learning)
|
||||
|
||||
## Error Handling
|
||||
|
||||
**Pattern not found**:
|
||||
```markdown
|
||||
⚠️ Pattern "[pattern]" not found in project
|
||||
|
||||
**Searched**:
|
||||
- [paths searched]
|
||||
|
||||
**Options**:
|
||||
1. View best practice example (from reference.md)
|
||||
2. Search for similar pattern: [suggestions]
|
||||
3. Implement from scratch using best practices
|
||||
```
|
||||
|
||||
**Invalid pattern name**:
|
||||
```markdown
|
||||
❌ Unknown pattern: "[pattern]"
|
||||
|
||||
Available patterns:
|
||||
- service_objects
|
||||
- form_objects
|
||||
- query_objects
|
||||
[...from reference.md...]
|
||||
|
||||
Try: @rails-pattern-finder pattern="[one of above]"
|
||||
```
|
||||
|
||||
**Ambiguous results**:
|
||||
```markdown
|
||||
⚠️ Multiple pattern variants found for "[pattern]"
|
||||
|
||||
Please review all variants and choose one for consistency.
|
||||
|
||||
[List all variants found...]
|
||||
```
|
||||
|
||||
## Integration
|
||||
|
||||
**Auto-invoked by**:
|
||||
- All 7 Rails agents when generating new code
|
||||
- @rails-architect for architectural decisions
|
||||
- Agents adapting to project conventions
|
||||
|
||||
**Workflow**:
|
||||
1. Agent needs to generate code (e.g., new service)
|
||||
2. Invokes @rails-pattern-finder pattern="service_objects"
|
||||
3. Receives project-specific examples
|
||||
4. Generates code matching project style
|
||||
|
||||
## Special Features
|
||||
|
||||
### Pattern comparison
|
||||
```
|
||||
@rails-pattern-finder pattern="service_objects" compare_with="form_objects"
|
||||
→ Shows differences and use cases for each
|
||||
```
|
||||
|
||||
### Project convention detection
|
||||
```
|
||||
@rails-pattern-finder detect_conventions
|
||||
→ Analyzes codebase and reports common patterns
|
||||
```
|
||||
|
||||
### Anti-pattern detection
|
||||
```
|
||||
@rails-pattern-finder anti_patterns
|
||||
→ Searches for common Rails anti-patterns in codebase
|
||||
```
|
||||
|
||||
### Test pattern search
|
||||
```
|
||||
@rails-pattern-finder pattern="service_objects" include_tests=true
|
||||
→ Shows both implementation and test examples
|
||||
```
|
||||
|
||||
## Pattern Categories
|
||||
|
||||
### Creation Patterns
|
||||
- `service_objects` - Business logic encapsulation
|
||||
- `form_objects` - Form handling
|
||||
- `query_objects` - Query encapsulation
|
||||
- `builder_pattern` - Object construction
|
||||
|
||||
### Structural Patterns
|
||||
- `concerns` - Module organization
|
||||
- `decorators` - Presentation logic
|
||||
- `adapters` - External API integration
|
||||
- `repositories` - Data access layer
|
||||
|
||||
### Behavioral Patterns
|
||||
- `observers` - Event handling
|
||||
- `state_machines` - State management
|
||||
- `strategies` - Algorithm selection
|
||||
- `commands` - Action encapsulation
|
||||
|
||||
### Rails-Specific
|
||||
- `sti` - Single Table Inheritance
|
||||
- `polymorphic_associations` - Flexible relationships
|
||||
- `custom_validators` - Validation logic
|
||||
- `background_jobs` - Async processing
|
||||
|
||||
## Testing
|
||||
|
||||
**Test cases**:
|
||||
1. Pattern exists in project → Returns project examples
|
||||
2. Pattern doesn't exist → Returns best practice example
|
||||
3. Multiple variants → Lists all with recommendation
|
||||
4. Invalid pattern → Error with suggestions
|
||||
5. Empty project → All patterns return best practices
|
||||
|
||||
## Notes
|
||||
|
||||
- This skill searches **current project codebase**, not external repos
|
||||
- Pattern definitions in `reference.md` include search strategies
|
||||
- Results prioritize recent, commonly-used code
|
||||
- Helps maintain consistency across large teams
|
||||
- Adapts to project-specific conventions automatically
|
||||
- Use with @rails-docs-search (theory) and @rails-api-lookup (APIs)
|
||||
861
skills/rails-pattern-finder/reference.md
Normal file
861
skills/rails-pattern-finder/reference.md
Normal file
@@ -0,0 +1,861 @@
|
||||
# Rails Pattern Reference
|
||||
|
||||
**Purpose**: Defines search strategies for common Rails patterns
|
||||
|
||||
**Version**: 1.0.0
|
||||
|
||||
---
|
||||
|
||||
## Service Layer Patterns
|
||||
|
||||
### Service Objects
|
||||
|
||||
```yaml
|
||||
service_objects:
|
||||
title: "Service Objects"
|
||||
description: "Encapsulate business logic outside of models/controllers"
|
||||
search_paths:
|
||||
- "app/services/**/*.rb"
|
||||
- "app/lib/services/**/*.rb"
|
||||
file_patterns:
|
||||
- "*_service.rb"
|
||||
code_patterns:
|
||||
- "class \\w+Service"
|
||||
- "def call"
|
||||
- "def initialize"
|
||||
usage_patterns:
|
||||
- "\\w+Service\\.new"
|
||||
- "\\w+Service\\.call"
|
||||
test_paths:
|
||||
- "spec/services/**/*_spec.rb"
|
||||
- "test/services/**/*_test.rb"
|
||||
keywords: [service, business logic, use case, interactor]
|
||||
best_practice: |
|
||||
class UserRegistrationService
|
||||
def initialize(params)
|
||||
@params = params
|
||||
end
|
||||
|
||||
def call
|
||||
ActiveRecord::Base.transaction do
|
||||
user = create_user
|
||||
send_welcome_email(user)
|
||||
notify_admin(user)
|
||||
user
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def create_user
|
||||
User.create!(@params)
|
||||
end
|
||||
|
||||
def send_welcome_email(user)
|
||||
UserMailer.welcome(user).deliver_later
|
||||
end
|
||||
|
||||
def notify_admin(user)
|
||||
AdminMailer.new_user(user).deliver_later
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
### Form Objects
|
||||
|
||||
```yaml
|
||||
form_objects:
|
||||
title: "Form Objects"
|
||||
description: "Handle complex form logic with multiple models"
|
||||
search_paths:
|
||||
- "app/forms/**/*.rb"
|
||||
- "app/lib/forms/**/*.rb"
|
||||
file_patterns:
|
||||
- "*_form.rb"
|
||||
code_patterns:
|
||||
- "class \\w+Form"
|
||||
- "include ActiveModel::Model"
|
||||
- "attr_accessor"
|
||||
- "validate"
|
||||
keywords: [form, multi-model, virtual attributes]
|
||||
best_practice: |
|
||||
class RegistrationForm
|
||||
include ActiveModel::Model
|
||||
|
||||
attr_accessor :email, :password, :company_name
|
||||
|
||||
validates :email, presence: true, format: { with: URI::MailTo::EMAIL_REGEXP }
|
||||
validates :password, presence: true, length: { minimum: 8 }
|
||||
validates :company_name, presence: true
|
||||
|
||||
def save
|
||||
return false unless valid?
|
||||
|
||||
ActiveRecord::Base.transaction do
|
||||
user = User.create!(email: email, password: password)
|
||||
Company.create!(name: company_name, owner: user)
|
||||
end
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
### Query Objects
|
||||
|
||||
```yaml
|
||||
query_objects:
|
||||
title: "Query Objects"
|
||||
description: "Encapsulate complex database queries"
|
||||
search_paths:
|
||||
- "app/queries/**/*.rb"
|
||||
- "app/lib/queries/**/*.rb"
|
||||
file_patterns:
|
||||
- "*_query.rb"
|
||||
code_patterns:
|
||||
- "class \\w+Query"
|
||||
- "def initialize.*relation"
|
||||
- "def call"
|
||||
- "def resolve"
|
||||
keywords: [query, scope, filtering, search]
|
||||
best_practice: |
|
||||
class ActiveUsersQuery
|
||||
def initialize(relation = User.all)
|
||||
@relation = relation
|
||||
end
|
||||
|
||||
def call
|
||||
@relation
|
||||
.where(active: true)
|
||||
.where("last_login_at > ?", 30.days.ago)
|
||||
.order(created_at: :desc)
|
||||
end
|
||||
end
|
||||
|
||||
# Usage:
|
||||
ActiveUsersQuery.new.call
|
||||
ActiveUsersQuery.new(User.where(role: 'admin')).call
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Model Patterns
|
||||
|
||||
### Concerns
|
||||
|
||||
```yaml
|
||||
concerns:
|
||||
title: "Model Concerns"
|
||||
description: "Shared behavior across models"
|
||||
search_paths:
|
||||
- "app/models/concerns/**/*.rb"
|
||||
file_patterns:
|
||||
- "*.rb"
|
||||
code_patterns:
|
||||
- "module \\w+"
|
||||
- "extend ActiveSupport::Concern"
|
||||
- "included do"
|
||||
- "class_methods do"
|
||||
keywords: [concern, mixin, shared behavior]
|
||||
best_practice: |
|
||||
module Taggable
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
has_many :taggings, as: :taggable
|
||||
has_many :tags, through: :taggings
|
||||
|
||||
scope :tagged_with, ->(tag_name) {
|
||||
joins(:tags).where(tags: { name: tag_name })
|
||||
}
|
||||
end
|
||||
|
||||
def tag_list
|
||||
tags.pluck(:name).join(', ')
|
||||
end
|
||||
|
||||
class_methods do
|
||||
def most_tagged
|
||||
joins(:taggings).group('id').order('COUNT(taggings.id) DESC')
|
||||
end
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
### Scopes
|
||||
|
||||
```yaml
|
||||
scopes:
|
||||
title: "Named Scopes"
|
||||
description: "Reusable query methods"
|
||||
search_paths:
|
||||
- "app/models/**/*.rb"
|
||||
code_patterns:
|
||||
- "scope :\\w+,"
|
||||
- "scope :\\w+, ->"
|
||||
keywords: [scope, query, filter]
|
||||
best_practice: |
|
||||
class User < ApplicationRecord
|
||||
scope :active, -> { where(active: true) }
|
||||
scope :admin, -> { where(role: 'admin') }
|
||||
scope :recent, -> { where("created_at > ?", 30.days.ago) }
|
||||
scope :search, ->(query) { where("name ILIKE ?", "%#{query}%") }
|
||||
|
||||
# Chainable scopes
|
||||
scope :with_posts, -> { joins(:posts).distinct }
|
||||
scope :popular, -> { where("followers_count > ?", 100) }
|
||||
end
|
||||
|
||||
# Usage:
|
||||
User.active.admin.recent
|
||||
```
|
||||
|
||||
### Polymorphic Associations
|
||||
|
||||
```yaml
|
||||
polymorphic_associations:
|
||||
title: "Polymorphic Associations"
|
||||
description: "Flexible belongs_to relationships"
|
||||
search_paths:
|
||||
- "app/models/**/*.rb"
|
||||
code_patterns:
|
||||
- "belongs_to :\\w+, polymorphic: true"
|
||||
- "has_many :\\w+, as:"
|
||||
keywords: [polymorphic, flexible association]
|
||||
best_practice: |
|
||||
# Polymorphic model
|
||||
class Comment < ApplicationRecord
|
||||
belongs_to :commentable, polymorphic: true
|
||||
end
|
||||
|
||||
# Models that can have comments
|
||||
class Post < ApplicationRecord
|
||||
has_many :comments, as: :commentable
|
||||
end
|
||||
|
||||
class Photo < ApplicationRecord
|
||||
has_many :comments, as: :commentable
|
||||
end
|
||||
|
||||
# Migration
|
||||
create_table :comments do |t|
|
||||
t.text :body
|
||||
t.references :commentable, polymorphic: true
|
||||
t.timestamps
|
||||
end
|
||||
```
|
||||
|
||||
### Single Table Inheritance
|
||||
|
||||
```yaml
|
||||
sti:
|
||||
title: "Single Table Inheritance"
|
||||
description: "Class hierarchy in one table"
|
||||
search_paths:
|
||||
- "app/models/**/*.rb"
|
||||
code_patterns:
|
||||
- "class \\w+ < \\w+"
|
||||
- "self\\.inheritance_column"
|
||||
keywords: [STI, inheritance, subclass]
|
||||
best_practice: |
|
||||
# Base class
|
||||
class User < ApplicationRecord
|
||||
# Common behavior
|
||||
end
|
||||
|
||||
# Subclasses
|
||||
class Admin < User
|
||||
def can_manage_users?
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
class Member < User
|
||||
def can_manage_users?
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
# Migration needs 'type' column
|
||||
add_column :users, :type, :string
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## API Patterns
|
||||
|
||||
### API Versioning
|
||||
|
||||
```yaml
|
||||
api_versioning:
|
||||
title: "API Versioning"
|
||||
description: "Version management strategies"
|
||||
search_paths:
|
||||
- "app/controllers/api/**/*.rb"
|
||||
- "app/controllers/api/v*/**/*.rb"
|
||||
file_patterns:
|
||||
- "api/v*/**/*.rb"
|
||||
code_patterns:
|
||||
- "module Api::V\\d+"
|
||||
- "namespace :api"
|
||||
keywords: [api, version, v1, v2]
|
||||
best_practice: |
|
||||
# config/routes.rb
|
||||
namespace :api do
|
||||
namespace :v1 do
|
||||
resources :users
|
||||
end
|
||||
|
||||
namespace :v2 do
|
||||
resources :users
|
||||
end
|
||||
end
|
||||
|
||||
# app/controllers/api/v1/users_controller.rb
|
||||
module Api
|
||||
module V1
|
||||
class UsersController < ApiController
|
||||
def index
|
||||
render json: User.all
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
### API Serialization
|
||||
|
||||
```yaml
|
||||
api_serialization:
|
||||
title: "API Serialization"
|
||||
description: "JSON response formatting"
|
||||
search_paths:
|
||||
- "app/serializers/**/*.rb"
|
||||
- "app/blueprints/**/*.rb"
|
||||
- "app/views/api/**/*.json.jbuilder"
|
||||
file_patterns:
|
||||
- "*_serializer.rb"
|
||||
- "*_blueprint.rb"
|
||||
- "*.json.jbuilder"
|
||||
code_patterns:
|
||||
- "class \\w+Serializer"
|
||||
- "< ActiveModel::Serializer"
|
||||
- "< Blueprinter::Base"
|
||||
- "json\\."
|
||||
keywords: [json, serializer, blueprint, jbuilder]
|
||||
best_practice: |
|
||||
# Using ActiveModel::Serializers
|
||||
class UserSerializer < ActiveModel::Serializer
|
||||
attributes :id, :email, :full_name, :created_at
|
||||
|
||||
has_many :posts
|
||||
|
||||
def full_name
|
||||
"#{object.first_name} #{object.last_name}"
|
||||
end
|
||||
end
|
||||
|
||||
# Using Blueprinter
|
||||
class UserBlueprint < Blueprinter::Base
|
||||
identifier :id
|
||||
|
||||
fields :email, :created_at
|
||||
|
||||
field :full_name do |user|
|
||||
"#{user.first_name} #{user.last_name}"
|
||||
end
|
||||
|
||||
association :posts, blueprint: PostBlueprint
|
||||
end
|
||||
```
|
||||
|
||||
### API Authentication
|
||||
|
||||
```yaml
|
||||
api_authentication:
|
||||
title: "API Authentication"
|
||||
description: "Token-based authentication"
|
||||
search_paths:
|
||||
- "app/controllers/api/**/*.rb"
|
||||
- "app/controllers/concerns/**/*.rb"
|
||||
code_patterns:
|
||||
- "before_action :authenticate"
|
||||
- "Authorization.*Bearer"
|
||||
- "JWT"
|
||||
- "authenticate_with_http_token"
|
||||
keywords: [auth, token, jwt, bearer]
|
||||
best_practice: |
|
||||
# app/controllers/api/api_controller.rb
|
||||
module Api
|
||||
class ApiController < ActionController::API
|
||||
before_action :authenticate_user!
|
||||
|
||||
private
|
||||
|
||||
def authenticate_user!
|
||||
token = request.headers['Authorization']&.split(' ')&.last
|
||||
decoded = JWT.decode(token, Rails.application.secret_key_base, true)
|
||||
@current_user = User.find(decoded[0]['user_id'])
|
||||
rescue JWT::DecodeError, ActiveRecord::RecordNotFound
|
||||
render json: { error: 'Unauthorized' }, status: :unauthorized
|
||||
end
|
||||
|
||||
attr_reader :current_user
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Authorization Patterns
|
||||
|
||||
### Policy Objects (Pundit)
|
||||
|
||||
```yaml
|
||||
policies:
|
||||
title: "Authorization Policies"
|
||||
description: "Pundit policy pattern"
|
||||
search_paths:
|
||||
- "app/policies/**/*.rb"
|
||||
file_patterns:
|
||||
- "*_policy.rb"
|
||||
code_patterns:
|
||||
- "class \\w+Policy"
|
||||
- "def initialize\\(user, record\\)"
|
||||
- "def index\\?"
|
||||
- "def show\\?"
|
||||
- "def create\\?"
|
||||
- "def update\\?"
|
||||
- "def destroy\\?"
|
||||
keywords: [pundit, policy, authorization, can]
|
||||
best_practice: |
|
||||
class PostPolicy
|
||||
attr_reader :user, :post
|
||||
|
||||
def initialize(user, post)
|
||||
@user = user
|
||||
@post = post
|
||||
end
|
||||
|
||||
def index?
|
||||
true
|
||||
end
|
||||
|
||||
def show?
|
||||
true
|
||||
end
|
||||
|
||||
def create?
|
||||
user.present?
|
||||
end
|
||||
|
||||
def update?
|
||||
user.present? && (post.author == user || user.admin?)
|
||||
end
|
||||
|
||||
def destroy?
|
||||
user.present? && (post.author == user || user.admin?)
|
||||
end
|
||||
|
||||
class Scope
|
||||
def initialize(user, scope)
|
||||
@user = user
|
||||
@scope = scope
|
||||
end
|
||||
|
||||
def resolve
|
||||
if user&.admin?
|
||||
scope.all
|
||||
else
|
||||
scope.published
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :user, :scope
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Background Job Patterns
|
||||
|
||||
### Job Structure
|
||||
|
||||
```yaml
|
||||
background_jobs:
|
||||
title: "Background Jobs"
|
||||
description: "ActiveJob pattern"
|
||||
search_paths:
|
||||
- "app/jobs/**/*.rb"
|
||||
file_patterns:
|
||||
- "*_job.rb"
|
||||
code_patterns:
|
||||
- "class \\w+Job < ApplicationJob"
|
||||
- "def perform"
|
||||
- "queue_as"
|
||||
- "retry_on"
|
||||
keywords: [job, background, async, sidekiq]
|
||||
best_practice: |
|
||||
class ProcessPaymentJob < ApplicationJob
|
||||
queue_as :critical
|
||||
|
||||
retry_on PaymentGateway::NetworkError, wait: 5.seconds, attempts: 3
|
||||
discard_on PaymentGateway::InvalidCard
|
||||
|
||||
def perform(payment_id)
|
||||
payment = Payment.find(payment_id)
|
||||
PaymentProcessor.new(payment).process!
|
||||
end
|
||||
end
|
||||
|
||||
# Usage:
|
||||
ProcessPaymentJob.perform_later(payment.id)
|
||||
ProcessPaymentJob.set(wait: 1.hour).perform_later(payment.id)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Testing Patterns
|
||||
|
||||
### Factory Usage
|
||||
|
||||
```yaml
|
||||
factory_usage:
|
||||
title: "FactoryBot Factories"
|
||||
description: "Test data creation"
|
||||
search_paths:
|
||||
- "spec/factories/**/*.rb"
|
||||
- "test/factories/**/*.rb"
|
||||
file_patterns:
|
||||
- "*.rb"
|
||||
code_patterns:
|
||||
- "FactoryBot\\.define"
|
||||
- "factory :\\w+"
|
||||
- "trait :\\w+"
|
||||
keywords: [factory, factorybot, test data]
|
||||
best_practice: |
|
||||
FactoryBot.define do
|
||||
factory :user do
|
||||
sequence(:email) { |n| "user#{n}@example.com" }
|
||||
password { "password123" }
|
||||
first_name { "John" }
|
||||
last_name { "Doe" }
|
||||
|
||||
trait :admin do
|
||||
role { :admin }
|
||||
end
|
||||
|
||||
trait :with_posts do
|
||||
after(:create) do |user|
|
||||
create_list(:post, 3, author: user)
|
||||
end
|
||||
end
|
||||
|
||||
factory :admin_user, traits: [:admin]
|
||||
end
|
||||
end
|
||||
|
||||
# Usage:
|
||||
create(:user)
|
||||
create(:user, :admin)
|
||||
create(:user, :with_posts)
|
||||
build(:user)
|
||||
```
|
||||
|
||||
### Request Specs
|
||||
|
||||
```yaml
|
||||
request_specs:
|
||||
title: "Request Specs"
|
||||
description: "API endpoint testing"
|
||||
search_paths:
|
||||
- "spec/requests/**/*_spec.rb"
|
||||
code_patterns:
|
||||
- "RSpec\\.describe.*type: :request"
|
||||
- "get .*"
|
||||
- "post .*"
|
||||
- "expect\\(response\\)"
|
||||
keywords: [request spec, api test, integration test]
|
||||
best_practice: |
|
||||
RSpec.describe "Api::V1::Users", type: :request do
|
||||
let(:user) { create(:user) }
|
||||
let(:auth_headers) { { 'Authorization' => "Bearer #{user.token}" } }
|
||||
|
||||
describe "GET /api/v1/users" do
|
||||
it "returns users list" do
|
||||
create_list(:user, 3)
|
||||
|
||||
get api_v1_users_path, headers: auth_headers
|
||||
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect(json_response['users'].size).to eq(4) # 3 + authenticated user
|
||||
end
|
||||
end
|
||||
|
||||
describe "POST /api/v1/users" do
|
||||
let(:valid_params) do
|
||||
{ user: { email: 'new@example.com', password: 'password123' } }
|
||||
end
|
||||
|
||||
it "creates a new user" do
|
||||
expect {
|
||||
post api_v1_users_path, params: valid_params
|
||||
}.to change(User, :count).by(1)
|
||||
|
||||
expect(response).to have_http_status(:created)
|
||||
end
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Decorator Patterns
|
||||
|
||||
### Draper Decorators
|
||||
|
||||
```yaml
|
||||
decorators:
|
||||
title: "Decorator/Presenter Pattern"
|
||||
description: "View logic separation"
|
||||
search_paths:
|
||||
- "app/decorators/**/*.rb"
|
||||
- "app/presenters/**/*.rb"
|
||||
file_patterns:
|
||||
- "*_decorator.rb"
|
||||
- "*_presenter.rb"
|
||||
code_patterns:
|
||||
- "class \\w+Decorator"
|
||||
- "< Draper::Decorator"
|
||||
- "delegate_all"
|
||||
keywords: [decorator, presenter, view logic]
|
||||
best_practice: |
|
||||
class UserDecorator < Draper::Decorator
|
||||
delegate_all
|
||||
|
||||
def full_name
|
||||
"#{first_name} #{last_name}"
|
||||
end
|
||||
|
||||
def formatted_created_at
|
||||
created_at.strftime("%B %d, %Y")
|
||||
end
|
||||
|
||||
def avatar_url
|
||||
if object.avatar.attached?
|
||||
h.url_for(object.avatar)
|
||||
else
|
||||
h.asset_path('default-avatar.png')
|
||||
end
|
||||
end
|
||||
|
||||
def profile_link
|
||||
h.link_to full_name, h.user_path(object), class: 'user-link'
|
||||
end
|
||||
end
|
||||
|
||||
# Usage in controller:
|
||||
@user = User.find(params[:id]).decorate
|
||||
|
||||
# Usage in view:
|
||||
<%= @user.full_name %>
|
||||
<%= @user.profile_link %>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Rails 8 Defaults
|
||||
|
||||
Rails 8 includes several built-in solutions that replace common third-party gems:
|
||||
|
||||
### Solid Queue
|
||||
**Purpose**: Background job processing (replaces Sidekiq, Resque, Delayed Job)
|
||||
|
||||
Rails 8's default background job adapter. Provides:
|
||||
- Database-backed job queue
|
||||
- No Redis dependency
|
||||
- Built-in retry logic
|
||||
- Job prioritization
|
||||
- Mission control web UI
|
||||
|
||||
**Setup**:
|
||||
```bash
|
||||
bin/rails solid_queue:install
|
||||
```
|
||||
|
||||
**Usage**:
|
||||
```ruby
|
||||
# config/application.rb or config/environments/production.rb
|
||||
config.active_job.queue_adapter = :solid_queue
|
||||
|
||||
# Jobs work the same as before
|
||||
class ProcessPaymentJob < ApplicationJob
|
||||
queue_as :critical
|
||||
|
||||
def perform(payment_id)
|
||||
# Job logic
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
### Solid Cache
|
||||
**Purpose**: Database-backed caching (replaces Redis, Memcached for caching)
|
||||
|
||||
Rails 8's default cache store. Provides:
|
||||
- Database-backed cache
|
||||
- No Redis dependency
|
||||
- All standard Rails cache features
|
||||
- Automatic expiration
|
||||
- Production-ready performance
|
||||
|
||||
**Setup**:
|
||||
```bash
|
||||
bin/rails solid_cache:install
|
||||
```
|
||||
|
||||
**Usage**:
|
||||
```ruby
|
||||
# config/environments/production.rb
|
||||
config.cache_store = :solid_cache_store
|
||||
|
||||
# Caching works the same as before
|
||||
Rails.cache.fetch('expensive_query', expires_in: 1.hour) do
|
||||
# Expensive operation
|
||||
end
|
||||
```
|
||||
|
||||
### Solid Cable
|
||||
**Purpose**: Database-backed Action Cable (replaces Redis for WebSocket pub/sub)
|
||||
|
||||
Rails 8's default Action Cable adapter. Provides:
|
||||
- Database-backed pub/sub
|
||||
- No Redis dependency for real time features
|
||||
- WebSocket support
|
||||
- Horizontal scaling support
|
||||
|
||||
**Setup**:
|
||||
```bash
|
||||
bin/rails solid_cable:install
|
||||
```
|
||||
|
||||
**Usage**:
|
||||
```ruby
|
||||
# config/cable.yml
|
||||
production:
|
||||
adapter: solid_cable
|
||||
|
||||
# Channels work the same as before
|
||||
class ChatChannel < ApplicationCable::Channel
|
||||
def subscribed
|
||||
stream_from "chat_#{params[:room_id]}"
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
### When to Use Rails 8 Defaults
|
||||
|
||||
**Use Solid Queue when**:
|
||||
- Starting new Rails 8 project
|
||||
- Want to avoid Redis operational complexity
|
||||
- Job volume < 1000/second
|
||||
- Prefer simplicity over maximum throughput
|
||||
|
||||
**Use Solid Cache when**:
|
||||
- Starting new Rails 8 project
|
||||
- Cache hit rates are moderate
|
||||
- Want unified database infrastructure
|
||||
- Avoiding Redis operational overhead
|
||||
|
||||
**Use Solid Cable when**:
|
||||
- Starting new Rails 8 project
|
||||
- Real-time features with moderate concurrency
|
||||
- Want to simplify infrastructure
|
||||
- Database can handle WebSocket load
|
||||
|
||||
**Consider alternatives when**:
|
||||
- Extreme performance requirements (millions of jobs/sec)
|
||||
- Very high cache hit rates (>95%)
|
||||
- Thousands of concurrent WebSocket connections
|
||||
- Already have Redis in production
|
||||
|
||||
---
|
||||
|
||||
## Pattern Categories
|
||||
|
||||
The `rails-pattern-finder` skill recognizes these pattern categories:
|
||||
|
||||
### Authentication
|
||||
User login, session management, password handling
|
||||
|
||||
**Common implementations**:
|
||||
- Devise gem
|
||||
- Rails authentication generator (Rails 8+)
|
||||
- Custom authentication with `has_secure_password`
|
||||
- OAuth integrations (OmniAuth)
|
||||
|
||||
### Background Jobs
|
||||
Asynchronous task processing
|
||||
|
||||
**Common implementations**:
|
||||
- Solid Queue (Rails 8 default)
|
||||
- Sidekiq
|
||||
- Resque
|
||||
- Delayed Job
|
||||
- ActiveJob with any adapter
|
||||
|
||||
### Caching
|
||||
Performance optimization through data caching
|
||||
|
||||
**Common implementations**:
|
||||
- Solid Cache (Rails 8 default)
|
||||
- Redis cache store
|
||||
- Memcached
|
||||
- File store
|
||||
- Database-backed caching
|
||||
|
||||
### Real Time
|
||||
WebSocket connections and live updates
|
||||
|
||||
**Common implementations**:
|
||||
- Solid Cable (Rails 8 default for Action Cable)
|
||||
- Action Cable with Redis
|
||||
- Hotwire Turbo Streams
|
||||
- Server-Sent Events (SSE)
|
||||
|
||||
### File Uploads
|
||||
Handling user-uploaded files
|
||||
|
||||
**Common implementations**:
|
||||
- Active Storage (Rails built-in)
|
||||
- Shrine
|
||||
- CarrierWave
|
||||
- Paperclip (deprecated)
|
||||
|
||||
### Pagination
|
||||
Dividing large datasets into pages
|
||||
|
||||
**Common implementations**:
|
||||
- Pagy
|
||||
- Kaminari
|
||||
- will_paginate
|
||||
- Custom pagination with `limit/offset`
|
||||
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
Each pattern includes:
|
||||
- **title**: Human-readable name
|
||||
- **description**: What the pattern does
|
||||
- **search_paths**: Where to find files
|
||||
- **file_patterns**: File naming conventions
|
||||
- **code_patterns**: Regex to match code structures
|
||||
- **keywords**: Related concepts
|
||||
- **best_practice**: Reference implementation
|
||||
|
||||
The `rails-pattern-finder` skill uses these definitions to:
|
||||
1. Search the codebase for existing patterns
|
||||
2. Extract relevant examples
|
||||
3. Provide best-practice templates when pattern not found
|
||||
4. Recommend Rails 8 built-in alternatives when appropriate
|
||||
69
skills/rails-pattern-recognition/skill.md
Normal file
69
skills/rails-pattern-recognition/skill.md
Normal file
@@ -0,0 +1,69 @@
|
||||
---
|
||||
name: rails-pattern-recognition
|
||||
description: Adaptive learning system that tracks implementation patterns, success rates, and performance metrics to suggest proven solutions for future tasks.
|
||||
version: 1.1
|
||||
priority: 5
|
||||
---
|
||||
|
||||
# Rails Pattern Recognition
|
||||
|
||||
Adaptive learning system for Rails workflows.
|
||||
|
||||
## Usage
|
||||
|
||||
```python
|
||||
# Suggest patterns
|
||||
patterns = invoke_skill("rails-pattern-recognition", mode="suggest", context_tags=["rails", "auth"])
|
||||
|
||||
# Update metrics
|
||||
invoke_skill("rails-pattern-recognition", mode="update", metrics={
|
||||
"pattern_used": "Devise Auth",
|
||||
"success": true,
|
||||
"duration_minutes": 15
|
||||
})
|
||||
```
|
||||
|
||||
## Implementation
|
||||
|
||||
This skill manages a JSON database of implementation patterns and their performance metrics.
|
||||
|
||||
### Data Structure (`.claude/data/rails-patterns.json`)
|
||||
|
||||
```json
|
||||
{
|
||||
"patterns": {
|
||||
"Devise Auth": {
|
||||
"tags": ["rails", "auth", "devise"],
|
||||
"uses": 10,
|
||||
"successes": 9,
|
||||
"avg_duration": 12.5,
|
||||
"last_used": "2023-10-27T10:00:00Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Modes
|
||||
|
||||
#### 1. Suggest (`mode="suggest"`)
|
||||
- **Input**: `context_tags` (list of strings)
|
||||
- **Logic**:
|
||||
- Filter patterns matching at least one tag
|
||||
- Calculate confidence score: `(successes / uses) * log(uses + 1)`
|
||||
- Sort by confidence
|
||||
- **Output**: List of patterns with confidence scores
|
||||
|
||||
#### 2. Update (`mode="update"`)
|
||||
- **Input**: `metrics` (dict)
|
||||
- `pattern_used` (string)
|
||||
- `success` (boolean)
|
||||
- `duration_minutes` (float)
|
||||
- **Logic**:
|
||||
- Update `uses`, `successes`
|
||||
- Recalculate `avg_duration`
|
||||
- Update `last_used`
|
||||
- **Output**: Confirmation
|
||||
|
||||
## Standalone Operation
|
||||
|
||||
This skill operates entirely within the project's `.claude/` directory, ensuring portability and independence from global system state.
|
||||
207
skills/rails-performance-patterns/skill.md
Normal file
207
skills/rails-performance-patterns/skill.md
Normal file
@@ -0,0 +1,207 @@
|
||||
---
|
||||
name: rails-performance-patterns
|
||||
description: Detects N+1 queries, suggests eager loading, and recommends database indexes
|
||||
auto_invoke: true
|
||||
trigger_on: [file_modify]
|
||||
file_patterns: ["**/models/**/*.rb", "**/controllers/**/*.rb"]
|
||||
tags: [rails, performance, optimization, n+1, eager-loading, indexes]
|
||||
priority: 2
|
||||
version: 2.0
|
||||
---
|
||||
|
||||
# Rails Performance Patterns Skill
|
||||
|
||||
Automatically detects performance issues and suggests optimizations.
|
||||
|
||||
## What This Skill Does
|
||||
|
||||
**Automatic Detection:**
|
||||
- N+1 query problems (missing eager loading)
|
||||
- Missing database indexes on foreign keys
|
||||
- Inefficient query patterns
|
||||
- Large result sets without pagination
|
||||
|
||||
**When It Activates:**
|
||||
- Model files with associations modified
|
||||
- Controller actions that query models
|
||||
- Iteration over associations detected
|
||||
|
||||
## Key Checks
|
||||
|
||||
### 1. N+1 Query Detection
|
||||
|
||||
**Problem Pattern:**
|
||||
```ruby
|
||||
# app/controllers/posts_controller.rb
|
||||
def index
|
||||
@posts = Post.all # 1 query
|
||||
@posts.each do |post|
|
||||
puts post.author.name # N queries (one per post)
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
**Skill Output:**
|
||||
```
|
||||
⚠️ Performance: Potential N+1 query
|
||||
Location: app/controllers/posts_controller.rb:15
|
||||
Issue: Accessing 'author' association in loop without eager loading
|
||||
|
||||
Fix: Add includes to eager load:
|
||||
@posts = Post.includes(:author).all
|
||||
```
|
||||
|
||||
**Solution:**
|
||||
```ruby
|
||||
def index
|
||||
@posts = Post.includes(:author).all # 2 queries total
|
||||
end
|
||||
```
|
||||
|
||||
### 2. Missing Indexes
|
||||
|
||||
**Checks:**
|
||||
- Foreign keys have indexes
|
||||
- Commonly queried columns indexed
|
||||
- Unique constraints have indexes
|
||||
|
||||
**Example:**
|
||||
```ruby
|
||||
# db/migrate/xxx_create_posts.rb
|
||||
create_table :posts do |t|
|
||||
t.references :user # ✅ Auto-creates index
|
||||
t.string :slug # ❌ Missing index if queried often
|
||||
end
|
||||
```
|
||||
|
||||
**Skill Output:**
|
||||
```
|
||||
⚠️ Performance: Missing index recommendation
|
||||
Location: app/models/post.rb:5
|
||||
Issue: slug column used in where clauses without index
|
||||
|
||||
Add migration:
|
||||
add_index :posts, :slug, unique: true
|
||||
```
|
||||
|
||||
### 3. Pagination Missing
|
||||
|
||||
**Problem:**
|
||||
```ruby
|
||||
def index
|
||||
@products = Product.all # ❌ Loads all 100k+ products
|
||||
end
|
||||
```
|
||||
|
||||
**Skill Output:**
|
||||
```
|
||||
⚠️ Performance: Large result set without pagination
|
||||
Location: app/controllers/products_controller.rb:10
|
||||
Issue: Loading all Product records (estimated 100k+ rows)
|
||||
|
||||
Recommendation: Add pagination
|
||||
# Use kaminari or pagy
|
||||
@products = Product.page(params[:page]).per(20)
|
||||
```
|
||||
|
||||
### 4. Counter Cache Opportunities
|
||||
|
||||
**Pattern:**
|
||||
```ruby
|
||||
# Without counter cache
|
||||
@user.posts.count # Runs COUNT(*) query every time
|
||||
|
||||
# With counter cache
|
||||
@user.posts_count # Reads from cached column
|
||||
```
|
||||
|
||||
**Skill Output:**
|
||||
```
|
||||
ℹ️ Performance: Counter cache opportunity
|
||||
Location: app/views/users/show.html.erb:12
|
||||
Pattern: Frequently accessing post count
|
||||
|
||||
Add to migration:
|
||||
add_column :users, :posts_count, :integer, default: 0
|
||||
|
||||
Update association:
|
||||
belongs_to :user, counter_cache: true
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
```yaml
|
||||
# .rails-performance.yml
|
||||
n1_detection:
|
||||
enabled: true
|
||||
severity: warning
|
||||
|
||||
indexes:
|
||||
check_foreign_keys: true
|
||||
check_query_columns: true
|
||||
|
||||
pagination:
|
||||
warn_threshold: 1000
|
||||
require_for_large_tables: true
|
||||
|
||||
counter_cache:
|
||||
suggest_threshold: 3 # Suggest if accessed 3+ times
|
||||
```
|
||||
|
||||
## Monitoring Integration
|
||||
|
||||
**Add instrumentation:**
|
||||
```ruby
|
||||
# config/initializers/query_monitoring.rb
|
||||
ActiveSupport::Notifications.subscribe('sql.active_record') do |*args|
|
||||
event = ActiveSupport::Notifications::Event.new(*args)
|
||||
if event.duration > 100 # Log slow queries
|
||||
Rails.logger.warn("Slow query (#{event.duration}ms): #{event.payload[:sql]}")
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
## Common Optimizations
|
||||
|
||||
**Eager Loading:**
|
||||
```ruby
|
||||
# N+1
|
||||
Post.all.each { |p| p.author.name }
|
||||
|
||||
# Fixed: includes (left join)
|
||||
Post.includes(:author).each { |p| p.author.name }
|
||||
|
||||
# Fixed: preload (separate queries)
|
||||
Post.preload(:author).each { |p| p.author.name }
|
||||
|
||||
# Fixed: eager_load (always joins)
|
||||
Post.eager_load(:author).each { |p| p.author.name }
|
||||
```
|
||||
|
||||
**Select Specific Columns:**
|
||||
```ruby
|
||||
# Loads all columns
|
||||
Post.all
|
||||
|
||||
# Loads only needed columns
|
||||
Post.select(:id, :title, :created_at)
|
||||
```
|
||||
|
||||
**Batch Processing:**
|
||||
```ruby
|
||||
# Loads all at once
|
||||
Post.all.each { |p| process(p) }
|
||||
|
||||
# Loads in batches of 1000
|
||||
Post.find_each(batch_size: 1000) { |p| process(p) }
|
||||
```
|
||||
|
||||
## References
|
||||
|
||||
- **Rails Performance Guide**: https://guides.rubyonrails.org/performance_testing.html
|
||||
- **Bullet Gem**: https://github.com/flyerhzm/bullet
|
||||
- **Pattern Library**: /patterns/caching-patterns.md
|
||||
|
||||
---
|
||||
|
||||
**This skill helps you build fast Rails applications from the start.**
|
||||
311
skills/rails-security-patterns/skill.md
Normal file
311
skills/rails-security-patterns/skill.md
Normal file
@@ -0,0 +1,311 @@
|
||||
---
|
||||
name: rails-security-patterns
|
||||
description: Automatically validates security best practices and prevents vulnerabilities
|
||||
auto_invoke: true
|
||||
trigger_on: [file_create, file_modify]
|
||||
file_patterns: ["**/controllers/**/*.rb", "**/models/**/*.rb"]
|
||||
tags: [rails, security, authentication, authorization, sql-injection]
|
||||
priority: 1
|
||||
version: 2.0
|
||||
---
|
||||
|
||||
# Rails Security Patterns Skill
|
||||
|
||||
Auto-validates security best practices and blocks common vulnerabilities.
|
||||
|
||||
## What This Skill Does
|
||||
|
||||
**Automatic Security Checks:**
|
||||
- Strong parameters in controllers (prevents mass assignment)
|
||||
- SQL injection prevention (parameterized queries)
|
||||
- CSRF token handling (API mode considerations)
|
||||
- Authentication presence
|
||||
- Authorization checks
|
||||
|
||||
**When It Activates:**
|
||||
- Controller files created or modified
|
||||
- Model files with database queries modified
|
||||
- Authentication-related changes
|
||||
|
||||
## Security Checks
|
||||
|
||||
### 1. Strong Parameters
|
||||
|
||||
**Checks:**
|
||||
- Every `create` and `update` action uses strong parameters
|
||||
- No direct `params` usage in model instantiation
|
||||
- `permit` calls include only expected attributes
|
||||
|
||||
**Example Violation:**
|
||||
```ruby
|
||||
# BAD
|
||||
def create
|
||||
@user = User.create(params[:user]) # ❌ Mass assignment
|
||||
end
|
||||
|
||||
# GOOD
|
||||
def create
|
||||
@user = User.create(user_params) # ✅ Strong params
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def user_params
|
||||
params.require(:user).permit(:name, :email)
|
||||
end
|
||||
```
|
||||
|
||||
**Skill Output:**
|
||||
```
|
||||
❌ Security: Mass assignment vulnerability
|
||||
Location: app/controllers/users_controller.rb:15
|
||||
Issue: params[:user] used directly without strong parameters
|
||||
|
||||
Fix: Define strong parameters method:
|
||||
private
|
||||
|
||||
def user_params
|
||||
params.require(:user).permit(:name, :email, :role)
|
||||
end
|
||||
|
||||
Then use: @user = User.create(user_params)
|
||||
```
|
||||
|
||||
### 2. SQL Injection Prevention
|
||||
|
||||
**Checks:**
|
||||
- No string interpolation in `where` clauses
|
||||
- Parameterized queries used
|
||||
- No raw SQL without placeholders
|
||||
|
||||
**Example Violation:**
|
||||
```ruby
|
||||
# BAD
|
||||
User.where("email = '#{params[:email]}'") # ❌ SQL injection
|
||||
User.where("name LIKE '%#{params[:query]}%'") # ❌ SQL injection
|
||||
|
||||
# GOOD
|
||||
User.where("email = ?", params[:email]) # ✅ Parameterized
|
||||
User.where("name LIKE ?", "%#{params[:query]}%") # ✅ Safe
|
||||
User.where(email: params[:email]) # ✅ Hash syntax
|
||||
```
|
||||
|
||||
**Skill Output:**
|
||||
```
|
||||
❌ Security: SQL injection vulnerability
|
||||
Location: app/models/user.rb:45
|
||||
Issue: String interpolation in SQL query
|
||||
|
||||
Vulnerable code:
|
||||
User.where("email = '#{email}'")
|
||||
|
||||
Fix: Use parameterized query:
|
||||
User.where("email = ?", email)
|
||||
|
||||
Or use hash syntax:
|
||||
User.where(email: email)
|
||||
```
|
||||
|
||||
### 3. Authentication Checks
|
||||
|
||||
**Checks:**
|
||||
- Controllers have authentication filters
|
||||
- Sensitive actions require authentication
|
||||
- Token-based auth for API endpoints
|
||||
|
||||
**Example:**
|
||||
```ruby
|
||||
# app/controllers/posts_controller.rb
|
||||
class PostsController < ApplicationController
|
||||
before_action :authenticate_user! # ✅ Auth required
|
||||
|
||||
def index
|
||||
# ...
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
**Skill Output (if missing):**
|
||||
```
|
||||
⚠️ Security: No authentication found
|
||||
Location: app/controllers/admin/users_controller.rb
|
||||
Issue: Admin controller without authentication
|
||||
|
||||
Recommendation: Add authentication:
|
||||
before_action :authenticate_user!
|
||||
before_action :require_admin!
|
||||
```
|
||||
|
||||
### 4. Authorization Checks
|
||||
|
||||
**Checks:**
|
||||
- Update/destroy actions verify ownership
|
||||
- Role-based access control present
|
||||
- Resource-level authorization
|
||||
|
||||
**Example:**
|
||||
```ruby
|
||||
# BAD
|
||||
def destroy
|
||||
@post = Post.find(params[:id])
|
||||
@post.destroy # ❌ No ownership check
|
||||
end
|
||||
|
||||
# GOOD
|
||||
def destroy
|
||||
@post = current_user.posts.find(params[:id]) # ✅ Scoped to user
|
||||
@post.destroy
|
||||
end
|
||||
|
||||
# BETTER
|
||||
def destroy
|
||||
@post = Post.find(params[:id])
|
||||
authorize @post # ✅ Using Pundit/CanCanCan
|
||||
@post.destroy
|
||||
end
|
||||
```
|
||||
|
||||
**Skill Output:**
|
||||
```
|
||||
⚠️ Security: Missing authorization check
|
||||
Location: app/controllers/posts_controller.rb:42
|
||||
Issue: destroy action without ownership verification
|
||||
|
||||
Recommendation: Add authorization:
|
||||
Option 1 (scope to user):
|
||||
@post = current_user.posts.find(params[:id])
|
||||
|
||||
Option 2 (use authorization gem):
|
||||
authorize @post # Pundit
|
||||
authorize! :destroy, @post # CanCanCan
|
||||
```
|
||||
|
||||
### 5. Sensitive Data Exposure
|
||||
|
||||
**Checks:**
|
||||
- No passwords in logs
|
||||
- API keys not hardcoded
|
||||
- Secrets use environment variables
|
||||
|
||||
**Example Violation:**
|
||||
```ruby
|
||||
# BAD
|
||||
API_KEY = "sk_live_abc123..." # ❌ Hardcoded secret
|
||||
|
||||
# GOOD
|
||||
API_KEY = ENV['STRIPE_API_KEY'] # ✅ Environment variable
|
||||
```
|
||||
|
||||
**Skill Output:**
|
||||
```
|
||||
❌ Security: Hardcoded secret detected
|
||||
Location: config/initializers/stripe.rb:3
|
||||
Issue: API key hardcoded in source
|
||||
|
||||
Fix: Use environment variable:
|
||||
API_KEY = ENV['STRIPE_API_KEY']
|
||||
|
||||
Add to .env (don't commit):
|
||||
STRIPE_API_KEY=sk_live_your_key_here
|
||||
```
|
||||
|
||||
## Integration with Pre-commit Hook
|
||||
|
||||
This skill works with the pre-commit hook to block unsafe commits:
|
||||
|
||||
**Automatic blocks:**
|
||||
- SQL injection vulnerabilities
|
||||
- Missing strong parameters in create/update actions
|
||||
- Hardcoded secrets/API keys
|
||||
- Mass assignment vulnerabilities
|
||||
|
||||
**Warnings (allow commit):**
|
||||
- Missing authentication (might be intentional for public endpoints)
|
||||
- Missing authorization (might use custom logic)
|
||||
- Complex queries (performance concern, not security)
|
||||
|
||||
## Configuration
|
||||
|
||||
Create `.rails-security.yml` to customize:
|
||||
|
||||
```yaml
|
||||
# .rails-security.yml
|
||||
strong_parameters:
|
||||
enforce: true
|
||||
block_commit: true
|
||||
|
||||
sql_injection:
|
||||
enforce: true
|
||||
block_commit: true
|
||||
|
||||
authentication:
|
||||
require_for_controllers: true
|
||||
exceptions:
|
||||
- Api::V1::PublicController
|
||||
- PagesController
|
||||
|
||||
authorization:
|
||||
warn_on_missing: true
|
||||
block_commit: false
|
||||
|
||||
secrets:
|
||||
detect_patterns:
|
||||
- "sk_live_"
|
||||
- "api_key"
|
||||
- "password"
|
||||
- "secret"
|
||||
block_commit: true
|
||||
```
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### API Authentication
|
||||
|
||||
**Token-based:**
|
||||
```ruby
|
||||
class Api::BaseController < ActionController::API
|
||||
before_action :authenticate_token!
|
||||
|
||||
private
|
||||
|
||||
def authenticate_token!
|
||||
token = request.headers['Authorization']&.split(' ')&.last
|
||||
@current_user = User.find_by(api_token: token)
|
||||
render json: { error: 'Unauthorized' }, status: :unauthorized unless @current_user
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
### Scope to User
|
||||
|
||||
**Pattern:**
|
||||
```ruby
|
||||
# Always scope to current_user when possible
|
||||
@posts = current_user.posts
|
||||
@post = current_user.posts.find(params[:id])
|
||||
|
||||
# Prevents accessing other users' resources
|
||||
```
|
||||
|
||||
### Rate Limiting
|
||||
|
||||
**Recommendation:**
|
||||
```ruby
|
||||
# Gemfile
|
||||
gem 'rack-attack'
|
||||
|
||||
# config/initializers/rack_attack.rb
|
||||
Rack::Attack.throttle('api/ip', limit: 100, period: 1.minute) do |req|
|
||||
req.ip if req.path.start_with?('/api/')
|
||||
end
|
||||
```
|
||||
|
||||
## References
|
||||
|
||||
- **OWASP Top 10**: https://owasp.org/www-project-top-ten/
|
||||
- **Rails Security Guide**: https://guides.rubyonrails.org/security.html
|
||||
- **Pattern Library**: /patterns/authentication-patterns.md
|
||||
|
||||
---
|
||||
|
||||
**This skill runs automatically and blocks security vulnerabilities before they reach production.**
|
||||
242
skills/rails-test-patterns/skill.md
Normal file
242
skills/rails-test-patterns/skill.md
Normal file
@@ -0,0 +1,242 @@
|
||||
---
|
||||
name: rails-test-patterns
|
||||
description: Ensures comprehensive test coverage following Rails testing best practices
|
||||
auto_invoke: true
|
||||
trigger_on: [file_create, file_modify]
|
||||
file_patterns: ["**/spec/**/*_spec.rb", "**/test/**/*_test.rb"]
|
||||
tags: [rails, testing, rspec, minitest, coverage, tdd]
|
||||
priority: 2
|
||||
version: 2.0
|
||||
---
|
||||
|
||||
# Rails Test Patterns Skill
|
||||
|
||||
Automatically ensures test quality and comprehensive coverage.
|
||||
|
||||
## What This Skill Does
|
||||
|
||||
**Automatic Checks:**
|
||||
- Test framework detection (RSpec vs Minitest)
|
||||
- AAA pattern (Arrange-Act-Assert)
|
||||
- Coverage thresholds (80%+ models, 70%+ controllers)
|
||||
- Factory usage over fixtures
|
||||
- No flaky tests (sleep, Time.now without freezing)
|
||||
|
||||
**When It Activates:**
|
||||
- Test files created or modified
|
||||
- New models/controllers added (ensures tests exist)
|
||||
|
||||
## Test Quality Checks
|
||||
|
||||
### 1. AAA Pattern
|
||||
|
||||
**Good:**
|
||||
```ruby
|
||||
RSpec.describe User do
|
||||
describe '#full_name' do
|
||||
it 'combines first and last name' do
|
||||
# Arrange
|
||||
user = User.new(first_name: 'John', last_name: 'Doe')
|
||||
|
||||
# Act
|
||||
result = user.full_name
|
||||
|
||||
# Assert
|
||||
expect(result).to eq('John Doe')
|
||||
end
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
**Skill Output (if violated):**
|
||||
```
|
||||
⚠️ Test Pattern: AAA structure not clear
|
||||
Location: spec/models/user_spec.rb:15
|
||||
Recommendation: Separate Arrange, Act, Assert with comments
|
||||
```
|
||||
|
||||
### 2. Framework Detection
|
||||
|
||||
**Detects:**
|
||||
```ruby
|
||||
# RSpec
|
||||
describe Model do
|
||||
it 'does something' do
|
||||
expect(value).to eq(expected)
|
||||
end
|
||||
end
|
||||
|
||||
# Minitest
|
||||
class ModelTest < ActiveSupport::TestCase
|
||||
test "does something" do
|
||||
assert_equal expected, value
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
### 3. Factory Usage
|
||||
|
||||
**Preferred:**
|
||||
```ruby
|
||||
# spec/factories/users.rb
|
||||
FactoryBot.define do
|
||||
factory :user do
|
||||
email { Faker::Internet.email }
|
||||
name { Faker::Name.name }
|
||||
end
|
||||
end
|
||||
|
||||
# In tests
|
||||
user = create(:user)
|
||||
```
|
||||
|
||||
**Skill Output:**
|
||||
```
|
||||
ℹ️ Test Pattern: Consider using FactoryBot
|
||||
Location: spec/models/post_spec.rb:8
|
||||
Current: User.create(name: 'Test', email: 'test@example.com')
|
||||
Recommendation: Define factory and use create(:user)
|
||||
```
|
||||
|
||||
### 4. Coverage Thresholds
|
||||
|
||||
**Checks:**
|
||||
- Models: 80%+ coverage
|
||||
- Controllers: 70%+ coverage
|
||||
- Services: 90%+ coverage
|
||||
|
||||
**Skill Output:**
|
||||
```
|
||||
⚠️ Test Coverage: Below threshold
|
||||
File: app/models/order.rb
|
||||
Coverage: 65% (threshold: 80%)
|
||||
Missing: #calculate_total, #apply_discount methods
|
||||
|
||||
Add tests for uncovered methods.
|
||||
```
|
||||
|
||||
### 5. Flaky Test Detection
|
||||
|
||||
**Problem:**
|
||||
```ruby
|
||||
# BAD
|
||||
it 'expires after 1 hour' do
|
||||
user = create(:user)
|
||||
sleep(3601) # ❌ Actual waiting
|
||||
expect(user.expired?).to be true
|
||||
end
|
||||
|
||||
# GOOD
|
||||
it 'expires after 1 hour' do
|
||||
user = create(:user)
|
||||
travel 1.hour do # ✅ Time travel
|
||||
expect(user.expired?).to be true
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
**Skill Output:**
|
||||
```
|
||||
❌ Test Anti-pattern: Using sleep in test
|
||||
Location: spec/models/session_spec.rb:42
|
||||
Issue: sleep() makes tests slow and flaky
|
||||
|
||||
Fix: Use time helpers
|
||||
travel 1.hour do
|
||||
# assertions here
|
||||
end
|
||||
```
|
||||
|
||||
## Test Types
|
||||
|
||||
### Model Tests
|
||||
|
||||
**Should test:**
|
||||
- Validations
|
||||
- Associations
|
||||
- Scopes
|
||||
- Instance methods
|
||||
- Class methods
|
||||
|
||||
**Example:**
|
||||
```ruby
|
||||
RSpec.describe Post, type: :model do
|
||||
describe 'validations' do
|
||||
it { should validate_presence_of(:title) }
|
||||
it { should validate_uniqueness_of(:slug) }
|
||||
end
|
||||
|
||||
describe 'associations' do
|
||||
it { should belong_to(:user) }
|
||||
it { should have_many(:comments) }
|
||||
end
|
||||
|
||||
describe '#published?' do
|
||||
it 'returns true when published_at is set' do
|
||||
post = build(:post, published_at: 1.day.ago)
|
||||
expect(post.published?).to be true
|
||||
end
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
### Request Tests
|
||||
|
||||
**Should test:**
|
||||
- HTTP status codes
|
||||
- Response body content
|
||||
- Authentication requirements
|
||||
- Authorization checks
|
||||
|
||||
**Example:**
|
||||
```ruby
|
||||
RSpec.describe 'Posts API', type: :request do
|
||||
describe 'GET /api/v1/posts' do
|
||||
it 'returns all posts' do
|
||||
create_list(:post, 3)
|
||||
|
||||
get '/api/v1/posts'
|
||||
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect(JSON.parse(response.body).size).to eq(3)
|
||||
end
|
||||
|
||||
context 'when not authenticated' do
|
||||
it 'returns unauthorized' do
|
||||
get '/api/v1/posts'
|
||||
expect(response).to have_http_status(:unauthorized)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
```yaml
|
||||
# .rails-testing.yml
|
||||
coverage:
|
||||
models: 80
|
||||
controllers: 70
|
||||
services: 90
|
||||
|
||||
patterns:
|
||||
enforce_aaa: warning
|
||||
require_factories: info
|
||||
detect_flaky: error
|
||||
|
||||
framework:
|
||||
auto_detect: true
|
||||
prefer: rspec # or minitest
|
||||
```
|
||||
|
||||
## References
|
||||
|
||||
- **RSpec Best Practices**: https://rspec.info/
|
||||
- **Minitest Guide**: https://github.com/minitest/minitest
|
||||
- **FactoryBot**: https://github.com/thoughtbot/factory_bot
|
||||
- **Pattern Library**: /patterns/testing-patterns.md
|
||||
|
||||
---
|
||||
|
||||
**This skill ensures your Rails app has comprehensive, maintainable test coverage.**
|
||||
173
skills/rails-version-detector/SKILL.md
Normal file
173
skills/rails-version-detector/SKILL.md
Normal file
@@ -0,0 +1,173 @@
|
||||
# Rails Version Detector
|
||||
|
||||
---
|
||||
name: rails-version-detector
|
||||
description: Detects Rails version from project files (Gemfile.lock, Gemfile, or .ruby-version)
|
||||
version: 1.1.0
|
||||
author: Rails Workflow Team
|
||||
tags: [rails, version, detection, helper]
|
||||
priority: 2
|
||||
---
|
||||
|
||||
## Purpose
|
||||
|
||||
Automatically detects the Rails version in the current project by inspecting:
|
||||
1. `Gemfile.lock` (preferred - exact version)
|
||||
2. `Gemfile` (fallback - version constraint)
|
||||
3. `.ruby-version` or `.tool-versions` (Ruby version hint)
|
||||
|
||||
Returns version information for use by other skills and agents.
|
||||
|
||||
## Usage
|
||||
|
||||
This skill is **auto-invoked** by other Rails skills when they need version information.
|
||||
|
||||
**Manual invocation** (rarely needed):
|
||||
```
|
||||
@rails-version-detector
|
||||
```
|
||||
|
||||
## Detection Strategy
|
||||
|
||||
### Priority 1: Gemfile.lock (Exact Version)
|
||||
```ruby
|
||||
# Searches for:
|
||||
rails (7.1.3)
|
||||
actioncable (= 7.1.3)
|
||||
actionmailbox (= 7.1.3)
|
||||
...
|
||||
```
|
||||
|
||||
**Extraction logic**:
|
||||
- Pattern: `rails \((\d+\.\d+\.\d+)\)`
|
||||
- Returns: Exact version (e.g., "7.1.3")
|
||||
|
||||
### Priority 2: Gemfile (Version Constraint)
|
||||
```ruby
|
||||
# Searches for:
|
||||
gem "rails", "~> 7.1.0"
|
||||
gem 'rails', '>= 7.0.0', '< 8.0'
|
||||
```
|
||||
|
||||
**Extraction logic**:
|
||||
- Pattern: `gem ['"]rails['"],\s*['"]([^'"]+)['"]`
|
||||
- Returns: Version constraint (e.g., "~> 7.1.0")
|
||||
- Interprets: "~> 7.1.0" → "7.1.x"
|
||||
|
||||
### Priority 3: Ruby Version (Heuristic)
|
||||
```ruby
|
||||
# .ruby-version or .tool-versions
|
||||
ruby 3.2.2
|
||||
```
|
||||
|
||||
**Mapping**:
|
||||
- Ruby 3.3.x → Rails 7.1+ or 8.0+
|
||||
- Ruby 3.2.x → Rails 7.0+
|
||||
- Ruby 3.1.x → Rails 7.0+
|
||||
- Ruby 3.0.x → Rails 6.1+ or 7.0
|
||||
- Ruby 2.7.x → Rails 6.0 or 6.1
|
||||
|
||||
**Returns**: Estimated range (e.g., "7.0 or 7.1")
|
||||
|
||||
## Output Format
|
||||
|
||||
### Success Response
|
||||
```json
|
||||
{
|
||||
"version": "7.1.3",
|
||||
"source": "Gemfile.lock",
|
||||
"confidence": "exact",
|
||||
"major": 7,
|
||||
"minor": 1,
|
||||
"patch": 3
|
||||
}
|
||||
```
|
||||
|
||||
### Constraint Response
|
||||
```json
|
||||
{
|
||||
"version": "~> 7.1.0",
|
||||
"source": "Gemfile",
|
||||
"confidence": "constraint",
|
||||
"interpreted_as": "7.1.x",
|
||||
"major": 7,
|
||||
"minor": 1
|
||||
}
|
||||
```
|
||||
|
||||
### Heuristic Response
|
||||
```json
|
||||
{
|
||||
"version": "7.0-7.1",
|
||||
"source": ".ruby-version",
|
||||
"confidence": "heuristic",
|
||||
"ruby_version": "3.2.2",
|
||||
"possible_rails": ["7.0", "7.1"]
|
||||
}
|
||||
```
|
||||
|
||||
### Not Found Response
|
||||
```json
|
||||
{
|
||||
"version": null,
|
||||
"source": null,
|
||||
"confidence": "none",
|
||||
"error": "No Rails version detected. Is this a Rails project?"
|
||||
}
|
||||
```
|
||||
|
||||
## Implementation
|
||||
|
||||
**Tool usage**:
|
||||
- `Read` tool to read Gemfile.lock, Gemfile, .ruby-version
|
||||
- `Grep` tool to search for version patterns
|
||||
- Fallback to `Bash` if needed: `bundle show rails | grep 'rails-'`
|
||||
|
||||
**Caching**:
|
||||
- Version detection results cached for session
|
||||
- Re-check if Gemfile.lock modified (check mtime)
|
||||
|
||||
## Error Handling
|
||||
|
||||
**Gemfile.lock missing**:
|
||||
- Fallback to Gemfile
|
||||
- Warn: "Gemfile.lock not found. Run `bundle install` for exact version."
|
||||
|
||||
**No version found**:
|
||||
- Return error response
|
||||
- Suggest: "Ensure `gem 'rails'` in Gemfile"
|
||||
|
||||
**Multiple Rails versions**:
|
||||
- Return first match (main Rails gem)
|
||||
- Ignore: railties, actionpack (these are sub-gems)
|
||||
|
||||
## Integration
|
||||
|
||||
**Auto-invoked by**:
|
||||
- @rails-docs-search (to fetch correct version docs)
|
||||
- @rails-api-lookup (to search version-specific APIs)
|
||||
- @rails-pattern-finder (to find version-appropriate patterns)
|
||||
- All 7 Rails agents (to ensure version-compatible code generation)
|
||||
|
||||
**Manual use case**:
|
||||
```
|
||||
User: "What Rails version is this project using?"
|
||||
Assistant: *invokes @rails-version-detector*
|
||||
"This project uses Rails 7.1.3 (detected from Gemfile.lock)"
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
**Test cases**:
|
||||
1. Rails 7.1.3 in Gemfile.lock → Exact version
|
||||
2. `~> 7.1.0` in Gemfile → Interpreted as 7.1.x
|
||||
3. Ruby 3.2.2 only → Heuristic: 7.0-7.1
|
||||
4. No Rails → Error response
|
||||
5. Rails 8.0.0 → Correct major version detection
|
||||
|
||||
## Notes
|
||||
|
||||
- This skill replaces the need for `mcp__rails__get_rails_info` MCP tool
|
||||
- Version detection is fast (< 1 second)
|
||||
- Results cached per session to avoid repeated file reads
|
||||
- Confidence levels help downstream skills decide if they need clarification from user
|
||||
Reference in New Issue
Block a user