Initial commit

This commit is contained in:
Zhongwei Li
2025-11-30 08:30:07 +08:00
commit d6f6fcbaad
33 changed files with 4697 additions and 0 deletions

View 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

View 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)

View 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"

View 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"