claude-brain 0.9.3 → 0.13.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/assets/CLAUDE.md +9 -1
- package/package.json +1 -1
- package/src/automation/phase12-manager.ts +456 -0
- package/src/automation/project-detector.ts +13 -0
- package/src/automation/repo-scanner.ts +205 -0
- package/src/cli/bin.ts +30 -0
- package/src/cli/commands/git-hook.ts +189 -0
- package/src/cli/commands/hooks.ts +8 -9
- package/src/cli/commands/init.ts +98 -0
- package/src/cli/commands/serve.ts +7 -20
- package/src/cli/commands/update.ts +3 -3
- package/src/config/defaults.ts +4 -1
- package/src/config/schema.ts +27 -7
- package/src/hooks/brain-hook.ts +8 -6
- package/src/hooks/capture.ts +9 -2
- package/src/hooks/git-capture.ts +94 -0
- package/src/hooks/git-hook-installer.ts +197 -0
- package/src/hooks/index.ts +1 -0
- package/src/hooks/session-tracker.ts +79 -3
- package/src/hooks/types.ts +1 -1
- package/src/intelligence/index.ts +24 -0
- package/src/knowledge/graph/builder.ts +26 -0
- package/src/memory/chroma/store.ts +18 -2
- package/src/memory/episodic/manager.ts +17 -0
- package/src/memory/index.ts +48 -18
- package/src/phase12/index.ts +3 -454
- package/src/routing/intent-classifier.ts +107 -9
- package/src/routing/response-filter.ts +50 -17
- package/src/routing/router.ts +472 -224
- package/src/routing/search-engine.ts +464 -0
- package/src/routing/types.ts +84 -0
- package/src/server/handlers/call-tool.ts +4 -49
- package/src/server/handlers/tools/analyze-decision-evolution.ts +1 -1
- package/src/server/handlers/tools/detect-trends.ts +1 -1
- package/src/server/handlers/tools/find-cross-project-patterns.ts +1 -1
- package/src/server/handlers/tools/get-decision-timeline.ts +2 -2
- package/src/server/handlers/tools/get-recommendations.ts +1 -1
- package/src/server/handlers/tools/index.ts +5 -7
- package/src/server/handlers/tools/what-if-analysis.ts +1 -1
- package/src/server/providers/resources.ts +195 -0
- package/src/server/services.ts +81 -6
- package/src/tools/schemas.ts +7 -329
- package/src/utils/phase12-helper.ts +2 -2
- package/src/utils/timing.ts +47 -0
- package/src/vault/writer.ts +22 -2
- /package/src/{cross-project → intelligence/cross-project}/affinity.ts +0 -0
- /package/src/{cross-project → intelligence/cross-project}/generalizer.ts +0 -0
- /package/src/{cross-project → intelligence/cross-project}/index.ts +0 -0
- /package/src/{cross-project → intelligence/cross-project}/transfer.ts +0 -0
- /package/src/{optimization → intelligence/optimization}/index.ts +0 -0
- /package/src/{optimization → intelligence/optimization}/precompute.ts +0 -0
- /package/src/{optimization → intelligence/optimization}/semantic-cache.ts +0 -0
- /package/src/{prediction → intelligence/prediction}/context-anticipator.ts +0 -0
- /package/src/{prediction → intelligence/prediction}/decision-predictor.ts +0 -0
- /package/src/{prediction → intelligence/prediction}/index.ts +0 -0
- /package/src/{prediction → intelligence/prediction}/recommender.ts +0 -0
- /package/src/{reasoning → intelligence/reasoning}/chain-retrieval.ts +0 -0
- /package/src/{reasoning → intelligence/reasoning}/counterfactual.ts +0 -0
- /package/src/{reasoning → intelligence/reasoning}/index.ts +0 -0
- /package/src/{reasoning → intelligence/reasoning}/synthesizer.ts +0 -0
- /package/src/{temporal → intelligence/temporal}/evolution.ts +0 -0
- /package/src/{temporal → intelligence/temporal}/index.ts +0 -0
- /package/src/{temporal → intelligence/temporal}/query-processor.ts +0 -0
- /package/src/{temporal → intelligence/temporal}/timeline.ts +0 -0
- /package/src/{temporal → intelligence/temporal}/trends.ts +0 -0
|
@@ -0,0 +1,464 @@
|
|
|
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
|
+
import { timed } from '@/utils/timing'
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Wrap a promise with a timeout. Returns fallback if the promise doesn't resolve in time.
|
|
24
|
+
*/
|
|
25
|
+
function withTimeout<T>(promise: Promise<T>, ms: number, fallback: T): Promise<T> {
|
|
26
|
+
return Promise.race([
|
|
27
|
+
promise,
|
|
28
|
+
new Promise<T>(resolve => setTimeout(() => resolve(fallback), ms))
|
|
29
|
+
])
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const SEARCH_TIMEOUT = 5000 // 5s timeout for normal searches
|
|
33
|
+
const INTELLIGENCE_TIMEOUT = 12000 // 12s timeout for bulk analytical features
|
|
34
|
+
|
|
35
|
+
export class SearchEngine {
|
|
36
|
+
private logger: Logger
|
|
37
|
+
|
|
38
|
+
constructor(logger: Logger) {
|
|
39
|
+
this.logger = logger.child({ component: 'search-engine' })
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Enhanced search — uses hybrid retrieval pipeline when available,
|
|
44
|
+
* falls back to plain searchRaw().
|
|
45
|
+
* Wraps with semantic cache for repeated queries.
|
|
46
|
+
*/
|
|
47
|
+
async enhancedSearch(
|
|
48
|
+
query: string,
|
|
49
|
+
options?: { project?: string; limit?: number; minSimilarity?: number }
|
|
50
|
+
): Promise<NormalizedResult[]> {
|
|
51
|
+
return timed('brain:search', () => this._enhancedSearchInner(query, options))
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
private async _enhancedSearchInner(
|
|
55
|
+
query: string,
|
|
56
|
+
options?: { project?: string; limit?: number; minSimilarity?: number }
|
|
57
|
+
): Promise<NormalizedResult[]> {
|
|
58
|
+
if (!isServicesInitialized()) return []
|
|
59
|
+
|
|
60
|
+
const cacheKey = `search:${query}:${options?.project || ''}:${options?.limit || 5}`
|
|
61
|
+
|
|
62
|
+
// Check semantic cache first
|
|
63
|
+
const cache = getSemanticCacheService()
|
|
64
|
+
if (cache) {
|
|
65
|
+
try {
|
|
66
|
+
const cached = await cache.get(cacheKey)
|
|
67
|
+
if (cached) {
|
|
68
|
+
this.logger.debug({ query }, 'Search cache hit')
|
|
69
|
+
return cached as NormalizedResult[]
|
|
70
|
+
}
|
|
71
|
+
} catch {
|
|
72
|
+
// Cache miss or error, continue
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Try hybrid search via RetrievalPipeline
|
|
77
|
+
const pipeline = getRetrievalPipeline()
|
|
78
|
+
if (pipeline) {
|
|
79
|
+
try {
|
|
80
|
+
const hybridResults = await withTimeout(
|
|
81
|
+
pipeline.search(query, {
|
|
82
|
+
project: options?.project,
|
|
83
|
+
limit: options?.limit || 5,
|
|
84
|
+
minSimilarity: options?.minSimilarity || 0.3
|
|
85
|
+
}),
|
|
86
|
+
SEARCH_TIMEOUT,
|
|
87
|
+
[]
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
if (hybridResults.length > 0) {
|
|
91
|
+
const normalized = hybridResults.map((r: any) => ({
|
|
92
|
+
id: r.id || '',
|
|
93
|
+
content: r.content || r.document || '',
|
|
94
|
+
score: r.score || r.similarity || 0,
|
|
95
|
+
source: 'decision' as const,
|
|
96
|
+
project: r.metadata?.project || options?.project || '',
|
|
97
|
+
date: r.metadata?.created_at || '',
|
|
98
|
+
metadata: r.metadata || {}
|
|
99
|
+
}))
|
|
100
|
+
|
|
101
|
+
// Cache results
|
|
102
|
+
if (cache) {
|
|
103
|
+
try { await cache.set(cacheKey, normalized) } catch {}
|
|
104
|
+
}
|
|
105
|
+
return normalized
|
|
106
|
+
}
|
|
107
|
+
} catch (error) {
|
|
108
|
+
this.logger.debug({ error }, 'Hybrid search failed, falling back to plain search')
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Fallback: plain searchRaw
|
|
113
|
+
return this.plainSearch(query, options, cache, cacheKey)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Plain search using memory.searchRaw() — always available.
|
|
118
|
+
*/
|
|
119
|
+
async plainSearch(
|
|
120
|
+
query: string,
|
|
121
|
+
options?: { project?: string; limit?: number; minSimilarity?: number },
|
|
122
|
+
cache?: any,
|
|
123
|
+
cacheKey?: string
|
|
124
|
+
): Promise<NormalizedResult[]> {
|
|
125
|
+
if (!isServicesInitialized()) return []
|
|
126
|
+
|
|
127
|
+
const memory = getMemoryService()
|
|
128
|
+
try {
|
|
129
|
+
const rawResults = await withTimeout(
|
|
130
|
+
memory.searchRaw(query, {
|
|
131
|
+
project: options?.project,
|
|
132
|
+
limit: options?.limit || 5,
|
|
133
|
+
minSimilarity: options?.minSimilarity || 0.3
|
|
134
|
+
}),
|
|
135
|
+
SEARCH_TIMEOUT,
|
|
136
|
+
[]
|
|
137
|
+
)
|
|
138
|
+
const normalized = normalizeSearchResults(rawResults)
|
|
139
|
+
|
|
140
|
+
// Cache results
|
|
141
|
+
if (cache && cacheKey) {
|
|
142
|
+
try { await cache.set(cacheKey, normalized) } catch {}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return normalized
|
|
146
|
+
} catch {
|
|
147
|
+
return []
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Search patterns with normalization and timeout
|
|
153
|
+
*/
|
|
154
|
+
async searchPatterns(
|
|
155
|
+
query: string,
|
|
156
|
+
options?: { project?: string; limit?: number; minSimilarity?: number }
|
|
157
|
+
): Promise<NormalizedResult[]> {
|
|
158
|
+
if (!isServicesInitialized()) return []
|
|
159
|
+
const memory = getMemoryService()
|
|
160
|
+
try {
|
|
161
|
+
const results = await withTimeout(
|
|
162
|
+
memory.searchPatterns(query, {
|
|
163
|
+
project: options?.project,
|
|
164
|
+
limit: options?.limit || 3,
|
|
165
|
+
minSimilarity: options?.minSimilarity || 0.3
|
|
166
|
+
}),
|
|
167
|
+
SEARCH_TIMEOUT,
|
|
168
|
+
[]
|
|
169
|
+
)
|
|
170
|
+
return normalizePatternResults(results)
|
|
171
|
+
} catch {
|
|
172
|
+
return []
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Search corrections with normalization and timeout
|
|
178
|
+
*/
|
|
179
|
+
async searchCorrections(
|
|
180
|
+
query: string,
|
|
181
|
+
options?: { project?: string; limit?: number; minSimilarity?: number }
|
|
182
|
+
): Promise<NormalizedResult[]> {
|
|
183
|
+
if (!isServicesInitialized()) return []
|
|
184
|
+
const memory = getMemoryService()
|
|
185
|
+
try {
|
|
186
|
+
const results = await withTimeout(
|
|
187
|
+
memory.searchCorrections(query, {
|
|
188
|
+
project: options?.project,
|
|
189
|
+
limit: options?.limit || 3,
|
|
190
|
+
minSimilarity: options?.minSimilarity || 0.3
|
|
191
|
+
}),
|
|
192
|
+
SEARCH_TIMEOUT,
|
|
193
|
+
[]
|
|
194
|
+
)
|
|
195
|
+
return normalizeCorrectionResults(results)
|
|
196
|
+
} catch {
|
|
197
|
+
return []
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Search knowledge graph for related concepts
|
|
203
|
+
*/
|
|
204
|
+
async searchGraph(query: string, limit: number = 5): Promise<NormalizedResult[]> {
|
|
205
|
+
try {
|
|
206
|
+
const kgService = getKnowledgeGraphService()
|
|
207
|
+
if (!kgService?.search) return []
|
|
208
|
+
|
|
209
|
+
const graphResults = kgService.search.search({ query, limit })
|
|
210
|
+
if (!graphResults?.nodes?.length) return []
|
|
211
|
+
|
|
212
|
+
return graphResults.nodes.map((n: any) => ({
|
|
213
|
+
id: n.id || '',
|
|
214
|
+
content: `**${n.label || n.name}** (${n.type})${n.properties?.description ? `\n${n.properties.description}` : ''}`,
|
|
215
|
+
score: graphResults.scores.get(n.id) || 0.5,
|
|
216
|
+
source: 'graph' as const,
|
|
217
|
+
project: n.properties?.project || '',
|
|
218
|
+
date: '',
|
|
219
|
+
metadata: n.properties || {}
|
|
220
|
+
}))
|
|
221
|
+
} catch {
|
|
222
|
+
return []
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Temporal search — parse temporal phrases from query, filter results by date range.
|
|
228
|
+
* Uses TemporalQueryProcessor when available.
|
|
229
|
+
*/
|
|
230
|
+
async temporalSearch(
|
|
231
|
+
query: string,
|
|
232
|
+
options?: { project?: string; limit?: number }
|
|
233
|
+
): Promise<{ results: NormalizedResult[]; cleanedQuery: string }> {
|
|
234
|
+
if (!isServicesInitialized()) return { results: [], cleanedQuery: query }
|
|
235
|
+
|
|
236
|
+
try {
|
|
237
|
+
// Dynamic import to avoid hard dependency
|
|
238
|
+
const { TemporalQueryProcessor } = await import('@/intelligence/temporal/query-processor')
|
|
239
|
+
const processor = new TemporalQueryProcessor(this.logger)
|
|
240
|
+
const parsed = processor.parse(query)
|
|
241
|
+
|
|
242
|
+
if (parsed.intent === 'none') {
|
|
243
|
+
// No temporal expressions found, use normal search
|
|
244
|
+
const results = await this.enhancedSearch(query, options)
|
|
245
|
+
return { results, cleanedQuery: query }
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Search with cleaned query (temporal phrases removed)
|
|
249
|
+
const results = await this.enhancedSearch(parsed.cleaned || query, {
|
|
250
|
+
...options,
|
|
251
|
+
limit: (options?.limit || 5) * 2 // Fetch more since we'll filter by date
|
|
252
|
+
})
|
|
253
|
+
|
|
254
|
+
// Filter results by date range
|
|
255
|
+
const filtered = results.filter(r => {
|
|
256
|
+
if (!r.date) return true // Keep results without dates
|
|
257
|
+
const date = new Date(r.date)
|
|
258
|
+
if (isNaN(date.getTime())) return true
|
|
259
|
+
if (parsed.startDate && date < parsed.startDate) return false
|
|
260
|
+
if (parsed.endDate && date > parsed.endDate) return false
|
|
261
|
+
return true
|
|
262
|
+
})
|
|
263
|
+
|
|
264
|
+
return {
|
|
265
|
+
results: filtered.slice(0, options?.limit || 5),
|
|
266
|
+
cleanedQuery: parsed.cleaned || query
|
|
267
|
+
}
|
|
268
|
+
} catch {
|
|
269
|
+
// TemporalQueryProcessor not available, fall back
|
|
270
|
+
const results = await this.enhancedSearch(query, options)
|
|
271
|
+
return { results, cleanedQuery: query }
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Build a timeline for a topic/project
|
|
277
|
+
*/
|
|
278
|
+
async buildTimeline(
|
|
279
|
+
options?: { project?: string; topic?: string; limit?: number }
|
|
280
|
+
): Promise<any | null> {
|
|
281
|
+
if (!isServicesInitialized()) return null
|
|
282
|
+
|
|
283
|
+
try {
|
|
284
|
+
const memory = getMemoryService()
|
|
285
|
+
if (!memory.isChromaDBEnabled()) return null
|
|
286
|
+
|
|
287
|
+
const { TimelineBuilder } = await import('@/intelligence/temporal/timeline')
|
|
288
|
+
const builder = new TimelineBuilder(
|
|
289
|
+
this.logger,
|
|
290
|
+
memory.chroma.collections,
|
|
291
|
+
memory.chroma.embeddings
|
|
292
|
+
)
|
|
293
|
+
return await withTimeout(
|
|
294
|
+
builder.buildTimeline(options),
|
|
295
|
+
SEARCH_TIMEOUT,
|
|
296
|
+
null
|
|
297
|
+
)
|
|
298
|
+
} catch {
|
|
299
|
+
return null
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Analyze decision evolution for a topic
|
|
305
|
+
*/
|
|
306
|
+
async analyzeEvolution(
|
|
307
|
+
topic: string,
|
|
308
|
+
options?: { project?: string; limit?: number }
|
|
309
|
+
): Promise<any | null> {
|
|
310
|
+
if (!isServicesInitialized()) return null
|
|
311
|
+
|
|
312
|
+
try {
|
|
313
|
+
const memory = getMemoryService()
|
|
314
|
+
if (!memory.isChromaDBEnabled()) return null
|
|
315
|
+
|
|
316
|
+
const { DecisionEvolutionTracker } = await import('@/intelligence/temporal/evolution')
|
|
317
|
+
const tracker = new DecisionEvolutionTracker(
|
|
318
|
+
this.logger,
|
|
319
|
+
memory.chroma.collections,
|
|
320
|
+
memory.chroma.embeddings
|
|
321
|
+
)
|
|
322
|
+
return await withTimeout(
|
|
323
|
+
tracker.analyzeEvolution(topic, options),
|
|
324
|
+
INTELLIGENCE_TIMEOUT,
|
|
325
|
+
null
|
|
326
|
+
)
|
|
327
|
+
} catch {
|
|
328
|
+
return null
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Detect trends
|
|
334
|
+
*/
|
|
335
|
+
async detectTrends(
|
|
336
|
+
options?: { project?: string; periodDays?: number; limit?: number }
|
|
337
|
+
): Promise<any | null> {
|
|
338
|
+
if (!isServicesInitialized()) return null
|
|
339
|
+
|
|
340
|
+
try {
|
|
341
|
+
const memory = getMemoryService()
|
|
342
|
+
if (!memory.isChromaDBEnabled()) return null
|
|
343
|
+
|
|
344
|
+
const { TrendDetector } = await import('@/intelligence/temporal/trends')
|
|
345
|
+
const detector = new TrendDetector(this.logger, memory.chroma.collections)
|
|
346
|
+
return await withTimeout(
|
|
347
|
+
detector.detectTrends(options),
|
|
348
|
+
INTELLIGENCE_TIMEOUT,
|
|
349
|
+
null
|
|
350
|
+
)
|
|
351
|
+
} catch {
|
|
352
|
+
return null
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Multi-hop chain retrieval for complex questions
|
|
358
|
+
*/
|
|
359
|
+
async chainSearch(
|
|
360
|
+
query: string,
|
|
361
|
+
options?: { project?: string; maxHops?: number }
|
|
362
|
+
): Promise<any | null> {
|
|
363
|
+
if (!isServicesInitialized()) return null
|
|
364
|
+
|
|
365
|
+
try {
|
|
366
|
+
const memory = getMemoryService()
|
|
367
|
+
if (!memory.isChromaDBEnabled()) return null
|
|
368
|
+
|
|
369
|
+
const { ChainRetrieval } = await import('@/intelligence/reasoning/chain-retrieval')
|
|
370
|
+
const chain = new ChainRetrieval(
|
|
371
|
+
this.logger,
|
|
372
|
+
memory.chroma.collections,
|
|
373
|
+
memory.chroma.embeddings
|
|
374
|
+
)
|
|
375
|
+
return await withTimeout(
|
|
376
|
+
chain.retrieve(query, {
|
|
377
|
+
maxHops: options?.maxHops || 3,
|
|
378
|
+
project: options?.project
|
|
379
|
+
}),
|
|
380
|
+
INTELLIGENCE_TIMEOUT,
|
|
381
|
+
null
|
|
382
|
+
)
|
|
383
|
+
} catch {
|
|
384
|
+
return null
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* Get recommendations based on context
|
|
390
|
+
*/
|
|
391
|
+
async getRecommendations(
|
|
392
|
+
query: string,
|
|
393
|
+
options?: { project?: string; limit?: number }
|
|
394
|
+
): Promise<any | null> {
|
|
395
|
+
if (!isServicesInitialized()) return null
|
|
396
|
+
|
|
397
|
+
try {
|
|
398
|
+
const memory = getMemoryService()
|
|
399
|
+
if (!memory.isChromaDBEnabled()) return null
|
|
400
|
+
|
|
401
|
+
const { Recommender } = await import('@/intelligence/prediction/recommender')
|
|
402
|
+
const recommender = new Recommender(
|
|
403
|
+
this.logger,
|
|
404
|
+
memory.chroma.collections,
|
|
405
|
+
memory.chroma.embeddings
|
|
406
|
+
)
|
|
407
|
+
return await withTimeout(
|
|
408
|
+
recommender.getRecommendations(query, {
|
|
409
|
+
project: options?.project,
|
|
410
|
+
limit: options?.limit || 5
|
|
411
|
+
}),
|
|
412
|
+
INTELLIGENCE_TIMEOUT,
|
|
413
|
+
null
|
|
414
|
+
)
|
|
415
|
+
} catch {
|
|
416
|
+
return null
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* Find cross-project patterns
|
|
422
|
+
*/
|
|
423
|
+
async findCrossProjectPatterns(
|
|
424
|
+
options?: { query?: string; minProjects?: number; limit?: number }
|
|
425
|
+
): Promise<any | null> {
|
|
426
|
+
if (!isServicesInitialized()) return null
|
|
427
|
+
|
|
428
|
+
try {
|
|
429
|
+
const memory = getMemoryService()
|
|
430
|
+
if (!memory.isChromaDBEnabled()) return null
|
|
431
|
+
|
|
432
|
+
const { PatternGeneralizer } = await import('@/intelligence/cross-project/generalizer')
|
|
433
|
+
const generalizer = new PatternGeneralizer(
|
|
434
|
+
this.logger,
|
|
435
|
+
memory.chroma.collections,
|
|
436
|
+
memory.chroma.embeddings
|
|
437
|
+
)
|
|
438
|
+
return await withTimeout(
|
|
439
|
+
generalizer.findCrossProjectPatterns(options),
|
|
440
|
+
INTELLIGENCE_TIMEOUT,
|
|
441
|
+
null
|
|
442
|
+
)
|
|
443
|
+
} catch {
|
|
444
|
+
return null
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
/**
|
|
449
|
+
* Invalidate cache entries for a project after store/update/delete
|
|
450
|
+
*/
|
|
451
|
+
invalidateCache(project?: string): void {
|
|
452
|
+
try {
|
|
453
|
+
const cache = getSemanticCacheService()
|
|
454
|
+
if (!cache) return
|
|
455
|
+
if (project) {
|
|
456
|
+
cache.invalidateProject(project)
|
|
457
|
+
} else {
|
|
458
|
+
cache.clear()
|
|
459
|
+
}
|
|
460
|
+
} catch {
|
|
461
|
+
// Cache not available
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
}
|
|
@@ -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)
|
|
@@ -12,7 +12,7 @@ import { withMemoryIndicator, formatMemoryStats } from '@/server/utils/memory-in
|
|
|
12
12
|
import { ErrorHandler } from '@/server/utils/error-handler'
|
|
13
13
|
import { McpError, ErrorCode } from '@modelcontextprotocol/sdk/types.js'
|
|
14
14
|
import { AnalyzeDecisionEvolutionSchema } from './schemas'
|
|
15
|
-
import { DecisionEvolutionTracker } from '@/temporal/evolution'
|
|
15
|
+
import { DecisionEvolutionTracker } from '@/intelligence/temporal/evolution'
|
|
16
16
|
|
|
17
17
|
export async function handleAnalyzeDecisionEvolution(
|
|
18
18
|
args: unknown,
|
|
@@ -12,7 +12,7 @@ import { withMemoryIndicator, formatMemoryStats } from '@/server/utils/memory-in
|
|
|
12
12
|
import { ErrorHandler } from '@/server/utils/error-handler'
|
|
13
13
|
import { McpError, ErrorCode } from '@modelcontextprotocol/sdk/types.js'
|
|
14
14
|
import { DetectTrendsSchema } from './schemas'
|
|
15
|
-
import { TrendDetector } from '@/temporal/trends'
|
|
15
|
+
import { TrendDetector } from '@/intelligence/temporal/trends'
|
|
16
16
|
|
|
17
17
|
export async function handleDetectTrends(
|
|
18
18
|
args: unknown,
|
|
@@ -12,7 +12,7 @@ import { withMemoryIndicator, formatMemoryStats } from '@/server/utils/memory-in
|
|
|
12
12
|
import { ErrorHandler } from '@/server/utils/error-handler'
|
|
13
13
|
import { McpError, ErrorCode } from '@modelcontextprotocol/sdk/types.js'
|
|
14
14
|
import { FindCrossProjectPatternsSchema } from './schemas'
|
|
15
|
-
import { PatternGeneralizer } from '@/cross-project/generalizer'
|
|
15
|
+
import { PatternGeneralizer } from '@/intelligence/cross-project/generalizer'
|
|
16
16
|
|
|
17
17
|
export async function handleFindCrossProjectPatterns(
|
|
18
18
|
args: unknown,
|
|
@@ -12,8 +12,8 @@ import { withMemoryIndicator, formatMemoryStats } from '@/server/utils/memory-in
|
|
|
12
12
|
import { ErrorHandler } from '@/server/utils/error-handler'
|
|
13
13
|
import { McpError, ErrorCode } from '@modelcontextprotocol/sdk/types.js'
|
|
14
14
|
import { GetDecisionTimelineSchema } from './schemas'
|
|
15
|
-
import { TimelineBuilder } from '@/temporal/timeline'
|
|
16
|
-
import { TemporalQueryProcessor } from '@/temporal/query-processor'
|
|
15
|
+
import { TimelineBuilder } from '@/intelligence/temporal/timeline'
|
|
16
|
+
import { TemporalQueryProcessor } from '@/intelligence/temporal/query-processor'
|
|
17
17
|
|
|
18
18
|
export async function handleGetDecisionTimeline(
|
|
19
19
|
args: unknown,
|
|
@@ -12,7 +12,7 @@ import { withMemoryIndicator, formatMemoryStats } from '@/server/utils/memory-in
|
|
|
12
12
|
import { ErrorHandler } from '@/server/utils/error-handler'
|
|
13
13
|
import { McpError, ErrorCode } from '@modelcontextprotocol/sdk/types.js'
|
|
14
14
|
import { GetRecommendationsSchema } from './schemas'
|
|
15
|
-
import { Recommender } from '@/prediction/recommender'
|
|
15
|
+
import { Recommender } from '@/intelligence/prediction/recommender'
|
|
16
16
|
|
|
17
17
|
export async function handleGetRecommendations(
|
|
18
18
|
args: unknown,
|
|
@@ -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.
|
|
@@ -12,7 +12,7 @@ import { withMemoryIndicator } from '@/server/utils/memory-indicator'
|
|
|
12
12
|
import { ErrorHandler } from '@/server/utils/error-handler'
|
|
13
13
|
import { McpError, ErrorCode } from '@modelcontextprotocol/sdk/types.js'
|
|
14
14
|
import { WhatIfAnalysisSchema } from './schemas'
|
|
15
|
-
import { CounterfactualAnalyzer } from '@/reasoning/counterfactual'
|
|
15
|
+
import { CounterfactualAnalyzer } from '@/intelligence/reasoning/counterfactual'
|
|
16
16
|
|
|
17
17
|
export async function handleWhatIfAnalysis(
|
|
18
18
|
args: unknown,
|