@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.
Files changed (84) hide show
  1. package/dist/brain/intelligence.d.ts +1 -0
  2. package/dist/brain/intelligence.d.ts.map +1 -1
  3. package/dist/brain/intelligence.js +160 -148
  4. package/dist/brain/intelligence.js.map +1 -1
  5. package/dist/control/identity-manager.d.ts +3 -1
  6. package/dist/control/identity-manager.d.ts.map +1 -1
  7. package/dist/control/identity-manager.js +53 -51
  8. package/dist/control/identity-manager.js.map +1 -1
  9. package/dist/control/intent-router.d.ts +1 -0
  10. package/dist/control/intent-router.d.ts.map +1 -1
  11. package/dist/control/intent-router.js +41 -32
  12. package/dist/control/intent-router.js.map +1 -1
  13. package/dist/curator/curator.d.ts +1 -0
  14. package/dist/curator/curator.d.ts.map +1 -1
  15. package/dist/curator/curator.js +58 -99
  16. package/dist/curator/curator.js.map +1 -1
  17. package/dist/facades/facade-factory.js +1 -1
  18. package/dist/facades/facade-factory.js.map +1 -1
  19. package/dist/governance/governance.d.ts +1 -0
  20. package/dist/governance/governance.d.ts.map +1 -1
  21. package/dist/governance/governance.js +51 -68
  22. package/dist/governance/governance.js.map +1 -1
  23. package/dist/index.d.ts +2 -1
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +1 -0
  26. package/dist/index.js.map +1 -1
  27. package/dist/persistence/index.d.ts +2 -1
  28. package/dist/persistence/index.d.ts.map +1 -1
  29. package/dist/persistence/index.js +1 -0
  30. package/dist/persistence/index.js.map +1 -1
  31. package/dist/persistence/postgres-provider.d.ts +46 -0
  32. package/dist/persistence/postgres-provider.d.ts.map +1 -0
  33. package/dist/persistence/postgres-provider.js +115 -0
  34. package/dist/persistence/postgres-provider.js.map +1 -0
  35. package/dist/persistence/sqlite-provider.d.ts +5 -2
  36. package/dist/persistence/sqlite-provider.d.ts.map +1 -1
  37. package/dist/persistence/sqlite-provider.js +37 -1
  38. package/dist/persistence/sqlite-provider.js.map +1 -1
  39. package/dist/persistence/types.d.ts +23 -1
  40. package/dist/persistence/types.d.ts.map +1 -1
  41. package/dist/project/project-registry.d.ts +4 -4
  42. package/dist/project/project-registry.d.ts.map +1 -1
  43. package/dist/project/project-registry.js +32 -50
  44. package/dist/project/project-registry.js.map +1 -1
  45. package/dist/runtime/admin-extra-ops.d.ts +3 -3
  46. package/dist/runtime/admin-extra-ops.d.ts.map +1 -1
  47. package/dist/runtime/admin-extra-ops.js +33 -3
  48. package/dist/runtime/admin-extra-ops.js.map +1 -1
  49. package/dist/runtime/core-ops.d.ts +4 -4
  50. package/dist/runtime/core-ops.js +4 -4
  51. package/dist/runtime/runtime.js +1 -1
  52. package/dist/runtime/runtime.js.map +1 -1
  53. package/dist/runtime/vault-extra-ops.d.ts +3 -2
  54. package/dist/runtime/vault-extra-ops.d.ts.map +1 -1
  55. package/dist/runtime/vault-extra-ops.js +40 -2
  56. package/dist/runtime/vault-extra-ops.js.map +1 -1
  57. package/dist/vault/vault.d.ts +21 -0
  58. package/dist/vault/vault.d.ts.map +1 -1
  59. package/dist/vault/vault.js +99 -0
  60. package/dist/vault/vault.js.map +1 -1
  61. package/package.json +4 -2
  62. package/src/__tests__/admin-extra-ops.test.ts +2 -2
  63. package/src/__tests__/core-ops.test.ts +8 -2
  64. package/src/__tests__/persistence.test.ts +66 -0
  65. package/src/__tests__/postgres-provider.test.ts +58 -0
  66. package/src/__tests__/vault-extra-ops.test.ts +2 -2
  67. package/src/__tests__/vault.test.ts +184 -0
  68. package/src/brain/intelligence.ts +258 -307
  69. package/src/control/identity-manager.ts +77 -75
  70. package/src/control/intent-router.ts +55 -57
  71. package/src/curator/curator.ts +124 -145
  72. package/src/facades/facade-factory.ts +1 -1
  73. package/src/governance/governance.ts +90 -107
  74. package/src/index.ts +2 -0
  75. package/src/persistence/index.ts +2 -0
  76. package/src/persistence/postgres-provider.ts +157 -0
  77. package/src/persistence/sqlite-provider.ts +55 -2
  78. package/src/persistence/types.ts +31 -1
  79. package/src/project/project-registry.ts +69 -74
  80. package/src/runtime/admin-extra-ops.ts +36 -3
  81. package/src/runtime/core-ops.ts +4 -4
  82. package/src/runtime/runtime.ts +1 -1
  83. package/src/runtime/vault-extra-ops.ts +42 -2
  84. package/src/vault/vault.ts +118 -0
@@ -9,6 +9,7 @@
9
9
  import { randomUUID } from 'node:crypto';
10
10
  import type { Vault } from '../vault/vault.js';
11
11
  import type { Brain } from './brain.js';
12
+ import type { PersistenceProvider } from '../persistence/types.js';
12
13
  import type {
13
14
  PatternStrength,
14
15
  StrengthsQuery,
@@ -40,18 +41,19 @@ const EXTRACTION_HIGH_FEEDBACK_RATIO = 0.8;
40
41
  export class BrainIntelligence {
41
42
  private vault: Vault;
42
43
  private brain: Brain;
44
+ private provider: PersistenceProvider;
43
45
 
44
46
  constructor(vault: Vault, brain: Brain) {
45
47
  this.vault = vault;
46
48
  this.brain = brain;
49
+ this.provider = vault.getProvider();
47
50
  this.initializeTables();
48
51
  }
49
52
 
50
53
  // ─── Table Initialization ─────────────────────────────────────────
51
54
 
52
55
  private initializeTables(): void {
53
- const db = this.vault.getDb();
54
- db.exec(`
56
+ this.provider.execSql(`
55
57
  CREATE TABLE IF NOT EXISTS brain_strengths (
56
58
  pattern TEXT NOT NULL,
57
59
  domain TEXT NOT NULL,
@@ -117,20 +119,19 @@ export class BrainIntelligence {
117
119
  // ─── Session Lifecycle ────────────────────────────────────────────
118
120
 
119
121
  lifecycle(input: SessionLifecycleInput): BrainSession {
120
- const db = this.vault.getDb();
121
-
122
122
  if (input.action === 'start') {
123
123
  const id = input.sessionId ?? randomUUID();
124
- db.prepare(
124
+ this.provider.run(
125
125
  `INSERT INTO brain_sessions (id, domain, context, tools_used, files_modified, plan_id)
126
126
  VALUES (?, ?, ?, ?, ?, ?)`,
127
- ).run(
128
- id,
129
- input.domain ?? null,
130
- input.context ?? null,
131
- JSON.stringify(input.toolsUsed ?? []),
132
- JSON.stringify(input.filesModified ?? []),
133
- input.planId ?? null,
127
+ [
128
+ id,
129
+ input.domain ?? null,
130
+ input.context ?? null,
131
+ JSON.stringify(input.toolsUsed ?? []),
132
+ JSON.stringify(input.filesModified ?? []),
133
+ input.planId ?? null,
134
+ ],
134
135
  );
135
136
  return this.getSession(id)!;
136
137
  }
@@ -160,7 +161,7 @@ export class BrainIntelligence {
160
161
  }
161
162
 
162
163
  values.push(sessionId);
163
- db.prepare(`UPDATE brain_sessions SET ${updates.join(', ')} WHERE id = ?`).run(...values);
164
+ this.provider.run(`UPDATE brain_sessions SET ${updates.join(', ')} WHERE id = ?`, values);
164
165
 
165
166
  // Auto-extract knowledge if session has enough signal
166
167
  this.autoExtractIfReady(this.getSession(sessionId)!);
@@ -191,11 +192,7 @@ export class BrainIntelligence {
191
192
  }
192
193
 
193
194
  getSessionContext(limit = 10): SessionContext {
194
- const db = this.vault.getDb();
195
-
196
- const rows = db
197
- .prepare('SELECT * FROM brain_sessions ORDER BY started_at DESC LIMIT ?')
198
- .all(limit) as Array<{
195
+ const rows = this.provider.all<{
199
196
  id: string;
200
197
  started_at: string;
201
198
  ended_at: string | null;
@@ -206,7 +203,7 @@ export class BrainIntelligence {
206
203
  plan_id: string | null;
207
204
  plan_outcome: string | null;
208
205
  extracted_at: string | null;
209
- }>;
206
+ }>('SELECT * FROM brain_sessions ORDER BY started_at DESC LIMIT ?', [limit]);
210
207
 
211
208
  const sessions = rows.map((r) => this.rowToSession(r));
212
209
 
@@ -234,36 +231,20 @@ export class BrainIntelligence {
234
231
  }
235
232
 
236
233
  archiveSessions(olderThanDays = 30): { archived: number } {
237
- const db = this.vault.getDb();
238
- const result = db
239
- .prepare(
240
- `DELETE FROM brain_sessions
241
- WHERE ended_at IS NOT NULL
242
- AND started_at < datetime('now', '-' || ? || ' days')`,
243
- )
244
- .run(olderThanDays);
234
+ const result = this.provider.run(
235
+ `DELETE FROM brain_sessions
236
+ WHERE ended_at IS NOT NULL
237
+ AND started_at < datetime('now', '-' || ? || ' days')`,
238
+ [olderThanDays],
239
+ );
245
240
  return { archived: result.changes };
246
241
  }
247
242
 
248
243
  // ─── Strength Scoring ─────────────────────────────────────────────
249
244
 
250
245
  computeStrengths(): PatternStrength[] {
251
- const db = this.vault.getDb();
252
-
253
246
  // Gather feedback data grouped by entry_id
254
- const feedbackRows = db
255
- .prepare(
256
- `SELECT entry_id,
257
- COUNT(*) as total,
258
- SUM(CASE WHEN action = 'accepted' THEN 1 ELSE 0 END) as accepted,
259
- SUM(CASE WHEN action = 'dismissed' THEN 1 ELSE 0 END) as dismissed,
260
- SUM(CASE WHEN action = 'modified' THEN 1 ELSE 0 END) as modified,
261
- SUM(CASE WHEN action = 'failed' THEN 1 ELSE 0 END) as failed,
262
- MAX(created_at) as last_used
263
- FROM brain_feedback
264
- GROUP BY entry_id`,
265
- )
266
- .all() as Array<{
247
+ const feedbackRows = this.provider.all<{
267
248
  entry_id: string;
268
249
  total: number;
269
250
  accepted: number;
@@ -271,12 +252,22 @@ export class BrainIntelligence {
271
252
  modified: number;
272
253
  failed: number;
273
254
  last_used: string;
274
- }>;
255
+ }>(
256
+ `SELECT entry_id,
257
+ COUNT(*) as total,
258
+ SUM(CASE WHEN action = 'accepted' THEN 1 ELSE 0 END) as accepted,
259
+ SUM(CASE WHEN action = 'dismissed' THEN 1 ELSE 0 END) as dismissed,
260
+ SUM(CASE WHEN action = 'modified' THEN 1 ELSE 0 END) as modified,
261
+ SUM(CASE WHEN action = 'failed' THEN 1 ELSE 0 END) as failed,
262
+ MAX(created_at) as last_used
263
+ FROM brain_feedback
264
+ GROUP BY entry_id`,
265
+ );
275
266
 
276
267
  // Count unique session domains as spread proxy
277
- const sessionRows = db
278
- .prepare('SELECT DISTINCT domain FROM brain_sessions WHERE domain IS NOT NULL')
279
- .all() as Array<{ domain: string }>;
268
+ const sessionRows = this.provider.all<{ domain: string }>(
269
+ 'SELECT DISTINCT domain FROM brain_sessions WHERE domain IS NOT NULL',
270
+ );
280
271
  const uniqueDomains = new Set(sessionRows.map((r) => r.domain));
281
272
 
282
273
  const now = Date.now();
@@ -326,23 +317,24 @@ export class BrainIntelligence {
326
317
  strengths.push(ps);
327
318
 
328
319
  // Persist
329
- db.prepare(
320
+ this.provider.run(
330
321
  `INSERT OR REPLACE INTO brain_strengths
331
322
  (pattern, domain, strength, usage_score, spread_score, success_score, recency_score,
332
323
  usage_count, unique_contexts, success_rate, last_used, updated_at)
333
324
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, datetime('now'))`,
334
- ).run(
335
- ps.pattern,
336
- ps.domain,
337
- ps.strength,
338
- ps.usageScore,
339
- ps.spreadScore,
340
- ps.successScore,
341
- ps.recencyScore,
342
- ps.usageCount,
343
- ps.uniqueContexts,
344
- ps.successRate,
345
- ps.lastUsed,
325
+ [
326
+ ps.pattern,
327
+ ps.domain,
328
+ ps.strength,
329
+ ps.usageScore,
330
+ ps.spreadScore,
331
+ ps.successScore,
332
+ ps.recencyScore,
333
+ ps.usageCount,
334
+ ps.uniqueContexts,
335
+ ps.successRate,
336
+ ps.lastUsed,
337
+ ],
346
338
  );
347
339
  }
348
340
 
@@ -350,7 +342,6 @@ export class BrainIntelligence {
350
342
  }
351
343
 
352
344
  getStrengths(query?: StrengthsQuery): PatternStrength[] {
353
- const db = this.vault.getDb();
354
345
  const conditions: string[] = [];
355
346
  const values: unknown[] = [];
356
347
 
@@ -367,9 +358,7 @@ export class BrainIntelligence {
367
358
  const limit = query?.limit ?? 50;
368
359
  values.push(limit);
369
360
 
370
- const rows = db
371
- .prepare(`SELECT * FROM brain_strengths ${where} ORDER BY strength DESC LIMIT ?`)
372
- .all(...values) as Array<{
361
+ const rows = this.provider.all<{
373
362
  pattern: string;
374
363
  domain: string;
375
364
  strength: number;
@@ -381,7 +370,7 @@ export class BrainIntelligence {
381
370
  unique_contexts: number;
382
371
  success_rate: number;
383
372
  last_used: string;
384
- }>;
373
+ }>(`SELECT * FROM brain_strengths ${where} ORDER BY strength DESC LIMIT ?`, values);
385
374
 
386
375
  return rows.map((r) => ({
387
376
  pattern: r.pattern,
@@ -426,18 +415,20 @@ export class BrainIntelligence {
426
415
 
427
416
  // Boost patterns with high source-specific acceptance rates
428
417
  if (context.source) {
429
- const db = this.vault.getDb();
430
418
  for (const s of strengths) {
431
- const row = db
432
- .prepare(
433
- `SELECT COUNT(*) as total,
434
- SUM(CASE WHEN action = 'accepted' THEN 1 ELSE 0 END) as accepted,
435
- SUM(CASE WHEN action = 'modified' THEN 1 ELSE 0 END) as modified
436
- FROM brain_feedback
437
- WHERE entry_id = (SELECT id FROM entries WHERE title = ? LIMIT 1)
438
- AND source = ?`,
439
- )
440
- .get(s.pattern, context.source) as {
419
+ const row = this.provider.get<{
420
+ total: number;
421
+ accepted: number;
422
+ modified: number;
423
+ }>(
424
+ `SELECT COUNT(*) as total,
425
+ SUM(CASE WHEN action = 'accepted' THEN 1 ELSE 0 END) as accepted,
426
+ SUM(CASE WHEN action = 'modified' THEN 1 ELSE 0 END) as modified
427
+ FROM brain_feedback
428
+ WHERE entry_id = (SELECT id FROM entries WHERE title = ? LIMIT 1)
429
+ AND source = ?`,
430
+ [s.pattern, context.source],
431
+ ) as {
441
432
  total: number;
442
433
  accepted: number;
443
434
  modified: number;
@@ -463,7 +454,6 @@ export class BrainIntelligence {
463
454
 
464
455
  const proposals: KnowledgeProposal[] = [];
465
456
  const rulesApplied: string[] = [];
466
- const db = this.vault.getDb();
467
457
 
468
458
  // Rule 1: Repeated tool usage (3+ same tool)
469
459
  const toolCounts = new Map<string, number>();
@@ -474,7 +464,7 @@ export class BrainIntelligence {
474
464
  if (count >= EXTRACTION_TOOL_THRESHOLD) {
475
465
  rulesApplied.push('repeated_tool_usage');
476
466
  proposals.push(
477
- this.createProposal(db, sessionId, 'repeated_tool_usage', 'pattern', {
467
+ this.createProposal(sessionId, 'repeated_tool_usage', 'pattern', {
478
468
  title: `Frequent use of ${tool}`,
479
469
  description: `Tool ${tool} was used ${count} times in session. Consider automating or abstracting this workflow.`,
480
470
  confidence: Math.min(0.9, 0.5 + count * 0.1),
@@ -487,7 +477,7 @@ export class BrainIntelligence {
487
477
  if (session.filesModified.length >= EXTRACTION_FILE_THRESHOLD) {
488
478
  rulesApplied.push('multi_file_edit');
489
479
  proposals.push(
490
- this.createProposal(db, sessionId, 'multi_file_edit', 'pattern', {
480
+ this.createProposal(sessionId, 'multi_file_edit', 'pattern', {
491
481
  title: `Multi-file change pattern (${session.filesModified.length} files)`,
492
482
  description: `Session modified ${session.filesModified.length} files: ${session.filesModified.slice(0, 5).join(', ')}${session.filesModified.length > 5 ? '...' : ''}. This may indicate an architectural pattern.`,
493
483
  confidence: Math.min(0.8, 0.4 + session.filesModified.length * 0.05),
@@ -503,7 +493,7 @@ export class BrainIntelligence {
503
493
  if (durationMin > EXTRACTION_LONG_SESSION_MINUTES) {
504
494
  rulesApplied.push('long_session');
505
495
  proposals.push(
506
- this.createProposal(db, sessionId, 'long_session', 'anti-pattern', {
496
+ this.createProposal(sessionId, 'long_session', 'anti-pattern', {
507
497
  title: `Long session (${Math.round(durationMin)} minutes)`,
508
498
  description: `Session lasted ${Math.round(durationMin)} minutes. Consider breaking complex tasks into smaller steps or improving automation.`,
509
499
  confidence: 0.5,
@@ -516,7 +506,7 @@ export class BrainIntelligence {
516
506
  if (session.planId && session.planOutcome === 'completed') {
517
507
  rulesApplied.push('plan_completed');
518
508
  proposals.push(
519
- this.createProposal(db, sessionId, 'plan_completed', 'workflow', {
509
+ this.createProposal(sessionId, 'plan_completed', 'workflow', {
520
510
  title: `Successful plan: ${session.planId}`,
521
511
  description: `Plan ${session.planId} completed successfully. This workflow can be reused for similar tasks.`,
522
512
  confidence: 0.8,
@@ -528,7 +518,7 @@ export class BrainIntelligence {
528
518
  if (session.planId && session.planOutcome === 'abandoned') {
529
519
  rulesApplied.push('plan_abandoned');
530
520
  proposals.push(
531
- this.createProposal(db, sessionId, 'plan_abandoned', 'anti-pattern', {
521
+ this.createProposal(sessionId, 'plan_abandoned', 'anti-pattern', {
532
522
  title: `Abandoned plan: ${session.planId}`,
533
523
  description: `Plan ${session.planId} was abandoned. Review what went wrong to avoid repeating in future sessions.`,
534
524
  confidence: 0.7,
@@ -537,15 +527,18 @@ export class BrainIntelligence {
537
527
  }
538
528
 
539
529
  // Rule 6: High feedback ratio (>80% accept or dismiss)
540
- const feedbackRow = db
541
- .prepare(
542
- `SELECT COUNT(*) as total,
543
- SUM(CASE WHEN action = 'accepted' THEN 1 ELSE 0 END) as accepted,
544
- SUM(CASE WHEN action = 'dismissed' THEN 1 ELSE 0 END) as dismissed
545
- FROM brain_feedback
546
- WHERE created_at >= ? AND created_at <= ?`,
547
- )
548
- .get(session.startedAt, session.endedAt ?? new Date().toISOString()) as {
530
+ const feedbackRow = this.provider.get<{
531
+ total: number;
532
+ accepted: number;
533
+ dismissed: number;
534
+ }>(
535
+ `SELECT COUNT(*) as total,
536
+ SUM(CASE WHEN action = 'accepted' THEN 1 ELSE 0 END) as accepted,
537
+ SUM(CASE WHEN action = 'dismissed' THEN 1 ELSE 0 END) as dismissed
538
+ FROM brain_feedback
539
+ WHERE created_at >= ? AND created_at <= ?`,
540
+ [session.startedAt, session.endedAt ?? new Date().toISOString()],
541
+ ) as {
549
542
  total: number;
550
543
  accepted: number;
551
544
  dismissed: number;
@@ -558,7 +551,7 @@ export class BrainIntelligence {
558
551
  if (acceptRate >= EXTRACTION_HIGH_FEEDBACK_RATIO) {
559
552
  rulesApplied.push('high_accept_ratio');
560
553
  proposals.push(
561
- this.createProposal(db, sessionId, 'high_accept_ratio', 'pattern', {
554
+ this.createProposal(sessionId, 'high_accept_ratio', 'pattern', {
562
555
  title: `High search acceptance rate (${Math.round(acceptRate * 100)}%)`,
563
556
  description: `Search results were accepted ${Math.round(acceptRate * 100)}% of the time. Brain scoring is well-calibrated for this type of work.`,
564
557
  confidence: 0.7,
@@ -567,7 +560,7 @@ export class BrainIntelligence {
567
560
  } else if (dismissRate >= EXTRACTION_HIGH_FEEDBACK_RATIO) {
568
561
  rulesApplied.push('high_dismiss_ratio');
569
562
  proposals.push(
570
- this.createProposal(db, sessionId, 'high_dismiss_ratio', 'anti-pattern', {
563
+ this.createProposal(sessionId, 'high_dismiss_ratio', 'anti-pattern', {
571
564
  title: `High search dismissal rate (${Math.round(dismissRate * 100)}%)`,
572
565
  description: `Search results were dismissed ${Math.round(dismissRate * 100)}% of the time. Brain scoring may need recalibration for this domain.`,
573
566
  confidence: 0.7,
@@ -577,9 +570,9 @@ export class BrainIntelligence {
577
570
  }
578
571
 
579
572
  // Mark session as extracted
580
- db.prepare("UPDATE brain_sessions SET extracted_at = datetime('now') WHERE id = ?").run(
573
+ this.provider.run("UPDATE brain_sessions SET extracted_at = datetime('now') WHERE id = ?", [
581
574
  sessionId,
582
- );
575
+ ]);
583
576
 
584
577
  return {
585
578
  sessionId,
@@ -591,28 +584,26 @@ export class BrainIntelligence {
591
584
  resetExtracted(options?: { sessionId?: string; since?: string; all?: boolean }): {
592
585
  reset: number;
593
586
  } {
594
- const db = this.vault.getDb();
595
-
596
587
  if (options?.sessionId) {
597
- const info = db
598
- .prepare(
599
- 'UPDATE brain_sessions SET extracted_at = NULL WHERE id = ? AND extracted_at IS NOT NULL',
600
- )
601
- .run(options.sessionId);
588
+ const info = this.provider.run(
589
+ 'UPDATE brain_sessions SET extracted_at = NULL WHERE id = ? AND extracted_at IS NOT NULL',
590
+ [options.sessionId],
591
+ );
602
592
  return { reset: info.changes };
603
593
  }
604
594
 
605
595
  if (options?.since) {
606
- const info = db
607
- .prepare('UPDATE brain_sessions SET extracted_at = NULL WHERE extracted_at >= ?')
608
- .run(options.since);
596
+ const info = this.provider.run(
597
+ 'UPDATE brain_sessions SET extracted_at = NULL WHERE extracted_at >= ?',
598
+ [options.since],
599
+ );
609
600
  return { reset: info.changes };
610
601
  }
611
602
 
612
603
  if (options?.all) {
613
- const info = db
614
- .prepare('UPDATE brain_sessions SET extracted_at = NULL WHERE extracted_at IS NOT NULL')
615
- .run();
604
+ const info = this.provider.run(
605
+ 'UPDATE brain_sessions SET extracted_at = NULL WHERE extracted_at IS NOT NULL',
606
+ );
616
607
  return { reset: info.changes };
617
608
  }
618
609
 
@@ -624,7 +615,6 @@ export class BrainIntelligence {
624
615
  promoted?: boolean;
625
616
  limit?: number;
626
617
  }): KnowledgeProposal[] {
627
- const db = this.vault.getDb();
628
618
  const conditions: string[] = [];
629
619
  const values: unknown[] = [];
630
620
 
@@ -641,9 +631,7 @@ export class BrainIntelligence {
641
631
  const limit = options?.limit ?? 50;
642
632
  values.push(limit);
643
633
 
644
- const rows = db
645
- .prepare(`SELECT * FROM brain_proposals ${where} ORDER BY created_at DESC LIMIT ?`)
646
- .all(...values) as Array<{
634
+ const rows = this.provider.all<{
647
635
  id: string;
648
636
  session_id: string;
649
637
  rule: string;
@@ -653,7 +641,7 @@ export class BrainIntelligence {
653
641
  confidence: number;
654
642
  promoted: number;
655
643
  created_at: string;
656
- }>;
644
+ }>(`SELECT * FROM brain_proposals ${where} ORDER BY created_at DESC LIMIT ?`, values);
657
645
 
658
646
  return rows.map((r) => this.rowToProposal(r));
659
647
  }
@@ -683,26 +671,23 @@ export class BrainIntelligence {
683
671
  failed: string[];
684
672
  gated: Array<{ id: string; action: string; reason?: string }>;
685
673
  } {
686
- const db = this.vault.getDb();
687
674
  let promoted = 0;
688
675
  const failed: string[] = [];
689
676
  const gated: Array<{ id: string; action: string; reason?: string }> = [];
690
677
  const pp = projectPath ?? '.';
691
678
 
692
679
  for (const id of proposalIds) {
693
- const row = db.prepare('SELECT * FROM brain_proposals WHERE id = ?').get(id) as
694
- | {
695
- id: string;
696
- session_id: string;
697
- rule: string;
698
- type: string;
699
- title: string;
700
- description: string;
701
- confidence: number;
702
- promoted: number;
703
- created_at: string;
704
- }
705
- | undefined;
680
+ const row = this.provider.get<{
681
+ id: string;
682
+ session_id: string;
683
+ rule: string;
684
+ type: string;
685
+ title: string;
686
+ description: string;
687
+ confidence: number;
688
+ promoted: number;
689
+ created_at: string;
690
+ }>('SELECT * FROM brain_proposals WHERE id = ?', [id]);
706
691
 
707
692
  if (!row) {
708
693
  failed.push(id);
@@ -761,7 +746,7 @@ export class BrainIntelligence {
761
746
  tags: ['auto-extracted', row.rule],
762
747
  });
763
748
 
764
- db.prepare('UPDATE brain_proposals SET promoted = 1 WHERE id = ?').run(id);
749
+ this.provider.run('UPDATE brain_proposals SET promoted = 1 WHERE id = ?', [id]);
765
750
  promoted++;
766
751
  }
767
752
 
@@ -788,16 +773,13 @@ export class BrainIntelligence {
788
773
  }
789
774
 
790
775
  getGlobalPatterns(limit = 20): GlobalPattern[] {
791
- const db = this.vault.getDb();
792
- const rows = db
793
- .prepare('SELECT * FROM brain_global_registry ORDER BY total_strength DESC LIMIT ?')
794
- .all(limit) as Array<{
776
+ const rows = this.provider.all<{
795
777
  pattern: string;
796
778
  domains: string;
797
779
  total_strength: number;
798
780
  avg_strength: number;
799
781
  domain_count: number;
800
- }>;
782
+ }>('SELECT * FROM brain_global_registry ORDER BY total_strength DESC LIMIT ?', [limit]);
801
783
 
802
784
  return rows.map((r) => ({
803
785
  pattern: r.pattern,
@@ -809,16 +791,13 @@ export class BrainIntelligence {
809
791
  }
810
792
 
811
793
  getDomainProfile(domain: string): DomainProfile | null {
812
- const db = this.vault.getDb();
813
- const row = db.prepare('SELECT * FROM brain_domain_profiles WHERE domain = ?').get(domain) as
814
- | {
815
- domain: string;
816
- top_patterns: string;
817
- session_count: number;
818
- avg_session_duration: number;
819
- last_activity: string;
820
- }
821
- | undefined;
794
+ const row = this.provider.get<{
795
+ domain: string;
796
+ top_patterns: string;
797
+ session_count: number;
798
+ avg_session_duration: number;
799
+ last_activity: string;
800
+ }>('SELECT * FROM brain_domain_profiles WHERE domain = ?', [domain]);
822
801
 
823
802
  if (!row) return null;
824
803
 
@@ -834,32 +813,27 @@ export class BrainIntelligence {
834
813
  // ─── Data Management ──────────────────────────────────────────────
835
814
 
836
815
  getStats(): BrainIntelligenceStats {
837
- const db = this.vault.getDb();
838
-
839
- const strengths = (
840
- db.prepare('SELECT COUNT(*) as c FROM brain_strengths').get() as { c: number }
841
- ).c;
842
- const sessions = (db.prepare('SELECT COUNT(*) as c FROM brain_sessions').get() as { c: number })
843
- .c;
844
- const activeSessions = (
845
- db.prepare('SELECT COUNT(*) as c FROM brain_sessions WHERE ended_at IS NULL').get() as {
846
- c: number;
847
- }
848
- ).c;
849
- const proposals = (
850
- db.prepare('SELECT COUNT(*) as c FROM brain_proposals').get() as { c: number }
851
- ).c;
852
- const promotedProposals = (
853
- db.prepare('SELECT COUNT(*) as c FROM brain_proposals WHERE promoted = 1').get() as {
854
- c: number;
855
- }
856
- ).c;
857
- const globalPatterns = (
858
- db.prepare('SELECT COUNT(*) as c FROM brain_global_registry').get() as { c: number }
859
- ).c;
860
- const domainProfiles = (
861
- db.prepare('SELECT COUNT(*) as c FROM brain_domain_profiles').get() as { c: number }
862
- ).c;
816
+ const strengths = this.provider.get<{ c: number }>(
817
+ 'SELECT COUNT(*) as c FROM brain_strengths',
818
+ )!.c;
819
+ const sessions = this.provider.get<{ c: number }>(
820
+ 'SELECT COUNT(*) as c FROM brain_sessions',
821
+ )!.c;
822
+ const activeSessions = this.provider.get<{ c: number }>(
823
+ 'SELECT COUNT(*) as c FROM brain_sessions WHERE ended_at IS NULL',
824
+ )!.c;
825
+ const proposals = this.provider.get<{ c: number }>(
826
+ 'SELECT COUNT(*) as c FROM brain_proposals',
827
+ )!.c;
828
+ const promotedProposals = this.provider.get<{ c: number }>(
829
+ 'SELECT COUNT(*) as c FROM brain_proposals WHERE promoted = 1',
830
+ )!.c;
831
+ const globalPatterns = this.provider.get<{ c: number }>(
832
+ 'SELECT COUNT(*) as c FROM brain_global_registry',
833
+ )!.c;
834
+ const domainProfiles = this.provider.get<{ c: number }>(
835
+ 'SELECT COUNT(*) as c FROM brain_domain_profiles',
836
+ )!.c;
863
837
 
864
838
  return {
865
839
  strengths,
@@ -873,13 +847,9 @@ export class BrainIntelligence {
873
847
  }
874
848
 
875
849
  exportData(): BrainExportData {
876
- const db = this.vault.getDb();
877
-
878
850
  const strengths = this.getStrengths({ limit: 10000 });
879
851
 
880
- const sessionRows = db
881
- .prepare('SELECT * FROM brain_sessions ORDER BY started_at DESC')
882
- .all() as Array<{
852
+ const sessionRows = this.provider.all<{
883
853
  id: string;
884
854
  started_at: string;
885
855
  ended_at: string | null;
@@ -890,19 +860,19 @@ export class BrainIntelligence {
890
860
  plan_id: string | null;
891
861
  plan_outcome: string | null;
892
862
  extracted_at: string | null;
893
- }>;
863
+ }>('SELECT * FROM brain_sessions ORDER BY started_at DESC');
894
864
  const sessions = sessionRows.map((r) => this.rowToSession(r));
895
865
 
896
866
  const proposals = this.getProposals({ limit: 10000 });
897
867
  const globalPatterns = this.getGlobalPatterns(10000);
898
868
 
899
- const profileRows = db.prepare('SELECT * FROM brain_domain_profiles').all() as Array<{
869
+ const profileRows = this.provider.all<{
900
870
  domain: string;
901
871
  top_patterns: string;
902
872
  session_count: number;
903
873
  avg_session_duration: number;
904
874
  last_activity: string;
905
- }>;
875
+ }>('SELECT * FROM brain_domain_profiles');
906
876
  const domainProfiles = profileRows.map((r) => ({
907
877
  domain: r.domain,
908
878
  topPatterns: JSON.parse(r.top_patterns) as Array<{ pattern: string; strength: number }>,
@@ -922,136 +892,125 @@ export class BrainIntelligence {
922
892
  }
923
893
 
924
894
  importData(data: BrainExportData): BrainImportResult {
925
- const db = this.vault.getDb();
926
895
  const result: BrainImportResult = {
927
896
  imported: { strengths: 0, sessions: 0, proposals: 0, globalPatterns: 0, domainProfiles: 0 },
928
897
  };
929
898
 
930
- const tx = db.transaction(() => {
899
+ this.provider.transaction(() => {
931
900
  // Import strengths
932
- const insertStrength = db.prepare(
933
- `INSERT OR REPLACE INTO brain_strengths
934
- (pattern, domain, strength, usage_score, spread_score, success_score, recency_score,
935
- usage_count, unique_contexts, success_rate, last_used, updated_at)
936
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, datetime('now'))`,
937
- );
938
901
  for (const s of data.strengths) {
939
- insertStrength.run(
940
- s.pattern,
941
- s.domain,
942
- s.strength,
943
- s.usageScore,
944
- s.spreadScore,
945
- s.successScore,
946
- s.recencyScore,
947
- s.usageCount,
948
- s.uniqueContexts,
949
- s.successRate,
950
- s.lastUsed,
902
+ this.provider.run(
903
+ `INSERT OR REPLACE INTO brain_strengths
904
+ (pattern, domain, strength, usage_score, spread_score, success_score, recency_score,
905
+ usage_count, unique_contexts, success_rate, last_used, updated_at)
906
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, datetime('now'))`,
907
+ [
908
+ s.pattern,
909
+ s.domain,
910
+ s.strength,
911
+ s.usageScore,
912
+ s.spreadScore,
913
+ s.successScore,
914
+ s.recencyScore,
915
+ s.usageCount,
916
+ s.uniqueContexts,
917
+ s.successRate,
918
+ s.lastUsed,
919
+ ],
951
920
  );
952
921
  result.imported.strengths++;
953
922
  }
954
923
 
955
924
  // Import sessions
956
- const insertSession = db.prepare(
957
- `INSERT OR IGNORE INTO brain_sessions
958
- (id, started_at, ended_at, domain, context, tools_used, files_modified, plan_id, plan_outcome, extracted_at)
959
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
960
- );
961
925
  for (const s of data.sessions) {
962
- const changes = insertSession.run(
963
- s.id,
964
- s.startedAt,
965
- s.endedAt,
966
- s.domain,
967
- s.context,
968
- JSON.stringify(s.toolsUsed),
969
- JSON.stringify(s.filesModified),
970
- s.planId,
971
- s.planOutcome,
972
- s.extractedAt ?? null,
926
+ const changes = this.provider.run(
927
+ `INSERT OR IGNORE INTO brain_sessions
928
+ (id, started_at, ended_at, domain, context, tools_used, files_modified, plan_id, plan_outcome, extracted_at)
929
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
930
+ [
931
+ s.id,
932
+ s.startedAt,
933
+ s.endedAt,
934
+ s.domain,
935
+ s.context,
936
+ JSON.stringify(s.toolsUsed),
937
+ JSON.stringify(s.filesModified),
938
+ s.planId,
939
+ s.planOutcome,
940
+ s.extractedAt ?? null,
941
+ ],
973
942
  );
974
943
  if (changes.changes > 0) result.imported.sessions++;
975
944
  }
976
945
 
977
946
  // Import proposals
978
- const insertProposal = db.prepare(
979
- `INSERT OR IGNORE INTO brain_proposals
980
- (id, session_id, rule, type, title, description, confidence, promoted, created_at)
981
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
982
- );
983
947
  for (const p of data.proposals) {
984
- const changes = insertProposal.run(
985
- p.id,
986
- p.sessionId,
987
- p.rule,
988
- p.type,
989
- p.title,
990
- p.description,
991
- p.confidence,
992
- p.promoted ? 1 : 0,
993
- p.createdAt,
948
+ const changes = this.provider.run(
949
+ `INSERT OR IGNORE INTO brain_proposals
950
+ (id, session_id, rule, type, title, description, confidence, promoted, created_at)
951
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
952
+ [
953
+ p.id,
954
+ p.sessionId,
955
+ p.rule,
956
+ p.type,
957
+ p.title,
958
+ p.description,
959
+ p.confidence,
960
+ p.promoted ? 1 : 0,
961
+ p.createdAt,
962
+ ],
994
963
  );
995
964
  if (changes.changes > 0) result.imported.proposals++;
996
965
  }
997
966
 
998
967
  // Import global patterns
999
- const insertGlobal = db.prepare(
1000
- `INSERT OR REPLACE INTO brain_global_registry
1001
- (pattern, domains, total_strength, avg_strength, domain_count, updated_at)
1002
- VALUES (?, ?, ?, ?, ?, datetime('now'))`,
1003
- );
1004
968
  for (const g of data.globalPatterns) {
1005
- insertGlobal.run(
1006
- g.pattern,
1007
- JSON.stringify(g.domains),
1008
- g.totalStrength,
1009
- g.avgStrength,
1010
- g.domainCount,
969
+ this.provider.run(
970
+ `INSERT OR REPLACE INTO brain_global_registry
971
+ (pattern, domains, total_strength, avg_strength, domain_count, updated_at)
972
+ VALUES (?, ?, ?, ?, ?, datetime('now'))`,
973
+ [g.pattern, JSON.stringify(g.domains), g.totalStrength, g.avgStrength, g.domainCount],
1011
974
  );
1012
975
  result.imported.globalPatterns++;
1013
976
  }
1014
977
 
1015
978
  // Import domain profiles
1016
- const insertProfile = db.prepare(
1017
- `INSERT OR REPLACE INTO brain_domain_profiles
1018
- (domain, top_patterns, session_count, avg_session_duration, last_activity, updated_at)
1019
- VALUES (?, ?, ?, ?, ?, datetime('now'))`,
1020
- );
1021
979
  for (const d of data.domainProfiles) {
1022
- insertProfile.run(
1023
- d.domain,
1024
- JSON.stringify(d.topPatterns),
1025
- d.sessionCount,
1026
- d.avgSessionDuration,
1027
- d.lastActivity,
980
+ this.provider.run(
981
+ `INSERT OR REPLACE INTO brain_domain_profiles
982
+ (domain, top_patterns, session_count, avg_session_duration, last_activity, updated_at)
983
+ VALUES (?, ?, ?, ?, ?, datetime('now'))`,
984
+ [
985
+ d.domain,
986
+ JSON.stringify(d.topPatterns),
987
+ d.sessionCount,
988
+ d.avgSessionDuration,
989
+ d.lastActivity,
990
+ ],
1028
991
  );
1029
992
  result.imported.domainProfiles++;
1030
993
  }
1031
994
  });
1032
995
 
1033
- tx();
1034
996
  return result;
1035
997
  }
1036
998
 
1037
999
  // ─── Private Helpers ──────────────────────────────────────────────
1038
1000
 
1039
1001
  private getSession(id: string): BrainSession | null {
1040
- const db = this.vault.getDb();
1041
- const row = db.prepare('SELECT * FROM brain_sessions WHERE id = ?').get(id) as
1042
- | {
1043
- id: string;
1044
- started_at: string;
1045
- ended_at: string | null;
1046
- domain: string | null;
1047
- context: string | null;
1048
- tools_used: string;
1049
- files_modified: string;
1050
- plan_id: string | null;
1051
- plan_outcome: string | null;
1052
- extracted_at: string | null;
1053
- }
1054
- | undefined;
1002
+ const row = this.provider.get<{
1003
+ id: string;
1004
+ started_at: string;
1005
+ ended_at: string | null;
1006
+ domain: string | null;
1007
+ context: string | null;
1008
+ tools_used: string;
1009
+ files_modified: string;
1010
+ plan_id: string | null;
1011
+ plan_outcome: string | null;
1012
+ extracted_at: string | null;
1013
+ }>('SELECT * FROM brain_sessions WHERE id = ?', [id]);
1055
1014
 
1056
1015
  if (!row) return null;
1057
1016
  return this.rowToSession(row);
@@ -1108,17 +1067,17 @@ export class BrainIntelligence {
1108
1067
  }
1109
1068
 
1110
1069
  private createProposal(
1111
- db: ReturnType<Vault['getDb']>,
1112
1070
  sessionId: string,
1113
1071
  rule: string,
1114
1072
  type: 'pattern' | 'anti-pattern' | 'workflow',
1115
1073
  data: { title: string; description: string; confidence: number },
1116
1074
  ): KnowledgeProposal {
1117
1075
  const id = randomUUID();
1118
- db.prepare(
1076
+ this.provider.run(
1119
1077
  `INSERT INTO brain_proposals (id, session_id, rule, type, title, description, confidence)
1120
1078
  VALUES (?, ?, ?, ?, ?, ?, ?)`,
1121
- ).run(id, sessionId, rule, type, data.title, data.description, data.confidence);
1079
+ [id, sessionId, rule, type, data.title, data.description, data.confidence],
1080
+ );
1122
1081
 
1123
1082
  return {
1124
1083
  id,
@@ -1134,8 +1093,6 @@ export class BrainIntelligence {
1134
1093
  }
1135
1094
 
1136
1095
  private buildGlobalRegistry(strengths: PatternStrength[]): number {
1137
- const db = this.vault.getDb();
1138
-
1139
1096
  // Group strengths by pattern
1140
1097
  const patternMap = new Map<string, PatternStrength[]>();
1141
1098
  for (const s of strengths) {
@@ -1144,13 +1101,7 @@ export class BrainIntelligence {
1144
1101
  patternMap.set(s.pattern, list);
1145
1102
  }
1146
1103
 
1147
- db.prepare('DELETE FROM brain_global_registry').run();
1148
-
1149
- const insert = db.prepare(
1150
- `INSERT INTO brain_global_registry
1151
- (pattern, domains, total_strength, avg_strength, domain_count, updated_at)
1152
- VALUES (?, ?, ?, ?, ?, datetime('now'))`,
1153
- );
1104
+ this.provider.run('DELETE FROM brain_global_registry');
1154
1105
 
1155
1106
  let count = 0;
1156
1107
  for (const [pattern, entries] of patternMap) {
@@ -1158,7 +1109,12 @@ export class BrainIntelligence {
1158
1109
  const totalStrength = entries.reduce((sum, e) => sum + e.strength, 0);
1159
1110
  const avgStrength = totalStrength / entries.length;
1160
1111
 
1161
- insert.run(pattern, JSON.stringify(domains), totalStrength, avgStrength, domains.length);
1112
+ this.provider.run(
1113
+ `INSERT INTO brain_global_registry
1114
+ (pattern, domains, total_strength, avg_strength, domain_count, updated_at)
1115
+ VALUES (?, ?, ?, ?, ?, datetime('now'))`,
1116
+ [pattern, JSON.stringify(domains), totalStrength, avgStrength, domains.length],
1117
+ );
1162
1118
  count++;
1163
1119
  }
1164
1120
 
@@ -1166,8 +1122,6 @@ export class BrainIntelligence {
1166
1122
  }
1167
1123
 
1168
1124
  private buildDomainProfiles(strengths: PatternStrength[]): number {
1169
- const db = this.vault.getDb();
1170
-
1171
1125
  // Group strengths by domain
1172
1126
  const domainMap = new Map<string, PatternStrength[]>();
1173
1127
  for (const s of strengths) {
@@ -1176,13 +1130,7 @@ export class BrainIntelligence {
1176
1130
  domainMap.set(s.domain, list);
1177
1131
  }
1178
1132
 
1179
- db.prepare('DELETE FROM brain_domain_profiles').run();
1180
-
1181
- const insert = db.prepare(
1182
- `INSERT INTO brain_domain_profiles
1183
- (domain, top_patterns, session_count, avg_session_duration, last_activity, updated_at)
1184
- VALUES (?, ?, ?, ?, ?, datetime('now'))`,
1185
- );
1133
+ this.provider.run('DELETE FROM brain_domain_profiles');
1186
1134
 
1187
1135
  let count = 0;
1188
1136
  for (const [domain, entries] of domainMap) {
@@ -1193,34 +1141,37 @@ export class BrainIntelligence {
1193
1141
  }));
1194
1142
 
1195
1143
  // Count sessions for this domain
1196
- const sessionCount = (
1197
- db.prepare('SELECT COUNT(*) as c FROM brain_sessions WHERE domain = ?').get(domain) as {
1198
- c: number;
1199
- }
1200
- ).c;
1144
+ const sessionCount = this.provider.get<{ c: number }>(
1145
+ 'SELECT COUNT(*) as c FROM brain_sessions WHERE domain = ?',
1146
+ [domain],
1147
+ )!.c;
1201
1148
 
1202
1149
  // Average session duration (in minutes)
1203
- const durationRow = db
1204
- .prepare(
1205
- `SELECT AVG(
1206
- (julianday(ended_at) - julianday(started_at)) * 1440
1207
- ) as avg_min
1208
- FROM brain_sessions
1209
- WHERE domain = ? AND ended_at IS NOT NULL`,
1210
- )
1211
- .get(domain) as { avg_min: number | null };
1150
+ const durationRow = this.provider.get<{ avg_min: number | null }>(
1151
+ `SELECT AVG(
1152
+ (julianday(ended_at) - julianday(started_at)) * 1440
1153
+ ) as avg_min
1154
+ FROM brain_sessions
1155
+ WHERE domain = ? AND ended_at IS NOT NULL`,
1156
+ [domain],
1157
+ )!;
1212
1158
 
1213
1159
  const lastActivity = entries.reduce(
1214
1160
  (latest, e) => (e.lastUsed > latest ? e.lastUsed : latest),
1215
1161
  '',
1216
1162
  );
1217
1163
 
1218
- insert.run(
1219
- domain,
1220
- JSON.stringify(topPatterns),
1221
- sessionCount,
1222
- durationRow.avg_min ?? 0,
1223
- lastActivity || new Date().toISOString(),
1164
+ this.provider.run(
1165
+ `INSERT INTO brain_domain_profiles
1166
+ (domain, top_patterns, session_count, avg_session_duration, last_activity, updated_at)
1167
+ VALUES (?, ?, ?, ?, ?, datetime('now'))`,
1168
+ [
1169
+ domain,
1170
+ JSON.stringify(topPatterns),
1171
+ sessionCount,
1172
+ durationRow.avg_min ?? 0,
1173
+ lastActivity || new Date().toISOString(),
1174
+ ],
1224
1175
  );
1225
1176
  count++;
1226
1177
  }