@soleri/core 2.0.1 → 2.1.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 +2 -49
- package/dist/brain/brain.d.ts.map +1 -1
- package/dist/brain/brain.js +1 -158
- package/dist/brain/brain.js.map +1 -1
- package/dist/brain/intelligence.d.ts +51 -0
- package/dist/brain/intelligence.d.ts.map +1 -0
- package/dist/brain/intelligence.js +666 -0
- package/dist/brain/intelligence.js.map +1 -0
- package/dist/brain/types.d.ts +165 -0
- package/dist/brain/types.d.ts.map +1 -0
- package/dist/brain/types.js +2 -0
- package/dist/brain/types.js.map +1 -0
- package/dist/curator/curator.d.ts +28 -0
- package/dist/curator/curator.d.ts.map +1 -0
- package/dist/curator/curator.js +525 -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 +11 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -0
- 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 +226 -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 +613 -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 +66 -0
- package/dist/runtime/runtime.js.map +1 -0
- package/dist/runtime/types.d.ts +41 -0
- package/dist/runtime/types.d.ts.map +1 -0
- package/dist/runtime/types.js +2 -0
- package/dist/runtime/types.js.map +1 -0
- 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-intelligence.test.ts +623 -0
- package/src/__tests__/core-ops.test.ts +218 -0
- package/src/__tests__/curator.test.ts +574 -0
- package/src/__tests__/domain-ops.test.ts +160 -0
- package/src/__tests__/llm-client.test.ts +69 -0
- package/src/__tests__/runtime.test.ts +95 -0
- package/src/brain/brain.ts +27 -221
- package/src/brain/intelligence.ts +1061 -0
- package/src/brain/types.ts +176 -0
- package/src/curator/curator.ts +699 -0
- package/src/curator/types.ts +114 -0
- package/src/index.ts +55 -1
- package/src/llm/llm-client.ts +310 -0
- package/src/runtime/core-ops.ts +665 -0
- package/src/runtime/domain-ops.ts +144 -0
- package/src/runtime/runtime.ts +76 -0
- package/src/runtime/types.ts +39 -0
- package/src/text/similarity.ts +168 -0
|
@@ -0,0 +1,623 @@
|
|
|
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 type { AgentRuntime } from '../runtime/types.js';
|
|
7
|
+
import type { BrainExportData } from '../brain/types.js';
|
|
8
|
+
|
|
9
|
+
describe('BrainIntelligence', () => {
|
|
10
|
+
let runtime: AgentRuntime;
|
|
11
|
+
let plannerDir: string;
|
|
12
|
+
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
plannerDir = join(tmpdir(), 'brain-intel-test-' + Date.now());
|
|
15
|
+
mkdirSync(plannerDir, { recursive: true });
|
|
16
|
+
runtime = createAgentRuntime({
|
|
17
|
+
agentId: 'test-brain-intel',
|
|
18
|
+
vaultPath: ':memory:',
|
|
19
|
+
plansPath: join(plannerDir, 'plans.json'),
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
afterEach(() => {
|
|
24
|
+
runtime.close();
|
|
25
|
+
rmSync(plannerDir, { recursive: true, force: true });
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
// ─── Initialization ─────────────────────────────────────────────
|
|
29
|
+
|
|
30
|
+
it('should initialize with empty stats', () => {
|
|
31
|
+
const stats = runtime.brainIntelligence.getStats();
|
|
32
|
+
expect(stats.strengths).toBe(0);
|
|
33
|
+
expect(stats.sessions).toBe(0);
|
|
34
|
+
expect(stats.activeSessions).toBe(0);
|
|
35
|
+
expect(stats.proposals).toBe(0);
|
|
36
|
+
expect(stats.promotedProposals).toBe(0);
|
|
37
|
+
expect(stats.globalPatterns).toBe(0);
|
|
38
|
+
expect(stats.domainProfiles).toBe(0);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('should be accessible from runtime', () => {
|
|
42
|
+
expect(runtime.brainIntelligence).toBeDefined();
|
|
43
|
+
expect(typeof runtime.brainIntelligence.getStats).toBe('function');
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// ─── Session Lifecycle ──────────────────────────────────────────
|
|
47
|
+
|
|
48
|
+
it('should start a session', () => {
|
|
49
|
+
const session = runtime.brainIntelligence.lifecycle({
|
|
50
|
+
action: 'start',
|
|
51
|
+
domain: 'testing',
|
|
52
|
+
context: 'unit tests',
|
|
53
|
+
});
|
|
54
|
+
expect(session.id).toBeDefined();
|
|
55
|
+
expect(session.domain).toBe('testing');
|
|
56
|
+
expect(session.context).toBe('unit tests');
|
|
57
|
+
expect(session.endedAt).toBeNull();
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('should start a session with custom id', () => {
|
|
61
|
+
const session = runtime.brainIntelligence.lifecycle({
|
|
62
|
+
action: 'start',
|
|
63
|
+
sessionId: 'custom-123',
|
|
64
|
+
domain: 'dev',
|
|
65
|
+
});
|
|
66
|
+
expect(session.id).toBe('custom-123');
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('should end a session', () => {
|
|
70
|
+
const started = runtime.brainIntelligence.lifecycle({
|
|
71
|
+
action: 'start',
|
|
72
|
+
sessionId: 'end-test',
|
|
73
|
+
domain: 'testing',
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
const ended = runtime.brainIntelligence.lifecycle({
|
|
77
|
+
action: 'end',
|
|
78
|
+
sessionId: started.id,
|
|
79
|
+
toolsUsed: ['search', 'edit'],
|
|
80
|
+
filesModified: ['a.ts', 'b.ts'],
|
|
81
|
+
planOutcome: 'completed',
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
expect(ended.endedAt).not.toBeNull();
|
|
85
|
+
expect(ended.toolsUsed).toEqual(['search', 'edit']);
|
|
86
|
+
expect(ended.filesModified).toEqual(['a.ts', 'b.ts']);
|
|
87
|
+
expect(ended.planOutcome).toBe('completed');
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('should throw when ending session without sessionId', () => {
|
|
91
|
+
expect(() => {
|
|
92
|
+
runtime.brainIntelligence.lifecycle({ action: 'end' });
|
|
93
|
+
}).toThrow('sessionId required');
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('should track active sessions in stats', () => {
|
|
97
|
+
runtime.brainIntelligence.lifecycle({ action: 'start', sessionId: 'active-1' });
|
|
98
|
+
runtime.brainIntelligence.lifecycle({ action: 'start', sessionId: 'active-2' });
|
|
99
|
+
runtime.brainIntelligence.lifecycle({
|
|
100
|
+
action: 'end',
|
|
101
|
+
sessionId: 'active-1',
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
const stats = runtime.brainIntelligence.getStats();
|
|
105
|
+
expect(stats.sessions).toBe(2);
|
|
106
|
+
expect(stats.activeSessions).toBe(1);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
// ─── Session Context ────────────────────────────────────────────
|
|
110
|
+
|
|
111
|
+
it('should return session context with frequencies', () => {
|
|
112
|
+
runtime.brainIntelligence.lifecycle({
|
|
113
|
+
action: 'start',
|
|
114
|
+
sessionId: 'ctx-1',
|
|
115
|
+
toolsUsed: ['search', 'edit', 'search'],
|
|
116
|
+
filesModified: ['a.ts'],
|
|
117
|
+
});
|
|
118
|
+
runtime.brainIntelligence.lifecycle({
|
|
119
|
+
action: 'start',
|
|
120
|
+
sessionId: 'ctx-2',
|
|
121
|
+
toolsUsed: ['search', 'write'],
|
|
122
|
+
filesModified: ['a.ts', 'b.ts'],
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
const ctx = runtime.brainIntelligence.getSessionContext();
|
|
126
|
+
expect(ctx.recentSessions.length).toBe(2);
|
|
127
|
+
expect(ctx.toolFrequency.length).toBeGreaterThan(0);
|
|
128
|
+
expect(ctx.fileFrequency.length).toBeGreaterThan(0);
|
|
129
|
+
|
|
130
|
+
const searchFreq = ctx.toolFrequency.find((t) => t.tool === 'search');
|
|
131
|
+
expect(searchFreq).toBeDefined();
|
|
132
|
+
expect(searchFreq!.count).toBe(3);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('should limit session context results', () => {
|
|
136
|
+
for (let i = 0; i < 5; i++) {
|
|
137
|
+
runtime.brainIntelligence.lifecycle({
|
|
138
|
+
action: 'start',
|
|
139
|
+
sessionId: `limit-${i}`,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
const ctx = runtime.brainIntelligence.getSessionContext(2);
|
|
143
|
+
expect(ctx.recentSessions.length).toBe(2);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
// ─── Archive Sessions ────────────────────────────────────────────
|
|
147
|
+
|
|
148
|
+
it('should archive old sessions', () => {
|
|
149
|
+
const db = runtime.vault.getDb();
|
|
150
|
+
// Insert an old session directly
|
|
151
|
+
db.prepare(
|
|
152
|
+
`INSERT INTO brain_sessions (id, started_at, ended_at, tools_used, files_modified)
|
|
153
|
+
VALUES (?, datetime('now', '-60 days'), datetime('now', '-59 days'), '[]', '[]')`,
|
|
154
|
+
).run('old-session');
|
|
155
|
+
|
|
156
|
+
runtime.brainIntelligence.lifecycle({ action: 'start', sessionId: 'new-session' });
|
|
157
|
+
runtime.brainIntelligence.lifecycle({ action: 'end', sessionId: 'new-session' });
|
|
158
|
+
|
|
159
|
+
const result = runtime.brainIntelligence.archiveSessions(30);
|
|
160
|
+
expect(result.archived).toBe(1);
|
|
161
|
+
|
|
162
|
+
const stats = runtime.brainIntelligence.getStats();
|
|
163
|
+
expect(stats.sessions).toBe(1); // only new-session remains
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it('should not archive active sessions', () => {
|
|
167
|
+
runtime.brainIntelligence.lifecycle({ action: 'start', sessionId: 'active' });
|
|
168
|
+
const result = runtime.brainIntelligence.archiveSessions(0);
|
|
169
|
+
expect(result.archived).toBe(0);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
// ─── Strength Scoring ──────────────────────────────────────────
|
|
173
|
+
|
|
174
|
+
it('should compute strengths from feedback', () => {
|
|
175
|
+
// Seed vault and record feedback
|
|
176
|
+
runtime.vault.seed([
|
|
177
|
+
{
|
|
178
|
+
id: 's-1',
|
|
179
|
+
type: 'pattern',
|
|
180
|
+
domain: 'testing',
|
|
181
|
+
title: 'Test pattern',
|
|
182
|
+
severity: 'warning',
|
|
183
|
+
description: 'A test pattern.',
|
|
184
|
+
tags: ['test'],
|
|
185
|
+
},
|
|
186
|
+
]);
|
|
187
|
+
|
|
188
|
+
runtime.brain.recordFeedback('test query', 's-1', 'accepted');
|
|
189
|
+
runtime.brain.recordFeedback('test query 2', 's-1', 'accepted');
|
|
190
|
+
runtime.brain.recordFeedback('test query 3', 's-1', 'dismissed');
|
|
191
|
+
|
|
192
|
+
const strengths = runtime.brainIntelligence.computeStrengths();
|
|
193
|
+
expect(strengths.length).toBe(1);
|
|
194
|
+
expect(strengths[0].pattern).toBe('Test pattern');
|
|
195
|
+
expect(strengths[0].domain).toBe('testing');
|
|
196
|
+
expect(strengths[0].usageCount).toBe(3);
|
|
197
|
+
expect(strengths[0].successRate).toBeCloseTo(2 / 3, 2);
|
|
198
|
+
expect(strengths[0].strength).toBeGreaterThan(0);
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it('should return empty strengths with no feedback', () => {
|
|
202
|
+
const strengths = runtime.brainIntelligence.computeStrengths();
|
|
203
|
+
expect(strengths).toEqual([]);
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it('should get strengths with filters', () => {
|
|
207
|
+
runtime.vault.seed([
|
|
208
|
+
{
|
|
209
|
+
id: 'sf-1',
|
|
210
|
+
type: 'pattern',
|
|
211
|
+
domain: 'd1',
|
|
212
|
+
title: 'P1',
|
|
213
|
+
severity: 'warning',
|
|
214
|
+
description: 'D',
|
|
215
|
+
tags: [],
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
id: 'sf-2',
|
|
219
|
+
type: 'pattern',
|
|
220
|
+
domain: 'd2',
|
|
221
|
+
title: 'P2',
|
|
222
|
+
severity: 'warning',
|
|
223
|
+
description: 'D',
|
|
224
|
+
tags: [],
|
|
225
|
+
},
|
|
226
|
+
]);
|
|
227
|
+
runtime.brain.recordFeedback('q', 'sf-1', 'accepted');
|
|
228
|
+
runtime.brain.recordFeedback('q', 'sf-2', 'accepted');
|
|
229
|
+
runtime.brainIntelligence.computeStrengths();
|
|
230
|
+
|
|
231
|
+
const d1 = runtime.brainIntelligence.getStrengths({ domain: 'd1' });
|
|
232
|
+
expect(d1.length).toBe(1);
|
|
233
|
+
expect(d1[0].domain).toBe('d1');
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
// ─── Recommendations ──────────────────────────────────────────
|
|
237
|
+
|
|
238
|
+
it('should recommend patterns', () => {
|
|
239
|
+
runtime.vault.seed([
|
|
240
|
+
{
|
|
241
|
+
id: 'r-1',
|
|
242
|
+
type: 'pattern',
|
|
243
|
+
domain: 'testing',
|
|
244
|
+
title: 'Test pattern',
|
|
245
|
+
severity: 'warning',
|
|
246
|
+
description: 'D',
|
|
247
|
+
tags: [],
|
|
248
|
+
},
|
|
249
|
+
]);
|
|
250
|
+
// Need enough feedback to reach minStrength threshold of 30
|
|
251
|
+
for (let i = 0; i < 10; i++) {
|
|
252
|
+
runtime.brain.recordFeedback(`q${i}`, 'r-1', 'accepted');
|
|
253
|
+
}
|
|
254
|
+
runtime.brainIntelligence.computeStrengths();
|
|
255
|
+
|
|
256
|
+
const recommendations = runtime.brainIntelligence.recommend({ domain: 'testing' });
|
|
257
|
+
expect(recommendations.length).toBeGreaterThan(0);
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
it('should boost recommendations by task context', () => {
|
|
261
|
+
runtime.vault.seed([
|
|
262
|
+
{
|
|
263
|
+
id: 'rb-1',
|
|
264
|
+
type: 'pattern',
|
|
265
|
+
domain: 'd',
|
|
266
|
+
title: 'Database migration pattern',
|
|
267
|
+
severity: 'warning',
|
|
268
|
+
description: 'D',
|
|
269
|
+
tags: [],
|
|
270
|
+
},
|
|
271
|
+
{
|
|
272
|
+
id: 'rb-2',
|
|
273
|
+
type: 'pattern',
|
|
274
|
+
domain: 'd',
|
|
275
|
+
title: 'API endpoint pattern',
|
|
276
|
+
severity: 'warning',
|
|
277
|
+
description: 'D',
|
|
278
|
+
tags: [],
|
|
279
|
+
},
|
|
280
|
+
]);
|
|
281
|
+
for (let i = 0; i < 10; i++) {
|
|
282
|
+
runtime.brain.recordFeedback(`q${i}`, 'rb-1', 'accepted');
|
|
283
|
+
runtime.brain.recordFeedback(`q${i}`, 'rb-2', 'accepted');
|
|
284
|
+
}
|
|
285
|
+
runtime.brainIntelligence.computeStrengths();
|
|
286
|
+
|
|
287
|
+
const recs = runtime.brainIntelligence.recommend({ task: 'database migration' });
|
|
288
|
+
if (recs.length >= 2) {
|
|
289
|
+
// Database pattern should be boosted
|
|
290
|
+
expect(recs[0].pattern).toContain('Database');
|
|
291
|
+
}
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
// ─── Knowledge Extraction ─────────────────────────────────────
|
|
295
|
+
|
|
296
|
+
it('should extract knowledge from session with repeated tools', () => {
|
|
297
|
+
runtime.brainIntelligence.lifecycle({
|
|
298
|
+
action: 'start',
|
|
299
|
+
sessionId: 'extract-1',
|
|
300
|
+
toolsUsed: ['search', 'search', 'search', 'edit'],
|
|
301
|
+
});
|
|
302
|
+
runtime.brainIntelligence.lifecycle({
|
|
303
|
+
action: 'end',
|
|
304
|
+
sessionId: 'extract-1',
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
const result = runtime.brainIntelligence.extractKnowledge('extract-1');
|
|
308
|
+
expect(result.sessionId).toBe('extract-1');
|
|
309
|
+
expect(result.rulesApplied).toContain('repeated_tool_usage');
|
|
310
|
+
expect(result.proposals.length).toBeGreaterThan(0);
|
|
311
|
+
expect(result.proposals[0].rule).toBe('repeated_tool_usage');
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
it('should extract multi-file edit pattern', () => {
|
|
315
|
+
runtime.brainIntelligence.lifecycle({
|
|
316
|
+
action: 'start',
|
|
317
|
+
sessionId: 'extract-2',
|
|
318
|
+
filesModified: ['a.ts', 'b.ts', 'c.ts'],
|
|
319
|
+
});
|
|
320
|
+
runtime.brainIntelligence.lifecycle({
|
|
321
|
+
action: 'end',
|
|
322
|
+
sessionId: 'extract-2',
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
const result = runtime.brainIntelligence.extractKnowledge('extract-2');
|
|
326
|
+
expect(result.rulesApplied).toContain('multi_file_edit');
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
it('should extract plan completed workflow', () => {
|
|
330
|
+
runtime.brainIntelligence.lifecycle({
|
|
331
|
+
action: 'start',
|
|
332
|
+
sessionId: 'extract-3',
|
|
333
|
+
planId: 'plan-1',
|
|
334
|
+
});
|
|
335
|
+
runtime.brainIntelligence.lifecycle({
|
|
336
|
+
action: 'end',
|
|
337
|
+
sessionId: 'extract-3',
|
|
338
|
+
planOutcome: 'completed',
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
const result = runtime.brainIntelligence.extractKnowledge('extract-3');
|
|
342
|
+
expect(result.rulesApplied).toContain('plan_completed');
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
it('should extract plan abandoned anti-pattern', () => {
|
|
346
|
+
runtime.brainIntelligence.lifecycle({
|
|
347
|
+
action: 'start',
|
|
348
|
+
sessionId: 'extract-4',
|
|
349
|
+
planId: 'plan-2',
|
|
350
|
+
});
|
|
351
|
+
runtime.brainIntelligence.lifecycle({
|
|
352
|
+
action: 'end',
|
|
353
|
+
sessionId: 'extract-4',
|
|
354
|
+
planOutcome: 'abandoned',
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
const result = runtime.brainIntelligence.extractKnowledge('extract-4');
|
|
358
|
+
expect(result.rulesApplied).toContain('plan_abandoned');
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
it('should throw for non-existent session', () => {
|
|
362
|
+
expect(() => {
|
|
363
|
+
runtime.brainIntelligence.extractKnowledge('non-existent');
|
|
364
|
+
}).toThrow('Session not found');
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
// ─── Proposals ─────────────────────────────────────────────────
|
|
368
|
+
|
|
369
|
+
it('should list proposals', () => {
|
|
370
|
+
runtime.brainIntelligence.lifecycle({
|
|
371
|
+
action: 'start',
|
|
372
|
+
sessionId: 'prop-1',
|
|
373
|
+
toolsUsed: ['a', 'a', 'a'],
|
|
374
|
+
});
|
|
375
|
+
runtime.brainIntelligence.lifecycle({ action: 'end', sessionId: 'prop-1' });
|
|
376
|
+
runtime.brainIntelligence.extractKnowledge('prop-1');
|
|
377
|
+
|
|
378
|
+
const proposals = runtime.brainIntelligence.getProposals();
|
|
379
|
+
expect(proposals.length).toBeGreaterThan(0);
|
|
380
|
+
expect(proposals[0].promoted).toBe(false);
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
it('should filter proposals by session', () => {
|
|
384
|
+
runtime.brainIntelligence.lifecycle({
|
|
385
|
+
action: 'start',
|
|
386
|
+
sessionId: 'prop-s1',
|
|
387
|
+
toolsUsed: ['x', 'x', 'x'],
|
|
388
|
+
});
|
|
389
|
+
runtime.brainIntelligence.lifecycle({ action: 'end', sessionId: 'prop-s1' });
|
|
390
|
+
runtime.brainIntelligence.extractKnowledge('prop-s1');
|
|
391
|
+
|
|
392
|
+
runtime.brainIntelligence.lifecycle({
|
|
393
|
+
action: 'start',
|
|
394
|
+
sessionId: 'prop-s2',
|
|
395
|
+
toolsUsed: ['y', 'y', 'y'],
|
|
396
|
+
});
|
|
397
|
+
runtime.brainIntelligence.lifecycle({ action: 'end', sessionId: 'prop-s2' });
|
|
398
|
+
runtime.brainIntelligence.extractKnowledge('prop-s2');
|
|
399
|
+
|
|
400
|
+
const s1 = runtime.brainIntelligence.getProposals({ sessionId: 'prop-s1' });
|
|
401
|
+
const s2 = runtime.brainIntelligence.getProposals({ sessionId: 'prop-s2' });
|
|
402
|
+
expect(s1.every((p) => p.sessionId === 'prop-s1')).toBe(true);
|
|
403
|
+
expect(s2.every((p) => p.sessionId === 'prop-s2')).toBe(true);
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
it('should promote proposals to vault', () => {
|
|
407
|
+
runtime.brainIntelligence.lifecycle({
|
|
408
|
+
action: 'start',
|
|
409
|
+
sessionId: 'promo-1',
|
|
410
|
+
toolsUsed: ['z', 'z', 'z'],
|
|
411
|
+
});
|
|
412
|
+
runtime.brainIntelligence.lifecycle({ action: 'end', sessionId: 'promo-1' });
|
|
413
|
+
runtime.brainIntelligence.extractKnowledge('promo-1');
|
|
414
|
+
|
|
415
|
+
const proposals = runtime.brainIntelligence.getProposals({ sessionId: 'promo-1' });
|
|
416
|
+
expect(proposals.length).toBeGreaterThan(0);
|
|
417
|
+
|
|
418
|
+
const result = runtime.brainIntelligence.promoteProposals([proposals[0].id]);
|
|
419
|
+
expect(result.promoted).toBe(1);
|
|
420
|
+
expect(result.failed).toEqual([]);
|
|
421
|
+
|
|
422
|
+
// Check vault has the entry
|
|
423
|
+
const entry = runtime.vault.get(`proposal-${proposals[0].id}`);
|
|
424
|
+
expect(entry).not.toBeNull();
|
|
425
|
+
expect(entry!.domain).toBe('brain-intelligence');
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
it('should handle promoting non-existent proposal', () => {
|
|
429
|
+
const result = runtime.brainIntelligence.promoteProposals(['non-existent']);
|
|
430
|
+
expect(result.promoted).toBe(0);
|
|
431
|
+
expect(result.failed).toContain('non-existent');
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
// ─── Build Intelligence ────────────────────────────────────────
|
|
435
|
+
|
|
436
|
+
it('should build intelligence pipeline', () => {
|
|
437
|
+
runtime.vault.seed([
|
|
438
|
+
{
|
|
439
|
+
id: 'bi-1',
|
|
440
|
+
type: 'pattern',
|
|
441
|
+
domain: 'd1',
|
|
442
|
+
title: 'Pattern A',
|
|
443
|
+
severity: 'warning',
|
|
444
|
+
description: 'D',
|
|
445
|
+
tags: [],
|
|
446
|
+
},
|
|
447
|
+
]);
|
|
448
|
+
runtime.brain.recordFeedback('q', 'bi-1', 'accepted');
|
|
449
|
+
|
|
450
|
+
const result = runtime.brainIntelligence.buildIntelligence();
|
|
451
|
+
expect(result.strengthsComputed).toBe(1);
|
|
452
|
+
expect(result.globalPatterns).toBe(1);
|
|
453
|
+
expect(result.domainProfiles).toBe(1);
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
it('should build with empty data', () => {
|
|
457
|
+
const result = runtime.brainIntelligence.buildIntelligence();
|
|
458
|
+
expect(result.strengthsComputed).toBe(0);
|
|
459
|
+
expect(result.globalPatterns).toBe(0);
|
|
460
|
+
expect(result.domainProfiles).toBe(0);
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
// ─── Global Patterns ──────────────────────────────────────────
|
|
464
|
+
|
|
465
|
+
it('should return global patterns after build', () => {
|
|
466
|
+
runtime.vault.seed([
|
|
467
|
+
{
|
|
468
|
+
id: 'gp-1',
|
|
469
|
+
type: 'pattern',
|
|
470
|
+
domain: 'd1',
|
|
471
|
+
title: 'Cross Pattern',
|
|
472
|
+
severity: 'warning',
|
|
473
|
+
description: 'D',
|
|
474
|
+
tags: [],
|
|
475
|
+
},
|
|
476
|
+
]);
|
|
477
|
+
runtime.brain.recordFeedback('q', 'gp-1', 'accepted');
|
|
478
|
+
runtime.brainIntelligence.buildIntelligence();
|
|
479
|
+
|
|
480
|
+
const patterns = runtime.brainIntelligence.getGlobalPatterns();
|
|
481
|
+
expect(patterns.length).toBe(1);
|
|
482
|
+
expect(patterns[0].pattern).toBe('Cross Pattern');
|
|
483
|
+
expect(patterns[0].domains).toContain('d1');
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
// ─── Domain Profiles ──────────────────────────────────────────
|
|
487
|
+
|
|
488
|
+
it('should return domain profile after build', () => {
|
|
489
|
+
runtime.vault.seed([
|
|
490
|
+
{
|
|
491
|
+
id: 'dp-1',
|
|
492
|
+
type: 'pattern',
|
|
493
|
+
domain: 'security',
|
|
494
|
+
title: 'Security Pattern',
|
|
495
|
+
severity: 'warning',
|
|
496
|
+
description: 'D',
|
|
497
|
+
tags: [],
|
|
498
|
+
},
|
|
499
|
+
]);
|
|
500
|
+
runtime.brain.recordFeedback('q', 'dp-1', 'accepted');
|
|
501
|
+
runtime.brainIntelligence.buildIntelligence();
|
|
502
|
+
|
|
503
|
+
const profile = runtime.brainIntelligence.getDomainProfile('security');
|
|
504
|
+
expect(profile).not.toBeNull();
|
|
505
|
+
expect(profile!.domain).toBe('security');
|
|
506
|
+
expect(profile!.topPatterns.length).toBe(1);
|
|
507
|
+
});
|
|
508
|
+
|
|
509
|
+
it('should return null for unknown domain', () => {
|
|
510
|
+
const profile = runtime.brainIntelligence.getDomainProfile('nonexistent');
|
|
511
|
+
expect(profile).toBeNull();
|
|
512
|
+
});
|
|
513
|
+
|
|
514
|
+
// ─── Export / Import ──────────────────────────────────────────
|
|
515
|
+
|
|
516
|
+
it('should export all data', () => {
|
|
517
|
+
runtime.brainIntelligence.lifecycle({
|
|
518
|
+
action: 'start',
|
|
519
|
+
sessionId: 'exp-1',
|
|
520
|
+
domain: 'testing',
|
|
521
|
+
});
|
|
522
|
+
runtime.brainIntelligence.lifecycle({
|
|
523
|
+
action: 'end',
|
|
524
|
+
sessionId: 'exp-1',
|
|
525
|
+
});
|
|
526
|
+
|
|
527
|
+
const data = runtime.brainIntelligence.exportData();
|
|
528
|
+
expect(data.sessions.length).toBe(1);
|
|
529
|
+
expect(data.exportedAt).toBeDefined();
|
|
530
|
+
});
|
|
531
|
+
|
|
532
|
+
it('should import data', () => {
|
|
533
|
+
const data: BrainExportData = {
|
|
534
|
+
strengths: [
|
|
535
|
+
{
|
|
536
|
+
pattern: 'Imported Pattern',
|
|
537
|
+
domain: 'testing',
|
|
538
|
+
strength: 75,
|
|
539
|
+
usageScore: 20,
|
|
540
|
+
spreadScore: 15,
|
|
541
|
+
successScore: 20,
|
|
542
|
+
recencyScore: 20,
|
|
543
|
+
usageCount: 8,
|
|
544
|
+
uniqueContexts: 3,
|
|
545
|
+
successRate: 0.8,
|
|
546
|
+
lastUsed: new Date().toISOString(),
|
|
547
|
+
},
|
|
548
|
+
],
|
|
549
|
+
sessions: [
|
|
550
|
+
{
|
|
551
|
+
id: 'imp-s1',
|
|
552
|
+
startedAt: new Date().toISOString(),
|
|
553
|
+
endedAt: new Date().toISOString(),
|
|
554
|
+
domain: 'testing',
|
|
555
|
+
context: 'import test',
|
|
556
|
+
toolsUsed: ['search'],
|
|
557
|
+
filesModified: [],
|
|
558
|
+
planId: null,
|
|
559
|
+
planOutcome: null,
|
|
560
|
+
},
|
|
561
|
+
],
|
|
562
|
+
proposals: [],
|
|
563
|
+
globalPatterns: [
|
|
564
|
+
{
|
|
565
|
+
pattern: 'Imported Global',
|
|
566
|
+
domains: ['testing'],
|
|
567
|
+
totalStrength: 75,
|
|
568
|
+
avgStrength: 75,
|
|
569
|
+
domainCount: 1,
|
|
570
|
+
},
|
|
571
|
+
],
|
|
572
|
+
domainProfiles: [
|
|
573
|
+
{
|
|
574
|
+
domain: 'testing',
|
|
575
|
+
topPatterns: [{ pattern: 'Imported', strength: 75 }],
|
|
576
|
+
sessionCount: 1,
|
|
577
|
+
avgSessionDuration: 5,
|
|
578
|
+
lastActivity: new Date().toISOString(),
|
|
579
|
+
},
|
|
580
|
+
],
|
|
581
|
+
exportedAt: new Date().toISOString(),
|
|
582
|
+
};
|
|
583
|
+
|
|
584
|
+
const result = runtime.brainIntelligence.importData(data);
|
|
585
|
+
expect(result.imported.strengths).toBe(1);
|
|
586
|
+
expect(result.imported.sessions).toBe(1);
|
|
587
|
+
expect(result.imported.globalPatterns).toBe(1);
|
|
588
|
+
expect(result.imported.domainProfiles).toBe(1);
|
|
589
|
+
|
|
590
|
+
// Verify imported data is accessible
|
|
591
|
+
const strengths = runtime.brainIntelligence.getStrengths();
|
|
592
|
+
expect(strengths.length).toBe(1);
|
|
593
|
+
expect(strengths[0].pattern).toBe('Imported Pattern');
|
|
594
|
+
});
|
|
595
|
+
|
|
596
|
+
it('should handle duplicate session import gracefully', () => {
|
|
597
|
+
runtime.brainIntelligence.lifecycle({ action: 'start', sessionId: 'dup-1' });
|
|
598
|
+
|
|
599
|
+
const data: BrainExportData = {
|
|
600
|
+
strengths: [],
|
|
601
|
+
sessions: [
|
|
602
|
+
{
|
|
603
|
+
id: 'dup-1',
|
|
604
|
+
startedAt: new Date().toISOString(),
|
|
605
|
+
endedAt: null,
|
|
606
|
+
domain: null,
|
|
607
|
+
context: null,
|
|
608
|
+
toolsUsed: [],
|
|
609
|
+
filesModified: [],
|
|
610
|
+
planId: null,
|
|
611
|
+
planOutcome: null,
|
|
612
|
+
},
|
|
613
|
+
],
|
|
614
|
+
proposals: [],
|
|
615
|
+
globalPatterns: [],
|
|
616
|
+
domainProfiles: [],
|
|
617
|
+
exportedAt: new Date().toISOString(),
|
|
618
|
+
};
|
|
619
|
+
|
|
620
|
+
const result = runtime.brainIntelligence.importData(data);
|
|
621
|
+
expect(result.imported.sessions).toBe(0); // Duplicate ignored
|
|
622
|
+
});
|
|
623
|
+
});
|