anima-core 0.3.0 → 1.0.1

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.
Files changed (270) hide show
  1. checksums.yaml +4 -4
  2. data/.reek.yml +27 -1
  3. data/CHANGELOG.md +4 -0
  4. data/README.md +219 -25
  5. data/agents/codebase-analyzer.md +88 -0
  6. data/agents/codebase-pattern-finder.md +83 -0
  7. data/agents/documentation-researcher.md +59 -0
  8. data/agents/thoughts-analyzer.md +102 -0
  9. data/agents/web-search-researcher.md +71 -0
  10. data/anima-core.gemspec +4 -1
  11. data/app/channels/session_channel.rb +76 -28
  12. data/app/jobs/agent_request_job.rb +24 -0
  13. data/app/jobs/analytical_brain_job.rb +33 -0
  14. data/app/jobs/count_event_tokens_job.rb +1 -1
  15. data/app/models/concerns/event/broadcasting.rb +20 -2
  16. data/app/models/event.rb +1 -1
  17. data/app/models/goal.rb +91 -0
  18. data/app/models/session.rb +347 -22
  19. data/config/application.rb +2 -0
  20. data/db/migrate/20260314075248_add_subagent_support_to_sessions.rb +6 -0
  21. data/db/migrate/20260314112417_add_granted_tools_to_sessions.rb +5 -0
  22. data/db/migrate/20260314140000_add_name_to_sessions.rb +7 -0
  23. data/db/migrate/20260314150000_add_viewport_event_ids_to_sessions.rb +7 -0
  24. data/db/migrate/20260315100000_add_active_skills_to_sessions.rb +7 -0
  25. data/db/migrate/20260315140843_create_goals.rb +16 -0
  26. data/db/migrate/20260315144837_add_completed_at_to_goals.rb +5 -0
  27. data/db/migrate/20260315191105_add_active_workflow_to_sessions.rb +5 -0
  28. data/lib/agent_loop.rb +65 -9
  29. data/lib/agents/definition.rb +116 -0
  30. data/lib/agents/registry.rb +106 -0
  31. data/lib/analytical_brain/runner.rb +276 -0
  32. data/lib/analytical_brain/tools/activate_skill.rb +52 -0
  33. data/lib/analytical_brain/tools/deactivate_skill.rb +43 -0
  34. data/lib/analytical_brain/tools/deactivate_workflow.rb +34 -0
  35. data/lib/analytical_brain/tools/everything_is_ready.rb +28 -0
  36. data/lib/analytical_brain/tools/finish_goal.rb +62 -0
  37. data/lib/analytical_brain/tools/read_workflow.rb +58 -0
  38. data/lib/analytical_brain/tools/rename_session.rb +63 -0
  39. data/lib/analytical_brain/tools/set_goal.rb +60 -0
  40. data/lib/analytical_brain/tools/update_goal.rb +60 -0
  41. data/lib/analytical_brain.rb +23 -0
  42. data/lib/anima/cli/mcp/secrets.rb +76 -0
  43. data/lib/anima/cli/mcp.rb +197 -0
  44. data/lib/anima/cli.rb +4 -0
  45. data/lib/anima/installer.rb +182 -6
  46. data/lib/anima/settings.rb +226 -0
  47. data/lib/anima/version.rb +1 -1
  48. data/lib/anima.rb +9 -0
  49. data/lib/credential_store.rb +103 -0
  50. data/lib/environment_probe.rb +232 -0
  51. data/lib/llm/client.rb +29 -10
  52. data/lib/mcp/client_manager.rb +86 -0
  53. data/lib/mcp/config.rb +213 -0
  54. data/lib/mcp/health_check.rb +77 -0
  55. data/lib/mcp/secrets.rb +73 -0
  56. data/lib/mcp/stdio_transport.rb +206 -0
  57. data/lib/providers/anthropic.rb +8 -7
  58. data/lib/shell_session.rb +11 -10
  59. data/lib/skills/definition.rb +97 -0
  60. data/lib/skills/registry.rb +105 -0
  61. data/lib/tools/edit.rb +3 -4
  62. data/lib/tools/mcp_tool.rb +114 -0
  63. data/lib/tools/read.rb +15 -16
  64. data/lib/tools/registry.rb +14 -12
  65. data/lib/tools/request_feature.rb +121 -0
  66. data/lib/tools/return_result.rb +81 -0
  67. data/lib/tools/spawn_specialist.rb +109 -0
  68. data/lib/tools/spawn_subagent.rb +111 -0
  69. data/lib/tools/subagent_prompts.rb +12 -0
  70. data/lib/tools/web_get.rb +8 -9
  71. data/lib/tui/app.rb +332 -43
  72. data/lib/tui/message_store.rb +20 -0
  73. data/lib/tui/screens/chat.rb +207 -20
  74. data/lib/workflows/definition.rb +97 -0
  75. data/lib/workflows/registry.rb +89 -0
  76. data/skills/activerecord/SKILL.md +255 -0
  77. data/skills/activerecord/examples/associations/association_extensions.rb +298 -0
  78. data/skills/activerecord/examples/associations/basic_associations.rb +118 -0
  79. data/skills/activerecord/examples/associations/counter_caches.rb +215 -0
  80. data/skills/activerecord/examples/associations/polymorphic_associations.rb +217 -0
  81. data/skills/activerecord/examples/associations/self_referential.rb +302 -0
  82. data/skills/activerecord/examples/associations/through_associations.rb +203 -0
  83. data/skills/activerecord/examples/basics/crud_operations.rb +209 -0
  84. data/skills/activerecord/examples/basics/dirty_tracking.rb +218 -0
  85. data/skills/activerecord/examples/basics/inheritance.rb +377 -0
  86. data/skills/activerecord/examples/basics/type_casting.rb +317 -0
  87. data/skills/activerecord/examples/callbacks/alternatives_to_callbacks.rb +447 -0
  88. data/skills/activerecord/examples/callbacks/conditional_callbacks.rb +353 -0
  89. data/skills/activerecord/examples/callbacks/lifecycle_callbacks.rb +280 -0
  90. data/skills/activerecord/examples/callbacks/transaction_callbacks.rb +340 -0
  91. data/skills/activerecord/examples/migrations/indexes_and_constraints.rb +337 -0
  92. data/skills/activerecord/examples/migrations/reversible_patterns.rb +403 -0
  93. data/skills/activerecord/examples/migrations/safe_patterns.rb +420 -0
  94. data/skills/activerecord/examples/migrations/schema_changes.rb +277 -0
  95. data/skills/activerecord/examples/querying/batch_processing.rb +226 -0
  96. data/skills/activerecord/examples/querying/eager_loading.rb +259 -0
  97. data/skills/activerecord/examples/querying/finder_methods.rb +170 -0
  98. data/skills/activerecord/examples/querying/optimization.rb +275 -0
  99. data/skills/activerecord/examples/querying/scopes.rb +260 -0
  100. data/skills/activerecord/examples/validations/built_in_validators.rb +277 -0
  101. data/skills/activerecord/examples/validations/conditional_validations.rb +288 -0
  102. data/skills/activerecord/examples/validations/custom_validators.rb +381 -0
  103. data/skills/activerecord/examples/validations/database_constraints.rb +432 -0
  104. data/skills/activerecord/examples/validations/validation_contexts.rb +367 -0
  105. data/skills/activerecord/references/associations.md +709 -0
  106. data/skills/activerecord/references/basics.md +622 -0
  107. data/skills/activerecord/references/callbacks.md +738 -0
  108. data/skills/activerecord/references/migrations.md +657 -0
  109. data/skills/activerecord/references/querying.md +655 -0
  110. data/skills/activerecord/references/validations.md +596 -0
  111. data/skills/dragonruby/SKILL.md +250 -0
  112. data/skills/dragonruby/examples/audio/audio_events.rb +55 -0
  113. data/skills/dragonruby/examples/audio/background_music.rb +29 -0
  114. data/skills/dragonruby/examples/audio/crossfade.rb +51 -0
  115. data/skills/dragonruby/examples/audio/music_controls.rb +51 -0
  116. data/skills/dragonruby/examples/audio/sound_effects.rb +30 -0
  117. data/skills/dragonruby/examples/core/coordinate_system.rb +27 -0
  118. data/skills/dragonruby/examples/core/hello_world.rb +24 -0
  119. data/skills/dragonruby/examples/core/labels.rb +22 -0
  120. data/skills/dragonruby/examples/core/sprites.rb +35 -0
  121. data/skills/dragonruby/examples/core/state_management.rb +29 -0
  122. data/skills/dragonruby/examples/distribution/background_pause.rb +42 -0
  123. data/skills/dragonruby/examples/distribution/build_workflow.sh +26 -0
  124. data/skills/dragonruby/examples/distribution/cvars_production.txt +16 -0
  125. data/skills/dragonruby/examples/distribution/game_metadata_hd.txt +23 -0
  126. data/skills/dragonruby/examples/distribution/game_metadata_minimal.txt +9 -0
  127. data/skills/dragonruby/examples/distribution/game_metadata_mobile.txt +31 -0
  128. data/skills/dragonruby/examples/distribution/platform_detection.rb +36 -0
  129. data/skills/dragonruby/examples/distribution/steam_metadata.txt +19 -0
  130. data/skills/dragonruby/examples/entities/collision_detection.rb +43 -0
  131. data/skills/dragonruby/examples/entities/entity_lifecycle.rb +68 -0
  132. data/skills/dragonruby/examples/entities/entity_storage.rb +38 -0
  133. data/skills/dragonruby/examples/entities/factory_methods.rb +45 -0
  134. data/skills/dragonruby/examples/entities/random_spawning.rb +50 -0
  135. data/skills/dragonruby/examples/game-logic/reset_patterns.rb +98 -0
  136. data/skills/dragonruby/examples/game-logic/save_load.rb +101 -0
  137. data/skills/dragonruby/examples/game-logic/scoring.rb +104 -0
  138. data/skills/dragonruby/examples/game-logic/state_transitions.rb +103 -0
  139. data/skills/dragonruby/examples/game-logic/timers.rb +87 -0
  140. data/skills/dragonruby/examples/input/action_triggers.rb +36 -0
  141. data/skills/dragonruby/examples/input/analog_movement.rb +28 -0
  142. data/skills/dragonruby/examples/input/controller_input.rb +28 -0
  143. data/skills/dragonruby/examples/input/directional_input.rb +24 -0
  144. data/skills/dragonruby/examples/input/keyboard_input.rb +28 -0
  145. data/skills/dragonruby/examples/input/mouse_click.rb +26 -0
  146. data/skills/dragonruby/examples/input/movement_with_bounds.rb +22 -0
  147. data/skills/dragonruby/examples/input/normalized_movement.rb +32 -0
  148. data/skills/dragonruby/examples/rendering/frame_animation.rb +32 -0
  149. data/skills/dragonruby/examples/rendering/labels.rb +32 -0
  150. data/skills/dragonruby/examples/rendering/layering.rb +51 -0
  151. data/skills/dragonruby/examples/rendering/solids.rb +61 -0
  152. data/skills/dragonruby/examples/rendering/sprites.rb +33 -0
  153. data/skills/dragonruby/examples/rendering/spritesheet_animation.rb +39 -0
  154. data/skills/dragonruby/examples/scenes/case_dispatch.rb +60 -0
  155. data/skills/dragonruby/examples/scenes/class_based.rb +150 -0
  156. data/skills/dragonruby/examples/scenes/pause_overlay.rb +100 -0
  157. data/skills/dragonruby/examples/scenes/safe_transitions.rb +68 -0
  158. data/skills/dragonruby/examples/scenes/scene_transitions.rb +98 -0
  159. data/skills/dragonruby/examples/scenes/send_dispatch.rb +88 -0
  160. data/skills/dragonruby/references/audio.md +396 -0
  161. data/skills/dragonruby/references/core.md +385 -0
  162. data/skills/dragonruby/references/distribution.md +434 -0
  163. data/skills/dragonruby/references/entities.md +516 -0
  164. data/skills/dragonruby/references/game-logic/persistence.md +386 -0
  165. data/skills/dragonruby/references/game-logic/state.md +389 -0
  166. data/skills/dragonruby/references/input.md +414 -0
  167. data/skills/dragonruby/references/rendering/animation.md +467 -0
  168. data/skills/dragonruby/references/rendering/primitives.md +403 -0
  169. data/skills/dragonruby/references/scenes.md +443 -0
  170. data/skills/draper-decorators/SKILL.md +344 -0
  171. data/skills/draper-decorators/examples/application_decorator.rb +61 -0
  172. data/skills/draper-decorators/examples/decorator_spec.rb +253 -0
  173. data/skills/draper-decorators/examples/model_decorator.rb +152 -0
  174. data/skills/draper-decorators/references/anti-patterns.md +640 -0
  175. data/skills/draper-decorators/references/patterns.md +507 -0
  176. data/skills/draper-decorators/references/testing.md +559 -0
  177. data/skills/gh-issue.md +182 -0
  178. data/skills/mcp-server/SKILL.md +177 -0
  179. data/skills/mcp-server/examples/dynamic_tools.rb +36 -0
  180. data/skills/mcp-server/examples/file_manager_tool.rb +85 -0
  181. data/skills/mcp-server/examples/http_client.rb +48 -0
  182. data/skills/mcp-server/examples/http_server.rb +97 -0
  183. data/skills/mcp-server/examples/rails_integration.rb +88 -0
  184. data/skills/mcp-server/examples/stdio_server.rb +108 -0
  185. data/skills/mcp-server/examples/streaming_client.rb +95 -0
  186. data/skills/mcp-server/references/gotchas.md +183 -0
  187. data/skills/mcp-server/references/prompts.md +98 -0
  188. data/skills/mcp-server/references/resources.md +53 -0
  189. data/skills/mcp-server/references/server.md +140 -0
  190. data/skills/mcp-server/references/tools.md +146 -0
  191. data/skills/mcp-server/references/transport.md +104 -0
  192. data/skills/ratatui-ruby/SKILL.md +315 -0
  193. data/skills/ratatui-ruby/references/core-concepts.md +340 -0
  194. data/skills/ratatui-ruby/references/events.md +387 -0
  195. data/skills/ratatui-ruby/references/frameworks.md +522 -0
  196. data/skills/ratatui-ruby/references/layout.md +423 -0
  197. data/skills/ratatui-ruby/references/styling.md +268 -0
  198. data/skills/ratatui-ruby/references/testing.md +433 -0
  199. data/skills/ratatui-ruby/references/widgets.md +532 -0
  200. data/skills/rspec/SKILL.md +340 -0
  201. data/skills/rspec/examples/core/basic_structure.rb +69 -0
  202. data/skills/rspec/examples/core/configuration.rb +126 -0
  203. data/skills/rspec/examples/core/hooks.rb +126 -0
  204. data/skills/rspec/examples/core/memoized_helpers.rb +139 -0
  205. data/skills/rspec/examples/core/metadata_filtering.rb +144 -0
  206. data/skills/rspec/examples/core/shared_examples.rb +145 -0
  207. data/skills/rspec/examples/factory_bot/associations.rb +314 -0
  208. data/skills/rspec/examples/factory_bot/build_strategies.rb +272 -0
  209. data/skills/rspec/examples/factory_bot/callbacks.rb +320 -0
  210. data/skills/rspec/examples/factory_bot/custom_construction.rb +328 -0
  211. data/skills/rspec/examples/factory_bot/factory_definition.rb +191 -0
  212. data/skills/rspec/examples/factory_bot/inheritance.rb +314 -0
  213. data/skills/rspec/examples/factory_bot/traits.rb +293 -0
  214. data/skills/rspec/examples/factory_bot/transients.rb +229 -0
  215. data/skills/rspec/examples/matchers/change.rb +115 -0
  216. data/skills/rspec/examples/matchers/collections.rb +154 -0
  217. data/skills/rspec/examples/matchers/comparisons.rb +79 -0
  218. data/skills/rspec/examples/matchers/composing.rb +155 -0
  219. data/skills/rspec/examples/matchers/custom_matchers.rb +197 -0
  220. data/skills/rspec/examples/matchers/equality.rb +58 -0
  221. data/skills/rspec/examples/matchers/errors.rb +136 -0
  222. data/skills/rspec/examples/matchers/output.rb +103 -0
  223. data/skills/rspec/examples/matchers/predicates.rb +87 -0
  224. data/skills/rspec/examples/matchers/truthiness.rb +101 -0
  225. data/skills/rspec/examples/matchers/types.rb +82 -0
  226. data/skills/rspec/examples/matchers/yield.rb +147 -0
  227. data/skills/rspec/examples/mocks/any_instance.rb +172 -0
  228. data/skills/rspec/examples/mocks/argument_matchers.rb +206 -0
  229. data/skills/rspec/examples/mocks/constants.rb +177 -0
  230. data/skills/rspec/examples/mocks/doubles.rb +139 -0
  231. data/skills/rspec/examples/mocks/expectations.rb +137 -0
  232. data/skills/rspec/examples/mocks/message_chains.rb +173 -0
  233. data/skills/rspec/examples/mocks/ordering.rb +144 -0
  234. data/skills/rspec/examples/mocks/receive_counts.rb +181 -0
  235. data/skills/rspec/examples/mocks/responses.rb +223 -0
  236. data/skills/rspec/examples/mocks/spies.rb +149 -0
  237. data/skills/rspec/examples/mocks/stubbing.rb +133 -0
  238. data/skills/rspec/examples/rails/channels.rb +250 -0
  239. data/skills/rspec/examples/rails/controller_specs.rb +302 -0
  240. data/skills/rspec/examples/rails/helper_specs.rb +245 -0
  241. data/skills/rspec/examples/rails/job_specs.rb +256 -0
  242. data/skills/rspec/examples/rails/mailer_specs.rb +228 -0
  243. data/skills/rspec/examples/rails/matchers.rb +374 -0
  244. data/skills/rspec/examples/rails/model_specs.rb +193 -0
  245. data/skills/rspec/examples/rails/request_specs.rb +275 -0
  246. data/skills/rspec/examples/rails/routing_specs.rb +276 -0
  247. data/skills/rspec/examples/rails/system_specs.rb +294 -0
  248. data/skills/rspec/examples/rails/transactions.rb +254 -0
  249. data/skills/rspec/examples/rails/view_specs.rb +252 -0
  250. data/skills/rspec/references/core.md +816 -0
  251. data/skills/rspec/references/factory_bot.md +641 -0
  252. data/skills/rspec/references/matchers.md +516 -0
  253. data/skills/rspec/references/mocks.md +381 -0
  254. data/skills/rspec/references/rails.md +528 -0
  255. data/templates/soul.md +40 -0
  256. data/workflows/commit.md +45 -0
  257. data/workflows/create_handoff.md +98 -0
  258. data/workflows/create_note.md +82 -0
  259. data/workflows/create_plan.md +457 -0
  260. data/workflows/decompose_ticket.md +109 -0
  261. data/workflows/feature.md +91 -0
  262. data/workflows/implement_plan.md +87 -0
  263. data/workflows/iterate_plan.md +247 -0
  264. data/workflows/research_codebase.md +210 -0
  265. data/workflows/resume_handoff.md +217 -0
  266. data/workflows/review_pr.md +320 -0
  267. data/workflows/thoughts_init.md +71 -0
  268. data/workflows/validate_plan.md +166 -0
  269. metadata +284 -2
  270. data/.mise.toml +0 -2
@@ -0,0 +1,816 @@
1
+ # RSpec Core Reference
2
+
3
+ Comprehensive reference for RSpec's core DSL, configuration, and structure.
4
+
5
+ ## Example Groups and Examples
6
+
7
+ ### describe / context
8
+
9
+ Example groups are classes that inherit from `RSpec::Core::ExampleGroup`:
10
+
11
+ ```ruby
12
+ RSpec.describe Order do
13
+ context "with no items" do
14
+ # nested context - creates a subclass
15
+ end
16
+ end
17
+ ```
18
+
19
+ - `describe` and `context` are aliases
20
+ - Blocks are evaluated eagerly when spec file loads
21
+ - Nested groups inherit from parent groups
22
+
23
+ ### it / specify / example
24
+
25
+ Individual test cases:
26
+
27
+ ```ruby
28
+ it "sums the prices" do
29
+ expect(order.total).to eq(5.55)
30
+ end
31
+
32
+ # Variants with metadata:
33
+ fit "focused example" # :focus => true
34
+ xit "skipped example" # :skip => 'Temporarily skipped'
35
+ pending "pending example" # :pending => true
36
+ ```
37
+
38
+ ## Hooks
39
+
40
+ ### before / after
41
+
42
+ ```ruby
43
+ before(:example) { } # runs before each example (alias: :each)
44
+ before(:context) { } # runs once before all examples (alias: :all)
45
+ before(:suite) { } # runs once before entire suite (only in RSpec.configure)
46
+
47
+ after(:example) { } # runs after each example
48
+ after(:context) { } # runs after all examples in group
49
+ after(:suite) { } # runs after entire suite
50
+ ```
51
+
52
+ ### Execution Order
53
+
54
+ ```
55
+ before(:suite) # RSpec.configure
56
+ before(:context) # RSpec.configure
57
+ before(:context) # parent group
58
+ before(:context) # current group
59
+ before(:example) # RSpec.configure
60
+ before(:example) # parent group
61
+ before(:example) # current group
62
+ # example runs
63
+ after(:example) # reverse order
64
+ after(:context) # reverse order
65
+ after(:suite) # reverse order
66
+ ```
67
+
68
+ ### Conditional Hooks
69
+
70
+ ```ruby
71
+ RSpec.configure do |config|
72
+ config.before(:example, :authorized => true) do
73
+ log_in_as :authorized_user
74
+ end
75
+ end
76
+
77
+ RSpec.describe Something, :authorized => true do
78
+ # before hook runs here
79
+ end
80
+ ```
81
+
82
+ ### Constraints
83
+
84
+ - `before(:context)` shares state via instance variables (ordering dependencies risk)
85
+ - `let`, `subject`, mocks/stubs NOT supported in `before(:context)`
86
+ - Database transactions expect `:example` scope
87
+
88
+ ### around
89
+
90
+ ```ruby
91
+ around(:example) do |example|
92
+ DatabaseCleaner.cleaning do
93
+ example.run
94
+ end
95
+ end
96
+ ```
97
+
98
+ ## Memoized Helpers
99
+
100
+ ### Why let Over Instance Variables
101
+
102
+ Instance variables in RSpec create critical problems:
103
+
104
+ ```ruby
105
+ # BAD - typo goes unnoticed, @usre returns nil silently
106
+ before { @user = create(:user) }
107
+ it "does something" do
108
+ expect(@usre.name).to eq("Alice") # nil.name raises NoMethodError
109
+ end
110
+
111
+ # GOOD - typo raises NameError immediately
112
+ let(:user) { create(:user) }
113
+ it "does something" do
114
+ expect(usre.name).to eq("Alice") # NameError: undefined local variable
115
+ end
116
+ ```
117
+
118
+ Problems with instance variables:
119
+ - **Silent failures**: Undefined instance variables return `nil` without errors
120
+ - **State leakage**: Can leak between test files and examples
121
+ - **No lazy evaluation**: Always executed in before blocks
122
+
123
+ ### Why let Over Helper Methods
124
+
125
+ Always prefer `let`:
126
+ - **Memoization**: Cached within example, prevents redundant queries
127
+ - **Lazy evaluation**: Only executes when referenced
128
+ - **Overridable**: Redefine in nested contexts with `super()`
129
+ - **Type safety**: Typos raise `NameError` immediately
130
+
131
+ Use helper methods only when:
132
+ - A `let` would only be used inside another `let` definition (chain of lets)
133
+ - Setup needs parameters that vary per call within the same example
134
+
135
+ ```ruby
136
+ # Always use let for test dependencies
137
+ let(:user) { create(:user) }
138
+ let(:post) { create(:post, author: user) }
139
+ let(:comment) { create(:comment, post:, author: user) }
140
+
141
+ # Helper method: parameterized setup called multiple times in one example
142
+ def create_order_with_items(count)
143
+ order = Order.new
144
+ count.times { order.add_item(build(:item)) }
145
+ order
146
+ end
147
+
148
+ it "compares orders of different sizes" do
149
+ small_order = create_order_with_items(2)
150
+ large_order = create_order_with_items(10)
151
+ expect(large_order.total).to be > small_order.total
152
+ end
153
+ ```
154
+
155
+ ### let
156
+
157
+ Lazily-evaluated, memoized helper:
158
+
159
+ ```ruby
160
+ let(:user) { create(:user) }
161
+
162
+ it "uses user" do
163
+ user # evaluated here
164
+ user # returns same instance
165
+ end
166
+ ```
167
+
168
+ Characteristics:
169
+ - Lazy evaluation: not invoked until first reference
170
+ - Memoized within an example (multiple calls return same value)
171
+ - NOT cached across examples (fresh for each example)
172
+ - Thread-safe by default
173
+
174
+ ### let!
175
+
176
+ Same as `let` but evaluated in implicit `before` hook:
177
+
178
+ ```ruby
179
+ let!(:user) { create(:user) } # evaluated before each example
180
+ ```
181
+
182
+ Use when:
183
+ - Side effects needed before example runs
184
+ - Data must exist for database queries/scopes
185
+ - Records needed for associations
186
+
187
+ ```ruby
188
+ # let! needed - testing scope that queries database
189
+ let!(:active_user) { create(:user, status: :active) }
190
+ let!(:inactive_user) { create(:user, status: :inactive) }
191
+
192
+ it "finds only active users" do
193
+ expect(User.active).to contain_exactly(active_user)
194
+ end
195
+ ```
196
+
197
+ ### let vs let! Decision Guide
198
+
199
+ | Use `let` when | Use `let!` when |
200
+ |----------------|-----------------|
201
+ | Value used in test body | Side effects needed before test |
202
+ | Not all examples use value | Testing database scopes/queries |
203
+ | Performance matters | Records must exist for count checks |
204
+
205
+ **Anti-pattern**: Using `let!` when `let` suffices:
206
+ ```ruby
207
+ # Bad - creates data unnecessarily
208
+ let!(:admin) { create(:user, :admin) }
209
+
210
+ it "validates email format" do
211
+ user = build(:user, email: "invalid")
212
+ expect(user).not_to be_valid
213
+ # admin created but never used!
214
+ end
215
+ ```
216
+
217
+ ### Overriding let in Nested Contexts
218
+
219
+ ```ruby
220
+ let(:discount) { 0 }
221
+
222
+ it "calculates full price" do
223
+ expect(cart.total).to eq(100)
224
+ end
225
+
226
+ context "with discount" do
227
+ let(:discount) { 20 } # Overrides parent
228
+
229
+ it "applies discount" do
230
+ expect(cart.total).to eq(80)
231
+ end
232
+ end
233
+ ```
234
+
235
+ **Using super() to extend parent values**:
236
+
237
+ ```ruby
238
+ let(:params) { { name: "Item", price: 10 } }
239
+
240
+ context "with discount" do
241
+ let(:params) { super().merge(discount: 2) } # Must use super() with parens
242
+ end
243
+ ```
244
+
245
+ ### subject
246
+
247
+ Implicit or explicit test target:
248
+
249
+ ```ruby
250
+ # Implicit - creates instance of described class
251
+ RSpec.describe Array do
252
+ it { is_expected.to be_empty } # subject is Array.new
253
+ end
254
+
255
+ # Explicit
256
+ subject { [1, 2, 3] }
257
+
258
+ # Named subject (creates both subject and named method)
259
+ subject(:account) { CheckingAccount.new(50) }
260
+ ```
261
+
262
+ One-liner syntax:
263
+ - `is_expected` wraps subject in `expect(subject)`
264
+ - `should` / `should_not` (legacy syntax)
265
+
266
+ ### Named Subject Best Practices
267
+
268
+ Always use named subject when referencing in tests:
269
+
270
+ ```ruby
271
+ # BAD - what is "subject"?
272
+ describe Article do
273
+ subject { Article.new }
274
+ it "validates presence of title" do
275
+ expect(subject).not_to be_valid # Requires scrolling to understand
276
+ end
277
+ end
278
+
279
+ # GOOD - intention-revealing name
280
+ describe Article do
281
+ subject(:article) { Article.new }
282
+ it "validates presence of title" do
283
+ expect(article).not_to be_valid
284
+ end
285
+ end
286
+ ```
287
+
288
+ ### Subject Anti-Patterns
289
+
290
+ **1. At class level, subject should be the object under test**:
291
+
292
+ At the top-level `describe`, subject represents the object being tested:
293
+
294
+ ```ruby
295
+ # BAD - at class level, subject should be the object, not a method result
296
+ RSpec.describe MyService do
297
+ subject(:response) { described_class.new.call } # Wrong: this is a result
298
+ end
299
+
300
+ # GOOD - subject is the object instance
301
+ RSpec.describe MyService do
302
+ subject(:service) { described_class.new }
303
+
304
+ it "returns success" do
305
+ expect(service.call).to be_success
306
+ end
307
+ end
308
+ ```
309
+
310
+ **Note**: Inside a `describe "#method"` block, subject CAN be the method result.
311
+ This is valid because the method IS what's being tested in that scope:
312
+
313
+ ```ruby
314
+ RSpec.describe Order do
315
+ subject(:order) { described_class.new(items) } # Class-level: the object
316
+
317
+ describe "#total" do
318
+ subject(:total) { order.total } # Method-level: the result is OK
319
+
320
+ it "sums item prices" do
321
+ expect(total).to eq(100)
322
+ end
323
+ end
324
+ end
325
+ ```
326
+
327
+ **2. Multiple subjects**:
328
+ ```ruby
329
+ # BAD - ambiguous which is THE subject
330
+ subject(:user) { User.new }
331
+ subject(:admin) { Admin.new }
332
+
333
+ # GOOD - one subject, rest as let
334
+ subject(:user) { User.new }
335
+ let(:admin) { Admin.new }
336
+ ```
337
+
338
+ **3. Missing subject**:
339
+ ```ruby
340
+ # BAD - repeated instantiation
341
+ it "returns 200" do
342
+ expect(Pinger.new.call).to eq(200)
343
+ end
344
+
345
+ # GOOD - define subject once
346
+ subject(:pinger) { Pinger.new }
347
+ it "returns 200" do
348
+ expect(pinger.call).to eq(200)
349
+ end
350
+ ```
351
+
352
+ ### Subject Placement
353
+
354
+ Subject must be first declaration in example group:
355
+
356
+ ```ruby
357
+ describe UserSerializer do
358
+ subject(:serializer) { described_class.new(user) } # First
359
+ let(:user) { create(:user) } # After subject
360
+ end
361
+ ```
362
+
363
+ ## Configuration
364
+
365
+ ### RSpec.configure
366
+
367
+ ```ruby
368
+ RSpec.configure do |config|
369
+ # Expectations
370
+ config.expect_with :rspec do |expectations|
371
+ expectations.include_chain_clauses_in_custom_matcher_descriptions = true
372
+ expectations.max_formatted_output_length = 1000
373
+ end
374
+
375
+ # Mocks
376
+ config.mock_with :rspec do |mocks|
377
+ mocks.verify_partial_doubles = true
378
+ end
379
+
380
+ # Execution
381
+ config.order = :random
382
+ config.fail_fast = true # or number like 3
383
+
384
+ # Filtering
385
+ config.filter_run_when_matching :focus
386
+ config.filter_run_excluding :slow
387
+
388
+ # Persistence
389
+ config.example_status_persistence_file_path = "spec/examples.txt"
390
+
391
+ # Output
392
+ config.default_formatter = "doc"
393
+ config.profile_examples = 10
394
+
395
+ # Include helpers
396
+ config.include MyHelpers
397
+ config.include AuthHelpers, type: :request
398
+ end
399
+ ```
400
+
401
+ ### Configuration Files
402
+
403
+ Precedence (lowest to highest):
404
+ 1. `$XDG_CONFIG_HOME/rspec/options` or `~/.rspec`
405
+ 2. `./.rspec`
406
+ 3. `./.rspec-local`
407
+ 4. Command-line options
408
+ 5. `SPEC_OPTS` environment variable
409
+
410
+ ## Metadata
411
+
412
+ ### Adding Metadata
413
+
414
+ ```ruby
415
+ it "does something", :slow, :ui => true do
416
+ # metadata[:slow] = true
417
+ # metadata[:ui] = true
418
+ end
419
+
420
+ RSpec.describe "Group", :integration do
421
+ # metadata[:integration] = true
422
+ end
423
+ ```
424
+
425
+ ### Accessing Metadata
426
+
427
+ ```ruby
428
+ it "does something" do |example|
429
+ example.metadata[:description] # => "does something"
430
+ example.metadata[:file_path] # => "/path/to/spec.rb"
431
+ end
432
+ ```
433
+
434
+ ### described_class
435
+
436
+ ```ruby
437
+ RSpec.describe Widget do
438
+ it "creates instance" do
439
+ widget = described_class.new
440
+ expect(widget).to be_a(Widget)
441
+ end
442
+ end
443
+ ```
444
+
445
+ ## Filtering
446
+
447
+ ### By Tag
448
+
449
+ ```bash
450
+ rspec --tag slow:true
451
+ rspec --tag ~slow # exclude
452
+
453
+ # In configure:
454
+ config.filter_run_including :foo => :bar
455
+ config.filter_run_excluding :foo => :bar
456
+ ```
457
+
458
+ ### By Description
459
+
460
+ ```bash
461
+ rspec --example "Homepage when logged in"
462
+ rspec -e "Homepage" -e "User" # multiple patterns
463
+ ```
464
+
465
+ ### By Location
466
+
467
+ ```bash
468
+ rspec spec/homepage_spec.rb:14 spec/widgets_spec.rb:40
469
+ ```
470
+
471
+ ### Focus Filtering
472
+
473
+ ```ruby
474
+ RSpec.configure do |config|
475
+ config.filter_run_when_matching :focus
476
+ end
477
+
478
+ fit "focused example" do # runs only this
479
+ end
480
+ ```
481
+
482
+ ## Shared Examples
483
+
484
+ ### What Shared Examples Are
485
+
486
+ Shared examples store test assertions (`it` blocks) for reuse across multiple test contexts. Content is only executed when included in another example group.
487
+
488
+ ### shared_examples vs shared_context
489
+
490
+ | Feature | `shared_examples` | `shared_context` |
491
+ |---------|-------------------|------------------|
492
+ | Contains | Test assertions (`it` blocks) | Setup (`let`, `before`, helpers) |
493
+ | Purpose | Share behavior tests | Share configuration |
494
+ | Use when | Same behavior across classes | Same initial state needed |
495
+
496
+ ### Definition
497
+
498
+ ```ruby
499
+ RSpec.shared_examples "a collection" do
500
+ it "responds to each" do
501
+ expect(subject).to respond_to(:each)
502
+ end
503
+ end
504
+
505
+ RSpec.shared_examples "a container" do |item|
506
+ it "contains #{item}" do
507
+ expect(subject).to include(item)
508
+ end
509
+ end
510
+ ```
511
+
512
+ ### Passing Data INTO Shared Examples
513
+
514
+ **Method 1: Positional Parameters** (compile-time):
515
+ ```ruby
516
+ RSpec.shared_examples "measurable" do |expected_size|
517
+ it "has size #{expected_size}" do
518
+ expect(subject.size).to eq(expected_size)
519
+ end
520
+ end
521
+
522
+ describe Array do
523
+ subject { [1, 2, 3] }
524
+ it_behaves_like "measurable", 3
525
+ end
526
+ ```
527
+
528
+ **Method 2: Keyword Arguments** (recommended):
529
+ ```ruby
530
+ shared_examples "configurable" do |defaults: {}, required: []|
531
+ required.each do |attr|
532
+ it "requires #{attr}" do
533
+ expect(subject.public_send(attr)).to be_present
534
+ end
535
+ end
536
+ end
537
+
538
+ describe Settings do
539
+ it_behaves_like "configurable", required: [:timeout, :retries]
540
+ end
541
+ ```
542
+
543
+ **Method 3: Block for Runtime Context**:
544
+ ```ruby
545
+ RSpec.shared_examples "a collection" do
546
+ it "is not empty" do
547
+ expect(collection).not_to be_empty
548
+ end
549
+ end
550
+
551
+ describe Array do
552
+ it_behaves_like "a collection" do
553
+ let(:collection) { [1, 2, 3] } # Defined at runtime
554
+ end
555
+ end
556
+ ```
557
+
558
+ ### Accessing Context FROM WITHIN Shared Examples
559
+
560
+ Shared examples can access from including context:
561
+ - `described_class`
562
+ - `subject`
563
+ - `let` definitions
564
+ - Metadata
565
+
566
+ ```ruby
567
+ RSpec.shared_examples "timestamped" do
568
+ it "responds to created_at" do
569
+ expect(subject).to respond_to(:created_at)
570
+ end
571
+
572
+ it "is instance of described class" do
573
+ expect(subject).to be_a(described_class)
574
+ end
575
+ end
576
+ ```
577
+
578
+ ### it_behaves_like vs include_examples
579
+
580
+ | Method | Context | Safety | Output |
581
+ |--------|---------|--------|--------|
582
+ | `it_behaves_like` | Creates nested context | Safe | `behaves like X` |
583
+ | `include_examples` | Merges into current | Risky | Flat |
584
+
585
+ **Use `it_behaves_like`** (default choice):
586
+ ```ruby
587
+ describe Array do
588
+ it_behaves_like "a collection" # Creates nested context
589
+ end
590
+ # Output: Array > behaves like a collection > responds to each
591
+ ```
592
+
593
+ **Avoid `include_examples`** multiple times:
594
+ ```ruby
595
+ # BAD - method conflicts, last let wins
596
+ describe Controller do
597
+ include_examples "user actions", :admin
598
+ include_examples "user actions", :regular # Overrides admin let!
599
+ end
600
+
601
+ # GOOD - isolated contexts
602
+ describe Controller do
603
+ it_behaves_like "user actions", :admin
604
+ it_behaves_like "user actions", :regular
605
+ end
606
+ ```
607
+
608
+ ### Shared Context
609
+
610
+ Use for shared setup (no assertions):
611
+
612
+ ```ruby
613
+ RSpec.shared_context "authenticated user" do
614
+ let(:current_user) { create(:user) }
615
+ before { sign_in(current_user) }
616
+ end
617
+
618
+ RSpec.describe DashboardController do
619
+ include_context "authenticated user"
620
+
621
+ it "shows dashboard" do
622
+ get :index
623
+ expect(response).to be_successful
624
+ end
625
+ end
626
+ ```
627
+
628
+ ### Anti-Patterns to Avoid
629
+
630
+ **1. Over-abstraction**:
631
+ ```ruby
632
+ # BAD - too much indirection
633
+ shared_context "with user" do
634
+ let(:user) { create(:user, role: role) }
635
+ let(:role) { :member }
636
+ end
637
+
638
+ shared_examples "authorized" do
639
+ it "allows access" do
640
+ expect(user.can_access?).to be true # Where did user come from?
641
+ end
642
+ end
643
+
644
+ # GOOD - some repetition is okay for clarity
645
+ describe Admin do
646
+ let(:admin) { create(:user, :admin) }
647
+
648
+ it "allows access" do
649
+ expect(admin.can_access?).to be true
650
+ end
651
+ end
652
+ ```
653
+
654
+ **2. The Mystery Guest** (RSpec DSL Puzzle):
655
+ ```ruby
656
+ # BAD - readers must hunt for definitions
657
+ describe BillingService do
658
+ include_context "user setup"
659
+ include_context "subscription setup"
660
+
661
+ it "charges user" do
662
+ BillingService.process(user) # Where is user defined?
663
+ expect(user.charged?).to be true
664
+ end
665
+ end
666
+ ```
667
+
668
+ **3. Hidden Dependencies**:
669
+ ```ruby
670
+ # BAD - shared example requires specific let names
671
+ shared_examples "sortable" do
672
+ it "sorts items" do
673
+ expect(items.sort).to eq(sorted_items) # Must define items AND sorted_items
674
+ end
675
+ end
676
+ ```
677
+
678
+ **4. Spec Explosion** (n*m problem):
679
+ Avoid including heavy shared examples in many places. If shared example has 10 assertions and included in 8 classes, you run 80 specs instead of 10+8.
680
+
681
+ ### Organization
682
+
683
+ **Same file** (isolated use):
684
+ ```ruby
685
+ # spec/models/user_spec.rb
686
+ RSpec.shared_examples "has timestamps" do
687
+ it { is_expected.to respond_to(:created_at) }
688
+ end
689
+
690
+ RSpec.describe User do
691
+ it_behaves_like "has timestamps"
692
+ end
693
+ ```
694
+
695
+ **Support directory** (widespread use):
696
+ ```
697
+ spec/support/
698
+ shared_contexts/
699
+ authenticated_user.rb
700
+ shared_examples/
701
+ timestampable.rb
702
+ ```
703
+
704
+ Load in `rails_helper.rb`:
705
+ ```ruby
706
+ Dir["./spec/support/**/*.rb"].sort.each { |f| require f }
707
+ ```
708
+
709
+ ### When to Use Shared Examples
710
+
711
+ **Good use cases**:
712
+ - Controller authentication (same auth checks across actions)
713
+ - Interface compliance testing
714
+ - Shared model behaviors (e.g., soft delete)
715
+
716
+ **Avoid when**:
717
+ - Models have unique behaviors
718
+ - Setup complexity exceeds benefit
719
+ - Tests become hard to understand
720
+
721
+ ## CLI Options
722
+
723
+ ### Common Options
724
+
725
+ ```bash
726
+ rspec # run all in spec/
727
+ rspec spec/models # specific directory
728
+ rspec spec/user_spec.rb # specific file
729
+ rspec spec/user_spec.rb:23 # specific line
730
+
731
+ # Formatting
732
+ rspec --format doc # documentation format
733
+ rspec --format progress # dots (default)
734
+ rspec --format json --out results.json
735
+
736
+ # Execution
737
+ rspec --fail-fast # stop on first failure
738
+ rspec --fail-fast=3 # stop after 3 failures
739
+ rspec --only-failures # re-run only failures
740
+ rspec --next-failure # run next failure
741
+ rspec --order random # randomize
742
+ rspec --seed 1234 # specific seed
743
+ rspec --profile 10 # show 10 slowest
744
+ rspec --dry-run # list without running
745
+ ```
746
+
747
+ ### .rspec File
748
+
749
+ ```
750
+ --format documentation
751
+ --color
752
+ --require spec_helper
753
+ --order random
754
+ ```
755
+
756
+ ## Best Practice Patterns
757
+
758
+ ### Context Blocks for States
759
+
760
+ ```ruby
761
+ describe "#withdraw" do
762
+ context "with sufficient funds" do
763
+ it "reduces balance" do
764
+ # ...
765
+ end
766
+ end
767
+
768
+ context "with insufficient funds" do
769
+ it "raises error" do
770
+ # ...
771
+ end
772
+ end
773
+ end
774
+ ```
775
+
776
+ ### Named Subject in Method Describe
777
+
778
+ ```ruby
779
+ describe "#calculate_total" do
780
+ subject(:total) { order.calculate_total }
781
+
782
+ it "sums items" do
783
+ expect(total).to eq(100)
784
+ end
785
+ end
786
+ ```
787
+
788
+ ### Helper Methods
789
+
790
+ ```ruby
791
+ RSpec.describe Order do
792
+ def create_order_with_items(count)
793
+ order = Order.new
794
+ count.times { order.add_item(Item.new) }
795
+ order
796
+ end
797
+
798
+ it "calculates total" do
799
+ order = create_order_with_items(3)
800
+ expect(order.total).to eq(30)
801
+ end
802
+ end
803
+ ```
804
+
805
+ ### Shared Examples for Reusable Behaviors
806
+
807
+ ```ruby
808
+ shared_examples "a timestamped model" do
809
+ it { is_expected.to respond_to(:created_at) }
810
+ it { is_expected.to respond_to(:updated_at) }
811
+ end
812
+
813
+ RSpec.describe User do
814
+ it_behaves_like "a timestamped model"
815
+ end
816
+ ```