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,146 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Affect::Resonance::Helpers::Category do
4
+ let(:prototype) { [0.8, 0.6, 0.4] }
5
+ subject(:category) { described_class.new(prototype: prototype) }
6
+
7
+ describe '#initialize' do
8
+ it 'assigns a UUID id' do
9
+ expect(category.id).to match(/\A[0-9a-f-]{36}\z/)
10
+ end
11
+
12
+ it 'normalizes prototype values to [0, 1]' do
13
+ cat = described_class.new(prototype: [1.5, -0.2, 0.5])
14
+ expect(cat.prototype).to eq([1.0, 0.0, 0.5])
15
+ end
16
+
17
+ it 'starts with match_count of 0' do
18
+ expect(category.match_count).to eq(0)
19
+ end
20
+
21
+ it 'starts with last_matched_at nil' do
22
+ expect(category.last_matched_at).to be_nil
23
+ end
24
+
25
+ it 'stores prototype as floats' do
26
+ expect(category.prototype).to all(be_a(Float))
27
+ end
28
+ end
29
+
30
+ describe '#match_quality' do
31
+ it 'returns 1.0 for identical input' do
32
+ quality = category.match_quality(input: prototype)
33
+ expect(quality).to be_within(0.001).of(1.0)
34
+ end
35
+
36
+ it 'returns 0.0 for empty prototype' do
37
+ cat = described_class.new(prototype: [])
38
+ expect(cat.match_quality(input: [0.5, 0.5])).to eq(0.0)
39
+ end
40
+
41
+ it 'returns 0.0 for empty input' do
42
+ expect(category.match_quality(input: [])).to eq(0.0)
43
+ end
44
+
45
+ it 'returns a value between 0 and 1 for partial matches' do
46
+ quality = category.match_quality(input: [0.2, 0.1, 0.9])
47
+ expect(quality).to be_between(0.0, 1.0)
48
+ end
49
+
50
+ it 'returns lower quality for orthogonal vectors' do
51
+ cat = described_class.new(prototype: [1.0, 0.0])
52
+ quality = cat.match_quality(input: [0.0, 1.0])
53
+ expect(quality).to be < 0.3
54
+ end
55
+
56
+ it 'handles inputs shorter than prototype' do
57
+ quality = category.match_quality(input: [0.8])
58
+ expect(quality).to be_between(0.0, 1.0)
59
+ end
60
+
61
+ it 'handles inputs longer than prototype' do
62
+ quality = category.match_quality(input: [0.8, 0.6, 0.4, 0.9])
63
+ expect(quality).to be_within(0.001).of(1.0)
64
+ end
65
+
66
+ it 'clamps input values before computing' do
67
+ quality_normal = category.match_quality(input: [0.8, 0.6, 0.4])
68
+ quality_clamped = category.match_quality(input: [2.0, 1.5, 0.4])
69
+ expect(quality_clamped).to be_between(0.0, 1.0)
70
+ expect(quality_normal).to be_within(0.001).of(1.0)
71
+ end
72
+ end
73
+
74
+ describe '#update_prototype!' do
75
+ it 'returns self' do
76
+ result = category.update_prototype!(input: [0.9, 0.7, 0.5])
77
+ expect(result).to be(category)
78
+ end
79
+
80
+ it 'increments match_count' do
81
+ category.update_prototype!(input: [0.9, 0.7, 0.5])
82
+ expect(category.match_count).to eq(1)
83
+ end
84
+
85
+ it 'sets last_matched_at' do
86
+ before = Time.now.utc
87
+ category.update_prototype!(input: [0.9, 0.7, 0.5])
88
+ expect(category.last_matched_at).to be >= before
89
+ end
90
+
91
+ it 'moves prototype toward input with default learning rate' do
92
+ original = category.prototype.dup
93
+ category.update_prototype!(input: [1.0, 1.0, 1.0])
94
+ expect(category.prototype[0]).to be > original[0]
95
+ end
96
+
97
+ it 'uses provided learning rate' do
98
+ category.update_prototype!(input: [1.0, 1.0, 1.0], learning_rate: 1.0)
99
+ expect(category.prototype).to eq([1.0, 1.0, 1.0])
100
+ end
101
+
102
+ it 'clamps learning rate to [0, 1]' do
103
+ expect { category.update_prototype!(input: [0.5, 0.5, 0.5], learning_rate: 2.0) }.not_to raise_error
104
+ expect(category.prototype).to all(be_between(0.0, 1.0))
105
+ end
106
+
107
+ it 'clamps prototype values to [0, 1] after update' do
108
+ cat = described_class.new(prototype: [0.95, 0.95])
109
+ cat.update_prototype!(input: [1.5, 1.5], learning_rate: 1.0)
110
+ expect(cat.prototype).to all(be <= 1.0)
111
+ end
112
+
113
+ it 'expands prototype if input is longer' do
114
+ cat = described_class.new(prototype: [0.5, 0.5])
115
+ cat.update_prototype!(input: [0.5, 0.5, 0.8])
116
+ expect(cat.prototype.size).to eq(3)
117
+ end
118
+ end
119
+
120
+ describe '#to_h' do
121
+ it 'includes id' do
122
+ expect(category.to_h[:id]).to eq(category.id)
123
+ end
124
+
125
+ it 'includes prototype' do
126
+ expect(category.to_h[:prototype]).to eq(category.prototype)
127
+ end
128
+
129
+ it 'includes match_count' do
130
+ expect(category.to_h[:match_count]).to eq(0)
131
+ end
132
+
133
+ it 'includes last_matched_at' do
134
+ expect(category.to_h).to have_key(:last_matched_at)
135
+ end
136
+
137
+ it 'includes dimensions' do
138
+ expect(category.to_h[:dimensions]).to eq(3)
139
+ end
140
+
141
+ it 'reflects updated match_count after update' do
142
+ category.update_prototype!(input: [0.5, 0.5, 0.5])
143
+ expect(category.to_h[:match_count]).to eq(1)
144
+ end
145
+ end
146
+ end
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Affect::Resonance::Helpers::Constants do
4
+ describe 'DEFAULT_VIGILANCE' do
5
+ it 'is 0.7' do
6
+ expect(described_class::DEFAULT_VIGILANCE).to eq(0.7)
7
+ end
8
+ end
9
+
10
+ describe 'MAX_CATEGORIES' do
11
+ it 'is 200' do
12
+ expect(described_class::MAX_CATEGORIES).to eq(200)
13
+ end
14
+ end
15
+
16
+ describe 'RESONANCE_THRESHOLD' do
17
+ it 'is 0.6' do
18
+ expect(described_class::RESONANCE_THRESHOLD).to eq(0.6)
19
+ end
20
+ end
21
+
22
+ describe 'DEFAULT_LEARNING_RATE' do
23
+ it 'is 0.2' do
24
+ expect(described_class::DEFAULT_LEARNING_RATE).to eq(0.2)
25
+ end
26
+ end
27
+
28
+ describe 'MATCH_LABELS' do
29
+ it 'is a frozen array' do
30
+ expect(described_class::MATCH_LABELS).to be_frozen
31
+ end
32
+
33
+ it 'has 5 entries' do
34
+ expect(described_class::MATCH_LABELS.size).to eq(5)
35
+ end
36
+
37
+ it 'includes a :perfect entry for high quality' do
38
+ entry = described_class::MATCH_LABELS.find { |e| e[:label] == :perfect }
39
+ expect(entry[:range]).to cover(0.95)
40
+ end
41
+ end
42
+
43
+ describe 'VIGILANCE_LABELS' do
44
+ it 'is a frozen array' do
45
+ expect(described_class::VIGILANCE_LABELS).to be_frozen
46
+ end
47
+
48
+ it 'has 4 entries' do
49
+ expect(described_class::VIGILANCE_LABELS.size).to eq(4)
50
+ end
51
+ end
52
+
53
+ describe '.match_label' do
54
+ it 'returns :perfect for quality >= 0.9' do
55
+ expect(described_class.match_label(0.95)).to eq(:perfect)
56
+ end
57
+
58
+ it 'returns :strong for quality in 0.75..0.9' do
59
+ expect(described_class.match_label(0.8)).to eq(:strong)
60
+ end
61
+
62
+ it 'returns :moderate for quality in 0.6..0.75' do
63
+ expect(described_class.match_label(0.65)).to eq(:moderate)
64
+ end
65
+
66
+ it 'returns :weak for quality in 0.4..0.6' do
67
+ expect(described_class.match_label(0.5)).to eq(:weak)
68
+ end
69
+
70
+ it 'returns :mismatch for quality below 0.4' do
71
+ expect(described_class.match_label(0.2)).to eq(:mismatch)
72
+ end
73
+
74
+ it 'returns :mismatch for quality = 0.0' do
75
+ expect(described_class.match_label(0.0)).to eq(:mismatch)
76
+ end
77
+
78
+ it 'returns :perfect for quality = 1.0' do
79
+ expect(described_class.match_label(1.0)).to eq(:perfect)
80
+ end
81
+ end
82
+
83
+ describe '.vigilance_label' do
84
+ it 'returns :fine for vigilance >= 0.85' do
85
+ expect(described_class.vigilance_label(0.9)).to eq(:fine)
86
+ end
87
+
88
+ it 'returns :medium for vigilance in 0.65..0.85' do
89
+ expect(described_class.vigilance_label(0.7)).to eq(:medium)
90
+ end
91
+
92
+ it 'returns :coarse for vigilance in 0.4..0.65' do
93
+ expect(described_class.vigilance_label(0.5)).to eq(:coarse)
94
+ end
95
+
96
+ it 'returns :very_coarse for vigilance below 0.4' do
97
+ expect(described_class.vigilance_label(0.2)).to eq(:very_coarse)
98
+ end
99
+
100
+ it 'returns :very_coarse for vigilance = 0.0' do
101
+ expect(described_class.vigilance_label(0.0)).to eq(:very_coarse)
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,189 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Affect::Resonance::Helpers::ResonanceEngine do
4
+ subject(:engine) { described_class.new }
5
+
6
+ describe '#initialize' do
7
+ it 'starts with default vigilance' do
8
+ expect(engine.vigilance).to eq(Legion::Extensions::Agentic::Affect::Resonance::Helpers::Constants::DEFAULT_VIGILANCE)
9
+ end
10
+
11
+ it 'starts with zero categories' do
12
+ expect(engine.category_count).to eq(0)
13
+ end
14
+
15
+ it 'accepts custom vigilance' do
16
+ e = described_class.new(vigilance: 0.9)
17
+ expect(e.vigilance).to eq(0.9)
18
+ end
19
+
20
+ it 'clamps vigilance to [0, 1]' do
21
+ e = described_class.new(vigilance: 1.5)
22
+ expect(e.vigilance).to eq(1.0)
23
+ end
24
+ end
25
+
26
+ describe '#present_input' do
27
+ let(:input) { [0.8, 0.6, 0.4] }
28
+
29
+ it 'creates a new category for the first input' do
30
+ result = engine.present_input(input: input)
31
+ expect(result[:outcome]).to eq(:new_category)
32
+ expect(result[:created]).to be(true)
33
+ expect(result[:category_id]).to be_a(String)
34
+ end
35
+
36
+ it 'increments category count after first input' do
37
+ engine.present_input(input: input)
38
+ expect(engine.category_count).to eq(1)
39
+ end
40
+
41
+ it 'resonates with an identical second input' do
42
+ engine.present_input(input: input)
43
+ result = engine.present_input(input: input)
44
+ expect(result[:outcome]).to eq(:resonance)
45
+ expect(result[:created]).to be(false)
46
+ end
47
+
48
+ it 'creates a new category for dissimilar input at high vigilance' do
49
+ high_engine = described_class.new(vigilance: 0.99)
50
+ high_engine.present_input(input: [1.0, 0.0, 0.0])
51
+ result = high_engine.present_input(input: [0.0, 1.0, 0.0])
52
+ expect(result[:outcome]).to eq(:new_category)
53
+ end
54
+
55
+ it 'resonates with similar input at low vigilance' do
56
+ low_engine = described_class.new(vigilance: 0.1)
57
+ low_engine.present_input(input: [0.8, 0.6, 0.4])
58
+ result = low_engine.present_input(input: [0.7, 0.5, 0.3])
59
+ expect(result[:outcome]).to eq(:resonance)
60
+ end
61
+
62
+ it 'returns a quality score' do
63
+ engine.present_input(input: input)
64
+ result = engine.present_input(input: input)
65
+ expect(result[:quality]).to be_between(0.0, 1.0)
66
+ end
67
+
68
+ it 'returns a match label' do
69
+ engine.present_input(input: input)
70
+ result = engine.present_input(input: input)
71
+ expect(result[:label]).to be_a(Symbol)
72
+ end
73
+
74
+ it 'returns :new for label when creating a category' do
75
+ result = engine.present_input(input: input)
76
+ expect(result[:label]).to eq(:new)
77
+ end
78
+ end
79
+
80
+ describe '#best_match' do
81
+ it 'returns nil when no categories exist' do
82
+ expect(engine.best_match([0.5, 0.5])).to be_nil
83
+ end
84
+
85
+ it 'returns a match hash with id and quality' do
86
+ engine.present_input(input: [0.8, 0.6])
87
+ match = engine.best_match([0.8, 0.6])
88
+ expect(match).to include(:id, :quality)
89
+ end
90
+
91
+ it 'returns best quality match when multiple categories exist' do
92
+ engine.present_input(input: [1.0, 0.0])
93
+ engine.present_input(input: [0.0, 1.0])
94
+ match = engine.best_match([1.0, 0.0])
95
+ expect(match[:quality]).to be_within(0.01).of(1.0)
96
+ end
97
+ end
98
+
99
+ describe '#adjust_vigilance' do
100
+ it 'increases vigilance' do
101
+ original = engine.vigilance
102
+ engine.adjust_vigilance(amount: 0.1)
103
+ expect(engine.vigilance).to be_within(0.001).of(original + 0.1)
104
+ end
105
+
106
+ it 'decreases vigilance' do
107
+ original = engine.vigilance
108
+ engine.adjust_vigilance(amount: -0.1)
109
+ expect(engine.vigilance).to be_within(0.001).of(original - 0.1)
110
+ end
111
+
112
+ it 'clamps vigilance at 1.0' do
113
+ engine.adjust_vigilance(amount: 999.0)
114
+ expect(engine.vigilance).to eq(1.0)
115
+ end
116
+
117
+ it 'clamps vigilance at 0.0' do
118
+ engine.adjust_vigilance(amount: -999.0)
119
+ expect(engine.vigilance).to eq(0.0)
120
+ end
121
+
122
+ it 'returns the new vigilance value' do
123
+ result = engine.adjust_vigilance(amount: 0.05)
124
+ expect(result).to eq(engine.vigilance)
125
+ end
126
+ end
127
+
128
+ describe '#category_count' do
129
+ it 'returns 0 initially' do
130
+ expect(engine.category_count).to eq(0)
131
+ end
132
+
133
+ it 'increments when new categories are created' do
134
+ engine.present_input(input: [1.0, 0.0])
135
+ engine.present_input(input: [0.0, 1.0])
136
+ expect(engine.category_count).to eq(2)
137
+ end
138
+ end
139
+
140
+ describe '#resonance_report' do
141
+ before do
142
+ engine.present_input(input: [0.8, 0.6])
143
+ engine.present_input(input: [0.2, 0.4])
144
+ end
145
+
146
+ it 'includes category_count' do
147
+ expect(engine.resonance_report[:category_count]).to eq(engine.category_count)
148
+ end
149
+
150
+ it 'includes vigilance' do
151
+ expect(engine.resonance_report[:vigilance]).to eq(engine.vigilance)
152
+ end
153
+
154
+ it 'includes vigilance_label' do
155
+ expect(engine.resonance_report[:vigilance_label]).to be_a(Symbol)
156
+ end
157
+
158
+ it 'includes categories array' do
159
+ report = engine.resonance_report
160
+ expect(report[:categories]).to be_an(Array)
161
+ expect(report[:categories].size).to eq(engine.category_count)
162
+ end
163
+
164
+ it 'returns each category as a hash with id and prototype' do
165
+ report = engine.resonance_report
166
+ report[:categories].each do |cat|
167
+ expect(cat).to include(:id, :prototype, :match_count)
168
+ end
169
+ end
170
+ end
171
+
172
+ describe '#to_h' do
173
+ it 'includes vigilance and category_count' do
174
+ expect(engine.to_h).to include(:vigilance, :category_count)
175
+ end
176
+ end
177
+
178
+ describe 'MAX_CATEGORIES pruning' do
179
+ let(:max) { Legion::Extensions::Agentic::Affect::Resonance::Helpers::Constants::MAX_CATEGORIES }
180
+
181
+ it 'does not exceed MAX_CATEGORIES' do
182
+ high_engine = described_class.new(vigilance: 1.0)
183
+ (max + 10).times do |i|
184
+ high_engine.present_input(input: [i.to_f / (max + 10), 0.0])
185
+ end
186
+ expect(high_engine.category_count).to be <= max
187
+ end
188
+ end
189
+ end
@@ -0,0 +1,197 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'legion/extensions/agentic/affect/resonance/client'
4
+
5
+ RSpec.describe Legion::Extensions::Agentic::Affect::Resonance::Runners::CognitiveResonance do
6
+ let(:client) { Legion::Extensions::Agentic::Affect::Resonance::Client.new }
7
+ let(:engine) { Legion::Extensions::Agentic::Affect::Resonance::Helpers::ResonanceEngine.new }
8
+
9
+ describe '#present_input' do
10
+ it 'returns success: true for valid input' do
11
+ result = client.present_input(input: [0.8, 0.6, 0.4])
12
+ expect(result[:success]).to be(true)
13
+ end
14
+
15
+ it 'returns the outcome' do
16
+ result = client.present_input(input: [0.8, 0.6, 0.4])
17
+ expect(result[:outcome]).to be_a(Symbol)
18
+ end
19
+
20
+ it 'returns a category_id' do
21
+ result = client.present_input(input: [0.8, 0.6, 0.4])
22
+ expect(result[:category_id]).to be_a(String)
23
+ end
24
+
25
+ it 'returns a quality score' do
26
+ result = client.present_input(input: [0.8, 0.6, 0.4])
27
+ expect(result[:quality]).to be_between(0.0, 1.0)
28
+ end
29
+
30
+ it 'returns success: false for empty input' do
31
+ result = client.present_input(input: [])
32
+ expect(result[:success]).to be(false)
33
+ expect(result[:error]).to eq(:empty_input)
34
+ end
35
+
36
+ it 'returns success: false for nil input' do
37
+ result = client.present_input(input: nil)
38
+ expect(result[:success]).to be(false)
39
+ end
40
+
41
+ it 'uses injected engine when provided' do
42
+ result = client.present_input(input: [0.5, 0.5], engine: engine)
43
+ expect(result[:success]).to be(true)
44
+ expect(engine.category_count).to be >= 1
45
+ end
46
+
47
+ it 'resonates on second identical input' do
48
+ client.present_input(input: [0.8, 0.6, 0.4])
49
+ result = client.present_input(input: [0.8, 0.6, 0.4])
50
+ expect(result[:outcome]).to eq(:resonance)
51
+ end
52
+
53
+ it 'creates new category on first input' do
54
+ result = client.present_input(input: [0.8, 0.6, 0.4])
55
+ expect(result[:outcome]).to eq(:new_category)
56
+ end
57
+ end
58
+
59
+ describe '#classify' do
60
+ it 'returns success: false for empty input' do
61
+ result = client.classify(input: [])
62
+ expect(result[:success]).to be(false)
63
+ end
64
+
65
+ it 'returns found: false when no categories exist' do
66
+ result = client.classify(input: [0.5, 0.5], engine: engine)
67
+ expect(result[:found]).to be(false)
68
+ end
69
+
70
+ it 'returns found: true when a category exists' do
71
+ client.present_input(input: [0.8, 0.6], engine: engine)
72
+ result = client.classify(input: [0.8, 0.6], engine: engine)
73
+ expect(result[:found]).to be(true)
74
+ end
75
+
76
+ it 'returns category_id and quality when found' do
77
+ client.present_input(input: [0.8, 0.6], engine: engine)
78
+ result = client.classify(input: [0.8, 0.6], engine: engine)
79
+ expect(result[:category_id]).to be_a(String)
80
+ expect(result[:quality]).to be_between(0.0, 1.0)
81
+ end
82
+
83
+ it 'returns a label' do
84
+ client.present_input(input: [0.8, 0.6], engine: engine)
85
+ result = client.classify(input: [0.8, 0.6], engine: engine)
86
+ expect(result[:label]).to be_a(Symbol)
87
+ end
88
+
89
+ it 'returns :none label when not found' do
90
+ result = client.classify(input: [0.5, 0.5], engine: engine)
91
+ expect(result[:label]).to eq(:none)
92
+ end
93
+
94
+ it 'returns quality: 0.0 and category_id: nil when not found' do
95
+ result = client.classify(input: [0.5, 0.5], engine: engine)
96
+ expect(result[:quality]).to eq(0.0)
97
+ expect(result[:category_id]).to be_nil
98
+ end
99
+ end
100
+
101
+ describe '#adjust_vigilance' do
102
+ it 'returns success: true' do
103
+ result = client.adjust_vigilance(amount: 0.1)
104
+ expect(result[:success]).to be(true)
105
+ end
106
+
107
+ it 'returns the new vigilance value' do
108
+ result = client.adjust_vigilance(amount: 0.1, engine: engine)
109
+ expect(result[:vigilance]).to be_between(0.0, 1.0)
110
+ end
111
+
112
+ it 'returns the vigilance_label' do
113
+ result = client.adjust_vigilance(amount: 0.0, engine: engine)
114
+ expect(result[:vigilance_label]).to be_a(Symbol)
115
+ end
116
+
117
+ it 'returns the clamped adjustment amount' do
118
+ result = client.adjust_vigilance(amount: 0.05, engine: engine)
119
+ expect(result[:adjustment]).to eq(0.05)
120
+ end
121
+
122
+ it 'clamps amount to [-1.0, 1.0]' do
123
+ result = client.adjust_vigilance(amount: 999.0, engine: engine)
124
+ expect(result[:adjustment]).to eq(1.0)
125
+ end
126
+
127
+ it 'increases vigilance' do
128
+ original = engine.vigilance
129
+ client.adjust_vigilance(amount: 0.1, engine: engine)
130
+ expect(engine.vigilance).to be > original
131
+ end
132
+
133
+ it 'decreases vigilance' do
134
+ original = engine.vigilance
135
+ client.adjust_vigilance(amount: -0.1, engine: engine)
136
+ expect(engine.vigilance).to be < original
137
+ end
138
+ end
139
+
140
+ describe '#resonance_report' do
141
+ before { client.present_input(input: [0.8, 0.6, 0.4]) }
142
+
143
+ it 'returns success: true' do
144
+ expect(client.resonance_report[:success]).to be(true)
145
+ end
146
+
147
+ it 'includes category_count' do
148
+ expect(client.resonance_report[:category_count]).to be >= 1
149
+ end
150
+
151
+ it 'includes vigilance' do
152
+ expect(client.resonance_report[:vigilance]).to be_between(0.0, 1.0)
153
+ end
154
+
155
+ it 'includes vigilance_label' do
156
+ expect(client.resonance_report[:vigilance_label]).to be_a(Symbol)
157
+ end
158
+
159
+ it 'includes categories array' do
160
+ expect(client.resonance_report[:categories]).to be_an(Array)
161
+ end
162
+
163
+ it 'uses injected engine' do
164
+ engine2 = Legion::Extensions::Agentic::Affect::Resonance::Helpers::ResonanceEngine.new
165
+ report = client.resonance_report(engine: engine2)
166
+ expect(report[:category_count]).to eq(0)
167
+ end
168
+ end
169
+
170
+ describe '#category_count' do
171
+ it 'returns success: true' do
172
+ expect(client.category_count[:success]).to be(true)
173
+ end
174
+
175
+ it 'returns 0 for fresh engine' do
176
+ expect(client.category_count(engine: engine)[:count]).to eq(0)
177
+ end
178
+
179
+ it 'returns the correct count after adding categories' do
180
+ client.present_input(input: [0.8, 0.6], engine: engine)
181
+ client.present_input(input: [0.1, 0.9], engine: engine)
182
+ expect(client.category_count(engine: engine)[:count]).to eq(2)
183
+ end
184
+ end
185
+
186
+ describe '#reset_engine' do
187
+ it 'returns success: true' do
188
+ expect(client.reset_engine[:success]).to be(true)
189
+ end
190
+
191
+ it 'resets the default engine' do
192
+ client.present_input(input: [0.8, 0.6, 0.4])
193
+ client.reset_engine
194
+ expect(client.category_count[:count]).to eq(0)
195
+ end
196
+ end
197
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe Legion::Extensions::Agentic::Affect::Reward::Client do
6
+ describe '#initialize' do
7
+ it 'creates a default reward store' do
8
+ client = described_class.new
9
+ expect(client.reward_store).to be_a(Legion::Extensions::Agentic::Affect::Reward::Helpers::RewardStore)
10
+ end
11
+
12
+ it 'accepts an injected reward store' do
13
+ store = Legion::Extensions::Agentic::Affect::Reward::Helpers::RewardStore.new
14
+ client = described_class.new(reward_store: store)
15
+ expect(client.reward_store).to be(store)
16
+ end
17
+
18
+ it 'ignores unknown kwargs' do
19
+ expect { described_class.new(unknown: 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(:compute_reward) }
27
+ it { expect(client).to respond_to(:reward_status) }
28
+ it { expect(client).to respond_to(:reward_for) }
29
+ it { expect(client).to respond_to(:reward_history) }
30
+ it { expect(client).to respond_to(:domain_rewards) }
31
+ it { expect(client).to respond_to(:reward_stats) }
32
+ end
33
+
34
+ describe 'shared state' do
35
+ it 'accumulates across multiple compute calls' do
36
+ client = described_class.new
37
+ tick = { prediction_engine: { rolling_accuracy: 0.7, error_rate: 0.2 } }
38
+ 15.times { client.compute_reward(tick_results: tick) }
39
+ expect(client.reward_history[:total]).to eq(15)
40
+ end
41
+ end
42
+ end