holosphere 2.0.0-alpha1 → 2.0.0-alpha4

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 (154) hide show
  1. package/dist/2019-D2OG2idw.js +6680 -0
  2. package/dist/2019-D2OG2idw.js.map +1 -0
  3. package/dist/2019-EION3wKo.cjs +8 -0
  4. package/dist/2019-EION3wKo.cjs.map +1 -0
  5. package/dist/_commonjsHelpers-C37NGDzP.cjs +2 -0
  6. package/dist/_commonjsHelpers-C37NGDzP.cjs.map +1 -0
  7. package/dist/_commonjsHelpers-CUmg6egw.js +7 -0
  8. package/dist/_commonjsHelpers-CUmg6egw.js.map +1 -0
  9. package/dist/browser-BSniCNqO.js +3058 -0
  10. package/dist/browser-BSniCNqO.js.map +1 -0
  11. package/dist/browser-Cq59Ij19.cjs +2 -0
  12. package/dist/browser-Cq59Ij19.cjs.map +1 -0
  13. package/dist/cjs/holosphere.cjs +2 -0
  14. package/dist/cjs/holosphere.cjs.map +1 -0
  15. package/dist/esm/holosphere.js +53 -0
  16. package/dist/esm/holosphere.js.map +1 -0
  17. package/dist/index-BB_vVJgv.cjs +5 -0
  18. package/dist/index-BB_vVJgv.cjs.map +1 -0
  19. package/dist/index-CBitK71M.cjs +12 -0
  20. package/dist/index-CBitK71M.cjs.map +1 -0
  21. package/dist/index-CV0eOogK.js +37423 -0
  22. package/dist/index-CV0eOogK.js.map +1 -0
  23. package/dist/index-Cz-PLCUR.js +15104 -0
  24. package/dist/index-Cz-PLCUR.js.map +1 -0
  25. package/dist/indexeddb-storage-CRsZyB2f.cjs +2 -0
  26. package/dist/indexeddb-storage-CRsZyB2f.cjs.map +1 -0
  27. package/dist/indexeddb-storage-DZaGlY_a.js +132 -0
  28. package/dist/indexeddb-storage-DZaGlY_a.js.map +1 -0
  29. package/dist/memory-storage-BkUi6sZG.js +51 -0
  30. package/dist/memory-storage-BkUi6sZG.js.map +1 -0
  31. package/dist/memory-storage-C0DuUsdY.cjs +2 -0
  32. package/dist/memory-storage-C0DuUsdY.cjs.map +1 -0
  33. package/dist/secp256k1-0kPdAVkK.cjs +12 -0
  34. package/dist/secp256k1-0kPdAVkK.cjs.map +1 -0
  35. package/dist/secp256k1-DN4FVXcv.js +1890 -0
  36. package/dist/secp256k1-DN4FVXcv.js.map +1 -0
  37. package/docs/CONTRACTS.md +797 -0
  38. package/docs/FOSDEM_PROPOSAL.md +388 -0
  39. package/docs/LOCALFIRST.md +266 -0
  40. package/docs/contracts/api-interface.md +793 -0
  41. package/docs/data-model.md +476 -0
  42. package/docs/gun-async-usage.md +338 -0
  43. package/docs/plan.md +349 -0
  44. package/docs/quickstart.md +674 -0
  45. package/docs/research.md +362 -0
  46. package/docs/spec.md +244 -0
  47. package/docs/storage-backends.md +326 -0
  48. package/docs/tasks.md +947 -0
  49. package/examples/demo.html +47 -0
  50. package/package.json +10 -5
  51. package/src/contracts/abis/Appreciative.json +1280 -0
  52. package/src/contracts/abis/AppreciativeFactory.json +101 -0
  53. package/src/contracts/abis/Bundle.json +1435 -0
  54. package/src/contracts/abis/BundleFactory.json +106 -0
  55. package/src/contracts/abis/Holon.json +881 -0
  56. package/src/contracts/abis/Holons.json +330 -0
  57. package/src/contracts/abis/Managed.json +1262 -0
  58. package/src/contracts/abis/ManagedFactory.json +149 -0
  59. package/src/contracts/abis/Membrane.json +261 -0
  60. package/src/contracts/abis/Splitter.json +1624 -0
  61. package/src/contracts/abis/SplitterFactory.json +220 -0
  62. package/src/contracts/abis/TestToken.json +321 -0
  63. package/src/contracts/abis/Zoned.json +1461 -0
  64. package/src/contracts/abis/ZonedFactory.json +154 -0
  65. package/src/contracts/chain-manager.js +375 -0
  66. package/src/contracts/deployer.js +443 -0
  67. package/src/contracts/event-listener.js +507 -0
  68. package/src/contracts/holon-contracts.js +344 -0
  69. package/src/contracts/index.js +83 -0
  70. package/src/contracts/networks.js +224 -0
  71. package/src/contracts/operations.js +670 -0
  72. package/src/contracts/queries.js +589 -0
  73. package/src/core/holosphere.js +453 -1
  74. package/src/crypto/nostr-utils.js +263 -0
  75. package/src/federation/handshake.js +455 -0
  76. package/src/federation/hologram.js +1 -1
  77. package/src/hierarchical/upcast.js +6 -5
  78. package/src/index.js +463 -1939
  79. package/src/lib/ai-methods.js +308 -0
  80. package/src/lib/contract-methods.js +293 -0
  81. package/src/lib/errors.js +23 -0
  82. package/src/lib/federation-methods.js +238 -0
  83. package/src/lib/index.js +26 -0
  84. package/src/spatial/h3-operations.js +2 -2
  85. package/src/storage/backends/gundb-backend.js +377 -46
  86. package/src/storage/global-tables.js +28 -1
  87. package/src/storage/gun-auth.js +303 -0
  88. package/src/storage/gun-federation.js +776 -0
  89. package/src/storage/gun-references.js +198 -0
  90. package/src/storage/gun-schema.js +291 -0
  91. package/src/storage/gun-wrapper.js +347 -31
  92. package/src/storage/indexeddb-storage.js +49 -11
  93. package/src/storage/memory-storage.js +5 -0
  94. package/src/storage/nostr-async.js +45 -23
  95. package/src/storage/nostr-client.js +11 -5
  96. package/src/storage/persistent-storage.js +6 -1
  97. package/src/storage/unified-storage.js +119 -0
  98. package/src/subscriptions/manager.js +1 -1
  99. package/types/index.d.ts +133 -0
  100. package/tests/unit/ai/aggregation.test.js +0 -295
  101. package/tests/unit/ai/breakdown.test.js +0 -446
  102. package/tests/unit/ai/classifier.test.js +0 -294
  103. package/tests/unit/ai/council.test.js +0 -262
  104. package/tests/unit/ai/embeddings.test.js +0 -384
  105. package/tests/unit/ai/federation-ai.test.js +0 -344
  106. package/tests/unit/ai/h3-ai.test.js +0 -458
  107. package/tests/unit/ai/index.test.js +0 -304
  108. package/tests/unit/ai/json-ops.test.js +0 -307
  109. package/tests/unit/ai/llm-service.test.js +0 -390
  110. package/tests/unit/ai/nl-query.test.js +0 -383
  111. package/tests/unit/ai/relationships.test.js +0 -311
  112. package/tests/unit/ai/schema-extractor.test.js +0 -384
  113. package/tests/unit/ai/spatial.test.js +0 -279
  114. package/tests/unit/ai/tts.test.js +0 -279
  115. package/tests/unit/content.test.js +0 -332
  116. package/tests/unit/contract/core.test.js +0 -88
  117. package/tests/unit/contract/crypto.test.js +0 -198
  118. package/tests/unit/contract/data.test.js +0 -223
  119. package/tests/unit/contract/federation.test.js +0 -181
  120. package/tests/unit/contract/hierarchical.test.js +0 -113
  121. package/tests/unit/contract/schema.test.js +0 -114
  122. package/tests/unit/contract/social.test.js +0 -217
  123. package/tests/unit/contract/spatial.test.js +0 -110
  124. package/tests/unit/contract/subscriptions.test.js +0 -128
  125. package/tests/unit/contract/utils.test.js +0 -159
  126. package/tests/unit/core.test.js +0 -152
  127. package/tests/unit/crypto.test.js +0 -328
  128. package/tests/unit/federation.test.js +0 -234
  129. package/tests/unit/gun-async.test.js +0 -252
  130. package/tests/unit/hierarchical.test.js +0 -399
  131. package/tests/unit/integration/scenario-01-geographic-storage.test.js +0 -74
  132. package/tests/unit/integration/scenario-02-federation.test.js +0 -76
  133. package/tests/unit/integration/scenario-03-subscriptions.test.js +0 -102
  134. package/tests/unit/integration/scenario-04-validation.test.js +0 -129
  135. package/tests/unit/integration/scenario-05-hierarchy.test.js +0 -125
  136. package/tests/unit/integration/scenario-06-social.test.js +0 -135
  137. package/tests/unit/integration/scenario-07-persistence.test.js +0 -130
  138. package/tests/unit/integration/scenario-08-authorization.test.js +0 -161
  139. package/tests/unit/integration/scenario-09-cross-dimensional.test.js +0 -139
  140. package/tests/unit/integration/scenario-10-cross-holosphere-capabilities.test.js +0 -357
  141. package/tests/unit/integration/scenario-11-cross-holosphere-federation.test.js +0 -410
  142. package/tests/unit/integration/scenario-12-capability-federated-read.test.js +0 -719
  143. package/tests/unit/performance/benchmark.test.js +0 -85
  144. package/tests/unit/schema.test.js +0 -213
  145. package/tests/unit/spatial.test.js +0 -158
  146. package/tests/unit/storage.test.js +0 -195
  147. package/tests/unit/subscriptions.test.js +0 -328
  148. package/tests/unit/test-data-permanence-debug.js +0 -197
  149. package/tests/unit/test-data-permanence.js +0 -340
  150. package/tests/unit/test-key-persistence-fixed.js +0 -148
  151. package/tests/unit/test-key-persistence.js +0 -172
  152. package/tests/unit/test-relay-permanence.js +0 -376
  153. package/tests/unit/test-second-node.js +0 -95
  154. package/tests/unit/test-simple-write.js +0 -89
@@ -1,383 +0,0 @@
1
- import { describe, it, expect, beforeEach, vi } from 'vitest';
2
- import { NLQuery } from '../../../src/ai/nl-query.js';
3
-
4
- describe('Unit: NLQuery', () => {
5
- let nlQuery;
6
- let mockLLM;
7
- let mockHolosphere;
8
-
9
- beforeEach(() => {
10
- vi.clearAllMocks();
11
-
12
- mockLLM = {
13
- getJSON: vi.fn(),
14
- sendMessage: vi.fn()
15
- };
16
-
17
- mockHolosphere = {
18
- getAll: vi.fn().mockResolvedValue([])
19
- };
20
-
21
- nlQuery = new NLQuery(mockLLM, mockHolosphere);
22
- });
23
-
24
- describe('Constructor', () => {
25
- it('should initialize with LLM service', () => {
26
- const nq = new NLQuery(mockLLM);
27
- expect(nq.llm).toBe(mockLLM);
28
- expect(nq.holosphere).toBeNull();
29
- });
30
-
31
- it('should accept optional HoloSphere instance', () => {
32
- expect(nlQuery.holosphere).toBe(mockHolosphere);
33
- });
34
- });
35
-
36
- describe('setHoloSphere', () => {
37
- it('should set HoloSphere instance', () => {
38
- const nq = new NLQuery(mockLLM);
39
- nq.setHoloSphere(mockHolosphere);
40
- expect(nq.holosphere).toBe(mockHolosphere);
41
- });
42
- });
43
-
44
- describe('parse', () => {
45
- it('should parse natural language query into structured format', async () => {
46
- mockLLM.getJSON.mockResolvedValue({
47
- holon: null,
48
- lens: 'projects',
49
- filters: { status: { op: 'eq', value: 'active' } },
50
- sort: { field: 'created_at', order: 'desc' },
51
- limit: 10,
52
- spatial: null,
53
- temporal: null
54
- });
55
-
56
- const result = await nlQuery.parse('Show me the 10 most recent active projects');
57
-
58
- expect(result.lens).toBe('projects');
59
- expect(result.filters.status.op).toBe('eq');
60
- expect(result.limit).toBe(10);
61
- });
62
-
63
- it('should include context in parsing', async () => {
64
- mockLLM.getJSON.mockResolvedValue({ holon: 'h1', lens: 'tasks' });
65
-
66
- await nlQuery.parse('Find tasks', {
67
- holons: ['h1', 'h2'],
68
- lenses: ['projects', 'tasks']
69
- });
70
-
71
- const call = mockLLM.getJSON.mock.calls[0];
72
- expect(call[0]).toContain('h1, h2');
73
- expect(call[0]).toContain('projects, tasks');
74
- });
75
-
76
- it('should parse spatial queries', async () => {
77
- mockLLM.getJSON.mockResolvedValue({
78
- lens: 'projects',
79
- spatial: { near: 'Rome', radius: '10km' }
80
- });
81
-
82
- const result = await nlQuery.parse('Show projects near Rome');
83
-
84
- expect(result.spatial.near).toBe('Rome');
85
- });
86
- });
87
-
88
- describe('execute', () => {
89
- it('should execute query and return results', async () => {
90
- mockLLM.getJSON.mockResolvedValue({
91
- holon: 'holon1',
92
- lens: 'projects',
93
- filters: null,
94
- sort: null,
95
- limit: null
96
- });
97
-
98
- mockHolosphere.getAll.mockResolvedValue([
99
- { id: 1, title: 'Project 1' },
100
- { id: 2, title: 'Project 2' }
101
- ]);
102
-
103
- const result = await nlQuery.execute('Show all projects');
104
-
105
- expect(result.query).toBe('Show all projects');
106
- expect(result.parsed.lens).toBe('projects');
107
- expect(result.results).toHaveLength(2);
108
- expect(result.count).toBe(2);
109
- });
110
-
111
- it('should throw error if HoloSphere not available', async () => {
112
- const nq = new NLQuery(mockLLM);
113
-
114
- await expect(nq.execute('query'))
115
- .rejects.toThrow('HoloSphere instance required for query execution');
116
- });
117
-
118
- it('should search across holons when only lens specified', async () => {
119
- mockLLM.getJSON.mockResolvedValue({
120
- holon: null,
121
- lens: 'tasks'
122
- });
123
-
124
- mockHolosphere.getAll
125
- .mockResolvedValueOnce([{ id: 1 }])
126
- .mockResolvedValueOnce([{ id: 2 }]);
127
-
128
- const result = await nlQuery.execute('Find all tasks', {
129
- holons: ['h1', 'h2']
130
- });
131
-
132
- expect(result.results).toHaveLength(2);
133
- expect(mockHolosphere.getAll).toHaveBeenCalledTimes(2);
134
- });
135
- });
136
-
137
- describe('_applyFilters', () => {
138
- it('should filter by equality', () => {
139
- const items = [
140
- { status: 'active' },
141
- { status: 'inactive' },
142
- { status: 'active' }
143
- ];
144
-
145
- const result = nlQuery._applyFilters(items, {
146
- status: { op: 'eq', value: 'active' }
147
- });
148
-
149
- expect(result).toHaveLength(2);
150
- });
151
-
152
- it('should filter by inequality', () => {
153
- const items = [
154
- { status: 'active' },
155
- { status: 'inactive' }
156
- ];
157
-
158
- const result = nlQuery._applyFilters(items, {
159
- status: { op: 'ne', value: 'inactive' }
160
- });
161
-
162
- expect(result).toHaveLength(1);
163
- expect(result[0].status).toBe('active');
164
- });
165
-
166
- it('should filter by greater than', () => {
167
- const items = [
168
- { priority: 1 },
169
- { priority: 5 },
170
- { priority: 10 }
171
- ];
172
-
173
- const result = nlQuery._applyFilters(items, {
174
- priority: { op: 'gt', value: 3 }
175
- });
176
-
177
- expect(result).toHaveLength(2);
178
- });
179
-
180
- it('should filter by greater than or equal', () => {
181
- const items = [
182
- { priority: 5 },
183
- { priority: 10 }
184
- ];
185
-
186
- const result = nlQuery._applyFilters(items, {
187
- priority: { op: 'gte', value: 5 }
188
- });
189
-
190
- expect(result).toHaveLength(2);
191
- });
192
-
193
- it('should filter by less than', () => {
194
- const items = [
195
- { priority: 1 },
196
- { priority: 5 }
197
- ];
198
-
199
- const result = nlQuery._applyFilters(items, {
200
- priority: { op: 'lt', value: 3 }
201
- });
202
-
203
- expect(result).toHaveLength(1);
204
- });
205
-
206
- it('should filter by less than or equal', () => {
207
- const items = [
208
- { priority: 1 },
209
- { priority: 3 }
210
- ];
211
-
212
- const result = nlQuery._applyFilters(items, {
213
- priority: { op: 'lte', value: 3 }
214
- });
215
-
216
- expect(result).toHaveLength(2);
217
- });
218
-
219
- it('should filter by contains (case insensitive)', () => {
220
- const items = [
221
- { title: 'Build Website' },
222
- { title: 'Write Documentation' },
223
- { title: 'Website Maintenance' }
224
- ];
225
-
226
- const result = nlQuery._applyFilters(items, {
227
- title: { op: 'contains', value: 'website' }
228
- });
229
-
230
- expect(result).toHaveLength(2);
231
- });
232
-
233
- it('should filter by in array', () => {
234
- const items = [
235
- { status: 'active' },
236
- { status: 'pending' },
237
- { status: 'completed' }
238
- ];
239
-
240
- const result = nlQuery._applyFilters(items, {
241
- status: { op: 'in', value: ['active', 'pending'] }
242
- });
243
-
244
- expect(result).toHaveLength(2);
245
- });
246
-
247
- it('should handle nested field paths', () => {
248
- const items = [
249
- { meta: { priority: 1 } },
250
- { meta: { priority: 5 } }
251
- ];
252
-
253
- const result = nlQuery._applyFilters(items, {
254
- 'meta.priority': { op: 'gt', value: 2 }
255
- });
256
-
257
- expect(result).toHaveLength(1);
258
- });
259
-
260
- it('should handle unknown operators', () => {
261
- const items = [{ value: 1 }];
262
-
263
- const result = nlQuery._applyFilters(items, {
264
- value: { op: 'unknown', value: 1 }
265
- });
266
-
267
- expect(result).toHaveLength(1);
268
- });
269
- });
270
-
271
- describe('_getNestedValue', () => {
272
- it('should get simple value', () => {
273
- const obj = { name: 'Test' };
274
- expect(nlQuery._getNestedValue(obj, 'name')).toBe('Test');
275
- });
276
-
277
- it('should get nested value', () => {
278
- const obj = { user: { profile: { name: 'Test' } } };
279
- expect(nlQuery._getNestedValue(obj, 'user.profile.name')).toBe('Test');
280
- });
281
-
282
- it('should return undefined for missing path', () => {
283
- const obj = { user: {} };
284
- expect(nlQuery._getNestedValue(obj, 'user.profile.name')).toBeUndefined();
285
- });
286
- });
287
-
288
- describe('_applySort', () => {
289
- it('should sort ascending', () => {
290
- const items = [
291
- { priority: 3 },
292
- { priority: 1 },
293
- { priority: 2 }
294
- ];
295
-
296
- const result = nlQuery._applySort(items, { field: 'priority', order: 'asc' });
297
-
298
- expect(result[0].priority).toBe(1);
299
- expect(result[2].priority).toBe(3);
300
- });
301
-
302
- it('should sort descending', () => {
303
- const items = [
304
- { priority: 1 },
305
- { priority: 3 },
306
- { priority: 2 }
307
- ];
308
-
309
- const result = nlQuery._applySort(items, { field: 'priority', order: 'desc' });
310
-
311
- expect(result[0].priority).toBe(3);
312
- expect(result[2].priority).toBe(1);
313
- });
314
-
315
- it('should handle string sorting', () => {
316
- const items = [
317
- { name: 'Charlie' },
318
- { name: 'Alice' },
319
- { name: 'Bob' }
320
- ];
321
-
322
- const result = nlQuery._applySort(items, { field: 'name', order: 'asc' });
323
-
324
- expect(result[0].name).toBe('Alice');
325
- expect(result[2].name).toBe('Charlie');
326
- });
327
-
328
- it('should not mutate original array', () => {
329
- const items = [{ v: 2 }, { v: 1 }];
330
- const original = [...items];
331
-
332
- nlQuery._applySort(items, { field: 'v', order: 'asc' });
333
-
334
- expect(items[0].v).toBe(original[0].v);
335
- });
336
- });
337
-
338
- describe('suggest', () => {
339
- it('should suggest queries based on context', async () => {
340
- mockLLM.getJSON.mockResolvedValue([
341
- 'Show all active projects',
342
- 'Find tasks due this week',
343
- 'List recent events'
344
- ]);
345
-
346
- const suggestions = await nlQuery.suggest({
347
- holons: ['region1'],
348
- lenses: ['projects', 'tasks', 'events']
349
- });
350
-
351
- expect(Array.isArray(suggestions)).toBe(true);
352
- expect(suggestions).toHaveLength(3);
353
- });
354
- });
355
-
356
- describe('explain', () => {
357
- it('should explain query results in natural language', async () => {
358
- mockLLM.sendMessage.mockResolvedValue('Found 5 active projects in the Rome region.');
359
-
360
- const result = await nlQuery.explain('Show projects near Rome', [
361
- { id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }, { id: 5 }
362
- ]);
363
-
364
- expect(result).toContain('5');
365
- expect(mockLLM.sendMessage).toHaveBeenCalledWith(
366
- expect.stringContaining('Show projects near Rome'),
367
- expect.any(String),
368
- expect.objectContaining({ temperature: 0.5 })
369
- );
370
- });
371
-
372
- it('should limit sample results to 5', async () => {
373
- mockLLM.sendMessage.mockResolvedValue('Summary');
374
-
375
- const manyResults = Array(20).fill({ id: 1 });
376
- await nlQuery.explain('Query', manyResults);
377
-
378
- const call = mockLLM.sendMessage.mock.calls[0];
379
- const resultSample = JSON.parse(call[1].replace('Results sample: ', ''));
380
- expect(resultSample).toHaveLength(5);
381
- });
382
- });
383
- });
@@ -1,311 +0,0 @@
1
- import { describe, it, expect, beforeEach, vi } from 'vitest';
2
- import { RelationshipDiscovery } from '../../../src/ai/relationships.js';
3
-
4
- describe('Unit: RelationshipDiscovery', () => {
5
- let relationships;
6
- let mockLLM;
7
- let mockHolosphere;
8
- let mockEmbeddings;
9
-
10
- beforeEach(() => {
11
- vi.clearAllMocks();
12
-
13
- mockLLM = {
14
- getJSON: vi.fn()
15
- };
16
-
17
- mockHolosphere = {
18
- getAll: vi.fn().mockResolvedValue([])
19
- };
20
-
21
- mockEmbeddings = {
22
- findSimilar: vi.fn().mockResolvedValue([])
23
- };
24
-
25
- relationships = new RelationshipDiscovery(mockLLM, mockHolosphere, mockEmbeddings);
26
- });
27
-
28
- describe('Constructor', () => {
29
- it('should initialize with LLM service', () => {
30
- const r = new RelationshipDiscovery(mockLLM);
31
- expect(r.llm).toBe(mockLLM);
32
- expect(r.holosphere).toBeNull();
33
- expect(r.embeddings).toBeNull();
34
- });
35
-
36
- it('should accept optional HoloSphere and Embeddings', () => {
37
- expect(relationships.holosphere).toBe(mockHolosphere);
38
- expect(relationships.embeddings).toBe(mockEmbeddings);
39
- });
40
- });
41
-
42
- describe('setHoloSphere', () => {
43
- it('should set HoloSphere instance', () => {
44
- const r = new RelationshipDiscovery(mockLLM);
45
- r.setHoloSphere(mockHolosphere);
46
- expect(r.holosphere).toBe(mockHolosphere);
47
- });
48
- });
49
-
50
- describe('setEmbeddings', () => {
51
- it('should set Embeddings instance', () => {
52
- const r = new RelationshipDiscovery(mockLLM);
53
- r.setEmbeddings(mockEmbeddings);
54
- expect(r.embeddings).toBe(mockEmbeddings);
55
- });
56
- });
57
-
58
- describe('discoverRelationships', () => {
59
- it('should discover relationships in holon data', async () => {
60
- mockHolosphere.getAll.mockResolvedValue([
61
- { id: 1, title: 'Project A', topic: 'sustainability' },
62
- { id: 2, title: 'Project B', topic: 'sustainability' },
63
- { id: 3, title: 'Project C', topic: 'tech' }
64
- ]);
65
-
66
- mockLLM.getJSON.mockResolvedValue({
67
- relationships: [
68
- {
69
- item1: { id: 1, title: 'Project A' },
70
- item2: { id: 2, title: 'Project B' },
71
- type: 'thematic',
72
- strength: 0.9,
73
- description: 'Both focus on sustainability'
74
- }
75
- ],
76
- clusters: [
77
- { theme: 'sustainability', items: [1, 2] }
78
- ],
79
- key_entities: ['sustainability', 'tech'],
80
- summary: 'Two sustainability projects are related'
81
- });
82
-
83
- const result = await relationships.discoverRelationships('holon1', 'projects');
84
-
85
- expect(result.holon).toBe('holon1');
86
- expect(result.lens).toBe('projects');
87
- expect(result.itemCount).toBe(3);
88
- expect(result.relationships.relationships).toHaveLength(1);
89
- });
90
-
91
- it('should throw error if HoloSphere not available', async () => {
92
- const r = new RelationshipDiscovery(mockLLM);
93
-
94
- await expect(r.discoverRelationships('holon', 'lens'))
95
- .rejects.toThrow('HoloSphere instance required');
96
- });
97
-
98
- it('should return message if not enough data', async () => {
99
- mockHolosphere.getAll.mockResolvedValue([{ id: 1 }]);
100
-
101
- const result = await relationships.discoverRelationships('holon1', 'lens');
102
-
103
- expect(result.relationships).toEqual([]);
104
- expect(result.message).toBe('Not enough data for relationship discovery');
105
- });
106
-
107
- it('should search multiple lenses when lens is null', async () => {
108
- mockHolosphere.getAll.mockResolvedValue([{ id: 1 }, { id: 2 }]);
109
- mockLLM.getJSON.mockResolvedValue({ relationships: [] });
110
-
111
- await relationships.discoverRelationships('holon1', null);
112
-
113
- expect(mockHolosphere.getAll.mock.calls.length).toBeGreaterThan(1);
114
- });
115
-
116
- it('should add _lens field to items', async () => {
117
- mockHolosphere.getAll.mockResolvedValue([{ id: 1 }, { id: 2 }]);
118
- mockLLM.getJSON.mockResolvedValue({ relationships: [] });
119
-
120
- await relationships.discoverRelationships('holon1', 'projects');
121
-
122
- const call = mockLLM.getJSON.mock.calls[0];
123
- expect(call[1]).toContain('projects');
124
- });
125
- });
126
-
127
- describe('findSimilar', () => {
128
- it('should use embeddings when available', async () => {
129
- mockEmbeddings.findSimilar.mockResolvedValue([
130
- { item: { id: 1 }, similarity: 0.9 }
131
- ]);
132
-
133
- const item = { title: 'Test' };
134
- const result = await relationships.findSimilar(item, 'holon1', 'lens1');
135
-
136
- expect(mockEmbeddings.findSimilar).toHaveBeenCalledWith(
137
- item,
138
- 'holon1',
139
- 'lens1',
140
- expect.objectContaining({ limit: 10, threshold: 0.5 })
141
- );
142
- expect(result).toHaveLength(1);
143
- });
144
-
145
- it('should fall back to LLM when embeddings not available', async () => {
146
- const r = new RelationshipDiscovery(mockLLM, mockHolosphere);
147
-
148
- mockHolosphere.getAll.mockResolvedValue([
149
- { id: 1, title: 'Similar' },
150
- { id: 2, title: 'Different' }
151
- ]);
152
-
153
- mockLLM.getJSON.mockResolvedValue([
154
- { item: { id: 1 }, similarity: 0.8, reasons: ['Same topic'] }
155
- ]);
156
-
157
- const result = await r.findSimilar({ title: 'Test' }, 'holon1', 'lens1');
158
-
159
- expect(result).toHaveLength(1);
160
- });
161
-
162
- it('should throw error if no holon for LLM-based similarity', async () => {
163
- const r = new RelationshipDiscovery(mockLLM, mockHolosphere);
164
-
165
- await expect(r.findSimilar({ title: 'Test' }, null))
166
- .rejects.toThrow('Holon required for LLM-based similarity');
167
- });
168
-
169
- it('should filter by threshold', async () => {
170
- const r = new RelationshipDiscovery(mockLLM, mockHolosphere);
171
-
172
- mockHolosphere.getAll.mockResolvedValue([{ id: 1 }]);
173
- mockLLM.getJSON.mockResolvedValue([
174
- { item: { id: 1 }, similarity: 0.8 },
175
- { item: { id: 2 }, similarity: 0.4 }
176
- ]);
177
-
178
- const result = await r.findSimilar({ title: 'Test' }, 'holon', 'lens', { threshold: 0.6 });
179
-
180
- expect(result).toHaveLength(1);
181
- });
182
-
183
- it('should skip embeddings when useEmbeddings is false', async () => {
184
- mockHolosphere.getAll.mockResolvedValue([{ id: 1 }]);
185
- mockLLM.getJSON.mockResolvedValue([]);
186
-
187
- await relationships.findSimilar({ title: 'Test' }, 'holon', 'lens', { useEmbeddings: false });
188
-
189
- expect(mockEmbeddings.findSimilar).not.toHaveBeenCalled();
190
- });
191
- });
192
-
193
- describe('buildGraph', () => {
194
- it('should build relationship graph structure', async () => {
195
- mockHolosphere.getAll.mockResolvedValue([{ id: 1 }, { id: 2 }]);
196
-
197
- mockLLM.getJSON.mockResolvedValue({
198
- relationships: [
199
- {
200
- item1: { id: 'a', title: 'A' },
201
- item2: { id: 'b', title: 'B' },
202
- type: 'thematic',
203
- strength: 0.8
204
- }
205
- ],
206
- clusters: [{ theme: 'test', items: ['a', 'b'] }]
207
- });
208
-
209
- const result = await relationships.buildGraph('holon1', 'lens1');
210
-
211
- expect(result.nodes).toHaveLength(2);
212
- expect(result.edges).toHaveLength(1);
213
- expect(result.edges[0].source).toBe('a');
214
- expect(result.edges[0].target).toBe('b');
215
- expect(result.clusters).toHaveLength(1);
216
- });
217
- });
218
-
219
- describe('findCrossHolonRelationships', () => {
220
- it('should find relationships across holons', async () => {
221
- mockHolosphere.getAll
222
- .mockResolvedValueOnce([{ id: 1, title: 'H1 Item' }])
223
- .mockResolvedValueOnce([{ id: 2, title: 'H2 Item' }]);
224
-
225
- mockLLM.getJSON.mockResolvedValue({
226
- cross_relationships: [
227
- {
228
- holon1: 'h1',
229
- item1: 1,
230
- holon2: 'h2',
231
- item2: 2,
232
- type: 'thematic',
233
- potential: 'collaboration'
234
- }
235
- ],
236
- shared_themes: [{ theme: 'innovation', holons: ['h1', 'h2'] }],
237
- opportunities: ['Joint project']
238
- });
239
-
240
- const result = await relationships.findCrossHolonRelationships(['h1', 'h2'], 'lens');
241
-
242
- expect(result.cross_relationships).toHaveLength(1);
243
- expect(result.shared_themes).toHaveLength(1);
244
- });
245
-
246
- it('should throw error if HoloSphere not available', async () => {
247
- const r = new RelationshipDiscovery(mockLLM);
248
-
249
- await expect(r.findCrossHolonRelationships(['h1', 'h2'], 'lens'))
250
- .rejects.toThrow('HoloSphere instance required');
251
- });
252
-
253
- it('should require at least 2 holons with data', async () => {
254
- mockHolosphere.getAll.mockResolvedValue([{ id: 1 }]);
255
-
256
- const result = await relationships.findCrossHolonRelationships(['h1'], 'lens');
257
-
258
- expect(result.message).toBe('Need at least 2 holons with data');
259
- });
260
- });
261
-
262
- describe('suggestConnections', () => {
263
- it('should suggest connections for an item', async () => {
264
- mockEmbeddings.findSimilar.mockResolvedValue([
265
- { item: { id: 2 }, similarity: 0.8 }
266
- ]);
267
-
268
- mockLLM.getJSON.mockResolvedValue({
269
- collaborations: [{ with: 2, action: 'Partner on project' }],
270
- resource_sharing: [{ items: [1, 2], resource: 'Equipment' }],
271
- initiatives: [{ description: 'Joint workshop', participants: [1, 2] }],
272
- knowledge_exchange: [{ from: 1, to: 2, topic: 'Best practices' }]
273
- });
274
-
275
- const result = await relationships.suggestConnections({ id: 1 }, 'holon', 'lens');
276
-
277
- expect(result.collaborations).toBeDefined();
278
- expect(result.resource_sharing).toBeDefined();
279
- });
280
- });
281
-
282
- describe('detectPatterns', () => {
283
- it('should detect relationship patterns', async () => {
284
- mockHolosphere.getAll.mockResolvedValue([{ id: 1 }, { id: 2 }, { id: 3 }]);
285
-
286
- mockLLM.getJSON
287
- .mockResolvedValueOnce({
288
- relationships: [
289
- { item1: { id: 'a' }, item2: { id: 'b' }, type: 'thematic', strength: 0.8 }
290
- ]
291
- })
292
- .mockResolvedValueOnce({
293
- patterns: [
294
- {
295
- type: 'hub',
296
- description: 'Central node connects multiple items',
297
- items: ['a'],
298
- significance: 'Key connector'
299
- }
300
- ],
301
- network_structure: 'Hub-spoke pattern',
302
- recommendations: ['Strengthen peripheral connections']
303
- });
304
-
305
- const result = await relationships.detectPatterns('holon', 'lens');
306
-
307
- expect(result.patterns).toBeDefined();
308
- expect(result.network_structure).toBeDefined();
309
- });
310
- });
311
- });