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,178 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Affect::Interoception::Helpers::BodyBudget do
4
+ subject(:budget) { described_class.new }
5
+
6
+ describe '#report_vital' do
7
+ it 'stores and smooths a vital signal' do
8
+ result = budget.report_vital(channel: :cpu_load, value: 0.7)
9
+ expect(result).to be_a(Float)
10
+ expect(result).to be_between(0.0, 1.0)
11
+ end
12
+
13
+ it 'applies EMA smoothing on repeated reports' do
14
+ budget.report_vital(channel: :cpu_load, value: 0.2)
15
+ budget.report_vital(channel: :cpu_load, value: 0.8)
16
+ val = budget.vital_for(:cpu_load)
17
+ expect(val).to be_between(0.2, 0.8)
18
+ end
19
+
20
+ it 'clamps values to 0..1' do
21
+ budget.report_vital(channel: :cpu_load, value: 5.0)
22
+ expect(budget.vital_for(:cpu_load)).to be <= 1.0
23
+ end
24
+
25
+ it 'updates baseline over time' do
26
+ 10.times { budget.report_vital(channel: :cpu_load, value: 0.9) }
27
+ expect(budget.baselines[:cpu_load]).to be > 0.5
28
+ end
29
+ end
30
+
31
+ describe '#deviation_for' do
32
+ it 'returns 0 for new channels' do
33
+ expect(budget.deviation_for(:cpu_load)).to eq(0.0)
34
+ end
35
+
36
+ it 'detects deviation from baseline' do
37
+ 5.times { budget.report_vital(channel: :cpu_load, value: 0.3) }
38
+ budget.report_vital(channel: :cpu_load, value: 0.9)
39
+ expect(budget.deviation_for(:cpu_load).abs).to be > 0
40
+ end
41
+ end
42
+
43
+ describe '#vital_label' do
44
+ it 'returns :healthy for healthy vitals' do
45
+ budget.report_vital(channel: :connection_health, value: 0.9)
46
+ expect(budget.vital_label(:connection_health)).to eq(:healthy)
47
+ end
48
+
49
+ it 'returns :critical for bad inverted vitals' do
50
+ budget.report_vital(channel: :cpu_load, value: 0.95)
51
+ expect(budget.vital_label(:cpu_load)).to eq(:critical)
52
+ end
53
+ end
54
+
55
+ describe '#vital_health' do
56
+ it 'inverts cpu_load (high load = low health)' do
57
+ budget.report_vital(channel: :cpu_load, value: 0.9)
58
+ expect(budget.vital_health(:cpu_load)).to be < 0.2
59
+ end
60
+
61
+ it 'keeps connection_health direct (high = good)' do
62
+ budget.report_vital(channel: :connection_health, value: 0.9)
63
+ expect(budget.vital_health(:connection_health)).to be > 0.8
64
+ end
65
+ end
66
+
67
+ describe '#deviating_channels' do
68
+ it 'returns empty when no deviations' do
69
+ expect(budget.deviating_channels).to be_empty
70
+ end
71
+
72
+ it 'detects channels with significant deviation' do
73
+ 5.times { budget.report_vital(channel: :cpu_load, value: 0.2) }
74
+ # Force a big jump
75
+ budget.report_vital(channel: :cpu_load, value: 0.95)
76
+ budget.report_vital(channel: :cpu_load, value: 0.95)
77
+ budget.report_vital(channel: :cpu_load, value: 0.95)
78
+ deviations = budget.deviating_channels
79
+ expect(deviations.first[:channel]).to eq(:cpu_load) if deviations.any?
80
+ end
81
+ end
82
+
83
+ describe 'somatic markers' do
84
+ describe '#create_marker' do
85
+ it 'creates and stores a marker' do
86
+ marker = budget.create_marker(action: :deploy, domain: :prod, valence: 0.7)
87
+ expect(marker).to be_a(Legion::Extensions::Agentic::Affect::Interoception::Helpers::SomaticMarker)
88
+ expect(budget.marker_count).to eq(1)
89
+ end
90
+ end
91
+
92
+ describe '#markers_for' do
93
+ it 'finds markers by action' do
94
+ budget.create_marker(action: :deploy, domain: :prod, valence: 0.5)
95
+ budget.create_marker(action: :deploy, domain: :staging, valence: 0.3)
96
+ budget.create_marker(action: :rollback, domain: :prod, valence: -0.5)
97
+ expect(budget.markers_for(action: :deploy).size).to eq(2)
98
+ end
99
+
100
+ it 'filters by domain' do
101
+ budget.create_marker(action: :deploy, domain: :prod, valence: 0.5)
102
+ budget.create_marker(action: :deploy, domain: :staging, valence: 0.3)
103
+ expect(budget.markers_for(action: :deploy, domain: :prod).size).to eq(1)
104
+ end
105
+ end
106
+
107
+ describe '#bias_for_action' do
108
+ it 'returns 0.0 with no markers' do
109
+ expect(budget.bias_for_action(action: :deploy)).to eq(0.0)
110
+ end
111
+
112
+ it 'returns positive bias for approach markers' do
113
+ budget.create_marker(action: :deploy, domain: :prod, valence: 0.8)
114
+ expect(budget.bias_for_action(action: :deploy)).to be > 0
115
+ end
116
+
117
+ it 'returns negative bias for avoid markers' do
118
+ budget.create_marker(action: :deploy, domain: :prod, valence: -0.8)
119
+ expect(budget.bias_for_action(action: :deploy)).to be < 0
120
+ end
121
+ end
122
+
123
+ describe '#reinforce_markers' do
124
+ it 'increases strength of matching markers' do
125
+ budget.create_marker(action: :deploy, domain: :prod, valence: 0.5, strength: 0.4)
126
+ budget.reinforce_markers(action: :deploy, amount: 0.2)
127
+ marker = budget.markers_for(action: :deploy).first
128
+ expect(marker.strength).to be_within(0.001).of(0.6)
129
+ end
130
+ end
131
+
132
+ describe '#decay_markers' do
133
+ it 'decays all markers' do
134
+ budget.create_marker(action: :deploy, domain: :prod, valence: 0.5)
135
+ before = budget.markers_for(action: :deploy).first.strength
136
+ budget.decay_markers
137
+ after = budget.markers_for(action: :deploy).first&.strength
138
+ expect(after).to be < before if after
139
+ end
140
+
141
+ it 'prunes faded markers' do
142
+ floor = Legion::Extensions::Agentic::Affect::Interoception::Helpers::Constants::MARKER_FLOOR
143
+ budget.create_marker(action: :deploy, domain: :prod, valence: 0.5, strength: floor + 0.01)
144
+ budget.decay_markers
145
+ expect(budget.marker_count).to eq(0)
146
+ end
147
+ end
148
+ end
149
+
150
+ describe '#overall_health' do
151
+ it 'returns DEFAULT_BASELINE with no vitals' do
152
+ expect(budget.overall_health).to eq(Legion::Extensions::Agentic::Affect::Interoception::Helpers::Constants::DEFAULT_BASELINE)
153
+ end
154
+
155
+ it 'computes average health across channels' do
156
+ budget.report_vital(channel: :connection_health, value: 0.9)
157
+ budget.report_vital(channel: :cpu_load, value: 0.1) # low load = healthy
158
+ expect(budget.overall_health).to be > 0.7
159
+ end
160
+ end
161
+
162
+ describe '#body_budget_label' do
163
+ it 'returns :comfortable for healthy vitals' do
164
+ budget.report_vital(channel: :connection_health, value: 0.7)
165
+ label = budget.body_budget_label
166
+ expect(%i[thriving comfortable]).to include(label)
167
+ end
168
+ end
169
+
170
+ describe '#to_h' do
171
+ it 'returns comprehensive stats' do
172
+ budget.report_vital(channel: :cpu_load, value: 0.3)
173
+ budget.create_marker(action: :deploy, domain: :prod, valence: 0.5)
174
+ h = budget.to_h
175
+ expect(h).to include(:overall_health, :body_budget_label, :channels, :markers, :vitals, :deviations)
176
+ end
177
+ end
178
+ end
@@ -0,0 +1,120 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Affect::Interoception::Helpers::SomaticMarker do
4
+ subject(:marker) { described_class.new(action: :deploy, domain: :production, valence: 0.6) }
5
+
6
+ describe '#initialize' do
7
+ it 'assigns fields' do
8
+ expect(marker.action).to eq(:deploy)
9
+ expect(marker.domain).to eq(:production)
10
+ expect(marker.valence).to eq(0.6)
11
+ expect(marker.strength).to eq(1.0)
12
+ end
13
+
14
+ it 'assigns uuid and timestamp' do
15
+ expect(marker.id).to match(/\A[0-9a-f-]{36}\z/)
16
+ expect(marker.created_at).to be_a(Time)
17
+ end
18
+
19
+ it 'clamps valence to -1.0..1.0' do
20
+ high = described_class.new(action: :a, domain: :d, valence: 2.0)
21
+ low = described_class.new(action: :a, domain: :d, valence: -2.0)
22
+ expect(high.valence).to eq(1.0)
23
+ expect(low.valence).to eq(-1.0)
24
+ end
25
+
26
+ it 'clamps strength to 0.0..1.0' do
27
+ high = described_class.new(action: :a, domain: :d, valence: 0.5, strength: 5.0)
28
+ expect(high.strength).to eq(1.0)
29
+ end
30
+ end
31
+
32
+ describe '#bias_for' do
33
+ it 'returns positive bias for matching action with positive valence' do
34
+ bias = marker.bias_for(:deploy)
35
+ expect(bias).to be > 0
36
+ end
37
+
38
+ it 'returns 0 for non-matching action' do
39
+ expect(marker.bias_for(:rollback)).to eq(0.0)
40
+ end
41
+
42
+ it 'applies MARKER_INFLUENCE factor' do
43
+ influence = Legion::Extensions::Agentic::Affect::Interoception::Helpers::Constants::MARKER_INFLUENCE
44
+ expected = marker.valence * marker.strength * influence
45
+ expect(marker.bias_for(:deploy)).to be_within(0.001).of(expected)
46
+ end
47
+ end
48
+
49
+ describe '#reinforce' do
50
+ it 'increases strength' do
51
+ marker.strength = 0.5
52
+ marker.reinforce(amount: 0.2)
53
+ expect(marker.strength).to be_within(0.001).of(0.7)
54
+ end
55
+
56
+ it 'caps at 1.0' do
57
+ marker.reinforce(amount: 0.5)
58
+ expect(marker.strength).to eq(1.0)
59
+ end
60
+ end
61
+
62
+ describe '#decay' do
63
+ it 'reduces strength by MARKER_DECAY' do
64
+ before = marker.strength
65
+ marker.decay
66
+ decay_amount = Legion::Extensions::Agentic::Affect::Interoception::Helpers::Constants::MARKER_DECAY
67
+ expect(marker.strength).to be_within(0.001).of(before - decay_amount)
68
+ end
69
+
70
+ it 'does not drop below MARKER_FLOOR' do
71
+ 50.times { marker.decay }
72
+ expect(marker.strength).to be >= Legion::Extensions::Agentic::Affect::Interoception::Helpers::Constants::MARKER_FLOOR
73
+ end
74
+ end
75
+
76
+ describe '#faded?' do
77
+ it 'returns false for strong markers' do
78
+ expect(marker.faded?).to be false
79
+ end
80
+
81
+ it 'returns true at or below floor' do
82
+ marker.strength = Legion::Extensions::Agentic::Affect::Interoception::Helpers::Constants::MARKER_FLOOR
83
+ expect(marker.faded?).to be true
84
+
85
+ marker.strength = Legion::Extensions::Agentic::Affect::Interoception::Helpers::Constants::MARKER_FLOOR + 0.01
86
+ expect(marker.faded?).to be false
87
+ end
88
+ end
89
+
90
+ describe '#positive? / #negative? / #label' do
91
+ it 'returns :approach for positive valence' do
92
+ pos = described_class.new(action: :a, domain: :d, valence: 0.5)
93
+ expect(pos.positive?).to be true
94
+ expect(pos.negative?).to be false
95
+ expect(pos.label).to eq(:approach)
96
+ end
97
+
98
+ it 'returns :avoid for negative valence' do
99
+ neg = described_class.new(action: :a, domain: :d, valence: -0.5)
100
+ expect(neg.positive?).to be false
101
+ expect(neg.negative?).to be true
102
+ expect(neg.label).to eq(:avoid)
103
+ end
104
+
105
+ it 'returns :neutral for middle valence' do
106
+ mid = described_class.new(action: :a, domain: :d, valence: 0.0)
107
+ expect(mid.positive?).to be false
108
+ expect(mid.negative?).to be false
109
+ expect(mid.label).to eq(:neutral)
110
+ end
111
+ end
112
+
113
+ describe '#to_h' do
114
+ it 'returns a hash with all fields' do
115
+ h = marker.to_h
116
+ expect(h).to include(:id, :action, :domain, :valence, :strength, :label, :created_at)
117
+ expect(h[:action]).to eq(:deploy)
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,108 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Affect::Interoception::Runners::Interoception do
4
+ let(:client) { Legion::Extensions::Agentic::Affect::Interoception::Client.new }
5
+
6
+ describe '#report_vital' do
7
+ it 'reports and smooths a vital signal' do
8
+ result = client.report_vital(channel: :cpu_load, value: 0.6)
9
+ expect(result[:success]).to be true
10
+ expect(result[:channel]).to eq(:cpu_load)
11
+ expect(result[:smoothed]).to be_a(Float)
12
+ expect(result).to have_key(:deviation)
13
+ expect(result).to have_key(:label)
14
+ end
15
+
16
+ it 'tracks deviation from baseline' do
17
+ 5.times { client.report_vital(channel: :cpu_load, value: 0.2) }
18
+ result = client.report_vital(channel: :cpu_load, value: 0.9)
19
+ expect(result[:deviation].abs).to be > 0
20
+ end
21
+ end
22
+
23
+ describe '#create_somatic_marker' do
24
+ it 'creates a marker with correct fields' do
25
+ result = client.create_somatic_marker(action: :deploy, domain: :prod, valence: 0.7)
26
+ expect(result[:success]).to be true
27
+ expect(result[:marker][:action]).to eq(:deploy)
28
+ expect(result[:marker][:valence]).to eq(0.7)
29
+ expect(result[:marker][:label]).to eq(:approach)
30
+ end
31
+
32
+ it 'creates negative marker' do
33
+ result = client.create_somatic_marker(action: :delete_data, domain: :prod, valence: -0.8)
34
+ expect(result[:marker][:label]).to eq(:avoid)
35
+ end
36
+ end
37
+
38
+ describe '#query_bias' do
39
+ it 'returns neutral with no markers' do
40
+ result = client.query_bias(action: :deploy)
41
+ expect(result[:success]).to be true
42
+ expect(result[:bias]).to eq(0.0)
43
+ expect(result[:label]).to eq(:neutral)
44
+ end
45
+
46
+ it 'returns approach bias for positive markers' do
47
+ client.create_somatic_marker(action: :deploy, domain: :prod, valence: 0.8)
48
+ result = client.query_bias(action: :deploy)
49
+ expect(result[:bias]).to be > 0
50
+ end
51
+
52
+ it 'returns avoid bias for negative markers' do
53
+ client.create_somatic_marker(action: :risky_change, domain: :prod, valence: -0.9)
54
+ result = client.query_bias(action: :risky_change)
55
+ expect(result[:bias]).to be < 0
56
+ end
57
+ end
58
+
59
+ describe '#reinforce_somatic' do
60
+ it 'reinforces matching markers' do
61
+ client.create_somatic_marker(action: :deploy, domain: :prod, valence: 0.5, strength: 0.3)
62
+ result = client.reinforce_somatic(action: :deploy, amount: 0.2)
63
+ expect(result[:success]).to be true
64
+ end
65
+ end
66
+
67
+ describe '#deviating_vitals' do
68
+ it 'returns deviating channels' do
69
+ result = client.deviating_vitals
70
+ expect(result[:success]).to be true
71
+ expect(result[:deviations]).to be_an(Array)
72
+ end
73
+ end
74
+
75
+ describe '#body_status' do
76
+ it 'returns overall health status' do
77
+ client.report_vital(channel: :cpu_load, value: 0.2)
78
+ result = client.body_status
79
+ expect(result[:success]).to be true
80
+ expect(result[:health]).to be_a(Float)
81
+ expect(result).to have_key(:label)
82
+ expect(result).to have_key(:channels)
83
+ expect(result).to have_key(:markers)
84
+ end
85
+ end
86
+
87
+ describe '#update_interoception' do
88
+ it 'decays markers and returns status' do
89
+ client.create_somatic_marker(action: :deploy, domain: :prod, valence: 0.5)
90
+ result = client.update_interoception
91
+ expect(result[:success]).to be true
92
+ expect(result).to have_key(:health)
93
+ expect(result).to have_key(:label)
94
+ expect(result).to have_key(:channels)
95
+ expect(result).to have_key(:markers)
96
+ end
97
+ end
98
+
99
+ describe '#interoception_stats' do
100
+ it 'returns comprehensive stats' do
101
+ client.report_vital(channel: :cpu_load, value: 0.4)
102
+ client.create_somatic_marker(action: :deploy, domain: :prod, valence: 0.7)
103
+ result = client.interoception_stats
104
+ expect(result[:success]).to be true
105
+ expect(result[:stats]).to include(:overall_health, :body_budget_label, :channels, :markers)
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Affect::Mood::Client do
4
+ it 'creates default mood state' do
5
+ client = described_class.new
6
+ expect(client.mood_state).to be_a(Legion::Extensions::Agentic::Affect::Mood::Helpers::MoodState)
7
+ end
8
+
9
+ it 'accepts injected mood state' do
10
+ state = Legion::Extensions::Agentic::Affect::Mood::Helpers::MoodState.new
11
+ client = described_class.new(mood_state: state)
12
+ expect(client.mood_state).to equal(state)
13
+ end
14
+
15
+ it 'includes Mood runner methods' do
16
+ client = described_class.new
17
+ expect(client).to respond_to(:update_mood, :current_mood, :mood_modulation,
18
+ :mood_history, :mood_stats)
19
+ end
20
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Affect::Mood::Helpers::Constants do
4
+ it 'defines 9 mood states' do
5
+ expect(described_class::MOOD_STATES.size).to eq(9)
6
+ end
7
+
8
+ it 'defines 4 dimensions' do
9
+ expect(described_class::DIMENSIONS).to contain_exactly(:valence, :arousal, :energy, :stability)
10
+ end
11
+
12
+ it 'defines modulations for every mood state' do
13
+ described_class::MOOD_STATES.each do |mood|
14
+ expect(described_class::MOOD_MODULATIONS).to have_key(mood)
15
+ end
16
+ end
17
+
18
+ it 'defines inertia for every mood state' do
19
+ described_class::MOOD_STATES.each do |mood|
20
+ expect(described_class::MOOD_INERTIA).to have_key(mood)
21
+ end
22
+ end
23
+
24
+ it 'has inertia values between 0 and 1' do
25
+ described_class::MOOD_INERTIA.each_value do |v|
26
+ expect(v).to be_between(0.0, 1.0)
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Affect::Mood::Helpers::MoodState do
4
+ subject(:state) { described_class.new }
5
+
6
+ describe '#initialize' do
7
+ it 'starts neutral' do
8
+ expect(state.current_mood).to eq(:neutral)
9
+ end
10
+
11
+ it 'starts with moderate values' do
12
+ expect(state.valence).to eq(0.5)
13
+ expect(state.arousal).to eq(0.3)
14
+ expect(state.energy).to eq(0.5)
15
+ end
16
+ end
17
+
18
+ describe '#update' do
19
+ it 'does not change mood before update interval' do
20
+ state.update(valence: 0.9, arousal: 0.1)
21
+ expect(state.tick_counter).to eq(1)
22
+ expect(state.history).to be_empty
23
+ end
24
+
25
+ it 'updates mood at update interval' do
26
+ Legion::Extensions::Agentic::Affect::Mood::Helpers::Constants::UPDATE_INTERVAL.times do
27
+ state.update(valence: 0.8, arousal: 0.2, energy: 0.7)
28
+ end
29
+ expect(state.history.size).to eq(1)
30
+ end
31
+
32
+ it 'gradually shifts dimensions via EMA' do
33
+ original_valence = state.valence
34
+ Legion::Extensions::Agentic::Affect::Mood::Helpers::Constants::UPDATE_INTERVAL.times do
35
+ state.update(valence: 0.9, arousal: 0.5, energy: 0.6)
36
+ end
37
+ expect(state.valence).to be > original_valence
38
+ end
39
+
40
+ it 'can reach serene mood with high valence and low arousal' do
41
+ 20.times do
42
+ Legion::Extensions::Agentic::Affect::Mood::Helpers::Constants::UPDATE_INTERVAL.times do
43
+ state.update(valence: 0.9, arousal: 0.1, energy: 0.8)
44
+ end
45
+ end
46
+ expect(state.current_mood).to eq(:serene)
47
+ end
48
+
49
+ it 'can reach anxious mood with low valence and high arousal' do
50
+ 20.times do
51
+ Legion::Extensions::Agentic::Affect::Mood::Helpers::Constants::UPDATE_INTERVAL.times do
52
+ state.update(valence: 0.1, arousal: 0.9, energy: 0.5)
53
+ end
54
+ end
55
+ expect(state.current_mood).to eq(:anxious)
56
+ end
57
+ end
58
+
59
+ describe '#modulations' do
60
+ it 'returns modulation hash for current mood' do
61
+ mods = state.modulations
62
+ expect(mods).to have_key(:attention_threshold)
63
+ expect(mods).to have_key(:risk_tolerance)
64
+ expect(mods).to have_key(:curiosity_boost)
65
+ end
66
+ end
67
+
68
+ describe '#inertia' do
69
+ it 'returns inertia value for current mood' do
70
+ expect(state.inertia).to be_a(Numeric)
71
+ expect(state.inertia).to be_between(0.0, 1.0)
72
+ end
73
+ end
74
+
75
+ describe '#duration_in_current_mood' do
76
+ it 'returns 0 with no history' do
77
+ expect(state.duration_in_current_mood).to eq(0)
78
+ end
79
+ end
80
+
81
+ describe '#mood_trend' do
82
+ it 'returns :insufficient_data with few entries' do
83
+ expect(state.mood_trend).to eq(:insufficient_data)
84
+ end
85
+ end
86
+
87
+ describe '#to_h' do
88
+ it 'returns complete state hash' do
89
+ h = state.to_h
90
+ expect(h).to include(:current_mood, :valence, :arousal, :energy,
91
+ :stability, :modulations, :inertia, :trend)
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Affect::Mood::Runners::Mood do
4
+ let(:client) { Legion::Extensions::Agentic::Affect::Mood::Client.new }
5
+
6
+ let(:tick_results) do
7
+ {
8
+ emotional_evaluation: { magnitude: 0.6, arousal: 0.4 },
9
+ elapsed: 2.0,
10
+ budget: 5.0
11
+ }
12
+ end
13
+
14
+ describe '#update_mood' do
15
+ it 'returns mood state' do
16
+ result = client.update_mood(tick_results: tick_results)
17
+ expect(result).to have_key(:mood)
18
+ expect(result).to have_key(:modulations)
19
+ end
20
+
21
+ it 'handles empty tick results' do
22
+ result = client.update_mood(tick_results: {})
23
+ expect(result[:mood]).to be_a(Symbol)
24
+ end
25
+
26
+ it 'handles valence hash from emotion' do
27
+ results = tick_results.merge(
28
+ emotional_evaluation: {
29
+ valence: { urgency: 0.2, importance: 0.6, novelty: 0.3, familiarity: 0.7 },
30
+ arousal: 0.5
31
+ }
32
+ )
33
+ result = client.update_mood(tick_results: results)
34
+ expect(result[:mood]).to be_a(Symbol)
35
+ end
36
+ end
37
+
38
+ describe '#current_mood' do
39
+ it 'returns full mood state' do
40
+ result = client.current_mood
41
+ expect(result).to include(:current_mood, :valence, :arousal, :energy, :stability)
42
+ end
43
+ end
44
+
45
+ describe '#mood_modulation' do
46
+ it 'returns modulation for a parameter' do
47
+ result = client.mood_modulation(parameter: :curiosity_boost)
48
+ expect(result[:parameter]).to eq(:curiosity_boost)
49
+ expect(result[:modulation]).to be_a(Numeric)
50
+ end
51
+
52
+ it 'returns 0.0 for unknown parameter' do
53
+ result = client.mood_modulation(parameter: :nonexistent)
54
+ expect(result[:modulation]).to eq(0.0)
55
+ end
56
+ end
57
+
58
+ describe '#mood_history' do
59
+ it 'returns empty history initially' do
60
+ result = client.mood_history
61
+ expect(result[:entries]).to be_empty
62
+ end
63
+ end
64
+
65
+ describe '#mood_stats' do
66
+ it 'returns stats summary' do
67
+ result = client.mood_stats
68
+ expect(result).to include(:current_mood, :stability, :trend, :ticks_processed)
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe Legion::Extensions::Agentic::Affect::Motivation::Client do
6
+ describe '#initialize' do
7
+ it 'creates a default motivation_store' do
8
+ client = described_class.new
9
+ expect(client.motivation_store).to be_a(Legion::Extensions::Agentic::Affect::Motivation::Helpers::MotivationStore)
10
+ end
11
+
12
+ it 'accepts an injected motivation_store' do
13
+ store = Legion::Extensions::Agentic::Affect::Motivation::Helpers::MotivationStore.new
14
+ client = described_class.new(motivation_store: store)
15
+ expect(client.motivation_store).to be(store)
16
+ end
17
+
18
+ it 'ignores unknown kwargs' do
19
+ expect { described_class.new(unknown_key: true) }.not_to raise_error
20
+ end
21
+ end
22
+
23
+ describe 'runner integration' do
24
+ let(:client) { described_class.new }
25
+
26
+ it { expect(client).to respond_to(:update_motivation) }
27
+ it { expect(client).to respond_to(:signal_drive) }
28
+ it { expect(client).to respond_to(:commit_to_goal) }
29
+ it { expect(client).to respond_to(:release_goal) }
30
+ it { expect(client).to respond_to(:motivation_for) }
31
+ it { expect(client).to respond_to(:most_motivated_goal) }
32
+ it { expect(client).to respond_to(:drive_status) }
33
+ it { expect(client).to respond_to(:motivation_stats) }
34
+ end
35
+ end