anima-core 0.3.0 → 1.0.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.
Files changed (269) 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 +3 -0
  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 +168 -0
  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 -1
@@ -0,0 +1,139 @@
1
+ # RSpec Core: Memoized Helpers Examples
2
+ # Source: rspec-core gem features/helper_methods/let.feature
3
+
4
+ # let - lazy evaluation and memoization
5
+ RSpec.describe "let behavior" do
6
+ let(:user) { build(:user) }
7
+
8
+ it "memoizes within an example" do
9
+ expect(user).to be(user) # Same object instance
10
+ end
11
+
12
+ it "creates fresh instance for each example" do
13
+ expect(user.object_id).not_to eq(0) # New user each time
14
+ end
15
+ end
16
+
17
+ # let! - eager evaluation (before hook)
18
+ RSpec.describe "let! behavior" do
19
+ let!(:user) { create(:user) }
20
+
21
+ it "creates user before example runs" do
22
+ # User already exists in database
23
+ expect(User.count).to eq(1)
24
+ end
25
+ end
26
+
27
+ # When to use let vs let!
28
+ RSpec.describe User do
29
+ # Use let - value referenced in test body
30
+ let(:user) { build(:user) }
31
+
32
+ it "validates email format" do
33
+ user.email = "invalid"
34
+ expect(user).not_to be_valid
35
+ end
36
+ end
37
+
38
+ RSpec.describe User, ".active scope" do
39
+ # Use let! - records must exist for database query
40
+ let!(:active_user) { create(:user, status: :active) }
41
+ let!(:inactive_user) { create(:user, status: :inactive) }
42
+
43
+ it "returns only active users" do
44
+ expect(User.active).to contain_exactly(active_user)
45
+ end
46
+ end
47
+
48
+ # Overriding let in nested contexts
49
+ RSpec.describe ShoppingCart do
50
+ subject(:cart) { build(:shopping_cart, items: [item], discount:) }
51
+
52
+ let(:discount) { 0 }
53
+ let(:item) { build(:item, price: 100) }
54
+
55
+ it "calculates full price" do
56
+ expect(cart.total).to eq(100)
57
+ end
58
+
59
+ context "with discount" do
60
+ let(:discount) { 20 } # Override parent definition
61
+
62
+ it "applies discount" do
63
+ expect(cart.total).to eq(80)
64
+ end
65
+ end
66
+ end
67
+
68
+ # Using super() to extend parent let
69
+ RSpec.describe "API request" do
70
+ let(:params) { { name: "Item", price: 10 } }
71
+
72
+ context "with discount" do
73
+ let(:params) { super().merge(discount: 2) } # Extend parent
74
+
75
+ it "includes discount in params" do
76
+ expect(params).to eq(name: "Item", price: 10, discount: 2)
77
+ end
78
+ end
79
+ end
80
+
81
+ # Named subject - always use named subjects
82
+ RSpec.describe Article do
83
+ subject(:article) { build(:article, title: "Hello") }
84
+
85
+ it "validates presence of body" do
86
+ expect(article).not_to be_valid
87
+ expect(article.errors[:body]).to include("can't be blank")
88
+ end
89
+ end
90
+
91
+ # Subject for method testing
92
+ RSpec.describe Calculator do
93
+ subject(:calculator) { build(:calculator) }
94
+
95
+ describe "#add" do
96
+ subject(:result) { calculator.add(2, 3) }
97
+
98
+ it { is_expected.to eq(5) }
99
+ end
100
+ end
101
+
102
+ # Subject placement - always first in example group
103
+ RSpec.describe UserSerializer do
104
+ subject(:serializer) { described_class.new(user) } # First
105
+ let(:user) { create(:user, name: "Alice") } # After subject
106
+
107
+ it "serializes name" do
108
+ expect(serializer.as_json[:name]).to eq("Alice")
109
+ end
110
+ end
111
+
112
+ # Anti-pattern: let! when let suffices
113
+ RSpec.describe "Unnecessary let!" do
114
+ # BAD - creates data unnecessarily
115
+ # let!(:admin) { create(:user, :admin) }
116
+
117
+ # GOOD - use let, only creates when referenced
118
+ let(:admin) { create(:user, :admin) }
119
+
120
+ it "validates email format" do
121
+ user = build(:user, email: "invalid")
122
+ expect(user).not_to be_valid
123
+ # admin never used, never created
124
+ end
125
+ end
126
+
127
+ # Helper methods for complex setup
128
+ RSpec.describe Order do
129
+ def create_order_with_items(count)
130
+ order = build(:order)
131
+ count.times { order.add_item(build(:item)) }
132
+ order
133
+ end
134
+
135
+ it "calculates total for multiple items" do
136
+ order = create_order_with_items(3)
137
+ expect(order.items.count).to eq(3)
138
+ end
139
+ end
@@ -0,0 +1,144 @@
1
+ # RSpec Core: Metadata and Filtering Examples
2
+ # Source: rspec-core gem features/filtering/*.feature
3
+
4
+ # Adding metadata to examples
5
+ RSpec.describe "API" do
6
+ it "handles fast requests", :fast do
7
+ end
8
+
9
+ it "handles slow requests", :slow, timeout: 30 do
10
+ end
11
+
12
+ it "requires authentication", authorized: true do
13
+ end
14
+ end
15
+
16
+ # Adding metadata to groups
17
+ RSpec.describe PaymentGateway, :integration, :external do
18
+ it "processes payment" do
19
+ end
20
+ end
21
+
22
+ # Accessing metadata in examples
23
+ RSpec.describe "Metadata access" do
24
+ it "can read its own metadata" do |example|
25
+ expect(example.metadata[:description]).to eq("can read its own metadata")
26
+ expect(example.metadata[:file_path]).to include("_spec.rb")
27
+ end
28
+ end
29
+
30
+ # Focus filtering - run only focused examples
31
+ RSpec.configure do |config|
32
+ config.filter_run_when_matching :focus
33
+ end
34
+
35
+ RSpec.describe "Debugging" do
36
+ it "skipped example" do
37
+ end
38
+
39
+ fit "focused example - only this runs" do # fit = it with :focus
40
+ end
41
+
42
+ it "another skipped example" do
43
+ end
44
+ end
45
+
46
+ # Exclusion filtering
47
+ RSpec.configure do |config|
48
+ config.filter_run_excluding :slow
49
+ config.filter_run_excluding broken: true
50
+ end
51
+
52
+ RSpec.describe "Suite" do
53
+ it "runs normally" do
54
+ end
55
+
56
+ it "skipped - marked slow", :slow do
57
+ end
58
+
59
+ it "skipped - marked broken", broken: true do
60
+ end
61
+ end
62
+
63
+ # Skip and pending
64
+ RSpec.describe "Skip examples" do
65
+ xit "skipped with xit" do
66
+ end
67
+
68
+ it "skipped with skip call" do
69
+ skip "Not implemented yet"
70
+ end
71
+
72
+ it "skipped via metadata", skip: "Waiting on API" do
73
+ end
74
+ end
75
+
76
+ RSpec.describe "Pending examples" do
77
+ it "pending - expected to fail" do
78
+ pending "Waiting for feature X"
79
+ expect(1 + 1).to eq(3) # Fails, but marked pending
80
+ end
81
+
82
+ # If pending test passes, RSpec fails it (unexpected pass)
83
+ end
84
+
85
+ # Conditional hooks with metadata
86
+ RSpec.configure do |config|
87
+ config.before(:example, :db) do
88
+ DatabaseCleaner.start
89
+ end
90
+
91
+ config.after(:example, :db) do
92
+ DatabaseCleaner.clean
93
+ end
94
+
95
+ config.around(:example, :vcr) do |example|
96
+ VCR.use_cassette(example.metadata[:description]) do
97
+ example.run
98
+ end
99
+ end
100
+ end
101
+
102
+ RSpec.describe UserService, :db do
103
+ it "creates user" do # DatabaseCleaner runs automatically
104
+ expect { UserService.create(name: "Alice") }.to change(User, :count).by(1)
105
+ end
106
+ end
107
+
108
+ RSpec.describe ExternalAPI, :vcr do
109
+ it "fetches data" do # VCR cassette used automatically
110
+ response = ExternalAPI.fetch
111
+ expect(response).to be_success
112
+ end
113
+ end
114
+
115
+ # Aggregate failures - continue after first failure
116
+ RSpec.describe "Response validation" do
117
+ it "validates all response fields" do
118
+ response = Client.make_request
119
+
120
+ aggregate_failures "response validation" do
121
+ expect(response.status).to eq(200)
122
+ expect(response.headers).to include("Content-Type" => "application/json")
123
+ expect(response.body).to include("success")
124
+ # All expectations run, failures collected
125
+ end
126
+ end
127
+ end
128
+
129
+ # Aggregate failures via metadata
130
+ RSpec.describe Client, :aggregate_failures do
131
+ it "validates response" do
132
+ response = Client.make_request
133
+ expect(response.status).to eq(200)
134
+ expect(response.body).to include("success")
135
+ # Both run even if first fails
136
+ end
137
+ end
138
+
139
+ # Global aggregate failures
140
+ RSpec.configure do |config|
141
+ config.define_derived_metadata do |meta|
142
+ meta[:aggregate_failures] = true
143
+ end
144
+ end
@@ -0,0 +1,145 @@
1
+ # RSpec Core: Shared Examples & Shared Context
2
+ # Source: rspec-core gem features/example_groups/shared_examples.feature
3
+
4
+ # shared_examples - reusable test assertions
5
+ RSpec.shared_examples "a collection" do
6
+ let(:collection) { build(:collection, items: [7, 2, 4]) }
7
+
8
+ context "initialized with 3 items" do
9
+ it "reports correct size" do
10
+ expect(collection.size).to eq(3)
11
+ end
12
+ end
13
+
14
+ describe "#include?" do
15
+ context "with item in collection" do
16
+ it "returns true" do
17
+ expect(collection.include?(7)).to be(true)
18
+ end
19
+ end
20
+
21
+ context "with item not in collection" do
22
+ it "returns false" do
23
+ expect(collection.include?(9)).to be(false)
24
+ end
25
+ end
26
+ end
27
+ end
28
+
29
+ RSpec.describe CustomArray do
30
+ it_behaves_like "a collection"
31
+ end
32
+
33
+ RSpec.describe CustomSet do
34
+ it_behaves_like "a collection"
35
+ end
36
+
37
+ # Passing data via positional parameters
38
+ RSpec.shared_examples "measurable" do |expected_size|
39
+ it "has size #{expected_size}" do
40
+ expect(subject.size).to eq(expected_size)
41
+ end
42
+ end
43
+
44
+ RSpec.describe Bucket do
45
+ subject(:bucket) { build(:bucket, :with_items, items_count: 3) }
46
+
47
+ it_behaves_like "measurable", 3
48
+ end
49
+
50
+ # Passing data via keyword arguments (recommended)
51
+ RSpec.shared_examples "validatable" do |required: [], optional: []|
52
+ required.each do |attr|
53
+ it "validates presence of #{attr}" do
54
+ subject.send("#{attr}=", nil)
55
+ expect(subject).not_to be_valid
56
+ end
57
+ end
58
+ end
59
+
60
+ RSpec.describe User do
61
+ subject(:user) { build(:user) }
62
+
63
+ it_behaves_like "validatable", required: [:email, :name]
64
+ end
65
+
66
+ # Passing data via block (runtime context)
67
+ RSpec.shared_examples "a container" do
68
+ it "is not empty" do
69
+ expect(collection).not_to be_empty
70
+ end
71
+ end
72
+
73
+ RSpec.describe "custom container" do
74
+ it_behaves_like "a container" do
75
+ let(:collection) { build_list(:item, 3) } # Defined at runtime
76
+ end
77
+ end
78
+
79
+ # shared_context - reusable setup (no assertions)
80
+ RSpec.shared_context "authenticated user" do
81
+ let(:current_user) { create(:user) }
82
+
83
+ before do
84
+ sign_in(current_user)
85
+ end
86
+ end
87
+
88
+ RSpec.describe DashboardController do
89
+ include_context "authenticated user"
90
+
91
+ it "shows dashboard" do
92
+ get :index
93
+ expect(response).to be_successful
94
+ end
95
+ end
96
+
97
+ # shared_context with metadata auto-inclusion
98
+ RSpec.shared_context "with admin", :admin do
99
+ let(:current_user) { create(:user, :admin) }
100
+
101
+ before { sign_in(current_user) }
102
+ end
103
+
104
+ RSpec.configure do |config|
105
+ config.include_context "with admin", :admin
106
+ end
107
+
108
+ RSpec.describe AdminController, :admin do
109
+ it "allows admin access" do # Context auto-included
110
+ get :index
111
+ expect(response).to be_successful
112
+ end
113
+ end
114
+
115
+ # it_behaves_like vs include_examples
116
+ # PREFER it_behaves_like - creates nested context, safe
117
+ RSpec.describe Controller do
118
+ it_behaves_like "user actions", :admin # Nested: "behaves like user actions"
119
+ it_behaves_like "user actions", :regular # Separate context, no conflicts
120
+ end
121
+
122
+ # AVOID include_examples multiple times - method conflicts
123
+ # describe Controller do
124
+ # include_examples "user actions", :admin
125
+ # include_examples "user actions", :regular # BAD: Overrides let definitions!
126
+ # end
127
+
128
+ # When to use shared examples
129
+ # GOOD: Interface compliance testing
130
+ RSpec.shared_examples "timestamped model" do
131
+ it { is_expected.to respond_to(:created_at) }
132
+ it { is_expected.to respond_to(:updated_at) }
133
+ end
134
+
135
+ RSpec.describe User do
136
+ subject(:user) { build(:user) }
137
+
138
+ it_behaves_like "timestamped model"
139
+ end
140
+
141
+ RSpec.describe Post do
142
+ subject(:post) { build(:post) }
143
+
144
+ it_behaves_like "timestamped model"
145
+ end