claude-brain 0.30.2 → 0.30.3
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 +241 -191
- package/VERSION +1 -1
- package/assets/CLAUDE-unified.md +11 -11
- package/assets/CLAUDE.md +29 -29
- package/package.json +7 -3
- 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/scripts/postinstall.mjs +531 -531
- package/src/automation/decision-detector.ts +452 -452
- 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 +210 -205
- package/src/cli/auto-setup.ts +75 -75
- package/src/cli/auto-start.ts +266 -266
- package/src/cli/bin.ts +264 -264
- package/src/cli/commands/autostart.ts +90 -90
- package/src/cli/commands/chroma.ts +578 -577
- package/src/cli/commands/export-training.ts +70 -70
- package/src/cli/commands/export.ts +130 -130
- package/src/cli/commands/git-hook.ts +183 -183
- package/src/cli/commands/hooks.ts +217 -217
- package/src/cli/commands/init.ts +123 -123
- package/src/cli/commands/install-mcp.ts +122 -111
- package/src/cli/commands/models.ts +979 -979
- package/src/cli/commands/pack.ts +200 -200
- package/src/cli/commands/refresh.ts +344 -339
- package/src/cli/commands/reindex.ts +120 -120
- package/src/cli/commands/serve.ts +466 -463
- package/src/cli/commands/start.ts +44 -44
- package/src/cli/commands/status.ts +220 -203
- package/src/cli/commands/uninstall-mcp.ts +45 -41
- package/src/cli/commands/update.ts +130 -124
- package/src/cli/migrate-chroma.ts +106 -106
- 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/code-intelligence/indexer.ts +352 -352
- package/src/code-intelligence/linker.ts +178 -178
- package/src/code-intelligence/parser.ts +484 -484
- package/src/code-intelligence/query.ts +291 -291
- package/src/code-intelligence/schema.ts +83 -83
- package/src/code-intelligence/types.ts +95 -95
- package/src/config/defaults.ts +52 -52
- package/src/config/home.ts +56 -56
- package/src/config/index.ts +5 -5
- package/src/config/loader.ts +192 -192
- package/src/config/schema.ts +446 -415
- package/src/config/validator.ts +182 -182
- package/src/context/assembler.ts +407 -400
- package/src/context/index.ts +79 -79
- package/src/context/progress-tracker.ts +174 -174
- package/src/context/standards-manager.ts +287 -287
- package/src/context/validator.ts +58 -58
- package/src/diagnostics/index.ts +122 -121
- package/src/health/index.ts +233 -232
- package/src/hooks/brain-hook.ts +134 -131
- package/src/hooks/capture.ts +168 -168
- package/src/hooks/claude-code-mastery.md +112 -112
- package/src/hooks/context-hook.ts +260 -245
- package/src/hooks/deduplicator.ts +72 -72
- package/src/hooks/git-capture.ts +109 -109
- package/src/hooks/git-hook-installer.ts +211 -207
- package/src/hooks/index.ts +20 -20
- package/src/hooks/installer.ts +306 -288
- package/src/hooks/interceptor-hook.ts +204 -201
- package/src/hooks/passive-classifier.ts +397 -397
- package/src/hooks/queue.ts +160 -129
- package/src/hooks/session-tracker.ts +312 -312
- package/src/hooks/types.ts +52 -52
- package/src/index.ts +7 -7
- package/src/intelligence/cross-project/generalizer.ts +283 -283
- package/src/intelligence/cross-project/index.ts +7 -7
- package/src/intelligence/hf-downloader.ts +222 -222
- package/src/intelligence/hf-manifest.json +78 -78
- package/src/intelligence/index.ts +24 -24
- package/src/intelligence/inference-router.ts +762 -762
- package/src/intelligence/model-manager.ts +263 -245
- package/src/intelligence/optimization/index.ts +10 -10
- package/src/intelligence/optimization/precompute.ts +202 -202
- package/src/intelligence/optimization/semantic-cache.ts +213 -207
- package/src/intelligence/prediction/index.ts +7 -7
- package/src/intelligence/prediction/recommender.ts +276 -268
- package/src/intelligence/reasoning/chain-retrieval.ts +243 -247
- package/src/intelligence/reasoning/index.ts +7 -7
- package/src/intelligence/temporal/evolution.ts +193 -197
- package/src/intelligence/temporal/index.ts +16 -16
- package/src/intelligence/temporal/query-processor.ts +190 -190
- package/src/intelligence/temporal/timeline.ts +272 -259
- package/src/intelligence/temporal/trends.ts +263 -263
- package/src/intelligence/tokenizer.ts +118 -118
- package/src/knowledge/entity-extractor.ts +447 -443
- 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 +166 -166
- package/src/knowledge/relationship-extractor.ts +108 -108
- package/src/memory/chroma/client.ts +211 -192
- package/src/memory/chroma/collection-manager.ts +92 -92
- package/src/memory/chroma/config.ts +57 -57
- package/src/memory/chroma/embeddings.ts +177 -175
- 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 +319 -315
- package/src/memory/chroma/store.ts +755 -747
- package/src/memory/compression.ts +121 -121
- package/src/memory/consolidation/archiver.ts +162 -165
- package/src/memory/consolidation/merger.ts +182 -186
- package/src/memory/consolidation/scorer.ts +136 -136
- package/src/memory/database.ts +9 -0
- package/src/memory/dual-write.ts +145 -0
- package/src/memory/embeddings.ts +226 -226
- package/src/memory/episodic/detector.ts +108 -108
- package/src/memory/episodic/manager.ts +347 -351
- package/src/memory/episodic/summarizer.ts +179 -179
- package/src/memory/episodic/types.ts +52 -52
- package/src/memory/fts5-search.ts +692 -633
- package/src/memory/index.ts +943 -1060
- package/src/memory/migrations/add-fts5.ts +118 -108
- package/src/memory/patterns.ts +438 -438
- package/src/memory/pruning.ts +60 -60
- package/src/memory/schema.ts +88 -88
- package/src/memory/store.ts +911 -787
- package/src/orchestrator/handlers/decision-handler.ts +204 -204
- 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 -297
- package/src/retrieval/bm25/tokenizer.ts +184 -184
- package/src/retrieval/feedback/adaptive.ts +221 -221
- package/src/retrieval/feedback/index.ts +16 -16
- package/src/retrieval/feedback/metrics.ts +221 -221
- package/src/retrieval/feedback/store.ts +283 -283
- package/src/retrieval/fusion/index.ts +194 -194
- package/src/retrieval/fusion/rrf.ts +165 -165
- package/src/retrieval/index.ts +12 -12
- package/src/retrieval/pipeline.ts +375 -375
- package/src/retrieval/query/expander.ts +203 -203
- package/src/retrieval/query/index.ts +27 -27
- package/src/retrieval/query/intent-classifier.ts +252 -252
- package/src/retrieval/query/temporal-parser.ts +295 -295
- package/src/retrieval/reranker/index.ts +189 -188
- package/src/retrieval/reranker/model.ts +99 -95
- package/src/retrieval/service.ts +125 -125
- package/src/retrieval/types.ts +162 -162
- package/src/routing/entity-extractor.ts +454 -454
- package/src/routing/handlers/exploration-handler.ts +369 -0
- package/src/routing/handlers/index.ts +19 -0
- package/src/routing/handlers/memory-handler.ts +273 -0
- package/src/routing/handlers/mutation-handler.ts +241 -0
- package/src/routing/handlers/recall-handler.ts +642 -0
- package/src/routing/handlers/shared.ts +515 -0
- package/src/routing/handlers/types.ts +48 -0
- package/src/routing/intent-classifier.ts +552 -552
- package/src/routing/response-filter.ts +399 -391
- package/src/routing/router.ts +245 -2193
- package/src/routing/search-engine.ts +521 -514
- package/src/routing/types.ts +104 -94
- package/src/scripts/health-check.ts +118 -118
- package/src/scripts/setup.ts +122 -122
- package/src/server/auto-updater.ts +283 -276
- package/src/server/handlers/call-tool.ts +159 -159
- package/src/server/handlers/list-tools.ts +35 -35
- package/src/server/handlers/tools/auto-remember.ts +165 -165
- package/src/server/handlers/tools/brain.ts +86 -86
- package/src/server/handlers/tools/create-project.ts +135 -135
- package/src/server/handlers/tools/get-code-standards.ts +123 -123
- package/src/server/handlers/tools/get-corrections.ts +152 -152
- package/src/server/handlers/tools/get-patterns.ts +156 -156
- package/src/server/handlers/tools/get-project-context.ts +75 -75
- package/src/server/handlers/tools/index.ts +30 -30
- package/src/server/handlers/tools/init-project.ts +756 -756
- package/src/server/handlers/tools/list-projects.ts +126 -126
- package/src/server/handlers/tools/recall-similar.ts +87 -87
- package/src/server/handlers/tools/recognize-pattern.ts +132 -132
- package/src/server/handlers/tools/record-correction.ts +131 -131
- package/src/server/handlers/tools/remember-decision.ts +168 -168
- package/src/server/handlers/tools/schemas.ts +179 -179
- package/src/server/handlers/tools/search-code.ts +122 -122
- package/src/server/handlers/tools/smart-context.ts +146 -146
- package/src/server/handlers/tools/update-progress.ts +131 -131
- package/src/server/http-api.ts +215 -1229
- package/src/server/mcp-proxy.ts +85 -84
- package/src/server/mcp-server.ts +285 -284
- package/src/server/middleware/auth.ts +39 -0
- package/src/server/middleware/error-handler.ts +37 -0
- package/src/server/middleware/rate-limit.ts +53 -0
- package/src/server/middleware/validate.ts +42 -0
- package/src/server/pid-manager.ts +137 -136
- package/src/server/providers/resources.ts +581 -581
- package/src/server/routes/code.ts +228 -0
- package/src/server/routes/context.ts +26 -0
- package/src/server/routes/health.ts +19 -0
- package/src/server/routes/helpers.ts +100 -0
- package/src/server/routes/hooks.ts +197 -0
- package/src/server/routes/mcp.ts +47 -0
- package/src/server/routes/memory.ts +397 -0
- package/src/server/routes/models.ts +96 -0
- package/src/server/routes/projects.ts +89 -0
- package/src/server/routes/types.ts +21 -0
- package/src/server/schemas/api-schemas.ts +202 -0
- package/src/server/services.ts +720 -720
- package/src/server/utils/memory-indicator.ts +84 -84
- package/src/server/utils/response-formatter.ts +129 -129
- package/src/server/web-viewer.ts +1145 -1115
- package/src/setup/index.ts +38 -38
- package/src/tools/registry.ts +115 -115
- package/src/tools/schemas.ts +666 -666
- package/src/tools/types.ts +412 -412
- package/src/training/data-store.ts +320 -298
- package/src/training/retrain-pipeline.ts +399 -394
- package/src/utils/error-handler.ts +136 -136
- package/src/utils/index.ts +58 -58
- package/src/utils/kill-port.ts +55 -53
- package/src/utils/phase12-helper.ts +56 -56
- package/src/utils/safe-path.ts +43 -0
- package/src/utils/timing.ts +47 -47
- package/src/utils/transaction.ts +63 -63
- package/src/vault/index.ts +4 -3
- package/src/vault/paths.ts +106 -106
- package/src/vault/query.ts +4 -1
- package/src/vault/reader.ts +44 -1
- package/src/vault/watcher.ts +24 -1
- package/src/vault/writer.ts +487 -413
- package/skills/persistent-memory/SKILL.md +0 -148
- package/skills/persistent-memory/references/tool-reference.md +0 -90
|
@@ -1,514 +1,521 @@
|
|
|
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
|
-
isServicesInitialized
|
|
18
|
-
} from '@/server/services'
|
|
19
|
-
import { timed } from '@/utils/timing'
|
|
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 normal searches
|
|
32
|
-
const INTELLIGENCE_TIMEOUT = 12000 // 12s timeout for bulk analytical features
|
|
33
|
-
|
|
34
|
-
/** Phase 25: Common stop words excluded from keyword boosting */
|
|
35
|
-
const KEYWORD_STOP_WORDS = new Set([
|
|
36
|
-
'the', 'a', 'an', 'and', 'or', 'but', 'in', 'on', 'at', 'to', 'for',
|
|
37
|
-
'of', 'with', 'by', 'from', 'is', 'it', 'as', 'be', 'was', 'are',
|
|
38
|
-
'were', 'been', 'has', 'have', 'had', 'do', 'does', 'did', 'will',
|
|
39
|
-
'would', 'could', 'should', 'may', 'might', 'can', 'shall', 'not',
|
|
40
|
-
'no', 'nor', 'so', 'up', 'out', 'if', 'about', 'into', 'through',
|
|
41
|
-
'during', 'before', 'after', 'above', 'below', 'between', 'under',
|
|
42
|
-
'again', 'further', 'then', 'once', 'here', 'there', 'when', 'where',
|
|
43
|
-
'why', 'how', 'all', 'each', 'every', 'both', 'few', 'more', 'most',
|
|
44
|
-
'other', 'some', 'such', 'than', 'too', 'very', 'just', 'because',
|
|
45
|
-
'what', 'which', 'who', 'whom', 'this', 'that', 'these', 'those',
|
|
46
|
-
'my', 'your', 'his', 'her', 'its', 'our', 'their', 'me', 'him',
|
|
47
|
-
'she', 'we', 'they', 'you', 'any', 'also', 'only', 'own', 'same',
|
|
48
|
-
'don', 'now', 'get', 'got', 'use', 'used', 'using', 'know', 'need',
|
|
49
|
-
'want', 'like', 'make', 'made', 'think', 'said', 'did', 'does'
|
|
50
|
-
])
|
|
51
|
-
|
|
52
|
-
export class SearchEngine {
|
|
53
|
-
private logger: Logger
|
|
54
|
-
|
|
55
|
-
constructor(logger: Logger) {
|
|
56
|
-
this.logger = logger.child({ component: 'search-engine' })
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Sort results so same-project entries come first (safety net for cross-project noise).
|
|
61
|
-
*/
|
|
62
|
-
private prioritizeProject(results: NormalizedResult[], project?: string): NormalizedResult[] {
|
|
63
|
-
if (!project || results.length === 0) return results
|
|
64
|
-
const sameProject = results.filter(r => r.project === project)
|
|
65
|
-
const otherProject = results.filter(r => r.project !== project)
|
|
66
|
-
if (sameProject.length > 0) return [...sameProject, ...otherProject]
|
|
67
|
-
return results
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Phase 25: Boost results that contain exact query keywords.
|
|
72
|
-
* Adds up to +0.15 based on keyword overlap ratio.
|
|
73
|
-
*/
|
|
74
|
-
private boostKeywordMatches(query: string, results: NormalizedResult[]): NormalizedResult[] {
|
|
75
|
-
const keywords = query.toLowerCase().split(/\s+/).filter(
|
|
76
|
-
w => w.length > 2 && !KEYWORD_STOP_WORDS.has(w)
|
|
77
|
-
)
|
|
78
|
-
if (keywords.length === 0) return results
|
|
79
|
-
|
|
80
|
-
return results.map(r => {
|
|
81
|
-
const contentLower = r.content.toLowerCase()
|
|
82
|
-
const matches = keywords.filter(kw => contentLower.includes(kw)).length
|
|
83
|
-
const overlapRatio = matches / keywords.length
|
|
84
|
-
const boost = overlapRatio * 0.15
|
|
85
|
-
return {
|
|
86
|
-
...r,
|
|
87
|
-
score: Math.min(r.score + boost, 1.0)
|
|
88
|
-
}
|
|
89
|
-
})
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* Enhanced search — uses hybrid retrieval pipeline when available,
|
|
94
|
-
* falls back to plain searchRaw().
|
|
95
|
-
* Wraps with semantic cache for repeated queries.
|
|
96
|
-
*/
|
|
97
|
-
async enhancedSearch(
|
|
98
|
-
query: string,
|
|
99
|
-
options?: { project?: string; limit?: number; minSimilarity?: number }
|
|
100
|
-
): Promise<NormalizedResult[]> {
|
|
101
|
-
return timed('brain:search', () => this._enhancedSearchInner(query, options))
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
private async _enhancedSearchInner(
|
|
105
|
-
query: string,
|
|
106
|
-
options?: { project?: string; limit?: number; minSimilarity?: number }
|
|
107
|
-
): Promise<NormalizedResult[]> {
|
|
108
|
-
if (!isServicesInitialized()) return []
|
|
109
|
-
|
|
110
|
-
const cacheKey = `search:${query}:${options?.project || 'general'}:${options?.limit || 5}`
|
|
111
|
-
|
|
112
|
-
// Check semantic cache first
|
|
113
|
-
const cache = getSemanticCacheService()
|
|
114
|
-
if (cache) {
|
|
115
|
-
try {
|
|
116
|
-
const cached = await cache.get(cacheKey)
|
|
117
|
-
if (cached) {
|
|
118
|
-
this.logger.debug({ query }, 'Search cache hit')
|
|
119
|
-
return cached as NormalizedResult[]
|
|
120
|
-
}
|
|
121
|
-
} catch {
|
|
122
|
-
// Cache miss or error, continue
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// Try hybrid search via RetrievalPipeline
|
|
127
|
-
const pipeline = getRetrievalPipeline()
|
|
128
|
-
if (pipeline) {
|
|
129
|
-
try {
|
|
130
|
-
const hybridResults = await withTimeout(
|
|
131
|
-
pipeline.search(query, {
|
|
132
|
-
project: options?.project,
|
|
133
|
-
limit: options?.limit || 5,
|
|
134
|
-
minScore: options?.minSimilarity || 0.3
|
|
135
|
-
}),
|
|
136
|
-
SEARCH_TIMEOUT,
|
|
137
|
-
[]
|
|
138
|
-
)
|
|
139
|
-
|
|
140
|
-
if (hybridResults.length > 0) {
|
|
141
|
-
const normalized = this.boostKeywordMatches(query, this.prioritizeProject(hybridResults.map((r:
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
return {
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
)
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
)
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
)
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
)
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
)
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
}
|
|
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
|
+
isServicesInitialized
|
|
18
|
+
} from '@/server/services'
|
|
19
|
+
import { timed } from '@/utils/timing'
|
|
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 normal searches
|
|
32
|
+
const INTELLIGENCE_TIMEOUT = 12000 // 12s timeout for bulk analytical features
|
|
33
|
+
|
|
34
|
+
/** Phase 25: Common stop words excluded from keyword boosting */
|
|
35
|
+
const KEYWORD_STOP_WORDS = new Set([
|
|
36
|
+
'the', 'a', 'an', 'and', 'or', 'but', 'in', 'on', 'at', 'to', 'for',
|
|
37
|
+
'of', 'with', 'by', 'from', 'is', 'it', 'as', 'be', 'was', 'are',
|
|
38
|
+
'were', 'been', 'has', 'have', 'had', 'do', 'does', 'did', 'will',
|
|
39
|
+
'would', 'could', 'should', 'may', 'might', 'can', 'shall', 'not',
|
|
40
|
+
'no', 'nor', 'so', 'up', 'out', 'if', 'about', 'into', 'through',
|
|
41
|
+
'during', 'before', 'after', 'above', 'below', 'between', 'under',
|
|
42
|
+
'again', 'further', 'then', 'once', 'here', 'there', 'when', 'where',
|
|
43
|
+
'why', 'how', 'all', 'each', 'every', 'both', 'few', 'more', 'most',
|
|
44
|
+
'other', 'some', 'such', 'than', 'too', 'very', 'just', 'because',
|
|
45
|
+
'what', 'which', 'who', 'whom', 'this', 'that', 'these', 'those',
|
|
46
|
+
'my', 'your', 'his', 'her', 'its', 'our', 'their', 'me', 'him',
|
|
47
|
+
'she', 'we', 'they', 'you', 'any', 'also', 'only', 'own', 'same',
|
|
48
|
+
'don', 'now', 'get', 'got', 'use', 'used', 'using', 'know', 'need',
|
|
49
|
+
'want', 'like', 'make', 'made', 'think', 'said', 'did', 'does'
|
|
50
|
+
])
|
|
51
|
+
|
|
52
|
+
export class SearchEngine {
|
|
53
|
+
private logger: Logger
|
|
54
|
+
|
|
55
|
+
constructor(logger: Logger) {
|
|
56
|
+
this.logger = logger.child({ component: 'search-engine' })
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Sort results so same-project entries come first (safety net for cross-project noise).
|
|
61
|
+
*/
|
|
62
|
+
private prioritizeProject(results: NormalizedResult[], project?: string): NormalizedResult[] {
|
|
63
|
+
if (!project || results.length === 0) return results
|
|
64
|
+
const sameProject = results.filter(r => r.project === project)
|
|
65
|
+
const otherProject = results.filter(r => r.project !== project)
|
|
66
|
+
if (sameProject.length > 0) return [...sameProject, ...otherProject]
|
|
67
|
+
return results
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Phase 25: Boost results that contain exact query keywords.
|
|
72
|
+
* Adds up to +0.15 based on keyword overlap ratio.
|
|
73
|
+
*/
|
|
74
|
+
private boostKeywordMatches(query: string, results: NormalizedResult[]): NormalizedResult[] {
|
|
75
|
+
const keywords = query.toLowerCase().split(/\s+/).filter(
|
|
76
|
+
w => w.length > 2 && !KEYWORD_STOP_WORDS.has(w)
|
|
77
|
+
)
|
|
78
|
+
if (keywords.length === 0) return results
|
|
79
|
+
|
|
80
|
+
return results.map(r => {
|
|
81
|
+
const contentLower = r.content.toLowerCase()
|
|
82
|
+
const matches = keywords.filter(kw => contentLower.includes(kw)).length
|
|
83
|
+
const overlapRatio = matches / keywords.length
|
|
84
|
+
const boost = overlapRatio * 0.15
|
|
85
|
+
return {
|
|
86
|
+
...r,
|
|
87
|
+
score: Math.min(r.score + boost, 1.0)
|
|
88
|
+
}
|
|
89
|
+
})
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Enhanced search — uses hybrid retrieval pipeline when available,
|
|
94
|
+
* falls back to plain searchRaw().
|
|
95
|
+
* Wraps with semantic cache for repeated queries.
|
|
96
|
+
*/
|
|
97
|
+
async enhancedSearch(
|
|
98
|
+
query: string,
|
|
99
|
+
options?: { project?: string; limit?: number; minSimilarity?: number }
|
|
100
|
+
): Promise<NormalizedResult[]> {
|
|
101
|
+
return timed('brain:search', () => this._enhancedSearchInner(query, options))
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
private async _enhancedSearchInner(
|
|
105
|
+
query: string,
|
|
106
|
+
options?: { project?: string; limit?: number; minSimilarity?: number }
|
|
107
|
+
): Promise<NormalizedResult[]> {
|
|
108
|
+
if (!isServicesInitialized()) return []
|
|
109
|
+
|
|
110
|
+
const cacheKey = `search:${query}:${options?.project || 'general'}:${options?.limit || 5}`
|
|
111
|
+
|
|
112
|
+
// Check semantic cache first
|
|
113
|
+
const cache = getSemanticCacheService()
|
|
114
|
+
if (cache) {
|
|
115
|
+
try {
|
|
116
|
+
const cached = await cache.get(cacheKey)
|
|
117
|
+
if (cached) {
|
|
118
|
+
this.logger.debug({ query }, 'Search cache hit')
|
|
119
|
+
return cached as NormalizedResult[]
|
|
120
|
+
}
|
|
121
|
+
} catch {
|
|
122
|
+
// Cache miss or error, continue
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Try hybrid search via RetrievalPipeline
|
|
127
|
+
const pipeline = getRetrievalPipeline()
|
|
128
|
+
if (pipeline) {
|
|
129
|
+
try {
|
|
130
|
+
const hybridResults = await withTimeout(
|
|
131
|
+
pipeline.search(query, {
|
|
132
|
+
project: options?.project,
|
|
133
|
+
limit: options?.limit || 5,
|
|
134
|
+
minScore: options?.minSimilarity || 0.3
|
|
135
|
+
}),
|
|
136
|
+
SEARCH_TIMEOUT,
|
|
137
|
+
[]
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
if (hybridResults.length > 0) {
|
|
141
|
+
const normalized = this.boostKeywordMatches(query, this.prioritizeProject(hybridResults.map((r: Record<string, unknown>) => {
|
|
142
|
+
const scores = r.scores as Record<string, unknown> | undefined
|
|
143
|
+
const meta = (r.metadata || {}) as Record<string, unknown>
|
|
144
|
+
return {
|
|
145
|
+
id: String(r.id || ''),
|
|
146
|
+
content: typeof r.content === 'string' ? r.content : String(r.document || JSON.stringify(r.content ?? '')),
|
|
147
|
+
score: (scores?.final as number) || (r.score as number) || (r.similarity as number) || 0,
|
|
148
|
+
source: (r.collection === 'patterns' ? 'pattern' as const : r.collection === 'corrections' ? 'correction' as const : 'decision' as const),
|
|
149
|
+
project: String(meta.project || options?.project || ''),
|
|
150
|
+
date: String(meta.created_at || ''),
|
|
151
|
+
metadata: meta
|
|
152
|
+
}
|
|
153
|
+
}), options?.project))
|
|
154
|
+
|
|
155
|
+
// Cache results
|
|
156
|
+
if (cache) {
|
|
157
|
+
try { await cache.set(cacheKey, normalized) } catch {}
|
|
158
|
+
}
|
|
159
|
+
return normalized
|
|
160
|
+
}
|
|
161
|
+
} catch (error) {
|
|
162
|
+
this.logger.debug({ error }, 'Hybrid search failed, falling back to plain search')
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Fallback: plain searchRaw
|
|
167
|
+
return this.plainSearch(query, options, cache, cacheKey)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Plain search using memory.searchRaw() — always available.
|
|
172
|
+
*/
|
|
173
|
+
async plainSearch(
|
|
174
|
+
query: string,
|
|
175
|
+
options?: { project?: string; limit?: number; minSimilarity?: number },
|
|
176
|
+
cache?: { set: (key: string, value: unknown) => Promise<void> },
|
|
177
|
+
cacheKey?: string
|
|
178
|
+
): Promise<NormalizedResult[]> {
|
|
179
|
+
if (!isServicesInitialized()) return []
|
|
180
|
+
|
|
181
|
+
const memory = getMemoryService()
|
|
182
|
+
try {
|
|
183
|
+
const rawResults = await withTimeout(
|
|
184
|
+
memory.searchRaw(query, {
|
|
185
|
+
project: options?.project,
|
|
186
|
+
limit: options?.limit || 5,
|
|
187
|
+
minSimilarity: options?.minSimilarity || 0.3
|
|
188
|
+
}),
|
|
189
|
+
SEARCH_TIMEOUT,
|
|
190
|
+
[]
|
|
191
|
+
)
|
|
192
|
+
const normalized = this.boostKeywordMatches(query, this.prioritizeProject(normalizeSearchResults(rawResults), options?.project))
|
|
193
|
+
|
|
194
|
+
// Cache results
|
|
195
|
+
if (cache && cacheKey) {
|
|
196
|
+
try { await cache.set(cacheKey, normalized) } catch {}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return normalized
|
|
200
|
+
} catch {
|
|
201
|
+
return []
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Search patterns with normalization and timeout
|
|
207
|
+
*/
|
|
208
|
+
async searchPatterns(
|
|
209
|
+
query: string,
|
|
210
|
+
options?: { project?: string; limit?: number; minSimilarity?: number }
|
|
211
|
+
): Promise<NormalizedResult[]> {
|
|
212
|
+
if (!isServicesInitialized()) return []
|
|
213
|
+
const memory = getMemoryService()
|
|
214
|
+
try {
|
|
215
|
+
const results = await withTimeout(
|
|
216
|
+
memory.searchPatterns(query, {
|
|
217
|
+
project: options?.project,
|
|
218
|
+
limit: options?.limit || 3,
|
|
219
|
+
minSimilarity: options?.minSimilarity || 0.3
|
|
220
|
+
}),
|
|
221
|
+
SEARCH_TIMEOUT,
|
|
222
|
+
[]
|
|
223
|
+
)
|
|
224
|
+
return normalizePatternResults(results)
|
|
225
|
+
} catch {
|
|
226
|
+
return []
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Search corrections with normalization and timeout
|
|
232
|
+
*/
|
|
233
|
+
async searchCorrections(
|
|
234
|
+
query: string,
|
|
235
|
+
options?: { project?: string; limit?: number; minSimilarity?: number }
|
|
236
|
+
): Promise<NormalizedResult[]> {
|
|
237
|
+
if (!isServicesInitialized()) return []
|
|
238
|
+
const memory = getMemoryService()
|
|
239
|
+
try {
|
|
240
|
+
const results = await withTimeout(
|
|
241
|
+
memory.searchCorrections(query, {
|
|
242
|
+
project: options?.project,
|
|
243
|
+
limit: options?.limit || 3,
|
|
244
|
+
minSimilarity: options?.minSimilarity || 0.3
|
|
245
|
+
}),
|
|
246
|
+
SEARCH_TIMEOUT,
|
|
247
|
+
[]
|
|
248
|
+
)
|
|
249
|
+
return normalizeCorrectionResults(results)
|
|
250
|
+
} catch {
|
|
251
|
+
return []
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Search knowledge graph for related concepts
|
|
257
|
+
*/
|
|
258
|
+
async searchGraph(query: string, limit: number = 5): Promise<NormalizedResult[]> {
|
|
259
|
+
try {
|
|
260
|
+
const kgService = getKnowledgeGraphService()
|
|
261
|
+
if (!kgService?.search) return []
|
|
262
|
+
|
|
263
|
+
const graphResults = kgService.search.search({ query, limit })
|
|
264
|
+
if (!graphResults?.nodes?.length) return []
|
|
265
|
+
|
|
266
|
+
return graphResults.nodes.map((n: Record<string, unknown>) => {
|
|
267
|
+
const props = (n.properties || {}) as Record<string, unknown>
|
|
268
|
+
return {
|
|
269
|
+
id: String(n.id || ''),
|
|
270
|
+
content: `**${String(n.label || n.name)}** (${String(n.type)})${props.description ? `\n${props.description}` : ''}`,
|
|
271
|
+
score: graphResults.scores.get(n.id as string) || 0.5,
|
|
272
|
+
source: 'graph' as const,
|
|
273
|
+
project: String(props.project || ''),
|
|
274
|
+
date: '',
|
|
275
|
+
metadata: props
|
|
276
|
+
}
|
|
277
|
+
})
|
|
278
|
+
} catch {
|
|
279
|
+
return []
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Temporal search — parse temporal phrases from query, filter results by date range.
|
|
285
|
+
* Uses TemporalQueryProcessor when available.
|
|
286
|
+
*/
|
|
287
|
+
async temporalSearch(
|
|
288
|
+
query: string,
|
|
289
|
+
options?: { project?: string; limit?: number }
|
|
290
|
+
): Promise<{ results: NormalizedResult[]; cleanedQuery: string }> {
|
|
291
|
+
if (!isServicesInitialized()) return { results: [], cleanedQuery: query }
|
|
292
|
+
|
|
293
|
+
try {
|
|
294
|
+
// Dynamic import to avoid hard dependency
|
|
295
|
+
const { TemporalQueryProcessor } = await import('@/intelligence/temporal/query-processor')
|
|
296
|
+
const processor = new TemporalQueryProcessor(this.logger)
|
|
297
|
+
const parsed = processor.parse(query)
|
|
298
|
+
|
|
299
|
+
if (parsed.intent === 'none') {
|
|
300
|
+
// No temporal expressions found, use normal search
|
|
301
|
+
const results = await this.enhancedSearch(query, options)
|
|
302
|
+
return { results, cleanedQuery: query }
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// Search with cleaned query (temporal phrases removed)
|
|
306
|
+
const results = await this.enhancedSearch(parsed.cleaned || query, {
|
|
307
|
+
...options,
|
|
308
|
+
limit: (options?.limit || 5) * 2 // Fetch more since we'll filter by date
|
|
309
|
+
})
|
|
310
|
+
|
|
311
|
+
// Filter results by date range
|
|
312
|
+
const filtered = results.filter(r => {
|
|
313
|
+
if (!r.date) return true // Keep results without dates
|
|
314
|
+
const date = new Date(r.date)
|
|
315
|
+
if (isNaN(date.getTime())) return true
|
|
316
|
+
if (parsed.startDate && date < new Date(parsed.startDate)) return false
|
|
317
|
+
if (parsed.endDate && date > new Date(parsed.endDate)) return false
|
|
318
|
+
return true
|
|
319
|
+
})
|
|
320
|
+
|
|
321
|
+
return {
|
|
322
|
+
results: filtered.slice(0, options?.limit || 5),
|
|
323
|
+
cleanedQuery: parsed.cleaned || query
|
|
324
|
+
}
|
|
325
|
+
} catch {
|
|
326
|
+
// TemporalQueryProcessor not available, fall back
|
|
327
|
+
const results = await this.enhancedSearch(query, options)
|
|
328
|
+
return { results, cleanedQuery: query }
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Build a timeline for a topic/project
|
|
334
|
+
*/
|
|
335
|
+
async buildTimeline(
|
|
336
|
+
options?: { project?: string; topic?: string; 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 { TimelineBuilder } = await import('@/intelligence/temporal/timeline')
|
|
345
|
+
const builder = new TimelineBuilder(
|
|
346
|
+
this.logger,
|
|
347
|
+
memory.chroma.collections,
|
|
348
|
+
memory.chroma.embeddings
|
|
349
|
+
)
|
|
350
|
+
return await withTimeout(
|
|
351
|
+
builder.buildTimeline(options),
|
|
352
|
+
SEARCH_TIMEOUT,
|
|
353
|
+
null
|
|
354
|
+
)
|
|
355
|
+
} catch {
|
|
356
|
+
return null
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Analyze decision evolution for a topic
|
|
362
|
+
*/
|
|
363
|
+
async analyzeEvolution(
|
|
364
|
+
topic: string,
|
|
365
|
+
options?: { project?: string; limit?: number }
|
|
366
|
+
): Promise<any | null> {
|
|
367
|
+
if (!isServicesInitialized()) return null
|
|
368
|
+
|
|
369
|
+
try {
|
|
370
|
+
const memory = getMemoryService()
|
|
371
|
+
if (!memory.isChromaDBEnabled()) return null
|
|
372
|
+
|
|
373
|
+
const { DecisionEvolutionTracker } = await import('@/intelligence/temporal/evolution')
|
|
374
|
+
const tracker = new DecisionEvolutionTracker(
|
|
375
|
+
this.logger,
|
|
376
|
+
memory.chroma.collections,
|
|
377
|
+
memory.chroma.embeddings
|
|
378
|
+
)
|
|
379
|
+
return await withTimeout(
|
|
380
|
+
tracker.analyzeEvolution(topic, options),
|
|
381
|
+
INTELLIGENCE_TIMEOUT,
|
|
382
|
+
null
|
|
383
|
+
)
|
|
384
|
+
} catch {
|
|
385
|
+
return null
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* Detect trends
|
|
391
|
+
*/
|
|
392
|
+
async detectTrends(
|
|
393
|
+
options?: { project?: string; periodDays?: number; 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 { TrendDetector } = await import('@/intelligence/temporal/trends')
|
|
402
|
+
const detector = new TrendDetector(this.logger, memory.chroma.collections)
|
|
403
|
+
return await withTimeout(
|
|
404
|
+
detector.detectTrends(options),
|
|
405
|
+
INTELLIGENCE_TIMEOUT,
|
|
406
|
+
null
|
|
407
|
+
)
|
|
408
|
+
} catch {
|
|
409
|
+
return null
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* Multi-hop chain retrieval for complex questions
|
|
415
|
+
*/
|
|
416
|
+
async chainSearch(
|
|
417
|
+
query: string,
|
|
418
|
+
options?: { project?: string; maxHops?: number }
|
|
419
|
+
): Promise<any | null> {
|
|
420
|
+
if (!isServicesInitialized()) return null
|
|
421
|
+
|
|
422
|
+
try {
|
|
423
|
+
const memory = getMemoryService()
|
|
424
|
+
if (!memory.isChromaDBEnabled()) return null
|
|
425
|
+
|
|
426
|
+
const { ChainRetrieval } = await import('@/intelligence/reasoning/chain-retrieval')
|
|
427
|
+
const chain = new ChainRetrieval(
|
|
428
|
+
this.logger,
|
|
429
|
+
memory.chroma.collections,
|
|
430
|
+
memory.chroma.embeddings
|
|
431
|
+
)
|
|
432
|
+
return await withTimeout(
|
|
433
|
+
chain.retrieve(query, {
|
|
434
|
+
maxHops: options?.maxHops || 3,
|
|
435
|
+
project: options?.project
|
|
436
|
+
}),
|
|
437
|
+
INTELLIGENCE_TIMEOUT,
|
|
438
|
+
null
|
|
439
|
+
)
|
|
440
|
+
} catch {
|
|
441
|
+
return null
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* Get recommendations based on context
|
|
447
|
+
*/
|
|
448
|
+
async getRecommendations(
|
|
449
|
+
query: string,
|
|
450
|
+
options?: { project?: string; limit?: number }
|
|
451
|
+
): Promise<any | null> {
|
|
452
|
+
if (!isServicesInitialized()) return null
|
|
453
|
+
|
|
454
|
+
try {
|
|
455
|
+
const memory = getMemoryService()
|
|
456
|
+
if (!memory.isChromaDBEnabled()) return null
|
|
457
|
+
|
|
458
|
+
const { Recommender } = await import('@/intelligence/prediction/recommender')
|
|
459
|
+
const recommender = new Recommender(
|
|
460
|
+
this.logger,
|
|
461
|
+
memory.chroma.collections,
|
|
462
|
+
memory.chroma.embeddings
|
|
463
|
+
)
|
|
464
|
+
return await withTimeout(
|
|
465
|
+
recommender.getRecommendations(query, {
|
|
466
|
+
project: options?.project,
|
|
467
|
+
limit: options?.limit || 5
|
|
468
|
+
}),
|
|
469
|
+
INTELLIGENCE_TIMEOUT,
|
|
470
|
+
null
|
|
471
|
+
)
|
|
472
|
+
} catch {
|
|
473
|
+
return null
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
/**
|
|
478
|
+
* Find cross-project patterns
|
|
479
|
+
*/
|
|
480
|
+
async findCrossProjectPatterns(
|
|
481
|
+
options?: { query?: string; minProjects?: number; limit?: number }
|
|
482
|
+
): Promise<any | null> {
|
|
483
|
+
if (!isServicesInitialized()) return null
|
|
484
|
+
|
|
485
|
+
try {
|
|
486
|
+
const memory = getMemoryService()
|
|
487
|
+
if (!memory.isChromaDBEnabled()) return null
|
|
488
|
+
|
|
489
|
+
const { PatternGeneralizer } = await import('@/intelligence/cross-project/generalizer')
|
|
490
|
+
const generalizer = new PatternGeneralizer(
|
|
491
|
+
this.logger,
|
|
492
|
+
memory.chroma.collections,
|
|
493
|
+
memory.chroma.embeddings
|
|
494
|
+
)
|
|
495
|
+
return await withTimeout(
|
|
496
|
+
generalizer.findCrossProjectPatterns(options),
|
|
497
|
+
INTELLIGENCE_TIMEOUT,
|
|
498
|
+
null
|
|
499
|
+
)
|
|
500
|
+
} catch {
|
|
501
|
+
return null
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
/**
|
|
506
|
+
* Invalidate cache entries for a project after store/update/delete
|
|
507
|
+
*/
|
|
508
|
+
invalidateCache(project?: string): void {
|
|
509
|
+
try {
|
|
510
|
+
const cache = getSemanticCacheService()
|
|
511
|
+
if (!cache) return
|
|
512
|
+
if (project) {
|
|
513
|
+
cache.invalidateProject(project)
|
|
514
|
+
} else {
|
|
515
|
+
cache.clear()
|
|
516
|
+
}
|
|
517
|
+
} catch {
|
|
518
|
+
// Cache not available
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
}
|