@soleri/core 2.0.1 → 2.0.2
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.
- package/dist/brain/brain.d.ts +3 -12
- package/dist/brain/brain.d.ts.map +1 -1
- package/dist/brain/brain.js +13 -305
- package/dist/brain/brain.js.map +1 -1
- package/dist/curator/curator.d.ts +28 -0
- package/dist/curator/curator.d.ts.map +1 -0
- package/dist/curator/curator.js +523 -0
- package/dist/curator/curator.js.map +1 -0
- package/dist/curator/types.d.ts +87 -0
- package/dist/curator/types.d.ts.map +1 -0
- package/dist/curator/types.js +3 -0
- package/dist/curator/types.js.map +1 -0
- package/dist/facades/types.d.ts +1 -1
- package/dist/index.d.ts +9 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -2
- package/dist/index.js.map +1 -1
- package/dist/llm/llm-client.d.ts +28 -0
- package/dist/llm/llm-client.d.ts.map +1 -0
- package/dist/llm/llm-client.js +219 -0
- package/dist/llm/llm-client.js.map +1 -0
- package/dist/runtime/core-ops.d.ts +17 -0
- package/dist/runtime/core-ops.d.ts.map +1 -0
- package/dist/runtime/core-ops.js +448 -0
- package/dist/runtime/core-ops.js.map +1 -0
- package/dist/runtime/domain-ops.d.ts +25 -0
- package/dist/runtime/domain-ops.d.ts.map +1 -0
- package/dist/runtime/domain-ops.js +130 -0
- package/dist/runtime/domain-ops.js.map +1 -0
- package/dist/runtime/runtime.d.ts +19 -0
- package/dist/runtime/runtime.d.ts.map +1 -0
- package/dist/runtime/runtime.js +62 -0
- package/dist/runtime/runtime.js.map +1 -0
- package/dist/runtime/types.d.ts +39 -0
- package/dist/runtime/types.d.ts.map +1 -0
- package/dist/runtime/types.js +2 -0
- package/dist/{cognee → runtime}/types.js.map +1 -1
- package/dist/text/similarity.d.ts +8 -0
- package/dist/text/similarity.d.ts.map +1 -0
- package/dist/text/similarity.js +161 -0
- package/dist/text/similarity.js.map +1 -0
- package/package.json +6 -2
- package/src/__tests__/brain.test.ts +27 -265
- package/src/__tests__/core-ops.test.ts +190 -0
- package/src/__tests__/curator.test.ts +479 -0
- package/src/__tests__/domain-ops.test.ts +124 -0
- package/src/__tests__/llm-client.test.ts +69 -0
- package/src/__tests__/runtime.test.ts +93 -0
- package/src/brain/brain.ts +19 -342
- package/src/curator/curator.ts +662 -0
- package/src/curator/types.ts +114 -0
- package/src/index.ts +40 -11
- package/src/llm/llm-client.ts +316 -0
- package/src/runtime/core-ops.ts +472 -0
- package/src/runtime/domain-ops.ts +144 -0
- package/src/runtime/runtime.ts +71 -0
- package/src/runtime/types.ts +37 -0
- package/src/text/similarity.ts +168 -0
- package/dist/cognee/client.d.ts +0 -35
- package/dist/cognee/client.d.ts.map +0 -1
- package/dist/cognee/client.js +0 -291
- package/dist/cognee/client.js.map +0 -1
- package/dist/cognee/types.d.ts +0 -46
- package/dist/cognee/types.d.ts.map +0 -1
- package/dist/cognee/types.js +0 -3
- package/src/__tests__/cognee-client.test.ts +0 -524
- package/src/cognee/client.ts +0 -352
- package/src/cognee/types.ts +0 -62
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, afterEach
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
2
|
import { Vault } from '../vault/vault.js';
|
|
3
3
|
import { Brain } from '../brain/brain.js';
|
|
4
4
|
import type { IntelligenceEntry } from '../intelligence/types.js';
|
|
5
|
-
import type { CogneeClient } from '../cognee/client.js';
|
|
6
|
-
import type { CogneeSearchResult, CogneeStatus } from '../cognee/types.js';
|
|
7
5
|
|
|
8
6
|
function makeEntry(overrides: Partial<IntelligenceEntry> = {}): IntelligenceEntry {
|
|
9
7
|
return {
|
|
@@ -17,36 +15,6 @@ function makeEntry(overrides: Partial<IntelligenceEntry> = {}): IntelligenceEntr
|
|
|
17
15
|
};
|
|
18
16
|
}
|
|
19
17
|
|
|
20
|
-
function makeMockCognee(
|
|
21
|
-
overrides: {
|
|
22
|
-
available?: boolean;
|
|
23
|
-
searchResults?: CogneeSearchResult[];
|
|
24
|
-
searchError?: boolean;
|
|
25
|
-
} = {},
|
|
26
|
-
): CogneeClient {
|
|
27
|
-
const available = overrides.available ?? true;
|
|
28
|
-
return {
|
|
29
|
-
get isAvailable() {
|
|
30
|
-
return available;
|
|
31
|
-
},
|
|
32
|
-
search: overrides.searchError
|
|
33
|
-
? vi.fn().mockRejectedValue(new Error('timeout'))
|
|
34
|
-
: vi.fn().mockResolvedValue(overrides.searchResults ?? []),
|
|
35
|
-
addEntries: vi.fn().mockResolvedValue({ added: 0 }),
|
|
36
|
-
cognify: vi.fn().mockResolvedValue({ status: 'ok' }),
|
|
37
|
-
healthCheck: vi
|
|
38
|
-
.fn()
|
|
39
|
-
.mockResolvedValue({ available, url: 'http://localhost:8000', latencyMs: 1 } as CogneeStatus),
|
|
40
|
-
getConfig: vi.fn().mockReturnValue({
|
|
41
|
-
baseUrl: 'http://localhost:8000',
|
|
42
|
-
dataset: 'vault',
|
|
43
|
-
timeoutMs: 5000,
|
|
44
|
-
healthCacheTtlMs: 60000,
|
|
45
|
-
}),
|
|
46
|
-
getStatus: vi.fn().mockReturnValue(null),
|
|
47
|
-
} as unknown as CogneeClient;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
18
|
describe('Brain', () => {
|
|
51
19
|
let vault: Vault;
|
|
52
20
|
let brain: Brain;
|
|
@@ -85,12 +53,6 @@ describe('Brain', () => {
|
|
|
85
53
|
const brain2 = new Brain(vault);
|
|
86
54
|
expect(brain2.getVocabularySize()).toBeGreaterThan(0);
|
|
87
55
|
});
|
|
88
|
-
|
|
89
|
-
it('should accept optional CogneeClient', () => {
|
|
90
|
-
const cognee = makeMockCognee();
|
|
91
|
-
const brain2 = new Brain(vault, cognee);
|
|
92
|
-
expect(brain2.getVocabularySize()).toBe(0);
|
|
93
|
-
});
|
|
94
56
|
});
|
|
95
57
|
|
|
96
58
|
// ─── Intelligent Search ──────────────────────────────────────
|
|
@@ -127,52 +89,49 @@ describe('Brain', () => {
|
|
|
127
89
|
brain = new Brain(vault);
|
|
128
90
|
});
|
|
129
91
|
|
|
130
|
-
it('should return ranked results',
|
|
131
|
-
const results =
|
|
92
|
+
it('should return ranked results', () => {
|
|
93
|
+
const results = brain.intelligentSearch('validation input');
|
|
132
94
|
expect(results.length).toBeGreaterThan(0);
|
|
133
95
|
expect(results[0].entry.id).toBe('is-1');
|
|
134
96
|
});
|
|
135
97
|
|
|
136
|
-
it('should include score breakdown
|
|
137
|
-
const results =
|
|
98
|
+
it('should include score breakdown', () => {
|
|
99
|
+
const results = brain.intelligentSearch('validation');
|
|
138
100
|
expect(results.length).toBeGreaterThan(0);
|
|
139
101
|
const breakdown = results[0].breakdown;
|
|
140
102
|
expect(breakdown).toHaveProperty('semantic');
|
|
141
|
-
expect(breakdown).toHaveProperty('vector');
|
|
142
103
|
expect(breakdown).toHaveProperty('severity');
|
|
143
104
|
expect(breakdown).toHaveProperty('recency');
|
|
144
105
|
expect(breakdown).toHaveProperty('tagOverlap');
|
|
145
106
|
expect(breakdown).toHaveProperty('domainMatch');
|
|
146
107
|
expect(breakdown).toHaveProperty('total');
|
|
147
108
|
expect(breakdown.total).toBe(results[0].score);
|
|
148
|
-
// Without cognee, vector should be 0
|
|
149
|
-
expect(breakdown.vector).toBe(0);
|
|
150
109
|
});
|
|
151
110
|
|
|
152
|
-
it('should return empty array for no matches',
|
|
153
|
-
const results =
|
|
111
|
+
it('should return empty array for no matches', () => {
|
|
112
|
+
const results = brain.intelligentSearch('xyznonexistent');
|
|
154
113
|
expect(results).toEqual([]);
|
|
155
114
|
});
|
|
156
115
|
|
|
157
|
-
it('should respect limit',
|
|
158
|
-
const results =
|
|
116
|
+
it('should respect limit', () => {
|
|
117
|
+
const results = brain.intelligentSearch('pattern', { limit: 1 });
|
|
159
118
|
expect(results.length).toBeLessThanOrEqual(1);
|
|
160
119
|
});
|
|
161
120
|
|
|
162
|
-
it('should filter by domain',
|
|
163
|
-
const results =
|
|
121
|
+
it('should filter by domain', () => {
|
|
122
|
+
const results = brain.intelligentSearch('pattern', { domain: 'security' });
|
|
164
123
|
expect(results.every((r) => r.entry.domain === 'security')).toBe(true);
|
|
165
124
|
});
|
|
166
125
|
|
|
167
|
-
it('should boost domain matches when domain is specified',
|
|
168
|
-
const withDomain =
|
|
126
|
+
it('should boost domain matches when domain is specified', () => {
|
|
127
|
+
const withDomain = brain.intelligentSearch('pattern', { domain: 'security' });
|
|
169
128
|
if (withDomain.length > 0) {
|
|
170
129
|
expect(withDomain[0].breakdown.domainMatch).toBe(1.0);
|
|
171
130
|
}
|
|
172
131
|
});
|
|
173
132
|
|
|
174
|
-
it('should boost severity in scoring',
|
|
175
|
-
const results =
|
|
133
|
+
it('should boost severity in scoring', () => {
|
|
134
|
+
const results = brain.intelligentSearch('pattern');
|
|
176
135
|
if (results.length >= 2) {
|
|
177
136
|
const critical = results.find((r) => r.entry.severity === 'critical');
|
|
178
137
|
const suggestion = results.find((r) => r.entry.severity === 'suggestion');
|
|
@@ -182,10 +141,8 @@ describe('Brain', () => {
|
|
|
182
141
|
}
|
|
183
142
|
});
|
|
184
143
|
|
|
185
|
-
it('should boost tag overlap when tags provided',
|
|
186
|
-
const results =
|
|
187
|
-
tags: ['validation', 'security'],
|
|
188
|
-
});
|
|
144
|
+
it('should boost tag overlap when tags provided', () => {
|
|
145
|
+
const results = brain.intelligentSearch('pattern', { tags: ['validation', 'security'] });
|
|
189
146
|
if (results.length > 0) {
|
|
190
147
|
const secEntry = results.find((r) => r.entry.id === 'is-1');
|
|
191
148
|
if (secEntry) {
|
|
@@ -194,185 +151,15 @@ describe('Brain', () => {
|
|
|
194
151
|
}
|
|
195
152
|
});
|
|
196
153
|
|
|
197
|
-
it('should handle search on empty vault gracefully',
|
|
154
|
+
it('should handle search on empty vault gracefully', () => {
|
|
198
155
|
const emptyVault = new Vault(':memory:');
|
|
199
156
|
const emptyBrain = new Brain(emptyVault);
|
|
200
|
-
const results =
|
|
157
|
+
const results = emptyBrain.intelligentSearch('anything');
|
|
201
158
|
expect(results).toEqual([]);
|
|
202
159
|
emptyVault.close();
|
|
203
160
|
});
|
|
204
161
|
});
|
|
205
162
|
|
|
206
|
-
// ─── Hybrid Search (with Cognee) ──────────────────────────────
|
|
207
|
-
|
|
208
|
-
describe('hybrid search with Cognee', () => {
|
|
209
|
-
beforeEach(() => {
|
|
210
|
-
vault.seed([
|
|
211
|
-
makeEntry({
|
|
212
|
-
id: 'hs-1',
|
|
213
|
-
title: 'Authentication flow',
|
|
214
|
-
description: 'JWT-based authentication for API endpoints.',
|
|
215
|
-
domain: 'security',
|
|
216
|
-
severity: 'critical',
|
|
217
|
-
tags: ['auth', 'jwt'],
|
|
218
|
-
}),
|
|
219
|
-
makeEntry({
|
|
220
|
-
id: 'hs-2',
|
|
221
|
-
title: 'Logging best practices',
|
|
222
|
-
description: 'Structured logging with correlation IDs for debugging.',
|
|
223
|
-
domain: 'observability',
|
|
224
|
-
severity: 'warning',
|
|
225
|
-
tags: ['logging', 'debugging'],
|
|
226
|
-
}),
|
|
227
|
-
]);
|
|
228
|
-
});
|
|
229
|
-
|
|
230
|
-
it('should match via [vault-id:] prefix (strategy 1)', async () => {
|
|
231
|
-
const cognee = makeMockCognee({
|
|
232
|
-
searchResults: [
|
|
233
|
-
{
|
|
234
|
-
id: 'cognee-uuid-1',
|
|
235
|
-
score: 0.92,
|
|
236
|
-
text: '[vault-id:hs-1]\nAuthentication flow\nJWT-based authentication for API endpoints.',
|
|
237
|
-
searchType: 'CHUNKS',
|
|
238
|
-
},
|
|
239
|
-
],
|
|
240
|
-
});
|
|
241
|
-
const hybridBrain = new Brain(vault, cognee);
|
|
242
|
-
const results = await hybridBrain.intelligentSearch('authentication');
|
|
243
|
-
expect(results.length).toBeGreaterThan(0);
|
|
244
|
-
const authResult = results.find((r) => r.entry.id === 'hs-1');
|
|
245
|
-
expect(authResult).toBeDefined();
|
|
246
|
-
expect(authResult!.breakdown.vector).toBe(0.92);
|
|
247
|
-
});
|
|
248
|
-
|
|
249
|
-
it('should match via title first-line (strategy 2)', async () => {
|
|
250
|
-
// Cognee stripped the [vault-id:] prefix during chunking — title is on first line
|
|
251
|
-
const cognee = makeMockCognee({
|
|
252
|
-
searchResults: [
|
|
253
|
-
{
|
|
254
|
-
id: 'cognee-uuid-2',
|
|
255
|
-
score: 0.9,
|
|
256
|
-
text: 'Authentication flow\nJWT-based authentication for API endpoints.',
|
|
257
|
-
searchType: 'CHUNKS',
|
|
258
|
-
},
|
|
259
|
-
],
|
|
260
|
-
});
|
|
261
|
-
const hybridBrain = new Brain(vault, cognee);
|
|
262
|
-
const results = await hybridBrain.intelligentSearch('authentication');
|
|
263
|
-
const authResult = results.find((r) => r.entry.id === 'hs-1');
|
|
264
|
-
expect(authResult).toBeDefined();
|
|
265
|
-
expect(authResult!.breakdown.vector).toBe(0.9);
|
|
266
|
-
});
|
|
267
|
-
|
|
268
|
-
it('should match via title substring (strategy 3)', async () => {
|
|
269
|
-
// Mid-document chunk where title isn't on the first line
|
|
270
|
-
const cognee = makeMockCognee({
|
|
271
|
-
searchResults: [
|
|
272
|
-
{
|
|
273
|
-
id: 'cognee-uuid-3',
|
|
274
|
-
score: 0.85,
|
|
275
|
-
text: 'Some preamble text\nAuthentication flow\nJWT-based auth...',
|
|
276
|
-
searchType: 'CHUNKS',
|
|
277
|
-
},
|
|
278
|
-
],
|
|
279
|
-
});
|
|
280
|
-
const hybridBrain = new Brain(vault, cognee);
|
|
281
|
-
const results = await hybridBrain.intelligentSearch('authentication');
|
|
282
|
-
const authResult = results.find((r) => r.entry.id === 'hs-1');
|
|
283
|
-
expect(authResult).toBeDefined();
|
|
284
|
-
expect(authResult!.breakdown.vector).toBe(0.85);
|
|
285
|
-
});
|
|
286
|
-
|
|
287
|
-
it('should merge cognee-only entries via FTS fallback (strategy 4)', async () => {
|
|
288
|
-
// hs-2 may not match FTS5 for "authentication" but Cognee finds it via semantic similarity.
|
|
289
|
-
// The chunk text starts with the entry title so strategy 4's vault.search(firstLine) finds it.
|
|
290
|
-
const cognee = makeMockCognee({
|
|
291
|
-
searchResults: [
|
|
292
|
-
{
|
|
293
|
-
id: 'cognee-uuid-a',
|
|
294
|
-
score: 0.95,
|
|
295
|
-
text: 'Authentication flow\nJWT-based authentication for API endpoints.',
|
|
296
|
-
searchType: 'CHUNKS',
|
|
297
|
-
},
|
|
298
|
-
{
|
|
299
|
-
id: 'cognee-uuid-b',
|
|
300
|
-
score: 0.6,
|
|
301
|
-
text: 'Logging best practices\nStructured logging with correlation IDs.',
|
|
302
|
-
searchType: 'CHUNKS',
|
|
303
|
-
},
|
|
304
|
-
],
|
|
305
|
-
});
|
|
306
|
-
const hybridBrain = new Brain(vault, cognee);
|
|
307
|
-
const results = await hybridBrain.intelligentSearch('authentication');
|
|
308
|
-
// Both entries should be in results (hs-2 merged from Cognee even if not in FTS5)
|
|
309
|
-
const ids = results.map((r) => r.entry.id);
|
|
310
|
-
expect(ids).toContain('hs-1');
|
|
311
|
-
expect(ids).toContain('hs-2');
|
|
312
|
-
const loggingResult = results.find((r) => r.entry.id === 'hs-2');
|
|
313
|
-
expect(loggingResult).toBeDefined();
|
|
314
|
-
expect(loggingResult!.breakdown.vector).toBe(0.6);
|
|
315
|
-
});
|
|
316
|
-
|
|
317
|
-
it('should fall back to FTS5-only on Cognee search error', async () => {
|
|
318
|
-
const cognee = makeMockCognee({ searchError: true });
|
|
319
|
-
const hybridBrain = new Brain(vault, cognee);
|
|
320
|
-
const results = await hybridBrain.intelligentSearch('authentication');
|
|
321
|
-
// Should still work, just without vector scores
|
|
322
|
-
for (const r of results) {
|
|
323
|
-
expect(r.breakdown.vector).toBe(0);
|
|
324
|
-
}
|
|
325
|
-
});
|
|
326
|
-
|
|
327
|
-
it('should work without Cognee (backward compatible)', async () => {
|
|
328
|
-
const noCogneeBrain = new Brain(vault);
|
|
329
|
-
const results = await noCogneeBrain.intelligentSearch('authentication');
|
|
330
|
-
for (const r of results) {
|
|
331
|
-
expect(r.breakdown.vector).toBe(0);
|
|
332
|
-
}
|
|
333
|
-
});
|
|
334
|
-
|
|
335
|
-
it('should handle unavailable Cognee gracefully', async () => {
|
|
336
|
-
const cognee = makeMockCognee({ available: false });
|
|
337
|
-
const hybridBrain = new Brain(vault, cognee);
|
|
338
|
-
const results = await hybridBrain.intelligentSearch('authentication');
|
|
339
|
-
for (const r of results) {
|
|
340
|
-
expect(r.breakdown.vector).toBe(0);
|
|
341
|
-
}
|
|
342
|
-
// search should not have been called
|
|
343
|
-
expect(cognee.search).not.toHaveBeenCalled();
|
|
344
|
-
});
|
|
345
|
-
});
|
|
346
|
-
|
|
347
|
-
// ─── syncToCognee ──────────────────────────────────────────────
|
|
348
|
-
|
|
349
|
-
describe('syncToCognee', () => {
|
|
350
|
-
it('should return 0 when Cognee not available', async () => {
|
|
351
|
-
const result = await brain.syncToCognee();
|
|
352
|
-
expect(result).toEqual({ synced: 0, cognified: false });
|
|
353
|
-
});
|
|
354
|
-
|
|
355
|
-
it('should sync all entries and cognify', async () => {
|
|
356
|
-
vault.seed([makeEntry({ id: 'sync-1' }), makeEntry({ id: 'sync-2' })]);
|
|
357
|
-
const cognee = makeMockCognee();
|
|
358
|
-
(cognee.addEntries as ReturnType<typeof vi.fn>).mockResolvedValue({ added: 2 });
|
|
359
|
-
const hybridBrain = new Brain(vault, cognee);
|
|
360
|
-
const result = await hybridBrain.syncToCognee();
|
|
361
|
-
expect(result.synced).toBe(2);
|
|
362
|
-
expect(result.cognified).toBe(true);
|
|
363
|
-
expect(cognee.addEntries).toHaveBeenCalledTimes(1);
|
|
364
|
-
expect(cognee.cognify).toHaveBeenCalledTimes(1);
|
|
365
|
-
});
|
|
366
|
-
|
|
367
|
-
it('should skip cognify when no entries added', async () => {
|
|
368
|
-
const cognee = makeMockCognee();
|
|
369
|
-
const hybridBrain = new Brain(vault, cognee);
|
|
370
|
-
const result = await hybridBrain.syncToCognee();
|
|
371
|
-
expect(result.synced).toBe(0);
|
|
372
|
-
expect(result.cognified).toBe(false);
|
|
373
|
-
});
|
|
374
|
-
});
|
|
375
|
-
|
|
376
163
|
// ─── Enrich and Capture ─────────────────────────────────────
|
|
377
164
|
|
|
378
165
|
describe('enrichAndCapture', () => {
|
|
@@ -456,21 +243,6 @@ describe('Brain', () => {
|
|
|
456
243
|
const entry = vault.get('cap-5');
|
|
457
244
|
expect(entry!.tags.length).toBeGreaterThan(0);
|
|
458
245
|
});
|
|
459
|
-
|
|
460
|
-
it('should fire-and-forget sync to Cognee on capture', () => {
|
|
461
|
-
const cognee = makeMockCognee();
|
|
462
|
-
const hybridBrain = new Brain(vault, cognee);
|
|
463
|
-
hybridBrain.enrichAndCapture({
|
|
464
|
-
id: 'cap-cognee-1',
|
|
465
|
-
type: 'pattern',
|
|
466
|
-
domain: 'testing',
|
|
467
|
-
title: 'Cognee sync test',
|
|
468
|
-
severity: 'warning',
|
|
469
|
-
description: 'Testing fire-and-forget Cognee sync.',
|
|
470
|
-
tags: [],
|
|
471
|
-
});
|
|
472
|
-
expect(cognee.addEntries).toHaveBeenCalledTimes(1);
|
|
473
|
-
});
|
|
474
246
|
});
|
|
475
247
|
|
|
476
248
|
// ─── Duplicate Detection ────────────────────────────────────
|
|
@@ -582,7 +354,6 @@ describe('Brain', () => {
|
|
|
582
354
|
const stats = brain.getStats();
|
|
583
355
|
const sum =
|
|
584
356
|
stats.weights.semantic +
|
|
585
|
-
stats.weights.vector +
|
|
586
357
|
stats.weights.severity +
|
|
587
358
|
stats.weights.recency +
|
|
588
359
|
stats.weights.tagOverlap +
|
|
@@ -600,11 +371,6 @@ describe('Brain', () => {
|
|
|
600
371
|
const stats = brain.getStats();
|
|
601
372
|
expect(stats.weights.semantic).toBeCloseTo(0.4, 1);
|
|
602
373
|
});
|
|
603
|
-
|
|
604
|
-
it('should keep vector weight at 0 in base weights', () => {
|
|
605
|
-
const stats = brain.getStats();
|
|
606
|
-
expect(stats.weights.vector).toBe(0);
|
|
607
|
-
});
|
|
608
374
|
});
|
|
609
375
|
|
|
610
376
|
// ─── Vocabulary ─────────────────────────────────────────────
|
|
@@ -676,7 +442,6 @@ describe('Brain', () => {
|
|
|
676
442
|
expect(stats.vocabularySize).toBe(0);
|
|
677
443
|
expect(stats.feedbackCount).toBe(0);
|
|
678
444
|
expect(stats.weights.semantic).toBeCloseTo(0.4, 2);
|
|
679
|
-
expect(stats.weights.vector).toBe(0);
|
|
680
445
|
});
|
|
681
446
|
|
|
682
447
|
it('should return correct vocabulary size after seeding', () => {
|
|
@@ -710,7 +475,7 @@ describe('Brain', () => {
|
|
|
710
475
|
// ─── Get Relevant Patterns ──────────────────────────────────
|
|
711
476
|
|
|
712
477
|
describe('getRelevantPatterns', () => {
|
|
713
|
-
it('should return ranked results for query context',
|
|
478
|
+
it('should return ranked results for query context', () => {
|
|
714
479
|
vault.seed([
|
|
715
480
|
makeEntry({
|
|
716
481
|
id: 'rel-1',
|
|
@@ -728,15 +493,12 @@ describe('Brain', () => {
|
|
|
728
493
|
}),
|
|
729
494
|
]);
|
|
730
495
|
brain = new Brain(vault);
|
|
731
|
-
const results =
|
|
732
|
-
query: 'authentication',
|
|
733
|
-
domain: 'security',
|
|
734
|
-
});
|
|
496
|
+
const results = brain.getRelevantPatterns({ query: 'authentication', domain: 'security' });
|
|
735
497
|
expect(results.length).toBeGreaterThan(0);
|
|
736
498
|
});
|
|
737
499
|
|
|
738
|
-
it('should return empty for no context matches',
|
|
739
|
-
const results =
|
|
500
|
+
it('should return empty for no context matches', () => {
|
|
501
|
+
const results = brain.getRelevantPatterns({ query: 'nonexistent' });
|
|
740
502
|
expect(results).toEqual([]);
|
|
741
503
|
});
|
|
742
504
|
});
|
|
@@ -744,13 +506,13 @@ describe('Brain', () => {
|
|
|
744
506
|
// ─── Graceful Degradation ───────────────────────────────────
|
|
745
507
|
|
|
746
508
|
describe('graceful degradation', () => {
|
|
747
|
-
it('should work without vocabulary (empty vault)',
|
|
509
|
+
it('should work without vocabulary (empty vault)', () => {
|
|
748
510
|
expect(brain.getVocabularySize()).toBe(0);
|
|
749
|
-
const results =
|
|
511
|
+
const results = brain.intelligentSearch('anything');
|
|
750
512
|
expect(results).toEqual([]);
|
|
751
513
|
});
|
|
752
514
|
|
|
753
|
-
it('should fall back to severity + recency scoring when vocabulary is empty',
|
|
515
|
+
it('should fall back to severity + recency scoring when vocabulary is empty', () => {
|
|
754
516
|
vault.seed([
|
|
755
517
|
makeEntry({
|
|
756
518
|
id: 'gd-1',
|
|
@@ -761,7 +523,7 @@ describe('Brain', () => {
|
|
|
761
523
|
}),
|
|
762
524
|
]);
|
|
763
525
|
brain = new Brain(vault);
|
|
764
|
-
const results =
|
|
526
|
+
const results = brain.intelligentSearch('fallback test');
|
|
765
527
|
expect(results.length).toBeGreaterThan(0);
|
|
766
528
|
expect(results[0].score).toBeGreaterThan(0);
|
|
767
529
|
});
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import { mkdirSync, rmSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { tmpdir } from 'node:os';
|
|
5
|
+
import { createAgentRuntime } from '../runtime/runtime.js';
|
|
6
|
+
import { createCoreOps } from '../runtime/core-ops.js';
|
|
7
|
+
import type { AgentRuntime } from '../runtime/types.js';
|
|
8
|
+
import type { OpDefinition } from '../facades/types.js';
|
|
9
|
+
|
|
10
|
+
describe('createCoreOps', () => {
|
|
11
|
+
let runtime: AgentRuntime;
|
|
12
|
+
let ops: OpDefinition[];
|
|
13
|
+
let plannerDir: string;
|
|
14
|
+
|
|
15
|
+
beforeEach(() => {
|
|
16
|
+
plannerDir = join(tmpdir(), 'core-ops-test-' + Date.now());
|
|
17
|
+
mkdirSync(plannerDir, { recursive: true });
|
|
18
|
+
runtime = createAgentRuntime({
|
|
19
|
+
agentId: 'test-core-ops',
|
|
20
|
+
vaultPath: ':memory:',
|
|
21
|
+
plansPath: join(plannerDir, 'plans.json'),
|
|
22
|
+
});
|
|
23
|
+
ops = createCoreOps(runtime);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
afterEach(() => {
|
|
27
|
+
runtime.close();
|
|
28
|
+
rmSync(plannerDir, { recursive: true, force: true });
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
function findOp(name: string): OpDefinition {
|
|
32
|
+
const op = ops.find((o) => o.name === name);
|
|
33
|
+
if (!op) throw new Error(`Op "${name}" not found`);
|
|
34
|
+
return op;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
it('should return 26 ops', () => {
|
|
38
|
+
expect(ops.length).toBe(26);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('should have all expected op names', () => {
|
|
42
|
+
const names = ops.map((o) => o.name);
|
|
43
|
+
// Search/Vault
|
|
44
|
+
expect(names).toContain('search');
|
|
45
|
+
expect(names).toContain('vault_stats');
|
|
46
|
+
expect(names).toContain('list_all');
|
|
47
|
+
expect(names).toContain('register');
|
|
48
|
+
// Memory
|
|
49
|
+
expect(names).toContain('memory_search');
|
|
50
|
+
expect(names).toContain('memory_capture');
|
|
51
|
+
expect(names).toContain('memory_list');
|
|
52
|
+
expect(names).toContain('session_capture');
|
|
53
|
+
// Export
|
|
54
|
+
expect(names).toContain('export');
|
|
55
|
+
// Planning
|
|
56
|
+
expect(names).toContain('create_plan');
|
|
57
|
+
expect(names).toContain('get_plan');
|
|
58
|
+
expect(names).toContain('approve_plan');
|
|
59
|
+
expect(names).toContain('update_task');
|
|
60
|
+
expect(names).toContain('complete_plan');
|
|
61
|
+
// Brain
|
|
62
|
+
expect(names).toContain('record_feedback');
|
|
63
|
+
expect(names).toContain('rebuild_vocabulary');
|
|
64
|
+
expect(names).toContain('brain_stats');
|
|
65
|
+
expect(names).toContain('llm_status');
|
|
66
|
+
// Curator
|
|
67
|
+
expect(names).toContain('curator_status');
|
|
68
|
+
expect(names).toContain('curator_detect_duplicates');
|
|
69
|
+
expect(names).toContain('curator_contradictions');
|
|
70
|
+
expect(names).toContain('curator_resolve_contradiction');
|
|
71
|
+
expect(names).toContain('curator_groom');
|
|
72
|
+
expect(names).toContain('curator_groom_all');
|
|
73
|
+
expect(names).toContain('curator_consolidate');
|
|
74
|
+
expect(names).toContain('curator_health_audit');
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('search should query vault via brain', async () => {
|
|
78
|
+
runtime.vault.seed([{
|
|
79
|
+
id: 'co-1',
|
|
80
|
+
type: 'pattern',
|
|
81
|
+
domain: 'testing',
|
|
82
|
+
title: 'Test pattern for core ops',
|
|
83
|
+
severity: 'warning',
|
|
84
|
+
description: 'Core ops test.',
|
|
85
|
+
tags: ['test'],
|
|
86
|
+
}]);
|
|
87
|
+
runtime.brain.rebuildVocabulary();
|
|
88
|
+
|
|
89
|
+
// Re-create ops since brain state changed
|
|
90
|
+
ops = createCoreOps(runtime);
|
|
91
|
+
const results = await findOp('search').handler({ query: 'core ops test' }) as unknown[];
|
|
92
|
+
expect(results.length).toBeGreaterThan(0);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('vault_stats should return counts', async () => {
|
|
96
|
+
runtime.vault.seed([{
|
|
97
|
+
id: 'vs-1',
|
|
98
|
+
type: 'pattern',
|
|
99
|
+
domain: 'd1',
|
|
100
|
+
title: 'T',
|
|
101
|
+
severity: 'warning',
|
|
102
|
+
description: 'D',
|
|
103
|
+
tags: ['t'],
|
|
104
|
+
}]);
|
|
105
|
+
const stats = await findOp('vault_stats').handler({}) as { totalEntries: number };
|
|
106
|
+
expect(stats.totalEntries).toBe(1);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('create_plan + get_plan should work', async () => {
|
|
110
|
+
const created = await findOp('create_plan').handler({
|
|
111
|
+
objective: 'Test plan',
|
|
112
|
+
scope: 'core-ops test',
|
|
113
|
+
tasks: [{ title: 'Task 1', description: 'Do something' }],
|
|
114
|
+
}) as { created: boolean; plan: { id: string; status: string } };
|
|
115
|
+
expect(created.created).toBe(true);
|
|
116
|
+
expect(created.plan.status).toBe('draft');
|
|
117
|
+
|
|
118
|
+
const plan = await findOp('get_plan').handler({ planId: created.plan.id }) as { id: string };
|
|
119
|
+
expect(plan.id).toBe(created.plan.id);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it('brain_stats should return stats', async () => {
|
|
123
|
+
const stats = await findOp('brain_stats').handler({}) as { vocabularySize: number; feedbackCount: number };
|
|
124
|
+
expect(stats.vocabularySize).toBe(0);
|
|
125
|
+
expect(stats.feedbackCount).toBe(0);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('llm_status should return provider info', async () => {
|
|
129
|
+
const status = await findOp('llm_status').handler({}) as {
|
|
130
|
+
providers: {
|
|
131
|
+
openai: { available: boolean };
|
|
132
|
+
anthropic: { available: boolean };
|
|
133
|
+
};
|
|
134
|
+
routes: unknown[];
|
|
135
|
+
};
|
|
136
|
+
expect(typeof status.providers.openai.available).toBe('boolean');
|
|
137
|
+
expect(typeof status.providers.anthropic.available).toBe('boolean');
|
|
138
|
+
expect(Array.isArray(status.routes)).toBe(true);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it('curator_status should return initialized', async () => {
|
|
142
|
+
const status = await findOp('curator_status').handler({}) as { initialized: boolean };
|
|
143
|
+
expect(status.initialized).toBe(true);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it('curator_health_audit should return score', async () => {
|
|
147
|
+
runtime.vault.seed([{
|
|
148
|
+
id: 'ha-1',
|
|
149
|
+
type: 'pattern',
|
|
150
|
+
domain: 'testing',
|
|
151
|
+
title: 'Health pattern',
|
|
152
|
+
severity: 'warning',
|
|
153
|
+
description: 'Testing health.',
|
|
154
|
+
tags: ['health'],
|
|
155
|
+
}]);
|
|
156
|
+
runtime.curator.groomAll();
|
|
157
|
+
const result = await findOp('curator_health_audit').handler({}) as { score: number };
|
|
158
|
+
expect(result.score).toBeGreaterThan(0);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it('memory_capture + memory_search should work', async () => {
|
|
162
|
+
await findOp('memory_capture').handler({
|
|
163
|
+
projectPath: '/test',
|
|
164
|
+
type: 'lesson',
|
|
165
|
+
context: 'Testing core ops',
|
|
166
|
+
summary: 'Core ops memory test works',
|
|
167
|
+
topics: ['testing'],
|
|
168
|
+
filesModified: [],
|
|
169
|
+
toolsUsed: [],
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
const results = await findOp('memory_search').handler({ query: 'core ops memory' }) as unknown[];
|
|
173
|
+
expect(results.length).toBeGreaterThan(0);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it('export should return bundles', async () => {
|
|
177
|
+
runtime.vault.seed([{
|
|
178
|
+
id: 'exp-1',
|
|
179
|
+
type: 'pattern',
|
|
180
|
+
domain: 'security',
|
|
181
|
+
title: 'Export test',
|
|
182
|
+
severity: 'warning',
|
|
183
|
+
description: 'Testing export.',
|
|
184
|
+
tags: ['export'],
|
|
185
|
+
}]);
|
|
186
|
+
const result = await findOp('export').handler({}) as { exported: boolean; totalEntries: number };
|
|
187
|
+
expect(result.exported).toBe(true);
|
|
188
|
+
expect(result.totalEntries).toBe(1);
|
|
189
|
+
});
|
|
190
|
+
});
|