red64-cli 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/parseArgs.d.ts.map +1 -1
- package/dist/cli/parseArgs.js +5 -0
- package/dist/cli/parseArgs.js.map +1 -1
- package/dist/components/init/CompleteStep.d.ts.map +1 -1
- package/dist/components/init/CompleteStep.js +2 -2
- package/dist/components/init/CompleteStep.js.map +1 -1
- package/dist/components/init/TestCheckStep.d.ts +16 -0
- package/dist/components/init/TestCheckStep.d.ts.map +1 -0
- package/dist/components/init/TestCheckStep.js +120 -0
- package/dist/components/init/TestCheckStep.js.map +1 -0
- package/dist/components/init/index.d.ts +1 -0
- package/dist/components/init/index.d.ts.map +1 -1
- package/dist/components/init/index.js +1 -0
- package/dist/components/init/index.js.map +1 -1
- package/dist/components/init/types.d.ts +9 -0
- package/dist/components/init/types.d.ts.map +1 -1
- package/dist/components/screens/InitScreen.d.ts.map +1 -1
- package/dist/components/screens/InitScreen.js +69 -6
- package/dist/components/screens/InitScreen.js.map +1 -1
- package/dist/components/screens/StartScreen.d.ts.map +1 -1
- package/dist/components/screens/StartScreen.js +89 -3
- package/dist/components/screens/StartScreen.js.map +1 -1
- package/dist/services/ConfigService.d.ts +1 -0
- package/dist/services/ConfigService.d.ts.map +1 -1
- package/dist/services/ConfigService.js.map +1 -1
- package/dist/services/ProjectDetector.d.ts +28 -0
- package/dist/services/ProjectDetector.d.ts.map +1 -0
- package/dist/services/ProjectDetector.js +236 -0
- package/dist/services/ProjectDetector.js.map +1 -0
- package/dist/services/TestRunner.d.ts +46 -0
- package/dist/services/TestRunner.d.ts.map +1 -0
- package/dist/services/TestRunner.js +85 -0
- package/dist/services/TestRunner.js.map +1 -0
- package/dist/services/index.d.ts +2 -0
- package/dist/services/index.d.ts.map +1 -1
- package/dist/services/index.js +2 -0
- package/dist/services/index.js.map +1 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js.map +1 -1
- package/framework/agents/claude/.claude/agents/red64/spec-impl.md +131 -2
- package/framework/agents/claude/.claude/commands/red64/spec-impl.md +24 -0
- package/framework/agents/codex/.codex/agents/red64/spec-impl.md +131 -2
- package/framework/agents/codex/.codex/commands/red64/spec-impl.md +24 -0
- package/framework/stacks/generic/feedback.md +80 -0
- package/framework/stacks/nextjs/accessibility.md +437 -0
- package/framework/stacks/nextjs/api.md +431 -0
- package/framework/stacks/nextjs/coding-style.md +282 -0
- package/framework/stacks/nextjs/commenting.md +226 -0
- package/framework/stacks/nextjs/components.md +411 -0
- package/framework/stacks/nextjs/conventions.md +333 -0
- package/framework/stacks/nextjs/css.md +310 -0
- package/framework/stacks/nextjs/error-handling.md +442 -0
- package/framework/stacks/nextjs/feedback.md +124 -0
- package/framework/stacks/nextjs/migrations.md +332 -0
- package/framework/stacks/nextjs/models.md +362 -0
- package/framework/stacks/nextjs/queries.md +410 -0
- package/framework/stacks/nextjs/responsive.md +338 -0
- package/framework/stacks/nextjs/tech-stack.md +177 -0
- package/framework/stacks/nextjs/test-writing.md +475 -0
- package/framework/stacks/nextjs/validation.md +467 -0
- package/framework/stacks/python/api.md +468 -0
- package/framework/stacks/python/authentication.md +342 -0
- package/framework/stacks/python/code-quality.md +283 -0
- package/framework/stacks/python/code-refactoring.md +315 -0
- package/framework/stacks/python/coding-style.md +462 -0
- package/framework/stacks/python/conventions.md +399 -0
- package/framework/stacks/python/error-handling.md +512 -0
- package/framework/stacks/python/feedback.md +92 -0
- package/framework/stacks/python/implement-ai-llm.md +468 -0
- package/framework/stacks/python/migrations.md +388 -0
- package/framework/stacks/python/models.md +399 -0
- package/framework/stacks/python/python.md +232 -0
- package/framework/stacks/python/queries.md +451 -0
- package/framework/stacks/python/structure.md +245 -58
- package/framework/stacks/python/tech.md +92 -35
- package/framework/stacks/python/testing.md +380 -0
- package/framework/stacks/python/validation.md +471 -0
- package/framework/stacks/rails/authentication.md +176 -0
- package/framework/stacks/rails/code-quality.md +287 -0
- package/framework/stacks/rails/code-refactoring.md +299 -0
- package/framework/stacks/rails/feedback.md +130 -0
- package/framework/stacks/rails/implement-ai-llm-with-rubyllm.md +342 -0
- package/framework/stacks/rails/rails.md +301 -0
- package/framework/stacks/rails/rails8-best-practices.md +498 -0
- package/framework/stacks/rails/rails8-css.md +573 -0
- package/framework/stacks/rails/structure.md +140 -0
- package/framework/stacks/rails/tech.md +108 -0
- package/framework/stacks/react/code-quality.md +521 -0
- package/framework/stacks/react/components.md +625 -0
- package/framework/stacks/react/data-fetching.md +586 -0
- package/framework/stacks/react/feedback.md +110 -0
- package/framework/stacks/react/forms.md +694 -0
- package/framework/stacks/react/performance.md +640 -0
- package/framework/stacks/react/product.md +22 -9
- package/framework/stacks/react/state-management.md +472 -0
- package/framework/stacks/react/structure.md +351 -44
- package/framework/stacks/react/tech.md +219 -30
- package/framework/stacks/react/testing.md +690 -0
- package/package.json +1 -1
- package/framework/stacks/node/product.md +0 -27
- package/framework/stacks/node/structure.md +0 -82
- package/framework/stacks/node/tech.md +0 -63
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
# Code Quality Standards
|
|
2
|
+
|
|
3
|
+
Project memory for code quality conventions, testing patterns, and maintainability standards in MediaPulse.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Linting and Style
|
|
8
|
+
|
|
9
|
+
### RuboCop Configuration
|
|
10
|
+
- **Style**: `rubocop-rails-omakase` - Rails default conventions
|
|
11
|
+
- Configuration: `.rubocop.yml` inherits from gem
|
|
12
|
+
- Run: `bundle exec rubocop`
|
|
13
|
+
|
|
14
|
+
```ruby
|
|
15
|
+
# .rubocop.yml pattern: inherit defaults, override sparingly
|
|
16
|
+
inherit_gem: { rubocop-rails-omakase: rubocop.yml }
|
|
17
|
+
|
|
18
|
+
# Only add project-specific overrides when necessary
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
### Naming Conventions
|
|
22
|
+
|
|
23
|
+
| Type | Pattern | Example |
|
|
24
|
+
|------|---------|---------|
|
|
25
|
+
| Models | Singular, CamelCase | `Source`, `CollectedContent` |
|
|
26
|
+
| Controllers | Plural + Controller | `SourcesController` |
|
|
27
|
+
| Services | Action-oriented | `ContentCollectionService` |
|
|
28
|
+
| Jobs | Descriptive + Job | `CollectContentJob` |
|
|
29
|
+
| Tests | Subject + Test | `SourceTest` |
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Testing Patterns
|
|
34
|
+
|
|
35
|
+
### Framework
|
|
36
|
+
- **Minitest** (Rails default) with Capybara for system tests
|
|
37
|
+
- **WebMock** for HTTP stubbing
|
|
38
|
+
- Parallel execution: `parallelize(workers: :number_of_processors)`
|
|
39
|
+
- Fixtures: `test/fixtures/*.yml`
|
|
40
|
+
|
|
41
|
+
### Test Organization
|
|
42
|
+
Tests organized by type in `test/` directory:
|
|
43
|
+
- `test/models/` - Unit tests for ActiveRecord models
|
|
44
|
+
- `test/controllers/` - Controller action tests
|
|
45
|
+
- `test/services/` - Service object tests
|
|
46
|
+
- `test/integration/` - Multi-component workflow tests
|
|
47
|
+
- `test/system/` - Full browser tests (Capybara/Selenium)
|
|
48
|
+
- `test/helpers/` - View helper tests
|
|
49
|
+
- `test/views/` - View rendering tests
|
|
50
|
+
- `test/jobs/` - Background job tests
|
|
51
|
+
|
|
52
|
+
### Test Style Pattern
|
|
53
|
+
```ruby
|
|
54
|
+
# Pattern: Descriptive test blocks with setup and clear assertions
|
|
55
|
+
class SourceTest < ActiveSupport::TestCase
|
|
56
|
+
setup do
|
|
57
|
+
# Stub external dependencies
|
|
58
|
+
stub_request(:head, /example\.com/).to_return(status: 200)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Test naming: describe behavior, not implementation
|
|
62
|
+
test "validates url presence" do
|
|
63
|
+
project = Project.create!(name: "Test", user: users(:one))
|
|
64
|
+
source = Source.new(project: project)
|
|
65
|
+
|
|
66
|
+
assert_not source.valid?
|
|
67
|
+
assert_includes source.errors[:url], "can't be blank"
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Pattern: Test behavior scenarios, not implementation details
|
|
71
|
+
test "detects youtube.com as youtube type" do
|
|
72
|
+
project = Project.create!(name: "Test", user: users(:one))
|
|
73
|
+
source = Source.create!(url: "https://www.youtube.com/watch?v=abc123", project: project)
|
|
74
|
+
|
|
75
|
+
assert source.youtube?
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Service Testing Pattern
|
|
81
|
+
```ruby
|
|
82
|
+
# Pattern: Inject dependencies for testability
|
|
83
|
+
class ContentCollectionServiceTest < ActiveSupport::TestCase
|
|
84
|
+
test "returns error when source is already collecting" do
|
|
85
|
+
source = sources(:one)
|
|
86
|
+
source.update!(status: :collecting)
|
|
87
|
+
|
|
88
|
+
service = ContentCollectionService.new(source: source)
|
|
89
|
+
result = service.call
|
|
90
|
+
|
|
91
|
+
assert result.error?
|
|
92
|
+
assert_equal :already_collecting, result.error_type
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Pattern: Use dependency injection for external services
|
|
96
|
+
test "enqueues collection job on success" do
|
|
97
|
+
job_enqueued = false
|
|
98
|
+
mock_enqueuer = ->(id) { job_enqueued = true; OpenStruct.new(job_id: "test-123") }
|
|
99
|
+
|
|
100
|
+
service = ContentCollectionService.new(
|
|
101
|
+
source: source,
|
|
102
|
+
skip_credential_check: true,
|
|
103
|
+
job_enqueuer: mock_enqueuer
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
result = service.call
|
|
107
|
+
assert result.success?
|
|
108
|
+
assert job_enqueued
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## Code Organization
|
|
116
|
+
|
|
117
|
+
### Service Objects
|
|
118
|
+
Place in `app/services/` when logic:
|
|
119
|
+
- Spans multiple models
|
|
120
|
+
- Involves external APIs
|
|
121
|
+
- Has complex business rules
|
|
122
|
+
- Needs extensive testing in isolation
|
|
123
|
+
|
|
124
|
+
```ruby
|
|
125
|
+
# Pattern: Result objects for service outcomes
|
|
126
|
+
class ContentCollectionService
|
|
127
|
+
class Result
|
|
128
|
+
attr_reader :job_id, :error_type, :error_message
|
|
129
|
+
|
|
130
|
+
def initialize(success:, job_id: nil, error_type: nil, error_message: nil)
|
|
131
|
+
@success = success
|
|
132
|
+
@job_id = job_id
|
|
133
|
+
@error_type = error_type
|
|
134
|
+
@error_message = error_message
|
|
135
|
+
freeze
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def success?
|
|
139
|
+
@success
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def error?
|
|
143
|
+
!@success
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# Factory methods for clarity
|
|
147
|
+
def self.success(job_id:)
|
|
148
|
+
new(success: true, job_id: job_id)
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def self.error(type:, message:)
|
|
152
|
+
new(success: false, error_type: type, error_message: message)
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### Error Handling Pattern
|
|
159
|
+
```ruby
|
|
160
|
+
# Pattern: Custom error hierarchy for services
|
|
161
|
+
class LlmService
|
|
162
|
+
class Error < StandardError; end
|
|
163
|
+
class ConfigurationError < Error; end
|
|
164
|
+
class AuthenticationError < Error; end
|
|
165
|
+
class RateLimitError < Error; end
|
|
166
|
+
class ApiError < Error; end
|
|
167
|
+
|
|
168
|
+
# Map external errors to domain errors
|
|
169
|
+
def generate_ideas(prompt:, system_prompt: nil, model: DEFAULT_MODEL)
|
|
170
|
+
chat = build_chat(model: model, system_prompt: system_prompt)
|
|
171
|
+
response = chat.ask(prompt)
|
|
172
|
+
build_response(response)
|
|
173
|
+
rescue RubyLLM::UnauthorizedError => e
|
|
174
|
+
raise AuthenticationError, e.message
|
|
175
|
+
rescue RubyLLM::RateLimitError => e
|
|
176
|
+
raise RateLimitError, e.message
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### Model Patterns
|
|
182
|
+
```ruby
|
|
183
|
+
# Pattern: Lean models with clear responsibilities
|
|
184
|
+
class Source < ApplicationRecord
|
|
185
|
+
# 1. Includes
|
|
186
|
+
include Turbo::Broadcastable
|
|
187
|
+
|
|
188
|
+
# 2. Associations
|
|
189
|
+
belongs_to :project
|
|
190
|
+
belongs_to :feed, optional: true
|
|
191
|
+
has_many :ideas, through: :idea_sources
|
|
192
|
+
|
|
193
|
+
# 3. Enums
|
|
194
|
+
enum :source_type, { web: 0, youtube: 1, substack: 2 }
|
|
195
|
+
enum :status, { pending: 0, collecting: 1, collected: 2, failed: 3 }
|
|
196
|
+
|
|
197
|
+
# 4. Validations
|
|
198
|
+
validates :url, presence: true, format: { with: URI::DEFAULT_PARSER.make_regexp(%w[http https]) }
|
|
199
|
+
validates :url, uniqueness: { scope: [:project_id, :feed_id], case_sensitive: false }
|
|
200
|
+
|
|
201
|
+
# 5. Scopes
|
|
202
|
+
scope :from_feed, ->(feed) { where(feed: feed) }
|
|
203
|
+
scope :manually_added, -> { where(feed: nil) }
|
|
204
|
+
scope :search, ->(query) { where("url LIKE ? OR title LIKE ?", "%#{query}%", "%#{query}%") }
|
|
205
|
+
|
|
206
|
+
# 6. Callbacks (use sparingly)
|
|
207
|
+
before_validation :detect_source_type, on: :create
|
|
208
|
+
|
|
209
|
+
# 7. Public instance methods
|
|
210
|
+
def has_full_metadata?
|
|
211
|
+
title.present? && description.present? && published_at.present?
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
private
|
|
215
|
+
|
|
216
|
+
# 8. Private methods
|
|
217
|
+
def detect_source_type
|
|
218
|
+
# implementation
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
---
|
|
224
|
+
|
|
225
|
+
## Security Analysis Tools
|
|
226
|
+
|
|
227
|
+
### Static Analysis
|
|
228
|
+
- **Brakeman**: Security vulnerability scanner
|
|
229
|
+
- Run: `bundle exec brakeman`
|
|
230
|
+
- Check before deployment for SQL injection, XSS, CSRF issues
|
|
231
|
+
|
|
232
|
+
- **bundler-audit**: Gem vulnerability scanning
|
|
233
|
+
- Configuration: `config/bundler-audit.yml` for ignoring known issues
|
|
234
|
+
- Run: `bundle audit`
|
|
235
|
+
|
|
236
|
+
### Credential Management
|
|
237
|
+
- Use Rails credentials: `bin/rails credentials:edit`
|
|
238
|
+
- Access: `Rails.application.credentials.dig(:service, :api_key)`
|
|
239
|
+
- Never commit unencrypted secrets
|
|
240
|
+
- Filter sensitive params (see `config/initializers/filter_parameter_logging.rb`)
|
|
241
|
+
|
|
242
|
+
---
|
|
243
|
+
|
|
244
|
+
## Documentation Standards
|
|
245
|
+
|
|
246
|
+
### Method Documentation
|
|
247
|
+
```ruby
|
|
248
|
+
# Pattern: Document public interfaces
|
|
249
|
+
# Unified service wrapping ruby_llm gem for multi-provider LLM access
|
|
250
|
+
#
|
|
251
|
+
# Usage:
|
|
252
|
+
# response = LlmService.generate_ideas(prompt: "...", system_prompt: "...")
|
|
253
|
+
# response.content #=> "Generated ideas..."
|
|
254
|
+
#
|
|
255
|
+
class LlmService
|
|
256
|
+
# Generate ideas from source content
|
|
257
|
+
#
|
|
258
|
+
# @param prompt [String] Formatted prompt with source content
|
|
259
|
+
# @param system_prompt [String, nil] System instructions
|
|
260
|
+
# @param model [String] Model identifier (default: claude-sonnet-4-20250514)
|
|
261
|
+
# @return [LlmResponse] Response with content and metadata
|
|
262
|
+
# @raise [ConfigurationError] if API key not configured
|
|
263
|
+
def generate_ideas(prompt:, system_prompt: nil, model: DEFAULT_MODEL)
|
|
264
|
+
# implementation
|
|
265
|
+
end
|
|
266
|
+
end
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
---
|
|
270
|
+
|
|
271
|
+
## Quality Commands
|
|
272
|
+
|
|
273
|
+
```bash
|
|
274
|
+
# Run all quality checks
|
|
275
|
+
bundle exec rubocop # Style
|
|
276
|
+
bundle exec brakeman # Security
|
|
277
|
+
bundle exec rake test # Tests
|
|
278
|
+
bundle exec rake test:system # System tests
|
|
279
|
+
|
|
280
|
+
# Development workflow
|
|
281
|
+
bin/rails test # Fast feedback
|
|
282
|
+
bin/rails test test/models/ # Focused testing
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
---
|
|
286
|
+
|
|
287
|
+
_Focus on patterns over exhaustive rules. Code should be readable, testable, and secure._
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
# Code Refactoring Patterns
|
|
2
|
+
|
|
3
|
+
Project memory for safe code refactoring strategies in MediaPulse.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Refactoring Philosophy
|
|
8
|
+
|
|
9
|
+
Refactoring in MediaPulse follows three core principles:
|
|
10
|
+
1. **Safety first**: Tests pass before and after every change
|
|
11
|
+
2. **Small steps**: Incremental changes that can be reverted independently
|
|
12
|
+
3. **Preserve behavior**: Refactoring changes structure, not functionality
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Extraction Patterns
|
|
17
|
+
|
|
18
|
+
### Service Extraction
|
|
19
|
+
|
|
20
|
+
Extract business logic from controllers/models when it:
|
|
21
|
+
- Spans multiple models
|
|
22
|
+
- Involves external APIs
|
|
23
|
+
- Has complex business rules
|
|
24
|
+
- Needs extensive testing
|
|
25
|
+
|
|
26
|
+
**Pattern**: Controller to Service
|
|
27
|
+
```ruby
|
|
28
|
+
# Before: Fat controller action
|
|
29
|
+
class CollectionsController < ApplicationController
|
|
30
|
+
def create
|
|
31
|
+
source = @project.sources.find(params[:source_id])
|
|
32
|
+
return render_error if source.collecting?
|
|
33
|
+
|
|
34
|
+
credential = Rails.application.credentials.dig(:service, :api_key)
|
|
35
|
+
return render_error("Missing credentials") if credential.blank?
|
|
36
|
+
|
|
37
|
+
source.update!(status: :collecting)
|
|
38
|
+
CollectContentJob.perform_later(source.id)
|
|
39
|
+
# ...
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# After: Thin controller, extracted service
|
|
44
|
+
class CollectionsController < ApplicationController
|
|
45
|
+
def create
|
|
46
|
+
result = ContentCollectionService.new(source: @source).call
|
|
47
|
+
|
|
48
|
+
if result.success?
|
|
49
|
+
redirect_to @source, notice: "Collection started"
|
|
50
|
+
else
|
|
51
|
+
redirect_to @source, alert: result.error_message
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
**Key elements**:
|
|
58
|
+
- Service returns Result object (success/error pattern)
|
|
59
|
+
- Controller only handles HTTP concerns
|
|
60
|
+
- Business logic fully testable in isolation
|
|
61
|
+
|
|
62
|
+
### Concern Extraction
|
|
63
|
+
|
|
64
|
+
Extract shared model behavior into concerns when:
|
|
65
|
+
- Same logic appears in 3+ models
|
|
66
|
+
- Behavior is cohesive and nameable
|
|
67
|
+
- Logic is self-contained
|
|
68
|
+
|
|
69
|
+
**Pattern**: Model to Concern
|
|
70
|
+
```ruby
|
|
71
|
+
# Before: Duplicated broadcast logic in models
|
|
72
|
+
class Source < ApplicationRecord
|
|
73
|
+
after_update_commit :broadcast_status_change, if: :saved_change_to_status?
|
|
74
|
+
|
|
75
|
+
def dom_target
|
|
76
|
+
"source_#{id}"
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def broadcast_status_change
|
|
80
|
+
broadcast_replace_later_to(project, target: dom_target, ...)
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# After: Extracted concern
|
|
85
|
+
# app/models/concerns/turbo_broadcastable.rb
|
|
86
|
+
module TurboBroadcastable
|
|
87
|
+
extend ActiveSupport::Concern
|
|
88
|
+
|
|
89
|
+
included do
|
|
90
|
+
include Turbo::Broadcastable
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def dom_target
|
|
94
|
+
"#{model_name.singular}_#{id}"
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Strategy Pattern Extraction
|
|
100
|
+
|
|
101
|
+
Use when type-based conditionals grow complex:
|
|
102
|
+
|
|
103
|
+
```ruby
|
|
104
|
+
# Before: Growing case statement
|
|
105
|
+
def process_content(source)
|
|
106
|
+
case source.source_type
|
|
107
|
+
when :youtube then # 50 lines
|
|
108
|
+
when :web then # 40 lines
|
|
109
|
+
when :twitter then # 45 lines
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# After: Strategy pattern via registry
|
|
114
|
+
# ExtractorRegistry.for(:youtube) => YoutubeExtractor
|
|
115
|
+
result = ExtractorRegistry.extract(source)
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
**Established patterns**:
|
|
119
|
+
- `ExtractorRegistry` maps source types to extractors
|
|
120
|
+
- `FeedTypeStrategy` provides type-specific configurations
|
|
121
|
+
- New types require only adding entry to registry + implementing class
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## Safe Refactoring Workflow
|
|
126
|
+
|
|
127
|
+
### Step 1: Ensure Test Coverage
|
|
128
|
+
|
|
129
|
+
Before refactoring, verify tests exist:
|
|
130
|
+
```bash
|
|
131
|
+
# Check existing coverage
|
|
132
|
+
bin/rails test test/models/source_test.rb
|
|
133
|
+
bin/rails test test/services/
|
|
134
|
+
|
|
135
|
+
# Add characterization tests if missing
|
|
136
|
+
test "current behavior for X" do
|
|
137
|
+
# Document existing behavior before changing
|
|
138
|
+
end
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Step 2: Make Small, Reversible Changes
|
|
142
|
+
|
|
143
|
+
**Good**: One concept per commit
|
|
144
|
+
```
|
|
145
|
+
commit 1: Extract Result class from service
|
|
146
|
+
commit 2: Move validation logic to private method
|
|
147
|
+
commit 3: Rename extracted method for clarity
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
**Avoid**: Large refactors that touch many files
|
|
151
|
+
|
|
152
|
+
### Step 3: Verify After Each Change
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
# Run related tests
|
|
156
|
+
bin/rails test test/services/content_collection_service_test.rb
|
|
157
|
+
|
|
158
|
+
# Run full suite before pushing
|
|
159
|
+
bin/rails test
|
|
160
|
+
bundle exec rubocop
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
165
|
+
## Dependency Injection for Testability
|
|
166
|
+
|
|
167
|
+
Services use dependency injection for external calls:
|
|
168
|
+
|
|
169
|
+
```ruby
|
|
170
|
+
class ContentCollectionService
|
|
171
|
+
def initialize(source:, credentials_checker: nil, job_enqueuer: nil)
|
|
172
|
+
@source = source
|
|
173
|
+
@credentials_checker = credentials_checker || method(:default_credentials_checker)
|
|
174
|
+
@job_enqueuer = job_enqueuer || method(:default_job_enqueuer)
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# In tests: inject mocks
|
|
179
|
+
service = ContentCollectionService.new(
|
|
180
|
+
source: source,
|
|
181
|
+
job_enqueuer: ->(id) { OpenStruct.new(job_id: "test-123") }
|
|
182
|
+
)
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
**Pattern**: Default to production behavior, override in tests.
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
## Refactoring Signals
|
|
190
|
+
|
|
191
|
+
### When to Extract
|
|
192
|
+
|
|
193
|
+
| Signal | Action |
|
|
194
|
+
|--------|--------|
|
|
195
|
+
| Controller action > 15 lines | Extract to service |
|
|
196
|
+
| Model > 150 lines | Consider concerns or query objects |
|
|
197
|
+
| Case statement > 3 branches | Consider strategy pattern |
|
|
198
|
+
| Same 5+ lines in multiple places | Extract to shared method/concern |
|
|
199
|
+
| External API call in model | Move to client class |
|
|
200
|
+
|
|
201
|
+
### When NOT to Extract
|
|
202
|
+
|
|
203
|
+
- Single-use logic that fits naturally in one place
|
|
204
|
+
- Simple CRUD without business rules
|
|
205
|
+
- Premature abstraction (wait for 3+ uses)
|
|
206
|
+
|
|
207
|
+
---
|
|
208
|
+
|
|
209
|
+
## Backward Compatibility
|
|
210
|
+
|
|
211
|
+
### API Deprecation Pattern
|
|
212
|
+
|
|
213
|
+
When changing public interfaces:
|
|
214
|
+
|
|
215
|
+
```ruby
|
|
216
|
+
# Step 1: Add new method, keep old
|
|
217
|
+
def self.config_for(feed_type, custom_rules: nil)
|
|
218
|
+
# new implementation
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
# @deprecated Use config_for instead
|
|
222
|
+
def self.prompt_for(feed_type, custom_rules: nil)
|
|
223
|
+
config_for(feed_type, custom_rules: custom_rules).prompt
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
# Step 2: Add deprecation warning (future release)
|
|
227
|
+
# Step 3: Remove old method (after deprecation period)
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
### Database Migration Safety
|
|
231
|
+
|
|
232
|
+
For schema changes that affect running code:
|
|
233
|
+
|
|
234
|
+
```ruby
|
|
235
|
+
# Safe: Add column with default
|
|
236
|
+
add_column :sources, :priority, :integer, default: 0
|
|
237
|
+
|
|
238
|
+
# Safe: Add index concurrently (PostgreSQL)
|
|
239
|
+
# For SQLite: migrations run quickly, less concern
|
|
240
|
+
|
|
241
|
+
# Risky: Rename column
|
|
242
|
+
# Use two-step: add new, migrate data, remove old
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
---
|
|
246
|
+
|
|
247
|
+
## Common Refactoring Targets
|
|
248
|
+
|
|
249
|
+
### Fat Models
|
|
250
|
+
|
|
251
|
+
Move to services when models handle:
|
|
252
|
+
- Complex state transitions
|
|
253
|
+
- Multi-model coordination
|
|
254
|
+
- External service calls
|
|
255
|
+
|
|
256
|
+
Keep in models:
|
|
257
|
+
- Validations and associations
|
|
258
|
+
- Simple scopes
|
|
259
|
+
- Attribute helpers
|
|
260
|
+
|
|
261
|
+
### Complex Conditionals
|
|
262
|
+
|
|
263
|
+
Replace nested conditionals with:
|
|
264
|
+
- Guard clauses (early returns)
|
|
265
|
+
- Strategy/registry patterns
|
|
266
|
+
- Polymorphism where appropriate
|
|
267
|
+
|
|
268
|
+
### Test Duplication
|
|
269
|
+
|
|
270
|
+
Extract to test helpers:
|
|
271
|
+
```ruby
|
|
272
|
+
# test/test_helper.rb
|
|
273
|
+
def stub_external_services
|
|
274
|
+
stub_request(:head, /example\.com/).to_return(status: 200)
|
|
275
|
+
end
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
---
|
|
279
|
+
|
|
280
|
+
## Refactoring Checklist
|
|
281
|
+
|
|
282
|
+
Before starting:
|
|
283
|
+
- [ ] Tests pass for affected code
|
|
284
|
+
- [ ] Understand current behavior fully
|
|
285
|
+
- [ ] Plan incremental steps
|
|
286
|
+
|
|
287
|
+
During refactoring:
|
|
288
|
+
- [ ] One logical change per commit
|
|
289
|
+
- [ ] Tests pass after each step
|
|
290
|
+
- [ ] No behavior changes (unless intentional)
|
|
291
|
+
|
|
292
|
+
After completing:
|
|
293
|
+
- [ ] Full test suite passes
|
|
294
|
+
- [ ] RuboCop passes
|
|
295
|
+
- [ ] Manual smoke test of affected features
|
|
296
|
+
|
|
297
|
+
---
|
|
298
|
+
|
|
299
|
+
_Refactoring improves code structure while preserving behavior. Small, tested steps over large rewrites._
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# Feedback Configuration
|
|
2
|
+
|
|
3
|
+
Project-specific commands for automated feedback during Rails implementation.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Test Commands
|
|
8
|
+
|
|
9
|
+
Commands to run tests during implementation. The agent will use these to verify code changes.
|
|
10
|
+
|
|
11
|
+
```yaml
|
|
12
|
+
# Primary test command (REQUIRED)
|
|
13
|
+
test: bin/rails test
|
|
14
|
+
|
|
15
|
+
# Test with verbose output
|
|
16
|
+
test_verbose: bin/rails test -v
|
|
17
|
+
|
|
18
|
+
# Run specific test file (use {file} as placeholder)
|
|
19
|
+
test_file: bin/rails test {file}
|
|
20
|
+
|
|
21
|
+
# Run system tests (browser-based)
|
|
22
|
+
test_system: bin/rails test:system
|
|
23
|
+
|
|
24
|
+
# Run all tests including system
|
|
25
|
+
test_all: bin/rails test:all
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Linting Commands
|
|
31
|
+
|
|
32
|
+
Commands for code quality checks.
|
|
33
|
+
|
|
34
|
+
```yaml
|
|
35
|
+
# Primary lint command (RuboCop)
|
|
36
|
+
lint: bundle exec rubocop
|
|
37
|
+
|
|
38
|
+
# Lint with auto-fix
|
|
39
|
+
lint_fix: bundle exec rubocop -A
|
|
40
|
+
|
|
41
|
+
# Security check (Brakeman)
|
|
42
|
+
security: bundle exec brakeman -q
|
|
43
|
+
|
|
44
|
+
# Bundle audit for dependencies
|
|
45
|
+
bundle_audit: bundle exec bundle-audit check --update
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## Development Server
|
|
51
|
+
|
|
52
|
+
Commands for starting the development server (required for UI verification).
|
|
53
|
+
|
|
54
|
+
```yaml
|
|
55
|
+
# Start dev server (Rails)
|
|
56
|
+
dev_server: bin/rails server
|
|
57
|
+
|
|
58
|
+
# Dev server port
|
|
59
|
+
dev_port: 3000
|
|
60
|
+
|
|
61
|
+
# Dev server base URL
|
|
62
|
+
dev_url: http://localhost:3000
|
|
63
|
+
|
|
64
|
+
# Start with binding (for Docker)
|
|
65
|
+
dev_server_docker: bin/rails server -b 0.0.0.0
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## Database Commands
|
|
71
|
+
|
|
72
|
+
Commands for database operations.
|
|
73
|
+
|
|
74
|
+
```yaml
|
|
75
|
+
# Run migrations
|
|
76
|
+
db_migrate: bin/rails db:migrate
|
|
77
|
+
|
|
78
|
+
# Setup test database
|
|
79
|
+
db_test_prepare: bin/rails db:test:prepare
|
|
80
|
+
|
|
81
|
+
# Reset database
|
|
82
|
+
db_reset: bin/rails db:reset
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## UI Verification
|
|
88
|
+
|
|
89
|
+
Settings for agent-browser UI verification.
|
|
90
|
+
|
|
91
|
+
```yaml
|
|
92
|
+
# Enable UI verification for this project
|
|
93
|
+
ui_verification_enabled: true
|
|
94
|
+
|
|
95
|
+
# Default wait time after navigation (milliseconds)
|
|
96
|
+
navigation_wait: 3000
|
|
97
|
+
|
|
98
|
+
# Screenshot directory
|
|
99
|
+
screenshot_dir: /tmp/ui-captures
|
|
100
|
+
|
|
101
|
+
# Common routes to verify
|
|
102
|
+
routes:
|
|
103
|
+
- /
|
|
104
|
+
- /health
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
## Asset Pipeline
|
|
110
|
+
|
|
111
|
+
Commands for asset management.
|
|
112
|
+
|
|
113
|
+
```yaml
|
|
114
|
+
# Precompile assets
|
|
115
|
+
assets_precompile: bin/rails assets:precompile
|
|
116
|
+
|
|
117
|
+
# Clean assets
|
|
118
|
+
assets_clean: bin/rails assets:clean
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
## Notes
|
|
124
|
+
|
|
125
|
+
- Uses Minitest by default (or RSpec if configured)
|
|
126
|
+
- RuboCop for Ruby style enforcement
|
|
127
|
+
- Brakeman for security scanning
|
|
128
|
+
- Rails dev server on port 3000 by default
|
|
129
|
+
- System tests use Capybara with Selenium
|
|
130
|
+
- For Hotwire/Turbo projects, UI verification is essential
|