@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.
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 -245
  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 -222
  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 -275
  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 -289
  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 -350
  68. package/src/cognee/types.ts +0 -62
@@ -0,0 +1,93 @@
1
+ import { describe, it, expect, afterEach } from 'vitest';
2
+ import { createAgentRuntime } from '../runtime/runtime.js';
3
+ import type { AgentRuntime } from '../runtime/types.js';
4
+
5
+ describe('createAgentRuntime', () => {
6
+ let runtime: AgentRuntime | null = null;
7
+
8
+ afterEach(() => {
9
+ runtime?.close();
10
+ runtime = null;
11
+ });
12
+
13
+ it('should create a runtime with all modules initialized', () => {
14
+ runtime = createAgentRuntime({
15
+ agentId: 'test-agent',
16
+ vaultPath: ':memory:',
17
+ });
18
+
19
+ expect(runtime.config.agentId).toBe('test-agent');
20
+ expect(runtime.vault).toBeDefined();
21
+ expect(runtime.brain).toBeDefined();
22
+ expect(runtime.planner).toBeDefined();
23
+ expect(runtime.curator).toBeDefined();
24
+ expect(runtime.keyPool.openai).toBeDefined();
25
+ expect(runtime.keyPool.anthropic).toBeDefined();
26
+ expect(runtime.llmClient).toBeDefined();
27
+ });
28
+
29
+ it('should use :memory: vault when specified', () => {
30
+ runtime = createAgentRuntime({
31
+ agentId: 'test-mem',
32
+ vaultPath: ':memory:',
33
+ });
34
+
35
+ const stats = runtime.vault.stats();
36
+ expect(stats.totalEntries).toBe(0);
37
+ });
38
+
39
+ it('should preserve config on runtime', () => {
40
+ runtime = createAgentRuntime({
41
+ agentId: 'test-cfg',
42
+ vaultPath: ':memory:',
43
+ dataDir: '/nonexistent',
44
+ });
45
+
46
+ expect(runtime.config.agentId).toBe('test-cfg');
47
+ expect(runtime.config.vaultPath).toBe(':memory:');
48
+ expect(runtime.config.dataDir).toBe('/nonexistent');
49
+ });
50
+
51
+ it('close() should not throw', () => {
52
+ runtime = createAgentRuntime({
53
+ agentId: 'test-close',
54
+ vaultPath: ':memory:',
55
+ });
56
+
57
+ expect(() => runtime!.close()).not.toThrow();
58
+ runtime = null; // already closed
59
+ });
60
+
61
+ it('brain should be wired to vault', () => {
62
+ runtime = createAgentRuntime({
63
+ agentId: 'test-brain-wire',
64
+ vaultPath: ':memory:',
65
+ });
66
+
67
+ // Seed some data through vault
68
+ runtime.vault.seed([{
69
+ id: 'rt-1',
70
+ type: 'pattern',
71
+ domain: 'testing',
72
+ title: 'Runtime test pattern',
73
+ severity: 'warning',
74
+ description: 'A test.',
75
+ tags: ['test'],
76
+ }]);
77
+
78
+ // Brain should find it
79
+ runtime.brain.rebuildVocabulary();
80
+ const results = runtime.brain.intelligentSearch('runtime test', { limit: 5 });
81
+ expect(results.length).toBeGreaterThan(0);
82
+ });
83
+
84
+ it('curator should be wired to vault', () => {
85
+ runtime = createAgentRuntime({
86
+ agentId: 'test-curator-wire',
87
+ vaultPath: ':memory:',
88
+ });
89
+
90
+ const status = runtime.curator.getStatus();
91
+ expect(status.initialized).toBe(true);
92
+ });
93
+ });
@@ -1,13 +1,18 @@
1
1
  import type { Vault } from '../vault/vault.js';
2
2
  import type { SearchResult } from '../vault/vault.js';
3
3
  import type { IntelligenceEntry } from '../intelligence/types.js';
4
- import type { CogneeClient } from '../cognee/client.js';
4
+ import {
5
+ tokenize,
6
+ calculateTf,
7
+ calculateTfIdf,
8
+ cosineSimilarity,
9
+ jaccardSimilarity,
10
+ } from '../text/similarity.js';
5
11
 
6
12
  // ─── Types ───────────────────────────────────────────────────────────
7
13
 
8
14
  export interface ScoringWeights {
9
15
  semantic: number;
10
- vector: number;
11
16
  severity: number;
12
17
  recency: number;
13
18
  tagOverlap: number;
@@ -16,7 +21,6 @@ export interface ScoringWeights {
16
21
 
17
22
  export interface ScoreBreakdown {
18
23
  semantic: number;
19
- vector: number;
20
24
  severity: number;
21
25
  recency: number;
22
26
  tagOverlap: number;
@@ -58,172 +62,6 @@ export interface QueryContext {
58
62
  tags?: string[];
59
63
  }
60
64
 
61
- type SparseVector = Map<string, number>;
62
-
63
- // ─── Stopwords ─────────────────────────────────────────────────────
64
-
65
- const STOPWORDS = new Set([
66
- 'a',
67
- 'an',
68
- 'the',
69
- 'and',
70
- 'or',
71
- 'but',
72
- 'in',
73
- 'on',
74
- 'at',
75
- 'to',
76
- 'for',
77
- 'of',
78
- 'with',
79
- 'by',
80
- 'from',
81
- 'as',
82
- 'is',
83
- 'was',
84
- 'are',
85
- 'were',
86
- 'been',
87
- 'be',
88
- 'have',
89
- 'has',
90
- 'had',
91
- 'do',
92
- 'does',
93
- 'did',
94
- 'will',
95
- 'would',
96
- 'could',
97
- 'should',
98
- 'may',
99
- 'might',
100
- 'shall',
101
- 'can',
102
- 'need',
103
- 'must',
104
- 'it',
105
- 'its',
106
- 'this',
107
- 'that',
108
- 'these',
109
- 'those',
110
- 'i',
111
- 'you',
112
- 'he',
113
- 'she',
114
- 'we',
115
- 'they',
116
- 'me',
117
- 'him',
118
- 'her',
119
- 'us',
120
- 'them',
121
- 'my',
122
- 'your',
123
- 'his',
124
- 'our',
125
- 'their',
126
- 'what',
127
- 'which',
128
- 'who',
129
- 'whom',
130
- 'when',
131
- 'where',
132
- 'why',
133
- 'how',
134
- 'all',
135
- 'each',
136
- 'every',
137
- 'both',
138
- 'few',
139
- 'more',
140
- 'most',
141
- 'other',
142
- 'some',
143
- 'such',
144
- 'no',
145
- 'not',
146
- 'only',
147
- 'same',
148
- 'so',
149
- 'than',
150
- 'too',
151
- 'very',
152
- 'just',
153
- 'because',
154
- 'if',
155
- 'then',
156
- 'else',
157
- 'about',
158
- 'up',
159
- 'out',
160
- 'into',
161
- ]);
162
-
163
- // ─── Text Processing (pure functions) ────────────────────────────
164
-
165
- function tokenize(text: string): string[] {
166
- return text
167
- .toLowerCase()
168
- .replace(/[^a-z0-9\s-]/g, ' ')
169
- .split(/\s+/)
170
- .filter((t) => t.length > 2 && !STOPWORDS.has(t));
171
- }
172
-
173
- function calculateTf(tokens: string[]): SparseVector {
174
- const tf: SparseVector = new Map();
175
- for (const token of tokens) {
176
- tf.set(token, (tf.get(token) ?? 0) + 1);
177
- }
178
- const len = tokens.length || 1;
179
- for (const [term, count] of tf) {
180
- tf.set(term, count / len);
181
- }
182
- return tf;
183
- }
184
-
185
- function calculateTfIdf(tokens: string[], vocabulary: Map<string, number>): SparseVector {
186
- const tf = calculateTf(tokens);
187
- const tfidf: SparseVector = new Map();
188
- for (const [term, tfValue] of tf) {
189
- const idf = vocabulary.get(term) ?? 0;
190
- if (idf > 0) {
191
- tfidf.set(term, tfValue * idf);
192
- }
193
- }
194
- return tfidf;
195
- }
196
-
197
- function cosineSimilarity(a: SparseVector, b: SparseVector): number {
198
- let dot = 0;
199
- let normA = 0;
200
- let normB = 0;
201
- for (const [term, valA] of a) {
202
- normA += valA * valA;
203
- const valB = b.get(term);
204
- if (valB !== undefined) {
205
- dot += valA * valB;
206
- }
207
- }
208
- for (const [, valB] of b) {
209
- normB += valB * valB;
210
- }
211
- const denom = Math.sqrt(normA) * Math.sqrt(normB);
212
- return denom === 0 ? 0 : dot / denom;
213
- }
214
-
215
- function jaccardSimilarity(a: string[], b: string[]): number {
216
- if (a.length === 0 && b.length === 0) return 0;
217
- const setA = new Set(a);
218
- const setB = new Set(b);
219
- let intersection = 0;
220
- for (const item of setA) {
221
- if (setB.has(item)) intersection++;
222
- }
223
- const union = new Set([...a, ...b]).size;
224
- return union === 0 ? 0 : intersection / union;
225
- }
226
-
227
65
  // ─── Severity scoring ──────────────────────────────────────────────
228
66
 
229
67
  const SEVERITY_SCORES: Record<string, number> = {
@@ -236,22 +74,12 @@ const SEVERITY_SCORES: Record<string, number> = {
236
74
 
237
75
  const DEFAULT_WEIGHTS: ScoringWeights = {
238
76
  semantic: 0.4,
239
- vector: 0.0,
240
77
  severity: 0.15,
241
78
  recency: 0.15,
242
79
  tagOverlap: 0.15,
243
80
  domainMatch: 0.15,
244
81
  };
245
82
 
246
- const COGNEE_WEIGHTS: ScoringWeights = {
247
- semantic: 0.25,
248
- vector: 0.35,
249
- severity: 0.1,
250
- recency: 0.1,
251
- tagOverlap: 0.1,
252
- domainMatch: 0.1,
253
- };
254
-
255
83
  const WEIGHT_BOUND = 0.15;
256
84
  const FEEDBACK_THRESHOLD = 30;
257
85
  const DUPLICATE_BLOCK_THRESHOLD = 0.8;
@@ -260,18 +88,16 @@ const RECENCY_HALF_LIFE_DAYS = 365;
260
88
 
261
89
  export class Brain {
262
90
  private vault: Vault;
263
- private cognee: CogneeClient | undefined;
264
91
  private vocabulary: Map<string, number> = new Map();
265
92
  private weights: ScoringWeights = { ...DEFAULT_WEIGHTS };
266
93
 
267
- constructor(vault: Vault, cognee?: CogneeClient) {
94
+ constructor(vault: Vault) {
268
95
  this.vault = vault;
269
- this.cognee = cognee;
270
96
  this.rebuildVocabulary();
271
97
  this.recomputeWeights();
272
98
  }
273
99
 
274
- async intelligentSearch(query: string, options?: SearchOptions): Promise<RankedResult[]> {
100
+ intelligentSearch(query: string, options?: SearchOptions): RankedResult[] {
275
101
  const limit = options?.limit ?? 10;
276
102
  const rawResults = this.vault.search(query, {
277
103
  domain: options?.domain,
@@ -280,30 +106,6 @@ export class Brain {
280
106
  limit: Math.max(limit * 3, 30),
281
107
  });
282
108
 
283
- // Cognee vector search (parallel, with timeout fallback)
284
- let cogneeScoreMap: Map<string, number> = new Map();
285
- const cogneeAvailable = this.cognee?.isAvailable ?? false;
286
- if (cogneeAvailable && this.cognee) {
287
- try {
288
- const cogneeResults = await this.cognee.search(query, { limit: Math.max(limit * 2, 20) });
289
- for (const cr of cogneeResults) {
290
- if (cr.id) cogneeScoreMap.set(cr.id, cr.score);
291
- }
292
- // Merge cognee-only entries into candidate pool
293
- for (const cr of cogneeResults) {
294
- if (cr.id && !rawResults.some((r) => r.entry.id === cr.id)) {
295
- const vaultEntry = this.vault.get(cr.id);
296
- if (vaultEntry) {
297
- rawResults.push({ entry: vaultEntry, score: cr.score });
298
- }
299
- }
300
- }
301
- } catch {
302
- // Cognee failed — fall back to FTS5 only
303
- cogneeScoreMap = new Map();
304
- }
305
- }
306
-
307
109
  if (rawResults.length === 0) return [];
308
110
 
309
111
  const queryTokens = tokenize(query);
@@ -311,22 +113,9 @@ export class Brain {
311
113
  const queryDomain = options?.domain;
312
114
  const now = Math.floor(Date.now() / 1000);
313
115
 
314
- // Use cognee-aware weights only if at least one ranked candidate has a vector score
315
- const hasVectorCandidate = rawResults.some((r) => cogneeScoreMap.has(r.entry.id));
316
- const activeWeights = hasVectorCandidate ? this.getCogneeWeights() : this.weights;
317
-
318
116
  const ranked = rawResults.map((result) => {
319
117
  const entry = result.entry;
320
- const vectorScore = cogneeScoreMap.get(entry.id) ?? 0;
321
- const breakdown = this.scoreEntry(
322
- entry,
323
- queryTokens,
324
- queryTags,
325
- queryDomain,
326
- now,
327
- vectorScore,
328
- activeWeights,
329
- );
118
+ const breakdown = this.scoreEntry(entry, queryTokens, queryTags, queryDomain, now);
330
119
  return { entry, score: breakdown.total, breakdown };
331
120
  });
332
121
 
@@ -377,11 +166,6 @@ export class Brain {
377
166
  this.vault.add(fullEntry);
378
167
  this.updateVocabularyIncremental(fullEntry);
379
168
 
380
- // Fire-and-forget Cognee sync
381
- if (this.cognee?.isAvailable) {
382
- this.cognee.addEntries([fullEntry]).catch(() => {});
383
- }
384
-
385
169
  const result: CaptureResult = {
386
170
  captured: true,
387
171
  id: entry.id,
@@ -405,39 +189,13 @@ export class Brain {
405
189
  this.recomputeWeights();
406
190
  }
407
191
 
408
- async getRelevantPatterns(context: QueryContext): Promise<RankedResult[]> {
192
+ getRelevantPatterns(context: QueryContext): RankedResult[] {
409
193
  return this.intelligentSearch(context.query, {
410
194
  domain: context.domain,
411
195
  tags: context.tags,
412
196
  });
413
197
  }
414
198
 
415
- async syncToCognee(): Promise<{ synced: number; cognified: boolean }> {
416
- if (!this.cognee?.isAvailable) return { synced: 0, cognified: false };
417
-
418
- const batchSize = 1000;
419
- let offset = 0;
420
- let totalSynced = 0;
421
-
422
- while (true) {
423
- const batch = this.vault.list({ limit: batchSize, offset });
424
- if (batch.length === 0) break;
425
-
426
- const { added } = await this.cognee.addEntries(batch);
427
- totalSynced += added;
428
- offset += batch.length;
429
-
430
- if (batch.length < batchSize) break;
431
- }
432
-
433
- if (totalSynced === 0) return { synced: 0, cognified: false };
434
-
435
- let cognified = false;
436
- const cognifyResult = await this.cognee.cognify();
437
- cognified = cognifyResult.status === 'ok';
438
- return { synced: totalSynced, cognified };
439
- }
440
-
441
199
  rebuildVocabulary(): void {
442
200
  const entries = this.vault.list({ limit: 100000 });
443
201
  const docCount = entries.length;
@@ -491,11 +249,7 @@ export class Brain {
491
249
  queryTags: string[],
492
250
  queryDomain: string | undefined,
493
251
  now: number,
494
- vectorScore: number = 0,
495
- activeWeights?: ScoringWeights,
496
252
  ): ScoreBreakdown {
497
- const w = activeWeights ?? this.weights;
498
-
499
253
  let semantic = 0;
500
254
  if (this.vocabulary.size > 0 && queryTokens.length > 0) {
501
255
  const entryText = [
@@ -520,17 +274,14 @@ export class Brain {
520
274
 
521
275
  const domainMatch = queryDomain && entry.domain === queryDomain ? 1.0 : 0;
522
276
 
523
- const vector = vectorScore;
524
-
525
277
  const total =
526
- w.semantic * semantic +
527
- w.vector * vector +
528
- w.severity * severity +
529
- w.recency * recency +
530
- w.tagOverlap * tagOverlap +
531
- w.domainMatch * domainMatch;
532
-
533
- return { semantic, vector, severity, recency, tagOverlap, domainMatch, total };
278
+ this.weights.semantic * semantic +
279
+ this.weights.severity * severity +
280
+ this.weights.recency * recency +
281
+ this.weights.tagOverlap * tagOverlap +
282
+ this.weights.domainMatch * domainMatch;
283
+
284
+ return { semantic, severity, recency, tagOverlap, domainMatch, total };
534
285
  }
535
286
 
536
287
  private generateTags(title: string, description: string, context?: string): string[] {
@@ -624,10 +375,6 @@ export class Brain {
624
375
  tx();
625
376
  }
626
377
 
627
- private getCogneeWeights(): ScoringWeights {
628
- return { ...COGNEE_WEIGHTS };
629
- }
630
-
631
378
  private recomputeWeights(): void {
632
379
  const db = this.vault.getDb();
633
380
  const feedbackCount = (
@@ -654,10 +401,7 @@ export class Brain {
654
401
  DEFAULT_WEIGHTS.semantic + WEIGHT_BOUND,
655
402
  );
656
403
 
657
- // vector stays 0 in base weights (only active during hybrid search)
658
- newWeights.vector = 0;
659
-
660
- const remaining = 1.0 - newWeights.semantic - newWeights.vector;
404
+ const remaining = 1.0 - newWeights.semantic;
661
405
  const otherSum =
662
406
  DEFAULT_WEIGHTS.severity +
663
407
  DEFAULT_WEIGHTS.recency +