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
data/lib/rubyn_code.rb
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "rubyn_code/version"
|
|
4
|
+
|
|
5
|
+
module RubynCode
|
|
6
|
+
class Error < StandardError; end
|
|
7
|
+
class AuthenticationError < Error; end
|
|
8
|
+
class BudgetExceededError < Error; end
|
|
9
|
+
class PermissionDeniedError < Error; end
|
|
10
|
+
class StallDetectedError < Error; end
|
|
11
|
+
class ToolNotFoundError < Error; end
|
|
12
|
+
class ConfigError < Error; end
|
|
13
|
+
|
|
14
|
+
# Infrastructure
|
|
15
|
+
autoload :Config, "rubyn_code/config/settings"
|
|
16
|
+
|
|
17
|
+
# Database
|
|
18
|
+
module DB
|
|
19
|
+
autoload :Connection, "rubyn_code/db/connection"
|
|
20
|
+
autoload :Migrator, "rubyn_code/db/migrator"
|
|
21
|
+
autoload :Schema, "rubyn_code/db/schema"
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Auth
|
|
25
|
+
module Auth
|
|
26
|
+
autoload :OAuth, "rubyn_code/auth/oauth"
|
|
27
|
+
autoload :TokenStore, "rubyn_code/auth/token_store"
|
|
28
|
+
autoload :Server, "rubyn_code/auth/server"
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# LLM
|
|
32
|
+
module LLM
|
|
33
|
+
autoload :Client, "rubyn_code/llm/client"
|
|
34
|
+
autoload :Streaming, "rubyn_code/llm/streaming"
|
|
35
|
+
autoload :MessageBuilder, "rubyn_code/llm/message_builder"
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Layer 1: Agent Loop
|
|
39
|
+
module Agent
|
|
40
|
+
autoload :Loop, "rubyn_code/agent/loop"
|
|
41
|
+
autoload :LoopDetector, "rubyn_code/agent/loop_detector"
|
|
42
|
+
autoload :Conversation, "rubyn_code/agent/conversation"
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Layer 2: Tool System
|
|
46
|
+
module Tools
|
|
47
|
+
autoload :Base, "rubyn_code/tools/base"
|
|
48
|
+
autoload :Registry, "rubyn_code/tools/registry"
|
|
49
|
+
autoload :Schema, "rubyn_code/tools/schema"
|
|
50
|
+
autoload :Executor, "rubyn_code/tools/executor"
|
|
51
|
+
autoload :ReadFile, "rubyn_code/tools/read_file"
|
|
52
|
+
autoload :WriteFile, "rubyn_code/tools/write_file"
|
|
53
|
+
autoload :EditFile, "rubyn_code/tools/edit_file"
|
|
54
|
+
autoload :Glob, "rubyn_code/tools/glob"
|
|
55
|
+
autoload :Grep, "rubyn_code/tools/grep"
|
|
56
|
+
autoload :Bash, "rubyn_code/tools/bash"
|
|
57
|
+
autoload :RailsGenerate, "rubyn_code/tools/rails_generate"
|
|
58
|
+
autoload :DbMigrate, "rubyn_code/tools/db_migrate"
|
|
59
|
+
autoload :RunSpecs, "rubyn_code/tools/run_specs"
|
|
60
|
+
autoload :BundleInstall, "rubyn_code/tools/bundle_install"
|
|
61
|
+
autoload :BundleAdd, "rubyn_code/tools/bundle_add"
|
|
62
|
+
autoload :Compact, "rubyn_code/tools/compact"
|
|
63
|
+
autoload :LoadSkill, "rubyn_code/tools/load_skill"
|
|
64
|
+
autoload :Task, "rubyn_code/tools/task"
|
|
65
|
+
autoload :MemorySearch, "rubyn_code/tools/memory_search"
|
|
66
|
+
autoload :MemoryWrite, "rubyn_code/tools/memory_write"
|
|
67
|
+
autoload :SendMessage, "rubyn_code/tools/send_message"
|
|
68
|
+
autoload :ReadInbox, "rubyn_code/tools/read_inbox"
|
|
69
|
+
autoload :ReviewPr, "rubyn_code/tools/review_pr"
|
|
70
|
+
autoload :SpawnAgent, "rubyn_code/tools/spawn_agent"
|
|
71
|
+
autoload :BackgroundRun, "rubyn_code/tools/background_run"
|
|
72
|
+
autoload :WebSearch, "rubyn_code/tools/web_search"
|
|
73
|
+
autoload :WebFetch, "rubyn_code/tools/web_fetch"
|
|
74
|
+
autoload :GitCommit, "rubyn_code/tools/git_commit"
|
|
75
|
+
autoload :GitDiff, "rubyn_code/tools/git_diff"
|
|
76
|
+
autoload :GitLog, "rubyn_code/tools/git_log"
|
|
77
|
+
autoload :GitStatus, "rubyn_code/tools/git_status"
|
|
78
|
+
autoload :SpawnTeammate, "rubyn_code/tools/spawn_teammate"
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Layer 3: Permissions
|
|
82
|
+
module Permissions
|
|
83
|
+
autoload :Tier, "rubyn_code/permissions/tier"
|
|
84
|
+
autoload :Policy, "rubyn_code/permissions/policy"
|
|
85
|
+
autoload :DenyList, "rubyn_code/permissions/deny_list"
|
|
86
|
+
autoload :Prompter, "rubyn_code/permissions/prompter"
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Layer 4: Context Management
|
|
90
|
+
module Context
|
|
91
|
+
autoload :Manager, "rubyn_code/context/manager"
|
|
92
|
+
autoload :Compactor, "rubyn_code/context/compactor"
|
|
93
|
+
autoload :MicroCompact, "rubyn_code/context/micro_compact"
|
|
94
|
+
autoload :AutoCompact, "rubyn_code/context/auto_compact"
|
|
95
|
+
autoload :ManualCompact, "rubyn_code/context/manual_compact"
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Layer 5: Skills
|
|
99
|
+
module Skills
|
|
100
|
+
autoload :Loader, "rubyn_code/skills/loader"
|
|
101
|
+
autoload :Catalog, "rubyn_code/skills/catalog"
|
|
102
|
+
autoload :Document, "rubyn_code/skills/document"
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Layer 6: Sub-Agents
|
|
106
|
+
module SubAgents
|
|
107
|
+
autoload :Runner, "rubyn_code/sub_agents/runner"
|
|
108
|
+
autoload :Summarizer, "rubyn_code/sub_agents/summarizer"
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Layer 7: Tasks
|
|
112
|
+
module Tasks
|
|
113
|
+
autoload :Manager, "rubyn_code/tasks/manager"
|
|
114
|
+
autoload :DAG, "rubyn_code/tasks/dag"
|
|
115
|
+
autoload :Models, "rubyn_code/tasks/models"
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# Layer 8: Background
|
|
119
|
+
module Background
|
|
120
|
+
autoload :Worker, "rubyn_code/background/worker"
|
|
121
|
+
autoload :Job, "rubyn_code/background/job"
|
|
122
|
+
autoload :Notifier, "rubyn_code/background/notifier"
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Layer 9: Teams
|
|
126
|
+
module Teams
|
|
127
|
+
autoload :Manager, "rubyn_code/teams/manager"
|
|
128
|
+
autoload :Mailbox, "rubyn_code/teams/mailbox"
|
|
129
|
+
autoload :Teammate, "rubyn_code/teams/teammate"
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# Layer 10: Protocols
|
|
133
|
+
module Protocols
|
|
134
|
+
autoload :ShutdownHandshake, "rubyn_code/protocols/shutdown_handshake"
|
|
135
|
+
autoload :PlanApproval, "rubyn_code/protocols/plan_approval"
|
|
136
|
+
autoload :InterruptHandler, "rubyn_code/protocols/interrupt_handler"
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# Layer 11: Autonomous
|
|
140
|
+
module Autonomous
|
|
141
|
+
autoload :Daemon, "rubyn_code/autonomous/daemon"
|
|
142
|
+
autoload :IdlePoller, "rubyn_code/autonomous/idle_poller"
|
|
143
|
+
autoload :TaskClaimer, "rubyn_code/autonomous/task_claimer"
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# Layer 12: Memory
|
|
147
|
+
module Memory
|
|
148
|
+
autoload :Store, "rubyn_code/memory/store"
|
|
149
|
+
autoload :Search, "rubyn_code/memory/search"
|
|
150
|
+
autoload :SessionPersistence, "rubyn_code/memory/session_persistence"
|
|
151
|
+
autoload :Models, "rubyn_code/memory/models"
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# Layer 13: Observability
|
|
155
|
+
module Observability
|
|
156
|
+
autoload :TokenCounter, "rubyn_code/observability/token_counter"
|
|
157
|
+
autoload :CostCalculator, "rubyn_code/observability/cost_calculator"
|
|
158
|
+
autoload :BudgetEnforcer, "rubyn_code/observability/budget_enforcer"
|
|
159
|
+
autoload :UsageReporter, "rubyn_code/observability/usage_reporter"
|
|
160
|
+
autoload :Models, "rubyn_code/observability/models"
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# Layer 14: Hooks
|
|
164
|
+
module Hooks
|
|
165
|
+
autoload :Registry, "rubyn_code/hooks/registry"
|
|
166
|
+
autoload :Runner, "rubyn_code/hooks/runner"
|
|
167
|
+
autoload :BuiltIn, "rubyn_code/hooks/built_in"
|
|
168
|
+
autoload :UserHooks, "rubyn_code/hooks/user_hooks"
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# Layer 15: MCP
|
|
172
|
+
module MCP
|
|
173
|
+
autoload :Client, "rubyn_code/mcp/client"
|
|
174
|
+
autoload :StdioTransport, "rubyn_code/mcp/stdio_transport"
|
|
175
|
+
autoload :SSETransport, "rubyn_code/mcp/sse_transport"
|
|
176
|
+
autoload :ToolBridge, "rubyn_code/mcp/tool_bridge"
|
|
177
|
+
autoload :Config, "rubyn_code/mcp/config"
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# Layer 16: Learning
|
|
181
|
+
module Learning
|
|
182
|
+
autoload :Extractor, "rubyn_code/learning/extractor"
|
|
183
|
+
autoload :Instinct, "rubyn_code/learning/instinct"
|
|
184
|
+
autoload :InstinctMethods, "rubyn_code/learning/instinct"
|
|
185
|
+
autoload :Injector, "rubyn_code/learning/injector"
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
# CLI
|
|
189
|
+
module CLI
|
|
190
|
+
autoload :App, "rubyn_code/cli/app"
|
|
191
|
+
autoload :REPL, "rubyn_code/cli/repl"
|
|
192
|
+
autoload :InputHandler, "rubyn_code/cli/input_handler"
|
|
193
|
+
autoload :Renderer, "rubyn_code/cli/renderer"
|
|
194
|
+
autoload :Spinner, "rubyn_code/cli/spinner"
|
|
195
|
+
autoload :StreamFormatter, "rubyn_code/cli/stream_formatter"
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
# Output
|
|
199
|
+
module Output
|
|
200
|
+
autoload :Formatter, "rubyn_code/output/formatter"
|
|
201
|
+
autoload :DiffRenderer, "rubyn_code/output/diff_renderer"
|
|
202
|
+
end
|
|
203
|
+
end
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
# Code Quality: Code That Fits in Your Head
|
|
2
|
+
|
|
3
|
+
## Core Principle
|
|
4
|
+
|
|
5
|
+
The human brain can hold approximately 7 items in working memory at once. Code should be structured so that understanding any single unit (method, class, module) requires holding fewer than 7 concepts simultaneously. When code exceeds working memory limits, bugs creep in because developers can't track all the moving parts.
|
|
6
|
+
|
|
7
|
+
## The 7±2 Rule Applied to Code
|
|
8
|
+
|
|
9
|
+
### Methods: Max 7 Lines of Logic
|
|
10
|
+
|
|
11
|
+
A method should fit in your mental "scratchpad." If you can't hold the entire method in your head at once, it's too complex.
|
|
12
|
+
|
|
13
|
+
```ruby
|
|
14
|
+
# GOOD: 5 concepts — easily fits in working memory
|
|
15
|
+
# 1. Load order 2. Check editable 3. Update 4. Success path 5. Failure path
|
|
16
|
+
def update
|
|
17
|
+
order = current_user.orders.find(params[:id])
|
|
18
|
+
return head :forbidden unless order.editable?
|
|
19
|
+
|
|
20
|
+
if order.update(order_params)
|
|
21
|
+
redirect_to order, notice: "Updated."
|
|
22
|
+
else
|
|
23
|
+
render :edit, status: :unprocessable_entity
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# BAD: 12+ concepts — exceeds working memory
|
|
28
|
+
def update
|
|
29
|
+
order = current_user.orders.find(params[:id])
|
|
30
|
+
return head :forbidden unless order.editable?
|
|
31
|
+
|
|
32
|
+
old_total = order.total
|
|
33
|
+
order.assign_attributes(order_params)
|
|
34
|
+
|
|
35
|
+
order.line_items.each do |item|
|
|
36
|
+
product = Product.find(item.product_id)
|
|
37
|
+
if product.stock < item.quantity
|
|
38
|
+
order.errors.add(:base, "#{product.name} insufficient stock")
|
|
39
|
+
end
|
|
40
|
+
item.unit_price = product.current_price # Price may have changed
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
order.subtotal = order.line_items.sum { |li| li.quantity * li.unit_price }
|
|
44
|
+
order.tax = TaxService.calculate(order.subtotal, order.shipping_address)
|
|
45
|
+
order.total = order.subtotal + order.tax
|
|
46
|
+
|
|
47
|
+
if order.total != old_total && order.paid?
|
|
48
|
+
difference = order.total - old_total
|
|
49
|
+
if difference > 0
|
|
50
|
+
Payments::ChargeService.call(order, difference)
|
|
51
|
+
else
|
|
52
|
+
Payments::RefundService.call(order, difference.abs)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
if order.errors.empty? && order.save
|
|
57
|
+
OrderMailer.updated(order).deliver_later if order.total != old_total
|
|
58
|
+
redirect_to order, notice: "Updated."
|
|
59
|
+
else
|
|
60
|
+
render :edit, status: :unprocessable_entity
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
The second method requires tracking: order, old_total, line_items, products, stock levels, pricing changes, subtotal, tax, total, paid status, payment difference, charge vs refund, email condition, save result. That's 13+ concepts — far beyond working memory.
|
|
66
|
+
|
|
67
|
+
### Classes: Max 7 Public Methods
|
|
68
|
+
|
|
69
|
+
A class with 3-7 public methods is graspable. You can understand its entire interface at a glance. Beyond 7, you start forgetting methods while reading others.
|
|
70
|
+
|
|
71
|
+
```ruby
|
|
72
|
+
# GOOD: 5 public methods — clear, focused interface
|
|
73
|
+
class Order < ApplicationRecord
|
|
74
|
+
# Queries
|
|
75
|
+
scope :recent, -> { where(created_at: 30.days.ago..) }
|
|
76
|
+
scope :pending, -> { where(status: :pending) }
|
|
77
|
+
|
|
78
|
+
# Commands
|
|
79
|
+
def confirm!
|
|
80
|
+
current_state.confirm(self)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def cancel!(reason:)
|
|
84
|
+
current_state.cancel(self, reason: reason)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Queries
|
|
88
|
+
def total
|
|
89
|
+
line_items.sum { |li| li.quantity * li.unit_price }
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def editable?
|
|
93
|
+
pending? || confirmed?
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Parameters: Max 3-4
|
|
99
|
+
|
|
100
|
+
After 4 parameters, callers start confusing argument order, even with keyword arguments. Extract a parameter object or rethink the method's responsibility.
|
|
101
|
+
|
|
102
|
+
```ruby
|
|
103
|
+
# GOOD: 2 parameters — trivially understandable
|
|
104
|
+
def send_notification(user, message)
|
|
105
|
+
|
|
106
|
+
# OK: 4 keyword parameters — manageable with names
|
|
107
|
+
def create_order(user:, items:, address:, payment_method:)
|
|
108
|
+
|
|
109
|
+
# BAD: 7 parameters — nobody can remember these
|
|
110
|
+
def create_order(user:, items:, address:, payment_method:, discount_code:, currency:, notify:)
|
|
111
|
+
|
|
112
|
+
# FIX: Extract a parameter object
|
|
113
|
+
OrderRequest = Data.define(:user, :items, :address, :payment_method, :discount_code, :currency, :notify)
|
|
114
|
+
def create_order(request)
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Nesting: Max 2 Levels
|
|
118
|
+
|
|
119
|
+
Each level of nesting adds a context to track. At 3+ levels, you're juggling too many conditions.
|
|
120
|
+
|
|
121
|
+
```ruby
|
|
122
|
+
# GOOD: 1 level of nesting
|
|
123
|
+
def process(order)
|
|
124
|
+
return error("Empty") if order.line_items.empty?
|
|
125
|
+
return error("Invalid address") unless order.address_valid?
|
|
126
|
+
|
|
127
|
+
charge(order)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# BAD: 4 levels
|
|
131
|
+
def process(order)
|
|
132
|
+
if order.line_items.any?
|
|
133
|
+
if order.address_valid?
|
|
134
|
+
if order.user.payment_method.present?
|
|
135
|
+
if order.user.payment_method.valid?
|
|
136
|
+
charge(order)
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## The Transformation Priority Principle
|
|
145
|
+
|
|
146
|
+
When your code is complex, simplify in this order:
|
|
147
|
+
|
|
148
|
+
1. **Extract till you drop.** Break long methods into smaller ones until each method is 5-7 lines.
|
|
149
|
+
2. **Flatten conditionals.** Use guard clauses to eliminate nesting.
|
|
150
|
+
3. **Replace primitives with objects.** Turn strings and hashes into value objects with named methods.
|
|
151
|
+
4. **Replace branching with polymorphism.** Case statements on type become separate classes.
|
|
152
|
+
5. **Compose small objects.** Large classes become coordinators of small, focused collaborators.
|
|
153
|
+
|
|
154
|
+
## Practical Heuristics
|
|
155
|
+
|
|
156
|
+
### The Squint Test
|
|
157
|
+
Squint at your code. If the indentation forms a deep "V" shape (deep nesting), it needs flattening. If you see large blocks of similar-looking code, it has duplication.
|
|
158
|
+
|
|
159
|
+
### The Headline Test
|
|
160
|
+
Can you describe what a method does in a short headline? "Calculates order total" — good. "Validates, calculates, charges, and notifies" — too many verbs, extract.
|
|
161
|
+
|
|
162
|
+
### The Scroll Test
|
|
163
|
+
If a method or class requires scrolling to read in your editor, it's too long. A method should fit on screen. A class should fit in a few screens.
|
|
164
|
+
|
|
165
|
+
### The Rename Test
|
|
166
|
+
If you can't think of a good name for a method, it probably does too many things. A method that does one thing always has a clear name. "Process" and "handle" are signs of unfocused methods.
|
|
167
|
+
|
|
168
|
+
## When To Apply
|
|
169
|
+
|
|
170
|
+
- **Every code review.** Check: does each method fit in working memory? Can you understand it without scrolling back?
|
|
171
|
+
- **When you re-read code and feel confused.** If you wrote it last week and can't quickly understand it, future-you (and every teammate) will have the same problem.
|
|
172
|
+
- **When modifying existing code.** Before adding a feature to a 50-line method, extract until the method is small, then add the feature to the appropriate extracted method.
|
|
173
|
+
|
|
174
|
+
## When NOT To Apply
|
|
175
|
+
|
|
176
|
+
- **Don't optimize for line count.** A 12-line method that reads clearly is better than 4 methods of 3 lines each where you lose the overall flow.
|
|
177
|
+
- **Don't extract if names don't improve clarity.** `do_step_1`, `do_step_2` are worse than inline code. Extract when the method name adds meaning.
|
|
178
|
+
- **Configuration and setup code is naturally longer.** A Rails initializer or a factory definition doesn't need to be 7 lines. The heuristics apply to logic, not configuration.
|
|
179
|
+
|
|
180
|
+
## Connection to Other Principles
|
|
181
|
+
|
|
182
|
+
This principle underpins everything else:
|
|
183
|
+
- **SRP** keeps classes small enough to fit in your head
|
|
184
|
+
- **Extract Method** keeps methods small enough to fit in your head
|
|
185
|
+
- **Guard Clauses** reduce nesting to fit in your head
|
|
186
|
+
- **Value Objects** replace primitives so you think in domain terms, not data types
|
|
187
|
+
- **Service Objects** keep controllers small enough to fit in your head
|
|
188
|
+
|
|
189
|
+
The goal is always the same: any developer should be able to read a unit of code and fully understand it without external aids, notes, or extensive scrolling.
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
# Code Quality: Naming Conventions
|
|
2
|
+
|
|
3
|
+
## Pattern
|
|
4
|
+
|
|
5
|
+
Good names are the cheapest documentation. A well-named method, class, or variable eliminates the need for comments and makes code read like prose. Ruby and Rails have strong naming conventions — follow them.
|
|
6
|
+
|
|
7
|
+
### Ruby Naming Rules
|
|
8
|
+
|
|
9
|
+
```ruby
|
|
10
|
+
# Classes and modules: CamelCase (PascalCase)
|
|
11
|
+
class OrderProcessor; end
|
|
12
|
+
module Authenticatable; end
|
|
13
|
+
class Api::V1::OrdersController; end
|
|
14
|
+
|
|
15
|
+
# Methods and variables: snake_case
|
|
16
|
+
def calculate_total; end
|
|
17
|
+
total_cents = 19_99
|
|
18
|
+
current_user = User.find(session[:user_id])
|
|
19
|
+
|
|
20
|
+
# Constants: SCREAMING_SNAKE_CASE
|
|
21
|
+
MAX_RETRIES = 3
|
|
22
|
+
DEFAULT_CURRENCY = "USD"
|
|
23
|
+
API_BASE_URL = "https://api.rubyn.ai"
|
|
24
|
+
|
|
25
|
+
# Predicates (boolean methods): end with ?
|
|
26
|
+
def active?; end
|
|
27
|
+
def can_cancel?; end
|
|
28
|
+
def shipped?; end
|
|
29
|
+
# Returns true/false. Never name it `is_active` or `check_active`.
|
|
30
|
+
|
|
31
|
+
# Dangerous methods: end with !
|
|
32
|
+
def save!; end # Raises on failure (vs save which returns false)
|
|
33
|
+
def destroy!; end # Raises on failure
|
|
34
|
+
def normalize!; end # Mutates in place (vs normalize which returns new value)
|
|
35
|
+
# ! means "this version does something surprising" — usually raises or mutates.
|
|
36
|
+
|
|
37
|
+
# Setters: end with =
|
|
38
|
+
def name=(value); end
|
|
39
|
+
attr_writer :name # Generates name= method
|
|
40
|
+
|
|
41
|
+
# Private/internal: prefix with _ (convention, not enforced)
|
|
42
|
+
def _build_cache_key; end
|
|
43
|
+
_temp_value = compute_intermediate_step
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Method Naming
|
|
47
|
+
|
|
48
|
+
```ruby
|
|
49
|
+
# GOOD: Verb for actions, noun for queries
|
|
50
|
+
def create_order(params); end # Action: does something
|
|
51
|
+
def send_confirmation(order); end # Action: does something
|
|
52
|
+
def total; end # Query: returns a value
|
|
53
|
+
def line_items; end # Query: returns a collection
|
|
54
|
+
def pending?; end # Predicate: returns boolean
|
|
55
|
+
|
|
56
|
+
# GOOD: Specific verbs that communicate intent
|
|
57
|
+
def charge_payment(order); end # Specific: charges
|
|
58
|
+
def validate_inventory(items); end # Specific: validates
|
|
59
|
+
def generate_reference; end # Specific: generates
|
|
60
|
+
|
|
61
|
+
# BAD: Vague verbs that could mean anything
|
|
62
|
+
def process(order); end # Process how? Does it charge? Ship? Validate?
|
|
63
|
+
def handle(data); end # Handle what exactly?
|
|
64
|
+
def do_stuff; end # Meaningless
|
|
65
|
+
def run; end # What does it run?
|
|
66
|
+
def execute; end # Same problem
|
|
67
|
+
|
|
68
|
+
# GOOD: call for service objects (convention)
|
|
69
|
+
class Orders::CreateService
|
|
70
|
+
def self.call(params, user); end # .call is the Rails service object convention
|
|
71
|
+
end
|
|
72
|
+
# This works because the CLASS NAME describes the action (CreateService)
|
|
73
|
+
# so .call doesn't need to be more specific
|
|
74
|
+
|
|
75
|
+
# GOOD: Named constructors for clarity
|
|
76
|
+
Order.from_cart(cart, user: current_user)
|
|
77
|
+
Money.from_dollars(19.99)
|
|
78
|
+
DateRange.parse("2026-01-01..2026-03-20")
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Class Naming
|
|
82
|
+
|
|
83
|
+
```ruby
|
|
84
|
+
# GOOD: Noun describing what it IS
|
|
85
|
+
class Order; end
|
|
86
|
+
class LineItem; end
|
|
87
|
+
class User; end
|
|
88
|
+
|
|
89
|
+
# GOOD: Adjective or role for modules (describes capability)
|
|
90
|
+
module Searchable; end
|
|
91
|
+
module Authenticatable; end
|
|
92
|
+
module Sluggable; end
|
|
93
|
+
|
|
94
|
+
# GOOD: Noun + purpose for service objects
|
|
95
|
+
class Orders::CreateService; end
|
|
96
|
+
class Credits::DeductionService; end
|
|
97
|
+
class Embeddings::CodebaseIndexer; end
|
|
98
|
+
|
|
99
|
+
# GOOD: Noun + type for specific patterns
|
|
100
|
+
class OrderPresenter; end # Presenter
|
|
101
|
+
class RegistrationForm; end # Form object
|
|
102
|
+
class OrdersSearchQuery; end # Query object
|
|
103
|
+
class StripeAdapter; end # Adapter
|
|
104
|
+
class EmailNotifier; end # Notifier
|
|
105
|
+
|
|
106
|
+
# BAD: Manager, Handler, Processor, Helper (vague — WHAT does it manage?)
|
|
107
|
+
class OrderManager; end # What aspect of orders?
|
|
108
|
+
class DataHandler; end # What data? What handling?
|
|
109
|
+
class OrderProcessor; end # Processes how?
|
|
110
|
+
class OrderHelper; end # Helps with what?
|
|
111
|
+
|
|
112
|
+
# FIX: Name the specific responsibility
|
|
113
|
+
class OrderManager → Orders::CreateService + Orders::CancelService
|
|
114
|
+
class DataHandler → CsvImporter + JsonParser
|
|
115
|
+
class OrderProcessor → Orders::FulfillmentService
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Variable Naming
|
|
119
|
+
|
|
120
|
+
```ruby
|
|
121
|
+
# GOOD: Descriptive, matches the domain
|
|
122
|
+
active_users = User.where(active: true)
|
|
123
|
+
pending_orders = Order.pending
|
|
124
|
+
credit_balance = user.credit_ledger_entries.sum(:amount)
|
|
125
|
+
shipping_address = order.addresses.find_by(type: "shipping")
|
|
126
|
+
|
|
127
|
+
# GOOD: Plural for collections, singular for individuals
|
|
128
|
+
orders = Order.recent # Collection
|
|
129
|
+
order = orders.first # Single item
|
|
130
|
+
line_items = order.line_items # Collection
|
|
131
|
+
line_item = line_items.first # Single item
|
|
132
|
+
|
|
133
|
+
# BAD: Single-letter variables (except in tiny blocks)
|
|
134
|
+
u = User.find(params[:id]) # What's u?
|
|
135
|
+
o = u.orders.last # What's o?
|
|
136
|
+
x = o.total * 0.08 # What's x?
|
|
137
|
+
|
|
138
|
+
# OK: Single-letter in small, obvious blocks
|
|
139
|
+
users.map { |u| u.email } # OK — the block is 1 line, u is clearly a user
|
|
140
|
+
users.map(&:email) # BETTER — no variable needed
|
|
141
|
+
|
|
142
|
+
# BAD: Misleading names
|
|
143
|
+
user_count = User.pluck(:email) # It's emails, not a count
|
|
144
|
+
is_valid = order.save # It's a save result, not a validation check
|
|
145
|
+
temp = Order.where(status: :pending) # "temp" tells you nothing
|
|
146
|
+
|
|
147
|
+
# BAD: Hungarian notation or type prefixes
|
|
148
|
+
str_name = "Alice" # Ruby doesn't need type prefixes
|
|
149
|
+
arr_items = [1, 2, 3]
|
|
150
|
+
int_count = 5
|
|
151
|
+
hash_config = { timeout: 30 }
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### Rails-Specific Conventions
|
|
155
|
+
|
|
156
|
+
```ruby
|
|
157
|
+
# Controllers: plural noun + Controller
|
|
158
|
+
class OrdersController; end # Not OrderController
|
|
159
|
+
class Api::V1::UsersController; end
|
|
160
|
+
|
|
161
|
+
# Models: singular noun
|
|
162
|
+
class Order; end # Not Orders
|
|
163
|
+
class LineItem; end # Not LineItems
|
|
164
|
+
|
|
165
|
+
# Tables: plural snake_case (matches model pluralized)
|
|
166
|
+
# orders, line_items, users, credit_ledger_entries
|
|
167
|
+
|
|
168
|
+
# Foreign keys: singular_model_id
|
|
169
|
+
# order_id, user_id, product_id
|
|
170
|
+
|
|
171
|
+
# Join tables: alphabetical, both pluralized
|
|
172
|
+
# orders_products, categories_products
|
|
173
|
+
|
|
174
|
+
# Migrations: verb + noun
|
|
175
|
+
class AddStatusToOrders; end
|
|
176
|
+
class CreateProjectMemberships; end
|
|
177
|
+
class RemoveDeletedAtFromOrders; end
|
|
178
|
+
|
|
179
|
+
# Jobs: noun + Job
|
|
180
|
+
class OrderConfirmationJob; end
|
|
181
|
+
class CodebaseIndexJob; end
|
|
182
|
+
|
|
183
|
+
# Mailers: noun + Mailer
|
|
184
|
+
class OrderMailer; end
|
|
185
|
+
class UserMailer; end
|
|
186
|
+
|
|
187
|
+
# Serializers: singular model + Serializer
|
|
188
|
+
class OrderSerializer; end
|
|
189
|
+
|
|
190
|
+
# Factories: plural snake_case (matching table)
|
|
191
|
+
# spec/factories/orders.rb
|
|
192
|
+
# spec/factories/line_items.rb
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
## Why This Is Good
|
|
196
|
+
|
|
197
|
+
- **Convention eliminates decisions.** When every controller is `PluralNounController` and every service is `Noun::VerbService`, developers don't waste time choosing names. The convention chooses for them.
|
|
198
|
+
- **Names replace comments.** `calculate_shipping_cost` doesn't need a comment explaining what it does. `process` does.
|
|
199
|
+
- **Predictable file locations.** `Orders::CreateService` lives at `app/services/orders/create_service.rb`. The name maps to the path. You can find any class without grep.
|
|
200
|
+
- **`?` and `!` suffixes communicate behavior.** `save` returns false on failure. `save!` raises. The developer knows what to expect from the suffix alone.
|
|
201
|
+
|
|
202
|
+
## The Rename Test
|
|
203
|
+
|
|
204
|
+
If you can't think of a good name for a method or class, it probably does too many things. A method that does one thing always has a clear name.
|
|
205
|
+
|
|
206
|
+
- ❌ `process_order` → What processing? Creating? Shipping? Billing? All three?
|
|
207
|
+
- ✅ `Orders::CreateService`, `Orders::ShipService`, `Orders::BillService`
|
|
208
|
+
|
|
209
|
+
- ❌ `handle_response` → Handle how?
|
|
210
|
+
- ✅ `parse_json_response`, `validate_response_status`, `extract_order_from_response`
|
|
211
|
+
|
|
212
|
+
- ❌ `UserManager` → Manages what about users?
|
|
213
|
+
- ✅ `Users::RegistrationService`, `Users::ProfileUpdater`, `Users::DeactivationService`
|