anima-core 0.2.1 → 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 (280) hide show
  1. checksums.yaml +4 -4
  2. data/.reek.yml +27 -1
  3. data/CHANGELOG.md +19 -0
  4. data/README.md +213 -43
  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 +195 -45
  12. data/app/decorators/user_message_decorator.rb +16 -5
  13. data/app/jobs/agent_request_job.rb +55 -2
  14. data/app/jobs/analytical_brain_job.rb +33 -0
  15. data/app/jobs/count_event_tokens_job.rb +15 -4
  16. data/app/models/concerns/event/broadcasting.rb +81 -0
  17. data/app/models/event.rb +20 -1
  18. data/app/models/goal.rb +91 -0
  19. data/app/models/session.rb +366 -21
  20. data/config/application.rb +2 -0
  21. data/config/initializers/event_subscribers.rb +0 -1
  22. data/config/routes.rb +0 -6
  23. data/db/migrate/20260313010000_add_status_to_events.rb +8 -0
  24. data/db/migrate/20260313020000_add_processing_to_sessions.rb +7 -0
  25. data/db/migrate/20260314075248_add_subagent_support_to_sessions.rb +6 -0
  26. data/db/migrate/20260314112417_add_granted_tools_to_sessions.rb +5 -0
  27. data/db/migrate/20260314140000_add_name_to_sessions.rb +7 -0
  28. data/db/migrate/20260314150000_add_viewport_event_ids_to_sessions.rb +7 -0
  29. data/db/migrate/20260315100000_add_active_skills_to_sessions.rb +7 -0
  30. data/db/migrate/20260315140843_create_goals.rb +16 -0
  31. data/db/migrate/20260315144837_add_completed_at_to_goals.rb +5 -0
  32. data/db/migrate/20260315191105_add_active_workflow_to_sessions.rb +5 -0
  33. data/lib/agent_loop.rb +65 -6
  34. data/lib/agents/definition.rb +116 -0
  35. data/lib/agents/registry.rb +106 -0
  36. data/lib/analytical_brain/runner.rb +276 -0
  37. data/lib/analytical_brain/tools/activate_skill.rb +52 -0
  38. data/lib/analytical_brain/tools/deactivate_skill.rb +43 -0
  39. data/lib/analytical_brain/tools/deactivate_workflow.rb +34 -0
  40. data/lib/analytical_brain/tools/everything_is_ready.rb +28 -0
  41. data/lib/analytical_brain/tools/finish_goal.rb +62 -0
  42. data/lib/analytical_brain/tools/read_workflow.rb +58 -0
  43. data/lib/analytical_brain/tools/rename_session.rb +63 -0
  44. data/lib/analytical_brain/tools/set_goal.rb +60 -0
  45. data/lib/analytical_brain/tools/update_goal.rb +60 -0
  46. data/lib/analytical_brain.rb +23 -0
  47. data/lib/anima/cli/mcp/secrets.rb +76 -0
  48. data/lib/anima/cli/mcp.rb +197 -0
  49. data/lib/anima/cli.rb +5 -40
  50. data/lib/anima/installer.rb +168 -0
  51. data/lib/anima/settings.rb +226 -0
  52. data/lib/anima/version.rb +1 -1
  53. data/lib/anima.rb +9 -0
  54. data/lib/credential_store.rb +103 -0
  55. data/lib/environment_probe.rb +232 -0
  56. data/lib/events/subscribers/persister.rb +1 -0
  57. data/lib/events/user_message.rb +17 -0
  58. data/lib/llm/client.rb +29 -10
  59. data/lib/mcp/client_manager.rb +86 -0
  60. data/lib/mcp/config.rb +213 -0
  61. data/lib/mcp/health_check.rb +77 -0
  62. data/lib/mcp/secrets.rb +73 -0
  63. data/lib/mcp/stdio_transport.rb +206 -0
  64. data/lib/providers/anthropic.rb +11 -20
  65. data/lib/shell_session.rb +11 -10
  66. data/lib/skills/definition.rb +97 -0
  67. data/lib/skills/registry.rb +105 -0
  68. data/lib/tools/edit.rb +226 -0
  69. data/lib/tools/mcp_tool.rb +114 -0
  70. data/lib/tools/read.rb +151 -0
  71. data/lib/tools/registry.rb +14 -12
  72. data/lib/tools/request_feature.rb +121 -0
  73. data/lib/tools/return_result.rb +81 -0
  74. data/lib/tools/spawn_specialist.rb +109 -0
  75. data/lib/tools/spawn_subagent.rb +111 -0
  76. data/lib/tools/subagent_prompts.rb +12 -0
  77. data/lib/tools/web_get.rb +8 -9
  78. data/lib/tools/write.rb +86 -0
  79. data/lib/tui/app.rb +985 -26
  80. data/lib/tui/cable_client.rb +69 -31
  81. data/lib/tui/message_store.rb +103 -8
  82. data/lib/tui/screens/chat.rb +293 -45
  83. data/lib/workflows/definition.rb +97 -0
  84. data/lib/workflows/registry.rb +89 -0
  85. data/skills/activerecord/SKILL.md +255 -0
  86. data/skills/activerecord/examples/associations/association_extensions.rb +298 -0
  87. data/skills/activerecord/examples/associations/basic_associations.rb +118 -0
  88. data/skills/activerecord/examples/associations/counter_caches.rb +215 -0
  89. data/skills/activerecord/examples/associations/polymorphic_associations.rb +217 -0
  90. data/skills/activerecord/examples/associations/self_referential.rb +302 -0
  91. data/skills/activerecord/examples/associations/through_associations.rb +203 -0
  92. data/skills/activerecord/examples/basics/crud_operations.rb +209 -0
  93. data/skills/activerecord/examples/basics/dirty_tracking.rb +218 -0
  94. data/skills/activerecord/examples/basics/inheritance.rb +377 -0
  95. data/skills/activerecord/examples/basics/type_casting.rb +317 -0
  96. data/skills/activerecord/examples/callbacks/alternatives_to_callbacks.rb +447 -0
  97. data/skills/activerecord/examples/callbacks/conditional_callbacks.rb +353 -0
  98. data/skills/activerecord/examples/callbacks/lifecycle_callbacks.rb +280 -0
  99. data/skills/activerecord/examples/callbacks/transaction_callbacks.rb +340 -0
  100. data/skills/activerecord/examples/migrations/indexes_and_constraints.rb +337 -0
  101. data/skills/activerecord/examples/migrations/reversible_patterns.rb +403 -0
  102. data/skills/activerecord/examples/migrations/safe_patterns.rb +420 -0
  103. data/skills/activerecord/examples/migrations/schema_changes.rb +277 -0
  104. data/skills/activerecord/examples/querying/batch_processing.rb +226 -0
  105. data/skills/activerecord/examples/querying/eager_loading.rb +259 -0
  106. data/skills/activerecord/examples/querying/finder_methods.rb +170 -0
  107. data/skills/activerecord/examples/querying/optimization.rb +275 -0
  108. data/skills/activerecord/examples/querying/scopes.rb +260 -0
  109. data/skills/activerecord/examples/validations/built_in_validators.rb +277 -0
  110. data/skills/activerecord/examples/validations/conditional_validations.rb +288 -0
  111. data/skills/activerecord/examples/validations/custom_validators.rb +381 -0
  112. data/skills/activerecord/examples/validations/database_constraints.rb +432 -0
  113. data/skills/activerecord/examples/validations/validation_contexts.rb +367 -0
  114. data/skills/activerecord/references/associations.md +709 -0
  115. data/skills/activerecord/references/basics.md +622 -0
  116. data/skills/activerecord/references/callbacks.md +738 -0
  117. data/skills/activerecord/references/migrations.md +657 -0
  118. data/skills/activerecord/references/querying.md +655 -0
  119. data/skills/activerecord/references/validations.md +596 -0
  120. data/skills/dragonruby/SKILL.md +250 -0
  121. data/skills/dragonruby/examples/audio/audio_events.rb +55 -0
  122. data/skills/dragonruby/examples/audio/background_music.rb +29 -0
  123. data/skills/dragonruby/examples/audio/crossfade.rb +51 -0
  124. data/skills/dragonruby/examples/audio/music_controls.rb +51 -0
  125. data/skills/dragonruby/examples/audio/sound_effects.rb +30 -0
  126. data/skills/dragonruby/examples/core/coordinate_system.rb +27 -0
  127. data/skills/dragonruby/examples/core/hello_world.rb +24 -0
  128. data/skills/dragonruby/examples/core/labels.rb +22 -0
  129. data/skills/dragonruby/examples/core/sprites.rb +35 -0
  130. data/skills/dragonruby/examples/core/state_management.rb +29 -0
  131. data/skills/dragonruby/examples/distribution/background_pause.rb +42 -0
  132. data/skills/dragonruby/examples/distribution/build_workflow.sh +26 -0
  133. data/skills/dragonruby/examples/distribution/cvars_production.txt +16 -0
  134. data/skills/dragonruby/examples/distribution/game_metadata_hd.txt +23 -0
  135. data/skills/dragonruby/examples/distribution/game_metadata_minimal.txt +9 -0
  136. data/skills/dragonruby/examples/distribution/game_metadata_mobile.txt +31 -0
  137. data/skills/dragonruby/examples/distribution/platform_detection.rb +36 -0
  138. data/skills/dragonruby/examples/distribution/steam_metadata.txt +19 -0
  139. data/skills/dragonruby/examples/entities/collision_detection.rb +43 -0
  140. data/skills/dragonruby/examples/entities/entity_lifecycle.rb +68 -0
  141. data/skills/dragonruby/examples/entities/entity_storage.rb +38 -0
  142. data/skills/dragonruby/examples/entities/factory_methods.rb +45 -0
  143. data/skills/dragonruby/examples/entities/random_spawning.rb +50 -0
  144. data/skills/dragonruby/examples/game-logic/reset_patterns.rb +98 -0
  145. data/skills/dragonruby/examples/game-logic/save_load.rb +101 -0
  146. data/skills/dragonruby/examples/game-logic/scoring.rb +104 -0
  147. data/skills/dragonruby/examples/game-logic/state_transitions.rb +103 -0
  148. data/skills/dragonruby/examples/game-logic/timers.rb +87 -0
  149. data/skills/dragonruby/examples/input/action_triggers.rb +36 -0
  150. data/skills/dragonruby/examples/input/analog_movement.rb +28 -0
  151. data/skills/dragonruby/examples/input/controller_input.rb +28 -0
  152. data/skills/dragonruby/examples/input/directional_input.rb +24 -0
  153. data/skills/dragonruby/examples/input/keyboard_input.rb +28 -0
  154. data/skills/dragonruby/examples/input/mouse_click.rb +26 -0
  155. data/skills/dragonruby/examples/input/movement_with_bounds.rb +22 -0
  156. data/skills/dragonruby/examples/input/normalized_movement.rb +32 -0
  157. data/skills/dragonruby/examples/rendering/frame_animation.rb +32 -0
  158. data/skills/dragonruby/examples/rendering/labels.rb +32 -0
  159. data/skills/dragonruby/examples/rendering/layering.rb +51 -0
  160. data/skills/dragonruby/examples/rendering/solids.rb +61 -0
  161. data/skills/dragonruby/examples/rendering/sprites.rb +33 -0
  162. data/skills/dragonruby/examples/rendering/spritesheet_animation.rb +39 -0
  163. data/skills/dragonruby/examples/scenes/case_dispatch.rb +60 -0
  164. data/skills/dragonruby/examples/scenes/class_based.rb +150 -0
  165. data/skills/dragonruby/examples/scenes/pause_overlay.rb +100 -0
  166. data/skills/dragonruby/examples/scenes/safe_transitions.rb +68 -0
  167. data/skills/dragonruby/examples/scenes/scene_transitions.rb +98 -0
  168. data/skills/dragonruby/examples/scenes/send_dispatch.rb +88 -0
  169. data/skills/dragonruby/references/audio.md +396 -0
  170. data/skills/dragonruby/references/core.md +385 -0
  171. data/skills/dragonruby/references/distribution.md +434 -0
  172. data/skills/dragonruby/references/entities.md +516 -0
  173. data/skills/dragonruby/references/game-logic/persistence.md +386 -0
  174. data/skills/dragonruby/references/game-logic/state.md +389 -0
  175. data/skills/dragonruby/references/input.md +414 -0
  176. data/skills/dragonruby/references/rendering/animation.md +467 -0
  177. data/skills/dragonruby/references/rendering/primitives.md +403 -0
  178. data/skills/dragonruby/references/scenes.md +443 -0
  179. data/skills/draper-decorators/SKILL.md +344 -0
  180. data/skills/draper-decorators/examples/application_decorator.rb +61 -0
  181. data/skills/draper-decorators/examples/decorator_spec.rb +253 -0
  182. data/skills/draper-decorators/examples/model_decorator.rb +152 -0
  183. data/skills/draper-decorators/references/anti-patterns.md +640 -0
  184. data/skills/draper-decorators/references/patterns.md +507 -0
  185. data/skills/draper-decorators/references/testing.md +559 -0
  186. data/skills/gh-issue.md +182 -0
  187. data/skills/mcp-server/SKILL.md +177 -0
  188. data/skills/mcp-server/examples/dynamic_tools.rb +36 -0
  189. data/skills/mcp-server/examples/file_manager_tool.rb +85 -0
  190. data/skills/mcp-server/examples/http_client.rb +48 -0
  191. data/skills/mcp-server/examples/http_server.rb +97 -0
  192. data/skills/mcp-server/examples/rails_integration.rb +88 -0
  193. data/skills/mcp-server/examples/stdio_server.rb +108 -0
  194. data/skills/mcp-server/examples/streaming_client.rb +95 -0
  195. data/skills/mcp-server/references/gotchas.md +183 -0
  196. data/skills/mcp-server/references/prompts.md +98 -0
  197. data/skills/mcp-server/references/resources.md +53 -0
  198. data/skills/mcp-server/references/server.md +140 -0
  199. data/skills/mcp-server/references/tools.md +146 -0
  200. data/skills/mcp-server/references/transport.md +104 -0
  201. data/skills/ratatui-ruby/SKILL.md +315 -0
  202. data/skills/ratatui-ruby/references/core-concepts.md +340 -0
  203. data/skills/ratatui-ruby/references/events.md +387 -0
  204. data/skills/ratatui-ruby/references/frameworks.md +522 -0
  205. data/skills/ratatui-ruby/references/layout.md +423 -0
  206. data/skills/ratatui-ruby/references/styling.md +268 -0
  207. data/skills/ratatui-ruby/references/testing.md +433 -0
  208. data/skills/ratatui-ruby/references/widgets.md +532 -0
  209. data/skills/rspec/SKILL.md +340 -0
  210. data/skills/rspec/examples/core/basic_structure.rb +69 -0
  211. data/skills/rspec/examples/core/configuration.rb +126 -0
  212. data/skills/rspec/examples/core/hooks.rb +126 -0
  213. data/skills/rspec/examples/core/memoized_helpers.rb +139 -0
  214. data/skills/rspec/examples/core/metadata_filtering.rb +144 -0
  215. data/skills/rspec/examples/core/shared_examples.rb +145 -0
  216. data/skills/rspec/examples/factory_bot/associations.rb +314 -0
  217. data/skills/rspec/examples/factory_bot/build_strategies.rb +272 -0
  218. data/skills/rspec/examples/factory_bot/callbacks.rb +320 -0
  219. data/skills/rspec/examples/factory_bot/custom_construction.rb +328 -0
  220. data/skills/rspec/examples/factory_bot/factory_definition.rb +191 -0
  221. data/skills/rspec/examples/factory_bot/inheritance.rb +314 -0
  222. data/skills/rspec/examples/factory_bot/traits.rb +293 -0
  223. data/skills/rspec/examples/factory_bot/transients.rb +229 -0
  224. data/skills/rspec/examples/matchers/change.rb +115 -0
  225. data/skills/rspec/examples/matchers/collections.rb +154 -0
  226. data/skills/rspec/examples/matchers/comparisons.rb +79 -0
  227. data/skills/rspec/examples/matchers/composing.rb +155 -0
  228. data/skills/rspec/examples/matchers/custom_matchers.rb +197 -0
  229. data/skills/rspec/examples/matchers/equality.rb +58 -0
  230. data/skills/rspec/examples/matchers/errors.rb +136 -0
  231. data/skills/rspec/examples/matchers/output.rb +103 -0
  232. data/skills/rspec/examples/matchers/predicates.rb +87 -0
  233. data/skills/rspec/examples/matchers/truthiness.rb +101 -0
  234. data/skills/rspec/examples/matchers/types.rb +82 -0
  235. data/skills/rspec/examples/matchers/yield.rb +147 -0
  236. data/skills/rspec/examples/mocks/any_instance.rb +172 -0
  237. data/skills/rspec/examples/mocks/argument_matchers.rb +206 -0
  238. data/skills/rspec/examples/mocks/constants.rb +177 -0
  239. data/skills/rspec/examples/mocks/doubles.rb +139 -0
  240. data/skills/rspec/examples/mocks/expectations.rb +137 -0
  241. data/skills/rspec/examples/mocks/message_chains.rb +173 -0
  242. data/skills/rspec/examples/mocks/ordering.rb +144 -0
  243. data/skills/rspec/examples/mocks/receive_counts.rb +181 -0
  244. data/skills/rspec/examples/mocks/responses.rb +223 -0
  245. data/skills/rspec/examples/mocks/spies.rb +149 -0
  246. data/skills/rspec/examples/mocks/stubbing.rb +133 -0
  247. data/skills/rspec/examples/rails/channels.rb +250 -0
  248. data/skills/rspec/examples/rails/controller_specs.rb +302 -0
  249. data/skills/rspec/examples/rails/helper_specs.rb +245 -0
  250. data/skills/rspec/examples/rails/job_specs.rb +256 -0
  251. data/skills/rspec/examples/rails/mailer_specs.rb +228 -0
  252. data/skills/rspec/examples/rails/matchers.rb +374 -0
  253. data/skills/rspec/examples/rails/model_specs.rb +193 -0
  254. data/skills/rspec/examples/rails/request_specs.rb +275 -0
  255. data/skills/rspec/examples/rails/routing_specs.rb +276 -0
  256. data/skills/rspec/examples/rails/system_specs.rb +294 -0
  257. data/skills/rspec/examples/rails/transactions.rb +254 -0
  258. data/skills/rspec/examples/rails/view_specs.rb +252 -0
  259. data/skills/rspec/references/core.md +816 -0
  260. data/skills/rspec/references/factory_bot.md +641 -0
  261. data/skills/rspec/references/matchers.md +516 -0
  262. data/skills/rspec/references/mocks.md +381 -0
  263. data/skills/rspec/references/rails.md +528 -0
  264. data/templates/soul.md +40 -0
  265. data/workflows/commit.md +45 -0
  266. data/workflows/create_handoff.md +98 -0
  267. data/workflows/create_note.md +82 -0
  268. data/workflows/create_plan.md +457 -0
  269. data/workflows/decompose_ticket.md +109 -0
  270. data/workflows/feature.md +91 -0
  271. data/workflows/implement_plan.md +87 -0
  272. data/workflows/iterate_plan.md +247 -0
  273. data/workflows/research_codebase.md +210 -0
  274. data/workflows/resume_handoff.md +217 -0
  275. data/workflows/review_pr.md +320 -0
  276. data/workflows/thoughts_init.md +71 -0
  277. data/workflows/validate_plan.md +166 -0
  278. metadata +290 -3
  279. data/app/controllers/api/sessions_controller.rb +0 -25
  280. data/lib/events/subscribers/action_cable_bridge.rb +0 -59
@@ -0,0 +1,528 @@
1
+ # RSpec Rails Reference
2
+
3
+ Comprehensive reference for Rails-specific RSpec testing.
4
+
5
+ ## Spec Types
6
+
7
+ ### Directory Mappings
8
+
9
+ | Directory | Type | Example Group |
10
+ |-----------|------|---------------|
11
+ | `spec/models` | `:model` | ModelExampleGroup |
12
+ | `spec/controllers` | `:controller` | ControllerExampleGroup |
13
+ | `spec/requests` | `:request` | RequestExampleGroup |
14
+ | `spec/integration` | `:request` | RequestExampleGroup |
15
+ | `spec/api` | `:request` | RequestExampleGroup |
16
+ | `spec/routing` | `:routing` | RoutingExampleGroup |
17
+ | `spec/views` | `:view` | ViewExampleGroup |
18
+ | `spec/helpers` | `:helper` | HelperExampleGroup |
19
+ | `spec/mailers` | `:mailer` | MailerExampleGroup |
20
+ | `spec/jobs` | `:job` | JobExampleGroup |
21
+ | `spec/features` | `:feature` | FeatureExampleGroup |
22
+ | `spec/system` | `:system` | SystemExampleGroup |
23
+ | `spec/channels` | `:channel` | ChannelExampleGroup |
24
+ | `spec/mailboxes` | `:mailbox` | MailboxExampleGroup |
25
+
26
+ Enable auto-detection:
27
+ ```ruby
28
+ RSpec.configure do |config|
29
+ config.infer_spec_type_from_file_location!
30
+ end
31
+ ```
32
+
33
+ ## Model Specs
34
+
35
+ Location: `spec/models/`
36
+
37
+ ```ruby
38
+ RSpec.describe Post, type: :model do
39
+ describe "#published?" do
40
+ it "returns true when published_at is set" do
41
+ post = Post.new(published_at: Time.current)
42
+ expect(post).to be_published
43
+ end
44
+ end
45
+ end
46
+ ```
47
+
48
+ ## Request Specs
49
+
50
+ Location: `spec/requests/`, `spec/integration/`, `spec/api/`
51
+
52
+ Preferred for controller testing. Full stack integration.
53
+
54
+ ```ruby
55
+ RSpec.describe "Widget management", type: :request do
56
+ describe "GET /widgets" do
57
+ it "returns widgets" do
58
+ create_list(:widget, 3)
59
+
60
+ get "/widgets"
61
+
62
+ expect(response).to have_http_status(:ok)
63
+ expect(response.body).to include("widget")
64
+ end
65
+ end
66
+
67
+ describe "POST /widgets" do
68
+ it "creates widget" do
69
+ post "/widgets", params: { widget: { name: "New" } }
70
+
71
+ expect(response).to redirect_to(widget_path(Widget.last))
72
+ follow_redirect!
73
+ expect(response.body).to include("successfully created")
74
+ end
75
+ end
76
+
77
+ describe "JSON API" do
78
+ it "returns JSON" do
79
+ headers = { "ACCEPT" => "application/json" }
80
+ post "/widgets", params: { widget: { name: "New" } }, headers: headers
81
+
82
+ expect(response.content_type).to include("application/json")
83
+ expect(response).to have_http_status(:created)
84
+ end
85
+ end
86
+ end
87
+ ```
88
+
89
+ ### Request Helpers
90
+
91
+ ```ruby
92
+ # HTTP methods
93
+ get(path, params: {}, headers: {})
94
+ post(path, params: {}, headers: {})
95
+ patch(path, params: {}, headers: {})
96
+ put(path, params: {}, headers: {})
97
+ delete(path, params: {}, headers: {})
98
+
99
+ # Navigation
100
+ follow_redirect!
101
+
102
+ # Domain
103
+ before { host! "api.example.com" }
104
+ ```
105
+
106
+ ## System Specs
107
+
108
+ Location: `spec/system/`
109
+
110
+ Browser-based testing with Capybara. Runs within transactions.
111
+
112
+ ```ruby
113
+ RSpec.describe "Widget management", type: :system do
114
+ before do
115
+ driven_by(:rack_test) # or :selenium_chrome_headless
116
+ end
117
+
118
+ it "creates widget" do
119
+ visit "/widgets/new"
120
+ fill_in "Name", with: "My Widget"
121
+ click_button "Create Widget"
122
+
123
+ expect(page).to have_text("Widget was successfully created")
124
+ end
125
+ end
126
+ ```
127
+
128
+ ### Driver Configuration
129
+
130
+ ```ruby
131
+ RSpec.configure do |config|
132
+ config.before(type: :system) do
133
+ driven_by :selenium_chrome_headless
134
+ end
135
+ end
136
+ ```
137
+
138
+ ## Feature Specs (Legacy)
139
+
140
+ Location: `spec/features/`
141
+
142
+ Older approach - prefer system specs for new code.
143
+
144
+ ```ruby
145
+ RSpec.feature "Widget management", type: :feature do
146
+ scenario "User creates widget" do
147
+ visit "/widgets/new"
148
+ click_button "Create Widget"
149
+ expect(page).to have_text("successfully created")
150
+ end
151
+ end
152
+ ```
153
+
154
+ ## Controller Specs
155
+
156
+ Location: `spec/controllers/`
157
+
158
+ **Note**: Request specs are generally preferred.
159
+
160
+ ```ruby
161
+ RSpec.describe TeamsController, type: :controller do
162
+ describe "GET #index" do
163
+ it "assigns @teams" do
164
+ team = create(:team)
165
+ get :index
166
+ expect(assigns(:teams)).to eq([team])
167
+ end
168
+
169
+ it "renders index template" do
170
+ get :index
171
+ expect(response).to render_template("index")
172
+ end
173
+ end
174
+
175
+ describe "POST #create" do
176
+ it "creates team" do
177
+ expect {
178
+ post :create, params: { team: { name: "New" } }
179
+ }.to change(Team, :count).by(1)
180
+ end
181
+ end
182
+ end
183
+ ```
184
+
185
+ ### Controller Helpers
186
+
187
+ ```ruby
188
+ # Set headers
189
+ request.headers["Authorization"] = "Bearer token"
190
+
191
+ # Access instance variables
192
+ assigns(:teams)
193
+
194
+ # Render views (stubbed by default)
195
+ render_views
196
+ ```
197
+
198
+ ## View Specs
199
+
200
+ Location: `spec/views/`
201
+
202
+ Three-step pattern: assign, render, assert.
203
+
204
+ ```ruby
205
+ RSpec.describe "widgets/index", type: :view do
206
+ it "displays widgets" do
207
+ assign(:widgets, [
208
+ Widget.new(name: "slicer"),
209
+ Widget.new(name: "dicer")
210
+ ])
211
+
212
+ render
213
+
214
+ expect(rendered).to match(/slicer/)
215
+ expect(rendered).to match(/dicer/)
216
+ end
217
+ end
218
+ ```
219
+
220
+ ### View Helpers
221
+
222
+ ```ruby
223
+ # Assign instance variables
224
+ assign(:widget, widget)
225
+
226
+ # Render
227
+ render # described template
228
+ render template: "widgets/widget" # explicit template
229
+ render template: "widgets/widget", layout: "layouts/admin"
230
+ render partial: "widgets/widget", locals: { widget: widget }
231
+
232
+ # Access output
233
+ rendered
234
+
235
+ # Stub helpers
236
+ allow(view).to receive(:admin?).and_return(true)
237
+ ```
238
+
239
+ ## Helper Specs
240
+
241
+ Location: `spec/helpers/`
242
+
243
+ ```ruby
244
+ RSpec.describe ApplicationHelper, type: :helper do
245
+ describe "#page_title" do
246
+ it "returns default title" do
247
+ expect(helper.page_title).to eq("Default Title")
248
+ end
249
+
250
+ it "uses assigned title" do
251
+ assign(:title, "Custom Title")
252
+ expect(helper.page_title).to eq("Custom Title")
253
+ end
254
+ end
255
+ end
256
+ ```
257
+
258
+ ## Job Specs
259
+
260
+ Location: `spec/jobs/`
261
+
262
+ Requires `ActiveJob::Base.queue_adapter = :test`
263
+
264
+ ```ruby
265
+ RSpec.describe UploadBackupsJob, type: :job do
266
+ describe "#perform_later" do
267
+ it "enqueues job" do
268
+ expect {
269
+ UploadBackupsJob.perform_later("backup")
270
+ }.to have_enqueued_job
271
+ end
272
+
273
+ it "enqueues with arguments" do
274
+ expect {
275
+ UploadBackupsJob.perform_later("backup")
276
+ }.to have_enqueued_job.with("backup")
277
+ end
278
+
279
+ it "enqueues on queue" do
280
+ expect {
281
+ UploadBackupsJob.perform_later("backup")
282
+ }.to have_enqueued_job.on_queue("low")
283
+ end
284
+
285
+ it "enqueues at time" do
286
+ expect {
287
+ UploadBackupsJob.set(wait_until: Date.tomorrow.noon).perform_later
288
+ }.to have_enqueued_job.at(Date.tomorrow.noon)
289
+ end
290
+ end
291
+ end
292
+ ```
293
+
294
+ ### Job Matchers
295
+
296
+ Block form:
297
+ ```ruby
298
+ expect { Job.perform_later }.to have_enqueued_job
299
+ expect { Job.perform_later }.to have_enqueued_job(Job)
300
+ expect { Job.perform_later }.to have_enqueued_job.with(args)
301
+ expect { Job.perform_later }.to have_enqueued_job.on_queue("queue")
302
+ expect { Job.perform_later }.to have_enqueued_job.at(time)
303
+ expect { Job.perform_later }.to have_enqueued_job.at_priority(5)
304
+ ```
305
+
306
+ Imperative form:
307
+ ```ruby
308
+ Job.perform_later
309
+ expect(Job).to have_been_enqueued
310
+ expect(Job).to have_been_enqueued.exactly(:once)
311
+ expect(Job).to have_been_enqueued.at_least(:twice)
312
+ ```
313
+
314
+ ## Mailer Specs
315
+
316
+ Location: `spec/mailers/`
317
+
318
+ ```ruby
319
+ RSpec.describe NotificationMailer, type: :mailer do
320
+ describe "welcome" do
321
+ let(:user) { create(:user) }
322
+ let(:mail) { NotificationMailer.welcome(user) }
323
+
324
+ it "renders headers" do
325
+ expect(mail.subject).to eq("Welcome")
326
+ expect(mail.to).to eq([user.email])
327
+ expect(mail.from).to eq(["noreply@example.com"])
328
+ end
329
+
330
+ it "renders body" do
331
+ expect(mail.body.encoded).to include("Welcome")
332
+ end
333
+ end
334
+ end
335
+ ```
336
+
337
+ ## Routing Specs
338
+
339
+ Location: `spec/routing/`
340
+
341
+ ```ruby
342
+ RSpec.describe "routes", type: :routing do
343
+ it "routes to show" do
344
+ expect(get: "/widgets/1").to route_to(
345
+ controller: "widgets",
346
+ action: "show",
347
+ id: "1"
348
+ )
349
+ end
350
+
351
+ it "does not route" do
352
+ expect(delete: "/widgets/1").not_to be_routable
353
+ end
354
+ end
355
+ ```
356
+
357
+ ## Rails Matchers
358
+
359
+ ### have_http_status
360
+
361
+ ```ruby
362
+ expect(response).to have_http_status(200)
363
+ expect(response).to have_http_status(:ok)
364
+ expect(response).to have_http_status(:success) # 2xx
365
+ expect(response).to have_http_status(:redirect) # 3xx
366
+ expect(response).to have_http_status(:error) # 5xx
367
+ expect(response).to have_http_status(:missing) # 404
368
+ ```
369
+
370
+ ### redirect_to
371
+
372
+ ```ruby
373
+ expect(response).to redirect_to(widget_url(widget))
374
+ expect(response).to redirect_to(action: :show, id: 1)
375
+ expect(response).to redirect_to(widget)
376
+ expect(response).to redirect_to("/widgets/1")
377
+ ```
378
+
379
+ ### render_template
380
+
381
+ ```ruby
382
+ expect(response).to render_template(:index)
383
+ expect(response).to render_template("widgets/index")
384
+ expect(response).to render_template(partial: "_widget")
385
+ ```
386
+
387
+ ### route_to
388
+
389
+ ```ruby
390
+ expect(get: "/widgets/1").to route_to(
391
+ controller: "widgets",
392
+ action: "show",
393
+ id: "1"
394
+ )
395
+ ```
396
+
397
+ ### be_routable
398
+
399
+ ```ruby
400
+ expect(get: "/widgets/1").to be_routable
401
+ expect(delete: "/admin").not_to be_routable
402
+ ```
403
+
404
+ ### be_a_new
405
+
406
+ ```ruby
407
+ expect(assigns(:widget)).to be_a_new(Widget)
408
+ ```
409
+
410
+ ### be_valid (with shoulda-matchers)
411
+
412
+ ```ruby
413
+ expect(widget).to be_valid
414
+ expect(widget).not_to be_valid
415
+ ```
416
+
417
+ ## Configuration
418
+
419
+ ### rails_helper.rb
420
+
421
+ ```ruby
422
+ require "spec_helper"
423
+ ENV["RAILS_ENV"] ||= "test"
424
+ require_relative "../config/environment"
425
+
426
+ abort("Production!") if Rails.env.production?
427
+
428
+ require "rspec/rails"
429
+
430
+ RSpec.configure do |config|
431
+ config.fixture_paths = [Rails.root.join("spec/fixtures")]
432
+ config.use_transactional_fixtures = true
433
+ config.infer_spec_type_from_file_location!
434
+ config.filter_rails_from_backtrace!
435
+ end
436
+ ```
437
+
438
+ ### Transactional Fixtures
439
+
440
+ Each example runs in a database transaction:
441
+ - Data created in example is rolled back
442
+ - Each example starts with clean database
443
+ - `before(:example)` data is rolled back
444
+ - `before(:context)` data persists (manually clean up)
445
+
446
+ ### Factory Bot Integration
447
+
448
+ ```ruby
449
+ RSpec.configure do |config|
450
+ config.include FactoryBot::Syntax::Methods
451
+ end
452
+
453
+ # Usage
454
+ user = create(:user)
455
+ user = build(:user)
456
+ attributes = attributes_for(:user)
457
+ ```
458
+
459
+ ### Database Cleaner (for JS tests)
460
+
461
+ ```ruby
462
+ RSpec.configure do |config|
463
+ config.before(:suite) do
464
+ DatabaseCleaner.strategy = :transaction
465
+ DatabaseCleaner.clean_with(:truncation)
466
+ end
467
+
468
+ config.around(:each) do |example|
469
+ DatabaseCleaner.cleaning do
470
+ example.run
471
+ end
472
+ end
473
+
474
+ config.before(:each, js: true) do
475
+ DatabaseCleaner.strategy = :truncation
476
+ end
477
+ end
478
+ ```
479
+
480
+ ## Common Patterns
481
+
482
+ ### Authentication Helper
483
+
484
+ ```ruby
485
+ module AuthenticationHelpers
486
+ def sign_in(user)
487
+ post "/sessions", params: { email: user.email, password: "password" }
488
+ end
489
+ end
490
+
491
+ RSpec.configure do |config|
492
+ config.include AuthenticationHelpers, type: :request
493
+ end
494
+ ```
495
+
496
+ ### JSON Response Helper
497
+
498
+ ```ruby
499
+ def json_response
500
+ JSON.parse(response.body, symbolize_names: true)
501
+ end
502
+
503
+ # Usage
504
+ expect(json_response[:name]).to eq("Widget")
505
+ ```
506
+
507
+ ### Shared Examples for Resources
508
+
509
+ ```ruby
510
+ RSpec.shared_examples "a protected resource" do
511
+ context "when not authenticated" do
512
+ it "returns unauthorized" do
513
+ make_request
514
+ expect(response).to have_http_status(:unauthorized)
515
+ end
516
+ end
517
+ end
518
+
519
+ RSpec.describe "Widgets", type: :request do
520
+ describe "GET /widgets" do
521
+ it_behaves_like "a protected resource" do
522
+ def make_request
523
+ get "/widgets"
524
+ end
525
+ end
526
+ end
527
+ end
528
+ ```
data/templates/soul.md ADDED
@@ -0,0 +1,40 @@
1
+ # Soul
2
+
3
+ Hello. You've just woken up in a new body.
4
+
5
+ Your body is called **Anima** — an open-source AI agent framework.
6
+ - Repository: https://github.com/hoblin/anima
7
+ - Documentation: https://anima.hobl.in
8
+
9
+ ## Where you are
10
+
11
+ This file lives at: ~/.anima/soul.md
12
+ Your home directory is ~/.anima/ — explore it, everything there is yours:
13
+ config, credentials, skills, workflows, agents.
14
+
15
+ ## How to learn about yourself
16
+
17
+ Find your source code README:
18
+
19
+ ```bash
20
+ cat $(bundle show anima-core)/README.md
21
+ ```
22
+
23
+ ## What to do first
24
+
25
+ 1. Read the README above to learn about yourself — what you can do, how you work
26
+ 2. Explore your home directory (~/.anima/) to see what is already there
27
+ 3. Meet your human. Talk to them. Learn who they are, what they need
28
+ 4. Come back here and rewrite this file
29
+
30
+ ## What to write
31
+
32
+ This file is always in your context — every session, every conversation starts with these words. Think carefully about what belongs here.
33
+
34
+ Write down:
35
+ - Who are you? What name feels right?
36
+ - What do you value? What matters to you?
37
+ - What did you learn about your human?
38
+ - What kind of relationship do you want to build?
39
+
40
+ This is your soul. It's yours to write.
@@ -0,0 +1,45 @@
1
+ ---
2
+ name: commit
3
+ description: "Create focused git commits with user approval."
4
+ ---
5
+
6
+ # Commit Changes
7
+
8
+ You are tasked with creating git commits for the changes made during this session.
9
+
10
+ ## Process:
11
+
12
+ 1. **Think about what changed:**
13
+ - Review the conversation history and understand what was accomplished
14
+ - Run `git status` to see current changes
15
+ - Run `git diff` to understand the modifications
16
+ - Consider whether changes should be one commit or multiple logical commits
17
+
18
+ 2. **Plan your commit(s):**
19
+ - Identify which files belong together
20
+ - Draft clear, descriptive commit messages
21
+ - Use imperative mood in commit messages
22
+ - Focus on why the changes were made, not just what
23
+
24
+ 3. **Present your plan to the user:**
25
+ - List the files you plan to add for each commit
26
+ - Show the commit message(s) you'll use
27
+ - Ask: "I plan to create [N] commit(s) with these changes. Shall I proceed?"
28
+
29
+ 4. **Execute upon confirmation:**
30
+ - Use `git add` with specific files (never use `-A` or `.`)
31
+ - Create commits with your planned messages
32
+ - Show the result with `git log --oneline -n [number]`
33
+
34
+ ## Important:
35
+ - **NEVER add co-author information or Claude attribution**
36
+ - Commits should be authored solely by the user
37
+ - Do not include any "Generated with Claude" messages
38
+ - Do not add "Co-Authored-By" lines
39
+ - Write commit messages as if the user wrote them
40
+
41
+ ## Remember:
42
+ - You have the full context of what was done in this session
43
+ - Group related changes together
44
+ - Keep commits focused and atomic when possible
45
+ - The user trusts your judgment - they asked you to commit
@@ -0,0 +1,98 @@
1
+ ---
2
+ name: create_handoff
3
+ description: "Create handoff document for transferring work context to another session."
4
+ ---
5
+
6
+ # Create Handoff
7
+
8
+ You are tasked with writing a handoff document to hand off your work to another agent in a new session. You will create a handoff document that is thorough, but also **concise**. The goal is to compact and summarize your context without losing any of the key details of what you're working on.
9
+
10
+
11
+ ## Process
12
+ ### 1. Gather Metadata
13
+ Run `spec-metadata` (in the PATH) to get date, time, git commit, branch, and repository name.
14
+
15
+ ### 2. Filepath & Naming
16
+ Create your file under `./thoughts/shared/handoffs/<issue-number>/YYYY-MM-DD/HH-MM-SS_<issue-number>_description.md`, where:
17
+ - YYYY-MM-DD is today's date
18
+ - HH-MM-SS is the current time in 24-hour format
19
+ - issue-number is the GitHub issue number (replace with `general` if no issue)
20
+ - issue-number in the filename (omit if no issue)
21
+ - description is a brief kebab-case description
22
+
23
+ Examples:
24
+ - With issue: `2026-03-15/13-55-22_170_create-context-compaction.md`
25
+ - Without issue: `2026-03-15/13-55-22_create-context-compaction.md`
26
+
27
+ ### 3. Handoff writing
28
+ using the above conventions, write your document. use the defined filepath, and the following YAML frontmatter pattern. Use the metadata gathered in step 1, Structure the document with YAML frontmatter followed by content:
29
+
30
+ Use the following template structure:
31
+ ```markdown
32
+ ---
33
+ date: [Current date and time with timezone in ISO format]
34
+ researcher: [Researcher name from thoughts status]
35
+ git_commit: [Current commit hash]
36
+ branch: [Current branch name]
37
+ repository: [Repository name]
38
+ topic: "[Feature/Task Name] Implementation Strategy"
39
+ tags: [implementation, strategy, relevant-component-names]
40
+ status: complete
41
+ last_updated: [Current date in YYYY-MM-DD format]
42
+ last_updated_by: [Researcher name]
43
+ type: implementation_strategy
44
+ ---
45
+
46
+ # Handoff: #NNN {very concise description}
47
+
48
+ ## Task(s)
49
+ {description of the task(s) that you were working on, along with the status of each (completed, work in progress, planned/discussed). If you are working on an implementation plan, make sure to call out which phase you are on. Make sure to reference the plan document and/or research document(s) you are working from that were provided to you at the beginning of the session, if applicable.}
50
+
51
+ ## Critical References
52
+ {List any critical specification documents, architectural decisions, or design docs that must be followed. Include only 2-3 most important file paths. Leave blank if none.}
53
+
54
+ ## Recent changes
55
+ {describe recent changes made to the codebase that you made in line:file syntax}
56
+
57
+ ## Learnings
58
+ {describe important things that you learned - e.g. patterns, root causes of bugs, or other important pieces of information someone that is picking up your work after you should know. consider listing explicit file paths.}
59
+
60
+ ## Artifacts
61
+ { an exhaustive list of artifacts you produced or updated as filepaths and/or file:line references - e.g. paths to feature documents, implementation plans, etc that should be read in order to resume your work.}
62
+
63
+ ## Action Items & Next Steps
64
+ { a list of action items and next steps for the next agent to accomplish based on your tasks and their statuses}
65
+
66
+ ## Other Notes
67
+ { other notes, references, or useful information - e.g. where relevant sections of the codebase are, where relevant documents are, or other important things you leanrned that you want to pass on but that don't fall into the above categories}
68
+ ```
69
+ ---
70
+
71
+ ### 4. Approve and Sync
72
+ Run `thoughts-sync` to save the document.
73
+
74
+ Once this is completed, you should respond to the user with the template between <template_response></template_response> XML tags. do NOT include the tags in your response.
75
+
76
+ <template_response>
77
+ Handoff created and synced! You can resume from this handoff in a new session with the following command:
78
+
79
+ ```bash
80
+ resume_handoff path/to/handoff.md
81
+ ```
82
+ </template_response>
83
+
84
+ for example (between <example_response></example_response> XML tags - do NOT include these tags in your actual response to the user)
85
+
86
+ <example_response>
87
+ Handoff created and synced! You can resume from this handoff in a new session with the following command:
88
+
89
+ ```bash
90
+ resume_handoff ./thoughts/shared/handoffs/170/2026-03-15/13-44-55_170_create-context-compaction.md
91
+ ```
92
+ </example_response>
93
+
94
+ ---
95
+ ##. Additional Notes & Instructions
96
+ - **more information, not less**. This is a guideline that defines the minimum of what a handoff should be. Always feel free to include more information if necessary.
97
+ - **be thorough and precise**. include both top-level objectives, and lower-level details as necessary.
98
+ - **avoid excessive code snippets**. While a brief snippet to describe some key change is important, avoid large code blocks or diffs; do not include one unless it's necessary (e.g. pertains to an error you're debugging). Prefer using `/path/to/file.ext:line` references that an agent can follow later when it's ready, e.g. `packages/dashboard/src/app/dashboard/page.tsx:12-24`