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
package/src/routing/router.ts
CHANGED
|
@@ -1,1314 +1,1322 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Brain Router
|
|
3
|
-
* Phase 16 + Phase 19: Core orchestrator for the unified brain() tool
|
|
4
|
-
*
|
|
5
|
-
* Routes classified intents to internal service calls and
|
|
6
|
-
* returns unified BrainResponse objects.
|
|
7
|
-
*
|
|
8
|
-
* Phase 19: Uses SearchEngine for all searches (hybrid, cached, temporal).
|
|
9
|
-
* Wires Timeline, Evolution, Trends, ChainRetrieval, Episodes,
|
|
10
|
-
* Recommender, CrossProject patterns, and KnowledgeGraph enrichment.
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
import type { Logger } from 'pino'
|
|
14
|
-
import { IntentClassifier, type ClassificationResult } from './intent-classifier'
|
|
15
|
-
import { BrainEntityExtractor, type BrainExtractedEntities } from './entity-extractor'
|
|
16
|
-
import { ResponseFilter, type BrainResponse, type TierResults } from './response-filter'
|
|
17
|
-
import { SearchEngine } from './search-engine'
|
|
18
|
-
import type { NormalizedResult } from './types'
|
|
19
|
-
import {
|
|
20
|
-
getMemoryService,
|
|
21
|
-
getVaultService,
|
|
22
|
-
getContextService,
|
|
23
|
-
getPhase12Service,
|
|
24
|
-
getKnowledgeGraphService,
|
|
25
|
-
getEpisodeService,
|
|
26
|
-
getSessionTracker,
|
|
27
|
-
isServicesInitialized
|
|
28
|
-
} from '@/server/services'
|
|
29
|
-
import { timed } from '@/utils/timing'
|
|
30
|
-
|
|
31
|
-
/** Default project when none can be detected */
|
|
32
|
-
const DEFAULT_PROJECT = 'general'
|
|
33
|
-
|
|
34
|
-
/** Phase 19 D4: Minimum similarity for destructive operations */
|
|
35
|
-
const DELETE_MIN_SIMILARITY = 0.5
|
|
36
|
-
const UPDATE_MIN_SIMILARITY = 0.5
|
|
37
|
-
|
|
38
|
-
export interface BrainInput {
|
|
39
|
-
message: string
|
|
40
|
-
project?: string
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export class BrainRouter {
|
|
44
|
-
private classifier: IntentClassifier
|
|
45
|
-
private entityExtractor: BrainEntityExtractor
|
|
46
|
-
private responseFilter: ResponseFilter
|
|
47
|
-
private searchEngine: SearchEngine
|
|
48
|
-
private logger: Logger
|
|
49
|
-
|
|
50
|
-
/** Track the most recently stored decision ID for update/delete operations */
|
|
51
|
-
private lastStoredId: string | null = null
|
|
52
|
-
private lastStoredProject: string | null = null
|
|
53
|
-
|
|
54
|
-
constructor(logger: Logger) {
|
|
55
|
-
this.classifier = new IntentClassifier()
|
|
56
|
-
this.entityExtractor = new BrainEntityExtractor()
|
|
57
|
-
this.responseFilter = new ResponseFilter()
|
|
58
|
-
this.searchEngine = new SearchEngine(logger)
|
|
59
|
-
this.logger = logger.child({ component: 'brain-router' })
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
async route(input: BrainInput): Promise<BrainResponse> {
|
|
63
|
-
const { message, project: inputProject } = input
|
|
64
|
-
|
|
65
|
-
// Classify intent
|
|
66
|
-
const classification = this.classifier.classify(message)
|
|
67
|
-
this.logger.debug({ intent: classification.primary, confidence: classification.confidence }, 'Intent classified')
|
|
68
|
-
|
|
69
|
-
// Extract entities
|
|
70
|
-
const entities = await this.entityExtractor.extract(message, inputProject)
|
|
71
|
-
const project = entities.project || inputProject
|
|
72
|
-
this.logger.debug({ project, technologies: entities.technologies }, 'Entities extracted')
|
|
73
|
-
|
|
74
|
-
// Route to handler
|
|
75
|
-
try {
|
|
76
|
-
switch (classification.primary) {
|
|
77
|
-
case 'no_action':
|
|
78
|
-
return this.handleNoAction(message)
|
|
79
|
-
|
|
80
|
-
case 'session_start':
|
|
81
|
-
return this.handleSessionStart(message, project, entities)
|
|
82
|
-
|
|
83
|
-
case 'context_needed':
|
|
84
|
-
return this.handleContextNeeded(message, project, entities, classification)
|
|
85
|
-
|
|
86
|
-
case 'decision_made':
|
|
87
|
-
return this.handleDecisionMade(message, project, entities)
|
|
88
|
-
|
|
89
|
-
case 'store_this':
|
|
90
|
-
return this.handleStoreThis(message, project, entities)
|
|
91
|
-
|
|
92
|
-
case 'pattern_found':
|
|
93
|
-
return this.handlePatternFound(message, project, entities)
|
|
94
|
-
|
|
95
|
-
case 'mistake_learned':
|
|
96
|
-
return this.handleMistakeLearned(message, project, entities)
|
|
97
|
-
|
|
98
|
-
case 'progress_update':
|
|
99
|
-
return this.handleProgressUpdate(message, project, entities)
|
|
100
|
-
|
|
101
|
-
case 'question':
|
|
102
|
-
return this.handleQuestion(message, project, entities, classification)
|
|
103
|
-
|
|
104
|
-
case 'comparison':
|
|
105
|
-
return this.handleComparison(message, project, entities)
|
|
106
|
-
|
|
107
|
-
case 'exploration':
|
|
108
|
-
return this.handleExploration(message, project, entities)
|
|
109
|
-
|
|
110
|
-
case 'list_all':
|
|
111
|
-
return this.handleListAll(message, project, entities)
|
|
112
|
-
|
|
113
|
-
case 'update_memory':
|
|
114
|
-
return this.handleUpdateMemory(message, project, entities)
|
|
115
|
-
|
|
116
|
-
case 'delete_memory':
|
|
117
|
-
return this.handleDeleteMemory(message, project, entities)
|
|
118
|
-
|
|
119
|
-
default:
|
|
120
|
-
return this.handleContextNeeded(message, project, entities, classification)
|
|
121
|
-
}
|
|
122
|
-
} catch (error) {
|
|
123
|
-
this.logger.error({ error, intent: classification.primary }, 'Router handler error')
|
|
124
|
-
return {
|
|
125
|
-
action: 'none',
|
|
126
|
-
summary: `Error processing request`,
|
|
127
|
-
content: `Failed to process: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
128
|
-
relevantItems: 0
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// ===== Intent Handlers =====
|
|
134
|
-
|
|
135
|
-
private handleNoAction(_message: string): BrainResponse {
|
|
136
|
-
return {
|
|
137
|
-
action: 'none',
|
|
138
|
-
summary: 'No action needed',
|
|
139
|
-
content: '',
|
|
140
|
-
relevantItems: 0
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
private async handleSessionStart(
|
|
145
|
-
message: string,
|
|
146
|
-
project: string | undefined,
|
|
147
|
-
entities: BrainExtractedEntities
|
|
148
|
-
): Promise<BrainResponse> {
|
|
149
|
-
if (!project) {
|
|
150
|
-
return {
|
|
151
|
-
action: 'none',
|
|
152
|
-
summary: 'No project detected',
|
|
153
|
-
content: 'Could not determine project. Please specify which project you are working on.',
|
|
154
|
-
relevantItems: 0
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
if (!isServicesInitialized()) {
|
|
159
|
-
return this.servicesNotReady()
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
const contextService = getContextService()
|
|
163
|
-
const phase12 = getPhase12Service()
|
|
164
|
-
|
|
165
|
-
// Get project context
|
|
166
|
-
const context = await contextService.getContext(project, {
|
|
167
|
-
includeMemories: false,
|
|
168
|
-
includeProgress: true,
|
|
169
|
-
includeStandards: true,
|
|
170
|
-
maxTokens: 6000,
|
|
171
|
-
relevanceThreshold: 0.5
|
|
172
|
-
})
|
|
173
|
-
|
|
174
|
-
const formattedContext = contextService.formatter.format(context)
|
|
175
|
-
|
|
176
|
-
// Process with Phase 12 for proactive recall
|
|
177
|
-
const phase12Result = await phase12.processMessage(
|
|
178
|
-
entities.topic || message,
|
|
179
|
-
project
|
|
180
|
-
)
|
|
181
|
-
|
|
182
|
-
// Build response
|
|
183
|
-
const parts: string[] = [formattedContext]
|
|
184
|
-
|
|
185
|
-
if (phase12Result.recalledMemories?.memories.length) {
|
|
186
|
-
parts.push('\n---\n## Relevant Past Decisions\n')
|
|
187
|
-
for (const mem of phase12Result.recalledMemories.memories) {
|
|
188
|
-
const similarity = Math.round(mem.similarity * 100)
|
|
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
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
const
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
//
|
|
347
|
-
this.
|
|
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
|
-
const
|
|
379
|
-
|
|
380
|
-
const
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
this.
|
|
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
|
-
const
|
|
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
|
-
const
|
|
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
|
-
const
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
const
|
|
564
|
-
const
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
const
|
|
641
|
-
|
|
642
|
-
if (
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
return
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
const
|
|
730
|
-
|
|
731
|
-
if (
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
return {
|
|
761
|
-
action: '
|
|
762
|
-
summary:
|
|
763
|
-
content: `
|
|
764
|
-
relevantItems: 0
|
|
765
|
-
}
|
|
766
|
-
}
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
'what
|
|
1245
|
-
'
|
|
1246
|
-
'
|
|
1247
|
-
'
|
|
1248
|
-
'
|
|
1249
|
-
'
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
}
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
export function
|
|
1313
|
-
routerInstance
|
|
1314
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Brain Router
|
|
3
|
+
* Phase 16 + Phase 19: Core orchestrator for the unified brain() tool
|
|
4
|
+
*
|
|
5
|
+
* Routes classified intents to internal service calls and
|
|
6
|
+
* returns unified BrainResponse objects.
|
|
7
|
+
*
|
|
8
|
+
* Phase 19: Uses SearchEngine for all searches (hybrid, cached, temporal).
|
|
9
|
+
* Wires Timeline, Evolution, Trends, ChainRetrieval, Episodes,
|
|
10
|
+
* Recommender, CrossProject patterns, and KnowledgeGraph enrichment.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import type { Logger } from 'pino'
|
|
14
|
+
import { IntentClassifier, type ClassificationResult } from './intent-classifier'
|
|
15
|
+
import { BrainEntityExtractor, type BrainExtractedEntities } from './entity-extractor'
|
|
16
|
+
import { ResponseFilter, type BrainResponse, type TierResults } from './response-filter'
|
|
17
|
+
import { SearchEngine } from './search-engine'
|
|
18
|
+
import type { NormalizedResult } from './types'
|
|
19
|
+
import {
|
|
20
|
+
getMemoryService,
|
|
21
|
+
getVaultService,
|
|
22
|
+
getContextService,
|
|
23
|
+
getPhase12Service,
|
|
24
|
+
getKnowledgeGraphService,
|
|
25
|
+
getEpisodeService,
|
|
26
|
+
getSessionTracker,
|
|
27
|
+
isServicesInitialized
|
|
28
|
+
} from '@/server/services'
|
|
29
|
+
import { timed } from '@/utils/timing'
|
|
30
|
+
|
|
31
|
+
/** Default project when none can be detected */
|
|
32
|
+
const DEFAULT_PROJECT = 'general'
|
|
33
|
+
|
|
34
|
+
/** Phase 19 D4: Minimum similarity for destructive operations */
|
|
35
|
+
const DELETE_MIN_SIMILARITY = 0.5
|
|
36
|
+
const UPDATE_MIN_SIMILARITY = 0.5
|
|
37
|
+
|
|
38
|
+
export interface BrainInput {
|
|
39
|
+
message: string
|
|
40
|
+
project?: string
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export class BrainRouter {
|
|
44
|
+
private classifier: IntentClassifier
|
|
45
|
+
private entityExtractor: BrainEntityExtractor
|
|
46
|
+
private responseFilter: ResponseFilter
|
|
47
|
+
private searchEngine: SearchEngine
|
|
48
|
+
private logger: Logger
|
|
49
|
+
|
|
50
|
+
/** Track the most recently stored decision ID for update/delete operations */
|
|
51
|
+
private lastStoredId: string | null = null
|
|
52
|
+
private lastStoredProject: string | null = null
|
|
53
|
+
|
|
54
|
+
constructor(logger: Logger) {
|
|
55
|
+
this.classifier = new IntentClassifier()
|
|
56
|
+
this.entityExtractor = new BrainEntityExtractor()
|
|
57
|
+
this.responseFilter = new ResponseFilter()
|
|
58
|
+
this.searchEngine = new SearchEngine(logger)
|
|
59
|
+
this.logger = logger.child({ component: 'brain-router' })
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async route(input: BrainInput): Promise<BrainResponse> {
|
|
63
|
+
const { message, project: inputProject } = input
|
|
64
|
+
|
|
65
|
+
// Classify intent
|
|
66
|
+
const classification = this.classifier.classify(message)
|
|
67
|
+
this.logger.debug({ intent: classification.primary, confidence: classification.confidence }, 'Intent classified')
|
|
68
|
+
|
|
69
|
+
// Extract entities
|
|
70
|
+
const entities = await this.entityExtractor.extract(message, inputProject)
|
|
71
|
+
const project = entities.project || inputProject
|
|
72
|
+
this.logger.debug({ project, technologies: entities.technologies }, 'Entities extracted')
|
|
73
|
+
|
|
74
|
+
// Route to handler
|
|
75
|
+
try {
|
|
76
|
+
switch (classification.primary) {
|
|
77
|
+
case 'no_action':
|
|
78
|
+
return this.handleNoAction(message)
|
|
79
|
+
|
|
80
|
+
case 'session_start':
|
|
81
|
+
return this.handleSessionStart(message, project, entities)
|
|
82
|
+
|
|
83
|
+
case 'context_needed':
|
|
84
|
+
return this.handleContextNeeded(message, project, entities, classification)
|
|
85
|
+
|
|
86
|
+
case 'decision_made':
|
|
87
|
+
return this.handleDecisionMade(message, project, entities)
|
|
88
|
+
|
|
89
|
+
case 'store_this':
|
|
90
|
+
return this.handleStoreThis(message, project, entities)
|
|
91
|
+
|
|
92
|
+
case 'pattern_found':
|
|
93
|
+
return this.handlePatternFound(message, project, entities)
|
|
94
|
+
|
|
95
|
+
case 'mistake_learned':
|
|
96
|
+
return this.handleMistakeLearned(message, project, entities)
|
|
97
|
+
|
|
98
|
+
case 'progress_update':
|
|
99
|
+
return this.handleProgressUpdate(message, project, entities)
|
|
100
|
+
|
|
101
|
+
case 'question':
|
|
102
|
+
return this.handleQuestion(message, project, entities, classification)
|
|
103
|
+
|
|
104
|
+
case 'comparison':
|
|
105
|
+
return this.handleComparison(message, project, entities)
|
|
106
|
+
|
|
107
|
+
case 'exploration':
|
|
108
|
+
return this.handleExploration(message, project, entities)
|
|
109
|
+
|
|
110
|
+
case 'list_all':
|
|
111
|
+
return this.handleListAll(message, project, entities)
|
|
112
|
+
|
|
113
|
+
case 'update_memory':
|
|
114
|
+
return this.handleUpdateMemory(message, project, entities)
|
|
115
|
+
|
|
116
|
+
case 'delete_memory':
|
|
117
|
+
return this.handleDeleteMemory(message, project, entities)
|
|
118
|
+
|
|
119
|
+
default:
|
|
120
|
+
return this.handleContextNeeded(message, project, entities, classification)
|
|
121
|
+
}
|
|
122
|
+
} catch (error) {
|
|
123
|
+
this.logger.error({ error, intent: classification.primary }, 'Router handler error')
|
|
124
|
+
return {
|
|
125
|
+
action: 'none',
|
|
126
|
+
summary: `Error processing request`,
|
|
127
|
+
content: `Failed to process: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
128
|
+
relevantItems: 0
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// ===== Intent Handlers =====
|
|
134
|
+
|
|
135
|
+
private handleNoAction(_message: string): BrainResponse {
|
|
136
|
+
return {
|
|
137
|
+
action: 'none',
|
|
138
|
+
summary: 'No action needed',
|
|
139
|
+
content: '',
|
|
140
|
+
relevantItems: 0
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
private async handleSessionStart(
|
|
145
|
+
message: string,
|
|
146
|
+
project: string | undefined,
|
|
147
|
+
entities: BrainExtractedEntities
|
|
148
|
+
): Promise<BrainResponse> {
|
|
149
|
+
if (!project) {
|
|
150
|
+
return {
|
|
151
|
+
action: 'none',
|
|
152
|
+
summary: 'No project detected',
|
|
153
|
+
content: 'Could not determine project. Please specify which project you are working on.',
|
|
154
|
+
relevantItems: 0
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (!isServicesInitialized()) {
|
|
159
|
+
return this.servicesNotReady()
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const contextService = getContextService()
|
|
163
|
+
const phase12 = getPhase12Service()
|
|
164
|
+
|
|
165
|
+
// Get project context
|
|
166
|
+
const context = await contextService.getContext(project, {
|
|
167
|
+
includeMemories: false,
|
|
168
|
+
includeProgress: true,
|
|
169
|
+
includeStandards: true,
|
|
170
|
+
maxTokens: 6000,
|
|
171
|
+
relevanceThreshold: 0.5
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
const formattedContext = contextService.formatter.format(context)
|
|
175
|
+
|
|
176
|
+
// Process with Phase 12 for proactive recall
|
|
177
|
+
const phase12Result = await phase12.processMessage(
|
|
178
|
+
entities.topic || message,
|
|
179
|
+
project
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
// Build response
|
|
183
|
+
const parts: string[] = [formattedContext]
|
|
184
|
+
|
|
185
|
+
if (phase12Result.recalledMemories?.memories.length) {
|
|
186
|
+
parts.push('\n---\n## Relevant Past Decisions\n')
|
|
187
|
+
for (const mem of phase12Result.recalledMemories.memories) {
|
|
188
|
+
const similarity = Math.round((mem.similarity || 0) * 100)
|
|
189
|
+
const decisionText = mem.decision?.decision || mem.memory?.content || ''
|
|
190
|
+
const safeText = typeof decisionText === 'string' ? decisionText : JSON.stringify(decisionText)
|
|
191
|
+
parts.push(`**[${similarity}%]** ${safeText.slice(0, 200)}`)
|
|
192
|
+
if (mem.decision?.reasoning) {
|
|
193
|
+
const reasoning = typeof mem.decision.reasoning === 'string' ? mem.decision.reasoning : JSON.stringify(mem.decision.reasoning)
|
|
194
|
+
parts.push(` _${reasoning}_`)
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// If Phase 12 found nothing, do a direct search fallback via SearchEngine
|
|
200
|
+
if (!phase12Result.recalledMemories?.memories.length) {
|
|
201
|
+
try {
|
|
202
|
+
const directResults = await this.searchEngine.enhancedSearch(entities.topic || message, {
|
|
203
|
+
project,
|
|
204
|
+
limit: 5,
|
|
205
|
+
minSimilarity: 0.3
|
|
206
|
+
})
|
|
207
|
+
if (directResults.length > 0) {
|
|
208
|
+
parts.push('\n---\n## Related Memories\n')
|
|
209
|
+
for (const r of directResults) {
|
|
210
|
+
const similarity = Math.round((r.score || 0) * 100)
|
|
211
|
+
const safeContent = typeof r.content === 'string' ? r.content : JSON.stringify(r.content ?? '')
|
|
212
|
+
parts.push(`**[${similarity}%]** ${safeContent.slice(0, 200)}`)
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
} catch {
|
|
216
|
+
// Direct search failed, continue without
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// C8: Register with episode manager
|
|
221
|
+
this.registerEpisodeMessage(message, project, 'session_start')
|
|
222
|
+
|
|
223
|
+
// C9: Append proactive recommendations
|
|
224
|
+
try {
|
|
225
|
+
const recommendations = await this.searchEngine.getRecommendations(
|
|
226
|
+
entities.topic || message,
|
|
227
|
+
{ project, limit: 3 }
|
|
228
|
+
)
|
|
229
|
+
if (recommendations?.recommendations?.length) {
|
|
230
|
+
parts.push('\n---\n## Proactive Recommendations\n')
|
|
231
|
+
for (const rec of recommendations.recommendations) {
|
|
232
|
+
parts.push(`- **${rec.type}**: ${rec.content?.slice(0, 150) || ''}`)
|
|
233
|
+
if (rec.reasoning) parts.push(` _${rec.reasoning}_`)
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
} catch {
|
|
237
|
+
// Recommendations not available
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const totalRecalled = phase12Result.recalledMemories?.memories.length || 0
|
|
241
|
+
return {
|
|
242
|
+
action: 'retrieved',
|
|
243
|
+
summary: `Session context for ${project}${totalRecalled ? ` (${totalRecalled} memories)` : ''}`,
|
|
244
|
+
content: parts.join('\n'),
|
|
245
|
+
relevantItems: totalRecalled
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
private async handleContextNeeded(
|
|
250
|
+
message: string,
|
|
251
|
+
project: string | undefined,
|
|
252
|
+
entities: BrainExtractedEntities,
|
|
253
|
+
classification?: ClassificationResult
|
|
254
|
+
): Promise<BrainResponse> {
|
|
255
|
+
if (!isServicesInitialized()) {
|
|
256
|
+
return this.servicesNotReady()
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const effectiveProject = project || DEFAULT_PROJECT
|
|
260
|
+
const query = entities.topic || message
|
|
261
|
+
const tiers: TierResults[] = []
|
|
262
|
+
const hasTemporal = classification?.secondary.includes('exploration') ||
|
|
263
|
+
this.classifier.hasTemporalSignal(message.toLowerCase())
|
|
264
|
+
|
|
265
|
+
// Use temporal search if temporal signals detected
|
|
266
|
+
if (hasTemporal) {
|
|
267
|
+
const { results } = await this.searchEngine.temporalSearch(query, { project: effectiveProject, limit: 5 })
|
|
268
|
+
if (results.length > 0) {
|
|
269
|
+
tiers.push({
|
|
270
|
+
label: 'Memories',
|
|
271
|
+
results: results.map(r => ({
|
|
272
|
+
content: r.content,
|
|
273
|
+
score: r.score,
|
|
274
|
+
source: r.source === 'decision' ? 'Past Decision' : r.source,
|
|
275
|
+
metadata: r.metadata as Record<string, unknown>
|
|
276
|
+
}))
|
|
277
|
+
})
|
|
278
|
+
}
|
|
279
|
+
} else {
|
|
280
|
+
// Standard enhanced search
|
|
281
|
+
const searchResults = await this.searchEngine.enhancedSearch(query, {
|
|
282
|
+
project: effectiveProject,
|
|
283
|
+
limit: 5,
|
|
284
|
+
minSimilarity: 0.3
|
|
285
|
+
})
|
|
286
|
+
if (searchResults.length > 0) {
|
|
287
|
+
tiers.push({
|
|
288
|
+
label: 'Memories',
|
|
289
|
+
results: searchResults.map(r => ({
|
|
290
|
+
content: r.content,
|
|
291
|
+
score: r.score,
|
|
292
|
+
source: r.source === 'decision' ? 'Past Decision' : r.source,
|
|
293
|
+
metadata: r.metadata as Record<string, unknown>
|
|
294
|
+
}))
|
|
295
|
+
})
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Also search patterns
|
|
300
|
+
const patternResults = await this.searchEngine.searchPatterns(query, { project: effectiveProject, limit: 3 })
|
|
301
|
+
if (patternResults.length > 0) {
|
|
302
|
+
tiers.push({
|
|
303
|
+
label: 'Patterns',
|
|
304
|
+
results: patternResults.map(p => ({
|
|
305
|
+
content: p.content,
|
|
306
|
+
score: p.score,
|
|
307
|
+
source: `Pattern`,
|
|
308
|
+
metadata: p.metadata as Record<string, unknown>
|
|
309
|
+
}))
|
|
310
|
+
})
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
return this.responseFilter.synthesize(tiers, message, effectiveProject)
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Handle explicit "store this" requests — always uses the FULL message as the decision text.
|
|
318
|
+
*/
|
|
319
|
+
private async handleStoreThis(
|
|
320
|
+
message: string,
|
|
321
|
+
project: string | undefined,
|
|
322
|
+
entities: BrainExtractedEntities
|
|
323
|
+
): Promise<BrainResponse> {
|
|
324
|
+
const effectiveProject = project || DEFAULT_PROJECT
|
|
325
|
+
|
|
326
|
+
if (!isServicesInitialized()) {
|
|
327
|
+
return this.servicesNotReady()
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
const memory = getMemoryService()
|
|
331
|
+
const decision = message
|
|
332
|
+
const reasoning = entities.reasoning || ''
|
|
333
|
+
const context = entities.topic || message.slice(0, 200)
|
|
334
|
+
|
|
335
|
+
const decisionId = await timed('brain:store', () => memory.rememberDecision(
|
|
336
|
+
effectiveProject,
|
|
337
|
+
context,
|
|
338
|
+
decision,
|
|
339
|
+
reasoning,
|
|
340
|
+
{
|
|
341
|
+
alternatives: entities.alternatives,
|
|
342
|
+
tags: entities.technologies.length > 0 ? entities.technologies : ['user-stored']
|
|
343
|
+
}
|
|
344
|
+
))
|
|
345
|
+
|
|
346
|
+
// Track for potential update/delete
|
|
347
|
+
this.lastStoredId = decisionId
|
|
348
|
+
this.lastStoredProject = effectiveProject
|
|
349
|
+
|
|
350
|
+
// Invalidate cache for this project
|
|
351
|
+
this.searchEngine.invalidateCache(effectiveProject)
|
|
352
|
+
|
|
353
|
+
// Background: vault write + episode linking (non-critical)
|
|
354
|
+
setImmediate(() => {
|
|
355
|
+
this.writeToVault(effectiveProject, decision, reasoning, context, entities.alternatives)
|
|
356
|
+
this.linkToActiveEpisode(effectiveProject, decisionId, 'decision')
|
|
357
|
+
})
|
|
358
|
+
|
|
359
|
+
return {
|
|
360
|
+
action: 'stored',
|
|
361
|
+
summary: `Stored: ${message.slice(0, 60)}`,
|
|
362
|
+
content: `Memory stored (ID: ${decisionId})\n\n**Project:** ${effectiveProject}\n**Content:** ${decision}${reasoning ? `\n**Reasoning:** ${reasoning}` : ''}`,
|
|
363
|
+
relevantItems: 1
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
private async handleDecisionMade(
|
|
368
|
+
message: string,
|
|
369
|
+
project: string | undefined,
|
|
370
|
+
entities: BrainExtractedEntities
|
|
371
|
+
): Promise<BrainResponse> {
|
|
372
|
+
const effectiveProject = project || DEFAULT_PROJECT
|
|
373
|
+
|
|
374
|
+
if (!isServicesInitialized()) {
|
|
375
|
+
return this.servicesNotReady()
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
const memory = getMemoryService()
|
|
379
|
+
const decision = message
|
|
380
|
+
const reasoning = entities.reasoning || ''
|
|
381
|
+
const context = entities.topic || message.slice(0, 200)
|
|
382
|
+
const alternatives = entities.alternatives
|
|
383
|
+
|
|
384
|
+
const decisionId = await timed('brain:store', () => memory.rememberDecision(
|
|
385
|
+
effectiveProject,
|
|
386
|
+
context,
|
|
387
|
+
decision,
|
|
388
|
+
reasoning,
|
|
389
|
+
{
|
|
390
|
+
alternatives,
|
|
391
|
+
tags: entities.technologies.length > 0 ? entities.technologies : ['auto-detected']
|
|
392
|
+
}
|
|
393
|
+
))
|
|
394
|
+
|
|
395
|
+
this.lastStoredId = decisionId
|
|
396
|
+
this.lastStoredProject = effectiveProject
|
|
397
|
+
|
|
398
|
+
// Invalidate cache
|
|
399
|
+
this.searchEngine.invalidateCache(effectiveProject)
|
|
400
|
+
|
|
401
|
+
// Background: vault write + episode linking (non-critical)
|
|
402
|
+
setImmediate(() => {
|
|
403
|
+
this.writeToVault(effectiveProject, decision, reasoning, context, alternatives)
|
|
404
|
+
this.linkToActiveEpisode(effectiveProject, decisionId, 'decision')
|
|
405
|
+
})
|
|
406
|
+
|
|
407
|
+
return {
|
|
408
|
+
action: 'stored',
|
|
409
|
+
summary: `Stored decision: ${message.slice(0, 60)}`,
|
|
410
|
+
content: `Decision stored (ID: ${decisionId})\n\n**Project:** ${effectiveProject}\n**Decision:** ${decision}\n**Reasoning:** ${reasoning}`,
|
|
411
|
+
relevantItems: 1
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
private async handlePatternFound(
|
|
416
|
+
message: string,
|
|
417
|
+
project: string | undefined,
|
|
418
|
+
entities: BrainExtractedEntities
|
|
419
|
+
): Promise<BrainResponse> {
|
|
420
|
+
const effectiveProject = project || DEFAULT_PROJECT
|
|
421
|
+
|
|
422
|
+
if (!isServicesInitialized()) {
|
|
423
|
+
return this.servicesNotReady()
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
const memory = getMemoryService()
|
|
427
|
+
const patternType = entities.patternType || 'solution'
|
|
428
|
+
const description = message
|
|
429
|
+
|
|
430
|
+
const patternId = await memory.storePattern({
|
|
431
|
+
project: effectiveProject,
|
|
432
|
+
pattern_type: patternType,
|
|
433
|
+
description,
|
|
434
|
+
confidence: 0.8,
|
|
435
|
+
context: message.slice(0, 300)
|
|
436
|
+
})
|
|
437
|
+
|
|
438
|
+
// Invalidate cache
|
|
439
|
+
this.searchEngine.invalidateCache(effectiveProject)
|
|
440
|
+
|
|
441
|
+
// Link to active episode
|
|
442
|
+
this.linkToActiveEpisode(effectiveProject, patternId, 'pattern')
|
|
443
|
+
|
|
444
|
+
return {
|
|
445
|
+
action: 'stored',
|
|
446
|
+
summary: `Stored ${patternType}: ${description.slice(0, 60)}`,
|
|
447
|
+
content: `Pattern stored (ID: ${patternId})\n\n**Type:** ${patternType}\n**Project:** ${effectiveProject}\n**Description:** ${description}`,
|
|
448
|
+
relevantItems: 1
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
private async handleMistakeLearned(
|
|
453
|
+
message: string,
|
|
454
|
+
project: string | undefined,
|
|
455
|
+
entities: BrainExtractedEntities
|
|
456
|
+
): Promise<BrainResponse> {
|
|
457
|
+
const effectiveProject = project || DEFAULT_PROJECT
|
|
458
|
+
|
|
459
|
+
if (!isServicesInitialized()) {
|
|
460
|
+
return this.servicesNotReady()
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
const memory = getMemoryService()
|
|
464
|
+
const original = entities.original || message
|
|
465
|
+
const correction = entities.correction || ''
|
|
466
|
+
const reasoning = entities.reasoning || 'Lesson learned from experience'
|
|
467
|
+
|
|
468
|
+
const correctionId = await memory.storeCorrection({
|
|
469
|
+
project: effectiveProject,
|
|
470
|
+
original,
|
|
471
|
+
correction: correction || message,
|
|
472
|
+
reasoning,
|
|
473
|
+
context: entities.topic || '',
|
|
474
|
+
confidence: 0.9
|
|
475
|
+
})
|
|
476
|
+
|
|
477
|
+
// Invalidate cache
|
|
478
|
+
this.searchEngine.invalidateCache(effectiveProject)
|
|
479
|
+
|
|
480
|
+
// Link to active episode
|
|
481
|
+
this.linkToActiveEpisode(effectiveProject, correctionId, 'correction')
|
|
482
|
+
|
|
483
|
+
return {
|
|
484
|
+
action: 'stored',
|
|
485
|
+
summary: `Stored correction: ${original.slice(0, 60)}`,
|
|
486
|
+
content: `Correction stored (ID: ${correctionId})\n\n**Project:** ${effectiveProject}\n**Original:** ${original}\n**Correction:** ${correction || '(see original message)'}`,
|
|
487
|
+
relevantItems: 1
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
private async handleProgressUpdate(
|
|
492
|
+
message: string,
|
|
493
|
+
project: string | undefined,
|
|
494
|
+
entities: BrainExtractedEntities
|
|
495
|
+
): Promise<BrainResponse> {
|
|
496
|
+
const effectiveProject = project || DEFAULT_PROJECT
|
|
497
|
+
|
|
498
|
+
if (!isServicesInitialized()) {
|
|
499
|
+
return this.servicesNotReady()
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
const contextService = getContextService()
|
|
503
|
+
const memory = getMemoryService()
|
|
504
|
+
|
|
505
|
+
const completedTask = entities.completedTask || message
|
|
506
|
+
const nextSteps = entities.nextSteps || 'Continue development'
|
|
507
|
+
|
|
508
|
+
// Store in session context
|
|
509
|
+
try {
|
|
510
|
+
await contextService.progress.addCompletedTask(effectiveProject, {
|
|
511
|
+
id: this.generateTaskId(completedTask),
|
|
512
|
+
title: completedTask,
|
|
513
|
+
status: 'done',
|
|
514
|
+
completedAt: new Date()
|
|
515
|
+
})
|
|
516
|
+
} catch {
|
|
517
|
+
// Progress update failed
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
// Also store as a searchable memory
|
|
521
|
+
try {
|
|
522
|
+
await memory.rememberDecision(
|
|
523
|
+
effectiveProject,
|
|
524
|
+
`Progress update`,
|
|
525
|
+
`Progress: ${completedTask}${nextSteps !== 'Continue development' ? `. Next: ${nextSteps}` : ''}`,
|
|
526
|
+
'Progress tracking',
|
|
527
|
+
{ tags: ['progress', ...entities.technologies] }
|
|
528
|
+
)
|
|
529
|
+
|
|
530
|
+
// Invalidate cache
|
|
531
|
+
this.searchEngine.invalidateCache(effectiveProject)
|
|
532
|
+
} catch {
|
|
533
|
+
// Memory storage failed
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
// Register with episode
|
|
537
|
+
this.registerEpisodeMessage(message, effectiveProject, 'progress_update')
|
|
538
|
+
|
|
539
|
+
return {
|
|
540
|
+
action: 'stored',
|
|
541
|
+
summary: `Progress: ${completedTask.slice(0, 60)}`,
|
|
542
|
+
content: `Progress updated for ${effectiveProject}\n\n**Completed:** ${completedTask}\n**Next:** ${nextSteps}`,
|
|
543
|
+
relevantItems: 1
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
/**
|
|
548
|
+
* List all decisions for a project
|
|
549
|
+
*/
|
|
550
|
+
private async handleListAll(
|
|
551
|
+
message: string,
|
|
552
|
+
project: string | undefined,
|
|
553
|
+
entities: BrainExtractedEntities
|
|
554
|
+
): Promise<BrainResponse> {
|
|
555
|
+
if (!isServicesInitialized()) {
|
|
556
|
+
return this.servicesNotReady()
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
const memory = getMemoryService()
|
|
560
|
+
const effectiveProject = project
|
|
561
|
+
|
|
562
|
+
try {
|
|
563
|
+
const decisions = await memory.fetchAllDecisions(effectiveProject)
|
|
564
|
+
const patterns = await memory.fetchAllPatterns(effectiveProject)
|
|
565
|
+
const corrections = await memory.fetchAllCorrections(effectiveProject)
|
|
566
|
+
|
|
567
|
+
const parts: string[] = []
|
|
568
|
+
const projectLabel = effectiveProject || 'all projects'
|
|
569
|
+
|
|
570
|
+
if (decisions.length > 0) {
|
|
571
|
+
parts.push(`## Decisions (${decisions.length})`)
|
|
572
|
+
for (const d of decisions.slice(0, 20)) {
|
|
573
|
+
const rawDecision = d.decision || d.document || d.content || ''
|
|
574
|
+
const decision = typeof rawDecision === 'string' ? rawDecision : JSON.stringify(rawDecision)
|
|
575
|
+
const rawDate = d.created_at || d.date || ''
|
|
576
|
+
const date = rawDate ? ` (${String(rawDate).split('T')[0]})` : ''
|
|
577
|
+
parts.push(`- ${decision.slice(0, 150)}${date}`)
|
|
578
|
+
}
|
|
579
|
+
if (decisions.length > 20) {
|
|
580
|
+
parts.push(`_...and ${decisions.length - 20} more_`)
|
|
581
|
+
}
|
|
582
|
+
parts.push('')
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
if (patterns.length > 0) {
|
|
586
|
+
parts.push(`## Patterns (${patterns.length})`)
|
|
587
|
+
for (const p of patterns.slice(0, 10)) {
|
|
588
|
+
const rawDesc = p.description || p.document || p.content || ''
|
|
589
|
+
const desc = typeof rawDesc === 'string' ? rawDesc : JSON.stringify(rawDesc)
|
|
590
|
+
parts.push(`- **${p.pattern_type || 'solution'}**: ${desc.slice(0, 120)}`)
|
|
591
|
+
}
|
|
592
|
+
parts.push('')
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
if (corrections.length > 0) {
|
|
596
|
+
parts.push(`## Corrections (${corrections.length})`)
|
|
597
|
+
for (const c of corrections.slice(0, 10)) {
|
|
598
|
+
const rawOrig = c.original || c.document || c.content || ''
|
|
599
|
+
const orig = typeof rawOrig === 'string' ? rawOrig : JSON.stringify(rawOrig)
|
|
600
|
+
parts.push(`- ${orig.slice(0, 120)}`)
|
|
601
|
+
}
|
|
602
|
+
parts.push('')
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
const total = decisions.length + patterns.length + corrections.length
|
|
606
|
+
|
|
607
|
+
if (total === 0) {
|
|
608
|
+
return {
|
|
609
|
+
action: 'none',
|
|
610
|
+
summary: `No memories found for ${projectLabel}`,
|
|
611
|
+
content: `No decisions, patterns, or corrections stored yet for ${projectLabel}.`,
|
|
612
|
+
relevantItems: 0
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
return {
|
|
617
|
+
action: 'retrieved',
|
|
618
|
+
summary: `${total} memories for ${projectLabel}`,
|
|
619
|
+
content: parts.join('\n'),
|
|
620
|
+
relevantItems: total
|
|
621
|
+
}
|
|
622
|
+
} catch (error) {
|
|
623
|
+
return {
|
|
624
|
+
action: 'none',
|
|
625
|
+
summary: 'Error listing memories',
|
|
626
|
+
content: `Failed to list memories: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
627
|
+
relevantItems: 0
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
/**
|
|
633
|
+
* Phase 19 D4: Update with higher similarity threshold
|
|
634
|
+
*/
|
|
635
|
+
private async handleUpdateMemory(
|
|
636
|
+
message: string,
|
|
637
|
+
project: string | undefined,
|
|
638
|
+
entities: BrainExtractedEntities
|
|
639
|
+
): Promise<BrainResponse> {
|
|
640
|
+
const effectiveProject = project || this.lastStoredProject || DEFAULT_PROJECT
|
|
641
|
+
|
|
642
|
+
if (!isServicesInitialized()) {
|
|
643
|
+
return this.servicesNotReady()
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
const memory = getMemoryService()
|
|
647
|
+
const topic = entities.topic || message
|
|
648
|
+
const hasSpecificContent = this.hasDescriptiveContent(message)
|
|
649
|
+
|
|
650
|
+
if (hasSpecificContent) {
|
|
651
|
+
try {
|
|
652
|
+
const results = await memory.searchRaw(topic, {
|
|
653
|
+
project: effectiveProject,
|
|
654
|
+
limit: 1,
|
|
655
|
+
minSimilarity: UPDATE_MIN_SIMILARITY
|
|
656
|
+
})
|
|
657
|
+
|
|
658
|
+
const matchId = results[0]?.memory?.id || results[0]?.decision?.id || results[0]?.id
|
|
659
|
+
const matchSimilarity = results[0]?.similarity || 0
|
|
660
|
+
|
|
661
|
+
if (results.length > 0 && matchId && matchSimilarity >= UPDATE_MIN_SIMILARITY) {
|
|
662
|
+
const oldId = matchId
|
|
663
|
+
const newId = await memory.updateDecision(
|
|
664
|
+
oldId,
|
|
665
|
+
effectiveProject,
|
|
666
|
+
`Updated: ${topic.slice(0, 200)}`,
|
|
667
|
+
message,
|
|
668
|
+
entities.reasoning || 'Updated via brain tool',
|
|
669
|
+
{ tags: entities.technologies.length > 0 ? entities.technologies : ['updated'] }
|
|
670
|
+
)
|
|
671
|
+
|
|
672
|
+
this.lastStoredId = newId
|
|
673
|
+
this.lastStoredProject = effectiveProject
|
|
674
|
+
|
|
675
|
+
// Invalidate cache
|
|
676
|
+
this.searchEngine.invalidateCache(effectiveProject)
|
|
677
|
+
|
|
678
|
+
const oldContent = results[0].decision?.decision || results[0].memory?.content?.slice(0, 100) || results[0].content?.slice(0, 100) || ''
|
|
679
|
+
return {
|
|
680
|
+
action: 'stored',
|
|
681
|
+
summary: `Updated decision`,
|
|
682
|
+
content: `Replaced: "${oldContent.slice(0, 80)}"\n\nWith:\n**New content:** ${message}`,
|
|
683
|
+
relevantItems: 1
|
|
684
|
+
}
|
|
685
|
+
} else if (results.length > 0 && matchSimilarity < UPDATE_MIN_SIMILARITY) {
|
|
686
|
+
// D4: No confident match — store as new instead of updating wrong memory
|
|
687
|
+
return this.storeAsNew(memory, effectiveProject, message, topic, entities, 'No confident match to update (similarity too low)')
|
|
688
|
+
}
|
|
689
|
+
} catch {
|
|
690
|
+
// Search failed, fall through
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
// Generic update — use lastStoredId
|
|
695
|
+
if (this.lastStoredId) {
|
|
696
|
+
const newId = await memory.updateDecision(
|
|
697
|
+
this.lastStoredId,
|
|
698
|
+
effectiveProject,
|
|
699
|
+
`Updated: ${topic.slice(0, 200)}`,
|
|
700
|
+
message,
|
|
701
|
+
entities.reasoning || 'Updated via brain tool',
|
|
702
|
+
{ tags: entities.technologies.length > 0 ? entities.technologies : ['updated'] }
|
|
703
|
+
)
|
|
704
|
+
|
|
705
|
+
this.lastStoredId = newId
|
|
706
|
+
this.lastStoredProject = effectiveProject
|
|
707
|
+
this.searchEngine.invalidateCache(effectiveProject)
|
|
708
|
+
|
|
709
|
+
return {
|
|
710
|
+
action: 'stored',
|
|
711
|
+
summary: `Updated decision`,
|
|
712
|
+
content: `Previous decision replaced with:\n\n**Project:** ${effectiveProject}\n**New content:** ${message}`,
|
|
713
|
+
relevantItems: 1
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
// Fallback: store as new decision
|
|
718
|
+
return this.storeAsNew(memory, effectiveProject, message, topic, entities, 'No matching decision found to update')
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
/**
|
|
722
|
+
* Phase 19 D4: Delete with higher similarity threshold
|
|
723
|
+
*/
|
|
724
|
+
private async handleDeleteMemory(
|
|
725
|
+
message: string,
|
|
726
|
+
project: string | undefined,
|
|
727
|
+
entities: BrainExtractedEntities
|
|
728
|
+
): Promise<BrainResponse> {
|
|
729
|
+
const effectiveProject = project || this.lastStoredProject || DEFAULT_PROJECT
|
|
730
|
+
|
|
731
|
+
if (!isServicesInitialized()) {
|
|
732
|
+
return this.servicesNotReady()
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
const memory = getMemoryService()
|
|
736
|
+
const topic = entities.topic || message
|
|
737
|
+
const hasSpecificContent = this.hasDescriptiveContent(message)
|
|
738
|
+
|
|
739
|
+
if (hasSpecificContent) {
|
|
740
|
+
try {
|
|
741
|
+
const results = await memory.searchRaw(topic, {
|
|
742
|
+
project: effectiveProject,
|
|
743
|
+
limit: 3,
|
|
744
|
+
minSimilarity: DELETE_MIN_SIMILARITY
|
|
745
|
+
})
|
|
746
|
+
|
|
747
|
+
const matchId = results[0]?.memory?.id || results[0]?.decision?.id || results[0]?.id
|
|
748
|
+
const matchSimilarity = results[0]?.similarity || 0
|
|
749
|
+
|
|
750
|
+
if (results.length > 0 && matchId && matchSimilarity >= DELETE_MIN_SIMILARITY) {
|
|
751
|
+
const targetId = matchId
|
|
752
|
+
const content = results[0].decision?.decision || results[0].memory?.content?.slice(0, 100) || results[0].content?.slice(0, 100) || ''
|
|
753
|
+
|
|
754
|
+
await memory.deleteDecision(targetId)
|
|
755
|
+
if (this.lastStoredId === targetId) this.lastStoredId = null
|
|
756
|
+
|
|
757
|
+
// Invalidate cache
|
|
758
|
+
this.searchEngine.invalidateCache(effectiveProject)
|
|
759
|
+
|
|
760
|
+
return {
|
|
761
|
+
action: 'stored',
|
|
762
|
+
summary: `Deleted memory`,
|
|
763
|
+
content: `Deleted: "${content.slice(0, 100)}" (ID: ${targetId})`,
|
|
764
|
+
relevantItems: 0
|
|
765
|
+
}
|
|
766
|
+
} else if (results.length > 0 && matchSimilarity < DELETE_MIN_SIMILARITY) {
|
|
767
|
+
// D4: No confident match
|
|
768
|
+
return {
|
|
769
|
+
action: 'none',
|
|
770
|
+
summary: 'No confident match to delete',
|
|
771
|
+
content: `Found a possible match but similarity (${Math.round(matchSimilarity * 100)}%) is too low for safe deletion. Try being more specific about what to delete.`,
|
|
772
|
+
relevantItems: 0
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
} catch {
|
|
776
|
+
// Search failed, fall through
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
// Generic request — use lastStoredId
|
|
781
|
+
if (this.lastStoredId) {
|
|
782
|
+
try {
|
|
783
|
+
await memory.deleteDecision(this.lastStoredId)
|
|
784
|
+
const deletedId = this.lastStoredId
|
|
785
|
+
this.lastStoredId = null
|
|
786
|
+
this.searchEngine.invalidateCache(effectiveProject)
|
|
787
|
+
return {
|
|
788
|
+
action: 'stored',
|
|
789
|
+
summary: `Deleted most recent memory`,
|
|
790
|
+
content: `Deleted memory (ID: ${deletedId})`,
|
|
791
|
+
relevantItems: 0
|
|
792
|
+
}
|
|
793
|
+
} catch {
|
|
794
|
+
// Deletion failed
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
return {
|
|
799
|
+
action: 'none',
|
|
800
|
+
summary: 'No matching memory found to delete',
|
|
801
|
+
content: 'Could not find a memory matching your request. Try being more specific about what to delete.',
|
|
802
|
+
relevantItems: 0
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
/**
|
|
807
|
+
* Phase 19 C7+C10+C11: Enhanced question handler
|
|
808
|
+
* - Uses SearchEngine for all searches (hybrid, cached)
|
|
809
|
+
* - Temporal search when temporal signals detected
|
|
810
|
+
* - ChainRetrieval for complex multi-part questions
|
|
811
|
+
* - CrossProject patterns for general/unspecified project
|
|
812
|
+
* - KnowledgeGraph enrichment
|
|
813
|
+
*/
|
|
814
|
+
private async handleQuestion(
|
|
815
|
+
message: string,
|
|
816
|
+
project: string | undefined,
|
|
817
|
+
entities: BrainExtractedEntities,
|
|
818
|
+
classification: ClassificationResult
|
|
819
|
+
): Promise<BrainResponse> {
|
|
820
|
+
if (!isServicesInitialized()) {
|
|
821
|
+
return this.servicesNotReady()
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
const effectiveProject = project || DEFAULT_PROJECT
|
|
825
|
+
const query = entities.topic || message
|
|
826
|
+
const tiers: TierResults[] = []
|
|
827
|
+
const hasTemporal = classification.secondary.includes('exploration') ||
|
|
828
|
+
this.classifier.hasTemporalSignal(message.toLowerCase())
|
|
829
|
+
|
|
830
|
+
// C7: Detect multi-part questions for chain retrieval
|
|
831
|
+
const isComplex = this.isComplexQuestion(message)
|
|
832
|
+
|
|
833
|
+
// Main search — temporal or standard
|
|
834
|
+
let searchResults: NormalizedResult[]
|
|
835
|
+
if (hasTemporal) {
|
|
836
|
+
const { results } = await this.searchEngine.temporalSearch(query, { project: effectiveProject, limit: 5 })
|
|
837
|
+
searchResults = results
|
|
838
|
+
} else if (isComplex) {
|
|
839
|
+
// C7: Try multi-hop chain retrieval
|
|
840
|
+
const chainResult = await this.searchEngine.chainSearch(query, { project: effectiveProject })
|
|
841
|
+
if (chainResult?.allResults?.length) {
|
|
842
|
+
searchResults = chainResult.allResults.map((r: any) => ({
|
|
843
|
+
id: r.id || '',
|
|
844
|
+
content: r.content || '',
|
|
845
|
+
score: r.similarity || 0,
|
|
846
|
+
source: 'decision' as const,
|
|
847
|
+
project: r.metadata?.project || effectiveProject || '',
|
|
848
|
+
date: r.metadata?.created_at || '',
|
|
849
|
+
metadata: r.metadata || {}
|
|
850
|
+
}))
|
|
851
|
+
} else {
|
|
852
|
+
searchResults = await this.searchEngine.enhancedSearch(query, { project: effectiveProject, limit: 5 })
|
|
853
|
+
}
|
|
854
|
+
} else {
|
|
855
|
+
searchResults = await this.searchEngine.enhancedSearch(query, { project: effectiveProject, limit: 5 })
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
if (searchResults.length > 0) {
|
|
859
|
+
tiers.push({
|
|
860
|
+
label: 'Memories',
|
|
861
|
+
results: searchResults.map(r => ({
|
|
862
|
+
content: r.content,
|
|
863
|
+
score: r.score,
|
|
864
|
+
source: r.source === 'decision' ? 'Past Decision' : r.source,
|
|
865
|
+
metadata: r.metadata as Record<string, unknown>
|
|
866
|
+
}))
|
|
867
|
+
})
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
// Parallel: patterns + corrections + graph
|
|
871
|
+
const [patternResults, correctionResults, graphResults] = await Promise.all([
|
|
872
|
+
this.searchEngine.searchPatterns(query, { project: effectiveProject, limit: 3 }),
|
|
873
|
+
this.searchEngine.searchCorrections(query, { project: effectiveProject, limit: 3 }),
|
|
874
|
+
// C11: KnowledgeGraph enrichment for all questions
|
|
875
|
+
this.searchEngine.searchGraph(query, 5)
|
|
876
|
+
])
|
|
877
|
+
|
|
878
|
+
if (patternResults.length > 0) {
|
|
879
|
+
tiers.push({
|
|
880
|
+
label: 'Patterns',
|
|
881
|
+
results: patternResults.map(p => ({
|
|
882
|
+
content: p.content,
|
|
883
|
+
score: p.score,
|
|
884
|
+
source: `Pattern`,
|
|
885
|
+
metadata: p.metadata as Record<string, unknown>
|
|
886
|
+
}))
|
|
887
|
+
})
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
if (correctionResults.length > 0) {
|
|
891
|
+
tiers.push({
|
|
892
|
+
label: 'Corrections',
|
|
893
|
+
results: correctionResults.map(c => ({
|
|
894
|
+
content: c.content,
|
|
895
|
+
score: c.score,
|
|
896
|
+
source: 'Lesson Learned',
|
|
897
|
+
metadata: c.metadata as Record<string, unknown>
|
|
898
|
+
}))
|
|
899
|
+
})
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
// C11: Add graph results as "Related Concepts"
|
|
903
|
+
if (graphResults.length > 0) {
|
|
904
|
+
tiers.push({
|
|
905
|
+
label: 'Related Concepts',
|
|
906
|
+
results: graphResults.map(g => ({
|
|
907
|
+
content: g.content,
|
|
908
|
+
score: g.score,
|
|
909
|
+
source: 'Knowledge Graph',
|
|
910
|
+
metadata: g.metadata as Record<string, unknown>
|
|
911
|
+
}))
|
|
912
|
+
})
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
// C10: CrossProject patterns for general/unspecified project queries
|
|
916
|
+
if (!project || project === 'general') {
|
|
917
|
+
try {
|
|
918
|
+
const crossProject = await this.searchEngine.findCrossProjectPatterns({
|
|
919
|
+
query,
|
|
920
|
+
limit: 3
|
|
921
|
+
})
|
|
922
|
+
if (crossProject?.patterns?.length) {
|
|
923
|
+
tiers.push({
|
|
924
|
+
label: 'Cross-Project Patterns',
|
|
925
|
+
results: crossProject.patterns.map((p: any) => ({
|
|
926
|
+
content: `${p.description || ''} (${p.projects?.join(', ') || 'multiple projects'})`,
|
|
927
|
+
score: p.confidence || 0.5,
|
|
928
|
+
source: 'Cross-Project',
|
|
929
|
+
metadata: {}
|
|
930
|
+
}))
|
|
931
|
+
})
|
|
932
|
+
}
|
|
933
|
+
} catch {
|
|
934
|
+
// Cross-project not available
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
// Phase 23b Fix 5: Session recall — "what have we discussed" queries session tracker
|
|
939
|
+
if (this.isSessionRecallQuery(message)) {
|
|
940
|
+
try {
|
|
941
|
+
const tracker = getSessionTracker()
|
|
942
|
+
if (tracker) {
|
|
943
|
+
const stats = tracker.getStats()
|
|
944
|
+
if (stats.totalItems > 0) {
|
|
945
|
+
tiers.push({
|
|
946
|
+
label: 'Current Session',
|
|
947
|
+
results: [{
|
|
948
|
+
content: `Active sessions: ${stats.activeSessions}, items tracked: ${stats.totalItems}`,
|
|
949
|
+
score: 0.95,
|
|
950
|
+
source: 'Session Tracker',
|
|
951
|
+
metadata: {}
|
|
952
|
+
}]
|
|
953
|
+
})
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
} catch {
|
|
957
|
+
// Session tracker not available
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
// Register with episode
|
|
962
|
+
this.registerEpisodeMessage(message, effectiveProject, 'question')
|
|
963
|
+
|
|
964
|
+
return this.responseFilter.synthesize(tiers, message, effectiveProject)
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
/**
|
|
968
|
+
* Phase 19 C6: Enhanced exploration handler
|
|
969
|
+
* - Timeline for "timeline"/"chronological" queries
|
|
970
|
+
* - DecisionEvolutionTracker for "how has X changed" queries
|
|
971
|
+
* - TrendDetector for "trends"/"emerging" queries
|
|
972
|
+
*/
|
|
973
|
+
private async handleExploration(
|
|
974
|
+
message: string,
|
|
975
|
+
project: string | undefined,
|
|
976
|
+
entities: BrainExtractedEntities
|
|
977
|
+
): Promise<BrainResponse> {
|
|
978
|
+
if (!isServicesInitialized()) {
|
|
979
|
+
return this.servicesNotReady()
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
const effectiveProject = project || DEFAULT_PROJECT
|
|
983
|
+
const query = entities.topic || message
|
|
984
|
+
const lower = message.toLowerCase()
|
|
985
|
+
const tiers: TierResults[] = []
|
|
986
|
+
|
|
987
|
+
// C6: Timeline queries
|
|
988
|
+
if (lower.includes('timeline') || lower.includes('chronological') || lower.includes('history of')) {
|
|
989
|
+
const timeline = await this.searchEngine.buildTimeline({
|
|
990
|
+
project: effectiveProject,
|
|
991
|
+
topic: query,
|
|
992
|
+
limit: 20
|
|
993
|
+
})
|
|
994
|
+
|
|
995
|
+
if (timeline?.entries?.length) {
|
|
996
|
+
tiers.push({
|
|
997
|
+
label: 'Timeline',
|
|
998
|
+
results: timeline.entries.map((e: any) => ({
|
|
999
|
+
content: `**${e.date || ''}** — ${e.content || ''}`,
|
|
1000
|
+
score: 0.8,
|
|
1001
|
+
source: `Timeline (${e.type || 'event'})`,
|
|
1002
|
+
metadata: e.metadata || {}
|
|
1003
|
+
}))
|
|
1004
|
+
})
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
// C6: Evolution queries
|
|
1009
|
+
if (lower.includes('evolution') || lower.includes('how has') || lower.includes('changed') || lower.includes('evolved')) {
|
|
1010
|
+
const evolution = await this.searchEngine.analyzeEvolution(query, { project: effectiveProject })
|
|
1011
|
+
if (evolution?.timeline?.length) {
|
|
1012
|
+
const parts = []
|
|
1013
|
+
parts.push(`**Stability:** ${evolution.stability || 'unknown'}`)
|
|
1014
|
+
if (evolution.currentState) parts.push(`**Current:** ${evolution.currentState}`)
|
|
1015
|
+
for (const change of (evolution.changes || []).slice(0, 5)) {
|
|
1016
|
+
parts.push(`- ${change.description || change}`)
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
tiers.push({
|
|
1020
|
+
label: 'Decision Evolution',
|
|
1021
|
+
results: [{
|
|
1022
|
+
content: parts.join('\n'),
|
|
1023
|
+
score: 0.8,
|
|
1024
|
+
source: 'Evolution Analysis',
|
|
1025
|
+
metadata: {}
|
|
1026
|
+
}]
|
|
1027
|
+
})
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
// C6: Trend queries
|
|
1032
|
+
if (lower.includes('trend') || lower.includes('emerging') || lower.includes('declining')) {
|
|
1033
|
+
const trends = await this.searchEngine.detectTrends({ project: effectiveProject })
|
|
1034
|
+
if (trends?.topTrends?.length) {
|
|
1035
|
+
tiers.push({
|
|
1036
|
+
label: 'Trends',
|
|
1037
|
+
results: trends.topTrends.slice(0, 5).map((t: any) => ({
|
|
1038
|
+
content: `**${t.term}** — ${t.trend} (${t.occurrences} occurrences, momentum: ${t.momentum || 'unknown'})`,
|
|
1039
|
+
score: t.occurrences > 5 ? 0.9 : 0.7,
|
|
1040
|
+
source: `Trend (${t.trend})`,
|
|
1041
|
+
metadata: {}
|
|
1042
|
+
}))
|
|
1043
|
+
})
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
// Always include graph exploration
|
|
1048
|
+
const graphResults = await this.searchEngine.searchGraph(query, 10)
|
|
1049
|
+
if (graphResults.length > 0) {
|
|
1050
|
+
tiers.push({
|
|
1051
|
+
label: 'Knowledge Graph',
|
|
1052
|
+
results: graphResults.map(g => ({
|
|
1053
|
+
content: g.content,
|
|
1054
|
+
score: g.score,
|
|
1055
|
+
source: 'Knowledge Graph',
|
|
1056
|
+
metadata: g.metadata as Record<string, unknown>
|
|
1057
|
+
}))
|
|
1058
|
+
})
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
// Issue 9: Cross-project patterns for exploration queries
|
|
1062
|
+
if (!project || project === 'general') {
|
|
1063
|
+
try {
|
|
1064
|
+
const crossProject = await this.searchEngine.findCrossProjectPatterns({
|
|
1065
|
+
query,
|
|
1066
|
+
limit: 3
|
|
1067
|
+
})
|
|
1068
|
+
if (crossProject?.patterns?.length) {
|
|
1069
|
+
tiers.push({
|
|
1070
|
+
label: 'Cross-Project Patterns',
|
|
1071
|
+
results: crossProject.patterns.map((p: any) => ({
|
|
1072
|
+
content: `${p.description || ''} (${p.projects?.join(', ') || 'multiple projects'})`,
|
|
1073
|
+
score: p.confidence || 0.5,
|
|
1074
|
+
source: 'Cross-Project',
|
|
1075
|
+
metadata: {}
|
|
1076
|
+
}))
|
|
1077
|
+
})
|
|
1078
|
+
}
|
|
1079
|
+
} catch {
|
|
1080
|
+
// Cross-project not available
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
// Also do a basic memory search as fallback
|
|
1085
|
+
const searchResults = await this.searchEngine.enhancedSearch(query, {
|
|
1086
|
+
project: effectiveProject,
|
|
1087
|
+
limit: 5,
|
|
1088
|
+
minSimilarity: 0.2
|
|
1089
|
+
})
|
|
1090
|
+
if (searchResults.length > 0) {
|
|
1091
|
+
tiers.push({
|
|
1092
|
+
label: 'Memories',
|
|
1093
|
+
results: searchResults.map(r => ({
|
|
1094
|
+
content: r.content,
|
|
1095
|
+
score: r.score,
|
|
1096
|
+
source: r.source === 'decision' ? 'Past Decision' : r.source,
|
|
1097
|
+
metadata: r.metadata as Record<string, unknown>
|
|
1098
|
+
}))
|
|
1099
|
+
})
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
return this.responseFilter.synthesize(tiers, message, effectiveProject, 'analyzed')
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
private async handleComparison(
|
|
1106
|
+
message: string,
|
|
1107
|
+
project: string | undefined,
|
|
1108
|
+
entities: BrainExtractedEntities
|
|
1109
|
+
): Promise<BrainResponse> {
|
|
1110
|
+
if (!isServicesInitialized()) {
|
|
1111
|
+
return this.servicesNotReady()
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
const effectiveProject = project || DEFAULT_PROJECT
|
|
1115
|
+
const query = entities.topic || message
|
|
1116
|
+
const tiers: TierResults[] = []
|
|
1117
|
+
|
|
1118
|
+
// Search for related decisions
|
|
1119
|
+
const searchResults = await this.searchEngine.enhancedSearch(query, {
|
|
1120
|
+
project: effectiveProject,
|
|
1121
|
+
limit: 5,
|
|
1122
|
+
minSimilarity: 0.2
|
|
1123
|
+
})
|
|
1124
|
+
if (searchResults.length > 0) {
|
|
1125
|
+
tiers.push({
|
|
1126
|
+
label: 'Related Decisions',
|
|
1127
|
+
results: searchResults.map(r => ({
|
|
1128
|
+
content: r.content,
|
|
1129
|
+
score: r.score,
|
|
1130
|
+
source: 'Past Decision',
|
|
1131
|
+
metadata: r.metadata as Record<string, unknown>
|
|
1132
|
+
}))
|
|
1133
|
+
})
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
// Graph search for comparison context
|
|
1137
|
+
const graphResults = await this.searchEngine.searchGraph(query, 5)
|
|
1138
|
+
if (graphResults.length > 0) {
|
|
1139
|
+
tiers.push({
|
|
1140
|
+
label: 'Knowledge Graph',
|
|
1141
|
+
results: graphResults.map(g => ({
|
|
1142
|
+
content: g.content,
|
|
1143
|
+
score: g.score,
|
|
1144
|
+
source: 'Knowledge Graph',
|
|
1145
|
+
metadata: g.metadata as Record<string, unknown>
|
|
1146
|
+
}))
|
|
1147
|
+
})
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
return this.responseFilter.synthesize(tiers, message, effectiveProject, 'analyzed')
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
// ===== Helpers =====
|
|
1154
|
+
|
|
1155
|
+
private async writeToVault(
|
|
1156
|
+
project: string,
|
|
1157
|
+
decision: string,
|
|
1158
|
+
reasoning: string,
|
|
1159
|
+
context: string,
|
|
1160
|
+
alternatives?: string
|
|
1161
|
+
): Promise<void> {
|
|
1162
|
+
try {
|
|
1163
|
+
const vault = getVaultService()
|
|
1164
|
+
const projectPaths = vault.getProjectPaths(project)
|
|
1165
|
+
const date = new Date().toISOString().split('T')[0]
|
|
1166
|
+
const entry = `### Decision: ${decision.slice(0, 100)}\n\n**Date:** ${date}\n**Context:** ${context}\n**Decision:** ${decision}\n**Reasoning:** ${reasoning}\n${alternatives ? `**Alternatives:** ${alternatives}\n` : ''}\n---\n\n`
|
|
1167
|
+
await vault.writer.appendContent(projectPaths.decisions, entry, '\n')
|
|
1168
|
+
} catch {
|
|
1169
|
+
// Vault write failed
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
/**
|
|
1174
|
+
* Store as a new decision (fallback for update when no match found)
|
|
1175
|
+
*/
|
|
1176
|
+
private async storeAsNew(
|
|
1177
|
+
memory: any,
|
|
1178
|
+
project: string,
|
|
1179
|
+
message: string,
|
|
1180
|
+
topic: string,
|
|
1181
|
+
entities: BrainExtractedEntities,
|
|
1182
|
+
reason: string
|
|
1183
|
+
): Promise<BrainResponse> {
|
|
1184
|
+
const decisionId = await memory.rememberDecision(
|
|
1185
|
+
project,
|
|
1186
|
+
topic.slice(0, 200),
|
|
1187
|
+
message,
|
|
1188
|
+
entities.reasoning || '',
|
|
1189
|
+
{ tags: entities.technologies.length > 0 ? entities.technologies : ['updated'] }
|
|
1190
|
+
)
|
|
1191
|
+
|
|
1192
|
+
this.lastStoredId = decisionId
|
|
1193
|
+
this.lastStoredProject = project
|
|
1194
|
+
this.searchEngine.invalidateCache(project)
|
|
1195
|
+
|
|
1196
|
+
return {
|
|
1197
|
+
action: 'stored',
|
|
1198
|
+
summary: `Stored as new (${reason})`,
|
|
1199
|
+
content: `Stored as new decision (ID: ${decisionId})\n\n**Project:** ${project}\n**Content:** ${message}`,
|
|
1200
|
+
relevantItems: 1
|
|
1201
|
+
}
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
/**
|
|
1205
|
+
* C8: Register a message with the episode manager
|
|
1206
|
+
*/
|
|
1207
|
+
private registerEpisodeMessage(message: string, project?: string, role: string = 'user'): void {
|
|
1208
|
+
try {
|
|
1209
|
+
const episodeManager = getEpisodeService()
|
|
1210
|
+
if (!episodeManager) return
|
|
1211
|
+
episodeManager.processMessage(
|
|
1212
|
+
{ role, content: message, timestamp: new Date().toISOString() },
|
|
1213
|
+
project
|
|
1214
|
+
).catch(() => {})
|
|
1215
|
+
} catch {
|
|
1216
|
+
// Episode manager not available
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
/**
|
|
1221
|
+
* C8: Link a stored decision/pattern/correction to the active episode
|
|
1222
|
+
*/
|
|
1223
|
+
private linkToActiveEpisode(project: string, id: string, type: 'decision' | 'pattern' | 'correction'): void {
|
|
1224
|
+
try {
|
|
1225
|
+
const episodeManager = getEpisodeService()
|
|
1226
|
+
if (!episodeManager) return
|
|
1227
|
+
const activeEpisode = episodeManager.getActiveEpisode(project)
|
|
1228
|
+
if (!activeEpisode) return
|
|
1229
|
+
|
|
1230
|
+
if (type === 'decision') episodeManager.linkDecision(activeEpisode.id, id)
|
|
1231
|
+
else if (type === 'pattern') episodeManager.linkPattern(activeEpisode.id, id)
|
|
1232
|
+
else if (type === 'correction') episodeManager.linkCorrection(activeEpisode.id, id)
|
|
1233
|
+
} catch {
|
|
1234
|
+
// Episode manager not available
|
|
1235
|
+
}
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
/**
|
|
1239
|
+
* Phase 23b: Detect session recall queries ("what have we discussed", "this session", etc.)
|
|
1240
|
+
*/
|
|
1241
|
+
private isSessionRecallQuery(message: string): boolean {
|
|
1242
|
+
const lower = message.toLowerCase()
|
|
1243
|
+
const SESSION_RECALL_PHRASES = [
|
|
1244
|
+
'what have we discussed',
|
|
1245
|
+
'what did we discuss',
|
|
1246
|
+
'what have we talked about',
|
|
1247
|
+
'what did we talk about',
|
|
1248
|
+
'this session',
|
|
1249
|
+
'current session',
|
|
1250
|
+
'session so far',
|
|
1251
|
+
'what have we done',
|
|
1252
|
+
'what did we do',
|
|
1253
|
+
'session summary',
|
|
1254
|
+
'summarize this session',
|
|
1255
|
+
'recap this session',
|
|
1256
|
+
'what happened this session',
|
|
1257
|
+
'what have we covered'
|
|
1258
|
+
]
|
|
1259
|
+
return SESSION_RECALL_PHRASES.some(p => lower.includes(p))
|
|
1260
|
+
}
|
|
1261
|
+
|
|
1262
|
+
/**
|
|
1263
|
+
* C7: Detect complex multi-part questions
|
|
1264
|
+
*/
|
|
1265
|
+
private isComplexQuestion(message: string): boolean {
|
|
1266
|
+
// Multiple question marks
|
|
1267
|
+
const questionMarks = (message.match(/\?/g) || []).length
|
|
1268
|
+
if (questionMarks >= 2) return true
|
|
1269
|
+
// Very long question (likely multi-part)
|
|
1270
|
+
if (message.length > 200 && message.includes('?')) return true
|
|
1271
|
+
// Multiple clauses with "and" or "also"
|
|
1272
|
+
if (message.includes(' and ') && message.includes('?') && message.length > 100) return true
|
|
1273
|
+
return false
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1276
|
+
private servicesNotReady(): BrainResponse {
|
|
1277
|
+
return {
|
|
1278
|
+
action: 'none',
|
|
1279
|
+
summary: 'Services not initialized',
|
|
1280
|
+
content: 'Claude Brain services are not ready. The server may still be starting up.',
|
|
1281
|
+
relevantItems: 0
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
/**
|
|
1286
|
+
* Check if a delete/update message has descriptive content beyond just the command words.
|
|
1287
|
+
*/
|
|
1288
|
+
private hasDescriptiveContent(message: string): boolean {
|
|
1289
|
+
const COMMAND_WORDS = [
|
|
1290
|
+
'forget', 'delete', 'remove', 'discard', 'erase', 'undo', 'clear', 'drop',
|
|
1291
|
+
'actually', 'correction', 'update', 'change', 'replace', 'modify', 'revise',
|
|
1292
|
+
'amend', 'override', 'scratch', 'no', 'wait', 'instead',
|
|
1293
|
+
'that', 'this', 'the', 'it', 'about', 'memory', 'decision', 'to', 'a', 'an'
|
|
1294
|
+
]
|
|
1295
|
+
const words = message.toLowerCase().split(/[\s,;:.!?]+/).filter(w => w.length > 1)
|
|
1296
|
+
const meaningfulWords = words.filter(w => !COMMAND_WORDS.includes(w))
|
|
1297
|
+
return meaningfulWords.length >= 2
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1300
|
+
private generateTaskId(title: string): string {
|
|
1301
|
+
return title
|
|
1302
|
+
.toLowerCase()
|
|
1303
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
1304
|
+
.replace(/^-+|-+$/g, '')
|
|
1305
|
+
.slice(0, 50)
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
// Lazy singleton
|
|
1310
|
+
let routerInstance: BrainRouter | null = null
|
|
1311
|
+
|
|
1312
|
+
export function getBrainRouter(logger: Logger): BrainRouter {
|
|
1313
|
+
if (!routerInstance) {
|
|
1314
|
+
routerInstance = new BrainRouter(logger)
|
|
1315
|
+
}
|
|
1316
|
+
return routerInstance
|
|
1317
|
+
}
|
|
1318
|
+
|
|
1319
|
+
/** Reset singleton for testing */
|
|
1320
|
+
export function _resetBrainRouterForTesting(): void {
|
|
1321
|
+
routerInstance = null
|
|
1322
|
+
}
|