lex-agentic-integration 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 (236) 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-integration.gemspec +30 -0
  7. data/lib/legion/extensions/agentic/integration/boundary/client.rb +15 -0
  8. data/lib/legion/extensions/agentic/integration/boundary/helpers/boundary.rb +90 -0
  9. data/lib/legion/extensions/agentic/integration/boundary/helpers/boundary_engine.rb +123 -0
  10. data/lib/legion/extensions/agentic/integration/boundary/helpers/constants.rb +44 -0
  11. data/lib/legion/extensions/agentic/integration/boundary/runners/cognitive_boundary.rb +100 -0
  12. data/lib/legion/extensions/agentic/integration/boundary/version.rb +13 -0
  13. data/lib/legion/extensions/agentic/integration/boundary.rb +19 -0
  14. data/lib/legion/extensions/agentic/integration/context/client.rb +26 -0
  15. data/lib/legion/extensions/agentic/integration/context/helpers/constants.rb +34 -0
  16. data/lib/legion/extensions/agentic/integration/context/helpers/context_manager.rb +150 -0
  17. data/lib/legion/extensions/agentic/integration/context/helpers/frame.rb +99 -0
  18. data/lib/legion/extensions/agentic/integration/context/runners/context.rb +94 -0
  19. data/lib/legion/extensions/agentic/integration/context/version.rb +13 -0
  20. data/lib/legion/extensions/agentic/integration/context.rb +19 -0
  21. data/lib/legion/extensions/agentic/integration/distributed_cognition/client.rb +19 -0
  22. data/lib/legion/extensions/agentic/integration/distributed_cognition/helpers/constants.rb +51 -0
  23. data/lib/legion/extensions/agentic/integration/distributed_cognition/helpers/distribution_engine.rb +159 -0
  24. data/lib/legion/extensions/agentic/integration/distributed_cognition/helpers/participant.rb +100 -0
  25. data/lib/legion/extensions/agentic/integration/distributed_cognition/runners/distributed_cognition.rb +107 -0
  26. data/lib/legion/extensions/agentic/integration/distributed_cognition/version.rb +13 -0
  27. data/lib/legion/extensions/agentic/integration/distributed_cognition.rb +19 -0
  28. data/lib/legion/extensions/agentic/integration/gestalt/client.rb +21 -0
  29. data/lib/legion/extensions/agentic/integration/gestalt/helpers/constants.rb +51 -0
  30. data/lib/legion/extensions/agentic/integration/gestalt/helpers/pattern.rb +90 -0
  31. data/lib/legion/extensions/agentic/integration/gestalt/helpers/pattern_store.rb +123 -0
  32. data/lib/legion/extensions/agentic/integration/gestalt/runners/gestalt.rb +82 -0
  33. data/lib/legion/extensions/agentic/integration/gestalt/version.rb +13 -0
  34. data/lib/legion/extensions/agentic/integration/gestalt.rb +19 -0
  35. data/lib/legion/extensions/agentic/integration/global_workspace/actors/competition.rb +45 -0
  36. data/lib/legion/extensions/agentic/integration/global_workspace/client.rb +29 -0
  37. data/lib/legion/extensions/agentic/integration/global_workspace/helpers/broadcast.rb +62 -0
  38. data/lib/legion/extensions/agentic/integration/global_workspace/helpers/competitor.rb +59 -0
  39. data/lib/legion/extensions/agentic/integration/global_workspace/helpers/constants.rb +65 -0
  40. data/lib/legion/extensions/agentic/integration/global_workspace/helpers/workspace.rb +188 -0
  41. data/lib/legion/extensions/agentic/integration/global_workspace/runners/global_workspace.rb +104 -0
  42. data/lib/legion/extensions/agentic/integration/global_workspace/version.rb +13 -0
  43. data/lib/legion/extensions/agentic/integration/global_workspace.rb +20 -0
  44. data/lib/legion/extensions/agentic/integration/integration/client.rb +19 -0
  45. data/lib/legion/extensions/agentic/integration/integration/helpers/constants.rb +62 -0
  46. data/lib/legion/extensions/agentic/integration/integration/helpers/integrated_representation.rb +106 -0
  47. data/lib/legion/extensions/agentic/integration/integration/helpers/integration_engine.rb +163 -0
  48. data/lib/legion/extensions/agentic/integration/integration/helpers/modal_signal.rb +62 -0
  49. data/lib/legion/extensions/agentic/integration/integration/runners/cognitive_integration.rb +105 -0
  50. data/lib/legion/extensions/agentic/integration/integration/version.rb +13 -0
  51. data/lib/legion/extensions/agentic/integration/integration.rb +20 -0
  52. data/lib/legion/extensions/agentic/integration/labyrinth/actors/thread_walker.rb +45 -0
  53. data/lib/legion/extensions/agentic/integration/labyrinth/client.rb +23 -0
  54. data/lib/legion/extensions/agentic/integration/labyrinth/helpers/constants.rb +39 -0
  55. data/lib/legion/extensions/agentic/integration/labyrinth/helpers/labyrinth.rb +138 -0
  56. data/lib/legion/extensions/agentic/integration/labyrinth/helpers/labyrinth_engine.rb +177 -0
  57. data/lib/legion/extensions/agentic/integration/labyrinth/helpers/node.rb +64 -0
  58. data/lib/legion/extensions/agentic/integration/labyrinth/runners/cognitive_labyrinth.rb +128 -0
  59. data/lib/legion/extensions/agentic/integration/labyrinth/version.rb +13 -0
  60. data/lib/legion/extensions/agentic/integration/labyrinth.rb +22 -0
  61. data/lib/legion/extensions/agentic/integration/map/actors/decay.rb +45 -0
  62. data/lib/legion/extensions/agentic/integration/map/client.rb +29 -0
  63. data/lib/legion/extensions/agentic/integration/map/helpers/cognitive_map_store.rb +179 -0
  64. data/lib/legion/extensions/agentic/integration/map/helpers/constants.rb +71 -0
  65. data/lib/legion/extensions/agentic/integration/map/helpers/graph_traversal.rb +124 -0
  66. data/lib/legion/extensions/agentic/integration/map/helpers/location.rb +71 -0
  67. data/lib/legion/extensions/agentic/integration/map/runners/cognitive_map.rb +95 -0
  68. data/lib/legion/extensions/agentic/integration/map/version.rb +13 -0
  69. data/lib/legion/extensions/agentic/integration/map.rb +20 -0
  70. data/lib/legion/extensions/agentic/integration/mosaic/client.rb +15 -0
  71. data/lib/legion/extensions/agentic/integration/mosaic/helpers/constants.rb +55 -0
  72. data/lib/legion/extensions/agentic/integration/mosaic/helpers/mosaic.rb +111 -0
  73. data/lib/legion/extensions/agentic/integration/mosaic/helpers/mosaic_engine.rb +124 -0
  74. data/lib/legion/extensions/agentic/integration/mosaic/helpers/tessera.rb +86 -0
  75. data/lib/legion/extensions/agentic/integration/mosaic/runners/cognitive_mosaic.rb +76 -0
  76. data/lib/legion/extensions/agentic/integration/mosaic/version.rb +13 -0
  77. data/lib/legion/extensions/agentic/integration/mosaic.rb +22 -0
  78. data/lib/legion/extensions/agentic/integration/mycelium/client.rb +19 -0
  79. data/lib/legion/extensions/agentic/integration/mycelium/helpers/constants.rb +52 -0
  80. data/lib/legion/extensions/agentic/integration/mycelium/helpers/fruiting_body.rb +50 -0
  81. data/lib/legion/extensions/agentic/integration/mycelium/helpers/hypha.rb +76 -0
  82. data/lib/legion/extensions/agentic/integration/mycelium/helpers/mycelial_node.rb +72 -0
  83. data/lib/legion/extensions/agentic/integration/mycelium/helpers/mycelium_engine.rb +151 -0
  84. data/lib/legion/extensions/agentic/integration/mycelium/runners/cognitive_mycelium.rb +73 -0
  85. data/lib/legion/extensions/agentic/integration/mycelium/version.rb +13 -0
  86. data/lib/legion/extensions/agentic/integration/mycelium.rb +23 -0
  87. data/lib/legion/extensions/agentic/integration/phenomenal_binding/client.rb +25 -0
  88. data/lib/legion/extensions/agentic/integration/phenomenal_binding/helpers/binding_engine.rb +156 -0
  89. data/lib/legion/extensions/agentic/integration/phenomenal_binding/helpers/binding_unit.rb +72 -0
  90. data/lib/legion/extensions/agentic/integration/phenomenal_binding/helpers/constants.rb +35 -0
  91. data/lib/legion/extensions/agentic/integration/phenomenal_binding/helpers/stream.rb +51 -0
  92. data/lib/legion/extensions/agentic/integration/phenomenal_binding/runners/phenomenal_binding.rb +110 -0
  93. data/lib/legion/extensions/agentic/integration/phenomenal_binding/version.rb +13 -0
  94. data/lib/legion/extensions/agentic/integration/phenomenal_binding.rb +20 -0
  95. data/lib/legion/extensions/agentic/integration/qualia/client.rb +19 -0
  96. data/lib/legion/extensions/agentic/integration/qualia/helpers/constants.rb +74 -0
  97. data/lib/legion/extensions/agentic/integration/qualia/helpers/quale.rb +103 -0
  98. data/lib/legion/extensions/agentic/integration/qualia/helpers/qualia_engine.rb +142 -0
  99. data/lib/legion/extensions/agentic/integration/qualia/runners/qualia.rb +67 -0
  100. data/lib/legion/extensions/agentic/integration/qualia/version.rb +13 -0
  101. data/lib/legion/extensions/agentic/integration/qualia.rb +19 -0
  102. data/lib/legion/extensions/agentic/integration/situation_model/client.rb +28 -0
  103. data/lib/legion/extensions/agentic/integration/situation_model/helpers/client.rb +23 -0
  104. data/lib/legion/extensions/agentic/integration/situation_model/helpers/constants.rb +40 -0
  105. data/lib/legion/extensions/agentic/integration/situation_model/helpers/situation_engine.rb +73 -0
  106. data/lib/legion/extensions/agentic/integration/situation_model/helpers/situation_event.rb +56 -0
  107. data/lib/legion/extensions/agentic/integration/situation_model/helpers/situation_model.rb +90 -0
  108. data/lib/legion/extensions/agentic/integration/situation_model/runners/situation_model.rb +99 -0
  109. data/lib/legion/extensions/agentic/integration/situation_model/version.rb +13 -0
  110. data/lib/legion/extensions/agentic/integration/situation_model.rb +21 -0
  111. data/lib/legion/extensions/agentic/integration/synthesis/client.rb +29 -0
  112. data/lib/legion/extensions/agentic/integration/synthesis/helpers/constants.rb +41 -0
  113. data/lib/legion/extensions/agentic/integration/synthesis/helpers/synthesis.rb +63 -0
  114. data/lib/legion/extensions/agentic/integration/synthesis/helpers/synthesis_engine.rb +213 -0
  115. data/lib/legion/extensions/agentic/integration/synthesis/helpers/synthesis_stream.rb +67 -0
  116. data/lib/legion/extensions/agentic/integration/synthesis/runners/cognitive_synthesis.rb +82 -0
  117. data/lib/legion/extensions/agentic/integration/synthesis/version.rb +13 -0
  118. data/lib/legion/extensions/agentic/integration/synthesis.rb +20 -0
  119. data/lib/legion/extensions/agentic/integration/tapestry/client.rb +15 -0
  120. data/lib/legion/extensions/agentic/integration/tapestry/helpers/constants.rb +46 -0
  121. data/lib/legion/extensions/agentic/integration/tapestry/helpers/loom_engine.rb +125 -0
  122. data/lib/legion/extensions/agentic/integration/tapestry/helpers/tapestry.rb +144 -0
  123. data/lib/legion/extensions/agentic/integration/tapestry/helpers/thread.rb +112 -0
  124. data/lib/legion/extensions/agentic/integration/tapestry/runners/cognitive_tapestry.rb +81 -0
  125. data/lib/legion/extensions/agentic/integration/tapestry/version.rb +13 -0
  126. data/lib/legion/extensions/agentic/integration/tapestry.rb +22 -0
  127. data/lib/legion/extensions/agentic/integration/tessellation/client.rb +19 -0
  128. data/lib/legion/extensions/agentic/integration/tessellation/helpers/constants.rb +72 -0
  129. data/lib/legion/extensions/agentic/integration/tessellation/helpers/mosaic.rb +99 -0
  130. data/lib/legion/extensions/agentic/integration/tessellation/helpers/tessellation_engine.rb +141 -0
  131. data/lib/legion/extensions/agentic/integration/tessellation/helpers/tile.rb +76 -0
  132. data/lib/legion/extensions/agentic/integration/tessellation/runners/cognitive_tessellation.rb +58 -0
  133. data/lib/legion/extensions/agentic/integration/tessellation/version.rb +13 -0
  134. data/lib/legion/extensions/agentic/integration/tessellation.rb +22 -0
  135. data/lib/legion/extensions/agentic/integration/version.rb +11 -0
  136. data/lib/legion/extensions/agentic/integration/zeitgeist/helpers/client.rb +17 -0
  137. data/lib/legion/extensions/agentic/integration/zeitgeist/helpers/cognitive_signal.rb +38 -0
  138. data/lib/legion/extensions/agentic/integration/zeitgeist/helpers/constants.rb +64 -0
  139. data/lib/legion/extensions/agentic/integration/zeitgeist/helpers/trend_window.rb +80 -0
  140. data/lib/legion/extensions/agentic/integration/zeitgeist/helpers/zeitgeist_engine.rb +157 -0
  141. data/lib/legion/extensions/agentic/integration/zeitgeist/runners/cognitive_zeitgeist.rb +99 -0
  142. data/lib/legion/extensions/agentic/integration/zeitgeist/version.rb +13 -0
  143. data/lib/legion/extensions/agentic/integration/zeitgeist.rb +22 -0
  144. data/lib/legion/extensions/agentic/integration.rb +34 -0
  145. data/spec/legion/extensions/agentic/integration/boundary/helpers/boundary_engine_spec.rb +165 -0
  146. data/spec/legion/extensions/agentic/integration/boundary/helpers/boundary_spec.rb +168 -0
  147. data/spec/legion/extensions/agentic/integration/boundary/helpers/constants_spec.rb +65 -0
  148. data/spec/legion/extensions/agentic/integration/boundary/runners/cognitive_boundary_spec.rb +111 -0
  149. data/spec/legion/extensions/agentic/integration/context/client_spec.rb +58 -0
  150. data/spec/legion/extensions/agentic/integration/context/helpers/constants_spec.rb +32 -0
  151. data/spec/legion/extensions/agentic/integration/context/helpers/context_manager_spec.rb +190 -0
  152. data/spec/legion/extensions/agentic/integration/context/helpers/frame_spec.rb +164 -0
  153. data/spec/legion/extensions/agentic/integration/context/runners/context_spec.rb +142 -0
  154. data/spec/legion/extensions/agentic/integration/distributed_cognition/client_spec.rb +21 -0
  155. data/spec/legion/extensions/agentic/integration/distributed_cognition/helpers/distribution_engine_spec.rb +149 -0
  156. data/spec/legion/extensions/agentic/integration/distributed_cognition/helpers/participant_spec.rb +116 -0
  157. data/spec/legion/extensions/agentic/integration/distributed_cognition/runners/distributed_cognition_spec.rb +102 -0
  158. data/spec/legion/extensions/agentic/integration/gestalt/client_spec.rb +53 -0
  159. data/spec/legion/extensions/agentic/integration/gestalt/helpers/constants_spec.rb +27 -0
  160. data/spec/legion/extensions/agentic/integration/gestalt/helpers/pattern_spec.rb +128 -0
  161. data/spec/legion/extensions/agentic/integration/gestalt/helpers/pattern_store_spec.rb +155 -0
  162. data/spec/legion/extensions/agentic/integration/gestalt/runners/gestalt_spec.rb +114 -0
  163. data/spec/legion/extensions/agentic/integration/global_workspace/client_spec.rb +49 -0
  164. data/spec/legion/extensions/agentic/integration/global_workspace/helpers/broadcast_spec.rb +83 -0
  165. data/spec/legion/extensions/agentic/integration/global_workspace/helpers/competitor_spec.rb +84 -0
  166. data/spec/legion/extensions/agentic/integration/global_workspace/helpers/workspace_spec.rb +174 -0
  167. data/spec/legion/extensions/agentic/integration/global_workspace/runners/global_workspace_spec.rb +118 -0
  168. data/spec/legion/extensions/agentic/integration/integration/cognitive_integration_spec.rb +7 -0
  169. data/spec/legion/extensions/agentic/integration/integration/helpers/integrated_representation_spec.rb +149 -0
  170. data/spec/legion/extensions/agentic/integration/integration/helpers/integration_engine_spec.rb +191 -0
  171. data/spec/legion/extensions/agentic/integration/integration/helpers/modal_signal_spec.rb +81 -0
  172. data/spec/legion/extensions/agentic/integration/labyrinth/client_spec.rb +122 -0
  173. data/spec/legion/extensions/agentic/integration/labyrinth/cognitive_labyrinth_spec.rb +19 -0
  174. data/spec/legion/extensions/agentic/integration/labyrinth/helpers/constants_spec.rb +72 -0
  175. data/spec/legion/extensions/agentic/integration/labyrinth/helpers/labyrinth_engine_spec.rb +222 -0
  176. data/spec/legion/extensions/agentic/integration/labyrinth/helpers/labyrinth_spec.rb +235 -0
  177. data/spec/legion/extensions/agentic/integration/labyrinth/helpers/node_spec.rb +157 -0
  178. data/spec/legion/extensions/agentic/integration/labyrinth/runners/cognitive_labyrinth_spec.rb +209 -0
  179. data/spec/legion/extensions/agentic/integration/map/client_spec.rb +89 -0
  180. data/spec/legion/extensions/agentic/integration/map/helpers/cognitive_map_store_spec.rb +321 -0
  181. data/spec/legion/extensions/agentic/integration/map/helpers/location_spec.rb +165 -0
  182. data/spec/legion/extensions/agentic/integration/map/runners/cognitive_map_spec.rb +190 -0
  183. data/spec/legion/extensions/agentic/integration/mosaic/client_spec.rb +16 -0
  184. data/spec/legion/extensions/agentic/integration/mosaic/helpers/constants_spec.rb +37 -0
  185. data/spec/legion/extensions/agentic/integration/mosaic/helpers/mosaic_engine_spec.rb +108 -0
  186. data/spec/legion/extensions/agentic/integration/mosaic/helpers/mosaic_spec.rb +120 -0
  187. data/spec/legion/extensions/agentic/integration/mosaic/helpers/tessera_spec.rb +92 -0
  188. data/spec/legion/extensions/agentic/integration/mosaic/runners/cognitive_mosaic_spec.rb +88 -0
  189. data/spec/legion/extensions/agentic/integration/mycelium/client_spec.rb +25 -0
  190. data/spec/legion/extensions/agentic/integration/mycelium/cognitive_mycelium_spec.rb +7 -0
  191. data/spec/legion/extensions/agentic/integration/mycelium/helpers/constants_spec.rb +28 -0
  192. data/spec/legion/extensions/agentic/integration/mycelium/helpers/fruiting_body_spec.rb +33 -0
  193. data/spec/legion/extensions/agentic/integration/mycelium/helpers/hypha_spec.rb +69 -0
  194. data/spec/legion/extensions/agentic/integration/mycelium/helpers/mycelial_node_spec.rb +79 -0
  195. data/spec/legion/extensions/agentic/integration/mycelium/helpers/mycelium_engine_spec.rb +136 -0
  196. data/spec/legion/extensions/agentic/integration/mycelium/runners/cognitive_mycelium_spec.rb +61 -0
  197. data/spec/legion/extensions/agentic/integration/phenomenal_binding/client_spec.rb +52 -0
  198. data/spec/legion/extensions/agentic/integration/phenomenal_binding/helpers/binding_engine_spec.rb +290 -0
  199. data/spec/legion/extensions/agentic/integration/phenomenal_binding/helpers/binding_unit_spec.rb +195 -0
  200. data/spec/legion/extensions/agentic/integration/phenomenal_binding/helpers/constants_spec.rb +92 -0
  201. data/spec/legion/extensions/agentic/integration/phenomenal_binding/helpers/stream_spec.rb +128 -0
  202. data/spec/legion/extensions/agentic/integration/phenomenal_binding/runners/phenomenal_binding_spec.rb +174 -0
  203. data/spec/legion/extensions/agentic/integration/qualia/client_spec.rb +21 -0
  204. data/spec/legion/extensions/agentic/integration/qualia/helpers/quale_spec.rb +168 -0
  205. data/spec/legion/extensions/agentic/integration/qualia/helpers/qualia_engine_spec.rb +174 -0
  206. data/spec/legion/extensions/agentic/integration/qualia/qualia_spec.rb +7 -0
  207. data/spec/legion/extensions/agentic/integration/qualia/runners_spec.rb +70 -0
  208. data/spec/legion/extensions/agentic/integration/situation_model/client_spec.rb +51 -0
  209. data/spec/legion/extensions/agentic/integration/situation_model/helpers/constants_spec.rb +56 -0
  210. data/spec/legion/extensions/agentic/integration/situation_model/helpers/situation_engine_spec.rb +203 -0
  211. data/spec/legion/extensions/agentic/integration/situation_model/helpers/situation_event_spec.rb +94 -0
  212. data/spec/legion/extensions/agentic/integration/situation_model/helpers/situation_model_spec.rb +235 -0
  213. data/spec/legion/extensions/agentic/integration/situation_model/runners/situation_model_spec.rb +204 -0
  214. data/spec/legion/extensions/agentic/integration/synthesis/client_spec.rb +26 -0
  215. data/spec/legion/extensions/agentic/integration/synthesis/helpers/constants_spec.rb +87 -0
  216. data/spec/legion/extensions/agentic/integration/synthesis/helpers/synthesis_engine_spec.rb +233 -0
  217. data/spec/legion/extensions/agentic/integration/synthesis/helpers/synthesis_spec.rb +101 -0
  218. data/spec/legion/extensions/agentic/integration/synthesis/helpers/synthesis_stream_spec.rb +116 -0
  219. data/spec/legion/extensions/agentic/integration/synthesis/runners/cognitive_synthesis_spec.rb +181 -0
  220. data/spec/legion/extensions/agentic/integration/tapestry/client_spec.rb +55 -0
  221. data/spec/legion/extensions/agentic/integration/tapestry/helpers/constants_spec.rb +88 -0
  222. data/spec/legion/extensions/agentic/integration/tapestry/helpers/loom_engine_spec.rb +225 -0
  223. data/spec/legion/extensions/agentic/integration/tapestry/helpers/tapestry_spec.rb +229 -0
  224. data/spec/legion/extensions/agentic/integration/tapestry/helpers/thread_spec.rb +211 -0
  225. data/spec/legion/extensions/agentic/integration/tapestry/runners/cognitive_tapestry_spec.rb +171 -0
  226. data/spec/legion/extensions/agentic/integration/tessellation/client_spec.rb +40 -0
  227. data/spec/legion/extensions/agentic/integration/tessellation/helpers/mosaic_spec.rb +99 -0
  228. data/spec/legion/extensions/agentic/integration/tessellation/helpers/tessellation_engine_spec.rb +145 -0
  229. data/spec/legion/extensions/agentic/integration/tessellation/helpers/tile_spec.rb +138 -0
  230. data/spec/legion/extensions/agentic/integration/zeitgeist/helpers/cognitive_signal_spec.rb +62 -0
  231. data/spec/legion/extensions/agentic/integration/zeitgeist/helpers/constants_spec.rb +73 -0
  232. data/spec/legion/extensions/agentic/integration/zeitgeist/helpers/trend_window_spec.rb +100 -0
  233. data/spec/legion/extensions/agentic/integration/zeitgeist/helpers/zeitgeist_engine_spec.rb +163 -0
  234. data/spec/legion/extensions/agentic/integration/zeitgeist/runners/cognitive_zeitgeist_spec.rb +165 -0
  235. data/spec/spec_helper.rb +46 -0
  236. metadata +320 -0
@@ -0,0 +1,116 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Integration::Synthesis::Helpers::SynthesisStream do
4
+ subject(:stream) do
5
+ described_class.new(stream_type: :emotional, content: { mood: 'alert' }, weight: 0.8, confidence: 0.9)
6
+ end
7
+
8
+ describe '#initialize' do
9
+ it 'assigns a UUID id' do
10
+ expect(stream.id).to match(/\A[0-9a-f-]{36}\z/)
11
+ end
12
+
13
+ it 'sets stream_type' do
14
+ expect(stream.stream_type).to eq(:emotional)
15
+ end
16
+
17
+ it 'sets content' do
18
+ expect(stream.content).to eq({ mood: 'alert' })
19
+ end
20
+
21
+ it 'clamps weight to 0-1' do
22
+ s = described_class.new(stream_type: :perceptual, content: {}, weight: 1.5)
23
+ expect(s.weight).to eq(1.0)
24
+ end
25
+
26
+ it 'clamps confidence to 0-1' do
27
+ s = described_class.new(stream_type: :perceptual, content: {}, confidence: -0.1)
28
+ expect(s.confidence).to eq(0.0)
29
+ end
30
+
31
+ it 'initializes freshness at 1.0' do
32
+ expect(stream.freshness).to eq(1.0)
33
+ end
34
+
35
+ it 'sets created_at as UTC time' do
36
+ expect(stream.created_at).to be_a(Time)
37
+ end
38
+ end
39
+
40
+ describe '#decay_freshness!' do
41
+ it 'reduces freshness by FRESHNESS_DECAY' do
42
+ before = stream.freshness
43
+ stream.decay_freshness!
44
+ expect(stream.freshness).to be_within(0.001).of(before - 0.02)
45
+ end
46
+
47
+ it 'never drops below 0.0' do
48
+ 60.times { stream.decay_freshness! }
49
+ expect(stream.freshness).to eq(0.0)
50
+ end
51
+ end
52
+
53
+ describe '#stale?' do
54
+ it 'returns false when freshness is high' do
55
+ expect(stream.stale?).to be false
56
+ end
57
+
58
+ it 'returns true when freshness drops below 0.1' do
59
+ 46.times { stream.decay_freshness! }
60
+ expect(stream.stale?).to be true
61
+ end
62
+ end
63
+
64
+ describe '#effective_weight' do
65
+ it 'equals weight * freshness * confidence' do
66
+ expected = (0.8 * 1.0 * 0.9).round(10)
67
+ expect(stream.effective_weight).to eq(expected)
68
+ end
69
+
70
+ it 'decreases after decay' do
71
+ before = stream.effective_weight
72
+ stream.decay_freshness!
73
+ expect(stream.effective_weight).to be < before
74
+ end
75
+ end
76
+
77
+ describe '#coherence_label' do
78
+ it 'returns :unified for weight 0.9' do
79
+ s = described_class.new(stream_type: :memorial, content: {}, weight: 0.9)
80
+ expect(s.coherence_label).to eq(:unified)
81
+ end
82
+
83
+ it 'returns :chaotic for weight 0.1' do
84
+ s = described_class.new(stream_type: :memorial, content: {}, weight: 0.1)
85
+ expect(s.coherence_label).to eq(:chaotic)
86
+ end
87
+ end
88
+
89
+ describe '#confidence_label' do
90
+ it 'returns :certain for confidence 0.95' do
91
+ s = described_class.new(stream_type: :memorial, content: {}, confidence: 0.95)
92
+ expect(s.confidence_label).to eq(:certain)
93
+ end
94
+
95
+ it 'returns :guessing for confidence 0.1' do
96
+ s = described_class.new(stream_type: :memorial, content: {}, confidence: 0.1)
97
+ expect(s.confidence_label).to eq(:guessing)
98
+ end
99
+ end
100
+
101
+ describe '#to_h' do
102
+ let(:hash) { stream.to_h }
103
+
104
+ it 'includes all expected keys' do
105
+ %i[id stream_type content weight confidence freshness effective_weight
106
+ stale coherence_label confidence_label created_at].each do |key|
107
+ expect(hash).to have_key(key)
108
+ end
109
+ end
110
+
111
+ it 'reflects current freshness' do
112
+ stream.decay_freshness!
113
+ expect(stream.to_h[:freshness]).to be < 1.0
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,181 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'legion/extensions/agentic/integration/synthesis/client'
4
+
5
+ RSpec.describe Legion::Extensions::Agentic::Integration::Synthesis::Runners::CognitiveSynthesis do
6
+ let(:client) { Legion::Extensions::Agentic::Integration::Synthesis::Client.new }
7
+
8
+ def add_two_streams
9
+ client.add_stream(stream_type: :emotional, content: { mood: :alert }, weight: 0.8, confidence: 0.9)
10
+ client.add_stream(stream_type: :perceptual, content: { threat: :detected }, weight: 0.6, confidence: 0.7)
11
+ end
12
+
13
+ describe '#add_stream' do
14
+ it 'adds a valid stream and returns success' do
15
+ result = client.add_stream(stream_type: :emotional, content: { mood: :calm })
16
+ expect(result[:success]).to be true
17
+ expect(result[:stream_id]).to match(/\A[0-9a-f-]{36}\z/)
18
+ end
19
+
20
+ it 'rejects an invalid stream type' do
21
+ result = client.add_stream(stream_type: :bogus, content: {})
22
+ expect(result[:success]).to be false
23
+ expect(result[:error]).to eq(:invalid_stream_type)
24
+ end
25
+
26
+ it 'accepts all valid stream types' do
27
+ %i[emotional perceptual memorial predictive reasoning social identity motor].each do |type|
28
+ result = client.add_stream(stream_type: type, content: {})
29
+ expect(result[:success]).to be true
30
+ end
31
+ end
32
+
33
+ it 'accepts injected engine kwarg' do
34
+ engine = Legion::Extensions::Agentic::Integration::Synthesis::Helpers::SynthesisEngine.new
35
+ result = client.add_stream(stream_type: :reasoning, content: {}, engine: engine)
36
+ expect(result[:success]).to be true
37
+ expect(engine.streams.size).to eq(1)
38
+ end
39
+ end
40
+
41
+ describe '#remove_stream' do
42
+ it 'removes an existing stream' do
43
+ add_result = client.add_stream(stream_type: :emotional, content: {})
44
+ remove_result = client.remove_stream(stream_id: add_result[:stream_id])
45
+ expect(remove_result[:success]).to be true
46
+ end
47
+
48
+ it 'returns not_found for unknown stream' do
49
+ result = client.remove_stream(stream_id: 'nonexistent-id')
50
+ expect(result[:success]).to be false
51
+ expect(result[:error]).to eq(:not_found)
52
+ end
53
+ end
54
+
55
+ describe '#synthesize' do
56
+ it 'returns insufficient_streams when fewer than 2 active streams' do
57
+ client.add_stream(stream_type: :emotional, content: {})
58
+ result = client.synthesize
59
+ expect(result[:success]).to be false
60
+ expect(result[:error]).to eq(:insufficient_streams)
61
+ end
62
+
63
+ it 'succeeds with 2+ active streams' do
64
+ add_two_streams
65
+ result = client.synthesize
66
+ expect(result[:success]).to be true
67
+ expect(result[:synthesis]).to have_key(:id)
68
+ end
69
+
70
+ it 'returns coherence between 0 and 1' do
71
+ add_two_streams
72
+ result = client.synthesize
73
+ expect(result[:synthesis][:coherence]).to be_between(0.0, 1.0)
74
+ end
75
+
76
+ it 'returns novelty = 1.0 for first synthesis' do
77
+ add_two_streams
78
+ result = client.synthesize
79
+ expect(result[:synthesis][:novelty]).to eq(1.0)
80
+ end
81
+
82
+ it 'uses injected engine when provided' do
83
+ engine = Legion::Extensions::Agentic::Integration::Synthesis::Helpers::SynthesisEngine.new
84
+ 2.times { engine.add_stream(stream_type: :emotional, content: {}) }
85
+ result = client.synthesize(engine: engine)
86
+ expect(result[:success]).to be true
87
+ end
88
+ end
89
+
90
+ describe '#decay_streams' do
91
+ it 'returns success' do
92
+ result = client.decay_streams
93
+ expect(result[:success]).to be true
94
+ end
95
+
96
+ it 'reports removed count' do
97
+ result = client.decay_streams
98
+ expect(result).to have_key(:streams_removed)
99
+ end
100
+ end
101
+
102
+ describe '#check_conflict' do
103
+ it 'detects conflict between opposing streams' do
104
+ a = client.add_stream(stream_type: :emotional, content: { signal: :danger }, weight: 0.9)
105
+ b = client.add_stream(stream_type: :perceptual, content: { signal: :safe }, weight: 0.1)
106
+ result = client.check_conflict(stream_id_a: a[:stream_id], stream_id_b: b[:stream_id])
107
+ expect(result[:success]).to be true
108
+ expect(result[:conflict]).to be true
109
+ end
110
+
111
+ it 'reports no conflict for similar streams' do
112
+ a = client.add_stream(stream_type: :emotional, content: { value: 1 }, weight: 0.6)
113
+ b = client.add_stream(stream_type: :perceptual, content: { value: 1 }, weight: 0.65)
114
+ result = client.check_conflict(stream_id_a: a[:stream_id], stream_id_b: b[:stream_id])
115
+ expect(result[:conflict]).to be false
116
+ end
117
+
118
+ it 'returns error for missing stream ids' do
119
+ result = client.check_conflict(stream_id_a: 'x', stream_id_b: 'y')
120
+ expect(result[:success]).to be false
121
+ end
122
+ end
123
+
124
+ describe '#dominant_stream' do
125
+ it 'returns error when no streams present' do
126
+ result = client.dominant_stream
127
+ expect(result[:success]).to be false
128
+ expect(result[:error]).to eq(:no_streams)
129
+ end
130
+
131
+ it 'returns stream with highest effective_weight' do
132
+ client.add_stream(stream_type: :emotional, content: {}, weight: 0.2, confidence: 0.2)
133
+ high = client.add_stream(stream_type: :memorial, content: {}, weight: 0.95, confidence: 0.95)
134
+ result = client.dominant_stream
135
+ expect(result[:stream][:id]).to eq(high[:stream_id])
136
+ end
137
+ end
138
+
139
+ describe '#synthesis_history' do
140
+ it 'returns empty list initially' do
141
+ result = client.synthesis_history
142
+ expect(result[:syntheses]).to be_empty
143
+ end
144
+
145
+ it 'returns syntheses after multiple runs' do
146
+ add_two_streams
147
+ 3.times { client.synthesize }
148
+ result = client.synthesis_history(limit: 2)
149
+ expect(result[:count]).to eq(2)
150
+ end
151
+ end
152
+
153
+ describe '#average_coherence' do
154
+ it 'returns 0.0 when no syntheses' do
155
+ result = client.average_coherence
156
+ expect(result[:average_coherence]).to eq(0.0)
157
+ end
158
+
159
+ it 'returns coherence value after syntheses' do
160
+ add_two_streams
161
+ 3.times { client.synthesize }
162
+ result = client.average_coherence
163
+ expect(result[:average_coherence]).to be_between(0.0, 1.0)
164
+ end
165
+ end
166
+
167
+ describe '#status' do
168
+ it 'returns success with stats' do
169
+ result = client.status
170
+ expect(result[:success]).to be true
171
+ expect(result).to have_key(:stream_count)
172
+ expect(result).to have_key(:synthesis_count)
173
+ end
174
+
175
+ it 'reflects added streams' do
176
+ add_two_streams
177
+ result = client.status
178
+ expect(result[:stream_count]).to eq(2)
179
+ end
180
+ end
181
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Integration::Tapestry::Client do
4
+ subject(:client) { described_class.new }
5
+
6
+ let(:engine) { Legion::Extensions::Agentic::Integration::Tapestry::Helpers::LoomEngine.new }
7
+
8
+ it 'includes Runners::CognitiveTapestry' do
9
+ expect(client).to respond_to(:spin_thread)
10
+ expect(client).to respond_to(:create_tapestry)
11
+ expect(client).to respond_to(:weave)
12
+ expect(client).to respond_to(:list_tapestries)
13
+ expect(client).to respond_to(:loom_status)
14
+ end
15
+
16
+ describe '#spin_thread via client' do
17
+ it 'returns a successful result' do
18
+ result = client.spin_thread(
19
+ thread_type: :memory, domain: :past, content: 'childhood memory', engine: engine
20
+ )
21
+ expect(result[:success]).to be true
22
+ end
23
+ end
24
+
25
+ describe '#create_tapestry via client' do
26
+ it 'returns a successful result' do
27
+ result = client.create_tapestry(name: 'autobiography', pattern: :twill, engine: engine)
28
+ expect(result[:success]).to be true
29
+ end
30
+ end
31
+
32
+ describe '#weave via client' do
33
+ it 'weaves thread into tapestry' do
34
+ t = engine.spin_thread(thread_type: :emotion, domain: :x, content: 'joy')
35
+ tap = engine.create_tapestry(name: 'canvas', pattern: :satin)
36
+ result = client.weave(thread_id: t.id, tapestry_id: tap.id, engine: engine)
37
+ expect(result[:success]).to be true
38
+ end
39
+ end
40
+
41
+ describe '#list_tapestries via client' do
42
+ it 'returns empty tapestries on fresh engine' do
43
+ result = client.list_tapestries(engine: engine)
44
+ expect(result[:count]).to eq(0)
45
+ end
46
+ end
47
+
48
+ describe '#loom_status via client' do
49
+ it 'returns status report' do
50
+ result = client.loom_status(engine: engine)
51
+ expect(result[:success]).to be true
52
+ expect(result[:report]).to be_a(Hash)
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Integration::Tapestry::Helpers::Constants do
4
+ describe 'THREAD_TYPES' do
5
+ it 'contains expected types' do
6
+ expect(described_class::THREAD_TYPES).to include(:experience, :belief, :memory, :emotion, :narrative)
7
+ end
8
+
9
+ it 'has five types' do
10
+ expect(described_class::THREAD_TYPES.size).to eq(5)
11
+ end
12
+
13
+ it 'is frozen' do
14
+ expect(described_class::THREAD_TYPES).to be_frozen
15
+ end
16
+ end
17
+
18
+ describe 'WEAVE_PATTERNS' do
19
+ it 'contains expected patterns' do
20
+ expect(described_class::WEAVE_PATTERNS).to include(:plain, :twill, :satin, :brocade)
21
+ end
22
+
23
+ it 'has four patterns' do
24
+ expect(described_class::WEAVE_PATTERNS.size).to eq(4)
25
+ end
26
+
27
+ it 'is frozen' do
28
+ expect(described_class::WEAVE_PATTERNS).to be_frozen
29
+ end
30
+ end
31
+
32
+ describe 'capacity constants' do
33
+ it 'defines MAX_THREADS as 500' do
34
+ expect(described_class::MAX_THREADS).to eq(500)
35
+ end
36
+
37
+ it 'defines MAX_TAPESTRIES as 50' do
38
+ expect(described_class::MAX_TAPESTRIES).to eq(50)
39
+ end
40
+
41
+ it 'defines TENSION_RATE as 0.05' do
42
+ expect(described_class::TENSION_RATE).to eq(0.05)
43
+ end
44
+
45
+ it 'defines FRAY_RATE as 0.03' do
46
+ expect(described_class::FRAY_RATE).to eq(0.03)
47
+ end
48
+ end
49
+
50
+ describe 'INTEGRITY_LABELS' do
51
+ it 'labels high strength as masterwork' do
52
+ expect(described_class.label_for(described_class::INTEGRITY_LABELS, 0.95)).to eq(:masterwork)
53
+ end
54
+
55
+ it 'labels near-zero strength as rags' do
56
+ expect(described_class.label_for(described_class::INTEGRITY_LABELS, 0.05)).to eq(:rags)
57
+ end
58
+
59
+ it 'labels mid strength as woven' do
60
+ expect(described_class.label_for(described_class::INTEGRITY_LABELS, 0.55)).to eq(:woven)
61
+ end
62
+
63
+ it 'labels fraying range correctly' do
64
+ expect(described_class.label_for(described_class::INTEGRITY_LABELS, 0.35)).to eq(:fraying)
65
+ end
66
+ end
67
+
68
+ describe 'COMPLEXITY_LABELS' do
69
+ it 'labels high complexity as baroque' do
70
+ expect(described_class.label_for(described_class::COMPLEXITY_LABELS, 0.95)).to eq(:baroque)
71
+ end
72
+
73
+ it 'labels low complexity as simple' do
74
+ expect(described_class.label_for(described_class::COMPLEXITY_LABELS, 0.1)).to eq(:simple)
75
+ end
76
+
77
+ it 'labels mid complexity as patterned' do
78
+ expect(described_class.label_for(described_class::COMPLEXITY_LABELS, 0.5)).to eq(:patterned)
79
+ end
80
+ end
81
+
82
+ describe '.label_for' do
83
+ it 'returns last label as fallback for out-of-range values' do
84
+ result = described_class.label_for(described_class::INTEGRITY_LABELS, -1.0)
85
+ expect(result).to eq(:rags)
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,225 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Integration::Tapestry::Helpers::LoomEngine do
4
+ subject(:engine) { described_class.new }
5
+
6
+ def spin(type: :experience, domain: :test, content: 'data', strength: 0.5)
7
+ engine.spin_thread(thread_type: type, domain: domain, content: content, strength: strength)
8
+ end
9
+
10
+ def create_tap(name: 'story', pattern: :plain, capacity: 10)
11
+ engine.create_tapestry(name: name, pattern: pattern, capacity: capacity)
12
+ end
13
+
14
+ describe '#spin_thread' do
15
+ it 'returns a Thread instance' do
16
+ expect(spin).to be_a(Legion::Extensions::Agentic::Integration::Tapestry::Helpers::Thread)
17
+ end
18
+
19
+ it 'stores the thread internally' do
20
+ t = spin
21
+ expect(engine.all_threads).to include(t)
22
+ end
23
+
24
+ it 'raises ArgumentError for invalid thread_type' do
25
+ expect { engine.spin_thread(thread_type: :bogus, domain: :x, content: 'y') }
26
+ .to raise_error(ArgumentError)
27
+ end
28
+
29
+ it 'raises ArgumentError when limit reached' do
30
+ stub_const('Legion::Extensions::Agentic::Integration::Tapestry::Helpers::Constants::MAX_THREADS', 1)
31
+ engine.spin_thread(thread_type: :belief, domain: :x, content: '1')
32
+ expect { engine.spin_thread(thread_type: :belief, domain: :x, content: '2') }
33
+ .to raise_error(ArgumentError, /limit/)
34
+ end
35
+ end
36
+
37
+ describe '#create_tapestry' do
38
+ it 'returns a Tapestry instance' do
39
+ expect(create_tap).to be_a(Legion::Extensions::Agentic::Integration::Tapestry::Helpers::Tapestry)
40
+ end
41
+
42
+ it 'stores the tapestry internally' do
43
+ tap = create_tap
44
+ expect(engine.all_tapestries).to include(tap)
45
+ end
46
+
47
+ it 'raises ArgumentError for invalid pattern' do
48
+ expect { engine.create_tapestry(name: 'x', pattern: :invalid) }
49
+ .to raise_error(ArgumentError)
50
+ end
51
+
52
+ it 'raises ArgumentError when limit reached' do
53
+ stub_const('Legion::Extensions::Agentic::Integration::Tapestry::Helpers::Constants::MAX_TAPESTRIES', 1)
54
+ engine.create_tapestry(name: 'one', pattern: :plain)
55
+ expect { engine.create_tapestry(name: 'two', pattern: :plain) }
56
+ .to raise_error(ArgumentError, /limit/)
57
+ end
58
+ end
59
+
60
+ describe '#weave' do
61
+ let(:t) { spin }
62
+ let(:tap) { create_tap }
63
+
64
+ it 'returns the Thread' do
65
+ result = engine.weave(thread_id: t.id, tapestry_id: tap.id)
66
+ expect(result).to be_a(Legion::Extensions::Agentic::Integration::Tapestry::Helpers::Thread)
67
+ end
68
+
69
+ it 'marks the thread as woven' do
70
+ engine.weave(thread_id: t.id, tapestry_id: tap.id)
71
+ expect(t.woven?).to be true
72
+ end
73
+
74
+ it 'adds thread id to tapestry' do
75
+ engine.weave(thread_id: t.id, tapestry_id: tap.id)
76
+ expect(tap.thread_ids).to include(t.id)
77
+ end
78
+
79
+ it 'raises ArgumentError when thread already woven' do
80
+ engine.weave(thread_id: t.id, tapestry_id: tap.id)
81
+ tap2 = create_tap(name: 'second', pattern: :twill)
82
+ expect { engine.weave(thread_id: t.id, tapestry_id: tap2.id) }
83
+ .to raise_error(ArgumentError, /already woven/)
84
+ end
85
+
86
+ it 'raises ArgumentError when tapestry is full' do
87
+ small_tap = engine.create_tapestry(name: 'tiny', pattern: :plain, capacity: 1)
88
+ t1 = spin(content: 'a')
89
+ t2 = spin(content: 'b')
90
+ engine.weave(thread_id: t1.id, tapestry_id: small_tap.id)
91
+ expect { engine.weave(thread_id: t2.id, tapestry_id: small_tap.id) }
92
+ .to raise_error(ArgumentError, /full/)
93
+ end
94
+
95
+ it 'raises ArgumentError for missing thread' do
96
+ expect { engine.weave(thread_id: 'nope', tapestry_id: tap.id) }
97
+ .to raise_error(ArgumentError, /thread not found/)
98
+ end
99
+
100
+ it 'raises ArgumentError for missing tapestry' do
101
+ expect { engine.weave(thread_id: t.id, tapestry_id: 'nope') }
102
+ .to raise_error(ArgumentError, /tapestry not found/)
103
+ end
104
+ end
105
+
106
+ describe '#unravel' do
107
+ let(:t) { spin }
108
+ let(:tap) { create_tap }
109
+
110
+ before { engine.weave(thread_id: t.id, tapestry_id: tap.id) }
111
+
112
+ it 'returns the Thread' do
113
+ result = engine.unravel(thread_id: t.id, tapestry_id: tap.id)
114
+ expect(result).to be_a(Legion::Extensions::Agentic::Integration::Tapestry::Helpers::Thread)
115
+ end
116
+
117
+ it 'marks thread as loose' do
118
+ engine.unravel(thread_id: t.id, tapestry_id: tap.id)
119
+ expect(t.loose?).to be true
120
+ end
121
+
122
+ it 'removes thread from tapestry' do
123
+ engine.unravel(thread_id: t.id, tapestry_id: tap.id)
124
+ expect(tap.thread_ids).not_to include(t.id)
125
+ end
126
+ end
127
+
128
+ describe '#fray_all!' do
129
+ it 'returns stats hash' do
130
+ spin
131
+ result = engine.fray_all!
132
+ expect(result).to have_key(:total)
133
+ expect(result).to have_key(:snapped)
134
+ end
135
+
136
+ it 'decreases thread strengths' do
137
+ t = spin(strength: 0.5)
138
+ engine.fray_all!
139
+ expect(t.strength).to be < 0.5
140
+ end
141
+
142
+ it 'counts snapped threads' do
143
+ t = spin(strength: 0.0)
144
+ result = engine.fray_all!
145
+ expect(result[:snapped]).to be >= 1
146
+ expect(t).to be_snap
147
+ end
148
+ end
149
+
150
+ describe '#most_complete' do
151
+ it 'returns tapestries sorted by completeness descending' do
152
+ tap1 = create_tap(name: 'a', pattern: :plain, capacity: 2)
153
+ create_tap(name: 'b', pattern: :twill, capacity: 10)
154
+ engine.spin_thread(thread_type: :experience, domain: :x, content: 'c').tap do |t|
155
+ engine.weave(thread_id: t.id, tapestry_id: tap1.id)
156
+ end
157
+ result = engine.most_complete(limit: 2)
158
+ expect(result.first.id).to eq(tap1.id)
159
+ end
160
+
161
+ it 'respects the limit' do
162
+ 3.times { |i| create_tap(name: "t#{i}", pattern: :plain) }
163
+ expect(engine.most_complete(limit: 2).size).to eq(2)
164
+ end
165
+ end
166
+
167
+ describe '#most_coherent' do
168
+ it 'returns tapestries' do
169
+ create_tap
170
+ result = engine.most_coherent(limit: 1)
171
+ expect(result).not_to be_empty
172
+ end
173
+ end
174
+
175
+ describe '#thread_inventory' do
176
+ it 'returns counts per thread type' do
177
+ spin(type: :belief)
178
+ spin(type: :memory)
179
+ inv = engine.thread_inventory
180
+ expect(inv[:belief]).to eq(1)
181
+ expect(inv[:memory]).to eq(1)
182
+ expect(inv[:experience]).to eq(0)
183
+ end
184
+ end
185
+
186
+ describe '#tapestry_report' do
187
+ it 'includes expected keys' do
188
+ result = engine.tapestry_report
189
+ %i[total_threads total_tapestries loose_count by_type fraying_count avg_completeness].each do |k|
190
+ expect(result).to have_key(k)
191
+ end
192
+ end
193
+
194
+ it 'counts loose threads' do
195
+ spin
196
+ expect(engine.tapestry_report[:loose_count]).to eq(1)
197
+ end
198
+ end
199
+
200
+ describe '#loose_threads' do
201
+ it 'returns unplaced threads' do
202
+ t = spin
203
+ expect(engine.loose_threads).to include(t)
204
+ end
205
+
206
+ it 'excludes woven threads' do
207
+ t = spin
208
+ tap = create_tap
209
+ engine.weave(thread_id: t.id, tapestry_id: tap.id)
210
+ expect(engine.loose_threads).not_to include(t)
211
+ end
212
+ end
213
+
214
+ describe '#all_threads and #all_tapestries' do
215
+ it 'returns all threads' do
216
+ spin
217
+ expect(engine.all_threads.size).to eq(1)
218
+ end
219
+
220
+ it 'returns all tapestries' do
221
+ create_tap
222
+ expect(engine.all_tapestries.size).to eq(1)
223
+ end
224
+ end
225
+ end