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,136 @@
1
+ # RSpec Matchers: Error/Exception Examples
2
+ # Source: rspec-expectations gem features/built_in_matchers/raise_error.feature,
3
+ # throw_symbol.feature
4
+
5
+ # raise_error / raise_exception
6
+ RSpec.describe "raise_error matcher" do
7
+ describe "basic usage" do
8
+ it "expects any error" do
9
+ expect { raise StandardError }.to raise_error
10
+ end
11
+
12
+ it "expects specific error class" do
13
+ expect { raise ArgumentError }.to raise_error(ArgumentError)
14
+ end
15
+
16
+ it "expects error with message string" do
17
+ expect { raise "boom" }.to raise_error("boom")
18
+ end
19
+
20
+ it "expects error with message regex" do
21
+ expect { raise "Something went wrong" }.to raise_error(/wrong/)
22
+ end
23
+
24
+ it "expects class and message" do
25
+ expect { raise ArgumentError, "invalid" }.to raise_error(ArgumentError, "invalid")
26
+ expect { raise ArgumentError, "invalid" }.to raise_error(ArgumentError, /inv/)
27
+ end
28
+ end
29
+
30
+ describe "with_message chain" do
31
+ it "uses with_message for readability" do
32
+ expect { raise StandardError, "detailed error" }
33
+ .to raise_error(StandardError)
34
+ .with_message(/detailed/)
35
+ end
36
+ end
37
+
38
+ describe "block form for complex assertions" do
39
+ it "yields error for inspection" do
40
+ expect { raise ArgumentError, "bad input" }.to raise_error { |error|
41
+ expect(error).to be_an(ArgumentError)
42
+ expect(error.message).to include("bad")
43
+ }
44
+ end
45
+ end
46
+
47
+ describe "composed matchers" do
48
+ it "uses matcher composition" do
49
+ expect { raise ArgumentError, "invalid value" }.to raise_error(
50
+ an_instance_of(ArgumentError).and(having_attributes(message: /invalid/))
51
+ )
52
+ end
53
+ end
54
+
55
+ describe "negative expectation" do
56
+ it "expects no error" do
57
+ expect { 1 + 1 }.not_to raise_error
58
+ end
59
+ end
60
+ end
61
+
62
+ # throw_symbol
63
+ RSpec.describe "throw_symbol matcher" do
64
+ describe "basic usage" do
65
+ it "expects any symbol thrown" do
66
+ expect { throw :done }.to throw_symbol
67
+ end
68
+
69
+ it "expects specific symbol" do
70
+ expect { throw :abort }.to throw_symbol(:abort)
71
+ end
72
+
73
+ it "expects symbol with value" do
74
+ expect { throw :result, 42 }.to throw_symbol(:result, 42)
75
+ end
76
+ end
77
+
78
+ describe "negative expectations" do
79
+ it "expects nothing thrown" do
80
+ expect { 1 + 1 }.not_to throw_symbol
81
+ end
82
+
83
+ it "expects different symbol" do
84
+ expect { throw :foo }.not_to throw_symbol(:bar)
85
+ end
86
+ end
87
+ end
88
+
89
+ # Practical examples
90
+ RSpec.describe PaymentService do
91
+ subject(:service) { build(:payment_service) }
92
+
93
+ describe "#process" do
94
+ context "with invalid amount" do
95
+ it "raises ArgumentError" do
96
+ expect { service.process(amount: -100) }
97
+ .to raise_error(ArgumentError, /positive/)
98
+ end
99
+ end
100
+
101
+ context "with invalid currency" do
102
+ it "raises UnsupportedCurrencyError" do
103
+ expect { service.process(amount: 100, currency: "XXX") }
104
+ .to raise_error(UnsupportedCurrencyError)
105
+ .with_message(/XXX/)
106
+ end
107
+ end
108
+
109
+ context "when gateway fails" do
110
+ before { allow(service).to receive(:gateway).and_raise(GatewayError) }
111
+
112
+ it "raises PaymentFailedError with cause" do
113
+ expect { service.process(amount: 100) }.to raise_error { |error|
114
+ expect(error).to be_a(PaymentFailedError)
115
+ expect(error.cause).to be_a(GatewayError)
116
+ }
117
+ end
118
+ end
119
+ end
120
+ end
121
+
122
+ RSpec.describe "early exit with throw/catch" do
123
+ describe "batch processor" do
124
+ subject(:processor) { build(:batch_processor) }
125
+
126
+ it "throws :halt on critical error" do
127
+ expect { processor.process_with_halt_on_error(bad_records) }
128
+ .to throw_symbol(:halt)
129
+ end
130
+
131
+ it "throws with error details" do
132
+ expect { processor.process_with_halt_on_error(bad_records) }
133
+ .to throw_symbol(:halt, a_hash_including(reason: :validation_failed))
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,103 @@
1
+ # RSpec Matchers: Output Examples
2
+ # Source: rspec-expectations gem features/built_in_matchers/output.feature
3
+
4
+ # output to stdout
5
+ RSpec.describe "output matcher - stdout" do
6
+ describe "any output" do
7
+ it "expects output to stdout" do
8
+ expect { print "hello" }.to output.to_stdout
9
+ end
10
+
11
+ it "expects no output" do
12
+ expect { 1 + 1 }.not_to output.to_stdout
13
+ end
14
+ end
15
+
16
+ describe "specific output" do
17
+ it "matches exact string" do
18
+ expect { print "hello" }.to output("hello").to_stdout
19
+ end
20
+
21
+ it "matches with regex" do
22
+ expect { puts "Hello, World!" }.to output(/World/).to_stdout
23
+ end
24
+ end
25
+ end
26
+
27
+ # output to stderr
28
+ RSpec.describe "output matcher - stderr" do
29
+ it "captures stderr" do
30
+ expect { warn "danger" }.to output.to_stderr
31
+ end
32
+
33
+ it "matches warning message" do
34
+ expect { warn "danger" }.to output("danger\n").to_stderr
35
+ expect { warn "danger" }.to output(/danger/).to_stderr
36
+ end
37
+ end
38
+
39
+ # output from subprocesses
40
+ RSpec.describe "output matcher - any process" do
41
+ it "captures subprocess stdout" do
42
+ expect { system("echo hello") }.to output("hello\n").to_stdout_from_any_process
43
+ end
44
+
45
+ it "captures subprocess stderr" do
46
+ expect { system("echo error >&2") }.to output("error\n").to_stderr_from_any_process
47
+ end
48
+ end
49
+
50
+ # Practical examples
51
+ RSpec.describe Logger do
52
+ subject(:logger) { build(:logger, output: $stdout) }
53
+
54
+ describe "#info" do
55
+ it "outputs formatted message to stdout" do
56
+ expect { logger.info("test message") }
57
+ .to output(/\[INFO\].*test message/).to_stdout
58
+ end
59
+ end
60
+
61
+ describe "#error" do
62
+ it "outputs to stderr" do
63
+ expect { logger.error("critical failure") }
64
+ .to output(/critical failure/).to_stderr
65
+ end
66
+ end
67
+ end
68
+
69
+ RSpec.describe CLI do
70
+ subject(:cli) { build(:cli) }
71
+
72
+ describe "#run" do
73
+ context "with --help flag" do
74
+ it "prints usage to stdout" do
75
+ expect { cli.run(["--help"]) }
76
+ .to output(/Usage:/).to_stdout
77
+ end
78
+ end
79
+
80
+ context "with invalid option" do
81
+ it "prints error to stderr" do
82
+ expect { cli.run(["--invalid"]) }
83
+ .to output(/unknown option/).to_stderr
84
+ end
85
+ end
86
+
87
+ context "in verbose mode" do
88
+ it "prints progress information" do
89
+ expect { cli.run(["--verbose", "process"]) }
90
+ .to output(/Processing/).to_stdout
91
+ end
92
+ end
93
+ end
94
+ end
95
+
96
+ RSpec.describe RakeTask do
97
+ describe "external command execution" do
98
+ it "captures output from system calls" do
99
+ expect { system("rake db:migrate") }
100
+ .to output(/migrated/).to_stdout_from_any_process
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,87 @@
1
+ # RSpec Matchers: Predicate Examples
2
+ # Source: rspec-expectations gem features/built_in_matchers/predicates.feature
3
+
4
+ # be_* - converts to predicate method call
5
+ RSpec.describe "dynamic be_* matchers" do
6
+ it "calls predicate methods ending in ?" do
7
+ expect(0).to be_zero # 0.zero?
8
+ expect([]).to be_empty # [].empty?
9
+ expect(5).to be_positive # 5.positive?
10
+ expect(-3).to be_negative # (-3).negative?
11
+ end
12
+
13
+ it "works with custom predicates" do
14
+ user = build(:user, status: :active)
15
+
16
+ expect(user).to be_active # user.active?
17
+ expect(user).not_to be_banned # !user.banned?
18
+ end
19
+
20
+ it "passes arguments to predicate" do
21
+ expect(12).to be_multiple_of(3) # 12.multiple_of?(3)
22
+ end
23
+ end
24
+
25
+ # have_* - converts to has_*? method call
26
+ RSpec.describe "dynamic have_* matchers" do
27
+ it "calls has_*? methods" do
28
+ expect({ a: 1 }).to have_key(:a) # {a: 1}.has_key?(:a)
29
+ expect({ a: 1 }).to have_value(1) # {a: 1}.has_value?(1)
30
+ end
31
+
32
+ it "works with custom has_*? methods" do
33
+ order = build(:order, :with_items)
34
+
35
+ expect(order).to have_items # order.has_items?
36
+ expect(order).to have_discount # order.has_discount?
37
+ end
38
+ end
39
+
40
+ # Practical examples with Rails models
41
+ RSpec.describe User do
42
+ subject(:user) { build(:user) }
43
+
44
+ describe "validation predicates" do
45
+ context "with valid attributes" do
46
+ it { is_expected.to be_valid }
47
+ end
48
+
49
+ context "without email" do
50
+ subject(:user) { build(:user, email: nil) }
51
+
52
+ it { is_expected.not_to be_valid }
53
+ end
54
+ end
55
+
56
+ describe "state predicates" do
57
+ context "when active" do
58
+ subject(:user) { build(:user, :active) }
59
+
60
+ it { is_expected.to be_active }
61
+ it { is_expected.not_to be_suspended }
62
+ end
63
+
64
+ context "when suspended" do
65
+ subject(:user) { build(:user, :suspended) }
66
+
67
+ it { is_expected.to be_suspended }
68
+ it { is_expected.not_to be_active }
69
+ end
70
+ end
71
+ end
72
+
73
+ RSpec.describe Order do
74
+ subject(:order) { build(:order) }
75
+
76
+ describe "collection predicates" do
77
+ context "with items" do
78
+ subject(:order) { build(:order, :with_items) }
79
+
80
+ it { is_expected.to have_items }
81
+ end
82
+
83
+ context "without items" do
84
+ it { is_expected.not_to have_items }
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,101 @@
1
+ # RSpec Matchers: Truthiness Examples
2
+ # Source: rspec-expectations gem features/built_in_matchers/be.feature, exist.feature
3
+
4
+ # be_truthy - any truthy value
5
+ RSpec.describe "be_truthy matcher" do
6
+ it "passes for true" do
7
+ expect(true).to be_truthy
8
+ end
9
+
10
+ it "passes for truthy values" do
11
+ expect(7).to be_truthy
12
+ expect("foo").to be_truthy
13
+ expect([]).to be_truthy # Empty array is truthy
14
+ expect(0).to be_truthy # Zero is truthy in Ruby!
15
+ end
16
+
17
+ it "fails for nil and false" do
18
+ expect(nil).not_to be_truthy
19
+ expect(false).not_to be_truthy
20
+ end
21
+ end
22
+
23
+ # be_falsey / be_falsy - nil or false only
24
+ RSpec.describe "be_falsey matcher" do
25
+ it "passes for nil" do
26
+ expect(nil).to be_falsey
27
+ end
28
+
29
+ it "passes for false" do
30
+ expect(false).to be_falsey
31
+ expect(false).to be_falsy # Alias
32
+ end
33
+
34
+ it "fails for truthy values" do
35
+ expect(true).not_to be_falsey
36
+ expect(0).not_to be_falsey # 0 is truthy!
37
+ expect("").not_to be_falsey # Empty string is truthy
38
+ end
39
+ end
40
+
41
+ # be_nil - exactly nil
42
+ RSpec.describe "be_nil matcher" do
43
+ it "passes only for nil" do
44
+ expect(nil).to be_nil
45
+ end
46
+
47
+ it "fails for false" do
48
+ expect(false).not_to be_nil
49
+ end
50
+ end
51
+
52
+ # be true / be false - exact boolean
53
+ RSpec.describe "exact boolean matchers" do
54
+ it "requires exact true" do
55
+ expect(true).to be true
56
+ expect(1).not_to be true # Truthy but not true
57
+ end
58
+
59
+ it "requires exact false" do
60
+ expect(false).to be false
61
+ expect(nil).not_to be false # Falsey but not false
62
+ end
63
+ end
64
+
65
+ # exist - calls exist? or exists?
66
+ RSpec.describe "exist matcher" do
67
+ it "checks existence" do
68
+ # For file-like objects
69
+ expect(File).to exist("/tmp")
70
+ expect(File).not_to exist("/nonexistent/path")
71
+ end
72
+ end
73
+
74
+ # Practical example: model validations
75
+ RSpec.describe User do
76
+ subject(:user) { build(:user) }
77
+
78
+ describe "#admin?" do
79
+ context "when user is admin" do
80
+ subject(:user) { build(:user, :admin) }
81
+
82
+ it "returns true" do
83
+ expect(user.admin?).to be true # Exact boolean check
84
+ end
85
+ end
86
+
87
+ context "when user is not admin" do
88
+ it "returns false" do
89
+ expect(user.admin?).to be false # Not just falsey
90
+ end
91
+ end
92
+ end
93
+
94
+ describe "#deleted_at" do
95
+ context "when not deleted" do
96
+ it "is nil" do
97
+ expect(user.deleted_at).to be_nil
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,82 @@
1
+ # RSpec Matchers: Type/Class Examples
2
+ # Source: rspec-expectations gem features/built_in_matchers/types.feature, respond_to.feature
3
+
4
+ # be_instance_of - exact class match
5
+ RSpec.describe "be_instance_of matcher" do
6
+ it "matches exact class only" do
7
+ expect(17.0).to be_instance_of(Float)
8
+ expect(17.0).to be_an_instance_of(Float) # Alias
9
+ end
10
+
11
+ it "does NOT match superclass" do
12
+ expect(17.0).not_to be_instance_of(Numeric) # Float < Numeric
13
+ end
14
+ end
15
+
16
+ # be_kind_of / be_a - class hierarchy match
17
+ RSpec.describe "be_kind_of matcher" do
18
+ it "matches actual class" do
19
+ expect(17.0).to be_kind_of(Float)
20
+ expect(17.0).to be_a(Float) # Alias
21
+ expect(17.0).to be_an(Integer) # Grammatical alias (fails - just for example)
22
+ end
23
+
24
+ it "matches superclass" do
25
+ expect(17.0).to be_kind_of(Numeric)
26
+ expect(17.0).to be_a(Object)
27
+ end
28
+
29
+ it "matches included modules" do
30
+ expect([1, 2, 3]).to be_kind_of(Enumerable)
31
+ end
32
+ end
33
+
34
+ # respond_to - interface checking
35
+ RSpec.describe "respond_to matcher" do
36
+ it "checks method existence" do
37
+ expect("string").to respond_to(:length)
38
+ expect("string").to respond_to(:upcase, :downcase) # Multiple
39
+ expect("string").not_to respond_to(:foo)
40
+ end
41
+
42
+ it "checks argument count" do
43
+ expect(7).to respond_to(:zero?).with(0).arguments
44
+ expect(7).to respond_to(:between?).with(2).arguments
45
+ end
46
+
47
+ it "checks argument range" do
48
+ # Methods with optional arguments
49
+ expect([]).to respond_to(:first).with(0..1).arguments
50
+ end
51
+
52
+ it "checks keyword arguments" do
53
+ service = build(:payment_service)
54
+
55
+ expect(service).to respond_to(:process).with_keywords(:amount, :currency)
56
+ expect(service).to respond_to(:process).with(1).argument.and_keywords(:amount)
57
+ end
58
+ end
59
+
60
+ # Practical example: duck typing
61
+ RSpec.describe "exportable object" do
62
+ subject(:exporter) { build(:csv_exporter) }
63
+
64
+ it "implements exportable interface" do
65
+ expect(exporter).to respond_to(:export).with(1).argument
66
+ expect(exporter).to respond_to(:headers)
67
+ expect(exporter).to respond_to(:rows)
68
+ end
69
+ end
70
+
71
+ # Practical example: model type checking
72
+ RSpec.describe User do
73
+ subject(:user) { build(:user) }
74
+
75
+ it "is an ApplicationRecord" do
76
+ expect(user).to be_a(ApplicationRecord)
77
+ end
78
+
79
+ it "includes Authenticatable" do
80
+ expect(user).to be_kind_of(Authenticatable)
81
+ end
82
+ end
@@ -0,0 +1,147 @@
1
+ # RSpec Matchers: Yield Examples
2
+ # Source: rspec-expectations gem features/built_in_matchers/yield.feature
3
+
4
+ # NOTE: All yield matchers require a block probe |b|
5
+
6
+ # yield_control - detects any yield
7
+ RSpec.describe "yield_control matcher" do
8
+ it "passes when block is yielded to" do
9
+ expect { |b| 5.tap(&b) }.to yield_control
10
+ end
11
+
12
+ it "fails when block is not yielded to" do
13
+ expect { |b| "no yield" }.not_to yield_control
14
+ end
15
+
16
+ describe "with counts" do
17
+ it "specifies exact yield count" do
18
+ expect { |b| 3.times(&b) }.to yield_control.exactly(3).times
19
+ expect { |b| 2.times(&b) }.to yield_control.twice
20
+ end
21
+
22
+ it "specifies minimum yields" do
23
+ expect { |b| 5.times(&b) }.to yield_control.at_least(3).times
24
+ expect { |b| 5.times(&b) }.to yield_control.at_least(:once)
25
+ end
26
+
27
+ it "specifies maximum yields" do
28
+ expect { |b| 2.times(&b) }.to yield_control.at_most(5).times
29
+ expect { |b| 1.times(&b) }.to yield_control.at_most(:twice)
30
+ end
31
+ end
32
+ end
33
+
34
+ # yield_with_no_args - yield without arguments
35
+ RSpec.describe "yield_with_no_args matcher" do
36
+ it "passes when yielded without arguments" do
37
+ expect { |b| yield_nothing(&b) }.to yield_with_no_args
38
+ end
39
+
40
+ def yield_nothing
41
+ yield
42
+ end
43
+
44
+ it "fails when yielded with arguments" do
45
+ expect { |b| yield_with_value(&b) }.not_to yield_with_no_args
46
+ end
47
+
48
+ def yield_with_value
49
+ yield 42
50
+ end
51
+ end
52
+
53
+ # yield_with_args - yield with specific arguments
54
+ RSpec.describe "yield_with_args matcher" do
55
+ it "passes for any arguments" do
56
+ expect { |b| "foo".tap(&b) }.to yield_with_args
57
+ end
58
+
59
+ it "matches exact values" do
60
+ expect { |b| yield_value(42, &b) }.to yield_with_args(42)
61
+ expect { |b| yield_values("a", "b", &b) }.to yield_with_args("a", "b")
62
+ end
63
+
64
+ it "matches types (uses ===)" do
65
+ expect { |b| yield_value(42, &b) }.to yield_with_args(Integer)
66
+ expect { |b| yield_value("foo", &b) }.to yield_with_args(String)
67
+ end
68
+
69
+ it "matches with regex" do
70
+ expect { |b| yield_value("foobar", &b) }.to yield_with_args(/bar/)
71
+ end
72
+
73
+ def yield_value(val)
74
+ yield val
75
+ end
76
+
77
+ def yield_values(*vals)
78
+ yield(*vals)
79
+ end
80
+ end
81
+
82
+ # yield_successive_args - multiple yields with different args
83
+ RSpec.describe "yield_successive_args matcher" do
84
+ it "matches each yielded value in order" do
85
+ expect { |b| [1, 2, 3].each(&b) }.to yield_successive_args(1, 2, 3)
86
+ end
87
+
88
+ it "works with hashes" do
89
+ expect { |b| { a: 1, b: 2 }.each(&b) }.to yield_successive_args([:a, 1], [:b, 2])
90
+ end
91
+
92
+ it "works with composed matchers" do
93
+ expect { |b| [1, 2, 3].each(&b) }
94
+ .to yield_successive_args(a_value < 2, 2, a_value > 2)
95
+ end
96
+ end
97
+
98
+ # Practical examples
99
+ RSpec.describe File do
100
+ describe ".open with block" do
101
+ it "yields file handle" do
102
+ expect { |b| File.open("/tmp/test.txt", "w", &b) }
103
+ .to yield_with_args(an_instance_of(File))
104
+ end
105
+ end
106
+ end
107
+
108
+ RSpec.describe Enumerator do
109
+ describe "#each_with_index" do
110
+ it "yields element and index pairs" do
111
+ enum = %w[a b c].each_with_index
112
+
113
+ expect { |b| enum.each(&b) }
114
+ .to yield_successive_args(["a", 0], ["b", 1], ["c", 2])
115
+ end
116
+ end
117
+ end
118
+
119
+ RSpec.describe "Database transaction" do
120
+ describe ".transaction" do
121
+ it "yields control for block execution" do
122
+ expect { |b| ActiveRecord::Base.transaction(&b) }.to yield_control
123
+ end
124
+
125
+ it "yields without arguments" do
126
+ expect { |b| ActiveRecord::Base.transaction(&b) }.to yield_with_no_args
127
+ end
128
+ end
129
+ end
130
+
131
+ RSpec.describe BatchProcessor do
132
+ subject(:processor) { build(:batch_processor) }
133
+
134
+ describe "#process_each" do
135
+ let(:items) { build_list(:item, 3) }
136
+
137
+ it "yields each item" do
138
+ expect { |b| processor.process_each(items, &b) }
139
+ .to yield_successive_args(*items)
140
+ end
141
+
142
+ it "yields expected number of times" do
143
+ expect { |b| processor.process_each(items, &b) }
144
+ .to yield_control.exactly(3).times
145
+ end
146
+ end
147
+ end