@soleri/core 0.0.1 → 1.0.0
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 +76 -0
- package/dist/brain/brain.d.ts.map +1 -0
- package/dist/brain/brain.js +431 -0
- package/dist/brain/brain.js.map +1 -0
- package/dist/facades/facade-factory.d.ts +5 -0
- package/dist/facades/facade-factory.d.ts.map +1 -0
- package/dist/facades/facade-factory.js +49 -0
- package/dist/facades/facade-factory.js.map +1 -0
- package/dist/facades/types.d.ts +42 -0
- package/dist/facades/types.d.ts.map +1 -0
- package/dist/facades/types.js +6 -0
- package/dist/facades/types.js.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +17 -0
- package/dist/index.js.map +1 -0
- package/dist/intelligence/loader.d.ts +3 -0
- package/dist/intelligence/loader.d.ts.map +1 -0
- package/dist/intelligence/loader.js +41 -0
- package/dist/intelligence/loader.js.map +1 -0
- package/dist/intelligence/types.d.ts +20 -0
- package/dist/intelligence/types.d.ts.map +1 -0
- package/dist/intelligence/types.js +2 -0
- package/dist/intelligence/types.js.map +1 -0
- package/dist/llm/key-pool.d.ts +38 -0
- package/dist/llm/key-pool.d.ts.map +1 -0
- package/dist/llm/key-pool.js +154 -0
- package/dist/llm/key-pool.js.map +1 -0
- package/dist/llm/types.d.ts +80 -0
- package/dist/llm/types.d.ts.map +1 -0
- package/dist/llm/types.js +37 -0
- package/dist/llm/types.js.map +1 -0
- package/dist/llm/utils.d.ts +26 -0
- package/dist/llm/utils.d.ts.map +1 -0
- package/dist/llm/utils.js +197 -0
- package/dist/llm/utils.js.map +1 -0
- package/dist/planning/planner.d.ts +48 -0
- package/dist/planning/planner.d.ts.map +1 -0
- package/dist/planning/planner.js +109 -0
- package/dist/planning/planner.js.map +1 -0
- package/dist/vault/vault.d.ts +80 -0
- package/dist/vault/vault.d.ts.map +1 -0
- package/dist/vault/vault.js +353 -0
- package/dist/vault/vault.js.map +1 -0
- package/package.json +56 -4
- package/src/__tests__/brain.test.ts +545 -0
- package/src/__tests__/llm.test.ts +556 -0
- package/src/__tests__/loader.test.ts +176 -0
- package/src/__tests__/planner.test.ts +261 -0
- package/src/__tests__/vault.test.ts +494 -0
- package/src/brain/brain.ts +581 -0
- package/src/facades/facade-factory.ts +64 -0
- package/src/facades/types.ts +42 -0
- package/src/index.ts +64 -0
- package/src/intelligence/loader.ts +42 -0
- package/src/intelligence/types.ts +20 -0
- package/src/llm/key-pool.ts +190 -0
- package/src/llm/types.ts +116 -0
- package/src/llm/utils.ts +248 -0
- package/src/planning/planner.ts +151 -0
- package/src/vault/vault.ts +455 -0
- package/tsconfig.json +22 -0
- package/vitest.config.ts +15 -0
|
@@ -0,0 +1,545 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import { Vault } from '../vault/vault.js';
|
|
3
|
+
import { Brain } from '../brain/brain.js';
|
|
4
|
+
import type { IntelligenceEntry } from '../intelligence/types.js';
|
|
5
|
+
|
|
6
|
+
function makeEntry(overrides: Partial<IntelligenceEntry> = {}): IntelligenceEntry {
|
|
7
|
+
return {
|
|
8
|
+
id: overrides.id ?? 'test-1',
|
|
9
|
+
type: overrides.type ?? 'pattern',
|
|
10
|
+
domain: overrides.domain ?? 'testing',
|
|
11
|
+
title: overrides.title ?? 'Test Pattern',
|
|
12
|
+
severity: overrides.severity ?? 'warning',
|
|
13
|
+
description: overrides.description ?? 'A test pattern for unit tests.',
|
|
14
|
+
tags: overrides.tags ?? ['testing', 'assertions'],
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
describe('Brain', () => {
|
|
19
|
+
let vault: Vault;
|
|
20
|
+
let brain: Brain;
|
|
21
|
+
|
|
22
|
+
beforeEach(() => {
|
|
23
|
+
vault = new Vault(':memory:');
|
|
24
|
+
brain = new Brain(vault);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
afterEach(() => {
|
|
28
|
+
vault.close();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
// ─── Constructor ──────────────────────────────────────────────
|
|
32
|
+
|
|
33
|
+
describe('constructor', () => {
|
|
34
|
+
it('should create brain with empty vocabulary on empty vault', () => {
|
|
35
|
+
expect(brain.getVocabularySize()).toBe(0);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('should build vocabulary from existing entries', () => {
|
|
39
|
+
vault.seed([
|
|
40
|
+
makeEntry({
|
|
41
|
+
id: 'v1',
|
|
42
|
+
title: 'Input validation pattern',
|
|
43
|
+
description: 'Always validate user input at boundaries.',
|
|
44
|
+
tags: ['validation', 'security'],
|
|
45
|
+
}),
|
|
46
|
+
makeEntry({
|
|
47
|
+
id: 'v2',
|
|
48
|
+
title: 'Caching strategy',
|
|
49
|
+
description: 'Use cache-aside for read-heavy workloads.',
|
|
50
|
+
tags: ['caching', 'performance'],
|
|
51
|
+
}),
|
|
52
|
+
]);
|
|
53
|
+
const brain2 = new Brain(vault);
|
|
54
|
+
expect(brain2.getVocabularySize()).toBeGreaterThan(0);
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// ─── Intelligent Search ──────────────────────────────────────
|
|
59
|
+
|
|
60
|
+
describe('intelligentSearch', () => {
|
|
61
|
+
beforeEach(() => {
|
|
62
|
+
vault.seed([
|
|
63
|
+
makeEntry({
|
|
64
|
+
id: 'is-1',
|
|
65
|
+
title: 'Input validation pattern',
|
|
66
|
+
description:
|
|
67
|
+
'Always validate user input at system boundaries to prevent injection attacks.',
|
|
68
|
+
domain: 'security',
|
|
69
|
+
severity: 'critical',
|
|
70
|
+
tags: ['validation', 'security', 'input'],
|
|
71
|
+
}),
|
|
72
|
+
makeEntry({
|
|
73
|
+
id: 'is-2',
|
|
74
|
+
title: 'Caching strategy for APIs',
|
|
75
|
+
description: 'Use cache-aside pattern for read-heavy API workloads.',
|
|
76
|
+
domain: 'performance',
|
|
77
|
+
severity: 'warning',
|
|
78
|
+
tags: ['caching', 'api', 'performance'],
|
|
79
|
+
}),
|
|
80
|
+
makeEntry({
|
|
81
|
+
id: 'is-3',
|
|
82
|
+
title: 'Error handling best practices',
|
|
83
|
+
description: 'Use typed errors with context for better debugging experience.',
|
|
84
|
+
domain: 'clean-code',
|
|
85
|
+
severity: 'suggestion',
|
|
86
|
+
tags: ['errors', 'debugging'],
|
|
87
|
+
}),
|
|
88
|
+
]);
|
|
89
|
+
brain = new Brain(vault);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('should return ranked results', () => {
|
|
93
|
+
const results = brain.intelligentSearch('validation input');
|
|
94
|
+
expect(results.length).toBeGreaterThan(0);
|
|
95
|
+
expect(results[0].entry.id).toBe('is-1');
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('should include score breakdown', () => {
|
|
99
|
+
const results = brain.intelligentSearch('validation');
|
|
100
|
+
expect(results.length).toBeGreaterThan(0);
|
|
101
|
+
const breakdown = results[0].breakdown;
|
|
102
|
+
expect(breakdown).toHaveProperty('semantic');
|
|
103
|
+
expect(breakdown).toHaveProperty('severity');
|
|
104
|
+
expect(breakdown).toHaveProperty('recency');
|
|
105
|
+
expect(breakdown).toHaveProperty('tagOverlap');
|
|
106
|
+
expect(breakdown).toHaveProperty('domainMatch');
|
|
107
|
+
expect(breakdown).toHaveProperty('total');
|
|
108
|
+
expect(breakdown.total).toBe(results[0].score);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('should return empty array for no matches', () => {
|
|
112
|
+
const results = brain.intelligentSearch('xyznonexistent');
|
|
113
|
+
expect(results).toEqual([]);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('should respect limit', () => {
|
|
117
|
+
const results = brain.intelligentSearch('pattern', { limit: 1 });
|
|
118
|
+
expect(results.length).toBeLessThanOrEqual(1);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('should filter by domain', () => {
|
|
122
|
+
const results = brain.intelligentSearch('pattern', { domain: 'security' });
|
|
123
|
+
expect(results.every((r) => r.entry.domain === 'security')).toBe(true);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('should boost domain matches when domain is specified', () => {
|
|
127
|
+
const withDomain = brain.intelligentSearch('pattern', { domain: 'security' });
|
|
128
|
+
if (withDomain.length > 0) {
|
|
129
|
+
expect(withDomain[0].breakdown.domainMatch).toBe(1.0);
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it('should boost severity in scoring', () => {
|
|
134
|
+
const results = brain.intelligentSearch('pattern');
|
|
135
|
+
if (results.length >= 2) {
|
|
136
|
+
const critical = results.find((r) => r.entry.severity === 'critical');
|
|
137
|
+
const suggestion = results.find((r) => r.entry.severity === 'suggestion');
|
|
138
|
+
if (critical && suggestion) {
|
|
139
|
+
expect(critical.breakdown.severity).toBeGreaterThan(suggestion.breakdown.severity);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it('should boost tag overlap when tags provided', () => {
|
|
145
|
+
const results = brain.intelligentSearch('pattern', { tags: ['validation', 'security'] });
|
|
146
|
+
if (results.length > 0) {
|
|
147
|
+
const secEntry = results.find((r) => r.entry.id === 'is-1');
|
|
148
|
+
if (secEntry) {
|
|
149
|
+
expect(secEntry.breakdown.tagOverlap).toBeGreaterThan(0);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it('should handle search on empty vault gracefully', () => {
|
|
155
|
+
const emptyVault = new Vault(':memory:');
|
|
156
|
+
const emptyBrain = new Brain(emptyVault);
|
|
157
|
+
const results = emptyBrain.intelligentSearch('anything');
|
|
158
|
+
expect(results).toEqual([]);
|
|
159
|
+
emptyVault.close();
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
// ─── Enrich and Capture ─────────────────────────────────────
|
|
164
|
+
|
|
165
|
+
describe('enrichAndCapture', () => {
|
|
166
|
+
it('should capture entry and return auto-tags', () => {
|
|
167
|
+
const result = brain.enrichAndCapture({
|
|
168
|
+
id: 'cap-1',
|
|
169
|
+
type: 'pattern',
|
|
170
|
+
domain: 'security',
|
|
171
|
+
title: 'SQL injection prevention',
|
|
172
|
+
severity: 'critical',
|
|
173
|
+
description:
|
|
174
|
+
'Always use parameterized queries to prevent SQL injection attacks on database.',
|
|
175
|
+
tags: [],
|
|
176
|
+
});
|
|
177
|
+
expect(result.captured).toBe(true);
|
|
178
|
+
expect(result.id).toBe('cap-1');
|
|
179
|
+
expect(result.autoTags.length).toBeGreaterThan(0);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it('should merge auto-tags with user-provided tags', () => {
|
|
183
|
+
const result = brain.enrichAndCapture({
|
|
184
|
+
id: 'cap-2',
|
|
185
|
+
type: 'pattern',
|
|
186
|
+
domain: 'security',
|
|
187
|
+
title: 'XSS prevention methods',
|
|
188
|
+
severity: 'critical',
|
|
189
|
+
description:
|
|
190
|
+
'Sanitize all user input before rendering in the browser to prevent cross-site scripting.',
|
|
191
|
+
tags: ['user-tag'],
|
|
192
|
+
});
|
|
193
|
+
expect(result.captured).toBe(true);
|
|
194
|
+
const entry = vault.get('cap-2');
|
|
195
|
+
expect(entry).not.toBeNull();
|
|
196
|
+
expect(entry!.tags).toContain('user-tag');
|
|
197
|
+
expect(entry!.tags.length).toBeGreaterThan(1);
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it('should store entry in vault', () => {
|
|
201
|
+
brain.enrichAndCapture({
|
|
202
|
+
id: 'cap-3',
|
|
203
|
+
type: 'rule',
|
|
204
|
+
domain: 'testing',
|
|
205
|
+
title: 'Always test edge cases',
|
|
206
|
+
severity: 'warning',
|
|
207
|
+
description: 'Write tests for boundary values, null inputs, and error conditions.',
|
|
208
|
+
tags: ['testing'],
|
|
209
|
+
});
|
|
210
|
+
const entry = vault.get('cap-3');
|
|
211
|
+
expect(entry).not.toBeNull();
|
|
212
|
+
expect(entry!.title).toBe('Always test edge cases');
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
it('should update vocabulary incrementally after capture', () => {
|
|
216
|
+
const sizeBefore = brain.getVocabularySize();
|
|
217
|
+
brain.enrichAndCapture({
|
|
218
|
+
id: 'cap-4',
|
|
219
|
+
type: 'pattern',
|
|
220
|
+
domain: 'performance',
|
|
221
|
+
title: 'Connection pooling optimization',
|
|
222
|
+
severity: 'warning',
|
|
223
|
+
description:
|
|
224
|
+
'Use connection pooling for database connections to reduce overhead and improve throughput.',
|
|
225
|
+
tags: ['database', 'performance'],
|
|
226
|
+
});
|
|
227
|
+
expect(brain.getVocabularySize()).toBeGreaterThan(sizeBefore);
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
it('should capture entry without tags and auto-generate them', () => {
|
|
231
|
+
const result = brain.enrichAndCapture({
|
|
232
|
+
id: 'cap-5',
|
|
233
|
+
type: 'anti-pattern',
|
|
234
|
+
domain: 'clean-code',
|
|
235
|
+
title: 'Deeply nested conditionals',
|
|
236
|
+
severity: 'warning',
|
|
237
|
+
description:
|
|
238
|
+
'Avoid deeply nested if-else blocks. Use early returns and guard clauses instead.',
|
|
239
|
+
tags: [],
|
|
240
|
+
});
|
|
241
|
+
expect(result.captured).toBe(true);
|
|
242
|
+
expect(result.autoTags.length).toBeGreaterThan(0);
|
|
243
|
+
const entry = vault.get('cap-5');
|
|
244
|
+
expect(entry!.tags.length).toBeGreaterThan(0);
|
|
245
|
+
});
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
// ─── Duplicate Detection ────────────────────────────────────
|
|
249
|
+
|
|
250
|
+
describe('duplicate detection', () => {
|
|
251
|
+
beforeEach(() => {
|
|
252
|
+
vault.seed([
|
|
253
|
+
makeEntry({
|
|
254
|
+
id: 'dup-existing',
|
|
255
|
+
domain: 'security',
|
|
256
|
+
title: 'Input validation pattern for user forms',
|
|
257
|
+
description:
|
|
258
|
+
'Always validate user input at system boundaries to prevent injection attacks.',
|
|
259
|
+
tags: ['validation', 'security'],
|
|
260
|
+
}),
|
|
261
|
+
]);
|
|
262
|
+
brain = new Brain(vault);
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
it('should warn on similar entry', () => {
|
|
266
|
+
const result = brain.enrichAndCapture({
|
|
267
|
+
id: 'dup-new-1',
|
|
268
|
+
type: 'pattern',
|
|
269
|
+
domain: 'security',
|
|
270
|
+
title: 'Input validation pattern for user forms and APIs',
|
|
271
|
+
severity: 'warning',
|
|
272
|
+
description: 'Validate all user input at boundaries to block injection vectors.',
|
|
273
|
+
tags: ['validation'],
|
|
274
|
+
});
|
|
275
|
+
expect(result.captured).toBe(true);
|
|
276
|
+
if (result.duplicate) {
|
|
277
|
+
expect(result.duplicate.id).toBe('dup-existing');
|
|
278
|
+
expect(result.duplicate.similarity).toBeGreaterThanOrEqual(0.6);
|
|
279
|
+
}
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
it('should allow dissimilar entries without duplicate warning', () => {
|
|
283
|
+
const result = brain.enrichAndCapture({
|
|
284
|
+
id: 'dup-different',
|
|
285
|
+
type: 'pattern',
|
|
286
|
+
domain: 'security',
|
|
287
|
+
title: 'Rate limiting configuration',
|
|
288
|
+
severity: 'warning',
|
|
289
|
+
description: 'Configure rate limits on API endpoints to prevent abuse.',
|
|
290
|
+
tags: ['rate-limiting'],
|
|
291
|
+
});
|
|
292
|
+
expect(result.captured).toBe(true);
|
|
293
|
+
});
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
// ─── Record Feedback ────────────────────────────────────────
|
|
297
|
+
|
|
298
|
+
describe('recordFeedback', () => {
|
|
299
|
+
it('should record feedback in database', () => {
|
|
300
|
+
brain.recordFeedback('test query', 'entry-1', 'accepted');
|
|
301
|
+
const stats = brain.getStats();
|
|
302
|
+
expect(stats.feedbackCount).toBe(1);
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
it('should record multiple feedback entries', () => {
|
|
306
|
+
brain.recordFeedback('query-1', 'entry-1', 'accepted');
|
|
307
|
+
brain.recordFeedback('query-2', 'entry-2', 'dismissed');
|
|
308
|
+
brain.recordFeedback('query-3', 'entry-3', 'accepted');
|
|
309
|
+
const stats = brain.getStats();
|
|
310
|
+
expect(stats.feedbackCount).toBe(3);
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
it('should keep default weights below feedback threshold', () => {
|
|
314
|
+
for (let i = 0; i < 10; i++) {
|
|
315
|
+
brain.recordFeedback('q' + i, 'e' + i, 'accepted');
|
|
316
|
+
}
|
|
317
|
+
const stats = brain.getStats();
|
|
318
|
+
expect(stats.weights.semantic).toBeCloseTo(0.4, 2);
|
|
319
|
+
});
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
// ─── Adaptive Weights ───────────────────────────────────────
|
|
323
|
+
|
|
324
|
+
describe('adaptive weights', () => {
|
|
325
|
+
it('should adjust weights after reaching feedback threshold', () => {
|
|
326
|
+
for (let i = 0; i < 35; i++) {
|
|
327
|
+
brain.recordFeedback('query-' + i, 'entry-' + i, 'accepted');
|
|
328
|
+
}
|
|
329
|
+
const stats = brain.getStats();
|
|
330
|
+
expect(stats.weights.semantic).toBeGreaterThan(0.4);
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
it('should decrease semantic weight with high dismiss rate', () => {
|
|
334
|
+
for (let i = 0; i < 35; i++) {
|
|
335
|
+
brain.recordFeedback('query-' + i, 'entry-' + i, 'dismissed');
|
|
336
|
+
}
|
|
337
|
+
const stats = brain.getStats();
|
|
338
|
+
expect(stats.weights.semantic).toBeLessThan(0.4);
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
it('should keep weights bounded within +/-0.15 of defaults', () => {
|
|
342
|
+
for (let i = 0; i < 50; i++) {
|
|
343
|
+
brain.recordFeedback('query-' + i, 'entry-' + i, 'accepted');
|
|
344
|
+
}
|
|
345
|
+
const stats = brain.getStats();
|
|
346
|
+
expect(stats.weights.semantic).toBeLessThanOrEqual(0.55);
|
|
347
|
+
expect(stats.weights.semantic).toBeGreaterThanOrEqual(0.25);
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
it('should normalize weights to sum to 1.0', () => {
|
|
351
|
+
for (let i = 0; i < 35; i++) {
|
|
352
|
+
brain.recordFeedback('query-' + i, 'entry-' + i, 'accepted');
|
|
353
|
+
}
|
|
354
|
+
const stats = brain.getStats();
|
|
355
|
+
const sum =
|
|
356
|
+
stats.weights.semantic +
|
|
357
|
+
stats.weights.severity +
|
|
358
|
+
stats.weights.recency +
|
|
359
|
+
stats.weights.tagOverlap +
|
|
360
|
+
stats.weights.domainMatch;
|
|
361
|
+
expect(sum).toBeCloseTo(1.0, 5);
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
it('should keep default weights with balanced feedback', () => {
|
|
365
|
+
for (let i = 0; i < 20; i++) {
|
|
366
|
+
brain.recordFeedback('qa-' + i, 'ea-' + i, 'accepted');
|
|
367
|
+
}
|
|
368
|
+
for (let i = 0; i < 20; i++) {
|
|
369
|
+
brain.recordFeedback('qd-' + i, 'ed-' + i, 'dismissed');
|
|
370
|
+
}
|
|
371
|
+
const stats = brain.getStats();
|
|
372
|
+
expect(stats.weights.semantic).toBeCloseTo(0.4, 1);
|
|
373
|
+
});
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
// ─── Vocabulary ─────────────────────────────────────────────
|
|
377
|
+
|
|
378
|
+
describe('vocabulary', () => {
|
|
379
|
+
it('should rebuild vocabulary from vault entries', () => {
|
|
380
|
+
vault.seed([
|
|
381
|
+
makeEntry({
|
|
382
|
+
id: 'voc-1',
|
|
383
|
+
title: 'Authentication pattern',
|
|
384
|
+
description: 'JWT tokens for API auth.',
|
|
385
|
+
tags: ['auth', 'jwt'],
|
|
386
|
+
}),
|
|
387
|
+
makeEntry({
|
|
388
|
+
id: 'voc-2',
|
|
389
|
+
title: 'Authorization rules',
|
|
390
|
+
description: 'Role-based access control.',
|
|
391
|
+
tags: ['rbac', 'auth'],
|
|
392
|
+
}),
|
|
393
|
+
]);
|
|
394
|
+
brain.rebuildVocabulary();
|
|
395
|
+
expect(brain.getVocabularySize()).toBeGreaterThan(0);
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
it('should clear vocabulary when vault is empty', () => {
|
|
399
|
+
vault.seed([
|
|
400
|
+
makeEntry({
|
|
401
|
+
id: 'voc-3',
|
|
402
|
+
title: 'Temp entry',
|
|
403
|
+
description: 'Will be removed.',
|
|
404
|
+
tags: ['temp'],
|
|
405
|
+
}),
|
|
406
|
+
]);
|
|
407
|
+
brain.rebuildVocabulary();
|
|
408
|
+
expect(brain.getVocabularySize()).toBeGreaterThan(0);
|
|
409
|
+
vault.remove('voc-3');
|
|
410
|
+
brain.rebuildVocabulary();
|
|
411
|
+
expect(brain.getVocabularySize()).toBe(0);
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
it('should persist vocabulary to database', () => {
|
|
415
|
+
vault.seed([
|
|
416
|
+
makeEntry({
|
|
417
|
+
id: 'voc-4',
|
|
418
|
+
title: 'Persistent vocabulary test',
|
|
419
|
+
description: 'Testing database persistence.',
|
|
420
|
+
tags: ['persistence'],
|
|
421
|
+
}),
|
|
422
|
+
]);
|
|
423
|
+
brain.rebuildVocabulary();
|
|
424
|
+
const db = vault.getDb();
|
|
425
|
+
const count = (
|
|
426
|
+
db.prepare('SELECT COUNT(*) as count FROM brain_vocabulary').get() as { count: number }
|
|
427
|
+
).count;
|
|
428
|
+
expect(count).toBeGreaterThan(0);
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
it('should handle rebuild on empty vault gracefully', () => {
|
|
432
|
+
brain.rebuildVocabulary();
|
|
433
|
+
expect(brain.getVocabularySize()).toBe(0);
|
|
434
|
+
});
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
// ─── Stats ──────────────────────────────────────────────────
|
|
438
|
+
|
|
439
|
+
describe('getStats', () => {
|
|
440
|
+
it('should return stats with zero counts for new brain', () => {
|
|
441
|
+
const stats = brain.getStats();
|
|
442
|
+
expect(stats.vocabularySize).toBe(0);
|
|
443
|
+
expect(stats.feedbackCount).toBe(0);
|
|
444
|
+
expect(stats.weights.semantic).toBeCloseTo(0.4, 2);
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
it('should return correct vocabulary size after seeding', () => {
|
|
448
|
+
vault.seed([
|
|
449
|
+
makeEntry({
|
|
450
|
+
id: 'st-1',
|
|
451
|
+
title: 'Pattern one',
|
|
452
|
+
description: 'Description one.',
|
|
453
|
+
tags: ['a'],
|
|
454
|
+
}),
|
|
455
|
+
makeEntry({
|
|
456
|
+
id: 'st-2',
|
|
457
|
+
title: 'Pattern two',
|
|
458
|
+
description: 'Description two.',
|
|
459
|
+
tags: ['b'],
|
|
460
|
+
}),
|
|
461
|
+
]);
|
|
462
|
+
brain.rebuildVocabulary();
|
|
463
|
+
const stats = brain.getStats();
|
|
464
|
+
expect(stats.vocabularySize).toBeGreaterThan(0);
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
it('should return correct feedback count', () => {
|
|
468
|
+
brain.recordFeedback('q1', 'e1', 'accepted');
|
|
469
|
+
brain.recordFeedback('q2', 'e2', 'dismissed');
|
|
470
|
+
const stats = brain.getStats();
|
|
471
|
+
expect(stats.feedbackCount).toBe(2);
|
|
472
|
+
});
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
// ─── Get Relevant Patterns ──────────────────────────────────
|
|
476
|
+
|
|
477
|
+
describe('getRelevantPatterns', () => {
|
|
478
|
+
it('should return ranked results for query context', () => {
|
|
479
|
+
vault.seed([
|
|
480
|
+
makeEntry({
|
|
481
|
+
id: 'rel-1',
|
|
482
|
+
title: 'Authentication pattern',
|
|
483
|
+
description: 'JWT for API auth.',
|
|
484
|
+
domain: 'security',
|
|
485
|
+
tags: ['auth'],
|
|
486
|
+
}),
|
|
487
|
+
makeEntry({
|
|
488
|
+
id: 'rel-2',
|
|
489
|
+
title: 'Database indexing',
|
|
490
|
+
description: 'Index frequently queried columns.',
|
|
491
|
+
domain: 'performance',
|
|
492
|
+
tags: ['indexing'],
|
|
493
|
+
}),
|
|
494
|
+
]);
|
|
495
|
+
brain = new Brain(vault);
|
|
496
|
+
const results = brain.getRelevantPatterns({ query: 'authentication', domain: 'security' });
|
|
497
|
+
expect(results.length).toBeGreaterThan(0);
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
it('should return empty for no context matches', () => {
|
|
501
|
+
const results = brain.getRelevantPatterns({ query: 'nonexistent' });
|
|
502
|
+
expect(results).toEqual([]);
|
|
503
|
+
});
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
// ─── Graceful Degradation ───────────────────────────────────
|
|
507
|
+
|
|
508
|
+
describe('graceful degradation', () => {
|
|
509
|
+
it('should work without vocabulary (empty vault)', () => {
|
|
510
|
+
expect(brain.getVocabularySize()).toBe(0);
|
|
511
|
+
const results = brain.intelligentSearch('anything');
|
|
512
|
+
expect(results).toEqual([]);
|
|
513
|
+
});
|
|
514
|
+
|
|
515
|
+
it('should fall back to severity + recency scoring when vocabulary is empty', () => {
|
|
516
|
+
vault.seed([
|
|
517
|
+
makeEntry({
|
|
518
|
+
id: 'gd-1',
|
|
519
|
+
title: 'Fallback test pattern',
|
|
520
|
+
description: 'Testing graceful degradation.',
|
|
521
|
+
severity: 'critical',
|
|
522
|
+
tags: ['fallback'],
|
|
523
|
+
}),
|
|
524
|
+
]);
|
|
525
|
+
brain = new Brain(vault);
|
|
526
|
+
const results = brain.intelligentSearch('fallback test');
|
|
527
|
+
expect(results.length).toBeGreaterThan(0);
|
|
528
|
+
expect(results[0].score).toBeGreaterThan(0);
|
|
529
|
+
});
|
|
530
|
+
|
|
531
|
+
it('should handle capture on empty vault without errors', () => {
|
|
532
|
+
const result = brain.enrichAndCapture({
|
|
533
|
+
id: 'gd-cap-1',
|
|
534
|
+
type: 'pattern',
|
|
535
|
+
domain: 'testing',
|
|
536
|
+
title: 'First pattern ever',
|
|
537
|
+
severity: 'warning',
|
|
538
|
+
description: 'The very first pattern captured in an empty vault.',
|
|
539
|
+
tags: [],
|
|
540
|
+
});
|
|
541
|
+
expect(result.captured).toBe(true);
|
|
542
|
+
expect(result.autoTags.length).toBeGreaterThan(0);
|
|
543
|
+
});
|
|
544
|
+
});
|
|
545
|
+
});
|