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,112 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe Legion::Extensions::Agentic::Affect::Flow::Helpers::Constants do
6
+ describe 'FLOW_STATES' do
7
+ it 'contains all eight Csikszentmihalyi states' do
8
+ expect(described_class::FLOW_STATES).to contain_exactly(
9
+ :flow, :arousal, :control, :relaxation, :boredom, :apathy, :worry, :anxiety
10
+ )
11
+ end
12
+
13
+ it 'is frozen' do
14
+ expect(described_class::FLOW_STATES).to be_frozen
15
+ end
16
+ end
17
+
18
+ describe 'FLOW_ZONE' do
19
+ it 'defines challenge and skill boundaries' do
20
+ zone = described_class::FLOW_ZONE
21
+ expect(zone[:challenge_min]).to eq(0.4)
22
+ expect(zone[:challenge_max]).to eq(0.8)
23
+ expect(zone[:skill_min]).to eq(0.4)
24
+ expect(zone[:skill_max]).to eq(0.8)
25
+ end
26
+
27
+ it 'defines balance tolerance' do
28
+ expect(described_class::FLOW_ZONE[:balance_tolerance]).to eq(0.15)
29
+ end
30
+
31
+ it 'is frozen' do
32
+ expect(described_class::FLOW_ZONE).to be_frozen
33
+ end
34
+ end
35
+
36
+ describe 'FLOW_ALPHA' do
37
+ it 'is 0.15' do
38
+ expect(described_class::FLOW_ALPHA).to eq(0.15)
39
+ end
40
+ end
41
+
42
+ describe 'DEEP_FLOW_THRESHOLD' do
43
+ it 'is 20 consecutive ticks' do
44
+ expect(described_class::DEEP_FLOW_THRESHOLD).to eq(20)
45
+ end
46
+ end
47
+
48
+ describe 'FLOW_EFFECTS' do
49
+ it 'defines performance and creativity boosts' do
50
+ effects = described_class::FLOW_EFFECTS
51
+ expect(effects[:performance_boost]).to eq(1.15)
52
+ expect(effects[:creativity_boost]).to eq(1.2)
53
+ end
54
+
55
+ it 'defines fatigue reduction' do
56
+ expect(described_class::FLOW_EFFECTS[:fatigue_reduction]).to eq(0.5)
57
+ end
58
+
59
+ it 'is frozen' do
60
+ expect(described_class::FLOW_EFFECTS).to be_frozen
61
+ end
62
+ end
63
+
64
+ describe 'FLOW_BREAKERS' do
65
+ it 'contains five breaker types' do
66
+ expect(described_class::FLOW_BREAKERS.size).to eq(5)
67
+ expect(described_class::FLOW_BREAKERS).to include(:high_anxiety, :burnout, :trust_violation)
68
+ end
69
+
70
+ it 'is frozen' do
71
+ expect(described_class::FLOW_BREAKERS).to be_frozen
72
+ end
73
+ end
74
+
75
+ describe 'STATE_REGIONS' do
76
+ it 'defines eight regions' do
77
+ expect(described_class::STATE_REGIONS.size).to eq(8)
78
+ end
79
+
80
+ it 'marks only flow as balanced' do
81
+ balanced = described_class::STATE_REGIONS.select { |_, v| v[:balanced] }.keys
82
+ expect(balanced).to eq([:flow])
83
+ end
84
+
85
+ it 'uses Range objects for challenge and skill' do
86
+ described_class::STATE_REGIONS.each_value do |region|
87
+ expect(region[:challenge]).to be_a(Range)
88
+ expect(region[:skill]).to be_a(Range)
89
+ end
90
+ end
91
+ end
92
+
93
+ describe 'score bonuses' do
94
+ it 'defines deep flow bonus' do
95
+ expect(described_class::DEEP_FLOW_BONUS).to eq(0.1)
96
+ end
97
+
98
+ it 'defines curiosity bonus' do
99
+ expect(described_class::CURIOSITY_BONUS).to eq(0.05)
100
+ end
101
+
102
+ it 'defines low error bonus' do
103
+ expect(described_class::LOW_ERROR_BONUS).to eq(0.05)
104
+ end
105
+ end
106
+
107
+ describe 'MAX_FLOW_HISTORY' do
108
+ it 'caps at 100' do
109
+ expect(described_class::MAX_FLOW_HISTORY).to eq(100)
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,268 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe Legion::Extensions::Agentic::Affect::Flow::Helpers::FlowDetector do
6
+ subject(:detector) { described_class.new }
7
+
8
+ describe '#initialize' do
9
+ it 'starts with neutral challenge and skill' do
10
+ expect(detector.challenge).to eq(0.5)
11
+ expect(detector.skill).to eq(0.5)
12
+ end
13
+
14
+ it 'starts in relaxation state' do
15
+ expect(detector.flow_state).to eq(:relaxation)
16
+ end
17
+
18
+ it 'starts with zero flow score' do
19
+ expect(detector.flow_score).to eq(0.0)
20
+ end
21
+
22
+ it 'starts with zero consecutive and total flow ticks' do
23
+ expect(detector.consecutive_flow_ticks).to eq(0)
24
+ expect(detector.total_flow_ticks).to eq(0)
25
+ end
26
+
27
+ it 'starts with empty history' do
28
+ expect(detector.history).to be_empty
29
+ end
30
+ end
31
+
32
+ describe '#update' do
33
+ it 'applies EMA to challenge and skill' do
34
+ detector.update(challenge_input: 0.8, skill_input: 0.8)
35
+ expect(detector.challenge).to be > 0.5
36
+ expect(detector.skill).to be > 0.5
37
+ end
38
+
39
+ it 'clamps inputs to 0.0..1.0' do
40
+ detector.update(challenge_input: 2.0, skill_input: -1.0)
41
+ expect(detector.challenge).to be_between(0.0, 1.0)
42
+ expect(detector.skill).to be_between(0.0, 1.0)
43
+ end
44
+
45
+ it 'records a snapshot in history' do
46
+ detector.update(challenge_input: 0.6, skill_input: 0.6)
47
+ expect(detector.history.size).to eq(1)
48
+ expect(detector.history.last).to include(:state, :flow_score, :challenge, :skill, :at)
49
+ end
50
+
51
+ it 'caps history at MAX_FLOW_HISTORY' do
52
+ max = Legion::Extensions::Agentic::Affect::Flow::Helpers::Constants::MAX_FLOW_HISTORY
53
+ (max + 10).times { detector.update(challenge_input: 0.6, skill_input: 0.6) }
54
+ expect(detector.history.size).to eq(max)
55
+ end
56
+
57
+ it 'accepts modifiers' do
58
+ detector.update(challenge_input: 0.6, skill_input: 0.6, modifiers: { curiosity_active: true })
59
+ expect(detector.flow_score).to be >= 0.0
60
+ end
61
+ end
62
+
63
+ describe 'flow state detection' do
64
+ def push_to_flow(count = 30)
65
+ count.times { detector.update(challenge_input: 0.6, skill_input: 0.6) }
66
+ end
67
+
68
+ it 'enters flow when challenge and skill are balanced and moderate' do
69
+ push_to_flow
70
+ expect(detector.flow_state).to eq(:flow)
71
+ end
72
+
73
+ it 'detects arousal when challenge is high and skill is moderate-low' do
74
+ 30.times { detector.update(challenge_input: 0.9, skill_input: 0.4) }
75
+ expect(detector.flow_state).to eq(:arousal)
76
+ end
77
+
78
+ it 'detects boredom when challenge is low and skill is high' do
79
+ 30.times { detector.update(challenge_input: 0.1, skill_input: 0.8) }
80
+ expect(detector.flow_state).to eq(:boredom)
81
+ end
82
+
83
+ it 'detects anxiety when challenge is very high and skill is very low' do
84
+ 30.times { detector.update(challenge_input: 0.95, skill_input: 0.1) }
85
+ expect(detector.flow_state).to eq(:anxiety)
86
+ end
87
+
88
+ it 'detects apathy when both are very low' do
89
+ 30.times { detector.update(challenge_input: 0.1, skill_input: 0.1) }
90
+ expect(detector.flow_state).to eq(:apathy)
91
+ end
92
+ end
93
+
94
+ describe '#in_flow?' do
95
+ it 'returns true when in flow state' do
96
+ 30.times { detector.update(challenge_input: 0.6, skill_input: 0.6) }
97
+ expect(detector.in_flow?).to be true
98
+ end
99
+
100
+ it 'returns false when not in flow' do
101
+ expect(detector.in_flow?).to be false
102
+ end
103
+ end
104
+
105
+ describe '#deep_flow?' do
106
+ it 'returns false before threshold' do
107
+ 19.times { detector.update(challenge_input: 0.6, skill_input: 0.6) }
108
+ expect(detector.deep_flow?).to be false
109
+ end
110
+
111
+ it 'returns true after consecutive flow ticks reach threshold' do
112
+ # Push challenge/skill into flow range quickly first
113
+ 30.times { detector.update(challenge_input: 0.6, skill_input: 0.6) }
114
+ # Now the detector should be in flow, keep it there past threshold
115
+ expect(detector.consecutive_flow_ticks).to be >= 20
116
+ expect(detector.deep_flow?).to be true
117
+ end
118
+ end
119
+
120
+ describe '#flow_effects' do
121
+ it 'returns neutral effects when not in flow' do
122
+ effects = detector.flow_effects
123
+ expect(effects[:performance_boost]).to eq(1.0)
124
+ expect(effects[:creativity_boost]).to eq(1.0)
125
+ expect(effects[:fatigue_reduction]).to eq(1.0)
126
+ end
127
+
128
+ it 'returns enhanced effects when in flow' do
129
+ 30.times { detector.update(challenge_input: 0.6, skill_input: 0.6) }
130
+ effects = detector.flow_effects
131
+ expect(effects[:performance_boost]).to be > 1.0
132
+ expect(effects[:creativity_boost]).to be > 1.0
133
+ expect(effects[:fatigue_reduction]).to be < 1.0
134
+ end
135
+
136
+ it 'provides extra boosts in deep flow' do
137
+ 40.times { detector.update(challenge_input: 0.6, skill_input: 0.6) }
138
+ effects = detector.flow_effects
139
+ base_perf = Legion::Extensions::Agentic::Affect::Flow::Helpers::Constants::FLOW_EFFECTS[:performance_boost]
140
+ expect(effects[:performance_boost]).to be > base_perf
141
+ end
142
+ end
143
+
144
+ describe '#challenge_skill_balance' do
145
+ it 'returns 0 when challenge equals skill' do
146
+ expect(detector.challenge_skill_balance).to eq(0.0)
147
+ end
148
+
149
+ it 'returns positive value when unbalanced' do
150
+ 30.times { detector.update(challenge_input: 0.9, skill_input: 0.1) }
151
+ expect(detector.challenge_skill_balance).to be > 0.0
152
+ end
153
+ end
154
+
155
+ describe '#flow_trend' do
156
+ it 'returns insufficient_data with fewer than 5 entries' do
157
+ 3.times { detector.update(challenge_input: 0.5, skill_input: 0.5) }
158
+ expect(detector.flow_trend).to eq(:insufficient_data)
159
+ end
160
+
161
+ it 'detects entering_flow when scores are increasing' do
162
+ 5.times { detector.update(challenge_input: 0.2, skill_input: 0.2) }
163
+ 10.times { detector.update(challenge_input: 0.6, skill_input: 0.6) }
164
+ trend = detector.flow_trend
165
+ expect(trend).to eq(:entering_flow).or eq(:stable)
166
+ end
167
+
168
+ it 'detects leaving_flow when scores are decreasing' do
169
+ 20.times { detector.update(challenge_input: 0.6, skill_input: 0.6) }
170
+ 10.times { detector.update(challenge_input: 0.1, skill_input: 0.9) }
171
+ trend = detector.flow_trend
172
+ expect(trend).to eq(:leaving_flow).or eq(:stable)
173
+ end
174
+
175
+ it 'returns stable when scores are consistent' do
176
+ 20.times { detector.update(challenge_input: 0.5, skill_input: 0.5) }
177
+ expect(detector.flow_trend).to eq(:stable)
178
+ end
179
+ end
180
+
181
+ describe '#flow_percentage' do
182
+ it 'returns 0.0 with empty history' do
183
+ expect(detector.flow_percentage).to eq(0.0)
184
+ end
185
+
186
+ it 'returns percentage of flow ticks' do
187
+ 30.times { detector.update(challenge_input: 0.6, skill_input: 0.6) }
188
+ expect(detector.flow_percentage).to be > 0.0
189
+ end
190
+ end
191
+
192
+ describe '#to_h' do
193
+ it 'returns a complete hash' do
194
+ detector.update(challenge_input: 0.6, skill_input: 0.6)
195
+ h = detector.to_h
196
+ expect(h).to include(
197
+ :state, :score, :challenge, :skill, :balance,
198
+ :in_flow, :deep_flow, :consecutive_flow_ticks,
199
+ :total_flow_ticks, :flow_percentage, :trend, :effects
200
+ )
201
+ end
202
+
203
+ it 'rounds numeric values' do
204
+ detector.update(challenge_input: 0.6, skill_input: 0.6)
205
+ h = detector.to_h
206
+ expect(h[:score].to_s.split('.').last.length).to be <= 3
207
+ expect(h[:challenge].to_s.split('.').last.length).to be <= 3
208
+ end
209
+ end
210
+
211
+ describe 'flow score computation' do
212
+ it 'gives higher scores when in flow with good balance' do
213
+ 30.times { detector.update(challenge_input: 0.6, skill_input: 0.6) }
214
+ expect(detector.flow_score).to be > 0.7
215
+ end
216
+
217
+ it 'gives lower scores when not in flow' do
218
+ 30.times { detector.update(challenge_input: 0.1, skill_input: 0.9) }
219
+ expect(detector.flow_score).to be < 0.5
220
+ end
221
+
222
+ it 'adds curiosity bonus' do
223
+ 20.times { detector.update(challenge_input: 0.6, skill_input: 0.6) }
224
+ score_without = detector.flow_score
225
+ detector2 = described_class.new
226
+ 20.times { detector2.update(challenge_input: 0.6, skill_input: 0.6, modifiers: { curiosity_active: true }) }
227
+ expect(detector2.flow_score).to be >= score_without
228
+ end
229
+
230
+ it 'adds low error bonus' do
231
+ 20.times { detector.update(challenge_input: 0.6, skill_input: 0.6) }
232
+ score_without = detector.flow_score
233
+ detector2 = described_class.new
234
+ 20.times { detector2.update(challenge_input: 0.6, skill_input: 0.6, modifiers: { low_errors: true }) }
235
+ expect(detector2.flow_score).to be >= score_without
236
+ end
237
+
238
+ it 'clamps score to 0.0..1.0' do
239
+ 50.times do
240
+ detector.update(challenge_input: 0.6, skill_input: 0.6,
241
+ modifiers: { curiosity_active: true, low_errors: true })
242
+ end
243
+ expect(detector.flow_score).to be_between(0.0, 1.0)
244
+ end
245
+ end
246
+
247
+ describe 'consecutive flow tick tracking' do
248
+ it 'increments when in flow' do
249
+ 30.times { detector.update(challenge_input: 0.6, skill_input: 0.6) }
250
+ expect(detector.consecutive_flow_ticks).to be > 0
251
+ end
252
+
253
+ it 'resets when leaving flow' do
254
+ 30.times { detector.update(challenge_input: 0.6, skill_input: 0.6) }
255
+ expect(detector.consecutive_flow_ticks).to be > 0
256
+ 30.times { detector.update(challenge_input: 0.1, skill_input: 0.9) }
257
+ expect(detector.consecutive_flow_ticks).to eq(0)
258
+ end
259
+
260
+ it 'accumulates total flow ticks across sessions' do
261
+ 30.times { detector.update(challenge_input: 0.6, skill_input: 0.6) }
262
+ first_total = detector.total_flow_ticks
263
+ 30.times { detector.update(challenge_input: 0.1, skill_input: 0.9) }
264
+ 30.times { detector.update(challenge_input: 0.6, skill_input: 0.6) }
265
+ expect(detector.total_flow_ticks).to be > first_total
266
+ end
267
+ end
268
+ end
@@ -0,0 +1,222 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe Legion::Extensions::Agentic::Affect::Flow::Runners::Flow do
6
+ let(:client) { Legion::Extensions::Agentic::Affect::Flow::Client.new }
7
+
8
+ describe '#update_flow' do
9
+ let(:tick_results) do
10
+ {
11
+ prediction_engine: { rolling_accuracy: 0.6, error_rate: 0.3 },
12
+ action_selection: { complexity: 0.5 },
13
+ memory_retrieval: { avg_strength: 0.6 },
14
+ habit: { automation_level: 0.5 },
15
+ curiosity: { intensity: 0.3 },
16
+ emotional_evaluation: { anxiety: 0.2 }
17
+ }
18
+ end
19
+
20
+ it 'returns flow state hash' do
21
+ result = client.update_flow(tick_results: tick_results)
22
+ expect(result).to include(:state, :score, :in_flow, :deep_flow, :effects, :breakers, :challenge, :skill)
23
+ end
24
+
25
+ it 'returns numeric score' do
26
+ result = client.update_flow(tick_results: tick_results)
27
+ expect(result[:score]).to be_a(Float)
28
+ expect(result[:score]).to be_between(0.0, 1.0)
29
+ end
30
+
31
+ it 'returns challenge and skill values' do
32
+ result = client.update_flow(tick_results: tick_results)
33
+ expect(result[:challenge]).to be_between(0.0, 1.0)
34
+ expect(result[:skill]).to be_between(0.0, 1.0)
35
+ end
36
+
37
+ it 'returns empty breakers for normal tick' do
38
+ result = client.update_flow(tick_results: tick_results)
39
+ expect(result[:breakers]).to be_empty
40
+ end
41
+
42
+ it 'returns effects hash' do
43
+ result = client.update_flow(tick_results: tick_results)
44
+ expect(result[:effects]).to be_a(Hash)
45
+ end
46
+
47
+ context 'with flow breakers' do
48
+ it 'detects high anxiety' do
49
+ tick_results[:emotional_evaluation][:anxiety] = 0.9
50
+ # Push into flow first
51
+ 20.times { client.update_flow(tick_results: tick_results.merge(emotional_evaluation: { anxiety: 0.1 })) }
52
+ result = client.update_flow(tick_results: tick_results)
53
+ expect(result[:breakers]).to include(:high_anxiety)
54
+ end
55
+
56
+ it 'detects trust violation' do
57
+ tick_results[:trust] = { violation: true }
58
+ result = client.update_flow(tick_results: tick_results)
59
+ expect(result[:breakers]).to include(:trust_violation)
60
+ end
61
+
62
+ it 'detects critical error' do
63
+ tick_results[:error] = { critical: true }
64
+ result = client.update_flow(tick_results: tick_results)
65
+ expect(result[:breakers]).to include(:critical_error)
66
+ end
67
+
68
+ it 'detects burnout' do
69
+ tick_results[:fatigue] = { burnout: true }
70
+ result = client.update_flow(tick_results: tick_results)
71
+ expect(result[:breakers]).to include(:burnout)
72
+ end
73
+
74
+ it 'detects conflict escalation' do
75
+ tick_results[:conflict] = { severity: 4 }
76
+ result = client.update_flow(tick_results: tick_results)
77
+ expect(result[:breakers]).to include(:conflict_escalation)
78
+ end
79
+ end
80
+
81
+ context 'with default tick_results' do
82
+ it 'handles empty tick_results' do
83
+ result = client.update_flow(tick_results: {})
84
+ expect(result[:state]).to be_a(Symbol)
85
+ expect(result[:score]).to be_a(Float)
86
+ end
87
+ end
88
+ end
89
+
90
+ describe '#flow_status' do
91
+ it 'returns full status hash' do
92
+ status = client.flow_status
93
+ expect(status).to include(
94
+ :state, :score, :challenge, :skill, :balance,
95
+ :in_flow, :deep_flow, :consecutive_flow_ticks,
96
+ :total_flow_ticks, :flow_percentage, :trend, :effects
97
+ )
98
+ end
99
+ end
100
+
101
+ describe '#flow_effects' do
102
+ it 'returns effects with flow info' do
103
+ result = client.flow_effects
104
+ expect(result).to include(:effects, :in_flow, :deep_flow)
105
+ expect(result[:effects]).to be_a(Hash)
106
+ end
107
+
108
+ it 'returns neutral effects initially' do
109
+ result = client.flow_effects
110
+ expect(result[:effects][:performance_boost]).to eq(1.0)
111
+ end
112
+ end
113
+
114
+ describe '#flow_history' do
115
+ it 'returns empty history initially' do
116
+ result = client.flow_history
117
+ expect(result[:history]).to be_empty
118
+ expect(result[:total]).to eq(0)
119
+ end
120
+
121
+ it 'returns history after updates' do
122
+ 5.times { client.update_flow(tick_results: {}) }
123
+ result = client.flow_history
124
+ expect(result[:history].size).to eq(5)
125
+ expect(result[:total]).to eq(5)
126
+ end
127
+
128
+ it 'respects limit parameter' do
129
+ 10.times { client.update_flow(tick_results: {}) }
130
+ result = client.flow_history(limit: 3)
131
+ expect(result[:history].size).to eq(3)
132
+ expect(result[:total]).to eq(10)
133
+ end
134
+ end
135
+
136
+ describe '#flow_stats' do
137
+ it 'returns stats hash' do
138
+ stats = client.flow_stats
139
+ expect(stats).to include(:state, :score, :consecutive_flow_ticks, :total_flow_ticks,
140
+ :flow_percentage, :trend, :balance)
141
+ end
142
+
143
+ it 'returns numeric values' do
144
+ stats = client.flow_stats
145
+ expect(stats[:score]).to be_a(Float)
146
+ expect(stats[:flow_percentage]).to be_a(Float)
147
+ expect(stats[:balance]).to be_a(Float)
148
+ end
149
+ end
150
+
151
+ describe 'challenge/skill extraction' do
152
+ it 'derives challenge from prediction accuracy, error rate, and complexity' do
153
+ high_challenge = {
154
+ prediction_engine: { rolling_accuracy: 0.2, error_rate: 0.8 },
155
+ action_selection: { complexity: 0.9 }
156
+ }
157
+ low_challenge = {
158
+ prediction_engine: { rolling_accuracy: 0.9, error_rate: 0.1 },
159
+ action_selection: { complexity: 0.1 }
160
+ }
161
+
162
+ client_high = Legion::Extensions::Agentic::Affect::Flow::Client.new
163
+ client_low = Legion::Extensions::Agentic::Affect::Flow::Client.new
164
+ 20.times do
165
+ client_high.update_flow(tick_results: high_challenge)
166
+ client_low.update_flow(tick_results: low_challenge)
167
+ end
168
+
169
+ high_result = client_high.flow_status
170
+ low_result = client_low.flow_status
171
+ expect(high_result[:challenge]).to be > low_result[:challenge]
172
+ end
173
+
174
+ it 'derives skill from prediction accuracy, memory strength, and habit' do
175
+ high_skill = {
176
+ prediction_engine: { rolling_accuracy: 0.9 },
177
+ memory_retrieval: { avg_strength: 0.9 },
178
+ habit: { automation_level: 0.9 }
179
+ }
180
+ low_skill = {
181
+ prediction_engine: { rolling_accuracy: 0.1 },
182
+ memory_retrieval: { avg_strength: 0.1 },
183
+ habit: { automation_level: 0.1 }
184
+ }
185
+
186
+ client_high = Legion::Extensions::Agentic::Affect::Flow::Client.new
187
+ client_low = Legion::Extensions::Agentic::Affect::Flow::Client.new
188
+ 20.times do
189
+ client_high.update_flow(tick_results: high_skill)
190
+ client_low.update_flow(tick_results: low_skill)
191
+ end
192
+
193
+ high_result = client_high.flow_status
194
+ low_result = client_low.flow_status
195
+ expect(high_result[:skill]).to be > low_result[:skill]
196
+ end
197
+ end
198
+
199
+ describe 'flow progression over time' do
200
+ let(:balanced_tick) do
201
+ {
202
+ prediction_engine: { rolling_accuracy: 0.5, error_rate: 0.5 },
203
+ action_selection: { complexity: 0.7 },
204
+ memory_retrieval: { avg_strength: 0.7 },
205
+ habit: { automation_level: 0.7 },
206
+ curiosity: { intensity: 0.6 }
207
+ }
208
+ end
209
+
210
+ it 'reaches flow state with sustained balanced input' do
211
+ 40.times { client.update_flow(tick_results: balanced_tick) }
212
+ expect(client.flow_status[:in_flow]).to be true
213
+ end
214
+
215
+ it 'reaches deep flow after threshold' do
216
+ 50.times { client.update_flow(tick_results: balanced_tick) }
217
+ status = client.flow_status
218
+ # May or may not reach deep flow depending on exact EMA convergence
219
+ expect(status[:consecutive_flow_ticks]).to be >= 0
220
+ end
221
+ end
222
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Affect::Interoception::Client do
4
+ subject(:client) { described_class.new }
5
+
6
+ it 'includes Runners::Interoception' do
7
+ expect(described_class.ancestors).to include(Legion::Extensions::Agentic::Affect::Interoception::Runners::Interoception)
8
+ end
9
+
10
+ it 'responds to all runner methods' do
11
+ expect(client).to respond_to(:report_vital)
12
+ expect(client).to respond_to(:create_somatic_marker)
13
+ expect(client).to respond_to(:query_bias)
14
+ expect(client).to respond_to(:reinforce_somatic)
15
+ expect(client).to respond_to(:deviating_vitals)
16
+ expect(client).to respond_to(:body_status)
17
+ expect(client).to respond_to(:update_interoception)
18
+ expect(client).to respond_to(:interoception_stats)
19
+ end
20
+
21
+ it 'supports full somatic marker lifecycle' do
22
+ # Report some vitals
23
+ client.report_vital(channel: :cpu_load, value: 0.3)
24
+ client.report_vital(channel: :connection_health, value: 0.9)
25
+
26
+ # Create markers based on outcomes
27
+ client.create_somatic_marker(action: :deploy, domain: :prod, valence: 0.8)
28
+ client.create_somatic_marker(action: :risky_change, domain: :prod, valence: -0.6)
29
+
30
+ # Query bias should reflect markers
31
+ approach = client.query_bias(action: :deploy)
32
+ expect(approach[:bias]).to be > 0
33
+
34
+ avoid = client.query_bias(action: :risky_change)
35
+ expect(avoid[:bias]).to be < 0
36
+
37
+ # Reinforce good outcome
38
+ client.reinforce_somatic(action: :deploy)
39
+
40
+ # Tick decay
41
+ client.update_interoception
42
+
43
+ # Check overall status
44
+ status = client.body_status
45
+ expect(status[:success]).to be true
46
+ expect(status[:health]).to be > 0
47
+
48
+ stats = client.interoception_stats
49
+ expect(stats[:stats][:channels]).to eq(2)
50
+ expect(stats[:stats][:markers]).to be >= 1
51
+ end
52
+ end