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,373 +1,373 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Proactive Recall Engine
|
|
3
|
-
* Phase 12: Advanced Memory & Intelligent Automation
|
|
4
|
-
*
|
|
5
|
-
* Automatically searches for relevant context before responding
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import type { Logger } from 'pino'
|
|
9
|
-
import type { MemoryManager } from '@/memory'
|
|
10
|
-
import type { MemorySearchResult } from '@/memory/types'
|
|
11
|
-
|
|
12
|
-
export interface RecallResult {
|
|
13
|
-
query: string
|
|
14
|
-
memories: MemorySearchResult[]
|
|
15
|
-
relevanceScore: number
|
|
16
|
-
triggeredBy: string[]
|
|
17
|
-
formattedContext?: string
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export interface RecallStats {
|
|
21
|
-
totalRecalls: number
|
|
22
|
-
successfulRecalls: number
|
|
23
|
-
averageRelevance: number
|
|
24
|
-
lastRecallTime?: Date
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export class ProactiveRecallEngine {
|
|
28
|
-
private logger: Logger
|
|
29
|
-
private memory: MemoryManager
|
|
30
|
-
|
|
31
|
-
// Track statistics
|
|
32
|
-
private recallCount = 0
|
|
33
|
-
private successfulRecalls = 0
|
|
34
|
-
private totalRelevance = 0
|
|
35
|
-
private lastRecallTime?: Date
|
|
36
|
-
|
|
37
|
-
// Keywords that trigger recall
|
|
38
|
-
private readonly RECALL_TRIGGERS = [
|
|
39
|
-
'how should',
|
|
40
|
-
"what's the best",
|
|
41
|
-
'which',
|
|
42
|
-
'recommend',
|
|
43
|
-
'approach',
|
|
44
|
-
'implement',
|
|
45
|
-
'handle',
|
|
46
|
-
'deal with',
|
|
47
|
-
'what did we',
|
|
48
|
-
'remember when',
|
|
49
|
-
'previously',
|
|
50
|
-
'last time',
|
|
51
|
-
'similar to',
|
|
52
|
-
'like before',
|
|
53
|
-
'as we did',
|
|
54
|
-
'pattern for',
|
|
55
|
-
'best practice',
|
|
56
|
-
'how do i',
|
|
57
|
-
'what is the'
|
|
58
|
-
]
|
|
59
|
-
|
|
60
|
-
// Question indicators
|
|
61
|
-
private readonly QUESTION_PATTERNS = [
|
|
62
|
-
/^how\s/i,
|
|
63
|
-
/^what\s/i,
|
|
64
|
-
/^which\s/i,
|
|
65
|
-
/^should\s/i,
|
|
66
|
-
/^can\s(i|we)\s/i,
|
|
67
|
-
/\?$/
|
|
68
|
-
]
|
|
69
|
-
|
|
70
|
-
constructor(logger: Logger, memory: MemoryManager) {
|
|
71
|
-
this.logger = logger.child({ component: 'proactive-recall' })
|
|
72
|
-
this.memory = memory
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* Check if query should trigger recall
|
|
77
|
-
*/
|
|
78
|
-
shouldRecall(query: string): boolean {
|
|
79
|
-
const queryLower = query.toLowerCase()
|
|
80
|
-
|
|
81
|
-
// Check trigger phrases
|
|
82
|
-
const hasTrigger = this.RECALL_TRIGGERS.some(trigger =>
|
|
83
|
-
queryLower.includes(trigger)
|
|
84
|
-
)
|
|
85
|
-
|
|
86
|
-
if (hasTrigger) return true
|
|
87
|
-
|
|
88
|
-
// Check question patterns
|
|
89
|
-
const isQuestion = this.QUESTION_PATTERNS.some(pattern =>
|
|
90
|
-
pattern.test(query)
|
|
91
|
-
)
|
|
92
|
-
|
|
93
|
-
// Questions about implementation or choices should trigger recall
|
|
94
|
-
if (isQuestion) {
|
|
95
|
-
const implementationWords = [
|
|
96
|
-
'implement', 'create', 'build', 'design', 'structure',
|
|
97
|
-
'organize', 'handle', 'manage', 'use', 'choose'
|
|
98
|
-
]
|
|
99
|
-
return implementationWords.some(word => queryLower.includes(word))
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
return false
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* Get which triggers matched
|
|
107
|
-
*/
|
|
108
|
-
private getMatchedTriggers(query: string): string[] {
|
|
109
|
-
const queryLower = query.toLowerCase()
|
|
110
|
-
const matched: string[] = []
|
|
111
|
-
|
|
112
|
-
for (const trigger of this.RECALL_TRIGGERS) {
|
|
113
|
-
if (queryLower.includes(trigger)) {
|
|
114
|
-
matched.push(trigger)
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
for (const pattern of this.QUESTION_PATTERNS) {
|
|
119
|
-
if (pattern.test(query)) {
|
|
120
|
-
matched.push('question-pattern')
|
|
121
|
-
break
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
return matched
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
/**
|
|
129
|
-
* Extract search terms from query
|
|
130
|
-
*/
|
|
131
|
-
extractSearchTerms(query: string): string[] {
|
|
132
|
-
// Common stop words to remove
|
|
133
|
-
const stopWords = new Set([
|
|
134
|
-
'the', 'a', 'an', 'and', 'or', 'but', 'in', 'on', 'at',
|
|
135
|
-
'to', 'for', 'of', 'with', 'by', 'from', 'how', 'what',
|
|
136
|
-
'should', 'i', 'we', 'is', 'are', 'do', 'does', 'can',
|
|
137
|
-
'could', 'would', 'will', 'be', 'been', 'being', 'have',
|
|
138
|
-
'has', 'had', 'this', 'that', 'these', 'those', 'it',
|
|
139
|
-
'its', 'my', 'our', 'your', 'their', 'about', 'like'
|
|
140
|
-
])
|
|
141
|
-
|
|
142
|
-
const words = query
|
|
143
|
-
.toLowerCase()
|
|
144
|
-
.replace(/[^\w\s]/g, ' ')
|
|
145
|
-
.split(/\s+/)
|
|
146
|
-
.filter(word => word.length > 2 && !stopWords.has(word))
|
|
147
|
-
|
|
148
|
-
// Also extract potential compound terms
|
|
149
|
-
const compounds: string[] = []
|
|
150
|
-
for (let i = 0; i < words.length - 1; i++) {
|
|
151
|
-
if (words[i]!.length > 3 && words[i + 1]!.length > 3) {
|
|
152
|
-
compounds.push(`${words[i]} ${words[i + 1]}`)
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
return [...new Set([...words, ...compounds])]
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
/**
|
|
160
|
-
* Proactively recall relevant memories
|
|
161
|
-
*/
|
|
162
|
-
async recall(query: string, project?: string): Promise<RecallResult | null> {
|
|
163
|
-
this.recallCount++
|
|
164
|
-
|
|
165
|
-
if (!this.shouldRecall(query)) {
|
|
166
|
-
return null
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
const triggeredBy = this.getMatchedTriggers(query)
|
|
170
|
-
this.logger.info({ query: query.slice(0, 50), triggeredBy }, 'Proactive recall triggered')
|
|
171
|
-
|
|
172
|
-
// Extract search terms
|
|
173
|
-
const searchTerms = this.extractSearchTerms(query)
|
|
174
|
-
|
|
175
|
-
if (searchTerms.length === 0) {
|
|
176
|
-
return null
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
// Search for each term and combine results
|
|
180
|
-
const allMemories: Map<string, MemorySearchResult> = new Map()
|
|
181
|
-
|
|
182
|
-
// Search with the full query first
|
|
183
|
-
try {
|
|
184
|
-
const fullResults = await this.memory.searchRaw(query, {
|
|
185
|
-
project,
|
|
186
|
-
limit: 5,
|
|
187
|
-
minSimilarity: 0.5
|
|
188
|
-
})
|
|
189
|
-
|
|
190
|
-
for (const memory of fullResults) {
|
|
191
|
-
allMemories.set(memory.memory.id, memory)
|
|
192
|
-
}
|
|
193
|
-
} catch (error) {
|
|
194
|
-
this.logger.warn({ error }, 'Full query search failed')
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
// Then search individual terms for broader coverage
|
|
198
|
-
for (const term of searchTerms.slice(0, 5)) {
|
|
199
|
-
try {
|
|
200
|
-
const memories = await this.memory.searchRaw(term, {
|
|
201
|
-
project,
|
|
202
|
-
limit: 3,
|
|
203
|
-
minSimilarity: 0.6
|
|
204
|
-
})
|
|
205
|
-
|
|
206
|
-
for (const memory of memories) {
|
|
207
|
-
// Only add if not already present or if higher similarity
|
|
208
|
-
const existing = allMemories.get(memory.memory.id)
|
|
209
|
-
if (!existing || memory.similarity > existing.similarity) {
|
|
210
|
-
allMemories.set(memory.memory.id, memory)
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
} catch (error) {
|
|
214
|
-
this.logger.warn({ error, term }, 'Term search failed')
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
const memories = Array.from(allMemories.values())
|
|
219
|
-
.sort((a, b) => b.similarity - a.similarity)
|
|
220
|
-
.slice(0, 5)
|
|
221
|
-
|
|
222
|
-
// Calculate relevance score
|
|
223
|
-
const relevanceScore = this.calculateRelevance(query, memories)
|
|
224
|
-
|
|
225
|
-
// Update stats
|
|
226
|
-
if (memories.length > 0) {
|
|
227
|
-
this.successfulRecalls++
|
|
228
|
-
this.totalRelevance += relevanceScore
|
|
229
|
-
}
|
|
230
|
-
this.lastRecallTime = new Date()
|
|
231
|
-
|
|
232
|
-
this.logger.info(
|
|
233
|
-
{
|
|
234
|
-
query: query.slice(0, 50),
|
|
235
|
-
memoryCount: memories.length,
|
|
236
|
-
relevanceScore
|
|
237
|
-
},
|
|
238
|
-
'Proactive recall complete'
|
|
239
|
-
)
|
|
240
|
-
|
|
241
|
-
const result: RecallResult = {
|
|
242
|
-
query,
|
|
243
|
-
memories,
|
|
244
|
-
relevanceScore,
|
|
245
|
-
triggeredBy
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
// Add formatted context if we have results
|
|
249
|
-
if (memories.length > 0 && relevanceScore > 0.4) {
|
|
250
|
-
result.formattedContext = this.formatForContext(result)
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
return result
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
/**
|
|
257
|
-
* Calculate relevance score
|
|
258
|
-
*/
|
|
259
|
-
private calculateRelevance(_query: string, memories: MemorySearchResult[]): number {
|
|
260
|
-
if (memories.length === 0) return 0
|
|
261
|
-
|
|
262
|
-
// Average similarity score
|
|
263
|
-
const avgSimilarity = memories.reduce((sum, m) => sum + m.similarity, 0) / memories.length
|
|
264
|
-
|
|
265
|
-
// Recency bonus (newer memories are more relevant)
|
|
266
|
-
const now = Date.now()
|
|
267
|
-
let totalRecencyScore = 0
|
|
268
|
-
|
|
269
|
-
for (const memory of memories) {
|
|
270
|
-
const age = now - memory.memory.createdAt.getTime()
|
|
271
|
-
const daysSince = age / (1000 * 60 * 60 * 24)
|
|
272
|
-
const recencyScore = Math.max(0, 1 - (daysSince / 365)) // Decay over a year
|
|
273
|
-
totalRecencyScore += recencyScore
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
const avgRecency = totalRecencyScore / memories.length
|
|
277
|
-
|
|
278
|
-
// Decision bonus (decisions are more relevant than raw memories)
|
|
279
|
-
const decisionCount = memories.filter(m => m.decision).length
|
|
280
|
-
const decisionBonus = (decisionCount / memories.length) * 0.1
|
|
281
|
-
|
|
282
|
-
// Combine scores
|
|
283
|
-
return (avgSimilarity * 0.6) + (avgRecency * 0.3) + decisionBonus
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
/**
|
|
287
|
-
* Format memories for context
|
|
288
|
-
*/
|
|
289
|
-
formatForContext(result: RecallResult): string {
|
|
290
|
-
if (result.memories.length === 0) {
|
|
291
|
-
return ''
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
const parts: string[] = []
|
|
295
|
-
parts.push('\n---')
|
|
296
|
-
parts.push('**Relevant Past Context** (proactively recalled):')
|
|
297
|
-
parts.push('')
|
|
298
|
-
|
|
299
|
-
for (const [i, memory] of result.memories.entries()) {
|
|
300
|
-
const title = memory.decision?.decision || memory.memory.content.slice(0, 100)
|
|
301
|
-
parts.push(`${i + 1}. **${title}**`)
|
|
302
|
-
|
|
303
|
-
if (memory.decision?.reasoning) {
|
|
304
|
-
parts.push(` *Reasoning:* ${memory.decision.reasoning}`)
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
parts.push(` *Project:* ${memory.memory.project}`)
|
|
308
|
-
parts.push(` *Relevance:* ${Math.round(memory.similarity * 100)}%`)
|
|
309
|
-
|
|
310
|
-
const date = memory.memory.createdAt.toLocaleDateString()
|
|
311
|
-
parts.push(` *Recorded:* ${date}`)
|
|
312
|
-
parts.push('')
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
parts.push('---')
|
|
316
|
-
return parts.join('\n')
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
/**
|
|
320
|
-
* Get recall statistics
|
|
321
|
-
*/
|
|
322
|
-
getStats(): RecallStats {
|
|
323
|
-
return {
|
|
324
|
-
totalRecalls: this.recallCount,
|
|
325
|
-
successfulRecalls: this.successfulRecalls,
|
|
326
|
-
averageRelevance: this.successfulRecalls > 0
|
|
327
|
-
? this.totalRelevance / this.successfulRecalls
|
|
328
|
-
: 0,
|
|
329
|
-
lastRecallTime: this.lastRecallTime
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
/**
|
|
334
|
-
* Reset statistics
|
|
335
|
-
*/
|
|
336
|
-
resetStats(): void {
|
|
337
|
-
this.recallCount = 0
|
|
338
|
-
this.successfulRecalls = 0
|
|
339
|
-
this.totalRelevance = 0
|
|
340
|
-
this.lastRecallTime = undefined
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
/**
|
|
344
|
-
* Add custom recall triggers
|
|
345
|
-
*/
|
|
346
|
-
addTriggers(triggers: string[]): void {
|
|
347
|
-
for (const trigger of triggers) {
|
|
348
|
-
if (!this.RECALL_TRIGGERS.includes(trigger.toLowerCase())) {
|
|
349
|
-
(this.RECALL_TRIGGERS as string[]).push(trigger.toLowerCase())
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
/**
|
|
355
|
-
* Quick recall for a specific topic
|
|
356
|
-
*/
|
|
357
|
-
async recallTopic(
|
|
358
|
-
topic: string,
|
|
359
|
-
project?: string,
|
|
360
|
-
limit: number = 3
|
|
361
|
-
): Promise<MemorySearchResult[]> {
|
|
362
|
-
try {
|
|
363
|
-
return await this.memory.searchRaw(topic, {
|
|
364
|
-
project,
|
|
365
|
-
limit,
|
|
366
|
-
minSimilarity: 0.5
|
|
367
|
-
})
|
|
368
|
-
} catch (error) {
|
|
369
|
-
this.logger.error({ error, topic }, 'Topic recall failed')
|
|
370
|
-
return []
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Proactive Recall Engine
|
|
3
|
+
* Phase 12: Advanced Memory & Intelligent Automation
|
|
4
|
+
*
|
|
5
|
+
* Automatically searches for relevant context before responding
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { Logger } from 'pino'
|
|
9
|
+
import type { MemoryManager } from '@/memory'
|
|
10
|
+
import type { MemorySearchResult } from '@/memory/types'
|
|
11
|
+
|
|
12
|
+
export interface RecallResult {
|
|
13
|
+
query: string
|
|
14
|
+
memories: MemorySearchResult[]
|
|
15
|
+
relevanceScore: number
|
|
16
|
+
triggeredBy: string[]
|
|
17
|
+
formattedContext?: string
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface RecallStats {
|
|
21
|
+
totalRecalls: number
|
|
22
|
+
successfulRecalls: number
|
|
23
|
+
averageRelevance: number
|
|
24
|
+
lastRecallTime?: Date
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export class ProactiveRecallEngine {
|
|
28
|
+
private logger: Logger
|
|
29
|
+
private memory: MemoryManager
|
|
30
|
+
|
|
31
|
+
// Track statistics
|
|
32
|
+
private recallCount = 0
|
|
33
|
+
private successfulRecalls = 0
|
|
34
|
+
private totalRelevance = 0
|
|
35
|
+
private lastRecallTime?: Date
|
|
36
|
+
|
|
37
|
+
// Keywords that trigger recall
|
|
38
|
+
private readonly RECALL_TRIGGERS = [
|
|
39
|
+
'how should',
|
|
40
|
+
"what's the best",
|
|
41
|
+
'which',
|
|
42
|
+
'recommend',
|
|
43
|
+
'approach',
|
|
44
|
+
'implement',
|
|
45
|
+
'handle',
|
|
46
|
+
'deal with',
|
|
47
|
+
'what did we',
|
|
48
|
+
'remember when',
|
|
49
|
+
'previously',
|
|
50
|
+
'last time',
|
|
51
|
+
'similar to',
|
|
52
|
+
'like before',
|
|
53
|
+
'as we did',
|
|
54
|
+
'pattern for',
|
|
55
|
+
'best practice',
|
|
56
|
+
'how do i',
|
|
57
|
+
'what is the'
|
|
58
|
+
]
|
|
59
|
+
|
|
60
|
+
// Question indicators
|
|
61
|
+
private readonly QUESTION_PATTERNS = [
|
|
62
|
+
/^how\s/i,
|
|
63
|
+
/^what\s/i,
|
|
64
|
+
/^which\s/i,
|
|
65
|
+
/^should\s/i,
|
|
66
|
+
/^can\s(i|we)\s/i,
|
|
67
|
+
/\?$/
|
|
68
|
+
]
|
|
69
|
+
|
|
70
|
+
constructor(logger: Logger, memory: MemoryManager) {
|
|
71
|
+
this.logger = logger.child({ component: 'proactive-recall' })
|
|
72
|
+
this.memory = memory
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Check if query should trigger recall
|
|
77
|
+
*/
|
|
78
|
+
shouldRecall(query: string): boolean {
|
|
79
|
+
const queryLower = query.toLowerCase()
|
|
80
|
+
|
|
81
|
+
// Check trigger phrases
|
|
82
|
+
const hasTrigger = this.RECALL_TRIGGERS.some(trigger =>
|
|
83
|
+
queryLower.includes(trigger)
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
if (hasTrigger) return true
|
|
87
|
+
|
|
88
|
+
// Check question patterns
|
|
89
|
+
const isQuestion = this.QUESTION_PATTERNS.some(pattern =>
|
|
90
|
+
pattern.test(query)
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
// Questions about implementation or choices should trigger recall
|
|
94
|
+
if (isQuestion) {
|
|
95
|
+
const implementationWords = [
|
|
96
|
+
'implement', 'create', 'build', 'design', 'structure',
|
|
97
|
+
'organize', 'handle', 'manage', 'use', 'choose'
|
|
98
|
+
]
|
|
99
|
+
return implementationWords.some(word => queryLower.includes(word))
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return false
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Get which triggers matched
|
|
107
|
+
*/
|
|
108
|
+
private getMatchedTriggers(query: string): string[] {
|
|
109
|
+
const queryLower = query.toLowerCase()
|
|
110
|
+
const matched: string[] = []
|
|
111
|
+
|
|
112
|
+
for (const trigger of this.RECALL_TRIGGERS) {
|
|
113
|
+
if (queryLower.includes(trigger)) {
|
|
114
|
+
matched.push(trigger)
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
for (const pattern of this.QUESTION_PATTERNS) {
|
|
119
|
+
if (pattern.test(query)) {
|
|
120
|
+
matched.push('question-pattern')
|
|
121
|
+
break
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return matched
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Extract search terms from query
|
|
130
|
+
*/
|
|
131
|
+
extractSearchTerms(query: string): string[] {
|
|
132
|
+
// Common stop words to remove
|
|
133
|
+
const stopWords = new Set([
|
|
134
|
+
'the', 'a', 'an', 'and', 'or', 'but', 'in', 'on', 'at',
|
|
135
|
+
'to', 'for', 'of', 'with', 'by', 'from', 'how', 'what',
|
|
136
|
+
'should', 'i', 'we', 'is', 'are', 'do', 'does', 'can',
|
|
137
|
+
'could', 'would', 'will', 'be', 'been', 'being', 'have',
|
|
138
|
+
'has', 'had', 'this', 'that', 'these', 'those', 'it',
|
|
139
|
+
'its', 'my', 'our', 'your', 'their', 'about', 'like'
|
|
140
|
+
])
|
|
141
|
+
|
|
142
|
+
const words = query
|
|
143
|
+
.toLowerCase()
|
|
144
|
+
.replace(/[^\w\s]/g, ' ')
|
|
145
|
+
.split(/\s+/)
|
|
146
|
+
.filter(word => word.length > 2 && !stopWords.has(word))
|
|
147
|
+
|
|
148
|
+
// Also extract potential compound terms
|
|
149
|
+
const compounds: string[] = []
|
|
150
|
+
for (let i = 0; i < words.length - 1; i++) {
|
|
151
|
+
if (words[i]!.length > 3 && words[i + 1]!.length > 3) {
|
|
152
|
+
compounds.push(`${words[i]} ${words[i + 1]}`)
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return [...new Set([...words, ...compounds])]
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Proactively recall relevant memories
|
|
161
|
+
*/
|
|
162
|
+
async recall(query: string, project?: string): Promise<RecallResult | null> {
|
|
163
|
+
this.recallCount++
|
|
164
|
+
|
|
165
|
+
if (!this.shouldRecall(query)) {
|
|
166
|
+
return null
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const triggeredBy = this.getMatchedTriggers(query)
|
|
170
|
+
this.logger.info({ query: query.slice(0, 50), triggeredBy }, 'Proactive recall triggered')
|
|
171
|
+
|
|
172
|
+
// Extract search terms
|
|
173
|
+
const searchTerms = this.extractSearchTerms(query)
|
|
174
|
+
|
|
175
|
+
if (searchTerms.length === 0) {
|
|
176
|
+
return null
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Search for each term and combine results
|
|
180
|
+
const allMemories: Map<string, MemorySearchResult> = new Map()
|
|
181
|
+
|
|
182
|
+
// Search with the full query first
|
|
183
|
+
try {
|
|
184
|
+
const fullResults = await this.memory.searchRaw(query, {
|
|
185
|
+
project,
|
|
186
|
+
limit: 5,
|
|
187
|
+
minSimilarity: 0.5
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
for (const memory of fullResults) {
|
|
191
|
+
allMemories.set(memory.memory.id, memory)
|
|
192
|
+
}
|
|
193
|
+
} catch (error) {
|
|
194
|
+
this.logger.warn({ error }, 'Full query search failed')
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Then search individual terms for broader coverage
|
|
198
|
+
for (const term of searchTerms.slice(0, 5)) {
|
|
199
|
+
try {
|
|
200
|
+
const memories = await this.memory.searchRaw(term, {
|
|
201
|
+
project,
|
|
202
|
+
limit: 3,
|
|
203
|
+
minSimilarity: 0.6
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
for (const memory of memories) {
|
|
207
|
+
// Only add if not already present or if higher similarity
|
|
208
|
+
const existing = allMemories.get(memory.memory.id)
|
|
209
|
+
if (!existing || memory.similarity > existing.similarity) {
|
|
210
|
+
allMemories.set(memory.memory.id, memory)
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
} catch (error) {
|
|
214
|
+
this.logger.warn({ error, term }, 'Term search failed')
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const memories = Array.from(allMemories.values())
|
|
219
|
+
.sort((a, b) => b.similarity - a.similarity)
|
|
220
|
+
.slice(0, 5)
|
|
221
|
+
|
|
222
|
+
// Calculate relevance score
|
|
223
|
+
const relevanceScore = this.calculateRelevance(query, memories)
|
|
224
|
+
|
|
225
|
+
// Update stats
|
|
226
|
+
if (memories.length > 0) {
|
|
227
|
+
this.successfulRecalls++
|
|
228
|
+
this.totalRelevance += relevanceScore
|
|
229
|
+
}
|
|
230
|
+
this.lastRecallTime = new Date()
|
|
231
|
+
|
|
232
|
+
this.logger.info(
|
|
233
|
+
{
|
|
234
|
+
query: query.slice(0, 50),
|
|
235
|
+
memoryCount: memories.length,
|
|
236
|
+
relevanceScore
|
|
237
|
+
},
|
|
238
|
+
'Proactive recall complete'
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
const result: RecallResult = {
|
|
242
|
+
query,
|
|
243
|
+
memories,
|
|
244
|
+
relevanceScore,
|
|
245
|
+
triggeredBy
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Add formatted context if we have results
|
|
249
|
+
if (memories.length > 0 && relevanceScore > 0.4) {
|
|
250
|
+
result.formattedContext = this.formatForContext(result)
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return result
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Calculate relevance score
|
|
258
|
+
*/
|
|
259
|
+
private calculateRelevance(_query: string, memories: MemorySearchResult[]): number {
|
|
260
|
+
if (memories.length === 0) return 0
|
|
261
|
+
|
|
262
|
+
// Average similarity score
|
|
263
|
+
const avgSimilarity = memories.reduce((sum, m) => sum + m.similarity, 0) / memories.length
|
|
264
|
+
|
|
265
|
+
// Recency bonus (newer memories are more relevant)
|
|
266
|
+
const now = Date.now()
|
|
267
|
+
let totalRecencyScore = 0
|
|
268
|
+
|
|
269
|
+
for (const memory of memories) {
|
|
270
|
+
const age = now - memory.memory.createdAt.getTime()
|
|
271
|
+
const daysSince = age / (1000 * 60 * 60 * 24)
|
|
272
|
+
const recencyScore = Math.max(0, 1 - (daysSince / 365)) // Decay over a year
|
|
273
|
+
totalRecencyScore += recencyScore
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const avgRecency = totalRecencyScore / memories.length
|
|
277
|
+
|
|
278
|
+
// Decision bonus (decisions are more relevant than raw memories)
|
|
279
|
+
const decisionCount = memories.filter(m => m.decision).length
|
|
280
|
+
const decisionBonus = (decisionCount / memories.length) * 0.1
|
|
281
|
+
|
|
282
|
+
// Combine scores
|
|
283
|
+
return (avgSimilarity * 0.6) + (avgRecency * 0.3) + decisionBonus
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Format memories for context
|
|
288
|
+
*/
|
|
289
|
+
formatForContext(result: RecallResult): string {
|
|
290
|
+
if (result.memories.length === 0) {
|
|
291
|
+
return ''
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
const parts: string[] = []
|
|
295
|
+
parts.push('\n---')
|
|
296
|
+
parts.push('**Relevant Past Context** (proactively recalled):')
|
|
297
|
+
parts.push('')
|
|
298
|
+
|
|
299
|
+
for (const [i, memory] of result.memories.entries()) {
|
|
300
|
+
const title = memory.decision?.decision || memory.memory.content.slice(0, 100)
|
|
301
|
+
parts.push(`${i + 1}. **${title}**`)
|
|
302
|
+
|
|
303
|
+
if (memory.decision?.reasoning) {
|
|
304
|
+
parts.push(` *Reasoning:* ${memory.decision.reasoning}`)
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
parts.push(` *Project:* ${memory.memory.project}`)
|
|
308
|
+
parts.push(` *Relevance:* ${Math.round(memory.similarity * 100)}%`)
|
|
309
|
+
|
|
310
|
+
const date = memory.memory.createdAt.toLocaleDateString()
|
|
311
|
+
parts.push(` *Recorded:* ${date}`)
|
|
312
|
+
parts.push('')
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
parts.push('---')
|
|
316
|
+
return parts.join('\n')
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Get recall statistics
|
|
321
|
+
*/
|
|
322
|
+
getStats(): RecallStats {
|
|
323
|
+
return {
|
|
324
|
+
totalRecalls: this.recallCount,
|
|
325
|
+
successfulRecalls: this.successfulRecalls,
|
|
326
|
+
averageRelevance: this.successfulRecalls > 0
|
|
327
|
+
? this.totalRelevance / this.successfulRecalls
|
|
328
|
+
: 0,
|
|
329
|
+
lastRecallTime: this.lastRecallTime
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Reset statistics
|
|
335
|
+
*/
|
|
336
|
+
resetStats(): void {
|
|
337
|
+
this.recallCount = 0
|
|
338
|
+
this.successfulRecalls = 0
|
|
339
|
+
this.totalRelevance = 0
|
|
340
|
+
this.lastRecallTime = undefined
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Add custom recall triggers
|
|
345
|
+
*/
|
|
346
|
+
addTriggers(triggers: string[]): void {
|
|
347
|
+
for (const trigger of triggers) {
|
|
348
|
+
if (!this.RECALL_TRIGGERS.includes(trigger.toLowerCase())) {
|
|
349
|
+
(this.RECALL_TRIGGERS as string[]).push(trigger.toLowerCase())
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Quick recall for a specific topic
|
|
356
|
+
*/
|
|
357
|
+
async recallTopic(
|
|
358
|
+
topic: string,
|
|
359
|
+
project?: string,
|
|
360
|
+
limit: number = 3
|
|
361
|
+
): Promise<MemorySearchResult[]> {
|
|
362
|
+
try {
|
|
363
|
+
return await this.memory.searchRaw(topic, {
|
|
364
|
+
project,
|
|
365
|
+
limit,
|
|
366
|
+
minSimilarity: 0.5
|
|
367
|
+
})
|
|
368
|
+
} catch (error) {
|
|
369
|
+
this.logger.error({ error, topic }, 'Topic recall failed')
|
|
370
|
+
return []
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
}
|