@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.
Files changed (68) hide show
  1. package/dist/brain/brain.d.ts +3 -12
  2. package/dist/brain/brain.d.ts.map +1 -1
  3. package/dist/brain/brain.js +13 -305
  4. package/dist/brain/brain.js.map +1 -1
  5. package/dist/curator/curator.d.ts +28 -0
  6. package/dist/curator/curator.d.ts.map +1 -0
  7. package/dist/curator/curator.js +523 -0
  8. package/dist/curator/curator.js.map +1 -0
  9. package/dist/curator/types.d.ts +87 -0
  10. package/dist/curator/types.d.ts.map +1 -0
  11. package/dist/curator/types.js +3 -0
  12. package/dist/curator/types.js.map +1 -0
  13. package/dist/facades/types.d.ts +1 -1
  14. package/dist/index.d.ts +9 -2
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +10 -2
  17. package/dist/index.js.map +1 -1
  18. package/dist/llm/llm-client.d.ts +28 -0
  19. package/dist/llm/llm-client.d.ts.map +1 -0
  20. package/dist/llm/llm-client.js +219 -0
  21. package/dist/llm/llm-client.js.map +1 -0
  22. package/dist/runtime/core-ops.d.ts +17 -0
  23. package/dist/runtime/core-ops.d.ts.map +1 -0
  24. package/dist/runtime/core-ops.js +448 -0
  25. package/dist/runtime/core-ops.js.map +1 -0
  26. package/dist/runtime/domain-ops.d.ts +25 -0
  27. package/dist/runtime/domain-ops.d.ts.map +1 -0
  28. package/dist/runtime/domain-ops.js +130 -0
  29. package/dist/runtime/domain-ops.js.map +1 -0
  30. package/dist/runtime/runtime.d.ts +19 -0
  31. package/dist/runtime/runtime.d.ts.map +1 -0
  32. package/dist/runtime/runtime.js +62 -0
  33. package/dist/runtime/runtime.js.map +1 -0
  34. package/dist/runtime/types.d.ts +39 -0
  35. package/dist/runtime/types.d.ts.map +1 -0
  36. package/dist/runtime/types.js +2 -0
  37. package/dist/{cognee → runtime}/types.js.map +1 -1
  38. package/dist/text/similarity.d.ts +8 -0
  39. package/dist/text/similarity.d.ts.map +1 -0
  40. package/dist/text/similarity.js +161 -0
  41. package/dist/text/similarity.js.map +1 -0
  42. package/package.json +6 -2
  43. package/src/__tests__/brain.test.ts +27 -265
  44. package/src/__tests__/core-ops.test.ts +190 -0
  45. package/src/__tests__/curator.test.ts +479 -0
  46. package/src/__tests__/domain-ops.test.ts +124 -0
  47. package/src/__tests__/llm-client.test.ts +69 -0
  48. package/src/__tests__/runtime.test.ts +93 -0
  49. package/src/brain/brain.ts +19 -342
  50. package/src/curator/curator.ts +662 -0
  51. package/src/curator/types.ts +114 -0
  52. package/src/index.ts +40 -11
  53. package/src/llm/llm-client.ts +316 -0
  54. package/src/runtime/core-ops.ts +472 -0
  55. package/src/runtime/domain-ops.ts +144 -0
  56. package/src/runtime/runtime.ts +71 -0
  57. package/src/runtime/types.ts +37 -0
  58. package/src/text/similarity.ts +168 -0
  59. package/dist/cognee/client.d.ts +0 -35
  60. package/dist/cognee/client.d.ts.map +0 -1
  61. package/dist/cognee/client.js +0 -291
  62. package/dist/cognee/client.js.map +0 -1
  63. package/dist/cognee/types.d.ts +0 -46
  64. package/dist/cognee/types.d.ts.map +0 -1
  65. package/dist/cognee/types.js +0 -3
  66. package/src/__tests__/cognee-client.test.ts +0 -524
  67. package/src/cognee/client.ts +0 -352
  68. package/src/cognee/types.ts +0 -62
@@ -1,9 +1,7 @@
1
- import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
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', async () => {
131
- const results = await brain.intelligentSearch('validation input');
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 with vector field', async () => {
137
- const results = await brain.intelligentSearch('validation');
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', async () => {
153
- const results = await brain.intelligentSearch('xyznonexistent');
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', async () => {
158
- const results = await brain.intelligentSearch('pattern', { limit: 1 });
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', async () => {
163
- const results = await brain.intelligentSearch('pattern', { domain: 'security' });
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', async () => {
168
- const withDomain = await brain.intelligentSearch('pattern', { domain: 'security' });
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', async () => {
175
- const results = await brain.intelligentSearch('pattern');
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', async () => {
186
- const results = await brain.intelligentSearch('pattern', {
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', async () => {
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 = await emptyBrain.intelligentSearch('anything');
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', async () => {
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 = await brain.getRelevantPatterns({
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', async () => {
739
- const results = await brain.getRelevantPatterns({ query: 'nonexistent' });
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)', async () => {
509
+ it('should work without vocabulary (empty vault)', () => {
748
510
  expect(brain.getVocabularySize()).toBe(0);
749
- const results = await brain.intelligentSearch('anything');
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', async () => {
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 = await brain.intelligentSearch('fallback test');
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
+ });