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::Reappraisal::Helpers::LlmEnhancer do
4
+ subject(:enhancer) { described_class }
5
+
6
+ describe '.available?' do
7
+ context 'when Legion::LLM is not defined' do
8
+ it 'returns false' do
9
+ expect(enhancer.available?).to be false
10
+ end
11
+ end
12
+
13
+ context 'when Legion::LLM is defined but not started' do
14
+ before do
15
+ stub_const('Legion::LLM', double(respond_to?: true, started?: false))
16
+ end
17
+
18
+ it 'returns false' do
19
+ expect(enhancer.available?).to be false
20
+ end
21
+ end
22
+
23
+ context 'when Legion::LLM is started' do
24
+ before do
25
+ stub_const('Legion::LLM', double(respond_to?: true, started?: true))
26
+ end
27
+
28
+ it 'returns true' do
29
+ expect(enhancer.available?).to be true
30
+ end
31
+ end
32
+
33
+ context 'when Legion::LLM raises an error' do
34
+ before do
35
+ stub_const('Legion::LLM', double)
36
+ allow(Legion::LLM).to receive(:respond_to?).and_raise(StandardError, 'boom')
37
+ end
38
+
39
+ it 'returns false' do
40
+ expect(enhancer.available?).to be false
41
+ end
42
+ end
43
+ end
44
+
45
+ describe '.generate_reappraisal' do
46
+ let(:mock_response) do
47
+ content = "REAPPRAISAL: This outage is an opportunity to strengthen the system's resilience and learn " \
48
+ 'from the failure patterns that emerged under load.'
49
+ double('response', content: content)
50
+ end
51
+
52
+ before do
53
+ stub_const('Legion::LLM', double)
54
+ chat = double('chat')
55
+ allow(Legion::LLM).to receive(:chat).and_return(chat)
56
+ allow(chat).to receive(:with_instructions)
57
+ allow(chat).to receive(:ask).and_return(mock_response)
58
+ end
59
+
60
+ it 'returns a hash with new_appraisal' do
61
+ result = enhancer.generate_reappraisal(
62
+ event_content: 'production outage',
63
+ initial_appraisal: 'catastrophic failure',
64
+ strategy: :benefit_finding,
65
+ valence: -0.7,
66
+ intensity: 0.5
67
+ )
68
+ expect(result).to be_a(Hash)
69
+ expect(result[:new_appraisal]).to be_a(String)
70
+ expect(result[:new_appraisal]).not_to be_empty
71
+ end
72
+
73
+ it 'strips leading/trailing whitespace from the reappraisal' do
74
+ result = enhancer.generate_reappraisal(
75
+ event_content: 'production outage',
76
+ initial_appraisal: 'catastrophic failure',
77
+ strategy: :benefit_finding,
78
+ valence: -0.7,
79
+ intensity: 0.5
80
+ )
81
+ expect(result[:new_appraisal]).to eq(result[:new_appraisal].strip)
82
+ end
83
+
84
+ context 'when LLM raises an error' do
85
+ before do
86
+ chat = double('chat')
87
+ allow(Legion::LLM).to receive(:chat).and_return(chat)
88
+ allow(chat).to receive(:with_instructions)
89
+ allow(chat).to receive(:ask).and_raise(StandardError, 'API error')
90
+ end
91
+
92
+ it 'returns nil' do
93
+ result = enhancer.generate_reappraisal(
94
+ event_content: 'test event',
95
+ initial_appraisal: 'bad',
96
+ strategy: :reinterpretation,
97
+ valence: -0.5,
98
+ intensity: 0.4
99
+ )
100
+ expect(result).to be_nil
101
+ end
102
+ end
103
+
104
+ context 'when response has no content' do
105
+ before do
106
+ chat = double('chat')
107
+ allow(Legion::LLM).to receive(:chat).and_return(chat)
108
+ allow(chat).to receive(:with_instructions)
109
+ allow(chat).to receive(:ask).and_return(double('response', content: nil))
110
+ end
111
+
112
+ it 'returns nil' do
113
+ result = enhancer.generate_reappraisal(
114
+ event_content: 'test',
115
+ initial_appraisal: 'test',
116
+ strategy: :distancing,
117
+ valence: -0.3,
118
+ intensity: 0.5
119
+ )
120
+ expect(result).to be_nil
121
+ end
122
+ end
123
+
124
+ context 'when response lacks REAPPRAISAL marker' do
125
+ before do
126
+ bad_response = double('response', content: 'Just some text without the format marker.')
127
+ chat = double('chat')
128
+ allow(Legion::LLM).to receive(:chat).and_return(chat)
129
+ allow(chat).to receive(:with_instructions)
130
+ allow(chat).to receive(:ask).and_return(bad_response)
131
+ end
132
+
133
+ it 'returns nil' do
134
+ result = enhancer.generate_reappraisal(
135
+ event_content: 'test',
136
+ initial_appraisal: 'test',
137
+ strategy: :normalizing,
138
+ valence: -0.4,
139
+ intensity: 0.3
140
+ )
141
+ expect(result).to be_nil
142
+ end
143
+ end
144
+
145
+ context 'with different strategies' do
146
+ %i[reinterpretation distancing benefit_finding normalizing perspective_taking temporal_distancing].each do |strategy|
147
+ it "works with strategy #{strategy}" do
148
+ result = enhancer.generate_reappraisal(
149
+ event_content: 'a difficult situation',
150
+ initial_appraisal: 'overwhelming',
151
+ strategy: strategy,
152
+ valence: -0.6,
153
+ intensity: 0.7
154
+ )
155
+ expect(result).to be_a(Hash)
156
+ expect(result[:new_appraisal]).to be_a(String)
157
+ end
158
+ end
159
+ end
160
+ end
161
+ end
@@ -0,0 +1,211 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Affect::Reappraisal::Helpers::ReappraisalEngine do
4
+ subject(:engine) { described_class.new }
5
+
6
+ let(:neg_event_id) do
7
+ engine.register_event(content: 'failure', valence: -0.7, intensity: 0.4, appraisal: 'terrible').id
8
+ end
9
+
10
+ let(:pos_event_id) do
11
+ engine.register_event(content: 'success', valence: 0.6, intensity: 0.3, appraisal: 'great').id
12
+ end
13
+
14
+ describe '#register_event' do
15
+ it 'returns an EmotionalEvent' do
16
+ event = engine.register_event(content: 'test', valence: -0.5, intensity: 0.4, appraisal: 'bad')
17
+ expect(event).to be_a(Legion::Extensions::Agentic::Affect::Reappraisal::Helpers::EmotionalEvent)
18
+ end
19
+
20
+ it 'stores the event by id' do
21
+ event = engine.register_event(content: 'test', valence: -0.5, intensity: 0.4, appraisal: 'bad')
22
+ expect(engine.events[event.id]).to eq(event)
23
+ end
24
+
25
+ it 'increments event count' do
26
+ expect { engine.register_event(content: 'x', valence: 0.0, intensity: 0.5, appraisal: 'neutral') }
27
+ .to change { engine.events.size }.by(1)
28
+ end
29
+ end
30
+
31
+ describe '#reappraise' do
32
+ it 'returns success for valid inputs' do
33
+ result = engine.reappraise(event_id: neg_event_id, strategy: :reinterpretation, new_appraisal: 'reframed')
34
+ expect(result[:success]).to be true
35
+ end
36
+
37
+ it 'returns event_id and strategy in response' do
38
+ result = engine.reappraise(event_id: neg_event_id, strategy: :distancing, new_appraisal: 'distant view')
39
+ expect(result[:event_id]).to eq(neg_event_id)
40
+ expect(result[:strategy]).to eq(:distancing)
41
+ end
42
+
43
+ it 'returns current_valence after reappraisal' do
44
+ result = engine.reappraise(event_id: neg_event_id, strategy: :benefit_finding, new_appraisal: 'learned')
45
+ expect(result[:current_valence]).to be_a(Float)
46
+ end
47
+
48
+ it 'fails with :event_not_found for unknown id' do
49
+ result = engine.reappraise(event_id: 'unknown-id', strategy: :reinterpretation, new_appraisal: 'x')
50
+ expect(result[:success]).to be false
51
+ expect(result[:reason]).to eq(:event_not_found)
52
+ end
53
+
54
+ it 'fails with :invalid_strategy for bad strategy' do
55
+ result = engine.reappraise(event_id: neg_event_id, strategy: :magic, new_appraisal: 'x')
56
+ expect(result[:success]).to be false
57
+ expect(result[:reason]).to eq(:invalid_strategy)
58
+ end
59
+
60
+ it 'logs the reappraisal in reappraisal_log' do
61
+ engine.reappraise(event_id: neg_event_id, strategy: :normalizing, new_appraisal: 'normal')
62
+ expect(engine.reappraisal_log.size).to eq(1)
63
+ expect(engine.reappraisal_log.first[:strategy]).to eq(:normalizing)
64
+ end
65
+
66
+ it 'records valence_change and intensity_change in log' do
67
+ engine.reappraise(event_id: neg_event_id, strategy: :reinterpretation, new_appraisal: 'changed')
68
+ log = engine.reappraisal_log.first
69
+ expect(log[:valence_change]).to be_a(Float)
70
+ expect(log[:intensity_change]).to be_a(Float)
71
+ end
72
+ end
73
+
74
+ describe '#auto_reappraise' do
75
+ it 'succeeds for a valid event' do
76
+ result = engine.auto_reappraise(event_id: neg_event_id)
77
+ expect(result[:success]).to be true
78
+ end
79
+
80
+ it 'selects distancing for intense negative event' do
81
+ intense_neg_id = engine.register_event(content: 'crisis', valence: -0.8, intensity: 0.9, appraisal: 'worst').id
82
+ result = engine.auto_reappraise(event_id: intense_neg_id)
83
+ expect(result[:strategy]).to eq(:distancing)
84
+ end
85
+
86
+ it 'selects reinterpretation for non-intense negative event' do
87
+ result = engine.auto_reappraise(event_id: neg_event_id)
88
+ expect(result[:strategy]).to eq(:reinterpretation)
89
+ end
90
+
91
+ it 'selects benefit_finding for mild positive events' do
92
+ result = engine.auto_reappraise(event_id: pos_event_id)
93
+ expect(result[:strategy]).to eq(:benefit_finding)
94
+ end
95
+
96
+ it 'fails for unknown event_id' do
97
+ result = engine.auto_reappraise(event_id: 'missing')
98
+ expect(result[:success]).to be false
99
+ expect(result[:reason]).to eq(:event_not_found)
100
+ end
101
+ end
102
+
103
+ describe '#negative_events' do
104
+ it 'returns events with negative current_valence' do
105
+ neg_event_id
106
+ pos_event_id
107
+ expect(engine.negative_events.size).to eq(1)
108
+ expect(engine.negative_events.first.negative?).to be true
109
+ end
110
+
111
+ it 'returns empty array when no negative events' do
112
+ pos_event_id
113
+ expect(engine.negative_events).to be_empty
114
+ end
115
+ end
116
+
117
+ describe '#intense_events' do
118
+ it 'returns events with high intensity' do
119
+ engine.register_event(content: 'crisis', valence: -0.8, intensity: 0.9, appraisal: 'worst')
120
+ engine.register_event(content: 'calm', valence: 0.2, intensity: 0.2, appraisal: 'fine')
121
+ expect(engine.intense_events.size).to eq(1)
122
+ end
123
+ end
124
+
125
+ describe '#most_regulated' do
126
+ before do
127
+ # Register two events and reappraise one multiple times
128
+ @event_a_id = engine.register_event(content: 'a', valence: -0.6, intensity: 0.4, appraisal: 'bad').id
129
+ @event_b_id = engine.register_event(content: 'b', valence: -0.4, intensity: 0.3, appraisal: 'ok').id
130
+ 3.times { engine.reappraise(event_id: @event_a_id, strategy: :reinterpretation, new_appraisal: 'better') }
131
+ end
132
+
133
+ it 'returns events sorted by regulation_amount descending' do
134
+ regulated = engine.most_regulated(limit: 2)
135
+ expect(regulated.first.id).to eq(@event_a_id)
136
+ end
137
+
138
+ it 'respects limit parameter' do
139
+ expect(engine.most_regulated(limit: 1).size).to eq(1)
140
+ end
141
+ end
142
+
143
+ describe '#strategy_effectiveness' do
144
+ before do
145
+ engine.reappraise(event_id: neg_event_id, strategy: :reinterpretation, new_appraisal: 'view 1')
146
+ engine.reappraise(event_id: neg_event_id, strategy: :reinterpretation, new_appraisal: 'view 2')
147
+ engine.reappraise(event_id: neg_event_id, strategy: :distancing, new_appraisal: 'distant')
148
+ end
149
+
150
+ it 'returns a hash keyed by strategy' do
151
+ eff = engine.strategy_effectiveness
152
+ expect(eff).to have_key(:reinterpretation)
153
+ expect(eff).to have_key(:distancing)
154
+ end
155
+
156
+ it 'computes average valence_change per strategy' do
157
+ eff = engine.strategy_effectiveness
158
+ expect(eff[:reinterpretation]).to be_a(Float)
159
+ end
160
+ end
161
+
162
+ describe '#average_regulation' do
163
+ it 'returns 0.0 for empty engine' do
164
+ expect(engine.average_regulation).to eq(0.0)
165
+ end
166
+
167
+ it 'returns positive value after events and reappraisals' do
168
+ neg_event_id
169
+ engine.reappraise(event_id: neg_event_id, strategy: :reinterpretation, new_appraisal: 'better')
170
+ expect(engine.average_regulation).to be > 0.0
171
+ end
172
+ end
173
+
174
+ describe '#overall_regulation_ability' do
175
+ it 'returns 0.0 for empty engine' do
176
+ expect(engine.overall_regulation_ability).to eq(0.0)
177
+ end
178
+
179
+ it 'returns value between 0 and 1' do
180
+ neg_event_id
181
+ engine.reappraise(event_id: neg_event_id, strategy: :benefit_finding, new_appraisal: 'learned')
182
+ expect(engine.overall_regulation_ability).to be_between(0.0, 1.0)
183
+ end
184
+ end
185
+
186
+ describe '#reappraisal_report' do
187
+ it 'returns a hash with all expected keys' do
188
+ report = engine.reappraisal_report
189
+ expect(report).to include(:total_events, :total_reappraisals, :negative_events,
190
+ :intense_events, :average_regulation,
191
+ :overall_regulation_ability, :strategy_effectiveness, :most_regulated)
192
+ end
193
+
194
+ it 'reflects actual counts' do
195
+ neg_event_id
196
+ pos_event_id
197
+ engine.reappraise(event_id: neg_event_id, strategy: :normalizing, new_appraisal: 'normal')
198
+ report = engine.reappraisal_report
199
+ expect(report[:total_events]).to eq(2)
200
+ expect(report[:total_reappraisals]).to eq(1)
201
+ end
202
+ end
203
+
204
+ describe '#to_h' do
205
+ it 'returns a hash with expected keys' do
206
+ h = engine.to_h
207
+ expect(h).to include(:events, :reappraisal_log, :average_regulation,
208
+ :overall_regulation_ability, :strategy_effectiveness)
209
+ end
210
+ end
211
+ end
@@ -0,0 +1,312 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'legion/extensions/agentic/affect/reappraisal/client'
4
+
5
+ RSpec.describe Legion::Extensions::Agentic::Affect::Reappraisal::Runners::CognitiveReappraisal do
6
+ let(:engine) { Legion::Extensions::Agentic::Affect::Reappraisal::Helpers::ReappraisalEngine.new }
7
+ let(:client) { Legion::Extensions::Agentic::Affect::Reappraisal::Client.new(engine: engine) }
8
+ let(:enhancer) { Legion::Extensions::Agentic::Affect::Reappraisal::Helpers::LlmEnhancer }
9
+
10
+ let(:registered) do
11
+ client.register_event(
12
+ content: 'production outage',
13
+ valence: -0.7,
14
+ intensity: 0.5,
15
+ appraisal: 'catastrophic failure',
16
+ engine: engine
17
+ )
18
+ end
19
+
20
+ describe '#register_event' do
21
+ it 'returns success: true' do
22
+ expect(registered[:success]).to be true
23
+ end
24
+
25
+ it 'returns a valid UUID event_id' do
26
+ expect(registered[:event_id]).to match(/\A[0-9a-f-]{36}\z/)
27
+ end
28
+
29
+ it 'returns valence and intensity' do
30
+ expect(registered[:valence]).to be_a(Float)
31
+ expect(registered[:intensity]).to be_a(Float)
32
+ end
33
+
34
+ it 'reports negative: true for negative event' do
35
+ expect(registered[:negative]).to be true
36
+ end
37
+
38
+ it 'reports intense: false for moderate intensity' do
39
+ expect(registered[:intense]).to be false
40
+ end
41
+
42
+ it 'reports intense: true for high intensity' do
43
+ result = client.register_event(
44
+ content: 'extreme', valence: -0.9, intensity: 0.9, appraisal: 'worst', engine: engine
45
+ )
46
+ expect(result[:intense]).to be true
47
+ end
48
+ end
49
+
50
+ describe '#reappraise_event' do
51
+ it 'succeeds with valid event_id and strategy' do
52
+ result = client.reappraise_event(
53
+ event_id: registered[:event_id],
54
+ strategy: :reinterpretation,
55
+ new_appraisal: 'learning opportunity',
56
+ engine: engine
57
+ )
58
+ expect(result[:success]).to be true
59
+ end
60
+
61
+ it 'returns current_valence after reappraisal' do
62
+ result = client.reappraise_event(
63
+ event_id: registered[:event_id],
64
+ strategy: :benefit_finding,
65
+ new_appraisal: 'builds resilience',
66
+ engine: engine
67
+ )
68
+ expect(result[:current_valence]).to be_a(Float)
69
+ end
70
+
71
+ it 'rejects invalid strategy' do
72
+ result = client.reappraise_event(
73
+ event_id: registered[:event_id],
74
+ strategy: :magic_thinking,
75
+ new_appraisal: 'irrelevant',
76
+ engine: engine
77
+ )
78
+ expect(result[:success]).to be false
79
+ expect(result[:reason]).to eq(:invalid_strategy)
80
+ expect(result[:valid_strategies]).to eq(Legion::Extensions::Agentic::Affect::Reappraisal::Helpers::Constants::STRATEGIES)
81
+ end
82
+
83
+ it 'rejects unknown event_id' do
84
+ result = client.reappraise_event(
85
+ event_id: 'does-not-exist',
86
+ strategy: :distancing,
87
+ new_appraisal: 'distant',
88
+ engine: engine
89
+ )
90
+ expect(result[:success]).to be false
91
+ end
92
+ end
93
+
94
+ describe '#auto_reappraise_event' do
95
+ it 'succeeds for a known event' do
96
+ result = client.auto_reappraise_event(event_id: registered[:event_id], engine: engine)
97
+ expect(result[:success]).to be true
98
+ end
99
+
100
+ it 'selects an appropriate strategy automatically' do
101
+ result = client.auto_reappraise_event(event_id: registered[:event_id], engine: engine)
102
+ expect(Legion::Extensions::Agentic::Affect::Reappraisal::Helpers::Constants::STRATEGIES).to include(result[:strategy])
103
+ end
104
+
105
+ it 'fails gracefully for unknown event' do
106
+ result = client.auto_reappraise_event(event_id: 'missing', engine: engine)
107
+ expect(result[:success]).to be false
108
+ end
109
+
110
+ context 'when LLM is available' do
111
+ let(:llm_appraisal) { 'This outage reveals systemic resilience that can be strengthened.' }
112
+
113
+ before do
114
+ allow(enhancer).to receive(:available?).and_return(true)
115
+ allow(enhancer).to receive(:generate_reappraisal).and_return({ new_appraisal: llm_appraisal })
116
+ end
117
+
118
+ it 'uses the LLM-generated appraisal text' do
119
+ result = client.auto_reappraise_event(event_id: registered[:event_id], engine: engine)
120
+ expect(result[:success]).to be true
121
+ expect(engine.events[registered[:event_id]].appraisal).to eq(llm_appraisal)
122
+ end
123
+
124
+ it 'calls the LLM enhancer with event details' do
125
+ expect(enhancer).to receive(:generate_reappraisal).with(
126
+ hash_including(
127
+ event_content: 'production outage',
128
+ initial_appraisal: 'catastrophic failure'
129
+ )
130
+ ).and_return({ new_appraisal: llm_appraisal })
131
+ client.auto_reappraise_event(event_id: registered[:event_id], engine: engine)
132
+ end
133
+ end
134
+
135
+ context 'when LLM is unavailable' do
136
+ before do
137
+ allow(enhancer).to receive(:available?).and_return(false)
138
+ end
139
+
140
+ it 'falls back to mechanical appraisal stub' do
141
+ result = client.auto_reappraise_event(event_id: registered[:event_id], engine: engine)
142
+ expect(result[:success]).to be true
143
+ expect(engine.events[registered[:event_id]].appraisal).to match(/auto-reappraised via/)
144
+ end
145
+ end
146
+
147
+ context 'when LLM returns nil' do
148
+ before do
149
+ allow(enhancer).to receive(:available?).and_return(true)
150
+ allow(enhancer).to receive(:generate_reappraisal).and_return(nil)
151
+ end
152
+
153
+ it 'falls back to mechanical appraisal stub' do
154
+ result = client.auto_reappraise_event(event_id: registered[:event_id], engine: engine)
155
+ expect(result[:success]).to be true
156
+ expect(engine.events[registered[:event_id]].appraisal).to match(/auto-reappraised via/)
157
+ end
158
+ end
159
+ end
160
+
161
+ describe '#negative_events' do
162
+ before { registered }
163
+
164
+ it 'returns count and events array' do
165
+ result = client.negative_events(engine: engine)
166
+ expect(result).to have_key(:events)
167
+ expect(result).to have_key(:count)
168
+ end
169
+
170
+ it 'includes the registered negative event' do
171
+ result = client.negative_events(engine: engine)
172
+ expect(result[:count]).to be >= 1
173
+ end
174
+ end
175
+
176
+ describe '#intense_events' do
177
+ it 'returns empty when no intense events' do
178
+ registered
179
+ result = client.intense_events(engine: engine)
180
+ expect(result[:count]).to eq(0)
181
+ end
182
+
183
+ it 'returns intense events when present' do
184
+ client.register_event(content: 'crisis', valence: -0.9, intensity: 0.95, appraisal: 'awful', engine: engine)
185
+ result = client.intense_events(engine: engine)
186
+ expect(result[:count]).to be >= 1
187
+ end
188
+ end
189
+
190
+ describe '#most_regulated_events' do
191
+ before do
192
+ registered
193
+ 3.times do
194
+ client.reappraise_event(
195
+ event_id: registered[:event_id],
196
+ strategy: :reinterpretation,
197
+ new_appraisal: 'better each time',
198
+ engine: engine
199
+ )
200
+ end
201
+ end
202
+
203
+ it 'returns events and count' do
204
+ result = client.most_regulated_events(limit: 3, engine: engine)
205
+ expect(result).to have_key(:events)
206
+ expect(result).to have_key(:count)
207
+ end
208
+
209
+ it 'respects the limit' do
210
+ result = client.most_regulated_events(limit: 1, engine: engine)
211
+ expect(result[:events].size).to eq(1)
212
+ end
213
+ end
214
+
215
+ describe '#reappraisal_status' do
216
+ before { registered }
217
+
218
+ it 'returns overall_regulation_ability' do
219
+ result = client.reappraisal_status(engine: engine)
220
+ expect(result).to have_key(:overall_regulation_ability)
221
+ end
222
+
223
+ it 'returns total_events count' do
224
+ result = client.reappraisal_status(engine: engine)
225
+ expect(result[:total_events]).to be >= 1
226
+ end
227
+
228
+ it 'returns strategy_effectiveness hash' do
229
+ result = client.reappraisal_status(engine: engine)
230
+ expect(result[:strategy_effectiveness]).to be_a(Hash)
231
+ end
232
+ end
233
+
234
+ describe '#reappraisal_report' do
235
+ before do
236
+ registered
237
+ client.reappraise_event(
238
+ event_id: registered[:event_id],
239
+ strategy: :perspective_taking,
240
+ new_appraisal: 'systemic issue, not personal failure',
241
+ engine: engine
242
+ )
243
+ end
244
+
245
+ it 'returns success: true' do
246
+ result = client.reappraisal_report(engine: engine)
247
+ expect(result[:success]).to be true
248
+ end
249
+
250
+ it 'includes a report hash' do
251
+ result = client.reappraisal_report(engine: engine)
252
+ expect(result[:report]).to be_a(Hash)
253
+ end
254
+
255
+ it 'report includes total_events and total_reappraisals' do
256
+ result = client.reappraisal_report(engine: engine)
257
+ expect(result[:report][:total_events]).to eq(1)
258
+ expect(result[:report][:total_reappraisals]).to eq(1)
259
+ end
260
+ end
261
+
262
+ describe '#regulate_pending_events' do
263
+ it 'returns checked, regulated, and event_ids keys' do
264
+ result = client.regulate_pending_events(engine: engine)
265
+ expect(result).to include(:checked, :regulated, :event_ids)
266
+ end
267
+
268
+ it 'returns zero regulated when no events registered' do
269
+ result = client.regulate_pending_events(engine: engine)
270
+ expect(result[:regulated]).to eq(0)
271
+ expect(result[:event_ids]).to eq([])
272
+ end
273
+
274
+ it 'regulates negative unreappraisals events' do
275
+ registered
276
+ result = client.regulate_pending_events(engine: engine)
277
+ expect(result[:regulated]).to eq(1)
278
+ expect(result[:event_ids]).to include(registered[:event_id])
279
+ end
280
+
281
+ it 'skips events that have already been reappraised' do
282
+ registered
283
+ client.reappraise_event(
284
+ event_id: registered[:event_id],
285
+ strategy: :reinterpretation,
286
+ new_appraisal: 'already handled',
287
+ engine: engine
288
+ )
289
+ result = client.regulate_pending_events(engine: engine)
290
+ expect(result[:regulated]).to eq(0)
291
+ end
292
+
293
+ it 'skips positive events' do
294
+ client.register_event(
295
+ content: 'great news',
296
+ valence: 0.8,
297
+ intensity: 0.3,
298
+ appraisal: 'wonderful',
299
+ engine: engine
300
+ )
301
+ result = client.regulate_pending_events(engine: engine)
302
+ expect(result[:regulated]).to eq(0)
303
+ end
304
+
305
+ it 'returns checked equal to total event count' do
306
+ registered
307
+ client.register_event(content: 'another', valence: 0.5, intensity: 0.2, appraisal: 'fine', engine: engine)
308
+ result = client.regulate_pending_events(engine: engine)
309
+ expect(result[:checked]).to eq(2)
310
+ end
311
+ end
312
+ end