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
|
@@ -1,475 +1,475 @@
|
|
|
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
|
-
* Sort results so same-project entries come first (safety net for cross-project noise).
|
|
44
|
-
*/
|
|
45
|
-
private prioritizeProject(results: NormalizedResult[], project?: string): NormalizedResult[] {
|
|
46
|
-
if (!project || results.length === 0) return results
|
|
47
|
-
const sameProject = results.filter(r => r.project === project)
|
|
48
|
-
const otherProject = results.filter(r => r.project !== project)
|
|
49
|
-
if (sameProject.length > 0) return [...sameProject, ...otherProject]
|
|
50
|
-
return results
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Enhanced search — uses hybrid retrieval pipeline when available,
|
|
55
|
-
* falls back to plain searchRaw().
|
|
56
|
-
* Wraps with semantic cache for repeated queries.
|
|
57
|
-
*/
|
|
58
|
-
async enhancedSearch(
|
|
59
|
-
query: string,
|
|
60
|
-
options?: { project?: string; limit?: number; minSimilarity?: number }
|
|
61
|
-
): Promise<NormalizedResult[]> {
|
|
62
|
-
return timed('brain:search', () => this._enhancedSearchInner(query, options))
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
private async _enhancedSearchInner(
|
|
66
|
-
query: string,
|
|
67
|
-
options?: { project?: string; limit?: number; minSimilarity?: number }
|
|
68
|
-
): Promise<NormalizedResult[]> {
|
|
69
|
-
if (!isServicesInitialized()) return []
|
|
70
|
-
|
|
71
|
-
const cacheKey = `search:${query}:${options?.project || ''}:${options?.limit || 5}`
|
|
72
|
-
|
|
73
|
-
// Check semantic cache first
|
|
74
|
-
const cache = getSemanticCacheService()
|
|
75
|
-
if (cache) {
|
|
76
|
-
try {
|
|
77
|
-
const cached = await cache.get(cacheKey)
|
|
78
|
-
if (cached) {
|
|
79
|
-
this.logger.debug({ query }, 'Search cache hit')
|
|
80
|
-
return cached as NormalizedResult[]
|
|
81
|
-
}
|
|
82
|
-
} catch {
|
|
83
|
-
// Cache miss or error, continue
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// Try hybrid search via RetrievalPipeline
|
|
88
|
-
const pipeline = getRetrievalPipeline()
|
|
89
|
-
if (pipeline) {
|
|
90
|
-
try {
|
|
91
|
-
const hybridResults = await withTimeout(
|
|
92
|
-
pipeline.search(query, {
|
|
93
|
-
project: options?.project,
|
|
94
|
-
limit: options?.limit || 5,
|
|
95
|
-
minSimilarity: options?.minSimilarity || 0.3
|
|
96
|
-
}),
|
|
97
|
-
SEARCH_TIMEOUT,
|
|
98
|
-
[]
|
|
99
|
-
)
|
|
100
|
-
|
|
101
|
-
if (hybridResults.length > 0) {
|
|
102
|
-
const normalized = this.prioritizeProject(hybridResults.map((r: any) => ({
|
|
103
|
-
id: r.id || '',
|
|
104
|
-
content: r.content
|
|
105
|
-
score: r.score || r.similarity || 0,
|
|
106
|
-
source: 'decision' as const,
|
|
107
|
-
project: r.metadata?.project || options?.project || '',
|
|
108
|
-
date: r.metadata?.created_at || '',
|
|
109
|
-
metadata: r.metadata || {}
|
|
110
|
-
})), options?.project)
|
|
111
|
-
|
|
112
|
-
// Cache results
|
|
113
|
-
if (cache) {
|
|
114
|
-
try { await cache.set(cacheKey, normalized) } catch {}
|
|
115
|
-
}
|
|
116
|
-
return normalized
|
|
117
|
-
}
|
|
118
|
-
} catch (error) {
|
|
119
|
-
this.logger.debug({ error }, 'Hybrid search failed, falling back to plain search')
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// Fallback: plain searchRaw
|
|
124
|
-
return this.plainSearch(query, options, cache, cacheKey)
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
/**
|
|
128
|
-
* Plain search using memory.searchRaw() — always available.
|
|
129
|
-
*/
|
|
130
|
-
async plainSearch(
|
|
131
|
-
query: string,
|
|
132
|
-
options?: { project?: string; limit?: number; minSimilarity?: number },
|
|
133
|
-
cache?: any,
|
|
134
|
-
cacheKey?: string
|
|
135
|
-
): Promise<NormalizedResult[]> {
|
|
136
|
-
if (!isServicesInitialized()) return []
|
|
137
|
-
|
|
138
|
-
const memory = getMemoryService()
|
|
139
|
-
try {
|
|
140
|
-
const rawResults = await withTimeout(
|
|
141
|
-
memory.searchRaw(query, {
|
|
142
|
-
project: options?.project,
|
|
143
|
-
limit: options?.limit || 5,
|
|
144
|
-
minSimilarity: options?.minSimilarity || 0.3
|
|
145
|
-
}),
|
|
146
|
-
SEARCH_TIMEOUT,
|
|
147
|
-
[]
|
|
148
|
-
)
|
|
149
|
-
const normalized = this.prioritizeProject(normalizeSearchResults(rawResults), options?.project)
|
|
150
|
-
|
|
151
|
-
// Cache results
|
|
152
|
-
if (cache && cacheKey) {
|
|
153
|
-
try { await cache.set(cacheKey, normalized) } catch {}
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
return normalized
|
|
157
|
-
} catch {
|
|
158
|
-
return []
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
/**
|
|
163
|
-
* Search patterns with normalization and timeout
|
|
164
|
-
*/
|
|
165
|
-
async searchPatterns(
|
|
166
|
-
query: string,
|
|
167
|
-
options?: { project?: string; limit?: number; minSimilarity?: number }
|
|
168
|
-
): Promise<NormalizedResult[]> {
|
|
169
|
-
if (!isServicesInitialized()) return []
|
|
170
|
-
const memory = getMemoryService()
|
|
171
|
-
try {
|
|
172
|
-
const results = await withTimeout(
|
|
173
|
-
memory.searchPatterns(query, {
|
|
174
|
-
project: options?.project,
|
|
175
|
-
limit: options?.limit || 3,
|
|
176
|
-
minSimilarity: options?.minSimilarity || 0.3
|
|
177
|
-
}),
|
|
178
|
-
SEARCH_TIMEOUT,
|
|
179
|
-
[]
|
|
180
|
-
)
|
|
181
|
-
return normalizePatternResults(results)
|
|
182
|
-
} catch {
|
|
183
|
-
return []
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
/**
|
|
188
|
-
* Search corrections with normalization and timeout
|
|
189
|
-
*/
|
|
190
|
-
async searchCorrections(
|
|
191
|
-
query: string,
|
|
192
|
-
options?: { project?: string; limit?: number; minSimilarity?: number }
|
|
193
|
-
): Promise<NormalizedResult[]> {
|
|
194
|
-
if (!isServicesInitialized()) return []
|
|
195
|
-
const memory = getMemoryService()
|
|
196
|
-
try {
|
|
197
|
-
const results = await withTimeout(
|
|
198
|
-
memory.searchCorrections(query, {
|
|
199
|
-
project: options?.project,
|
|
200
|
-
limit: options?.limit || 3,
|
|
201
|
-
minSimilarity: options?.minSimilarity || 0.3
|
|
202
|
-
}),
|
|
203
|
-
SEARCH_TIMEOUT,
|
|
204
|
-
[]
|
|
205
|
-
)
|
|
206
|
-
return normalizeCorrectionResults(results)
|
|
207
|
-
} catch {
|
|
208
|
-
return []
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
/**
|
|
213
|
-
* Search knowledge graph for related concepts
|
|
214
|
-
*/
|
|
215
|
-
async searchGraph(query: string, limit: number = 5): Promise<NormalizedResult[]> {
|
|
216
|
-
try {
|
|
217
|
-
const kgService = getKnowledgeGraphService()
|
|
218
|
-
if (!kgService?.search) return []
|
|
219
|
-
|
|
220
|
-
const graphResults = kgService.search.search({ query, limit })
|
|
221
|
-
if (!graphResults?.nodes?.length) return []
|
|
222
|
-
|
|
223
|
-
return graphResults.nodes.map((n: any) => ({
|
|
224
|
-
id: n.id || '',
|
|
225
|
-
content: `**${n.label || n.name}** (${n.type})${n.properties?.description ? `\n${n.properties.description}` : ''}`,
|
|
226
|
-
score: graphResults.scores.get(n.id) || 0.5,
|
|
227
|
-
source: 'graph' as const,
|
|
228
|
-
project: n.properties?.project || '',
|
|
229
|
-
date: '',
|
|
230
|
-
metadata: n.properties || {}
|
|
231
|
-
}))
|
|
232
|
-
} catch {
|
|
233
|
-
return []
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
/**
|
|
238
|
-
* Temporal search — parse temporal phrases from query, filter results by date range.
|
|
239
|
-
* Uses TemporalQueryProcessor when available.
|
|
240
|
-
*/
|
|
241
|
-
async temporalSearch(
|
|
242
|
-
query: string,
|
|
243
|
-
options?: { project?: string; limit?: number }
|
|
244
|
-
): Promise<{ results: NormalizedResult[]; cleanedQuery: string }> {
|
|
245
|
-
if (!isServicesInitialized()) return { results: [], cleanedQuery: query }
|
|
246
|
-
|
|
247
|
-
try {
|
|
248
|
-
// Dynamic import to avoid hard dependency
|
|
249
|
-
const { TemporalQueryProcessor } = await import('@/intelligence/temporal/query-processor')
|
|
250
|
-
const processor = new TemporalQueryProcessor(this.logger)
|
|
251
|
-
const parsed = processor.parse(query)
|
|
252
|
-
|
|
253
|
-
if (parsed.intent === 'none') {
|
|
254
|
-
// No temporal expressions found, use normal search
|
|
255
|
-
const results = await this.enhancedSearch(query, options)
|
|
256
|
-
return { results, cleanedQuery: query }
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
// Search with cleaned query (temporal phrases removed)
|
|
260
|
-
const results = await this.enhancedSearch(parsed.cleaned || query, {
|
|
261
|
-
...options,
|
|
262
|
-
limit: (options?.limit || 5) * 2 // Fetch more since we'll filter by date
|
|
263
|
-
})
|
|
264
|
-
|
|
265
|
-
// Filter results by date range
|
|
266
|
-
const filtered = results.filter(r => {
|
|
267
|
-
if (!r.date) return true // Keep results without dates
|
|
268
|
-
const date = new Date(r.date)
|
|
269
|
-
if (isNaN(date.getTime())) return true
|
|
270
|
-
if (parsed.startDate && date < parsed.startDate) return false
|
|
271
|
-
if (parsed.endDate && date > parsed.endDate) return false
|
|
272
|
-
return true
|
|
273
|
-
})
|
|
274
|
-
|
|
275
|
-
return {
|
|
276
|
-
results: filtered.slice(0, options?.limit || 5),
|
|
277
|
-
cleanedQuery: parsed.cleaned || query
|
|
278
|
-
}
|
|
279
|
-
} catch {
|
|
280
|
-
// TemporalQueryProcessor not available, fall back
|
|
281
|
-
const results = await this.enhancedSearch(query, options)
|
|
282
|
-
return { results, cleanedQuery: query }
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
/**
|
|
287
|
-
* Build a timeline for a topic/project
|
|
288
|
-
*/
|
|
289
|
-
async buildTimeline(
|
|
290
|
-
options?: { project?: string; topic?: string; limit?: number }
|
|
291
|
-
): Promise<any | null> {
|
|
292
|
-
if (!isServicesInitialized()) return null
|
|
293
|
-
|
|
294
|
-
try {
|
|
295
|
-
const memory = getMemoryService()
|
|
296
|
-
if (!memory.isChromaDBEnabled()) return null
|
|
297
|
-
|
|
298
|
-
const { TimelineBuilder } = await import('@/intelligence/temporal/timeline')
|
|
299
|
-
const builder = new TimelineBuilder(
|
|
300
|
-
this.logger,
|
|
301
|
-
memory.chroma.collections,
|
|
302
|
-
memory.chroma.embeddings
|
|
303
|
-
)
|
|
304
|
-
return await withTimeout(
|
|
305
|
-
builder.buildTimeline(options),
|
|
306
|
-
SEARCH_TIMEOUT,
|
|
307
|
-
null
|
|
308
|
-
)
|
|
309
|
-
} catch {
|
|
310
|
-
return null
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
/**
|
|
315
|
-
* Analyze decision evolution for a topic
|
|
316
|
-
*/
|
|
317
|
-
async analyzeEvolution(
|
|
318
|
-
topic: string,
|
|
319
|
-
options?: { project?: string; limit?: number }
|
|
320
|
-
): Promise<any | null> {
|
|
321
|
-
if (!isServicesInitialized()) return null
|
|
322
|
-
|
|
323
|
-
try {
|
|
324
|
-
const memory = getMemoryService()
|
|
325
|
-
if (!memory.isChromaDBEnabled()) return null
|
|
326
|
-
|
|
327
|
-
const { DecisionEvolutionTracker } = await import('@/intelligence/temporal/evolution')
|
|
328
|
-
const tracker = new DecisionEvolutionTracker(
|
|
329
|
-
this.logger,
|
|
330
|
-
memory.chroma.collections,
|
|
331
|
-
memory.chroma.embeddings
|
|
332
|
-
)
|
|
333
|
-
return await withTimeout(
|
|
334
|
-
tracker.analyzeEvolution(topic, options),
|
|
335
|
-
INTELLIGENCE_TIMEOUT,
|
|
336
|
-
null
|
|
337
|
-
)
|
|
338
|
-
} catch {
|
|
339
|
-
return null
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
/**
|
|
344
|
-
* Detect trends
|
|
345
|
-
*/
|
|
346
|
-
async detectTrends(
|
|
347
|
-
options?: { project?: string; periodDays?: number; limit?: number }
|
|
348
|
-
): Promise<any | null> {
|
|
349
|
-
if (!isServicesInitialized()) return null
|
|
350
|
-
|
|
351
|
-
try {
|
|
352
|
-
const memory = getMemoryService()
|
|
353
|
-
if (!memory.isChromaDBEnabled()) return null
|
|
354
|
-
|
|
355
|
-
const { TrendDetector } = await import('@/intelligence/temporal/trends')
|
|
356
|
-
const detector = new TrendDetector(this.logger, memory.chroma.collections)
|
|
357
|
-
return await withTimeout(
|
|
358
|
-
detector.detectTrends(options),
|
|
359
|
-
INTELLIGENCE_TIMEOUT,
|
|
360
|
-
null
|
|
361
|
-
)
|
|
362
|
-
} catch {
|
|
363
|
-
return null
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
/**
|
|
368
|
-
* Multi-hop chain retrieval for complex questions
|
|
369
|
-
*/
|
|
370
|
-
async chainSearch(
|
|
371
|
-
query: string,
|
|
372
|
-
options?: { project?: string; maxHops?: number }
|
|
373
|
-
): Promise<any | null> {
|
|
374
|
-
if (!isServicesInitialized()) return null
|
|
375
|
-
|
|
376
|
-
try {
|
|
377
|
-
const memory = getMemoryService()
|
|
378
|
-
if (!memory.isChromaDBEnabled()) return null
|
|
379
|
-
|
|
380
|
-
const { ChainRetrieval } = await import('@/intelligence/reasoning/chain-retrieval')
|
|
381
|
-
const chain = new ChainRetrieval(
|
|
382
|
-
this.logger,
|
|
383
|
-
memory.chroma.collections,
|
|
384
|
-
memory.chroma.embeddings
|
|
385
|
-
)
|
|
386
|
-
return await withTimeout(
|
|
387
|
-
chain.retrieve(query, {
|
|
388
|
-
maxHops: options?.maxHops || 3,
|
|
389
|
-
project: options?.project
|
|
390
|
-
}),
|
|
391
|
-
INTELLIGENCE_TIMEOUT,
|
|
392
|
-
null
|
|
393
|
-
)
|
|
394
|
-
} catch {
|
|
395
|
-
return null
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
/**
|
|
400
|
-
* Get recommendations based on context
|
|
401
|
-
*/
|
|
402
|
-
async getRecommendations(
|
|
403
|
-
query: string,
|
|
404
|
-
options?: { project?: string; limit?: number }
|
|
405
|
-
): Promise<any | null> {
|
|
406
|
-
if (!isServicesInitialized()) return null
|
|
407
|
-
|
|
408
|
-
try {
|
|
409
|
-
const memory = getMemoryService()
|
|
410
|
-
if (!memory.isChromaDBEnabled()) return null
|
|
411
|
-
|
|
412
|
-
const { Recommender } = await import('@/intelligence/prediction/recommender')
|
|
413
|
-
const recommender = new Recommender(
|
|
414
|
-
this.logger,
|
|
415
|
-
memory.chroma.collections,
|
|
416
|
-
memory.chroma.embeddings
|
|
417
|
-
)
|
|
418
|
-
return await withTimeout(
|
|
419
|
-
recommender.getRecommendations(query, {
|
|
420
|
-
project: options?.project,
|
|
421
|
-
limit: options?.limit || 5
|
|
422
|
-
}),
|
|
423
|
-
INTELLIGENCE_TIMEOUT,
|
|
424
|
-
null
|
|
425
|
-
)
|
|
426
|
-
} catch {
|
|
427
|
-
return null
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
/**
|
|
432
|
-
* Find cross-project patterns
|
|
433
|
-
*/
|
|
434
|
-
async findCrossProjectPatterns(
|
|
435
|
-
options?: { query?: string; minProjects?: number; limit?: number }
|
|
436
|
-
): Promise<any | null> {
|
|
437
|
-
if (!isServicesInitialized()) return null
|
|
438
|
-
|
|
439
|
-
try {
|
|
440
|
-
const memory = getMemoryService()
|
|
441
|
-
if (!memory.isChromaDBEnabled()) return null
|
|
442
|
-
|
|
443
|
-
const { PatternGeneralizer } = await import('@/intelligence/cross-project/generalizer')
|
|
444
|
-
const generalizer = new PatternGeneralizer(
|
|
445
|
-
this.logger,
|
|
446
|
-
memory.chroma.collections,
|
|
447
|
-
memory.chroma.embeddings
|
|
448
|
-
)
|
|
449
|
-
return await withTimeout(
|
|
450
|
-
generalizer.findCrossProjectPatterns(options),
|
|
451
|
-
INTELLIGENCE_TIMEOUT,
|
|
452
|
-
null
|
|
453
|
-
)
|
|
454
|
-
} catch {
|
|
455
|
-
return null
|
|
456
|
-
}
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
/**
|
|
460
|
-
* Invalidate cache entries for a project after store/update/delete
|
|
461
|
-
*/
|
|
462
|
-
invalidateCache(project?: string): void {
|
|
463
|
-
try {
|
|
464
|
-
const cache = getSemanticCacheService()
|
|
465
|
-
if (!cache) return
|
|
466
|
-
if (project) {
|
|
467
|
-
cache.invalidateProject(project)
|
|
468
|
-
} else {
|
|
469
|
-
cache.clear()
|
|
470
|
-
}
|
|
471
|
-
} catch {
|
|
472
|
-
// Cache not available
|
|
473
|
-
}
|
|
474
|
-
}
|
|
475
|
-
}
|
|
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
|
+
* Sort results so same-project entries come first (safety net for cross-project noise).
|
|
44
|
+
*/
|
|
45
|
+
private prioritizeProject(results: NormalizedResult[], project?: string): NormalizedResult[] {
|
|
46
|
+
if (!project || results.length === 0) return results
|
|
47
|
+
const sameProject = results.filter(r => r.project === project)
|
|
48
|
+
const otherProject = results.filter(r => r.project !== project)
|
|
49
|
+
if (sameProject.length > 0) return [...sameProject, ...otherProject]
|
|
50
|
+
return results
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Enhanced search — uses hybrid retrieval pipeline when available,
|
|
55
|
+
* falls back to plain searchRaw().
|
|
56
|
+
* Wraps with semantic cache for repeated queries.
|
|
57
|
+
*/
|
|
58
|
+
async enhancedSearch(
|
|
59
|
+
query: string,
|
|
60
|
+
options?: { project?: string; limit?: number; minSimilarity?: number }
|
|
61
|
+
): Promise<NormalizedResult[]> {
|
|
62
|
+
return timed('brain:search', () => this._enhancedSearchInner(query, options))
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
private async _enhancedSearchInner(
|
|
66
|
+
query: string,
|
|
67
|
+
options?: { project?: string; limit?: number; minSimilarity?: number }
|
|
68
|
+
): Promise<NormalizedResult[]> {
|
|
69
|
+
if (!isServicesInitialized()) return []
|
|
70
|
+
|
|
71
|
+
const cacheKey = `search:${query}:${options?.project || ''}:${options?.limit || 5}`
|
|
72
|
+
|
|
73
|
+
// Check semantic cache first
|
|
74
|
+
const cache = getSemanticCacheService()
|
|
75
|
+
if (cache) {
|
|
76
|
+
try {
|
|
77
|
+
const cached = await cache.get(cacheKey)
|
|
78
|
+
if (cached) {
|
|
79
|
+
this.logger.debug({ query }, 'Search cache hit')
|
|
80
|
+
return cached as NormalizedResult[]
|
|
81
|
+
}
|
|
82
|
+
} catch {
|
|
83
|
+
// Cache miss or error, continue
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Try hybrid search via RetrievalPipeline
|
|
88
|
+
const pipeline = getRetrievalPipeline()
|
|
89
|
+
if (pipeline) {
|
|
90
|
+
try {
|
|
91
|
+
const hybridResults = await withTimeout(
|
|
92
|
+
pipeline.search(query, {
|
|
93
|
+
project: options?.project,
|
|
94
|
+
limit: options?.limit || 5,
|
|
95
|
+
minSimilarity: options?.minSimilarity || 0.3
|
|
96
|
+
}),
|
|
97
|
+
SEARCH_TIMEOUT,
|
|
98
|
+
[]
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
if (hybridResults.length > 0) {
|
|
102
|
+
const normalized = this.prioritizeProject(hybridResults.map((r: any) => ({
|
|
103
|
+
id: r.id || '',
|
|
104
|
+
content: typeof r.content === 'string' ? r.content : (r.document || JSON.stringify(r.content ?? '')),
|
|
105
|
+
score: r.scores?.final || r.score || r.similarity || 0,
|
|
106
|
+
source: (r.collection === 'patterns' ? 'pattern' : r.collection === 'corrections' ? 'correction' : 'decision') as const,
|
|
107
|
+
project: r.metadata?.project || options?.project || '',
|
|
108
|
+
date: r.metadata?.created_at || '',
|
|
109
|
+
metadata: r.metadata || {}
|
|
110
|
+
})), options?.project)
|
|
111
|
+
|
|
112
|
+
// Cache results
|
|
113
|
+
if (cache) {
|
|
114
|
+
try { await cache.set(cacheKey, normalized) } catch {}
|
|
115
|
+
}
|
|
116
|
+
return normalized
|
|
117
|
+
}
|
|
118
|
+
} catch (error) {
|
|
119
|
+
this.logger.debug({ error }, 'Hybrid search failed, falling back to plain search')
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Fallback: plain searchRaw
|
|
124
|
+
return this.plainSearch(query, options, cache, cacheKey)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Plain search using memory.searchRaw() — always available.
|
|
129
|
+
*/
|
|
130
|
+
async plainSearch(
|
|
131
|
+
query: string,
|
|
132
|
+
options?: { project?: string; limit?: number; minSimilarity?: number },
|
|
133
|
+
cache?: any,
|
|
134
|
+
cacheKey?: string
|
|
135
|
+
): Promise<NormalizedResult[]> {
|
|
136
|
+
if (!isServicesInitialized()) return []
|
|
137
|
+
|
|
138
|
+
const memory = getMemoryService()
|
|
139
|
+
try {
|
|
140
|
+
const rawResults = await withTimeout(
|
|
141
|
+
memory.searchRaw(query, {
|
|
142
|
+
project: options?.project,
|
|
143
|
+
limit: options?.limit || 5,
|
|
144
|
+
minSimilarity: options?.minSimilarity || 0.3
|
|
145
|
+
}),
|
|
146
|
+
SEARCH_TIMEOUT,
|
|
147
|
+
[]
|
|
148
|
+
)
|
|
149
|
+
const normalized = this.prioritizeProject(normalizeSearchResults(rawResults), options?.project)
|
|
150
|
+
|
|
151
|
+
// Cache results
|
|
152
|
+
if (cache && cacheKey) {
|
|
153
|
+
try { await cache.set(cacheKey, normalized) } catch {}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return normalized
|
|
157
|
+
} catch {
|
|
158
|
+
return []
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Search patterns with normalization and timeout
|
|
164
|
+
*/
|
|
165
|
+
async searchPatterns(
|
|
166
|
+
query: string,
|
|
167
|
+
options?: { project?: string; limit?: number; minSimilarity?: number }
|
|
168
|
+
): Promise<NormalizedResult[]> {
|
|
169
|
+
if (!isServicesInitialized()) return []
|
|
170
|
+
const memory = getMemoryService()
|
|
171
|
+
try {
|
|
172
|
+
const results = await withTimeout(
|
|
173
|
+
memory.searchPatterns(query, {
|
|
174
|
+
project: options?.project,
|
|
175
|
+
limit: options?.limit || 3,
|
|
176
|
+
minSimilarity: options?.minSimilarity || 0.3
|
|
177
|
+
}),
|
|
178
|
+
SEARCH_TIMEOUT,
|
|
179
|
+
[]
|
|
180
|
+
)
|
|
181
|
+
return normalizePatternResults(results)
|
|
182
|
+
} catch {
|
|
183
|
+
return []
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Search corrections with normalization and timeout
|
|
189
|
+
*/
|
|
190
|
+
async searchCorrections(
|
|
191
|
+
query: string,
|
|
192
|
+
options?: { project?: string; limit?: number; minSimilarity?: number }
|
|
193
|
+
): Promise<NormalizedResult[]> {
|
|
194
|
+
if (!isServicesInitialized()) return []
|
|
195
|
+
const memory = getMemoryService()
|
|
196
|
+
try {
|
|
197
|
+
const results = await withTimeout(
|
|
198
|
+
memory.searchCorrections(query, {
|
|
199
|
+
project: options?.project,
|
|
200
|
+
limit: options?.limit || 3,
|
|
201
|
+
minSimilarity: options?.minSimilarity || 0.3
|
|
202
|
+
}),
|
|
203
|
+
SEARCH_TIMEOUT,
|
|
204
|
+
[]
|
|
205
|
+
)
|
|
206
|
+
return normalizeCorrectionResults(results)
|
|
207
|
+
} catch {
|
|
208
|
+
return []
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Search knowledge graph for related concepts
|
|
214
|
+
*/
|
|
215
|
+
async searchGraph(query: string, limit: number = 5): Promise<NormalizedResult[]> {
|
|
216
|
+
try {
|
|
217
|
+
const kgService = getKnowledgeGraphService()
|
|
218
|
+
if (!kgService?.search) return []
|
|
219
|
+
|
|
220
|
+
const graphResults = kgService.search.search({ query, limit })
|
|
221
|
+
if (!graphResults?.nodes?.length) return []
|
|
222
|
+
|
|
223
|
+
return graphResults.nodes.map((n: any) => ({
|
|
224
|
+
id: n.id || '',
|
|
225
|
+
content: `**${n.label || n.name}** (${n.type})${n.properties?.description ? `\n${n.properties.description}` : ''}`,
|
|
226
|
+
score: graphResults.scores.get(n.id) || 0.5,
|
|
227
|
+
source: 'graph' as const,
|
|
228
|
+
project: n.properties?.project || '',
|
|
229
|
+
date: '',
|
|
230
|
+
metadata: n.properties || {}
|
|
231
|
+
}))
|
|
232
|
+
} catch {
|
|
233
|
+
return []
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Temporal search — parse temporal phrases from query, filter results by date range.
|
|
239
|
+
* Uses TemporalQueryProcessor when available.
|
|
240
|
+
*/
|
|
241
|
+
async temporalSearch(
|
|
242
|
+
query: string,
|
|
243
|
+
options?: { project?: string; limit?: number }
|
|
244
|
+
): Promise<{ results: NormalizedResult[]; cleanedQuery: string }> {
|
|
245
|
+
if (!isServicesInitialized()) return { results: [], cleanedQuery: query }
|
|
246
|
+
|
|
247
|
+
try {
|
|
248
|
+
// Dynamic import to avoid hard dependency
|
|
249
|
+
const { TemporalQueryProcessor } = await import('@/intelligence/temporal/query-processor')
|
|
250
|
+
const processor = new TemporalQueryProcessor(this.logger)
|
|
251
|
+
const parsed = processor.parse(query)
|
|
252
|
+
|
|
253
|
+
if (parsed.intent === 'none') {
|
|
254
|
+
// No temporal expressions found, use normal search
|
|
255
|
+
const results = await this.enhancedSearch(query, options)
|
|
256
|
+
return { results, cleanedQuery: query }
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Search with cleaned query (temporal phrases removed)
|
|
260
|
+
const results = await this.enhancedSearch(parsed.cleaned || query, {
|
|
261
|
+
...options,
|
|
262
|
+
limit: (options?.limit || 5) * 2 // Fetch more since we'll filter by date
|
|
263
|
+
})
|
|
264
|
+
|
|
265
|
+
// Filter results by date range
|
|
266
|
+
const filtered = results.filter(r => {
|
|
267
|
+
if (!r.date) return true // Keep results without dates
|
|
268
|
+
const date = new Date(r.date)
|
|
269
|
+
if (isNaN(date.getTime())) return true
|
|
270
|
+
if (parsed.startDate && date < parsed.startDate) return false
|
|
271
|
+
if (parsed.endDate && date > parsed.endDate) return false
|
|
272
|
+
return true
|
|
273
|
+
})
|
|
274
|
+
|
|
275
|
+
return {
|
|
276
|
+
results: filtered.slice(0, options?.limit || 5),
|
|
277
|
+
cleanedQuery: parsed.cleaned || query
|
|
278
|
+
}
|
|
279
|
+
} catch {
|
|
280
|
+
// TemporalQueryProcessor not available, fall back
|
|
281
|
+
const results = await this.enhancedSearch(query, options)
|
|
282
|
+
return { results, cleanedQuery: query }
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Build a timeline for a topic/project
|
|
288
|
+
*/
|
|
289
|
+
async buildTimeline(
|
|
290
|
+
options?: { project?: string; topic?: string; limit?: number }
|
|
291
|
+
): Promise<any | null> {
|
|
292
|
+
if (!isServicesInitialized()) return null
|
|
293
|
+
|
|
294
|
+
try {
|
|
295
|
+
const memory = getMemoryService()
|
|
296
|
+
if (!memory.isChromaDBEnabled()) return null
|
|
297
|
+
|
|
298
|
+
const { TimelineBuilder } = await import('@/intelligence/temporal/timeline')
|
|
299
|
+
const builder = new TimelineBuilder(
|
|
300
|
+
this.logger,
|
|
301
|
+
memory.chroma.collections,
|
|
302
|
+
memory.chroma.embeddings
|
|
303
|
+
)
|
|
304
|
+
return await withTimeout(
|
|
305
|
+
builder.buildTimeline(options),
|
|
306
|
+
SEARCH_TIMEOUT,
|
|
307
|
+
null
|
|
308
|
+
)
|
|
309
|
+
} catch {
|
|
310
|
+
return null
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Analyze decision evolution for a topic
|
|
316
|
+
*/
|
|
317
|
+
async analyzeEvolution(
|
|
318
|
+
topic: string,
|
|
319
|
+
options?: { project?: string; limit?: number }
|
|
320
|
+
): Promise<any | null> {
|
|
321
|
+
if (!isServicesInitialized()) return null
|
|
322
|
+
|
|
323
|
+
try {
|
|
324
|
+
const memory = getMemoryService()
|
|
325
|
+
if (!memory.isChromaDBEnabled()) return null
|
|
326
|
+
|
|
327
|
+
const { DecisionEvolutionTracker } = await import('@/intelligence/temporal/evolution')
|
|
328
|
+
const tracker = new DecisionEvolutionTracker(
|
|
329
|
+
this.logger,
|
|
330
|
+
memory.chroma.collections,
|
|
331
|
+
memory.chroma.embeddings
|
|
332
|
+
)
|
|
333
|
+
return await withTimeout(
|
|
334
|
+
tracker.analyzeEvolution(topic, options),
|
|
335
|
+
INTELLIGENCE_TIMEOUT,
|
|
336
|
+
null
|
|
337
|
+
)
|
|
338
|
+
} catch {
|
|
339
|
+
return null
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Detect trends
|
|
345
|
+
*/
|
|
346
|
+
async detectTrends(
|
|
347
|
+
options?: { project?: string; periodDays?: number; limit?: number }
|
|
348
|
+
): Promise<any | null> {
|
|
349
|
+
if (!isServicesInitialized()) return null
|
|
350
|
+
|
|
351
|
+
try {
|
|
352
|
+
const memory = getMemoryService()
|
|
353
|
+
if (!memory.isChromaDBEnabled()) return null
|
|
354
|
+
|
|
355
|
+
const { TrendDetector } = await import('@/intelligence/temporal/trends')
|
|
356
|
+
const detector = new TrendDetector(this.logger, memory.chroma.collections)
|
|
357
|
+
return await withTimeout(
|
|
358
|
+
detector.detectTrends(options),
|
|
359
|
+
INTELLIGENCE_TIMEOUT,
|
|
360
|
+
null
|
|
361
|
+
)
|
|
362
|
+
} catch {
|
|
363
|
+
return null
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Multi-hop chain retrieval for complex questions
|
|
369
|
+
*/
|
|
370
|
+
async chainSearch(
|
|
371
|
+
query: string,
|
|
372
|
+
options?: { project?: string; maxHops?: number }
|
|
373
|
+
): Promise<any | null> {
|
|
374
|
+
if (!isServicesInitialized()) return null
|
|
375
|
+
|
|
376
|
+
try {
|
|
377
|
+
const memory = getMemoryService()
|
|
378
|
+
if (!memory.isChromaDBEnabled()) return null
|
|
379
|
+
|
|
380
|
+
const { ChainRetrieval } = await import('@/intelligence/reasoning/chain-retrieval')
|
|
381
|
+
const chain = new ChainRetrieval(
|
|
382
|
+
this.logger,
|
|
383
|
+
memory.chroma.collections,
|
|
384
|
+
memory.chroma.embeddings
|
|
385
|
+
)
|
|
386
|
+
return await withTimeout(
|
|
387
|
+
chain.retrieve(query, {
|
|
388
|
+
maxHops: options?.maxHops || 3,
|
|
389
|
+
project: options?.project
|
|
390
|
+
}),
|
|
391
|
+
INTELLIGENCE_TIMEOUT,
|
|
392
|
+
null
|
|
393
|
+
)
|
|
394
|
+
} catch {
|
|
395
|
+
return null
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* Get recommendations based on context
|
|
401
|
+
*/
|
|
402
|
+
async getRecommendations(
|
|
403
|
+
query: string,
|
|
404
|
+
options?: { project?: string; limit?: number }
|
|
405
|
+
): Promise<any | null> {
|
|
406
|
+
if (!isServicesInitialized()) return null
|
|
407
|
+
|
|
408
|
+
try {
|
|
409
|
+
const memory = getMemoryService()
|
|
410
|
+
if (!memory.isChromaDBEnabled()) return null
|
|
411
|
+
|
|
412
|
+
const { Recommender } = await import('@/intelligence/prediction/recommender')
|
|
413
|
+
const recommender = new Recommender(
|
|
414
|
+
this.logger,
|
|
415
|
+
memory.chroma.collections,
|
|
416
|
+
memory.chroma.embeddings
|
|
417
|
+
)
|
|
418
|
+
return await withTimeout(
|
|
419
|
+
recommender.getRecommendations(query, {
|
|
420
|
+
project: options?.project,
|
|
421
|
+
limit: options?.limit || 5
|
|
422
|
+
}),
|
|
423
|
+
INTELLIGENCE_TIMEOUT,
|
|
424
|
+
null
|
|
425
|
+
)
|
|
426
|
+
} catch {
|
|
427
|
+
return null
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* Find cross-project patterns
|
|
433
|
+
*/
|
|
434
|
+
async findCrossProjectPatterns(
|
|
435
|
+
options?: { query?: string; minProjects?: number; limit?: number }
|
|
436
|
+
): Promise<any | null> {
|
|
437
|
+
if (!isServicesInitialized()) return null
|
|
438
|
+
|
|
439
|
+
try {
|
|
440
|
+
const memory = getMemoryService()
|
|
441
|
+
if (!memory.isChromaDBEnabled()) return null
|
|
442
|
+
|
|
443
|
+
const { PatternGeneralizer } = await import('@/intelligence/cross-project/generalizer')
|
|
444
|
+
const generalizer = new PatternGeneralizer(
|
|
445
|
+
this.logger,
|
|
446
|
+
memory.chroma.collections,
|
|
447
|
+
memory.chroma.embeddings
|
|
448
|
+
)
|
|
449
|
+
return await withTimeout(
|
|
450
|
+
generalizer.findCrossProjectPatterns(options),
|
|
451
|
+
INTELLIGENCE_TIMEOUT,
|
|
452
|
+
null
|
|
453
|
+
)
|
|
454
|
+
} catch {
|
|
455
|
+
return null
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
/**
|
|
460
|
+
* Invalidate cache entries for a project after store/update/delete
|
|
461
|
+
*/
|
|
462
|
+
invalidateCache(project?: string): void {
|
|
463
|
+
try {
|
|
464
|
+
const cache = getSemanticCacheService()
|
|
465
|
+
if (!cache) return
|
|
466
|
+
if (project) {
|
|
467
|
+
cache.invalidateProject(project)
|
|
468
|
+
} else {
|
|
469
|
+
cache.clear()
|
|
470
|
+
}
|
|
471
|
+
} catch {
|
|
472
|
+
// Cache not available
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
}
|