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,190 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe Legion::Extensions::Agentic::Self::Agency::Helpers::EfficacyModel do
6
+ subject(:model) { described_class.new }
7
+
8
+ let(:constants) { Legion::Extensions::Agentic::Self::Agency::Helpers::Constants }
9
+
10
+ describe '#initialize' do
11
+ it 'starts with empty domains' do
12
+ expect(model.domains).to eq({})
13
+ end
14
+
15
+ it 'starts with empty history' do
16
+ expect(model.history).to eq([])
17
+ end
18
+ end
19
+
20
+ describe '#efficacy_for' do
21
+ it 'returns default for unknown domains' do
22
+ expect(model.efficacy_for(:coding)).to eq(constants::DEFAULT_EFFICACY)
23
+ end
24
+
25
+ it 'initializes the domain on first access' do
26
+ model.efficacy_for(:coding)
27
+ expect(model.domains).to have_key(:coding)
28
+ end
29
+ end
30
+
31
+ describe '#efficacy_label' do
32
+ it 'returns :uncertain for default efficacy' do
33
+ expect(model.efficacy_label(:new_domain)).to eq(:uncertain)
34
+ end
35
+ end
36
+
37
+ describe '#record_outcome' do
38
+ it 'records a success event and increases efficacy' do
39
+ initial = model.efficacy_for(:coding)
40
+ event = Legion::Extensions::Agentic::Self::Agency::Helpers::OutcomeEvent.new(domain: :coding, outcome_type: :success)
41
+ model.record_outcome(event)
42
+ expect(model.efficacy_for(:coding)).to be > initial
43
+ end
44
+
45
+ it 'records a failure event and decreases efficacy' do
46
+ initial = model.efficacy_for(:coding)
47
+ event = Legion::Extensions::Agentic::Self::Agency::Helpers::OutcomeEvent.new(domain: :coding, outcome_type: :failure)
48
+ model.record_outcome(event)
49
+ expect(model.efficacy_for(:coding)).to be < initial
50
+ end
51
+
52
+ it 'clamps efficacy within bounds' do
53
+ 20.times do
54
+ event = Legion::Extensions::Agentic::Self::Agency::Helpers::OutcomeEvent.new(domain: :x, outcome_type: :success)
55
+ model.record_outcome(event)
56
+ end
57
+ expect(model.efficacy_for(:x)).to be <= constants::EFFICACY_CEILING
58
+
59
+ 20.times do
60
+ event = Legion::Extensions::Agentic::Self::Agency::Helpers::OutcomeEvent.new(domain: :y, outcome_type: :failure)
61
+ model.record_outcome(event)
62
+ end
63
+ expect(model.efficacy_for(:y)).to be >= constants::EFFICACY_FLOOR
64
+ end
65
+
66
+ it 'applies vicarious multiplier' do
67
+ mastery = Legion::Extensions::Agentic::Self::Agency::Helpers::OutcomeEvent.new(domain: :a, outcome_type: :success, source: :mastery)
68
+ vicarious = Legion::Extensions::Agentic::Self::Agency::Helpers::OutcomeEvent.new(domain: :b, outcome_type: :success, source: :vicarious)
69
+
70
+ model.record_outcome(mastery)
71
+ model.record_outcome(vicarious)
72
+
73
+ # Mastery should have stronger effect than vicarious
74
+ expect(model.efficacy_for(:a)).to be > model.efficacy_for(:b)
75
+ end
76
+
77
+ it 'applies persuasion multiplier' do
78
+ event = Legion::Extensions::Agentic::Self::Agency::Helpers::OutcomeEvent.new(domain: :coding, outcome_type: :success, source: :persuasion)
79
+ initial = model.efficacy_for(:coding)
80
+ model.record_outcome(event)
81
+ # Persuasion has weaker effect than mastery
82
+ delta_persuasion = model.efficacy_for(:coding) - initial
83
+
84
+ model2 = described_class.new
85
+ mastery = Legion::Extensions::Agentic::Self::Agency::Helpers::OutcomeEvent.new(domain: :coding, outcome_type: :success, source: :mastery)
86
+ initial2 = model2.efficacy_for(:coding)
87
+ model2.record_outcome(mastery)
88
+ delta_mastery = model2.efficacy_for(:coding) - initial2
89
+
90
+ expect(delta_mastery).to be > delta_persuasion
91
+ end
92
+
93
+ it 'stores event in history' do
94
+ event = Legion::Extensions::Agentic::Self::Agency::Helpers::OutcomeEvent.new(domain: :coding, outcome_type: :success)
95
+ model.record_outcome(event)
96
+ expect(model.history.size).to eq(1)
97
+ end
98
+
99
+ it 'trims history at MAX_TOTAL_HISTORY' do
100
+ (constants::MAX_TOTAL_HISTORY + 10).times do
101
+ event = Legion::Extensions::Agentic::Self::Agency::Helpers::OutcomeEvent.new(domain: :coding, outcome_type: :success)
102
+ model.record_outcome(event)
103
+ end
104
+ expect(model.history.size).to eq(constants::MAX_TOTAL_HISTORY)
105
+ end
106
+ end
107
+
108
+ describe '#decay_all' do
109
+ it 'moves efficacy toward default' do
110
+ event = Legion::Extensions::Agentic::Self::Agency::Helpers::OutcomeEvent.new(domain: :coding, outcome_type: :success)
111
+ 5.times { model.record_outcome(event) }
112
+ high = model.efficacy_for(:coding)
113
+
114
+ model.decay_all
115
+ expect(model.efficacy_for(:coding)).to be < high
116
+ end
117
+
118
+ it 'trims excess domains' do
119
+ (constants::MAX_DOMAINS + 5).times do |i|
120
+ model.efficacy_for(:"domain_#{i}")
121
+ end
122
+ model.decay_all
123
+ expect(model.domain_count).to be <= constants::MAX_DOMAINS
124
+ end
125
+ end
126
+
127
+ describe '#domain_history' do
128
+ it 'filters history by domain' do
129
+ event_a = Legion::Extensions::Agentic::Self::Agency::Helpers::OutcomeEvent.new(domain: :a, outcome_type: :success)
130
+ event_b = Legion::Extensions::Agentic::Self::Agency::Helpers::OutcomeEvent.new(domain: :b, outcome_type: :failure)
131
+ model.record_outcome(event_a)
132
+ model.record_outcome(event_b)
133
+ expect(model.domain_history(:a).size).to eq(1)
134
+ end
135
+ end
136
+
137
+ describe '#success_rate' do
138
+ it 'returns 0 for empty domain' do
139
+ expect(model.success_rate(:empty)).to eq(0.0)
140
+ end
141
+
142
+ it 'computes correct rate' do
143
+ 3.times do
144
+ event = Legion::Extensions::Agentic::Self::Agency::Helpers::OutcomeEvent.new(domain: :x, outcome_type: :success)
145
+ model.record_outcome(event)
146
+ end
147
+ event = Legion::Extensions::Agentic::Self::Agency::Helpers::OutcomeEvent.new(domain: :x, outcome_type: :failure)
148
+ model.record_outcome(event)
149
+ expect(model.success_rate(:x)).to eq(0.75)
150
+ end
151
+ end
152
+
153
+ describe '#strongest_domains / #weakest_domains' do
154
+ before do
155
+ model.record_outcome(Legion::Extensions::Agentic::Self::Agency::Helpers::OutcomeEvent.new(domain: :strong, outcome_type: :success))
156
+ model.record_outcome(Legion::Extensions::Agentic::Self::Agency::Helpers::OutcomeEvent.new(domain: :strong, outcome_type: :success))
157
+ model.record_outcome(Legion::Extensions::Agentic::Self::Agency::Helpers::OutcomeEvent.new(domain: :weak, outcome_type: :failure))
158
+ model.record_outcome(Legion::Extensions::Agentic::Self::Agency::Helpers::OutcomeEvent.new(domain: :weak, outcome_type: :failure))
159
+ end
160
+
161
+ it 'returns strongest domains first' do
162
+ top = model.strongest_domains(1)
163
+ expect(top.keys.first).to eq(:strong)
164
+ end
165
+
166
+ it 'returns weakest domains first' do
167
+ bottom = model.weakest_domains(1)
168
+ expect(bottom.keys.first).to eq(:weak)
169
+ end
170
+ end
171
+
172
+ describe '#overall_efficacy' do
173
+ it 'returns default when empty' do
174
+ expect(model.overall_efficacy).to eq(constants::DEFAULT_EFFICACY)
175
+ end
176
+
177
+ it 'returns average across domains' do
178
+ model.efficacy_for(:a)
179
+ model.efficacy_for(:b)
180
+ expect(model.overall_efficacy).to eq(constants::DEFAULT_EFFICACY)
181
+ end
182
+ end
183
+
184
+ describe '#to_h' do
185
+ it 'returns a snapshot hash' do
186
+ h = model.to_h
187
+ expect(h).to include(:domain_count, :overall_efficacy, :history_size, :domains)
188
+ end
189
+ end
190
+ end
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe Legion::Extensions::Agentic::Self::Agency::Helpers::OutcomeEvent do
6
+ subject(:event) { described_class.new(domain: :coding, outcome_type: :success) }
7
+
8
+ describe '#initialize' do
9
+ it 'generates a uuid' do
10
+ expect(event.id).to match(/\A[0-9a-f-]{36}\z/)
11
+ end
12
+
13
+ it 'stores domain' do
14
+ expect(event.domain).to eq(:coding)
15
+ end
16
+
17
+ it 'stores outcome_type' do
18
+ expect(event.outcome_type).to eq(:success)
19
+ end
20
+
21
+ it 'defaults source to mastery' do
22
+ expect(event.source).to eq(:mastery)
23
+ end
24
+
25
+ it 'defaults magnitude to 1.0' do
26
+ expect(event.magnitude).to eq(1.0)
27
+ end
28
+
29
+ it 'defaults attribution to full_agency' do
30
+ expect(event.attribution).to eq(:full_agency)
31
+ end
32
+
33
+ it 'clamps magnitude to 0..1' do
34
+ high = described_class.new(domain: :x, outcome_type: :success, magnitude: 5.0)
35
+ low = described_class.new(domain: :x, outcome_type: :success, magnitude: -1.0)
36
+ expect(high.magnitude).to eq(1.0)
37
+ expect(low.magnitude).to eq(0.0)
38
+ end
39
+
40
+ it 'records timestamp' do
41
+ expect(event.timestamp).to be_a(Time)
42
+ end
43
+ end
44
+
45
+ describe '#success?' do
46
+ it 'returns true for success' do
47
+ expect(event.success?).to be true
48
+ end
49
+
50
+ it 'returns true for partial_success' do
51
+ partial = described_class.new(domain: :x, outcome_type: :partial_success)
52
+ expect(partial.success?).to be true
53
+ end
54
+
55
+ it 'returns false for failure' do
56
+ failure = described_class.new(domain: :x, outcome_type: :failure)
57
+ expect(failure.success?).to be false
58
+ end
59
+
60
+ it 'returns false for unexpected' do
61
+ unexpected = described_class.new(domain: :x, outcome_type: :unexpected)
62
+ expect(unexpected.success?).to be false
63
+ end
64
+ end
65
+
66
+ describe '#attributed_magnitude' do
67
+ it 'scales magnitude by attribution level' do
68
+ full = described_class.new(domain: :x, outcome_type: :success, attribution: :full_agency)
69
+ low = described_class.new(domain: :x, outcome_type: :success, attribution: :low_agency)
70
+ expect(full.attributed_magnitude).to be > low.attributed_magnitude
71
+ end
72
+
73
+ it 'returns 0 for no_agency' do
74
+ none = described_class.new(domain: :x, outcome_type: :success, attribution: :no_agency)
75
+ expect(none.attributed_magnitude).to eq(0.0)
76
+ end
77
+ end
78
+
79
+ describe '#to_h' do
80
+ it 'returns all fields' do
81
+ h = event.to_h
82
+ expect(h).to include(:id, :domain, :outcome_type, :source, :magnitude, :attribution, :success, :attributed_magnitude, :timestamp)
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,132 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe Legion::Extensions::Agentic::Self::Agency::Runners::Agency do
6
+ let(:runner) do
7
+ obj = Object.new
8
+ obj.extend(described_class)
9
+ obj
10
+ end
11
+
12
+ describe '#record_mastery' do
13
+ it 'records a success and increases efficacy' do
14
+ result = runner.record_mastery(domain: :coding, outcome_type: :success)
15
+ expect(result[:success]).to be true
16
+ expect(result[:efficacy]).to be > 0.5
17
+ end
18
+
19
+ it 'records a failure and decreases efficacy' do
20
+ result = runner.record_mastery(domain: :coding, outcome_type: :failure)
21
+ expect(result[:success]).to be true
22
+ expect(result[:efficacy]).to be < 0.5
23
+ end
24
+
25
+ it 'respects attribution level' do
26
+ full = runner.record_mastery(domain: :a, outcome_type: :success, attribution: :full_agency)
27
+ runner2 = Object.new.extend(described_class)
28
+ low = runner2.record_mastery(domain: :a, outcome_type: :success, attribution: :low_agency)
29
+ expect(full[:efficacy]).to be > low[:efficacy]
30
+ end
31
+ end
32
+
33
+ describe '#record_vicarious' do
34
+ it 'has weaker effect than mastery' do
35
+ runner.record_mastery(domain: :a, outcome_type: :success)
36
+ mastery_efficacy = runner.efficacy_model.efficacy_for(:a)
37
+
38
+ runner2 = Object.new.extend(described_class)
39
+ runner2.record_vicarious(domain: :a, outcome_type: :success)
40
+ vicarious_efficacy = runner2.efficacy_model.efficacy_for(:a)
41
+
42
+ expect(mastery_efficacy).to be > vicarious_efficacy
43
+ end
44
+ end
45
+
46
+ describe '#record_persuasion' do
47
+ it 'increases efficacy for positive persuasion' do
48
+ result = runner.record_persuasion(domain: :public_speaking, positive: true)
49
+ expect(result[:success]).to be true
50
+ expect(result[:efficacy]).to be > 0.5
51
+ end
52
+
53
+ it 'decreases efficacy for negative persuasion' do
54
+ result = runner.record_persuasion(domain: :public_speaking, positive: false)
55
+ expect(result[:efficacy]).to be < 0.5
56
+ end
57
+ end
58
+
59
+ describe '#record_physiological' do
60
+ it 'increases efficacy for positive states' do
61
+ result = runner.record_physiological(domain: :exercise, state: :energized)
62
+ expect(result[:success]).to be true
63
+ expect(result[:efficacy]).to be >= 0.5
64
+ end
65
+
66
+ it 'decreases efficacy for negative states' do
67
+ result = runner.record_physiological(domain: :exercise, state: :exhausted)
68
+ expect(result[:efficacy]).to be < 0.5
69
+ end
70
+ end
71
+
72
+ describe '#update_agency' do
73
+ it 'decays efficacy and returns stats' do
74
+ runner.record_mastery(domain: :coding, outcome_type: :success)
75
+ result = runner.update_agency
76
+ expect(result[:success]).to be true
77
+ expect(result[:stats]).to include(:domain_count, :overall_efficacy)
78
+ end
79
+ end
80
+
81
+ describe '#check_efficacy' do
82
+ it 'returns efficacy details for a domain' do
83
+ runner.record_mastery(domain: :coding, outcome_type: :success)
84
+ result = runner.check_efficacy(domain: :coding)
85
+ expect(result[:success]).to be true
86
+ expect(result[:domain]).to eq(:coding)
87
+ expect(result[:label]).to be_a(Symbol)
88
+ expect(result[:success_rate]).to be_a(Float)
89
+ end
90
+ end
91
+
92
+ describe '#should_attempt?' do
93
+ it 'recommends attempting when efficacy is above threshold' do
94
+ 5.times { runner.record_mastery(domain: :coding, outcome_type: :success) }
95
+ result = runner.should_attempt?(domain: :coding, threshold: 0.3)
96
+ expect(result[:should_attempt]).to be true
97
+ end
98
+
99
+ it 'discourages attempting when efficacy is below threshold' do
100
+ 5.times { runner.record_mastery(domain: :new_thing, outcome_type: :failure) }
101
+ result = runner.should_attempt?(domain: :new_thing, threshold: 0.5)
102
+ expect(result[:should_attempt]).to be false
103
+ end
104
+ end
105
+
106
+ describe '#strongest_domains' do
107
+ it 'returns the highest-efficacy domains' do
108
+ 3.times { runner.record_mastery(domain: :strong, outcome_type: :success) }
109
+ runner.record_mastery(domain: :weak, outcome_type: :failure)
110
+ result = runner.strongest_domains(count: 1)
111
+ expect(result[:success]).to be true
112
+ expect(result[:domains].keys.first).to eq(:strong)
113
+ end
114
+ end
115
+
116
+ describe '#weakest_domains' do
117
+ it 'returns the lowest-efficacy domains' do
118
+ runner.record_mastery(domain: :strong, outcome_type: :success)
119
+ 3.times { runner.record_mastery(domain: :weak, outcome_type: :failure) }
120
+ result = runner.weakest_domains(count: 1)
121
+ expect(result[:domains].keys.first).to eq(:weak)
122
+ end
123
+ end
124
+
125
+ describe '#agency_stats' do
126
+ it 'returns overall stats' do
127
+ result = runner.agency_stats
128
+ expect(result[:success]).to be true
129
+ expect(result[:stats]).to include(:domain_count, :overall_efficacy, :history_size, :domains)
130
+ end
131
+ end
132
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Self::Anchor::Client do
4
+ subject(:client) { described_class.new }
5
+
6
+ it 'includes the runner module' do
7
+ expect(described_class.ancestors).to include(
8
+ Legion::Extensions::Agentic::Self::Anchor::Runners::CognitiveAnchor
9
+ )
10
+ end
11
+
12
+ it 'responds to create_anchor' do
13
+ expect(client).to respond_to(:create_anchor)
14
+ end
15
+
16
+ it 'responds to apply_bias' do
17
+ expect(client).to respond_to(:apply_bias)
18
+ end
19
+
20
+ it 'responds to anchor_status' do
21
+ expect(client).to respond_to(:anchor_status)
22
+ end
23
+
24
+ it 'can create anchor and apply bias through client' do
25
+ result = client.create_anchor(anchor_type: :number, domain: :test, content: 'price anchor')
26
+ expect(result[:success]).to be true
27
+ bias_result = client.apply_bias(anchor_id: result[:anchor][:id], new_value: 0.8)
28
+ expect(bias_result[:success]).to be true
29
+ end
30
+ end
@@ -0,0 +1,109 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Self::Anchor::Helpers::AnchorEngine do
4
+ subject(:engine) { described_class.new }
5
+
6
+ let(:default_attrs) { { anchor_type: :belief, domain: :reasoning, content: 'first impression' } }
7
+
8
+ describe '#create_anchor' do
9
+ it 'creates and stores an anchor' do
10
+ a = engine.create_anchor(**default_attrs)
11
+ expect(a).to be_a(Legion::Extensions::Agentic::Self::Anchor::Helpers::Anchor)
12
+ expect(engine.all_anchors.size).to eq(1)
13
+ end
14
+
15
+ it 'raises when limit reached' do
16
+ stub_const('Legion::Extensions::Agentic::Self::Anchor::Helpers::Constants::MAX_ANCHORS', 1)
17
+ engine.create_anchor(**default_attrs)
18
+ expect do
19
+ engine.create_anchor(anchor_type: :number, domain: :t, content: 'x')
20
+ end.to raise_error(ArgumentError, /anchor limit/)
21
+ end
22
+ end
23
+
24
+ describe '#create_chain' do
25
+ it 'creates a chain linked to anchor' do
26
+ a = engine.create_anchor(**default_attrs)
27
+ c = engine.create_chain(anchor_id: a.id)
28
+ expect(c.anchor_id).to eq(a.id)
29
+ end
30
+
31
+ it 'raises for unknown anchor' do
32
+ expect do
33
+ engine.create_chain(anchor_id: 'bad')
34
+ end.to raise_error(ArgumentError, /anchor not found/)
35
+ end
36
+ end
37
+
38
+ describe '#apply_bias' do
39
+ it 'pulls value toward anchor reference' do
40
+ a = engine.create_anchor(**default_attrs, reference_value: 0.3, grip: 0.8, weight: 0.6)
41
+ result = engine.apply_bias(anchor_id: a.id, new_value: 0.9)
42
+ expect(result[:biased]).to be < 0.9
43
+ expect(result[:shift]).to be > 0
44
+ end
45
+ end
46
+
47
+ describe '#drag_anchor' do
48
+ it 'increases anchor grip' do
49
+ a = engine.create_anchor(**default_attrs, grip: 0.5)
50
+ engine.drag_anchor(anchor_id: a.id)
51
+ expect(a.grip).to be > 0.5
52
+ end
53
+ end
54
+
55
+ describe '#drift_all!' do
56
+ it 'drifts all anchors' do
57
+ a = engine.create_anchor(**default_attrs)
58
+ initial = a.grip
59
+ engine.drift_all!
60
+ expect(a.grip).to be < initial
61
+ end
62
+ end
63
+
64
+ describe '#wear_all_chains!' do
65
+ it 'wears all chains' do
66
+ a = engine.create_anchor(**default_attrs)
67
+ c = engine.create_chain(anchor_id: a.id)
68
+ initial = c.flexibility
69
+ engine.wear_all_chains!
70
+ expect(c.flexibility).to be < initial
71
+ end
72
+ end
73
+
74
+ describe '#anchors_by_type' do
75
+ it 'returns counts per type' do
76
+ engine.create_anchor(**default_attrs)
77
+ engine.create_anchor(anchor_type: :number, domain: :t, content: 'x')
78
+ counts = engine.anchors_by_type
79
+ expect(counts[:belief]).to eq(1)
80
+ expect(counts[:number]).to eq(1)
81
+ end
82
+ end
83
+
84
+ describe '#strongest_anchors' do
85
+ it 'returns sorted by grip descending' do
86
+ engine.create_anchor(**default_attrs, grip: 0.3)
87
+ a2 = engine.create_anchor(anchor_type: :number, domain: :t, content: 'x', grip: 0.9)
88
+ expect(engine.strongest_anchors(limit: 1).first).to eq(a2)
89
+ end
90
+ end
91
+
92
+ describe '#chains_for' do
93
+ it 'returns chains for a specific anchor' do
94
+ a = engine.create_anchor(**default_attrs)
95
+ engine.create_chain(anchor_id: a.id)
96
+ engine.create_chain(anchor_id: a.id, material: :rope)
97
+ expect(engine.chains_for(a.id).size).to eq(2)
98
+ end
99
+ end
100
+
101
+ describe '#anchor_report' do
102
+ it 'returns comprehensive hash' do
103
+ engine.create_anchor(**default_attrs)
104
+ report = engine.anchor_report
105
+ expect(report).to include(:total_anchors, :total_chains, :by_type,
106
+ :ironclad_count, :drifting_count, :avg_grip)
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,124 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Self::Anchor::Helpers::Anchor do
4
+ subject(:anchor) do
5
+ described_class.new(anchor_type: :belief, domain: :reasoning, content: 'first impression')
6
+ end
7
+
8
+ describe '#initialize' do
9
+ it 'assigns a UUID' do
10
+ expect(anchor.id).to match(/\A[0-9a-f-]{36}\z/)
11
+ end
12
+
13
+ it 'sets anchor_type' do
14
+ expect(anchor.anchor_type).to eq(:belief)
15
+ end
16
+
17
+ it 'defaults reference_value to 0.5' do
18
+ expect(anchor.reference_value).to eq(0.5)
19
+ end
20
+
21
+ it 'defaults grip to 0.7' do
22
+ expect(anchor.grip).to eq(0.7)
23
+ end
24
+
25
+ it 'defaults weight to 0.5' do
26
+ expect(anchor.weight).to eq(0.5)
27
+ end
28
+
29
+ it 'clamps grip to 0..1' do
30
+ a = described_class.new(anchor_type: :number, domain: :t, content: 'x', grip: 5.0)
31
+ expect(a.grip).to eq(1.0)
32
+ end
33
+
34
+ it 'raises on unknown type' do
35
+ expect do
36
+ described_class.new(anchor_type: :magic, domain: :t, content: 'x')
37
+ end.to raise_error(ArgumentError, /unknown anchor type/)
38
+ end
39
+ end
40
+
41
+ describe '#drag!' do
42
+ it 'increases grip' do
43
+ initial = anchor.grip
44
+ anchor.drag!
45
+ expect(anchor.grip).to eq((initial + 0.06).round(10))
46
+ end
47
+ end
48
+
49
+ describe '#drift!' do
50
+ it 'decreases grip' do
51
+ initial = anchor.grip
52
+ anchor.drift!
53
+ expect(anchor.grip).to eq((initial - 0.03).round(10))
54
+ end
55
+ end
56
+
57
+ describe '#bias_pull' do
58
+ it 'pulls new value toward reference' do
59
+ anchor.grip = 0.8
60
+ anchor.weight = 0.5
61
+ biased = anchor.bias_pull(0.9)
62
+ expect(biased).to be < 0.9
63
+ expect(biased).to be > 0.5
64
+ end
65
+
66
+ it 'has no effect with zero grip' do
67
+ anchor.grip = 0.0
68
+ expect(anchor.bias_pull(0.9)).to eq(0.9)
69
+ end
70
+
71
+ it 'returns reference when grip and weight are 1.0' do
72
+ anchor.grip = 1.0
73
+ anchor.weight = 1.0
74
+ expect(anchor.bias_pull(0.9)).to eq(anchor.reference_value)
75
+ end
76
+ end
77
+
78
+ describe '#ironclad?' do
79
+ it 'returns false at default' do
80
+ expect(anchor).not_to be_ironclad
81
+ end
82
+
83
+ it 'returns true at 0.8+' do
84
+ anchor.grip = 0.85
85
+ expect(anchor).to be_ironclad
86
+ end
87
+ end
88
+
89
+ describe '#drifting?' do
90
+ it 'returns false at default' do
91
+ expect(anchor).not_to be_drifting
92
+ end
93
+
94
+ it 'returns true below 0.2' do
95
+ anchor.grip = 0.1
96
+ expect(anchor).to be_drifting
97
+ end
98
+ end
99
+
100
+ describe '#heavy?' do
101
+ it 'returns false at default' do
102
+ expect(anchor).not_to be_heavy
103
+ end
104
+
105
+ it 'returns true at 0.7+' do
106
+ anchor.weight = 0.8
107
+ expect(anchor).to be_heavy
108
+ end
109
+ end
110
+
111
+ describe '#grip_label' do
112
+ it 'returns :firm at default' do
113
+ expect(anchor.grip_label).to eq(:firm)
114
+ end
115
+ end
116
+
117
+ describe '#to_h' do
118
+ it 'includes all expected keys' do
119
+ expected = %i[id anchor_type domain content reference_value grip weight
120
+ grip_label ironclad drifting created_at]
121
+ expect(anchor.to_h.keys).to match_array(expected)
122
+ end
123
+ end
124
+ end