Initial commit
This commit is contained in:
231
hooks/analyze-and-log-smells.md
Normal file
231
hooks/analyze-and-log-smells.md
Normal file
@@ -0,0 +1,231 @@
|
||||
# Code Smell Logger Hook
|
||||
|
||||
This hook analyzes Ruby files after they're written and logs detected code smells to help track refactoring opportunities.
|
||||
|
||||
## Trigger
|
||||
|
||||
- Event: post-write
|
||||
- Pattern: **/*.rb
|
||||
|
||||
## Purpose
|
||||
|
||||
Maintain a running log of code smells to:
|
||||
- Track refactoring debt over time
|
||||
- Prioritize refactoring efforts
|
||||
- Learn from recurring patterns
|
||||
- Monitor code quality trends
|
||||
|
||||
## Analysis Process
|
||||
|
||||
### 1. Detect Code Smells
|
||||
|
||||
Analyze the file for common smells based on Sandi Metz's principles:
|
||||
|
||||
**Complexity Smells:**
|
||||
- Methods longer than 5 lines
|
||||
- Classes longer than 100 lines
|
||||
- Cyclomatic complexity > 3
|
||||
- Nested conditionals (depth > 2)
|
||||
- Long parameter lists (> 4 parameters)
|
||||
|
||||
**Design Smells:**
|
||||
- Missing dependency injection
|
||||
- Feature envy (accessing other objects' data)
|
||||
- Data clumps (same parameters used together)
|
||||
- Primitive obsession
|
||||
- Law of Demeter violations (chained method calls)
|
||||
|
||||
**OO Design Smells:**
|
||||
- Missing single responsibility
|
||||
- Unclear public/private interface
|
||||
- Deep inheritance (> 1 level)
|
||||
- Missing abstraction opportunities
|
||||
- Duplication (similar code patterns)
|
||||
|
||||
**Ruby-Specific Smells:**
|
||||
- Missing frozen_string_literal
|
||||
- Not using Ruby iterators (using for loops)
|
||||
- Not using blocks/yield appropriately
|
||||
- Missing null object pattern where appropriate
|
||||
- Overly complex conditionals vs polymorphism
|
||||
|
||||
### 2. Calculate Severity
|
||||
|
||||
**High Priority:**
|
||||
- Multiple responsibilities in one class
|
||||
- Deep nesting (> 2 levels)
|
||||
- Methods > 10 lines
|
||||
- Clear SOLID violations
|
||||
|
||||
**Medium Priority:**
|
||||
- Methods 6-10 lines
|
||||
- Missing dependency injection
|
||||
- Law of Demeter violations
|
||||
- Primitive obsession
|
||||
|
||||
**Low Priority:**
|
||||
- Methods exactly 5 lines
|
||||
- Minor style issues
|
||||
- Potential improvements (not violations)
|
||||
|
||||
### 3. Log Format
|
||||
|
||||
Append to `.claude/code-smells.log`:
|
||||
|
||||
```
|
||||
[TIMESTAMP] - [FILE_PATH]
|
||||
Severity: [HIGH/MEDIUM/LOW]
|
||||
Smell: [SMELL_NAME]
|
||||
Location: [CLASS_NAME]#[METHOD_NAME] (lines X-Y)
|
||||
Description: [Specific issue detected]
|
||||
Suggestion: [Refactoring approach]
|
||||
Related Pattern: [POODR principle or pattern]
|
||||
---
|
||||
```
|
||||
|
||||
## Example Output
|
||||
|
||||
```
|
||||
2025-11-01 14:23:15 - lib/user_service.rb
|
||||
Severity: HIGH
|
||||
Smell: God Object / Multiple Responsibilities
|
||||
Location: UserService (lines 1-156)
|
||||
Description: Class has 8 public methods handling authentication, validation, email sending, and database operations
|
||||
Suggestion: Extract into separate services: UserAuthenticator, UserValidator, UserMailer, UserRepository
|
||||
Related Pattern: Single Responsibility Principle (POODR Ch. 2)
|
||||
---
|
||||
|
||||
2025-11-01 14:23:15 - lib/user_service.rb
|
||||
Severity: MEDIUM
|
||||
Smell: Long Method
|
||||
Location: UserService#create_user (lines 23-42)
|
||||
Description: Method is 19 lines, handles validation, creation, and email sending
|
||||
Suggestion: Extract methods: validate_user_data, persist_user, send_welcome_email
|
||||
Related Pattern: Small Methods (Sandi's Rules)
|
||||
---
|
||||
|
||||
2025-11-01 14:23:15 - lib/order_processor.rb
|
||||
Severity: MEDIUM
|
||||
Smell: Missing Dependency Injection
|
||||
Location: OrderProcessor#process (line 15)
|
||||
Description: Hard-coded instantiation of EmailMailer.new inside method
|
||||
Suggestion: Inject mailer dependency through constructor
|
||||
Related Pattern: Dependency Injection (POODR Ch. 3)
|
||||
---
|
||||
|
||||
2025-11-01 14:23:15 - lib/payment_handler.rb
|
||||
Severity: LOW
|
||||
Smell: Primitive Obsession
|
||||
Location: PaymentHandler#validate_card (lines 8-12)
|
||||
Description: Using string for credit card number validation
|
||||
Suggestion: Create CreditCard value object with validation
|
||||
Related Pattern: Value Object pattern
|
||||
---
|
||||
```
|
||||
|
||||
## Response to User
|
||||
|
||||
After logging, provide a brief summary:
|
||||
|
||||
**Code Smell Analysis Complete**
|
||||
|
||||
Analyzed: [file_path]
|
||||
|
||||
**Detected Issues:**
|
||||
- 1 High Priority: God Object in UserService
|
||||
- 2 Medium Priority: Long method, missing DI
|
||||
- 1 Low Priority: Primitive obsession
|
||||
|
||||
**Top Recommendation:**
|
||||
Extract UserService into separate concerns following Single Responsibility Principle.
|
||||
|
||||
Full details logged to `.claude/code-smells.log`
|
||||
|
||||
**Quick Actions:**
|
||||
- Run `/refactor` to get detailed refactoring plan
|
||||
- Run `/review-ruby` for comprehensive code review
|
||||
- View log: `cat .claude/code-smells.log | tail -20`
|
||||
|
||||
## Log Management
|
||||
|
||||
### View Recent Smells
|
||||
```bash
|
||||
# Last 20 entries
|
||||
tail -20 .claude/code-smells.log
|
||||
|
||||
# Smells from specific file
|
||||
grep "lib/user.rb" .claude/code-smells.log
|
||||
|
||||
# High priority only
|
||||
grep "Severity: HIGH" .claude/code-smells.log
|
||||
|
||||
# By smell type
|
||||
grep "Smell: Long Method" .claude/code-smells.log
|
||||
```
|
||||
|
||||
### Generate Summary Report
|
||||
```bash
|
||||
# Count by severity
|
||||
grep -c "Severity: HIGH" .claude/code-smells.log
|
||||
grep -c "Severity: MEDIUM" .claude/code-smells.log
|
||||
grep -c "Severity: LOW" .claude/code-smells.log
|
||||
|
||||
# Most common smells
|
||||
grep "Smell:" .claude/code-smells.log | sort | uniq -c | sort -rn
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
Customize in `.claude/settings.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"plugins": {
|
||||
"rubyist": {
|
||||
"codeSmellLogging": {
|
||||
"enabled": true,
|
||||
"logPath": ".claude/code-smells.log",
|
||||
"severityThreshold": "MEDIUM",
|
||||
"excludePatterns": [
|
||||
"**/*_spec.rb",
|
||||
"db/migrate/**",
|
||||
"lib/generated/**"
|
||||
],
|
||||
"autoRefactorSuggestions": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Integration with Refactoring Workflow
|
||||
|
||||
The log helps prioritize refactoring:
|
||||
|
||||
1. **Review accumulated smells** weekly/sprint
|
||||
2. **Prioritize by frequency** (same smell in multiple files)
|
||||
3. **Track improvements** (smell resolution over time)
|
||||
4. **Learn patterns** (recurring issues suggest training needs)
|
||||
|
||||
## Educational Benefit
|
||||
|
||||
Over time, the log becomes a learning tool:
|
||||
- See which smells occur most frequently
|
||||
- Understand which POODR principles need focus
|
||||
- Track improvement in code quality
|
||||
- Share patterns with team
|
||||
|
||||
## Privacy Note
|
||||
|
||||
The log is local to the repository and should be added to `.gitignore` unless team wants to track collectively:
|
||||
|
||||
```
|
||||
# .gitignore
|
||||
.claude/code-smells.log
|
||||
```
|
||||
|
||||
Or commit for team awareness:
|
||||
```
|
||||
# Track team code quality
|
||||
!.claude/code-smells.log
|
||||
```
|
||||
35
hooks/hooks.json
Normal file
35
hooks/hooks.json
Normal file
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"name": "ruby-pre-commit",
|
||||
"description": "Run RuboCop on staged Ruby files before commit",
|
||||
"event": "pre-commit",
|
||||
"command": "git diff --cached --name-only --diff-filter=ACM | grep '\\.rb$' | xargs -r bundle exec rubocop --force-exclusion",
|
||||
"continueOnError": false,
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
"name": "ruby-post-write",
|
||||
"description": "Suggest tests for newly created Ruby files",
|
||||
"event": "post-write",
|
||||
"pattern": "**/*.rb",
|
||||
"excludePattern": "**/*_spec.rb",
|
||||
"action": "suggest-tests"
|
||||
},
|
||||
{
|
||||
"name": "code-smell-logger",
|
||||
"description": "Log detected code smells after file writes to aid refactoring tracking",
|
||||
"event": "post-write",
|
||||
"pattern": "**/*.rb",
|
||||
"action": "analyze-and-log-smells"
|
||||
},
|
||||
{
|
||||
"name": "ruby-test-on-save",
|
||||
"description": "Run related tests when Ruby files are saved",
|
||||
"event": "post-write",
|
||||
"pattern": "lib/**/*.rb",
|
||||
"action": "run-related-tests",
|
||||
"enabled": false
|
||||
}
|
||||
]
|
||||
}
|
||||
136
hooks/suggest-tests.md
Normal file
136
hooks/suggest-tests.md
Normal file
@@ -0,0 +1,136 @@
|
||||
# Suggest Tests Hook
|
||||
|
||||
This hook triggers after writing a new Ruby file and suggests relevant tests.
|
||||
|
||||
## Trigger
|
||||
|
||||
- Event: post-write
|
||||
- Pattern: **/*.rb (excluding *_spec.rb files)
|
||||
|
||||
## Action
|
||||
|
||||
When a new Ruby file is created or significantly modified, analyze the file and suggest appropriate tests.
|
||||
|
||||
## Analysis Process
|
||||
|
||||
1. **Read the File**
|
||||
- Determine the class/module name
|
||||
- Identify public methods
|
||||
- Note any dependencies or collaborators
|
||||
- Identify edge cases from code structure
|
||||
|
||||
2. **Check for Existing Tests**
|
||||
- Look for corresponding spec file (e.g., `lib/user.rb` → `spec/user_spec.rb`)
|
||||
- If exists, check coverage of new methods
|
||||
- If missing, offer to create spec file
|
||||
|
||||
3. **Generate Test Suggestions**
|
||||
|
||||
Based on the code structure, suggest tests for:
|
||||
|
||||
### Public Methods
|
||||
- Happy path (normal operation)
|
||||
- Edge cases (boundary conditions)
|
||||
- Error cases (invalid inputs)
|
||||
- Null cases (nil/empty values)
|
||||
|
||||
### Class Responsibilities
|
||||
- Single Responsibility compliance
|
||||
- Interface clarity
|
||||
- Dependency injection points
|
||||
|
||||
### Common Patterns
|
||||
- Data object serialization (to_h, to_json)
|
||||
- Factory methods (build, from_json, from_h)
|
||||
- Validation logic
|
||||
- Error handling
|
||||
- State management
|
||||
|
||||
## Response Format
|
||||
|
||||
**New File Detected: [file_path]**
|
||||
|
||||
I noticed you created/modified a Ruby file with:
|
||||
- Class: [ClassName]
|
||||
- Public methods: [method1, method2, method3]
|
||||
- Dependencies: [list]
|
||||
|
||||
**Test File Status:**
|
||||
- [ ] Spec file exists at: [spec_path]
|
||||
- [ ] No spec file found
|
||||
|
||||
**Suggested Tests:**
|
||||
|
||||
```ruby
|
||||
# spec/path/to/class_spec.rb
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe ClassName do
|
||||
describe '.build' do
|
||||
it 'creates instance from hash attributes' do
|
||||
# Test factory method
|
||||
end
|
||||
|
||||
context 'with missing attributes' do
|
||||
it 'handles gracefully' do
|
||||
# Test edge case
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#public_method' do
|
||||
let(:instance) { described_class.new(dependencies) }
|
||||
|
||||
it 'performs expected behavior' do
|
||||
# Happy path test
|
||||
end
|
||||
|
||||
context 'when error condition occurs' do
|
||||
it 'raises appropriate exception' do
|
||||
# Error handling test
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#to_h' do
|
||||
it 'serializes to hash with expected keys' do
|
||||
# Serialization test
|
||||
end
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
**Additional Considerations:**
|
||||
- [ ] Test dependency injection
|
||||
- [ ] Test error handling for [specific method]
|
||||
- [ ] Test edge case: [description]
|
||||
- [ ] Add integration test for [interaction]
|
||||
|
||||
Would you like me to:
|
||||
1. Create the spec file with these tests?
|
||||
2. Add tests for specific methods?
|
||||
3. Skip test suggestions for this file?
|
||||
|
||||
## Configuration
|
||||
|
||||
Users can disable this hook in `.claude/settings.json`:
|
||||
```json
|
||||
{
|
||||
"hooks": {
|
||||
"ruby-post-write": {
|
||||
"enabled": false
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Or configure per-project patterns to exclude:
|
||||
```json
|
||||
{
|
||||
"hooks": {
|
||||
"ruby-post-write": {
|
||||
"excludePattern": ["lib/generated/**", "lib/legacy/**"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
Reference in New Issue
Block a user