claude-brain 0.14.2 → 0.14.4
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/README.md +191 -191
- package/VERSION +1 -1
- package/assets/CLAUDE-unified.md +11 -11
- package/assets/CLAUDE.md +11 -11
- package/bunfig.toml +8 -8
- package/package.json +80 -80
- package/packs/backend/node.json +173 -173
- package/packs/core/javascript.json +176 -176
- package/packs/core/typescript.json +222 -222
- package/packs/frontend/react.json +254 -254
- package/packs/meta/testing.json +172 -172
- package/src/automation/auto-context.ts +240 -240
- package/src/automation/decision-detector.ts +452 -452
- package/src/automation/index.ts +11 -11
- package/src/automation/phase12-manager.ts +456 -456
- package/src/automation/proactive-recall.ts +373 -373
- package/src/automation/project-detector.ts +310 -310
- package/src/automation/repo-scanner.ts +205 -205
- package/src/cli/auto-setup.ts +82 -82
- package/src/cli/bin.ts +202 -202
- package/src/cli/commands/chroma.ts +573 -573
- package/src/cli/commands/git-hook.ts +189 -189
- package/src/cli/commands/hooks.ts +213 -213
- package/src/cli/commands/init.ts +122 -122
- package/src/cli/commands/install-mcp.ts +92 -92
- package/src/cli/commands/pack.ts +197 -197
- package/src/cli/commands/serve.ts +167 -167
- package/src/cli/commands/start.ts +42 -42
- package/src/cli/commands/uninstall-mcp.ts +41 -41
- package/src/cli/commands/update.ts +121 -121
- package/src/cli/diagnose.ts +4 -4
- package/src/cli/health-check.ts +4 -4
- package/src/cli/migrate-chroma.ts +106 -106
- package/src/cli/setup.ts +4 -4
- package/src/cli/ui/animations.ts +80 -80
- package/src/cli/ui/components.ts +82 -82
- package/src/cli/ui/index.ts +4 -4
- package/src/cli/ui/logo.ts +36 -36
- package/src/cli/ui/theme.ts +55 -55
- package/src/config/defaults.ts +50 -50
- package/src/config/home.ts +55 -55
- package/src/config/index.ts +7 -7
- package/src/config/loader.ts +166 -166
- package/src/config/migration.ts +76 -76
- package/src/config/schema.ts +360 -360
- package/src/config/validator.ts +184 -184
- package/src/config/watcher.ts +86 -86
- package/src/context/assembler.ts +398 -398
- package/src/context/cache-manager.ts +101 -101
- package/src/context/formatter.ts +84 -84
- package/src/context/hierarchy.ts +85 -85
- package/src/context/index.ts +83 -83
- package/src/context/progress-tracker.ts +174 -174
- package/src/context/standards-manager.ts +287 -287
- package/src/context/types.ts +252 -252
- package/src/context/validator.ts +58 -58
- package/src/diagnostics/index.ts +123 -123
- package/src/health/index.ts +229 -229
- package/src/hooks/brain-hook.ts +112 -112
- package/src/hooks/capture.ts +168 -168
- package/src/hooks/deduplicator.ts +72 -72
- package/src/hooks/git-capture.ts +109 -109
- package/src/hooks/git-hook-installer.ts +207 -207
- package/src/hooks/index.ts +20 -20
- package/src/hooks/installer.ts +191 -194
- package/src/hooks/passive-classifier.ts +366 -366
- package/src/hooks/queue.ts +129 -129
- package/src/hooks/session-tracker.ts +275 -275
- package/src/hooks/types.ts +47 -47
- package/src/index.ts +7 -7
- package/src/intelligence/cross-project/affinity.ts +162 -162
- package/src/intelligence/cross-project/generalizer.ts +283 -283
- package/src/intelligence/cross-project/index.ts +13 -13
- package/src/intelligence/cross-project/transfer.ts +201 -201
- package/src/intelligence/index.ts +24 -24
- package/src/intelligence/optimization/index.ts +10 -10
- package/src/intelligence/optimization/precompute.ts +202 -202
- package/src/intelligence/optimization/semantic-cache.ts +207 -207
- package/src/intelligence/prediction/context-anticipator.ts +198 -198
- package/src/intelligence/prediction/decision-predictor.ts +184 -184
- package/src/intelligence/prediction/index.ts +13 -13
- package/src/intelligence/prediction/recommender.ts +268 -268
- package/src/intelligence/reasoning/chain-retrieval.ts +247 -247
- package/src/intelligence/reasoning/counterfactual.ts +248 -248
- package/src/intelligence/reasoning/index.ts +13 -13
- package/src/intelligence/reasoning/synthesizer.ts +169 -169
- package/src/intelligence/temporal/evolution.ts +197 -197
- package/src/intelligence/temporal/index.ts +16 -16
- package/src/intelligence/temporal/query-processor.ts +190 -190
- package/src/intelligence/temporal/timeline.ts +259 -259
- package/src/intelligence/temporal/trends.ts +263 -263
- package/src/knowledge/entity-extractor.ts +416 -416
- package/src/knowledge/graph/builder.ts +185 -185
- package/src/knowledge/graph/linker.ts +201 -201
- package/src/knowledge/graph/memory-graph.ts +359 -359
- package/src/knowledge/graph/schema.ts +99 -99
- package/src/knowledge/graph/search.ts +168 -168
- package/src/knowledge/relationship-extractor.ts +108 -108
- package/src/memory/chroma/client.ts +174 -174
- package/src/memory/chroma/collection-manager.ts +94 -94
- package/src/memory/chroma/config.ts +57 -57
- package/src/memory/chroma/embeddings.ts +153 -153
- package/src/memory/chroma/index.ts +82 -82
- package/src/memory/chroma/migration.ts +270 -270
- package/src/memory/chroma/schemas.ts +69 -69
- package/src/memory/chroma/search.ts +315 -315
- package/src/memory/chroma/store.ts +741 -741
- package/src/memory/consolidation/archiver.ts +164 -164
- package/src/memory/consolidation/merger.ts +186 -186
- package/src/memory/consolidation/scorer.ts +138 -138
- package/src/memory/context-builder.ts +236 -236
- package/src/memory/database.ts +169 -169
- package/src/memory/embedding-utils.ts +156 -156
- package/src/memory/embeddings.ts +226 -226
- package/src/memory/episodic/detector.ts +108 -108
- package/src/memory/episodic/manager.ts +351 -351
- package/src/memory/episodic/summarizer.ts +179 -179
- package/src/memory/episodic/types.ts +52 -52
- package/src/memory/index.ts +582 -582
- package/src/memory/knowledge-extractor.ts +455 -455
- package/src/memory/learning.ts +378 -378
- package/src/memory/patterns.ts +396 -396
- package/src/memory/schema.ts +88 -88
- package/src/memory/search.ts +309 -309
- package/src/memory/store.ts +787 -787
- package/src/memory/types.ts +121 -121
- package/src/orchestrator/coordinator.ts +272 -272
- package/src/orchestrator/decision-logger.ts +228 -228
- package/src/orchestrator/event-emitter.ts +198 -198
- package/src/orchestrator/event-queue.ts +184 -184
- package/src/orchestrator/handlers/base-handler.ts +70 -70
- package/src/orchestrator/handlers/context-handler.ts +73 -73
- package/src/orchestrator/handlers/decision-handler.ts +204 -204
- package/src/orchestrator/handlers/index.ts +10 -10
- package/src/orchestrator/handlers/status-handler.ts +131 -131
- package/src/orchestrator/handlers/task-handler.ts +171 -171
- package/src/orchestrator/index.ts +275 -275
- package/src/orchestrator/task-parser.ts +284 -284
- package/src/orchestrator/types.ts +98 -98
- package/src/packs/index.ts +9 -9
- package/src/packs/loader.ts +134 -134
- package/src/packs/manager.ts +204 -204
- package/src/packs/ranker.ts +78 -78
- package/src/packs/types.ts +81 -81
- package/src/phase12/index.ts +5 -5
- package/src/retrieval/bm25/index.ts +300 -300
- package/src/retrieval/bm25/tokenizer.ts +184 -184
- package/src/retrieval/feedback/adaptive.ts +223 -223
- package/src/retrieval/feedback/index.ts +16 -16
- package/src/retrieval/feedback/metrics.ts +223 -223
- package/src/retrieval/feedback/store.ts +283 -283
- package/src/retrieval/fusion/index.ts +194 -194
- package/src/retrieval/fusion/rrf.ts +163 -163
- package/src/retrieval/index.ts +12 -12
- package/src/retrieval/pipeline.ts +375 -375
- package/src/retrieval/query/expander.ts +198 -198
- package/src/retrieval/query/index.ts +27 -27
- package/src/retrieval/query/intent-classifier.ts +236 -236
- package/src/retrieval/query/temporal-parser.ts +295 -295
- package/src/retrieval/reranker/index.ts +188 -188
- package/src/retrieval/reranker/model.ts +95 -95
- package/src/retrieval/service.ts +125 -125
- package/src/retrieval/types.ts +162 -162
- package/src/routing/entity-extractor.ts +428 -428
- package/src/routing/intent-classifier.ts +436 -436
- package/src/routing/response-filter.ts +258 -254
- package/src/routing/router.ts +1322 -1314
- package/src/routing/search-engine.ts +475 -475
- package/src/routing/types.ts +94 -84
- package/src/scripts/health-check.ts +118 -118
- package/src/scripts/setup.ts +122 -122
- package/src/server/handlers/call-tool.ts +156 -156
- package/src/server/handlers/index.ts +9 -9
- package/src/server/handlers/list-tools.ts +35 -35
- package/src/server/handlers/tools/analyze-decision-evolution.ts +151 -151
- package/src/server/handlers/tools/auto-remember.ts +200 -200
- package/src/server/handlers/tools/brain.ts +85 -85
- package/src/server/handlers/tools/create-project.ts +135 -135
- package/src/server/handlers/tools/detect-trends.ts +144 -144
- package/src/server/handlers/tools/find-cross-project-patterns.ts +168 -168
- package/src/server/handlers/tools/get-activity-log.ts +194 -194
- package/src/server/handlers/tools/get-code-standards.ts +124 -124
- package/src/server/handlers/tools/get-corrections.ts +154 -154
- package/src/server/handlers/tools/get-decision-timeline.ts +172 -172
- package/src/server/handlers/tools/get-episode.ts +103 -103
- package/src/server/handlers/tools/get-patterns.ts +158 -158
- package/src/server/handlers/tools/get-phase12-status.ts +63 -63
- package/src/server/handlers/tools/get-project-context.ts +75 -75
- package/src/server/handlers/tools/get-recommendations.ts +145 -145
- package/src/server/handlers/tools/index.ts +31 -31
- package/src/server/handlers/tools/init-project.ts +757 -757
- package/src/server/handlers/tools/list-episodes.ts +90 -90
- package/src/server/handlers/tools/list-projects.ts +125 -125
- package/src/server/handlers/tools/rate-memory.ts +101 -101
- package/src/server/handlers/tools/recall-similar.ts +87 -87
- package/src/server/handlers/tools/recognize-pattern.ts +126 -126
- package/src/server/handlers/tools/record-correction.ts +125 -125
- package/src/server/handlers/tools/remember-decision.ts +153 -153
- package/src/server/handlers/tools/schemas.ts +253 -253
- package/src/server/handlers/tools/search-knowledge-graph.ts +102 -102
- package/src/server/handlers/tools/smart-context.ts +146 -146
- package/src/server/handlers/tools/update-progress.ts +131 -131
- package/src/server/handlers/tools/what-if-analysis.ts +135 -135
- package/src/server/http-api.ts +693 -693
- package/src/server/index.ts +40 -40
- package/src/server/mcp-server.ts +283 -283
- package/src/server/providers/index.ts +7 -7
- package/src/server/providers/prompts.ts +327 -327
- package/src/server/providers/resources.ts +622 -622
- package/src/server/services.ts +468 -468
- package/src/server/types.ts +39 -39
- package/src/server/utils/error-handler.ts +155 -155
- package/src/server/utils/index.ts +13 -13
- package/src/server/utils/memory-indicator.ts +83 -83
- package/src/server/utils/request-context.ts +122 -122
- package/src/server/utils/response-formatter.ts +129 -124
- package/src/server/utils/validators.ts +210 -210
- package/src/setup/index.ts +48 -48
- package/src/setup/wizard.ts +461 -461
- package/src/tools/index.ts +24 -24
- package/src/tools/registry.ts +115 -115
- package/src/tools/schemas.test.ts +30 -30
- package/src/tools/schemas.ts +617 -617
- package/src/tools/types.ts +412 -412
- package/src/utils/circuit-breaker.ts +130 -130
- package/src/utils/cleanup.ts +34 -34
- package/src/utils/error-handler.ts +132 -132
- package/src/utils/error-messages.ts +60 -60
- package/src/utils/fallback.ts +45 -45
- package/src/utils/index.ts +54 -54
- package/src/utils/logger-utils.ts +80 -80
- package/src/utils/logger.ts +88 -88
- package/src/utils/phase12-helper.ts +56 -56
- package/src/utils/retry.ts +94 -94
- package/src/utils/timing.ts +47 -47
- package/src/utils/transaction.ts +63 -63
- package/src/vault/frontmatter.ts +264 -264
- package/src/vault/index.ts +318 -318
- package/src/vault/paths.ts +106 -106
- package/src/vault/query.ts +422 -422
- package/src/vault/reader.ts +264 -264
- package/src/vault/templates.ts +186 -186
- package/src/vault/types.ts +73 -73
- package/src/vault/watcher.ts +277 -277
- package/src/vault/writer.ts +413 -413
- package/tsconfig.json +30 -30
package/src/memory/search.ts
CHANGED
|
@@ -1,309 +1,309 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Semantic Search Engine
|
|
3
|
-
* Phase 3: Memory and Embedding System
|
|
4
|
-
*
|
|
5
|
-
* Finds similar memories using vector similarity
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import type { Database } from 'bun:sqlite'
|
|
9
|
-
import type { Logger } from 'pino'
|
|
10
|
-
import type { Decision, MemorySearchResult, SearchOptions, MemoryRow, DecisionRow } from './types'
|
|
11
|
-
import { EmbeddingService } from './embeddings'
|
|
12
|
-
import { bufferToEmbedding, cosineSimilarity } from './embedding-utils'
|
|
13
|
-
|
|
14
|
-
export class SemanticSearch {
|
|
15
|
-
private db: Database
|
|
16
|
-
private embeddings: EmbeddingService
|
|
17
|
-
private logger: Logger
|
|
18
|
-
|
|
19
|
-
constructor(db: Database, embeddings: EmbeddingService, logger: Logger) {
|
|
20
|
-
this.db = db
|
|
21
|
-
this.embeddings = embeddings
|
|
22
|
-
this.logger = logger.child({ component: 'semantic-search' })
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Search for similar memories using semantic similarity
|
|
27
|
-
*/
|
|
28
|
-
async search(query: string, options: SearchOptions = {}): Promise<MemorySearchResult[]> {
|
|
29
|
-
try {
|
|
30
|
-
const startTime = Date.now()
|
|
31
|
-
|
|
32
|
-
// Generate query embedding
|
|
33
|
-
const queryEmbedding = await this.embeddings.generateEmbedding(query)
|
|
34
|
-
|
|
35
|
-
// Build SQL query with filters
|
|
36
|
-
let sql = 'SELECT * FROM memories WHERE 1=1'
|
|
37
|
-
const params: string[] = []
|
|
38
|
-
|
|
39
|
-
if (options.project) {
|
|
40
|
-
sql += ' AND project = ?'
|
|
41
|
-
params.push(options.project)
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// Get all matching memories
|
|
45
|
-
const stmt = this.db.prepare(sql)
|
|
46
|
-
const rows = stmt.all(...params) as MemoryRow[]
|
|
47
|
-
|
|
48
|
-
// Calculate similarity for each memory
|
|
49
|
-
const results: MemorySearchResult[] = []
|
|
50
|
-
|
|
51
|
-
for (const row of rows) {
|
|
52
|
-
const embedding = bufferToEmbedding(row.embedding)
|
|
53
|
-
const similarity = cosineSimilarity(queryEmbedding, embedding)
|
|
54
|
-
|
|
55
|
-
// Filter by minimum similarity
|
|
56
|
-
if (options.minSimilarity && similarity < options.minSimilarity) {
|
|
57
|
-
continue
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// Get associated decision if exists
|
|
61
|
-
let decision: Decision | undefined = undefined
|
|
62
|
-
if (row.metadata) {
|
|
63
|
-
const metadata = JSON.parse(row.metadata)
|
|
64
|
-
if (metadata.type === 'decision') {
|
|
65
|
-
const decisionStmt = this.db.prepare('SELECT * FROM decisions WHERE memory_id = ?')
|
|
66
|
-
const decisionRow = decisionStmt.get(row.id) as DecisionRow | null
|
|
67
|
-
|
|
68
|
-
if (decisionRow) {
|
|
69
|
-
decision = {
|
|
70
|
-
id: decisionRow.id,
|
|
71
|
-
memoryId: decisionRow.memory_id,
|
|
72
|
-
context: decisionRow.context,
|
|
73
|
-
decision: decisionRow.decision,
|
|
74
|
-
reasoning: decisionRow.reasoning,
|
|
75
|
-
alternatives: decisionRow.alternatives || undefined,
|
|
76
|
-
outcome: decisionRow.outcome || undefined,
|
|
77
|
-
tags: decisionRow.tags ? JSON.parse(decisionRow.tags) : undefined
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// Filter by tags if specified
|
|
81
|
-
if (options.tags && options.tags.length > 0) {
|
|
82
|
-
const decisionTags = decision.tags || []
|
|
83
|
-
const hasMatchingTag = options.tags.some((tag) => decisionTags.includes(tag))
|
|
84
|
-
if (!hasMatchingTag) {
|
|
85
|
-
continue
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
results.push({
|
|
93
|
-
memory: {
|
|
94
|
-
id: row.id,
|
|
95
|
-
project: row.project,
|
|
96
|
-
content: row.content,
|
|
97
|
-
embedding: embedding,
|
|
98
|
-
createdAt: new Date(row.created_at),
|
|
99
|
-
updatedAt: new Date(row.updated_at),
|
|
100
|
-
metadata: row.metadata ? JSON.parse(row.metadata) : undefined
|
|
101
|
-
},
|
|
102
|
-
similarity,
|
|
103
|
-
decision
|
|
104
|
-
})
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// Sort by similarity (descending)
|
|
108
|
-
results.sort((a, b) => b.similarity - a.similarity)
|
|
109
|
-
|
|
110
|
-
// Apply limit
|
|
111
|
-
const limit = options.limit || 10
|
|
112
|
-
const limitedResults = results.slice(0, limit)
|
|
113
|
-
|
|
114
|
-
const duration = Date.now() - startTime
|
|
115
|
-
this.logger.info(
|
|
116
|
-
{ query: query.slice(0, 50), resultCount: limitedResults.length, duration },
|
|
117
|
-
'Semantic search completed'
|
|
118
|
-
)
|
|
119
|
-
|
|
120
|
-
return limitedResults
|
|
121
|
-
} catch (error) {
|
|
122
|
-
this.logger.error({ error, query, options }, 'Semantic search failed')
|
|
123
|
-
return []
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
/**
|
|
128
|
-
* Find similar decisions to a given context
|
|
129
|
-
*/
|
|
130
|
-
async findSimilarDecisions(
|
|
131
|
-
context: string,
|
|
132
|
-
project?: string,
|
|
133
|
-
limit: number = 5
|
|
134
|
-
): Promise<MemorySearchResult[]> {
|
|
135
|
-
// Filter for decision-type memories
|
|
136
|
-
// Use lower similarity threshold (0.3) to catch more variations in phrasing
|
|
137
|
-
const results = await this.search(context, {
|
|
138
|
-
project,
|
|
139
|
-
limit: limit * 2, // Get more to filter
|
|
140
|
-
minSimilarity: 0.3
|
|
141
|
-
})
|
|
142
|
-
|
|
143
|
-
// Keep only results with decisions
|
|
144
|
-
return results.filter((r) => r.decision !== undefined).slice(0, limit)
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
/**
|
|
148
|
-
* Get recommendations based on current context
|
|
149
|
-
*/
|
|
150
|
-
async getRecommendations(
|
|
151
|
-
currentContext: string,
|
|
152
|
-
project: string,
|
|
153
|
-
limit: number = 3
|
|
154
|
-
): Promise<MemorySearchResult[]> {
|
|
155
|
-
return this.search(currentContext, {
|
|
156
|
-
project,
|
|
157
|
-
limit,
|
|
158
|
-
minSimilarity: 0.7 // Higher threshold for recommendations
|
|
159
|
-
})
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
/**
|
|
163
|
-
* Find memories by tag
|
|
164
|
-
*/
|
|
165
|
-
async findByTags(tags: string[], project?: string, limit: number = 10): Promise<MemorySearchResult[]> {
|
|
166
|
-
try {
|
|
167
|
-
// Get all decision memories that might have tags
|
|
168
|
-
let sql = `
|
|
169
|
-
SELECT m.*, d.tags as decision_tags
|
|
170
|
-
FROM memories m
|
|
171
|
-
LEFT JOIN decisions d ON m.id = d.memory_id
|
|
172
|
-
WHERE m.metadata LIKE '%"type":"decision"%'
|
|
173
|
-
`
|
|
174
|
-
const params: string[] = []
|
|
175
|
-
|
|
176
|
-
if (project) {
|
|
177
|
-
sql += ' AND m.project = ?'
|
|
178
|
-
params.push(project)
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
const stmt = this.db.prepare(sql)
|
|
182
|
-
const rows = stmt.all(...params) as Array<MemoryRow & { decision_tags: string | null }>
|
|
183
|
-
|
|
184
|
-
const results: MemorySearchResult[] = []
|
|
185
|
-
|
|
186
|
-
for (const row of rows) {
|
|
187
|
-
if (!row.decision_tags) continue
|
|
188
|
-
|
|
189
|
-
const decisionTags = JSON.parse(row.decision_tags) as string[]
|
|
190
|
-
const hasMatchingTag = tags.some((tag) => decisionTags.includes(tag))
|
|
191
|
-
|
|
192
|
-
if (!hasMatchingTag) continue
|
|
193
|
-
|
|
194
|
-
// Get the full decision
|
|
195
|
-
const decisionStmt = this.db.prepare('SELECT * FROM decisions WHERE memory_id = ?')
|
|
196
|
-
const decisionRow = decisionStmt.get(row.id) as DecisionRow | null
|
|
197
|
-
|
|
198
|
-
if (decisionRow) {
|
|
199
|
-
results.push({
|
|
200
|
-
memory: {
|
|
201
|
-
id: row.id,
|
|
202
|
-
project: row.project,
|
|
203
|
-
content: row.content,
|
|
204
|
-
embedding: bufferToEmbedding(row.embedding),
|
|
205
|
-
createdAt: new Date(row.created_at),
|
|
206
|
-
updatedAt: new Date(row.updated_at),
|
|
207
|
-
metadata: row.metadata ? JSON.parse(row.metadata) : undefined
|
|
208
|
-
},
|
|
209
|
-
similarity: 1.0, // Tag match, no semantic similarity
|
|
210
|
-
decision: {
|
|
211
|
-
id: decisionRow.id,
|
|
212
|
-
memoryId: decisionRow.memory_id,
|
|
213
|
-
context: decisionRow.context,
|
|
214
|
-
decision: decisionRow.decision,
|
|
215
|
-
reasoning: decisionRow.reasoning,
|
|
216
|
-
alternatives: decisionRow.alternatives || undefined,
|
|
217
|
-
outcome: decisionRow.outcome || undefined,
|
|
218
|
-
tags: decisionRow.tags ? JSON.parse(decisionRow.tags) : undefined
|
|
219
|
-
}
|
|
220
|
-
})
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
// Sort by created date (newest first)
|
|
225
|
-
results.sort((a, b) => b.memory.createdAt.getTime() - a.memory.createdAt.getTime())
|
|
226
|
-
|
|
227
|
-
return results.slice(0, limit)
|
|
228
|
-
} catch (error) {
|
|
229
|
-
this.logger.error({ error, tags, project }, 'Tag search failed')
|
|
230
|
-
return []
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
/**
|
|
235
|
-
* Search across all content with text matching and semantic similarity
|
|
236
|
-
*/
|
|
237
|
-
async hybridSearch(
|
|
238
|
-
query: string,
|
|
239
|
-
options: SearchOptions = {}
|
|
240
|
-
): Promise<MemorySearchResult[]> {
|
|
241
|
-
// Get semantic results
|
|
242
|
-
const semanticResults = await this.search(query, {
|
|
243
|
-
...options,
|
|
244
|
-
limit: (options.limit || 10) * 2
|
|
245
|
-
})
|
|
246
|
-
|
|
247
|
-
// Also do text search
|
|
248
|
-
const textResults = this.textSearch(query, options.project)
|
|
249
|
-
|
|
250
|
-
// Merge and deduplicate results
|
|
251
|
-
const resultMap = new Map<string, MemorySearchResult>()
|
|
252
|
-
|
|
253
|
-
// Add semantic results first (they have similarity scores)
|
|
254
|
-
for (const result of semanticResults) {
|
|
255
|
-
resultMap.set(result.memory.id, result)
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
// Add text results (boost their similarity if they also matched semantically)
|
|
259
|
-
for (const result of textResults) {
|
|
260
|
-
const existing = resultMap.get(result.memory.id)
|
|
261
|
-
if (existing) {
|
|
262
|
-
// Boost similarity for hybrid matches
|
|
263
|
-
existing.similarity = Math.min(1.0, existing.similarity * 1.2)
|
|
264
|
-
} else {
|
|
265
|
-
resultMap.set(result.memory.id, result)
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
// Convert to array and sort
|
|
270
|
-
const combined = Array.from(resultMap.values())
|
|
271
|
-
combined.sort((a, b) => b.similarity - a.similarity)
|
|
272
|
-
|
|
273
|
-
return combined.slice(0, options.limit || 10)
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
/**
|
|
277
|
-
* Simple text search (case-insensitive)
|
|
278
|
-
*/
|
|
279
|
-
private textSearch(query: string, project?: string): MemorySearchResult[] {
|
|
280
|
-
try {
|
|
281
|
-
let sql = 'SELECT * FROM memories WHERE content LIKE ?'
|
|
282
|
-
const params: string[] = [`%${query}%`]
|
|
283
|
-
|
|
284
|
-
if (project) {
|
|
285
|
-
sql += ' AND project = ?'
|
|
286
|
-
params.push(project)
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
const stmt = this.db.prepare(sql)
|
|
290
|
-
const rows = stmt.all(...params) as MemoryRow[]
|
|
291
|
-
|
|
292
|
-
return rows.map((row) => ({
|
|
293
|
-
memory: {
|
|
294
|
-
id: row.id,
|
|
295
|
-
project: row.project,
|
|
296
|
-
content: row.content,
|
|
297
|
-
embedding: bufferToEmbedding(row.embedding),
|
|
298
|
-
createdAt: new Date(row.created_at),
|
|
299
|
-
updatedAt: new Date(row.updated_at),
|
|
300
|
-
metadata: row.metadata ? JSON.parse(row.metadata) : undefined
|
|
301
|
-
},
|
|
302
|
-
similarity: 0.8 // Default similarity for text matches
|
|
303
|
-
}))
|
|
304
|
-
} catch (error) {
|
|
305
|
-
this.logger.error({ error, query }, 'Text search failed')
|
|
306
|
-
return []
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Semantic Search Engine
|
|
3
|
+
* Phase 3: Memory and Embedding System
|
|
4
|
+
*
|
|
5
|
+
* Finds similar memories using vector similarity
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { Database } from 'bun:sqlite'
|
|
9
|
+
import type { Logger } from 'pino'
|
|
10
|
+
import type { Decision, MemorySearchResult, SearchOptions, MemoryRow, DecisionRow } from './types'
|
|
11
|
+
import { EmbeddingService } from './embeddings'
|
|
12
|
+
import { bufferToEmbedding, cosineSimilarity } from './embedding-utils'
|
|
13
|
+
|
|
14
|
+
export class SemanticSearch {
|
|
15
|
+
private db: Database
|
|
16
|
+
private embeddings: EmbeddingService
|
|
17
|
+
private logger: Logger
|
|
18
|
+
|
|
19
|
+
constructor(db: Database, embeddings: EmbeddingService, logger: Logger) {
|
|
20
|
+
this.db = db
|
|
21
|
+
this.embeddings = embeddings
|
|
22
|
+
this.logger = logger.child({ component: 'semantic-search' })
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Search for similar memories using semantic similarity
|
|
27
|
+
*/
|
|
28
|
+
async search(query: string, options: SearchOptions = {}): Promise<MemorySearchResult[]> {
|
|
29
|
+
try {
|
|
30
|
+
const startTime = Date.now()
|
|
31
|
+
|
|
32
|
+
// Generate query embedding
|
|
33
|
+
const queryEmbedding = await this.embeddings.generateEmbedding(query)
|
|
34
|
+
|
|
35
|
+
// Build SQL query with filters
|
|
36
|
+
let sql = 'SELECT * FROM memories WHERE 1=1'
|
|
37
|
+
const params: string[] = []
|
|
38
|
+
|
|
39
|
+
if (options.project) {
|
|
40
|
+
sql += ' AND project = ?'
|
|
41
|
+
params.push(options.project)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Get all matching memories
|
|
45
|
+
const stmt = this.db.prepare(sql)
|
|
46
|
+
const rows = stmt.all(...params) as MemoryRow[]
|
|
47
|
+
|
|
48
|
+
// Calculate similarity for each memory
|
|
49
|
+
const results: MemorySearchResult[] = []
|
|
50
|
+
|
|
51
|
+
for (const row of rows) {
|
|
52
|
+
const embedding = bufferToEmbedding(row.embedding)
|
|
53
|
+
const similarity = cosineSimilarity(queryEmbedding, embedding)
|
|
54
|
+
|
|
55
|
+
// Filter by minimum similarity
|
|
56
|
+
if (options.minSimilarity && similarity < options.minSimilarity) {
|
|
57
|
+
continue
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Get associated decision if exists
|
|
61
|
+
let decision: Decision | undefined = undefined
|
|
62
|
+
if (row.metadata) {
|
|
63
|
+
const metadata = JSON.parse(row.metadata)
|
|
64
|
+
if (metadata.type === 'decision') {
|
|
65
|
+
const decisionStmt = this.db.prepare('SELECT * FROM decisions WHERE memory_id = ?')
|
|
66
|
+
const decisionRow = decisionStmt.get(row.id) as DecisionRow | null
|
|
67
|
+
|
|
68
|
+
if (decisionRow) {
|
|
69
|
+
decision = {
|
|
70
|
+
id: decisionRow.id,
|
|
71
|
+
memoryId: decisionRow.memory_id,
|
|
72
|
+
context: decisionRow.context,
|
|
73
|
+
decision: decisionRow.decision,
|
|
74
|
+
reasoning: decisionRow.reasoning,
|
|
75
|
+
alternatives: decisionRow.alternatives || undefined,
|
|
76
|
+
outcome: decisionRow.outcome || undefined,
|
|
77
|
+
tags: decisionRow.tags ? JSON.parse(decisionRow.tags) : undefined
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Filter by tags if specified
|
|
81
|
+
if (options.tags && options.tags.length > 0) {
|
|
82
|
+
const decisionTags = decision.tags || []
|
|
83
|
+
const hasMatchingTag = options.tags.some((tag) => decisionTags.includes(tag))
|
|
84
|
+
if (!hasMatchingTag) {
|
|
85
|
+
continue
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
results.push({
|
|
93
|
+
memory: {
|
|
94
|
+
id: row.id,
|
|
95
|
+
project: row.project,
|
|
96
|
+
content: row.content,
|
|
97
|
+
embedding: embedding,
|
|
98
|
+
createdAt: new Date(row.created_at),
|
|
99
|
+
updatedAt: new Date(row.updated_at),
|
|
100
|
+
metadata: row.metadata ? JSON.parse(row.metadata) : undefined
|
|
101
|
+
},
|
|
102
|
+
similarity,
|
|
103
|
+
decision
|
|
104
|
+
})
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Sort by similarity (descending)
|
|
108
|
+
results.sort((a, b) => b.similarity - a.similarity)
|
|
109
|
+
|
|
110
|
+
// Apply limit
|
|
111
|
+
const limit = options.limit || 10
|
|
112
|
+
const limitedResults = results.slice(0, limit)
|
|
113
|
+
|
|
114
|
+
const duration = Date.now() - startTime
|
|
115
|
+
this.logger.info(
|
|
116
|
+
{ query: query.slice(0, 50), resultCount: limitedResults.length, duration },
|
|
117
|
+
'Semantic search completed'
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
return limitedResults
|
|
121
|
+
} catch (error) {
|
|
122
|
+
this.logger.error({ error, query, options }, 'Semantic search failed')
|
|
123
|
+
return []
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Find similar decisions to a given context
|
|
129
|
+
*/
|
|
130
|
+
async findSimilarDecisions(
|
|
131
|
+
context: string,
|
|
132
|
+
project?: string,
|
|
133
|
+
limit: number = 5
|
|
134
|
+
): Promise<MemorySearchResult[]> {
|
|
135
|
+
// Filter for decision-type memories
|
|
136
|
+
// Use lower similarity threshold (0.3) to catch more variations in phrasing
|
|
137
|
+
const results = await this.search(context, {
|
|
138
|
+
project,
|
|
139
|
+
limit: limit * 2, // Get more to filter
|
|
140
|
+
minSimilarity: 0.3
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
// Keep only results with decisions
|
|
144
|
+
return results.filter((r) => r.decision !== undefined).slice(0, limit)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Get recommendations based on current context
|
|
149
|
+
*/
|
|
150
|
+
async getRecommendations(
|
|
151
|
+
currentContext: string,
|
|
152
|
+
project: string,
|
|
153
|
+
limit: number = 3
|
|
154
|
+
): Promise<MemorySearchResult[]> {
|
|
155
|
+
return this.search(currentContext, {
|
|
156
|
+
project,
|
|
157
|
+
limit,
|
|
158
|
+
minSimilarity: 0.7 // Higher threshold for recommendations
|
|
159
|
+
})
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Find memories by tag
|
|
164
|
+
*/
|
|
165
|
+
async findByTags(tags: string[], project?: string, limit: number = 10): Promise<MemorySearchResult[]> {
|
|
166
|
+
try {
|
|
167
|
+
// Get all decision memories that might have tags
|
|
168
|
+
let sql = `
|
|
169
|
+
SELECT m.*, d.tags as decision_tags
|
|
170
|
+
FROM memories m
|
|
171
|
+
LEFT JOIN decisions d ON m.id = d.memory_id
|
|
172
|
+
WHERE m.metadata LIKE '%"type":"decision"%'
|
|
173
|
+
`
|
|
174
|
+
const params: string[] = []
|
|
175
|
+
|
|
176
|
+
if (project) {
|
|
177
|
+
sql += ' AND m.project = ?'
|
|
178
|
+
params.push(project)
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const stmt = this.db.prepare(sql)
|
|
182
|
+
const rows = stmt.all(...params) as Array<MemoryRow & { decision_tags: string | null }>
|
|
183
|
+
|
|
184
|
+
const results: MemorySearchResult[] = []
|
|
185
|
+
|
|
186
|
+
for (const row of rows) {
|
|
187
|
+
if (!row.decision_tags) continue
|
|
188
|
+
|
|
189
|
+
const decisionTags = JSON.parse(row.decision_tags) as string[]
|
|
190
|
+
const hasMatchingTag = tags.some((tag) => decisionTags.includes(tag))
|
|
191
|
+
|
|
192
|
+
if (!hasMatchingTag) continue
|
|
193
|
+
|
|
194
|
+
// Get the full decision
|
|
195
|
+
const decisionStmt = this.db.prepare('SELECT * FROM decisions WHERE memory_id = ?')
|
|
196
|
+
const decisionRow = decisionStmt.get(row.id) as DecisionRow | null
|
|
197
|
+
|
|
198
|
+
if (decisionRow) {
|
|
199
|
+
results.push({
|
|
200
|
+
memory: {
|
|
201
|
+
id: row.id,
|
|
202
|
+
project: row.project,
|
|
203
|
+
content: row.content,
|
|
204
|
+
embedding: bufferToEmbedding(row.embedding),
|
|
205
|
+
createdAt: new Date(row.created_at),
|
|
206
|
+
updatedAt: new Date(row.updated_at),
|
|
207
|
+
metadata: row.metadata ? JSON.parse(row.metadata) : undefined
|
|
208
|
+
},
|
|
209
|
+
similarity: 1.0, // Tag match, no semantic similarity
|
|
210
|
+
decision: {
|
|
211
|
+
id: decisionRow.id,
|
|
212
|
+
memoryId: decisionRow.memory_id,
|
|
213
|
+
context: decisionRow.context,
|
|
214
|
+
decision: decisionRow.decision,
|
|
215
|
+
reasoning: decisionRow.reasoning,
|
|
216
|
+
alternatives: decisionRow.alternatives || undefined,
|
|
217
|
+
outcome: decisionRow.outcome || undefined,
|
|
218
|
+
tags: decisionRow.tags ? JSON.parse(decisionRow.tags) : undefined
|
|
219
|
+
}
|
|
220
|
+
})
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Sort by created date (newest first)
|
|
225
|
+
results.sort((a, b) => b.memory.createdAt.getTime() - a.memory.createdAt.getTime())
|
|
226
|
+
|
|
227
|
+
return results.slice(0, limit)
|
|
228
|
+
} catch (error) {
|
|
229
|
+
this.logger.error({ error, tags, project }, 'Tag search failed')
|
|
230
|
+
return []
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Search across all content with text matching and semantic similarity
|
|
236
|
+
*/
|
|
237
|
+
async hybridSearch(
|
|
238
|
+
query: string,
|
|
239
|
+
options: SearchOptions = {}
|
|
240
|
+
): Promise<MemorySearchResult[]> {
|
|
241
|
+
// Get semantic results
|
|
242
|
+
const semanticResults = await this.search(query, {
|
|
243
|
+
...options,
|
|
244
|
+
limit: (options.limit || 10) * 2
|
|
245
|
+
})
|
|
246
|
+
|
|
247
|
+
// Also do text search
|
|
248
|
+
const textResults = this.textSearch(query, options.project)
|
|
249
|
+
|
|
250
|
+
// Merge and deduplicate results
|
|
251
|
+
const resultMap = new Map<string, MemorySearchResult>()
|
|
252
|
+
|
|
253
|
+
// Add semantic results first (they have similarity scores)
|
|
254
|
+
for (const result of semanticResults) {
|
|
255
|
+
resultMap.set(result.memory.id, result)
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Add text results (boost their similarity if they also matched semantically)
|
|
259
|
+
for (const result of textResults) {
|
|
260
|
+
const existing = resultMap.get(result.memory.id)
|
|
261
|
+
if (existing) {
|
|
262
|
+
// Boost similarity for hybrid matches
|
|
263
|
+
existing.similarity = Math.min(1.0, existing.similarity * 1.2)
|
|
264
|
+
} else {
|
|
265
|
+
resultMap.set(result.memory.id, result)
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Convert to array and sort
|
|
270
|
+
const combined = Array.from(resultMap.values())
|
|
271
|
+
combined.sort((a, b) => b.similarity - a.similarity)
|
|
272
|
+
|
|
273
|
+
return combined.slice(0, options.limit || 10)
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Simple text search (case-insensitive)
|
|
278
|
+
*/
|
|
279
|
+
private textSearch(query: string, project?: string): MemorySearchResult[] {
|
|
280
|
+
try {
|
|
281
|
+
let sql = 'SELECT * FROM memories WHERE content LIKE ?'
|
|
282
|
+
const params: string[] = [`%${query}%`]
|
|
283
|
+
|
|
284
|
+
if (project) {
|
|
285
|
+
sql += ' AND project = ?'
|
|
286
|
+
params.push(project)
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const stmt = this.db.prepare(sql)
|
|
290
|
+
const rows = stmt.all(...params) as MemoryRow[]
|
|
291
|
+
|
|
292
|
+
return rows.map((row) => ({
|
|
293
|
+
memory: {
|
|
294
|
+
id: row.id,
|
|
295
|
+
project: row.project,
|
|
296
|
+
content: row.content,
|
|
297
|
+
embedding: bufferToEmbedding(row.embedding),
|
|
298
|
+
createdAt: new Date(row.created_at),
|
|
299
|
+
updatedAt: new Date(row.updated_at),
|
|
300
|
+
metadata: row.metadata ? JSON.parse(row.metadata) : undefined
|
|
301
|
+
},
|
|
302
|
+
similarity: 0.8 // Default similarity for text matches
|
|
303
|
+
}))
|
|
304
|
+
} catch (error) {
|
|
305
|
+
this.logger.error({ error, query }, 'Text search failed')
|
|
306
|
+
return []
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|