rubyn-code 0.1.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.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/README.md +620 -0
- data/db/migrations/000_create_schema_migrations.sql +4 -0
- data/db/migrations/001_create_sessions.sql +16 -0
- data/db/migrations/002_create_messages.sql +16 -0
- data/db/migrations/003_create_tasks.sql +17 -0
- data/db/migrations/004_create_task_dependencies.sql +8 -0
- data/db/migrations/005_create_memories.sql +44 -0
- data/db/migrations/006_create_cost_records.sql +16 -0
- data/db/migrations/007_create_hooks.sql +12 -0
- data/db/migrations/008_create_skills_cache.sql +8 -0
- data/db/migrations/009_create_teams.sql +27 -0
- data/db/migrations/010_create_instincts.sql +15 -0
- data/exe/rubyn-code +6 -0
- data/lib/rubyn_code/agent/conversation.rb +193 -0
- data/lib/rubyn_code/agent/loop.rb +517 -0
- data/lib/rubyn_code/agent/loop_detector.rb +78 -0
- data/lib/rubyn_code/auth/oauth.rb +174 -0
- data/lib/rubyn_code/auth/server.rb +126 -0
- data/lib/rubyn_code/auth/token_store.rb +153 -0
- data/lib/rubyn_code/autonomous/daemon.rb +233 -0
- data/lib/rubyn_code/autonomous/idle_poller.rb +111 -0
- data/lib/rubyn_code/autonomous/task_claimer.rb +100 -0
- data/lib/rubyn_code/background/job.rb +19 -0
- data/lib/rubyn_code/background/notifier.rb +44 -0
- data/lib/rubyn_code/background/worker.rb +146 -0
- data/lib/rubyn_code/cli/app.rb +118 -0
- data/lib/rubyn_code/cli/input_handler.rb +79 -0
- data/lib/rubyn_code/cli/renderer.rb +205 -0
- data/lib/rubyn_code/cli/repl.rb +519 -0
- data/lib/rubyn_code/cli/spinner.rb +100 -0
- data/lib/rubyn_code/cli/stream_formatter.rb +149 -0
- data/lib/rubyn_code/config/defaults.rb +43 -0
- data/lib/rubyn_code/config/project_config.rb +120 -0
- data/lib/rubyn_code/config/settings.rb +127 -0
- data/lib/rubyn_code/context/auto_compact.rb +81 -0
- data/lib/rubyn_code/context/compactor.rb +89 -0
- data/lib/rubyn_code/context/manager.rb +91 -0
- data/lib/rubyn_code/context/manual_compact.rb +87 -0
- data/lib/rubyn_code/context/micro_compact.rb +135 -0
- data/lib/rubyn_code/db/connection.rb +176 -0
- data/lib/rubyn_code/db/migrator.rb +146 -0
- data/lib/rubyn_code/db/schema.rb +106 -0
- data/lib/rubyn_code/hooks/built_in.rb +124 -0
- data/lib/rubyn_code/hooks/registry.rb +99 -0
- data/lib/rubyn_code/hooks/runner.rb +88 -0
- data/lib/rubyn_code/hooks/user_hooks.rb +90 -0
- data/lib/rubyn_code/learning/extractor.rb +191 -0
- data/lib/rubyn_code/learning/injector.rb +138 -0
- data/lib/rubyn_code/learning/instinct.rb +172 -0
- data/lib/rubyn_code/llm/client.rb +218 -0
- data/lib/rubyn_code/llm/message_builder.rb +116 -0
- data/lib/rubyn_code/llm/streaming.rb +203 -0
- data/lib/rubyn_code/mcp/client.rb +139 -0
- data/lib/rubyn_code/mcp/config.rb +83 -0
- data/lib/rubyn_code/mcp/sse_transport.rb +225 -0
- data/lib/rubyn_code/mcp/stdio_transport.rb +196 -0
- data/lib/rubyn_code/mcp/tool_bridge.rb +164 -0
- data/lib/rubyn_code/memory/models.rb +62 -0
- data/lib/rubyn_code/memory/search.rb +181 -0
- data/lib/rubyn_code/memory/session_persistence.rb +194 -0
- data/lib/rubyn_code/memory/store.rb +199 -0
- data/lib/rubyn_code/observability/budget_enforcer.rb +159 -0
- data/lib/rubyn_code/observability/cost_calculator.rb +61 -0
- data/lib/rubyn_code/observability/models.rb +29 -0
- data/lib/rubyn_code/observability/token_counter.rb +42 -0
- data/lib/rubyn_code/observability/usage_reporter.rb +140 -0
- data/lib/rubyn_code/output/diff_renderer.rb +212 -0
- data/lib/rubyn_code/output/formatter.rb +120 -0
- data/lib/rubyn_code/permissions/deny_list.rb +49 -0
- data/lib/rubyn_code/permissions/policy.rb +59 -0
- data/lib/rubyn_code/permissions/prompter.rb +80 -0
- data/lib/rubyn_code/permissions/tier.rb +22 -0
- data/lib/rubyn_code/protocols/interrupt_handler.rb +95 -0
- data/lib/rubyn_code/protocols/plan_approval.rb +67 -0
- data/lib/rubyn_code/protocols/shutdown_handshake.rb +109 -0
- data/lib/rubyn_code/skills/catalog.rb +70 -0
- data/lib/rubyn_code/skills/document.rb +80 -0
- data/lib/rubyn_code/skills/loader.rb +57 -0
- data/lib/rubyn_code/sub_agents/runner.rb +168 -0
- data/lib/rubyn_code/sub_agents/summarizer.rb +57 -0
- data/lib/rubyn_code/tasks/dag.rb +208 -0
- data/lib/rubyn_code/tasks/manager.rb +212 -0
- data/lib/rubyn_code/tasks/models.rb +31 -0
- data/lib/rubyn_code/teams/mailbox.rb +128 -0
- data/lib/rubyn_code/teams/manager.rb +175 -0
- data/lib/rubyn_code/teams/teammate.rb +38 -0
- data/lib/rubyn_code/tools/background_run.rb +41 -0
- data/lib/rubyn_code/tools/base.rb +84 -0
- data/lib/rubyn_code/tools/bash.rb +81 -0
- data/lib/rubyn_code/tools/bundle_add.rb +53 -0
- data/lib/rubyn_code/tools/bundle_install.rb +41 -0
- data/lib/rubyn_code/tools/compact.rb +57 -0
- data/lib/rubyn_code/tools/db_migrate.rb +52 -0
- data/lib/rubyn_code/tools/edit_file.rb +49 -0
- data/lib/rubyn_code/tools/executor.rb +62 -0
- data/lib/rubyn_code/tools/git_commit.rb +97 -0
- data/lib/rubyn_code/tools/git_diff.rb +61 -0
- data/lib/rubyn_code/tools/git_log.rb +59 -0
- data/lib/rubyn_code/tools/git_status.rb +59 -0
- data/lib/rubyn_code/tools/glob.rb +44 -0
- data/lib/rubyn_code/tools/grep.rb +81 -0
- data/lib/rubyn_code/tools/load_skill.rb +41 -0
- data/lib/rubyn_code/tools/memory_search.rb +77 -0
- data/lib/rubyn_code/tools/memory_write.rb +52 -0
- data/lib/rubyn_code/tools/rails_generate.rb +54 -0
- data/lib/rubyn_code/tools/read_file.rb +38 -0
- data/lib/rubyn_code/tools/read_inbox.rb +64 -0
- data/lib/rubyn_code/tools/registry.rb +48 -0
- data/lib/rubyn_code/tools/review_pr.rb +145 -0
- data/lib/rubyn_code/tools/run_specs.rb +75 -0
- data/lib/rubyn_code/tools/schema.rb +59 -0
- data/lib/rubyn_code/tools/send_message.rb +53 -0
- data/lib/rubyn_code/tools/spawn_agent.rb +154 -0
- data/lib/rubyn_code/tools/spawn_teammate.rb +168 -0
- data/lib/rubyn_code/tools/task.rb +148 -0
- data/lib/rubyn_code/tools/web_fetch.rb +108 -0
- data/lib/rubyn_code/tools/web_search.rb +196 -0
- data/lib/rubyn_code/tools/write_file.rb +30 -0
- data/lib/rubyn_code/version.rb +5 -0
- data/lib/rubyn_code.rb +203 -0
- data/skills/code_quality/fits_in_your_head.md +189 -0
- data/skills/code_quality/naming_conventions.md +213 -0
- data/skills/code_quality/null_object.md +205 -0
- data/skills/code_quality/technical_debt.md +135 -0
- data/skills/code_quality/value_objects.md +216 -0
- data/skills/code_quality/yagni.md +176 -0
- data/skills/design_patterns/adapter.md +191 -0
- data/skills/design_patterns/bridge_memento_visitor.md +254 -0
- data/skills/design_patterns/builder.md +158 -0
- data/skills/design_patterns/command.md +126 -0
- data/skills/design_patterns/composite.md +147 -0
- data/skills/design_patterns/decorator.md +204 -0
- data/skills/design_patterns/facade.md +133 -0
- data/skills/design_patterns/factory_method.md +169 -0
- data/skills/design_patterns/iterator.md +116 -0
- data/skills/design_patterns/mediator.md +133 -0
- data/skills/design_patterns/observer.md +177 -0
- data/skills/design_patterns/proxy.md +140 -0
- data/skills/design_patterns/singleton.md +124 -0
- data/skills/design_patterns/state.md +207 -0
- data/skills/design_patterns/strategy.md +127 -0
- data/skills/design_patterns/template_method.md +173 -0
- data/skills/gems/devise.md +365 -0
- data/skills/gems/dry_rb.md +186 -0
- data/skills/gems/factory_bot.md +268 -0
- data/skills/gems/faraday.md +263 -0
- data/skills/gems/graphql_ruby.md +514 -0
- data/skills/gems/pundit.md +446 -0
- data/skills/gems/redis.md +219 -0
- data/skills/gems/rubocop.md +257 -0
- data/skills/gems/sidekiq.md +360 -0
- data/skills/gems/stripe.md +224 -0
- data/skills/minitest/assertions.md +185 -0
- data/skills/minitest/fixtures.md +238 -0
- data/skills/minitest/integration_tests.md +210 -0
- data/skills/minitest/mailers_and_jobs.md +218 -0
- data/skills/minitest/mocking_stubbing.md +202 -0
- data/skills/minitest/service_tests_and_performance.md +246 -0
- data/skills/minitest/structure_and_conventions.md +169 -0
- data/skills/minitest/system_tests.md +237 -0
- data/skills/rails/action_cable.md +160 -0
- data/skills/rails/active_record_basics.md +174 -0
- data/skills/rails/active_storage.md +242 -0
- data/skills/rails/api_design.md +212 -0
- data/skills/rails/associations.md +182 -0
- data/skills/rails/background_jobs.md +212 -0
- data/skills/rails/caching.md +158 -0
- data/skills/rails/callbacks.md +135 -0
- data/skills/rails/concerns_controllers.md +218 -0
- data/skills/rails/concerns_models.md +280 -0
- data/skills/rails/controllers.md +190 -0
- data/skills/rails/engines.md +201 -0
- data/skills/rails/form_objects.md +168 -0
- data/skills/rails/hotwire.md +229 -0
- data/skills/rails/internationalization.md +192 -0
- data/skills/rails/logging.md +198 -0
- data/skills/rails/mailers.md +180 -0
- data/skills/rails/migrations.md +200 -0
- data/skills/rails/multitenancy.md +207 -0
- data/skills/rails/n_plus_one.md +151 -0
- data/skills/rails/presenters.md +244 -0
- data/skills/rails/query_objects.md +177 -0
- data/skills/rails/routing.md +194 -0
- data/skills/rails/scopes.md +187 -0
- data/skills/rails/security.md +233 -0
- data/skills/rails/serializers.md +243 -0
- data/skills/rails/service_objects.md +184 -0
- data/skills/rails/testing_strategy.md +258 -0
- data/skills/rails/validations.md +206 -0
- data/skills/refactoring/code_smells.md +251 -0
- data/skills/refactoring/command_query_separation.md +166 -0
- data/skills/refactoring/encapsulate_collection.md +125 -0
- data/skills/refactoring/extract_class.md +138 -0
- data/skills/refactoring/extract_method.md +185 -0
- data/skills/refactoring/replace_conditional.md +211 -0
- data/skills/refactoring/value_objects.md +246 -0
- data/skills/rspec/build_stubbed.md +199 -0
- data/skills/rspec/factory_design.md +206 -0
- data/skills/rspec/let_vs_let_bang.md +161 -0
- data/skills/rspec/mocking_stubbing.md +209 -0
- data/skills/rspec/request_specs.md +212 -0
- data/skills/rspec/service_specs.md +262 -0
- data/skills/rspec/shared_examples.md +244 -0
- data/skills/rspec/system_specs.md +286 -0
- data/skills/rspec/test_performance.md +215 -0
- data/skills/ruby/blocks_procs_lambdas.md +204 -0
- data/skills/ruby/classes.md +155 -0
- data/skills/ruby/concurrency.md +194 -0
- data/skills/ruby/data_struct_openstruct.md +158 -0
- data/skills/ruby/debugging_profiling.md +204 -0
- data/skills/ruby/enumerable_patterns.md +168 -0
- data/skills/ruby/exception_handling.md +199 -0
- data/skills/ruby/file_io.md +217 -0
- data/skills/ruby/hashes.md +195 -0
- data/skills/ruby/metaprogramming.md +170 -0
- data/skills/ruby/modules.md +210 -0
- data/skills/ruby/pattern_matching.md +177 -0
- data/skills/ruby/regular_expressions.md +166 -0
- data/skills/ruby/result_objects.md +200 -0
- data/skills/ruby/strings.md +177 -0
- data/skills/ruby_project/bundler_dependencies.md +181 -0
- data/skills/ruby_project/cli_tools.md +224 -0
- data/skills/ruby_project/rake_tasks.md +146 -0
- data/skills/ruby_project/structure.md +261 -0
- data/skills/sinatra/application_structure.md +241 -0
- data/skills/sinatra/middleware_and_deployment.md +221 -0
- data/skills/sinatra/testing.md +233 -0
- data/skills/solid/dependency_inversion.md +195 -0
- data/skills/solid/interface_segregation.md +237 -0
- data/skills/solid/liskov_substitution.md +263 -0
- data/skills/solid/open_closed.md +212 -0
- data/skills/solid/single_responsibility.md +183 -0
- metadata +397 -0
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
# SOLID: Single Responsibility Principle (SRP)
|
|
2
|
+
|
|
3
|
+
## Pattern
|
|
4
|
+
|
|
5
|
+
A class should have one — and only one — reason to change. Every class encapsulates one concept, one actor's requirements, or one axis of change. When requirements from different stakeholders would cause the same class to change, split it.
|
|
6
|
+
|
|
7
|
+
```ruby
|
|
8
|
+
# GOOD: Each class has one reason to change
|
|
9
|
+
|
|
10
|
+
# Reason to change: how orders are persisted
|
|
11
|
+
class Order < ApplicationRecord
|
|
12
|
+
belongs_to :user
|
|
13
|
+
has_many :line_items, dependent: :destroy
|
|
14
|
+
|
|
15
|
+
validates :shipping_address, presence: true
|
|
16
|
+
|
|
17
|
+
def total
|
|
18
|
+
line_items.sum { |li| li.quantity * li.unit_price }
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Reason to change: how order totals are calculated (tax rules, discounts)
|
|
23
|
+
class Orders::TotalCalculator
|
|
24
|
+
def initialize(order)
|
|
25
|
+
@order = order
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def call
|
|
29
|
+
subtotal = @order.line_items.sum { |li| li.quantity * li.unit_price }
|
|
30
|
+
tax = TaxService.calculate(subtotal, @order.shipping_address)
|
|
31
|
+
discount = DiscountService.calculate(subtotal, @order.user)
|
|
32
|
+
subtotal + tax - discount
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Reason to change: how order confirmations are formatted and delivered
|
|
37
|
+
class Orders::ConfirmationNotifier
|
|
38
|
+
def initialize(order)
|
|
39
|
+
@order = order
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def call
|
|
43
|
+
OrderMailer.confirmation(@order).deliver_later
|
|
44
|
+
SmsNotifier.send(@order.user.phone, "Order #{@order.reference} confirmed!") if @order.user.sms_enabled?
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Reason to change: how order data is exported
|
|
49
|
+
class Orders::CsvExporter
|
|
50
|
+
HEADERS = %w[reference customer total status created_at].freeze
|
|
51
|
+
|
|
52
|
+
def call(orders)
|
|
53
|
+
CSV.generate(headers: true) do |csv|
|
|
54
|
+
csv << HEADERS
|
|
55
|
+
orders.each { |order| csv << row(order) }
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
private
|
|
60
|
+
|
|
61
|
+
def row(order)
|
|
62
|
+
[order.reference, order.user.email, order.total, order.status, order.created_at.iso8601]
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
The controller orchestrates, each specialist does its job:
|
|
68
|
+
|
|
69
|
+
```ruby
|
|
70
|
+
class OrdersController < ApplicationController
|
|
71
|
+
def create
|
|
72
|
+
@order = current_user.orders.build(order_params)
|
|
73
|
+
@order.total = Orders::TotalCalculator.new(@order).call
|
|
74
|
+
|
|
75
|
+
if @order.save
|
|
76
|
+
Orders::ConfirmationNotifier.new(@order).call
|
|
77
|
+
redirect_to @order, notice: "Order placed."
|
|
78
|
+
else
|
|
79
|
+
render :new, status: :unprocessable_entity
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def export
|
|
84
|
+
orders = current_user.orders.recent
|
|
85
|
+
csv = Orders::CsvExporter.new.call(orders)
|
|
86
|
+
send_data csv, filename: "orders-#{Date.current}.csv"
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Why This Is Good
|
|
92
|
+
|
|
93
|
+
- **Changes are isolated.** Tax law changes? Modify `TotalCalculator`. Email template changes? Modify `ConfirmationNotifier`. Neither change touches the Order model, the other service, or the controller.
|
|
94
|
+
- **Smaller classes are easier to understand.** A developer opening `CsvExporter` knows it does one thing. They don't need to scroll past tax calculations and email logic to find the export code.
|
|
95
|
+
- **Easier to test.** Test `TotalCalculator` with unit tests against various tax/discount scenarios. Test `ConfirmationNotifier` by asserting emails are enqueued. Each test file is focused and fast.
|
|
96
|
+
- **Team-friendly.** Developer A works on tax calculation while Developer B works on notifications. No merge conflicts because the changes are in separate files.
|
|
97
|
+
- **Reusable.** `TotalCalculator` can be used in the controller, in a background job, in an API endpoint, and in an admin panel. If calculation lived inside the controller, it couldn't be reused.
|
|
98
|
+
|
|
99
|
+
## Anti-Pattern
|
|
100
|
+
|
|
101
|
+
A god class that handles persistence, calculation, notification, and export:
|
|
102
|
+
|
|
103
|
+
```ruby
|
|
104
|
+
class Order < ApplicationRecord
|
|
105
|
+
belongs_to :user
|
|
106
|
+
has_many :line_items
|
|
107
|
+
|
|
108
|
+
after_create :send_confirmation_email
|
|
109
|
+
after_create :send_sms_notification
|
|
110
|
+
after_update :recalculate_total
|
|
111
|
+
|
|
112
|
+
def calculate_total
|
|
113
|
+
subtotal = line_items.sum { |li| li.quantity * li.unit_price }
|
|
114
|
+
tax = subtotal * tax_rate_for(shipping_address)
|
|
115
|
+
discount = loyalty_discount_for(user)
|
|
116
|
+
self.total = subtotal + tax - discount
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def tax_rate_for(address)
|
|
120
|
+
case address.state
|
|
121
|
+
when "CA" then 0.0725
|
|
122
|
+
when "TX" then 0.0625
|
|
123
|
+
when "NY" then 0.08
|
|
124
|
+
else 0.05
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def loyalty_discount_for(user)
|
|
129
|
+
return 0 unless user.loyalty_tier == :gold
|
|
130
|
+
total * 0.1
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def send_confirmation_email
|
|
134
|
+
OrderMailer.confirmation(self).deliver_later
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def send_sms_notification
|
|
138
|
+
return unless user.sms_enabled?
|
|
139
|
+
SmsClient.send(user.phone, "Order #{reference} confirmed!")
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def to_csv
|
|
143
|
+
[reference, user.email, total, status, created_at.iso8601].join(",")
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def self.export_csv(orders)
|
|
147
|
+
headers = "reference,customer,total,status,created_at\n"
|
|
148
|
+
headers + orders.map(&:to_csv).join("\n")
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## Why This Is Bad
|
|
154
|
+
|
|
155
|
+
- **Four reasons to change in one class.** Tax rules, notification channels, export format, and persistence logic are all tangled together. A change to tax rates risks breaking CSV export because they share the same file.
|
|
156
|
+
- **500+ lines.** This Order model is headed toward 500 lines as each responsibility grows. Adding international tax, promo codes, push notifications, and PDF export will push it past 1,000.
|
|
157
|
+
- **Callbacks hide side effects.** `after_create :send_sms_notification` silently sends texts. Creating an order in tests, seeds, or the console triggers SMS delivery.
|
|
158
|
+
- **Untestable in isolation.** Testing `tax_rate_for` requires an Order instance. Testing `send_confirmation_email` requires a saved order. Everything is coupled to the model lifecycle.
|
|
159
|
+
- **Violates Open/Closed.** Adding a new notification channel (push, Slack) means modifying the Order class. With SRP, you modify the Notifier class or add a new one — the Order never changes.
|
|
160
|
+
|
|
161
|
+
## When To Apply
|
|
162
|
+
|
|
163
|
+
- **A class is changing for multiple reasons.** If your last 5 commits to `order.rb` were: fix tax calculation, update email template, add CSV header, fix discount logic — that's 4 different responsibilities.
|
|
164
|
+
- **A class is longer than ~100 lines.** Length isn't a hard rule, but it's a signal. A 200-line model usually has at least 2 responsibilities that could be extracted.
|
|
165
|
+
- **You describe a class with "and."** "The Order class persists data AND calculates totals AND sends notifications AND exports CSV." Each "and" is a candidate for extraction.
|
|
166
|
+
- **Different team members frequently edit the same file.** Merge conflicts on a model file are a sign that multiple concerns are competing for the same space.
|
|
167
|
+
|
|
168
|
+
## When NOT To Apply
|
|
169
|
+
|
|
170
|
+
- **Don't extract prematurely.** A 30-line model with validations and 2 scopes has one responsibility (data access). Don't split it into 4 classes for theoretical purity.
|
|
171
|
+
- **Simple derived values belong on the model.** `def full_name; "#{first_name} #{last_name}"; end` is fine on User. It's a data representation concern, not a separate responsibility.
|
|
172
|
+
- **Don't create single-method classes for trivial operations.** A `StringCapitalizer` class is over-engineering. SRP means one *responsibility*, not one *method*.
|
|
173
|
+
|
|
174
|
+
## Edge Cases
|
|
175
|
+
|
|
176
|
+
**How do you identify responsibilities?**
|
|
177
|
+
Ask: "Who would request this change?" If the answer is different people or departments (accounting wants tax changes, marketing wants email changes, ops wants export changes), those are different responsibilities.
|
|
178
|
+
|
|
179
|
+
**Rails models seem to violate SRP by default:**
|
|
180
|
+
ActiveRecord models combine data access, validation, association management, and query interface. This is an intentional framework trade-off. The SRP boundary in Rails is: models own persistence and validation, everything else is extracted. Don't fight the framework — work within it.
|
|
181
|
+
|
|
182
|
+
**Service objects can also violate SRP:**
|
|
183
|
+
A `ProcessOrderService` that calculates totals, sends emails, AND updates inventory violates SRP just as badly as a fat model. Each step should be its own service, orchestrated by a coordinator.
|
metadata
ADDED
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: rubyn-code
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- fadedmaturity
|
|
8
|
+
bindir: exe
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: sqlite3
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '2.0'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '2.0'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: faraday
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - ">="
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '2.0'
|
|
33
|
+
- - "<"
|
|
34
|
+
- !ruby/object:Gem::Version
|
|
35
|
+
version: '3.0'
|
|
36
|
+
type: :runtime
|
|
37
|
+
prerelease: false
|
|
38
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
39
|
+
requirements:
|
|
40
|
+
- - ">="
|
|
41
|
+
- !ruby/object:Gem::Version
|
|
42
|
+
version: '2.0'
|
|
43
|
+
- - "<"
|
|
44
|
+
- !ruby/object:Gem::Version
|
|
45
|
+
version: '3.0'
|
|
46
|
+
- !ruby/object:Gem::Dependency
|
|
47
|
+
name: pastel
|
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
|
49
|
+
requirements:
|
|
50
|
+
- - "~>"
|
|
51
|
+
- !ruby/object:Gem::Version
|
|
52
|
+
version: '0.8'
|
|
53
|
+
type: :runtime
|
|
54
|
+
prerelease: false
|
|
55
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
56
|
+
requirements:
|
|
57
|
+
- - "~>"
|
|
58
|
+
- !ruby/object:Gem::Version
|
|
59
|
+
version: '0.8'
|
|
60
|
+
- !ruby/object:Gem::Dependency
|
|
61
|
+
name: rouge
|
|
62
|
+
requirement: !ruby/object:Gem::Requirement
|
|
63
|
+
requirements:
|
|
64
|
+
- - ">="
|
|
65
|
+
- !ruby/object:Gem::Version
|
|
66
|
+
version: '4.0'
|
|
67
|
+
- - "<"
|
|
68
|
+
- !ruby/object:Gem::Version
|
|
69
|
+
version: '5.0'
|
|
70
|
+
type: :runtime
|
|
71
|
+
prerelease: false
|
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
73
|
+
requirements:
|
|
74
|
+
- - ">="
|
|
75
|
+
- !ruby/object:Gem::Version
|
|
76
|
+
version: '4.0'
|
|
77
|
+
- - "<"
|
|
78
|
+
- !ruby/object:Gem::Version
|
|
79
|
+
version: '5.0'
|
|
80
|
+
- !ruby/object:Gem::Dependency
|
|
81
|
+
name: tty-prompt
|
|
82
|
+
requirement: !ruby/object:Gem::Requirement
|
|
83
|
+
requirements:
|
|
84
|
+
- - "~>"
|
|
85
|
+
- !ruby/object:Gem::Version
|
|
86
|
+
version: '0.23'
|
|
87
|
+
type: :runtime
|
|
88
|
+
prerelease: false
|
|
89
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
90
|
+
requirements:
|
|
91
|
+
- - "~>"
|
|
92
|
+
- !ruby/object:Gem::Version
|
|
93
|
+
version: '0.23'
|
|
94
|
+
- !ruby/object:Gem::Dependency
|
|
95
|
+
name: tty-spinner
|
|
96
|
+
requirement: !ruby/object:Gem::Requirement
|
|
97
|
+
requirements:
|
|
98
|
+
- - "~>"
|
|
99
|
+
- !ruby/object:Gem::Version
|
|
100
|
+
version: '0.9'
|
|
101
|
+
type: :runtime
|
|
102
|
+
prerelease: false
|
|
103
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
104
|
+
requirements:
|
|
105
|
+
- - "~>"
|
|
106
|
+
- !ruby/object:Gem::Version
|
|
107
|
+
version: '0.9'
|
|
108
|
+
- !ruby/object:Gem::Dependency
|
|
109
|
+
name: tty-markdown
|
|
110
|
+
requirement: !ruby/object:Gem::Requirement
|
|
111
|
+
requirements:
|
|
112
|
+
- - "~>"
|
|
113
|
+
- !ruby/object:Gem::Version
|
|
114
|
+
version: '0.7'
|
|
115
|
+
type: :runtime
|
|
116
|
+
prerelease: false
|
|
117
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
118
|
+
requirements:
|
|
119
|
+
- - "~>"
|
|
120
|
+
- !ruby/object:Gem::Version
|
|
121
|
+
version: '0.7'
|
|
122
|
+
- !ruby/object:Gem::Dependency
|
|
123
|
+
name: webrick
|
|
124
|
+
requirement: !ruby/object:Gem::Requirement
|
|
125
|
+
requirements:
|
|
126
|
+
- - "~>"
|
|
127
|
+
- !ruby/object:Gem::Version
|
|
128
|
+
version: '1.8'
|
|
129
|
+
type: :runtime
|
|
130
|
+
prerelease: false
|
|
131
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
132
|
+
requirements:
|
|
133
|
+
- - "~>"
|
|
134
|
+
- !ruby/object:Gem::Version
|
|
135
|
+
version: '1.8'
|
|
136
|
+
description: An AI-powered CLI coding assistant specialized for Ruby and Rails, featuring
|
|
137
|
+
a 16-layer agentic architecture, local SQLite persistence, and Claude OAuth.
|
|
138
|
+
executables:
|
|
139
|
+
- rubyn-code
|
|
140
|
+
extensions: []
|
|
141
|
+
extra_rdoc_files: []
|
|
142
|
+
files:
|
|
143
|
+
- LICENSE
|
|
144
|
+
- README.md
|
|
145
|
+
- db/migrations/000_create_schema_migrations.sql
|
|
146
|
+
- db/migrations/001_create_sessions.sql
|
|
147
|
+
- db/migrations/002_create_messages.sql
|
|
148
|
+
- db/migrations/003_create_tasks.sql
|
|
149
|
+
- db/migrations/004_create_task_dependencies.sql
|
|
150
|
+
- db/migrations/005_create_memories.sql
|
|
151
|
+
- db/migrations/006_create_cost_records.sql
|
|
152
|
+
- db/migrations/007_create_hooks.sql
|
|
153
|
+
- db/migrations/008_create_skills_cache.sql
|
|
154
|
+
- db/migrations/009_create_teams.sql
|
|
155
|
+
- db/migrations/010_create_instincts.sql
|
|
156
|
+
- exe/rubyn-code
|
|
157
|
+
- lib/rubyn_code.rb
|
|
158
|
+
- lib/rubyn_code/agent/conversation.rb
|
|
159
|
+
- lib/rubyn_code/agent/loop.rb
|
|
160
|
+
- lib/rubyn_code/agent/loop_detector.rb
|
|
161
|
+
- lib/rubyn_code/auth/oauth.rb
|
|
162
|
+
- lib/rubyn_code/auth/server.rb
|
|
163
|
+
- lib/rubyn_code/auth/token_store.rb
|
|
164
|
+
- lib/rubyn_code/autonomous/daemon.rb
|
|
165
|
+
- lib/rubyn_code/autonomous/idle_poller.rb
|
|
166
|
+
- lib/rubyn_code/autonomous/task_claimer.rb
|
|
167
|
+
- lib/rubyn_code/background/job.rb
|
|
168
|
+
- lib/rubyn_code/background/notifier.rb
|
|
169
|
+
- lib/rubyn_code/background/worker.rb
|
|
170
|
+
- lib/rubyn_code/cli/app.rb
|
|
171
|
+
- lib/rubyn_code/cli/input_handler.rb
|
|
172
|
+
- lib/rubyn_code/cli/renderer.rb
|
|
173
|
+
- lib/rubyn_code/cli/repl.rb
|
|
174
|
+
- lib/rubyn_code/cli/spinner.rb
|
|
175
|
+
- lib/rubyn_code/cli/stream_formatter.rb
|
|
176
|
+
- lib/rubyn_code/config/defaults.rb
|
|
177
|
+
- lib/rubyn_code/config/project_config.rb
|
|
178
|
+
- lib/rubyn_code/config/settings.rb
|
|
179
|
+
- lib/rubyn_code/context/auto_compact.rb
|
|
180
|
+
- lib/rubyn_code/context/compactor.rb
|
|
181
|
+
- lib/rubyn_code/context/manager.rb
|
|
182
|
+
- lib/rubyn_code/context/manual_compact.rb
|
|
183
|
+
- lib/rubyn_code/context/micro_compact.rb
|
|
184
|
+
- lib/rubyn_code/db/connection.rb
|
|
185
|
+
- lib/rubyn_code/db/migrator.rb
|
|
186
|
+
- lib/rubyn_code/db/schema.rb
|
|
187
|
+
- lib/rubyn_code/hooks/built_in.rb
|
|
188
|
+
- lib/rubyn_code/hooks/registry.rb
|
|
189
|
+
- lib/rubyn_code/hooks/runner.rb
|
|
190
|
+
- lib/rubyn_code/hooks/user_hooks.rb
|
|
191
|
+
- lib/rubyn_code/learning/extractor.rb
|
|
192
|
+
- lib/rubyn_code/learning/injector.rb
|
|
193
|
+
- lib/rubyn_code/learning/instinct.rb
|
|
194
|
+
- lib/rubyn_code/llm/client.rb
|
|
195
|
+
- lib/rubyn_code/llm/message_builder.rb
|
|
196
|
+
- lib/rubyn_code/llm/streaming.rb
|
|
197
|
+
- lib/rubyn_code/mcp/client.rb
|
|
198
|
+
- lib/rubyn_code/mcp/config.rb
|
|
199
|
+
- lib/rubyn_code/mcp/sse_transport.rb
|
|
200
|
+
- lib/rubyn_code/mcp/stdio_transport.rb
|
|
201
|
+
- lib/rubyn_code/mcp/tool_bridge.rb
|
|
202
|
+
- lib/rubyn_code/memory/models.rb
|
|
203
|
+
- lib/rubyn_code/memory/search.rb
|
|
204
|
+
- lib/rubyn_code/memory/session_persistence.rb
|
|
205
|
+
- lib/rubyn_code/memory/store.rb
|
|
206
|
+
- lib/rubyn_code/observability/budget_enforcer.rb
|
|
207
|
+
- lib/rubyn_code/observability/cost_calculator.rb
|
|
208
|
+
- lib/rubyn_code/observability/models.rb
|
|
209
|
+
- lib/rubyn_code/observability/token_counter.rb
|
|
210
|
+
- lib/rubyn_code/observability/usage_reporter.rb
|
|
211
|
+
- lib/rubyn_code/output/diff_renderer.rb
|
|
212
|
+
- lib/rubyn_code/output/formatter.rb
|
|
213
|
+
- lib/rubyn_code/permissions/deny_list.rb
|
|
214
|
+
- lib/rubyn_code/permissions/policy.rb
|
|
215
|
+
- lib/rubyn_code/permissions/prompter.rb
|
|
216
|
+
- lib/rubyn_code/permissions/tier.rb
|
|
217
|
+
- lib/rubyn_code/protocols/interrupt_handler.rb
|
|
218
|
+
- lib/rubyn_code/protocols/plan_approval.rb
|
|
219
|
+
- lib/rubyn_code/protocols/shutdown_handshake.rb
|
|
220
|
+
- lib/rubyn_code/skills/catalog.rb
|
|
221
|
+
- lib/rubyn_code/skills/document.rb
|
|
222
|
+
- lib/rubyn_code/skills/loader.rb
|
|
223
|
+
- lib/rubyn_code/sub_agents/runner.rb
|
|
224
|
+
- lib/rubyn_code/sub_agents/summarizer.rb
|
|
225
|
+
- lib/rubyn_code/tasks/dag.rb
|
|
226
|
+
- lib/rubyn_code/tasks/manager.rb
|
|
227
|
+
- lib/rubyn_code/tasks/models.rb
|
|
228
|
+
- lib/rubyn_code/teams/mailbox.rb
|
|
229
|
+
- lib/rubyn_code/teams/manager.rb
|
|
230
|
+
- lib/rubyn_code/teams/teammate.rb
|
|
231
|
+
- lib/rubyn_code/tools/background_run.rb
|
|
232
|
+
- lib/rubyn_code/tools/base.rb
|
|
233
|
+
- lib/rubyn_code/tools/bash.rb
|
|
234
|
+
- lib/rubyn_code/tools/bundle_add.rb
|
|
235
|
+
- lib/rubyn_code/tools/bundle_install.rb
|
|
236
|
+
- lib/rubyn_code/tools/compact.rb
|
|
237
|
+
- lib/rubyn_code/tools/db_migrate.rb
|
|
238
|
+
- lib/rubyn_code/tools/edit_file.rb
|
|
239
|
+
- lib/rubyn_code/tools/executor.rb
|
|
240
|
+
- lib/rubyn_code/tools/git_commit.rb
|
|
241
|
+
- lib/rubyn_code/tools/git_diff.rb
|
|
242
|
+
- lib/rubyn_code/tools/git_log.rb
|
|
243
|
+
- lib/rubyn_code/tools/git_status.rb
|
|
244
|
+
- lib/rubyn_code/tools/glob.rb
|
|
245
|
+
- lib/rubyn_code/tools/grep.rb
|
|
246
|
+
- lib/rubyn_code/tools/load_skill.rb
|
|
247
|
+
- lib/rubyn_code/tools/memory_search.rb
|
|
248
|
+
- lib/rubyn_code/tools/memory_write.rb
|
|
249
|
+
- lib/rubyn_code/tools/rails_generate.rb
|
|
250
|
+
- lib/rubyn_code/tools/read_file.rb
|
|
251
|
+
- lib/rubyn_code/tools/read_inbox.rb
|
|
252
|
+
- lib/rubyn_code/tools/registry.rb
|
|
253
|
+
- lib/rubyn_code/tools/review_pr.rb
|
|
254
|
+
- lib/rubyn_code/tools/run_specs.rb
|
|
255
|
+
- lib/rubyn_code/tools/schema.rb
|
|
256
|
+
- lib/rubyn_code/tools/send_message.rb
|
|
257
|
+
- lib/rubyn_code/tools/spawn_agent.rb
|
|
258
|
+
- lib/rubyn_code/tools/spawn_teammate.rb
|
|
259
|
+
- lib/rubyn_code/tools/task.rb
|
|
260
|
+
- lib/rubyn_code/tools/web_fetch.rb
|
|
261
|
+
- lib/rubyn_code/tools/web_search.rb
|
|
262
|
+
- lib/rubyn_code/tools/write_file.rb
|
|
263
|
+
- lib/rubyn_code/version.rb
|
|
264
|
+
- skills/code_quality/fits_in_your_head.md
|
|
265
|
+
- skills/code_quality/naming_conventions.md
|
|
266
|
+
- skills/code_quality/null_object.md
|
|
267
|
+
- skills/code_quality/technical_debt.md
|
|
268
|
+
- skills/code_quality/value_objects.md
|
|
269
|
+
- skills/code_quality/yagni.md
|
|
270
|
+
- skills/design_patterns/adapter.md
|
|
271
|
+
- skills/design_patterns/bridge_memento_visitor.md
|
|
272
|
+
- skills/design_patterns/builder.md
|
|
273
|
+
- skills/design_patterns/command.md
|
|
274
|
+
- skills/design_patterns/composite.md
|
|
275
|
+
- skills/design_patterns/decorator.md
|
|
276
|
+
- skills/design_patterns/facade.md
|
|
277
|
+
- skills/design_patterns/factory_method.md
|
|
278
|
+
- skills/design_patterns/iterator.md
|
|
279
|
+
- skills/design_patterns/mediator.md
|
|
280
|
+
- skills/design_patterns/observer.md
|
|
281
|
+
- skills/design_patterns/proxy.md
|
|
282
|
+
- skills/design_patterns/singleton.md
|
|
283
|
+
- skills/design_patterns/state.md
|
|
284
|
+
- skills/design_patterns/strategy.md
|
|
285
|
+
- skills/design_patterns/template_method.md
|
|
286
|
+
- skills/gems/devise.md
|
|
287
|
+
- skills/gems/dry_rb.md
|
|
288
|
+
- skills/gems/factory_bot.md
|
|
289
|
+
- skills/gems/faraday.md
|
|
290
|
+
- skills/gems/graphql_ruby.md
|
|
291
|
+
- skills/gems/pundit.md
|
|
292
|
+
- skills/gems/redis.md
|
|
293
|
+
- skills/gems/rubocop.md
|
|
294
|
+
- skills/gems/sidekiq.md
|
|
295
|
+
- skills/gems/stripe.md
|
|
296
|
+
- skills/minitest/assertions.md
|
|
297
|
+
- skills/minitest/fixtures.md
|
|
298
|
+
- skills/minitest/integration_tests.md
|
|
299
|
+
- skills/minitest/mailers_and_jobs.md
|
|
300
|
+
- skills/minitest/mocking_stubbing.md
|
|
301
|
+
- skills/minitest/service_tests_and_performance.md
|
|
302
|
+
- skills/minitest/structure_and_conventions.md
|
|
303
|
+
- skills/minitest/system_tests.md
|
|
304
|
+
- skills/rails/action_cable.md
|
|
305
|
+
- skills/rails/active_record_basics.md
|
|
306
|
+
- skills/rails/active_storage.md
|
|
307
|
+
- skills/rails/api_design.md
|
|
308
|
+
- skills/rails/associations.md
|
|
309
|
+
- skills/rails/background_jobs.md
|
|
310
|
+
- skills/rails/caching.md
|
|
311
|
+
- skills/rails/callbacks.md
|
|
312
|
+
- skills/rails/concerns_controllers.md
|
|
313
|
+
- skills/rails/concerns_models.md
|
|
314
|
+
- skills/rails/controllers.md
|
|
315
|
+
- skills/rails/engines.md
|
|
316
|
+
- skills/rails/form_objects.md
|
|
317
|
+
- skills/rails/hotwire.md
|
|
318
|
+
- skills/rails/internationalization.md
|
|
319
|
+
- skills/rails/logging.md
|
|
320
|
+
- skills/rails/mailers.md
|
|
321
|
+
- skills/rails/migrations.md
|
|
322
|
+
- skills/rails/multitenancy.md
|
|
323
|
+
- skills/rails/n_plus_one.md
|
|
324
|
+
- skills/rails/presenters.md
|
|
325
|
+
- skills/rails/query_objects.md
|
|
326
|
+
- skills/rails/routing.md
|
|
327
|
+
- skills/rails/scopes.md
|
|
328
|
+
- skills/rails/security.md
|
|
329
|
+
- skills/rails/serializers.md
|
|
330
|
+
- skills/rails/service_objects.md
|
|
331
|
+
- skills/rails/testing_strategy.md
|
|
332
|
+
- skills/rails/validations.md
|
|
333
|
+
- skills/refactoring/code_smells.md
|
|
334
|
+
- skills/refactoring/command_query_separation.md
|
|
335
|
+
- skills/refactoring/encapsulate_collection.md
|
|
336
|
+
- skills/refactoring/extract_class.md
|
|
337
|
+
- skills/refactoring/extract_method.md
|
|
338
|
+
- skills/refactoring/replace_conditional.md
|
|
339
|
+
- skills/refactoring/value_objects.md
|
|
340
|
+
- skills/rspec/build_stubbed.md
|
|
341
|
+
- skills/rspec/factory_design.md
|
|
342
|
+
- skills/rspec/let_vs_let_bang.md
|
|
343
|
+
- skills/rspec/mocking_stubbing.md
|
|
344
|
+
- skills/rspec/request_specs.md
|
|
345
|
+
- skills/rspec/service_specs.md
|
|
346
|
+
- skills/rspec/shared_examples.md
|
|
347
|
+
- skills/rspec/system_specs.md
|
|
348
|
+
- skills/rspec/test_performance.md
|
|
349
|
+
- skills/ruby/blocks_procs_lambdas.md
|
|
350
|
+
- skills/ruby/classes.md
|
|
351
|
+
- skills/ruby/concurrency.md
|
|
352
|
+
- skills/ruby/data_struct_openstruct.md
|
|
353
|
+
- skills/ruby/debugging_profiling.md
|
|
354
|
+
- skills/ruby/enumerable_patterns.md
|
|
355
|
+
- skills/ruby/exception_handling.md
|
|
356
|
+
- skills/ruby/file_io.md
|
|
357
|
+
- skills/ruby/hashes.md
|
|
358
|
+
- skills/ruby/metaprogramming.md
|
|
359
|
+
- skills/ruby/modules.md
|
|
360
|
+
- skills/ruby/pattern_matching.md
|
|
361
|
+
- skills/ruby/regular_expressions.md
|
|
362
|
+
- skills/ruby/result_objects.md
|
|
363
|
+
- skills/ruby/strings.md
|
|
364
|
+
- skills/ruby_project/bundler_dependencies.md
|
|
365
|
+
- skills/ruby_project/cli_tools.md
|
|
366
|
+
- skills/ruby_project/rake_tasks.md
|
|
367
|
+
- skills/ruby_project/structure.md
|
|
368
|
+
- skills/sinatra/application_structure.md
|
|
369
|
+
- skills/sinatra/middleware_and_deployment.md
|
|
370
|
+
- skills/sinatra/testing.md
|
|
371
|
+
- skills/solid/dependency_inversion.md
|
|
372
|
+
- skills/solid/interface_segregation.md
|
|
373
|
+
- skills/solid/liskov_substitution.md
|
|
374
|
+
- skills/solid/open_closed.md
|
|
375
|
+
- skills/solid/single_responsibility.md
|
|
376
|
+
homepage: https://rubyn.dev
|
|
377
|
+
licenses:
|
|
378
|
+
- MIT
|
|
379
|
+
metadata: {}
|
|
380
|
+
rdoc_options: []
|
|
381
|
+
require_paths:
|
|
382
|
+
- lib
|
|
383
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
384
|
+
requirements:
|
|
385
|
+
- - ">="
|
|
386
|
+
- !ruby/object:Gem::Version
|
|
387
|
+
version: '3.3'
|
|
388
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
389
|
+
requirements:
|
|
390
|
+
- - ">="
|
|
391
|
+
- !ruby/object:Gem::Version
|
|
392
|
+
version: '0'
|
|
393
|
+
requirements: []
|
|
394
|
+
rubygems_version: 3.6.9
|
|
395
|
+
specification_version: 4
|
|
396
|
+
summary: Ruby & Rails agentic coding assistant
|
|
397
|
+
test_files: []
|