claude-brain 0.3.7 → 0.4.1
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 +1 -1
- package/src/cli/auto-setup.ts +8 -0
- package/src/cli/bin.ts +8 -0
- package/src/cli/commands/chroma.ts +407 -0
- package/src/config/defaults.ts +1 -1
- package/src/config/schema.ts +1 -1
- package/src/context/standards-manager.ts +20 -0
- package/src/memory/chroma/client.ts +6 -5
- package/src/memory/chroma/config.ts +12 -1
- package/src/memory/index.ts +59 -29
- package/src/memory/schema.ts +33 -1
- package/src/memory/store.ts +311 -1
- package/src/server/handlers/tools/analyze-decision-evolution.ts +10 -0
- package/src/server/handlers/tools/detect-trends.ts +10 -0
- package/src/server/handlers/tools/find-cross-project-patterns.ts +10 -0
- package/src/server/handlers/tools/get-decision-timeline.ts +10 -0
- package/src/server/handlers/tools/get-recommendations.ts +10 -0
- package/src/server/handlers/tools/schemas.ts +1 -1
- package/src/server/handlers/tools/search-knowledge-graph.ts +14 -1
- package/src/server/handlers/tools/update-progress.ts +12 -0
- package/src/server/handlers/tools/what-if-analysis.ts +11 -0
- package/src/server/services.ts +14 -9
- package/src/setup/wizard.ts +81 -11
package/src/memory/index.ts
CHANGED
|
@@ -55,6 +55,7 @@ export class MemoryManager {
|
|
|
55
55
|
private logger: Logger
|
|
56
56
|
private initialized: boolean = false
|
|
57
57
|
private useChromaDB: boolean = true
|
|
58
|
+
private onDecisionStoredCallbacks: ((input: any) => void)[] = []
|
|
58
59
|
|
|
59
60
|
constructor(
|
|
60
61
|
dbPath: string,
|
|
@@ -176,6 +177,24 @@ export class MemoryManager {
|
|
|
176
177
|
return this.database.healthCheck()
|
|
177
178
|
}
|
|
178
179
|
|
|
180
|
+
/**
|
|
181
|
+
* Check if ChromaDB backend is enabled and connected
|
|
182
|
+
*/
|
|
183
|
+
isChromaDBEnabled(): boolean {
|
|
184
|
+
return this.useChromaDB
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Add a listener that fires when a decision is stored (from any backend)
|
|
189
|
+
*/
|
|
190
|
+
addDecisionStoredListener(callback: (input: any) => void): void {
|
|
191
|
+
this.onDecisionStoredCallbacks.push(callback)
|
|
192
|
+
// Also wire to ChromaDB listener if available
|
|
193
|
+
if (this.useChromaDB) {
|
|
194
|
+
this.chroma.store.addDecisionStoredListener(callback)
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
179
198
|
async rememberDecision(
|
|
180
199
|
project: string,
|
|
181
200
|
context: string,
|
|
@@ -193,7 +212,7 @@ export class MemoryManager {
|
|
|
193
212
|
tags: options?.tags
|
|
194
213
|
})
|
|
195
214
|
}
|
|
196
|
-
|
|
215
|
+
const id = await this.store.storeDecision({
|
|
197
216
|
project,
|
|
198
217
|
context,
|
|
199
218
|
decision,
|
|
@@ -201,6 +220,13 @@ export class MemoryManager {
|
|
|
201
220
|
alternatives: options?.alternatives,
|
|
202
221
|
tags: options?.tags
|
|
203
222
|
})
|
|
223
|
+
// Notify listeners (e.g., knowledge graph builder) for SQLite path
|
|
224
|
+
for (const cb of this.onDecisionStoredCallbacks) {
|
|
225
|
+
try {
|
|
226
|
+
cb({ project, context, decision, reasoning, alternatives: options?.alternatives, tags: options?.tags, id })
|
|
227
|
+
} catch {}
|
|
228
|
+
}
|
|
229
|
+
return id
|
|
204
230
|
}
|
|
205
231
|
|
|
206
232
|
/**
|
|
@@ -281,7 +307,7 @@ export class MemoryManager {
|
|
|
281
307
|
}
|
|
282
308
|
|
|
283
309
|
/**
|
|
284
|
-
* Store a pattern in memory
|
|
310
|
+
* Store a pattern in memory — routes to ChromaDB or SQLite
|
|
285
311
|
*/
|
|
286
312
|
async storePattern(input: {
|
|
287
313
|
project: string
|
|
@@ -291,14 +317,14 @@ export class MemoryManager {
|
|
|
291
317
|
confidence: number
|
|
292
318
|
context?: string
|
|
293
319
|
}): Promise<string> {
|
|
294
|
-
if (
|
|
295
|
-
|
|
320
|
+
if (this.useChromaDB) {
|
|
321
|
+
return this.chroma.store.storePattern(input)
|
|
296
322
|
}
|
|
297
|
-
return this.
|
|
323
|
+
return this.store.storePattern(input)
|
|
298
324
|
}
|
|
299
325
|
|
|
300
326
|
/**
|
|
301
|
-
* Store a correction/lesson learned
|
|
327
|
+
* Store a correction/lesson learned — routes to ChromaDB or SQLite
|
|
302
328
|
*/
|
|
303
329
|
async storeCorrection(input: {
|
|
304
330
|
project: string
|
|
@@ -308,14 +334,14 @@ export class MemoryManager {
|
|
|
308
334
|
context?: string
|
|
309
335
|
confidence: number
|
|
310
336
|
}): Promise<string> {
|
|
311
|
-
if (
|
|
312
|
-
|
|
337
|
+
if (this.useChromaDB) {
|
|
338
|
+
return this.chroma.store.storeCorrection(input)
|
|
313
339
|
}
|
|
314
|
-
return this.
|
|
340
|
+
return this.store.storeCorrection(input)
|
|
315
341
|
}
|
|
316
342
|
|
|
317
343
|
/**
|
|
318
|
-
* Get patterns for a project
|
|
344
|
+
* Get patterns for a project — routes to ChromaDB or SQLite
|
|
319
345
|
*/
|
|
320
346
|
async getPatterns(
|
|
321
347
|
project?: string,
|
|
@@ -324,35 +350,39 @@ export class MemoryManager {
|
|
|
324
350
|
limit?: number
|
|
325
351
|
}
|
|
326
352
|
): Promise<any[]> {
|
|
327
|
-
if (
|
|
328
|
-
|
|
353
|
+
if (this.useChromaDB) {
|
|
354
|
+
if (project) {
|
|
355
|
+
return this.chroma.store.getPatternsByProject(project, options)
|
|
356
|
+
}
|
|
357
|
+
return this.chroma.store.searchPatterns('', { limit: options?.limit || 10 })
|
|
329
358
|
}
|
|
330
359
|
if (project) {
|
|
331
|
-
return this.
|
|
360
|
+
return this.store.getPatternsByProject(project, options)
|
|
332
361
|
}
|
|
333
|
-
|
|
334
|
-
return this.chroma.store.searchPatterns('', { limit: options?.limit || 10 })
|
|
362
|
+
return this.store.searchPatterns('', { limit: options?.limit || 10 })
|
|
335
363
|
}
|
|
336
364
|
|
|
337
365
|
/**
|
|
338
|
-
* Get corrections for a project
|
|
366
|
+
* Get corrections for a project — routes to ChromaDB or SQLite
|
|
339
367
|
*/
|
|
340
368
|
async getCorrections(
|
|
341
369
|
project?: string,
|
|
342
370
|
options?: { limit?: number }
|
|
343
371
|
): Promise<any[]> {
|
|
344
|
-
if (
|
|
345
|
-
|
|
372
|
+
if (this.useChromaDB) {
|
|
373
|
+
if (project) {
|
|
374
|
+
return this.chroma.store.getCorrectionsByProject(project, options?.limit || 10)
|
|
375
|
+
}
|
|
376
|
+
return this.chroma.store.searchCorrections('', { limit: options?.limit || 10 })
|
|
346
377
|
}
|
|
347
378
|
if (project) {
|
|
348
|
-
return this.
|
|
379
|
+
return this.store.getCorrectionsByProject(project, options?.limit || 10)
|
|
349
380
|
}
|
|
350
|
-
|
|
351
|
-
return this.chroma.store.searchCorrections('', { limit: options?.limit || 10 })
|
|
381
|
+
return this.store.searchCorrections('', { limit: options?.limit || 10 })
|
|
352
382
|
}
|
|
353
383
|
|
|
354
384
|
/**
|
|
355
|
-
* Search patterns by query
|
|
385
|
+
* Search patterns by query — routes to ChromaDB or SQLite
|
|
356
386
|
*/
|
|
357
387
|
async searchPatterns(
|
|
358
388
|
query: string,
|
|
@@ -363,14 +393,14 @@ export class MemoryManager {
|
|
|
363
393
|
minSimilarity?: number
|
|
364
394
|
}
|
|
365
395
|
): Promise<any[]> {
|
|
366
|
-
if (
|
|
367
|
-
|
|
396
|
+
if (this.useChromaDB) {
|
|
397
|
+
return this.chroma.store.searchPatterns(query, options)
|
|
368
398
|
}
|
|
369
|
-
return this.
|
|
399
|
+
return this.store.searchPatterns(query, options)
|
|
370
400
|
}
|
|
371
401
|
|
|
372
402
|
/**
|
|
373
|
-
* Search corrections by query
|
|
403
|
+
* Search corrections by query — routes to ChromaDB or SQLite
|
|
374
404
|
*/
|
|
375
405
|
async searchCorrections(
|
|
376
406
|
query: string,
|
|
@@ -380,10 +410,10 @@ export class MemoryManager {
|
|
|
380
410
|
minSimilarity?: number
|
|
381
411
|
}
|
|
382
412
|
): Promise<any[]> {
|
|
383
|
-
if (
|
|
384
|
-
|
|
413
|
+
if (this.useChromaDB) {
|
|
414
|
+
return this.chroma.store.searchCorrections(query, options)
|
|
385
415
|
}
|
|
386
|
-
return this.
|
|
416
|
+
return this.store.searchCorrections(query, options)
|
|
387
417
|
}
|
|
388
418
|
}
|
|
389
419
|
|
package/src/memory/schema.ts
CHANGED
|
@@ -41,12 +41,44 @@ CREATE TABLE IF NOT EXISTS decisions (
|
|
|
41
41
|
-- Index for decision lookups
|
|
42
42
|
CREATE INDEX IF NOT EXISTS idx_decisions_memory
|
|
43
43
|
ON decisions(memory_id);
|
|
44
|
+
|
|
45
|
+
-- Patterns table (Phase 12)
|
|
46
|
+
-- Stores reusable patterns with SQLite fallback
|
|
47
|
+
CREATE TABLE IF NOT EXISTS patterns (
|
|
48
|
+
id TEXT PRIMARY KEY,
|
|
49
|
+
memory_id TEXT NOT NULL,
|
|
50
|
+
project TEXT NOT NULL,
|
|
51
|
+
pattern_type TEXT NOT NULL CHECK(pattern_type IN ('solution','anti-pattern','best-practice','common-issue')),
|
|
52
|
+
description TEXT NOT NULL,
|
|
53
|
+
example TEXT,
|
|
54
|
+
confidence REAL DEFAULT 0.8,
|
|
55
|
+
context TEXT,
|
|
56
|
+
created_at TEXT NOT NULL,
|
|
57
|
+
FOREIGN KEY (memory_id) REFERENCES memories(id) ON DELETE CASCADE
|
|
58
|
+
);
|
|
59
|
+
CREATE INDEX IF NOT EXISTS idx_patterns_project ON patterns(project);
|
|
60
|
+
|
|
61
|
+
-- Corrections table (Phase 12)
|
|
62
|
+
-- Stores lessons learned with SQLite fallback
|
|
63
|
+
CREATE TABLE IF NOT EXISTS corrections (
|
|
64
|
+
id TEXT PRIMARY KEY,
|
|
65
|
+
memory_id TEXT NOT NULL,
|
|
66
|
+
project TEXT NOT NULL,
|
|
67
|
+
original TEXT NOT NULL,
|
|
68
|
+
correction TEXT NOT NULL,
|
|
69
|
+
reasoning TEXT NOT NULL,
|
|
70
|
+
context TEXT,
|
|
71
|
+
confidence REAL DEFAULT 0.9,
|
|
72
|
+
created_at TEXT NOT NULL,
|
|
73
|
+
FOREIGN KEY (memory_id) REFERENCES memories(id) ON DELETE CASCADE
|
|
74
|
+
);
|
|
75
|
+
CREATE INDEX IF NOT EXISTS idx_corrections_project ON corrections(project);
|
|
44
76
|
`
|
|
45
77
|
|
|
46
78
|
/**
|
|
47
79
|
* Schema version for migrations
|
|
48
80
|
*/
|
|
49
|
-
export const SCHEMA_VERSION =
|
|
81
|
+
export const SCHEMA_VERSION = 2
|
|
50
82
|
|
|
51
83
|
/**
|
|
52
84
|
* Check if schema needs migration
|
package/src/memory/store.ts
CHANGED
|
@@ -17,7 +17,7 @@ import type {
|
|
|
17
17
|
DecisionRow
|
|
18
18
|
} from './types'
|
|
19
19
|
import { EmbeddingService } from './embeddings'
|
|
20
|
-
import { embeddingToBuffer, bufferToEmbedding } from './embedding-utils'
|
|
20
|
+
import { embeddingToBuffer, bufferToEmbedding, cosineSimilarity } from './embedding-utils'
|
|
21
21
|
|
|
22
22
|
export class MemoryStore {
|
|
23
23
|
private db: Database
|
|
@@ -326,6 +326,316 @@ export class MemoryStore {
|
|
|
326
326
|
}
|
|
327
327
|
}
|
|
328
328
|
|
|
329
|
+
/**
|
|
330
|
+
* Store a pattern (creates memory + pattern record)
|
|
331
|
+
*/
|
|
332
|
+
async storePattern(input: {
|
|
333
|
+
project: string
|
|
334
|
+
pattern_type: 'solution' | 'anti-pattern' | 'best-practice' | 'common-issue'
|
|
335
|
+
description: string
|
|
336
|
+
example?: string
|
|
337
|
+
confidence: number
|
|
338
|
+
context?: string
|
|
339
|
+
}): Promise<string> {
|
|
340
|
+
try {
|
|
341
|
+
const content = `Pattern (${input.pattern_type}): ${input.description}${input.context ? `\nContext: ${input.context}` : ''}${input.example ? `\nExample: ${input.example}` : ''}`
|
|
342
|
+
|
|
343
|
+
const memoryId = await this.storeMemory({
|
|
344
|
+
project: input.project,
|
|
345
|
+
content,
|
|
346
|
+
metadata: {
|
|
347
|
+
type: 'pattern',
|
|
348
|
+
pattern_type: input.pattern_type,
|
|
349
|
+
confidence: input.confidence
|
|
350
|
+
}
|
|
351
|
+
})
|
|
352
|
+
|
|
353
|
+
const patternId = randomUUID()
|
|
354
|
+
const now = new Date().toISOString()
|
|
355
|
+
const stmt = this.db.prepare(`
|
|
356
|
+
INSERT INTO patterns (id, memory_id, project, pattern_type, description, example, confidence, context, created_at)
|
|
357
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
358
|
+
`)
|
|
359
|
+
|
|
360
|
+
stmt.run(
|
|
361
|
+
patternId,
|
|
362
|
+
memoryId,
|
|
363
|
+
input.project,
|
|
364
|
+
input.pattern_type,
|
|
365
|
+
input.description,
|
|
366
|
+
input.example || null,
|
|
367
|
+
input.confidence,
|
|
368
|
+
input.context || null,
|
|
369
|
+
now
|
|
370
|
+
)
|
|
371
|
+
|
|
372
|
+
this.logger.info({ patternId, memoryId, project: input.project }, 'Pattern stored')
|
|
373
|
+
return patternId
|
|
374
|
+
} catch (error) {
|
|
375
|
+
this.logger.error({ error, input }, 'Failed to store pattern')
|
|
376
|
+
throw error
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* Store a correction (creates memory + correction record)
|
|
382
|
+
*/
|
|
383
|
+
async storeCorrection(input: {
|
|
384
|
+
project: string
|
|
385
|
+
original: string
|
|
386
|
+
correction: string
|
|
387
|
+
reasoning: string
|
|
388
|
+
context?: string
|
|
389
|
+
confidence: number
|
|
390
|
+
}): Promise<string> {
|
|
391
|
+
try {
|
|
392
|
+
const content = `Correction: ${input.correction}\nOriginal: ${input.original}\nReasoning: ${input.reasoning}${input.context ? `\nContext: ${input.context}` : ''}`
|
|
393
|
+
|
|
394
|
+
const memoryId = await this.storeMemory({
|
|
395
|
+
project: input.project,
|
|
396
|
+
content,
|
|
397
|
+
metadata: {
|
|
398
|
+
type: 'correction',
|
|
399
|
+
confidence: input.confidence
|
|
400
|
+
}
|
|
401
|
+
})
|
|
402
|
+
|
|
403
|
+
const correctionId = randomUUID()
|
|
404
|
+
const now = new Date().toISOString()
|
|
405
|
+
const stmt = this.db.prepare(`
|
|
406
|
+
INSERT INTO corrections (id, memory_id, project, original, correction, reasoning, context, confidence, created_at)
|
|
407
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
408
|
+
`)
|
|
409
|
+
|
|
410
|
+
stmt.run(
|
|
411
|
+
correctionId,
|
|
412
|
+
memoryId,
|
|
413
|
+
input.project,
|
|
414
|
+
input.original,
|
|
415
|
+
input.correction,
|
|
416
|
+
input.reasoning,
|
|
417
|
+
input.context || null,
|
|
418
|
+
input.confidence,
|
|
419
|
+
now
|
|
420
|
+
)
|
|
421
|
+
|
|
422
|
+
this.logger.info({ correctionId, memoryId, project: input.project }, 'Correction stored')
|
|
423
|
+
return correctionId
|
|
424
|
+
} catch (error) {
|
|
425
|
+
this.logger.error({ error, input }, 'Failed to store correction')
|
|
426
|
+
throw error
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
/**
|
|
431
|
+
* Get patterns by project with optional type filter
|
|
432
|
+
*/
|
|
433
|
+
getPatternsByProject(project: string, options?: {
|
|
434
|
+
pattern_type?: 'solution' | 'anti-pattern' | 'best-practice' | 'common-issue'
|
|
435
|
+
limit?: number
|
|
436
|
+
}): any[] {
|
|
437
|
+
try {
|
|
438
|
+
let sql = 'SELECT * FROM patterns WHERE project = ?'
|
|
439
|
+
const params: any[] = [project]
|
|
440
|
+
|
|
441
|
+
if (options?.pattern_type) {
|
|
442
|
+
sql += ' AND pattern_type = ?'
|
|
443
|
+
params.push(options.pattern_type)
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
sql += ' ORDER BY created_at DESC'
|
|
447
|
+
|
|
448
|
+
if (options?.limit) {
|
|
449
|
+
sql += ' LIMIT ?'
|
|
450
|
+
params.push(options.limit)
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
const stmt = this.db.prepare(sql)
|
|
454
|
+
const rows = stmt.all(...params) as any[]
|
|
455
|
+
|
|
456
|
+
return rows.map(row => ({
|
|
457
|
+
id: row.id,
|
|
458
|
+
description: row.description,
|
|
459
|
+
metadata: {
|
|
460
|
+
project: row.project,
|
|
461
|
+
pattern_type: row.pattern_type,
|
|
462
|
+
example: row.example || '',
|
|
463
|
+
confidence: row.confidence,
|
|
464
|
+
context: row.context || '',
|
|
465
|
+
created_at: row.created_at
|
|
466
|
+
}
|
|
467
|
+
}))
|
|
468
|
+
} catch (error) {
|
|
469
|
+
this.logger.error({ error, project }, 'Failed to get patterns by project')
|
|
470
|
+
return []
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
/**
|
|
475
|
+
* Get corrections by project
|
|
476
|
+
*/
|
|
477
|
+
getCorrectionsByProject(project: string, limit: number = 10): any[] {
|
|
478
|
+
try {
|
|
479
|
+
const stmt = this.db.prepare(
|
|
480
|
+
'SELECT * FROM corrections WHERE project = ? ORDER BY created_at DESC LIMIT ?'
|
|
481
|
+
)
|
|
482
|
+
const rows = stmt.all(project, limit) as any[]
|
|
483
|
+
|
|
484
|
+
return rows.map(row => ({
|
|
485
|
+
id: row.id,
|
|
486
|
+
correction: row.correction,
|
|
487
|
+
metadata: {
|
|
488
|
+
project: row.project,
|
|
489
|
+
original: row.original,
|
|
490
|
+
correction: row.correction,
|
|
491
|
+
reasoning: row.reasoning,
|
|
492
|
+
context: row.context || '',
|
|
493
|
+
confidence: row.confidence,
|
|
494
|
+
created_at: row.created_at
|
|
495
|
+
}
|
|
496
|
+
}))
|
|
497
|
+
} catch (error) {
|
|
498
|
+
this.logger.error({ error, project }, 'Failed to get corrections by project')
|
|
499
|
+
return []
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
/**
|
|
504
|
+
* Search patterns using semantic search on underlying memories
|
|
505
|
+
*/
|
|
506
|
+
async searchPatterns(
|
|
507
|
+
query: string,
|
|
508
|
+
options?: {
|
|
509
|
+
project?: string
|
|
510
|
+
pattern_type?: 'solution' | 'anti-pattern' | 'best-practice' | 'common-issue'
|
|
511
|
+
limit?: number
|
|
512
|
+
minSimilarity?: number
|
|
513
|
+
}
|
|
514
|
+
): Promise<any[]> {
|
|
515
|
+
try {
|
|
516
|
+
// If no query, return all patterns for the project
|
|
517
|
+
if (!query && options?.project) {
|
|
518
|
+
return this.getPatternsByProject(options.project, {
|
|
519
|
+
pattern_type: options?.pattern_type,
|
|
520
|
+
limit: options?.limit
|
|
521
|
+
})
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
// Generate embedding for semantic search
|
|
525
|
+
const queryEmbedding = await this.embeddings.generateEmbedding(query || 'pattern')
|
|
526
|
+
|
|
527
|
+
let sql = `
|
|
528
|
+
SELECT p.*, m.embedding, m.content as memory_content
|
|
529
|
+
FROM patterns p
|
|
530
|
+
JOIN memories m ON p.memory_id = m.id
|
|
531
|
+
WHERE 1=1
|
|
532
|
+
`
|
|
533
|
+
const params: any[] = []
|
|
534
|
+
|
|
535
|
+
if (options?.project) {
|
|
536
|
+
sql += ' AND p.project = ?'
|
|
537
|
+
params.push(options.project)
|
|
538
|
+
}
|
|
539
|
+
if (options?.pattern_type) {
|
|
540
|
+
sql += ' AND p.pattern_type = ?'
|
|
541
|
+
params.push(options.pattern_type)
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
const stmt = this.db.prepare(sql)
|
|
545
|
+
const rows = stmt.all(...params) as any[]
|
|
546
|
+
|
|
547
|
+
const results = rows.map(row => {
|
|
548
|
+
const embedding = bufferToEmbedding(row.embedding)
|
|
549
|
+
const similarity = cosineSimilarity(queryEmbedding, embedding)
|
|
550
|
+
return {
|
|
551
|
+
id: row.id,
|
|
552
|
+
content: row.description,
|
|
553
|
+
metadata: {
|
|
554
|
+
project: row.project,
|
|
555
|
+
pattern_type: row.pattern_type,
|
|
556
|
+
example: row.example || '',
|
|
557
|
+
confidence: row.confidence,
|
|
558
|
+
context: row.context || '',
|
|
559
|
+
created_at: row.created_at
|
|
560
|
+
},
|
|
561
|
+
similarity
|
|
562
|
+
}
|
|
563
|
+
})
|
|
564
|
+
.filter(r => r.similarity >= (options?.minSimilarity || 0.3))
|
|
565
|
+
.sort((a, b) => b.similarity - a.similarity)
|
|
566
|
+
.slice(0, options?.limit || 10)
|
|
567
|
+
|
|
568
|
+
return results
|
|
569
|
+
} catch (error) {
|
|
570
|
+
this.logger.error({ error, query }, 'Pattern search failed')
|
|
571
|
+
return []
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
/**
|
|
576
|
+
* Search corrections using semantic search on underlying memories
|
|
577
|
+
*/
|
|
578
|
+
async searchCorrections(
|
|
579
|
+
query: string,
|
|
580
|
+
options?: {
|
|
581
|
+
project?: string
|
|
582
|
+
limit?: number
|
|
583
|
+
minSimilarity?: number
|
|
584
|
+
}
|
|
585
|
+
): Promise<any[]> {
|
|
586
|
+
try {
|
|
587
|
+
// If no query, return all corrections for the project
|
|
588
|
+
if (!query && options?.project) {
|
|
589
|
+
return this.getCorrectionsByProject(options.project, options?.limit || 10)
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
const queryEmbedding = await this.embeddings.generateEmbedding(query || 'correction')
|
|
593
|
+
|
|
594
|
+
let sql = `
|
|
595
|
+
SELECT c.*, m.embedding, m.content as memory_content
|
|
596
|
+
FROM corrections c
|
|
597
|
+
JOIN memories m ON c.memory_id = m.id
|
|
598
|
+
WHERE 1=1
|
|
599
|
+
`
|
|
600
|
+
const params: any[] = []
|
|
601
|
+
|
|
602
|
+
if (options?.project) {
|
|
603
|
+
sql += ' AND c.project = ?'
|
|
604
|
+
params.push(options.project)
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
const stmt = this.db.prepare(sql)
|
|
608
|
+
const rows = stmt.all(...params) as any[]
|
|
609
|
+
|
|
610
|
+
const results = rows.map(row => {
|
|
611
|
+
const embedding = bufferToEmbedding(row.embedding)
|
|
612
|
+
const similarity = cosineSimilarity(queryEmbedding, embedding)
|
|
613
|
+
return {
|
|
614
|
+
id: row.id,
|
|
615
|
+
content: row.correction,
|
|
616
|
+
metadata: {
|
|
617
|
+
project: row.project,
|
|
618
|
+
original: row.original,
|
|
619
|
+
correction: row.correction,
|
|
620
|
+
reasoning: row.reasoning,
|
|
621
|
+
context: row.context || '',
|
|
622
|
+
confidence: row.confidence,
|
|
623
|
+
created_at: row.created_at
|
|
624
|
+
},
|
|
625
|
+
similarity
|
|
626
|
+
}
|
|
627
|
+
})
|
|
628
|
+
.filter(r => r.similarity >= (options?.minSimilarity || 0.3))
|
|
629
|
+
.sort((a, b) => b.similarity - a.similarity)
|
|
630
|
+
.slice(0, options?.limit || 10)
|
|
631
|
+
|
|
632
|
+
return results
|
|
633
|
+
} catch (error) {
|
|
634
|
+
this.logger.error({ error, query }, 'Correction search failed')
|
|
635
|
+
return []
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
|
|
329
639
|
/**
|
|
330
640
|
* Convert database row to Decision object
|
|
331
641
|
*/
|
|
@@ -23,6 +23,16 @@ export async function handleAnalyzeDecisionEvolution(
|
|
|
23
23
|
const { topic, project_name, limit } = input
|
|
24
24
|
|
|
25
25
|
const memory = getMemoryService()
|
|
26
|
+
|
|
27
|
+
if (!memory.isChromaDBEnabled()) {
|
|
28
|
+
return ResponseFormatter.text(
|
|
29
|
+
`No decisions found for topic: "${topic}"\n\n` +
|
|
30
|
+
'Note: ChromaDB is not connected. Decision evolution analysis requires ChromaDB for semantic search. ' +
|
|
31
|
+
'Decisions stored via SQLite fallback are available through recall_similar. ' +
|
|
32
|
+
'To enable evolution tracking, start a ChromaDB server or configure persistent mode.'
|
|
33
|
+
)
|
|
34
|
+
}
|
|
35
|
+
|
|
26
36
|
const tracker = new DecisionEvolutionTracker(
|
|
27
37
|
logger,
|
|
28
38
|
memory.chroma.collections,
|
|
@@ -23,6 +23,16 @@ export async function handleDetectTrends(
|
|
|
23
23
|
const { project_name, period_days, min_occurrences, limit } = input
|
|
24
24
|
|
|
25
25
|
const memory = getMemoryService()
|
|
26
|
+
|
|
27
|
+
if (!memory.isChromaDBEnabled()) {
|
|
28
|
+
return ResponseFormatter.text(
|
|
29
|
+
'No decisions found for trend analysis.\n\n' +
|
|
30
|
+
'Note: ChromaDB is not connected. Trend detection requires ChromaDB for semantic search across decisions. ' +
|
|
31
|
+
'Decisions stored via SQLite fallback are available through recall_similar and get_patterns. ' +
|
|
32
|
+
'To enable trend detection, start a ChromaDB server or configure persistent mode.'
|
|
33
|
+
)
|
|
34
|
+
}
|
|
35
|
+
|
|
26
36
|
const detector = new TrendDetector(logger, memory.chroma.collections)
|
|
27
37
|
|
|
28
38
|
const analysis = await detector.detectTrends({
|
|
@@ -23,6 +23,16 @@ export async function handleFindCrossProjectPatterns(
|
|
|
23
23
|
const { min_projects, limit, query } = input
|
|
24
24
|
|
|
25
25
|
const memory = getMemoryService()
|
|
26
|
+
|
|
27
|
+
if (!memory.isChromaDBEnabled()) {
|
|
28
|
+
return ResponseFormatter.text(
|
|
29
|
+
'No cross-project patterns found.\n\n' +
|
|
30
|
+
'Note: ChromaDB is not connected. Cross-project pattern analysis requires ChromaDB for semantic search across decisions. ' +
|
|
31
|
+
'Decisions stored via SQLite fallback are available through recall_similar and get_patterns. ' +
|
|
32
|
+
'To enable cross-project analysis, start a ChromaDB server or configure persistent mode.'
|
|
33
|
+
)
|
|
34
|
+
}
|
|
35
|
+
|
|
26
36
|
const generalizer = new PatternGeneralizer(
|
|
27
37
|
logger,
|
|
28
38
|
memory.chroma.collections,
|
|
@@ -24,6 +24,16 @@ export async function handleGetDecisionTimeline(
|
|
|
24
24
|
const { project_name, topic, time_range, limit } = input
|
|
25
25
|
|
|
26
26
|
const memory = getMemoryService()
|
|
27
|
+
|
|
28
|
+
if (!memory.isChromaDBEnabled()) {
|
|
29
|
+
return ResponseFormatter.text(
|
|
30
|
+
'No decisions found for the specified criteria.\n\n' +
|
|
31
|
+
'Note: ChromaDB is not connected. Decision timeline requires ChromaDB for semantic search. ' +
|
|
32
|
+
'Decisions stored via SQLite fallback are available through recall_similar. ' +
|
|
33
|
+
'To enable timeline view, start a ChromaDB server or configure persistent mode.'
|
|
34
|
+
)
|
|
35
|
+
}
|
|
36
|
+
|
|
27
37
|
const timelineBuilder = new TimelineBuilder(
|
|
28
38
|
logger,
|
|
29
39
|
memory.chroma.collections,
|
|
@@ -23,6 +23,16 @@ export async function handleGetRecommendations(
|
|
|
23
23
|
const { query, project_name, limit } = input
|
|
24
24
|
|
|
25
25
|
const memory = getMemoryService()
|
|
26
|
+
|
|
27
|
+
if (!memory.isChromaDBEnabled()) {
|
|
28
|
+
return ResponseFormatter.text(
|
|
29
|
+
`No recommendations found for: "${query}"\n\n` +
|
|
30
|
+
'Note: ChromaDB is not connected. Recommendations require ChromaDB for semantic search across patterns, corrections, and decisions. ' +
|
|
31
|
+
'Decisions stored via SQLite fallback are available through recall_similar, get_patterns, and get_corrections. ' +
|
|
32
|
+
'To enable recommendations, start a ChromaDB server or configure persistent mode.'
|
|
33
|
+
)
|
|
34
|
+
}
|
|
35
|
+
|
|
26
36
|
const recommender = new Recommender(
|
|
27
37
|
logger,
|
|
28
38
|
memory.chroma.collections,
|
|
@@ -204,7 +204,7 @@ export const GetRecommendationsSchema = z.object({
|
|
|
204
204
|
})
|
|
205
205
|
|
|
206
206
|
export const FindCrossProjectPatternsSchema = z.object({
|
|
207
|
-
min_projects: z.number().int().min(
|
|
207
|
+
min_projects: z.number().int().min(1).max(50).optional().default(2),
|
|
208
208
|
limit: z.number().int().min(1).max(100).optional().default(20),
|
|
209
209
|
query: z.string().optional()
|
|
210
210
|
})
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
import type { Logger } from 'pino'
|
|
7
7
|
import type { ToolResponse } from '@/tools/types'
|
|
8
|
-
import { getKnowledgeGraphService, isServicesInitialized } from '@/server/services'
|
|
8
|
+
import { getKnowledgeGraphService, getMemoryService, isServicesInitialized } from '@/server/services'
|
|
9
9
|
import { ToolValidator } from '@/server/utils/validators'
|
|
10
10
|
import { ResponseFormatter } from '@/server/utils/response-formatter'
|
|
11
11
|
import { ErrorHandler } from '@/server/utils/error-handler'
|
|
@@ -72,6 +72,19 @@ export async function handleSearchKnowledgeGraph(
|
|
|
72
72
|
edgeCount: result.edges.length
|
|
73
73
|
}, 'Knowledge graph search complete')
|
|
74
74
|
|
|
75
|
+
// If graph is empty and ChromaDB is not connected, add guidance
|
|
76
|
+
if (result.nodes.length === 0 && result.edges.length === 0) {
|
|
77
|
+
const memory = getMemoryService()
|
|
78
|
+
if (!memory.isChromaDBEnabled()) {
|
|
79
|
+
return ResponseFormatter.text(
|
|
80
|
+
'Knowledge graph is empty (0 nodes, 0 edges).\n\n' +
|
|
81
|
+
'Note: ChromaDB is not connected. The knowledge graph is populated from stored decisions. ' +
|
|
82
|
+
'New decisions stored via SQLite will populate the graph going forward. ' +
|
|
83
|
+
'To migrate existing decisions into the graph, start a ChromaDB server or configure persistent mode.'
|
|
84
|
+
)
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
75
88
|
return ResponseFormatter.json(response, 'Knowledge Graph Search Results')
|
|
76
89
|
|
|
77
90
|
} catch (error) {
|