holosphere 2.0.0-alpha1 → 2.0.0-alpha2

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 (87) hide show
  1. package/dist/cjs/holosphere.cjs +2 -0
  2. package/dist/cjs/holosphere.cjs.map +1 -0
  3. package/dist/esm/holosphere.js +56 -0
  4. package/dist/esm/holosphere.js.map +1 -0
  5. package/dist/index-CDfIuXew.js +15974 -0
  6. package/dist/index-CDfIuXew.js.map +1 -0
  7. package/dist/index-ifOgtDvd.cjs +3 -0
  8. package/dist/index-ifOgtDvd.cjs.map +1 -0
  9. package/dist/indexeddb-storage-CMW4qRQS.js +96 -0
  10. package/dist/indexeddb-storage-CMW4qRQS.js.map +1 -0
  11. package/dist/indexeddb-storage-DLZOgetM.cjs +2 -0
  12. package/dist/indexeddb-storage-DLZOgetM.cjs.map +1 -0
  13. package/dist/memory-storage-DQzcAZlf.js +47 -0
  14. package/dist/memory-storage-DQzcAZlf.js.map +1 -0
  15. package/dist/memory-storage-DmePEP2q.cjs +2 -0
  16. package/dist/memory-storage-DmePEP2q.cjs.map +1 -0
  17. package/dist/secp256k1-CP0ZkpAx.cjs +13 -0
  18. package/dist/secp256k1-CP0ZkpAx.cjs.map +1 -0
  19. package/dist/secp256k1-vOXp40Fx.js +2281 -0
  20. package/dist/secp256k1-vOXp40Fx.js.map +1 -0
  21. package/docs/FOSDEM_PROPOSAL.md +388 -0
  22. package/docs/LOCALFIRST.md +266 -0
  23. package/docs/contracts/api-interface.md +793 -0
  24. package/docs/data-model.md +476 -0
  25. package/docs/gun-async-usage.md +338 -0
  26. package/docs/plan.md +349 -0
  27. package/docs/quickstart.md +674 -0
  28. package/docs/research.md +362 -0
  29. package/docs/spec.md +244 -0
  30. package/docs/storage-backends.md +326 -0
  31. package/docs/tasks.md +947 -0
  32. package/package.json +1 -1
  33. package/tests/unit/ai/aggregation.test.js +0 -295
  34. package/tests/unit/ai/breakdown.test.js +0 -446
  35. package/tests/unit/ai/classifier.test.js +0 -294
  36. package/tests/unit/ai/council.test.js +0 -262
  37. package/tests/unit/ai/embeddings.test.js +0 -384
  38. package/tests/unit/ai/federation-ai.test.js +0 -344
  39. package/tests/unit/ai/h3-ai.test.js +0 -458
  40. package/tests/unit/ai/index.test.js +0 -304
  41. package/tests/unit/ai/json-ops.test.js +0 -307
  42. package/tests/unit/ai/llm-service.test.js +0 -390
  43. package/tests/unit/ai/nl-query.test.js +0 -383
  44. package/tests/unit/ai/relationships.test.js +0 -311
  45. package/tests/unit/ai/schema-extractor.test.js +0 -384
  46. package/tests/unit/ai/spatial.test.js +0 -279
  47. package/tests/unit/ai/tts.test.js +0 -279
  48. package/tests/unit/content.test.js +0 -332
  49. package/tests/unit/contract/core.test.js +0 -88
  50. package/tests/unit/contract/crypto.test.js +0 -198
  51. package/tests/unit/contract/data.test.js +0 -223
  52. package/tests/unit/contract/federation.test.js +0 -181
  53. package/tests/unit/contract/hierarchical.test.js +0 -113
  54. package/tests/unit/contract/schema.test.js +0 -114
  55. package/tests/unit/contract/social.test.js +0 -217
  56. package/tests/unit/contract/spatial.test.js +0 -110
  57. package/tests/unit/contract/subscriptions.test.js +0 -128
  58. package/tests/unit/contract/utils.test.js +0 -159
  59. package/tests/unit/core.test.js +0 -152
  60. package/tests/unit/crypto.test.js +0 -328
  61. package/tests/unit/federation.test.js +0 -234
  62. package/tests/unit/gun-async.test.js +0 -252
  63. package/tests/unit/hierarchical.test.js +0 -399
  64. package/tests/unit/integration/scenario-01-geographic-storage.test.js +0 -74
  65. package/tests/unit/integration/scenario-02-federation.test.js +0 -76
  66. package/tests/unit/integration/scenario-03-subscriptions.test.js +0 -102
  67. package/tests/unit/integration/scenario-04-validation.test.js +0 -129
  68. package/tests/unit/integration/scenario-05-hierarchy.test.js +0 -125
  69. package/tests/unit/integration/scenario-06-social.test.js +0 -135
  70. package/tests/unit/integration/scenario-07-persistence.test.js +0 -130
  71. package/tests/unit/integration/scenario-08-authorization.test.js +0 -161
  72. package/tests/unit/integration/scenario-09-cross-dimensional.test.js +0 -139
  73. package/tests/unit/integration/scenario-10-cross-holosphere-capabilities.test.js +0 -357
  74. package/tests/unit/integration/scenario-11-cross-holosphere-federation.test.js +0 -410
  75. package/tests/unit/integration/scenario-12-capability-federated-read.test.js +0 -719
  76. package/tests/unit/performance/benchmark.test.js +0 -85
  77. package/tests/unit/schema.test.js +0 -213
  78. package/tests/unit/spatial.test.js +0 -158
  79. package/tests/unit/storage.test.js +0 -195
  80. package/tests/unit/subscriptions.test.js +0 -328
  81. package/tests/unit/test-data-permanence-debug.js +0 -197
  82. package/tests/unit/test-data-permanence.js +0 -340
  83. package/tests/unit/test-key-persistence-fixed.js +0 -148
  84. package/tests/unit/test-key-persistence.js +0 -172
  85. package/tests/unit/test-relay-permanence.js +0 -376
  86. package/tests/unit/test-second-node.js +0 -95
  87. 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
- });