@soleri/core 2.5.0 → 2.7.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/intelligence.d.ts +1 -0
- package/dist/brain/intelligence.d.ts.map +1 -1
- package/dist/brain/intelligence.js +160 -148
- package/dist/brain/intelligence.js.map +1 -1
- package/dist/control/identity-manager.d.ts +3 -1
- package/dist/control/identity-manager.d.ts.map +1 -1
- package/dist/control/identity-manager.js +53 -51
- package/dist/control/identity-manager.js.map +1 -1
- package/dist/control/intent-router.d.ts +1 -0
- package/dist/control/intent-router.d.ts.map +1 -1
- package/dist/control/intent-router.js +41 -32
- package/dist/control/intent-router.js.map +1 -1
- package/dist/curator/curator.d.ts +1 -0
- package/dist/curator/curator.d.ts.map +1 -1
- package/dist/curator/curator.js +58 -99
- package/dist/curator/curator.js.map +1 -1
- package/dist/facades/facade-factory.js +1 -1
- package/dist/facades/facade-factory.js.map +1 -1
- package/dist/governance/governance.d.ts +1 -0
- package/dist/governance/governance.d.ts.map +1 -1
- package/dist/governance/governance.js +51 -68
- package/dist/governance/governance.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/persistence/index.d.ts +2 -1
- package/dist/persistence/index.d.ts.map +1 -1
- package/dist/persistence/index.js +1 -0
- package/dist/persistence/index.js.map +1 -1
- package/dist/persistence/postgres-provider.d.ts +46 -0
- package/dist/persistence/postgres-provider.d.ts.map +1 -0
- package/dist/persistence/postgres-provider.js +115 -0
- package/dist/persistence/postgres-provider.js.map +1 -0
- package/dist/persistence/sqlite-provider.d.ts +5 -2
- package/dist/persistence/sqlite-provider.d.ts.map +1 -1
- package/dist/persistence/sqlite-provider.js +37 -1
- package/dist/persistence/sqlite-provider.js.map +1 -1
- package/dist/persistence/types.d.ts +23 -1
- package/dist/persistence/types.d.ts.map +1 -1
- package/dist/project/project-registry.d.ts +4 -4
- package/dist/project/project-registry.d.ts.map +1 -1
- package/dist/project/project-registry.js +32 -50
- package/dist/project/project-registry.js.map +1 -1
- package/dist/runtime/admin-extra-ops.d.ts +3 -3
- package/dist/runtime/admin-extra-ops.d.ts.map +1 -1
- package/dist/runtime/admin-extra-ops.js +33 -3
- package/dist/runtime/admin-extra-ops.js.map +1 -1
- package/dist/runtime/core-ops.d.ts +4 -4
- package/dist/runtime/core-ops.js +4 -4
- package/dist/runtime/runtime.js +1 -1
- package/dist/runtime/runtime.js.map +1 -1
- package/dist/runtime/vault-extra-ops.d.ts +3 -2
- package/dist/runtime/vault-extra-ops.d.ts.map +1 -1
- package/dist/runtime/vault-extra-ops.js +40 -2
- package/dist/runtime/vault-extra-ops.js.map +1 -1
- package/dist/vault/vault.d.ts +21 -0
- package/dist/vault/vault.d.ts.map +1 -1
- package/dist/vault/vault.js +99 -0
- package/dist/vault/vault.js.map +1 -1
- package/package.json +4 -2
- package/src/__tests__/admin-extra-ops.test.ts +2 -2
- package/src/__tests__/core-ops.test.ts +8 -2
- package/src/__tests__/persistence.test.ts +66 -0
- package/src/__tests__/postgres-provider.test.ts +58 -0
- package/src/__tests__/vault-extra-ops.test.ts +2 -2
- package/src/__tests__/vault.test.ts +184 -0
- package/src/brain/intelligence.ts +258 -307
- package/src/control/identity-manager.ts +77 -75
- package/src/control/intent-router.ts +55 -57
- package/src/curator/curator.ts +124 -145
- package/src/facades/facade-factory.ts +1 -1
- package/src/governance/governance.ts +90 -107
- package/src/index.ts +2 -0
- package/src/persistence/index.ts +2 -0
- package/src/persistence/postgres-provider.ts +157 -0
- package/src/persistence/sqlite-provider.ts +55 -2
- package/src/persistence/types.ts +31 -1
- package/src/project/project-registry.ts +69 -74
- package/src/runtime/admin-extra-ops.ts +36 -3
- package/src/runtime/core-ops.ts +4 -4
- package/src/runtime/runtime.ts +1 -1
- package/src/runtime/vault-extra-ops.ts +42 -2
- package/src/vault/vault.ts +118 -0
|
@@ -11,6 +11,7 @@ import type { PatternStrength, StrengthsQuery, BrainSession, SessionLifecycleInp
|
|
|
11
11
|
export declare class BrainIntelligence {
|
|
12
12
|
private vault;
|
|
13
13
|
private brain;
|
|
14
|
+
private provider;
|
|
14
15
|
constructor(vault: Vault, brain: Brain);
|
|
15
16
|
private initializeTables;
|
|
16
17
|
lifecycle(input: SessionLifecycleInput): BrainSession;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"intelligence.d.ts","sourceRoot":"","sources":["../../src/brain/intelligence.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"intelligence.d.ts","sourceRoot":"","sources":["../../src/brain/intelligence.ts"],"names":[],"mappings":"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"}
|
|
@@ -18,15 +18,16 @@ const EXTRACTION_HIGH_FEEDBACK_RATIO = 0.8;
|
|
|
18
18
|
export class BrainIntelligence {
|
|
19
19
|
vault;
|
|
20
20
|
brain;
|
|
21
|
+
provider;
|
|
21
22
|
constructor(vault, brain) {
|
|
22
23
|
this.vault = vault;
|
|
23
24
|
this.brain = brain;
|
|
25
|
+
this.provider = vault.getProvider();
|
|
24
26
|
this.initializeTables();
|
|
25
27
|
}
|
|
26
28
|
// ─── Table Initialization ─────────────────────────────────────────
|
|
27
29
|
initializeTables() {
|
|
28
|
-
|
|
29
|
-
db.exec(`
|
|
30
|
+
this.provider.execSql(`
|
|
30
31
|
CREATE TABLE IF NOT EXISTS brain_strengths (
|
|
31
32
|
pattern TEXT NOT NULL,
|
|
32
33
|
domain TEXT NOT NULL,
|
|
@@ -90,11 +91,17 @@ export class BrainIntelligence {
|
|
|
90
91
|
}
|
|
91
92
|
// ─── Session Lifecycle ────────────────────────────────────────────
|
|
92
93
|
lifecycle(input) {
|
|
93
|
-
const db = this.vault.getDb();
|
|
94
94
|
if (input.action === 'start') {
|
|
95
95
|
const id = input.sessionId ?? randomUUID();
|
|
96
|
-
|
|
97
|
-
VALUES (?, ?, ?, ?, ?, ?)
|
|
96
|
+
this.provider.run(`INSERT INTO brain_sessions (id, domain, context, tools_used, files_modified, plan_id)
|
|
97
|
+
VALUES (?, ?, ?, ?, ?, ?)`, [
|
|
98
|
+
id,
|
|
99
|
+
input.domain ?? null,
|
|
100
|
+
input.context ?? null,
|
|
101
|
+
JSON.stringify(input.toolsUsed ?? []),
|
|
102
|
+
JSON.stringify(input.filesModified ?? []),
|
|
103
|
+
input.planId ?? null,
|
|
104
|
+
]);
|
|
98
105
|
return this.getSession(id);
|
|
99
106
|
}
|
|
100
107
|
// action === 'end'
|
|
@@ -120,7 +127,7 @@ export class BrainIntelligence {
|
|
|
120
127
|
values.push(input.planOutcome);
|
|
121
128
|
}
|
|
122
129
|
values.push(sessionId);
|
|
123
|
-
|
|
130
|
+
this.provider.run(`UPDATE brain_sessions SET ${updates.join(', ')} WHERE id = ?`, values);
|
|
124
131
|
// Auto-extract knowledge if session has enough signal
|
|
125
132
|
this.autoExtractIfReady(this.getSession(sessionId));
|
|
126
133
|
// Return fresh session (extractedAt may have been set by auto-extract)
|
|
@@ -147,10 +154,7 @@ export class BrainIntelligence {
|
|
|
147
154
|
}
|
|
148
155
|
}
|
|
149
156
|
getSessionContext(limit = 10) {
|
|
150
|
-
const
|
|
151
|
-
const rows = db
|
|
152
|
-
.prepare('SELECT * FROM brain_sessions ORDER BY started_at DESC LIMIT ?')
|
|
153
|
-
.all(limit);
|
|
157
|
+
const rows = this.provider.all('SELECT * FROM brain_sessions ORDER BY started_at DESC LIMIT ?', [limit]);
|
|
154
158
|
const sessions = rows.map((r) => this.rowToSession(r));
|
|
155
159
|
// Aggregate tool frequency
|
|
156
160
|
const toolCounts = new Map();
|
|
@@ -172,33 +176,25 @@ export class BrainIntelligence {
|
|
|
172
176
|
return { recentSessions: sessions, toolFrequency, fileFrequency };
|
|
173
177
|
}
|
|
174
178
|
archiveSessions(olderThanDays = 30) {
|
|
175
|
-
const
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
WHERE ended_at IS NOT NULL
|
|
179
|
-
AND started_at < datetime('now', '-' || ? || ' days')`)
|
|
180
|
-
.run(olderThanDays);
|
|
179
|
+
const result = this.provider.run(`DELETE FROM brain_sessions
|
|
180
|
+
WHERE ended_at IS NOT NULL
|
|
181
|
+
AND started_at < datetime('now', '-' || ? || ' days')`, [olderThanDays]);
|
|
181
182
|
return { archived: result.changes };
|
|
182
183
|
}
|
|
183
184
|
// ─── Strength Scoring ─────────────────────────────────────────────
|
|
184
185
|
computeStrengths() {
|
|
185
|
-
const db = this.vault.getDb();
|
|
186
186
|
// Gather feedback data grouped by entry_id
|
|
187
|
-
const feedbackRows =
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
GROUP BY entry_id`)
|
|
197
|
-
.all();
|
|
187
|
+
const feedbackRows = this.provider.all(`SELECT entry_id,
|
|
188
|
+
COUNT(*) as total,
|
|
189
|
+
SUM(CASE WHEN action = 'accepted' THEN 1 ELSE 0 END) as accepted,
|
|
190
|
+
SUM(CASE WHEN action = 'dismissed' THEN 1 ELSE 0 END) as dismissed,
|
|
191
|
+
SUM(CASE WHEN action = 'modified' THEN 1 ELSE 0 END) as modified,
|
|
192
|
+
SUM(CASE WHEN action = 'failed' THEN 1 ELSE 0 END) as failed,
|
|
193
|
+
MAX(created_at) as last_used
|
|
194
|
+
FROM brain_feedback
|
|
195
|
+
GROUP BY entry_id`);
|
|
198
196
|
// Count unique session domains as spread proxy
|
|
199
|
-
const sessionRows =
|
|
200
|
-
.prepare('SELECT DISTINCT domain FROM brain_sessions WHERE domain IS NOT NULL')
|
|
201
|
-
.all();
|
|
197
|
+
const sessionRows = this.provider.all('SELECT DISTINCT domain FROM brain_sessions WHERE domain IS NOT NULL');
|
|
202
198
|
const uniqueDomains = new Set(sessionRows.map((r) => r.domain));
|
|
203
199
|
const now = Date.now();
|
|
204
200
|
const strengths = [];
|
|
@@ -237,15 +233,26 @@ export class BrainIntelligence {
|
|
|
237
233
|
};
|
|
238
234
|
strengths.push(ps);
|
|
239
235
|
// Persist
|
|
240
|
-
|
|
236
|
+
this.provider.run(`INSERT OR REPLACE INTO brain_strengths
|
|
241
237
|
(pattern, domain, strength, usage_score, spread_score, success_score, recency_score,
|
|
242
238
|
usage_count, unique_contexts, success_rate, last_used, updated_at)
|
|
243
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, datetime('now'))
|
|
239
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, datetime('now'))`, [
|
|
240
|
+
ps.pattern,
|
|
241
|
+
ps.domain,
|
|
242
|
+
ps.strength,
|
|
243
|
+
ps.usageScore,
|
|
244
|
+
ps.spreadScore,
|
|
245
|
+
ps.successScore,
|
|
246
|
+
ps.recencyScore,
|
|
247
|
+
ps.usageCount,
|
|
248
|
+
ps.uniqueContexts,
|
|
249
|
+
ps.successRate,
|
|
250
|
+
ps.lastUsed,
|
|
251
|
+
]);
|
|
244
252
|
}
|
|
245
253
|
return strengths;
|
|
246
254
|
}
|
|
247
255
|
getStrengths(query) {
|
|
248
|
-
const db = this.vault.getDb();
|
|
249
256
|
const conditions = [];
|
|
250
257
|
const values = [];
|
|
251
258
|
if (query?.domain) {
|
|
@@ -259,9 +266,7 @@ export class BrainIntelligence {
|
|
|
259
266
|
const where = conditions.length > 0 ? 'WHERE ' + conditions.join(' AND ') : '';
|
|
260
267
|
const limit = query?.limit ?? 50;
|
|
261
268
|
values.push(limit);
|
|
262
|
-
const rows =
|
|
263
|
-
.prepare(`SELECT * FROM brain_strengths ${where} ORDER BY strength DESC LIMIT ?`)
|
|
264
|
-
.all(...values);
|
|
269
|
+
const rows = this.provider.all(`SELECT * FROM brain_strengths ${where} ORDER BY strength DESC LIMIT ?`, values);
|
|
265
270
|
return rows.map((r) => ({
|
|
266
271
|
pattern: r.pattern,
|
|
267
272
|
domain: r.domain,
|
|
@@ -297,16 +302,13 @@ export class BrainIntelligence {
|
|
|
297
302
|
}
|
|
298
303
|
// Boost patterns with high source-specific acceptance rates
|
|
299
304
|
if (context.source) {
|
|
300
|
-
const db = this.vault.getDb();
|
|
301
305
|
for (const s of strengths) {
|
|
302
|
-
const row =
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
AND source = ?`)
|
|
309
|
-
.get(s.pattern, context.source);
|
|
306
|
+
const row = this.provider.get(`SELECT COUNT(*) as total,
|
|
307
|
+
SUM(CASE WHEN action = 'accepted' THEN 1 ELSE 0 END) as accepted,
|
|
308
|
+
SUM(CASE WHEN action = 'modified' THEN 1 ELSE 0 END) as modified
|
|
309
|
+
FROM brain_feedback
|
|
310
|
+
WHERE entry_id = (SELECT id FROM entries WHERE title = ? LIMIT 1)
|
|
311
|
+
AND source = ?`, [s.pattern, context.source]);
|
|
310
312
|
if (row.total >= 3) {
|
|
311
313
|
const sourceRate = (row.accepted + row.modified * 0.5) / row.total;
|
|
312
314
|
// Boost up to +10 points for high source-specific acceptance
|
|
@@ -324,7 +326,6 @@ export class BrainIntelligence {
|
|
|
324
326
|
throw new Error('Session not found: ' + sessionId);
|
|
325
327
|
const proposals = [];
|
|
326
328
|
const rulesApplied = [];
|
|
327
|
-
const db = this.vault.getDb();
|
|
328
329
|
// Rule 1: Repeated tool usage (3+ same tool)
|
|
329
330
|
const toolCounts = new Map();
|
|
330
331
|
for (const t of session.toolsUsed) {
|
|
@@ -333,7 +334,7 @@ export class BrainIntelligence {
|
|
|
333
334
|
for (const [tool, count] of toolCounts) {
|
|
334
335
|
if (count >= EXTRACTION_TOOL_THRESHOLD) {
|
|
335
336
|
rulesApplied.push('repeated_tool_usage');
|
|
336
|
-
proposals.push(this.createProposal(
|
|
337
|
+
proposals.push(this.createProposal(sessionId, 'repeated_tool_usage', 'pattern', {
|
|
337
338
|
title: `Frequent use of ${tool}`,
|
|
338
339
|
description: `Tool ${tool} was used ${count} times in session. Consider automating or abstracting this workflow.`,
|
|
339
340
|
confidence: Math.min(0.9, 0.5 + count * 0.1),
|
|
@@ -343,7 +344,7 @@ export class BrainIntelligence {
|
|
|
343
344
|
// Rule 2: Multi-file edits (3+ files)
|
|
344
345
|
if (session.filesModified.length >= EXTRACTION_FILE_THRESHOLD) {
|
|
345
346
|
rulesApplied.push('multi_file_edit');
|
|
346
|
-
proposals.push(this.createProposal(
|
|
347
|
+
proposals.push(this.createProposal(sessionId, 'multi_file_edit', 'pattern', {
|
|
347
348
|
title: `Multi-file change pattern (${session.filesModified.length} files)`,
|
|
348
349
|
description: `Session modified ${session.filesModified.length} files: ${session.filesModified.slice(0, 5).join(', ')}${session.filesModified.length > 5 ? '...' : ''}. This may indicate an architectural pattern.`,
|
|
349
350
|
confidence: Math.min(0.8, 0.4 + session.filesModified.length * 0.05),
|
|
@@ -355,7 +356,7 @@ export class BrainIntelligence {
|
|
|
355
356
|
const durationMin = durationMs / 60000;
|
|
356
357
|
if (durationMin > EXTRACTION_LONG_SESSION_MINUTES) {
|
|
357
358
|
rulesApplied.push('long_session');
|
|
358
|
-
proposals.push(this.createProposal(
|
|
359
|
+
proposals.push(this.createProposal(sessionId, 'long_session', 'anti-pattern', {
|
|
359
360
|
title: `Long session (${Math.round(durationMin)} minutes)`,
|
|
360
361
|
description: `Session lasted ${Math.round(durationMin)} minutes. Consider breaking complex tasks into smaller steps or improving automation.`,
|
|
361
362
|
confidence: 0.5,
|
|
@@ -365,7 +366,7 @@ export class BrainIntelligence {
|
|
|
365
366
|
// Rule 4: Plan completed
|
|
366
367
|
if (session.planId && session.planOutcome === 'completed') {
|
|
367
368
|
rulesApplied.push('plan_completed');
|
|
368
|
-
proposals.push(this.createProposal(
|
|
369
|
+
proposals.push(this.createProposal(sessionId, 'plan_completed', 'workflow', {
|
|
369
370
|
title: `Successful plan: ${session.planId}`,
|
|
370
371
|
description: `Plan ${session.planId} completed successfully. This workflow can be reused for similar tasks.`,
|
|
371
372
|
confidence: 0.8,
|
|
@@ -374,26 +375,24 @@ export class BrainIntelligence {
|
|
|
374
375
|
// Rule 5: Plan abandoned
|
|
375
376
|
if (session.planId && session.planOutcome === 'abandoned') {
|
|
376
377
|
rulesApplied.push('plan_abandoned');
|
|
377
|
-
proposals.push(this.createProposal(
|
|
378
|
+
proposals.push(this.createProposal(sessionId, 'plan_abandoned', 'anti-pattern', {
|
|
378
379
|
title: `Abandoned plan: ${session.planId}`,
|
|
379
380
|
description: `Plan ${session.planId} was abandoned. Review what went wrong to avoid repeating in future sessions.`,
|
|
380
381
|
confidence: 0.7,
|
|
381
382
|
}));
|
|
382
383
|
}
|
|
383
384
|
// Rule 6: High feedback ratio (>80% accept or dismiss)
|
|
384
|
-
const feedbackRow =
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
WHERE created_at >= ? AND created_at <= ?`)
|
|
390
|
-
.get(session.startedAt, session.endedAt ?? new Date().toISOString());
|
|
385
|
+
const feedbackRow = this.provider.get(`SELECT COUNT(*) as total,
|
|
386
|
+
SUM(CASE WHEN action = 'accepted' THEN 1 ELSE 0 END) as accepted,
|
|
387
|
+
SUM(CASE WHEN action = 'dismissed' THEN 1 ELSE 0 END) as dismissed
|
|
388
|
+
FROM brain_feedback
|
|
389
|
+
WHERE created_at >= ? AND created_at <= ?`, [session.startedAt, session.endedAt ?? new Date().toISOString()]);
|
|
391
390
|
if (feedbackRow.total >= 3) {
|
|
392
391
|
const acceptRate = feedbackRow.accepted / feedbackRow.total;
|
|
393
392
|
const dismissRate = feedbackRow.dismissed / feedbackRow.total;
|
|
394
393
|
if (acceptRate >= EXTRACTION_HIGH_FEEDBACK_RATIO) {
|
|
395
394
|
rulesApplied.push('high_accept_ratio');
|
|
396
|
-
proposals.push(this.createProposal(
|
|
395
|
+
proposals.push(this.createProposal(sessionId, 'high_accept_ratio', 'pattern', {
|
|
397
396
|
title: `High search acceptance rate (${Math.round(acceptRate * 100)}%)`,
|
|
398
397
|
description: `Search results were accepted ${Math.round(acceptRate * 100)}% of the time. Brain scoring is well-calibrated for this type of work.`,
|
|
399
398
|
confidence: 0.7,
|
|
@@ -401,7 +400,7 @@ export class BrainIntelligence {
|
|
|
401
400
|
}
|
|
402
401
|
else if (dismissRate >= EXTRACTION_HIGH_FEEDBACK_RATIO) {
|
|
403
402
|
rulesApplied.push('high_dismiss_ratio');
|
|
404
|
-
proposals.push(this.createProposal(
|
|
403
|
+
proposals.push(this.createProposal(sessionId, 'high_dismiss_ratio', 'anti-pattern', {
|
|
405
404
|
title: `High search dismissal rate (${Math.round(dismissRate * 100)}%)`,
|
|
406
405
|
description: `Search results were dismissed ${Math.round(dismissRate * 100)}% of the time. Brain scoring may need recalibration for this domain.`,
|
|
407
406
|
confidence: 0.7,
|
|
@@ -409,7 +408,9 @@ export class BrainIntelligence {
|
|
|
409
408
|
}
|
|
410
409
|
}
|
|
411
410
|
// Mark session as extracted
|
|
412
|
-
|
|
411
|
+
this.provider.run("UPDATE brain_sessions SET extracted_at = datetime('now') WHERE id = ?", [
|
|
412
|
+
sessionId,
|
|
413
|
+
]);
|
|
413
414
|
return {
|
|
414
415
|
sessionId,
|
|
415
416
|
proposals,
|
|
@@ -417,29 +418,21 @@ export class BrainIntelligence {
|
|
|
417
418
|
};
|
|
418
419
|
}
|
|
419
420
|
resetExtracted(options) {
|
|
420
|
-
const db = this.vault.getDb();
|
|
421
421
|
if (options?.sessionId) {
|
|
422
|
-
const info =
|
|
423
|
-
.prepare('UPDATE brain_sessions SET extracted_at = NULL WHERE id = ? AND extracted_at IS NOT NULL')
|
|
424
|
-
.run(options.sessionId);
|
|
422
|
+
const info = this.provider.run('UPDATE brain_sessions SET extracted_at = NULL WHERE id = ? AND extracted_at IS NOT NULL', [options.sessionId]);
|
|
425
423
|
return { reset: info.changes };
|
|
426
424
|
}
|
|
427
425
|
if (options?.since) {
|
|
428
|
-
const info =
|
|
429
|
-
.prepare('UPDATE brain_sessions SET extracted_at = NULL WHERE extracted_at >= ?')
|
|
430
|
-
.run(options.since);
|
|
426
|
+
const info = this.provider.run('UPDATE brain_sessions SET extracted_at = NULL WHERE extracted_at >= ?', [options.since]);
|
|
431
427
|
return { reset: info.changes };
|
|
432
428
|
}
|
|
433
429
|
if (options?.all) {
|
|
434
|
-
const info =
|
|
435
|
-
.prepare('UPDATE brain_sessions SET extracted_at = NULL WHERE extracted_at IS NOT NULL')
|
|
436
|
-
.run();
|
|
430
|
+
const info = this.provider.run('UPDATE brain_sessions SET extracted_at = NULL WHERE extracted_at IS NOT NULL');
|
|
437
431
|
return { reset: info.changes };
|
|
438
432
|
}
|
|
439
433
|
return { reset: 0 };
|
|
440
434
|
}
|
|
441
435
|
getProposals(options) {
|
|
442
|
-
const db = this.vault.getDb();
|
|
443
436
|
const conditions = [];
|
|
444
437
|
const values = [];
|
|
445
438
|
if (options?.sessionId) {
|
|
@@ -453,19 +446,16 @@ export class BrainIntelligence {
|
|
|
453
446
|
const where = conditions.length > 0 ? 'WHERE ' + conditions.join(' AND ') : '';
|
|
454
447
|
const limit = options?.limit ?? 50;
|
|
455
448
|
values.push(limit);
|
|
456
|
-
const rows =
|
|
457
|
-
.prepare(`SELECT * FROM brain_proposals ${where} ORDER BY created_at DESC LIMIT ?`)
|
|
458
|
-
.all(...values);
|
|
449
|
+
const rows = this.provider.all(`SELECT * FROM brain_proposals ${where} ORDER BY created_at DESC LIMIT ?`, values);
|
|
459
450
|
return rows.map((r) => this.rowToProposal(r));
|
|
460
451
|
}
|
|
461
452
|
promoteProposals(proposalIds, governanceGate, projectPath) {
|
|
462
|
-
const db = this.vault.getDb();
|
|
463
453
|
let promoted = 0;
|
|
464
454
|
const failed = [];
|
|
465
455
|
const gated = [];
|
|
466
456
|
const pp = projectPath ?? '.';
|
|
467
457
|
for (const id of proposalIds) {
|
|
468
|
-
const row =
|
|
458
|
+
const row = this.provider.get('SELECT * FROM brain_proposals WHERE id = ?', [id]);
|
|
469
459
|
if (!row) {
|
|
470
460
|
failed.push(id);
|
|
471
461
|
continue;
|
|
@@ -512,7 +502,7 @@ export class BrainIntelligence {
|
|
|
512
502
|
description: row.description,
|
|
513
503
|
tags: ['auto-extracted', row.rule],
|
|
514
504
|
});
|
|
515
|
-
|
|
505
|
+
this.provider.run('UPDATE brain_proposals SET promoted = 1 WHERE id = ?', [id]);
|
|
516
506
|
promoted++;
|
|
517
507
|
}
|
|
518
508
|
return { promoted, failed, gated };
|
|
@@ -532,10 +522,7 @@ export class BrainIntelligence {
|
|
|
532
522
|
};
|
|
533
523
|
}
|
|
534
524
|
getGlobalPatterns(limit = 20) {
|
|
535
|
-
const
|
|
536
|
-
const rows = db
|
|
537
|
-
.prepare('SELECT * FROM brain_global_registry ORDER BY total_strength DESC LIMIT ?')
|
|
538
|
-
.all(limit);
|
|
525
|
+
const rows = this.provider.all('SELECT * FROM brain_global_registry ORDER BY total_strength DESC LIMIT ?', [limit]);
|
|
539
526
|
return rows.map((r) => ({
|
|
540
527
|
pattern: r.pattern,
|
|
541
528
|
domains: JSON.parse(r.domains),
|
|
@@ -545,8 +532,7 @@ export class BrainIntelligence {
|
|
|
545
532
|
}));
|
|
546
533
|
}
|
|
547
534
|
getDomainProfile(domain) {
|
|
548
|
-
const
|
|
549
|
-
const row = db.prepare('SELECT * FROM brain_domain_profiles WHERE domain = ?').get(domain);
|
|
535
|
+
const row = this.provider.get('SELECT * FROM brain_domain_profiles WHERE domain = ?', [domain]);
|
|
550
536
|
if (!row)
|
|
551
537
|
return null;
|
|
552
538
|
return {
|
|
@@ -559,15 +545,13 @@ export class BrainIntelligence {
|
|
|
559
545
|
}
|
|
560
546
|
// ─── Data Management ──────────────────────────────────────────────
|
|
561
547
|
getStats() {
|
|
562
|
-
const
|
|
563
|
-
const
|
|
564
|
-
const
|
|
565
|
-
|
|
566
|
-
const
|
|
567
|
-
const
|
|
568
|
-
const
|
|
569
|
-
const globalPatterns = db.prepare('SELECT COUNT(*) as c FROM brain_global_registry').get().c;
|
|
570
|
-
const domainProfiles = db.prepare('SELECT COUNT(*) as c FROM brain_domain_profiles').get().c;
|
|
548
|
+
const strengths = this.provider.get('SELECT COUNT(*) as c FROM brain_strengths').c;
|
|
549
|
+
const sessions = this.provider.get('SELECT COUNT(*) as c FROM brain_sessions').c;
|
|
550
|
+
const activeSessions = this.provider.get('SELECT COUNT(*) as c FROM brain_sessions WHERE ended_at IS NULL').c;
|
|
551
|
+
const proposals = this.provider.get('SELECT COUNT(*) as c FROM brain_proposals').c;
|
|
552
|
+
const promotedProposals = this.provider.get('SELECT COUNT(*) as c FROM brain_proposals WHERE promoted = 1').c;
|
|
553
|
+
const globalPatterns = this.provider.get('SELECT COUNT(*) as c FROM brain_global_registry').c;
|
|
554
|
+
const domainProfiles = this.provider.get('SELECT COUNT(*) as c FROM brain_domain_profiles').c;
|
|
571
555
|
return {
|
|
572
556
|
strengths,
|
|
573
557
|
sessions,
|
|
@@ -579,15 +563,12 @@ export class BrainIntelligence {
|
|
|
579
563
|
};
|
|
580
564
|
}
|
|
581
565
|
exportData() {
|
|
582
|
-
const db = this.vault.getDb();
|
|
583
566
|
const strengths = this.getStrengths({ limit: 10000 });
|
|
584
|
-
const sessionRows =
|
|
585
|
-
.prepare('SELECT * FROM brain_sessions ORDER BY started_at DESC')
|
|
586
|
-
.all();
|
|
567
|
+
const sessionRows = this.provider.all('SELECT * FROM brain_sessions ORDER BY started_at DESC');
|
|
587
568
|
const sessions = sessionRows.map((r) => this.rowToSession(r));
|
|
588
569
|
const proposals = this.getProposals({ limit: 10000 });
|
|
589
570
|
const globalPatterns = this.getGlobalPatterns(10000);
|
|
590
|
-
const profileRows =
|
|
571
|
+
const profileRows = this.provider.all('SELECT * FROM brain_domain_profiles');
|
|
591
572
|
const domainProfiles = profileRows.map((r) => ({
|
|
592
573
|
domain: r.domain,
|
|
593
574
|
topPatterns: JSON.parse(r.top_patterns),
|
|
@@ -605,62 +586,93 @@ export class BrainIntelligence {
|
|
|
605
586
|
};
|
|
606
587
|
}
|
|
607
588
|
importData(data) {
|
|
608
|
-
const db = this.vault.getDb();
|
|
609
589
|
const result = {
|
|
610
590
|
imported: { strengths: 0, sessions: 0, proposals: 0, globalPatterns: 0, domainProfiles: 0 },
|
|
611
591
|
};
|
|
612
|
-
|
|
592
|
+
this.provider.transaction(() => {
|
|
613
593
|
// Import strengths
|
|
614
|
-
const insertStrength = db.prepare(`INSERT OR REPLACE INTO brain_strengths
|
|
615
|
-
(pattern, domain, strength, usage_score, spread_score, success_score, recency_score,
|
|
616
|
-
usage_count, unique_contexts, success_rate, last_used, updated_at)
|
|
617
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, datetime('now'))`);
|
|
618
594
|
for (const s of data.strengths) {
|
|
619
|
-
|
|
595
|
+
this.provider.run(`INSERT OR REPLACE INTO brain_strengths
|
|
596
|
+
(pattern, domain, strength, usage_score, spread_score, success_score, recency_score,
|
|
597
|
+
usage_count, unique_contexts, success_rate, last_used, updated_at)
|
|
598
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, datetime('now'))`, [
|
|
599
|
+
s.pattern,
|
|
600
|
+
s.domain,
|
|
601
|
+
s.strength,
|
|
602
|
+
s.usageScore,
|
|
603
|
+
s.spreadScore,
|
|
604
|
+
s.successScore,
|
|
605
|
+
s.recencyScore,
|
|
606
|
+
s.usageCount,
|
|
607
|
+
s.uniqueContexts,
|
|
608
|
+
s.successRate,
|
|
609
|
+
s.lastUsed,
|
|
610
|
+
]);
|
|
620
611
|
result.imported.strengths++;
|
|
621
612
|
}
|
|
622
613
|
// Import sessions
|
|
623
|
-
const insertSession = db.prepare(`INSERT OR IGNORE INTO brain_sessions
|
|
624
|
-
(id, started_at, ended_at, domain, context, tools_used, files_modified, plan_id, plan_outcome, extracted_at)
|
|
625
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`);
|
|
626
614
|
for (const s of data.sessions) {
|
|
627
|
-
const changes =
|
|
615
|
+
const changes = this.provider.run(`INSERT OR IGNORE INTO brain_sessions
|
|
616
|
+
(id, started_at, ended_at, domain, context, tools_used, files_modified, plan_id, plan_outcome, extracted_at)
|
|
617
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
618
|
+
s.id,
|
|
619
|
+
s.startedAt,
|
|
620
|
+
s.endedAt,
|
|
621
|
+
s.domain,
|
|
622
|
+
s.context,
|
|
623
|
+
JSON.stringify(s.toolsUsed),
|
|
624
|
+
JSON.stringify(s.filesModified),
|
|
625
|
+
s.planId,
|
|
626
|
+
s.planOutcome,
|
|
627
|
+
s.extractedAt ?? null,
|
|
628
|
+
]);
|
|
628
629
|
if (changes.changes > 0)
|
|
629
630
|
result.imported.sessions++;
|
|
630
631
|
}
|
|
631
632
|
// Import proposals
|
|
632
|
-
const insertProposal = db.prepare(`INSERT OR IGNORE INTO brain_proposals
|
|
633
|
-
(id, session_id, rule, type, title, description, confidence, promoted, created_at)
|
|
634
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`);
|
|
635
633
|
for (const p of data.proposals) {
|
|
636
|
-
const changes =
|
|
634
|
+
const changes = this.provider.run(`INSERT OR IGNORE INTO brain_proposals
|
|
635
|
+
(id, session_id, rule, type, title, description, confidence, promoted, created_at)
|
|
636
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
637
|
+
p.id,
|
|
638
|
+
p.sessionId,
|
|
639
|
+
p.rule,
|
|
640
|
+
p.type,
|
|
641
|
+
p.title,
|
|
642
|
+
p.description,
|
|
643
|
+
p.confidence,
|
|
644
|
+
p.promoted ? 1 : 0,
|
|
645
|
+
p.createdAt,
|
|
646
|
+
]);
|
|
637
647
|
if (changes.changes > 0)
|
|
638
648
|
result.imported.proposals++;
|
|
639
649
|
}
|
|
640
650
|
// Import global patterns
|
|
641
|
-
const insertGlobal = db.prepare(`INSERT OR REPLACE INTO brain_global_registry
|
|
642
|
-
(pattern, domains, total_strength, avg_strength, domain_count, updated_at)
|
|
643
|
-
VALUES (?, ?, ?, ?, ?, datetime('now'))`);
|
|
644
651
|
for (const g of data.globalPatterns) {
|
|
645
|
-
|
|
652
|
+
this.provider.run(`INSERT OR REPLACE INTO brain_global_registry
|
|
653
|
+
(pattern, domains, total_strength, avg_strength, domain_count, updated_at)
|
|
654
|
+
VALUES (?, ?, ?, ?, ?, datetime('now'))`, [g.pattern, JSON.stringify(g.domains), g.totalStrength, g.avgStrength, g.domainCount]);
|
|
646
655
|
result.imported.globalPatterns++;
|
|
647
656
|
}
|
|
648
657
|
// Import domain profiles
|
|
649
|
-
const insertProfile = db.prepare(`INSERT OR REPLACE INTO brain_domain_profiles
|
|
650
|
-
(domain, top_patterns, session_count, avg_session_duration, last_activity, updated_at)
|
|
651
|
-
VALUES (?, ?, ?, ?, ?, datetime('now'))`);
|
|
652
658
|
for (const d of data.domainProfiles) {
|
|
653
|
-
|
|
659
|
+
this.provider.run(`INSERT OR REPLACE INTO brain_domain_profiles
|
|
660
|
+
(domain, top_patterns, session_count, avg_session_duration, last_activity, updated_at)
|
|
661
|
+
VALUES (?, ?, ?, ?, ?, datetime('now'))`, [
|
|
662
|
+
d.domain,
|
|
663
|
+
JSON.stringify(d.topPatterns),
|
|
664
|
+
d.sessionCount,
|
|
665
|
+
d.avgSessionDuration,
|
|
666
|
+
d.lastActivity,
|
|
667
|
+
]);
|
|
654
668
|
result.imported.domainProfiles++;
|
|
655
669
|
}
|
|
656
670
|
});
|
|
657
|
-
tx();
|
|
658
671
|
return result;
|
|
659
672
|
}
|
|
660
673
|
// ─── Private Helpers ──────────────────────────────────────────────
|
|
661
674
|
getSession(id) {
|
|
662
|
-
const
|
|
663
|
-
const row = db.prepare('SELECT * FROM brain_sessions WHERE id = ?').get(id);
|
|
675
|
+
const row = this.provider.get('SELECT * FROM brain_sessions WHERE id = ?', [id]);
|
|
664
676
|
if (!row)
|
|
665
677
|
return null;
|
|
666
678
|
return this.rowToSession(row);
|
|
@@ -692,10 +704,10 @@ export class BrainIntelligence {
|
|
|
692
704
|
createdAt: row.created_at,
|
|
693
705
|
};
|
|
694
706
|
}
|
|
695
|
-
createProposal(
|
|
707
|
+
createProposal(sessionId, rule, type, data) {
|
|
696
708
|
const id = randomUUID();
|
|
697
|
-
|
|
698
|
-
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
709
|
+
this.provider.run(`INSERT INTO brain_proposals (id, session_id, rule, type, title, description, confidence)
|
|
710
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)`, [id, sessionId, rule, type, data.title, data.description, data.confidence]);
|
|
699
711
|
return {
|
|
700
712
|
id,
|
|
701
713
|
sessionId,
|
|
@@ -709,7 +721,6 @@ export class BrainIntelligence {
|
|
|
709
721
|
};
|
|
710
722
|
}
|
|
711
723
|
buildGlobalRegistry(strengths) {
|
|
712
|
-
const db = this.vault.getDb();
|
|
713
724
|
// Group strengths by pattern
|
|
714
725
|
const patternMap = new Map();
|
|
715
726
|
for (const s of strengths) {
|
|
@@ -717,22 +728,20 @@ export class BrainIntelligence {
|
|
|
717
728
|
list.push(s);
|
|
718
729
|
patternMap.set(s.pattern, list);
|
|
719
730
|
}
|
|
720
|
-
|
|
721
|
-
const insert = db.prepare(`INSERT INTO brain_global_registry
|
|
722
|
-
(pattern, domains, total_strength, avg_strength, domain_count, updated_at)
|
|
723
|
-
VALUES (?, ?, ?, ?, ?, datetime('now'))`);
|
|
731
|
+
this.provider.run('DELETE FROM brain_global_registry');
|
|
724
732
|
let count = 0;
|
|
725
733
|
for (const [pattern, entries] of patternMap) {
|
|
726
734
|
const domains = [...new Set(entries.map((e) => e.domain))];
|
|
727
735
|
const totalStrength = entries.reduce((sum, e) => sum + e.strength, 0);
|
|
728
736
|
const avgStrength = totalStrength / entries.length;
|
|
729
|
-
|
|
737
|
+
this.provider.run(`INSERT INTO brain_global_registry
|
|
738
|
+
(pattern, domains, total_strength, avg_strength, domain_count, updated_at)
|
|
739
|
+
VALUES (?, ?, ?, ?, ?, datetime('now'))`, [pattern, JSON.stringify(domains), totalStrength, avgStrength, domains.length]);
|
|
730
740
|
count++;
|
|
731
741
|
}
|
|
732
742
|
return count;
|
|
733
743
|
}
|
|
734
744
|
buildDomainProfiles(strengths) {
|
|
735
|
-
const db = this.vault.getDb();
|
|
736
745
|
// Group strengths by domain
|
|
737
746
|
const domainMap = new Map();
|
|
738
747
|
for (const s of strengths) {
|
|
@@ -740,10 +749,7 @@ export class BrainIntelligence {
|
|
|
740
749
|
list.push(s);
|
|
741
750
|
domainMap.set(s.domain, list);
|
|
742
751
|
}
|
|
743
|
-
|
|
744
|
-
const insert = db.prepare(`INSERT INTO brain_domain_profiles
|
|
745
|
-
(domain, top_patterns, session_count, avg_session_duration, last_activity, updated_at)
|
|
746
|
-
VALUES (?, ?, ?, ?, ?, datetime('now'))`);
|
|
752
|
+
this.provider.run('DELETE FROM brain_domain_profiles');
|
|
747
753
|
let count = 0;
|
|
748
754
|
for (const [domain, entries] of domainMap) {
|
|
749
755
|
entries.sort((a, b) => b.strength - a.strength);
|
|
@@ -752,17 +758,23 @@ export class BrainIntelligence {
|
|
|
752
758
|
strength: e.strength,
|
|
753
759
|
}));
|
|
754
760
|
// Count sessions for this domain
|
|
755
|
-
const sessionCount =
|
|
761
|
+
const sessionCount = this.provider.get('SELECT COUNT(*) as c FROM brain_sessions WHERE domain = ?', [domain]).c;
|
|
756
762
|
// Average session duration (in minutes)
|
|
757
|
-
const durationRow =
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
WHERE domain = ? AND ended_at IS NOT NULL`)
|
|
763
|
-
.get(domain);
|
|
763
|
+
const durationRow = this.provider.get(`SELECT AVG(
|
|
764
|
+
(julianday(ended_at) - julianday(started_at)) * 1440
|
|
765
|
+
) as avg_min
|
|
766
|
+
FROM brain_sessions
|
|
767
|
+
WHERE domain = ? AND ended_at IS NOT NULL`, [domain]);
|
|
764
768
|
const lastActivity = entries.reduce((latest, e) => (e.lastUsed > latest ? e.lastUsed : latest), '');
|
|
765
|
-
|
|
769
|
+
this.provider.run(`INSERT INTO brain_domain_profiles
|
|
770
|
+
(domain, top_patterns, session_count, avg_session_duration, last_activity, updated_at)
|
|
771
|
+
VALUES (?, ?, ?, ?, ?, datetime('now'))`, [
|
|
772
|
+
domain,
|
|
773
|
+
JSON.stringify(topPatterns),
|
|
774
|
+
sessionCount,
|
|
775
|
+
durationRow.avg_min ?? 0,
|
|
776
|
+
lastActivity || new Date().toISOString(),
|
|
777
|
+
]);
|
|
766
778
|
count++;
|
|
767
779
|
}
|
|
768
780
|
return count;
|