claude-brain 0.4.1 → 0.5.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/bin.ts +12 -6
- package/src/cli/commands/chroma.ts +182 -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 +6 -2
- package/src/memory/chroma/index.ts +1 -1
- package/src/memory/chroma/store.ts +27 -8
- 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.1
|
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,111 @@ 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
|
+
const isWindows = process.platform === 'win32'
|
|
33
|
+
const chromaName = isWindows ? 'chroma.exe' : 'chroma'
|
|
34
|
+
|
|
35
|
+
// Try bare 'chroma' first (on PATH)
|
|
29
36
|
try {
|
|
30
37
|
execSync('chroma --version', { stdio: 'pipe', timeout: 5000 })
|
|
31
|
-
return
|
|
32
|
-
} catch {
|
|
33
|
-
|
|
38
|
+
return 'chroma'
|
|
39
|
+
} catch {}
|
|
40
|
+
|
|
41
|
+
const { homedir } = require('os')
|
|
42
|
+
const home = homedir()
|
|
43
|
+
|
|
44
|
+
// Platform-specific search paths
|
|
45
|
+
const candidates: string[] = isWindows
|
|
46
|
+
? [
|
|
47
|
+
// Windows pip install locations
|
|
48
|
+
join(home, 'AppData', 'Local', 'Programs', 'Python', 'Python39', 'Scripts', chromaName),
|
|
49
|
+
join(home, 'AppData', 'Local', 'Programs', 'Python', 'Python310', 'Scripts', chromaName),
|
|
50
|
+
join(home, 'AppData', 'Local', 'Programs', 'Python', 'Python311', 'Scripts', chromaName),
|
|
51
|
+
join(home, 'AppData', 'Local', 'Programs', 'Python', 'Python312', 'Scripts', chromaName),
|
|
52
|
+
join(home, 'AppData', 'Local', 'Programs', 'Python', 'Python313', 'Scripts', chromaName),
|
|
53
|
+
// Windows user pip install (pip install --user)
|
|
54
|
+
join(home, 'AppData', 'Roaming', 'Python', 'Python39', 'Scripts', chromaName),
|
|
55
|
+
join(home, 'AppData', 'Roaming', 'Python', 'Python310', 'Scripts', chromaName),
|
|
56
|
+
join(home, 'AppData', 'Roaming', 'Python', 'Python311', 'Scripts', chromaName),
|
|
57
|
+
join(home, 'AppData', 'Roaming', 'Python', 'Python312', 'Scripts', chromaName),
|
|
58
|
+
join(home, 'AppData', 'Roaming', 'Python', 'Python313', 'Scripts', chromaName),
|
|
59
|
+
// Scoop / Chocolatey / winget
|
|
60
|
+
join(home, 'scoop', 'shims', chromaName),
|
|
61
|
+
'C:\\Python39\\Scripts\\' + chromaName,
|
|
62
|
+
'C:\\Python310\\Scripts\\' + chromaName,
|
|
63
|
+
'C:\\Python311\\Scripts\\' + chromaName,
|
|
64
|
+
'C:\\Python312\\Scripts\\' + chromaName,
|
|
65
|
+
'C:\\Python313\\Scripts\\' + chromaName,
|
|
66
|
+
]
|
|
67
|
+
: [
|
|
68
|
+
// macOS pip install locations
|
|
69
|
+
join(home, 'Library', 'Python', '3.9', 'bin', chromaName),
|
|
70
|
+
join(home, 'Library', 'Python', '3.10', 'bin', chromaName),
|
|
71
|
+
join(home, 'Library', 'Python', '3.11', 'bin', chromaName),
|
|
72
|
+
join(home, 'Library', 'Python', '3.12', 'bin', chromaName),
|
|
73
|
+
join(home, 'Library', 'Python', '3.13', 'bin', chromaName),
|
|
74
|
+
// Linux pip install locations
|
|
75
|
+
join(home, '.local', 'bin', chromaName),
|
|
76
|
+
'/usr/local/bin/' + chromaName,
|
|
77
|
+
'/opt/homebrew/bin/' + chromaName,
|
|
78
|
+
]
|
|
79
|
+
|
|
80
|
+
for (const candidate of candidates) {
|
|
81
|
+
try {
|
|
82
|
+
if (existsSync(candidate)) {
|
|
83
|
+
execSync(`"${candidate}" --version`, { stdio: 'pipe', timeout: 5000 })
|
|
84
|
+
return candidate
|
|
85
|
+
}
|
|
86
|
+
} catch {}
|
|
34
87
|
}
|
|
88
|
+
|
|
89
|
+
// Try finding via python -m site (works on all platforms)
|
|
90
|
+
const pythonCmd = isWindows ? 'python' : 'python3'
|
|
91
|
+
try {
|
|
92
|
+
const sitePackages = execSync(`${pythonCmd} -c "import site; print(site.getusersitepackages())"`, {
|
|
93
|
+
encoding: 'utf-8', stdio: 'pipe', timeout: 5000
|
|
94
|
+
}).trim()
|
|
95
|
+
|
|
96
|
+
let binDir: string
|
|
97
|
+
if (isWindows) {
|
|
98
|
+
// Windows: C:\Users\x\AppData\Roaming\Python\Python311\site-packages → Scripts
|
|
99
|
+
binDir = sitePackages.replace(/[\\\/]site-packages$/, '\\Scripts')
|
|
100
|
+
} else {
|
|
101
|
+
// Unix: /Users/x/Library/Python/3.9/lib/python/site-packages → bin
|
|
102
|
+
binDir = sitePackages.replace(/\/lib\/.*/, '/bin')
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const chromaPath = join(binDir, chromaName)
|
|
106
|
+
if (existsSync(chromaPath)) {
|
|
107
|
+
execSync(`"${chromaPath}" --version`, { stdio: 'pipe', timeout: 5000 })
|
|
108
|
+
return chromaPath
|
|
109
|
+
}
|
|
110
|
+
} catch {}
|
|
111
|
+
|
|
112
|
+
return null
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
let _cachedChromaBinary: string | null | undefined = undefined
|
|
116
|
+
|
|
117
|
+
function getChromaBinary(): string | null {
|
|
118
|
+
if (_cachedChromaBinary === undefined) {
|
|
119
|
+
_cachedChromaBinary = findChromaBinary()
|
|
120
|
+
}
|
|
121
|
+
return _cachedChromaBinary
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function isChromaCliInstalled(): boolean {
|
|
125
|
+
return getChromaBinary() !== null
|
|
35
126
|
}
|
|
36
127
|
|
|
37
128
|
function getChromaVersion(): string {
|
|
129
|
+
const binary = getChromaBinary()
|
|
130
|
+
if (!binary) return 'unknown'
|
|
38
131
|
try {
|
|
39
|
-
return execSync(
|
|
132
|
+
return execSync(`"${binary}" --version`, { encoding: 'utf-8', stdio: 'pipe', timeout: 5000 }).trim()
|
|
40
133
|
} catch {
|
|
41
134
|
return 'unknown'
|
|
42
135
|
}
|
|
@@ -75,15 +168,17 @@ function getRunningPid(): number | null {
|
|
|
75
168
|
}
|
|
76
169
|
|
|
77
170
|
function isChromaReachable(): boolean {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
171
|
+
// Try v2 API first (ChromaDB 1.x server), then v1 (older servers)
|
|
172
|
+
for (const apiVersion of ['v2', 'v1']) {
|
|
173
|
+
try {
|
|
174
|
+
execSync(`curl -sf http://localhost:${DEFAULT_PORT}/api/${apiVersion}/heartbeat`, {
|
|
175
|
+
stdio: 'pipe',
|
|
176
|
+
timeout: 3000
|
|
177
|
+
})
|
|
178
|
+
return true
|
|
179
|
+
} catch {}
|
|
86
180
|
}
|
|
181
|
+
return false
|
|
87
182
|
}
|
|
88
183
|
|
|
89
184
|
async function sleep(ms: number): Promise<void> {
|
|
@@ -112,8 +207,9 @@ async function chromaStart(): Promise<void> {
|
|
|
112
207
|
}
|
|
113
208
|
|
|
114
209
|
// Check if chroma CLI is installed
|
|
115
|
-
|
|
116
|
-
|
|
210
|
+
const chromaBinary = getChromaBinary()
|
|
211
|
+
if (!chromaBinary) {
|
|
212
|
+
console.log(errorText('ChromaDB CLI is not installed or not found on PATH.'))
|
|
117
213
|
console.log()
|
|
118
214
|
console.log(dimText('Install it with:'))
|
|
119
215
|
console.log(` ${theme.primary('pip install chromadb')}`)
|
|
@@ -126,12 +222,13 @@ async function chromaStart(): Promise<void> {
|
|
|
126
222
|
|
|
127
223
|
const dataPath = getChromaDataPath()
|
|
128
224
|
|
|
225
|
+
console.log(dimText(`Binary: ${chromaBinary}`))
|
|
129
226
|
console.log(dimText(`Data path: ${dataPath}`))
|
|
130
227
|
console.log(dimText(`Port: ${DEFAULT_PORT}`))
|
|
131
228
|
console.log()
|
|
132
229
|
|
|
133
230
|
// Start ChromaDB server in background
|
|
134
|
-
const child = spawn(
|
|
231
|
+
const child = spawn(chromaBinary, ['run', '--path', dataPath, '--port', DEFAULT_PORT], {
|
|
135
232
|
detached: true,
|
|
136
233
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
137
234
|
env: { ...process.env }
|
|
@@ -375,6 +472,75 @@ function printChromaHelp(): void {
|
|
|
375
472
|
console.log()
|
|
376
473
|
}
|
|
377
474
|
|
|
475
|
+
// ── Auto-start for serve command ──────────────────────────
|
|
476
|
+
|
|
477
|
+
/**
|
|
478
|
+
* Ensures ChromaDB is running before the MCP server starts.
|
|
479
|
+
* Returns true if ChromaDB is reachable after this call, false otherwise.
|
|
480
|
+
* Designed to be called from serve.ts — does not call process.exit().
|
|
481
|
+
*/
|
|
482
|
+
export async function ensureChromaRunning(options?: { silent?: boolean }): Promise<boolean> {
|
|
483
|
+
const log = options?.silent ? () => {} : console.error.bind(console)
|
|
484
|
+
|
|
485
|
+
// Already running? Great.
|
|
486
|
+
if (isChromaReachable()) {
|
|
487
|
+
return true
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
// Already managed by us but not responding? Clean up stale PID.
|
|
491
|
+
const stalePid = getRunningPid()
|
|
492
|
+
if (stalePid) {
|
|
493
|
+
try {
|
|
494
|
+
process.kill(stalePid, 'SIGTERM')
|
|
495
|
+
await sleep(1000)
|
|
496
|
+
} catch {}
|
|
497
|
+
try { unlinkSync(getPidFilePath()) } catch {}
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
const chromaBinary = getChromaBinary()
|
|
501
|
+
if (!chromaBinary) {
|
|
502
|
+
log('[ChromaDB] Not installed — running with SQLite fallback.')
|
|
503
|
+
log('[ChromaDB] Install with: claude-brain chroma install')
|
|
504
|
+
return false
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
const dataPath = getChromaDataPath()
|
|
508
|
+
log(`[ChromaDB] Starting server (port ${DEFAULT_PORT})...`)
|
|
509
|
+
|
|
510
|
+
const child = spawn(chromaBinary, ['run', '--path', dataPath, '--port', DEFAULT_PORT], {
|
|
511
|
+
detached: true,
|
|
512
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
513
|
+
env: { ...process.env }
|
|
514
|
+
})
|
|
515
|
+
|
|
516
|
+
const pid = child.pid
|
|
517
|
+
if (!pid) {
|
|
518
|
+
log('[ChromaDB] Failed to spawn server process.')
|
|
519
|
+
return false
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
child.unref()
|
|
523
|
+
writeFileSync(getPidFilePath(), String(pid), 'utf-8')
|
|
524
|
+
|
|
525
|
+
// Wait for server to become reachable (up to 15 seconds)
|
|
526
|
+
for (let i = 0; i < 30; i++) {
|
|
527
|
+
await sleep(500)
|
|
528
|
+
if (isChromaReachable()) {
|
|
529
|
+
log(`[ChromaDB] Server started (PID: ${pid})`)
|
|
530
|
+
return true
|
|
531
|
+
}
|
|
532
|
+
// Check if process died
|
|
533
|
+
try {
|
|
534
|
+
process.kill(pid, 0)
|
|
535
|
+
} catch {
|
|
536
|
+
break
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
log('[ChromaDB] Server started but not yet responding — will retry connection during initialization.')
|
|
541
|
+
return isChromaReachable()
|
|
542
|
+
}
|
|
543
|
+
|
|
378
544
|
// ── Entry Point ───────────────────────────────────────────
|
|
379
545
|
|
|
380
546
|
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.1',
|
|
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.1'),
|
|
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
|
|
@@ -88,7 +92,7 @@ export class ChromaClientManager {
|
|
|
88
92
|
}
|
|
89
93
|
|
|
90
94
|
} catch (error) {
|
|
91
|
-
this.logger.
|
|
95
|
+
this.logger.warn({ error }, 'ChromaDB not available, will use SQLite fallback')
|
|
92
96
|
throw error
|
|
93
97
|
}
|
|
94
98
|
}
|
|
@@ -6,6 +6,25 @@ import type { DecisionMetadata, MemoryMetadata } from './schemas'
|
|
|
6
6
|
import type { EmbeddingProvider } from './embeddings'
|
|
7
7
|
import type { SearchResult } from './search'
|
|
8
8
|
|
|
9
|
+
/**
|
|
10
|
+
* Sanitize metadata for ChromaDB v3.x compatibility.
|
|
11
|
+
* Strips undefined/null values (ChromaDB only accepts string, number, boolean).
|
|
12
|
+
*/
|
|
13
|
+
function sanitizeMetadata(metadata: Record<string, any>): Record<string, string | number | boolean> {
|
|
14
|
+
const clean: Record<string, string | number | boolean> = {}
|
|
15
|
+
for (const [key, value] of Object.entries(metadata)) {
|
|
16
|
+
if (value === undefined || value === null) continue
|
|
17
|
+
if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
|
|
18
|
+
clean[key] = value
|
|
19
|
+
} else if (Array.isArray(value)) {
|
|
20
|
+
clean[key] = JSON.stringify(value)
|
|
21
|
+
} else {
|
|
22
|
+
clean[key] = String(value)
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return clean
|
|
26
|
+
}
|
|
27
|
+
|
|
9
28
|
export interface StoreDecisionInput {
|
|
10
29
|
project: string
|
|
11
30
|
context: string
|
|
@@ -145,7 +164,7 @@ export class ChromaMemoryStore {
|
|
|
145
164
|
await collection.add({
|
|
146
165
|
ids: [id],
|
|
147
166
|
documents: [input.decision],
|
|
148
|
-
metadatas: [metadata as Record<string, any>],
|
|
167
|
+
metadatas: [sanitizeMetadata(metadata as Record<string, any>)],
|
|
149
168
|
...(embeddings ? { embeddings } : {})
|
|
150
169
|
})
|
|
151
170
|
|
|
@@ -166,7 +185,7 @@ export class ChromaMemoryStore {
|
|
|
166
185
|
await memoriesCollection.add({
|
|
167
186
|
ids: [id], // Use same ID for cross-reference
|
|
168
187
|
documents: [memoryContent],
|
|
169
|
-
metadatas: [memoryMetadata],
|
|
188
|
+
metadatas: [sanitizeMetadata(memoryMetadata)],
|
|
170
189
|
...(embeddings ? { embeddings } : {})
|
|
171
190
|
})
|
|
172
191
|
|
|
@@ -230,7 +249,7 @@ export class ChromaMemoryStore {
|
|
|
230
249
|
await collection.add({
|
|
231
250
|
ids: [id],
|
|
232
251
|
documents: [input.description],
|
|
233
|
-
metadatas: [metadata],
|
|
252
|
+
metadatas: [sanitizeMetadata(metadata)],
|
|
234
253
|
...(embeddings ? { embeddings } : {})
|
|
235
254
|
})
|
|
236
255
|
|
|
@@ -252,7 +271,7 @@ export class ChromaMemoryStore {
|
|
|
252
271
|
await memoriesCollection.add({
|
|
253
272
|
ids: [id],
|
|
254
273
|
documents: [memoryContent],
|
|
255
|
-
metadatas: [memoryMetadata],
|
|
274
|
+
metadatas: [sanitizeMetadata(memoryMetadata)],
|
|
256
275
|
...(embeddings ? { embeddings } : {})
|
|
257
276
|
})
|
|
258
277
|
|
|
@@ -304,7 +323,7 @@ export class ChromaMemoryStore {
|
|
|
304
323
|
await collection.add({
|
|
305
324
|
ids: [id],
|
|
306
325
|
documents: [input.correction],
|
|
307
|
-
metadatas: [metadata],
|
|
326
|
+
metadatas: [sanitizeMetadata(metadata)],
|
|
308
327
|
...(embeddings ? { embeddings } : {})
|
|
309
328
|
})
|
|
310
329
|
|
|
@@ -325,7 +344,7 @@ export class ChromaMemoryStore {
|
|
|
325
344
|
await memoriesCollection.add({
|
|
326
345
|
ids: [id],
|
|
327
346
|
documents: [memoryContent],
|
|
328
|
-
metadatas: [memoryMetadata],
|
|
347
|
+
metadatas: [sanitizeMetadata(memoryMetadata)],
|
|
329
348
|
...(embeddings ? { embeddings } : {})
|
|
330
349
|
})
|
|
331
350
|
|
|
@@ -367,7 +386,7 @@ export class ChromaMemoryStore {
|
|
|
367
386
|
await collection.add({
|
|
368
387
|
ids: [id],
|
|
369
388
|
documents: [input.content],
|
|
370
|
-
metadatas: [metadata as Record<string, any>],
|
|
389
|
+
metadatas: [sanitizeMetadata(metadata as Record<string, any>)],
|
|
371
390
|
...(embeddings ? { embeddings } : {})
|
|
372
391
|
})
|
|
373
392
|
|
|
@@ -409,7 +428,7 @@ export class ChromaMemoryStore {
|
|
|
409
428
|
await collection.upsert({
|
|
410
429
|
ids: [id],
|
|
411
430
|
documents: [input.decision],
|
|
412
|
-
metadatas: [metadata as Record<string, any>],
|
|
431
|
+
metadatas: [sanitizeMetadata(metadata as Record<string, any>)],
|
|
413
432
|
...(embeddings ? { embeddings } : {})
|
|
414
433
|
})
|
|
415
434
|
|
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
|
*/
|