claude-brain 0.17.13 → 0.22.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/VERSION +1 -1
- package/package.json +3 -1
- package/scripts/postinstall.mjs +80 -104
- package/src/cli/auto-setup.ts +1 -9
- package/src/cli/bin.ts +23 -2
- package/src/cli/commands/export.ts +130 -0
- package/src/cli/commands/reindex.ts +107 -0
- package/src/cli/commands/serve.ts +54 -0
- package/src/cli/commands/status.ts +158 -0
- package/src/code-intelligence/indexer.ts +315 -0
- package/src/code-intelligence/linker.ts +178 -0
- package/src/code-intelligence/parser.ts +484 -0
- package/src/code-intelligence/query.ts +291 -0
- package/src/code-intelligence/schema.ts +83 -0
- package/src/code-intelligence/types.ts +95 -0
- package/src/config/defaults.ts +3 -3
- package/src/config/loader.ts +6 -0
- package/src/config/schema.ts +28 -2
- package/src/health/index.ts +5 -2
- package/src/hooks/brain-hook.ts +4 -1
- package/src/hooks/context-hook.ts +69 -10
- package/src/hooks/installer.ts +4 -7
- package/src/intelligence/cross-project/index.ts +1 -7
- package/src/intelligence/prediction/index.ts +1 -7
- package/src/intelligence/reasoning/index.ts +1 -7
- package/src/memory/compression.ts +105 -0
- package/src/memory/fts5-search.ts +456 -0
- package/src/memory/index.ts +342 -38
- package/src/memory/migrations/add-fts5.ts +98 -0
- package/src/memory/pruning.ts +60 -0
- package/src/routing/intent-classifier.ts +58 -1
- package/src/routing/response-filter.ts +128 -0
- package/src/routing/router.ts +457 -54
- package/src/server/http-api.ts +319 -1
- package/src/server/providers/resources.ts +1 -42
- package/src/server/services.ts +113 -12
- package/src/server/web-viewer.ts +1115 -0
- package/src/setup/index.ts +12 -22
- package/src/tools/schemas.ts +1 -1
- package/src/intelligence/cross-project/affinity.ts +0 -159
- package/src/intelligence/cross-project/transfer.ts +0 -201
- package/src/intelligence/prediction/context-anticipator.ts +0 -198
- package/src/intelligence/prediction/decision-predictor.ts +0 -184
- package/src/intelligence/reasoning/counterfactual.ts +0 -248
- package/src/intelligence/reasoning/synthesizer.ts +0 -167
- package/src/setup/wizard.ts +0 -459
package/src/memory/index.ts
CHANGED
|
@@ -13,6 +13,8 @@ import { SemanticSearch } from './search'
|
|
|
13
13
|
import { MemoryContextBuilder } from './context-builder'
|
|
14
14
|
import type { MemorySystemStats } from './types'
|
|
15
15
|
import { ChromaManager, DEFAULT_CHROMA_CONFIG, getChromaConfigFromEnv, ChromaMigration, type MigrationOptions } from './chroma'
|
|
16
|
+
import { FTS5Search } from './fts5-search'
|
|
17
|
+
import { addFTS5Tables } from './migrations/add-fts5'
|
|
16
18
|
|
|
17
19
|
// Re-export all types and classes for external use
|
|
18
20
|
export * from './types'
|
|
@@ -38,6 +40,10 @@ export { PatternRecognizer, type Pattern } from './patterns'
|
|
|
38
40
|
export { LearningSystem, type Correction, type Preference, type LearningInsights } from './learning'
|
|
39
41
|
export { KnowledgeExtractor, type ExtractedKnowledge, type ExtractionResult } from './knowledge-extractor'
|
|
40
42
|
|
|
43
|
+
// Phase 26: FTS5 Search
|
|
44
|
+
export { FTS5Search } from './fts5-search'
|
|
45
|
+
export type { ObservationCategory, NewObservation, ObservationResult, ScoredResult } from './fts5-search'
|
|
46
|
+
|
|
41
47
|
/**
|
|
42
48
|
* Unified memory system manager
|
|
43
49
|
* Combines database, embeddings, store, search, and context building
|
|
@@ -48,6 +54,9 @@ export class MemoryManager {
|
|
|
48
54
|
readonly contextBuilder: MemoryContextBuilder
|
|
49
55
|
readonly chroma: ChromaManager
|
|
50
56
|
|
|
57
|
+
// Phase 26: FTS5 search (always available, no external deps)
|
|
58
|
+
private _fts5: FTS5Search | null = null
|
|
59
|
+
|
|
51
60
|
// Store and search are initialized after database is ready
|
|
52
61
|
private _store: MemoryStore | null = null
|
|
53
62
|
private _search: SemanticSearch | null = null
|
|
@@ -98,6 +107,15 @@ export class MemoryManager {
|
|
|
98
107
|
this._store = new MemoryStore(db, this.embeddings, this.logger)
|
|
99
108
|
this._search = new SemanticSearch(db, this.embeddings, this.logger)
|
|
100
109
|
|
|
110
|
+
// Phase 26: Always initialize FTS5 (just SQLite, no external deps)
|
|
111
|
+
try {
|
|
112
|
+
addFTS5Tables(db)
|
|
113
|
+
this._fts5 = new FTS5Search(db, this.logger)
|
|
114
|
+
this.logger.info('FTS5 search initialized')
|
|
115
|
+
} catch (error) {
|
|
116
|
+
this.logger.warn({ error }, 'Failed to initialize FTS5, continuing without it')
|
|
117
|
+
}
|
|
118
|
+
|
|
101
119
|
if (this.useChromaDB) {
|
|
102
120
|
try {
|
|
103
121
|
await this.chroma.initialize()
|
|
@@ -136,6 +154,13 @@ export class MemoryManager {
|
|
|
136
154
|
return this._search
|
|
137
155
|
}
|
|
138
156
|
|
|
157
|
+
/**
|
|
158
|
+
* Get the FTS5 search engine (null if not initialized)
|
|
159
|
+
*/
|
|
160
|
+
get fts5(): FTS5Search | null {
|
|
161
|
+
return this._fts5
|
|
162
|
+
}
|
|
163
|
+
|
|
139
164
|
/**
|
|
140
165
|
* Check if memory system is initialized
|
|
141
166
|
*/
|
|
@@ -219,8 +244,51 @@ export class MemoryManager {
|
|
|
219
244
|
reasoning: string,
|
|
220
245
|
options?: { alternatives?: string; tags?: string[] }
|
|
221
246
|
): Promise<string> {
|
|
247
|
+
// Phase 26: Always store in FTS5 if available
|
|
248
|
+
let fts5Id: string | undefined
|
|
249
|
+
if (this._fts5) {
|
|
250
|
+
try {
|
|
251
|
+
// Check for duplicates first
|
|
252
|
+
const dup = this._fts5.searchForDuplicates(decision, project)
|
|
253
|
+
if (dup) {
|
|
254
|
+
this.logger.info({ existingId: dup.id, score: dup.score }, 'FTS5 skipping duplicate decision')
|
|
255
|
+
return dup.id
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
fts5Id = this._fts5.store({
|
|
259
|
+
project,
|
|
260
|
+
category: 'decision',
|
|
261
|
+
content: decision,
|
|
262
|
+
reasoning,
|
|
263
|
+
context,
|
|
264
|
+
tags: options?.tags
|
|
265
|
+
})
|
|
266
|
+
} catch (error) {
|
|
267
|
+
this.logger.warn({ error }, 'FTS5 store failed, continuing with other backends')
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Store in ChromaDB if available
|
|
222
272
|
if (this.useChromaDB) {
|
|
223
|
-
|
|
273
|
+
try {
|
|
274
|
+
const chromaId = await this.chroma.store.storeDecision({
|
|
275
|
+
project,
|
|
276
|
+
context,
|
|
277
|
+
decision,
|
|
278
|
+
reasoning,
|
|
279
|
+
alternatives: options?.alternatives,
|
|
280
|
+
tags: options?.tags
|
|
281
|
+
})
|
|
282
|
+
return fts5Id || chromaId
|
|
283
|
+
} catch (error) {
|
|
284
|
+
this.logger.warn({ error }, 'ChromaDB store failed')
|
|
285
|
+
if (fts5Id) return fts5Id
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// Fallback to legacy SQLite store if no FTS5 and no ChromaDB
|
|
290
|
+
if (!fts5Id) {
|
|
291
|
+
const id = await this.store.storeDecision({
|
|
224
292
|
project,
|
|
225
293
|
context,
|
|
226
294
|
decision,
|
|
@@ -228,40 +296,83 @@ export class MemoryManager {
|
|
|
228
296
|
alternatives: options?.alternatives,
|
|
229
297
|
tags: options?.tags
|
|
230
298
|
})
|
|
299
|
+
fts5Id = id
|
|
231
300
|
}
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
context,
|
|
235
|
-
decision,
|
|
236
|
-
reasoning,
|
|
237
|
-
alternatives: options?.alternatives,
|
|
238
|
-
tags: options?.tags
|
|
239
|
-
})
|
|
240
|
-
// Notify listeners (e.g., knowledge graph builder) for SQLite path
|
|
301
|
+
|
|
302
|
+
// Notify listeners (e.g., knowledge graph builder)
|
|
241
303
|
for (const cb of this.onDecisionStoredCallbacks) {
|
|
242
304
|
try {
|
|
243
|
-
cb({ project, context, decision, reasoning, alternatives: options?.alternatives, tags: options?.tags, id })
|
|
305
|
+
cb({ project, context, decision, reasoning, alternatives: options?.alternatives, tags: options?.tags, id: fts5Id })
|
|
244
306
|
} catch {}
|
|
245
307
|
}
|
|
246
|
-
return
|
|
308
|
+
return fts5Id!
|
|
247
309
|
}
|
|
248
310
|
|
|
249
311
|
/**
|
|
250
|
-
* Get raw search results -
|
|
312
|
+
* Get raw search results - uses FTS5 as primary, ChromaDB as enrichment
|
|
251
313
|
* Use this for internal operations that need raw results
|
|
252
314
|
*/
|
|
253
315
|
async searchRaw(
|
|
254
316
|
query: string,
|
|
255
317
|
options?: { project?: string; limit?: number; minSimilarity?: number }
|
|
256
318
|
): Promise<any[]> {
|
|
319
|
+
const limit = options?.limit || 5
|
|
320
|
+
|
|
321
|
+
// Phase 26: Try FTS5 first (always available)
|
|
322
|
+
if (this._fts5) {
|
|
323
|
+
const ftsResults = this._fts5.searchWithConfidence(query, options?.project, limit)
|
|
324
|
+
|
|
325
|
+
if (ftsResults.length > 0) {
|
|
326
|
+
// Transform FTS5 results to match expected MemorySearchResult structure
|
|
327
|
+
const results = ftsResults.map(r => ({
|
|
328
|
+
id: r.id,
|
|
329
|
+
content: r.content,
|
|
330
|
+
memory: {
|
|
331
|
+
id: r.id,
|
|
332
|
+
project: r.project,
|
|
333
|
+
content: r.content,
|
|
334
|
+
createdAt: new Date(r.created_at),
|
|
335
|
+
metadata: {
|
|
336
|
+
project: r.project,
|
|
337
|
+
category: r.category,
|
|
338
|
+
context: r.context || '',
|
|
339
|
+
reasoning: r.reasoning || '',
|
|
340
|
+
tags: r.tags,
|
|
341
|
+
created_at: r.created_at
|
|
342
|
+
}
|
|
343
|
+
},
|
|
344
|
+
similarity: r.score,
|
|
345
|
+
decision: r.category === 'decision' ? {
|
|
346
|
+
id: r.id,
|
|
347
|
+
project: r.project,
|
|
348
|
+
context: r.context || '',
|
|
349
|
+
decision: r.content,
|
|
350
|
+
reasoning: r.reasoning || '',
|
|
351
|
+
alternatives: '',
|
|
352
|
+
tags: r.tags,
|
|
353
|
+
createdAt: new Date(r.created_at)
|
|
354
|
+
} : undefined,
|
|
355
|
+
metadata: {
|
|
356
|
+
project: r.project,
|
|
357
|
+
category: r.category,
|
|
358
|
+
context: r.context || '',
|
|
359
|
+
reasoning: r.reasoning || '',
|
|
360
|
+
tags: r.tags,
|
|
361
|
+
created_at: r.created_at
|
|
362
|
+
}
|
|
363
|
+
}))
|
|
364
|
+
|
|
365
|
+
return results
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// Fallback: Try ChromaDB if available
|
|
257
370
|
if (this.useChromaDB) {
|
|
258
371
|
const chromaResults = await this.chroma.search.searchDecisions(query, {
|
|
259
372
|
project: options?.project,
|
|
260
|
-
limit
|
|
373
|
+
limit,
|
|
261
374
|
minSimilarity: options?.minSimilarity || 0.5
|
|
262
375
|
})
|
|
263
|
-
// Transform ChromaDB results to match MemorySearchResult structure
|
|
264
|
-
// Includes flat `content` field for direct access (Phase 19)
|
|
265
376
|
return chromaResults.map(r => {
|
|
266
377
|
const memoryContent = typeof r.content === 'string' ? r.content : JSON.stringify(r.content)
|
|
267
378
|
const decisionObj = r.metadata.decision ? {
|
|
@@ -277,10 +388,8 @@ export class MemoryManager {
|
|
|
277
388
|
} : undefined
|
|
278
389
|
|
|
279
390
|
return {
|
|
280
|
-
// Flat fields for direct access (Phase 19)
|
|
281
391
|
id: r.id,
|
|
282
392
|
content: decisionObj ? decisionObj.decision : memoryContent,
|
|
283
|
-
// Nested fields for backward compatibility
|
|
284
393
|
memory: {
|
|
285
394
|
id: r.id,
|
|
286
395
|
project: r.metadata.project || options?.project || 'unknown',
|
|
@@ -293,13 +402,14 @@ export class MemoryManager {
|
|
|
293
402
|
metadata: r.metadata
|
|
294
403
|
}
|
|
295
404
|
})
|
|
296
|
-
} else {
|
|
297
|
-
return await this.search.search(query, {
|
|
298
|
-
project: options?.project,
|
|
299
|
-
limit: options?.limit || 5,
|
|
300
|
-
minSimilarity: options?.minSimilarity || 0.5
|
|
301
|
-
})
|
|
302
405
|
}
|
|
406
|
+
|
|
407
|
+
// Final fallback: legacy SQLite search
|
|
408
|
+
return await this.search.search(query, {
|
|
409
|
+
project: options?.project,
|
|
410
|
+
limit,
|
|
411
|
+
minSimilarity: options?.minSimilarity || 0.5
|
|
412
|
+
})
|
|
303
413
|
}
|
|
304
414
|
|
|
305
415
|
async recallSimilar(
|
|
@@ -335,7 +445,7 @@ export class MemoryManager {
|
|
|
335
445
|
}
|
|
336
446
|
|
|
337
447
|
/**
|
|
338
|
-
* Store a pattern in memory —
|
|
448
|
+
* Store a pattern in memory — dual-writes to FTS5 + ChromaDB/SQLite
|
|
339
449
|
*/
|
|
340
450
|
async storePattern(input: {
|
|
341
451
|
project: string
|
|
@@ -346,14 +456,41 @@ export class MemoryManager {
|
|
|
346
456
|
context?: string
|
|
347
457
|
source?: string
|
|
348
458
|
}): Promise<string> {
|
|
459
|
+
// Phase 26: Dual-write to FTS5
|
|
460
|
+
let fts5Id: string | undefined
|
|
461
|
+
if (this._fts5) {
|
|
462
|
+
try {
|
|
463
|
+
fts5Id = this._fts5.store({
|
|
464
|
+
project: input.project,
|
|
465
|
+
category: 'pattern',
|
|
466
|
+
content: input.description,
|
|
467
|
+
context: input.context,
|
|
468
|
+
confidence: input.confidence,
|
|
469
|
+
source: input.source
|
|
470
|
+
})
|
|
471
|
+
} catch (error) {
|
|
472
|
+
this.logger.warn({ error }, 'FTS5 pattern store failed')
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
349
476
|
if (this.useChromaDB) {
|
|
350
|
-
|
|
477
|
+
try {
|
|
478
|
+
const chromaId = await this.chroma.store.storePattern(input)
|
|
479
|
+
return fts5Id || chromaId
|
|
480
|
+
} catch (error) {
|
|
481
|
+
this.logger.warn({ error }, 'ChromaDB pattern store failed')
|
|
482
|
+
if (fts5Id) return fts5Id
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
if (!fts5Id) {
|
|
487
|
+
return this.store.storePattern(input)
|
|
351
488
|
}
|
|
352
|
-
return
|
|
489
|
+
return fts5Id
|
|
353
490
|
}
|
|
354
491
|
|
|
355
492
|
/**
|
|
356
|
-
* Store a correction/lesson learned —
|
|
493
|
+
* Store a correction/lesson learned — dual-writes to FTS5 + ChromaDB/SQLite
|
|
357
494
|
*/
|
|
358
495
|
async storeCorrection(input: {
|
|
359
496
|
project: string
|
|
@@ -363,14 +500,41 @@ export class MemoryManager {
|
|
|
363
500
|
context?: string
|
|
364
501
|
confidence: number
|
|
365
502
|
}): Promise<string> {
|
|
503
|
+
// Phase 26: Dual-write to FTS5
|
|
504
|
+
let fts5Id: string | undefined
|
|
505
|
+
if (this._fts5) {
|
|
506
|
+
try {
|
|
507
|
+
fts5Id = this._fts5.store({
|
|
508
|
+
project: input.project,
|
|
509
|
+
category: 'correction',
|
|
510
|
+
content: input.correction,
|
|
511
|
+
reasoning: input.reasoning,
|
|
512
|
+
context: input.context,
|
|
513
|
+
confidence: input.confidence
|
|
514
|
+
})
|
|
515
|
+
} catch (error) {
|
|
516
|
+
this.logger.warn({ error }, 'FTS5 correction store failed')
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
|
|
366
520
|
if (this.useChromaDB) {
|
|
367
|
-
|
|
521
|
+
try {
|
|
522
|
+
const chromaId = await this.chroma.store.storeCorrection(input)
|
|
523
|
+
return fts5Id || chromaId
|
|
524
|
+
} catch (error) {
|
|
525
|
+
this.logger.warn({ error }, 'ChromaDB correction store failed')
|
|
526
|
+
if (fts5Id) return fts5Id
|
|
527
|
+
}
|
|
368
528
|
}
|
|
369
|
-
|
|
529
|
+
|
|
530
|
+
if (!fts5Id) {
|
|
531
|
+
return this.store.storeCorrection(input)
|
|
532
|
+
}
|
|
533
|
+
return fts5Id
|
|
370
534
|
}
|
|
371
535
|
|
|
372
536
|
/**
|
|
373
|
-
* Get patterns for a project — routes to ChromaDB or SQLite
|
|
537
|
+
* Get patterns for a project — routes to FTS5, ChromaDB, or legacy SQLite
|
|
374
538
|
*/
|
|
375
539
|
async getPatterns(
|
|
376
540
|
project?: string,
|
|
@@ -379,6 +543,24 @@ export class MemoryManager {
|
|
|
379
543
|
limit?: number
|
|
380
544
|
}
|
|
381
545
|
): Promise<any[]> {
|
|
546
|
+
// Phase 26: Try FTS5 first
|
|
547
|
+
if (this._fts5 && project) {
|
|
548
|
+
const results = this._fts5.fetchAll(project, 'pattern')
|
|
549
|
+
if (results.length > 0) {
|
|
550
|
+
return results.map(r => ({
|
|
551
|
+
id: r.id,
|
|
552
|
+
description: r.content,
|
|
553
|
+
metadata: {
|
|
554
|
+
project: r.project,
|
|
555
|
+
pattern_type: r.category,
|
|
556
|
+
confidence: r.confidence,
|
|
557
|
+
context: r.context || '',
|
|
558
|
+
created_at: r.created_at
|
|
559
|
+
}
|
|
560
|
+
}))
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
|
|
382
564
|
if (this.useChromaDB) {
|
|
383
565
|
if (project) {
|
|
384
566
|
return this.chroma.store.getPatternsByProject(project, options)
|
|
@@ -392,12 +574,30 @@ export class MemoryManager {
|
|
|
392
574
|
}
|
|
393
575
|
|
|
394
576
|
/**
|
|
395
|
-
* Get corrections for a project — routes to ChromaDB or SQLite
|
|
577
|
+
* Get corrections for a project — routes to FTS5, ChromaDB, or legacy SQLite
|
|
396
578
|
*/
|
|
397
579
|
async getCorrections(
|
|
398
580
|
project?: string,
|
|
399
581
|
options?: { limit?: number }
|
|
400
582
|
): Promise<any[]> {
|
|
583
|
+
// Phase 26: Try FTS5 first
|
|
584
|
+
if (this._fts5 && project) {
|
|
585
|
+
const results = this._fts5.fetchAll(project, 'correction')
|
|
586
|
+
if (results.length > 0) {
|
|
587
|
+
return results.map(r => ({
|
|
588
|
+
id: r.id,
|
|
589
|
+
correction: r.content,
|
|
590
|
+
metadata: {
|
|
591
|
+
project: r.project,
|
|
592
|
+
reasoning: r.reasoning || '',
|
|
593
|
+
context: r.context || '',
|
|
594
|
+
confidence: r.confidence,
|
|
595
|
+
created_at: r.created_at
|
|
596
|
+
}
|
|
597
|
+
}))
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
|
|
401
601
|
if (this.useChromaDB) {
|
|
402
602
|
if (project) {
|
|
403
603
|
return this.chroma.store.getCorrectionsByProject(project, options?.limit || 10)
|
|
@@ -411,10 +611,28 @@ export class MemoryManager {
|
|
|
411
611
|
}
|
|
412
612
|
|
|
413
613
|
/**
|
|
414
|
-
* Fetch all decisions with content — routes to ChromaDB or SQLite
|
|
614
|
+
* Fetch all decisions with content — routes to FTS5, ChromaDB, or legacy SQLite
|
|
415
615
|
* Used by analytical tools that need bulk access to decision data
|
|
416
616
|
*/
|
|
417
617
|
async fetchAllDecisions(project?: string): Promise<any[]> {
|
|
618
|
+
// Phase 26: Try FTS5 first
|
|
619
|
+
if (this._fts5 && project) {
|
|
620
|
+
const results = this._fts5.fetchAll(project, 'decision')
|
|
621
|
+
if (results.length > 0) {
|
|
622
|
+
return results.map(r => ({
|
|
623
|
+
id: r.id,
|
|
624
|
+
content: r.content,
|
|
625
|
+
date: r.created_at,
|
|
626
|
+
project: r.project,
|
|
627
|
+
context: r.context || '',
|
|
628
|
+
decision: r.content,
|
|
629
|
+
reasoning: r.reasoning || '',
|
|
630
|
+
alternatives: '',
|
|
631
|
+
tags: r.tags
|
|
632
|
+
}))
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
|
|
418
636
|
if (this.useChromaDB) {
|
|
419
637
|
try {
|
|
420
638
|
const collection = await this.chroma.collections.getDecisions()
|
|
@@ -443,9 +661,27 @@ export class MemoryManager {
|
|
|
443
661
|
}
|
|
444
662
|
|
|
445
663
|
/**
|
|
446
|
-
* Fetch all patterns with content — routes to ChromaDB or SQLite
|
|
664
|
+
* Fetch all patterns with content — routes to FTS5, ChromaDB, or legacy SQLite
|
|
447
665
|
*/
|
|
448
666
|
async fetchAllPatterns(project?: string): Promise<any[]> {
|
|
667
|
+
// Phase 26: Try FTS5 first
|
|
668
|
+
if (this._fts5 && project) {
|
|
669
|
+
const results = this._fts5.fetchAll(project, 'pattern')
|
|
670
|
+
if (results.length > 0) {
|
|
671
|
+
return results.map(r => ({
|
|
672
|
+
id: r.id,
|
|
673
|
+
content: r.content,
|
|
674
|
+
date: r.created_at,
|
|
675
|
+
project: r.project,
|
|
676
|
+
pattern_type: '',
|
|
677
|
+
description: r.content,
|
|
678
|
+
example: '',
|
|
679
|
+
confidence: r.confidence,
|
|
680
|
+
context: r.context || ''
|
|
681
|
+
}))
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
|
|
449
685
|
if (this.useChromaDB) {
|
|
450
686
|
try {
|
|
451
687
|
const collection = await this.chroma.collections.getPatterns()
|
|
@@ -474,9 +710,27 @@ export class MemoryManager {
|
|
|
474
710
|
}
|
|
475
711
|
|
|
476
712
|
/**
|
|
477
|
-
* Fetch all corrections with content — routes to ChromaDB or SQLite
|
|
713
|
+
* Fetch all corrections with content — routes to FTS5, ChromaDB, or legacy SQLite
|
|
478
714
|
*/
|
|
479
715
|
async fetchAllCorrections(project?: string): Promise<any[]> {
|
|
716
|
+
// Phase 26: Try FTS5 first
|
|
717
|
+
if (this._fts5 && project) {
|
|
718
|
+
const results = this._fts5.fetchAll(project, 'correction')
|
|
719
|
+
if (results.length > 0) {
|
|
720
|
+
return results.map(r => ({
|
|
721
|
+
id: r.id,
|
|
722
|
+
content: r.content,
|
|
723
|
+
date: r.created_at,
|
|
724
|
+
project: r.project,
|
|
725
|
+
original: '',
|
|
726
|
+
correction: r.content,
|
|
727
|
+
reasoning: r.reasoning || '',
|
|
728
|
+
context: r.context || '',
|
|
729
|
+
confidence: r.confidence
|
|
730
|
+
}))
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
|
|
480
734
|
if (this.useChromaDB) {
|
|
481
735
|
try {
|
|
482
736
|
const collection = await this.chroma.collections.getCorrections()
|
|
@@ -505,7 +759,7 @@ export class MemoryManager {
|
|
|
505
759
|
}
|
|
506
760
|
|
|
507
761
|
/**
|
|
508
|
-
* Search patterns by query — routes to ChromaDB or SQLite
|
|
762
|
+
* Search patterns by query — routes to FTS5, ChromaDB, or legacy SQLite
|
|
509
763
|
*/
|
|
510
764
|
async searchPatterns(
|
|
511
765
|
query: string,
|
|
@@ -516,6 +770,27 @@ export class MemoryManager {
|
|
|
516
770
|
minSimilarity?: number
|
|
517
771
|
}
|
|
518
772
|
): Promise<any[]> {
|
|
773
|
+
// Phase 26: Try FTS5 first
|
|
774
|
+
if (this._fts5 && query) {
|
|
775
|
+
const results = this._fts5.searchWithConfidence(query, options?.project, options?.limit || 10)
|
|
776
|
+
const patterns = results.filter(r => r.category === 'pattern')
|
|
777
|
+
if (patterns.length > 0) {
|
|
778
|
+
return patterns.map(r => ({
|
|
779
|
+
id: r.id,
|
|
780
|
+
content: r.content,
|
|
781
|
+
metadata: {
|
|
782
|
+
project: r.project,
|
|
783
|
+
pattern_type: '',
|
|
784
|
+
description: r.content,
|
|
785
|
+
confidence: r.confidence,
|
|
786
|
+
context: r.context || '',
|
|
787
|
+
created_at: r.created_at
|
|
788
|
+
},
|
|
789
|
+
similarity: r.score
|
|
790
|
+
}))
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
|
|
519
794
|
if (this.useChromaDB) {
|
|
520
795
|
return this.chroma.store.searchPatterns(query, options)
|
|
521
796
|
}
|
|
@@ -523,7 +798,7 @@ export class MemoryManager {
|
|
|
523
798
|
}
|
|
524
799
|
|
|
525
800
|
/**
|
|
526
|
-
* Search corrections by query — routes to ChromaDB or SQLite
|
|
801
|
+
* Search corrections by query — routes to FTS5, ChromaDB, or legacy SQLite
|
|
527
802
|
*/
|
|
528
803
|
async searchCorrections(
|
|
529
804
|
query: string,
|
|
@@ -533,6 +808,26 @@ export class MemoryManager {
|
|
|
533
808
|
minSimilarity?: number
|
|
534
809
|
}
|
|
535
810
|
): Promise<any[]> {
|
|
811
|
+
// Phase 26: Try FTS5 first
|
|
812
|
+
if (this._fts5 && query) {
|
|
813
|
+
const results = this._fts5.searchWithConfidence(query, options?.project, options?.limit || 10)
|
|
814
|
+
const corrections = results.filter(r => r.category === 'correction')
|
|
815
|
+
if (corrections.length > 0) {
|
|
816
|
+
return corrections.map(r => ({
|
|
817
|
+
id: r.id,
|
|
818
|
+
content: r.content,
|
|
819
|
+
metadata: {
|
|
820
|
+
project: r.project,
|
|
821
|
+
reasoning: r.reasoning || '',
|
|
822
|
+
context: r.context || '',
|
|
823
|
+
confidence: r.confidence,
|
|
824
|
+
created_at: r.created_at
|
|
825
|
+
},
|
|
826
|
+
similarity: r.score
|
|
827
|
+
}))
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
|
|
536
831
|
if (this.useChromaDB) {
|
|
537
832
|
return this.chroma.store.searchCorrections(query, options)
|
|
538
833
|
}
|
|
@@ -540,9 +835,18 @@ export class MemoryManager {
|
|
|
540
835
|
}
|
|
541
836
|
|
|
542
837
|
/**
|
|
543
|
-
* Delete a decision by ID —
|
|
838
|
+
* Delete a decision by ID — removes from FTS5 + ChromaDB/SQLite
|
|
544
839
|
*/
|
|
545
840
|
async deleteDecision(id: string): Promise<void> {
|
|
841
|
+
// Phase 26: Delete from FTS5
|
|
842
|
+
if (this._fts5) {
|
|
843
|
+
try {
|
|
844
|
+
this._fts5.delete(id)
|
|
845
|
+
} catch (error) {
|
|
846
|
+
this.logger.warn({ error, id }, 'FTS5 delete failed')
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
|
|
546
850
|
if (this.useChromaDB) {
|
|
547
851
|
await this.chroma.store.deleteDecision(id)
|
|
548
852
|
} else {
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FTS5 Migration — Phase 26
|
|
3
|
+
* Adds observations table with FTS5 virtual table for full-text search.
|
|
4
|
+
* This replaces ChromaDB as the primary search backend.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { Database } from 'bun:sqlite'
|
|
8
|
+
|
|
9
|
+
export function addFTS5Tables(db: Database): void {
|
|
10
|
+
// Observations table (unified storage replacing separate ChromaDB collections)
|
|
11
|
+
db.run(`
|
|
12
|
+
CREATE TABLE IF NOT EXISTS observations (
|
|
13
|
+
id TEXT PRIMARY KEY,
|
|
14
|
+
project TEXT NOT NULL,
|
|
15
|
+
category TEXT NOT NULL CHECK(category IN ('decision', 'pattern', 'correction', 'insight', 'preference')),
|
|
16
|
+
content TEXT NOT NULL,
|
|
17
|
+
reasoning TEXT,
|
|
18
|
+
context TEXT,
|
|
19
|
+
confidence REAL DEFAULT 0.8,
|
|
20
|
+
source TEXT DEFAULT 'explicit',
|
|
21
|
+
tags TEXT,
|
|
22
|
+
file_paths TEXT,
|
|
23
|
+
symbols TEXT,
|
|
24
|
+
access_count INTEGER DEFAULT 0,
|
|
25
|
+
last_accessed TEXT,
|
|
26
|
+
created_at TEXT NOT NULL,
|
|
27
|
+
updated_at TEXT NOT NULL,
|
|
28
|
+
archived INTEGER DEFAULT 0
|
|
29
|
+
)
|
|
30
|
+
`)
|
|
31
|
+
|
|
32
|
+
// FTS5 virtual table for full-text search
|
|
33
|
+
db.run(`
|
|
34
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS observations_fts USING fts5(
|
|
35
|
+
content, reasoning, context, tags,
|
|
36
|
+
content='observations', content_rowid='rowid'
|
|
37
|
+
)
|
|
38
|
+
`)
|
|
39
|
+
|
|
40
|
+
// Keep FTS index in sync with observations table
|
|
41
|
+
db.run(`
|
|
42
|
+
CREATE TRIGGER IF NOT EXISTS observations_ai AFTER INSERT ON observations BEGIN
|
|
43
|
+
INSERT INTO observations_fts(rowid, content, reasoning, context, tags)
|
|
44
|
+
VALUES (new.rowid, new.content, new.reasoning, new.context, new.tags);
|
|
45
|
+
END
|
|
46
|
+
`)
|
|
47
|
+
|
|
48
|
+
db.run(`
|
|
49
|
+
CREATE TRIGGER IF NOT EXISTS observations_ad AFTER DELETE ON observations BEGIN
|
|
50
|
+
INSERT INTO observations_fts(observations_fts, rowid, content, reasoning, context, tags)
|
|
51
|
+
VALUES ('delete', old.rowid, old.content, old.reasoning, old.context, old.tags);
|
|
52
|
+
END
|
|
53
|
+
`)
|
|
54
|
+
|
|
55
|
+
db.run(`
|
|
56
|
+
CREATE TRIGGER IF NOT EXISTS observations_au AFTER UPDATE ON observations BEGIN
|
|
57
|
+
INSERT INTO observations_fts(observations_fts, rowid, content, reasoning, context, tags)
|
|
58
|
+
VALUES ('delete', old.rowid, old.content, old.reasoning, old.context, old.tags);
|
|
59
|
+
INSERT INTO observations_fts(rowid, content, reasoning, context, tags)
|
|
60
|
+
VALUES (new.rowid, new.content, new.reasoning, new.context, new.tags);
|
|
61
|
+
END
|
|
62
|
+
`)
|
|
63
|
+
|
|
64
|
+
// Indexes for common queries
|
|
65
|
+
db.run(`CREATE INDEX IF NOT EXISTS idx_obs_project ON observations(project)`)
|
|
66
|
+
db.run(`CREATE INDEX IF NOT EXISTS idx_obs_category ON observations(category)`)
|
|
67
|
+
db.run(`CREATE INDEX IF NOT EXISTS idx_obs_created ON observations(created_at)`)
|
|
68
|
+
db.run(`CREATE INDEX IF NOT EXISTS idx_obs_archived ON observations(archived)`)
|
|
69
|
+
|
|
70
|
+
// Activity log table
|
|
71
|
+
db.run(`
|
|
72
|
+
CREATE TABLE IF NOT EXISTS activity_log (
|
|
73
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
74
|
+
session_id TEXT,
|
|
75
|
+
tool_name TEXT NOT NULL,
|
|
76
|
+
tool_input TEXT,
|
|
77
|
+
tool_output TEXT,
|
|
78
|
+
project TEXT,
|
|
79
|
+
file_path TEXT,
|
|
80
|
+
created_at TEXT NOT NULL
|
|
81
|
+
)
|
|
82
|
+
`)
|
|
83
|
+
|
|
84
|
+
db.run(`CREATE INDEX IF NOT EXISTS idx_activity_session ON activity_log(session_id)`)
|
|
85
|
+
db.run(`CREATE INDEX IF NOT EXISTS idx_activity_created ON activity_log(created_at)`)
|
|
86
|
+
db.run(`CREATE INDEX IF NOT EXISTS idx_activity_project ON activity_log(project)`)
|
|
87
|
+
|
|
88
|
+
// Observation links (supersedes/contradicts/supports relationships)
|
|
89
|
+
db.run(`
|
|
90
|
+
CREATE TABLE IF NOT EXISTS observation_links (
|
|
91
|
+
source_id TEXT NOT NULL,
|
|
92
|
+
target_id TEXT NOT NULL,
|
|
93
|
+
link_type TEXT NOT NULL,
|
|
94
|
+
created_at TEXT NOT NULL,
|
|
95
|
+
PRIMARY KEY (source_id, target_id)
|
|
96
|
+
)
|
|
97
|
+
`)
|
|
98
|
+
}
|