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,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'legion/extensions/agentic/self/identity/client'
4
+
5
+ RSpec.describe Legion::Extensions::Agentic::Self::Identity::Runners::Identity do
6
+ let(:client) { Legion::Extensions::Agentic::Self::Identity::Client.new }
7
+
8
+ describe '#observe_behavior' do
9
+ it 'records a single observation' do
10
+ result = client.observe_behavior(dimension: :communication_cadence, value: 0.7)
11
+ expect(result[:recorded]).to be true
12
+ expect(result[:observation_count]).to eq(1)
13
+ end
14
+ end
15
+
16
+ describe '#observe_all' do
17
+ it 'records multiple observations' do
18
+ result = client.observe_all(observations: {
19
+ communication_cadence: 0.6,
20
+ vocabulary_patterns: 0.7,
21
+ emotional_response: 0.5
22
+ })
23
+ expect(result[:dimensions_observed].size).to eq(3)
24
+ expect(result[:observation_count]).to eq(3)
25
+ end
26
+ end
27
+
28
+ describe '#check_entropy' do
29
+ it 'returns entropy classification' do
30
+ result = client.check_entropy
31
+ expect(result[:entropy]).to be_between(0.0, 1.0)
32
+ expect(result).to have_key(:classification)
33
+ expect(result).to have_key(:trend)
34
+ expect(result).to have_key(:in_range)
35
+ end
36
+
37
+ it 'warns on high entropy observations' do
38
+ # Build baseline
39
+ 20.times { client.observe_behavior(dimension: :communication_cadence, value: 0.5) }
40
+ # Check with very different observation
41
+ result = client.check_entropy(observations: { communication_cadence: 10.0 })
42
+ expect(result[:warning]).to eq(:possible_impersonation_or_drift) if result[:classification] == :high_entropy
43
+ end
44
+ end
45
+
46
+ describe '#identity_status' do
47
+ it 'returns model state' do
48
+ status = client.identity_status
49
+ expect(status).to have_key(:model)
50
+ expect(status).to have_key(:maturity)
51
+ expect(status).to have_key(:observation_count)
52
+ end
53
+ end
54
+
55
+ describe '#identity_maturity' do
56
+ it 'returns maturity level' do
57
+ result = client.identity_maturity
58
+ expect(result[:maturity]).to eq(:nascent)
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Self::Metacognition::Client do
4
+ describe '#initialize' do
5
+ it 'creates a default snapshot store' do
6
+ client = described_class.new
7
+ expect(client.snapshot_store).to be_a(Legion::Extensions::Agentic::Self::Metacognition::Helpers::SnapshotStore)
8
+ end
9
+
10
+ it 'accepts an injected snapshot store' do
11
+ store = Legion::Extensions::Agentic::Self::Metacognition::Helpers::SnapshotStore.new
12
+ client = described_class.new(snapshot_store: store)
13
+ expect(client.snapshot_store).to equal(store)
14
+ end
15
+ end
16
+
17
+ it 'includes Runners::Metacognition' do
18
+ expect(described_class.ancestors).to include(Legion::Extensions::Agentic::Self::Metacognition::Runners::Metacognition)
19
+ end
20
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Self::Metacognition::Helpers::Constants do
4
+ it 'defines MAX_SNAPSHOTS' do
5
+ expect(described_class::MAX_SNAPSHOTS).to eq(50)
6
+ end
7
+
8
+ it 'defines SNAPSHOT_TTL' do
9
+ expect(described_class::SNAPSHOT_TTL).to eq(30)
10
+ end
11
+
12
+ it 'defines SUBSYSTEMS as frozen array' do
13
+ expect(described_class::SUBSYSTEMS).to be_frozen
14
+ expect(described_class::SUBSYSTEMS).to include(:tick, :cortex, :memory)
15
+ end
16
+
17
+ it 'defines CAPABILITY_CATEGORIES' do
18
+ expect(described_class::CAPABILITY_CATEGORIES).to include(:perception, :cognition, :memory, :motivation, :safety)
19
+ end
20
+
21
+ it 'maps every extension to a capability category' do
22
+ described_class::EXTENSION_CAPABILITIES.each_value do |cat|
23
+ expect(described_class::CAPABILITY_CATEGORIES).to include(cat)
24
+ end
25
+ end
26
+
27
+ it 'defines HEALTH_LABELS for full range' do
28
+ labels = described_class::HEALTH_LABELS
29
+ expect(labels.size).to eq(5)
30
+ end
31
+ end
@@ -0,0 +1,102 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Self::Metacognition::Helpers::NarratorBridge do
4
+ let(:model) do
5
+ {
6
+ identity: { framework: 'LegionIO', role: :cognitive_agent, model: :brain_modeled, extensions: 24 },
7
+ architecture: { total_extensions: 24, loaded_count: 18, unloaded_count: 6, loaded: Array.new(18, :Ext), unloaded: %i[A B C D E F] },
8
+ cognitive: { mode: :full_active, phases_run: 12, health: 0.92, active_drives: %i[curiosity corrective], attention: { spotlight: 3 },
9
+ curiosity: { open_wonders: 4 } },
10
+ capabilities: { perception: { active: true }, cognition: { active: true }, memory: { active: true }, motivation: { active: true },
11
+ safety: { active: false } }
12
+ }
13
+ end
14
+
15
+ describe '.narrate_self_model' do
16
+ it 'produces a prose string' do
17
+ prose = described_class.narrate_self_model(model)
18
+ expect(prose).to be_a(String)
19
+ expect(prose.length).to be > 50
20
+ end
21
+
22
+ it 'includes identity info' do
23
+ prose = described_class.narrate_self_model(model)
24
+ expect(prose).to include('brain_modeled')
25
+ expect(prose).to include('LegionIO')
26
+ end
27
+
28
+ it 'includes architecture stats' do
29
+ prose = described_class.narrate_self_model(model)
30
+ expect(prose).to include('18 of 24')
31
+ end
32
+
33
+ it 'includes cognitive state' do
34
+ prose = described_class.narrate_self_model(model)
35
+ expect(prose).to include('full_active')
36
+ expect(prose).to include('12 phases')
37
+ end
38
+
39
+ it 'includes health label' do
40
+ prose = described_class.narrate_self_model(model)
41
+ expect(prose).to include('excellent')
42
+ end
43
+
44
+ it 'includes active drives' do
45
+ prose = described_class.narrate_self_model(model)
46
+ expect(prose).to include('curiosity')
47
+ end
48
+ end
49
+
50
+ describe '.narrate_identity' do
51
+ it 'returns nil for non-hash' do
52
+ expect(described_class.narrate_identity(nil)).to be_nil
53
+ end
54
+
55
+ it 'describes the agent identity' do
56
+ result = described_class.narrate_identity(model[:identity])
57
+ expect(result).to include('24 extension slots')
58
+ end
59
+ end
60
+
61
+ describe '.narrate_architecture' do
62
+ it 'mentions missing extensions' do
63
+ result = described_class.narrate_architecture(model[:architecture])
64
+ expect(result).to include('Missing:')
65
+ end
66
+
67
+ it 'omits missing section when all loaded' do
68
+ arch = model[:architecture].merge(unloaded: [])
69
+ result = described_class.narrate_architecture(arch)
70
+ expect(result).not_to include('Missing')
71
+ end
72
+ end
73
+
74
+ describe '.narrate_cognitive' do
75
+ it 'returns no tick data message' do
76
+ result = described_class.narrate_cognitive(status: :no_tick_data)
77
+ expect(result).to include('No tick data')
78
+ end
79
+
80
+ it 'includes spotlight count' do
81
+ result = described_class.narrate_cognitive(model[:cognitive])
82
+ expect(result).to include('3 signals in spotlight')
83
+ end
84
+
85
+ it 'includes open wonders' do
86
+ result = described_class.narrate_cognitive(model[:cognitive])
87
+ expect(result).to include('4 open questions')
88
+ end
89
+ end
90
+
91
+ describe '.narrate_capabilities' do
92
+ it 'lists active capability categories' do
93
+ result = described_class.narrate_capabilities(model[:capabilities])
94
+ expect(result).to include('perception')
95
+ expect(result).not_to include('safety')
96
+ end
97
+
98
+ it 'returns nil for empty capabilities' do
99
+ expect(described_class.narrate_capabilities({})).to be_nil
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,227 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Self::Metacognition::Helpers::RegistryStore do
4
+ subject(:store) { described_class.new }
5
+
6
+ let(:entry) do
7
+ {
8
+ name: 'lex-memory',
9
+ module_name: 'Memory',
10
+ category: 'memory',
11
+ status: 'active',
12
+ health_score: 1.0
13
+ }
14
+ end
15
+
16
+ let(:entry2) do
17
+ {
18
+ name: 'lex-emotion',
19
+ module_name: 'Emotion',
20
+ category: 'perception',
21
+ status: 'active',
22
+ health_score: 0.9
23
+ }
24
+ end
25
+
26
+ describe '#register' do
27
+ it 'stores an entry by name' do
28
+ store.register(entry)
29
+ expect(store.count).to eq(1)
30
+ end
31
+
32
+ it 'sets created_at and updated_at on register' do
33
+ store.register(entry)
34
+ result = store.get('lex-memory')
35
+ expect(result[:created_at]).to be_a(Time)
36
+ expect(result[:updated_at]).to be_a(Time)
37
+ end
38
+
39
+ it 'sets invocation_count to 0 on register' do
40
+ store.register(entry)
41
+ expect(store.get('lex-memory')[:invocation_count]).to eq(0)
42
+ end
43
+
44
+ it 'sets health_score to 1.0 on register' do
45
+ entry_no_health = entry.except(:health_score)
46
+ store.register(entry_no_health)
47
+ expect(store.get('lex-memory')[:health_score]).to eq(1.0)
48
+ end
49
+
50
+ it 'overwrites an existing entry with the same name' do
51
+ store.register(entry)
52
+ store.register(entry.merge(category: 'cognition'))
53
+ expect(store.get('lex-memory')[:category]).to eq('cognition')
54
+ expect(store.count).to eq(1)
55
+ end
56
+ end
57
+
58
+ describe '#deregister' do
59
+ it 'removes an entry by name' do
60
+ store.register(entry)
61
+ store.deregister('lex-memory')
62
+ expect(store.count).to eq(0)
63
+ end
64
+
65
+ it 'returns nil when deregistering a non-existent name' do
66
+ expect(store.deregister('lex-nonexistent')).to be_nil
67
+ end
68
+ end
69
+
70
+ describe '#get' do
71
+ it 'returns nil for unknown name' do
72
+ expect(store.get('lex-unknown')).to be_nil
73
+ end
74
+
75
+ it 'returns a copy of the entry' do
76
+ store.register(entry)
77
+ result = store.get('lex-memory')
78
+ expect(result[:name]).to eq('lex-memory')
79
+ end
80
+
81
+ it 'returns a dup (not the same object)' do
82
+ store.register(entry)
83
+ result1 = store.get('lex-memory')
84
+ result2 = store.get('lex-memory')
85
+ expect(result1).not_to be(result2)
86
+ end
87
+ end
88
+
89
+ describe '#list' do
90
+ before do
91
+ store.register(entry)
92
+ store.register(entry2)
93
+ store.register(entry.merge(name: 'lex-episodic', category: 'memory', status: 'inactive'))
94
+ end
95
+
96
+ it 'returns all entries with no filters' do
97
+ expect(store.list.size).to eq(3)
98
+ end
99
+
100
+ it 'filters by status' do
101
+ active = store.list(status: 'active')
102
+ expect(active.size).to eq(2)
103
+ expect(active.map { |e| e[:status] }.uniq).to eq(['active'])
104
+ end
105
+
106
+ it 'filters by category' do
107
+ memory = store.list(category: 'memory')
108
+ expect(memory.size).to eq(2)
109
+ end
110
+
111
+ it 'filters by both status and category' do
112
+ result = store.list(status: 'active', category: 'memory')
113
+ expect(result.size).to eq(1)
114
+ expect(result.first[:name]).to eq('lex-memory')
115
+ end
116
+
117
+ it 'returns empty array when no match' do
118
+ expect(store.list(category: 'nonexistent')).to eq([])
119
+ end
120
+ end
121
+
122
+ describe '#update' do
123
+ it 'returns nil for unknown name' do
124
+ expect(store.update('lex-missing', { health_score: 0.5 })).to be_nil
125
+ end
126
+
127
+ it 'updates the specified attributes' do
128
+ store.register(entry)
129
+ result = store.update('lex-memory', { health_score: 0.75, status: 'degraded' })
130
+ expect(result[:health_score]).to eq(0.75)
131
+ expect(result[:status]).to eq('degraded')
132
+ end
133
+
134
+ it 'updates updated_at on modification' do
135
+ store.register(entry)
136
+ original_time = store.get('lex-memory')[:updated_at]
137
+ result = store.update('lex-memory', { health_score: 0.5 })
138
+ expect(result[:updated_at]).to be >= original_time
139
+ end
140
+
141
+ it 'preserves unchanged attributes' do
142
+ store.register(entry)
143
+ store.update('lex-memory', { health_score: 0.5 })
144
+ expect(store.get('lex-memory')[:module_name]).to eq('Memory')
145
+ end
146
+ end
147
+
148
+ describe '#category_distribution' do
149
+ it 'returns empty hash when no entries' do
150
+ expect(store.category_distribution).to eq({})
151
+ end
152
+
153
+ it 'counts entries per category' do
154
+ store.register(entry)
155
+ store.register(entry2)
156
+ store.register(entry.merge(name: 'lex-episodic', category: 'memory'))
157
+ dist = store.category_distribution
158
+ expect(dist['memory']).to eq(2)
159
+ expect(dist['perception']).to eq(1)
160
+ end
161
+ end
162
+
163
+ describe '#count' do
164
+ it 'returns 0 when empty' do
165
+ expect(store.count).to eq(0)
166
+ end
167
+
168
+ it 'returns the number of registered entries' do
169
+ store.register(entry)
170
+ store.register(entry2)
171
+ expect(store.count).to eq(2)
172
+ end
173
+ end
174
+
175
+ describe '#by_health' do
176
+ before do
177
+ store.register(entry.merge(health_score: 0.9))
178
+ store.register(entry2.merge(health_score: 0.3))
179
+ store.register(entry.merge(name: 'lex-prediction', module_name: 'Prediction', category: 'cognition', health_score: 0.1))
180
+ end
181
+
182
+ it 'returns entries below default threshold of 0.4' do
183
+ result = store.by_health
184
+ expect(result.size).to eq(2)
185
+ result.each { |e| expect(e[:health_score]).to be < 0.4 }
186
+ end
187
+
188
+ it 'accepts a custom threshold' do
189
+ result = store.by_health(threshold: 0.2)
190
+ expect(result.size).to eq(1)
191
+ expect(result.first[:health_score]).to eq(0.1)
192
+ end
193
+
194
+ it 'returns empty array when all entries are healthy' do
195
+ healthy_store = described_class.new
196
+ healthy_store.register(entry.merge(health_score: 0.95))
197
+ expect(healthy_store.by_health).to eq([])
198
+ end
199
+ end
200
+
201
+ describe 'thread safety' do
202
+ it 'handles concurrent registrations without data corruption' do
203
+ threads = 20.times.map do |i|
204
+ Thread.new do
205
+ store.register(entry.merge(name: "lex-thread-#{i}", module_name: "Mod#{i}"))
206
+ end
207
+ end
208
+ threads.each(&:join)
209
+ expect(store.count).to eq(20)
210
+ end
211
+
212
+ it 'handles concurrent reads and writes safely' do
213
+ store.register(entry)
214
+ errors = []
215
+ threads = 10.times.map do
216
+ Thread.new do
217
+ store.get('lex-memory')
218
+ store.update('lex-memory', { health_score: rand })
219
+ rescue StandardError => e
220
+ errors << e
221
+ end
222
+ end
223
+ threads.each(&:join)
224
+ expect(errors).to be_empty
225
+ end
226
+ end
227
+ end
@@ -0,0 +1,117 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Self::Metacognition::Helpers::SelfModel do
4
+ describe '.build' do
5
+ it 'returns a structured self-model' do
6
+ model = described_class.build
7
+ expect(model).to have_key(:identity)
8
+ expect(model).to have_key(:architecture)
9
+ expect(model).to have_key(:capabilities)
10
+ expect(model).to have_key(:subsystems)
11
+ expect(model).to have_key(:cognitive)
12
+ expect(model).to have_key(:assembled_at)
13
+ end
14
+
15
+ it 'includes identity information' do
16
+ model = described_class.build
17
+ expect(model[:identity][:framework]).to eq('LegionIO')
18
+ expect(model[:identity][:role]).to eq(:cognitive_agent)
19
+ expect(model[:identity][:model]).to eq(:brain_modeled)
20
+ end
21
+
22
+ it 'discovers Metacognition as loaded' do
23
+ model = described_class.build
24
+ expect(model[:architecture][:loaded]).to include(:Metacognition)
25
+ end
26
+
27
+ it 'includes cognitive snapshot from tick_results' do
28
+ tick_results = {
29
+ post_tick_reflection: { cognitive_health: 0.85 },
30
+ action_selection: { intentions: [{ drive: :curiosity }] }
31
+ }
32
+ model = described_class.build(tick_results: tick_results)
33
+ expect(model[:cognitive][:health]).to eq(0.85)
34
+ expect(model[:cognitive][:active_drives]).to eq([:curiosity])
35
+ end
36
+
37
+ it 'returns no_tick_data for empty tick_results' do
38
+ model = described_class.build(tick_results: {})
39
+ expect(model[:cognitive][:status]).to eq(:no_tick_data)
40
+ end
41
+ end
42
+
43
+ describe '.discover_loaded_extensions' do
44
+ it 'returns hash of extension availability' do
45
+ result = described_class.discover_loaded_extensions
46
+ expect(result).to be_a(Hash)
47
+ expect(result[:Metacognition][:loaded]).to be true
48
+ end
49
+ end
50
+
51
+ describe '.map_capabilities' do
52
+ it 'groups loaded extensions by category' do
53
+ loaded = { Metacognition: { loaded: true }, Memory: { loaded: false } }
54
+ caps = described_class.map_capabilities(loaded)
55
+ expect(caps[:introspection][:active]).to be true
56
+ expect(caps[:memory][:active]).to be false
57
+ end
58
+
59
+ it 'lists extensions per category' do
60
+ loaded = { Metacognition: { loaded: true }, Reflection: { loaded: true } }
61
+ caps = described_class.map_capabilities(loaded)
62
+ expect(caps[:introspection][:extensions]).to include(:Metacognition, :Reflection)
63
+ end
64
+ end
65
+
66
+ describe '.build_cognitive_snapshot' do
67
+ it 'extracts attention data' do
68
+ tick_results = {
69
+ sensory_processing: { spotlight_count: 3, total_signals: 10 }
70
+ }
71
+ snapshot = described_class.build_cognitive_snapshot(tick_results)
72
+ expect(snapshot[:attention]).to eq(spotlight: 3, total: 10)
73
+ end
74
+
75
+ it 'extracts curiosity data' do
76
+ tick_results = {
77
+ working_memory_integration: { curiosity_intensity: 0.7, open_wonders: 4 }
78
+ }
79
+ snapshot = described_class.build_cognitive_snapshot(tick_results)
80
+ expect(snapshot[:curiosity]).to eq(intensity: 0.7, open_wonders: 4)
81
+ end
82
+
83
+ it 'extracts prediction data' do
84
+ tick_results = {
85
+ prediction_engine: { confidence: 0.9, mode: :functional_mapping }
86
+ }
87
+ snapshot = described_class.build_cognitive_snapshot(tick_results)
88
+ expect(snapshot[:prediction]).to eq(confidence: 0.9, mode: :functional_mapping)
89
+ end
90
+ end
91
+
92
+ describe '.health_label' do
93
+ it 'returns :excellent for >= 0.8' do
94
+ expect(described_class.health_label(0.92)).to eq(:excellent)
95
+ end
96
+
97
+ it 'returns :good for 0.6-0.8' do
98
+ expect(described_class.health_label(0.7)).to eq(:good)
99
+ end
100
+
101
+ it 'returns :fair for 0.4-0.6' do
102
+ expect(described_class.health_label(0.5)).to eq(:fair)
103
+ end
104
+
105
+ it 'returns :degraded for 0.2-0.4' do
106
+ expect(described_class.health_label(0.3)).to eq(:degraded)
107
+ end
108
+
109
+ it 'returns :critical for < 0.2' do
110
+ expect(described_class.health_label(0.1)).to eq(:critical)
111
+ end
112
+
113
+ it 'returns :unknown for nil' do
114
+ expect(described_class.health_label(nil)).to eq(:unknown)
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,128 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Self::Metacognition::Helpers::SnapshotStore do
4
+ subject(:store) { described_class.new }
5
+
6
+ let(:model) do
7
+ {
8
+ identity: { framework: 'LegionIO' },
9
+ architecture: { loaded: %i[Tick Memory], loaded_count: 2, total_extensions: 5, unloaded: %i[Emotion Prediction Cortex] },
10
+ capabilities: {},
11
+ subsystems: {},
12
+ cognitive: { health: 0.85, mode: :full_active },
13
+ assembled_at: Time.now.utc
14
+ }
15
+ end
16
+
17
+ describe '#store' do
18
+ it 'adds a model snapshot' do
19
+ store.store(model)
20
+ expect(store.size).to eq(1)
21
+ end
22
+
23
+ it 'caps at MAX_SNAPSHOTS' do
24
+ 55.times { |i| store.store(model.merge(assembled_at: Time.now.utc + i)) }
25
+ expect(store.size).to eq(50)
26
+ end
27
+ end
28
+
29
+ describe '#latest' do
30
+ it 'returns nil when empty' do
31
+ expect(store.latest).to be_nil
32
+ end
33
+
34
+ it 'returns the most recent snapshot' do
35
+ store.store(model)
36
+ expect(store.latest[:identity][:framework]).to eq('LegionIO')
37
+ end
38
+ end
39
+
40
+ describe '#stale?' do
41
+ it 'returns true when empty' do
42
+ expect(store.stale?).to be true
43
+ end
44
+
45
+ it 'returns false for fresh snapshot' do
46
+ store.store(model)
47
+ expect(store.stale?).to be false
48
+ end
49
+
50
+ it 'returns true for old snapshot' do
51
+ old = model.merge(assembled_at: Time.now.utc - 60)
52
+ store.store(old)
53
+ expect(store.stale?).to be true
54
+ end
55
+ end
56
+
57
+ describe '#history' do
58
+ it 'returns limited history' do
59
+ 5.times { |i| store.store(model.merge(assembled_at: Time.now.utc + i)) }
60
+ expect(store.history(limit: 3).size).to eq(3)
61
+ end
62
+ end
63
+
64
+ describe '#architecture_changes' do
65
+ it 'returns empty array with fewer than 2 snapshots' do
66
+ store.store(model)
67
+ expect(store.architecture_changes).to eq([])
68
+ end
69
+
70
+ it 'detects added extensions' do
71
+ store.store(model)
72
+ updated = model.merge(
73
+ architecture: { loaded: %i[Tick Memory Emotion], loaded_count: 3, total_extensions: 5, unloaded: %i[Prediction Cortex] },
74
+ assembled_at: Time.now.utc + 1
75
+ )
76
+ store.store(updated)
77
+
78
+ changes = store.architecture_changes
79
+ expect(changes.size).to eq(1)
80
+ expect(changes.first[:added]).to eq([:Emotion])
81
+ expect(changes.first[:removed]).to eq([])
82
+ end
83
+
84
+ it 'detects removed extensions' do
85
+ store.store(model)
86
+ updated = model.merge(
87
+ architecture: { loaded: [:Tick], loaded_count: 1, total_extensions: 5, unloaded: %i[Memory Emotion Prediction Cortex] },
88
+ assembled_at: Time.now.utc + 1
89
+ )
90
+ store.store(updated)
91
+
92
+ changes = store.architecture_changes
93
+ expect(changes.first[:removed]).to eq([:Memory])
94
+ end
95
+
96
+ it 'skips snapshots with no changes' do
97
+ 3.times { |i| store.store(model.merge(assembled_at: Time.now.utc + i)) }
98
+ expect(store.architecture_changes).to eq([])
99
+ end
100
+ end
101
+
102
+ describe '#health_trend' do
103
+ it 'extracts health scores from snapshots' do
104
+ 3.times do |i|
105
+ store.store(model.merge(
106
+ cognitive: { health: 0.8 + (i * 0.05) },
107
+ assembled_at: Time.now.utc + i
108
+ ))
109
+ end
110
+ trend = store.health_trend(limit: 3)
111
+ expect(trend.size).to eq(3)
112
+ expect(trend.last[:health]).to eq(0.9)
113
+ end
114
+
115
+ it 'skips snapshots without health' do
116
+ store.store(model.merge(cognitive: { status: :no_tick_data }, assembled_at: Time.now.utc))
117
+ expect(store.health_trend).to eq([])
118
+ end
119
+ end
120
+
121
+ describe '#clear' do
122
+ it 'removes all snapshots' do
123
+ store.store(model)
124
+ store.clear
125
+ expect(store.size).to eq(0)
126
+ end
127
+ end
128
+ end