@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
|
@@ -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
|
+
});
|
package/src/brain/brain.ts
CHANGED
|
@@ -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
|
|
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
|
|
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
|
-
|
|
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,97 +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
|
-
|
|
290
|
-
// Build title → entryIds reverse index from FTS results for text-based matching.
|
|
291
|
-
// Cognee assigns its own UUIDs to chunks and may strip embedded metadata during
|
|
292
|
-
// chunking, so we need multiple strategies to cross-reference results.
|
|
293
|
-
// Multiple entries can share a title, so map to arrays of IDs.
|
|
294
|
-
const titleToIds = new Map<string, string[]>();
|
|
295
|
-
for (const r of rawResults) {
|
|
296
|
-
const key = r.entry.title.toLowerCase().trim();
|
|
297
|
-
const ids = titleToIds.get(key) ?? [];
|
|
298
|
-
ids.push(r.entry.id);
|
|
299
|
-
titleToIds.set(key, ids);
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
const vaultIdPattern = /\[vault-id:([^\]]+)\]/;
|
|
303
|
-
const unmatchedCogneeResults: Array<{ text: string; score: number }> = [];
|
|
304
|
-
|
|
305
|
-
for (const cr of cogneeResults) {
|
|
306
|
-
const text = cr.text ?? '';
|
|
307
|
-
|
|
308
|
-
// Strategy 1: Extract vault ID from [vault-id:XXX] prefix (if Cognee preserved it)
|
|
309
|
-
const vaultIdMatch = text.match(vaultIdPattern);
|
|
310
|
-
if (vaultIdMatch) {
|
|
311
|
-
const vaultId = vaultIdMatch[1];
|
|
312
|
-
cogneeScoreMap.set(vaultId, Math.max(cogneeScoreMap.get(vaultId) ?? 0, cr.score));
|
|
313
|
-
continue;
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
// Strategy 2: Match first line of chunk text against known entry titles.
|
|
317
|
-
// serializeEntry() puts the title on the first line after the [vault-id:] prefix,
|
|
318
|
-
// and Cognee's chunking typically preserves this as the chunk start.
|
|
319
|
-
const firstLine = text.split('\n')[0]?.trim().toLowerCase() ?? '';
|
|
320
|
-
const matchedIds = firstLine ? titleToIds.get(firstLine) : undefined;
|
|
321
|
-
if (matchedIds) {
|
|
322
|
-
for (const id of matchedIds) {
|
|
323
|
-
cogneeScoreMap.set(id, Math.max(cogneeScoreMap.get(id) ?? 0, cr.score));
|
|
324
|
-
}
|
|
325
|
-
continue;
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
// Strategy 3: Check if any known title appears as a substring in the chunk.
|
|
329
|
-
// Handles cases where the title isn't on the first line (mid-document chunks).
|
|
330
|
-
const textLower = text.toLowerCase();
|
|
331
|
-
let found = false;
|
|
332
|
-
for (const [title, ids] of titleToIds) {
|
|
333
|
-
if (title.length >= 8 && textLower.includes(title)) {
|
|
334
|
-
for (const id of ids) {
|
|
335
|
-
cogneeScoreMap.set(id, Math.max(cogneeScoreMap.get(id) ?? 0, cr.score));
|
|
336
|
-
}
|
|
337
|
-
found = true;
|
|
338
|
-
break;
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
if (!found && text.length > 0) {
|
|
342
|
-
unmatchedCogneeResults.push({ text, score: cr.score });
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
// Strategy 4: For Cognee-only semantic matches (not in FTS results),
|
|
347
|
-
// use the first line as a vault FTS query to find the source entry.
|
|
348
|
-
// Preserve caller filters (domain/type/severity) to avoid reintroducing
|
|
349
|
-
// entries the original query excluded.
|
|
350
|
-
for (const unmatched of unmatchedCogneeResults) {
|
|
351
|
-
const searchTerm = unmatched.text.split('\n')[0]?.trim();
|
|
352
|
-
if (!searchTerm || searchTerm.length < 3) continue;
|
|
353
|
-
const vaultHits = this.vault.search(searchTerm, {
|
|
354
|
-
domain: options?.domain,
|
|
355
|
-
type: options?.type,
|
|
356
|
-
severity: options?.severity,
|
|
357
|
-
limit: 1,
|
|
358
|
-
});
|
|
359
|
-
if (vaultHits.length > 0) {
|
|
360
|
-
const id = vaultHits[0].entry.id;
|
|
361
|
-
cogneeScoreMap.set(id, Math.max(cogneeScoreMap.get(id) ?? 0, unmatched.score));
|
|
362
|
-
// Also add to FTS results pool if not already present
|
|
363
|
-
if (!rawResults.some((r) => r.entry.id === id)) {
|
|
364
|
-
rawResults.push(vaultHits[0]);
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
} catch {
|
|
369
|
-
// Cognee failed — fall back to FTS5 only
|
|
370
|
-
cogneeScoreMap = new Map();
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
|
|
374
109
|
if (rawResults.length === 0) return [];
|
|
375
110
|
|
|
376
111
|
const queryTokens = tokenize(query);
|
|
@@ -378,22 +113,9 @@ export class Brain {
|
|
|
378
113
|
const queryDomain = options?.domain;
|
|
379
114
|
const now = Math.floor(Date.now() / 1000);
|
|
380
115
|
|
|
381
|
-
// Use cognee-aware weights only if at least one ranked candidate has a vector score
|
|
382
|
-
const hasVectorCandidate = rawResults.some((r) => cogneeScoreMap.has(r.entry.id));
|
|
383
|
-
const activeWeights = hasVectorCandidate ? this.getCogneeWeights() : this.weights;
|
|
384
|
-
|
|
385
116
|
const ranked = rawResults.map((result) => {
|
|
386
117
|
const entry = result.entry;
|
|
387
|
-
const
|
|
388
|
-
const breakdown = this.scoreEntry(
|
|
389
|
-
entry,
|
|
390
|
-
queryTokens,
|
|
391
|
-
queryTags,
|
|
392
|
-
queryDomain,
|
|
393
|
-
now,
|
|
394
|
-
vectorScore,
|
|
395
|
-
activeWeights,
|
|
396
|
-
);
|
|
118
|
+
const breakdown = this.scoreEntry(entry, queryTokens, queryTags, queryDomain, now);
|
|
397
119
|
return { entry, score: breakdown.total, breakdown };
|
|
398
120
|
});
|
|
399
121
|
|
|
@@ -444,11 +166,6 @@ export class Brain {
|
|
|
444
166
|
this.vault.add(fullEntry);
|
|
445
167
|
this.updateVocabularyIncremental(fullEntry);
|
|
446
168
|
|
|
447
|
-
// Fire-and-forget Cognee sync
|
|
448
|
-
if (this.cognee?.isAvailable) {
|
|
449
|
-
this.cognee.addEntries([fullEntry]).catch(() => {});
|
|
450
|
-
}
|
|
451
|
-
|
|
452
169
|
const result: CaptureResult = {
|
|
453
170
|
captured: true,
|
|
454
171
|
id: entry.id,
|
|
@@ -472,39 +189,13 @@ export class Brain {
|
|
|
472
189
|
this.recomputeWeights();
|
|
473
190
|
}
|
|
474
191
|
|
|
475
|
-
|
|
192
|
+
getRelevantPatterns(context: QueryContext): RankedResult[] {
|
|
476
193
|
return this.intelligentSearch(context.query, {
|
|
477
194
|
domain: context.domain,
|
|
478
195
|
tags: context.tags,
|
|
479
196
|
});
|
|
480
197
|
}
|
|
481
198
|
|
|
482
|
-
async syncToCognee(): Promise<{ synced: number; cognified: boolean }> {
|
|
483
|
-
if (!this.cognee?.isAvailable) return { synced: 0, cognified: false };
|
|
484
|
-
|
|
485
|
-
const batchSize = 1000;
|
|
486
|
-
let offset = 0;
|
|
487
|
-
let totalSynced = 0;
|
|
488
|
-
|
|
489
|
-
while (true) {
|
|
490
|
-
const batch = this.vault.list({ limit: batchSize, offset });
|
|
491
|
-
if (batch.length === 0) break;
|
|
492
|
-
|
|
493
|
-
const { added } = await this.cognee.addEntries(batch);
|
|
494
|
-
totalSynced += added;
|
|
495
|
-
offset += batch.length;
|
|
496
|
-
|
|
497
|
-
if (batch.length < batchSize) break;
|
|
498
|
-
}
|
|
499
|
-
|
|
500
|
-
if (totalSynced === 0) return { synced: 0, cognified: false };
|
|
501
|
-
|
|
502
|
-
let cognified = false;
|
|
503
|
-
const cognifyResult = await this.cognee.cognify();
|
|
504
|
-
cognified = cognifyResult.status === 'ok';
|
|
505
|
-
return { synced: totalSynced, cognified };
|
|
506
|
-
}
|
|
507
|
-
|
|
508
199
|
rebuildVocabulary(): void {
|
|
509
200
|
const entries = this.vault.list({ limit: 100000 });
|
|
510
201
|
const docCount = entries.length;
|
|
@@ -558,11 +249,7 @@ export class Brain {
|
|
|
558
249
|
queryTags: string[],
|
|
559
250
|
queryDomain: string | undefined,
|
|
560
251
|
now: number,
|
|
561
|
-
vectorScore: number = 0,
|
|
562
|
-
activeWeights?: ScoringWeights,
|
|
563
252
|
): ScoreBreakdown {
|
|
564
|
-
const w = activeWeights ?? this.weights;
|
|
565
|
-
|
|
566
253
|
let semantic = 0;
|
|
567
254
|
if (this.vocabulary.size > 0 && queryTokens.length > 0) {
|
|
568
255
|
const entryText = [
|
|
@@ -587,17 +274,14 @@ export class Brain {
|
|
|
587
274
|
|
|
588
275
|
const domainMatch = queryDomain && entry.domain === queryDomain ? 1.0 : 0;
|
|
589
276
|
|
|
590
|
-
const vector = vectorScore;
|
|
591
|
-
|
|
592
277
|
const total =
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
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 };
|
|
601
285
|
}
|
|
602
286
|
|
|
603
287
|
private generateTags(title: string, description: string, context?: string): string[] {
|
|
@@ -691,10 +375,6 @@ export class Brain {
|
|
|
691
375
|
tx();
|
|
692
376
|
}
|
|
693
377
|
|
|
694
|
-
private getCogneeWeights(): ScoringWeights {
|
|
695
|
-
return { ...COGNEE_WEIGHTS };
|
|
696
|
-
}
|
|
697
|
-
|
|
698
378
|
private recomputeWeights(): void {
|
|
699
379
|
const db = this.vault.getDb();
|
|
700
380
|
const feedbackCount = (
|
|
@@ -721,10 +401,7 @@ export class Brain {
|
|
|
721
401
|
DEFAULT_WEIGHTS.semantic + WEIGHT_BOUND,
|
|
722
402
|
);
|
|
723
403
|
|
|
724
|
-
|
|
725
|
-
newWeights.vector = 0;
|
|
726
|
-
|
|
727
|
-
const remaining = 1.0 - newWeights.semantic - newWeights.vector;
|
|
404
|
+
const remaining = 1.0 - newWeights.semantic;
|
|
728
405
|
const otherSum =
|
|
729
406
|
DEFAULT_WEIGHTS.severity +
|
|
730
407
|
DEFAULT_WEIGHTS.recency +
|