claude-brain 0.9.2 → 0.10.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/config/defaults.ts +1 -1
- package/src/config/schema.ts +1 -1
- package/src/memory/chroma/store.ts +6 -1
- package/src/memory/index.ts +29 -15
- package/src/routing/intent-classifier.ts +94 -6
- package/src/routing/response-filter.ts +50 -17
- package/src/routing/router.ts +447 -221
- package/src/routing/search-engine.ts +455 -0
- package/src/routing/types.ts +84 -0
- package/src/server/handlers/call-tool.ts +4 -49
- package/src/server/handlers/tools/index.ts +5 -7
- package/src/server/services.ts +28 -0
- package/src/tools/schemas.ts +6 -328
|
@@ -0,0 +1,455 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Search Engine
|
|
3
|
+
* Phase 19: Wires all advanced intelligence features into a unified search path.
|
|
4
|
+
*
|
|
5
|
+
* This is the core intelligence layer between the router and raw memory.
|
|
6
|
+
* Provides: hybrid search, semantic caching, temporal filtering, timeouts.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { Logger } from 'pino'
|
|
10
|
+
import type { NormalizedResult } from './types'
|
|
11
|
+
import { normalizeSearchResults, normalizePatternResults, normalizeCorrectionResults } from './types'
|
|
12
|
+
import {
|
|
13
|
+
getMemoryService,
|
|
14
|
+
getSemanticCacheService,
|
|
15
|
+
getRetrievalPipeline,
|
|
16
|
+
getKnowledgeGraphService,
|
|
17
|
+
getEpisodeService,
|
|
18
|
+
isServicesInitialized
|
|
19
|
+
} from '@/server/services'
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Wrap a promise with a timeout. Returns fallback if the promise doesn't resolve in time.
|
|
23
|
+
*/
|
|
24
|
+
function withTimeout<T>(promise: Promise<T>, ms: number, fallback: T): Promise<T> {
|
|
25
|
+
return Promise.race([
|
|
26
|
+
promise,
|
|
27
|
+
new Promise<T>(resolve => setTimeout(() => resolve(fallback), ms))
|
|
28
|
+
])
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const SEARCH_TIMEOUT = 5000 // 5s timeout for all searches
|
|
32
|
+
|
|
33
|
+
export class SearchEngine {
|
|
34
|
+
private logger: Logger
|
|
35
|
+
|
|
36
|
+
constructor(logger: Logger) {
|
|
37
|
+
this.logger = logger.child({ component: 'search-engine' })
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Enhanced search — uses hybrid retrieval pipeline when available,
|
|
42
|
+
* falls back to plain searchRaw().
|
|
43
|
+
* Wraps with semantic cache for repeated queries.
|
|
44
|
+
*/
|
|
45
|
+
async enhancedSearch(
|
|
46
|
+
query: string,
|
|
47
|
+
options?: { project?: string; limit?: number; minSimilarity?: number }
|
|
48
|
+
): Promise<NormalizedResult[]> {
|
|
49
|
+
if (!isServicesInitialized()) return []
|
|
50
|
+
|
|
51
|
+
const cacheKey = `search:${query}:${options?.project || ''}:${options?.limit || 5}`
|
|
52
|
+
|
|
53
|
+
// Check semantic cache first
|
|
54
|
+
const cache = getSemanticCacheService()
|
|
55
|
+
if (cache) {
|
|
56
|
+
try {
|
|
57
|
+
const cached = await cache.get(cacheKey)
|
|
58
|
+
if (cached) {
|
|
59
|
+
this.logger.debug({ query }, 'Search cache hit')
|
|
60
|
+
return cached as NormalizedResult[]
|
|
61
|
+
}
|
|
62
|
+
} catch {
|
|
63
|
+
// Cache miss or error, continue
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Try hybrid search via RetrievalPipeline
|
|
68
|
+
const pipeline = getRetrievalPipeline()
|
|
69
|
+
if (pipeline) {
|
|
70
|
+
try {
|
|
71
|
+
const hybridResults = await withTimeout(
|
|
72
|
+
pipeline.search(query, {
|
|
73
|
+
project: options?.project,
|
|
74
|
+
limit: options?.limit || 5,
|
|
75
|
+
minSimilarity: options?.minSimilarity || 0.3
|
|
76
|
+
}),
|
|
77
|
+
SEARCH_TIMEOUT,
|
|
78
|
+
[]
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
if (hybridResults.length > 0) {
|
|
82
|
+
const normalized = hybridResults.map((r: any) => ({
|
|
83
|
+
id: r.id || '',
|
|
84
|
+
content: r.content || r.document || '',
|
|
85
|
+
score: r.score || r.similarity || 0,
|
|
86
|
+
source: 'decision' as const,
|
|
87
|
+
project: r.metadata?.project || options?.project || '',
|
|
88
|
+
date: r.metadata?.created_at || '',
|
|
89
|
+
metadata: r.metadata || {}
|
|
90
|
+
}))
|
|
91
|
+
|
|
92
|
+
// Cache results
|
|
93
|
+
if (cache) {
|
|
94
|
+
try { await cache.set(cacheKey, normalized) } catch {}
|
|
95
|
+
}
|
|
96
|
+
return normalized
|
|
97
|
+
}
|
|
98
|
+
} catch (error) {
|
|
99
|
+
this.logger.debug({ error }, 'Hybrid search failed, falling back to plain search')
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Fallback: plain searchRaw
|
|
104
|
+
return this.plainSearch(query, options, cache, cacheKey)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Plain search using memory.searchRaw() — always available.
|
|
109
|
+
*/
|
|
110
|
+
async plainSearch(
|
|
111
|
+
query: string,
|
|
112
|
+
options?: { project?: string; limit?: number; minSimilarity?: number },
|
|
113
|
+
cache?: any,
|
|
114
|
+
cacheKey?: string
|
|
115
|
+
): Promise<NormalizedResult[]> {
|
|
116
|
+
if (!isServicesInitialized()) return []
|
|
117
|
+
|
|
118
|
+
const memory = getMemoryService()
|
|
119
|
+
try {
|
|
120
|
+
const rawResults = await withTimeout(
|
|
121
|
+
memory.searchRaw(query, {
|
|
122
|
+
project: options?.project,
|
|
123
|
+
limit: options?.limit || 5,
|
|
124
|
+
minSimilarity: options?.minSimilarity || 0.3
|
|
125
|
+
}),
|
|
126
|
+
SEARCH_TIMEOUT,
|
|
127
|
+
[]
|
|
128
|
+
)
|
|
129
|
+
const normalized = normalizeSearchResults(rawResults)
|
|
130
|
+
|
|
131
|
+
// Cache results
|
|
132
|
+
if (cache && cacheKey) {
|
|
133
|
+
try { await cache.set(cacheKey, normalized) } catch {}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return normalized
|
|
137
|
+
} catch {
|
|
138
|
+
return []
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Search patterns with normalization and timeout
|
|
144
|
+
*/
|
|
145
|
+
async searchPatterns(
|
|
146
|
+
query: string,
|
|
147
|
+
options?: { project?: string; limit?: number; minSimilarity?: number }
|
|
148
|
+
): Promise<NormalizedResult[]> {
|
|
149
|
+
if (!isServicesInitialized()) return []
|
|
150
|
+
const memory = getMemoryService()
|
|
151
|
+
try {
|
|
152
|
+
const results = await withTimeout(
|
|
153
|
+
memory.searchPatterns(query, {
|
|
154
|
+
project: options?.project,
|
|
155
|
+
limit: options?.limit || 3,
|
|
156
|
+
minSimilarity: options?.minSimilarity || 0.3
|
|
157
|
+
}),
|
|
158
|
+
SEARCH_TIMEOUT,
|
|
159
|
+
[]
|
|
160
|
+
)
|
|
161
|
+
return normalizePatternResults(results)
|
|
162
|
+
} catch {
|
|
163
|
+
return []
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Search corrections with normalization and timeout
|
|
169
|
+
*/
|
|
170
|
+
async searchCorrections(
|
|
171
|
+
query: string,
|
|
172
|
+
options?: { project?: string; limit?: number; minSimilarity?: number }
|
|
173
|
+
): Promise<NormalizedResult[]> {
|
|
174
|
+
if (!isServicesInitialized()) return []
|
|
175
|
+
const memory = getMemoryService()
|
|
176
|
+
try {
|
|
177
|
+
const results = await withTimeout(
|
|
178
|
+
memory.searchCorrections(query, {
|
|
179
|
+
project: options?.project,
|
|
180
|
+
limit: options?.limit || 3,
|
|
181
|
+
minSimilarity: options?.minSimilarity || 0.3
|
|
182
|
+
}),
|
|
183
|
+
SEARCH_TIMEOUT,
|
|
184
|
+
[]
|
|
185
|
+
)
|
|
186
|
+
return normalizeCorrectionResults(results)
|
|
187
|
+
} catch {
|
|
188
|
+
return []
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Search knowledge graph for related concepts
|
|
194
|
+
*/
|
|
195
|
+
async searchGraph(query: string, limit: number = 5): Promise<NormalizedResult[]> {
|
|
196
|
+
try {
|
|
197
|
+
const kgService = getKnowledgeGraphService()
|
|
198
|
+
if (!kgService?.search) return []
|
|
199
|
+
|
|
200
|
+
const graphResults = kgService.search.search({ query, limit })
|
|
201
|
+
if (!graphResults?.nodes?.length) return []
|
|
202
|
+
|
|
203
|
+
return graphResults.nodes.map((n: any) => ({
|
|
204
|
+
id: n.id || '',
|
|
205
|
+
content: `**${n.label || n.name}** (${n.type})${n.metadata?.description ? `\n${n.metadata.description}` : ''}`,
|
|
206
|
+
score: n.score || 0.5,
|
|
207
|
+
source: 'graph' as const,
|
|
208
|
+
project: n.metadata?.project || '',
|
|
209
|
+
date: '',
|
|
210
|
+
metadata: n.metadata || {}
|
|
211
|
+
}))
|
|
212
|
+
} catch {
|
|
213
|
+
return []
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Temporal search — parse temporal phrases from query, filter results by date range.
|
|
219
|
+
* Uses TemporalQueryProcessor when available.
|
|
220
|
+
*/
|
|
221
|
+
async temporalSearch(
|
|
222
|
+
query: string,
|
|
223
|
+
options?: { project?: string; limit?: number }
|
|
224
|
+
): Promise<{ results: NormalizedResult[]; cleanedQuery: string }> {
|
|
225
|
+
if (!isServicesInitialized()) return { results: [], cleanedQuery: query }
|
|
226
|
+
|
|
227
|
+
try {
|
|
228
|
+
// Dynamic import to avoid hard dependency
|
|
229
|
+
const { TemporalQueryProcessor } = await import('@/temporal/query-processor')
|
|
230
|
+
const processor = new TemporalQueryProcessor(this.logger)
|
|
231
|
+
const parsed = processor.parse(query)
|
|
232
|
+
|
|
233
|
+
if (parsed.intent === 'none') {
|
|
234
|
+
// No temporal expressions found, use normal search
|
|
235
|
+
const results = await this.enhancedSearch(query, options)
|
|
236
|
+
return { results, cleanedQuery: query }
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Search with cleaned query (temporal phrases removed)
|
|
240
|
+
const results = await this.enhancedSearch(parsed.cleaned || query, {
|
|
241
|
+
...options,
|
|
242
|
+
limit: (options?.limit || 5) * 2 // Fetch more since we'll filter by date
|
|
243
|
+
})
|
|
244
|
+
|
|
245
|
+
// Filter results by date range
|
|
246
|
+
const filtered = results.filter(r => {
|
|
247
|
+
if (!r.date) return true // Keep results without dates
|
|
248
|
+
const date = new Date(r.date)
|
|
249
|
+
if (isNaN(date.getTime())) return true
|
|
250
|
+
if (parsed.startDate && date < parsed.startDate) return false
|
|
251
|
+
if (parsed.endDate && date > parsed.endDate) return false
|
|
252
|
+
return true
|
|
253
|
+
})
|
|
254
|
+
|
|
255
|
+
return {
|
|
256
|
+
results: filtered.slice(0, options?.limit || 5),
|
|
257
|
+
cleanedQuery: parsed.cleaned || query
|
|
258
|
+
}
|
|
259
|
+
} catch {
|
|
260
|
+
// TemporalQueryProcessor not available, fall back
|
|
261
|
+
const results = await this.enhancedSearch(query, options)
|
|
262
|
+
return { results, cleanedQuery: query }
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Build a timeline for a topic/project
|
|
268
|
+
*/
|
|
269
|
+
async buildTimeline(
|
|
270
|
+
options?: { project?: string; topic?: string; limit?: number }
|
|
271
|
+
): Promise<any | null> {
|
|
272
|
+
if (!isServicesInitialized()) return null
|
|
273
|
+
|
|
274
|
+
try {
|
|
275
|
+
const memory = getMemoryService()
|
|
276
|
+
if (!memory.isChromaDBEnabled()) return null
|
|
277
|
+
|
|
278
|
+
const { TimelineBuilder } = await import('@/temporal/timeline')
|
|
279
|
+
const builder = new TimelineBuilder(
|
|
280
|
+
this.logger,
|
|
281
|
+
memory.chroma.collections,
|
|
282
|
+
memory.chroma.embeddings
|
|
283
|
+
)
|
|
284
|
+
return await withTimeout(
|
|
285
|
+
builder.buildTimeline(options),
|
|
286
|
+
SEARCH_TIMEOUT,
|
|
287
|
+
null
|
|
288
|
+
)
|
|
289
|
+
} catch {
|
|
290
|
+
return null
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Analyze decision evolution for a topic
|
|
296
|
+
*/
|
|
297
|
+
async analyzeEvolution(
|
|
298
|
+
topic: string,
|
|
299
|
+
options?: { project?: string; limit?: number }
|
|
300
|
+
): Promise<any | null> {
|
|
301
|
+
if (!isServicesInitialized()) return null
|
|
302
|
+
|
|
303
|
+
try {
|
|
304
|
+
const memory = getMemoryService()
|
|
305
|
+
if (!memory.isChromaDBEnabled()) return null
|
|
306
|
+
|
|
307
|
+
const { DecisionEvolutionTracker } = await import('@/temporal/evolution')
|
|
308
|
+
const tracker = new DecisionEvolutionTracker(
|
|
309
|
+
this.logger,
|
|
310
|
+
memory.chroma.collections,
|
|
311
|
+
memory.chroma.embeddings
|
|
312
|
+
)
|
|
313
|
+
return await withTimeout(
|
|
314
|
+
tracker.analyzeEvolution(topic, options),
|
|
315
|
+
SEARCH_TIMEOUT,
|
|
316
|
+
null
|
|
317
|
+
)
|
|
318
|
+
} catch {
|
|
319
|
+
return null
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Detect trends
|
|
325
|
+
*/
|
|
326
|
+
async detectTrends(
|
|
327
|
+
options?: { project?: string; periodDays?: number; limit?: number }
|
|
328
|
+
): Promise<any | null> {
|
|
329
|
+
if (!isServicesInitialized()) return null
|
|
330
|
+
|
|
331
|
+
try {
|
|
332
|
+
const memory = getMemoryService()
|
|
333
|
+
if (!memory.isChromaDBEnabled()) return null
|
|
334
|
+
|
|
335
|
+
const { TrendDetector } = await import('@/temporal/trends')
|
|
336
|
+
const detector = new TrendDetector(this.logger, memory.chroma.collections)
|
|
337
|
+
return await withTimeout(
|
|
338
|
+
detector.detectTrends(options),
|
|
339
|
+
SEARCH_TIMEOUT,
|
|
340
|
+
null
|
|
341
|
+
)
|
|
342
|
+
} catch {
|
|
343
|
+
return null
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Multi-hop chain retrieval for complex questions
|
|
349
|
+
*/
|
|
350
|
+
async chainSearch(
|
|
351
|
+
query: string,
|
|
352
|
+
options?: { project?: string; maxHops?: number }
|
|
353
|
+
): Promise<any | null> {
|
|
354
|
+
if (!isServicesInitialized()) return null
|
|
355
|
+
|
|
356
|
+
try {
|
|
357
|
+
const memory = getMemoryService()
|
|
358
|
+
if (!memory.isChromaDBEnabled()) return null
|
|
359
|
+
|
|
360
|
+
const { ChainRetrieval } = await import('@/reasoning/chain-retrieval')
|
|
361
|
+
const chain = new ChainRetrieval(
|
|
362
|
+
this.logger,
|
|
363
|
+
memory.chroma.collections,
|
|
364
|
+
memory.chroma.embeddings
|
|
365
|
+
)
|
|
366
|
+
return await withTimeout(
|
|
367
|
+
chain.retrieve(query, {
|
|
368
|
+
maxHops: options?.maxHops || 3,
|
|
369
|
+
project: options?.project
|
|
370
|
+
}),
|
|
371
|
+
SEARCH_TIMEOUT,
|
|
372
|
+
null
|
|
373
|
+
)
|
|
374
|
+
} catch {
|
|
375
|
+
return null
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* Get recommendations based on context
|
|
381
|
+
*/
|
|
382
|
+
async getRecommendations(
|
|
383
|
+
query: string,
|
|
384
|
+
options?: { project?: string; limit?: number }
|
|
385
|
+
): Promise<any | null> {
|
|
386
|
+
if (!isServicesInitialized()) return null
|
|
387
|
+
|
|
388
|
+
try {
|
|
389
|
+
const memory = getMemoryService()
|
|
390
|
+
if (!memory.isChromaDBEnabled()) return null
|
|
391
|
+
|
|
392
|
+
const { Recommender } = await import('@/prediction/recommender')
|
|
393
|
+
const recommender = new Recommender(
|
|
394
|
+
this.logger,
|
|
395
|
+
memory.chroma.collections,
|
|
396
|
+
memory.chroma.embeddings
|
|
397
|
+
)
|
|
398
|
+
return await withTimeout(
|
|
399
|
+
recommender.getRecommendations(query, {
|
|
400
|
+
project: options?.project,
|
|
401
|
+
limit: options?.limit || 5
|
|
402
|
+
}),
|
|
403
|
+
SEARCH_TIMEOUT,
|
|
404
|
+
null
|
|
405
|
+
)
|
|
406
|
+
} catch {
|
|
407
|
+
return null
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* Find cross-project patterns
|
|
413
|
+
*/
|
|
414
|
+
async findCrossProjectPatterns(
|
|
415
|
+
options?: { query?: string; minProjects?: number; limit?: number }
|
|
416
|
+
): Promise<any | null> {
|
|
417
|
+
if (!isServicesInitialized()) return null
|
|
418
|
+
|
|
419
|
+
try {
|
|
420
|
+
const memory = getMemoryService()
|
|
421
|
+
if (!memory.isChromaDBEnabled()) return null
|
|
422
|
+
|
|
423
|
+
const { PatternGeneralizer } = await import('@/cross-project/generalizer')
|
|
424
|
+
const generalizer = new PatternGeneralizer(
|
|
425
|
+
this.logger,
|
|
426
|
+
memory.chroma.collections,
|
|
427
|
+
memory.chroma.embeddings
|
|
428
|
+
)
|
|
429
|
+
return await withTimeout(
|
|
430
|
+
generalizer.findCrossProjectPatterns(options),
|
|
431
|
+
SEARCH_TIMEOUT,
|
|
432
|
+
null
|
|
433
|
+
)
|
|
434
|
+
} catch {
|
|
435
|
+
return null
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
/**
|
|
440
|
+
* Invalidate cache entries for a project after store/update/delete
|
|
441
|
+
*/
|
|
442
|
+
invalidateCache(project?: string): void {
|
|
443
|
+
try {
|
|
444
|
+
const cache = getSemanticCacheService()
|
|
445
|
+
if (!cache) return
|
|
446
|
+
if (project) {
|
|
447
|
+
cache.invalidateProject(project)
|
|
448
|
+
} else {
|
|
449
|
+
cache.clear()
|
|
450
|
+
}
|
|
451
|
+
} catch {
|
|
452
|
+
// Cache not available
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Routing Types
|
|
3
|
+
* Phase 19: Normalized result types for the brain() router
|
|
4
|
+
*
|
|
5
|
+
* Eliminates scattered `r.decision?.decision || r.content?.slice(0, 300)`
|
|
6
|
+
* by normalizing all search results to a flat structure.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export interface NormalizedResult {
|
|
10
|
+
id: string
|
|
11
|
+
content: string
|
|
12
|
+
score: number
|
|
13
|
+
source: 'decision' | 'pattern' | 'correction' | 'memory' | 'graph' | 'episode' | 'cross-project'
|
|
14
|
+
project?: string
|
|
15
|
+
date?: string
|
|
16
|
+
metadata?: Record<string, unknown>
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Normalize raw searchRaw() results into flat NormalizedResult[]
|
|
21
|
+
* Handles the nested { memory: { id }, decision: { id } } structure
|
|
22
|
+
*/
|
|
23
|
+
export function normalizeSearchResults(rawResults: any[]): NormalizedResult[] {
|
|
24
|
+
return rawResults.map(r => {
|
|
25
|
+
const id = r.memory?.id || r.decision?.id || r.id || ''
|
|
26
|
+
const project = r.memory?.project || r.decision?.project || r.metadata?.project || ''
|
|
27
|
+
const date = r.memory?.createdAt || r.decision?.createdAt || r.metadata?.created_at || ''
|
|
28
|
+
|
|
29
|
+
// Prefer decision content, fall back to memory content
|
|
30
|
+
let content: string
|
|
31
|
+
if (r.decision?.decision) {
|
|
32
|
+
content = r.decision.decision
|
|
33
|
+
if (r.decision.reasoning) {
|
|
34
|
+
content = `**${content}**\n${r.decision.reasoning}`
|
|
35
|
+
}
|
|
36
|
+
} else if (r.memory?.content) {
|
|
37
|
+
content = r.memory.content
|
|
38
|
+
} else if (r.content) {
|
|
39
|
+
content = typeof r.content === 'string' ? r.content : JSON.stringify(r.content)
|
|
40
|
+
} else {
|
|
41
|
+
content = ''
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
id,
|
|
46
|
+
content,
|
|
47
|
+
score: r.similarity || r.score || 0,
|
|
48
|
+
source: r.decision ? 'decision' as const : 'memory' as const,
|
|
49
|
+
project: typeof project === 'string' ? project : String(project),
|
|
50
|
+
date: date instanceof Date ? date.toISOString() : typeof date === 'string' ? date : '',
|
|
51
|
+
metadata: r.metadata || r.decision || r.memory?.metadata || {}
|
|
52
|
+
}
|
|
53
|
+
})
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Normalize pattern search results
|
|
58
|
+
*/
|
|
59
|
+
export function normalizePatternResults(results: any[]): NormalizedResult[] {
|
|
60
|
+
return results.map(p => ({
|
|
61
|
+
id: p.id || '',
|
|
62
|
+
content: p.metadata?.description || p.description || p.content || '',
|
|
63
|
+
score: p.similarity || p.score || 0,
|
|
64
|
+
source: 'pattern' as const,
|
|
65
|
+
project: p.metadata?.project || p.project || '',
|
|
66
|
+
date: p.metadata?.created_at || '',
|
|
67
|
+
metadata: p.metadata || {}
|
|
68
|
+
}))
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Normalize correction search results
|
|
73
|
+
*/
|
|
74
|
+
export function normalizeCorrectionResults(results: any[]): NormalizedResult[] {
|
|
75
|
+
return results.map(c => ({
|
|
76
|
+
id: c.id || '',
|
|
77
|
+
content: `Original: ${c.metadata?.original || c.original || ''}\nFix: ${c.metadata?.correction || c.correction || c.content || ''}`,
|
|
78
|
+
score: c.similarity || c.score || 0,
|
|
79
|
+
source: 'correction' as const,
|
|
80
|
+
project: c.metadata?.project || c.project || '',
|
|
81
|
+
date: c.metadata?.created_at || '',
|
|
82
|
+
metadata: c.metadata || {}
|
|
83
|
+
}))
|
|
84
|
+
}
|
|
@@ -31,23 +31,11 @@ import { handleGetCorrections } from './tools/get-corrections'
|
|
|
31
31
|
import { handleCreateProject } from './tools/create-project'
|
|
32
32
|
import { handleInitProject } from './tools/init-project'
|
|
33
33
|
|
|
34
|
-
// Phase 13 Retrieval handlers
|
|
35
|
-
import { handleRateMemory } from './tools/rate-memory'
|
|
36
|
-
|
|
37
|
-
// Phase 14 Knowledge Graph & Episodic Memory handlers
|
|
38
|
-
import { handleSearchKnowledgeGraph } from './tools/search-knowledge-graph'
|
|
39
|
-
import { handleGetEpisode } from './tools/get-episode'
|
|
40
|
-
import { handleListEpisodes } from './tools/list-episodes'
|
|
41
|
-
|
|
42
|
-
// Phase 15 Advanced Intelligence & Temporal Reasoning handlers
|
|
43
|
-
import { handleGetDecisionTimeline } from './tools/get-decision-timeline'
|
|
44
|
-
import { handleAnalyzeDecisionEvolution } from './tools/analyze-decision-evolution'
|
|
45
|
-
import { handleDetectTrends } from './tools/detect-trends'
|
|
46
|
-
import { handleWhatIfAnalysis } from './tools/what-if-analysis'
|
|
47
|
-
import { handleGetRecommendations } from './tools/get-recommendations'
|
|
48
|
-
import { handleFindCrossProjectPatterns } from './tools/find-cross-project-patterns'
|
|
49
|
-
|
|
50
34
|
// Phase 16 Unified Brain Tool
|
|
35
|
+
// Phase 19: Removed 9 redundant tool handlers (rate_memory, search_knowledge_graph,
|
|
36
|
+
// get_episode, list_episodes, get_decision_timeline, analyze_decision_evolution,
|
|
37
|
+
// detect_trends, what_if_analysis, get_recommendations, find_cross_project_patterns).
|
|
38
|
+
// Their functionality is now absorbed into brain().
|
|
51
39
|
import { handleBrain } from './tools/brain'
|
|
52
40
|
|
|
53
41
|
/**
|
|
@@ -134,39 +122,6 @@ export async function handleCallTool(
|
|
|
134
122
|
case 'get_corrections':
|
|
135
123
|
return await handleGetCorrections(args, logger)
|
|
136
124
|
|
|
137
|
-
// Phase 13 Retrieval Tools
|
|
138
|
-
case 'rate_memory':
|
|
139
|
-
return await handleRateMemory(args, logger)
|
|
140
|
-
|
|
141
|
-
// Phase 14 Knowledge Graph & Episodic Memory Tools
|
|
142
|
-
case 'search_knowledge_graph':
|
|
143
|
-
return await handleSearchKnowledgeGraph(args, logger)
|
|
144
|
-
|
|
145
|
-
case 'get_episode':
|
|
146
|
-
return await handleGetEpisode(args, logger)
|
|
147
|
-
|
|
148
|
-
case 'list_episodes':
|
|
149
|
-
return await handleListEpisodes(args, logger)
|
|
150
|
-
|
|
151
|
-
// Phase 15 Advanced Intelligence & Temporal Reasoning Tools
|
|
152
|
-
case 'get_decision_timeline':
|
|
153
|
-
return await handleGetDecisionTimeline(args, logger)
|
|
154
|
-
|
|
155
|
-
case 'analyze_decision_evolution':
|
|
156
|
-
return await handleAnalyzeDecisionEvolution(args, logger)
|
|
157
|
-
|
|
158
|
-
case 'detect_trends':
|
|
159
|
-
return await handleDetectTrends(args, logger)
|
|
160
|
-
|
|
161
|
-
case 'what_if_analysis':
|
|
162
|
-
return await handleWhatIfAnalysis(args, logger)
|
|
163
|
-
|
|
164
|
-
case 'get_recommendations':
|
|
165
|
-
return await handleGetRecommendations(args, logger)
|
|
166
|
-
|
|
167
|
-
case 'find_cross_project_patterns':
|
|
168
|
-
return await handleFindCrossProjectPatterns(args, logger)
|
|
169
|
-
|
|
170
125
|
// Phase 16 Unified Brain Tool
|
|
171
126
|
case 'brain':
|
|
172
127
|
return await handleBrain(args, logger)
|
|
@@ -24,10 +24,8 @@ export { handleGetCorrections } from './get-corrections'
|
|
|
24
24
|
export { handleCreateProject } from './create-project'
|
|
25
25
|
export { handleInitProject } from './init-project'
|
|
26
26
|
|
|
27
|
-
// Phase
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
export { handleGetRecommendations } from './get-recommendations'
|
|
33
|
-
export { handleFindCrossProjectPatterns } from './find-cross-project-patterns'
|
|
27
|
+
// Phase 19: Removed 9 redundant tool exports.
|
|
28
|
+
// rate_memory, search_knowledge_graph, get_episode, list_episodes,
|
|
29
|
+
// get_decision_timeline, analyze_decision_evolution, detect_trends,
|
|
30
|
+
// what_if_analysis, get_recommendations, find_cross_project_patterns
|
|
31
|
+
// are now absorbed into brain(). Handler files kept for reference.
|
package/src/server/services.ts
CHANGED
|
@@ -11,6 +11,7 @@ import { VaultManager } from '@/vault'
|
|
|
11
11
|
import { ContextManager } from '@/context'
|
|
12
12
|
import { Phase12Manager } from '@/phase12'
|
|
13
13
|
import { RetrievalService } from '@/retrieval/service'
|
|
14
|
+
import { RetrievalPipeline } from '@/retrieval/pipeline'
|
|
14
15
|
import { InMemoryKnowledgeGraph } from '@/knowledge/graph/memory-graph'
|
|
15
16
|
import { GraphSearchEngine } from '@/knowledge/graph/search'
|
|
16
17
|
import { KnowledgeGraphBuilder } from '@/knowledge/graph/builder'
|
|
@@ -36,6 +37,7 @@ export interface Services {
|
|
|
36
37
|
context: ContextManager
|
|
37
38
|
phase12: Phase12Manager
|
|
38
39
|
retrieval: RetrievalService | null
|
|
40
|
+
retrievalPipeline: RetrievalPipeline | null
|
|
39
41
|
knowledgeGraph: KnowledgeGraphServiceContainer | null
|
|
40
42
|
episodeManager: EpisodeManager | null
|
|
41
43
|
semanticCache: SemanticCache | null
|
|
@@ -127,6 +129,23 @@ export async function initializeServices(config: Config, logger: Logger): Promis
|
|
|
127
129
|
serviceLogger.warn('Retrieval service requires ChromaDB, skipping initialization')
|
|
128
130
|
}
|
|
129
131
|
|
|
132
|
+
// Initialize Retrieval Pipeline (Phase 19) — hybrid search with BM25 + semantic + fusion
|
|
133
|
+
let retrievalPipeline: RetrievalPipeline | null = null
|
|
134
|
+
if (config.retrieval?.enabled && memory.isChromaDBEnabled()) {
|
|
135
|
+
try {
|
|
136
|
+
retrievalPipeline = new RetrievalPipeline(
|
|
137
|
+
logger,
|
|
138
|
+
memory.chroma.collections,
|
|
139
|
+
memory.chroma.embeddings,
|
|
140
|
+
config.retrieval
|
|
141
|
+
)
|
|
142
|
+
await retrievalPipeline.initialize()
|
|
143
|
+
serviceLogger.info('Retrieval pipeline initialized (hybrid search)')
|
|
144
|
+
} catch (error) {
|
|
145
|
+
serviceLogger.warn({ error }, 'Failed to initialize retrieval pipeline, continuing without hybrid search')
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
130
149
|
// Initialize Knowledge Graph Service (Phase 14)
|
|
131
150
|
let knowledgeGraph: KnowledgeGraphServiceContainer | null = null
|
|
132
151
|
if (config.knowledge?.graph?.enabled !== false) {
|
|
@@ -221,6 +240,7 @@ export async function initializeServices(config: Config, logger: Logger): Promis
|
|
|
221
240
|
context,
|
|
222
241
|
phase12,
|
|
223
242
|
retrieval,
|
|
243
|
+
retrievalPipeline,
|
|
224
244
|
knowledgeGraph,
|
|
225
245
|
episodeManager,
|
|
226
246
|
semanticCache,
|
|
@@ -283,6 +303,14 @@ export function getRetrievalService(): RetrievalService | null {
|
|
|
283
303
|
return getServices().retrieval
|
|
284
304
|
}
|
|
285
305
|
|
|
306
|
+
/**
|
|
307
|
+
* Get Retrieval Pipeline (Phase 19)
|
|
308
|
+
* Returns null if hybrid search is not enabled
|
|
309
|
+
*/
|
|
310
|
+
export function getRetrievalPipeline(): RetrievalPipeline | null {
|
|
311
|
+
return getServices().retrievalPipeline
|
|
312
|
+
}
|
|
313
|
+
|
|
286
314
|
/**
|
|
287
315
|
* Get Knowledge Graph service
|
|
288
316
|
* Returns null if knowledge graph is not enabled
|