lex-agentic-self 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 (249) 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-self.gemspec +31 -0
  7. data/lib/legion/extensions/agentic/self/agency/client.rb +21 -0
  8. data/lib/legion/extensions/agentic/self/agency/helpers/constants.rb +77 -0
  9. data/lib/legion/extensions/agentic/self/agency/helpers/efficacy_model.rb +136 -0
  10. data/lib/legion/extensions/agentic/self/agency/helpers/outcome_event.rb +52 -0
  11. data/lib/legion/extensions/agentic/self/agency/runners/agency.rb +117 -0
  12. data/lib/legion/extensions/agentic/self/agency/version.rb +13 -0
  13. data/lib/legion/extensions/agentic/self/agency.rb +19 -0
  14. data/lib/legion/extensions/agentic/self/anchor/client.rb +15 -0
  15. data/lib/legion/extensions/agentic/self/anchor/helpers/anchor.rb +92 -0
  16. data/lib/legion/extensions/agentic/self/anchor/helpers/anchor_engine.rb +123 -0
  17. data/lib/legion/extensions/agentic/self/anchor/helpers/chain.rb +93 -0
  18. data/lib/legion/extensions/agentic/self/anchor/helpers/constants.rb +46 -0
  19. data/lib/legion/extensions/agentic/self/anchor/runners/cognitive_anchor.rb +70 -0
  20. data/lib/legion/extensions/agentic/self/anchor/version.rb +13 -0
  21. data/lib/legion/extensions/agentic/self/anchor.rb +22 -0
  22. data/lib/legion/extensions/agentic/self/anosognosia/client.rb +28 -0
  23. data/lib/legion/extensions/agentic/self/anosognosia/helpers/anosognosia_engine.rb +153 -0
  24. data/lib/legion/extensions/agentic/self/anosognosia/helpers/cognitive_deficit.rb +71 -0
  25. data/lib/legion/extensions/agentic/self/anosognosia/helpers/constants.rb +29 -0
  26. data/lib/legion/extensions/agentic/self/anosognosia/runners/anosognosia.rb +98 -0
  27. data/lib/legion/extensions/agentic/self/anosognosia/version.rb +13 -0
  28. data/lib/legion/extensions/agentic/self/anosognosia.rb +19 -0
  29. data/lib/legion/extensions/agentic/self/architecture/client.rb +19 -0
  30. data/lib/legion/extensions/agentic/self/architecture/helpers/architecture_engine.rb +167 -0
  31. data/lib/legion/extensions/agentic/self/architecture/helpers/connection.rb +57 -0
  32. data/lib/legion/extensions/agentic/self/architecture/helpers/constants.rb +37 -0
  33. data/lib/legion/extensions/agentic/self/architecture/helpers/subsystem.rb +80 -0
  34. data/lib/legion/extensions/agentic/self/architecture/runners/cognitive_architecture.rb +125 -0
  35. data/lib/legion/extensions/agentic/self/architecture/version.rb +13 -0
  36. data/lib/legion/extensions/agentic/self/architecture.rb +20 -0
  37. data/lib/legion/extensions/agentic/self/default_mode_network/actors/idle.rb +45 -0
  38. data/lib/legion/extensions/agentic/self/default_mode_network/client.rb +28 -0
  39. data/lib/legion/extensions/agentic/self/default_mode_network/helpers/constants.rb +53 -0
  40. data/lib/legion/extensions/agentic/self/default_mode_network/helpers/dmn_engine.rb +221 -0
  41. data/lib/legion/extensions/agentic/self/default_mode_network/helpers/wandering_thought.rb +60 -0
  42. data/lib/legion/extensions/agentic/self/default_mode_network/runners/default_mode_network.rb +122 -0
  43. data/lib/legion/extensions/agentic/self/default_mode_network/version.rb +13 -0
  44. data/lib/legion/extensions/agentic/self/default_mode_network.rb +20 -0
  45. data/lib/legion/extensions/agentic/self/fingerprint/client.rb +28 -0
  46. data/lib/legion/extensions/agentic/self/fingerprint/helpers/cognitive_trait.rb +73 -0
  47. data/lib/legion/extensions/agentic/self/fingerprint/helpers/constants.rb +60 -0
  48. data/lib/legion/extensions/agentic/self/fingerprint/helpers/fingerprint_engine.rb +169 -0
  49. data/lib/legion/extensions/agentic/self/fingerprint/runners/cognitive_fingerprint.rb +86 -0
  50. data/lib/legion/extensions/agentic/self/fingerprint/version.rb +13 -0
  51. data/lib/legion/extensions/agentic/self/fingerprint.rb +19 -0
  52. data/lib/legion/extensions/agentic/self/identity/actors/credential_refresh.rb +49 -0
  53. data/lib/legion/extensions/agentic/self/identity/actors/orphan_check.rb +52 -0
  54. data/lib/legion/extensions/agentic/self/identity/client.rb +27 -0
  55. data/lib/legion/extensions/agentic/self/identity/helpers/dimensions.rb +75 -0
  56. data/lib/legion/extensions/agentic/self/identity/helpers/fingerprint.rb +170 -0
  57. data/lib/legion/extensions/agentic/self/identity/helpers/graph_client.rb +29 -0
  58. data/lib/legion/extensions/agentic/self/identity/helpers/graph_token.rb +36 -0
  59. data/lib/legion/extensions/agentic/self/identity/helpers/token_cache.rb +59 -0
  60. data/lib/legion/extensions/agentic/self/identity/helpers/vault_secrets.rb +80 -0
  61. data/lib/legion/extensions/agentic/self/identity/local_migrations/20260316000030_create_fingerprint.rb +20 -0
  62. data/lib/legion/extensions/agentic/self/identity/runners/entra.rb +402 -0
  63. data/lib/legion/extensions/agentic/self/identity/runners/identity.rb +90 -0
  64. data/lib/legion/extensions/agentic/self/identity/version.rb +13 -0
  65. data/lib/legion/extensions/agentic/self/identity.rb +28 -0
  66. data/lib/legion/extensions/agentic/self/metacognition/client.rb +27 -0
  67. data/lib/legion/extensions/agentic/self/metacognition/helpers/constants.rb +377 -0
  68. data/lib/legion/extensions/agentic/self/metacognition/helpers/narrator_bridge.rb +85 -0
  69. data/lib/legion/extensions/agentic/self/metacognition/helpers/registry_store.rb +70 -0
  70. data/lib/legion/extensions/agentic/self/metacognition/helpers/self_model.rb +160 -0
  71. data/lib/legion/extensions/agentic/self/metacognition/helpers/snapshot_store.rb +82 -0
  72. data/lib/legion/extensions/agentic/self/metacognition/runners/metacognition.rb +116 -0
  73. data/lib/legion/extensions/agentic/self/metacognition/runners/registry.rb +180 -0
  74. data/lib/legion/extensions/agentic/self/metacognition/version.rb +13 -0
  75. data/lib/legion/extensions/agentic/self/metacognition.rb +22 -0
  76. data/lib/legion/extensions/agentic/self/metacognitive_monitoring/client.rb +25 -0
  77. data/lib/legion/extensions/agentic/self/metacognitive_monitoring/helpers/calibration_tracker.rb +96 -0
  78. data/lib/legion/extensions/agentic/self/metacognitive_monitoring/helpers/constants.rb +47 -0
  79. data/lib/legion/extensions/agentic/self/metacognitive_monitoring/helpers/monitoring_engine.rb +141 -0
  80. data/lib/legion/extensions/agentic/self/metacognitive_monitoring/helpers/monitoring_judgment.rb +79 -0
  81. data/lib/legion/extensions/agentic/self/metacognitive_monitoring/runners/metacognitive_monitoring.rb +151 -0
  82. data/lib/legion/extensions/agentic/self/metacognitive_monitoring/version.rb +13 -0
  83. data/lib/legion/extensions/agentic/self/metacognitive_monitoring.rb +20 -0
  84. data/lib/legion/extensions/agentic/self/narrative_arc/client.rb +29 -0
  85. data/lib/legion/extensions/agentic/self/narrative_arc/helpers/arc.rb +137 -0
  86. data/lib/legion/extensions/agentic/self/narrative_arc/helpers/arc_engine.rb +119 -0
  87. data/lib/legion/extensions/agentic/self/narrative_arc/helpers/beat_event.rb +59 -0
  88. data/lib/legion/extensions/agentic/self/narrative_arc/helpers/constants.rb +66 -0
  89. data/lib/legion/extensions/agentic/self/narrative_arc/runners/narrative.rb +101 -0
  90. data/lib/legion/extensions/agentic/self/narrative_arc/version.rb +13 -0
  91. data/lib/legion/extensions/agentic/self/narrative_arc.rb +20 -0
  92. data/lib/legion/extensions/agentic/self/narrative_identity/actors/narrative_decay.rb +45 -0
  93. data/lib/legion/extensions/agentic/self/narrative_identity/client.rb +22 -0
  94. data/lib/legion/extensions/agentic/self/narrative_identity/helpers/chapter.rb +48 -0
  95. data/lib/legion/extensions/agentic/self/narrative_identity/helpers/constants.rb +62 -0
  96. data/lib/legion/extensions/agentic/self/narrative_identity/helpers/episode.rb +67 -0
  97. data/lib/legion/extensions/agentic/self/narrative_identity/helpers/narrative_engine.rb +187 -0
  98. data/lib/legion/extensions/agentic/self/narrative_identity/helpers/theme.rb +50 -0
  99. data/lib/legion/extensions/agentic/self/narrative_identity/runners/narrative_identity.rb +158 -0
  100. data/lib/legion/extensions/agentic/self/narrative_identity/version.rb +13 -0
  101. data/lib/legion/extensions/agentic/self/narrative_identity.rb +21 -0
  102. data/lib/legion/extensions/agentic/self/narrative_self/client.rb +27 -0
  103. data/lib/legion/extensions/agentic/self/narrative_self/helpers/autobiography.rb +187 -0
  104. data/lib/legion/extensions/agentic/self/narrative_self/helpers/constants.rb +42 -0
  105. data/lib/legion/extensions/agentic/self/narrative_self/helpers/episode.rb +81 -0
  106. data/lib/legion/extensions/agentic/self/narrative_self/helpers/narrative_thread.rb +65 -0
  107. data/lib/legion/extensions/agentic/self/narrative_self/runners/narrative_self.rb +86 -0
  108. data/lib/legion/extensions/agentic/self/narrative_self/version.rb +13 -0
  109. data/lib/legion/extensions/agentic/self/narrative_self.rb +20 -0
  110. data/lib/legion/extensions/agentic/self/personality/client.rb +21 -0
  111. data/lib/legion/extensions/agentic/self/personality/helpers/constants.rb +84 -0
  112. data/lib/legion/extensions/agentic/self/personality/helpers/personality_store.rb +126 -0
  113. data/lib/legion/extensions/agentic/self/personality/helpers/trait_model.rb +147 -0
  114. data/lib/legion/extensions/agentic/self/personality/runners/personality.rb +102 -0
  115. data/lib/legion/extensions/agentic/self/personality/version.rb +13 -0
  116. data/lib/legion/extensions/agentic/self/personality.rb +19 -0
  117. data/lib/legion/extensions/agentic/self/reflection/client.rb +27 -0
  118. data/lib/legion/extensions/agentic/self/reflection/helpers/constants.rb +66 -0
  119. data/lib/legion/extensions/agentic/self/reflection/helpers/llm_enhancer.rb +166 -0
  120. data/lib/legion/extensions/agentic/self/reflection/helpers/monitors.rb +186 -0
  121. data/lib/legion/extensions/agentic/self/reflection/helpers/reflection.rb +54 -0
  122. data/lib/legion/extensions/agentic/self/reflection/helpers/reflection_store.rb +99 -0
  123. data/lib/legion/extensions/agentic/self/reflection/runners/reflection.rb +199 -0
  124. data/lib/legion/extensions/agentic/self/reflection/version.rb +13 -0
  125. data/lib/legion/extensions/agentic/self/reflection.rb +21 -0
  126. data/lib/legion/extensions/agentic/self/self_model/client.rb +19 -0
  127. data/lib/legion/extensions/agentic/self/self_model/helpers/capability.rb +93 -0
  128. data/lib/legion/extensions/agentic/self/self_model/helpers/constants.rb +46 -0
  129. data/lib/legion/extensions/agentic/self/self_model/helpers/knowledge_domain.rb +82 -0
  130. data/lib/legion/extensions/agentic/self/self_model/helpers/self_model.rb +150 -0
  131. data/lib/legion/extensions/agentic/self/self_model/runners/self_model.rb +82 -0
  132. data/lib/legion/extensions/agentic/self/self_model/version.rb +13 -0
  133. data/lib/legion/extensions/agentic/self/self_model.rb +21 -0
  134. data/lib/legion/extensions/agentic/self/self_talk/actors/volume_decay.rb +45 -0
  135. data/lib/legion/extensions/agentic/self/self_talk/client.rb +30 -0
  136. data/lib/legion/extensions/agentic/self/self_talk/helpers/constants.rb +63 -0
  137. data/lib/legion/extensions/agentic/self/self_talk/helpers/dialogue.rb +114 -0
  138. data/lib/legion/extensions/agentic/self/self_talk/helpers/dialogue_turn.rb +43 -0
  139. data/lib/legion/extensions/agentic/self/self_talk/helpers/inner_voice.rb +77 -0
  140. data/lib/legion/extensions/agentic/self/self_talk/helpers/llm_enhancer.rb +135 -0
  141. data/lib/legion/extensions/agentic/self/self_talk/helpers/self_talk_engine.rb +160 -0
  142. data/lib/legion/extensions/agentic/self/self_talk/runners/self_talk.rb +172 -0
  143. data/lib/legion/extensions/agentic/self/self_talk/version.rb +13 -0
  144. data/lib/legion/extensions/agentic/self/self_talk.rb +22 -0
  145. data/lib/legion/extensions/agentic/self/version.rb +11 -0
  146. data/lib/legion/extensions/agentic/self.rb +33 -0
  147. data/spec/legion/extensions/agentic/self/agency/client_spec.rb +67 -0
  148. data/spec/legion/extensions/agentic/self/agency/helpers/constants_spec.rb +73 -0
  149. data/spec/legion/extensions/agentic/self/agency/helpers/efficacy_model_spec.rb +190 -0
  150. data/spec/legion/extensions/agentic/self/agency/helpers/outcome_event_spec.rb +85 -0
  151. data/spec/legion/extensions/agentic/self/agency/runners/agency_spec.rb +132 -0
  152. data/spec/legion/extensions/agentic/self/anchor/client_spec.rb +30 -0
  153. data/spec/legion/extensions/agentic/self/anchor/helpers/anchor_engine_spec.rb +109 -0
  154. data/spec/legion/extensions/agentic/self/anchor/helpers/anchor_spec.rb +124 -0
  155. data/spec/legion/extensions/agentic/self/anchor/helpers/chain_spec.rb +106 -0
  156. data/spec/legion/extensions/agentic/self/anchor/helpers/constants_spec.rb +53 -0
  157. data/spec/legion/extensions/agentic/self/anchor/runners/cognitive_anchor_spec.rb +70 -0
  158. data/spec/legion/extensions/agentic/self/anosognosia/anosognosia_spec.rb +15 -0
  159. data/spec/legion/extensions/agentic/self/anosognosia/client_spec.rb +50 -0
  160. data/spec/legion/extensions/agentic/self/anosognosia/helpers/anosognosia_engine_spec.rb +266 -0
  161. data/spec/legion/extensions/agentic/self/anosognosia/helpers/cognitive_deficit_spec.rb +150 -0
  162. data/spec/legion/extensions/agentic/self/anosognosia/helpers/constants_spec.rb +58 -0
  163. data/spec/legion/extensions/agentic/self/anosognosia/runners/anosognosia_spec.rb +225 -0
  164. data/spec/legion/extensions/agentic/self/architecture/client_spec.rb +51 -0
  165. data/spec/legion/extensions/agentic/self/architecture/helpers/architecture_engine_spec.rb +321 -0
  166. data/spec/legion/extensions/agentic/self/architecture/helpers/connection_spec.rb +118 -0
  167. data/spec/legion/extensions/agentic/self/architecture/helpers/subsystem_spec.rb +189 -0
  168. data/spec/legion/extensions/agentic/self/architecture/runners/cognitive_architecture_spec.rb +181 -0
  169. data/spec/legion/extensions/agentic/self/default_mode_network/client_spec.rb +69 -0
  170. data/spec/legion/extensions/agentic/self/default_mode_network/helpers/constants_spec.rb +76 -0
  171. data/spec/legion/extensions/agentic/self/default_mode_network/helpers/dmn_engine_spec.rb +321 -0
  172. data/spec/legion/extensions/agentic/self/default_mode_network/helpers/wandering_thought_spec.rb +145 -0
  173. data/spec/legion/extensions/agentic/self/default_mode_network/runners/default_mode_network_spec.rb +269 -0
  174. data/spec/legion/extensions/agentic/self/fingerprint/client_spec.rb +54 -0
  175. data/spec/legion/extensions/agentic/self/fingerprint/helpers/cognitive_trait_spec.rb +180 -0
  176. data/spec/legion/extensions/agentic/self/fingerprint/helpers/constants_spec.rb +108 -0
  177. data/spec/legion/extensions/agentic/self/fingerprint/helpers/fingerprint_engine_spec.rb +318 -0
  178. data/spec/legion/extensions/agentic/self/fingerprint/runners/cognitive_fingerprint_spec.rb +232 -0
  179. data/spec/legion/extensions/agentic/self/identity/actors/orphan_check_spec.rb +104 -0
  180. data/spec/legion/extensions/agentic/self/identity/client_spec.rb +32 -0
  181. data/spec/legion/extensions/agentic/self/identity/helpers/dimensions_spec.rb +51 -0
  182. data/spec/legion/extensions/agentic/self/identity/helpers/fingerprint_spec.rb +66 -0
  183. data/spec/legion/extensions/agentic/self/identity/helpers/graph_client_spec.rb +19 -0
  184. data/spec/legion/extensions/agentic/self/identity/helpers/graph_token_spec.rb +31 -0
  185. data/spec/legion/extensions/agentic/self/identity/helpers/token_cache_spec.rb +50 -0
  186. data/spec/legion/extensions/agentic/self/identity/local_persistence_spec.rb +329 -0
  187. data/spec/legion/extensions/agentic/self/identity/runners/entra_spec.rb +655 -0
  188. data/spec/legion/extensions/agentic/self/identity/runners/identity_spec.rb +61 -0
  189. data/spec/legion/extensions/agentic/self/metacognition/client_spec.rb +20 -0
  190. data/spec/legion/extensions/agentic/self/metacognition/helpers/constants_spec.rb +31 -0
  191. data/spec/legion/extensions/agentic/self/metacognition/helpers/narrator_bridge_spec.rb +102 -0
  192. data/spec/legion/extensions/agentic/self/metacognition/helpers/registry_store_spec.rb +227 -0
  193. data/spec/legion/extensions/agentic/self/metacognition/helpers/self_model_spec.rb +117 -0
  194. data/spec/legion/extensions/agentic/self/metacognition/helpers/snapshot_store_spec.rb +128 -0
  195. data/spec/legion/extensions/agentic/self/metacognition/runners/metacognition_spec.rb +110 -0
  196. data/spec/legion/extensions/agentic/self/metacognition/runners/registry_spec.rb +281 -0
  197. data/spec/legion/extensions/agentic/self/metacognitive_monitoring/client_spec.rb +59 -0
  198. data/spec/legion/extensions/agentic/self/metacognitive_monitoring/helpers/calibration_tracker_spec.rb +143 -0
  199. data/spec/legion/extensions/agentic/self/metacognitive_monitoring/helpers/constants_spec.rb +91 -0
  200. data/spec/legion/extensions/agentic/self/metacognitive_monitoring/helpers/monitoring_engine_spec.rb +198 -0
  201. data/spec/legion/extensions/agentic/self/metacognitive_monitoring/helpers/monitoring_judgment_spec.rb +172 -0
  202. data/spec/legion/extensions/agentic/self/metacognitive_monitoring/runners/metacognitive_monitoring_spec.rb +244 -0
  203. data/spec/legion/extensions/agentic/self/narrative_arc/client_spec.rb +22 -0
  204. data/spec/legion/extensions/agentic/self/narrative_arc/helpers/arc_engine_spec.rb +183 -0
  205. data/spec/legion/extensions/agentic/self/narrative_arc/helpers/arc_spec.rb +177 -0
  206. data/spec/legion/extensions/agentic/self/narrative_arc/helpers/beat_event_spec.rb +96 -0
  207. data/spec/legion/extensions/agentic/self/narrative_arc/helpers/constants_spec.rb +75 -0
  208. data/spec/legion/extensions/agentic/self/narrative_arc/runners/narrative_spec.rb +142 -0
  209. data/spec/legion/extensions/agentic/self/narrative_identity/client_spec.rb +69 -0
  210. data/spec/legion/extensions/agentic/self/narrative_identity/helpers/chapter_spec.rb +85 -0
  211. data/spec/legion/extensions/agentic/self/narrative_identity/helpers/constants_spec.rb +83 -0
  212. data/spec/legion/extensions/agentic/self/narrative_identity/helpers/episode_spec.rb +180 -0
  213. data/spec/legion/extensions/agentic/self/narrative_identity/helpers/narrative_engine_spec.rb +307 -0
  214. data/spec/legion/extensions/agentic/self/narrative_identity/helpers/theme_spec.rb +107 -0
  215. data/spec/legion/extensions/agentic/self/narrative_identity/runners/narrative_identity_spec.rb +240 -0
  216. data/spec/legion/extensions/agentic/self/narrative_self/client_spec.rb +67 -0
  217. data/spec/legion/extensions/agentic/self/narrative_self/helpers/autobiography_spec.rb +155 -0
  218. data/spec/legion/extensions/agentic/self/narrative_self/helpers/constants_spec.rb +28 -0
  219. data/spec/legion/extensions/agentic/self/narrative_self/helpers/episode_spec.rb +144 -0
  220. data/spec/legion/extensions/agentic/self/narrative_self/helpers/narrative_thread_spec.rb +87 -0
  221. data/spec/legion/extensions/agentic/self/narrative_self/runners/narrative_self_spec.rb +118 -0
  222. data/spec/legion/extensions/agentic/self/personality/client_spec.rb +20 -0
  223. data/spec/legion/extensions/agentic/self/personality/helpers/constants_spec.rb +41 -0
  224. data/spec/legion/extensions/agentic/self/personality/helpers/personality_store_spec.rb +66 -0
  225. data/spec/legion/extensions/agentic/self/personality/helpers/trait_model_spec.rb +148 -0
  226. data/spec/legion/extensions/agentic/self/personality/runners/personality_spec.rb +67 -0
  227. data/spec/legion/extensions/agentic/self/reflection/client_spec.rb +24 -0
  228. data/spec/legion/extensions/agentic/self/reflection/helpers/llm_enhancer_spec.rb +191 -0
  229. data/spec/legion/extensions/agentic/self/reflection/helpers/monitors_spec.rb +120 -0
  230. data/spec/legion/extensions/agentic/self/reflection/helpers/reflection_spec.rb +49 -0
  231. data/spec/legion/extensions/agentic/self/reflection/helpers/reflection_store_spec.rb +93 -0
  232. data/spec/legion/extensions/agentic/self/reflection/runners/reflection_spec.rb +204 -0
  233. data/spec/legion/extensions/agentic/self/self_model/client_spec.rb +55 -0
  234. data/spec/legion/extensions/agentic/self/self_model/helpers/capability_spec.rb +160 -0
  235. data/spec/legion/extensions/agentic/self/self_model/helpers/knowledge_domain_spec.rb +128 -0
  236. data/spec/legion/extensions/agentic/self/self_model/helpers/self_model_spec.rb +238 -0
  237. data/spec/legion/extensions/agentic/self/self_model/runners/self_model_spec.rb +143 -0
  238. data/spec/legion/extensions/agentic/self/self_talk/actors/volume_decay_spec.rb +46 -0
  239. data/spec/legion/extensions/agentic/self/self_talk/client_spec.rb +26 -0
  240. data/spec/legion/extensions/agentic/self/self_talk/helpers/constants_spec.rb +110 -0
  241. data/spec/legion/extensions/agentic/self/self_talk/helpers/dialogue_spec.rb +191 -0
  242. data/spec/legion/extensions/agentic/self/self_talk/helpers/dialogue_turn_spec.rb +78 -0
  243. data/spec/legion/extensions/agentic/self/self_talk/helpers/inner_voice_spec.rb +172 -0
  244. data/spec/legion/extensions/agentic/self/self_talk/helpers/llm_enhancer_spec.rb +206 -0
  245. data/spec/legion/extensions/agentic/self/self_talk/helpers/self_talk_engine_spec.rb +239 -0
  246. data/spec/legion/extensions/agentic/self/self_talk/runners/self_talk_llm_spec.rb +169 -0
  247. data/spec/legion/extensions/agentic/self/self_talk/runners/self_talk_spec.rb +196 -0
  248. data/spec/spec_helper.rb +46 -0
  249. metadata +347 -0
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Self::Personality::Helpers::PersonalityStore do
4
+ subject(:store) { described_class.new }
5
+
6
+ describe '#update' do
7
+ it 'delegates to the trait model' do
8
+ store.update(curiosity: { intensity: 0.8 })
9
+ expect(store.model.observation_count).to be >= 0
10
+ end
11
+
12
+ it 'extracts signals from tick results' do
13
+ store.update(
14
+ working_memory_integration: { intensity: 0.8, wonder_count: 5 },
15
+ prediction_engine: { accuracy: 0.9 },
16
+ mesh_interface: { message_count: 10 },
17
+ emotional_evaluation: { volatility: 0.3 },
18
+ mood: { stability: 0.8 }
19
+ )
20
+ expect(store.model.observation_count).to eq(1)
21
+ end
22
+ end
23
+
24
+ describe '#full_description' do
25
+ it 'returns not-formed message before threshold' do
26
+ desc = store.full_description
27
+ expect(desc).to include('not yet formed')
28
+ end
29
+
30
+ it 'returns trait descriptions after formation' do
31
+ Legion::Extensions::Agentic::Self::Personality::Helpers::Constants::FORMATION_THRESHOLD.times do
32
+ store.update(
33
+ working_memory_integration: { intensity: 0.9, wonder_count: 8 },
34
+ prediction_engine: { accuracy: 0.95 }
35
+ )
36
+ end
37
+ desc = store.full_description
38
+ expect(desc).not_to include('not yet formed')
39
+ expect(desc).to be_a(String)
40
+ end
41
+ end
42
+
43
+ describe '#compatibility_score' do
44
+ it 'returns nil for non-hash input' do
45
+ expect(store.compatibility_score('invalid')).to be_nil
46
+ end
47
+
48
+ it 'returns 1.0 for identical profiles' do
49
+ profile = store.model.profile
50
+ score = store.compatibility_score(profile)
51
+ expect(score).to eq(1.0)
52
+ end
53
+
54
+ it 'returns lower score for divergent profiles' do
55
+ opposite = {
56
+ openness: 0.0,
57
+ conscientiousness: 0.0,
58
+ extraversion: 0.0,
59
+ agreeableness: 0.0,
60
+ neuroticism: 0.0
61
+ }
62
+ score = store.compatibility_score(opposite)
63
+ expect(score).to be < 1.0
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,148 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Self::Personality::Helpers::TraitModel do
4
+ subject(:model) { described_class.new }
5
+
6
+ describe '#initialize' do
7
+ it 'starts all traits at 0.5' do
8
+ Legion::Extensions::Agentic::Self::Personality::Helpers::Constants::TRAITS.each do |trait|
9
+ expect(model.trait(trait)).to eq(0.5)
10
+ end
11
+ end
12
+
13
+ it 'starts with zero observation count' do
14
+ expect(model.observation_count).to eq(0)
15
+ end
16
+
17
+ it 'starts with empty history' do
18
+ expect(model.history).to be_empty
19
+ end
20
+ end
21
+
22
+ describe '#update' do
23
+ it 'increments observation count' do
24
+ model.update(curiosity_intensity: 0.8)
25
+ expect(model.observation_count).to eq(1)
26
+ end
27
+
28
+ it 'shifts openness with curiosity signals' do
29
+ 10.times { model.update(curiosity_intensity: 0.9, novel_domains: 0.8) }
30
+ expect(model.trait(:openness)).to be > 0.5
31
+ end
32
+
33
+ it 'shifts conscientiousness with accuracy signals' do
34
+ 10.times { model.update(prediction_accuracy: 0.95, habit_automatic_ratio: 0.7) }
35
+ expect(model.trait(:conscientiousness)).to be > 0.5
36
+ end
37
+
38
+ it 'shifts neuroticism with volatility signals' do
39
+ 10.times { model.update(emotional_volatility: 0.9, anxiety_frequency: 0.8) }
40
+ expect(model.trait(:neuroticism)).to be > 0.5
41
+ end
42
+
43
+ it 'ignores nil values' do
44
+ model.update(curiosity_intensity: nil)
45
+ expect(model.observation_count).to eq(0)
46
+ end
47
+
48
+ it 'records snapshots in history' do
49
+ model.update(curiosity_intensity: 0.8)
50
+ expect(model.history.size).to eq(1)
51
+ end
52
+ end
53
+
54
+ describe '#formed?' do
55
+ it 'returns false before formation threshold' do
56
+ expect(model.formed?).to be false
57
+ end
58
+
59
+ it 'returns true after enough observations' do
60
+ Legion::Extensions::Agentic::Self::Personality::Helpers::Constants::FORMATION_THRESHOLD.times do
61
+ model.update(curiosity_intensity: 0.7)
62
+ end
63
+ expect(model.formed?).to be true
64
+ end
65
+ end
66
+
67
+ describe '#dominant_trait' do
68
+ it 'returns the trait furthest from neutral' do
69
+ 20.times { model.update(curiosity_intensity: 0.95, novel_domains: 0.9) }
70
+ expect(model.dominant_trait).to eq(:openness)
71
+ end
72
+ end
73
+
74
+ describe '#trait_level' do
75
+ it 'returns :mid for default trait values' do
76
+ expect(model.trait_level(:openness)).to eq(:mid)
77
+ end
78
+
79
+ it 'returns :high for elevated traits' do
80
+ 50.times { model.update(curiosity_intensity: 0.99, novel_domains: 0.99) }
81
+ expect(model.trait_level(:openness)).to eq(:high)
82
+ end
83
+
84
+ it 'returns nil for unknown trait' do
85
+ expect(model.trait_level(:nonexistent)).to be_nil
86
+ end
87
+ end
88
+
89
+ describe '#describe' do
90
+ it 'returns descriptor string for known trait' do
91
+ desc = model.describe(:openness)
92
+ expect(desc).to be_a(String)
93
+ end
94
+
95
+ it 'returns nil for unknown trait' do
96
+ expect(model.describe(:nonexistent)).to be_nil
97
+ end
98
+ end
99
+
100
+ describe '#profile' do
101
+ it 'returns all 5 traits' do
102
+ p = model.profile
103
+ expect(p.keys.size).to eq(5)
104
+ Legion::Extensions::Agentic::Self::Personality::Helpers::Constants::TRAITS.each do |t|
105
+ expect(p).to have_key(t)
106
+ end
107
+ end
108
+
109
+ it 'rounds values to 3 decimal places' do
110
+ model.update(curiosity_intensity: 0.8)
111
+ p = model.profile
112
+ p.each_value do |v|
113
+ expect(v.to_s.split('.').last&.length || 0).to be <= 3
114
+ end
115
+ end
116
+ end
117
+
118
+ describe '#stability' do
119
+ it 'returns 0.0 with insufficient history' do
120
+ expect(model.stability).to eq(0.0)
121
+ end
122
+
123
+ it 'returns high stability with consistent inputs' do
124
+ 10.times { model.update(curiosity_intensity: 0.7) }
125
+ expect(model.stability).to be > 0.5
126
+ end
127
+ end
128
+
129
+ describe '#trend' do
130
+ it 'returns :insufficient_data with few entries' do
131
+ expect(model.trend(:openness)).to eq(:insufficient_data)
132
+ end
133
+
134
+ it 'detects increasing trend' do
135
+ 20.times { model.update(curiosity_intensity: 0.1, novel_domains: 0.1) }
136
+ 20.times { model.update(curiosity_intensity: 0.99, novel_domains: 0.99) }
137
+ expect(model.trend(:openness)).to eq(:increasing)
138
+ end
139
+ end
140
+
141
+ describe '#to_h' do
142
+ it 'returns complete state hash' do
143
+ h = model.to_h
144
+ expect(h).to include(:traits, :observation_count, :formed, :stability,
145
+ :dominant_trait, :history_size)
146
+ end
147
+ end
148
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Self::Personality::Runners::Personality do
4
+ let(:client) { Legion::Extensions::Agentic::Self::Personality::Client.new }
5
+
6
+ describe '#update_personality' do
7
+ it 'returns update result' do
8
+ result = client.update_personality(tick_results: {
9
+ working_memory_integration: { intensity: 0.7 }
10
+ })
11
+ expect(result[:updated]).to be true
12
+ expect(result).to include(:observation_count, :formed, :profile, :dominant_trait)
13
+ end
14
+
15
+ it 'handles empty tick results' do
16
+ result = client.update_personality(tick_results: {})
17
+ expect(result[:updated]).to be true
18
+ end
19
+ end
20
+
21
+ describe '#personality_profile' do
22
+ it 'returns profile with all traits' do
23
+ result = client.personality_profile
24
+ expect(result[:traits].keys.size).to eq(5)
25
+ expect(result).to include(:formed, :stability, :dominant, :observations)
26
+ end
27
+ end
28
+
29
+ describe '#describe_personality' do
30
+ it 'returns description' do
31
+ result = client.describe_personality
32
+ expect(result[:description]).to be_a(String)
33
+ expect(result).to have_key(:formed)
34
+ end
35
+ end
36
+
37
+ describe '#trait_detail' do
38
+ it 'returns detail for known trait' do
39
+ result = client.trait_detail(trait: :openness)
40
+ expect(result[:trait]).to eq(:openness)
41
+ expect(result).to include(:value, :level, :description, :trend)
42
+ end
43
+
44
+ it 'returns error for unknown trait' do
45
+ result = client.trait_detail(trait: :nonexistent)
46
+ expect(result[:error]).to eq(:unknown_trait)
47
+ end
48
+ end
49
+
50
+ describe '#personality_compatibility' do
51
+ it 'scores compatibility with another profile' do
52
+ other = { openness: 0.5, conscientiousness: 0.5, extraversion: 0.5,
53
+ agreeableness: 0.5, neuroticism: 0.5 }
54
+ result = client.personality_compatibility(other_profile: other)
55
+ expect(result[:compatibility]).to be_a(Numeric)
56
+ expect(result[:interpretation]).to be_a(Symbol)
57
+ end
58
+ end
59
+
60
+ describe '#personality_stats' do
61
+ it 'returns stats summary' do
62
+ result = client.personality_stats
63
+ expect(result).to include(:observation_count, :formed, :stability,
64
+ :dominant_trait, :profile, :trait_trends)
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Self::Reflection::Client do
4
+ subject(:client) { described_class.new }
5
+
6
+ it 'initializes with a default reflection store' do
7
+ expect(client.reflection_store).to be_a(Legion::Extensions::Agentic::Self::Reflection::Helpers::ReflectionStore)
8
+ end
9
+
10
+ it 'accepts an injected store' do
11
+ custom = Legion::Extensions::Agentic::Self::Reflection::Helpers::ReflectionStore.new
12
+ client = described_class.new(store: custom)
13
+ expect(client.reflection_store).to be(custom)
14
+ end
15
+
16
+ it 'includes the Reflection runner' do
17
+ expect(client).to respond_to(:reflect)
18
+ expect(client).to respond_to(:cognitive_health)
19
+ expect(client).to respond_to(:recent_reflections)
20
+ expect(client).to respond_to(:reflections_by_category)
21
+ expect(client).to respond_to(:adapt)
22
+ expect(client).to respond_to(:reflection_stats)
23
+ end
24
+ end
@@ -0,0 +1,191 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Self::Reflection::Helpers::LlmEnhancer do
4
+ describe '.available?' do
5
+ context 'when Legion::LLM is not defined' do
6
+ it 'returns a falsy value' do
7
+ # Legion::LLM is not defined in the test environment
8
+ expect(described_class.available?).to be_falsy
9
+ end
10
+ end
11
+
12
+ context 'when Legion::LLM is defined but not started' do
13
+ before do
14
+ stub_const('Legion::LLM', double(respond_to?: true, started?: false))
15
+ end
16
+
17
+ it 'returns false' do
18
+ expect(described_class.available?).to be false
19
+ end
20
+ end
21
+
22
+ context 'when Legion::LLM is started' do
23
+ before do
24
+ stub_const('Legion::LLM', double(respond_to?: true, started?: true))
25
+ end
26
+
27
+ it 'returns true' do
28
+ expect(described_class.available?).to be true
29
+ end
30
+ end
31
+
32
+ context 'when an error is raised' do
33
+ before do
34
+ stub_const('Legion::LLM', double)
35
+ allow(Legion::LLM).to receive(:respond_to?).and_raise(StandardError)
36
+ end
37
+
38
+ it 'returns false' do
39
+ expect(described_class.available?).to be false
40
+ end
41
+ end
42
+ end
43
+
44
+ describe '.enhance_reflection' do
45
+ let(:fake_response) do
46
+ double(content: <<~TEXT)
47
+ EMOTION: My arousal is elevated at 0.7 while stability holds at 0.8, suggesting urgency without destabilization.
48
+ PREDICTION: Confidence at 65% with a declining trend and 3 pending predictions signals I am losing ground in forward modeling.
49
+ MEMORY: Memory health appears nominal with no concerning decay patterns detected this cycle.
50
+ TRUST: Trust scores remain stable with no significant drift observed.
51
+ CURIOSITY: Curiosity intensity is moderate; resolution rates suggest effective exploration.
52
+ IDENTITY: Identity entropy is within expected bounds; no drift detected.
53
+ TEXT
54
+ end
55
+ let(:fake_chat) { double }
56
+ let(:monitors_data) do
57
+ [
58
+ {
59
+ category: :emotional_stability,
60
+ observation: 'original',
61
+ severity: :notable,
62
+ metrics: { stability: 0.8 },
63
+ recommendation: :no_action
64
+ },
65
+ {
66
+ category: :prediction_calibration,
67
+ observation: 'original',
68
+ severity: :notable,
69
+ metrics: { confidence: 0.65 },
70
+ recommendation: :increase_curiosity
71
+ }
72
+ ]
73
+ end
74
+ let(:health_scores) do
75
+ {
76
+ prediction_calibration: 0.65,
77
+ curiosity_effectiveness: 0.8,
78
+ emotional_stability: 0.8,
79
+ trust_drift: 1.0,
80
+ memory_health: 0.95,
81
+ cognitive_load: 0.9,
82
+ mode_patterns: 1.0
83
+ }
84
+ end
85
+
86
+ before do
87
+ stub_const('Legion::LLM', double)
88
+ allow(Legion::LLM).to receive(:chat).and_return(fake_chat)
89
+ allow(fake_chat).to receive(:with_instructions)
90
+ allow(fake_chat).to receive(:ask).and_return(fake_response)
91
+ end
92
+
93
+ it 'returns observations hash with at least some categories' do
94
+ result = described_class.enhance_reflection(
95
+ monitors_data: monitors_data,
96
+ health_scores: health_scores
97
+ )
98
+ expect(result).to be_a(Hash)
99
+ expect(result[:observations]).to be_a(Hash)
100
+ expect(result[:observations]).not_to be_empty
101
+ end
102
+
103
+ it 'parses per-category observation text' do
104
+ result = described_class.enhance_reflection(
105
+ monitors_data: monitors_data,
106
+ health_scores: health_scores
107
+ )
108
+ expect(result[:observations][:emotional_stability]).to include('arousal')
109
+ expect(result[:observations][:prediction_calibration]).to include('Confidence')
110
+ end
111
+
112
+ context 'when LLM returns nil content' do
113
+ before { allow(fake_chat).to receive(:ask).and_return(double(content: nil)) }
114
+
115
+ it 'returns nil' do
116
+ result = described_class.enhance_reflection(
117
+ monitors_data: monitors_data,
118
+ health_scores: health_scores
119
+ )
120
+ expect(result).to be_nil
121
+ end
122
+ end
123
+
124
+ context 'when LLM raises an error' do
125
+ before { allow(fake_chat).to receive(:ask).and_raise(StandardError, 'LLM timeout') }
126
+
127
+ it 'returns nil and logs a warning' do
128
+ expect(Legion::Logging).to receive(:warn).with(/enhance_reflection failed/)
129
+ result = described_class.enhance_reflection(
130
+ monitors_data: monitors_data,
131
+ health_scores: health_scores
132
+ )
133
+ expect(result).to be_nil
134
+ end
135
+ end
136
+ end
137
+
138
+ describe '.reflect_on_dream' do
139
+ let(:fake_response) do
140
+ double(content: <<~TEXT)
141
+ REFLECTION: The dream cycle surfaced 3 unresolved traces and resolved 1 contradiction. Memory consolidation is progressing normally with agenda items focused on identity coherence.
142
+ TEXT
143
+ end
144
+ let(:fake_chat) { double }
145
+ let(:dream_results) do
146
+ {
147
+ memory_audit: { decayed: 5, pruned: 2, unresolved_count: 3 },
148
+ contradiction_resolution: { detected: 2, resolved: 1 },
149
+ agenda_formation: { agenda_items: 4 }
150
+ }
151
+ end
152
+
153
+ before do
154
+ stub_const('Legion::LLM', double)
155
+ allow(Legion::LLM).to receive(:chat).and_return(fake_chat)
156
+ allow(fake_chat).to receive(:with_instructions)
157
+ allow(fake_chat).to receive(:ask).and_return(fake_response)
158
+ end
159
+
160
+ it 'returns a reflection string' do
161
+ result = described_class.reflect_on_dream(dream_results: dream_results)
162
+ expect(result).to be_a(Hash)
163
+ expect(result[:reflection]).to be_a(String)
164
+ expect(result[:reflection]).not_to be_empty
165
+ end
166
+
167
+ it 'includes dream cycle content in the reflection' do
168
+ result = described_class.reflect_on_dream(dream_results: dream_results)
169
+ expect(result[:reflection]).to include('unresolved traces')
170
+ end
171
+
172
+ context 'when LLM returns nil content' do
173
+ before { allow(fake_chat).to receive(:ask).and_return(double(content: nil)) }
174
+
175
+ it 'returns nil' do
176
+ result = described_class.reflect_on_dream(dream_results: dream_results)
177
+ expect(result).to be_nil
178
+ end
179
+ end
180
+
181
+ context 'when LLM raises an error' do
182
+ before { allow(fake_chat).to receive(:ask).and_raise(StandardError, 'model error') }
183
+
184
+ it 'returns nil and logs a warning' do
185
+ expect(Legion::Logging).to receive(:warn).with(/reflect_on_dream failed/)
186
+ result = described_class.reflect_on_dream(dream_results: dream_results)
187
+ expect(result).to be_nil
188
+ end
189
+ end
190
+ end
191
+ end
@@ -0,0 +1,120 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Self::Reflection::Helpers::Monitors do
4
+ describe '.monitor_predictions' do
5
+ it 'generates reflection for low confidence' do
6
+ results = described_class.monitor_predictions(
7
+ { prediction_engine: { confidence: 0.2 } },
8
+ []
9
+ )
10
+ expect(results.size).to be >= 1
11
+ expect(results.first[:category]).to eq(:prediction_calibration)
12
+ expect(results.first[:recommendation]).to eq(:increase_curiosity)
13
+ end
14
+
15
+ it 'returns empty for good confidence' do
16
+ results = described_class.monitor_predictions(
17
+ { prediction_engine: { confidence: 0.9 } },
18
+ []
19
+ )
20
+ expect(results).to be_empty
21
+ end
22
+
23
+ it 'detects accuracy trend drop' do
24
+ history = Array.new(5) { { prediction_engine: { confidence: 0.9 } } } +
25
+ Array.new(5) { { prediction_engine: { confidence: 0.5 } } }
26
+
27
+ results = described_class.monitor_predictions(
28
+ { prediction_engine: { confidence: 0.5 } },
29
+ history
30
+ )
31
+ trend = results.find { |r| r[:metrics][:trend_drop] }
32
+ expect(trend).not_to be_nil
33
+ end
34
+ end
35
+
36
+ describe '.monitor_curiosity' do
37
+ it 'generates reflection for low resolution rate' do
38
+ results = described_class.monitor_curiosity(
39
+ working_memory_integration: { resolution_rate: 0.1 }
40
+ )
41
+ expect(results.size).to eq(1)
42
+ expect(results.first[:recommendation]).to eq(:decrease_curiosity)
43
+ end
44
+
45
+ it 'celebrates high resolution rate' do
46
+ results = described_class.monitor_curiosity(
47
+ working_memory_integration: { resolution_rate: 0.9 }
48
+ )
49
+ expect(results.size).to eq(1)
50
+ expect(results.first[:recommendation]).to eq(:celebrate_success)
51
+ end
52
+ end
53
+
54
+ describe '.monitor_emotions' do
55
+ it 'generates reflection for instability' do
56
+ results = described_class.monitor_emotions(
57
+ emotional_evaluation: { stability: 0.1 }
58
+ )
59
+ expect(results.size).to eq(1)
60
+ expect(results.first[:category]).to eq(:emotional_stability)
61
+ expect(results.first[:severity]).to eq(:significant)
62
+ end
63
+
64
+ it 'detects emotional flatness' do
65
+ results = described_class.monitor_emotions(
66
+ emotional_evaluation: { stability: 0.99 }
67
+ )
68
+ expect(results.size).to eq(1)
69
+ expect(results.first[:observation]).to include('flat')
70
+ end
71
+ end
72
+
73
+ describe '.monitor_memory' do
74
+ it 'generates reflection for high decay ratio' do
75
+ results = described_class.monitor_memory(
76
+ memory_consolidation: { pruned: 90, total: 100 }
77
+ )
78
+ expect(results.size).to eq(1)
79
+ expect(results.first[:recommendation]).to eq(:consolidate_memory)
80
+ end
81
+
82
+ it 'returns empty for healthy memory' do
83
+ results = described_class.monitor_memory(
84
+ memory_consolidation: { pruned: 5, total: 100 }
85
+ )
86
+ expect(results).to be_empty
87
+ end
88
+ end
89
+
90
+ describe '.monitor_cognitive_load' do
91
+ it 'generates reflection when near budget' do
92
+ results = described_class.monitor_cognitive_load(
93
+ elapsed: 4.8, budget: 5.0
94
+ )
95
+ expect(results.size).to eq(1)
96
+ expect(results.first[:category]).to eq(:cognitive_load)
97
+ end
98
+
99
+ it 'returns empty when within budget' do
100
+ results = described_class.monitor_cognitive_load(
101
+ elapsed: 1.0, budget: 5.0
102
+ )
103
+ expect(results).to be_empty
104
+ end
105
+ end
106
+
107
+ describe '.run_all' do
108
+ it 'aggregates results from all monitors' do
109
+ tick_results = {
110
+ prediction_engine: { confidence: 0.2 },
111
+ emotional_evaluation: { stability: 0.1 },
112
+ memory_consolidation: { pruned: 90, total: 100 },
113
+ elapsed: 4.8,
114
+ budget: 5.0
115
+ }
116
+ results = described_class.run_all(tick_results, [])
117
+ expect(results.size).to be >= 3
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Self::Reflection::Helpers::ReflectionFactory do
4
+ describe '.new_reflection' do
5
+ it 'creates a reflection with required fields' do
6
+ r = described_class.new_reflection(
7
+ category: :prediction_calibration,
8
+ observation: 'Low accuracy detected'
9
+ )
10
+ expect(r[:reflection_id]).to be_a(String)
11
+ expect(r[:category]).to eq(:prediction_calibration)
12
+ expect(r[:observation]).to eq('Low accuracy detected')
13
+ expect(r[:severity]).to eq(:notable)
14
+ expect(r[:recommendation]).to eq(:no_action)
15
+ expect(r[:acted_on]).to be false
16
+ end
17
+
18
+ it 'raises on invalid category' do
19
+ expect { described_class.new_reflection(category: :invalid, observation: 'x') }
20
+ .to raise_error(ArgumentError, /invalid category/)
21
+ end
22
+
23
+ it 'raises on invalid severity' do
24
+ expect { described_class.new_reflection(category: :trust_drift, observation: 'x', severity: :invalid) }
25
+ .to raise_error(ArgumentError, /invalid severity/)
26
+ end
27
+ end
28
+
29
+ describe '.severity_weight' do
30
+ it 'returns 1.0 for critical' do
31
+ expect(described_class.severity_weight(:critical)).to eq(1.0)
32
+ end
33
+
34
+ it 'returns lower weights for lower severities' do
35
+ expect(described_class.severity_weight(:trivial)).to be < described_class.severity_weight(:notable)
36
+ expect(described_class.severity_weight(:notable)).to be < described_class.severity_weight(:significant)
37
+ end
38
+ end
39
+
40
+ describe '.severity_for_drop' do
41
+ it 'returns critical for large drops' do
42
+ expect(described_class.severity_for_drop(0.5)).to eq(:critical)
43
+ end
44
+
45
+ it 'returns trivial for small drops' do
46
+ expect(described_class.severity_for_drop(0.05)).to eq(:trivial)
47
+ end
48
+ end
49
+ end