claude-brain 0.4.1 → 0.5.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 +1 -1
- package/src/cli/bin.ts +12 -6
- package/src/cli/commands/chroma.ts +146 -16
- package/src/cli/commands/serve.ts +6 -0
- package/src/cli/commands/start.ts +42 -0
- package/src/config/defaults.ts +1 -1
- package/src/config/schema.ts +1 -1
- package/src/memory/chroma/client.ts +5 -1
- package/src/memory/index.ts +91 -0
- package/src/memory/store.ts +136 -4
- package/src/server/handlers/tools/analyze-decision-evolution.ts +76 -6
- package/src/server/handlers/tools/detect-trends.ts +60 -6
- package/src/server/handlers/tools/find-cross-project-patterns.ts +91 -6
- package/src/server/handlers/tools/get-decision-timeline.ts +91 -15
- package/src/server/handlers/tools/get-episode.ts +11 -1
- package/src/server/handlers/tools/get-recommendations.ts +76 -6
- package/src/server/handlers/tools/list-episodes.ts +11 -1
- package/src/server/handlers/tools/rate-memory.ts +8 -2
- package/src/server/handlers/tools/smart-context.ts +23 -1
- package/src/server/handlers/tools/update-progress.ts +18 -13
- package/src/server/handlers/tools/what-if-analysis.ts +58 -7
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.
|
|
1
|
+
0.5.0
|
package/package.json
CHANGED
package/src/cli/bin.ts
CHANGED
|
@@ -26,6 +26,7 @@ function printHelp() {
|
|
|
26
26
|
console.log()
|
|
27
27
|
|
|
28
28
|
const commands = [
|
|
29
|
+
['start', 'Start ChromaDB + MCP server'],
|
|
29
30
|
['serve', 'Start the MCP server (default)'],
|
|
30
31
|
['setup', 'Run interactive setup wizard'],
|
|
31
32
|
['install', 'Register as MCP server in Claude Code'],
|
|
@@ -55,12 +56,11 @@ function printHelp() {
|
|
|
55
56
|
` ${theme.primary('-h'.padEnd(12))} ${dimText('Show help')}`,
|
|
56
57
|
'',
|
|
57
58
|
theme.bold('Examples:'),
|
|
58
|
-
` ${dimText('claude-brain')}
|
|
59
|
-
` ${dimText('claude-brain
|
|
60
|
-
` ${dimText('claude-brain
|
|
61
|
-
` ${dimText('claude-brain
|
|
62
|
-
` ${dimText('claude-brain
|
|
63
|
-
` ${dimText('claude-brain health')} ${dimText('Check system health')}`,
|
|
59
|
+
` ${dimText('claude-brain start')} ${dimText('Start ChromaDB + MCP server')}`,
|
|
60
|
+
` ${dimText('claude-brain start --chroma-only')} ${dimText('Start only ChromaDB')}`,
|
|
61
|
+
` ${dimText('claude-brain setup')} ${dimText('Configure Claude Brain')}`,
|
|
62
|
+
` ${dimText('claude-brain install')} ${dimText('Register with Claude Code')}`,
|
|
63
|
+
` ${dimText('claude-brain health')} ${dimText('Check system health')}`,
|
|
64
64
|
'',
|
|
65
65
|
theme.bold('Environment:'),
|
|
66
66
|
` ${theme.primary('CLAUDE_BRAIN_HOME')} ${dimText('Override home directory (default: ~/.claude-brain/')}`,
|
|
@@ -74,6 +74,12 @@ async function main() {
|
|
|
74
74
|
const command = process.argv[2] || 'serve'
|
|
75
75
|
|
|
76
76
|
switch (command) {
|
|
77
|
+
case 'start': {
|
|
78
|
+
const { runStart } = await import('./commands/start')
|
|
79
|
+
await runStart()
|
|
80
|
+
break
|
|
81
|
+
}
|
|
82
|
+
|
|
77
83
|
case 'serve': {
|
|
78
84
|
const { runServe } = await import('./commands/serve')
|
|
79
85
|
await runServe()
|
|
@@ -25,18 +25,75 @@ function getChromaDataPath(): string {
|
|
|
25
25
|
return paths.chroma
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
/**
|
|
29
|
+
* Find the chroma binary - checks PATH first, then common pip install locations
|
|
30
|
+
*/
|
|
31
|
+
function findChromaBinary(): string | null {
|
|
32
|
+
// Try bare 'chroma' first (on PATH)
|
|
29
33
|
try {
|
|
30
34
|
execSync('chroma --version', { stdio: 'pipe', timeout: 5000 })
|
|
31
|
-
return
|
|
32
|
-
} catch {
|
|
33
|
-
|
|
35
|
+
return 'chroma'
|
|
36
|
+
} catch {}
|
|
37
|
+
|
|
38
|
+
// Search common pip install locations
|
|
39
|
+
const { homedir } = require('os')
|
|
40
|
+
const home = homedir()
|
|
41
|
+
const candidates = [
|
|
42
|
+
join(home, 'Library', 'Python', '3.9', 'bin', 'chroma'),
|
|
43
|
+
join(home, 'Library', 'Python', '3.10', 'bin', 'chroma'),
|
|
44
|
+
join(home, 'Library', 'Python', '3.11', 'bin', 'chroma'),
|
|
45
|
+
join(home, 'Library', 'Python', '3.12', 'bin', 'chroma'),
|
|
46
|
+
join(home, 'Library', 'Python', '3.13', 'bin', 'chroma'),
|
|
47
|
+
join(home, '.local', 'bin', 'chroma'),
|
|
48
|
+
'/usr/local/bin/chroma',
|
|
49
|
+
'/opt/homebrew/bin/chroma',
|
|
50
|
+
]
|
|
51
|
+
|
|
52
|
+
for (const candidate of candidates) {
|
|
53
|
+
try {
|
|
54
|
+
if (existsSync(candidate)) {
|
|
55
|
+
execSync(`"${candidate}" --version`, { stdio: 'pipe', timeout: 5000 })
|
|
56
|
+
return candidate
|
|
57
|
+
}
|
|
58
|
+
} catch {}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Try finding via python -m site
|
|
62
|
+
try {
|
|
63
|
+
const sitePackages = execSync('python3 -c "import site; print(site.getusersitepackages())"', {
|
|
64
|
+
encoding: 'utf-8', stdio: 'pipe', timeout: 5000
|
|
65
|
+
}).trim()
|
|
66
|
+
// User site-packages is like /Users/x/Library/Python/3.9/lib/python/site-packages
|
|
67
|
+
// The bin is at the same level as lib
|
|
68
|
+
const binDir = sitePackages.replace(/\/lib\/.*/, '/bin')
|
|
69
|
+
const chromaPath = join(binDir, 'chroma')
|
|
70
|
+
if (existsSync(chromaPath)) {
|
|
71
|
+
execSync(`"${chromaPath}" --version`, { stdio: 'pipe', timeout: 5000 })
|
|
72
|
+
return chromaPath
|
|
73
|
+
}
|
|
74
|
+
} catch {}
|
|
75
|
+
|
|
76
|
+
return null
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
let _cachedChromaBinary: string | null | undefined = undefined
|
|
80
|
+
|
|
81
|
+
function getChromaBinary(): string | null {
|
|
82
|
+
if (_cachedChromaBinary === undefined) {
|
|
83
|
+
_cachedChromaBinary = findChromaBinary()
|
|
34
84
|
}
|
|
85
|
+
return _cachedChromaBinary
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function isChromaCliInstalled(): boolean {
|
|
89
|
+
return getChromaBinary() !== null
|
|
35
90
|
}
|
|
36
91
|
|
|
37
92
|
function getChromaVersion(): string {
|
|
93
|
+
const binary = getChromaBinary()
|
|
94
|
+
if (!binary) return 'unknown'
|
|
38
95
|
try {
|
|
39
|
-
return execSync(
|
|
96
|
+
return execSync(`"${binary}" --version`, { encoding: 'utf-8', stdio: 'pipe', timeout: 5000 }).trim()
|
|
40
97
|
} catch {
|
|
41
98
|
return 'unknown'
|
|
42
99
|
}
|
|
@@ -75,15 +132,17 @@ function getRunningPid(): number | null {
|
|
|
75
132
|
}
|
|
76
133
|
|
|
77
134
|
function isChromaReachable(): boolean {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
135
|
+
// Try v2 API first (ChromaDB 1.x server), then v1 (older servers)
|
|
136
|
+
for (const apiVersion of ['v2', 'v1']) {
|
|
137
|
+
try {
|
|
138
|
+
execSync(`curl -sf http://localhost:${DEFAULT_PORT}/api/${apiVersion}/heartbeat`, {
|
|
139
|
+
stdio: 'pipe',
|
|
140
|
+
timeout: 3000
|
|
141
|
+
})
|
|
142
|
+
return true
|
|
143
|
+
} catch {}
|
|
86
144
|
}
|
|
145
|
+
return false
|
|
87
146
|
}
|
|
88
147
|
|
|
89
148
|
async function sleep(ms: number): Promise<void> {
|
|
@@ -112,8 +171,9 @@ async function chromaStart(): Promise<void> {
|
|
|
112
171
|
}
|
|
113
172
|
|
|
114
173
|
// Check if chroma CLI is installed
|
|
115
|
-
|
|
116
|
-
|
|
174
|
+
const chromaBinary = getChromaBinary()
|
|
175
|
+
if (!chromaBinary) {
|
|
176
|
+
console.log(errorText('ChromaDB CLI is not installed or not found on PATH.'))
|
|
117
177
|
console.log()
|
|
118
178
|
console.log(dimText('Install it with:'))
|
|
119
179
|
console.log(` ${theme.primary('pip install chromadb')}`)
|
|
@@ -126,12 +186,13 @@ async function chromaStart(): Promise<void> {
|
|
|
126
186
|
|
|
127
187
|
const dataPath = getChromaDataPath()
|
|
128
188
|
|
|
189
|
+
console.log(dimText(`Binary: ${chromaBinary}`))
|
|
129
190
|
console.log(dimText(`Data path: ${dataPath}`))
|
|
130
191
|
console.log(dimText(`Port: ${DEFAULT_PORT}`))
|
|
131
192
|
console.log()
|
|
132
193
|
|
|
133
194
|
// Start ChromaDB server in background
|
|
134
|
-
const child = spawn(
|
|
195
|
+
const child = spawn(chromaBinary, ['run', '--path', dataPath, '--port', DEFAULT_PORT], {
|
|
135
196
|
detached: true,
|
|
136
197
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
137
198
|
env: { ...process.env }
|
|
@@ -375,6 +436,75 @@ function printChromaHelp(): void {
|
|
|
375
436
|
console.log()
|
|
376
437
|
}
|
|
377
438
|
|
|
439
|
+
// ── Auto-start for serve command ──────────────────────────
|
|
440
|
+
|
|
441
|
+
/**
|
|
442
|
+
* Ensures ChromaDB is running before the MCP server starts.
|
|
443
|
+
* Returns true if ChromaDB is reachable after this call, false otherwise.
|
|
444
|
+
* Designed to be called from serve.ts — does not call process.exit().
|
|
445
|
+
*/
|
|
446
|
+
export async function ensureChromaRunning(options?: { silent?: boolean }): Promise<boolean> {
|
|
447
|
+
const log = options?.silent ? () => {} : console.error.bind(console)
|
|
448
|
+
|
|
449
|
+
// Already running? Great.
|
|
450
|
+
if (isChromaReachable()) {
|
|
451
|
+
return true
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// Already managed by us but not responding? Clean up stale PID.
|
|
455
|
+
const stalePid = getRunningPid()
|
|
456
|
+
if (stalePid) {
|
|
457
|
+
try {
|
|
458
|
+
process.kill(stalePid, 'SIGTERM')
|
|
459
|
+
await sleep(1000)
|
|
460
|
+
} catch {}
|
|
461
|
+
try { unlinkSync(getPidFilePath()) } catch {}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
const chromaBinary = getChromaBinary()
|
|
465
|
+
if (!chromaBinary) {
|
|
466
|
+
log('[ChromaDB] Not installed — running with SQLite fallback.')
|
|
467
|
+
log('[ChromaDB] Install with: claude-brain chroma install')
|
|
468
|
+
return false
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
const dataPath = getChromaDataPath()
|
|
472
|
+
log(`[ChromaDB] Starting server (port ${DEFAULT_PORT})...`)
|
|
473
|
+
|
|
474
|
+
const child = spawn(chromaBinary, ['run', '--path', dataPath, '--port', DEFAULT_PORT], {
|
|
475
|
+
detached: true,
|
|
476
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
477
|
+
env: { ...process.env }
|
|
478
|
+
})
|
|
479
|
+
|
|
480
|
+
const pid = child.pid
|
|
481
|
+
if (!pid) {
|
|
482
|
+
log('[ChromaDB] Failed to spawn server process.')
|
|
483
|
+
return false
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
child.unref()
|
|
487
|
+
writeFileSync(getPidFilePath(), String(pid), 'utf-8')
|
|
488
|
+
|
|
489
|
+
// Wait for server to become reachable (up to 15 seconds)
|
|
490
|
+
for (let i = 0; i < 30; i++) {
|
|
491
|
+
await sleep(500)
|
|
492
|
+
if (isChromaReachable()) {
|
|
493
|
+
log(`[ChromaDB] Server started (PID: ${pid})`)
|
|
494
|
+
return true
|
|
495
|
+
}
|
|
496
|
+
// Check if process died
|
|
497
|
+
try {
|
|
498
|
+
process.kill(pid, 0)
|
|
499
|
+
} catch {
|
|
500
|
+
break
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
log('[ChromaDB] Server started but not yet responding — will retry connection during initialization.')
|
|
505
|
+
return isChromaReachable()
|
|
506
|
+
}
|
|
507
|
+
|
|
378
508
|
// ── Entry Point ───────────────────────────────────────────
|
|
379
509
|
|
|
380
510
|
export async function runChroma(): Promise<void> {
|
|
@@ -5,6 +5,7 @@ import { ClaudeBrainMCPServer } from '@/server'
|
|
|
5
5
|
import { initializeServices, shutdownServices, getVaultService, getMemoryService } from '@/server/services'
|
|
6
6
|
import { createOrchestrator, type Orchestrator } from '@/orchestrator'
|
|
7
7
|
import { ensureHomeDirectory } from '@/cli/auto-setup'
|
|
8
|
+
import { ensureChromaRunning } from '@/cli/commands/chroma'
|
|
8
9
|
|
|
9
10
|
const BANNER = `
|
|
10
11
|
╔═══════════════════════════════════════════════════════╗
|
|
@@ -42,6 +43,11 @@ export async function runServe() {
|
|
|
42
43
|
cacheSize: config.cacheSize
|
|
43
44
|
}, 'Configuration loaded')
|
|
44
45
|
|
|
46
|
+
// Auto-start ChromaDB if not already running
|
|
47
|
+
mainLogger.info('Ensuring ChromaDB is available...')
|
|
48
|
+
const chromaReady = await ensureChromaRunning({ silent: process.env.NODE_ENV === 'production' })
|
|
49
|
+
mainLogger.info({ chromaReady }, chromaReady ? 'ChromaDB is ready' : 'ChromaDB not available, using SQLite fallback')
|
|
50
|
+
|
|
45
51
|
mainLogger.info('Initializing services...')
|
|
46
52
|
await initializeServices(config, logger)
|
|
47
53
|
mainLogger.info('Services initialized successfully')
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Start Command
|
|
3
|
+
* Starts ChromaDB + MCP server together, or just ChromaDB with --chroma-only
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* claude-brain start Start ChromaDB + MCP server
|
|
7
|
+
* claude-brain start --chroma-only Start only ChromaDB server
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
heading, successText, warningText, dimText,
|
|
12
|
+
} from '@/cli/ui/index.js'
|
|
13
|
+
import { ensureChromaRunning } from '@/cli/commands/chroma'
|
|
14
|
+
|
|
15
|
+
export async function runStart(): Promise<void> {
|
|
16
|
+
const chromaOnly = process.argv.includes('--chroma-only')
|
|
17
|
+
|
|
18
|
+
if (chromaOnly) {
|
|
19
|
+
console.log()
|
|
20
|
+
console.log(heading('Starting ChromaDB'))
|
|
21
|
+
console.log()
|
|
22
|
+
|
|
23
|
+
const chromaReady = await ensureChromaRunning()
|
|
24
|
+
console.log()
|
|
25
|
+
|
|
26
|
+
if (chromaReady) {
|
|
27
|
+
console.log(successText('ChromaDB is running and ready.'))
|
|
28
|
+
} else {
|
|
29
|
+
console.log(warningText('ChromaDB could not be started. Check installation with: claude-brain chroma status'))
|
|
30
|
+
}
|
|
31
|
+
console.log()
|
|
32
|
+
return
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Full start: ChromaDB + MCP server
|
|
36
|
+
// serve.ts already calls ensureChromaRunning(), so just delegate
|
|
37
|
+
console.error(dimText('Starting ChromaDB + MCP server...'))
|
|
38
|
+
console.error()
|
|
39
|
+
|
|
40
|
+
const { runServe } = await import('./serve')
|
|
41
|
+
await runServe()
|
|
42
|
+
}
|
package/src/config/defaults.ts
CHANGED
|
@@ -3,7 +3,7 @@ import type { PartialConfig } from './schema'
|
|
|
3
3
|
/** Default configuration values for Claude Brain */
|
|
4
4
|
export const defaultConfig: PartialConfig = {
|
|
5
5
|
serverName: 'claude-brain',
|
|
6
|
-
serverVersion: '0.
|
|
6
|
+
serverVersion: '0.5.0',
|
|
7
7
|
logLevel: 'info',
|
|
8
8
|
logFilePath: './logs/claude-brain.log',
|
|
9
9
|
dbPath: './data/memory.db',
|
package/src/config/schema.ts
CHANGED
|
@@ -196,7 +196,7 @@ export const ConfigSchema = z.object({
|
|
|
196
196
|
serverName: z.string().default('claude-brain'),
|
|
197
197
|
|
|
198
198
|
/** Server version in semver format */
|
|
199
|
-
serverVersion: z.string().regex(/^\d+\.\d+\.\d+$/, 'Version must be semver format').default('0.
|
|
199
|
+
serverVersion: z.string().regex(/^\d+\.\d+\.\d+$/, 'Version must be semver format').default('0.5.0'),
|
|
200
200
|
|
|
201
201
|
/** Logging level */
|
|
202
202
|
logLevel: LogLevelSchema.default('info'),
|
|
@@ -29,8 +29,12 @@ export class ChromaClientManager {
|
|
|
29
29
|
|
|
30
30
|
switch (this.config.mode) {
|
|
31
31
|
case 'persistent':
|
|
32
|
+
// ChromaDB v3.x no longer supports embedded persistent mode via 'path'.
|
|
33
|
+
// Fall through to client-server connecting to localhost.
|
|
34
|
+
this.logger.info('Persistent mode: connecting to ChromaDB server on localhost (v3.x requires a running server)')
|
|
32
35
|
this.client = new ChromaClient({
|
|
33
|
-
|
|
36
|
+
host: this.config.host || 'localhost',
|
|
37
|
+
port: this.config.port || 8000,
|
|
34
38
|
...authConfig
|
|
35
39
|
})
|
|
36
40
|
break
|
package/src/memory/index.ts
CHANGED
|
@@ -381,6 +381,97 @@ export class MemoryManager {
|
|
|
381
381
|
return this.store.searchCorrections('', { limit: options?.limit || 10 })
|
|
382
382
|
}
|
|
383
383
|
|
|
384
|
+
/**
|
|
385
|
+
* Fetch all decisions with content — routes to ChromaDB or SQLite
|
|
386
|
+
* Used by analytical tools that need bulk access to decision data
|
|
387
|
+
*/
|
|
388
|
+
async fetchAllDecisions(project?: string): Promise<any[]> {
|
|
389
|
+
if (this.useChromaDB) {
|
|
390
|
+
try {
|
|
391
|
+
const results = await this.chroma.collections.decisions.get({
|
|
392
|
+
where: project ? { project } : undefined
|
|
393
|
+
})
|
|
394
|
+
if (results && results.ids) {
|
|
395
|
+
return results.ids.map((id: string, i: number) => ({
|
|
396
|
+
id,
|
|
397
|
+
content: results.documents?.[i] || '',
|
|
398
|
+
date: results.metadatas?.[i]?.created_at || new Date().toISOString(),
|
|
399
|
+
project: results.metadatas?.[i]?.project || project || 'unknown',
|
|
400
|
+
context: results.metadatas?.[i]?.context || '',
|
|
401
|
+
decision: results.metadatas?.[i]?.decision || results.documents?.[i] || '',
|
|
402
|
+
reasoning: results.metadatas?.[i]?.reasoning || '',
|
|
403
|
+
alternatives: results.metadatas?.[i]?.alternatives_considered || '',
|
|
404
|
+
tags: results.metadatas?.[i]?.tags || []
|
|
405
|
+
}))
|
|
406
|
+
}
|
|
407
|
+
return []
|
|
408
|
+
} catch (error) {
|
|
409
|
+
this.logger.warn({ error }, 'ChromaDB fetchAllDecisions failed, falling back to SQLite')
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
return this.store.getAllDecisionsWithContent(project)
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* Fetch all patterns with content — routes to ChromaDB or SQLite
|
|
417
|
+
*/
|
|
418
|
+
async fetchAllPatterns(project?: string): Promise<any[]> {
|
|
419
|
+
if (this.useChromaDB) {
|
|
420
|
+
try {
|
|
421
|
+
const results = await this.chroma.collections.patterns.get({
|
|
422
|
+
where: project ? { project } : undefined
|
|
423
|
+
})
|
|
424
|
+
if (results && results.ids) {
|
|
425
|
+
return results.ids.map((id: string, i: number) => ({
|
|
426
|
+
id,
|
|
427
|
+
content: results.documents?.[i] || '',
|
|
428
|
+
date: results.metadatas?.[i]?.created_at || new Date().toISOString(),
|
|
429
|
+
project: results.metadatas?.[i]?.project || project || 'unknown',
|
|
430
|
+
pattern_type: results.metadatas?.[i]?.pattern_type || '',
|
|
431
|
+
description: results.metadatas?.[i]?.description || results.documents?.[i] || '',
|
|
432
|
+
example: results.metadatas?.[i]?.example || '',
|
|
433
|
+
confidence: results.metadatas?.[i]?.confidence || 0,
|
|
434
|
+
context: results.metadatas?.[i]?.context || ''
|
|
435
|
+
}))
|
|
436
|
+
}
|
|
437
|
+
return []
|
|
438
|
+
} catch (error) {
|
|
439
|
+
this.logger.warn({ error }, 'ChromaDB fetchAllPatterns failed, falling back to SQLite')
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
return this.store.getAllPatternsWithContent(project)
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* Fetch all corrections with content — routes to ChromaDB or SQLite
|
|
447
|
+
*/
|
|
448
|
+
async fetchAllCorrections(project?: string): Promise<any[]> {
|
|
449
|
+
if (this.useChromaDB) {
|
|
450
|
+
try {
|
|
451
|
+
const results = await this.chroma.collections.corrections.get({
|
|
452
|
+
where: project ? { project } : undefined
|
|
453
|
+
})
|
|
454
|
+
if (results && results.ids) {
|
|
455
|
+
return results.ids.map((id: string, i: number) => ({
|
|
456
|
+
id,
|
|
457
|
+
content: results.documents?.[i] || '',
|
|
458
|
+
date: results.metadatas?.[i]?.created_at || new Date().toISOString(),
|
|
459
|
+
project: results.metadatas?.[i]?.project || project || 'unknown',
|
|
460
|
+
original: results.metadatas?.[i]?.original || '',
|
|
461
|
+
correction: results.metadatas?.[i]?.correction || results.documents?.[i] || '',
|
|
462
|
+
reasoning: results.metadatas?.[i]?.reasoning || '',
|
|
463
|
+
context: results.metadatas?.[i]?.context || '',
|
|
464
|
+
confidence: results.metadatas?.[i]?.confidence || 0
|
|
465
|
+
}))
|
|
466
|
+
}
|
|
467
|
+
return []
|
|
468
|
+
} catch (error) {
|
|
469
|
+
this.logger.warn({ error }, 'ChromaDB fetchAllCorrections failed, falling back to SQLite')
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
return this.store.getAllCorrectionsWithContent(project)
|
|
473
|
+
}
|
|
474
|
+
|
|
384
475
|
/**
|
|
385
476
|
* Search patterns by query — routes to ChromaDB or SQLite
|
|
386
477
|
*/
|
package/src/memory/store.ts
CHANGED
|
@@ -459,10 +459,14 @@ export class MemoryStore {
|
|
|
459
459
|
metadata: {
|
|
460
460
|
project: row.project,
|
|
461
461
|
pattern_type: row.pattern_type,
|
|
462
|
+
description: row.description,
|
|
462
463
|
example: row.example || '',
|
|
463
464
|
confidence: row.confidence,
|
|
464
465
|
context: row.context || '',
|
|
465
|
-
created_at: row.created_at
|
|
466
|
+
created_at: row.created_at,
|
|
467
|
+
first_seen: row.created_at,
|
|
468
|
+
last_seen: row.created_at,
|
|
469
|
+
occurrences: 1
|
|
466
470
|
}
|
|
467
471
|
}))
|
|
468
472
|
} catch (error) {
|
|
@@ -484,14 +488,19 @@ export class MemoryStore {
|
|
|
484
488
|
return rows.map(row => ({
|
|
485
489
|
id: row.id,
|
|
486
490
|
correction: row.correction,
|
|
491
|
+
description: row.correction,
|
|
487
492
|
metadata: {
|
|
488
493
|
project: row.project,
|
|
489
494
|
original: row.original,
|
|
490
495
|
correction: row.correction,
|
|
496
|
+
description: row.correction,
|
|
491
497
|
reasoning: row.reasoning,
|
|
492
498
|
context: row.context || '',
|
|
493
499
|
confidence: row.confidence,
|
|
494
|
-
created_at: row.created_at
|
|
500
|
+
created_at: row.created_at,
|
|
501
|
+
first_seen: row.created_at,
|
|
502
|
+
last_seen: row.created_at,
|
|
503
|
+
occurrences: 1
|
|
495
504
|
}
|
|
496
505
|
}))
|
|
497
506
|
} catch (error) {
|
|
@@ -553,10 +562,14 @@ export class MemoryStore {
|
|
|
553
562
|
metadata: {
|
|
554
563
|
project: row.project,
|
|
555
564
|
pattern_type: row.pattern_type,
|
|
565
|
+
description: row.description,
|
|
556
566
|
example: row.example || '',
|
|
557
567
|
confidence: row.confidence,
|
|
558
568
|
context: row.context || '',
|
|
559
|
-
created_at: row.created_at
|
|
569
|
+
created_at: row.created_at,
|
|
570
|
+
first_seen: row.created_at,
|
|
571
|
+
last_seen: row.created_at,
|
|
572
|
+
occurrences: 1
|
|
560
573
|
},
|
|
561
574
|
similarity
|
|
562
575
|
}
|
|
@@ -613,14 +626,19 @@ export class MemoryStore {
|
|
|
613
626
|
return {
|
|
614
627
|
id: row.id,
|
|
615
628
|
content: row.correction,
|
|
629
|
+
description: row.correction,
|
|
616
630
|
metadata: {
|
|
617
631
|
project: row.project,
|
|
618
632
|
original: row.original,
|
|
619
633
|
correction: row.correction,
|
|
634
|
+
description: row.correction,
|
|
620
635
|
reasoning: row.reasoning,
|
|
621
636
|
context: row.context || '',
|
|
622
637
|
confidence: row.confidence,
|
|
623
|
-
created_at: row.created_at
|
|
638
|
+
created_at: row.created_at,
|
|
639
|
+
first_seen: row.created_at,
|
|
640
|
+
last_seen: row.created_at,
|
|
641
|
+
occurrences: 1
|
|
624
642
|
},
|
|
625
643
|
similarity
|
|
626
644
|
}
|
|
@@ -636,6 +654,120 @@ export class MemoryStore {
|
|
|
636
654
|
}
|
|
637
655
|
}
|
|
638
656
|
|
|
657
|
+
/**
|
|
658
|
+
* Get all decisions with full content (for analytical tools without ChromaDB)
|
|
659
|
+
*/
|
|
660
|
+
getAllDecisionsWithContent(project?: string): any[] {
|
|
661
|
+
try {
|
|
662
|
+
let sql = `
|
|
663
|
+
SELECT d.id, d.context, d.decision, d.reasoning, d.alternatives, d.tags,
|
|
664
|
+
m.content, m.project, m.created_at, m.metadata
|
|
665
|
+
FROM decisions d
|
|
666
|
+
JOIN memories m ON d.memory_id = m.id
|
|
667
|
+
`
|
|
668
|
+
const params: any[] = []
|
|
669
|
+
if (project) {
|
|
670
|
+
sql += ' WHERE m.project = ?'
|
|
671
|
+
params.push(project)
|
|
672
|
+
}
|
|
673
|
+
sql += ' ORDER BY m.created_at DESC'
|
|
674
|
+
|
|
675
|
+
const stmt = this.db.prepare(sql)
|
|
676
|
+
const rows = stmt.all(...params) as any[]
|
|
677
|
+
|
|
678
|
+
return rows.map(row => ({
|
|
679
|
+
id: row.id,
|
|
680
|
+
content: row.content,
|
|
681
|
+
date: row.created_at,
|
|
682
|
+
project: row.project,
|
|
683
|
+
context: row.context,
|
|
684
|
+
decision: row.decision,
|
|
685
|
+
reasoning: row.reasoning,
|
|
686
|
+
alternatives: row.alternatives,
|
|
687
|
+
tags: row.tags ? JSON.parse(row.tags) : []
|
|
688
|
+
}))
|
|
689
|
+
} catch (error) {
|
|
690
|
+
this.logger.error({ error, project }, 'Failed to get all decisions with content')
|
|
691
|
+
return []
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
/**
|
|
696
|
+
* Get all patterns with full content (for analytical tools without ChromaDB)
|
|
697
|
+
*/
|
|
698
|
+
getAllPatternsWithContent(project?: string): any[] {
|
|
699
|
+
try {
|
|
700
|
+
let sql = `
|
|
701
|
+
SELECT p.id, p.pattern_type, p.description, p.example, p.confidence, p.context,
|
|
702
|
+
m.content, m.project, m.created_at
|
|
703
|
+
FROM patterns p
|
|
704
|
+
JOIN memories m ON p.memory_id = m.id
|
|
705
|
+
`
|
|
706
|
+
const params: any[] = []
|
|
707
|
+
if (project) {
|
|
708
|
+
sql += ' WHERE p.project = ?'
|
|
709
|
+
params.push(project)
|
|
710
|
+
}
|
|
711
|
+
sql += ' ORDER BY m.created_at DESC'
|
|
712
|
+
|
|
713
|
+
const stmt = this.db.prepare(sql)
|
|
714
|
+
const rows = stmt.all(...params) as any[]
|
|
715
|
+
|
|
716
|
+
return rows.map(row => ({
|
|
717
|
+
id: row.id,
|
|
718
|
+
content: row.content,
|
|
719
|
+
date: row.created_at,
|
|
720
|
+
project: row.project,
|
|
721
|
+
pattern_type: row.pattern_type,
|
|
722
|
+
description: row.description,
|
|
723
|
+
example: row.example,
|
|
724
|
+
confidence: row.confidence,
|
|
725
|
+
context: row.context
|
|
726
|
+
}))
|
|
727
|
+
} catch (error) {
|
|
728
|
+
this.logger.error({ error, project }, 'Failed to get all patterns with content')
|
|
729
|
+
return []
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
/**
|
|
734
|
+
* Get all corrections with full content (for analytical tools without ChromaDB)
|
|
735
|
+
*/
|
|
736
|
+
getAllCorrectionsWithContent(project?: string): any[] {
|
|
737
|
+
try {
|
|
738
|
+
let sql = `
|
|
739
|
+
SELECT c.id, c.original, c.correction, c.reasoning, c.context as correction_context,
|
|
740
|
+
c.confidence, m.content, m.project, m.created_at
|
|
741
|
+
FROM corrections c
|
|
742
|
+
JOIN memories m ON c.memory_id = m.id
|
|
743
|
+
`
|
|
744
|
+
const params: any[] = []
|
|
745
|
+
if (project) {
|
|
746
|
+
sql += ' WHERE c.project = ?'
|
|
747
|
+
params.push(project)
|
|
748
|
+
}
|
|
749
|
+
sql += ' ORDER BY m.created_at DESC'
|
|
750
|
+
|
|
751
|
+
const stmt = this.db.prepare(sql)
|
|
752
|
+
const rows = stmt.all(...params) as any[]
|
|
753
|
+
|
|
754
|
+
return rows.map(row => ({
|
|
755
|
+
id: row.id,
|
|
756
|
+
content: row.content,
|
|
757
|
+
date: row.created_at,
|
|
758
|
+
project: row.project,
|
|
759
|
+
original: row.original,
|
|
760
|
+
correction: row.correction,
|
|
761
|
+
reasoning: row.reasoning,
|
|
762
|
+
context: row.correction_context,
|
|
763
|
+
confidence: row.confidence
|
|
764
|
+
}))
|
|
765
|
+
} catch (error) {
|
|
766
|
+
this.logger.error({ error, project }, 'Failed to get all corrections with content')
|
|
767
|
+
return []
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
|
|
639
771
|
/**
|
|
640
772
|
* Convert database row to Decision object
|
|
641
773
|
*/
|