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,307 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Self::NarrativeIdentity::Helpers::NarrativeEngine do
4
+ let(:engine) { described_class.new }
5
+
6
+ def add_ep(content: 'test event', type: :achievement, valence: 0.5, sig: 0.5, domain: 'test')
7
+ engine.add_episode(
8
+ content: content,
9
+ episode_type: type,
10
+ emotional_valence: valence,
11
+ significance: sig,
12
+ domain: domain
13
+ )
14
+ end
15
+
16
+ describe '#add_episode' do
17
+ it 'creates and stores an episode' do
18
+ ep = add_ep
19
+ expect(engine.episodes[ep.id]).to eq(ep)
20
+ end
21
+
22
+ it 'returns an Episode object' do
23
+ expect(add_ep).to be_a(Legion::Extensions::Agentic::Self::NarrativeIdentity::Helpers::Episode)
24
+ end
25
+
26
+ it 'increments episode count' do
27
+ 3.times { add_ep }
28
+ expect(engine.episodes.size).to eq(3)
29
+ end
30
+
31
+ it 'prunes oldest episode when MAX_EPISODES reached' do
32
+ max = Legion::Extensions::Agentic::Self::NarrativeIdentity::Helpers::Constants::MAX_EPISODES
33
+ first_ep = add_ep(content: 'oldest')
34
+ (max - 1).times { |i| add_ep(content: "ep #{i}") }
35
+ expect(engine.episodes.size).to eq(max)
36
+ add_ep(content: 'newest overflow')
37
+ expect(engine.episodes.size).to eq(max)
38
+ expect(engine.episodes.key?(first_ep.id)).to be false
39
+ end
40
+ end
41
+
42
+ describe '#create_chapter' do
43
+ it 'creates and stores a chapter' do
44
+ ch = engine.create_chapter(title: 'Act One', label: :origin)
45
+ expect(engine.chapters[ch.id]).to eq(ch)
46
+ end
47
+
48
+ it 'returns a Chapter object' do
49
+ expect(engine.create_chapter(title: 'x', label: :growth)).to be_a(
50
+ Legion::Extensions::Agentic::Self::NarrativeIdentity::Helpers::Chapter
51
+ )
52
+ end
53
+ end
54
+
55
+ describe '#assign_to_chapter' do
56
+ it 'assigns episode to chapter bidirectionally' do
57
+ ep = add_ep
58
+ ch = engine.create_chapter(title: 'Act One', label: :origin)
59
+ result = engine.assign_to_chapter(episode_id: ep.id, chapter_id: ch.id)
60
+ expect(result).to be true
61
+ expect(ep.chapter_id).to eq(ch.id)
62
+ expect(ch.episode_ids).to include(ep.id)
63
+ end
64
+
65
+ it 'returns false for unknown episode' do
66
+ ch = engine.create_chapter(title: 'x', label: :current)
67
+ expect(engine.assign_to_chapter(episode_id: 'no-such', chapter_id: ch.id)).to be false
68
+ end
69
+
70
+ it 'returns false for unknown chapter' do
71
+ ep = add_ep
72
+ expect(engine.assign_to_chapter(episode_id: ep.id, chapter_id: 'no-such')).to be false
73
+ end
74
+
75
+ it 'does not duplicate episode_id in chapter' do
76
+ ep = add_ep
77
+ ch = engine.create_chapter(title: 'x', label: :current)
78
+ engine.assign_to_chapter(episode_id: ep.id, chapter_id: ch.id)
79
+ engine.assign_to_chapter(episode_id: ep.id, chapter_id: ch.id)
80
+ expect(ch.episode_ids.count(ep.id)).to eq(1)
81
+ end
82
+ end
83
+
84
+ describe '#close_chapter' do
85
+ it 'sets end_time on the chapter' do
86
+ ch = engine.create_chapter(title: 'x', label: :growth)
87
+ expect(ch.current?).to be true
88
+ result = engine.close_chapter(chapter_id: ch.id)
89
+ expect(result).to be true
90
+ expect(ch.current?).to be false
91
+ expect(ch.end_time).to be_a(Time)
92
+ end
93
+
94
+ it 'returns false for unknown chapter' do
95
+ expect(engine.close_chapter(chapter_id: 'missing')).to be false
96
+ end
97
+ end
98
+
99
+ describe '#add_theme' do
100
+ it 'creates and stores a theme' do
101
+ t = engine.add_theme(name: 'resilience', theme_type: :growth)
102
+ expect(engine.themes[t.id]).to eq(t)
103
+ end
104
+
105
+ it 'returns a Theme object' do
106
+ expect(engine.add_theme(name: 'x', theme_type: :agency)).to be_a(
107
+ Legion::Extensions::Agentic::Self::NarrativeIdentity::Helpers::Theme
108
+ )
109
+ end
110
+ end
111
+
112
+ describe '#link_theme' do
113
+ it 'links theme to episode bidirectionally' do
114
+ ep = add_ep
115
+ t = engine.add_theme(name: 'growth', theme_type: :growth)
116
+ result = engine.link_theme(episode_id: ep.id, theme_id: t.id)
117
+ expect(result).to be true
118
+ expect(ep.themes).to include(t.id)
119
+ expect(t.episode_ids).to include(ep.id)
120
+ end
121
+
122
+ it 'returns false for unknown episode' do
123
+ t = engine.add_theme(name: 'x', theme_type: :agency)
124
+ expect(engine.link_theme(episode_id: 'no-ep', theme_id: t.id)).to be false
125
+ end
126
+
127
+ it 'returns false for unknown theme' do
128
+ ep = add_ep
129
+ expect(engine.link_theme(episode_id: ep.id, theme_id: 'no-theme')).to be false
130
+ end
131
+
132
+ it 'does not duplicate theme_id on episode' do
133
+ ep = add_ep
134
+ t = engine.add_theme(name: 'x', theme_type: :growth)
135
+ engine.link_theme(episode_id: ep.id, theme_id: t.id)
136
+ engine.link_theme(episode_id: ep.id, theme_id: t.id)
137
+ expect(ep.themes.count(t.id)).to eq(1)
138
+ end
139
+ end
140
+
141
+ describe '#reinforce_theme' do
142
+ it 'increases theme strength' do
143
+ t = engine.add_theme(name: 'courage', theme_type: :agency)
144
+ expect { engine.reinforce_theme(theme_id: t.id, amount: 0.3) }
145
+ .to change { t.strength }.by(0.3)
146
+ end
147
+
148
+ it 'returns false for unknown theme' do
149
+ expect(engine.reinforce_theme(theme_id: 'ghost', amount: 0.1)).to be false
150
+ end
151
+ end
152
+
153
+ describe '#narrative_coherence' do
154
+ it 'returns 0.0 with no episodes' do
155
+ expect(engine.narrative_coherence).to eq(0.0)
156
+ end
157
+
158
+ it 'returns 0.0 with no themes' do
159
+ add_ep
160
+ expect(engine.narrative_coherence).to eq(0.0)
161
+ end
162
+
163
+ it 'returns > 0 when episodes are linked to themes' do
164
+ ep = add_ep
165
+ t = engine.add_theme(name: 'x', theme_type: :growth)
166
+ engine.link_theme(episode_id: ep.id, theme_id: t.id)
167
+ engine.reinforce_theme(theme_id: t.id, amount: 0.7)
168
+ expect(engine.narrative_coherence).to be > 0.0
169
+ end
170
+
171
+ it 'returns a value between 0 and 1' do
172
+ 5.times do
173
+ ep = add_ep
174
+ t = engine.add_theme(name: "theme #{ep.id}", theme_type: :growth)
175
+ engine.link_theme(episode_id: ep.id, theme_id: t.id)
176
+ engine.reinforce_theme(theme_id: t.id, amount: 0.8)
177
+ end
178
+ score = engine.narrative_coherence
179
+ expect(score).to be >= 0.0
180
+ expect(score).to be <= 1.0
181
+ end
182
+ end
183
+
184
+ describe '#most_defining_episodes' do
185
+ it 'returns episodes sorted by significance descending' do
186
+ add_ep(sig: 0.3)
187
+ add_ep(sig: 0.9)
188
+ add_ep(sig: 0.5)
189
+ result = engine.most_defining_episodes(limit: 3)
190
+ sigs = result.map(&:significance)
191
+ expect(sigs).to eq(sigs.sort.reverse)
192
+ end
193
+
194
+ it 'respects limit' do
195
+ 5.times { add_ep(sig: rand) }
196
+ expect(engine.most_defining_episodes(limit: 3).size).to eq(3)
197
+ end
198
+
199
+ it 'returns fewer than limit when not enough episodes' do
200
+ 2.times { add_ep }
201
+ expect(engine.most_defining_episodes(limit: 5).size).to eq(2)
202
+ end
203
+ end
204
+
205
+ describe '#prominent_themes' do
206
+ it 'returns only themes with strength >= 0.6' do
207
+ strong = engine.add_theme(name: 'strong', theme_type: :agency)
208
+ engine.reinforce_theme(theme_id: strong.id, amount: 0.7)
209
+ weak = engine.add_theme(name: 'weak', theme_type: :stability)
210
+ engine.reinforce_theme(theme_id: weak.id, amount: 0.3)
211
+ result = engine.prominent_themes
212
+ expect(result.map(&:id)).to include(strong.id)
213
+ expect(result.map(&:id)).not_to include(weak.id)
214
+ end
215
+
216
+ it 'returns empty array when no prominent themes' do
217
+ engine.add_theme(name: 'faint', theme_type: :stability)
218
+ expect(engine.prominent_themes).to eq([])
219
+ end
220
+ end
221
+
222
+ describe '#current_chapter' do
223
+ it 'returns nil when no chapters exist' do
224
+ expect(engine.current_chapter).to be_nil
225
+ end
226
+
227
+ it 'returns the chapter with no end_time' do
228
+ ch1 = engine.create_chapter(title: 'Past', label: :origin)
229
+ engine.close_chapter(chapter_id: ch1.id)
230
+ ch2 = engine.create_chapter(title: 'Now', label: :current)
231
+ expect(engine.current_chapter).to eq(ch2)
232
+ end
233
+ end
234
+
235
+ describe '#decay_all_themes!' do
236
+ it 'reduces strength on all themes' do
237
+ t1 = engine.add_theme(name: 'a', theme_type: :growth)
238
+ engine.reinforce_theme(theme_id: t1.id, amount: 0.5)
239
+ t2 = engine.add_theme(name: 'b', theme_type: :agency)
240
+ engine.reinforce_theme(theme_id: t2.id, amount: 0.8)
241
+ s1_before = t1.strength
242
+ s2_before = t2.strength
243
+ engine.decay_all_themes!
244
+ decay = Legion::Extensions::Agentic::Self::NarrativeIdentity::Helpers::Constants::COHERENCE_DECAY
245
+ expect(t1.strength).to be_within(0.001).of(s1_before - decay)
246
+ expect(t2.strength).to be_within(0.001).of(s2_before - decay)
247
+ end
248
+ end
249
+
250
+ describe '#identity_summary' do
251
+ it 'returns expected keys' do
252
+ summary = engine.identity_summary
253
+ expect(summary).to include(
254
+ :top_themes, :defining_episodes, :current_chapter,
255
+ :coherence, :coherence_label, :episode_count, :theme_count, :chapter_count
256
+ )
257
+ end
258
+
259
+ it 'coherence_label is a symbol' do
260
+ expect(engine.identity_summary[:coherence_label]).to be_a(Symbol)
261
+ end
262
+
263
+ it 'reflects episode count' do
264
+ 3.times { add_ep }
265
+ expect(engine.identity_summary[:episode_count]).to eq(3)
266
+ end
267
+ end
268
+
269
+ describe '#life_story' do
270
+ it 'returns an array of chapter entries' do
271
+ ch = engine.create_chapter(title: 'Act I', label: :origin)
272
+ ep = add_ep
273
+ engine.assign_to_chapter(episode_id: ep.id, chapter_id: ch.id)
274
+ story = engine.life_story
275
+ expect(story).to be_an(Array)
276
+ expect(story.first[:chapter][:id]).to eq(ch.id)
277
+ expect(story.first[:episodes].map { |e| e[:id] }).to include(ep.id)
278
+ end
279
+
280
+ it 'returns empty array when no chapters' do
281
+ expect(engine.life_story).to eq([])
282
+ end
283
+ end
284
+
285
+ describe '#narrative_report' do
286
+ it 'returns expected top-level keys' do
287
+ report = engine.narrative_report
288
+ expect(report).to include(:identity_summary, :life_story, :narrative_state)
289
+ end
290
+
291
+ it 'narrative_state includes coherence' do
292
+ expect(engine.narrative_report[:narrative_state]).to include(:coherence, :coherence_label)
293
+ end
294
+ end
295
+
296
+ describe '#to_h' do
297
+ it 'serializes episodes, themes, and chapters' do
298
+ add_ep
299
+ engine.add_theme(name: 't', theme_type: :growth)
300
+ engine.create_chapter(title: 'ch', label: :current)
301
+ h = engine.to_h
302
+ expect(h[:episodes].size).to eq(1)
303
+ expect(h[:themes].size).to eq(1)
304
+ expect(h[:chapters].size).to eq(1)
305
+ end
306
+ end
307
+ end
@@ -0,0 +1,107 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Self::NarrativeIdentity::Helpers::Theme do
4
+ let(:theme) do
5
+ described_class.new(name: 'perseverance', theme_type: :growth, strength: 0.5)
6
+ end
7
+
8
+ describe '#initialize' do
9
+ it 'assigns an id' do
10
+ expect(theme.id).to be_a(String)
11
+ expect(theme.id.length).to eq(36)
12
+ end
13
+
14
+ it 'assigns name' do
15
+ expect(theme.name).to eq('perseverance')
16
+ end
17
+
18
+ it 'assigns theme_type' do
19
+ expect(theme.theme_type).to eq(:growth)
20
+ end
21
+
22
+ it 'assigns strength' do
23
+ expect(theme.strength).to eq(0.5)
24
+ end
25
+
26
+ it 'initializes episode_ids as empty array' do
27
+ expect(theme.episode_ids).to eq([])
28
+ end
29
+
30
+ it 'clamps strength above 1.0' do
31
+ t = described_class.new(name: 'x', theme_type: :agency, strength: 2.0)
32
+ expect(t.strength).to eq(1.0)
33
+ end
34
+
35
+ it 'clamps strength below 0.0' do
36
+ t = described_class.new(name: 'x', theme_type: :agency, strength: -0.5)
37
+ expect(t.strength).to eq(0.0)
38
+ end
39
+
40
+ it 'accepts an explicit id' do
41
+ t = described_class.new(name: 'x', theme_type: :growth, id: 'my-id')
42
+ expect(t.id).to eq('my-id')
43
+ end
44
+ end
45
+
46
+ describe '#reinforce!' do
47
+ it 'increases strength' do
48
+ theme.reinforce!(0.2)
49
+ expect(theme.strength).to eq(0.7)
50
+ end
51
+
52
+ it 'clamps at 1.0' do
53
+ theme.reinforce!(0.8)
54
+ expect(theme.strength).to eq(1.0)
55
+ end
56
+
57
+ it 'rounds to 10 decimal places' do
58
+ t = described_class.new(name: 'x', theme_type: :growth, strength: 0.1)
59
+ t.reinforce!(0.2)
60
+ expect(t.strength).to be_a(Float)
61
+ expect(t.strength).to be_within(0.0000000001).of(0.3)
62
+ end
63
+ end
64
+
65
+ describe '#decay!' do
66
+ it 'decreases strength by COHERENCE_DECAY' do
67
+ decay = Legion::Extensions::Agentic::Self::NarrativeIdentity::Helpers::Constants::COHERENCE_DECAY
68
+ theme.decay!
69
+ expect(theme.strength).to be_within(0.0001).of(0.5 - decay)
70
+ end
71
+
72
+ it 'clamps at 0.0' do
73
+ weak = described_class.new(name: 'x', theme_type: :stability, strength: 0.005)
74
+ weak.decay!
75
+ expect(weak.strength).to eq(0.0)
76
+ end
77
+ end
78
+
79
+ describe '#prominent?' do
80
+ it 'returns true when strength >= 0.6' do
81
+ t = described_class.new(name: 'x', theme_type: :agency, strength: 0.6)
82
+ expect(t.prominent?).to be true
83
+ end
84
+
85
+ it 'returns false when strength < 0.6' do
86
+ expect(theme.prominent?).to be false
87
+ end
88
+
89
+ it 'returns true at exactly 0.6' do
90
+ t = described_class.new(name: 'x', theme_type: :agency, strength: 0.6)
91
+ expect(t.prominent?).to be true
92
+ end
93
+ end
94
+
95
+ describe '#to_h' do
96
+ it 'includes all fields' do
97
+ h = theme.to_h
98
+ expect(h).to include(:id, :name, :theme_type, :strength, :episode_ids)
99
+ end
100
+
101
+ it 'returns a copy of episode_ids' do
102
+ h = theme.to_h
103
+ h[:episode_ids] << 'injected'
104
+ expect(theme.episode_ids).to be_empty
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,240 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Self::NarrativeIdentity::Runners::NarrativeIdentity do
4
+ let(:engine) { Legion::Extensions::Agentic::Self::NarrativeIdentity::Helpers::NarrativeEngine.new }
5
+ let(:runner) { Object.new.extend(described_class) }
6
+
7
+ def record(content: 'did a thing', type: :achievement, valence: 0.5, sig: 0.6, domain: 'test')
8
+ runner.record_episode(
9
+ content: content,
10
+ episode_type: type,
11
+ emotional_valence: valence,
12
+ significance: sig,
13
+ domain: domain,
14
+ engine: engine
15
+ )
16
+ end
17
+
18
+ describe '#record_episode' do
19
+ it 'returns success: true' do
20
+ expect(record[:success]).to be true
21
+ end
22
+
23
+ it 'returns episode hash' do
24
+ result = record
25
+ expect(result[:episode]).to include(:id, :content, :episode_type, :significance)
26
+ end
27
+
28
+ it 'rejects unknown episode_type' do
29
+ result = runner.record_episode(
30
+ content: 'x', episode_type: :bogus, emotional_valence: 0.0, significance: 0.5,
31
+ domain: 'd', engine: engine
32
+ )
33
+ expect(result[:success]).to be false
34
+ expect(result[:error]).to include('unknown episode_type')
35
+ end
36
+
37
+ it 'stores the episode in the engine' do
38
+ result = record
39
+ expect(engine.episodes.key?(result[:episode][:id])).to be true
40
+ end
41
+ end
42
+
43
+ describe '#create_chapter' do
44
+ it 'returns success: true' do
45
+ result = runner.create_chapter(title: 'Act I', label: :origin, engine: engine)
46
+ expect(result[:success]).to be true
47
+ end
48
+
49
+ it 'returns chapter hash' do
50
+ result = runner.create_chapter(title: 'Act I', label: :growth, engine: engine)
51
+ expect(result[:chapter]).to include(:id, :title, :label)
52
+ end
53
+
54
+ it 'rejects unknown label' do
55
+ result = runner.create_chapter(title: 'bad', label: :invalid, engine: engine)
56
+ expect(result[:success]).to be false
57
+ expect(result[:error]).to include('unknown chapter label')
58
+ end
59
+ end
60
+
61
+ describe '#close_chapter' do
62
+ it 'closes an existing chapter' do
63
+ created = runner.create_chapter(title: 'Past', label: :origin, engine: engine)
64
+ ch_id = created[:chapter][:id]
65
+ result = runner.close_chapter(chapter_id: ch_id, engine: engine)
66
+ expect(result[:success]).to be true
67
+ end
68
+
69
+ it 'returns false for nonexistent chapter' do
70
+ result = runner.close_chapter(chapter_id: 'ghost-id', engine: engine)
71
+ expect(result[:success]).to be false
72
+ end
73
+ end
74
+
75
+ describe '#assign_episode_to_chapter' do
76
+ it 'links episode to chapter' do
77
+ ep = record
78
+ ch = runner.create_chapter(title: 'Now', label: :current, engine: engine)
79
+ res = runner.assign_episode_to_chapter(
80
+ episode_id: ep[:episode][:id],
81
+ chapter_id: ch[:chapter][:id],
82
+ engine: engine
83
+ )
84
+ expect(res[:success]).to be true
85
+ end
86
+
87
+ it 'returns false for unknown IDs' do
88
+ res = runner.assign_episode_to_chapter(
89
+ episode_id: 'no-ep', chapter_id: 'no-ch', engine: engine
90
+ )
91
+ expect(res[:success]).to be false
92
+ end
93
+ end
94
+
95
+ describe '#add_theme' do
96
+ it 'returns success: true' do
97
+ result = runner.add_theme(name: 'courage', theme_type: :growth, engine: engine)
98
+ expect(result[:success]).to be true
99
+ end
100
+
101
+ it 'returns theme hash' do
102
+ result = runner.add_theme(name: 'resilience', theme_type: :agency, engine: engine)
103
+ expect(result[:theme]).to include(:id, :name, :theme_type)
104
+ end
105
+
106
+ it 'rejects unknown theme_type' do
107
+ result = runner.add_theme(name: 'x', theme_type: :nonsense, engine: engine)
108
+ expect(result[:success]).to be false
109
+ expect(result[:error]).to include('unknown theme_type')
110
+ end
111
+ end
112
+
113
+ describe '#link_theme' do
114
+ it 'links theme and episode' do
115
+ ep = record
116
+ t = runner.add_theme(name: 'growth', theme_type: :growth, engine: engine)
117
+ r = runner.link_theme(episode_id: ep[:episode][:id], theme_id: t[:theme][:id], engine: engine)
118
+ expect(r[:success]).to be true
119
+ end
120
+
121
+ it 'returns false for missing episode or theme' do
122
+ r = runner.link_theme(episode_id: 'no-ep', theme_id: 'no-t', engine: engine)
123
+ expect(r[:success]).to be false
124
+ end
125
+ end
126
+
127
+ describe '#reinforce_theme' do
128
+ it 'increases theme strength' do
129
+ t = runner.add_theme(name: 'x', theme_type: :growth, engine: engine)
130
+ theme = engine.themes[t[:theme][:id]]
131
+ before = theme.strength
132
+ runner.reinforce_theme(theme_id: t[:theme][:id], amount: 0.3, engine: engine)
133
+ expect(theme.strength).to be > before
134
+ end
135
+
136
+ it 'returns false for unknown theme' do
137
+ r = runner.reinforce_theme(theme_id: 'ghost', amount: 0.1, engine: engine)
138
+ expect(r[:success]).to be false
139
+ end
140
+ end
141
+
142
+ describe '#narrative_coherence' do
143
+ it 'returns success: true with coherence value' do
144
+ result = runner.narrative_coherence(engine: engine)
145
+ expect(result[:success]).to be true
146
+ expect(result[:coherence]).to be_a(Float)
147
+ end
148
+ end
149
+
150
+ describe '#identity_summary' do
151
+ it 'returns success: true with summary' do
152
+ result = runner.identity_summary(engine: engine)
153
+ expect(result[:success]).to be true
154
+ expect(result[:summary]).to include(:episode_count, :theme_count)
155
+ end
156
+ end
157
+
158
+ describe '#life_story' do
159
+ it 'returns success: true with life_story array' do
160
+ result = runner.life_story(engine: engine)
161
+ expect(result[:success]).to be true
162
+ expect(result[:life_story]).to be_an(Array)
163
+ end
164
+ end
165
+
166
+ describe '#most_defining_episodes' do
167
+ it 'returns success: true with episodes array' do
168
+ result = runner.most_defining_episodes(limit: 3, engine: engine)
169
+ expect(result[:success]).to be true
170
+ expect(result[:episodes]).to be_an(Array)
171
+ end
172
+
173
+ it 'respects the limit' do
174
+ 5.times { record }
175
+ result = runner.most_defining_episodes(limit: 3, engine: engine)
176
+ expect(result[:episodes].size).to be <= 3
177
+ end
178
+ end
179
+
180
+ describe '#prominent_themes' do
181
+ it 'returns success: true with themes array' do
182
+ result = runner.prominent_themes(engine: engine)
183
+ expect(result[:success]).to be true
184
+ expect(result[:themes]).to be_an(Array)
185
+ end
186
+ end
187
+
188
+ describe '#current_chapter' do
189
+ it 'returns success: true' do
190
+ result = runner.current_chapter(engine: engine)
191
+ expect(result[:success]).to be true
192
+ end
193
+
194
+ it 'returns nil chapter when none exists' do
195
+ expect(runner.current_chapter(engine: engine)[:chapter]).to be_nil
196
+ end
197
+
198
+ it 'returns the open chapter' do
199
+ runner.create_chapter(title: 'Now', label: :current, engine: engine)
200
+ result = runner.current_chapter(engine: engine)
201
+ expect(result[:chapter]).not_to be_nil
202
+ end
203
+ end
204
+
205
+ describe '#decay_themes' do
206
+ it 'returns success: true' do
207
+ result = runner.decay_themes(engine: engine)
208
+ expect(result[:success]).to be true
209
+ end
210
+
211
+ it 'applies decay to all themes' do
212
+ t = runner.add_theme(name: 'x', theme_type: :growth, engine: engine)
213
+ runner.reinforce_theme(theme_id: t[:theme][:id], amount: 0.5, engine: engine)
214
+ theme = engine.themes[t[:theme][:id]]
215
+ before = theme.strength
216
+ runner.decay_themes(engine: engine)
217
+ expect(theme.strength).to be < before
218
+ end
219
+ end
220
+
221
+ describe '#narrative_report' do
222
+ it 'returns success: true with report' do
223
+ result = runner.narrative_report(engine: engine)
224
+ expect(result[:success]).to be true
225
+ expect(result[:report]).to include(:identity_summary, :life_story, :narrative_state)
226
+ end
227
+ end
228
+
229
+ describe 'default engine memoization' do
230
+ it 'uses a shared default engine across calls when no engine injected' do
231
+ r = Object.new.extend(described_class)
232
+ r.record_episode(
233
+ content: 'event', episode_type: :achievement,
234
+ emotional_valence: 0.5, significance: 0.5, domain: 'd'
235
+ )
236
+ summary = r.identity_summary
237
+ expect(summary[:summary][:episode_count]).to eq(1)
238
+ end
239
+ end
240
+ end