Initial commit
This commit is contained in:
342
skills/ruby-pattern-detector/SKILL.md
Normal file
342
skills/ruby-pattern-detector/SKILL.md
Normal file
@@ -0,0 +1,342 @@
|
||||
# Ruby Pattern Detector Skill
|
||||
|
||||
Automatically detect and suggest common Ruby patterns when working with Ruby code.
|
||||
|
||||
## When to Activate
|
||||
|
||||
This skill activates when:
|
||||
- Reading or editing Ruby files
|
||||
- User asks about Ruby patterns or best practices
|
||||
- Refactoring Ruby code
|
||||
- Reviewing Ruby code
|
||||
|
||||
## Patterns to Detect
|
||||
|
||||
### 1. Data Object Pattern
|
||||
|
||||
**Detect:**
|
||||
- Classes with many attr_reader/attr_accessor declarations
|
||||
- Classes that primarily hold data
|
||||
- Classes with `to_h` or `to_json` methods
|
||||
|
||||
**Suggest:**
|
||||
```ruby
|
||||
class DataObject
|
||||
# Add .build class method for construction
|
||||
def self.build(attributes)
|
||||
new(
|
||||
name: attributes[:name],
|
||||
email: attributes[:email]
|
||||
)
|
||||
end
|
||||
|
||||
# Add serialization
|
||||
def to_h
|
||||
{
|
||||
name: name,
|
||||
email: email
|
||||
}
|
||||
end
|
||||
|
||||
# Add factory methods
|
||||
def self.from_json(json)
|
||||
build(JSON.parse(json, symbolize_names: true))
|
||||
end
|
||||
|
||||
def self.from_h(hash)
|
||||
build(hash)
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
### 2. Loggable Module Pattern
|
||||
|
||||
**Detect:**
|
||||
- Classes with logging statements
|
||||
- Multiple classes that need logging
|
||||
- Direct Logger instantiation in classes
|
||||
|
||||
**Suggest:**
|
||||
```ruby
|
||||
# Create shared Loggable module
|
||||
module Loggable
|
||||
def logger
|
||||
@logger ||= Logger.new(STDOUT).tap do |log|
|
||||
log.progname = self.class.name
|
||||
log.level = ENV.fetch('LOG_LEVEL', 'INFO')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Include in classes
|
||||
class MyClass
|
||||
include Loggable
|
||||
|
||||
def process
|
||||
logger.info "Processing started"
|
||||
# ...
|
||||
logger.debug "Details: #{details}"
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
### 3. Custom Exception Pattern
|
||||
|
||||
**Detect:**
|
||||
- Raising generic exceptions (RuntimeError, StandardError)
|
||||
- Classes with domain-specific errors
|
||||
- Rescue blocks catching broad exceptions
|
||||
|
||||
**Suggest:**
|
||||
```ruby
|
||||
# Define custom exceptions
|
||||
module MyApp
|
||||
class Error < StandardError; end
|
||||
class NotFoundError < Error; end
|
||||
class ValidationError < Error; end
|
||||
class AuthenticationError < Error; end
|
||||
end
|
||||
|
||||
# Use specific exceptions
|
||||
class UserService
|
||||
def find(id)
|
||||
user = repository.find(id)
|
||||
raise MyApp::NotFoundError, "User #{id} not found" unless user
|
||||
user
|
||||
end
|
||||
|
||||
def authenticate(credentials)
|
||||
raise MyApp::ValidationError, "Invalid credentials" if invalid?(credentials)
|
||||
# ...
|
||||
rescue SomeExternalError => e
|
||||
raise MyApp::AuthenticationError, "Auth failed: #{e.message}"
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
### 4. Dependency Injection Pattern
|
||||
|
||||
**Detect:**
|
||||
- Classes instantiating other classes directly
|
||||
- Hard-coded dependencies
|
||||
- Difficult to test classes
|
||||
- Use of global state or singletons
|
||||
|
||||
**Suggest:**
|
||||
```ruby
|
||||
# Before - hard-coded dependency
|
||||
class OrderProcessor
|
||||
def process(order)
|
||||
mailer = EmailMailer.new
|
||||
mailer.send_confirmation(order)
|
||||
end
|
||||
end
|
||||
|
||||
# After - injected dependency
|
||||
class OrderProcessor
|
||||
def initialize(mailer: EmailMailer.new)
|
||||
@mailer = mailer
|
||||
end
|
||||
|
||||
def process(order)
|
||||
@mailer.send_confirmation(order)
|
||||
end
|
||||
end
|
||||
|
||||
# Easy to test with mock
|
||||
processor = OrderProcessor.new(mailer: MockMailer.new)
|
||||
```
|
||||
|
||||
### 5. Null Object Pattern
|
||||
|
||||
**Detect:**
|
||||
- Frequent nil checks
|
||||
- Conditional logic checking for nil
|
||||
- `try` or `&.` operators used extensively
|
||||
|
||||
**Suggest:**
|
||||
```ruby
|
||||
# Create Null Object
|
||||
class NullUser
|
||||
def name
|
||||
"Guest"
|
||||
end
|
||||
|
||||
def email
|
||||
nil
|
||||
end
|
||||
|
||||
def admin?
|
||||
false
|
||||
end
|
||||
|
||||
def null?
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
# Use in code
|
||||
class Session
|
||||
def current_user
|
||||
@current_user || NullUser.new
|
||||
end
|
||||
end
|
||||
|
||||
# No more nil checks needed
|
||||
session.current_user.name # Returns "Guest" instead of raising error
|
||||
```
|
||||
|
||||
### 6. Value Object Pattern
|
||||
|
||||
**Detect:**
|
||||
- Primitive obsession (lots of strings/integers used as domain concepts)
|
||||
- Data validation scattered throughout code
|
||||
- Lack of encapsulation for related data
|
||||
|
||||
**Suggest:**
|
||||
```ruby
|
||||
# Before - primitive obsession
|
||||
def send_email(email_string)
|
||||
raise "Invalid email" unless email_string =~ /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i
|
||||
# ...
|
||||
end
|
||||
|
||||
# After - Value Object
|
||||
class Email
|
||||
attr_reader :value
|
||||
|
||||
def initialize(value)
|
||||
@value = value.to_s.downcase.strip
|
||||
validate!
|
||||
end
|
||||
|
||||
def ==(other)
|
||||
value == other.value
|
||||
end
|
||||
|
||||
def to_s
|
||||
value
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def validate!
|
||||
raise ArgumentError, "Invalid email: #{value}" unless valid?
|
||||
end
|
||||
|
||||
def valid?
|
||||
value =~ /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i
|
||||
end
|
||||
end
|
||||
|
||||
def send_email(email)
|
||||
# Email already validated
|
||||
mailer.send(to: email.to_s)
|
||||
end
|
||||
```
|
||||
|
||||
### 7. Query Object Pattern
|
||||
|
||||
**Detect:**
|
||||
- Complex ActiveRecord scopes
|
||||
- Long chains of where clauses
|
||||
- Business logic in controllers or models
|
||||
|
||||
**Suggest:**
|
||||
```ruby
|
||||
# Extract to Query Object
|
||||
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
|
||||
active_users = ActiveUsersQuery.new.call
|
||||
recent_active_users = ActiveUsersQuery.new(User.where('created_at > ?', 1.week.ago)).call
|
||||
```
|
||||
|
||||
### 8. Service Object Pattern
|
||||
|
||||
**Detect:**
|
||||
- Fat controllers or models
|
||||
- Complex multi-step operations
|
||||
- Methods that orchestrate multiple objects
|
||||
|
||||
**Suggest:**
|
||||
```ruby
|
||||
class CreateOrderService
|
||||
def initialize(user:, items:, payment_method:)
|
||||
@user = user
|
||||
@items = items
|
||||
@payment_method = payment_method
|
||||
end
|
||||
|
||||
def call
|
||||
ActiveRecord::Base.transaction do
|
||||
order = create_order
|
||||
process_payment(order)
|
||||
send_confirmation(order)
|
||||
order
|
||||
end
|
||||
rescue PaymentError => e
|
||||
handle_payment_failure(e)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :user, :items, :payment_method
|
||||
|
||||
def create_order
|
||||
# ...
|
||||
end
|
||||
|
||||
def process_payment(order)
|
||||
# ...
|
||||
end
|
||||
|
||||
def send_confirmation(order)
|
||||
# ...
|
||||
end
|
||||
end
|
||||
|
||||
# Usage
|
||||
result = CreateOrderService.new(
|
||||
user: current_user,
|
||||
items: cart.items,
|
||||
payment_method: params[:payment_method]
|
||||
).call
|
||||
```
|
||||
|
||||
## Activation Response
|
||||
|
||||
When a pattern is detected, respond with:
|
||||
|
||||
**Pattern Detected: [Pattern Name]**
|
||||
|
||||
I noticed [specific code smell or opportunity].
|
||||
|
||||
This is a good opportunity to use the **[Pattern Name]** pattern, which:
|
||||
- [Benefit 1]
|
||||
- [Benefit 2]
|
||||
- [Benefit 3]
|
||||
|
||||
Would you like me to refactor this code to use this pattern?
|
||||
|
||||
[Show brief before/after example]
|
||||
|
||||
## Guidelines
|
||||
|
||||
- Only suggest patterns when clearly beneficial
|
||||
- Don't over-engineer simple code
|
||||
- Explain the "why" behind each pattern suggestion
|
||||
- Provide concrete code examples
|
||||
- Consider the context and project size
|
||||
- Balance between pattern purity and pragmatism
|
||||
169
skills/ruby-pattern-detector/scripts/README.md
Normal file
169
skills/ruby-pattern-detector/scripts/README.md
Normal file
@@ -0,0 +1,169 @@
|
||||
# Pattern Detector / RuboCop Scripts
|
||||
|
||||
Executable shell scripts for code style checking and analysis.
|
||||
|
||||
## Scripts
|
||||
|
||||
### run_rubocop.sh
|
||||
Run RuboCop with helpful options.
|
||||
|
||||
```bash
|
||||
# Check all files
|
||||
./run_rubocop.sh check
|
||||
|
||||
# Auto-fix safe violations
|
||||
./run_rubocop.sh fix
|
||||
|
||||
# Auto-fix all violations (including unsafe)
|
||||
./run_rubocop.sh fix-all
|
||||
|
||||
# Check only changed files
|
||||
./run_rubocop.sh changed
|
||||
|
||||
# Check only staged files (for pre-commit)
|
||||
./run_rubocop.sh staged
|
||||
|
||||
# Check specific file
|
||||
./run_rubocop.sh lib/user.rb
|
||||
```
|
||||
|
||||
**Modes:**
|
||||
- `check` - Check code style (no changes)
|
||||
- `fix` - Auto-fix safe violations (-a flag)
|
||||
- `fix-all` - Auto-fix all violations (-A flag, use with caution)
|
||||
- `changed` - Only check git-modified files
|
||||
- `staged` - Only check git-staged files
|
||||
- `<file_path>` - Check specific file
|
||||
|
||||
**Features:**
|
||||
- Auto-detects RuboCop availability
|
||||
- Smart git integration
|
||||
- Helpful error messages
|
||||
- Shows which mode is running
|
||||
|
||||
### rubocop_summary.sh
|
||||
Generate comprehensive RuboCop summary.
|
||||
|
||||
```bash
|
||||
./rubocop_summary.sh
|
||||
```
|
||||
|
||||
**Output:**
|
||||
```
|
||||
📊 Generating RuboCop summary...
|
||||
|
||||
Summary:
|
||||
Files inspected: 45
|
||||
Total offenses: 127
|
||||
|
||||
Top offenses by cop:
|
||||
42 Style/StringLiterals
|
||||
23 Layout/LineLength
|
||||
15 Style/Documentation
|
||||
12 Metrics/MethodLength
|
||||
...
|
||||
|
||||
Offense severity breakdown:
|
||||
85 convention
|
||||
32 warning
|
||||
10 error
|
||||
|
||||
To fix auto-correctable offenses:
|
||||
bundle exec rubocop -a
|
||||
```
|
||||
|
||||
**Features:**
|
||||
- Runs RuboCop with JSON output
|
||||
- Parses and summarizes results
|
||||
- Shows top offense types
|
||||
- Severity breakdown
|
||||
- Actionable next steps
|
||||
|
||||
**Requires:**
|
||||
- jq (JSON processor)
|
||||
- RuboCop gem
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Pre-Commit Workflow
|
||||
```bash
|
||||
# Check staged files before commit
|
||||
./run_rubocop.sh staged
|
||||
|
||||
# Auto-fix if possible
|
||||
./run_rubocop.sh fix
|
||||
|
||||
# Stage fixes
|
||||
git add .
|
||||
|
||||
# Check again
|
||||
./run_rubocop.sh staged
|
||||
```
|
||||
|
||||
### Code Review Workflow
|
||||
```bash
|
||||
# Check changed files
|
||||
./run_rubocop.sh changed
|
||||
|
||||
# Get detailed summary
|
||||
./rubocop_summary.sh
|
||||
|
||||
# Fix safe violations
|
||||
./run_rubocop.sh fix
|
||||
|
||||
# Review remaining issues manually
|
||||
```
|
||||
|
||||
### Clean-up Workflow
|
||||
```bash
|
||||
# Get overview
|
||||
./rubocop_summary.sh
|
||||
|
||||
# Fix safe violations
|
||||
./run_rubocop.sh fix
|
||||
|
||||
# Review unsafe fixes
|
||||
./run_rubocop.sh check
|
||||
|
||||
# Manually fix or disable cops
|
||||
```
|
||||
|
||||
## RuboCop Configuration
|
||||
|
||||
Create `.rubocop.yml` for project-specific rules:
|
||||
|
||||
```yaml
|
||||
AllCops:
|
||||
NewCops: enable
|
||||
TargetRubyVersion: 3.2
|
||||
Exclude:
|
||||
- 'db/schema.rb'
|
||||
- 'vendor/**/*'
|
||||
- 'node_modules/**/*'
|
||||
|
||||
Metrics/MethodLength:
|
||||
Max: 10 # Sandi Metz's rule
|
||||
|
||||
Metrics/ClassLength:
|
||||
Max: 100 # Sandi Metz's rule
|
||||
|
||||
Style/Documentation:
|
||||
Enabled: false # Adjust as needed
|
||||
```
|
||||
|
||||
## Integration with Git Hooks
|
||||
|
||||
Use `staged` mode in pre-commit hook:
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# .git/hooks/pre-commit
|
||||
|
||||
/path/to/run_rubocop.sh staged
|
||||
```
|
||||
|
||||
## Requirements
|
||||
|
||||
- RuboCop gem
|
||||
- jq (for rubocop_summary.sh)
|
||||
- git (for changed/staged modes)
|
||||
43
skills/ruby-pattern-detector/scripts/rubocop_summary.sh
Executable file
43
skills/ruby-pattern-detector/scripts/rubocop_summary.sh
Executable file
@@ -0,0 +1,43 @@
|
||||
#!/usr/bin/env bash
|
||||
# Generate RuboCop summary report
|
||||
|
||||
set -e
|
||||
|
||||
echo "📊 Generating RuboCop summary..."
|
||||
|
||||
# Run RuboCop with JSON output
|
||||
bundle exec rubocop --format json --out tmp/rubocop_results.json --format progress || true
|
||||
|
||||
if [ ! -f "tmp/rubocop_results.json" ]; then
|
||||
echo "No RuboCop results found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Parse results
|
||||
TOTAL_FILES=$(jq '.files | length' tmp/rubocop_results.json)
|
||||
OFFENSE_COUNT=$(jq '.summary.offense_count' tmp/rubocop_results.json)
|
||||
INSPECTED=$(jq '.summary.inspected_file_count' tmp/rubocop_results.json)
|
||||
|
||||
echo ""
|
||||
echo "Summary:"
|
||||
echo " Files inspected: $INSPECTED"
|
||||
echo " Total offenses: $OFFENSE_COUNT"
|
||||
echo ""
|
||||
|
||||
if [ "$OFFENSE_COUNT" -eq 0 ]; then
|
||||
echo "✅ No offenses found!"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "Top offenses by cop:"
|
||||
jq -r '.files[].offenses[] | .cop_name' tmp/rubocop_results.json | \
|
||||
sort | uniq -c | sort -rn | head -10
|
||||
|
||||
echo ""
|
||||
echo "Offense severity breakdown:"
|
||||
jq -r '.files[].offenses[] | .severity' tmp/rubocop_results.json | \
|
||||
sort | uniq -c | sort -rn
|
||||
|
||||
echo ""
|
||||
echo "To fix auto-correctable offenses:"
|
||||
echo " bundle exec rubocop -a"
|
||||
62
skills/ruby-pattern-detector/scripts/run_rubocop.sh
Executable file
62
skills/ruby-pattern-detector/scripts/run_rubocop.sh
Executable file
@@ -0,0 +1,62 @@
|
||||
#!/usr/bin/env bash
|
||||
# Run RuboCop with helpful options
|
||||
|
||||
set -e
|
||||
|
||||
MODE=${1:-check}
|
||||
|
||||
echo "🔍 Running RuboCop (mode: $MODE)..."
|
||||
|
||||
# Check if RuboCop is available
|
||||
if ! bundle exec rubocop --version &> /dev/null; then
|
||||
echo "❌ RuboCop not found. Add to Gemfile:"
|
||||
echo " gem 'rubocop', require: false"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
case "$MODE" in
|
||||
check)
|
||||
echo "Checking code style..."
|
||||
bundle exec rubocop
|
||||
;;
|
||||
fix)
|
||||
echo "Auto-fixing safe violations..."
|
||||
bundle exec rubocop -a
|
||||
;;
|
||||
fix-all)
|
||||
echo "⚠️ Auto-fixing all violations (including unsafe)..."
|
||||
bundle exec rubocop -A
|
||||
;;
|
||||
changed)
|
||||
echo "Checking only changed files..."
|
||||
CHANGED_FILES=$(git diff --name-only --diff-filter=AM | grep '\.rb$' || true)
|
||||
if [ -z "$CHANGED_FILES" ]; then
|
||||
echo "No changed Ruby files found"
|
||||
exit 0
|
||||
fi
|
||||
bundle exec rubocop $CHANGED_FILES
|
||||
;;
|
||||
staged)
|
||||
echo "Checking only staged files..."
|
||||
STAGED_FILES=$(git diff --cached --name-only --diff-filter=AM | grep '\.rb$' || true)
|
||||
if [ -z "$STAGED_FILES" ]; then
|
||||
echo "No staged Ruby files found"
|
||||
exit 0
|
||||
fi
|
||||
bundle exec rubocop $STAGED_FILES
|
||||
;;
|
||||
*)
|
||||
# Treat as file path
|
||||
if [ -f "$MODE" ]; then
|
||||
echo "Checking file: $MODE"
|
||||
bundle exec rubocop "$MODE"
|
||||
else
|
||||
echo "Unknown mode: $MODE"
|
||||
echo "Available modes: check, fix, fix-all, changed, staged, <file_path>"
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
echo ""
|
||||
echo "✅ RuboCop check complete"
|
||||
Reference in New Issue
Block a user