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,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Affect::Defusion::Client do
4
+ let(:client) { described_class.new }
5
+
6
+ describe '#initialize' do
7
+ it 'creates a client with a default engine' do
8
+ expect(client).to respond_to(:register_thought)
9
+ end
10
+
11
+ it 'accepts an injected engine' do
12
+ engine = Legion::Extensions::Agentic::Affect::Defusion::Helpers::DefusionEngine.new
13
+ c = described_class.new(engine: engine)
14
+ expect(c).to respond_to(:register_thought)
15
+ end
16
+ end
17
+
18
+ describe 'runner method delegation' do
19
+ it 'responds to all runner methods' do
20
+ %i[
21
+ register_thought apply_defusion apply_all_techniques visit_thought
22
+ fuse_thought enmeshed_thoughts defused_thoughts ruminating_thoughts
23
+ most_fused recommend_technique defusion_report defusion_state
24
+ ].each do |method|
25
+ expect(client).to respond_to(method)
26
+ end
27
+ end
28
+ end
29
+
30
+ describe 'full workflow integration' do
31
+ it 'registers, defuses, and reports on a thought cycle' do
32
+ # Register a fused thought
33
+ reg = client.register_thought(content: 'I am not good enough', thought_type: :self_concept, belief_strength: 0.9)
34
+ expect(reg[:success]).to be true
35
+ tid = reg[:thought_id]
36
+
37
+ # Recommend a technique
38
+ rec = client.recommend_technique(thought_id: tid)
39
+ expect(rec[:technique]).to eq(:distancing)
40
+
41
+ # Apply defusion
42
+ defused = client.apply_defusion(thought_id: tid, technique: rec[:technique])
43
+ expect(defused[:success]).to be true
44
+ expect(defused[:after]).to be < defused[:before]
45
+
46
+ # Check report
47
+ report = client.defusion_report
48
+ expect(report[:total_thoughts]).to eq(1)
49
+ expect(report[:defusion_attempts]).to eq(1)
50
+ expect(report[:defusion_effectiveness]).to be > 0.0
51
+ end
52
+
53
+ it 'tracks enmeshed and defused thoughts over multiple operations' do
54
+ # Register two thoughts
55
+ t1 = client.register_thought(content: 'thought 1', thought_type: :belief, belief_strength: 0.8)
56
+ t2 = client.register_thought(content: 'thought 2', thought_type: :judgment, belief_strength: 0.6)
57
+
58
+ # Fully defuse t1
59
+ 20.times { client.apply_defusion(thought_id: t1[:thought_id], technique: :acceptance) }
60
+
61
+ # t2 remains enmeshed
62
+ enmeshed = client.enmeshed_thoughts
63
+ defused = client.defused_thoughts
64
+
65
+ expect(enmeshed[:count]).to eq(1)
66
+ expect(defused[:count]).to eq(1)
67
+ expect(enmeshed[:thoughts].first[:id]).to eq(t2[:thought_id])
68
+ end
69
+
70
+ it 'detects rumination after repeated visits' do
71
+ reg = client.register_thought(content: 'am I enough?', thought_type: :evaluation, belief_strength: 0.6)
72
+ tid = reg[:thought_id]
73
+
74
+ Legion::Extensions::Agentic::Affect::Defusion::Helpers::Constants::RUMINATION_COUNT.times { client.visit_thought(thought_id: tid) }
75
+
76
+ rum = client.ruminating_thoughts
77
+ expect(rum[:count]).to eq(1)
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Affect::Defusion::Helpers::Constants do
4
+ let(:mod) { described_class }
5
+
6
+ describe 'DEFUSION_TECHNIQUES' do
7
+ it 'contains expected techniques' do
8
+ expect(mod::DEFUSION_TECHNIQUES).to include(:labeling, :distancing, :contextualization, :acceptance, :reframing, :metaphor)
9
+ end
10
+
11
+ it 'is frozen' do
12
+ expect(mod::DEFUSION_TECHNIQUES).to be_frozen
13
+ end
14
+ end
15
+
16
+ describe 'TECHNIQUE_POTENCY' do
17
+ it 'has a potency for every technique' do
18
+ mod::DEFUSION_TECHNIQUES.each do |technique|
19
+ expect(mod::TECHNIQUE_POTENCY).to have_key(technique)
20
+ end
21
+ end
22
+
23
+ it 'has acceptance as highest potency' do
24
+ expect(mod::TECHNIQUE_POTENCY[:acceptance]).to eq(0.15)
25
+ end
26
+
27
+ it 'has metaphor as lowest potency' do
28
+ expect(mod::TECHNIQUE_POTENCY[:metaphor]).to eq(0.06)
29
+ end
30
+ end
31
+
32
+ describe 'FUSION_LABELS' do
33
+ it 'labels 0.9 as enmeshed' do
34
+ label = mod::FUSION_LABELS.find { |range, _| range.cover?(0.9) }&.last
35
+ expect(label).to eq(:enmeshed)
36
+ end
37
+
38
+ it 'labels 0.7 as fused' do
39
+ label = mod::FUSION_LABELS.find { |range, _| range.cover?(0.7) }&.last
40
+ expect(label).to eq(:fused)
41
+ end
42
+
43
+ it 'labels 0.5 as partially_fused' do
44
+ label = mod::FUSION_LABELS.find { |range, _| range.cover?(0.5) }&.last
45
+ expect(label).to eq(:partially_fused)
46
+ end
47
+
48
+ it 'labels 0.1 as fully_defused' do
49
+ label = mod::FUSION_LABELS.find { |range, _| range.cover?(0.1) }&.last
50
+ expect(label).to eq(:fully_defused)
51
+ end
52
+ end
53
+
54
+ describe 'BELIEF_LABELS' do
55
+ it 'labels 0.9 as entrenched' do
56
+ label = mod::BELIEF_LABELS.find { |range, _| range.cover?(0.9) }&.last
57
+ expect(label).to eq(:entrenched)
58
+ end
59
+
60
+ it 'labels 0.15 as negligible' do
61
+ label = mod::BELIEF_LABELS.find { |range, _| range.cover?(0.15) }&.last
62
+ expect(label).to eq(:negligible)
63
+ end
64
+ end
65
+
66
+ describe 'THOUGHT_TYPES' do
67
+ it 'includes all expected types' do
68
+ expect(mod::THOUGHT_TYPES).to include(:belief, :assumption, :evaluation, :prediction, :judgment, :self_concept, :rule)
69
+ end
70
+
71
+ it 'is frozen' do
72
+ expect(mod::THOUGHT_TYPES).to be_frozen
73
+ end
74
+ end
75
+
76
+ describe 'RECOMMENDED_TECHNIQUES' do
77
+ it 'maps every thought type to a valid technique' do
78
+ mod::THOUGHT_TYPES.each do |type|
79
+ technique = mod::RECOMMENDED_TECHNIQUES[type]
80
+ expect(mod::DEFUSION_TECHNIQUES).to include(technique), "No valid technique for #{type}"
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,250 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Affect::Defusion::Helpers::DefusionEngine do
4
+ let(:engine) { described_class.new }
5
+
6
+ def register(content: 'I must be perfect', thought_type: :belief, belief_strength: 0.8)
7
+ engine.register_thought(content: content, thought_type: thought_type, belief_strength: belief_strength)
8
+ end
9
+
10
+ describe '#register_thought' do
11
+ it 'creates a thought and returns its id' do
12
+ result = register
13
+ expect(result[:thought_id]).to match(/\A[0-9a-f-]{36}\z/)
14
+ expect(result[:thought]).to be_a(Hash)
15
+ end
16
+
17
+ it 'stores the thought in the engine' do
18
+ result = register
19
+ expect(engine.thoughts).to have_key(result[:thought_id])
20
+ end
21
+
22
+ it 'returns error for invalid thought_type' do
23
+ result = engine.register_thought(content: 'x', thought_type: :invalid_type, belief_strength: 0.5)
24
+ expect(result[:error]).to eq(:invalid_thought_type)
25
+ expect(result[:valid]).to eq(Legion::Extensions::Agentic::Affect::Defusion::Helpers::Constants::THOUGHT_TYPES)
26
+ end
27
+
28
+ it 'accepts all valid thought types' do
29
+ Legion::Extensions::Agentic::Affect::Defusion::Helpers::Constants::THOUGHT_TYPES.each do |type|
30
+ result = engine.register_thought(content: "thought #{type}", thought_type: type, belief_strength: 0.5)
31
+ expect(result[:thought_id]).not_to be_nil
32
+ end
33
+ end
34
+ end
35
+
36
+ describe '#apply_defusion' do
37
+ let(:thought_id) { register[:thought_id] }
38
+
39
+ it 'reduces fusion and returns result' do
40
+ result = engine.apply_defusion(thought_id: thought_id, technique: :acceptance)
41
+ expect(result[:success]).to be true
42
+ expect(result[:after]).to be < result[:before]
43
+ end
44
+
45
+ it 'records the defusion in history' do
46
+ engine.apply_defusion(thought_id: thought_id, technique: :labeling)
47
+ expect(engine.defusion_history.size).to eq(1)
48
+ expect(engine.defusion_history.first[:technique]).to eq(:labeling)
49
+ end
50
+
51
+ it 'returns error for unknown thought_id' do
52
+ result = engine.apply_defusion(thought_id: 'nonexistent', technique: :labeling)
53
+ expect(result[:error]).to eq(:thought_not_found)
54
+ end
55
+
56
+ it 'returns error for invalid technique' do
57
+ result = engine.apply_defusion(thought_id: thought_id, technique: :voodoo)
58
+ expect(result[:error]).to eq(:invalid_technique)
59
+ end
60
+
61
+ it 'includes fusion_label in result' do
62
+ result = engine.apply_defusion(thought_id: thought_id, technique: :acceptance)
63
+ expect(result[:fusion_label]).to be_a(Symbol)
64
+ end
65
+ end
66
+
67
+ describe '#apply_all_techniques' do
68
+ let(:thought_id) { register[:thought_id] }
69
+
70
+ it 'applies all techniques and returns aggregated result' do
71
+ result = engine.apply_all_techniques(thought_id: thought_id)
72
+ expect(result[:total_applied]).to eq(Legion::Extensions::Agentic::Affect::Defusion::Helpers::Constants::DEFUSION_TECHNIQUES.size)
73
+ expect(result[:final_fusion]).to be_a(Float)
74
+ end
75
+
76
+ it 'significantly reduces fusion' do
77
+ initial = engine.thoughts[thought_id].fusion_level
78
+ engine.apply_all_techniques(thought_id: thought_id)
79
+ expect(engine.thoughts[thought_id].fusion_level).to be < initial
80
+ end
81
+
82
+ it 'returns error for unknown thought_id' do
83
+ result = engine.apply_all_techniques(thought_id: 'nonexistent')
84
+ expect(result[:error]).to eq(:thought_not_found)
85
+ end
86
+ end
87
+
88
+ describe '#visit_thought' do
89
+ let(:thought_id) { register[:thought_id] }
90
+
91
+ it 'increments visit count' do
92
+ result = engine.visit_thought(thought_id: thought_id)
93
+ expect(result[:visit_count]).to eq(1)
94
+ end
95
+
96
+ it 'returns ruminating flag' do
97
+ result = engine.visit_thought(thought_id: thought_id)
98
+ expect(result).to have_key(:ruminating)
99
+ end
100
+
101
+ it 'returns error for unknown thought_id' do
102
+ result = engine.visit_thought(thought_id: 'nonexistent')
103
+ expect(result[:error]).to eq(:thought_not_found)
104
+ end
105
+
106
+ it 'becomes ruminating after RUMINATION_COUNT visits' do
107
+ count = Legion::Extensions::Agentic::Affect::Defusion::Helpers::Constants::RUMINATION_COUNT
108
+ count.times { engine.visit_thought(thought_id: thought_id) }
109
+ result = engine.visit_thought(thought_id: thought_id)
110
+ expect(result[:ruminating]).to be true
111
+ end
112
+ end
113
+
114
+ describe '#enmeshed_thoughts' do
115
+ it 'returns thoughts above fusion threshold' do
116
+ register
117
+ expect(engine.enmeshed_thoughts.size).to be >= 1
118
+ end
119
+
120
+ it 'excludes defused thoughts' do
121
+ result = register
122
+ tid = result[:thought_id]
123
+ 20.times { engine.apply_defusion(thought_id: tid, technique: :acceptance) }
124
+ expect(engine.enmeshed_thoughts).not_to include(engine.thoughts[tid])
125
+ end
126
+ end
127
+
128
+ describe '#defused_thoughts' do
129
+ it 'returns empty when no thoughts are defused' do
130
+ register
131
+ expect(engine.defused_thoughts).to be_empty
132
+ end
133
+
134
+ it 'returns thought after full defusion' do
135
+ result = register
136
+ tid = result[:thought_id]
137
+ 20.times { engine.apply_defusion(thought_id: tid, technique: :acceptance) }
138
+ expect(engine.defused_thoughts).to include(engine.thoughts[tid])
139
+ end
140
+ end
141
+
142
+ describe '#ruminating_thoughts' do
143
+ it 'returns empty with no visits' do
144
+ register
145
+ expect(engine.ruminating_thoughts).to be_empty
146
+ end
147
+
148
+ it 'returns thought after enough visits' do
149
+ result = register
150
+ tid = result[:thought_id]
151
+ count = Legion::Extensions::Agentic::Affect::Defusion::Helpers::Constants::RUMINATION_COUNT
152
+ count.times { engine.visit_thought(thought_id: tid) }
153
+ expect(engine.ruminating_thoughts).not_to be_empty
154
+ end
155
+ end
156
+
157
+ describe '#most_fused' do
158
+ it 'returns thoughts sorted by fusion descending' do
159
+ r1 = register(content: 'thought 1', thought_type: :belief, belief_strength: 0.8)
160
+ r2 = register(content: 'thought 2', thought_type: :judgment, belief_strength: 0.5)
161
+ # Defuse r2 to make r1 more fused
162
+ 5.times { engine.apply_defusion(thought_id: r2[:thought_id], technique: :acceptance) }
163
+
164
+ top = engine.most_fused(limit: 2)
165
+ expect(top.first.id).to eq(r1[:thought_id])
166
+ end
167
+
168
+ it 'respects the limit parameter' do
169
+ 5.times { |i| register(content: "thought #{i}", thought_type: :belief, belief_strength: 0.5) }
170
+ expect(engine.most_fused(limit: 2).size).to eq(2)
171
+ end
172
+ end
173
+
174
+ describe '#recommend_technique' do
175
+ it 'returns a recommended technique for the thought type' do
176
+ result = register
177
+ rec = engine.recommend_technique(thought_id: result[:thought_id])
178
+ expect(rec[:technique]).to be_a(Symbol)
179
+ expect(Legion::Extensions::Agentic::Affect::Defusion::Helpers::Constants::DEFUSION_TECHNIQUES).to include(rec[:technique])
180
+ end
181
+
182
+ it 'recommends acceptance for belief type' do
183
+ result = register(thought_type: :belief)
184
+ rec = engine.recommend_technique(thought_id: result[:thought_id])
185
+ expect(rec[:technique]).to eq(:acceptance)
186
+ end
187
+
188
+ it 'returns error for unknown thought_id' do
189
+ result = engine.recommend_technique(thought_id: 'nonexistent')
190
+ expect(result[:error]).to eq(:thought_not_found)
191
+ end
192
+
193
+ it 'includes projected_fusion' do
194
+ result = register
195
+ rec = engine.recommend_technique(thought_id: result[:thought_id])
196
+ expect(rec[:projected_fusion]).to be < rec[:current_fusion]
197
+ end
198
+ end
199
+
200
+ describe '#average_fusion' do
201
+ it 'returns 0.0 with no thoughts' do
202
+ expect(engine.average_fusion).to eq(0.0)
203
+ end
204
+
205
+ it 'returns DEFAULT_FUSION when all thoughts are fresh' do
206
+ 2.times { register }
207
+ expect(engine.average_fusion).to be_within(0.001).of(Legion::Extensions::Agentic::Affect::Defusion::Helpers::Constants::DEFAULT_FUSION)
208
+ end
209
+
210
+ it 'decreases as thoughts are defused' do
211
+ result = register
212
+ before_avg = engine.average_fusion
213
+ 10.times { engine.apply_defusion(thought_id: result[:thought_id], technique: :acceptance) }
214
+ expect(engine.average_fusion).to be < before_avg
215
+ end
216
+ end
217
+
218
+ describe '#defusion_effectiveness' do
219
+ it 'returns 0.0 with no history' do
220
+ expect(engine.defusion_effectiveness).to eq(0.0)
221
+ end
222
+
223
+ it 'returns positive value after defusion attempts' do
224
+ result = register
225
+ engine.apply_defusion(thought_id: result[:thought_id], technique: :acceptance)
226
+ expect(engine.defusion_effectiveness).to be > 0.0
227
+ end
228
+ end
229
+
230
+ describe '#defusion_report' do
231
+ it 'includes all expected keys' do
232
+ register
233
+ report = engine.defusion_report
234
+ %i[total_thoughts enmeshed_count defused_count ruminating_count average_fusion
235
+ defusion_attempts defusion_effectiveness most_fused].each do |key|
236
+ expect(report).to have_key(key)
237
+ end
238
+ end
239
+ end
240
+
241
+ describe '#to_h' do
242
+ it 'includes thoughts, defusion_history, and report' do
243
+ register
244
+ h = engine.to_h
245
+ expect(h).to have_key(:thoughts)
246
+ expect(h).to have_key(:defusion_history)
247
+ expect(h).to have_key(:report)
248
+ end
249
+ end
250
+ end
@@ -0,0 +1,178 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Affect::Defusion::Helpers::Thought do
4
+ let(:thought) { described_class.new(content: 'I am a failure', thought_type: :belief, belief_strength: 0.8) }
5
+
6
+ describe '#initialize' do
7
+ it 'assigns a UUID id' do
8
+ expect(thought.id).to match(/\A[0-9a-f-]{36}\z/)
9
+ end
10
+
11
+ it 'stores content' do
12
+ expect(thought.content).to eq('I am a failure')
13
+ end
14
+
15
+ it 'stores thought_type' do
16
+ expect(thought.thought_type).to eq(:belief)
17
+ end
18
+
19
+ it 'stores belief_strength' do
20
+ expect(thought.belief_strength).to eq(0.8)
21
+ end
22
+
23
+ it 'starts at DEFAULT_FUSION' do
24
+ expect(thought.fusion_level).to eq(Legion::Extensions::Agentic::Affect::Defusion::Helpers::Constants::DEFAULT_FUSION)
25
+ end
26
+
27
+ it 'starts with zero defusion_count' do
28
+ expect(thought.defusion_count).to eq(0)
29
+ end
30
+
31
+ it 'starts with zero visit_count' do
32
+ expect(thought.visit_count).to eq(0)
33
+ end
34
+
35
+ it 'clamps belief_strength above 1.0' do
36
+ t = described_class.new(content: 'x', thought_type: :judgment, belief_strength: 1.5)
37
+ expect(t.belief_strength).to eq(1.0)
38
+ end
39
+
40
+ it 'clamps belief_strength below 0.0' do
41
+ t = described_class.new(content: 'x', thought_type: :judgment, belief_strength: -0.2)
42
+ expect(t.belief_strength).to eq(0.0)
43
+ end
44
+ end
45
+
46
+ describe '#defuse!' do
47
+ it 'reduces fusion by the technique potency' do
48
+ potency = Legion::Extensions::Agentic::Affect::Defusion::Helpers::Constants::TECHNIQUE_POTENCY[:labeling]
49
+ result = thought.defuse!(technique: :labeling)
50
+ expect(result[:reduction]).to be_within(0.0001).of(potency)
51
+ expect(thought.fusion_level).to be_within(0.0001).of(0.7 - potency)
52
+ end
53
+
54
+ it 'increments defusion_count' do
55
+ thought.defuse!(technique: :acceptance)
56
+ expect(thought.defusion_count).to eq(1)
57
+ end
58
+
59
+ it 'does not reduce fusion below 0.0' do
60
+ 10.times { thought.defuse!(technique: :acceptance) }
61
+ expect(thought.fusion_level).to be >= 0.0
62
+ end
63
+
64
+ it 'returns before, after, and reduction' do
65
+ result = thought.defuse!(technique: :distancing)
66
+ expect(result).to have_key(:before)
67
+ expect(result).to have_key(:after)
68
+ expect(result).to have_key(:reduction)
69
+ end
70
+ end
71
+
72
+ describe '#fuse!' do
73
+ it 'increases fusion by FUSION_DELTA_FUSE' do
74
+ delta = Legion::Extensions::Agentic::Affect::Defusion::Helpers::Constants::FUSION_DELTA_FUSE
75
+ before = thought.fusion_level
76
+ result = thought.fuse!
77
+ expect(result[:after]).to be_within(0.0001).of(before + delta)
78
+ end
79
+
80
+ it 'does not exceed 1.0' do
81
+ # Start from a high value
82
+ t = described_class.new(content: 'x', thought_type: :rule, belief_strength: 0.5)
83
+ 20.times { t.fuse! }
84
+ expect(t.fusion_level).to eq(1.0)
85
+ end
86
+ end
87
+
88
+ describe '#visit!' do
89
+ it 'increments visit_count' do
90
+ thought.visit!
91
+ expect(thought.visit_count).to eq(1)
92
+ end
93
+
94
+ it 'slightly increases fusion' do
95
+ delta = Legion::Extensions::Agentic::Affect::Defusion::Helpers::Constants::FUSION_DELTA_VISIT
96
+ before = thought.fusion_level
97
+ thought.visit!
98
+ expect(thought.fusion_level).to be_within(0.0001).of(before + delta)
99
+ end
100
+
101
+ it 'returns visit_count and fusion values' do
102
+ result = thought.visit!
103
+ expect(result).to have_key(:visit_count)
104
+ expect(result).to have_key(:fusion_before)
105
+ expect(result).to have_key(:fusion_after)
106
+ end
107
+ end
108
+
109
+ describe '#enmeshed?' do
110
+ it 'returns true when fusion >= FUSION_THRESHOLD' do
111
+ # default fusion is 0.7 which equals the threshold
112
+ expect(thought.enmeshed?).to be true
113
+ end
114
+
115
+ it 'returns false when fusion is low' do
116
+ 10.times { thought.defuse!(technique: :acceptance) }
117
+ expect(thought.enmeshed?).to be false
118
+ end
119
+ end
120
+
121
+ describe '#defused?' do
122
+ it 'returns false at default fusion' do
123
+ expect(thought.defused?).to be false
124
+ end
125
+
126
+ it 'returns true when fusion drops below DEFUSED_THRESHOLD' do
127
+ 20.times { thought.defuse!(technique: :acceptance) }
128
+ expect(thought.defused?).to be true
129
+ end
130
+ end
131
+
132
+ describe '#ruminating?' do
133
+ it 'returns false at zero visits' do
134
+ expect(thought.ruminating?).to be false
135
+ end
136
+
137
+ it 'returns true at RUMINATION_COUNT visits' do
138
+ count = Legion::Extensions::Agentic::Affect::Defusion::Helpers::Constants::RUMINATION_COUNT
139
+ count.times { thought.visit! }
140
+ expect(thought.ruminating?).to be true
141
+ end
142
+ end
143
+
144
+ describe '#fusion_label' do
145
+ it 'returns :enmeshed at default fusion 0.7' do
146
+ # 0.7 is >= FUSION_THRESHOLD, but label range is 0.6...0.8 => :fused
147
+ # and 0.8.. => :enmeshed. 0.7 falls in fused.
148
+ expect(thought.fusion_label).to eq(:fused)
149
+ end
150
+
151
+ it 'returns :fully_defused for very low fusion' do
152
+ 20.times { thought.defuse!(technique: :acceptance) }
153
+ expect(thought.fusion_label).to eq(:fully_defused)
154
+ end
155
+ end
156
+
157
+ describe '#belief_label' do
158
+ it 'returns :strong for belief_strength 0.8' do
159
+ # 0.8.. is entrenched, 0.6...0.8 is strong. 0.8 is on the boundary => entrenched
160
+ expect(thought.belief_label).to eq(:entrenched)
161
+ end
162
+
163
+ it 'returns :moderate for belief_strength 0.5' do
164
+ t = described_class.new(content: 'x', thought_type: :assumption, belief_strength: 0.5)
165
+ expect(t.belief_label).to eq(:moderate)
166
+ end
167
+ end
168
+
169
+ describe '#to_h' do
170
+ it 'includes all expected keys' do
171
+ h = thought.to_h
172
+ %i[id content thought_type belief_strength fusion_level defusion_count
173
+ visit_count fusion_label belief_label enmeshed defused ruminating created_at].each do |key|
174
+ expect(h).to have_key(key)
175
+ end
176
+ end
177
+ end
178
+ end