@soleri/core 2.5.0 → 2.6.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 +164 -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 +49 -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 +32 -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 +48 -99
- package/dist/curator/curator.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 +39 -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 +25 -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 +29 -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/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,7 @@ 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 = ?", [sessionId]);
|
|
413
412
|
return {
|
|
414
413
|
sessionId,
|
|
415
414
|
proposals,
|
|
@@ -417,29 +416,21 @@ export class BrainIntelligence {
|
|
|
417
416
|
};
|
|
418
417
|
}
|
|
419
418
|
resetExtracted(options) {
|
|
420
|
-
const db = this.vault.getDb();
|
|
421
419
|
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);
|
|
420
|
+
const info = this.provider.run('UPDATE brain_sessions SET extracted_at = NULL WHERE id = ? AND extracted_at IS NOT NULL', [options.sessionId]);
|
|
425
421
|
return { reset: info.changes };
|
|
426
422
|
}
|
|
427
423
|
if (options?.since) {
|
|
428
|
-
const info =
|
|
429
|
-
.prepare('UPDATE brain_sessions SET extracted_at = NULL WHERE extracted_at >= ?')
|
|
430
|
-
.run(options.since);
|
|
424
|
+
const info = this.provider.run('UPDATE brain_sessions SET extracted_at = NULL WHERE extracted_at >= ?', [options.since]);
|
|
431
425
|
return { reset: info.changes };
|
|
432
426
|
}
|
|
433
427
|
if (options?.all) {
|
|
434
|
-
const info =
|
|
435
|
-
.prepare('UPDATE brain_sessions SET extracted_at = NULL WHERE extracted_at IS NOT NULL')
|
|
436
|
-
.run();
|
|
428
|
+
const info = this.provider.run('UPDATE brain_sessions SET extracted_at = NULL WHERE extracted_at IS NOT NULL');
|
|
437
429
|
return { reset: info.changes };
|
|
438
430
|
}
|
|
439
431
|
return { reset: 0 };
|
|
440
432
|
}
|
|
441
433
|
getProposals(options) {
|
|
442
|
-
const db = this.vault.getDb();
|
|
443
434
|
const conditions = [];
|
|
444
435
|
const values = [];
|
|
445
436
|
if (options?.sessionId) {
|
|
@@ -453,19 +444,16 @@ export class BrainIntelligence {
|
|
|
453
444
|
const where = conditions.length > 0 ? 'WHERE ' + conditions.join(' AND ') : '';
|
|
454
445
|
const limit = options?.limit ?? 50;
|
|
455
446
|
values.push(limit);
|
|
456
|
-
const rows =
|
|
457
|
-
.prepare(`SELECT * FROM brain_proposals ${where} ORDER BY created_at DESC LIMIT ?`)
|
|
458
|
-
.all(...values);
|
|
447
|
+
const rows = this.provider.all(`SELECT * FROM brain_proposals ${where} ORDER BY created_at DESC LIMIT ?`, values);
|
|
459
448
|
return rows.map((r) => this.rowToProposal(r));
|
|
460
449
|
}
|
|
461
450
|
promoteProposals(proposalIds, governanceGate, projectPath) {
|
|
462
|
-
const db = this.vault.getDb();
|
|
463
451
|
let promoted = 0;
|
|
464
452
|
const failed = [];
|
|
465
453
|
const gated = [];
|
|
466
454
|
const pp = projectPath ?? '.';
|
|
467
455
|
for (const id of proposalIds) {
|
|
468
|
-
const row =
|
|
456
|
+
const row = this.provider.get('SELECT * FROM brain_proposals WHERE id = ?', [id]);
|
|
469
457
|
if (!row) {
|
|
470
458
|
failed.push(id);
|
|
471
459
|
continue;
|
|
@@ -512,7 +500,7 @@ export class BrainIntelligence {
|
|
|
512
500
|
description: row.description,
|
|
513
501
|
tags: ['auto-extracted', row.rule],
|
|
514
502
|
});
|
|
515
|
-
|
|
503
|
+
this.provider.run('UPDATE brain_proposals SET promoted = 1 WHERE id = ?', [id]);
|
|
516
504
|
promoted++;
|
|
517
505
|
}
|
|
518
506
|
return { promoted, failed, gated };
|
|
@@ -532,10 +520,7 @@ export class BrainIntelligence {
|
|
|
532
520
|
};
|
|
533
521
|
}
|
|
534
522
|
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);
|
|
523
|
+
const rows = this.provider.all('SELECT * FROM brain_global_registry ORDER BY total_strength DESC LIMIT ?', [limit]);
|
|
539
524
|
return rows.map((r) => ({
|
|
540
525
|
pattern: r.pattern,
|
|
541
526
|
domains: JSON.parse(r.domains),
|
|
@@ -545,8 +530,7 @@ export class BrainIntelligence {
|
|
|
545
530
|
}));
|
|
546
531
|
}
|
|
547
532
|
getDomainProfile(domain) {
|
|
548
|
-
const
|
|
549
|
-
const row = db.prepare('SELECT * FROM brain_domain_profiles WHERE domain = ?').get(domain);
|
|
533
|
+
const row = this.provider.get('SELECT * FROM brain_domain_profiles WHERE domain = ?', [domain]);
|
|
550
534
|
if (!row)
|
|
551
535
|
return null;
|
|
552
536
|
return {
|
|
@@ -559,15 +543,13 @@ export class BrainIntelligence {
|
|
|
559
543
|
}
|
|
560
544
|
// ─── Data Management ──────────────────────────────────────────────
|
|
561
545
|
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;
|
|
546
|
+
const strengths = (this.provider.get('SELECT COUNT(*) as c FROM brain_strengths')).c;
|
|
547
|
+
const sessions = (this.provider.get('SELECT COUNT(*) as c FROM brain_sessions')).c;
|
|
548
|
+
const activeSessions = (this.provider.get('SELECT COUNT(*) as c FROM brain_sessions WHERE ended_at IS NULL')).c;
|
|
549
|
+
const proposals = (this.provider.get('SELECT COUNT(*) as c FROM brain_proposals')).c;
|
|
550
|
+
const promotedProposals = (this.provider.get('SELECT COUNT(*) as c FROM brain_proposals WHERE promoted = 1')).c;
|
|
551
|
+
const globalPatterns = (this.provider.get('SELECT COUNT(*) as c FROM brain_global_registry')).c;
|
|
552
|
+
const domainProfiles = (this.provider.get('SELECT COUNT(*) as c FROM brain_domain_profiles')).c;
|
|
571
553
|
return {
|
|
572
554
|
strengths,
|
|
573
555
|
sessions,
|
|
@@ -579,15 +561,12 @@ export class BrainIntelligence {
|
|
|
579
561
|
};
|
|
580
562
|
}
|
|
581
563
|
exportData() {
|
|
582
|
-
const db = this.vault.getDb();
|
|
583
564
|
const strengths = this.getStrengths({ limit: 10000 });
|
|
584
|
-
const sessionRows =
|
|
585
|
-
.prepare('SELECT * FROM brain_sessions ORDER BY started_at DESC')
|
|
586
|
-
.all();
|
|
565
|
+
const sessionRows = this.provider.all('SELECT * FROM brain_sessions ORDER BY started_at DESC');
|
|
587
566
|
const sessions = sessionRows.map((r) => this.rowToSession(r));
|
|
588
567
|
const proposals = this.getProposals({ limit: 10000 });
|
|
589
568
|
const globalPatterns = this.getGlobalPatterns(10000);
|
|
590
|
-
const profileRows =
|
|
569
|
+
const profileRows = this.provider.all('SELECT * FROM brain_domain_profiles');
|
|
591
570
|
const domainProfiles = profileRows.map((r) => ({
|
|
592
571
|
domain: r.domain,
|
|
593
572
|
topPatterns: JSON.parse(r.top_patterns),
|
|
@@ -605,62 +584,99 @@ export class BrainIntelligence {
|
|
|
605
584
|
};
|
|
606
585
|
}
|
|
607
586
|
importData(data) {
|
|
608
|
-
const db = this.vault.getDb();
|
|
609
587
|
const result = {
|
|
610
588
|
imported: { strengths: 0, sessions: 0, proposals: 0, globalPatterns: 0, domainProfiles: 0 },
|
|
611
589
|
};
|
|
612
|
-
|
|
590
|
+
this.provider.transaction(() => {
|
|
613
591
|
// 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
592
|
for (const s of data.strengths) {
|
|
619
|
-
|
|
593
|
+
this.provider.run(`INSERT OR REPLACE INTO brain_strengths
|
|
594
|
+
(pattern, domain, strength, usage_score, spread_score, success_score, recency_score,
|
|
595
|
+
usage_count, unique_contexts, success_rate, last_used, updated_at)
|
|
596
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, datetime('now'))`, [
|
|
597
|
+
s.pattern,
|
|
598
|
+
s.domain,
|
|
599
|
+
s.strength,
|
|
600
|
+
s.usageScore,
|
|
601
|
+
s.spreadScore,
|
|
602
|
+
s.successScore,
|
|
603
|
+
s.recencyScore,
|
|
604
|
+
s.usageCount,
|
|
605
|
+
s.uniqueContexts,
|
|
606
|
+
s.successRate,
|
|
607
|
+
s.lastUsed,
|
|
608
|
+
]);
|
|
620
609
|
result.imported.strengths++;
|
|
621
610
|
}
|
|
622
611
|
// 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
612
|
for (const s of data.sessions) {
|
|
627
|
-
const changes =
|
|
613
|
+
const changes = this.provider.run(`INSERT OR IGNORE INTO brain_sessions
|
|
614
|
+
(id, started_at, ended_at, domain, context, tools_used, files_modified, plan_id, plan_outcome, extracted_at)
|
|
615
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
616
|
+
s.id,
|
|
617
|
+
s.startedAt,
|
|
618
|
+
s.endedAt,
|
|
619
|
+
s.domain,
|
|
620
|
+
s.context,
|
|
621
|
+
JSON.stringify(s.toolsUsed),
|
|
622
|
+
JSON.stringify(s.filesModified),
|
|
623
|
+
s.planId,
|
|
624
|
+
s.planOutcome,
|
|
625
|
+
s.extractedAt ?? null,
|
|
626
|
+
]);
|
|
628
627
|
if (changes.changes > 0)
|
|
629
628
|
result.imported.sessions++;
|
|
630
629
|
}
|
|
631
630
|
// 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
631
|
for (const p of data.proposals) {
|
|
636
|
-
const changes =
|
|
632
|
+
const changes = this.provider.run(`INSERT OR IGNORE INTO brain_proposals
|
|
633
|
+
(id, session_id, rule, type, title, description, confidence, promoted, created_at)
|
|
634
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
635
|
+
p.id,
|
|
636
|
+
p.sessionId,
|
|
637
|
+
p.rule,
|
|
638
|
+
p.type,
|
|
639
|
+
p.title,
|
|
640
|
+
p.description,
|
|
641
|
+
p.confidence,
|
|
642
|
+
p.promoted ? 1 : 0,
|
|
643
|
+
p.createdAt,
|
|
644
|
+
]);
|
|
637
645
|
if (changes.changes > 0)
|
|
638
646
|
result.imported.proposals++;
|
|
639
647
|
}
|
|
640
648
|
// 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
649
|
for (const g of data.globalPatterns) {
|
|
645
|
-
|
|
650
|
+
this.provider.run(`INSERT OR REPLACE INTO brain_global_registry
|
|
651
|
+
(pattern, domains, total_strength, avg_strength, domain_count, updated_at)
|
|
652
|
+
VALUES (?, ?, ?, ?, ?, datetime('now'))`, [
|
|
653
|
+
g.pattern,
|
|
654
|
+
JSON.stringify(g.domains),
|
|
655
|
+
g.totalStrength,
|
|
656
|
+
g.avgStrength,
|
|
657
|
+
g.domainCount,
|
|
658
|
+
]);
|
|
646
659
|
result.imported.globalPatterns++;
|
|
647
660
|
}
|
|
648
661
|
// 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
662
|
for (const d of data.domainProfiles) {
|
|
653
|
-
|
|
663
|
+
this.provider.run(`INSERT OR REPLACE INTO brain_domain_profiles
|
|
664
|
+
(domain, top_patterns, session_count, avg_session_duration, last_activity, updated_at)
|
|
665
|
+
VALUES (?, ?, ?, ?, ?, datetime('now'))`, [
|
|
666
|
+
d.domain,
|
|
667
|
+
JSON.stringify(d.topPatterns),
|
|
668
|
+
d.sessionCount,
|
|
669
|
+
d.avgSessionDuration,
|
|
670
|
+
d.lastActivity,
|
|
671
|
+
]);
|
|
654
672
|
result.imported.domainProfiles++;
|
|
655
673
|
}
|
|
656
674
|
});
|
|
657
|
-
tx();
|
|
658
675
|
return result;
|
|
659
676
|
}
|
|
660
677
|
// ─── Private Helpers ──────────────────────────────────────────────
|
|
661
678
|
getSession(id) {
|
|
662
|
-
const
|
|
663
|
-
const row = db.prepare('SELECT * FROM brain_sessions WHERE id = ?').get(id);
|
|
679
|
+
const row = this.provider.get('SELECT * FROM brain_sessions WHERE id = ?', [id]);
|
|
664
680
|
if (!row)
|
|
665
681
|
return null;
|
|
666
682
|
return this.rowToSession(row);
|
|
@@ -692,10 +708,10 @@ export class BrainIntelligence {
|
|
|
692
708
|
createdAt: row.created_at,
|
|
693
709
|
};
|
|
694
710
|
}
|
|
695
|
-
createProposal(
|
|
711
|
+
createProposal(sessionId, rule, type, data) {
|
|
696
712
|
const id = randomUUID();
|
|
697
|
-
|
|
698
|
-
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
713
|
+
this.provider.run(`INSERT INTO brain_proposals (id, session_id, rule, type, title, description, confidence)
|
|
714
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)`, [id, sessionId, rule, type, data.title, data.description, data.confidence]);
|
|
699
715
|
return {
|
|
700
716
|
id,
|
|
701
717
|
sessionId,
|
|
@@ -709,7 +725,6 @@ export class BrainIntelligence {
|
|
|
709
725
|
};
|
|
710
726
|
}
|
|
711
727
|
buildGlobalRegistry(strengths) {
|
|
712
|
-
const db = this.vault.getDb();
|
|
713
728
|
// Group strengths by pattern
|
|
714
729
|
const patternMap = new Map();
|
|
715
730
|
for (const s of strengths) {
|
|
@@ -717,22 +732,20 @@ export class BrainIntelligence {
|
|
|
717
732
|
list.push(s);
|
|
718
733
|
patternMap.set(s.pattern, list);
|
|
719
734
|
}
|
|
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'))`);
|
|
735
|
+
this.provider.run('DELETE FROM brain_global_registry');
|
|
724
736
|
let count = 0;
|
|
725
737
|
for (const [pattern, entries] of patternMap) {
|
|
726
738
|
const domains = [...new Set(entries.map((e) => e.domain))];
|
|
727
739
|
const totalStrength = entries.reduce((sum, e) => sum + e.strength, 0);
|
|
728
740
|
const avgStrength = totalStrength / entries.length;
|
|
729
|
-
|
|
741
|
+
this.provider.run(`INSERT INTO brain_global_registry
|
|
742
|
+
(pattern, domains, total_strength, avg_strength, domain_count, updated_at)
|
|
743
|
+
VALUES (?, ?, ?, ?, ?, datetime('now'))`, [pattern, JSON.stringify(domains), totalStrength, avgStrength, domains.length]);
|
|
730
744
|
count++;
|
|
731
745
|
}
|
|
732
746
|
return count;
|
|
733
747
|
}
|
|
734
748
|
buildDomainProfiles(strengths) {
|
|
735
|
-
const db = this.vault.getDb();
|
|
736
749
|
// Group strengths by domain
|
|
737
750
|
const domainMap = new Map();
|
|
738
751
|
for (const s of strengths) {
|
|
@@ -740,10 +753,7 @@ export class BrainIntelligence {
|
|
|
740
753
|
list.push(s);
|
|
741
754
|
domainMap.set(s.domain, list);
|
|
742
755
|
}
|
|
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'))`);
|
|
756
|
+
this.provider.run('DELETE FROM brain_domain_profiles');
|
|
747
757
|
let count = 0;
|
|
748
758
|
for (const [domain, entries] of domainMap) {
|
|
749
759
|
entries.sort((a, b) => b.strength - a.strength);
|
|
@@ -752,17 +762,23 @@ export class BrainIntelligence {
|
|
|
752
762
|
strength: e.strength,
|
|
753
763
|
}));
|
|
754
764
|
// Count sessions for this domain
|
|
755
|
-
const sessionCount =
|
|
765
|
+
const sessionCount = (this.provider.get('SELECT COUNT(*) as c FROM brain_sessions WHERE domain = ?', [domain])).c;
|
|
756
766
|
// Average session duration (in minutes)
|
|
757
|
-
const durationRow =
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
WHERE domain = ? AND ended_at IS NOT NULL`)
|
|
763
|
-
.get(domain);
|
|
767
|
+
const durationRow = this.provider.get(`SELECT AVG(
|
|
768
|
+
(julianday(ended_at) - julianday(started_at)) * 1440
|
|
769
|
+
) as avg_min
|
|
770
|
+
FROM brain_sessions
|
|
771
|
+
WHERE domain = ? AND ended_at IS NOT NULL`, [domain]);
|
|
764
772
|
const lastActivity = entries.reduce((latest, e) => (e.lastUsed > latest ? e.lastUsed : latest), '');
|
|
765
|
-
|
|
773
|
+
this.provider.run(`INSERT INTO brain_domain_profiles
|
|
774
|
+
(domain, top_patterns, session_count, avg_session_duration, last_activity, updated_at)
|
|
775
|
+
VALUES (?, ?, ?, ?, ?, datetime('now'))`, [
|
|
776
|
+
domain,
|
|
777
|
+
JSON.stringify(topPatterns),
|
|
778
|
+
sessionCount,
|
|
779
|
+
durationRow.avg_min ?? 0,
|
|
780
|
+
lastActivity || new Date().toISOString(),
|
|
781
|
+
]);
|
|
766
782
|
count++;
|
|
767
783
|
}
|
|
768
784
|
return count;
|