lex-agentic-affect 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (218) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +12 -0
  3. data/Gemfile +5 -0
  4. data/LICENSE +21 -0
  5. data/README.md +13 -0
  6. data/lex-agentic-affect.gemspec +30 -0
  7. data/lib/legion/extensions/agentic/affect/appraisal/client.rb +20 -0
  8. data/lib/legion/extensions/agentic/affect/appraisal/helpers/appraisal.rb +112 -0
  9. data/lib/legion/extensions/agentic/affect/appraisal/helpers/appraisal_engine.rb +129 -0
  10. data/lib/legion/extensions/agentic/affect/appraisal/helpers/constants.rb +43 -0
  11. data/lib/legion/extensions/agentic/affect/appraisal/runners/appraisal.rb +105 -0
  12. data/lib/legion/extensions/agentic/affect/appraisal/version.rb +13 -0
  13. data/lib/legion/extensions/agentic/affect/appraisal.rb +19 -0
  14. data/lib/legion/extensions/agentic/affect/cognitive_empathy/client.rb +19 -0
  15. data/lib/legion/extensions/agentic/affect/cognitive_empathy/helpers/constants.rb +37 -0
  16. data/lib/legion/extensions/agentic/affect/cognitive_empathy/helpers/empathy_engine.rb +151 -0
  17. data/lib/legion/extensions/agentic/affect/cognitive_empathy/helpers/perspective.rb +92 -0
  18. data/lib/legion/extensions/agentic/affect/cognitive_empathy/runners/cognitive_empathy.rb +93 -0
  19. data/lib/legion/extensions/agentic/affect/cognitive_empathy/version.rb +13 -0
  20. data/lib/legion/extensions/agentic/affect/cognitive_empathy.rb +20 -0
  21. data/lib/legion/extensions/agentic/affect/contagion/client.rb +28 -0
  22. data/lib/legion/extensions/agentic/affect/contagion/helpers/constants.rb +34 -0
  23. data/lib/legion/extensions/agentic/affect/contagion/helpers/contagion_engine.rb +184 -0
  24. data/lib/legion/extensions/agentic/affect/contagion/helpers/meme.rb +97 -0
  25. data/lib/legion/extensions/agentic/affect/contagion/runners/cognitive_contagion.rb +125 -0
  26. data/lib/legion/extensions/agentic/affect/contagion/version.rb +13 -0
  27. data/lib/legion/extensions/agentic/affect/contagion.rb +19 -0
  28. data/lib/legion/extensions/agentic/affect/defusion/client.rb +28 -0
  29. data/lib/legion/extensions/agentic/affect/defusion/helpers/constants.rb +64 -0
  30. data/lib/legion/extensions/agentic/affect/defusion/helpers/defusion_engine.rb +167 -0
  31. data/lib/legion/extensions/agentic/affect/defusion/helpers/thought.rb +92 -0
  32. data/lib/legion/extensions/agentic/affect/defusion/runners/cognitive_defusion.rb +127 -0
  33. data/lib/legion/extensions/agentic/affect/defusion/version.rb +13 -0
  34. data/lib/legion/extensions/agentic/affect/defusion.rb +19 -0
  35. data/lib/legion/extensions/agentic/affect/emotion/actors/momentum_decay.rb +45 -0
  36. data/lib/legion/extensions/agentic/affect/emotion/client.rb +36 -0
  37. data/lib/legion/extensions/agentic/affect/emotion/helpers/baseline.rb +52 -0
  38. data/lib/legion/extensions/agentic/affect/emotion/helpers/momentum.rb +52 -0
  39. data/lib/legion/extensions/agentic/affect/emotion/helpers/valence.rb +92 -0
  40. data/lib/legion/extensions/agentic/affect/emotion/runners/gut.rb +102 -0
  41. data/lib/legion/extensions/agentic/affect/emotion/runners/valence.rb +120 -0
  42. data/lib/legion/extensions/agentic/affect/emotion/version.rb +13 -0
  43. data/lib/legion/extensions/agentic/affect/emotion.rb +20 -0
  44. data/lib/legion/extensions/agentic/affect/empathy/client.rb +21 -0
  45. data/lib/legion/extensions/agentic/affect/empathy/helpers/constants.rb +54 -0
  46. data/lib/legion/extensions/agentic/affect/empathy/helpers/mental_model.rb +185 -0
  47. data/lib/legion/extensions/agentic/affect/empathy/helpers/model_store.rb +88 -0
  48. data/lib/legion/extensions/agentic/affect/empathy/runners/empathy.rb +173 -0
  49. data/lib/legion/extensions/agentic/affect/empathy/version.rb +13 -0
  50. data/lib/legion/extensions/agentic/affect/empathy.rb +20 -0
  51. data/lib/legion/extensions/agentic/affect/fatigue/client.rb +26 -0
  52. data/lib/legion/extensions/agentic/affect/fatigue/helpers/constants.rb +54 -0
  53. data/lib/legion/extensions/agentic/affect/fatigue/helpers/energy_model.rb +181 -0
  54. data/lib/legion/extensions/agentic/affect/fatigue/helpers/fatigue_store.rb +146 -0
  55. data/lib/legion/extensions/agentic/affect/fatigue/runners/fatigue.rb +89 -0
  56. data/lib/legion/extensions/agentic/affect/fatigue/version.rb +13 -0
  57. data/lib/legion/extensions/agentic/affect/fatigue.rb +19 -0
  58. data/lib/legion/extensions/agentic/affect/flow/client.rb +25 -0
  59. data/lib/legion/extensions/agentic/affect/flow/helpers/constants.rb +84 -0
  60. data/lib/legion/extensions/agentic/affect/flow/helpers/flow_detector.rb +166 -0
  61. data/lib/legion/extensions/agentic/affect/flow/runners/flow.rb +129 -0
  62. data/lib/legion/extensions/agentic/affect/flow/version.rb +13 -0
  63. data/lib/legion/extensions/agentic/affect/flow.rb +18 -0
  64. data/lib/legion/extensions/agentic/affect/interoception/actors/decay.rb +45 -0
  65. data/lib/legion/extensions/agentic/affect/interoception/client.rb +28 -0
  66. data/lib/legion/extensions/agentic/affect/interoception/helpers/body_budget.rb +152 -0
  67. data/lib/legion/extensions/agentic/affect/interoception/helpers/constants.rb +68 -0
  68. data/lib/legion/extensions/agentic/affect/interoception/helpers/somatic_marker.rb +75 -0
  69. data/lib/legion/extensions/agentic/affect/interoception/runners/interoception.rb +101 -0
  70. data/lib/legion/extensions/agentic/affect/interoception/version.rb +13 -0
  71. data/lib/legion/extensions/agentic/affect/interoception.rb +20 -0
  72. data/lib/legion/extensions/agentic/affect/mood/client.rb +21 -0
  73. data/lib/legion/extensions/agentic/affect/mood/helpers/constants.rb +78 -0
  74. data/lib/legion/extensions/agentic/affect/mood/helpers/mood_state.rb +154 -0
  75. data/lib/legion/extensions/agentic/affect/mood/runners/mood.rb +122 -0
  76. data/lib/legion/extensions/agentic/affect/mood/version.rb +13 -0
  77. data/lib/legion/extensions/agentic/affect/mood.rb +18 -0
  78. data/lib/legion/extensions/agentic/affect/motivation/client.rb +26 -0
  79. data/lib/legion/extensions/agentic/affect/motivation/helpers/constants.rb +48 -0
  80. data/lib/legion/extensions/agentic/affect/motivation/helpers/drive_state.rb +98 -0
  81. data/lib/legion/extensions/agentic/affect/motivation/helpers/motivation_store.rb +106 -0
  82. data/lib/legion/extensions/agentic/affect/motivation/runners/motivation.rb +165 -0
  83. data/lib/legion/extensions/agentic/affect/motivation/version.rb +13 -0
  84. data/lib/legion/extensions/agentic/affect/motivation.rb +19 -0
  85. data/lib/legion/extensions/agentic/affect/reappraisal/actors/auto_regulate.rb +45 -0
  86. data/lib/legion/extensions/agentic/affect/reappraisal/client.rb +28 -0
  87. data/lib/legion/extensions/agentic/affect/reappraisal/helpers/constants.rb +82 -0
  88. data/lib/legion/extensions/agentic/affect/reappraisal/helpers/emotional_event.rb +98 -0
  89. data/lib/legion/extensions/agentic/affect/reappraisal/helpers/llm_enhancer.rb +88 -0
  90. data/lib/legion/extensions/agentic/affect/reappraisal/helpers/reappraisal_engine.rb +153 -0
  91. data/lib/legion/extensions/agentic/affect/reappraisal/runners/cognitive_reappraisal.rb +164 -0
  92. data/lib/legion/extensions/agentic/affect/reappraisal/version.rb +13 -0
  93. data/lib/legion/extensions/agentic/affect/reappraisal.rb +20 -0
  94. data/lib/legion/extensions/agentic/affect/regulation/client.rb +25 -0
  95. data/lib/legion/extensions/agentic/affect/regulation/helpers/constants.rb +71 -0
  96. data/lib/legion/extensions/agentic/affect/regulation/helpers/regulation_model.rb +175 -0
  97. data/lib/legion/extensions/agentic/affect/regulation/runners/emotional_regulation.rb +127 -0
  98. data/lib/legion/extensions/agentic/affect/regulation/version.rb +13 -0
  99. data/lib/legion/extensions/agentic/affect/regulation.rb +18 -0
  100. data/lib/legion/extensions/agentic/affect/resilience/client.rb +27 -0
  101. data/lib/legion/extensions/agentic/affect/resilience/helpers/adversity_tracker.rb +130 -0
  102. data/lib/legion/extensions/agentic/affect/resilience/helpers/constants.rb +79 -0
  103. data/lib/legion/extensions/agentic/affect/resilience/helpers/resilience_model.rb +165 -0
  104. data/lib/legion/extensions/agentic/affect/resilience/runners/resilience.rb +150 -0
  105. data/lib/legion/extensions/agentic/affect/resilience/version.rb +13 -0
  106. data/lib/legion/extensions/agentic/affect/resilience.rb +19 -0
  107. data/lib/legion/extensions/agentic/affect/resonance/client.rb +24 -0
  108. data/lib/legion/extensions/agentic/affect/resonance/helpers/category.rb +75 -0
  109. data/lib/legion/extensions/agentic/affect/resonance/helpers/constants.rb +47 -0
  110. data/lib/legion/extensions/agentic/affect/resonance/helpers/resonance_engine.rb +115 -0
  111. data/lib/legion/extensions/agentic/affect/resonance/runners/cognitive_resonance.rb +94 -0
  112. data/lib/legion/extensions/agentic/affect/resonance/version.rb +13 -0
  113. data/lib/legion/extensions/agentic/affect/resonance.rb +19 -0
  114. data/lib/legion/extensions/agentic/affect/reward/client.rb +26 -0
  115. data/lib/legion/extensions/agentic/affect/reward/helpers/constants.rb +67 -0
  116. data/lib/legion/extensions/agentic/affect/reward/helpers/reward_signal.rb +178 -0
  117. data/lib/legion/extensions/agentic/affect/reward/helpers/reward_store.rb +142 -0
  118. data/lib/legion/extensions/agentic/affect/reward/runners/reward.rb +92 -0
  119. data/lib/legion/extensions/agentic/affect/reward/version.rb +13 -0
  120. data/lib/legion/extensions/agentic/affect/reward.rb +19 -0
  121. data/lib/legion/extensions/agentic/affect/somatic_marker/actors/decay.rb +45 -0
  122. data/lib/legion/extensions/agentic/affect/somatic_marker/client.rb +29 -0
  123. data/lib/legion/extensions/agentic/affect/somatic_marker/helpers/body_state.rb +69 -0
  124. data/lib/legion/extensions/agentic/affect/somatic_marker/helpers/constants.rb +43 -0
  125. data/lib/legion/extensions/agentic/affect/somatic_marker/helpers/marker_store.rb +160 -0
  126. data/lib/legion/extensions/agentic/affect/somatic_marker/helpers/somatic_marker.rb +74 -0
  127. data/lib/legion/extensions/agentic/affect/somatic_marker/runners/somatic_marker.rb +132 -0
  128. data/lib/legion/extensions/agentic/affect/somatic_marker/version.rb +13 -0
  129. data/lib/legion/extensions/agentic/affect/somatic_marker.rb +20 -0
  130. data/lib/legion/extensions/agentic/affect/version.rb +11 -0
  131. data/lib/legion/extensions/agentic/affect.rb +34 -0
  132. data/spec/legion/extensions/agentic/affect/appraisal/client_spec.rb +52 -0
  133. data/spec/legion/extensions/agentic/affect/appraisal/helpers/appraisal_engine_spec.rb +161 -0
  134. data/spec/legion/extensions/agentic/affect/appraisal/helpers/appraisal_spec.rb +175 -0
  135. data/spec/legion/extensions/agentic/affect/appraisal/helpers/constants_spec.rb +49 -0
  136. data/spec/legion/extensions/agentic/affect/appraisal/runners/appraisal_spec.rb +116 -0
  137. data/spec/legion/extensions/agentic/affect/cognitive_empathy/client_spec.rb +62 -0
  138. data/spec/legion/extensions/agentic/affect/cognitive_empathy/helpers/empathy_engine_spec.rb +316 -0
  139. data/spec/legion/extensions/agentic/affect/cognitive_empathy/helpers/perspective_spec.rb +132 -0
  140. data/spec/legion/extensions/agentic/affect/cognitive_empathy/runners/cognitive_empathy_spec.rb +200 -0
  141. data/spec/legion/extensions/agentic/affect/contagion/client_spec.rb +63 -0
  142. data/spec/legion/extensions/agentic/affect/contagion/helpers/constants_spec.rb +86 -0
  143. data/spec/legion/extensions/agentic/affect/contagion/helpers/contagion_engine_spec.rb +241 -0
  144. data/spec/legion/extensions/agentic/affect/contagion/helpers/meme_spec.rb +160 -0
  145. data/spec/legion/extensions/agentic/affect/contagion/runners/cognitive_contagion_spec.rb +211 -0
  146. data/spec/legion/extensions/agentic/affect/defusion/client_spec.rb +80 -0
  147. data/spec/legion/extensions/agentic/affect/defusion/helpers/constants_spec.rb +84 -0
  148. data/spec/legion/extensions/agentic/affect/defusion/helpers/defusion_engine_spec.rb +250 -0
  149. data/spec/legion/extensions/agentic/affect/defusion/helpers/thought_spec.rb +178 -0
  150. data/spec/legion/extensions/agentic/affect/defusion/runners/cognitive_defusion_spec.rb +185 -0
  151. data/spec/legion/extensions/agentic/affect/emotion/actors/momentum_decay_spec.rb +46 -0
  152. data/spec/legion/extensions/agentic/affect/emotion/client_spec.rb +46 -0
  153. data/spec/legion/extensions/agentic/affect/emotion/helpers/baseline_spec.rb +48 -0
  154. data/spec/legion/extensions/agentic/affect/emotion/helpers/momentum_spec.rb +45 -0
  155. data/spec/legion/extensions/agentic/affect/emotion/helpers/valence_spec.rb +91 -0
  156. data/spec/legion/extensions/agentic/affect/emotion/runners/gut_spec.rb +73 -0
  157. data/spec/legion/extensions/agentic/affect/emotion/runners/valence_spec.rb +67 -0
  158. data/spec/legion/extensions/agentic/affect/empathy/client_spec.rb +20 -0
  159. data/spec/legion/extensions/agentic/affect/empathy/helpers/constants_spec.rb +23 -0
  160. data/spec/legion/extensions/agentic/affect/empathy/helpers/mental_model_spec.rb +150 -0
  161. data/spec/legion/extensions/agentic/affect/empathy/helpers/model_store_spec.rb +94 -0
  162. data/spec/legion/extensions/agentic/affect/empathy/runners/empathy_spec.rb +127 -0
  163. data/spec/legion/extensions/agentic/affect/fatigue/client_spec.rb +66 -0
  164. data/spec/legion/extensions/agentic/affect/fatigue/helpers/constants_spec.rb +130 -0
  165. data/spec/legion/extensions/agentic/affect/fatigue/helpers/energy_model_spec.rb +281 -0
  166. data/spec/legion/extensions/agentic/affect/fatigue/helpers/fatigue_store_spec.rb +157 -0
  167. data/spec/legion/extensions/agentic/affect/fatigue/runners/fatigue_spec.rb +127 -0
  168. data/spec/legion/extensions/agentic/affect/flow/client_spec.rb +58 -0
  169. data/spec/legion/extensions/agentic/affect/flow/helpers/constants_spec.rb +112 -0
  170. data/spec/legion/extensions/agentic/affect/flow/helpers/flow_detector_spec.rb +268 -0
  171. data/spec/legion/extensions/agentic/affect/flow/runners/flow_spec.rb +222 -0
  172. data/spec/legion/extensions/agentic/affect/interoception/client_spec.rb +52 -0
  173. data/spec/legion/extensions/agentic/affect/interoception/helpers/body_budget_spec.rb +178 -0
  174. data/spec/legion/extensions/agentic/affect/interoception/helpers/somatic_marker_spec.rb +120 -0
  175. data/spec/legion/extensions/agentic/affect/interoception/runners/interoception_spec.rb +108 -0
  176. data/spec/legion/extensions/agentic/affect/mood/client_spec.rb +20 -0
  177. data/spec/legion/extensions/agentic/affect/mood/helpers/constants_spec.rb +29 -0
  178. data/spec/legion/extensions/agentic/affect/mood/helpers/mood_state_spec.rb +94 -0
  179. data/spec/legion/extensions/agentic/affect/mood/runners/mood_spec.rb +71 -0
  180. data/spec/legion/extensions/agentic/affect/motivation/client_spec.rb +35 -0
  181. data/spec/legion/extensions/agentic/affect/motivation/helpers/constants_spec.rb +111 -0
  182. data/spec/legion/extensions/agentic/affect/motivation/helpers/drive_state_spec.rb +183 -0
  183. data/spec/legion/extensions/agentic/affect/motivation/helpers/motivation_store_spec.rb +185 -0
  184. data/spec/legion/extensions/agentic/affect/motivation/runners/motivation_spec.rb +248 -0
  185. data/spec/legion/extensions/agentic/affect/reappraisal/actors/auto_regulate_spec.rb +46 -0
  186. data/spec/legion/extensions/agentic/affect/reappraisal/client_spec.rb +64 -0
  187. data/spec/legion/extensions/agentic/affect/reappraisal/helpers/constants_spec.rb +102 -0
  188. data/spec/legion/extensions/agentic/affect/reappraisal/helpers/emotional_event_spec.rb +177 -0
  189. data/spec/legion/extensions/agentic/affect/reappraisal/helpers/llm_enhancer_spec.rb +161 -0
  190. data/spec/legion/extensions/agentic/affect/reappraisal/helpers/reappraisal_engine_spec.rb +211 -0
  191. data/spec/legion/extensions/agentic/affect/reappraisal/runners/cognitive_reappraisal_spec.rb +312 -0
  192. data/spec/legion/extensions/agentic/affect/regulation/client_spec.rb +61 -0
  193. data/spec/legion/extensions/agentic/affect/regulation/helpers/constants_spec.rb +108 -0
  194. data/spec/legion/extensions/agentic/affect/regulation/helpers/regulation_model_spec.rb +200 -0
  195. data/spec/legion/extensions/agentic/affect/regulation/runners/emotional_regulation_spec.rb +190 -0
  196. data/spec/legion/extensions/agentic/affect/resilience/client_spec.rb +36 -0
  197. data/spec/legion/extensions/agentic/affect/resilience/helpers/adversity_tracker_spec.rb +164 -0
  198. data/spec/legion/extensions/agentic/affect/resilience/helpers/constants_spec.rb +78 -0
  199. data/spec/legion/extensions/agentic/affect/resilience/helpers/resilience_model_spec.rb +133 -0
  200. data/spec/legion/extensions/agentic/affect/resilience/runners/resilience_spec.rb +150 -0
  201. data/spec/legion/extensions/agentic/affect/resonance/client_spec.rb +66 -0
  202. data/spec/legion/extensions/agentic/affect/resonance/cognitive_resonance_spec.rb +27 -0
  203. data/spec/legion/extensions/agentic/affect/resonance/helpers/category_spec.rb +146 -0
  204. data/spec/legion/extensions/agentic/affect/resonance/helpers/constants_spec.rb +104 -0
  205. data/spec/legion/extensions/agentic/affect/resonance/helpers/resonance_engine_spec.rb +189 -0
  206. data/spec/legion/extensions/agentic/affect/resonance/runners/cognitive_resonance_spec.rb +197 -0
  207. data/spec/legion/extensions/agentic/affect/reward/client_spec.rb +42 -0
  208. data/spec/legion/extensions/agentic/affect/reward/helpers/constants_spec.rb +91 -0
  209. data/spec/legion/extensions/agentic/affect/reward/helpers/reward_signal_spec.rb +296 -0
  210. data/spec/legion/extensions/agentic/affect/reward/helpers/reward_store_spec.rb +167 -0
  211. data/spec/legion/extensions/agentic/affect/reward/runners/reward_spec.rb +149 -0
  212. data/spec/legion/extensions/agentic/affect/somatic_marker/client_spec.rb +83 -0
  213. data/spec/legion/extensions/agentic/affect/somatic_marker/helpers/body_state_spec.rb +155 -0
  214. data/spec/legion/extensions/agentic/affect/somatic_marker/helpers/marker_store_spec.rb +233 -0
  215. data/spec/legion/extensions/agentic/affect/somatic_marker/helpers/somatic_marker_spec.rb +172 -0
  216. data/spec/legion/extensions/agentic/affect/somatic_marker/runners/somatic_marker_spec.rb +181 -0
  217. data/spec/spec_helper.rb +46 -0
  218. metadata +302 -0
@@ -0,0 +1,161 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Affect::Appraisal::Helpers::AppraisalEngine do
4
+ let(:engine) { described_class.new }
5
+
6
+ let(:primary_joy) { { relevance: 0.9, goal_congruence: 0.9, goal_importance: 0.8 } }
7
+ let(:primary_threat) { { relevance: 0.9, goal_congruence: 0.3, goal_importance: 0.8 } }
8
+ let(:secondary_low) { { coping_potential: 0.2, control_expectation: 0.3, future_expectancy: 0.4 } }
9
+ let(:secondary_high) { { coping_potential: 0.8, control_expectation: 0.7, future_expectancy: 0.6 } }
10
+
11
+ describe '#appraise' do
12
+ it 'creates and returns an appraisal' do
13
+ record = engine.appraise(event: 'test', primary: primary_joy, secondary: secondary_high)
14
+ expect(record).to be_a(Legion::Extensions::Agentic::Affect::Appraisal::Helpers::Appraisal)
15
+ expect(record.event).to eq('test')
16
+ end
17
+
18
+ it 'stores appraisals by id' do
19
+ record = engine.appraise(event: 'test', primary: primary_joy, secondary: secondary_high)
20
+ expect(engine.to_h[:appraisals]).to have_key(record.id)
21
+ end
22
+
23
+ it 'computes emotional_outcome' do
24
+ record = engine.appraise(event: 'test', primary: primary_joy, secondary: secondary_high)
25
+ expect(record.emotional_outcome).to eq(:joy)
26
+ end
27
+ end
28
+
29
+ describe '#reappraise' do
30
+ it 'updates an existing appraisal' do
31
+ record = engine.appraise(event: 'test', primary: primary_threat, secondary: secondary_low)
32
+ expect(record.emotional_outcome).to eq(:anxiety)
33
+ updated = engine.reappraise(appraisal_id: record.id, new_primary: primary_joy, new_secondary: secondary_high)
34
+ expect(updated.emotional_outcome).to eq(:joy)
35
+ expect(updated.reappraised).to be(true)
36
+ end
37
+
38
+ it 'returns nil for unknown id' do
39
+ result = engine.reappraise(appraisal_id: 'unknown', new_primary: primary_joy, new_secondary: secondary_high)
40
+ expect(result).to be_nil
41
+ end
42
+ end
43
+
44
+ describe '#select_coping' do
45
+ it 'assigns coping to appraisal' do
46
+ record = engine.appraise(event: 'test', primary: primary_threat, secondary: secondary_low)
47
+ updated = engine.select_coping(appraisal_id: record.id, coping_type: :problem_focused)
48
+ expect(updated.coping_strategy).not_to be_nil
49
+ end
50
+
51
+ it 'prefers registered strategies for the coping type' do
52
+ engine.add_coping_strategy(name: 'action_plan', coping_type: :problem_focused, effectiveness: 0.9)
53
+ record = engine.appraise(event: 'test', primary: primary_threat, secondary: secondary_low)
54
+ updated = engine.select_coping(appraisal_id: record.id, coping_type: :problem_focused)
55
+ expect(updated.coping_strategy).to eq('action_plan')
56
+ end
57
+
58
+ it 'returns nil for unknown appraisal' do
59
+ result = engine.select_coping(appraisal_id: 'unknown', coping_type: :problem_focused)
60
+ expect(result).to be_nil
61
+ end
62
+ end
63
+
64
+ describe '#add_coping_strategy' do
65
+ it 'registers a strategy and returns true' do
66
+ result = engine.add_coping_strategy(name: 'reframing', coping_type: :emotion_focused, effectiveness: 0.7)
67
+ expect(result).to be(true)
68
+ end
69
+
70
+ it 'clamps effectiveness to [0, 1]' do
71
+ engine.add_coping_strategy(name: 'over', coping_type: :problem_focused, effectiveness: 1.5)
72
+ data = engine.to_h
73
+ # Strategy stored (engine is internal, test via evaluate_coping behavior)
74
+ expect(data).to be_a(Hash)
75
+ end
76
+ end
77
+
78
+ describe '#evaluate_coping' do
79
+ it 'returns effectiveness 0.0 when no coping assigned' do
80
+ record = engine.appraise(event: 'test', primary: primary_threat, secondary: secondary_low)
81
+ result = engine.evaluate_coping(appraisal_id: record.id)
82
+ expect(result[:effectiveness]).to eq(0.0)
83
+ expect(result[:resolved]).to be(false)
84
+ end
85
+
86
+ it 'uses registered strategy effectiveness' do
87
+ engine.add_coping_strategy(name: 'mindfulness', coping_type: :emotion_focused, effectiveness: 0.85)
88
+ record = engine.appraise(event: 'test', primary: primary_threat, secondary: secondary_low)
89
+ engine.select_coping(appraisal_id: record.id, coping_type: :emotion_focused)
90
+ result = engine.evaluate_coping(appraisal_id: record.id)
91
+ expect(result[:effectiveness]).to be_within(0.01).of(0.85)
92
+ end
93
+
94
+ it 'returns defaults for unknown appraisal' do
95
+ result = engine.evaluate_coping(appraisal_id: 'unknown')
96
+ expect(result[:effectiveness]).to eq(0.0)
97
+ end
98
+ end
99
+
100
+ describe '#by_emotion' do
101
+ it 'filters appraisals by emotional outcome' do
102
+ engine.appraise(event: 'a', primary: primary_joy, secondary: secondary_high)
103
+ engine.appraise(event: 'b', primary: primary_threat, secondary: secondary_low)
104
+ joy_list = engine.by_emotion(emotion: :joy)
105
+ expect(joy_list.size).to eq(1)
106
+ expect(joy_list.first.event).to eq('a')
107
+ end
108
+ end
109
+
110
+ describe '#by_domain' do
111
+ it 'filters appraisals by domain' do
112
+ engine.appraise(event: 'a', primary: primary_joy, secondary: secondary_high, domain: 'work')
113
+ engine.appraise(event: 'b', primary: primary_joy, secondary: secondary_high, domain: 'personal')
114
+ work_list = engine.by_domain(domain: 'work')
115
+ expect(work_list.size).to eq(1)
116
+ expect(work_list.first.event).to eq('a')
117
+ end
118
+ end
119
+
120
+ describe '#unresolved' do
121
+ it 'returns appraisals without coping strategy' do
122
+ rec1 = engine.appraise(event: 'a', primary: primary_joy, secondary: secondary_high)
123
+ rec2 = engine.appraise(event: 'b', primary: primary_joy, secondary: secondary_high)
124
+ engine.select_coping(appraisal_id: rec1.id, coping_type: :problem_focused)
125
+ unresolved = engine.unresolved
126
+ expect(unresolved.map(&:id)).to include(rec2.id)
127
+ expect(unresolved.map(&:id)).not_to include(rec1.id)
128
+ end
129
+ end
130
+
131
+ describe '#emotional_pattern' do
132
+ it 'returns emotion counts sorted by frequency' do
133
+ 3.times { engine.appraise(event: 'a', primary: primary_joy, secondary: secondary_high) }
134
+ engine.appraise(event: 'b', primary: primary_threat, secondary: secondary_low)
135
+ pattern = engine.emotional_pattern
136
+ expect(pattern.first.first).to eq(:joy)
137
+ end
138
+
139
+ it 'returns empty hash when no appraisals' do
140
+ expect(engine.emotional_pattern).to eq({})
141
+ end
142
+ end
143
+
144
+ describe '#decay_all' do
145
+ it 'reduces intensity for all appraisals' do
146
+ rec = engine.appraise(event: 'test', primary: primary_joy, secondary: secondary_high)
147
+ engine.decay_all
148
+ expect(rec.intensity).to be < 0.5
149
+ end
150
+ end
151
+
152
+ describe '#to_h' do
153
+ it 'returns hash with appraisals, coping_strategies, history_size' do
154
+ engine.appraise(event: 'test', primary: primary_joy, secondary: secondary_high)
155
+ result = engine.to_h
156
+ expect(result).to have_key(:appraisals)
157
+ expect(result).to have_key(:coping_strategies)
158
+ expect(result).to have_key(:history_size)
159
+ end
160
+ end
161
+ end
@@ -0,0 +1,175 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Affect::Appraisal::Helpers::Appraisal do
4
+ let(:primary_low) { { relevance: 0.2, goal_congruence: 0.2, goal_importance: 0.5 } }
5
+ let(:primary_high) { { relevance: 0.8, goal_congruence: 0.8, goal_importance: 0.9 } }
6
+ let(:secondary_low) { { coping_potential: 0.2, control_expectation: 0.3, future_expectancy: 0.4 } }
7
+ let(:secondary_high) { { coping_potential: 0.8, control_expectation: 0.7, future_expectancy: 0.6 } }
8
+
9
+ def build(primary: primary_high, secondary: secondary_high, domain: 'work')
10
+ described_class.new(event: 'test event', primary: primary, secondary: secondary, domain: domain)
11
+ end
12
+
13
+ describe '#initialize' do
14
+ it 'sets id, event, domain' do
15
+ appraisal = build
16
+ expect(appraisal.id).to be_a(String)
17
+ expect(appraisal.event).to eq('test event')
18
+ expect(appraisal.domain).to eq('work')
19
+ end
20
+
21
+ it 'normalizes primary dimensions' do
22
+ appraisal = build
23
+ expect(appraisal.primary.keys).to contain_exactly(:relevance, :goal_congruence, :goal_importance)
24
+ end
25
+
26
+ it 'normalizes secondary dimensions' do
27
+ appraisal = build
28
+ expect(appraisal.secondary.keys).to contain_exactly(:coping_potential, :control_expectation, :future_expectancy)
29
+ end
30
+
31
+ it 'clamps out-of-range values' do
32
+ appraisal = described_class.new(
33
+ event: 'e',
34
+ primary: { relevance: 2.0, goal_congruence: -0.5, goal_importance: 0.5 },
35
+ secondary: secondary_high
36
+ )
37
+ expect(appraisal.primary[:relevance]).to eq(1.0)
38
+ expect(appraisal.primary[:goal_congruence]).to eq(0.0)
39
+ end
40
+
41
+ it 'sets default intensity' do
42
+ expect(build.intensity).to eq(0.5)
43
+ end
44
+
45
+ it 'sets reappraised to false' do
46
+ expect(build.reappraised).to be(false)
47
+ end
48
+ end
49
+
50
+ describe '#compute_emotion' do
51
+ it 'returns :indifference when relevance is low' do
52
+ appraisal = described_class.new(
53
+ event: 'e',
54
+ primary: { relevance: 0.2, goal_congruence: 0.5, goal_importance: 0.5 },
55
+ secondary: secondary_high
56
+ )
57
+ expect(appraisal.emotional_outcome).to eq(:indifference)
58
+ end
59
+
60
+ it 'returns :anxiety for threat with low coping' do
61
+ appraisal = described_class.new(
62
+ event: 'e',
63
+ primary: { relevance: 0.9, goal_congruence: 0.3, goal_importance: 0.8 },
64
+ secondary: { coping_potential: 0.2, control_expectation: 0.3, future_expectancy: 0.4 }
65
+ )
66
+ expect(appraisal.emotional_outcome).to eq(:anxiety)
67
+ end
68
+
69
+ it 'returns :challenge for threat with high coping' do
70
+ appraisal = described_class.new(
71
+ event: 'e',
72
+ primary: { relevance: 0.9, goal_congruence: 0.3, goal_importance: 0.8 },
73
+ secondary: { coping_potential: 0.8, control_expectation: 0.7, future_expectancy: 0.6 }
74
+ )
75
+ expect(appraisal.emotional_outcome).to eq(:challenge)
76
+ end
77
+
78
+ it 'returns :anger for very low goal_congruence' do
79
+ appraisal = described_class.new(
80
+ event: 'e',
81
+ primary: { relevance: 0.9, goal_congruence: 0.2, goal_importance: 0.8 },
82
+ secondary: { coping_potential: 0.5, control_expectation: 0.5, future_expectancy: 0.5 }
83
+ )
84
+ expect(appraisal.emotional_outcome).to eq(:anger)
85
+ end
86
+
87
+ it 'returns :joy for high goal_congruence' do
88
+ appraisal = described_class.new(
89
+ event: 'e',
90
+ primary: { relevance: 0.9, goal_congruence: 0.9, goal_importance: 0.8 },
91
+ secondary: secondary_high
92
+ )
93
+ expect(appraisal.emotional_outcome).to eq(:joy)
94
+ end
95
+
96
+ it 'returns :sadness as fallback' do
97
+ appraisal = described_class.new(
98
+ event: 'e',
99
+ primary: { relevance: 0.9, goal_congruence: 0.5, goal_importance: 0.5 },
100
+ secondary: { coping_potential: 0.5, control_expectation: 0.5, future_expectancy: 0.5 }
101
+ )
102
+ expect(appraisal.emotional_outcome).to eq(:sadness)
103
+ end
104
+ end
105
+
106
+ describe '#reappraise' do
107
+ it 'updates emotion and reduces intensity' do
108
+ appraisal = described_class.new(
109
+ event: 'e', primary: primary_low, secondary: secondary_low
110
+ )
111
+ original_intensity = appraisal.intensity
112
+ appraisal.reappraise(new_primary: primary_high, new_secondary: secondary_high)
113
+ expect(appraisal.reappraised).to be(true)
114
+ expect(appraisal.intensity).to be < original_intensity
115
+ expect(appraisal.reappraised_at).not_to be_nil
116
+ end
117
+
118
+ it 'recomputes emotional_outcome' do
119
+ appraisal = described_class.new(
120
+ event: 'e',
121
+ primary: { relevance: 0.2, goal_congruence: 0.5, goal_importance: 0.5 },
122
+ secondary: secondary_high
123
+ )
124
+ expect(appraisal.emotional_outcome).to eq(:indifference)
125
+ appraisal.reappraise(
126
+ new_primary: { relevance: 0.9, goal_congruence: 0.9, goal_importance: 0.8 },
127
+ new_secondary: secondary_high
128
+ )
129
+ expect(appraisal.emotional_outcome).to eq(:joy)
130
+ end
131
+
132
+ it 'applies REAPPRAISAL_DISCOUNT to intensity' do
133
+ appraisal = build
134
+ appraisal.reappraise(new_primary: primary_high, new_secondary: secondary_high)
135
+ expected = 0.5 * (1 - Legion::Extensions::Agentic::Affect::Appraisal::Helpers::Constants::REAPPRAISAL_DISCOUNT)
136
+ expect(appraisal.intensity).to be_within(0.001).of(expected)
137
+ end
138
+ end
139
+
140
+ describe '#decay!' do
141
+ it 'reduces intensity by DECAY_RATE' do
142
+ appraisal = build
143
+ appraisal.decay!
144
+ expected = 0.5 - Legion::Extensions::Agentic::Affect::Appraisal::Helpers::Constants::DECAY_RATE
145
+ expect(appraisal.intensity).to be_within(0.001).of(expected)
146
+ end
147
+
148
+ it 'does not go below INTENSITY_FLOOR' do
149
+ appraisal = described_class.new(
150
+ event: 'e',
151
+ primary: { relevance: 0.9, goal_congruence: 0.9, goal_importance: 0.9 },
152
+ secondary: secondary_high
153
+ )
154
+ 60.times { appraisal.decay! }
155
+ expect(appraisal.intensity).to eq(0.0)
156
+ end
157
+ end
158
+
159
+ describe '#assign_coping' do
160
+ it 'sets coping_strategy' do
161
+ appraisal = build
162
+ appraisal.assign_coping('reframing')
163
+ expect(appraisal.coping_strategy).to eq('reframing')
164
+ end
165
+ end
166
+
167
+ describe '#to_h' do
168
+ it 'returns a hash with expected keys' do
169
+ appraisal = build
170
+ keys = appraisal.to_h.keys
171
+ expect(keys).to include(:id, :event, :domain, :primary, :secondary, :emotional_outcome,
172
+ :intensity, :coping_strategy, :reappraised, :created_at, :reappraised_at)
173
+ end
174
+ end
175
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Affect::Appraisal::Helpers::Constants do
4
+ let(:mod) { described_class }
5
+
6
+ it 'defines MAX_APPRAISALS' do
7
+ expect(mod::MAX_APPRAISALS).to eq(200)
8
+ end
9
+
10
+ it 'defines MAX_COPING_STRATEGIES' do
11
+ expect(mod::MAX_COPING_STRATEGIES).to eq(50)
12
+ end
13
+
14
+ it 'defines MAX_HISTORY' do
15
+ expect(mod::MAX_HISTORY).to eq(300)
16
+ end
17
+
18
+ it 'defines intensity bounds' do
19
+ expect(mod::INTENSITY_FLOOR).to eq(0.0)
20
+ expect(mod::INTENSITY_CEILING).to eq(1.0)
21
+ expect(mod::DEFAULT_INTENSITY).to eq(0.5)
22
+ end
23
+
24
+ it 'defines DECAY_RATE' do
25
+ expect(mod::DECAY_RATE).to eq(0.02)
26
+ end
27
+
28
+ it 'defines REAPPRAISAL_DISCOUNT' do
29
+ expect(mod::REAPPRAISAL_DISCOUNT).to eq(0.3)
30
+ end
31
+
32
+ it 'defines PRIMARY_DIMENSIONS' do
33
+ expect(mod::PRIMARY_DIMENSIONS).to contain_exactly(:relevance, :goal_congruence, :goal_importance)
34
+ end
35
+
36
+ it 'defines SECONDARY_DIMENSIONS' do
37
+ expect(mod::SECONDARY_DIMENSIONS).to contain_exactly(:coping_potential, :control_expectation, :future_expectancy)
38
+ end
39
+
40
+ it 'maps APPRAISAL_EMOTIONS' do
41
+ expect(mod::APPRAISAL_EMOTIONS[:threat_low_coping]).to eq(:anxiety)
42
+ expect(mod::APPRAISAL_EMOTIONS[:goal_congruent]).to eq(:joy)
43
+ expect(mod::APPRAISAL_EMOTIONS[:irrelevant]).to eq(:indifference)
44
+ end
45
+
46
+ it 'defines COPING_TYPES' do
47
+ expect(mod::COPING_TYPES).to include(:problem_focused, :emotion_focused, :meaning_focused)
48
+ end
49
+ end
@@ -0,0 +1,116 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'legion/extensions/agentic/affect/appraisal/client'
4
+
5
+ RSpec.describe Legion::Extensions::Agentic::Affect::Appraisal::Runners::Appraisal do
6
+ let(:client) { Legion::Extensions::Agentic::Affect::Appraisal::Client.new }
7
+
8
+ let(:primary_joy) { { relevance: 0.9, goal_congruence: 0.9, goal_importance: 0.8 } }
9
+ let(:primary_threat) { { relevance: 0.9, goal_congruence: 0.3, goal_importance: 0.8 } }
10
+ let(:secondary_low) { { coping_potential: 0.2, control_expectation: 0.3, future_expectancy: 0.4 } }
11
+ let(:secondary_high) { { coping_potential: 0.8, control_expectation: 0.7, future_expectancy: 0.6 } }
12
+
13
+ describe '#appraise_event' do
14
+ it 'returns success: true with an appraisal' do
15
+ result = client.appraise_event(event: 'deadline', primary: primary_joy, secondary: secondary_high)
16
+ expect(result[:success]).to be(true)
17
+ expect(result[:appraisal]).to include(:id, :emotional_outcome)
18
+ end
19
+
20
+ it 'assigns emotional_outcome based on appraisal pattern' do
21
+ result = client.appraise_event(event: 'win', primary: primary_joy, secondary: secondary_high)
22
+ expect(result[:appraisal][:emotional_outcome]).to eq(:joy)
23
+ end
24
+
25
+ it 'accepts optional domain' do
26
+ result = client.appraise_event(event: 'test', primary: primary_joy, secondary: secondary_high,
27
+ domain: 'work')
28
+ expect(result[:appraisal][:domain]).to eq('work')
29
+ end
30
+ end
31
+
32
+ describe '#reappraise_event' do
33
+ it 'updates existing appraisal' do
34
+ appraisal_id = client.appraise_event(event: 'e', primary: primary_threat,
35
+ secondary: secondary_low)[:appraisal][:id]
36
+ result = client.reappraise_event(appraisal_id: appraisal_id, new_primary: primary_joy,
37
+ new_secondary: secondary_high)
38
+ expect(result[:success]).to be(true)
39
+ expect(result[:appraisal][:reappraised]).to be(true)
40
+ end
41
+
42
+ it 'returns failure for unknown id' do
43
+ result = client.reappraise_event(appraisal_id: 'unknown', new_primary: primary_joy,
44
+ new_secondary: secondary_high)
45
+ expect(result[:success]).to be(false)
46
+ expect(result[:error]).to include('not found')
47
+ end
48
+ end
49
+
50
+ describe '#select_coping_strategy' do
51
+ it 'assigns a coping strategy to the appraisal' do
52
+ appraisal_id = client.appraise_event(event: 'e', primary: primary_threat,
53
+ secondary: secondary_low)[:appraisal][:id]
54
+ result = client.select_coping_strategy(appraisal_id: appraisal_id, coping_type: :problem_focused)
55
+ expect(result[:success]).to be(true)
56
+ expect(result[:appraisal][:coping_strategy]).not_to be_nil
57
+ end
58
+
59
+ it 'returns failure for unknown appraisal' do
60
+ result = client.select_coping_strategy(appraisal_id: 'unknown', coping_type: :problem_focused)
61
+ expect(result[:success]).to be(false)
62
+ end
63
+ end
64
+
65
+ describe '#add_coping_strategy' do
66
+ it 'registers a strategy' do
67
+ result = client.add_coping_strategy(name: 'journaling', coping_type: :emotion_focused,
68
+ effectiveness: 0.75)
69
+ expect(result[:success]).to be(true)
70
+ expect(result[:name]).to eq('journaling')
71
+ end
72
+ end
73
+
74
+ describe '#evaluate_coping' do
75
+ it 'returns effectiveness for an appraisal with coping' do
76
+ client.add_coping_strategy(name: 'breathing', coping_type: :emotion_focused, effectiveness: 0.8)
77
+ appraisal_id = client.appraise_event(event: 'e', primary: primary_threat,
78
+ secondary: secondary_low)[:appraisal][:id]
79
+ client.select_coping_strategy(appraisal_id: appraisal_id, coping_type: :emotion_focused)
80
+ result = client.evaluate_coping(appraisal_id: appraisal_id)
81
+ expect(result[:success]).to be(true)
82
+ expect(result[:effectiveness]).to be_a(Float)
83
+ end
84
+ end
85
+
86
+ describe '#emotional_pattern' do
87
+ it 'returns success with pattern hash' do
88
+ client.appraise_event(event: 'a', primary: primary_joy, secondary: secondary_high)
89
+ client.appraise_event(event: 'b', primary: primary_joy, secondary: secondary_high)
90
+ result = client.emotional_pattern
91
+ expect(result[:success]).to be(true)
92
+ expect(result[:pattern]).to be_a(Hash)
93
+ expect(result[:pattern][:joy]).to eq(2)
94
+ end
95
+ end
96
+
97
+ describe '#update_appraisal' do
98
+ it 'runs decay and returns success' do
99
+ client.appraise_event(event: 'e', primary: primary_joy, secondary: secondary_high)
100
+ result = client.update_appraisal
101
+ expect(result[:success]).to be(true)
102
+ end
103
+ end
104
+
105
+ describe '#appraisal_stats' do
106
+ it 'returns stats hash with totals' do
107
+ client.appraise_event(event: 'a', primary: primary_joy, secondary: secondary_high)
108
+ client.appraise_event(event: 'b', primary: primary_threat, secondary: secondary_low)
109
+ result = client.appraisal_stats
110
+ expect(result[:success]).to be(true)
111
+ expect(result[:total]).to eq(2)
112
+ expect(result[:unresolved]).to eq(2)
113
+ expect(result[:history_size]).to be >= 2
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Affect::CognitiveEmpathy::Client do
4
+ subject(:client) { described_class.new }
5
+
6
+ it 'full lifecycle: take perspective, record outcome, check accuracy, stats' do
7
+ p1 = client.take_empathic_perspective(
8
+ agent_id: :person_a,
9
+ perspective_type: :cognitive,
10
+ predicted_state: { valence: 0.7, arousal: 0.4 },
11
+ confidence: 0.8
12
+ )
13
+ expect(p1[:success]).to be true
14
+ perspective_id = p1[:perspective_id]
15
+
16
+ outcome = client.record_empathic_outcome(
17
+ perspective_id: perspective_id,
18
+ actual_state: { valence: 0.7, arousal: 0.4 }
19
+ )
20
+ expect(outcome[:success]).to be true
21
+
22
+ acc = client.empathic_accuracy_for(agent_id: :person_a)
23
+ expect(acc[:accuracy]).to be_a(Float)
24
+
25
+ client.apply_emotional_contagion(emotion_valence: 0.8, intensity: 0.6)
26
+ state = client.current_empathic_state
27
+ expect(Legion::Extensions::Agentic::Affect::CognitiveEmpathy::Helpers::Constants::EMPATHIC_STATES)
28
+ .to include(state[:empathic_state])
29
+
30
+ stats = client.cognitive_empathy_stats
31
+ expect(stats[:perspective_count]).to eq(1)
32
+ expect(stats[:resolved_count]).to eq(1)
33
+ end
34
+
35
+ it 'accepts an injected engine' do
36
+ engine = Legion::Extensions::Agentic::Affect::CognitiveEmpathy::Helpers::EmpathyEngine.new
37
+ c = described_class.new(engine: engine)
38
+ c.take_empathic_perspective(
39
+ agent_id: :bob,
40
+ perspective_type: :affective,
41
+ predicted_state: {},
42
+ confidence: 0.5
43
+ )
44
+ expect(engine.perspectives.size).to eq(1)
45
+ end
46
+
47
+ it 'blind spots returns nil agents on fresh client' do
48
+ result = client.empathic_blind_spots
49
+ expect(result[:success]).to be true
50
+ expect(result[:least_accurate_agent]).to be_nil
51
+ end
52
+
53
+ it 'overall accuracy is at default on fresh client' do
54
+ result = client.overall_empathic_accuracy
55
+ expect(result[:accuracy]).to eq(Legion::Extensions::Agentic::Affect::CognitiveEmpathy::Helpers::Constants::DEFAULT_ACCURACY)
56
+ end
57
+
58
+ it 'perspectives_for_agent returns empty list for unknown agent' do
59
+ result = client.perspectives_for_agent(agent_id: :ghost)
60
+ expect(result[:count]).to eq(0)
61
+ end
62
+ end