@soleri/core 2.0.0 → 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 -245
- 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 -222
- 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 -275
- 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 -289
- 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 -350
- 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,142 +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 include vector scores from Cognee in breakdown', async () => {
|
|
231
|
-
const cognee = makeMockCognee({
|
|
232
|
-
searchResults: [{ id: 'hs-1', score: 0.92, text: 'Auth flow', searchType: 'INSIGHTS' }],
|
|
233
|
-
});
|
|
234
|
-
const hybridBrain = new Brain(vault, cognee);
|
|
235
|
-
const results = await hybridBrain.intelligentSearch('authentication');
|
|
236
|
-
expect(results.length).toBeGreaterThan(0);
|
|
237
|
-
const authResult = results.find((r) => r.entry.id === 'hs-1');
|
|
238
|
-
expect(authResult).toBeDefined();
|
|
239
|
-
expect(authResult!.breakdown.vector).toBe(0.92);
|
|
240
|
-
});
|
|
241
|
-
|
|
242
|
-
it('should use cognee-aware weights when vector results are present', async () => {
|
|
243
|
-
const cognee = makeMockCognee({
|
|
244
|
-
searchResults: [{ id: 'hs-1', score: 0.9, text: 'Auth', searchType: 'INSIGHTS' }],
|
|
245
|
-
});
|
|
246
|
-
const hybridBrain = new Brain(vault, cognee);
|
|
247
|
-
const results = await hybridBrain.intelligentSearch('authentication');
|
|
248
|
-
// With cognee weights, vector contributes significantly
|
|
249
|
-
const authResult = results.find((r) => r.entry.id === 'hs-1');
|
|
250
|
-
expect(authResult).toBeDefined();
|
|
251
|
-
// vector=0.9 * weight=0.35 = 0.315 contribution
|
|
252
|
-
expect(authResult!.breakdown.vector).toBe(0.9);
|
|
253
|
-
});
|
|
254
|
-
|
|
255
|
-
it('should merge cognee-only entries into results', async () => {
|
|
256
|
-
// hs-2 may not match FTS5 for "authentication" but Cognee finds it via semantic similarity
|
|
257
|
-
const cognee = makeMockCognee({
|
|
258
|
-
searchResults: [
|
|
259
|
-
{ id: 'hs-1', score: 0.95, text: 'Auth', searchType: 'INSIGHTS' },
|
|
260
|
-
{ id: 'hs-2', score: 0.6, text: 'Logging', searchType: 'INSIGHTS' },
|
|
261
|
-
],
|
|
262
|
-
});
|
|
263
|
-
const hybridBrain = new Brain(vault, cognee);
|
|
264
|
-
const results = await hybridBrain.intelligentSearch('authentication');
|
|
265
|
-
// Both entries should be in results (hs-2 merged from Cognee even if not in FTS5)
|
|
266
|
-
const ids = results.map((r) => r.entry.id);
|
|
267
|
-
expect(ids).toContain('hs-1');
|
|
268
|
-
expect(ids).toContain('hs-2');
|
|
269
|
-
const loggingResult = results.find((r) => r.entry.id === 'hs-2');
|
|
270
|
-
expect(loggingResult).toBeDefined();
|
|
271
|
-
expect(loggingResult!.breakdown.vector).toBe(0.6);
|
|
272
|
-
});
|
|
273
|
-
|
|
274
|
-
it('should fall back to FTS5-only on Cognee search error', async () => {
|
|
275
|
-
const cognee = makeMockCognee({ searchError: true });
|
|
276
|
-
const hybridBrain = new Brain(vault, cognee);
|
|
277
|
-
const results = await hybridBrain.intelligentSearch('authentication');
|
|
278
|
-
// Should still work, just without vector scores
|
|
279
|
-
for (const r of results) {
|
|
280
|
-
expect(r.breakdown.vector).toBe(0);
|
|
281
|
-
}
|
|
282
|
-
});
|
|
283
|
-
|
|
284
|
-
it('should work without Cognee (backward compatible)', async () => {
|
|
285
|
-
const noCogneeBrain = new Brain(vault);
|
|
286
|
-
const results = await noCogneeBrain.intelligentSearch('authentication');
|
|
287
|
-
for (const r of results) {
|
|
288
|
-
expect(r.breakdown.vector).toBe(0);
|
|
289
|
-
}
|
|
290
|
-
});
|
|
291
|
-
|
|
292
|
-
it('should handle unavailable Cognee gracefully', async () => {
|
|
293
|
-
const cognee = makeMockCognee({ available: false });
|
|
294
|
-
const hybridBrain = new Brain(vault, cognee);
|
|
295
|
-
const results = await hybridBrain.intelligentSearch('authentication');
|
|
296
|
-
for (const r of results) {
|
|
297
|
-
expect(r.breakdown.vector).toBe(0);
|
|
298
|
-
}
|
|
299
|
-
// search should not have been called
|
|
300
|
-
expect(cognee.search).not.toHaveBeenCalled();
|
|
301
|
-
});
|
|
302
|
-
});
|
|
303
|
-
|
|
304
|
-
// ─── syncToCognee ──────────────────────────────────────────────
|
|
305
|
-
|
|
306
|
-
describe('syncToCognee', () => {
|
|
307
|
-
it('should return 0 when Cognee not available', async () => {
|
|
308
|
-
const result = await brain.syncToCognee();
|
|
309
|
-
expect(result).toEqual({ synced: 0, cognified: false });
|
|
310
|
-
});
|
|
311
|
-
|
|
312
|
-
it('should sync all entries and cognify', async () => {
|
|
313
|
-
vault.seed([makeEntry({ id: 'sync-1' }), makeEntry({ id: 'sync-2' })]);
|
|
314
|
-
const cognee = makeMockCognee();
|
|
315
|
-
(cognee.addEntries as ReturnType<typeof vi.fn>).mockResolvedValue({ added: 2 });
|
|
316
|
-
const hybridBrain = new Brain(vault, cognee);
|
|
317
|
-
const result = await hybridBrain.syncToCognee();
|
|
318
|
-
expect(result.synced).toBe(2);
|
|
319
|
-
expect(result.cognified).toBe(true);
|
|
320
|
-
expect(cognee.addEntries).toHaveBeenCalledTimes(1);
|
|
321
|
-
expect(cognee.cognify).toHaveBeenCalledTimes(1);
|
|
322
|
-
});
|
|
323
|
-
|
|
324
|
-
it('should skip cognify when no entries added', async () => {
|
|
325
|
-
const cognee = makeMockCognee();
|
|
326
|
-
const hybridBrain = new Brain(vault, cognee);
|
|
327
|
-
const result = await hybridBrain.syncToCognee();
|
|
328
|
-
expect(result.synced).toBe(0);
|
|
329
|
-
expect(result.cognified).toBe(false);
|
|
330
|
-
});
|
|
331
|
-
});
|
|
332
|
-
|
|
333
163
|
// ─── Enrich and Capture ─────────────────────────────────────
|
|
334
164
|
|
|
335
165
|
describe('enrichAndCapture', () => {
|
|
@@ -413,21 +243,6 @@ describe('Brain', () => {
|
|
|
413
243
|
const entry = vault.get('cap-5');
|
|
414
244
|
expect(entry!.tags.length).toBeGreaterThan(0);
|
|
415
245
|
});
|
|
416
|
-
|
|
417
|
-
it('should fire-and-forget sync to Cognee on capture', () => {
|
|
418
|
-
const cognee = makeMockCognee();
|
|
419
|
-
const hybridBrain = new Brain(vault, cognee);
|
|
420
|
-
hybridBrain.enrichAndCapture({
|
|
421
|
-
id: 'cap-cognee-1',
|
|
422
|
-
type: 'pattern',
|
|
423
|
-
domain: 'testing',
|
|
424
|
-
title: 'Cognee sync test',
|
|
425
|
-
severity: 'warning',
|
|
426
|
-
description: 'Testing fire-and-forget Cognee sync.',
|
|
427
|
-
tags: [],
|
|
428
|
-
});
|
|
429
|
-
expect(cognee.addEntries).toHaveBeenCalledTimes(1);
|
|
430
|
-
});
|
|
431
246
|
});
|
|
432
247
|
|
|
433
248
|
// ─── Duplicate Detection ────────────────────────────────────
|
|
@@ -539,7 +354,6 @@ describe('Brain', () => {
|
|
|
539
354
|
const stats = brain.getStats();
|
|
540
355
|
const sum =
|
|
541
356
|
stats.weights.semantic +
|
|
542
|
-
stats.weights.vector +
|
|
543
357
|
stats.weights.severity +
|
|
544
358
|
stats.weights.recency +
|
|
545
359
|
stats.weights.tagOverlap +
|
|
@@ -557,11 +371,6 @@ describe('Brain', () => {
|
|
|
557
371
|
const stats = brain.getStats();
|
|
558
372
|
expect(stats.weights.semantic).toBeCloseTo(0.4, 1);
|
|
559
373
|
});
|
|
560
|
-
|
|
561
|
-
it('should keep vector weight at 0 in base weights', () => {
|
|
562
|
-
const stats = brain.getStats();
|
|
563
|
-
expect(stats.weights.vector).toBe(0);
|
|
564
|
-
});
|
|
565
374
|
});
|
|
566
375
|
|
|
567
376
|
// ─── Vocabulary ─────────────────────────────────────────────
|
|
@@ -633,7 +442,6 @@ describe('Brain', () => {
|
|
|
633
442
|
expect(stats.vocabularySize).toBe(0);
|
|
634
443
|
expect(stats.feedbackCount).toBe(0);
|
|
635
444
|
expect(stats.weights.semantic).toBeCloseTo(0.4, 2);
|
|
636
|
-
expect(stats.weights.vector).toBe(0);
|
|
637
445
|
});
|
|
638
446
|
|
|
639
447
|
it('should return correct vocabulary size after seeding', () => {
|
|
@@ -667,7 +475,7 @@ describe('Brain', () => {
|
|
|
667
475
|
// ─── Get Relevant Patterns ──────────────────────────────────
|
|
668
476
|
|
|
669
477
|
describe('getRelevantPatterns', () => {
|
|
670
|
-
it('should return ranked results for query context',
|
|
478
|
+
it('should return ranked results for query context', () => {
|
|
671
479
|
vault.seed([
|
|
672
480
|
makeEntry({
|
|
673
481
|
id: 'rel-1',
|
|
@@ -685,15 +493,12 @@ describe('Brain', () => {
|
|
|
685
493
|
}),
|
|
686
494
|
]);
|
|
687
495
|
brain = new Brain(vault);
|
|
688
|
-
const results =
|
|
689
|
-
query: 'authentication',
|
|
690
|
-
domain: 'security',
|
|
691
|
-
});
|
|
496
|
+
const results = brain.getRelevantPatterns({ query: 'authentication', domain: 'security' });
|
|
692
497
|
expect(results.length).toBeGreaterThan(0);
|
|
693
498
|
});
|
|
694
499
|
|
|
695
|
-
it('should return empty for no context matches',
|
|
696
|
-
const results =
|
|
500
|
+
it('should return empty for no context matches', () => {
|
|
501
|
+
const results = brain.getRelevantPatterns({ query: 'nonexistent' });
|
|
697
502
|
expect(results).toEqual([]);
|
|
698
503
|
});
|
|
699
504
|
});
|
|
@@ -701,13 +506,13 @@ describe('Brain', () => {
|
|
|
701
506
|
// ─── Graceful Degradation ───────────────────────────────────
|
|
702
507
|
|
|
703
508
|
describe('graceful degradation', () => {
|
|
704
|
-
it('should work without vocabulary (empty vault)',
|
|
509
|
+
it('should work without vocabulary (empty vault)', () => {
|
|
705
510
|
expect(brain.getVocabularySize()).toBe(0);
|
|
706
|
-
const results =
|
|
511
|
+
const results = brain.intelligentSearch('anything');
|
|
707
512
|
expect(results).toEqual([]);
|
|
708
513
|
});
|
|
709
514
|
|
|
710
|
-
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', () => {
|
|
711
516
|
vault.seed([
|
|
712
517
|
makeEntry({
|
|
713
518
|
id: 'gd-1',
|
|
@@ -718,7 +523,7 @@ describe('Brain', () => {
|
|
|
718
523
|
}),
|
|
719
524
|
]);
|
|
720
525
|
brain = new Brain(vault);
|
|
721
|
-
const results =
|
|
526
|
+
const results = brain.intelligentSearch('fallback test');
|
|
722
527
|
expect(results.length).toBeGreaterThan(0);
|
|
723
528
|
expect(results[0].score).toBeGreaterThan(0);
|
|
724
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
|
+
});
|